added new feature, lagkage

This commit is contained in:
Christopher Konopka
2025-11-21 00:09:14 -05:00
parent 4be7193695
commit 2e674134c0
21 changed files with 1460 additions and 0 deletions

1
css/style.css Normal file

File diff suppressed because one or more lines are too long

1
css/style.min.css vendored Normal file

File diff suppressed because one or more lines are too long

167
experimental/wipe2.sh Normal file
View File

@@ -0,0 +1,167 @@
#!/bin/bash
# Combined ffmpeg -y commands for all static video wipes with 3-second delay between each
# Initial list wipes
ffmpeg -y -i 0.mp4 -i 1.mp4 -filter_complex "[0:v][1:v]xfade=transition=wipeleft:duration=1.0:offset=5,format=yuv420p" -c:v libx264 -y horizontal_wipe.mp4
sleep 3
ffmpeg -y -i 0.mp4 -i 1.mp4 -filter_complex "[0:v][1:v]xfade=transition=wipeup:duration=1.0:offset=5,format=yuv420p" -c:v libx264 -y vertical_wipe.mp4
sleep 3
ffmpeg -y -i 0.mp4 -i 1.mp4 -filter_complex "[0:v][1:v]xfade=transition=diagbr:duration=1.0:offset=5,format=yuv420p" -c:v libx264 -y diagonal_topleft_bottomright_wipe.mp4
sleep 3
ffmpeg -y -i 0.mp4 -i 1.mp4 -filter_complex "[0:v][1:v]xfade=transition=diagbl:duration=1.0:offset=5,format=yuv420p" -c:v libx264 -y diagonal_topright_bottomleft_wipe.mp4
sleep 3
ffmpeg -y -i 0.mp4 -i 1.mp4 -filter_complex "[0:v][1:v]xfade=transition=circleopen:duration=1.0:offset=5,format=yuv420p" -c:v libx264 -y circular_wipe.mp4
sleep 3
ffmpeg -y -i 0.mp4 -i 1.mp4 -filter_complex "[0:v][1:v]xfade=transition=rectcrop:duration=1.0:offset=5,format=yuv420p" -c:v libx264 -y square_wipe.mp4
sleep 3
ffmpeg -y -i 0.mp4 -i 1.mp4 -i diamond_mask.png -filter_complex "[1:v][2:v]alphamerge[fg];[0:v][fg]overlay=0:0,format=yuv420p" -c:v libx264 -y diamond_wipe.mp4
sleep 3
ffmpeg -y -i 0.mp4 -i 1.mp4 -i star_mask.png -filter_complex "[1:v][2:v]alphamerge[fg];[0:v][fg]overlay=0:0,format=yuv420p" -c:v libx264 -y star_wipe.mp4
sleep 3
ffmpeg -y -i 0.mp4 -i 1.mp4 -i cross_mask.png -filter_complex "[1:v][2:v]alphamerge[fg];[0:v][fg]overlay=0:0,format=yuv420p" -c:v libx264 -y cross_wipe.mp4
sleep 3
ffmpeg -y -i 0.mp4 -i 1.mp4 -filter_complex "[0:v][1:v]xfade=transition=pixelize:duration=1.0:offset=5,format=yuv420p" -c:v libx264 -y checkerboard_wipe.mp4
sleep 3
# Expanded list wipes
ffmpeg -y -i 0.mp4 -i 1.mp4 -filter_complex "[0:v][1:v]xfade=transition=fade:duration=1.0:offset=5,format=yuv420p" -c:v libx264 -y full_wipe.mp4
sleep 3
ffmpeg -y -i 0.mp4 -i 1.mp4 -filter_complex "[0:v][1:v]xfade=transition=circleopen:duration=1.0:offset=5,format=yuv420p" -c:v libx264 -y circle_wipe.mp4
sleep 3
ffmpeg -y -i 0.mp4 -i 1.mp4 -filter_complex "[0:v][1:v]xfade=transition=circleopen:duration=1.0:offset=5,format=yuv420p" -c:v libx264 -y iris_circle_wipe.mp4
sleep 3
ffmpeg -y -i 0.mp4 -i 1.mp4 -filter_complex "[0:v][1:v]xfade=transition=circleopen:duration=1.0:offset=5,format=yuv420p" -c:v libx264 -y ellipse_wipe.mp4
sleep 3
ffmpeg -y -i 0.mp4 -i 1.mp4 -filter_complex "[0:v][1:v]xfade=transition=clock:duration=1.0:offset=5,format=yuv420p" -c:v libx264 -y clock_wipe.mp4
sleep 3
ffmpeg -y -i 0.mp4 -i 1.mp4 -filter_complex "[0:v][1:v]xfade=transition=circleopen:duration=1.0:offset=5,format=yuv420p" -c:v libx264 -y reveal_circle_wipe.mp4
sleep 3
ffmpeg -y -i 0.mp4 -i 1.mp4 -i triangle_mask.png -filter_complex "[1:v][2:v]alphamerge[fg];[0:v][fg]overlay=0:0,format=yuv420p" -c:v libx264 -y triangle_wipe.mp4
sleep 3
ffmpeg -y -i 0.mp4 -i 1.mp4 -i triangle_mask.png -filter_complex "[1:v][2:v]alphamerge[fg];[0:v][fg]overlay=0:0,format=yuv420p" -c:v libx264 -y reveal_triangle_wipe.mp4
sleep 3
ffmpeg -y -i 0.mp4 -i 1.mp4 -filter_complex "[0:v][1:v]xfade=transition=wipeleft:duration=1.0:offset=5,format=yuv420p" -c:v libx264 -y half_wipe.mp4
sleep 3
ffmpeg -y -i 0.mp4 -i 1.mp4 -filter_complex "[0:v][1:v]xfade=transition=rectcrop:duration=1.0:offset=5,format=yuv420p" -c:v libx264 -y iris_square_wipe.mp4
sleep 3
ffmpeg -y -i 0.mp4 -i 1.mp4 -filter_complex "[0:v][1:v]xfade=transition=rectcrop:duration=1.0:offset=5,format=yuv420p" -c:v libx264 -y rectangle_wipe.mp4
sleep 3
ffmpeg -y -i 0.mp4 -i 1.mp4 -filter_complex "[0:v][1:v]xfade=transition=rectcrop:duration=1.0:offset=5,format=yuv420p" -c:v libx264 -y reveal_square_wipe.mp4
sleep 3
ffmpeg -y -i 0.mp4 -i 1.mp4 -i star_mask.png -filter_complex "[1:v][2:v]alphamerge[fg];[0:v][fg]overlay=0:0,format=yuv420p" -c:v libx264 -y reveal_star_wipe.mp4
sleep 3
ffmpeg -y -i 0.mp4 -i 1.mp4 -i heart_mask.png -filter_complex "[1:v][2:v]alphamerge[fg];[0:v][fg]overlay=0:0,format=yuv420p" -c:v libx264 -y heart_wipe.mp4
sleep 3
ffmpeg -y -i 0.mp4 -i 1.mp4 -i pentagon_mask.png -filter_complex "[1:v][2:v]alphamerge[fg];[0:v][fg]overlay=0:0,format=yuv420p" -c:v libx264 -y pentagon_wipe.mp4
sleep 3
ffmpeg -y -i 0.mp4 -i 1.mp4 -i hexagon_mask.png -filter_complex "[1:v][2:v]alphamerge[fg];[0:v][fg]overlay=0:0,format=yuv420p" -c:v libx264 -y hexagon_wipe.mp4
sleep 3
ffmpeg -y -i 0.mp4 -i 1.mp4 -i octagon_mask.png -filter_complex "[1:v][2:v]alphamerge[fg];[0:v][fg]overlay=0:0,format=yuv420p" -c:v libx264 -y octagon_wipe.mp4
sleep 3
ffmpeg -y -i 0.mp4 -i 1.mp4 -i parallelogram_mask.png -filter_complex "[1:v][2:v]alphamerge[fg];[0:v][fg]overlay=0:0,format=yuv420p" -c:v libx264 -y parallelogram_wipe.mp4
sleep 3
ffmpeg -y -i 0.mp4 -i 1.mp4 -i chevron_mask.png -filter_complex "[1:v][2:v]alphamerge[fg];[0:v][fg]overlay=0:0,format=yuv420p" -c:v libx264 -y chevron_wipe.mp4
sleep 3
ffmpeg -y -i 0.mp4 -i 1.mp4 -filter_complex "[0:v][1:v]xfade=transition=diagbr:duration=1.0:offset=5,format=yuv420p" -c:v libx264 -y diagonal_linear_wipe.mp4
sleep 3
ffmpeg -y -i 0.mp4 -i 1.mp4 -filter_complex "[0:v][1:v]xfade=transition=diagbl:duration=1.0:offset=5,format=yuv420p" -c:v libx264 -y split_diagonal_wipe.mp4
sleep 3
ffmpeg -y -i 0.mp4 -i 1.mp4 -filter_complex "[0:v][1:v]xfade=transition=radial:duration=1.0:offset=5,format=yuv420p" -c:v libx264 -y angular_shutter_wipe.mp4
sleep 3
ffmpeg -y -i 0.mp4 -i 1.mp4 -i zigzag_mask.png -filter_complex "[1:v][2:v]alphamerge[fg];[0:v][fg]overlay=0:0,format=yuv420p" -c:v libx264 -y zigzag_wipe.mp4
sleep 3
ffmpeg -y -i 0.mp4 -i 1.mp4 -i sawtooth_mask.png -filter_complex "[1:v][2:v]alphamerge[fg];[0:v][fg]overlay=0:0,format=yuv420p" -c:v libx264 -y sawtooth_wipe.mp4
sleep 3
ffmpeg -y -i 0.mp4 -i 1.mp4 -filter_complex "[0:v][1:v]xfade=transition=pixelize:duration=1.0:offset=5,format=yuv420p" -c:v libx264 -y grid_wipe.mp4
sleep 3
ffmpeg -y -i 0.mp4 -i 1.mp4 -i honeycomb_mask.png -filter_complex "[1:v][2:v]alphamerge[fg];[0:v][fg]overlay=0:0,format=yuv420p" -c:v libx264 -y honeycomb_wipe.mp4
sleep 3
ffmpeg -y -i 0.mp4 -i 1.mp4 -i starfield_mask.png -filter_complex "[1:v][2:v]alphamerge[fg];[0:v][fg]overlay=0:0,format=yuv420p" -c:v libx264 -y starfield_wipe.mp4
sleep 3
ffmpeg -y -i 0.mp4 -i 1.mp4 -filter_complex "[0:v][1:v]xfade=transition=pixelize:duration=1.0:offset=5,format=yuv420p" -c:v libx264 -y mosaic_wipe.mp4
sleep 3
ffmpeg -y -i 0.mp4 -i 1.mp4 -filter_complex "[0:v][1:v]xfade=transition=hlr:duration=1.0:offset=5,format=yuv420p" -c:v libx264 -y barn_door_h_wipe.mp4
sleep 3
ffmpeg -y -i 0.mp4 -i 1.mp4 -filter_complex "[0:v][1:v]xfade=transition=vud:duration=1.0:offset=5,format=yuv420p" -c:v libx264 -y barn_door_v_w
ipe.mp4
sleep 3
ffmpeg -y -i 0.mp4 -i 1.mp4 -i four_panel_mask.png -filter_complex "[1:v][2:v]alphamerge[fg];[0:v][fg]overlay=0:0,format=yuv420p" -c:v libx264 -y four_panel_wipe.mp4
sleep 3
ffmpeg -y -i 0.mp4 -i 1.mp4 -filter_complex "[0:v][1:v]xfade=transition=hblur:duration=1.0:offset=5,format=yuv420p" -c:v libx264 -y blinds_h_wipe.mp4
sleep 3
ffmpeg -y -i 0.mp4 -i 1.mp4 -filter_complex "[0:v][1:v]xfade=transition=vblur:duration=1.0:offset=5,format=yuv420p" -c:v libx264 -y blinds_v_wipe.mp4
sleep 3
ffmpeg -y -i 0.mp4 -i 1.mp4 -filter_complex "[0:v][1:v]xfade=transition=radial:duration=1.0:offset=5,format=yuv420p" -c:v libx264 -y fan_blades_wipe.mp4
sleep 3
ffmpeg -y -i 0.mp4 -i 1.mp4 -filter_complex "[0:v][1:v]xfade=transition=clock:duration=1.0:offset=5,format=yuv420p" -c:v libx264 -y clock_hands_wipe.mp4
sleep 3
ffmpeg -y -i 0.mp4 -i 1.mp4 -filter_complex "[0:v][1:v]xfade=transition=circleopen:duration=1.0:offset=5,format=yuv420p" -c:v libx264 -y curved_arc_wipe.mp4
sleep 3
ffmpeg -y -i 0.mp4 -i 1.mp4 -i page_curl_mask.png -filter_complex "[1:v][2:v]alphamerge[fg];[0:v][fg]overlay=0:0,format=yuv420p" -c:v libx264 -y page_curl_wipe.mp4
sleep 3
ffmpeg -y -i 0.mp4 -i 1.mp4 -i flipboard_mask.png -filter_complex "[1:v][2:v]alphamerge[fg];[0:v][fg]overlay=0:0,format=yuv420p" -c:v libx264 -y flipboard_wipe.mp4
sleep 3
ffmpeg -y -i 0.mp4 -i 1.mp4 -filter_complex "[0:v][1:v]xfade=transition=hblur:duration=1.0:offset=5,format=yuv420p" -c:v libx264 -y louver_wipe.mp4
sleep 3
ffmpeg -y -i 0.mp4 -i 1.mp4 -i sliding_polygon_mask.png -filter_complex "[1:v][2:v]alphamerge[fg];[0:v][fg]overlay=0:0,format=yuv420p" -c:v libx264 -y sliding_polygon_wipe.mp4
sleep 3
ffmpeg -y -i 0.mp4 -i 1.mp4 -i fractal_mask.png -filter_complex "[1:v][2:v]alphamerge[fg];[0:v][fg]overlay=0:0,format=yuv420p" -c:v libx264 -y fractal_wipe.mp4
sleep 3
ffmpeg -y -i 0.mp4 -i 1.mp4 -filter_complex "[0:v][1:v]xfade=transition=pixelize:duration=1.0:offset=5,format=yuv420p" -c:v libx264 -y fade_grid_wipe.mp4
sleep 3
ffmpeg -y -i 0.mp4 -i 1.mp4 -i cube_spin_mask.png -filter_complex "[1:v][2:v]alphamerge[fg];[0:v][fg]overlay=0:0,format=yuv420p" -c:v libx264 -y cube_spin_wipe.mp4
sleep 3
ffmpeg -y -i 0.mp4 -i 1.mp4 -i card_flip_mask.png -filter_complex "[1:v][2:v]alphamerge[fg];[0:v][fg]overlay=0:0,format=yuv420p" -c:v libx264 -y card_flip_wipe.mp4
sleep 3
ffmpeg -y -i 0.mp4 -i 1.mp4 -i polygon_tunnel_mask.png -filter_complex "[1:v][2:v]alphamerge[fg];[0:v][fg]overlay=0:0,format=yuv420p" -c:v libx264 -y polygon_tunnel_wipe.mp4
sleep 3
ffmpeg -y -i 0.mp4 -i 1.mp4 -i prism_fold_mask.png -filter_complex "[1:v][2:v]alphamerge[fg];[0:v][fg]overlay=0:0,format=yuv420p" -c:v libx264 -y prism_fold_wipe.mp4
sleep 3
ffmpeg -y -i 0.mp4 -i 1.mp4 -i svg_mask.png -filter_complex "[1:v][2:v]alphamerge[fg];[0:v][fg]overlay=0:0,format=yuv420p" -c:v libx264 -y svg_mask_wipe.mp4
sleep 3
ffmpeg -y -i 0.mp4 -i 1.mp4 -i text_reveal_mask.png -filter_complex "[1:v][2:v]alphamerge[fg];[0:v][fg]overlay=0:0,format=yuv420p" -c:v libx264 -y text_reveal_wipe.mp4
sleep 3
ffmpeg -y -i 0.mp4 -i 1.mp4 -i logo_mask.png -filter_complex "[1:v][2:v]alphamerge[fg];[0:v][fg]overlay=0:0,format=yuv420p" -c:v libx264 -y logo_wipe.mp4
sleep 3
ffmpeg -y -i 0.mp4 -i 1.mp4 -filter_complex "[0:v][1:v]xfade=transition=rectcrop:duration=1.0:offset=5,format=yuv420p" -c:v libx264 -y letterboxed_wipe.mp4
sleep 3
ffmpeg -y -i 0.mp4 -i 1.mp4 -filter_complex "[0:v][1:v]xfade=transition=radial:duration=1.0:offset=5,format=yuv420p" -c:v libx264 -y angular_burst_wipe.mp4
sleep 3
ffmpeg -y -i 0.mp4 -i 1.mp4 -i polygon_scatter_mask.png -filter_complex "[1:v][2:v]alphamerge[fg];[0:v][fg]overlay=0:0,format=yuv420p" -c:v libx264 -y polygon_scatter_wipe.mp4
sleep 3
ffmpeg -y -i 0.mp4 -i 1.mp4 -i blade_slash_mask.png -filter_complex "[1:v][2:v]alphamerge[fg];[0:v][fg]overlay=0:0,format=yuv420p" -c:v libx264 -y blade_slash_wipe.mp4
sleep 3
ffmpeg -y -i 0.mp4 -i 1.mp4 -filter_complex "[0:v][1:v]xfade=transition=radial:duration=1.0:offset=5,format=yuv420p" -c:v libx264 -y rotating_window_wipe.mp4
sleep 3
ffmpeg -y -i 0.mp4 -i 1.mp4 -filter_complex "[0:v][1:v]xfade=transition=radial:duration=1.0:offset=5,format=yuv420p" -c:v libx264 -y radial_wipe.mp4
sleep 3
ffmpeg -y -i 0.mp4 -i 1.mp4 -filter_complex "[0:v][1:v]xfade=transition=wipeleft:duration=1.0:offset=5,format=yuv420p" -c:v libx264 -y bar_h_wipe.mp4
sleep 3
ffmpeg -y -i 0.mp4 -i 1.mp4 -filter_complex "[0:v][1:v]xfade=transition=wipeup:duration=1.0:offset=5,format=yuv420p" -c:v libx264 -y bar_v_wipe.mp4
sleep 3
ffmpeg -y -i 0.mp4 -i 1.mp4 -filter_complex "[0:v][1:v]xfade=transition=hblur:duration=1.0:offset=5,format=yuv420p" -c:v libx264 -y venetian_wipe.mp4
sleep 3
ffmpeg -y -i 0.mp4 -i 1.mp4 -filter_complex "[0:v][1:v]xfade=transition=vblur:duration=1.0:offset=5,format=yuv420p" -c:v libx264 -y window_blind_wipe.mp4
sleep 3
ffmpeg -y -i 0.mp4 -i 1.mp4 -filter_complex "[0:v][1:v]xfade=transition=hlr:duration=1.0:offset=5,format=yuv420p" -c:v libx264 -y door_h_wipe.mp4
sleep 3
ffmpeg -y -i 0.mp4 -i 1.mp4 -filter_complex "[0:v][1:v]xfade=transition=vud:duration=1.0:offset=5,format=yuv420p" -c:v libx264 -y door_v_wipe.mp4
sleep 3
ffmpeg -y -i 0.mp4 -i 1.mp4 -i page_peel_mask.png -filter_complex "[1:v][2:v]alphamerge[fg];[0:v][fg]overlay=0:0,format=yuv420p" -c:v libx264 -y page_peel_wipe.mp4
sleep 3
ffmpeg -y -i 0.mp4 -i 1.mp4 -filter_complex "[0:v][1:v]xfade=transition=zoom:duration=1.0:offset=5,format=yuv420p" -c:v libx264 -y zoom_in_wipe.mp4
sleep 3
ffmpeg -y -i 0.mp4 -i 1.mp4 -filter_complex "[0:v][1:v]xfade=transition=zoom:duration=1.0:offset=5,format=yuv420p" -c:v libx264 -y zoom_out_wipe.mp4
sleep 3
ffmpeg -y -i 0.mp4 -i 1.mp4 -filter_complex "[0:v][1:v]xfade=transition=wipeleft:duration=1.0:offset=5,format=yuv420p" -c:v libx264 -y push_left_wipe.mp4
sleep 3
ffmpeg -y -i 0.mp4 -i 1.mp4 -filter_complex "[0:v][1:v]xfade=transition=wiperight:duration=1.0:offset=5,format=yuv420p" -c:v libx264 -y push_right_wipe.mp4
sleep 3
ffmpeg -y -i 0.mp4 -i 1.mp4 -filter_complex "[0:v][1:v]xfade=transition=wipeup:duration=1.0:offset=5,format=yuv420p" -c:v libx264 -y push_up_wipe.mp4
sleep 3
ffmpeg -y -i 0.mp4 -i 1.mp4 -filter_complex "[0:v][1:v]xfade=transition=wipedown:duration=1.0:offset=5,format=yuv420p" -c:v libx264 -y push_down_wipe.mp4
sleep 3
ffmpeg -y -i 0.mp4 -i 1.mp4 -filter_complex "[0:v][1:v]xfade=transition=radial:duration=1.0:offset=5,format=yuv420p" -c:v libx264 -y spiral_wipe.mp4
sleep 3
ffmpeg -y -i 0.mp4 -i 1.mp4 -i wave_mask.png -filter_complex "[1:v][2:v]alphamerge[fg];[0:v][fg]overlay=0:0,format=yuv420p" -c:v libx264 -y wave_wipe.mp4

