Compare commits

...

4 Commits

Author SHA1 Message Date
17f51e757a POC 2025-12-06 13:21:03 +01:00
4c486199ed travail d'un we (j'en ai big marre) 2025-11-23 23:39:43 +01:00
1ff888cf34 version k-means qui marche!! 2025-11-21 14:31:37 +01:00
b77383681e enlever fichier nul 2025-11-18 12:17:39 +01:00
96 changed files with 668 additions and 3207 deletions

1
.cache
View File

@@ -1 +0,0 @@
{"access_token": "BQDeVV_yylewo72n9ZtSMTn1WNhJKSbUBDKv18Cl-QY7dphidwC9jDuGuZWKaIWZv-fVIG1uuP1XvJFKwcj3GOxgRxYJbaWOLtogCHkAxC-iOaZe6uphn-vCy5rbX6Xlx9XVUG-jHNu0jaCf1-ueTRUQuyF0Btu4_aAcIktTnYyofSUJuk_Mdi1J6V3hmARkCl34Rv-4hgfXcIqQ_rpdCFHGheEU1rSL5oME", "token_type": "Bearer", "expires_in": 3600, "scope": "user-modify-playback-state user-read-playback-state", "expires_at": 1757966041, "refresh_token": "AQAxD3wm_8aDlK9w2ZsibKsrPY0SxUJb2UaqiT2ygxK-pEq2ojxPzgLa6DNt9EpP3mjFDCLmGO5glj5fSpR3ZnqEa26belG4EQv5FOeee38ix31o2xuxPbDsY44KR5Uj1KE"}

View File

@@ -1,2 +0,0 @@
__pycache__
.venv

View File

