version k-means qui marche!!
This commit is contained in:
1
final-loop/spotify-codes-part-2/.cache
Normal file
1
final-loop/spotify-codes-part-2/.cache
Normal file
@@ -0,0 +1 @@
|
||||
{"access_token": "BQAHRFXWQMMKWNbUO3sfhk92iUGi5VJNZmB2lTjRim-g2AIe88mgDvyc1NywjCVBTeZK-b3PkXzGDANJDDOouitEt5P8z0jpVuPrpalwAg0PzEwtKBXr8PHvacajO9B3s7Wy0s7G3CFlfGfbOfmXtW_vF8G2_ooGnlFp_oM8RcOyBl7D5Okf6kr07CWIJty4mNeTf1Ifd212u1tBnbU64lckqlQ0f0W105mt", "token_type": "Bearer", "expires_in": 3600, "refresh_token": "AQCSe9ARCDB-Q6gzna_KKAlcEuNOx2IC9BSVXS5GZxNDi4AjJdA4Kn3hp1YPfiPfogd2rz-5zLbyjUxiRj9oTd-q5EItEPZHIVt-eMf7gCGHfj6h-hLpRrnhgMZjzRAS7CY", "scope": "user-modify-playback-state user-read-playback-state", "expires_at": 1757966002}
|
||||
21
final-loop/spotify-codes-part-2/requirements.txt
Normal file
21
final-loop/spotify-codes-part-2/requirements.txt
Normal file
@@ -0,0 +1,21 @@
|
||||
certifi>=2023.5.7
|
||||
chardet>=5.1.0
|
||||
crccheck>=1.0
|
||||
cycler>=0.11.0
|
||||
decorator>=5.1.1
|
||||
idna>=3.5
|
||||
imageio>=2.31.1
|
||||
kiwisolver>=1.4.4
|
||||
matplotlib>=3.7.1
|
||||
networkx>=3.1
|
||||
numpy>=1.24.2
|
||||
Pillow>=10.0.0
|
||||
pyparsing>=3.0.9
|
||||
python-dateutil>=2.8.2
|
||||
PyWavelets>=1.4.1
|
||||
requests>=2.31.0
|
||||
scikit-image>=0.25.2
|
||||
scipy>=1.11.1
|
||||
six>=1.16.0
|
||||
tifffile>=2023.7.23
|
||||
urllib3>=2.0.4
|
||||
31
final-loop/spotify-codes-part-2/src/crc.py
Normal file
31
final-loop/spotify-codes-part-2/src/crc.py
Normal file
@@ -0,0 +1,31 @@
|
||||
|
||||
def crc(data, polynomial):
|
||||
n = len(polynomial) - 1
|
||||
initial_length = len(data)
|
||||
check_bits = data + [0] * n
|
||||
for i in range(initial_length):
|
||||
if check_bits[i] == 1:
|
||||
for j, p in enumerate(polynomial):
|
||||
check_bits[i + j] = check_bits[i + j] ^ p
|
||||
return check_bits[-n:]
|
||||
|
||||
def check_crc(data, polynomial, check_bits):
|
||||
full_data = data + check_bits
|
||||
for i in range(len(data)):
|
||||
if full_data[i] == 1:
|
||||
for j, p in enumerate(polynomial):
|
||||
full_data[i + j] = full_data[i + j] ^ p
|
||||
return 1 not in full_data
|
||||
data = [0,0,1,0,0,0,1,0,1,1,1,1,0,1,1,1,1,0,0,0,1,1,1,1,0,1,1,0,1,0,1,1,0,0,0,0,1,1,0,1]
|
||||
check = [1,1,0,0,1,1,0,0]
|
||||
poly = [1,0,0,0,0,0,1,1,1]
|
||||
print(check_crc(data, poly, check))
|
||||
|
||||
if __name__ == "__main__":
|
||||
example = "0010001011110111100011110110101100001101"
|
||||
long = [int(i) for i in example]
|
||||
polynomial = [1, 0, 0, 0, 0, 0, 1, 1, 1] # crc8 polynomial
|
||||
check = crc(long, polynomial)
|
||||
print(f"Check bits: {check}")
|
||||
checked = check_crc(long, polynomial, check)
|
||||
print(f"Checked: {checked}")
|
||||
57
final-loop/spotify-codes-part-2/src/decode_barcode.py
Normal file
57
final-loop/spotify-codes-part-2/src/decode_barcode.py
Normal file
@@ -0,0 +1,57 @@
|
||||
import argparse
|
||||
import pprint
|
||||
from get_heights import get_heights
|
||||
from encode_decode import spotify_bar_decode
|
||||
from get_uri import get_uri, get_info
|
||||
import cv2
|
||||
|
||||
|
||||
def process_frame(frame, token):
|
||||
heights = get_heights(frame)
|
||||
print(len(heights)) # assumes get_heights can take image array
|
||||
if len(heights) != 23:
|
||||
return None # skip bad frames
|
||||
else:
|
||||
print("ON TROUVE UN CODE")
|
||||
|
||||
heights = heights[1:11] + heights[12:-1]
|
||||
decoded = spotify_bar_decode(heights)
|
||||
uri = get_uri(decoded, token)
|
||||
summary, full_response = get_info(uri["target"], token)
|
||||
return summary
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--token", required=True)
|
||||
args = parser.parse_args()
|
||||
token = args.token
|
||||
|
||||
cap = cv2.VideoCapture(0) # 0 for default webcam
|
||||
|
||||
while True:
|
||||
ret, frame = cap.read()
|
||||
if not ret:
|
||||
break
|
||||
|
||||
# Définir les dimensions et la marge du cadre
|
||||
height, width, _ = frame.shape
|
||||
margin = 100 # ajustez la taille du cadre ici
|
||||
frame = frame[margin:height - margin, margin:width - margin]
|
||||
|
||||
try:
|
||||
summary = process_frame(frame, token)
|
||||
except Exception:
|
||||
continue
|
||||
|
||||
if summary:
|
||||
print("Summary:")
|
||||
pprint.pprint(summary)
|
||||
|
||||
cv2.imshow("Live Barcode", frame)
|
||||
|
||||
if cv2.waitKey(1) & 0xFF == ord('q'):
|
||||
break
|
||||
|
||||
cap.release()
|
||||
cv2.destroyAllWindows()
|
||||
118
final-loop/spotify-codes-part-2/src/encode_decode.py
Normal file
118
final-loop/spotify-codes-part-2/src/encode_decode.py
Normal file
@@ -0,0 +1,118 @@
|
||||
import numpy as np
|
||||
import crccheck
|
||||
|
||||
# This code was written by "Doyle" on Stack Overflow
|
||||
# https://stackoverflow.com/a/64950150/10703868
|
||||
|
||||
|
||||
# Utils for conversion between int, array of binary
|
||||
# and array of bytes (as ints)
|
||||
def int_to_bin(num, length, endian):
|
||||
if endian == 'l':
|
||||
return [num >> i & 1 for i in range(0, length)]
|
||||
elif endian == 'b':
|
||||
return [num >> i & 1 for i in range(length-1, -1, -1)]
|
||||
|
||||
def bin_to_int(bin,length):
|
||||
return int("".join([str(bin[i]) for i in range(length-1,-1,-1)]),2)
|
||||
|
||||
def bin_to_bytes(bin, length):
|
||||
b = bin[0:length] + [0] * (-length % 8)
|
||||
return [(b[i]<<7) + (b[i+1]<<6) + (b[i+2]<<5) + (b[i+3]<<4) +
|
||||
(b[i+4]<<3) + (b[i+5]<<2) + (b[i+6]<<1) + b[i+7] for i in range(0,len(b),8)]
|
||||
|
||||
# Return the circular right shift of an array by 'n' positions
|
||||
def shift_right(arr, n):
|
||||
return arr[-n % len(arr):len(arr):] + arr[0:-n % len(arr)]
|
||||
|
||||
gray_code = [0,1,3,2,7,6,4,5]
|
||||
gray_code_inv = [[0,0,0],[0,0,1],[0,1,1],[0,1,0],
|
||||
[1,1,0],[1,1,1],[1,0,1],[1,0,0]]
|
||||
|
||||
# CRC using Rocksoft model:
|
||||
# NOTE: this is not quite any of their predefined CRC's
|
||||
# 8: number of check bits (degree of poly)
|
||||
# 0x7: representation of poly without high term (x^8+x^2+x+1)
|
||||
# 0x0: initial fill of register
|
||||
# True: byte reverse data
|
||||
# True: byte reverse check
|
||||
# 0xff: Mask check (i.e. invert)
|
||||
spotify_crc = crccheck.crc.Crc(8, 0x7, 0x0, True, True, 0xff)
|
||||
|
||||
def calc_spotify_crc(bin37):
|
||||
bytes = bin_to_bytes(bin37, 37)
|
||||
return int_to_bin(spotify_crc.calc(bytes), 8, 'b')
|
||||
|
||||
def check_spotify_crc(bin45):
|
||||
data = bin_to_bytes(bin45,37)
|
||||
return spotify_crc.calc(data) == bin_to_bytes(bin45[37:], 8)[0]
|
||||
|
||||
# Simple convolutional encoder
|
||||
def encode_cc(dat):
|
||||
gen1 = [1,0,1,1,0,1,1]
|
||||
gen2 = [1,1,1,1,0,0,1]
|
||||
punct = [1,1,0]
|
||||
dat_pad = dat[-6:] + dat # 6 bits are needed to initialize
|
||||
# register for tail-biting
|
||||
stream1 = np.convolve(dat_pad, gen1, mode='valid') % 2
|
||||
stream2 = np.convolve(dat_pad, gen2, mode='valid') % 2
|
||||
enc = [val for pair in zip(stream1, stream2) for val in pair]
|
||||
return [enc[i] for i in range(len(enc)) if punct[i % 3]]
|
||||
|
||||
# To create a generator matrix for a code, we encode each row
|
||||
# of the identity matrix. Note that the CRC is not quite linear
|
||||
# because of the check mask so we apply the lamda function to
|
||||
# invert it. Given a 37 bit media reference we can encode by
|
||||
# ref * spotify_generator + spotify_mask (mod 2)
|
||||
_i37 = np.identity(37, dtype=bool)
|
||||
crc_generator = [_i37[r].tolist() +
|
||||
list(map(lambda x : 1-x, calc_spotify_crc(_i37[r].tolist())))
|
||||
for r in range(37)]
|
||||
spotify_generator = 1*np.array([encode_cc(crc_generator[r]) for r in range(37)], dtype=bool)
|
||||
del _i37
|
||||
|
||||
spotify_mask = 1*np.array(encode_cc(37*[0] + 8*[1]), dtype=bool)
|
||||
|
||||
# The following matrix is used to "invert" the convolutional code.
|
||||
# In particular, we choose a 45 vector basis for the columns of the
|
||||
# generator matrix (by deleting those in positions equal to 2 mod 4)
|
||||
# and then inverting the matrix. By selecting the corresponding 45
|
||||
# elements of the convolutionally encoded vector and multiplying
|
||||
# on the right by this matrix, we get back to the unencoded data,
|
||||
# assuming there are no errors.
|
||||
# Note: numpy does not invert binary matrices, i.e. GF(2), so we
|
||||
# hard code the following 3 row vectors to generate the matrix.
|
||||
conv_gen = [[0,1,0,1,1,1,1,0,1,1,0,0,0,1]+31*[0],
|
||||
[1,0,1,0,1,0,1,0,0,0,1,1,1] + 32*[0],
|
||||
[0,0,1,0,1,1,1,1,1,1,0,0,1] + 32*[0] ]
|
||||
|
||||
conv_generator_inv = 1*np.array([shift_right(conv_gen[(s-27) % 3],s) for s in range(27,72)], dtype=bool)
|
||||
|
||||
|
||||
# Given an integer media reference, returns list of 20 barcode levels
|
||||
def spotify_bar_code(ref):
|
||||
bin37 = np.array([int_to_bin(ref, 37, 'l')], dtype=bool)
|
||||
enc = (np.add(1*np.dot(bin37, spotify_generator), spotify_mask) % 2).flatten()
|
||||
perm = [enc[7*i % 60] for i in range(60)]
|
||||
return [gray_code[4*perm[i]+2*perm[i+1]+perm[i+2]] for i in range(0,len(perm),3)]
|
||||
|
||||
# Equivalent function but using CRC and CC encoders.
|
||||
def spotify_bar_code2(ref):
|
||||
bin37 = int_to_bin(ref, 37, 'l')
|
||||
enc_crc = bin37 + calc_spotify_crc(bin37)
|
||||
enc_cc = encode_cc(enc_crc)
|
||||
perm = [enc_cc[7*i % 60] for i in range(60)]
|
||||
return [gray_code[4*perm[i]+2*perm[i+1]+perm[i+2]] for i in range(0,len(perm),3)]
|
||||
|
||||
# Given 20 (clean) barcode levels, returns media reference
|
||||
def spotify_bar_decode(levels):
|
||||
level_bits = np.array([gray_code_inv[levels[i]] for i in range(20)], dtype=bool).flatten()
|
||||
conv_bits = [level_bits[43*i % 60] for i in range(60)]
|
||||
cols = [i for i in range(60) if i % 4 != 2] # columns to invert
|
||||
conv_bits45 = np.array([conv_bits[c] for c in cols], dtype=bool)
|
||||
bin45 = (1*np.dot(conv_bits45, conv_generator_inv) % 2).tolist()
|
||||
if check_spotify_crc(bin45):
|
||||
return bin_to_int(bin45, 37)
|
||||
else:
|
||||
print('Error in levels; Use real decoder!!!')
|
||||
return -1
|
||||
48
final-loop/spotify-codes-part-2/src/get_heights.py
Normal file
48
final-loop/spotify-codes-part-2/src/get_heights.py
Normal file
@@ -0,0 +1,48 @@
|
||||
import numpy as np
|
||||
import matplotlib.pyplot as plt
|
||||
from skimage import io
|
||||
from skimage.filters import threshold_otsu, gaussian
|
||||
from skimage.morphology import closing, square
|
||||
from skimage.measure import label, regionprops
|
||||
from skimage.color import rgb2gray
|
||||
import matplotlib.patches as patches
|
||||
from sklearn.cluster import KMeans
|
||||
import warnings
|
||||
|
||||
warnings.filterwarnings("ignore", category=FutureWarning, message=".*square.*deprecated.*")
|
||||
|
||||
|
||||
def get_heights(image):
|
||||
# image = io.imread(filename)
|
||||
im = rgb2gray(image)
|
||||
binary_im = im > threshold_otsu(im)
|
||||
smooth_im = gaussian(binary_im.astype(float), sigma=1)
|
||||
morph_im = closing(smooth_im > 0.5, square(3))
|
||||
labeled = label(morph_im)
|
||||
bar_dims = [r.bbox for r in regionprops(labeled)]
|
||||
bar_dims.sort(key=lambda x: x[1]) # left to right
|
||||
|
||||
bars = bar_dims[1:] # skip logo
|
||||
bar_heights_raw = []
|
||||
if(len(bars)!=23):
|
||||
return [0]
|
||||
for bar in bars:
|
||||
top, left, bottom, right = bar
|
||||
effective_height = bottom - top # use bounding box height directly
|
||||
bar_heights_raw.append(effective_height)
|
||||
|
||||
# Cluster measured heights to 8 clusters representing discrete bar levels
|
||||
bar_heights_raw_np = np.array(bar_heights_raw).reshape(-1, 1)
|
||||
|
||||
kmeans = KMeans(n_clusters=8, random_state=0).fit(bar_heights_raw_np)
|
||||
cluster_centers = np.sort(kmeans.cluster_centers_.flatten())
|
||||
|
||||
# Assign each bar height to closest cluster center index (0 to 7)
|
||||
predicted_levels = []
|
||||
for h in bar_heights_raw:
|
||||
diffs = np.abs(cluster_centers - h)
|
||||
closest_cluster = np.argmin(diffs)
|
||||
predicted_levels.append(int(closest_cluster))
|
||||
|
||||
|
||||
return predicted_levels
|
||||
56
final-loop/spotify-codes-part-2/src/get_uri.py
Normal file
56
final-loop/spotify-codes-part-2/src/get_uri.py
Normal file
@@ -0,0 +1,56 @@
|
||||
from typing import Tuple
|
||||
import requests
|
||||
|
||||
HEADERS_LUT = {
|
||||
"X-Client-Id": "58bd3c95768941ea9eb4350aaa033eb3",
|
||||
"Accept-Encoding": "gzip, deflate",
|
||||
"Connection": "close",
|
||||
"App-Platform": "iOS",
|
||||
"Accept": "*/*",
|
||||
"User-Agent": "Spotify/8.5.68 iOS/13.4 (iPhone9,3)",
|
||||
"Accept-Language": "en",
|
||||
"Spotify-App-Version": "8.5.68",
|
||||
}
|
||||
MEDIA_REF_LUT_URL = "https://spclient.wg.spotify.com:443/scannable-id/id"
|
||||
|
||||
def get_uri(media_ref: int, token: str):
|
||||
"""Query Spotify internal API to get the URI of the media reference."""
|
||||
header = {
|
||||
**HEADERS_LUT,
|
||||
"Authorization": f"Bearer {token}"
|
||||
}
|
||||
url = f'{MEDIA_REF_LUT_URL}/{media_ref}?format=json'
|
||||
response = requests.get(url, headers=header)
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
|
||||
def get_info(uri: str, token: str) -> Tuple[dict, dict]:
|
||||
"""Query the Spotify API to get information about a URI."""
|
||||
info_headers = {
|
||||
"Content-Type": "application/json",
|
||||
"Accept": "application/json",
|
||||
"Authorization": f"Bearer {token}"
|
||||
}
|
||||
split = uri.split(":")
|
||||
content_type = split[-2] + "s"
|
||||
id = split[-1]
|
||||
response = requests.get(f"https://api.spotify.com/v1/{content_type}/{id}", headers=info_headers)
|
||||
response.raise_for_status()
|
||||
resp = response.json()
|
||||
result = {
|
||||
"name": resp["name"],
|
||||
"type": split[-2],
|
||||
"url": resp["external_urls"]["spotify"],
|
||||
}
|
||||
if "artists" in resp:
|
||||
result['artists'] = []
|
||||
for a in resp['artists']:
|
||||
result["artists"].append(a["name"])
|
||||
if "album" in resp:
|
||||
result['album'] = resp['album']['name']
|
||||
|
||||
if "description" in resp:
|
||||
result["description"] = resp['description']
|
||||
|
||||
return result, resp
|
||||
|
||||
11
final-loop/spotify-codes-part-2/src/permute.py
Normal file
11
final-loop/spotify-codes-part-2/src/permute.py
Normal file
@@ -0,0 +1,11 @@
|
||||
|
||||
|
||||
|
||||
def permute(bits, step=7):
|
||||
for i in range(len(bits)):
|
||||
yield bits[(i * step) % len(bits)]
|
||||
|
||||
if __name__ == "__main__":
|
||||
bits = "111000111100101111101110111001011100110000100100011100110011"
|
||||
print("".join(permute(bits)))
|
||||
# 111100110001110101101000011110010110101100111111101000111000
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
@@ -0,0 +1,35 @@
|
||||
from typing import List
|
||||
|
||||
|
||||
def encode(bits: List[int], polynomial: List[int], tail_bite=False):
|
||||
"""Convolutionally encode the stream of bits using the generator polynomial.
|
||||
If tail_bite == True, prepend the tail of the input. Otherwise use 0s to fill.
|
||||
"""
|
||||
if tail_bite:
|
||||
tail = bits[-(len(polynomial) - 1):]
|
||||
else:
|
||||
tail = [0 for i in range(len(polynomial) - 1)]
|
||||
full = tail + bits
|
||||
polynomial.reverse() # Reverse since we're working the other direction
|
||||
parity_bits = []
|
||||
for i in range(len(bits)):
|
||||
parity = 0
|
||||
for j in range(len(polynomial)):
|
||||
parity ^= full[i + j] * polynomial[j]
|
||||
parity_bits.append(parity)
|
||||
return parity_bits
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
g0 = [1, 0, 1, 1, 0, 1, 1]
|
||||
g1 = [1, 1, 1, 1, 0, 0, 1]
|
||||
bits = "010001001110111111110001110101101011011001100"
|
||||
g0_expected = "100011100111110100110011110100000010001001011"
|
||||
g1_expected = "110011100010110110110100101101011100110011011"
|
||||
bits = [int(i) for i in bits]
|
||||
|
||||
p0 = encode(bits, g0, True)
|
||||
p1 = encode(bits, g1, True)
|
||||
|
||||
print(g0_expected == "".join(str(i) for i in p0))
|
||||
print(g1_expected == "".join(str(i) for i in p1))
|
||||
15
final-loop/spotify-codes-part-2/src/step_by_step.py
Normal file
15
final-loop/spotify-codes-part-2/src/step_by_step.py
Normal file
@@ -0,0 +1,15 @@
|
||||
|
||||
|
||||
|
||||
|
||||
media_ref = 57639171874
|
||||
binary = f"{bin(media_ref)[2:]:0>37}"
|
||||
print(binary)
|
||||
# pad with 3 bits to the right:
|
||||
binary = f"{binary:0<39}"
|
||||
print(binary)
|
||||
|
||||
a = "100011100111110100110011110100000010001001011"
|
||||
b = "110011100010110110110100101101011100110011011"
|
||||
c = zip(a, b)
|
||||
print("".join(i + j for i, j in c))
|
||||
Reference in New Issue
Block a user