Files
autoclip/scripts/render_title_six.py
Kris K 0835c9f81b feat: integrate visual highlights and unified editing for AutoClip 1.4 (#147)
* docs: isolate visual understanding and game ad creative work

* docs: map current AutoClip interface and interaction flows

* refactor(frontend): remove legacy UI and isolate clip export flow

* feat(studio): unify creative workflow and configurable vision models

* feat(studio): preserve independent language variants for 1.4

* feat(studio): connect candidate editing and verify full render workflow

* feat(studio): fill portrait frames and add styled title templates

* feat(studio): add shared artwork templates with bundled fonts

* fix(studio): preserve original audio across reordered cuts

* feat(studio): refine four versioned title presets against design

* feat(studio): add glossy, pixel and frosted title presets

* Refine comic title geometry and preserve ASR word timing

* Add optional punchier comic title variant for visual review

* Calibrate all six launch title presets and add bilingual review pack

* Use design reference thumbnails for six title preset choices

* Close out first-release video packaging and defer motion captions

* docs: audit game recording to finished creative workflow

* docs: prioritize a unified import edit and export experience

* feat: confirm smart import plans before production and preserve highlight sequences

* docs: record confirmed import and continuous highlight verification

* fix: preserve independent highlights instead of merging adjacent events

* fix: distinguish source duration from per-highlight duration

* fix: isolate SQLite file connections during concurrent preview reads

* Prioritize gameplay evidence before selecting and refining highlights

Filter explicit non-play categories before the six-highlight cap, rank optional model viewing-value estimates, and retain plain-scene endpoint compatibility. Preserve independent events and audit selection; reject menu-only results without fabricating highlights.

126 distinct offline regression cases pass. One bounded real Seed scan failed after 189.72s; no live quality improvement is claimed. See release log.

* Avoid repeated visual analysis after shared failures

Share one analysis attempt across selected visual goals; retain independent content processing and completed highlights when promotional copy fails. Persist safe request error codes, stages, durations and HTTP statuses without private payloads. Offline regression: 71 passed; no paid calls.

* fix(studio): distinguish draft and versioned export states

* fix(studio): preserve full exported frame in result thumbnail

* docs: record export state integration checks and remaining gaps

* chore: prepare 1.4 integration on latest v1.3.2 main

* fix(ui): keep project timestamps readable over cover images

* ci: install FFmpeg for real rendering regression tests

* feat(publish): use immutable Studio exports in shared publishing flow

* feat: observe Studio outcomes with anonymous aggregate events

* feat: localize Studio results and export history in eight languages

* feat: localize unified import and Studio editing workflow

* fix: generate bounded cached still thumbnails for Studio drafts

* fix: sample thumbnail frames inside scene boundaries

* fix: resume legacy clips and collections in the shared editor

* docs: record unified editor and bundled macOS validation gates

* test: retain upstream regressions after latest main rebase

* fix: bound Studio observation retries and stop terminal polling

* docs: record bounded polling integration verification

* test: add reproducible offline macOS bundle verification

* docs: record bundled runtime verification and new main integration blocker

* fix: complete v1.3.3 integration and expose native download response

* fix: save Studio exports with the native desktop downloader

* docs: record v1.3.4 candidate integration and remaining RC gates

* docs: record current macOS bundle evidence and RC rollback gates

* fix: localize Studio processing stages and export warnings

* fix: recover Studio exports when task dispatch fails

* fix: translate export dispatch recovery and record integration checks

* fix: preserve import confirmation when production dispatch fails

* fix: translate production dispatch recovery and record verification

* fix: isolate YouTube imports and plan opt-in visual analysis rollout
2026-09-27 14:16:25 +08:00

66 lines
4.2 KiB
Python

"""Render first-release six-preset review using production FFmpeg, without model calls."""
import argparse
import json
import os
from pathlib import Path
import shutil
import subprocess
import sys
import tempfile
sys.path.insert(0,str(Path(__file__).resolve().parents[1]))
from PIL import Image,ImageDraw
STYLES=[('comic','漫画冲击'),('neon','荧光挑战'),('arena','竞技斜切'),('pixel','像素街机'),('editorial','极简大字'),('frosted','磨砂字幕卡')]
CROPS=[(36,89,362,691),(382,89,705,691),(724,89,1050,691),(36,754,362,1357),(382,754,705,1357),(724,754,1050,1357)]
def main():
p=argparse.ArgumentParser(description=__doc__)
for name in ['source','reference','baseline','comic-baseline','output']:p.add_argument('--'+name,type=Path,required=True)
p.add_argument('--styles',nargs='+',choices=[s[0] for s in STYLES],default=[s[0] for s in STYLES])
args=p.parse_args();out=args.output;out.mkdir(parents=True,exist_ok=True)
ref=Image.open(args.reference)
shutil.copy2(args.reference,out/'design-reference.png')
with tempfile.TemporaryDirectory(prefix='autoclip-six-review-') as data:
os.environ['AUTOCLIP_DATA_DIR']=data
from backend.services.studio.models import Draft,Scene
from backend.services.studio.render import render_draft
from backend.services.studio.store import directory
from backend.services.studio.title_art import font_for
from backend.utils.ffmpeg_utils import get_ffmpeg_path
directory('review').mkdir(parents=True)
prior=json.loads((out/'evidence.json').read_text()) if (out/'evidence.json').exists() else []
evidence=[e for e in prior if e['draft']['title_style'] not in args.styles]
board=Image.new('RGB',(1200,1170),'#111821');d=ImageDraw.Draw(board)
for i,(style,label) in enumerate(STYLES):
for lang,hook in [('en','CAN YOU\nESCAPE?'),('zh','你能逃出\n这里吗?')]:
key=f'{style}-{lang}'
if style not in args.styles:continue
draft=Draft(id=key,title=label,hook=hook,title_style=style,title_template_version=6,aspect='portrait',layout='crop',subtitles=False,scenes=[Scene(id='a',start=8,end=14),Scene(id='b',start=16,end=20)])
result=render_draft('review',args.source,draft,key,lambda _:None)
video=out/f'{key}.mp4';shutil.copy2(directory('review')/'output/studio'/video.name,video)
subprocess.run([get_ffmpeg_path(),'-v','error','-ss','1','-i',str(video),'-frames:v','1','-y',str(out/f'{key}.jpg')],check=True)
evidence.append({'file':video.name,'draft':draft.model_dump(),'result':result})
print(key,result,flush=True)
previous=args.comic_baseline/'comic-v5' if style=='comic' else args.baseline/f'{style}-v3'
for suffix in ['.mp4','.jpg']:shutil.copy2(str(previous)+suffix,out/f'{style}-old{suffix}')
panel=ref.crop(CROPS[i]);panel.save(out/f'{style}-reference.jpg',quality=95)
comparison=Image.new('RGB',(1500,520),'#111821');cd=ImageDraw.Draw(comparison)
for col,name in enumerate(['设计参考','已确认漫画' if style=='comic' else '上一轮','本轮优化']):
cd.text((col*500+16,12),name,font=font_for(name,28),fill='white')
comparison.paste(panel.crop((0,0,panel.width,round(panel.width*.86))).resize((480,413)),(10,65))
for col,key in [(1,'old'),(2,'en')]:
image=Image.open(out/f'{style}-{key}.jpg')
comparison.paste(image.crop((0,0,1080,929)).resize((480,413)),(col*500+10,65))
comparison.save(out/f'{style}-comparison.jpg',quality=95)
x=(i%3)*400;y=(i//3)*585
d.text((x+15,y+10),f'{i+1:02} {label}',font=font_for(label,27),fill='white')
frame=Image.open(out/f'{style}-en.jpg').crop((0,0,1080,1450)).resize((390,524))
board.paste(frame,(x+5,y+51))
board.save(out/'contact.jpg',quality=95)
(out/'evidence.json').write_text(json.dumps(evidence,ensure_ascii=False,indent=2))
shutil.copy2(Path(__file__).with_name('title_six_review.html'),out/'index.html')
print(out/'index.html',flush=True)
if __name__=='__main__':main()