mirror of
https://github.com/zhouxiaoka/autoclip.git
synced 2026-10-02 02:34:34 +08:00
Merge pull request #204 from zhouxiaoka/codex/subtitle-highlights
feat: support subtitle highlights in the shared editor
This commit is contained in:
@@ -149,9 +149,11 @@ def run_content(project_id, video):
|
||||
if not result or not result.get('success'):
|
||||
raise RuntimeError((result or {}).get('error') or '内容切片未完成,请检查语音与文字模型设置后重试')
|
||||
|
||||
if not result.get('result', {}).get('result', {}).get('titled_clips'):
|
||||
clips = result.get('result', {}).get('result', {}).get('titled_clips')
|
||||
if not clips:
|
||||
raise ValueError('没有提取到可用内容片段,请检查语音/字幕,或修改方案为精彩高光')
|
||||
|
||||
return clips
|
||||
|
||||
def inspect_project(project_id, options, url=None, browser=None):
|
||||
"""Only ingest and screen. Expensive production requires an explicit confirmation."""
|
||||
@@ -190,8 +192,8 @@ def confirm_project(project_id, body):
|
||||
if plan.get('id') != body.plan_id or (state.get('analysis') or {}).get('status') != 'awaiting_confirmation':
|
||||
raise store.ConflictError('方案已变化或任务已开始,请刷新后确认')
|
||||
route = body.analysis_mode or plan.get('recommended_analysis', 'subtitle')
|
||||
if route == 'subtitle' and any(goal != 'content' for goal in body.goals):
|
||||
raise ValueError('字幕链路目前支持内容切片;视觉高光与推广分析需要显式选择视觉模式,原素材已保留')
|
||||
if route == 'subtitle' and 'promo' in body.goals:
|
||||
raise ValueError('字幕链路支持内容切片和字幕高光;推广分析仍需视觉模式,原素材已保留')
|
||||
if route == 'visual' and body.goals == ['content']:
|
||||
raise ValueError('内容切片当前使用字幕分析,请选择字幕模式后确认')
|
||||
if route == 'visual':
|
||||
@@ -223,6 +225,8 @@ def _produce_selected(project_id, plan):
|
||||
diagnostics = []
|
||||
visual_error = None
|
||||
events = coverage = None
|
||||
subtitle_clips = None
|
||||
subtitle_error = None
|
||||
def stage(message):
|
||||
store.change(project_id, lambda data:data['analysis'].update(message=message))
|
||||
try:
|
||||
@@ -233,8 +237,21 @@ def _produce_selected(project_id, plan):
|
||||
try:
|
||||
prefs = Preferences.model_validate({**plan['confirmed_preferences'], 'goal':goal})
|
||||
stage('制作' + labels[goal])
|
||||
if goal == 'content':
|
||||
run_content(project_id, video)
|
||||
if goal == 'promo' and plan.get('confirmed_analysis', 'subtitle') == 'subtitle':
|
||||
raise ValueError('推广分析需要显式选择视觉模式')
|
||||
if goal == 'content' or plan.get('confirmed_analysis', 'subtitle') == 'subtitle':
|
||||
if subtitle_error is not None:
|
||||
raise subtitle_error
|
||||
if subtitle_clips is None:
|
||||
try:
|
||||
subtitle_clips = run_content(project_id, video)
|
||||
except Exception as error:
|
||||
subtitle_error = error
|
||||
raise
|
||||
if goal == 'highlight':
|
||||
from backend.services.studio.subtitle_highlights import make_highlights
|
||||
drafts = make_highlights(subtitle_clips, prefs, intelligence._probe(video).get('duration'))
|
||||
store.change(project_id, lambda data:data['drafts'].extend({**d,'updated_at':store.now()} for d in drafts))
|
||||
mark_project(project_id, 'processing')
|
||||
continue
|
||||
if plan.get('confirmed_analysis', 'subtitle') != 'visual':
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
"""Adapt this run's scored subtitle ranges without another model request."""
|
||||
import math
|
||||
import uuid
|
||||
from backend.services.studio.models import Draft, Scene
|
||||
from backend.services.studio.intelligence import validate_scenes
|
||||
from backend.utils.text_processor import TextProcessor
|
||||
|
||||
|
||||
def make_highlights(clips, preferences, source_duration):
|
||||
ranked = []
|
||||
for clip in clips:
|
||||
try:
|
||||
score = float(clip.get('final_score', 0))
|
||||
if not math.isfinite(score):
|
||||
continue
|
||||
scene = Scene(id=uuid.uuid4().hex,
|
||||
label=str(clip.get('generated_title') or clip.get('outline') or '字幕高光')[:120],
|
||||
start=TextProcessor.time_to_seconds(clip['start_time']),
|
||||
end=TextProcessor.time_to_seconds(clip['end_time']))
|
||||
validate_scenes([scene], source_duration)
|
||||
ranked.append((score, scene))
|
||||
except (ValueError, TypeError, KeyError, AttributeError):
|
||||
continue
|
||||
ranked.sort(key=lambda row: row[0], reverse=True)
|
||||
selected = []
|
||||
for _, scene in ranked:
|
||||
# Preserve whole semantic ranges, removing duplicate/mostly overlapping candidates.
|
||||
if any(max(0, min(scene.end, old.end)-max(scene.start, old.start)) /
|
||||
min(scene.end-scene.start, old.end-old.start) > .8 for old in selected):
|
||||
continue
|
||||
selected.append(scene)
|
||||
if len(selected) == 6:
|
||||
break
|
||||
if not selected:
|
||||
raise ValueError('没有可用的字幕高光,请检查字幕与选段结果;原素材已保留')
|
||||
return [Draft(id=uuid.uuid4().hex, title=s.label, scenes=[s], origin='subtitle-highlight',
|
||||
language=preferences.language, aspect=preferences.aspect,
|
||||
layout='crop' if preferences.aspect == 'portrait' else 'fit', subtitles=True).model_dump()
|
||||
for s in selected]
|
||||
@@ -37,7 +37,7 @@ def test_subtitle_confirm_uses_only_old_pipeline_and_freezes_route(client, sourc
|
||||
assert not calls and state['analysis']['status'] == 'awaiting_confirmation'
|
||||
# Changing global settings cannot upgrade an already screened plan.
|
||||
monkeypatch.setattr(ap, 'load', lambda: ap.AnalysisPreferences(analysis_mode='visual'))
|
||||
rejected = client.post(f'/studio/{pid}/start', json={'plan_id':state['plan']['id'], 'goals':['highlight']})
|
||||
rejected = client.post(f'/studio/{pid}/start', json={'plan_id':state['plan']['id'], 'goals':['promo']})
|
||||
assert rejected.status_code == 422
|
||||
assert store.read(pid)['analysis']['status'] == 'awaiting_confirmation'
|
||||
accepted = client.post(f'/studio/{pid}/start', json={'plan_id':state['plan']['id'], 'goals':['content']})
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
import pytest
|
||||
from backend.tests.test_studio import root, source
|
||||
from backend.services.studio import jobs, store, intelligence
|
||||
from backend.services.studio.models import Preferences
|
||||
from backend.services.studio.subtitle_highlights import make_highlights
|
||||
|
||||
|
||||
def clip(start, end, score=1):
|
||||
return {'start_time':f'00:00:{start:02d},000','end_time':f'00:00:{end:02d},000','generated_title':'Semantic event','final_score':score}
|
||||
|
||||
|
||||
def test_ranked_whole_ranges_deduplicate_and_cap():
|
||||
rows = [clip(i*5,i*5+4,i) for i in range(9)] + [clip(40,44,99),clip(50,59,1),clip(0,99)]
|
||||
drafts=make_highlights(rows, Preferences(goal='highlight'), 55)
|
||||
assert len(drafts)==6
|
||||
assert drafts[0]['scenes'][0]['start']==40
|
||||
assert all(d['scenes'][0]['end']-d['scenes'][0]['start']==4 for d in drafts)
|
||||
assert all(d['origin']=='subtitle-highlight' and d['subtitles'] for d in drafts)
|
||||
|
||||
|
||||
def test_invalid_results_do_not_fabricate_full_length_highlight():
|
||||
with pytest.raises(ValueError,match='没有可用'):
|
||||
make_highlights([{},clip(2,1),clip(0,40),clip(1,2,float('nan'))],Preferences(),30)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('fails',[False,True])
|
||||
def test_combined_subtitle_outputs_share_one_attempt(root,source,monkeypatch,fails):
|
||||
calls=[]
|
||||
def run(*args):
|
||||
calls.append(args)
|
||||
if fails: raise ValueError('test failure')
|
||||
return [clip(0,1)]
|
||||
monkeypatch.setattr(jobs,'run_content',run)
|
||||
monkeypatch.setattr(jobs,'source',lambda _:source)
|
||||
monkeypatch.setattr(jobs,'mark_project',lambda *a,**k:None)
|
||||
monkeypatch.setattr(intelligence,'vision_call',lambda *a,**k:pytest.fail('vision call'))
|
||||
monkeypatch.setattr(jobs,'analyze',lambda *a,**k:pytest.fail('visual analysis'))
|
||||
store.write('p1',{'drafts':[],'events':[],'jobs':[],'analysis':{'status':'running'}})
|
||||
jobs._produce_selected('p1',{'selected_goals':['content','highlight'],'confirmed_analysis':'subtitle','confirmed_preferences':Preferences().model_dump()})
|
||||
result=store.read('p1')
|
||||
assert len(calls)==1
|
||||
assert result['analysis']['status']==('failed' if fails else 'completed')
|
||||
assert len(result['drafts'])==(0 if fails else 1)
|
||||
|
||||
from backend.tests.test_studio import client
|
||||
|
||||
def test_subtitle_highlight_confirmation_reaches_shared_editor(client,source,monkeypatch):
|
||||
from backend.services.studio import analysis_preferences as ap
|
||||
monkeypatch.setattr(ap,'load',lambda:ap.AnalysisPreferences())
|
||||
monkeypatch.setattr(intelligence,'vision_call',lambda *a,**k:pytest.fail('vision forbidden'))
|
||||
monkeypatch.setattr(jobs,'run_content',lambda *a:[clip(0,1)])
|
||||
class Immediate:
|
||||
def submit(self,fn,*args): fn(*args)
|
||||
monkeypatch.setattr(jobs,'executor',Immediate())
|
||||
imported=client.post('/studio/import',files={'video':('test.mp4',source.read_bytes(),'video/mp4')})
|
||||
assert imported.status_code==200
|
||||
pid=imported.json()['project_id'];plan=store.read(pid)['plan']
|
||||
assert client.post(f'/studio/{pid}/start',json={'plan_id':plan['id'],'analysis_mode':'subtitle','goals':['highlight']}).status_code==200
|
||||
state=client.get(f'/studio/{pid}').json()
|
||||
assert state['analysis']['status']=='completed'
|
||||
draft=state['drafts'][0]
|
||||
assert draft['origin']=='subtitle-highlight'
|
||||
draft['title']='Edited subtitle highlight'
|
||||
response=client.put(f"/studio/{pid}/drafts/{draft['id']}",json=draft)
|
||||
assert response.status_code==200,response.text
|
||||
assert response.json()['revision']==2
|
||||
@@ -71,3 +71,11 @@
|
||||
验证:108项前端测试通过,类型、ESLint和生产构建通过。真实本地前后端浏览器验证设置读取、智能模式保存和刷新持久化,付费初筛关闭;数据在/tmp/autoclip-mode-ui-check,不使用用户项目或调用模型。截图/tmp/autoclip-analysis-settings.png。确认页的模式不匹配、能力缺失和确认参数由组件交互测试覆盖,尚未完成真实视频浏览器端到端验收。
|
||||
|
||||
仍需字幕来源高光/推广适配、完整低成本旧端点治理、本地证据推荐和真实样本/跨平台验收。本项保持草稿,不能据此宣称双链路全部完成或候选可发布。
|
||||
|
||||
## 9月26日:字幕高光适配(依赖#203)
|
||||
|
||||
`codex/subtitle-highlights` 复用本次字幕管线的titled_clips,不读取旧项目残留候选,不增加视觉或文案模型调用。按已有final_score选择最多6个完整语义范围,过滤非法/越界和高度重叠范围;不按目标时长拆碎。生成origin=subtitle-highlight草稿,进入相同编辑/导出模型。内容切片与字幕高光同选时共享一次字幕管线,失败也不重复尝试。原字幕管线包含转写、文字模型和已有切片渲染,不代表整个任务免费或免渲染。
|
||||
|
||||
确认页允许字幕+高光,八语提示同步范围;推广仍明确要求视觉。字幕高光依据讲话语义,不承诺识别无对白游戏动作。
|
||||
|
||||
验证:49项字幕/路由/导入相关测试通过;补充真实导入→确认字幕高光→编辑保存API测试后新增模块5项全部通过(与前述4项重叠)。108项前端测试、类型、Lint和生产构建通过。模型与字幕分析结果模拟,源视频为测试夹具;不视为真实文字模型或真实样本效果验收。下一步补旧端点授权治理、本地推荐证据和真实样本对照,推广字幕来源仍未适配。
|
||||
|
||||
@@ -28,7 +28,7 @@ export default function PlanSummary({projectId, plan, status, onChanged, onStart
|
||||
const show=()=>{setValue({...defaultImportOptions,...plan?.overrides});setError('');setOpen(true)}
|
||||
const apply=async()=>{setBusy(true);setError('');try{await studioApi.correctPlan(projectId,value);setOpen(false);onChanged()}catch(e){setError(t(errorText(e)))}finally{setBusy(false)}}
|
||||
const start=async()=>{if(!plan||!selected.length)return;setBusy(true);setError('');try{await studioApi.confirmPlan(projectId,plan.id,selected,value,analysisMode);if(onStarted)onStarted();else onChanged()}catch(e){setError(t(errorText(e)))}finally{setBusy(false)}}
|
||||
const incompatible=analysisMode==='subtitle'?selected.some(g=>g!=='content'):selected.length===1&&selected[0]==='content'
|
||||
const incompatible=analysisMode==='subtitle'?selected.includes('promo'):selected.length===1&&selected[0]==='content'
|
||||
const unavailable=analysisMode==='visual'&&!capability?.visual_analysis
|
||||
const prefs=plan?.preferences
|
||||
return <>
|
||||
@@ -38,9 +38,9 @@ export default function PlanSummary({projectId, plan, status, onChanged, onStart
|
||||
<label className="studio-field">{t("本次分析方式")}<select disabled={busy} value={analysisMode} onChange={e=>setAnalysisMode(e.target.value as AnalysisMode)}><option value="subtitle">{t("字幕分析 · 低成本")}</option><option value="visual">{t("视觉分析")}</option></select></label>
|
||||
<p className="studio-muted">{t(analysisMode==='subtitle'?"仅分析字幕文本;无字幕时需要转写。":"发送抽样画面与文本,按模型服务商计费。")}{analysisMode==='visual'&&capability?.visual_model?` · ${capability.visual_model}`:''}</p>
|
||||
{unavailable&&<p role="alert" className="studio-error">{t("视觉模型不可用,请前往模型设置。")}</p>}
|
||||
{incompatible&&<p role="alert" className="studio-error">{t("当前内容切片使用字幕分析;高光和推广使用视觉分析。请调整方式或制作类型。")}</p>}
|
||||
{incompatible&&<p role="alert" className="studio-error">{t("字幕分析支持内容切片和字幕高光;推广仍需视觉分析。请调整方式或制作类型。")}</p>}
|
||||
<p className="studio-muted">{plan.suggested_goals.length?t("已勾选建议制作的类型,你可以取消或补选。"):t("本次未能自动推荐,请按素材内容选择制作类型。")}{' '}{t("确认后才开始详细理解与剪辑。")}</p>
|
||||
<div className="studio-output-choices">{choices.map(({goal,description})=><label key={goal} className={`studio-output-choice ${selected.includes(goal)?'is-selected':''}`}><input type="checkbox" checked={selected.includes(goal)} disabled={busy} onChange={e=>setSelected(e.target.checked?[...selected,goal]:selected.filter(g=>g!==goal))}/><b>{t(goalLabels[goal])}</b>{plan.suggested_goals.includes(goal)&&<small>{t("建议")}</small>}<span className="studio-muted">{t(description)}</span></label>)}</div>
|
||||
<div className="studio-output-choices">{choices.map(({goal,description})=><label key={goal} className={`studio-output-choice ${selected.includes(goal)?'is-selected':''}`}><input type="checkbox" checked={selected.includes(goal)} disabled={busy} onChange={e=>setSelected(e.target.checked?[...selected,goal]:selected.filter(g=>g!==goal))}/><b>{t(goalLabels[goal])}</b>{plan.suggested_goals.includes(goal)&&<small>{t("建议")}</small>}<span className="studio-muted">{t(goal==='highlight'&&analysisMode==='subtitle'?'按语音与内容含义,提炼完整片段':description)}</span></label>)}</div>
|
||||
<details className="studio-details"><summary>{t("调整制作参数(可选)")}</summary><ImportPreferences value={value} onChange={setValue} hideGoal/></details>
|
||||
<div className="studio-row studio-confirm-footer"><span className="studio-muted">{selected.length?t('将制作 {{count}} 类内容', { count: selected.length }):t("至少选择一种制作类型")}</span><Btn variant="cta" disabled={!selected.length||busy||incompatible||unavailable} loading={busy} onClick={start}>{t("确认并开始制作")}</Btn></div>
|
||||
</> : <div className="studio-actions">{plan?.selected_goals&&<span className="studio-muted">{plan.selected_goals.map(g=>t(goalLabels[g])).join(' / ')}</span>}<Btn size="sm" disabled={running} onClick={show}>{t("修改方案")}</Btn></div>}
|
||||
|
||||
@@ -1113,6 +1113,6 @@
|
||||
"关闭时不调用视觉初筛,确认后才开始正式分析。": "When off, no visual screening runs. Full analysis starts after confirmation.",
|
||||
"分析偏好已保存": "Analysis preference saved",
|
||||
"视觉模型不可用,请前往模型设置。": "Visual model unavailable. Open model settings.",
|
||||
"当前内容切片使用字幕分析;高光和推广使用视觉分析。请调整方式或制作类型。": "Currently, content clips use subtitles; highlights and promos use visual analysis. Adjust the method or output types.",
|
||||
"按字幕链路处理,未调用视觉初筛;正式制作时使用已有字幕或转写,尚未验证语音内容。": "Uses the subtitle workflow without visual screening. Production uses existing subtitles or transcription; speech content has not been verified."
|
||||
"按字幕链路处理,未调用视觉初筛;正式制作时使用已有字幕或转写,尚未验证语音内容。": "Uses the subtitle workflow without visual screening. Production uses existing subtitles or transcription; speech content has not been verified.",
|
||||
"字幕分析支持内容切片和字幕高光;推广仍需视觉分析。请调整方式或制作类型。": "Subtitle analysis supports content clips and spoken highlights. Promos still require visual analysis. Adjust the method or output types."
|
||||
}
|
||||
|
||||
@@ -1113,6 +1113,6 @@
|
||||
"关闭时不调用视觉初筛,确认后才开始正式分析。": "Desactivado: sin evaluación visual. El análisis completo comienza tras confirmar.",
|
||||
"分析偏好已保存": "Preferencia guardada",
|
||||
"视觉模型不可用,请前往模型设置。": "Modelo visual no disponible. Abre los ajustes del modelo.",
|
||||
"当前内容切片使用字幕分析;高光和推广使用视觉分析。请调整方式或制作类型。": "Actualmente, los clips usan subtítulos; los momentos destacados y promociones usan análisis visual. Ajusta el método o los tipos.",
|
||||
"按字幕链路处理,未调用视觉初筛;正式制作时使用已有字幕或转写,尚未验证语音内容。": "Usa subtítulos sin evaluación visual. La producción usará subtítulos o transcripción; el habla aún no se ha verificado."
|
||||
"按字幕链路处理,未调用视觉初筛;正式制作时使用已有字幕或转写,尚未验证语音内容。": "Usa subtítulos sin evaluación visual. La producción usará subtítulos o transcripción; el habla aún no se ha verificado.",
|
||||
"字幕分析支持内容切片和字幕高光;推广仍需视觉分析。请调整方式或制作类型。": "El análisis de subtítulos permite clips y momentos destacados del habla. Las promociones requieren análisis visual. Ajusta el método o los tipos."
|
||||
}
|
||||
|
||||
@@ -1113,6 +1113,6 @@
|
||||
"关闭时不调用视觉初筛,确认后才开始正式分析。": "Désactivé : aucun préexamen visuel. L’analyse complète attend votre confirmation.",
|
||||
"分析偏好已保存": "Préférence enregistrée",
|
||||
"视觉模型不可用,请前往模型设置。": "Modèle visuel indisponible. Ouvrez les paramètres du modèle.",
|
||||
"当前内容切片使用字幕分析;高光和推广使用视觉分析。请调整方式或制作类型。": "Actuellement, les extraits utilisent les sous-titres ; les temps forts et promos utilisent l’analyse visuelle. Ajustez la méthode ou les types.",
|
||||
"按字幕链路处理,未调用视觉初筛;正式制作时使用已有字幕或转写,尚未验证语音内容。": "Utilise les sous-titres sans préexamen visuel. La production utilisera les sous-titres ou une transcription ; la parole n’a pas été vérifiée."
|
||||
"按字幕链路处理,未调用视觉初筛;正式制作时使用已有字幕或转写,尚未验证语音内容。": "Utilise les sous-titres sans préexamen visuel. La production utilisera les sous-titres ou une transcription ; la parole n’a pas été vérifiée.",
|
||||
"字幕分析支持内容切片和字幕高光;推广仍需视觉分析。请调整方式或制作类型。": "Les sous-titres permettent des extraits et temps forts parlés. Les promos nécessitent une analyse visuelle. Ajustez la méthode ou les types."
|
||||
}
|
||||
|
||||
@@ -1113,6 +1113,6 @@
|
||||
"关闭时不调用视觉初筛,确认后才开始正式分析。": "オフの場合は映像の予備分析を行いません。本分析は確認後に始まります。",
|
||||
"分析偏好已保存": "分析設定を保存しました",
|
||||
"视觉模型不可用,请前往模型设置。": "映像モデルが利用できません。モデル設定を確認してください。",
|
||||
"当前内容切片使用字幕分析;高光和推广使用视觉分析。请调整方式或制作类型。": "現在、コンテンツ切り抜きは字幕分析、ハイライトとプロモーションは映像分析を使います。方法または制作タイプを変更してください。",
|
||||
"按字幕链路处理,未调用视觉初筛;正式制作时使用已有字幕或转写,尚未验证语音内容。": "映像の予備分析なしで字幕処理を使います。制作時に字幕または文字起こしを使用します。音声内容は未確認です。"
|
||||
"按字幕链路处理,未调用视觉初筛;正式制作时使用已有字幕或转写,尚未验证语音内容。": "映像の予備分析なしで字幕処理を使います。制作時に字幕または文字起こしを使用します。音声内容は未確認です。",
|
||||
"字幕分析支持内容切片和字幕高光;推广仍需视觉分析。请调整方式或制作类型。": "字幕分析では切り抜きと言葉のハイライトを作れます。プロモーションには映像分析が必要です。方法または制作タイプを変更してください。"
|
||||
}
|
||||
|
||||
@@ -1113,6 +1113,6 @@
|
||||
"关闭时不调用视觉初筛,确认后才开始正式分析。": "끄면 영상 사전 분석을 호출하지 않습니다. 본 분석은 확인 후 시작됩니다.",
|
||||
"分析偏好已保存": "분석 설정 저장됨",
|
||||
"视觉模型不可用,请前往模型设置。": "영상 모델을 사용할 수 없습니다. 모델 설정을 확인하세요.",
|
||||
"当前内容切片使用字幕分析;高光和推广使用视觉分析。请调整方式或制作类型。": "현재 콘텐츠 클립은 자막 분석을, 하이라이트와 홍보 영상은 영상 분석을 사용합니다. 방식 또는 제작 유형을 조정하세요.",
|
||||
"按字幕链路处理,未调用视觉初筛;正式制作时使用已有字幕或转写,尚未验证语音内容。": "영상 사전 분석 없이 자막 경로를 사용합니다. 제작 시 자막 또는 전사를 사용하며 음성 내용은 아직 확인되지 않았습니다."
|
||||
"按字幕链路处理,未调用视觉初筛;正式制作时使用已有字幕或转写,尚未验证语音内容。": "영상 사전 분석 없이 자막 경로를 사용합니다. 제작 시 자막 또는 전사를 사용하며 음성 내용은 아직 확인되지 않았습니다.",
|
||||
"字幕分析支持内容切片和字幕高光;推广仍需视觉分析。请调整方式或制作类型。": "자막 분석은 콘텐츠 클립과 발화 하이라이트를 지원합니다. 홍보 영상은 영상 분석이 필요합니다. 방식 또는 유형을 조정하세요."
|
||||
}
|
||||
|
||||
@@ -1113,6 +1113,6 @@
|
||||
"关闭时不调用视觉初筛,确认后才开始正式分析。": "Desativado: sem triagem visual. A análise completa começa após a confirmação.",
|
||||
"分析偏好已保存": "Preferência salva",
|
||||
"视觉模型不可用,请前往模型设置。": "Modelo visual indisponível. Abra as configurações do modelo.",
|
||||
"当前内容切片使用字幕分析;高光和推广使用视觉分析。请调整方式或制作类型。": "Atualmente, clipes usam legendas; destaques e promoções usam análise visual. Ajuste o método ou os tipos.",
|
||||
"按字幕链路处理,未调用视觉初筛;正式制作时使用已有字幕或转写,尚未验证语音内容。": "Usa legendas sem triagem visual. A produção usará legendas ou transcrição; a fala ainda não foi verificada."
|
||||
"按字幕链路处理,未调用视觉初筛;正式制作时使用已有字幕或转写,尚未验证语音内容。": "Usa legendas sem triagem visual. A produção usará legendas ou transcrição; a fala ainda não foi verificada.",
|
||||
"字幕分析支持内容切片和字幕高光;推广仍需视觉分析。请调整方式或制作类型。": "A análise de legendas permite clipes e destaques da fala. Promoções exigem análise visual. Ajuste o método ou os tipos."
|
||||
}
|
||||
|
||||
@@ -1113,6 +1113,6 @@
|
||||
"关闭时不调用视觉初筛,确认后才开始正式分析。": "Если выключено, визуальный отбор не выполняется. Полный анализ — после подтверждения.",
|
||||
"分析偏好已保存": "Настройки анализа сохранены",
|
||||
"视觉模型不可用,请前往模型设置。": "Визуальная модель недоступна. Откройте настройки модели.",
|
||||
"当前内容切片使用字幕分析;高光和推广使用视觉分析。请调整方式或制作类型。": "Сейчас фрагменты используют субтитры, а яркие моменты и промо — визуальный анализ. Измените способ или тип результата.",
|
||||
"按字幕链路处理,未调用视觉初筛;正式制作时使用已有字幕或转写,尚未验证语音内容。": "Используется обработка субтитров без визуального отбора. При создании используются субтитры или транскрипция; речь ещё не проверена."
|
||||
"按字幕链路处理,未调用视觉初筛;正式制作时使用已有字幕或转写,尚未验证语音内容。": "Используется обработка субтитров без визуального отбора. При создании используются субтитры или транскрипция; речь ещё не проверена.",
|
||||
"字幕分析支持内容切片和字幕高光;推广仍需视觉分析。请调整方式或制作类型。": "Анализ субтитров поддерживает фрагменты и яркие моменты речи. Для промо нужен визуальный анализ. Измените способ или тип."
|
||||
}
|
||||
|
||||
@@ -1113,6 +1113,6 @@
|
||||
"关闭时不调用视觉初筛,确认后才开始正式分析。": "关闭时不调用视觉初筛,确认后才开始正式分析。",
|
||||
"分析偏好已保存": "分析偏好已保存",
|
||||
"视觉模型不可用,请前往模型设置。": "视觉模型不可用,请前往模型设置。",
|
||||
"当前内容切片使用字幕分析;高光和推广使用视觉分析。请调整方式或制作类型。": "当前内容切片使用字幕分析;高光和推广使用视觉分析。请调整方式或制作类型。",
|
||||
"按字幕链路处理,未调用视觉初筛;正式制作时使用已有字幕或转写,尚未验证语音内容。": "按字幕链路处理,未调用视觉初筛;正式制作时使用已有字幕或转写,尚未验证语音内容。"
|
||||
"按字幕链路处理,未调用视觉初筛;正式制作时使用已有字幕或转写,尚未验证语音内容。": "按字幕链路处理,未调用视觉初筛;正式制作时使用已有字幕或转写,尚未验证语音内容。",
|
||||
"字幕分析支持内容切片和字幕高光;推广仍需视觉分析。请调整方式或制作类型。": "字幕分析支持内容切片和字幕高光;推广仍需视觉分析。请调整方式或制作类型。"
|
||||
}
|
||||
|
||||
@@ -20,10 +20,10 @@ function view(mode,goals,available=true){
|
||||
return {states,calls,render:()=>nodes(render())}
|
||||
}
|
||||
test('subtitle selection blocks visual goals without silently changing choices',()=>{
|
||||
const x=view('subtitle',['highlight']);let nodes=x.render()
|
||||
const x=view('subtitle',['promo']);let nodes=x.render()
|
||||
assert.equal(nodes.find(n=>n.props?.children==='确认并开始制作').props.disabled,true)
|
||||
nodes.find(n=>n.type==='select').props.onChange({target:{value:'visual'}})
|
||||
assert.deepEqual(x.states[4],['highlight'])
|
||||
assert.deepEqual(x.states[4],['promo'])
|
||||
assert.equal(x.render().find(n=>n.props?.children==='确认并开始制作').props.disabled,false)
|
||||
})
|
||||
test('missing capability blocks visual confirmation but subtitle content remains available',()=>{
|
||||
|
||||
Reference in New Issue
Block a user