travail d'un we (j'en ai big marre)

This commit is contained in:
2025-11-23 23:39:43 +01:00
parent 1ff888cf34
commit 4c486199ed
69 changed files with 208 additions and 111 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"}

10
final-loop/Dockerfile Normal file
View File

@@ -0,0 +1,10 @@
FROM python:3.11-slim
WORKDIR /app
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

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

@@ -0,0 +1,34 @@
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/video2
volumes:
- ./src:/app
# - ./spotvinyl-cache
working_dir: /app
env_file:
- .env
command: python3 decode_barcode.py
ports:
- "5000:5000"

View File

@@ -0,0 +1,32 @@
certifi==2025.11.12
chardet==5.2.0
charset-normalizer==3.4.4
contourpy==1.3.3
crccheck==1.3.1
cycler==0.12.1
decorator==5.2.1
fonttools==4.60.1
idna==3.11
ImageIO==2.37.2
joblib==1.5.2
kiwisolver==1.4.9
lazy_loader==0.4
matplotlib==3.10.7
networkx==3.5
numpy==2.2.6
opencv-python==4.12.0.88
packaging==25.0
pillow==12.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
urllib3==2.5.0

View File

@@ -1 +0,0 @@
{"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}

View File

@@ -1,21 +0,0 @@
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

View File

@@ -1,57 +0,0 @@
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()

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -0,0 +1,86 @@
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"] = "xcb"
def process_frame(frame, token):
heights,preprocess = get_heights(frame)
cv2.imshow("frame", preprocess)
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)
print(f"SONG FOUND : {summary["name"]}")
if uri:
start_song(uri['target'],os.getenv('DEVICE_ID'))
return summary
if __name__ == "__main__":
cap = cv2.VideoCapture('/dev/video0')
token = os.getenv('ACCESS_TOKEN')
if not token:
token = "BQCph85n2Cr-h8jKWhsdsdhywaX3h_bn4pJ_-jdque2u_gn9bI-OdthIowGSU6r058QozL0eJfzy_ClWezXYKrQO2npuyfWVphxSQrKqhBWkGr5bK0UrIfsKKAdJvoNrXD9Db-ObgP5D3-rMpF0Xq3RXwMpTal9NpzTJcHZs_PBjbNClJVy24Jk5WfGbKZPkMs_Hon5TjABx4QzxzE2vxjd4X4EyPlyPuKiIVp-f7yTSJbbRLqt-_O_VJ9mnQ1RgGK16afY7p3JZH_B6-VSCrFuhK_m9yhSieiWoqEeopFEX47Nc4-tuqe8CXcYGiRLZBIcc4w64ly36ZIftxRN7ehcJb2gcV26ZqMS1lg1Yxp0OD4ShJJsinA69X535_w"
print(token)
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, token)
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

@@ -8,24 +8,46 @@ from skimage.color import rgb2gray
import matplotlib.patches as patches import matplotlib.patches as patches
from sklearn.cluster import KMeans from sklearn.cluster import KMeans
import warnings 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.*") 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): def get_heights(image):
# image = io.imread(filename) # image = io.imread(filename)
im = rgb2gray(image) im = rgb2gray(image)
binary_im = im > threshold_otsu(im)
smooth_im = gaussian(binary_im.astype(float), sigma=1) # Filtrage gaussien léger pour diminuer bruit
morph_im = closing(smooth_im > 0.5, square(3)) im_blur = gaussian_filter(im, sigma=1)
labeled = label(morph_im)
# Calcul du seuil local avec block_size impair, ajuster offset si besoin
th = threshold_local(im_blur, block_size=71, offset=-0.09)
binary_im = im_blur > th
separated = binary_im
labeled = label(separated)
bar_dims = [r.bbox for r in regionprops(labeled)] bar_dims = [r.bbox for r in regionprops(labeled)]
bar_dims.sort(key=lambda x: x[1]) # left to right bar_dims.sort(key=lambda x: x[1]) # left to right
bars = bar_dims[1:] # skip logo bars = bar_dims#[1:] # skip logo
bar_heights_raw = [] bar_heights_raw = []
if(len(bars)!=23): if(len(bars)!=23):
return [0] print(len(bars))
return [0], (separated.astype("uint8") * 255) # convert to 0/255 uint8
for bar in bars: for bar in bars:
top, left, bottom, right = bar top, left, bottom, right = bar
effective_height = bottom - top # use bounding box height directly effective_height = bottom - top # use bounding box height directly
@@ -44,5 +66,5 @@ def get_heights(image):
closest_cluster = np.argmin(diffs) closest_cluster = np.argmin(diffs)
predicted_levels.append(int(closest_cluster)) predicted_levels.append(int(closest_cluster))
print(predicted_levels)
return predicted_levels return predicted_levels, (separated.astype("uint8") * 255)

