<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>FLAC 重命名工具 · 读取内嵌元数据</title> <style> * { margin: 0; padding: 0; box-sizing: border-box; } body { background: #f5f0e8; font-family: 'Segoe UI', system-ui, sans-serif; min-height: 100vh; display: flex; justify-content: center; align-items: center; padding: 1.5rem; } .container { max-width: 850px; width: 100%; background: #fffdf7; border-radius: 1.8rem; box-shadow: 0 20px 40px rgba(0,0,0,0.1); padding: 2rem; } h1 { font-size: 2rem; color: #4a3724; display: flex; align-items: center; gap: 0.5rem; margin-bottom: 0.3rem; } .badge { background: #d4b896; color: #2d1f0e; font-size: 0.85rem; padding: 0.3rem 1rem; border-radius: 20px; font-weight: 600; } .desc { color: #6b5d4b; margin-bottom: 1.5rem; font-size: 0.95rem; border-left: 3px solid #c9a87c; padding-left: 1rem; } .drop-area { background: #faf7f1; border: 2px dashed #c8b28b; border-radius: 1.5rem; padding: 2.5rem; text-align: center; cursor: pointer; transition: all 0.2s; margin-bottom: 1.5rem; } .drop-area:hover, .drop-area.active { border-color: #a0845c; background: #f5ede0; box-shadow: 0 0 0 4px rgba(180,140,90,0.1); } .drop-area .icon { font-size: 3rem; margin-bottom: 0.5rem; } .drop-area .main-text { font-weight: 600; color: #4d3a24; font-size: 1.1rem; } .drop-area .sub-text { color: #8b7a62; font-size: 0.9rem; margin-top: 0.2rem; } input[type="file"] { display: none; } .file-panel { background: #fefcf8; border: 1px solid #e3d5bd; border-radius: 1.2rem; padding: 1rem; max-height: 380px; overflow-y: auto; margin: 1.2rem 0; display: none; } .file-row { display: flex; align-items: center; gap: 0.8rem; padding: 0.7rem 0.5rem; border-bottom: 1px solid #efe4d0; flex-wrap: wrap; } .file-row:last-child { border-bottom: none; } .orig { font-family: 'Consolas', 'Monaco', monospace; background: #f3ecdd; padding: 0.3rem 0.8rem; border-radius: 20px; font-size: 0.85rem; color: #5c4a30; word-break: break-all; flex: 1; min-width: 130px; } .arrow { color: #b39260; font-weight: bold; font-size: 1.2rem; } .newname { font-family: 'Consolas', 'Monaco', monospace; background: #e2edda; padding: 0.3rem 0.8rem; border-radius: 20px; font-size: 0.85rem; color: #2d4a1e; font-weight: 600; word-break: break-all; flex: 1; min-width: 130px; text-align: right; } .newname.missing { background: #ffe8e0; color: #a04030; font-style: italic; } .meta-detail { font-size: 0.7rem; color: #8b7356; background: #f9f4ea; padding: 0.15rem 0.6rem; border-radius: 12px; white-space: nowrap; } .actions { display: flex; gap: 0.8rem; flex-wrap: wrap; align-items: center; } button { padding: 0.8rem 1.8rem; border-radius: 2rem; border: 1px solid #d4bc92; background: #f1e7d4; color: #4d3a22; font-weight: 600; cursor: pointer; font-size: 0.95rem; transition: 0.2s; display: flex; align-items: center; gap: 0.3rem; } button:hover:not(:disabled) { background: #e5d3b0; } button.primary { background: #c7a16b; border-color: #9c7a4a; color: #fffdf5; box-shadow: 0 4px 12px rgba(160,120,50,0.2); } button.primary:hover:not(:disabled) { background: #b38845; } button:disabled { opacity: 0.45; cursor: not-allowed; } .status { margin-left: auto; font-size: 0.9rem; color: #6b5d48; background: #f6f1e6; padding: 0.4rem 1.2rem; border-radius: 20px; } .footer { margin-top: 1rem; font-size: 0.8rem; color: #9b8b74; text-align: center; } </style> </head> <body> <div class="container"> <h1>🎵 FLAC 元数据重命名 <span class="badge">标题-作曲家</span></h1> <div class="desc">读取 FLAC 文件内嵌的歌曲标题 (TITLE) 和作曲家 (COMPOSER/ARTIST) 标签,自动重命名为 "标题-作曲家.flac"</div> <div class="drop-area" id="dropZone"> <div class="icon">📂</div> <div class="main-text">点击选择或拖拽 FLAC 文件</div> <div class="sub-text">支持批量 · 读取内嵌元数据标签</div> </div> <input type="file" id="fileInput" accept=".flac,audio/flac" multiple> <div class="file-panel" id="filePanel"> <div id="fileList"></div> </div> <div class="actions"> <button id="clearBtn" disabled>🗑️ 清空</button> <button id="renameBtn" class="primary" disabled>💾 下载重命名文件</button> <span class="status" id="status">等待添加 FLAC 文件...</span> </div> <div class="footer">* 浏览器安全限制:通过下载方式生成新文件名,原文件不会被修改</div> </div> <script> (function() { const dropZone = document.getElementById('dropZone'); const fileInput = document.getElementById('fileInput'); const filePanel = document.getElementById('filePanel'); const fileListDiv = document.getElementById('fileList'); const clearBtn = document.getElementById('clearBtn'); const renameBtn = document.getElementById('renameBtn'); const statusEl = document.getElementById('status'); let filesData = []; // { file, title, composer, newName, error } // ========== 解析 FLAC 内嵌元数据 (Vorbis Comment) ========== async function readFlacMetadata(file) { return new Promise((resolve) => { const reader = new FileReader(); reader.onload = (e) => { try { const buf = e.target.result; const view = new DataView(buf); if (buf.byteLength < 4) return resolve({ title: null, composer: null, error: '文件过小' }); const magic = String.fromCharCode(view.getUint8(0), view.getUint8(1), view.getUint8(2), view.getUint8(3)); if (magic !== 'fLaC') return resolve({ title: null, composer: null, error: '非FLAC文件' }); let offset = 4; let lastBlock = false; let title = null; let composer = null; while (offset < buf.byteLength && !lastBlock) { if (offset + 4 > buf.byteLength) break; const header = view.getUint8(offset); lastBlock = (header & 0x80) !== 0; const blockType = header & 0x7F; const blockSize = (view.getUint8(offset+1) << 16) | (view.getUint8(offset+2) << 8) | view.getUint8(offset+3); offset += 4; if (blockType === 4 && offset + blockSize <= buf.byteLength) { // VORBIS_COMMENT 块 const block = new Uint8Array(buf, offset, blockSize); const dec = new TextDecoder('utf-8'); if (block.length < 4) { offset += blockSize; continue; } const vendorLen = block[0] | (block[1]<<8) | (block[2]<<16) | (block[3]<<24); let pos = 4 + vendorLen; if (pos + 4 > block.length) { offset += blockSize; continue; } const numComments = block[pos] | (block[pos+1]<<8) | (block[pos+2]<<16) | (block[pos+3]<<24); pos += 4; for (let i = 0; i < numComments; i++) { if (pos + 4 > block.length) break; const commentLen = block[pos] | (block[pos+1]<<8) | (block[pos+2]<<16) | (block[pos+3]<<24); pos += 4; if (pos + commentLen > block.length) break; const commentStr = dec.decode(block.slice(pos, pos + commentLen)); pos += commentLen; const eqIdx = commentStr.indexOf('='); if (eqIdx > 0) { const key = commentStr.substring(0, eqIdx).toUpperCase().trim(); const val = commentStr.substring(eqIdx + 1).trim(); if (key === 'TITLE' && !title) title = val; // 优先 COMPOSER,其次 ARTIST if (key === 'COMPOSER' && !composer) composer = val; if (key === 'ARTIST' && !composer) composer = val; } } // 找到注释块即可退出 break; } offset += blockSize; } resolve({ title: title || null, composer: composer || null, error: null }); } catch (err) { resolve({ title: null, composer: null, error: err.message }); } }; reader.onerror = () => resolve({ title: null, composer: null, error: '读取失败' }); reader.readAsArrayBuffer(file); }); } // ========== 生成新文件名 ========== function makeNewName(title, composer, origName) { const sanitize = (s) => s.replace(/[\\/:*?"<>|]/g, '_').replace(/\s+/g, ' ').trim().substring(0, 200); const fallback = origName.replace(/\.flac$/i, '') || 'unknown'; const t = (title && title.trim()) ? sanitize(title) : sanitize(fallback); const c = (composer && composer.trim()) ? sanitize(composer) : '未知作曲家'; return `${t}-${c}.flac`; } // ========== 渲染文件列表 ========== function render() { fileListDiv.innerHTML = ''; if (filesData.length === 0) { filePanel.style.display = 'none'; clearBtn.disabled = true; renameBtn.disabled = true; setStatus('📭 暂无文件'); return; } filePanel.style.display = 'block'; clearBtn.disabled = false; renameBtn.disabled = false; filesData.forEach(item => { const row = document.createElement('div'); row.className = 'file-row'; const origSpan = document.createElement('span'); origSpan.className = 'orig'; origSpan.textContent = item.file.name; const arrow = document.createElement('span'); arrow.className = 'arrow'; arrow.textContent = '→'; const newSpan = document.createElement('span'); newSpan.className = 'newname'; if (item.error && !item.title && !item.composer) { newSpan.classList.add('missing'); newSpan.textContent = '⚠ 元数据缺失'; } else { newSpan.textContent = item.newName; } // 显示读取到的标签详情 const metaDetail = document.createElement('span'); metaDetail.className = 'meta-detail'; const t = item.title || '—'; const c = item.composer || '—'; metaDetail.textContent = `TITLE: ${t} | COMPOSER: ${c}`; row.appendChild(origSpan); row.appendChild(arrow); row.appendChild(newSpan); row.appendChild(metaDetail); fileListDiv.appendChild(row); }); const valid = filesData.filter(f => f.title || f.composer).length; setStatus(`📋 ${filesData.length} 个文件 · ${valid} 个可重命名`); } function setStatus(msg) { statusEl.textContent = msg; } // ========== 处理添加文件 ========== async function addFiles(fileList) { const flacFiles = Array.from(fileList).filter(f => f.name.toLowerCase().endsWith('.flac')); if (flacFiles.length === 0) { setStatus('❌ 请选择 .flac 文件'); return; } setStatus('🔍 正在读取内嵌元数据...'); renameBtn.disabled = true; clearBtn.disabled = true; for (const file of flacFiles) { const meta = await readFlacMetadata(file); const newName = makeNewName(meta.title, meta.composer, file.name); filesData.push({ file, title: meta.title, composer: meta.composer, newName, error: meta.error }); } // 去重 const seen = new Map(); filesData = filesData.filter(item => { const key = `${item.file.name}|${item.file.size}|${item.file.lastModified}`; if (seen.has(key)) return false; seen.set(key, true); return true; }); render(); setStatus('✅ 元数据读取完成'); } // ========== 执行下载重命名 ========== async function doRename() { const valid = filesData.filter(f => f.title || f.composer); if (valid.length === 0) { setStatus('⚠️ 没有可重命名的文件'); return; } setStatus('⏳ 正在下载重命名文件...'); renameBtn.disabled = true; clearBtn.disabled = true; for (const item of valid) { const blob = new Blob([item.file], { type: 'audio/flac' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = item.newName; document.body.appendChild(a); a.click(); document.body.removeChild(a); await new Promise(r => setTimeout(r, 150)); URL.revokeObjectURL(url); } setStatus('🎉 下载完成!原文件未被修改'); renameBtn.disabled = false; clearBtn.disabled = false; } function clearAll() { filesData = []; fileInput.value = ''; render(); setStatus('🧹 已清空'); } // ========== 事件绑定 ========== dropZone.addEventListener('click', () => fileInput.click()); dropZone.addEventListener('dragover', e => { e.preventDefault(); dropZone.classList.add('active'); }); dropZone.addEventListener('dragleave', e => { e.preventDefault(); dropZone.classList.remove('active'); }); dropZone.addEventListener('drop', e => { e.preventDefault(); dropZone.classList.remove('active'); if (e.dataTransfer.files.length) addFiles(e.dataTransfer.files); }); fileInput.addEventListener('change', e => { if (e.target.files.length) addFiles(e.target.files); }); clearBtn.addEventListener('click', clearAll); renameBtn.addEventListener('click', doRename); render(); setStatus('📎 选择或拖入 FLAC 文件以读取内嵌元数据'); })(); </script> </body> </html> 1 个帖子 - 1 位参与者 阅读完整话题
今天重命名了项目文件夹,结果发现CodexApp直接不认新文件夹了,而且还不能重新修改老项目的文件夹位置。 于是干脆让Codex写了一个Python程序,可以把一个项目的对话文件迁移到另一个项目。 其中类型为异常项目的就是原文件夹已经不存在的项目。 选择项目后可以选择自定义新项目文件夹或合并到已有项目,如下图。 合并完后如果状态还是不存在只要在CodexApp里 手动添加 这个项目文件夹就可以,对话记录会一并出现,如下图。 使用方法 在有Python环境的电脑上直接运行 codex_project_manager_tui.cmd outputs.zip (47.7 KB) 1 个帖子 - 1 位参与者 阅读完整话题
在 Build 2026 开发者大会上微软宣布将 WinUI 3 框架重命名为 WinUI,同时微软开始鼓励开发者使用 WinUI 框架为 Windows 11 开发高性能原生应用,微软释放的信号似乎是不要再纵容基于 Web 技术的劣质应用继续拖累 Windows 11,当然原本微软也是制造 Web 垃圾的一员。 鼓励开发者开发原生应用: 目前 Windows 平台充斥着大量基于 Electron、React Native 或基于 WebView 2 Runtime 打包的应用,这些应用占用的内存比较高、启动速度慢、界面风格也存在差异,当然最重要的还是性能体验非常差。例如微软自家开发的基于 WebView2 Runtime 的 Outlook 体验就非常糟糕。 不过从今年开始微软已经决定在 Windows 11 中抛弃基于 Web 技术的应用,转而采用更加可靠的原生架构,例如开始菜单 (推荐部分),所以微软也开始鼓励开发者也采用原生框架开发体验更好的原生应用,只不过对开发者而言采用 Electron 可以降低开发成本,开发者是否愿意遵从微软的建议开发原生应用还需要观察,毕竟微软的态度变化非常快。 为什么要将 WinUI 3 更名为 WinUI: 科技以更名为本,所以现在微软宣布将 Windows 应用生产级平台 WinUI 3 开发框架更名为 WinUI,更名的原因有可能是微软觉得以 2 3 4 这种版本号发布可能会让开发者担心,即微软不停地发布新框架,开发者需要不断地适配新框架,要是微软进行破坏性的大改动,那开发者适配成本就会大幅度增加。 所以微软强调基于长期稳定性考虑,WinUI 框架正式取消版本号,微软没有计划在未来开发新的 UI 框架,也不会进行破坏性的大改动,让开发者们可以安心专注于基于 WinUI 框架进行应用开发,为应用带来更好的使用体验。 微软还透露 Windows 11 更多系统级 Shell 组件将会陆续迁移到 WinUI,实现全平台界面风格和性能的一致性,但微软也不会排斥其他框架,只不过微软建议开发者使用 WinUI 而不是 Electron 等框架。 查看评论
微软的 OneDrive 云存储服务提供离线访问、已知文件夹移动、按需文件下载、回收站 / 防止勒索软件等诸多功能,微软也在持续为 OneDrive 提供更多新功能,例如 Microsoft 365 团队已经预告将在 2026 年 6 月为用户提供基于人工智能的文件重命名功能,该功能可以分析文件内容提供描述式标题。 新的建议重命名功能: 这项新功能名为建议重命名,功能本身由 Microsoft Copilot 人工智能助手提供,人工智能将首先检查文件内容然后再提供描述性强且清晰易懂的文件名,这意味着文件名实际上具有上下文感知能力。在实际操作时人工智能将提供 3 个不同的文件名建议,用户只需要点击自己喜欢的文件名,当然用户也可以忽略建议直接保留原文件名。 建议重命名功能将在用户重命名文件时自动弹出建议的文件名,人工智能助手会在后台自动扫描文件并给出建议,用户没有准备对文件重命名时人工智能助手自然也会保持安静,不过实际建议的文件名效果怎么样还需要等待 6 月份后微软上线这项新功能后进行测试。 最后目前这项功能可能仅会在 Microsoft OneDrive 网页版中提供,考虑到 Microsoft Copilot 在桌面版应用中的实际功能,可能短时间内微软不会在桌面版 OneDrive 中提供此类重命名建议功能。 支持扫描哪些文件给出建议: Microsoft Word / DOCX Microsoft Excel / XLSX Microsoft PowerPoint / PPTX PDF 文档格式 Markdown / MD 文档 常见图片格式如 PNG / JPEG / BMP / WEBP 等 查看评论
如下图我想要重命名文件夹时,使用快捷键调用ditto,就会自动退出重命名 请问怎么解决呢 1 个帖子 - 1 位参与者 阅读完整话题