@@ -1,12 +0,0 @@
# Code snippets
This repo contains the code snippets I reference in [my blog](https://boonepeter.github.io/).
## Index
Code|Blog Post|
---|---|
[spotify-codes](./spotify-codes)|[How do Spotify Codes Work?](https://boonepeter.github.io/posts/2020-11-10-spotify-codes/)|
[spotify-codes-part-2](./spotify-codes-part-2)|[Spotify Codes Part 2](https://boonepeter.github.io/posts/spotify-codes-part-2/)
[snapcodes](./snapcodes)|No post yet

View File

@@ -1,37 +0,0 @@
# Snapcodes
Using Python 3.10.1
```bash
pip3 install -r requirements.txt
```
Running this command
```bash
python3 img_to_points.py snapcode.png
```
Gets the points from this image:
![](./src/snapcode.png)
Points:
```python
[1, 2, 3, 4, 6, 9, 14, 18, 20, 21, 22, 23, 27, 28, 29, 30, 32, 38, 39, 41, 43, 44, 45, 49, 53, 58, 59, 62, 64, 65, 66, 67, 70, 71, 73, 76, 77, 83, 87, 90, 93, 94, 96, 99, 100, 101, 103, 104, 105, 106, 107, 108, 109, 110, 112, 113, 115, 118, 119, 120, 130, 131, 134, 140, 141, 144, 145, 146, 150, 153, 155, 157, 159, 161, 163, 169, 170, 171, 172, 173, 174, 175, 178, 182, 183, 184, 185, 188, 190, 192, 193, 196, 197, 202, 204, 206, 207, 209]
```
Points are numbered left to right, top to bottom, starting with 0 and ending at 211.
And this command
```bash
python3 img_to_svg.py -i snapcode.png -o output.svg
```
Outputs an SVG file:
![](./src/output.svg)
The svg is useful for checking that the point parsing was correct.

View File

@@ -1,10 +0,0 @@
imageio==2.13.5
networkx==2.6.3
numpy==1.22.1
packaging==21.3
Pillow==9.0.0
pyparsing==3.0.6
PyWavelets==1.2.0
scikit-image==0.19.1
scipy==1.7.3
tifffile==2021.11.2

View File

@@ -1,2 +0,0 @@
/imgs
.ipynb_checkpoints/

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Binary file not shown.

Before

Width:  |  Height:  |  Size: 200 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 106 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 106 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 119 KiB

View File

@@ -1,305 +0,0 @@
import argparse
from skimage import io
import numpy as np
from skimage import io, img_as_ubyte
from skimage.filters import threshold_otsu
from skimage.color import rgb2gray, rgba2rgb
from skimage.measure import label, regionprops
## BBOX of points from all_dots_yellow.png
ALL_BBOX = [
[0.025, 0.076, 0.0583, 0.1094],
[0.025, 0.1271, 0.0583, 0.1604],
[0.025, 0.1781, 0.0583, 0.2115],
[0.025, 0.2292, 0.0583, 0.2625],
[0.025, 0.2802, 0.0583, 0.3135],
[0.025, 0.3302, 0.0583, 0.3635],
[0.025, 0.3812, 0.0583, 0.4146],
[0.025, 0.4323, 0.0583, 0.4656],
[0.025, 0.4833, 0.0583, 0.5167],
[0.025, 0.5344, 0.0583, 0.5677],
[0.025, 0.5854, 0.0583, 0.6188],
[0.025, 0.6365, 0.0583, 0.6698],
[0.025, 0.6865, 0.0583, 0.7198],
[0.025, 0.7375, 0.0583, 0.7708],
[0.025, 0.7885, 0.0583, 0.8219],
[0.025, 0.8396, 0.0583, 0.8729],
[0.025, 0.8906, 0.0583, 0.924],
[0.076, 0.025, 0.1094, 0.0583],
[0.076, 0.076, 0.1094, 0.1094],
[0.076, 0.1271, 0.1094, 0.1604],
[0.076, 0.1781, 0.1094, 0.2115],
[0.076, 0.2292, 0.1094, 0.2625],
[0.076, 0.2802, 0.1094, 0.3135],
[0.076, 0.3302, 0.1094, 0.3635],
[0.076, 0.3812, 0.1094, 0.4146],
[0.076, 0.4323, 0.1094, 0.4656],
[0.076, 0.4833, 0.1094, 0.5167],
[0.076, 0.5344, 0.1094, 0.5677],
[0.076, 0.5854, 0.1094, 0.6188],
[0.076, 0.6365, 0.1094, 0.6698],
[0.076, 0.6865, 0.1094, 0.7198],
[0.076, 0.7375, 0.1094, 0.7708],
[0.076, 0.7885, 0.1094, 0.8219],
[0.076, 0.8396, 0.1094, 0.8729],
[0.076, 0.8906, 0.1094, 0.924],
[0.076, 0.9417, 0.1094, 0.975],
[0.1271, 0.025, 0.1604, 0.0583],
[0.1271, 0.076, 0.1604, 0.1094],
[0.1271, 0.1271, 0.1604, 0.1604],
[0.1271, 0.1781, 0.1604, 0.2115],
[0.1271, 0.2292, 0.1604, 0.2625],
[0.1271, 0.2802, 0.1604, 0.3135],
[0.1271, 0.3312, 0.1604, 0.3635],
[0.1271, 0.3812, 0.1604, 0.4146],
[0.1271, 0.4323, 0.1604, 0.4656],
[0.1271, 0.4833, 0.1604, 0.5167],
[0.1271, 0.5344, 0.1604, 0.5677],
[0.1271, 0.5854, 0.1604, 0.6188],
[0.1271, 0.6365, 0.1604, 0.6698],
[0.1271, 0.6865, 0.1604, 0.7198],
[0.1271, 0.7375, 0.1604, 0.7708],
[0.1271, 0.7885, 0.1604, 0.8219],
[0.1271, 0.8396, 0.1604, 0.8729],
[0.1271, 0.8906, 0.1604, 0.924],
[0.1271, 0.9417, 0.1604, 0.975],
[0.1781, 0.025, 0.2115, 0.0583],
[0.1781, 0.076, 0.2115, 0.1094],
[0.1781, 0.1271, 0.2115, 0.1604],
[0.1781, 0.1781, 0.2115, 0.2115],
[0.1781, 0.2292, 0.2115, 0.2625],
[0.1781, 0.2802, 0.2115, 0.3135],
[0.1781, 0.6865, 0.2115, 0.7198],
[0.1781, 0.7375, 0.2115, 0.7708],
[0.1781, 0.7885, 0.2115, 0.8219],
[0.1781, 0.8396, 0.2115, 0.8729],
[0.1781, 0.8906, 0.2115, 0.924],
[0.1781, 0.9417, 0.2115, 0.975],
[0.2292, 0.025, 0.2625, 0.0583],
[0.2292, 0.076, 0.2625, 0.1094],
[0.2292, 0.1271, 0.2625, 0.1604],
[0.2292, 0.1781, 0.2625, 0.2115],
[0.2292, 0.2292, 0.2625, 0.2625],
[0.2292, 0.7375, 0.2625, 0.7708],
[0.2292, 0.7885, 0.2625, 0.8219],
[0.2292, 0.8396, 0.2625, 0.8729],
[0.2292, 0.8906, 0.2625, 0.924],
[0.2292, 0.9417, 0.2625, 0.975],
[0.2802, 0.025, 0.3135, 0.0583],
[0.2802, 0.076, 0.3135, 0.1094],
[0.2802, 0.1271, 0.3135, 0.1604],
[0.2802, 0.1781, 0.3135, 0.2115],
[0.2802, 0.7885, 0.3135, 0.8219],
[0.2802, 0.8396, 0.3135, 0.8729],
[0.2802, 0.8906, 0.3135, 0.924],
[0.2802, 0.9417, 0.3135, 0.975],
[0.3302, 0.025, 0.3635, 0.0583],
[0.3302, 0.076, 0.3635, 0.1094],
[0.3302, 0.1271, 0.3635, 0.1604],
[0.3302, 0.8396, 0.3635, 0.8729],
[0.3302, 0.8906, 0.3635, 0.924],
[0.3302, 0.9417, 0.3635, 0.975],
[0.3812, 0.025, 0.4146, 0.0583],
[0.3812, 0.076, 0.4146, 0.1094],
[0.3812, 0.1271, 0.4146, 0.1604],
[0.3812, 0.8396, 0.4146, 0.8729],
[0.3812, 0.8906, 0.4146, 0.924],
[0.3812, 0.9417, 0.4146, 0.975],
[0.4323, 0.025, 0.4656, 0.0583],
[0.4323, 0.076, 0.4656, 0.1094],
[0.4323, 0.1271, 0.4656, 0.1604],
[0.4323, 0.8396, 0.4656, 0.8729],
[0.4323, 0.8906, 0.4656, 0.924],
[0.4323, 0.9417, 0.4656, 0.975],
[0.4833, 0.025, 0.5167, 0.0583],
[0.4833, 0.076, 0.5167, 0.1094],
[0.4833, 0.1271, 0.5167, 0.1604],
[0.4833, 0.8396, 0.5167, 0.8729],
[0.4833, 0.8906, 0.5167, 0.924],
[0.4833, 0.9417, 0.5167, 0.975],
[0.5344, 0.025, 0.5677, 0.0583],
[0.5344, 0.076, 0.5677, 0.1094],
[0.5344, 0.1271, 0.5677, 0.1604],
[0.5344, 0.8396, 0.5677, 0.8729],
[0.5344, 0.8906, 0.5677, 0.924],
[0.5344, 0.9417, 0.5677, 0.975],
[0.5854, 0.025, 0.6188, 0.0583],
[0.5854, 0.076, 0.6188, 0.1094],
[0.5854, 0.1271, 0.6188, 0.1604],
[0.5854, 0.8396, 0.6188, 0.8729],
[0.5854, 0.8906, 0.6188, 0.924],
[0.5854, 0.9417, 0.6188, 0.975],
[0.6365, 0.025, 0.6698, 0.0583],
[0.6365, 0.076, 0.6698, 0.1094],
[0.6365, 0.1271, 0.6698, 0.1604],
[0.6365, 0.8396, 0.6698, 0.8729],
[0.6365, 0.8906, 0.6698, 0.924],
[0.6365, 0.9417, 0.6698, 0.975],
[0.6865, 0.025, 0.7198, 0.0583],
[0.6865, 0.076, 0.7198, 0.1094],
[0.6865, 0.1271, 0.7198, 0.1604],
[0.6865, 0.1781, 0.7198, 0.2115],
[0.6865, 0.7885, 0.7198, 0.8219],
[0.6865, 0.8396, 0.7198, 0.8729],
[0.6865, 0.8906, 0.7198, 0.924],
[0.6865, 0.9417, 0.7198, 0.975],
[0.7375, 0.025, 0.7708, 0.0583],
[0.7375, 0.076, 0.7708, 0.1094],
[0.7375, 0.1271, 0.7708, 0.1604],
[0.7375, 0.1781, 0.7708, 0.2115],
[0.7375, 0.2292, 0.7708, 0.2625],
[0.7375, 0.7375, 0.7708, 0.7708],
[0.7375, 0.7885, 0.7708, 0.8219],
[0.7375, 0.8396, 0.7708, 0.8729],
[0.7375, 0.8906, 0.7708, 0.924],
[0.7375, 0.9417, 0.7708, 0.975],
[0.7885, 0.025, 0.8219, 0.0583],
[0.7885, 0.076, 0.8219, 0.1094],
[0.7885, 0.1271, 0.8219, 0.1604],
[0.7885, 0.1781, 0.8219, 0.2115],
[0.7885, 0.2292, 0.8219, 0.2625],
[0.7885, 0.2802, 0.8219, 0.3135],
[0.7885, 0.6865, 0.8219, 0.7198],
[0.7885, 0.7375, 0.8219, 0.7708],
[0.7885, 0.7885, 0.8219, 0.8219],
[0.7885, 0.8396, 0.8219, 0.8729],
[0.7885, 0.8906, 0.8219, 0.924],
[0.7885, 0.9417, 0.8219, 0.975],
[0.8396, 0.025, 0.8729, 0.0583],
[0.8396, 0.076, 0.8729, 0.1094],
[0.8396, 0.1271, 0.8729, 0.1604],
[0.8396, 0.1781, 0.8729, 0.2115],
[0.8396, 0.2292, 0.8729, 0.2625],
[0.8396, 0.2802, 0.8729, 0.3135],
[0.8396, 0.3312, 0.8729, 0.3635],
[0.8396, 0.3812, 0.8729, 0.4146],
[0.8396, 0.4323, 0.8729, 0.4656],
[0.8396, 0.4833, 0.8729, 0.5167],
[0.8396, 0.5344, 0.8729, 0.5677],
[0.8396, 0.5854, 0.8729, 0.6188],
[0.8396, 0.6365, 0.8729, 0.6698],
[0.8396, 0.6865, 0.8729, 0.7198],
[0.8396, 0.7375, 0.8729, 0.7708],
[0.8396, 0.7885, 0.8729, 0.8219],
[0.8396, 0.8396, 0.8729, 0.8729],
[0.8396, 0.8906, 0.8729, 0.924],
[0.8396, 0.9417, 0.8729, 0.975],
[0.8906, 0.025, 0.924, 0.0583],
[0.8906, 0.076, 0.924, 0.1094],
[0.8906, 0.1271, 0.924, 0.1604],
[0.8906, 0.1781, 0.924, 0.2115],
[0.8906, 0.2292, 0.924, 0.2625],
[0.8906, 0.2802, 0.924, 0.3135],
[0.8906, 0.3302, 0.924, 0.3635],
[0.8906, 0.3812, 0.924, 0.4146],
[0.8906, 0.4323, 0.924, 0.4656],
[0.8906, 0.4833, 0.924, 0.5167],
[0.8906, 0.5344, 0.924, 0.5677],
[0.8906, 0.5854, 0.924, 0.6188],
[0.8906, 0.6365, 0.924, 0.6698],
[0.8906, 0.6865, 0.924, 0.7198],
[0.8906, 0.7375, 0.924, 0.7708],
[0.8906, 0.7885, 0.924, 0.8219],
[0.8906, 0.8396, 0.924, 0.8729],
[0.8906, 0.8906, 0.924, 0.924],
[0.8906, 0.9417, 0.924, 0.975],
[0.9417, 0.076, 0.975, 0.1094],
[0.9417, 0.1271, 0.975, 0.1604],
[0.9417, 0.1781, 0.975, 0.2115],
[0.9417, 0.2292, 0.975, 0.2625],
[0.9417, 0.2802, 0.975, 0.3135],
[0.9417, 0.3302, 0.975, 0.3635],
[0.9417, 0.3812, 0.975, 0.4146],
[0.9417, 0.4323, 0.975, 0.4656],
[0.9417, 0.4833, 0.975, 0.5167],
[0.9417, 0.5344, 0.975, 0.5677],
[0.9417, 0.5854, 0.975, 0.6188],
[0.9417, 0.6365, 0.975, 0.6698],
[0.9417, 0.6865, 0.975, 0.7198],
[0.9417, 0.7375, 0.975, 0.7708],
[0.9417, 0.7885, 0.975, 0.8219],
[0.9417, 0.8396, 0.975, 0.8729],
[0.9417, 0.8906, 0.975, 0.924],
]
def color_crop(im, color=[(250, 255), (245, 255), (0, 10)]):
"""Crop an image to the min and max occurances of a color range.
Accepts an image and list of colors (channels and colors must match).
"""
assert len(color) == im.shape[-1]
binary = np.ones(im.shape[:-1], dtype=bool)
for i in range(3):
binary &= (im[:,:,i] >= color[i][0]) & (im[:,:,i] <= color[i][1])
labeled = label(binary, background=0)
rp = regionprops(labeled)
bbox = max(rp, key=lambda x: x.bbox_area).bbox
return im[bbox[0]:bbox[2], bbox[1]:bbox[3], :]
def _get_points(image):
im = rgb2gray(image)
t = threshold_otsu(im)
return im > t
def get_bbox_and_centers(im, color=[(250, 255), (245, 255), (0, 10)]):
im = color_crop(im, color=color)
p = _get_points(im)
l = label(p, background=1)
rp = regionprops(l)
(maxx, maxy) = l.shape
max_size = l.shape[0] * l.shape[1] // 500
min_size = l.shape[0] * l.shape[1] // 2000
bbs = []
centers = []
for props in rp:
minr, minc, maxr, maxc = props.bbox
if props.bbox_area < max_size and props.bbox_area > min_size:
bbs.append((props.bbox[0] / maxx, props.bbox[1] / maxx, props.bbox[2] / maxx, props.bbox[3] / maxx))
centers.append(
(
(((minr / maxy) + (maxr / maxy)) / 2),
(((minc / maxx) + (maxc / maxx)) / 2)
),
)
return bbs, centers
def get_numbers(centers, bbs):
points = []
for i, b in enumerate(bbs):
for x, y in centers:
if x > b[0] and x < b[2] and y > b[1] and y < b[3]:
points.append(i)
return points
def img_to_points(filepath, check_counts=False):
im = io.imread(filepath)
color = [(250, 255), (245, 255), (0, 10)]
# convert to 3 channel
if im.shape[-1] == 4:
im = rgba2rgb(im)
# convert to uint8 (0-255)
im = img_as_ubyte(im)
try:
_, centers = get_bbox_and_centers(im, color)
except:
try:
# for some reason, some of the colors are off
# when the images are read in
color = [(250, 255), (245, 255), (75, 90)]
_, centers = get_bbox_and_centers(im, color)
except Exception as e:
raise e
if check_counts and (len(centers) < 50 or len(centers) > 120):
raise Exception("Too many or too few points")
return get_numbers(centers, ALL_BBOX)
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument("filepath", type=str)
parser.add_argument("--check_counts", action="store_true")
args = parser.parse_args()
print(img_to_points(args.filepath, args.check_counts))

View File

@@ -1,21 +0,0 @@
import argparse
from img_to_points import img_to_points
from points_to_svg import points_to_svg
def img_to_svg(filepath, output_filepath):
"""
Converts an image to an SVG file.
"""
points = img_to_points(filepath, True)
print(points)
svg = points_to_svg(points)
with open(output_filepath, "w") as f:
f.write(svg)
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Convert an image to an SVG file.")
parser.add_argument("-i", "--input", help="Input filepath", required=True)
parser.add_argument("-o", "--output", help="Output filepath", required=True)
args = parser.parse_args()
img_to_svg(args.input, args.output)

View File

@@ -1,300 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg xmlns:xlink="http://www.w3.org/1999/xlink" height="1024" version="1.1" viewBox="0 0 1024 1024" width="1024" xmlns="http://www.w3.org/2000/svg">
<path d=" M749.88,641.75 C763.31,649.46,754.85,644.24,749.88,641.75 M0,860.16 C0,950.64,73.36,1024,163.84,1024 L860.16,1024 C950.64,1024,1024,950.64,1024,860.16 L1024,163.84 C1024,73.36,950.64,0,860.16,0 L163.84,0 C73.36,0,0,73.36,0,163.84 L0,860.16" fill="#000" fill-rule="evenodd"/>
<path d="M32,163.84 C32,91.03,91.03,32,163.84,32 L860.16,32 C932.97,32,992,91.03,992,163.84 L992,860.16 C992,932.97,932.97,992,860.16,992 L163.84,992 C91.03,992,32,932.97,32,860.16 L32,163.84
M170.10500000000002,56.42
A16,16,0,0,0,170.10500000000002,88.42
A16,16,0,0,0,170.10500000000002,56.42
M218.9475,56.42
A16,16,0,0,0,218.9475,88.42
A16,16,0,0,0,218.9475,56.42
M267.79,56.42
A16,16,0,0,0,267.79,88.42
A16,16,0,0,0,267.79,56.42
M316.6325,56.42
A16,16,0,0,0,316.6325,88.42
A16,16,0,0,0,316.6325,56.42
M414.31750000000005,56.42
A16,16,0,0,0,414.31750000000005,88.42
A16,16,0,0,0,414.31750000000005,56.42
M560.845,56.42
A16,16,0,0,0,560.845,88.42
A16,16,0,0,0,560.845,56.42
M805.0575,56.42
A16,16,0,0,0,805.0575,88.42
A16,16,0,0,0,805.0575,56.42
M121.2625,105.2625
A16,16,0,0,0,121.2625,137.2625
A16,16,0,0,0,121.2625,105.2625
M218.9475,105.2625
A16,16,0,0,0,218.9475,137.2625
A16,16,0,0,0,218.9475,105.2625
M267.79,105.2625
A16,16,0,0,0,267.79,137.2625
A16,16,0,0,0,267.79,105.2625
M316.6325,105.2625
A16,16,0,0,0,316.6325,137.2625
A16,16,0,0,0,316.6325,105.2625
M365.475,105.2625
A16,16,0,0,0,365.475,137.2625
A16,16,0,0,0,365.475,105.2625
M560.845,105.2625
A16,16,0,0,0,560.845,137.2625
A16,16,0,0,0,560.845,105.2625
M609.6875,105.2625
A16,16,0,0,0,609.6875,137.2625
A16,16,0,0,0,609.6875,105.2625
M658.53,105.2625
A16,16,0,0,0,658.53,137.2625
A16,16,0,0,0,658.53,105.2625
M707.3725,105.2625
A16,16,0,0,0,707.3725,137.2625
A16,16,0,0,0,707.3725,105.2625
M805.0575,105.2625
A16,16,0,0,0,805.0575,137.2625
A16,16,0,0,0,805.0575,105.2625
M170.10500000000002,154.10500000000002
A16,16,0,0,0,170.10500000000002,186.10500000000002
A16,16,0,0,0,170.10500000000002,154.10500000000002
M218.9475,154.10500000000002
A16,16,0,0,0,218.9475,186.10500000000002
A16,16,0,0,0,218.9475,154.10500000000002
M316.6325,154.10500000000002
A16,16,0,0,0,316.6325,186.10500000000002
A16,16,0,0,0,316.6325,154.10500000000002
M414.31750000000005,154.10500000000002
A16,16,0,0,0,414.31750000000005,186.10500000000002
A16,16,0,0,0,414.31750000000005,154.10500000000002
M463.16,154.10500000000002
A16,16,0,0,0,463.16,186.10500000000002
A16,16,0,0,0,463.16,154.10500000000002
M512.0024999999999,154.10500000000002
A16,16,0,0,0,512.0024999999999,186.10500000000002
A16,16,0,0,0,512.0024999999999,154.10500000000002
M707.3725,154.10500000000002
A16,16,0,0,0,707.3725,186.10500000000002
A16,16,0,0,0,707.3725,154.10500000000002
M902.7425,154.10500000000002
A16,16,0,0,0,902.7425,186.10500000000002
A16,16,0,0,0,902.7425,154.10500000000002
M218.9475,202.9475
A16,16,0,0,0,218.9475,234.9475
A16,16,0,0,0,218.9475,202.9475
M267.79,202.9475
A16,16,0,0,0,267.79,234.9475
A16,16,0,0,0,267.79,202.9475
M756.215,202.9475
A16,16,0,0,0,756.215,234.9475
A16,16,0,0,0,756.215,202.9475
M853.9,202.9475
A16,16,0,0,0,853.9,234.9475
A16,16,0,0,0,853.9,202.9475
M902.7425,202.9475
A16,16,0,0,0,902.7425,234.9475
A16,16,0,0,0,902.7425,202.9475
M951.5849999999999,202.9475
A16,16,0,0,0,951.5849999999999,234.9475
A16,16,0,0,0,951.5849999999999,202.9475
M72.42,251.79000000000002
A16,16,0,0,0,72.42,283.79
A16,16,0,0,0,72.42,251.79000000000002
M218.9475,251.79000000000002
A16,16,0,0,0,218.9475,283.79
A16,16,0,0,0,218.9475,251.79000000000002
M267.79,251.79000000000002
A16,16,0,0,0,267.79,283.79
A16,16,0,0,0,267.79,251.79000000000002
M805.0575,251.79000000000002
A16,16,0,0,0,805.0575,283.79
A16,16,0,0,0,805.0575,251.79000000000002
M951.5849999999999,251.79000000000002
A16,16,0,0,0,951.5849999999999,283.79
A16,16,0,0,0,951.5849999999999,251.79000000000002
M72.42,300.6325
A16,16,0,0,0,72.42,332.6325
A16,16,0,0,0,72.42,300.6325
M902.7425,300.6325
A16,16,0,0,0,902.7425,332.6325
A16,16,0,0,0,902.7425,300.6325
M170.10500000000002,349.475
A16,16,0,0,0,170.10500000000002,381.475
A16,16,0,0,0,170.10500000000002,349.475
M951.5849999999999,349.475
A16,16,0,0,0,951.5849999999999,381.475
A16,16,0,0,0,951.5849999999999,349.475
M170.10500000000002,398.31750000000005
A16,16,0,0,0,170.10500000000002,430.31750000000005
A16,16,0,0,0,170.10500000000002,398.31750000000005
M853.9,398.31750000000005
A16,16,0,0,0,853.9,430.31750000000005
A16,16,0,0,0,853.9,398.31750000000005
M951.5849999999999,398.31750000000005
A16,16,0,0,0,951.5849999999999,430.31750000000005
A16,16,0,0,0,951.5849999999999,398.31750000000005
M170.10500000000002,447.16
A16,16,0,0,0,170.10500000000002,479.16
A16,16,0,0,0,170.10500000000002,447.16
M853.9,447.16
A16,16,0,0,0,853.9,479.16
A16,16,0,0,0,853.9,447.16
M902.7425,447.16
A16,16,0,0,0,902.7425,479.16
A16,16,0,0,0,902.7425,447.16
M72.42,496.0025
A16,16,0,0,0,72.42,528.0025
A16,16,0,0,0,72.42,496.0025
M121.2625,496.0025
A16,16,0,0,0,121.2625,528.0025
A16,16,0,0,0,121.2625,496.0025
M170.10500000000002,496.0025
A16,16,0,0,0,170.10500000000002,528.0025
A16,16,0,0,0,170.10500000000002,496.0025
M853.9,496.0025
A16,16,0,0,0,853.9,528.0025
A16,16,0,0,0,853.9,496.0025
M902.7425,496.0025
A16,16,0,0,0,902.7425,528.0025
A16,16,0,0,0,902.7425,496.0025
M951.5849999999999,496.0025
A16,16,0,0,0,951.5849999999999,528.0025
A16,16,0,0,0,951.5849999999999,496.0025
M72.42,544.845
A16,16,0,0,0,72.42,576.845
A16,16,0,0,0,72.42,544.845
M121.2625,544.845
A16,16,0,0,0,121.2625,576.845
A16,16,0,0,0,121.2625,544.845
M853.9,544.845
A16,16,0,0,0,853.9,576.845
A16,16,0,0,0,853.9,544.845
M902.7425,544.845
A16,16,0,0,0,902.7425,576.845
A16,16,0,0,0,902.7425,544.845
M72.42,593.6875
A16,16,0,0,0,72.42,625.6875
A16,16,0,0,0,72.42,593.6875
M853.9,593.6875
A16,16,0,0,0,853.9,625.6875
A16,16,0,0,0,853.9,593.6875
M902.7425,593.6875
A16,16,0,0,0,902.7425,625.6875
A16,16,0,0,0,902.7425,593.6875
M951.5849999999999,593.6875
A16,16,0,0,0,951.5849999999999,625.6875
A16,16,0,0,0,951.5849999999999,593.6875
M218.9475,691.3725
A16,16,0,0,0,218.9475,723.3725
A16,16,0,0,0,218.9475,691.3725
M805.0575,691.3725
A16,16,0,0,0,805.0575,723.3725
A16,16,0,0,0,805.0575,691.3725
M951.5849999999999,691.3725
A16,16,0,0,0,951.5849999999999,723.3725
A16,16,0,0,0,951.5849999999999,691.3725
M756.215,740.215
A16,16,0,0,0,756.215,772.215
A16,16,0,0,0,756.215,740.215
M805.0575,740.215
A16,16,0,0,0,805.0575,772.215
A16,16,0,0,0,805.0575,740.215
M951.5849999999999,740.215
A16,16,0,0,0,951.5849999999999,772.215
A16,16,0,0,0,951.5849999999999,740.215
M72.42,789.0575
A16,16,0,0,0,72.42,821.0575
A16,16,0,0,0,72.42,789.0575
M121.2625,789.0575
A16,16,0,0,0,121.2625,821.0575
A16,16,0,0,0,121.2625,789.0575
M316.6325,789.0575
A16,16,0,0,0,316.6325,821.0575
A16,16,0,0,0,316.6325,789.0575
M805.0575,789.0575
A16,16,0,0,0,805.0575,821.0575
A16,16,0,0,0,805.0575,789.0575
M902.7425,789.0575
A16,16,0,0,0,902.7425,821.0575
A16,16,0,0,0,902.7425,789.0575
M72.42,837.9
A16,16,0,0,0,72.42,869.9
A16,16,0,0,0,72.42,837.9
M170.10500000000002,837.9
A16,16,0,0,0,170.10500000000002,869.9
A16,16,0,0,0,170.10500000000002,837.9
M267.79,837.9
A16,16,0,0,0,267.79,869.9
A16,16,0,0,0,267.79,837.9
M365.475,837.9
A16,16,0,0,0,365.475,869.9
A16,16,0,0,0,365.475,837.9
M658.53,837.9
A16,16,0,0,0,658.53,869.9
A16,16,0,0,0,658.53,837.9
M707.3725,837.9
A16,16,0,0,0,707.3725,869.9
A16,16,0,0,0,707.3725,837.9
M756.215,837.9
A16,16,0,0,0,756.215,869.9
A16,16,0,0,0,756.215,837.9
M805.0575,837.9
A16,16,0,0,0,805.0575,869.9
A16,16,0,0,0,805.0575,837.9
M853.9,837.9
A16,16,0,0,0,853.9,869.9
A16,16,0,0,0,853.9,837.9
M902.7425,837.9
A16,16,0,0,0,902.7425,869.9
A16,16,0,0,0,902.7425,837.9
M951.5849999999999,837.9
A16,16,0,0,0,951.5849999999999,869.9
A16,16,0,0,0,951.5849999999999,837.9
M170.10500000000002,886.7425
A16,16,0,0,0,170.10500000000002,918.7425
A16,16,0,0,0,170.10500000000002,886.7425
M365.475,886.7425
A16,16,0,0,0,365.475,918.7425
A16,16,0,0,0,365.475,886.7425
M414.31750000000005,886.7425
A16,16,0,0,0,414.31750000000005,918.7425
A16,16,0,0,0,414.31750000000005,886.7425
M463.16,886.7425
A16,16,0,0,0,463.16,918.7425
A16,16,0,0,0,463.16,886.7425
M512.0024999999999,886.7425
A16,16,0,0,0,512.0024999999999,918.7425
A16,16,0,0,0,512.0024999999999,886.7425
M658.53,886.7425
A16,16,0,0,0,658.53,918.7425
A16,16,0,0,0,658.53,886.7425
M756.215,886.7425
A16,16,0,0,0,756.215,918.7425
A16,16,0,0,0,756.215,886.7425
M853.9,886.7425
A16,16,0,0,0,853.9,918.7425
A16,16,0,0,0,853.9,886.7425
M902.7425,886.7425
A16,16,0,0,0,902.7425,918.7425
A16,16,0,0,0,902.7425,886.7425
M170.10500000000002,935.5849999999999
A16,16,0,0,0,170.10500000000002,967.5849999999999
A16,16,0,0,0,170.10500000000002,935.5849999999999
M218.9475,935.5849999999999
A16,16,0,0,0,218.9475,967.5849999999999
A16,16,0,0,0,218.9475,935.5849999999999
M463.16,935.5849999999999
A16,16,0,0,0,463.16,967.5849999999999
A16,16,0,0,0,463.16,935.5849999999999
M560.845,935.5849999999999
A16,16,0,0,0,560.845,967.5849999999999
A16,16,0,0,0,560.845,935.5849999999999
M658.53,935.5849999999999
A16,16,0,0,0,658.53,967.5849999999999
A16,16,0,0,0,658.53,935.5849999999999
M707.3725,935.5849999999999
A16,16,0,0,0,707.3725,967.5849999999999
A16,16,0,0,0,707.3725,935.5849999999999
M805.0575,935.5849999999999
A16,16,0,0,0,805.0575,967.5849999999999
A16,16,0,0,0,805.0575,935.5849999999999
" fill="#FFFC00"/>
</svg>

Before

Width:  |  Height:  |  Size: 10 KiB

View File

@@ -1,78 +0,0 @@
import argparse
positions = [
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17],
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18],
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18],
[0, 1, 2, 3, 4, 5, 13, 14, 15, 16, 17, 18],
[0, 1, 2, 3, 4, 14, 15, 16, 17, 18],
[0, 1, 2, 3, 15, 16, 17, 18],
[0, 1, 2, 16, 17, 18],
[0, 1, 2, 16, 17, 18],
[0, 1, 2, 16, 17, 18],
[0, 1, 2, 16, 17, 18],
[0, 1, 2, 16, 17, 18],
[0, 1, 2, 16, 17, 18],
[0, 1, 2, 16, 17, 18],
[0, 1, 2, 3, 15, 16, 17, 18],
[0, 1, 2, 3, 4, 14, 15, 16, 17, 18],
[0, 1, 2, 3, 4, 5, 13, 14, 15, 16, 17, 18],
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18],
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18],
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17],
]
BORDER = 56.42
BORDER_C = 72.42
MULT = 48.8425
DIAMETER = 32
SVG = '''<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg xmlns:xlink="http://www.w3.org/1999/xlink" height="1024" version="1.1" viewBox="0 0 1024 1024" width="1024" xmlns="http://www.w3.org/2000/svg">
<path d=" M749.88,641.75 C763.31,649.46,754.85,644.24,749.88,641.75 M0,860.16 C0,950.64,73.36,1024,163.84,1024 L860.16,1024 C950.64,1024,1024,950.64,1024,860.16 L1024,163.84 C1024,73.36,950.64,0,860.16,0 L163.84,0 C73.36,0,0,73.36,0,163.84 L0,860.16" fill="#000" fill-rule="evenodd"/>
<path d="M32,163.84 C32,91.03,91.03,32,163.84,32 L860.16,32 C932.97,32,992,91.03,992,163.84 L992,860.16 C992,932.97,932.97,992,860.16,992 L163.84,992 C91.03,992,32,932.97,32,860.16 L32,163.84
{path}" fill="#FFFC00"/>
</svg>
'''
def points_to_svg(points):
counter = 0
path = ""
points.sort()
for r in range(len(positions)):
for c in range(len(positions[r])):
col = positions[r][c]
if len(points) == 0:
break
if points[0] == counter:
path += f"""M{col * MULT + BORDER_C},{r * MULT + BORDER}
A16,16,0,0,0,{col * MULT + BORDER_C},{r * MULT + BORDER + DIAMETER}
A16,16,0,0,0,{col * MULT + BORDER_C},{r * MULT + BORDER}
"""
points.pop(0)
counter += 1
return SVG.format(path=path)
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument("--points", type=str, help="Points to draw")
parser.add_argument("--output", type=str, help="Output file")
parser.add_argument("--show", action="store_true", help="Show SVG")
parser.set_defaults(points=",".join([str(i) for i in range(212)]))
args = parser.parse_args()
points = args.points.split(",")
points = [int(i) for i in points]
svg = points_to_svg(points)
if args.output:
with open(args.output, "w") as f:
f.write(svg)
if args.show:
import subprocess, os, platform
if platform.system() == 'Darwin': # macOS
subprocess.call(('open', args.output))
elif platform.system() == 'Windows': # Windows
os.startfile(args.output)
else: # linux variants
subprocess.call(('xdg-open', args.output))
if not args.show or not args.output:
print(svg)

