/// <summary> /// Verify the image was created from the video. If not, it might be because the video is shorter than the position /// where we tried to grab the image. If this is the case, try again, except grab an image from the beginning of the video. /// </summary> /// <param name="tmpVideoThumbnailPath">The video thumbnail path.</param> /// <param name="videoThumbnailPosition">The position, in seconds, in the video where the thumbnail is generated from a frame.</param> private void ValidateVideoThumbnail(string tmpVideoThumbnailPath, int videoThumbnailPosition) { if (!File.Exists(tmpVideoThumbnailPath)) { IGalleryObjectMetadataItem metadataItem; if (GalleryObject.MetadataItems.TryGetMetadataItem(MetadataItemName.Duration, out metadataItem)) { TimeSpan duration; if (TimeSpan.TryParse(metadataItem.Value, out duration)) { if (duration < new TimeSpan(0, 0, videoThumbnailPosition)) { // Video is shorter than the number of seconds where we are suppossed to grab the thumbnail. // Try again, except use 1 second instead of the gallery setting. const int videoThumbnailPositionFallback = 1; FFmpeg.GenerateThumbnail(GalleryObject.Original.FileNamePhysicalPath, tmpVideoThumbnailPath, videoThumbnailPositionFallback, GalleryObject.GalleryId); } } } } }
/// <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 override 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. FFmpeg.GenerateThumbnail(GalleryObject.Original.FileNamePhysicalPath, tmpVideoThumbnailPath, gallerySetting.VideoThumbnailPosition, GalleryObject.GalleryId); // 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. If a file name has already been previously // calculated for this media object, re-use it. Otherwise generate a unique name. var newFilename = GalleryObject.Thumbnail.FileName; var newFilePath = GalleryObject.Thumbnail.FileNamePhysicalPath; if (String.IsNullOrEmpty(newFilename)) { var thumbnailPath = HelperFunctions.MapAlbumDirectoryStructureToAlternateDirectory(this.GalleryObject.Original.FileInfo.DirectoryName, gallerySetting.FullThumbnailPath, gallerySetting.FullMediaObjectPath); newFilename = GenerateJpegFilename(thumbnailPath, gallerySetting.ThumbnailFileNamePrefix); newFilePath = Path.Combine(thumbnailPath, newFilename); } 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)) { var newSize = CalculateWidthAndHeight(new System.Windows.Size(originalBitmap.Width, originalBitmap.Height), gallerySetting.MaxThumbnailLength, false); // Get JPEG quality value (0 - 100). This is ignored if imgFormat = GIF. int jpegQuality = gallerySetting.ThumbnailImageJpegQuality; // Generate the new image and save to disk. var size = ImageHelper.SaveImageFile(originalBitmap, newFilePath, ImageFormat.Jpeg, newSize.Width, newSize.Height, jpegQuality); var rotatedSize = ExecuteAutoRotation(newFilePath, jpegQuality); if (!rotatedSize.IsEmpty) { size = rotatedSize; } GalleryObject.Thumbnail.Width = (int)size.Width; GalleryObject.Thumbnail.Height = (int)size.Height; } 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) { EventController.RecordError(ex, AppSetting.Instance, GalleryObject.GalleryId, Factory.LoadGallerySettings()); } catch (UnauthorizedAccessException ex) { EventController.RecordError(ex, AppSetting.Instance, GalleryObject.GalleryId, Factory.LoadGallerySettings()); } catch (NotSupportedException ex) { EventController.RecordError(ex, AppSetting.Instance, GalleryObject.GalleryId, Factory.LoadGallerySettings()); } } else { // FFmpeg didn't run or no thumbnail image was created by FFmpeg. Build a generic video thumbnail. using (Bitmap originalBitmap = Resources.GenericThumbnailImage_Video) { var newSize = CalculateWidthAndHeight(new System.Windows.Size(originalBitmap.Width, originalBitmap.Height), gallerySetting.MaxThumbnailLength, true); // Get JPEG quality value (0 - 100). int jpegQuality = gallerySetting.ThumbnailImageJpegQuality; // Generate the new image and save to disk. var size = ImageHelper.SaveImageFile(originalBitmap, newFilePath, ImageFormat.Jpeg, newSize.Width, newSize.Height, jpegQuality); GalleryObject.Thumbnail.Width = (int)size.Width; GalleryObject.Thumbnail.Height = (int)size.Height; } } GalleryObject.Thumbnail.FileName = newFilename; GalleryObject.Thumbnail.FileNamePhysicalPath = newFilePath; int fileSize = (int)(GalleryObject.Thumbnail.FileInfo.Length / 1024); GalleryObject.Thumbnail.FileSizeKB = (fileSize < 1 ? 1 : fileSize); // Very small files should be 1, not 0. }
/// <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 (Exception 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. }