protected int FindExternalSubtitles(ILocalFsResourceAccessor lfsra, IDictionary <Guid, IList <MediaItemAspect> > extractedAspectData)
        {
            int subtitleCount = 0;

            try
            {
                IList <MultipleMediaItemAspect> providerResourceAspects;
                if (!MediaItemAspect.TryGetAspects(extractedAspectData, ProviderResourceAspect.Metadata, out providerResourceAspects))
                {
                    return(0);
                }

                int newResourceIndex = -1;
                foreach (MultipleMediaItemAspect providerResourceAspect in providerResourceAspects)
                {
                    int resouceIndex = providerResourceAspect.GetAttributeValue <int>(ProviderResourceAspect.ATTR_RESOURCE_INDEX);
                    if (newResourceIndex < resouceIndex)
                    {
                        newResourceIndex = resouceIndex;
                    }
                }
                newResourceIndex++;

                using (lfsra.EnsureLocalFileSystemAccess())
                {
                    foreach (MultipleMediaItemAspect mmia in providerResourceAspects)
                    {
                        string       accessorPath = (string)mmia.GetAttributeValue(ProviderResourceAspect.ATTR_RESOURCE_ACCESSOR_PATH);
                        ResourcePath resourcePath = ResourcePath.Deserialize(accessorPath);

                        if (mmia.GetAttributeValue <int>(ProviderResourceAspect.ATTR_TYPE) != ProviderResourceAspect.TYPE_PRIMARY)
                        {
                            continue;
                        }

                        string filePath = LocalFsResourceProviderBase.ToDosPath(resourcePath);
                        if (string.IsNullOrEmpty(filePath))
                        {
                            continue;
                        }

                        List <string> subs = new List <string>();
                        int           videoResouceIndex = (int)mmia.GetAttributeValue(ProviderResourceAspect.ATTR_RESOURCE_INDEX);
                        string[]      subFiles          = Directory.GetFiles(Path.GetDirectoryName(filePath), Path.GetFileNameWithoutExtension(filePath) + "*.*");
                        if (subFiles != null)
                        {
                            subs.AddRange(subFiles);
                        }
                        foreach (string folder in SUBTITLE_FOLDERS)
                        {
                            if (string.IsNullOrEmpty(Path.GetPathRoot(folder)) && Directory.Exists(Path.Combine(Path.GetDirectoryName(filePath), folder))) //Is relative path
                            {
                                subFiles = Directory.GetFiles(Path.Combine(Path.GetDirectoryName(filePath), folder), Path.GetFileNameWithoutExtension(filePath) + "*.*");
                            }
                            else if (Directory.Exists(folder)) //Is absolute path
                            {
                                subFiles = Directory.GetFiles(folder, Path.GetFileNameWithoutExtension(filePath) + "*.*");
                            }

                            if (subFiles != null)
                            {
                                subs.AddRange(subFiles);
                            }
                        }
                        foreach (string subFile in subFiles)
                        {
                            if (!HasSubtitleExtension(subFile))
                            {
                                continue;
                            }

                            LocalFsResourceAccessor fsra = new LocalFsResourceAccessor((LocalFsResourceProvider)lfsra.ParentProvider, LocalFsResourceProviderBase.ToProviderPath(subFile));

                            //Check if already exists
                            bool exists = false;
                            foreach (MultipleMediaItemAspect providerResourceAspect in providerResourceAspects)
                            {
                                string       subAccessorPath = (string)providerResourceAspect.GetAttributeValue(ProviderResourceAspect.ATTR_RESOURCE_ACCESSOR_PATH);
                                ResourcePath subResourcePath = ResourcePath.Deserialize(subAccessorPath);
                                if (subResourcePath.Equals(fsra.CanonicalLocalResourcePath))
                                {
                                    //Already exists
                                    exists = true;
                                    break;
                                }
                            }
                            if (exists)
                            {
                                continue;
                            }

                            string subFormat = GetSubtitleFormat(subFile);
                            if (!string.IsNullOrEmpty(subFormat))
                            {
                                MultipleMediaItemAspect providerResourceAspect = MediaItemAspect.CreateAspect(extractedAspectData, ProviderResourceAspect.Metadata);
                                providerResourceAspect.SetAttribute(ProviderResourceAspect.ATTR_RESOURCE_INDEX, newResourceIndex);
                                providerResourceAspect.SetAttribute(ProviderResourceAspect.ATTR_TYPE, ProviderResourceAspect.TYPE_SECONDARY);
                                providerResourceAspect.SetAttribute(ProviderResourceAspect.ATTR_MIME_TYPE, GetSubtitleMime(subFormat));
                                providerResourceAspect.SetAttribute(ProviderResourceAspect.ATTR_SIZE, fsra.Size);
                                providerResourceAspect.SetAttribute(ProviderResourceAspect.ATTR_RESOURCE_ACCESSOR_PATH, fsra.CanonicalLocalResourcePath.Serialize());

                                MultipleMediaItemAspect subtitleResourceAspect = MediaItemAspect.CreateAspect(extractedAspectData, SubtitleAspect.Metadata);
                                subtitleResourceAspect.SetAttribute(SubtitleAspect.ATTR_RESOURCE_INDEX, newResourceIndex);
                                subtitleResourceAspect.SetAttribute(SubtitleAspect.ATTR_VIDEO_RESOURCE_INDEX, videoResouceIndex);
                                subtitleResourceAspect.SetAttribute(SubtitleAspect.ATTR_STREAM_INDEX, -1); //External subtitle
                                subtitleResourceAspect.SetAttribute(SubtitleAspect.ATTR_SUBTITLE_FORMAT, subFormat);
                                subtitleResourceAspect.SetAttribute(SubtitleAspect.ATTR_INTERNAL, false);
                                subtitleResourceAspect.SetAttribute(SubtitleAspect.ATTR_DEFAULT, subFile.ToLowerInvariant().Contains(".default."));
                                subtitleResourceAspect.SetAttribute(SubtitleAspect.ATTR_FORCED, subFile.ToLowerInvariant().Contains(".forced."));

                                bool   imageBased = IsImageBasedSubtitle(subFormat);
                                string language   = GetSubtitleLanguage(subFile, imageBased);
                                if (language != null)
                                {
                                    subtitleResourceAspect.SetAttribute(SubtitleAspect.ATTR_SUBTITLE_LANGUAGE, language);
                                }
                                if (imageBased == false)
                                {
                                    string encoding = GetSubtitleEncoding(subFile, language);
                                    if (encoding != null)
                                    {
                                        subtitleResourceAspect.SetAttribute(SubtitleAspect.ATTR_SUBTITLE_ENCODING, encoding);
                                    }
                                }
                                else
                                {
                                    subtitleResourceAspect.SetAttribute(SubtitleAspect.ATTR_SUBTITLE_ENCODING, SubtitleAspect.BINARY_ENCODING);
                                }
                                newResourceIndex++;
                                subtitleCount++;
                            }
                        }
                    }
                }
            }
            catch (Exception e)
            {
                ServiceRegistration.Get <ILogger>().Info("SubtitleMetadataExtractor: Exception finding external subtitles for resource '{0}' (Text: '{1}')", e, lfsra.CanonicalLocalResourcePath, e.Message);
            }
            return(subtitleCount);
        }
        private bool ExtractThumbnail(ILocalFsResourceAccessor lfsra, IDictionary <Guid, IList <MediaItemAspect> > extractedAspectData)
        {
            // We can only work on files and make sure this file was detected by a lower MDE before (title is set then).
            // VideoAspect must be present to be sure it is actually a video resource.
            if (!lfsra.IsFile || !extractedAspectData.ContainsKey(VideoStreamAspect.ASPECT_ID))
            {
                return(false);
            }

            byte[] thumb;
            // We only want to create missing thumbnails here, so check for existing ones first
            if (MediaItemAspect.TryGetAttribute(extractedAspectData, ThumbnailLargeAspect.ATTR_THUMBNAIL, out thumb) && thumb != null)
            {
                return(false);
            }

            //ServiceRegistration.Get<ILogger>().Info("VideoThumbnailer: Evaluate {0}", lfsra.ResourceName);

            bool isPrimaryResource = false;
            IList <MultipleMediaItemAspect> resourceAspects;

            if (MediaItemAspect.TryGetAspects(extractedAspectData, ProviderResourceAspect.Metadata, out resourceAspects))
            {
                foreach (MultipleMediaItemAspect pra in resourceAspects)
                {
                    string       accessorPath = (string)pra.GetAttributeValue(ProviderResourceAspect.ATTR_RESOURCE_ACCESSOR_PATH);
                    ResourcePath resourcePath = ResourcePath.Deserialize(accessorPath);
                    if (resourcePath.Equals(lfsra.CanonicalLocalResourcePath))
                    {
                        if (pra.GetAttributeValue <bool?>(ProviderResourceAspect.ATTR_PRIMARY) == true)
                        {
                            isPrimaryResource = true;
                            break;
                        }
                    }
                }
            }

            if (!isPrimaryResource) //Ignore subtitles
            {
                return(false);
            }

            // Check for a reasonable time offset
            long   defaultVideoOffset = 720;
            long   videoDuration;
            string downscale = ",scale=iw/2:-1"; // Reduces the video frame size to a half of original
            IList <MultipleMediaItemAspect> videoAspects;

            if (MediaItemAspect.TryGetAspects(extractedAspectData, VideoStreamAspect.Metadata, out videoAspects))
            {
                if ((videoDuration = videoAspects[0].GetAttributeValue <long>(VideoStreamAspect.ATTR_DURATION)) > 0)
                {
                    if (defaultVideoOffset > videoDuration * 1 / 3)
                    {
                        defaultVideoOffset = videoDuration * 1 / 3;
                    }
                }

                int videoWidth = videoAspects[0].GetAttributeValue <int>(VideoStreamAspect.ATTR_WIDTH);
                // Don't downscale SD video frames, quality is already quite low.
                if (videoWidth > 0 && videoWidth <= 720)
                {
                    downscale = "";
                }
            }

            // ToDo: Move creation of temp file names to FileUtils class
            string tempFileName = Path.GetTempPath() + Guid.NewGuid() + ".jpg";
            string executable   = FileUtils.BuildAssemblyRelativePath("ffmpeg.exe");
            string arguments    = string.Format("-ss {0} -i \"{1}\" -vframes 1 -an -dn -vf \"yadif='mode=send_frame:parity=auto:deint=all',scale=iw*sar:ih,setsar=1/1{3}\" -y \"{2}\"",
                                                defaultVideoOffset,
                                                // Calling EnsureLocalFileSystemAccess not necessary; access for external process ensured by ExecuteWithResourceAccess
                                                lfsra.LocalFileSystemPath,
                                                tempFileName,
                                                downscale);

            //ServiceRegistration.Get<ILogger>().Info("VideoThumbnailer: FFMpeg {0} {1}", executable, arguments);

            try
            {
                Task <ProcessExecutionResult> executionResult = null;
                FFMPEG_THROTTLE_LOCK.Wait();
                executionResult = FFMpegBinary.FFMpegExecuteWithResourceAccessAsync(lfsra, arguments, ProcessPriorityClass.BelowNormal, PROCESS_TIMEOUT_MS);
                if (executionResult.Result.Success && File.Exists(tempFileName))
                {
                    var binary = FileUtils.ReadFile(tempFileName);
                    MediaItemAspect.SetAttribute(extractedAspectData, ThumbnailLargeAspect.ATTR_THUMBNAIL, binary);
                    // Calling EnsureLocalFileSystemAccess not necessary; only string operation
                    ServiceRegistration.Get <ILogger>().Info("VideoThumbnailer: Successfully created thumbnail for resource '{0}'", lfsra.LocalFileSystemPath);
                }
                else
                {
                    // Calling EnsureLocalFileSystemAccess not necessary; only string operation
                    ServiceRegistration.Get <ILogger>().Warn("VideoThumbnailer: Failed to create thumbnail for resource '{0}'", lfsra.LocalFileSystemPath);
                    ServiceRegistration.Get <ILogger>().Debug("VideoThumbnailer: FFMpeg failure {0} dump:\n{1}", executionResult.Result.ExitCode, executionResult.Result.StandardError);
                }
            }
            catch (AggregateException ae)
            {
                ae.Handle(e =>
                {
                    if (e is TaskCanceledException)
                    {
                        ServiceRegistration.Get <ILogger>().Warn("VideoThumbnailer.ExtractThumbnail: External process aborted due to timeout: Executable='{0}', Arguments='{1}', Timeout='{2}'", executable, arguments, PROCESS_TIMEOUT_MS);
                        return(true);
                    }
                    return(false);
                });
            }
            finally
            {
                FFMPEG_THROTTLE_LOCK.Release();

                try
                {
                    if (File.Exists(tempFileName))
                    {
                        File.Delete(tempFileName);
                    }
                }
                catch { }
            }
            return(true);
        }
        private Task <bool> ExtractThumbnailAsync(ILocalFsResourceAccessor lfsra, IDictionary <Guid, IList <MediaItemAspect> > extractedAspectData)
        {
            // We can only work on files and make sure this file was detected by a lower MDE before (title is set then).
            // VideoAspect must be present to be sure it is actually a video resource.
            if (!lfsra.IsFile || !extractedAspectData.ContainsKey(VideoStreamAspect.ASPECT_ID))
            {
                return(Task.FromResult(false));
            }

            //ServiceRegistration.Get<ILogger>().Info("OCVVideoThumbnailer: Evaluate {0}", lfsra.ResourceName);

            bool isPrimaryResource = false;
            IList <MultipleMediaItemAspect> resourceAspects;

            if (MediaItemAspect.TryGetAspects(extractedAspectData, ProviderResourceAspect.Metadata, out resourceAspects))
            {
                foreach (MultipleMediaItemAspect pra in resourceAspects)
                {
                    string       accessorPath = (string)pra.GetAttributeValue(ProviderResourceAspect.ATTR_RESOURCE_ACCESSOR_PATH);
                    ResourcePath resourcePath = ResourcePath.Deserialize(accessorPath);
                    if (resourcePath.Equals(lfsra.CanonicalLocalResourcePath))
                    {
                        if (pra.GetAttributeValue <int?>(ProviderResourceAspect.ATTR_TYPE) == ProviderResourceAspect.TYPE_PRIMARY)
                        {
                            isPrimaryResource = true;
                            break;
                        }
                    }
                }
            }

            if (!isPrimaryResource) //Ignore subtitles
            {
                return(Task.FromResult(false));
            }

            // Check for a reasonable time offset
            int    defaultVideoOffset = 720;
            long   videoDuration;
            double downscale = 2; // Reduces the video frame size to a half of original
            IList <MultipleMediaItemAspect> videoAspects;

            if (MediaItemAspect.TryGetAspects(extractedAspectData, VideoStreamAspect.Metadata, out videoAspects))
            {
                if ((videoDuration = videoAspects[0].GetAttributeValue <long>(VideoStreamAspect.ATTR_DURATION)) > 0)
                {
                    if (defaultVideoOffset > videoDuration * 1 / 3)
                    {
                        defaultVideoOffset = Convert.ToInt32(videoDuration * 1 / 3);
                    }
                }

                double width  = videoAspects[0].GetAttributeValue <int>(VideoStreamAspect.ATTR_WIDTH);
                double height = videoAspects[0].GetAttributeValue <int>(VideoStreamAspect.ATTR_HEIGHT);
                downscale = width / 256.0; //256 is max size of large thumbnail aspect
            }

            using (lfsra.EnsureLocalFileSystemAccess())
            {
                using (VideoCapture capture = new VideoCapture())
                {
                    capture.Open(lfsra.LocalFileSystemPath);
                    capture.PosMsec = defaultVideoOffset * 1000;
                    using (var mat = capture.RetrieveMat())
                    {
                        if (mat.Height > 0 && mat.Width > 0)
                        {
                            double width  = mat.Width;
                            double height = mat.Height;
                            ServiceRegistration.Get <ILogger>().Debug("OCVVideoThumbnailer: Scaling thumbnail of size {1}x{2} for resource '{0}'", lfsra.LocalFileSystemPath, width, height);
                            using (var scaledMat = mat.Resize(new OpenCvSharp.Size(width / downscale, height / downscale)))
                            {
                                var binary = scaledMat.ToBytes();
                                MediaItemAspect.SetAttribute(extractedAspectData, ThumbnailLargeAspect.ATTR_THUMBNAIL, binary);
                                ServiceRegistration.Get <ILogger>().Info("OCVVideoThumbnailer: Successfully created thumbnail for resource '{0}'", lfsra.LocalFileSystemPath);
                            }
                        }
                        else
                        {
                            ServiceRegistration.Get <ILogger>().Warn("OCVVideoThumbnailer: Failed to create thumbnail for resource '{0}'", lfsra.LocalFileSystemPath);
                        }
                    }
                }
            }
            return(Task.FromResult(true));
        }
