/// <summary> /// Tries to read a valid IMDB id from additional .nfo or .txt files. /// </summary> /// <param name="fsra">FileSystemResourceAccessor</param> /// <param name="imdbId">Returns a valid IMDB or <c>null</c></param> /// <returns>true if matched</returns> public static bool TryMatchImdbId(IFileSystemResourceAccessor fsra, out string imdbId) { imdbId = null; if (fsra == null) return false; // First try to find a nfo file that has the same name as our main movie. if (fsra.IsFile) foreach (string extension in NFO_EXTENSIONS) { string metaFilePath = ResourcePathHelper.ChangeExtension(fsra.CanonicalLocalResourcePath.ToString(), extension); if (TryRead(metaFilePath, out imdbId)) return true; } // Prepare a list of paths to check: for chained resource path we will also check relative parent paths (like for DVD-ISO files) List<string> pathsToCheck = new List<string> { fsra.CanonicalLocalResourcePath.ToString() }; if (fsra.CanonicalLocalResourcePath.PathSegments.Count > 1) { string canocialPath = fsra.CanonicalLocalResourcePath.ToString(); pathsToCheck.Add(canocialPath.Substring(0, canocialPath.LastIndexOf('>'))); } // Then test for special named files, like "movie.nfo" foreach (string path in pathsToCheck) foreach (string fileName in NFO_FILENAMES) { string metaFilePath = ResourcePathHelper.GetDirectoryName(path); metaFilePath = ResourcePathHelper.Combine(metaFilePath, fileName); if (TryRead(metaFilePath, out imdbId)) return true; } // Now check siblings of movie for any IMDB id containing filename. IFileSystemResourceAccessor directoryFsra = null; if (!fsra.IsFile) directoryFsra = fsra.Clone() as IFileSystemResourceAccessor; if (fsra.IsFile) directoryFsra = GetContainingDirectory(fsra); if (directoryFsra == null) return false; using (directoryFsra) foreach (IFileSystemResourceAccessor file in directoryFsra.GetFiles()) using (file) if (ImdbIdMatcher.TryMatchImdbId(file.ResourceName, out imdbId)) return true; return false; }
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); }
/// <summary> /// Executes the given <paramref name="importJob"/>. /// </summary> /// <remarks> /// This method automatically terminates if it encounters that this importer worker was suspended or that the /// given <paramref name="importJob"/> was cancelled. /// </remarks> /// <param name="importJob">Import job to be executed. The state variables of this parameter will be updated by /// this method.</param> protected void Process(ImportJob importJob) { ImportJobState state = importJob.State; if (state == ImportJobState.Finished || state == ImportJobState.Cancelled || state == ImportJobState.Erroneous) { return; } // Preparation IMediaAccessor mediaAccessor = ServiceRegistration.Get <IMediaAccessor>(); IImportResultHandler resultHandler; IMediaBrowsing mediaBrowsing; lock (_syncObj) { resultHandler = _importResultHandler; mediaBrowsing = _mediaBrowsingCallback; } if (mediaBrowsing == null || resultHandler == null) { // Can be the case if this importer worker was asynchronously suspended return; } try { try { ICollection <IMetadataExtractor> metadataExtractors = new List <IMetadataExtractor>(); foreach (Guid metadataExtractorId in importJob.MetadataExtractorIds) { IMetadataExtractor extractor; if (!mediaAccessor.LocalMetadataExtractors.TryGetValue(metadataExtractorId, out extractor)) { continue; } metadataExtractors.Add(extractor); } // Prepare import if (state == ImportJobState.Scheduled) { ServiceRegistration.Get <ILogger>().Info("ImporterWorker: Starting import job '{0}'", importJob); ImporterWorkerMessaging.SendImportMessage(ImporterWorkerMessaging.MessageType.ImportStarted, importJob.BasePath); IResourceAccessor ra; if (!importJob.BasePath.TryCreateLocalResourceAccessor(out ra)) { throw new ArgumentException(string.Format("Unable to access resource path '{0}'", importJob.BasePath)); } using (ra) { IFileSystemResourceAccessor fsra = ra as IFileSystemResourceAccessor; if (fsra != null) { // Prepare complex import process importJob.PendingResources.Add(new PendingImportResource(Guid.Empty, (IFileSystemResourceAccessor)fsra.Clone())); importJob.State = ImportJobState.Active; } else { // Simple resource import ImportSingleFile(importJob, ra, metadataExtractors, mediaBrowsing, resultHandler, mediaAccessor); lock (importJob.SyncObj) if (importJob.State == ImportJobState.Active) { importJob.State = ImportJobState.Finished; } return; } } } else { ServiceRegistration.Get <ILogger>().Info("ImporterWorker: Resuming import job '{0}' ({1} items pending)", importJob, importJob.PendingResources.Count); } // Actual import process while (importJob.HasPendingResources) { Thread.Sleep(0); CheckImportStillRunning(importJob.State); PendingImportResource pendingImportResource; lock (importJob.SyncObj) pendingImportResource = importJob.PendingResources.FirstOrDefault(); if (pendingImportResource.IsValid) { IFileSystemResourceAccessor fsra = pendingImportResource.ResourceAccessor; int numPending = importJob.PendingResources.Count; string moreResources = numPending > 1 ? string.Format(" ({0} more resources pending)", numPending) : string.Empty; ServiceRegistration.Get <ILogger>().Info("ImporterWorker: Importing '{0}'{1}", fsra.ResourcePathName, moreResources); if (fsra.IsFile && fsra.Exists) { ImportResource(importJob, fsra, pendingImportResource.ParentDirectory, metadataExtractors, resultHandler, mediaAccessor); } else if (!fsra.IsFile) { CheckImportStillRunning(importJob.State); Guid?currentDirectoryId = ImportDirectory(importJob, pendingImportResource.ParentDirectory, fsra, metadataExtractors, mediaBrowsing, resultHandler, mediaAccessor); CheckImportStillRunning(importJob.State); if (currentDirectoryId.HasValue && importJob.IncludeSubDirectories) { // Add subdirectories in front of work queue lock (importJob.SyncObj) { ICollection <IFileSystemResourceAccessor> directories = FileSystemResourceNavigator.GetChildDirectories(fsra, false); if (directories != null) { foreach (IFileSystemResourceAccessor childDirectory in directories) { importJob.PendingResources.Insert(0, new PendingImportResource(currentDirectoryId.Value, childDirectory)); } } } } } else { ServiceRegistration.Get <ILogger>().Warn("ImporterWorker: Cannot import resource '{0}': It's neither a file nor a directory", fsra.CanonicalLocalResourcePath.Serialize()); } } lock (importJob.SyncObj) importJob.PendingResources.Remove(pendingImportResource); pendingImportResource.Dispose(); } lock (importJob.SyncObj) if (importJob.State == ImportJobState.Active) { importJob.State = ImportJobState.Finished; } ServiceRegistration.Get <ILogger>().Info("ImporterWorker: Finished import job '{0}'", importJob); ImporterWorkerMessaging.SendImportMessage(ImporterWorkerMessaging.MessageType.ImportCompleted, importJob.BasePath); } catch (Exception e) { CheckSuspended(e); // Throw ImportAbortException if suspended - will skip warning and tagging job as erroneous ServiceRegistration.Get <ILogger>().Warn("ImporterWorker: Problem processing '{0}'", e, importJob); ImporterWorkerMessaging.SendImportMessage(ImporterWorkerMessaging.MessageType.ImportCompleted, importJob.BasePath); importJob.State = ImportJobState.Erroneous; } } catch (ImportSuspendedException) { ServiceRegistration.Get <ILogger>().Info("ImporterWorker: Suspending import job '{0}' ({1} items pending - will be continued next time)", importJob, importJob.PendingResources.Count); } catch (ImportAbortException) { ServiceRegistration.Get <ILogger>().Info("ImporterWorker: Aborting import job '{0}' ({1} items pending)", importJob, importJob.PendingResources.Count); } }
public bool TryExtractMetadata(IResourceAccessor mediaItemAccessor, IDictionary <Guid, MediaItemAspect> extractedAspectData, bool forceQuickMode) { try { VideoResult result = null; IFileSystemResourceAccessor fsra = mediaItemAccessor as IFileSystemResourceAccessor; if (fsra == null) { return(false); } if (!fsra.IsFile && fsra.ResourceExists("VIDEO_TS")) { IFileSystemResourceAccessor fsraVideoTs = fsra.GetResource("VIDEO_TS"); if (fsraVideoTs != null && fsraVideoTs.ResourceExists("VIDEO_TS.IFO")) { // Video DVD using (MediaInfoWrapper videoTsInfo = ReadMediaInfo(fsraVideoTs.GetResource("VIDEO_TS.IFO"))) { if (!videoTsInfo.IsValid || videoTsInfo.GetVideoCount() == 0) { return(false); // Invalid video_ts.ifo file } result = VideoResult.CreateDVDInfo(fsra.ResourceName, videoTsInfo); } // Iterate over all video files; MediaInfo finds different audio/video metadata for each .ifo file ICollection <IFileSystemResourceAccessor> files = fsraVideoTs.GetFiles(); if (files != null) { foreach (IFileSystemResourceAccessor file in files) { string lowerPath = (file.ResourcePathName ?? string.Empty).ToLowerInvariant(); if (!lowerPath.EndsWith(".ifo") || lowerPath.EndsWith("video_ts.ifo")) { continue; } using (MediaInfoWrapper mediaInfo = ReadMediaInfo(file)) { // Before we start evaluating the file, check if it is a video at all if (mediaInfo.IsValid && mediaInfo.GetVideoCount() == 0) { continue; } result.AddMediaInfo(mediaInfo); } } } } } else if (fsra.IsFile) { string filePath = fsra.ResourcePathName; if (!HasVideoExtension(filePath)) { return(false); } using (MediaInfoWrapper fileInfo = ReadMediaInfo(fsra)) { // Before we start evaluating the file, check if it is a video at all if (!fileInfo.IsValid || (fileInfo.GetVideoCount() == 0 && !IsWorkaroundRequired(filePath))) { return(false); } string mediaTitle = DosPathHelper.GetFileNameWithoutExtension(fsra.ResourceName); result = VideoResult.CreateFileInfo(mediaTitle, fileInfo); } using (Stream stream = fsra.OpenRead()) result.MimeType = MimeTypeDetector.GetMimeType(stream, DEFAULT_MIMETYPE); } if (result != null) { result.UpdateMetadata(extractedAspectData); ILocalFsResourceAccessor disposeLfsra = null; try { ILocalFsResourceAccessor lfsra = fsra as ILocalFsResourceAccessor; if (lfsra == null && !forceQuickMode) { // In case forceQuickMode, we only want local browsing IFileSystemResourceAccessor localFsra = (IFileSystemResourceAccessor)fsra.Clone(); try { lfsra = StreamedResourceToLocalFsAccessBridge.GetLocalFsResourceAccessor(localFsra); disposeLfsra = lfsra; // Remember to dispose the extra resource accessor instance } catch (Exception) { localFsra.Dispose(); } } if (lfsra != null) { string localFsPath = lfsra.LocalFileSystemPath; ExtractMatroskaTags(localFsPath, extractedAspectData, forceQuickMode); ExtractMp4Tags(localFsPath, extractedAspectData, forceQuickMode); ExtractThumbnailData(localFsPath, extractedAspectData, forceQuickMode); } } finally { if (disposeLfsra != null) { disposeLfsra.Dispose(); } } 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("VideoMetadataExtractor: Exception reading resource '{0}' (Text: '{1}')", mediaItemAccessor.CanonicalLocalResourcePath, e.Message); } return(false); }
/// <summary> /// Tries to read a valid IMDB id from additional .nfo or .txt files. /// </summary> /// <param name="fsra">FileSystemResourceAccessor</param> /// <param name="imdbId">Returns a valid IMDB or <c>null</c></param> /// <returns>true if matched</returns> public static bool TryMatchImdbId(IFileSystemResourceAccessor fsra, out string imdbId) { imdbId = null; if (fsra == null) { return(false); } // First try to find a nfo file that has the same name as our main movie. if (fsra.IsFile) { foreach (string extension in NFO_EXTENSIONS) { string metaFilePath = ResourcePathHelper.ChangeExtension(fsra.CanonicalLocalResourcePath.ToString(), extension); if (TryRead(metaFilePath, out imdbId)) { return(true); } } } // Prepare a list of paths to check: for chained resource path we will also check relative parent paths (like for DVD-ISO files) List <string> pathsToCheck = new List <string> { fsra.CanonicalLocalResourcePath.ToString() }; if (fsra.CanonicalLocalResourcePath.PathSegments.Count > 1) { string canocialPath = fsra.CanonicalLocalResourcePath.ToString(); pathsToCheck.Add(canocialPath.Substring(0, canocialPath.LastIndexOf('>'))); } // Then test for special named files, like "movie.nfo" foreach (string path in pathsToCheck) { foreach (string fileName in NFO_FILENAMES) { string metaFilePath = ResourcePathHelper.GetDirectoryName(path); metaFilePath = ResourcePathHelper.Combine(metaFilePath, fileName); if (TryRead(metaFilePath, out imdbId)) { return(true); } } } // Now check siblings of movie for any IMDB id containing filename. IFileSystemResourceAccessor directoryFsra = null; if (fsra.IsDirectory) { directoryFsra = fsra.Clone() as IFileSystemResourceAccessor; } if (fsra.IsFile) { directoryFsra = GetContainingDirectory(fsra); } if (directoryFsra == null) { return(false); } using (directoryFsra) foreach (IFileSystemResourceAccessor file in directoryFsra.GetFiles()) { using (file) if (ImdbIdMatcher.TryMatchImdbId(file.ResourceName, out imdbId)) { return(true); } } return(false); }