View File

@@ -0,0 +1,34 @@
#!/bin/bash
ffmpeg -i 0.mp4 -i 1.mp4 -filter_complex "[0:v][1:v]xfade=transition=wipeleft:duration=1:offset=5,format=yuv420p" -c:v libx264 -y horizontal_wipe.mp4
sleep 2
ffmpeg -i 0.mp4 -i 1.mp4 -filter_complex "[0:v][1:v]xfade=transition=wipeup:duration=1:offset=5,format=yuv420p" -c:v libx264 -y vertical_wipe.mp4
sleep 2
ffmpeg -i 0.mp4 -i 1.mp4 -filter_complex "[0:v][1:v]xfade=transition=diagbr:duration=1:offset=5,format=yuv420p" -c:v libx264 -y diagonal_topleft_bottomright_wipe.mp4
sleep 2
#!/bin/bash
ffmpeg -i 0.mp4 -i 1.mp4 -filter_complex "[0:v][1:v]xfade=transition=diagbl:duration=1:offset=5,format=yuv420p" -c:v libx264 -y diagonal_topright_bottomleft_wipe.mp4
sleep 2
#!/bin/bash
ffmpeg -i 0.mp4 -i 1.mp4 -filter_complex "[0:v][1:v]xfade=transition=circleopen:duration=1:offset=5,format=yuv420p" -c:v libx264 -y circular_wipe.mp4
sleep 2
#!/bin/bash
ffmpeg -i 0.mp4 -i 1.mp4 -filter_complex "[0:v][1:v]xfade=transition=rectcrop:duration=1:offset=5,format=yuv420p" -c:v libx264 -y square_wipe.mp4
sleep 2
#!/bin/bash
# Note: FFmpeg's xfade does not have a direct diamond wipe. Approximating with custom mask.
# Create a diamond-shaped mask image (white diamond on black background) named diamond_mask.png
ffmpeg -i 0.mp4 -i 1.mp4 -i diamond_mask.png -filter_complex "[1:v][2:v]alphamerge[fg];[0:v][fg]overlay=0:0,format=yuv420p" -c:v libx264 -y diamond_wipe.mp4
sleep 2
#!/bin/bash
# Note: FFmpeg's xfade does not support star wipe. Requires custom star-shaped mask.
# Create a star-shaped mask image (white star on black background) named star_mask.png
ffmpeg -i 0.mp4 -i 1.mp4 -i star_mask.png -filter_complex "[1:v][2:v]alphamerge[fg];[0:v][fg]overlay=0:0,format=yuv420p" -c:v libx264 -y star_wipe.mp4
sleep 2
#!/bin/bash
# Note: FFmpeg's xfade does not support cross wipe. Approximating with custom mask.
# Create a cross-shaped mask image (white cross on black background) named cross_mask.png
ffmpeg -i 0.mp4 -i 1.mp4 -i cross_mask.png -filter_complex "[1:v][2:v]alphamerge[fg];[0:v][fg]overlay=0:0,format=yuv420p" -c:v libx264 -y cross_wipe.mp4
sleep 2
#!/bin/bash
# Note: FFmpeg's xfade does not support checkerboard wipe. Approximating with pixelize filter.
ffmpeg -i 0.mp4 -i 1.mp4 -filter_complex "[0:v][1:v]xfade=transition=pixelize:duration=1:offset=5,format=yuv420p" -c:v libx264 -y checkerboard_wipe.mp4

