mirror of
https://git.stupid.fish/teidesu/scripts.git
synced 2025-10-13 08:11:23 +11:00
chore: update public repo
This commit is contained in:
parent
728699b3ec
commit
96ca247fcb
6 changed files with 261 additions and 133 deletions
16
utils/fs.ts
16
utils/fs.ts
|
@ -21,3 +21,19 @@ export async function directoryExists(path: string): Promise<boolean> {
|
|||
export function sanitizeFilename(filename: string) {
|
||||
return filename.replace(/[/\\?%*:|"<>]/g, '_')
|
||||
}
|
||||
|
||||
export async function writeWebStreamToFile(stream: ReadableStream<unknown>, path: string) {
|
||||
const fd = await fsp.open(path, 'w+')
|
||||
const writer = fd.createWriteStream()
|
||||
|
||||
for await (const chunk of stream as any) {
|
||||
writer.write(chunk)
|
||||
}
|
||||
|
||||
writer.end()
|
||||
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
writer.on('error', reject)
|
||||
writer.on('finish', resolve)
|
||||
})
|
||||
}
|
||||
|
|
102
utils/media-metadata.ts
Normal file
102
utils/media-metadata.ts
Normal file
|
@ -0,0 +1,102 @@
|
|||
import type { ProcessPromise } from 'zx'
|
||||
import { Readable } from 'node:stream'
|
||||
import { Bytes, write } from '@fuman/io'
|
||||
import { $ } from 'zx'
|
||||
|
||||
export async function generateOpusImageBlob(image: Uint8Array) {
|
||||
// todo we should probably not use ffprobe here but whatever lol
|
||||
const proc = $`ffprobe -of json -v error -show_entries stream=codec_name,width,height pipe:0`
|
||||
proc.stdin.write(image)
|
||||
proc.stdin.end()
|
||||
const json = await proc.json()
|
||||
|
||||
const img = json.streams[0]
|
||||
// https://www.rfc-editor.org/rfc/rfc9639.html#section-8.8
|
||||
const mime = img.codec_name === 'mjpeg' ? 'image/jpeg' : 'image/png'
|
||||
const description = 'Cover Artwork'
|
||||
|
||||
const res = Bytes.alloc(image.length + 128)
|
||||
write.uint32be(res, 3) // picture type = album cover
|
||||
write.uint32be(res, mime.length)
|
||||
write.rawString(res, mime)
|
||||
write.uint32be(res, description.length)
|
||||
write.rawString(res, description)
|
||||
write.uint32be(res, img.width)
|
||||
write.uint32be(res, img.height)
|
||||
write.uint32be(res, 0) // color depth
|
||||
write.uint32be(res, 0) // color index (unused, for gifs)
|
||||
write.uint32be(res, image.length)
|
||||
write.bytes(res, image)
|
||||
|
||||
return res.result()
|
||||
}
|
||||
|
||||
export async function runMetaflac(options: {
|
||||
path: string
|
||||
tags: Partial<Record<
|
||||
| 'TITLE'
|
||||
| 'ARTIST'
|
||||
| 'COMPOSER'
|
||||
| 'ALBUM'
|
||||
| 'DATE'
|
||||
| 'DISCNUMBER'
|
||||
| 'TRACKNUMBER'
|
||||
| 'COMMENT'
|
||||
| 'PRODUCER'
|
||||
| 'COPYRIGHT'
|
||||
| 'ISRC'
|
||||
| 'LYRICS'
|
||||
| 'MAIN_ARTIST',
|
||||
string | number | string[] | null
|
||||
>
|
||||
>
|
||||
coverPath?: string
|
||||
}) {
|
||||
const params: string[] = [
|
||||
'--remove-all-tags',
|
||||
]
|
||||
|
||||
for (const [key, value] of Object.entries(options.tags)) {
|
||||
if (value == null) continue
|
||||
if (Array.isArray(value)) {
|
||||
for (const v of value) {
|
||||
params.push(`--set-tag=${key}=${v}`)
|
||||
}
|
||||
} else {
|
||||
params.push(`--set-tag=${key}=${value}`)
|
||||
}
|
||||
}
|
||||
|
||||
if (options.coverPath) {
|
||||
params.push(`--import-picture-from=${options.coverPath}`)
|
||||
}
|
||||
|
||||
params.push(options.path)
|
||||
|
||||
await $`metaflac ${params}`
|
||||
}
|
||||
|
||||
export function generateFfmpegMetadataFlags(metadata: Partial<Record<string, string | string[]>>) {
|
||||
const res: string[] = []
|
||||
|
||||
for (const [key, value] of Object.entries(metadata)) {
|
||||
if (value == null) continue
|
||||
if (Array.isArray(value)) {
|
||||
for (const v of value) {
|
||||
res.push('-metadata', `${key}=${v}`)
|
||||
}
|
||||
} else {
|
||||
res.push('-metadata', `${key}=${value}`)
|
||||
}
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
export async function pipeIntoProc(proc: ProcessPromise, stream: ReadableStream) {
|
||||
const pipe = Readable.fromWeb(stream as any).pipe(proc.stdin)
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
pipe.on('error', reject)
|
||||
pipe.on('finish', resolve)
|
||||
})
|
||||
}
|
|
@ -1,30 +0,0 @@
|
|||
import { Bytes, write } from '@fuman/io'
|
||||
import { $ } from 'zx'
|
||||
|
||||
export async function generateOpusImageBlob(image: Uint8Array) {
|
||||
// todo we should probably not use ffprobe here but whatever lol
|
||||
const proc = $`ffprobe -of json -v error -show_entries stream=codec_name,width,height pipe:0`
|
||||
proc.stdin.write(image)
|
||||
proc.stdin.end()
|
||||
const json = await proc.json()
|
||||
|
||||
const img = json.streams[0]
|
||||
// https://www.rfc-editor.org/rfc/rfc9639.html#section-8.8
|
||||
const mime = img.codec_name === 'mjpeg' ? 'image/jpeg' : 'image/png'
|
||||
const description = 'Cover Artwork'
|
||||
|
||||
const res = Bytes.alloc(image.length + 128)
|
||||
write.uint32be(res, 3) // picture type = album cover
|
||||
write.uint32be(res, mime.length)
|
||||
write.rawString(res, mime)
|
||||
write.uint32be(res, description.length)
|
||||
write.rawString(res, description)
|
||||
write.uint32be(res, img.width)
|
||||
write.uint32be(res, img.height)
|
||||
write.uint32be(res, 0) // color depth
|
||||
write.uint32be(res, 0) // color index (unused, for gifs)
|
||||
write.uint32be(res, image.length)
|
||||
write.bytes(res, image)
|
||||
|
||||
return res.result()
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue