audiowaveform 0.1.0

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.
Files changed (40) hide show
  1. checksums.yaml +7 -0
  2. data/COPYING +674 -0
  3. data/Cargo.lock +1108 -0
  4. data/Cargo.toml +28 -0
  5. data/README.md +255 -0
  6. data/bindings/ruby/CHANGELOG.md +29 -0
  7. data/bindings/ruby/Cargo.lock +621 -0
  8. data/bindings/ruby/Cargo.toml +8 -0
  9. data/bindings/ruby/README.md +193 -0
  10. data/bindings/ruby/ext/audiowaveform/Cargo.toml +20 -0
  11. data/bindings/ruby/ext/audiowaveform/build.rs +4 -0
  12. data/bindings/ruby/ext/audiowaveform/extconf.rb +6 -0
  13. data/bindings/ruby/ext/audiowaveform/src/lib.rs +239 -0
  14. data/bindings/ruby/lib/audiowaveform/version.rb +5 -0
  15. data/bindings/ruby/lib/audiowaveform.rb +131 -0
  16. data/crates/audiowaveform/Cargo.toml +73 -0
  17. data/crates/audiowaveform/examples/generate_from_pcm.rs +25 -0
  18. data/crates/audiowaveform/examples/generate_waveform.rs +18 -0
  19. data/crates/audiowaveform/examples/render_waveform.rs +18 -0
  20. data/crates/audiowaveform/examples/resample_waveform.rs +19 -0
  21. data/crates/audiowaveform/src/audio.rs +938 -0
  22. data/crates/audiowaveform/src/color.rs +201 -0
  23. data/crates/audiowaveform/src/error.rs +89 -0
  24. data/crates/audiowaveform/src/format.rs +215 -0
  25. data/crates/audiowaveform/src/lib.rs +79 -0
  26. data/crates/audiowaveform/src/render.rs +802 -0
  27. data/crates/audiowaveform/src/wav.rs +95 -0
  28. data/crates/audiowaveform/src/waveform.rs +790 -0
  29. data/crates/audiowaveform/tests/formats.rs +223 -0
  30. data/crates/audiowaveform/tests/generate.rs +233 -0
  31. data/crates/audiowaveform/tests/render.rs +263 -0
  32. data/crates/audiowaveform/tests/support/mod.rs +125 -0
  33. data/crates/audiowaveform/tests/wav.rs +54 -0
  34. data/crates/audiowaveform/tests/waveform_io.rs +255 -0
  35. data/crates/audiowaveform-cli/Cargo.toml +43 -0
  36. data/crates/audiowaveform-cli/src/main.rs +803 -0
  37. data/crates/audiowaveform-cli/tests/cli.rs +483 -0
  38. data/crates/audiowaveform-cli/tests/support/mod.rs +111 -0
  39. data/sig/audiowaveform.rbs +33 -0
  40. metadata +100 -0
