From 757b0e0e9832b03eefe2b4b1606f7e2083cc4e92 Mon Sep 17 00:00:00 2001 From: unknown Date: Fri, 19 Dec 2025 00:06:38 +0100 Subject: [PATCH] Prepare V0.2 --- librespot/audio/__init__.py | 73 ++++++++++++++++++ .../__pycache__/__init__.cpython-314.pyc | Bin 79868 -> 84149 bytes zotify/__pycache__/album.cpython-314.pyc | Bin 4783 -> 4850 bytes zotify/__pycache__/app.cpython-314.pyc | Bin 0 -> 14478 bytes zotify/__pycache__/playlist.cpython-314.pyc | Bin 0 -> 4915 bytes zotify/__pycache__/track.cpython-314.pyc | Bin 34277 -> 36225 bytes zotify/album.py | 2 +- zotify/app.py | 1 + zotify/playlist.py | 7 +- zotify/track.py | 57 +++++++++++++- 10 files changed, 137 insertions(+), 3 deletions(-) create mode 100644 zotify/__pycache__/app.cpython-314.pyc create mode 100644 zotify/__pycache__/playlist.cpython-314.pyc diff --git a/librespot/audio/__init__.py b/librespot/audio/__init__.py index 22a263f..c53346e 100644 --- a/librespot/audio/__init__.py +++ b/librespot/audio/__init__.py @@ -322,10 +322,83 @@ class AudioKeyManager(PacketsReceiver, Closeable): "Couldn't handle packet, cmd: {}, length: {}".format( packet.cmd, len(packet.payload))) + def _is_premium_user(self) -> bool: + """Best-effort check whether the current Spotify account is Premium. + + We rely on Session user attributes populated from the AP product_info packet. + Historically, the attribute named "type" contains values like "premium", "free", + "open", etc. + """ + try: + raw = ( + self.__session.get_user_attribute("type") + or self.__session.get_user_attribute("product") + or self.__session.get_user_attribute("product_type") + or self.__session.get_user_attribute("subscription") + or self.__session.get_user_attribute("account_type") + or "" + ) + product = str(raw).strip().lower() + if not product: + return False + + if "premium" in product: + return True + + # Legacy/alt plan names occasionally seen. + return product in {"unlimited"} + except Exception: + return False + + def _get_spotify_audio_key(self, gid: bytes, file_id: bytes, retry: bool = True) -> bytes: + """Request the audio key directly from Spotify (Premium accounts).""" + tries = 0 + last_err: typing.Optional[Exception] = None + + while True: + tries += 1 + callback = AudioKeyManager.SyncCallback(self) + + with self.__seq_holder_lock: + AudioKeyManager.__seq_holder += 1 + seq = AudioKeyManager.__seq_holder + AudioKeyManager.__callbacks[seq] = callback + + try: + payload = struct.pack(">i", seq) + gid + file_id + self.__session.send(Packet.Type.request_key, payload) + + key = callback.wait_response() + if key is None: + raise RuntimeError("Audio key request failed") + return key + except Exception as exc: # noqa: BLE001 + last_err = exc + self.logger.warning("Spotify audio key request failed (try %d): %s", tries, exc) + if not retry or tries >= 3: + break + time.sleep(1) + finally: + try: + AudioKeyManager.__callbacks.pop(seq, None) + except Exception: + pass + + raise RuntimeError( + "Failed fetching Audio Key from Spotify for gid: {}, fileId: {} (last error: {})".format( + util.bytes_to_hex(gid), util.bytes_to_hex(file_id), last_err + ) + ) + def get_audio_key(self, gid: bytes, file_id: bytes, retry: bool = True) -> bytes: + # If the user is Premium, Spotify will return audio keys directly. + # In that case, do not use the SpotiClub API. + if self._is_premium_user(): + return self._get_spotify_audio_key(gid, file_id, retry=retry) + global spoticlub_user, spoticlub_password, spoticlub_client_serial, spoticlub_loaded_logged if not spoticlub_user or not spoticlub_password or spoticlub_user == "anonymous": try: diff --git a/librespot/audio/__pycache__/__init__.cpython-314.pyc b/librespot/audio/__pycache__/__init__.cpython-314.pyc index f5ebab4f1dc5ad7d63e6c1ebf9efed482f952d46..3e9fa8fa284dccbda82b7e14adbddf939f39407a 100644 GIT binary patch delta 11493 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 DV<#|V diff --git a/zotify/__pycache__/album.cpython-314.pyc b/zotify/__pycache__/album.cpython-314.pyc index 7f46fc725d2aa0b4d56c06b42b7548677307a980..62df1d06798ccb4931a7bb96ad7288f6fb62e65d 100644 GIT binary patch delta 344 zcmZ3l`bm{fn~#@^0SHbsyJSl8Z{#!P;^qZ%nHd;ZK7V7Jd|p6ea|)LzBV+XBD(jMF$>)O@}oev0q;AWHH`@qMb zsQO)$o0ID^D}!heXATP`|aW-Q)D8TM)HhG&s7o+oJM?nWxcc4Vn5n`tPG+>j6Xl| zGK;VkgWNOQ#94{upa8qG(&V24U5rkXTLc|g-GCC2lTQlnVFMXm&Sq+B{@JqI?#fKp z{;}UVSLaF)xzm~2$F;fl-1GSE_d4hN&UcPlOvYRauFw9sKh)x&sQ->$$fZanp4w=N zTBI&gEbXRTv_My)w8E`$DFg*hE8R+$N>Jgn%B^;31dU58Xz{z+t#joFIj&qG*QFQq zE`wlj83m)uB$!-zLY~Vkm|YgZ;>s8DT?Imc%PLr1g+igLNGNg@3&og*#%*(z2qieJ zb(gxzgff?1u)E5Ia#w{=f%A3lN*5zAt}3AlzvsBCT{S|Dt5&GR@44N)4Q8o%|f%QMQCxg3avQL;BIp{1V<&s8ijV&R7|mX#niM}=wQu|w&3&uHouq> z4zdL`RFm}|k(!!l(a<|L5emOF7nlwS^YE?{0+;5d140neDsMoTYEp=%iSX6g>2P4e zADF&0HzO9M-+3Vv6{PHFQ=X_EV|)7sMb${}uv1jH`$bJJJLVo86SdNFR8+gioWrA{ z23H&vbb)?8DCCgTsA%YSkD@m}{)}qu zg4YS$YKDNrAm5}C^DhSle`Go^KaH4+hG#EFMa!PN(ClPbER)5U!3jUkh>BK}GZ~(q4qt`L09J1lT56)eMs!Nl!!soKL$i@NLA3J0%fV~X3pC>W zbNqA^Y2xLu5SpA{sG1UlNc32Hdms{WL?bxUF&>_2zjB~G8VvB`Q{&J_DD;Oe)N_>Z z_tXxzMT(;?Dvo@Yq9!2EPQ64=(Jaj>+G+3}hcXRz>+VB*UvCsr1vQC+V(KqANuY`W5XX zg_PGIC*?#HMcOo^@+K@uZ$1;L;x&_0`Z=7JQJ$a9Cn;8uQEnx<=_jkqD9_ejl~JDM z`|YX-&1Fi4EVA$Nmh8)bS0Za`X}T<}pIIZxD6a%3B*Ux&JGAgB#r0$4f(2Cr+0%}LpL>llc9s1Xm;tYJah0x#> znYC)ZiFdaW8tEsS!zm3En|n~1;nlht=s#YVL-$`q=!t_^lLtt7_S%Hci`0>KVZ;D0 z0?$v&k@X9t4blMS0=~5WlDPnn0*Z%7Ink-ywwsispK{xOhua<{C8VvH8n{PM5smLS zDd9A}7f71uNTz~i;xyhKQX~EJy-3nTreT`ZMYNn&ZhaW>z-hC#KDEEK+zR^6b6fR4 zuhr#7Ebkva5)Keb=_i{<$*>KKn-QJQLrORu(JgQUBN^&PDf(VgC;gP^zm7FXd9&1E4M)y{ufk+x2hJVFZ0>vKVVz9Gs4W+xZ`Jq(6vd)kPqz?={kHM0mlEkQt{IREPsG8fJL0EmY?z5V`|&h`$u$1aFE zQioUL4+pOS+Kq~;a3nY@YJ%5<;OvB$!v_Ns)1ld5R8&U=J`@r2Ni)BMdI2(u8Yjzo z*ili10Hmm#1_C+)dwDd)i+Zd-2}|>60v2;a(a>yE2+WQLMb$)TTo9FDMjpW`9$_^e zEzv|Z(&{MBptN~EuH~yyJuNEJNY)V57}+-Eg6(-g?RhEj{o#lZ3eQIQK~z~c2^O9W z%)p0HN>m{MMHTWOF$V)_fB4d?qDF$xfSIwS{Q~GDinyu=BT5&K=1EaAJ{<~z0sZiT zap{l1#TThBRJluC-yQlwrGI1iUk|_Ax|?6OnO}GJ%1^GvRUMBtR8HQ#`rYP}Tg@kT zn_t{&e(_zyuDO28T>shh?!dXtfpZC+nKVl1OsKSK(_EF%6};=)Eo|8=Y+o(myfFv6T`~IIfw-#dkyii4jW=#A-bmzGmQ;y?;#())IkBWm z>hqR$Z=KxLGn;y5S6{uQuTEMDm#0>hYnOK|-P@M#2ek>a^_Kn}{rbc&X8(LP-aQbr zpWf^qSetnN<@a9xw?0U2+E2&Lt}m5T{^4gD%2>G~#0|AibRgF=C1te!VEB8(w_A5h z+crzv)(h9Kt-t!Iaie`CZt(m{XF`0>IkZ0UN&jw3?^a9iit2scd%C-Y zxAQ&~mfQ~u?5pmd4Sz6vzjn9w@K){N2lIb*<1cUg9lP7(-Rkis&BeEjKQ!K+-mU1^ ztmxP{I1@L|{>p4i8cJ6Vue$GD`swhF;ouWJRoL*v0FnXUyIMQ^-_Y8VMa?@!txE$* zL-lI+j-mO9k}7WdZ<1`+-?+YbJ)zJ0!SOebuhf8CiBkL0U^2gO`L$Sn^=crV-;k`R z*{JP`RUG=EK558bJ{~tzO3G7H*7{Z9XV*WtzB;p8(6v?2l{6PE3%9Pnb3JaZ0TqDn z6qhXNlP1e@OUzWUS`|0dBugtdn2uQKf$t3_b*823JG!!e{>>{iRqmp(g}|yCmhRt0 z(SY+=Q^m*;_1|c1Bb|o7snkOHKX^BdRWi!_!SPkIpi}U=lE-!GHAgwy)r`kkVf#us$gA_LP~%uV2a~(K|mLP2v9vVu;ObE`Zxx) zR2j9<04CK^rRU{Az%-oNNeAa;YG_oH>dELIP>24jxmSTB{!cH`5rYqKuXOqHC_tQJaSZDmqoHC!&MOM3xl zAhffsIXNRKWpmPP(%$q{&tY>pHDLSa$kDE04P@zV*onHC)fw$9Y01I z203pQBA_PuO@N@*w5XTgLldG!evz<9XaWFgE`Sd{;D0SRALY?fw5fGo7eyoez`70xcp=5VifZMi)Pjn%ILZl$90^vTytEpmAgc<$PrPx$*wQ zZsYN-#^awJ{SPO8c_RMO=x*QGR^M3CT6Syrhr@SUcdNTLtGhP3U*ECvkBXUFH{Q8% zcPv)iycUWVA5PjD*Gldm{IGo6cK8ch{p!N{(NEZqPdu`BtT%nq|8aZ5UcaVF*qiV5 zt*idj{gLPAo(Ha2t21G5y*GZpW_>nx&>3?KBHd@cXEjtw!{b`YWZN>Id3o2+m@rzmjf|94*1C3Xr|i(OHfd&7 zi+9XTfD-N9Pey6VW`BI1rVQ3a&sQgH1K1N!+AK_~XwqgCtddo+YK}qyIUInbykO@2 z7YPMGa8QA#vMVqIC^>~!p1jj7(!s$9Oz0P>Jp-o>57?*hfSsPu<#6If8i%Z;Fju-< zh7d5+p*2J5;#8Pg8KwrqjdZa&UU_mSc`&%i-ynn46b*sMTtwkAtdk(Lqh$4%-WYl- zqtamDTrGSzO=^!sgc^hjW$2dT_`e=F0-jA+ z0?-PUAP>KRRqWK@HU;5aw%$WW)DYIyq^BNEwO5`DXeDc!Ovo_YUj|!i&oyM)F{@Sa zS7CBx_txdqM?t0um`e6YYR+yY*5Z``Qd;Gl4AKZ+X+1$05P{Ks*Iph-ZJ3}!sc=<( z*6+xZB1f|tq+ys00W_ijSB93LEK?hVdMZE#a|b|HEujFq*P&iB0PqF+O!gi@bXUmf zvPqNnUXTFTLi}(#GM8W8??nZKBiJUgW|dP8-~pFvB&W|x6zD}0*1ih>oD76I>2`{v zPthtWk^`_f7v$4l1lW-VT}bjw{bUUdjvqexDcG({g|pTDb!7!uPnmK8j!@f&(pL5~$YtH{?ueBzsX*2L$j(JgCVf6nMu zoZ(l+#BWl5^%wj6s+^SL(apvcgI|r~al@9)uejXjjVBHn0sH|YhvjQ+{hUz?Mh;n< zUt{ZFHd}M~zsK@LSuJm4ZET4{@r}lm;vO4VzQhN3RJ#5Jmj7MK@&rQfL66U|d})T| zONnJumfzZE`3h2wEnmi!g5?*$@^-H5IhMC)A5l2FubarZXVg2AE%zJEF!rzYljraR zej@AsYy9Mo$PB|3W%>qZ*z@-L{H7$U-;}|K(ew>RjMAPm&Hz426bDb>D)X%*^9?Ku ztB};jW_ot3Ok0+%tDyjUl&3jlnRIDJZdKOY>WtjQq5I! zOvd^KFbP++7ip&P2-YZs0b$M2OTNI?a#h(qk*q2JT|T_w* zQVpO9D~PYql*hjen$B-P^CzJH)9L!eGVrx^sV5A6COwhwFqF?g>T0M7z}R-!EY4VW z8nUg%#$-%Ks87)$O4?jbZAV)|uqllphMJzCnuvpL8LoG`gKty%twL?WjG$zieq7|Qnag;#PmeE zZJ~Z7%tQh_Y(T<3aukx$XmElFOrl+7X`}KObNxo{f}(ADLDPo6wQ$Hw8tkoq{YI(@ zGcyOl$xA`l5NFWo!OKD3k-Jdkp1lG`GbbQ)2^^=II+)?WH6|2g4s}2kMVqjoZo`bf zAb(^ZS6}@Kj))3VKm-LvkL6y!A=0@b-MU~RU&I^q4j$tA)PfmrkxOSb@do&U@>|YV zcr;A^PBrQn@xYN!v>&U1t!@F%k-{0sDcE=h?cf8Y)4|CZAw@(w-zgEXV6v&Bp;A@> z&XUi1N}1&&9)@*}o*fz+Wd=NFNBWsIv@1UbyY&u-V}Uuzj5>#$eXvOnyY|vHIpgt; zxjiGysSAv|f1xv@2HQJw+KKXe`}=_aY6Bbg%qi#CIj0jEc*b1Nq#?R+_$1?D882e5 z@9a=7n<6>7r){5Sc(_+?jN0WcXx;sd(WYbIb)cUSIFPEw4kZPb)!420SD*-X(*778 z{CD97CuGTCPn-$oVbNJz{(JlDsXbR`gM3ph?B}DSplHW@B6tmli^W_wN;`Qz%;Wtx zQ3;+T=5m4QxggFJ)qG&~GVGPV8VY0n5e%$I!%%enH8d9R7mL&920@oDtq)KGKiHbSt zq>&^gqIkC=CklhxI6597YVhd^IKdGKL&t+#2xlunGN6JEsrVr?S_8a%CHzi~M5Le6 zzAc0%DIVRB1kwKq=7E5`sc>0Hn2J`E3E0Cvo-kQAiaH-_m8L_B18|zbRI+QV*fdte zjLhOd(o(#nPMC{s8Qw9hoQ;`lmeh~*=4H$Gk1R*u?t#Gip&u`-_1^wYGB1C*`>pFM z2flw}RUOZ(OBOL3Rc9VnHGELCrv6F!Qzc#8@tC45=;VdLvfTgni52JS`MAC9u|j3+ zNSX_l2Uab24&U^|&9%>T6uft@(svqe_QdjPRxic#8ZkjLg6DSZt$%OtUm8x7l`jn?3rklHA*y%#)-*pE+$ro>rV|AvH;=Bobn}JP z&UiunudF4HP{!(Tyx>5xw05Iz;$dCO2i%%-z3Uf8|NQ8KS7M!mvAQ8(zifmC{tMx* zp)BRQ=9*1&&Fa8U-8*K-6AM*z00QzARqvmF@BHd?th^H#>wbIWNe`88dv=PZbo!<4 z<@)cRjA=_(0+qNICCvvlQ6be6zYx}mauj@6u2?5_-#gwD3BtK8I8uBvx*^-r`w z>PsDEuqREnjgqFAsd+=!oX9V{)%Z^1Mp;WNzjbk7sqKnK8jrU47^(WV_ zz-_VrktuKSbV6^0RDpGID5*6pYnHFvH0_#eH_f%HqcL;Swzer@DSUVGru}ws+fuuv zK}SQbymfrV@^(+sVA;s8jT`C`C1p!zJ};?URwoKERnhW$ZaFLhkN7C7sq_t>8ef!d#oTSyZQg>H%rzK%6z3qIgRh8#I zqg0jxa8`?L<-kqD54BIqsZ!=~1!b>Ued*3|u)V?ZZsW?a-%xZ%TjFrfGbLTni>Mn< z(O-c7)xJFlPMt89{vXpvTKF~R{n`%)|6j;lfMm9U%-dxL)_b?h4(*ZKkvQ@qlKT{v zyKkS|*22Z%q}I4xw4%9Lv1_T@wA8JR$1KfpEqGPYyH{_v-BoYr*Dq<2A6$Lwg_Wwf zz6?7;UEENQ&(nNfTD7c6n9Fv})tlz()zfR-{r(Sq8#Tw`=Hsd3OUQ(p+x(qeWWxHp z);q1pgaa8S%uSmxk1DHvY^KUM|#Kute`^wZ^;m+*Z)cv^+!=FrVI|etbL-2jeNT#Fe z?&jYyASoN~cHMCxDP7s6v{2>sFj!R9uNk*14m~hzSM-1SM{rYNqYi3 zx3eX2=mZk92MPLO+JGg%-6L(m^7OX0di9lU?ZI{bwzmIo>A2RN)EPHSZ96*0BWn=| ziQYQmx&t`TAJ;i?!X4M0LC2Sx-kM#d*}@jg8S^p#XGj6i4yw?cc<*0I$qL-N}~4;{`BgOt)1j+;w6rx&9N4R?NZ2x z?b4>TnjM?tX(4P*K7|8WrT_FSprWi5&nN{<7HDd@kZo#FLh1kd&8sxkdXE0*-#E}C zYJ++2ugx}px8gtL7k#@j|Gyk9`gU#p!|wWT+tnjYouX|zbO|Aeu;2)w-%>dyf^ZZS zpf5h}iLsEVjleIdP(8F177fymV;mSBNc;W>@W}b&aME%ANlFPsVTgsX7e-+id!=y} z23BdDg&`K&Ss}m1aiHID1$iD;-ISP@VuB!wjH3szhLzBlXd;!u6XOBk3GI~N!vvxw zRRYLqCOjv=&yl2)2>1{z5m17?3~Gd84EVgKEajXKnvUXgvJ(2MgBmiY5_Rc}?T=7X zbT$*83&2=}Z$_^UbVCbeQTM`3cw%lkc#{7sctxw==(phpeVwL%O&$0(RsL(r_CKhe zFBLT1`6adI_5{8D1I0X~UXM|)Kcr4Rq&go`o=wX0kUF(Vo%%J!JTi7H=KfZLex~%> S0R@~6l+U#k(!r#qe)Jud%JJe?Aqbd zM!g%!v){~qGduIm@B4k<_^6}ahM@GV_=KAdgucZMy_j}|CkhBTG>AB4n{>`Ko+&m zfmk{@c>VZbk{=Wk(VGcDR^;sAA5b*3V!LM29yHV>Levw6z&0B|BAOo(iFqWNT2R=~ z4C=NudJyVBVM=daadSkCwngu+K7-b(tV<_$1bmr1Bsewt=2T#IZZ>k6n^G+oXQsyj0e{Jt7GYt>zjRmR^WSZ5fM9d>u(SukayqprvqFlE8Zc&6VlZ1b zs8NtCVXu;~1Mb3A0@pxl{WHLn8nIFnu9|U$u_*0>%4=5bI+h|rTs3Q-)9_e9<^fQW zv8#7uP)gWwRX0YVF{(166JE4p-CsflFfHEeyAvtt;vL>-l%`$-5&3X1MI$ zSN0qzd)l`>2R=RbyK`HfqZ^*1MbAr5?G~49h1zi-tFr>Z`rk6YX4d9z9r@&ECA#DJ z0CeT1B)pJk@Q2wA_DnswudS{IH7{VvTEyv0wG3kODngOVFiibvbiw z>xV`Tn*bG-Y@g0+LTVu`C8LUvPO)-Kii&)k#f(e1dV4r*gw)#E7ho+A zv^-jnd#=XwSJeaCz-2e+f$>7*v}tk#AuBqBL`o#Xm}IpQ)DkYeRe5%x5XG6oMxYK+ z`aU_5qeiHTmS~3;XbzR2z(yqLFL-OJUsY8E5#eU;*2^)mjR7Gwl=&LKaH=&K_fR^Xj;{iE~u&ih1>9V)xn^{(~U z-GO5F>0;-Zva2nB`Z@h`HoS4wY1>6+X8hAI*o#@uaRB%9_qGPgL|?SvI|@G$Sj}WV+y;xB*d1COrlrzvPzoOosi9%&af)V9<-fjXZ=(Di*xKK zt0$T4g*VxNFFRcGiW{9C_v8LipD!>y4$8jK*$MW7f9{IkKh0j4nVWziJ#u#BJUhd& z!BK85aPjigD96qO=K?d+v(JpZICJUJs4*Civt{c7K3L-!Hp}QlE2I|LY_F}aZxr}e zCk>fhieBg0t301FHp9pJ`fSr)yLNj58p0QR31TVI_)F@=5+DT<9PMJSlF6v3l2{4f zi8UJXTBQA&Ml79yevR;~vSvu&LDi*yJ`!6>3o%|+okkC^-Bq}jVrRukwa#}fx~U^t zQiiXtMwTtT2Fe`#r%2cku@TW-%;th85N`TU06E>aNu&V)o%_P$q1^$4k#4LTJ zc0a0wEWInE2nks`L$!W)bmgJtw-R=1EwR;bQHh0GXvA?xJA%)Vw!lhaOP@`q+o(Jgg1H?Y`oGl<%*>y5k|JdRnnjUvH5)F?B UNMf9L%rq13r$f!eDQ))u0EwLa7XSbN literal 0 HcmV?d00001 diff --git a/zotify/__pycache__/track.cpython-314.pyc b/zotify/__pycache__/track.cpython-314.pyc index 6f0a5e483101e958c752fb6d340e1a0ad72bc72c..bb2d3ba3bac85d1528c0aeb89d5cbf95ef7c19ae 100644 GIT binary patch delta 4063 zcmdT{du&_P89&E2e#Lp#P3zz|cH*>o_!T=&{D|ZD`q3t>-5STeDa1*dCT(#aiRZYl zFbJ(+AR%pC4~!t73u9VSfoPaF(P>PYk%m|&ZKml|&kHNlT0nrdmS*Z$5$v3MbDd~x z{R3%#%zu2p^ZT9eJLmhpbMCc${Yk-z=LBWCveIIJKk6s^adhytGP|>GqzGzn0%D(+ z-(c|-ioyD`ug1&{vUS-d4k>BjZ72H(8N??+iDS71zhLVfRRVc?7cFuCk7&$**xRB^9V-Zlgh z*4X3xJW~^CL+)*j@ClMi1yjjbBG!l{(#}wk4x0#hwpAP2aYzT#F(kC(TNr?2?2Y{| zDLjeOzXyG|eJ%Q8n;L$HWm%V0Xk*&L-$nF#IaMD<1nX>kIXvRBtg%obF&jFyy-IA2 zb}+Vx4ZX5m1v}6q_p0I3?3$il2`UN83WZ4`Oq-s4|8NT=?2MJM=nIb(08B;1zMAWo zA6nv{a4-(WZhs1ZeeB_Fy#+vwJz7y6AXCV*V=a0stSjs%RXStO@VWLM=WkJ>6F=21 zk|zpn(WZ~w+a>6=L>xBpa#pU`vwRR!vr@Pc#GYHx84=UjKhysPQ_8rkQchQc*bC_M zJ_me}G!OMgOIA{WU1;8~gFhgzK5-{|v0bOVRp>QFPEG;Ic5E!~ik&TN;AGlxJ?qQe ztF$i0Ni^7v9^Nr3J;(c^Ak&2!nS0>N$jeNi{+JxSr>hjVt-AZiTy?D8y1fFq`h8Ss za1Nd9|GX#G#4jQOW4Qhrpa#*FM2iXp#6e{obW$o1hZ=AfKT$9g2k6Ys4OCH3yo_@6 z*3LVmO(UJL)2AFW{crz>NA z6m|1gB>Xyi>rchn;m_F7doeBiIXkKy6qmcZ-2p17#&-{o;_%-a0R1R2(Efi|@abK{ zr9CUh%RQ3liCyJwzuloTIG3aXK6Bk`C`jGAh<;wHl3Ps<Z4#HtLQ0-Lgb-V&8Bgxo2U@=~_s;UgmllYbyw$Tl`vf}V~)j$cQr{rJVSU$QP~B@rt8TaPt2F`Im|0v+1# z5bP*Q&+WfS3A|MLx$$=e(>IENcEhQyx%JAKqfbreYMZ`4{_ObNz(TEKhRSJL&X&Dc zHuunirfWv{fwK1O;TxiIl`*GlKkq*0zRIh@(aX&J zR{{f<0t53!uS;H&Tv&g(mClarneELrwq9y-KJ`*|}T2wS#QCQwlSaSE&&vwol z=B6$*Ur_#Xb5;>t5>i#`Zb*Panf~E7zFj0ZAV$_B$arVT3K|Sc0OF%+GQ1?bLE+Jz zCCgXEf8O@O^u;6JgMyGKz57@xoSw(Orrzk0&_M_IwXl=GMJS^^4d7x4D_*QBWks#u zPVjd%bQ!^{jy4dyq-dr+cFBTB#6pRQhzl-y4IO9$zps$6P~E_Sn>G@>Vqj@b-avc0 zra{i`bK(J5>|mW2yV==y8#w2yaxICuYHy%v8+cFU?Iie@8Vgacm5W(WC}^)4Tr<<{ z1mEAts_z>)=x`FB56mq4z^R~pLhzv+yJGxEGR?{#RdArd#bM`<8@+ade^qaweFov* zioDpnT6)uMBk-wHL*SNDLMCr%>2l(`)Yd@zA^5w(*Gcd*xs0fvsa3S!1wM26*Ae=K zoGu~wrH0Kv<7t>Kr9JIb#$%>P<1!vAv~oA(+Z$Bo>)*IY{b1bE)faUX;%#~K^&n-*e+byUW;W?87h4ur2;=G8LV*HW7C zHAqRd8J_`jw>0CkOW6hd&`SFqRL0-V`TOmVNp6IhtnLgYDBJ|mW7*W@QgMunS`uPVxDXRL2 zy#92=OcL`kF`u9p9vOu-2%fM(J#w7bFIz+2kHp}xS5JH?fYs>LN9)!V5&2URGDyr0 nLMe!@Ke|!GzERhp%Ewf2KQcY`qYCnO{dSME0Un3R4($Ce2rtyU delta 2381 zcmd5;?N3uz7(eG;T3XuzrBtMAX=zIbv;~CHR$eVqMWm*?beET15C$8fY&5{8yeKlW z%|$aep3Ui|SvHrsm@RX;**CY$4_h+*ufOwp`BWcBZ<>pRl%qB1zEE>q#wtT9F)#Qvo17eaG?*O96>ruD*<@L0GAIj8z^OVXD0?!r zW?t)}w=FKtG8MFE^Exia--1w&8zCd&5$9b);yn&(3EN<8I8)Ux?Fc^$r-B*qd^pu? z9V%owSe6P{?EpoY#!b`npOyG=$|Xi+3Z^Mw&edra@l z506SamoYrPtV>$70KyeHxF4=;&W6_>Q|g@^DC!a>q&RQnY#s(C)L9yOI?tBRzuB{U=6C4{7O_@%f@Bl!?1BT~p0agTzb$%G51+RV_(>EMzx z4Mv+(OBE~Zyweq*n8eO#G4J9FT&m@B_%tpLVoh1*)%4$ka{0dG#i&Pk9g3SXZ6NLB zjl7&MZ9d$5n%D3ik22vmBjHWh*1QHEpXVWh>8>K5Kzx~7D8lJqmu39IlvcNt$o-E!hCDp5|Y_d#xJ0e%m{ty}PMIMq4; z8(L^}NYcV5LEYw1N4(O* zgg;#zas}|6Zv0u?THC6G6P*SuQY(X2NM^%Z<0W<8N@y_KnR9=&*aL-I%KopFebe2X z>05rV2R;${TUXzsDg9h(Q7eux9`ZTS^&BIWZd+_YAMQgHtO@nue80G_=Nnuxs$F5T z*(?@IQzXDeHg4Wvv5v~D7UG*>2YP!WGt7=BXK-yFNE32Sz7OO_w=&U;KJFC~keYQG3 z#qW%P6pArtz)kUoRr#Y>@uOT$rMif^eyj|n@6JHeX$mT{>{M0<3JLt|rp9@leN>Ll z``48d_+`RNX}{Fa$OU_xc2Q>|#znt<453S=+6n@{8v{Dx`=hwlPvEb--0>9I-b|(YTs5xJEXb53m4y`5xLLW7r6imMt7n&i{dXnh{F{ZTZeVwF#4r`_*0OgsG;lW! zQ(YU^Gt_}#CVSVi-W3R@vuA?oGHRa*8q|SC6F~`xub$?Yk1`zgHxg5K+eiJ t%Kn>*b}H&A>kfj+0}gq7QWfyY0TXVAa|b?5qkoQWwIti|2p;qj_J7}kJp%v$ diff --git a/zotify/album.py b/zotify/album.py index 99ddb81..9ede74a 100644 --- a/zotify/album.py +++ b/zotify/album.py @@ -78,8 +78,8 @@ def download_album(album): track[ID], extra_keys={ 'album_num': str(n).zfill(2), + 'album_total': str(len(tracks)), 'album_id': album, - # Used by download_track() to decide whether to insert a Disc folder. 'album_multi_disc': '1' if album_multi_disc else '0', }, disable_progressbar=True diff --git a/zotify/app.py b/zotify/app.py index c41fe6a..b103829 100644 --- a/zotify/app.py +++ b/zotify/app.py @@ -118,6 +118,7 @@ def download_from_urls(urls: list[str]) -> bool: 'playlist_song_name': track_obj[NAME], 'playlist': name, 'playlist_num': str(enum).zfill(char_num), + 'playlist_total': str(len(playlist_songs)), 'playlist_id': playlist_id, 'playlist_track_id': track_obj[ID] }) diff --git a/zotify/playlist.py b/zotify/playlist.py index 166a19d..043aebf 100644 --- a/zotify/playlist.py +++ b/zotify/playlist.py @@ -67,7 +67,12 @@ def download_playlist(playlist): download_track( 'extplaylist', song[TRACK][ID], - extra_keys={'playlist': pl_name, 'playlist_num': str(enum).zfill(2)}, + extra_keys={ + 'playlist': pl_name, + 'playlist_num': str(enum).zfill(2), + 'playlist_total': str(len(playlist_songs)), + 'playlist_id': playlist[ID], + }, disable_progressbar=True ) p_bar.set_description(song[TRACK][NAME]) diff --git a/zotify/track.py b/zotify/track.py index 1e80afc..08c432f 100644 --- a/zotify/track.py +++ b/zotify/track.py @@ -372,7 +372,62 @@ def download_track(mode: str, track_id: str, extra_keys=None, disable_progressba Printer.print(PrintChannel.SKIPS, f"### LYRICS_UNAVAILABLE: Lyrics for {song_name} not available or API returned empty/errored response ###") else: - Printer.print(PrintChannel.PROGRESS_INFO, '\n### STARTING "' + song_name + '" ###' + "\n") + prog_prefix = '' + if mode == 'album': + cur = extra_keys.get('album_num') + total = extra_keys.get('album_total') + + if cur and not total: + # No info about total tracks? Let's query the album from Spotify's API in last resort + try: + album_id = extra_keys.get('album_id') + if album_id: + locale = Zotify.CONFIG.get_locale() + resp = Zotify.invoke_url_with_params( + f'https://api.spotify.com/v1/albums/{album_id}/tracks', + limit=1, + offset=0, + market='from_token', + locale=locale, + ) + total_val = resp.get('total') if isinstance(resp, dict) else None + if total_val is not None: + total = str(total_val) + except Exception: + total = total + + if cur and total: + prog_prefix = f'({cur}/{total}) ' + elif mode in ('playlist', 'extplaylist'): + cur = extra_keys.get('playlist_num') + total = extra_keys.get('playlist_total') + + if cur and not total: + # Same fallback for total tracks in playlist + try: + playlist_id = extra_keys.get('playlist_id') + if playlist_id: + locale = Zotify.CONFIG.get_locale() + resp = Zotify.invoke_url_with_params( + f'https://api.spotify.com/v1/playlists/{playlist_id}/tracks', + limit=1, + offset=0, + market='from_token', + locale=locale, + ) + total_val = resp.get('total') if isinstance(resp, dict) else None + if total_val is not None: + total = str(total_val) + except Exception: + total = total + + if cur and total: + prog_prefix = f'({cur}/{total}) ' + + Printer.print( + PrintChannel.PROGRESS_INFO, + f'\n### {prog_prefix}STARTING "{song_name}" ###\n' + ) if ext == 'ogg': # SpotiClub : TEMP? : For albums/playlists, wait 5 seconds between OGG tracks to avoid # spamming the SpotiClub API for audio keys.