mirror of
https://git.stupid.fish/teidesu/scripts.git
synced 2025-11-27 16:31:25 +11:00
chore: update public repo
This commit is contained in:
parent
261c7eefa0
commit
171ba5de7a
3 changed files with 520 additions and 3 deletions
123
utils/mpd.ts
Normal file
123
utils/mpd.ts
Normal file
|
|
@ -0,0 +1,123 @@
|
|||
import { assert, ConditionVariable } from '@fuman/utils'
|
||||
import { load } from 'cheerio'
|
||||
import { ffetch } from './fetch.ts'
|
||||
import { writeWebStreamToFile } from './fs.ts'
|
||||
|
||||
interface SimpleMpd {
|
||||
codecs: string
|
||||
initUrl: string
|
||||
segmentUrls: string[]
|
||||
}
|
||||
|
||||
export function parseSimpleMpd(xml: string): SimpleMpd {
|
||||
const $ = load(xml, { xml: true })
|
||||
|
||||
const period = $('Period')
|
||||
assert(period.length === 1, 'expected exactly one period')
|
||||
|
||||
const adaptations = period.find('AdaptationSet')
|
||||
assert(adaptations.length === 1, 'expected exactly one adaptation set')
|
||||
|
||||
const representation = adaptations.find('Representation')
|
||||
assert(representation.length === 1, 'expected exactly one representation')
|
||||
|
||||
const segmentTemplate = representation.find('SegmentTemplate')
|
||||
assert(segmentTemplate.length === 1, 'expected exactly one segment template')
|
||||
|
||||
const initUrl = segmentTemplate.attr('initialization')
|
||||
const templateUrl = segmentTemplate.attr('media')
|
||||
const startNum = segmentTemplate.attr('startNumber')
|
||||
|
||||
assert(initUrl !== undefined, 'expected initialization url')
|
||||
assert(templateUrl !== undefined, 'expected template url')
|
||||
assert(!templateUrl.match(/\$(RepresentationID|Bandwidth|Time)\$/), 'unsupported template url')
|
||||
assert(startNum !== undefined, 'expected start number')
|
||||
|
||||
const timeline = segmentTemplate.find('SegmentTimeline')
|
||||
assert(timeline.length === 1, 'expected exactly one segment timeline')
|
||||
|
||||
const segments = timeline.find('S')
|
||||
assert(segments.length > 0, 'expected at least one segment')
|
||||
|
||||
const segmentUrls: string[] = []
|
||||
|
||||
let segmentNum = Number(startNum)
|
||||
for (const segment of segments) {
|
||||
const duration = $(segment).attr('d')
|
||||
assert(duration !== undefined, 'expected duration')
|
||||
const r = $(segment).attr('r')
|
||||
const repeats = r ? Number.parseInt(r) + 1 : 1
|
||||
|
||||
for (let i = 0; i < repeats; i++) {
|
||||
segmentUrls.push(templateUrl.replace('$Number$', String(segmentNum)))
|
||||
segmentNum++
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
codecs: representation.attr('codecs')!,
|
||||
initUrl,
|
||||
segmentUrls,
|
||||
}
|
||||
}
|
||||
|
||||
export function concatMpdSegments(options: {
|
||||
mpd: SimpleMpd
|
||||
fetch: (url: string) => Promise<Uint8Array>
|
||||
poolSize?: number
|
||||
}): ReadableStream {
|
||||
const { mpd, fetch, poolSize = 8 } = options
|
||||
|
||||
let nextSegmentIdx = -1
|
||||
let nextWorkerSegmentIdx = -1
|
||||
const nextSegmentCv = new ConditionVariable()
|
||||
const buffer: Record<number, Uint8Array> = {}
|
||||
|
||||
const downloadSegment = async (idx = nextWorkerSegmentIdx++) => {
|
||||
// console.log('downloading segment %s', idx)
|
||||
const url = idx === -1 ? mpd.initUrl : mpd.segmentUrls[idx]
|
||||
const chunk = await fetch(url)
|
||||
buffer[idx] = chunk
|
||||
|
||||
if (idx === nextSegmentIdx) {
|
||||
nextSegmentCv.notify()
|
||||
}
|
||||
|
||||
if (nextWorkerSegmentIdx < mpd.segmentUrls.length) {
|
||||
return downloadSegment()
|
||||
}
|
||||
}
|
||||
|
||||
let error: unknown
|
||||
void Promise.all(Array.from({
|
||||
length: Math.min(poolSize, mpd.segmentUrls.length),
|
||||
}, downloadSegment))
|
||||
.catch((e) => {
|
||||
error = e
|
||||
nextSegmentCv.notify()
|
||||
})
|
||||
|
||||
return new ReadableStream({
|
||||
async start(controller) {
|
||||
while (true) {
|
||||
await nextSegmentCv.wait()
|
||||
if (error) {
|
||||
controller.error(error)
|
||||
return
|
||||
}
|
||||
|
||||
while (nextSegmentIdx in buffer) {
|
||||
const buf = buffer[nextSegmentIdx]
|
||||
delete buffer[nextSegmentIdx]
|
||||
nextSegmentIdx++
|
||||
controller.enqueue(buf)
|
||||
}
|
||||
|
||||
if (nextSegmentIdx >= mpd.segmentUrls.length) {
|
||||
controller.close()
|
||||
return
|
||||
}
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue