Commit a4e82556f579e3f7e121bfaf2f2431b52966c138
1 parent
ad566a41
h3-console/raylight: 支持竖屏视频生成(宽高互换,非独立参数)
MiniMax-H3 竖屏走的还是 width/height 机制,没有单独的 orientation 字段。给4个已实测 横屏档位(1344×768/832×480/448×256/1024×576)各配一个宽高互换的竖屏选项,帧数上限 沿用对应横屏档位的实测值(同总像素数,显存/耗时特征预期一致,但没有专门重跑过竖屏 OOM 边界测试)。前端 RESOLUTIONS 和后端 RESOLUTION_CEILINGS 保持同步。 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EhWwz7mLf5PLBn6noJoAcG
Showing
2 changed files
with
55 additions
and
4 deletions
h3-console/static/index.html
| ... | ... | @@ -172,8 +172,10 @@ |
| 172 | 172 | <textarea id="fl2va-prompt" rows="10" placeholder="..."></textarea> |
| 173 | 173 | <label>首帧图</label> |
| 174 | 174 | <input id="fl2va-first" type="file" accept="image/*" onchange="uploadTo('fl2va-first')"> |
| 175 | + <input id="fl2va-first-url" type="url" placeholder="或粘贴图片 URL,回车添加" onkeydown="if(event.key==='Enter'){event.preventDefault();addSingleUrl('fl2va-first','fl2va-first-url',renderFl2vaThumbs,'fl2va-err')}"> | |
| 175 | 176 | <label>尾帧图(可选)</label> |
| 176 | 177 | <input id="fl2va-last" type="file" accept="image/*" onchange="uploadTo('fl2va-last')"> |
| 178 | + <input id="fl2va-last-url" type="url" placeholder="或粘贴图片 URL,回车添加" onkeydown="if(event.key==='Enter'){event.preventDefault();addSingleUrl('fl2va-last','fl2va-last-url',renderFl2vaThumbs,'fl2va-err')}"> | |
| 177 | 179 | <div class="thumbrow" id="fl2va-thumbs"></div> |
| 178 | 180 | <div class="row"> |
| 179 | 181 | <div><label>分辨率</label><select id="fl2va-res" class="res-select"></select></div> |
| ... | ... | @@ -210,12 +212,15 @@ |
| 210 | 212 | <textarea id="ref2va-prompt" rows="10" placeholder="..."></textarea> |
| 211 | 213 | <label>参考图(可多选,没有张数上限,越多序列越长越慢)</label> |
| 212 | 214 | <input id="ref2va-refs" type="file" accept="image/*" multiple onchange="uploadMulti()"> |
| 215 | + <input id="ref2va-refs-url" type="url" placeholder="或粘贴图片 URL,回车添加(可加多个)" onkeydown="if(event.key==='Enter'){event.preventDefault();addArrayUrl('ref2va-refs','ref2va-refs-url',renderRefThumbs,'ref2va-err')}"> | |
| 213 | 216 | <div class="thumbrow" id="ref2va-thumbs"></div> |
| 214 | 217 | <label>参考视频(可选,最多3段,2-15秒,音轨会自动带上)</label> |
| 215 | 218 | <input id="ref2va-videos-input" type="file" accept="video/*" multiple onchange="uploadMultiTagged('ref2va-videos-input','ref2va-videos','ref2va-video-tags')"> |
| 219 | + <input id="ref2va-videos-url" type="url" placeholder="或粘贴视频 URL,回车添加(最多3个)" onkeydown="if(event.key==='Enter'){event.preventDefault();addArrayUrl('ref2va-videos','ref2va-videos-url',()=>renderTags('ref2va-video-tags','ref2va-videos'),'ref2va-err',3)}"> | |
| 216 | 220 | <div class="thumbrow" id="ref2va-video-tags"></div> |
| 217 | 221 | <label>独立音频参考(可选,最多3个,不跟参考视频绑定)</label> |
| 218 | 222 | <input id="ref2va-audios-input" type="file" accept="audio/*" multiple onchange="uploadMultiTagged('ref2va-audios-input','ref2va-audios','ref2va-audio-tags')"> |
| 223 | + <input id="ref2va-audios-url" type="url" placeholder="或粘贴音频 URL,回车添加(最多3个)" onkeydown="if(event.key==='Enter'){event.preventDefault();addArrayUrl('ref2va-audios','ref2va-audios-url',()=>renderTags('ref2va-audio-tags','ref2va-audios'),'ref2va-err',3)}"> | |
| 219 | 224 | <div class="thumbrow" id="ref2va-audio-tags"></div> |
| 220 | 225 | <div class="row"> |
| 221 | 226 | <div><label>分辨率</label><select id="ref2va-res" class="res-select"></select></div> |
| ... | ... | @@ -368,11 +373,18 @@ function showCreate(pushUrl = true) { |
| 368 | 373 | // 数字改动两边一起改。832×480/1024×576/1344×768 三个是真实CUDA OOM边界;448×256 技术上测到 |
| 369 | 374 | // 3000帧都没OOM,1200帧(50秒)是画质边界(用户看过实际输出判定50秒往后不能看),不是显存边界。 |
| 370 | 375 | // 1344×768(官方默认分辨率)排第一,作为新建任务的默认选项——2026-08-23 用户要求。 |
| 376 | +// 2026-09-01:竖屏是宽高互换实现的,不是独立的横竖屏参数(生产已有 720×1280 提交案例,见 | |
| 377 | +// docs/todo/h3-showcase-cases.md)。maxFrames 沿用对应横屏档位的实测值——宽高互换总像素数不变, | |
| 378 | +// 显存/耗时特征预期一致,但没有单独针对竖屏重新跑过 OOM 边界测试,如果发现竖屏更早 OOM 要单独下调。 | |
| 371 | 379 | const RESOLUTIONS = [ |
| 372 | - { value: '1344x768', width: 1344, height: 768, label: '1344×768(官方默认分辨率)', maxFrames: 412 }, | |
| 373 | - { value: '832x480', width: 832, height: 480, label: '832×480(标准档)', maxFrames: 1125 }, | |
| 374 | - { value: '448x256', width: 448, height: 256, label: '448×256(长时长档)', maxFrames: 1200 }, | |
| 375 | - { value: '1024x576', width: 1024, height: 576, label: '1024×576(高清档)', maxFrames: 743 }, | |
| 380 | + { value: '1344x768', width: 1344, height: 768, label: '1344×768(官方默认分辨率,横屏)', maxFrames: 412 }, | |
| 381 | + { value: '768x1344', width: 768, height: 1344, label: '768×1344(官方默认分辨率,竖屏)', maxFrames: 412 }, | |
| 382 | + { value: '832x480', width: 832, height: 480, label: '832×480(标准档,横屏)', maxFrames: 1125 }, | |
| 383 | + { value: '480x832', width: 480, height: 832, label: '480×832(标准档,竖屏)', maxFrames: 1125 }, | |
| 384 | + { value: '448x256', width: 448, height: 256, label: '448×256(长时长档,横屏)', maxFrames: 1200 }, | |
| 385 | + { value: '256x448', width: 256, height: 448, label: '256×448(长时长档,竖屏)', maxFrames: 1200 }, | |
| 386 | + { value: '1024x576', width: 1024, height: 576, label: '1024×576(高清档,横屏)', maxFrames: 743 }, | |
| 387 | + { value: '576x1024', width: 576, height: 1024, label: '576×1024(高清档,竖屏)', maxFrames: 743 }, | |
| 376 | 388 | ]; |
| 377 | 389 | const DURATIONS = [ |
| 378 | 390 | { value: '124', frames: 124, label: '5s' }, |
| ... | ... | @@ -441,6 +453,34 @@ async function uploadFile(file) { |
| 441 | 453 | return await r.json(); |
| 442 | 454 | } |
| 443 | 455 | |
| 456 | +// 外部 URL 不经过 /v1/assets 转存,直接把地址原样交给后端——raylight 拿到 reference_image_urls | |
| 457 | +// 等字段里的 URL 就是 urllib.request.urlopen() 下载,本机 /v1/assets 生成的 URL 和外部 URL | |
| 458 | +// 对它没有区别。所以这里跳过上传,直接把用户粘贴的地址塞进 uploaded 状态。 | |
| 459 | +// ponytail: 不校验 URL 是否可达/是图片——排队执行时后端下载失败才会报错,不在提交阶段拦截。 | |
| 460 | +function addSingleUrl(key, inputId, renderFn, errBoxId) { | |
| 461 | + const input = document.getElementById(inputId); | |
| 462 | + const url = input.value.trim(); | |
| 463 | + if (!url) return; | |
| 464 | + uploaded[key] = url; | |
| 465 | + input.value = ''; | |
| 466 | + document.getElementById(errBoxId).textContent = ''; | |
| 467 | + renderFn(); | |
| 468 | +} | |
| 469 | + | |
| 470 | +function addArrayUrl(key, inputId, renderFn, errBoxId, cap) { | |
| 471 | + const input = document.getElementById(inputId); | |
| 472 | + const url = input.value.trim(); | |
| 473 | + if (!url) return; | |
| 474 | + if (cap && uploaded[key].length >= cap) { | |
| 475 | + document.getElementById(errBoxId).textContent = `最多${cap}个,已达上限`; | |
| 476 | + return; | |
| 477 | + } | |
| 478 | + uploaded[key].push(url); | |
| 479 | + input.value = ''; | |
| 480 | + document.getElementById(errBoxId).textContent = ''; | |
| 481 | + renderFn(); | |
| 482 | +} | |
| 483 | + | |
| 444 | 484 | // server.py 把原始文件名塞进了存盘文件名里(<aid>__<原名>.ext),这里直接从 URL 反解出来 |
| 445 | 485 | // 显示——不用单独维护一份 filename 状态,旧任务(改动前上传的,URL 里没有 __ 分段)会 |
| 446 | 486 | // 退化成显示 uuid 文件名,不算错误,只是没有原名可用。 |
| ... | ... | @@ -629,12 +669,17 @@ function clearForm(kind) { |
| 629 | 669 | uploaded['fl2va-first'] = null; uploaded['fl2va-last'] = null; |
| 630 | 670 | document.getElementById('fl2va-first').value = ''; |
| 631 | 671 | document.getElementById('fl2va-last').value = ''; |
| 672 | + document.getElementById('fl2va-first-url').value = ''; | |
| 673 | + document.getElementById('fl2va-last-url').value = ''; | |
| 632 | 674 | renderFl2vaThumbs(); |
| 633 | 675 | } else if (kind === 'ref2va') { |
| 634 | 676 | uploaded['ref2va-refs'] = []; uploaded['ref2va-videos'] = []; uploaded['ref2va-audios'] = []; |
| 635 | 677 | document.getElementById('ref2va-refs').value = ''; |
| 636 | 678 | document.getElementById('ref2va-videos-input').value = ''; |
| 637 | 679 | document.getElementById('ref2va-audios-input').value = ''; |
| 680 | + document.getElementById('ref2va-refs-url').value = ''; | |
| 681 | + document.getElementById('ref2va-videos-url').value = ''; | |
| 682 | + document.getElementById('ref2va-audios-url').value = ''; | |
| 638 | 683 | renderRefThumbs(); |
| 639 | 684 | renderTags('ref2va-video-tags', 'ref2va-videos'); |
| 640 | 685 | renderTags('ref2va-audio-tags', 'ref2va-audios'); | ... | ... |
raylight/scripts/server.py
| ... | ... | @@ -96,11 +96,17 @@ RAY_INIT_CFG = { |
| 96 | 96 | # 或画质边界(448×256,见notes.md——技术上测到3000帧都没OOM,是用户看过实际输出判定50秒往后 |
| 97 | 97 | # 画质不能看,不是显存拦的)。跟 h3-console 前端 RESOLUTIONS 里的 maxFrames 保持一致—— |
| 98 | 98 | # 两边算法对不上就会出现"前端放行、后端拒绝"或反过来,数字改动两边一起改。 |
| 99 | +# 2026-09-01:竖屏(宽高互换)条目沿用对应横屏档位的实测上限——总像素数不变,显存/耗时特征预期 | |
| 100 | +# 一致,但没有专门为竖屏重跑过 OOM 边界测试,如果实测发现竖屏更早 OOM 要单独下调,不要假设两者永远相等。 | |
| 99 | 101 | RESOLUTION_CEILINGS = { |
| 100 | 102 | (832, 480): 1125, |
| 103 | + (480, 832): 1125, | |
| 101 | 104 | (448, 256): 1200, |
| 105 | + (256, 448): 1200, | |
| 102 | 106 | (1024, 576): 743, |
| 107 | + (576, 1024): 743, | |
| 103 | 108 | (1344, 768): 412, |
| 109 | + (768, 1344): 412, | |
| 104 | 110 | } |
| 105 | 111 | CLIP_NAME = "qwen3vl_32b_minimax_h3_nvfp4_awq.safetensors" |
| 106 | 112 | VAE_VIDEO = "minimax_h3_video_vae_fp16.safetensors" | ... | ... |