|
| 1 | +--- |
| 2 | +name: vse-python |
| 3 | +description: Build Video Sequence Editor timelines from Python. SequenceEditor.strips vs .sequences, new_effect length vs frame_end, and the 5.2 COLOR strip width/height bake from scene resolution. |
| 4 | +standards-version: 1.10.0 |
| 5 | +--- |
| 6 | + |
| 7 | +# Video Sequence Editor in Python |
| 8 | + |
| 9 | +## Trigger |
| 10 | + |
| 11 | +Use this skill when the user: |
| 12 | + |
| 13 | +- Builds or inspects a VSE / sequencer timeline from a script |
| 14 | +- Mentions `sequence_editor`, `strips`, `sequences`, `new_effect`, COLOR strips, or `StripTransform` |
| 15 | +- Hits `AttributeError: 'SequenceEditor' object has no attribute 'sequences'` on 5.x |
| 16 | +- Hits `TypeError` on `new_effect` kwargs (`frame_end` vs `length`) |
| 17 | +- Renders a sequencer composite whose COLOR cells ignore `transform.scale_*` after a resolution change |
| 18 | + |
| 19 | +## High-level shape |
| 20 | + |
| 21 | +```python |
| 22 | +import bpy |
| 23 | + |
| 24 | +scene = bpy.context.scene |
| 25 | +se = scene.sequence_editor or scene.sequence_editor_create() |
| 26 | +coll = se.strips if hasattr(se, "strips") else se.sequences |
| 27 | + |
| 28 | +if bpy.app.version >= (5, 0, 0): |
| 29 | + strip = coll.new_effect( |
| 30 | + name="A", type="COLOR", channel=1, |
| 31 | + frame_start=1, length=10, |
| 32 | + ) |
| 33 | +else: |
| 34 | + strip = coll.new_effect( |
| 35 | + name="A", type="COLOR", channel=1, |
| 36 | + frame_start=1, frame_end=11, |
| 37 | + ) |
| 38 | +strip.color = (0.85, 0.10, 0.22) |
| 39 | +``` |
| 40 | + |
| 41 | +Branch on `bpy.app.version`, never on `bpy.app.version_string`. An empty `bpy_prop_collection` is falsy — `se.strips or se.sequences` silently falls through to the legacy accessor on an empty timeline. Always branch on `hasattr`. |
| 42 | + |
| 43 | +## Accessor: `.strips` vs `.sequences` |
| 44 | + |
| 45 | +| | 4.5 LTS | 5.1 / 5.2+ | |
| 46 | +| --- | --- | --- | |
| 47 | +| Canonical collection | `.strips` (`.sequences` is a bridge to the same strips) | `.strips` only | |
| 48 | +| `.sequences` | Present, same contents as `.strips` | `AttributeError` | |
| 49 | + |
| 50 | +```python |
| 51 | +def strips_coll(se): |
| 52 | + return se.strips if hasattr(se, "strips") else se.sequences |
| 53 | +``` |
| 54 | + |
| 55 | +Do not write `se.sequences` in new code. On 5.x it is gone; on 4.5 `.strips` already exists. |
| 56 | + |
| 57 | +## `new_effect` end kwarg |
| 58 | + |
| 59 | +`strips.new_effect(...)` ends a strip with **one** accepted end argument. The other raises `TypeError`. |
| 60 | + |
| 61 | +```python |
| 62 | +span = (1, 33) # end-exclusive [start, end) |
| 63 | + |
| 64 | +if bpy.app.version >= (5, 0, 0): |
| 65 | + strip = coll.new_effect( |
| 66 | + name="A", type="COLOR", channel=1, |
| 67 | + frame_start=span[0], length=span[1] - span[0], |
| 68 | + ) |
| 69 | +else: |
| 70 | + strip = coll.new_effect( |
| 71 | + name="A", type="COLOR", channel=1, |
| 72 | + frame_start=span[0], frame_end=span[1], |
| 73 | + ) |
| 74 | +``` |
| 75 | + |
| 76 | +Timeline bounds: `frame_final_start` / `frame_final_end` / `frame_final_duration` on 4.5. On 5.x those names are deprecated aliases (removal announced for 6.0); canonical accessors are `left_handle` / `right_handle` / `duration`. Scene strips take four args — `new_scene(name, scene, channel, frame_start)` — with no `length` / `frame_end` kwarg on either version. |
| 77 | + |
| 78 | +The `TRANSFORM` effect type is removed on 5.x. Place strips with per-strip `strip.transform` (`StripTransform`) on both versions. Effect inputs are `input_1` / `input_2` (creation kwargs `input1` / `input2`). |
| 79 | + |
| 80 | +## COLOR strip intrinsic size (5.2) |
| 81 | + |
| 82 | +4.5 LTS and 5.1 COLOR strips have **no** intrinsic size. `transform.scale_*` is a fraction of the **output frame**. Creating at factory resolution then rendering a tiny buffer is fine. |
| 83 | + |
| 84 | +5.2 and later bake readonly `width` / `height` from **scene render resolution at `new_effect` time**. Scale and offset are then in that **media** space, not a later output size. Set scene resolution **before** building strips. |
| 85 | + |
| 86 | +This split is **not** in the 5.2 `python_api` release notes. Witnessed by `examples/vse-cut-list/` `--check-pixels`. |
| 87 | + |
| 88 | +```python |
| 89 | +width, height = 96, 54 |
| 90 | +scene.render.resolution_x = width |
| 91 | +scene.render.resolution_y = height |
| 92 | + |
| 93 | +se = scene.sequence_editor or scene.sequence_editor_create() |
| 94 | +coll = se.strips if hasattr(se, "strips") else se.sequences |
| 95 | + |
| 96 | +if bpy.app.version >= (5, 0, 0): |
| 97 | + strip = coll.new_effect( |
| 98 | + name="A", type="COLOR", channel=1, |
| 99 | + frame_start=1, length=10, |
| 100 | + ) |
| 101 | +else: |
| 102 | + strip = coll.new_effect( |
| 103 | + name="A", type="COLOR", channel=1, |
| 104 | + frame_start=1, frame_end=11, |
| 105 | + ) |
| 106 | + |
| 107 | +if bpy.app.version >= (5, 2, 0): |
| 108 | + # 5.2+: readonly width/height baked at new_effect from scene.render |
| 109 | + assert (strip.width, strip.height) == (width, height) |
| 110 | + # strip.width = 64 # TypeError — readonly |
| 111 | +else: |
| 112 | + # 4.5 / 5.1: no intrinsic size; hasattr(strip, "width") is False |
| 113 | + pass |
| 114 | + |
| 115 | +strip.transform.scale_x = 0.36 |
| 116 | +strip.transform.scale_y = 0.36 |
| 117 | +``` |
| 118 | + |
| 119 | +Creating at factory 1920×1080 then dropping the scene to 96×54 for a check render makes a 0.36-scaled COLOR cell larger than the output. On 5.2 the long-runner covers the frame; pixel samples read the wrong strip. |
| 120 | + |
| 121 | +A lone COLOR strip **does** honor `transform.scale_*` on 5.2. The break is media size vs a later output size, not "transform is ignored." |
| 122 | + |
| 123 | +## Common AI mistakes |
| 124 | + |
| 125 | +1. **`sequence_editor.sequences` on 5.x.** `AttributeError`. Use `.strips` (with `hasattr` only as a 4.5 bridge). |
| 126 | + |
| 127 | +2. **Wrong `new_effect` end kwarg.** `frame_end=` on 5.x and `length=` on 4.5 both raise `TypeError`. |
| 128 | + |
| 129 | +3. **Creating COLOR strips, then changing render size** (the 5.2 trap): |
| 130 | + |
| 131 | + ```python |
| 132 | + a = coll.new_effect(...) # bakes 1920×1080 on 5.2 |
| 133 | + scene.render.resolution_x = 96 # output changed; media size did not |
| 134 | + ``` |
| 135 | + |
| 136 | + Set `scene.render.resolution_*` **before** `new_effect`. |
| 137 | + |
| 138 | +4. **`se.strips or se.sequences`.** An empty collection is falsy, so this falls through to `.sequences` on a fresh editor. |
| 139 | + |
| 140 | +5. **`TRANSFORM` effect type on 5.x.** Gone. Use `strip.transform`. |
| 141 | + |
| 142 | +6. **Scene strip pointing at its own scene.** Feedback loop; renders transparent. Source a **separate** scene. |
| 143 | + |
| 144 | +7. **Effect strips consume their inputs** only when stacked on a channel **above** those inputs. Below, the inputs keep compositing independently. A `GAMMA_CROSS` asked to outlast its inputs' overlap is silently clamped to the overlap. |
| 145 | + |
| 146 | +## See also |
| 147 | + |
| 148 | +- Example `examples/vse-cut-list/` — accessor rename, `new_effect` kwargs, save/reload, and the 5.2 COLOR size bake (`--check-pixels` asserts `A.width, A.height == render size` on 5.2+). |
| 149 | +- Example `examples/vse-gamma-cross/` — `GAMMA_CROSS` blend curve. |
| 150 | +- Audit log: `docs/technical-audit.md` § Findings the release notes did not list. |
| 151 | + |
| 152 | +## References |
| 153 | + |
| 154 | +- `bpy.types.SequenceEditor`: https://docs.blender.org/api/current/bpy.types.SequenceEditor.html |
| 155 | +- `bpy.types.Sequence`: https://docs.blender.org/api/current/bpy.types.Sequence.html |
| 156 | +- 4.5 LTS `SequenceEditor`: https://docs.blender.org/api/4.5/bpy.types.SequenceEditor.html |
| 157 | +- 5.1 `SequenceEditor`: https://docs.blender.org/api/5.1/bpy.types.SequenceEditor.html |
0 commit comments