-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathvid2gif.js
166 lines (150 loc) · 5.15 KB
/
vid2gif.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
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;
const ytdlpFormat = 'bestvideo[height<=?480]/best';
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))
.addIntegerOption(option =>
option.setName('quality')
.setDescription('Quality of the gif conversion. Default 70. Number between 1 and 100')
.setRequired(false))
.addIntegerOption(option =>
option.setName('fps')
.setDescription('Change the speed at which the gif play at. Number between 1 and 100.')
.setRequired(false))
.addBooleanOption(option =>
option.setName('autocrop')
.setDescription('Autocrop borders on gif.')
.setRequired(false))
.addBooleanOption(option =>
option.setName('noloop')
.setDescription('Stop the gif from looping')
.setRequired(false)),
category: 'utility',
alias: ['v2g', 'togif'],
integration_types: [0, 1],
async execute(interaction, args) {
await interaction.deferReply({ ephemeral: false });
const maxFileSize = await utils.getMaxFileSize(interaction.guild);
const url = args.url;
let quality = args.quality;
if (quality) {
if (quality <= 0) {
quality = 1;
}
else if (quality > 100) {
quality = 100;
}
}
if (args.fps) {
if (args.fps <= 0) {
args.fps = 1;
}
else if (args.fps > 100) {
args.fps = 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 });
}
const aproxFileSize = await utils.getVideoSize(url, ytdlpFormat);
console.log(aproxFileSize);
if (aproxFileSize > 4) {
return interaction.editReply('The file you are trying to convert is too big! Limit is 4 MB');
};
utils.downloadVideo(url, interaction.id, ytdlpFormat)
.then(async () => {
const file = fs.readdirSync(os.tmpdir()).filter(fn => fn.startsWith(interaction.id));
let output = `${os.tmpdir()}/${file}`;
if (args.autocrop) {
const oldOutput = output;
output = `${os.tmpdir()}/autocrop${file}`;
await utils.autoCrop(oldOutput, output);
}
// Get the fps of the original video
if (!args.fps) {
args.fps = getVideoFramerate(output);
}
const gifskiOutput = output.replace(path.extname(output), '.gif');
const gifsicleOutput = output.replace(path.extname(output), 'gifsicle.gif');
// Extract every frame for gifski
await utils.ffmpeg(['-i', output, `${os.tmpdir()}/frame${interaction.id}%04d.png`]);
// Make it look better
await gifski(gifskiOutput, `${os.tmpdir()}/frame${interaction.id}*`, quality, await args.fps);
// Optimize it
await gifsicle(gifskiOutput, gifsicleOutput, args.noloop);
const fileStat = fs.statSync(gifsicleOutput);
const fileSize = fileStat.size / 1000000.0;
if (fileSize > 25) {
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 });
}
});
},
};
async function gifski(output, input, quality, fps) {
return await new Promise((resolve, reject) => {
// Shell: true should be fine as no user input is being passed
execFile('gifski', ['--quality', quality ? quality : 70, '--fps', fps ? fps : 20, '-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();
});
});
}
async function getVideoFramerate(input) {
return await new Promise((resolve, reject) => {
execFile('ffprobe', ['-v', 'error', '-of', 'csv=p=0', '-select_streams', 'v:0', '-show_entries', 'stream=r_frame_rate', input], (err, stdout, stderr) => {
if (err) {
reject(stderr);
}
if (stderr) {
console.error(stderr);
}
const tempfps = stdout.trim().split('/');
const fps = tempfps[0] / tempfps[1];
return resolve(fps);
});
});
}