From 8513270e9e3a6cc2fd40d54304e6ee5211441d15 Mon Sep 17 00:00:00 2001 From: Dan Chen Date: Mon, 18 Mar 2024 17:31:26 +0800 Subject: [PATCH] feat: text to audio --- .gitignore | 3 ++- README.md | 5 +++-- audio.mp3 | Bin 0 -> 5184 bytes src/blackbox/audio_to_text.py | 10 +++++----- src/blackbox/blackbox.py | 25 +++++++++++++++++++++---- src/blackbox/blockbox_factory.py | 4 +++- src/blackbox/text_to_audio.py | 29 +++++++++++++++++++++++++++++ src/main.py | 1 - src/my_file.mp3 | 0 9 files changed, 63 insertions(+), 14 deletions(-) create mode 100644 audio.mp3 create mode 100644 src/blackbox/text_to_audio.py create mode 100644 src/my_file.mp3 diff --git a/.gitignore b/.gitignore index d56d403..1a6abbc 100644 --- a/.gitignore +++ b/.gitignore @@ -160,4 +160,5 @@ cython_debug/ #.idea/ # Macos -.DS_Store \ No newline at end of file +.DS_Store +playground.py \ No newline at end of file diff --git a/README.md b/README.md index e89c0a7..f3ec72c 100644 --- a/README.md +++ b/README.md @@ -7,9 +7,10 @@ | python | fastAPI | https://fastapi.tiangolo.com/ | pip install fastapi | | python | python-multipart | https://pypi.org/project/python-multipart/ | pip install python-multipart | | python | uvicorn | https://www.uvicorn.org/ | pip install "uvicorn[standard]" | - +| python | SpeechRecognition | https://pypi.org/project/SpeechRecognition/ | pip install SpeechRecognition | ## Start Dev ```bash -src git:(main) pip install "uvicorn[standard]" +cd src +uvicorn main:app --reload ``` \ No newline at end of file diff --git a/audio.mp3 b/audio.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..4203f9346ca72b45b2ebeeb423c9f5b2cf54db79 GIT binary patch literal 5184 zcmeI$XHZk^wg>PHgwP=*p(;I~G!0cLN(Agw`5J50On)D)4 zUKD8}U8u`o|z49;@SZKU@)T7 z1_02)px#pN`r*F*%%a;NL9U>xy=E@haUjO6iJlV)526f3M6+=mx_FcS99#!iwppWA z^R&R9USfb_9ry%L_S+0PJPbH9osS_+deKX?i!E1yN4^zOtlNU_(OF>L_O!%b?l#>O zQNkLrlqH*nI9|n<0UzOU%1L$ANhjpFVzPySS%kfBy^&?0wI)`xBaT0!;Oe8M*G4f< z`}gj)^sG#+%9Sv2%46?>S>W)B4NwEXyUf z##s%ns9hj-dQs|oAzwkLT0I=NfM}>}ImFz%{@t-C{B>KjGvFudDW0K_v!=S8!lY;t zkCxwbn5+4m%Otb$R(m|`N3~>1!PPMJQXjP!f$`jhw`Ml34D74y+{SF41z?W$ED~0z zly%7GK#T2B0TUC-h=R0GtE)XdN3M3`tt;j2d_0vU9?au%O7K@DME=J_{YMeu*}doj zy#_>~Ywe3)55#5N8r_H&=7~6KbooQkfx^Sa?w#8zb9W?pHOWu0E)sl@&xJNxEdt)~ zle}Ws$wG||merpDo_P&S;F!>l{OsqDg-tUUYDY1GC8mGoGl>9UW3ow}qoc0XM0 ztbfn<56PhzF_p{3DG#|uOZFy{43%h4&S2P$b#G0-fP4f`m02awZ$WOtAuuCsrum%4 zyXRGISB}DJW->|S@XpWdd2xE(FKk4<4y2(E-2?C!Ob zo+#{`Gkp-HHLXlTeR{IxlGE4BQN0Z(?<+P>$S+uTC+ArdNic=hr5yis1KcA%>s{6P zLz2m`wS0=u_PhQ(U(2b$mf2Y&ie)WZO98 zGb4=~lYnU$`CD=A+%Ez3Pj3N+P^y|zo&?}|#aQ#Z$0N02R(-_=0?}9f`NZc79u-=k z$ooUBnfD;*X84TLTBBkxfR7})S4)iU+6}}r!Zk=2wNyByqQmPUpBvfL7!OQ_lZ(RbauOtox4!(Tz$d(EQ!t1nIrHnj>^hnO!euYf_| z;mT-n`aRY)AU=Mym0#1@PWC3;%^LSqVn_Eb zoUpk2U)6f*Ho*g{9q`9`_|N?u*Cp!!-B!KlZW74i2MB_`_#!v2SAemaRk1G!e)aqE zg#Kd0@Ow~Rw5t@o?RL?64D#90T`!8D`s4g3;yE6O89X$6$CYk-EvLZ3G{5(rVl6JL zIcncvItbj}1qoJr$qnW|!8tkwZ*sLzWA0t6#(LaYL$StVw;zI_ec;>q;+h#S58k-> z?OaC)Bdd7E>T%oh8&44Y!MBkyQnAt88q$#5YYuMribb?soSPuPGas$~ z^wpHs_Ph9_$KfaTY(+#d;ts9KPaVOYt|WB{_4@bxi=o!HslX^FWs49K#}{Q}FFQjC z%ox(zH2Tx$oB^%*;h7bXc=Cj~RbZsLkKEowAqZ{_ts(4t9G=PE0m@s#5)U-K(hybI zCL!dTb`7aA=g@ZJ=92`a+cm%Yi$Qu79Q7ARUN|THvTxGO>f_;|9|(R!?w2ixo%4Iy`Is#@y>J zM!C!^#bFFoA9*B8CK;H5kzX|r?PY=Bqp`lz!%Jn4)qtE7)wCZ_ZF`8#dy^xW1s=+A zIu(%s_WSMAZG1*Oi1i)@W-2F{gb5EoyUof6|A zKP=|}?^_5yX7#PO4z^e~ zY3HvJR;*Jog8^_4-|n^v%kviMsPn}RUnRE_p|Ar}?Q~DY>_v}6cb{asnUA_)n&I%# zAbxP2U<{LxZ-p37zY8o(nk@hicPd z)uuwG-a)=Iw{{8%SQN%9u()dFiMp`E=)UEahO5t%l(+t}s z88I(E$9OiK>oURn&EQ@SGwNyvt@CB2bKZvwv-IDxHkvY@Dsac^)V7^ib0^B@)#+k{ z_9L??nj;8dPZX)=V&KAKx^S=G^$A5)u9AW26jJ^9x<$q@rBmoKj+m08us_UxqD>n1 zQW7N+di6Y*890~*tq~ii=bJScDgNHWLl$P=Zz!aGi%2y=?T0%nrhGTK{k<)1|2tiR z;MEu(`DPH@gz`snFO5-wehTRu$8N_X^3EM9EED}jw>Ys~(6c>6ou!M*$p^TVIi=D1 zB>C}^7J~$ipLesBq(0sI6^iJ|Cnt2JcFML{lrpxMYzKh{_XY;B?)p`Cny^msNhruq zM!X(6y1#Nr+xC88J~^vxV!qM4*;)5tY$bmQrp0pc1vwXG!lVU{p%yv~3e&oDZl}Fb zE?t)@@s`G0rKBCT#XG$BnQxR1S%%h07!>5mGA%?~lG#336frUY;j*>tb@glwr~Je?|0UG=t6H3e;`zpz%SS+2{Ow zp3ACEu@4y@r^tIVIFL*hb>z(UBm*?q>1`mt9BK6CG0;yU<@aDCbu2wy!3$o3{5quc-eX`4Mv0#gNaYkPS?Ka2gv~PF z)}tOb=h>o2Z3cmRTFa4I0m`QS(jUYULMmk?gW{Pv5_HU(LTQnz9^_K|K9bc;7B$N( z3S<7gpIcC(V`QlQ%J8O$c?*8u_fp&}$$gon6E@8lQy%?Nno+}N=Lm&kLEyXmNT0z$ zU8x2oE{>+PVa~`qoxIC7ag4RQ3O%GMwSK2--!%fpaA`r zB=t&a^|Lxj;i;fgZWc>2wx^`ngMI`Zq<=Jt?#0KJTsYY~gwlItC&cP{_aU8c8Q{qF zQ+0xY-UoIi+*QBprm2_ikW@;|d-WE6Su;B}@I5$G2Knz$PMk;ew=p zH$Uoz@)A`Xa_VZ;pnqW!Jgkgq8Zi?}fYK))rC80OG`B?;#B9T}0MDsfx#{YzijsJm>TY%(VV;-T9DRdD$Atd zXfrFL+QB9q?MSN-cp59fb6{0@!MJ#g9md@i!)&gheIYZ3x-{LxX160Uw!Zj#uE1^S zO6qG?Ui+JnKf}#(KIy2xr27QDrJuo(c?S}`7CO#}y2ZS(Zmfs?EoX699N{iYV$m_m z2M2)Ov_RVmj;(67ze{F(a)A7~qXU`lXv~Mtah{4Mxnc7osM6?K=aOowNo&wf$%Yh2$M|8g{-jS+O%S zvwIM6?f$^P#-VkA_2+q;lO3Eji7KhAQ2r$z9eY3MeoQ38m>I+8X|$7oPlkB*%+m0V z{P{Hb;A^^#_gR;%RUd_wxeZ-QU`TOIGKbP~NPeB(Pfl+Q%a+=6~ArfK&^Vvhg@buW+kl+}0*sQp_JI-2V*JTp7 zJ@P;@I;6A`%Krjs@roUAX2<(V@;_kDE)pE^pR*XVR=%`|Q>hnBBg~GIE7#u1*p`Gn z@?c{&2%Do Response: data = (await request.form()).get("data") @@ -33,7 +33,7 @@ class AudioToText(Blackbox): return JSONResponse(content={"error": "data is required"}, status_code=status.HTTP_400_BAD_REQUEST) d = await data.read() try: - txt = self.processing(d) + txt = await self.processing(d) except ValueError as e: return JSONResponse(content={"error": str(e)}, status_code=status.HTTP_400_BAD_REQUEST) return JSONResponse(content={"txt": txt}, status_code=status.HTTP_200_OK) \ No newline at end of file diff --git a/src/blackbox/blackbox.py b/src/blackbox/blackbox.py index bc573ee..33712ce 100644 --- a/src/blackbox/blackbox.py +++ b/src/blackbox/blackbox.py @@ -2,20 +2,37 @@ from abc import ABC, abstractmethod from fastapi import Request, Response - class Blackbox(ABC): - + """Blackbox class that provides a standard way to create an blackbox class using + inheritance. All blackbox classes should inherit from this class and implement + the methods processing, valid and fast_api_handler. + If implemented correctly, the blackbox class can be used in the main.py file + """ def __init__(self, config: any) -> None: pass + """ + processing method should return the processed data. The data is passed as an argument + to the method. All processing shouldn't interaction with the disk + but dist / list / string / bytes / io.BytesIO or other data type that in memory. + Output same as above. + """ @abstractmethod - def processing(self, data: any): + async def processing(self, data: any) -> any: pass + """ + valid method should return True if the data is valid and False if the data is invalid + """ @abstractmethod def valid(self, data: any) -> bool: pass - + + """ + fast_api_handler method should return a fastapi Response object. This method is used + to handle the request from the fastapi server. The request object is passed as an argument + to the method. + """ @abstractmethod def fast_api_handler(self, request: Request) -> Response: pass \ No newline at end of file diff --git a/src/blackbox/blockbox_factory.py b/src/blackbox/blockbox_factory.py index 18d9c31..0483122 100644 --- a/src/blackbox/blockbox_factory.py +++ b/src/blackbox/blockbox_factory.py @@ -1,5 +1,6 @@ from blackbox.audio_to_text import AudioToText from blackbox.blackbox import Blackbox +from blackbox.text_to_audio import TextToAudio class BlockboxFactory: @@ -8,5 +9,6 @@ class BlockboxFactory: if blockbox_type == "audio_to_text": return AudioToText(blockbox_config) - + if blockbox_type == "text_to_audio": + return TextToAudio(blockbox_config) raise ValueError("Invalid blockbox type") \ No newline at end of file diff --git a/src/blackbox/text_to_audio.py b/src/blackbox/text_to_audio.py new file mode 100644 index 0000000..6b58e9b --- /dev/null +++ b/src/blackbox/text_to_audio.py @@ -0,0 +1,29 @@ +from fastapi import Response, status +from fastapi.responses import FileResponse, JSONResponse +from blackbox.blackbox import Blackbox +from gtts import gTTS +from io import BytesIO + +class TextToAudio(Blackbox): + def valid(self, data: any) -> bool: + return isinstance(data, str) + + def processing(self, text: any) -> BytesIO: + if not self.valid(text): + raise ValueError("Invalid data") + tts = gTTS(text=text, lang="en") + fp = BytesIO() + tts.write_to_fp(fp) + fp.seek(0) + return fp + + async def fast_api_handler(self, request) -> Response: + try: + data = await request.json() + except: + return JSONResponse(content={"error": "json parse error"}, status_code=status.HTTP_400_BAD_REQUEST) + text = data.get("text") + if text is None: + return JSONResponse(content={"error": "text is required"}, status_code=status.HTTP_400_BAD_REQUEST) + by = self.processing(text) + return Response(content=by.read(), media_type="audio/mpeg", headers={"Content-Disposition": "attachment; filename=audio.mp3"}) \ No newline at end of file diff --git a/src/main.py b/src/main.py index dc3ba09..0de0a8d 100644 --- a/src/main.py +++ b/src/main.py @@ -6,7 +6,6 @@ from fastapi.responses import JSONResponse from blackbox.blockbox_factory import BlockboxFactory app = FastAPI() - blackbox_factory = BlockboxFactory() @app.post("/") diff --git a/src/my_file.mp3 b/src/my_file.mp3 new file mode 100644 index 0000000..e69de29