58
58
pAttr ("blur" , 9 , is_nat ),
59
59
pAttr ("width" , 400 , is_nat1 ),
60
60
)
61
- pixeldiff_builder = pAttrs (
62
- "pixeldiff" ,
63
- pAttr ("threshold" , 1 , is_nat ),
64
- pAttr ("stream" , 0 , is_nat ),
65
- )
66
61
subtitle_builder = pAttrs (
67
62
"subtitle" ,
68
63
pAttr ("pattern" , Required , is_str ),
74
69
builder_map = {
75
70
"audio" : audio_builder ,
76
71
"motion" : motion_builder ,
77
- "pixeldiff" : pixeldiff_builder ,
78
72
"subtitle" : subtitle_builder ,
79
73
}
80
74
@@ -353,7 +347,6 @@ def cleanhtml(raw_html: str) -> str:
353
347
354
348
def motion (self , s : int , blur : int , width : int ) -> NDArray [np .float64 ]:
355
349
import av
356
- from PIL import ImageChops , ImageFilter
357
350
358
351
av .logging .set_level (av .logging .PANIC )
359
352
@@ -370,28 +363,20 @@ def motion(self, s: int, blur: int, width: int) -> NDArray[np.float64]:
370
363
stream = container .streams .video [s ]
371
364
stream .thread_type = "AUTO"
372
365
373
- if (
374
- stream .duration is None
375
- or stream .time_base is None
376
- or stream .average_rate is None
377
- ):
378
- inaccurate_dur = 1
379
- else :
380
- inaccurate_dur = int (
381
- stream .duration * stream .time_base * stream .average_rate
382
- )
383
-
366
+ inaccurate_dur = 1 if stream .duration is None else stream .duration
384
367
self .bar .start (inaccurate_dur , "Analyzing motion" )
385
368
386
- prev_image = None
387
- image = None
369
+ prev_frame = None
370
+ current_frame = None
388
371
total_pixels = self .src .videos [0 ].width * self .src .videos [0 ].height
389
372
index = 0
390
373
391
374
graph = av .filter .Graph ()
392
375
link_nodes (
393
376
graph .add_buffer (template = stream ),
394
377
graph .add ("scale" , f"{ width } :-1" ),
378
+ graph .add ("format" , "gray" ),
379
+ graph .add ("gblur" , f"sigma={ blur } " ),
395
380
graph .add ("buffersink" ),
396
381
)
397
382
graph .configure ()
@@ -402,98 +387,31 @@ def motion(self, s: int, blur: int, width: int) -> NDArray[np.float64]:
402
387
graph .push (unframe )
403
388
frame = graph .pull ()
404
389
405
- prev_image = image
406
-
407
- assert isinstance (frame .time , float )
390
+ # Showing progress ...
391
+ assert frame .time is not None
408
392
index = int (frame .time * self .tb )
409
- self .bar .tick (index )
393
+ if frame .pts is not None :
394
+ self .bar .tick (frame .pts )
395
+
396
+ current_frame = frame .to_ndarray ()
410
397
411
398
if index > len (threshold_list ) - 1 :
412
399
threshold_list = np .concatenate (
413
400
(threshold_list , np .zeros ((len (threshold_list )), dtype = np .float64 )),
414
401
axis = 0 ,
415
402
)
416
403
417
- image = frame .to_image ().convert ("L" )
418
-
419
- if blur > 0 :
420
- image = image .filter (ImageFilter .GaussianBlur (radius = blur ))
421
-
422
- if prev_image is not None :
423
- count = np .count_nonzero (ImageChops .difference (prev_image , image ))
424
-
425
- threshold_list [index ] = count / total_pixels
426
-
427
- self .bar .end ()
428
- result = threshold_list [:index ]
429
- del threshold_list
430
-
431
- return self .cache ("motion" , mobj , result )
432
-
433
- def pixeldiff (self , s : int ) -> NDArray [np .uint64 ]:
434
- import av
435
- from PIL import ImageChops
436
-
437
- av .logging .set_level (av .logging .PANIC )
438
-
439
- pobj = {"stream" : s }
440
-
441
- if s >= len (self .src .videos ):
442
- raise LevelError (f"pixeldiff: video stream '{ s } ' does not exist." )
443
-
444
- if (arr := self .read_cache ("pixeldiff" , pobj )) is not None :
445
- return arr
446
-
447
- container = av .open (f"{ self .src .path } " , "r" )
448
-
449
- stream = container .streams .video [s ]
450
- stream .thread_type = "AUTO"
451
-
452
- if (
453
- stream .duration is None
454
- or stream .time_base is None
455
- or stream .average_rate is None
456
- ):
457
- inaccurate_dur = 1
458
- else :
459
- inaccurate_dur = int (
460
- stream .duration * stream .time_base * stream .average_rate
461
- )
462
-
463
- self .bar .start (inaccurate_dur , "Analyzing pixel diffs" )
464
-
465
- prev_image = None
466
- image = None
467
- index = 0
468
-
469
- threshold_list = np .zeros ((1024 ), dtype = np .uint64 )
470
-
471
- for frame in container .decode (stream ):
472
- prev_image = image
473
-
474
- assert frame .time is not None
475
- index = int (frame .time * self .tb )
476
- self .bar .tick (index )
477
-
478
- if index > len (threshold_list ) - 1 :
479
- threshold_list = np .concatenate (
480
- (threshold_list , np .zeros ((len (threshold_list )), dtype = np .uint64 )),
481
- axis = 0 ,
404
+ if prev_frame is not None :
405
+ # Use `int16` to avoid underflow with `uint8` datatype
406
+ diff = np .abs (
407
+ prev_frame .astype (np .int16 ) - current_frame .astype (np .int16 )
482
408
)
409
+ threshold_list [index ] = np .count_nonzero (diff ) / total_pixels
483
410
484
- assert isinstance (frame , av .VideoFrame )
485
- image = frame .to_image ()
486
-
487
- if prev_image is not None :
488
- threshold_list [index ] = np .count_nonzero (
489
- ImageChops .difference (prev_image , image )
490
- )
411
+ prev_frame = current_frame
491
412
492
413
self .bar .end ()
493
- result = threshold_list [:index ]
494
- del threshold_list
495
-
496
- return self .cache ("pixeldiff" , pobj , result )
414
+ return self .cache ("motion" , mobj , threshold_list [:index ])
497
415
498
416
499
417
def edit_method (val : str , filesetup : FileSetup , env : Env ) -> NDArray [np .bool_ ]:
@@ -558,8 +476,6 @@ def edit_method(val: str, filesetup: FileSetup, env: Env) -> NDArray[np.bool_]:
558
476
levels .motion (obj ["stream" ], obj ["blur" ], obj ["width" ]),
559
477
obj ["threshold" ],
560
478
)
561
- if method == "pixeldiff" :
562
- return to_threshold (levels .pixeldiff (obj ["stream" ]), obj ["threshold" ])
563
479
564
480
if method == "subtitle" :
565
481
return levels .subtitle (
0 commit comments