From ff7f396f9d5567fcce4602025486640c5bfb09ed Mon Sep 17 00:00:00 2001 From: laride <198868291+laride@users.noreply.github.com> Date: Thu, 13 Mar 2025 12:14:17 +0800 Subject: [PATCH] feat: Audio loudness normalization for assets files (#332) --- main/assets/common/exclamation.p3 | Bin 1936 -> 1702 bytes main/assets/common/low_battery.p3 | Bin 2473 -> 2433 bytes main/assets/common/success.p3 | Bin 1827 -> 2040 bytes main/assets/common/vibration.p3 | Bin 1946 -> 1815 bytes scripts/p3_tools/README.md | 30 ++++++++++++-- scripts/p3_tools/convert_audio_to_p3.py | 48 ++++++++++++++-------- scripts/p3_tools/convert_p3_to_audio.py | 51 ++++++++++++++++++++++++ scripts/p3_tools/requirements.txt | 4 +- 8 files changed, 112 insertions(+), 21 deletions(-) create mode 100644 scripts/p3_tools/convert_p3_to_audio.py diff --git a/main/assets/common/exclamation.p3 b/main/assets/common/exclamation.p3 index 65396687365ec1591741bbfd1cd2c1efe22c1733..17e96cf715da174e8c2af113c3385ee9a0452c9d 100644 GIT binary patch literal 1702 zcmV;X23h$4001Lc0{NEQUd&Fe*XZKX!2foI{BwzaO{=qr4f#x$umEE$f?xmu04P`j zWc$o-6w8a^9bXTaRxAWi^O;fDrRGo4G%~3CdH@N_V}2MW z=S@aE&vzoRM^(nrv=FlM)bs`|b0TIPU9-t`>q)Y|k>s_)n&4kx0001zSm# zm(|>Ym2pDFZ(s%c3-u)xP99=Ur7-{i07_UQq89$K&B>Q^p8Y=ul2buS!L?wbq^4(dc6%%Idg zUMQp)FEmr45qtkI&_q8#I5o#Ie^kfJSw1vliH2KsclW%&O~`Xp*+!t-`IsZ21wRJ1 zJj>yag@_+?KF-#_rytlwF)DaSwn003NA#-H+qS)I;+HlC{4Pdi88vW1}0{};KgTnIw| z!wYcPK>aK%lm@5xR-sCzFE4VlODHw;Y+HNFIwG?rvLx|l33^%CE5NtCpF9~ zW-qJ({y#CEn&|*b-F4N{Y|T)|CUb{ajt2_gE1Ui#3u@K|(@{YC#uUBhwAYP$#B2m3 zdeI|2yAVj!)Cg38ZL zy=FH>HRMaS+5ALy;Pa4o(X6y=E^nHzJK{jOf2WUGIl(ebM|mp38PI+ zRzXVyVkHtplczIGVa6RBE4RnBeWbyGYH^tI{-qh6ehb7h9nqljl-?Cp6;KkY=&T|m z^{N4U56Srcnu}HbU3tUVefa%60001eSm#@@rDTS3Ke1S%t7<355gPnond=Seu5A w+HwQoT?_@_VFC}**FY1hMWc{Dsc{lRcmx-Nt6BE!6jnkGN;&r49sMBIAlMKk)c^nh literal 1936 zcmV;B2XFWQ001ah0{JjayA9?&*_e>A5$tUBl>vf|aBBfve z001gj0-Hk(p_Ql9&(>paD#vivA#DFnHq!oSuIF0{T9V#EoiE}OCiHKg)&KwiEm#7Z zJnlDtfQD#Gu+=Zus+dk4JIa6*`C~6l?}nLcxXp1KJ4zlE=;er2*Kq&<05wzT?%U1}^MQ#C%h_@82MeD1d9A8z&&Jgk9nX8W)6~V4>}oPKvH8FN003oJ zVximtbX){Qk0xKY(?u_?UO6j-wdEXq%u!>Krct>4O`bmZ$!*xWG!!q9{A=atqocI* z&CEzwuO#kpi{I`Yq^`|24?yv?*+SRa*=Cgw4|JJw_6hr@)$arWRlX6$?vsI}0001w zSm{R1#F8JSgx^`&Ou8+)5mXi?LqnnN*j^z;GtM9}Ll`C5b)L=}(hbMm`61@V_sX+^ zyIS%)CqC1zFy?%Y^`D$XwEr2LRryO?WLBK_$*pgg(7do*fBtxS0X;OkE60JoOrR^b zF`u!XKlJVBev76YVvVdyZ|NXgl@}&i^LYBq9YB<}3Gko*005O(=|;`Ok_FjS zW!RU`Fh+9adQ1M7<#PcbIa`QC6#~hdt!aF!QVkdDCg3cWRE!FJoogCimm|lTxmkN5 z0k#I`{WC#uG(n?SmS2-9Kj`kuKr;cderRR|HnVCiNllPF_M^r)a{STHFPH*0{!SEp zKjD|t7JG>6IyB5@6w&}@;`|+jhGUh2K|2@#E;Z^9^2OQU0001YSjOB7MH{^)1$(NX zvZ$gpH?xc7Z6&SsgzDly_y$_a(!O)D;=pKV^h#^J42dA>4J0D7D=v`F`8>^iaS;?D zJ$DvQchF*rKwu)ZYmA1WoxGpllv6(FL9+b+E-%69=E_Iyfbkq{D(1IizV2~FxO!bQ z;M~9f002{1BNZ2|T-MQdnP%TKtEIuslZ>gHKukzMX)#n8e3n!{MNoz%z`&u9rmH0m zEw=0<)>dQGqm+jeme@90#1(`AWq~W`Mf+20<8-cj@867|8&d!P0EbxVnA>+`C+|F# zA4VJ6zKdK^GY+#%5!kiXArFT++1l%p+<5sc=%z&P2^WrDE+TD+HuQc(g}tXTMWzZu5}b0t+3&b8Lf4B!6YlPJKc`jNeP_`VXXO68$Hy4%s}lG4wo3 z14lhfn8j1(265m3003)P$BC;k6A5|RmBGwVdE$RO%^(*{_^v3i`>(ql*`4=`4UjU9 z$6B?evnvaY-=(ltu+%cRZ%BWiJ_~mZ32R$LmGmxiSQjO|3}nsAG`=>{PEVOT7J?Y{ z`+FfFNOBqAb9HNO`fU`l-8cXM083aTY05)~;?+;8!0+g-@38x3)c>DO3iK9ezX)Dw}uMBgYSct4=SLOdNRWaC!_3dOFD*Ma;VC30%RGynhqlvwFt zh1H3a_wFtsQA4z0k7+Ju_oR-3$T2jZ7xekcm4of_xiQZQRacO{8Z~fR!T9qbq)VGi z-5B9c*VA$q5;sC#C=V57Ggiz~CKmY&$l33{SH8Fu- z1gB+y1ra@tBx7LGMRg~)i3T}iB@Rp@f4lyZbmD!96v+rG#n@@h1(e&zSG&qT-WCe- zK!~_7ca$fH5vaOT@w0D+8JrqGL(jip)Hk3200633==Le4UgQy}6bq72 zFkeb2H|Gdv!I_7fnUziG@D}F$CrrLEd7MsB!VO7#r5$X^F=+Zq|Hm4rZ0mSB$BKu{ z&+q29YJK@T@gpx7s@!WxSNr#EdL+LElm0Xpcv*5f&1{-}hVSnOH18HX-EgqXmz>fv zBvJv`C!-hG2VUvE#*t!!H7C#HYBviRDQZeZO%9s`Is2x}6SCR$UD;)yyYKZAbkpjl zkN^Mxs#xgqH8Fu;0ZyaJOVMH^y#6ZP%~|2| zgyxxw<^zAIv*WX`*Mp%IfTgy<1$EAs!piRDTavvoIJZ`292+=un1bgFOSNU3PdVSK zgqj;HG30hV6bJ_o#r!HiCPUlK;0&sKt#Y1PqKmM$-AVnZ`}cco=>{!T4&@CXO{-mK WV?V@q!Gr9Ln`LTQ;@V`UoIS{ojjQnh diff --git a/main/assets/common/low_battery.p3 b/main/assets/common/low_battery.p3 index 3106473396e9400ac75a7815930f9c065d49782e..03669ef9ccdca9ada640e7fe4b7fbf2024bb8db5 100644 GIT binary patch literal 2433 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 literal 2473 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 diff --git a/main/assets/common/success.p3 b/main/assets/common/success.p3 index 353a1b28909f2f7dce5ac0a9620a94501eba596c..4f1bd1cf5a04929a305b2788359fb9a9ed7d6e23 100644 GIT binary patch literal 2040 zcmVV^rO((eC8#o+|%!=uYkL#906>fYE^% z2*w6*P)ptLSRN)P{Yih}fp5aNijOA3xT@mBVz@?+``cHLF`#xvrnjDc-=ei#gJ z;exsw^p`I@j=fk^uGl0^71+u=A=_3kU63-qF&0zqY_Cm0^PH_BQzTW0MzLCgIfq@9 zl$@0wB%z>4oUFj026BQ*}=ZikA8!&lc;3 ze1zQF%HaM3Rb7Vs(w+jW63f&^LuGxN`9W<3q1I!D<^sn$Xk01y=n5}G&DiWiWf`Xj zgFL(O(UsM=L7C6AE`mt`IK6=t{}m2jpD$h8YP!Jrp_B;u?MITNY)mgk2^baT|0JK=^5jGucUPsb47aBpVewH{H zyv_0+WOat7pbAm(q{KM75rZMZp((|(PiY;yG5{9cE*h@>Uxer(BxRz0`E8Td?{@GJ z&h_4kg(XX*8+WRov0B^TYTx)|fK4>d2kOC2mxr8ro8JPoRrK;X%~&?)+caYxR&Zaz z$U8bX_9rzT4ixatJoFWXF`1sd_4nP)@`P^bqbFH&))KMCO*BCSTjvt{l>XiU@*)O< zeQB8s2@C)L0Jd1@LZ-RizeV7~5aaL3YijY$6->fzWsvD@5Fi0m(ID1yIs!(tDH`Iy zY%4t>>wwZbx;=FS^b%Dq7mqUdZpj}AL>Z&cb^uF{{m5sW+4cok88DeLmLCUGBx1f*Zw3VD zTbz#~d$aqy%>;aN-1-f3_;q3bXP^loV6Q<3S)T=+)Bp+K0001?Sm(*^-%9*bq_lT} zYW4rhyi^|M{zox{E*!o6+2R|HclK;a@W+%ddred&eS{#827$ob5j3~`GzYp>E}K}1 z{)^L#HHE54_r1Rp#gJS`MmyQxl2wcki^3}#qq;yNuV|Y4zZJSj`>1Q2h)(n5cnAhf zj4aCU3IoCTis6A6C9e>^`|c88lAfgp{$cU9Fob@~7;WN+`Bp}kidg(tFaQ7mlUU~g zrOX8bHiq-=PazmvPV%pR;63x}d7qJSWJruS-!}}z`Dth{+s!4M??pcdei|}1pdis} ztyRvgzsW&CeNBupjvqv5A|MK|wZIwv)>1vSDa;qi6;#h%J4*}q%u569SpeORJl+DJ z4)ds8NpK&*vqOBzIlmKpi0?`)eK7EXVr}dcC?w=gqb6XQ=4nQp0001qSl}rAuFU4H z&x8re2kuRUn@j5SrCn%#LDFlOL6r9vR|I_@Sw>z5vv~eWGVa}MT>J?Vb#-{KN!;<~ z1AiQ3-MAll;W8HGPbp$+kjV7^I$qn1!QO0cuwppbvC>d8j4O+g#%$Zs%%(h5@rSeS zbmhyO9RQ#2kRvoFH8c^|!6oN=pv25BgYp4ryp586>{^k__ z8aN?7x08&^0ne1Z}J^yrd z5zL%wWAIs1Hplt=8g6#f#z6IL#9AZy*ROX&>1yxu_L5co{y1nB2~|Y}af_s-!|hPs zm@N@n>bN$lQt*zO<{vIjcjdX@0000;SOAy|6uvE~@{Y>+_)u3oJa-Ni^3?5-9mdyz zAQV5l+}th+Mezv>sdX-3G;jYGu(Fch2E~(1BLbsST*`@=u`-BttqZ;&0000`SOkiS zg$Ww11ohcMp&zrV0>VOuyu>I=S}^a*JgIPg3$?z~!CEIyA*0H?x~HX_RtbW=db1__ z%xi)LZPw%xvknkn4K2|zSpU5~RcdQJ0000?SOrtUgh;6aCvN{XTcFuCt61+XzNEy& z63+z$DOR#+?J3+gwo{BTQSZgF?{N63elKC&Y7Yc`VubY4{L)oZ&Vs1jw<*1betVGM z0000~SOm`Qq&%~!*#^WG`rCq-2UZ(&d_rHY&9&*4->xi2nJ37&IOI(mT!oAf1Z(7u z(`VvexlYDNzTfWAyxada_1tfjwheP!c$puS6%VaqqFfJv0000?SOk8j!a>;Q~Fk463_5jlfr_*1K-RLBG#x}Bzy=Xjtzay4p z2sDL0{~<{9%WGKQq!$80&aAh%%{0j*2ti?~vu>}yDjv*vpM literal 1827 zcmV+;2i*7o0022yBKa^)yA9?)ZH{NzZ9%M|SrwA84Vb;|TtdLfib}WcAkbg$9^*Wp z@5v1?l`R)`utYh|zEJmo0001wSmvwlfANwDprDXAoV}MSV$cSozuX21Y_%Mnb0x*@ zb~d}*+GDG0@O0%fOsns4pT`8@o{l^j{o(kna%2@$FWF;+GvCC%@C7YZa_-MK)(%C(34>0ZoD@|h`p#&x|}Br;N}Q^BiGpwDcwyzTBkz--cy*P&~yDosVcn&n*LF zwE!x-{y%iB$9`cWB%D?N00664=iOsLzE?LWa@+|tZTTeBul&W>u_FR4Ui$Y9!!*Lt zHE%U*p%zI@seEPUlSI%N^N7asX@a=U{Qiv%P;YBT1}r{W7XD<}Z&ADH^*<9W=HOQu zw~%=4?Kv@-`<7<8|3nhQSz0I@#uA7bRH!>!7SKlp4=73vU-9swjZ!jVktfw@qT#`Y zfA#M6?eSm^W{SU&+AuruuTzOxI4QYkX1uSTYHX>EDJB2_0JK=tz1xzHnO=rvtg2_GvuevlhpE1Q zoK3ZQ0+KmZURI55SgVlo{ca1Hu#YPNEN>t9H)7gcfes@R=n;#Lq<6)U?cAw|Ec8jBsSEJ2X#MdVc zrD?90%QrI9yqbn%7cP*>rz4qjx%{VV@trPl=(nX!u8hY1*_;AXO$d6*$O(5)zG%p2 z%lQm#E!he97LoHCL`<#k63%td?p}}p004kk;2LtiFU_#Fg6+xtv zJ9)^trtOri?zufyP{mTvpHNL6pI9I%gTZhGrJw`vM8_u$RTZWD{^h^Yhs5cLNiw}cAx1`R1w-<4)fL`Je!qv5;gi7Mj$gLZzLsGC z004DZV0)VjtUnN!u4_Y@XqzipYCRh*l(<{@Dbv!tLDurtV0Lc2x-4bi5eP{^r32G^ z7ckL`l);@(CDRSv!)!fzlb{>yIsp0ouPTSCdupx)GAXPwbziX=boF4*XX4QmZnNj7 zTb4Kahi35L zdOnQyr(Bz->q^rO+tuQnEXAWibr=MtX6TMYf6Q&l=K>`Yqi<-82$x!Y%`mHtXjzj# z_Fw=2083Z{O5nd0PS;N)-l}NsufZ%@9w>(!=f{(`5s9eke`WptU`0h?mNg>K2PM&N z(dty!W8C>8Epu?UNd`j9Wze~;6L3hG=P<3BKmY&$MOXt$;J+%WsgLmb@%q^_U)wne zAkj5*o{{$*oj#~nLoCoENV+LDtD@pZDxwQ^1l29RbGX^6ac+2%Dfgk3GR-mPldu2) z07_T_+VcDym^u9pybFJmi7+pMmYGlVCr%tK z3o}5XNft2`iKgr@r{X;we`Kbm|*vP^uy0000zSOVAx zcw=K?Jqw?md`&W&(3_oSrKvpqdGJ(*6F&(Bwd<)B z0YGX!5go8zd?Zg^(BeJmzbPZMzqe)a!pQFU;LC}ORSf~rw)tEGKmY&$Nmv5b`o3L~ zC6@-qcAwdBf5})D8V_Ix0^@fr;J(dGZ=VM~24`P9(ysrmP8h$nzBPeQyv#jDS RxPYG!;0YtiF`ZZ@CqP@tc+mg= diff --git a/main/assets/common/vibration.p3 b/main/assets/common/vibration.p3 index dc876acd28485be0dbdeec77c1fe58190bdd289d..99724de3ab8b84552c26d55a8172435bc8d1f5d9 100644 GIT binary patch literal 1815 zcmV+y2k7_!001~xBKehu@L{GC-6E1e)%XTXEj_{FeX!|_LsauJjG0HpE||z{0UL97xzC7X6d2sd$f;%B zp^xlsy1ieZL{f%z3ci$ko-;T^!tV|p8K z5P_RF1E4-#q7?1HaDD^DB2AJGc~nZfA)?_F9d*QWF|F}if9>lPD9=>fZfpUVqY}DY z=fszWI*RjG0000`SOqptdww_*aU1!-9u^mjWqD-dkB7hD+8)HS*nl*INv9|n?`=+T z8Y1oARa*mPm<=>WJ0})Isi^7%0skQE-^`LE?)uVUjeXsaSo=Q60000^SOdXf(bJt9 zMMB)`f4(9*zK-mx91oIdR6);Jr$LpT&;UY zFniivzp`C(Dc4XfTcij&cVGYj09#lmD@tFM0O;qgiW}j(0f)?<(h@>=Si`SbV5H4l z8WYo~jl_{197@q=5(kM1$Lq|Jovsdb`sea<2_CI%9Q~5ou`G4Yy#14qY3QbThHgs2BN|QHvf;yo=zffJ>&SU zy7C{#L%woLMQ6lg){0iDbKp*Sp+|dxva(Kp`8*~A?OT*pJ%3O~u#M#>cS=l3UoZdw z0Et-WHVM@V70oLIphyIxOlbd1Z|Jap-ZR4U-%D5;?&&IVk1qDg?Qf5UB^Pvpc=?tx z?>fLcG;SWaUnixo`cE}2mx-9lLRq=(Oo&EL6)C;xkRzIo&Fn@)KbUrWu004_v=vAOyz)snRcb?K^ zB#Ctp=Ji*q$GRs(hxf3aJ?wVTdybO59^3r81jTc&afix;{wf&log1{F9pO|IAwU2C0CHI8;XWCZ?XoJ)c@epVIVqY`dE`{dFI3ifU`@jD`=)e=dmeAo9o&IoxRmr+0Bii*FBC(+K^R%LF z)9SvJwRUu`F>lh+r<su1);W9(;d zExL+}fOh%nZIk(IA)1_~i+ORbI`}>rl-kWX+_LCzy#+T5v?932=iyJ8SdRY}5qiEb z!%^S!)L(6-Z@J0Xu>4@S(*LEuTjT%$0AN_>|J*K}BXMKI+aQF5{IaU&8;s}059}8J z0~n5KYMkBFNvMRzdri-c4?ZTf*Pz(x+kLZ(I<0yC3{ut94t|Jo@$8k&q~~Y5@;-Iq zsG$n>Rp<}jRK4;scLsl}`k(*+0C`yGT~~LsBLH}LzS~JTjGM2OUGUyRu#EgTd3P3$ zV97DyuzfFaU%^7U3!|dshs70jZ{=%_5V)Sh;f&9KaM|+Tano5T$m(q4TbP%|o{!hO z8B3e~8(9M&nDJQ~uK|w-LguiQv3So*3{s3p#~^IQNb2(PxXeFAfgDw;2cGpExXyZJyDkyRmZgk7LVIQEMbtN@tn?QZP5(x%>`U z0ZY%56eW$<45!20Mj&tV>hQx=W+B<|>9FOTT=eAB%oqHq#1S!_$pR57g>hn+kd*G) zNeC`FfB*mhR9Ff(2;r~7NI{keHa02%N0K^ySvxz=%oZVZAu)cwsissVOMa)m1_TUF zK4kFjvocY?xzZm*GXg5xHW+7oBE@>&JDBPGawl`i6Db|qGDN6q-T(jqQCI|jzwEcy z3(K3Vyy;!H>RGph>bTy0g^2m#YW$X#f;#QRrsOTPP0~2er(SW0?gwCGRI&2*6~4u8 z{=x&18l@2s8{z1)3=VC6a|xcWglGT&08m&1=Fm0YZ}9(w(eb)yKt^w)&DwdOdlm_Y z3h;SJRCNRF*gI$%O+%c(U~P2Z(uGc``?*yLVdAwz%1DOLjIX~%rR+z;VGRT=L F@IaIzckTcH literal 1946 zcmV;L2W9vG0028!V)-ylyA9+!8J3$__MqVS^gyj|k>ovv2PBqJ@_}b*R84NmNat-! z{nG^lW$TdqK5%Br-Ujv;#8?0T0BTs_(50~PCRcSn5dIYC=>}DwZ2i@qReFRsojPT5 zgB7J?T>0@?>8-)-ekN4h^&JddB9!l_DLZ^HHUa$JYH`ZHWV&r%Ra>zG~fMw^(j#?Y$+6pV~t- z<0=@=_*SwFX8C$h@k*>2^6ykA=5lfu>p2h&KSFwIOn%zcH}PWy00015SP4Rh@s@Mt z8I~E96{-<<7&SrIr!#F?`iPc+X2 zV&+0kfMpM)@wcA-`_X+Z6O5|H#u9qMuR9V!>)-$Y09RNDc|T0OE0NIRUEz0e00vl6 zy(#*JBma&uGz)HuLZ6=a3f52CIb?)uX8DHEm<*bH%_fIyZKamS%vjgBWtng9{22yY z_53Z*U*EM|{$j6*jmCO=zyJUMZdl?UibfO${LcoK$Lpx_>azt~$V2z{tX#1;{`bAs zx+`x3F>;b#(u6LV73U-Yt+e5GZOZOHIZA7Mg~|CahoCX#BL>-!5jp@`QtAENmRP?@ zfAY2e5KcLt*sOo*Ug26!NO$gs;V5e7;g$pd004Se;4+S5kNp2gp!q|;T}gg!t%sA9 z#W_=olZaRO#|I$=q1z+Iss;p3V8l@ZbS8Z0Le~%U$lD3Eq`8%I0hsjB49CYUuFVWv zFQYxE4abLVsBeR{D`VR|OpM|;w%1n(c|lQfLs1mirj=YiWyIB`VB1dJkcB9Q&Hw-a zj9BVhea9z@)qvdf%BNt2nbYdu4kJ|g8jHP4t(wu&*nA`(Io&LDQ-fb%7aCTOwfGAZ=^y{ZdI(!l!bUO&BQf| z)wFl2-(=a_%WsgrIxB#6Eni(^x$43^G~+6ef^vK{g2bi}9smFUnpo;;R+Pht8N&or zIb)9s?GC%5*6K*z5 z34JY2=y$A>|*$9 zzdlN3UoY#V<^kv06|1y1JE3njW+744jXf>l$#}ofmV-8+dDnevJkDcuDkxo+7vyUB z0001LSn6G&Gi0Gl2Foe~v?Haq4YC>N(CW;`3#?UpD>+bgt!tWCw`O;RPK595W@2A0 zO~`fn=Cn>-IydAkL+~v#j~^dr?}dn|l8O#K7zYktCl}7BkC?uG1t@x^F%vS>iFR8` zC(D4F{onuq0E1ZSgt2~5`jrUb*kC>49yS+ez{BcjIg`*_WZkqGZC)UAiBsBlM>Z6a zmCm8)Pf}K3I4c0{1IN*?X8E(rF@@{J#4x^4e?EwS)iKxd1YC=gB2HOeSPTABAqFXn z9o@Gbu__34cZIe!C|XaE2JfLQAl`vU0nQSz=R zL1FOf+0|=uy7b&oZ}=065TA*x@Epm--Mg#KjZMJKG&qv|M(Wmyr*w3ystTcBm@!^0 zw+F2m3(;;rKyoqP+lOx&Sx+%6VmcWbOtEt^l~>;8(GASWYdL_$IvtcM=S=lhoyAra z<_AT`dg~zFjtp9^AOHXWU04kioWw~}nijG&o;#>0VhUVA#VR!;AbL3?XrTlwl(jXx zrVEK%8&p70`|yF0a)?7JsDucc{~~!QVpwOgC}HeGK%@y)&W#gOLaITjg2Ic``$n1? zsi-St&UDCj3Lu?s9B+C_qw1)nsRPSYKZ z`0gc^sYk&H5sdOni)~Ho@GHpKs#s> <输出P3文件> +python convert_audio_to_p3.py <输入音频文件> <输出P3文件> [-l LUFS] [-d] ``` +其中,可选选项 `-l` 用于指定响度标准化的目标响度,默认为 -16 LUFS;可选选项 `-d` 可以禁用响度标准化。 + +如果输入的音频文件符合下面的任一条件,建议使用 `-d` 禁用响度标准化: +- 音频过短 +- 音频已经调整过响度 +- 音频来自默认 TTS (小智当前使用的 TTS 的默认响度已是 -16 LUFS) + 例如: ```bash python convert_audio_to_p3.py input.mp3 output.p3 @@ -38,12 +45,29 @@ python play_p3.py python play_p3.py output.p3 ``` +## 3. 音频转回工具 (convert_p3_to_audio.py) + +将P3格式转换回普通音频文件。 + +### 使用方法 + +```bash +python convert_p3_to_audio.py <输入P3文件> <输出音频文件> +``` + +输出音频文件需要有扩展名。 + +例如: +```bash +python convert_p3_to_audio.py input.p3 output.wav +``` + ## 依赖安装 在使用这些脚本前,请确保安装了所需的Python库: ```bash -pip install librosa opuslib numpy tqdm sounddevice +pip install librosa opuslib numpy tqdm sounddevice pyloudnorm soundfile ``` 或者使用提供的requirements.txt文件: diff --git a/scripts/p3_tools/convert_audio_to_p3.py b/scripts/p3_tools/convert_audio_to_p3.py index 5a92f829..519d6620 100644 --- a/scripts/p3_tools/convert_audio_to_p3.py +++ b/scripts/p3_tools/convert_audio_to_p3.py @@ -5,44 +5,58 @@ import struct import sys import tqdm import numpy as np +import argparse +import pyloudnorm as pyln -def encode_audio_to_opus(input_file, output_file): +def encode_audio_to_opus(input_file, output_file, target_lufs=None): # Load audio file using librosa audio, sample_rate = librosa.load(input_file, sr=None, mono=False, dtype=np.float32) + # Convert to mono if stereo + if audio.ndim == 2: + audio = librosa.to_mono(audio) + + if target_lufs is not None: + print("Note: Automatic loudness adjustment is enabled, which may cause", file=sys.stderr) + print(" audio distortion. If the input audio has already been ", file=sys.stderr) + print(" loudness-adjusted or if the input audio is TTS audio, ", file=sys.stderr) + print(" please use the `-d` parameter to disable loudness adjustment.", file=sys.stderr) + meter = pyln.Meter(sample_rate) + current_loudness = meter.integrated_loudness(audio) + audio = pyln.normalize.loudness(audio, current_loudness, target_lufs) + print(f"Adjusted loudness: {current_loudness:.1f} LUFS -> {target_lufs} LUFS") + # Convert sample rate to 16000Hz if necessary target_sample_rate = 16000 if sample_rate != target_sample_rate: audio = librosa.resample(audio, orig_sr=sample_rate, target_sr=target_sample_rate) sample_rate = target_sample_rate - # Get left channel if stereo - if audio.ndim == 2: - audio = audio[0] - - # Convert audio data back to int16 after resampling + # Convert audio data back to int16 after processing audio = (audio * 32767).astype(np.int16) # Initialize Opus encoder encoder = opuslib.Encoder(sample_rate, 1, opuslib.APPLICATION_AUDIO) - # Encode audio data to Opus packets - # Save encoded data to file + # Encode and save with open(output_file, 'wb') as f: - duration = 60 # 60ms every frame + duration = 60 # 60ms per frame frame_size = int(sample_rate * duration / 1000) for i in tqdm.tqdm(range(0, len(audio) - frame_size, frame_size)): frame = audio[i:i + frame_size] opus_data = encoder.encode(frame.tobytes(), frame_size=frame_size) - # protocol format, [1u type, 1u reserved, 2u len, data] packet = struct.pack('>BBH', 0, 0, len(opus_data)) + opus_data f.write(packet) -# Example usage -if len(sys.argv) != 3: - print('Usage: python convert.py ') - sys.exit(1) +if __name__ == "__main__": + parser = argparse.ArgumentParser(description='Convert audio to Opus with loudness normalization') + parser.add_argument('input_file', help='Input audio file') + parser.add_argument('output_file', help='Output .opus file') + parser.add_argument('-l', '--lufs', type=float, default=-16.0, + help='Target loudness in LUFS (default: -16)') + parser.add_argument('-d', '--disable-loudnorm', action='store_true', + help='Disable loudness normalization') + args = parser.parse_args() -input_file = sys.argv[1] -output_file = sys.argv[2] -encode_audio_to_opus(input_file, output_file) + target_lufs = None if args.disable_loudnorm else args.lufs + encode_audio_to_opus(args.input_file, args.output_file, target_lufs) \ No newline at end of file diff --git a/scripts/p3_tools/convert_p3_to_audio.py b/scripts/p3_tools/convert_p3_to_audio.py new file mode 100644 index 00000000..f870b01c --- /dev/null +++ b/scripts/p3_tools/convert_p3_to_audio.py @@ -0,0 +1,51 @@ +import struct +import sys +import opuslib +import numpy as np +from tqdm import tqdm +import soundfile as sf + + +def decode_p3_to_audio(input_file, output_file): + sample_rate = 16000 + channels = 1 + decoder = opuslib.Decoder(sample_rate, channels) + + pcm_frames = [] + frame_size = int(sample_rate * 60 / 1000) + + with open(input_file, "rb") as f: + f.seek(0, 2) + total_size = f.tell() + f.seek(0) + + with tqdm(total=total_size, unit="B", unit_scale=True) as pbar: + while True: + header = f.read(4) + if not header or len(header) < 4: + break + + pkt_type, reserved, opus_len = struct.unpack(">BBH", header) + opus_data = f.read(opus_len) + if len(opus_data) != opus_len: + break + + pcm = decoder.decode(opus_data, frame_size) + pcm_frames.append(np.frombuffer(pcm, dtype=np.int16)) + + pbar.update(4 + opus_len) + + if not pcm_frames: + raise ValueError("No valid audio data found") + + pcm_data = np.concatenate(pcm_frames) + + sf.write(output_file, pcm_data, sample_rate, subtype="PCM_16") + + +if __name__ == "__main__": + if len(sys.argv) != 3: + print("Usage: python convert_p3_to_audio.py ") + sys.exit(1) + + decode_p3_to_audio(sys.argv[1], sys.argv[2]) diff --git a/scripts/p3_tools/requirements.txt b/scripts/p3_tools/requirements.txt index 64d4cc8a..d76d4cd5 100644 --- a/scripts/p3_tools/requirements.txt +++ b/scripts/p3_tools/requirements.txt @@ -2,4 +2,6 @@ librosa>=0.9.2 opuslib>=3.0.1 numpy>=1.20.0 tqdm>=4.62.0 -sounddevice>=0.4.4 \ No newline at end of file +sounddevice>=0.4.4 +pyloudnorm>=0.1.1 +soundfile>=0.13.1