protected static bool CanExtract(IResourceAccessor mediaItemAccessor, IDictionary <Guid, IList <MediaItemAspect> > extractedAspectData, out IResourceAccessor metaFileAccessor) { metaFileAccessor = null; IFileSystemResourceAccessor fsra = mediaItemAccessor as IFileSystemResourceAccessor; if (fsra == null || !fsra.IsFile) { return(false); } string title; if (!MediaItemAspect.TryGetAttribute(extractedAspectData, MediaAspect.ATTR_TITLE, out title) || string.IsNullOrEmpty(title)) { return(false); } string filePath = mediaItemAccessor.CanonicalLocalResourcePath.ToString(); string lowerExtension = StringUtils.TrimToEmpty(ProviderPathHelper.GetExtension(filePath)).ToLowerInvariant(); if (lowerExtension != ".ts") { return(false); } string metaFilePath = ProviderPathHelper.ChangeExtension(filePath, ".arg"); if (!ResourcePath.Deserialize(metaFilePath).TryCreateLocalResourceAccessor(out metaFileAccessor)) { return(false); } return(true); }
public override void Update(MediaItem mediaItem) { base.Update(mediaItem); SingleMediaItemAspect imageAspect; if (MediaItemAspect.TryGetAspect(mediaItem.Aspects, ImageAspect.Metadata, out imageAspect)) { SimpleTitle = Title; int?width = (int?)imageAspect[ImageAspect.ATTR_WIDTH]; int?height = (int?)imageAspect[ImageAspect.ATTR_HEIGHT]; if (width.HasValue && width.Value > 0 && height.HasValue && height.Value > 0) { Width = width; Height = height; Size = width + " x " + height; } } IList <MultipleMediaItemAspect> resourceAspects; if (MediaItemAspect.TryGetAspects(mediaItem.Aspects, ProviderResourceAspect.Metadata, out resourceAspects)) { ResourcePath rp = ResourcePath.Deserialize((string)resourceAspects[0][ProviderResourceAspect.ATTR_RESOURCE_ACCESSOR_PATH]); string ext = ProviderPathHelper.GetExtension(rp.FileName); if (ext.Length > 1) { // remove leading '.' ext = ext.Substring(1); } Extension = ext; MimeType = (string)resourceAspects[0][ProviderResourceAspect.ATTR_MIME_TYPE]; } FireChange(); }
protected bool TryGetConfiguration(MediaItem mediaItem, out EmulatorConfiguration configuration) { configuration = null; List <string> mimeTypes; string mimeType; if (mediaItem == null || !MediaItemAspect.TryGetAttribute(mediaItem.Aspects, ProviderResourceAspect.ATTR_MIME_TYPE, out mimeTypes) || string.IsNullOrEmpty(mimeType = mimeTypes.First())) { return(false); } List <string> paths; string path; if (!MediaItemAspect.TryGetAttribute(mediaItem.Aspects, ProviderResourceAspect.ATTR_RESOURCE_ACCESSOR_PATH, out paths) || string.IsNullOrEmpty(path = paths.First())) { return(false); } ResourcePath rp = ResourcePath.Deserialize(path); string ext = ProviderPathHelper.GetExtension(rp.FileName); return(ServiceRegistration.Get <IEmulatorManager>().TryGetConfiguration(mimeType, ext, out configuration)); }
public bool ResourceExists(string path) { if (string.IsNullOrEmpty(path)) { return(false); } path = ProviderPathHelper.Combine(_path, path); return(_provider.IsResource(path)); }
public IFileSystemResourceAccessor GetResource(string path) { IResourceAccessor result; if (_provider.TryCreateResourceAccessor(ProviderPathHelper.Combine(_path, path), out result)) { return((IFileSystemResourceAccessor)result); } return(null); }
public IFileSystemResourceAccessor GetResource(string path) { IFileSystemResourceAccessor ra = (IFileSystemResourceAccessor)_mountingDataProxy.ResourceAccessor.Clone(); try { return(GetLocalFsResourceAccessor(ra, ProviderPathHelper.Combine(_path, path))); } catch { ra.Dispose(); throw; } }
public static Type GetPlayerTypeForMediaItem(IResourceLocator locator, string mimeType) { Type playerType; // When a mimetype is set, try to get the player type for it if (mimeType != null && MIMETYPES2PLAYER.TryGetValue(mimeType.ToLowerInvariant(), out playerType)) { return(playerType); } // 2nd chance: If no mimetype matches, try extension string path = locator.NativeResourcePath.LastPathSegment.Path; string extension = StringUtils.TrimToEmpty(ProviderPathHelper.GetExtension(path)).ToLowerInvariant(); if (EXTENSIONS2PLAYER.TryGetValue(extension, out playerType)) { return(playerType); } return(null); }
protected static bool CanExtract(ILocalFsResourceAccessor lfsra, IDictionary <Guid, IList <MediaItemAspect> > extractedAspectData) { if (lfsra == null || !lfsra.IsFile) { return(false); } string title; if (!MediaItemAspect.TryGetAttribute(extractedAspectData, MediaAspect.ATTR_TITLE, out title) || string.IsNullOrEmpty(title)) { return(false); } string filePath = lfsra.CanonicalLocalResourcePath.ToString(); string lowerExtension = StringUtils.TrimToEmpty(ProviderPathHelper.GetExtension(filePath)).ToLowerInvariant(); if (lowerExtension != ".wtv" && lowerExtension != ".dvr-ms") { return(false); } return(true); }
public ImageItem(MediaItem mediaItem) : base(mediaItem) { MediaItemAspect imageAspect; if (mediaItem.Aspects.TryGetValue(ImageAspect.ASPECT_ID, out imageAspect)) { SimpleTitle = Title; int?width = (int?)imageAspect[ImageAspect.ATTR_WIDTH]; int?height = (int?)imageAspect[ImageAspect.ATTR_HEIGHT]; if (width.HasValue && width.Value > 0 && height.HasValue && height.Value > 0) { Width = width; Height = height; Size = width + " x " + height; } } MediaItemAspect resourceAspect; if (mediaItem.Aspects.TryGetValue(ProviderResourceAspect.ASPECT_ID, out resourceAspect)) { ResourcePath rp = ResourcePath.Deserialize((string)resourceAspect[ProviderResourceAspect.ATTR_RESOURCE_ACCESSOR_PATH]); string ext = ProviderPathHelper.GetExtension(rp.FileName); if (ext.Length > 1) { // remove leading '.' ext = ext.Substring(1); } Extension = ext; } MediaItemAspect mediaAspect; if (mediaItem.Aspects.TryGetValue(MediaAspect.ASPECT_ID, out mediaAspect)) { MimeType = (string)mediaAspect[MediaAspect.ATTR_MIME_TYPE]; } }
protected static bool CanExtract(IResourceAccessor mediaItemAccessor, IDictionary <Guid, IList <MediaItemAspect> > extractedAspectData) { IFileSystemResourceAccessor fsra = mediaItemAccessor as IFileSystemResourceAccessor; if (fsra == null || !fsra.IsFile) { return(false); } string filePath = mediaItemAccessor.CanonicalLocalResourcePath.ToString(); string lowerExtension = StringUtils.TrimToEmpty(ProviderPathHelper.GetExtension(filePath)).ToLowerInvariant(); if (lowerExtension != ".ts") { return(false); } string metaFilePath = ProviderPathHelper.ChangeExtension(filePath, ".xml"); if (!ResourcePath.Deserialize(metaFilePath).TryCreateLocalResourceAccessor(out _)) { return(false); } return(true); }
public bool TryExtractMetadata(IResourceAccessor mediaItemAccessor, IDictionary <Guid, IList <MediaItemAspect> > extractedAspectData, bool importOnly, bool forceQuickMode) { IFileSystemResourceAccessor fsra = mediaItemAccessor as IFileSystemResourceAccessor; if (fsra == null || !fsra.IsFile) { return(false); } if (extractedAspectData.ContainsKey(AudioAspect.ASPECT_ID)) { return(false); } try { var extension = DosPathHelper.GetExtension(fsra.ResourceName).ToLowerInvariant(); if (extension != ".ts") { return(false); } using (MediaInfoWrapper mediaInfo = ReadMediaInfo(fsra)) { // Before we start evaluating the file, check if it is not a video file ( if (mediaInfo.IsValid && (mediaInfo.GetVideoCount() != 0 || mediaInfo.GetAudioCount() == 0)) { return(false); } string fileName = ProviderPathHelper.GetFileNameWithoutExtension(fsra.Path) ?? string.Empty; MediaItemAspect.SetAttribute(extractedAspectData, MediaAspect.ATTR_TITLE, fileName); MultipleMediaItemAspect providerResourceAspect = MediaItemAspect.CreateAspect(extractedAspectData, ProviderResourceAspect.Metadata); providerResourceAspect.SetAttribute(ProviderResourceAspect.ATTR_SIZE, fsra.Size); providerResourceAspect.SetAttribute(ProviderResourceAspect.ATTR_MIME_TYPE, "slimtv/radio"); var audioBitrate = mediaInfo.GetAudioBitrate(0); if (audioBitrate.HasValue) { MediaItemAspect.SetAttribute(extractedAspectData, AudioAspect.ATTR_BITRATE, (int)(audioBitrate.Value / 1000)); // We store kbit/s; } var audioChannels = mediaInfo.GetAudioChannels(0); if (audioChannels.HasValue) { MediaItemAspect.SetAttribute(extractedAspectData, AudioAspect.ATTR_CHANNELS, audioChannels.Value); } var audioSampleRate = mediaInfo.GetAudioSampleRate(0); if (audioSampleRate.HasValue) { MediaItemAspect.SetAttribute(extractedAspectData, AudioAspect.ATTR_SAMPLERATE, audioSampleRate.Value); } MediaItemAspect.SetAttribute(extractedAspectData, AudioAspect.ATTR_ENCODING, mediaInfo.GetAudioCodec(0)); // MediaInfo returns milliseconds, we need seconds long?time = mediaInfo.GetPlaytime(0); if (time.HasValue && time > 1000) { MediaItemAspect.SetAttribute(extractedAspectData, AudioAspect.ATTR_DURATION, time.Value / 1000); } } return(true); } catch (Exception e) { // Only log at the info level here - And simply return false. This makes the importer know that we // couldn't perform our task here ServiceRegistration.Get <ILogger>().Info("RadioRecordingMetadataExtractor: Exception reading resource '{0}' (Text: '{1}')", fsra.CanonicalLocalResourcePath, e.Message); return(false); } }
public bool TryExtractMetadata(IResourceAccessor mediaItemAccessor, IDictionary <Guid, MediaItemAspect> extractedAspectData, bool forceQuickMode) { IFileSystemResourceAccessor fsra = mediaItemAccessor as IFileSystemResourceAccessor; if (fsra == null) { return(false); } if (!fsra.IsFile) { return(false); } string fileName = fsra.ResourceName; if (!HasAudioExtension(fileName)) { return(false); } try { File tag; try { ByteVector.UseBrokenLatin1Behavior = true; // Otherwise we have problems retrieving non-latin1 chars tag = File.Create(new ResourceProviderFileAbstraction(fsra)); } catch (CorruptFileException) { // Only log at the info level here - And simply return false. This makes the importer know that we // couldn't perform our task here. ServiceRegistration.Get <ILogger>().Info("AudioMetadataExtractor: Audio file '{0}' seems to be broken", fsra.CanonicalLocalResourcePath); return(false); } // Some file extensions like .mp4 can contain audio and video. Do not handle files with video content here. if (tag.Properties.VideoHeight > 0 && tag.Properties.VideoWidth > 0) { return(false); } fileName = ProviderPathHelper.GetFileNameWithoutExtension(fileName) ?? string.Empty; string title; string artist; uint? trackNo; GuessMetadataFromFileName(fileName, out title, out artist, out trackNo); if (!string.IsNullOrEmpty(tag.Tag.Title)) { title = tag.Tag.Title; } IEnumerable <string> artists; if (tag.Tag.Performers.Length > 0) { artists = tag.Tag.Performers; if ((tag.TagTypes & TagTypes.Id3v2) != 0) { artists = PatchID3v23Enumeration(artists); } } else { artists = artist == null ? null : new string[] { artist } }; if (tag.Tag.Track != 0) { trackNo = tag.Tag.Track; } MediaItemAspect.SetAttribute(extractedAspectData, MediaAspect.ATTR_TITLE, title); MediaItemAspect.SetAttribute(extractedAspectData, MediaAspect.ATTR_SIZE, fsra.Size); // FIXME Albert: tag.MimeType returns taglib/mp3 for an MP3 file. This is not what we want and collides with the // mimetype handling in the BASS player, which expects audio/xxx. if (!string.IsNullOrWhiteSpace(tag.MimeType)) { MediaItemAspect.SetAttribute(extractedAspectData, MediaAspect.ATTR_MIME_TYPE, tag.MimeType.Replace("taglib/", "audio/")); } MediaItemAspect.SetCollectionAttribute(extractedAspectData, AudioAspect.ATTR_ARTISTS, ApplyAdditionalSeparator(artists)); MediaItemAspect.SetAttribute(extractedAspectData, AudioAspect.ATTR_ALBUM, StringUtils.TrimToNull(tag.Tag.Album)); IEnumerable <string> albumArtists = tag.Tag.AlbumArtists; if ((tag.TagTypes & TagTypes.Id3v2) != 0) { albumArtists = PatchID3v23Enumeration(albumArtists); } MediaItemAspect.SetCollectionAttribute(extractedAspectData, AudioAspect.ATTR_ALBUMARTISTS, ApplyAdditionalSeparator(albumArtists)); MediaItemAspect.SetAttribute(extractedAspectData, AudioAspect.ATTR_BITRATE, tag.Properties.AudioBitrate); MediaItemAspect.SetAttribute(extractedAspectData, MediaAspect.ATTR_COMMENT, StringUtils.TrimToNull(tag.Tag.Comment)); IEnumerable <string> composers = tag.Tag.Composers; if ((tag.TagTypes & TagTypes.Id3v2) != 0) { composers = PatchID3v23Enumeration(composers); } MediaItemAspect.SetCollectionAttribute(extractedAspectData, AudioAspect.ATTR_COMPOSERS, ApplyAdditionalSeparator(composers)); MediaItemAspect.SetAttribute(extractedAspectData, AudioAspect.ATTR_DURATION, (long)tag.Properties.Duration.TotalSeconds); if (tag.Tag.Genres.Length > 0) { IEnumerable <string> genres = tag.Tag.Genres; if ((tag.TagTypes & TagTypes.Id3v2) != 0) { genres = PatchID3v23Enumeration(genres); } MediaItemAspect.SetCollectionAttribute(extractedAspectData, AudioAspect.ATTR_GENRES, ApplyAdditionalSeparator(genres)); } if (trackNo.HasValue) { MediaItemAspect.SetAttribute(extractedAspectData, AudioAspect.ATTR_TRACK, (int)trackNo.Value); } if (tag.Tag.TrackCount != 0) { MediaItemAspect.SetAttribute(extractedAspectData, AudioAspect.ATTR_NUMTRACKS, (int)tag.Tag.TrackCount); } int year = (int)tag.Tag.Year; if (year >= 30 && year <= 99) { year += 1900; } if (year >= 1930 && year <= 2030) { MediaItemAspect.SetAttribute(extractedAspectData, MediaAspect.ATTR_RECORDINGTIME, new DateTime(year, 1, 1)); } // The following code gets cover art images from file (embedded) or from windows explorer cache (supports folder.jpg). IPicture[] pics = tag.Tag.Pictures; if (pics.Length > 0) { try { using (MemoryStream stream = new MemoryStream(pics[0].Data.Data)) using (MemoryStream resized = (MemoryStream)ImageUtilities.ResizeImage(stream, ImageFormat.Jpeg, MAX_COVER_WIDTH, MAX_COVER_HEIGHT)) { MediaItemAspect.SetAttribute(extractedAspectData, ThumbnailLargeAspect.ATTR_THUMBNAIL, resized.ToArray()); } } // Decoding of invalid image data can fail, but main MediaItem is correct. catch { } } else { // In quick mode only allow thumbs taken from cache. bool cachedOnly = forceQuickMode; // Thumbnail extraction fileName = mediaItemAccessor.ResourcePathName; IThumbnailGenerator generator = ServiceRegistration.Get <IThumbnailGenerator>(); byte[] thumbData; ImageType imageType; if (generator.GetThumbnail(fileName, 256, 256, cachedOnly, out thumbData, out imageType)) { MediaItemAspect.SetAttribute(extractedAspectData, ThumbnailLargeAspect.ATTR_THUMBNAIL, thumbData); } } return(true); } catch (UnsupportedFormatException) { ServiceRegistration.Get <ILogger>().Info("AudioMetadataExtractor: Unsupported audio file '{0}'", fsra.CanonicalLocalResourcePath); return(false); } catch (Exception e) { // Only log at the info level here - And simply return false. This makes the importer know that we // couldn't perform our task here ServiceRegistration.Get <ILogger>().Info("AudioMetadataExtractor: Exception reading resource '{0}' (Text: '{1}')", fsra.CanonicalLocalResourcePath, e.Message); } return(false); }
protected ICollection <IFileSystemResourceAccessor> CreateChildResourceAccessors(IEnumerable <string> namesWithPathPrefix, bool isDirectory) { string rootPath = StringUtils.CheckSuffix(_path, "/"); return(namesWithPathPrefix.Select(filePath => new StreamedResourceToLocalFsAccessBridge(_mountingDataProxy, rootPath + ProviderPathHelper.GetFileName(filePath) + (isDirectory ? "/" : string.Empty))).Cast <IFileSystemResourceAccessor>().ToList()); }
protected string ExpandPath(string relativeOrAbsoluteProviderPath) { return(ProviderPathHelper.Combine(_pathToDirOrFile, relativeOrAbsoluteProviderPath)); }
public bool TryExtractMetadata(IResourceAccessor mediaItemAccessor, IDictionary <Guid, MediaItemAspect> extractedAspectData, bool forceQuickMode) { string fileName = mediaItemAccessor.ResourceName; if (!HasImageExtension(fileName)) { return(false); } MediaItemAspect mediaAspect = MediaItemAspect.GetOrCreateAspect(extractedAspectData, MediaAspect.Metadata); MediaItemAspect imageAspect = MediaItemAspect.GetOrCreateAspect(extractedAspectData, ImageAspect.Metadata); MediaItemAspect thumbnailSmallAspect = MediaItemAspect.GetOrCreateAspect(extractedAspectData, ThumbnailSmallAspect.Metadata); MediaItemAspect thumbnailLargeAspect = MediaItemAspect.GetOrCreateAspect(extractedAspectData, ThumbnailLargeAspect.Metadata); try { if (!(mediaItemAccessor is IFileSystemResourceAccessor)) { return(false); } IFileSystemResourceAccessor fsra = mediaItemAccessor as IFileSystemResourceAccessor; // Open a stream for media item to detect mimeType. using (Stream mediaStream = fsra.OpenRead()) { string mimeType = MimeTypeDetector.GetMimeType(mediaStream); if (mimeType != null) { mediaAspect.SetAttribute(MediaAspect.ATTR_MIME_TYPE, mimeType); } } // Extract EXIF information from media item. using (ExifMetaInfo.ExifMetaInfo exif = new ExifMetaInfo.ExifMetaInfo(fsra)) { mediaAspect.SetAttribute(MediaAspect.ATTR_TITLE, ProviderPathHelper.GetFileNameWithoutExtension(fileName)); mediaAspect.SetAttribute(MediaAspect.ATTR_RECORDINGTIME, exif.OriginalDate != DateTime.MinValue ? exif.OriginalDate : fsra.LastChanged); mediaAspect.SetAttribute(MediaAspect.ATTR_COMMENT, StringUtils.TrimToNull(exif.ImageDescription)); if (exif.PixXDim.HasValue) { imageAspect.SetAttribute(ImageAspect.ATTR_WIDTH, (int)exif.PixXDim); } if (exif.PixYDim.HasValue) { imageAspect.SetAttribute(ImageAspect.ATTR_HEIGHT, (int)exif.PixYDim); } imageAspect.SetAttribute(ImageAspect.ATTR_MAKE, StringUtils.TrimToNull(exif.EquipMake)); imageAspect.SetAttribute(ImageAspect.ATTR_MODEL, StringUtils.TrimToNull(exif.EquipModel)); if (exif.ExposureBias.HasValue) { imageAspect.SetAttribute(ImageAspect.ATTR_EXPOSURE_BIAS, ((double)exif.ExposureBias).ToString()); } imageAspect.SetAttribute(ImageAspect.ATTR_EXPOSURE_TIME, exif.ExposureTime); imageAspect.SetAttribute(ImageAspect.ATTR_FLASH_MODE, StringUtils.TrimToNull(exif.FlashMode)); if (exif.FNumber.HasValue) { imageAspect.SetAttribute(ImageAspect.ATTR_FNUMBER, string.Format("F {0}", (double)exif.FNumber)); } imageAspect.SetAttribute(ImageAspect.ATTR_ISO_SPEED, StringUtils.TrimToNull(exif.ISOSpeed)); imageAspect.SetAttribute(ImageAspect.ATTR_ORIENTATION, (Int32)(exif.OrientationType ?? 0)); imageAspect.SetAttribute(ImageAspect.ATTR_METERING_MODE, exif.MeteringMode.ToString()); if (exif.Latitude.HasValue && exif.Longitude.HasValue) { imageAspect.SetAttribute(ImageAspect.ATTR_LATITUDE, exif.Latitude); imageAspect.SetAttribute(ImageAspect.ATTR_LONGITUDE, exif.Longitude); LocationInfo locationInfo; if (!forceQuickMode && GeoLocationMatcher.Instance.TryLookup(exif.Latitude.Value, exif.Longitude.Value, out locationInfo)) { imageAspect.SetAttribute(ImageAspect.ATTR_CITY, locationInfo.City); imageAspect.SetAttribute(ImageAspect.ATTR_STATE, locationInfo.State); imageAspect.SetAttribute(ImageAspect.ATTR_COUNTRY, locationInfo.Country); } } using (ILocalFsResourceAccessor lfsra = StreamedResourceToLocalFsAccessBridge.GetLocalFsResourceAccessor((IFileSystemResourceAccessor)fsra.Clone())) { string localFsResourcePath = lfsra.LocalFileSystemPath; if (localFsResourcePath != null) { // In quick mode only allow thumbs taken from cache. bool cachedOnly = forceQuickMode; // Thumbnail extraction IThumbnailGenerator generator = ServiceRegistration.Get <IThumbnailGenerator>(); byte[] thumbData; ImageType imageType; if (generator.GetThumbnail(localFsResourcePath, 96, 96, cachedOnly, out thumbData, out imageType)) { thumbnailSmallAspect.SetAttribute(ThumbnailSmallAspect.ATTR_THUMBNAIL, thumbData); } if (generator.GetThumbnail(localFsResourcePath, 256, 256, cachedOnly, out thumbData, out imageType)) { thumbnailLargeAspect.SetAttribute(ThumbnailLargeAspect.ATTR_THUMBNAIL, thumbData); } } } } return(true); } catch (Exception e) { // Only log at the info level here - And simply return false. This makes the importer know that we // couldn't perform our task here. ServiceRegistration.Get <ILogger>().Info("ImageMetadataExtractor: Exception reading resource '{0}' (Text: '{1}')", mediaItemAccessor.CanonicalLocalResourcePath, e.Message); } return(false); }
public override bool TryExtractMetadata(IResourceAccessor mediaItemAccessor, IDictionary <Guid, MediaItemAspect> extractedAspectData, bool forceQuickMode) { // If the base AudioMDE already extracted metadata, don't try here again to avoid conflicts. if (extractedAspectData.ContainsKey(AudioAspect.ASPECT_ID)) { return(false); } ILocalFsResourceAccessor fsra = mediaItemAccessor as ILocalFsResourceAccessor; if (fsra == null) { return(false); } if (!fsra.IsFile) { return(false); } string fileName = fsra.ResourceName; if (!HasAudioExtension(fileName)) { return(false); } try { TAG_INFO tags; using (fsra.EnsureLocalFileSystemAccess()) tags = BassTags.BASS_TAG_GetFromFile(fsra.LocalFileSystemPath); if (tags == null) { return(false); } fileName = ProviderPathHelper.GetFileNameWithoutExtension(fileName) ?? string.Empty; string title; string artist; uint? trackNo; GuessMetadataFromFileName(fileName, out title, out artist, out trackNo); if (!string.IsNullOrWhiteSpace(tags.title)) { title = tags.title; } IEnumerable <string> artists; if (!string.IsNullOrWhiteSpace(tags.artist)) { artists = SplitTagEnum(tags.artist); artists = PatchID3v23Enumeration(artists); } else { artists = artist == null ? null : new string[] { artist } }; if (!string.IsNullOrWhiteSpace(tags.track) && tags.track != "0") { int iTrackNo; if (int.TryParse(tags.track, out iTrackNo)) { trackNo = (uint?)iTrackNo; } else { trackNo = null; } } MediaItemAspect.SetAttribute(extractedAspectData, MediaAspect.ATTR_TITLE, title); MediaItemAspect.SetAttribute(extractedAspectData, MediaAspect.ATTR_SIZE, fsra.Size); // Calling EnsureLocalFileSystemAccess not necessary; only string operation MediaItemAspect.SetAttribute(extractedAspectData, MediaAspect.ATTR_MIME_TYPE, "audio/" + Path.GetExtension(fsra.LocalFileSystemPath).Substring(1)); MediaItemAspect.SetCollectionAttribute(extractedAspectData, AudioAspect.ATTR_ARTISTS, ApplyAdditionalSeparator(artists)); MediaItemAspect.SetAttribute(extractedAspectData, AudioAspect.ATTR_ALBUM, StringUtils.TrimToNull(tags.album)); IEnumerable <string> albumArtists = SplitTagEnum(tags.albumartist); albumArtists = PatchID3v23Enumeration(albumArtists); MediaItemAspect.SetCollectionAttribute(extractedAspectData, AudioAspect.ATTR_ALBUMARTISTS, ApplyAdditionalSeparator(albumArtists)); MediaItemAspect.SetAttribute(extractedAspectData, AudioAspect.ATTR_BITRATE, tags.bitrate); MediaItemAspect.SetAttribute(extractedAspectData, MediaAspect.ATTR_COMMENT, StringUtils.TrimToNull(tags.comment)); IEnumerable <string> composers = SplitTagEnum(tags.composer); composers = PatchID3v23Enumeration(composers); MediaItemAspect.SetCollectionAttribute(extractedAspectData, AudioAspect.ATTR_COMPOSERS, ApplyAdditionalSeparator(composers)); MediaItemAspect.SetAttribute(extractedAspectData, AudioAspect.ATTR_DURATION, (long)tags.duration); IEnumerable <string> genres = SplitTagEnum(tags.genre); genres = PatchID3v23Enumeration(genres); MediaItemAspect.SetCollectionAttribute(extractedAspectData, AudioAspect.ATTR_GENRES, ApplyAdditionalSeparator(genres)); if (trackNo.HasValue) { MediaItemAspect.SetAttribute(extractedAspectData, AudioAspect.ATTR_TRACK, (int)trackNo.Value); } int year; if (int.TryParse(tags.year, out year)) { if (year >= 30 && year <= 99) { year += 1900; } if (year >= 1930 && year <= 2030) { MediaItemAspect.SetAttribute(extractedAspectData, MediaAspect.ATTR_RECORDINGTIME, new DateTime(year, 1, 1)); } } // The following code gets cover art images from file (embedded) or from windows explorer cache (supports folder.jpg). if (tags.PictureCount > 0) { try { using (Image cover = tags.PictureGetImage(0)) using (Image resized = ImageUtilities.ResizeImage(cover, MAX_COVER_WIDTH, MAX_COVER_HEIGHT)) using (MemoryStream result = new MemoryStream()) { resized.Save(result, ImageFormat.Jpeg); MediaItemAspect.SetAttribute(extractedAspectData, ThumbnailLargeAspect.ATTR_THUMBNAIL, result.ToArray()); } } // Decoding of invalid image data can fail, but main MediaItem is correct. catch { } } else { // In quick mode only allow thumbs taken from cache. bool cachedOnly = forceQuickMode; // Thumbnail extraction fileName = mediaItemAccessor.ResourcePathName; IThumbnailGenerator generator = ServiceRegistration.Get <IThumbnailGenerator>(); byte[] thumbData; ImageType imageType; if (generator.GetThumbnail(fileName, cachedOnly, out thumbData, out imageType)) { MediaItemAspect.SetAttribute(extractedAspectData, ThumbnailLargeAspect.ATTR_THUMBNAIL, thumbData); } } return(true); } catch (Exception e) { // Only log at the info level here - And simply return false. This makes the importer know that we // couldn't perform our task here ServiceRegistration.Get <ILogger>().Info("BassAudioMetadataExtractor: Exception reading resource '{0}' (Text: '{1}')", fsra.CanonicalLocalResourcePath, e.Message); } return(false); }
public bool TryExtractMetadata(IResourceAccessor mediaItemAccessor, IDictionary <Guid, MediaItemAspect> extractedAspectData, bool forceQuickMode) { try { IFileSystemResourceAccessor fsra = mediaItemAccessor as IFileSystemResourceAccessor; if (fsra == null || !mediaItemAccessor.IsFile) { return(false); } string filePath = mediaItemAccessor.CanonicalLocalResourcePath.ToString(); string lowerExtension = StringUtils.TrimToEmpty(ProviderPathHelper.GetExtension(filePath)).ToLowerInvariant(); if (lowerExtension != ".ts") { return(false); } string metaFilePath = ProviderPathHelper.ChangeExtension(filePath, ".xml"); IResourceAccessor metaFileAccessor; if (!ResourcePath.Deserialize(metaFilePath).TryCreateLocalResourceAccessor(out metaFileAccessor)) { return(false); } Tags tags; using (metaFileAccessor) { using (Stream metaStream = metaFileAccessor.OpenRead()) tags = (Tags)GetTagsXmlSerializer().Deserialize(metaStream); } MediaItemAspect mediaAspect = MediaItemAspect.GetOrCreateAspect(extractedAspectData, MediaAspect.Metadata); MediaItemAspect videoAspect = MediaItemAspect.GetOrCreateAspect(extractedAspectData, VideoAspect.Metadata); MediaItemAspect recordingAspect = MediaItemAspect.GetOrCreateAspect(extractedAspectData, RecordingAspect.Metadata); // Handle series information SeriesInfo seriesInfo = GetSeriesFromTags(tags); if (seriesInfo.IsCompleteMatch) { if (!forceQuickMode) { SeriesTvDbMatcher.Instance.FindAndUpdateSeries(seriesInfo); } seriesInfo.SetMetadata(extractedAspectData); } string value; if (TryGet(tags, TAG_TITLE, out value) && !string.IsNullOrEmpty(value)) { mediaAspect.SetAttribute(MediaAspect.ATTR_TITLE, value); } if (TryGet(tags, TAG_GENRE, out value)) { videoAspect.SetCollectionAttribute(VideoAspect.ATTR_GENRES, new List <String> { value }); } if (TryGet(tags, TAG_PLOT, out value)) { videoAspect.SetAttribute(VideoAspect.ATTR_STORYPLOT, value); Match yearMatch = _yearMatcher.Match(value); int guessedYear; if (int.TryParse(yearMatch.Value, out guessedYear)) { MediaItemAspect.SetAttribute(extractedAspectData, MediaAspect.ATTR_RECORDINGTIME, new DateTime(guessedYear, 1, 1)); } } if (TryGet(tags, TAG_CHANNEL, out value)) { recordingAspect.SetAttribute(RecordingAspect.ATTR_CHANNEL, value); } // Recording date formatted: 2011-11-04 20:55 DateTime recordingStart; DateTime recordingEnd; if (TryGet(tags, TAG_STARTTIME, out value) && DateTime.TryParse(value, out recordingStart)) { recordingAspect.SetAttribute(RecordingAspect.ATTR_STARTTIME, recordingStart); } if (TryGet(tags, TAG_ENDTIME, out value) && DateTime.TryParse(value, out recordingEnd)) { recordingAspect.SetAttribute(RecordingAspect.ATTR_ENDTIME, recordingEnd); } return(true); } catch (Exception e) { // Only log at the info level here - And simply return false. This lets the caller know that we // couldn't perform our task here. ServiceRegistration.Get <ILogger>().Info("Tve3RecordingMetadataExtractor: Exception reading resource '{0}' (Text: '{1}')", mediaItemAccessor.CanonicalLocalResourcePath, e.Message); } return(false); }
private bool ExtractMetadata(ILocalFsResourceAccessor lfsra, IDictionary <Guid, MediaItemAspect> extractedAspectData, bool forceQuickMode) { if (lfsra == null || !lfsra.IsFile) { return(false); } string title; if (!MediaItemAspect.TryGetAttribute(extractedAspectData, MediaAspect.ATTR_TITLE, out title) || string.IsNullOrEmpty(title)) { return(false); } string filePath = lfsra.CanonicalLocalResourcePath.ToString(); string lowerExtension = StringUtils.TrimToEmpty(ProviderPathHelper.GetExtension(filePath)).ToLowerInvariant(); if (lowerExtension != ".wtv" && lowerExtension != ".dvr-ms") { return(false); } using (var rec = new MCRecMetadataEditor(lfsra.LocalFileSystemPath)) { // Handle series information IDictionary tags = rec.GetAttributes(); SeriesInfo seriesInfo = GetSeriesFromTags(tags); if (!forceQuickMode) { if (SeriesTvDbMatcher.Instance.FindAndUpdateSeries(seriesInfo)) { seriesInfo.SetMetadata(extractedAspectData); } } // Force MimeType MediaItemAspect.SetAttribute(extractedAspectData, MediaAspect.ATTR_MIME_TYPE, "slimtv/wtv"); string value; if (TryGet(tags, TAG_TITLE, out value) && !string.IsNullOrEmpty(value)) { MediaItemAspect.SetAttribute(extractedAspectData, MediaAspect.ATTR_TITLE, value); } if (TryGet(tags, TAG_GENRE, out value)) { MediaItemAspect.SetCollectionAttribute(extractedAspectData, VideoAspect.ATTR_GENRES, new List <String>(value.Split(new[] { ";" }, StringSplitOptions.RemoveEmptyEntries))); } if (TryGet(tags, TAG_PLOT, out value)) { MediaItemAspect.SetAttribute(extractedAspectData, VideoAspect.ATTR_STORYPLOT, value); } if (TryGet(tags, TAG_ORIGINAL_TIME, out value)) { DateTime origTime; if (DateTime.TryParse(value, out origTime)) { MediaItemAspect.SetAttribute(extractedAspectData, MediaAspect.ATTR_RECORDINGTIME, origTime); } } if (TryGet(tags, TAG_CHANNEL, out value)) { MediaItemAspect.SetAttribute(extractedAspectData, RecordingAspect.ATTR_CHANNEL, value); } long lValue; if (TryGet(tags, TAG_STARTTIME, out lValue)) { MediaItemAspect.SetAttribute(extractedAspectData, RecordingAspect.ATTR_STARTTIME, FromMCEFileTime(lValue)); } if (TryGet(tags, TAG_ENDTIME, out lValue)) { MediaItemAspect.SetAttribute(extractedAspectData, RecordingAspect.ATTR_ENDTIME, FromMCEFileTime(lValue)); } } return(true); }
public override async Task <AsyncResult <ContentDirectoryMessaging.MediaItemChangeType> > ProcessAsync(MediaItem mediaItem) { IContentDirectory cd = ServiceRegistration.Get <IServerConnectionManager>().ContentDirectory; bool removeFromML = IsManagedByMediaLibrary(mediaItem) && cd != null; var falseResult = new AsyncResult <ContentDirectoryMessaging.MediaItemChangeType>(false, ContentDirectoryMessaging.MediaItemChangeType.None); // Support multi-resource media items and secondary resources IList <MultipleMediaItemAspect> providerAspects; if (!MediaItemAspect.TryGetAspects(mediaItem.Aspects, ProviderResourceAspect.Metadata, out providerAspects)) { return(falseResult); } foreach (MultipleMediaItemAspect providerAspect in providerAspects) { string systemId = (string)providerAspect[ProviderResourceAspect.ATTR_SYSTEM_ID]; string resourceAccessorPath = (string)providerAspect[ProviderResourceAspect.ATTR_RESOURCE_ACCESSOR_PATH]; var rl = new ResourceLocator(systemId, ResourcePath.Deserialize(resourceAccessorPath)); using (var ra = rl.CreateAccessor()) { var rad = ra as IResourceDeletor; if (rad == null) { return(falseResult); } // First try to delete the file from storage. if (!rad.Delete()) { return(falseResult); } // If the MediaItem was loaded from ML, remove it there as well. if (removeFromML) { await cd.DeleteMediaItemOrPathAsync(rl.NativeSystemId, rl.NativeResourcePath, true); } } } // Check for special cases here: // 1) Recordings have an additional .xml attached // 2) Deleting files could lead to empty folders that should be also removed foreach (DeleteRule rule in _defaultRules.Where(r => r.IsEnabled)) { if (mediaItem.Aspects.ContainsKey(rule.HasAspectGuid)) { var tsPath = mediaItem.GetResourceLocator().NativeResourcePath.ToString(); foreach (string otherExtension in rule.DeleteOtherExtensions) { string otherFilePath = ProviderPathHelper.ChangeExtension(tsPath, otherExtension); IResourceAccessor ra; if (!ResourcePath.Deserialize(otherFilePath).TryCreateLocalResourceAccessor(out ra)) { continue; } // Delete other file. We do not check here for existance of file, the Delete needs to handle this. using (ra) { var rad = ra as IResourceDeletor; rad?.Delete(); } } if (rule.DeleteEmptyFolders) { var folderPath = ProviderPathHelper.GetDirectoryName(tsPath); IResourceAccessor ra; if (!ResourcePath.Deserialize(folderPath).TryCreateLocalResourceAccessor(out ra)) { continue; } // Delete folder if empty using (ra) { IFileSystemResourceAccessor fsra = ra as IFileSystemResourceAccessor; if (fsra != null) { var isEmpty = fsra.GetFiles().Count == 0 && fsra.GetChildDirectories().Count == 0; if (isEmpty) { var rad = ra as IResourceDeletor; rad?.Delete(); } } } } } } return(new AsyncResult <ContentDirectoryMessaging.MediaItemChangeType>(true, ContentDirectoryMessaging.MediaItemChangeType.Deleted)); }
public override async Task <bool> TryExtractMetadataAsync(IResourceAccessor mediaItemAccessor, IDictionary <Guid, IList <MediaItemAspect> > extractedAspectData, bool forceQuickMode) { // If the base AudioMDE already extracted metadata, don't try here again to avoid conflicts. if (extractedAspectData.ContainsKey(AudioAspect.ASPECT_ID)) { return(false); } ILocalFsResourceAccessor fsra = mediaItemAccessor as ILocalFsResourceAccessor; if (fsra == null) { return(false); } if (!fsra.IsFile) { return(false); } string fileName = fsra.ResourceName; if (!HasAudioExtension(fileName)) { return(false); } if (extractedAspectData.ContainsKey(ReimportAspect.ASPECT_ID)) //Ignore for reimports because the tags might be the cause of the wrong match { return(false); } try { TAG_INFO tags; using (fsra.EnsureLocalFileSystemAccess()) tags = BassTags.BASS_TAG_GetFromFile(fsra.LocalFileSystemPath); if (tags == null) { return(false); } fileName = ProviderPathHelper.GetFileNameWithoutExtension(fileName) ?? string.Empty; string title; string artist; uint? trackNo; GuessMetadataFromFileName(fileName, out title, out artist, out trackNo); if (!string.IsNullOrWhiteSpace(tags.title)) { title = tags.title; } IEnumerable <string> artists; if (!string.IsNullOrWhiteSpace(tags.artist)) { artists = SplitTagEnum(tags.artist); artists = PatchID3v23Enumeration(artists); } else { artists = artist == null ? null : new string[] { artist } }; if (!string.IsNullOrWhiteSpace(tags.track) && tags.track != "0") { int iTrackNo; if (int.TryParse(tags.track, out iTrackNo)) { trackNo = (uint?)iTrackNo; } else { trackNo = null; } } TrackInfo trackInfo = new TrackInfo(); if (extractedAspectData.ContainsKey(AudioAspect.ASPECT_ID)) { trackInfo.FromMetadata(extractedAspectData); } else { MediaItemAspect.SetAttribute(extractedAspectData, MediaAspect.ATTR_TITLE, title); IList <MultipleMediaItemAspect> providerResourceAspect; if (MediaItemAspect.TryGetAspects(extractedAspectData, ProviderResourceAspect.Metadata, out providerResourceAspect)) { providerResourceAspect[0].SetAttribute(ProviderResourceAspect.ATTR_SIZE, fsra.Size); // Calling EnsureLocalFileSystemAccess not necessary; only string operation providerResourceAspect[0].SetAttribute(ProviderResourceAspect.ATTR_MIME_TYPE, "audio/" + Path.GetExtension(fsra.LocalFileSystemPath).Substring(1)); } MediaItemAspect.SetAttribute(extractedAspectData, AudioAspect.ATTR_BITRATE, tags.bitrate); MediaItemAspect.SetAttribute(extractedAspectData, MediaAspect.ATTR_COMMENT, StringUtils.TrimToNull(tags.comment)); MediaItemAspect.SetAttribute(extractedAspectData, AudioAspect.ATTR_DURATION, (long)tags.duration); } if (!trackInfo.IsBaseInfoPresent) { trackInfo.TrackName = title; trackInfo.Album = StringUtils.TrimToNull(tags.album); if (trackNo.HasValue) { trackInfo.TrackNum = (int)trackNo.Value; } trackInfo.Artists = new List <PersonInfo>(); foreach (string artistName in ApplyAdditionalSeparator(artists)) { trackInfo.Artists.Add(new PersonInfo() { Name = artistName, Occupation = PersonAspect.OCCUPATION_ARTIST, ParentMediaName = trackInfo.Album, MediaName = trackInfo.TrackName }); } IEnumerable <string> albumArtists = SplitTagEnum(tags.albumartist); albumArtists = PatchID3v23Enumeration(albumArtists); trackInfo.AlbumArtists = new List <PersonInfo>(); foreach (string artistName in ApplyAdditionalSeparator(albumArtists)) { trackInfo.AlbumArtists.Add(new PersonInfo() { Name = artistName, Occupation = PersonAspect.OCCUPATION_ARTIST, ParentMediaName = trackInfo.Album, MediaName = trackInfo.TrackName }); } IEnumerable <string> composers = SplitTagEnum(tags.composer); composers = PatchID3v23Enumeration(composers); trackInfo.Composers = new List <PersonInfo>(); foreach (string composerName in ApplyAdditionalSeparator(composers)) { trackInfo.Composers.Add(new PersonInfo() { Name = composerName, Occupation = PersonAspect.OCCUPATION_COMPOSER, ParentMediaName = trackInfo.Album, MediaName = trackInfo.TrackName }); } IEnumerable <string> genres = SplitTagEnum(tags.genre); genres = PatchID3v23Enumeration(genres); trackInfo.Genres = ApplyAdditionalSeparator(genres).Where(s => !string.IsNullOrEmpty(s?.Trim())).Select(s => new GenreInfo { Name = s.Trim() }).ToList(); IGenreConverter converter = ServiceRegistration.Get <IGenreConverter>(); foreach (var genre in trackInfo.Genres) { if (!genre.Id.HasValue && converter.GetGenreId(genre.Name, GenreCategory.Music, null, out int genreId)) { genre.Id = genreId; } } int year; if (int.TryParse(tags.year, out year)) { if (year >= 30 && year <= 99) { year += 1900; } if (year >= 1930 && year <= 2030) { trackInfo.ReleaseDate = new DateTime(year, 1, 1); } } if (!trackInfo.HasThumbnail) { // The following code gets cover art images from file (embedded) or from windows explorer cache (supports folder.jpg). if (tags.PictureCount > 0) { try { using (Image cover = tags.PictureGetImage(0)) using (MemoryStream result = new MemoryStream()) { cover.Save(result, ImageFormat.Jpeg); trackInfo.Thumbnail = result.ToArray(); trackInfo.HasChanged = true; } } // Decoding of invalid image data can fail, but main MediaItem is correct. catch { } } } } if (!SkipOnlineSearches && !forceQuickMode) { await OnlineMatcherService.Instance.FindAndUpdateTrackAsync(trackInfo).ConfigureAwait(false); } if (!trackInfo.HasChanged) { return(false); } trackInfo.SetMetadata(extractedAspectData); return(trackInfo.IsBaseInfoPresent); } catch (Exception e) { // Only log at the info level here - And simply return false. This makes the importer know that we // couldn't perform our task here ServiceRegistration.Get <ILogger>().Info("BassAudioMetadataExtractor: Exception reading resource '{0}' (Text: '{1}')", fsra.CanonicalLocalResourcePath, e.Message); } return(false); }
/// <summary> /// Tries to load chapter information from external file. This method checks for ComSkip files (.txt). /// </summary> /// <returns></returns> protected virtual bool EnumerateExternalChapters() { var fsra = _resourceAccessor as IFileSystemResourceAccessor; if (fsra == null || !fsra.IsFile) { return(false); } try { string filePath = _resourceAccessor.CanonicalLocalResourcePath.ToString(); string metaFilePath = ProviderPathHelper.ChangeExtension(filePath, ".txt"); IResourceAccessor raTextFile; if (!ResourcePath.Deserialize(metaFilePath).TryCreateLocalResourceAccessor(out raTextFile)) { return(false); } List <double> positions = new List <double>(); using (LocalFsResourceAccessorHelper lfsra = new LocalFsResourceAccessorHelper(raTextFile)) { if (lfsra.LocalFsResourceAccessor == null) { return(false); } using (var stream = lfsra.LocalFsResourceAccessor.OpenRead()) using (var chaptersReader = new StreamReader(stream)) { string line = chaptersReader.ReadLine(); int fps; if (string.IsNullOrWhiteSpace(line) || !int.TryParse(line.Substring(line.LastIndexOf(' ') + 1), out fps)) { ServiceRegistration.Get <ILogger>().Warn("VideoPlayer: EnumerateExternalChapters() - Invalid ComSkip chapter file"); return(false); } double framesPerSecond = fps / 100.0; while ((line = chaptersReader.ReadLine()) != null) { if (String.IsNullOrEmpty(line)) { continue; } string[] tokens = line.Split('\t'); if (tokens.Length != 2) { continue; } foreach (var token in tokens) { int time; if (int.TryParse(token, NumberStyles.Float, NumberFormatInfo.InvariantInfo, out time)) { positions.Add(time / framesPerSecond); } } } } } // Insert start of video as position if (!positions.Contains(0d)) { positions.Insert(0, 0d); } var chapterNames = new List <string>(); var chapterTimes = new List <double>(); for (int index = 0; index < positions.Count - 1; index++) { var timeFrom = positions[index]; var timeTo = positions[index + 1]; // Filter out segments with less than 2 seconds duration if (timeTo - timeFrom <= 2) { continue; } var chapterName = string.Format("ComSkip {0} [{1} - {2}]", chapterNames.Count + 1, FormatDuration(timeFrom), FormatDuration(timeTo)); chapterNames.Add(chapterName); chapterTimes.Add(timeFrom); } _chapterNames = chapterNames.ToArray(); _chapterTimestamps = chapterTimes.ToArray(); return(_chapterNames.Length > 0); } catch (Exception ex) { ServiceRegistration.Get <ILogger>().Error("VideoPlayer: EnumerateExternalChapters() - Exception while reading ComSkip chapter file", ex); return(false); } }
public virtual bool TryExtractMetadata(IResourceAccessor mediaItemAccessor, IDictionary <Guid, IList <MediaItemAspect> > extractedAspectData, bool importOnly, bool forceQuickMode) { IFileSystemResourceAccessor fsra = mediaItemAccessor as IFileSystemResourceAccessor; if (fsra == null) { return(false); } if (!fsra.IsFile) { return(false); } string fileName = fsra.ResourceName; if (!HasAudioExtension(fileName)) { return(false); } bool refresh = false; if (extractedAspectData.ContainsKey(AudioAspect.ASPECT_ID)) { refresh = true; } try { TrackInfo trackInfo = new TrackInfo(); if (refresh) { trackInfo.FromMetadata(extractedAspectData); } if (!trackInfo.IsBaseInfoPresent) { File tag = null; try { ByteVector.UseBrokenLatin1Behavior = true; // Otherwise we have problems retrieving non-latin1 chars tag = File.Create(new ResourceProviderFileAbstraction(fsra)); } catch (CorruptFileException) { // Only log at the info level here - And simply return false. This makes the importer know that we // couldn't perform our task here. ServiceRegistration.Get <ILogger>().Info("AudioMetadataExtractor: Audio file '{0}' seems to be broken", fsra.CanonicalLocalResourcePath); return(false); } using (tag) { // Some file extensions like .mp4 can contain audio and video. Do not handle files with video content here. if (tag.Properties.VideoHeight > 0 && tag.Properties.VideoWidth > 0) { return(false); } fileName = ProviderPathHelper.GetFileNameWithoutExtension(fileName) ?? string.Empty; string title; string sortTitle; string artist; uint? trackNo; GuessMetadataFromFileName(fileName, out title, out artist, out trackNo); if (!string.IsNullOrEmpty(title)) { title = CultureInfo.InvariantCulture.TextInfo.ToTitleCase(title.ToLowerInvariant()); } if (!string.IsNullOrEmpty(artist)) { artist = CultureInfo.InvariantCulture.TextInfo.ToTitleCase(artist.ToLowerInvariant()); } if (!string.IsNullOrEmpty(tag.Tag.Title)) { title = tag.Tag.Title.Trim(); } sortTitle = BaseInfo.GetSortTitle(title); if (!string.IsNullOrEmpty(tag.Tag.TitleSort)) { sortTitle = tag.Tag.TitleSort.Trim(); } IEnumerable <string> artists; if (tag.Tag.Performers.Length > 0) { artists = tag.Tag.Performers; if ((tag.TagTypes & TagTypes.Id3v2) != 0) { artists = PatchID3v23Enumeration(artists); } } else { artists = artist == null ? null : new string[] { artist.Trim() } }; if (tag.Tag.Track != 0) { trackNo = tag.Tag.Track; } if (importOnly) { MultipleMediaItemAspect providerResourceAspect = MediaItemAspect.CreateAspect(extractedAspectData, ProviderResourceAspect.Metadata); providerResourceAspect.SetAttribute(ProviderResourceAspect.ATTR_RESOURCE_INDEX, 0); providerResourceAspect.SetAttribute(ProviderResourceAspect.ATTR_PRIMARY, true); providerResourceAspect.SetAttribute(ProviderResourceAspect.ATTR_SIZE, fsra.Size); providerResourceAspect.SetAttribute(ProviderResourceAspect.ATTR_RESOURCE_ACCESSOR_PATH, fsra.CanonicalLocalResourcePath.Serialize()); // FIXME Albert: tag.MimeType returns taglib/mp3 for an MP3 file. This is not what we want and collides with the // mimetype handling in the BASS player, which expects audio/xxx. if (!string.IsNullOrWhiteSpace(tag.MimeType)) { providerResourceAspect.SetAttribute(ProviderResourceAspect.ATTR_MIME_TYPE, tag.MimeType.Replace("taglib/", "audio/")); } MediaItemAspect.SetAttribute(extractedAspectData, MediaAspect.ATTR_TITLE, title); MediaItemAspect.SetAttribute(extractedAspectData, MediaAspect.ATTR_SORT_TITLE, sortTitle); MediaItemAspect.SetAttribute(extractedAspectData, MediaAspect.ATTR_ISVIRTUAL, false); MediaItemAspect.SetAttribute(extractedAspectData, MediaAspect.ATTR_COMMENT, StringUtils.TrimToNull(tag.Tag.Comment)); MediaItemAspect.SetAttribute(extractedAspectData, MediaAspect.ATTR_RECORDINGTIME, fsra.LastChanged); } trackInfo.TrackName = title; trackInfo.TrackNameSort = sortTitle; if (tag.Properties.Codecs.Count() > 0) { trackInfo.Encoding = tag.Properties.Codecs.First().Description; } if (tag.Properties.Duration.TotalSeconds != 0) { trackInfo.Duration = (long)tag.Properties.Duration.TotalSeconds; } if (tag.Properties.AudioBitrate != 0) { trackInfo.BitRate = (int)tag.Properties.AudioBitrate; } if (tag.Properties.AudioChannels != 0) { trackInfo.Channels = (int)tag.Properties.AudioChannels; } if (tag.Properties.AudioSampleRate != 0) { trackInfo.SampleRate = (int)tag.Properties.AudioSampleRate; } TagLib.Id3v2.Tag id3Tag = (TagLib.Id3v2.Tag)tag.GetTag(TagTypes.Id3v2, false); if (id3Tag != null && !id3Tag.IsEmpty) { trackInfo.Compilation = id3Tag.IsCompilation; } trackInfo.Album = !string.IsNullOrEmpty(tag.Tag.Album) ? tag.Tag.Album.Trim() : null; if (!string.IsNullOrEmpty(tag.Tag.AlbumSort)) { IAudioRelationshipExtractor.StoreAlbum(extractedAspectData, tag.Tag.Album, tag.Tag.AlbumSort.Trim()); } if (trackNo.HasValue) { trackInfo.TrackNum = (int)trackNo.Value; } if (tag.Tag.TrackCount != 0) { trackInfo.TotalTracks = (int)tag.Tag.TrackCount; } if (tag.Tag.Disc != 0) { trackInfo.DiscNum = (int)tag.Tag.Disc; } if (tag.Tag.DiscCount != 0) { trackInfo.TotalDiscs = (int)tag.Tag.DiscCount; } if (!string.IsNullOrEmpty(tag.Tag.Lyrics)) { trackInfo.TrackLyrics = tag.Tag.Lyrics; } if (tag.Tag.TrackCount != 0) { trackInfo.TotalTracks = (int)tag.Tag.TrackCount; } if (!string.IsNullOrEmpty(tag.Tag.MusicBrainzTrackId)) { trackInfo.MusicBrainzId = tag.Tag.MusicBrainzTrackId; } if (!string.IsNullOrEmpty(tag.Tag.MusicBrainzReleaseId)) { trackInfo.AlbumMusicBrainzId = tag.Tag.MusicBrainzReleaseId; } if (!string.IsNullOrEmpty(tag.Tag.MusicBrainzDiscId)) { trackInfo.AlbumMusicBrainzDiscId = tag.Tag.MusicBrainzDiscId; } if (!string.IsNullOrEmpty(tag.Tag.AmazonId)) { trackInfo.AlbumAmazonId = tag.Tag.AmazonId; } if (!string.IsNullOrEmpty(tag.Tag.MusicIpId)) { trackInfo.MusicIpId = tag.Tag.MusicIpId; } trackInfo.Artists = new List <PersonInfo>(); if (artists != null) { foreach (string artistName in ApplyAdditionalSeparator(artists)) { trackInfo.Artists.Add(new PersonInfo() { Name = artistName.Trim(), Occupation = PersonAspect.OCCUPATION_ARTIST, ParentMediaName = trackInfo.Album, MediaName = trackInfo.TrackName }); } } //Save id if possible if (trackInfo.Artists.Count == 1 && !string.IsNullOrEmpty(tag.Tag.MusicBrainzArtistId)) { trackInfo.Artists[0].MusicBrainzId = tag.Tag.MusicBrainzArtistId; } IEnumerable <string> albumArtists = tag.Tag.AlbumArtists; if ((tag.TagTypes & TagTypes.Id3v2) != 0) { albumArtists = PatchID3v23Enumeration(albumArtists); } trackInfo.AlbumArtists = new List <PersonInfo>(); if (albumArtists != null) { foreach (string artistName in ApplyAdditionalSeparator(albumArtists)) { trackInfo.AlbumArtists.Add(new PersonInfo() { Name = artistName.Trim(), Occupation = PersonAspect.OCCUPATION_ARTIST, ParentMediaName = trackInfo.Album, MediaName = trackInfo.TrackName }); } } //Save id if possible if (trackInfo.AlbumArtists.Count == 1 && !string.IsNullOrEmpty(tag.Tag.MusicBrainzReleaseArtistId)) { trackInfo.AlbumArtists[0].MusicBrainzId = tag.Tag.MusicBrainzReleaseArtistId; } IEnumerable <string> composers = tag.Tag.Composers; if ((tag.TagTypes & TagTypes.Id3v2) != 0) { composers = PatchID3v23Enumeration(composers); } trackInfo.Composers = new List <PersonInfo>(); if (composers != null) { foreach (string composerName in ApplyAdditionalSeparator(composers)) { trackInfo.Composers.Add(new PersonInfo() { Name = composerName.Trim(), Occupation = PersonAspect.OCCUPATION_COMPOSER, ParentMediaName = trackInfo.Album, MediaName = trackInfo.TrackName }); } } if (tag.Tag.Genres.Length > 0) { IEnumerable <string> genres = tag.Tag.Genres; if ((tag.TagTypes & TagTypes.Id3v2) != 0) { genres = PatchID3v23Enumeration(genres); } trackInfo.Genres = ApplyAdditionalSeparator(genres).Select(s => new GenreInfo { Name = s.Trim() }).ToList(); OnlineMatcherService.Instance.AssignMissingMusicGenreIds(trackInfo.Genres); } int year = (int)tag.Tag.Year; if (year >= 30 && year <= 99) { year += 1900; } if (year >= 1930 && year <= 2030) { trackInfo.ReleaseDate = new DateTime(year, 1, 1); } if (!trackInfo.HasThumbnail) { // The following code gets cover art images from file (embedded) or from windows explorer cache (supports folder.jpg). IPicture[] pics = tag.Tag.Pictures; if (pics.Length > 0) { try { using (MemoryStream stream = new MemoryStream(pics[0].Data.Data)) { trackInfo.Thumbnail = stream.ToArray(); trackInfo.HasChanged = true; } } // Decoding of invalid image data can fail, but main MediaItem is correct. catch { } } else { // In quick mode only allow thumbs taken from cache. bool cachedOnly = importOnly || forceQuickMode; // Thumbnail extraction fileName = mediaItemAccessor.ResourcePathName; IThumbnailGenerator generator = ServiceRegistration.Get <IThumbnailGenerator>(); byte[] thumbData; ImageType imageType; if (generator.GetThumbnail(fileName, cachedOnly, out thumbData, out imageType)) { trackInfo.Thumbnail = thumbData; trackInfo.HasChanged = true; } } } } if (string.IsNullOrEmpty(trackInfo.Album) || trackInfo.Artists.Count == 0) { MusicNameMatcher.MatchTrack(fileName, trackInfo); } } //Determine compilation if (importOnly && !trackInfo.Compilation) { if (trackInfo.AlbumArtists.Count > 0 && (trackInfo.AlbumArtists[0].Name.IndexOf("Various", StringComparison.InvariantCultureIgnoreCase) >= 0 || trackInfo.AlbumArtists[0].Name.Equals("VA", StringComparison.InvariantCultureIgnoreCase))) { trackInfo.Compilation = true; } else { //Look for itunes compilation folder var mediaItemPath = mediaItemAccessor.CanonicalLocalResourcePath; var albumMediaItemDirectoryPath = ResourcePathHelper.Combine(mediaItemPath, "../"); var artistMediaItemDirectoryPath = ResourcePathHelper.Combine(mediaItemPath, "../../"); if (IsDiscFolder(trackInfo.Album, albumMediaItemDirectoryPath.FileName)) { //Probably a CD folder so try next parent artistMediaItemDirectoryPath = ResourcePathHelper.Combine(mediaItemPath, "../../../"); } if (artistMediaItemDirectoryPath.FileName.IndexOf("Compilation", StringComparison.InvariantCultureIgnoreCase) >= 0) { trackInfo.Compilation = true; } } } if (!refresh) { //Check artists trackInfo.Artists = GetCorrectedArtistsList(trackInfo, trackInfo.Artists); trackInfo.AlbumArtists = GetCorrectedArtistsList(trackInfo, trackInfo.AlbumArtists); } trackInfo.AssignNameId(); if (!forceQuickMode) { AudioCDMatcher.GetDiscMatchAndUpdate(mediaItemAccessor.ResourcePathName, trackInfo); if (SkipOnlineSearches && !SkipFanArtDownload) { TrackInfo tempInfo = trackInfo.Clone(); OnlineMatcherService.Instance.FindAndUpdateTrack(tempInfo, importOnly); trackInfo.CopyIdsFrom(tempInfo); trackInfo.HasChanged = tempInfo.HasChanged; } else if (!SkipOnlineSearches) { OnlineMatcherService.Instance.FindAndUpdateTrack(trackInfo, importOnly); } } if (refresh) { if ((IncludeArtistDetails && !BaseInfo.HasRelationship(extractedAspectData, PersonAspect.ROLE_ARTIST) && trackInfo.Artists.Count > 0) || (IncludeArtistDetails && !BaseInfo.HasRelationship(extractedAspectData, PersonAspect.ROLE_ALBUMARTIST) && trackInfo.AlbumArtists.Count > 0) || (IncludeComposerDetails && !BaseInfo.HasRelationship(extractedAspectData, PersonAspect.ROLE_COMPOSER) && trackInfo.Composers.Count > 0)) { trackInfo.HasChanged = true; } } if (!trackInfo.HasChanged && !importOnly) { return(false); } trackInfo.SetMetadata(extractedAspectData); if (importOnly) { //Store metadata for the Relationship Extractors if (IncludeArtistDetails) { IAudioRelationshipExtractor.StorePersons(extractedAspectData, trackInfo.Artists, false); IAudioRelationshipExtractor.StorePersons(extractedAspectData, trackInfo.AlbumArtists, true); } if (IncludeComposerDetails) { IAudioRelationshipExtractor.StorePersons(extractedAspectData, trackInfo.Composers, false); } } return(trackInfo.IsBaseInfoPresent); } catch (UnsupportedFormatException) { ServiceRegistration.Get <ILogger>().Info("AudioMetadataExtractor: Unsupported audio file '{0}'", fsra.CanonicalLocalResourcePath); return(false); } catch (Exception e) { // Only log at the info level here - And simply return false. This makes the importer know that we // couldn't perform our task here ServiceRegistration.Get <ILogger>().Info("AudioMetadataExtractor: Exception reading resource '{0}' (Text: '{1}')", fsra.CanonicalLocalResourcePath, e.Message); } return(false); }
public bool TryExtractMetadata(IResourceAccessor mediaItemAccessor, IDictionary <Guid, IList <MediaItemAspect> > extractedAspectData, bool importOnly, bool forceQuickMode) { string fileName = mediaItemAccessor.ResourceName; if (!HasImageExtension(fileName)) { return(false); } bool refresh = false; if (extractedAspectData.ContainsKey(ImageAspect.ASPECT_ID)) { refresh = true; } try { IFileSystemResourceAccessor fsra = mediaItemAccessor as IFileSystemResourceAccessor; if (!refresh) { MultipleMediaItemAspect providerResourceAspect = MediaItemAspect.CreateAspect(extractedAspectData, ProviderResourceAspect.Metadata); providerResourceAspect.SetAttribute(ProviderResourceAspect.ATTR_RESOURCE_INDEX, 0); providerResourceAspect.SetAttribute(ProviderResourceAspect.ATTR_PRIMARY, true); if (!(mediaItemAccessor is IFileSystemResourceAccessor)) { return(false); } // Open a stream for media item to detect mimeType. using (Stream mediaStream = fsra.OpenRead()) { string mimeType = MimeTypeDetector.GetMimeType(mediaStream) ?? DEFAULT_MIMETYPE; providerResourceAspect.SetAttribute(ProviderResourceAspect.ATTR_MIME_TYPE, mimeType); providerResourceAspect.SetAttribute(ProviderResourceAspect.ATTR_SIZE, fsra.Size); } } MediaItemAspect mediaAspect = MediaItemAspect.GetOrCreateAspect(extractedAspectData, MediaAspect.Metadata); mediaAspect.SetAttribute(MediaAspect.ATTR_ISVIRTUAL, false); MediaItemAspect imageAspect = MediaItemAspect.GetOrCreateAspect(extractedAspectData, ImageAspect.Metadata); if (!refresh) { // Extract EXIF information from media item. using (ExifMetaInfo.ExifMetaInfo exif = new ExifMetaInfo.ExifMetaInfo(fsra)) { mediaAspect.SetAttribute(MediaAspect.ATTR_TITLE, ProviderPathHelper.GetFileNameWithoutExtension(fileName)); mediaAspect.SetAttribute(MediaAspect.ATTR_RECORDINGTIME, exif.OriginalDate != DateTime.MinValue ? exif.OriginalDate : fsra.LastChanged); mediaAspect.SetAttribute(MediaAspect.ATTR_COMMENT, StringUtils.TrimToNull(exif.ImageDescription)); if (exif.PixXDim.HasValue) { imageAspect.SetAttribute(ImageAspect.ATTR_WIDTH, (int)exif.PixXDim); } if (exif.PixYDim.HasValue) { imageAspect.SetAttribute(ImageAspect.ATTR_HEIGHT, (int)exif.PixYDim); } imageAspect.SetAttribute(ImageAspect.ATTR_MAKE, StringUtils.TrimToNull(exif.EquipMake)); imageAspect.SetAttribute(ImageAspect.ATTR_MODEL, StringUtils.TrimToNull(exif.EquipModel)); if (exif.ExposureBias.HasValue) { imageAspect.SetAttribute(ImageAspect.ATTR_EXPOSURE_BIAS, ((double)exif.ExposureBias).ToString()); } imageAspect.SetAttribute(ImageAspect.ATTR_EXPOSURE_TIME, exif.ExposureTime); imageAspect.SetAttribute(ImageAspect.ATTR_FLASH_MODE, StringUtils.TrimToNull(exif.FlashMode)); if (exif.FNumber.HasValue) { imageAspect.SetAttribute(ImageAspect.ATTR_FNUMBER, string.Format("F {0}", (double)exif.FNumber)); } imageAspect.SetAttribute(ImageAspect.ATTR_ISO_SPEED, StringUtils.TrimToNull(exif.ISOSpeed)); imageAspect.SetAttribute(ImageAspect.ATTR_ORIENTATION, (Int32)(exif.OrientationType ?? 0)); imageAspect.SetAttribute(ImageAspect.ATTR_METERING_MODE, exif.MeteringMode.ToString()); if (exif.Latitude.HasValue && exif.Longitude.HasValue) { imageAspect.SetAttribute(ImageAspect.ATTR_LATITUDE, exif.Latitude); imageAspect.SetAttribute(ImageAspect.ATTR_LONGITUDE, exif.Longitude); } } byte[] thumbData; // We only want to create missing thumbnails here, so check for existing ones first if (MediaItemAspect.TryGetAttribute(extractedAspectData, ThumbnailLargeAspect.ATTR_THUMBNAIL, out thumbData) && thumbData != null) { return(true); } using (LocalFsResourceAccessorHelper rah = new LocalFsResourceAccessorHelper(mediaItemAccessor)) using (rah.LocalFsResourceAccessor.EnsureLocalFileSystemAccess()) { string localFsResourcePath = rah.LocalFsResourceAccessor.LocalFileSystemPath; if (localFsResourcePath != null) { // Thumbnail extraction IThumbnailGenerator generator = ServiceRegistration.Get <IThumbnailGenerator>(); ImageType imageType; if (generator.GetThumbnail(localFsResourcePath, true, out thumbData, out imageType)) { MediaItemAspect.SetAttribute(extractedAspectData, ThumbnailLargeAspect.ATTR_THUMBNAIL, thumbData); } } } return(true); } else { bool updated = false; double?latitude = imageAspect.GetAttributeValue <double?>(ImageAspect.ATTR_LATITUDE); double?longitude = imageAspect.GetAttributeValue <double?>(ImageAspect.ATTR_LONGITUDE); if (IncludeGeoLocationDetails && !importOnly && latitude.HasValue && longitude.HasValue && string.IsNullOrEmpty(imageAspect.GetAttributeValue <string>(ImageAspect.ATTR_COUNTRY))) { CivicAddress locationInfo; if (!forceQuickMode && GeoLocationService.Instance.TryLookup(new GeoCoordinate(latitude.Value, longitude.Value), out locationInfo)) { imageAspect.SetAttribute(ImageAspect.ATTR_CITY, locationInfo.City); imageAspect.SetAttribute(ImageAspect.ATTR_STATE, locationInfo.StateProvince); imageAspect.SetAttribute(ImageAspect.ATTR_COUNTRY, locationInfo.CountryRegion); updated = true; } } byte[] thumbData; // We only want to create missing thumbnails here, so check for existing ones first if (MediaItemAspect.TryGetAttribute(extractedAspectData, ThumbnailLargeAspect.ATTR_THUMBNAIL, out thumbData) && thumbData != null) { return(updated); } using (LocalFsResourceAccessorHelper rah = new LocalFsResourceAccessorHelper(mediaItemAccessor)) using (rah.LocalFsResourceAccessor.EnsureLocalFileSystemAccess()) { string localFsResourcePath = rah.LocalFsResourceAccessor.LocalFileSystemPath; if (localFsResourcePath != null) { // In quick mode only allow thumbs taken from cache. bool cachedOnly = forceQuickMode; // Thumbnail extraction IThumbnailGenerator generator = ServiceRegistration.Get <IThumbnailGenerator>(); ImageType imageType; if (generator.GetThumbnail(localFsResourcePath, cachedOnly, out thumbData, out imageType)) { MediaItemAspect.SetAttribute(extractedAspectData, ThumbnailLargeAspect.ATTR_THUMBNAIL, thumbData); updated = true; } } } return(updated); } } catch (Exception e) { // Only log at the info level here - And simply return false. This makes the importer know that we // couldn't perform our task here. ServiceRegistration.Get <ILogger>().Info("ImageMetadataExtractor: Exception reading resource '{0}' (Text: '{1}')", mediaItemAccessor.CanonicalLocalResourcePath, e.Message); } return(false); }
private static void TestRecording(string filename) { if (Directory.Exists("_Test")) { Directory.Delete("_Test", true); } ServiceRegistration.Set <IPathManager>(new PathManager()); ServiceRegistration.Get <IPathManager>().SetPath("DATA", "_Test/Data"); ServiceRegistration.Set <ILocalization>(new NoLocalization()); ServiceRegistration.Set <ILogger>(new ConsoleLogger(LogLevel.All, true)); ServiceRegistration.Set <IMediaAccessor>(new TestMediaAccessor()); ServiceRegistration.Set <IMediaItemAspectTypeRegistration>(new MockMediaItemAspectTypeRegistration()); ApplicationCore.RegisterDefaultMediaItemAspectTypes().Wait(); ServiceRegistration.Set <SeriesTvDbMatcher>(new SeriesTvDbMatcher()); IDictionary <Guid, IList <MediaItemAspect> > aspects = new Dictionary <Guid, IList <MediaItemAspect> >(); string ext = StringUtils.TrimToEmpty(ProviderPathHelper.GetExtension(filename)).ToLowerInvariant(); if (ext != ".xml") { Console.WriteLine("Filetype must be XML"); return; } XmlSerializer serializer = new XmlSerializer(typeof(Tve3RecordingMetadataExtractor.Tags)); using (Stream stream = new FileStream(filename, FileMode.Open, FileAccess.Read)) { Tve3RecordingMetadataExtractor.Tags tags = (Tve3RecordingMetadataExtractor.Tags)serializer.Deserialize(stream); Tve3RecordingMetadataExtractor.SimpleTag tag = tags.Tag.Find(t => t.Name == "TITLE"); MediaItemAspect.SetAttribute(aspects, MediaAspect.ATTR_TITLE, tag.Value); } IMediaItemAspectTypeRegistration registration = ServiceRegistration.Get <IMediaItemAspectTypeRegistration>(); Console.WriteLine("Before extract:"); ShowMIAs(aspects, registration); IMetadataExtractor extractor = new Tve3RecordingMetadataExtractor(); IResourceAccessor accessor = new MockLocalFsResourceAccessor(ProviderPathHelper.ChangeExtension(filename, ".ts")); extractor.TryExtractMetadataAsync(accessor, aspects, false); Console.WriteLine("After extract:"); ShowMIAs(aspects, registration); string value; if (MediaItemAspect.TryGetExternalAttribute(aspects, ExternalIdentifierAspect.SOURCE_TVDB, ExternalIdentifierAspect.TYPE_SERIES, out value)) { SeriesInfo seriesInfo = new SeriesInfo() { TvdbId = Int32.Parse(value) }; SeriesTvDbMatcher.Instance.UpdateSeriesAsync(seriesInfo, false).Wait(); Console.WriteLine("{0}: {1}", seriesInfo.SeriesName, seriesInfo.Description); } }