From 43774fe580b3c99112886a4071d86319b988fd10 Mon Sep 17 00:00:00 2001 From: phoenix Date: Fri, 18 Apr 2025 00:37:42 +0000 Subject: [PATCH] Cover art code (#21) Reviewed-on: https://git.kundeng.us/phoenix/icarus_meta/pulls/21 Co-authored-by: phoenix Co-committed-by: phoenix --- src/meta.rs | 158 ++++++++++++++++++ .../Sample Tracks 3 - Other one.png | Bin 0 -> 9627 bytes 2 files changed, 158 insertions(+) create mode 100644 tests/sample_tracks3/Sample Tracks 3 - Other one.png diff --git a/src/meta.rs b/src/meta.rs index 234ff50..2c9443f 100644 --- a/src/meta.rs +++ b/src/meta.rs @@ -15,6 +15,81 @@ fn get_type(t: types::Type) -> Result { } } +pub mod coverart { + + use lofty::{file::AudioFile, ogg::OggPictureStorage}; + + pub fn set_coverart( + song_filepath: &String, + coverart_filepath: &String, + ) -> Result, std::io::Error> { + let coverart_path = std::path::Path::new(coverart_filepath); + + match std::fs::File::open(song_filepath) { + Ok(mut file) => { + match lofty::flac::FlacFile::read_from( + &mut file, + lofty::config::ParseOptions::new(), + ) { + Ok(mut flac_file) => { + let mut coverart_file = std::fs::File::open(coverart_path).unwrap(); + match lofty::picture::Picture::from_reader(&mut coverart_file) { + Ok(pic) => { + match lofty::picture::PictureInformation::from_picture(&pic) { + Ok(info) => { + flac_file.set_picture(0, pic.clone(), info); + Ok(pic.into_data()) + } + Err(err) => Err(std::io::Error::new( + std::io::ErrorKind::InvalidData, + err.to_string(), + )), + } + } + Err(err) => Err(std::io::Error::new( + std::io::ErrorKind::InvalidData, + err.to_string(), + )), + } + } + Err(err) => Err(std::io::Error::new( + std::io::ErrorKind::InvalidData, + err.to_string(), + )), + } + } + Err(err) => Err(err), + } + } + + pub fn get_coverart(song_filepath: &String) -> Result, std::io::Error> { + match std::fs::File::open(song_filepath) { + Ok(mut file) => { + match lofty::flac::FlacFile::read_from( + &mut file, + lofty::config::ParseOptions::new(), + ) { + Ok(flac_file) => { + let pictures = flac_file.pictures(); + let res = pictures.to_vec(); + if !res.is_empty() { + let picture = &res[0]; + Ok(picture.clone().0.into_data()) + } else { + Ok(Vec::new()) + } + } + Err(err) => Err(std::io::Error::new( + std::io::ErrorKind::InvalidData, + err.to_string(), + )), + } + } + Err(err) => Err(err), + } + } +} + pub fn get_meta(t: types::Type, filepath: &String) -> Result { match std::fs::File::open(filepath) { Ok(mut content) => { @@ -109,6 +184,19 @@ mod tests { use super::*; mod util { + + use std::io::{self, Write}; + + // Function to save a Vec to a file + pub fn save_bytes_to_file(bytes: &[u8], file_path: &String) -> io::Result<()> { + let file = std::path::Path::new(file_path); + let mut file = std::fs::File::create(file)?; + + match file.write_all(bytes) { + Ok(_res) => Ok(()), + Err(err) => Err(err), + } + } pub fn get_full_path( directory: &String, filename: &String, @@ -787,4 +875,74 @@ mod tests { }; } } + + mod pictures { + + use super::*; + + #[test] + fn test_get_picture() { + let filename = util::get_filename(1); + let dir = String::from(util::TESTFILEDIRECTORY); + + let temp_file = tempfile::tempdir().expect("Could not create test directory"); + let test_dir = String::from(temp_file.path().to_str().unwrap()); + + match file_exists(&dir, &filename) { + Ok(_) => { + let filepath = get_full_path(&dir, &filename).unwrap(); + + match coverart::get_coverart(&filepath) { + Ok(coverart) => { + let is_empty = coverart.is_empty(); + assert_eq!(is_empty, false, "Should not be empty"); + + let mut new_coverart_path: String = test_dir.clone(); + new_coverart_path += &String::from("/newcovvv.png"); + let _ = util::save_bytes_to_file(&coverart, &new_coverart_path); + let created_file = std::path::Path::new(&new_coverart_path); + assert!( + created_file.exists(), + "Error: {:?} has not been created", + new_coverart_path + ); + } + Err(err) => { + assert!(false, "Error: {:?}", err.to_string()); + } + } + } + Err(err) => { + assert!(false, "Error: File does not exist {:?}", err.to_string()); + } + }; + } + + #[test] + fn test_set_picture() { + let filename = util::get_filename(1); + let dir = String::from(util::TESTFILEDIRECTORY); + + let new_coverart = String::from("Sample Tracks 3 - Other one.png"); + let new_cover_art_path = get_full_path(&dir, &new_coverart).unwrap(); + + match file_exists(&dir, &filename) { + Ok(_) => { + let filepath = get_full_path(&dir, &filename).unwrap(); + + match coverart::set_coverart(&filepath, &new_cover_art_path) { + Ok(bytes) => { + assert_eq!(false, bytes.is_empty(), "This should not be empty"); + } + Err(err) => { + assert!(false, "Error: {:?}", err); + } + } + } + Err(err) => { + assert!(false, "Error: File does not exist {:?}", err.to_string()); + } + }; + } + } } diff --git a/tests/sample_tracks3/Sample Tracks 3 - Other one.png b/tests/sample_tracks3/Sample Tracks 3 - Other one.png new file mode 100644 index 0000000000000000000000000000000000000000..fd550def0284148d6d2d82fcd7a0e6877dcf5988 GIT binary patch literal 9627 zcmeHt^;J;C|bK>ZFE%8B2rS4AQ0bcD+>!9H9@{_ zmb4ZY-}(e@LH~K7gU7$pu?XmDYT@|C(ZbP`nUrX1I){MGwSvGXCI-K{jwVQ!@CL58 zy61+8C=Xg5Vqp>|dfat%9KS!aVXnF?dqHzsmk{20b9ghdz*%4f$6Ls4S5B ze?|g4$EkzL;v&Ms{}aOj-~CT9_zoEd{LbDk>|ZfNZNzf_VNl>9YD4p|v7!If5op+S z{XaP0ZU!2DYim{dU$h_q&dGm@iT|&Iv*IlEneIqTRx)9_87M3U$;e_x1{&IfWkSUj zEx54+vQ6#x5yJN!9y7%&0*)aHSA9lfSQaKh{>-Sdc63g&uPpDEErbJ}Jpb?`CCsoE zUN#6i;)`la$hs>K-OT|QKCm^r22!LT*k&_$%_R6=6?3_UHS3==nS&Gw&*(BG5Y_%0 zPOJ@jyjzvsKD)ZT{nF9(oAbz0c&aL`I5xo0R(Yi(+ z8Wb0To1lATh_Rue$Wm}aDhLmTGC7n(=5&pod%eHUahH%;1?Gn9S%QQ4VEeS0jEM<| zy>ObQyfB&tZ+T)Ql=L%~WLc&L7XTtI98}*VTZxx}68SmOYqYK1gcV>g%s6`T0!f@A zJtUiUZZ1PxoyJv<_+%tM2hU54Bq#kLZwT1nFFOiK6hqGzO8aad)|9tDs3GIBidGyY z2hbD!g+{@pAnb-frmt`ZK3d{_Z+n3V!3i>cHN=!< zZl0Kk6ZsW7Wm&E;7GmP`v*^hk5r1)WHvPQw62kz{_{-;)m@6=GVp$C2jx2v^hktg1 z3LtYP`mZSkW`Bm_ifBF?Vn_gA#Cc{OCsbV)I^Ao~=blFGKUC!VM*BUARE!o-xOUAa zU~Qsd{r#eeKHZTFun6Fa;zA5DN`Dz`0mcv08}?!i`qypbd(_nVz7cofNyXrQ8A$=g z@vooYizXhgj|x*qLU60R-0%?#z)Cp`tCzVpJJKdmu0PZ#W6O2up`011hM4TXLLUM` zV~RIiZZ+sj?Ai8c>hs}!-e46eRZ(~}Q_ze#lc!5o{K;dwk(67aHUy4^u1HcbHbCLC za7rMqJ3Eedc@;9D#QV2T5l9!}kL09A5)Ne?Ai%hIiQQyLv&pleJAS>4ntHm_AuWrh zZyCv30xj~F1MaZ@vy{osUIcr=x?AS86@~d}ZPM04CkUfRN#*G=xERIF>-o2v%a}3!2FqW6Yl?E)bW4$CZWYF%VD)7}gTR3~mLXf~G%= zF(x(>7YWJ1Grynd-5?BJ*XX?6hQ9u4MiZe#<$g z!V(hH2JFB})NxI6pA1{Wq=J$JYt|R6nv;qOTOgrAEF|(A@M~G)hoI)ti2L>ihiB*t zL$BZMoh=ncg&mMkc*J}F4SDO0aRelcM$sD{y=Of@L2K!;TZW2`#C)@pXhTz(LBd!{ zW$|BLwyIS4)L&;HK(*=OV!Os5^DqUzU)2m)B(Eq~>7fOs%F1uI;9WWaCUf%6IS8qQ z2@=jygG8`oip7Np?_SH~HNM#Z51XffZhN8yCCimilDI;V@yO3&a20KJx~8!U33)Uj zF&airdV)kin+lP)>g$;}bVIlUOHQ{SkkExAML~LNyagYmwB=W?P6NC6*TZiSmsuOV|!xhxBA)1nD}BlyraNt_6cYsMTh zKE9-LukSJv`Q2vxq`}2SU(g(7m>(#BBM|3P84XRk2ddEW5}zc~RSnT0=`~a*`e9{* zD%#tmID%=qQ0@0gcY+ENBhZ2T^FPBJ^xaG2Da?PJ7?JT;dv8+w(YkqSEllbo&2)UA zU-SXebdBLrIEJs^&5#NoY8e8RQ$^8(R})jy)43$yNYOQ|q<&}6z+?DATz|FULtksx zsw$&uD%`cY5aj|=L$$O`E2dqwc}jdW6+J1mWpN3KR({}MF3C>o!+nh1jQc zk9s6L5Y7B?ifqn(dviZWneP`t-^{cP?JcO!&e|v4(`Q zlxoC88uHy(%^yc1RJfPmv1aMa5<0a2jH&VHH?#y4G35mEC`0T5e@Bj@>-{>mcpTia6qmpymnsEeDrIuoRZE^eUf7}8G0K_4k9D096HJ+4f? z91cXLo2=BmuL~4i&C#HSm5yRj1~lPqnEV)?G{EWmCo&u%x@8SlX2t7tlRpU5U_il% zCLkL5b-CaLIVK#g;9wb9pBLMkYDTXB?tW^*T>qz-clc0gKAHDH7H%Y+J})F(a`+#D zX5Fpu7`Mg8cKmJVv`W@)H0oD)$3vgfq}N$2#3yDiV<4dyy~%1phW0q0{yTs_1D~Cl z%bB$a0qaacEH@g-UczJLI%+-t?w&Le*|pHxisk5ZzVC^_J}+YBs=f<0gjW>Oc|_@? z+%{ilymo5VSo3jKi7#Y{2p<}_8ES#(GYsyCW@o#?H7@jvW!sEQThesD8@;s3s-Z+mJ9V)atY|wpqU(yj!pJl?Iw(*yh$Q zM=oNx7a^{O;>&4xHk>&#Jvz*jBsHn!kAnwr%srgz@Ev2?n*7cT7|ZqRPkAo-g$fiF zc_)Pv@#@A%U5bbh{Air043^X*F_fAcTeKkn2YPbFJLO|S&`NUxLhmi{kwg-&{nwRf z54V$drMu~aM*OJ>u$`;x_)s7m-=q0=`1RRI*0c{(1-@3%J=xtEHH^$MzV$4 zslFR%8{xsAdjQZzwx@nbwxJngyySCkB6QAMV#c1fJ~h$+L_!ASqeg&)3Nf>A(50bB ziFO@qY=77EO35Rbmm}ltPA&`vy=7Qi7mW{fVf!ASaUYd4(i%16K;mG*F9)!!T}^b zvB$SJh0(w?V~-zi=R;t(mqJ&qZjDH(05WstYO_DT*5y@Qr1uvDBHu8kI_7hNK{(e; zz}4mQl>2~Kh24Poi&_8b)7^0SDWN0ztJBz|J2sI?a{fN&Y>csV_`Vew)XS3>*mm(o zx0P7YQdp(rmCe*sBs{5cBY5|i%nbFf_si_u&A9l*yS0)h_xq#mXpxcatpBl?UAp%N zsg+dsBEy=+7@3K^TCY@v2LPI;LSJBq=M!=Bj}*Ka#X7*Y-X(vz_NXtE(aiqkZZNxH z8OnL$?!oCmz)Y&gsFAIcQ*V)J?U3x@pwe=j5|s!o<(J1lJtv*Rm(#EzhJI9pxU6F; zS5fnitv9$Cxvn+CbcIWtu3sEvTsghGSY%J()_kA%cX;By^-nN^rLFM>W)3AXSHrZ> zFUL(J)QJ*}r+q35*k&AQ+V?jNG0gyC^*T4(MH*l~Jmin~Vu6@^Wr=^d^CPx?hRVLb znJZQnJInE0&h^PL9kkf4xRF`hsg%s4dwDe3=Tf(28&2i&bx+0j@~FD8o<8N1@#pSTzx}S|K_%+j+>LRf zuS>dC(3Fr+%xwen`cvuRSdTdF(2Fp|d-a{8&J=HaZ^XaHcr7x|+W$ElYn<3z?oWHV zf_(S7dVkQZ4e-OQlxa5KP0JYRb-sgU>X)TOo59_@zp9~-&`i_fvhICbg2>@KJ50LH~vIf^uAaK8s)O2f1idN*f_rG4&g+g&xnPw-o+Y{=bIJL zVa(z#EV2aMR@zU@{LZyY@0WYb|Lli2_NPb;JV>9q6w5;5FOO$uzg}JLWWeM{nF%KV zj>ptR4Ns=85)91QxRucy;AYrFp{2ptpa$5v^sv~~zsHuKPd4D)@4gvZQ|0_}z<0)K z=V?TCl}Ea?M?sR5`-f>ghl)ON(E-0@(Y{d=SLdp6TW01RZei~T@1eKpBl>BKU*||w z-g5i44&`cYeT6u0=lC<;z2JDgJ3jDolqKL|ul{@XB@3Xw1}+8fuK{G{Wz= zBhYJG@k^|#EYCu`r#H;(0xHE`%$0SrRWOSbo7GRKVblayZ?_4U?LjMmrKGT0>ura*3DrwiBiAPr zxoOXTf8Tm4birFX8*|;)+j!|xKs>P`p~#VB%4XTx{bbvuN_1(z_#|QUsqmi=IDgLPe z*FW#sB4y?GKXo)TSnk$WuVB?EMK0DJ1(a*je|D}2X|a<#xRDb7iCX@`&3z$)_lxy( zyh{tK5HySe@XGIR|NIWij+L8Oogzy!KFwmR|0efmMgGd+O2f3$Gsf?C zp}BK=i>CRRPTgfCRmqT>+P7u{AihI3^Vnchjk(O+tf*?vUF``~5(y$lpnt0GJGjIXSJfA&I{)SRg<%aRImxtNxHz=+A z)-=t_O;k!us$J~UzTDZ{432FG!|e8|6PHk7k!~bsdj7NE)?a&L^Cs6OEd5XB!?$wU zUo;$~dBz02S1zb789RqE@eDRV%`X-h>r55gDJz%C#npUHd(2%te_CEYIBvX}{)1{< zWZgjx_>5L;PLXcaNb;>Z2hOmpUjHhpmU?(Cf_SO08Y-RB&F#1Co*_Em)gjv3aK8QY z*nW_imMjN@^Lx;+tgFCxVjV5Hjx)r!#>`Z)bryx-l_q&8Ao=JU2VazV2b#jiOA&(X8g_K>reiWLCS>A6w zZaBW@-f_e3Q04Z85#7ztABTgfUsXC__7b1_!rH867Yc zSGm?V2qKJI^2;tpO#+K*Y-HaC4n~qjgcp^z!%Y%(AKmjn(^I~-$1c>({QmUGZlY6B zQ=|e1R?G|3HjU5z!RQlAIK4qj{>XP?xV(ICAxT&c@NWxiRxuchG#(b8MX`f}9|1a| z@_CCdsn;ozdr*S*r@wHdXEN&Qt;gnm`eYYJ9>}0P_2PAfUXU`}0 zj%R#b-Ft3V|74(@xQ@dI1_E{;gDvuc+4BM!MF%8uG{CRB4hgD&Uq;HU)|H1 zEhX$&q#v`-H0}dwuyB2YrOMC;^&Xuu!8$;B&RHL+ij${#CX zxIqhL3aeR&6xi%|c(yl2eI?S#Vr6zdF;-RRwcC7s@|~wEr3P0BP=XY_e z=UCTKc%V@7kw^$yp`X@GpN5=>S@>On#4SYfA5QikD#;R=GrwP+Z`maXu6G(w3Y+`? z{*i5~t?~7V=Xm?q^XVON_LHXT6Dw-=9~UjdS#1xlwqG#yEuQDF}=iAzF-ejXs zPi92Smpq%0_DzUBGdc>UukR3!9+9|ZySmhp5J z-iEspkNgm(1qtKv_4lUaC1egtx2el8J7$2LeMTmm9JdZ7 zHOv}199n=tZtzk1`Wr3S_S8h!?YsJj z*Pa2JQ$*#3#8UE=VS?`-w5+_-eOTtoQaNn>lvkZKh}AKF5|Bvn(fIjWMKHvja( z9#EsnelnOzyEbNu^FCN!THo5x3SH)2?KCPxRyvitTYc!iD6IL@ZO|~(|9Hu9DfnU0 z$kZ!fwoXt{wG6MMMMr-e2822v&u7^JrxwXUx>Jkbby^C7%du?lqeHHeQ*xtL;$&vj zi)~Yx6$Lup&z0Kpy=jVX`MH^%zsdE)j7)<2l-#Dj)Qav{j!gt=xH8dK;?Q*}j+zcy zA2z2ud<@OB541lV#dFOnGhJ6WskL8~N0@QYGDA$8=$28rqFvcATgZA@q zTB?J8Zr9$7vtCqDz|9xb33+JBVheb9GNTQ}^6amGMSGJUuNW~Pd+@;hXxxsP0b0LS zQnxMb-o>5pe5**(dwnv^x8`+oVqty*RHXE=0JUI^Y zA5_#azE6YBZ>@$wkzVKv4`snNSF0Tp!_O9pY~x=;1xmgSIsGiP%zX0s5l-``V`1@T zRloFooje7{;hV+AgGa;sx;Vwco9X-S@_tq@Hvm-?b6VKP+eyE}78as}tFTHqMR)3k z-B4i!Yqamd)ZoqDbrZMLsKAe4e=H&>hXKKl$ZK5dCvF z8VMFw%Z{N?%CWTDF)><-Q)aDilqQ-5s?nr&2fO>My3@i%K&|cau?R^)DPcdJ3nXiu zZJ*uX`{A>^S_+x1N?ilE^pWwT{6y%4uaPIw5o&Db9yX z3fT^`i=xviAK@Nmj1XzH;+AFk>X&6?cf1EgCnAkqN$&P?Xe*+YFH9aM*~B`be4QJU zr0mCTYpCKde5?+b8~t+IR6FZESEn;cH@Berdtrp`8FRYPq1dC+%xdb^83l@NrOWW8 zo`lJOF*9FhndBMS)#5;^Sm|GJN}EYETj3FAdh;fbQ026)16xtMW_OZY!}{_Sl-^$G zOwN7#DJGsZbA-AA`W(A_h?UQE)EzntV9pNPMOSf8;h zp~_AR_;c!+pBYnr7EeG%FUHExJoLKwQS$PKumH~ps`1Jmtl!C5rlT}Krtdk_d~306~fh*7wK zBlOOgyWiw_U!;zo{c}{^S8mhOyOx*Xax6Kx-^qicDj!M-8$sUyyrkCmw8cr7j-|EI zMCbE+3rwQS({$asp7l#91~Vm2)+heGBAq`2_#dW-Px~KFF9Y*@f>G^1xzn`9nyelL zxksXjQtr0C|70yA?KyL(e20waq8NoeKb0+`>}zx0%YUGaJOd(=;SpJ58a{N_IZ>HB z5^`;8ntkV)4qac#C5_LqS|XzGJcj2szrFq9PpjC^DMB8dh^@C(gwbWq;>9XeO$J-R zQ@z)J6Wwy+-Vb9@iq{28Bu>>EaUn;Ph^H=w@_a+aHiYOme|LO&KJvRwB)0P{p${FD zG;e0SajgZ1;K;>>?gHnogE#%V`?Z?%QS|;#QtPsacn3Q)Cl-ukq`rV7qUmvkkC5~_ zyD=vdctiZahws(j?xWs`(A@SZWu+XEg5{=(RDkIsA`^I}<=1w&^+G&3wF?9lNYWYkl!m4oHfjUcECi;sk0=xxp*m0IRn#-^X#gL zM1e(VeUtw8MZT$~6xW7v7ND$L2KG-L{To1$=>Cvdr$u{Kkb@pw&hNph9B7FLj_KGb zg0#bKs$62JKYVAhNz+0?FZ{1{0U{AD#OYO|`(M(bg!d_aRyy^qK3->wC5mH?Z5_nmhpq(ouJ zf=rF{XxKNVH;7!upK7_xe0r)FbD&CJ-tQ5X0E&>3KCv-VE8sJUuA`Pv%^Y!d>j$CQ zU$n!&#u;+O3wSF8Gthj8O$%1Lz!`RPO5C;Qw1}ICu;0Wo2l(H{h4hsyIxFh-0;>hV zZTS8u$)$I?1wsX{FRq``qX{(b82ffGA}k+)8XtRxcQdqsCXDP}q7l z#GC4dzKtgQh#Y<>)8}yizCiszz&XI%Y!{_n%?b9Zz=W_l)N>D>hgnY0Tk*)a^ldnE zxj)|gdeGmKl$E+_Wo5Nw{YzVDpZiqLPJ!?F(44e5uw?!e)bO1EOOv)Pt|;Z4ds^<) zAG^5EXGs$E8LY}O5)&|~x1>(A0xcnpTS{9{+Etb}h`Sx8rI0tgYMH|cFesU{0#0kW z4~8 zY}w(ge8ltG|GnN|3fL-+obmO)*CN(v6a;hZ+>I&z_aehLARGQa_5ZFl=B_Az;!|Gz TD1ZC+`j7hkhsvMO7P$WhA2;fY literal 0 HcmV?d00001