2022-08-28 17:03:15 +02:00
import { SlashCommandBuilder } from 'discord.js' ;
2022-06-17 07:31:58 +02:00
import utils from '../../utils/videos.js' ;
2022-06-17 01:25:35 +02:00
import fs from 'node:fs' ;
import os from 'node:os' ;
import path from 'node:path' ;
2024-02-02 03:28:46 +01:00
import { execFile } from 'node:child_process' ;
2022-09-14 11:31:19 +02:00
const { NODE _ENV } = process . env ;
2022-06-17 01:25:35 +02:00
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' )
2023-04-15 23:49:34 +02:00
. setRequired ( true ) )
2023-12-26 16:39:34 +01:00
. addIntegerOption ( option =>
option . setName ( 'quality' )
. setDescription ( 'Quality of the gif conversion. Default 70. Number between 1 and 100' )
. setRequired ( false ) )
2024-06-30 02:01:57 +02:00
. addIntegerOption ( option =>
option . setName ( 'fps' )
. setDescription ( 'Change the speed at which the gif play at. Default 20. Number between 1 and 100.' )
. setRequired ( false ) )
2024-07-08 23:50:28 +02:00
. addBooleanOption ( option =>
option . setName ( 'autocrop' )
. setDescription ( 'Autocrop borders on gif.' )
. setRequired ( false ) )
2023-04-15 23:49:34 +02:00
. addBooleanOption ( option =>
option . setName ( 'noloop' )
. setDescription ( 'Stop the gif from looping' )
. setRequired ( false ) ) ,
2022-08-28 17:03:15 +02:00
category : 'utility' ,
2024-06-15 14:13:44 +02:00
alias : [ 'v2g' , 'togif' ] ,
integration _types : [ 0 , 1 ] ,
2022-08-28 17:03:15 +02:00
async execute ( interaction , args ) {
2022-06-17 01:25:35 +02:00
await interaction . deferReply ( { ephemeral : false } ) ;
2023-04-19 17:05:35 +02:00
const maxFileSize = await utils . getMaxFileSize ( interaction . guild ) ;
2022-09-01 01:43:59 +02:00
const url = args . url ;
2023-12-26 16:39:34 +01:00
let quality = args . quality ;
2024-06-30 02:01:57 +02:00
if ( quality ) {
if ( quality <= 0 ) {
quality = 1 ;
}
else if ( quality > 100 ) {
quality = 100 ;
}
2023-12-26 16:39:34 +01:00
}
2024-06-30 02:01:57 +02:00
if ( args . fps ) {
if ( args . fps <= 0 ) {
args . fps = 1 ;
}
else if ( args . fps > 100 ) {
args . fps = 100 ;
}
2023-12-26 16:39:34 +01:00
}
2022-06-17 01:25:35 +02:00
2022-06-20 08:34:18 +02:00
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 } ) ;
}
2022-06-17 01:25:35 +02:00
utils . downloadVideo ( url , interaction . id )
. then ( async ( ) => {
const file = fs . readdirSync ( os . tmpdir ( ) ) . filter ( fn => fn . startsWith ( interaction . id ) ) ;
2024-07-08 23:50:28 +02:00
let output = ` ${ os . tmpdir ( ) } / ${ file } ` ;
if ( args . autocrop ) {
const oldOutput = output ;
output = ` ${ os . tmpdir ( ) } /autocrop ${ file } ` ;
await utils . autoCrop ( oldOutput , output ) ;
}
2022-06-17 01:25:35 +02:00
const gifskiOutput = output . replace ( path . extname ( output ) , '.gif' ) ;
const gifsicleOutput = output . replace ( path . extname ( output ) , 'gifsicle.gif' ) ;
2022-06-17 02:14:14 +02:00
// Extract every frame for gifski
2024-02-02 03:28:46 +01:00
await utils . ffmpeg ( [ '-i' , output , ` ${ os . tmpdir ( ) } /frame ${ interaction . id } %04d.png ` ] ) ;
2022-06-17 02:14:14 +02:00
// Make it look better
2024-06-30 02:01:57 +02:00
await gifski ( gifskiOutput , ` ${ os . tmpdir ( ) } /frame ${ interaction . id } * ` , quality , args . fps ) ;
2022-06-17 02:14:14 +02:00
// Optimize it
2023-04-15 23:49:34 +02:00
await gifsicle ( gifskiOutput , gifsicleOutput , args . noloop ) ;
2022-06-17 01:25:35 +02:00
const fileStat = fs . statSync ( gifsicleOutput ) ;
const fileSize = fileStat . size / 1000000.0 ;
2024-06-15 14:13:44 +02:00
if ( fileSize > 25 ) {
2022-06-17 01:25:35 +02:00
await interaction . deleteReply ( ) ;
2022-07-01 21:24:39 +02:00
await interaction . followUp ( '❌ Uh oh! The video once converted is too big!' , { ephemeral : true } ) ;
2022-06-17 01:25:35 +02:00
}
2023-04-19 17:05:35 +02:00
else if ( fileSize > maxFileSize ) {
2022-06-17 01:25:35 +02:00
const fileURL = await utils . upload ( gifsicleOutput )
. catch ( err => {
console . error ( err ) ;
} ) ;
2023-04-19 17:05:35 +02:00
await interaction . editReply ( { content : ` ℹ ️ File was bigger than ${ maxFileSize } mb. It has been uploaded to an external site. \n ${ fileURL } ` , ephemeral : false } ) ;
2022-06-17 01:25:35 +02:00
}
else {
await interaction . editReply ( { files : [ gifsicleOutput ] , ephemeral : false } ) ;
}
} ) ;
} ,
} ;
2024-06-30 02:01:57 +02:00
async function gifski ( output , input , quality , fps ) {
2022-06-17 01:25:35 +02:00
return await new Promise ( ( resolve , reject ) => {
2024-02-02 03:28:46 +01:00
// Shell: true should be fine as no user input is being passed
2024-06-30 02:01:57 +02:00
execFile ( 'gifski' , [ '--quality' , quality ? quality : 70 , '--fps' , fps ? fps : 20 , '-o' , output , input ] , { shell : true } , ( err , stdout , stderr ) => {
2022-06-17 01:25:35 +02:00
if ( err ) {
reject ( stderr ) ;
}
if ( stderr ) {
console . error ( stderr ) ;
}
2022-09-14 11:31:19 +02:00
console . log ( NODE _ENV === 'development' ? stdout : null ) ;
2022-09-12 11:33:25 +02:00
resolve ( ) ;
2022-06-17 01:25:35 +02:00
} ) ;
} ) ;
}
2023-04-15 23:49:34 +02:00
async function gifsicle ( input , output , loop = false ) {
2022-06-17 01:25:35 +02:00
return await new Promise ( ( resolve , reject ) => {
2024-02-02 03:28:46 +01:00
// 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 ) => {
2022-06-17 01:25:35 +02:00
if ( err ) {
reject ( stderr ) ;
}
if ( stderr ) {
console . error ( stderr ) ;
}
2022-09-14 11:31:19 +02:00
console . log ( NODE _ENV === 'development' ? stdout : null ) ;
2022-09-12 11:33:25 +02:00
resolve ( ) ;
2022-06-17 01:25:35 +02:00
} ) ;
} ) ;
2022-06-20 08:34:18 +02:00
}