
    Kj7                    T   % S r SSKJr  SSKrSSKrSSKrSSKrSSKrSSKrSSK	r	SSK
Jr  SSKJr  SSKrSSKr\" \5      R&                  R&                  r\S-  S-  r\S-  S-  rS	rS
r\S-  S-  rSrSSSSS.rS\S'   \R:                  " 5       rSSS.S,S jjrS-S jr S-S jr!S r"S.S jr#S/S jr$S0S jr%S1S jr&S2S3S jjr'S4S jr(S4S jr)S5S  jr*S6S! jr+S7S" jr,S8S9S# jjr-S:S$ jr.\/S%:X  a;  \0" S&5        \'" SSS'9r1\0" S(\2" \15       S)35        \0" S*5        \)" \15      r3\0" S+\35        gg);u   
Song-Erkennungs-Backend für M-029.
Weg A: Mikrofon (Raum-Audio) — Studio Display-Mikrofon
Weg B: BlackHole 2ch (System-Loopback)
Spotify-Speicherung via spotipy OAuth (playlist-modify-public).
    )annotationsN)Path)Optionalhuezspotify_config.jsonz.spotify_song_cachez&http://127.0.0.1:8089/spotify/callbacka{  ugc-image-upload user-read-playback-state user-modify-playback-state user-read-currently-playing streaming playlist-read-private playlist-read-collaborative playlist-modify-private playlist-modify-public user-follow-modify user-follow-read user-read-playback-position user-top-read user-read-recently-played user-library-modify user-library-read user-read-email user-read-privatespotify_historyzerkannte_songs.jsonlu   🎵 Erkanntidle)statesongerrortsdict_job)r
   r   c                   [            U [        S'   U[        S'   U[        S'   [        R                  " 5       [        S'   S S S 5        g ! , (       d  f       g = f)Nr	   r
   r   r   )	_job_lockr   time)r	   r
   r   s      E/Users/victorholland/Vibe Coding/dispatcher/cockpit/song_erkennung.py
_set_stater   8   s9    	WVW		T
	 
s   8A
Ac                 b    [            [        [        5      sS S S 5        $ ! , (       d  f       g = fN)r   r   r        r   
get_statusr   @   s    	Dz 
s    
.c                 x     [         R                  " [        R                  5       5      $ ! [         a    0 s $ f = fr   )jsonloadsSPOTIFY_CONFIG_FILE	read_text	Exceptionr   r   r   _spotify_cfgr   E   s4    zz-779:: 	s   '* 99c                     SSK n SSKJn  [        5       nUR	                  SS5      nUR	                  SS5      nU(       d  gU" UU[
        [        U R                  [        [        5      S9SS	9nUR                  5       nU(       d  gUR                  U5      (       a  UR                  US
   5      nU R                  US   S9$ ! [         a     gf = f)u=   Gibt ein authentifiziertes spotipy.Spotify zurück oder None.r   NSpotifyOAuth	client_id client_secret
cache_pathFr#   r%   redirect_uriscopecache_handleropen_browserrefresh_tokenaccess_token)auth)spotipyspotipy.oauth2r"   r   getSPOTIFY_REDIRECT_URISPOTIFY_SCOPECacheFileHandlerstrSPOTIFY_CACHE_FILEget_cached_tokenis_token_expiredrefresh_access_tokenSpotifyr   )r0   r"   cfgr#   r%   r/   tokens          r   _spotify_clientr>   L   s    /nGGK,	4'-!22cBT>U2V
 %%'  ''--eO.DEEE.$9:: s   ?C AC ;C 
CCc                     SSK n SSKJn  [        5       nU" UR	                  SS5      UR	                  SS5      [
        [        U R                  [        [        5      S9SS	9nUR                  5       $ )
uA   Gibt die Spotify-OAuth-URL zurück (für einmaligen Login-Knopf).r   Nr!   r#   r$   r%   r&   Fr(   )r0   r1   r"   r   r2   r3   r4   r5   r6   r7   get_authorize_url)r0   r"   r<   r/   s       r   spotify_auth_urlrA   h   sf    +
.C''+r*ggor2)..#>P:Q.RD !!##r   c                
    SSK nSSKJn  [        5       nU" UR	                  SS5      UR	                  SS5      [
        [        UR                  [        [        5      S9SS	9nUR                  U 5        g
! [         a     gf = f)z:Tauscht den Auth-Code gegen einen Token und speichert ihn.r   Nr!   r#   r$   r%   r&   Fr(   T)r0   r1   r"   r   r2   r3   r4   r5   r6   r7   get_access_tokenr   )coder0   r"   r<   r/   s        r   spotify_handle_callbackrE   x   s    /nggk2.''/26-!22cBT>U2V
 	d# s   A2A5 5
BBc                     [        5       S L$ r   )r>   r   r   r   spotify_is_connectedrG      s    D((r   c                   [         R                  " 5       nU R                  5       R                  SS5      n[	        U5       H<  u  p4US   R                  5       R                  SS5      nX%;   d  M/  US   S:  d  M:  Us  $    g)u   Sucht ein Gerät anhand eines Name-Fragmentes. Gibt Index zurück oder None.
Normalisiert Non-Breaking Spaces ( ) zu regulären Spaces für den Vergleich.     namemax_input_channelsr   N)sdquery_deviceslowerreplace	enumerate)name_fragmentdevicesfragment_normid	name_norms         r   _find_devicerX      sx      G!'')11&#>M'"fIOO%--fc:	%!,@*AA*EH # r   
   mikrofonc                   SnSnUS:X  a  [        S5      nSnO[        S5      nUc  Sn[        R                  " [        X-  5      UUSUS	9n[        R                  " 5         US:X  a,  UR                  SS
9R                  [        R                  5      n[        R                  " SSS9 nUR                  nSSS5        [        R                  " WS5       nUR                  S5        UR                  S5        UR!                  U5        UR#                  UR%                  5       5        SSS5        ['        U5      R)                  5       n	[*        R,                  " U5        U	$ ! , (       d  f       N= f! , (       d  f       NP= f)u   
Nimmt 'duration' Sekunden Audio auf.
source: 'mikrofon' → Studio Display-Mikrofon
        'blackhole' → BlackHole 2ch (System-Loopback)
Gibt WAV-Bytes zurück.
iD     	blackholezBlackHole 2ch   zStudio DisplayNint16)
sampleratechannelsdtypedevice)axis.wavFsuffixdeletewb)rX   rM   recintwaitmeanastypenpr_   tempfileNamedTemporaryFilerK   waveopensetnchannelssetsampwidthsetframeratewriteframestobytesr   
read_bytesosunlink)
durationsourcer`   ra   
device_idxframesftmp_pathwfdatas
             r   record_audior      s:    JH!/2
!"23
JVVH!"F GGI 1}!$++BHH5		$	$F5	AQ66 
B 
8T	"b



#
v~~'(	 
# >$$&DIIhK 
B	A 
#	"s   (E AE1 
E.1
E?c                  #    SSK Jn  U" 5       n[        R                  " SSS9 nUR	                  U 5        UR
                  nS S S 5         UR                  W5      I S h  vN nU(       a  SU;  a    [        R                  " U5        g US   nUR                  SS5      nUR                  S	S5      nUR                  S
S5      n	XxXS. [        R                  " U5        $ ! , (       d  f       N= f N! [         a     g f = f! [         a     $ f = f!  [        R                  " W5        f ! [         a     f f = f= f7f)Nr   )Shazamre   Frf   tracktitler$   subtitleisrc)r   artistr   raw)shazamior   rp   rq   writerK   	recognizerz   r{   r   r2   )
	wav_bytesr   shazamr   tmpresultr   r   r   r   s
             r   _shazam_recognizer      s    XF		$	$F5	AQ		ff 
B'',,.	IIcN w7B':r*62&$M	IIcN 
B	A -  		y 			IIcN 		s   #EC"ED  C3!D 4C5 
E?D D!E"
C0,E3D 5
D?EDE
DEDED>D.-D>.
D;8D>:D;;D>>Ec                     [         R                  " 5       nUR                  [        U 5      5      nUR	                  5         U$ ! [
         a
  n SnAgSnAff = f)z)Synchroner Wrapper um shazamio (asyncio).N)asyncionew_event_looprun_until_completer   closer   )r   loopr   es       r   recognize_audior      sL    %%'(():9)EF

 s   A A 
AAc                v    U R                  5       nUS   nSn U R                  SUS9nUR                  S/ 5      nU(       d  OLU H,  nU(       d  M  UR                  S5      [        :X  d  M'  US   s  $    UR                  S5      c  OUS-  nMv  U R	                  U[        S	S
S9nUS   $ ! [
         a     gf = f)uJ   Findet oder erstellt die 'Erkannt'-Playlist. Gibt die Playlist-ID zurück.idr   2   )limitoffsetitemsrK   nextNFz1Automatisch erkannte Songs via Dispatcher-Cockpit)userrK   publicdescription)current_usercurrent_user_playlistsr2   ERKANNT_PLAYLIST_NAMEuser_playlist_creater   )spmeuser_idr   r   r   pnew_pls           r   _get_or_create_playlistr      s    __T(..R.GFJJw+E1v*??T7N  zz&!)bLF  ((&K	 ) 
 d| s$   AB+ B+ -B+ 46B+ +
B87B8c                   [        5       nU(       d  SSS.$ U R                  SS5      nU R                  SS5      nU R                  SS5      nSnU(       aF   UR                  S	U 3S
SS9nUR                  S0 5      R                  S/ 5      nU(       a  US   S   nU(       d^  U(       aW   SU 3nU(       a  USU 3-  nUR                  US
SS9nUR                  S0 5      R                  S/ 5      nU(       a  US   S   nU(       d	  SSU S3S.$ SU 3n	SU 3n
/ n UR	                  U/5        UR                  S5         [        U5      nU(       a#  UR                  X/5        UR                  S5        [        X5        U(       d  SSS.$ SXZUS.$ ! [         a     GNf = f! [         a     Nf = f! [         a     Nf = f! [         a
  n SnAN]SnAff = f)u   
Sucht den Song auf Spotify und speichert ihn:
- Als Liked Song (user-library-modify, falls Scope vorhanden)
- In der 'Erkannt'-Playlist (immer)
Gibt {"ok": True/False, "track_id": ..., "url": ...} zurück.
FzSpotify nicht verbunden)okr   r   r$   r   r   Nzisrc:r   r\   )qtyper   tracksr   r   r   ztrack:z artist:zSong 'z' nicht auf Spotify gefundenzspotify:track:zhttps://open.spotify.com/track/likedplaylistz0Gespeichert konnte nicht werden (Scope-Problem?)T)r   track_idurlsaved_as)	r>   r2   searchr   current_user_saved_tracks_addappendr   playlist_add_items	_log_song)	song_infor   r   r   r   r   rr   r   	track_uri	track_urlr   pl_idr   s                 r   save_to_spotifyr     s	    
	B&?@@]]7B'E]]8R(F]]62&DH 			eD6N	BAEE(B'++GR8E 8D>
 		 Axx((		AG1	5AEE(B'++GR8E 8D> ug5Q&RSS 
+I1(<IH
(((4 
'+!!%5OOJ'
 i"&XYYHHUU]  		  		    sJ   AF +AF% #F5 5G 
F"!F"%
F21F25
GG
GGc                    SS K nUR                   R                  5       R                  5       U R                  S5      U R                  S5      U R                  S5      US.n[        R
                  R                  SSS9  [        R                  SS	S
9 nUR                  [        R                  " USS9S-   5        S S S 5        g ! , (       d  f       g = f! [         a     g f = f)Nr   r   r   r   )r   r   r   r   r   T)parentsexist_okazutf-8)encodingF)ensure_ascii
)datetimenow	isoformatr2   SONG_LOG_FILEparentmkdirrs   r   r   dumpsr   )r   r   r   entryr   s        r   r   r   ]  s    ##'')335]]7+mmH-MM&) 
 	""4$"?g6!GGDJJu59D@A 766 s0   BC (C;C 
CC C 
C"!C"c                `   ^ ^ UU 4S jn[         R                  " USS9nUR                  5         g)z2Startet den kompletten Flow im Hintergrund-Thread.c            	     J  >  [        S5        [        TTS9n [        S5        [        U 5      nU(       a  UR                  S5      (       d  [        SSS9  g [        SUS   US	   UR                  S
S5      S.S9  g ! [         a  n[        S[        U5      S9   S nAg S nAff = f)N	recordingr|   r}   recognizingr   r   zSong nicht erkanntr   doner   r   r$   )r   r   r   r
   )r   r   r   r2   r   r6   )wavr
   r   r|   r}   s      r   _flow#run_recognition_flow.<locals>._flowt  s    	.{#@C}%"3'Dtxx007*>?vgx.,% 
  	.wc!f-	.s   AA; $A; ;
B"BB"TtargetdaemonN	threadingThreadstart)r}   r|   r   ts   ``  r   run_recognition_flowr   r  s%    .( 	d3AGGIr   c                \   ^  U 4S jn[         R                  " USS9nUR                  5         g)z@Speichert einen bereits erkannten Song in Spotify (Hintergrund).c                 L  >  [        STS9  [        T5      n U S   (       a?  [        T5      nU R                  S5      US'   U R                  S/ 5      US'   [        SUS9  g [        SU R                  SS5      S	9  g ! [         a  n[        S[        U5      S	9   S nAg S nAff = f)
Nsavingr   r   r   r   r   r   zUnbekannter Fehlerr   )r   r   r   r2   r   r6   )r   song_with_urlr   r   s      r   _saverun_save_flow.<locals>._save  s    	.xi0$Y/Fd| $Y'-zz%'8e$,2JJz2,Fj)667&**W>R*ST 	.wc!f-	.s   AA< !A< <
B#BB#Tr   Nr   )r   r   r   s   `  r   run_save_flowr     s%    . 	d3AGGIr   __main__u"   Starte Aufnahme (10s, Mikrofon)…r   zAufgenommen: z Bytesu
   Erkenne…z	Ergebnis:)r	   r6   )returnr   )r   r6   )rD   r6   r   bool)r   r   )rR   r6   r   zOptional[int])rY   rZ   )r|   rk   r}   r6   r   bytes)r   r   r   zOptional[dict])r   zOptional[str])r   r   r   r   )r   r   r   r6   )rZ   rY   )r}   r6   r|   rk   )r   r   )4__doc__
__future__r   r   r   rz   rp   r   r   rr   pathlibr   typingr   numpyro   sounddevicerM   __file__r   
DISPATCHERr   r7   r3   r4   r   r   r   __annotations__Lockr   r   r   r   r>   rA   rE   rG   rX   r   r   r   r   r   r   r   r   __name__printr   lenr   r   r   r   <module>r     sl   #   	        (^""))
 5(+@@  5(+@@ ? (  ..1GG '  
	d  NN	 $(t $
8$ ()	*b,<FVR*4( z	
./
:
6C	M#c(6
*+	,S!F	+v r   