View File

@@ -1,15 +0,0 @@
import os
from img_to_svg import img_to_svg
files = os.listdir("./imgs/twitter")
files = [os.path.join("./imgs/twitter", f) for f in files if f.endswith(".jpeg")]
for f in files:
output_path = f.replace(".jpeg", ".svg")
try:
img_to_svg(f, output_path)
except Exception as e:
print(f"Error processing {f}")
print(e)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 85 KiB

View File

@@ -1,78 +0,0 @@
from skimage import io
from skimage.color import rgb2gray
from skimage.filters import threshold_otsu
from skimage.measure import label, regionprops
import matplotlib.pyplot as plt
import matplotlib.patches as patches
from skimage import io
from skimage.color import rgb2gray
from skimage.filters import threshold_otsu, gaussian
from skimage.measure import label, regionprops
import matplotlib.pyplot as plt
import matplotlib.patches as patches
def get_heights(filename: str) -> list:
"""Open an image and return a list of the bar heights.
Also saves three images: the processed binary image, the original image with drawings, and a smoothed binary image.
"""
image = io.imread(filename)
im = rgb2gray(image)
binary_im = im > threshold_otsu(im)
# Apply Gaussian smoothing to binary image to make bars look rounder
smooth_im = gaussian(binary_im, sigma=2)
# Threshold again to obtain a smoothed binary
smooth_binary = smooth_im > 0.5
labeled = label(smooth_binary)
bar_dimensions = [r.bbox for r in regionprops(labeled)]
bar_dimensions.sort(key=lambda x: x[1]) # Sort left to right
# The first object (spotify logo) is the max height of the bars
logo = bar_dimensions[0]
max_height = logo[2] - logo[0]
sequence = []
# Create figure and axis for drawing
fig, ax = plt.subplots()
ax.imshow(smooth_binary, cmap='gray')
# Draw rectangle around logo
logo_rect = patches.Rectangle(
(logo[1], logo[0]),
logo[3]-logo[1],
logo[2]-logo[0],
linewidth=2,
edgecolor='yellow',
facecolor='none'
)
ax.add_patch(logo_rect)
# Add 'Logo' text near the rectangle
ax.text(logo[1], logo[0] - 10, 'Logo', color='yellow', fontsize=12, weight='bold')
# Draw bars and center markers
for bar in bar_dimensions[1:]:
height = bar[2] - bar[0]
ratio = height / max_height
ratio *= 8
ratio //= 1
val = int(ratio - 1)
sequence.append(val)
# Draw rectangle around bar
rect = patches.Rectangle(
(bar[1], bar[0]),
bar[3]-bar[1],
bar[2]-bar[0],
linewidth=1,
edgecolor='red',
facecolor='none'
)
ax.add_patch(rect)
# Draw center marker (white circle)
center_x = (bar[1] + bar[3]) / 2
center_y = (bar[0] + bar[2]) / 2
center_marker = patches.Circle((center_x, center_y), radius=5, color='black')
ax.add_patch(center_marker)
# Save processed binary image used for calculations
plt.imsave('processed_binary_image.png', binary_im, cmap='gray')
# Save smoothed binary image
plt.imsave('img_smooth.png', smooth_binary, cmap='gray')
# Save image with drawings
plt.axis('off')
plt.savefig('image_with_drawings.png', bbox_inches='tight', pad_inches=0)
plt.close()
return sequence

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

