{"id":34777,"date":"2024-07-18T07:00:00","date_gmt":"2024-07-18T14:00:00","guid":{"rendered":"https:\/\/cloudinary.com\/blog\/?p=34777"},"modified":"2025-04-16T14:41:31","modified_gmt":"2025-04-16T21:41:31","slug":"transcription-video-in-next-js","status":"publish","type":"post","link":"https:\/\/cloudinary.com\/blog\/transcription-video-in-next-js","title":{"rendered":"Add a Transcription to Your Video in Next.js"},"content":{"rendered":"\n<p>Accessibility is a core factor of software development, and for websites, video transcriptions improve the user experience. Video transcriptions are instrumental in many cases, such as for viewers with hearing impairments, those in noisy environments, language learners following along with lectures, and people who prefer to consume information by reading.<\/p>\n\n\n\n<p>In this blog post, we\u2019ll learn how to generate video transcription into our Next.js applications using Cloudinary\u2019s AI features.&nbsp;<\/p>\n\n\n\n<p>To follow along, you\u2019ll need a basic understanding of TypeScript or JavaScript and a Cloudinary account; you can <a target=\"_blank\" href=\"https:\/\/cloudinary.com\/users\/register_free\" rel=\"noreferrer noopener\">create one for free<\/a>. The completed app can be found in this <a target=\"_blank\" href=\"https:\/\/github.com\/folucode\/cloudinary-video-transcription\" rel=\"noreferrer noopener\">GitHub Repository<\/a>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Set Up a Next.js App<\/h2>\n\n\n\n<p>Run the command below to setup a Next.js app. You can give it any name you wish:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-1\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml shcb-wrap-lines\">$ npx create-next-app@latest <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">YOUR-APPLICATION-NAME<\/span>&gt;<\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-1\"><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>Select the options below in the process of setting up the app, or you can choose whatever setting works for you:<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img width=\"1024\" height=\"171\" data-public-id=\"Web_Assets\/blog\/blog-Add-a-Transcription-to-Your-Video-in-Nextjs-1\/blog-Add-a-Transcription-to-Your-Video-in-Nextjs-1.png\" decoding=\"async\" src=\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/w_1024,h_171,c_scale\/f_auto,q_auto\/v1721320179\/Web_Assets\/blog\/blog-Add-a-Transcription-to-Your-Video-in-Nextjs-1\/blog-Add-a-Transcription-to-Your-Video-in-Nextjs-1.png?_i=AA\" alt=\"\" class=\"wp-post-34777 wp-image-34778\" loading=\"lazy\" data-format=\"png\" data-transformations=\"f_auto,q_auto\" data-version=\"1721320179\" data-seo=\"1\" srcset=\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1721320179\/Web_Assets\/blog\/blog-Add-a-Transcription-to-Your-Video-in-Nextjs-1\/blog-Add-a-Transcription-to-Your-Video-in-Nextjs-1.png?_i=AA 1159w, https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1721320179\/Web_Assets\/blog\/blog-Add-a-Transcription-to-Your-Video-in-Nextjs-1\/blog-Add-a-Transcription-to-Your-Video-in-Nextjs-1.png?_i=AA 300w, https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1721320179\/Web_Assets\/blog\/blog-Add-a-Transcription-to-Your-Video-in-Nextjs-1\/blog-Add-a-Transcription-to-Your-Video-in-Nextjs-1.png?_i=AA 768w, https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1721320179\/Web_Assets\/blog\/blog-Add-a-Transcription-to-Your-Video-in-Nextjs-1\/blog-Add-a-Transcription-to-Your-Video-in-Nextjs-1.png?_i=AA 1024w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p>Install the Cloudinary package with the command below:<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs shcb-wrap-lines\">$ npm i cloudinary<\/code><\/span><\/pre>\n\n\n<h2 class=\"wp-block-heading\">Style the Application<\/h2>\n\n\n\n<p>Change the directory into the application we created above and navigate to the <code>page.module.css<\/code> file. Replace the boilerplate code with the code below. These are just the basic CSS styles we would use in our app:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-2\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css shcb-wrap-lines\">\n<span class=\"hljs-selector-class\">.main<\/span> {\n  <span class=\"hljs-attribute\">display<\/span>: flex;\n  <span class=\"hljs-attribute\">flex-direction<\/span>: column;\n  <span class=\"hljs-attribute\">justify-content<\/span>: space-evenly;\n  <span class=\"hljs-attribute\">align-items<\/span>: center;\n  <span class=\"hljs-attribute\">padding<\/span>: <span class=\"hljs-number\">6rem<\/span>;\n  <span class=\"hljs-attribute\">height<\/span>: <span class=\"hljs-number\">100vh<\/span>;\n}\n<span class=\"hljs-selector-class\">.main<\/span> <span class=\"hljs-selector-tag\">form<\/span> {\n  <span class=\"hljs-attribute\">background-color<\/span>: white;\n  <span class=\"hljs-attribute\">width<\/span>: <span class=\"hljs-number\">400px<\/span>;\n  <span class=\"hljs-attribute\">border-radius<\/span>: <span class=\"hljs-number\">5px<\/span>;\n  <span class=\"hljs-attribute\">padding<\/span>: <span class=\"hljs-number\">10px<\/span>;\n}\n<span class=\"hljs-selector-class\">.main<\/span> <span class=\"hljs-selector-tag\">form<\/span> <span class=\"hljs-selector-tag\">label<\/span> {\n  <span class=\"hljs-attribute\">color<\/span>: black;\n}\n<span class=\"hljs-selector-class\">.main<\/span> <span class=\"hljs-selector-tag\">form<\/span> <span class=\"hljs-selector-tag\">input<\/span> {\n  <span class=\"hljs-attribute\">padding<\/span>: <span class=\"hljs-number\">0.5rem<\/span>;\n  <span class=\"hljs-attribute\">border<\/span>: <span class=\"hljs-number\">1px<\/span> solid <span class=\"hljs-number\">#ccc<\/span>;\n  <span class=\"hljs-attribute\">border-radius<\/span>: <span class=\"hljs-number\">4px<\/span>;\n  <span class=\"hljs-attribute\">background<\/span>: none;\n  <span class=\"hljs-attribute\">color<\/span>: black;\n  <span class=\"hljs-attribute\">margin<\/span>: <span class=\"hljs-number\">0<\/span> <span class=\"hljs-number\">10px<\/span>;\n}\n<span class=\"hljs-selector-class\">.main<\/span> <span class=\"hljs-selector-tag\">form<\/span> <span class=\"hljs-selector-tag\">button<\/span> {\n  <span class=\"hljs-attribute\">height<\/span>: <span class=\"hljs-number\">40px<\/span>;\n  <span class=\"hljs-attribute\">width<\/span>: <span class=\"hljs-number\">100%<\/span>;\n  <span class=\"hljs-attribute\">margin<\/span>: <span class=\"hljs-number\">20px<\/span> <span class=\"hljs-number\">0<\/span>;\n  <span class=\"hljs-attribute\">color<\/span>: white;\n  <span class=\"hljs-attribute\">font-size<\/span>: large;\n  <span class=\"hljs-attribute\">border-radius<\/span>: <span class=\"hljs-number\">5px<\/span>;\n  <span class=\"hljs-attribute\">border<\/span>: none;\n  <span class=\"hljs-attribute\">background-color<\/span>: <span class=\"hljs-built_in\">rgb<\/span>(<span class=\"hljs-number\">28<\/span>, <span class=\"hljs-number\">25<\/span>, <span class=\"hljs-number\">25<\/span>);\n}\n<span class=\"hljs-selector-class\">.main<\/span> <span class=\"hljs-selector-tag\">form<\/span> <span class=\"hljs-selector-tag\">button<\/span><span class=\"hljs-selector-pseudo\">:hover<\/span> {\n  <span class=\"hljs-attribute\">cursor<\/span>: pointer;\n  <span class=\"hljs-attribute\">background-color<\/span>: <span class=\"hljs-built_in\">rgb<\/span>(<span class=\"hljs-number\">85<\/span>, <span class=\"hljs-number\">82<\/span>, <span class=\"hljs-number\">82<\/span>);\n}\n<span class=\"hljs-selector-class\">.main<\/span> <span class=\"hljs-selector-tag\">form<\/span> <span class=\"hljs-selector-tag\">button<\/span><span class=\"hljs-selector-pseudo\">:disabled<\/span> {\n  <span class=\"hljs-attribute\">background-color<\/span>: <span class=\"hljs-number\">#ccc<\/span>;\n  <span class=\"hljs-attribute\">color<\/span>: <span class=\"hljs-number\">#666<\/span>;\n  <span class=\"hljs-attribute\">cursor<\/span>: not-allowed;\n  <span class=\"hljs-attribute\">opacity<\/span>: <span class=\"hljs-number\">0.6<\/span>;\n}\n<span class=\"hljs-selector-class\">.video-transcription-section<\/span> {\n  <span class=\"hljs-attribute\">display<\/span>: flex;\n  <span class=\"hljs-attribute\">flex-direction<\/span>: row;\n  <span class=\"hljs-attribute\">justify-content<\/span>: space-evenly;\n  <span class=\"hljs-attribute\">align-items<\/span>: center;\n  <span class=\"hljs-attribute\">padding<\/span>: <span class=\"hljs-number\">3rem<\/span>;\n  <span class=\"hljs-attribute\">width<\/span>: <span class=\"hljs-number\">100vw<\/span>;\n  <span class=\"hljs-attribute\">height<\/span>: <span class=\"hljs-number\">350px<\/span>;\n}\n<span class=\"hljs-selector-class\">.video-transcription-section<\/span> <span class=\"hljs-selector-tag\">video<\/span> {\n  <span class=\"hljs-attribute\">height<\/span>: inherit;\n  <span class=\"hljs-attribute\">border-radius<\/span>: <span class=\"hljs-number\">5px<\/span>;\n  <span class=\"hljs-attribute\">border<\/span>: <span class=\"hljs-number\">#ccc<\/span> solid <span class=\"hljs-number\">1px<\/span>;\n}\n<span class=\"hljs-selector-class\">.transcription<\/span> {\n  <span class=\"hljs-attribute\">border-radius<\/span>: <span class=\"hljs-number\">5px<\/span>;\n  <span class=\"hljs-attribute\">border<\/span>: <span class=\"hljs-number\">#ccc<\/span> solid <span class=\"hljs-number\">1px<\/span>;\n  <span class=\"hljs-attribute\">height<\/span>: inherit;\n  <span class=\"hljs-attribute\">width<\/span>: <span class=\"hljs-number\">500px<\/span>;\n  <span class=\"hljs-attribute\">padding<\/span>: <span class=\"hljs-number\">5px<\/span>;\n  <span class=\"hljs-attribute\">color<\/span>: lightgreen;\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-2\"><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>Next, in the <code>page.tsx<\/code> file, replace the boilerplate code with the code below. This code snippet is the app\u2019s foundation which stores required data and eventually renders a transcript:<\/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\">\n<span class=\"hljs-string\">'use client'<\/span>;\n<span class=\"hljs-keyword\">import<\/span> { useState } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'react'<\/span>;\n<span class=\"hljs-keyword\">import<\/span> styles <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'.\/page.module.css'<\/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  <span class=\"hljs-keyword\">const<\/span> &#91;videoUrl, setVideoUrl] = useState&lt;string&gt;(<span class=\"hljs-string\">''<\/span>);\n  <span class=\"hljs-keyword\">const<\/span> &#91;transcript, setTranscript] = useState&lt;string&gt;(<span class=\"hljs-string\">''<\/span>);\n  <span class=\"hljs-keyword\">const<\/span> &#91;isUploading, setIsUploading] = useState&lt;boolean&gt;(<span class=\"hljs-literal\">false<\/span>);\n\n  <span class=\"hljs-keyword\">const<\/span> uploadVideo = <span class=\"hljs-keyword\">async<\/span> (event: React.FormEvent&lt;HTMLFormElement&gt;) =&gt; {}\n\n  <span class=\"hljs-keyword\">return<\/span> (\n    <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">main<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">{styles.main}<\/span>&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">form<\/span> <span class=\"hljs-attr\">onSubmit<\/span>=<span class=\"hljs-string\">{uploadVideo}<\/span>&gt;<\/span>\n        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">label<\/span> <span class=\"hljs-attr\">htmlFor<\/span>=<span class=\"hljs-string\">'video_file'<\/span>&gt;<\/span>Video file:<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">label<\/span>&gt;<\/span>\n        <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_file'<\/span> <span class=\"hljs-attr\">accept<\/span>=<span class=\"hljs-string\">'video\/*'<\/span> <span class=\"hljs-attr\">required<\/span> \/&gt;<\/span>\n        <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\">{isUploading}<\/span>&gt;<\/span>\n          {isUploading ? 'Uploading video...' : 'Upload'}\n        <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">button<\/span>&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">form<\/span>&gt;<\/span>\n      {videoUrl &amp;&amp; (\n        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">{styles&#91;<\/span>'<span class=\"hljs-attr\">video-transcription-section<\/span>']}&gt;<\/span>\n          <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">video<\/span> <span class=\"hljs-attr\">crossOrigin<\/span>=<span class=\"hljs-string\">'anonymous'<\/span> <span class=\"hljs-attr\">controls<\/span> <span class=\"hljs-attr\">muted<\/span>&gt;<\/span>\n            <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">source<\/span> <span class=\"hljs-attr\">src<\/span>=<span class=\"hljs-string\">{videoUrl}<\/span> <span class=\"hljs-attr\">type<\/span>=<span class=\"hljs-string\">'video\/mp4'<\/span> \/&gt;<\/span>\n          <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">video<\/span>&gt;<\/span>\n          <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">{styles.transcription}<\/span>&gt;<\/span>\n            <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">p<\/span>&gt;<\/span>\n              {transcript ? transcript : 'Transcription is being processed...'}\n            <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">p<\/span>&gt;<\/span>\n          <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span>\n        <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span>\n      )}\n    <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">main<\/span>&gt;<\/span><\/span>\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, we initialized the state variables we&#8217;d need:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><code>videoUrl<\/code>. This variable keeps track of the uploaded video&#8217;s URL.<\/li>\n\n\n\n<li><code>transcript<\/code>. This variable stores the transcribed text associated with the uploaded video.<\/li>\n\n\n\n<li><code>isUploading<\/code>. This boolean flag indicates whether a video upload is in progress.<\/li>\n<\/ul>\n\n\n\n<p>There is also an empty uploadVideo function that will contain our upload logic. We conditionally show the video and transcript based on their respective state variables.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Set Up Environment Variables<\/h2>\n\n\n\n<p>In the project root directory, add a new file, <code>.env.local<\/code>, and paste the code below in that file. This file would house all the app keys we need to use Cloudinary in our project.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-4\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml shcb-wrap-lines\">\nCLOUDINARY_CLOUD_NAME=<span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">CLOUDINARY_CLOUD_NAME<\/span>&gt;<\/span>\nCLOUDINARY_API_KEY=<span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">CLOUDINARY_API_KEY<\/span>&gt;<\/span>\nCLOUDINARY_API_SECRET=<span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">CLOUDINARY_API_SECRET<\/span>&gt;<\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-4\"><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>To get these details, navigate to the <a href=\"https:\/\/console.cloudinary.com\/pm\/c-44312a85dae383cdb3fedab809bc64\/developer-dashboard\" target=\"_blank\" rel=\"noreferrer noopener\">Cloudinary developer dashboard<\/a>.You should see all the details under the <strong>Product Environment Credentials<\/strong>.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img width=\"1024\" height=\"207\" data-public-id=\"Web_Assets\/blog\/blog-Add-a-Transcription-to-Your-Video-in-Nextjs-2\/blog-Add-a-Transcription-to-Your-Video-in-Nextjs-2.png\" decoding=\"async\" src=\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/w_1024,h_207,c_scale\/f_auto,q_auto\/v1721320177\/Web_Assets\/blog\/blog-Add-a-Transcription-to-Your-Video-in-Nextjs-2\/blog-Add-a-Transcription-to-Your-Video-in-Nextjs-2.png?_i=AA\" alt=\"\" class=\"wp-post-34777 wp-image-34779\" loading=\"lazy\" data-format=\"png\" data-transformations=\"f_auto,q_auto\" data-version=\"1721320177\" data-seo=\"1\" srcset=\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1721320177\/Web_Assets\/blog\/blog-Add-a-Transcription-to-Your-Video-in-Nextjs-2\/blog-Add-a-Transcription-to-Your-Video-in-Nextjs-2.png?_i=AA 1600w, https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1721320177\/Web_Assets\/blog\/blog-Add-a-Transcription-to-Your-Video-in-Nextjs-2\/blog-Add-a-Transcription-to-Your-Video-in-Nextjs-2.png?_i=AA 300w, https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1721320177\/Web_Assets\/blog\/blog-Add-a-Transcription-to-Your-Video-in-Nextjs-2\/blog-Add-a-Transcription-to-Your-Video-in-Nextjs-2.png?_i=AA 768w, https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1721320177\/Web_Assets\/blog\/blog-Add-a-Transcription-to-Your-Video-in-Nextjs-2\/blog-Add-a-Transcription-to-Your-Video-in-Nextjs-2.png?_i=AA 1024w, https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1721320177\/Web_Assets\/blog\/blog-Add-a-Transcription-to-Your-Video-in-Nextjs-2\/blog-Add-a-Transcription-to-Your-Video-in-Nextjs-2.png?_i=AA 1536w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">Set Up Cloudinary for Video Transcription<\/h2>\n\n\n\n<p>We must provision our Cloudinary account with the <strong>Google AI Transcription Add-on <\/strong>to use the video transcription feature. Navigate to the <a href=\"https:\/\/console.cloudinary.com\/settings\/c-44312a85dae383cdb3fedab809bc64\/addons\" target=\"_blank\" rel=\"noreferrer noopener\">Add-ons page<\/a> on the Cloudinary console. We need to be logged in to our Cloudinary account first to view the page:<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img width=\"1024\" height=\"579\" data-public-id=\"Web_Assets\/blog\/blog-Add-a-Transcription-to-Your-Video-in-Nextjs-3\/blog-Add-a-Transcription-to-Your-Video-in-Nextjs-3.png\" decoding=\"async\" src=\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/w_1024,h_579,c_scale\/f_auto,q_auto\/v1721320172\/Web_Assets\/blog\/blog-Add-a-Transcription-to-Your-Video-in-Nextjs-3\/blog-Add-a-Transcription-to-Your-Video-in-Nextjs-3.png?_i=AA\" alt=\"\" class=\"wp-post-34777 wp-image-34780\" loading=\"lazy\" data-format=\"png\" data-transformations=\"f_auto,q_auto\" data-version=\"1721320172\" data-seo=\"1\" srcset=\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1721320172\/Web_Assets\/blog\/blog-Add-a-Transcription-to-Your-Video-in-Nextjs-3\/blog-Add-a-Transcription-to-Your-Video-in-Nextjs-3.png?_i=AA 1600w, https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1721320172\/Web_Assets\/blog\/blog-Add-a-Transcription-to-Your-Video-in-Nextjs-3\/blog-Add-a-Transcription-to-Your-Video-in-Nextjs-3.png?_i=AA 300w, https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1721320172\/Web_Assets\/blog\/blog-Add-a-Transcription-to-Your-Video-in-Nextjs-3\/blog-Add-a-Transcription-to-Your-Video-in-Nextjs-3.png?_i=AA 768w, https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1721320172\/Web_Assets\/blog\/blog-Add-a-Transcription-to-Your-Video-in-Nextjs-3\/blog-Add-a-Transcription-to-Your-Video-in-Nextjs-3.png?_i=AA 1024w, https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1721320172\/Web_Assets\/blog\/blog-Add-a-Transcription-to-Your-Video-in-Nextjs-3\/blog-Add-a-Transcription-to-Your-Video-in-Nextjs-3.png?_i=AA 1536w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p>Click the <strong>Google AI Video Transcription<\/strong> card to see this page that lists the available plans.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img width=\"1024\" height=\"576\" data-public-id=\"Web_Assets\/blog\/blog-Add-a-Transcription-to-Your-Video-in-Nextjs-4\/blog-Add-a-Transcription-to-Your-Video-in-Nextjs-4.png\" decoding=\"async\" src=\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/w_1024,h_576,c_scale\/f_auto,q_auto\/v1721320166\/Web_Assets\/blog\/blog-Add-a-Transcription-to-Your-Video-in-Nextjs-4\/blog-Add-a-Transcription-to-Your-Video-in-Nextjs-4.png?_i=AA\" alt=\"\" class=\"wp-post-34777 wp-image-34781\" loading=\"lazy\" data-format=\"png\" data-transformations=\"f_auto,q_auto\" data-version=\"1721320166\" data-seo=\"1\" srcset=\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1721320166\/Web_Assets\/blog\/blog-Add-a-Transcription-to-Your-Video-in-Nextjs-4\/blog-Add-a-Transcription-to-Your-Video-in-Nextjs-4.png?_i=AA 1600w, https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1721320166\/Web_Assets\/blog\/blog-Add-a-Transcription-to-Your-Video-in-Nextjs-4\/blog-Add-a-Transcription-to-Your-Video-in-Nextjs-4.png?_i=AA 300w, https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1721320166\/Web_Assets\/blog\/blog-Add-a-Transcription-to-Your-Video-in-Nextjs-4\/blog-Add-a-Transcription-to-Your-Video-in-Nextjs-4.png?_i=AA 768w, https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1721320166\/Web_Assets\/blog\/blog-Add-a-Transcription-to-Your-Video-in-Nextjs-4\/blog-Add-a-Transcription-to-Your-Video-in-Nextjs-4.png?_i=AA 1024w, https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1721320166\/Web_Assets\/blog\/blog-Add-a-Transcription-to-Your-Video-in-Nextjs-4\/blog-Add-a-Transcription-to-Your-Video-in-Nextjs-4.png?_i=AA 1536w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p>We can subscribe to any of the available plans. The plan we subscribed to will show up on our dashboard, which signifies that our account now has the <a href=\"https:\/\/cloudinary.com\/products\/video\">Cloudinary video<\/a> transcription feature. For this article, we subscribed to the free plan.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img width=\"1024\" height=\"577\" data-public-id=\"Web_Assets\/blog\/blog-Add-a-Transcription-to-Your-Video-in-Nextjs-5\/blog-Add-a-Transcription-to-Your-Video-in-Nextjs-5.png\" decoding=\"async\" src=\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/w_1024,h_577,c_scale\/f_auto,q_auto\/v1721320162\/Web_Assets\/blog\/blog-Add-a-Transcription-to-Your-Video-in-Nextjs-5\/blog-Add-a-Transcription-to-Your-Video-in-Nextjs-5.png?_i=AA\" alt=\"\" class=\"wp-post-34777 wp-image-34782\" loading=\"lazy\" data-format=\"png\" data-transformations=\"f_auto,q_auto\" data-version=\"1721320162\" data-seo=\"1\" srcset=\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1721320162\/Web_Assets\/blog\/blog-Add-a-Transcription-to-Your-Video-in-Nextjs-5\/blog-Add-a-Transcription-to-Your-Video-in-Nextjs-5.png?_i=AA 1600w, https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1721320162\/Web_Assets\/blog\/blog-Add-a-Transcription-to-Your-Video-in-Nextjs-5\/blog-Add-a-Transcription-to-Your-Video-in-Nextjs-5.png?_i=AA 300w, https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1721320162\/Web_Assets\/blog\/blog-Add-a-Transcription-to-Your-Video-in-Nextjs-5\/blog-Add-a-Transcription-to-Your-Video-in-Nextjs-5.png?_i=AA 768w, https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1721320162\/Web_Assets\/blog\/blog-Add-a-Transcription-to-Your-Video-in-Nextjs-5\/blog-Add-a-Transcription-to-Your-Video-in-Nextjs-5.png?_i=AA 1024w, https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1721320162\/Web_Assets\/blog\/blog-Add-a-Transcription-to-Your-Video-in-Nextjs-5\/blog-Add-a-Transcription-to-Your-Video-in-Nextjs-5.png?_i=AA 1536w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">Set Up Application Utils<\/h2>\n\n\n\n<p>Now that we&#8217;re all set up, let&#8217;s start building the application\u2019s functionality. Let&#8217;s establish a few utilities we&#8217;ll need later on in the app.&nbsp;<\/p>\n\n\n\n<p>First is the Cloudinary library. To do this:<\/p>\n\n\n\n<ol start=\"1\" class=\"wp-block-list\">\n<li>Add a new folder called <strong>lib<\/strong> in the <strong>src<\/strong> folder.&nbsp;<\/li>\n\n\n\n<li>In that folder, add a file and call it <strong>cloudinary.ts<\/strong>.&nbsp;<\/li>\n\n\n\n<li>In the <strong>cloudinary.ts<\/strong> file, paste the code below:<\/li>\n<\/ol>\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\">\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  <span class=\"hljs-attr\">cloud_name<\/span>: process.env.CLOUDINARY_CLOUD_NAME,\n  <span class=\"hljs-attr\">api_key<\/span>: process.env.CLOUDINARY_API_KEY,\n  <span class=\"hljs-attr\">api_secret<\/span>: process.env.CLOUDINARY_API_SECRET,\n});\n\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">default<\/span> cloudinary;<\/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, we import the Cloudinary library. The <strong>v2<\/strong> version is being used and aliased as <strong>cloudinary<\/strong>. The <strong>cloudinary.config()<\/strong> method is called to configure the Cloudinary client with the account details we retrieved from our dashboard in the last section.<\/p>\n\n\n\n<p>We then export the configured <strong>Cloudinary<\/strong> object as this module&#8217;s default export. This setup allows other parts of our application to import and use the configured Cloudinary client without configuring it again.<\/p>\n\n\n\n<p>Next is the transcript parser. This utility function takes in the transcript data and parses it into a paragraph of text that we can display.<\/p>\n\n\n\n<ol start=\"1\" class=\"wp-block-list\">\n<li>In the lib folder, add a file and call it <strong>transcript.ts<\/strong>.&nbsp;<\/li>\n\n\n\n<li>In the <strong>transcript.ts<\/strong> file, paste the code below:<\/li>\n<\/ol>\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\">\n<span class=\"hljs-keyword\">import<\/span> { TranscriptData } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'@\/types\/transcript-data.type'<\/span>;\n\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">const<\/span> parseTranscriptData = <span class=\"hljs-keyword\">async<\/span> (\n  data: TranscriptData&#91;]\n): <span class=\"hljs-built_in\">Promise<\/span>&lt;string&gt; =&gt; {\n  <span class=\"hljs-keyword\">let<\/span> transcript: string = <span class=\"hljs-string\">''<\/span>;\n  data.forEach(\n    <span class=\"hljs-function\">(<span class=\"hljs-params\">line: TranscriptData<\/span>) =&gt;<\/span> (transcript = transcript + <span class=\"hljs-string\">` <span class=\"hljs-subst\">${line.transcript}<\/span>`<\/span>)\n  );\n  <span class=\"hljs-keyword\">return<\/span> transcript;\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>The <code>TranscriptData<\/code> interface defines the <a href=\"https:\/\/cloudinary.com\/documentation\/google_ai_video_transcription_addon#cloudinary_transcript_files\" target=\"_blank\" rel=\"noreferrer noopener\">structure<\/a> of the transcription file we get from Cloudinary. Our app doesn&#8217;t currently have the <code>TranscriptData<\/code> interface defined. To do that, we need to create a new folder in the <code>src<\/code> directory and name it types. In the types folder, add a new file named <code>transcript-data.type.ts<\/code>.<\/p>\n\n\n\n<p>Add the code below in the <code>transcript-data.type.ts<\/code> file:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-7\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css shcb-wrap-lines\">\n<span class=\"hljs-selector-tag\">export<\/span> <span class=\"hljs-selector-tag\">interface<\/span> <span class=\"hljs-selector-tag\">Word<\/span> {\n  <span class=\"hljs-attribute\">word<\/span>: string;\n  <span class=\"hljs-attribute\">start_time<\/span>: number;\n  <span class=\"hljs-attribute\">end_time<\/span>: number;\n}\n\n<span class=\"hljs-selector-tag\">export<\/span> <span class=\"hljs-selector-tag\">interface<\/span> <span class=\"hljs-selector-tag\">TranscriptData<\/span> {\n  <span class=\"hljs-attribute\">transcript<\/span>: string;\n  <span class=\"hljs-attribute\">confidence<\/span>: number;\n  <span class=\"hljs-attribute\">words<\/span>: Word&#91;];\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-7\"><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<h2 class=\"wp-block-heading\">Upload a Video to Cloudinary<\/h2>\n\n\n\n<p>In the uploadVideo function in the <code>page.tsx<\/code> file, we want to set up the logic to upload our video to Cloudinary.<\/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\">\n<span class=\"hljs-keyword\">const<\/span> uploadVideo = <span class=\"hljs-keyword\">async<\/span> (event: React.FormEvent&lt;HTMLFormElement&gt;) =&gt; {\n    event.preventDefault();\n    setIsUploading(<span class=\"hljs-literal\">true<\/span>);\n\n    <span class=\"hljs-keyword\">const<\/span> formData = <span class=\"hljs-keyword\">new<\/span> FormData(event.currentTarget);\n\n    <span class=\"hljs-keyword\">try<\/span> {\n      <span class=\"hljs-keyword\">const<\/span> response = <span class=\"hljs-keyword\">await<\/span> fetch(<span class=\"hljs-string\">'\/api\/upload'<\/span>, {\n        <span class=\"hljs-attr\">method<\/span>: <span class=\"hljs-string\">'POST'<\/span>,\n        <span class=\"hljs-attr\">body<\/span>: formData,\n      });\n\n      <span class=\"hljs-keyword\">if<\/span> (!response.ok) {\n        <span class=\"hljs-keyword\">throw<\/span> <span class=\"hljs-keyword\">new<\/span> <span class=\"hljs-built_in\">Error<\/span>(<span class=\"hljs-string\">'Failed to upload video'<\/span>);\n      }\n\n      <span class=\"hljs-keyword\">const<\/span> data = <span class=\"hljs-keyword\">await<\/span> response.json();\n\n      setVideoUrl(data.videoUrl);\n      setTranscript(<span class=\"hljs-string\">''<\/span>);\n    } <span class=\"hljs-keyword\">catch<\/span> (error: any) {\n      <span class=\"hljs-built_in\">console<\/span>.error(error);\n    } <span class=\"hljs-keyword\">finally<\/span> {\n      setIsUploading(<span class=\"hljs-literal\">false<\/span>);\n    }\n  };<\/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>In this function, we get the data from our form and make a post request to the upload API endpoint, a serverless function, which we will create in a second. Then, we handle the response by throwing an error if the upload failed and setting the <code>videoUrl<\/code> and <code>transcript<\/code> state variables otherwise.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Create the Upload API Endpoint<\/h2>\n\n\n\n<p>To set up the upload endpoint, follow these steps:<\/p>\n\n\n\n<ol start=\"1\" class=\"wp-block-list\">\n<li>Create a new folder in the app called <code>api<\/code>.<\/li>\n\n\n\n<li>In the <code>api<\/code> folder, create a new folder called <code>upload<\/code>.<\/li>\n\n\n\n<li>In the <code>upload<\/code> folder, add a file called <code>route.ts<\/code>.<\/li>\n\n\n\n<li>In the <code>route.ts<\/code>, paste the code below:<\/li>\n<\/ol>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-9\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\">\n<span class=\"hljs-keyword\">import<\/span> { UploadApiResponse } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'cloudinary'<\/span>;\n<span class=\"hljs-keyword\">import<\/span> cloudinary <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'@\/lib\/cloudinary'<\/span>;\n<span class=\"hljs-keyword\">import<\/span> { NextResponse } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'next\/server'<\/span>;\n\n<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\">POST<\/span>(<span class=\"hljs-params\">req: Request, res: NextResponse<\/span>) <\/span>{\n  <span class=\"hljs-keyword\">try<\/span> {\n    <span class=\"hljs-keyword\">const<\/span> formData = <span class=\"hljs-keyword\">await<\/span> req.formData();\n    <span class=\"hljs-keyword\">const<\/span> file = formData.get(<span class=\"hljs-string\">'video_file'<\/span>) <span class=\"hljs-keyword\">as<\/span> File;\n    <span class=\"hljs-keyword\">const<\/span> buffer: Buffer = Buffer.from(<span class=\"hljs-keyword\">await<\/span> file.arrayBuffer());\n    <span class=\"hljs-keyword\">const<\/span> cloud_name: string | <span class=\"hljs-literal\">undefined<\/span> = process.env.CLOUDINARY_CLOUD_NAME;\n\n    <span class=\"hljs-keyword\">const<\/span> base64Image: string = <span class=\"hljs-string\">`data:<span class=\"hljs-subst\">${file.type}<\/span>;base64,<span class=\"hljs-subst\">${buffer.toString(\n      <span class=\"hljs-string\">'base64'<\/span>\n    )}<\/span>`<\/span>;\n\n    <span class=\"hljs-keyword\">const<\/span> uploadResult: UploadApiResponse = <span class=\"hljs-keyword\">await<\/span> cloudinary.uploader.upload(\n      base64Image,\n      {\n        <span class=\"hljs-attr\">resource_type<\/span>: <span class=\"hljs-string\">'video'<\/span>,\n        <span class=\"hljs-attr\">public_id<\/span>: <span class=\"hljs-string\">`videos\/<span class=\"hljs-subst\">${<span class=\"hljs-built_in\">Date<\/span>.now()}<\/span>`<\/span>,\n        <span class=\"hljs-attr\">raw_convert<\/span>: <span class=\"hljs-string\">'google_speech'<\/span>,\n      }\n    );\n\n    <span class=\"hljs-keyword\">const<\/span> videoUrl = uploadResult.secure_url;\n\n    <span class=\"hljs-keyword\">const<\/span> transcriptionFileUrl = <span class=\"hljs-string\">`https:\/\/res.cloudinary.com\/<span class=\"hljs-subst\">${cloud_name}<\/span>\/raw\/upload\/v<span class=\"hljs-subst\">${uploadResult.version + <span class=\"hljs-number\">1<\/span>}<\/span>\/<span class=\"hljs-subst\">${uploadResult.public_id}<\/span>.transcript`<\/span>;\n\n    <span class=\"hljs-keyword\">return<\/span> NextResponse.json(\n      { videoUrl, transcriptionFileUrl },\n      { <span class=\"hljs-attr\">status<\/span>: <span class=\"hljs-number\">200<\/span> }\n    );\n  } <span class=\"hljs-keyword\">catch<\/span> (error: any) {\n    <span class=\"hljs-keyword\">throw<\/span> <span class=\"hljs-keyword\">new<\/span> <span class=\"hljs-built_in\">Error<\/span>(error);\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\">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 file, we retrieve the video file from the form data, cast it as a <strong>File<\/strong> object, and then convert the video file into a binary <strong>Buffer<\/strong> by reading the file&#8217;s content as an <strong>ArrayBuffer<\/strong>. Subsequently, we convert this Buffer into a <strong>Buffer<\/strong> object using <strong>Buffer.from()<\/strong>. This conversion is necessary to efficiently handle the video\u2019s binary data on the server.&nbsp;<\/p>\n\n\n\n<p>Next, we convert the video file from a <strong>Buffer<\/strong> object to a <strong>Base64-encoded <\/strong>string using the file&#8217;s MIME type and binary data. The Buffer object is transformed into a Base64 string with <code>Buffer.toString('base64')<\/code>, then prefixed with the data URI scheme to create a valid Base64 image string for Cloudinary upload.<\/p>\n\n\n\n<p>Next, we upload the Base64-encoded video to Cloudinary using the upload method we set up from the Cloudinary client (<code>cloudinary.uploader.upload<\/code>). This method takes two main parameters: the Base64 string of the video and an options object. Within the options object, the <strong>resource_type<\/strong> is set to <code>video<\/code> to indicate that the file being uploaded is a video.&nbsp;<\/p>\n\n\n\n<p>The <code>raw_convert<\/code> option tells Cloudinary to generate a transcript file(.transcript) for the uploaded video using Google Speech-to-Text. Depending on the length of the video, the transcript file may take several seconds or minutes to generate and to handle this, we would be implementing system design method called polling later on in this article.<\/p>\n\n\n\n<p>Finally, we construct the transcription file URL by combining the <code>cloud_name<\/code>, <code>version<\/code>, and <code>public_id<\/code>. This URL points to the transcription file hosted on Cloudinary. Notice we add one to the version number in the transcript file <code>construct(uploadResult.version + 1)<\/code>. This is because the transcript file takes some time to process after the video has been uploaded, and then Cloudinary increases the file version number by one which indicates that the file has finished processing. This is the URL we&#8217;ll use when polling for the transcript file.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Fetch the Transcription File<\/h2>\n\n\n\n<p>Currently, we can upload our video to Cloudinary and set it to show up in our application. Still, we can&#8217;t view the transcript of the uploaded video. This is because, after our video uploads, the <code>google_speech<\/code> parameter value asynchronously activates a call to Google&#8217;s Cloud Speech API, which has an initial <strong>pending<\/strong> state. The time it takes for the transcript file to finish generating depends on the length of the uploaded video.<\/p>\n\n\n\n<p>To handle this, we will use the polling method to periodically check Cloudinary for the status of the transcript file generation. <a target=\"_blank\" href=\"https:\/\/www.geeksforgeeks.org\/polling-in-system-design\/\" rel=\"noreferrer noopener\">Polling<\/a><a target=\"_blank\" href=\"https:\/\/www.geeksforgeeks.org\/polling-in-system-design\/\" rel=\"noreferrer noopener\"> <\/a>is a method used in system design to continuously check the status or retrieve data from a source at predefined intervals.<\/p>\n\n\n\n<p>We need to add a new function to our <code>page.tsx<\/code> file called <code>checkTranscriptionStatus<\/code>. We&#8217;ll add it just after the state variables:<\/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\">\n...\n\nconst POLLING_INTERVAL = <span class=\"hljs-number\">5000<\/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  const checkTranscriptionStatus = <span class=\"hljs-keyword\">async<\/span> (url: string) =&gt; {\n    <span class=\"hljs-keyword\">try<\/span> {\n      <span class=\"hljs-keyword\">const<\/span> response = <span class=\"hljs-keyword\">await<\/span> fetch(\n        <span class=\"hljs-string\">`\/api\/transcript?url=<span class=\"hljs-subst\">${<span class=\"hljs-built_in\">encodeURIComponent<\/span>(url)}<\/span>`<\/span>\n      );\n\n      <span class=\"hljs-keyword\">const<\/span> data = <span class=\"hljs-keyword\">await<\/span> response.json();\n\n      <span class=\"hljs-keyword\">if<\/span> (data.available) {\n        setTranscript(data.transcript);\n      } <span class=\"hljs-keyword\">else<\/span> {\n        setTimeout(<span class=\"hljs-function\"><span class=\"hljs-params\">()<\/span> =&gt;<\/span> checkTranscriptionStatus(url), POLLING_INTERVAL);\n      }\n    } <span class=\"hljs-keyword\">catch<\/span> (error: any) {\n      <span class=\"hljs-built_in\">console<\/span>.error(<span class=\"hljs-string\">'Error checking transcription status:'<\/span>, error);\n    }\n  };\n  ...\n  return (\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>At the top, we&#8217;ll define the polling interval to 5000 milliseconds. In the <code>checkTranscriptionStatus<\/code> function, we&#8217;ll make a GET request to the transcript API endpoint \u2014 we&#8217;ll create the endpoint in a moment \u2014 and send the transcript URL as a query param.<\/p>\n\n\n\n<p>We&#8217;ll then check if the transcript is available using the available param in our endpoint&#8217;s response data and set the transcript data to the state. If it isn&#8217;t yet available, we&#8217;ll define a <code>setTimeout<\/code> to call the API again in five seconds (5000 milliseconds). This will keep checking Cloudinary to retrieve the transcript data.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Create the Transcript API Endpoint<\/h2>\n\n\n\n<p>To set up the upload endpoint, we&#8217;ll follow these steps:<\/p>\n\n\n\n<ol start=\"1\" class=\"wp-block-list\">\n<li>In the <code>api<\/code> folder, create a new folder called <code>transcript<\/code>.<\/li>\n\n\n\n<li>In the transcript folder, add a file called <code>route.ts<\/code>.<\/li>\n\n\n\n<li>In the <code>route.ts file<\/code>, paste the code below.<\/li>\n<\/ol>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-11\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\">\n<span class=\"hljs-keyword\">import<\/span> { parseTranscriptData } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'@\/lib\/transcript'<\/span>;\n<span class=\"hljs-keyword\">import<\/span> { TranscriptData } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'@\/types\/transcript-data.type'<\/span>;\n<span class=\"hljs-keyword\">import<\/span> { NextResponse, type NextRequest } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'next\/server'<\/span>;\n\n<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\">GET<\/span>(<span class=\"hljs-params\">req: NextRequest<\/span>) <\/span>{\n  <span class=\"hljs-keyword\">const<\/span> searchParams = req.nextUrl.searchParams;\n  <span class=\"hljs-keyword\">const<\/span> url: string | <span class=\"hljs-literal\">null<\/span> = searchParams.get(<span class=\"hljs-string\">'url'<\/span>);\n\n  <span class=\"hljs-keyword\">try<\/span> {\n    <span class=\"hljs-keyword\">const<\/span> response = <span class=\"hljs-keyword\">await<\/span> fetch(url <span class=\"hljs-keyword\">as<\/span> string);\n\n    <span class=\"hljs-keyword\">if<\/span> (response.ok) {\n      <span class=\"hljs-keyword\">const<\/span> transcriptData: TranscriptData&#91;] = <span class=\"hljs-keyword\">await<\/span> response.json();\n      <span class=\"hljs-keyword\">const<\/span> transcript: string = <span class=\"hljs-keyword\">await<\/span> parseTranscriptData(transcriptData);\n\n      <span class=\"hljs-keyword\">return<\/span> NextResponse.json(\n        { <span class=\"hljs-attr\">available<\/span>: <span class=\"hljs-literal\">true<\/span>, transcript },\n        { <span class=\"hljs-attr\">status<\/span>: <span class=\"hljs-number\">200<\/span> }\n      );\n    } <span class=\"hljs-keyword\">else<\/span> {\n      <span class=\"hljs-keyword\">return<\/span> NextResponse.json({ <span class=\"hljs-attr\">available<\/span>: <span class=\"hljs-literal\">false<\/span> }, { <span class=\"hljs-attr\">status<\/span>: <span class=\"hljs-number\">200<\/span> });\n    }\n  } <span class=\"hljs-keyword\">catch<\/span> (error: any) {\n    <span class=\"hljs-keyword\">throw<\/span> <span class=\"hljs-keyword\">new<\/span> <span class=\"hljs-built_in\">Error<\/span>(error);\n  }\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-11\"><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, we import the <code>parseTranscriptDat<\/code>a method and the <code>TranscriptData<\/code> interface we created in the utils section earlier, and then in the function, we extract the URL parameter from the query string of the incoming request, and then we attempt to fetch data from this URL. The response from the fetch is checked for a successful status (<strong>response.ok<\/strong>). If successful, we parse the JSON response into the <strong>transcriptData<\/strong> array and then process this data into a string format using the <strong>parseTranscriptData<\/strong> function. The resulting <strong>transcript<\/strong> is returned with the <strong>available<\/strong> status set to <strong>true<\/strong>.<\/p>\n\n\n\n<p>If the fetch operation does not return a successful response, the function returns a JSON response with <strong>available<\/strong> set to <strong>false<\/strong>. Our app will keep polling the <code>\/api\/transcript<\/code> endpoint until the transcript file has been generated.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">The Final Result<\/h2>\n\n\n\n<p>When we run the app, the video and transcript section won\u2019t show up initially until after the video has successfully uploaded. The transcript section would show a message saying <strong>\u201cTranscription is being processed&#8230;\u201d, <\/strong>this means our app is still trying to fetch the transcript from Cloudinary and once the transcript is successfully generated, it will replace the default message.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img width=\"1024\" height=\"579\" data-public-id=\"Web_Assets\/blog\/blog-Add-a-Transcription-to-Your-Video-in-Nextjs-6\/blog-Add-a-Transcription-to-Your-Video-in-Nextjs-6.png\" decoding=\"async\" src=\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/w_1024,h_579,c_scale\/f_auto,q_auto\/v1721320159\/Web_Assets\/blog\/blog-Add-a-Transcription-to-Your-Video-in-Nextjs-6\/blog-Add-a-Transcription-to-Your-Video-in-Nextjs-6.png?_i=AA\" alt=\"\" class=\"wp-post-34777 wp-image-34783\" loading=\"lazy\" data-format=\"png\" data-transformations=\"f_auto,q_auto\" data-version=\"1721320159\" data-seo=\"1\" srcset=\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1721320159\/Web_Assets\/blog\/blog-Add-a-Transcription-to-Your-Video-in-Nextjs-6\/blog-Add-a-Transcription-to-Your-Video-in-Nextjs-6.png?_i=AA 1600w, https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1721320159\/Web_Assets\/blog\/blog-Add-a-Transcription-to-Your-Video-in-Nextjs-6\/blog-Add-a-Transcription-to-Your-Video-in-Nextjs-6.png?_i=AA 300w, https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1721320159\/Web_Assets\/blog\/blog-Add-a-Transcription-to-Your-Video-in-Nextjs-6\/blog-Add-a-Transcription-to-Your-Video-in-Nextjs-6.png?_i=AA 768w, https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1721320159\/Web_Assets\/blog\/blog-Add-a-Transcription-to-Your-Video-in-Nextjs-6\/blog-Add-a-Transcription-to-Your-Video-in-Nextjs-6.png?_i=AA 1024w, https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1721320159\/Web_Assets\/blog\/blog-Add-a-Transcription-to-Your-Video-in-Nextjs-6\/blog-Add-a-Transcription-to-Your-Video-in-Nextjs-6.png?_i=AA 1536w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">Wrapping Up<\/h2>\n\n\n\n<p>In this blog post, we discussed how to use the <strong>Google AI Transcription Add-on <\/strong>provided by Cloudinary to generate video transcriptions in a Next.js application.&nbsp;Try adding more features to the app like displaying the timestamp for each transcribed paragraph. To learn more about <a href=\"https:\/\/cloudinary.com\/products\/cloudinary_ai\">Cloudinary AI<\/a>, contact us today.<\/p>\n\n\n\n<p>If you found this blog post helpful and want to discuss it in more detail, join the <a target=\"_blank\" href=\"https:\/\/community.cloudinary.com\/\" rel=\"noreferrer noopener\"><u>Cloudinary Community forum<\/u><\/a> and its associated <a target=\"_blank\" href=\"https:\/\/discord.com\/invite\/cloudinary\" rel=\"noreferrer noopener\"><u>Discord<\/u><\/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\/google_ai_video_transcription_addon#generating_standard_subtitle_formats\" target=\"_blank\" rel=\"noreferrer noopener\">Cloudinary Video Transcription 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:\/\/nextjs.org\/docs\" target=\"_blank\" rel=\"noreferrer noopener\">Next.js Documentation<\/a><\/li>\n<\/ul>\n","protected":false},"excerpt":{"rendered":"<p>Accessibility is a core factor of software development, and for websites, video transcriptions improve the user experience. Video transcriptions are instrumental in many cases, such as for viewers with hearing impairments, those in noisy environments, language learners following along with lectures, and people who prefer to consume information by reading. In this blog post, we\u2019ll [&hellip;]<\/p>\n","protected":false},"author":87,"featured_media":34785,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"_cloudinary_featured_overwrite":false,"footnotes":""},"categories":[1],"tags":[336,303],"class_list":["post-34777","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-uncategorized","tag-ai","tag-video"],"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>Your Guide to Adding a Transcription to Video in Next.js<\/title>\n<meta name=\"description\" content=\"Learn how to generate video transcription into your Next.js applications using Cloudinary\u2019s AI features.\u00a0\" \/>\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\/transcription-video-in-next-js\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Add a Transcription to Your Video in Next.js\" \/>\n<meta property=\"og:description\" content=\"Learn how to generate video transcription into your Next.js applications using Cloudinary\u2019s AI features.\u00a0\" \/>\n<meta property=\"og:url\" content=\"https:\/\/cloudinary.com\/blog\/transcription-video-in-next-js\" \/>\n<meta property=\"og:site_name\" content=\"Cloudinary Blog\" \/>\n<meta property=\"article:published_time\" content=\"2024-07-18T14:00:00+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2025-04-16T21:41:31+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/v1720046145\/transcript_video_nextjs-blog\/transcript_video_nextjs-blog-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\/transcription-video-in-next-js#article\",\"isPartOf\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/transcription-video-in-next-js\"},\"author\":{\"name\":\"melindapham\",\"@id\":\"https:\/\/cloudinary.com\/blog\/#\/schema\/person\/0d5ad601e4c3b5be89245dfb14be42d9\"},\"headline\":\"Add a Transcription to Your Video in Next.js\",\"datePublished\":\"2024-07-18T14:00:00+00:00\",\"dateModified\":\"2025-04-16T21:41:31+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/transcription-video-in-next-js\"},\"wordCount\":1704,\"publisher\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/#organization\"},\"image\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/transcription-video-in-next-js#primaryimage\"},\"thumbnailUrl\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1720046145\/transcript_video_nextjs-blog\/transcript_video_nextjs-blog.jpg?_i=AA\",\"keywords\":[\"AI\",\"Video\"],\"inLanguage\":\"en-US\",\"copyrightYear\":\"2024\",\"copyrightHolder\":{\"@id\":\"https:\/\/cloudinary.com\/#organization\"}},{\"@type\":\"WebPage\",\"@id\":\"https:\/\/cloudinary.com\/blog\/transcription-video-in-next-js\",\"url\":\"https:\/\/cloudinary.com\/blog\/transcription-video-in-next-js\",\"name\":\"Your Guide to Adding a Transcription to Video in Next.js\",\"isPartOf\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/transcription-video-in-next-js#primaryimage\"},\"image\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/transcription-video-in-next-js#primaryimage\"},\"thumbnailUrl\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1720046145\/transcript_video_nextjs-blog\/transcript_video_nextjs-blog.jpg?_i=AA\",\"datePublished\":\"2024-07-18T14:00:00+00:00\",\"dateModified\":\"2025-04-16T21:41:31+00:00\",\"description\":\"Learn how to generate video transcription into your Next.js applications using Cloudinary\u2019s AI features.\u00a0\",\"breadcrumb\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/transcription-video-in-next-js#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/cloudinary.com\/blog\/transcription-video-in-next-js\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/cloudinary.com\/blog\/transcription-video-in-next-js#primaryimage\",\"url\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1720046145\/transcript_video_nextjs-blog\/transcript_video_nextjs-blog.jpg?_i=AA\",\"contentUrl\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1720046145\/transcript_video_nextjs-blog\/transcript_video_nextjs-blog.jpg?_i=AA\",\"width\":2000,\"height\":1100},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/cloudinary.com\/blog\/transcription-video-in-next-js#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/cloudinary.com\/blog\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Add a Transcription to Your Video 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":"Your Guide to Adding a Transcription to Video in Next.js","description":"Learn how to generate video transcription into your Next.js applications using Cloudinary\u2019s AI features.\u00a0","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\/transcription-video-in-next-js","og_locale":"en_US","og_type":"article","og_title":"Add a Transcription to Your Video in Next.js","og_description":"Learn how to generate video transcription into your Next.js applications using Cloudinary\u2019s AI features.\u00a0","og_url":"https:\/\/cloudinary.com\/blog\/transcription-video-in-next-js","og_site_name":"Cloudinary Blog","article_published_time":"2024-07-18T14:00:00+00:00","article_modified_time":"2025-04-16T21:41:31+00:00","og_image":[{"width":2000,"height":1100,"url":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/v1720046145\/transcript_video_nextjs-blog\/transcript_video_nextjs-blog-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\/transcription-video-in-next-js#article","isPartOf":{"@id":"https:\/\/cloudinary.com\/blog\/transcription-video-in-next-js"},"author":{"name":"melindapham","@id":"https:\/\/cloudinary.com\/blog\/#\/schema\/person\/0d5ad601e4c3b5be89245dfb14be42d9"},"headline":"Add a Transcription to Your Video in Next.js","datePublished":"2024-07-18T14:00:00+00:00","dateModified":"2025-04-16T21:41:31+00:00","mainEntityOfPage":{"@id":"https:\/\/cloudinary.com\/blog\/transcription-video-in-next-js"},"wordCount":1704,"publisher":{"@id":"https:\/\/cloudinary.com\/blog\/#organization"},"image":{"@id":"https:\/\/cloudinary.com\/blog\/transcription-video-in-next-js#primaryimage"},"thumbnailUrl":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1720046145\/transcript_video_nextjs-blog\/transcript_video_nextjs-blog.jpg?_i=AA","keywords":["AI","Video"],"inLanguage":"en-US","copyrightYear":"2024","copyrightHolder":{"@id":"https:\/\/cloudinary.com\/#organization"}},{"@type":"WebPage","@id":"https:\/\/cloudinary.com\/blog\/transcription-video-in-next-js","url":"https:\/\/cloudinary.com\/blog\/transcription-video-in-next-js","name":"Your Guide to Adding a Transcription to Video in Next.js","isPartOf":{"@id":"https:\/\/cloudinary.com\/blog\/#website"},"primaryImageOfPage":{"@id":"https:\/\/cloudinary.com\/blog\/transcription-video-in-next-js#primaryimage"},"image":{"@id":"https:\/\/cloudinary.com\/blog\/transcription-video-in-next-js#primaryimage"},"thumbnailUrl":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1720046145\/transcript_video_nextjs-blog\/transcript_video_nextjs-blog.jpg?_i=AA","datePublished":"2024-07-18T14:00:00+00:00","dateModified":"2025-04-16T21:41:31+00:00","description":"Learn how to generate video transcription into your Next.js applications using Cloudinary\u2019s AI features.\u00a0","breadcrumb":{"@id":"https:\/\/cloudinary.com\/blog\/transcription-video-in-next-js#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/cloudinary.com\/blog\/transcription-video-in-next-js"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/cloudinary.com\/blog\/transcription-video-in-next-js#primaryimage","url":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1720046145\/transcript_video_nextjs-blog\/transcript_video_nextjs-blog.jpg?_i=AA","contentUrl":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1720046145\/transcript_video_nextjs-blog\/transcript_video_nextjs-blog.jpg?_i=AA","width":2000,"height":1100},{"@type":"BreadcrumbList","@id":"https:\/\/cloudinary.com\/blog\/transcription-video-in-next-js#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/cloudinary.com\/blog\/"},{"@type":"ListItem","position":2,"name":"Add a Transcription to Your Video 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\/v1720046145\/transcript_video_nextjs-blog\/transcript_video_nextjs-blog.jpg?_i=AA","_links":{"self":[{"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/posts\/34777","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=34777"}],"version-history":[{"count":5,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/posts\/34777\/revisions"}],"predecessor-version":[{"id":37457,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/posts\/34777\/revisions\/37457"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/media\/34785"}],"wp:attachment":[{"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/media?parent=34777"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/categories?post=34777"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/tags?post=34777"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}