<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Alessandro De Cristofaro</title>
    <description>The latest articles on DEV Community by Alessandro De Cristofaro (@hiutaky).</description>
    <link>https://dev.to/hiutaky</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1218001%2Fe2cdfcc2-4986-4ac0-8926-d8d44de0471b.png</url>
      <title>DEV Community: Alessandro De Cristofaro</title>
      <link>https://dev.to/hiutaky</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/hiutaky"/>
    <language>en</language>
    <item>
      <title>Generate Thumbnails from Remote Videos using Python</title>
      <dc:creator>Alessandro De Cristofaro</dc:creator>
      <pubDate>Fri, 24 Nov 2023 13:24:24 +0000</pubDate>
      <link>https://dev.to/hiutaky/generate-remove-video-thumbnails-using-python-2l52</link>
      <guid>https://dev.to/hiutaky/generate-remove-video-thumbnails-using-python-2l52</guid>
      <description>&lt;p&gt;A customer of mine is running a video tube using &lt;strong&gt;WordPress&lt;/strong&gt; and obv a PHP environment. &lt;/p&gt;

&lt;p&gt;The machine running his CMS comes without FFMPEG module, meaning that it's practically impossible to: manipulate videos and mostly take screenshot at specific duration to generate the thumbnail.&lt;/p&gt;

&lt;p&gt;In a first implementation of his custom plugin I was using a free API service that provided a GET endpoint accepting:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Video Source&lt;/li&gt;
&lt;li&gt;Duration at which get the screenshot/image&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So you're now thinking: &lt;strong&gt;the problem is solved.&lt;/strong&gt; &lt;/p&gt;

&lt;p&gt;Yep, but &lt;strong&gt;just for 2 weeks&lt;/strong&gt;. In fact this API service &lt;strong&gt;went down&lt;/strong&gt; 4 days ago and my customer remained without a Video-to-Image service and the ability to generate thumbs for his videos.&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating your own Video to Image Script
&lt;/h2&gt;

&lt;p&gt;I than decided to create my own service with API endpoint to generate Images from Remote Videos. &lt;/p&gt;

&lt;p&gt;Hopefully this wont go down.&lt;/p&gt;

&lt;p&gt;The concept is really simple, we're going to create a Python Script and run it on a machine that provide us FFMpeg library (out there you can find a lot of free services to run your python code). &lt;/p&gt;

&lt;p&gt;Manipulating videos using &lt;strong&gt;FFMPEG is pretty easy&lt;/strong&gt; and you can use the Python based library or run it as a command using subprocess ( the approach used in this tutorial ).&lt;/p&gt;

&lt;p&gt;This is the command we're going to use:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;ffmpeg -y -i [video_source] -ss [seconds formatted] -vframes 1 -f image2 [image_path]&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The -y command let us overwrite the image file once generated, since the script doesn’t provide dynamic naming.&lt;/p&gt;

&lt;p&gt;Before writing down this script I searched a lot on Github for a build-in solution, but all the video thumbnail generator comes without an API endpoint, that is the main feature I needed. &lt;/p&gt;

&lt;p&gt;So at that point I decided to create my own.&lt;/p&gt;

&lt;p&gt;Lets see the code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;flask&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Flask&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;send_file&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;subprocess&lt;/span&gt;

&lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Flask&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;__name__&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nd"&gt;@app.route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;/screenshot&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;methods&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;GET&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_screenshot&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;video_source&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;video_source&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;seconds&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;seconds&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;default&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;filePath&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;results/image1.jpg&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;subprocess&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;ffmpeg&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;-y&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;-i&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;video_source&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;-ss&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;seconds_to_time_format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;seconds&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;-vframes&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;1&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;-f&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;image2&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;filePath&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;send_file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;./&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;filePath&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;mimetype&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;image/jpg&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;seconds_to_time_format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;seconds&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;hours&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;seconds&lt;/span&gt; &lt;span class="o"&gt;//&lt;/span&gt; &lt;span class="mi"&gt;3600&lt;/span&gt;
    &lt;span class="n"&gt;seconds&lt;/span&gt; &lt;span class="o"&gt;%=&lt;/span&gt; &lt;span class="mi"&gt;3600&lt;/span&gt;
    &lt;span class="n"&gt;minutes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;seconds&lt;/span&gt; &lt;span class="o"&gt;//&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;
    &lt;span class="n"&gt;seconds&lt;/span&gt; &lt;span class="o"&gt;%=&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;%02d:%02d:%02d&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hours&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;minutes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;seconds&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;__main__&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;debug&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you can see, the code is really super simple, comes without proper parameters validations and so on. &lt;/p&gt;

&lt;p&gt;It's not a big deal since I do validation before sending the request and I send these requests in a synchronous way, but feel free to add all the needed controls you need.&lt;/p&gt;

&lt;p&gt;We uses Flask to easily create a Webserver and provide a /screenshot GET Endpoint. &lt;/p&gt;

&lt;p&gt;This endpoint accepts just 2 parameters: the remote video source and the seconds at which get the image.&lt;/p&gt;

&lt;p&gt;Then a file path ( static ) is defined and using subprocess we run the ffmpeg script on the remote video. &lt;/p&gt;

&lt;p&gt;The second method is just for formatting integer seconds in to the proper format ( 00:00:00 ).&lt;/p&gt;

&lt;h2&gt;
  
  
  How to run the Script
&lt;/h2&gt;

&lt;p&gt;Running the script is pretty easy, you just have to navigate in to the script's folder and run python3 start.py. &lt;/p&gt;

&lt;p&gt;At that point the webserver will be initialized on the port 5000 and by navigating to &lt;a href="http://localhost:5000/screenshot"&gt;http://localhost:5000/screenshot&lt;/a&gt; you'll be able to send your request.&lt;/p&gt;

&lt;p&gt;Here's a request example: &lt;br&gt;
&lt;code&gt;http://localhost:5000/screenshot?seconds=100&amp;amp;video_source=https://remote-source/video_name.mp4&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;That's all. Once the script has done its job, you'll receive as response the newly generated thumbnail.&lt;/p&gt;

&lt;p&gt;You can fork the script from there: &lt;a href="https://github.com/Hiutaky/api-video-thumbnailer"&gt;API Video Thumbnailer&lt;/a&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>programming</category>
      <category>python</category>
      <category>api</category>
    </item>
  </channel>
</rss>
