mirror of
https://github.com/kuhyx/testsAndMisc.git
synced 2026-07-04 16:23:04 +02:00
- Move puzzle_solver/, poker_modifier_app/, articles/, tests/ into python_pkg/ - Move moviepy_showcase.py and _moviepy_*.py into python_pkg/moviepy_showcase/ - Update all imports to use python_pkg. prefix - Update pyproject.toml per-file-ignores and pytest testpaths - Add pre-commit hook to enforce Python files under python_pkg/
358 lines
11 KiB
Python
358 lines
11 KiB
Python
"""MoviePy showcase — Part 4 (Audio), 5 (Composition), 6 (Drawing), 7 (Output)."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from moviepy import (
|
|
AudioArrayClip,
|
|
AudioClip,
|
|
ColorClip,
|
|
CompositeAudioClip,
|
|
CompositeVideoClip,
|
|
ImageClip,
|
|
TextClip,
|
|
VideoClip,
|
|
concatenate_audioclips,
|
|
concatenate_videoclips,
|
|
)
|
|
from moviepy.audio.fx import (
|
|
AudioDelay,
|
|
AudioFadeIn,
|
|
AudioFadeOut,
|
|
AudioLoop,
|
|
AudioNormalize,
|
|
MultiplyStereoVolume,
|
|
MultiplyVolume,
|
|
)
|
|
from moviepy.video.compositing.CompositeVideoClip import clips_array
|
|
from moviepy.video.tools.drawing import (
|
|
circle,
|
|
color_gradient,
|
|
color_split,
|
|
)
|
|
import numpy as np
|
|
|
|
from python_pkg.moviepy_showcase.moviepy_showcase import (
|
|
CLIP_DUR,
|
|
FONT_B,
|
|
FONT_R,
|
|
H,
|
|
W,
|
|
_base_clip,
|
|
_resize_to_canvas,
|
|
_section_header,
|
|
_titled,
|
|
)
|
|
|
|
|
|
def _make_sine(freq: float = 440.0, dur: float = CLIP_DUR) -> AudioClip:
|
|
"""Pure sine-wave AudioClip."""
|
|
|
|
def maker(t: np.ndarray) -> np.ndarray:
|
|
t_arr = np.asarray(t)
|
|
wave = 0.3 * np.sin(2 * np.pi * freq * t_arr.flatten())
|
|
stereo = np.column_stack([wave, wave])
|
|
# MoviePy probes with scalar t=0 and uses len(list(frame0))
|
|
# for nchannels. A (1,2) array iterates as 1 row → nchannels=1.
|
|
# Returning shape (2,) for scalar t lets MoviePy detect 2 channels.
|
|
if t_arr.ndim == 0:
|
|
return stereo[0]
|
|
return stereo
|
|
|
|
return AudioClip(maker, duration=dur, fps=44100)
|
|
|
|
|
|
def part4_audio() -> list[VideoClip]:
|
|
"""Demonstrate audio clips and all 7 audio effects."""
|
|
scenes: list[VideoClip] = [
|
|
_section_header(
|
|
"Part 4: Audio",
|
|
"AudioClip · AudioArrayClip · CompositeAudioClip · 7 Audio Effects",
|
|
),
|
|
]
|
|
bg = ColorClip(size=(W, H), color=(20, 30, 50))
|
|
|
|
# AudioClip
|
|
a1 = _make_sine(440, CLIP_DUR)
|
|
c1 = bg.with_duration(CLIP_DUR).with_audio(a1)
|
|
scenes.append(_titled(c1, "AudioClip(sine_440Hz)"))
|
|
|
|
# AudioArrayClip
|
|
sr = 44100
|
|
t_arr = np.linspace(0, CLIP_DUR, int(sr * CLIP_DUR), endpoint=False)
|
|
arr = (0.3 * np.sin(2 * np.pi * 880 * t_arr)).astype(np.float64)
|
|
stereo = np.column_stack([arr, arr])
|
|
a2 = AudioArrayClip(stereo, fps=sr)
|
|
c2 = bg.with_duration(CLIP_DUR).with_audio(a2)
|
|
scenes.append(_titled(c2, "AudioArrayClip(numpy_array, fps=44100) # 880Hz"))
|
|
|
|
# CompositeAudioClip
|
|
low = _make_sine(220, CLIP_DUR)
|
|
high = _make_sine(660, CLIP_DUR)
|
|
comp_audio = CompositeAudioClip([low, high])
|
|
c3 = bg.with_duration(CLIP_DUR).with_audio(comp_audio)
|
|
scenes.append(_titled(c3, "CompositeAudioClip([220Hz, 660Hz])"))
|
|
|
|
# concatenate_audioclips
|
|
a_cat = concatenate_audioclips([_make_sine(330, 1.0), _make_sine(550, 1.0)])
|
|
c4 = bg.with_duration(CLIP_DUR).with_audio(a_cat)
|
|
scenes.append(_titled(c4, "concatenate_audioclips([330Hz, 550Hz])"))
|
|
|
|
# AudioFadeIn
|
|
a_fi = _make_sine(440, CLIP_DUR).with_effects([AudioFadeIn(duration=1.5)])
|
|
c5 = bg.with_duration(CLIP_DUR).with_audio(a_fi)
|
|
scenes.append(_titled(c5, "AudioFadeIn(duration=1.5)"))
|
|
|
|
# AudioFadeOut
|
|
a_fo = _make_sine(440, CLIP_DUR).with_effects([AudioFadeOut(duration=1.5)])
|
|
c6 = bg.with_duration(CLIP_DUR).with_audio(a_fo)
|
|
scenes.append(_titled(c6, "AudioFadeOut(duration=1.5)"))
|
|
|
|
# AudioDelay
|
|
a_delay = _make_sine(440, CLIP_DUR).with_effects(
|
|
[AudioDelay(offset=0.2, n_repeats=4, decay=1)]
|
|
)
|
|
c7 = bg.with_duration(a_delay.duration).with_audio(a_delay)
|
|
scenes.append(
|
|
_titled(
|
|
c7.with_duration(CLIP_DUR), "AudioDelay(offset=0.2, n_repeats=4, decay=1)"
|
|
)
|
|
)
|
|
|
|
# AudioLoop
|
|
short_a = _make_sine(440, 0.5)
|
|
a_loop = short_a.with_effects([AudioLoop(duration=CLIP_DUR)])
|
|
c8 = bg.with_duration(CLIP_DUR).with_audio(a_loop)
|
|
scenes.append(_titled(c8, "AudioLoop(duration=2.0)"))
|
|
|
|
# AudioNormalize
|
|
quiet = _make_sine(440, CLIP_DUR) # already normalized but demonstrates the call
|
|
a_norm = quiet.with_effects([AudioNormalize()])
|
|
c9 = bg.with_duration(CLIP_DUR).with_audio(a_norm)
|
|
scenes.append(_titled(c9, "AudioNormalize()"))
|
|
|
|
# MultiplyStereoVolume
|
|
a_stereo = _make_sine(440, CLIP_DUR).with_effects(
|
|
[MultiplyStereoVolume(left=1.0, right=0.2)]
|
|
)
|
|
c10 = bg.with_duration(CLIP_DUR).with_audio(a_stereo)
|
|
scenes.append(_titled(c10, "MultiplyStereoVolume(left=1.0, right=0.2)"))
|
|
|
|
# MultiplyVolume
|
|
a_vol = _make_sine(440, CLIP_DUR).with_effects([MultiplyVolume(factor=0.3)])
|
|
c11 = bg.with_duration(CLIP_DUR).with_audio(a_vol)
|
|
scenes.append(_titled(c11, "MultiplyVolume(factor=0.3)"))
|
|
|
|
return scenes
|
|
|
|
|
|
def part5_composition() -> list[VideoClip]:
|
|
"""Demonstrate composition & concatenation."""
|
|
scenes: list[VideoClip] = [
|
|
_section_header(
|
|
"Part 5: Composition",
|
|
"CompositeVideoClip · concatenate_videoclips · clips_array",
|
|
),
|
|
]
|
|
|
|
# CompositeVideoClip with bg_color, use_bgclip
|
|
bg = _base_clip()
|
|
overlay = (
|
|
ColorClip(size=(400, 400), color=(255, 50, 50))
|
|
.with_duration(CLIP_DUR)
|
|
.with_position(("center", "center"))
|
|
.with_opacity(0.6)
|
|
)
|
|
comp1 = CompositeVideoClip([bg, overlay], size=(W, H), bg_color=(0, 0, 0))
|
|
scenes.append(_titled(comp1, "CompositeVideoClip(clips, bg_color, use_bgclip)"))
|
|
|
|
# concatenate_videoclips — method='chain'
|
|
c1 = ColorClip(size=(W, H), color=(200, 50, 50)).with_duration(0.7)
|
|
c2 = ColorClip(size=(W, H), color=(50, 200, 50)).with_duration(0.7)
|
|
c3 = ColorClip(size=(W, H), color=(50, 50, 200)).with_duration(0.6)
|
|
cat = concatenate_videoclips([c1, c2, c3], method="chain")
|
|
scenes.append(_titled(cat, "concatenate_videoclips(method='chain')"))
|
|
|
|
# concatenate_videoclips — method='compose' with padding
|
|
cat2 = concatenate_videoclips(
|
|
[
|
|
c1.resized((W // 2, H // 2)),
|
|
c2.resized((W // 2, H // 2)),
|
|
c3.resized((W, H)),
|
|
],
|
|
method="compose",
|
|
bg_color=(0, 0, 0),
|
|
padding=-0.2,
|
|
)
|
|
scenes.append(
|
|
_titled(
|
|
_resize_to_canvas(cat2),
|
|
"concatenate_videoclips(method='compose', padding=-0.2)",
|
|
)
|
|
)
|
|
|
|
# concatenate_videoclips with transition
|
|
cat3 = concatenate_videoclips(
|
|
[c1, c2, c3],
|
|
padding=-0.3,
|
|
method="compose",
|
|
)
|
|
scenes.append(
|
|
_titled(
|
|
cat3.with_duration(CLIP_DUR),
|
|
"concatenate_videoclips(padding=-0.3) # overlap",
|
|
)
|
|
)
|
|
|
|
# clips_array
|
|
a = ColorClip(size=(W // 2, H // 2), color=(200, 50, 50)).with_duration(CLIP_DUR)
|
|
b = ColorClip(size=(W // 2, H // 2), color=(50, 200, 50)).with_duration(CLIP_DUR)
|
|
c = ColorClip(size=(W // 2, H // 2), color=(50, 50, 200)).with_duration(CLIP_DUR)
|
|
d = ColorClip(size=(W // 2, H // 2), color=(200, 200, 50)).with_duration(CLIP_DUR)
|
|
grid = clips_array([[a, b], [c, d]])
|
|
scenes.append(_titled(_resize_to_canvas(grid), "clips_array([[a, b], [c, d]])"))
|
|
|
|
return scenes
|
|
|
|
|
|
def part6_drawing_tools() -> list[VideoClip]:
|
|
"""Demonstrate moviepy.video.tools.drawing functions."""
|
|
scenes: list[VideoClip] = [
|
|
_section_header(
|
|
"Part 6: Drawing Tools", "circle · color_gradient · color_split"
|
|
),
|
|
]
|
|
|
|
# circle
|
|
circ = circle(
|
|
screensize=(W, H),
|
|
center=(W // 2, H // 2),
|
|
radius=300,
|
|
color=1.0,
|
|
bg_color=0.0,
|
|
blur=30,
|
|
)
|
|
circ_rgb = (np.dstack([circ, circ, circ]) * 255).astype(np.uint8)
|
|
scenes.append(
|
|
_titled(
|
|
ImageClip(circ_rgb, duration=CLIP_DUR),
|
|
"drawing.circle(center, radius=300, blur=30)",
|
|
)
|
|
)
|
|
|
|
# color_gradient — linear
|
|
grad = color_gradient(
|
|
size=(W, H),
|
|
p1=(0, 0),
|
|
p2=(W, H),
|
|
color_1=0.0,
|
|
color_2=1.0,
|
|
shape="linear",
|
|
)
|
|
grad_rgb = (np.dstack([grad, grad, grad]) * 255).astype(np.uint8)
|
|
scenes.append(
|
|
_titled(
|
|
ImageClip(grad_rgb, duration=CLIP_DUR),
|
|
"drawing.color_gradient(shape='linear')",
|
|
)
|
|
)
|
|
|
|
# color_gradient — radial
|
|
grad_r = color_gradient(
|
|
size=(W, H),
|
|
p1=(W // 2, H // 2),
|
|
radius=500,
|
|
color_1=1.0,
|
|
color_2=0.0,
|
|
shape="radial",
|
|
)
|
|
grad_r_rgb = (np.dstack([grad_r, grad_r, grad_r]) * 255).astype(np.uint8)
|
|
scenes.append(
|
|
_titled(
|
|
ImageClip(grad_r_rgb, duration=CLIP_DUR),
|
|
"drawing.color_gradient(shape='radial', radius=500)",
|
|
)
|
|
)
|
|
|
|
# color_split
|
|
split = color_split(
|
|
size=(W, H),
|
|
x=W // 2,
|
|
color_1=0.0,
|
|
color_2=1.0,
|
|
gradient_width=100,
|
|
)
|
|
split_rgb = (np.dstack([split, split, split]) * 255).astype(np.uint8)
|
|
scenes.append(
|
|
_titled(
|
|
ImageClip(split_rgb, duration=CLIP_DUR),
|
|
"drawing.color_split(x=W/2, gradient_width=100)",
|
|
)
|
|
)
|
|
|
|
return scenes
|
|
|
|
|
|
def part7_output() -> list[VideoClip]:
|
|
"""Label-only slides for output methods + parameters."""
|
|
scenes: list[VideoClip] = [
|
|
_section_header(
|
|
"Part 7: Output Methods",
|
|
"write_videofile · write_gif · save_frame · write_images_sequence",
|
|
),
|
|
]
|
|
|
|
bg = ColorClip(size=(W, H), color=(15, 20, 35))
|
|
|
|
methods = [
|
|
(
|
|
"write_videofile()",
|
|
"filename, fps, codec, bitrate, audio, audio_fps,\n"
|
|
"preset, audio_nbytes, audio_codec, audio_bitrate,\n"
|
|
"audio_bufsize, temp_audiofile, threads,\n"
|
|
"ffmpeg_params, logger, pixel_format",
|
|
),
|
|
(
|
|
"write_gif()",
|
|
"filename, fps, loop, logger",
|
|
),
|
|
(
|
|
"save_frame()",
|
|
"filename, t, with_mask",
|
|
),
|
|
(
|
|
"write_images_sequence()",
|
|
"name_format, fps, with_mask, logger",
|
|
),
|
|
(
|
|
"write_audiofile()",
|
|
"filename, fps, nbytes, buffersize,\ncodec, bitrate, ffmpeg_params, logger",
|
|
),
|
|
]
|
|
|
|
for title, params in methods:
|
|
t1 = (
|
|
TextClip(
|
|
text=title, font_size=56, color="cyan", font=FONT_B, margin=(0, 20)
|
|
)
|
|
.with_duration(2.5)
|
|
.with_position(("center", 300))
|
|
)
|
|
t2 = (
|
|
TextClip(
|
|
text=f"Parameters:\n{params}",
|
|
font_size=32,
|
|
color="#dddddd",
|
|
font=FONT_R,
|
|
method="caption",
|
|
size=(W - 300, None),
|
|
text_align="center",
|
|
interline=8,
|
|
margin=(0, 15),
|
|
)
|
|
.with_duration(2.5)
|
|
.with_position(("center", 500))
|
|
)
|
|
scenes.append(CompositeVideoClip([bg.with_duration(2.5), t1, t2], size=(W, H)))
|
|
|
|
return scenes
|