201
index.html Normal file
View File

@@ -0,0 +1,201 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>MySoftware - The Missing Package Manager</title>
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif;
margin: 0;
padding: 0;
line-height: 1.6;
color: #2f2f2f;
background-color: #f5f5f5;
text-align: center;
}
.container {
max-width: 960px;
margin: 0 auto;
padding: 0 20px;
}
header {
padding: 80px 0;
background-color: #fff;
}
.logo {
max-width: 120px;
margin-bottom: 24px;
}
h1 {
font-size: 48px;
font-weight: 700;
margin: 0;
color: #000;
}
.tagline {
font-size: 24px;
color: #595959;
margin: 16px 0 0;
}
section {
padding: 80px 0;
background-color: #fff;
}
h2 {
font-size: 32px;
font-weight: 700;
margin-bottom: 24px;
color: #000;
}
.two-column {
display: flex;
justify-content: space-between;
gap: 40px;
align-items: center;
}
.column-left {
flex: 1;
text-align: left;
max-width: 50%;
}
.column-right {
flex: 1;
max-width: 50%;
position: relative;
}
.column-left p {
font-size: 18px;
margin: 0 0 16px;
}
line{
color:black;
}
p {
font-size: 18px;
margin: 0 0 16px;
}
.install-code {
background: #2f2f2f;
color: #fff;
padding: 16px;
border-radius: 8px;
font-family: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
font-size: 14px;
text-align: left;
margin: 0;
white-space: pre-wrap;
position: relative;
}
.copy-button {
position: absolute;
: top: 8px;
right: 8px;
background: #f36d21;
color: #fff;
border: none;
border-radius: 4px;
padding: 8px 16px;
font-size: 14px;
cursor: pointer;
opacity: 0;
transition: opacity 0.2s;
}
.install-code:hover .copy-button {
opacity: 1;
}
.copy-button:hover {
background: #d15e1c;
}
a {
color: #f36d21;
text-decoration: underline;
transition: color 0.2s;
}
a:hover {
color: #d15e1c;
}
@media (max-width: 768px) {
.two-column {
flex-direction: column;
gap: 24px;
}
.column-left,
.column-right {
max-width: 100%;
}
h1 {
font-size: 36px;
}
.tagline {
font-size: 20px;
}
h2 {
font-size: 28px;
}
p, .column-left p {
font-size: 16px;
}
.install-code {
font-size: 13px;
}
}
</style>
</head>
<body>
<header>
<div class="container">
<img src="https://via.placeholder.com/120" alt="MySoftware Logo" class="logo">
<h1>MySoftware</h1>
<p class="tagline">The Missing Package Manager for Your System</p>
</div>
</header>
<section id="install">
<div class="container">
<h2>Installation</h2>
<p>Paste that in a macOS Terminal or Linux shell prompt.</p>
<pre class="install-code">/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/MySoftware/install/main/install.sh)"<button class="copy-button" onclick="copyCode(this)">Copy</button></pre>
<p>The script explains what it will do and then pauses before it does it. <a href="https://docs.mysoftware.com/Installation">Read about other installation options.</a></p>
</div>
</section>
<section id="what-does">
<div class="container">
<h2>What Does videobeaux Do?</h2>
<div class="two-column">
<div class="column-left">
<p>MySoftware installs the stuff you need that your system didnt.</p>
</div>
<div class="column-right">
<pre class="install-code">mysoftware install package<button class="copy-button" onclick="copyCode(this)">Copy</button></pre>
</div>
</div>
</div>
<div class="container">
<div class="two-column">
<div class="column-left">
<p>MySoftware installs the stuff you need that your system didnt.</p>
</div>
<div class="column-right">
<pre class="install-code">mysoftware install package<button class="copy-button" onclick="copyCode(this)">Copy</button></pre>
</div>
</div>
</div>
</section>
<script>
function copyCode(button) {
const code = button.parentElement.textContent.replace('Copy', '').trim();
navigator.clipboard.writeText(code).then(() => {
button.textContent = 'Copied!';
setTimeout(() => {
button.textContent = 'Copy';
}, 2000);
});
}
</script>
</body>
</html>

