mirror of
https://github.com/kuhyx/testsAndMisc.git
synced 2026-07-04 15:03:01 +02:00
Fix ruff violations in ~15 source files and ~60+ test files to minimize per-file-ignores in pyproject.toml. Remaining ignores are justified with comments explaining why each suppression is necessary. Source fixes: FBT003 (keyword args), S310 (URL validation), SLF001 (private access), T201 (print→logging), C901 (complexity), E501 (line length), E402 (import order). Test fixes: SIM117 (combined with), FBT (boolean args), PERF203 (try in loop), S310/S607 (URLs/executables), E402/E501 (imports/lines), S108 (tmp paths), PLR0913 (too many args), ARG (unused args), ANN (type annotations), RUF059 (unused unpacked vars), PT019 (fixture naming). Remaining per-file-ignores (with justifications): - Tests: ARG, D, PLC0415, PLR2004, S101, SLF001 - music_gen sources: PLC0415 (heavy ML lazy imports) - moviepy_showcase: PLC0415 (circular dependency) - generate_images: PLR0913 (matplotlib helpers need many params) - praca_magisterska_video: E501, E402 (long paths, mpl.use)
311 lines
10 KiB
Python
311 lines
10 KiB
Python
"""MoviePy 2.x — Comprehensive Showcase of ALL Features.
|
|
|
|
Generates a video demonstrating every MoviePy class, method, effect,
|
|
and tool. Organised into sections:
|
|
|
|
Part 1: Clip Types (VideoClip, ColorClip, TextClip, ImageClip,
|
|
BitmapClip, DataVideoClip, ImageSequenceClip)
|
|
Part 2: Clip Methods (subclipped, cropped, resized, rotated, with_position,
|
|
with_opacity, with_mask, image_transform, transform,
|
|
time_transform, with_speed_scaled, with_section_cut_out,
|
|
to_ImageClip, to_mask, to_RGB, with_background_color,
|
|
with_effects_on_subclip, with_layer_index)
|
|
Part 3: Video Effects (all 34)
|
|
Part 4: Audio (AudioClip, AudioArrayClip, CompositeAudioClip,
|
|
all 7 audio effects)
|
|
Part 5: Composition (CompositeVideoClip, concatenate_videoclips, clips_array)
|
|
Part 6: Drawing Tools (circle, color_gradient, color_split)
|
|
Part 7: Output (write_videofile params, write_gif, save_frame,
|
|
write_images_sequence)
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import logging
|
|
import os
|
|
from pathlib import Path
|
|
import shutil
|
|
import tempfile
|
|
|
|
from moviepy import (
|
|
ColorClip,
|
|
CompositeVideoClip,
|
|
TextClip,
|
|
VideoClip,
|
|
VideoFileClip,
|
|
concatenate_videoclips,
|
|
)
|
|
from moviepy.video.fx import FadeIn, FadeOut
|
|
import numpy as np
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
os.environ["FFMPEG_BINARY"] = "/usr/bin/ffmpeg"
|
|
|
|
# ── Constants ─────────────────────────────────────────────────────
|
|
W, H = 1920, 1080
|
|
FPS = 30
|
|
CLIP_DUR = 2.0 # duration of each demo clip
|
|
HEADER_DUR = 1.5 # duration of section headers
|
|
OUTPUT = "moviepy_showcase_full.mp4"
|
|
FONT_B = "/usr/share/fonts/noto/NotoSans-Bold.ttf"
|
|
FONT_R = "/usr/share/fonts/noto/NotoSans-Regular.ttf"
|
|
|
|
# ── Pre-computed gradient LUTs ────────────────────────────────────
|
|
_G_CH = (
|
|
np.linspace(0, 255, H, dtype=np.uint8)[:, None]
|
|
* np.ones(W, dtype=np.uint8)[None, :]
|
|
)
|
|
_B_CH = (
|
|
np.ones(H, dtype=np.uint8)[:, None]
|
|
* np.linspace(0, 255, W, dtype=np.uint8)[None, :]
|
|
)
|
|
|
|
|
|
def _gradient(t: float) -> np.ndarray:
|
|
f = np.empty((H, W, 3), dtype=np.uint8)
|
|
f[:, :, 0] = int(128 + 127 * np.sin(t * 2))
|
|
f[:, :, 1] = _G_CH
|
|
f[:, :, 2] = _B_CH
|
|
return f
|
|
|
|
|
|
def _checkerboard(t: float) -> np.ndarray:
|
|
sq = 60
|
|
off = int(t * 40) % sq
|
|
xs = np.arange(W, dtype=np.int32)[None, :]
|
|
ys = np.arange(H, dtype=np.int32)[:, None]
|
|
v = (((xs + off) // sq + (ys + off) // sq) % 2 * 255).astype(np.uint8)
|
|
return np.dstack([v, v, v])
|
|
|
|
|
|
# ── Helpers ───────────────────────────────────────────────────────
|
|
def _base_clip(dur: float = CLIP_DUR) -> VideoClip:
|
|
"""Animated gradient as a reusable base clip."""
|
|
return VideoClip(_gradient, duration=dur).with_fps(FPS)
|
|
|
|
|
|
def _label(
|
|
text: str,
|
|
size: int = 36,
|
|
color: str = "white",
|
|
pos: tuple[str, int] | tuple[str, str] = ("center", 40),
|
|
dur: float = CLIP_DUR,
|
|
) -> TextClip:
|
|
"""Small label overlay (transparent bg)."""
|
|
return (
|
|
TextClip(
|
|
text=text,
|
|
font_size=size,
|
|
color=color,
|
|
font=FONT_R,
|
|
margin=(0, 15),
|
|
)
|
|
.with_duration(dur)
|
|
.with_position(pos)
|
|
)
|
|
|
|
|
|
def _titled(clip: VideoClip, text: str) -> CompositeVideoClip:
|
|
"""Overlay a label onto a clip."""
|
|
lbl = _label(text, dur=clip.duration)
|
|
return CompositeVideoClip(
|
|
[clip.with_duration(clip.duration), lbl],
|
|
size=(W, H),
|
|
)
|
|
|
|
|
|
def _section_header(title: str, subtitle: str = "") -> CompositeVideoClip:
|
|
"""Dark background with centred title text."""
|
|
bg = ColorClip(size=(W, H), color=(15, 15, 40)).with_duration(HEADER_DUR)
|
|
t = (
|
|
TextClip(
|
|
text=title,
|
|
font_size=72,
|
|
color="white",
|
|
font=FONT_B,
|
|
margin=(0, 30),
|
|
)
|
|
.with_duration(HEADER_DUR)
|
|
.with_position(("center", 380))
|
|
)
|
|
parts: list[VideoClip] = [bg, t]
|
|
if subtitle:
|
|
s = (
|
|
TextClip(
|
|
text=subtitle,
|
|
font_size=32,
|
|
color="#aaaaaa",
|
|
font=FONT_R,
|
|
margin=(0, 15),
|
|
)
|
|
.with_duration(HEADER_DUR)
|
|
.with_position(("center", 520))
|
|
)
|
|
parts.append(s)
|
|
return CompositeVideoClip(parts, size=(W, H))
|
|
|
|
|
|
def _resize_to_canvas(clip: VideoClip) -> VideoClip:
|
|
"""Resize a clip to fit (W, H) and centre on black background."""
|
|
cw, ch = clip.size
|
|
scale = min(W / cw, H / ch)
|
|
return clip.resized(
|
|
width=int(cw * scale), height=int(ch * scale)
|
|
).with_background_color(size=(W, H), color=(0, 0, 0))
|
|
|
|
|
|
# ══════════════════════════════════════════════════════════════════
|
|
# ASSEMBLY — memory-safe: render each part to a temp file, then
|
|
# concatenate via VideoFileClip so only one part is in RAM at a time.
|
|
# ══════════════════════════════════════════════════════════════════
|
|
def _render_part(
|
|
scenes: list[VideoClip],
|
|
path: str,
|
|
label: str,
|
|
) -> None:
|
|
"""Concatenate *scenes*, write to *path*, then close all clips."""
|
|
logger.info(" Rendering %s (%d scenes) → %s", label, len(scenes), Path(path).name)
|
|
part = concatenate_videoclips(scenes, method="compose", bg_color=(0, 0, 0))
|
|
part.write_videofile(
|
|
path,
|
|
fps=FPS,
|
|
codec="libx264",
|
|
preset="ultrafast",
|
|
audio=False,
|
|
logger=None,
|
|
)
|
|
# Free memory
|
|
part.close()
|
|
for s in scenes:
|
|
s.close()
|
|
|
|
|
|
def main() -> None:
|
|
"""Assemble all parts into the final showcase video."""
|
|
logging.basicConfig(level=logging.INFO)
|
|
logger.info("Building MoviePy comprehensive showcase…")
|
|
|
|
tmpdir = tempfile.mkdtemp(prefix="moviepy_showcase_")
|
|
try:
|
|
_build(tmpdir)
|
|
finally:
|
|
shutil.rmtree(tmpdir, ignore_errors=True)
|
|
|
|
|
|
def _build(tmpdir: str) -> None:
|
|
# ── Lazy imports to avoid circular dependency ────────────────
|
|
# These submodules import constants from this module, so they
|
|
# cannot be imported at the top level.
|
|
from moviepy.audio.fx import MultiplyVolume
|
|
|
|
from python_pkg.moviepy_showcase._moviepy_audio_output import (
|
|
_make_sine,
|
|
part4_audio,
|
|
part5_composition,
|
|
part6_drawing_tools,
|
|
part7_output,
|
|
)
|
|
from python_pkg.moviepy_showcase._moviepy_clip_types import (
|
|
part1_clip_types,
|
|
part2_clip_methods,
|
|
)
|
|
from python_pkg.moviepy_showcase._moviepy_video_effects import (
|
|
part3_video_effects,
|
|
)
|
|
|
|
# ── Render each part to its own temp file ─────────────────────
|
|
# Title card
|
|
title_bg = ColorClip(size=(W, H), color=(10, 10, 30)).with_duration(3.0)
|
|
title_txt = (
|
|
TextClip(
|
|
text="MoviePy 2.x\nComplete Feature Showcase",
|
|
font_size=80,
|
|
color="white",
|
|
font=FONT_B,
|
|
method="caption",
|
|
size=(W - 200, None),
|
|
text_align="center",
|
|
margin=(0, 40),
|
|
)
|
|
.with_duration(3.0)
|
|
.with_position("center")
|
|
)
|
|
title_card = CompositeVideoClip([title_bg, title_txt], size=(W, H)).with_effects(
|
|
[FadeIn(1.0)]
|
|
)
|
|
|
|
# Outro
|
|
outro_bg = ColorClip(size=(W, H), color=(10, 10, 30)).with_duration(3.0)
|
|
outro_txt = (
|
|
TextClip(
|
|
text="That's all of MoviePy 2.x!\n34 video effects · 7 audio effects\n"
|
|
"11 clip types · drawing tools · composition",
|
|
font_size=52,
|
|
color="white",
|
|
font=FONT_B,
|
|
method="caption",
|
|
size=(W - 200, None),
|
|
text_align="center",
|
|
margin=(0, 30),
|
|
)
|
|
.with_duration(3.0)
|
|
.with_position("center")
|
|
)
|
|
outro = CompositeVideoClip([outro_bg, outro_txt], size=(W, H)).with_effects(
|
|
[FadeOut(1.5)]
|
|
)
|
|
|
|
part_builders = [
|
|
("title", lambda: [title_card]),
|
|
("Part 1: Clip Types", part1_clip_types),
|
|
("Part 2: Clip Methods", part2_clip_methods),
|
|
("Part 3: Video Effects", part3_video_effects),
|
|
("Part 4: Audio", part4_audio),
|
|
("Part 5: Composition", part5_composition),
|
|
("Part 6: Drawing Tools", part6_drawing_tools),
|
|
("Part 7: Output Methods", part7_output),
|
|
("outro", lambda: [outro]),
|
|
]
|
|
|
|
part_files: list[str] = []
|
|
for i, (label, builder) in enumerate(part_builders):
|
|
path = str(Path(tmpdir) / f"part_{i:02d}.mp4")
|
|
scenes = builder()
|
|
_render_part(scenes, path, label)
|
|
part_files.append(path)
|
|
|
|
# ── Load temp files as lightweight VideoFileClips & concat ─────
|
|
logger.info("Concatenating all parts…")
|
|
file_clips = [VideoFileClip(p) for p in part_files]
|
|
final = concatenate_videoclips(file_clips, method="chain")
|
|
|
|
# Background audio
|
|
audio = _make_sine(330, final.duration).with_effects([MultiplyVolume(factor=0.5)])
|
|
final = final.with_audio(audio)
|
|
|
|
logger.info("Total duration: %.1fs", final.duration)
|
|
logger.info("Writing %s (NVENC GPU)…", OUTPUT)
|
|
|
|
final.write_videofile(
|
|
OUTPUT,
|
|
fps=FPS,
|
|
codec="h264_nvenc",
|
|
audio_codec="aac",
|
|
threads=os.cpu_count(),
|
|
ffmpeg_params=["-preset", "p4", "-rc", "constqp", "-qp", "18", "-b:v", "0"],
|
|
logger="bar",
|
|
)
|
|
|
|
# Clean up
|
|
final.close()
|
|
for c in file_clips:
|
|
c.close()
|
|
|
|
size_mb = Path(OUTPUT).stat().st_size / (1024 * 1024)
|
|
logger.info("✔ Saved %s (%.1f MB)", OUTPUT, size_mb)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|