Beispiel #4
0
        private async Task <bool> ExtractThumbnailAsync(ILocalFsResourceAccessor lfsra, IDictionary <Guid, IList <MediaItemAspect> > extractedAspectData)
        {
            // We can only work on files and make sure this file was detected by a lower MDE before (title is set then).
            // VideoAspect must be present to be sure it is actually a video resource.
            if (!lfsra.IsFile || !extractedAspectData.ContainsKey(VideoStreamAspect.ASPECT_ID))
            {
                return(false);
            }

            //ServiceRegistration.Get<ILogger>().Info("VideoThumbnailer: Evaluate {0}", lfsra.ResourceName);

            bool isPrimaryResource = false;
            IList <MultipleMediaItemAspect> resourceAspects;

            if (MediaItemAspect.TryGetAspects(extractedAspectData, ProviderResourceAspect.Metadata, out resourceAspects))
            {
                foreach (MultipleMediaItemAspect pra in resourceAspects)
                {
                    string       accessorPath = (string)pra.GetAttributeValue(ProviderResourceAspect.ATTR_RESOURCE_ACCESSOR_PATH);
                    ResourcePath resourcePath = ResourcePath.Deserialize(accessorPath);
                    if (resourcePath.Equals(lfsra.CanonicalLocalResourcePath))
                    {
                        if (pra.GetAttributeValue <int?>(ProviderResourceAspect.ATTR_TYPE) == ProviderResourceAspect.TYPE_PRIMARY)
                        {
                            isPrimaryResource = true;
                            break;
                        }
                    }
                }
            }

            if (!isPrimaryResource) //Ignore subtitles
            {
                return(false);
            }

            // Check for a reasonable time offset
            long   defaultVideoOffset = 720;
            long   videoDuration;
            string downscale = ",scale='min(256,iw)':-1"; // 256 is max size of large thumbnail aspect
            IList <MultipleMediaItemAspect> videoAspects;

            if (MediaItemAspect.TryGetAspects(extractedAspectData, VideoStreamAspect.Metadata, out videoAspects))
            {
                if ((videoDuration = videoAspects[0].GetAttributeValue <long>(VideoStreamAspect.ATTR_DURATION)) > 0)
                {
                    if (defaultVideoOffset > videoDuration * 1 / 3)
                    {
                        defaultVideoOffset = videoDuration * 1 / 3;
                    }
                }
            }

            string tempFileName = FileUtils.GetTempFileName(".jpg");
            string arguments    = string.Format("-ss {0} -i \"{1}\" -vframes 1 -an -dn -vf \"yadif='mode=send_frame:parity=auto:deint=all',scale=iw*sar:ih,setsar=1/1{3}\" -y \"{2}\"",
                                                defaultVideoOffset,
                                                // Calling EnsureLocalFileSystemAccess not necessary; access for external process ensured by ExecuteWithResourceAccess
                                                lfsra.LocalFileSystemPath,
                                                tempFileName,
                                                downscale);

            //ServiceRegistration.Get<ILogger>().Info("VideoThumbnailer: FFMpeg {0} {1}", executable, arguments);

            await FFMPEG_THROTTLE_LOCK.WaitAsync().ConfigureAwait(false);

            try
            {
                ProcessExecutionResult executionResult = await FFMpegBinary.FFMpegExecuteWithResourceAccessAsync(lfsra, arguments, ProcessPriorityClass.BelowNormal, PROCESS_TIMEOUT_MS).ConfigureAwait(false);

                if (executionResult.Success && File.Exists(tempFileName))
                {
                    var binary = FileUtils.ReadFile(tempFileName);
                    MediaItemAspect.SetAttribute(extractedAspectData, ThumbnailLargeAspect.ATTR_THUMBNAIL, binary);
                    // Calling EnsureLocalFileSystemAccess not necessary; only string operation
                    ServiceRegistration.Get <ILogger>().Info("VideoThumbnailer: Successfully created thumbnail for resource '{0}'", lfsra.LocalFileSystemPath);
                }
                else
                {
                    // Calling EnsureLocalFileSystemAccess not necessary; only string operation
                    ServiceRegistration.Get <ILogger>().Warn("VideoThumbnailer: Failed to create thumbnail for resource '{0}'", lfsra.LocalFileSystemPath);
                    ServiceRegistration.Get <ILogger>().Debug("VideoThumbnailer: FFMpeg failure {0} dump:\n{1}", executionResult.ExitCode, executionResult.StandardError);
                }
            }
            catch (TaskCanceledException)
            {
                ServiceRegistration.Get <ILogger>().Warn("VideoThumbnailer: External process aborted due to timeout: Executable='{0}', Arguments='{1}'", FFMpegBinary.FFMpegPath, arguments);
            }
            finally
            {
                FFMPEG_THROTTLE_LOCK.Release();

                try
                {
                    if (File.Exists(tempFileName))
                    {
                        File.Delete(tempFileName);
                    }
                }
                catch { }
            }
            return(true);
        }