/// <summary> /// Processes the current media queue item. This can be a long running process and is /// intended to be invoked on a background thread. /// </summary> private void ProcessItem() { try { if (!BeginProcessItem()) { return; } MediaConversionSettings conversionResults = ExecuteMediaConversion(); OnMediaConversionComplete(conversionResults); } catch (Exception ex) { // I know it's bad form to catch all exceptions, but I don't know how to catch all // non-fatal exceptions (like ArgumentNullException) while letting the catastrophic // ones go through (like StackOverFlowException) unless we explictly catch and then // rethrow them, but that seems like it could have its own issues. Events.EventController.RecordError(ex, AppSetting.Instance, null, Factory.LoadGallerySettings()); } finally { Instance.Status = MediaQueueStatus.Idle; } ProcessNextItemInQueue(false); }
private MediaConversionSettings RotateVideo(IGalleryObject mediaObject) { var gallerySetting = Factory.LoadGallerySetting(mediaObject.GalleryId); // Determine file name and path of the new file. var dirName = Path.GetDirectoryName(mediaObject.Original.FileNamePhysicalPath) ?? String.Empty; var newFilename = HelperFunctions.ValidateFileName(dirName, mediaObject.Original.FileName); var newFilePath = Path.Combine(dirName, newFilename); const string args = @"-i ""{SourceFilePath}"" -vf ""{AutoRotateFilter}"" -q:a 0 -q:v 0 -acodec copy -metadata:s:v:0 rotate=0 ""{DestinationFilePath}"""; var encoderSetting = new MediaEncoderSettings(Path.GetExtension(newFilename), Path.GetExtension(mediaObject.Original.FileName), args, 0); var mediaSettings = new MediaConversionSettings { FilePathSource = mediaObject.Original.FileNamePhysicalPath, FilePathDestination = newFilePath, EncoderSetting = encoderSetting, GalleryId = mediaObject.GalleryId, MediaQueueId = _currentMediaQueueItemId, TimeoutMs = gallerySetting.MediaEncoderTimeoutMs, MediaObjectId = mediaObject.Id, TargetWidth = 0, TargetHeight = 0, FFmpegArgs = String.Empty, FFmpegOutput = String.Empty, CancellationToken = CancelTokenSource.Token }; mediaSettings.FFmpegOutput = FFmpeg.CreateMedia(mediaSettings); mediaSettings.FileCreated = ValidateFile(mediaSettings.FilePathDestination); return(mediaSettings); }
/// <summary> /// Complete processing the current media item by updating the media queue instance and /// reseting the status of the conversion queue. /// </summary> /// <param name="settings">An instance of <see cref="MediaConversionSettings" /> containing /// settings and results used in the conversion</param> private void CompleteProcessItem(MediaConversionSettings settings) { // Update status and persist to data store MediaQueueDto mediaQueueDto = GetCurrentMediaQueueItem(); mediaQueueDto.DateConversionCompleted = DateTime.Now; if (settings.FileCreated) { mediaQueueDto.Status = MediaQueueItemStatus.Complete.ToString(); } else { mediaQueueDto.Status = MediaQueueItemStatus.Error.ToString(); string msg = String.Format(CultureInfo.CurrentCulture, "Unable to process file '{0}'.", Path.GetFileName(settings.FilePathSource)); RecordEvent(msg, settings); } Factory.GetDataProvider().MediaQueue_Save(mediaQueueDto); // Update the item in the collection. //MediaQueueItems[mediaQueueDto.MediaQueueId] = mediaQueueDto; Reset(); }
/// <summary> /// Performs post-processing tasks on the media object and media queue items. Specifically, /// if the file was successfully created, updates the media object instance with information /// about the new file. Updates the media queue instance and resets the status of the /// conversion queue. No action is taken if <paramref name="settings" /> is null. /// </summary> /// <param name="settings">An instance of <see cref="MediaConversionSettings" /> containing /// settings and results used in the conversion. When null, the function immediately returns.</param> private void OnMediaConversionComplete(MediaConversionSettings settings) { if (settings == null) { return; } // Update media object properties IGalleryObject mediaObject = Factory.LoadMediaObjectInstance(settings.MediaObjectId, true); if (settings.FileCreated) { string msg = String.Format(CultureInfo.CurrentCulture, "INFO (not an error): FFmpeg created video '{0}'.", Path.GetFileName(settings.FilePathDestination)); RecordEvent(msg, settings); mediaObject.Optimized.FileName = Path.GetFileName(settings.FilePathDestination); mediaObject.Optimized.FileNamePhysicalPath = settings.FilePathDestination; mediaObject.Optimized.Width = mediaObject.Original.Width; mediaObject.Optimized.Height = mediaObject.Original.Height; int fileSize = (int)(mediaObject.Optimized.FileInfo.Length / 1024); mediaObject.Optimized.FileSizeKB = (fileSize < 1 ? 1 : fileSize); // Very small files should be 1, not 0. mediaObject.LastModifiedByUserName = "******"; mediaObject.DateLastModified = DateTime.Now; mediaObject.Save(); HelperFunctions.PurgeCache(); } CompleteProcessItem(settings); }
/// <summary> /// Complete processing the current media item by updating the media queue instance and /// reseting the status of the conversion queue. /// </summary> /// <param name="settings">An instance of <see cref="MediaConversionSettings" /> containing /// settings and results used in the conversion. A null value is acceptable.</param> private void CompleteProcessItem(MediaConversionSettings settings) { // Update status and persist to data store MediaQueueItem mqItem = GetCurrentMediaQueueItem(); mqItem.DateConversionCompleted = DateTime.Now; if (settings != null && settings.FileCreated) { mqItem.Status = MediaQueueItemStatus.Complete; } else { mqItem.Status = MediaQueueItemStatus.Error; var fileName = (settings != null && !String.IsNullOrEmpty(settings.FilePathSource) ? Path.GetFileName(settings.FilePathSource) : "<Unknown>"); string msg = String.Format(CultureInfo.CurrentCulture, "Unable to process file '{0}'.", fileName); RecordEvent(msg, settings); } //Factory.GetDataProvider().MediaQueue_Save(mediaQueueDto); mqItem.Save(); // Update the item in the collection. //MediaQueueItems[mediaQueueDto.MediaQueueId] = mediaQueueDto; Reset(); }
private static string ReplaceTokens(string encoderArguments, MediaConversionSettings mediaSettings) { encoderArguments = encoderArguments.Replace("{SourceFilePath}", mediaSettings.FilePathSource); encoderArguments = encoderArguments.Replace("{DestinationFilePath}", mediaSettings.FilePathDestination); encoderArguments = encoderArguments.Replace("{BinPath}", Path.Combine(AppSetting.Instance.PhysicalApplicationPath, "bin")); encoderArguments = encoderArguments.Replace("{GalleryResourcesPath}", Path.Combine(AppSetting.Instance.PhysicalApplicationPath, AppSetting.Instance.GalleryResourcesPath)); return(encoderArguments); }
/// <summary> /// Execute the FFmpeg utility with the given <paramref name="mediaSettings"/> and return the text output generated by it. /// See http://www.ffmpeg.org for documentation. /// </summary> /// <param name="mediaSettings">The media settings.</param> /// <returns> /// Returns the text output from the execution of the FFmpeg utility. This data can be parsed to learn more about the media file. /// </returns> private static string ExecuteFFmpeg(MediaConversionSettings mediaSettings) { FFmpeg ffmpeg = new FFmpeg(mediaSettings); ffmpeg.Execute(); mediaSettings.FFmpegOutput = ffmpeg.Output; return(mediaSettings.FFmpegOutput); }
private static void RecordEvent(string msg, MediaConversionSettings settings) { Exception ex = new BusinessException(msg); ex.Data.Add("FFmpeg args", settings.FFmpegArgs); ex.Data.Add("FFmpeg output", settings.FFmpegOutput); ex.Data.Add("StackTrace", Environment.StackTrace); ErrorHandler.Error.Record(ex, settings.GalleryId, Factory.LoadGallerySettings(), AppSetting.Instance); }
/// <summary> /// Performs post-processing tasks on the media object after a video has been rotated. Specifically, /// if the file was successfully created, update the media object instance with information /// about the new file. No action is taken if <paramref name="settings" /> is null. /// </summary> /// <param name="settings">An instance of <see cref="MediaConversionSettings" /> containing /// settings and results used in the conversion. May be null.</param> /// <remarks>This function is invoked only when a video is manually rotated by the user, and /// only for the original video file. Videos that are auto-rotated will be the optimized ones /// and will end up running the <see cref="OnMediaConversionCompleteOptimizedCreated(MediaConversionSettings)" /> /// function instead of this one.</remarks> private static void OnMediaConversionCompleteVideoRotated(MediaConversionSettings settings) { if (settings == null) { return; } var mediaObject = Factory.LoadMediaObjectInstance(settings.MediaObjectId, true); if (settings.FileCreated) { string msg = String.Format(CultureInfo.CurrentCulture, "FFmpeg created file '{0}'.", Path.GetFileName(settings.FilePathDestination)); RecordEvent(msg, settings); // Step 1: Update the width and height of the original video file, if we have that info. var originalWidth = FFmpeg.ParseOutputVideoWidth(settings.FFmpegOutput); var originalHeight = FFmpeg.ParseOutputVideoHeight(settings.FFmpegOutput); if (originalWidth > int.MinValue) { mediaObject.Original.Width = originalWidth; } if (originalHeight > int.MinValue) { mediaObject.Original.Height = originalHeight; } // Step 2: Delete the original file and rename the new one to match the original. if ((settings.FilePathDestination != mediaObject.Original.FileNamePhysicalPath) && File.Exists(mediaObject.Original.FileNamePhysicalPath)) { var curFilePath = mediaObject.Original.FileNamePhysicalPath; File.Delete(curFilePath); File.Move(settings.FilePathDestination, curFilePath); settings.FilePathDestination = curFilePath; } else { // I don't expect we'll ever get here, but just to be safe... mediaObject.Original.FileName = Path.GetFileName(settings.FilePathDestination); mediaObject.Original.FileNamePhysicalPath = settings.FilePathDestination; } int fileSize = (int)(mediaObject.Original.FileInfo.Length / 1024); mediaObject.Original.FileSizeKB = (fileSize < 1 ? 1 : fileSize); // Very small files should be 1, not 0. RefreshOriginalVideoMetadata(mediaObject); } // Step 3: Save and finish up. mediaObject.LastModifiedByUserName = GlobalConstants.SystemUserName; mediaObject.DateLastModified = DateTime.Now; mediaObject.RegenerateOptimizedOnSave = true; mediaObject.RegenerateThumbnailOnSave = true; mediaObject.Save(); }
/// <summary> /// Executes the actual media conversion, returning an object that contains settings and the /// results of the conversion. /// </summary> /// <param name="mediaObject">The media object.</param> /// <param name="encoderSetting">The encoder setting that defines the conversion parameters.</param> /// <returns> /// Returns an instance of <see cref="MediaConversionSettings"/> containing settings and /// results used in the conversion. /// </returns> /// <exception cref="ArgumentNullException">Thrown when <paramref name="mediaObject" /> or /// <paramref name="encoderSetting" /> is null.</exception> private MediaConversionSettings ExecuteMediaConversion(IGalleryObject mediaObject, IMediaEncoderSettings encoderSetting) { if (mediaObject == null) { throw new ArgumentNullException("mediaObject"); } if (encoderSetting == null) { throw new ArgumentNullException("encoderSetting"); } AttemptedEncoderSettings.Add(encoderSetting); IGallerySettings gallerySetting = Factory.LoadGallerySetting(mediaObject.GalleryId); // Determine file name and path of the new file. string optimizedPath = HelperFunctions.MapAlbumDirectoryStructureToAlternateDirectory(mediaObject.Original.FileInfo.DirectoryName, gallerySetting.FullOptimizedPath, gallerySetting.FullMediaObjectPath); string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(mediaObject.Original.FileInfo.Name); string newFilename = GenerateNewFilename(optimizedPath, fileNameWithoutExtension, encoderSetting.DestinationFileExtension, gallerySetting.OptimizedFileNamePrefix); string newFilePath = Path.Combine(optimizedPath, newFilename); MediaConversionSettings mediaSettings = new MediaConversionSettings { FilePathSource = mediaObject.Original.FileNamePhysicalPath, FilePathDestination = newFilePath, EncoderSetting = encoderSetting, GalleryId = mediaObject.GalleryId, MediaQueueId = _currentMediaQueueItemId, TimeoutMs = gallerySetting.MediaEncoderTimeoutMs, MediaObjectId = mediaObject.Id, FFmpegArgs = String.Empty, FFmpegOutput = String.Empty, CancellationToken = CancelTokenSource.Token }; mediaSettings.FFmpegOutput = FFmpeg.CreateMedia(mediaSettings); mediaSettings.FileCreated = ValidateFile(mediaSettings.FilePathDestination); if (!mediaSettings.FileCreated) { // Could not create the requested version of the file. Record the event, then try again, // using the next encoder setting (if one exists). string msg = String.Format(CultureInfo.CurrentCulture, "FAILURE: FFmpeg was not able to create video '{0}'.", Path.GetFileName(mediaSettings.FilePathDestination)); RecordEvent(msg, mediaSettings); IMediaEncoderSettings nextEncoderSetting = GetEncoderSetting(mediaObject); if (nextEncoderSetting != null) { return(ExecuteMediaConversion(mediaObject, nextEncoderSetting)); } } return(mediaSettings); }
private static string ReplaceTokens(string encoderArguments, MediaConversionSettings mediaSettings) { encoderArguments = encoderArguments.Replace("{SourceFilePath}", mediaSettings.FilePathSource); encoderArguments = encoderArguments.Replace("{Width}", mediaSettings.TargetWidth.ToString(CultureInfo.InvariantCulture)); encoderArguments = encoderArguments.Replace("{Height}", mediaSettings.TargetHeight.ToString(CultureInfo.InvariantCulture)); encoderArguments = encoderArguments.Replace(AutoRotateFilterName, GetAutoRotationFilter(MediaConversionQueue.Instance.Get(mediaSettings.MediaQueueId).RotationAmount, encoderArguments)); encoderArguments = encoderArguments.Replace("{AspectRatio}", Math.Round(mediaSettings.TargetWidth / (double)mediaSettings.TargetHeight, 2).ToString(CultureInfo.InvariantCulture)); encoderArguments = encoderArguments.Replace("{DestinationFilePath}", mediaSettings.FilePathDestination); encoderArguments = encoderArguments.Replace("{BinPath}", Path.Combine(AppSetting.Instance.PhysicalApplicationPath, "bin")); encoderArguments = encoderArguments.Replace("{GalleryResourcesPath}", Path.Combine(AppSetting.Instance.PhysicalApplicationPath, AppSetting.Instance.GalleryResourcesPath)); // If the above changes result in an empty filter setting, remove it altogether. encoderArguments = encoderArguments.Replace(@"-vf """"", String.Empty); return(encoderArguments); }
/// <summary> /// Execute the FFmpeg utility with the given <paramref name="arguments" /> and return the text output generated by it. /// A default timeout value of 3 seconds is used. See http://www.ffmpeg.org for documentation. /// </summary> /// <param name="arguments">The argument values to pass to the FFmpeg utility. /// Example: -ss 00:00:03 -i "D:\media\video\myvideo.flv" -an -vframes 1 -y "D:\media\video\zThumb_myvideo.jpg"</param> /// <param name="galleryId">The gallery ID.</param> /// <returns>Returns the text output from the execution of the FFmpeg utility. This data can be parsed to learn more about the media file.</returns> private static string ExecuteFFmpeg(string arguments, int galleryId) { MediaConversionSettings mediaSettings = new MediaConversionSettings { FilePathSource = String.Empty, FilePathDestination = String.Empty, EncoderSetting = null, GalleryId = galleryId, MediaQueueId = int.MinValue, TimeoutMs = 3000, // 3-second timeout MediaObjectId = int.MinValue, FFmpegArgs = arguments, FFmpegOutput = String.Empty }; return(ExecuteFFmpeg(mediaSettings)); }
private static void RecordEvent(string msg, MediaConversionSettings settings) { int?galleryId = null; Dictionary <string, string> data = null; if (settings != null) { galleryId = settings.GalleryId; data = new Dictionary <string, string> { { "FFmpeg args", settings.FFmpegArgs }, { "FFmpeg output", settings.FFmpegOutput }, { "StackTrace", Environment.StackTrace } }; } Events.EventController.RecordEvent(msg, EventType.Info, galleryId, Factory.LoadGallerySettings(), AppSetting.Instance, data); }
/// <summary> /// Creates a media file based on an existing one using the values in the /// <paramref name="mediaSettings" /> parameter. The output from FFmpeg is returned. The /// arguments passed to FFmpeg are stored on the /// <see cref="MediaConversionSettings.FFmpegArgs" /> property. /// </summary> /// <param name="mediaSettings">The settings which dicate the media file creation process.</param> /// <returns>Returns the text output from FFmpeg.</returns> public static string CreateMedia(MediaConversionSettings mediaSettings) { if (!IsAvailable) { return(String.Empty); } if (mediaSettings == null) { throw new ArgumentNullException("mediaSettings"); } if (mediaSettings.EncoderSetting == null) { throw new ArgumentNullException("mediaSettings", "The EncoderSetting property on the mediaSettings parameter was null."); } mediaSettings.FFmpegArgs = ReplaceTokens(mediaSettings.EncoderSetting.EncoderArguments, mediaSettings); return(ExecuteFFmpeg(mediaSettings)); }
/// <summary> /// Performs post-processing tasks on the media object and media queue items. Specifically, /// if the file was successfully created, updates the media object instance with information /// about the new file. Updates the media queue instance and resets the status of the /// conversion queue. /// </summary> /// <param name="settings">An instance of <see cref="MediaConversionSettings" /> containing /// settings and results used in the conversion. May be null.</param> private void OnMediaConversionComplete(MediaConversionSettings settings) { try { switch (GetCurrentMediaQueueItem().ConversionType) { case MediaQueueItemConversionType.CreateOptimized: OnMediaConversionCompleteOptimizedCreated(settings); break; case MediaQueueItemConversionType.RotateVideo: OnMediaConversionCompleteVideoRotated(settings); break; } } finally { HelperFunctions.PurgeCache(); CompleteProcessItem(settings); } }
private FFmpeg(MediaConversionSettings mediaSettings) { MediaSettings = mediaSettings; _output = new StringBuilder(); }
/// <summary> /// Creates a media file based on an existing one using the values in the /// <paramref name="mediaSettings" /> parameter. The output from FFmpeg is returned. The /// arguments passed to FFmpeg are stored on the /// <see cref="MediaConversionSettings.FFmpegArgs" /> property. /// </summary> /// <param name="mediaSettings">The settings which dicate the media file creation process.</param> /// <returns>Returns the text output from FFmpeg.</returns> public static string CreateMedia(MediaConversionSettings mediaSettings) { if (!IsAvailable) { return String.Empty; } if (mediaSettings == null) throw new ArgumentNullException("mediaSettings"); if (mediaSettings.EncoderSetting == null) throw new ArgumentNullException("mediaSettings", "The EncoderSetting property on the mediaSettings parameter was null."); mediaSettings.FFmpegArgs = ReplaceTokens(mediaSettings.EncoderSetting.EncoderArguments, mediaSettings); return ExecuteFFmpeg(mediaSettings); }
/// <summary> /// Execute the FFmpeg utility with the given <paramref name="mediaSettings"/> and return the text output generated by it. /// See http://www.ffmpeg.org for documentation. /// </summary> /// <param name="mediaSettings">The media settings.</param> /// <returns> /// Returns the text output from the execution of the FFmpeg utility. This data can be parsed to learn more about the media file. /// </returns> private static string ExecuteFFmpeg(MediaConversionSettings mediaSettings) { FFmpeg ffmpeg = new FFmpeg(mediaSettings); ffmpeg.Execute(); mediaSettings.FFmpegOutput = ffmpeg.Output; return mediaSettings.FFmpegOutput; }
private static string ReplaceTokens(string encoderArguments, MediaConversionSettings mediaSettings) { encoderArguments = encoderArguments.Replace("{SourceFilePath}", mediaSettings.FilePathSource); encoderArguments = encoderArguments.Replace("{DestinationFilePath}", mediaSettings.FilePathDestination); encoderArguments = encoderArguments.Replace("{BinPath}", Path.Combine(AppSetting.Instance.PhysicalApplicationPath, "bin")); encoderArguments = encoderArguments.Replace("{GalleryResourcesPath}", Path.Combine(AppSetting.Instance.PhysicalApplicationPath, AppSetting.Instance.GalleryResourcesPath)); return encoderArguments; }
private MediaConversionSettings RotateVideo(IGalleryObject mediaObject) { var gallerySetting = Factory.LoadGallerySetting(mediaObject.GalleryId); // Determine file name and path of the new file. var dirName = Path.GetDirectoryName(mediaObject.Original.FileNamePhysicalPath) ?? String.Empty; var newFilename = HelperFunctions.ValidateFileName(dirName, mediaObject.Original.FileName); var newFilePath = Path.Combine(dirName, newFilename); const string args = @"-i ""{SourceFilePath}"" -vf ""{AutoRotateFilter}"" -q:a 0 -q:v 0 -acodec copy -metadata:s:v:0 rotate=0 ""{DestinationFilePath}"""; var encoderSetting = new MediaEncoderSettings(Path.GetExtension(newFilename), Path.GetExtension(mediaObject.Original.FileName), args, 0); var mediaSettings = new MediaConversionSettings { FilePathSource = mediaObject.Original.FileNamePhysicalPath, FilePathDestination = newFilePath, EncoderSetting = encoderSetting, GalleryId = mediaObject.GalleryId, MediaQueueId = _currentMediaQueueItemId, TimeoutMs = gallerySetting.MediaEncoderTimeoutMs, MediaObjectId = mediaObject.Id, TargetWidth = 0, TargetHeight = 0, FFmpegArgs = String.Empty, FFmpegOutput = String.Empty, CancellationToken = CancelTokenSource.Token }; mediaSettings.FFmpegOutput = FFmpeg.CreateMedia(mediaSettings); mediaSettings.FileCreated = ValidateFile(mediaSettings.FilePathDestination); return mediaSettings; }
/// <summary> /// Execute the FFmpeg utility with the given <paramref name="arguments" /> and return the text output generated by it. /// A default timeout value of 3 seconds is used. See http://www.ffmpeg.org for documentation. /// </summary> /// <param name="arguments">The argument values to pass to the FFmpeg utility. /// Example: -ss 00:00:03 -i "D:\media\video\myvideo.flv" -an -vframes 1 -y "D:\media\video\zThumb_myvideo.jpg"</param> /// <param name="galleryId">The gallery ID.</param> /// <returns>Returns the text output from the execution of the FFmpeg utility. This data can be parsed to learn more about the media file.</returns> private static string ExecuteFFmpeg(string arguments, int galleryId) { MediaConversionSettings mediaSettings = new MediaConversionSettings { FilePathSource = String.Empty, FilePathDestination = String.Empty, EncoderSetting = null, GalleryId = galleryId, MediaQueueId = int.MinValue, TimeoutMs = 3000, // 3-second timeout MediaObjectId = int.MinValue, FFmpegArgs = arguments, FFmpegOutput = String.Empty }; return ExecuteFFmpeg(mediaSettings); }
/// <summary> /// Performs post-processing tasks on the media object after an optimized file has been created. Specifically, /// if the file was successfully created, update the media object instance with information /// about the new file. No action is taken if <paramref name="settings" /> is null. /// </summary> /// <param name="settings">An instance of <see cref="MediaConversionSettings" /> containing /// settings and results used in the conversion. May be null.</param> private static void OnMediaConversionCompleteOptimizedCreated(MediaConversionSettings settings) { if (settings == null) { return; } var mediaObject = Factory.LoadMediaObjectInstance(settings.MediaObjectId, true); // Step 1: Update the media object with info about the newly created file. if (settings.FileCreated) { string msg = String.Format(CultureInfo.CurrentCulture, "FFmpeg created file '{0}'.", Path.GetFileName(settings.FilePathDestination)); RecordEvent(msg, settings); if (mediaObject.GalleryObjectType == GalleryObjectType.Video) { var width = FFmpeg.ParseOutputVideoWidth(settings.FFmpegOutput); var height = FFmpeg.ParseOutputVideoHeight(settings.FFmpegOutput); if (width > int.MinValue) { mediaObject.Optimized.Width = width; } if (height > int.MinValue) { mediaObject.Optimized.Height = height; } } else { mediaObject.Optimized.Width = settings.TargetWidth; mediaObject.Optimized.Height = settings.TargetHeight; } // Step 2: If we already had an optimized file and we just created a second one, delete the first one // and rename the new one to match the first one. var optFileDifferentThanOriginal = !String.Equals(mediaObject.Optimized.FileName, mediaObject.Original.FileName, StringComparison.InvariantCultureIgnoreCase); var optFileDifferentThanCreatedFile = !String.Equals(mediaObject.Optimized.FileName, Path.GetFileName(settings.FilePathDestination), StringComparison.InvariantCultureIgnoreCase); if (optFileDifferentThanOriginal && optFileDifferentThanCreatedFile && File.Exists(mediaObject.Optimized.FileNamePhysicalPath)) { var curFilePath = mediaObject.Optimized.FileNamePhysicalPath; File.Delete(curFilePath); var optFileExtDifferentThanCreatedFileExt = !Path.GetExtension(curFilePath).Equals(Path.GetExtension(settings.FilePathDestination), StringComparison.InvariantCultureIgnoreCase); if (optFileExtDifferentThanCreatedFileExt) { // Extension of created file is different than current optimized file. This can happen, for example, when syncing after // changing encoder settings to produce MP4's instead of FLV's. Use the filename of the current optimized file and combine // it with the extension of the created file. var newOptFileName = String.Concat(Path.GetFileNameWithoutExtension(curFilePath), Path.GetExtension(settings.FilePathDestination)); var newOptFilePath = String.Concat(Path.GetDirectoryName(curFilePath), Path.DirectorySeparatorChar, newOptFileName); if (!settings.FilePathDestination.Equals(newOptFilePath, StringComparison.InvariantCultureIgnoreCase)) { // Calculated file name differs from the one that was generated, so rename it, deleting any existing file first. if (File.Exists(newOptFilePath)) { File.Delete(newOptFilePath); } File.Move(settings.FilePathDestination, newOptFilePath); settings.FilePathDestination = newOptFilePath; } mediaObject.Optimized.FileName = newOptFileName; mediaObject.Optimized.FileNamePhysicalPath = newOptFilePath; } else { File.Move(settings.FilePathDestination, curFilePath); settings.FilePathDestination = curFilePath; } } else { // We typically get here when the media object is first added. mediaObject.Optimized.FileName = Path.GetFileName(settings.FilePathDestination); mediaObject.Optimized.FileNamePhysicalPath = settings.FilePathDestination; } // Now that we have the optimized file name all set, grab it's size. int fileSize = (int)(mediaObject.Optimized.FileInfo.Length / 1024); mediaObject.Optimized.FileSizeKB = (fileSize < 1 ? 1 : fileSize); // Very small files should be 1, not 0. } // Step 3: Save and finish up. mediaObject.LastModifiedByUserName = GlobalConstants.SystemUserName; mediaObject.DateLastModified = DateTime.Now; mediaObject.Save(); }
/// <summary> /// Performs post-processing tasks on the media object after an optimized file has been created. Specifically, /// if the file was successfully created, update the media object instance with information /// about the new file. No action is taken if <paramref name="settings" /> is null. /// </summary> /// <param name="settings">An instance of <see cref="MediaConversionSettings" /> containing /// settings and results used in the conversion. May be null.</param> private static void OnMediaConversionCompleteOptimizedCreated(MediaConversionSettings settings) { if (settings == null) return; var mediaObject = Factory.LoadMediaObjectInstance(settings.MediaObjectId, true); // Step 1: Update the media object with info about the newly created file. if (settings.FileCreated) { string msg = String.Format(CultureInfo.CurrentCulture, "FFmpeg created file '{0}'.", Path.GetFileName(settings.FilePathDestination)); RecordEvent(msg, settings); if (mediaObject.GalleryObjectType == GalleryObjectType.Video) { var width = FFmpeg.ParseOutputVideoWidth(settings.FFmpegOutput); var height = FFmpeg.ParseOutputVideoHeight(settings.FFmpegOutput); if (width > int.MinValue) mediaObject.Optimized.Width = width; if (height > int.MinValue) mediaObject.Optimized.Height = height; } else { mediaObject.Optimized.Width = settings.TargetWidth; mediaObject.Optimized.Height = settings.TargetHeight; } // Step 2: If we already had an optimized file and we just created a second one, delete the first one // and rename the new one to match the first one. var optFileDifferentThanOriginal = !String.Equals(mediaObject.Optimized.FileName, mediaObject.Original.FileName, StringComparison.InvariantCultureIgnoreCase); var optFileDifferentThanCreatedFile = !String.Equals(mediaObject.Optimized.FileName, Path.GetFileName(settings.FilePathDestination), StringComparison.InvariantCultureIgnoreCase); if (optFileDifferentThanOriginal && optFileDifferentThanCreatedFile && File.Exists(mediaObject.Optimized.FileNamePhysicalPath)) { var curFilePath = mediaObject.Optimized.FileNamePhysicalPath; File.Delete(curFilePath); var optFileExtDifferentThanCreatedFileExt = !Path.GetExtension(curFilePath).Equals(Path.GetExtension(settings.FilePathDestination), StringComparison.InvariantCultureIgnoreCase); if (optFileExtDifferentThanCreatedFileExt) { // Extension of created file is different than current optimized file. This can happen, for example, when syncing after // changing encoder settings to produce MP4's instead of FLV's. Use the filename of the current optimized file and combine // it with the extension of the created file. var newOptFileName = String.Concat(Path.GetFileNameWithoutExtension(curFilePath), Path.GetExtension(settings.FilePathDestination)); var newOptFilePath = String.Concat(Path.GetDirectoryName(curFilePath), Path.DirectorySeparatorChar, newOptFileName); if (!settings.FilePathDestination.Equals(newOptFilePath, StringComparison.InvariantCultureIgnoreCase)) { // Calculated file name differs from the one that was generated, so rename it, deleting any existing file first. if (File.Exists(newOptFilePath)) { File.Delete(newOptFilePath); } File.Move(settings.FilePathDestination, newOptFilePath); settings.FilePathDestination = newOptFilePath; } mediaObject.Optimized.FileName = newOptFileName; mediaObject.Optimized.FileNamePhysicalPath = newOptFilePath; } else { File.Move(settings.FilePathDestination, curFilePath); settings.FilePathDestination = curFilePath; } } else { // We typically get here when the media object is first added. mediaObject.Optimized.FileName = Path.GetFileName(settings.FilePathDestination); mediaObject.Optimized.FileNamePhysicalPath = settings.FilePathDestination; } // Now that we have the optimized file name all set, grab it's size. int fileSize = (int)(mediaObject.Optimized.FileInfo.Length / 1024); mediaObject.Optimized.FileSizeKB = (fileSize < 1 ? 1 : fileSize); // Very small files should be 1, not 0. } // Step 3: Save and finish up. mediaObject.LastModifiedByUserName = GlobalConstants.SystemUserName; mediaObject.DateLastModified = DateTime.Now; mediaObject.Save(); }
/// <summary> /// Performs post-processing tasks on the media object after an optimized file has been created. Specifically, /// if the file was successfully created, update the media object instance with information /// about the new file. No action is taken if <paramref name="settings" /> is null. /// </summary> /// <param name="settings">An instance of <see cref="MediaConversionSettings" /> containing /// settings and results used in the conversion. May be null.</param> private static void OnMediaConversionCompleteOptimizedCreated(MediaConversionSettings settings) { if (settings == null) { return; } var mediaObject = Factory.LoadMediaObjectInstance(settings.MediaObjectId, true); // Step 1: Update the media object with info about the newly created file. if (settings.FileCreated) { string msg = String.Format(CultureInfo.CurrentCulture, "FFmpeg created file '{0}'.", Path.GetFileName(settings.FilePathDestination)); RecordEvent(msg, settings); if (mediaObject.GalleryObjectType == GalleryObjectType.Video) { var width = FFmpeg.ParseOutputVideoWidth(settings.FFmpegOutput); var height = FFmpeg.ParseOutputVideoHeight(settings.FFmpegOutput); if (width > int.MinValue) { mediaObject.Optimized.Width = width; } if (height > int.MinValue) { mediaObject.Optimized.Height = height; } } else { mediaObject.Optimized.Width = settings.TargetWidth; mediaObject.Optimized.Height = settings.TargetHeight; } int fileSize = (int)(mediaObject.Optimized.FileInfo.Length / 1024); mediaObject.Optimized.FileSizeKB = (fileSize < 1 ? 1 : fileSize); // Very small files should be 1, not 0. // Step 2: If we already had an optimized file and we just created a second one, delete the first one // and rename the new one to match the first one. var optFileDifferentThanOriginal = !String.Equals(mediaObject.Optimized.FileName, mediaObject.Original.FileName, StringComparison.OrdinalIgnoreCase); var optFileDifferentThanCreatedFile = !String.Equals(mediaObject.Optimized.FileName, Path.GetFileName(settings.FilePathDestination), StringComparison.OrdinalIgnoreCase); if (optFileDifferentThanOriginal && optFileDifferentThanCreatedFile && File.Exists(mediaObject.Optimized.FileNamePhysicalPath)) { var curFilePath = mediaObject.Optimized.FileNamePhysicalPath; File.Delete(curFilePath); File.Move(settings.FilePathDestination, curFilePath); settings.FilePathDestination = curFilePath; } else { // We typically get here when the media object is first added. mediaObject.Optimized.FileName = Path.GetFileName(settings.FilePathDestination); mediaObject.Optimized.FileNamePhysicalPath = settings.FilePathDestination; } } // Step 3: Save and finish up. mediaObject.LastModifiedByUserName = GlobalConstants.SystemUserName; mediaObject.DateLastModified = DateTime.Now; mediaObject.Save(); }
private MediaConversionSettings CreateOptimizedMediaObject(IGalleryObject mediaObject, IMediaEncoderSettings encoderSetting) { AttemptedEncoderSettings.Add(encoderSetting); IGallerySettings gallerySetting = Factory.LoadGallerySetting(mediaObject.GalleryId); // Determine file name and path of the new file. string optimizedPath = HelperFunctions.MapAlbumDirectoryStructureToAlternateDirectory(mediaObject.Original.FileInfo.DirectoryName, gallerySetting.FullOptimizedPath, gallerySetting.FullMediaObjectPath); string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(mediaObject.Original.FileInfo.Name); string newFilename = GenerateNewFilename(optimizedPath, fileNameWithoutExtension, encoderSetting.DestinationFileExtension, gallerySetting.OptimizedFileNamePrefix); string newFilePath = Path.Combine(optimizedPath, newFilename); var mediaSettings = new MediaConversionSettings { FilePathSource = mediaObject.Original.FileNamePhysicalPath, FilePathDestination = newFilePath, EncoderSetting = encoderSetting, GalleryId = mediaObject.GalleryId, MediaQueueId = _currentMediaQueueItemId, TimeoutMs = gallerySetting.MediaEncoderTimeoutMs, MediaObjectId = mediaObject.Id, TargetWidth = GetTargetWidth(mediaObject, gallerySetting, encoderSetting), TargetHeight = GetTargetHeight(mediaObject, gallerySetting, encoderSetting), FFmpegArgs = String.Empty, FFmpegOutput = String.Empty, CancellationToken = CancelTokenSource.Token, UseCustomTool=true }; mediaSettings.FFmpegOutput = FFmpeg.CreateMedia(mediaSettings); mediaSettings.FileCreated = ValidateFile(mediaSettings.FilePathDestination); if (!mediaSettings.FileCreated) { // Could not create the requested version of the file. Record the event, then try again, // using the next encoder setting (if one exists). string msg = String.Format(CultureInfo.CurrentCulture, "FAILURE: FFmpeg was not able to create file '{0}'.", Path.GetFileName(mediaSettings.FilePathDestination)); RecordEvent(msg, mediaSettings); IMediaEncoderSettings nextEncoderSetting = GetEncoderSetting(mediaObject); if (nextEncoderSetting != null) { return ExecuteMediaConversion(mediaObject, nextEncoderSetting); } } return mediaSettings; }
/// <summary> /// Performs post-processing tasks on the media object and media queue items. Specifically, /// if the file was successfully created, updates the media object instance with information /// about the new file. Updates the media queue instance and resets the status of the /// conversion queue. No action is taken if <paramref name="settings" /> is null. /// </summary> /// <param name="settings">An instance of <see cref="MediaConversionSettings" /> containing /// settings and results used in the conversion. When null, the function immediately returns.</param> private void OnMediaConversionComplete(MediaConversionSettings settings) { if (settings == null) return; // Update media object properties IGalleryObject mediaObject = Factory.LoadMediaObjectInstance(settings.MediaObjectId, true); if (settings.FileCreated) { string msg = String.Format(CultureInfo.CurrentCulture, "INFO (not an error): FFmpeg created video '{0}'.", Path.GetFileName(settings.FilePathDestination)); RecordEvent(msg, settings); mediaObject.Optimized.FileName = Path.GetFileName(settings.FilePathDestination); mediaObject.Optimized.FileNamePhysicalPath = settings.FilePathDestination; mediaObject.Optimized.Width = mediaObject.Original.Width; mediaObject.Optimized.Height = mediaObject.Original.Height; int fileSize = (int)(mediaObject.Optimized.FileInfo.Length / 1024); mediaObject.Optimized.FileSizeKB = (fileSize < 1 ? 1 : fileSize); // Very small files should be 1, not 0. mediaObject.LastModifiedByUserName = "******"; mediaObject.DateLastModified = DateTime.Now; mediaObject.Save(); HelperFunctions.PurgeCache(); } CompleteProcessItem(settings); }
private static void RecordEvent(string msg, MediaConversionSettings settings) { int? galleryId = null; Dictionary<string, string> data = null; if (settings != null) { galleryId = settings.GalleryId; data = new Dictionary<string, string> { {"FFmpeg args", settings.FFmpegArgs}, {"FFmpeg output", settings.FFmpegOutput}, {"StackTrace", Environment.StackTrace} }; } Events.EventController.RecordEvent(msg, EventType.Info, galleryId, Factory.LoadGallerySettings(), AppSetting.Instance, data); }
/// <summary> /// Performs post-processing tasks on the media object after a video has been rotated. Specifically, /// if the file was successfully created, update the media object instance with information /// about the new file. No action is taken if <paramref name="settings" /> is null. /// </summary> /// <param name="settings">An instance of <see cref="MediaConversionSettings" /> containing /// settings and results used in the conversion. May be null.</param> /// <remarks>This function is invoked only when a video is manually rotated by the user, and /// only for the original video file. Videos that are auto-rotated will be the optimized ones /// and will end up running the <see cref="OnMediaConversionCompleteOptimizedCreated(MediaConversionSettings)" /> /// function instead of this one.</remarks> private static void OnMediaConversionCompleteVideoRotated(MediaConversionSettings settings) { if (settings == null) return; var mediaObject = Factory.LoadMediaObjectInstance(settings.MediaObjectId, true); if (settings.FileCreated) { string msg = String.Format(CultureInfo.CurrentCulture, "FFmpeg created file '{0}'.", Path.GetFileName(settings.FilePathDestination)); RecordEvent(msg, settings); // Step 1: Update the width and height of the original video file, if we have that info. var originalWidth = FFmpeg.ParseOutputVideoWidth(settings.FFmpegOutput); var originalHeight = FFmpeg.ParseOutputVideoHeight(settings.FFmpegOutput); if (originalWidth > int.MinValue) mediaObject.Original.Width = originalWidth; if (originalHeight > int.MinValue) mediaObject.Original.Height = originalHeight; // Step 2: Delete the original file and rename the new one to match the original. if ((settings.FilePathDestination != mediaObject.Original.FileNamePhysicalPath) && File.Exists(mediaObject.Original.FileNamePhysicalPath)) { var curFilePath = mediaObject.Original.FileNamePhysicalPath; File.Delete(curFilePath); File.Move(settings.FilePathDestination, curFilePath); settings.FilePathDestination = curFilePath; } else { // I don't expect we'll ever get here, but just to be safe... mediaObject.Original.FileName = Path.GetFileName(settings.FilePathDestination); mediaObject.Original.FileNamePhysicalPath = settings.FilePathDestination; } int fileSize = (int)(mediaObject.Original.FileInfo.Length / 1024); mediaObject.Original.FileSizeKB = (fileSize < 1 ? 1 : fileSize); // Very small files should be 1, not 0. RefreshOriginalVideoMetadata(mediaObject); } // Step 3: Save and finish up. mediaObject.LastModifiedByUserName = GlobalConstants.SystemUserName; mediaObject.DateLastModified = DateTime.Now; mediaObject.RegenerateOptimizedOnSave = true; mediaObject.RegenerateThumbnailOnSave = true; mediaObject.Save(); }
private static string ReplaceTokens(string encoderArguments, MediaConversionSettings mediaSettings) { encoderArguments = encoderArguments.Replace("{SourceFilePath}", mediaSettings.FilePathSource); encoderArguments = encoderArguments.Replace("{Width}", mediaSettings.TargetWidth.ToString(CultureInfo.InvariantCulture)); encoderArguments = encoderArguments.Replace("{Height}", mediaSettings.TargetHeight.ToString(CultureInfo.InvariantCulture)); encoderArguments = encoderArguments.Replace(AutoRotateFilterName, GetAutoRotationFilter(MediaConversionQueue.Instance.Get(mediaSettings.MediaQueueId).RotationAmount, encoderArguments)); encoderArguments = encoderArguments.Replace("{AspectRatio}", Math.Round(mediaSettings.TargetWidth / (double)mediaSettings.TargetHeight, 2).ToString(CultureInfo.InvariantCulture)); encoderArguments = encoderArguments.Replace("{DestinationFilePath}", mediaSettings.FilePathDestination); encoderArguments = encoderArguments.Replace("{BinPath}", Path.Combine(AppSetting.Instance.PhysicalApplicationPath, "bin")); encoderArguments = encoderArguments.Replace("{GalleryResourcesPath}", Path.Combine(AppSetting.Instance.PhysicalApplicationPath, AppSetting.Instance.GalleryResourcesPath)); // If the above changes result in an empty filter setting, remove it altogether. encoderArguments = encoderArguments.Replace(@"-vf """"", String.Empty); return encoderArguments; }