ShareX/ShareX.MediaLib/VideoThumbnailer.cs

337 lines
12 KiB
C#
Raw Normal View History

2015-08-04 19:31:43 +12:00
#region License Information (GPL v3)
/*
ShareX - A program that allows you to take screenshots and share any file type
2024-01-03 12:57:14 +13:00
Copyright (c) 2007-2024 ShareX Team
2015-08-04 19:31:43 +12:00
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
Optionally you can also view the license at <http://www.gnu.org/licenses/>.
*/
#endregion License Information (GPL v3)
using ShareX.HelpersLib;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.IO;
namespace ShareX.MediaLib
{
public class VideoThumbnailer
{
2015-08-05 01:05:14 +12:00
public delegate void ProgressChangedEventHandler(int current, int length);
public event ProgressChangedEventHandler ProgressChanged;
2015-08-04 19:31:43 +12:00
public string FFmpegPath { get; private set; }
public VideoThumbnailOptions Options { get; private set; }
public string MediaPath { get; private set; }
2015-08-04 21:39:27 +12:00
public VideoInfo VideoInfo { get; private set; }
2015-08-04 19:31:43 +12:00
public VideoThumbnailer(string ffmpegPath, VideoThumbnailOptions options)
2015-08-04 19:31:43 +12:00
{
FFmpegPath = ffmpegPath;
Options = options;
}
2015-08-04 19:31:43 +12:00
private void UpdateVideoInfo()
{
using (FFmpegCLIManager ffmpeg = new FFmpegCLIManager(FFmpegPath))
2015-08-04 21:39:27 +12:00
{
VideoInfo = ffmpeg.GetVideoInfo(MediaPath);
2015-08-04 21:39:27 +12:00
}
2015-08-04 19:31:43 +12:00
}
public List<VideoThumbnailInfo> TakeThumbnails(string mediaPath)
2015-08-04 19:31:43 +12:00
{
MediaPath = mediaPath;
UpdateVideoInfo();
if (VideoInfo == null || VideoInfo.Duration == TimeSpan.Zero)
{
return null;
}
2015-08-07 21:02:42 +12:00
List<VideoThumbnailInfo> tempThumbnails = new List<VideoThumbnailInfo>();
2015-08-04 19:31:43 +12:00
2015-08-07 21:02:42 +12:00
for (int i = 0; i < Options.ThumbnailCount; i++)
2015-08-04 19:31:43 +12:00
{
string mediaFileName = Path.GetFileNameWithoutExtension(MediaPath);
2015-08-05 22:41:37 +12:00
int timeSliceElapsed;
if (Options.RandomFrame)
{
timeSliceElapsed = GetRandomTimeSlice(i);
}
else
{
2015-08-07 21:02:42 +12:00
timeSliceElapsed = GetTimeSlice(Options.ThumbnailCount) * (i + 1);
2015-08-05 22:41:37 +12:00
}
2021-12-12 10:21:19 +13:00
string fileName = string.Format("{0}-{1}.{2}", mediaFileName, timeSliceElapsed, Options.ImageFormat.GetDescription());
string tempThumbnailPath = Path.Combine(GetOutputDirectory(), fileName);
2015-08-04 19:31:43 +12:00
2018-12-07 07:51:41 +13:00
using (Process process = new Process())
2015-08-04 19:31:43 +12:00
{
2018-12-07 07:51:41 +13:00
ProcessStartInfo psi = new ProcessStartInfo()
{
FileName = FFmpegPath,
Arguments = $"-ss {timeSliceElapsed} -i \"{MediaPath}\" -f image2 -vframes 1 -y \"{tempThumbnailPath}\"",
UseShellExecute = false,
CreateNoWindow = true
};
process.StartInfo = psi;
process.Start();
process.WaitForExit(1000 * 30);
2015-08-04 19:31:43 +12:00
}
2015-08-07 21:02:42 +12:00
if (File.Exists(tempThumbnailPath))
2015-08-04 19:31:43 +12:00
{
2015-08-07 21:02:42 +12:00
VideoThumbnailInfo screenshotInfo = new VideoThumbnailInfo(tempThumbnailPath)
2015-08-04 19:31:43 +12:00
{
Timestamp = TimeSpan.FromSeconds(timeSliceElapsed)
};
2015-08-07 21:02:42 +12:00
tempThumbnails.Add(screenshotInfo);
2015-08-04 19:31:43 +12:00
}
2015-08-05 01:05:14 +12:00
2015-08-07 21:02:42 +12:00
OnProgressChanged(i + 1, Options.ThumbnailCount);
2015-08-04 19:31:43 +12:00
}
2015-08-07 21:02:42 +12:00
return Finish(tempThumbnails);
2015-08-04 19:31:43 +12:00
}
2015-08-07 21:02:42 +12:00
private List<VideoThumbnailInfo> Finish(List<VideoThumbnailInfo> tempThumbnails)
2015-08-04 19:31:43 +12:00
{
2015-08-07 21:02:42 +12:00
List<VideoThumbnailInfo> thumbnails = new List<VideoThumbnailInfo>();
2015-08-04 23:47:34 +12:00
2015-08-07 21:02:42 +12:00
if (tempThumbnails != null && tempThumbnails.Count > 0)
2015-08-04 19:31:43 +12:00
{
if (Options.CombineScreenshots)
{
2015-08-07 21:02:42 +12:00
using (Image img = CombineScreenshots(tempThumbnails))
2015-08-04 19:31:43 +12:00
{
2022-01-05 20:44:18 +13:00
string tempFilePath = Path.Combine(GetOutputDirectory(), Path.GetFileNameWithoutExtension(MediaPath) + Options.FilenameSuffix + "." + Options.ImageFormat.GetDescription());
ImageHelpers.SaveImage(img, tempFilePath);
thumbnails.Add(new VideoThumbnailInfo(tempFilePath));
2015-08-04 19:31:43 +12:00
}
2015-08-05 23:44:04 +12:00
if (!Options.KeepScreenshots)
{
2022-01-05 20:44:18 +13:00
tempThumbnails.ForEach(x => File.Delete(x.FilePath));
2015-08-05 23:44:04 +12:00
}
2015-08-04 19:31:43 +12:00
}
else
{
2015-08-07 21:02:42 +12:00
thumbnails.AddRange(tempThumbnails);
2015-08-04 19:31:43 +12:00
}
2015-08-04 23:47:34 +12:00
2015-08-07 21:02:42 +12:00
if (Options.OpenDirectory && thumbnails.Count > 0)
2015-08-05 22:41:37 +12:00
{
FileHelpers.OpenFolderWithFile(thumbnails[0].FilePath);
2015-08-05 22:41:37 +12:00
}
2015-08-05 01:05:14 +12:00
}
2015-08-07 21:02:42 +12:00
return thumbnails;
2015-08-04 23:47:34 +12:00
}
2015-08-05 01:05:14 +12:00
protected void OnProgressChanged(int current, int length)
{
2021-06-10 10:14:01 +12:00
ProgressChanged?.Invoke(current, length);
2015-08-05 01:05:14 +12:00
}
2015-08-04 23:47:34 +12:00
private string GetOutputDirectory()
{
2015-08-15 17:13:51 +12:00
string directory;
2015-08-05 01:05:14 +12:00
switch (Options.OutputLocation)
2015-08-04 23:47:34 +12:00
{
2015-08-05 22:41:37 +12:00
default:
case ThumbnailLocationType.DefaultFolder:
2015-08-15 17:13:51 +12:00
directory = Options.DefaultOutputDirectory;
break;
2015-08-04 23:47:34 +12:00
case ThumbnailLocationType.ParentFolder:
2015-08-15 17:13:51 +12:00
directory = Path.GetDirectoryName(MediaPath);
break;
2015-08-04 23:47:34 +12:00
case ThumbnailLocationType.CustomFolder:
directory = FileHelpers.ExpandFolderVariables(Options.CustomOutputDirectory);
2015-08-15 17:13:51 +12:00
break;
2015-08-04 23:47:34 +12:00
}
2015-08-15 17:13:51 +12:00
FileHelpers.CreateDirectory(directory);
2015-08-15 17:13:51 +12:00
return directory;
2015-08-04 19:31:43 +12:00
}
2015-08-05 22:41:37 +12:00
private int GetTimeSlice(int count)
2015-08-04 19:31:43 +12:00
{
2015-08-04 23:47:34 +12:00
return (int)(VideoInfo.Duration.TotalSeconds / count);
2015-08-04 19:31:43 +12:00
}
2015-08-05 22:41:37 +12:00
private int GetRandomTimeSlice(int start)
2015-08-04 19:31:43 +12:00
{
2015-08-05 22:41:37 +12:00
List<int> mediaSeekTimes = new List<int>();
2015-08-07 21:02:42 +12:00
for (int i = 1; i < Options.ThumbnailCount + 2; i++)
2015-08-05 22:41:37 +12:00
{
2015-08-07 21:02:42 +12:00
mediaSeekTimes.Add(GetTimeSlice(Options.ThumbnailCount + 2) * i);
2015-08-05 22:41:37 +12:00
}
return (int)((RandomFast.NextDouble() * (mediaSeekTimes[start + 1] - mediaSeekTimes[start])) + mediaSeekTimes[start]);
2015-08-04 19:31:43 +12:00
}
2015-08-07 21:02:42 +12:00
private Image CombineScreenshots(List<VideoThumbnailInfo> thumbnails)
2015-08-04 19:31:43 +12:00
{
List<Bitmap> images = new List<Bitmap>();
2015-08-04 19:31:43 +12:00
Image finalImage = null;
try
{
string infoString = "";
int infoStringHeight = 0;
2015-08-05 23:44:04 +12:00
if (Options.AddVideoInfo)
2015-08-04 19:31:43 +12:00
{
2015-08-04 21:39:27 +12:00
infoString = VideoInfo.ToString();
2015-08-06 01:28:32 +12:00
using (Font font = new Font("Arial", 12))
2015-08-04 19:31:43 +12:00
{
infoStringHeight = Helpers.MeasureText(infoString, font).Height;
}
}
2015-08-07 21:02:42 +12:00
foreach (VideoThumbnailInfo thumbnail in thumbnails)
2015-08-04 19:31:43 +12:00
{
2022-01-05 20:44:18 +13:00
Bitmap bmp = ImageHelpers.LoadImage(thumbnail.FilePath);
2015-08-04 19:31:43 +12:00
if (Options.MaxThumbnailWidth > 0 && bmp.Width > Options.MaxThumbnailWidth)
2015-08-04 19:31:43 +12:00
{
int maxThumbnailHeight = (int)((float)Options.MaxThumbnailWidth / bmp.Width * bmp.Height);
bmp = ImageHelpers.ResizeImage(bmp, Options.MaxThumbnailWidth, maxThumbnailHeight);
2015-08-04 19:31:43 +12:00
}
images.Add(bmp);
2015-08-04 19:31:43 +12:00
}
int columnCount = Options.ColumnCount;
int thumbWidth = images[0].Width;
int width = (Options.Padding * 2) +
(thumbWidth * columnCount) +
((columnCount - 1) * Options.Spacing);
2015-08-04 19:31:43 +12:00
int rowCount = (int)Math.Ceiling(images.Count / (float)columnCount);
int thumbHeight = images[0].Height;
int height = (Options.Padding * 3) +
2015-08-04 19:31:43 +12:00
infoStringHeight +
(thumbHeight * rowCount) +
((rowCount - 1) * Options.Spacing);
2015-08-04 19:31:43 +12:00
finalImage = new Bitmap(width, height);
using (Graphics g = Graphics.FromImage(finalImage))
{
g.Clear(Color.WhiteSmoke);
if (!string.IsNullOrEmpty(infoString))
{
2015-08-06 01:28:32 +12:00
using (Font font = new Font("Arial", 12))
2015-08-04 19:31:43 +12:00
{
g.DrawString(infoString, font, Brushes.Black, Options.Padding, Options.Padding);
}
}
int i = 0;
int offsetY = (Options.Padding * 2) + infoStringHeight;
2015-08-04 19:31:43 +12:00
for (int y = 0; y < rowCount; y++)
{
int offsetX = Options.Padding;
for (int x = 0; x < columnCount; x++)
{
if (Options.DrawShadow)
{
int shadowOffset = 3;
2015-08-05 01:05:14 +12:00
using (Brush shadowBrush = new SolidBrush(Color.FromArgb(75, Color.Black)))
2015-08-04 19:31:43 +12:00
{
g.FillRectangle(shadowBrush, offsetX + shadowOffset, offsetY + shadowOffset, thumbWidth, thumbHeight);
}
}
g.DrawImage(images[i], offsetX, offsetY, thumbWidth, thumbHeight);
2015-08-06 01:28:32 +12:00
if (Options.DrawBorder)
{
g.DrawRectangleProper(Pens.Black, offsetX, offsetY, thumbWidth, thumbHeight);
}
2015-08-04 19:31:43 +12:00
if (Options.AddTimestamp)
{
int timestampOffset = 10;
2015-08-06 01:28:32 +12:00
using (Font font = new Font("Arial", 10, FontStyle.Bold))
2015-08-04 19:31:43 +12:00
{
2016-11-13 06:22:49 +13:00
g.DrawTextWithShadow(thumbnails[i].Timestamp.ToString(), new Point(offsetX + timestampOffset, offsetY + timestampOffset), font, Brushes.White, Brushes.Black);
2015-08-04 19:31:43 +12:00
}
}
i++;
if (i >= images.Count)
{
return finalImage;
}
offsetX += thumbWidth + Options.Spacing;
}
offsetY += thumbHeight + Options.Spacing;
}
}
return finalImage;
}
catch
{
if (finalImage != null)
{
finalImage.Dispose();
}
throw;
}
finally
{
foreach (Bitmap image in images)
2015-08-04 19:31:43 +12:00
{
if (image != null)
{
image.Dispose();
}
}
}
}
}
}