View File

@@ -0,0 +1,39 @@
{
"sequence_direction": "forward",
"layers": [
{
"layer_number": 1,
"name": "logo",
"filename": "media/mask8.png",
"type": "img",
"mode": "place",
"place": "top_right",
"size": 20,
"opacity": 0.85,
"blend_mode": "normal"
},
{
"layer_number": 2,
"name": "corner_camera",
"filename": "media/schwwaaa.mp4",
"type": "video",
"mode": "free",
"pos_x": 120,
"pos_y": 200,
"size": 35,
"opacity": 0.7,
"blend_mode": "normal"
},
{
"layer_number": 3,
"name": "animated_sticker",
"filename": "media/media.gif",
"type": "gif",
"mode": "place",
"place": "bottom_left",
"size": 15,
"opacity": 0.6,
"blend_mode": "normal"
}
]
}

View File

@@ -0,0 +1,108 @@
{
"sequence_direction": "forward",
"layers": [
{
"layer_number": 1,
"name": "top_right_logo",
"filename": "media/reem.png",
"type": "img",
"mode": "place",
"place": "top_right",
"size": 18,
"opacity": 0.95,
"blend_mode": "normal"
},
{
"layer_number": 2,
"name": "top_left_bug",
"filename": "media/screen.png",
"type": "img",
"mode": "place",
"place": "top_left",
"size": 10,
"opacity": 0.8,
"blend_mode": "normal"
},
{
"layer_number": 3,
"name": "pip_camera_a",
"filename": "media/schwwaaa.mp4",
"type": "video",
"mode": "free",
"pos_x": 80,
"pos_y": 80,
"size": 28,
"opacity": 1.0,
"blend_mode": "normal"
},
{
"layer_number": 4,
"name": "pip_camera_b",
"filename": "media/faith.mp4",
"type": "video",
"mode": "free",
"pos_x": 80,
"pos_y": 420,
"size": 28,
"opacity": 1.0,
"blend_mode": "normal"
},
{
"layer_number": 5,
"name": "right_side_vertical_bar",
"filename": "media/mask1.png",
"type": "img",
"mode": "place",
"place": "center_right",
"size": 12,
"opacity": 0.9,
"blend_mode": "normal"
},
{
"layer_number": 6,
"name": "lower_third_plate",
"filename": "media/gross.png",
"type": "img",
"mode": "place",
"place": "bottom_center",
"size": 60,
"opacity": 0.95,
"blend_mode": "normal"
},
{
"layer_number": 7,
"name": "looping_sticker_left",
"filename": "media/circus.gif",
"type": "gif",
"mode": "free",
"pos_x": 120,
"pos_y": 260,
"size": 14,
"opacity": 1.0,
"blend_mode": "normal"
},
{
"layer_number": 8,
"name": "looping_sticker_right",
"filename": "media/bunny.gif",
"type": "gif",
"mode": "free",
"pos_x": 1520,
"pos_y": 260,
"size": 14,
"opacity": 1.0,
"blend_mode": "normal"
},
{
"layer_number": 9,
"name": "full_frame_vhs_noise",
"filename": "media/media.gif",
"type": "gif",
"mode": "place",
"place": "center",
"size": 100,
"opacity": 0.35,
"blend_mode": "normal"
}
]
}