@@ -0,0 +1,223 @@
1
+ #![cfg(feature = "decode")]
2
+
3
+ mod support;
4
+
5
+ use std::io::Cursor;
6
+
7
+ use audiowaveform::{
8
+ AudioFormat, Error, GenerateOptions, decode_audio_from_path, generate_waveform_from_path,
9
+ generate_waveform_from_reader,
10
+ };
11
+ use support::fixture_path;
12
+ #[cfg(feature = "format-m4a")]
13
+ use support::read_fixture;
14
+
15
+ #[test]
16
+ fn format_features_control_decoding() {
17
+ for (fixture, enabled, feature, sample_rate, channels) in [
18
+ (
19
+ "formats/stereo.aac",
20
+ cfg!(feature = "format-aac"),
21
+ "format-aac",
22
+ 48_000,
23
+ 2,
24
+ ),
25
+ (
26
+ "formats/stereo.m4a",
27
+ cfg!(feature = "format-m4a"),
28
+ "format-m4a",
29
+ 48_000,
30
+ 2,
31
+ ),
32
+ (
33
+ "formats/mono.m4a",
34
+ cfg!(feature = "format-m4a"),
35
+ "format-m4a",
36
+ 44_100,
37
+ 1,
38
+ ),
39
+ (
40
+ "formats/alac.m4a",
41
+ cfg!(feature = "format-m4a"),
42
+ "format-m4a",
43
+ 48_000,
44
+ 2,
45
+ ),
46
+ (
47
+ "formats/fragmented.mp4",
48
+ cfg!(feature = "format-m4a"),
49
+ "format-m4a",
50
+ 48_000,
51
+ 2,
52
+ ),
53
+ (
54
+ "formats/video-first.mp4",
55
+ cfg!(feature = "format-m4a"),
56
+ "format-m4a",
57
+ 48_000,
58
+ 2,
59
+ ),
60
+ (
61
+ "formats/stereo.aiff",
62
+ cfg!(feature = "format-aiff"),
63
+ "format-aiff",
64
+ 48_000,
65
+ 2,
66
+ ),
67
+ (
68
+ "formats/stereo.caf",
69
+ cfg!(feature = "format-caf"),
70
+ "format-caf",
71
+ 48_000,
72
+ 2,
73
+ ),
74
+ (
75
+ "formats/pcm.caf",
76
+ cfg!(feature = "format-caf"),
77
+ "format-caf",
78
+ 48_000,
79
+ 2,
80
+ ),
81
+ (
82
+ "formats/stereo.webm",
83
+ cfg!(feature = "format-mkv"),
84
+ "format-mkv",
85
+ 48_000,
86
+ 2,
87
+ ),
88
+ (
89
+ "formats/stereo.mka",
90
+ cfg!(feature = "format-mkv"),
91
+ "format-mkv",
92
+ 48_000,
93
+ 2,
94
+ ),
95
+ (
96
+ "formats/stereo.mp2",
97
+ cfg!(feature = "format-mp2"),
98
+ "format-mp2",
99
+ 48_000,
100
+ 2,
101
+ ),
102
+ (
103
+ "formats/silence.mp1",
104
+ cfg!(feature = "format-mp1"),
105
+ "format-mp1",
106
+ 44_100,
107
+ 1,
108
+ ),
109
+ (
110
+ "test_file_stereo.mp3",
111
+ cfg!(feature = "format-mp3"),
112
+ "format-mp3",
113
+ 16_000,
114
+ 2,
115
+ ),
116
+ (
117
+ "test_file_stereo.flac",
118
+ cfg!(feature = "format-flac"),
119
+ "format-flac",
120
+ 16_000,
121
+ 2,
122
+ ),
123
+ (
124
+ "formats/flac.ogg",
125
+ cfg!(feature = "format-ogg"),
126
+ "format-ogg",
127
+ 48_000,
128
+ 2,
129
+ ),
130
+ (
131
+ "test_file_stereo.oga",
132
+ cfg!(feature = "format-ogg"),
133
+ "format-ogg",
134
+ 16_000,
135
+ 2,
136
+ ),
137
+ (
138
+ "formats/stereo.wav",
139
+ cfg!(feature = "format-wav"),
140
+ "format-wav",
141
+ 48_000,
142
+ 2,
143
+ ),
144
+ (
145
+ "formats/adpcm.wav",
146
+ cfg!(feature = "format-wav"),
147
+ "format-wav",
148
+ 48_000,
149
+ 2,
150
+ ),
151
+ ] {
152
+ let result = decode_audio_from_path(fixture_path(fixture));
153
+ if !enabled {
154
+ assert!(
155
+ matches!(result, Err(Error::FeatureDisabled { feature: actual }) if actual == feature),
156
+ "{fixture}: {result:?}"
157
+ );
158
+ continue;
159
+ }
160
+ let pcm = result.unwrap_or_else(|error| panic!("{fixture}: {error}"));
161
+ assert_eq!(pcm.sample_rate(), sample_rate, "{fixture}");
162
+ assert_eq!(pcm.channels(), channels, "{fixture}");
163
+ assert!(pcm.frame_count() > 0, "{fixture}");
164
+ if fixture.starts_with("formats/") {
165
+ assert!(
166
+ (pcm.duration_seconds() - 0.25).abs() < 0.1,
167
+ "{fixture}: {}",
168
+ pcm.duration_seconds()
169
+ );
170
+ }
171
+ if !fixture.ends_with("silence.mp1") {
172
+ assert!(pcm.samples().iter().any(|sample| *sample != 0), "{fixture}");
173
+ }
174
+ let waveform =
175
+ generate_waveform_from_path(fixture_path(fixture), &GenerateOptions::default())
176
+ .expect("generate");
177
+ assert!(!waveform.is_empty(), "{fixture}");
178
+ }
179
+ }
180
+
181
+ #[cfg(feature = "format-m4a")]
182
+ #[test]
183
+ fn m4a_decoding_selects_audio_and_supports_probing_without_a_filename() {
184
+ let options = GenerateOptions::default();
185
+ let audio = generate_waveform_from_path(fixture_path("formats/stereo.m4a"), &options).unwrap();
186
+ let video =
187
+ generate_waveform_from_path(fixture_path("formats/video-first.mp4"), &options).unwrap();
188
+ assert_eq!(audio, video);
189
+ let probed = generate_waveform_from_reader(
190
+ Cursor::new(read_fixture("formats/stereo.m4a")),
191
+ None,
192
+ &options,
193
+ )
194
+ .unwrap();
195
+ assert_eq!(audio, probed);
196
+ assert!(
197
+ !generate_waveform_from_path(fixture_path("formats/short.m4a"), &options)
198
+ .unwrap()
199
+ .is_empty()
200
+ );
201
+ let error = decode_audio_from_path(fixture_path("formats/video-only.mp4")).unwrap_err();
202
+ assert!(error.to_string().contains("no supported audio track"));
203
+ }
204
+
205
+ #[test]
206
+ fn corrupt_enabled_formats_return_errors() {
207
+ for format in [
208
+ AudioFormat::Aac,
209
+ AudioFormat::Mp4,
210
+ AudioFormat::Wav,
211
+ AudioFormat::Mp3,
212
+ AudioFormat::Mkv,
213
+ ] {
214
+ if format.ensure_enabled().is_ok() {
215
+ let result = generate_waveform_from_reader(
216
+ Cursor::new(b"not an audio file".to_vec()),
217
+ Some(format),
218
+ &GenerateOptions::default(),
219
+ );
220
+ assert!(result.is_err(), "{format:?}");
221
+ }
222
+ }
223
+ }
@@ -0,0 +1,233 @@
1
+ mod support;
2
+
3
+ #[cfg(feature = "format-wav")]
4
+ use std::io::Cursor;
5
+
6
+ use audiowaveform::{
7
+ GenerateOptions, ScaleSpec, WaveformFormat, decode_audio_from_path, generate_waveform_from_path,
8
+ };
9
+
10
+ #[cfg(feature = "format-wav")]
11
+ use self::support::read_fixture;
12
+ use self::support::{assert_bytes_eq, fixture_path};
13
+ #[cfg(feature = "format-wav")]
14
+ use audiowaveform::{RawAudioConfig, RawSampleFormat, generate_waveform_from_raw_reader};
15
+
16
+ #[cfg(feature = "format-wav")]
17
+ #[test]
18
+ fn generates_expected_binary_waveform_from_wav() {
19
+ let waveform = generate_waveform_from_path(
20
+ fixture_path("test_file_stereo.wav"),
21
+ &GenerateOptions {
22
+ scale: ScaleSpec::SamplesPerPixel(64),
23
+ split_channels: false,
24
+ amplitude_scale: None,
25
+ },
26
+ )
27
+ .expect("generate waveform");
28
+
29
+ let mut output = Vec::new();
30
+ waveform
31
+ .write_to_writer(&mut output, WaveformFormat::Dat, Some(8))
32
+ .expect("write dat");
33
+
34
+ assert_bytes_eq(&output, "test_file_stereo_8bit_64spp_wav.dat");
35
+ }
36
+
37
+ #[test]
38
+ fn decodes_supported_audio_inputs_with_expected_metadata() {
39
+ let cases = [
40
+ ("test_file_stereo.wav", 16_000, 2, 113_519_usize),
41
+ ("test_file_mono_float32.wav", 16_000, 1, 115_190_usize),
42
+ ("test_file_stereo.mp3", 16_000, 2, 113_519_usize),
43
+ ("test_file_stereo.flac", 16_000, 2, 113_519_usize),
44
+ ("test_file_stereo.oga", 16_000, 2, 113_792_usize),
45
+ ];
46
+
47
+ for (fixture, sample_rate, channels, frames) in cases {
48
+ if audiowaveform::AudioFormat::from_path(fixture)
49
+ .unwrap()
50
+ .ensure_enabled()
51
+ .is_err()
52
+ {
53
+ continue;
54
+ }
55
+ let pcm = decode_audio_from_path(fixture_path(fixture)).expect("decode audio");
56
+ assert_eq!(pcm.sample_rate(), sample_rate, "{fixture}");
57
+ assert_eq!(pcm.channels(), channels, "{fixture}");
58
+ assert_eq!(pcm.frame_count(), frames, "{fixture}");
59
+ }
60
+ }
61
+
62
+ #[test]
63
+ fn generates_expected_waveform_bytes_from_supported_audio_inputs() {
64
+ let cases = [
65
+ (
66
+ "test_file_stereo.wav",
67
+ "test_file_stereo_8bit_64spp_wav.dat",
68
+ "test_file_stereo_8bit_64spp_wav.json",
69
+ ),
70
+ (
71
+ "test_file_stereo.mp3",
72
+ "test_file_stereo_8bit_64spp_mp3.dat",
73
+ "test_file_stereo_8bit_64spp_mp3.json",
74
+ ),
75
+ (
76
+ "test_file_stereo.flac",
77
+ "test_file_stereo_8bit_64spp_flac.dat",
78
+ "test_file_stereo_8bit_64spp_flac.json",
79
+ ),
80
+ (
81
+ "test_file_stereo.oga",
82
+ "test_file_stereo_8bit_64spp_oga.dat",
83
+ "test_file_stereo_8bit_64spp_oga.json",
84
+ ),
85
+ ];
86
+
87
+ for (input, dat_fixture, json_fixture) in cases {
88
+ if audiowaveform::AudioFormat::from_path(input)
89
+ .unwrap()
90
+ .ensure_enabled()
91
+ .is_err()
92
+ {
93
+ continue;
94
+ }
95
+ let waveform = generate_waveform_from_path(
96
+ fixture_path(input),
97
+ &GenerateOptions {
98
+ scale: ScaleSpec::SamplesPerPixel(64),
99
+ split_channels: false,
100
+ amplitude_scale: None,
101
+ },
102
+ )
103
+ .expect("generate waveform");
104
+
105
+ let mut dat = Vec::new();
106
+ waveform
107
+ .write_to_writer(&mut dat, WaveformFormat::Dat, Some(8))
108
+ .expect("write dat");
109
+ assert_bytes_eq(&dat, dat_fixture);
110
+
111
+ let mut json = Vec::new();
112
+ waveform
113
+ .write_to_writer(&mut json, WaveformFormat::Json, Some(8))
114
+ .expect("write json");
115
+ assert_bytes_eq(&json, json_fixture);
116
+ }
117
+ }
118
+
119
+ #[cfg(feature = "format-wav")]
120
+ #[test]
121
+ fn generates_expected_waveform_from_float_wav_audio() {
122
+ let waveform = generate_waveform_from_path(
123
+ fixture_path("test_file_mono_float32.wav"),
124
+ &GenerateOptions {
125
+ scale: ScaleSpec::SamplesPerPixel(64),
126
+ split_channels: false,
127
+ amplitude_scale: None,
128
+ },
129
+ )
130
+ .expect("generate waveform");
131
+
132
+ let mut dat = Vec::new();
133
+ waveform
134
+ .write_to_writer(&mut dat, WaveformFormat::Dat, Some(8))
135
+ .expect("write dat");
136
+ assert_bytes_eq(&dat, "test_file_mono_float32_8bit_64spp.dat");
137
+ }
138
+
139
+ #[cfg(feature = "format-wav")]
140
+ #[test]
141
+ fn generates_expected_waveform_from_raw_audio() {
142
+ let raw = read_fixture("test_file_mono.raw");
143
+ let raw_config = RawAudioConfig::new(16_000, 1, RawSampleFormat::S16Le).expect("raw config");
144
+ let options = GenerateOptions {
145
+ scale: ScaleSpec::SamplesPerPixel(64),
146
+ split_channels: false,
147
+ amplitude_scale: None,
148
+ };
149
+
150
+ let waveform_from_raw =
151
+ generate_waveform_from_raw_reader(Cursor::new(raw), &raw_config, &options)
152
+ .expect("generate waveform from raw");
153
+ let waveform_from_wav =
154
+ generate_waveform_from_path(fixture_path("test_file_mono.wav"), &options)
155
+ .expect("generate waveform from wav");
156
+
157
+ assert_eq!(waveform_from_raw, waveform_from_wav);
158
+ }
159
+
160
+ #[cfg(feature = "format-wav")]
161
+ #[test]
162
+ fn generates_expected_split_channel_and_auto_scaled_waveforms() {
163
+ let split = generate_waveform_from_path(
164
+ fixture_path("test_file_stereo.wav"),
165
+ &GenerateOptions {
166
+ scale: ScaleSpec::SamplesPerPixel(64),
167
+ split_channels: true,
168
+ amplitude_scale: None,
169
+ },
170
+ )
171
+ .expect("generate split channels");
172
+ let mut split_dat = Vec::new();
173
+ split
174
+ .write_to_writer(&mut split_dat, WaveformFormat::Dat, Some(8))
175
+ .expect("write split dat");
176
+ assert_bytes_eq(&split_dat, "test_file_2channel_8bit_64spp_wav.dat");
177
+
178
+ let auto = generate_waveform_from_path(
179
+ fixture_path("test_file_stereo.wav"),
180
+ &GenerateOptions {
181
+ scale: ScaleSpec::SamplesPerPixel(64),
182
+ split_channels: false,
183
+ amplitude_scale: Some(audiowaveform::AmplitudeScale::Auto),
184
+ },
185
+ )
186
+ .expect("generate auto-scaled waveform");
187
+ let mut auto_dat = Vec::new();
188
+ auto.write_to_writer(&mut auto_dat, WaveformFormat::Dat, Some(8))
189
+ .expect("write auto dat");
190
+ assert_bytes_eq(&auto_dat, "test_file_stereo_8bit_64spp_wav_auto_scale.dat");
191
+ }
192
+
193
+ #[cfg(feature = "format-wav")]
194
+ #[test]
195
+ fn reader_based_generation_matches_path_based_generation() {
196
+ let bytes = read_fixture("test_file_stereo.wav");
197
+ let from_reader = audiowaveform::generate_waveform_from_reader(
198
+ Cursor::new(bytes),
199
+ Some(audiowaveform::AudioFormat::Wav),
200
+ &GenerateOptions {
201
+ scale: ScaleSpec::SamplesPerPixel(64),
202
+ split_channels: false,
203
+ amplitude_scale: None,
204
+ },
205
+ )
206
+ .expect("generate from reader");
207
+ let from_path = generate_waveform_from_path(
208
+ fixture_path("test_file_stereo.wav"),
209
+ &GenerateOptions {
210
+ scale: ScaleSpec::SamplesPerPixel(64),
211
+ split_channels: false,
212
+ amplitude_scale: None,
213
+ },
214
+ )
215
+ .expect("generate from path");
216
+
217
+ assert_eq!(from_reader, from_path);
218
+ }
219
+
220
+ #[test]
221
+ fn rejects_opus_generation_until_supported() {
222
+ let error = generate_waveform_from_path(
223
+ fixture_path("test_file_stereo.opus"),
224
+ &GenerateOptions {
225
+ scale: ScaleSpec::SamplesPerPixel(64),
226
+ split_channels: false,
227
+ amplitude_scale: None,
228
+ },
229
+ )
230
+ .expect_err("opus should be unsupported");
231
+
232
+ assert_eq!(error.to_string(), "Unsupported format: opus");
233
+ }