/// <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> /// 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; }
/// <summary> /// Generate the thumbnail image for this display object and save it to the file system. The routine may decide that /// a file does not need to be generated, usually because it already exists. However, it will always be /// created if the relevant flag is set on the parent <see cref="IGalleryObject" />. (Example: If /// <see cref="IGalleryObject.RegenerateThumbnailOnSave" /> = true, the thumbnail file will always be created.) No data is /// persisted to the data store. /// </summary> public void GenerateAndSaveFile() { // If necessary, generate and save the thumbnail version of the video. if (!(IsThumbnailImageRequired())) { return; // No thumbnail image required. } IGallerySettings gallerySetting = Factory.LoadGallerySetting(_galleryObject.GalleryId); // Generate a temporary filename to store the thumbnail created by FFmpeg. string tmpVideoThumbnailPath = Path.Combine(AppSetting.Instance.TempUploadDirectory, String.Concat(Guid.NewGuid().ToString(), ".jpg")); // Request that FFmpeg create the thumbnail. If successful, the file will be created. string ffmpegOutput = FFmpeg.GenerateThumbnail(this._galleryObject.Original.FileNamePhysicalPath, tmpVideoThumbnailPath, gallerySetting.VideoThumbnailPosition, this._galleryObject.GalleryId); if (!String.IsNullOrEmpty(ffmpegOutput) && this._galleryObject.IsNew && gallerySetting.ExtractMetadata && this._galleryObject.ExtractMetadataOnSave) { // When metadata extraction is enabled and we have a new video where we have some output from FFmpeg, parse the data. MediaObjectMetadataExtractor metadata = new MediaObjectMetadataExtractor(this._galleryObject.Original.FileNamePhysicalPath, this._galleryObject.GalleryId, ffmpegOutput); this._galleryObject.MetadataItems.AddRange(metadata.GetGalleryObjectMetadataItemCollection()); this._galleryObject.ExtractMetadataOnSave = false; // Sends signal to save routine to not re-extract metadata } // Verify image was created from video, trying again using a different video position setting if necessary. ValidateVideoThumbnail(tmpVideoThumbnailPath, gallerySetting.VideoThumbnailPosition); // Determine file name and path of the thumbnail image. string thumbnailPath = HelperFunctions.MapAlbumDirectoryStructureToAlternateDirectory(this._galleryObject.Original.FileInfo.DirectoryName, gallerySetting.FullThumbnailPath, gallerySetting.FullMediaObjectPath); string newFilename = GenerateNewFilename(thumbnailPath, ImageFormat.Jpeg, gallerySetting.ThumbnailFileNamePrefix); string newFilePath = Path.Combine(thumbnailPath, newFilename); int newWidth, newHeight; if (File.Exists(tmpVideoThumbnailPath)) { // FFmpeg successfully created a thumbnail image the same size as the video. Now resize it to the width and height we need. using (Bitmap originalBitmap = new Bitmap(tmpVideoThumbnailPath)) { ImageHelper.CalculateThumbnailWidthAndHeight(originalBitmap.Width, originalBitmap.Height, out newWidth, out newHeight, false, gallerySetting.MaxThumbnailLength); // Get JPEG quality value (0 - 100). This is ignored if imgFormat = GIF. int jpegQuality = gallerySetting.ThumbnailImageJpegQuality; // Generate the new image and save to disk. ImageHelper.SaveImageFile(originalBitmap, newFilePath, ImageFormat.Jpeg, newWidth, newHeight, jpegQuality); } try { // Now delete the thumbnail image created by FFmpeg, but no worries if an error happens. The file is in the temp directory // which is cleaned out each time the app starts anyway. File.Delete(tmpVideoThumbnailPath); } catch (IOException ex) { ErrorHandler.Error.Record(ex, this._galleryObject.GalleryId, Factory.LoadGallerySettings(), AppSetting.Instance); } catch (UnauthorizedAccessException ex) { ErrorHandler.Error.Record(ex, this._galleryObject.GalleryId, Factory.LoadGallerySettings(), AppSetting.Instance); } catch (NotSupportedException ex) { ErrorHandler.Error.Record(ex, this._galleryObject.GalleryId, Factory.LoadGallerySettings(), AppSetting.Instance); } } else { // FFmpeg didn't run or no thumbnail image was created by FFmpeg. Build a generic video thumbnail. using (Bitmap originalBitmap = Resources.GenericThumbnailImage_Video) { ImageHelper.CalculateThumbnailWidthAndHeight(originalBitmap.Width, originalBitmap.Height, out newWidth, out newHeight, true, gallerySetting.MaxThumbnailLength); // Get JPEG quality value (0 - 100). int jpegQuality = gallerySetting.ThumbnailImageJpegQuality; // Generate the new image and save to disk. ImageHelper.SaveImageFile(originalBitmap, newFilePath, ImageFormat.Jpeg, newWidth, newHeight, jpegQuality); } } this._galleryObject.Thumbnail.Width = newWidth; this._galleryObject.Thumbnail.Height = newHeight; this._galleryObject.Thumbnail.FileName = newFilename; this._galleryObject.Thumbnail.FileNamePhysicalPath = newFilePath; int fileSize = (int)(this._galleryObject.Thumbnail.FileInfo.Length / 1024); this._galleryObject.Thumbnail.FileSizeKB = (fileSize < 1 ? 1 : fileSize); // Very small files should be 1, not 0. }