View File

@@ -0,0 +1,112 @@
{
"sequence_direction": "forward",
"layers": [
{
"layer_number": 1,
"name": "top_right_logo_floating",
"filename": "media/reem.png",
"type": "img",
"mode": "free",
"pos_x": 1430,
"pos_y": 70,
"size": 14,
"opacity": 0.95,
"blend_mode": "normal"
},
{
"layer_number": 2,
"name": "offset_corner_bug",
"filename": "media/screen.png",
"type": "img",
"mode": "free",
"pos_x": 90,
"pos_y": 40,
"size": 12,
"opacity": 0.8,
"blend_mode": "normal"
},
{
"layer_number": 3,
"name": "pip_camera_a_small_top",
"filename": "media/schwwaaa.mp4",
"type": "video",
"mode": "free",
"pos_x": 260,
"pos_y": 120,
"size": 22,
"opacity": 1.0,
"blend_mode": "normal"
},
{
"layer_number": 4,
"name": "pip_camera_b_large_bottom",
"filename": "media/faith.mp4",
"type": "video",
"mode": "free",
"pos_x": 1080,
"pos_y": 530,
"size": 36,
"opacity": 0.95,
"blend_mode": "normal"
},
{
"layer_number": 5,
"name": "angled_mask_mid_left",
"filename": "media/mask1.png",
"type": "img",
"mode": "free",
"pos_x": 160,
"pos_y": 360,
"size": 26,
"opacity": 0.9,
"blend_mode": "normal"
},
{
"layer_number": 6,
"name": "lower_third_plate_offset",
"filename": "media/gross.png",
"type": "img",
"mode": "free",
"pos_x": 260,
"pos_y": 780,
"size": 54,
"opacity": 0.95,
"blend_mode": "normal"
},
{
"layer_number": 7,
"name": "looping_sticker_left_mid",
"filename": "media/circus.gif",
"type": "gif",
"mode": "free",
"pos_x": 220,
"pos_y": 260,
"size": 18,
"opacity": 1.0,
"blend_mode": "normal"
},
{
"layer_number": 8,
"name": "looping_sticker_right_high",
"filename": "media/bunny.gif",
"type": "gif",
"mode": "free",
"pos_x": 1380,
"pos_y": 180,
"size": 11,
"opacity": 1.0,
"blend_mode": "normal"
},
{
"layer_number": 9,
"name": "full_frame_vhs_noise_soft",
"filename": "media/media.gif",
"type": "gif",
"mode": "place",
"place": "center",
"size": 115,
"opacity": 0.3,
"blend_mode": "normal"
}
]
}

View File

@@ -0,0 +1,111 @@
{
"sequence_direction": "forward",
"layers": [
{
"layer_number": 1,
"name": "logo_top_right",
"filename": "media/reem.png",
"type": "img",
"mode": "place",
"place": "top_right",
"size": 16,
"opacity": 0.95,
"blend_mode": "normal"
},
{
"layer_number": 2,
"name": "screen_near_top_center",
"filename": "media/screen.png",
"type": "img",
"mode": "free",
"pos_x": 420,
"pos_y": 60,
"size": 12,
"opacity": 0.8,
"blend_mode": "normal"
},
{
"layer_number": 3,
"name": "pip_camera_a_left_mid",
"filename": "media/schwwaaa.mp4",
"type": "video",
"mode": "free",
"pos_x": 120,
"pos_y": 160,
"size": 24,
"opacity": 1.0,
"blend_mode": "normal"
},
{
"layer_number": 4,
"name": "pip_camera_b_right_lower",
"filename": "media/faith.mp4",
"type": "video",
"mode": "free",
"pos_x": 1120,
"pos_y": 520,
"size": 32,
"opacity": 0.95,
"blend_mode": "normal"
},
{
"layer_number": 5,
"name": "mask_left_edge_half_off",
"filename": "media/mask1.png",
"type": "img",
"mode": "free",
"pos_x": -80,
"pos_y": 340,
"size": 22,
"opacity": 0.9,
"blend_mode": "normal"
},
{
"layer_number": 6,
"name": "gross_lower_band",
"filename": "media/gross.png",
"type": "img",
"mode": "free",
"pos_x": 260,
"pos_y": 800,
"size": 52,
"opacity": 0.95,
"blend_mode": "normal"
},
{
"layer_number": 7,
"name": "circus_sticker_top_half_off",
"filename": "media/circus.gif",
"type": "gif",
"mode": "free",
"pos_x": 220,
"pos_y": -40,
"size": 16,
"opacity": 1.0,
"blend_mode": "normal"
},
{
"layer_number": 8,
"name": "bunny_sticker_right_mid",
"filename": "media/bunny.gif",
"type": "gif",
"mode": "free",
"pos_x": 1460,
"pos_y": 260,
"size": 12,
"opacity": 1.0,
"blend_mode": "normal"
},
{
"layer_number": 9,
"name": "full_frame_vhs_noise",
"filename": "media/media.gif",
"type": "gif",
"mode": "place",
"place": "center",
"size": 110,
"opacity": 0.3,
"blend_mode": "normal"
}
]
}

