+
    Kj7                      a  0 t $ R t^ RIHt ^ RIt^ RIt^ RIt^ RIt^ RIt^ RI	t	^ RI
t
^ RIHt ^ RIHt ^ RIt^ RIt]! ]4      P(                  P(                  t]R,          R,          t]R,          R,          tRtR	t]R
,          R,          tRtRRRRRRRR/tR]R&   ]P<                  ! 4       tRRRR/R R llt R R lt!R R lt"R t#R R lt$R R lt%R R  lt&R! R" lt'R;R$ R% llt(R& R' lt)R( R) lt*R* R+ lt+R, R- lt,R. R/ lt-R<R0 R1 llt.R2 R3 lt/]0R48X  d@   ]1! R54       ](! ^
R#R67      t2]1! R7]3! ]24       R824       ]1! R94       ]*! ]24      t4]1! R:]44       R# R# )=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stateidlesongerrortsdict_jobc                   V ^8  d   QhRR/# )   r   str )formats   "E/Users/victorholland/Vibe Coding/dispatcher/cockpit/song_erkennung.py__annotate__r   8   s     $ $c $    c                   \         ;_uu_ 4        V \        R &   V\        R&   V\        R&   \        P                  ! 4       \        R&   RRR4       R#   + '       g   i     R# ; i)r   r	   r
   r   N)	_job_lockr   time)r   r	   r
   s   &$$r   
_set_stater   8   s=    	WVW		T
	 
s   8AA"	c                   V ^8  d   QhRR/# r   returnr   r   )r   s   "r   r   r   @   s      D r   c                 z    \         ;_uu_ 4        \        \        4      uuR R R 4       #   + '       g   i     R # ; iN)r   r   r   r   r   r   
get_statusr   @   s    	Dz 
s   ):	c                   V ^8  d   QhRR/# r   r   )r   s   "r   r   r   E   s      d r   c                 z     \         P                  ! \        P                  4       4      #   \         d    / u # i ; ir   )jsonloadsSPOTIFY_CONFIG_FILE	read_text	Exceptionr   r   r   _spotify_cfgr'   E   s4    zz-779:: 	s   '* ::c                     ^ RI p ^ RIHp \        4       pVP	                  RR4      pVP	                  RR4      pV'       g   R# V! VV\
        \        V P                  \        \        4      R7      RR7      pVP                  4       pV'       g   R# VP                  V4      '       d   VP                  VR	,          4      pV P                  VR
,          R7      #   \         d     R# i ; i)u=   Gibt ein authentifiziertes spotipy.Spotify zurück oder None.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CacheFileHandlerr   SPOTIFY_CACHE_FILEget_cached_tokenis_token_expiredrefresh_access_tokenSpotifyr&   )r8   r*   cfgr+   r-   r7   tokens          r   _spotify_clientrE   L   s    /nGGK,	4'-!22cBT>U2V
 %%'  ''--eO.DEEE.$9:: s%   A C AC C (0C C('C(c                   V ^8  d   QhRR/# )r   r   r   r   )r   s   "r   r   r   h   s     $ $# $r   c                     ^ RI p ^ RIHp \        4       pV! VP	                  RR4      VP	                  RR4      \
        \        V P                  \        \        4      R7      RR7      pVP                  4       # )	uA   Gibt die Spotify-OAuth-URL zurück (für einmaligen Login-Knopf).Nr)   r+   r,   r-   r.   Fr0   )r8   r9   r*   r'   r:   r;   r<   r=   r   r>   get_authorize_url)r8   r*   rC   r7   s       r   spotify_auth_urlrI   h   sf    +
.C''+r*ggor2)..#>P:Q.RD !!##r   c                    V ^8  d   QhRRRR/# )r   coder   r   boolr   )r   s   "r   r   r   x   s      # $ r   c                    ^ RI p^ RIHp \        4       pV! VP	                  RR4      VP	                  RR4      \
        \        VP                  \        \        4      R7      RR7      pVP                  V 4       R	#   \         d     R# i ; i)
z:Tauscht den Auth-Code gegen einen Token und speichert ihn.Nr)   r+   r,   r-   r.   Fr0   T)r8   r9   r*   r'   r:   r;   r<   r=   r   r>   get_access_tokenr&   )rK   r8   r*   rC   r7   s   &    r   spotify_handle_callbackrO   x   s    /nggk2.''/26-!22cBT>U2V
 	d# s   A8A< <B
