flowocr 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- flowocr/__init__.py +15 -0
- flowocr/analyze/__init__.py +8 -0
- flowocr/analyze/align.py +151 -0
- flowocr/analyze/build_tracks.py +2886 -0
- flowocr/analyze/cluster_layers.py +155 -0
- flowocr/analyze/game_align.py +652 -0
- flowocr/analyze/gamescript.py +1220 -0
- flowocr/analyze/gtdbundle.py +306 -0
- flowocr/analyze/match.py +67 -0
- flowocr/analyze/matchers/__init__.py +5 -0
- flowocr/analyze/matchers/gametext.py +70 -0
- flowocr/analyze/merge_nameplate.py +178 -0
- flowocr/analyze/models/slot_pair.json +148 -0
- flowocr/analyze/nameplate.py +148 -0
- flowocr/analyze/pair_features.py +168 -0
- flowocr/analyze/pair_model.py +127 -0
- flowocr/analyze/refine_boundaries.py +409 -0
- flowocr/analyze/script_align.py +641 -0
- flowocr/analyze/scriptmatch.py +1018 -0
- flowocr/analyze/slot_learned.py +306 -0
- flowocr/analyze/slot_lines.py +251 -0
- flowocr/analyze/slot_modes.py +143 -0
- flowocr/analyze/slot_pairs.py +562 -0
- flowocr/analyze/slot_veto.py +104 -0
- flowocr/analyze/uigate.py +351 -0
- flowocr/artifacts/__init__.py +5 -0
- flowocr/artifacts/evalkit.py +123 -0
- flowocr/artifacts/matchedio.py +75 -0
- flowocr/artifacts/srtio.py +166 -0
- flowocr/artifacts/tracksio.py +345 -0
- flowocr/extensions.py +77 -0
- flowocr/extract/__init__.py +8 -0
- flowocr/extract/childproc.py +118 -0
- flowocr/extract/decode_proc.py +511 -0
- flowocr/extract/decode_shards.py +574 -0
- flowocr/extract/detpost.py +215 -0
- flowocr/extract/edge_proc.py +314 -0
- flowocr/extract/edge_refine.py +598 -0
- flowocr/extract/fast_det.py +214 -0
- flowocr/extract/ffcheck.py +87 -0
- flowocr/extract/framegrid.py +265 -0
- flowocr/extract/framesource.py +1264 -0
- flowocr/extract/ocr_args.py +754 -0
- flowocr/extract/ocr_complete.py +231 -0
- flowocr/extract/ocr_parallel.py +208 -0
- flowocr/extract/ort_server.py +632 -0
- flowocr/extract/ortclient.py +363 -0
- flowocr/extract/ptsclock.py +150 -0
- flowocr/extract/recdecode.py +78 -0
- flowocr/extract/recort.py +162 -0
- flowocr/extract/recpack.py +94 -0
- flowocr/extract/recpool.py +206 -0
- flowocr/extract/recprep.py +71 -0
- flowocr/extract/refine_video.py +271 -0
- flowocr/extract/regions.py +387 -0
- flowocr/extract/reuse_v2.py +593 -0
- flowocr/extract/run_groups.py +247 -0
- flowocr/extract/run_ocr2.py +1605 -0
- flowocr/extract/supervisor.py +261 -0
- flowocr/extract/timeline.py +84 -0
- flowocr/extract/typewriter_fuse.py +394 -0
- flowocr/models.py +263 -0
- flowocr/output/__init__.py +3 -0
- flowocr/output/export.py +205 -0
- flowocr/output/layout.py +210 -0
- flowocr/output/presets/__init__.py +6 -0
- flowocr/output/presets/_overlay.py +40 -0
- flowocr/output/presets/default.py +21 -0
- flowocr/output/presets/default_all.py +20 -0
- flowocr/output/presets/dev.py +21 -0
- flowocr/output/presets/matched_srt.py +32 -0
- flowocr/output/presets/script.py +20 -0
- flowocr/output/presets/srt_main.py +33 -0
- flowocr/output/render.py +82 -0
- flowocr/output/run_srt.py +83 -0
- flowocr/output/script.py +541 -0
- flowocr/paths.py +168 -0
- flowocr/provenance.py +119 -0
- flowocr/typeset/__init__.py +9 -0
- flowocr/typeset/__main__.py +38 -0
- flowocr/typeset/assfile.py +216 -0
- flowocr/typeset/core.py +821 -0
- flowocr/typeset/fx/__init__.py +11 -0
- flowocr-0.1.0.dist-info/METADATA +109 -0
- flowocr-0.1.0.dist-info/RECORD +91 -0
- flowocr-0.1.0.dist-info/WHEEL +5 -0
- flowocr-0.1.0.dist-info/entry_points.txt +10 -0
- flowocr-0.1.0.dist-info/licenses/LICENSE +674 -0
- flowocr-0.1.0.dist-info/licenses/LICENSES/Apache-2.0.txt +201 -0
- flowocr-0.1.0.dist-info/licenses/LICENSES/PP-OCRv6-NOTICE.md +18 -0
- flowocr-0.1.0.dist-info/top_level.txt +1 -0
flowocr/output/render.py
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"""阶段 3 的薄入口:拿一份**阶段 2 的产物**,跑一个输出预设。
|
|
2
|
+
|
|
3
|
+
python -m flowocr.output.render out/x/x-tracks.json --outdir tmp/x # 默认预设 default
|
|
4
|
+
python -m flowocr.output.render out/x/x-tracks.json --preset srt_main --outdir tmp/x
|
|
5
|
+
python -m flowocr.output.render out/x/x-matched.json --preset dev --outdir tmp/x --opt lang=cn
|
|
6
|
+
python -m flowocr.output.render out/x/x-tracks.json --preset D:/mine/preset.py --outdir tmp/x --opt start=full
|
|
7
|
+
|
|
8
|
+
吃两种产物(2026-09-22 起,Codex 审计 P2:原来写死 `tracksio.load`,匹配结果连预设的门都进不去):
|
|
9
|
+
|
|
10
|
+
| schema | 读法 | 内置预设 |
|
|
11
|
+
| --- | --- | --- |
|
|
12
|
+
| `flowocr-tracks/1` | `tracksio.load` | 叠加三个(`default` / `default_all` / `dev`,叠 OCR 原文)、`script`(只写字幕稿)、`srt_main`(只取主轨) |
|
|
13
|
+
| `flowocr-matched/2` | `matchedio.load` | 叠加三个(叠剧本译文)、`script`、`matched_srt`(拍平成 SRT) |
|
|
14
|
+
| 别的(用户匹配器自己的格式) | 原样 `json.load`,**不校验** | 只有声明了这个 schema 的预设收(见 `ACCEPTS`) |
|
|
15
|
+
|
|
16
|
+
不给 `--preset` 时是 **`default`**(生产默认,owner 2026-09-24):只留主轨的叠加字幕。叠加预设写两份——
|
|
17
|
+
可编辑的字幕稿(`*.script.ass`,阶段 3)和最终 ASS(阶段 4 `flowocr.typeset`);三个的差别见
|
|
18
|
+
`flowocr.output.presets._overlay` 和 script-fx 计划。
|
|
19
|
+
|
|
20
|
+
预设可以声明 `ACCEPTS = ("flowocr-tracks/1", …)`:喂进来的产物不在里头就**当场报错**,
|
|
21
|
+
而不是让它在预设内部以 `KeyError` 的形式炸("失败不冒充成功",计划)。不声明就是什么都收。
|
|
22
|
+
`--opt k=v` 原样交给预设的 `options`(值是字符串,预设自己解释);预设要什么参数、默认多少,是它自己的事。
|
|
23
|
+
"""
|
|
24
|
+
from __future__ import annotations
|
|
25
|
+
|
|
26
|
+
import argparse
|
|
27
|
+
import json
|
|
28
|
+
from pathlib import Path
|
|
29
|
+
|
|
30
|
+
from flowocr import extensions
|
|
31
|
+
from flowocr.artifacts import matchedio, tracksio
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def load_doc(path: Path) -> dict:
|
|
35
|
+
"""按 `schema` 挑读法。认得的两种各自校验版本和必需字段,不认得的原样返回(用户匹配器的格式)。"""
|
|
36
|
+
raw = json.loads(Path(path).read_text(encoding="utf-8"))
|
|
37
|
+
schema = raw.get("schema") if isinstance(raw, dict) else None
|
|
38
|
+
if schema == tracksio.SCHEMA:
|
|
39
|
+
tracksio.validate(raw) # 不再 `tracksio.load` 一遍:整片的 tracks.json 几十 MB,解析两次白花
|
|
40
|
+
elif schema == matchedio.SCHEMA:
|
|
41
|
+
matchedio.validate(raw, where=str(path))
|
|
42
|
+
return raw
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def check_accepts(preset, doc: dict, spec: str) -> None:
|
|
46
|
+
"""预设声明了 `ACCEPTS` 就核一下,喂错产物当场说清楚。"""
|
|
47
|
+
accepts = getattr(preset, "ACCEPTS", None)
|
|
48
|
+
if accepts is None:
|
|
49
|
+
return
|
|
50
|
+
got = doc.get("schema") if isinstance(doc, dict) else None
|
|
51
|
+
if got not in tuple(accepts):
|
|
52
|
+
raise SystemExit(f"预设 `{spec}` 吃的是 {list(accepts)},给的是 {got!r}——"
|
|
53
|
+
f"阶段 2 的哪份产物喂给哪个预设,见 flowocr.output.render 文件头")
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def main(argv: list[str] | None = None) -> int:
|
|
57
|
+
ap = argparse.ArgumentParser(description="跑一个输出预设")
|
|
58
|
+
ap.add_argument("doc", metavar="产物", help="阶段 2 的产物:*-tracks.json 或 *-matched.json")
|
|
59
|
+
ap.add_argument("--preset", default="default",
|
|
60
|
+
help="内置名(%s)/ 模块名 / .py 路径" % "、".join(extensions.builtin_names("preset")))
|
|
61
|
+
ap.add_argument("--outdir", default="", help="默认写在产物旁边")
|
|
62
|
+
ap.add_argument("--opt", action="append", default=[], metavar="K=V", help="给预设的选项,可多次")
|
|
63
|
+
a = ap.parse_args(argv)
|
|
64
|
+
opts = {}
|
|
65
|
+
for kv in a.opt:
|
|
66
|
+
if "=" not in kv:
|
|
67
|
+
ap.error(f"--opt 要 K=V:{kv}")
|
|
68
|
+
k, v = kv.split("=", 1)
|
|
69
|
+
opts[k] = v
|
|
70
|
+
preset = extensions.load(a.preset, "preset")
|
|
71
|
+
src = Path(a.doc)
|
|
72
|
+
doc = load_doc(src)
|
|
73
|
+
check_accepts(preset, doc, a.preset)
|
|
74
|
+
outdir = Path(a.outdir) if a.outdir else src.parent
|
|
75
|
+
written = preset.render(doc, outdir, opts)
|
|
76
|
+
for p in written:
|
|
77
|
+
print(p)
|
|
78
|
+
return 0
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
if __name__ == "__main__":
|
|
82
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
"""从 build_tracks 切好的段直接写 SRT(一级产物旁的区域 / 主轨 / 名牌 SRT),以及清掉上一轮的旧产物。
|
|
2
|
+
|
|
3
|
+
2026-09-22 从 `flowocr.analyze.build_tracks` 原样搬出来(正规化阶段 C 第二刀,project-structure:
|
|
4
|
+
阶段 2 产分析结果,写字幕是阶段 3 的事)。**只投影,不判断**:段怎么切、哪些行是常驻 UI 都是调用方判好传进来的。
|
|
5
|
+
`export.py` 是从 `<tag>-tracks.json` 投影的那条路;这里是 build 时从内存里的 run 直接写,两条路产同一批文件。
|
|
6
|
+
段里的元素是 `build_tracks.Run`(鸭子类型:只用 `.text`)。
|
|
7
|
+
"""
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
|
|
12
|
+
from flowocr.artifacts import srtio
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
DROPPED_UI: list[tuple[str, set[str], int, int]] = []
|
|
16
|
+
"""`(产物名, 判为 UI 的行, 删掉的行数, 整条被删的 cue 数)`——
|
|
17
|
+
**删了什么必须打出来**:上一版一行日志都不打,于是"105 个空的区域 SRT"
|
|
18
|
+
在磁盘上躺了一整夜没人看见(methodology-audit-2 报告)。"""
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def write_srt_segments(path: Path, segs: list[tuple[int, int, list]],
|
|
22
|
+
ui: set[str], ui_ids: set[int] | None = None,
|
|
23
|
+
idx: dict[int, int] | None = None) -> int:
|
|
24
|
+
"""**投影**:把已经切好的段按 `ui`(这条轨判为常驻 UI 的行)剔一遍再写 SRT。
|
|
25
|
+
|
|
26
|
+
返回**写了几条 cue**——调用方应当把它打印出来,好让文档里的数
|
|
27
|
+
可以直接用 `grep -c -- '-->'` 数回来(boundary-refinement 报告)。
|
|
28
|
+
"""
|
|
29
|
+
before_cues, before_lines = len(segs), sum(len(a) for _, _, a in segs)
|
|
30
|
+
def drop(r) -> bool:
|
|
31
|
+
return (r.text.strip() in ui
|
|
32
|
+
or (ui_ids is not None and idx is not None and idx.get(id(r)) in ui_ids))
|
|
33
|
+
|
|
34
|
+
if ui or ui_ids:
|
|
35
|
+
segs = [(s, e, keep) for s, e, keep in
|
|
36
|
+
((s, e, [r for r in a if not drop(r)]) for s, e, a in segs) if keep]
|
|
37
|
+
dl = before_lines - sum(len(a) for _, _, a in segs)
|
|
38
|
+
if dl:
|
|
39
|
+
DROPPED_UI.append((Path(path).name, ui, dl, before_cues - len(segs)))
|
|
40
|
+
return srtio.write_srt_blocks(path, ((s, e, [r.text for r in active])
|
|
41
|
+
for s, e, active in segs))
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def clear_stale_srts(outdir: Path, tag: str) -> int:
|
|
45
|
+
"""把上一轮的 `{tag}-region*.srt`(含已去掉的回抠 CLI 留下的 `-refined.srt`、`flowocr-export --start full` 的 `-full.srt`)、
|
|
46
|
+
`{tag}-main.srt`、`{tag}-nameplate.srt`(及它们的 `-full.srt`)、已去掉的回抠 CLI 另存过的 `{tag}-tracks-refined.json`、
|
|
47
|
+
作废的 `{tag}-provenance.json` 和上一轮的 `{tag}-tracks.json` 删掉,返回删了几个。
|
|
48
|
+
|
|
49
|
+
`{tag}-tracks.json` 也要删:建轨最后才写它(回抠结算、SRT 重出都成功之后),中途失败时留着上一轮的那份,
|
|
50
|
+
驱动判缓存就会拿它配这一轮写了一半的 SRT(2026-09-26 复查)。`-full.srt` 是它的派生物,同理。
|
|
51
|
+
|
|
52
|
+
`{tag}-main.srt` 也要删:它只在 `--main-band` 打开时才产,**关掉之后
|
|
53
|
+
旧的那份会留在原地**,而 provenance 里的 main_srt 已经指回区域文件——
|
|
54
|
+
磁盘上于是躺着一份没人认领、看着却很正经的主轨。这正是 2026-09-07
|
|
55
|
+
那次"字典序捡到隔夜坏产物"的同一形状,只是换了个文件名。
|
|
56
|
+
|
|
57
|
+
为什么必须删(2026-09-07 实测的代价):文件名里带 label,**label 一变就是
|
|
58
|
+
新文件名**,旧的留在原地。`out/yuka-f4/` 里于是同时躺着
|
|
59
|
+
`f4-region01-static-overlay.srt`(09-06,17,748 条,`gap_frames` 修好之前的坏轨)
|
|
60
|
+
和 `f4-region01-subtitle.srt`(当天,4,387 条);而 `head2head.sh` 那时按
|
|
61
|
+
`f4-region01-*.srt | head -1` 取主轨,字典序 `sta` < `sub`——
|
|
62
|
+
**整部 input4 的基线是拿一份隔夜的坏产物量的,一行报错都没有。**
|
|
63
|
+
|
|
64
|
+
只删自己这个 tag 的区域 SRT:同一个 outdir 里可能有别的 tag 的产物。
|
|
65
|
+
"""
|
|
66
|
+
stale = sorted(outdir.glob(f"{tag}-region*.srt"))
|
|
67
|
+
# `-tracks-refined.json`(回抠 CLI 另存的那份;CLI 09-26 已去掉,旧目录里还可能有):它出自上一版的轨,重建之后就过时了,
|
|
68
|
+
# 留着就是又一份看着正经的一级产物(区域的 `-refined.srt` 已被上面的 `region*` 通配带走)
|
|
69
|
+
for name in (f"{tag}-main.srt", f"{tag}-nameplate.srt", f"{tag}-main-full.srt", f"{tag}-nameplate-full.srt",
|
|
70
|
+
f"{tag}-provenance.json", f"{tag}-tracks-refined.json", f"{tag}-tracks.json"):
|
|
71
|
+
# `-nameplate.srt` 同 `-main.srt` 的理由(2026-09-20 复审第 3 条):它只在挑中了名牌时才产,
|
|
72
|
+
# **`--no-nameplate-mark` 重建、或者这一版一条都没挑中时,旧的那份会留在原地**。
|
|
73
|
+
# 现在 `merge_nameplate` 从 JSON 取名牌轨、取不到就退出,所以不会被静默读到;
|
|
74
|
+
# 但"旧文件留在原地、看着很正经"就是 09-07 那次的形状,一起删。
|
|
75
|
+
# `-provenance.json` **已经并进 `-tracks.json`**(`output-format-plan(已归档)`,
|
|
76
|
+
# 不做兼容)。旧的那份留在原地最危险:它看着很正经,而且指向的 main_srt
|
|
77
|
+
# 可能是上一版的——正是"字典序捡到隔夜坏产物"的同一形状。
|
|
78
|
+
p = outdir / name
|
|
79
|
+
if p.exists():
|
|
80
|
+
stale.append(p)
|
|
81
|
+
for p in stale:
|
|
82
|
+
p.unlink()
|
|
83
|
+
return len(stale)
|