hyperframes 0.6.97 → 0.6.98
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/beat-analyzer.global.js +326 -0
- package/dist/cli.js +2479 -1961
- package/dist/commands/layout-audit.browser.js +86 -0
- package/dist/hyperframe-runtime.js +22 -22
- package/dist/hyperframe.manifest.json +1 -1
- package/dist/hyperframe.runtime.iife.js +22 -22
- package/dist/skills/hyperframes-cli/SKILL.md +67 -103
- package/dist/skills/hyperframes-cli/references/doctor-browser.md +45 -0
- package/dist/skills/hyperframes-cli/references/init-and-scaffold.md +51 -0
- package/dist/skills/hyperframes-cli/references/lambda.md +132 -0
- package/dist/skills/hyperframes-cli/references/lint-validate-inspect.md +93 -0
- package/dist/skills/hyperframes-cli/references/preview-render.md +107 -0
- package/dist/skills/hyperframes-cli/references/upgrade-info-misc.md +75 -0
- package/dist/studio/assets/hyperframes-player-DgsMQSvV.js +418 -0
- package/dist/studio/assets/index-B62bDCQv.css +1 -0
- package/dist/studio/assets/index-Ce3pBm_I.js +252 -0
- package/dist/studio/assets/{index-HveJ0MuV.js → index-D-ET9M0b.js} +1 -1
- package/dist/studio/assets/index-D-bS9Dxx.js +1 -0
- package/dist/studio/index.html +2 -2
- package/dist/templates/_shared/AGENTS.md +46 -21
- package/dist/templates/_shared/CLAUDE.md +16 -14
- package/package.json +3 -2
- package/dist/pngDecodeBlitWorker.js +0 -239
- package/dist/skills/gsap/SKILL.md +0 -240
- package/dist/skills/gsap/references/effects.md +0 -297
- package/dist/skills/gsap/scripts/extract-audio-data.py +0 -188
- package/dist/skills/hyperframes/SKILL.md +0 -491
- package/dist/skills/hyperframes/data-in-motion.md +0 -19
- package/dist/skills/hyperframes/house-style.md +0 -73
- package/dist/skills/hyperframes/palettes/bold-energetic.md +0 -14
- package/dist/skills/hyperframes/palettes/clean-corporate.md +0 -14
- package/dist/skills/hyperframes/palettes/dark-premium.md +0 -14
- package/dist/skills/hyperframes/palettes/jewel-rich.md +0 -14
- package/dist/skills/hyperframes/palettes/monochrome.md +0 -14
- package/dist/skills/hyperframes/palettes/nature-earth.md +0 -14
- package/dist/skills/hyperframes/palettes/neon-electric.md +0 -14
- package/dist/skills/hyperframes/palettes/pastel-soft.md +0 -14
- package/dist/skills/hyperframes/palettes/warm-editorial.md +0 -14
- package/dist/skills/hyperframes/patterns.md +0 -191
- package/dist/skills/hyperframes/references/audio-reactive.md +0 -76
- package/dist/skills/hyperframes/references/beat-direction.md +0 -171
- package/dist/skills/hyperframes/references/captions.md +0 -163
- package/dist/skills/hyperframes/references/css-patterns.md +0 -373
- package/dist/skills/hyperframes/references/design-picker.md +0 -117
- package/dist/skills/hyperframes/references/dynamic-techniques.md +0 -102
- package/dist/skills/hyperframes/references/html-in-canvas-patterns.md +0 -507
- package/dist/skills/hyperframes/references/motion-principles.md +0 -150
- package/dist/skills/hyperframes/references/narration.md +0 -92
- package/dist/skills/hyperframes/references/prompt-expansion.md +0 -68
- package/dist/skills/hyperframes/references/techniques.md +0 -525
- package/dist/skills/hyperframes/references/text-effects.md +0 -64
- package/dist/skills/hyperframes/references/transcript-guide.md +0 -107
- package/dist/skills/hyperframes/references/transitions/catalog.md +0 -117
- package/dist/skills/hyperframes/references/transitions/css-3d.md +0 -12
- package/dist/skills/hyperframes/references/transitions/css-blur.md +0 -51
- package/dist/skills/hyperframes/references/transitions/css-cover.md +0 -43
- package/dist/skills/hyperframes/references/transitions/css-destruction.md +0 -95
- package/dist/skills/hyperframes/references/transitions/css-dissolve.md +0 -66
- package/dist/skills/hyperframes/references/transitions/css-distortion.md +0 -45
- package/dist/skills/hyperframes/references/transitions/css-grid.md +0 -10
- package/dist/skills/hyperframes/references/transitions/css-light.md +0 -49
- package/dist/skills/hyperframes/references/transitions/css-mechanical.md +0 -30
- package/dist/skills/hyperframes/references/transitions/css-other.md +0 -25
- package/dist/skills/hyperframes/references/transitions/css-push.md +0 -41
- package/dist/skills/hyperframes/references/transitions/css-radial.md +0 -37
- package/dist/skills/hyperframes/references/transitions/css-scale.md +0 -24
- package/dist/skills/hyperframes/references/transitions.md +0 -138
- package/dist/skills/hyperframes/references/typography.md +0 -175
- package/dist/skills/hyperframes/references/video-composition.md +0 -62
- package/dist/skills/hyperframes/scripts/animation-map.mjs +0 -601
- package/dist/skills/hyperframes/scripts/contrast-report.mjs +0 -348
- package/dist/skills/hyperframes/scripts/package-loader.mjs +0 -269
- package/dist/skills/hyperframes/templates/design-picker.html +0 -1432
- package/dist/skills/hyperframes/visual-styles.md +0 -443
- package/dist/studio/assets/hyperframes-player-Daj5djxa.js +0 -418
- package/dist/studio/assets/index-B0twsRu0.css +0 -1
- package/dist/studio/assets/index-Cfye9xzo.js +0 -251
|
@@ -1,188 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""
|
|
3
|
-
Extract per-frame audio visualization data from an audio or video file.
|
|
4
|
-
|
|
5
|
-
Outputs JSON with RMS amplitude and frequency band data at the target FPS,
|
|
6
|
-
ready to embed in a HyperFrames composition.
|
|
7
|
-
|
|
8
|
-
Usage:
|
|
9
|
-
python extract-audio-data.py input.mp3 -o audio-data.json
|
|
10
|
-
python extract-audio-data.py input.mp4 --fps 30 --bands 16 -o audio-data.json
|
|
11
|
-
|
|
12
|
-
Requirements:
|
|
13
|
-
- Python 3.9+
|
|
14
|
-
- ffmpeg (for decoding audio)
|
|
15
|
-
- numpy (pip install numpy)
|
|
16
|
-
"""
|
|
17
|
-
|
|
18
|
-
import argparse
|
|
19
|
-
import json
|
|
20
|
-
import subprocess
|
|
21
|
-
import sys
|
|
22
|
-
|
|
23
|
-
import numpy as np
|
|
24
|
-
|
|
25
|
-
# ---------------------------------------------------------------------------
|
|
26
|
-
# FFT parameters
|
|
27
|
-
#
|
|
28
|
-
# A 4096-sample window gives ~10.8 Hz per bin at 44100Hz — enough to resolve
|
|
29
|
-
# low-frequency bands cleanly. The per-frame audio slice (44100/30 = 1470
|
|
30
|
-
# samples at 30fps) is too small and causes low bands to map to the same bins.
|
|
31
|
-
#
|
|
32
|
-
# Frequency range 30Hz–16kHz covers the useful range for music. Below 30Hz is
|
|
33
|
-
# sub-bass most speakers can't reproduce; above 16kHz is noise/harmonics that
|
|
34
|
-
# don't contribute to perceived rhythm or melody.
|
|
35
|
-
# ---------------------------------------------------------------------------
|
|
36
|
-
|
|
37
|
-
SAMPLE_RATE = 44100
|
|
38
|
-
FFT_SIZE = 4096
|
|
39
|
-
MIN_FREQ = 30.0
|
|
40
|
-
MAX_FREQ = 16000.0
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
def decode_audio(path: str) -> np.ndarray:
|
|
44
|
-
"""Decode audio to mono float32 samples via ffmpeg."""
|
|
45
|
-
cmd = [
|
|
46
|
-
"ffmpeg", "-i", path,
|
|
47
|
-
"-vn", "-ac", "1", "-ar", str(SAMPLE_RATE),
|
|
48
|
-
"-f", "s16le", "-acodec", "pcm_s16le",
|
|
49
|
-
"-loglevel", "error",
|
|
50
|
-
"pipe:1",
|
|
51
|
-
]
|
|
52
|
-
result = subprocess.run(cmd, capture_output=True)
|
|
53
|
-
if result.returncode != 0:
|
|
54
|
-
print(f"ffmpeg error: {result.stderr.decode()}", file=sys.stderr)
|
|
55
|
-
sys.exit(1)
|
|
56
|
-
return np.frombuffer(result.stdout, dtype=np.int16).astype(np.float32) / 32768.0
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
def compute_band_edges(n_bands: int) -> np.ndarray:
|
|
60
|
-
"""Logarithmically-spaced frequency band edges from MIN_FREQ to MAX_FREQ."""
|
|
61
|
-
return np.array([
|
|
62
|
-
MIN_FREQ * (MAX_FREQ / MIN_FREQ) ** (i / n_bands)
|
|
63
|
-
for i in range(n_bands + 1)
|
|
64
|
-
])
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
def compute_fft_bands(
|
|
68
|
-
windowed: np.ndarray, freq_per_bin: float, n_bins: int,
|
|
69
|
-
band_edges: np.ndarray, n_bands: int,
|
|
70
|
-
) -> np.ndarray:
|
|
71
|
-
"""Compute peak magnitude in logarithmically-spaced frequency bands."""
|
|
72
|
-
magnitudes = np.abs(np.fft.rfft(windowed))
|
|
73
|
-
|
|
74
|
-
bands = np.zeros(n_bands)
|
|
75
|
-
for b in range(n_bands):
|
|
76
|
-
low_bin = max(0, int(band_edges[b] / freq_per_bin))
|
|
77
|
-
high_bin = min(n_bins, int(band_edges[b + 1] / freq_per_bin))
|
|
78
|
-
if high_bin <= low_bin:
|
|
79
|
-
high_bin = low_bin + 1
|
|
80
|
-
# Clamp to valid range to avoid empty slices
|
|
81
|
-
low_bin = min(low_bin, n_bins - 1)
|
|
82
|
-
high_bin = min(high_bin, n_bins)
|
|
83
|
-
bands[b] = np.max(magnitudes[low_bin:high_bin])
|
|
84
|
-
|
|
85
|
-
return bands
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
def extract(path: str, fps: int, n_bands: int) -> dict:
|
|
89
|
-
"""Extract per-frame audio data."""
|
|
90
|
-
print(f"Decoding audio from {path}...", file=sys.stderr)
|
|
91
|
-
samples = decode_audio(path)
|
|
92
|
-
duration = len(samples) / SAMPLE_RATE
|
|
93
|
-
frame_step = SAMPLE_RATE // fps
|
|
94
|
-
total_frames = int(duration * fps)
|
|
95
|
-
|
|
96
|
-
print(f"Duration: {duration:.1f}s, {total_frames} frames at {fps}fps", file=sys.stderr)
|
|
97
|
-
print(f"FFT window: {FFT_SIZE} samples ({SAMPLE_RATE / FFT_SIZE:.1f} Hz/bin)", file=sys.stderr)
|
|
98
|
-
print(f"Frequency range: {MIN_FREQ:.0f}-{MAX_FREQ:.0f} Hz, {n_bands} bands", file=sys.stderr)
|
|
99
|
-
|
|
100
|
-
# Precompute constants
|
|
101
|
-
hann = np.hanning(FFT_SIZE)
|
|
102
|
-
band_edges = compute_band_edges(n_bands)
|
|
103
|
-
freq_per_bin = SAMPLE_RATE / FFT_SIZE
|
|
104
|
-
n_bins = FFT_SIZE // 2 + 1
|
|
105
|
-
half_fft = FFT_SIZE // 2
|
|
106
|
-
|
|
107
|
-
# Pass 1: extract raw values
|
|
108
|
-
rms_values = np.zeros(total_frames)
|
|
109
|
-
band_values = np.zeros((total_frames, n_bands))
|
|
110
|
-
|
|
111
|
-
for f in range(total_frames):
|
|
112
|
-
# RMS from the frame's audio slice
|
|
113
|
-
rms_start = f * frame_step
|
|
114
|
-
rms_end = rms_start + frame_step
|
|
115
|
-
frame_slice = samples[rms_start:min(rms_end, len(samples))]
|
|
116
|
-
if len(frame_slice) > 0:
|
|
117
|
-
rms_values[f] = np.sqrt(np.mean(frame_slice ** 2))
|
|
118
|
-
|
|
119
|
-
# FFT from a centered 4096-sample window
|
|
120
|
-
center = rms_start + frame_step // 2
|
|
121
|
-
win_start = center - half_fft
|
|
122
|
-
win_end = center + half_fft
|
|
123
|
-
|
|
124
|
-
if win_start >= 0 and win_end <= len(samples):
|
|
125
|
-
window = samples[win_start:win_end] * hann
|
|
126
|
-
else:
|
|
127
|
-
# Zero-pad at edges
|
|
128
|
-
padded = np.zeros(FFT_SIZE)
|
|
129
|
-
src_start = max(0, win_start)
|
|
130
|
-
src_end = min(len(samples), win_end)
|
|
131
|
-
dst_start = src_start - win_start
|
|
132
|
-
dst_end = dst_start + (src_end - src_start)
|
|
133
|
-
padded[dst_start:dst_end] = samples[src_start:src_end]
|
|
134
|
-
window = padded * hann
|
|
135
|
-
|
|
136
|
-
band_values[f] = compute_fft_bands(window, freq_per_bin, n_bins, band_edges, n_bands)
|
|
137
|
-
|
|
138
|
-
# Pass 2: normalize
|
|
139
|
-
peak_rms = rms_values.max() if total_frames > 0 else 1.0
|
|
140
|
-
if peak_rms > 0:
|
|
141
|
-
rms_values /= peak_rms
|
|
142
|
-
|
|
143
|
-
# Per-band normalization so treble is visible alongside louder bass
|
|
144
|
-
band_peaks = band_values.max(axis=0)
|
|
145
|
-
band_peaks[band_peaks == 0] = 1.0
|
|
146
|
-
band_values /= band_peaks
|
|
147
|
-
|
|
148
|
-
# Build output
|
|
149
|
-
frames = []
|
|
150
|
-
for f in range(total_frames):
|
|
151
|
-
frames.append({
|
|
152
|
-
"time": round(f / fps, 4),
|
|
153
|
-
"rms": round(float(rms_values[f]), 4),
|
|
154
|
-
"bands": [round(float(b), 4) for b in band_values[f]],
|
|
155
|
-
})
|
|
156
|
-
|
|
157
|
-
return {
|
|
158
|
-
"duration": round(duration, 4),
|
|
159
|
-
"fps": fps,
|
|
160
|
-
"bands": n_bands,
|
|
161
|
-
"totalFrames": total_frames,
|
|
162
|
-
"frames": frames,
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
def main():
|
|
167
|
-
parser = argparse.ArgumentParser(description="Extract per-frame audio visualization data")
|
|
168
|
-
parser.add_argument("input", help="Audio or video file")
|
|
169
|
-
parser.add_argument("-o", "--output", default="audio-data.json", help="Output JSON path")
|
|
170
|
-
parser.add_argument("--fps", type=int, default=30, help="Frames per second (default: 30)")
|
|
171
|
-
parser.add_argument("--bands", type=int, default=16, help="Number of frequency bands (default: 16)")
|
|
172
|
-
args = parser.parse_args()
|
|
173
|
-
|
|
174
|
-
if args.fps < 1:
|
|
175
|
-
parser.error("--fps must be at least 1")
|
|
176
|
-
if args.bands < 1:
|
|
177
|
-
parser.error("--bands must be at least 1")
|
|
178
|
-
|
|
179
|
-
data = extract(args.input, args.fps, args.bands)
|
|
180
|
-
|
|
181
|
-
with open(args.output, "w") as f:
|
|
182
|
-
json.dump(data, f)
|
|
183
|
-
|
|
184
|
-
print(f"Wrote {args.output} ({data['totalFrames']} frames, {data['bands']} bands)", file=sys.stderr)
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
if __name__ == "__main__":
|
|
188
|
-
main()
|