Files
freecut/static/index.html
T
hz4th_coder 3a6d4fc683 FreeCut 自由剪辑 v1.0.0: 免费开源视频编辑器(Web)
- 多轨道时间线: 视频/图片/音频/文字, 拖拽/修剪/分割/复制/删除
- 12款滤镜特效 + 4种转场 + 4种文字动画
- 6款程序化原创配乐(Web Audio 实时合成, 零版权风险)
- Canvas 合成 + MediaRecorder 实时导出 WebM(VP9+Opus)
- 账号体系 + 每日导出额度 + 企业/AI短剧付费升级 + 管理后台
- 项目自动保存/缩略图, 移动端自适应
- 个人免费每天30次导出, 企业¥299/月 不限量, AI短剧¥499/月 不限量
2026-08-26 17:37:56 +08:00

244 lines
12 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>FreeCut 自由剪辑 · 免费开源视频编辑器</title>
<link rel="stylesheet" href="/css/style.css">
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>✂️</text></svg>">
<meta name="description" content="FreeCut 自由剪辑 —— 完全免费开源的视频编辑器,精美特效+优秀配乐,自媒体人放心用,企业/AI短剧才付费。">
</head>
<body>
<nav class="land-nav">
<span class="logo">✂️ FreeCut 自由剪辑</span>
<span class="spacer"></span>
<span id="nav-user" style="font-size:13px"></span>
<button class="btn small" id="nav-login">登录 / 注册</button>
<button class="btn small ghost hidden" id="nav-logout">退出</button>
<button class="btn small ghost hidden" id="nav-admin">后台</button>
</nav>
<section class="hero">
<h1>免费开源,<span class="grad">剪出大片</span><br>自媒体人的剪辑神器</h1>
<p>FreeCut 是一套完全免费的在线视频编辑器:精美特效、程序化原创配乐、文字字幕、转场动画,浏览器直接出片。
<b>个人自媒体永久免费</b>,视频制作公司与 AI 短剧批量生产才需要付费。</p>
<div class="cta">
<button class="btn" id="hero-start">🎬 立即开始创作</button>
<a class="btn ghost" href="#pricing">查看免费额度</a>
</div>
</section>
<section class="features">
<div class="feature"><div class="ic">🎨</div><h3>精美特效</h3><p>12 款一键滤镜:电影、复古、赛博、梦幻… 转场动画与文字动画任你选。</p></div>
<div class="feature"><div class="ic">🎼</div><h3>优秀配乐</h3><p>6 大情绪原创配乐,浏览器端实时合成、零版权风险,随时商用。</p></div>
<div class="feature"><div class="ic">✂️</div><h3>专业剪辑</h3><p>多轨道时间线、剪辑分割、拖动修剪、音量控制、画布比例(9:16/16:9/1:1)。</p></div>
<div class="feature"><div class="ic">🔒</div><h3>本地渲染</h3><p>视频在浏览器本地编码,素材隐私不出设备,服务器只存工程文件。</p></div>
<div class="feature"><div class="ic">💎</div><h3>个人免费</h3><p>个人自媒体每天 30 次导出额度,无水印、全功能,足够日常使用。</p></div>
<div class="feature"><div class="ic">🌱</div><h3>开源可部署</h3><p>纯 Flask + 原生前端,开源代码随意部署,数据都在自己手里。</p></div>
</section>
<section class="pricing" id="pricing">
<h2>💰 定价:普通人免费,生产方付费</h2>
<div class="price-grid">
<div class="price-card">
<div class="p-name">🆓 个人免费版</div>
<div class="p-price">¥0<small> / 永久</small></div>
<ul>
<li>全部功能免费使用</li>
<li>每天 30 次导出额度(够用)</li>
<li>无水印、可商用</li>
<li>特效 / 配乐 / 字幕全开放</li>
</ul>
<button class="btn block" data-go-editor>免费开始</button>
</div>
<div class="price-card hot">
<div class="p-name">🏢 企业版</div>
<div class="p-price">¥299<small> / 月</small></div>
<ul>
<li>不限量导出</li>
<li>团队协作(规划中)</li>
<li>优先渲染通道(规划中)</li>
<li>商用授权 + 技术支持</li>
</ul>
<button class="btn block" data-apply="business">申请企业版</button>
</div>
<div class="price-card">
<div class="p-name">🎬 AI 短剧版</div>
<div class="p-price">¥499<small> / 月</small></div>
<ul>
<li>不限量导出</li>
<li>批量生产流水线(规划中)</li>
<li>独享高速编码(规划中)</li>
<li>商用授权 + 专属支持</li>
</ul>
<button class="btn block" data-apply="ai_short">申请 AI 短剧版</button>
</div>
</div>
</section>
<section class="features" style="padding-top:0">
<div class="feature" style="grid-column:1/-1;text-align:center"><div class="ic">🧭</div><h3>创作流程</h3><p style="margin:0 auto;max-width:640px">① 上传或选择素材 → ② 拖入时间线,点击修剪分割 → ③ 一键加特效、配乐、转场、字幕 → ④ 导出 WebM 视频,直接发布。全部在浏览器完成。</p></div>
</section>
<footer class="footer">
<div>✂️ FreeCut 自由剪辑 · 免费开源 · 个人自媒体免费,企业与 AI 短剧付费</div>
<div style="margin-top:6px">构建于 Flask + 原生 JS + Web Audio / Canvas / MediaRecorder · 浏览器本地渲染</div>
<div style="margin-top:6px">© 2026 FreeCut · 开源协议(MIT)· 部署:<a href="/admin">管理后台</a></div>
</footer>
<!-- 登录/注册弹窗 -->
<div class="modal-mask hidden" id="auth-modal">
<div class="modal">
<h3 id="auth-title">登录 FreeCut</h3>
<div class="row"><label>用户名</label><input id="auth-user" autocomplete="username"></div>
<div class="row"><label>密码</label><input id="auth-pass" type="password" autocomplete="current-password"></div>
<div class="row hidden" id="auth-nick-row"><label>昵称</label><input id="auth-nick"></div>
<div id="auth-err" class="muted" style="color:var(--err);font-size:12px;min-height:16px"></div>
<button class="btn block" id="auth-submit">登录</button>
<p class="muted" style="text-align:center;margin-top:10px;font-size:12px">没有账号?<a href="#" id="auth-switch">注册一个(免费)</a></p>
</div>
</div>
<!-- 申请企业/AI版弹窗 -->
<div class="modal-mask hidden" id="apply-modal">
<div class="modal">
<h3 id="apply-title">申请开通</h3>
<div class="row" id="apply-desc" class="muted" style="font-size:13px"></div>
<div class="row"><label>联系方式(邮箱/微信/电话)</label><input id="apply-contact" placeholder="管理员审核通过后开通"></div>
<div id="apply-err" class="muted" style="color:var(--err);font-size:12px;min-height:16px"></div>
<div style="display:flex;gap:10px;justify-content:flex-end">
<button class="btn ghost" id="apply-close">取消</button>
<button class="btn" id="apply-submit">提交申请</button>
</div>
</div>
</div>
<script src="/js/common.js"></script>
<script>
(() => {
let mode = 'login';
let me = null;
const $ = s => document.querySelector(s);
function updateNav() {
if (me) {
$('#nav-user').textContent = `👤 ${me.nickname} · ${me.tier_name}`;
$('#nav-login').classList.add('hidden');
$('#nav-logout').classList.remove('hidden');
if (me.username === 'admin') $('#nav-admin').classList.remove('hidden');
else $('#nav-admin').classList.add('hidden');
} else {
$('#nav-user').textContent = '';
$('#nav-login').classList.remove('hidden');
$('#nav-logout').classList.add('hidden');
$('#nav-admin').classList.add('hidden');
}
}
async function loadMe() {
try { const r = await FC.api('/api/me'); me = { ...r.quota, username: r.quota.username }; updateNav(); }
catch (e) { me = null; updateNav(); }
}
function openAuth(m) {
mode = m || 'login';
$('#auth-title').textContent = mode === 'login' ? '登录 FreeCut' : '注册 FreeCut(免费)';
$('#auth-submit').textContent = mode === 'login' ? '登录' : '注册并开始';
$('#auth-nick-row').classList.toggle('hidden', mode === 'login');
$('#auth-err').textContent = '';
$('#auth-modal').classList.remove('hidden');
}
async function goEditor() {
if (!me) { openAuth('register'); FC.toast('请先注册(免费)'); return; }
try {
const r = await FC.api('/api/projects', { method: 'POST', body: { name: '我的新作品' } });
location.href = '/editor?id=' + r.id;
} catch (e) { FC.toast(e.message, 'err'); }
}
function renderProjects(list) {
const grid = $('#proj-grid');
if (!grid) return;
if (!list.length) {
grid.innerHTML = '<div class="empty">还没有作品,点右上角「新建作品」开始创作 ✂️</div>';
return;
}
grid.innerHTML = list.map(p => `
<div class="proj-card" data-id="${p.id}">
${p.thumb ? `<img class="proj-thumb" src="${p.thumb}" style="width:100%;aspect-ratio:16/9;object-fit:cover">` : '<div class="proj-thumb">🎬</div>'}
<div class="proj-info"><div class="t">${FC.esc(p.name)}</div><div class="s">${new Date(p.updated_at * 1000).toLocaleString()}</div></div>
</div>`).join('');
grid.querySelectorAll('.proj-card').forEach(c => c.addEventListener('click', () => {
location.href = '/editor?id=' + c.dataset.id;
}));
}
async function loadProjects() {
const r = await FC.api('/api/projects');
renderProjects(r.projects);
}
// 首页加载后如果有账号显示作品区
async function boot() {
await loadMe();
if (me) {
const sec = document.createElement('section');
sec.className = 'app-shell';
sec.innerHTML = `<h2 style="display:flex;align-items:center;gap:12px">📁 我的作品 <button class="btn small" id="new-proj"> 新建作品</button></h2><div class="proj-grid" id="proj-grid"></div>`;
document.querySelector('.features').after(sec);
$('#new-proj').addEventListener('click', goEditor);
loadProjects();
}
}
document.addEventListener('DOMContentLoaded', () => {
boot();
$('#nav-login').addEventListener('click', () => openAuth('login'));
$('#nav-logout').addEventListener('click', async () => { await FC.api('/api/auth/logout', { method: 'POST' }); me = null; updateNav(); location.reload(); });
$('#nav-admin').addEventListener('click', () => location.href = '/admin');
$('#hero-start').addEventListener('click', goEditor);
document.querySelectorAll('[data-go-editor]').forEach(b => b.addEventListener('click', goEditor));
// 认证
$('#auth-switch').addEventListener('click', e => { e.preventDefault(); mode = mode === 'login' ? 'register' : 'login'; openAuth(mode); });
$('#auth-submit').addEventListener('click', async () => {
const user = $('#auth-user').value.trim(), pass = $('#auth-pass').value, nick = $('#auth-nick').value.trim();
$('#auth-err').textContent = '';
try {
if (mode === 'login') {
await FC.api('/api/auth/login', { method: 'POST', body: { username: user, password: pass } });
} else {
await FC.api('/api/auth/register', { method: 'POST', body: { username: user, password: pass, nickname: nick } });
await FC.api('/api/auth/login', { method: 'POST', body: { username: user, password: pass } });
}
$('#auth-modal').classList.add('hidden');
FC.toast('欢迎回来!开始创作吧 🎉', 'ok');
location.reload();
} catch (e) { $('#auth-err').textContent = e.message; }
});
$('#auth-pass').addEventListener('keydown', e => { if (e.key === 'Enter') $('#auth-submit').click(); });
// 申请
let applyTier = 'business';
document.querySelectorAll('[data-apply]').forEach(b => b.addEventListener('click', () => {
if (!me) { openAuth('register'); return; }
applyTier = b.dataset.apply;
const isBiz = applyTier === 'business';
$('#apply-title').textContent = isBiz ? '🏢 申请企业版' : '🎬 申请 AI 短剧版';
$('#apply-desc').textContent = isBiz ? '企业版 ¥299/月 · 不限量导出、商用授权、团队协作(规划中)' : 'AI短剧版 ¥499/月 · 不限量导出、批量生产流水线(规划中)、商用授权';
$('#apply-err').textContent = '';
$('#apply-modal').classList.remove('hidden');
}));
$('#apply-close').addEventListener('click', () => $('#apply-modal').classList.add('hidden'));
$('#apply-submit').addEventListener('click', async () => {
try {
await FC.api('/api/upgrade/apply', { method: 'POST', body: { tier: applyTier, contact: $('#apply-contact').value.trim() } });
$('#apply-modal').classList.add('hidden');
FC.toast('申请已提交,管理员审核通过后自动开通 ✅', 'ok');
} catch (e) { $('#apply-err').textContent = e.message; }
});
});
})();
</script>
</body>
</html>