You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
Haha-Yes/commands/utility/vid2gif.js

111 lines
3.6 KiB
JavaScript

import { SlashCommandBuilder } from 'discord.js';
import utils from '../../utils/videos.js';
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import { execFile } from 'node:child_process';
const { NODE_ENV } = process.env;
export default {
data: new SlashCommandBuilder()
.setName('vid2gif')
.setDescription('Convert your video into a gif.')
.addStringOption(option =>
option.setName('url')
.setDescription('URL of the video you want to convert')
.setRequired(true))
4 months ago
.addIntegerOption(option =>
option.setName('quality')
.setDescription('Quality of the gif conversion. Default 70. Number between 1 and 100')
.setRequired(false))
.addBooleanOption(option =>
option.setName('noloop')
.setDescription('Stop the gif from looping')
.setRequired(false)),
category: 'utility',
alias: ['v2g'],
async execute(interaction, args) {
await interaction.deferReply({ ephemeral: false });
const maxFileSize = await utils.getMaxFileSize(interaction.guild);
const url = args.url;
4 months ago
let quality = args.quality;
if (quality <= 0) {
quality = 1;
}
else if (quality > 100) {
quality = 100;
}
if (!await utils.stringIsAValidurl(url)) {
console.error(`Not a url!!! ${url}`);
return interaction.editReply({ content: '❌ This does not look like a valid url!', ephemeral: true });
}
utils.downloadVideo(url, interaction.id)
.then(async () => {
const file = fs.readdirSync(os.tmpdir()).filter(fn => fn.startsWith(interaction.id));
const output = `${os.tmpdir()}/${file}`;
const gifskiOutput = output.replace(path.extname(output), '.gif');
const gifsicleOutput = output.replace(path.extname(output), 'gifsicle.gif');
2 years ago
// Extract every frame for gifski
await utils.ffmpeg(['-i', output, `${os.tmpdir()}/frame${interaction.id}%04d.png`]);
2 years ago
// Make it look better
4 months ago
await gifski(gifskiOutput, `${os.tmpdir()}/frame${interaction.id}*`, quality);
2 years ago
// Optimize it
await gifsicle(gifskiOutput, gifsicleOutput, args.noloop);
const fileStat = fs.statSync(gifsicleOutput);
const fileSize = fileStat.size / 1000000.0;
if (fileSize > 100) {
await interaction.deleteReply();
await interaction.followUp('❌ Uh oh! The video once converted is too big!', { ephemeral: true });
}
else if (fileSize > maxFileSize) {
const fileURL = await utils.upload(gifsicleOutput)
.catch(err => {
console.error(err);
});
await interaction.editReply({ content: ` File was bigger than ${maxFileSize} mb. It has been uploaded to an external site.\n${fileURL}`, ephemeral: false });
}
else {
await interaction.editReply({ files: [gifsicleOutput], ephemeral: false });
}
});
},
};
4 months ago
async function gifski(output, input, quality) {
return await new Promise((resolve, reject) => {
// Shell: true should be fine as no user input is being passed
execFile('gifski', ['--quality', quality ? quality : 70, '-o', output, input], { shell: true }, (err, stdout, stderr) => {
if (err) {
reject(stderr);
}
if (stderr) {
console.error(stderr);
}
console.log(NODE_ENV === 'development' ? stdout : null);
resolve();
});
});
}
async function gifsicle(input, output, loop = false) {
return await new Promise((resolve, reject) => {
// Shell: true should be fine as no user input is being passed
execFile('gifsicle', ['--colors', '256', loop ? '--no-loopcount' : '', '-i', input, '-o', output], { shell: true }, (err, stdout, stderr) => {
if (err) {
reject(stderr);
}
if (stderr) {
console.error(stderr);
}
console.log(NODE_ENV === 'development' ? stdout : null);
resolve();
});
});
}