From 1f314240155464703cfb9f3b883298716f2918a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=82=93=E6=96=87=E5=85=B5?= <441889070@qq.com> Date: Wed, 26 Aug 2026 07:26:35 +0800 Subject: [PATCH] feat: build licensed desktop image processor --- .gitignore | 10 + README.md | 50 +- build/icon.png | Bin 0 -> 16334 bytes build/icon.svg | 9 + docs/需求/260815.md | 0 docs/需求/参考/郑州情趣王_图片处理逻辑.py | 192 ++ docs/需求/参考/郑州情趣王一手科技.exe | Bin 0 -> 20060712 bytes electron/main/image-processor.spec.ts | 106 + electron/main/image-processor.ts | 294 ++ electron/main/index.ts | 333 ++ electron/main/license-manager.ts | 170 + electron/main/license-store.ts | 70 + electron/main/server-api-headers.spec.ts | 15 + electron/main/server-api-headers.ts | 15 + electron/main/server-api.ts | 102 + electron/preload/index.ts | 36 + index.html | 14 + package.json | 83 + pnpm-lock.yaml | 3701 +++++++++++++++++++++ pnpm-workspace.yaml | 7 + scripts/build-icon.cjs | 16 + scripts/clean-electron.cjs | 5 + src/env.d.ts | 15 + src/renderer/App.vue | 720 ++++ src/renderer/main.ts | 10 + src/renderer/mock-bridge.ts | 106 + src/renderer/styles.css | 277 ++ src/shared/types.ts | 90 + tsconfig.electron.json | 17 + tsconfig.json | 7 + tsconfig.renderer.json | 22 + vite.config.mjs | 25 + 32 files changed, 6515 insertions(+), 2 deletions(-) create mode 100644 .gitignore create mode 100644 build/icon.png create mode 100644 build/icon.svg create mode 100644 docs/需求/260815.md create mode 100755 docs/需求/参考/郑州情趣王_图片处理逻辑.py create mode 100644 docs/需求/参考/郑州情趣王一手科技.exe create mode 100644 electron/main/image-processor.spec.ts create mode 100644 electron/main/image-processor.ts create mode 100644 electron/main/index.ts create mode 100644 electron/main/license-manager.ts create mode 100644 electron/main/license-store.ts create mode 100644 electron/main/server-api-headers.spec.ts create mode 100644 electron/main/server-api-headers.ts create mode 100644 electron/main/server-api.ts create mode 100644 electron/preload/index.ts create mode 100644 index.html create mode 100644 package.json create mode 100644 pnpm-lock.yaml create mode 100644 pnpm-workspace.yaml create mode 100644 scripts/build-icon.cjs create mode 100644 scripts/clean-electron.cjs create mode 100644 src/env.d.ts create mode 100644 src/renderer/App.vue create mode 100644 src/renderer/main.ts create mode 100644 src/renderer/mock-bridge.ts create mode 100644 src/renderer/styles.css create mode 100644 src/shared/types.ts create mode 100644 tsconfig.electron.json create mode 100644 tsconfig.json create mode 100644 tsconfig.renderer.json create mode 100644 vite.config.mjs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d3f4b04 --- /dev/null +++ b/.gitignore @@ -0,0 +1,10 @@ +node_modules/ +dist/ +dist-electron/ +release/ +dogfood-output/ +.vite/ +*.log +.DS_Store +.env.local +.env.*.local diff --git a/README.md b/README.md index 99f13ff..83dc97b 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,49 @@ -# image-processing +# 正版情趣王 -图片处理 \ No newline at end of file +面向图片交付场景的本地批量处理桌面软件,基于 Electron、Vue 3 和 Sharp 开发。图片只在电脑本地处理,不上传到服务器。 + +## 核心能力 + +- 支持 JPG、JPEG、JPE、JFIF、PNG、WebP、GIF、TIF、TIFF、AVIF、SVG,支持选择文件夹和拖拽导入;动图和多页图片取首帧。 +- 非 JPEG 输入会先转换为高质量 JPEG,透明区域使用白色背景,再执行现有混淆处理。 +- 内置 AI 混淆算法,自动完成图片数据特征混淆与交付兼容处理。 +- 追加数据默认 `1 MB`,可在软件中设置为 `0–100 MB`,设置会保存在本机。 +- 支持生成 `原文件名.file` 和 `原文件名.file.jpg` 两种交付格式。 +- 支持激活码激活、设备绑定、在线授权校验、72 小时离线宽限和软件版本升级检查。 +- 生产接口基地址:`https://apimall.v2.jxxyk.com/api`。 + +## 本地开发 + +```bash +pnpm install +pnpm dev +``` + +## 验证与打包 + +```bash +pnpm test +pnpm run typecheck +pnpm run build +pnpm run dist:win +``` + +Windows 安装包输出到 `release/`,安装程序名称为 `正版情趣王-Setup-<版本号>.exe`。 + +## 后台配套 + +后台项目:`/Users/bing/work/code/jxxyk/jxxyk-v2-mall` + +上线前先执行数据库脚本: + +```text +sql/update/20260826_desktop_image_tool_license.sql +``` + +客户端接口: + +- `POST /api/desktop/activate`:激活并绑定设备。 +- `POST /api/desktop/login`:软件启动/授权校验并记录登录。 +- `POST /api/desktop/version/check`:检查 `正版情趣王` 最新版本。 + +后台管理入口为 `/admin/desktop-license/list`,支持批量生成激活码、启停授权、查看绑定设备及登录记录。软件版本沿用 `/admin/version/list` 管理。 diff --git a/build/icon.png b/build/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..4facdb6352f0b55f272b2a34ebdda7b1a28273be GIT binary patch literal 16334 zcma)jc|6qL_y22*X2wnup)pcaDx^}`hLRMKq$FE*vP9X&GNL_EO0=Lzwven1W|FcM z%DxjKjD5y3X6AQi zhi`9F`&k2?y)~HY`o})$*H0Nm7ygnNsZA`g&OH3~S&DO_bBe>yT|$Dew_-|1_pRU0 zb)8%5zL_KvP>0~=BM8z0PU&NV;o zq@;LmD~F5WVFrj~(bBG`+>WlLI}0P@%ISnT#Xt5x8#{P9cdKWW)^0v@&0$g^pSDLQ zKaWbjy4g*qK26s5gT6cQ$&kCdX_5UtPw|7iBj+NB>0pl!=Q;?Q^Z) zM14mtrYG(ge{0|#@=V0%1`^KS591}_TY2q@&E2_i2|LD{-MXoHrH4hyY2EcZT$(zF zsT1}e)1~&`pb;{244-xJy9jIzM=C<_Ps*6meT9axN9qiFR}u-qA}nQBzP5O@ern>+s}y zZrqvOye2iRPW3`2cJ?1Pz66Ro5<{>IY7=6OA(1?J?QUc|E4S$FF;myV^(1oaG4p#Q z(tIkKQ0aVNp9D~!80uc=F5mhpnT{QeOY=YC4O6i$y(LSG4*7r^oymNPY#ocmUKY%e zFmNZfw+x+XiMQG=HFkGKMgKHi_)O6)vCI1|vKG1>6KYi`(rjpwwcRR1oorN-UvD1eUhvNjz@=P=E#5Ovi#Xn zI{v+5c75}8_4y%D;$^KgKJ*G7Q(>XI3Kgn0!{TIOZ`)?n`DZKNcwSeLt=Yg<%0eyP zV%M#W?~h>~)!ZFLx|q+VhuxTh5Y`Yv+ZrR^^?__bJ<>5Ny8ly0p=r%cXb#~A0IpX*H!ao+@w3`MdyRTSie z7n}bC=9lHvf8B9!bdy>Cfix@q6^x!Wexgey^|w!yR%K!sFgbvj z-$0@~7mU&(GURH|&p5>_muBk?CgvGU1!hsPn-srJYC|q|@pQR%&2zpc|JoQtlEOPIECh93g5h~{u(s5^vGauB2 zs^^w7wCP(`j0_XoZldOw^A6YkT)z4pwZ6Mv>?-f!6=st+Rgn!F{Z$jad&c~l>)|78 zGm@}>YUQuzB2R6jUW%3qYFqgcKU)*FtFX}IQr0eC?>vLQ%2%{5nM*uOSZ^Emde~NV zUZ>@N7z=v9hw-xwaDvQ7cz6c^7kuv2v7aiUt6zw`Ra`}GH*i+lZgvzlUW+Z?({e!M z??)_#cULzd!|Ay|v_HK?5tIK)m6#SkpGX%`d4_Cs5a_=GJ9DJ;I-7 z@SkaLxS6{Y&n0zyb@y=ockR{HgyF!y%%_WUUoXL;JGI=BR+l^a1?!Fkj+FDumVD@Q zS@VVhtL~N939Kr#H)<%$ck&NUFyWW^bWMVWBuapVYK%J~w=1al*O`1#bz-FkbIJOz zS5^V5ShGUtU4;zBhw|^|RMxGE$pOT~QSr)TiM(Ov-Aw(a1Mt;P7kJw;&dPFr7<=yYxI937&K6LA*s_xAvqC+U~Q7iQ7GUdE^BI<7o ze5l0Nfo?IKZ}W%;SI0lzRK-op`&PaCIn$s&#Wlov)rl1QwsH=>i(9U@nvp$I^d`2B zwBkQ_*l;M)XSyk_)Z=n6w7u)blGVn)bRgc?SGurb^}@915cBz56~`Oqh^yEYmxbVU z5(2qhyL*Q-7d@OWKviBUy0nukahTIdfF z%BW9Q4GO{C?F77hPYv|a6fTN1Zt`34H^sgZ3P%2g+ejH?=FZfnom_QqK%)R@(HwbT z542&zM&GgT696-CCIgtnKW_ne!%dBX%t1gCzohwATSxgM-A))9_51Yeq0e0kq z5Em8Eb~k91dIk(!U5>|hZh{u=-oIvrKvm?g^n0V0(F>Hxx=)^n&1^ytZ&9C`lfvcs z3OKf(agxUK&lHtl0k3(c?14q!-G{k8Z~7ZL6#K4I5^bNUx-Y{pzA8>3p8wcJc3jJ} z&9(9n{b{z%uqI-282?I8T}RdeTW(zASUjWiW5!#j5T}35nbh*F$Bps>y?A`Ria@V2 zHKktFJv>%M_W&C@BXni6271A^=T<_SbtrzkjtzfRg;nFtyGD^bQ%Mf-jiVxU{`wFE z&$@yhq`p>ySLM7vkUMUpn;Ic`1PhfUGDbpux0_seS^=u^Qk|MA=|8 z5FG>y|GxV!i*@ZmLwepzweuGlE}OkiTlr6Y>*+{nW|RPur2SpM#8%?=u*4v47E%NS z;2zH{cSpuHo5y>$Pen644^QVZW>R@*HSoH$^pIenwLbA-tqHW0&rGlP&7h2GGR8r< z&l+=)D#COn8I^>!qB*3C|9gC=DzuIu9ua2eW44B6{`*_ zbe#;xHCaJ)>__z+%}oxlM05V5SWbBGUQA)P{f)BBrq=j5UEEQOtAO7dqXf}#qywjb zT~^4KL1KO0=mxOciA_a@((9e+yBWvSw?wt_`-5T9VA#CiRJG3UA*i9n%@DpG6AIH5 zixQd7ow0P!b~XGR;dd01EnRbAotqT^5WaI0^f5gouNHxw(7tcuNy1AJW}OW?zON_n z|Fj)Zs^byJEsrl8EgpylrISRHad$Hbrn)a)?faKDBrw5Q@j#)4Lzx4?Me zkPyjmP7_rQXZj2eYz$d|g;^jemBL=Z0Z|0p;BLHCMu=cI5?oOp^38MGih4J_ONKms?k3HUUzae9a zbo3VpYJ9l}YXVCtie`v|c-@fK_bnv#k0#u8&C2bR4-N#x2ORO;i*s^cOul4=&0Q7_ zMRGy{dpOd2;jHDB8|2l2=>lT8n&2FGq$n(c3`0&SPA{R=4#wWLbl{l9Lw%ogu%B^? zl<}8@+0B&`fuqq9;!GKi_G?C#n)ZQ>1NYye1tGN})Cf_%4?@d!wv{80!ax4?vq0a& z&eRS*iGD3*F&43x)8E*{q|d%rl@fLvCGgjbWpoS(7Tc*~AU{tbN9o?u4Nmu$^fiHa;i=nVVyg6v;A zCjuo&Nt&UFeri#z)`!4Cg>k5rh4NITG8QOuyc@ESkSDKgkhB4t0n}`>iG2u!^aPgG|~3LNN{E2D4!3k z<1u4zVs}E6YZ|f(cAWPx84(696wCHlf~K@CNFQ+EePoXMMQwr4Sw4Nt8lvC9hX5M{ zxO4f0Xg`1;PELq??DiB!2QgB*XNN!$JSsL%f)^Zmrk=vcjBrU9ghD|b5rX|%cX=Cd z7RG2#Bu2?uMT`~q72q9ou0tGG#$7HoWe(a^6?ux94ZH36A343uu z7!2g3qRon5*vV^p{-n+LisWu3lpEHpB2Ae@F`v$@awt>uTJ<>hYI8b?Hd;^)(RX>VzTk6W331`)g6%AW83 zk0uA)E{1r~AI&64T{!37QIm~JKa_u^_Q4Dl+4>|TFsQ_L-M`GBpLA{JEPRO1H#=>= z<6RdWy{hFUxe-lo3X zfAD-v088fc; zUI*^7nJu3zNz^zcEAt<@2`VX#9v3fU^?o}MQZxCe@U-2Pbn4Ml5Jq2Y0)n)7eW6pu zF8F4Ki6W)z*Sq}IGx?Q#zR(EsrY?jP41gA9f?V@)hjk({+{;wHQJ}6M7K+5#DEcbj zel5ZvIJMzKEPd))KUpjl_yZdZ)~V9f)UpjCmx6{vuZ;>HhT)BDir^5ZbPp`*^SQRp z1n$K>SSRFvNw_I;&_wetcX1Z&^mz$R=T&S*IaE9>GvU?cay1;~gs$J{=Z6HtuOhJ< zjmUlZAv>qm-_-PTI~WODm>vS))n%iiDFYztiO*z8>dmG%y*$G?nE$Aw*Pfy&qSwu^ zKIj4emsiCG?Dy?=AeR{c03k&$8%r5-Li8S2wGAK!IIF}4G!tDU8U;jjDDS#bG~!Xv zQfaH-b`G|;QC!H!9?+A1r8Y|-nk$6?I_AX%{-VRut-DShGLg)`*2zzqJ_s z4(Q3HHSFnsgIRwscsHbQPC=g7!wmFqel1ty9Uqjp+@$R7`iK}q^gYQe??`y4dGgJf zH7pC;LViAb zq&U#--*LbwdM#YJc0*Tp?tvtYhm{3fJFy12Yiti-4;^3Y3$l3-Nd`(6`ahW22s4h@ zzQ!d0jjo?WqaZ#g8+Oxbt)VVJQ#LY*$TrmWh5ti%swpYql6>Fx|Is`TWbB2BG=+-d zKI32i4@D(Ntx#+7VBb1Pq@>PT_}zwK$BtV+;CS$Mjj*y!Y@h=KAFq{VPAo<~mM?H` zVRV>;{SSFQ$k@9z)2VupS^ZXOr<5h_I<@tU_}Ty=>;*-A^!B%E1ZY0DK%=MKd9oG) zXefEYQmG-Ynn|lo@34w=(O%0gFU)%63SRB4$DneEJFW8naJ#4rW%k}jdibN78M_wl z5!OnT0x6ozWR+cRQ;y#Zx4%VHplXjg47%w;20F?$cNa9zGwO!u+13R~olj;1%6m5G zt#LXO)O0-m8tuW1jMe$Wy9z_5;=bgEpk3CejTeR&Ppe~6d6IHzPiQR&*Z4IOf}q?# z`W4r5zj~t6(WG{ir}O$gqyHgg+j>p&f`#Q$>60PrBHxnAvzh^cN63dl)R!5HPh*}) zuk|-U=xhA)t*que^K+c^E<$|3Yu}R~=`dO(RyOv2ii&7hD>F7Ini<6ym9MRdF#p91 zJK1_!A+of{ql;HcosfM|xUC0T<1j~{)DRGooDsmde&lk9U6V zkihkTs!aZT8?hOMS+-*VdVv>fg0BQ=Y1N?fHLjMOpw6d~=9%NU^8$ zf;oyw78qsLCH(^nk=;^!BTpwMDBZBGPk7#)8)uFAfXcDQ_PG!_P=wA!5na#uT@1`Slu)( zRk$o4`O$M(o-y{L(w!RO0j*(4PaWEKX-3Kz=PTAZkkz^GJkGLLKZ{8bB7TlqgM(yA zNc_;^WV&JR`}Bg`p2`l5^DWf9P6t|bzl~Cs&g!jQc|Wwz6-axiH}F;0@BU@qADY>h zH+6c&MrE%JhdU5>2Sdh@!?PK+t`g%5#=S+%FEP2hBaa4Cx>9~tu8nq09M}w5rZoKz z-^tp1^yO$AQ(2;xo{}FwId5^u$i4lyAxeC$W9jXH@&ab09_=bJF9{%Y%m)Wox6I_s z1~iK?mm}$&{{seNF6aWl+o_UF;>Em73IUPz*Wc26EvVvAg1^=th4i^$SRBi^j`PhA zI^E7z7nDdXmK#$h)#tWXyj785TPi_s^`ujQh63Z3Z(VIDZk`#g&vUSOky+l#C?kv# z1m4L9?O^M2_{ZvzO$iD0$}%lH?(|aHZl(m!OH7(2G1Prq`$gws6fzsldEUFSy;>u3 z^|W?pKeTXoQNX!o-?`vfQH29T1m?&zU;co9K5^hUO=iJ}?JV`L*{7_lKPBn1!sgIa z&`fVoE9qEFrc%`BJ9)%bCmekFml?ejmREU3{ZUV1P$}#B2+n1R(arrG5#YQd_!Ar} z+FyF)z<35@vf3@txGUcL3(mK|#*I)=OjA zyAzs{x2y8UQ;WBa_9j_c&^|Z6ad0-A;Dmw@?1N*9a1q=nMPpIm&z!{57zKB6W>)A- zfW`1``6f-KU!-BM`}-c?_0c#sYcE$>Q)L*_kDZAO%n}12c^UBi*OlzY%ucEBMLM%} z_-QJcFHt>y^x+(W>>7UYBuFM~MQnyydx3c| z0jWNot>dj31^E&Nw_%vyz(H@l>@}~mDDrQc?4^|#fE+2^YT7FDqld*0uV9v{x%(sd z2gTX?N_|j#8&d2!VFSRsT>dm7$miX)=LlChUYC%9W20qqDlzYNk*iD5dL9jBd#dt5 zh6a17VYB*kNHP9|^GxrxR?;iSp-yD&OmL*5>ua<7K1$WC6IZ|*$Wn#g!h!e@P0-QF}*sRH}vR1v*iF|AT2U*V;>gGPTktxtY zXRM-2{XZrgSL1-9i5j;w{^=Ut!E8v3~*`@Z=!#Du20kh7a7iNgv2*!D0Z20XpX|)E?gSX z%L+LW+a7TE=z+UbqV_?-)VME$+4YI_hbhm#FzXm_-gQMV>ZCDizAiCEoHc)u zN6U(7W;@T?AZ_n>Ll+-`D^{wE{Wh$s0g z!sE-a(bWR7!7p6tN{js$aOORr7-=ft= zdrtxhnwE*q4b4#jo}XkX)BBzsM6_*Fod>JiEH2BEe5Ec_&K=P?U8q@fDo8D2YY*f( z8Waolg$nH$6^j&D_YCB4I)a|R7B_wx=qNU@Vo`Hj!#~z z4geXh@F7|p1X4hJ;KqK{f|k@gI^eWVPoQDq2rHSDND<8QCCnrrS~76j%8F-R0~_Pv zMm8}y^1QB*Ne{Q$S_QE3Tw|;}*V9RJj#_S)yWH;?e_$c!nD^Q^+McA{gQAM;gra<8 z#0^3M>FXyrj6OBr{H|IfU_m$*Jr^ z*3`)7@2_H1p&0fufxSXTS6R6x?|X%N*PYgC`f#tXCAJx@VypMH%!jGPN)&z+lnhA= zRu(_`wznwnA~9}V=wl`iQzKy3m@>UL#_n6Auf+^dAcU$(a-@|CdLIAaVP(eebsRI~ z%=KJ!xshjdAQ@fieLxiw56}c@#f$!_!@BUv^!lFs3xxVJO*yIzI{9TVah8|?7NcGK zyI5Mh2P%({Rc3p(_@yfBu`Y2ryIB53wbfSJ_@3cM^LW5nPXh137kifLc=FG^+kWDM zoyCdmh4fn$TzLKE-`fOG5XlIx70>0Ok}^G7?|z}C6ZUx2CB*DSx;p>YjHEq-i<&{b zz#^CzVWG!<(z3=CJc@99zVOtT=9G7;HQBw)Oa3RI>?J-qe&`oJUK*sUoM^E7DFRl7 z$;)+NV{QARTt?-Cd;P6n&Z{m*Z0ljZotFSf(eYs9U=c)xi=FgZsG(?DT4*#EzA4!I z{I%?v@+iqaxb8^Pry+A@Ap+F2b9=uQ&+1L8fJA9qsgUF$`I2i;(x1Hg-Fp=1JvAMn z+5+mtw5#$3Ph3^_W6TSqc~j5DC`w;O$&}w!lo>}I>5x=0*8TR2qRGKZe0|D*H*`9N zQx0Q58dppTzW3{&pydT+R+_I{BJ>4c@FnYPICxMFT@s6WzuWc&gh$>4T9o$Sxes#3 zUn`+b=Fi#h?&ur=$f$$^<*#%5Yt0}xd|HV#7QzV~0Wsu zv<5k2BM+G6Gv#c9#gD^>2pyT2zdNOavbU zX1T8kZ7xs2>XYLa>D?$AfAxYmI ze6f0B2<=PQue+iF!dRXm+lk^=O?VfYFLAg$Y+4ax8v|k4R*$_bW9@1!E5&jAJZ}y~ zA*i>hALa@{Z}u1s23;bj++^9sp>L$Ugl1^v3)pqK$gSatxCe_1L}8@CI!}5kH$$H{ zgAaBx)=G)N-ara*c;$zM$f+|;lOTDE;-V-Z2JskKYgy}TV1W~cE1d58C{&eoNzO8* zcMxRbNg11>-dER!%muO6YRSgSI82|wL<_(wbP;x!XT}nGJb>C8GLI=zE^maps04%~ zKPzEuqyLKG3tr!J@0yGgwFC2@d!)-Idn4cau`)jXMfs+-__E?RuPZymr~uA&@fh9$G#Pk z+^d`w5jwbk@6NhMt{LCY$y}S#Kfri3NnoX{p$SQSZp`Ak^^F!#&unDjV28_W^Y-bn z1XZaN7dD+rNq9)}XYrc<5|a6xJ`If#X-h4t2#{AECLc{;`dT`2>ZGREdr zh3^!>!3)!Et@q=gHMm1xZ43>8m) z>@Ja7Q__otx4pgkb}K9YZ2Hs8+Qq(mca~jL6_<{ex>CZ(dyWr=+-Ko;g#pS=Z8O9_?O!+8GLvMlrCZB zA8|k8`tt7L16~n>7rx~5c^sFsY_U2Om!g>k-kVn*5|3>Tu$1(L38I}qAOg7(8PPZF zXHM!(L^)6D;NE5!-foj{I5Ww8~@E+@J@3zG*l--Qu zjFc4o);GKU1fSl$T(?;tj%z8s5XQ3}_i}oP!Nk3H{{H%YT=4c(VjOSIKtgc#UamXr zu{x7Q_)-{3kn{Eru_&e2>fkZ{>k4s^YTCC-iyBTW3Ln}JV$3I1=2B42)~&2JTj_0I zyzCUq!;S&jkKh8gfG0lo3#H597rHP`6bEM^Lw!U?_6qy8F-_0iJJ&!_8ZVd=4J)8E zPwL5@W`Edmw~zw`aYCka*LmsaBDTFuuC**6%`UpN!N>t~6c(P@MogrP=Wx-@k zF6qqz;X$VKF)$;CJ>&5<+DmU!DB9CS`eSPZbfZk6D_29*m z7Bd();yjw^Bwl!{A?%7FE3-BELr+c5Geg@!)3p2Shj)op4 z34uWm{ia2&P7Jt1*~@?C#Ro0H;U5z0;YIn&iqit1#r)bq=BVJ5l%dBi&A{$Q3kfvC z;TED#0{grZ%pV@+_?7O`=SCbXU^+#@^Rf+aCZ6Ci&MB-e1jMh`$t>Te*6jwp~qkE%3<;R zx%QJ1r~-(L93QdvZ#KkTr&!F~O{89PNj_DSNi1l+LG{!2W5pyvW0|Od(}vHIdMhN} zB*;z0gX$Qxv(?*UdTl`x_ZUqyl$Q)Rbwbc|0KOt`dlaPT0~ImjA^^eDhr@M3M(Ob4 z)8N(s-I)(Ny3xp4W%k%@UC0)RmpVFw!s~=A1;LAzSl>?w!iPrM`xUq!?+(fLcu&g` z+XyQg+rq#2V?wm5c?j&r=dX+H+T5L0aPOIqcfx(UNgNpncY`6GsJJuJB@)z^u(YGJ z|2ln(pK@P;RavS=^-_9+3di~bX4Bcda;(cGc=OXYf*&(Id#l7^>QQhYcVm{dwl8RH z82b}iWhL<7V~*iV+={6CIL?(RUyrzMB!;d^G4Z;zZGc}uzYiRRL5Tuhc2X--7bQbf z6Z{&x{KrgoJv$z9Hi-$KU`xVwV>%NvgV@^)^J=^g)Tpl39Av)JSyZ3#O zJ$75b=@~}txmj;1qcfEWD8*7uVSz3K`Vr7wMW1;)J+KY5>##%Oh7~BcP#m1-l z9DjM)kFIKY*AdM#(+Mw_ez1x06ba_Ho-P(`r0p8bjmO4a|tjw!G|emTrT%`@S( zvEgUYOw)6`Dzo!^lDUo3%vNZ7kLwIPAWQQMe3gv>zaM1 z*6Z)UOX99rk)J6~Op&(!m2gfyYQAiU?m@2R+TRNAnQoPGt@0mOS$284Z?ovsGwV$0 z!JJwD?}oTQ>ES0UOU~4W71pP=Y`pDkcs^ALU6rrqiKwULSBiM`0AK>6V3=xA>n=@} zh3g%>`z5$BZaE*+6Pf(d@akkp4@FFb?qlS72LvxLsV$9q`m@kb+!Uwe})@f=SBeQ+1 zk1?=Ar(z<)E~rf3YXzI!6XJpSDo{XaT9m$v<0898DZCE9OCwsxsiUys%)zQ#XO`#u zb@y-xjB^yjr()U6A>lK%bKsyLY=3#Zq5bq5Dn-Z7ddoDXmMkL@O~B=|n2Z&RdgnOw zG@Y8fsX05iVD{{=cyX#)6}N98Cw|h2RZsQ4htJv#A}-}0I_hVAa~4zB`$!j3furyw z>}(6dGoG|?1b?}xB1L0v+Y-<*(kiMI$|l(%#Ci8Ug&lej69gZ{Wizc!7nIR|s_j3h zS9$rGy(!JKN z&huLr(shDq37=sa_qCRCoL?bX%_Jv4o%Q-DNC0u69^W;3YB5}EapKmDi+rL6***hN0cyx&NrS-If1%9GfAxtt}n8X#hX(N7z*p~w~8^mC&U^YvGF%5i|#}P z%AF!N4)gSfwr_&aeutpg@*V^z8iq3zG0{Wd(3?;smoc!1oV__FnJfpcUY-iLsG*6L zr*N!!{~nSg0>!&fiPw*po{+*MnE7a5o@|*HWYv?N^8>~CT^1bJ`ZTc$R!!rkC_NMY za<2Xe*y8;ev$zFHv6i3pu_?@$4|8A$%JFzR25qzsg%QEHv%zO+e!BQORtW;($P`O~ znH%l3FuV#3y7gPgR4_`Nb8zt^wL-z>XwT5FC{aTcC7^E+O*EgmTg`lP13wQFgh1zo zFXz7N_+?H1ui5obIb7Ki6f{qI!unjSIb_99^Et0@kn(coUN>xWxk%eQwVL?4^op_WZ zWuV={h>^LhD)~Jlgbqqe5dlDJ?``CmFJYu0kv@nY@ypJ7bk4&E5kTFVFHMs#qy-=Ao^J76e+I?6iJ30z67yU&2V#)r1?1F80gdf|QC2$!XnqiR6L{SF@>#XN8e>OmWlqWzWn-pV(Ezb`w0kB$er7Dh%6BCx7h_kbcF-g>pU}0 zW?G=(7bE}hs;!FiDIiK@;=_|^Q=B7(lPvJy-&35%3MwC{LQNAKgA-ToJ5H?@rt{PR z^~}odTWsaPK2dVs8O{U2Hf zIqA2m#zF%2O+ItV!zzLdS>CI3Kkyex5Tap@kSnY5aw#`h&MjM> zlw&?Ug0-1!V$7_TxEtGnO<-RBLUW6m`oY?62RGqW#U@u^O^XbEU-q@u`Jg|ck>tkt zm$lHxVqjPB*CFt;x~)K8Rm(rEAl#r!k$Cq0*hsih~LB7Fi8$!u-aR38xOkWX4&0b z>ETA?8|qu+#}RIgtHi>;VPiTev{U9+&u>ev`PgOUB5`Y#MLR^t-BG!n(xx#41=r{Q z`S~~kbPsN8dqjb5&~JRav#M0>OPE3d&e^VzWS6l5vTPxI)M2%U1Qi-1l{>Po`CvkaZ}$bvzBXSRDnR0mQEcKR%m4Xp~oq*)jfQ z6e07s9c=sZEHL2Wlh}JI@Hy$fT_<%4piFxH$q1-W&o^_QG**%L8)dNlp~zxFlGm}t z=TA1Auyj)_Cg^rwT}8=_6eP6_UYF;QnryQ9B^+m0Ofy7CfndnC(rECUWfolBZeiBu^f@vRcIklVwOri>%fa(0NG$n$=AS&h_;mFSg!$ zWR3w17b!ufS2Z8&aEbvG*hO!N_boLSw)XN>2#fsmw~I~&4hF}JD>L7pFx3Y4GmYdG zHIw#tgoUWuEnRPQt^W|*x@pKG*G%yHt>18#%S|g<6ZhcyoD8!+nV6b5divlGmG#I|228!ENze43lgCHf#$@Wu0K|t%k^Dc%I*?-@Ni+mSN-$vB+pO(E8w1Af? z*j0SSd2Q6JcU@2_Z_ao2-pqPY1c+qNuR4r~%OE&dNYu_k6M*p>H_3bxjNx|=a zg08ZTM*Fc-fG`Xt+*Vevh9-r>E4tl&rnkkn z*ZCfQW4vf%x_nJ*&6r3^PG>$0yAoZ}l&pR2Zj|ahC~S@AkKTQzO|#0YHA4WtGW&Qa z=vB=sB@FxK4{M&#CV|*wVvdOgEo2~{%CZi&NrDy_@i|bfTN$)LHZDc2^!tPsfd)J2PAeifQn_UaRc@n!8dmJ)h2IY9>B2R%0GG)7F{S|JT`V_C|?N zZ<(D|KaSH#l6`-?VZ#J5PfL4+VH?oGIT2S-G90pZ`m0xkr;T@ef|#eGIPI3+NdjFxTEOE8t z)#%v*521X}-IUcE4SHhV!J?f@2F5dzFy`#W5TQ$ zq{7@3S3)VaQI=83@_0M>MQ$CkC9A(G(~}u1;sXzhI>yTaiw$T^R;%rPXZAKoK^cAa zT(xOrekHs(NMFKwMH0n{JIp^F%qW7p95++SkYnaGWzKaFxzrf8Q P@JCPQ)X^+$>+Amy8*^0# literal 0 HcmV?d00001 diff --git a/build/icon.svg b/build/icon.svg new file mode 100644 index 0000000..17c490a --- /dev/null +++ b/build/icon.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/docs/需求/260815.md b/docs/需求/260815.md new file mode 100644 index 0000000..e69de29 diff --git a/docs/需求/参考/郑州情趣王_图片处理逻辑.py b/docs/需求/参考/郑州情趣王_图片处理逻辑.py new file mode 100755 index 0000000..9b94a39 --- /dev/null +++ b/docs/需求/参考/郑州情趣王_图片处理逻辑.py @@ -0,0 +1,192 @@ +#!/usr/bin/env python3 +"""从“郑州情趣王一手科技.exe”提取的 JPEG 图片处理逻辑。 + +处理步骤与原程序一致: +1. 裁掉 JPEG 底部两行并以 quality=95 重新编码; +2. 把 SOF 段中的图片高度改回原始高度; +3. 删除最后一个 JPEG EOI(FF D9)结束标志; +4. 按指定大小追加随机字节,输出为同名 .file 文件。 + +注意:输出是刻意构造的非标准 JPEG,部分看图软件可能拒绝打开。 +""" + +from __future__ import annotations + +import argparse +import random +import sys +from pathlib import Path +from typing import Iterable + +try: + from PIL import Image +except ImportError as exc: + raise SystemExit("缺少 Pillow,请先执行:python3 -m pip install pillow") from exc + + +JPEG_EXTENSIONS = {".jpg", ".jpeg", ".jpe", ".jfif"} +SOF_MARKERS = { + 0xC0, + 0xC1, + 0xC2, + 0xC3, + 0xC5, + 0xC6, + 0xC7, + 0xC9, + 0xCA, + 0xCB, + 0xCD, + 0xCE, + 0xCF, +} + + +def find_and_modify_height(data: bytearray, original_height: int) -> bytearray | None: + """找到第一个 JPEG SOF 段,把高度字段恢复为裁剪前的高度。""" + index = 0 + length = len(data) + + while index < length - 1: + if data[index] != 0xFF: + index += 1 + continue + + marker = data[index + 1] + if marker in SOF_MARKERS: + height_offset = index + 5 + if height_offset + 1 >= length: + return None + data[height_offset] = (original_height >> 8) & 0xFF + data[height_offset + 1] = original_height & 0xFF + return data + + # SOI 和 EOI 标记没有长度字段。 + if marker in (0xD8, 0xD9): + index += 2 + continue + + if index + 3 < length: + segment_length = (data[index + 2] << 8) | data[index + 3] + index += 2 + segment_length + else: + index += 2 + + return None + + +def remove_eoi(data: bytearray) -> tuple[bytearray, bool]: + """从末尾向前查找并删除最后一个 FF D9 结束标志。""" + for index in range(len(data) - 1, 0, -1): + if data[index - 1] == 0xFF and data[index] == 0xD9: + return data[: index - 1] + data[index + 1 :], True + return data, False + + +def process_single_image( + input_file: Path | str, + output_file: Path | str, + junk_size_mb: float = 1.0, +) -> Path: + """按原程序逻辑处理一张图片,并返回输出路径。""" + input_path = Path(input_file) + output_path = Path(output_file) + temp_path = Path(f"{output_path}.tmp") + + if not 0 <= junk_size_mb <= 100: + raise ValueError("junk_size_mb 必须在 0 到 100 之间") + + output_path.parent.mkdir(parents=True, exist_ok=True) + + try: + with Image.open(input_path) as image: + width, height = image.size + if height <= 2: + raise ValueError(f"图片高度不足 3 行:{input_path}") + + cropped_image = image.crop((0, 0, width, height - 2)) + try: + cropped_image.save(temp_path, "JPEG", quality=95) + finally: + cropped_image.close() + + image_data = bytearray(temp_path.read_bytes()) + modified_data = find_and_modify_height(image_data, height) or image_data + modified_data, _ = remove_eoi(modified_data) + + junk_size_bytes = int(junk_size_mb * 1024 * 1024) + if junk_size_bytes > 0: + # 保留原程序的逐字节伪随机生成方式。 + modified_data += bytearray( + random.getrandbits(8) for _ in range(junk_size_bytes) + ) + + output_path.write_bytes(modified_data) + return output_path + finally: + if temp_path.exists(): + temp_path.unlink() + + +def collect_images(paths: Iterable[Path]) -> list[Path]: + """收集输入文件,并按原程序支持的扩展名递归扫描目录。""" + images: list[Path] = [] + for path in paths: + if path.is_file() and path.suffix.lower() in JPEG_EXTENSIONS: + images.append(path) + elif path.is_dir(): + images.extend( + candidate + for candidate in path.rglob("*") + if candidate.is_file() + and candidate.suffix.lower() in JPEG_EXTENSIONS + ) + else: + print(f"跳过无效路径或非 JPEG 文件:{path}", file=sys.stderr) + return images + + +def build_output_path(input_path: Path, output_dir: Path | None) -> Path: + """原程序使用图片主文件名加 .file 作为输出名。""" + directory = output_dir if output_dir is not None else input_path.parent + return directory / f"{input_path.stem}.file" + + +def main() -> int: + parser = argparse.ArgumentParser( + description="裁掉 JPEG 底部两行、伪造原高度、删除 EOI 并追加随机数据。" + ) + parser.add_argument("inputs", nargs="+", type=Path, help="JPEG 文件或目录") + parser.add_argument("-o", "--output-dir", type=Path, help="统一输出目录") + parser.add_argument( + "--junk-mb", + type=float, + default=1.0, + help="每张图片追加的随机数据大小,范围 0–100 MB,默认 1 MB", + ) + args = parser.parse_args() + + if not 0 <= args.junk_mb <= 100: + parser.error("--junk-mb 必须在 0 到 100 之间") + + images = collect_images(args.inputs) + if not images: + print("没有找到可处理的 JPEG 图片。", file=sys.stderr) + return 1 + + succeeded = 0 + for index, input_path in enumerate(images, start=1): + output_path = build_output_path(input_path, args.output_dir) + try: + process_single_image(input_path, output_path, args.junk_mb) + succeeded += 1 + print(f"[{index}/{len(images)}] 完成:{input_path} -> {output_path}") + except Exception as exc: + print(f"[{index}/{len(images)}] 失败:{input_path} - {exc}", file=sys.stderr) + + print(f"处理完成:总计 {len(images)},成功 {succeeded},失败 {len(images) - succeeded}") + return 0 if succeeded == len(images) else 2 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/docs/需求/参考/郑州情趣王一手科技.exe b/docs/需求/参考/郑州情趣王一手科技.exe new file mode 100644 index 0000000000000000000000000000000000000000..95b5ed59c8b19ceef8b21b96fe1bf7b70edab90a GIT binary patch literal 20060712 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