chore: update public repo

This commit is contained in:
desu-bot 2025-01-14 02:38:00 +00:00
parent e0109980c0
commit e7c9507247
No known key found for this signature in database
25 changed files with 5364 additions and 0 deletions

View file

@ -0,0 +1,58 @@
import { iter } from '@fuman/utils'
import { z } from 'zod'
import { minimist, question } from 'zx'
import { downloadFile, ffetch } from '../../utils/fetch.ts'
const args = minimist(process.argv.slice(2), {
string: ['filename'],
})
const query = args._[0] ?? await question('Search query (Artist - Album): ')
const data = await ffetch('https://api.deezer.com/search', {
query: {
q: query,
limit: 15,
},
}).parsedJson(z.object({
data: z.array(z.object({
type: z.literal('track'),
title: z.string(),
artist: z.object({
name: z.string(),
}),
album: z.object({
id: z.number(),
title: z.string(),
cover_xl: z.string(),
}),
})),
}))
const groupedByAlbum = new Map<number, typeof data['data']>()
for (const result of data.data) {
const albumId = result.album.id
if (!groupedByAlbum.has(albumId)) {
groupedByAlbum.set(albumId, [])
}
groupedByAlbum.get(albumId)!.push(result)
}
const idxToAlbum = new Map<number, number>()
for (const [idx, [id, tracks]] of iter.enumerate(groupedByAlbum.entries())) {
idxToAlbum.set(idx, id)
console.log(`${idx + 1}. ${tracks[0].artist.name} - ${tracks[0].album.title}`)
for (const track of tracks) {
console.log(` ${track.title}`)
}
}
console.log('Enter number to download album art:')
const number = Number.parseInt(await question('[1] > ') || '1')
const artworkUrl = groupedByAlbum.get(idxToAlbum.get(number - 1)!)![0].album.cover_xl
await downloadFile(artworkUrl, args.filename ?? `assets/${query.replace(/\s/g, '_')}.jpg`)

View file

