| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758 |
- import type { AiCoursewareOutline } from "@/api/aiCourseware"
- import type { Slide } from "@/types/slides"
- import type { AiPptQualityIssue } from "@/utils/aiPptQuality"
- // 仅在浏览器检查实际可加载性,不以 URL 格式代替媒体可用性。
- export async function inspectAiPptResources(outline: AiCoursewareOutline, slides: Slide[]): Promise<AiPptQualityIssue[]> {
- const pending = new Map<string, Promise<{ width: number; height: number }>>()
- const issues: AiPptQualityIssue[] = []
- const probe = (url: string, kind: "image" | "audio" | "video") => {
- const key = `${kind}:${url}`
- if (!pending.has(key)) pending.set(key, new Promise((resolve, reject) => {
- const media = kind === "image" ? new Image() : document.createElement(kind)
- const finish = (error?: string) => {
- clearTimeout(timer)
- media.onload = media.onerror = null
- if (media instanceof HTMLMediaElement) {
- media.onloadedmetadata = null
- media.removeAttribute("src")
- media.load()
- }
- if (error) reject(new Error(error))
- else resolve(media instanceof HTMLImageElement ? { width: media.naturalWidth, height: media.naturalHeight } : { width: 0, height: 0 })
- }
- const timer = window.setTimeout(() => finish("加载超时"), 15000)
- media.onerror = () => finish("无法加载或格式不受支持")
- if (media instanceof HTMLMediaElement) {
- media.preload = "metadata"
- media.onloadedmetadata = () => finish()
- } else media.onload = () => finish()
- media.src = url
- }))
- return pending.get(key)!
- }
- await Promise.all(slides.map(async (slide, index) => {
- const scoreUrls = new Set((outline.pages[index]?.resourceBindings || []).filter(item => item.resourceType === "MUSIC").map(item => item.scoreImageUrl))
- await Promise.all(slide.elements.map(async element => {
- const kind = element.type === "image" ? "image" : element.type === "elf" && element.subtype === "elf-audio" ? "audio" : element.type === "elf" && element.subtype === "elf-video" ? "video" : undefined
- if (!kind || !("src" in element)) return
- try {
- const size = await probe(element.src, kind)
- // 谱图完整等比放进原槽位,禁止拉伸音符或裁掉小节。
- if (element.type === "image" && scoreUrls.has(element.src) && size.width && size.height) {
- const scale = Math.min(element.width / size.width, element.height / size.height)
- const width = size.width * scale
- const height = size.height * scale
- element.left += (element.width - width) / 2
- element.top += (element.height - height) / 2
- element.width = width
- element.height = height
- }
- } catch (error) {
- issues.push({ code: "resource_unavailable", severity: "error", pageNo: index + 1,
- message: `${kind === "image" ? "图片" : kind === "audio" ? "音频" : "视频"}${error instanceof Error ? error.message : "不可用"},请更换资源后重试` })
- }
- }))
- }))
- return issues
- }
|