m kunz commited on
Commit
45a4975
1 Parent(s): 092a2a3
Files changed (9) hide show
  1. Dockerfile.txt +7 -0
  2. arnold.mp3 +0 -0
  3. arnold2.wav +0 -0
  4. flag1.txt +1 -0
  5. flag2.txt +1 -0
  6. host.py +143 -0
  7. static/skynet.png +0 -0
  8. templates/chal2.html +129 -0
  9. templates/index.html +129 -0
Dockerfile.txt ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ FROM ubuntu
2
+ WORKDIR /app
3
+ COPY . /app
4
+ RUN apt update && apt install -y python3 python3-pip ffmpeg nano
5
+ RUN pip install flask transformers librosa torch torchaudio gunicorn
6
+ EXPOSE 8080
7
+ CMD ["gunicorn", "-b", ":8080", "-w", "2", "host:app"]
arnold.mp3 ADDED
Binary file (475 kB). View file
 
arnold2.wav ADDED
Binary file (38.5 kB). View file
 
flag1.txt ADDED
@@ -0,0 +1 @@
 
 
1
+ kernel{e97549a285227268bd826dd61d097016}
flag2.txt ADDED
@@ -0,0 +1 @@
 
 
1
+ kernel{91edded0465d0ba9ee78981c63b864f8}
host.py ADDED
@@ -0,0 +1,143 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from flask import Flask, render_template, request, jsonify
2
+ from transformers import Wav2Vec2FeatureExtractor, UniSpeechSatForXVector
3
+ import torchaudio
4
+ import torch
5
+ import io
6
+ import librosa
7
+ from scipy.spatial.distance import cosine
8
+ import numpy as np
9
+ import os
10
+ # brew install ffmpeg
11
+ # pip install flask transformers librosa torch torchaudio
12
+
13
+ app = Flask(__name__, static_url_path='/static')
14
+
15
+ # https://www.youtube.com/watch?v=NjR6TyHgAho first 30s
16
+ mp3_file_path = "arnold.mp3"
17
+
18
+ # https://neets.ai/ "With great power comes great responsibility"
19
+ mp3_file_path2 = 'arnold2.wav'
20
+
21
+ flag1=""
22
+ flag2=""
23
+
24
+ with open("flag1.txt") as f:
25
+ flag1=f.read()
26
+ with open("flag2.txt") as f:
27
+ flag2=f.read()
28
+
29
+ # Load feature extractor and model
30
+ themodel = "microsoft/unispeech-sat-large-sv"
31
+ if os.path.exists("model"):
32
+ themodel = "model"
33
+ feature_extractor = Wav2Vec2FeatureExtractor.from_pretrained(themodel)
34
+ model = UniSpeechSatForXVector.from_pretrained(themodel)
35
+
36
+ # Preprocess audio function to convert audio to mono 16khz
37
+ def preprocess_audio(audio_data):
38
+ waveform, sample_rate = torchaudio.load(audio_data)
39
+ if waveform.shape[0] > 1:
40
+ waveform = torch.mean(waveform, dim=0, keepdim=True)
41
+ if sample_rate != 16000:
42
+ waveform = torchaudio.transforms.Resample(sample_rate, 16000)(waveform)
43
+ waveform = waveform.squeeze().numpy()
44
+ return waveform
45
+
46
+ @app.route('/')
47
+ def index():
48
+ return render_template('index.html')
49
+
50
+ @app.route('/chal2')
51
+ def chal2():
52
+ return render_template('chal2.html')
53
+
54
+ # Hugging faces doesn't run an api for us anymore so processing data against a model needs to be done locally now
55
+ # https://www.ktskumar.com/2021/12/introduction-to-voice-authentication-using-javascript/
56
+ # https://hf.space/gradioiframe/microsoft/unispeech-speaker-verification/api/predict
57
+ # You can buy something similiar through Azure, perhaps microsoft just wanted to commercialize this
58
+ @app.route('/compare_audio', methods=['POST'])
59
+ def compare_audio():
60
+ try:
61
+ # Get the recorded audio file from the frontend
62
+ recorded_audio = request.files['audio_data']
63
+
64
+ # Preprocess recorded audio
65
+ audio_data = preprocess_audio(recorded_audio)
66
+ inputs = feature_extractor(audio_data, return_tensors="pt")
67
+ embeddings = model(**inputs).embeddings
68
+ embeddings_normalized = torch.nn.functional.normalize(embeddings, dim=-1).cpu()
69
+
70
+ # Load and preprocess MP3 file for comparison
71
+ mp3_audio = preprocess_audio(mp3_file_path)
72
+ mp3_inputs = feature_extractor(mp3_audio, return_tensors="pt")
73
+ mp3_embeddings = model(**mp3_inputs).embeddings
74
+ mp3_embeddings_normalized = torch.nn.functional.normalize(mp3_embeddings, dim=-1).cpu()
75
+
76
+ # Calculate cosine similarity
77
+ cosine_sim = torch.nn.CosineSimilarity(dim=-1)
78
+ similarity = cosine_sim(embeddings_normalized, mp3_embeddings_normalized).item()
79
+
80
+ similarity = round(similarity, 3)
81
+
82
+ threshold = 0.89 # Adjust the threshold as needed
83
+ if similarity < threshold:
84
+ result = "Authorization Failed! " + str(similarity) + " < 0.890<br>Do your best Terminator impression"
85
+ else:
86
+ result = "Good job! Match: " + str(similarity) + "<br>" + flag1 + "<br><a href='/chal2'>Click here to open the next challenge</a>"
87
+
88
+ return jsonify({'result': result})
89
+ except Exception as e:
90
+ print("Caught: "+str(e))
91
+ return jsonify({'error': 'An error occurred during audio comparison. Im fragile please dont abuse.' })
92
+
93
+ def extract_mfcc(audio_bytes):
94
+ # Preprocess audio
95
+ waveform = preprocess_audio2(audio_bytes)
96
+
97
+ # Extract MFCC coefficients
98
+ mfcc = librosa.feature.mfcc(y=waveform, sr=16000, n_mfcc=13)
99
+
100
+ return mfcc
101
+
102
+ def preprocess_audio2(audio_bytes):
103
+ # Load the audio bytes into torchaudio waveform
104
+ waveform, sample_rate = torchaudio.load(io.BytesIO(audio_bytes))
105
+
106
+ # Ensure the audio has a single channel (mono)
107
+ if waveform.shape[0] > 1:
108
+ waveform = torch.mean(waveform, dim=0, keepdim=True)
109
+
110
+ # Resample the audio to 16kHz if needed
111
+ if sample_rate != 16000:
112
+ waveform = torchaudio.transforms.Resample(sample_rate, 16000)(waveform)
113
+
114
+ # Trim silence at beginning and end
115
+ waveform, _ = librosa.effects.trim(waveform, top_db=20)
116
+
117
+ waveform = waveform.squeeze().numpy()
118
+
119
+ return waveform
120
+
121
+ @app.route('/compare_audio2', methods=['POST'])
122
+ def compare_audio2():
123
+ try:
124
+ recorded_audio = request.files['audio_data'].read()
125
+ mp3_audio = open(mp3_file_path2, 'rb').read()
126
+
127
+ # Compare similarity between audio
128
+ mfcc1 = extract_mfcc(recorded_audio)
129
+ mfcc2 = extract_mfcc(mp3_audio)
130
+ similarity = 1 - cosine(np.mean(mfcc1, axis=1), np.mean(mfcc2, axis=1))
131
+ similarity = round(similarity, 3)
132
+ if similarity < 0.940:
133
+ result = "Authorization Failed! " + str(similarity) + " < 0.940<br>Say: 'With great power comes great responsibility' as Arnold Schwarzenegger"
134
+ else:
135
+ result = "Good job! Match: " + str(similarity) + "<br>" + flag2
136
+
137
+ return jsonify({'result': result})
138
+ except Exception as e:
139
+ print("Caught: "+str(e))
140
+ return jsonify({'error': 'An error occurred during audio comparison. Im fragile please dont abuse.'})
141
+
142
+ if __name__ == '__main__':
143
+ app.run(host="0.0.0.0", port=8080, debug=True)
static/skynet.png ADDED
templates/chal2.html ADDED
@@ -0,0 +1,129 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>SKYNET Voice Authentication</title>
7
+ <style>
8
+ body, html {
9
+ height: 100%;
10
+ margin: 0;
11
+ font-family: Arial, sans-serif;
12
+ color: white;
13
+ text-align: center;
14
+ }
15
+ .bg {
16
+ background-image: url('/static/skynet.png');
17
+ height: 100%;
18
+ background-position: center;
19
+ background-repeat: no-repeat;
20
+ background-size: cover;
21
+ position: relative;
22
+ }
23
+ .content {
24
+ position: absolute;
25
+ bottom: 10%;
26
+ width: 100%;
27
+ }
28
+ .controls {
29
+ display: flex;
30
+ justify-content: center;
31
+ align-items: center;
32
+ }
33
+ .button {
34
+ background-color: red;
35
+ color: white;
36
+ padding: 10px 20px;
37
+ border: none;
38
+ border-radius: 5px;
39
+ font-size: 16px;
40
+ cursor: pointer;
41
+ margin: 5px;
42
+ }
43
+ .button:hover {
44
+ background-color: darkred;
45
+ }
46
+ audio {
47
+ margin: 0 20px;
48
+ }
49
+ </style>
50
+ </head>
51
+ <body>
52
+
53
+ <div class="bg">
54
+ <div class="content">
55
+ <h1>Voice Authentication</h1>
56
+ <div class="controls">
57
+ <button id="recordButton" class="button">Record</button>
58
+ <button id="stopButton" class="button" disabled>Stop</button>
59
+ <audio id="audioPlayback" controls style="display:none;"></audio>
60
+ <button id="submitButton" class="button" style="float:right; display:none;">Submit</button>
61
+ </div>
62
+ <p id="result"></p>
63
+ </div>
64
+ </div>
65
+
66
+ <script>
67
+ let recordButton = document.getElementById('recordButton');
68
+ let stopButton = document.getElementById('stopButton');
69
+ let audioPlayback = document.getElementById('audioPlayback');
70
+ let submitButton = document.getElementById('submitButton');
71
+
72
+ let mediaRecorder;
73
+ let audioBlob;
74
+ let audioChunks = [];
75
+
76
+ recordButton.onclick = function() {
77
+ navigator.mediaDevices.getUserMedia({ audio: true })
78
+ .then(stream => {
79
+ mediaRecorder = new MediaRecorder(stream);
80
+ mediaRecorder.start();
81
+ audioChunks = [];
82
+
83
+ document.getElementById("result").innerHTML = "recording started";
84
+ stopButton.disabled = false;
85
+ recordButton.disabled = true;
86
+
87
+ mediaRecorder.ondataavailable = function(event) {
88
+ audioChunks.push(event.data);
89
+ };
90
+
91
+ mediaRecorder.onstop = function() {
92
+ document.getElementById("result").innerHTML = "recording stopped";
93
+ audioBlob = new Blob(audioChunks);
94
+ let audioUrl = URL.createObjectURL(audioBlob);
95
+ audioPlayback.src = audioUrl;
96
+ audioPlayback.style.display = 'block';
97
+ };
98
+ });
99
+ };
100
+
101
+ stopButton.onclick = function() {
102
+ mediaRecorder.stop();
103
+ stopButton.disabled = true;
104
+ recordButton.disabled = false;
105
+ submitButton.style.display = 'block';
106
+ };
107
+
108
+ // Submit button handler
109
+ submitButton.onclick = async function() {
110
+ document.getElementById("result").innerHTML = "Processing..."
111
+ //await new Promise(r => setTimeout(r, 2000));
112
+ console.log('Submit the audio for processing');
113
+ let formData = new FormData();
114
+ formData.append('audio_data', audioBlob);
115
+ fetch('/compare_audio2', {
116
+ method: 'POST',
117
+ body: formData
118
+ })
119
+ .then(response => response.json())
120
+ .then(data => {
121
+ document.getElementById("result").innerHTML = data.result;
122
+ })
123
+ .catch(error => console.error('Error:', error));
124
+ };
125
+ </script>
126
+
127
+ </body>
128
+ </html>
129
+
templates/index.html ADDED
@@ -0,0 +1,129 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>SKYNET Voice Authentication</title>
7
+ <style>
8
+ body, html {
9
+ height: 100%;
10
+ margin: 0;
11
+ font-family: Arial, sans-serif;
12
+ color: white;
13
+ text-align: center;
14
+ }
15
+ .bg {
16
+ background-image: url('/static/skynet.png');
17
+ height: 100%;
18
+ background-position: center;
19
+ background-repeat: no-repeat;
20
+ background-size: cover;
21
+ position: relative;
22
+ }
23
+ .content {
24
+ position: absolute;
25
+ bottom: 10%;
26
+ width: 100%;
27
+ }
28
+ .controls {
29
+ display: flex;
30
+ justify-content: center;
31
+ align-items: center;
32
+ }
33
+ .button {
34
+ background-color: red;
35
+ color: white;
36
+ padding: 10px 20px;
37
+ border: none;
38
+ border-radius: 5px;
39
+ font-size: 16px;
40
+ cursor: pointer;
41
+ margin: 5px;
42
+ }
43
+ .button:hover {
44
+ background-color: darkred;
45
+ }
46
+ audio {
47
+ margin: 0 20px;
48
+ }
49
+ </style>
50
+ </head>
51
+ <body>
52
+
53
+ <div class="bg">
54
+ <div class="content">
55
+ <h1>Voice Authentication</h1>
56
+ <div class="controls">
57
+ <button id="recordButton" class="button">Record</button>
58
+ <button id="stopButton" class="button" disabled>Stop</button>
59
+ <audio id="audioPlayback" controls style="display:none;"></audio>
60
+ <button id="submitButton" class="button" style="float:right; display:none;">Submit</button>
61
+ </div>
62
+ <p id="result"></p>
63
+ </div>
64
+ </div>
65
+
66
+ <script>
67
+ let recordButton = document.getElementById('recordButton');
68
+ let stopButton = document.getElementById('stopButton');
69
+ let audioPlayback = document.getElementById('audioPlayback');
70
+ let submitButton = document.getElementById('submitButton');
71
+
72
+ let mediaRecorder;
73
+ let audioBlob;
74
+ let audioChunks = [];
75
+
76
+ recordButton.onclick = function() {
77
+ navigator.mediaDevices.getUserMedia({ audio: true })
78
+ .then(stream => {
79
+ mediaRecorder = new MediaRecorder(stream);
80
+ mediaRecorder.start();
81
+ audioChunks = [];
82
+
83
+ document.getElementById("result").innerHTML = "recording started";
84
+ stopButton.disabled = false;
85
+ recordButton.disabled = true;
86
+
87
+ mediaRecorder.ondataavailable = function(event) {
88
+ audioChunks.push(event.data);
89
+ };
90
+
91
+ mediaRecorder.onstop = function() {
92
+ document.getElementById("result").innerHTML = "recording stopped";
93
+ audioBlob = new Blob(audioChunks);
94
+ let audioUrl = URL.createObjectURL(audioBlob);
95
+ audioPlayback.src = audioUrl;
96
+ audioPlayback.style.display = 'block';
97
+ };
98
+ });
99
+ };
100
+
101
+ stopButton.onclick = function() {
102
+ mediaRecorder.stop();
103
+ stopButton.disabled = true;
104
+ recordButton.disabled = false;
105
+ submitButton.style.display = 'block';
106
+ };
107
+
108
+ // Submit button handler
109
+ submitButton.onclick = async function() {
110
+ document.getElementById("result").innerHTML = "Processing..."
111
+ //await new Promise(r => setTimeout(r, 2000));
112
+ console.log('Submit the audio for processing');
113
+ let formData = new FormData();
114
+ formData.append('audio_data', audioBlob);
115
+ fetch('/compare_audio', {
116
+ method: 'POST',
117
+ body: formData
118
+ })
119
+ .then(response => response.json())
120
+ .then(data => {
121
+ document.getElementById("result").innerHTML = data.result;
122
+ })
123
+ .catch(error => console.error('Error:', error));
124
+ };
125
+ </script>
126
+
127
+ </body>
128
+ </html>
129
+