View File

@@ -0,0 +1,111 @@
{
"sequence_direction": "forward",
"layers": [
{
"layer_number": 1,
"name": "logo_top_right_free",
"filename": "../media/reem.png",
"type": "img",
"mode": "free",
"pos_x": 20,
"pos_y": 420,
"size": 30,
"opacity": 0.95,
"blend_mode": "normal"
},
{
"layer_number": 2,
"name": "screen_top_left_free",
"filename": "../media/screen.png",
"type": "img",
"mode": "free",
"pos_x": 10,
"pos_y": 210,
"size": 50,
"opacity": 0.8,
"blend_mode": "normal"
},
{
"layer_number": 3,
"name": "pip_camera_a_left_mid_free",
"filename": "../media/schwwaaa.mp4",
"type": "video",
"mode": "place",
"place": "top_right",
"size": 40,
"opacity": 1.0,
"blend_mode": "normal"
},
{
"layer_number": 4,
"name": "pip_camera_b_right_low_free",
"filename": "../media/faith.mp4",
"type": "video",
"mode": "place",
"place": "top_left",
"size": 34,
"opacity": 0.95,
"blend_mode": "normal"
},
{
"layer_number": 5,
"name": "mask_left_edge_half_off_free",
"filename": "../media/mask1.png",
"type": "img",
"mode": "free",
"pos_x": 450,
"pos_y": 360,
"size": 22,
"opacity": 0.9,
"blend_mode": "normal"
},
{
"layer_number": 6,
"name": "gross_lower_band_free",
"filename": "../media/gross.png",
"type": "img",
"mode": "free",
"pos_x": 180,
"pos_y": 180,
"size": 25,
"opacity": 0.95,
"blend_mode": "normal"
},
{
"layer_number": 7,
"name": "circus_sticker_top_center_free",
"filename": "../media/circus.gif",
"type": "gif",
"mode": "free",
"pos_x": 60,
"pos_y": 260,
"size": 50,
"opacity": 1.0,
"blend_mode": "normal"
},
{
"layer_number": 8,
"name": "bunny_sticker_right_mid_free",
"filename": "../media/bunny.gif",
"type": "gif",
"mode": "free",
"pos_x": 50,
"pos_y": 365,
"size": 75,
"opacity": 1.0,
"blend_mode": "normal"
},
{
"layer_number": 9,
"name": "vhs_noise_center_free",
"filename": "../media/media.gif",
"type": "gif",
"mode": "free",
"pos_x": 175,
"pos_y": 350,
"size": 60,
"opacity": 0.3,
"blend_mode": "normal"
}
]
}

View File

@@ -0,0 +1,87 @@
{
"sequence_direction": "forward",
"layers": [
{
"layer_number": 1,
"name": "logo_top_right_free",
"filename": "../media/reem.png",
"type": "img",
"mode": "free",
"pos_x": 20,
"pos_y": 420,
"size": 30,
"opacity": 0.95,
"blend_mode": "normal"
},
{
"layer_number": 3,
"name": "pip_camera_a_left_mid_free",
"filename": "../media/schwwaaa.mp4",
"type": "video",
"mode": "place",
"place": "top_right",
"size": 40,
"opacity": 1.0,
"blend_mode": "normal"
},
{
"layer_number": 4,
"name": "pip_camera_b_right_low_free",
"filename": "../media/faith.mp4",
"type": "video",
"mode": "place",
"place": "top_left",
"size": 34,
"opacity": 0.95,
"blend_mode": "normal"
},
{
"layer_number": 6,
"name": "gross_lower_band_free",
"filename": "../media/gross.png",
"type": "img",
"mode": "free",
"pos_x": 180,
"pos_y": 180,
"size": 25,
"opacity": 0.95,
"blend_mode": "normal"
},
{
"layer_number": 7,
"name": "circus_sticker_top_center_free",
"filename": "../media/circus.gif",
"type": "gif",
"mode": "free",
"pos_x": 60,
"pos_y": 260,
"size": 50,
"opacity": 1.0,
"blend_mode": "normal"
},
{
"layer_number": 8,
"name": "bunny_sticker_right_mid_free",
"filename": "../media/bunny.gif",
"type": "gif",
"mode": "free",
"pos_x": 50,
"pos_y": 365,
"size": 75,
"opacity": 1.0,
"blend_mode": "normal"
},
{
"layer_number": 9,
"name": "vhs_noise_center_free",
"filename": "../media/media.gif",
"type": "gif",
"mode": "free",
"pos_x": 175,
"pos_y": 350,
"size": 60,
"opacity": 0.3,
"blend_mode": "normal"
}
]
}

BIN
media/audio.wav Normal file

Binary file not shown.

BIN
media/bunny.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 334 KiB

BIN
media/circus.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 179 KiB

BIN
media/gross.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 MiB

Binary file not shown.

BIN
media/reem.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 432 KiB

BIN
media/screen.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 MiB

View File

@@ -0,0 +1 @@
[{"content": "in the new", "start": 0.002077, "end": 10.096557, "words": [{"conf": 0.793502, "end": 3.831176, "start": 0.002077, "word": "in"}, {"conf": 1.0, "end": 9.721856, "start": 3.831176, "word": "the"}, {"conf": 0.257509, "end": 10.096557, "start": 9.899999, "word": "new"}]}, {"content": "receive a very special night of the what", "start": 10.593582, "end": 13.62, "words": [{"conf": 0.880698, "end": 10.98, "start": 10.593582, "word": "receive"}, {"conf": 0.908101, "end": 11.069593, "start": 10.980199, "word": "a"}, {"conf": 0.994212, "end": 11.34, "start": 11.069593, "word": "very"}, {"conf": 0.836042, "end": 11.819474, "start": 11.34, "word": "special"}, {"conf": 0.890157, "end": 12.539867, "start": 12.3, "word": "night"}, {"conf": 0.800731, "end": 12.627985, "start": 12.54, "word": "of"}, {"conf": 0.981462, "end": 12.87, "start": 12.627985, "word": "the"}, {"conf": 0.985449, "end": 13.62, "start": 13.47, "word": "what"}]}, {"content": "part of the came from avellino in the", "start": 13.62, "end": 16.979918, "words": [{"conf": 0.999751, "end": 13.859696, "start": 13.62, "word": "part"}, {"conf": 0.999751, "end": 13.919696, "start": 13.859696, "word": "of"}, {"conf": 0.985837, "end": 15.799475, "start": 14.543549, "word": "the"}, {"conf": 0.451724, "end": 16.014001, "start": 15.84, "word": "came"}, {"conf": 0.999723, "end": 16.14, "start": 16.014001, "word": "from"}, {"conf": 0.994632, "end": 16.71, "start": 16.14, "word": "avellino"}, {"conf": 0.421944, "end": 16.918451, "start": 16.831659, "word": "in"}, {"conf": 0.927838, "end": 16.979918, "start": 16.918451, "word": "the"}]}, {"content": "village o'clock them not which the", "start": 16.980042, "end": 25.115975, "words": [{"conf": 0.999343, "end": 17.28, "start": 16.980042, "word": "village"}, {"conf": 0.518104, "end": 17.578077, "start": 17.28, "word": "o'clock"}, {"conf": 0.531982, "end": 17.7, "start": 17.581578, "word": "them"}, {"conf": 0.357179, "end": 18.15, "start": 17.76, "word": "not"}, {"conf": 0.427486, "end": 18.57, "start": 18.36, "word": "which"}, {"conf": 0.99724, "end": 25.115975, "start": 18.718674, "word": "the"}]}]

