spectrumalyzer 0.1.0__tar.gz
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.
- spectrumalyzer-0.1.0/PKG-INFO +70 -0
- spectrumalyzer-0.1.0/README.md +42 -0
- spectrumalyzer-0.1.0/setup.cfg +4 -0
- spectrumalyzer-0.1.0/setup.py +33 -0
- spectrumalyzer-0.1.0/spectrumalyzer.egg-info/PKG-INFO +70 -0
- spectrumalyzer-0.1.0/spectrumalyzer.egg-info/SOURCES.txt +7 -0
- spectrumalyzer-0.1.0/spectrumalyzer.egg-info/dependency_links.txt +1 -0
- spectrumalyzer-0.1.0/spectrumalyzer.egg-info/top_level.txt +1 -0
- spectrumalyzer-0.1.0/spectrumalyzer.py +194 -0
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: spectrumalyzer
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Audio beat analysis tool for motion graphics
|
|
5
|
+
Home-page: https://github.com/Dream-Pixels-Forge/spectrumalyzer
|
|
6
|
+
Author: Dream-Pixels-Forge
|
|
7
|
+
Author-email: dev@dream-pixels-forge.example.com
|
|
8
|
+
Classifier: Development Status :: 3 - Alpha
|
|
9
|
+
Classifier: Intended Audience :: Developers
|
|
10
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
+
Classifier: Operating System :: OS Independent
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.7
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Requires-Python: >=3.7
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
Dynamic: author
|
|
21
|
+
Dynamic: author-email
|
|
22
|
+
Dynamic: classifier
|
|
23
|
+
Dynamic: description
|
|
24
|
+
Dynamic: description-content-type
|
|
25
|
+
Dynamic: home-page
|
|
26
|
+
Dynamic: requires-python
|
|
27
|
+
Dynamic: summary
|
|
28
|
+
|
|
29
|
+
# Spectrumalyzer
|
|
30
|
+
|
|
31
|
+
Audio beat analysis tool for motion graphics - provides BPM detection, beat timestamps, and frequency spectrum data compatible with Remotion and HyperFrames workflows.
|
|
32
|
+
|
|
33
|
+
## Features
|
|
34
|
+
|
|
35
|
+
- BPM (Beats Per Minute) detection
|
|
36
|
+
- Beat timestamp extraction
|
|
37
|
+
- Frequency spectrum analysis
|
|
38
|
+
- Compatible with Remotion and HyperFrames workflows
|
|
39
|
+
|
|
40
|
+
## Installation
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
pip install spectrumalyzer
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Usage
|
|
47
|
+
|
|
48
|
+
```python
|
|
49
|
+
from spectrumalyzer import analyze_audio
|
|
50
|
+
|
|
51
|
+
# Analyze an audio file
|
|
52
|
+
results = analyze_audio("path/to/audio/file.mp3")
|
|
53
|
+
|
|
54
|
+
# Get BPM
|
|
55
|
+
bpm = results["bpm"]
|
|
56
|
+
|
|
57
|
+
# Get beat timestamps
|
|
58
|
+
beats = results["beats"]
|
|
59
|
+
|
|
60
|
+
# Get frequency spectrum
|
|
61
|
+
spectrum = results["spectrum"]
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## API
|
|
65
|
+
|
|
66
|
+
See the [SKILL.md](skills/spectrumalyzer/SKILL.md) file for detailed API documentation.
|
|
67
|
+
|
|
68
|
+
## License
|
|
69
|
+
|
|
70
|
+
MIT
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# Spectrumalyzer
|
|
2
|
+
|
|
3
|
+
Audio beat analysis tool for motion graphics - provides BPM detection, beat timestamps, and frequency spectrum data compatible with Remotion and HyperFrames workflows.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- BPM (Beats Per Minute) detection
|
|
8
|
+
- Beat timestamp extraction
|
|
9
|
+
- Frequency spectrum analysis
|
|
10
|
+
- Compatible with Remotion and HyperFrames workflows
|
|
11
|
+
|
|
12
|
+
## Installation
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
pip install spectrumalyzer
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Usage
|
|
19
|
+
|
|
20
|
+
```python
|
|
21
|
+
from spectrumalyzer import analyze_audio
|
|
22
|
+
|
|
23
|
+
# Analyze an audio file
|
|
24
|
+
results = analyze_audio("path/to/audio/file.mp3")
|
|
25
|
+
|
|
26
|
+
# Get BPM
|
|
27
|
+
bpm = results["bpm"]
|
|
28
|
+
|
|
29
|
+
# Get beat timestamps
|
|
30
|
+
beats = results["beats"]
|
|
31
|
+
|
|
32
|
+
# Get frequency spectrum
|
|
33
|
+
spectrum = results["spectrum"]
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## API
|
|
37
|
+
|
|
38
|
+
See the [SKILL.md](skills/spectrumalyzer/SKILL.md) file for detailed API documentation.
|
|
39
|
+
|
|
40
|
+
## License
|
|
41
|
+
|
|
42
|
+
MIT
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
from setuptools import setup
|
|
2
|
+
|
|
3
|
+
with open("README.md", "r", encoding="utf-8") as fh:
|
|
4
|
+
long_description = fh.read()
|
|
5
|
+
|
|
6
|
+
setup(
|
|
7
|
+
name="spectrumalyzer",
|
|
8
|
+
version="0.1.0",
|
|
9
|
+
author="Dream-Pixels-Forge",
|
|
10
|
+
author_email="dev@dream-pixels-forge.example.com",
|
|
11
|
+
description="Audio beat analysis tool for motion graphics",
|
|
12
|
+
long_description=long_description,
|
|
13
|
+
long_description_content_type="text/markdown",
|
|
14
|
+
url="https://github.com/Dream-Pixels-Forge/spectrumalyzer",
|
|
15
|
+
py_modules=["spectrumalyzer"],
|
|
16
|
+
classifiers=[
|
|
17
|
+
"Development Status :: 3 - Alpha",
|
|
18
|
+
"Intended Audience :: Developers",
|
|
19
|
+
"License :: OSI Approved :: MIT License",
|
|
20
|
+
"Operating System :: OS Independent",
|
|
21
|
+
"Programming Language :: Python :: 3",
|
|
22
|
+
"Programming Language :: Python :: 3.7",
|
|
23
|
+
"Programming Language :: Python :: 3.8",
|
|
24
|
+
"Programming Language :: Python :: 3.9",
|
|
25
|
+
"Programming Language :: Python :: 3.10",
|
|
26
|
+
"Programming Language :: Python :: 3.11",
|
|
27
|
+
],
|
|
28
|
+
python_requires=">=3.7",
|
|
29
|
+
install_requires=[
|
|
30
|
+
# Add your dependencies here
|
|
31
|
+
# For example: "numpy>=1.19.0", "scipy>=1.5.0"
|
|
32
|
+
],
|
|
33
|
+
)
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: spectrumalyzer
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Audio beat analysis tool for motion graphics
|
|
5
|
+
Home-page: https://github.com/Dream-Pixels-Forge/spectrumalyzer
|
|
6
|
+
Author: Dream-Pixels-Forge
|
|
7
|
+
Author-email: dev@dream-pixels-forge.example.com
|
|
8
|
+
Classifier: Development Status :: 3 - Alpha
|
|
9
|
+
Classifier: Intended Audience :: Developers
|
|
10
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
+
Classifier: Operating System :: OS Independent
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.7
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Requires-Python: >=3.7
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
Dynamic: author
|
|
21
|
+
Dynamic: author-email
|
|
22
|
+
Dynamic: classifier
|
|
23
|
+
Dynamic: description
|
|
24
|
+
Dynamic: description-content-type
|
|
25
|
+
Dynamic: home-page
|
|
26
|
+
Dynamic: requires-python
|
|
27
|
+
Dynamic: summary
|
|
28
|
+
|
|
29
|
+
# Spectrumalyzer
|
|
30
|
+
|
|
31
|
+
Audio beat analysis tool for motion graphics - provides BPM detection, beat timestamps, and frequency spectrum data compatible with Remotion and HyperFrames workflows.
|
|
32
|
+
|
|
33
|
+
## Features
|
|
34
|
+
|
|
35
|
+
- BPM (Beats Per Minute) detection
|
|
36
|
+
- Beat timestamp extraction
|
|
37
|
+
- Frequency spectrum analysis
|
|
38
|
+
- Compatible with Remotion and HyperFrames workflows
|
|
39
|
+
|
|
40
|
+
## Installation
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
pip install spectrumalyzer
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Usage
|
|
47
|
+
|
|
48
|
+
```python
|
|
49
|
+
from spectrumalyzer import analyze_audio
|
|
50
|
+
|
|
51
|
+
# Analyze an audio file
|
|
52
|
+
results = analyze_audio("path/to/audio/file.mp3")
|
|
53
|
+
|
|
54
|
+
# Get BPM
|
|
55
|
+
bpm = results["bpm"]
|
|
56
|
+
|
|
57
|
+
# Get beat timestamps
|
|
58
|
+
beats = results["beats"]
|
|
59
|
+
|
|
60
|
+
# Get frequency spectrum
|
|
61
|
+
spectrum = results["spectrum"]
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## API
|
|
65
|
+
|
|
66
|
+
See the [SKILL.md](skills/spectrumalyzer/SKILL.md) file for detailed API documentation.
|
|
67
|
+
|
|
68
|
+
## License
|
|
69
|
+
|
|
70
|
+
MIT
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
spectrumalyzer
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
import os, sys, json, math, struct, subprocess
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
import argparse
|
|
5
|
+
|
|
6
|
+
def parse_args():
|
|
7
|
+
parser = argparse.ArgumentParser(prog='spectrumalyzer',
|
|
8
|
+
description='Audio beat analysis tool for motion graphics')
|
|
9
|
+
parser.add_argument('audio_file', help='Path to audio file')
|
|
10
|
+
parser.add_argument('--output', '-o', default='beats.json',
|
|
11
|
+
help='Output JSON file path')
|
|
12
|
+
parser.add_argument('--format', '-f', choices=['json', 'remotion'],
|
|
13
|
+
default='json', help='Output format')
|
|
14
|
+
parser.add_argument('--threshold', '-t', type=float, default=0.15,
|
|
15
|
+
help='Beat detection threshold (0.0-1.0, default: 0.15)')
|
|
16
|
+
parser.add_argument('--fft-size', type=int, default=1024,
|
|
17
|
+
help='FFT size for spectrum (256-4096)')
|
|
18
|
+
return parser.parse_args()
|
|
19
|
+
|
|
20
|
+
def get_audio_duration(filepath):
|
|
21
|
+
try:
|
|
22
|
+
result = subprocess.run(
|
|
23
|
+
['ffprobe', '-v', 'error', '-show_entries', 'format=duration',
|
|
24
|
+
'-of', 'default=noprint_wrappers=1:nokey=1', filepath],
|
|
25
|
+
capture_output=True, text=True)
|
|
26
|
+
return float(result.stdout.strip())
|
|
27
|
+
except Exception as e:
|
|
28
|
+
print(f'Error getting duration: {e}', file=sys.stderr)
|
|
29
|
+
return 0
|
|
30
|
+
|
|
31
|
+
def extract_pcm(filepath, duration):
|
|
32
|
+
try:
|
|
33
|
+
result = subprocess.run(
|
|
34
|
+
['ffmpeg', '-i', filepath, '-vn', '-acodec', 'pcm_s16le',
|
|
35
|
+
'-ar', '44100', '-ac', '1', '-f', 's16le',
|
|
36
|
+
'-ss', '0', '-t', str(duration or 999999), 'pipe:1'],
|
|
37
|
+
capture_output=True)
|
|
38
|
+
if len(result.stdout) == 0:
|
|
39
|
+
print('Warning: No audio data extracted', file=sys.stderr)
|
|
40
|
+
return []
|
|
41
|
+
samples = struct.unpack(f'<{len(result.stdout)//2}h', result.stdout)
|
|
42
|
+
return [s / 32768.0 for s in samples]
|
|
43
|
+
except Exception as e:
|
|
44
|
+
print(f'Error extracting PCM: {e}', file=sys.stderr)
|
|
45
|
+
return []
|
|
46
|
+
|
|
47
|
+
def fft_transform(signal, inverse=False):
|
|
48
|
+
n = len(signal)
|
|
49
|
+
if n <= 1:
|
|
50
|
+
return signal[:]
|
|
51
|
+
output = [0] * n
|
|
52
|
+
bits = n.bit_length() - 1
|
|
53
|
+
for i in range(n):
|
|
54
|
+
j = 0
|
|
55
|
+
for k in range(bits):
|
|
56
|
+
if i & (1 << k):
|
|
57
|
+
j |= (1 << (bits - 1 - k))
|
|
58
|
+
output[j] = signal[i]
|
|
59
|
+
size = 2
|
|
60
|
+
while size < n:
|
|
61
|
+
step = size * 2
|
|
62
|
+
angle = 2 * math.pi / step * (-1 if inverse else 1)
|
|
63
|
+
w_real = math.cos(angle)
|
|
64
|
+
w_imag = math.sin(angle)
|
|
65
|
+
for i in range(0, n, step):
|
|
66
|
+
for j in range(size):
|
|
67
|
+
k = i + j
|
|
68
|
+
m = k + size
|
|
69
|
+
t_real = output[m] * w_real - output[m + 1] * w_imag
|
|
70
|
+
t_imag = output[m] * w_imag + output[m + 1] * w_real
|
|
71
|
+
output[k] = output[k] - t_real
|
|
72
|
+
output[k + 1] = output[k + 1] - t_imag
|
|
73
|
+
output[m] = output[k] + t_real
|
|
74
|
+
output[m + 1] = output[k + 1] + t_imag
|
|
75
|
+
size = step
|
|
76
|
+
if inverse:
|
|
77
|
+
for i in range(n):
|
|
78
|
+
output[i] /= n
|
|
79
|
+
return output
|
|
80
|
+
|
|
81
|
+
def detect_beats(audio, sample_rate=44100, threshold=0.3, min_interval=0.5):
|
|
82
|
+
hop_size = 512
|
|
83
|
+
num_frames = len(audio) // hop_size
|
|
84
|
+
energies = [0] * num_frames
|
|
85
|
+
for i in range(num_frames):
|
|
86
|
+
start = i * hop_size
|
|
87
|
+
end = min(start + hop_size, len(audio))
|
|
88
|
+
energy = 0
|
|
89
|
+
for j in range(start, end):
|
|
90
|
+
energy += audio[j] * audio[j]
|
|
91
|
+
energies[i] = math.sqrt(energy / max(1, end - start))
|
|
92
|
+
onsets = [0] * (num_frames - 1)
|
|
93
|
+
for i in range(1, num_frames):
|
|
94
|
+
diff = energies[i] - energies[i - 1]
|
|
95
|
+
onsets[i - 1] = max(0, diff)
|
|
96
|
+
beat_times = []
|
|
97
|
+
last_beat_idx = -float('inf')
|
|
98
|
+
min_frames = int(min_interval * sample_rate / hop_size)
|
|
99
|
+
for i in range(len(onsets)):
|
|
100
|
+
if onsets[i] > threshold and (i - last_beat_idx) > min_frames:
|
|
101
|
+
beat_times.append(i * hop_size / sample_rate)
|
|
102
|
+
last_beat_idx = i
|
|
103
|
+
bpm = 0
|
|
104
|
+
if len(beat_times) >= 2:
|
|
105
|
+
intervals = []
|
|
106
|
+
for i in range(1, len(beat_times)):
|
|
107
|
+
interval = beat_times[i] - beat_times[i - 1]
|
|
108
|
+
if 0 < interval < 10.0:
|
|
109
|
+
intervals.append(interval)
|
|
110
|
+
if intervals:
|
|
111
|
+
sorted_intervals = sorted(intervals)
|
|
112
|
+
n = len(sorted_intervals)
|
|
113
|
+
mid = n // 2
|
|
114
|
+
median_interval = sorted_intervals[mid] if n % 2 == 1 else (sorted_intervals[mid - 1] + sorted_intervals[mid]) / 2
|
|
115
|
+
if median_interval > 0:
|
|
116
|
+
bpm = round(60 / median_interval)
|
|
117
|
+
bpm = max(20, min(220, bpm))
|
|
118
|
+
return beat_times, bpm
|
|
119
|
+
|
|
120
|
+
def generate_spectrum(audio, sample_rate=44100, fft_size=1024):
|
|
121
|
+
num_bins = fft_size // 2
|
|
122
|
+
spectrum = []
|
|
123
|
+
for i in range(num_bins):
|
|
124
|
+
start = int((i / num_bins) * len(audio))
|
|
125
|
+
end = int(((i + 1) / num_bins) * len(audio))
|
|
126
|
+
energy = 0
|
|
127
|
+
for j in range(start, min(end, len(audio))):
|
|
128
|
+
energy += audio[j] * audio[j]
|
|
129
|
+
energy = math.sqrt(energy / max(1, end - start)) * 100
|
|
130
|
+
spectrum.append(energy)
|
|
131
|
+
freq_data = []
|
|
132
|
+
for i, e in enumerate(spectrum):
|
|
133
|
+
freq = (i * sample_rate) / fft_size
|
|
134
|
+
freq_data.append({'frequency': freq, 'energy': e})
|
|
135
|
+
return freq_data
|
|
136
|
+
|
|
137
|
+
def main():
|
|
138
|
+
args = parse_args()
|
|
139
|
+
if not os.path.exists(args.audio_file):
|
|
140
|
+
print(f'Error: File not found: {args.audio_file}', file=sys.stderr)
|
|
141
|
+
sys.exit(1)
|
|
142
|
+
print('=== spectrumalyzer ===')
|
|
143
|
+
print(f'Input: {args.audio_file}')
|
|
144
|
+
duration = get_audio_duration(args.audio_file)
|
|
145
|
+
print(f'Duration: {duration:.2f}s')
|
|
146
|
+
audio = extract_pcm(args.audio_file, duration)
|
|
147
|
+
print(f'Samples: {len(audio)}')
|
|
148
|
+
if len(audio) == 0:
|
|
149
|
+
print('Error: No audio data could be extracted', file=sys.stderr)
|
|
150
|
+
sys.exit(1)
|
|
151
|
+
threshold = max(0.0, min(1.0, args.threshold))
|
|
152
|
+
min_interval = max(0.1, 2.0)
|
|
153
|
+
beat_times, bpm = detect_beats(audio, 44100, threshold, min_interval)
|
|
154
|
+
print(f'Beats: {len(beat_times)}')
|
|
155
|
+
print(f'BPM: {bpm}')
|
|
156
|
+
fft_size = max(256, min(4096, args.fft_size))
|
|
157
|
+
spectrum = generate_spectrum(audio, 44100, fft_size)
|
|
158
|
+
beat_times_rounded = [round(t, 4) for t in beat_times]
|
|
159
|
+
spectrum_128 = spectrum[:128]
|
|
160
|
+
output = {
|
|
161
|
+
'bpm': bpm,
|
|
162
|
+
'duration': duration,
|
|
163
|
+
'sampleRate': 44100,
|
|
164
|
+
'beatCount': len(beat_times),
|
|
165
|
+
'beats': beat_times_rounded,
|
|
166
|
+
'beatTimes': beat_times_rounded,
|
|
167
|
+
'spectrum': [{'frequency': round(s['frequency'], 1), 'energy': round(s['energy'], 2)} for s in spectrum_128],
|
|
168
|
+
'metadata': {'version': '1.0.0', 'created': datetime.now().isoformat(), 'analyzer': 'spectrumalyzer'}
|
|
169
|
+
}
|
|
170
|
+
out_path = os.path.abspath(args.output)
|
|
171
|
+
with open(out_path, 'w') as f:
|
|
172
|
+
json.dump(output, f, indent=2)
|
|
173
|
+
remotion_output = {'bpm': bpm, 'beatTimes': beat_times_rounded,
|
|
174
|
+
'duration': duration, 'beatCount': len(beat_times),
|
|
175
|
+
'measures': [], 'beatData': [{'time': t, 'index': i + 1} for i, t in enumerate(beat_times_rounded)]}
|
|
176
|
+
rem_path = out_path.replace('.json', '.remotion.json')
|
|
177
|
+
with open(rem_path, 'w') as f:
|
|
178
|
+
json.dump(remotion_output, f, indent=2)
|
|
179
|
+
print()
|
|
180
|
+
print('=== Output Summary ===')
|
|
181
|
+
print(f'Beats: {len(beat_times)}')
|
|
182
|
+
print(f'BPM: {bpm}')
|
|
183
|
+
print(f'Output: {out_path}')
|
|
184
|
+
print()
|
|
185
|
+
print('First 8 beats:')
|
|
186
|
+
for i, t in enumerate(beat_times_rounded[:8]):
|
|
187
|
+
print(f' {i + 1}. {t:.2f}s')
|
|
188
|
+
print()
|
|
189
|
+
print(f'Remotion format: {rem_path}')
|
|
190
|
+
print()
|
|
191
|
+
print('=== spectrumalyzer complete ===')
|
|
192
|
+
|
|
193
|
+
if __name__ == '__main__':
|
|
194
|
+
main()
|