View File

@@ -9,7 +9,7 @@ REDIRECT_URI = 'https://vinyly.couraud.xyz'
SCOPE = 'user-modify-playback-state user-read-playback-state' SCOPE = 'user-modify-playback-state user-read-playback-state'
def main(): def start_song(URI="spotify:track:0RoA7ObU6phWpqhlC9zH4Z",device_id="a499b8f3684d8098ffb5f9ff11392ebdd61fc6d1"):
auth_manager = SpotifyOAuth(client_id=CLIENT_ID, auth_manager = SpotifyOAuth(client_id=CLIENT_ID,
client_secret=CLIENT_SECRET, client_secret=CLIENT_SECRET,
redirect_uri=REDIRECT_URI, redirect_uri=REDIRECT_URI,
@@ -26,28 +26,18 @@ def main():
sp = spotipy.Spotify(auth_manager=auth_manager) 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() devices = sp.devices()
if not devices['devices']: if not devices['devices']:
print('No active Spotify devices found. Please start playback on a device.') print('No active Spotify devices found. Please start playback on a device.')
sys.exit(1) sys.exit(1)
active_device_id = devices['devices'][0]['id'] current = sp.current_user_playing_track()
if current and current.get("item"):
sp.start_playback(device_id=active_device_id, uris=[track_uri]) current_uri = current["item"]["uri"]
print(f'Playing "{song_name}" on device ID: {active_device_id}')
if current_uri != URI:
sp.start_playback(device_id=device_id, uris=[URI])
if __name__ == '__main__': if __name__ == '__main__':
main() start_song(URI,device_id)

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

Before

Width:  |  Height:  |  Size: 240 KiB

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

@@ -21,12 +21,12 @@ def get_heights(filename: str):
bars = bar_dims[1:] # skip logo bars = bar_dims[1:] # skip logo
bar_heights_raw = [] bar_heights_raw = []
print(len(bars))
for bar in bars: for bar in bars:
top, left, bottom, right = bar top, left, bottom, right = bar
effective_height = bottom - top # use bounding box height directly effective_height = bottom - top # use bounding box height directly
bar_heights_raw.append(effective_height) bar_heights_raw.append(effective_height)
print(len(bars))
# Cluster measured heights to 8 clusters representing discrete bar levels # Cluster measured heights to 8 clusters representing discrete bar levels
bar_heights_raw_np = np.array(bar_heights_raw).reshape(-1, 1) bar_heights_raw_np = np.array(bar_heights_raw).reshape(-1, 1)
@@ -38,8 +38,8 @@ def get_heights(filename: str):
for h in bar_heights_raw: for h in bar_heights_raw:
diffs = np.abs(cluster_centers - h) diffs = np.abs(cluster_centers - h)
closest_cluster = np.argmin(diffs) closest_cluster = np.argmin(diffs)
predicted_levels.append(closest_cluster) predicted_levels.append(int(closest_cluster))
print(len(predicted_levels)) print((predicted_levels))
return predicted_levels return predicted_levels

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