View File

@@ -0,0 +1,284 @@
# videobeaux/programs/lagkage.py
#
# Compose multiple visual layers (images/gifs/videos) on top of a base video
# using a single JSON layout file.
#
# GIF layers are preprocessed into finite MP4s that loop for roughly the base
# video duration, so the main overlay graph stays simple and stable.
import json
import os
import random
import subprocess
from pathlib import Path
from videobeaux.utils.ffmpeg_operations import run_ffmpeg_with_progress
def register_arguments(parser):
parser.description = (
"Compose multiple visual layers (images/gifs/videos) on a base video "
"using a JSON layout file. All layers are sized & positioned relative "
"to the base video dimensions."
)
parser.add_argument(
"--layout-json",
required=True,
help="Path to JSON layout describing all layers."
)
parser.add_argument(
"--sequence-direction",
choices=["forward", "backward", "random"],
help="Override sequence_direction in the JSON (optional)."
)
def _load_layout(path: Path):
with open(path, "r", encoding="utf-8") as f:
layout = json.load(f)
if "layers" not in layout or not layout["layers"]:
raise ValueError("Layout JSON must contain a non-empty 'layers' array.")
return layout
def _resolve_sequence(layout, override=None):
seq = override or layout.get("sequence_direction", "forward")
layers = layout["layers"]
layers_sorted = sorted(layers, key=lambda L: L.get("layer_number", 0))
if seq == "forward":
ordered = layers_sorted
elif seq == "backward":
ordered = list(reversed(layers_sorted))
elif seq == "random":
ordered = layers_sorted[:]
random.shuffle(ordered)
else:
ordered = layers_sorted
return seq, ordered
def _probe_base_info(path: str):
"""Return (width, height, duration_seconds) for the base video."""
cmd = [
"ffprobe",
"-v", "error",
"-select_streams", "v:0",
"-show_entries", "stream=width,height",
"-show_entries", "format=duration",
"-of", "json",
path,
]
proc = subprocess.run(cmd, capture_output=True, text=True)
if proc.returncode != 0:
raise RuntimeError(
f"ffprobe failed for {path} (code {proc.returncode}): {proc.stderr}"
)
data = json.loads(proc.stdout)
streams = data.get("streams") or []
if not streams:
raise RuntimeError(f"No video stream found in {path}")
s0 = streams[0]
width = int(s0.get("width", 0) or 0)
height = int(s0.get("height", 0) or 0)
if width <= 0 or height <= 0:
raise RuntimeError(f"Invalid video size from ffprobe for {path}: {width}x{height}")
fmt = data.get("format") or {}
dur_str = fmt.get("duration") or "0"
try:
duration = float(dur_str)
except ValueError:
duration = 0.0
if duration <= 0:
duration = 0.0
return width, height, duration
def _place_expr(layer):
"""Return (x, y) expressions for the overlay filter."""
mode = layer.get("mode", "free")
if mode == "free":
x = str(layer.get("pos_x", 0))
y = str(layer.get("pos_y", 0))
return x, y
slot = layer.get("place", "center")
if slot == "top_left":
return "0", "0"
if slot == "top_right":
return "W-w", "0"
if slot == "bottom_left":
return "0", "H-h"
if slot == "bottom_right":
return "W-w", "H-h"
return "(W-w)/2", "(H-h)/2"
def _preprocess_gif(src: str, base_duration: float, tmp_dir: Path, idx: int) -> str:
"""
Transcode a GIF to a looping video with alpha that roughly matches
the base duration.
IMPORTANT:
- We force even dimensions so filters/encoders don't choke.
- We use an alpha-capable codec (qtrle in a MOV container),
so transparency is preserved instead of being flattened.
"""
tmp_dir.mkdir(parents=True, exist_ok=True)
# Use .mov since qtrle is typically stored in a QuickTime container
temp_path = tmp_dir / f"lagkage_gif_{idx}.mov"
cmd = [
"ffmpeg",
"-hide_banner",
"-loglevel", "error",
"-ignore_loop", "0",
"-stream_loop", "-1",
"-i", src,
# Make width/height even and ensure we have RGBA (with alpha)
"-vf", "scale=trunc(iw/2)*2:trunc(ih/2)*2,format=rgba",
]
# If we know the base duration, trim to that; otherwise just loop and
# let the main overlay graph's base video duration cap play-out.
if base_duration > 0:
cmd += ["-t", f"{base_duration:.3f}"]
cmd += [
# Alpha-capable codec
"-c:v", "qtrle",
"-an",
"-y",
str(temp_path),
]
proc = subprocess.run(cmd)
if proc.returncode != 0:
raise RuntimeError(f"GIF preprocess failed for {src} (code {proc.returncode})")
return str(temp_path)
def run(args):
layout_path = Path(args.layout_json)
if not layout_path.exists():
raise FileNotFoundError(f"Layout JSON not found: {layout_path}")
layout = _load_layout(layout_path)
seq, ordered_layers = _resolve_sequence(layout, args.sequence_direction)
base_input = args.input
if not base_input:
raise ValueError("Global --input (base video) is required for json_layers.")
# 1) Probe base video info once
base_w, base_h, base_duration = _probe_base_info(base_input)
# 2) Prepare inputs for main ffmpeg call
# index 0: base video
# index 1..N: layer sources (with GIFs preprocessed to MP4)
inputs_for_ffmpeg = [("base", base_input)]
layer_inputs = [] # (layer_dict, input_index, target_width_px)
# Folder to hold temp GIF->MP4 files (next to the output file)
tmp_dir = Path(args.output).with_suffix("")
for idx, layer in enumerate(ordered_layers, start=1):
filename = layer.get("filename")
if not filename:
raise ValueError(f"Layer missing 'filename': {layer}")
# Resolve relative to layout JSON file
if not os.path.isabs(filename):
src = str(layout_path.parent / filename)
else:
src = filename
size_pct = float(layer.get("size", 100)) / 100.0
target_w = max(1, int(base_w * size_pct))
layer_type = (layer.get("type") or "").lower()
# If GIF, pre-process to finite-length MP4 that loops to base duration
if layer_type == "gif":
overlay_src = _preprocess_gif(src, base_duration, tmp_dir, idx)
else:
overlay_src = src
inputs_for_ffmpeg.append(("video", overlay_src))
layer_inputs.append((layer, len(inputs_for_ffmpeg) - 1, target_w))
# 3) Build filter_complex
filter_parts = []
# Base video label is [0:v] directly (like overlay_img_pro)
current_label = "[0:v]"
for idx, (layer, input_index, target_w) in enumerate(layer_inputs, start=1):
lay_in = f"[{input_index}:v]"
lay_alpha = f"[lay{idx}]"
next_label = f"[tmp{idx}]"
opacity = float(layer.get("opacity", 1.0))
# blend_mode is kept for future use but ignored here
_blend_mode = (layer.get("blend_mode") or "normal").lower()
x_expr, y_expr = _place_expr(layer)
# scale + alpha
filter_parts.append(
f"{lay_in}"
f"scale={target_w}:-1,"
f"format=rgba,colorchannelmixer=aa={opacity}"
f"{lay_alpha}"
)
# overlay
filter_parts.append(
f"{current_label}{lay_alpha}"
f"overlay=x={x_expr}:y={y_expr}:format=auto"
f"{next_label}"
)
current_label = next_label
out_label = "[out_v]"
filter_parts.append(f"{current_label}format=yuv420p{out_label}")
filter_complex = ";".join(filter_parts)
# 4) Build main ffmpeg command
command = [
"ffmpeg",
"-err_detect", "ignore_err",
"-fflags", "+discardcorrupt+genpts",
]
for _typ, src in inputs_for_ffmpeg:
command.extend(["-i", src])
command.extend([
"-filter_complex", filter_complex,
"-map", out_label,
"-map", "0:a",
"-c:v", "libx264",
"-profile:v", "high",
"-level:v", "4.2",
"-pix_fmt", "yuv420p",
"-movflags", "+faststart",
"-c:a", "aac",
args.output,
])
final_cmd = (command[:1] + ["-y"] + command[1:]) if args.force else command
run_ffmpeg_with_progress(final_cmd, args.input, args.output)

