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
Copyright © 2007 - 2015 ShareX Developers
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.Drawing.Imaging ;
using System.IO ;
using System.Linq ;
using System.Text ;
namespace ShareX.MediaLib
{
public class VideoThumbnailer
{
public string MediaPath { get ; private set ; }
public string FFmpegPath { get ; private set ; }
public VideoThumbnailOptions Options { 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
2015-08-04 21:39:27 +12:00
private int timeSlice ;
private List < int > mediaSeekTimes = new List < int > ( ) ;
2015-08-04 19:31:43 +12:00
public VideoThumbnailer ( string mediaPath , string ffmpegPath , VideoThumbnailOptions options )
{
2015-08-04 21:39:27 +12:00
MediaPath = mediaPath ;
2015-08-04 19:31:43 +12:00
FFmpegPath = ffmpegPath ;
Options = options ;
2015-08-04 21:39:27 +12:00
using ( FFmpegCLIManager ffmpegCLI = new FFmpegCLIManager ( FFmpegPath ) )
{
VideoInfo = ffmpegCLI . GetVideoInfo ( MediaPath ) ;
}
timeSlice = GetTimeSlice ( Options . ScreenshotCount ) ;
2015-08-04 19:31:43 +12:00
for ( int i = 1 ; i < Options . ScreenshotCount + 2 ; i + + )
{
2015-08-04 21:39:27 +12:00
mediaSeekTimes . Add ( GetTimeSlice ( Options . ScreenshotCount + 2 ) * i ) ;
2015-08-04 19:31:43 +12:00
}
}
2015-08-04 23:47:34 +12:00
public virtual List < VideoThumbnailInfo > TakeScreenshots ( )
2015-08-04 19:31:43 +12:00
{
2015-08-04 23:47:34 +12:00
if ( ! File . Exists ( FFmpegPath ) )
{
return null ;
}
List < VideoThumbnailInfo > tempScreenshots = new List < VideoThumbnailInfo > ( ) ;
2015-08-04 19:31:43 +12:00
for ( int i = 0 ; i < Options . ScreenshotCount ; i + + )
{
string mediaFileName = Path . GetFileNameWithoutExtension ( MediaPath ) ;
//worker.ReportProgress((int)ProgressType.UPDATE_STATUSBAR_DEBUG, string.Format("Taking screenshot {0} of {1} for {2}", i + 1, Options.ScreenshotCount, mediaFileName));
2015-08-04 21:39:27 +12:00
int timeSliceElapsed = Options . RandomFrame ? GetRandomTimeSlice ( i ) : timeSlice * ( i + 1 ) ;
2015-08-04 19:31:43 +12:00
string filename = string . Format ( "{0}-{1}.{2}" , mediaFileName , timeSliceElapsed . ToString ( "00000" ) , Options . FFmpegThumbnailExtension ) ;
2015-08-04 23:47:34 +12:00
string tempScreenshotPath = Path . Combine ( GetOutputDirectory ( ) , filename ) ;
2015-08-04 19:31:43 +12:00
ProcessStartInfo psi = new ProcessStartInfo ( FFmpegPath ) ;
psi . WindowStyle = ProcessWindowStyle . Hidden ;
psi . Arguments = string . Format ( "-ss {0} -i \"{1}\" -f image2 -vframes 1 -y \"{2}\"" , timeSliceElapsed , MediaPath , tempScreenshotPath ) ;
using ( Process p = new Process ( ) )
{
p . StartInfo = psi ;
p . Start ( ) ;
p . WaitForExit ( 1000 * 30 ) ;
}
if ( File . Exists ( tempScreenshotPath ) )
{
2015-08-04 23:47:34 +12:00
VideoThumbnailInfo screenshotInfo = new VideoThumbnailInfo ( tempScreenshotPath )
2015-08-04 19:31:43 +12:00
{
Args = psi . Arguments ,
Timestamp = TimeSpan . FromSeconds ( timeSliceElapsed )
} ;
2015-08-04 21:39:27 +12:00
tempScreenshots . Add ( screenshotInfo ) ;
2015-08-04 19:31:43 +12:00
}
}
2015-08-04 23:47:34 +12:00
return Finish ( tempScreenshots ) ;
2015-08-04 19:31:43 +12:00
}
2015-08-04 23:47:34 +12:00
protected virtual List < VideoThumbnailInfo > Finish ( List < VideoThumbnailInfo > tempScreenshots )
2015-08-04 19:31:43 +12:00
{
2015-08-04 23:47:34 +12:00
List < VideoThumbnailInfo > screenshots = new List < VideoThumbnailInfo > ( ) ;
2015-08-04 21:39:27 +12:00
if ( tempScreenshots ! = null & & tempScreenshots . Count > 0 )
2015-08-04 19:31:43 +12:00
{
if ( Options . CombineScreenshots )
{
string temp_fp = "" ;
2015-08-04 21:39:27 +12:00
using ( Image img = CombineScreenshots ( tempScreenshots ) )
2015-08-04 19:31:43 +12:00
{
2015-08-04 23:47:34 +12:00
temp_fp = Path . Combine ( GetOutputDirectory ( ) , Path . GetFileNameWithoutExtension ( MediaPath ) + "_s." + Options . FFmpegThumbnailExtension ) ;
2015-08-04 19:31:43 +12:00
switch ( Options . FFmpegThumbnailExtension )
{
case EImageFormat . PNG :
img . Save ( temp_fp , ImageFormat . Png ) ;
break ;
case EImageFormat . JPEG :
img . Save ( temp_fp , ImageFormat . Jpeg ) ;
break ;
}
2015-08-04 23:47:34 +12:00
screenshots . Add ( new VideoThumbnailInfo ( temp_fp ) { Args = tempScreenshots [ 0 ] . Args } ) ;
2015-08-04 19:31:43 +12:00
}
2015-08-04 21:39:27 +12:00
tempScreenshots . ForEach ( x = > File . Delete ( x . LocalPath ) ) ;
2015-08-04 19:31:43 +12:00
}
else
{
2015-08-04 21:39:27 +12:00
screenshots . AddRange ( tempScreenshots ) ;
2015-08-04 19:31:43 +12:00
}
}
2015-08-04 23:47:34 +12:00
return screenshots ;
}
private string GetOutputDirectory ( )
{
switch ( Options . ScreenshotsLocation )
{
case ThumbnailLocationType . ParentFolder :
return Path . GetDirectoryName ( MediaPath ) ;
case ThumbnailLocationType . CustomFolder :
return Options . OutputDirectory ;
default :
case ThumbnailLocationType . DefaultFolder : // TODO
return "" ;
}
2015-08-04 19:31:43 +12:00
}
2015-08-04 21:39:27 +12:00
protected 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-04 21:39:27 +12:00
protected int GetRandomTimeSlice ( int start )
2015-08-04 19:31:43 +12:00
{
2015-08-04 21:39:27 +12:00
Random random = new Random ( ) ;
return ( int ) ( random . NextDouble ( ) * ( mediaSeekTimes [ start + 1 ] - mediaSeekTimes [ start ] ) + mediaSeekTimes [ start ] ) ;
2015-08-04 19:31:43 +12:00
}
2015-08-04 23:47:34 +12:00
private Image CombineScreenshots ( List < VideoThumbnailInfo > screenshots )
2015-08-04 19:31:43 +12:00
{
List < Image > images = new List < Image > ( ) ;
Image finalImage = null ;
try
{
string infoString = "" ;
int infoStringHeight = 0 ;
if ( Options . AddMovieInfo )
{
2015-08-04 21:39:27 +12:00
infoString = VideoInfo . ToString ( ) ;
2015-08-04 19:31:43 +12:00
using ( Font font = new Font ( "Arial" , 14 ) )
{
infoStringHeight = Helpers . MeasureText ( infoString , font ) . Height ;
}
}
2015-08-04 23:47:34 +12:00
foreach ( VideoThumbnailInfo screenshot in screenshots )
2015-08-04 19:31:43 +12:00
{
Image img = Image . FromFile ( screenshot . LocalPath ) ;
if ( Options . MaxThumbnailWidth > 0 & & img . Width > Options . MaxThumbnailWidth )
{
int maxThumbnailHeight = ( int ) ( ( float ) Options . MaxThumbnailWidth / img . Width * img . Height ) ;
img = ImageHelpers . ResizeImage ( img , Options . MaxThumbnailWidth , maxThumbnailHeight ) ;
}
images . Add ( img ) ;
}
int columnCount = Options . ColumnCount ;
int thumbWidth = images [ 0 ] . Width ;
int width = Options . Padding * 2 +
thumbWidth * columnCount +
( columnCount - 1 ) * Options . Spacing ;
int rowCount = ( int ) Math . Ceiling ( images . Count / ( float ) columnCount ) ;
int thumbHeight = images [ 0 ] . Height ;
int height = Options . Padding * 3 +
infoStringHeight +
thumbHeight * rowCount +
( rowCount - 1 ) * Options . Spacing ;
finalImage = new Bitmap ( width , height ) ;
using ( Graphics g = Graphics . FromImage ( finalImage ) )
{
g . Clear ( Color . WhiteSmoke ) ;
if ( ! string . IsNullOrEmpty ( infoString ) )
{
using ( Font font = new Font ( "Arial" , 14 ) )
{
g . DrawString ( infoString , font , Brushes . Black , Options . Padding , Options . Padding ) ;
}
}
int i = 0 ;
int offsetY = Options . Padding * 2 + infoStringHeight ;
for ( int y = 0 ; y < rowCount ; y + + )
{
int offsetX = Options . Padding ;
for ( int x = 0 ; x < columnCount ; x + + )
{
if ( Options . DrawShadow )
{
int shadowOffset = 3 ;
using ( Brush shadowBrush = new SolidBrush ( Color . FromArgb ( 50 , Color . Black ) ) )
{
g . FillRectangle ( shadowBrush , offsetX + shadowOffset , offsetY + shadowOffset , thumbWidth , thumbHeight ) ;
}
}
g . DrawImage ( images [ i ] , offsetX , offsetY , thumbWidth , thumbHeight ) ;
if ( Options . AddTimestamp )
{
int timestampOffset = 10 ;
using ( Font font = new Font ( "Arial" , 12 ) )
{
ImageHelpers . DrawTextWithShadow ( g , screenshots [ i ] . Timestamp . ToString ( ) ,
new Point ( offsetX + timestampOffset , offsetY + timestampOffset ) , font , Color . White , Color . Black ) ;
}
}
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 ( Image image in images )
{
if ( image ! = null )
{
image . Dispose ( ) ;
}
}
}
}
}
}