reel-driven-development API Documentation
adoc
Created on 2026-08-10.
asciidoc rendering of a reel
The reel file carries the Recording, the run configuration and the hops. The wiki templates render that model as wikitext; the templates here render the same model as asciidoc, so page and document say the same thing. The document is a build artefact of the folder - anyone holding the folder can rebuild it, with no wiki in the path.
see https://github.com/WolfgangFahl/reel-driven-development/issues/23
@author: wf
RecordingDoc
The asciidoc document of one reel.
One block per hop - the frame, when it was reached, the node and what happened there - so a reviewer reads their own walk and can write into the rendered pdf beside every step.
Source code in rdd/adoc.py
50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 | |
images_dir
property
The directory the document takes its frames from.
A frame is a full screen capture; embedding it at full resolution is what makes a document of two dozen hops too heavy to mail. The scaled copies live in a hidden directory of the recording folder, derived and rebuildable, so the evidence frames themselves stay untouched.
lang
property
The language of the Recording, English where it carries none.
participants
property
The participants as the Recording names them.
__init__(hop_set, folder, persons=None, transcript=None, width=640)
Initialize the document of the given reel.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
hop_set
|
HopSet
|
the reel with its Recording and its hops. |
required |
folder
|
str
|
the recording folder the frames live in. |
required |
persons
|
Optional[Dict[str, str]]
|
person name to the url they are identifiable by outside the wiki; a name that is not in it keeps its plain form, so a document renders before the mapping is complete and the gap is visible in it. |
None
|
transcript
|
Optional[Transcript]
|
the improved transcript, None where the folder carries none. |
None
|
width
|
int
|
width of an evidence frame in the document. |
640
|
Source code in rdd/adoc.py
58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 | |
asciidoc()
Get the whole document as asciidoc.
Returns:
| Type | Description |
|---|---|
str
|
the asciidoc source. |
Source code in rdd/adoc.py
300 301 302 303 304 305 306 307 308 309 310 311 312 313 | |
frame_path(hop)
Get the file path of the evidence frame of the given hop.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
hop
|
HopContent
|
the hop record. |
required |
Returns:
| Type | Description |
|---|---|
Optional[str]
|
the path of the frame, None where the hop has none or the |
Optional[str]
|
frame is not in the folder - a missing frame is left out of |
Optional[str]
|
the document rather than rendered as a broken image. |
Source code in rdd/adoc.py
219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 | |
header()
Get the document header lines.
Source code in rdd/adoc.py
237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 | |
hop_block(hop)
Get the asciidoc lines of one hop.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
hop
|
HopContent
|
the hop record. |
required |
Returns:
| Type | Description |
|---|---|
List[str]
|
the lines of the hop block. |
Source code in rdd/adoc.py
281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 | |
label(key)
Get the heading of the given section in the document language.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
key
|
str
|
the section key. |
required |
Returns:
| Type | Description |
|---|---|
str
|
the heading. |
Source code in rdd/adoc.py
93 94 95 96 97 98 99 100 101 102 103 | |
of_folder(folder, **kwargs)
classmethod
Get the document of the given recording folder.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
folder
|
str
|
the recording folder. |
required |
kwargs
|
passed on to the constructor, e.g. the frame width. |
{}
|
Returns:
| Type | Description |
|---|---|
RecordingDoc
|
the document of its reel file. |
Raises:
| Type | Description |
|---|---|
ValueError
|
if the folder carries no reel file. |
Source code in rdd/adoc.py
105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 | |
person_link(name)
Get the asciidoc form of the given person.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name
|
str
|
the person as the Recording names them. |
required |
Returns:
| Type | Description |
|---|---|
str
|
a link where the person has a url, the plain name otherwise. |
Source code in rdd/adoc.py
161 162 163 164 165 166 167 168 169 170 171 172 | |
persons_of(yaml_path)
classmethod
Read the person mapping of the given file.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
yaml_path
|
str
|
file of name to url entries. |
required |
Returns:
| Type | Description |
|---|---|
Dict[str, str]
|
the mapping, empty where the file is not there - the mapping |
Dict[str, str]
|
grows with the reels and is not a precondition of a document. |
Source code in rdd/adoc.py
132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 | |
save(path)
Write the document to the given path.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
path
|
str
|
the file to write. |
required |
Returns:
| Type | Description |
|---|---|
str
|
the path written. |
Source code in rdd/adoc.py
315 316 317 318 319 320 321 322 323 324 325 326 327 | |
scale_frames()
Write the scaled copies of the evidence frames.
Returns:
| Type | Description |
|---|---|
int
|
the number of frames written. |
Source code in rdd/adoc.py
189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 | |
transcript_block()
Get the asciidoc lines of the improved transcript.
Returns:
| Type | Description |
|---|---|
List[str]
|
the lines, empty where the folder carries no transcript - a |
List[str]
|
reel may be documented before its transcript is improved. |
Source code in rdd/adoc.py
265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 | |
adoc_cmd
Created on 2026-08-10.
command line interface of the asciidoc rendering of a reel
see https://github.com/WolfgangFahl/reel-driven-development/issues/23
@author: wf
ReelDocCmd
Bases: BaseCmd
Render the reel of a recording folder as an asciidoc document.
Source code in rdd/adoc_cmd.py
21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 | |
__init__()
Initialize with the reel-driven-development version info.
Source code in rdd/adoc_cmd.py
24 25 26 | |
add_arguments(parser)
Add the document arguments to the given parser.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
parser
|
ArgumentParser
|
the parser to add arguments to. |
required |
Source code in rdd/adoc_cmd.py
28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 | |
handle_args(args)
Handle the parsed arguments by rendering the document.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
args
|
Namespace
|
parsed argument namespace. |
required |
Returns:
| Type | Description |
|---|---|
bool
|
True if the arguments were handled. |
Raises:
| Type | Description |
|---|---|
ValueError
|
if the folder argument is missing. |
Source code in rdd/adoc_cmd.py
47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 | |
render(args)
Render the reel of the given folder.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
args
|
Namespace
|
parsed argument namespace. |
required |
Source code in rdd/adoc_cmd.py
67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 | |
main(argv=None)
Command line entry point for the asciidoc rendering.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
argv
|
Optional[List[str]]
|
command line arguments; defaults to sys.argv. |
None
|
Returns:
| Type | Description |
|---|---|
int
|
exit code: 0 = OK, 1 = KeyboardInterrupt, 2 = Exception. |
Source code in rdd/adoc_cmd.py
94 95 96 97 98 99 100 101 102 103 104 105 | |
config
Created on 2026-08-09.
the values that decide a hop set
@author: wf
HopConfig
Every value that can change which hops are found - see issue #4.
A hop set is only evidence if the values that decided it are known, so the configuration is stored beside the hop set and a run is repeatable from it. The detector is named; the names are the offer of HopDetector.get_detectors.
Source code in rdd/config.py
13 14 15 16 17 18 19 20 21 22 23 24 25 | |
frame
Created on 2026-08-08.
the frame module hides the technical datails of numpy
@author: wf
Frame
One picture of a recording together with its position in the recording.
The pixel representation is an implementation detail: callers ask a frame what it shows and where it sits in the recording, never how it is stored. The type of a picture is known to its suppliers - the video reader handing frames in, make building them - never to its users, who only ever pass the picture on as img.
Source code in rdd/frame.py
297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 | |
fps
property
Frames per second of the recording this frame belongs to.
height
property
Height of the frame in pixels.
img
property
The picture this frame shows.
The handle a user passes on without asking what it is made of; only frame.py and the suppliers of a picture know its type.
time_sec
property
Position of the frame in the recording in seconds, if the recording knows its rate.
timecode
property
Position of the frame as mm:ss, or the frame number if no fps.
width
property
Width of the frame in pixels.
__init__(img, frame_num=0, reel=None)
Initialize the frame.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
img
|
ndarray
|
the picture as a HxW or HxWxC array. |
required |
frame_num
|
int
|
the position of the frame in the recording. |
0
|
reel
|
Optional[Reel]
|
the reel this picture belongs to; a frame without one is given a reel that has neither a stream nor a rate, so that a frame always knows its reel. |
None
|
Source code in rdd/frame.py
307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 | |
crop(region)
Restrict the frame to a region of interest.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
region
|
Optional[Region]
|
the region; None returns the frame itself. |
required |
Returns:
| Type | Description |
|---|---|
Frame
|
a Frame showing only the region. |
Source code in rdd/frame.py
387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 | |
is_blank(tolerance=1.0)
Decide whether the frame shows a single uniform color.
A blank frame is what a browser shows before a page has rendered; capturing it as evidence is a false hop - see issue #1.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
tolerance
|
float
|
maximum spread in gray levels still counting as blank. |
1.0
|
Returns:
| Type | Description |
|---|---|
bool
|
True if the frame is uniform within the tolerance. |
Source code in rdd/frame.py
406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 | |
make(frame_num=0, fps=25.0, width=1280, height=720, value=128, channels=None)
classmethod
Create a frame of a single uniform color.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
frame_num
|
int
|
the position of the frame in the recording. |
0
|
fps
|
Optional[float]
|
frames per second of the recording; None leaves the recording unknown. |
25.0
|
width
|
int
|
frame width in pixels. |
1280
|
height
|
int
|
frame height in pixels. |
720
|
value
|
int
|
the gray level or channel value to fill the frame with. |
128
|
channels
|
Optional[int]
|
number of color channels; None creates a gray frame. |
None
|
Returns:
| Type | Description |
|---|---|
Frame
|
the Frame. |
Source code in rdd/frame.py
422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 | |
save(path)
Write the frame to an image file.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
path
|
str
|
the file path; the suffix selects the format. |
required |
Returns:
| Type | Description |
|---|---|
bool
|
True if the file was written. |
Source code in rdd/frame.py
468 469 470 471 472 473 474 475 476 477 478 | |
with_rect(y0, y1, x0, x1, value)
Copy the frame with a rectangle painted in a single value.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
y0
|
int
|
first row of the rectangle. |
required |
y1
|
int
|
row behind the last row of the rectangle. |
required |
x0
|
int
|
first column of the rectangle. |
required |
x1
|
int
|
column behind the last column of the rectangle. |
required |
value
|
int
|
the gray level or channel value to paint with. |
required |
Returns:
| Type | Description |
|---|---|
Frame
|
a Frame showing the painted rectangle. |
Source code in rdd/frame.py
450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 | |
Reel
Bases: Recording
A Recording that can be played - the source of frames.
A reel knows how fast it runs and, where it has one, the stream it reads its pictures from. The stream is what is optional here, not the reel: a frame always belongs to a reel, while a reel built for a test has no video behind it and answers no pictures.
The video library stays inside this class - callers ask for a frame at a position and get a Frame, never a stream, a timecode object or a codec.
Source code in rdd/frame.py
161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 | |
keyframes
property
The frame numbers of the key frames of this recording.
Key frames are the positions that can be seeked to without decoding forward, so they are the cheap probes. The index is read once with ffprobe; without a stream or without ffprobe the list stays empty and the caller falls back to plain bisection.
__init__(path=None, fps=None)
Open a reel, or create one that has no video.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
path
|
Optional[str]
|
path of the video file; None creates a reel without a stream, as used by tests and by synthetic frames. |
None
|
fps
|
Optional[float]
|
frames per second; read from the video when a path is given, otherwise as given here and None where unknown. |
None
|
Raises:
| Type | Description |
|---|---|
FileNotFoundError
|
if a path is given that holds no video file. |
Source code in rdd/frame.py
173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 | |
frame_at(frame_num)
Read the frame at the given position.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
frame_num
|
int
|
the frame number to read. |
required |
Returns:
| Type | Description |
|---|---|
Optional[Frame]
|
the Frame, or None without a stream or past the end of the recording. |
Source code in rdd/frame.py
279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 | |
frame_num_of(time_sec)
Convert a position in seconds to a frame number.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
time_sec
|
float
|
the position in seconds. |
required |
Returns:
| Type | Description |
|---|---|
int
|
the frame number. |
Raises:
| Type | Description |
|---|---|
ValueError
|
if the recording does not know its rate. |
Source code in rdd/frame.py
202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 | |
time_of(frame_num)
Convert a frame number to a position in seconds.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
frame_num
|
int
|
the frame number. |
required |
Returns:
| Type | Description |
|---|---|
Optional[float]
|
the position in seconds, or None if the rate is unknown. |
Source code in rdd/frame.py
219 220 221 222 223 224 225 226 227 228 229 230 231 | |
Region
dataclass
A rectangular part of a frame.
The region of interest restricts every judgement to the part of the screen that belongs to the walk, so that a permanently changing area outside it - live participant tiles, a clock, a scrolling log - can not defeat the detection - see issue #5.
Coordinates are either fractions of the frame (width and height <= 1) or pixels; the two forms describe the same area on a given frame.
Source code in rdd/frame.py
77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 | |
is_fractional
property
Decide whether the region is given as fractions of the frame.
Returns:
| Type | Description |
|---|---|
bool
|
True if width and height are fractions. |
bounds(width, height)
Compute the pixel bounds of the region on a frame of the given size.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
width
|
int
|
frame width in pixels. |
required |
height
|
int
|
frame height in pixels. |
required |
Returns:
| Type | Description |
|---|---|
Tuple[int, int, int, int]
|
(y0, y1, x0, x1) crop bounds, clamped to the frame. |
Source code in rdd/frame.py
139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 | |
of_str(text)
classmethod
Create a region from a comma separated string.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
text
|
str
|
"x,y,width,height" e.g. "0,0,0.875,1.0". |
required |
Returns:
| Type | Description |
|---|---|
Region
|
the Region. |
Raises:
| Type | Description |
|---|---|
ValueError
|
if the text does not hold four numbers. |
Source code in rdd/frame.py
109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 | |
of_tuple(values)
classmethod
Create a region from an (x, y, width, height) tuple.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
values
|
Tuple[float, float, float, float]
|
the four coordinates. |
required |
Returns:
| Type | Description |
|---|---|
Region
|
the Region. |
Source code in rdd/frame.py
95 96 97 98 99 100 101 102 103 104 105 106 107 | |
hop_frame_name(time_sec, with_ms=False)
The file name of the evidence frame of a hop at the given offset.
Issue #21: a running number cannot take an insert or a removal, so an evidence frame is named by the point in the reel it shows, which is the identity that survives curation. The fields are zero padded to a fixed width, so the names sort chronologically, and the name without the millisecond part is a prefix of the one with it, so that ordering holds across both forms. No prefix beyond "hop-" is added - the part directory says which reel the frame belongs to.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
time_sec
|
float
|
the offset of the frame in the reel in seconds. |
required |
with_ms
|
bool
|
append the millisecond field; only needed where two hops fall in the same second. |
False
|
Returns:
| Type | Description |
|---|---|
str
|
the file name, e.g. "hop-00h02m12s.jpg" or "hop-00h02m12s480ms.jpg". |
Source code in rdd/frame.py
20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 | |
hop_frame_names(times_sec)
The evidence frame names of a whole hop set.
The millisecond field is emitted only where it is needed - where two or more hops fall in the same second - so the common case stays as short as it can be read.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
times_sec
|
List[float]
|
the offsets of the hops in the reel in seconds. |
required |
Returns:
| Type | Description |
|---|---|
List[str]
|
one file name per offset, in the given order. |
Source code in rdd/frame.py
50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 | |
hopdetect_cmd
Created on 2026-08-08.
@author: wf
HopDetectCmd
Bases: BaseCmd
Reel Driven Development command line interface.
Source code in rdd/hopdetect_cmd.py
18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 | |
__init__()
Initialize with the reel-driven-development version info.
Source code in rdd/hopdetect_cmd.py
21 22 23 24 | |
add_arguments(parser)
Add the hop detection arguments to the given parser.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
parser
|
ArgumentParser
|
the parser to add arguments to. |
required |
Source code in rdd/hopdetect_cmd.py
60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 | |
detect(args)
Run the hop detection on the given arguments.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
args
|
Namespace
|
parsed argument namespace. |
required |
Source code in rdd/hopdetect_cmd.py
107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 | |
get_config(args)
Get the configuration the given arguments select.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
args
|
Namespace
|
parsed argument namespace. |
required |
Returns:
| Type | Description |
|---|---|
HopConfig
|
the configuration of this run. |
Source code in rdd/hopdetect_cmd.py
44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 | |
handle_args(args)
Handle the parsed arguments by running the detection.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
args
|
Namespace
|
parsed argument namespace. |
required |
Returns:
| Type | Description |
|---|---|
bool
|
True if the arguments were handled. |
Raises:
| Type | Description |
|---|---|
ValueError
|
if the video argument is missing. |
Source code in rdd/hopdetect_cmd.py
87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 | |
time_of(text)
staticmethod
Convert a time argument to seconds.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
text
|
Optional[str]
|
mm:ss, hh:mm:ss or a number of seconds; None stays None. |
required |
Returns:
| Type | Description |
|---|---|
Optional[float]
|
the time in seconds or None. |
Source code in rdd/hopdetect_cmd.py
26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 | |
main(argv=None)
Command line entry point for hop detection.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
argv
|
Optional[List[str]]
|
command line arguments; defaults to sys.argv. |
None
|
Returns:
| Type | Description |
|---|---|
int
|
exit code: 0 = OK, 1 = KeyboardInterrupt, 2 = Exception. |
Source code in rdd/hopdetect_cmd.py
131 132 133 134 135 136 137 138 139 140 141 142 | |
hopdetector
Created on 2026-08-08.
hop detector over a reel
@author: wf
HopDetector
Detect hops in the reel which e.g might be scene change.
Source code in rdd/hopdetector.py
28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 | |
__init__(reel)
Initialize the detector.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
reel
|
Reel
|
the reel to analyze. |
required |
Source code in rdd/hopdetector.py
31 32 33 34 35 36 37 | |
clear(out_dir, force)
Make sure a hop set is not silently mixed with an older one.
Writing a hop set over an older one leaves the frames the new run does not cut at behind, and the directory then shows a hop set that never existed. An existing hop set is therefore kept unless it is replaced whole, and replacing it removes the frames the old hop set named.
What a person wrote into the reel file - the name, the acronym, the participants - is not a hop set and survives the replacement: a detection replaces what it produced, never what it was given.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
out_dir
|
str
|
the directory the hop set is written to. |
required |
force
|
bool
|
replace an existing hop set instead of keeping it. |
required |
Returns:
| Type | Description |
|---|---|
Optional[HopSet]
|
the reel file that was there, None where there was none. |
Raises:
| Type | Description |
|---|---|
ValueError
|
if a hop set is there and force is not given. |
Source code in rdd/hopdetector.py
144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 | |
get_detector(config)
Get the detector the given configuration names.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
config
|
HopConfig
|
the configuration naming the detector. |
required |
Returns:
| Type | Description |
|---|---|
SceneDetector
|
the detector on offer under that name. |
Raises:
| Type | Description |
|---|---|
ValueError
|
if the named detector is not on offer. |
Source code in rdd/hopdetector.py
81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 | |
get_detector_names()
classmethod
Get the names the detectors are on offer under.
Returns:
| Type | Description |
|---|---|
List[str]
|
the names, in the order they are offered. |
Source code in rdd/hopdetector.py
71 72 73 74 75 76 77 78 79 | |
get_detectors()
classmethod
Get the detectors on offer.
A detector at one threshold says nothing about how it answers to that threshold, so each is offered around its library default. Which value is right can only be decided against a labeled corpus - until we have one these are measurements, not claims.
ThresholdDetector is not on offer: it detects fades to a near-black level, which our material of mostly zoom recordings does not have.
The detectors and their measured quality are documented at https://www.scenedetect.com/benchmarks/
Yields:
| Type | Description |
|---|---|
Tuple[str, SceneDetector]
|
the name of the detector and the detector itself |
Source code in rdd/hopdetector.py
39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 | |
hops(config, out_dir=None, progress=False, force=False)
Turn the cuts of the given detector into hop records.
The evidence frame of a hop is the frame the detector cuts at - the first frame of the new content. It is named by its offset in the reel and never by its position in the run - see issue #21 and hop_frame_names. node, url and summary of the walk stay empty: they come from the transcript and are never guessed from the picture.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
config
|
HopConfig
|
the values selecting and parameterizing the detector. |
required |
out_dir
|
Optional[str]
|
directory the evidence frames and the reel.yaml that carries them with the values reproducing them are written to; a hopless reel.yaml already there is the input of the run and its recording values are kept. None writes nothing. |
None
|
progress
|
bool
|
show the tqdm progress bar while detecting. |
False
|
force
|
bool
|
overwrite a hop set that is already there. |
False
|
Returns:
| Type | Description |
|---|---|
HopContents
|
the hops of this run. |
Raises:
| Type | Description |
|---|---|
ValueError
|
if the output directory already holds a hop set and force is not given. |
Source code in rdd/hopdetector.py
201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 | |
recording_of(given=None)
The Recording record of the reel this run analyzed.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
given
|
Optional[Recording]
|
the Recording of a reel file written beforehand; its values win, because they are what a person knew and the reel cannot answer. |
None
|
Returns:
| Type | Description |
|---|---|
Recording
|
the Recording, with the fields the reel itself can answer |
Recording
|
filled in where the given one leaves them open. |
Source code in rdd/hopdetector.py
182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 | |
scenes(config, progress=False)
Candidate hop positions from the given scene detector.
Which detector is used is the caller's choice - the library offers several and they are benchmarked against each other, so the detector is a parameter and never fixed here. Each detector carries its own thresholds in its own constructor.
See https://www.scenedetect.com/benchmarks/
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
config
|
HopConfig
|
the values selecting and parameterizing the detector; end_sec None runs to one frame before the end, since the opencv backend fails on the last frame of some files with an undefined timestamp. |
required |
progress
|
bool
|
show the tqdm progress bar of the library - a run over a reel takes minutes and must not be silent. |
False
|
Returns:
| Type | Description |
|---|---|
List[int]
|
the frame numbers where the detector cuts, empty without a video. |
Source code in rdd/hopdetector.py
104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 | |
hopset
Created on 2026-08-09.
the reel file - the record of one reel and its hops
@author: wf
HopSet
The record of one reel: what it is, what produced its hops and the hops themselves.
The reel and the values that decided the hop set belong in the file that carries the hops: a hop set read a year later is only evidence if it says which reel it describes and what produced it. recording is the Recording of https://contexts.bitplan.com/index.php/Concept:Recording so the file reads straight back into the model.
The file is also the input of a run: what only a person knows - the name, the acronym, the date, the participants, the language - is written into a hopless file beforehand and kept when the hops are added, so a detection never invents the identity of a reel and never loses it (issue #24).
hopCount is not stored: in this file it would be the length of the hop list a second time and could only ever disagree with it. The equality it controls is the one on the wiki page, where the hops are subobjects.
Source code in rdd/hopset.py
23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 | |
hopCount
property
The number of hops of this reel.
__post_init__()
Start with an empty hop list where none was given.
Source code in rdd/hopset.py
52 53 54 55 | |
of_dir(out_dir)
classmethod
Get the reel file of the given directory.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
out_dir
|
str
|
the recording directory. |
required |
Returns:
| Type | Description |
|---|---|
Optional[HopSet]
|
the hop set, None where the directory carries no reel file. |
Source code in rdd/hopset.py
76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 | |
path_of(out_dir)
classmethod
Get the path of the reel file in the given directory.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
out_dir
|
str
|
the recording directory. |
required |
Returns:
| Type | Description |
|---|---|
str
|
the path of the reel file. |
Source code in rdd/hopset.py
63 64 65 66 67 68 69 70 71 72 73 74 | |
save(path, version, date)
Save this reel with the header that says where it comes from.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
path
|
str
|
the file to write. |
required |
version
|
str
|
the version of hopdetect that produced this hop set. |
required |
date
|
str
|
the ISO date of the run. |
required |
Returns:
| Type | Description |
|---|---|
None
|
None |
Source code in rdd/hopset.py
92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 | |
i18n
Created on 2026-08-14.
i18n of the reel site - de and en for a start, the default being the browser setting and a selector with flag per the i18n issue. The texts are a resource of the package: rdd/resources/i18n.yaml.
@author: wf
I18n
The i18n texts of a reel site.
Loaded from the i18n.yaml resource the package ships - the texts of the site pages and of the packaged review page, the languages and their flags.
Source code in rdd/i18n.py
17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 | |
get_instance()
classmethod
Get the shared instance, loaded once from the resource.
Source code in rdd/i18n.py
45 46 47 48 49 50 | |
of_resource()
classmethod
Load the i18n texts shipped with the package.
Source code in rdd/i18n.py
39 40 41 42 43 | |
resource_path()
classmethod
Path of the i18n texts shipped with the package.
Source code in rdd/i18n.py
33 34 35 36 37 | |
pick_language(query_lang=None, cookie_lang=None, accept_language=None)
Pick the language of a request.
The explicit choice wins, then the remembered one, then the browser setting - per the i18n issue the default is the browser setting.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
query_lang
|
Optional[str]
|
the ?lang= parameter, if any. |
None
|
cookie_lang
|
Optional[str]
|
the remembered choice, if any. |
None
|
accept_language
|
Optional[str]
|
the Accept-Language header, if any. |
None
|
Returns:
| Type | Description |
|---|---|
str
|
the language code; en where nothing decides. |
Source code in rdd/i18n.py
88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 | |
review_texts(lang)
The review page texts of the given language.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
lang
|
str
|
the language code. |
required |
Returns:
| Type | Description |
|---|---|
Dict[str, str]
|
the texts; english where the language is not carried. |
Source code in rdd/i18n.py
74 75 76 77 78 79 80 81 82 83 84 85 | |
texts(lang)
The site page texts of the given language.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
lang
|
str
|
the language code. |
required |
Returns:
| Type | Description |
|---|---|
Dict[str, str]
|
the texts; english where the language is not carried. |
Source code in rdd/i18n.py
60 61 62 63 64 65 66 67 68 69 70 71 | |
icons
Created on 2026-08-12.
the material icons a reel site draws in its menu
The BITPlan applications wear the material icon set that quasar loads for nicegui. A reel site may not load a font from a foreign host - the Review UI stack decision requires that the browser loads nothing but the reel site - so the handful of icons the menu needs travel as inline svg path data taken from https://github.com/google/material-design-icons (Apache-2.0), same names, same shapes.
@author: wf
svg(name, size='1.2em')
Render the named material icon as an inline svg.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name
|
str
|
material icon name e.g. home. |
required |
size
|
str
|
css length for width and height. |
'1.2em'
|
Returns:
| Type | Description |
|---|---|
str
|
the svg markup, drawn in the current text color. |
Raises:
| Type | Description |
|---|---|
ValueError
|
if the icon is not one of the shipped ones. |
Source code in rdd/icons.py
46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 | |
mint
Created on 2026-08-13.
owner bootstrap and token minting of a reel site
per the Owner bootstrap and minting ADR on https://media.bitplan.com/index.php/Talk:Rdd.bitplan.com minting is a CLI matter - the webservice never mints.
@author: wf
Mint
Mint review tokens - the CLI side of access rights.
A site without reviews.yaml is in installation mode: init_site seeds the owner and mints the wildcard owner token. mint_review adds a reviewer. Tokens are 128-bit random - possession is the right per the Reel Review decision.
Source code in rdd/mint.py
19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 | |
base_url
property
The base url of the site - the configured public url, localhost where the configuration names none.
__init__(config, rdd_path='~/.rdd')
Initialize with the site configuration.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
config
|
RddSiteConfig
|
the site configuration - names the url the links carry. |
required |
rdd_path
|
str
|
the directory beside the site configuration where persons.yaml, reviews.yaml and the owner link live. |
'~/.rdd'
|
Source code in rdd/mint.py
30 31 32 33 34 35 36 37 38 39 40 41 42 | |
init_site(username, name, email, url)
Initialize the site: seed the owner and mint the owner token.
Per the Owner bootstrap decision the owner is written into persons.yaml, a wildcard Review into reviews.yaml, and the owner link into a file of mode 600 beside the site configuration - the caller shows it once on the interactive terminal and nowhere else.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
username
|
str
|
the username of the owner. |
required |
name
|
str
|
the full name of the owner. |
required |
email
|
str
|
the email of the owner. |
required |
url
|
str
|
the url of the owner. |
required |
Returns:
| Type | Description |
|---|---|
str
|
the owner link. |
Raises:
| Type | Description |
|---|---|
ValueError
|
where reviews.yaml already exists - a site is initialized exactly once. |
Source code in rdd/mint.py
72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 | |
mint_review(person, meeting='', reels=None)
Mint a review token for the given person.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
person
|
str
|
the person the link is for. |
required |
meeting
|
str
|
the meeting the review belongs to. |
''
|
reels
|
Optional[List[str]]
|
the acronyms the review grants. |
None
|
Returns:
| Type | Description |
|---|---|
str
|
the review link. |
Raises:
| Type | Description |
|---|---|
ValueError
|
where the site is not initialized - init_site comes first. |
Source code in rdd/mint.py
132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 | |
review_url(review)
The link of the given review.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
review
|
Review
|
the review. |
required |
Returns:
| Type | Description |
|---|---|
str
|
the url whose possession is the right. |
Source code in rdd/mint.py
60 61 62 63 64 65 66 67 68 69 70 | |
save_reviews(reviews)
Save the given reviews with owner-only permissions.
The tokens in reviews.yaml are the rights themselves, so the file is as protected as the owner link.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
reviews
|
Reviews
|
the reviews to save. |
required |
Source code in rdd/mint.py
120 121 122 123 124 125 126 127 128 129 130 | |
token()
Mint a 128-bit token.
Returns:
| Type | Description |
|---|---|
str
|
32 hex characters of cryptographic randomness. |
Source code in rdd/mint.py
51 52 53 54 55 56 57 58 | |
palette
Created on 2026-08-12.
the color palette of a reel site
@author: wf
Palette
dataclass
One Material palette schema.
The eight values are the ones the ColorSchema of ngwidgets carries, so a reel site looks like the other BITPlan applications without depending on the library.
Source code in rdd/palette.py
15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 | |
as_css()
Render the palette as CSS custom properties.
Returns:
| Type | Description |
|---|---|
str
|
the eight values as --name: value declarations. |
Source code in rdd/palette.py
33 34 35 36 37 38 39 40 41 42 | |
Palettes
The palette schemas a reel site may name.
Source code in rdd/palette.py
45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 | |
by_name(name)
Get the palette of the given name.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name
|
str
|
name of a Material palette schema e.g. blue_grey. |
required |
Returns:
| Type | Description |
|---|---|
Palette
|
the palette. |
Raises:
| Type | Description |
|---|---|
ValueError
|
if no palette of that name is shipped. |
Source code in rdd/palette.py
63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 | |
of_resource()
classmethod
Load the palettes shipped with the package.
Source code in rdd/palette.py
57 58 59 60 61 | |
resource_path()
classmethod
Path of the palettes shipped with the package.
Source code in rdd/palette.py
51 52 53 54 55 | |
rdd_cmd
Created on 2026-08-14.
rdd - the dispatcher command of Reel Driven Development
rdd is the name of what we do, so rdd is the one command name a user has to know; each subcommand forwards to the tool of the pipeline and the tool names stay available as entry points of their own.
@author: wf
RddCmd
Bases: BaseCmd
The rdd dispatcher - answers what rdd can do.
Dispatching itself happens in main before argument parsing, so a subcommand owns its own arguments; this class only serves the case of no subcommand - the standard options and the list of subcommands.
Source code in rdd/rdd_cmd.py
33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 | |
__init__()
Initialize with the reel-driven-development version info.
Source code in rdd/rdd_cmd.py
42 43 44 | |
add_arguments(parser)
Add the subcommand overview to the given parser.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
parser
|
ArgumentParser
|
the parser to add arguments to. |
required |
Source code in rdd/rdd_cmd.py
46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 | |
handle_args(args)
Handle the parsed arguments - without a subcommand the help is the answer.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
args
|
Namespace
|
parsed argument namespace. |
required |
Returns:
| Type | Description |
|---|---|
bool
|
True if the arguments were handled. |
Source code in rdd/rdd_cmd.py
62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 | |
main(argv=None)
Command line entry point of the rdd dispatcher.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
argv
|
Optional[List[str]]
|
command line arguments; defaults to sys.argv. |
None
|
Returns:
| Type | Description |
|---|---|
int
|
the exit code of the subcommand, or 0 = OK, 1 = |
int
|
KeyboardInterrupt, 2 = Exception of the dispatcher itself. |
Source code in rdd/rdd_cmd.py
79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 | |
rdd_site
Created on 2026-08-12.
the site of an organization's reel driven development videos - home page, menu and about
see https://media.bitplan.com/index.php/Talk:Rdd.bitplan.com ADRs: Review UI stack, Home page and menu
@author: wf
MenuEntry
dataclass
One entry of the menu.
Source code in rdd/rdd_site.py
192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 | |
as_html()
Render the entry as an icon labelled link button.
Returns:
| Type | Description |
|---|---|
str
|
the anchor markup carrying the material icon and the name. |
Source code in rdd/rdd_site.py
201 202 203 204 205 206 207 208 209 210 211 212 213 | |
Person
One person of a site - the seed minimum of the Owner bootstrap decision.
The username is the key; full name, email and url are what a site needs to address its people.
Source code in rdd/rdd_site.py
92 93 94 95 96 97 98 99 100 101 102 103 104 | |
Persons
The persons of a site.
Source code in rdd/rdd_site.py
107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 | |
of_path(path=None)
classmethod
Load the persons from the given yaml file.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
path
|
Optional[str]
|
the persons file; the default path when None. |
None
|
Returns:
| Type | Description |
|---|---|
Persons
|
the persons; none where no file exists. |
Source code in rdd/rdd_site.py
115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 | |
RddSiteConfig
Everything an organization configures to run its own reel site.
One yaml is the whole configuration - the site names itself, its repository, its documentation, the palette it wears and its recordings directory, so a stranger's site names their project and not ours.
The reels are not configured: the site names the recordings directory and the reels are what that directory holds, so publishing a reel is putting its folder there. main_demo names the demo the home page offers - a site has at least one reel in demo status.
Source code in rdd/rdd_site.py
32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 | |
recordings_dir
property
The recordings directory with the user's home resolved.
of_file(path)
classmethod
Load the site configuration from the given yaml file.
Source code in rdd/rdd_site.py
62 63 64 65 66 | |
of_path(path=None)
classmethod
Load the site configuration from the given yaml file.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
path
|
Optional[str]
|
the configuration file; the default path when None. |
None
|
Returns:
| Type | Description |
|---|---|
RddSiteConfig
|
the configuration; the default configuration where no file exists. |
Source code in rdd/rdd_site.py
68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 | |
ReelSite
The pages of a reel site.
The layout is the one the BITPlan applications share - a menu with home, github, help and about, a footer with copyright and version, per the Home page and menu decision. It is rendered here rather than imported so that a reel site needs python and nothing else.
Source code in rdd/rdd_site.py
216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 | |
__init__(config, version=None, reels=None, reviews=None)
Initialize with the site configuration.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
config
|
RddSiteConfig
|
the configuration of this site. |
required |
version
|
Optional[Version]
|
version info of the software; defaults to the package version. |
None
|
reels
|
Optional[Reels]
|
the reels of this site; scanned from the configuration when None. |
None
|
reviews
|
Optional[Reviews]
|
the review rights of this site; loaded from the default path when None. |
None
|
Source code in rdd/rdd_site.py
225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 | |
about(lang='en')
The about page - version, license and repository.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
lang
|
str
|
the language of the page. |
'en'
|
Source code in rdd/rdd_site.py
730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 | |
allowed(reel, review=None)
Whether the holder of the given right may inspect the reel.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
reel
|
Reel
|
the reel. |
required |
review
|
Optional[Review]
|
the review right; None for anonymous. |
None
|
Returns:
| Type | Description |
|---|---|
bool
|
True for a public or demo reel, or a reel the review grants - |
bool
|
every reel where the review grants the wildcard. |
Source code in rdd/rdd_site.py
301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 | |
check_main_demo()
Get the mandatory main demo of this site.
Returns:
| Type | Description |
|---|---|
Reel
|
the reel the configuration names as main_demo. |
Raises:
| Type | Description |
|---|---|
ValueError
|
where main_demo is unset, unknown or not in demo status - a site has at least one demo. |
Source code in rdd/rdd_site.py
257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 | |
demo_card(lang='en')
The demo section of the home page - the main_demo of this site.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
lang
|
str
|
the language of the card. |
'en'
|
Returns:
| Type | Description |
|---|---|
str
|
the demo card markup; empty where the main_demo does not |
str
|
resolve, so a misconfigured site still serves its home page. |
Source code in rdd/rdd_site.py
572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 | |
flag_selector(lang)
The language selector with flag - the other languages as links.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
lang
|
str
|
the language of the current page. |
required |
Returns:
| Type | Description |
|---|---|
str
|
the selector markup. |
Source code in rdd/rdd_site.py
510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 | |
home(lang='en')
The home page - what this site is and the two ways in.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
lang
|
str
|
the language of the page. |
'en'
|
Source code in rdd/rdd_site.py
596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 | |
installation(reviews_file)
The installation mode page - the state a visitor sees while the site is not initialized.
Per the Owner bootstrap decision a site without reviews.yaml refuses to serve reels and names the init command instead; the state is shown, never hidden behind a dead backend.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
reviews_file
|
str
|
the reviews file whose absence is the state. |
required |
Returns:
| Type | Description |
|---|---|
str
|
the installation mode page. |
Source code in rdd/rdd_site.py
668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 | |
menu(lang='en')
The menu entries - settings and chat are dropped, a visitor has neither.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
lang
|
str
|
the language of the entry names. |
'en'
|
Source code in rdd/rdd_site.py
463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 | |
not_found(path, lang='en')
The framed 404 page - it shows like any other page, with an example of a valid address.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
path
|
str
|
the path that has no page. |
required |
lang
|
str
|
the language of the page. |
'en'
|
Returns:
| Type | Description |
|---|---|
str
|
the framed 404 page. |
Source code in rdd/rdd_site.py
701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 | |
page(title, content, lang='en')
Render a page with menu and footer.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
title
|
str
|
title of the page. |
required |
content
|
str
|
the html of the page body. |
required |
lang
|
str
|
the language of the page. |
'en'
|
Returns:
| Type | Description |
|---|---|
str
|
the complete html page. |
Source code in rdd/rdd_site.py
526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 | |
reel_files(reel)
The files of the given reel folder.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
reel
|
Reel
|
the reel. |
required |
Returns:
| Type | Description |
|---|---|
List[str]
|
the sorted file names, hidden files and the review page |
List[str]
|
excluded - the review page is code of the package, never |
List[str]
|
data of a reel. |
Source code in rdd/rdd_site.py
345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 | |
reel_page(reel, lang='en')
The page of one reel - what it is and the files it carries.
The review and file links are relative so a review link keeps its token.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
reel
|
Reel
|
the reel. |
required |
lang
|
str
|
the language of the page. |
'en'
|
Returns:
| Type | Description |
|---|---|
str
|
the reel page. |
Source code in rdd/rdd_site.py
365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 | |
reel_url(reel, review=None)
The url this site serves the given reel under.
Per the Delivery decision the acronym is the only address a url needs; a review link carries its token before the acronym.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
reel
|
Reel
|
the reel. |
required |
review
|
Optional[Review]
|
the review whose token the url carries; None for the public url. |
None
|
Returns:
| Type | Description |
|---|---|
str
|
the url of the reel. |
Source code in rdd/rdd_site.py
281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 | |
reel_zip(reel)
Zip the folder of the given reel into a temporary file.
Per the Reel verdict decision the verdict page offers the reel folder as one download; videos do not compress, so the entries are stored.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
reel
|
Reel
|
the reel. |
required |
Returns:
| Type | Description |
|---|---|
str
|
the path of the temporary zip file - the caller removes it |
str
|
after delivery. |
Source code in rdd/rdd_site.py
437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 | |
reels(review=None, lang='en')
The reels directory as the holder of the given right sees it.
Anyone sees the public and demo reels; a Review right adds its private reels under the same directory.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
review
|
Optional[Review]
|
the review right; None for the anonymous directory. |
None
|
lang
|
str
|
the language of the page. |
'en'
|
Returns:
| Type | Description |
|---|---|
str
|
the reels directory page. |
Source code in rdd/rdd_site.py
626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 | |
resolve_reel(parts)
Resolve the addressed reel - shortcut or lengthy form.
Per the Hop url decision the acronym is the shortcut address and year/month/acronym the lengthy form disambiguating non-unique acronyms.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
parts
|
List[str]
|
the path parts after /reels/ with the token stripped. |
required |
Returns:
| Type | Description |
|---|---|
|
the reel or None, and the remaining path parts. |
Source code in rdd/rdd_site.py
318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 | |
review_page(lang='en')
The review page of this site.
Per the Review UI stack decision the packaged page is one self-contained file; serving it, the site derives the CSS variables from its named palette, puts its own menu into the marked header slot and speaks the visitor's language via the marked i18n slot, so the review wears the same palette, menu and language as every other page of the site - one source, no second copy to keep in step.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
lang
|
str
|
the language of the page. |
'en'
|
Returns:
| Type | Description |
|---|---|
str
|
the review page in the site's palette, menu and language. |
Source code in rdd/rdd_site.py
399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 | |
scan()
Scan the recordings directory of this site into its directory of reels.
Returns:
| Type | Description |
|---|---|
Reels
|
the reels the recordings directory holds. |
Source code in rdd/rdd_site.py
247 248 249 250 251 252 253 254 255 | |
style()
The stylesheet, derived from the named palette.
Source code in rdd/rdd_site.py
480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 | |
Review
One review right - a reviewer and the reels their link grants.
Per the Reel Review decision the review rights are not modeled in SMW (yet) but kept as entities the rdd site must keep track of. Per the Owner bootstrap decision the owner's Review grants the wildcard '*' - every reel, including future ones.
Source code in rdd/rdd_site.py
133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 | |
is_wildcard
property
Whether this review grants every reel.
Reviews
The review rights of a site.
Source code in rdd/rdd_site.py
157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 | |
by_token()
The lookup from token to review.
Returns:
| Type | Description |
|---|---|
Dict[str, Review]
|
the lookup from token to review. |
Source code in rdd/rdd_site.py
182 183 184 185 186 187 188 189 | |
of_path(path=None)
classmethod
Load the reviews from the given yaml file.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
path
|
Optional[str]
|
the reviews file; the default path when None. |
None
|
Returns:
| Type | Description |
|---|---|
Reviews
|
the reviews; no reviews where no file exists. |
Source code in rdd/rdd_site.py
165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 | |
init_command()
The init command as this installation runs it.
A user cannot be expected to know a command name, and the venv of a service is not on anybody's PATH - so the command is named with the absolute path of the running installation where the rdd dispatcher lies beside the interpreter, and by its bare name otherwise.
Returns:
| Type | Description |
|---|---|
str
|
the command that initializes this site. |
Source code in rdd/rdd_site.py
755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 | |
serve(config, host='127.0.0.1', reviews_path=None)
Serve the site of the given configuration with uvicorn.
Per the Owner bootstrap decision a site without reviews.yaml is in installation mode: it stays up, refuses to serve reels and names the init command on every request, so the state is never hidden behind a dead backend.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
config
|
RddSiteConfig
|
the site configuration. |
required |
host
|
str
|
the interface to listen on; localhost by default - the web server in front is what the internet talks to. |
'127.0.0.1'
|
reviews_path
|
Optional[str]
|
the reviews file; the default path when None. |
None
|
Source code in rdd/rdd_site.py
773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 | |
recording
Created on 2026-08-08.
hop records of a graph walk
Schema: the Meeting context of https://contexts.bitplan.com * https://contexts.bitplan.com/index.php/Concept:HopContent * https://contexts.bitplan.com/index.php/Concept:Recording
@author: wf
HopContent
One node visit in the graph walk of a Recording.
The node is the page, screen or application reached by a context switch; the record says when it was reached and what happened there.
The field names are the property names of https://contexts.bitplan.com/index.php/Concept:HopContent and are never renamed: a record is stored as a subobject on its Recording page and read back by the same names.
Source code in rdd/recording.py
55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 | |
HopContents
The hops of one Recording.
https://contexts.bitplan.com/index.php/Concept:Recording is linked to its hops 1:n via the recordingHops TopicLink, and the hopCount of the Recording must equal the number of hop records - that equality is the mechanical completeness control of the walk.
Source code in rdd/recording.py
77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 | |
hopCount
property
The number of hop records, to be compared with the hopCount of the Recording.
__post_init__()
Start with an empty hop list where none was given.
Source code in rdd/recording.py
90 91 92 93 | |
add(hop)
Add a hop, giving it the next position and this recording.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
hop
|
HopContent
|
the hop record to add. |
required |
Returns:
| Type | Description |
|---|---|
HopContent
|
the added hop. |
Source code in rdd/recording.py
102 103 104 105 106 107 108 109 110 111 112 113 114 | |
Recording
One recorded video of a session, with its processing state.
The field names are the property names of https://contexts.bitplan.com/index.php/Concept:Recording and are never renamed: a Recording is a page in the Meeting context and is read back by the same names.
A Recording is the record of a video, not the video itself; reading pictures from the file is the business of the specialization in rdd.frame.
The acronym is the short ASCII identifier every artefact of this Recording carries as its name prefix, so an artefact can be traced back to its Recording by name alone.
Source code in rdd/recording.py
17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 | |
reelreview
Reel Driven Development - reelreview server https://github.com/WolfgangFahl/reel-driven-development
Serves a recording folder for the reelreview.html human-in-the-loop verdict pass and accepts the curated reel.yaml back:
GET / -> reelreview.html
GET /<file> -> static file from the recording folder
GET /api/files -> JSON list of the folder's file names
GET /api/info -> JSON folder name and acronym of the reviewed reel
GET /api/reel -> JSON of the reel.yaml hop set parsed by the model
POST /api/save -> body replaces reel.yaml (RCS checkpoint first)
POST /api/feedback -> body replaces reel-feedback.yaml (RCS checkpoint first)
POST /api/upload -> forward reel-feedback.yaml body to the feedback_url
configured in reel.yaml's config block
The YAML work is done by the HopSet model - the page never parses YAML.
Usage
reelreview [folder][--port PORT]
ReelReviewHandler
Bases: SimpleHTTPRequestHandler
Serve one recording folder for the review pass.
The whole folder is readable and reel.yaml and reel-feedback.yaml are writable through the api, with no authentication of any kind - this is a single user tool for the person curating a reel on their own machine. It is bound to localhost for that reason; a --host that opens it to a network hands that write access to everyone who can reach the port.
Source code in rdd/reelreview.py
37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 | |
checkpoint(target)
Version the current reel.yaml with RCS before overwriting.
Source code in rdd/reelreview.py
151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 | |
do_GET()
Answer a file or api request.
/, /index.html and /reelreview.html serve the packaged review page - the page is never data of the folder, so a stale copy in the folder is shadowed; an extensionless path is a hop url per the Hop url decision and serves the page too, positioned by the page itself. /api/info answers the folder name and acronym of the reviewed reel, /api/files the file names of the folder, /api/reel the hop set of reel.yaml parsed by the model; anything else is a static file of the folder.
Source code in rdd/reelreview.py
47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 | |
do_POST()
Take the curated yaml back.
/api/save replaces reel.yaml and /api/feedback reel- feedback.yaml, both after an RCS checkpoint of the file being replaced; /api/upload forwards the body to the feedback_url of reel.yaml. Anything else is a 404.
Source code in rdd/reelreview.py
107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 | |
upload(content)
Forward the feedback to the feedback_url configured in reel.yaml.
Source code in rdd/reelreview.py
130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 | |
page_path()
Path of the review page shipped with the package.
Source code in rdd/reelreview.py
168 169 170 171 | |
serve(folder, port=DEFAULT_PORT, host=DEFAULT_HOST)
Serve the given recording folder for the review pass.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
folder
|
Path
|
the recording folder holding reel.yaml. |
required |
port
|
int
|
port to serve on. |
DEFAULT_PORT
|
host
|
str
|
interface to listen on; localhost by default - the api writes reel.yaml without authentication, so binding a reachable interface is an explicit choice of the person starting the server. |
DEFAULT_HOST
|
Raises:
| Type | Description |
|---|---|
ValueError
|
if the folder holds no reel.yaml or the page is missing. |
Source code in rdd/reelreview.py
174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 | |
reelreview_cmd
Created on 2026-08-12.
command line interface of the reel review pass
see https://github.com/WolfgangFahl/reel-driven-development/issues/27
@author: wf
ReelReviewCmd
Bases: BaseCmd
Serve a recording folder for the human in the loop review pass.
Source code in rdd/reelreview_cmd.py
21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 | |
__init__()
Initialize with the reel-driven-development version info.
Source code in rdd/reelreview_cmd.py
24 25 26 | |
add_arguments(parser)
Add the review arguments to the given parser.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
parser
|
ArgumentParser
|
the parser to add arguments to. |
required |
Source code in rdd/reelreview_cmd.py
28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 | |
handle_args(args)
Handle the parsed arguments by serving the folder.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
args
|
Namespace
|
parsed argument namespace. |
required |
Returns:
| Type | Description |
|---|---|
bool
|
True if the arguments were handled. |
Source code in rdd/reelreview_cmd.py
53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 | |
main(argv=None)
Command line entry point of the review pass.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
argv
|
Optional[List[str]]
|
command line arguments; defaults to sys.argv. |
None
|
Returns:
| Type | Description |
|---|---|
int
|
exit code: 0 = OK, 1 = KeyboardInterrupt, 2 = Exception. |
Source code in rdd/reelreview_cmd.py
70 71 72 73 74 75 76 77 78 79 80 81 | |
reels
Created on 2026-08-13.
the directory of reels of an installation - found by scanning a recordings directory
see the Directory of reels and Reel Review ADRs on https://media.bitplan.com/index.php/Talk:Rdd.bitplan.com
@author: wf
Reel
One reel of an installation - the published form of a Recording.
Per the Unit of reel publication decision the reel is the folder with its reel.yaml; the folder name is the identifier a url can carry, the hop set is the record the reviews and the document pass read back.
Source code in rdd/reels.py
24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 | |
acronym
property
The acronym of the reel, the folder name where the reel names none.
folder
property
The name of the reel folder.
hop_count
property
The number of hops of this reel.
is_demo
property
Whether this reel is offered in true inspection mode.
is_public
property
Whether anyone may inspect this reel.
recording
property
The Recording of this reel, None where the reel names none.
status
property
The status of this reel - the state of its Recording.
The status continues from the processing states into publication: public and demo per the Reel Review decision.
title
property
The name of the recording, the acronym where the reel names none.
year_month
property
The year/month of the recording date - the lengthy address part of the Hop url decision; None where the reel names no date.
hop_slugs()
The persistent identifier slugs of the hops of this reel.
Per #21 the slug is the evidence frame name minus its extension - the base name, so frame, hop and url carry one identity even when the frame lies in a subfolder.
Returns:
| Type | Description |
|---|---|
List[str]
|
the slugs of the hops carrying a screenshot. |
Source code in rdd/reels.py
109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 | |
Reels
The directory of reels below a recordings directory.
The directory is built at startup by walking for reel.yaml files and reading each of them, so what the site offers is what the disk has. The walk and the read are timed - a scan that grows with the number of reels has to be measurable before a cache is worth its complexity.
Source code in rdd/reels.py
128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 | |
count
property
The number of reels held in memory.
total_time
property
The time the whole scan took.
as_summary()
A one line summary of the scan for the service log.
Returns:
| Type | Description |
|---|---|
str
|
the counts and timings of this scan. |
Source code in rdd/reels.py
255 256 257 258 259 260 261 262 263 264 265 266 267 268 | |
by_acronym()
The directory of reels - the lookup from acronym to reel.
Returns:
| Type | Description |
|---|---|
Dict[str, Reel]
|
the lookup from acronym to reel. |
Source code in rdd/reels.py
204 205 206 207 208 209 210 211 | |
by_pid()
The lookup by the lengthy address - year/month/acronym.
Per the Hop url decision the acronym is a shortcut that holds while acronyms are unique; the lengthy form disambiguates by the year and month of the Recording date.
Returns:
| Type | Description |
|---|---|
Dict[str, Reel]
|
the lookup from year/month/acronym to reel. |
Source code in rdd/reels.py
213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 | |
of_dir(recordings_dir)
classmethod
Scan the given directory into the directory of reels.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
recordings_dir
|
str
|
the directory holding the reel folders. |
required |
Returns:
| Type | Description |
|---|---|
Reels
|
the reels found, with the timings of the scan. |
Source code in rdd/reels.py
179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 | |
paths_of(recordings_dir)
classmethod
Get the sorted paths of the reel folders below the given directory.
A reel folder is a directory carrying a reel.yaml; the walk does not descend into a reel folder, so files beside the reel - frames, video, document - cost nothing but their directory entry.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
recordings_dir
|
str
|
the directory to walk. |
required |
Returns:
| Type | Description |
|---|---|
List[str]
|
the sorted paths of the reel folders. |
Source code in rdd/reels.py
157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 | |
visible(granted=None)
The reels the holder of the given right may see.
Anyone sees the public and demo reels; a Review right adds its private reels - per the Reel Review decision the access right changes the visibility of reels in the reels directory. The wildcard '*' grants every reel per the Owner bootstrap decision.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
granted
|
Optional[List[str]]
|
the acronyms a Review grants; None for anonymous. |
None
|
Returns:
| Type | Description |
|---|---|
List[Reel]
|
the visible reels, in the order of the scan. |
Source code in rdd/reels.py
230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 | |
reelsite_cmd
Created on 2026-08-12.
command line interface of the reel site
@author: wf
ReelSiteCmd
Bases: BaseCmd
Serve the reel site of an organization.
Source code in rdd/reelsite_cmd.py
21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 | |
__init__()
Initialize with the reel-driven-development version info.
Source code in rdd/reelsite_cmd.py
24 25 26 | |
add_arguments(parser)
Add the site arguments to the given parser.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
parser
|
ArgumentParser
|
the parser to add arguments to. |
required |
Source code in rdd/reelsite_cmd.py
28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 | |
handle_args(args)
Handle the parsed arguments - init, mint or serve.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
args
|
Namespace
|
parsed argument namespace. |
required |
Returns:
| Type | Description |
|---|---|
bool
|
True if the arguments were handled. |
Source code in rdd/reelsite_cmd.py
67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 | |
init_site(config)
Run installation mode - seed the owner interactively.
Per the Owner bootstrap decision the owner link is shown once on the interactive terminal and written to a mode 600 file; it goes nowhere else.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
config
|
RddSiteConfig
|
the site configuration. |
required |
Source code in rdd/reelsite_cmd.py
104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 | |
main(argv=None)
Command line entry point of the reel site.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
argv
|
Optional[List[str]]
|
command line arguments; defaults to sys.argv. |
None
|
Returns:
| Type | Description |
|---|---|
int
|
exit code: 0 = OK, 1 = KeyboardInterrupt, 2 = Exception. |
Source code in rdd/reelsite_cmd.py
124 125 126 127 128 129 130 131 132 133 134 135 | |
transcript
Created on 2026-08-10.
the transcript of a reel
Schema: the Meeting context of https://contexts.bitplan.com * https://contexts.bitplan.com/index.php/Concept:TranscriptSegment
The segments live in a file of their own beside the reel: the reel file is what a person curates by hand, and a transcript of a few hundred segments would drown the few dozen hops in it.
see https://github.com/WolfgangFahl/reel-driven-development/issues/25
@author: wf
Transcript
The improved transcript of one reel.
The raw result of the transcription stays as it came out of the tool; this is the corrected reading of it, and the two stay diffable.
Source code in rdd/transcript.py
41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 | |
segmentCount
property
The number of segments of this transcript.
__post_init__()
Start with an empty segment list where none was given.
Source code in rdd/transcript.py
54 55 56 57 | |
of_dir(folder)
classmethod
Get the transcript of the given folder.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
folder
|
str
|
the recording folder. |
required |
Returns:
| Type | Description |
|---|---|
Optional[Transcript]
|
the transcript, None where the folder carries none - a reel |
Optional[Transcript]
|
may be documented before its transcript is improved. |
Source code in rdd/transcript.py
78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 | |
path_of(folder)
classmethod
Get the path of the transcript file in the given folder.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
folder
|
str
|
the recording folder. |
required |
Returns:
| Type | Description |
|---|---|
str
|
the path of the transcript file. |
Source code in rdd/transcript.py
65 66 67 68 69 70 71 72 73 74 75 76 | |
TranscriptSegment
One timed segment of the improved transcript of a Recording.
The field names are the property names of https://contexts.bitplan.com/index.php/Concept:TranscriptSegment and are never renamed. end stays empty where the source provides only starts; speaker stays empty where diarization and content disagree - an unattributed segment is the honest form, a guessed one is not.
Source code in rdd/transcript.py
23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | |
version
Created on 2026-08-02.
@author: wf
Version
dataclass
Version information for reel-driven-development.
Source code in rdd/version.py
11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | |
webapp
Created on 2026-08-14.
the fastapi application of a reel site - the pages and the api as decided, plus /docs and /openapi.json per the OpenAPI docs issue
Per the Delivery decision the site itself answers below /reels/; the web server in front only proxies. Per the framed 404 issue every miss answers the framed page. The docs are self-contained like every other page: the swagger assets are served by the site, never by a CDN.
@author: wf
InstallationApp
The installation mode application.
Per the Owner bootstrap decision a site without reviews.yaml refuses to serve reels and names the init command on every request - the state is shown, never hidden behind a dead backend.
Source code in rdd/webapp.py
376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 | |
__init__(page)
Initialize with the installation page.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
page
|
str
|
the installation mode page. |
required |
Source code in rdd/webapp.py
384 385 386 387 388 389 390 391 392 | |
add_routes()
Every request answers the installation state.
Source code in rdd/webapp.py
394 395 396 397 398 399 400 401 402 | |
RateLimit
Per-client limit on missed lookups.
Per the Reel Review decision unknown tokens are rate-limited: every miss is tarpitted, and a client whose misses exceed the limit within the window answers 429 until the window has passed.
Source code in rdd/webapp.py
32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 | |
__init__(max_misses=10, window_seconds=60.0)
Initialize the limit.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
max_misses
|
int
|
the misses a client may accumulate per window. |
10
|
window_seconds
|
float
|
the sliding window in seconds. |
60.0
|
Source code in rdd/webapp.py
40 41 42 43 44 45 46 47 48 49 | |
miss(client, now=None)
Record a miss for the given client.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
client
|
str
|
the client address the miss counts against. |
required |
now
|
Optional[float]
|
the time of the miss; the monotonic clock by default. |
None
|
Returns:
| Type | Description |
|---|---|
bool
|
True where the client is over the limit. |
Source code in rdd/webapp.py
51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 | |
ReelApp
The fastapi application of a reel site.
One instance wires the routes of one ReelSite; the app is what uvicorn serves and what /docs documents.
Source code in rdd/webapp.py
122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 | |
__init__(site)
Initialize with the site to serve.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
site
|
ReelSite
|
the reel site. |
required |
Source code in rdd/webapp.py
129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 | |
add_routes()
Wire the routes of the site.
Source code in rdd/webapp.py
221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 | |
address_parts(address)
Resolve an address below /reels/ into right, reel and rest.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
address
|
str
|
the path after /reels/, optionally token first. |
required |
Returns:
| Type | Description |
|---|---|
Optional[Review]
|
the review right or None, the reel or None, and the |
Optional[Reel]
|
remaining path parts. |
Source code in rdd/webapp.py
185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 | |
checked_reel(address)
The reel of the given address where the right allows it.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
address
|
str
|
the path after /reels/, optionally token first. |
required |
Returns:
| Type | Description |
|---|---|
Optional[Reel]
|
reel, review and remaining parts; reel is None where the |
Optional[Review]
|
address resolves to nothing the right allows. |
Source code in rdd/webapp.py
204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 | |
not_found(request, tarpit=False)
The framed 404 response.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
request
|
Request
|
the request that has no page. |
required |
tarpit
|
bool
|
delay the answer so tokens and private acronyms cannot be probed; a client over the rate limit answers 429 instead. |
False
|
Returns:
| Type | Description |
|---|---|
HTMLResponse
|
the framed 404 page as a response; 429 over the limit. |
Source code in rdd/webapp.py
158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 | |
create_app(site)
Create the fastapi application of the given site.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
site
|
ReelSite
|
the reel site. |
required |
Returns:
| Type | Description |
|---|---|
FastAPI
|
the application. |
Source code in rdd/webapp.py
405 406 407 408 409 410 411 412 413 414 415 | |
create_installation_app(page)
Create the installation mode application.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
page
|
str
|
the installation mode page. |
required |
Returns:
| Type | Description |
|---|---|
FastAPI
|
the application. |
Source code in rdd/webapp.py
418 419 420 421 422 423 424 425 426 427 428 | |
lang_of(request)
The language of the given request.
Per the i18n issue the default is the browser setting; an explicit ?lang= wins and is remembered by the cookie the response sets.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
request
|
Request
|
the request. |
required |
Returns:
| Type | Description |
|---|---|
str
|
the language code. |
Source code in rdd/webapp.py
71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 | |
page_response(page, status=200)
The given page as an html response.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
page
|
str
|
the html page. |
required |
status
|
int
|
the http status; 200 by default. |
200
|
Returns:
| Type | Description |
|---|---|
HTMLResponse
|
the response; no-cache so a browser never shows a stale page. |
Source code in rdd/webapp.py
107 108 109 110 111 112 113 114 115 116 117 118 119 | |
remember_lang(request, response)
Remember an explicit language choice in the cookie.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
request
|
Request
|
the request whose ?lang= is the choice, if any. |
required |
response
|
HTMLResponse
|
the response to carry the cookie. |
required |
Returns:
| Type | Description |
|---|---|
HTMLResponse
|
the response. |
Source code in rdd/webapp.py
91 92 93 94 95 96 97 98 99 100 101 102 103 104 | |