I recently published a Sitecore Marketplace module
PDF Thumbnail Creater Handler. Basically it allows to generate thumbnail on-the-fly (dynamically) for the uploaded PDF in sitecore by passing width and/or height. This will allow user to request thumbnail for any height or width and the thumbnail will be stored as a media cache in Sitecore.
Requirement
Suppose user uploaded PDF in Sitecore.
- User want to generate thumbnails of the fist page of uploaded PDF.
- User can choose height and/or width of thumbnails without any configurations.
- If user replaces a new file instead of that PDF, it should serve thumbnail of newly uploaded PDF.
- Similarly the thumbnail URL should work if user moves/copies/deletes the PDF item.
- Finally, the conversion process should be scalable and quick enough so it does not affect performance and the thumbnails should be cached as media cache too
Below are the outputs of same PDF but different size thumbnails:
Theoretical Concept
I just thought to achieve this overriding
Sitecore MediaRequestHandler. Sitecore allows to generate different sized thumbnails of uploaded image files,
See how. Why should not I use the same concept to generate thumbnails from PDF? Only concern I looked was to convert PDF to JPG only, but was not that much easy.
So, what I wanted to achieve:
How this achieved
PDF to JPG conversion can be done using
GhostScript (With GPL License, which is free), which is very efficient and gives flexibility with many other options.
You can read my older post regarding
Sitecore Custom HTTP Handler, I have described there in detail.
I created own Sitecore Custom Handlers (
SitecoreTactics.ThumbnailManager.PDFThumbnailRequestHandler) to generate thumbnails of media(PDF) items. See below config changes this requires:
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
<sitecore>
<!-- Define Custom Handler -->
<customHandlers>
<handler trigger="~/mediathumb/" handler="sitecore_media_thumb.ashx" />
</customHandlers>
<!-- Define Media Prefix -->
<mediaLibrary>
<mediaPrefixes>
<prefix value="~/mediathumb" />
</mediaPrefixes>
</mediaLibrary>
</sitecore>
<!-- Define Web Handler -->
<system.webServer>
<handlers>
<add verb="*" path="sitecore_media_thumb.ashx" type="SitecoreTactics.ThumbnailManager.PDFThumbnailRequestHandler, SitecoreTactics.ThumbnailManager" name="SitecoreTactics.PDFThumbnailRequestHandler"/>
</handlers>
</system.webServer>
</configuration>
Handler's source code to process thumbnail and use media cache as below.
namespace SitecoreTactics.ThumbnailManager
{
public class PDFThumbnailRequestHandler : Sitecore.Resources.Media.MediaRequestHandler
{
protected override bool DoProcessRequest(HttpContext context)
{
Assert.ArgumentNotNull(context, "context");
MediaRequest request = MediaManager.ParseMediaRequest(context.Request);
if (request == null)
return false;
Sitecore.Resources.Media.Media media = null;
try
{
media = MediaManager.GetMedia(request.MediaUri);
if (media != null)
return this.DoProcessRequest(context, request, media);
}
catch (Exception ex)
{
Log.Error("PDF Thumbnail Generator error - URL:" + context.Request.Url.ToString() + ". Exception:" + ex.ToString(), this);
}
if (media == null)
{
context.Response.Write("404 - File not found");
context.Response.End();
}
else
{
string itemNotFoundUrl = (Context.Site.LoginPage != string.Empty) ? Context.Site.LoginPage : Settings.NoAccessUrl;
if (Settings.RequestErrors.UseServerSideRedirect)
HttpContext.Current.Server.Transfer(itemNotFoundUrl);
else
HttpContext.Current.Response.Redirect(itemNotFoundUrl);
}
return true;
}
protected override bool DoProcessRequest(HttpContext context, MediaRequest request, Sitecore.Resources.Media.Media media)
{
Assert.ArgumentNotNull(context, "context");
Assert.ArgumentNotNull(request, "request");
Assert.ArgumentNotNull(media, "media");
if (this.Modified(context, media, request.Options) == Sitecore.Tristate.False)
{
Event.RaiseEvent("media:request", new object[] { request });
this.SendMediaHeaders(media, context);
context.Response.StatusCode = 0x130;
return true;
}
// Gets media stream for the requested media item thumbnail
MediaStream stream = ProcessThumbnail(request, media);
if (stream == null)
{
return false;
}
Event.RaiseEvent("media:request", new object[] { request });
this.SendMediaHeaders(media, context);
this.SendStreamHeaders(stream, context);
using (stream)
{
context.Response.AddHeader("Content-Length", stream.Stream.Length.ToString());
WebUtil.TransmitStream(stream.Stream, context.Response, Settings.Media.StreamBufferSize);
}
return true;
}
private MediaStream ProcessThumbnail(MediaRequest request, Sitecore.Resources.Media.Media media)
{
MediaStream mStream = null;
ParseQueryString(request);
mStream = MediaManager.Cache.GetStream(media, request.Options);
if (mStream == null)
{
string tempPath = Settings.TempFolderPath + "/PDF-Thumbnails/";
tempPath = MainUtil.MapPath(tempPath);
if (!Directory.Exists(tempPath))
Directory.CreateDirectory(tempPath);
// Prepare filenames
string pdfFile = tempPath + media.MediaData.MediaId + ".pdf";
string jpgFile = tempPath + media.MediaData.MediaId + ".jpg";
string resizedJpgFile = tempPath + media.MediaData.MediaId + "_" + request.Options.Width.ToString() + "_" + request.Options.Height.ToString();
if (!File.Exists(jpgFile))
{
// Save BLOB media file to disk
MediaConverter.ConvertMediaItemToFile(media.MediaData.MediaItem, pdfFile);
// Convert PDF to Jpeg - First Pager
MediaConverter.ConvertPDFtoJPG(pdfFile, 1, jpgFile);
}
// Resize Image
MediaConverter.ReSizeJPG(jpgFile, resizedJpgFile, request.Options.Width, request.Options.Height, true);
// Convert resized image to stream
MediaStream resizedStream = new MediaStream(File.Open(resizedJpgFile, FileMode.Open, FileAccess.Read, FileShare.Read), "jpg", media.MediaData.MediaItem);
// Add the requested thumbnail to Media Cache
MediaStream outStream = null;
MediaManager.Cache.AddStream(media, request.Options, resizedStream, out outStream);
if (outStream != null)
{
// If Media cache is enabled
return outStream;
}
}
// If Media cache is disabled
return mStream;
}
public void ParseQueryString(MediaRequest mediaRequest)
{
HttpRequest httpRequest = mediaRequest.InnerRequest;
Assert.ArgumentNotNull((object)httpRequest, "httpRequest");
string str1 = httpRequest.QueryString["as"];
if (!string.IsNullOrEmpty(str1))
mediaRequest.Options.AllowStretch = MainUtil.GetBool(str1, false);
string color = httpRequest.QueryString["bc"];
if (!string.IsNullOrEmpty(color))
mediaRequest.Options.BackgroundColor = MainUtil.StringToColor(color);
string str2 = httpRequest.QueryString["dmc"];
mediaRequest.Options.Height = MainUtil.GetInt(httpRequest.QueryString["h"], 0);
string str3 = httpRequest.QueryString["iar"];
if (!string.IsNullOrEmpty(str3))
mediaRequest.Options.IgnoreAspectRatio = MainUtil.GetBool(str3, false);
mediaRequest.Options.MaxHeight = MainUtil.GetInt(httpRequest.QueryString["mh"], 0);
mediaRequest.Options.MaxWidth = MainUtil.GetInt(httpRequest.QueryString["mw"], 0);
mediaRequest.Options.Scale = MainUtil.GetFloat(httpRequest.QueryString["sc"], 0.0f);
string str4 = httpRequest.QueryString["thn"];
if (!string.IsNullOrEmpty(str4))
mediaRequest.Options.Thumbnail = MainUtil.GetBool(str4, false);
mediaRequest.Options.Width = MainUtil.GetInt(httpRequest.QueryString["w"], 0);
}
}
}
You can get full source code (of older version) of this module from
Sitecore Marketplace.
Update:
Module available on Sitecore Marketplace contains older code, having a bug on media cache that when someone overwrite media files (using detach/attach), it was serving older thumbnail. This bug has been fixed in above code, and will be available on marketplace very soon. Meanwhile, you can download the source code (excluding DLLs) from
https://drive.google.com/file/d/0B1otw7vE3rGTQmU1U1l2TTJHQTA/view?usp=sharing:
Benefits of this approach
- Dynamic conversion of PDF to Thumbnail when requested
- Allows to convert different size thumbnails
- Repeated thumbnails will be served from media cache.
- Conversion is fast using GhostScript and media cache adds more power.
Related Posts:
-
Show PDF Thumbnail Icons in Content Editor
-
Sitecore HTTP Custom Handler