no message

This commit is contained in:
DESKTOP-RQ919RC\Pc
2025-12-10 16:15:17 +08:00
parent 7bf9b3af82
commit 8d664fcbea
4 changed files with 570 additions and 30 deletions

View File

@@ -29,9 +29,10 @@
body { body {
font-family: -apple-system, BlinkMacSystemFont, "SF Pro Display", Roboto, sans-serif; font-family: -apple-system, BlinkMacSystemFont, "SF Pro Display", Roboto, sans-serif;
background: #1a82ea; /* background: #1a82ea; */
color: var(--text-main); color: var(--text-main);
height: 100vh; height: 100dvh;
min-height: 100vh;
overflow: hidden; overflow: hidden;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@@ -46,6 +47,8 @@
height: 100%; height: 100%;
z-index: 1; z-index: 1;
overflow: hidden; overflow: hidden;
background: #1a82ea;
} }
.bg-image { .bg-image {
@@ -435,10 +438,18 @@
scroll-behavior: smooth; scroll-behavior: smooth;
-webkit-mask-image: linear-gradient(to bottom, transparent 0%, black 15%, black 85%, transparent 100%); -webkit-mask-image: linear-gradient(to bottom, transparent 0%, black 15%, black 85%, transparent 100%);
mask-image: linear-gradient(to bottom, transparent 0%, black 15%, black 85%, transparent 100%); mask-image: linear-gradient(to bottom, transparent 0%, black 15%, black 85%, transparent 100%);
scrollbar-width: thin;
scrollbar-color: rgba(255, 255, 255, 0.1) transparent;
} }
.lyrics-scroll::-webkit-scrollbar { .lyrics-scroll::-webkit-scrollbar {
display: none; width: 4px;
background: transparent;
}
.lyrics-scroll::-webkit-scrollbar-thumb {
background: rgba(255, 255, 255, 0.1);
border-radius: 4px;
} }
.lyric-line { .lyric-line {
@@ -497,6 +508,8 @@
order: 3; order: 3;
width: 100%; width: 100%;
padding: 20px 25px 40px; padding: 20px 25px 40px;
padding-bottom: calc(40px + env(safe-area-inset-bottom));
padding-bottom: calc(40px + constant(safe-area-inset-bottom));
background: var(--glass-bg); background: var(--glass-bg);
backdrop-filter: blur(20px) saturate(180%); backdrop-filter: blur(20px) saturate(180%);
-webkit-backdrop-filter: blur(20px) saturate(180%); -webkit-backdrop-filter: blur(20px) saturate(180%);
@@ -714,8 +727,25 @@
opacity: 0.3; opacity: 0.3;
} }
} }
@keyframes recBlink {
0% {
opacity: 1;
}
50% {
opacity: 0.3;
}
100% {
opacity: 1;
}
}
</style> </style>
<script src="https://res.wx.qq.com/open/js/jweixin-1.6.0.js"></script> <script src="https://res.wx.qq.com/open/js/jweixin-1.6.0.js"></script>
<script src="/static/js/ffmpeg.min.js"></script>
<script src="/static/js/html2canvas.min.js"></script>
<script src="/static/js/html-to-image.js"></script>
</head> </head>
<body> <body>
@@ -725,6 +755,9 @@
<div class="loader-text">LOADING...</div> <div class="loader-text">LOADING...</div>
</div> </div>
<div class="main-container">
<!-- 背景层 --> <!-- 背景层 -->
<div class="bg-layer"> <div class="bg-layer">
<div class="bg-image" id="bgImg"></div> <div class="bg-image" id="bgImg"></div>
@@ -732,7 +765,6 @@
<div class="particles" id="particles"></div> <div class="particles" id="particles"></div>
</div> </div>
<div class="main-container">
<!-- 歌名信息 --> <!-- 歌名信息 -->
<div class="header-info"> <div class="header-info">
<div class="header-title" id="songTitle">...</div> <div class="header-title" id="songTitle">...</div>
@@ -838,11 +870,24 @@
<path d="M15 15v2h-2v-2h2zm0-8v2h-2V7h2zm-4 4v2H9v-2h2zm0-4v2H9V7h2zm-4 4v2H5v-2h2zm0-4v2H5V7h2zm12 12H3V3h18v16z" /> <path d="M15 15v2h-2v-2h2zm0-8v2h-2V7h2zm-4 4v2H9v-2h2zm0-4v2H9V7h2zm-4 4v2H5v-2h2zm0-4v2H5V7h2zm12 12H3V3h18v16z" />
</svg> </svg>
</button> </button>
<button class="btn btn-side" id="recToggleBtn" onclick="toggleRecording()" title="录制开关">
<svg width="22" height="22" viewBox="0 0 24 24" fill="currentColor">
<circle cx="12" cy="12" r="6" />
</svg>
</button>
</div> </div>
</div> </div>
</div> </div>
<div id="recIndicator" style="position:fixed;top:12px;right:12px;z-index:999;display:none;align-items:center;gap:6px;background:rgba(0,0,0,0.4);padding:6px 10px;border-radius:16px;color:#fff;backdrop-filter:blur(6px)">
<span style="display:inline-block;width:8px;height:8px;border-radius:50%;background:#ff3b30;box-shadow:0 0 10px #ff3b30;animation:recBlink 1s infinite"></span>
<span style="font-size:12px;letter-spacing:1px">REC</span>
</div>
<audio id="audio" crossorigin="anonymous"></audio> <audio id="audio" crossorigin="anonymous"></audio>
<video id="tabCaptureVideo" playsinline muted style="display:none"></video>
<script> <script>
// 获取参数 // 获取参数
@@ -875,7 +920,10 @@
totalTime: document.getElementById('totalTime'), totalTime: document.getElementById('totalTime'),
lyricsMode: document.getElementById('lyricsMode'), lyricsMode: document.getElementById('lyricsMode'),
lyricsBox: document.getElementById('lyricsBox'), lyricsBox: document.getElementById('lyricsBox'),
miniLyrics: document.getElementById('miniLyrics') miniLyrics: document.getElementById('miniLyrics'),
recIndicator: document.getElementById('recIndicator'),
recToggleBtn: document.getElementById('recToggleBtn'),
tabVideo: document.getElementById('tabCaptureVideo')
}; };
// 初始化 // 初始化
@@ -912,10 +960,22 @@
setTimeout(() => els.loader.remove(), 600); setTimeout(() => els.loader.remove(), 600);
} }
function renderUI(data) { async function renderUI(data) {
els.songTitle.innerText = data.title; els.songTitle.innerText = data.title;
// 预加载图片并转换为 Blob URL (解决跨域和缓存问题)
try {
const imgRes = await fetch(data.img);
const imgBlob = await imgRes.blob();
const imgUrl = URL.createObjectURL(imgBlob);
els.coverImg.src = imgUrl;
els.bgImg.style.backgroundImage = `url('${imgUrl}')`;
} catch (e) {
console.warn('图片缓存失败,使用原始链接', e);
els.coverImg.src = data.img; els.coverImg.src = data.img;
els.bgImg.style.backgroundImage = `url('${data.img}')`; els.bgImg.style.backgroundImage = `url('${data.img}')`;
}
els.audio.src = data.playurl; els.audio.src = data.playurl;
// 图片加载后显示 // 图片加载后显示
@@ -949,6 +1009,7 @@
els.audio.addEventListener('play', () => { els.audio.addEventListener('play', () => {
isPlaying = true; isPlaying = true;
updatePlayState(); updatePlayState();
if (recordEnabled) startSilentMV();
}); });
els.audio.addEventListener('pause', () => { els.audio.addEventListener('pause', () => {
@@ -956,6 +1017,45 @@
updatePlayState(); updatePlayState();
}); });
els.audio.addEventListener('ended', () => {
stopSilentMV();
});
function downloadFile(name, blob) {
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = name;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
}
async function transcodeToMp4(webmBlob, title) {
try {
if (!window.FFmpeg || !FFmpeg.createFFmpeg) {
downloadFile(`${title}.webm`, webmBlob);
msg('已保存 WebM');
return;
}
const { createFFmpeg, fetchFile } = FFmpeg;
const ffmpeg = createFFmpeg({ log: false, corePath: 'https://unpkg.com/@ffmpeg/core@0.12.15/dist/ffmpeg-core.js' });
msg('正在转码为 MP4');
await ffmpeg.load();
ffmpeg.FS('writeFile', 'input.webm', await fetchFile(webmBlob));
await ffmpeg.run('-i', 'input.webm', '-c:v', 'libx264', '-preset', 'veryfast', '-crf', '23', '-c:a', 'aac', '-b:a', '128k', 'output.mp4');
const data = ffmpeg.FS('readFile', 'output.mp4');
const mp4Blob = new Blob([data.buffer], { type: 'video/mp4' });
downloadFile(`${title}.mp4`, mp4Blob);
msg('MP4 已生成');
} catch (e) {
console.error(e);
downloadFile(`${title}.webm`, webmBlob);
msg('转码失败,已保存 WebM');
}
}
function updatePlayState() { function updatePlayState() {
if (isPlaying) { if (isPlaying) {
els.body.classList.add('is-playing'); els.body.classList.add('is-playing');
@@ -1264,6 +1364,423 @@
setTimeout(() => div.remove(), 2000); setTimeout(() => div.remove(), 2000);
} }
let recordEnabled = false;
let mvCanvas = null;
let mvCtx = null;
let mvRecorder = null;
let mvChunks = [];
let mvRenderTimer = null;
let isMVRecording = false;
let mvStream = null;
let mvAudioTrack = null;
let mvRafId = 0;
let displayStream = null;
els.viewWrapper = document.querySelector('.view-wrapper');
els.captureTarget = document.querySelector('.view-wrapper');
const isMobile = /Mobile|Android|iP(hone|od|ad)|IEMobile|BlackBerry|Opera Mini/i.test(navigator.userAgent) || (navigator.maxTouchPoints > 1);
function buildSVGFromElement(el, w, h) {
const styles = Array.from(document.querySelectorAll('style')).map(s => s.textContent).join('\n');
const html = el.outerHTML;
const svg = `<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"${w}\" height=\"${h}\"><foreignObject width=\"100%\" height=\"100%\"><div xmlns=\"http://www.w3.org/1999/xhtml\"><style>${styles}</style>${html}</div></foreignObject></svg>`;
return 'data:image/svg+xml;charset=utf-8,' + encodeURIComponent(svg);
}
// 手动绘制 Canvas 帧(高性能)
function drawCanvasFrame() {
if (!mvCtx || !mvCanvas) return;
const w = mvCanvas.width;
const h = mvCanvas.height;
const ctx = mvCtx;
const dpr = window.devicePixelRatio || 1;
// 清空画布
ctx.clearRect(0, 0, w, h);
// --- 1. 绘制背景 ---
// 模拟 .bg-image 的呼吸效果
ctx.save();
const scale = isPlaying ? (1.1 + Math.sin(Date.now() / 2000) * 0.1) : 1.1; // 呼吸动画
if (els.coverImg.complete && els.coverImg.naturalWidth > 0) {
// 绘制模糊背景图
// 为了性能,不使用 context.filter (部分浏览器支持不佳且慢),而是假设背景是暗色或绘制蒙版
// 这里简单绘制放大的图片并覆盖半透明蒙层
const imgW = w * scale;
const imgH = h * scale;
const x = (w - imgW) / 2;
const y = (h - imgH) / 2;
ctx.drawImage(els.coverImg, x, y, imgW, imgH);
} else {
ctx.fillStyle = '#222';
ctx.fillRect(0, 0, w, h);
}
// 绘制蒙版 .bg-mask
const gradient = ctx.createLinearGradient(0, 0, 0, h);
gradient.addColorStop(0, 'rgba(0, 0, 0, 0.1)');
gradient.addColorStop(0.5, 'rgba(0, 0, 0, 0.5)');
gradient.addColorStop(1, 'rgba(0, 0, 0, 0.9)');
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, w, h);
ctx.restore();
// --- 2. 绘制标题 ---
ctx.save();
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillStyle = '#ffffff';
ctx.shadowColor = 'rgba(0,0,0,0.5)';
ctx.shadowBlur = 10 * dpr;
ctx.shadowOffsetY = 4 * dpr;
ctx.font = `bold ${22 * dpr}px sans-serif`;
// 顶部留出空间,大约 15% 高度处
ctx.fillText(els.songTitle.innerText, w / 2, h * 0.15);
// 绘制 Mini Lyrics (如果不在歌词模式)
if (!isLyricView) {
// 简单绘制一行当前歌词
const activeLine = document.querySelector('.mini-line.active');
if (activeLine) {
ctx.font = `bold ${16 * dpr}px sans-serif`;
ctx.fillStyle = '#d4af37'; // var(--theme-color)
ctx.fillText(activeLine.innerText, w / 2, h * 0.22);
}
}
ctx.restore();
// --- 3. 绘制中间内容 (唱片 或 歌词) ---
const centerX = w / 2;
const centerY = h * 0.5; // 垂直居中
if (!isLyricView) {
// === 唱片模式 ===
const discRadius = Math.min(w, h) * 0.35;
ctx.save();
ctx.translate(centerX, centerY);
// 旋转动画
if (isPlaying) {
const angle = (Date.now() / 5000) * 360 % 360;
ctx.rotate(angle * Math.PI / 180);
} else {
// 暂停时不旋转,保持当前角度 (简化处理直接重置或保持0)
// 为了平滑最好记录上次角度这里简化为0
}
// 唱片外圈 (黑色纹理)
ctx.beginPath();
ctx.arc(0, 0, discRadius, 0, Math.PI * 2);
ctx.fillStyle = '#111';
ctx.fill();
ctx.lineWidth = 6 * dpr;
ctx.strokeStyle = '#080808';
ctx.stroke();
// 唱片纹理 (简单模拟)
ctx.beginPath();
ctx.arc(0, 0, discRadius * 0.9, 0, Math.PI * 2);
ctx.strokeStyle = '#222';
ctx.lineWidth = 2 * dpr;
ctx.stroke();
// 封面图片 (圆形裁切)
const coverRadius = discRadius * 0.65;
ctx.save();
ctx.beginPath();
ctx.arc(0, 0, coverRadius, 0, Math.PI * 2);
ctx.clip();
// 封面跳动效果
const beatScale = isPlaying ? (1 + Math.sin(Date.now() / 200) * 0.01) : 1;
ctx.scale(beatScale, beatScale);
if (els.coverImg.complete) {
ctx.drawImage(els.coverImg, -coverRadius, -coverRadius, coverRadius * 2, coverRadius * 2);
}
ctx.restore();
ctx.restore(); // 结束唱片旋转
// 绘制唱臂 (Needle)
// 唱臂是固定在顶部的,不随唱片旋转,但随播放状态旋转
ctx.save();
// 唱臂支点位置:唱片上方
const pivotX = centerX;
const pivotY = centerY - discRadius - (40 * dpr);
ctx.translate(pivotX, pivotY); // 移动到支点附近
// 唱臂旋转角度:播放时 -5度暂停时 -35度
// 这里坐标系不同,需要调整
// 假设 pivot 在上方,针头向下
const needleAngle = isPlaying ? -5 : -35;
// 调整绘制原点偏移
ctx.translate(0, -50 * dpr); // 模拟 CSS 的 transform-origin
ctx.rotate(needleAngle * Math.PI / 180);
// 绘制唱臂杆
ctx.fillStyle = '#ccc';
ctx.fillRect(-4 * dpr, 0, 8 * dpr, 140 * dpr);
// 绘制唱头
ctx.fillStyle = '#1a1a1a';
ctx.fillRect(-12 * dpr, 140 * dpr, 24 * dpr, 38 * dpr);
// 绘制支点
ctx.beginPath();
ctx.arc(0, 0, 20 * dpr, 0, Math.PI * 2);
ctx.fillStyle = '#e0e0e0';
ctx.fill();
ctx.restore();
} else {
// === 歌词模式 ===
ctx.save();
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
const boxH = h * 0.6;
const startY = (h - boxH) / 2;
// 简单的歌词绘制逻辑:绘制当前句及前后几句
// 找到 active 的歌词
let activeIndex = lyricLines.findIndex(l => Math.abs(l.time - els.audio.currentTime) < 0.5);
// 如果没找到精确匹配,找最近的一个过去的时间
if (activeIndex === -1) {
activeIndex = lyricLines.filter(l => l.time <= els.audio.currentTime).length - 1;
}
if (activeIndex === -1) activeIndex = 0;
const lineHeight = 40 * dpr;
const maxLines = 7; // 显示行数
for (let i = -3; i <= 3; i++) {
const idx = activeIndex + i;
if (idx >= 0 && idx < lyricLines.length) {
const line = lyricLines[idx];
const y = centerY + i * lineHeight;
if (i === 0) {
// 当前句
ctx.font = `bold ${22 * dpr}px sans-serif`;
ctx.fillStyle = '#d4af37';
ctx.shadowColor = 'rgba(212, 175, 55, 0.4)';
ctx.shadowBlur = 10;
} else {
// 其他句
ctx.font = `${16 * dpr}px sans-serif`;
ctx.fillStyle = 'rgba(255, 255, 255, 0.4)';
ctx.shadowBlur = 0;
}
ctx.fillText(line.el.innerText, centerX, y);
}
}
ctx.restore();
}
// --- 4. 绘制底部进度条 ---
const barY = h * 0.85;
const barWidth = w * 0.8;
const barX = (w - barWidth) / 2;
// 总进度条背景
ctx.fillStyle = 'rgba(255, 255, 255, 0.15)';
ctx.beginPath();
ctx.roundRect(barX, barY, barWidth, 4 * dpr, 2 * dpr);
ctx.fill();
// 当前进度
const duration = els.audio.duration || 1;
const progress = els.audio.currentTime / duration;
const currentBarWidth = barWidth * progress;
// 进度填充
const gradBar = ctx.createLinearGradient(barX, 0, barX + currentBarWidth, 0);
gradBar.addColorStop(0, '#fff');
gradBar.addColorStop(1, '#d4af37');
ctx.fillStyle = gradBar;
ctx.beginPath();
ctx.roundRect(barX, barY, currentBarWidth, 4 * dpr, 2 * dpr);
ctx.fill();
// 进度点
ctx.beginPath();
ctx.arc(barX + currentBarWidth, barY + 2 * dpr, 6 * dpr, 0, Math.PI * 2);
ctx.fillStyle = '#fff';
ctx.shadowColor = 'rgba(255, 255, 255, 0.8)';
ctx.shadowBlur = 10 * dpr;
ctx.fill();
// 时间文字
ctx.fillStyle = 'rgba(255, 255, 255, 0.5)';
ctx.font = `${11 * dpr}px monospace`;
ctx.textAlign = 'left';
ctx.fillText(els.currTime.innerText, barX, barY - 15 * dpr);
ctx.textAlign = 'right';
ctx.fillText(els.totalTime.innerText, barX + barWidth, barY - 15 * dpr);
// --- 5. 绘制音频波形 (可选) ---
// 简单模拟
if (isPlaying) {
ctx.save();
ctx.translate(centerX, barY - 40 * dpr);
ctx.fillStyle = '#fff';
for (let i = -2; i <= 2; i++) {
const hWave = Math.random() * 16 * dpr;
ctx.fillRect(i * 6 * dpr, -hWave, 3 * dpr, hWave);
}
ctx.restore();
}
}
async function startSilentMV() {
if (!recordEnabled || isMVRecording) return;
// if (isMobile) { msg('移动端暂不支持录制'); return; }
// 初始化 Canvas (基于 .main-container 大小)
// 注意:这里使用固定比例或窗口大小,为了清晰度,使用 devicePixelRatio
const rect = els.mainContainer ? els.mainContainer.getBoundingClientRect() : document.body.getBoundingClientRect();
const dpr = window.devicePixelRatio || 1;
// 创建新的 Canvas 实例用于录制
mvCanvas = document.createElement('canvas');
mvCtx = mvCanvas.getContext('2d');
// 设置 Canvas 尺寸
mvCanvas.width = rect.width * dpr;
mvCanvas.height = rect.height * dpr;
// 启动渲染循环html-to-image 截图
// 目标帧率 120 FPS不再使用 setTimeout 限流,而是全力渲染
const targetFPS = 120;
async function renderLoop() {
if (!isMVRecording) return;
// 标记开始处理
const beginTime = Date.now();
try {
const source = document.querySelector('.main-container');
if (source) {
// 恢复高清录制,限制最大 dpr 为 2 以平衡性能
const dpr = Math.min(window.devicePixelRatio || 1, 2);
const canvas = await htmlToImage.toCanvas(source, {
backgroundColor: null,
pixelRatio: dpr,
skipAutoScale: true,
cacheBust: false,
fontEmbedCSS: '', // 禁用字体嵌入,防止崩溃
filter: (node) => {
// 过滤无效图片防止崩溃
if (node.tagName === 'IMG' && (!node.src || node.src === window.location.href)) return false;
return true;
},
style: {
transform: 'translateZ(0)'
}
});
// 将捕捉到的画面绘制到录制画布上
mvCtx.clearRect(0, 0, mvCanvas.width, mvCanvas.height);
mvCtx.drawImage(canvas, 0, 0, mvCanvas.width, mvCanvas.height);
}
} catch (e) {
console.error('Frame capture error:', e);
}
// 不再使用 setTimeout 进行延时,避免 JS 定时器精度问题导致的抖动
// 直接请求下一帧,让浏览器决定最佳时机(通常是 60Hz如果设备支持高刷则更高
// 这样可以消除人为引入的卡顿
if (isMVRecording) {
mvRafId = requestAnimationFrame(renderLoop);
}
}
mvRafId = requestAnimationFrame(renderLoop);
mvStream = mvCanvas.captureStream(targetFPS);
// 添加音频轨道
const audioEl = document.getElementById('audio');
if (audioEl) {
try {
let audioStream;
if (audioEl.captureStream) {
audioStream = audioEl.captureStream();
} else if (audioEl.mozCaptureStream) {
audioStream = audioEl.mozCaptureStream();
}
if (audioStream) {
const audioTrack = audioStream.getAudioTracks()[0];
if (audioTrack) {
mvStream.addTrack(audioTrack);
}
}
} catch (e) {
console.warn('Audio capture failed:', e);
}
}
mvChunks = [];
const optsList = [
{ mimeType: 'video/mp4;codecs=h264,aac' },
{ mimeType: 'video/mp4' },
{ mimeType: 'video/webm;codecs=vp9,opus' },
{ mimeType: 'video/webm;codecs=vp8,opus' },
{ mimeType: 'video/webm' }
];
let opts = {};
for (const o of optsList) {
if (MediaRecorder.isTypeSupported(o.mimeType)) {
opts = {
...o,
videoBitsPerSecond: 25000000 // 25 Mbps 高码率,确保视频高清
};
break;
}
}
mvRecorder = new MediaRecorder(mvStream, opts);
mvRecorder.ondataavailable = e => { if (e.data && e.data.size) mvChunks.push(e.data); };
mvRecorder.onstop = async () => {
const title = (musicData.title || 'mv').replace(/\s+/g, '_');
const blob = new Blob(mvChunks, { type: mvRecorder.mimeType || 'video/webm' });
const type = mvRecorder.mimeType || 'video/webm';
if (/mp4/i.test(type)) {
downloadFile(`${title}.mp4`, blob);
} else {
await transcodeToMp4(blob, title);
}
};
mvRecorder.start();
isMVRecording = true;
if (els.recIndicator) els.recIndicator.style.display = 'flex';
if (els.recToggleBtn) els.recToggleBtn.style.color = '#ff3b30';
}
function stopSilentMV() {
if (!isMVRecording) return;
try { mvRecorder && mvRecorder.stop(); } catch (_) { }
try { if (mvStream) mvStream.getTracks().forEach(t => t.stop()); } catch (_) { }
try { if (displayStream) displayStream.getTracks().forEach(t => t.stop()); } catch (_) { }
if (mvRafId) cancelAnimationFrame(mvRafId);
mvRafId = 0;
clearInterval(mvRenderTimer);
mvRenderTimer = null;
isMVRecording = false;
if (els.recIndicator) els.recIndicator.style.display = 'none';
if (els.recToggleBtn) els.recToggleBtn.style.color = '';
}
function toggleRecording() {
recordEnabled = !recordEnabled;
if (recordEnabled && isPlaying) startSilentMV();
if (!recordEnabled && isMVRecording) stopSilentMV();
}
init(); init();
</script> </script>
</body> </body>

1
static/js/ffmpeg.min.js vendored Normal file
View File

@@ -0,0 +1 @@
!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.FFmpegWASM=t():e.FFmpegWASM=t()}(self,()=>(()=>{"use strict";var i={m:{},d:(e,t)=>{for(var s in t)i.o(t,s)&&!i.o(e,s)&&Object.defineProperty(e,s,{enumerable:!0,get:t[s]})},u:e=>e+".ffmpeg.js"};i.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(e){if("object"==typeof window)return window}}(),i.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t),i.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},i.g.importScripts&&(e=i.g.location+"");var e,t=i.g.document;if(!e&&t&&!(e=t.currentScript?t.currentScript.src:e)){var s=t.getElementsByTagName("script");if(s.length)for(var a=s.length-1;-1<a&&!e;)e=s[a--].src}if(!e)throw new Error("Automatic publicPath is not supported in this browser");e=e.replace(/#.*$/,"").replace(/\?.*$/,"").replace(/\/[^\/]+$/,"/"),i.p=e,i.b=document.baseURI||self.location.href;var r,o,n,t={};i.r(t),i.d(t,{FFFSType:()=>o,FFmpeg:()=>c}),(n=r=r||{}).LOAD="LOAD",n.EXEC="EXEC",n.FFPROBE="FFPROBE",n.WRITE_FILE="WRITE_FILE",n.READ_FILE="READ_FILE",n.DELETE_FILE="DELETE_FILE",n.RENAME="RENAME",n.CREATE_DIR="CREATE_DIR",n.LIST_DIR="LIST_DIR",n.DELETE_DIR="DELETE_DIR",n.ERROR="ERROR",n.DOWNLOAD="DOWNLOAD",n.PROGRESS="PROGRESS",n.LOG="LOG",n.MOUNT="MOUNT",n.UNMOUNT="UNMOUNT";const E=(()=>{let e=0;return()=>e++})(),p=(new Error("unknown message type"),new Error("ffmpeg is not loaded, call `await ffmpeg.load()` first")),d=new Error("called FFmpeg.terminate()");new Error("failed to import ffmpeg-core.js");class c{#e=null;#t={};#s={};#r=[];#a=[];loaded=!1;#o=()=>{this.#e&&(this.#e.onmessage=({data:{id:e,type:t,data:s}})=>{switch(t){case r.LOAD:this.loaded=!0,this.#t[e](s);break;case r.MOUNT:case r.UNMOUNT:case r.EXEC:case r.FFPROBE:case r.WRITE_FILE:case r.READ_FILE:case r.DELETE_FILE:case r.RENAME:case r.CREATE_DIR:case r.LIST_DIR:case r.DELETE_DIR:this.#t[e](s);break;case r.LOG:this.#r.forEach(e=>e(s));break;case r.PROGRESS:this.#a.forEach(e=>e(s));break;case r.ERROR:this.#s[e](s)}delete this.#t[e],delete this.#s[e]})};#i=({type:i,data:a},r=[],o)=>this.#e?new Promise((e,t)=>{const s=E();this.#e&&this.#e.postMessage({id:s,type:i,data:a},r),this.#t[s]=e,this.#s[s]=t,o?.addEventListener("abort",()=>{t(new DOMException(`Message # ${s} was aborted`,"AbortError"))},{once:!0})}):Promise.reject(p);on(e,t){"log"===e?this.#r.push(t):"progress"===e&&this.#a.push(t)}off(e,t){"log"===e?this.#r=this.#r.filter(e=>e!==t):"progress"===e&&(this.#a=this.#a.filter(e=>e!==t))}load=({classWorkerURL:e,...t}={},{signal:s}={})=>(this.#e||(this.#e=e?new Worker(new URL(e,"file:///Users/focus/Projects/ffmpeg.wasm/packages/ffmpeg/dist/esm/classes.js"),{type:"module"}):new Worker(new URL(i.p+i.u(814),i.b),{type:void 0}),this.#o()),this.#i({type:r.LOAD,data:t},void 0,s));exec=(e,t=-1,{signal:s}={})=>this.#i({type:r.EXEC,data:{args:e,timeout:t}},void 0,s);ffprobe=(e,t=-1,{signal:s}={})=>this.#i({type:r.FFPROBE,data:{args:e,timeout:t}},void 0,s);terminate=()=>{for(const e of Object.keys(this.#s))this.#s[e](d),delete this.#s[e],delete this.#t[e];this.#e&&(this.#e.terminate(),this.#e=null,this.loaded=!1)};writeFile=(e,t,{signal:s}={})=>{const i=[];return t instanceof Uint8Array&&i.push(t.buffer),this.#i({type:r.WRITE_FILE,data:{path:e,data:t}},i,s)};mount=(e,t,s)=>this.#i({type:r.MOUNT,data:{fsType:e,options:t,mountPoint:s}},[]);unmount=e=>this.#i({type:r.UNMOUNT,data:{mountPoint:e}},[]);readFile=(e,t="binary",{signal:s}={})=>this.#i({type:r.READ_FILE,data:{path:e,encoding:t}},void 0,s);deleteFile=(e,{signal:t}={})=>this.#i({type:r.DELETE_FILE,data:{path:e}},void 0,t);rename=(e,t,{signal:s}={})=>this.#i({type:r.RENAME,data:{oldPath:e,newPath:t}},void 0,s);createDir=(e,{signal:t}={})=>this.#i({type:r.CREATE_DIR,data:{path:e}},void 0,t);listDir=(e,{signal:t}={})=>this.#i({type:r.LIST_DIR,data:{path:e}},void 0,t);deleteDir=(e,{signal:t}={})=>this.#i({type:r.DELETE_DIR,data:{path:e}},void 0,t)}return(n=o=o||{}).MEMFS="MEMFS",n.NODEFS="NODEFS",n.NODERAWFS="NODERAWFS",n.IDBFS="IDBFS",n.WORKERFS="WORKERFS",n.PROXYFS="PROXYFS",t})());

File diff suppressed because one or more lines are too long

20
static/js/html2canvas.min.js vendored Normal file

File diff suppressed because one or more lines are too long