Bc                   V ^8  d   QhRR/# )r   r   rL   r   )r   s   "r   r   r      s     ) )d )r   c                     \        4       R J# r   )rE   r   r   r   spotify_is_connectedrR      s    D((r   c                    V ^8  d   QhRRRR/# )r   name_fragmentr   r   zOptional[int]r   )r   s   "r   r   r      s     	 	 	 	r   c                   \         P                  ! 4       pV P                  4       P                  RR4      p\	        V4       FF  w  r4VR,          P                  4       P                  RR4      pW%9   g   K4  VR,          ^ 8  g   KD  Vu # 	  R# )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_channelsN)sdquery_deviceslowerreplace	enumerate)rT   devicesfragment_normid	name_norms   &     r   _find_devicerd      sx      G!'')11&#>M'"fIOO%--fc:	%!,@*AA*EH # r   mikrofonc               $    V ^8  d   QhRRRRRR/# )r   durationintsourcer   r   bytesr   )r   s   "r   r   r      s!     * *3 *S *% *r   c                L   Rp^pVR8X  d   \        R4      p^pM\        R4      pVf   Rp\        P                  ! \        W,          4      VVRVR7      p\        P                  ! 4        V^8X  d0   VP                  ^R7      P                  \        P                  4      p\        P                  ! R	R
R7      ;_uu_ 4       pVP                  pRRR4       \        P                  ! XR4      ;_uu_ 4       pVP                  ^4       VP                  ^4       VP!                  V4       VP#                  VP%                  4       4       RRR4       \'        V4      P)                  4       p	\*        P,                  ! V4       V	#   + '       g   i     L; i  + '       g   i     LT; i)u   
Nimmt 'duration' Sekunden Audio auf.
source: 'mikrofon' → Studio Display-Mikrofon
        'blackhole' → BlackHole 2ch (System-Loopback)
Gibt WAV-Bytes zurück.
iD  	blackholezBlackHole 2chzStudio DisplayNint16)
sampleratechannelsdtypedevice)axis.wavFsuffixdeletewb)rd   rZ   recrh   waitmeanastypenprm   tempfileNamedTemporaryFilerX   waveopensetnchannelssetsampwidthsetframeratewriteframestobytesr   
read_bytesosunlink)
rg   ri   rn   ro   
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	A	AQ66 
B 
8T	"	"b



#
v~~'(	 
# >$$&DIIhK 
B	A 
#	"s    F 4AF F	F#	c                    V ^8  d   QhRRRR/# r   	wav_bytesrj   r   zOptional[dict]r   )r   s   "r   r   r      s      u  r   c                  "   ^ RI Hp V! 4       p\        P                  ! RRR7      ;_uu_ 4       pVP	                  V 4       VP
                  pRRR4        VP                  X4      G Rj  xL
 pV'       d   RV9  d     \        P                  ! V4       R# VR,          pVP                  RR4      pVP                  R	R4      pVP                  R
R4      p	RVRVR
V	RV/ \        P                  ! V4       #   + '       g   i     L; i L  \         d     R# i ; i  \         d     # i ; i   \        P                  ! X4       i   \         d     i i ; i; i5i)    )Shazamrs   Frt   Ntracktitler,   subtitleisrcartistraw)shazamior   r}   r~   writerX   	recognizer   r   r&   r:   )
r   r   shazamr   tmpresultr   r   r   r   s
   &         r   _shazam_recognizer      s'    XF		$	$F5	A	AQ		ff 
B'',,.	IIcN w7B':r*62&&&$uM	IIcN 
B	A -  		y 			IIcN 		s   .EC9ED1 +D,D1 8D1 D EAD1 "D 8E9D		ED1 DEDE D.+E-D..E1E3E
	E
EEEEEc                    V ^8  d   QhRRRR/# r   r   )r   s   "r   r   r      s      u  r   c                     \         P                  ! 4       pVP                  \        V 4      4      pVP	                  4        V#   \
         d   p Rp?R# Rp?ii ; i)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 ^8  d   QhRR/# )r   r   zOptional[str]r   )r   s   "r   r   r      s      = r   c                    V P                  4       pVR,          p^ p V P                  ^2VR7      pVP                  R. 4      pV'       g   MWV F2  pV'       g   K  VP                  R4      \        8X  g   K)  VR,          u # 	  VP                  R4      f   MV^2,          pK  V P	                  V\        RRR	7      pVR,          #   \
         d     R# i ; i)
