Files
olivv-cs-ComfyUI-FunPack/tools/motion_counts.py
T

80 lines
3.5 KiB
Python

"""Counts-only check: how many shots in your projects describe no real motion.
Read-only. Prints numbers, never prompt text. Not shipped to users.
python tools/motion_counts.py # ~/.funpack_movie/projects
python tools/motion_counts.py /path/to/projects # another project folder
A "shot" is a [Shot N] block of a scene's text, or the whole text when it has none. A shot is STATIC when it
has no verb except stative ones (is, stand, sit, look, hold, wear, have...), found with the spaCy tagger
Shot camera already uses (en_core_web_sm). Ratings are the labels the Composer stored: "liked*" and
"disliked*" are split out, any other label (a number, say) is counted under "other rating".
"""
import collections
import glob
import json
import os
import re
import sys
STATIVE = frozenset("""be stand sit look hold wear have lie rest appear seem remain stay face feature show sport dress lean wait
gaze stare hang display contain surround fill""".split())
SHOT = re.compile(r"\[Shot \d+[^\]]*\]")
def bucket(rating):
r = str(rating or "").strip().lower()
if not r:
return "unrated"
return "disliked" if r.startswith("dislik") else "liked" if r.startswith("lik") else "other rating"
def shots(text):
parts = [p.strip() for p in SHOT.split(text or "")]
return [p for p in parts if p]
def main(folder):
try:
import spacy
nlp = spacy.load("en_core_web_sm")
except Exception as exc: # noqa: BLE001 - say what is missing, plainly
sys.exit(f"needs spaCy and its small English model ({type(exc).__name__}): pip install spacy && python -m spacy download en_core_web_sm")
files = sorted(glob.glob(os.path.join(os.path.expanduser(folder), "*.json")))
seen, words = collections.Counter(), collections.defaultdict(list)
scenes = 0
for path in files:
try:
with open(path, encoding="utf-8") as fh:
project = json.load(fh)
except (OSError, ValueError):
seen["unreadable project files"] += 1
continue
for scene in project.get("scenes", []) if isinstance(project, dict) else []:
if not isinstance(scene, dict):
continue
scenes += 1
group = bucket(scene.get("rating"))
rated = scene.get("rated") if isinstance(scene.get("rated"), dict) else {}
text = (rated.get("text") if group != "unrated" else None) or scene.get("text") or "" # what the rating is about
for block in shots(text):
doc = nlp(block)
moving = any(t.pos_ == "VERB" and t.lemma_.lower() not in STATIVE for t in doc)
seen[(group, "moving" if moving else "static")] += 1
words[group].append(len(block.split()))
print(f"project files {len(files)}, scenes {scenes}, unreadable {seen.pop('unreadable project files', 0)}")
print(f"{'group':<14}{'shots':>7}{'static':>8}{'static %':>10}{'avg words':>11}")
total = [0, 0]
for group in ("liked", "disliked", "other rating", "unrated"):
s, m = seen[(group, "static")], seen[(group, "moving")]
n = s + m
total[0] += s
total[1] += n
w = words[group]
print(f"{group:<14}{n:>7}{s:>8}{(100 * s / n if n else 0):>9.0f}%{(sum(w) / len(w) if w else 0):>11.1f}")
print(f"{'all':<14}{total[1]:>7}{total[0]:>8}{(100 * total[0] / total[1] if total[1] else 0):>9.0f}%")
if __name__ == "__main__":
main(sys.argv[1] if len(sys.argv) > 1 else "~/.funpack_movie/projects")