{"id":36475,"date":"2024-12-20T07:00:00","date_gmt":"2024-12-20T15:00:00","guid":{"rendered":"https:\/\/cloudinary.com\/blog\/?p=36475"},"modified":"2024-12-20T14:38:51","modified_gmt":"2024-12-20T22:38:51","slug":"youtube-shorts-creator-highlighted-subtitles-next-js","status":"publish","type":"post","link":"https:\/\/cloudinary.com\/blog\/youtube-shorts-creator-highlighted-subtitles-next-js","title":{"rendered":"Build a YouTube Shorts Creator With Highlighted Subtitles in Next.js"},"content":{"rendered":"\n<p>YouTube Shorts are a powerful way to reach a wider audience with short, engaging videos. However, many creators face a common challenge: transforming widescreen videos into the vertical format required for YouTube Shorts without losing the key parts of the frame.<\/p>\n\n\n\n<p>This blog post explains how to automatically transform wide videos into <a target=\"_blank\" href=\"https:\/\/www.youtube.com\/intl\/en_ph\/creators\/shorts\/\" rel=\"noreferrer noopener\">YouTube <\/a><a target=\"_blank\" href=\"https:\/\/www.youtube.com\/intl\/en_ph\/creators\/shorts\/\" rel=\"noreferrer noopener\">Shorts<\/a> using <a target=\"_blank\" href=\"https:\/\/cloudinary.com\/blog\/smart-cropping-just-got-smarter\" rel=\"noreferrer noopener\">Cloudinary\u2019s smart cropping <\/a><a target=\"_blank\" href=\"https:\/\/cloudinary.com\/blog\/smart-cropping-just-got-smarter\" rel=\"noreferrer noopener\">feature<\/a> and how to add auto-generated, highlighted subtitles with Google Speech-to-Text to make your content more engaging and accessible.<\/p>\n\n\n\n<p>Here\u2019s a demo preview of what you\u2019ll be building:<\/p>\n\n\n<cld-video-player\n      cloud-name='cloudinary-marketing'\n      public-id='v1734661885\/YouTube_Shorts_Generator'\n      js-config='{\"playbackRates\":[0.5,1,1.5,2]}'\n      style='max-width: ;'\n      class='c-video-player'\n      \n      core-version='2.12.3'\n      player-version='1.7.0'\n      >\n      <video\n        id='_video-player69d3db7ff40be'\n        data-cld-big-play-button='init'\n        data-cld-source-types='[\"hls\",\"webm\\\/vp9\",\"mp4\\\/h265\",\"mp4\"]'\n        controls\n        muted\n        class='cld-video-player cld-fluid wp-block-cloudinary-video-player  cld-video-player-skin-dark'\n      ><\/video>\n    <\/cld-video-player>\n\n\n<p>Before getting into the setup, you can check out the full code on <a target=\"_blank\" href=\"https:\/\/github.com\/olawanlejoel\/yt-shorts-cloudinary\" rel=\"noreferrer noopener\">GitHub<\/a>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Set Up Cloudinary and Next.js<\/h2>\n\n\n\n<p>To get started, create a new Next.js project by running the following command:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-1\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css shcb-wrap-lines\"><span class=\"hljs-selector-tag\">npx<\/span> <span class=\"hljs-selector-tag\">create-next-app<\/span><span class=\"hljs-keyword\">@14<\/span> youtube-shorts-creator\n\ncd youtube-shorts-creator<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-1\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>For this blog post, you\u2019ll use Next.js 14 and <a target=\"_blank\" href=\"https:\/\/nextjs.org\/docs\/app\/building-your-application\/data-fetching\/server-actions-and-mutations\" rel=\"noreferrer noopener\">server actions<\/a> to handle video uploads (Next.js 13 can also be used as it supports Sever Actions). Once your project is set up, install the Cloudinary SDK with the command below:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-2\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml shcb-wrap-lines\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">code<\/span>&gt;<\/span>npm install cloudinary<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">code<\/span>&gt;<\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-2\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>You&#8217;ll need a Cloudinary account to access your credentials, which allow you to interact with the Cloudinary SDK. If you don\u2019t have an account yet, sign up at <a target=\"_blank\" href=\"https:\/\/cloudinary.com\" rel=\"noreferrer noopener\">Cloudinary<\/a>.<\/p>\n\n\n\n<p>After signing in, go to your Cloudinary dashboard and copy the following details: <strong>Cloud Name<\/strong>, <strong>API Key<\/strong>, and <strong>API Secret<\/strong>.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img width=\"1024\" height=\"445\" data-public-id=\"Web_Assets\/blog\/blog-Build-a-YouTube-Shorts-Creator-With-Highlighted-Subtitles-in-Nextjs-1\/blog-Build-a-YouTube-Shorts-Creator-With-Highlighted-Subtitles-in-Nextjs-1.png\" loading=\"lazy\" decoding=\"async\" src=\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/w_1024,h_445,c_scale\/f_auto,q_auto\/v1734733993\/Web_Assets\/blog\/blog-Build-a-YouTube-Shorts-Creator-With-Highlighted-Subtitles-in-Nextjs-1\/blog-Build-a-YouTube-Shorts-Creator-With-Highlighted-Subtitles-in-Nextjs-1.png?_i=AA\" alt=\"\" class=\"wp-post-36475 wp-image-36476\" data-format=\"png\" data-transformations=\"f_auto,q_auto\" data-version=\"1734733993\" data-seo=\"1\" srcset=\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1734733993\/Web_Assets\/blog\/blog-Build-a-YouTube-Shorts-Creator-With-Highlighted-Subtitles-in-Nextjs-1\/blog-Build-a-YouTube-Shorts-Creator-With-Highlighted-Subtitles-in-Nextjs-1.png?_i=AA 1600w, https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1734733993\/Web_Assets\/blog\/blog-Build-a-YouTube-Shorts-Creator-With-Highlighted-Subtitles-in-Nextjs-1\/blog-Build-a-YouTube-Shorts-Creator-With-Highlighted-Subtitles-in-Nextjs-1.png?_i=AA 300w, https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1734733993\/Web_Assets\/blog\/blog-Build-a-YouTube-Shorts-Creator-With-Highlighted-Subtitles-in-Nextjs-1\/blog-Build-a-YouTube-Shorts-Creator-With-Highlighted-Subtitles-in-Nextjs-1.png?_i=AA 768w, https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1734733993\/Web_Assets\/blog\/blog-Build-a-YouTube-Shorts-Creator-With-Highlighted-Subtitles-in-Nextjs-1\/blog-Build-a-YouTube-Shorts-Creator-With-Highlighted-Subtitles-in-Nextjs-1.png?_i=AA 1024w, https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1734733993\/Web_Assets\/blog\/blog-Build-a-YouTube-Shorts-Creator-With-Highlighted-Subtitles-in-Nextjs-1\/blog-Build-a-YouTube-Shorts-Creator-With-Highlighted-Subtitles-in-Nextjs-1.png?_i=AA 1536w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><figcaption class=\"wp-element-caption\">Cloudinary API keys from its dashboard<\/figcaption><\/figure>\n\n\n\n<p>Now, in your Next.js project, create a <code>.env.local<\/code> file at the root and add your Cloudinary credentials like this:<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs shcb-wrap-lines\">NEXT_PUBLIC_CLOUDINARY_CLOUD_NAME=your_cloud_name\n\nNEXT_PUBLIC_CLOUDINARY_API_KEY=your_api_key\n\nCLOUDINARY_API_SECRET=your_api_secret<\/code><\/span><\/pre>\n\n\n<p>Replace <code>your_cloud_name<\/code>, <code>your_api_key<\/code>, and <code>your_api_secret<\/code> with your actual Cloudinary credentials from the dashboard.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">How to Upload a Video to Cloudinary<\/h2>\n\n\n\n<p>With the Cloudinary SDK installed and credentials set up, let\u2019s implement the logic to upload videos from the frontend and process them through Cloudinary.&nbsp;<\/p>\n\n\n\n<p>You\u2019ll start by creating a form to allow users to upload their videos, then handle these uploads using server actions.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Step 1: Create the Upload Form<\/strong><\/h3>\n\n\n\n<p>Create a file upload form in your page.tsx. This form will allow users to select a video from their local device and submit it for upload to Cloudinary.<\/p>\n\n\n\n<p>Here&#8217;s a basic form setup. You can paste this into your <code>page.tsx<\/code> file:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-3\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-string\">'use client'<\/span>;\n\n<span class=\"hljs-keyword\">import<\/span> { useState } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'react'<\/span>;\n\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">default<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">Home<\/span>(<span class=\"hljs-params\"><\/span>) <\/span>{\n\n\u00a0 \u00a0 <span class=\"hljs-keyword\">const<\/span>&#91;loading, setLoading]= useState(<span class=\"hljs-literal\">false<\/span>);\n\n\u00a0 \u00a0 <span class=\"hljs-keyword\">const<\/span> handleSubmit = <span class=\"hljs-keyword\">async<\/span> (event: React.FormEvent&lt;HTMLFormElement&gt;)=&gt;{\n\n\u00a0 \u00a0 \u00a0 \u00a0 event.preventDefault();\n\n\u00a0 \u00a0 \u00a0 \u00a0 setLoading(<span class=\"hljs-literal\">true<\/span>);\n\n\u00a0 \u00a0 \u00a0 \u00a0 <span class=\"hljs-comment\">\/\/ Get the file from the form<\/span>\n\n<span class=\"hljs-keyword\">const<\/span> formData =<span class=\"hljs-keyword\">new<\/span> FormData(event.currentTarget);\n\n\u00a0 \u00a0 \u00a0 \u00a0 <span class=\"hljs-keyword\">try<\/span> {\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 <span class=\"hljs-comment\">\/\/ Upload the video to Cloudinary<\/span>\n\n} <span class=\"hljs-keyword\">catch<\/span> (error){\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 <span class=\"hljs-built_in\">console<\/span>.error(<span class=\"hljs-string\">'Upload failed:'<\/span>, error);\n\n} <span class=\"hljs-keyword\">finally<\/span> {\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 setLoading(<span class=\"hljs-literal\">false<\/span>);\n\n}\n\n};\n\n\u00a0 \u00a0 <span class=\"hljs-keyword\">return<\/span> (\n\n\u00a0 \u00a0 \u00a0 \u00a0 <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span>&gt;<\/span>\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">h1<\/span>&gt;<\/span>Upload a Video<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">h1<\/span>&gt;<\/span>\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">form<\/span> <span class=\"hljs-attr\">onSubmit<\/span>=<span class=\"hljs-string\">{handleSubmit}<\/span>&gt;<\/span>\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">input<\/span> <span class=\"hljs-attr\">type<\/span>=<span class=\"hljs-string\">\"file\"<\/span> <span class=\"hljs-attr\">name<\/span>=<span class=\"hljs-string\">\"video\"<\/span> <span class=\"hljs-attr\">accept<\/span>=<span class=\"hljs-string\">\"video\/*\"<\/span> <span class=\"hljs-attr\">required<\/span> \/&gt;<\/span>\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">button<\/span> <span class=\"hljs-attr\">type<\/span>=<span class=\"hljs-string\">\"submit\"<\/span> <span class=\"hljs-attr\">disabled<\/span>=<span class=\"hljs-string\">{loading}<\/span>&gt;<\/span>\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 {loading ? 'Uploading...' : 'Upload Video'}\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">button<\/span>&gt;<\/span>\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">form<\/span>&gt;<\/span>\n\n\u00a0 \u00a0 \u00a0 \u00a0 <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span><\/span>\n\n\u00a0 \u00a0 );\n\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-3\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>In the code above, the form allows users to upload a video. When the form is submitted:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>The <code>handleSubmit<\/code> function extracts the video file using <code>FormData<\/code>.<\/li>\n\n\n\n<li>The loading state ensures the button is disabled during the upload, displaying &#8220;Uploading&#8230;&#8221; while the process runs.<\/li>\n\n\n\n<li>A try-catch block handles any errors that may occur during the upload.<\/li>\n<\/ul>\n\n\n\n<p>The actual upload logic will be added after the server action for uploading the video to Cloudinary is handled.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Step 2: Set Up Server Action<\/strong><\/h3>\n\n\n\n<p>In this step, let\u2019s handle the video upload on the server side with server actions. This ensures that sensitive operations, like handling API keys, are kept on the server side.<\/p>\n\n\n\n<p>Create an actions\/upload.ts file and configure Cloudinary to handle the video upload:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-4\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-string\">'use server'<\/span>;\n\n<span class=\"hljs-keyword\">import<\/span> { v2 <span class=\"hljs-keyword\">as<\/span> cloudinary } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'cloudinary'<\/span>;\n\ncloudinary.config({\n\n\u00a0 <span class=\"hljs-attr\">cloud_name<\/span>: process.env.NEXT_PUBLIC_CLOUDINARY_CLOUD_NAME,\n\n\u00a0 <span class=\"hljs-attr\">api_key<\/span>: process.env.NEXT_PUBLIC_CLOUDINARY_API_KEY,\n\n\u00a0 <span class=\"hljs-attr\">api_secret<\/span>: process.env.CLOUDINARY_API_SECRET,\n\n});<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-4\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Once you have configured Cloudinary, set up an upload function to handle video uploads to Cloudinary:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-5\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">async<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">upload<\/span>(<span class=\"hljs-params\">formData: FormData<\/span>) <\/span>{\n\n\u00a0 \u00a0 <span class=\"hljs-keyword\">const<\/span> file = formData.get(<span class=\"hljs-string\">'video'<\/span>) <span class=\"hljs-keyword\">as<\/span> File;\n\n\u00a0 \u00a0 <span class=\"hljs-keyword\">const<\/span> buffer: Buffer = Buffer.from(<span class=\"hljs-keyword\">await<\/span> file.arrayBuffer());\n\n\u00a0 \u00a0 <span class=\"hljs-comment\">\/\/ Sanitize the public_id (file name) if needed<\/span>\n\n\u00a0 \u00a0 <span class=\"hljs-keyword\">const<\/span> safePublicId = file.name.replace(<span class=\"hljs-regexp\">\/&#91;^a-zA-Z0-9-_]\/g<\/span>,<span class=\"hljs-string\">'_'<\/span>);\n\n\u00a0 \u00a0 <span class=\"hljs-keyword\">const<\/span> uploadResponse = <span class=\"hljs-keyword\">await<\/span> <span class=\"hljs-keyword\">new<\/span> <span class=\"hljs-built_in\">Promise<\/span>&lt;{\n\n\u00a0 \u00a0 \u00a0 \u00a0 <span class=\"hljs-attr\">secure_url<\/span>: string;\n\n\u00a0 \u00a0 \u00a0 \u00a0 public_id: string;\n\n}&gt;<span class=\"hljs-function\">(<span class=\"hljs-params\">(resolve, reject<\/span>)=&gt;<\/span>{\n\n\u00a0 \u00a0 \u00a0 \u00a0 cloudinary.uploader\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 .upload_stream(\n\n{\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 <span class=\"hljs-attr\">resource_type<\/span>:<span class=\"hljs-string\">'video'<\/span>,\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 <span class=\"hljs-attr\">public_id<\/span>: safePublicId,\n\n},\n\n(error, result)=&gt;{\n\n<span class=\"hljs-keyword\">if<\/span>(error){\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 reject(error);\n\n}elseif(result){\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 resolve(result);\n\n}<span class=\"hljs-keyword\">else<\/span>{\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 reject(<span class=\"hljs-keyword\">new<\/span> <span class=\"hljs-built_in\">Error<\/span>(<span class=\"hljs-string\">'Upload result is undefined'<\/span>));\n\n}\n\n}\n\n)\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 .end(buffer);\n\n});\n\n\u00a0 \u00a0 <span class=\"hljs-comment\">\/\/ Return the original URL and the video ID<\/span>\n\n\u00a0 \u00a0 <span class=\"hljs-keyword\">return<\/span> {\n\n\u00a0 \u00a0 \u00a0 \u00a0 <span class=\"hljs-attr\">originalUrl<\/span>: uploadResponse.secure_url,\n\n\u00a0 \u00a0 \u00a0 \u00a0 <span class=\"hljs-attr\">videoId<\/span>: uploadResponse.public_id,\n\n\u00a0 \u00a0 };\n\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-5\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>In the code above, the video file is converted to a buffer file before being uploaded to Cloudinary\u2019s upload_stream function. Once the video is uploaded, the response includes important details, such as the video URL (<code>secure_url<\/code>) and the video identifier (<code>public_id<\/code>).<\/p>\n\n\n\n<p>The <code>originalUrl<\/code> and <code>videoId<\/code> are returned to the frontend. The videoId will be critical when you implement transformations on the client side, as Cloudinary uses this ID to reference and modify the video (e.g., applying subtitles or cropping).<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Step 3: Connect the Frontend to the Upload Action<\/strong><\/h3>\n\n\n\n<p>Modify the frontend to connect it with the server-side upload function. This will allow the form submission to trigger the video upload process.<\/p>\n\n\n\n<p>Here\u2019s how you update your <code>page.tsx<\/code> to integrate the server-side upload logic:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-6\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-comment\">\/\/ Import the upload function<\/span>\n\n<span class=\"hljs-keyword\">import<\/span> { upload } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'..\/actions\/upload'<\/span>;\n\n<span class=\"hljs-keyword\">const<\/span> handleSubmit = <span class=\"hljs-keyword\">async<\/span> (event: React.FormEvent&lt;HTMLFormElement&gt;) =&gt; {\n\n\u00a0 \u00a0 event.preventDefault();\n\n\u00a0 \u00a0 <span class=\"hljs-comment\">\/\/ Show loading state<\/span>\n\n\u00a0 \u00a0 setLoading(<span class=\"hljs-literal\">true<\/span>);\n\n\u00a0 \u00a0 <span class=\"hljs-keyword\">const<\/span> formData = <span class=\"hljs-keyword\">new<\/span> FormData(event.currentTarget);\n\n\u00a0 \u00a0 <span class=\"hljs-keyword\">try<\/span> {\n\n\u00a0 \u00a0 \u00a0 \u00a0 <span class=\"hljs-comment\">\/\/ Call the upload function to upload the video<\/span>\n\n\u00a0 \u00a0 \u00a0 \u00a0 <span class=\"hljs-keyword\">const<\/span> result = <span class=\"hljs-keyword\">await<\/span> upload(file);\u00a0\n\n\u00a0 \u00a0 \u00a0 \u00a0 <span class=\"hljs-comment\">\/\/ Log the video URL &amp; video ID<\/span>\n\n\u00a0 \u00a0 \u00a0 \u00a0 <span class=\"hljs-built_in\">console<\/span>.log(<span class=\"hljs-string\">'Uploaded video URL:'<\/span>, result.originalUrl);\n\n\u00a0 \u00a0 \u00a0 \u00a0 <span class=\"hljs-built_in\">console<\/span>.log(<span class=\"hljs-string\">'Uploaded video ID:'<\/span>, result.videoId);\u00a0\n\n\u00a0 \u00a0 } <span class=\"hljs-keyword\">catch<\/span> (error) {\n\n\u00a0 \u00a0 \u00a0 \u00a0 <span class=\"hljs-comment\">\/\/ Handle any errors that occur during upload<\/span>\n\n\u00a0 \u00a0 \u00a0 \u00a0 <span class=\"hljs-built_in\">console<\/span>.error(<span class=\"hljs-string\">'Upload failed:'<\/span>, error);\n\n\u00a0 \u00a0 } <span class=\"hljs-keyword\">finally<\/span> {\n\n\u00a0 \u00a0 \u00a0 \u00a0 <span class=\"hljs-comment\">\/\/ End loading state<\/span>\n\n\u00a0 \u00a0 \u00a0 \u00a0 setLoading(<span class=\"hljs-literal\">false<\/span>);\n\n\u00a0 \u00a0 }\n\n};<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-6\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>In the code above, the upload function is called. When the form is submitted, the selected video file is passed to the server action, which uploads it to Cloudinary.&nbsp;<\/p>\n\n\n\n<p>The server then returns the originalUrl (to display the video) and the videoId (for future transformations like adding subtitles or cropping). These values are logged to the console for now, but they\u2019ll be used in the next steps.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Step 4: Display the Uploaded Video<\/strong><\/h3>\n\n\n\n<p>Once the video is uploaded, it will be displayed using the originalUrl that Cloudinary provides. Update the page.tsx file to store the video URL and display it in a video player:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-7\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-keyword\">const<\/span> &#91;videoUrl, setVideoUrl] = useState&lt;string | <span class=\"hljs-literal\">null<\/span>&gt;(<span class=\"hljs-literal\">null<\/span>); <span class=\"hljs-comment\">\/\/ Store video URL<\/span>\n\n<span class=\"hljs-keyword\">const<\/span>&#91;videoId, setVideoId]= useState&lt;string | <span class=\"hljs-literal\">null<\/span>&gt;(<span class=\"hljs-literal\">null<\/span>);\n\n<span class=\"hljs-keyword\">const<\/span> handleSubmit = <span class=\"hljs-keyword\">async<\/span> (event: React.FormEvent&lt;HTMLFormElement&gt;) =&gt; {\n\n\u00a0 \u00a0 event.preventDefault();\n\n\u00a0 \u00a0 setLoading(<span class=\"hljs-literal\">true<\/span>);\n\n\u00a0 \u00a0 <span class=\"hljs-keyword\">const<\/span> formData = <span class=\"hljs-keyword\">new<\/span> FormData(event.currentTarget);\n\n\u00a0 \u00a0 <span class=\"hljs-keyword\">try<\/span> {\n\n\u00a0 \u00a0 \u00a0 \u00a0 <span class=\"hljs-keyword\">const<\/span> result = <span class=\"hljs-keyword\">await<\/span> upload(formData); <span class=\"hljs-comment\">\/\/ Upload the video<\/span>\n\n\u00a0 \u00a0 \u00a0 \u00a0 setVideoUrl(result.originalUrl); <span class=\"hljs-comment\">\/\/ Set the video URL in state<\/span>\n\n\u00a0 \u00a0 \u00a0 \u00a0 setVideoId(result.videoId);<span class=\"hljs-comment\">\/\/ Set the video ID for further transformations<\/span>\n\n\u00a0 \u00a0 } <span class=\"hljs-keyword\">catch<\/span> (error) {\n\n\u00a0 \u00a0 \u00a0 \u00a0 <span class=\"hljs-built_in\">console<\/span>.error(<span class=\"hljs-string\">'Upload failed:'<\/span>, error);\n\n\u00a0 \u00a0 } <span class=\"hljs-keyword\">finally<\/span> {\n\n\u00a0 \u00a0 \u00a0 \u00a0 setLoading(<span class=\"hljs-literal\">false<\/span>);\n\n\u00a0 \u00a0 }\n\n};\n\n<span class=\"hljs-comment\">\/\/ Render the uploaded video<\/span>\n\n<span class=\"hljs-keyword\">return<\/span> (\n\n\u00a0 \u00a0 <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span>&gt;<\/span>\n\n\u00a0 \u00a0 \u00a0 \u00a0 <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">h1<\/span>&gt;<\/span>Upload a Video<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">h1<\/span>&gt;<\/span>\n\n\u00a0 \u00a0 \u00a0 \u00a0 <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">form<\/span> <span class=\"hljs-attr\">onSubmit<\/span>=<span class=\"hljs-string\">{handleSubmit}<\/span>&gt;<\/span>\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">input<\/span> <span class=\"hljs-attr\">type<\/span>=<span class=\"hljs-string\">\"file\"<\/span> <span class=\"hljs-attr\">name<\/span>=<span class=\"hljs-string\">\"video\"<\/span> <span class=\"hljs-attr\">accept<\/span>=<span class=\"hljs-string\">\"video\/*\"<\/span> <span class=\"hljs-attr\">required<\/span> \/&gt;<\/span>\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">button<\/span> <span class=\"hljs-attr\">type<\/span>=<span class=\"hljs-string\">\"submit\"<\/span> <span class=\"hljs-attr\">disabled<\/span>=<span class=\"hljs-string\">{loading}<\/span>&gt;<\/span>\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 {loading ? 'Uploading...' : 'Upload Video'}\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">button<\/span>&gt;<\/span>\n\n\u00a0 \u00a0 \u00a0 \u00a0 <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">form<\/span>&gt;<\/span>\n\n\u00a0 \u00a0 \u00a0 \u00a0 {videoUrl &amp;&amp; (\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span>&gt;<\/span>\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">h2<\/span>&gt;<\/span>Uploaded Video:<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">h2<\/span>&gt;<\/span>\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">video<\/span> <span class=\"hljs-attr\">src<\/span>=<span class=\"hljs-string\">{videoUrl}<\/span> <span class=\"hljs-attr\">controls<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"w-full max-w-lg\"<\/span> \/&gt;<\/span>\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span>\n\n\u00a0 \u00a0 \u00a0 \u00a0 )}\n\n\u00a0 \u00a0 <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span><\/span>\n\n);<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-7\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>In this step, the videoUrl is stored in the component\u2019s state using setVideoUrl. Once the video is successfully uploaded, the URL is rendered inside an HTML &lt;video&gt; element, allowing the user to watch the uploaded video directly on the page.<\/p>\n\n\n\n<p>Now, users can select a video, upload it to Cloudinary, and see the uploaded video displayed on the screen. Store the videoIdin a state variable, as you\u2019ll use it in the next section to transform the video by adding subtitles and cropping it to the right format.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Smart Cropping Videos With Cloudinary<\/h2>\n\n\n\n<p>After you upload your video to Cloudinary, you can adjust it using <a target=\"_blank\" href=\"https:\/\/cloudinary.com\/guides\/video-effects\/smart-crop-video\" rel=\"noreferrer noopener\">Cloudinary&#8217;s smart cropping<\/a> feature, which allows you to adjust wide videos into a vertical format for YouTube Shorts.&nbsp;<\/p>\n\n\n\n<p>This feature analyzes the content of the video and adjusts the frame to focus on the main subject or action. In this case, let\u2019s focus on cropping the video to a vertical format using the c_fill, w, and h transformation parameters:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><code><strong>c_fill<\/strong><\/code><strong>.<\/strong> Crops the video to ensure the output fills the specified dimensions.<\/li>\n\n\n\n<li><strong>w and h.<\/strong> These define the width and height of the video. In this case, let\u2019s set the width to 720 and the height to 1280 to create a vertical video. You can modify this dimension to fit whatever platform specification you desire.<\/li>\n<\/ul>\n\n\n\n<p>Here\u2019s an example transformation URL:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-8\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\">https:<span class=\"hljs-comment\">\/\/res.cloudinary.com\/${cloudName}\/video\/upload\/c_fill,w_720,h_1280\/${videoId}.mp4<\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-8\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>This URL takes the uploaded video and transforms it to the specified width and height, filling the frame while ensuring the most important part of the video remains in focus.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Display the Uploaded and Transformed Videos Side by Side<\/strong><\/h2>\n\n\n\n<p>Now that you know what the transformation URL will look like, you can display the original video uploaded to Cloudianry and the transformed video side by side.&nbsp;<\/p>\n\n\n\n<p>Here\u2019s how to render both videos in the <code>page.tsx<\/code> file:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-9\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml shcb-wrap-lines\">{videoUrl &amp;&amp; transformedVideoUrl &amp;&amp; (\n\n\u00a0 \u00a0 <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"flex justify-center space-x-4 mt-10\"<\/span>&gt;<\/span>\n\n\u00a0 \u00a0 \u00a0 \u00a0 {\/* Original Video *\/}\n\n\u00a0 \u00a0 \u00a0 \u00a0 <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span>&gt;<\/span>\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">h2<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"text-lg font-bold text-center mb-4\"<\/span>&gt;<\/span>Uploaded Video:<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">h2<\/span>&gt;<\/span>\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">video<\/span> <span class=\"hljs-attr\">src<\/span>=<span class=\"hljs-string\">{videoUrl}<\/span> <span class=\"hljs-attr\">controls<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"w-full max-w-md border-4 rounded\"<\/span> \/&gt;<\/span>\n\n\u00a0 \u00a0 \u00a0 \u00a0 <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span>\n\n\u00a0 \u00a0 \u00a0 \u00a0 {\/* Transformed Video *\/}\n\n\u00a0 \u00a0 \u00a0 \u00a0 <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span>&gt;<\/span>\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">h2<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"text-lg font-bold text-center mb-4\"<\/span>&gt;<\/span>Transformed Video (Smart Cropping):<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">h2<\/span>&gt;<\/span>\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">video<\/span>\u00a0\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 <span class=\"hljs-attr\">src<\/span>=<span class=\"hljs-string\">{<\/span>`<span class=\"hljs-attr\">https:<\/span>\/\/<span class=\"hljs-attr\">res.cloudinary.com<\/span>\/${<span class=\"hljs-attr\">process.env.NEXT_PUBLIC_CLOUDINARY_CLOUD_NAME<\/span>}\/<span class=\"hljs-attr\">video<\/span>\/<span class=\"hljs-attr\">upload<\/span>\/<span class=\"hljs-attr\">c_fill<\/span>,<span class=\"hljs-attr\">w_720<\/span>,<span class=\"hljs-attr\">h_1280<\/span>\/${<span class=\"hljs-attr\">videoId<\/span>}<span class=\"hljs-attr\">.mp4<\/span>`}\u00a0\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 <span class=\"hljs-attr\">controls<\/span>\u00a0\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"w-full max-w-md border-4 rounded\"<\/span>\u00a0\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \/&gt;<\/span>\n\n\u00a0 \u00a0 \u00a0 \u00a0 <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span>\n\n\u00a0 \u00a0 <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span>\n\n)}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-9\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>The code above assigns the transformed URL, which incorporates Cloudinary&#8217;s transformation parameters, to the src attribute of the second &lt;video&gt; tag. This ensures the tag dynamically loads the transformed video, allowing you to display the smart-cropped version alongside the original.<\/p>\n\n\n<div class='c-callout  c-callout--inline-title c-callout--note'><strong class='c-callout__title'>Note:<\/strong> <p>Cloudinary offers many more transformation options beyond cropping and subtitles. For example, you can control the duration of the video or specify start and end points using Cloudinary\u2019s offset feature. Read more in the <a href=\"https:\/\/cloudinary.com\/documentation\/video_trimming_and_concatenating\" target=\"_blank\" rel=\"noreferrer noopener\">documentation<\/a>.<\/p>\n<\/div>\n\n\n<h2 class=\"wp-block-heading\">Generate and Add Subtitles to Videos With Cloudinary<\/h2>\n\n\n\n<p>Cloudinary integrates with <a target=\"_blank\" href=\"https:\/\/cloudinary.com\/documentation\/google_ai_video_transcription_addon\" rel=\"noreferrer noopener\">Google\u2019s Speech-to-Text<\/a> to auto-generate subtitles based on the audio in the uploaded video. Once generated, these subtitles can be applied as an overlay on the video.<\/p>\n\n\n\n<p>To auto-generate subtitles using Google Speech-to-Text, modify the upload process by adding a raw_convert transformation. This tells Cloudinary to send the video to Google\u2019s Speech-to-Text API and return a subtitle file in the form of .transcript.<\/p>\n\n\n\n<p>Before you do this, ensure you search for the Google AI Video Transcription add-on on your Cloudinary dashboard:<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img width=\"1000\" height=\"484\" data-public-id=\"Web_Assets\/blog\/blog-Build-a-YouTube-Shorts-Creator-With-Highlighted-Subtitles-in-Nextjs-2\/blog-Build-a-YouTube-Shorts-Creator-With-Highlighted-Subtitles-in-Nextjs-2.png\" loading=\"lazy\" decoding=\"async\" src=\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/w_1000,h_484,c_scale\/f_auto,q_auto\/v1734733990\/Web_Assets\/blog\/blog-Build-a-YouTube-Shorts-Creator-With-Highlighted-Subtitles-in-Nextjs-2\/blog-Build-a-YouTube-Shorts-Creator-With-Highlighted-Subtitles-in-Nextjs-2.png?_i=AA\" alt=\"\" class=\"wp-post-36475 wp-image-36477\" data-format=\"png\" data-transformations=\"f_auto,q_auto\" data-version=\"1734733990\" data-seo=\"1\" srcset=\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1734733990\/Web_Assets\/blog\/blog-Build-a-YouTube-Shorts-Creator-With-Highlighted-Subtitles-in-Nextjs-2\/blog-Build-a-YouTube-Shorts-Creator-With-Highlighted-Subtitles-in-Nextjs-2.png?_i=AA 1000w, https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1734733990\/Web_Assets\/blog\/blog-Build-a-YouTube-Shorts-Creator-With-Highlighted-Subtitles-in-Nextjs-2\/blog-Build-a-YouTube-Shorts-Creator-With-Highlighted-Subtitles-in-Nextjs-2.png?_i=AA 300w, https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1734733990\/Web_Assets\/blog\/blog-Build-a-YouTube-Shorts-Creator-With-Highlighted-Subtitles-in-Nextjs-2\/blog-Build-a-YouTube-Shorts-Creator-With-Highlighted-Subtitles-in-Nextjs-2.png?_i=AA 768w\" sizes=\"auto, (max-width: 1000px) 100vw, 1000px\" \/><figcaption class=\"wp-element-caption\">Google AI Video Transcription add-on<\/figcaption><\/figure>\n\n\n\n<p>Click the add-on and subscribe to the free plan:<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img width=\"1000\" height=\"485\" data-public-id=\"Web_Assets\/blog\/blog-Build-a-YouTube-Shorts-Creator-With-Highlighted-Subtitles-in-Nextjs-3\/blog-Build-a-YouTube-Shorts-Creator-With-Highlighted-Subtitles-in-Nextjs-3.png\" loading=\"lazy\" decoding=\"async\" src=\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/w_1000,h_485,c_scale\/f_auto,q_auto\/v1734733985\/Web_Assets\/blog\/blog-Build-a-YouTube-Shorts-Creator-With-Highlighted-Subtitles-in-Nextjs-3\/blog-Build-a-YouTube-Shorts-Creator-With-Highlighted-Subtitles-in-Nextjs-3.png?_i=AA\" alt=\"\" class=\"wp-post-36475 wp-image-36478\" data-format=\"png\" data-transformations=\"f_auto,q_auto\" data-version=\"1734733985\" data-seo=\"1\" srcset=\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1734733985\/Web_Assets\/blog\/blog-Build-a-YouTube-Shorts-Creator-With-Highlighted-Subtitles-in-Nextjs-3\/blog-Build-a-YouTube-Shorts-Creator-With-Highlighted-Subtitles-in-Nextjs-3.png?_i=AA 1000w, https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1734733985\/Web_Assets\/blog\/blog-Build-a-YouTube-Shorts-Creator-With-Highlighted-Subtitles-in-Nextjs-3\/blog-Build-a-YouTube-Shorts-Creator-With-Highlighted-Subtitles-in-Nextjs-3.png?_i=AA 300w, https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1734733985\/Web_Assets\/blog\/blog-Build-a-YouTube-Shorts-Creator-With-Highlighted-Subtitles-in-Nextjs-3\/blog-Build-a-YouTube-Shorts-Creator-With-Highlighted-Subtitles-in-Nextjs-3.png?_i=AA 768w\" sizes=\"auto, (max-width: 1000px) 100vw, 1000px\" \/><figcaption class=\"wp-element-caption\">Subscribe to the free plan<\/figcaption><\/figure>\n\n\n\n<p>Once Google AI Video Transcription is set up, you can make API calls to Cloudinary. Modify the uploadResponse <em>upload<\/em> server function to include subtitle generation:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-10\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-keyword\">const<\/span> uploadResponse = <span class=\"hljs-keyword\">await<\/span> <span class=\"hljs-keyword\">new<\/span> <span class=\"hljs-built_in\">Promise<\/span>&lt;{\n\n\u00a0 \u00a0 <span class=\"hljs-attr\">secure_url<\/span>: string;\n\n\u00a0 \u00a0 public_id: string;\n\n}&gt;<span class=\"hljs-function\">(<span class=\"hljs-params\">(resolve, reject<\/span>)=&gt;<\/span>{\n\n\u00a0 \u00a0 cloudinary.uploader\n\n\u00a0 \u00a0 \u00a0 \u00a0 .upload_stream(\n\n{\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 <span class=\"hljs-attr\">resource_type<\/span>:<span class=\"hljs-string\">'video'<\/span>,\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 <span class=\"hljs-attr\">public_id<\/span>: safePublicId,\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 <span class=\"hljs-attr\">raw_convert<\/span>:<span class=\"hljs-string\">'google_speech'<\/span>,<span class=\"hljs-comment\">\/\/ Request transcription<\/span>\n\n},\n\n(error, result)=&gt;{\n\n<span class=\"hljs-keyword\">if<\/span>(error){\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 reject(<span class=\"hljs-string\">`Upload failed: <span class=\"hljs-subst\">${error.message}<\/span>`<\/span>);\n\n}<span class=\"hljs-keyword\">else<\/span>{\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 resolve(result);\n\n}\n\n}\n\n)\n\n\u00a0 \u00a0 \u00a0 \u00a0 .end(buffer);\n\n});<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-10\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Once the subtitles are generated, you can overlay them on the transformed video using Cloudinary\u2019s <a target=\"_blank\" href=\"https:\/\/cloudinary.com\/documentation\/google_ai_video_transcription_addon#displaying_transcripts_as_subtitle_overlays\" rel=\"noreferrer noopener\">l_subtitles<\/a><a target=\"_blank\" href=\"https:\/\/cloudinary.com\/documentation\/google_ai_video_transcription_addon#displaying_transcripts_as_subtitle_overlays\" rel=\"noreferrer noopener\"> transformation<\/a> or by adding the transcript file as a track to the video. For this demo, we\u2019ll use the latter option.<\/p>\n\n\n\n<p>To include the transcript as a separate track for a video player, you can request Cloudinary to generate an <a href=\"https:\/\/en.wikipedia.org\/wiki\/SubRip\" target=\"_blank\" rel=\"noreferrer noopener\">SRT<\/a> and\/or <a href=\"https:\/\/en.wikipedia.org\/wiki\/WebVTT\" target=\"_blank\" rel=\"noreferrer noopener\">WebVTT<\/a> file by using the <code>google_speech<\/code> value with the <code>srt<\/code> or <code>vtt<\/code> qualifiers. For example, to upload a video and request a WebVTT (.vtt) file for the transcript, set the raw_convert parameter as follows during the upload process:<\/p>\n\n\n\n<p><code><em>raw_convert<\/em>:'google_speech:vtt',<\/code><\/p>\n\n\n\n<p>With the generated transcript file, you can now modify the transformed video\u2019s &lt;video&gt; tag to include both the video source and the subtitles track using the &lt;track&gt; tag:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-11\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml shcb-wrap-lines\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">video<\/span>\n\n\u00a0 <span class=\"hljs-attr\">crossOrigin<\/span>=<span class=\"hljs-string\">\"anonymous\"<\/span>\n\n\u00a0 <span class=\"hljs-attr\">controls<\/span>\n\n\u00a0 <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"w-full max-w-md border-4 rounded\"<\/span>\n\n&gt;<\/span>\n\n\u00a0 <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">source<\/span>\n\n\u00a0 \u00a0 <span class=\"hljs-attr\">id<\/span>=<span class=\"hljs-string\">\"mp4\"<\/span>\n\n\u00a0 \u00a0 <span class=\"hljs-attr\">src<\/span>=<span class=\"hljs-string\">{<\/span>`<span class=\"hljs-attr\">https:<\/span>\/\/<span class=\"hljs-attr\">res.cloudinary.com<\/span>\/${<span class=\"hljs-attr\">process.env.NEXT_PUBLIC_CLOUDINARY_CLOUD_NAME<\/span>}\/<span class=\"hljs-attr\">video<\/span>\/<span class=\"hljs-attr\">upload<\/span>\/<span class=\"hljs-attr\">c_fill<\/span>,<span class=\"hljs-attr\">w_720<\/span>,<span class=\"hljs-attr\">h_1280<\/span>\/${<span class=\"hljs-attr\">videoId<\/span>}<span class=\"hljs-attr\">.mp4<\/span>`}\n\n\u00a0 \u00a0 <span class=\"hljs-attr\">type<\/span>=<span class=\"hljs-string\">\"video\/mp4\"<\/span>\n\n\u00a0 \/&gt;<\/span>\n\n\u00a0 <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">track<\/span>\n\n\u00a0 \u00a0 <span class=\"hljs-attr\">label<\/span>=<span class=\"hljs-string\">\"English\"<\/span>\n\n\u00a0 \u00a0 <span class=\"hljs-attr\">kind<\/span>=<span class=\"hljs-string\">\"subtitles\"<\/span>\n\n\u00a0 \u00a0 <span class=\"hljs-attr\">srcLang<\/span>=<span class=\"hljs-string\">\"en\"<\/span>\n\n\u00a0 \u00a0 <span class=\"hljs-attr\">src<\/span>=<span class=\"hljs-string\">{<\/span>`<span class=\"hljs-attr\">https:<\/span>\/\/<span class=\"hljs-attr\">res.cloudinary.com<\/span>\/${<span class=\"hljs-attr\">process.env.NEXT_PUBLIC_CLOUDINARY_CLOUD_NAME<\/span>}\/<span class=\"hljs-attr\">raw<\/span>\/<span class=\"hljs-attr\">upload<\/span>\/${<span class=\"hljs-attr\">videoId<\/span>}<span class=\"hljs-attr\">.vtt<\/span>`}\n\n\u00a0 \u00a0 <span class=\"hljs-attr\">default<\/span>\n\n\u00a0 \/&gt;<\/span>\n\n<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">video<\/span>&gt;<\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-11\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>In this setup:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>The &lt;source> tag specifies the transformed video file.<\/li>\n\n\n\n<li>The &lt;track> tag references the WebVTT file containing the subtitles, allowing them to be displayed in the video player.<\/li>\n<\/ul>\n\n\n\n<p>This method ensures subtitles are accessible and can be toggled by users. For more details on formatting subtitles, refer to Cloudinary&#8217;s <a target=\"_blank\" href=\"https:\/\/cloudinary.com\/documentation\/google_ai_video_transcription_addon#generating_standard_subtitle_formats\" rel=\"noreferrer noopener\">documentation<\/a>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Conclusion<\/h2>\n\n\n\n<p>This blog post demonstrates how to transform wide videos into vertical YouTube Shorts with smart cropping, and auto-generated subtitles, all using Cloudinary and Next.js. Although we focused on creating YouTube Shorts, the same techniques can be easily applied to videos for platforms like TikTok, Instagram Reels, and Facebook.<\/p>\n\n\n\n<p>By combining <a href=\"https:\/\/cloudinary.com\/documentation\/video_manipulation_and_delivery\" target=\"_blank\" rel=\"noreferrer noopener\">Cloudinary\u2019s powerful video transformation<\/a> capabilities with a simple upload interface, creators can easily convert their videos into the required format for social media platforms while adding professional features like subtitles. <a href=\"https:\/\/cloudinary.com\/users\/register_free\" target=\"_blank\" rel=\"noreferrer noopener\">Sign up for a free account today<\/a> to try it for yourself.<\/p>\n\n\n\n<p>If you enjoyed this post and want to discuss it more, join the <a target=\"_blank\" href=\"https:\/\/community.cloudinary.com\/\" rel=\"noreferrer noopener\">Cloudinary Community forum<\/a> and its associated <a target=\"_blank\" href=\"https:\/\/discord.com\/invite\/cloudinary\" rel=\"noreferrer noopener\">Discord<\/a>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Resources<\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li><a href=\"https:\/\/cloudinary.com\/documentation\/video_manipulation_and_delivery\" target=\"_blank\" rel=\"noreferrer noopener\">Cloudinary Video Documentation<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/cloudinary.com\/documentation\/programmable_media_overview\" target=\"_blank\" rel=\"noreferrer noopener\">Cloudinary Overall Media Documentation<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/cloudinary.com\/documentation\/google_ai_video_transcription_addon\" target=\"_blank\" rel=\"noreferrer noopener\">Google AI Video Transcription<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/cloudinary.com\/documentation\/video_manipulation_and_delivery\" target=\"_blank\" rel=\"noreferrer noopener\">Video transformations<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/nextjs.org\/docs\" target=\"_blank\" rel=\"noreferrer noopener\">Next.js Documentation<\/a><\/li>\n<\/ul>\n","protected":false},"excerpt":{"rendered":"<p>YouTube Shorts are a powerful way to reach a wider audience with short, engaging videos. However, many creators face a common challenge: transforming widescreen videos into the vertical format required for YouTube Shorts without losing the key parts of the frame. This blog post explains how to automatically transform wide videos into YouTube Shorts using [&hellip;]<\/p>\n","protected":false},"author":87,"featured_media":36481,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"_cloudinary_featured_overwrite":false,"footnotes":""},"categories":[1],"tags":[212,403,304],"class_list":["post-36475","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-uncategorized","tag-next-js","tag-transcriptions","tag-video-transformation"],"acf":[],"yoast_head":"<!-- This site is optimized with the Yoast SEO Premium plugin v25.6 (Yoast SEO v26.9) - https:\/\/yoast.com\/product\/yoast-seo-premium-wordpress\/ -->\n<title>Build a YouTube Shorts Creator With Highlighted Subtitles in Next.js<\/title>\n<meta name=\"description\" content=\"Convert widescreen videos into YouTube Shorts with Cloudinary\u2019s smart cropping and add auto-generated subtitles with Google Speech-to-Text in Next.js.\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/cloudinary.com\/blog\/youtube-shorts-creator-highlighted-subtitles-next-js\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Build a YouTube Shorts Creator With Highlighted Subtitles in Next.js\" \/>\n<meta property=\"og:description\" content=\"Convert widescreen videos into YouTube Shorts with Cloudinary\u2019s smart cropping and add auto-generated subtitles with Google Speech-to-Text in Next.js.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/cloudinary.com\/blog\/youtube-shorts-creator-highlighted-subtitles-next-js\" \/>\n<meta property=\"og:site_name\" content=\"Cloudinary Blog\" \/>\n<meta property=\"article:published_time\" content=\"2024-12-20T15:00:00+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2024-12-20T22:38:51+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/v1734734325\/Web_Assets\/blog\/Build_a_YouTube_shorts_creator_with_highlighted_subtitles_in_Next.js_\/Build_a_YouTube_shorts_creator_with_highlighted_subtitles_in_Next-js_-jpg?_i=AA\" \/>\n\t<meta property=\"og:image:width\" content=\"2000\" \/>\n\t<meta property=\"og:image:height\" content=\"1100\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/jpeg\" \/>\n<meta name=\"author\" content=\"melindapham\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"NewsArticle\",\"@id\":\"https:\/\/cloudinary.com\/blog\/youtube-shorts-creator-highlighted-subtitles-next-js#article\",\"isPartOf\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/youtube-shorts-creator-highlighted-subtitles-next-js\"},\"author\":{\"name\":\"melindapham\",\"@id\":\"https:\/\/cloudinary.com\/blog\/#\/schema\/person\/0d5ad601e4c3b5be89245dfb14be42d9\"},\"headline\":\"Build a YouTube Shorts Creator With Highlighted Subtitles in Next.js\",\"datePublished\":\"2024-12-20T15:00:00+00:00\",\"dateModified\":\"2024-12-20T22:38:51+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/youtube-shorts-creator-highlighted-subtitles-next-js\"},\"wordCount\":1533,\"publisher\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/#organization\"},\"image\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/youtube-shorts-creator-highlighted-subtitles-next-js#primaryimage\"},\"thumbnailUrl\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1734734325\/Web_Assets\/blog\/Build_a_YouTube_shorts_creator_with_highlighted_subtitles_in_Next.js_\/Build_a_YouTube_shorts_creator_with_highlighted_subtitles_in_Next.js_.jpg?_i=AA\",\"keywords\":[\"Next.js\",\"Transcriptions\",\"Video Transformation\"],\"inLanguage\":\"en-US\",\"copyrightYear\":\"2024\",\"copyrightHolder\":{\"@id\":\"https:\/\/cloudinary.com\/#organization\"}},{\"@type\":\"WebPage\",\"@id\":\"https:\/\/cloudinary.com\/blog\/youtube-shorts-creator-highlighted-subtitles-next-js\",\"url\":\"https:\/\/cloudinary.com\/blog\/youtube-shorts-creator-highlighted-subtitles-next-js\",\"name\":\"Build a YouTube Shorts Creator With Highlighted Subtitles in Next.js\",\"isPartOf\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/youtube-shorts-creator-highlighted-subtitles-next-js#primaryimage\"},\"image\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/youtube-shorts-creator-highlighted-subtitles-next-js#primaryimage\"},\"thumbnailUrl\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1734734325\/Web_Assets\/blog\/Build_a_YouTube_shorts_creator_with_highlighted_subtitles_in_Next.js_\/Build_a_YouTube_shorts_creator_with_highlighted_subtitles_in_Next.js_.jpg?_i=AA\",\"datePublished\":\"2024-12-20T15:00:00+00:00\",\"dateModified\":\"2024-12-20T22:38:51+00:00\",\"description\":\"Convert widescreen videos into YouTube Shorts with Cloudinary\u2019s smart cropping and add auto-generated subtitles with Google Speech-to-Text in Next.js.\",\"breadcrumb\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/youtube-shorts-creator-highlighted-subtitles-next-js#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/cloudinary.com\/blog\/youtube-shorts-creator-highlighted-subtitles-next-js\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/cloudinary.com\/blog\/youtube-shorts-creator-highlighted-subtitles-next-js#primaryimage\",\"url\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1734734325\/Web_Assets\/blog\/Build_a_YouTube_shorts_creator_with_highlighted_subtitles_in_Next.js_\/Build_a_YouTube_shorts_creator_with_highlighted_subtitles_in_Next.js_.jpg?_i=AA\",\"contentUrl\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1734734325\/Web_Assets\/blog\/Build_a_YouTube_shorts_creator_with_highlighted_subtitles_in_Next.js_\/Build_a_YouTube_shorts_creator_with_highlighted_subtitles_in_Next.js_.jpg?_i=AA\",\"width\":2000,\"height\":1100},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/cloudinary.com\/blog\/youtube-shorts-creator-highlighted-subtitles-next-js#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/cloudinary.com\/blog\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Build a YouTube Shorts Creator With Highlighted Subtitles in Next.js\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/cloudinary.com\/blog\/#website\",\"url\":\"https:\/\/cloudinary.com\/blog\/\",\"name\":\"Cloudinary Blog\",\"description\":\"\",\"publisher\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/cloudinary.com\/blog\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Organization\",\"@id\":\"https:\/\/cloudinary.com\/blog\/#organization\",\"name\":\"Cloudinary Blog\",\"url\":\"https:\/\/cloudinary.com\/blog\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/cloudinary.com\/blog\/#\/schema\/logo\/image\/\",\"url\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1649718331\/Web_Assets\/blog\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877.png?_i=AA\",\"contentUrl\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1649718331\/Web_Assets\/blog\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877.png?_i=AA\",\"width\":312,\"height\":60,\"caption\":\"Cloudinary Blog\"},\"image\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/#\/schema\/logo\/image\/\"}},{\"@type\":\"Person\",\"@id\":\"https:\/\/cloudinary.com\/blog\/#\/schema\/person\/0d5ad601e4c3b5be89245dfb14be42d9\",\"name\":\"melindapham\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/cloudinary.com\/blog\/#\/schema\/person\/image\/\",\"url\":\"https:\/\/secure.gravatar.com\/avatar\/e6f989fa97fe94be61596259d8629c3df65aec4c7da5c0000f90d810f313d4f4?s=96&d=mm&r=g\",\"contentUrl\":\"https:\/\/secure.gravatar.com\/avatar\/e6f989fa97fe94be61596259d8629c3df65aec4c7da5c0000f90d810f313d4f4?s=96&d=mm&r=g\",\"caption\":\"melindapham\"}}]}<\/script>\n<!-- \/ Yoast SEO Premium plugin. -->","yoast_head_json":{"title":"Build a YouTube Shorts Creator With Highlighted Subtitles in Next.js","description":"Convert widescreen videos into YouTube Shorts with Cloudinary\u2019s smart cropping and add auto-generated subtitles with Google Speech-to-Text in Next.js.","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/cloudinary.com\/blog\/youtube-shorts-creator-highlighted-subtitles-next-js","og_locale":"en_US","og_type":"article","og_title":"Build a YouTube Shorts Creator With Highlighted Subtitles in Next.js","og_description":"Convert widescreen videos into YouTube Shorts with Cloudinary\u2019s smart cropping and add auto-generated subtitles with Google Speech-to-Text in Next.js.","og_url":"https:\/\/cloudinary.com\/blog\/youtube-shorts-creator-highlighted-subtitles-next-js","og_site_name":"Cloudinary Blog","article_published_time":"2024-12-20T15:00:00+00:00","article_modified_time":"2024-12-20T22:38:51+00:00","og_image":[{"width":2000,"height":1100,"url":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/v1734734325\/Web_Assets\/blog\/Build_a_YouTube_shorts_creator_with_highlighted_subtitles_in_Next.js_\/Build_a_YouTube_shorts_creator_with_highlighted_subtitles_in_Next-js_-jpg?_i=AA","type":"image\/jpeg"}],"author":"melindapham","twitter_card":"summary_large_image","schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"NewsArticle","@id":"https:\/\/cloudinary.com\/blog\/youtube-shorts-creator-highlighted-subtitles-next-js#article","isPartOf":{"@id":"https:\/\/cloudinary.com\/blog\/youtube-shorts-creator-highlighted-subtitles-next-js"},"author":{"name":"melindapham","@id":"https:\/\/cloudinary.com\/blog\/#\/schema\/person\/0d5ad601e4c3b5be89245dfb14be42d9"},"headline":"Build a YouTube Shorts Creator With Highlighted Subtitles in Next.js","datePublished":"2024-12-20T15:00:00+00:00","dateModified":"2024-12-20T22:38:51+00:00","mainEntityOfPage":{"@id":"https:\/\/cloudinary.com\/blog\/youtube-shorts-creator-highlighted-subtitles-next-js"},"wordCount":1533,"publisher":{"@id":"https:\/\/cloudinary.com\/blog\/#organization"},"image":{"@id":"https:\/\/cloudinary.com\/blog\/youtube-shorts-creator-highlighted-subtitles-next-js#primaryimage"},"thumbnailUrl":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1734734325\/Web_Assets\/blog\/Build_a_YouTube_shorts_creator_with_highlighted_subtitles_in_Next.js_\/Build_a_YouTube_shorts_creator_with_highlighted_subtitles_in_Next.js_.jpg?_i=AA","keywords":["Next.js","Transcriptions","Video Transformation"],"inLanguage":"en-US","copyrightYear":"2024","copyrightHolder":{"@id":"https:\/\/cloudinary.com\/#organization"}},{"@type":"WebPage","@id":"https:\/\/cloudinary.com\/blog\/youtube-shorts-creator-highlighted-subtitles-next-js","url":"https:\/\/cloudinary.com\/blog\/youtube-shorts-creator-highlighted-subtitles-next-js","name":"Build a YouTube Shorts Creator With Highlighted Subtitles in Next.js","isPartOf":{"@id":"https:\/\/cloudinary.com\/blog\/#website"},"primaryImageOfPage":{"@id":"https:\/\/cloudinary.com\/blog\/youtube-shorts-creator-highlighted-subtitles-next-js#primaryimage"},"image":{"@id":"https:\/\/cloudinary.com\/blog\/youtube-shorts-creator-highlighted-subtitles-next-js#primaryimage"},"thumbnailUrl":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1734734325\/Web_Assets\/blog\/Build_a_YouTube_shorts_creator_with_highlighted_subtitles_in_Next.js_\/Build_a_YouTube_shorts_creator_with_highlighted_subtitles_in_Next.js_.jpg?_i=AA","datePublished":"2024-12-20T15:00:00+00:00","dateModified":"2024-12-20T22:38:51+00:00","description":"Convert widescreen videos into YouTube Shorts with Cloudinary\u2019s smart cropping and add auto-generated subtitles with Google Speech-to-Text in Next.js.","breadcrumb":{"@id":"https:\/\/cloudinary.com\/blog\/youtube-shorts-creator-highlighted-subtitles-next-js#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/cloudinary.com\/blog\/youtube-shorts-creator-highlighted-subtitles-next-js"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/cloudinary.com\/blog\/youtube-shorts-creator-highlighted-subtitles-next-js#primaryimage","url":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1734734325\/Web_Assets\/blog\/Build_a_YouTube_shorts_creator_with_highlighted_subtitles_in_Next.js_\/Build_a_YouTube_shorts_creator_with_highlighted_subtitles_in_Next.js_.jpg?_i=AA","contentUrl":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1734734325\/Web_Assets\/blog\/Build_a_YouTube_shorts_creator_with_highlighted_subtitles_in_Next.js_\/Build_a_YouTube_shorts_creator_with_highlighted_subtitles_in_Next.js_.jpg?_i=AA","width":2000,"height":1100},{"@type":"BreadcrumbList","@id":"https:\/\/cloudinary.com\/blog\/youtube-shorts-creator-highlighted-subtitles-next-js#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/cloudinary.com\/blog\/"},{"@type":"ListItem","position":2,"name":"Build a YouTube Shorts Creator With Highlighted Subtitles in Next.js"}]},{"@type":"WebSite","@id":"https:\/\/cloudinary.com\/blog\/#website","url":"https:\/\/cloudinary.com\/blog\/","name":"Cloudinary Blog","description":"","publisher":{"@id":"https:\/\/cloudinary.com\/blog\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/cloudinary.com\/blog\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Organization","@id":"https:\/\/cloudinary.com\/blog\/#organization","name":"Cloudinary Blog","url":"https:\/\/cloudinary.com\/blog\/","logo":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/cloudinary.com\/blog\/#\/schema\/logo\/image\/","url":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1649718331\/Web_Assets\/blog\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877.png?_i=AA","contentUrl":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1649718331\/Web_Assets\/blog\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877.png?_i=AA","width":312,"height":60,"caption":"Cloudinary Blog"},"image":{"@id":"https:\/\/cloudinary.com\/blog\/#\/schema\/logo\/image\/"}},{"@type":"Person","@id":"https:\/\/cloudinary.com\/blog\/#\/schema\/person\/0d5ad601e4c3b5be89245dfb14be42d9","name":"melindapham","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/cloudinary.com\/blog\/#\/schema\/person\/image\/","url":"https:\/\/secure.gravatar.com\/avatar\/e6f989fa97fe94be61596259d8629c3df65aec4c7da5c0000f90d810f313d4f4?s=96&d=mm&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/e6f989fa97fe94be61596259d8629c3df65aec4c7da5c0000f90d810f313d4f4?s=96&d=mm&r=g","caption":"melindapham"}}]}},"jetpack_featured_media_url":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1734734325\/Web_Assets\/blog\/Build_a_YouTube_shorts_creator_with_highlighted_subtitles_in_Next.js_\/Build_a_YouTube_shorts_creator_with_highlighted_subtitles_in_Next.js_.jpg?_i=AA","_links":{"self":[{"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/posts\/36475","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/users\/87"}],"replies":[{"embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/comments?post=36475"}],"version-history":[{"count":2,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/posts\/36475\/revisions"}],"predecessor-version":[{"id":36480,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/posts\/36475\/revisions\/36480"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/media\/36481"}],"wp:attachment":[{"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/media?parent=36475"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/categories?post=36475"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/tags?post=36475"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}