Video trimming is a common task nowadays as it is now the preferred media for content sharing. Short videos have taken the world by storm. Let us learn how to trim short videos from long videos programmatically as this is a requirement for interactive platforms.
The final project can be viewed on Codesandbox.
You can find the full source code on my Github repository.
Knowledge of HTML, CSS, and JavaScript is required to follow along with this article. Knowledge of Vue.Js is recommended but not required.
[Nuxt.Js](http://nuxtjs.org is an intuitive Vue.Js framework that boasts being easy to learn and master. We will use it to improve our productivity and developer experience.
We will use the create-nuxt-app utility. Ensure you have yarn installed or npm v5.2+ v6.1+.
Open your terminal in your preferred working directory:
yarn create nuxt-app nuxjs-video-trimmer
# OR
npx create-nuxt-app nuxjs-video-trimmer
# OR
npm init nuxt-app nuxjs-video-trimmer
Code language: PHP (php)
This will trigger a set of questions required to customize your installation. Here are our recommended defaults:
Project name: nuxtjs-video-trimmer
Programming language: JavaScript
Package manager: Yarn
UI Framework: TailwindCSS
Nuxt.JS modules: N/A
Linting tools: N/A
Testing framework: None
Rendering mode: Universal (SSR/SSG)
Deployment target: Server (Node.Js hosting)
Development tools: N/A
What is your Github username:
<your-github-username>
Version control system: Git
Once the setup is complete, feel free to enter and run the project:
cd nuxtjs-video-trimmer
yarn dev
# OR
npm run dev
Code language: PHP (php)
Your application will now be running on http://localhost:3000
Cloudinary is a media management platform that enables us to make the most out of our content via their comprehensive and powerful APIs and SDKs.
To install the recommended Nuxt.Js plugin, @nuxtjs/cloudinary open the terminal in your project folder. Run the following command:
yarn add @nuxtjs/cloudinary
# OR
npm install @nuxtjs/cloudinary
Code language: CSS (css)
Add it to the modules
section of nuxt.config.js
:
// nuxt.config.js
export default {
...
modules: [
'@nuxtjs/cloudinary'
]
...
}
Code language: JavaScript (javascript)
Add cloudinary
section in nuxt.config.js
to configure our instance:
// nuxt.config.js
export default {
...
cloudinary: {
cloudName: process.env.NUXT_ENV_CLOUDINARY_CLOUD_NAME,
useComponent: true
}
}
Code language: JavaScript (javascript)
In the above configuration, we refer to the NUXT_ENV_CLOUDINARY_CLOUD_NAME
environmental variable. Environmental variables are variables we configure in our development environment instead of in our codebase. This is because they are dependent on the environment our code is deployed in or even are too sensitive to be in our code repositories.
To obtain our Cloudinary cloud name, open your dashboard. If you don’t have an account you can register here. It should be visible in the Account Details
section.
We will store our environmental variables in the env
file. Let’s create one:
touch .env
Code language: CSS (css)
Add your cloud name:
<!-- .env -->
NUXT_ENV_CLOUDINARY_CLOUD_NAME=<secret-cloud-name>
Code language: HTML, XML (xml)
Before we trim the video, we will upload it to the Cloudinary platform. Let us create an upload
component.
touch components/Upload.vue
Let us create an HTML form to upload the video:
<!-- components/Upload.vue -->
<template>
<div class="mt-12">
<form
action="#"
method="POST"
@submit.prevent="submit"
>
<div>
<label for="file">File</label>
<div>
<input
accept="video/*"
@change="handleFile"
type="file"
name="file"
id="file"
/>
</div>
</div>
<div>
<p v-if="uploading" >
Uploading...
</p>
<button v-else type="submit">
Upload
</button>
</div>
</form>
</div>
</template>
Code language: HTML, XML (xml)
When a file is selected, we trigger the handleFile
method. This method saves the selected file in the component state
// components/Upload.vue
<script>
export default {
data() {
return {
...
uploadVideo: null,
};
},
methods: {
async handleFile(e) {
this.uploadVideo = e.target.files[0];
},
...
},
};
</script>
Code language: HTML, XML (xml)
This prepares the file for upload. When the file is submitted, we trigger the submit
method. This uploads the file to Cloudinary and emits the uploaded
method:
<script>
export default {
data() {
return {
uploading: false,
...
};
},
methods: {
...
async readData(f) {
return new Promise((resolve) => {
const reader = new FileReader();
reader.onloadend = () => resolve(reader.result);
reader.readAsDataURL(f);
});
},
async submit() {
this.uploading = true;
const videoData = await this.readData(this.uploadVideo);
this.cloudinaryVideo = await this.$cloudinary.upload(videoData, {
upload_preset: "default-preset",
folder: "nuxtjs-video-trimmer",
});
this.$emit("uploaded", this.cloudinaryVideo);
this.uploading = false;
},
},
};
</script>
Code language: HTML, XML (xml)
Once the event is emitted, it is received in the index file.
<!-- pages/index.vue -->
<template>
...
<upload v-else @uploaded="storeVideo" />
...
</template>
Code language: HTML, XML (xml)
The uploaded
event is handled by the storeVideo
method:
// pages/index.vue
<script>
import Upload from "../components/Upload.vue";
...
export default {
components: {
Upload,
...
},
data() {
return {
uploadVideo: null,
};
},
methods: {
storeVideo(video) {
this.uploadVideo = video;
},
},
};
</script>
Code language: HTML, XML (xml)
We will create a Trimmer
component to handle our trimming logic:
touch components/Trimmer.vue
Once created, let’s inject the Cloudinary video instance into the component:
<!-- pages/index.vue -->
<template>
...
<trimmer
v-if="uploadVideo"
:video="uploadVideo"
/>
<upload
v-else
@uploaded="storeVideo"
/>
...
</template>
Code language: HTML, XML (xml)
// pages/index.vue
<script>
...
import Trimmer from "../components/Trimmer.vue";
export default {
components: {
...
Trimmer,
},
};
</script>
Code language: HTML, XML (xml)
Let us display basic information about the video:
<!-- components/Trimmer.vue -->
<template>
...
<cld-video
:public-id="video.public_id"
/>
...
<ul>
<li>Video format: {{ video.format }}</li>
<li>Duration: {{ video.duration }} Seconds</li>
</ul>
...
</template>
Code language: HTML, XML (xml)
To obtain the start and stop timestamps for the trimmed video, let’s create an HTML form:
<!-- components/Trimmer.vue -->
<template>
...
<form @submit.prevent="trimmed = true">
...
<input
min="0"
step="0.01"
:max="stop"
placeholder="Start"
type="number"
name="start"
v-model="start"
id="start"
/>
...
<input
type="number"
:min="start"
step="0.01"
:max="video.duration + 0.1"
placeholder="End"
v-model="stop"
name="end"
id="end"
/>
...
<button type="submit">Trim</button>
...
</form>
</template>
Code language: HTML, XML (xml)
When the form is submitted, trimmed is set to true
. This will render the trimmed video and a download button. Let us see the code that enables it:
// components/Trimmer.vue
<script>
export default {
props: {
video: {
required: true,
type: Object,
},
},
data() {
return {
start: 0,
stop: this.video.duration,
trimmed: false,
};
},
computed: {
trimmedUrl() {
return this.$cloudinary.video.url(
this.video.public_id, {
start_offset: this.start,
end_offset: this.stop,
format: "mp4",
});
},
},
};
</script>
Code language: HTML, XML (xml)
We can now render the trimmed video and download button:
<cld-video
autoplay="true"
v-if="trimmed"
:public-id="video.public_id"
controls="true"
>
<cld-transformation
:end-offset="stop"
:start-offset="start"
/>
</cld-video>
<div v-if="trimmed">
<a target="_blank" :href="trimmedUrl" >Download</a>
</div>
Code language: HTML, XML (xml)
We are now able to trim our videos seamlessly without the need for a complex setup. To see what more we can do with our media, feel free to check out the Cloudinary documentation.