[fix] .wav .flac ファイルを再生可能にする (#10686)
* .wav .flac ファイルを再生可能にする file-typeにより判定されたMIME TypeをHTML5 Audio/Video要素に認識されるものに書き換える * fix typecheck error * frontend側の FILE_TYPE_BROWSERSAFEも更新 * Update packages/backend/src/core/FileInfoService.ts * ✌️ * 後方互換を確保 * add tests * update changelog.md --------- Co-authored-by: tamaina <tamaina@hotmail.co.jp>
This commit is contained in:
		| @@ -56,6 +56,11 @@ export const FILE_TYPE_BROWSERSAFE = [ | ||||
| 	'audio/webm', | ||||
|  | ||||
| 	'audio/aac', | ||||
|  | ||||
| 	// see https://github.com/misskey-dev/misskey/pull/10686 | ||||
| 	'audio/flac', | ||||
| 	'audio/wav', | ||||
| 	// backward compatibility | ||||
| 	'audio/x-flac', | ||||
| 	'audio/vnd.wave', | ||||
| ]; | ||||
|   | ||||
| @@ -5,7 +5,7 @@ import * as stream from 'node:stream'; | ||||
| import * as util from 'node:util'; | ||||
| import { Injectable } from '@nestjs/common'; | ||||
| import { FSWatcher } from 'chokidar'; | ||||
| import { fileTypeFromFile } from 'file-type'; | ||||
| import * as fileType from 'file-type'; | ||||
| import FFmpeg from 'fluent-ffmpeg'; | ||||
| import isSvg from 'is-svg'; | ||||
| import probeImageSize from 'probe-image-size'; | ||||
| @@ -301,21 +301,34 @@ export class FileInfoService { | ||||
| 		return fs.promises.access(path).then(() => true, () => false); | ||||
| 	} | ||||
|  | ||||
| 	@bindThis | ||||
| 	public fixMime(mime: string | fileType.MimeType): string { | ||||
| 		// see https://github.com/misskey-dev/misskey/pull/10686 | ||||
| 		if (mime === "audio/x-flac") { | ||||
| 			return "audio/flac"; | ||||
| 		} | ||||
| 		if (mime === "audio/vnd.wave") { | ||||
| 			return "audio/wav"; | ||||
| 		} | ||||
|  | ||||
| 		return mime; | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * Detect MIME Type and extension | ||||
| 	 */ | ||||
| 	@bindThis | ||||
| 	public async detectType(path: string): Promise<{ | ||||
| 	mime: string; | ||||
| 	ext: string | null; | ||||
| }> { | ||||
| 		mime: string; | ||||
| 		ext: string | null; | ||||
| 	}> { | ||||
| 	// Check 0 byte | ||||
| 		const fileSize = await this.getFileSize(path); | ||||
| 		if (fileSize === 0) { | ||||
| 			return TYPE_OCTET_STREAM; | ||||
| 		} | ||||
|  | ||||
| 		const type = await fileTypeFromFile(path); | ||||
| 		const type = await fileType.fileTypeFromFile(path); | ||||
|  | ||||
| 		if (type) { | ||||
| 		// XMLはSVGかもしれない | ||||
| @@ -324,7 +337,7 @@ export class FileInfoService { | ||||
| 			} | ||||
|  | ||||
| 			return { | ||||
| 				mime: type.mime, | ||||
| 				mime: this.fixMime(type.mime), | ||||
| 				ext: type.ext, | ||||
| 			}; | ||||
| 		} | ||||
|   | ||||
| @@ -454,7 +454,8 @@ export class FileServerService { | ||||
| 			fileRole: 'original', | ||||
| 			file, | ||||
| 			filename: file.name, | ||||
| 			mime: file.type, | ||||
| 			// 古いファイルは修正前のmimeを持っているのでできるだけ修正してあげる | ||||
| 			mime: this.fileInfoService.fixMime(file.type), | ||||
| 			ext: null, | ||||
| 			path, | ||||
| 		}; | ||||
|   | ||||
							
								
								
									
										
											BIN
										
									
								
								packages/backend/test/resources/kick_gaba7.aac
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								packages/backend/test/resources/kick_gaba7.aac
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								packages/backend/test/resources/kick_gaba7.flac
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								packages/backend/test/resources/kick_gaba7.flac
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								packages/backend/test/resources/kick_gaba7.mp3
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								packages/backend/test/resources/kick_gaba7.mp3
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								packages/backend/test/resources/kick_gaba7.wav
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								packages/backend/test/resources/kick_gaba7.wav
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								packages/backend/test/resources/kick_gaba7.webm
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								packages/backend/test/resources/kick_gaba7.webm
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| @@ -7,10 +7,10 @@ import { ModuleMocker } from 'jest-mock'; | ||||
| import { Test } from '@nestjs/testing'; | ||||
| import { GlobalModule } from '@/GlobalModule.js'; | ||||
| import { FileInfoService } from '@/core/FileInfoService.js'; | ||||
| import { DI } from '@/di-symbols.js'; | ||||
| //import { DI } from '@/di-symbols.js'; | ||||
| import { AiService } from '@/core/AiService.js'; | ||||
| import type { TestingModule } from '@nestjs/testing'; | ||||
| import type { jest } from '@jest/globals'; | ||||
| import { describe, beforeAll, afterAll, test } from '@jest/globals'; | ||||
| import type { MockFunctionMetadata } from 'jest-mock'; | ||||
|  | ||||
| const _filename = fileURLToPath(import.meta.url); | ||||
| @@ -74,164 +74,271 @@ describe('FileInfoService', () => { | ||||
| 		}); | ||||
| 	}); | ||||
|  | ||||
| 	test('Generic JPEG', async () => { | ||||
| 		const path = `${resources}/Lenna.jpg`; | ||||
| 		const info = await fileInfoService.getFileInfo(path, { skipSensitiveDetection: true }) as any; | ||||
| 		delete info.warnings; | ||||
| 		delete info.blurhash; | ||||
| 		delete info.sensitive; | ||||
| 		delete info.porn; | ||||
| 		assert.deepStrictEqual(info, { | ||||
| 			size: 25360, | ||||
| 			md5: '091b3f259662aa31e2ffef4519951168', | ||||
| 			type: { | ||||
| 				mime: 'image/jpeg', | ||||
| 				ext: 'jpg', | ||||
| 			}, | ||||
| 			width: 512, | ||||
| 			height: 512, | ||||
| 			orientation: undefined, | ||||
| 	describe('IMAGE', () => { | ||||
| 		test('Generic JPEG', async () => { | ||||
| 			const path = `${resources}/Lenna.jpg`; | ||||
| 			const info = await fileInfoService.getFileInfo(path, { skipSensitiveDetection: true }) as any; | ||||
| 			delete info.warnings; | ||||
| 			delete info.blurhash; | ||||
| 			delete info.sensitive; | ||||
| 			delete info.porn; | ||||
| 			assert.deepStrictEqual(info, { | ||||
| 				size: 25360, | ||||
| 				md5: '091b3f259662aa31e2ffef4519951168', | ||||
| 				type: { | ||||
| 					mime: 'image/jpeg', | ||||
| 					ext: 'jpg', | ||||
| 				}, | ||||
| 				width: 512, | ||||
| 				height: 512, | ||||
| 				orientation: undefined, | ||||
| 			}); | ||||
| 		}); | ||||
| 	 | ||||
| 		test('Generic APNG', async () => { | ||||
| 			const path = `${resources}/anime.png`; | ||||
| 			const info = await fileInfoService.getFileInfo(path, { skipSensitiveDetection: true }) as any; | ||||
| 			delete info.warnings; | ||||
| 			delete info.blurhash; | ||||
| 			delete info.sensitive; | ||||
| 			delete info.porn; | ||||
| 			assert.deepStrictEqual(info, { | ||||
| 				size: 1868, | ||||
| 				md5: '08189c607bea3b952704676bb3c979e0', | ||||
| 				type: { | ||||
| 					mime: 'image/apng', | ||||
| 					ext: 'apng', | ||||
| 				}, | ||||
| 				width: 256, | ||||
| 				height: 256, | ||||
| 				orientation: undefined, | ||||
| 			}); | ||||
| 		}); | ||||
| 	 | ||||
| 		test('Generic AGIF', async () => { | ||||
| 			const path = `${resources}/anime.gif`; | ||||
| 			const info = await fileInfoService.getFileInfo(path, { skipSensitiveDetection: true }) as any; | ||||
| 			delete info.warnings; | ||||
| 			delete info.blurhash; | ||||
| 			delete info.sensitive; | ||||
| 			delete info.porn; | ||||
| 			assert.deepStrictEqual(info, { | ||||
| 				size: 2248, | ||||
| 				md5: '32c47a11555675d9267aee1a86571e7e', | ||||
| 				type: { | ||||
| 					mime: 'image/gif', | ||||
| 					ext: 'gif', | ||||
| 				}, | ||||
| 				width: 256, | ||||
| 				height: 256, | ||||
| 				orientation: undefined, | ||||
| 			}); | ||||
| 		}); | ||||
| 	 | ||||
| 		test('PNG with alpha', async () => { | ||||
| 			const path = `${resources}/with-alpha.png`; | ||||
| 			const info = await fileInfoService.getFileInfo(path, { skipSensitiveDetection: true }) as any; | ||||
| 			delete info.warnings; | ||||
| 			delete info.blurhash; | ||||
| 			delete info.sensitive; | ||||
| 			delete info.porn; | ||||
| 			assert.deepStrictEqual(info, { | ||||
| 				size: 3772, | ||||
| 				md5: 'f73535c3e1e27508885b69b10cf6e991', | ||||
| 				type: { | ||||
| 					mime: 'image/png', | ||||
| 					ext: 'png', | ||||
| 				}, | ||||
| 				width: 256, | ||||
| 				height: 256, | ||||
| 				orientation: undefined, | ||||
| 			}); | ||||
| 		}); | ||||
| 	 | ||||
| 		test('Generic SVG', async () => { | ||||
| 			const path = `${resources}/image.svg`; | ||||
| 			const info = await fileInfoService.getFileInfo(path, { skipSensitiveDetection: true }) as any; | ||||
| 			delete info.warnings; | ||||
| 			delete info.blurhash; | ||||
| 			delete info.sensitive; | ||||
| 			delete info.porn; | ||||
| 			assert.deepStrictEqual(info, { | ||||
| 				size: 505, | ||||
| 				md5: 'b6f52b4b021e7b92cdd04509c7267965', | ||||
| 				type: { | ||||
| 					mime: 'image/svg+xml', | ||||
| 					ext: 'svg', | ||||
| 				}, | ||||
| 				width: 256, | ||||
| 				height: 256, | ||||
| 				orientation: undefined, | ||||
| 			}); | ||||
| 		}); | ||||
| 	 | ||||
| 		test('SVG with XML definition', async () => { | ||||
| 			// https://github.com/misskey-dev/misskey/issues/4413 | ||||
| 			const path = `${resources}/with-xml-def.svg`; | ||||
| 			const info = await fileInfoService.getFileInfo(path, { skipSensitiveDetection: true }) as any; | ||||
| 			delete info.warnings; | ||||
| 			delete info.blurhash; | ||||
| 			delete info.sensitive; | ||||
| 			delete info.porn; | ||||
| 			assert.deepStrictEqual(info, { | ||||
| 				size: 544, | ||||
| 				md5: '4b7a346cde9ccbeb267e812567e33397', | ||||
| 				type: { | ||||
| 					mime: 'image/svg+xml', | ||||
| 					ext: 'svg', | ||||
| 				}, | ||||
| 				width: 256, | ||||
| 				height: 256, | ||||
| 				orientation: undefined, | ||||
| 			}); | ||||
| 		}); | ||||
| 	 | ||||
| 		test('Dimension limit', async () => { | ||||
| 			const path = `${resources}/25000x25000.png`; | ||||
| 			const info = await fileInfoService.getFileInfo(path, { skipSensitiveDetection: true }) as any; | ||||
| 			delete info.warnings; | ||||
| 			delete info.blurhash; | ||||
| 			delete info.sensitive; | ||||
| 			delete info.porn; | ||||
| 			assert.deepStrictEqual(info, { | ||||
| 				size: 75933, | ||||
| 				md5: '268c5dde99e17cf8fe09f1ab3f97df56', | ||||
| 				type: { | ||||
| 					mime: 'application/octet-stream',	// do not treat as image | ||||
| 					ext: null, | ||||
| 				}, | ||||
| 				width: 25000, | ||||
| 				height: 25000, | ||||
| 				orientation: undefined, | ||||
| 			}); | ||||
| 		}); | ||||
| 	 | ||||
| 		test('Rotate JPEG', async () => { | ||||
| 			const path = `${resources}/rotate.jpg`; | ||||
| 			const info = await fileInfoService.getFileInfo(path, { skipSensitiveDetection: true }) as any; | ||||
| 			delete info.warnings; | ||||
| 			delete info.blurhash; | ||||
| 			delete info.sensitive; | ||||
| 			delete info.porn; | ||||
| 			assert.deepStrictEqual(info, { | ||||
| 				size: 12624, | ||||
| 				md5: '68d5b2d8d1d1acbbce99203e3ec3857e', | ||||
| 				type: { | ||||
| 					mime: 'image/jpeg', | ||||
| 					ext: 'jpg', | ||||
| 				}, | ||||
| 				width: 512, | ||||
| 				height: 256, | ||||
| 				orientation: 8, | ||||
| 			}); | ||||
| 		}); | ||||
| 	}); | ||||
|  | ||||
| 	test('Generic APNG', async () => { | ||||
| 		const path = `${resources}/anime.png`; | ||||
| 		const info = await fileInfoService.getFileInfo(path, { skipSensitiveDetection: true }) as any; | ||||
| 		delete info.warnings; | ||||
| 		delete info.blurhash; | ||||
| 		delete info.sensitive; | ||||
| 		delete info.porn; | ||||
| 		assert.deepStrictEqual(info, { | ||||
| 			size: 1868, | ||||
| 			md5: '08189c607bea3b952704676bb3c979e0', | ||||
| 			type: { | ||||
| 				mime: 'image/apng', | ||||
| 				ext: 'apng', | ||||
| 			}, | ||||
| 			width: 256, | ||||
| 			height: 256, | ||||
| 			orientation: undefined, | ||||
| 	describe('AUDIO', () => { | ||||
| 		test('MP3', async () => { | ||||
| 			const path = `${resources}/kick_gaba7.mp3`; | ||||
| 			const info = await fileInfoService.getFileInfo(path, { skipSensitiveDetection: true }) as any; | ||||
| 			delete info.warnings; | ||||
| 			delete info.blurhash; | ||||
| 			delete info.sensitive; | ||||
| 			delete info.porn; | ||||
| 			delete info.width; | ||||
| 			delete info.height; | ||||
| 			delete info.orientation; | ||||
| 			assert.deepStrictEqual(info, { | ||||
| 				size: 19853, | ||||
| 				md5: '4f557df8548bc3cecc794c652f690446', | ||||
| 				type: { | ||||
| 					mime: 'audio/mpeg', | ||||
| 					ext: 'mp3', | ||||
| 				}, | ||||
| 			}); | ||||
| 		}); | ||||
| 	}); | ||||
|  | ||||
| 	test('Generic AGIF', async () => { | ||||
| 		const path = `${resources}/anime.gif`; | ||||
| 		const info = await fileInfoService.getFileInfo(path, { skipSensitiveDetection: true }) as any; | ||||
| 		delete info.warnings; | ||||
| 		delete info.blurhash; | ||||
| 		delete info.sensitive; | ||||
| 		delete info.porn; | ||||
| 		assert.deepStrictEqual(info, { | ||||
| 			size: 2248, | ||||
| 			md5: '32c47a11555675d9267aee1a86571e7e', | ||||
| 			type: { | ||||
| 				mime: 'image/gif', | ||||
| 				ext: 'gif', | ||||
| 			}, | ||||
| 			width: 256, | ||||
| 			height: 256, | ||||
| 			orientation: undefined, | ||||
| 	 | ||||
| 		test('WAV', async () => { | ||||
| 			const path = `${resources}/kick_gaba7.wav`; | ||||
| 			const info = await fileInfoService.getFileInfo(path, { skipSensitiveDetection: true }) as any; | ||||
| 			delete info.warnings; | ||||
| 			delete info.blurhash; | ||||
| 			delete info.sensitive; | ||||
| 			delete info.porn; | ||||
| 			delete info.width; | ||||
| 			delete info.height; | ||||
| 			delete info.orientation; | ||||
| 			assert.deepStrictEqual(info, { | ||||
| 				size: 87630, | ||||
| 				md5: '8bc9bb4fe5e77bb1871448209be635c1', | ||||
| 				type: { | ||||
| 					mime: 'audio/wav', | ||||
| 					ext: 'wav', | ||||
| 				}, | ||||
| 			}); | ||||
| 		}); | ||||
| 	}); | ||||
|  | ||||
| 	test('PNG with alpha', async () => { | ||||
| 		const path = `${resources}/with-alpha.png`; | ||||
| 		const info = await fileInfoService.getFileInfo(path, { skipSensitiveDetection: true }) as any; | ||||
| 		delete info.warnings; | ||||
| 		delete info.blurhash; | ||||
| 		delete info.sensitive; | ||||
| 		delete info.porn; | ||||
| 		assert.deepStrictEqual(info, { | ||||
| 			size: 3772, | ||||
| 			md5: 'f73535c3e1e27508885b69b10cf6e991', | ||||
| 			type: { | ||||
| 				mime: 'image/png', | ||||
| 				ext: 'png', | ||||
| 			}, | ||||
| 			width: 256, | ||||
| 			height: 256, | ||||
| 			orientation: undefined, | ||||
| 	 | ||||
| 		test('AAC', async () => { | ||||
| 			const path = `${resources}/kick_gaba7.aac`; | ||||
| 			const info = await fileInfoService.getFileInfo(path, { skipSensitiveDetection: true }) as any; | ||||
| 			delete info.warnings; | ||||
| 			delete info.blurhash; | ||||
| 			delete info.sensitive; | ||||
| 			delete info.porn; | ||||
| 			delete info.width; | ||||
| 			delete info.height; | ||||
| 			delete info.orientation; | ||||
| 			assert.deepStrictEqual(info, { | ||||
| 				size: 7291, | ||||
| 				md5: '2789323f05e3392b648066f50be6a2a6', | ||||
| 				type: { | ||||
| 					mime: 'audio/aac', | ||||
| 					ext: 'aac', | ||||
| 				}, | ||||
| 			}); | ||||
| 		}); | ||||
| 	}); | ||||
|  | ||||
| 	test('Generic SVG', async () => { | ||||
| 		const path = `${resources}/image.svg`; | ||||
| 		const info = await fileInfoService.getFileInfo(path, { skipSensitiveDetection: true }) as any; | ||||
| 		delete info.warnings; | ||||
| 		delete info.blurhash; | ||||
| 		delete info.sensitive; | ||||
| 		delete info.porn; | ||||
| 		assert.deepStrictEqual(info, { | ||||
| 			size: 505, | ||||
| 			md5: 'b6f52b4b021e7b92cdd04509c7267965', | ||||
| 			type: { | ||||
| 				mime: 'image/svg+xml', | ||||
| 				ext: 'svg', | ||||
| 			}, | ||||
| 			width: 256, | ||||
| 			height: 256, | ||||
| 			orientation: undefined, | ||||
| 	 | ||||
| 		test('FLAC', async () => { | ||||
| 			const path = `${resources}/kick_gaba7.flac`; | ||||
| 			const info = await fileInfoService.getFileInfo(path, { skipSensitiveDetection: true }) as any; | ||||
| 			delete info.warnings; | ||||
| 			delete info.blurhash; | ||||
| 			delete info.sensitive; | ||||
| 			delete info.porn; | ||||
| 			delete info.width; | ||||
| 			delete info.height; | ||||
| 			delete info.orientation; | ||||
| 			assert.deepStrictEqual(info, { | ||||
| 				size: 108793, | ||||
| 				md5: 'bc0f3adfe0e1ca99ae6c7528c46b3173', | ||||
| 				type: { | ||||
| 					mime: 'audio/flac', | ||||
| 					ext: 'flac', | ||||
| 				}, | ||||
| 			}); | ||||
| 		}); | ||||
| 	}); | ||||
|  | ||||
| 	test('SVG with XML definition', async () => { | ||||
| 		// https://github.com/misskey-dev/misskey/issues/4413 | ||||
| 		const path = `${resources}/with-xml-def.svg`; | ||||
| 		const info = await fileInfoService.getFileInfo(path, { skipSensitiveDetection: true }) as any; | ||||
| 		delete info.warnings; | ||||
| 		delete info.blurhash; | ||||
| 		delete info.sensitive; | ||||
| 		delete info.porn; | ||||
| 		assert.deepStrictEqual(info, { | ||||
| 			size: 544, | ||||
| 			md5: '4b7a346cde9ccbeb267e812567e33397', | ||||
| 			type: { | ||||
| 				mime: 'image/svg+xml', | ||||
| 				ext: 'svg', | ||||
| 			}, | ||||
| 			width: 256, | ||||
| 			height: 256, | ||||
| 			orientation: undefined, | ||||
| 		}); | ||||
| 	}); | ||||
|  | ||||
| 	test('Dimension limit', async () => { | ||||
| 		const path = `${resources}/25000x25000.png`; | ||||
| 		const info = await fileInfoService.getFileInfo(path, { skipSensitiveDetection: true }) as any; | ||||
| 		delete info.warnings; | ||||
| 		delete info.blurhash; | ||||
| 		delete info.sensitive; | ||||
| 		delete info.porn; | ||||
| 		assert.deepStrictEqual(info, { | ||||
| 			size: 75933, | ||||
| 			md5: '268c5dde99e17cf8fe09f1ab3f97df56', | ||||
| 			type: { | ||||
| 				mime: 'application/octet-stream',	// do not treat as image | ||||
| 				ext: null, | ||||
| 			}, | ||||
| 			width: 25000, | ||||
| 			height: 25000, | ||||
| 			orientation: undefined, | ||||
| 		}); | ||||
| 	}); | ||||
|  | ||||
| 	test('Rotate JPEG', async () => { | ||||
| 		const path = `${resources}/rotate.jpg`; | ||||
| 		const info = await fileInfoService.getFileInfo(path, { skipSensitiveDetection: true }) as any; | ||||
| 		delete info.warnings; | ||||
| 		delete info.blurhash; | ||||
| 		delete info.sensitive; | ||||
| 		delete info.porn; | ||||
| 		assert.deepStrictEqual(info, { | ||||
| 			size: 12624, | ||||
| 			md5: '68d5b2d8d1d1acbbce99203e3ec3857e', | ||||
| 			type: { | ||||
| 				mime: 'image/jpeg', | ||||
| 				ext: 'jpg', | ||||
| 			}, | ||||
| 			width: 512, | ||||
| 			height: 256, | ||||
| 			orientation: 8, | ||||
| 	 | ||||
| 		/* | ||||
| 		 * video/webmとして検出されてしまう | ||||
| 		test('WEBM AUDIO', async () => { | ||||
| 			const path = `${resources}/kick_gaba7.webm`; | ||||
| 			const info = await fileInfoService.getFileInfo(path, { skipSensitiveDetection: true }) as any; | ||||
| 			delete info.warnings; | ||||
| 			delete info.blurhash; | ||||
| 			delete info.sensitive; | ||||
| 			delete info.porn; | ||||
| 			delete info.width; | ||||
| 			delete info.height; | ||||
| 			delete info.orientation; | ||||
| 			assert.deepStrictEqual(info, { | ||||
| 				size: 8879, | ||||
| 				md5: '3350083dec312419cfdc06c16413aca7', | ||||
| 				type: { | ||||
| 					mime: 'audio/webm', | ||||
| 					ext: 'webm', | ||||
| 				}, | ||||
| 			}); | ||||
| 		}); | ||||
| 		 */ | ||||
| 	}); | ||||
| }); | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Yuriha
					Yuriha