@ -0,0 +1,129 @@
import { rm } from 'node:fs/promises'
import { $, question } from 'zx'
import { fileExists } from '../../utils/fs.ts'
let filename = await question('filename >')!
const startTs = await question('start timestamp >')
const endTs = await question('end timestamp >')
const outputFilename = await question('output filename [output.mp4] >') || 'assets/output.mp4'
if (filename[0] === '\'' && filename[filename.length - 1] === '\'') {
filename = filename.slice(1, -1)
}
const ffprobe = await $`ffprobe -v error -show_entries stream=codec_type,codec_name,index:stream_tags=title,language -of json ${filename}`.json()
async function chooseStream(type: string, options: any[], allowNone = false) {
console.log(`Found ${type} streams:`)
for (let i = 0; i < options.length; i++) {
const stream = options[i]
console.log(`[${i + 1}] (${stream.codec_name}, ${stream.tags.language}) ${stream.tags.title}`)
}
if (allowNone) {
console.log(`[0] No ${type}`)
}
const res = await question(`select ${type} >`) || '0'
if (res === '0' && allowNone) {
return null
}
const streamIndex = Number.parseInt(res)
if (Number.isNaN(streamIndex) || streamIndex < 1 || streamIndex > options.length) {
console.error('Invalid input')
process.exit(1)
}
return streamIndex - 1
}
const allVideos = ffprobe.streams.filter(stream => stream.codec_type === 'video')
const allAudios = ffprobe.streams.filter(stream => stream.codec_type === 'audio')
const allSubtitles = ffprobe.streams.filter(stream => stream.codec_type === 'subtitle')
let videoStream: number | null = null
let audioStream: number | null = null
let subtitleStream: number | null = null
if (allVideos.length > 1) {
videoStream = await chooseStream('video', allVideos)
} else if (allVideos.length > 0) {
videoStream = 0
} else {
console.error('No video streams found')
process.exit(1)
}
if (allAudios.length > 1) {
audioStream = await chooseStream('audio', allAudios)
} else if (allAudios.length > 0) {
audioStream = 0
} else {
console.warn('No audio streams found, proceeding without audio')
}
if (allSubtitles.length > 0) {
subtitleStream = await chooseStream('subtitle', allSubtitles, true)
}
const args: string[] = [
'-i',
filename,
'-c:v',
'libx264',
'-map',
`0:v:${videoStream}`,
'-c:v',
'libx264',
]
if (audioStream !== null) {
args.push('-map', `0:a:${audioStream}`)
}
if (subtitleStream !== null) {
const filenameEscaped = filename.replace(/'/g, "'\\\\\\''")
args.push('-vf', `format=yuv420p,subtitles='${filenameEscaped}':si=${subtitleStream}`)
} else {
args.push('-vf', 'format=yuv420p')
}
if (audioStream !== null) {
args.push('-c:a', 'libopus')
if (allAudios[audioStream].codec_name === 'flac') {
args.push('-b:a', '320k')
}
}
args.push(
'-ss',
startTs!,
'-to',
endTs!,
outputFilename,
)
if (await fileExists(outputFilename)) {
const overwrite = await question('Output file already exists, overwrite? [y/N] >')
if (overwrite?.toLowerCase() !== 'y') {
process.exit(0)
}
await rm(outputFilename)
}
try {
$.env.AV_LOG_FORCE_COLOR = 'true'
await $`ffmpeg ${args}`
} catch (e) {
process.exit(1)
}
const openDir = await question('open output directory? [Y/n] >')
if (!openDir || openDir?.toLowerCase() === 'y') {
await $`open -R ${outputFilename}`
}

View file

@ -0,0 +1,46 @@
import { iter } from '@fuman/utils'
import { z } from 'zod'
import { minimist, question } from 'zx'
import { downloadFile, ffetch } from '../../utils/fetch.ts'
const args = minimist(process.argv.slice(2), {
string: ['entity', 'filename'],
})
const entity = args.entity ?? 'album'
const query = args._[0] ?? await question('Search query (Artist - Album): ')
const data = await ffetch('https://itunes.apple.com/search', {
query: {
term: query,
entity,
limit: 15,
},
}).parsedJson(z.object({
results: z.array(z.object({
kind: z.literal('song').optional(),
artistName: z.string(),
collectionName: z.string(),
artworkUrl100: z.string(),
releaseDate: z.string(),
trackName: z.string().optional(),
}).passthrough()),
}))
for (const [i, result] of iter.enumerate(data.results)) {
if (result.kind === 'song') {
console.log(`${i + 1}. ${result.artistName} - ${result.trackName} (${result.collectionName}, ${new Date(result.releaseDate).toLocaleDateString('ru-RU')})`)
continue
}
console.log(`${i + 1}. ${result.artistName} - ${result.collectionName} (${new Date(result.releaseDate).toLocaleDateString('ru-RU')})`)
}
console.log('Enter number to download album art:')
const number = Number.parseInt(await question('[1] > ') || '1')
const artworkUrl = data.results[number - 1].artworkUrl100.replace('100x100', '1500x1500')
await downloadFile(artworkUrl, args.filename ?? `assets/${query.replace(/\s/g, '_')}.jpg`)

View file

@ -0,0 +1,63 @@
import { iter } from '@fuman/utils'
import { z } from 'zod'
import { minimist, question } from 'zx'
import { downloadFile, ffetch } from '../../utils/fetch.ts'
const args = minimist(process.argv.slice(2), {
string: ['filename'],
})
const query = args._[0] ?? await question('Search query: ')
const data = await ffetch('https://itunes.apple.com/search', {
query: {
term: query,
entity: 'musicArtist',
limit: 15,
},
}).parsedJson(z.object({
results: z.array(z.object({
wrapperType: z.literal('artist'),
artistName: z.string(),
artistLinkUrl: z.string(),
primaryGenreName: z.string().default('Unknown'),
}).passthrough()),
}))
for (const [i, result] of iter.enumerate(data.results)) {
console.log(`${i + 1}. ${result.artistName} (${result.primaryGenreName})`)
continue
}
console.log('Enter number to download artist art:')
const number = Number.parseInt(await question('[1] > ') || '1')
const pageUrl = data.results[number - 1].artistLinkUrl
const $ = await ffetch(pageUrl).cheerio()
const pageData = JSON.parse($('#serialized-server-data').html()!)
const pageDataValidated = z.tuple([
z.object({
data: z.object({
seoData: z.object({
artworkUrl: z.string(),
}),
}),
}),
]).parse(pageData)
// {w}x{h}{c}.{f}
const artworkUrl = pageDataValidated[0].data.seoData.artworkUrl
.replace('{w}', '2500')
.replace('{h}', '2500')
.replace('{c}', 'cc')
.replace('{f}', 'jpg')
if (artworkUrl === '/assets/meta/apple-music.png') {
console.log('No artwork available')
process.exit(1)
}
await downloadFile(artworkUrl, args.filename ?? `assets/${query.replace(/\s/g, '_')}.jpg`)