uJ   Findet oder erstellt die 'Erkannt'-Playlist. Gibt die Playlist-ID zurück.id)limitoffsetitemsrX   nextNFz1Automatisch erkannte Songs via Dispatcher-Cockpit)userrX   publicdescription)current_usercurrent_user_playlistsr:   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C C C 7
C AC CCc                    V ^8  d   QhRRRR/# )r   	song_infor   r   r   )r   s   "r   r   r     s"     FV FVt FV FVr   c                   \        4       pV'       g   RRRR/# V P                  RR4      pV P                  RR4      pV P                  RR4      pR	pV'       dS    VP                  R
V 2R^R7      pVP                  R/ 4      P                  R. 4      pV'       d   V^ ,          R,          pV'       gq   V'       di    RV 2pV'       d   VRV 2,          pVP                  VR^R7      pVP                  R/ 4      P                  R. 4      pV'       d   V^ ,          R,          pV'       g   RRRRV R2/# RV 2p	RV 2p
. p VP	                  V.4       VP                  R4        \        V4      pV'       d$   VP                  W.4       VP                  R4       \        Y4       T'       g   RRRR/# RRRTRT
RT/#   \         d     EL"i ; i  \         d     Li ; i  \         d     Li ; i  \         d   p R	p?LgR	p?ii ; i)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.
okFr
   zSpotify nicht verbundenr   r,   r   r   Nzisrc:r   )qtyper   tracksr   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track_idurlsaved_as)	rE   r:   searchr&   current_user_saved_tracks_addappendr   playlist_add_items	_log_song)r   r   r   r   r   r   rr   r   	track_uri	track_urlr   pl_idr   s   &             r   save_to_spotifyr     s    
	BeW&?@@]]7B'E]]8R(F]]62&DH 			eD6N	BAEE(B'++GR8E 8D>
 		 Axx((		AG1	5AEE(B'++GR8E 8D> eWug5Q&RSS 
+I1(<IH
(((4 
'+!!%5OOJ'
 i"eW&XYY$
HeY
HUU]  		  		    s\   A F? F? <G 	A	G G #G" &6G3 ?GGGG"G0/G03HHc                    V ^8  d   QhRRRR/# )r   r   r   r   r   r   )r   s   "r   r   r   ]  s        r   c           
         ^ RI pRVP                   P                  4       P                  4       RV P                  R4      RV P                  R4      RV P                  R4      RV/p\        P
                  P                  RRR7       \        P                  R	R
R7      ;_uu_ 4       pVP                  \        P                  ! VRR7      R,           4       RRR4       R#   + '       g   i     R# ; i  \         d     R# i ; i)r   Nr   r   r   r   r   T)parentsexist_okazutf-8)encodingF)ensure_ascii
)datetimenow	isoformatr:   SONG_LOG_FILEparentmkdirr   r   r"   dumpsr&   )r   r   r   entryr   s   &&   r   r   r   ]  s    (##'')335Y]]7+immH-IMM&)
 	""4$"?g66!GGDJJu59D@A 7666 s0   B#C2 %/CC2 C/	)C2 /C2 2D Dc                    V ^8  d   QhRRRR/# )r   ri   r   rg   rh   r   )r   s   "r   r   r   r  s       S r   c                h   a a VV 3R lp\         P                  ! VRR7      pVP                  4        R# )z2Startet den kompletten Flow im Hintergrund-Thread.c                   <  \        R 4       \        SSR7      p \        R4       \        V 4      pV'       d   VP                  R4      '       g   \        RRR7       R# \        RRVR,          R	VR	,          R
VP                  R
R4      /R7       R#   \         d"   p\        R\        T4      R7        Rp?R# Rp?ii ; i)	recordingrg   ri   recognizingr   r
   zSong nicht erkanntr
   Ndoner   r   r,   r	   )r   r   r   r:   r&   r   )wavr	   r   rg   ri   s      r   _flow#run_recognition_flow.<locals>._flowt  s    	.{#@C}%"3'Dtxx007*>?vg$x.,% 
  	.wc!f--	.s$   AB B 1B B>B99B>TtargetdaemonN	threadingThreadstart)ri   rg   r   ts   ff  r   run_recognition_flowr   r  s%    .( 	d3AGGIr   c                   V ^8  d   QhRR/# )r   r   r   r   )r   s   "r   r   r     s      T r   c                d   a  V 3R lp\         P                  ! VRR7      pVP                  4        R# )z@Speichert einen bereits erkannten Song in Spotify (Hintergrund).c                 v  <  \        R SR7       \        S4      p V R,          '       dD   \        S4      pV P                  R4      VR&   V P                  R. 4      VR&   \        RVR7       R	# \        RV P                  RR4      R7       R	#   \         d"   p\        R\        T4      R7        R	p?R	# R	p?ii ; i)
savingr   r   r   r   r   r
   zUnbekannter Fehlerr   N)r   r   r   r:   r&   r   )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(B -B B8B33B8Tr   Nr   )r   r   r   s   f  r   run_save_flowr    s%    . 	d3AGGIr   __main__u"   Starte Aufnahme (10s, Mikrofon)…r   zAufgenommen: z Bytesu
   Erkenne…z	Ergebnis:)
   re   )re   r  )5__conditional_annotations____doc__
__future__r   r   r"   r   r}   r   r   r   pathlibr   typingr   numpyr|   sounddevicerZ   __file__r   
DISPATCHERr$   r>   r;   r<   r   r   r   __annotations__Lockr   r   r   r'   rE   rI   rO   rR   rd   r   r   r   r   r   r   r   r  __name__printr   lenr   )r  s   @r   <module>r     s|   #   	        (^""))
 5(+@@  5(+@@ ? (  ..1GG '  V
DT$	d  NN	$4 $t $
8$ ()	*b,<FVR*4( z	
./
:
6C	M#c(6
*+	,S!F	+v r   