View File

@@ -1,11 +0,0 @@
import crc8
hash = crc8.crc8()
media_ref = 67775490487
ref_bytes = media_ref.to_bytes(5, byteorder="big")
print(ref_bytes)
# b'\x0f\xc7\xbb\xe9\xb7'
hash.update(ref_bytes)
check_bits = hash.digest()
print(check_bits)
# b'\x0c'

View File

@@ -1,52 +0,0 @@
from skimage import io
from skimage.measure import label, regionprops
from skimage.filters import threshold_otsu
from skimage.color import rgb2gray
from skimage import io
from skimage.color import rgb2gray
from skimage.filters import threshold_sauvola
from skimage.exposure import equalize_adapthist
from skimage.measure import label, regionprops
from skimage.morphology import closing, square
def get_heights(filename: str) -> list:
"""Open an image and return a list of the bar heights."""
# Load image and convert to grayscale
image = io.imread(filename)
im = rgb2gray(image)
# Apply adaptive histogram equalization to enhance contrast
im_eq = equalize_adapthist(im, clip_limit=0.03)
# Adaptive thresholding using Sauvola method
window_size = 25 # Adjust window size if needed
thresh_sauvola = threshold_sauvola(im_eq, window_size=window_size)
binary_im = im_eq > thresh_sauvola
# Morphological closing to fill gaps and clean noise
binary_im = closing(binary_im, square(3))
# Label connected components
labeled = label(binary_im)
# Get bounding boxes for each detected object
bar_dimensions = [r.bbox for r in regionprops(labeled)]
# Sort bounding boxes by horizontal position (xmin)
bar_dimensions.sort(key=lambda x: x[1])
# Use the first detected object (logo) as max height reference
logo = bar_dimensions[0]
max_height = logo[2] - logo[0]
sequence = []
for bar in bar_dimensions[1:]:
height = bar[2] - bar[0]
ratio = height / max_height
ratio *= 8
ratio = int(ratio) # truncate decimal part
sequence.append(ratio - 1)
return sequence

