export const MIN_AUDIO_PLAYBACK_RATE = 0.1; export const MAX_AUDIO_PLAYBACK_RATE = 4; export const AUDIO_PLAYBACK_RATE_STEP = 0.1; export type AudioToolRequest = | { kind: "trim"; startSeconds: number; endSeconds: number } | { kind: "speed"; playbackRate: number }; export function normalizeAudioPlaybackRate(value: unknown, fallback = 1) { const parsed = Number.parseFloat(String(value ?? "").replace(/x$/i, "")); const safe = Number.isFinite(parsed) ? parsed : fallback; return Math.round( Math.min(MAX_AUDIO_PLAYBACK_RATE, Math.max(MIN_AUDIO_PLAYBACK_RATE, safe)) * 100, ) / 100; } export function stepAudioPlaybackRate(value: unknown, direction: 1 | -1) { return normalizeAudioPlaybackRate( normalizeAudioPlaybackRate(value) + direction * AUDIO_PLAYBACK_RATE_STEP, ); } export function formatAudioPlaybackRate(value: unknown) { return `${normalizeAudioPlaybackRate(value).toFixed(2)}x`; } export function formatAudioPlaybackRateSuffix(value: unknown) { const rate = normalizeAudioPlaybackRate(value); return Number.isInteger(rate) ? `${rate}x` : `${rate.toFixed(2).replace(/0+$/, "")}x`; } export function getDefaultAudioTrimRange(durationSeconds: unknown) { const duration = Math.max(0.1, Number(durationSeconds) || 0.1); return { startSeconds: 0, endSeconds: Math.min(duration, Math.max(0.1, duration / 3)), }; } export function normalizeAudioTrimRange( durationSeconds: unknown, startSeconds: unknown, endSeconds: unknown, ) { const duration = Math.max(0.1, Number(durationSeconds) || 0.1); const start = Math.min(duration - 0.05, Math.max(0, Number(startSeconds) || 0)); const end = Math.min(duration, Math.max(start + 0.05, Number(endSeconds) || duration)); return { startSeconds: start, endSeconds: end }; } export function getProcessedAudioTitle(sourceTitle: string, request: AudioToolRequest) { const base = String(sourceTitle || "音频").trim() || "音频"; return request.kind === "trim" ? `${base}_截取` : `${base}_${formatAudioPlaybackRateSuffix(request.playbackRate)}`; } export function getAudioDownloadExtension(source: unknown) { const pathname = String(source || "").split(/[?#]/, 1)[0]; return pathname.match(/\.(wav|mp3|m4a|aac|ogg|webm)$/i)?.[1]?.toLowerCase() || "mp3"; }