Created
January 21, 2024 20:02
-
-
Save igortrinidad/3e00e85f4be53ab97fe75e0980134603 to your computer and use it in GitHub Desktop.
Multipart upload chunk
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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