19
final-loop/Dockerfile Normal file
View File

@@ -0,0 +1,19 @@
FROM python:3.11-slim
ENV DEBIAN_FRONTEND=noninteractive
WORKDIR /app
# Install only packages that exist in Debian Trixie
RUN apt-get update && apt-get install -y \
libgl1 \
libglib2.0-0 \
libsm6 \
libxext6 \
libxrender1 \
&& rm -rf /var/lib/apt/lists/*
COPY requirements.txt ./
RUN pip install --no-cache-dir -r requirements.txt
COPY src/ .

View File

@@ -0,0 +1 @@
{"username":"mathias-120","auth_type":1,"auth_data":"QWdDZF9iOVZDTUltQ0Noc0lnSFdfNU5QOXBMdFdiQllFclJ3dHM3ZVRaY1lzUEpKd1Jwc1pOc3FzdjFwYnRYZ2ZRSXE2Q1ZiWTBjdzRDTUYzdkJMekdqOVliRTh1UldlZlJOdF94eEgydzJUWmhtT0RlX295ODdrTzZDYXFuMW5SVEQ4TV95S2R0aEJLTDZldkxPeDlva0wtcHJNY0p6Q3VzQl9YOGFhVHNYaw=="}

View File

@@ -0,0 +1 @@
65535

40
final-loop/compose.yml Normal file
View File

@@ -0,0 +1,40 @@
services:
librespot:
container_name: spotvinyl-client
image: giof71/librespot:latest
network_mode: host
devices:
- /dev/snd:/dev/snd
environment:
- DEVICE=hw:x20,0
- BACKEND=alsa
- BITRATE=320
- INITIAL_VOLUME=100
- DEVICE_NAME=spotvinyl
- ENABLE_OAUTH=headless # Enables OAuth headless mode
- ENABLE_SYSTEM_CACHE=Y # Cache token for persistent login
volumes:
- ./client-cache:/data/system-cache
stdin_open: true
tty: true
spotvinyl:
build: .
container_name: spotvinyl
devices:
- /dev/video0:/dev/video0
- /dev/video1:/dev/video1
- /dev/video2:/dev/video2
- /dev/video3:/dev/video3
volumes:
- ./src:/app
# - ./spotvinyl-cache
working_dir: /app
environment:
- PYTHONUNBUFFERED=1
env_file:
- .env
command: python3 -u decode_barcode.py
ports:
- "5000:5000"

View File

@@ -0,0 +1,43 @@
blinker==1.9.0
certifi==2025.11.12
chardet==5.2.0
charset-normalizer==3.4.4
click==8.3.1
contourpy==1.3.3
crccheck==1.3.1
cycler==0.12.1
decorator==5.2.1
Flask==3.1.2
fonttools==4.60.1
greenlet==3.2.4
idna==3.11
ImageIO==2.37.2
itsdangerous==2.2.0
Jinja2==3.1.6
joblib==1.5.2
kiwisolver==1.4.9
lazy_loader==0.4
MarkupSafe==3.0.3
matplotlib==3.10.7
networkx==3.5
numpy==2.2.6
opencv-python==4.12.0.88
packaging==25.0
pillow==12.0.0
playwright==1.56.0
pyee==13.0.0
pyparsing==3.2.5
python-dateutil==2.9.0.post0
PyWavelets==1.9.0
redis==7.1.0
requests==2.32.5
scikit-image==0.25.2
scikit-learn==1.7.2
scipy==1.16.3
six==1.17.0
spotipy==2.25.1
threadpoolctl==3.6.0
tifffile==2025.10.16
typing_extensions==4.15.0
urllib3==2.5.0
Werkzeug==3.1.3

1
final-loop/src/.cache Normal file
View File

@@ -0,0 +1 @@
{"access_token": "BQDrlnB0kiEzwgXmPTK1_c4IfsmaAxTBhIy6XdV8WLETUahlOzbqZjsCnt75YR-IyPUplWlHpcxStmm_yw3IyGhu6cLIuhX1A1tfndQhNERescv7pkW9vFOx2VNhSq_u4z66_C0cKeC1zxGrB5mP03YQfOgC1jTdT6qzfThHd1_XJ_c5LogWiWNH8B8lxaKEmVi-sVJKldynGTuj6nU3HA6G8jYef1YGK_34f1qwsLYOcYUN0tjfUA", "token_type": "Bearer", "expires_in": 3600, "scope": "user-modify-playback-state user-read-playback-state", "expires_at": 1765023017, "refresh_token": "AQAUKbWNL0mkEiOaoWfodsTnE2r8pqocU7XCRq6MvSLXlJGU5Wc6QaTRgL24EIVVOvjBzucYbMQ571v-GzbTCK02EDQfLBgWcslm9RnwtfVScGCYvl1mpzSJF0cK-I9OIUo"}

View File

@@ -0,0 +1,85 @@
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
from start_song import start_song
import os
from flask import Flask, Response
import threading
import time
import numpy as np
os.environ["QT_QPA_PLATFORM"] = "offscreen"
os.makedirs("/app/img_cache", exist_ok=True) # create folder if missing
def process_frame(frame):
heights,preprocess = get_heights(frame)
cv2.imwrite("/app/img_cache/frame.jpg", preprocess)
if len(heights)!=23:
return None # skip bad frames
else:
print("ON TROUVE UN CODE")
if heights == [0, 4, 2, 6, 5, 7, 2, 3, 1, 5, 5, 7, 4, 6, 4, 1, 2, 0, 6, 7, 3, 5, 0]:
print("VICTOIREEEEEEE")
heights = heights[1:11] + heights[12:-1]
decoded = spotify_bar_decode(heights)
if decoded == -1:
return None
uri = get_uri(decoded)
print(uri)
if uri:
start_song(uri,os.getenv('DEVICE_ID'))
return summary
if __name__ == "__main__":
cap = cv2.VideoCapture('/dev/video2')
if not cap.isOpened():
print("Error: Could not open camera.")
exit(1)
else:
print("Camera is working and ready to capture frames.")
while True:
ret, frame = cap.read()
if not ret:
break
# Crop same as streaming for consistency
height, width, _ = frame.shape
margin = 100
frame = frame[margin:height - margin, margin:width - margin]
try:
summary = process_frame(frame)
except Exception as e:
print(e)
print("")
continue
# cv2.imshow("preframe", frame)
if cv2.waitKey(1) & 0xFF == ord('q'):
break
cap.release()
cv2.destroyAllWindows()

View File

@@ -0,0 +1,70 @@
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
from skimage.morphology import remove_small_objects
from skimage.transform import resize
from skimage import exposure
from skimage.morphology import erosion, square
import cv2
from skimage.filters import threshold_local
from scipy import ndimage as ndi
from skimage.segmentation import watershed
from skimage.morphology import opening, rectangle
from scipy.ndimage import gaussian_filter
warnings.filterwarnings("ignore", category=FutureWarning, message=".*square.*deprecated.*")
#### keep this. If does not work, remove black and reaply the logic only to not-black part of the picture
def get_heights(image):
# image = io.imread(filename)
im = rgb2gray(image)
im_eq = exposure.equalize_adapthist(im, clip_limit=0.01)
# Filtrage gaussien léger pour diminuer bruit
im_blur = gaussian_filter(im_eq, sigma=1)
# Calcul du seuil local avec block_size impair, ajuster offset si besoin
th = threshold_local(im_blur, block_size=31, offset=-0.14)
binary_im = im_blur > th
separated = binary_im
labeled = label(separated)
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):
print(len(bars))
return [0], (separated.astype("uint8") * 255) # convert to 0/255 uint8
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))
print(predicted_levels)
return predicted_levels, (separated.astype("uint8") * 255)

137
final-loop/src/get_uri.py Normal file
View File

@@ -0,0 +1,137 @@
from typing import Tuple
import requests
import spotipy
from spotipy.oauth2 import SpotifyOAuth
import sys
from urllib.parse import urlencode
from urllib.parse import urlencode, urlparse, parse_qs
import webbrowser
import time
import base64
CLIENT_ID = 'a1b29f64bef643b5ade0944830637510'
CLIENT_SECRET = '1d74196e6fec41f9986917afd57df3da'
REDIRECT_URI = 'https://vinyly.couraud.xyz'
SCOPE = 'user-modify-playback-state user-read-playback-state'
TOKEN_FILE = 'spotify_token.json'
import json
def get_spotify_token():
# Try to load existing token
try:
with open(TOKEN_FILE) as f:
data = json.load(f)
if data['expires_at'] > time.time():
return data['access_token']
# refresh token
resp = requests.post(
'https://accounts.spotify.com/api/token',
data={'grant_type': 'refresh_token', 'refresh_token': data['refresh_token']},
headers={'Authorization': 'Basic ' + base64.b64encode(f"{CLIENT_ID}:{CLIENT_SECRET}".encode()).decode()}
).json()
data['access_token'] = resp['access_token']
data['expires_at'] = time.time() + resp['expires_in']
with open(TOKEN_FILE, 'w') as f2:
json.dump(data, f2)
return data['access_token']
except FileNotFoundError:
# Get authorization code
auth_url = 'https://accounts.spotify.com/authorize?' + urlencode({
'client_id': CLIENT_ID,
'response_type': 'code',
'redirect_uri': REDIRECT_URI,
'scope': SCOPE
})
print("Open this URL and authorize:", auth_url)
webbrowser.open(auth_url)
code = input("Paste the full redirected URL here: ")
code = parse_qs(urlparse(code).query)['code'][0]
# Exchange code for tokens
resp = requests.post(
'https://accounts.spotify.com/api/token',
data={
'grant_type': 'authorization_code',
'code': code,
'redirect_uri': REDIRECT_URI
},
headers={'Authorization': 'Basic ' + base64.b64encode(f"{CLIENT_ID}:{CLIENT_SECRET}".encode()).decode()}
).json()
data = {
'access_token': resp['access_token'],
'refresh_token': resp['refresh_token'],
'expires_at': time.time() + resp['expires_in']
}
with open(TOKEN_FILE, 'w') as f:
json.dump(data, f)
return data['access_token']
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"
auth_url = "https://accounts.spotify.com/authorize?" + urlencode({
"client_id": CLIENT_ID,
"response_type": "code",
"redirect_uri": REDIRECT_URI,
"scope": SCOPE
})
def get_uri(media_ref: int):
with open("/app/songs.json") as f:
code_to_url = json.load(f)
uri = code_to_url.get(str(media_ref))
if uri:
return uri
else:
return -1
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

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

View File

@@ -0,0 +1,2 @@
{"6818913446" : "spotify:track:0RoA7ObU6phWpqhlC9zH4Z"}

View File

@@ -9,7 +9,7 @@ REDIRECT_URI = 'https://vinyly.couraud.xyz'
SCOPE = 'user-modify-playback-state user-read-playback-state'
def main():
def start_song(URI="spotify:track:0RoA7ObU6phWpqhlC9zH4Z",device_id="2f97220989834a8d84e8d93860444601fde25f44"):
auth_manager = SpotifyOAuth(client_id=CLIENT_ID,
client_secret=CLIENT_SECRET,
redirect_uri=REDIRECT_URI,
@@ -26,28 +26,21 @@ def main():
sp = spotipy.Spotify(auth_manager=auth_manager)
if len(sys.argv) < 2:
print('Usage: python play_spotify.py "song name"')
sys.exit(1)
song_name = ' '.join(sys.argv[1:])
results = sp.search(q=song_name, type='track', limit=1)
if not results['tracks']['items']:
print(f'No track found for: {song_name}')
sys.exit(1)
track_uri = results['tracks']['items'][0]['uri']
devices = sp.devices()
if not devices['devices']:
print('No active Spotify devices found. Please start playback on a device.')
sys.exit(1)
active_device_id = devices['devices'][0]['id']
sp.start_playback(device_id=active_device_id, uris=[track_uri])
print(f'Playing "{song_name}" on device ID: {active_device_id}')
print(devices['devices'])
current = sp.current_user_playing_track()
current_uri = ""
if current and current.get("item"):
current_uri = current["item"]["uri"]
if current_uri != URI:
print(URI)
sp.start_playback(device_id=device_id, uris=[URI])
if __name__ == '__main__':
main()
start_song()

Binary file not shown.

Before

Width:  |  Height:  |  Size: 468 KiB

1
old/.cache Normal file
View File

@@ -0,0 +1 @@
{"access_token": "BQBR3Dnu7QMSOnq8G_8lOiz858WKJmMxiCgskqjp3QDu17EcQWx3Y8ALVQGSXzUcTgkJb-z5oHjJxEQJNfz8tSYREfJGGMKQfoCo0P2tCUbnB0wdGm7iSmWOlYuvrnDTVleJ9nJBGG6vVGc2ZEIl43T54_TmKbkPzampV8FuuGIATUvzbjRO6uEHqzc3ImuBELCZJjj0n8-h4zImXifkJP6pssEY76_GWgKo_HVx", "token_type": "Bearer", "expires_in": 3600, "scope": "user-modify-playback-state user-read-playback-state", "expires_at": 1763737778, "refresh_token": "AQAxD3wm_8aDlK9w2ZsibKsrPY0SxUJb2UaqiT2ygxK-pEq2ojxPzgLa6DNt9EpP3mjFDCLmGO5glj5fSpR3ZnqEa26belG4EQv5FOeee38ix31o2xuxPbDsY44KR5Uj1KE"}

View File

@@ -69,10 +69,11 @@ def image_for_mathias(name="1.jpg"):
# Show result
cv2.imshow("Spotify Code Deskewed", warped)
cv2.waitKey(0)
cv2.destroyAllWindows()
l = ["1.jpg", "2.jpg", "3.jpg", "4.jpg"]
l = ["rebirth.jpg"]
for name in l:
image_for_mathias(name)

BIN
old/rebirth.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 240 KiB

View File

Before

Width:  |  Height:  |  Size: 79 KiB

After

Width:  |  Height:  |  Size: 79 KiB

View File

Before

Width:  |  Height:  |  Size: 31 KiB

After

Width:  |  Height:  |  Size: 31 KiB

View File

Before

Width:  |  Height:  |  Size: 37 KiB

After

Width:  |  Height:  |  Size: 37 KiB

View File

Before

Width:  |  Height:  |  Size: 57 KiB

After

Width:  |  Height:  |  Size: 57 KiB

View File

Before

Width:  |  Height:  |  Size: 188 KiB

After

Width:  |  Height:  |  Size: 188 KiB

View File

Before

Width:  |  Height:  |  Size: 47 KiB

After

Width:  |  Height:  |  Size: 47 KiB

View File

Before

Width:  |  Height:  |  Size: 83 KiB

After

Width:  |  Height:  |  Size: 83 KiB

View File

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 26 KiB

View File

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

View File

Before

Width:  |  Height:  |  Size: 129 KiB

After

Width:  |  Height:  |  Size: 129 KiB

View File

Before

Width:  |  Height:  |  Size: 71 KiB

After

Width:  |  Height:  |  Size: 71 KiB

View File

Before

Width:  |  Height:  |  Size: 8.6 KiB

After

Width:  |  Height:  |  Size: 8.6 KiB

View File

Before

Width:  |  Height:  |  Size: 74 KiB

After

Width:  |  Height:  |  Size: 74 KiB

View File

Before

Width:  |  Height:  |  Size: 9.4 KiB

After

Width:  |  Height:  |  Size: 9.4 KiB

View File

Before

Width:  |  Height:  |  Size: 55 KiB

After

Width:  |  Height:  |  Size: 55 KiB

View File

Before

Width:  |  Height:  |  Size: 9.4 KiB

After

Width:  |  Height:  |  Size: 9.4 KiB

View File

Before

Width:  |  Height:  |  Size: 9.5 KiB

After

Width:  |  Height:  |  Size: 9.5 KiB

View File

Before

Width:  |  Height:  |  Size: 83 KiB

After

Width:  |  Height:  |  Size: 83 KiB

View File

Before

Width:  |  Height:  |  Size: 8.5 KiB

After

Width:  |  Height:  |  Size: 8.5 KiB

View File

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 34 KiB

View File

Before

Width:  |  Height:  |  Size: 3.1 MiB

After

Width:  |  Height:  |  Size: 3.1 MiB

View File

Before

Width:  |  Height:  |  Size: 53 KiB

After

Width:  |  Height:  |  Size: 53 KiB

View File

Before

Width:  |  Height:  |  Size: 109 KiB

After

Width:  |  Height:  |  Size: 109 KiB

View File

Before

Width:  |  Height:  |  Size: 77 KiB

After

Width:  |  Height:  |  Size: 77 KiB

View File

Before

Width:  |  Height:  |  Size: 37 KiB

After

Width:  |  Height:  |  Size: 37 KiB

View File

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 29 KiB

View File

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

View 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}")

View File

@@ -17,7 +17,6 @@ if __name__ == "__main__":
filename = args.filename
token = args.token
heights = get_heights(filename)
print(f"Heights: {heights}")
# drop the fist, last, and 12th bar
heights = heights[1:11] + heights[12:-1]
decoded = spotify_bar_decode(heights)

View 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

View File

@@ -0,0 +1,45 @@
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
def get_heights(filename: str):
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 = []
print(len(bars))
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))
print((predicted_levels))
return predicted_levels

View 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

View File

@@ -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))

View 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))

View File

Before

Width:  |  Height:  |  Size: 8.6 KiB

After

Width:  |  Height:  |  Size: 8.6 KiB

View File

Before

Width:  |  Height:  |  Size: 90 KiB

After

Width:  |  Height:  |  Size: 90 KiB

View File

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 59 KiB

View File

@@ -1,44 +0,0 @@
import cv2
from ultralytics import YOLO
model = YOLO('yolov5s.pt') # Replace with your trained logo detection model path
cap = cv2.VideoCapture(0) # Webcam input
if not cap.isOpened():
print("Error: Could not open video stream.")
exit()
while True:
ret, frame = cap.read()
if not ret:
break
results = model(frame)
for result in results:
boxes = result.boxes.xyxy.cpu().numpy()
scores = result.boxes.conf.cpu().numpy()
class_ids = result.boxes.cls.cpu().numpy()
for box, score, cls_id in zip(boxes, scores, class_ids):
if score < 0.3:
continue
x1, y1, x2, y2 = map(int, box)
label = model.names[int(cls_id)]
# Draw bounding box and label
cv2.rectangle(frame, (x1, y1), (x2, y2), (0, 255, 0), 2)
cv2.putText(frame, f'{label} {score:.2f}', (x1, y1 - 10),
cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 2)
# Log detection to console
print(f"Detected: {label} | Confidence: {score:.2f} | Box: ({x1}, {y1}), ({x2}, {y2})")
cv2.imshow('Logo Detection', frame)
if cv2.waitKey(1) & 0xFF == 27:
break
cap.release()
cv2.destroyAllWindows()

View File

@@ -1 +0,0 @@
test my bad

Binary file not shown.