Skip to content

Instantly share code, notes, and snippets.

@igortrinidad
Created January 21, 2024 20:02
Show Gist options
  • Save igortrinidad/3e00e85f4be53ab97fe75e0980134603 to your computer and use it in GitHub Desktop.
Save igortrinidad/3e00e85f4be53ab97fe75e0980134603 to your computer and use it in GitHub Desktop.
Multipart upload chunk
import Application from '@ioc:Adonis/Core/Application'
import fs from 'fs'
import fsPromises from 'fs/promises'
import ffmpeg from 'fluent-ffmpeg'
export default class MultipartUploadService {
public stream: any = null
public file_name: string = ''
public chunk_index: number = 0
public total_chunks: number = 0
public webmFileSaved: boolean = false
constructor(stream: any = null, file_name: string = '', chunk_index: string, total_chunks: string) {
this.stream = stream
this.file_name = file_name
this.chunk_index = parseInt(chunk_index)
this.total_chunks = parseInt(total_chunks)
}
public get getChunksDir() {
return Application.tmpPath('chunks')
}
public get getChunkFolder() {
return `${ this.getChunksDir }/${ this.file_name }`
}
public get getMp4FilePath() {
return `${ this.getChunkFolder }/converted_${ this.file_name }.mp4`
}
public get getWebMFilePath() {
return `${ this.getChunkFolder }/converted_${ this.file_name }`
}
public getChunkPath(chunk_index: number) {
return `${ this.getChunkFolder }/chunk_${ chunk_index }`
}
public getChunkFinishedPath(chunk_index: number) {
return `${ this.getChunkFolder }/chunk_finished_${ chunk_index }`
}
public async ensureChunksFolder() {
await fsPromises.mkdir(this.getChunkFolder, { recursive: true })
}
public async process() {
try {
await this.ensureChunksFolder()
await this.pipeStreamToFile()
await this.renameChunkOnFinished()
await this.wait(500)
if(await this.checkAllChunksFinished()) {
await this.saveWebmfile()
await this.convertWebMtoMP4()
await this.cleanupChunks()
}
} catch (error) {
console.error(error)
throw error
}
return true
}
public async wait(ms: number) {
return new Promise(resolve => setTimeout(resolve, ms))
}
private async pipeStreamToFile(): Promise<void> {
console.log('pipeStreamToFile')
const fileStream = fs.createWriteStream(this.getChunkPath(this.chunk_index))
this.stream.pipe(fileStream)
return new Promise<void>((resolve, reject) => {
this.stream.on('end', () => {
fileStream.end()
resolve()
})
this.stream.on('error', (error) => {
fileStream.end()
reject(error)
})
})
}
public async renameChunkOnFinished() {
console.log('renameChunkOnFinished')
await fsPromises.rename(this.getChunkPath(this.chunk_index), this.getChunkFinishedPath(this.chunk_index))
}
public async checkAllChunksFinished() {
console.log('checkAllChunksFinished')
const files = await fsPromises.readdir(this.getChunkFolder)
const filesFinished = files.filter(file => file.includes('chunk_finished_'))
if (filesFinished.length === this.total_chunks) {
console.log('All chunks finished')
return true
}
return false
}
public async saveWebmfile() {
console.log('saveWebmfile')
if (this.chunk_index === this.total_chunks - 1) {
const concatenatedBuffer = await this.concatChunks()
await fsPromises.writeFile(this.getWebMFilePath, concatenatedBuffer)
console.log('Webm file written to file:', this.getWebMFilePath)
this.webmFileSaved = true
}
}
private async concatChunks(): Promise<Buffer> {
console.log('concatChunks')
const concatenatedBuffer = []
for (let i = 0; i < this.total_chunks; i++) {
const currentChunkPath = this.getChunkFinishedPath(i)
const chunkBuffer = await fsPromises.readFile(currentChunkPath)
concatenatedBuffer.push(chunkBuffer)
}
return Buffer.concat(concatenatedBuffer)
}
private async cleanupChunks(): Promise<void> {
console.log('cleanupChunks')
for (let i = 0; i < this.total_chunks; i++) {
const currentChunkPath = this.getChunkFinishedPath(i)
await fsPromises.unlink(currentChunkPath)
}
}
private async convertWebMtoMP4(): Promise<void> {
console.log('convertWebMtoMP4')
return new Promise<void>((resolve, reject) => {
ffmpeg()
.input(this.getWebMFilePath)
// Scale the video to 720 pixels in height. The -2 means FFmpeg should figure out the
// exact size of the other dimension. In other words, to make the video 720 pixels wide
// and make FFmpeg calculate its height, use scale=720:-2 instead.
.outputOptions('-vf','scale=1280:-2')
// Output file
.saveToFile(this.getMp4FilePath)
// Log the percentage of work completed
.on('progress', (progress) => {
if (progress.percent) {
console.log(`Processing: ${Math.floor(progress.percent)}% done`)
}
})
// The callback that is run when FFmpeg is finished
.on('end', () => {
console.log('FFmpeg has finished.')
resolve()
})
// The callback that is run when FFmpeg encountered an error
.on('error', (error) => {
console.error(error)
reject()
})
})
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment