feat(motion): put full-screen motions on an opaque backdrop
A full-screen motion is a layer over the A-roll: without an opaque background covering the whole frame, the video shows through.
c9cb501 parent: 5e269af modified
backend/studio/modules/projects/montage_tasks.py +1 -0 | @@ -36,6 +36,7 @@ setTimeout, de Date.now(), de Math.random(), de repeat infini. | ||
| 36 | 36 | - Entrée ET sortie animées dans la composition (c'est la transition) : sortie dans les 0,4 dernières \ |
| 37 | 37 | secondes. Garde 80 px de marge avec les bords. Respecte la place du beat (plein écran, tiers gauche \ |
| 38 | 38 | ou droit, bandeau haut ou bas) : hors plein écran, rien ne passe sur le visage. |
| 39 | +- Plein écran : la composition est un calque qui recouvre la vidéo. Son fond est un aplat OPAQUE qui couvre tout le cadre (1920×1080, sans marge, aux couleurs de la charte : var(--card) ou var(--card-2)), sinon on voit la vidéo et son visage à travers. Le fond entre en premier et sort en dernier, avec la composition ; la marge de 80 px ne vaut que pour ce qui est posé dessus. | |
| 39 | 40 | - Pas une pastille de texte : schéma, chiffre qui se construit, liste qui se coche, comparaison, \ |
| 40 | 41 | chronologie… |
| 41 | 42 | - Montre, ne dis pas. Le spectateur entend déjà la voix et voit déjà l'image : un texte qui redit l'une \ |
| @@ -36,6 +36,7 @@ setTimeout, de Date.now(), de Math.random(), de repeat infini. | |||
| 36 | - Entrée ET sortie animées dans la composition (c'est la transition) : sortie dans les 0,4 dernières \ | 36 | - Entrée ET sortie animées dans la composition (c'est la transition) : sortie dans les 0,4 dernières \ |
| 37 | secondes. Garde 80 px de marge avec les bords. Respecte la place du beat (plein écran, tiers gauche \ | 37 | secondes. Garde 80 px de marge avec les bords. Respecte la place du beat (plein écran, tiers gauche \ |
| 38 | ou droit, bandeau haut ou bas) : hors plein écran, rien ne passe sur le visage. | 38 | ou droit, bandeau haut ou bas) : hors plein écran, rien ne passe sur le visage. |
| 39 | +- Plein écran : la composition est un calque qui recouvre la vidéo. Son fond est un aplat OPAQUE qui couvre tout le cadre (1920×1080, sans marge, aux couleurs de la charte : var(--card) ou var(--card-2)), sinon on voit la vidéo et son visage à travers. Le fond entre en premier et sort en dernier, avec la composition ; la marge de 80 px ne vaut que pour ce qui est posé dessus. | ||
| 39 | - Pas une pastille de texte : schéma, chiffre qui se construit, liste qui se coche, comparaison, \ | 40 | - Pas une pastille de texte : schéma, chiffre qui se construit, liste qui se coche, comparaison, \ |
| 40 | chronologie… | 41 | chronologie… |
| 41 | - Montre, ne dis pas. Le spectateur entend déjà la voix et voit déjà l'image : un texte qui redit l'une \ | 42 | - Montre, ne dis pas. Le spectateur entend déjà la voix et voit déjà l'image : un texte qui redit l'une \ |
modified
backend/studio/modules/projects/storyboard.py +8 -1 | @@ -83,6 +83,9 @@ BROLL_DUR = (2.0, 10.0) | ||
| 83 | 83 | MAX_VISUALS = 300 |
| 84 | 84 | KINDS = ["counter", "diagram", "comparison", "timeline", "list", "logo", "screenshot", "quote", "map", "other"] |
| 85 | 85 | PLACES = ["full", "left", "right", "top", "lower"] |
| 86 | +# un motion plein écran recouvre la vidéo : sans fond opaque, on la voit à travers | |
| 87 | +FULL_SCREEN = ("Plein écran : un calque au-dessus de la vidéo, sur un fond opaque de la charte qui couvre tout le cadre " | |
| 88 | + "du début à la fin (il entre en premier, sort en dernier) : la vidéo ne doit jamais se voir dessous.") | |
| 86 | 89 | # pour Daryl : ce que chaque place veut dire à l'écran |
| 87 | 90 | PLACE_TEXT = {"full": "plein écran", "left": "tiers gauche de l'image", "right": "tiers droit de l'image", |
| 88 | 91 | "top": "bandeau en haut de l'image", "lower": "bandeau en bas de l'image"} |
| @@ -1226,7 +1229,9 @@ def image_brief(pid: str, b: dict) -> list[str]: | ||
| 1226 | 1229 | "par le mot qui le motive (en retard se lit comme voulu, en avance comme une erreur) ; au plus une dérive " |
| 1227 | 1230 | "à peine perceptible (2 % sur tout le plan) pour qu'elle ne soit pas figée. Entrée et sortie : coupe franche, poussée, glissé ou masque qui s'ouvre ; un fondu " |
| 1228 | 1231 | "seulement s'il a une raison.") |
| 1229 | - if b["place"] != "full": | |
| 1232 | + if b["place"] == "full": | |
| 1233 | + lines.append(f"- {FULL_SCREEN}") | |
| 1234 | + else: | |
| 1230 | 1235 | lines.append("- Comme un portrait sur le côté : encadrée, avec une ombre, elle entre depuis son bord, reste " |
| 1231 | 1236 | "posée pendant qu'il parle, puis repart ; rien sur son visage à lui.") |
| 1232 | 1237 | return lines |
| @@ -1255,6 +1260,8 @@ def brief(pid: str, ids: list[str]) -> str: | ||
| 1255 | 1260 | lines += image_brief(pid, b) |
| 1256 | 1261 | else: |
| 1257 | 1262 | lines.append(f"- Type : {b['kind']} · place : {PLACE_TEXT.get(b['place'], b['place'])}") |
| 1263 | + if b["place"] == "full": | |
| 1264 | + lines.append(f"- {FULL_SCREEN}") | |
| 1258 | 1265 | if b.get("anim"): |
| 1259 | 1266 | lines.append(f"- Base : l'animation du kit montage/motion/exemples/{b['anim']} (adapte-la, garde sa mécanique)") |
| 1260 | 1267 | if b.get("enter") or b.get("exit"): |
| @@ -83,6 +83,9 @@ BROLL_DUR = (2.0, 10.0) | |||
| 83 | MAX_VISUALS = 300 | 83 | MAX_VISUALS = 300 |
| 84 | KINDS = ["counter", "diagram", "comparison", "timeline", "list", "logo", "screenshot", "quote", "map", "other"] | 84 | KINDS = ["counter", "diagram", "comparison", "timeline", "list", "logo", "screenshot", "quote", "map", "other"] |
| 85 | PLACES = ["full", "left", "right", "top", "lower"] | 85 | PLACES = ["full", "left", "right", "top", "lower"] |
| 86 | +# un motion plein écran recouvre la vidéo : sans fond opaque, on la voit à travers | ||
| 87 | +FULL_SCREEN = ("Plein écran : un calque au-dessus de la vidéo, sur un fond opaque de la charte qui couvre tout le cadre " | ||
| 88 | + "du début à la fin (il entre en premier, sort en dernier) : la vidéo ne doit jamais se voir dessous.") | ||
| 86 | # pour Daryl : ce que chaque place veut dire à l'écran | 89 | # pour Daryl : ce que chaque place veut dire à l'écran |
| 87 | PLACE_TEXT = {"full": "plein écran", "left": "tiers gauche de l'image", "right": "tiers droit de l'image", | 90 | PLACE_TEXT = {"full": "plein écran", "left": "tiers gauche de l'image", "right": "tiers droit de l'image", |
| 88 | "top": "bandeau en haut de l'image", "lower": "bandeau en bas de l'image"} | 91 | "top": "bandeau en haut de l'image", "lower": "bandeau en bas de l'image"} |
| @@ -1226,7 +1229,9 @@ def image_brief(pid: str, b: dict) -> list[str]: | |||
| 1226 | "par le mot qui le motive (en retard se lit comme voulu, en avance comme une erreur) ; au plus une dérive " | 1229 | "par le mot qui le motive (en retard se lit comme voulu, en avance comme une erreur) ; au plus une dérive " |
| 1227 | "à peine perceptible (2 % sur tout le plan) pour qu'elle ne soit pas figée. Entrée et sortie : coupe franche, poussée, glissé ou masque qui s'ouvre ; un fondu " | 1230 | "à peine perceptible (2 % sur tout le plan) pour qu'elle ne soit pas figée. Entrée et sortie : coupe franche, poussée, glissé ou masque qui s'ouvre ; un fondu " |
| 1228 | "seulement s'il a une raison.") | 1231 | "seulement s'il a une raison.") |
| 1229 | - if b["place"] != "full": | 1232 | + if b["place"] == "full": |
| 1233 | + lines.append(f"- {FULL_SCREEN}") | ||
| 1234 | + else: | ||
| 1230 | lines.append("- Comme un portrait sur le côté : encadrée, avec une ombre, elle entre depuis son bord, reste " | 1235 | lines.append("- Comme un portrait sur le côté : encadrée, avec une ombre, elle entre depuis son bord, reste " |
| 1231 | "posée pendant qu'il parle, puis repart ; rien sur son visage à lui.") | 1236 | "posée pendant qu'il parle, puis repart ; rien sur son visage à lui.") |
| 1232 | return lines | 1237 | return lines |
| @@ -1255,6 +1260,8 @@ def brief(pid: str, ids: list[str]) -> str: | |||
| 1255 | lines += image_brief(pid, b) | 1260 | lines += image_brief(pid, b) |
| 1256 | else: | 1261 | else: |
| 1257 | lines.append(f"- Type : {b['kind']} · place : {PLACE_TEXT.get(b['place'], b['place'])}") | 1262 | lines.append(f"- Type : {b['kind']} · place : {PLACE_TEXT.get(b['place'], b['place'])}") |
| 1263 | + if b["place"] == "full": | ||
| 1264 | + lines.append(f"- {FULL_SCREEN}") | ||
| 1258 | if b.get("anim"): | 1265 | if b.get("anim"): |
| 1259 | lines.append(f"- Base : l'animation du kit montage/motion/exemples/{b['anim']} (adapte-la, garde sa mécanique)") | 1266 | lines.append(f"- Base : l'animation du kit montage/motion/exemples/{b['anim']} (adapte-la, garde sa mécanique)") |
| 1260 | if b.get("enter") or b.get("exit"): | 1267 | if b.get("enter") or b.get("exit"): |