Prompt enhancer: Dynamic style for more motion; counts-only motion check script
This commit is contained in:
@@ -0,0 +1,79 @@
|
||||
"""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")
|
||||
Reference in New Issue
Block a user