View File

@@ -0,0 +1,203 @@
# videobeaux/programs/xpiritualism.py
#
# Xpiritualism-style aesthetic:
# - Soft pastel glow
# - Multi-layer bloom
# - Hazy vignette (implemented via blend, not fragile options)
# - Gentle film grain
# - Optional hue shift
#
# Usage example:
# videobeaux -P xpiritualism -i input.mp4 -o xpiri_soft.mp4 \
# --style soft --bloom-radius 10 --bloom-strength 0.8 \
# --saturation 1.2 --grain 8 --vignette 0.4
from videobeaux.utils.ffmpeg_operations import run_ffmpeg_with_progress
def register_arguments(parser):
parser.description = (
"Xpiritualism aesthetic: multi-layer bloom + pastel spiritualizer.\n"
"Layers:\n"
" - Soft bloom / glow\n"
" - Pastel color grade / hue shift\n"
" - Hazy vignette\n"
" - Film-like grain overlay\n"
"Presets via --style, with tunable intensities."
)
# High-level “mood” preset
parser.add_argument(
"--style",
choices=["soft", "deep", "cosmic"],
default="soft",
help=(
"Xpiritualism style preset:\n"
" soft = gentle, pastel, minimal grain (default)\n"
" deep = richer contrast, stronger vignette & bloom\n"
" cosmic = more hue shift, grainier, dreamy"
)
)
# Bloom controls
parser.add_argument(
"--bloom-radius",
type=float,
default=8.0,
help="Bloom blur radius (luma_radius for boxblur). Higher = softer glow. Default: 8.0"
)
parser.add_argument(
"--bloom-strength",
type=float,
default=0.7,
help="Bloom blend opacity (0.01.0). Default: 0.7"
)
# Color & tone
parser.add_argument(
"--saturation",
type=float,
default=1.15,
help="Overall saturation multiplier. Default: 1.15"
)
parser.add_argument(
"--hue-shift",
type=float,
default=0.0,
help="Hue shift in DEGREES (used in ffmpeg hue filter). Default: 0.0"
)
# Vignette intensity (we implement this via blend, not filter options)
parser.add_argument(
"--vignette",
type=float,
default=0.35,
help="Vignette blend strength (0.01.0-ish). Default: 0.35"
)
# Grain
parser.add_argument(
"--grain",
type=float,
default=8.0,
help="Film grain strength (ffmpeg noise alls parameter). Default: 8.0"
)
def run(args):
"""
Filtergraph structure:
[0:v] format=yuv444p,split=4 [base][bloom_src][grain_src][vig_src];
# Bloom layer:
[bloom_src] boxblur -> eq (sat bump) -> [bloom]
[base][bloom] blend=screen -> [bloomed]
# Pastel grade + hue shift:
[bloomed] eq (contrast/brightness/sat) + hue -> [pastel]
# Vignette branch (no fancy options; defaults are robust):
[vig_src] vignette [vig_mask]
[pastel][vig_mask] blend=multiply:opacity=VIGNETTE -> [vigged]
# Grain layer:
[grain_src] noise -> [grain]
# Final composite:
[vigged][grain] blend=overlay:opacity=0.30 -> [out_v]
"""
# Start with user-provided values
bloom_radius = float(getattr(args, "bloom_radius", 8.0))
bloom_strength = float(getattr(args, "bloom_strength", 0.7))
saturation = float(getattr(args, "saturation", 1.15))
hue_shift = float(getattr(args, "hue_shift", 0.0))
vignette = float(getattr(args, "vignette", 0.35))
# this is a blend opacity, not a direct vignette filter param now
grain = float(getattr(args, "grain", 8.0))
# Adjust based on style preset
style = getattr(args, "style", "soft")
if style == "deep":
bloom_strength *= 1.15
saturation *= 1.10
vignette *= 1.20
grain *= 1.05
elif style == "cosmic":
# if user left hue_shift at default, give it a gentle cosmic twist
if abs(hue_shift) < 0.01:
hue_shift = 18.0 # degrees
bloom_strength *= 1.10
saturation *= 1.05
vignette *= 1.10
grain *= 1.30
# Clamp some values into sane ranges
def clamp(val, lo, hi):
return max(lo, min(hi, val))
bloom_strength = clamp(bloom_strength, 0.0, 1.0)
saturation = clamp(saturation, 0.5, 2.0)
vignette = clamp(vignette, 0.0, 1.0) # now used directly as blend opacity
grain = clamp(grain, 0.0, 40.0)
# Build filtergraph
# Note: hue filter uses radians internally; we pass degrees * PI/180.
filtergraph = (
# Prep + split into four branches
f"[0:v]format=yuv444p,split=4[base][bloom_src][grain_src][vig_src];"
# Bloom branch
f"[bloom_src]boxblur=luma_radius={bloom_radius}:luma_power=2,"
f"eq=saturation=1.20[bloom];"
# Screen bloom over base
f"[base][bloom]blend=all_mode=screen:all_opacity={bloom_strength}[bloomed];"
# Pastel EQ + hue shift
f"[bloomed]eq=contrast=1.02:brightness=0.02:saturation={saturation},"
f"hue=h={hue_shift}*PI/180[pastel];"
# Vignette branch robust: use default vignette, then multiply with pastel
f"[vig_src]vignette[vig_mask];"
f"[pastel][vig_mask]blend=all_mode=multiply:all_opacity={vignette}[vigged];"
# Grain branch
f"[grain_src]noise=alls={int(grain)}:allf=t+u[grain];"
# Final overlay blend
f"[vigged][grain]blend=all_mode=overlay:all_opacity=0.30[out_v]"
)
command = [
"ffmpeg",
"-err_detect", "ignore_err",
"-fflags", "+genpts+discardcorrupt",
"-i", args.input,
"-filter_complex", filtergraph,
"-map", "[out_v]",
"-map", "0:a?", # keep audio if present
# Video encoding
"-c:v", "libx264",
"-preset", "medium",
"-crf", "18",
"-pix_fmt", "yuv420p",
# Audio copy to avoid unnecessary re-encode
"-c:a", "copy",
args.output,
]
# Respect global --force: inject -y right after 'ffmpeg'
final_cmd = (command[:1] + ["-y"] + command[1:]) if getattr(args, "force", False) else command
# Uncomment this if we need to debug the exact ffmpeg command later:
# print(" ".join(f'"{c}"' if " " in c else c for c in final_cmd))
run_ffmpeg_with_progress(final_cmd, args.input, args.output)