-
Notifications
You must be signed in to change notification settings - Fork 4
/
Copy pathShazam.cs
85 lines (65 loc) · 2.79 KB
/
Shazam.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
using System;
using System.Net.Http;
using System.Text;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using NAudio.CoreAudioApi;
using NAudio.Wave;
using Shozom.Magic;
namespace Shozom {
internal static class Shazam {
private static readonly MMDeviceEnumerator Enumerator = new();
private static readonly HttpClient Http = new() { Timeout = TimeSpan.FromSeconds(3) };
private static readonly string DeviceId = Guid.NewGuid().ToString();
public static async Task<ShozomMatch> IdentifyAsync(string deviceId, CancellationToken cancel) {
var device = Enumerator.GetDevice(deviceId);
if (device == null || device.State != DeviceState.Active) throw new ArgumentException("Selected device not available");
var capture = device.DataFlow switch {
DataFlow.Capture => new WasapiCapture(device),
DataFlow.Render => new WasapiLoopbackCapture(device)
};
var buffer = new BufferedWaveProvider(capture.WaveFormat) { ReadFully = false, DiscardOnBufferOverflow = true };
using var resampler = new MediaFoundationResampler(buffer, new WaveFormat(16000, 16, 1));
var samples = resampler.ToSampleProvider();
capture.DataAvailable += (_, e) => { buffer.AddSamples(e.Buffer, 0, e.BytesRecorded); };
capture.StartRecording();
var analyser = new Analyser();
var landmarker = new Landmarker(analyser);
var retryMs = 3000;
while (true) {
if (cancel.IsCancellationRequested) {
capture.StopRecording();
throw new OperationCanceledException();
}
if (buffer.BufferedDuration.TotalSeconds < 1) {
Thread.Sleep(100);
continue;
}
analyser.ReadChunk(samples);
if (analyser.StripeCount > 2 * Landmarker.RADIUS_TIME) landmarker.Find(analyser.StripeCount - Landmarker.RADIUS_TIME - 1);
if (analyser.ProcessedMs < retryMs) continue;
var body = new ShazamRequest {
Signature = new ShazamSignature {
Uri = "data:audio/vnd.shazam.sig;base64," + Convert.ToBase64String(Signature.Create(Analyser.SAMPLE_RATE, analyser.ProcessedSamples, landmarker)),
SampleMs = analyser.ProcessedMs
}
};
using var res = await Http.PostAsync($"https://amp.shazam.com/discovery/v5/en/US/android/-/tag/{DeviceId}/{Guid.NewGuid()}", new StringContent(JsonSerializer.Serialize(body), Encoding.UTF8, "application/json"), cancel);
var data = JsonSerializer.Deserialize<ShazamResponse>(await res.Content.ReadAsStringAsync(cancel));
if (data.RetryMs > 0) {
retryMs = (int) data.RetryMs;
continue;
}
capture.StopRecording();
if (data.Track == null) return null;
return new ShozomMatch {
Title = data.Track.Title,
Artist = data.Track.Subtitle,
Link = data.Track.Share.Link,
Cover = data.Track?.Images?.CoverHQ ?? data.Track?.Images?.Cover ?? data.Track.Share.Image
};
}
}
}
}