From 6fbf4f365a5b624809a3a16d0528f05d874e8d76 Mon Sep 17 00:00:00 2001 From: LazySwift <36437577+LazySwift@users.noreply.github.com> Date: Sat, 10 Oct 2026 07:41:33 +0300 Subject: [PATCH 001/306] Update NoBreakAfterList with additional items Added multiple new items to the NoBreakAfterList in Russian. --- Dictionaries/ru_NoBreakAfterList.xml | 92 +++++++++++++++++++++++++++- 1 file changed, 90 insertions(+), 2 deletions(-) diff --git a/Dictionaries/ru_NoBreakAfterList.xml b/Dictionaries/ru_NoBreakAfterList.xml index cf50a9b34ff..cb97e3fdd96 100644 --- a/Dictionaries/ru_NoBreakAfterList.xml +++ b/Dictionaries/ru_NoBreakAfterList.xml @@ -1,4 +1,4 @@ - + Д-р Д-р. Др @@ -15,4 +15,92 @@ г-жа г-жа. г-ца. - \ No newline at end of file + в + В + во + Во + на + На + с + С + со + Со + к + К + ко + Ко + о + О + об + Об + обо + Обо + от + От + ото + Ото + по + По + у + У + за + За + из + Из + изо + Изо + до + До + для + Для + без + Без + под + Под + подо + Подо + над + Над + при + При + про + Про + через + Через + перед + Перед + из-за + Из-за + из-под + Из-под + и + И + а + А + но + Но + да + Да + или + Или + либо + Либо + ни + Ни + что + Что + чтобы + Чтобы + как + Как + если + Если + когда + Когда + то + То + не + Не + я + Я + From c38b36d46742ce3c773534d0a3e726346f3ca2e9 Mon Sep 17 00:00:00 2001 From: niksedk Date: Sat, 10 Oct 2026 07:37:08 +0200 Subject: [PATCH 002/306] Improve nOCR accuracy: scale-aware matching, auto spaces, shadow threshold, better Latin.nocr MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Measured on ~1,500 images from 11 real Blu-ray/DVD subtitle files (ground truth where Apple Vision and Tesseract agree) plus two held-out sets, end-to-end character error rate with the OCR fix engine goes from 16.7% to 2.0% (held-out real 15.5% -> 2.1%, fonts not in the database 42.8% -> 9.1%). - NOcrDb: aspect, top margin and error budgets were absolute pixels at the trained size; compare them relative to the glyph's scale. Rank candidates by share of wrong points, and settle o/O-style case twins by vertical position. - NOcrSpaceDetector: word spaces relative to the line height, per image (biggest jump in the gap sizes) with earlier images as fallback. "Pixels are space" gets an Auto entry (0), the new default; seconv always uses it. - OcrTwoColorThreshold: switch to Otsu's threshold when a grey drop shadow would fuse with the letters at the usual 200. - NOcrTrainer: only diacritics/punctuation may be multi-part, add lines where a glyph differs from same-font lookalikes, don't train plain-bar "I" (ties with "l"). - OcrFixEngine: dictionary-gated swap of accent lookalikes ("ìs" -> "is", "Màske" -> "Måske"). - Latin.nocr: retrained on top of the old one with Arial, Helvetica, Verdana, Tahoma, Trebuchet MS, Arial Narrow, Helvetica Neue and Times New Roman (30+40 px, bold, italic). Co-Authored-By: Claude Opus 5.5 --- Ocr/Latin.nocr | Bin 488100 -> 863019 bytes src/libuilogic/Ocr/FixEngine/OcrFixEngine.cs | 139 ++++++++++ src/libuilogic/Ocr/NOcrDb.cs | 185 +++++++++++-- src/libuilogic/Ocr/NOcrSpaceDetector.cs | 174 ++++++++++++ src/libuilogic/Ocr/NOcrTrainer.cs | 253 +++++++++++++++++- src/libuilogic/Ocr/OcrTwoColorThreshold.cs | 93 +++++++ src/seconv/Core/NOcrOcrEngine.cs | 7 +- src/ui/Features/Ocr/OcrViewModel.cs | 43 ++- src/ui/Features/Ocr/OcrWindow.cs | 14 +- .../Tools/BatchConvert/BatchConverter.cs | 20 +- src/ui/Logic/Config/SeOcr.cs | 2 +- .../Ocr/FixEngine/OcrFixAccentMisreadTests.cs | 103 +++++++ .../libuilogic/Ocr/NOcrSpaceDetectorTests.cs | 96 +++++++ tests/libuilogic/Ocr/NOcrTrainerTests.cs | 84 ++++++ .../Ocr/OcrTwoColorThresholdTests.cs | 61 +++++ 15 files changed, 1230 insertions(+), 44 deletions(-) create mode 100644 src/libuilogic/Ocr/NOcrSpaceDetector.cs create mode 100644 src/libuilogic/Ocr/OcrTwoColorThreshold.cs create mode 100644 tests/UI/Features/Ocr/FixEngine/OcrFixAccentMisreadTests.cs create mode 100644 tests/libuilogic/Ocr/NOcrSpaceDetectorTests.cs create mode 100644 tests/libuilogic/Ocr/OcrTwoColorThresholdTests.cs diff --git a/Ocr/Latin.nocr b/Ocr/Latin.nocr index 4a6a5b715c5bf5801275bb681e734f83cfd8d170..74383b76755dddd4e241a9ab8ff36a35c7f492a3 100644 GIT binary patch literal 863019 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 z z!u}`!Xyln-L;uU)_QfeSNFXcU!!3Obq}@~7LM*Qb6}H*z@m z%UyPbjegJlCR=6#8+utvt5X6D!W>R}WlE#LamMrlC+ zb@wnYPB|_g=B64;4Bb^ic)0z`;V!$s=U2JP?H=)^UE_c(s@nR5Tr~KF&iCsTBTVJA zlN)0E>5c?CK@veq$voUFVeN|?5Afw7iY5oO-T(U^5~rPT;zwrj&7F9a-@Niyaq|op zM|p_Eo%!ZJ{8RkMo8P?4CIrLyhyTb+{*Z6+#lG~n1OoAo{1BIXmtW>vF7YX*yq^E$ zw>{W`HO;ocjSlo zk#92asDAjxzU3_kb0YHx`C(swNwm@6P*+4^1XJ@xnt3&cJ?v?l+0S_8G7p0d!f&|3 zJk`FNTWX{TBLZLe1HY4lDe$f#ICR~%*h&UKZ}5Z`0sbJbD5Gel(ar?L;}gR6T3d-TYQKNr z|L)iG1JCk@JNYhtWZ^&D@elURztNAl<%XYHSQ1Moru@<0Fm>QvzR+*{!#~Zp_>nK{ z$HK?|;P4@KS)dsq7#Hw`fF!T_=5HJhgWvR9yo)dSkV#R4#NaQy%g#UL!Mi*qQk`ujk+m zQcgG;WKx8mARRnOwB5clf;enOIVK#8H54(53qI|%2|?re4c+F|erP3?RT}zB1T~x~ zn9!b;7L4qxc_2n9EO+sZZWB#G@Xs~d&BMHdz#0IgaLKpAaFS8QujqwfjD*&TbeLD- zOfGao;vHuKS3+!>twBjHHfW*8oqp2Y(G8@(l(Qd(Q_C ztf_bRdF0*oM}6aod-2X*w%6qs0grD`>RkG=m{X>*Y3VW}KsNn!K^5nal>+uf?W2FhRNY1=jWfk+ zd+sI7xb^9pgq>YG$(Gi|m~xK3w8#w;=@IU5m-~3E*JKkYW)r;L1GR&1lXzWc-e16V*|#VeCN}*P)jL89eM}nL%H$^1~uK}kdQ#+ zlEo166E6Zi0EB1>2~O-0^tDhYy~LJg`32|A3mw8dCWHBQWDsm$!aI;ITf~0~iYyGN~yU_Aa({9`+{5J?o&} zITw6?&uf15m+?Nn=?DH1TioPB9^#Z6q&W12gM6t4mnZt+FKNW+kfP(Q9L~cpl$uzo zm#b|2LtpX(UQBiChqzUhM}NsT`D8ER0erFc7+>=zgtDBE$yyVY39-s-v`#@8p`{W` zDesfkzKUf2=5KEoANs4YLFbr&=IiTa!hz~J_uFed7Hc?s`*-YkVVyej5kF|e6!gW* zHp>Rd;m3V40q#tK*IbT9ishSr-{)FOPvO4YyIF3FAN|b;1%}rep@cyf^~*!a>ZF;L zMK;@y{Z0|W=EJ_@PCGfsxyx*p1#SV{{F%R8vvc>=hwpZpreW~lm=iEN1mp+og`NkP zFZE+jvrxcUy~}sN-R`yaa9u+Y2{kn7M$5wD;XD28FdYMCgTrBqO@s0h9b~B=^;JId zJMjn%Z8Cx{RahE@gX)X?bwfMy-~WTfmZDC|MLR##;SM_)DQqk5C}{j9&jkLg^W6`a zi-^Njd9fqWO4nR9-{FSfPq7PHYYhTjm2Ax{DW_X=hoeFQ{<2g02frp;3c79Mi1*Ip zVAk+JGg;!unsWtFQ3Hj1Lpt~uA=N#ld z&xjamkNUPA54&P$o!&1Wqk2<+AQC=L>ANvWz;7 zJof9lQz;p$uCmGk)5rO;Zm+e59l&rS9s5`aBYwRcH$XD3bu8sn0^j{eB?TCYytG%4 z`#I;d$R-nx#e*&?AFtO-7D+?!0L0j$>6+f$ z+wXnn|MXAeqbBzVkKAL6k9f-0bcMz{eI+0tnehw{`e3i@!CrwL0c~`2&;dg*w8>pl zo+W1&Udo6s{w_0BvdLLyN#sUQHaPJ^zNQy{qm#d&Lp7N6=T%Yx@7P1X)KIg3@Yh`9 zOUX5&jUO%LU|e==;sYJ*K_7V8hj>e@aB|=W*xL0Ad#fe;=xZh?9k3ILtwnURA)I&S zg}wxwIh?ryF3iw`{^I{vQ{}|d$}^HoJ?GArz}C4^N=$pS191sIJ%J1GqH&2VvmnOB z7CNggN~{S##i37q8R(07=t?7fjb)l%D>Q3u&x>H~pIInxTnP@GVxZ}lM**CwirJFU+_ z*z%iF#6t238zepR#tWxlr5SmU1vfay(cdem|M?%lQ;}|zYf1*Ls|12kTbyaMsPE_O z{DyrfP?Vk$;7lR|NZ;)aGKd$qxzLipk{C5aDB*V!rREn|a2PA0RDYBP?`41Hp0raW zGs{jq#Dxa9lF=d_Z(MS&k$GW?$sPdREk#WZb`+Nf^U}CO-DKkm_E2s}dJeHu0?~c{ zi$By(5vSS*9i0XL7RGjpf(=2T+2#WW_gRzz9@Gp&#)B}yY~+b|QABltkB}L}_6<`u z#RN{ZbhjHimlQLsAy5!)sI02WGmsw?Y`S!}1J?ZB`@|&?B8Wmb;x1VTxVS1aH(3@t zO}EX=a-Z37%0rmC^7Hf0e>L>V)$0#8i*U<8V_? zJHudI4K_q?B*Bkg%B`7P;;7jIJIN_VU8a@M$N?3)WLHzbe;O^_CI+#Y?wWZ%!u`?v zsO|766ht_NvMx%9hNCP5(HY>JSyu_RXzX-2p-qaqVa&K7ND+X>ajc!tUSo<{Yb#`z z0a5@NIO(JX&jAvM04xVI=AalGyRJIhjxhv^I6;ztcpIfvwu~zHBW-zzUG~_f*#;|N zvH5nz*S5>_0BW()mzfuICXCS<22%o4;*WGd_RgV*fMf5H%}Q(Qic4X;BkiQ{Kl?L6 z1|e>^g%lR7VHBz-s1W65Wc}NC-Yh?71Vs zM1lpaIKs+`aBqY5*w0{maXo4jm_dR-l+oeID)5v-@e>WkV)82l2aYA{i$_s8gd~zU zvA;v)lvFz1q=M*{&ax;bk)Aq9tQ4peRMK1uO#rl{n>K1ltrA27v{y?_u#W&FULl1v zfB?LTU>wsTRJbS0>Wu&#L=dmRF4ywXS( z8@$@CYwZceP%zr_Kpoy_%rypOh?cW#K+FIj9$24@b5dhZg=QMBa4Mt;^fL3+E)4C7 zfIK0T5R3dw6K;h8-kj({8!N;Q7+YQo(9;X@M#OWMNfxO+5dPDDrfXGEimp0T+7!|e zCb#gJipq1c*-2=>kZi1n$RbRrva;}OVL=D3I&X#mHCfyy>K33stpdfwafK~4LG;7| zOCD~}j#;l7<&KC$-$2iDGI(&Y!TSI@m?Fi1Ii`s^&WIHfQ!ZiFO|qawvaACzQ-mO( z%=S-CeXt=wsf;DX%i?wYo_pp z0dxL)putm3;aCbuFgRXY6#Vc75@#rpTuO~ltOc)%)J&(6J9Wf{Gpux%Q#x=!qphs) zMr*?iQ5hEuQyv@%4B9H_4B@d1M3{8O14W$j_B-C?b~EhUVu4Oq+T~!HOE?J;%PhIk znO7ZU)SA?d@Y|menD}rG*ct^iZiHFDl{1D!6%k<}SjACd5h=!1NFqfAgAF4rV$k*h zbxUwg01RKLqf`=FAvW=(-oR1R6U&nzR5B+jS5k`uZ5}C?idi6jqKh6FbAy;b#DWrs zOE7@R!~;wfBm!|E4o~pjfewnfWmzQd;RS&uCM0kNhFg>)%On+$9DXK272(e$rAEM2Ve;?6GfTij*cuJ*Z9_E| z)^Ov5#th!lgdl~VFMX}0RaU~^XWALAnN~1LkeWFqw1v?N3>TEt5I0MESsPDM6Ul9H$ ze+4`TF8eA5kLq|Tgfe0H6;fr%_M;@~JnlLJ<5ta4&qP~ENhexl;kx+5Kt->tPwnWn zR}1#wpf5C;R7Rex`70e3Tbg~@xbxgu6{Dq^LrfU<3|tUSsbW>$Oeii5weY(bqxHrk zu`xti)g?2#TFpV7w2KLJtTOvyA6cc6FJNpIh)gxl8U(=E(2d8~;Z}e8lb?cmT5E1h zO7Fa=;ZQf;V!@Z#Jlum@L5UF&Aedt5Q*r93a3rSk>{b8czttvpJ?CTGfC>R{AbyYs zD5~LWZ-%ROcyJAq;b)kSb-AwlyxYT@U}ju#fhIem&>rhyrtS0Wx6?*0y!ewRvcj0> ze4q!pM_6&$72G!x|zair$al z6`Vt*8P1QQSNa?DETNV(vaO#Gm z37iK7^t1Y-JhajVoExgBh$L1bftX!qmqJWW(q*u4ZjaI{PAE20Z#eBp#IPBvtF4cA zj|)A}<*YX_lCjeduL39%F?Wxi2=xiq|03>(e7_Ysnr)GG0mN6J=4`irhO%% z7!y1IJc2d4EQz#S?#78rxc3Q%lGyqI3#53f6O0uQv}c_deuyMH&UG};Ay1;Xt7{g> z%wr?zX!QU?3Jqks%bb!^0hv{Ol}jG%q(h#=wJe0iC4&aeFnxn^V3t{G=chR*+$~#c zMPy67C}GxjTe6<`uf_&KOIc@0H*nK@28D zbmOx<`VBi0UqXlApEcU~?kgzVZS!^9^MMe-?H;!;w&`F(LlYs61oZe|p`fEn^X`uh z*xVV%GjAfbe)hZm<{z0y0XO#pZ+>R7C_nKzX6uR>;vf3vO1hR)$_aSO!ljE`EsrMe zbk#DNe&lnI$=E4wa^Y>sAo&^tNrpqa0dTNTcy=ez)TR=8ka z+>UwyD;^;|6)pD=FxAS{ea68tEMjNtSz9rP>XQNLBxn5wH(0$ zK{`wE^6evH?j=LAu z_#qZ+e5xV_VgwcCAq?OA47)2Uq%s@>tx9B1AfPX!DNX?h%9))zR4J>f-W0Q&uHN#X zqsTJ4Xo4#adfP_BkY7T{QURs(cP}@B)koJ#N?z%OK;>|T9u{=Shw0DG zXPjw{tKE<+=%yIE?B-K0^uUi__cy}-)1Rt4-Apq(KkCO^;gS~_`mX1Whdk_VbW#&8 zc7`jw#*g;tuk<=sJ%JfOJLp&y2P6Lmi;v=8iABGHwvWbhIw9|uBebHU8 zga0DZTm7oXYKOk;_=@jP1v}vG0hNfme4b`7B`jV2w1srSPrCl)AC}6;+up62nnbD? z38IE=RX2!7RqaIV(hHShFx_q7a;Pu#f9!@}KLP_=UU|ldO!=^?Ti|)N;IDZ$TwhTCWfQ2|F$4ou2o2q?&uL zEuH9mh}k|^XM zciC(^GO0HYb5VoikuUwXtn6M@1G=L%Ek^GQEa$6J&Nt{7X&53xeGl+(F^05E3(4rn zgHf^Y5>k@Y`;o7GRXIzyO2Z3M)?7oK{VP{Z>sBFJ5gK9s;a*4ey5PFK%8FWLPSyqtW)Sp0D)g+ zU7Je|`bH<($XR6}cV?Py2@IRgRGzHsEr(ux<%yS3L&h6DgcCJgH^P7Q&j}%iAmUK5 z5pP8GZk`#Ea{x4KgRLv{0SLFwkxLEJEj@E%;)@;a1SAYO891u3&>`yxtpFjZb)E;L zRkYTodhN4cxnPZqD6UP4AjUpE9epy^Q623 zuQP>-ie>e`aXjE7>~xlqVZparPbW}ndUsQu_OQFJdh9ihTHs}4T0F@%yDTwWcZ(lt zxM|E8j??vY9;1L2!JPJT*pue6UuBJ5&%8k{ zT;k9FdB7yH-7ebG2`6?ssUT5vb5L(7Ev(tQJW#S56~RcNOHm4c{0AA15wmu_z>O(S zw%B$X*c$DqH?TTH#!(;hfIkN0c@a-TIrocZ;z(fPj1PJ8C{MkBZHfw2B68+#y$W3t zo*}4G*FiCV=bKm-3>nl=Zi!`Wh(d5cF5PpHEER5IsKt-;%z5y{GUN8rQ}NC?#NIuf z_AUqa*lJ=V3q%>=s5_eVr+4}v3z29yBlTnkV5Nby4-z-nbt5( z16!c>)>9}B+?-$xaY-j{q0iVtPT9k8{ns%h{x`oS=GBCbDDH&CaK|& zs)Ax@Ox9Sd<3yRm&o0j@Ij7k~oyOoSZ6ZUaJZ#e$V? z#1*Tb5kq{|<1)qKOba8dbRfkchUrm92VbbP%Jeb{aoiItY}XZ3nw2PO;efaDOfWOD z)n>rJf^coy<(HlYdU8}?3G__SO~)GPL>wuu=+!24^gyVhKs5^dP~b*2GoYt$Rv#Vq zV+f3S)weA+OTdEoH-A4d#ap&6W1ZOLKuzhOPCQ!Ckm$6LYDQQWRw1 z&M+vAP${$ptQ}NZWg@MiL`7ny;vgBbRxs;DQh{{kQ`QhvFd-;Ku@EU+OR->1WtLkm zK6#)uFk@NG7=jF$Ih`?umq*TBDk-eD`tY}CjDW?*&qJ`AY7n7%N7szJq zwfCBHt}z&`_150^{Xfs|SL|ek2%5MWt!YENB#atvi06q24mG@o*&%>O(eTXerG$Vu zEir7U?kMD0i7--Wb?!qPKdhT&4TRho8W*eV8WTHvpjtK@#iwlOV4wtMUhGq^w*j=a`m%m zt^Huo(srhWQmoOgse&|sUg@lzG;+lY(j^^7Xwyw~lw$xo+mP8NlOr}l3~?Al^FhX> zQ)Hqj2WwZrsP1YsvaYg`PK%T$Q)j~T*8A_|g z&zizum}!IZu*gv=;!>k2ln6IhKGnRNGE2OSE> z&?}hcN|9FCN|I?fGlkBG5LQI)4X3EYA>2?jKJzYDm1MEiq2ixaF(fdQwsKqgh&m~q z4YmZzYO8Sf2N?}zMD*Ue!(q4d8o1A*y9pvmng#eO5*QhkK^>J9$Jbs*g|w2BMk5x4 zP%xMfIQ8wS>GHY8f0yyfS+59xNX?1qDRf7;ZBeM`7(x=g2H{hrK<|lKtW&PWhJt_A zE=a#q>$=|6UH@Dl$gvrQyNb{z+ig?xfu;_g&3 znSYRrU|}_rb<#oU;tL5R_F0ywgTlkFo2tPTs7qf^Wrmon&$i%ecY`fiLr{z18tWo+ z(q5H{YxIX0xYdMcsuf8kW6zj?tcimT>8TczV)wU1s;R;3(&Z=@IZ{o5NNH2hUJlaC z!nz@Xmy(hIbJ_|csiiS;TWavR>BYKIO16s}xQeNQ_rNUEU@cifg`ROBS!#z|ypJK( zFs$=|4l^zBR86~(Ca0l-QOOM2Zs@~!mRM?L;SR_Q`f0o*rqQp5u%W*SuXT1uW3O6{hoWW9EhMG{Ehr6{koR+4&Nq>2c?QyO%&I%z43wt!ni z5rIo3i6Bvt86h;=VxvsT7gK_Y#uZl~uKN&tsx3GxG1|$Zvkf{MmRa%Hq_X7clv2p< zfIuOkC(gDgrDc>^!JBTAO01Cd;e4X}nFa2d02!JrJSngfRn#BW%Kz{el9Ol&wcW%) zycA}sS%9&dZ9HCw7dPdm6|A-7y;VqFh0+*Gs4nvu7HYJmheK^gvINaWQl zdZ3+OQ}w*@(PW}cdO|!4c>b4?8*`+v?xv%PF(qZw7%#U}SO8kp{@L#-+bDuTTSZ#r zkX5nDX{)0^T0dkY@nSGG7^O{;K1Ju8@Hs_?6u+~@M+hQ)Ov)xxLP-u&d@pP?6-qCd zRa);g>Y!U2LuOsZ(iV)@oz%)_9}FP(-UlnS#?iSkR;r{i$eefqSUBpi_<(=GE05d9 zp~YvVtFugSK|->5)G=*wNqHG%G=O0(q4?OAHcI$K=zss$j5XgT*@wB=&31D)#(1b) z`H%g!%nc&S!_}-^E6L0Jub$>o*+@##Tr}8`JXZA{=a}MHKg!B zPdUj>?j#Y%Ci86LmE35dE8i*74GV|Iq65Dm8eM^-{Gjxr*u@t=Wap7#hzNZCm-u)w z#9$3vF~q%KfJan?fPK#d5S5bM1XNjF1QtoB1&;#g$>k;P>`rdr6S^P& zEd5K<|L!jv2br`QQCZS|+zk+OFMAD5WUTeJaJ99y<{PcKRYRE6w{*3UKEGP_mU*R- zD`E<5%G~@VIPq0B?MW5XqWVpoCyKY+cxg2%1eYX@oO@DQTLq)hAjh<&%h5nsrLNT0 zsVA08t}VzghJ~ssDYK1u*^3#LFm!LUn#vvFm8Y_ny`|WjZWG8zO_&0I$zAeFt+i5H zX-nP_4eTQ;oD=4w4qNuk zS5g&nJQ%&P&d(Z}Nk_co#iqaPo$aL5tzA6RQhYPO-T0fn;#IclDy_5ONrCp)-u8MW zS3>cvv5jRqShIJwR@rFV%O#iD-G(@Ucy)0|mqLJx~YAdCkd zJ^{}_Otv5<53{&U3aJg3!X)?MGz!g)EvouzN?cNpG@yH-Kl`SC`Zv@RyUb^ z<{DZViN0CZ(mPdjG1j9^VwH+2r&s}M$=+revbH(6B1=d?I$r~9Fo?*oya4aQ%fH?eVTxfcKqJ)DhFo2WgCPx_N>vWPg8*hiU^69sBq@il>uNeLm* zdd)aQI#?(ujS#lAMtK>eD=uV}9kLJs9BZXaDj_$MO!OWtr(~Uj26iSuz^cj}Xb9K9 zhNCJf3(+~slUTb;43&}F`CQ|_l#uY6NLaCyCQGUHi>1`0DN9(eos+cq~q(yorL@%=r(`2JrVu(Qyl#(OxNO+Ujt7dAH(L>loEEZLQ z31vhV0f|rI1uS1g50z-tN>ygydN55jq%0%ly!2kx5ZO@+`BUrgpLh)MeQ#LHF1B~B zm|sK(1XL(q6vIgqPQ0|pq*BGTN-UB;!8;(9;-GN0+M4X5r;O6*I}TS37_ddh(n9gh zHt0W1iOF~uN>a!@nwV17*@tAU&)qs}Q}ESBlM#Oo?ZdOB3+>Ux)O~TE6Jc9Tv{8eV z?Q+Q7wdhTBHoN#?;HQQI1s_#bA4^ezOFfoQV#M7{7B)QwTETft<#E2rCmGFmUsjl+ z|MlNl&S4>ppeL*G?O*(^pJQ1r|05pbdo+^S$~?Mdw9!L8_?--#*Dhy!E_qA>(cZUy zu|j;m$2aoF<&|FPrC!UqcUF>dp!kGU+T|8cL8mVl`Hl1Cf&WulvlEoTCp<&o@^Ivz{VuYiPx>Pl>C$xF+mFOWs+OkS>aY{EU(|f` zWs4nVxFy~7sgVuF>E*0Fy0iow_jnu2ArBSZ629~aIJM+lWD{&S!`?)rN;@5E8ot|6 zDfFXEGAR&@4Dv`-Nv}YnXYnYKk^3$AdL=|mpI~f&U0}QJbbUg+5HY*<}|xSYG+Xk z<%H%D7Z(lUU5i)V!sro%3Cp|wWU`Rud-+LZXicU1tW300U1t9@IDA*i3++7j zHjxPkzDuN$L^^T82Pdj7ADGy`{71*kKgYsdE3IFZa# z-TG6#s-0=umtEY*V4DbAZ=N>3=*X6R8Ta`pPw}K{eJ-TWRo}T9O!8GnOfY( ztSs|>>`OHcVEesq_x>ubJ9*IkT-LM2;FfFI=408}rN6}`TB3!EjV{f#xaDoCJ6+UG zJm@NNdydyqMwr*~Es}M-o3PxDT|M(Jf=LRbR}p5E1>>rA5^ed8dVP{QrapZ0WHR8iV$2M)QJ6oLVj zdbx{BrFOeH|CPT7S%~6>EbdI`84P}EWurvw45!o6o`HLkP;;}I`bstejC&>6UL7cV ze5?rQ3PKE5$&TH$})v?z6vmkJi7*Iox(QyB5?A z#bEiI+u>gFv8L+mTVCQ2kL9@@>%Qf6oO5|B=kf?YOE_kI_ec1ZemPs*Huk&hytPOA zVfmwyi2&sXns8%6MT+QhW)~Ub-oD#YKj$;N*SX(lljkwF#k|UMpR<~8c_%{#w9mr3 zmTtB?6w=5p_k<(XdTmkQ0zy0<_*A1Pbl#OGtbkweyf-)C{r zZ_Bc&hk1x6-$&jOCRi|;8Uj!?e)5r762(9M{YDPOqu|%Op7Q+K-;*u!WP>m(Wm@bS zv!D4YHQnRUx-HUB-7!15ySp7}4c!;^i@i@$g}cn)R2je6&6m1ii$B$>spbYxSGYL9 zfJOgp`*r>dUfJ&m&hcyhO1JXmo2^ta3)E-d>W)zGCzZCRz2uxbsT97Cbf&$`Gypr( z3@TO5z;JFy+Z^D`yn=qfcDW1SykWZ~-{edF@E^bXMSRZKsWjcJ8ypyV14j#WF|c5C zJIV%({z^{bzU-~^ycD{E5>0Tt4o-ZfI|?A!*OClN4mn67RTkDsxqWWTKjD+z=4(Ir zDZaOSZTw<&t4Fua+wvoy+84W~YdPAUu5I7;w%_*DJl#ZgZ+;uD`D5L`a7=V8*WA-S z%v*hjU+#5mdq(K1IO{|-fft9<$K2ljeXiTRz5UM;w!$vfMG|fOEDnLv3AJ_sKW7O~6fp*J9U)?PtGqwFNgb2lxdytgfo9X%$xKu`H|J z+?sCrcF2qC&27RFH_7BH*$4_N+A?3+)JYrC*n+u$2)gM^Dha~L-|2A`uzx(+pjxFx zRMK^nuwHlUyHz36{P9WmfBfC}>s*dmEj!bX|6zNyWau{^_Qf#PdhTtxopU+mN!DX7 zi8p#hXCjag>$0W2FNN;vJO220B}uH0`kub{{s^yaKUccRi~^>_vKlFdpZvq-NB!LT zvp%KoAD(@GjIZ%p;Pbu*gkRqN7b3Cz#?SQAU-HJg^9AQB6UY)(Sk6EFDI1;aWJ`G0 z2jpz)ecaD=>05*MYPq4Nv5vLbXL<22`2e_miN_LoA(8hFjy?!1pA#u8vVZ#flWZ|Z z$oDvEO~(P*pIih$#d)U8UgY~^hoRESwz?Tldt-)qcl2p{8;3X?UgHZLoOvoYb1G)I zmd2=63{HRO)7{g3W1EBo_*2knF(1$Kb^e^s-E2nSbeb}G*zL0cT-9QGtA%K07d4cH zaq1`ssCy?rh*e7a)8F^k`SLlmGTPjKoo_&a4v3_t9SCo@-LK#zn9Cgz$eul~)N$9> zKE;px`4mW6X-7CMPIPdB2QWz(uB8@>+x$G}M)H+z=kxd}6N;9&`Zcb;#U5||l0tB4 zy>dhvuQi1b)M{lc*JAZmWDFE(B#v?u(Q+nAdTs>dcChg{%9-a}6^G5wzxXf2mvH*W zMCJG?hyCGMJ$biJ?aWgb`(wX()-M4Y^_NKI&ZE8A!oRf>PZ9ZBKlzV{a^+b&eej2Z z#ylD?r}cc=q`u_aT z;!6}c)Rf=y#=rEp-P8}hz9@V(-=dq-R@3c|dhu1A>c>3v6tIzB_FJD4#G%HMY08P! zF8nULdYPAe*$aR!{%zm#M{Rfz@>8w)Ee^Hmw{kp+FMiq|dx?POdaq%LitT=^N7eDnwe9Nk*+{DGbF8buBzVVm1)QQNqdW)yNWRbYhy0G$Fe=CP! zD-ZH5H+%)Q{_r_s z46GANRyJ57Uu0)rWT8*;Vpu7?+4-4{6{3L(Cxx8sLBD9El04WqJ;`7<@VCiNtKk*2LBed=u1t#e7fgNHQiL!Q6>FQ zFZ|%?i@ZsoLA~_1GWzp+Aq(BHs8`?Wt={^Luqin9d=?o=e@V(0Dx*t&+)#fC= z>3c$^`zB za$ZGukMJzd`htrA8=&&2U-6xv_1#}^NSTLwkw`sK7s4BFnki+eQwHwpy?wJAy{Rf| zA(}znZEj|)*ErT|eDBZk zaA&(rvq|saO3vHu(Qfw8XYw|h=3Vh^$c?}D*ZR&kby`GAZtXT^4Q|-rF8){!xIpKZ zx|^9;rTCgbM1~@kv%!4Y#iv+_dFCgr%1l)h^Z`O@NtvM2c1a4R$gFqSt>9+01VJF*6w_Dh&^2TmzL(xqx0R6xHgDhl2LSIN2=nis> zqd=$@XIz?`N(7aTvQ>gJj8}aj?<1V>V8tGDAIwlMhWPBbVT?0R_l+|PVsUQBoCR!% za}l;UkuYBRQkmzGVY9N*1H6RY9~L1o_!}q=uC#<(ZRGFztl#yav=UM)$r8;22vg1M zYNji4a!;i#7+-TwJQpPBu0Lr*3~vXMZi znMnl7I$4OLE2zfCNgJN-)nD`J-|WHP_GRAe(Vpc^zSsfNJl)wBizKt1j{Yuh_olD@ zEN^y@ixjpZVVGuJ5)T|2cRV002_$nzvh)O?SwoFX9FQVUi?rlpP=JT9-t>X|bKCvZ zi>bX~`ruOC(=FWkS1u)@#yPiHYH=kV>OzFme8sDr@tk*mbdjlT6@BWjlxs3~ee1V= z)U$lKW=bcMg7ejQqvfmL>s5Ap=*uOOKat2UbumHdxXpXM%2zG9mfW*B)!Fa)KJWbt zuzJvfo2Y#73H@I|ngY9#8E17jcL$TKs;;1Isn<9aGEPM(SS%g;kD;h{qg4cU;gw1;&b$yRq`a2zWSTK zsP~pEtoS#|apdez|02^(Hre5pHpyM!W{K<~4}uH-a&K6U-J5)~gAVXy*^7OFmP`(| z$3;vx!z9?dIBCh9o%JbR?loU};X^KTrQhns7C*zYzv&lN8nAoym$t*HXL!?!Yz9_F-nwFB=sZ{uFhe#91PW0gU| zJ`tyZ0|#??638qQipdIYfcdgFghA9@-;v}FB(f}2Q< zaFb!o3t%0=6v9-nC|5oXFCTBE9W>`EUBv^ z|Pjb*qDhtHTZN~QH-ptdML^eqsU-g?T;%m-vC%gKl3-y>KLbfIz{IbvXfN^A*nDmW=m0)EKOO!A&+uIrCOh zD-x0#g>RwA>}uk1zMx&p?bd#s^@0w0E3!kJEWGiMrU1wbgb)E3$) z6H>U%9iiHS5>V=;x?8*}w1)ur#O=nC4$Rok%?JJ`3rHH9a^XznU;TshK3D&deYsv`Mn0{M0jvUI-Zw099`rYUgFLtY4UV0mSmf^eI)RIp5uXBqJHJ4xUrjl zxhLuJB8RBba~vAh_O;&AOTX5AZ)AwdPoB5A-I7}(-_?Cd>uBHe!FN&nbKUT#Nf(hN z(c+J~%R@bA)v;hT>}KG*yu$*uq~#pFvHRNz5A(ocJtr+c(Vzb}tyw3kJAx|24Z z%UAj_-{M>B>8C#DZ9Z`N9L#j|qrtWMJ+FR`|IP1*f|DIKo!}_O zLa5SZP0iiS=Chkd5xL1jE5+@?jxg1s54z~E`RR{#)}D`e%e9Txp_AX#O7j|HZ+0wn zN;~boyVG$=)Xo=fni;K^n%2CJozn4N67PRFYbj z9XH}X|5KaUGdQ`KX}aFzt`E8C;R^d!7k#O-F5@akKkMup4Abl_F7m^l&L>y*`4C_1 zyN>AF*=9Y{S!P7?wIP^+nOJn**{rq{gPUK=PrsMm&wkgdIDt1|KkT75ak3wde|GD) z^_@;}MpS2~>3y#od#QbS)P4C`p8Z;Wa;dvI)IxsvSENOHJh*uvzIY4f*vEsJ?XVEg zD$5}g`p3Vco_Z{1kcH=G`R-w`@{VtADc#x{i|&aFQ8%$_Wtbjrl$C6OBvIgjNA7hY z!^U@c@kVZIUDc3XYsqE(=|9_jJ@GtU*LCBTIrCOO{^5U6x!LHX^*cZ7tNp~)ZwU4f z$z*t@$A}ZB-<20W>N9^Md69m~tLa5o+mw3g$C8%1x+ToMcDKfru4IdE{}hs&#wx|s zkqMy;u;57eZST-KkZ9#rQ8}fK{zG?#9Gaf-NpYdWCKhQ zTr`CdhapVitT>wKy!6}~u00NPgl9S%Tb3`v{_6kvd%dJH+Z-qV6v^LFJkbu^-3Lqos)n6wZC{xVe+8!Z9ITkWTYJv+yK}3VR()EIw07 znJaM)(KS%j^B9*|+(2OtK5pMbMUIZ$2o~s{-mhEyKo`TeSW)D}2Pby>nVGp3I+(zX zX2KPfZunIgs;3`z+G!w|ZoEP|K``GN688~PaJ7adE`v|xK089`{7&y7r%G@1KJW9= zUDtMNcZ^x*IWDW5%yg1MCM)bCq96$IdTw<7L|wf8ZQzFbzyDB2jbw_GbXMb;chI5B z)*8aE^f0%8T5Vycy2$D-b5l36)Qn$`he%{o-ntTcr8|y5B|^4?9dscJZ;2#U@WZIB zi;FHi$v{@J^HbgHT5CMaJsrLAo$d-#vt8fx&0Q^)0KaBWWnrhi$qzW7Y>5TIPTIuf zt`_QAka29aWQQAzx1MWer@iRLs)rwzMhX*-`xtQ|`A7%2;so|)#k4M<6p3y>KYUX^ z%tJRks4>$BX$&SG!)}D2bmCWxt-_+Vu(^dXWth~Ws!r0d-5@xULUqw*<`~v})=WLISL=Kam+-9C!`|ddYxTxT@W^g_WgBjmk^9JSNN&aT*>bkE&moU| zLOdW>I)dwQmO9fp76~ClcDC#VXXSfztt4oPf%Hb!iKE>Y`9JvmsROPfNk)`iLk9nFm`Y94J>ntIO&+EZ@X;lso-s*)sG7&TcR4wX zs6$T!xHy$UA;!^Vr_>TN?R1iBN;zke3DU1|3qX~_u9;nl@SoREke8bGe#5~D+UxB; zZ&P(SXr-a0;ks$WFiRZKnn*I#RGl>TA$$f{++)YdJ7b)f7Mc=V;Kst!Rd+MdgTkc4 zG68HS$G^&AF-aNuErMtNfBkh4*eMddi9!!@W2YekpX!~1Cox46l`Wb`&I?{Cys#-9 zp8|xk%Th(@4LBoFF%SF{u$iW8$|Q;i`zgTptr@GL#uVf6V- zr!rglWE;5&FEcxN#TyW_J4=G#QqupDX|Mkn$M*!LsLZ&`NP-H+!~VYVC7rzC~G+VWp3d@|I#CjC_;CY1`_qn-*A!Oj#E=_!8xaQ zd4$uQYEyW1QRr9grEeqdUGNFkm$}&<#%FL+T*V6& zs7v2$Czb{XkT9zXfZwfZF;XkH`4b;_ec3~v`t+!4x}epn7q^*RqT)Lgr;H|)7~Nc;ibj(iisOV7>Ir4Qi)W*)belR{ zaXTSY+My0NOp-TunrVkIPPu01$3RFp!=a_VsY^FKm|+@9FBmH=8Zbjwv)jd;?xv<$ z7^BowT|fzUXqg@QLg2-$W^<>(*XdR|ZjtEU-KGzB7;lmu7C~_EZMD8B1RPHZ_!?5H z@dH%9KK*FPxEpHd-CTB2i#rUAx+iu6K#T+0sk-yhIP=@U1^jn^%35tbClgF|Di|wH zccxZ9r|XqhDv_C(GO~k5vg(VBk1V;+jrF|QtDwXj&`&6pZ}K23PwZf;KhsfNExlHZ zE(=BU(FMad(j9fY;S2=vfh+Ajv?3!Fl znc@&9ozm0k&$t(4hyhs@jYt=?TV>#pG8O{dKD+!hvUr9w{;+iR+b0rR5W`pFe> zoKj=Aup!;w&@%^egf!AgcU&^7r&>usL#R_Cl@d38b=^Iqc&0GbFsqS4p*GksZoZYP zA3NFnj~GRb9rBNxsvsN(43sZJXM$R3mf7hUe6DMA>-?v*np%=Akq zjdxL%1$I$W5Ia;zJ~-)wPC7&9&}B0r_++BXj8kD*#5jOpmh{`UYW7$E{%_2>;85{_MY&iF3 z;T5?tVU6V7&n^m;gU2vRGdz;1E9-h1Po=RUkyg@31pA_a?zu5|n;WIV(1Uj#&5&d8 zZj6kisi8hd*tPUwg7#QZQG7xSA%L$fc`XFRR@7dJ;io|ENA=u?5v#FN1N;sWE3us6GYAjHiz-pC!PKc+WCdK~h-~YKI zO`AzmH>G*foB>!@^}rNOI`QfT?=-PcOEcA%#(?a=jKP!WhM9WOh+}Wb5-S{b0_F_C z=7K56c3E``I+UOlR9~c-Zs|66Y>P$bSqMu~cY}D*WFxU@eD%WpERcBzH*>G0royvS z3_@=%itHxp!WroB-GGa37uIyqKMw-nq(ae#nb?YWTPRRu~SUa&ggbt$cLa z^O#*NZIl|Q=Cp}eTQSKs>)h~9_2s&_i0EoolWlfQOuzZFFY_Qzm=9QHjxOWsAV^%~ zizm|L7k}8m992A1wa^B}4l*!b_FMN+>WzQ%W^+|s%50|xBk9JTx|-U)`L`*oxtyC@ z*u{7vbz#N9E!#y+U;JqbGeBRLhq}l<`pC4(Kj5Oi*n@C1{|yPx^c)Zdg=q74yHDmw zSjwJAU<@th0AD*<^!Hgqe?haubPAm8LkVFR$ns{8Ui#K%g&>|=Sfq@u=Un|0Lt^3^Bl^u!9w z9D3%Zu5yPhOF;ti=wD+qhdSg5ACmcP4Uzu&UzPpKZgt96~DYa;#0Rt%(Idu^6qjAONmO#&~G)!6|g0dq!?ptZ4 z6I^g*l$48`e&LH)^pI2uxtNIxuFyhYFwPgbSRs|lB17Y+swfhd>^Bt%{SQ_^BAH_d zns}-N9I?QYQz|$b5{9BDN5rRe!4&g2Hlpb7=nr&AFhd3I6Z~$DEw2Yz44DbRhWPZb zA;KjDwo=J)ko$>4E9B9HmjD3*v$y&GkC>uAsJ%=a$Mx0@V#sKd6fjBsgh0r zilZ=*r;12?{{pZ+3Z56G=!m0AhjY!}mK&vi^G}d{Y^|(QR##Xd=FEGSQ(+xgHncB_ z9~_O9x^|r-U+B{4)GddYLWvFMMrU1=jD#-w6gyqo%&QWf4V9@>mRBee^pvgDrO{z> zh@pNOIuLHem}0MG9W*3oFzP~3GG6PruC@rN8~hc^MYm3(q`ZgEYQ~LmvJZx&=D;ir%#5 zg_PcnI!MRrw);xEhH)2Q5UDclf-h{7R;po}DZsRCbZ9P2p$P~uhB_^3jDaY@Ess^; zs!7{eD@G_u8=JNJolJeYBkYA1E&;+@IZki~N7#4Ljy%IqwHRShOzcno>nQc&MUP@{}B8?As>#YdZ;rFwY8S3Q8ccDc~x~007)?1krWoSo%SFPRk zqEFGQEbtut<7@u#RH)ioV*%7b+Q$Q^b?)}Ct1IL6Ri#IxWq?y93EhGQlzEYB?F znibT5a@SifX~XB@Gt~6Gg+m`L6_Z`7MF&w!p7P|i`zXaEi6;Uc)RA4!ju#v@b|!WbHlhE+((&&O(jkIa8t0Z3P;K?(;C|CpTPb0&?&$7D_3^{sb2s)u>M^9!tNgs0JGQyIa%O4$WCsqgv3sMLnRe=8TD$X_u7LNU{_Tm z$)pUtOa2CRnwA+3zUqVHcN9ofJDUo(q0C_Ql`N2$_nN6^3&~=&r#|En4&vA-u0=yM zgP>AyPvTxDA1oV!>`jYVp#Vx-o%XDy$h4cI#_xeB)>btiG>M&q5R?=X%pdlOo4Y)z zhGh~U!4j)E0c8uMT2iU4s(LtSYp8<{d62{aZHscFtq3q(YwD?5MTKZ7uZSiI2R24Z#G|>=C#(T^FB2*3AqLqJ%L%`lumx>RUhDY9|Cikoc%7^A`j(_2Qwd z_A!`jqn1z^*cH#B!y4zj_grZYCM=ieupSw-UPnnHks$A(MkFjoPO(^HqDkt_i!i}(HS*eV=gc-Y5GVS{s-yA>tO&;m0 z#bvc!Yj;wb-tio1`iJ^eY%=2n!~hiGh@$90`&AF688Rko|m=d!k$r#Z$Wr4mOR zwr}e-&VA{~*v_n z#>J$8EUbF%6p^f0?kGF?YN_|^r1`Ub9Do%BJy3GcR(jdxB_G%Od>+s1;2T5f{bzqc z!srfrgy;D>-sf`%Shwuuy2uGY3Onqn<;ItJjhz%;KJ%x2#(U6{?RD+@tNm1D@vU%_ zb>4P;h<=Up=X#x=@mikO1Eb`%Z*@E9AwKhh zUy__Z9J{@jOI(}UOt_hAnJuq8vyeIv--qR%uewqQ5$GbWrROA_&ls96y&aJ?mW%TF zn%mc25nFa#sVR3Qoy$2bb61_uJ_}v=4yY97EIzlay~}_SE^V|$t$c`8j0J{f=@$HH z93R`#*(witn7h8@3Gs@JD%5kX{LnXh_qVy_v(n7?;cuO-dTz7(G|H|jg*=0A;`(o} zZ$UT1Y`9Qyd)d<5ue~Y8p!95Iv7_DBn?gXrQ@)@5?sTzwlT~KXm?lCG762|y)7Gk; zrJ6xiu4WjKW)1{a>bjfeyV;TZ9%+dqECNa%70r@XA2zq=B~Hx!v&>pHUdOH9m?Uam z>x~LZ;@6QC`r>C~nUu8D3D2G;hQ>W?n0{uNBsxk3*|X3*U{-$jEAEw072B`nwB0t~ zg?_s|AxJ6YGDo_c@N{y=hP?EdX{VlLy7R$>;jvzIlP4YEN{wULE`6kal>KZS7ZEao1qOX0_W1j1cpAakp9j45Yw!X5{X=wq9?1#I;&W}I}KP&Dg zf{{mAmW{7+&|Mv(5`*h%rc^oWPBTBnLGjjXKx>6do`*c@Wp;()+NoB7;~^d{e#)J8 zR(Ex|d9OCfS8B%~Xb} z&9>M8=vi`0)rW>*U-e@Q+ryfiA6jR%hCOVnpPn)%baEMY085$~Dnc0R%wo1ULxZ%f zrj+H-!w!tJ>^@qn>v0^$Ion2QO;uTHsORIlzEo=4b&b`#=4Mn0jx}-7gG3wPu0xD* z0Ti9x`NIr9M{KZRr4VDX(=0lgH2jPZH)DdDYZ@25ID8z)*C&~@f0)PSdR^KYep{xY z|MD*xPdXiiXGKOMiiq54(4r~oC~bh}4N|%s0pS=@8R@bo-H3{!B5Q*QrWmRzT8JNc z;LAsw=(rW;Rga`hEHq0p(y%^Q-du0Eqp74f?kwETjYLg`?}--j9bYy-ASoswW@250 zCEGUk1>qZomDwQep>v!bkVwW9`X&MSTwzEW0x;fc`WS_Al4EXVmTYIQNZ9G_rlgQ$ z;D$nj_=X8OvA}b;FnsmZNoMf*NY0RR_PLN>K4%tbLO4OZ*atRo~UlXG^t z0F*myvIt5LV~qE6y=iaVkTThI%n|zGU_7eu;~wQaNvPNZfFvLdwc*?(b8+v!V}}C1GN&kuT(-; zy?!%kkyPP(tVsl9;vnRvsfM0f=>eBofHY%25#S!jix=U#0K%o24)M;!12NPBFh>K> z;Y62M0Y7jJuzT2%A}J6CqJJTru);BKhn@IeVPAhOscxF_h43q4{X~ngdw|=2yQZQ4 z;y1=v!zUZ4EXkyi`!vZULL8B}1}-f%BAMFg3gU((j*%#xU>(v(K9UZ!$J9`={UYjN zT9*ttM(#x-DQ4IT6dMTQu|ydq!!D}jMKMZbkGZE&suxK!oXY~kE0~2BxEHrf(HW)Fq$& zEZ^ko@Xcpakc{cFiM^g}vxiy77_Qm=l~?@pKWNNM>D{oM(C8ZvUEt(8()T0KPH^3_ z3_}nmQixU+*F!VS_E=87K&os5LTn~IPNTa1Y zsN?Y%l}0mlY|@ryolaWL3eDXOkI6t8xzR{6(zJD?IZE$=dN$^$qt<4KCbjq0V3nnT z_Aw>~4~R(+PmH5pkV#vc9Xmj9L+}7k41K<^;Ks^OT?ZN%o2>TKD_$)$k?MtIqVZAi zBmendEW*MhOuwJ&kh;+5qBp&W5GhfPQPZVm)Qv);4OwVY3QNijTU|Asf*%=^Q_0pC zGgrXtO68?e30Dl92nhxy7~-~tP)M>Nna$O# zW|R3Jm(feDP0>MH8Qs_11_``%)kSK(DCV#qla6}DI!CLi_K{kb)v_c<9<6GL-?HWg zXpYeVUJL>`2jl{{CZfw;a|xDt4o5XG5-VDSt+(ZGVI4*Y)7YR#xmpt$9jlcdps3KrHMf^{*J(kv+9UrYu{ozXBYKnb(L zD?VgL4yozxero{V|5a3JNRdk*4rof2V(`Al2HAb7-WB0gGy?YN;Yi>vO=_ zMA;ODGmc|Gp@h>6rx85J7K8bMZ~>tFTxym>j2SN81_^~Hddh`0Hoi%K#tLz`Bh5h1 zV_0vHb46-D2vAfh#f;2FU7V{j6K_h=9&lC$7X`Fq!C1{W;Vz3Dba5>31f&}T7PaMp zxfbB{0>N_QHD)n3+rl`fUMF2!U35|f(0cUBndhl4(6#YsrB{?OUPm5jh6PxDtnCES z8qOF)(W&4jI~j1hD}KhT4cz2P3PHg0muTXzF=eUsLKM$6m!$|(qHsAx&z01$J%nM< z87yKj#f=(B*Go}y>S$x6fRzi^O`?@p3%977^jt;c+-bsE>b$8I2Wvg0%9OGtlo~~5 zfTVus_dy~-O#&&hf~>|gVOBs4Q3Bzd7Tv+M1&;%9P8x*-qI`)C08>D$za8I0i5MqY zC@z^}cg-LqP-$mnX|uUv`qC#53RW6LVF3lsFPlP;%v*3^pg8X@z}|FIj8RTu88*e| zPCBQ7IO<8@3!U&eS%@tFGs&)y1r;opF{iQkZj;V%_W3O>n*Ou@LJB(Q5RO&B9pgqJ zwP<*OgUfRC;)Rg-rLf2>K($&+HpsLb6L}$of+Cb#CAl$NN**Of)PXcIc_EE!AqCt` zlY5RXS0YUasT{-4VM%cwz;z5v=}G$KVI!^s?b3`j}2qN5u& zp#}vzk~gZ^;B-teN+30y_D<=u+) z9=FHA23I|X(AabGSg;JCtYxRQ+B;_h=9G|*t73DI!Q!6XB;$0q9X?q+PbK2>`|Aw7 z&t$lW9Ln$^CXs|OL}NH}sIRao1z@@(gTN}EW)>?0k8#=k*eb7dSY;mNjwufw^Zy{P^YB1WtN9Sk2@nQJl zibgajqLFH|k2b@vfj$Gy{uZi`t_PArHX~4v6osF7IK9&@M4JLCIA};k6Vdm8JtmYe z0!~p)<4LP{(5&%RS3PZj_YdzYACUnm?)x3JglLkvgl!DIIW`eLr&DlC$RLRYnsB&g zhHxUycVkE@tCSH#GaD9Z_dzBurkSN31Xan1BaKDHgrJpxc##2iq#3Ih!OKJwBRH`X zkxGIjAizTu2Pefe6`He5#UGiXfaXrRiDI=G)~cX~@y5u&r$WHUDQ&G7W{7N^l{$;6 zg2*b^$g3JAn9k?d%?U8EWApKr1%vjID`)a7e1W z#;z0YRlgdGuS`Zn!YfRc+;or~bV+uw?$Em@nWN!__zx*&Nz^e?ptPi|nww(dbYt5tMfD*KI;6y;^{S;yqD-HAG&g+gRV!T6A^ zc1{%)tasXE2ZwS@RvVin`0z4Hy#B&3&^v5@u-pSWN^Q5dg6Yjv1v6Iuu=KoXTFnl-Ne zGPM8(DUBES6L(O**&Ct%szV3!ewaHownENRP2l@PJW3_)nYnvL~+i%nc^@`4Z)}~8!E2m6sOn`Q!*Eb{BjY8LH1jyORkg>!#O57 zEC|j8Lm$LrFt08OU>##Za8A{wPqRyu4FV}NQGv{W#KOWzArUeU6dsYrGetKYjK%&( zb=?tYHHX^p7633y4!Kg0F(8%^gfW!WEe)!vObHeprsSdr?pP6{(@hPriA(INibHJT z;Fma56T7&+pZEXr=h?+QxkOZsO<~zC9X=_x2`3gADLRRhWjr&qcmbBl` z<)YX{9@2{>3y+wm(3KrBvPyP#2^d)_IoQQ3Us=a9A~WAX1!D7CG+DXl8K@{8M4@0| zc7CvfujCTrPngtSxeq#gR3n=um5!0GEE3m9 zFKZY~l+g&(D+zBSp#X(yVSLf}-2RvuVHjG@NhGmxr;@ltq&tiBQiB_5`KC6W#R|1F zpLC~QOxVTR&_ODh5^vP%f3SJrqS5YkWlWI%HzYC;Q% zq_RX7wUF<=ibqmZ=Z;0*=%IF2*~uE+@Ysn<;hY95(TRVb;1{@y#DYA@_Py*GViA*P zW_Y4QRnWl2PKz26b=X}VGN>l4TKOU_hR662gI;VBZ>eNTC@Li`wbVDgr-f9KN}`oi zxUJW}pep~*-(r?MW-f2Cn$NJ~n!EooKctHnY0b`S+WHt{30YWLZ|-O2C5D%JFWa(^ zLXY^0pZur~JgVc?uKt)OJQR45QAgG}7sEX>bF?nHxY?ib?8~x~O`If%G%jzEiL)Szk^nC)c`wrL8;8$ zMRVwwv(LVyW*X=hde2zpY>4A$dFJ6xy@lpCGx5+H7js(knVWBucIlJea&u9y{xXYR z!%V9)L0jBP#xF7F{Pq1~)N1s%bb=xpZwjL|M4&n^siR{OQw}M@i;>Lonr{!ghV#b8 z5N!7&?P)b@STSG?S3ZSc%xq@Sr@5T>2|Khj!(7Ou2xJt%!}yTohyTQwvOCQQbs6S4 z)28ApjE61-Y;7~8f?BU5);R1an<`W)A`$_;l9Eg&kz5cj()WVH3kVVxw5h-1 zghVDuxT(DGK>lBUH(1hV*lxy=opk*;mu{Em687Ey@WVbi)^t04{?T``;S|9;-+0(= zdzUYLN0WkBZn@1DE8^If6FAG~vaEMe#Km^~ z8lhYJ7SC#%4?j>NY>QaQK3_14*atnyrtjsm9^0MmVma2WZp(tjrS;Wt3C0=cJHaj1+|6sz5e(k9j=(xMp;VB?W8-!q~3Yx-u|E`eILj_DWCZeH}xc4ga#VpwmfBYMnBnAjK%o%%0KhIEypoK zHtYuN3tN-v?r1aY>v}D>?WU~^TRyfmZasb0`p%c;e-M|2uJURgGqd zpiVvNJ>Hqp3|@FSbpxQkoN2V1xuaQLN{jW(H_oMQ$5>I1Y)1x_XSl1(cIDNc?hzj8 z=V0XF{gT_X_FuJv*_Ijc*!IcvyR?!4Yki6TkQ7c&ffcq3pv1AQ1mHe6TjYe|L31A z&n1#-p2u3>$5_twmR}>TiSFWQ4bSJYRNU_6r+&*sH@edijQ1>y=-%wc2QB95U+x`V z`>`JNxm^2LF4E~y-u6wqtnsJ6rd!8;E{%6~swBf+W)TaS>$(*A6wCURziijuwi_+R zlPNEhqlc@;KI6x^=+s@m*e+5r$B#w2r-ZkH^>BXjO!kW_fH^!lq4r=gq*c5o|RaG2|co{#L`V24JZS@c@(T6o;?9 z6__MKN$Z(T+(9Va>w=po@?`I@*w$CK?_%l%f%_Wk;G1KFSWsFfJxI91k*;#OW@_b4 zn5nnzv%TYyH|7{JHX_zq-4<5AyHyX$%xZE;RZ))U zoa``Eoc$PN2xLsal;6xIh0-c?KkHuCGwiMa7Bj=Jhh6c${sQJ0?sf$0$T<~Y9t1-< zq+IDd)FzE4QUGXAf-Gd^jc+`YX|SB)ID}>Q7rbSVN~$Pi{^y78-~JEVmVV@mWpSpT zz4bR;j3v{&7Qfu{N56H{F8t)rp0_4aFmxA7d-+%3kwPpNMW)++tRmU4Z!^!Z&g!IR z=NHRT_G|5Qmp6Vlcl$E;>U!AP(%EHRWXLjJ`OQ{3a6cgqb?^87R-U&tuOYm|ExhYP zxnlNfnd8+S+qEx?PgWlMQQymQz%80fSFZvMIDIF#ANY^$9ZZt;4=BP@2oo^w~HfuFL7EKv{bV&Uy- zCn!h@+gPo!E^?s-I+)&@q>MqQde}odV(B7Qy6Rm{wtmZRuw6PZyp)vj%IE4Dj=s(+ zTwM}XP&pt*dh(5L0u%#)4!RV?!(dK0a0CB&IsUhQ?bz1)UblX1+sN&XcxeXXoQ!-VM-#~;{{Ks@VT8wQ*K~_3nr&nB z#kV+Jj?-p$6}c&2bnQznWBAc}9x2DpsgX9i($+k7vnX@qGp}i`V)t?CtzB*L$K2to zKXA!YTMI`yYD@u3prw|(?r@_!?c%!9itk^n#=&s|u(O3bU?-@iAQSj|xGkaySQ(DT z{(=-?<%88Z7R!yCdJ*hHfAX!~;7Nrwuw3Q7m_?{;^YL@M(I$lz&J5YnG72|;t23`I zxTFuguRqHt@8O7hIB~P~y@()E1*Z6~XuKq-6(QB(h-W4I5cVJckH4s#ddr(I(u$|} zh$v2FBxJ=|S#hde7BS>QoZ<<`oO%l{t*G))tMKBKo0#g9FZI#P2o+_PPOgCIh+XXp z?fE1Pc%9;qmw3sC%p%CUT2vONnDVBk`pA#kadR#2>Jl&YFkBH8lo3&y@89l_+aE!8 z@1wPJr%?(i9qzlU-QDHi4uTgbn-T^aZnGfSe(Z;Q$b%ka*x_C_+m$|Pa|ud2J3N$$ zo1OOVE_;A-=-v=akd9>DE`f5*ixx2gQgS;_j zamHx?CH%CjO|Ae`hpC6N-qD1RoY(&54)UNEIQcHw__eC-WO{&RCLR7^AMWO_V(|g} zZ-2_HvMi{;nriZ+igN0qGkd6@-fYuW7V|Md-sQ=x`bdb(FMY`=Uh*b)v9f70|L{e; z_`x>z1$06Z!cTEGyDaRj9<0fm8BiqXuFY~)R!@lxzmeAQgB+M8`4!h~C3j;~)K+%%UD=&~WYJ$T1>eOfcNOG@UyWF_7mFiCuseUsB6oHd z7k|+=`>>lBI*FZr_zV6GzH4DA-{qpZw){mt{FJM>$)dD&24!xTYQ;AginNUUA}6UP zop2u94488-9s|LrKmBe$`sSbJ=1T*~E}}RTl3nc0e}Md9LX&WW z9h@Czs11idc|<#yYd;(WnKS5s)-z*_@bGaj^VUzyDJh{0T43br9C`q&S%GPAyWc>2 z42Rp862{!j(>?v&Jl!L_{3m?uhoT{ww-5!hwDxQNcNIGrQ zd@pW041+k9Gs~U-_@{#zvV@9i5>iiqztD?p@}cn!FE)f>gU1QFn-Z+&0qGXarKV{1 z3K_H^lE8=Vc~C`vABMld(1FHS7>r#E>MHqRLg6Md1)0>4wsFK%JQAuD^+RUCAR zK$~d%pr}P%{3TZ2#Y=4DK~|dhLY{mTE5j<8c%_pEzL5{#nIV;~;-ppVbQ8z7t?YOs z2Obx4`))U|z#Cr0jqe24mT&Q(LsYiX7Yp2yNT;~djz%%F9Jae80>gv_iBt5dD&As-0WV5TO$ywn~4Bmt`k=x0f#w1iby$)<+RN8I`(ZY4SGtXP{j9fcix+ouOem{IEbD7o_s{y7*KCcIZ_P!#)ftmp zQfgMb=m+rn{_*Gs&`1F3F()MI_kqncRtZnv?|b-j-t}(F>Ls7zp3bp%nq-ol#Z&L@tY;%rOu0%}Y8Q#&5c+%li@2<868wpa-vtd` z(A~rUww@)?e*tPyk=gAY@~7Sy7s&||aaIe~Ccdh)`n}pu|JXNH+I}MzOEu5%mE-b< ziohklc}630pGbC|+(>sKSgurwZ~qL>@IXp2_q+(nxqg`^6eP-Pqu=I((k$d)Vv(C> zhSq0#xu<{jcYDx4f*z`gN(;N_O$LtOgoYf*1XEOJ-QB}2ExFsQN^s6^X7?CXP)EaJ zR}XzrAOX2=2CAO2D(biUfF;JOXz7_B`JJC29wPz-v^HDCqL>jLVni)$lbeoPTdQ<& z$Z4|)q06k$?_B6kyX<@+Xg&oCwGRHBa{E4jf4{i(ll}T|QGCSByD^XVa#MSBeUoQ) z9%=sveJ`(hxRech@sD>t=EGj|SdVa;RlMBpea-7+_f>giVe=G< z3D^TP6@*lQ7x5}A*R+5KB{h%IZ}~{q{ko+Q2mAymhk#a0I$hG!Kag%%=pY+>Uhnof z_G1%NibL-fBat)jc|+lkEmRueh!u9pYg+VX!#g~H{@!6LA{$Mw2t~_YU+r~?9gGdO zs?8q$3Po);sqbb$1toQsT1VMcC7Y;ZW0kC=oOlrb)nE0Vz4{i7I1w1yK4$Q@IsD!? zd%&8dSGldP^St|zymaJ2mvtFcb3iyTnIxSO zD_7Gtw=@s=lFEdp-D?UuIn+j-9PP$dG>Z%PP$&_m8Nop#0|EDHyyQ(6KJQ(pCsKr?f~ z!F*z!N=_LT6wC~ncqyn8j5{GIm@GkQlT{6gPIRj);%?=830F*}D+yAezxln8!`uts z*UU1(e||k=|N6gP?$Io__I+T+w!Cc915Ea0OPPn??Gbmi+UE9f?4NOXpNvb+BjOg1 zzKJI`!)jmeWk>sUT(#*^L9mQ+Iig z4|~ha9nsbttc|+K+xl2uc=L$U?K#%k_i>@=Qb@!MmjqV7WaPj5`vW!XMt8SWK^Q4= zB(a$~WM*K}?8;>YP>T8KLmo(_PJU?8`z<1YlS`>M?PZ zA3ybDlu$?7g62{*)0JkHZ)1nJ%rY!}D_u7zB({AUaut#)zCur6HXB~?v2oZ+R}N?2 z?hm&u>tF!Xpe`#HbL2gbPyJx?-gt$Wp8JQrL2i8Hz3kg@uAzr6rsyb?Dxx;IGyn8& zG#cYGd}v^nEPVa4;?;Cc;DTE!D-27r6VEs+3m$_1^$(cohTRzc?jPdVg9Z9hCR_Hl zZ}v7HQn^87o_+FLe&~z6=wKi9Ci~YfXsMx-zxXFjGUwZUf+0qI+8h5Zd5AYN%ptDC z0F$tE$Pat+K_C7j-(=^58UpIpN>(w+TfE1|ywj(=NdZ`koD2shD-HROn;}skVx1)R z(Dr)(RMIlc)eXaN(h?~x!DRh!|4uiK_p$>m+=^;x52w4OZ5;M0$Jon5K2lb3JmO`F z4`s_|&$YCaQmwL-dpuK#K!i01(GhQQK2U}C7n+*moZHyij*r4cAEs>i z&$9U=ADmL+qmKHnAL@{YJM;k&(qMz=hq)u7+4g$OTfKqx){w`?**Nwb@9B;TDvazp z0-WL7yv;|u)koj@=}&XbHfMqiCB}+~tKIC?;h8O?#UtDz88FYOhzoyUGkJW(1vUgOL?Uy z8e78CW$n|GMT~XXvbyMMUjCsk`8ei0kFk{Uh%aNUU(e;vBVU%-jSkXxw|4yquMyL) zL-Ru{{PK@=-){Ni-Ynyv)YDrYlBSDH7Tb{JB!Bw*H9A$4)D-7t4q%kU84pm7uR8Lil4BFj zj@SO`&6MM_J@@DQm^GX=)uz|f1t)lBX;@2lyIsqfOKYi797Q=7?g)o#x^H4vlWt@s z8&7+@aPMIkvzDu#FoDMAOF8Ohs%~Y;!%x5KsYluBzI@hKD>dQ!NFMA}COFEm?$7h( zXIf%%xvqJ))wYGg-Ncybp}X#vRW2&U8j(r%;DM9CONbtkfdD*|#MUm5M3eaKctZv9 z2QipkfWh}DrkD}b;rrN^bLJ|=B_7LGHujSD^*(#Hz7{>=viD8i^_Fe5db8+S$n~|q zmO0AyTJPQV0E1b`F)ZiU?4s9MXPVnG*4KK3&8!G7ug_)3L*Wp93%Tj+tL_dzlf6I< zwyT}ppl)eq*#H%m=OVJLtdc3JLC4D0;?WC&8=ri%E8HlJ%qyZI!7LeL$t3&JpW2#`pY=&9v+-(oV@-&}d+$lKBuFyJS1388c=LYG;raC zLwb5ETThSp40r#eib}q-g)Cx0VLXj8B2DzGeg%)p2MEo7(rs@v1GmsG^IyI&+A}vZlinJKM1y-+Zu%YF@4Dsrqss zEkcBNtgGC^zTehuyD#@Pw{slZORf+lt`~g7^3LwtnYP}W53$JewH*AW?s5^&_1qpo z#b8r%*hg)*v6)@mye(nemRBxIw;hNc;U<9-V}xf@5bE}MU8meQ-RM}& z<+P$&8=;|43AcFTQ3}GenQpfi*`4;Zue@te@h-91O`Vx-ds4AnsLnRh`DQymZ6#e5MEb}= zO1~NQAOGLKoMmLZnBfSMx{Jn*V4Sn$4!@xRBpUOgSzPp}oKw%(PK?+T6_PnFd1c0r zQq|=TH&sIcE7hFc&;xsYTpshJ1y6?)ajgZzc}*$;jhqL zdNvq5R-?HJNQup(EbQPp?)fMwpCz|YO6gyp%*s3Nl5?wtuIi?4bZ2M~PvPXHue?;x z3!mwXdp^@zZJAi{p@?g)G=AXU@ka~Y(v8nN5{UC9k9@|XmcXK5O(fWDklZ$>yormh z-B*{+w5yBnNm>M|1O}6i;?^}1$NaAo)0g2T8Fe~Z!i?^;ptzJliz3=eg%)Uv-bsZ4 zJ~1LWxB6_mU8xn+DorsZC599xf+9>2Z>&%~CZt0}8VA^{Yck#@2Qr2?9op}!Z-g?2MjrM9jbL$8q~7`k`$VFRWQt^7b*Ux$V982h7incFhzZR=9aC30$--J|d8xGqap)VVGVn+iahi)P@llV8s`s>3$AfVq(rLcmV$fZCjDv|BsKjo#W-&n}ua$;W=R zU88CT6c7-@fD3ltx@ugnZi}F5U*#q^6U|hd6djsvw%B2#4`w%;;))@fia4~1wlIbB zBvVvV?l-%@E^e}eCkI}e(YL-@iorX$HH0_`CWLb=nlb^FJ zWvSRYwq%=+R~`7Wd+umH)!tA)LP7BKhjc1}z#yWI@`wju2(`2}MhoF)ueQ~~R*_}l zrEayUUBX;b0C>QJJ=kO&b;6x&+FM!0CQ;}PY7<9IbxBsBeHiNO(Hv!01i!w(4TA_Q zaKJSrale+Duj;ItRylNHLKiZq(o9U%wV$yz))|%5PBx91wrGWZ33IWe!G%IHM@-V- zJ&;T)g>F7KN^=VmJyVr59)`uM{UVy`jJ_eO+!vE8uLP0;EX?Gf$Gqka93V`SzQ%93 zs$cUkQVEIa;*H&K17W*^m)CX7l4KM8)~`$o#Skp4Zc1fjT-?ejmqT@vX8MMrJW~1~ zh@(Dch=Mdbkgu;o<2W8jrnpm#q=;Vzi+}fD;g?l5cO_q9@8AWBqb%uExbe8vC7$MD zi1%&4$QCS1nPz3%Y?)40^4<&2r1esEvHKe`x;(JnW`W&&=b4PMwxuoUsIe{T;2ZFv zz1OHLi{*$Ax*XaG?_he}^3g{vc<(hxQBHA?p$-AiVd9?eG?OO%fA@Qx9R)F_CD5NS z$*hx#EJQUszF3MGx0tS0v*3%9c>A`ls1PVShy;GZCef%**>vVMvw`~6ZKJZe1XNeM z%8eXkx7mhJV+W!#!%oZ+McoauL+XfSSbw^%m~CKb!7vClX;8D^Q9^4L0s!Aa5tM_X zBd`?M&8!5+H^CLmQPfh^&vrxAzR_nRIZOBbAG)1RI1UT#|*1v1Oo*dvy5&wmURTA%Weo-JwGdTK)*d%nY0`0#fP(PJ>leV0p|>S|~D zv{~=Idg5qnjKjGx)ct*dP1>I=-*cLL)U3zKZ} z0*?xg99=2Msg~&FDvx^PfZ2A`dKU@Ec@_A=xZ;m}h8iNyv{_40b!C6@GFBU$#k-ji zVN!*@y|!Y8sr_6)ISYFmES!)#iD7aQ zLC5cM_u67FCCX)D`PAEgrw={q@}KY>KmEcmgnh!dXy!3b{O+Tl6D%Lf>aX__Bmm=z zRuH?y3-4Omp(rWXHX2$g_u9cd!2t)Tg+;bd!%Q>Enj%@+yi@gCuwgqgRAm+W5rBbU ztHN+l*F(6&14=T(;n(~g-?*4g0H7s-lP9=l5>~cn-XO%XwuIso3d~bi?Jqv1l?-(g z!o1+v10iB34M-2|LfQ*K;aHUu98XBl;+0lBj8TN!l@N}nuBUFQa%K2Ry+=(@6x4=7e=)74e#8y^@zLDhk~8TXvK=axHK@ZuqJ80sJ#yi`vt%{G6Ph~36}w;Y2uS1#Ip4`H7m~lmPrt&)CBzo5%+k-FFC(tElKN zdR)=neRMRp_UwCP37ngmk3^n4=vZhZMEBOlkCugy`H>ibF>F$bTp<-n?B9t`e!fmX3LLsLaC3a z@_93&k31#VbdcR$>J&w=Dlc|#i2}mji|>sBb4i!7swompe9+>NC!*;RAbnOr02&FU z^&@XZk`yX82^tp>#Vx<&m-u?azQFGtYD4s+4!o^I!=~~jV#bk<>~5q@0M|M&MQ*r2k_s@bN%&&u{oCjCWUveA~oB=eDx9-DPg zT}2C~-C?WzQV)IPp}*u-!!~;w;94(*iLhooW)frou1!z#r58|E`chFZ`(?Lk@>HM_ z4+{36qy{1H^f5PdUQ8MKlt7DqlP`~sfxbsn+&GY zE>qPhzs$1Jcslj^k2&z=9B*(hg%;8p*ZAWuj&5R-(`n~qv^%!+QWCJii5%hA;4i-3j3|*6kN^gbY#gMk49Xb%Ozz^-MaQBDkRKUN z2>0PHhZigaD9q-kiFe=s^tqCj$))%MjBOp z_)9+e(I4Cv5>M9`PTuyLK60BmT5TV!zZ=Tke3Ur@K5sB$<2dW{5j{+HRpO z3?g>~kx;QQkmFTwDTEhM7vsh2WJOoX3XK^75A&mN3?)h?DPQ4%gmhQ2X}Vid&T21% z*V-xRF|%c5uu3uuytmL)v6nx_IX;j;dF@OrAv21hMYVt_@RAA$VldHk*DXdm`NUBM z+jA~Z!2!V0d0&^x?03k89kmmU|2c`f3|TyT)xl>S@AN0d^;*&E*vXG-9{X_^te(CBpt zAQ&aiJHi#!q_)6!f%4EDX>S8;GpZ^SJO*{+Z)}{j9j>uE_ zxy_R=Us5Tu!Z@bIm-x1ay#RWt+*v~@WQl>nDpZsaFQC-qm9}YA{SgnwV218SI>3Oe z!N6JG8Q4lCi6|qCX9kL&f{9dm3*!-tH4!1LpD;H6-+vCj#5npwqNI&+wJU$rqxQJl zQJ&GqQSMgzQJ?c`)bNM~Ns% zjiY^WP(W{k+|uPm-z~M(pLSLFJ-^DG=BQ)mPn>4yt)4O4SNf{gSQQrgVSfRFH_w5; zP3x1s{dYh8ce_T%TacqEUS#cKPth{{gP$*c)dQj+cV zp{i}K7e?oi7Cw_aTKCG(6x9?xxu-e|iCX*g{|Ro2cFq~tI-t`vHrcZsFg>ymvhQ}z zmCm`-sif54TD#yl0CbC*80=q`tx!fmd>g?5nd1~J%{cl5Td=%dE8kK|t-v3Oa;LYh zL2P%HgEB?jBzRS{voE29O*u9VO#g@t+( z(zuzldbdk;OatJdl1Y0SZqvdHykCC<7YcAGR0TOkTSDmb%qI+ebJugOep-ilW=9^s zE_s5RaqM|GI7WIH1?Kx)su05TV=l~#Lfz>-vt?Kqr5?vz9IYFB4%j%`IgeCIjWt(~ zU*$a^=V1fAX2*HXi6SZz?4Vk=W1b+;zfz>%|K4@syUUM^UUtTdE~%{Q-MAm=(+BLB z%29#O8)0bUsPm_tU)Os2uStX|3azxvEPhQ?2FcLsyxU)5$fJ&1pY|bi$ZO11UX|$_5Q_V_@Vp zY}KgLpq^{9Y+b4QkNH#+^`HD)o$SxNkf9l$_UUR!*r=zNwxXbex?&YeE?Uek4>>JX zr*OK{V=giI(7wjUhu}ci2EHL49^S3pKlA&&M%$fTO&xrUcJVRIFzwU7X)%&$pZ?t+ zpMf0o0k~p>$I(-PVjag+1))7aQqwGE4sZnmUiA9dzUT8o8z-*Yf7y;YUl^)Vrg7V~ z_L@y&?$lL~$t$ahu1;S163|Cg!RoEpv;~TKx!iZNNx!0NUvzrv0cu><5y=_P-{Dhz z8#T8nSW)ZE@;7~&E0v|RFd1Q93X;PDm%_XZ$J=e&pYo;O&*>9C#{c63TML9RW%#~pXr(KUY>_x!_rSiw~aVc)DYJl`lQT>fiO_5s~n~{su^|4=z5v)zl_d6-{6u*(G1v`3oWc z$xon>ZE>tatOK%*BP_r)NB#=m$5;)Sx#gBB=pHz!jxk?8tU{CXPG3uGw8aSjC5Ggq z&3o=6hj0G)?{UxLh^rh!Xa)C9lv@CKzriN-U+TixZ~k^)Qj2l`-Xa8{&9uWx^Iqfd zODZSOXHWVRvwz%26m=eI7bQgeM{omX_JIX)iFI>t4UX@8wCyds&M9=2P1pI=g(VSU zu%NouTxX2k^pJM|6$pIHo~Y))rP&Nka}p`Jg90y`S_VWF;_w)PYsg2bOmUT661d4R zc3*uqK|=@~TnaqxNU26{wnAnXD)v&LpoUDDY9Y zCKtgbYCaV;;@g1RE3I}PamFwG9JUBfHqmEI#hZm zgxdWVc+XyD2{Bake#dy`agP1M{TiWzWDf+0O|KJxxAW_)Da=%OCA-{dPp)?0gr$3! zRLWIm3C*jdDp39k16DS`WbGyvg}JFYA$ z7_`Fgv>{{U14-H$*YR23@Uri`3IHQ%)g8H5SeCJ6_5>eef}AEx(7~WdT^0?s;!n2@ zwFTq{;oyB<2Oc0az9Q_n>2#+YvbV2a3i;pt9ztCZu8K$?j){|}F>FXhbzswb@>SE< zx-~pl-}7-mHx-f1oj%4yFy)N<$aVUQ?|5x{@uyw;x#N`^%D~6CFF&+wbo-Awf>d0@ zws$JO1zGO%+~8=4Z}ssu5{yO`6IC@8 zc!!IH$j;B@I%i*4ew$y6HD-a=vvQrhf|<0R^%>sS@QIJbxE-c=e|o-!{D*%|Nqv!3 zcR^YUIFQL0!U>XEMcEE|1dVk$SboL|`P3Trp~E z9pnzu%8r__h?w>ACOQC`nm*Jg%dXT%U%Omfa1&_EM z`XKeCTdcWJREKHSbdiy^$uC!4Bx#1NsOHZa+F$%tI&xo6+~>scFdXf?*PYvE0PHEd zo%Xuo*}m>4oqeT`yRY-UbBgZn3k(t)&VDXm(!IyW?=~kddwHH_v(RLe^Vy%W+Ab)=X>HOG_q^unu7-N& zvpnVQq1$8M%bvW(Id3=-{l|UcSNzn3zpT9^VPr6!nNx@`OdWZjpW~A*f5zpO|G4|U zucO*dKaIo5|K)Gi=6$W~K>LF4IjN#jPO+8{I~852XmZ|b0zlQOl6@4-Eial$U`C^G ziYoe+t<;jNH&!&L9_#{yo4t>`;)JERmORs}B}d|_J3(}iV~CBSkX28+0=&wLrfR&? zU`IYgP1R)S5c2oV`)y{<Prh zA++$`1PGl1q*LfQ`W`D~S4^=9EHjkBxW(p*t$p_%$WfcP9{(xUie7{7I3ds8d>Ew$ zV!G{wG6@vwX}k5>x#4%ud1!Vny{tK{8V#9a9aRQ~7z6Scm*L7>W5s%g4_qgKaWNKI zC#1~#9!o@pYm#xLyBLbA2U0Ns)(gpF=ivqq|C(OhuzCxGJb_iOkVVNY7pjpB3KnoC zYyq{}6-c9#%47G{-e=@bTlGP^ir}musSH>r9u+NbJwyJnbtma%>0!P7U#}>eAPPRu zkdm?rg%y`gLIak`DeHEaAQ1ayp?zD*kx!vE1iwjYgX&Z1d77!4ohUT5Qccp@W!R7_ z2kn&69A(jUz*wbv*m`K6qil@5ML>PrdhKhn0pQxAlWH}Fq`drdTn{`y)&yg1Fd}iz zE?Hk3i6r@xj3s_{^xfOJV;ESfn zwopwrvNcT2bq>S^nbMyEfZ2bnv-+|DBPv!8*GoMt2|3C~-_+2k zGHCFfXy)ll9+Y)9Q)hGN#o9@AnNxqsTOabnY@+cnLaM2|&ZI4~V1ncuqx81uDIaqv zwD}JCWl>8vI1pGxU7yYX>SCO6tjsc({?hk9!rCUfI9z1Z)g^t9h}O)FPT_&x6+$#s z{KuWb%E6}vjGEy#5<;_mxV)0?ZQrpo4^=Io*|&cbHhMuMZVsdLq{%1{7U|1!vLe)-`K&4N)-^cb-1fO0czW7a{B z`!@RMR>B}pR zS&s+Aeurdi_m}Pd_R2F3f6d|^_&Z9eM|??ZBu!xrfE!95M$nh6`XVi%xGoYslrM8q z!PF`p0z{=l+-AyKUtr&>MVCYnMi~4WBZOr6r3YMGRMny1#+@-D5R@r59cYE)|EDs& zhjRr&bvBnkBl!`Sf{c0_mPtCLB(iwhU<$&i#}xI2nKi(j^%OUusr~3%6z!A{+f*Vc zz|+uO-14SQR(i_P(Fdmb@E-~JKmTp@YyP$V08Bu$znvTQH@;AyUz*==reFH#Jb&9j zzxCez5oh-+|4Uo+@64}l_mh8Ve(ON#0R4X3sefxf4$jM8`^z67-tg@=p(?NW7yjcT zM_S|^q~(9a-{x2RYr6SYZrf&lN53$?^S|WZm|wYHx?l4{-|E-=E5hLWZ~QkU-gNrY z{*m4MOZ7+g<_}5M`gimzKWrlJ8Xx|7Z=lN-`W>BKvX+0Oe`gQ>OZTDQ=-<+B`8W3R zfydv)vtw?xvS*Vpu(DKvE$Cm{?lj5tB)4@hf@Cs3&f7z2&8UA-|L~K<7m5 z720Ktwgq@J0@l7S4fdRUC*Pd*vZ8Tmv?i^v*2xMA8xsE_nnQu%_N9;<7P`lHa09Koqvl8tIa$t$}nZnxesA?8++w3`u>dUudYTqVn>@pQV7=ZE!Ama8aE0jjb??0 z8?P{}5>8G)Z|K8i_!s*bJKrvR(s=Tq;#|(cw>?+vj80(^MYs`@& zbMjn(K4)KH3hP_&#Vq!ddD34IO^DasIC#1I*RtlsP7>KkeSZH^6jSpd`^ zI82=2@8N8wD791&Hnr5ePaes*g<4anTm|aBN(&(L-aX=6-ggIu~EQmiMFa5 zE0Q9WtZ~A2zeJRdFgM1ew1;CsMTzkqr;9~xHkw>!Z%zkELviY5NXJrUs$uX-qX3CN zrBkN2FmP=Uro||@EN+6uL{ma`Z+QCkcYfYCy3iutJ&r@k&fA_k5|ysU>JsX3E~cEC z8v*Je2#AJHu7)0R2r)7j??MDNCGiN^kJMuIEg}AqgAWYl<`Q#+j#@2>hqmv!eTo!~gnih9;QIG_<7AI7Ga3(}jlSS7&F@nIcVEJBg&1R~aE= zk?fQRgnA+w?J!Yhqcz^TAOi)sJn*X0iL}AbbZkIyX-$9I%;joZo^-^TvGI*9#L`0d z@)qaFsizT|xcDm6vSVjM@*NDD+BvqXo`CM;;(81nMLb`Rc8pYdhpoycS6k@NlD5<+ z=2YGka`3(!{gkKLfwILsLk=l9#43w@W_{ucq7%Uvo$}T=Ok3AoE_XY# zj+8=jbkf~gu6Ve6>X8pR+xXz3ZEXNG(J*Bvw6igHi3@|J9Bgq|Xr)sX9c##s-igfs zh>^zYz)Po+YNlgEP9)Roi0hsHNjVN&vK(5i-rBPJdi7E|rF_;EVu1x9>D>ye zOD^)&_NO0(nr8~m+h8)>I9#Yc<;d{YA|t`UCtEDCm$8--p%o--n~ZNC7cx6SuYBNgLnWz8Tpe^N>;ZzT=bdY3Ir&4$sJ74B{%383vNnv%aibc zy5Jk)>4Abi?7^40tIH^Wg`P?(wI)76ik2XyPfFy=uG_H#&HTm$(N7nip8vz)1(j#a z>NYpDll9D_9;q+PhPP4Pk>g7p!UT!HEg$MMYp<%JkYF3e+xPQY*V@N*xK)qcZ(}5j zV%%E$kdV)j6USazNE!PzZc}86rXax`l~PTdGL&5N91GfR#Bg%)$t9aPOn+eF1#u`H zjhBZ!;osFj!$FYEg&_Ml#~NolrpYovYp~Aq!kCpP0uEw%`W+B#f|}2@Ff;qK*lC% zpS312jyZ*55w|Y@C1QFublZ!Mp{u3c3dcOhrfQ)zZw(|mu_T(yMvxp6BF=W&%sKQ_ zVX?xr0wSGp) zM5+F&)rW4DDKOVQ^5j~V*dmt_Qz;NZW&{Y&gc143-52MZ8nSkt>sm>j;t3)2NWR3g zl;R8Ws{++id`Rfyha(-)A@|))iMdqxN^fyvmzZ4*=)5E-S89fK$w(5!(2z)DeLNY#ikpl^B{c~X$m1^fC6jE z9a;pP5S57-cSg4avLU2)sFAZlhv+G2Z(ItR<4^C0|8H@#%IfDT83i(Uv8a&MlP*%8rT*3@U7>8Q^ZwWHg$7-1Yw^ z&}nbn`6iiVoJsa}kKPnetEOQpzQgtQ_kaGdC1|HeMa9%XH7eWnMpZ>u!wPG&kQ5I; z1^BU^aZrJCMd!#d&3qtOm0CJ*Dhd~Ok2n2}2T%s_$|n~WdGeCD%`b75kb*~LnHEJK zoyB82!**x5@czv$0z%;O4tqT7a)$6;G!EulaFAA+Nb8kSd+2`o_c)d#Xp;aP&2t_= zdg8B9O}02Gw=5UZ zq{hhuE4ymyHF$xolEpvx`NO20W=_5@k%9K`LMqetTCS%OjyU-px>gFxxn^8&BXV5D zk2){x$O*;Ly(U*%;!YKFovRJ7)(^bR=UM|BTksp2Y$Es6Y1kd6pR>$_$Ood0!G*Ny z=(z1|vXB+#cJ?Dw*B3RyI;*uNY+D)WCGE_L6R(Ttk_T1OBd(0GU9X+&*8uxH_p{G} zZ2WeEyPso>(w=coxeQwCX@&u^vN`EUIQ0NkZaig^RW_3V*pJ|*tH{|*`Qxtrw;o0~ zdz*uAbC|;fQ_K%F+Mr`{2eDrKDOK}UHMEq zY@F-ydXF&{CRTy7!%GucoP-Qe0MZ~8AX$-%!HArzoN))IkUhM3fU~%bxFc-q9e?0- zk+XtB1~OmN49>VA$E~tr`X3idBGQ3EmFRNHChEednCVafiXd=GLG8K=%&`PPCcHUp z5U;dII=Y`0b}Rq-s~70bRh3=Gbe5fu4x8&Cwt|f$f`Wqm`0M8hDvwlGcfF`Xpzhzy z4XBtL4R-q6;8m(Bs@Zua_f92)hE1+P-0WulCx7MnTs|`dk4J_FS;0>dr`_IY3nGjI z6;?}?CP$EK?T#haaFoDZJFUEOf;#-JfJL|xW^`t|t;m>pwG;7a-%+bFreEcD!}Yo5 zHn3VwT1a~-WbTlglM)luuWHw7E0oh5rt21=hhR^tx$A$%D^^OSEki{(7tuvQ~mbt)R(-(57 z?rDC+)!VVqIL25+*3~OE3$%>XJop@PLT@ShNUQXtX9b~iwtnTFTb#q1D6$&~)lyKg ztqHFq&l+S!8DqZU@BO?I;9tarSA=|b#l|iAaQP;TaB*`CIafF02_a~_VrY8UNzY@`Vyd8w5}W+k^$F*_AYiv9}QqY1Tu5z8C1f?aeOVhCxa%Vm_7 zA_|5@8m&PxOfzrXgl}w9Ntutd;faTcw7-?x9Hf*gpW%2lrx@85#x&K4hsRX{?n?}} zBEwfAs>M(u3bn&w4thEk+WgoEbkWv(Bm7P+SwMAD(ZHTv`)EfmeZk6$B)I72!T}l| zJz|og=o7<(YRJ}7FRFr%Qsbq6mC&>433Py-iz?4k(r}v`%pI> z+G44zrw%awAuyS~USU<2s9S6~>uz11qJxftl{cSl>7sFOqc41@OCTIkCRTz4)sl(9 zno!ov@vd{`%71mipeaX!C+y$@GW#PmGyHyy1X-H2TqI1O7dK1#A+GOsDp-=;wiC;u zMVwzp@Vqb`zc5&;En6(|^T9_o4k!Z@YlqS;?ta3_98Gsc@(&Q|%W9^H3iAjvBf5}c zsCPl&*k>GxEphB|Y9nn}cKPOVjNU7yv?ECA9VluhC|2#Nx3Y$Zoz6{KOvZrD0*FJz zsD&o$Ys5%(gsQo`Ux@2F{o}tIxVp9oICS2hcLlJp9~bPGa^L|qmns4{sL)HF;Jm+m zp(Q4O8LEl`J|*%pI)L<`PnAH1mkSG`0=%q=hOS84^%TYU(b^%J@qH)sy>Q_dJOKo^ zG*H6E{n#ww;_(@dDTdWR*1Vi0J1U@1dZ2)89U9CT+3M)SnU}-fbJ7S`oJ!btZQvf3 zTQQ_f`yHU3HZ;-L$5gs060a8xBXoBa3CG+MxR(-JadiG?nAoc@!cAzWWtC`#;l(9x z@u&wY2gYyGj3b#yp!JnrDla~93T;q%Uf~+H8!#OPShF<0U=yFbOl(E9`(!Do)1;8e!f7wHTD^O|emQfzpH{r+++w|LWJh zmlw|8=18r#g#)eXV)i<7Z$02i>myyN$Sn>$g&hRFDTaXnDv5~{sd;*6PAq-vTO4im z3~>bjakxpcse+>?uX!1v8ZlWwxnkZ?q5L}j_z&;J zfAx_=Mb*SCSKmGN_mBXb4vM|1$s&r5@<$pY;t7*eH9V8#WY&iOPBg2DpHrA?ToQY; zqG~((u#cF`B189rveH}4c(FLxv_JEPEBlSDD}y|p#k&PzSrj5;jJ5MX6wY$O3{I|S z7y#XEPhUVLsb~jI+QZe?d5%H9^!7r+AVKPtj*vBOxC29?cp|aJ7)z8^7!#lUGjB+{ zZSpBomNa>W`cAV=RP0HylDD|POhSm)XV=0kGg8uxr?bO%zj>CD3K^|p@)3iw-F6dl zPKm137@BPjnFUKoB48xvRtNpTF4yfPp4hc9fD75My^M6@m7g8lD5>1myf z>b8L%a^^4?F>z)qr!EUdVo}(go)!cJ2fv=Z>C~G5*~8qbRN79ct8rk5JOw;->-41_ z>#3`OOt`%%X|E7~%}{#VVIMQqvbTOm!)=0T9ARB^;moU75nOhWPKCEI@EO|-a~Tva4|vO!(@mXv zs;E7I+i=sOr3wud)S!=r1%NhWE7W__YIpp7zlH$*SB@@U7Ul=%+!x9Nu zrN<#$dVwfMWX}Un2yeU5CANz@M_cXYfT+BPazo~T8GAnLMnr%lxZ`O=AvaDb8&VN7sM7i?O@NI8+1=68KV_M_#Mfl+7wlNC#u>H&P|H;uA+YLJ7B3WHqbADEtLA z{YX(pg(g%--PP}SBp;D;8wU0?VU>VNzG^T+uuT>+36IjY%9K(|Goe@9{V>`nT};){ zB-mO*juCS+CK@NLc|R9L+G*?_oQxU(1x&^YjZu9o5IbRlUiyUGLPin7h`>%ACV(+6 zPv1i$t)<8!q~)hllDyEu3FAD-(SpQ}W$G{ghC0aGZJb&*&`B6wJ83Crle(?fkMicC zp{Mn#8ps7UNPDWQY7gb8*;}9HQWpj;&0`XL6AyfUf z#Fks~6bBD?NH2#+Nu;@eRD>up-~zeB1&?Ddj(%h@m>DFN6-|WxzQ9Gse*SXXD@Ryg zxVq*q2f8^HsOAf!k=cY+@W~ZvltiDBvxDC=$#>#LMk$qX@cb3m+{K3wT0ko5l{aF@ z>JkSfq{7BV47tUY+Yu3-Nq_i(pn^Z#dM#*Z?Fp@<5cbD1_m{u>|1ERQJj$z^oPMaP zL0dP<3f7yYE?qE9gBtn!yN2o}m`yj~2^;F9OxZN$3|dx^9`zW^13YTeq0orF%F^#g(4#Jo14mYrG5aS|x96v~n#Y=iFi}KJ9$JJ1ny3o>A|8dRYk$0XTlV@K|R} zlcalh6Up;PL0cf`s3Q2LoHZ&$wEj#8DtK)Xa5}||f-N${K^eHV<$HDlL$*!mMpci) z%{dlcK9-E0t=A5O*y2lLa4za1y>y7qlhI%T2CoA$_u$0}R!rlbw?*qzlSYV7RW)=` z8Py1TGbkGia1X$fu#~~R-dmeqBng;t?{k_en=Gc{Rq?*zil|P!Q~d@Fu%H7=Qnr=J z^%`xM8SB|r)x-U$8UBmk)F)-1>KQ}3%h|8`j63)`Vhd^9VVzh0 zuPgkrimRzX2xXyLxzH?m_+DbL=;TCEe$uoGz=u47mP%7rX2psm$3!ur$B9m3Xc_G- zvWZ5^4qkO)cso$Bv4-N2$&*`-aL0QdYoo)e zi!7*GAOvq6!>xe92Zh}FB5L+gJNBk=)Y|{y0{=4jc0`{_u638* zZv{MG%B9~Kg9R>?I@{geOApm?f7+1;GTXFkk0sCX1Y25~JV6|I#ZEHoH0?wX;vM;Z zzemX$r?9ACqRA$y|LNxoYJ)OOv@Db?5+#o&v3H254%%c`JGEDx!5@6fLw9-VB49~^ zso7ai$PgQonnRj$Whs;s!}Nwib?trhIhGsV-~K3^un*&Rqf~VP?2n4d4GEpc-_Y61 zDBJ=GF)Z$7Bx5X+c})&Ar(N3K_FQ%1*JZKQq>B#__G-YyXO#;$>oD15fishcl8FgJtmQv~A>^5p>5kFgw#!cAbJP8@W>*J}g(>78HuXCv=Y^O;~YKG5}v zPrf<`Ot8Y=<;hQ;#h2k)2H|ezl8=JcX~P61rj`LV?ef1-)abRDcH8un+n@Fkn!L0G z0@f)`iB{C9HYc03HPV=@5mhpnA;W?t4FKEOd%{#VxmUjJjdZG;inZL5EPeHWJTzd} zV5H-XF7Z5PoBeht#-ZMV#KHR=UPc`)n<$e+d1W%Or0?Zy)tQ@?X-*LYIFlw+Cwo=?9j2Ciz`xd)eW zf9i_K#WL!hTHm%QBqHAB6D=`xwEoAxT1GnxJ~F-2mU5cu7Y>B7#9@Wd*5CFN0azMk zvS}gf8K=ocNoO=DwHlRsrL*l$lLfX$4zOAD6f!W*q@uT;!G#=sPHn6KvUA!=E1{HU z>afe8&|P58lo^xwV#USS6wK0_@f^^9!X^V-{>mi)TY~BWtP90s%!!>rfUcCUsn75w+@w4%?2gGSI6Kmcj@- zfr2|En5=uuKEY#`B#f#u0gU=qK-hXtCZVBPEgCaLG~4x$C+MI5GK4xqi1>S580<;8 z9T-8#Etm0N`+>JuyYcjt5hH#!gp9Glgr^8{rFM>|KV{H%QQZchqq&}O*- zgu3GeBCODP@O;^4FZ0M05~nF@>Zy~B2s*%=-9w2g>lki#_PD}WYYsh8*}$pJ$@xOs zdK;&F|7;B;pg--=YAY0U0BLKDwd`e2P#}#rP%*sl!a+?hUOF81m|?jN zg=kq7@*U-2bdx8D1uKm_^^kHP>{%f~lunY2w|i>rJ_zuGv`g@2xU7OcHT_yQcrO8T^yIn3o4H@YqQvX?$-nu8`_!M1SHC#`bDE0RZa zpiY~qQy^wfohH3gPHIRiqjDXS)j~@@Bcd{}eP+M;z}y!|KG_ANP79`5QN}?ja;EBJ z(3nJ&e8g##5_L3u$e4>+a7T?=UuqNDL8HwqwGe7b5a%!q?v*7wW6z^i00wFqbrzR` zUjeiJ)N>E9`Ic8)c=2Gz7*Y&#&4Hr?X7&I(1zK7**hEnLHlgJn$P@S4ve#B2_k=zjJL!);D?Ild(An=oa6iZ*LrJjdkbHqH_-&0&6xMZ7wv9pV=*uFR&V_*@ANh=_Xep9Z~a!F z%uNybT%9%c@9|Iw{H`+}cihe=jgjfu)9#`vchcbIt!t!;IKqsXPh9R1A5abZ>R1+q zI@|Kkyy6IB_@UbJD`EQ@U1Y{h#AZGH$-cz$$ou*nJ>#SIF;h-uj1F?`yB-ez6rX$$ zNi?#`fMiB)*cjz8`eM04qKR1I@PyooD~Aw9X$d;nz`%k%n(r~CNQ8WBwGR@41L8TQ z^#?8ZCj)K1TfXQo`_{yZZ-ayNqkOe@`$}KF_->CI!TD@n@@r`FwYDp6`{t(q>}K#( z%Bbu~TG_`kZhOPpONgw(;`-_fwUUJGqp}>U>`kkzO&03Nq?ODjD^(w3^fZ?^GSE(w z(4>n@tIq|QX+72(51FzX=+4Ta1sMAA>-`FjkRH)wH15|Xu%>tffoRx?3LkQYYA-Yba>_Ge6Ke%XihasE74GL zM{&wQ2P2lSCK#ksfu#_wv$4_?--)j?A1%y3`AuDHFsx*wIl5qLYyww@`!-*MoZ2sa zGzze!kNqOr-16UEFotZg-!T5c(=ct*4}09y4+BSg=@%u)ed#a90wCznzVo;FvOVi4 zP1O{mZ!}KsvnvqRM462jMSDp7@+uK-*_dq((RF@}HCNkh&f3IubB#9fj%w{8-eUGM zQymj=;I4AYx<_lzKDd&lP^Pc?sH1HJpm20}&0`EPJi{Zt>I()D44zpV>abK(rI3vF zD9>3(r{lV$RU$xE9~h+~zh-kB-|5BuK}kx_aAFFDp%oEW-|Z21c+^=BYIIQKi z9_6L!p>P*;r=MY(84fKgZvB>|NQjaz+Q>p|VHuEeV%p{5l8fcpI}5nbLcGkHmwV(_ z-Ioqr0wtlAS9}Sn1SPE9)}+b4`vS$qyNHOO)@mV8gKVM(GR{R`3aQ9q%r@Gomr)(E z%XlcbXMD8LN;>Hk-tkF)J60P*bvw8^VlN>9Y9-Q>P2Kgl;#$$PY3>(no4=rZEI)iz z-}cMA%oh(cf46V9%sWKiJ=5d{`m_goMs_JzH*3oOzxu6d^}RMIDbsD$s#sofS!9Qx z8J^{Zehi6<)}}eU7Jw;l3qi7@)8tM|m+U&g5l;8AT6o155d%>=v&@uAYpaBYj;syh zk|Gn~3w!T};PSA^mef;z5sz`nqK5>naMJN4unaNK_>Ve!ln6T24ccn4=l!)_+DdD( zjykJsFi9yWqxDcqkd9-=8nf_cH$4b(+G7o)j6-9hgC-rV;PP(RPQUjfn`VDv(-gxi z46YSes*6u~`HSCXe`uqA>*+vl_~+4YyX@94J#E$x9duv^X`ckX+suus-|kC4`gi@d zWO#k6Z%x^Gn{JbC=38&~eM`}_Pu&bApzrXlzU@OJW7zg-q8*m@`zw5#Wdq%{{kElj zm2dlvKdpZ0X0uloZ^Tcw5v9gxYZ_@eyS7>86gF61_bG<$i2Z0{xkPLcHML1cd|h}> zJG|yXDLvO1S}T zyscx^4eZ}!7321&O~*%iO*a!^!?UJp{TRf(iilCKYC$`VJ z$!ezSmPHzhf&_!+mY{4XLBVL1#Ppw1^cQ?sEN9>53)c8- z#XtCEcXOf7xW);OYKI7KkNu|}d93nNhef!BE0g;C5OVJQdhaJ zt~*Oh<&z)P1Z`Ck<(goAO4MH-{q4TdvzoX1HgEfFUgp~kxA-?-edokdvheY#U-MzU z-1OZ%Nl~XP7Tqszv}o)Ms{T)Z>WWm-f@LamB-EJQ`Vz{7prp-lR@H4$Lbd%hzh+(2 zWZ75U-%=zk06#}RUIr^)s9d#_MvHdh>*{6U{itAiE(O^T=_L+{hmd0Ke%R(&5yk%A zP*#@*+p)^mQtGHZvk_zIL?L4~F<2rjxi1GzNdZu3x&|xE%PiT)?0t>Cci%ecs*}vz zQKs0U9`fB+-${9`mR@8WLKmXVhCm(uvA;E+ zb>?jAZI`LHqiMDfnxqdsk^44Be=|;*@BZE2jw>0NRJ^Q_dD$h?e3?LDyTxl^@um+& z%4{7?F%Z+f^-I5P`H5!qr$2Q%uXd(u6bh{p1AzI zPao~HCnAkuvzF3oth+!H;CIzh$CT4HOx=XiUEuv}^}s5)^;uu>)f;cp?6_G&QgG7f z^eWgHK1O2zYg;O-CN9~ltx7_J>{L*oQ_E~HdT&-hB^r8SFid2dlq@CLStAD{Eyg;KJ`JeHBu0kJG`!FK!Z)jK`l{Q4r=@v zo78&j%RAiR(@%W5M_XgU?`l{B&#^!VW2GdOJE8VDHb5PB)~LO62)r~V8tEVicz9F7 zsUIIqsQ>M_=hP|I1YLE7%gvF%3;4i+IVIz-;Faa>ROdz+J>_v$XJRaV37nm=b3Q5O z|XF@n#gZ_j1X*Klbn7wL=ml(81MPO(-?f90ky*=R&A(r8;Fm_KhP*w=v{m#$us6Yu| z?;vR^`m6zPUHG7k|MahEqBqLUqU%y0gR}b*Lp@iTO{aU30nQ#`sx~veeFI_Cn0>5) z#ch)t3lMD2o89TArd8F$+q|nC_CpO{UKY`(TfzGB5xDx}OFU&q_!O;dIA}M$v}3F9 zYJ?3iEF?}Cp`8hHW7*-g7R(OXiCWSD)xCCRPFNDS&GOqQZ&Jvd-e#ts5c{AHAY94? zKM6#Ubr6PYwwsz&Plh)vkd>xLOu`I1ue8yg?X%9tGyc_|#ERVTyd5`zFG4D5TWs?^ z9^*Y;UK!;v+7X_YRDm_|8U%?Qqn7X;BhJg|?k&30TJ6Vrn_-SMMi*yVb-pEMTZ1v@ zO3ThgveA>P?qg8!PybL>`!X+sy?Q|6q969L*9da@w~nTXp}T%w+_aoEPk-%hgHDh% zR-bXkxp(P)w{=WXNI;iqa*W3r_Nlkv)D#!Sb7Lhy_q;BK*!NUNwLMQUNiIX+-P>%9 zuD|O7{GyIoleT5Tb|*WAHLy{1R6EUa6PS5KSh!tQt$?YB+8>Pd15HI-c}OkMOLe zl=uqI`Y4Z5N=551%^~EVhUHJ_v6HM&D|r))M|CMLqE=RCN^}don}x+U_N0*i43bA5 zYlym=sE17++e2^f57wQg8~&Hf_VIbEe{J@;#efU0dU;QxzYCuCsw({Uqv-lEXU9we za&_r`B!i&0t1?%ATqk4?dqFL=WuB=MeNH@C7scY2lx~DbA91dkuo`hM{q!&Uf_dYd zasT;G(|^l>j21jTM=P=00@UAHcvLzC;S=F-?qF@IUgCkdjiV;DnB2)I(;k6tDP68b z%Pq~CN?OE6mP*h7(llfZrj!~jE~&e8O~7i*Wyz`P%4k{9;^r_tE=Z5sG1(%nsF{*W z*k<6C>h^oR_t}>pj8ZktG6Mn?a>ed<-Db*Jrz|iUcX_cZzdc1;^NhV$ zJeQbjf=SSrpm9RKmQmz`I%%)TQC<)Qv0g2JEZ2HB;l?Mta`|$cGUocH-!da-H z6N=Nauvw}k+GR$e7M5urIDS6Y@~?1`3_`9@kcY^vdU~Q3yE3*(i)NJdS`jiG$nnHM zDzaGCS#9ZACz45FS(pde)s{n_D3ggL3h(9hHN#6b^0ZY@^lvd_H(%>Z4q`{H8Ynu4 zZ>M#-n!3`gmMvV;vD$)|DKIX{8Yz>HmCVbY_5bI_cSe$$YDHEJK`#khegAV|`g3%JwY>g3>oU-FPoe=;( z!V}db?8?&yfvLH?(o7p{){@ z3T3*h_UL(8{#H9hW5I)ERf(PjNO+}FbYx#X+tx>7VuvvYsS0!KNm<4lVV6};+JWRW ztc1fezKH$=%d^3H%InFqIO`1Lywg_08GQPNKR5*PGx~n?c6P7l5d!J*abRYHU!MqhE6sg_^aIF*+>zK@DFZLYYz4w~F) zLMNhLdK|wj914{T&VW--)y!6NyOpO|URC2}-T{u5J95l0>VJ4;94^_tcPAjQ=w#f= zWzYU#yC7h{@3oAQW}UhB=#Nxm!$oT2^kuRJ--fjB=FRkS#o0rL|!&;GC(3vLK4TJKjr(lF|<{FTE5cP%-9kd*f`!(jOwdK@PGeWDic1J~p zc(|YbNHZ?bZMv*>`lE3m9&-an^Oqav!NdL4oo5zF;kD7G10PmMK?AncI}b;r)92!H z8!HVv%Zj2(FIjehNJTa9^uvzTXl2cUcQNek3A>)%b}g#%A{mVmZ}zK(@4n?wyzPJY z&Ug{;(8?}wz;hbAA55WajCQGQ)4+hwxfmSFtn5v;6{TuPJsRut%C&(N8LW?8_A!{X z=UQ`aD9MFKX(HuZ6O~Pa6KX_MVGujJ@X0wBRPVF#K@11`4U8PS%sL>5_-r zaJ{&I?Fq7q!C}V;6qR~jA-DjF0y2}xBjbgEAt8nM@n=V7tDI zEq+k1J3g-cgZvV;^fJwm7|37$o;m{i{vXtrpz+7`(dUsNVo%rmKe%SUCD{nXqJsKi zeiH_6lrZt3p2jN?u0=BWRC}+h|h;I-1JU@ zM|i!*YUoF9=MusEwUih{7$`pEN;!gD^b9}>W>lI9xulP=|5}*Cq z*j|b%tB^{usC6Td&X8XA0gceEwz^gBDvC)dz;_4v_nXKdGcBoWS{V|kjh9Yc`-&kt zn(RkCW8~x1U;Pi0UKgU5g%e8rl~dVG+u#8?0l+0{V^`-+OVj|bF zBfi^SMW^B?pRcPbgqG5Ea8O4l)mmjiS}j3L98~ZC?DE@eklcjpp)}Dj(Mu=zbVuZNre+G>uV#k1kfz;ARb^hPO-XVpYt9o9oVCPb4I&v?}kN zMAoHr9UWB|eud6pRmD|72!PDdxrF|o>(;1)UV7ORq?1HaM(G63Pjo{N_~>%}4zFN0 zS)f(Jmkr`n>kZ#vax(CnZ18hKa=p4L+HARsqOxs}cx9`&6lxK=>zAk_in=m7A!V@6 zvG8MJ3S@6jOs_-cT^3TB>Y9gS<-7}|LL*{^0|cd%@BklO!kn4|Mr13?d^bXOoJgvF z6T{!Xl*pE2L^;*bd>+F8Rr1NSFBn%Yiee7AqI*l=?}deH9v%lJ@NfMt5M5I$@5E|BUlKA}lSoXmE>Yi)lU)R=^62bAxwdlU4{m zea$|W#upM7RzJljAZP$pjMwCmh#)K&J|5!S4@OzTmWnFsrfV0`)>;>-E8j1yEtyqZvejDH)FRucTKqi7|CK?dc*Dv(`9{AN7I|Q4ppGjwk&1$FxZ!A3azfya?|6~&%2?84cV?6hc0CFBV=#_J6sVySZ%!U_&6;$u#YV}ZEgqOd?szfW?wL* zG|Mi-GLwW3m@Qs-mwn48Z%0*cAu9kI{P~sKkIT!IY3PO^r2X?i3CIIS%kD+d`k zIW5dxm}@BhoHVC>@!1LJ3C$5;+>^`BcgF}Vv|*`q58sHqOD5Z>RrKT>eID@dmJ$w3 z`vh5Yk$J2V_E>6NPIu8kcI>-zkC1tW>Y6P4@xX5)FhqM(b~yq@_mM|Ssf`Qn<8@Xz5AJiS|1BnH zHO>N6Y*K4E$zrIC85!%zYv8riLEnz2CWa+{e3d0IAcN$x6ljM-wZqL&^xd*@jVU3N zSI#60rGkrGXjnZhB%z}b(mH3fBT<=hV7et-GDVXdR0{=Ds2ftWOh`WA*vG6c zoeFG^2~MMK3a6~q#%d=1X$dK3vcj@i+2WW^X!Xb+*LX06X0KA;b(eWl1i?y1TC7>M zHNQBO|MoXyh#TA_^%yl#6Y$fP4l$HYc89!tJXurAMPnMcWe(?_;fA;rcNJa_{tNaz z2pKbBcw3IR@9A2(M);*o5y?b{xdhG3fkt0?YI>`fTh3;S9(&5tu4{OgK)aaN06C&|t#1z$zO8<`r+@vjb?f^Fhs$RdyMmPG$ZC_j%C& z`)`>ea~zp`_hgRhAXtoosYG7RnoBI?mFgu}GEQBt*#a4kqf|Q0j8doL6~{}A-&m}< z$bq^C8l1}aVGdF62h~)LJ!0R`SvE2qrBtd8iVud;)0tEt>foz(^WsV%3DF?{HraYx zWHLr7mfpUofP;=T*MCeKzB{(;H&+P*NMU3`a%3)j(kLSYGqUs|(2ZU!fC5U}BeX0s zE9innWhtG|$VM)d_<6VZK6Nx687kIicj&nZdpvPXnUE&f>;lKblYWJNL1gq(<>d2= z2mcmGHMVH>aE?XSOH>5e_NYZfU}-7IflMo9Hg<@tIe{sDYHDt!D0YRyDNZdlTvoLp zNtf*XpYY(Qk#wV$<8_2NiVTvi zt}UDFHptGcYXDmNwW8^WiA_|@6j9)$mu$E*)lpRrUF0^=%b=0x$f-MwIQ(bj(d2Je zO{a4&K8?Le58A^vhQp}5)|iV3gcGi1J>#`G{1eYbeWA7&G&FD^Qa2P zM_1hrkwIJwT0ok7Y?Ewpkz7KbL$Tc>^ob5ajStwqfxMfLeIyZ1JM7zFsD{EzV!(T` zG&SDZ;w$On0^h3QGI`RRY{*GWR^Gehrr)MphMOv!odiKOkMS~a1g4AcZbh+kiaegm8=ZxzQrF$<%Q4T zgdLcDt!obM(V)Kupp&QuAi-9dTn|13@9?2uEcGpvFq!9Q%&}Y&^s>Vd6%h%|5YtsZ za6Tb@Pb@hd{~JW~%~i->BuIx7CJ7&K;(%ePuQ;jTLdnRCEhF^5dMQlnp3Ls6V|5lGMY-s= zZ1|Dlu|)v8o5WyC8vo>zXLj8V1I8(wI091vdYa-o5AyEts8Iy5OnAwP{-6pc9a%aa z)#Y*vj})Z;IF~r<*xRvZ0UK}xAn@=BY~f<{!?hnh59ojXnU0EAJ7H$4j$EHTqsfkz zos>)#xtJUbc}z5!J&rv3=!C_Gj{4BJq4+f!`%R527!%vpya_>*&&1b4p-`;V@* z@uIrzX4_zI_^>-VsBWe^YT2Q-38pLbzm3ZcrmJ228XgGj4Y%Df%O*_5^s*BXx2@*b zrH2R11@QHRPY-|(+-^1ez5%R#SQ z^WJG61R-lpEZ1EWMp{DSobeDI9k?hPTxY#PHsq_rQL>13iL2W+PfAi_b>*9DFr#D@ z7=O)cGX`UXPei!8`9hiDlJ_5cl&g^qA-WGQJ)@=*65rI%CHBAkz$M0~Nv)Ip(wonn zO}1K>p4pL`N!}Ac#dF&&a%$m3F#wa{zOXY@$E2&*-~o0{C!Sz0K7}1laL$G12Tc>7iAaJ6 zm~D3*JdV?L$&^9bu>JBbK>8W%rS9vSEi`S=GOnUbWdpF$RvQQ}xVZRgyH~*n>D_Sm zq5bS~u7kPfOl`9%`U+RqpfHC>CaJ9PpCKs%Kg&(kQoQWLG*wBSLX~K$!sUq_l)6m| zy|K1JUDU}6b(<6`e#NdU48T|pCo+DJvkAxA$RrnirNup?0;SmjfXj)&;U5bu>KQ9! zNS4rsKe;(nH?AGg@2MgkcZs3ztaWMN8h!m|oc!-j?0!bf>{dKPe9Em4;b>uoBcSY- z*z^$#Gi+QHN=PZJgxkpG>!#vKCdAd@f+cs+1rp>FHwjzp_`FFU8jKlwVBW);0Mfz5I{_6@4}0ZJ^~8c@ETgb32$<=V z`w+Xvo}VrAk&|=H!-<^C67Kpw9$!?G!*4=hVo{8H+TLF8wu#57KO0VWmu7PvLW7=kCD$XhFH%@^(iygic3oVsFlj-QkA&!36fL;}}T*a6Q(097G1DYkn$Hu1{@EBd96 zm^_e3ChQJGA_w$H>ZpZb0lO1_<9nj`<+P!LVGO&xU+rZqqDVj%pbHUL!bu~w)zSxR z6pKEBnxxdhA(9O6d~J43E9cBK)lqM3AlAX&Q*TwU#a2(e3QI6ojMagai?`L62>g{D zW+Z}IVwk8wV2Kn32nIH>RwLX8Ndri}5mN{>`C2H0EU=}=mC5D{J+p9B7^1si1pf=s z$bU}GKPU1NXD2+(4~6%}e8MqYav+)D)eh|NF@sK!sNDi0Nn7u@;`swk4hg&@oF+bY zm$=PtOJs(foDGX<8E~Q3CgZZM)r~eyE~wn$(cH_zWRJ4TfRw}rW?z=fUg*yW`xm#~ zh^Y^v*%UcP5l^`3IaSC#F~D5mu+NSs+!II*QX4D$Fk&e&^wd1z049pdc97i6h#9?y z+@Jwqnc3kV18*u%s27%Q5;WC8pw^zXf(*{#6GxmBk3$$X$9E@e6tu<;F5;@~goI+1 zb2S*wSyl~NqK7ObLPSeL+3jF)#F8uWnmcJ|S}m4W9ZrprBJsCG0ery%gMIvchaF@v zGQv6kmfx!}z9NSt2=eH32*S9}srnc7)}D3i(XBz$CG5ZzKFR2y8 z`9UK>*G>)NwzE9hV0awSa)ndpZv7*12ZK(lOp@ugrW(&%m{>L%jFI8sU?B*}`Nztt z@qhAQA(qZ&;;Cb{?EJfgzPtZWnaq)cd#Fqr-5e~#uD!=(WX?KC zeK3p@ofKhn#DHJG!_`|Xt^t(0Nhk4pcdy~K@Y8oGWMlzws~%kaVJEN)lt|0~lw-A+ z8ttx(SkO9fU8(?Y0ELFD*-*K06DiJ)*!H$TdPIcp&!JKHvPm?qILXY5H?RHMMaVzTf_JM-hWZb6M_KDjufj zgvwn+{U<*ieYT0$l`=V_uKv(rjxKQ!QAQdGmd#s=I}?jQ=g6_OF6d%tp`~-80J+*w|!PW)t{D&2Pf2dEb_}9=Hr-%OECESw&)Z&qQ z0xQyf5eT%aGPC{*T|JvlICZwZbTW) z6$!{|dJfB$(_u)C=QGD$KFes4$HBry1O=!~+PDac!Pmv=D$4dN8EvLz2sR?#o5)nx zsVlY;BOUeivOe-eo}E=zX`@54JdFD$x*i6Rf414mZAZZpK_Vs;z(Fj6@i$E_QUcLN zgwKilHUpW2IJxw~`_f9I!H?~3LrHw{VTV4miKt0l#keZOx4MA*IH~gN=uHPr;bdK>5D63$ z`P=62@sS5$EH(bwCM`S0(o4@(9<}l;JnwjLd%6OxQE|F1MT)TgoZh z5>VD0lFR~>!3M^$#W&uC>H3AUxgQ2!&lh%;#0}bEE7OB(;iI=`p>pMa=urP}$sPr| z+k`9VtY?}902CF^${|%kX!0zuDhSec|2{5^KhdFD2Q6xiy|c4!gaXsT7Y5;IoaUYZ zr>C2M>R0fhwN)}xgcbt!OPFTKtu}5AAsFM6t4N{Pr(FCwTvKQO{6J0_S2IzVtd3!Q zz!PygaOnZ*Xo6lG*h&vgas3*BtnvU4k!! z@U7vgrLj~`5DOEF*^0KAtV2AIVVcuZXF^q4E;7Sxg@%-JDKSU5$t%J0@Fl02Ld+3P za45A@Dqvy*KXXq(@y*?#kDSIJeieKe{<<45i=-}ME(Xr>XDCmVMYtwAzgDk+F@Tg?`mpR(A%IQsW~I-!C_5$KyAHwHl~&#GQW ztDBGXey|W>WWYD3-5?x=`2vPEqzHG@U2Jp7CPo6|aFSuS&lv6#7-HEK?gQh=6$agh z*6OIaHp5oqb)ojJM??6_8)p67v~d2rT-8XS zwE{z3c(=wH_Y3~nzj_0!0-1(iOyai&&)Au~QToK=fi#H!JWL6xgaP`K2rrtcmRK;) zJMu_0w;sj`ToJw(etfuSc!aX@VUYuq2sRCeL5mEXMls(4r}EjuWyBMS7E%TeDMQb!Xn>$eF)WvU2Vc-tN`Hmq z;=1W~50@Tib-SD*^Fgxm8_2Ny}P^pV7rWjPK14>1tBkH)XaXvxUn0n;vNg`6sQcOpV;1VDSTz9G7KY?Px887L00E5q^ zbpsV{0tY~WaN@_GT9@Q7t4X+~NIqPSF60#|A<#&689pQfnB?38$X%W;zXRdEE)XKw zzPU{G1FXAOB@mLhtRYSjmk^(8=Fg;}x@>T00A8W~5=>WY0uqPWPAg|M{6Hs6k|m)3 zaV}eUYW$#DsK5F@e^V=65{7I=>*7OGM(30xrp>HlDzwVzN|4c1k_^z;6bE#zN^~XS ztf-5$86K34rQn?u3PIb534s4SFqK4aj&3uk#0HY3qKVM+qm8w=2=A(7<3iLHp+Zn) z@{n2kaj>bSrRK)=h1zq786X?|_NU1UM zdWn&Uwu_i*zBIQravEjB0za-(Q(L>{sR|ApDpZq+d9RTBU&4Q_QinPKkpx?x! zAtC=aC0w+4wnc;pqQXZI0|g4rfPx?zly?bY1hjeu1_$Jq?SO6OhU8{47+_Xq4 zk%%DCC}W+qN>-Tj_OAy+0vv3%u7_4o5w*OUhH8cx+8W%jV7z_MuwWY39+R8_vxnu; z4cQEyUuht4LOuWJARs0vgD8WQ8kc6(%Y|u6o_n!9>aP0Ck@sog!FoCLOcHE5&UQfm}6D$?zHZJ-AMmdOp%Ke0{xNukZ8y7GLV&PkMj7Uj)15SA3$zkiTxw zxfWAskW(CS{UOgKlU_aZxbKows_b;G=wj!QPMaW#X4}d0uBYwWq2r)O)-zz7x2xJ89Fi9L$|aXTE`XMYZD74Vfm{^lr&Rh0Dh)&OH$Gv+Vi=*U66yRaVj1 z%+yk0UFG_S54NDx(6cF&E)fnK_|sA|>|+5- zlE=m+=V3uY*d7X>(ABwTo}vEjpW40{gDs~# zemTSi(@Tl$Tbin-$5(#R`CW1Z-&a!QCa*2M@XdnHNL^qNfQ*gwy?h6*nDo>*ffn%; zuk?%rXEglD;0+h7g>3TbrC$XMBl%fgD1`9D`0?=&@^8GN%{|hhl)B2yrHl?! zYxiB3IdY6s7sknLQ9zjbpo$$&p5d>%&=v+TkhMy$WN)=H*D0ePo?#voW~b$Hr|rm-=2!G_Qa76g zn3{4x6tZWYJj67o?^t5?!Q<3%`f--Tao8z{9OZyC^O%!mM(qWw`gKDr?yUJVi3QeB7s3MzEMD2%OhHSD)UXp532Ca7A<$)M#?2f=kdL15q zs7E}7EvfCMpT5Se<57AVuxmcV+^nbB<jcwh)(@k|xw_RHHkyu1y2nMUNByUfBxK&EQ`0Md}t;%#i&Qi$<7f2(Zx4l{OF8sdsA016|Gctdqnl@Gf;%c19a zJVd_{bp3>9ibUefc;pbRJkkjx90KOWmXoTkny%>)_dp6LnlcZ6SSw--1#2IB%GuXo z6DEIOLiGMm?s}s+D3v-e-Abzii$w25I{C=ttkxQzfn&IB%@gt>~d>wS)76wKr_ntup>?( zg(c0fm4=lf4bw6*6jy;Sk*?$rBqrS1zexq*{Ls5}@SQRAB|LRVKyZI!IvjxF>hPbhww zQOcNN+Kq^@-DbA`5SGzSOIk>sZK)~iV)`{}i#3&;uXMWC)=FwDeXENazlsb~wYJ)G z&w04* zgH(wA6-+bfQc_^_pzNisPPYDxn*j-hwKg&!@lQh*TePkWqUkHM7hBQ!~t8LD4x- zYolH`dTK9*?Br_?_a)?i`P)jVr&`vVr_qMMCTE{E-JDTv2Vs_)s2pS8x~T*iN^AA7 z8}g=ps-zZBw2sQys-6geF-yLbIp#;JmVKFOq*=^Q%sj-XSDn|81@xOzQklZERM`~9kAfeOP1*DdEva0&kL8N0{3IkB;FDW zmm!_tZ7Jd*HxhQtt7R8M1ewK-g8@lzyOsm#S!&rT_w z`Y1kzdq)59?~5c~@~NeMsGy}prUQrYDV2O_sezcc2Yw5f3uc__A+J5{48}9$oE?f-;as zom_e{6Q@UfrLXjW-^K9z!88O0)BXY?fWSvR$anPKc_yFqh)+WfJbnI@Gku3I60a!n z0Fn~l)1}Up5q8ti5}QKevC{PYCZlw4FbxuVZx-C*DGvk_|K*?ThVM=(`GD1_qQ(63 zUqnaCzxiv%+G}6Wm}paKQy_Y{qIA6+SdB6KrSkVTabP8EjjMIp)DD zZM*N5pYP@%3wM0151W0nhd+Uj*k?DAE>sv*?)=ARs1QHuEUaAS+r8A|y68^mT_(%qzugztBz2Wc7=QfcQISC} z-jdll>mgCM1eEP~0pBL(rJf!i$ERE%*Xkh-gm1Y?%`^pT(5reLy5rpE-sbI(dtGc) z%Nx%vf9qF8HCjJpkSy^#5lNbBHE9?qo7N29*83~yoyuiGA_a%rW|5*w%nF7?!d=xt zS1hM0YAdH27Ql#HMAmffWY=c6Z*Z{X5?P1;=%&cM#6yy>u7o)!V=Q%Li{6DBVH`Yf-yBZ=enT^ahilYtk?Na=g6WKeabIqt@+VJA%F9U%Y=Z;$?_VV2L5;a{UObU zs|WkZGRIxAFio7|-}xms+=k{4rYJ3(R|0k&)@tGQeW+=f7a>B1`*Fh}1Lb7HjZX)` z53F{)4ny;II_fer8Cy9_fUOX??u>HLSCA1lWB1q+Gz#tOr-R2X)r1iJ`jVrY6qVjGqx5i+NuN8kzXxUd1b zx`S+&Pp+qJ7f#`BvTlH>l8AhmzNCst4zQeGt|N=NbompAv-hc_fy^!(M@Kks8;BX{PE6boo0b583cWHo^F~ z(@E^BYs}DQ`TWyhJ>*D=#+m2ZeWsdN`d1ND0;7vLl2sfOUQYEodLq<`R9$*7xHbV| z*bYN>O$f{WF+7C)wT4{KDd-1Im}pQAc&sB}B^alRlZd~ZFxDAv8+`6{w({cCc*tXL z>@AkeTI=m9ENt%ppFU2b9^oXhPEv8NYZ5h)HA79*u&;&ovO=jfg83Ic29+4?P#+NsyXiXHBFi;4%;B};{bk9-eneA`B-WX>=waK8|`$XM6NC9!YYW@Gt-NgVVs zTm`NY2sPi58qZb(2HrZ{tCawlX{mgL>ur0<)AR3lk5N)ue;5uQ} z3X;j1O5rq>$}@-E#TXJjYx!@~UmRy?E+VDdL<}>K2;v@Q(GWmD^}~iKf9K$*aHTod z%@F=I?+RL9qqM!>50EW463ViRknlVWeIoNbB7iF*F^b>#xKc%$TMXQ8QM-H^3|~(PQ1csrEMzipQ}Zuvu4XyotJ$-vt94IcQqHRz=Q&1nL+|Aa0sERix5HrwYKbkVOf@K{d&if z#r201C#rVsZZ(-R=N#i1dF2BJx}*dwlM;1Ziy5_Ld6-xU7>DfHm!s8^WvXJl7r#QY zL!R>JLP!$vU&@fzi^NW8#hOZCl#|Pb2m}Vs=&6`xwIz#8X?okNwqZF=bz2*2S{8Ib z8bjsPr7yPyj1|<4LHoF6W`)UJ&FO6k-6$qBisFaKCJLznr#!TDT9w3XAJmKAj@&Rp zm!@=C*!%BZ|Bt?8QAk%SP1{g(%-v_^T?l~^5~{_-!=y4?<^@Ug2YZ?K6-S*?6t>P% zMg&+A?PE4!I81YHRh4x5ZE7mFyhL*CR>V&1E#cocFp3??ue+Wy%JIS8dg!W^5gnk8 z2+rOyj=eYn%V*s#9tW?$eHf9KbA{o09Fa*{_rft4JF>$>N72+-nIVZ4#n@Hf$$ak6ctRCN^p7IhF|2i=Got(1ml_h%*InqeQKP!?P3XbXENx?!jSAbj8m z-FNGc2(EzxHA~GjD7(qtvlT{yfTGEz6wtGrs`zGz&svuJ^L>(19S;|kHxuG8T*DNS z2!`UYPub2Etb-!b#BQ{OzQfsLG2`8#e8FkUQ0($4hU+C3hl8i(hIjlDs;=+1+kyAf z4ZGZQyM|`{bW^4vJM;3$wZu1=1w36_nzfX(sRsW2geP5H-_%NB{7* zTJsT+ddWT~JWshwIA)g?6K;|!;Fuj?%AY1FmKzh{6hw^AfQN{l3`Z{xhZwzX$tbx* z2)+~|sTQXTyJxnsAXy@|HP&hq=N*5{in^-byKWKO6Vn3NnJ`(?bdpeGi$-fao2Q_u zA&4C{Hm8PfX`G zART|6PZg-Gz_X9U9_G=-WHSBfskdD@sSctuk;&^nh}966(M+OZsBzd{dyNIdUj8#x z7_jHdj>D|on8EpY=}_9)NIsP8a_@7btSlNwY{o!bb~|_~yt-FU*Ud%|wefsC6KET^ zQ`tNmqg*b#0V8a*<}7@*#ukV*omZ zUGujo1q&oNuq8%8o)=FL)M4A)d zU9SEt7>}d?;<9lR&`P_lHp3@QM~In$?^rUs3Rx)1siU@k++k0?OjF3XXo$F0|FEDP z0x*M?f)FYSSoeT=qW;RW%ve*Z_Rf}dRw}IPW?IlEDZupbVn%4jhJb8~dWGThT`4#r zRY1XZwceseU+$D>5JiMuMq`Q8!J16P#t5G=D@FN@x6M=*wvY^=>Ddyn-()Vmwj|wn|>{@4_QOSvA+lKeAr5C|L0gxCT*VPij z0o!`T5d4qwQu_CQE3{|`2?~+x=p}jkglwN|B5A$GKjuj;nwX+>i+__sC>fg)?5Sms zU%vU{E5Q$ZEF`yXF1Yp8J-#MSnxaqpZP4vWF0CJi#F`?!>r+yrk0)=t=G`h z)x`M~*kpNyeNV-telbsF-`c&npdscLB2l{TZzc zHY<#}S5u0Bqh!FK<*p4szEbSsywD8ZMd#d0tNx~pfxIBsz7&cxsr(rfE5q5VS4q2O zNC>QMS>n-RBC^ap- zSB$>962UZ@DD^1^XK6VN>`aen+9y^ptMWwhXU?)j>^T%K|+V1^!# zIBC0`w(21{Ez&o&O1D+^PhvzUx1L&UltYeEqIVI1}o>L416E}=gmTSe& zvcQU>L1jkZmgVOiz|*kfm}UCi$A0_|*4Uj*-l|z*#e?0btu3bkDKW1cF$y*@_&Ye# zI^9a89>`Rq0d=4rwvp&+BfKHqDa5b|YD9*OLx}Yik!E1}2!FXsS$swYACvKuV=C0y zw5zAU;?zk#1sI)Ukqbwl^=5wY>wZM=q0x$B@dxdE+1Wj;`ZV%DPkGuE?jf9`ZoOq? z3D^CBKDW`y0A8(%Qhdgvc0KI@ri)%B-5MM~bo*Jm|M+bv4UDsE#6|)m8mNozEmp=i zrOZ~1Q^eflvdU=bvc_pmA>mnOUQ7m36xsr*K- z#neG1c^?{4DXzxK^`t(;RN_sUn5TDHU2pYR98@EO5X6^b)2=g)iI1gWG=PpGOiXvb zQve2(_yjIQm)6S!l?3A#!06?z;1kQXL^R`0o0(^{4FRr@O5|4(EOKBGkn3UaA@0+; zWQa_%^KjjNcPKBSQ!Lrc67(tF?Ns%4t1DldXYci`pMaWUbX zq~&I2IRCTc+?Ss=_y|HdwK;lkhz?ravEg&BbdW6PJCVF1_UIiwiq>W`2tLU(?A*Ka znv{;T2x*epm}mPCEX5oUt=>kRaXQddAPr0)RARAw+dS*II2zuUs8b)kO}81;+?i$} z{a8Bv(Z2Go)HEDY7eE!!wM?0WT>~?*fNQLCLt2|*q60w!qJOMM>Hm$v`njY`XjK>a zVYQEhCvpZh%F&ynH+lT=GZgFzenECh`Cpciz}b?YSVog2D3bjMpv@%Vr8fGkX-X`s?-8YzGvhq$qvOogD1=Kbq@Y3I1Xzb1 zair1ayvcv->cg@v{q%*G5F3}_Y|Th*=MCSl=&X@rSJITZ_SGVENfUYEoJVL}wLUU5 z@h}`UOd}x`zZ>QKsb{+Ggz;NwF^(*=j-GY2GmqGIo3#M#lPqDx`Qlo?7%GkL)>IPEb9`~F^;lwm!zKXZpur|1+G_DT}s{R#Dqk$RgTkcbPrIUv1BCC8p? z>`z#fldBNdXdv%in|(HgNn38A#x2hnX%=SAzM2M`^6@942-@Yz5679c=8!(^hy#h( z^_8x$N1vDU#oT7jv3u|Jfi64-vS`+cV!IkocyNtD#q`-!6PDjKR0r_&$LFK;e=2=5 zx46@hG*4el1DWFiE6bcoirn7M#jqT7^?9$4_70|`92_cPJwMVl1I?%I^0p_=_zo+) z&5jHurg^zX;Pk!2>cP0m(ZyMlTsKuK9Y5By;R-OH#w+-a?{Z^taK;WL%x!t9(kyF~ zU3h*!=4%-j+{>cfiT8aE&DKceEa4vi;D0*Q4gkpLVKACWK9g$2(*4ADe!I7Sms3t& z#W)*Jdy0oP?Sod!fH1h7=8yK!f)tlS`q;0@*a>RweP;jD@9teGz)Vfv3~~UETV3WM zX4LBv&zXnQFf}P_gvdM)>ssr={Hsd}>x?B>{=mZ1@PnEbFE!Vm$8X7C*1fiP4{xr2pGd2GYSb@GaawU>$1+Z zl_$@NaWRAC8sQKzhaOS%G|3cZ*BWm+);{xuBcYj2gZ3Fao$*X8J}8Cp$AA>K#h}Yy zXu@eWK=x%`Nv_3H4jS7DJ)xX<(=U$fo4jPA;cV_O28}v12o2U|MqyNys_K5jX|B4k z(gO8$<*B^}bBPVFK@ejEgfD5yA#N63$=rPxk~fzlW^|NOp6s(NPe1d%8~^K}H-r3! z*bC+r)z*5u6P{CB)rNr^0)}N9BQ=~LP&TZX+H?tenrfonrRViempt;qC{}iRppp19 z`&s4mkMj6ec*$O2#C_HyPu=rl&HYaA@KO7C_mB0OV-jJxWR>@Px%WQJ?l4aKa8G&L zeMqA`XZF$i8S~xW>*KsRDXow3ngkUg=Kh#xG8P(RktAf6?Dk&IS!M#m8XxKHpS5<_ z_F!a+Cr$$F-$X{2*>*a$_rCj?yPdJ8jPdM!KKwfbo6VY|#~H@E>~f!AG80d~;brqT zxUPSchgTsPwf$?BNtw9YTUI=6ncSSJ5~4t+oQJ4H3C4%is|+dWnRaUKFs2qQd| z-KFar0veppl}W=i=&yz<5VN8Wmnt@P^RZHv+g%5us>~Q7uL6C9T9NjY61D8GPfe-F zim#b^uDd)1!_{dodFlh5nIb<$?<^D)}$R zuu;EtpKgaQ?aTU7pZ#91wajC`x{slYCp-{k4&g8LW4z~kyw8{Qc>#Ez^UGZBu{6m( z&&Q;9HdddX>1&QAn=k7Ay7Um+CEX`Z7eCr2N(Rv^$ zw_Rsjbpg<-Pdt%0X_a+gaHa|%TpRl)V{|vMCjQ_){l|VocE(NY zbIc^50~qqF8bLK`I75HIUVtlul^HrrxBSZrsGT{zfzHrsbCE#F-1nS0aSM}cNf@z& zlb?RVQ^@bCIu{CyfG2lKocn1V&)huiMkRJUe$Psu8Rd{=!d&BEH%>bw6a6WtpFK9G&9$CQR9Ff%NGZh;rV(Ro4RJ)uYaZwtHOG*M zNCV4AV~D-{nY)kESB}|^wQx+VluHKGcAxkESlu8{CBmRrG|n%3Fe2u?Z|kBv>ARb2 z%=M>TFs-$inoJIC(X<*9aa06-3fg%(<6cGrHBd$KHfazW{pnAB<~~HX^uEE8RgrG1GTW6oMpNYsoA43rBC?ob5Qz|~k+yD6w4(xf)`>V5P2gIdvx<5w@$jWY3oC%grei>>G0`;Mtl`S42 zvKO;m^>(ZlWQ@s7RiX9r^=iWLajT%fgpy8TXPM0p%PD``%%o25PHWL3LZ}wqD3d-% zK2DMZ$ZG7i`RV8t%rSTGb$A$A!T8H&@Rt!AnN`6F1ky3)oSc2hX0*itfo=^cMYFA? z0r?KkC5`msnk_%(OX+{VYxA+qnCfml)1au3b85SzG6GC@eqzRzQz}w9bRbobI2Y*8 zrOn!5sa`Kabpjju0PsMKp-Y(L60;H+Qll#X7Qbpt))Y8m*TQ@wCY_k59g1%JG`5gq zfw{?dlVZD?hlGdA(a>7tm-sEQCO&DX12gb2*qwmSDuo7n!>F9*3%7I|pkl z>{?(%5UCq>%_+HM2h|6Vq}Xp}BE74u;ab#&lOwpnM+}f6@JxR^*Alr0j*%4(<=&q%!<))b-S^!|aUycI;YwwjVr|f$&z<41y zDF=p!$~BQ{!sF}~iQ^=yW8BHmco7vSfTkcj$t1xKfjh)4S#z{e5((gNH;uQePI1Xy zRxfmJI8dvu3ol9|?;(>)-lB@T-rNA`{4xkybwsafMNhcQw5zX?KI3SsdSFGAWNkFi zLl9vB2;F}8zx%Ytl8P1Fv9rVVvn`-UFO*dzi_R()z0~zW2~|R|fDS3OcPjjNvc_(+ zkz}-(*7r3}6+RmB?;kf*K50$8!|EOAL0NvyRY#Pv!_KQsvxQDIvqxB0+sz3MWV%gm zqF#P_G<$o_1~6mPSz<^I>KF5wwDY>0%s3%8q}3+EPP6hxGaE+jveHQ70zqeqCcCMp zA8ofiCh^{Tj&T`)_?1SPX|j!yGaa*onXLcqSNw&mE|iiGiymGm`yg(Ho= zjo>0QgL2VZ<->>petF8&;SdTIXy#_AmJ>36a0#*#m~wavtk7 z4z~M{iapPo@FkTYSE2R&!NXqOvE&eaE47Q2+*7rcmR1*+`+lDlW-71_kL*d(A#7rD zH!}3_SZw9j%`KdHp8a)xl?)UwF{@NGSA03z;r8vKb~_+BVPLHcdo6pm%Mnc`;qs2eO3D3 z{t;Z#U~HOxdsX~uuhZLSdrq0QyyqABY;TqZQa4y9pFGS7YdmoiR47lJxN8&qEkYu1 z=5Fo-3-58Z-`kb)5chbeXRi9`W#1ywqdMY8xfBtJ$Ome-@>u8q1;zAn07m3n&*AgPe zKv-sD%S!q)eTKJMRPE0h?>wZc6~5$8`}nu|qCd-*l*)*uZlYWnwU;47QoqdZM;(KP z6uU`yg%9&#AN^_HwYNpyB*WG+%O>Fg5Oa%_ue53@kvzzK_B(uWpUC2|E#a4b-s@F% zT^#DE|Kex-V&7MHNuAQwhdMOnp3mpyKK)y|OgvsCq}W1(kHNM%TJ7b>^C5Z`O2(?1Oh0nkHu~&z2wV23 z#=K;AYm%LFA9RW5U?wJY%rQ$I!g^45XX-WQM!FGlJ99{GXK=jBYn(%JE_nEUc9GNP z7<<;z7bVp+-aCqIoF4=#+=TAnsq8$uTl!WK+tmOG+8<|An|)9(&)qiuj@_1|RW?Fe z_S&-Y;565IovRFLq)P0bnr0xk7=6^*3q9bDF=*^Hd*~iWxfuk8={ff3i9&3xX)t;u z7odOZfAe8G0<&v#C20322DI2nD3hS1RQ=bvL5waH{c`A0!e|X$+HuV9R>|Gl=?Q~6e%LTuef@tHJ`rwh`rU=S_exjn;Bi}WyHo=G7J=!wUY9bM(ZM} z$;qQ0>4c7CB80&oiz=+(0UvU&6MbrFxwYN76&orZT6`IO#+%|CY{p;$7;;k71xA1) zW6|qrz!?jujzWyrc{p;bqn>XyO>R4@z1z#pEZxf;PrmnmidkFgmI%qVp>@~7xUv?*sHa3{NI^5Vk zNh9BN+udgT)vxytdCsT+g#r>Ea#p@4?$Y7#^jvbRQT$9RvAPomLE>-7q+FN`IG>yp zhGsREbIwDmR3nRoUbgMz44EK~w3;&4dd{;p%m%_?S)%sorxC%Ni%+iFOkE2vcg0aQ z^UmyhZSmwYCfu*T`5&J)Hf#5h>99R8zImjd6ZVXU*VAprdMB?1Cqy%>W(Dyk9a&<7 zo3pcPEO02~>h(v~Sb5XV0)-Nud@$G2Q|+{PZFB2s^!iW5#D2%~plFjV!#>>GVuGue zr&t`ECXU(?A5+7zZG}Dm?AQB*gr1`>mfEK!lFYK_FF1 zr<`NV(sM&=)nRiDc*KpaMQ`wDl~WkAVf3X;5SP~Yaw<>M?Sk~&hRE}rjVbR!A1^>m z-~K61!RixHwXXD1NohOJ+3>bgiHS9bvt#aQVx-;Avv0sCF}E=%RJK$|*%pUbldM02 zx5*Hg_Z|98l`OHSl%|y5_zUO+#!I+LhQV@}bHNL%bBv`R_U!5i)oY__4s1kF;f8`}L{Hi|t$UOjHc=W-`ky45UF>@KEqtz{i+->P)q)Engmo}QT zntLwmoTsAkA#JWoOt@t3+wO~ApxxAi1Y~pxqst*3Dy{u)_Cq(@X!Hhr{8 z6P^&9e|=%0OOJOvll^wjrxEh?5#IY_zRSx!Fmf=EV9`w`tik?q=Hw}jo z%U^Q3?XX|$Xf~b0#AMR~{mw&hqk2vTfzRPQVbnSHbuRh%**>9t@hd|44N7PJJ@n1ty*xOS_)-qraCRKkiY`Vd$mSM|o9z`0umN z$9Xm{30yOja@QMRZAe^F>q1zpzm2>gv>EQz0Y!eug^{(C&p3Nh5B>Gu^=>pNB?C!ERN zsTd44R2(a7^z5Upct&{cVrx$2+a&GIKZ9sWfxGj_5&%8V#Nc|DhSjMz<= z!Q!50m~Dd6h^L-}(5$D04axVolla_bWR)iE`}m$oe3;uF@G>@!Hg`UVwmnHpv876% z?wq~FR2^FEa@sm`OCF)`wV9FEs){B=vCew(s^j!6%+8n)gpxS^rGDD#w}?uUPzs{o zsVVXx3#Cn!Rk(fbzy3|Db6kt#foIV`SUrTpfS!9ALE3L^vyTF{16vdAJ*E z4?3n97GUxMiOTjajSQn4EcK#Y3vnv{#z1r5_QIRB`s1+bQdW0PI?eZh&8&iwJ3(4( zs+>A?W-c&Xi^CN&Qr6vs(nuL;5wTATf}WZlwm3=9kE6trsgWd3fGKlF!3W%Q}v4&T``A9?3@ ze4Vr3@l&65P9`pQJ>x^aNd+74v(77@efLx|REC=I-F`FhXFlf_`O?0p@8!Gs=Dwpp zw8M@}dery+ZGM+;=Ue+MFnX35Y1^j0_3yZ*o2|;lotE|9FFW-2`8k27f6HH4Ptw== zHGZF8_Xs_I%&+77{C?i+bHZ#Jy6JD>Yalo5yL@dx+~yb)$RJ#5Z1W%Ub9@h9>C<3l zBF)^OmXI*xxACpfqyE0X0g3r(#iI*s6&Wo)%`m^k?{1DcN#FgFzlQmRYy6v!c~*Qp~5bIKS0&N`Bp^K%|-@b1xv88J+7noN-ut==JX z%ttMUgO?`btDjI-X~(_%Kgw_iSAyEol~uri)i|pYD}o&FvwzHP2HsYDyS|5N=~sNT z51(~O=<0nBGim-rUUn^^X9g=fO?oS7_6WbNGUvYt#_Yv^_7C|?pMVUQWz8F?0TVY? z5}A3$1}-P|0m!eNQkLeEs(P()dTjB-ecDg=agRbbNmdt_=fXgPd?3=~uCDQ4F&#fy zVf~7U-Xr!f<~P@~2Rvcf<95ib(V(-?|M+c@*k}6x1TujJqrnjAhPGt>lD(Altegoy zQ(V8$brUj-M(lRRZbry;R=5J!DFsUI*3#=$7Ou z^MqjZr^aVLgmiRUZ@M|Q3sY^Kc5*N;c(gb*UJ^1Og7R~Ju2CYEuL2C5`9!V^6btXn zodx}?7d_6*oNe=8B>P(2wnNS}>K$~ZkX!w}z0ktF3EU%e#>Xzy0K|>NgP_q^*G>7= za-wn{Wx7`DNF*PGqGw38`ryJO6^R(iI8zdZ-ypN6_?}arrelSqsXC@kQR?u?NUS=g z*`$@3#vT3Uh!3$a9E%ju7XmtS$hq(p+DwyXf9$0XPi`A?hb-iFRt=n_VFR44mv?`0{lH6x zGoJC@?_PGk=Ac)5hOvZuEBnmXo{&&!=mUEVBR=7z$1ay83064rhZaMZ_yTR^S(E{r zJo$|bFTM7AywjT`5JUR%ZvAq8=vgbJ?7a8k@7Wqo&EteZ>k|Wl*SRp82COltIT*tP zl9jKv`dHFI9n4qjgWjv3i$WrE2?2<>GxlDrW#{xgcF7i7Pxn*CLOw#-;-%M%74*}( zKKse3@}_H2#rmXLb9Ih&z;_X#hpPP_Z-Bz?)fruEdFHXJ?)LUGy!ET?{`RlB{O}lH z-T}7si(wb7?^b-%kLmeE#zi>pT_1~b5N7`qxw0bz3gF4+PZ&tvMCjSalfs#L9=z@E z{KS`gYu2AM;U1|t3(2TGoHDRMwYNKDKALRAJd}m9`zt-+E$=0|nG;@N=AUS_*LV;3 z-S7F584n(BLxxwKc~R=zTJo=c!P?YD%(=!qcCL(Y(NEamauvp*}qCKBZ22yHT~kdB3|*AT08hqV+?3J z+7H+8XA<>2M_&YGlQ{#Gj=Y0Q`l`&zS@=jMwh`=|u*3@H9zjtK(A!wWUV}$V?42f z@lIyPe+?WU_Ib~B7Gf3|WMaCJ-EY)!$8W&(@!s7IGPyuqiq9J6L!g8PN$IkUTrc#8 zgpsM}2o*NC!li)lH;Ej_TTBsT!pFzufp|u5EKuZo^wAH`Gp0lQ;)GXq5iksCR_;Z{ z6R&Xvv+n6Qh;2?ilmsav2On3Af8Zd5YeSORLh{ET4e*jy&X-eoN0KHpLu*|^F;~mq zX#8`%NZzrzjCjaGHb!?Vjyj-H53+Y%Tcpz=uQK7=TVcm0QA}q-ZFoAk2I$2J?&0~n z*h2;xC>9Y+I4IM~=2Z@RJ!d7Lvey?}E%}y-_r^5SNoLo7395%^_JR2oJwXhZ1{38{ zL9v5YfchQ#U#5k4>_K(qz|O6Zz$(&syLCyP`C`vi^hCmPl~7>9!iJ(z>R`HVBYjad z)D3%}oGZB*PWU&Cde&gNnsE0MwDw&)++tI$!Nvoxn{u*A8AvQSL)v6eI#x(o*N7pS ziQX{Tr5a1H#YDobV3$G$BEAVL&n0y$=S(-$%(muI@cv)c{LEVyhLX9LUCw27RA zG=T18jB4lLT=0GauS}Z^x;mkRD-yGPjgZ}D;#&W)!N|+ECJYGFxwXqxqx`Yjmm9sk z?&Hrw2WvZB^;_`E77zDLNOX6}4bsNp!ujMqc1;f69*sx0;QOor4tCF+jJnMRxCq$P zbh|AtWwQ`emSw~{jIzyVkm^>fMZoRE6c`}{cit6O1@0>T-RpGWPpC!VBoMhqU>^S| z^*gMAaYYw;G-@gO2#~%O$Xyt*#CvM13I`}go3v8UE#3EXGTV)o`uOs~VD`@-H!P1J z1UfAI9r0>6P*MM6+LB(Ml)quNIEEjNqViV$K~iNVRrFZbx@XOy&3&QgzmrVsNO z-p~TVr+fRZ#F?ZB(&f5!OY)Z@Jl=X3n2=I+SMftW;kKnNrN7E&f$Y#+_wV?TKj$Y> z7O}8CsBN?LWu*10uZE}p)jybdam8!tRjTdiMg@Av&sZ#%rdx)}?9+aFI4vLPGko+9 zfiuD!56>PtN2HGc-eAw9o{((x7MuTEuj#-zA+&H}-{XPQxrd%Ns~0rnXM5Mr{7E*Y z;ibHN+<)6bQu`Tfn0-sR#cM zZ)99HO)8tKN+Y(2X3pMezB^^7bOAI0Im=1}-botgp58a%nE~0*ovr@Gha!!=s#YJ_ zs@RQvNEbbvzklCH>)5Inq^~~ZT=dG)6)P&ylA6h$SFKMRw*6M424Cv3>CRGpjyE$S zdOxy_ZKsSCE2W$mS0_Iz@vsqn&faTaIc13lF|x-xDL|}`wfhth=ZBbUU3FY#8|Um$ z6B%^#Y|PupVpK<8XRMYqe(Ge<&yasH6B6S^qdHa8iay0%7;7Si~EbWLMbA43QDpE|4}RspdE#ZH%HATf*bUpZ|JxofCuK zoID2DmUr7so8zKQ7fO-ja>bM!5*g+hu8x)kg(LV$7tgmeWAQae@{FSa3ZPGlk}|=x zlw9~_iQ**maSP`@m|$4hn9AWI-bo@lb(qo_UDF5ynfk!l|9rgt8`!eUE zHjcaf*FWh&dS%{x%3#jadz6R?^K`*E+ox8JD(=JYi97=s!H2{z|WQ?^wc;4AeQD$ky&PM zy<8OjY^RWeO&9j`DM3n;juf(^%j~C~y6a(f1vr7S_iC{I{GDL+0OL=g(7U5b4#%F` zxBl5ry-G9gN(lrRMw_J|miTGdDV4hm1Ol*G?q{p8! zMMgKnA~x6B)YZzs;fps9jEW6ExxqI@+E}P!^!gf$EdAxP4l|HS-CKL^=j>H`n=Bxh z9Ele^b0gK993Kc(zX>@o2b9&kS(fkFroppfl|#6VXGh*h`zzk$Ef%(kTp`>tyViX2Fj*E6I(cg;_2P9gHQLa$DM3H`iE*O^`OGvlui4Y10=M15GWNz$?e~@1}4w z7jcUbSlm`@|#k{aGjQw!Gr0_V`Vy_$n{YOzYU!owK~clV5rF zd+v~@yviC$h3XHjgG4Y5Mw)%=u(x@Xi+Yg7Y^o`+@;g!DS*upl@W#|Wx*crUBME0@ zJmXUYFrGz8BR+M2+6zQb0vI#txA^6yob^lzg^a6+_Xu77*{@5DzT=K7e1mdi(Ot}Lt4Bu-Sb5X5!zkoEYL3M5e)vJf z$O(P^9y+NcR~jWLma_I{??MY45-_crM{#M)y*DZq{FuqIQlLC7u1G?tP}ka+{nI{s z;{-xuut`YV>(Jf<$^>hOV8#SYIuu)=pj7NiO>V)KOM>rrV;GPul2PHZC#~_EJ<$yw z!d>)GRm%K%UGF)KvqoR5t-Sm$D{PSE*kn^@nv+>!R$L-$NmZg(or6}w$t#b!6D;fH z+f95XX}276HqG|YA;gS-%ysJM_rU*2a|!POFpQ^DPwv*|e^)%Q@K zQ>pZ6Ayb(;vPGAcCXt}k1r~Ae>%OF;wr)O$RIy3MQX~qnj8d}TlW3*pz0XO6-q)JW znVTR(VrjXHAw&-f8S?}KKEys~3eMaUJ?&)(0%7)@9!R3W!C6L%bmrD|u;)@+XFJbJ z>3{Y+4h0`jbXbH{XD23@88#AWBln#Qf#%a9!y6Wv6;+(%Cmcib6mUuN&It`J8jseQ z2?T^JzPs~9l>>3(gOkXmy1|A)OTgCv@6}ZU&qb1SI41K%gYl;PjE@_Cdb1Jd7C2x8 zv`dDl+w4SmdaDv?i;R!QJY!9tS`2Nsx+1kfj#0+g3`<)@tvPpZyM^6ltTO$$Y{3aI zar9n#LZ1Wx_-070yc5~VMBz}D#g+<9CW78&j3tm(eF{((2>m%ll>%%L~Y>MGP^@kvqxy6Ms2 z@|!(zqm9w$sPyb4jZs&JH#uSa z{=q$?z!*?u0OP$Pk-=&-0ggCwF_k_Kyx7vybXw6{f&t~6(E>-3nE;LQT~^91PkO$d z79-t#xZ&PgJi>ddP1Xs4j@2~0lrSqh2x-D9(K;Y_6<0aKfJ0Pd?Gb>zWoKqXbtH2B z#%?R4I|CtTs29(^jF?*k{_-E(TE%&pvtoG(H6in{-77B; z{q2WtKgeIQkwY*Ody2(V zOioN+@S{WV#b##RaE8WGi#rU$gmPO7`+$@I9;Y&(aT)C51are&Jl8yK&G*YYH>Dcw zj4kKvEe}Vnho5UoLlM8hq4zq3pL@1B$jzp!OxyYfJtf;PrP6tmHKpd^`x-KP&DPti z6Dee~*3oB|@`vbcSo9)kIy1tZi=n1Pq#L5#gy&!;YRXwdv?)eI?=9GhMs#qN4>8;` zTV`O7R{;Tr2_{PEJlb@|aCKow&NM51j4*lu#|9G}{E2E4ZX?!n!5PZCkFu~Jcn5L@ zf>@G(e2g>n>T|5f4j8F5n6sf*Qe=8;cLw@Tr9-$-sCBg(+F9Q)$)9D8| z0ub#*x{&k65u-4npouy22eME&2Y z!H->a``eTsQnpFVf}`Kya8x)>6?CR;zaPPIGRq(tBDg@^ehr^dJ9w@sG{>mS&_+!c zdKlnr6tXLPjS6yAFeCtTd2g$V6CKQkOR=UmP+K5SbigvooIZr7N4{f@?>3BAtL!F z->s_O%BeDMBp#dizMnq1L^fegEkZlK==8w236v|-4d$wN5@BkP=Lz60*7WmPxJg5n zL{~?~0Cy6v99IO7856)7T0mf}?TJXAsdzuzON#42R8XyDmH2}o$jUE21T z2M0XsfcQ*R^>64G%klw@I1_vFbX$XlRjo=X+s6WOm($Hfi zK^o0|zF~IMRg3I4RB&AuzHrWODEMR}^@pl%FbvrX*8FceX1m;R&op#LAX4RE45rs%2`{YPYAkws8aAxoS7AY^h6u2&Pd zp@HB-t%U9d+s9_IkR&xnFHu_s)!x-yorDsR-CM7N1mUseJ&ul;^Hlf>RN>c^NbDA` zIq!p{8gLep*i3yug0h6h*CQzu4~IoWr-KI4l5Tilbo71c!7~45rpUSh?ac~ey}|GM zwTe}6jG^tpIG~FG4zCobS%tZUT#jRFatT~0b_1W~>4Ylyja4$fE63=!NamSjeyE}c zXVnfZS-?H`_xHPs&HEw!>>Ms%p>+3GGL7l8URLr{d7= z(t$$n05|JzldP|28PF!WgsZDd@F+122-<@?VWq39v#RAra0om-i*}^fmYWexy7c|& zvu4ZW{m%2%gZ{sNw<_$dp)^I3iZ!EDxK+TnJ$h{tu>u;aT=j^(qV&J^f zS&vqx6R~1LrOoV(M+8zFubrxn*d}Tqt6dm%B=pXsG{9!QnNjbX8zQ`JLp>*|S~zS7 zdB~{v1LbVh5J;Sk3qVTT%@Wb%H#|kC|MyIGBczf7_qNI>p}n5?5+;aSFWvfH}Nl6 zZEH0=g4Ot1!tcOR1N?E(rAUaG_1{2t=tGgr38>550#l?Gj}VDPA%mJ%44RPo_#$}6 zD?UKfMclI#P4IL&YfJU&1VZrja=J4G0{C82TdqIFK=aD`SP^U(I?dq?^+Vv*;&uMtkPaOJO!+P2u7Y9oJ1K^fMO07up)SBpQ{> zrqRn(9?8%Ge?{62p}5?8wg9Z9H}<_bY}yUYAIC#7lBhlJHTb`Rtya->;<<0CqTr*g z=>ASOJ*%o5h~X4@3__ruM^u$eD#^!8!062uP3dy`sRM%xH#C4|LALN|jY4ilzUXl@Mf>y{YpqjPH)j(bWNt7^pQXFA&eS8jq zs63#ucZ;i0sL>dhUcC+Rni|ef6kjOsT!|UInAS5Nt^dp4cg9U`ED}kj1JQEKvQbXc zLDEElxT_2De!8y30tR5OqiiH2qq=Rc5Yu!|P93cVfaI{BsbmR%WFta4f{zz7P9uh$ zj(`hx(Yf={Z9NInKiG_T5l85e-=tX`o))-rT@zkm8Bt?Y`exCDV;lyh}QP*rU5wf%sO7aPtknrWnEs`8kauNdCQpU_4%?Y7jv`TM5UTX1trjz7z;dw$PpZOsxizearIj5gc zgNh(0bNr)&(tv-ZR5%^8P}5^+_*cD@oC@Wpt5#x*RC8QF@?^4)F(&?$kmKKWRi5nG z|M?qJKh9fBfDBz{=ZuN&j(KkWYA}xK$cQ7uR}AX=YU=e+(4)Qway;w6>>wjr{x~wD=@QpeEA_LECO;y+(`DO8U=frg@RDK}B3wkFaSxdkxlu4i<3dgfV6p!`% zhde?5AM=U^s}6C7O%Z8;{>IFK425>csoc0XvQ%<%w69Q36_79K|D)Exm34C zCxoDBmB!c@_GcN1OyZDQo`A$0OC^b%+VQP;c?pyjz*VPlq-1#uio!a_Sy9ph=b$IM zyyQHeZ;5FT>xt3|Pdm#4SuFEJ6;C}Zn>x`?qx8|Z9y6&lK~<947DXbR{bc&37&8Gx zY+EczNP?3{A?YZEQ!pTS_^wz|__MxChN|5dpX>sIEr@ppdfqMFG7dD_I1goPkPd0r zDZ0E3a`}^c2{MiIqnQfEfR&_+T4N9}pD+b&Io?W5h~QEWX%_aiKCTq-klxE7`R>8v*~1s8h)>Sq0{v-Y^Ct#Ye9 zAHho`leFfA)}f3r@!w(_h%V1^kt^WnxL@^hm{W#dr83pbEu+07>@9Mz2vm&`sc7^e z91SWI3iM74gHv!n(SQ8MySRi%8jP?Gnbs&)H$l_XCG>-aJyfIwOw=Vs%vYqmVd>{A z6btK8P(?;Bl|FUIF%$4oy8s^+uo`GWYYo(mfS?_oQ|4Q+!LT)h*|Nbn3LC?IYO{45 zD)1?sx=8bX(%5sr6HIWH`p2Q=58j@2*kpS$S)2}htR+S0B$+3dXmg+0Un-S&#?ycO z$N#!giY&iis&p!88B2j~qGB$NJlY(hltw2q6^#%+rlfUInr!L%pu?X;NP7o)KJwU@DR#DsNil^~q3Gd(` zJ!$2fJn1--e})zaL03LRc%rf^eKCTb$1rf^eo9fsQ39 zOG|_v06I!>qPDEb!v5xWKAUrvk}CKSeOI61+nYTwWT1h}Speu{qBkWqu)*PZ38z*K$`~`mwz}fGQ70^9nXPruqU59}oIH8OyRNFGDQ#Fvgu&Sti5hZi*KryK z7V+VIIkFM74{)WAETo3oWb9@&kB`U6g7(T}IkeFOsH$Yv5_DA(%ZWCJ=_^f@7qPT}@zqk`4=9U*p2`_c zzf$5QTJoY>$mS0T-nbtq+QAhMpKK*~Shu{4L}^{8RtmYh*e-F?A_eAZQ2|$qP!N ztvG!c$UY~X&`<(i$|+%Jsapad>D6cp&pY$Oyn_^a$5FidmN`j)Ar>>fqoSy_(O_t~%&c6DH0@$3 zVu%EHrBUFBW}zRVL(RF(d^Cy4HiR6S#42Yf{t5eLH$fEzfQqlg87FgT8DTeb5%ld& z!Orutoh~JEP*!1hm$gyg!4dJ+*-R|G2nut3jV+R)>FGj=C<};nWP;45MJDjvj`$_y zfBb7MxQ~nGX_EVxU|xQjr+=DhKK3#nzsdhIZ}+yz1iwr$Z&Uokzsw7RvYH6HoRv3AUD!P8)Ib)6xUSX3yE$N(Vfq3WSnTe@~ zeyUqu`W6k*pbUDcF1zR_H`gewmst9tFR;wK{^aGVOWru=DON<)m2;0Ciu7WXj8jQv zjyE#v5?0vaR-676rXk%$qqtQ{>+lXAxOKq?^OL`%{0DEf0L57+33$tf?A(Pdf$l4Z zViN@9=VpYHYRJaZAlq3CmdY52by2wEihyg1d3fPKqjuF!Hfy#24P{Qw>N;E*K|+@@ zHoG7xmxTUybt*yDJ^xO0^YotK$>ZbaW6!lAd;HY<%zODpPRE%vX>ktPjuTimz}O9| zyH?-4Z|ul)7Oa-6io$9k&U*h3zV}4B{T$lhZ)Ll6luHJ))P{$kHcSVzGzcaXFF|(` z468W-p<8Z{o2rkDr`=Y{OTll2Z{$73|^z4 zfx(hsxzzwuE2U9?nzbKAk3$t)-Esx-jBU;$nXs0?Mr6=#V&MgJ=$c?b08mTb?RO@t zwT)b3Qe7>m8K~>D2cjLWx?p*!I=T`TKlVxK9Box?tV@Q~+o2R;i?k8TF5AoZ&xlx^ zq4E1uxrO}u7rit&#f5sCM0iGesvEegTQ!{+t5IVCo=qGljrG~b5q5_A93*561<6s@ z-+`XP^Ml%jU~IF^)Z0OvQNk%CMK&n!l@u|HWLTpa08)$zXpL6Vf;D_EOiZ$hiaY~5 z+;Gzl0zufKm8QupB7;7r0+DYU$ko<6n_wmHqZG<)d$Er)-#B}v7jW=E2b{eFUCi-5 z{r%rL6;M={>PDkfQVp4<651PlFigq73J--&(G!57T=r?Dk}m4hBgni+CM8O}Oezrq z?A6poC(~6pYdDiu4=O+z!;GZ!eV+nXAe7LBULW#lMdy@r&vlj*NeyDVKzn+Xt8{aT z>$ec|H8O%k3$R@(#`H7%Y?48z7ww$o+JSmBgL=4_Xj*TOYiAX$WV*D=4;&}nL<2$q z^BL71c#Hf!{}gBuP6cl0ZX!NuNJ9iVi3$#x010yfZPgjsh1P3ti&^C9ef>SE4nzG) z4;s#&&0?I40rCU783Si_P6LmDYt+Q1`!P}6fTbQbWhDBeL`)mDwN9SPs&SZ+3SiID z>RE51I8s9bK-`S;)ZepR%sI^T@QNBCuUu>TV?>d{jYVEb&#Hz-=0hK=lZMm$2-8f5 z=K}mzC5w)hT)X;2&_aH}xH@^OhnwGH<5dVegr`^a_fil^>&sv8Ez~j>IVj30g&0&$ z1v!6m!+x&>kYSvSEvt%P`cQq<^e~~bSprS9qjFY`U)~?f&AV70mQ6~DgNH~GKJ%bi0eFw@Uby@OOPF_Dj3-$2ncm|*%vT0sIOuoL z$D_+}dJcbQB0tgem@_}H&fp&vNds6)(kmjDk>x>9t^(k_)m*pi>Iu5YuKq?Mgdt@% z32hkp;8+2dQ*&j)L8d)0C!)C-OfY**K1Sx4Ogh+#`6w3h7(M&CV90|V3NK)0P-5)a zy<^Q8&niZ_GsF-==a^~7&}}_?zHmyMxzOMFReEsB;#(E;;Sg6avpr83AbN9Zbd{8M ztSf>BHGO^^kenDI|6mgHHwIfJ_#MwEOGTn*jG=Zeo+wY&Za#CCK}%uXkuf}Y*;phz zElm3gm+kLQUy=f6zPe4=-kV}{Q|*lz^ELI>sh`}fKl-;;B2OPJfx4d(h|%VxxF9G1F7J2eEy@Q{8A>ItlChZypS2{ z@mT^f>)A8lU?We?JLDD^H6j7<4k4{U7AY8{(k04?=Ge(Y`rC=Efi;}?3OQ7@7Y4!9 z<1RU_uvv3cuHopSA)A2N7s4xm6ba{A(9&xz1w(YoCLD*6<%}sLTvbFno-VcySW)O_ za@LIWkOloRSnlLRGD;`+u5Vy$byMeaK*SC2EmzXomRpw$7U*b2U=!|~-TCLiVc`2i zD}e?87;RF~g60S&;dk{IfDvb6%Atllp*9jDE;WZ`44f-O^j<-5(=4N>UE4DY+5FLsw zcWsqA{d6|CEp#dzWdys0Q4Sx8#r1-Hj#<00#wObY7eQ#nppZcqjUwM%*V-}&#HdGG z7GjZw0XCSc|E(a25k=4{6*R?B0Z$$Lk?AB{fij!{;fXXbC7 z9!19M&@xIfqV033wNsx}W-2+6u9Z_LiM)zZ5|b5_jiZd3fB(B@Qw>!bf?p@uNwV`m z#ES1#AmGPITktMaxEnmkZp9XvhKTG)xA7z7Dx<2fg(UMEZ@)dr9#;p>S*n;C$kU14Rh$d0I4$lICGxA@f#r}MKXbB3KbB* zrNTB!{v}rvqymo?vPsVVI86!DV5S1sE~I2J*<+hi$@o}2sX(XoLEV4^62U9;cYp#R zA@L*xE|N~}QN9y#O$J`gn|ZhM!NR=VRZp}BD9+dTXu-{DbsnWtMd#)w(<`pkE}^QOTtKkH+qmrD%= z`U&VE$ofuh`sH^fU3Cz$p|oQpO!@}&1u>7B3dMys!ZdoK^va9?D$EaD7Oshl*FH3o zGVULQ`o(|y2V(g5wMQs!*R|7^_i}W zoY`Vx$D~6P{Rn&3*Isj%uW`qheXECR)y<}#vd+hD>)PGF)Wbdf`>ON4W(($)!#vZ+ zzWbX*tdaOUvy4}Ht<*w$ui;nB+l@QloPNGNN6G=Wr1Hv- zd|iH<{k302a{Cs+4@)N{QP8#u1|q`qU^pp!lb35^gCpVhnoeeuo5Iih^1=CY{wsdQ zYmYsyrJw$5t=(6?rr=$B#V;Mjg0Gst#%q-x+(+*ES&#Y&AJOGnclk0;|7G9h>!dO_ zIov0Hc3-W(_mtbcRd!x(+2`}>CV1cc%2y}Z*@SCdLp{{)VMa0V=jzu?*AP=)=9Ruy z`L;o_&$4DZmw9IplrR8xteUK~6@rFI;;;YwfaIL+rKSy+=f+!K^YW_?K(!YGzHJ9# zNp*PG;nUrQBw^N)R%_p2O5^SN^|im+qfDYr!XrKGZ7>z4#6YX`5+S$?`Kih_7W1G{4q+%6&#k@}oZRkpn~dX|HzowLj~NlomKg zqr^b6CZ~ZSF5+u!PbeFGI{H(T(?YYHn}CcK%ijGbg!;ul{qwumALKS!JxwuBdD|qf z`PoBE{dQ5z@z|sOupauBbMBdC*K0mkOF!uMT-U>r&690xU*!k;rN(-UCzKG?)g%32 zUnR^s9{n>u>z;Wvx&3GFS?0TZED_V|+I#P@o+JCO`oqB~w({-}P?KbRi@aBR0o2rpB16(98h?n+51-5np~ zrtuI?GdyNoMPnk!wDo0+B$%D?N;mEb6N6Nw^lfD{S?KCX>;WuLg- zm1k-*TmkQO!aa?L$MBAJS><+JREP*nQS47Qwah>J&FObHAl{}4yUmb4!u4)K5R9e&T0;2)#?T2I%9Hbod&rX{&Fk#FP&@K>qo835t(IX)Che>_v< z)w1BKAih_EfA(**xYl^?t%|-8&!?~Om3~}Lap|Hi^=z0)mP`FTKl#V;Y+u*qKbG6P z)7N+t+B+9($7&W_GvR`-eP4Hf%bR(%dPc&fx45o7n=UDet>!8vp_muLyskbz_Djw5 zV}H%hUVG2JSC7}Maqsm=KdPtw=+_=C#joo&v-ErobQ4T0t4_KPU%3*w_{-6leatG=IKh_ z1AzQq?|QYCf_|Dyzs=J&-_|Q*s}$*S8yJ@TrPc5JOnm1-%4LyV^&qu*aj8ni`r<76zT&=>6n4i! zM=0we%dS_I5HmB~L9NLOLLRcm)yU4M^IoQz_-Y5R z&Q3R{;zzc?=vpXdWM>$xOI?WnGc$Pus;oLZTokSpwk*?CP{a!Fg!G61E2gLE*8~tA z3SPnA)19Ir+VwJ_-S9_A&I{In2&hNC8GF{ujhUJQXlcjAMKl0Qv~r&U+N1W zo+L5w7Fo^qkze8|J|Mj0m)Nl0(noxiul6wpr2~!dBgNE^C!qE_0VK%@{+=rBt(L|WFcW1i~p=7Uu;Gagk?V^_wW~fsB+sY zJ}r?Uw?)&(0Dy3YJ8t}66sz>o{B19^N@lz3=x(pLfh>~+i4-MX@rH+phbWhVt0vJ* zCZYz6V!Fb1Pj}l#crb9IcT?_s<(nu!yU}&9N9RX)x~IQGCGsn?XtkV5`RV=$^^1T0 zTlztQv3^|X8oP97y~?|e+r9I9h3@>jzJ|Y2d*+v{a_v6dN*n24++_{pW!2C4-t8)4 z>Ud5wE?E7fVwYpwe4dqdU4sVYaJ$T1UyA_j=hgCS?Vabf@_aa2LJ_tXsz;Er^yf7c z4fB_Ot}$TrJ0(@0?m(CF?RM+wb~iDoCAAqJIfsXxa5ehmnF|5LhRae*+eh7DGUFRL zJ>rZtUAdWg$t~<%AaOHd&3j)r>V{e<{oBAb9&;k-clbMOWhDjTxbEw<<;EiHfauLv zf7eR8CFPV-9<2>-PQwJO2u@^ED3igj>nG^;svL!l*7RB86(5bSEyrdrdnb~aTkez93liD{f50b=Tn#U_@ZyOQ-5M?3Pb_NhByfyYWn)pq#qa+WQ)3@r`eNi$ULA zR`XEB1zq$*ukg~(`J8KnIS0hsdok5)u-KqHE=ool(03;6I^B)nbyNs}5dj|uwm@Fl zH47J^VaM@G2%16?T+W~Ug`ZC`rFk4l9eNkWuS*OQ?{fK(_Aw)o5L^7##^^03ZjJ^~ zJzSxo_M3K*AL&V-C?i+un2RmL3j?^T&a#&qCa%2wH;rtoxB5a(OuXq1+mZ4OCC6W3 zU7z`dYXG8z{;0QB5|YjxYs~)klh_ZOQ5>z25Qs1R!NFO`_4~TskO%1PhvgQ|tAiRa z)6`@eUCG33LnSe;ABFnGfA#w*HE6ZTZabqdH6r`&sx&3XLPx&AKINu@^#O5f7G2*Y5G?0@f>eV{utQA59aFc z^`1YBd%f#Tje|4S-4WGI_J`6JEtAV)vd1#ynH!h@@X+HD4oKc@D7i&s*2Hk6zjjc# zrt`oT@}MzkwjcZ#eJ=kjUjml_*-}cOU$*m_FeYd?cJ~!uLyWK_-kNS_8HvPz@Pqw9 zN@RZNv)l*@qdai=DF^wKG>=x8sr^tsGtNp5?feFwA0D~__4h-19RLrU1`YIje8XO< z?C#6H2^XAXdJ?X{F!U2YMD4J!;26TJhex#Km1)&@{vHH5MpTYJP8k`6_p?>k-{jOfBs^RZ^H%>MF0s`B2 zse7!8vDu}&UgWl+b;K|A3~AVHOj4qN;_YgZYXaumiuz2`&JJjFLD174?%Vg z5rJwckI`m8b{MTgRA%Dp3F&4VBazqrSodA?Xffa&@hSFFX=08VM_NF6Nvnnul)S!xI|(Io3_6y%AcRI(-{?irZI-{z8voyOvSsUr@G&UxQA z>EI*NG~pd^IhkPL)8?Kgu!_n+V}`M>`$kGLM!syQN>s6a8r-!1^P>te#d`+i`x8uo zg4WRP6GDe*hj(@{t0kmah;X#=O*CnXAIL;teh?;!X{lcw2#7@EXmp6#`36)IZ2_O4c}^xnH+t?Ql5coea^QPbC9izT%iLlqF6K;E>GE=S z#tD6kRRM0_Vj5Ijs|9Ap&_$m;r6k&iNEwQ)YU;S*#e| zs+gKMEF|X1DM5EYo_}|OLx5KL#~59g88_UKpU^w^ zGcjdHQW(?K*wzs>f#W0>L(q582i<(^Kmf+_XH;@}igo^fRdK9kH z?V8g*jR+C+zw`6bWrtgCq(b}}OPG=;D9HMnLX7yz4mocSVOvcIg!K6C0vC;8(sA05 zd0j(mDjO?d9xezzOGD`mbRxQnnZ6uWoZu#-@@P}#waV8dqM4kVc6 z)1{PXG$b zsBDp%SmAU>&5L~H7)t4}1Pn)FjHgh)_+S2*RHf*9i;q@a5UtG|5Y@XKZZMv`WE==9 z0N$Lv>WvvxU-8aM4&4aLAf_7-f^IT5I!?X-8y=#Wr9n6AsA%8ou2Z<%kkdmjzuYZH z!_^Fev_@YqDCh3R#26oKh6j~Hy6Xxcsg_`XQsq<&dRYd&9^g63W_JfGKxTVZt+q<7 zyKy|}Epr0_1~3ZMoe83bbeaFD^E}T%*+d)7fBshvT5g704t=#+2jtvvmzjMdA^19a+0=9VeoIOAi2S@=HzQ zG5g4d8bddA%cYC-n5ajPY^ z?w(R;t0ixB8L#Sz7Vw0WTrSz$FM}pK;PMGG1PqneR$%p8_hlJWK>yMdE*=Qb? z>dzc;_3fs;`L9`axoo=Ib`t|gFTgy&j2|w^C8x{7va~a&Y5LX5x7Eog+P$O6fThV}kJ@K^+HDRWV&PDv6{)F6e zxZ6D3gUk!D>LXujn}@nBO0J%+debu%mmE2#Af|p~lIyPaD384K!#zq&CCzjZlbm-t zVnpX-P!Yk9S{gdm)DIO16O0;#`R`ClACnBO+15r&Um`?`_NIGqssSXlu6IC&x#^MEeHG6d5(+-HU0+W&AyeX8KZm`&%v|7{#ur(mp;u3Zu*lFgs zbond^8k@RP*b5;|3n0V_>$L=nh`?7{rng-DU(2gJ#gez2@`|iw+dgB=2~VlK-BUls zLJ=uA$dQ5Y%v)UgnRdC)GkfRQ9{F}p^@w-A>{D#9<;=T1+oL?&GvD=D?)EIa0i*Qt zOjqhRm+;G8fM6S#a!nqm)CVhRg&dtH`~Ug_;Yrdfq0QpWR*?L*tZr+suf~LBwOoB^ zYDzD=D)zR^-l~^SiozSWBwv;fm)o^80IjPwW;5uL>*a1Wam3}_ zrY2Ev*WK2?1=u4*3-cyRljxwCSejZ~PyT zbC~h~nrqA~OgXHb372##GH6fU(bp34@(O9~PIu`pxOT27Gst zb_#pg;j}=jm<($P_hyv;?Y+9HOt%A^Ylk{N&uvP%?#AT;CoNMEiH%F(eNL?%h zbt{OxBtLQn`zb~tf9qF?n9`1ip08axE3DUiHYg^n&@f3g1W%e-1aGj_8RQ~-v^-z2 zH(KJ`04#;M3jdIGK{_Ip0E-^mJliCMSS>Ju5G&)H`PHB4v)lwI__(DZ2PvhYFh-_i4X|Be1Sq(i$9HHyZ^WLW_)4t42n8w$ZA1iUJdJH!3g!B`fIX^9m6ensT5vTKC?xIrnw%oMWb+6-yxa zHSPOeY75e}r@F-6a*JDRdAFO?%o~>;EDE*dZEm^ghBf@@x*4zqxcivwA&B~%9ASe@(Z;dgebfUNtIfAk=Mg_Yo1N-r?2}5qyw;Hr{GGkrJ zp%nrLfMx4u(?Rp6Ttd=0R#K)l2xD4%PBuJ(GBnqSpmS24ZqR{=Li)xDA(V9r9I`Tp zZo=krq(((1CW+-Pz3sa2b|UR$B&n;U8F$y#in>*$udk!JAwcArx=k{*h2ODlaUTfj zC5K6+2b7P%P%r=mlTaG ziGs-Y1IqJ?hA1VwPzQ$%yyYCNAQ?hC#7oSGihvmgwHzMYfdv|c`J#e~Pzi#zp!#PJ zl&l_FiXqCeUcy-&qWxsUlE3{r1{SGc6?myxi6B86#TsKk1o0>Q%kp5XX*l3=B$&hs zuN~`!=Jdj>)M>hjMD}vz!Qrgu$$cky0ayb~p3QEFfrv3O;)aXc9qp3^O-zw0h(w6SVF%~!bO zEQKT}_@!!`4A6v?B~4q(L<~ylf!U_`q-P9tcf2N@aN1J+M4VX5bUg#hAPlpHe#BTk zwX=Y7{j@?t=8jvwyIM>oBpzZ=qH#*=2P#M!8L&w?Cadats&UYGVH`%vD&|FUBIwg$ z{MSv_8AgEm18v{q#32Hf0KuB7H-W>ejLT1%OqM)sQmLOl4x4QL{ ztw=t_TRh~GmWh1Wmw38cKhmRKZjnrDpW+#uHk1M?A!{*K?zQh1pW%F-hkeU!^Gf?E zANDEUGT&u+?PLImtG&kXNs}*a6x3;oTTLQGpZ+D}RwHsUwQp?pCK#(;`UcZzX$|Uf z=G^iR&zj_`9rhAq&#Rjr>BM3`Ep!;)T>?v z8VNgnKCbD)|C`%u|!-T1Zugrsm)6&m?qh|f?p~R~oB0gE(g)L(c0lFI+ z2vpEjCY6aKR9kdHRt}q>$E2dVLiSYvh9adi zx@Q2}MIkvlmC$)CET>tg4EjI7GY;<(84ipso9|f%VLF@}*FlG; zLWf#%*Pz)MtYf<8q6|}R*+S6nH;=QIQ8c1*07Qk&UWCHgTt^E;hdcobO%+#*C$qsE z$2NBp*aPWBfLST}h()H>wFxUJM*I@e@ky3YTIojz8&yEj$^;3btj{({H0Tr>U@3CQ z)O#nXObPwdd^q`^{V5LPMO8!WQkKn<9M&c9M~1l~LjHs-LM=K3;El?|rVw8i^3s)q zrJ)kGLRSf1U{i@&D_iw|>5d{f48d6(c%g22m&!G8LZ5%N5^aQ`lzYliVne6P^^drY zdb_Kw-t;oJ+YV(B0^}R{0WoODc~1lCjzeyL1Aqx4olFDNJ#Xd-S8jy8R6F zPogJQ9*1zje8jc)*=BR2)(#s;CO&&S#iNdBFLzsOL*(r=*|*iU+kR1WoTR($rd(=s zsbxnbt#)w9x80|{>xMs=B6BTdt--9HL^0t=E}r8|TT(Ei`&}fuap^t18V^)woGHRbYdqHCxe$F|ASx zxFGXOPqJE*bd9|ruB_(PX$1md;;sHS9itm<1B-{eVVj$cO^;!p03&+a6>iJ#7nLqh z0kI5v=N(YopV*3E;w_iG#mOd0GhiydV(<=!Ivxx;<+dr1XkgQd%h2P$)Fk*aKmi43 z?}CAzZkF3HNjU$)WuYdFR~q;+)y)vnEqSS7Y_MQjz!@$Nt>BDDi{2oe%2w02V(v#!gKj6I%xn09x9KN$o>&ahP|0Pz z4Wq-rnoeH5ZWhi~hA(0VC~}708eqk_RLyE zxFZ==LbpSIWah^T&bqCw5Mc6~$JHc}BS2emjo7)9hh~g8wMq$r%mbH!??piEHd#y7 zTX4`<3+fz8G=vT&CA2ZYS|BmZqirFR+vo2PZv0NPB$@GBC=$%WLkV;TlRXdbVKxw7 z4`PS)8`6Ub_LP|<{E@&&0tkBwTy)kVvfg-d+=-tjxY!mRFI0)8*OLlFtb_@`qZ9uV zup$&Ff#@0}Kh*T24=4Y|uV%%vazfc#>xp?x%PAE}JYg|C+eAl1AZ+5g$|`{s#Y7qz z$1_+pV9BJ?rGbvmC+n>BlX`tjByi3_ROKA&5x_RnO%sK%=0CxiHIP+i4xKcIM8G8D zFQ2S+n*BY%*gpN1z)oij**u0QNm~G|NMcCntz;nS0+A`5Wr%i2o*?>iq@yb>o2bO9 zrJoiat^ejuyFSZ1-`2sf^10smDPQ7~ANf@u<&~fHRbFY6#!z4RMs?m-Ys0r5x^-On zHaf|`FN7iTBkw=t;6kh~r$B|vB%V0%5uRAX8L#?#KIYrr=Ec*OVs>`959!?g89(9e zU$f3WBA*ck6Wc#!m(f2e%G5msr+VvpgnW%B1+|7KC2X_HX z+rWtPiEsOKk9p4pm7bwip?rFT_qgYSAvp1NY;*w%tLAt_T~mpJBqxCem$ZmXg%YEk zi}{mG;*)*8ODga5C;|6XcJ)(V)u~*bz~ZEHBY#|1DZlDFzr~V{Zg(B&5qGWSlkW1) zEj{wcpZXFnxyzkDg%0nxzk&}(!1;u^R7j11fAuQPq}k1Q9a!&zx><2+^4QQ zYROJi^|EiV?OQ%Frj=YOlS}=Muk!FOIn=xM+!4OhuaOUt>s215=7``$zwFz8>Rn4o z;HvUY@5uRwNuUm7X7RHKxWZ^$umouuWLE0a$T)E4laOO*{}ZOFP&M(fLH)&_q0`p7 z2!PTP_rlP;h2)o6q1qMbDMx?8H;9I&)HvRP&f(sp6{&!q{6zyTB->G=p55gR+ucC= z0I{=Ma)trugKrj2or}TS%ipZo$ehckd^uv>ODN3${qWUc`a-3LT=OzBkXk2z#2Ii# z(gGDDwVf#_NdIakpeG(2N?r@*=d#@+m|#R8pcunRJsE%J=kEvPzyB5W5_j2zjg*_G zzubkcYbz7re{tfW-*U?*VX@WKqF(+ZnsE6Q-(^&%p5jqgdX-vN*)^3#=d)g7$p@gE zvg&s9w~vjJzBzby&sawMhd1$2cR&d6vK{ggdzH zPGAnw9#8P7^>&$!^p!U{ln`|{Kq;064@yYM6)4>}6nOV!0W=pBNDzYv%|l_z5*!!> zQ}uuagmE&2=o!Y*DwBv1l0D}LSlNL+P$gj?%AI7m=zpm&7GBoxpT+=g@sIo-m9dG67qNSlOkAxsw|?8FtZ#&n%c2B!*Tkj5_h zfVd)^4$`=yyWJB>!in*_{gcBQeFa47fp9cTl>zXAA>5W7)xx6p2##Q_f7K7kS|mj# zvdTG9J0FvY-cLRK3R5?|0vfc_oQ1sQotyr$L5Da(JFCvaJhU{Pt}Kao@t}*V;`isr zF^mg3wDceh8S1B7D@b?Y*WVxW)BJ|jAZH!5ufg>3IaXi2^wQ{;7Ch?YpQwj_0X?&_i0}0!Qr!yK~rTWS3RwF(Sc7O@Uwx~Fy|5G%Bw!oTMi~=-i_Hy-}P1R zLG(rQ@Ay%d7;3Ah2XzG|SMwla6(L55!Cb*Mg5!kEl|MTVIiG-ER~6+E&rj8b@LJm2 zmQHNe@{>$26rIRs!hiWJQN{Xhr$FPGl@NJcp!*vmh>$ZmNa5kZZ zi2icTXN2=;(D0Gq0`L_wfm5lM3313j!jq{${%W5SQAxME>eIf>Gx-+NdtAJuNE~EU3Dtsnm}g)4C6;@0f)@?W z4jwQ4#EV?s;dQvnRj1-x4g_Mb-_yQhB6?a&o4&^@o}2a3~*X(A?MP?&>bqd@5U{6k1#MBS+{&wR@2}`21Lqw zqn$^y7I{Xv)9$)UMiS@Cm0HF-pvUWMy=e7_qg7f;{bj!FunkrGS%Ti`L)jBjD$KFu zm;Cq=bN?EzL?AKRZ`o171pu4mD_S7}X$X*lEg|eu`d>bHzlr2ztUl;E_T8&}>nCF4 zc8{y)f-Z462foal(7;hphL7c(4wbcRmsjUcpJM{F8mor#%x~nzY~8?X)nm^kjhj+$iHDOg3`ebuP8XzCF@{R$sB&9jw$w^$F>LuW^p`;<))r1$i?8(dpR)8_o;t`qaEH9hySK5*Yji{C!WO;WV`!kHt0i&q^_o|kW4f2z_3oP! z1S<=^j$hU^;yiic^AAEmclmx>Dqv4jnIiBID_GBgW`cEqz) z2-!7*@CsIh1OT(@;yJw}%=BvVQFwqb=z$GbsVeKg$0926^Y0JU&t~s;4Y0SyD6f8x zeO4QViEYa=3R^`_bDHc!WlI@z!71XqJ?GY*gR^FtdzJ>Ob7gUZ_y=x1+Mv1L%|o?* z_hYRft5oG?Bl4zulq?_riytdRwUg9yn|3MrCbv|pG+BrG z&Tzo$Pct8}m)>1*bof`qH{EVZNG(%bejf65;qtcy*@2Sco^}6}9ea&|~%1Bq@7*CP; zd$GOv&6lu(z!*!1EsnJ3lN*AZG-55)4z!jASCuxum7^Zx+1`YVB80(iLY+d>Nt$4+ zyU75RE$+r!79`JDQ9A-3XQ&?is}4!gj-fDJrIkvU82`18;1-msW~yk3GMO%E=d0~p zQ&kOL^;qGAm5#VhyY#;@Z#%)lGNY+u>wSHRV^Fowi=B^ z_xm)>$7J`M8 z_5gdbo@@bxU=hlp0`pX5%;%#*{KK!U|DJ*N@PGfO4S7NYcvs@^mKNu2FB^>5Zr+PZ zI*Iajn?b1--C@vWiy?sNdl?dx#X{qF zn1?CmTrq!dJI%|y-26>VX4PBXOY;7dEKfi5Ye{P^Pdxk~@QOU%`8B!^erT_{JQ3LF zjP^n&bx>8Rhdx<3kQJLIHnbOOYqRsznNXrj#81&qWN3Z-*V}D$?UR3fi-q7 z(w5i3zvM>vNf*#}EHSd{UElJSr@9rhf<7G=_P_llEB0Z6dF$II{j`U9g7naDOn&x; zUuMO;^uz9Y)6G6L35IWb7_qyl`elR8_SP>8i z{on>aT(Z+Jks*Ijxd;?_#UIi5>H29;{We2C&6}s@6aJ&Da<^PY3}FNh5H*%M3O=?OSJU31Fc)t&BR5Qv`YMp7*g510E2V8M!{HL~IujTV*b*0m|EHGwTU=C6d?|X3$TeCfN$VP}V_mIP$fmN~{uyug zB%N{Xl-gOG5d??kJ3fp7dq89$f_9KT9G*#TbI`>Knlrazk(Cc`_vX|p#(S$s(Ix65 zsPv9c>&bd@;@|GMB%YKSSrK-=n%$=BCU5_8+0^t-cfQjrz4MzGNr^r%qb>ldDDShb zD7n2wU#S6x5OqqDlZnsp%J1~h-q3>03k*W@8U5_??0lFNq{w9|9ov1e9j}Hj*2Q9b z`rNSp@@Mvx(eM6-o|4^N`-(?d~t=b+y&mUO?L0 ztKT>w+fk*YEOlgwM?LIqUNOiX5;(aJ>4_nDF_?(ZCw)GyHXDOhYfRD}1aCASVkJ{N z5xt=__o9_6L6a3NR&^0=B1*App#wOrb*C&07MFUu!|iar(cg@&7?fVa^zd@;Miv*D z&_fS-hFgqt@hK0YCdm;qJXqeYK|Df!+~JD^_l_@!uOLjqL*B$3hNr)sw=q1zz2xz? zJQvaG<)5@E_-=W0)62g+Ya{4+)-zNBkM!oK>2);RwUrl^bzaf-^c;uUNdsEsY^E;s zm|WD^c_4?eI%oO#xuO5nFZFjl`rTfBw^v%WniT53Zf4@9^}8H?`aOl1>e+5?D}tsK zC?q9GwT+evmikrL)W7UEU7O za50U3C>k;1+K6P1PyYguYEih#k|&EPj`Lvi#Z>azS8gNuEYU`~b7mOG!7jbygSZVg zHQq*V9f7DkpurGb{Ya1SNKa-kBx!)uM`D0*4&g{y%>wzwN3vAQa^>T2Zjo%>rg4e{ zKeu(Wo*@P4&$6c+LVwjGq!`>Zx2N=dw}-j>jxYj4pQI=Y5dQ8DL4!ns&0&cHzU*-4 zN+oAd04kVEX@mMZKT5?$JkacD!{N~I&=cR6=LUL`ws1qol80)7?RT_>nTs~K@h^GL zI?s`t)6H7uO>~LytI93UNvKLEvbzZN`(h-%^NXjQ)KlaR)?-D$NxT9PHDQ&7)1({D z6qP!MCgP;+46PBoP!%;j_AXp4`IT>I*NO-P{*2)^@5#pGzH4|JXs%yQ_!8NGM0a9OrKA@Qg&xnpE+QEd-em<%dZT9&%T(ooj zeza4~#?Ab1eo{{*^%dUXov6@W{;l}&tOQ`Z;D?5n8tPr&`mH(Wz3N+0GKvc6C2z>A zWYgGJd9VH-X~>JL-42`#$!jX-Q0?t@ zU?H~UW>e1gbcZ|R0rN8_Xyxh7XW7xk3Jl5H@3siYl~i*st7VfCBje_Glxy+84_*nB zz3ox%vZte5{z4^Z&wRp`MKty?@Hr}~;#;XRs4Tk~E)F zs}%|^UYoebo8{o!dTahcx6KZ7P_YehL?6A?%TB|2;Z@%1J&2x}Hrw4mZ6P_6AD@DO zw?RtH^jODZzE;kcaT)^Vc!WD58{g$BxziM>WuLEE=c7O7J+FMF$K2cHiRScLRH?V@ z^U?3cFY0RWRsj=V)&gdW&Z=;S135X>(j;pt=Tgpm#YcJ&=N3;nH2giS-F(b9h8W(Y zQF;nB*L;TuBbe3e+U7a-aWi$QSYWxwDb@LG57r{GP(Z^nQJAWmdG=$b$q2oLJDqbI zC%TzdI#G~{8&R(EC@Uut-vSml6m;~^=*_K<-=Fe-@OwtGn30Ei)R%fO-_VzOsgj?Z zW5YW$XnN;&c*~Q$QX!gr+GiCJ4=3rF-ukI}cp^jMPv$4%7Sq*JJ?t~Q)I&eiTLGBj zcAloMK`)s8mpVgmQr_uRB?Xyz3LT2eJ-5P<0S_gGDa=P!oVKzB-1C0r@P`&&!VfXxl|ZH>n*{x zQysLH3IQnAn1RHW6MOdh)#@*IGH1nnL|_qUNcaGiZaiVJRZ4-ryXyX^k!h?9uESkb zXA`b;kUCoTdXVYv`C3)d{%{9yVNy0~E2!de;#f3t9}kYv*d!>CVIzlSRLwtb>d(vq z8p{jyBui#~$5(#oH_OplmUdtW9}+#17XL0URPS_{{5!s^XtGkozWqx@EB38l>d>RI z6mDiZL{u0!u@An}n}<{0=A|wb>@aCTdqp zE6o*mmm`(Zgb1euv{+KwEpk$T$DE+7Kf~?86I5HsL1&5Mp#YCwuCm)hPWB^r*+X)y zNV)@Ix;2!7c=QN+sj0=i9RBn-HFiEKt&dtwMg)Rn&s0)Uk9bv^&3Xd%xRX~z;5b!i zO|<}GcDN=g8P2x&BkU}r_`g=DBOCV$FRlbds-RE`(xF090gkYKzhVFNUs!KoP*2Yu zQg!xdVW!S`I#N$I1)b%=yPysP`;G?2Q@xS9ET8QwzZ7ZEw<0p?sPFP5;lv_%PeSYP zqe+}7uN%Xcs|3 zK#5=@lYSrBp$1>Vtt1*Uy3ZRP`&T=N0uDrfBn{ascFmC@Ygx2v|g_D3|x>8d9HrMg!XHz(Le4u~bwkdj1}LW`!280-z5~> z+<)|Q)%A`Pf?;I4X9qAmw%P6No?{<*DG|3(2=JX-d8lbhyT;B@VQym-KZ^^&&q1rM z_;$_F&+=I8^`y5E`{u;v5nFULbDEd zN<&%gR-iR#2Zfxu2-Bs8*xT!pLbBi+54Y>LdOH^Hr0_241SS+PPh+qG2o|c9x8V#s z1d#q*_19BH!>nwddVmgesg%U6AXBJpnRSEl1H*$7>9dlo#LjxjH6HUSwRGx-yq7&| zzIfZRnT~1hr#t+Q{$P~eb#uAr67{KT4P#TyUiDe;(Hapd-9~+68Qnh0s{|Kt%IIpJ zmTucCKl={Cxy5<3huUZ!NNjg~iL^B4Jkl#XC$Ro2J<-jmX`lV>o$-Pi?(Rf5XQo0E zGgD)vs5TckY-f?s5ffKi8BfG#eT=KOzRzPG{g|UYTON$}5XeLy)m&2rfGUVQmUlm> zzp)08W~j2I-py2UARdf0Z3PDXC^J!Sq9Zs%&S%(CE(FsYGM6j-OTBS@0635m#!%De zD-UWziXw)7XaQyEgG)o7G9U9RlHIrov!QLg=A+EqyPZDlWsh{H-{vdK)LCBJ*MLZE z)kA&3=j-qBMF$$Viai1Xkgk-*?a zk_WLes606j(zT0B2gU?M-E)VaLU!z^f(b{J`(a{KH>!WCZWwK_)46atZ1 zVMxP8+)U*zPkEKqGQ&z^nFHlwnZ9;3m4t`b&jJ4L{SpUth&pb#qd(hCbVuFuI;6YZ zTm*#_yvDmd8h9jrO@vGw0r%VPdP0A+ha3D#-TKoUX&;aNGA{!<^5|FDT(vWU6Vy)q z(T{OIVq%+Bn|2bgHY3|x>eD#gI8ayBifJkpVHP!pyz>J|eZ1xFwhOCzCnP?0-&*rH zGqdYOp5fiw9DvsdAY)Y==a|J+O?2)|oo)t6&&%&J(e&-`#-CwQD5zHyYzbY zr=R8ld&u1GY{-kB{e~$|bGyV8`lFsQ%v}f|Kjp|69wf&8u6r46XLP~4nG6SSD$5wF z4XGk7H8ocY1P3s4jcc;J{#^1aJkdvoWf3)(H`ZHZ4936~u~jtL1lE~ufk0&4!IfLD zcb&AtrW(9z;uk-I`QJ+((a~QdfC^)UMx9$cP=Tkv4F)l>L2v3XWA&lN*jQNE>CdYk*_oJ3LcMo5{A&CZoR&92w9`*PXV>!Z^7n2Zs12 zN*E-lP#qErrVYlgW04HH+S4r${2b|7-sphYi(mT;*tY z-NTivFWE{=CJ1)u#cMP^C;YGob99V<^rOk3x^pwpq|BmGl6Ir#W45UYW$Cf`OktTu z9$pnn7=!C*RNpihP}4M_P;tcSGqDj{08K!$zpq5)rC0fyW6V(+SE7z%PMR2?4yH_= zBL|nAg|@~XOw|=r8Rw7g2m02CpjVM9{zH8=7;|Zudv0C1KxyDRWgASCmfUlvdc>S?Nys-GNc-H08qK{Q@L zU;wx6@Z{496L6>uaf}gSEchm|dTFZ-FtiQ7iP!%&qX|#cPFjKVpX~Vy@`ITU9HmIb z@`fW$4>f@IIs&naB(v)gf{hx0y-N`SriF#-7oM4v zW`eFc)!`MiZuc

ar%NOduAfvk9#-!f+~~P~ve|jM+N_a%6ZOT0o=;@Lg3EMJR;b z2A!qOx)+mICKwLBdga?q8OG`LL}d&f#yOn6Flg7)(0mVR8ATiNZz-Q-V?V$GhsArbz)*&Hrf&w@rhR+BPwuSq{I%x1dRHS zT)@pNbRf#PC^*?iz}*3YJn6pCuZC}0UqKR5Cq4%y6eeDOtBX_qD>MSqQI@&E0qt2y zX$CS;SFAq-6U~4ZZj=#^Qp&_U=!1=<4^mM@ZhGZH$_ZZHM6=MQ2E@QjgVU(Ll7@oXVla6Cr}$Ze&h)mM=c&#=}~%&deb>Z7uI^dQ6H$9fd>=w)#IT$IL5pYhnV-= z(-BUyTIe<=mJ01mIsrQtwv>r-F9b)Yo1Cr;hGL1Swm?~Cpy{~S@EgHV27olxZ@_d!8Sj1yBuwz#CdR<^jX{rP`%*cD0 z$8$2ycfA%vr1A)mfKD=>z8Ed}7Qj&hr-~rnl~b{v`DWLnm#O(0^@3_T*yS7!mMFT1 z>FHU398y(z@XiDsustKNSwH9Z%9@n_=#7aj;ZJ^EBOmAIEqY_&gN{igwAxhRCT4KH zfD+jhG5A#8qjWe90>N-l4mJcf&slKcVHMaF+g8%MzcB-T#sHxE82&l+7nk84-QZ3l zg>UcxESt@aWtir>0P?XV6gH<>7)b0$U|q$w6^7qg4rsqyKqaF1`+Fv(zPV1&y`O%h zyPT_qNNB_Gdv=uI>*2P3*j{WV`7b~QU__eN=Oll$n6)CODcU|mj2~e$uX4qxqzq`S&CSKxPbQ5dj><=G)vM+zHliy;jPlB z1-l}or=e+<%Uz`gZ~)mHrlzA5E-Siaencng_gI2R&RQ_u89RA8fPJ(8v9j0qcC; z;!$dzCc2Qf6|Bc(ZBalJ%CoFm?&Y&{78mGW@sx6*$-PK;8;jntzqt@&K%U33-C6Z- z@npi#@!3tXGwhVg&WN2ObU%60LUDU^3B+pviH;qK35$iO$E&<)w;3{D7Zk5h|Z0f!BIrI2c@afKxRxxoMDYV;waT(RJvhTv!YJ=Dyl ztdV7&84zIlq_ME_szNn_9+&=q9#~Mg_`0M$Tp^f5$ks2OP+lLfDk0;43LVua$}kPr zRVAKVlTC1bxS2z?yn?O*Xi6%BE%n(FExG>6F0;TywTYt#HcORZlQPf$9_y+ua|d*% zh-o%A*F1yr;a0ati|Vv3ce+{KL-rY%DPTm{DtbMYHj?I>ZAM6h=9~ko2EkG3|6$!*-y{dYd83M=)gy}dGVQx%D8Z~ z9F(#Cu$RB=36_B8i3(t~b7?_GO<7%WDYE^q4#LVuM|Gzd%Pw0Zr$>LegHPe9X1oEg zo~<8MwW|vPkDBIEhb1s}ry^8`J_%ePH40j^BIu8#seuF5OdZrTyD4h=oV>bz5#GvJ{%>>9QcZm@o@QMh6_Es2w4 z>K2Mjjv-Mw#RC7mbHpmC5Q=%mGv{bTgYxk`Hf$Go<@mM5JSKONI4?NE7T^aEOhMVs zF$mA&dSjTIf~{RV6$a=A<8mN%*y%+!TYu89VR(vKpp%Q~vblZ9lsEwJQaa~hJHZWI zhYaQ(t6id2Is}qkP5>~LIb?F-WcM>VMYsI>g7r^N#AwTl*7b--+G21aZCtG76M+xA za~Jc4=*D#Nm=f7S*+h2F=mH%~`QY8W`tv_(6COtPL%!)uii1u*gKifUvMOs>gK!k) z%`#u1hM#uJ1{zz^>llTDu|=pX+IDLe@pQSc#GfV&~&N)T-#8>f_75>Hn; z6M4@YJqg}AaN&`Svm{7Bt?+kK45sUucDWKS#4u;v(@3yTwzQ5)3)MkH`7Alk(*NKW zwFrGDhqKz@9dIa46=ZjOOaxIudUw;N7`1UTtmYH zO#}xP0)#wxBQRixEA6nj5liGVl^DI4;X8n%e}kt|i!sR;{mG%#;x@Zm0QHD{{mQ%K z0p?BgY-`yRMmq~8!4n&8ay|>^Qw#cQWoQ_r!Sp<6RTQ?yWnD)`&%wL+EOaiCFW7=* z8Lc;vZyCj)qn=nlsD(Z?h50;*|Kw*pQ9O-2O}XPT&K>(#acSygy%4CDCwj#tuzBbNpIQ6e?hJ9*fFigQ$@X&r{<;y9rIiRbk4Eq#f-^O2mB2`UhH#OU(h;O}7(! zn)y6J7G|*|&#)e?i|dN#Adrw-@sWSTd^V?SbMS_>3E?Wb`s5sB`*o52*PlG#_|0_H z8I$v5s1`#eR0@y_l97{w?)$-XQbwq0@;Z1P{)Q~6vn^)RB@+hbKK(EHUYS0a3ks4k zw%O22{mu27(WMI?f(Mf}M_n3JV)t8(Lu)<7Bi$%KE%_>Eyz|8g5*~`W^<4;fq6-OL zg-3zrl|$*Q1wRz?yTx{Ab)UlJ+T)bn5|+(6H)VoW;dD;ho!x?qs=cFYYtvnB9hlLr zP$k}L_6wEpn0_wa#SY1{^f}?^#`%U)e!)qW<^z3}5BMt;?BLTd4yD@$Qg(r>q7x%6 zHQ!L*xc==&dGR^A8C5i`3b!wXg79{{?Whr|d*F{|Z4dj_r?^)f|| zUhKWk#rdCaqHR7G1|q!XQnYpQ-qsoX^Ac{VFbrm==a zvB6^U;pH^Uv@N|O_81y#ywa3j$wrUj@1x-R(V_ew{;cG(tAt6BK9uSb^A-##+hvlI z$O50Y%!;<6Fk_9~(yyBOfI-ZxlFgiG58af@lN&@MF0EVOmG-HXVT>Xobu3bVXDzuFoL z_Ze0&Ov;+ z|AeC98$~C1IUeNua1+0~bZO!irk}Vmc3QNhnzf5pdW};+GB>3yrWHh=ujer3f34?~J6)S_Wg=|ZlA3y1|mJN23 zLE+_dOsk${XB}>%)M1klTQKf@Z^!<#b+}Gzy#6`XFbd#E6GO@rp^&o}crv&)({-Kc z;^Z@*F{;DedZtS8`Wrk&x|+(9gF={-zW;t$X1Ai2TI-Z6o`RCcf8E91?yNV=IH{M& zVNNa!Q|Qc*5BUC+DcR+9WCEsYvZ1Sx!Th@7(`r|Z3J}x-CLaD@$Z$B1$8YlR$XbRd*CWl{6)=T%r*>!Wf_Nx&0bar%b zj?59K9pa5fT}2bf0gE65di9UBw_2OE1MY6;oW8OV*8zyuv1a|+(8e+JUD9g~;UJ6X zFfX9Tzy2e$jX{fZ>k;>}5|jAP2hBAXBc;Mg>yz+c_sC2>4$M=zc>+F$I>SvUZCKDx zqMe-jtsk`%=UJYD)frX=+c5oLkof$A9EJ&Bpo(|MRM?Wqm0G(Z!OS#wToIO%%rD~Z z5KUC!a{W?c5@gGj#UYJfnI}*Vf2__kQwnH;hhIqD9EwL~y&9?v{k;y^5)rG|R`{~R zr!A{#(?rJQ-|FtoAOAl;Ux#oLZE9g~KrRvuXSz%wWq4kLF%763`+Cuo35h`&hk?NW zv_u_LUL{P(l#*a1Gk^#FcJ%2(eMjHJ$kS_KBp_2&2}R@5)6H#Wsx23pzl?T+Nd>Bw z6*Ys(L>wcEU*yfO<}fN`Mi#dMtG6v_N|weXG|xN;IL@OC>TOb|^H%z@@NQ{x#DZ~D zkUDwN(6!2=Y~RjJ%pd=cS2kcuttGhdaA5ulCNkrO>7-JTTs8XEa5S&ryo^WTaCQLX z%`p>t=%A=q9dp*j$M9#?B5SnjemF#R3<~K_r;#Ulhub{HC=ES20Y;hg7EcuaHg^_& z?I^Xzky&5rgi$%R39e}$$5>C7HfrrW%rdVGjDus&YC~`O&@&S2_GPU^P>6}o z9LpEoGyU-|v+4)fHQr%@(a3Vp_MakWrTz& zohn6(NAchy*%(B*1qxHVzCdy5LsCz31Vb32gf*RSX{FaJu?<8Lh}-R<^q)qeM!-S@ zU2ZX$MhD10(6HdYY;+=_hzVXyaA=IYD|XknEvR6M;?BAL%PV?ADP6C!*CSsjp6DVX zHupDuMqWok1vN0Jt8DqGXFk`qW0kGs0^jxg6z_{2SM#24_8`7=cl%?VxoByx2r6ST zdhWAs-`ZR0pU)Ku3XzgD)sCb$^#!YD_XDv>xa2K4f%RQ^W9e{DdxJVMBKdcgN{oE6 zGe66-zA(w_Y(~A}lclzrsx4s!mwM1l3a|VGn_HgYNeW?Y)GD>cEl#~`%xWw?L{hlU zGJYXOIFf+N7Q4OGxl$Lm*kB@nz+6!Y1f+Hj ze5sQnubE#Fnt1sqsz(^Da9K&l^QCOYCb!y1d#qH({^8qi|JYJX3oyDyX&D71!qH-< z>GrK2F@~(3_NAWsSswAdY&_5ODv$i&`78kR`QB-6;)27p_|Pv@66A=LS9yG54tb0PMV?|I1-oddiJ_DLyd@c^CE8(k z0>t&N|6I2uF0xJ+L_kM9@`h!w%-9)|BcfPsR*B7ub!9I-Em=m{>Jb-z5K~UxOJ}>R zEuMMA&QU6l3fv2Bd!XnoTN<9vj1ajy}X-yFS6{mZ||6}P=~A{SC~D9N(w zf&{O_**64Kbd_>CNS4mthXd^y6Nhcu6B+J)ht?tg9i1aDS2syOFx`iv^IlC;P zAr#QYlr$m%k;JG?ecEmh^YEfSeEEbSKArYquLXKO^_D_P+=qCl6PlDMnpnZfnboox z9_rw|iZF+!-jJ4g5f87Do}W6*r7;+9X$OUaU=im5d!xSe(^OT%Qy%O!>5;w|SIawn z#h&PaSG-2Nj#%o`D?SsELX>(z#qf}4xZ}hxXh9BibjB92$yDP9K zpxUx)F*hMJ7wW}g{Ku~}5{dW9n)FEU?qWonbGSPl;a~*gXZVsWeaR~vX}T*l+0xtJ zq@blFGh$55($E@;NOGrtEUrEy9$>SGQg?Ugss|urHPn$_=?>4tm3b2$$e5elE`<7W z%R%$U6Sr4%mpeVujGIVgz~qPx1UdoAlTY|F$i$| z@QSZBK3#f1?c(L{Z}FFu{*9lj&3WC;PwKXyG`gDRC)F%Hxf5x*ewhbEAJwt?>svoo z`{zL-oAbnWtw9N?pVK*0L|bnj>e6rXlex@G({LyE?KV%~NGnhNt#6bP=F2=ybY8#B z&GxN!Q~s*i=q_*oUGm@-5}3>dymj_vPk~(cCDVV&mw$)bJ;QBx|5}ps)1qi#beI2|P_jWIN$rYaV z>ATxPlWCW#&y(X|X?J5hR4?_DM&|RDFOQ!q`3gl|dKf{3_%08HfdYgsGw~Rn5U$I? zg-uAaR$|XPd@bd~!@a-RsPvyXIrqUuAX0QSbvSpE8%?Y+|0bIN|LL2cazG9-CSr@3ij@7g{)^2 zX^7-4;{F}*D`}sYO_42m(GenCy`1qP5KgUrfCR(DZZjw(H$Bak=9(VkJrnv4j6chn z4@bguH66`u$*CgtD6MROHf%A8F1z>@t(3!_w{qkwj?#g_6C29F-VCz@JVarqZCT+@ zbWcGfYP#@SYylsK4Tt_W$4XaS-T(?qvv|^!`5c4Suz@>lR>UlSj`KgiqSdF!*YeHT zL;tz(i3(qo$2GI(_OIR@BQhFJ`uCs6+QXg(PJKD;l_e*2xNGI30bASMl_m8wQl=kO zLTstaNvT58GO5Rl;!u|LB!`onWvOwaKN|argIq&{nfGuP!qRpWG)!X6ROC!fngI?R z;?yIb`SxU2Mre>U%Zgfa*J(z%ad)E`*P1L@mN2e2xqTt6Nkc?B<;hhc>g^~u-*!II z9j|h8m)04FVKmd5vutEZmM!mPF0RW>%0oKKO$So{{;#kY29q4!WI%FD^GWXphw-UN z6^^D#V}zSqW^mb<&(YQ8x~POG(qKkzD%qBh>gMHz=9;CH*4cKsPD%-SB`7*P4X&e% z{@)-63hU43I`Lg{ zf|N)U3C&E|kUthB8P5{B@enNCSq8{Cz}4*m@hFsVauCl#Bm71IO~!b+(?!c_4WvmJ z!M~9tuj}|1uV@lt@|=QjbWAG zXs>h5wE3L?ry>;~XUIe=U1qxBcF(hT@eI?NgD1*b&%4GD-A>oY>md_pH5~wQo$2<& z&RH+5fA7dRjo6)+XT<8%sFD$Xnjy!nCPfxq|3*FTBXyb=J8-TZC#eP4cRsXc7`HIT_%4@0}Xr9&^aAt znnTX0qa%dr!>~{^Z@|&DGRxGn)`8JWzxYs&>U#ZI49^)u7_^-9?x z*{tko6#t=%&4O&)(p0H3WJ3<)7iWE)hpTN%p=q@AbFcLFkYn zJglyraEa&OfB2Ka!&smaE;uAiSgpK^Wyoodga>qT0?E*LQPTvc>|r;Kfp#}DmzWUf zIAlK%LRY{qQAIYc+%<3L$@DT(V}t`wu2eZW1Ei$@GW14co-uJ2G|S^z2J*?YrAu+LQ~GW_J-X1#)u0zX$*T1Dh%N(sJr-(-BF#{TVJrr(3e z(nnnS#l}S@ag6U>9_sVujkw_fp%hL;N;BEkE7sGQ?naz`pnpqJ6#SG_Qq&y@04M=d zlMQRCL&#v<2;`BFRf>PeddBy)b`?Q!jDnwI1LiyxBJ7aEUhY%}E@1CLesGA<$z6*4 zbWY#NXM+p@Z`TAteN^;P{T`m=j?8F0~^j;LF_Elxu>~M z+?&fD$aYjBvk!DEx>{(WV0O)lfaY=M>$)cmxm3JqNvF%@s1uzpZB+mu`|yExYViPa-#U3x>IFE z!!wv7302&ig5|s}9n^@<)H344gqSuv@|Ie#?rhkJTR%j6tpDO z@=ds9+F+WMYN`%5p$Q&Ui(N8%tt?hkBv#CD*A#*~NKta>lfMX{j=2R9W%Q590MMrf zdw$iUo&@8%%N)Q|85W>LGl4^gb3|vm!(WVs>G82_H;9-geTzAF@ZJrJ>vs1|ruQP$gf2QzpHZL_F+2+k_1kPGW7HFDzbY@wenn6K z?<1UnjCBX;fT?Tz%Q0r!K|KmG_#4mZU${!NoAeqYZ4XFisRn(;%j|xv7v=OZz+g{L|gJcI*mP!n#-(-`537uMmf1@=%gqT5*#tj6_o7l$Dp8a7e_^0R#RYGtvwfsZWz8{&DPPDr+JEQA z_v1o9SUzP76Iqu8i~nV(P46X?AU=g5-HET@1dxq|D4WgRd4jVDeQy$7^3_u>-R1H{ z=oQ3ej9v!QgrqZZo?(*B=1*_W6$*G6h$4Lt1KzZotOvF%Tso0m$oz3|U*;C)P^f6m z&^`q^_MILE*F%`A_V9`IqD1IrGUKRP`Xk4B)|kJ!k~({tnW(_fXf0$F%qY1R*fl*=u%yWX zC@g6h$Bpz+4Phib4E-Ok^+;)WT&Z3=0^uEd3y#4C9LBk{H3Z~wxc~y9jxp2@!vzc( z+qSJGx5UoaA;$GeC=~(Lx$k{CsH{d2>BNTA2#X=H*>qcpd6Hn65JPp%=xlv*iT)o4 z$gz!Uq`HnDcwrorQ1MJ=5_AGYNwu|7W}a;udmUw4)i$*b6AN)Na?jKyB<8OB`FCyt z?h$Jb)p+0wQ8tK`o59bPl0W=agu(n$IwoP5P^6R26l$c@RW54EteX9T`e%@hf3=ss zkE>rb=WArL@&I3!z0pq{;~c~_Zynq6WvAZj*`X}-aipUQv4XwyP-Z>Gx<2LVgN<&a z7MRiFvNuCFN)u$uYaY&?xvRd`dWr&Al12<_oStgIq^fsyj?wyHRJo^%pHdv)=ILAA zTN)8PX_w*DX_moyg?u$#sNb=@X4d>_Nb}B>N%N57IjV@d4+vr%Z_i$ifG0q=xPQJ-9&#<|jp!B_+uGa$9rp>(tw}m6Nwaw;i zTMVbGV{TVf*^Oi0=e75-Z9A(97SF1S^uFzZ$f?+>-~Fp}5-hexK(_R4-}r zcfSVbo$EZW%?^EU7a;KKKJS^2^{j1WD<`k;wVxM+0nVRD`tSXk{0ZTOBQoPK7n>Tr z!J#*mu6FT>zl~ez|Kvs~*Hsb1?P*>v-~V9;vsomC&F08C(Ven6qP91B)o#nFnLsmz z;H=%B+Hf-~x-tz<<=@wrYA|k+^{&jcQ(?E+FVW8Dy`B_m`K%p`P~X$u_B<_1o0D3$ zc-6hfaMfLc2V@ho4yImhZzt*2r6>34py$0@fwG6eoA-gHU%T=pCRn}cJ@P&wAhk*N z*}#A^3mM)x1OM0&{Ci-rWQ7@c1X}1TzZ$a#YR2flr^z`fwvfqR zvv61(C)I?j!H8M8W-Ef;f!b@ax?$VmmKp1!6$d?xJ_%_bu?iicMr(_m|%W? z=|_Omoeh}jOeAp?*;j<~2wO)XcX#T78C{8*j&l;HldG|-+*=NP_AH&3)2irYd7PM6 zCZCu0(oSYyGJ$Fm_@kTc4Tl1ly;2LxaNTq+cf0NMz1{7=Y{je_ZIfF%P}fx~lyQAJgK8>uY+f=#I79VzA{qv7x_M`LksOg}l(iLTyX=_<9(RZZP>voV(6 zT{V)aip;0P>|&En@{6sm+bSzwQ^?xT&7kX|yO+9c9@y}f2l;tkYDy!5(q)SQi?mxVRWG1 z=jp(1h|A+3q6cMvz01u^d~Q#r|KmR^+W5v0b#KQAqgTQa?sCjH1_0DPXYbo+ZJ&UW zk8xFq2T>v8$4Ej5x}riDamFtFY6sx@jCniiF~+EN_h&IP0)t>!0^1(a5>(UfW+T&) z;bA*wbymf)w{zw$EXa0OZCvv)PKF75oR7cq1JNp-ZB(LBjU{9lMzLpQ*k{s^N>jNAG`O5Y&w};Yy>vt0V1hmz?IhEYe4(ix^2BlLyW=^LkSGVGA zPs+Gv0i(?{OApPC!Qf=X?lfbsG)Lch^2EV66?^S|jNa@@y^7gi*UYnv5v)rZzdq2h!C-_X8CYhP3?;f81Xd z#N-prwk$DL$UaFx&1>wg3A!8!9x`JImB5|I+y<@dLMnKIBX)gpyU5y1lQKMkBU?tb zaZe&p0(~K72((h+0C{$aC?Fcs=UmMjlsg(LW{z|nn3F~cl2QqAvDma`sLni2U+N&u zmpY#K#Bt6mPv(6Y0AuQRzeoqf+CI*Hf$_>E@V_+st@MBN^Kv%CXJ^y3wF}x;g&?Ba z)j}hw^{5wNLzNx9w!?+^Nhdqh)moX>Yy2w_okBfZf)=vad|Nx)VD-zhUTM@lZ!J_? zMmgEDYiL_dtu<9syXq)=+wzS4cY_uOWIuS%G|5I2EFN5r^)`powRoixK%reS?HME{ zd&!KRXyX`cL51iEt|_+jlvw4iaD`(HDt33uZaITz+c~I#sg5+FyQ=}jXR|e?B<#-- zY~U5IM3V~erb<8zuK-a-VG*Rn?jU5L#j>~>%IeZ$Ug=dJYL{F%qe z31LS|*v&3Hy<0f=dez}J1Lf%dXX@QvZTr^rtad&h`*DmuW`&~4s%=ucQme{sX^<*Y zg-Jw%hv1C}f*=^tn;@Pp6+{Cn7(^6>s6?+)x$!cW`yZ31&HXEKvG#B6Ip>;l%+Y&q z?Q7rnzR$bK!F<_=Eu7XmKd^q-!PHsqYU-((-0gA~8wLfxTRc<7jyX6U$Al5|l=~)$ z++`Tj-Dq=h7j-r&9qwCun$Riw>wy z)3K%kKQ~&kmMaNTqMQ{N=NWTEjHQz-d|k$eV-K^_=*WrvBhh@v*()>JPXaff;*h^6 z*I`lY=XSCh6blU{%Kh9oP@jxT_G3F&madluNYI*_{LPBqnB_~HbUek8PjTWwlB7Mr z)?p5OVvv=mMlh7c@h|?l4%xZjvx5@1B~s!slvg`KPBX`a2g^~$7U>+d#FL9t!}}aU zZpozTM$oCm8EZ)1dzF>%u27`-5j*;pm7PU#S4iT^iR#qVkRzQm=VGdf9u3rn=sQ2% zO)ovq1PAVxgAZ{_SRXOf6A8ePzp|CE_paM^W_ASm!DQ2- z$H{*sq3py4wOZY$V~Ed4n96|`l@(L$A*FAwJX~vuYTYT7W}ecx;+!5xkDL^k6`Qf> z8Esj<>R_xb1h$w2+(M8uDxHc)K4q}ggAC1deeGl*Y??)7)veVgq6ej_wbH3VAftg; zoB&B6JX;p2q!!=OJI6%VapNv6{k3MFk$IbOdRfX5P@o{dS7zz3BdPKb2FOW6V6;r! z80!_vHQ-X2*8n7qo?o{EY#QNm+8Y4JC7x>vcyX2ne=;KP>FF)OGhX-+tfy!>!qQfm zQ%rNp-8;hTy!Lwtq4`3pux1Fk$-{>Ls+IP*^CkEo&_)Nb*q?L<^|Vcsni7SweW}j) zJMf0AtRg~JS}mf3ldc@>fF`YO%Ge0ZZ;2Vu2xLudsSza^QeeC5_(mMl8)gzPdk~6M z+m~mrs3q}UI1$z{I$Kcc3O%U>XJVcSFma`qF#~`Ws^HE86@cNgRBfmctpt*Yj^21@ z^)MS#7uAqh>5lpqS=-vH{^eJlm28Ptauw_x^(>~IWbPE6cv&q^r>MnYS*yAd&$QQ$ zhkwz9jxvj^h0|qBnMM`kU!uibJ!}2bc=%ze+wJrx7YFk z^aP{#KF9!LzrpfA84+-H0sryAu$S2uo1rQ_+(8~z?2+B?2w2;|U!8h$djNQAA^NkI z1~Q;j6ijf*&T=yhtD?dW$>Q^an(uGCnyCsUa=^r1WH015%8t=MlSs_)ua;P>%A%Eq zMux97^o+BpiqzHCYjLOA>a0DNmgA`p?U4^B_1AyOJhx2Iyz7HG-zj#NedJtNY{rid zj>*_&hCJmFf@%FMmz$v@l+3<|)J+pGCubhfwP=U8CZ=^l!EK9ensw8lg+XT%f?LL$ zW-7g_I5VD)$YGtZz_9r-#@K;NwyU3j-WP3-_{}YL09OmVu9X4A$@Y#1RAHLF#0(zAH21}dNdZ}>dW8cM;a&phI^J&ibG zOVjM}%r!K^)Y6HlDE55XZcg9QW>0`Yd8E^iu;*%ybmV4EbW*NGa=sd>r*PQe4mZL^ zU?O^oXhZW2wtL*&#(lrS2+>=Nl~2z{ce0gTBa%Ee46L+tILF1!5A=~}g@|1w0h@{{ zkF{+3aIDwZ4EzHd?9vDPwckV}L@Ek(hGGpo!WpSJpnRTUmeU>x9@g8@WkN(v#1!*b zuHf=aVxI=YeV9|6{?tvc0d|M-l6_#4sUa8_f-yQ|w|>j1YYEnB;@d~Ys|FU* zl$yvLXyj?9TB^n3hw#J(N*Uuv=$0_kp=ZSUA2M2R&ZU0NP5qMpB*JmBA#q}@iV8WxC+%~F-s2!g(pS)` zB|F$+p2{nYT#Ih3;1w_|+w)&Jz3i0M8`23_9Hwgb8;CAYShuBJ<-J90-u z-P=qu80gixwGDi%nzlxrgqoU&iU z@$`T$5+Q59;`$pq-Jy-N>11XryglqjIR^unPzod}$c3PQISsHD?Tb{+HX21FhRU!* zij9YK-yDByO-lm}?~NHPR-hM0wG z+9r%YRipDL`s9dW_9R1LoD8B5>IL>VOa5!IuusqzN}mI90S#$SC??v2L|7^89*PI# zIc)Zg!$GKM{K=_;ndwUOE51?2;0I6fvO;LLyA}2Wo;if%n4gfjjZ-z*5P9}EdHq66 zneY}XNNCjOO*A45>E<)DlUew9mQ8mx^SK5Cy@XQHVR5F=L)MkinA0q$KTA(tO)*nT zTN$~dx-T>LvJ*F(8!-|RZTne{Qp?PSD&hFMqI%Dxz3NSKD$6aWH@ zvPmW;ngM;~nOQja$*1We{HVjp|Khb+cS<#Mb4g*^@QD^Qo*&sY6$ztGe4AvOI@uS~ zFoqHt*O&kek@8I!xd6E_wRMeFc6>0uBd~;0pan&i0sg13N&tewlOzu_#gK{4Sk3`- z(yV$IsIx7N)PPcpPC3OzO=Z?ua$#`cx`BwG;Wmh+B$t%fQh6OpFOB_((FT3-l6bVU zB``4Mf?)vx27ABd5y7`G=5X>qe-Zi%%P;5BODUb~(r$(tdI-a`(?+e6t)*2W0>DtG zsG>pA3>gPYNaC|LH|GV|@v5rlk;Ss5MAi_1KpX1xJ&eIb> zx)Hsx=pen#h5~pv9vy?y=etj4g>$x$@!++?T7n)=dPi%9^PA_ZExJK#0IR`uVV+^^ zV)7uJE|xw4Ka1(D_DCIR!_a+P%6Wt6gf^gb>?;=5kL44^q#wK={|V&7LKWhIb}%$a z?Fi9&M{o-PKrpIpNT`pgQ)00$L=yrRt*NH1){DkuaUBM*mP?(f$y%Y5#5S5jAV%Dq zqEj5H698i%rnmqiZ3$fnn!r!L{JNM#dLl?WljUWzEd=*2U8YcCp&YVZUV$ZN8fg$* zy8t3ldi3Q?8!!i<4I7Ey^=Bbj^l{u`U=~;sSnYMp9%DzqSB1kwDi~*h;4{cij9pa3 zas-f&XV)n2TP!4lp_>R%SU``A%VCBPPtR04#8_i-Va5DmV=bN|iH<2WJR7k_{Mw69 zb(^e;sn>CjLFf4WVddb-mS+1u2v zCrgbykvixJ)E)04aAnMrZn_;U2RdoHdAc>5PmZJgj=qIvg(fcaG&>nfc2^tEIH-U5 zAAjVjysD7?HNHSVS_de5+L}cDsUg79)4q+8<|{XhAeI&fOxDs!I=x@#yl zE%IE0A7J(U|D!|oI~ex+M?oPY2z61n+HgIEm6$ z)hJcv{DYrn0zo(r-XQO}eLR9Ey>XY0^+pJ9nC$qyk0~+?STwDu@rO9ead1S>4r!e?%L# zBGQ;!MqFmq(=Q5q+fXB#P=Mt0r+U=UtEo=vd9z*~SwKeOe$QH&ebU2;bmnP}O7%q&3+1nr&gbjP07|P1;~jfyG-&Z>R2b=kI)H`T1hYxnrOo}0u}&ypDHz(%B^oK z?e>)-fzbwPm|JV%-@dtva)4~LqMZ|Xd*shHuz?zoaQr<%LMCQ6ZmB2_897%EN2|r6 zm2AnUJw>>FxzK@*A?H8GAc`m;i!VSpzEDg4T(_s`?!bNM@yBhn9hqOa16wmSCirOA z-N|#quSupH_(DAC6j|&Q23}}4leq@%8PsuTVrZV?Wpt-LSG=@#aC;5?EuK|h9cqW} z3yDc9qEf>zTFF}+2u;#SbF+(tv1F}{fBQF82ti?{W@W5k2blM#ipGgJIZ2xM&H+(J zaj2NiM0@~{vJ|&eQ*y{*Q_SfktLpRy+aMk=8`lL4I?q zAxZl`WbHGoJ5=V%Xjkl!RB~px=7e2Q$fV}cjCb8MN=@unh=twBaocRTR6tN#?IpA0 z1+E)*i;U+qBx%Fsjg+TFB%t55K&0&0yF1~Ce;+|S?FYWjn$0u<(A{Bn&*y}}&ZMBB zf8%oPkqn?E+p)U^V>SLUl6q`&dDVdb!EZz&_GHj;CThKqMy%vegu|T4u7n{IA!jP# zqaoUaZ=M99WYl`pd_*^KI7>dLG!hbbOGqV*BC=%`89Rs!>_65Ngt08uHMzCDQajGy zGL!xx(hrZ)%6X4d%dh9tf=UdInlgCDLj$tR@CX?nZM20OQm#a~1p@FKkhEWQbzn2& zv@AQjxIr$lvSC@MjWL@VF4?luA>f3NCb*p6+Kqe*`S1T)+LH~zdRmQ3%Q5ia=#Y*y zL8GO09!fUt0kv8|dKhDBF|MerMP|yz{#}%*B($Lm9l8QO{#7Q0r8Z!_ffD0UCibP6o@iU~t9Xw8c>P$MXySgmm)16e zJm#m6PC+{AW;dQ|vzfkPHl-&7Lc(l7%SpND)C!=%V&)h()4gB?TONB}6?5z620bv) z>%?S0AY{VH8Ul3~;*??QC8x5OG|F`H-}`kEhPjhgqHv9wUro!vC=K?X;C!;c;Y}3g z<&wM;GK4SzM#1J!TeHT6Pz-_!y^JPH>umpHn2r(H(HYJLWw6E2cfhu~}Q5z2)g2zb4c# z|J$Ec08oqwqsaNFhf2z7(*EdK)7O_l+QT35w1+50=cDb~Ac8`0XZ8T_j8=SAPK|IW z@`!*aP(JUVFU*GKL{pKGG_~s?quw>)Z$*>@D(uu7>p#O`7A5vpM1cP)Hy<>uHo8p{ z|3;qWXHMx)4xO&xBn`$$m=jnA;Cyi|gx4qXbj{1tN zT)=VK8M?1Nue|ZQZY+ZJ80b*5ywxsV;u0@;wXI$z#5P|qV;9U)ArV5i=$qQdl#D^|1lSKQ$yH0Je=?ZVYJV<9r}pf-0`*#llQJ- zijRHrmerkFdw5JQlf>B8(NuXDPHCzMs1P{ZHjB17o#vw_6R-%eyu6sGAE^uNpdX=%c)A zXhprlr@lN{4l%vU8>DW-%G5jL#FC#r?Sm&rzssGTTKlD%*!xx9z1NW+_uzxlZ#i15 zN5AUUL(7yNE%d$De$tg_uk@~0&*rV~-pf0`>#N;u4`2PnLRsE*k01M1c&E70lOpGi zf*|$Tx_ZmvvDtm4x6G<#>VHPEpYT-?f0!WIAd@0YV+lg!zs`*?OaZ@?+~Mxj!tQ3= z!)BwM_6QT9B^#AXkXz&J4m%Ul$7FkesbQPy*0vP94b@LX>G&dT$JWJ+EBRnMY(i0G zpo{_J1+#+lG%QP+h!mjPx1_Q&Lw7#<;-f>l-d&4FxUeWV?_HQ~rjZVwURbGw4Jz{TL58^aWz2XBlL=Cy=sCtjR$rHn! z3Gz*6NNu#4+N^uM0oJ-t+gh`adh)yNb*pE+Th~DsHC64UHFb*{1-5>R+kfQSec4C7 z)YE1?)6)(jp~U6BJ%{XKOVQgjC;&7-^A>b9nG7pcobTf`FK(sr8dqq1ol=~t z{pAm4S;e&#F&{^!^LKfdmVCE-)ekvFI15SiOEnf05$&tO(NpcyKl3wR%;*_$kflsz z756y&VNM6%V+2%SJ1J6$yUWKkzxtc=$g-XPr4*nY_ZoP zfRl`d`8yq~5(+_5-yer&wDM;8{8%@`|NO`ON*^(FPQIo&#x_b0q8s3eFS0hqK59Su zjYUW7`U+qDuCGCF@*ThQ4NP_%_30@A>Fwc@K72)f!k72ao4457Yho*_`mHWGRUT)3 z#jo=2b_S;$sv=6C}Z+Zr4hJnd4vk>4tKbUx81iz z-FCa5i|I7NC}%q6)4yVOO@GO+Qd0&!nXh;l5_A=Fbl9G>@<=mfemxW!V>n@f;eP+2 zamP8P`|nlc@BPT$g?1rt%MZOghX3htpLVB5O!XGKdW~(4@Ub8NC2m##U?;QoG`BHk z9=m#pvu8H@p2Wbspx@a?uioZ~Pkz*`-l~XuZawz1yha@xE6N;wlTE%xpH=KSi_N65 z&8~_IEhI!N6C=DT!I$@#EzaV;%(L9BWTu-a{>`6m0N^~DV-sWSD_$V-v?o$7dz8&wW4LH;IC0^AUt+<}mErbAeT@}{6lqrhMBK)X3n z*Yx*%1H|(s(f8O;4Ax#MCi6uElwOxwx_H!Am}w6+m+@7;-1HkORw=cwAWESX;1^)m zIBCu9_Nj-_9AAR);RHaQ4tKiS4_}&UOffCfhxX}Dm>w3`hJ~;QaQmzv1UgNIjUgFl z*WY=1MCYh0l2P`175RIASmPY?m>U#KrlsnV&41KgLnfN7VF(brs&lK)-1-r}2KC34 z{HXZp$Lx0v!HXewp7NG*=-2G1J;yfM?AedA-N@nDoba2CGLL!Gx9Ixa@BH+0;;4ou zc&N)g>Lx0i+N{Lz(J!$njsUNo=UmNumcqH^}||Pq(~2}@Np}9+4IEVl4@y$c}j!b zID@S{!&Wx&5?{W1*tuqkj{48q-AoXb>@2Q@fjv3U+8$xI!*>)d6tBY;OPx~GjM+Bf zv=WJ2WG3cpEvlO&reMC~>63^mAlATRrwzm}$=`qEFQI<5QsVd~7nFpUZX` zj2DX?kvY5atG(g}=R}--rW%rar`=65qf$sOnn>;wirf4Zb9luOM>_1so&lF^_a!_~ zlS2_jME40#7|EqqLTt5eo0wAxPki|tTDsyJ);RtOcl$N7@-GWn@341x0U$y@=aD&7 z4=`^J@9<$I;Hf3$g;RRm)8QU_`;F5Azkz@36CZqDZuh(YgckwxtlD9VBM>Z17St#%Pd-N(Lcg%oWg)1k#* zVeZ@A+aB)irY-KWn|-^>J+8QJgYEWXZ&%!7Uv|1^AJ<*5`LF#1SrM14aZgFz>XT0* zz*0r)nC>{#OB|w$hoh8w5n-tEY9c^{@WSPhp7crRuCsW_C&$<*@|b?5EyIlU@=}O} zkNi$ARb)AeQGB;S0*TQFVP)C!_khd6V|L%rk&-~ob)ToT>iOLR5ltxk@PGD`o$ z^Jf3|zpk2TYMN<+eCet_HpxdJ!2rvCtD!#X5+8l5k3Q6;UuM_QkhzY`DOs>>QGUKh zoR%85tB?FJ3RaK8$Q9HMY(^hCLN2#h?{1y-Wg0-V)tOgTDg!wjT%!=lyO`kik=VbGeUWhr`a<#cpnQnU8sBtcfC%dF)47scBZ*2`a!TF0Nq?pafnM%o+tU?w1(_ZUE-zK&OV~bN1oy% zps2saTU0ewmw~EGeZ;xeQ1Fx=^{9tuknB`B#aodlpMPeGxy`AkF&bfjHE;baA7lWs zXo?xyYpBc2?X2-;>W^{e&>y1n%(gPhwDI~jhkneXPjj0MK#)1)(w1t0q$5Sr(xHGiO zmAl8MoH>@NUhX9Go++NWUA{)W1CAZ66_Hes;^>_c| zs~suZqka0j-u)#%ZnL|)ifpQ+QpdY}+|{oBsk0CaZ5kkI9ZeI#R8jTN|IRX9#r3F6 zn&LJ@Z?Hruo5{Sx|0)YTUJ02E)ZgDcR_9v$W#-%%vvLz zaE@b-Rm>)dV{-(^D&-IaN`lE~k$=J?i@;9BY}j&7^*kC?kf!HPN01yjKFlV@GZ=dmLkGa~#1zkjdtL@`yk9yo3pZ*dbw~LQ{aZ`+Udsu(v z#~wM?j;OVs`6}o5^iSQ!rylOquko&TEX#6xn%nOAs>i>(sEb4>nc$ipl4MWO1O7X& zD#5~bfqfyd+aAc7##Ts%UMGQz@w-h%Vs1}3z;}5VYo#0}#UM&@9-BxtO?JY;gYwc_ z<2Zx~tBJB_3!Mb0Nn|F7&a0|kub6ezbRN8_t)}QHH{9UXcqO|eYjba|>lXULjO361 z(7x-TM`9=_MVIR=c*jW_YbnH9h6>9xaZS6>JV?qzVa;A6#3~VPE`gxgk{#ZdBl_7s>&DC z0>e*<$n2@L_~I^qkO$@udl8N0kGkVcVd9p6(QJOtr@QPG zt~%`*E`RVQ*AaKS1A-4*Hd!&i!AoCft+|$7PfjB7Di|QM&4g%G8D-vq5S2vSB8hMD zs6=Whf2OmJGEso#919c3eAKIsvI(Pg*~=(bJtBJlt?mWr(UExe@K&0V63vP=zmQVv zij2?{VyY6!!EyJeJ;Kfb8$^gZ+==*w*k+EXK)aXGdj0pG{FCiZpZ_sfA^z?^Eguup zBR-12r;mKgOMmpHi+ zO}~4@JAdfun2&w<&YJ!EQiu51p(d4|-pr66-Pr=kmr;j_YBxo_b@?e*75?PkT1{_- zH4%-Bv3&$~y(89A+wD#)c_Qfr&%^S?u4S#Z{YsZaY+uC4H#sD2>xJBMw+A6?z0zLp zcHy6{sibz++aKQ}i*{Kh?p zleWIvn<;Bc4OU*RcYAlyNrpAtzX*{P`aLUDn;lkr0$3zhcDexk$;Hq4igza94+`Jb z8x$h+Rj|L1utcH3m${|iLZeL5A}fPtwnF1uE|d{l!}luicYn?*pc2KrDB6on1@)3i zKBoIlFsgcqT|V_8kXtm#$^c4WldKLq08T}(({nSFcYK?7dTbX%Kk5=wKH{>ExXdHo znj-`LQm=;y;%(k5YMJuu&@TlQwaKX;(cuxx$GrX9KJ1mis274sc(vx5Kz-_#PFq5* zd)j%j?U@g<{$NF8)XA6IBR+Or^fIj{o67E(L|}W8#Z=+X_0)h9J8B6f9A)^u9JD{m zSw`X*%na;`SAi>-eX3VJ2-wT;`)ArKbZ-H3QHV0?MxQYjm*bu{f4O5l&%JCAy~`CZ zoQgcvVT)&-d5fjg6)tjwqt?2B=v9^#6`yH>o#DcG#nMw|9m}Mk;7&4&Nix~7-!?OVOwBQE!pyYBT;yEuohn-04AN8D}eUcba1-sJ_bM;@BSz`ND9*DJ4? zhHfhAt}Xy|>xo`f`@sA%Z*T2vpYe9t9=^M4?UXpUX2}UfC^VKCuaZ2`i8Y(h%#nHy z#0vs3OgRk4J+KjsYwpnK#FwiFJC+%UOZ)c!3zRCimG!u*)_sRHn?YIomB&O-28TQ8!i#_XZq z@#!o-v-@(lqvdw_51CqvgY_=&zF<>eX8Wkgf7)HHeyJ#%<+G(mG1S&`pM9&do#8_) zUyrTT*)LJlrY36vz9cpDCHDpcbk?pe>MIdyX)`?XtZ)BmuYSpwI4!A4LW#fSCAO1< zg)aCW_b`xgg7vT`s`t1B7c@;S;vmB1v)wWAgkl>l>je+u>~#XHGd8Wf7k@{?mxp>kL2 zWLH?^925hHVrbbo1|c9s&}DI>kX*MC`!vGVY(`%<(r?L2`g%7Cgd`60A~>+#{t-oP zpx;`kTSaOyTWP7H2PI3pw$U!lsB4zjvKYL>Ba`s{!);SXy=$sx8})3v=rY%>buK%r zga2UIkds@WrgoG&ztQlPce~|FT>mz{R>R|n^Rk$17Yra66JmuRL)wgw2HR1$J|=(0 zX^*s7PhznXvWp~eAxw%_`LVmAm8BOfglrl}n|xc2zU)Y!IN9 zMhZp+V0-Mf1fYQLcSK06F26NiTR(PgfdA!BliOfTHSyhm1pg zx|hDcY2z(!BRq(1^sBzZv+ez;VU+kbuYQl+M7bsB{5-}~O=0s-bF1(6+FE^YyR6=g zBYwzf=y4mO={vmhTkra1E=`weAHBau-3SgA1`2vrhq*-?*GT5?kdMezsB4q zkv5~v!jmL6wTWS0+dlfr{+l+WCvYMG__~C;TN|^6=;Fs*2o$<*@=ta(g=?wR5w?{5 zh8ui7$DF9m=sDyTz#C)|>v~CYNkbly)x-utmRuK?p7!AWB9y_JB*=JB zMWkv|T=%09EuLmiVtC@sbr-GLDXN7-D^n&9V3d7=CyX=eAyV1KutRNa??XF5Yt8DN ztO!oxTOvMrV15p~6JvC!cD8TB7U$2tH5=1R-G@_6Jw)LbR#|qfvy?#*nzI`wMpAyq zBB7<*&TXbyavZrXA60@PQIbngqGAWD52nTk8@6*?+tsh-Nl$FFVFVu>+$Fl)PLSG^~-s?*Lu3x zoBeby1q37>{x51;0}DzxDiBXTCwLnXYznqP0kk#v=wkBSZZR)54FCYpXsClqV5&jZ z+B(!S6d;zpo$VlQ6(kgH25Kvq79Ln zZ>~#iIOA;Ou(=Qj@Tn^R(g_y`UCg23-SC735DGIW(d>kgjOu{3B$!4HwSn=9)`M^1_X80o!9k$D+w$+_mj)Za5fu3OqF-GY+#oNEARqwwuz8A_;~ zovh5d+e=J;sDqHZwT*1xp3;F`GlvLKc$O?fsCTv{GpY6h`l9(JOWDDV|er$|N<1PWbo<(vxXfV8;XE`5aC!Yry-^f3gQsi0N6 z%k66DxUgM!nSuuSMlluPJ7<;6Bhh9P(v}i31`o)O!aU0nY2AL*U zAS<4ZgyaJ@G4;!T^y|jE&3b#dXb-TvxJO+ej?s04AQ&wBX%lLeMyPJsBa}UD0VV4|0E6+Px|N18f{e8mfIF*MgiZ7rM)pHzhAU z6~ozGO)6Ts>D^?BggMY!m6seQ%L5a*y1-7GpePY`*>zCmH!uaG=d_o>yuF*tCNnXl z=nKmM&B|psy7a{_W>0-#OtdSYovp|mA`2EKQk5`aACO+aS5UDopn%0QyEuIgWC_4$ zOi;{kN!K>R9K*6M|HHSr;YgZG-6k#&@L@KyEvM=@!=@Q)ML<4EXFvrxTt7Tl{Lg+%u^I(+=^`(^OU`atGBhbB za9zZuhKi=Dp@N*^TE9dllTL4nFvn^FGfl)hCM@m8)G(%lJm4(oZlc2Ok5`G%c!;JO zwtpSK@DPEpFdW9MI0W{S-(%f!_|v#H*r2A(Zg>0_M2>aDzmRjlIi(tq644R8dbxe^ z#IgTj!mTd3YAd$LwS%9A4!Cq+4ipbG6FjGxPzwUn0d-z(*O$EzZko%iXWtH6%;7GP zB}!j@Hgix*)RtpF@9UKpw%QY4fi8$bY_fye z9!P^ZYyk76&zIOF4z`nR6*K^@6Mp%X`O#9owc{y!8*`HQx?CIdej-qTjH<=!o)x1VQ+W4 zfhytq?yv|?iY;QwgE!XRc87knlt_m>9j4!Gle{GXV!?qMjV=HQK3)YO88Gs)Y!|5nF?&)MCE)UDVkwBO|ZG~V+(#nIOk9r6m!b+rhCTL@Y5U7$j zq>y<;X4xe-s>ZnIBlnu}P6iBs$r9~FJy^3p6Q6D7Qb#Sh<}LHHhYO-xv(9uf>ADmM z-X!@~ej2c8yC_tK<1E4(%*<|v95R5sP^8wL5(O2mSwW^!qQHe5>RN!R`Hkd6KR4VK zAov&0^gnQFB8^tD>qJda-c~qwURqNwpn(~Tu-}ne&|TG>+xS~Iu-V;0U2FlBz`hVh z;F7d-zWa^z=hX{VN=B$*0+6y(frwdA)%V5uKYwni;wY`zIfpzlz{w+RY35G59n3+V zCFWX7I&)>sMyDZmbLE=%1wPK{AZ(pdh>L)2+CrFacfmQH?REzlET+~w{2a=W0)ijn2?geQB6n1r{4i|R!Vrr@)S#*LFz zIfK&z_65RsC&93xanV>OedWkXDy^BmLaIX2t&CbnUFG4{7^bNzqj%;|K;yzHjhcxJ zP4%7n3IeUM#DZ0)msPQy_hR%iLrl_V$4hcnO zfDzJaOInd8?ECwo{a-&@Z)&WRRc8n_5^n`HKi7Qn>=1FoMOcXMPIoArHRh^LG4Tfx z{gg-IiHRsN@O=9-t&5b&=iV{c2xb#U3Vi26SqGJ~EYu-j%e{aYD`J`$j=V9NEF$T@ z$_4T;qK$P*Xeb{d2Mh4s(#Q7qoTRb+SzweF( zf5C67d5Cyu19YLlH(kpBl2+7c80&|fRv3shmXn(uu2ROC&5iGo^WXo=>8#8qJTC1q z>OOmo=5mSb8TQvon(tW-z~}Xtjc5}r-sR|9-$pBR$m;0wV{VQHOEbOew|dOWTg-(n z2BfNCv@E<4AM;x7FVI0pA3?b?%Iv*%kU*o`5@xN4*30ap+F|ax_YF7Z>?LOOkZ-g9 z>7S3I-{m8$v^p3UZ+~ipNw?TVfxV-UJP>@F6*~~Ru&G?Q6SKWrX0eBj&Y~nNMGgcM zQP}*dx9n;HFQi$8Y&U%pQrv7l?oG-<*wdu+Teh*=zw@iSbW|mbJkfxNB71m;n~aBg zcV1BkgD{`|%AdBRSMwaFUm#MkwX$ZnUYOPkglqw zUGs!gs`8@bR0t+;ifH5^ov7-ltqqOqS0+vRcv8_POV}Zi0dNl?YKakZ>u_fEYAtCF z-X4$?&xcb)w+f_(fZRKi5gOc8TrRV1$>WEQUf+eN2`Vrho2CH)i3|S@7fi+CpUWfP} zzpi=bc!OFw*R(spQN9qIWJS?C;aloZ(d=bqoOaSL|(mpJdd~A6OHOGA9qMEPV z9yz5$(0-zoFjb2IDsAp@%bir)-?kzWf8>hpLruxW&)C z#1%{;vmn~(#%c`FkRN^!+(&yuobr-cgk*`e$j+Let1Uo{OoW3Vyv`0jP$k-AVNa3k z(f?r&`riEipI@+v(?rc%?s5}jmA9dQL~H&`TiSD{z5(=J5(89!=E)c}bNW3g2pE;5 znNBJ+G1ZZ4P3g#0pZ3Z~og(ejP;#!1|t@P4{Nn8Cl0-WrRX{XgpMWnUW_K+b{kshL) znPwNjx9PALMAO>Z;Wr*Ls!WKyd`hio10nq8NJm;ljvuD4KmbhN)lYAP##^5NzCQs-+RWfLN~yI(o^F%?)fPj@jJyk zI1}tF445pzI{9iS3(qp`ut-YD#Qsh{v$&0hbHR;#EJ*QH4_1Xmm0d}h(T6+SY0Vc0 zBotK6`$||FWlI6^xv}mZSHI$224JOVeJ``{2wvmvJ#WQ%h`k0-p}FIUR+0dSbO)N| z7lI^qBgf`(*Rzb4y!@j-Kz^H@2_<@0NQsTgyCA8}Vo?N z0sAa)gL>5d)DH{iyq34O0*zFayiI=LAC_4u^ov?F%Z)jvkS2b zv8HMUUsB9oxf&{6-<5V=Wr~!;25o>czPR72-?>G`>PStdS?8+;#8z}M)VN6tTezLI zVjI$#)L83mwZxc0rAGgUS)rN?TMlFpAS#Fpnz0urG)VE_uAC->6wqDNbVI!ckmXI# z5y&F0Fgs!t@-O}RI^qFr;wYw^mkR=>?7~3N(@-nQRNL=BBA$P`I$#FDVu8<>N_qIo zRzlNKhG&`+fmwP{x;qQo4CdK-jTTus?-X7Mug^7xAik2gU63Cu_!Q_&;P}0C%Y1|c z%s_0Ur?`hz2!>+KNx0?++7R!@`;W&qnS%G}G3PN}7{NLZPu~pqN7FIrnWI+ZIbqgkMnVqzEPxd0f^hVV4zmrlq(AJDzQ!0;cZ9PXK2;I~OG&jD1*>`YoHh+M{3IJE z;yrE^>MQ&GUXBF*&=W{|xLtUejTVHp?8Q-O>aNLBsUWUo9IDCIN;urkj#k5=$6sPS z6FWB#aL zLjF^~nRZ*kWnXk^?UIVyqM~hS_csUuu=zn-YieR(QdMv!9p()KC(h7Sij(2UQfA)z z1pzb7v}@)~##q>yp}Qt-b+5;~B=nv=-0|e5PDhyLNk_Cntj+-;Pg+_8t*+eRSz_@| zx!_NySH3d|c?{Xu)!3dM{}3$aeM)8*H4_KpgD*ZrGerIWd3uvxTe>wnYu0rcGa^>8 z_daP`S==n{&68w7CN9(0HWIRpjT@HeB4Jr_4+LF)0S%x7{2c!=^vr!PjZ=Kz+0|MR zG3R{WcZ_GGSN`CJ$S?n)4sLyYz?`(x%#uq;F&7AcMHakBXRdfkh=nghV7%oJ+U21k zG%pUfy60zl>#jNkIzm(e$J~-T$6A8cII`l_W6|&!zNR^84dN~=@AOeV;@hQ^_5*(C zsf5(wBffB0%isNdh@!@^um>n)YuY18 zB1dG!Qz6(u>e$otEPKh8JmW1O@!jSjoyw^6!<8oqsQCbCqjNfA6T#{@T?-HtoAUqc zw?=K*%(+Ee(;l$OT+CIz^hcnpMl7~eGQRVN0LwA#VY;00BfhsbuM7H{E9OA3(2moU zAL0dnX-|dT>SemyUHbe^FLRg^;}67{{0<*n57=HfFEZS|(Z5y*b6fn%}MjT>Q!_gLHN2%tJQ0GN?=ddr<3EJWI|;KI+vX#2#%u`gZ&o z10+0O&t2&(CzVzvF(Zop*T2;RPCJ#e5JF&qi_CI`2(m-e!70-Rxs`slF^id&5CBI8 zv6*JH36W!TJ^dK7AyVsz1Z?!Sg+|8|sv?jx@_DBminE^WZbK}h_e^tQQ$sN*vcpgG zMA?#4=iL}>Zu-(O2C{QDhd#%gbvDuortj8vGo~4cq6FEN1T@g99sBzHl0J}4IwA}b zoJW%El^8KJD}dN((QAo-ZxhWC1;x~pgdu$NE2YfP7yuZIo>0yd8PpgS{ggvKI|UPb z6l|73Eq~>nA`yg4C(4n5z-NvtJ3z%MU(N;yt(Mj9a^|73O_asp%jt0b#b(h!1o2&g z2J3y0#5z$3@Vy8c=e1UxU71cW)Qys0^P>8kwJPhyt?nVGCts-_V*-Hm&9qkg zaL(8%r=6_QW@`E{RQcTx4`}c=30$r3_odK_WeUN`aLYH0b8drHdYHvjQd5Xo@&$f! zZ*966<{W*DhmgM)nUD9;4>Tr{rZ@>3rRa9_j_NShxc-W1gvoMHMTsT1ER=?nx$P;I zE)rNmQC&B20N@Zw)=Fey=t>lE6yM7ZTWPZOR8u!K(^f#nrHjekRbNtzZVye}Y??l1 zk`m1%QD!2^eB{}Dr{D?tw%4AEPD)@86tA6>-80Fb>mpaxXsYQyqJ(s155a@uGc)j8;8OBTmd{4NIP5Z`IR+ zXKsQ?g0@whf2NWpd3U%|pYq#Qmko6kAu;q5@bqHH-qu*q$TGNY0XJvm5by3FTUgyu z2IUxBll0@NOC(~sgezFn8QBr!bUN_KDAw^LGu~Y`2nR%-Upqfbco&@4QF+*v`g;&V z9YO9N9xcw33aTzp$)9do`CnZ~h9v^nrt8kT(GstE<)$GRlwz3oLo%w>THvvlL)YyN z<`6Hp){)O?qHlRe`FkdqLRSTejq()1DhdN43{i~9HVJ~`A_H*~S)>F`29C{}ybAIU zsU77J4hM^~JYvhS&l(LI(@5U0AEs{v6c}vcwz@H< z0lWwOOB&1uW?M!Nd-@4)m_~><81^wnaAj%EB-;0z9S)aa7gUTBF(smZ)u5&+EF*=M z@jugtkbn5Q`oaF{Rw(M}Ct)tWc?a&wn^2v!jS&rVLLY;((`$g#o5hF-c-KUZ%4}od z{cyo8^8Qe>s;51IbIMn<{FE=WlN>52`VD~S83;ipms7bOnma=ITlPG}{QB*%)eOC~ zw=WO$y@`j2;fgPl^o;L#hSR+j3PI2X!ban@))J_ppx*tM39&S5#ahHrOFngxu>za8Lh;5BEg7!ZlFCP?Nx7F|o%*=qltvy!>!} zz$kE*50^&?RsA!*(Csi27R)-|;pHCw8CJZY5u&UzS%Gq#+$+OzZGO+3{4jRdET8Hy zPQ2;6Y2u_``qCDu9^!6P7;4%`U7&6~jnmZ<-#3(lPO;BD4|DSP6Ba;p2jF@2zy!Wq z+Ch!28DXhNwu1ns*9M(Nfxd2-vTRo8A2>REUBcc;C^pQ~-Wq5wur%0@sJ{knQD%eI zh>K^7$O*7JvJ+TBa}mMAwox4dVO5e-o`N0w5S`T9z3||Un4+79c{7@|5A)dZL2wA( z&;VpJZ;e+lAi(+=KZN|n?-;9=H45y>LsqX8?3|%*U98d7SPq)H%1TOvM%KjNvVLOg zGi3H4D>_xvoOuonPNu>dGjBN%G-=X47b_Q$?K()@=Lb*#ef*mH*FDUFcxezBj{WD` zGS*k5(uTD2x`UtgZ41$UtiPd!hkdxGClu~UJd=$sdTTEKuqzzkzT9n}?%^NQ`-Yfx zDOH$e(l>S4k^H7en*^o@1J{A&dYtU8bw#UT}w@+?b^w`jlxyYnEDi2hNzTiV9{plE^s2 zFB$dCGhko?N)==$a$Fw(*-e#IF7wcbfy~|1DT{8FC#Z_*KkY+QK)dSF$!PzfPTjvP z1BCzVGr0KBZ+CbrUiyQ+Bs>rCVLyG_MB`+qt?ad2DgBn8zIbqN7gHcdsoVTR*j<|h z#Nfb82$ICp?%{?{OHza&wTW7js73kKpXx)rJt;&ac3Ww8>d;d>IGjQB+O?7u_yP~} zDL?iho`b~n2ViwtU*c0d`9lH3qTRl0aT^fmBIMj_%`bjO-_j{$_6SlgWmt$bY3)XqoL`sWP!irQ+MmsTRf1o+1q6G7EhFT;E=>aTo|-06=KmD zBraEDvrFFcVGesz-k4<%#81H1Y1ts0GAxsf{JLd9td?Msw|d)QmSrJ4p&LnMqlxxr z@E{aR;!kab{DZi=i#!GN%J#622Z#g6J{<5k3Gp*Mg#5?9uWGQWL|USfuuaY(7{NHf z1ib^RvLT=hNwGn~En9Rp1|rh9jYM$jmZx`7quq~=o!;+I>tKnRCAu8Fu%R6)sXez6 z5T`M_ItgRil3)+LUCX5iqZa?AJ7PAM?)HudT#|#}PJY~Xe4Y)bX-vtG8N?VbP|0Tb z5$|{s9=$Dm*`3aaWJknXb;ZNnOoX`&JXYY!@G|5LRlVJXw8wN?cj$5R(+%68lYmMD z-)s&ugw@Vpp6iIH1iZXqFvpNwd6^7u)8P&TxxbMFRdbSHV@!moF?<`5_x|q&qncry zrEGL8GxUoIdL;+STVXOoY~(}+cDUsxGE}!rjp%9bA+8jvS?*`nA+#2a)+ibU9)Msd zxbYKMlDe?iJ5_>H%PdmK{@fp=+6Y835twI4p0>VsXJBVEribD>iL{PHHS2&eS!Lbn z@6g@NQNr z9d36C2)Q|AktHEei{h4%wLbzs2PtQR1O+gfxTH)Eeb|SPfAv@uFrErKDA%_~RU!E6(ctRjPo-UfU<&J$`A z0rA2F!|e)ohCwSK$mkjg403Ag>Uga$wgM7ORM^sVCN6=I4{O^VA_%;1LNGYmT5rA= zQK+P-@I;_krAy%C_k>j|QkVF8VLwC8g0`6QPkxt|V=NckI9{0|M2=}`FyxH}+;A*S zmbyyuv6kd=yU!t82BwHcSIVOj)K>fbbV>3o_iH7LQ<6}^qf55}z?qRibc~Ukm;61O zvAh6OK&!t+a`)JItyBv8C3+n+LoVI{YGfAsNb)nXGl`BY+MuEiAgT8aM}@^N{!5L0 z?Y=YvWXT=7Ul}kktVb**lpa9|(m4$CY9RMY@3>iXYTIxCK#?mIEHxtOe3FG^2W+BO z-LIKmFJ+$KYicDIWFM{quZS}M>(Dk*BR}UFY3X!8nIr|RCriK!vJ!_VQggKoyb!MA zPiv?A)oCbM`SGaXw=CJ9+C$*~kyrF9~Qk{Gsu z(O}KMRg|kgXko^l8z4Y>&L{&s(5Q&#H)1Z1vDX}jxjY7vlC?S0$f)E-Zn4LScK0XR zYujN&n_iB1*&%^~P5PMhguxZnyBsv3X?hc=QtmQqFSoe#b_Nh(x;-cN?$jOW&SyOe zyjloO3xH8Q>OjCw4XC_QWRWIcw%%!lipu6ku6GH19!jnzvIJA5Qk zCNFu56G-+??0$ds3mwJbQKA%`JTsl1OOgiscM{#87l267b=DA@Th1{nqTdmApbjN# zSdy@3k~bPh5Vfo!)DP|`GK-rA?Ah7e6l`et+ zLBr4_Lj~`q$Ed0}!qJS&S6Q?PI3VOJpZodwt3M^V)L2pT${E8px&%=adF6v^QASF+ z*UntPUwV-cOfRFfypS84C~A<#rn59>RNyBjO~+k%X-{;Trml(k@Rw+M$&M(&nc8F4 z%U!#vTI4WBRjzS7Yga6fl^|Q(6~?KiqFQRm{Jlq&7P0-?fACwmd$rkTS#0oRIS>g( zbt83{wijMaNUo+xFtH1SL}*DX?Z|NH2@>;#v%B=JXyK(M4U3I|fNt0Qs3AKr0QB5L z4HF#S7D#~1sJ1dUVH0!RA1v4P%y)Ik1O6&Dd(KwXCKI8XNh7k!0cV0#YBzR^D(DB1 zp9sL#u-hR+AS)~v&n>4wdH)=@kbm)|Yt>DGuqu{?1LqFZigXPy!>Zwl&yKBC{Zgb_PymN{@!h+yrWiy3Y~xh0?o$~CFOzuA4M zSv2EW<8@0ds|a>=`{a2pJUcK+gcQL$1cKFtUHY_>lb1O4#au^EF?X5*(QKr!1nKeK zw9q^bRZ@8s5ula>gYow8i~MYgJ9_5Ynw%ek8y_OLAEim zKN7^87|t|_$i@U=Zg<9#4&XLHMaPLP0<|Maion^Pj%cmS-KRYFFgp?qZwZxI?%cv* zq@l>Mq{>;PY+~Y_G8R+~J|8UR$yFM_EE6&M#K%ozYx_cUA)s(}) zP2}_x;9!$IMW&ykfI0B>n(hXkQMlT!GmBE#@uq~{RgqVzx@%CBwk)31ut-~ovta~! z=Xucf;Ie`RO8GMLB!XPV#)@B)-F8E4^&PE$)QHMSl5gE_P$v zc!2zguFYOP{i(hvjm0Iarm5ETCB7A$oJ8SOZs8V@-eM4~K*Lfk9RMNdOPo+x6$gz& zpCEQgW8azf{GG3LsRe09PCeK|B8#j=s=rgPaO(hkkDe2uA71M}*@oL>v3nE)cab z#TJ??bfwiT&c5muB1}ehdL?M;!2+-eh_6k!LBx_boq3yGg|6pEY$3V~Kg5+-H~@P1 zJ9oY5*RZ&&TiiW%mHh!qFARg#AP4L)IeLg5e&Un0-uD;`Mh#p7#%6FzemG-@lI#!-{Sj-v>hk#b zIN5ZQais+xeO>jcXTGR=9P4d16@3zb3|Ba$ND*de0S;(p>u}DSUANp8*cSmjjcI-; zCV8|0o4Mi+KXs4OYwh`Sv`fhU{_mkaP1=kiOjj#9DT?q>o^H{hGKW0NX=@1XR7XAS z=}tWvt-9ojfi->lwW-A}1hwi(W(E7LZSvf6=TjX7vjFjvXI!?~63@!UJG1biEJH}K z`A2!wRAL$A1}U$q)~aToil;bX5)I(nN)C3Vw$O@4F%m}hF~?p)9dYjhG&wZks9w8_ z)oG-N=?~7SARf-?%|kEn5osf#eCx7-pQewANC-S2CgRWnV;!i0EKqy4ublp-7{RvF z#?)NN_oQ#YIL+5y8#OiEcFrUU*?*Qf=88Up0}+4-`mY5C%Fov=x@_KOsPOj(`W~wRsWQwQp7lO|Zaavb(mqzz?jINrQSe^`- zDN@C42x`TLkZm1k)do6(0PhfMn?Cr$rqWJPkv+}Kh6_}ebm^IC-u|QKINKHPo}js<3!JV zAlid>bGdWKB_93RX(iLaJg#|k6j*CrUU;?EJRbcT&+}gK@bga31b^>JcL7a*m&dli zKGUPZ^g)lDmRtAvT&pg)m&ao~&O!3ZbzWT^>Y46B)1%vINjn6qa;}@DVoU}+`&#`{ z36pv%GIS5Q+}-MSL>OFl!#lNKuFY|UvmE$pIvW3;E2Wpl1HAg|+PS{^86V=zJnf@G zdh>I_himiDK#81$&f{Y2?T_QAqc?ZV%VS07 zL|jdF|0G?tKhH-!%pneYgd=qMXpg;5Tj9+!UPqbvj90lT#nj2qSNqO!IvLLCgO&W5 zpZ(b%^Lad)KF#$Ek9wVVyX&*UT}x*Tiv@f`Pjw3|Si!>%M5CLt6GqgKW~YaH>VZ6S~ist;{7)1GYS*#t9#dLndAwU~3B5r!@9C zLN~)qYtr5z=R=YP4k-=EBE8UPhOBPtX$NU$2!p&UXur@2PTj>UXSwb4GX!UK^>EC= z<{Eecm>G~}MiT?2P=?b;Uo45d1EQ(di>jtsco$V>Qz9T|zk{XGV4|ex+p^wu)6)&Y zraAk0i(aSL5>u3A}qZ~)E#L=q}mt66eqMsrx_1>hmr7I z7|$3;8X58kOIVlvnJ0FSZ>D(~xqF<8r@hiLuz4W=H&%Kec>jkl_p zhL=@L-(?mfknq(Vxr_u@H4=}lNkMcs?DRw8dqwMGok<)Lrftckl#TU)J8VxWWrocw zhb;NdSGL|FmSX^c5sfO0mLJv?vVui3EvFEjaXyBe^srzS>1)!?aDl?mMF#Rbs%;nj z1{P%Y#yKMnF>(xX(4YD5{#+@A%9$hrR}N1cFf;0zyNoO=K4(x*HrB=oYLR#jE6GYqf~AP@7A!Ve#XseY-h1BwKpVW}yzI_+cH7#9hAlUd z!uC?((+_&@{_@Ax`=0)Cn+axp%RR&^fF3)xN{>jmGhIG7*LP~cPA;f^#go3XyHK;( zf%Y9upY_(3N?~CawLq_J)Z8aXY<4nHw zVtlKz0V5VJnM#_Ni#lh@t1l@mlZkPD1;1*46ur z(`A9hP(@Xy8@;~z%diJm4cYg@p89m!5H?0VY(T|xC+a0zy!ty`?N!d12gk&!-H4-| zuj7sPL^j0kq8fVDPDiVQ+C02T5sXJ7;|B$6W13^0?&bI4pViZltk?HC)csU% z^ATeX=Y6hk>^H9I;Ey>L6CV3i59-;Ai182o1bjTNd2&Oq`&g@89H3Nu#Sc#A*F4Y5 zUm2DBHP3O~=S{*&Ot~D#I-<{(;1N^}mpeU&jN@LX4>7rSc#<4Et`8yq?VmZBrJkZN z(M+6hFVYwCO(v7Y8&&(!o{MXLN8!>Q)srrGyy`hB z*N66Yy85V!2<~nMXwEZSGNQ32sI@ypcO)exF(6x1^j~=AMbfK07^alRa8>(Djlg#O z+S0_-?MT1g(4*im*mUECq+eg;D&yya^z^Ipf<&v7n%cCLA7D*iau^3EH13k>S*n5$24 zpELFPE^mAyx#g5Nt&!;%`KVaMTfC0rT<#g4r#^Rx$@cvab7Qj8FZEcU5mHJe$DCbG z$5;m@J?2I6<(`W8wwHPU!`c1sfAcG~v{#Kei9FRXrrFA|KGTzEwna@=2h37iRQEZP zUR^ce<*8`z1ep^>Hw4|}HYi{dc=uY!NR~w^g|^NiNxhK9i%ShlQ>4&kt3g~kBY*F< z=v}_9892jXpibG2G~ zIs4UGD`=Imzrh7Zu|zadj4rm3M(Z6PvF@{swkp|}*Y=_Ow|{ArDBBOb@;TBh!;LD6 zS$(EUMAe+qddBXJmZ8t-dBy<9$|6HK`|wx1Nns#dNNc{%tnt=I$F>vo3efqMYH|C4 zMUFQ;LCXnFg{v*nT8W^h**aUB?WpIh-C}-hVB5^dS+X~@RrO5xi1eX{zti27`As$hqsL+09?R6kep=mtC2X@RWw>Mu z)OlMYdaZ~JW1Bic4H+zRif!I=iCd6~GD}b0!yRd{65N}jkD(JKQ+fqTl?%f*Kxqy2 zU}DCJkBh39?oUX7GO)mgG{f*M)i`3MbH|*3u)7o@z_l9ynd_l)ukVB|Wkl^QD@*u)YahC(u?XwhS1#*%n4(z2k^nVYSm5+ zq|M+pVyet*N1K^U?_n4b1&e=80OlOBRbV|mR63Wlo9^puxJG1MV6YjO8>5;We{;kM zAL4W;O*&M!9$#qzrwZcph)2sT(lXgO4i30i6|gF@p6BdjDUu}tV78DZx9lRX9y3}z z!U)Ts7$wj}5KM85x!CFbjDx+7rc1se&v*laT9{;uB+PP?z@cz?THP0F7dLz($SBjz zz2*g;mN5a92)aoax$*KR>9og5li^rLo#p3s3;D19aMe?sa|zwO#(lZ^xoR8kj7yzi z^_FX0Yq<@GJhq-r_OOIkF`w-)CkISJ_|S?NJ-E`-u8@w>)KqJ!BY11_NgT2~-MMqL zQ$OYKYn=rJ^t3%4`t-Q57nWN33hKm@*Gil2A8k_B(%07CTn?Z0rk45UtFp2pMHg*P zLtmPwv^U97*kQlhKmMWA?K{-(LM7f`j*v9&lwMPS&^R zN_2_u#{vLUd||JAU+rr6kZE%8j&S~*Ze?8@_#O_dY zhNE_ck@#q9rB0`~)YwZS<~jDXn$NxKMjWZmEwRmXPqQzOzwVgnLcAHyX#i>+s+o0! zkh*{uI~_hA^QgwDG1Lg1(S48oFMsRr_9DeYoYo(1pd+OkYDc;H8V6vC( z&ISzjI61ExFH=emtvh*}OP`_01pI-xVbjJqko{T|l%R@DME;A~SvzLKLMraLHhN>v z!nfScu`!_;A+bpgpBmL0eF0+m+Gs1QAZ@oTp+TjGebvL9?qJsWb3TOpum1>O1r}J* zl>@@9kZ5Mv>QPR6sueDGiehz?Rf?z`BizgfBZ?#zvdT~Ze6L+Gl)4g@_f*lKIi^QW zi=v!J)>yo5r<_zDl(J40DW*HUA{typIsN@@=^$c-5DR%Qwq;WA&v|a*%-zo&1r#8l zW@|VU+Io-6hH$t^9EKe^`XIpP7TZ&g(|M0fBD!D->D@iJ6sDWa0%yby41&gmIKvnb z?)ug)ajXcTg`UZ}sBxF0?Il!x3_vvq!FVO1K?3QoR5AWxtE6hkbdoDeIW zU=?*VMxTB$Uy%n(qgzHaA5ZRC}u}mzuhxbLvO7$+X^QYo#;SfW2OQ z+s|1$`{E4ZDrR|Msnt7>v)EIEKwy%tvyY^s+d7MX_6woK)k>a93dp=A0jiyjNYg!lmtw% zVnd{rvd=M@>9QGN7;84A&pIDX2e$5q?J3&fnlILSawPC9XOmKO6+0iyICygnMpSbn z<(QdeQuA7d=wa7>MGHVi(!^N^cj`YOfl%22)wJ9#6a-hV$i2?@T8mS1U3JQt^Jx% z?@3C$M*~|$Xi};MxhDOdqE9ksxPRN^GK664yr(({I^En z*S{ZbHFy&(d2hrhs36RtaNH%4?U~T?s3hoj*wdd*iD>W|O;iKn)a-9b1?n;IR-(|+ z5TMM1fiaAaVj*9^)XdS$DjC&80<%L)s$GOq?=118agY_5zyHm?UmTDHhoxliIkt1# zCzqnPEHBBFl1J~YcNc7G(dQvW|qmxJWhzjY+o+MK!QMU`!3k*lT>{zu3PBn-?MyLYJ0V0IF$m` zh3^r#2X>$oaoB(uYc>1sTSpFCy1J^a8>u8NeQiBDHSW?^J;G&3Ll&JRU)@Xz%u}#y zl4QVqMHdV3($!vzKhs%Ydt7EG8bigB^ijBOPzLBoEWC$SV+4gi>5BwMap%@pB3bZfC6I7R0t9cb5Pk@_}*1qoGbohBc5C68((6w zFmSG$J+Kjh19Z+A-+8WFtDz*cVt7@_VnHvF05c?H=p~_ja04xcYvi9p;x@w`-}NQW zdsS@P&iXyGXCy{UrAObk(auF|zh);PmB|gX3vvi86$T>||8w3#{_XFzS!`|NB5#kx zD7Ac6%MNuq)TVl@aoH}fahBdlfCQ=9N=LMAhOMpU;YtFj7VB)auQDtl;nVas%CLxm zQqg^li!x0e@+!Lce$~=oJiDS@Hof$Iq8DJ6|Myvs80r9M;|z|aL0Qn*QA&^RL6&^k zrJTba};iwn z@@K64a;Q3veI07&LDDk}%u+;RI+6%-9cB={$|*37+UqO{Cqh5^r4BoyO1}0y)N)Z? z=o`kRF3p4=w4Ulmfsa}vF-ZsMLVYK9mx0ou4D=EG5w; zP_co33!B);P$G&@bXVjg0@ssW=8;Dy)P~w0E+>#4d~w6KX!kh>PEU8+SeH~PXI^u2 zDBy>aPtpTmP>ga|EHMZY=ritV<^TE%&>P<~U+f1nF%#=~YhYzB3+t6Ygng=mpgFV5 z40N87&eky0R16kqn&G`FxT&yv)BxynxqY#6rZC5yhM+;r9p+2 zjkiS#6A7OvE|WMe{~D`O967B{4EhE2E5ldFJvGf-y-Q>R)k~+4k2=el{Bmc%{LwE$ zpUDV6+EG#Sv($Cy_BFMr}ow%WxF zaM#MWTSY2(0EqBQ0^`fgp;U4&udKfxnYdy6&I zuE^yTo`aq0rqMs+=uCyr= z;7!|x^6z9{becBOb8+2nePVaiv0j92$~j(UZM~QPxJ-ji9!ob|G{XF0Mvz$m6Fa^!^&Di zb3mAQR=g1iuE}MIY3JDD$5awbD|me8fVakwnJ~K+D?6+i@VagIJE-V)QW!26?3v-vbr6eaD@2cSpE`;ISvNNrx>-MsImEyYT zPCJ&|@a^|h`dhUrohh~pK)^&(EW2px+2yA7E^&r5lZQ)!CDJf506e4Y!~jQ5;BdUR zXlzb}LS+g14@4J9O~i(fh+Y#;6~z6rBWzQ85CA1SE8qMqWw#V(o#reEYC zi@s&X)ubyEW-)#T&nz@*BP=Rbj6QJXxEF zm_OL>f!?@qZ%CsT#+jzIhOq;A%7Sg)<2V|du6r4~Q^E#!avUBzyj0DOr{ zc@&vw1WZx|O;?rh z?w$JG?MXGRoAO*j#YO7yqKKY`P6S6wdyRrL5T|LHCdKcx=MF0aKZ{oghG4Xf8JmL1 z4lm(1zkbLi;5U=!eGn&s|4ml0;E9oEE3>?)yZBu|Y{|RI>(I+KH2^mu&@9?*gji+3 zRy!P~6BH&OZ;|td8=Fmz4P5;8eS*N`wb$SG1&BOWFc2z-x=5N+6A+{7rW?w>tg1QH zG6ZZK#ECLvFoWa>MTi>Uw5l&v6`y+m>QViZ@H9C@vB8o6N7I5=M$-8fAwr7~gqDx} zTKSi2zb|-^=)G|2035)!Fa&)X>Le`vhU5ji?A(<1Lk@TcKl^RvfA~WzH<-S+Uf5_( zCxL!D5UAX3iOp5CS{ARwmB~{&f+(o3j33V6GNWIGBO;3dOk4DXW=C?m8_@>bnvf(Enn;1`l<2pviub`ZL}oCjvPktRCyfP` zZQwO-fJPGqD_T3+)pE&tYIblE31oE zu5uO{jc3FAZRCIW8`X1XRi(eg)ZE`?1uXt5#ysPdVflG&(iv`fhC92E>yf6(k-k-p zqa0<0Zixfa38m^r5NRb%o@J?KyX9M}$bms6JoF&%(K_GuiXrcWFf1k^#~s>%(@Ht5 z%1Wyq@uvNrQqnnimA$9DaYPDY3sp(kq|y#+mBzzp(dN#lyY+4+!0OT!Z=MyA;gSrogAz6EiWKiP(EaKAFoQo%Jr{bfjjL8Gjuu-|M|2%D6 zzhPaw$`MWnh%3d&&$?4^=^0NTx}YruKo-Vw6A9`#%OrI@ zoauBtZ%O%d`fG=20-IFAxeHpMuw|yU?|3VbU|sAtaCP(AU*kw7`K=q43P!jd?POw`VWKr+FrCJ^y5vNA0a~vU=xwU;v%Gjj9OqwB_IIdD4nq z0_vJ+RMLqu_SSX?r;8fW#+fqxC|K+D6|OS!L9;l0v_oXcB&)RW}9OI96=^_fFjPT)~93ff5 zSXI1>`Nq{BRZD?4YAD4Vx&l(sep zu6+*xc#BUtmV~QxbJtPlmAqvo%ZPZLHJV;zC?3=J4FI)*?gFlY+Q z`S5aZ{|5l*COf*vuoX&-;ph1~LjC9e_YY^S?<%^IZ~tYAx<~7``4lgHQ2EwFJk>2< z?uUAdSChl2Px;WF@{vEQgQKDW0PpxxUk< zDK;kT=0EIb`Ya#yqdsPw-G8bt|CPS#M|%aDGjP95;hZA=6%P@{1psHqkN%Zzce#fr zn`#HRND_065$kJwsVG!hsZ(cyh?h}HQI*cf=VplD$#{?BQRlkbkKkl^}!K zg0CO#D~~$KAAuyAp^|A_6H7bq#Tfen{}Re~{;VJ3VLruEuJW$W`mWDj+ml&Af5a~_ zL~N@)Pr=Gy?zjKSpP0oOUblC9^rLn85|xmFHu%i??vL{BttIggi9?m5{*6jyS0RN>wM3#47EouNUJgG?pBKE?@YGvzXl>d?o&Xo@%Tr5}C`|6)& z_~>siHMv6AG>;f^BaO~5V=pnA6wx#j`g7z(%#?e?N4RrJ!#qd~@|Bd=m=K}n*kqRm z1=3|7{^>6OI9rv6)=@R% zpW$hS8~*KH4h2qhdj`m49cOVo^X=-6ep`kLJDY6paChB}L^gQ1Wt|U%HLzL5kNoH# z{YSv`ahaB7hb-=yXna4-|M%~zPx(?$IHU5_kACY9|0Uo0gD1IP^<_T&7q>xs&dO72 z5x@0u=%u@O$~PU6zK*!!%<`@7_G-6!sL%XaDIf6stbWgg>3od(j-UC{{*bpz`Y57V zx%68N^&#Kx2a(VJdOx{l2+u)VI@Kbnjk&fEtcXFY1lle|L(c4swftCLQfS9K1UTW+ zHKKnyp7pDY^zaY=4#|mh`uF@=0H_*repqO6aYC{uk~Ov>O4@v6N>@2Q*VlT_Pycc+ z1Rt_+SK_9_eQJtJl5;D^9pM{C$I4t61k1R)Rgml z9b~L*vfFwb_qvX&eIB3XNB+#x1kdadC0~uYVzq0GaBTSNh?ixXx8S zkJ3L>wV&-Hewhz*9gm}qd$sw@UnZ3}j^nT|{Xiw+-sU>*5!sqyq z4>k4M9Opfr_MN`kYrf4h?*00BjFx`Xk8vEA9y(KoxU5>rvkhAm?ZrePjzF4(4n^ih z78R}H{(BPEG8Jgp`Q=_UE;E5N|0rw$+PEc87Z1idsfxGyk!!lr$PXG$r#iVFF0Wh< zarn~eBuKfAhyoL23|Yf!H?&c3qvVrH#j{+?ACciVLEg(uO{m{i8qajrSyy`Io1oJr zhv0@fVQy!OG$mGS9>4larBq@be~mM zK_60_;vt{9g)jZdl6Ra(>GABQ%0-|VaDM+ozJNJcf7HSY!Gs?@HG}H1Y3$djsU@CTYg?sHGhK5uQAcsCN{H z0vO7PUP8U{Gu*ZNRdRUdF0_PV5vN<)ulVG@*4KG~%``^n^o%F*zWrdpt|z)NJ3c!+{WGpGOUhpj8I>&SNd_3j!pX*LP zScT-njJew9I=PT*p656qH}^KPeTX&II%TuPzU5_;IsAuuoNKlkO_I~*^rc^_`xs(H zJpNQwy~eA)-OD`W0KtgpM!#iKgB3+TDAPAOSG?s9`WrIA%%X^PQYNR%GtWapxjJ>c(w#1U zNhN&gd2ud;A3)N1n(-jCKap1wMO3eP zPk(y0Jov)d9i}RbNc0*dfLmbBuA1uM>c(%btm5D30gm`XsQ>)G|5Q)|zBJ*{UpAVT zed+`Xh;q=f8GuXt5?_`eu=bl2%Wn9bmq7*iX;1xSrfK?#l-k}q)1hJ|)?el}AW%==Re*ZP$nWlwTH$&zBkcw$r>c$J0Z9`Y(M!NC%=Gm|`<0-86cSiA!RP(0kkg z=JwY-PO6FK-+qa=#0LKkr>mUI!{t^uJ%Q*?|8V&Kr=l}+i&J&=5^wo-&ro~`Exg<< zi+ZXeAN~%H@WRFr8lrwV)l^Rj9||!zG@$Ol!yc+}F?L?Yt!{aVr+O z8G1N8XNU*Q=x{I{2=DNM%jsVJ1?42v(ER1-rB64)u@~f_Cj1CL-Tn>?fbo053=<#f zV@C^YYDoZNX2!uq{onozpY=n1s<+3-d$-H{e>A;IuWs9VUe)jS`?!9WsU##$92|$O z5XZzQiWA2K1R@9l0?J&92=NOjAO)x+3KRelN>D&lsQZthHkMsD$@bZ2uf5is-yEa& z_kEt$PP59X&vxVyVs7m!Tga~VZ{6lkh1UL>-}Wl!_{bjveeoVY{qFDn6+h<8H4nfW z{Vu0J&B0yBEZ5EM&U)2N{-}3|DfjRE##ko9&TzKV4)rdhET3GjXRaJm`*;4Ng&&z@iD!((52vB2@hZ~1Yb{EeUZLr|-z zR-i~0S0h}u^poF1REK{k{B%%~5(q=*?>kZr>~|b4a7H{}7#{^94Dk zwj8jGY-$dS6=_t`4h%u#=C}AQAHsShF#Om2gpdDDLOHScrUwu%0jM^M>XMimJ4pYc;>e@9TCw>`>;Mc?-1n~{6UQ6SExVxAOEVloG%~$ zRv-NWAy&Ne>aEW3QE$3Q`sF`1S?GmtP3`&P-s;BHWlMaQkNBu}@HQ=9 zE_?BQjr6F`zPwakMJ{aBy1g=K`c^Xxm3fc2U0b*|Ug zI`^Mz9^)jz)>E$!-^xlq-~BVMbs6d8+#7QQMMSn5*o5ul~fZTw&@@=B=7! z^ihA!mI!8M%WdGaEC)xNFe&7ZxXio#$f$?LQOiU5M^9Z{GGDn=m$4;M{Q5cQnXYdA z^ZXNE1rPhjoMJOs{D`TapqP#7D|iw``Es*<{8vrAif9 zq#VtU1oZJDBmxQH+23*qgA%Xt?_d{pq_rNPytE{jqWn8N`sr20qn;eha0bCGELVI& zBK3?{yxLV;taR8O6zv`FR8wmLow<~Oza}Vp)GM9g0-v91X!R}^j=XrsqqJ5j=ld%j zjiWK(t-Boc;NR*mUiRfmcfBh-gI4Z%`P1L+EO&Zt!Yoz!H#Uo4C^5|xLO95!Cc57? zzTyA;uh^F`J9F_>_M9cOho`sM}6`s*nDO-}2Qzns@QCKl+nJHRT(B ze&R>(uCT-+@5S%$xtDLI3$F}l z=0+=V{ERoiGDU&(?jLC}Bq&o$E4|LCy(*}`v-QME?Rfhu7^&B{9czvc>?$K=jz(L5 zy4xS&B*4Dz^%;G&D6L$d{+?#((i6d5WGl|!{?)E}KHkVOO1k{nZ>~+L=eym*c9(P@ zXXEJn_>)k!as;*d*SYO_-|9oXMg5p)e#G~I^Ztk* zUGz`;ie0C|)UBJOZZW}l>2La|C^m8G!)~IQuRQEqOz~06Cz1TuzI~*HVK)Kd`I0~K zEq_$zZYVF=4OgbGTyd#W9O+Yj)Go8GB6%FMZ*|9?@DY)ooZJ{f>8U^Z5Sx0}AM;i} zvXL7yQ6)Q5RN4QCEB}NKbT0X$Pl8eH<3Gf`5>pTX#~7-!M>oc0ME*m5GTV`Egz|8q>v|zFvy>A_+NLT@|Q9!9lXvttwvWgt+o_>RW20%1G#^Bi*Hj%KVzEF9Hj$ zgucd>YUYD=x1*c@#~!OF$2(U{yu+QYz)v}EGZBAv!+-xz{^G-6qrB}`Wbvbl zD(Wk~CaIT-=FmSr$z--L-BMfq=AZnP?}ADBEr5Cz^ zn6Lb*m&&3ye)Xi5IT3-GC98|(tKafR9DbZ0zoO@(%ZRxOk!zHhO0}{S^*|mhH$V`Qg}dYe(A~ zSw3XxF7wGii=z2pN=Ay(s7J?uI@To`-7?(%0AsGKw6K^+28Ju2m6#bHBxO{+%I)CT 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

R7jbJpaG_A3+Cm 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

|xqlS~!Sr4vasf?;R-^U>~naT7jB1i4f7geE! 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�NMKw$*?>ZlB$wvB-sUI9cov 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

XUXVl?qv7`I^SZ&hkWh@kgAV>ht+iG# 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⪙VR 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

&@0lA2=0D$r?9)^6Ya@hSHBvm8ua<Z0Ate%*a94 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 z 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

DK3&uH<`0hP_xK!d5BBC*mWV+N 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 literal 488100 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

=jm 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.% z-j<#{!cxF`!RI?ktd(n#mgMU{00l-XO59)xHk?(+!4K;Y}ZAV24-17^E1QGL1AG&e599f8D2A;jj_sZloLCD_O$* ziNWDY2tf2n)>fo~5sshSq96U%pXwt_YsIN{<)tHgb=b#0EoleJ&7OG0Pe1gtebw7P!%bVt{N`8XwI`-BVGVP=lgk|HV4im@=T+RhlkDv- z2rt4IXE+D~4xj$(kdO{%{{XN(&ix1}5TLx8%fhp7mzUhAZ)|l`Pa~MnMoYMqxx}U2 zJ3K;3BPAErOJ7#fynd^%Q%((8hni4%LTOX`Dwxf$5R9#k>Y!V_HIYKu%2V19x42^% zhmJk%S36~BWC6(O5V(j#*VDSLI2WR!O=}OgGtQ(eS?Q;4R+qSi=b^1yLM?Sb=xiWO zbGQvkq3ul%2!V*N`mmZ&NZLrREY*#)r`sW&)T2Icmv&K_o2n@r|21E=sjKFM^=mgt zB}GG;f&qW=)4WOxUGcKjjUwR=Pk!bnp77LXLtfqWt#AFv$GmauhA<5i5hd5V-ysyy zo&BjF_0WdU0SBz5QbQPe{t@4{WQ3ml{BLGdTooz!{IV9Md6A;LMM(}`ariw1ct0Gg zXZsya3@IXgCo?71&Sf&iN*9!37BE`k&FX%<<0#&GMhq4j!Ty%=Q5O0b48B)hbly6$Xy*$rKFqi^l2DE^%zz5DZbVV{i8`n8-980ht!j}ZIA+A#b zMpIm+;O{5_M*P#Nf)7=)R$lj1a0=un2Jl#RD)>~Y>u!yD2wJ_Hqf5|v?{^K34ivpwePHv0PX|DXfm)_c2pC0+x};zU1v|sfc>3S$f5}f* zf*3%B!OjYH)v_!r5JrH&;b&?sU|(I-xWUcuE7oNVVvC<06h7fooPvG*XMiU;$Tq-m znSd|#XMF_;3rpzFPLIp{XC4JxYF&a0kbyZ9tb;$}A($a)S^v!OC@H{d4H7>16)tEA z%X@I~fzdAm+c+5czz7J|Rj@2V;R=iuWm#gd9;Pg5fA2YX;fs>|45>x(`;LA#c_8(J zy|gJBFj{~JwP?!r2Q_6$12$bi<^Rly7+(9b1&d_Slnem*U@$CL0snvc^T2{F5G=W% z`T(naTH=Bh?B{mQSHkDEEkbw#>jplvcm$3niGD%uVmS(?F89%0bz> zShA%t%xWu){Hh0rKcKY9FLD)ExdXsny!xZD!13cgW1RY|&KQ8!1!z+cha5R>btBu4 zcwxfkKlTIVxw%~GA}3k47Aw5-hioXvbG{lyblGOCx{EuX@k|YKC9kA@%@=k@Q!!RB zOdij9&)0JA?(Vh7X2p?jPyq(ef3~7o|IrW7tF2S6f}u3-3i-8P{%Joj1T4;cAIU-r zRcU6Z>RohYRpc;)(~tT_aG^P=i@L19n~)#HwodaT09HV$zqN4iE8`yXLKd+ostRw} zFO@<|+1K1#m3|ZS-B8Kd5sCuU469V_Ysu0RVoFB8EHh-!Yw%?a%UwGBDB?z#|{a zJ{wxrvP}Wu6Nkc=Qq=KI)0vL##t+xIZswB!e>ZCxTR+PeIc$QAeDC&dQoa01 zybw-!$9pix7;Y)jSdXG-E0AK)lvJ+pX_XY4-Si?h0kBP8GNLwfq&WscYX*%gx4GG$ zZc9GNcOK^vo*Q8|I~~~AmYhHxtqg@rJ#t!UReZ~@8{GnXKoIhXNzR6VNPW*zgA~A3 znPgHt!Pce$oeW2PW8N(i&&t*v*&AOCo|@6@qsg@+{GzNQ-Yly61^jzdEtuC z0qAcs)0WPO-1rIy=(X4jExg#tUg_4b`MZ7LZmYQCtZr%c$^S$VRr27wo|+z*i*^Vj zOa_&U4_R0*`PTS%)wxqH+>apKKRr3Mooh^%GrfdRJ@~D?j*F ze2PDOU=zTN;91vfbk4uUhtj4^1M2s5OT@4w>HqsTOL4#y)uD2wM=lAyk6B+1)&~z0 zq8y<#5LZJf4WySY1P6gd_`Gh%W)3j0cVk!L9P!8?y# z;;r?d5Vj$BfUNt*wV?@RtUa7`sEf||Jp97%NMxK%&FI=d0rZf=d0k>&cGZ}f%z<$Z z)Wk8mkVE6I%8QVAR1IYZcTXrD^-%1}MW%f2t1Qj(dL|>Bg+m&^(N$oPGv7r3 z6pFu%Z4%;Y?*=!(9Ya%HIqIlv6iKIK)}@{Z&E^P z5QegxmtYeUQYf9PidUkJS_j1Lv9h@9k<&Uof^TQ@~h>Q*)&YW(dZh{$W(x^zS%*w-rEXsDAVIx$4mGMSUU zR#cD*Xde}85KUWK8#u~=)JO}hsbWf(8n>VmW-SNv6>Qufu|xBwV1`;+wiyAlCZ~*7 zoU|?HG8ec}6igF{*;eH&XB38aj#>t`u!N>Rq0j&L*K4E4bJ$?%q#yNxP<5s&EfEB9 zs)s>&Zp=E9nk-AQ3|iGeIg@|T+tXe^N1DLGo)7Re)7gRc>M5R_Vd1gK;t?MhqWs`Y zse#M@2CzBInLq~P9{YhJBP_@=kkuoUOUpIfRm!`5px9@PQ8-|mslqU%P;QVdG&Zq_ z*nPq?bi-5v!!oVXvFgido(7!fC5$gw!Z8#k1Eer~9|X*eKK=pDEM)WZPk!4}C7tgh z>_H$0pPAgLkA7rol6=BT9|zado>wM^VECQg*$0+t1Xqc>?Atu#V;}tqkGs)>4#0(E zW8MSMncnpSWsE+I0cP0vK_^#yl6rgW=~!$Eqh@c?UatSb9P(4VIfQyw6xpbDL}TgbOv8joWbc zzVDi+{zzmn6j6oI<}X0D#3trZtqR1KrqjKlrUEiQw8a)YBUy=NX;xD8WU!k#_{5+_ zx0uE?euz`5QUI#2auz=ePt&vn0+DfzqpmF}?&21m7?*c_NZYVD`oTAb&c%$y#uu2W z%bpL60gH*WY+?%(PDD`M^v?HcS7~-LFMQGb!$07!1#lFb*hY9QMm%HJ`))x?F1X?# zLer~V*Lywb&r+f%@B4Yv5C!a49A?UcuEFj2G@bCqoNH3)0-f? zYH3q5+O%Sj0O6})4#eknost=cAb&FvsOWHl?+cYS1(z3&6;*#boVB#})! z(%J1{)n0qnHcS<|Y71Hc!XJP*oGS*iot3WKbTY2Vl9<-iZZ=Ch^HoiJs_MFdv#x~d ztl**?%AuLaVrRRun$>ajt0ks^J6+unB6hj+h3~3YGxSUS5JubC*e89$DTf^)Cv&yQ zO)su=5L~R(sKArlw{=soiO*!Ib`{kKysQ~702nL&f0Ev8S9T<9%C>V(J2RbwhjeJfis=H6R_pnw~tt$J?cq5*0M++VV0-b6lOQmN$ zo3lGiW30dbsp^0G_o$D@X)o8Qb)jc{mLs>;cGEs@$8Z^;(~vLwLZ2$5Ux69+WQ_HP!){J>9Fjw)GJMyC6^S<8s&Q5?NhHY5R_xT_P0|eHB)xY1?TjxXU zI_tquxy+Y=KlsIZYmV>nWP;Pe)G?H+IgKa-8C%=Xd`yxE5v@C(`Vri`#Q~Kt<#` zJ>aoKi0HWOc<+eyx}4j6zfQ-zS=+WL$AL|3DA)ChicV6xIYKWtGUT@j>0{b(~bC4(~ZRCgzwLNIu$CISy*l2Mzt{boP8RWD))V;Y|E z5x1}xni;q=c`75Ql10u!Wwp#=;%myO{WPE9-9PlC`|hxNXnbq;=1AZ1+w`jM9^C^b zBSS1-hIwoeN)ds_d-n*>aGyjk@^gH|OT4MrL^R;WZ~FRI_%@&BWAFAk`yATb4y7%+ zzox4i^q|i6%yh2&&E2i&7);fUTcJDUCprmzZH~ShyXWkSQM-lqZuR*1)SuN0si(jJ9mKmllDj1}4L0APFD`ymlN6H99 zVK5x(Uw{y0#iXuLG0u5~lNsD>ltIs@gjS3RXcWKFXei+vO}R`k=;wlC>cFCa;z)(> z3wa-phy1kh--E-zBq-;CpzNIfYL9sRdk9cJDf#^lBLaoZ=vjoyOYjVSx_d?FkEw~r zSpV9Tp+e(7fE35?LOF&0jHg8e#AzlfN};VF;ehA&duA0hZXmTW!!axfeqXh&N%i}d zH8gE-3{g6XE-f66#jgdJP$-b3#)%mVamr{d2!WvzXIV;#KzVV^C>}8$XiaD>7^7gS z;~5B!KgRLXsA@(zUS>2dquivXq!NrVQHx&z^iM~pr2ZYyVf=>TPHW)mYUo8+4g9_W z+KP!_c*ZrFg7DyLM6@{`?Qdzra6rcs;rIiLpqPW>=`uq8(@z(!WL@!!qoe|5%hst3RzF?2!uz1c8~^uX zg!YU-CHe3B{qqP=2UAt}JLs*%;sV{=kj`AXI{kZvQS6LIfS)J8rqIu^U%+lb zz!Xx+3f~G35;{6Gee5Z)ANy(9^e4~b@#7td$6P^k|Lp-V6;Syr$i`>2#%_lEz8;?S z@4?`~{$FygY-9QE$rRoeH z(@G9PsmSA$U`U+Wj99ZI8(Sc7qp0*(To-9S5983&NTy+&05f4yNJCvcxyjSa7SPc1 zluICqBCkH}o zedT~cBhxZX*fsQ`C%>&;N8Fg^ex4_AWie09vFZuDS`_TChS|9pTMRh>`k-rYLMbE@ z0<2g7D5+rJ)clO5s51ApsafHf_9Ud*R}_gU&0E5VMBaGhqavE{X__;NAzfwBXGnNM zT6j9feu}6h57gJh{-(edULJz`OG6Fha0CngHj9;ZQBJEyT8Pz4H*2HXt_{{TUE}w- zyXZWc!^05bKebCf`okW+^_@N3u6jMTi&`6R0*VfN>Uzit$}1CdDnUg!(gvWO^=^)? z@r_L_*6ltHDFykDbI~Oq+kiB#nNYgbwO&QcF@4jwbGt?9wKi?j4mufU+ODMH4!5Qu z#bBbd(3+NZg&q@9jJ`L$gXMa+doya3QlhwV=#n2o4*f#s+7(|=eAtz4Z#5JrtGtUg zwo$i-I@0mWR8Z>o7si%EMT*Y_35((EUv8*S(5L`=a={sWCNHY^$^M!51@D{IvCmj<~3HT!L z`DYtHc%cip`{3t8Uq%lK$`f0vDermaiZ*N@gAhVdAp~u?W(QjVq~aiD2_?8?EwRk7 zov(mNKx;>Mkj-qsSrx@-hP5@tlwk?J?}7mj${4(0WTz{ElK}S?&m3FG0w6cacsPJ5 z15XS?izNm|c(_0uI-GDe;sUJm+0~}v9kXO(2Tbi$!wW`ch6A0Z7WW2{$Ur0Y432qF z_A(tAm*TIHc5nL7=)JMc5v>XFuyBhyXPeb7PI}ocda-?%5EeyPFK%H24O~0zus>=w zJ`hHS!|!1ai#A%dUiq%Kqn+Cl_qeG=WiKO-DGX{*W7l|Tw&w8CH^w-lce6K#*+c6U z{O8TsjMlU!7+kmP=;2g0&c(wYUGl+sI9R)_i`|H@UE7&}^BGS(wXSUdD{8F+-oeDC zYd{?|#x}-V*X?eFR_=m}T{N-Tn=PziMOYcv)D}w--$v8u#%y+LhbHT$ZM$GYfRU>2 z`c8Wl;h&OG8QrKb*g?0AYWH?+dOa#osA3y`FOsn>6k^>p26qOVZQqqxf)9|OOboHJ zs@1l!vK#&AS3lQVz5Nywn(8LF5Jx|@Lz}RO>3rwirY7euRWCIFT0k`(YrKwP)vgWuNde^EpI2+JM7dnm9*H^Sl_uoQf&{jmZn%{VP>J0 zqoFyA5pQ#`s5y1Pmu~f)hXmO;wX`e83U$so)t>0h0H$mkW+`(t^lM%vS2T8grO6R#C$4cC&W}0GQbGJK`%Y1?y-ev!39*;fq*1m>b7WZL`dv_pYGOJk>w|b_9ie#XuQs#+ytW+9eeisFNRGUSXBD3NxgTmqf z>>2n3y-Gaw3Qz-vR7)@?c5*v<5$M-fGkidS}OXL_>JA=KYj zxy#+4TGdu#HEdxExz2T)Z4swBQ7I^Jq^;_z8QKxW#71<29()gzANGCScd<7O{M_;v z45?gkGlx;UW@zD@>Y5NVrNk-aHrp|XfI&jK(T!W#x774aJ2XRkv`5fDlyr@+eH~=b zaR?*GN$as}8{H&!bqMWrDfaM6p>`mF42bGWTq`s#Jg5SUVz;|>lNo>exXF(dBdpZ9 z?o&Uu%1Lt4Es9RmzV_|dCc4&3eUPIPm9~B8OInjlejq5`Xxi4C=B#)nwskw%DGvn7 z;lE5n!r5htP2a>Z0oI4ztepA8fqFuT%~|bjYg=DpmX)YA$}cFb zh$nvTb6=Hu^Nnc$z+URL8p=@2WM)0+NuKiffvo=1UvB7ozK^&3$d~8_ixc*IpYC~^ z3Mwc&8yu>^*^M0(eAj(CMaS&?${*o*vc?;>({su*k~jTS#%&0L71;A)W(cH1$QY$z z1hGLuHO@GU{JhxHbOp(JyiPeU^u<2_WA0Sp<<#)&PhV!4#P2nD65qpe^?0@BEpc{LLTq!{7YMPktd+0J!p& zXOuBc-}%F_KOK3@XWqs$Uoc+{K6?gGYE2oq*wb}_`1Tz?^S5!v&cih22MVU<3vfhq zA@lSazxg&jCFGqyz zOCBc}s5s=LiqLVI0St}#7&oE_L(w&MfAf2K{c;^qbU;AKD108KMBy{|l6SsLJ6x{G zO>Um&6-}Xk%`1)R>%VDCliixlAL%fC8(ZEAKZPN}n)K~Hy-eD^&8NF|r_UWqNs_rV zSM&C7JJ|t)kq3?M((PI2wg6})dTy3>8_*nEDdz6xK9lmBd3!NP^KCw@Y_w{cCiO8* zaf)b9)Svd%G>vSy6+w6%#@L4?EKObFRp|~xChbmo@Uw|X4kt~1NX#fqZd7a3EdPr*YOfJ6x3t=V+K9= z#V=+u)6d5cZET+A;V$8Hr#aFsp7yK1Zg73Kng!Q7ST|ALf|B8dF9hWp{@s036{}HZ z1t4{t(rjPu<=3#=rQI@{=v4Q_fOxO9M6C(H*T0b85C;&FFp?u)(xxUxV7ubCs{hwt zbqK1Sx|1(Sm63OV5$vmZp)5wfn}&!<_R{NCYM`5%NArRHUPQBn`GS6mVFaLS^K-1Q2;%CBrI>!NPzAq1E&O3OoDgpr+Ugw{+j^=R(s zRFS4>^;I2kzQ|K<1->Xc?9fkp=<3^eztVQ6t@2<+!sr`v^@Gz&o?dN-3j%OMB7j zU0+c8Di66RRDa?y36S+&-DHqGx56ugm%K>o(Yz4uRu_54%-MeQOGMdmD?1V8pfB>G zTY-c`Yd-erUgj;RkgzO?EEYMdvHjSO>V&mV_m)9+Dym*1LaCHg06c6OTa+xE@}l6A zqc-z)@8;9K;M10Ob-_EvyxXVTL=i=9YEwc3(|7W$+Lcu4|`Q> zJ)6xgAY}V%xTDCvde;wk+Dn+i3_!Xmc0tKeH@k)QRgaB=@0<@gpmaHPM+bE9)oCYv z-7%9~3X|{ajV$z1!y0E8~4qZPKzmb8A^^?5o4}FKVTPmQsQq|Lw-YxJo z(r~N_>YQ0{ZPFEj%f>zg+F8j^TU35vm^*da;qsnBea<$S| z{U(>{W~Xj8H**Q9(`b+kqZ`Orjc&5Dzxlh5!2{>xC!5JgN3`K~$&WZcgXZlPx4Ggr zcXS7!G?seu+sUAUfYrn#*N3?S)mqaBc~TG?WG&5P`>^?=Ug{MgV|uG@^sFwpxX^_j zVGVozAs;k|LqjvgmU<s@azZa16T$U#pVeW-oag)u}!OdSA=&sDA?N$OBXeIa+B zO!Bcy9r3)L>Ec(oNB*U%|HEG%Wn?4C{G@whoPh?9S%%DX%yAl$4UR2z?jZhsx(~9b z$$7w$6LN3)I^W?JoMY})P?m1w$a}uPkyz?n&27>7I^9K7A$K|S@AyIo?{v$L>Dj+f zmL(qQF?7lCr+NVwIp5~T_?RDb=99dO=RD6NcKOD%P6ZL?aq9DBy3J%}<%9A}G^XH7E8IKSn!ER#ot)ifXy zH6pthvqvekeRp4a@@wA8mR)YLo@KN>*g~&T6*k2ou$3$`Ep;g+BlFKzG#Ah^_46oh z?3%Ch14UzStK~jtvavNk%Ny*AB1$uLQz)!d>|{6h$Kq=GW}p})o=VD*E@Urr`R$hH z>vTuvo(^=95B16_GxZj)GUf>;T6&{Lew0^ucW+os$}2xH%cgD!rO&;$(dAzWtk>Yn?A?9(0cwjPhd|-4PBaj2Nm|cfwIuJFJTNTX*kn zLrTKx4)kl4F(?|kj#vlHt*GV<2+4^;u4NOCq=8Ef8 zg8N+dT#wC+$Om?Ftx(+JeR1kjBY2~X3`^V^Q{&e#68ZVRkqA*We#&c@gHipr~S z?XRADiY%`=%vqoH2}4O-!e+N_1HcZUEdJ%+4&jxp*@rWpY-1a;YdvOJYG|`IC)S>o zsa#81(vEs;&UR~aJ1V6F7R7G&Yg?%*UgI4czS_{Xmx)1I!`Zt+-ubKkge7Eu^|wI{ zywz4#Llpp^cB{qFbPbiSLNhdNF$)BihD+Y9p>#*L+k5vwI0!j2yEG7}g_9ol@TQVP zNTz7q;|Y_o8{>#Y7?VO^M`2Yc}cdE(|(zr>B*8QQlp znx^YboP1+n@)2+P!j_$nqOZU7!(QS;E(AT?eSOwDx*+Qdy)j0p_=v~PL-47;B9d5t z$g95dJ4-dn;0e9b9a~X2SsBiuo_KZ>SH94NtZb`OJ+PCl0xs#Vc-I|u(3WeIx;^`8k%iI=$JI`eZH zY<#7ZRP<8cSQ111sNZ~IGriU~088n+e$yZVf075^#DP*kI?~9oWQP->X9;IF{uCe5 z*Au&{TslYEF9d$70I#YzV@)rA#0)O|gW zNU!yY^EG|ccmAO-KwSkEs90jdO)#(t*U{ROh@9U`D#@#cHRMKaSX@f{yT}EHySiZ@ zEY7mwx_VT1?9^*jOC$m1O+K+3|9KHsxv_?*YOIp@h!1P1mS6eAKq~L@#E1to5At@E zuh6@x0Uh~8#~PyLDR-%*{N?{H@~qD4cn*sIYC^Dh@!Gqzs%t;YaOCz!4kk+i9YdXVZDuZ8VF{Ja~#)?1;GVU(NPkRp`0I$h+2Ndn=usovFBHcZ2> zPIRqgltHet)cCb$^=1>BP^2oO9EIeK6hcWWaAKu+{7Uoe-51|dT!{Bo)4|M{0o z%E`p0JWe@@QDPIOyS8gx3rk}3Ap*B$!Q*a-sfc2@!Bswn4*Gpdn>y}kF>}=4tBr$gpQanStQqCxe;V(9k(I zPSY$3U#DfvauW;I&&a{R6WN0M9DRpXd{Zl$NY7O*S@6kgXB}&W&BK(3euyZjKpsSs zmio?m!Q@YJOKFJiaGUCsLHV*+^xFHr=S;F;O{;W`9p_=Oq#@EMq!>DsKPcMNeTW`M zOV|yOdp&6TQT&Aq|_DG@cyThD- zoJMT%;Z2T1Y7cu3eePZD$uLg^xyr-`WdDqF&bNm>7<4t(OH=DAr4ahfZ+w-jFv{@4 zac^kZ%vPL#;d4fhUR$~9FBar|6HT_Io5)Z>9fS`MCI*QNhVUAYD&6ZLA}bSZ9FKhT z?SPv}O=2D9rf4OVB&4pypdeR9MHImvuNz%&8xLu?5rgiFEtafg1%0jT^KM$Be#f7_ zN`7#|Zz-rcRUP%#==KMoGIKln$xf^nL&$zA$8=a@jY>^2!U&vK1^~kMyklvq206CU zs_aXg&vJ+;Qr9}w^=%LS3^aXcW2KpOwbchcc1kOa8zMXCS#Q;byl>k8iRw*iqxoqW zlfj0hi*w90(KMU6^}>sg{VbArc5X5jCCF=65V=wndNaB>rm0FK z09uQxzsXKc_D#>D==E=+R;uzO=4NQ%|C H0Rx`sPmMk-@Nr-{t_W>w60Iyk_!{` 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%0AmYAM0XgU|mS#>S~*} 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 "so proud of you") runs even // with "try to guess unknown words" off, like SE4's cautious guess level: every part must // be a dictionary word, so it rarely breaks a correct word. The heuristic splitter and the @@ -684,6 +696,133 @@ bool Accept(IReadOnlyList swap) return false; } + private static readonly string[] AccentLookalikeGroups = + { + "aàáâãäåā", "AÀÁÂÃÄÅĀ", "eèéêëēěęė", "EÈÉÊËĒĚĘĖ", "iìíîïī", "IÌÍÎÏĪ", "oòóôõöøō", "OÒÓÔÕÖØŌ", + "uùúûüūů", "UÙÚÛÜŪŮ", "cçčć", "CÇČĆ", "sšśş", "SŠŚŞ", "zžźż", "ZŽŹŻ", "nñńň", "NÑŃŇ", + "yýÿ", "YÝŸ", "rř", "RŘ", "dď", "DĎ", "tť", "TŤ", "lł", "LŁ", "gğ", "GĞ", "Bß", + }; + + // Lookalikes across different base letters (the hook of "č" vs the dots of "ë"). + private static readonly string[] AccentCrossGroups = { "čë", "ČË", "šë", "žä", "ŽÄ" }; + + private static string GetAccentLookalikes(char c) + { + var sb = new StringBuilder(); + foreach (var group in AccentLookalikeGroups) + { + if (group.IndexOf(c) >= 0) + { + sb.Append(group); + } + } + + foreach (var group in AccentCrossGroups) + { + if (group.IndexOf(c) >= 0) + { + sb.Append(group); + } + } + + // The OCR did see a mark, so other accented letters come first and the plain letter last + // (Danish "Màske" is "Måske", not "Maske"); "ìs" still reaches "is" when "ís" is no word. + var lookalikes = sb.ToString().Where(ch => ch != c).Distinct().ToList(); + return new string(lookalikes.Where(ch => ch > 127).Concat(lookalikes.Where(ch => ch <= 127)).ToArray()); + } + + private static bool IsAccentCandidate(char c) + { + return c > 127 && char.IsLetter(c) && GetAccentLookalikes(c).Length > 0; + } + + /// + /// Tries the lookalikes of every accented letter in (fewest changes + /// first, other accented letters before the plain letter) and returns the first variant the + /// dictionary or names list accepts. + /// + private bool TryFixAccentMisread(string word, out string fixedWord) + { + fixedWord = word; + if (word.Length < 2) + { + return false; + } + + var positions = new List(); + for (var i = 0; i < word.Length; i++) + { + var ch = word[i]; + if (IsAccentCandidate(ch)) + { + positions.Add(i); + } + else if (!char.IsLetter(ch) && ch != '\'' && ch != '-') + { + return false; + } + } + + const int maxAccentPositions = 3; + if (positions.Count == 0 || positions.Count > maxAccentPositions) + { + return false; + } + + var alternatives = positions.Select(p => GetAccentLookalikes(word[p])).ToList(); + var chars = word.ToCharArray(); + string? found = null; + + bool Accept() + { + var candidate = new string(chars); + if (IsSpelledCorrect(candidate) || _spellCheckWordLists.HasName(candidate.Trim('\'', '-'))) + { + found = candidate; + return true; + } + + return false; + } + + // Change exactly `changes` of the positions (from index `from` on), recursively. + bool Search(int from, int changes) + { + if (changes == 0) + { + return Accept(); + } + + for (var k = from; k < positions.Count; k++) + { + var pos = positions[k]; + foreach (var alt in alternatives[k]) + { + chars[pos] = alt; + if (Search(k + 1, changes - 1)) + { + return true; + } + } + + chars[pos] = word[pos]; + } + + return false; + } + + for (var changes = 1; changes <= positions.Count; changes++) + { + if (Search(0, changes)) + { + fixedWord = found!; + return true; + } + } + + return false; + } + private const char UnknownCharacter = '*'; private const string UnknownCharacterCandidates = "abcdefghijklmnopqrstuvwxyz'"; private const string UnknownCharacterCandidatesFirst = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; diff --git a/src/libuilogic/Ocr/NOcrDb.cs b/src/libuilogic/Ocr/NOcrDb.cs index fa02a1fdd22..bcdd518ac87 100644 --- a/src/libuilogic/Ocr/NOcrDb.cs +++ b/src/libuilogic/Ocr/NOcrDb.cs @@ -654,12 +654,18 @@ private void CacheMatch(MatchCacheKey key, MatchCacheEntry entry) // Best-match within the pass: the first candidate that fits the error budget used to // win outright, so with generous budgets a mediocre early entry beat a near-perfect - // later one. Now the candidate with the fewest wrong pixels wins; ties keep list - // order (newest first), so a user's just-added character still takes precedence over - // an equally-good older one. The counting loop shrinks its budget to "current best - // minus one", so each candidate aborts as soon as it can no longer win. + // later one. Now the candidate with the lowest share of wrong points wins; ties keep + // list order (newest first), so a user's just-added character still takes precedence + // over an equally-good older one. Ranking by share instead of by count matters once + // entries differ in line count: a 100-line entry (from Train nOCR) used to beat a + // 300-line one on a count that was low only because fewer points were checked, which + // read "o" as a sparsely trained "ø" and "i" as "ì". NOcrChar? best = null; - var bestErrors = int.MaxValue; + var bestRate = double.MaxValue; + NOcrChar? bestUpper = null; + var bestUpperRate = double.MaxValue; + NOcrChar? bestLower = null; + var bestLowerRate = double.MaxValue; foreach (var oc in OcrCharacters) { if (!PassFilter(bitmap, heightToWidthPercent, oc, topMargin, pass)) @@ -675,18 +681,71 @@ private void CacheMatch(MatchCacheKey key, MatchCacheEntry entry) ? pass.ErrorsAllowedSensitive(maxWrongPixels) : errorsAllowed; - var budget = Math.Min(Math.Min(candidateAllowed, areaErrorCap), bestErrors - 1); - if (TryCountErrors(bitmap, oc, budget, out var errors)) + // The number of points walked grows with the glyph's size, so a character trained + // at 20 px and matched at 40 px collects twice the errors for the same fit. + var scale = bitmap.Height / (double)oc.Height; + if (scale > 1) { - best = oc; - bestErrors = errors; - if (errors == 0) + candidateAllowed = (int)Math.Round(candidateAllowed * scale); + } + + var budget = Math.Min(candidateAllowed, areaErrorCap); + if (best != null) + { + // A candidate can only win with a lower share than the best so far, and its + // share is at least errors / (all its points), so stop counting once that + // bound reaches the best share. + var maxPoints = CountPointsUpperBound(oc, bitmap.Width, bitmap.Height); + budget = Math.Min(budget, (int)Math.Ceiling((bestRate + CaseTwinRateMargin) * maxPoints) - 1); + } + + if (TryCountErrors(bitmap, oc, budget, out var errors, out var points) && points > 0) + { + var rate = errors / (double)points; + if (IsCaseTwinLetter(oc.Text)) + { + if (char.IsUpper(oc.Text[0]) && rate < bestUpperRate) + { + bestUpper = oc; + bestUpperRate = rate; + } + else if (char.IsLower(oc.Text[0]) && rate < bestLowerRate) + { + bestLower = oc; + bestLowerRate = rate; + } + } + + if (rate < bestRate) { - break; + best = oc; + bestRate = rate; + if (errors == 0 && !IsCaseTwinLetter(oc.Text)) + { + break; + } } } } + if (best != null && IsCaseTwinLetter(best.Text) && (bestUpper == null || bestLower == null)) + { + // The strict passes gate on size, so the other case (smaller or bigger by + // definition) is often not even a candidate in the pass that found this one. + var twinText = char.IsUpper(best.Text[0]) ? best.Text.ToLowerInvariant() : best.Text.ToUpperInvariant(); + var (twin, twinRate) = FindBestByText(bitmap, twinText, bestRate + CaseTwinRateMargin); + if (char.IsUpper(twinText[0])) + { + (bestUpper, bestUpperRate) = (twin, twinRate); + } + else + { + (bestLower, bestLowerRate) = (twin, twinRate); + } + } + + best = ResolveCaseTwin(best, bestRate, bestUpper, bestUpperRate, bestLower, bestLowerRate, bitmap.Height, topMargin); + if (best != null) { return best; @@ -725,8 +784,12 @@ private static bool PassFilter(NikseBitmap2 bitmap, double heightToWidthPercent, var aspectMaxDelta = oc.IsSensitive && pass.AspectMaxDeltaSensitive != 0 ? pass.AspectMaxDeltaSensitive : pass.AspectMaxDelta; - if (aspectMaxDelta != int.MaxValue && - Math.Abs(heightToWidthPercent - oc.HeightToWidthPercent) >= aspectMaxDelta) + // Relative difference, in percent of the narrower aspect: an absolute h/w-% delta + // treated tall glyphs unfairly - "i" at 4x21 vs 6x43 is 525 vs 717, so a delta of 190 + // shut it out of every pass although the shapes are the same. + var aspectDelta = Math.Abs(heightToWidthPercent - oc.HeightToWidthPercent) * 100.0 / + Math.Min(heightToWidthPercent, oc.HeightToWidthPercent); + if (aspectMaxDelta != int.MaxValue && aspectDelta >= aspectMaxDelta) { return false; } @@ -739,7 +802,13 @@ private static bool PassFilter(NikseBitmap2 bitmap, double heightToWidthPercent, return false; } - if (Math.Abs(oc.MarginTop - topMargin) >= pass.MarginTopMaxDelta) + // Top margin and its tolerance are pixels at the size the character was trained at; scale + // both to the glyph being matched, or a "." trained at 30 px can never match one at 60 px + // (expected top 18, actual 36), while an "8" passes for an "e" at a smaller size. + var scale = bitmap.Height / (double)oc.Height; + var expectedTop = oc.MarginTop * scale; + var topTolerance = pass.MarginTopMaxDelta * Math.Max(1.0, scale); + if (Math.Abs(expectedTop - topMargin) >= topTolerance) { return false; } @@ -855,14 +924,94 @@ private readonly struct MatchPass }, }; + // Letters whose upper and lower case differ only in size ("o"/"O", "s"/"S", ...): scaled to + // the glyph they fit equally well, so pixels alone can't pick the case. + private const string CaseTwinLetters = "cCoOsSuUvVwWxXzZøØ"; + private const double CaseTwinRateMargin = 0.01; + + private static bool IsCaseTwinLetter(string text) => text.Length == 1 && CaseTwinLetters.Contains(text[0]); + + /// + /// When the winner is a case-twin letter and its other case scored within + /// , pick the case whose trained top margin, scaled to the + /// glyph, is closer to where the glyph sits in its line. The case fixer does this from the + /// heights seen so far, but has nothing to go on for the first lines of a file. + /// + private static NOcrChar? ResolveCaseTwin(NOcrChar? best, double bestRate, NOcrChar? upper, double upperRate, NOcrChar? lower, double lowerRate, int height, int topMargin) + { + if (best == null || !IsCaseTwinLetter(best.Text) || upper == null || lower == null || + !string.Equals(upper.Text, lower.Text, StringComparison.OrdinalIgnoreCase) || + Math.Max(upperRate, lowerRate) > bestRate + CaseTwinRateMargin) + { + return best; + } + + double TopDistance(NOcrChar oc) => Math.Abs(oc.MarginTop * height / (double)oc.Height - topMargin); + return TopDistance(upper) <= TopDistance(lower) ? upper : lower; + } + + /// + /// The entry with text and a similar aspect that fits + /// best, if its share of wrong points is below . + /// + private (NOcrChar? Match, double Rate) FindBestByText(NikseBitmap2 bitmap, string text, double maxRate) + { + NOcrChar? best = null; + var bestRate = maxRate; + var heightToWidthPercent = bitmap.Height * 100.0 / bitmap.Width; + foreach (var oc in OcrCharacters) + { + if (oc.Text != text || + Math.Abs(heightToWidthPercent - oc.HeightToWidthPercent) * 100.0 / Math.Min(heightToWidthPercent, oc.HeightToWidthPercent) >= 30) + { + continue; + } + + var maxPoints = CountPointsUpperBound(oc, bitmap.Width, bitmap.Height); + if (TryCountErrors(bitmap, oc, (int)Math.Ceiling(bestRate * maxPoints), out var errors, out var points) && + points > 0 && errors / (double)points <= bestRate) + { + best = oc; + bestRate = errors / (double)points; + } + } + + return (best, bestRate); + } + + /// + /// Number of points walks for at the given + /// size if none of them falls outside the bitmap. + /// + private static int CountPointsUpperBound(NOcrChar oc, int width, int height) + { + var points = 0; + foreach (var op in oc.LinesForeground) + { + var start = op.GetScaledStart(oc, width, height); + var end = op.GetScaledEnd(oc, width, height); + points += Math.Max(Math.Abs(end.X - start.X), Math.Abs(end.Y - start.Y)) + 1; + } + + foreach (var op in oc.LinesBackground) + { + var start = op.GetScaledStart(oc, width, height); + var end = op.GetScaledEnd(oc, width, height); + points += Math.Max(Math.Abs(end.X - start.X), Math.Abs(end.Y - start.Y)) + 1; + } + + return points; + } + /// /// Like but reports how many pixels disagreed, so callers can rank - /// candidates that all fit the budget. Returns false (and an unspecified count) as soon as - /// the budget is exceeded. + /// candidates that all fit the budget, and how many points were checked. Returns false (and + /// unspecified counts) as soon as the budget is exceeded. /// - private static bool TryCountErrors(NikseBitmap2 bitmap, NOcrChar oc, int errorsAllowed, out int errors) + private static bool TryCountErrors(NikseBitmap2 bitmap, NOcrChar oc, int errorsAllowed, out int errors, out int points) { errors = 0; + points = 0; if (errorsAllowed < 0) { return false; @@ -885,6 +1034,7 @@ private static bool TryCountErrors(NikseBitmap2 bitmap, NOcrChar oc, int errorsA { if ((uint)point.X < (uint)width && (uint)point.Y < (uint)height) { + points++; var a = pixelData[point.X * 4 + point.Y * widthX4 + 3]; if (a <= 150) { @@ -903,6 +1053,7 @@ private static bool TryCountErrors(NikseBitmap2 bitmap, NOcrChar oc, int errorsA { if ((uint)point.X < (uint)width && (uint)point.Y < (uint)height) { + points++; var a = pixelData[point.X * 4 + point.Y * widthX4 + 3]; if (a > 150) { diff --git a/src/libuilogic/Ocr/NOcrSpaceDetector.cs b/src/libuilogic/Ocr/NOcrSpaceDetector.cs new file mode 100644 index 00000000000..408a1c44d68 --- /dev/null +++ b/src/libuilogic/Ocr/NOcrSpaceDetector.cs @@ -0,0 +1,174 @@ +namespace Nikse.SubtitleEdit.UiLogic.Ocr; + +/// +/// Decides which gaps between glyphs are word spaces, relative to the text size instead of a +/// fixed pixel count. A fixed "pixels are space" value cannot fit every source: DVD-sized text +/// has 7-9 px word spaces (all lost at the default 12), while widely tracked all-caps Blu-ray +/// text has letter gaps of 12 px. Measured on real subtitles, letter gaps stay below ~0.19 of +/// the line's ink height and word spaces start at ~0.22 - except for tracked all-caps lines, +/// where letters sit up to 0.32 apart and spaces start at 0.47. So the threshold is found per +/// image from the biggest jump in its sorted gap sizes, and falls back to the median of earlier +/// images (one-word lines have no jump) or to . +/// +/// Split with so every gap becomes a space item, then call +/// . One instance per OCR run, like . +/// +/// +public class NOcrSpaceDetector +{ + /// "Pixels are space" value to split with, so that every gap is reported. + public const int SplitPixelsAreSpace = 1; + + private const double DefaultRelativeThreshold = 0.22; + private const double MinJumpRatio = 1.5; + private const double MinJumpSize = 0.08; + private const double MinSpace = 0.18; + private const double MaxLetterGap = 0.5; + private const int MaxHistory = 100; + + private readonly Lock _lock = new(); + private readonly List _history = new(); + + /// + /// Removes the space items of that are only gaps between letters. + /// + /// Split with . + /// The threshold used, in pixels (of the first line), for code + /// that still needs a pixel value. + public List RemoveFalseSpaces(List letters, out int pixelsAreSpace) + { + var lines = GetLines(letters); + var relativeThreshold = FindRelativeThreshold(letters, lines); + + var result = new List(letters.Count); + pixelsAreSpace = -1; + foreach (var (start, end, height) in lines) + { + var threshold = Math.Max(2, (int)Math.Round(height * relativeThreshold, MidpointRounding.AwayFromZero)); + if (pixelsAreSpace < 0 && height > 0) + { + pixelsAreSpace = threshold; + } + + for (var i = start; i < end; i++) + { + var item = letters[i]; + if (item.SpecialCharacter == " " && item.NikseBitmap == null && item.SpacePixels < threshold) + { + continue; + } + + result.Add(item); + } + + if (end < letters.Count) + { + result.Add(letters[end]); // the line break + } + } + + if (pixelsAreSpace < 0) + { + pixelsAreSpace = 12; + } + + return result; + } + + private double FindRelativeThreshold(List letters, List<(int Start, int End, int Height)> lines) + { + var gaps = new List(); + foreach (var (start, end, height) in lines) + { + if (height <= 0) + { + continue; + } + + for (var i = start; i < end; i++) + { + var item = letters[i]; + if (item.SpecialCharacter == " " && item.NikseBitmap == null) + { + gaps.Add(item.SpacePixels / (double)height); + } + } + } + + gaps.Sort(); + var bestRatio = 0.0; + var bestThreshold = -1.0; + for (var i = 0; i + 1 < gaps.Count; i++) + { + var low = gaps[i]; + var high = gaps[i + 1]; + if (high < MinSpace || low > MaxLetterGap || high - low < MinJumpSize) + { + continue; + } + + var ratio = high / Math.Max(low, 0.02); + if (ratio > bestRatio) + { + bestRatio = ratio; + bestThreshold = (low + high) / 2; + } + } + + lock (_lock) + { + if (bestRatio >= MinJumpRatio) + { + if (_history.Count >= MaxHistory) + { + _history.RemoveAt(0); + } + + _history.Add(bestThreshold); + return bestThreshold; + } + + if (_history.Count > 0) + { + var sorted = _history.OrderBy(p => p).ToList(); + return sorted[sorted.Count / 2]; + } + } + + return DefaultRelativeThreshold; + } + + /// Item ranges per text line (end exclusive, at the line break) with the line's ink height. + private static List<(int Start, int End, int Height)> GetLines(List letters) + { + var lines = new List<(int, int, int)>(); + var start = 0; + while (start <= letters.Count) + { + var end = start; + var top = int.MaxValue; + var bottom = int.MinValue; + while (end < letters.Count && letters[end].SpecialCharacter != Environment.NewLine) + { + var bitmap = letters[end].NikseBitmap; + if (bitmap != null) + { + top = Math.Min(top, letters[end].Top); + bottom = Math.Max(bottom, letters[end].Top + bitmap.Height); + } + + end++; + } + + lines.Add((start, end, bottom > top ? bottom - top : 0)); + if (end >= letters.Count) + { + break; + } + + start = end + 1; + } + + return lines; + } +} diff --git a/src/libuilogic/Ocr/NOcrTrainer.cs b/src/libuilogic/Ocr/NOcrTrainer.cs index b673f2fb5ac..2fcb7589af1 100644 --- a/src/libuilogic/Ocr/NOcrTrainer.cs +++ b/src/libuilogic/Ocr/NOcrTrainer.cs @@ -78,6 +78,12 @@ public int Train(NOcrTrainerSettings settings, NOcrDb db, Func? abortReque foreach (var fontName in settings.FontNames) { + var lookalikes = new Dictionary<(bool Bold, bool Italic), Dictionary>(); + foreach (var style in GetStyles(settings)) + { + lookalikes[style] = RenderSinglePartGlyphs(singleCharacters, fontName, settings.FontSize, style.Bold, style.Italic); + } + foreach (var text in singleCharacters.Concat(mergedCombinations)) { if (abortRequested?.Invoke() == true) @@ -88,7 +94,7 @@ public int Train(NOcrTrainerSettings settings, NOcrDb db, Func? abortReque var isMerged = text.Length > 1; foreach (var (bold, italic) in GetStyles(settings)) { - if (TrainCharacter(settings, db, text, fontName, bold, italic, isMerged)) + if (TrainCharacter(settings, db, text, fontName, bold, italic, isMerged, lookalikes[(bold, italic)])) { learned++; } @@ -126,7 +132,35 @@ public int Train(NOcrTrainerSettings settings, NOcrDb db, Func? abortReque } } - private static bool TrainCharacter(NOcrTrainerSettings settings, NOcrDb db, string text, string fontName, bool bold, bool italic, bool isMerged) + /// + /// Renders every character that comes out as one connected glyph, keyed by its text. Used as + /// the set of lookalikes a newly trained character must not match. + /// + private static Dictionary RenderSinglePartGlyphs(List characters, string fontName, float fontSize, bool bold, bool italic) + { + var result = new Dictionary(); + foreach (var text in characters) + { + using var bitmap = RenderCharacterImage("H " + text, fontName, fontSize, bold, italic); + if (bitmap == null) + { + continue; + } + + var nikseBitmap = new NikseBitmap2(bitmap); + nikseBitmap.MakeTwoColor(TwoColorThreshold); + nikseBitmap.CropTop(0, new SKColor(0, 0, 0, 0)); + var list = NikseBitmapImageSplitter2.SplitBitmapToLettersNew(nikseBitmap, PixelsAreSpace, false, false, MinLineHeight, false); + if (list.Count == 3 && list[2].NikseBitmap != null) + { + result[text] = list[2].NikseBitmap!; + } + } + + return result; + } + + private static bool TrainCharacter(NOcrTrainerSettings settings, NOcrDb db, string text, string fontName, bool bold, bool italic, bool isMerged, Dictionary lookalikes) { // "H" establishes the line's cap height so the target glyph gets a realistic top // margin; the wide gap makes the splitter separate it from the target. @@ -146,11 +180,19 @@ private static bool TrainCharacter(NOcrTrainerSettings settings, NOcrDb db, stri { var item = list[2]; var match = db.GetMatchSingle(item.NikseBitmap!, item.Top, false, MaxWrongPixels); - if (match != null && match.Text == text) + if (match != null && (match.Text == text || IsSameShapeInSansSerif(match.Text, text))) { return false; // already recognized } + if (text is "I" or "|" && IsSolidBar(item.NikseBitmap!)) + { + // A plain bar is also the "l" of most sans-serif fonts: an "I" entry would only + // tie with those and win or lose by list order. Keep reading bars as "l"; the + // fix engine settles I/l from the dictionary. + return false; + } + var nOcrChar = new NOcrChar(text) { Width = item.NikseBitmap!.Width, @@ -160,6 +202,15 @@ private static bool TrainCharacter(NOcrTrainerSettings settings, NOcrDb db, stri }; var segments = settings.NumberOfLineSegments + (isMerged ? 20 : 0); NOcrChar.GenerateLineSegments(segments, false, nOcrChar, item.NikseBitmap, settings.LineSegmentAlgorithm); + foreach (var (otherText, otherBitmap) in lookalikes) + { + // Case pairs (o/O, s/S, ...) share the shape; size and position tell them apart. + if (!otherText.Equals(text, StringComparison.OrdinalIgnoreCase)) + { + AddDiscriminativeLines(nOcrChar, item.NikseBitmap, otherBitmap); + } + } + db.Add(nOcrChar); return true; } @@ -167,7 +218,7 @@ private static bool TrainCharacter(NOcrTrainerSettings settings, NOcrDb db, stri // Multi-part glyphs (e.g. ", %, ?) - train as an expanded character spanning all parts. // Merged letter combinations are only trained when they render as one connected glyph // (same as SE4); separate glyphs are already covered by the single characters. - if (!isMerged && list.Count is 4 or 5) + if (!isMerged && list.Count is 4 or 5 && CanBeMultiPart(text)) { var group = ExpandedOcrGroup.Create(nikseBitmap, list, 2, list.Count - 2); if (group == null) @@ -193,6 +244,200 @@ private static bool TrainCharacter(NOcrTrainerSettings settings, NOcrDb db, stri return false; } + /// + /// "I", "l" and "|" are the same bar in sans-serif fonts. Training one when the database + /// already reads the bar as another only adds an identical entry with a different label that + /// then wins ties at random; the OCR fix engine settles I/l from the dictionary. + /// + private static bool IsSameShapeInSansSerif(string a, string b) + { + return a is "I" or "l" or "|" && b is "I" or "l" or "|"; + } + + /// + /// A plain vertical bar: tall, thin and almost fully inked, like "I" and "l" in sans-serif + /// fonts. Two bars differ by a column of anti-aliasing at most, and the matcher scales the + /// rest away. + /// + private static bool IsSolidBar(NikseBitmap2 bitmap) + { + if (bitmap.Height < bitmap.Width * 3) + { + return false; + } + + var ink = 0; + for (var y = 0; y < bitmap.Height; y++) + { + for (var x = 0; x < bitmap.Width; x++) + { + if (bitmap.GetAlpha(x, y) > 150) + { + ink++; + } + } + } + + return ink * 10 >= bitmap.Width * bitmap.Height * 9; + } + + /// + /// The random line generator only keeps lines whose pixels and their neighbours are all ink, + /// so strokes narrower than three pixels - the slash in "ø", an accent, the bar of "e" in a + /// light font - get no lines at all, and the trained character then matches its lookalike + /// ("o", "i", "c") with zero errors. When would match + /// , add precise lines on the pixels where the two glyphs + /// differ until the lookalike clearly fails. + /// + internal static void AddDiscriminativeLines(NOcrChar nOcrChar, NikseBitmap2 own, NikseBitmap2 lookalike) + { + var ownAspect = own.Height / (double)own.Width; + var otherAspect = lookalike.Height / (double)lookalike.Width; + if (Math.Max(ownAspect, otherAspect) / Math.Min(ownAspect, otherAspect) > 1.6) + { + return; // the matcher's aspect gate already keeps these apart + } + + var (errors, points) = CountErrors(nOcrChar, lookalike); + var wanted = Math.Max(8, points * 6 / 100); + if (points == 0 || errors >= Math.Max(4, points * 3 / 100)) + { + return; // already tells them apart + } + + // Start lines only where the glyphs differ: ink here but not in the lookalike (a + // foreground line) or the other way round (a background line). Random starts anywhere + // almost never hit a one pixel slash. + var inkOnlyHere = new List(); + var inkOnlyThere = new List(); + for (var y = 0; y < own.Height; y++) + { + for (var x = 0; x < own.Width; x++) + { + var p = new NOcrLine(new OcrPoint(x, y), new OcrPoint(x, y)).GetScaledStart(nOcrChar, lookalike.Width, lookalike.Height); + if ((uint)p.X >= (uint)lookalike.Width || (uint)p.Y >= (uint)lookalike.Height) + { + continue; + } + + var here = own.GetAlpha(x, y) > 150; + var there = lookalike.GetAlpha(p.X, p.Y) > 150; + if (here && !there) + { + inkOnlyHere.Add(new OcrPoint(x, y)); + } + else if (!here && there) + { + inkOnlyThere.Add(new OcrPoint(x, y)); + } + } + } + + if (inkOnlyHere.Count == 0 && inkOnlyThere.Count == 0) + { + return; // identical at this size - nothing to tell them apart by + } + + var r = Random.Shared; + for (var attempt = 0; attempt < 3000 && errors < wanted; attempt++) + { + var foreground = inkOnlyThere.Count == 0 || (inkOnlyHere.Count > 0 && attempt % 2 == 0); + var starts = foreground ? inkOnlyHere : inkOnlyThere; + var start = starts[r.Next(starts.Count)]; + var end = new OcrPoint( + Math.Clamp(start.X + r.Next(-8, 9), 0, nOcrChar.Width - 1), + Math.Clamp(start.Y + r.Next(-8, 9), 0, nOcrChar.Height - 1)); + if (Math.Abs(start.X - end.X) < 2 && Math.Abs(start.Y - end.Y) < 2) + { + continue; + } + + var line = new NOcrLine(start, end); + var fits = foreground + ? NOcrChar.IsMatchPointForeGround(line, false, own, nOcrChar) + : NOcrChar.IsMatchPointBackGround(line, false, own, nOcrChar); + if (!fits) + { + continue; + } + + var lineErrors = 0; + foreach (var p in line.ScaledWalkPoints(nOcrChar, lookalike.Width, lookalike.Height)) + { + if ((uint)p.X < (uint)lookalike.Width && (uint)p.Y < (uint)lookalike.Height && + lookalike.GetAlpha(p.X, p.Y) > 150 != foreground) + { + lineErrors++; + } + } + + if (lineErrors >= 2) + { + (foreground ? nOcrChar.LinesForeground : nOcrChar.LinesBackground).Add(line); + errors += lineErrors; + } + } + } + + private static (int Errors, int Points) CountErrors(NOcrChar nOcrChar, NikseBitmap2 bitmap) + { + var errors = 0; + var points = 0; + foreach (var line in nOcrChar.LinesForeground) + { + foreach (var p in line.ScaledWalkPoints(nOcrChar, bitmap.Width, bitmap.Height)) + { + if ((uint)p.X < (uint)bitmap.Width && (uint)p.Y < (uint)bitmap.Height) + { + points++; + if (bitmap.GetAlpha(p.X, p.Y) <= 150) + { + errors++; + } + } + } + } + + foreach (var line in nOcrChar.LinesBackground) + { + foreach (var p in line.ScaledWalkPoints(nOcrChar, bitmap.Width, bitmap.Height)) + { + if ((uint)p.X < (uint)bitmap.Width && (uint)p.Y < (uint)bitmap.Height) + { + points++; + if (bitmap.GetAlpha(p.X, p.Y) > 150) + { + errors++; + } + } + } + } + + return (errors, points); + } + + /// + /// Only characters that are drawn in separate pieces by design (a letter plus diacritic, or + /// punctuation like " % ; :) may be trained as an expanded character. A plain letter that + /// falls apart is a thin font losing hairlines at the two-color threshold; stored as an + /// expanded "f" or "m" it later claims pairs of ordinary glyphs ("t." read as "f"). + /// + internal static bool CanBeMultiPart(string text) + { + if (text.Length != 1) + { + return false; + } + + var c = text[0]; + if (char.IsLetterOrDigit(c)) + { + return text.Normalize(System.Text.NormalizationForm.FormD).Length > 1 || c is 'Ø' or 'ø'; + } + + return c is '"' or '%' or ';' or ':' or '?' or '!' or '=' or '÷' or '¡' or '¿' or '‰'; + } + /// /// Renders text like a typical image subtitle: white fill with a black outline on a /// transparent background. diff --git a/src/libuilogic/Ocr/OcrTwoColorThreshold.cs b/src/libuilogic/Ocr/OcrTwoColorThreshold.cs new file mode 100644 index 00000000000..6d9e722b7a2 --- /dev/null +++ b/src/libuilogic/Ocr/OcrTwoColorThreshold.cs @@ -0,0 +1,93 @@ +namespace Nikse.SubtitleEdit.UiLogic.Ocr; + +/// +/// Picks the brightness threshold (sum of R+G+B) for +/// before matching. The usual 200 keeps a white or yellow fill and drops a black outline, but a +/// grey drop shadow ("3D" subtitles) sums to 190-290 and fuses with the letters, so whole words +/// came out as "*". When a large share of what 200 would keep is mid-grey that Otsu's method +/// puts on the dark side, use Otsu's threshold instead. Normal subtitles keep 200: there only +/// anti-aliased edge pixels fall in that band (under 10% on real discs, against over 30% for +/// shadowed text), and a higher threshold would thin the glyphs compared to the database. +/// +public static class OcrTwoColorThreshold +{ + public const int Default = 200; + private const double ShadowShare = 0.2; + + public static int Get(NikseBitmap2 bitmap) + { + var data = bitmap.GetPixelData(); + var histogram = new double[766]; + for (var i = 0; i + 3 < data.Length; i += 4) + { + var alpha = data[i + 3]; + if (alpha == 0) + { + continue; + } + + histogram[data[i] + data[i + 1] + data[i + 2]] += alpha / 255.0; + } + + var otsu = GetOtsuThreshold(histogram); + if (otsu <= Default) + { + return Default; + } + + double kept = 0; + double shadow = 0; + for (var i = Default; i < histogram.Length; i++) + { + kept += histogram[i]; + if (i < otsu) + { + shadow += histogram[i]; + } + } + + return kept > 0 && shadow / kept > ShadowShare ? otsu : Default; + } + + private static int GetOtsuThreshold(double[] histogram) + { + double total = 0; + double sumAll = 0; + for (var i = 0; i < histogram.Length; i++) + { + total += histogram[i]; + sumAll += i * histogram[i]; + } + + double weightBackground = 0; + double sumBackground = 0; + var bestVariance = -1.0; + var threshold = Default; + for (var i = 0; i < histogram.Length; i++) + { + weightBackground += histogram[i]; + if (weightBackground == 0) + { + continue; + } + + var weightForeground = total - weightBackground; + if (weightForeground <= 0) + { + break; + } + + sumBackground += i * histogram[i]; + var meanBackground = sumBackground / weightBackground; + var meanForeground = (sumAll - sumBackground) / weightForeground; + var variance = weightBackground * weightForeground * (meanBackground - meanForeground) * (meanBackground - meanForeground); + if (variance > bestVariance) + { + bestVariance = variance; + threshold = i + 1; + } + } + + return threshold; + } +} diff --git a/src/seconv/Core/NOcrOcrEngine.cs b/src/seconv/Core/NOcrOcrEngine.cs index 7e2de19e4b9..a742e8105e3 100644 --- a/src/seconv/Core/NOcrOcrEngine.cs +++ b/src/seconv/Core/NOcrOcrEngine.cs @@ -15,7 +15,7 @@ internal sealed class NOcrOcrEngine : IOcrEngine private readonly NOcrDb _db; private readonly NOcrCaseFixer _caseFixer = new(); private const int MaxWrongPixels = 25; - private const int PixelsAreSpaceDefault = 12; + private readonly NOcrSpaceDetector _spaceDetector = new(); public NOcrOcrEngine(string nOcrDbPath) { @@ -40,12 +40,13 @@ public string Recognize(SKBitmap bitmap) } var parent = new NikseBitmap2(bitmap); - parent.MakeTwoColor(200); + parent.MakeTwoColor(OcrTwoColorThreshold.Get(parent)); parent.CropTop(0, new SKColor(0, 0, 0, 0)); var letters = NikseBitmapImageSplitter2.SplitBitmapToLettersNew( - parent, PixelsAreSpaceDefault, rightToLeft: false, topToBottom: true, + parent, NOcrSpaceDetector.SplitPixelsAreSpace, rightToLeft: false, topToBottom: true, _lineHeightTracker.GetMinLineHeight(), autoHeight: true, _lineHeightTracker.GetAverageLineHeight()); _lineHeightTracker.Update(letters); + letters = _spaceDetector.RemoveFalseSpaces(letters, out _); var matches = new List(); var i = 0; diff --git a/src/ui/Features/Ocr/OcrViewModel.cs b/src/ui/Features/Ocr/OcrViewModel.cs index 1c8339ec711..f4fd3bfc309 100644 --- a/src/ui/Features/Ocr/OcrViewModel.cs +++ b/src/ui/Features/Ocr/OcrViewModel.cs @@ -181,6 +181,7 @@ public partial class OcrViewModel : ObservableObject private IOcrSubtitle? _ocrSubtitle; private OcrLineHeightTracker _lineHeightTracker = new(); + private NOcrSpaceDetector _nOcrSpaceDetector = new(); private List _allOcrSubtitleItems = new(); private string _sourceFileName = string.Empty; private Iso639Dash2LanguageCode? _sourceLanguageIso; @@ -242,7 +243,7 @@ public OcrViewModel( ImageCompareDatabases = new ObservableCollection(BinaryOcrDb.GetDatabases(Se.OcrFolder)); SelectedImageCompareDatabase = ImageCompareDatabases.FirstOrDefault(); NOcrMaxWrongPixelsList = new ObservableCollection(Enumerable.Range(0, 500)); - NOcrPixelsAreSpaceList = new ObservableCollection(Enumerable.Range(1, 50)); + NOcrPixelsAreSpaceList = new ObservableCollection(Enumerable.Range(0, 51)); // 0 = auto BinaryOcrPixelsAreSpaceList = new ObservableCollection(Enumerable.Range(1, 50)); OllamaLanguages = new ObservableCollection(Iso639Dash2LanguageCode.List .Select(p => p.EnglishName) @@ -1180,10 +1181,9 @@ private async Task InspectLineNOcr(OcrSubtitleItem item) var bitmap = item.GetSkBitmap(); var nBmp = new NikseBitmap2(bitmap); - nBmp.MakeTwoColor(200); + nBmp.MakeTwoColor(OcrTwoColorThreshold.Get(nBmp)); nBmp.CropTop(0, new SKColor(0, 0, 0, 0)); - var letters = - NikseBitmapImageSplitter2.SplitBitmapToLettersNew(nBmp, SelectedNOcrPixelsAreSpace, false, true, _lineHeightTracker.GetMinLineHeight(), true, _lineHeightTracker.GetAverageLineHeight()); + var letters = SplitNOcrLetters(nBmp, out _); var matches = new List(new NOcrChar?[letters.Count]); var idx = 0; while (idx < letters.Count) @@ -3289,10 +3289,9 @@ private void NOcrWarmUp() var item = OcrSubtitleItems[i]; var bitmap = item.GetSkBitmap(); var parentBitmap = new NikseBitmap2(bitmap); - parentBitmap.MakeTwoColor(200); + parentBitmap.MakeTwoColor(OcrTwoColorThreshold.Get(parentBitmap)); parentBitmap.CropTop(0, new SKColor(0, 0, 0, 0)); - var letters = NikseBitmapImageSplitter2.SplitBitmapToLettersNew(parentBitmap, SelectedNOcrPixelsAreSpace, - false, true, _lineHeightTracker.GetMinLineHeight(), true, _lineHeightTracker.GetAverageLineHeight()); + var letters = SplitNOcrLetters(parentBitmap, out _); _lineHeightTracker.Update(letters); var index = 0; while (index < letters.Count) @@ -3329,10 +3328,9 @@ private async Task RunNOcrLoop(List selectedIndices, CancellationToken canc var item = OcrSubtitleItems[i]; var bitmap = item.GetSkBitmap(); var parentBitmap = new NikseBitmap2(bitmap); - parentBitmap.MakeTwoColor(200); + parentBitmap.MakeTwoColor(OcrTwoColorThreshold.Get(parentBitmap)); parentBitmap.CropTop(0, new SKColor(0, 0, 0, 0)); - var letters = NikseBitmapImageSplitter2.SplitBitmapToLettersNew(parentBitmap, SelectedNOcrPixelsAreSpace, - false, true, _lineHeightTracker.GetMinLineHeight(), true, _lineHeightTracker.GetAverageLineHeight()); + var letters = SplitNOcrLetters(parentBitmap, out var pixelsAreSpace); _lineHeightTracker.Update(letters); OcrUiUpdates.EnqueueSelect(i); var index = 0; @@ -3481,14 +3479,14 @@ await _windowService return; } - matches = RemoveSpacesAfter1(matches, SelectedNOcrPixelsAreSpace); + matches = RemoveSpacesAfter1(matches, pixelsAreSpace); item.Text = ItalicTextMerger.MergeWithItalicTags(matches).Trim(); var ocrFixResultTemp = OcrFixLine(i, item); if (ocrFixResultTemp.UnknownWords.Count > 0 && item.Text.Contains("", StringComparison.Ordinal)) { var unItalicFactor = 0.33; - var text = ItalicSpaceFixer.GetTextWithMoreSpacesInItalic(matches, letters, parentBitmap, unItalicFactor, SelectedNOcrPixelsAreSpace); + var text = ItalicSpaceFixer.GetTextWithMoreSpacesInItalic(matches, letters, parentBitmap, unItalicFactor, pixelsAreSpace); var unItalicItem = new OcrSubtitleItem(item, text); var unItalicResultTemp = OcrFixLine(i, unItalicItem); if (ocrFixResultTemp.UnknownWords.Count > unItalicResultTemp.UnknownWords.Count) @@ -3527,6 +3525,26 @@ await _windowService IsOcrRunning = false; } + /// + /// Splits a two-color bitmap into letters for nOCR. "Pixels are space" 0 means auto: split at + /// every gap and let decide the word spaces relative to the + /// text size. is the pixel threshold actually used. + /// + private List SplitNOcrLetters(NikseBitmap2 parentBitmap, out int pixelsAreSpace) + { + var isAuto = SelectedNOcrPixelsAreSpace <= 0; + var letters = NikseBitmapImageSplitter2.SplitBitmapToLettersNew(parentBitmap, + isAuto ? NOcrSpaceDetector.SplitPixelsAreSpace : SelectedNOcrPixelsAreSpace, + false, true, _lineHeightTracker.GetMinLineHeight(), true, _lineHeightTracker.GetAverageLineHeight()); + pixelsAreSpace = SelectedNOcrPixelsAreSpace; + if (isAuto) + { + letters = _nOcrSpaceDetector.RemoveFalseSpaces(letters, out pixelsAreSpace); + } + + return letters; + } + private static List RemoveSpacesAfter1(List matches, int pixelsAreSpace) { var deleteItems = new List(); @@ -5269,6 +5287,7 @@ private void SetOcrSubtitleItems() { FallbackMinLineHeight = _ocrSubtitle is OcrSubtitleBluRay or OcrSubtitleMkvBluRay ? 25 : 12, }; + _nOcrSpaceDetector = new NOcrSpaceDetector(); } partial void OnShowOnlyForcedChanged(bool value) diff --git a/src/ui/Features/Ocr/OcrWindow.cs b/src/ui/Features/Ocr/OcrWindow.cs index ed8b4508cd8..a92610bd0de 100644 --- a/src/ui/Features/Ocr/OcrWindow.cs +++ b/src/ui/Features/Ocr/OcrWindow.cs @@ -288,8 +288,8 @@ private static Grid MakeTopControlsView(OcrViewModel vm) .WithMarginRight(10) .BindIsEnabled(vm, nameof(OcrViewModel.IsOcrRunning), InverseBooleanConverter.Instance), UiUtil.MakeLabel(Se.Language.Ocr.NumberOfPixelsIsSpace, vm => vm.IsNOcrVisible), - UiUtil.MakeComboBox(vm.NOcrPixelsAreSpaceList, vm, nameof(vm.SelectedNOcrPixelsAreSpace), - nameof(vm.IsNOcrVisible)) + WithPixelsAreSpaceItemTemplate(UiUtil.MakeComboBox(vm.NOcrPixelsAreSpaceList, vm, nameof(vm.SelectedNOcrPixelsAreSpace), + nameof(vm.IsNOcrVisible))) .WithMarginRight(10) .BindIsEnabled(vm, nameof(OcrViewModel.IsOcrRunning), InverseBooleanConverter.Instance), @@ -1336,6 +1336,16 @@ private static DownloadDotStatus GetOcrEngineDotStatus(OcrEngineItem engine) // Model combo item template: a dot (green = downloaded, grey = not downloaded yet) plus the // model's download size - same treatment as the speech-to-text model combo. + // 0 = auto: word spaces relative to the text size (NOcrSpaceDetector). + private static ComboBox WithPixelsAreSpaceItemTemplate(ComboBox comboBox) + { + comboBox.ItemTemplate = new FuncDataTemplate((value, _) => new TextBlock + { + Text = value <= 0 ? Se.Language.General.Auto : value.ToString(System.Globalization.CultureInfo.CurrentCulture), + }, true); + return comboBox; + } + private static FuncDataTemplate MakeCrispEmbedModelItemTemplate() { return StatusDots.ComboItemTemplate( diff --git a/src/ui/Features/Tools/BatchConvert/BatchConverter.cs b/src/ui/Features/Tools/BatchConvert/BatchConverter.cs index 233a4923e35..4555d8538c0 100644 --- a/src/ui/Features/Tools/BatchConvert/BatchConverter.cs +++ b/src/ui/Features/Tools/BatchConvert/BatchConverter.cs @@ -104,6 +104,7 @@ public class BatchConverter : IBatchConverter, IFixCallbacks private readonly INOcrCaseFixer _nOcrCaseFixer; private readonly IBinaryOcrMatcher _binaryOcrMatcher; private OcrLineHeightTracker _lineHeightTracker = new(); + private NOcrSpaceDetector _nOcrSpaceDetector = new(); private readonly INamesList _namesList; private string _namesListFolder = string.Empty; private string _namesListLanguage = string.Empty; @@ -1001,6 +1002,7 @@ private static async Task RunOcrTesseract(IOcrSubtitle imageSubtitles, BatchConv private void RunNOcr(IOcrSubtitle imageSubtitles, BatchConvertItem item, CancellationToken cancellationToken) { _lineHeightTracker = new OcrLineHeightTracker { FallbackMinLineHeight = item.Format == FormatBluRaySup ? 25 : 12 }; + _nOcrSpaceDetector = new NOcrSpaceDetector(); var fileName = Path.Combine(Se.OcrFolder, Se.Settings.Ocr.NOcrDatabase + ".nocr"); var nOcrDb = new NOcrDb(fileName); var totalCount = imageSubtitles.Count; @@ -1023,10 +1025,11 @@ private void RunNOcr(IOcrSubtitle imageSubtitles, BatchConvertItem item, Cancell } } - // Pre-flight: detect pixels-is-space from a sample of images. + // Pre-flight: detect pixels-is-space from a sample of images. With "pixels are space" + // set to auto (0) every image is measured on its own instead (NOcrSpaceDetector). var sampleSize = Math.Min(OcrPreflightSampleSize, totalCount); - var pixelsAreSpace = Se.Settings.Ocr.NOcrPixelsAreSpace > 0 ? Se.Settings.Ocr.NOcrPixelsAreSpace : 12; - if (sampleSize > 0) + var pixelsAreSpace = Se.Settings.Ocr.NOcrPixelsAreSpace; + if (sampleSize > 0 && pixelsAreSpace > 0) { item.Status = Se.Language.General.OcrDotDotDot; var detected = DetectPixelsIsSpace(imageSubtitles, sampleSize, cancellationToken); @@ -1095,11 +1098,18 @@ private Paragraph OcrSingleNOcrImage(IOcrSubtitle imageSubtitles, int i, NOcrDb { var bitmap = imageSubtitles.GetBitmap(i); var parentBitmap = new NikseBitmap2(bitmap); - parentBitmap.MakeTwoColor(200); + parentBitmap.MakeTwoColor(OcrTwoColorThreshold.Get(parentBitmap)); parentBitmap.CropTop(0, new SKColor(0, 0, 0, 0)); - var letters = NikseBitmapImageSplitter2.SplitBitmapToLettersNew(parentBitmap, pixelsAreSpace, + var isAutoSpace = pixelsAreSpace <= 0; + var letters = NikseBitmapImageSplitter2.SplitBitmapToLettersNew(parentBitmap, + isAutoSpace ? NOcrSpaceDetector.SplitPixelsAreSpace : pixelsAreSpace, false, true, _lineHeightTracker.GetMinLineHeight(), true, _lineHeightTracker.GetAverageLineHeight()); _lineHeightTracker.Update(letters); + if (isAutoSpace) + { + letters = _nOcrSpaceDetector.RemoveFalseSpaces(letters, out pixelsAreSpace); + } + var index = 0; var matches = new List(); var maxErrorPercent = Se.Settings.Ocr.BinaryOcrMaxErrorPercent > 0 ? Se.Settings.Ocr.BinaryOcrMaxErrorPercent : 7.5; diff --git a/src/ui/Logic/Config/SeOcr.cs b/src/ui/Logic/Config/SeOcr.cs index 6615ec6b768..974b6ba9522 100644 --- a/src/ui/Logic/Config/SeOcr.cs +++ b/src/ui/Logic/Config/SeOcr.cs @@ -114,7 +114,7 @@ public SeOcr() NOcrBinaryOcrFallbackDatabase = string.Empty; BinaryOcrNOcrFallbackDatabase = string.Empty; NOcrMaxWrongPixels = 25; - NOcrPixelsAreSpace = 12; + NOcrPixelsAreSpace = 0; // auto, see NOcrSpaceDetector NOcrDrawUnknownText = true; NOcrNoOfLinesToAutoDraw = 60; NOcrZoomFactor = 4; diff --git a/tests/UI/Features/Ocr/FixEngine/OcrFixAccentMisreadTests.cs b/tests/UI/Features/Ocr/FixEngine/OcrFixAccentMisreadTests.cs new file mode 100644 index 00000000000..8902ed76fb9 --- /dev/null +++ b/tests/UI/Features/Ocr/FixEngine/OcrFixAccentMisreadTests.cs @@ -0,0 +1,103 @@ +using Nikse.SubtitleEdit.Core.Common; +using Nikse.SubtitleEdit.Features.Ocr.FixEngine; +using Nikse.SubtitleEdit.Features.SpellCheck; +using System; +using System.Collections.Generic; +using System.IO; +using Nikse.SubtitleEdit.UiLogic.Ocr.FixEngine; +using Nikse.SubtitleEdit.UiLogic.SpellCheck; + +namespace UITests.Features.Ocr.FixEngine; + +// nOCR reads a plain letter as an accented lookalike when the accent sits where the letter's dot +// is ("ìs", "rîght") and mixes up accents ("Màske" for "Måske"). Measured on real subtitles these +// were the most common errors left after matching; the dictionary decides between the lookalikes. +public class OcrFixAccentMisreadTests : IDisposable +{ + private readonly Func _originalSpellCheckDictionariesFolder; + private readonly string _tempDictionariesFolder; + + public OcrFixAccentMisreadTests() + { + _originalSpellCheckDictionariesFolder = SpellCheckConfig.DictionariesFolder; + _tempDictionariesFolder = Path.Combine(Path.GetTempPath(), "SeOcrFixAccentMisreadTest_" + Guid.NewGuid().ToString("N")); + Directory.CreateDirectory(_tempDictionariesFolder); + SpellCheckConfig.DictionariesFolder = () => _tempDictionariesFolder; + } + + [Theory] + [InlineData("This ìs it.", "This is it.")] + [InlineData("All rîght, bîg guy.", "All right, big guy.")] + [InlineData("Wìth a bunch of movíes.", "With a bunch of movies.")] + public void FixOcrErrors_PlainLetterReadAsAccented_IsFixed(string input, string expected) + { + var engine = CreateEngine("eng", "this", "is", "it", "all", "right", "big", "guy", "with", "a", "bunch", "of", "movies"); + + Assert.Equal(expected, engine.FixOcrErrors(0, input, doTryToGuessUnknownWords: false).GetText()); + } + + [Fact] + public void FixOcrErrors_WrongAccent_PrefersAnotherAccentOverThePlainLetter() + { + // Both "maske" (mask) and "måske" (maybe) are Danish words; the OCR saw a mark, so the + // accented reading wins. + var engine = CreateEngine("dan", "maske", "måske", "får", "far", "du", "det"); + + Assert.Equal("Måske får du det.", engine.FixOcrErrors(0, "Màske fàr du det.", doTryToGuessUnknownWords: false).GetText()); + } + + [Fact] + public void FixOcrErrors_CorrectAccentedWord_IsLeftAlone() + { + var engine = CreateEngine("fra", "café", "cafe", "un"); + + Assert.Equal("Un café.", engine.FixOcrErrors(0, "Un café.", doTryToGuessUnknownWords: false).GetText()); + } + + [Fact] + public void FixOcrErrors_NoLookalikeInDictionary_IsLeftAlone() + { + var engine = CreateEngine("eng", "is"); + + Assert.Equal("Zoë is", engine.FixOcrErrors(0, "Zoë is", doTryToGuessUnknownWords: false).GetText()); + } + + private static IOcrFixEngine CreateEngine(string language, params string[] words) + { + IOcrFixEngine engine = new OcrFixEngine(new FakeSpellChecker(words)); + engine.Initialize(new Subtitle(), language, new SpellCheckDictionaryDisplay()); + return engine; + } + + private sealed class FakeSpellChecker(string[] words) : ISpellChecker + { + private readonly HashSet _words = new(words, StringComparer.Ordinal); + + public bool Initialize(string dictionaryFile, string twoLetterLanguageCode) => true; + + public bool IsWordCorrect(string word) + { + if (string.IsNullOrWhiteSpace(word)) + { + return true; + } + + return _words.Contains(word) || _words.Contains(word.ToLowerInvariant()); + } + + public List GetSuggestions(string word) => new(); + } + + public void Dispose() + { + SpellCheckConfig.DictionariesFolder = _originalSpellCheckDictionariesFolder; + try + { + Directory.Delete(_tempDictionariesFolder, recursive: true); + } + catch + { + // Best-effort cleanup. + } + } +} diff --git a/tests/libuilogic/Ocr/NOcrSpaceDetectorTests.cs b/tests/libuilogic/Ocr/NOcrSpaceDetectorTests.cs new file mode 100644 index 00000000000..11b57707301 --- /dev/null +++ b/tests/libuilogic/Ocr/NOcrSpaceDetectorTests.cs @@ -0,0 +1,96 @@ +using Nikse.SubtitleEdit.UiLogic.Ocr; + +namespace LibUiLogicTests.Ocr; + +/// +/// Word spaces are decided relative to the line height and per image, because a fixed "pixels +/// are space" value dropped every space of DVD-sized text (7-9 px gaps under the default 12) and +/// split widely tracked all-caps text into single letters (12 px letter gaps). +/// +public class NOcrSpaceDetectorTests +{ + // One text line: a glyph of the given height, then for each gap a space item and a glyph. + private static List Line(int height, params int[] gaps) + { + var list = new List { Glyph(height) }; + foreach (var gap in gaps) + { + list.Add(new ImageSplitterItem2(" ") { SpacePixels = gap }); + list.Add(Glyph(height)); + } + + return list; + } + + private static ImageSplitterItem2 Glyph(int height) + { + return new ImageSplitterItem2(0, 0, new NikseBitmap2(10, height)) { Top = 0 }; + } + + private static List SpacesKept(List letters) + { + return letters.Where(p => p.SpecialCharacter == " ").Select(p => p.SpacePixels).ToList(); + } + + [Fact] + public void DvdSizedText_KeepsSmallWordSpaces() + { + var detector = new NOcrSpaceDetector(); + + // 28 px line: letter gaps 2-3 px, word spaces 8-9 px (all dropped by the old fixed 12). + var result = detector.RemoveFalseSpaces(Line(28, 2, 3, 8, 2, 3, 9, 2), out _); + + Assert.Equal(new List { 8, 9 }, SpacesKept(result)); + } + + [Fact] + public void TrackedAllCaps_LetterGapsAreNotSpaces() + { + var detector = new NOcrSpaceDetector(); + + // 40 px all-caps line with wide tracking: 10-13 px letter gaps, 22-24 px word spaces. + var result = detector.RemoveFalseSpaces(Line(40, 12, 11, 13, 22, 10, 12, 24, 11), out var pixelsAreSpace); + + Assert.Equal(new List { 22, 24 }, SpacesKept(result)); + Assert.InRange(pixelsAreSpace, 14, 22); + } + + [Fact] + public void OneWordLine_UsesThresholdOfEarlierImages() + { + var detector = new NOcrSpaceDetector(); + detector.RemoveFalseSpaces(Line(40, 12, 11, 13, 22, 10, 12, 24, 11), out _); + + // No word space to calibrate on - the 13 px letter gaps must not turn into spaces, + // which the default relative threshold (0.22 x 40 = 9 px) would do. + var result = detector.RemoveFalseSpaces(Line(40, 12, 13, 11), out _); + + Assert.Empty(SpacesKept(result)); + } + + [Fact] + public void NoHistory_FallsBackToDefaultRelativeThreshold() + { + var detector = new NOcrSpaceDetector(); + + // 50 px line, 0.22 x 50 = 11 px. + var result = detector.RemoveFalseSpaces(Line(50, 4, 4), out var pixelsAreSpace); + + Assert.Empty(SpacesKept(result)); + Assert.Equal(11, pixelsAreSpace); + } + + [Fact] + public void LineBreaks_AreKept_AndEachLineUsesItsOwnHeight() + { + var detector = new NOcrSpaceDetector(); + var letters = Line(30, 2, 9, 2); + letters.Add(new ImageSplitterItem2(Environment.NewLine)); + letters.AddRange(Line(60, 4, 18, 4)); + + var result = detector.RemoveFalseSpaces(letters, out _); + + Assert.Single(result, p => p.SpecialCharacter == Environment.NewLine); + Assert.Equal(new List { 9, 18 }, SpacesKept(result)); + } +} diff --git a/tests/libuilogic/Ocr/NOcrTrainerTests.cs b/tests/libuilogic/Ocr/NOcrTrainerTests.cs index da4e3660d80..48d7fb9656c 100644 --- a/tests/libuilogic/Ocr/NOcrTrainerTests.cs +++ b/tests/libuilogic/Ocr/NOcrTrainerTests.cs @@ -133,4 +133,88 @@ public void Trainer_UnknownGlyph_RecognizesAsAsterisk() Assert.StartsWith("Hello ", result); Assert.Contains("*", result); } + + [Theory] + [InlineData("é", true)] + [InlineData("ø", true)] + [InlineData("\"", true)] + [InlineData("%", true)] + [InlineData("f", false)] + [InlineData("m", false)] + [InlineData("2", false)] + [InlineData("(", false)] + public void CanBeMultiPart_OnlyForGlyphsDrawnInPieces(string text, bool expected) + { + // A plain letter falling apart is a thin font losing hairlines at the threshold; stored + // as an expanded "f" it later claimed "t." as one character. + Assert.Equal(expected, NOcrTrainer.CanBeMultiPart(text)); + } + + [Fact] + public void AddDiscriminativeLines_SeparatesThinFeatureFromLookalike() + { + // "ø" vs "o": a ring with and without a one pixel slash through the hole. The random + // generator puts no lines on a stroke that thin, so the entry matched both. + var own = Ring(withSlash: true); + var lookalike = Ring(withSlash: false); + var nOcrChar = new NOcrChar("ø") { Width = own.Width, Height = own.Height }; + nOcrChar.LinesForeground.Add(new NOcrLine(new OcrPoint(1, 2), new OcrPoint(1, 17))); + Assert.True(NOcrDb.IsMatch(lookalike, nOcrChar, 0)); + + NOcrTrainer.AddDiscriminativeLines(nOcrChar, own, lookalike); + + Assert.True(NOcrDb.IsMatch(own, nOcrChar, 0)); + Assert.False(NOcrDb.IsMatch(lookalike, nOcrChar, 3)); + } + + private static NikseBitmap2 Ring(bool withSlash) + { + using var bitmap = new SKBitmap(20, 20); + bitmap.Erase(SKColors.Transparent); + for (var y = 0; y < 20; y++) + { + for (var x = 0; x < 20; x++) + { + var inHole = x >= 5 && x < 15 && y >= 5 && y < 15; + var onSlash = withSlash && inHole && x == y; + if (!inHole || onSlash) + { + bitmap.SetPixel(x, y, SKColors.White); + } + } + } + + return new NikseBitmap2(bitmap); + } + + [Fact] + public void TrainedDb_RecognizesTextAtTwiceTheTrainedSize() + { + // Top margins, aspect gates and error budgets used to be absolute pixels at the trained + // size, so "." and "i" (tall, thin, or small) never matched at another size. + var db = Train("This."); + + var result = RecognizeAtSize(db, "This is his.", FontSize * 2); + + Assert.Equal("This is his.", result); + } + + private static string RecognizeAtSize(NOcrDb db, string text, float fontSize) + { + using var bmp = NOcrTrainer.RenderCharacterImage(text, TestFontName, fontSize, false, false); + Assert.NotNull(bmp); + var parent = new NikseBitmap2(bmp!); + parent.MakeTwoColor(200); + parent.CropTop(0, new SKColor(0, 0, 0, 0)); + var letters = NikseBitmapImageSplitter2.SplitBitmapToLettersNew(parent, PixelsAreSpace * 2, false, false, 25, false); + var sb = new StringBuilder(); + foreach (var item in letters) + { + sb.Append(item.NikseBitmap == null + ? item.SpecialCharacter + : db.GetMatch(parent, letters, item, item.Top, true, MaxWrongPixels)?.Text ?? "*"); + } + + return sb.ToString().Trim(); + } } diff --git a/tests/libuilogic/Ocr/OcrTwoColorThresholdTests.cs b/tests/libuilogic/Ocr/OcrTwoColorThresholdTests.cs new file mode 100644 index 00000000000..466c642f929 --- /dev/null +++ b/tests/libuilogic/Ocr/OcrTwoColorThresholdTests.cs @@ -0,0 +1,61 @@ +using Nikse.SubtitleEdit.UiLogic.Ocr; +using SkiaSharp; + +namespace LibUiLogicTests.Ocr; + +/// +/// "3D" subtitles carry a grey drop shadow that the fixed two-color threshold of 200 keeps as +/// ink, fusing it with the letters; the threshold switches to Otsu's value only then. +/// +public class OcrTwoColorThresholdTests +{ + private static NikseBitmap2 Make(SKColor fill, int fillPixels, SKColor edge, int edgePixels) + { + using var bitmap = new SKBitmap(100, 10); + bitmap.Erase(SKColors.Transparent); + var n = 0; + for (var y = 0; y < 10; y++) + { + for (var x = 0; x < 100; x++, n++) + { + if (n < fillPixels) + { + bitmap.SetPixel(x, y, fill); + } + else if (n < fillPixels + edgePixels) + { + bitmap.SetPixel(x, y, edge); + } + } + } + + return new NikseBitmap2(bitmap); + } + + [Fact] + public void WhiteTextWithBlackOutline_KeepsDefault() + { + var bitmap = Make(SKColors.White, 400, SKColors.Black, 300); + + Assert.Equal(OcrTwoColorThreshold.Default, OcrTwoColorThreshold.Get(bitmap)); + } + + [Fact] + public void FewAntiAliasedGreyPixels_KeepsDefault() + { + var bitmap = Make(SKColors.White, 400, new SKColor(90, 90, 90), 30); + + Assert.Equal(OcrTwoColorThreshold.Default, OcrTwoColorThreshold.Get(bitmap)); + } + + [Fact] + public void GreyDropShadow_IsThresholdedAway() + { + // Light grey fill with a large mid-grey (sum 240) shadow, which 200 would keep as ink. + var bitmap = Make(new SKColor(220, 220, 220), 400, new SKColor(80, 80, 80), 300); + + var threshold = OcrTwoColorThreshold.Get(bitmap); + + Assert.InRange(threshold, 241, 660); + } +} From 7e2e60539bf097519e18f2a4c360e8f2d802e9c0 Mon Sep 17 00:00:00 2001 From: niksedk Date: Sat, 10 Oct 2026 07:40:11 +0200 Subject: [PATCH 003/306] Perf hunt round 31: ten verified speed-ups - CDG frames: write transparent pixels while packing (53x) - Ripple delete / column commands: row index lookup instead of IndexOf per row (50x at 10k selected) - Merge lines with same text preview: blocked line numbers once per pass (5.9x) - Apply duration limits preview: binary search shot changes (4.5x) - FCE NormalizeStrings: skip ASCII lines and lines without replaced chars (2.3x) - JSON parser: slice plain strings, no boxing log calls, ordinal Accept (2.1x) - FCE FixMusicNotation: precompute symbols, strip tags only on change (2.0x) - FCE FixUnnecessaryLeadingDots: skip culture StartsWith for letter/digit starts (1.5-1.85x) - TTS spoken text in video: single-pass closest line (1.35x) - ARIB roll-up merge: one compaction instead of RemoveAt per merge (1.29x) Benchmarks: tests/benchmarks/PerfHuntRound31Benchmarks.cs Co-Authored-By: Claude Opus 5.5 --- src/libse/CDG/CdgGraphics.cs | 47 +-- src/libse/Common/TinyJsonParser.cs | 33 +- .../TransportStream/AribCaptionParser.cs | 21 +- .../Forms/FixCommonErrors/FixMusicNotation.cs | 19 +- .../FixUnnecessaryLeadingDots.cs | 8 + .../Forms/FixCommonErrors/NormalizeStrings.cs | 193 +++++---- src/ui/Features/Main/MainViewModel.cs | 23 +- .../ApplyDurationLimitsViewModel.cs | 25 +- .../MergeSameTextViewModel.cs | 18 +- .../TextToSpeech/TextToSpeechViewModel.cs | 14 +- tests/benchmarks/PerfHuntRound31Benchmarks.cs | 396 ++++++++++++++++++ 11 files changed, 661 insertions(+), 136 deletions(-) create mode 100644 tests/benchmarks/PerfHuntRound31Benchmarks.cs diff --git a/src/libse/CDG/CdgGraphics.cs b/src/libse/CDG/CdgGraphics.cs index ebe438b87cc..5da5ed3b213 100644 --- a/src/libse/CDG/CdgGraphics.cs +++ b/src/libse/CDG/CdgGraphics.cs @@ -84,7 +84,11 @@ public SKBitmap ToBitmap(int packetNumber) // long timeInMilliseconds) // Skia read that back as R,G,B,A - i.e. red and blue swapped in every frame. var bitmap = new SKBitmap(FullWidth, FullHeight, SKColorType.Bgra8888, SKAlphaType.Premul); - // Copy pixel data into the bitmap + // Copy pixel data into the bitmap, making the color at (1, 1) transparent on the way + // (equivalent to the original's image.MakeTransparent(image.GetPixel(1, 1))). Every + // pixel is opaque, so a match is written as 0 - what a Src-blended transparent + // DrawPoint used to produce, without two native calls per pixel. + var transparentValue = PackOpaque(graphicData[1, 1]); var pixelData = bitmap.GetPixels(); unsafe { @@ -93,41 +97,24 @@ public SKBitmap ToBitmap(int packetNumber) // long timeInMilliseconds) { for (var x = 0; x < FullWidth; x++) { - var colorValue = graphicData[y, x]; - var a = 255; // Fully opaque - var r = (byte)((colorValue >> 16) & 0xFF); - var g = (byte)((colorValue >> 8) & 0xFF); - var b = (byte)(colorValue & 0xFF); - - // Pack ARGB components into a single 32-bit integer - pixels[y * FullWidth + x] = (uint)((a << 24) | (r << 16) | (g << 8) | b); + var packed = PackOpaque(graphicData[y, x]); + pixels[y * FullWidth + x] = packed == transparentValue ? 0 : packed; } } } - // Make a specific color transparent (equivalent to the original's image.MakeTransparent(image.GetPixel(1, 1))) - var transparentColor = bitmap.GetPixel(1, 1); - using (var canvas = new SKCanvas(bitmap)) - { - var paint = new SKPaint - { - Color = SKColors.Transparent, - BlendMode = SKBlendMode.Src - }; + return bitmap; + } - for (var y = 0; y < FullHeight; y++) - { - for (var x = 0; x < FullWidth; x++) - { - if (bitmap.GetPixel(x, y) == transparentColor) - { - canvas.DrawPoint(x, y, paint); - } - } - } - } + private static uint PackOpaque(int colorValue) + { + var a = 255; // Fully opaque + var r = (byte)((colorValue >> 16) & 0xFF); + var g = (byte)((colorValue >> 8) & 0xFF); + var b = (byte)(colorValue & 0xFF); - return bitmap; + // Pack ARGB components into a single 32-bit integer + return (uint)((a << 24) | (r << 16) | (g << 8) | b); } private bool Process(Packet packet) diff --git a/src/libse/Common/TinyJsonParser.cs b/src/libse/Common/TinyJsonParser.cs index 7a2eeacc2fd..c9d10c3b246 100644 --- a/src/libse/Common/TinyJsonParser.cs +++ b/src/libse/Common/TinyJsonParser.cs @@ -132,6 +132,12 @@ private void WriteLineLog(string msg, params object[] args) Logger?.WriteLine(msg, args); } + // The params overload boxes the value and allocates an array for every token, logger or not. + private void WriteLineLog(string msg, T arg) + { + Logger?.WriteLine(msg, arg); + } + private ParserException BuildParserException(string msg) { if (CollectLineInfo) @@ -176,11 +182,10 @@ private string Accept(string s) return null; } - if (Input.IndexOf(s, Pos, len, StringComparison.Ordinal) != -1) + if (string.CompareOrdinal(Input, Pos, s, 0, len) == 0) { - var match = Input.Substring(Pos, len); AdvanceInput(len); - return match; + return s; } return null; @@ -325,6 +330,28 @@ private void ExpectDigits(ref int pos) private string String() { int currentPos = Pos; + + // Most strings have no escapes: slice them out instead of appending char by char. + while (currentPos < InputLength) + { + var ch = Input[currentPos]; + if (ch == '"') + { + var plain = Input.Substring(Pos, currentPos - Pos); + AdvanceInput(currentPos - Pos + 1); + WriteLineLog("string: {0}", plain); + return plain; + } + + if (ch == '\\' || ch < 0x20) + { + break; + } + + currentPos++; + } + + currentPos = Pos; StringBuilder sb = new StringBuilder(); while (true) diff --git a/src/libse/ContainerFormats/TransportStream/AribCaptionParser.cs b/src/libse/ContainerFormats/TransportStream/AribCaptionParser.cs index d1e4172988b..7d5574c3266 100644 --- a/src/libse/ContainerFormats/TransportStream/AribCaptionParser.cs +++ b/src/libse/ContainerFormats/TransportStream/AribCaptionParser.cs @@ -105,6 +105,10 @@ public void Flush() /// private static void MergeContinuations(List paragraphs) { + // A merge only removes index i, above everything compared later, so the removals + // are collected and done in one compaction (RemoveAt per merge was O(n) each). + var removed = new bool[paragraphs.Count]; + var anyRemoved = false; for (var i = paragraphs.Count - 1; i > 0; i--) { var previous = paragraphs[i - 1]; @@ -114,9 +118,24 @@ private static void MergeContinuations(List paragraphs) { previous.Text = current.Text; previous.EndTime.TotalMilliseconds = current.EndTime.TotalMilliseconds; - paragraphs.RemoveAt(i); + removed[i] = true; + anyRemoved = true; } } + + if (anyRemoved) + { + var write = 0; + for (var read = 0; read < paragraphs.Count; read++) + { + if (!removed[read]) + { + paragraphs[write++] = paragraphs[read]; + } + } + + paragraphs.RemoveRange(write, paragraphs.Count - write); + } } private void ParseCaptionManagementData(byte[] payload, int index, int end, int groupSet) diff --git a/src/libse/Forms/FixCommonErrors/FixMusicNotation.cs b/src/libse/Forms/FixCommonErrors/FixMusicNotation.cs index 622c8be083c..bcfb135c6f2 100644 --- a/src/libse/Forms/FixCommonErrors/FixMusicNotation.cs +++ b/src/libse/Forms/FixCommonErrors/FixMusicNotation.cs @@ -16,6 +16,16 @@ public void Fix(Subtitle subtitle, IFixCallbacks callbacks) string fixAction = Language.FixMusicNotation; int fixCount = 0; string[] musicSymbols = Configuration.Settings.Tools.MusicSymbolReplace.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); + + // Trimmed and upper-cased once per run instead of once per symbol per line. + var trimmedSymbols = new string[musicSymbols.Length]; + var upperSymbols = new string[musicSymbols.Length]; + for (var k = 0; k < musicSymbols.Length; k++) + { + trimmedSymbols[k] = musicSymbols[k].Trim(); + upperSymbols[k] = trimmedSymbols[k].ToUpperInvariant(); + } + for (int i = 0; i < subtitle.Paragraphs.Count; i++) { Paragraph p = subtitle.Paragraphs[i]; @@ -24,9 +34,9 @@ public void Fix(Subtitle subtitle, IFixCallbacks callbacks) var oldText = p.Text; var newText = oldText; bool containsFontTag = oldText.Contains(" 0 && text[0] < 0x80 && char.IsLetterOrDigit(text[0])) + { + continue; + } + if ((text.StartsWith("..") || text.StartsWith("…")) && callbacks.AllowFix(p, fixAction)) { var oldText = p.Text; diff --git a/src/libse/Forms/FixCommonErrors/NormalizeStrings.cs b/src/libse/Forms/FixCommonErrors/NormalizeStrings.cs index 0ea2058f9fa..527516fa7b3 100644 --- a/src/libse/Forms/FixCommonErrors/NormalizeStrings.cs +++ b/src/libse/Forms/FixCommonErrors/NormalizeStrings.cs @@ -11,6 +11,31 @@ public static class Language public static string NormalizeStrings { get; set; } = "Normalize strings"; } + private static readonly string[] LowQuoteLanguages = { "de", "cs", "sk", "pl", "sl", "hr", "sr", "hu", "lt", "et", "ro", "is", "bg", "ka", "sq" }; + private static readonly string[] CyrillicLanguages = { "ru", "uk", "bg", "sr", "mk", "be", "kk", "ky", "mn", "tg" }; + + // Every character any replacement below looks for, whatever the language. + private static readonly char[] AllReplacedChars = + { + '\u00a0', '\u200B', '\uFEFF', '\u02F8', '\uFF1A', '\uFE13', '\u2043', '\u2010', '\u2011', '\u2012', '\u2013', '\u2015', + '\u2026', '\u2032', '\u2033', '\u2018', '\u2019', '\u201A', '\u201B', '\u201C', '\u201D', '\u201E', '\u201F', '\u0435', + '\u03a4', '\u03a5', '\u03b3', '\u03a7', '\u03ba', '\u03bd', '\u03c1', '\u03c5', '\u039c', '\u039a', '\u039d', '\u039f', + '\u03a1', '\u0395', '\u0396', '\u0397', '\u0384', '\u0392', '\u0391', '\u03bf', + }; + + private static bool IsAscii(string s) + { + foreach (var ch in s) + { + if (ch >= 0x80) + { + return false; + } + } + + return true; + } + public void Fix(Subtitle subtitle, IFixCallbacks callbacks) { var twoLetterLanguageCode = callbacks.Language; @@ -18,87 +43,21 @@ public void Fix(Subtitle subtitle, IFixCallbacks callbacks) foreach (var p in subtitle.Paragraphs) { var oldText = p.Text; - var text = p.Text - .Normalize() - - .Replace('\u00a0', ' ') // replace non-break-space (160 decimal) ascii char with normal space - .Replace("\u200B", string.Empty) // Zero Width Space - .Replace("\uFEFF", string.Empty) // Zero Width No-Break Space - - .Replace('\u02F8', ':') // ˸ Modifier Letter Raised Colon (\u02F8) - .Replace('\uFF1A', ':') // : Fullwidth Colon (\uFF1A) - .Replace('\uFE13', ':') // ︓ Presentation Form for Vertical Colon (\uFE13) - - .Replace('\u2043', '-') // ⁃ Hyphen bullet (\u2043) - .Replace('\u2010', '-') // ‐ Hyphen (\u2010) - .Replace('\u2011', '-') // ‑ Non-breaking hyphen (\u2011) - .Replace('\u2012', '-') // ‒ Figure dash (\u2012) - .Replace('\u2013', '-') // – En dash (\u2013) - //.Replace('\u2014', '-') // — Em dash (\u2014) - we keep em dash - .Replace('\u2015', '\u2014') // ― Horizontal bar (\u2015) - - // Machine translation hands back typographic punctuation the source never - // had - DeepL alone answers plain ASCII input with curly quotes, en dashes - // and a one-character ellipsis (issue #14092). - .Replace("\u2026", "...") // … Horizontal ellipsis (\u2026) - .Replace('\u2032', '\'') // ′ Prime (\u2032) - .Replace('\u2033', '"') // ″ Double prime (\u2033) - ; - // Quotation marks are the language-specific part of this: „…“ and ‚…‘ are how German - // and several of its neighbours quote, so replacing them there would take the - // language's own punctuation away rather than normalize anything. - var lowQuoteLanguages = new[] { "de", "cs", "sk", "pl", "sl", "hr", "sr", "hu", "lt", "et", "ro", "is", "bg", "ka", "sq" }; - if (!lowQuoteLanguages.Contains(twoLetterLanguageCode)) + // Every character replaced below is outside ASCII and Normalize() leaves ASCII + // alone, so a plain ASCII line (most lines in most files) cannot change - skip + // the ~45 scans and the Normalize call. + if (IsAscii(oldText)) { - text = text - .Replace('\u2018', '\'') // ‘ Left single quotation mark (\u2018) - .Replace('\u2019', '\'') // ’ Right single quotation mark (\u2019) - the typographic apostrophe - .Replace('\u201A', '\'') // ‚ Single low-9 quotation mark (\u201A) - .Replace('\u201B', '\'') // ‛ Single high-reversed-9 quotation mark (\u201B) - .Replace('\u201C', '"') // “ Left double quotation mark (\u201C) - .Replace('\u201D', '"') // ” Right double quotation mark (\u201D) - .Replace('\u201E', '"') // „ Double low-9 quotation mark (\u201E) - .Replace('\u201F', '"') // ‟ Double high-reversed-9 quotation mark (\u201F) - ; + continue; } - // Replacing Cyrillic "е" with Latin "e" is an OCR heuristic for Latin-script - // languages. Only Russian was exempt, so Bulgarian, Ukrainian, Serbian, Macedonian - // and the rest had a Latin "e" spliced into Cyrillic words - breaking spell check, - // search and sorting. - var cyrillicLanguages = new[] { "ru", "uk", "bg", "sr", "mk", "be", "kk", "ky", "mn", "tg" }; - if (!cyrillicLanguages.Contains(twoLetterLanguageCode)) - { - text = text - .Replace('\u0435', 'e') // Cyrillic Small Letter Ie: "е" - ; - } + var text = oldText.Normalize(); - if (twoLetterLanguageCode != "el") + // Only a line holding one of the replaced characters needs the replace chain. + if (text.IndexOfAny(AllReplacedChars) >= 0) { - text = text - .Replace("\u03a4", "T") // Greek Capital Letter Tau - .Replace("\u03a5", "Y") // Greek Capital Letter Upsilon - .Replace("\u03b3", "Y") // Greek Small Letter Gamma - .Replace("\u03a7", "X") // Greek Capital Letter Chi - .Replace("\u03ba", "k") // Greek Small Letter Kappa - .Replace("\u03bd", "v") // Greek Small Letter Nu - .Replace("\u03c1", "p") // Greek Small Letter Rho - .Replace("\u03c5", "u") // Greek Small Letter Upsilon - .Replace("\u039c", "M") // Greek Capital Letter Mu - .Replace("\u039a", "K") // Greek Capital Letter Kappa - .Replace("\u039d", "N") // Greek Capital Letter Nu - .Replace("\u039f", "O") // Greek Capital Letter Omicron - .Replace("\u03a1", "P") // Greek Capital Letter Rho - .Replace("\u0395", "E") // Greek Capital Letter Epsilon - .Replace("\u0396", "Z") // Greek Capital Letter Zeta - .Replace("\u0397", "H") // Greek Capital Letter Eta - .Replace("\u0384", "'") // Greek Tonos - .Replace("\u0392", "B") // Greek Capital Letter Beta - .Replace("\u0391", "A") // Greek Capital Letter Alpha - .Replace("\u03bf", "o") // Greek small letter Omicron - ; + text = ReplaceChars(text, twoLetterLanguageCode); } if (oldText != text && callbacks.AllowFix(p, Language.NormalizeStrings)) @@ -110,5 +69,89 @@ public void Fix(Subtitle subtitle, IFixCallbacks callbacks) } callbacks.UpdateFixStatus(noOfFixes, Language.NormalizeStrings); } + + private static string ReplaceChars(string text, string twoLetterLanguageCode) + { + text = text + .Replace('\u00a0', ' ') // replace non-break-space (160 decimal) ascii char with normal space + .Replace("\u200B", string.Empty) // Zero Width Space + .Replace("\uFEFF", string.Empty) // Zero Width No-Break Space + + .Replace('\u02F8', ':') // ˸ Modifier Letter Raised Colon (\u02F8) + .Replace('\uFF1A', ':') // : Fullwidth Colon (\uFF1A) + .Replace('\uFE13', ':') // ︓ Presentation Form for Vertical Colon (\uFE13) + + .Replace('\u2043', '-') // ⁃ Hyphen bullet (\u2043) + .Replace('\u2010', '-') // ‐ Hyphen (\u2010) + .Replace('\u2011', '-') // ‑ Non-breaking hyphen (\u2011) + .Replace('\u2012', '-') // ‒ Figure dash (\u2012) + .Replace('\u2013', '-') // – En dash (\u2013) + //.Replace('\u2014', '-') // — Em dash (\u2014) - we keep em dash + .Replace('\u2015', '\u2014') // ― Horizontal bar (\u2015) + + // Machine translation hands back typographic punctuation the source never + // had - DeepL alone answers plain ASCII input with curly quotes, en dashes + // and a one-character ellipsis (issue #14092). + .Replace("\u2026", "...") // … Horizontal ellipsis (\u2026) + .Replace('\u2032', '\'') // ′ Prime (\u2032) + .Replace('\u2033', '"') // ″ Double prime (\u2033) + ; + + // Quotation marks are the language-specific part of this: „…“ and ‚…‘ are how German + // and several of its neighbours quote, so replacing them there would take the + // language's own punctuation away rather than normalize anything. + if (!LowQuoteLanguages.Contains(twoLetterLanguageCode)) + { + text = text + .Replace('\u2018', '\'') // ‘ Left single quotation mark (\u2018) + .Replace('\u2019', '\'') // ’ Right single quotation mark (\u2019) - the typographic apostrophe + .Replace('\u201A', '\'') // ‚ Single low-9 quotation mark (\u201A) + .Replace('\u201B', '\'') // ‛ Single high-reversed-9 quotation mark (\u201B) + .Replace('\u201C', '"') // “ Left double quotation mark (\u201C) + .Replace('\u201D', '"') // ” Right double quotation mark (\u201D) + .Replace('\u201E', '"') // „ Double low-9 quotation mark (\u201E) + .Replace('\u201F', '"') // ‟ Double high-reversed-9 quotation mark (\u201F) + ; + } + + // Replacing Cyrillic "е" with Latin "e" is an OCR heuristic for Latin-script + // languages. Only Russian was exempt, so Bulgarian, Ukrainian, Serbian, Macedonian + // and the rest had a Latin "e" spliced into Cyrillic words - breaking spell check, + // search and sorting. + if (!CyrillicLanguages.Contains(twoLetterLanguageCode)) + { + text = text + .Replace('\u0435', 'e') // Cyrillic Small Letter Ie: "е" + ; + } + + if (twoLetterLanguageCode != "el") + { + text = text + .Replace("\u03a4", "T") // Greek Capital Letter Tau + .Replace("\u03a5", "Y") // Greek Capital Letter Upsilon + .Replace("\u03b3", "Y") // Greek Small Letter Gamma + .Replace("\u03a7", "X") // Greek Capital Letter Chi + .Replace("\u03ba", "k") // Greek Small Letter Kappa + .Replace("\u03bd", "v") // Greek Small Letter Nu + .Replace("\u03c1", "p") // Greek Small Letter Rho + .Replace("\u03c5", "u") // Greek Small Letter Upsilon + .Replace("\u039c", "M") // Greek Capital Letter Mu + .Replace("\u039a", "K") // Greek Capital Letter Kappa + .Replace("\u039d", "N") // Greek Capital Letter Nu + .Replace("\u039f", "O") // Greek Capital Letter Omicron + .Replace("\u03a1", "P") // Greek Capital Letter Rho + .Replace("\u0395", "E") // Greek Capital Letter Epsilon + .Replace("\u0396", "Z") // Greek Capital Letter Zeta + .Replace("\u0397", "H") // Greek Capital Letter Eta + .Replace("\u0384", "'") // Greek Tonos + .Replace("\u0392", "B") // Greek Capital Letter Beta + .Replace("\u0391", "A") // Greek Capital Letter Alpha + .Replace("\u03bf", "o") // Greek small letter Omicron + ; + } + + return text; + } } } diff --git a/src/ui/Features/Main/MainViewModel.cs b/src/ui/Features/Main/MainViewModel.cs index 27353fc9f22..eb74717dcc1 100644 --- a/src/ui/Features/Main/MainViewModel.cs +++ b/src/ui/Features/Main/MainViewModel.cs @@ -586,6 +586,21 @@ private static int IndexFromMap(Dictionary map, Subt return map.TryGetValue(row, out var index) ? index : -1; } + /// + /// rows.IndexOf as a lookup built in one pass: the column and ripple-delete commands + /// map every selected row to its position, which was O(rows * selected) with IndexOf. + /// + internal static Func IndexLookup(List rows) + { + var map = new Dictionary(rows.Count); + for (var i = 0; i < rows.Count; i++) + { + map.TryAdd(rows[i], i); // first position wins, like IndexOf + } + + return row => row != null && map.TryGetValue(row, out var index) ? index : -1; + } + private List GetSelectedSubtitlesInOrder(bool includeReferenceOnly) { var selected = SubtitleGrid.SelectedItems; @@ -7438,7 +7453,7 @@ private void ColumnInsertEmptyTextAndShiftCellsDown() // Insert empty cells at the original selection indices and shift down // Use delta to keep correct original target positions var indices = selectedItems - .Select(x => column.IndexOf(x)) + .Select(IndexLookup(column)) .Where(i => i >= 0 && i < total) .Distinct() .OrderBy(i => i) @@ -7699,7 +7714,7 @@ private void ColumnTextUp() var column = Subtitles.Where(p => !p.IsReferenceOnly).ToList(); var total = column.Count; var sel = selectedItems - .Select(x => column.IndexOf(x)) + .Select(IndexLookup(column)) .Where(i => i >= 0 && i < total) .Distinct() .OrderBy(i => i) @@ -7768,7 +7783,7 @@ private void ColumnTextDown() var column = Subtitles.Where(p => !p.IsReferenceOnly).ToList(); var total = column.Count; var sel = selectedItems - .Select(x => column.IndexOf(x)) + .Select(IndexLookup(column)) .Where(i => i >= 0 && i < total) .Distinct() .OrderBy(i => i) @@ -31047,7 +31062,7 @@ private async Task RippleDeleteSelectedItems() // the shift for the rest of the file. var workingRows = Subtitles.Where(p => !p.IsReferenceOnly).ToList(); var sortedIndices = selectedItems - .Select(item => workingRows.IndexOf(item)) + .Select(IndexLookup(workingRows)) .Where(i => i >= 0) .OrderBy(i => i) .ToList(); diff --git a/src/ui/Features/Tools/ApplyDurationLimits/ApplyDurationLimitsViewModel.cs b/src/ui/Features/Tools/ApplyDurationLimits/ApplyDurationLimitsViewModel.cs index 10184780d0e..072c06f826e 100644 --- a/src/ui/Features/Tools/ApplyDurationLimits/ApplyDurationLimitsViewModel.cs +++ b/src/ui/Features/Tools/ApplyDurationLimits/ApplyDurationLimitsViewModel.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using Avalonia.Controls; using Avalonia.Input; @@ -248,16 +248,27 @@ private TimeSpan CapAtShotChange(SubtitleLineViewModel item, TimeSpan newEndTime return newEndTime; } - // _shotChanges is sorted in Initialize, so the first hit is the earliest one. - foreach (var shotChangeSeconds in _shotChanges) + // _shotChanges is sorted in Initialize: binary search the first shot change after the + // current end (a scan from the start per line was O(lines x shot changes)). + var low = 0; + var high = _shotChanges.Count; + while (low < high) { - var shotChangeMs = shotChangeSeconds * 1000.0; - if (shotChangeMs >= newEndMs) + var mid = low + (high - low) / 2; + if (_shotChanges[mid] * 1000.0 > currentEndMs) { - break; + high = mid; } + else + { + low = mid + 1; + } + } - if (shotChangeMs > currentEndMs) + if (low < _shotChanges.Count) + { + var shotChangeMs = _shotChanges[low] * 1000.0; + if (shotChangeMs < newEndMs) { return TimeSpan.FromMilliseconds(shotChangeMs); } diff --git a/src/ui/Features/Tools/MergeSubtitlesWithSameText/MergeSameTextViewModel.cs b/src/ui/Features/Tools/MergeSubtitlesWithSameText/MergeSameTextViewModel.cs index 416c95e0f43..a59c1a6c4c7 100644 --- a/src/ui/Features/Tools/MergeSubtitlesWithSameText/MergeSameTextViewModel.cs +++ b/src/ui/Features/Tools/MergeSubtitlesWithSameText/MergeSameTextViewModel.cs @@ -1,4 +1,4 @@ -using Avalonia.Controls; +using Avalonia.Controls; using Avalonia.Input; using Avalonia.Threading; using CommunityToolkit.Mvvm.ComponentModel; @@ -125,6 +125,10 @@ private void UpdatePreview() AddRollUpMerges(maxMsBetween, removed); } + // Line numbers held by unchecked merge items. Every item added below starts checked, so + // the set stays valid for the whole pass (scanning all items per merge was quadratic). + var blockedNumbers = GetUncheckedLineNumbers(); + for (var i = 1; i < _subtitles.Count; i++) { if (removed.Contains(i - 1)) @@ -151,7 +155,7 @@ private void UpdatePreview() Number = nextS.Number, }; var incrementText = string.Empty; - if ((MergeLinesSameTextUtils.QualifiesForMerge(p, next, maxMsBetween) || fixIncrementing && MergeLinesSameTextUtils.QualifiesForMergeIncrement(p, next, maxMsBetween, out incrementText)) && IsFixAllowed(p)) + if ((MergeLinesSameTextUtils.QualifiesForMerge(p, next, maxMsBetween) || fixIncrementing && MergeLinesSameTextUtils.QualifiesForMergeIncrement(p, next, maxMsBetween, out incrementText)) && !blockedNumbers.Contains(p.Number)) { p.Text = next.Text; p.EndTime.TotalMilliseconds = next.EndTime.TotalMilliseconds; @@ -235,20 +239,18 @@ private void AddRollUpMerges(int maxMsBetween, HashSet removed) } } - private bool IsFixAllowed(Paragraph p) + private HashSet GetUncheckedLineNumbers() { + var numbers = new HashSet(); foreach (var mi in MergeItems.Where(p => !p.Apply)) { foreach (var line in mi.LinesToMerge) { - if (line.Number == p.Number) - { - return false; - } + numbers.Add(line.Number); } } - return true; + return numbers; } private void LoadSettings() diff --git a/src/ui/Features/Video/TextToSpeech/TextToSpeechViewModel.cs b/src/ui/Features/Video/TextToSpeech/TextToSpeechViewModel.cs index 727d1de1c8f..fa8aceb983d 100644 --- a/src/ui/Features/Video/TextToSpeech/TextToSpeechViewModel.cs +++ b/src/ui/Features/Video/TextToSpeech/TextToSpeechViewModel.cs @@ -4190,9 +4190,17 @@ private async Task TranscribePerLineCloneClipsAsync() var startMs = (long)Math.Round(paragraph.StartTime.TotalMilliseconds); if (!_originalByStartMs.TryGetValue(startMs, out var best)) { - best = _originalSubtitle.Paragraphs - .OrderBy(p => Math.Abs(p.StartTime.TotalMilliseconds - paragraph.StartTime.TotalMilliseconds)) - .First(); + // Closest start time, earliest line on a tie - one pass, not a sort per moved line. + var bestDistance = double.MaxValue; + foreach (var p in _originalSubtitle.Paragraphs) + { + var distance = Math.Abs(p.StartTime.TotalMilliseconds - paragraph.StartTime.TotalMilliseconds); + if (best == null || distance < bestDistance) + { + best = p; + bestDistance = distance; + } + } } if (Math.Abs(best.StartTime.TotalMilliseconds - paragraph.StartTime.TotalMilliseconds) > 500) diff --git a/tests/benchmarks/PerfHuntRound31Benchmarks.cs b/tests/benchmarks/PerfHuntRound31Benchmarks.cs new file mode 100644 index 00000000000..04e1795d496 --- /dev/null +++ b/tests/benchmarks/PerfHuntRound31Benchmarks.cs @@ -0,0 +1,396 @@ +using System.Globalization; +using System.Reflection; +using System.Text; +using BenchmarkDotNet.Attributes; +using Nikse.SubtitleEdit.Core.CDG; +using Nikse.SubtitleEdit.Core.Common; +using AribCaptionParser = Nikse.SubtitleEdit.Core.ContainerFormats.TransportStream.AribCaptionParser; +using Nikse.SubtitleEdit.Core.Forms; +using Nikse.SubtitleEdit.Core.Forms.FixCommonErrors; +using Nikse.SubtitleEdit.Core.Interfaces; +using Nikse.SubtitleEdit.Core.SubtitleFormats; +using Nikse.SubtitleEdit.Features.Main; + +namespace Nikse.SubtitleEdit.Benchmarks; + +// Round 31. Every benchmark calls the real code, so the class is run once against the commit +// before the change and once after; ControlAutoBreak and SubtitleCopyControl are drift controls. + +/// Fix common errors rules, JSON parsing, CD+G frames, ARIB roll-up merge. +[MemoryDiagnoser] +public class Round31Benchmarks +{ + private static readonly string[] Texts = + { + "It was the best of times {0}, it was the worst", + "I told you already {0}", + "- No, Mrs. smith {0}.\r\n- Then stay here and wait", + "...somewhere in Denmark {0},\r\na quiet evening", + "♪ Plain line {0} with bold ♪", + "Tom and \"Jerry\" {0} were here", + "[DOOR SLAMS] {0}", + "MAN: Are you coming with us, John {0}", + "Hvad så, går det godt {0}? Ja, det gør det", + "Somebody said hello {0}...", + }; + + private Subtitle _subtitle = null!; + private readonly StubFixCallbacks _callbacks = new(); + private string _json = null!; + private List _cdgPackets = null!; + private Action> _aribMerge = null!; + + [Params(5000)] + public int Lines { get; set; } + + [GlobalSetup] + public void Setup() + { + Configuration.DataDirectory = BenchmarkSubtitles.FindDataDirectory(); + _subtitle = new Subtitle(); + for (var i = 0; i < Lines; i++) + { + _subtitle.Paragraphs.Add(new Paragraph(string.Format(Texts[i % Texts.Length], i), i * 2500, i * 2500 + 2000)); + } + + _subtitle.Renumber(); + + // Word-level transcript JSON, as TranscriptiveJson / JsonType14 / UnknownFormatImporter read it. + var sb = new StringBuilder("{\"words\":["); + for (var i = 0; i < Lines * 8; i++) + { + if (i > 0) + { + sb.Append(','); + } + + sb.Append("{\"text\":\"word").Append(i).Append("\",\"start\":").Append((i * 0.25m).ToString(CultureInfo.InvariantCulture)).Append(",\"end\":") + .Append((i * 0.25m + 0.2m).ToString(CultureInfo.InvariantCulture)).Append(",\"speaker\":\"Speaker ").Append(i % 3).Append("\",\"confidence\":0.98,\"index\":").Append(i).Append('}'); + } + + sb.Append("]}"); + _json = sb.ToString(); + + _cdgPackets = BuildCdgPackets(2000); + + var method = typeof(AribCaptionParser).GetMethod("MergeContinuations", BindingFlags.NonPublic | BindingFlags.Static)!; + _aribMerge = (Action>)Delegate.CreateDelegate(typeof(Action>), method); + } + + private static List BuildCdgPackets(int count) + { + var packets = new List(count); + var random = new Random(31); + for (var i = 0; i < count; i++) + { + var data = new byte[24]; + data[0] = 9; // Graphic + if (i == 0) + { + data[1] = 30; // LoadColorTableLower + for (var c = 0; c < 8; c++) + { + data[4 + c * 2] = (byte)(c * 7 & 0x3F); + data[5 + c * 2] = (byte)(c * 5 & 0x3F); + } + } + else if (i == 1) + { + data[1] = 1; // MemoryPreset color 0 + } + else + { + data[1] = 6; // TileBlockNormal + data[4] = 0; + data[5] = (byte)(1 + i % 7); + data[6] = (byte)random.Next(18); + data[7] = (byte)random.Next(50); + for (var k = 8; k < 20; k++) + { + data[k] = (byte)random.Next(64); + } + } + + packets.Add(new Packet(data)); + } + + return packets; + } + + private Subtitle Copy() => new Subtitle(_subtitle); + + [Benchmark] + public int FceNormalizeStrings() + { + var s = Copy(); + new NormalizeStrings().Fix(s, _callbacks); + return s.Paragraphs.Count; + } + + [Benchmark] + public int FceFixMusicNotation() + { + var s = Copy(); + new FixMusicNotation().Fix(s, _callbacks); + return s.Paragraphs.Count; + } + + [Benchmark] + public int FceFixUnnecessaryLeadingDots() + { + var s = Copy(); + new FixUnnecessaryLeadingDots().Fix(s, _callbacks); + return s.Paragraphs.Count; + } + + /// What the three FCE rows above pay for their own copy of the subtitle. + [Benchmark] + public int SubtitleCopyControl() => Copy().Paragraphs.Count; + + [Benchmark] + public object JsonParse() => new JsonParser().Parse(_json); + + /// One CD+G frame grab every 20 packets, as CdgToImageList does. + [Benchmark] + public int CdgToBitmap() + { + var graphics = new CdgGraphics(_cdgPackets); + var total = 0; + for (var packet = 20; packet < _cdgPackets.Count; packet += 20) + { + using var bmp = graphics.ToBitmap(packet); + total += bmp?.Width ?? 0; + } + + return total; + } + + /// Roll-up ARIB captions: every word repaints the line, so ten statements collapse into one. + [Benchmark] + public int AribMergeContinuations() + { + var list = new List(Lines * 4); + for (var i = 0; i < Lines * 4; i++) + { + var chain = i / 10; + var step = i % 10; + var text = "Caption " + chain + new string('w', step + 1); + var start = i * 300.0; + list.Add(new Paragraph(text, start, start + 300)); + } + + _aribMerge(list); + return list.Count; + } + + /// Drift control - untouched by this round. + [Benchmark] + public int ControlAutoBreak() + { + var total = 0; + for (var i = 0; i < 3000; i++) + { + total += Utilities.AutoBreakLine(BenchmarkSubtitles.Sentences[i % BenchmarkSubtitles.Sentences.Length]).Length; + } + + return total; + } + + private sealed class StubFixCallbacks : IFixCallbacks + { + public bool AllowFix(Paragraph p, string action) => true; + public void AddFixToListView(Paragraph p, string action, string before, string after) { } + public void AddFixToListView(Paragraph p, string action, string before, string after, bool isChecked) { } + public void LogStatus(string sender, string message) { } + public void LogStatus(string sender, string message, bool isImportant) { } + public void UpdateFixStatus(int fixes, string message) { } + public bool IsName(string candidate) => false; + public HashSet GetAbbreviations() => new(); + public void AddToTotalErrors(int count) { } + public void AddToDeleteIndices(int index) { } + public SubtitleFormat Format { get; } = new SubRip(); + public Encoding Encoding { get; } = Encoding.UTF8; + public string Language => "en"; + } +} + +/// +/// UI view-model paths reached by reflection (both run against the commit before the change and +/// after): Apply duration limits preview with shot changes, and the TTS "spoken text in video" +/// lookup for a translation whose lines no longer share the original's start times. +/// +[MemoryDiagnoser] +public class Round31UiBenchmarks +{ + private Nikse.SubtitleEdit.Features.Tools.ApplyDurationLimits.ApplyDurationLimitsViewModel _durationLimits = null!; + private Action _buildPreview = null!; + private Subtitle _translation = null!; + private Func _spokenText = null!; + + [Params(5000)] + public int Lines { get; set; } + + [GlobalSetup] + public void Setup() + { + Configuration.DataDirectory = BenchmarkSubtitles.FindDataDirectory(); + var flags = BindingFlags.NonPublic | BindingFlags.Instance; + + // Every line is 300 ms (too short), one shot change every ~2.3 s. + var lines = new List(Lines); + var srt = new SubRip(); + for (var i = 0; i < Lines; i++) + { + lines.Add(new SubtitleLineViewModel(new Paragraph("Line " + i, i * 3000, i * 3000 + 300), srt)); + } + + var shotChanges = new List(); + for (var t = 1.1; t < Lines * 3.0; t += 2.3) + { + shotChanges.Add(t); + } + + var type = typeof(Nikse.SubtitleEdit.Features.Tools.ApplyDurationLimits.ApplyDurationLimitsViewModel); + _durationLimits = new Nikse.SubtitleEdit.Features.Tools.ApplyDurationLimits.ApplyDurationLimitsViewModel(); + type.GetField("_allSubtitles", flags)!.SetValue(_durationLimits, lines); + type.GetField("_shotChanges", flags)!.SetValue(_durationLimits, shotChanges); + _durationLimits.DoNotGoPastShotChange = true; + _durationLimits.FixMinDurationMs = true; + _durationLimits.FixMaxDurationMs = false; + _durationLimits.MinDurationMsOrFrames = 1200; + _durationLimits.MaxDurationMsOrFrames = 8000; + _buildPreview = (Action)Delegate.CreateDelegate(typeof(Action), _durationLimits, type.GetMethod("BuildPreview", flags)!); + + // Original and a translation shifted by 40 ms, so no start time matches exactly. + var original = new Subtitle(); + _translation = new Subtitle(); + for (var i = 0; i < Lines; i++) + { + original.Paragraphs.Add(new Paragraph("Original line " + i, i * 2500, i * 2500 + 2000)); + _translation.Paragraphs.Add(new Paragraph("Translated line " + i, i * 2500 + 40, i * 2500 + 2040)); + } + + var ttsType = typeof(Nikse.SubtitleEdit.Features.Video.TextToSpeech.TextToSpeechViewModel); + var tts = System.Runtime.CompilerServices.RuntimeHelpers.GetUninitializedObject(ttsType); + ttsType.GetField("_originalSubtitle", flags)!.SetValue(tts, original); + _spokenText = (Func)Delegate.CreateDelegate(typeof(Func), tts, ttsType.GetMethod("GetSpokenTextInVideo", flags)!); + } + + [Benchmark] + public int ApplyDurationLimitsPreview() + { + _buildPreview(); + return _durationLimits.AllSubtitlesFixed.Count; + } + + /// Every 5th translated line looked up, as a dub's per-line clone pass does. + [Benchmark] + public int TtsSpokenTextInVideo() + { + var total = 0; + for (var i = 0; i < _translation.Paragraphs.Count; i += 5) + { + total += _spokenText(_translation.Paragraphs[i])?.Length ?? 0; + } + + return total; + } +} + +/// +/// Ripple delete and the column shift/rotate commands map every selected row to its position: +/// the old column.IndexOf per row against MainViewModel.IndexLookup (reached by +/// reflection so the class still compiles against the commit before the change). +/// +[MemoryDiagnoser] +public class Round31RowIndexBenchmarks +{ + private List _column = null!; + private List _selected = null!; + private Func, Func>? _indexLookup; + + [Params(1000, 10000)] + public int Selected { get; set; } + + [GlobalSetup] + public void Setup() + { + var srt = new SubRip(); + _column = new List(10000); + for (var i = 0; i < 10000; i++) + { + _column.Add(new SubtitleLineViewModel(new Paragraph("Line " + i, i * 2000, i * 2000 + 1500), srt)); + } + + _selected = _column.Skip(10000 - Selected).ToList(); + var method = typeof(MainViewModel).GetMethod("IndexLookup", BindingFlags.NonPublic | BindingFlags.Static); + if (method != null) + { + _indexLookup = (Func, Func>)Delegate.CreateDelegate( + typeof(Func, Func>), method); + } + } + + [Benchmark(Baseline = true)] + public int IndexOfPerRow() => _selected + .Select(x => _column.IndexOf(x)) + .Where(i => i >= 0 && i < _column.Count) + .Distinct() + .OrderBy(i => i) + .ToList().Count; + + [Benchmark] + public int IndexLookup() => _selected + .Select(_indexLookup!(_column)) + .Where(i => i >= 0 && i < _column.Count) + .Distinct() + .OrderBy(i => i) + .ToList().Count; +} + +/// +/// Merge lines with same text preview (run against the commit before the change and after): +/// 6000 lines in groups of three identical lines, so 2000 merge items. +/// +[MemoryDiagnoser] +public class Round31MergeSameTextBenchmarks +{ + private Action _updatePreview = null!; + private object _vm = null!; + private System.Collections.ICollection _mergeItems = null!; + + [Params(6000)] + public int Lines { get; set; } + + [GlobalSetup] + public void Setup() + { + Configuration.DataDirectory = BenchmarkSubtitles.FindDataDirectory(); + var flags = BindingFlags.NonPublic | BindingFlags.Instance; + var type = typeof(Nikse.SubtitleEdit.Features.Tools.MergeSubtitlesWithSameText.MergeSameTextViewModel); + + var lines = new List(Lines); + var srt = new SubRip(); + for (var i = 0; i < Lines; i++) + { + var line = new SubtitleLineViewModel(new Paragraph("Same text " + i / 3, i * 1000, i * 1000 + 900), srt) { Number = i + 1 }; + lines.Add(line); + } + + // The constructor builds an Avalonia TableView; the preview only needs these fields. + _vm = System.Runtime.CompilerServices.RuntimeHelpers.GetUninitializedObject(type); + _mergeItems = new System.Collections.ObjectModel.ObservableCollection(); + type.GetField("_mergeItems", flags)!.SetValue(_vm, _mergeItems); + type.GetField("_mergeSubtitles", flags)!.SetValue(_vm, new System.Collections.ObjectModel.ObservableCollection()); + type.GetField("_maxMsOrFramesBetweenLines", flags)!.SetValue(_vm, 250); + type.GetField("_subtitles", flags)!.SetValue(_vm, lines); + _updatePreview = (Action)Delegate.CreateDelegate(typeof(Action), _vm, type.GetMethod("UpdatePreview", flags)!); + } + + [Benchmark] + public int MergeSameTextPreview() + { + _updatePreview(); + return _mergeItems.Count; + } +} From 67edc46c19f633fcb062214991ddd2bcb6427af7 Mon Sep 17 00:00:00 2001 From: niksedk Date: Sat, 10 Oct 2026 07:47:21 +0200 Subject: [PATCH 004/306] ASSA draw: shape tool and shape library - Shape tool (S) in the tool strip opens the shape library palette: speech bubbles (8), arrows, basic shapes and symbols - 31 built-in shapes written as SVG path data - plus "My shapes" - Drag on the canvas to place the picked shape in that rectangle (dashed preview; Shift keeps its proportions), or click for a default size; it lands on a new layer in the current color and is selected with the select tool, as one undo step - Shape menu: Add to shape library... saves the selection with its colors per layer (Settings, Assa.DrawShapeLibrary); right-click a saved shape in the palette to remove it - ASSA drawing parser moved to DrawShape.ParseAssa (also used for saved shapes) - SVG/library conversion turns each arc segment (Skia conic) into one cubic instead of four, e.g. a rounded speech bubble went from 60 to 20 points Co-Authored-By: Claude Opus 5.5 --- .../Features/Assa/AssaDraw/AssaDrawCanvas.cs | 119 +++++++ .../Assa/AssaDraw/AssaDrawViewModel.cs | 210 +++++++----- .../Features/Assa/AssaDraw/AssaDrawWindow.cs | 131 ++++++++ src/ui/Features/Assa/AssaDraw/DrawShape.cs | 92 ++++++ src/ui/Features/Assa/AssaDraw/DrawingTool.cs | 3 + src/ui/Features/Assa/AssaDraw/ShapeLibrary.cs | 302 ++++++++++++++++++ src/ui/Features/Assa/AssaDraw/SvgImporter.cs | 64 +++- .../Config/Language/Assa/LanguageAssa.cs | 82 +++++ src/ui/Logic/Config/SeAssa.cs | 6 + src/ui/Logic/Config/SeAssaDrawShape.cs | 22 ++ .../Assa/AssaDrawShapeLibraryTests.cs | 162 ++++++++++ 11 files changed, 1108 insertions(+), 85 deletions(-) create mode 100644 src/ui/Features/Assa/AssaDraw/ShapeLibrary.cs create mode 100644 src/ui/Logic/Config/SeAssaDrawShape.cs create mode 100644 tests/UI/Features/Assa/AssaDrawShapeLibraryTests.cs diff --git a/src/ui/Features/Assa/AssaDraw/AssaDrawCanvas.cs b/src/ui/Features/Assa/AssaDraw/AssaDrawCanvas.cs index 2530ab7e724..d24b10b2000 100644 --- a/src/ui/Features/Assa/AssaDraw/AssaDrawCanvas.cs +++ b/src/ui/Features/Assa/AssaDraw/AssaDrawCanvas.cs @@ -20,6 +20,9 @@ public class AssaDrawCanvas : Control private Point? _lastMousePosition; private bool _isPanning; private List? _dragShapes; + private (float X, float Y)? _insertStart; + private (float X, float Y) _insertEnd; + private bool _insertConstrain; private DrawShape? _pressedShape; private bool _dragShapeMoved; private bool _pointDragStarted; @@ -259,6 +262,16 @@ public float ZoomFactor /// public event EventHandler? EditStarting; + /// + /// Shape tool: place the library shape in this rectangle (canvas coordinates). + /// + public event EventHandler? InsertRequested; + + /// + /// The library shape the shape tool places (previewed while dragging). + /// + public ShapeLibraryItem? InsertShape { get; set; } + static AssaDrawCanvas() { AffectsRender( @@ -484,6 +497,19 @@ public override void Render(DrawingContext context) } } + // Shape tool: outline of the library shape in the dragged rectangle + if (_insertStart != null && InsertShape != null && GetInsertRect() is { IsClick: false } insert) + { + var previewPen = GetPen(AccentColor, 1.5, lineJoin: PenLineJoin.Round, dashed: true); + foreach (var shape in InsertShape.CreateShapes(insert.X, insert.Y, insert.Width, insert.Height, Colors.White, 0)) + { + if (shape.Points.Count > 1) + { + context.DrawGeometry(GetBrush(Color.FromArgb(40, AccentColor.R, AccentColor.G, AccentColor.B)), previewPen, BuildGeometry(shape, true)); + } + } + } + // Highlight active point if (ActivePoint != null) { @@ -935,6 +961,16 @@ protected override void OnPointerPressed(PointerPressedEventArgs e) return; } + // Shape tool: drag out the rectangle the library shape goes into + if (CurrentTool == DrawingTool.Shape && properties.IsLeftButtonPressed && InsertShape != null) + { + _insertStart = (x, y); + _insertEnd = (x, y); + _insertConstrain = e.KeyModifiers.HasFlag(KeyModifiers.Shift); + e.Handled = true; + return; + } + // Scale/rotate handles of the selection (select tool) if (properties.IsLeftButtonPressed) { @@ -1021,6 +1057,15 @@ protected override void OnPointerMoved(PointerEventArgs e) return; } + if (_insertStart != null) + { + _insertEnd = (x, y); + _insertConstrain = e.KeyModifiers.HasFlag(KeyModifiers.Shift); + InvalidateVisual(); + CanvasMouseMoved?.Invoke(this, new CanvasMouseEventArgs(x, y)); + return; + } + if (_transformShapes != null && e.GetCurrentPoint(this).Properties.IsLeftButtonPressed) { if (!_transformStarted) @@ -1115,6 +1160,19 @@ protected override void OnPointerReleased(PointerReleasedEventArgs e) _lastMousePosition = null; _pointDragStarted = false; + if (_insertStart != null) + { + var rect = GetInsertRect(); + _insertStart = null; + InvalidateVisual(); + if (rect != null) + { + InsertRequested?.Invoke(this, rect); + } + + return; + } + var dragShapes = _dragShapes; var moved = _dragShapeMoved; _dragShapes = null; @@ -1146,6 +1204,45 @@ protected override void OnPointerReleased(PointerReleasedEventArgs e) InvalidateVisual(); } + /// + /// The rectangle the shape tool drag describes; a click (tiny drag) means "default size here". + /// Dragging past the start point flips the rectangle, Shift keeps the shape's proportions. + /// + private CanvasInsertEventArgs? GetInsertRect() + { + if (_insertStart is not { } start || InsertShape == null) + { + return null; + } + + var width = _insertEnd.X - start.X; + var height = _insertEnd.Y - start.Y; + if (Math.Abs(width) * _zoomFactor < 4 && Math.Abs(height) * _zoomFactor < 4) + { + return new CanvasInsertEventArgs(start.X, start.Y, 0, 0, isClick: true); + } + + if (_insertConstrain) + { + var aspect = InsertShape.AspectRatio; + if (Math.Abs(width) * aspect > Math.Abs(height)) + { + height = Math.Sign(height == 0 ? 1 : height) * Math.Abs(width) * aspect; + } + else + { + width = Math.Sign(width == 0 ? 1 : width) * Math.Abs(height) / aspect; + } + } + + return new CanvasInsertEventArgs( + width < 0 ? start.X + width : start.X, + height < 0 ? start.Y + height : start.Y, + Math.Max(1, Math.Abs(width)), + Math.Max(1, Math.Abs(height)), + isClick: false); + } + private DrawShape? FindShape(DrawCoordinate point) { return point.DrawShape ?? Shapes.FirstOrDefault(s => s.Points.Contains(point)); @@ -1234,3 +1331,25 @@ public CanvasContextEventArgs(Point position, float x, float y, DrawCoordinate? Shape = shape; } } + +public class CanvasInsertEventArgs : EventArgs +{ + public float X { get; } + public float Y { get; } + public float Width { get; } + public float Height { get; } + + /// + /// A click without dragging: X/Y is where to center the shape at its default size. + /// + public bool IsClick { get; } + + public CanvasInsertEventArgs(float x, float y, float width, float height, bool isClick) + { + X = x; + Y = y; + Width = width; + Height = height; + IsClick = isClick; + } +} diff --git a/src/ui/Features/Assa/AssaDraw/AssaDrawViewModel.cs b/src/ui/Features/Assa/AssaDraw/AssaDrawViewModel.cs index 08f88bb083f..e8319ec15d3 100644 --- a/src/ui/Features/Assa/AssaDraw/AssaDrawViewModel.cs +++ b/src/ui/Features/Assa/AssaDraw/AssaDrawViewModel.cs @@ -11,6 +11,7 @@ using Nikse.SubtitleEdit.Features.Main; using Nikse.SubtitleEdit.Features.Shared; using Nikse.SubtitleEdit.Features.Shared.PickLayer; +using Nikse.SubtitleEdit.Features.Shared.PromptTextBox; using Nikse.SubtitleEdit.Features.Video.GoToVideoPosition; using Nikse.SubtitleEdit.Logic; using Nikse.SubtitleEdit.Logic.Config; @@ -62,6 +63,7 @@ public partial class AssaDrawViewModel : ObservableObject [ObservableProperty] private ObservableCollection _shapeTreeItems = []; [ObservableProperty] private ShapeTreeItem? _selectedTreeItem; [ObservableProperty] private List _selectedShapes = []; + [ObservableProperty] private ShapeLibraryItem? _currentLibraryShape = ShapeLibrary.BuiltIn[0]; [ObservableProperty] private float _shapeX; [ObservableProperty] private float _shapeY; [ObservableProperty] private float _shapeWidth; @@ -217,6 +219,132 @@ public void SetCanvas(AssaDrawCanvas canvas) Canvas.ShapeClicked += OnShapeClicked; Canvas.ShapeMoved += OnShapeMoved; Canvas.EditStarting += (_, _) => SaveUndo(); + Canvas.InsertRequested += OnInsertRequested; + Canvas.InsertShape = CurrentLibraryShape; + } + + partial void OnCurrentLibraryShapeChanged(ShapeLibraryItem? value) + { + if (Canvas != null) + { + Canvas.InsertShape = value; + } + } + + [RelayCommand] + private void ShapeTool() => SetTool(DrawingTool.Shape); + + /// + /// Picks the shape the shape tool places (and switches to the shape tool). + /// + [RelayCommand] + private void PickLibraryShape(ShapeLibraryItem? item) + { + if (item == null) + { + return; + } + + CurrentLibraryShape = item; + SetTool(DrawingTool.Shape); + } + + private void OnInsertRequested(object? sender, CanvasInsertEventArgs e) => InsertLibraryShape(e); + + /// + /// Places the current library shape on a new layer above the drawing, in the current color, + /// then selects it with the select tool so it can be moved and resized right away. + /// + public void InsertLibraryShape(CanvasInsertEventArgs e) + { + var item = CurrentLibraryShape; + if (item == null) + { + return; + } + + var x = e.X; + var y = e.Y; + var width = e.Width; + var height = e.Height; + if (e.IsClick) + { + // Default size: a fifth of the frame width (or a third of its height for tall shapes) + width = CanvasWidth / 5f; + height = width * item.AspectRatio; + if (height > CanvasHeight / 3f) + { + height = CanvasHeight / 3f; + width = height / item.AspectRatio; + } + + x -= width / 2f; + y -= height / 2f; + } + + CancelDrawing(); + SaveUndo(); + var firstLayer = Shapes.Count == 0 ? 0 : Shapes.Max(s => s.Layer) + 1; + var shapes = item.CreateShapes(x, y, width, height, LayerColor, firstLayer); + Shapes.AddRange(shapes); + SetTool(DrawingTool.Select); + RefreshTreeView(); + if (shapes.Count == 1) + { + SelectShape(shapes[0]); + } + else + { + SelectedShapes = shapes; + UpdateSelectionInfo(); + } + + Canvas?.InvalidateVisual(); + } + + [RelayCommand] + private async Task AddToLibrary() + { + var shapes = SelectedShapes.Count > 1 ? SelectedShapes.ToList() : TargetShape is { } target ? [target] : []; + if (shapes.Count == 0 || Window == null) + { + return; + } + + var vm = await _windowService.ShowDialogAsync(Window, vm => + vm.Initialize(Se.Language.Assa.DrawShapeName, string.Empty, 300, 30, returnSubmits: true)); + if (!vm.OkPressed || string.IsNullOrWhiteSpace(vm.Text)) + { + return; + } + + AddToLibrary(vm.Text.Trim(), shapes); + } + + /// + /// Saves shapes to "My shapes" and makes the new item the shape tool's current shape. + /// + public ShapeLibraryItem AddToLibrary(string name, List shapes) + { + var saved = ShapeLibrary.Save(name, shapes); + var item = ShapeLibrary.GetUserShapes().First(i => i.UserShape == saved); + CurrentLibraryShape = item; + return item; + } + + [RelayCommand] + private void RemoveFromLibrary(ShapeLibraryItem? item) + { + if (item?.UserShape == null) + { + return; + } + + ShapeLibrary.Remove(item.UserShape); + if (CurrentLibraryShape == item || CurrentLibraryShape?.UserShape == item.UserShape) + { + CurrentLibraryShape = ShapeLibrary.BuiltIn[0]; + } } private void OnShapeClicked(object? sender, DrawShape? shape) @@ -2016,84 +2144,7 @@ private void RefreshTreeView() private void ImportAssaDrawingFromText(string text, int layer, Color color, bool isEraser) { - text = _regexStart.Replace(text, string.Empty); - text = _regexEnd.Replace(text, string.Empty); - var arr = text.Split((char[]?)null, StringSplitOptions.RemoveEmptyEntries); - - var i = 0; - var bezierCount = 0; - var state = DrawCoordinateType.None; - DrawCoordinate? moveCoordinate = null; - DrawShape? drawShape = null; - - while (i < arr.Length) - { - var v = arr[i]; - - if (v == "m" && i < arr.Length - 2 && - float.TryParse(arr[i + 1], NumberStyles.Float, CultureInfo.InvariantCulture, out var mX) && - float.TryParse(arr[i + 2], NumberStyles.Float, CultureInfo.InvariantCulture, out var mY)) - { - bezierCount = 0; - moveCoordinate = new DrawCoordinate(null, DrawCoordinateType.Move, mX, mY, DrawSettings.PointColor); - state = DrawCoordinateType.Move; - i += 2; - } - else if (v == "l") - { - state = DrawCoordinateType.Line; - bezierCount = 0; - if (moveCoordinate != null) - { - drawShape = new DrawShape { Layer = layer, ForeColor = color, IsEraser = isEraser }; - drawShape.AddPoint(DrawCoordinateType.Line, moveCoordinate.X, moveCoordinate.Y, DrawSettings.PointColor); - moveCoordinate = null; - Shapes.Add(drawShape); - } - } - else if (v == "b") - { - state = DrawCoordinateType.BezierCurve; - if (moveCoordinate != null) - { - drawShape = new DrawShape { Layer = layer, ForeColor = color, IsEraser = isEraser }; - drawShape.AddPoint(DrawCoordinateType.BezierCurve, moveCoordinate.X, moveCoordinate.Y, DrawSettings.PointColor); - moveCoordinate = null; - Shapes.Add(drawShape); - } - bezierCount = 1; - } - else if (state == DrawCoordinateType.Line && drawShape != null && i < arr.Length - 1 && - float.TryParse(arr[i], NumberStyles.Float, CultureInfo.InvariantCulture, out var lX) && - float.TryParse(arr[i + 1], NumberStyles.Float, CultureInfo.InvariantCulture, out var lY)) - { - drawShape.AddPoint(DrawCoordinateType.Line, lX, lY, DrawSettings.PointColor); - i++; - } - else if (state == DrawCoordinateType.BezierCurve && drawShape != null && i < arr.Length - 1 && - float.TryParse(arr[i], NumberStyles.Float, CultureInfo.InvariantCulture, out var bX) && - float.TryParse(arr[i + 1], NumberStyles.Float, CultureInfo.InvariantCulture, out var bY)) - { - bezierCount++; - if (bezierCount > 3) - { - bezierCount = 1; - } - - var pointType = bezierCount switch - { - 2 => DrawCoordinateType.BezierCurveSupport1, - 3 => DrawCoordinateType.BezierCurveSupport2, - _ => DrawCoordinateType.BezierCurve - }; - - var pointColor = bezierCount is 2 or 3 ? DrawSettings.PointHelperColor : DrawSettings.PointColor; - drawShape.AddPoint(pointType, bX, bY, pointColor); - i++; - } - - i++; - } + Shapes.AddRange(DrawShape.ParseAssa(text, layer, color, isEraser)); } private static bool TryGetPlayRes(string? header, out int width, out int height) @@ -2290,7 +2341,7 @@ internal async void OnKeyDown(KeyEventArgs e) break; } } - else if (e.KeyModifiers == KeyModifiers.None && !IsTextInput(e) && e.Key is Key.V or Key.L or Key.B or Key.R or Key.C) + else if (e.KeyModifiers == KeyModifiers.None && !IsTextInput(e) && e.Key is Key.V or Key.L or Key.B or Key.R or Key.C or Key.S) { // Single-key tools like other drawing programs (not while typing in a number box) SetTool(e.Key switch @@ -2299,6 +2350,7 @@ internal async void OnKeyDown(KeyEventArgs e) Key.L => DrawingTool.Line, Key.B => DrawingTool.Bezier, Key.R => DrawingTool.Rectangle, + Key.S => DrawingTool.Shape, _ => DrawingTool.Circle, }); e.Handled = true; diff --git a/src/ui/Features/Assa/AssaDraw/AssaDrawWindow.cs b/src/ui/Features/Assa/AssaDraw/AssaDrawWindow.cs index d58af995df2..75c6ab692a0 100644 --- a/src/ui/Features/Assa/AssaDraw/AssaDrawWindow.cs +++ b/src/ui/Features/Assa/AssaDraw/AssaDrawWindow.cs @@ -368,6 +368,9 @@ private void AddShapeItems(List items, DrawShape shape) shape.Hidden ? "fa-solid fa-eye" : "fa-solid fa-eye-slash", _vm.ToggleShapeVisibilityCommand)); + AddSeparator(items); + items.Add(MakeMenuItem(Se.Language.Assa.DrawAddToLibrary, "fa-solid fa-shapes", _vm.AddToLibraryCommand)); + AddSeparator(items); items.Add(MakeMenuItem(WithoutShortcut(Se.Language.Assa.DrawDeleteShape), "fa-solid fa-trash", _vm.DeleteShapeCommand, new KeyGesture(Key.Delete))); } @@ -720,6 +723,25 @@ void AddTool(string icon, string tooltip, System.Windows.Input.ICommand command, AddTool("fa-solid fa-bezier-curve", Se.Language.Assa.DrawBezierTool, vm.BezierToolCommand, DrawingTool.Bezier, "B"); AddTool("fa-regular fa-square", Se.Language.Assa.DrawRectangleTool, vm.RectangleToolCommand, DrawingTool.Rectangle, "R"); AddTool("fa-regular fa-circle", Se.Language.Assa.DrawCircleTool, vm.CircleToolCommand, DrawingTool.Circle, "C"); + + // Shape tool: picks a shape from the library palette that opens next to the strip + var shapeButton = CreateToolButton("fa-solid fa-shapes", Se.Language.Assa.DrawShapeTool + " (S)", null, 38, "S"); + BindHighlight(shapeButton, vm, () => vm.CurrentTool == DrawingTool.Shape, nameof(vm.CurrentTool)); + var palette = new Popup + { + PlacementTarget = shapeButton, + Placement = PlacementMode.RightEdgeAlignedTop, + HorizontalOffset = 8, + IsLightDismissEnabled = true, + }; + shapeButton.Click += (_, _) => + { + vm.ShapeToolCommand.Execute(null); + palette.Child = MakeShapePalette(vm, () => palette.IsOpen = false); + palette.IsOpen = true; + }; + panel.Children.Add(shapeButton); + panel.Children.Add(palette); panel.Children.Add(MakeStripSeparator()); panel.Children.Add(CreateToolButton("fa-solid fa-magnifying-glass-plus", Se.Language.Assa.DrawZoomIn, vm.ZoomInCommand, 38)); panel.Children.Add(CreateToolButton("fa-solid fa-magnifying-glass-minus", Se.Language.Assa.DrawZoomOut, vm.ZoomOutCommand, 38)); @@ -755,6 +777,115 @@ void AddTool(string icon, string tooltip, System.Windows.Input.ICommand command, }; } + /// + /// The shape library: built-in categories, then "My shapes". Clicking a shape picks it for the + /// shape tool; saved shapes have a menu to remove them. + /// + private static Border MakeShapePalette(AssaDrawViewModel vm, Action close) + { + const int columns = 6; + const double cell = 46; + var content = new StackPanel { Spacing = 6 }; + content.Children.Add(MakeSectionHeader(Se.Language.Assa.DrawShapeLibrary)); + + void AddCategory(string title, IEnumerable items) + { + var list = items.ToList(); + if (list.Count == 0) + { + return; + } + + content.Children.Add(new TextBlock + { + Text = title, + FontSize = 12, + Foreground = DimTextBrush, + Margin = new Thickness(2, 6, 0, 0), + }); + + var wrap = new WrapPanel { Width = columns * (cell + 4) }; + foreach (var item in list) + { + var path = new Avalonia.Controls.Shapes.Path + { + Data = item.ToGeometry(), + Fill = item.UserShape != null ? null : new SolidColorBrush(Color.FromRgb(214, 218, 226)), + Stretch = Stretch.Uniform, + Margin = new Thickness(9), + }; + if (item.UserShape != null) + { + // Saved shapes show their own (first) color + var color = item.Template.FirstOrDefault(s => !s.IsEraser)?.ForeColor ?? Colors.White; + path.Fill = new SolidColorBrush(color); + } + + var button = new Button + { + Content = path, + Width = cell, + Height = cell, + Margin = new Thickness(0, 0, 4, 4), + HorizontalContentAlignment = HorizontalAlignment.Stretch, + VerticalContentAlignment = VerticalAlignment.Stretch, + }; + button.Classes.Add("tool"); + button.Classes.Set("active", vm.CurrentLibraryShape?.Name == item.Name && vm.CurrentLibraryShape?.Category == item.Category); + ToolTip.SetTip(button, item.Name); + AutomationProperties.SetName(button, item.Name); + button.Click += (_, _) => + { + vm.PickLibraryShapeCommand.Execute(item); + close(); + }; + + if (item.UserShape != null) + { + var remove = MakeMenuItem(Se.Language.Assa.DrawRemoveFromLibrary, "fa-solid fa-trash", null); + remove.Click += (_, _) => + { + vm.RemoveFromLibraryCommand.Execute(item); + wrap.Children.Remove(button); + }; + button.ContextMenu = new ContextMenu { ItemsSource = new List { remove } }; + } + + wrap.Children.Add(button); + } + + content.Children.Add(wrap); + } + + foreach (var category in ShapeLibrary.BuiltInCategories) + { + AddCategory(category, ShapeLibrary.BuiltIn.Where(i => i.Category == category)); + } + + AddCategory(Se.Language.Assa.DrawCategoryMyShapes, ShapeLibrary.GetUserShapes()); + + content.Children.Add(new TextBlock + { + Text = Se.Language.Assa.DrawShapeLibraryHint, + FontSize = 11.5, + Foreground = FaintTextBrush, + TextWrapping = TextWrapping.Wrap, + Width = columns * (cell + 4), + Margin = new Thickness(2, 4, 0, 0), + }); + + return new Border + { + Child = new ScrollViewer { Content = content, MaxHeight = 560 }, + Background = MenuBrush, + BorderBrush = MenuBorderBrush, + BorderThickness = new Thickness(1), + CornerRadius = new CornerRadius(10), + Padding = new Thickness(12, 10, 8, 10), + BoxShadow = new BoxShadows(new BoxShadow { Blur = 24, OffsetY = 8, Color = Color.FromArgb(150, 0, 0, 0) }), + }; + } + private static Border MakeStripSeparator() { return new Border diff --git a/src/ui/Features/Assa/AssaDraw/DrawShape.cs b/src/ui/Features/Assa/AssaDraw/DrawShape.cs index 892994a8ab9..52a155cdbb8 100644 --- a/src/ui/Features/Assa/AssaDraw/DrawShape.cs +++ b/src/ui/Features/Assa/AssaDraw/DrawShape.cs @@ -3,6 +3,7 @@ using System.Globalization; using System.Linq; using System.Text; +using System.Text.RegularExpressions; using Avalonia.Media; namespace Nikse.SubtitleEdit.Features.Assa.AssaDraw; @@ -386,6 +387,97 @@ public int ConvertAllCurvesToLines() return count; } + private static readonly Regex RegexDrawStart = new(@"\{[^{]*\\p1[^}]*\}", RegexOptions.Compiled); + private static readonly Regex RegexDrawEnd = new(@"\{[^{]*\\p0[^}]*\}", RegexOptions.Compiled); + + /// + /// Parses ASSA drawing commands (m/l/b, several "m" = several shapes) into shapes. + /// + public static List ParseAssa(string text, int layer, Color color, bool isEraser) + { + var shapes = new List(); + text = RegexDrawStart.Replace(text, string.Empty); + text = RegexDrawEnd.Replace(text, string.Empty); + var arr = text.Split((char[]?)null, StringSplitOptions.RemoveEmptyEntries); + + var i = 0; + var bezierCount = 0; + var state = DrawCoordinateType.None; + DrawCoordinate? moveCoordinate = null; + DrawShape? drawShape = null; + + while (i < arr.Length) + { + var v = arr[i]; + + if (v == "m" && i < arr.Length - 2 && + float.TryParse(arr[i + 1], NumberStyles.Float, CultureInfo.InvariantCulture, out var mX) && + float.TryParse(arr[i + 2], NumberStyles.Float, CultureInfo.InvariantCulture, out var mY)) + { + bezierCount = 0; + moveCoordinate = new DrawCoordinate(null, DrawCoordinateType.Move, mX, mY, DrawSettings.PointColor); + state = DrawCoordinateType.Move; + i += 2; + } + else if (v == "l") + { + state = DrawCoordinateType.Line; + bezierCount = 0; + if (moveCoordinate != null) + { + drawShape = new DrawShape { Layer = layer, ForeColor = color, IsEraser = isEraser }; + drawShape.AddPoint(DrawCoordinateType.Line, moveCoordinate.X, moveCoordinate.Y, DrawSettings.PointColor); + moveCoordinate = null; + shapes.Add(drawShape); + } + } + else if (v == "b") + { + state = DrawCoordinateType.BezierCurve; + if (moveCoordinate != null) + { + drawShape = new DrawShape { Layer = layer, ForeColor = color, IsEraser = isEraser }; + drawShape.AddPoint(DrawCoordinateType.BezierCurve, moveCoordinate.X, moveCoordinate.Y, DrawSettings.PointColor); + moveCoordinate = null; + shapes.Add(drawShape); + } + bezierCount = 1; + } + else if (state == DrawCoordinateType.Line && drawShape != null && i < arr.Length - 1 && + float.TryParse(arr[i], NumberStyles.Float, CultureInfo.InvariantCulture, out var lX) && + float.TryParse(arr[i + 1], NumberStyles.Float, CultureInfo.InvariantCulture, out var lY)) + { + drawShape.AddPoint(DrawCoordinateType.Line, lX, lY, DrawSettings.PointColor); + i++; + } + else if (state == DrawCoordinateType.BezierCurve && drawShape != null && i < arr.Length - 1 && + float.TryParse(arr[i], NumberStyles.Float, CultureInfo.InvariantCulture, out var bX) && + float.TryParse(arr[i + 1], NumberStyles.Float, CultureInfo.InvariantCulture, out var bY)) + { + bezierCount++; + if (bezierCount > 3) + { + bezierCount = 1; + } + + var pointType = bezierCount switch + { + 2 => DrawCoordinateType.BezierCurveSupport1, + 3 => DrawCoordinateType.BezierCurveSupport2, + _ => DrawCoordinateType.BezierCurve + }; + + var pointColor = bezierCount is 2 or 3 ? DrawSettings.PointHelperColor : DrawSettings.PointColor; + drawShape.AddPoint(pointType, bX, bY, pointColor); + i++; + } + + i++; + } + + return shapes; + } + private static float DistanceToSegment(float px, float py, float ax, float ay, float bx, float by) { var dx = bx - ax; diff --git a/src/ui/Features/Assa/AssaDraw/DrawingTool.cs b/src/ui/Features/Assa/AssaDraw/DrawingTool.cs index 96958a28b58..2e31131154f 100644 --- a/src/ui/Features/Assa/AssaDraw/DrawingTool.cs +++ b/src/ui/Features/Assa/AssaDraw/DrawingTool.cs @@ -22,4 +22,7 @@ public enum DrawingTool /// Color picker tool ColorPicker, + + /// Place a shape from the shape library + Shape, } diff --git a/src/ui/Features/Assa/AssaDraw/ShapeLibrary.cs b/src/ui/Features/Assa/AssaDraw/ShapeLibrary.cs new file mode 100644 index 00000000000..1e42195edf1 --- /dev/null +++ b/src/ui/Features/Assa/AssaDraw/ShapeLibrary.cs @@ -0,0 +1,302 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Text; +using Avalonia.Media; +using Nikse.SubtitleEdit.Logic.Config; + +namespace Nikse.SubtitleEdit.Features.Assa.AssaDraw; + +/// +/// A shape that can be placed with the shape tool: a built-in (speech bubbles, arrows, ...) or +/// one the user saved. Built-ins have no color of their own and take the current layer color. +/// +public sealed class ShapeLibraryItem +{ + private List? _template; + private readonly Func> _build; + + public string Name { get; } + public string Category { get; } + + /// + /// Saved by the user (can be removed); its parts keep their own colors. + /// + public SeAssaDrawShape? UserShape { get; } + + public ShapeLibraryItem(string name, string category, Func> build, SeAssaDrawShape? userShape = null) + { + Name = name; + Category = category; + _build = build; + UserShape = userShape; + } + + /// + /// The shapes in their own coordinates; Layer is the part index, ForeColor the part color. + /// + public List Template => _template ??= _build(); + + public (float Left, float Top, float Right, float Bottom) Bounds + { + get + { + var shapes = Template.Where(s => s.Points.Count > 0).ToList(); + if (shapes.Count == 0) + { + return (0, 0, 1, 1); + } + + var bounds = shapes[0].GetBounds(); + foreach (var shape in shapes.Skip(1)) + { + var b = shape.GetBounds(); + bounds = (Math.Min(bounds.Left, b.Left), Math.Min(bounds.Top, b.Top), Math.Max(bounds.Right, b.Right), Math.Max(bounds.Bottom, b.Bottom)); + } + + return bounds; + } + } + + /// + /// Height / width of the shape, for placing it with its own proportions. + /// + public float AspectRatio + { + get + { + var (left, top, right, bottom) = Bounds; + return right - left > 0.01f ? (bottom - top) / (right - left) : 1f; + } + } + + /// + /// Copies of the shape scaled into the rectangle. Built-ins get on + /// ; a saved shape gets one layer per part, with its own colors. + /// + public List CreateShapes(float x, float y, float width, float height, Color color, int firstLayer) + { + var (left, top, right, bottom) = Bounds; + var scaleX = right - left > 0.01f ? width / (right - left) : 1f; + var scaleY = bottom - top > 0.01f ? height / (bottom - top) : 1f; + var result = new List(); + foreach (var template in Template) + { + var shape = template.Clone(); + shape.Layer = firstLayer + template.Layer; + shape.ForeColor = UserShape != null ? template.ForeColor : color; + shape.Hidden = false; + foreach (var point in shape.Points) + { + point.X = MathF.Round(x + (point.X - left) * scaleX, 2); + point.Y = MathF.Round(y + (point.Y - top) * scaleY, 2); + } + + result.Add(shape); + } + + return result; + } + + /// + /// Thumbnail geometry in the shape's own coordinates (non-zero fill keeps holes open). + /// + public Geometry ToGeometry() + { + var geometry = new StreamGeometry(); + using var ctx = geometry.Open(); + ctx.SetFillRule(FillRule.NonZero); + foreach (var shape in Template.Where(s => s.Points.Count > 2 && !s.IsEraser)) + { + ctx.BeginFigure(new Avalonia.Point(shape.Points[0].X, shape.Points[0].Y), true); + var i = 1; + while (i < shape.Points.Count) + { + var point = shape.Points[i]; + if (point.DrawType == DrawCoordinateType.BezierCurveSupport1 && i + 2 < shape.Points.Count) + { + ctx.CubicBezierTo( + new Avalonia.Point(point.X, point.Y), + new Avalonia.Point(shape.Points[i + 1].X, shape.Points[i + 1].Y), + new Avalonia.Point(shape.Points[i + 2].X, shape.Points[i + 2].Y)); + i += 3; + } + else + { + ctx.LineTo(new Avalonia.Point(point.X, point.Y)); + i++; + } + } + + ctx.EndFigure(true); + } + + return geometry; + } +} + +/// +/// The built-in shapes (written as SVG path data in a 100 x 100 box) and the user's saved shapes. +/// +public static class ShapeLibrary +{ + private static List? _builtIn; + + public static IReadOnlyList BuiltIn => _builtIn ??= MakeBuiltIn(); + + public static IReadOnlyList BuiltInCategories => + [ + Se.Language.Assa.DrawCategorySpeechBubbles, + Se.Language.Assa.DrawCategoryArrows, + Se.Language.Assa.DrawCategoryBasicShapes, + Se.Language.Assa.DrawCategorySymbols, + ]; + + public static List GetUserShapes() + { + return Se.Settings.Assa.DrawShapeLibrary + .Select(saved => new ShapeLibraryItem(saved.Name, Se.Language.Assa.DrawCategoryMyShapes, () => ParseSaved(saved), saved)) + .ToList(); + } + + /// + /// Saves shapes as a library item: one part per layer (the layer's color), erasers kept as erasers. + /// + public static SeAssaDrawShape Save(string name, IEnumerable shapes) + { + var saved = new SeAssaDrawShape { Name = name }; + foreach (var group in shapes.Where(s => s.Points.Count > 0).GroupBy(s => (s.Layer, s.IsEraser)).OrderBy(g => g.Key.Layer).ThenBy(g => g.Key.IsEraser)) + { + saved.Parts.Add(new SeAssaDrawShapePart + { + Drawing = string.Join(" ", group.Select(s => s.ToAssa())), + Color = group.First().ForeColor.ToString(), + IsEraser = group.Key.IsEraser, + }); + } + + Se.Settings.Assa.DrawShapeLibrary.Add(saved); + return saved; + } + + public static void Remove(SeAssaDrawShape saved) + { + Se.Settings.Assa.DrawShapeLibrary.Remove(saved); + } + + private static List ParseSaved(SeAssaDrawShape saved) + { + var result = new List(); + var layer = 0; + int? previousLayerKey = null; + foreach (var part in saved.Parts) + { + var color = Color.TryParse(part.Color, out var parsed) ? parsed : Colors.White; + + // An eraser part belongs to the layer of the draw part before it + if (!part.IsEraser && previousLayerKey != null) + { + layer++; + } + + previousLayerKey = layer; + result.AddRange(DrawShape.ParseAssa(part.Drawing, layer, color, part.IsEraser)); + } + + return result; + } + + private static ShapeLibraryItem Item(string name, string category, string[] union, string[]? subtract = null) + { + return new ShapeLibraryItem(name, category, () => SvgImporter.FromPathData(union, subtract)); + } + + private static string F(double value) => value.ToString("0.##", CultureInfo.InvariantCulture); + + private static string Circle(double cx, double cy, double r) => + $"M{F(cx - r)} {F(cy)} A{F(r)} {F(r)} 0 1 0 {F(cx + r)} {F(cy)} A{F(r)} {F(r)} 0 1 0 {F(cx - r)} {F(cy)} Z"; + + /// + /// Closed polygon around (cx, cy) with the radii repeated around the circle (two radii = a star). + /// + private static string Polygon(double cx, double cy, int corners, double startDegrees, params double[] radii) + { + var sb = new StringBuilder(); + for (var i = 0; i < corners; i++) + { + var angle = (startDegrees + 360.0 * i / corners) * Math.PI / 180; + var r = radii[i % radii.Length]; + sb.Append(i == 0 ? "M" : " L").Append(F(cx + r * Math.Cos(angle))).Append(' ').Append(F(cy + r * Math.Sin(angle))); + } + + return sb.Append(" Z").ToString(); + } + + private static List MakeBuiltIn() + { + var l = Se.Language.Assa; + var bubbles = l.DrawCategorySpeechBubbles; + var arrows = l.DrawCategoryArrows; + var basic = l.DrawCategoryBasicShapes; + var symbols = l.DrawCategorySymbols; + + return + [ + // Speech bubbles + Item(l.DrawShapeSpeechBubble, bubbles, ["M12 0 H88 A12 12 0 0 1 100 12 V58 A12 12 0 0 1 88 70 H38 L14 94 L22 70 H12 A12 12 0 0 1 0 58 V12 A12 12 0 0 1 12 0 Z"]), + Item(l.DrawShapeSpeechBubbleRight, bubbles, ["M12 0 H88 A12 12 0 0 1 100 12 V58 A12 12 0 0 1 88 70 H78 L86 94 L62 70 H12 A12 12 0 0 1 0 58 V12 A12 12 0 0 1 12 0 Z"]), + Item(l.DrawShapeSpeechBubbleUp, bubbles, ["M12 24 H22 L30 0 L46 24 H88 A12 12 0 0 1 100 36 V82 A12 12 0 0 1 88 94 H12 A12 12 0 0 1 0 82 V36 A12 12 0 0 1 12 24 Z"]), + Item(l.DrawShapeOvalBubble, bubbles, ["M0 38 A50 38 0 1 1 100 38 A50 38 0 1 1 0 38 Z", "M22 62 L8 96 L44 72 Z"]), + Item(l.DrawShapeBoxBubble, bubbles, ["M0 0 H100 V70 H60 L50 94 L40 70 H0 Z"]), + Item(l.DrawShapeThoughtBubble, bubbles, + [ + Circle(26, 34, 20), Circle(46, 22, 22), Circle(68, 24, 20), Circle(84, 40, 16), + Circle(70, 56, 18), Circle(46, 58, 20), Circle(22, 54, 17), Circle(50, 40, 26), + Circle(18, 82, 7), Circle(8, 95, 4), + ]), + Item(l.DrawShapeShoutBubble, bubbles, [Polygon(50, 50, 24, -90, 50, 36, 46, 34, 50, 38, 44, 35)]), + Item(l.DrawShapeCaptionBox, bubbles, ["M6 0 H94 A6 6 0 0 1 100 6 V24 A6 6 0 0 1 94 30 H6 A6 6 0 0 1 0 24 V6 A6 6 0 0 1 6 0 Z"]), + + // Arrows + Item(l.DrawShapeArrow, arrows, ["M0 35 H60 V15 L100 50 L60 85 V65 H0 Z"]), + Item(l.DrawShapeDoubleArrow, arrows, ["M0 50 L30 20 V38 H70 V20 L100 50 L70 80 V62 H30 V80 Z"]), + Item(l.DrawShapeChevron, arrows, ["M0 0 H40 L80 50 L40 100 H0 L40 50 Z"]), + Item(l.DrawShapeCurvedArrow, arrows, ["M10 90 A60 60 0 0 1 70 30 V10 L100 40 L70 70 V50 A40 40 0 0 0 30 90 Z"]), + + // Basic shapes + Item(l.DrawShapeRoundedRectangle, basic, ["M15 0 H85 A15 15 0 0 1 100 15 V85 A15 15 0 0 1 85 100 H15 A15 15 0 0 1 0 85 V15 A15 15 0 0 1 15 0 Z"]), + Item(l.DrawShapeTriangle, basic, ["M50 0 L100 88 H0 Z"]), + Item(l.DrawShapeDiamond, basic, ["M50 0 L100 50 L50 100 L0 50 Z"]), + Item(l.DrawShapePentagon, basic, [Polygon(50, 50, 5, -90, 50)]), + Item(l.DrawShapeHexagon, basic, [Polygon(50, 50, 6, 0, 50)]), + Item(l.DrawShapeOctagon, basic, [Polygon(50, 50, 8, 22.5, 50)]), + Item(l.DrawShapeStar, basic, [Polygon(50, 50, 10, -90, 50, 20)]), + Item(l.DrawShapeRing, basic, [Circle(50, 50, 50)], [Circle(50, 50, 32)]), + Item(l.DrawShapePlus, basic, ["M35 0 H65 V35 H100 V65 H65 V100 H35 V65 H0 V35 H35 Z"]), + Item(l.DrawShapeBanner, basic, ["M0 20 H100 L88 45 L100 70 H0 L12 45 Z"]), + + // Symbols + Item(l.DrawShapeHeart, symbols, ["M50 92 C20 70 0 52 0 30 C0 12 13 0 28 0 C38 0 46 6 50 14 C54 6 62 0 72 0 C87 0 100 12 100 30 C100 52 80 70 50 92 Z"]), + Item(l.DrawShapeCheck, symbols, ["M0 55 L15 40 L38 62 L85 10 L100 25 L38 92 Z"]), + Item(l.DrawShapeMusicNote, symbols, + [ + "M14.96 85.47 A16 11 -20 1 0 45.04 74.53 A16 11 -20 1 0 14.96 85.47 Z", + "M40 8 H46 V76 H40 Z", + "M46 8 C52 22 74 28 74 50 C70 38 58 33 46 32 Z", + ]), + Item(l.DrawShapeMusicNotes, symbols, + [ + "M9.04 87.47 A16 11 -20 1 0 39.04 76.53 A16 11 -20 1 0 9.04 87.47 Z", + "M59 77.47 A16 11 -20 1 0 89.04 66.53 A16 11 -20 1 0 59 77.47 Z", + "M34 18 H40 V80 H34 Z", + "M84 8 H90 V70 H84 Z", + "M34 18 L90 6 V18 L34 30 Z", + ]), + Item(l.DrawShapeLightning, symbols, ["M58 0 L10 58 H44 L36 100 L90 38 H56 Z"]), + Item(l.DrawShapeCloud, symbols, [Circle(30, 58, 22), Circle(52, 42, 28), Circle(76, 58, 22), "M30 58 H76 V80 H30 Z"]), + Item(l.DrawShapeMoon, symbols, [Circle(50, 50, 50)], [Circle(70, 36, 42)]), + Item(l.DrawShapeBadge, symbols, [Polygon(50, 50, 32, -90, 50, 43)]), + ]; + } +} diff --git a/src/ui/Features/Assa/AssaDraw/SvgImporter.cs b/src/ui/Features/Assa/AssaDraw/SvgImporter.cs index bb77f4843bd..7ee4f7495ab 100644 --- a/src/ui/Features/Assa/AssaDraw/SvgImporter.cs +++ b/src/ui/Features/Assa/AssaDraw/SvgImporter.cs @@ -144,6 +144,46 @@ public static Result Import(string svgText, int canvasWidth, int canvasHeight, i return result; } + /// + /// Builds shapes from SVG path data: the paths are merged, the + /// paths cut out of them, and the result scaled by . + /// Used for the built-in shape library, which is written as SVG path data. + /// + public static List FromPathData(IEnumerable union, IEnumerable? subtract = null, float scale = 10) + { + SKPath? result = null; + foreach (var data in union) + { + var path = SKPath.ParseSvgPathData(data); + if (path == null) + { + continue; + } + + path.FillType = SKPathFillType.Winding; + result = result == null ? path.Simplify() ?? path : result.Op(path, SKPathOp.Union) ?? result; + } + + if (result == null) + { + return []; + } + + foreach (var data in subtract ?? []) + { + var path = SKPath.ParseSvgPathData(data); + if (path != null) + { + result = result.Op(path, SKPathOp.Difference) ?? result; + } + } + + result.Transform(SKMatrix.CreateScale(scale, scale)); + var contours = ReadContours(result.Simplify() ?? result); + NormalizeDirections(contours); + return contours.Select(c => ToDrawShape(c, 0, Colors.White)).ToList(); + } + private static void Walk(XElement element, Context parent, List painted, Dictionary ids, Dictionary> classRules, int depth) { @@ -459,12 +499,7 @@ private static List ReadContours(SKPath path) break; case SKPathVerb.Conic when current != null: - var quads = SKPath.ConvertConicToQuads(points[0], points[1], points[2], iterator.ConicWeight(), 2); - for (var i = 0; i + 2 < quads.Length; i += 2) - { - current.Segments.Add(QuadToCubic(quads[i], quads[i + 1], quads[i + 2])); - } - + current.Segments.Add(ConicToCubic(points[0], points[1], points[2], iterator.ConicWeight())); break; case SKPathVerb.Cubic when current != null: @@ -620,6 +655,23 @@ private static DrawShape ToDrawShape(Contour contour, int layer, Color color) private static bool IsSamePoint(SKPoint a, SKPoint b) => Math.Abs(a.X - b.X) < 0.01f && Math.Abs(a.Y - b.Y) < 0.01f; + /// + /// One cubic for a conic: control points at 4w/(3(1+w)) along the conic's tangents. Skia makes + /// arcs of at most 90 degrees, where this is the usual circle approximation (k = 0.5523) - one + /// bezier per quarter circle instead of the four the quad conversion produced. + /// + private static Segment ConicToCubic(SKPoint p0, SKPoint p1, SKPoint p2, float weight) + { + var k = 4f * weight / (3f * (1f + weight)); + return new Segment + { + IsCubic = true, + Control1 = new SKPoint(p0.X + (p1.X - p0.X) * k, p0.Y + (p1.Y - p0.Y) * k), + Control2 = new SKPoint(p2.X + (p1.X - p2.X) * k, p2.Y + (p1.Y - p2.Y) * k), + End = p2, + }; + } + private static Segment QuadToCubic(SKPoint p0, SKPoint q, SKPoint p2) { return new Segment diff --git a/src/ui/Logic/Config/Language/Assa/LanguageAssa.cs b/src/ui/Logic/Config/Language/Assa/LanguageAssa.cs index 37ea5c79c3c..6e40a8378df 100644 --- a/src/ui/Logic/Config/Language/Assa/LanguageAssa.cs +++ b/src/ui/Logic/Config/Language/Assa/LanguageAssa.cs @@ -44,6 +44,47 @@ public class LanguageAssa public string DrawBackground { get; set; } public string DrawPosition { get; set; } public string DrawZoom { get; set; } + public string DrawShapeTool { get; set; } + public string DrawShapeLibrary { get; set; } + public string DrawShapeLibraryHint { get; set; } + public string DrawAddToLibrary { get; set; } + public string DrawRemoveFromLibrary { get; set; } + public string DrawShapeName { get; set; } + public string DrawCategorySpeechBubbles { get; set; } + public string DrawCategoryArrows { get; set; } + public string DrawCategoryBasicShapes { get; set; } + public string DrawCategorySymbols { get; set; } + public string DrawCategoryMyShapes { get; set; } + public string DrawShapeSpeechBubble { get; set; } + public string DrawShapeSpeechBubbleRight { get; set; } + public string DrawShapeSpeechBubbleUp { get; set; } + public string DrawShapeOvalBubble { get; set; } + public string DrawShapeBoxBubble { get; set; } + public string DrawShapeThoughtBubble { get; set; } + public string DrawShapeShoutBubble { get; set; } + public string DrawShapeCaptionBox { get; set; } + public string DrawShapeArrow { get; set; } + public string DrawShapeDoubleArrow { get; set; } + public string DrawShapeChevron { get; set; } + public string DrawShapeCurvedArrow { get; set; } + public string DrawShapeRoundedRectangle { get; set; } + public string DrawShapeTriangle { get; set; } + public string DrawShapeDiamond { get; set; } + public string DrawShapePentagon { get; set; } + public string DrawShapeHexagon { get; set; } + public string DrawShapeOctagon { get; set; } + public string DrawShapeStar { get; set; } + public string DrawShapeRing { get; set; } + public string DrawShapePlus { get; set; } + public string DrawShapeBanner { get; set; } + public string DrawShapeHeart { get; set; } + public string DrawShapeCheck { get; set; } + public string DrawShapeMusicNote { get; set; } + public string DrawShapeMusicNotes { get; set; } + public string DrawShapeLightning { get; set; } + public string DrawShapeCloud { get; set; } + public string DrawShapeMoon { get; set; } + public string DrawShapeBadge { get; set; } public string DrawSize { get; set; } public string DrawLayer { get; set; } public string DrawBackgroundVideoFrame { get; set; } @@ -335,6 +376,47 @@ public LanguageAssa() DrawBackground = "Background"; DrawPosition = "Position"; DrawZoom = "Zoom"; + DrawShapeTool = "Shape tool"; + DrawShapeLibrary = "Shape library"; + DrawShapeLibraryHint = "Drag on the canvas to place a shape, or click for the default size. Shift keeps its proportions."; + DrawAddToLibrary = "Add to shape library..."; + DrawRemoveFromLibrary = "Remove from shape library"; + DrawShapeName = "Shape name"; + DrawCategorySpeechBubbles = "Speech bubbles"; + DrawCategoryArrows = "Arrows"; + DrawCategoryBasicShapes = "Basic shapes"; + DrawCategorySymbols = "Symbols"; + DrawCategoryMyShapes = "My shapes"; + DrawShapeSpeechBubble = "Speech bubble"; + DrawShapeSpeechBubbleRight = "Speech bubble, tail right"; + DrawShapeSpeechBubbleUp = "Speech bubble, tail up"; + DrawShapeOvalBubble = "Oval speech bubble"; + DrawShapeBoxBubble = "Box speech bubble"; + DrawShapeThoughtBubble = "Thought bubble"; + DrawShapeShoutBubble = "Shout bubble"; + DrawShapeCaptionBox = "Caption box"; + DrawShapeArrow = "Arrow"; + DrawShapeDoubleArrow = "Double arrow"; + DrawShapeChevron = "Chevron"; + DrawShapeCurvedArrow = "Curved arrow"; + DrawShapeRoundedRectangle = "Rounded rectangle"; + DrawShapeTriangle = "Triangle"; + DrawShapeDiamond = "Diamond"; + DrawShapePentagon = "Pentagon"; + DrawShapeHexagon = "Hexagon"; + DrawShapeOctagon = "Octagon"; + DrawShapeStar = "Star"; + DrawShapeRing = "Ring"; + DrawShapePlus = "Plus"; + DrawShapeBanner = "Banner"; + DrawShapeHeart = "Heart"; + DrawShapeCheck = "Check mark"; + DrawShapeMusicNote = "Music note"; + DrawShapeMusicNotes = "Music notes"; + DrawShapeLightning = "Lightning"; + DrawShapeCloud = "Cloud"; + DrawShapeMoon = "Moon"; + DrawShapeBadge = "Badge"; DrawSize = "Size"; DrawLayer = "Layer"; DrawBackgroundVideoFrame = "Video frame (current position)"; diff --git a/src/ui/Logic/Config/SeAssa.cs b/src/ui/Logic/Config/SeAssa.cs index a4264504d98..5286f35dc29 100644 --- a/src/ui/Logic/Config/SeAssa.cs +++ b/src/ui/Logic/Config/SeAssa.cs @@ -46,6 +46,11 @@ public class SeAssa /// ASSA draw: stretch a background image to the canvas instead of fitting it with its aspect ratio. /// public bool DrawBackgroundStretch { get; set; } + + /// + /// ASSA draw: shapes the user saved to the shape library ("My shapes"). + /// + public List DrawShapeLibrary { get; set; } public bool HideLayersFromSubtitleGrid { get; set; } public bool HideLayersFromVideoPreview { get; set; } public bool FontCollectorTrimFonts { get; set; } @@ -83,6 +88,7 @@ public SeAssa() HideLayersFromWaveform = true; DrawBackgroundOpacity = 1.0; + DrawShapeLibrary = new List(); HideLayersFromSubtitleGrid = false; HideLayersFromVideoPreview = false; } diff --git a/src/ui/Logic/Config/SeAssaDrawShape.cs b/src/ui/Logic/Config/SeAssaDrawShape.cs new file mode 100644 index 00000000000..dd66735a20f --- /dev/null +++ b/src/ui/Logic/Config/SeAssaDrawShape.cs @@ -0,0 +1,22 @@ +using System.Collections.Generic; + +namespace Nikse.SubtitleEdit.Logic.Config; + +/// +/// A shape saved to the ASSA draw shape library. +/// +public class SeAssaDrawShape +{ + public string Name { get; set; } = string.Empty; + public List Parts { get; set; } = []; +} + +/// +/// One color of a saved library shape: ASSA drawing commands (may hold several "m" shapes). +/// +public class SeAssaDrawShapePart +{ + public string Drawing { get; set; } = string.Empty; + public string Color { get; set; } = string.Empty; + public bool IsEraser { get; set; } +} diff --git a/tests/UI/Features/Assa/AssaDrawShapeLibraryTests.cs b/tests/UI/Features/Assa/AssaDrawShapeLibraryTests.cs new file mode 100644 index 00000000000..fd38ab7176e --- /dev/null +++ b/tests/UI/Features/Assa/AssaDrawShapeLibraryTests.cs @@ -0,0 +1,162 @@ +using Avalonia; +using Avalonia.Headless; +using Avalonia.Headless.XUnit; +using Avalonia.Input; +using Avalonia.Input.Raw; +using Avalonia.Media; +using Avalonia.Threading; +using Nikse.SubtitleEdit.Core.Common; +using Nikse.SubtitleEdit.Core.SubtitleFormats; +using Nikse.SubtitleEdit.Features.Assa.AssaDraw; +using Nikse.SubtitleEdit.Features.Main; +using Nikse.SubtitleEdit.Logic.Config; +using Nikse.SubtitleEdit.Logic.Media; + +namespace UITests.Features.Assa; + +/// +/// Shape library + shape tool: built-in shapes, placing them, saving your own. +/// +public class AssaDrawShapeLibraryTests +{ + private static AssaDrawViewModel MakeViewModel(params string[] texts) + { + var vm = new AssaDrawViewModel(new FileHelper(), new StubWindowService()); + var lines = texts.Select((t, i) => new SubtitleLineViewModel(new Paragraph(t, 0, 2000) { Extra = "Default", Layer = i }, new AdvancedSubStationAlpha())).ToList(); + vm.Initialize(new Subtitle { Header = AdvancedSubStationAlpha.DefaultHeader }, lines, 1920, 1080); + return vm; + } + + [Fact] + public void EveryBuiltInShape_HasClosedContours_AndAThumbnail() + { + Assert.True(ShapeLibrary.BuiltIn.Count >= 25); + foreach (var item in ShapeLibrary.BuiltIn) + { + Assert.NotEmpty(item.Template); + Assert.All(item.Template, s => Assert.True(s.Points.Count >= 3, item.Name)); + var (left, top, right, bottom) = item.Bounds; + Assert.True(right - left > 100 && bottom - top > 100, item.Name); + Assert.False(item.ToGeometry().Bounds.Width <= 0, item.Name); + } + + Assert.Equal(2, ShapeLibrary.BuiltIn.First(i => i.Name == Se.Language.Assa.DrawShapeRing).Template.Count); + Assert.True(ShapeLibrary.BuiltIn.Count(i => i.Category == Se.Language.Assa.DrawCategorySpeechBubbles) >= 6); + } + + [Fact] + public void Insert_FillsTheRectangle_OnANewLayer_InTheCurrentColor_AndCanBeUndone() + { + var vm = MakeViewModel("{\\p1}m 0 0 l 10 0 10 10{\\p0}"); + vm.LayerColor = Colors.Orange; + vm.CurrentLibraryShape = ShapeLibrary.BuiltIn.First(i => i.Name == Se.Language.Assa.DrawShapeSpeechBubble); + + vm.InsertLibraryShape(new CanvasInsertEventArgs(100, 200, 400, 300, isClick: false)); + + var inserted = Assert.Single(vm.Shapes.Skip(1)); + var (left, top, right, bottom) = inserted.GetBounds(); + Assert.Equal(100, left, 1); + Assert.Equal(200, top, 1); + Assert.Equal(500, right, 1); + Assert.Equal(500, bottom, 1); + Assert.Equal(1, inserted.Layer); + Assert.Equal(Colors.Orange, inserted.ForeColor); + Assert.Equal(DrawingTool.Select, vm.CurrentTool); + Assert.Same(inserted, vm.SelectedTreeItem?.Shape); + + vm.UndoCommand.Execute(null); + Assert.Single(vm.Shapes); + } + + [Fact] + public void Click_PlacesDefaultSize_CenteredOnTheClick_KeepingProportions() + { + var vm = MakeViewModel(); + var item = ShapeLibrary.BuiltIn.First(i => i.Name == Se.Language.Assa.DrawShapeArrow); + vm.CurrentLibraryShape = item; + + vm.InsertLibraryShape(new CanvasInsertEventArgs(960, 540, 0, 0, isClick: true)); + + var (left, top, right, bottom) = vm.Shapes[0].GetBounds(); + Assert.Equal(1920 / 5f, right - left, 1); + Assert.Equal(960, (left + right) / 2, 1); + Assert.Equal(540, (top + bottom) / 2, 1); + Assert.Equal(item.AspectRatio, (bottom - top) / (right - left), 2); + } + + [Fact] + public void SavedShape_KeepsItsColorsPerLayer_AndCanBeRemoved() + { + var saved = Se.Settings.Assa.DrawShapeLibrary.ToList(); + try + { + var vm = MakeViewModel( + "{\\p1}m 0 0 l 100 0 100 100 0 100{\\p0}", + "{\\p1}m 25 25 l 75 25 75 75 25 75{\\p0}"); + vm.Shapes[0].ForeColor = Colors.Red; + vm.Shapes[1].ForeColor = Colors.Blue; + + var item = vm.AddToLibrary("Target", vm.Shapes.ToList()); + Assert.Same(item, vm.CurrentLibraryShape); + Assert.Contains(ShapeLibrary.GetUserShapes(), i => i.Name == "Target"); + + vm.InsertLibraryShape(new CanvasInsertEventArgs(1000, 500, 200, 200, isClick: false)); + + var inserted = vm.Shapes.Skip(2).ToList(); + Assert.Equal(2, inserted.Count); + Assert.Equal(Colors.Red, inserted[0].ForeColor); + Assert.Equal(Colors.Blue, inserted[1].ForeColor); + Assert.Equal(2, inserted[0].Layer); + Assert.Equal(3, inserted[1].Layer); + Assert.Equal(1050, inserted[1].GetBounds().Left, 1); + + vm.RemoveFromLibraryCommand.Execute(item); + Assert.DoesNotContain(ShapeLibrary.GetUserShapes(), i => i.Name == "Target"); + Assert.NotSame(item, vm.CurrentLibraryShape); + } + finally + { + Se.Settings.Assa.DrawShapeLibrary = saved; + } + } + + [AvaloniaFact] + public void ShapeTool_DragOnCanvas_PlacesShape() + { + var vm = MakeViewModel(); + var window = new AssaDrawWindow(vm) { Width = 1200, Height = 800 }; + window.Show(); + Dispatcher.UIThread.RunJobs(); + try + { + var canvas = vm.Canvas!; + canvas.FitToView(); + vm.CurrentLibraryShape = ShapeLibrary.BuiltIn.First(i => i.Name == Se.Language.Assa.DrawShapeStar); + vm.ShapeToolCommand.Execute(null); + + Point ToWindow(double x, double y) + { + var origin = canvas.TranslatePoint(new Point(0, 0), window)!.Value; + var zoom = canvas.ZoomFactor; + return new Point(origin.X + (canvas.Bounds.Width - 1920 * zoom) / 2 + x * zoom, origin.Y + (canvas.Bounds.Height - 1080 * zoom) / 2 + y * zoom); + } + + window.MouseMove(ToWindow(400, 300)); + window.MouseDown(ToWindow(400, 300), MouseButton.Left); + window.MouseMove(ToWindow(600, 450), RawInputModifiers.LeftMouseButton); + window.MouseMove(ToWindow(800, 600), RawInputModifiers.LeftMouseButton); + window.MouseUp(ToWindow(800, 600), MouseButton.Left); + Dispatcher.UIThread.RunJobs(); + + var (left, top, right, bottom) = Assert.Single(vm.Shapes).GetBounds(); + Assert.Equal(400, left, 0); + Assert.Equal(300, top, 0); + Assert.Equal(800, right, 0); + Assert.Equal(600, bottom, 0); + } + finally + { + window.Close(); + } + } +} From 5d75a5dcb067885fbdf81a32143fba2fa277fdec Mon Sep 17 00:00:00 2001 From: niksedk Date: Sat, 10 Oct 2026 07:48:49 +0200 Subject: [PATCH 005/306] seconv test: show the full OCR output when the image list OCR assertion fails Co-Authored-By: Claude Opus 5.5 --- tests/seconv/Core/Se4FormatInputTest.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/seconv/Core/Se4FormatInputTest.cs b/tests/seconv/Core/Se4FormatInputTest.cs index 47fe6493213..c0dd99386d1 100644 --- a/tests/seconv/Core/Se4FormatInputTest.cs +++ b/tests/seconv/Core/Se4FormatInputTest.cs @@ -110,8 +110,9 @@ public async Task ImageListInput_IsOcrd(string format, string pattern) var (result, srt) = await ToSubRip(index, ocrDb: ocrDb); Assert.True(result.Success, string.Join("; ", result.Errors)); - Assert.Contains("Hello, World!", srt); - Assert.Contains("This is a test subtitle.", srt); + // Full output in the message: Assert.Contains truncates it, which hides what nOCR read. + Assert.True(srt.Contains("Hello, World!"), srt); + Assert.True(srt.Contains("This is a test subtitle."), srt); } [Fact] From 8a542671cfdfbdb8667e941b642fa9142b585b89 Mon Sep 17 00:00:00 2001 From: LazySwift Date: Sat, 10 Oct 2026 08:59:41 +0300 Subject: [PATCH 006/306] Improve Russian translation: add missing strings, fix mistranslations --- src/ui/Assets/Languages/Russian.json | 103 ++++++++++++++++++++++----- 1 file changed, 84 insertions(+), 19 deletions(-) diff --git a/src/ui/Assets/Languages/Russian.json b/src/ui/Assets/Languages/Russian.json index 4806c6849ae..722506dba3f 100644 --- a/src/ui/Assets/Languages/Russian.json +++ b/src/ui/Assets/Languages/Russian.json @@ -7,6 +7,24 @@ "abort": "Отмена", "actor": "Актер", "actors": "Актеры", + "setActorDotDotDot": "Задать актера...", + "actorPickerTitle": "Задать актера", + "actorPickerFilterHint": "Начните вводить для фильтрации или введите имя нового актера", + "actorPickerKeysHint": "Нажмите цифру или Enter, чтобы назначить актера. Alt+Вверх/Вниз меняет порядок, Delete удаляет актера.", + "actorPickerNewActorX": "Новый актер \"{0}\"", + "actorPickerLinesSelectedX": "Выбрано строк: {0}", + "setStyleDotDotDot": "Задать стиль...", + "stylePickerTitle": "Задать стиль", + "stylePickerFilterHint": "Начните вводить для фильтрации или введите имя нового стиля", + "stylePickerKeysHint": "Нажмите цифру (1-9, 0), чтобы назначить стиль всем выбранным строкам.", + "stylePickerNewStyleX": "Новый стиль \"{0}\"", + "stylePickerNewStyleInfo": "Нажмите Enter, чтобы добавить этот стиль с настройками по умолчанию и назначить его выбранным строкам. Шрифт, цвета и положение можно изменить позже через \"Управление стилями...\".", + "stylePickerCurrentStyleX": "Текущий стиль: {0}", + "stylePickerLinesUsingStyleX": "Строк с этим стилем: {0}", + "stylePickerMarginsXYZ": "Слева {0}, справа {1}, по вертикали {2}", + "stylePickerSetStyle": "_Задать стиль", + "stylePickerManageStyles": "Управление стилями...", + "stylePickerManageStylesHint": "Закрыть это окно и открыть управление стилями, чтобы изменить шрифты, цвета, положение и другое", "add": "Добавить", "addDotDotDot": "Добавлять...", "addToNamesListCaseSensitive": "Добавить в список имен (с учетом регистра)", @@ -24,9 +42,9 @@ "allFiles": "Все файлы", "alphaAdjustment": "Альфа-коррекция", "alphaThreshold": "Альфа-порог", - "apiKey": "API key", + "apiKey": "API-ключ", "formality": "Формальность", - "apiSecret": "Secret", + "apiSecret": "Секретный ключ", "appearance": "Оформление", "append": "Добавить", "apply": "Применить", @@ -270,7 +288,7 @@ "goToLineNumber": "Перейти к номеру строки", "goToNextShotChange": "Перейти к следующей смене кадра", "goToPreviousShotChange": "Перейти к предыдущей смене кадра", - "googleIt": "Google it ", + "googleIt": "Найти в Google ", "group": "Группа", "header": "Заголовок", "height": "Высота", @@ -337,10 +355,11 @@ "mergeSelectedAsDialog": "Объединение выбрано в качестве диалогового окна", "mergeSelectedLines": "Объединить выбранные строки", "mergeSelectedLinesDialog": "Объединить выбранные строки в диалоговое окно", - "mergeWithLineAfterAndAutoBreak": "Объединить с последующей строкой и автоматическим переносом", - "mergeWithLineAfterKeepBreaks": "Объединить с последующей строкой (сохранить разрывы)", - "mergeWithLineBeforeAndAutoBreak": "Объединить с предыдущей строкой и автоматически разорвать", - "mergeWithLineBeforeKeepBreaks": "Объединить с предыдущей строкой (сохранить разрывы)", + "mergeWithLineAfterAndAutoBreak": "Объединить со следующей строкой и расставить переносы автоматически", + "mergeWithLineAfterKeepBreaks": "Объединить со следующей строкой (сохранить переносы)", + "mergeWithLineAfterAndUnbreak": "Объединить со следующей строкой и убрать переносы", + "mergeWithLineBeforeAndAutoBreak": "Объединить с предыдущей строкой и расставить переносы автоматически", + "mergeWithLineBeforeKeepBreaks": "Объединить с предыдущей строкой (сохранить переносы)", "middleCenter": "Середина-центр", "middleLeft": "Средне-левый", "middleRight": "Средне-правый", @@ -353,6 +372,8 @@ "moveAllShotChangeOneFrameForward": "Переместить все смены кадров на один кадр вперед", "multiMode": "Многорежимный", "moveDown": "Спустить ниже", + "increase": "Увеличить", + "decrease": "Уменьшить", "moveUp": "Поднять выше", "moveToTop": "Перейти наверх", "moveToBottom": "Перейти вниз", @@ -422,6 +443,7 @@ "percent": "Процент", "pickLayer": "Установить слой", "pickOllamaModel": "Выбрать модель Ollama", + "pickModel": "Выбрать модель", "llamaCppUseRemoteServer": "Использовать внешний сервер (URL)", "llamaCppEngineSettings": "Настройки движка llama.cpp", "llamaCppEngineSettingsSubtitle": "Локальная сборка сервера llama.cpp", @@ -498,6 +520,7 @@ "reset": "Сбросить", "resizePanels": "Изменить размер панелей", "resolution": "Разрешение", + "retry": "Повторить", "reverseRightToLeftStartEnd": "Начало/конец в обратном порядке RTL", "right": "Верно", "rightToLeft": "Справа налево", @@ -559,6 +582,8 @@ "shadowColor": "Цвет тени", "shadowWidth": "ширина тени", "shiftLineBy": "Сдвинуть строку на", + "shotIn": "Начало плана", + "shotOut": "Конец плана", "shortcut": "Быстрый доступ", "shortcuts": "Ярлыки", "show": "Начало", @@ -573,6 +598,8 @@ "showLayerColumn": "Показать столбец \"Слой\"", "showPreview": "Показать предварительный просмотр", "showShotChangesList": "Показать список изменений в снимках", + "showShotInColumn": "Показать столбец \"Начало плана\"", + "showShotOutColumn": "Показать столбец \"Конец плана\"", "showStyleColumn": "Показать столбец \"Стиль\"", "showTeletext": "Показать TT", "showTimeCodes": "Показать временные коды", @@ -629,12 +656,14 @@ "styleExaggeration": "Преувеличение стиля", "styles": "Стили", "subtitleFile": "файл субтитров", + "subtitleFileChangedOutsideX": "Файл \"{0}\" был изменен вне Subtitle Edit.\n\nПерезагрузить его? Несохраненные изменения будут потеряны.", "subtitleFileName": "Название файла субтитров", "subtitleFileSaved": "Файл субтитров сохранен", "subtitleFileSavedToX": "Файл субтитров сохранен в {0}", "subtitleFiles": "файлы субтитров", "subtitleFormats": "Форматы субтитров", "subtitleLoadedX": "Субтитры загружены: {0}", + "subtitleReloadedChangedOutsideX": "Субтитры перезагружены (изменены вне Subtitle Edit): {0}", "suffix": "Суффикс", "suggestions": "Предложения", "sync": "Синхронизация", @@ -678,8 +707,9 @@ "translation": "Перевод", "twoLetterLanguageCode": "Двухбуквенный языковой код", "type": "Тип", - "unbreak": "Неразрывный текст", - "unbreakLines": "Неразрывные строки", + "unbreak": "Убрать переносы", + "unbreakLines": "Убрать переносы строк", + "undelete": "Восстановить удаленное", "underline": "Подчеркнуть", "undo": "Отменить", "unknown": "Неизвестный", @@ -717,10 +747,23 @@ "videoCustom3ForwardX": "Видео, пользовательские миллисекунды ({0:#,###,##0}) вперед, 3", "videoCustom4BackX": "Видео, пользовательские миллисекунды ({0:#,###,##0}) назад, 4", "videoCustom4ForwardX": "Видео, пользовательские миллисекунды ({0:#,###,##0}) вперед, 4", + "videoCustom1BackAndPauseX": "Видео, заданное время ({0:#,###,##0} мс) назад и пауза, 1", + "videoCustom1ForwardAndPauseX": "Видео, заданное время ({0:#,###,##0} мс) вперед и пауза, 1", + "videoCustom2BackAndPauseX": "Видео, заданное время ({0:#,###,##0} мс) назад и пауза, 2", + "videoCustom2ForwardAndPauseX": "Видео, заданное время ({0:#,###,##0} мс) вперед и пауза, 2", + "videoCustom3BackAndPauseX": "Видео, заданное время ({0:#,###,##0} мс) назад и пауза, 3", + "videoCustom3ForwardAndPauseX": "Видео, заданное время ({0:#,###,##0} мс) вперед и пауза, 3", + "videoCustom4BackAndPauseX": "Видео, заданное время ({0:#,###,##0} мс) назад и пауза, 4", + "videoCustom4ForwardAndPauseX": "Видео, заданное время ({0:#,###,##0} мс) вперед и пауза, 4", "videoExtension": "Расширение видеофайла", "videoFile": "Видеофайл", "videoFileGenerated": "Видеофайл сгенерирован", "videoFileGeneratedX": "Видеофайл сгенерирован: \"{0}\"", + "videoFilesGenerated": "Видеофайлы созданы", + "xVideosGenerated": "Создано видео: {0}", + "xOfYVideosGenerated": "Создано видео: {0} из {1}", + "savedInX": "Сохранено в {0}", + "savedInXFolders": "Сохранено в папках: {0}", "videoFiles": "Видеофайлы", "videoAndAudioFiles": "Видео- и аудиофайлы", "videoOffset": "Смещение видео", @@ -788,6 +831,9 @@ "dashScopeSttRegion": "Область", "dashScopeSttEnableWords": "Временные метки на уровне слов", "dashScopeSttRegionKeyHint": "Примечание: API-ключи Alibaba Cloud Model Studio привязаны к региону — убедитесь, что выбранный регион совпадает с регионом, где был создан API-ключ (Китай или международный регион).", + "googleCloudSttProjectId": "ID проекта (только для Application Default Credentials)", + "googleCloudSttBucketName": "Бакет хранилища (пусто = автоматически)", + "googleCloudSttDynamicBatching": "Динамическая пакетная обработка (примерно в 5 раз дешевле, без гарантии времени ответа)", "onlineSttApiKeyMissing": "Для доступа к API требуется ключ. Пожалуйста, введите свой ключ API и повторите попытку.", "openAiCompatibleSttAutoTranscribeOnAudioSelection": "Автоматическая транскрипция выбранной новой формы волны с помощью преобразования речи в текст", "openAiCompatibleSttStream": "Потоковый ответ", @@ -852,20 +898,21 @@ "applyDurationLimits": "Применить ограничения по продолжительности...", "batchConvert": "_Пакетное преобразование...", "beautifyTimeCodes": "Украсить тайм-коды...", + "improveTimeCodes": "Улучшить тайм-коды (принудительное выравнивание)...", "bridgeGaps": "Преодолеть пробелы...", "applyMinGap": "Установить миним. промежуток между субтитрами...", "changeCasing": "_Изменить регистр...", "changeFormatting": "Изменить форматирование...", "aiReview": "Обзор ИИ...", - "fixCommonErrors": "_Исправление распространенных ошибок...", + "fixCommonErrors": "_Исправить типичные ошибки...", "checkAndFixNetflixErrors": "Проверить и исправить ошибки Netfli_x...", "listErrors": "Ошибки списка L...", "makeEmptyTranslationFromCurrentSubtitle": "Создать пустой перевод из текущего субтитра", "mergeLinesWithSameText": "_Объединить строки с одинаковым текстом...", - "mergeLinesWithSameTimeCodes": "Объединение строк с одинаковыми временами...", + "mergeLinesWithSameTimeCodes": "Объединить строки с одинаковыми тайм-кодами...", "splitBreakLongLines": "Разделение/перебалансировка длинных линий...", "evenlyDistributeLines": "Равномерно распределить строки (по CPS)", - "mergeShortLines": "Объединить короткие очереди...", + "mergeShortLines": "Объединить короткие строки...", "mergeContinuationLines": "Объединить строки продолжения...", "snapAllTimesToFrames": "Привязать все время к кадрам", "renumber": "Перенумеровать...", @@ -990,6 +1037,9 @@ "selectCurrentLineWhilePlayingHint": "Выбирать текущий субтитр во время воспроизведения {0}", "videoPosition": "Позиция видео {0}", "videoPositionTextBox": "Текстовое поле «Позиция видео» {0}", + "toolbarLineBreakX": "Разрыв {0} (дальше панель инструментов продолжается с нового ряда)", + "initialText": "Исходный текст выбранной строки", + "initialTextHint": "Текст выбранной строки в том виде, в каком он был в момент выбора", "hideWaveformToolbar": "Скрыть панель инструментов {0}", "resetZoomAndSpeed": "Сбросить масштаб и скорость воспроизведения {0}", "removeBlankLines": "Удалить пустые строки {0}", @@ -1003,6 +1053,15 @@ "textPlayHint": "Воспроизвести текущие субтитры и остановить в конце {0}", "textPauseHint": "Приостановить воспроизведение {0}", "textNextHint": "Воспроизвести следующие субтитры и остановить в конце {0}", + "moveSelectedLines": "Сдвигать выбранные строки (кнопки назад/вперед)", + "moveSelectedLinesAndFollowing": "Сдвигать выбранные и все последующие строки (кнопки назад/вперед)", + "moveAllLines": "Сдвигать все строки (кнопки назад/вперед)", + "moveSelectedLinesBackHint": "Сдвинуть выбранные строки на {0} мс назад {1}", + "moveSelectedLinesForwardHint": "Сдвинуть выбранные строки на {0} мс вперед {1}", + "moveSelectedLinesAndFollowingBackHint": "Сдвинуть выбранные и все последующие строки на {0} мс назад {1}", + "moveSelectedLinesAndFollowingForwardHint": "Сдвинуть выбранные и все последующие строки на {0} мс вперед {1}", + "moveAllLinesBackHint": "Сдвинуть все строки на {0} мс назад {1}", + "moveAllLinesForwardHint": "Сдвинуть все строки на {0} мс вперед {1}", "guessStartNoLineSelected": "Предполагаемое начало: строка не выбрана", "guessStartTimeCodesLocked": "Предполагаемое начало: временные коды заблокированы", "guessStartNoWaveform": "Предполагаемое начало: отсутствует осциллограмма", @@ -1062,11 +1121,14 @@ "numberOfLinesEvenlyDistributedX": "Равномерно распределенные {0} строки", "oneLineCopiedFromOriginal": "Одна строка скопирована из оригинальных субтитров", "oneLineCopiedToOriginal": "Одна строка скопирована в оригинальный субтитр", + "onlineOnlyVideo": "Видео только в облаке", + "onlineOnlyVideoXSizeYDownloadAndOpen": "Видеофайл \"{0}\" ({1}) хранится только в облаке (например, в Dropbox или iCloud Drive), и перед открытием его нужно скачать.\n\nСкачать и открыть его сейчас?", "oneLineMerged": "Одна строка объединена", "oneLineSwitched": "Одна линия переключилась", "overwriteExistingCells": "Перезаписать существующие ячейки", "overwriteOrShiftCellsDown": "Перезаписать/сдвинуть ячейки вниз", "parsingMatroskaFile": "Анализ файла Matroska...", + "readingDvdSubtitles": "Чтение субтитров DVD...", "pasteFromClipboardDotDotDot": "Вставить из буфера обмена...", "redoPerformed": "Повтор выполнен", "redoPerformedXActionLeft": "Повтор выполнен (осталось действий: {0})", @@ -1106,12 +1168,14 @@ "trimmedXLines": "Урезаны {0} строки субтитров", "pastedXLinesOverSelectedLines": "Вставлено {0} строк поверх выделенных строк", "pastedXOfYLinesOverSelectedLines": "Вставлено {0} из {1} строк поверх выделенных строк — остальные не поместились в выделение", + "insertedXLinesFromClipboard": "Вставлено строк из буфера обмена: {0}", "openOriginalDifferentNumberOfSubtitlesXY": "Исходный файл субтитров содержит меньше субтитров, чем текущий файл субтитров.\n\n• Исходные субтитры: {0}\n• Текущие субтитры: {1}", "originalTextReadOnly": "Оригинальный текст (только для чтения)", "originalTextEditMode": "Оригинальный текст (режим редактирования)", "originalIsReadOnlyNotSaved": "Оригинальный подзаголовок является ссылкой только для чтения и не был сохранен", "originalIsEmptyNotSaved": "В оригинальном субтитре нет текста, и он не был сохранён", "originalIsReadOnlyReference": "Оригинальный подзаголовок доступен только для чтения", + "closeTranslationQuestionXY": "Закрыть перевод \"{0}\"?\n\nРедактируемыми станут исходные субтитры \"{1}\". Сам файл перевода не изменится.", "allowEditOfOriginalSubtitle": "Разрешить редактирование оригинального субтитра", "showAllOriginalLinesX": "Показать все исходные строки ({0} не имеют здесь совпадений)", "showAllOriginalLinesHint": "Строки, для которых не найдено совпадение, отображаются в виде дополнительных строк, чтобы вы могли увидеть, чего не хватает в переводе.", @@ -1146,6 +1210,7 @@ "fileExportedInFormatXToY": "Файл экспортирован в формате {0} в {1}", "fileExportedInFormatXToFileY": "Файл экспортирован в формате \"{0}\" в файл \"{1}\"", "fixedXLines": "Исправлено {0} строк", + "toolXChangedYLines": "{0}: изменено строк: {1}", "transcriptionCompletedWithXLines": "Транскрипция завершена, осталось {0} строк", "openSubtitleFileX": "Открыть файл субтитров \"{0}\"?", "replacedXOccurrences": "Заменено {0} вхождений", @@ -1412,7 +1477,7 @@ "deleteAll": "Удалить все" }, "propertiesDCinema": { - "title": "D-Cinema SMPTE Properties", + "title": "Свойства D-Cinema SMPTE", "generateIdAuto": "Сгенерировать новый идентификатор при сохранении", "subtitleId": "Идентификатор субтитра", "generateId": "Сгенерировать ID", @@ -2405,9 +2470,9 @@ "generationFolderDescription": "Папка для аудиоклипов, созданных во время выполнения программы. Оставить поле пустым, чтобы использовать системную временную папку. Для каждого запуска создается своя подпапка.", "deleteTempFiles": "Удалять сгенерированные клипы при закрытии", "deleteTempFilesDescription": "При закрытии окна преобразования текста в речь удаляет папку, в которой был создан файл запуска. Сохраненные аудио- и видеофайлы остаются без изменений. Отключить эту функцию, чтобы сохранить отдельные клипы.", - "edgeTtsRate": "Edge-TTS rate", + "edgeTtsRate": "Скорость Edge-TTS", "edgeTtsRateDescription": "Скорость речи для Edge-TTS, например, \"+50%\", \"-30%\" или \"+0%\" по умолчанию.", - "edgeTtsPitch": "Edge-TTS pitch", + "edgeTtsPitch": "Высота голоса Edge-TTS", "edgeTtsPitchDescription": "Настройка высоты тона для Edge-TTS, например, \"+10 Гц\", \"-5 Гц\" или \"+0 Гц\" по умолчанию.", "edgeTtsVolume": "Объем Edge-TTS", "edgeTtsVolumeDescription": "Регулировка громкости для Edge-TTS, например, \"+20%\", \"-10%\" или \"+0%\" по умолчанию.", @@ -2429,7 +2494,7 @@ "voicePitch": "Тон", "voiceAccent": "Акцент", "voiceInstructionClonedVoiceNote": "Изменение тембра голоса влияет только на \"стандартный\" голос — клонированный голос сохраняет свои собственные характеристики.", - "actorVoicesTitle": "TTS - Cast", + "actorVoicesTitle": "TTS - голоса актеров", "actorVoicesSubtitle": "Назначить голос TTS (и дополнительную инструкцию по созданию голоса) каждому актеру или голосу.", "actorVoicesAssignedXOfY": "{0} из {1} назначено", "actorOrVoice": "Актер / Голос", @@ -2752,7 +2817,7 @@ "advancedDotDotDot": "Подробнее...", "advancedSettings": "Расширенные настройки", "advancedSettingsSubtitle": "Параметры пакетной обработки, контекста и выборки для расширенного механизма llama.cpp", - "context": "Context", + "context": "Контекст", "synopsis": "Краткое содержание", "synopsisHint": "Дополнительное описание содержания (жанр, место действия, тон) — включается в каждый запрос", "glossary": "Глоссарий", @@ -2865,7 +2930,7 @@ "autoBreakUsePixelWidth": "Автоматический перенос по ширине пикселя", "autoBreakPreferBottomHeavy": "Автобрейк предпочитает сильное давление в нижней части", "autoBreakPreferBottomPercent": "Процент пробития нижней границы автопробоя", - "useDoNotBreakAfterList": "Используйте список того, что нельзя ломать после", + "useDoNotBreakAfterList": "Не переносить строку после слов из списка", "newEmptyDefaultMs": "Длительность новых субтитров по умолчанию (мс)", "timeCodeUpDownStepMs": "Шаг увеличения/уменьшения времени (мс)", "promptBeforeDelete": "Запрос перед удалением", @@ -3320,7 +3385,7 @@ "assaGenerateProgressBar": "ASSA Создать индикатор выполнения", "assaGenerateBackgroundBox": "ASSA Создать фоновое поле", "assaStyles": "Стили ASSA", - "assaProperties": "ASSA Properties", + "assaProperties": "Свойства ASSA", "assaAttachments": "Вложения ASSA", "recalculateDurationSelectedLines": "Пересчитать Длительность (выбранные строки)", "setDurationMaxCpsSelectedLines": "Установить продолжительность на максимальное значение CPS (выбранные строки)", From 0b369534952cae578475125d3395b00ffbd36e27 Mon Sep 17 00:00:00 2001 From: niksedk Date: Sat, 10 Oct 2026 08:02:55 +0200 Subject: [PATCH 007/306] nOCR: don't stretch small entries onto much bigger glyphs Every gate scales with the glyph, so a tiny entry with a few lines fitted the stem of a far bigger letter: on Linux CI "test" came out as "'est" (an apostrophe trained at 3x9 matched an 8x22 "t" with zero errors). When either glyph is small, the areas may now differ at most 5x. Co-Authored-By: Claude Opus 5.5 --- src/libuilogic/Ocr/NOcrDb.cs | 13 ++++++ .../Ocr/NOcrDbSmallGlyphScaleTests.cs | 43 +++++++++++++++++++ 2 files changed, 56 insertions(+) create mode 100644 tests/libuilogic/Ocr/NOcrDbSmallGlyphScaleTests.cs diff --git a/src/libuilogic/Ocr/NOcrDb.cs b/src/libuilogic/Ocr/NOcrDb.cs index bcdd518ac87..0dd65d54816 100644 --- a/src/libuilogic/Ocr/NOcrDb.cs +++ b/src/libuilogic/Ocr/NOcrDb.cs @@ -760,9 +760,22 @@ private void CacheMatch(MatchCacheKey key, MatchCacheEntry entry) // aspect ratio instead. private const int SmallGlyphAreaLimit = 150; private const double SmallGlyphMaxAspectRatio = 2.0; + private const int SmallGlyphMaxAreaRatio = 5; private static bool PassFilter(NikseBitmap2 bitmap, double heightToWidthPercent, NOcrChar oc, int topMargin, in MatchPass pass) { + // A small entry (dot, comma, apostrophe) carries only a few lines of evidence, and since + // every gate below scales with the glyph, it fits a much bigger glyph once stretched + // onto it: an apostrophe trained at 3x9 read the stem of a 8x22 "t" with zero errors. + // Allow about twice the size per axis, as between a DVD and a Blu-ray rendering. + var area = bitmap.Width * bitmap.Height; + var ocArea = oc.Width * oc.Height; + var smallArea = Math.Min(area, ocArea); + if (smallArea < SmallGlyphAreaLimit && Math.Max(area, ocArea) > smallArea * SmallGlyphMaxAreaRatio) + { + return false; + } + if (bitmap.Width * bitmap.Height < SmallGlyphAreaLimit) { // Small glyphs: absolute h/w% deltas are useless here - a dot (aspect ~100) vs a diff --git a/tests/libuilogic/Ocr/NOcrDbSmallGlyphScaleTests.cs b/tests/libuilogic/Ocr/NOcrDbSmallGlyphScaleTests.cs new file mode 100644 index 00000000000..662b0d3db51 --- /dev/null +++ b/tests/libuilogic/Ocr/NOcrDbSmallGlyphScaleTests.cs @@ -0,0 +1,43 @@ +using Nikse.SubtitleEdit.UiLogic.Ocr; +using SkiaSharp; + +namespace LibUiLogicTests.Ocr; + +/// +/// Matching scales a database entry to the glyph, so a tiny entry with a few lines (an +/// apostrophe) fitted the stem of a much bigger letter: "test" was read as "'est" on Linux CI. +/// +public class NOcrDbSmallGlyphScaleTests +{ + private static NikseBitmap2 Bar(int width, int height) + { + using var bitmap = new SKBitmap(width, height); + bitmap.Erase(SKColors.White); + return new NikseBitmap2(bitmap); + } + + private static NOcrDb DbWithApostrophe() + { + var db = new NOcrDb(Path.Combine(Path.GetTempPath(), Path.GetRandomFileName() + ".nocr")); + var apostrophe = new NOcrChar("'") { Width = 3, Height = 9, MarginTop = 0 }; + apostrophe.LinesForeground.Add(new NOcrLine(new OcrPoint(1, 0), new OcrPoint(1, 8))); + db.Add(apostrophe); + return db; + } + + [Fact] + public void SmallEntry_DoesNotMatchGlyphManyTimesItsSize() + { + var db = DbWithApostrophe(); + + Assert.Null(db.GetMatchSingle(Bar(8, 22), 0, true, 25, lastDitch: true)); + } + + [Fact] + public void SmallEntry_StillMatchesAtTwiceItsSize() + { + var db = DbWithApostrophe(); + + Assert.Equal("'", db.GetMatchSingle(Bar(6, 18), 0, true, 25)?.Text); + } +} From 27a50cf19d5ac60d21aa252dbdca253e57cc0237 Mon Sep 17 00:00:00 2001 From: niksedk Date: Sat, 10 Oct 2026 08:08:58 +0200 Subject: [PATCH 008/306] Train nOCR window: modern layout, preview, start-from database, several sizes, progress - Card layout with a header, like the newer dialogs. - Fonts: search box, "N selected" count, a "Subtitle fonts" preset and Clear. - Preview of the highlighted font rendered the way the trainer renders characters, once per style that will be trained. - Start from an existing database (default Latin) instead of always an empty one, and train several sizes at once (default "30, 40"); both measured clearly better for real subtitles. - Line segments default 100 (was 60). - Progress bar with the current font, size and character, and learned/skipped counters; settings are locked while training. Co-Authored-By: Claude Opus 5.5 --- .../Features/Ocr/NOcr/NOcrTrainViewModel.cs | 329 +++++++++++-- src/ui/Features/Ocr/NOcr/NOcrTrainWindow.cs | 446 +++++++++++++++--- .../Logic/Config/Language/Ocr/LanguageOcr.cs | 26 + src/ui/Logic/Config/SeOcr.cs | 8 +- .../Features/Ocr/NOcrTrainViewModelTests.cs | 69 +++ 5 files changed, 776 insertions(+), 102 deletions(-) create mode 100644 tests/UI/Features/Ocr/NOcrTrainViewModelTests.cs diff --git a/src/ui/Features/Ocr/NOcr/NOcrTrainViewModel.cs b/src/ui/Features/Ocr/NOcr/NOcrTrainViewModel.cs index 416b9de6a89..1a35262b193 100644 --- a/src/ui/Features/Ocr/NOcr/NOcrTrainViewModel.cs +++ b/src/ui/Features/Ocr/NOcr/NOcrTrainViewModel.cs @@ -1,5 +1,6 @@ using Avalonia.Controls; using Avalonia.Input; +using Avalonia.Media.Imaging; using Avalonia.Threading; using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; @@ -9,8 +10,12 @@ using Nikse.SubtitleEdit.Logic.Config; using Nikse.SubtitleEdit.Logic.Media; using Nikse.SubtitleEdit.UiLogic.Ocr; +using SkiaSharp; using System; +using System.Collections.Generic; using System.Collections.ObjectModel; +using System.Collections.Specialized; +using System.Globalization; using System.IO; using System.Linq; using System.Text; @@ -36,20 +41,52 @@ public NOcrTrainFontItem(string name, bool isSelected) public partial class NOcrTrainViewModel : ObservableObject { + /// + /// The usual faces of image subtitles. Training the shipped Latin.nocr on the first eight + /// (regular, bold and italic at 30 and 40 px, on top of the previous database) measured best + /// on real Blu-ray/DVD subtitles; the rest are their Linux/Windows stand-ins. + /// + public static readonly string[] SubtitleFontPreset = + { + "Arial", "Helvetica", "Verdana", "Tahoma", "Trebuchet MS", "Arial Narrow", "Helvetica Neue", "Times New Roman", + "Liberation Sans", "DejaVu Sans", "Segoe UI", + }; + + public const string PreviewText = "Subtitle Edit 123"; + private const int PreviewFontSize = 30; + public Window? Window { get; set; } + /// All installed fonts. public ObservableCollection Fonts { get; } + /// The fonts matching , shown in the list. + public ObservableCollection FilteredFonts { get; } = new(); + + /// "Start from" choices: an empty database, then the existing databases. + public ObservableCollection BaseDatabases { get; } = new(); + + [ObservableProperty] private string _fontSearchText; + [ObservableProperty] private string _selectedFontsText; + [ObservableProperty] private NOcrTrainFontItem? _highlightedFont; + [ObservableProperty] private Bitmap? _previewImage; + [ObservableProperty] private string _previewFontName; [ObservableProperty] private string _databaseName; + [ObservableProperty] private string? _selectedBaseDatabase; + [ObservableProperty] private string _fontSizes; [ObservableProperty] private string _charactersToTrain; [ObservableProperty] private string _mergedLetters; [ObservableProperty] private bool _trainBold; [ObservableProperty] private bool _trainItalic; - [ObservableProperty] private int _fontSize; [ObservableProperty] private int _numberOfSegments; [ObservableProperty] private string _statusText; [ObservableProperty] private bool _isTraining; + [ObservableProperty] private bool _isNotTraining; [ObservableProperty] private string _trainButtonText; + [ObservableProperty] private double _progressValue; + [ObservableProperty] private bool _isProgressVisible; + [ObservableProperty] private string _learnedText; + [ObservableProperty] private string _skippedText; /// Set when a database was trained and saved; the caller should refresh its database list. public string? TrainedDatabaseName { get; private set; } @@ -65,16 +102,202 @@ public NOcrTrainViewModel(IFileHelper fileHelper) var selectedFonts = (ocr.NOcrTrainFonts ?? string.Empty).Split(';', StringSplitOptions.RemoveEmptyEntries); Fonts = new ObservableCollection( FontHelper.GetSystemFonts().Select(name => new NOcrTrainFontItem(name, selectedFonts.Contains(name)))); + foreach (var font in Fonts) + { + font.PropertyChanged += (_, _) => UpdateSelectedFontsText(); + } + + Fonts.CollectionChanged += OnFontsChanged; + BaseDatabases.Add(Se.Language.Ocr.EmptyDatabase); + foreach (var name in NOcrDb.GetDatabases(Se.OcrFolder)) + { + BaseDatabases.Add(name); + } + + FontSearchText = string.Empty; + SelectedFontsText = string.Empty; + PreviewFontName = string.Empty; DatabaseName = string.Empty; + SelectedBaseDatabase = !string.IsNullOrEmpty(ocr.NOcrTrainBaseDatabase) && BaseDatabases.Contains(ocr.NOcrTrainBaseDatabase) + ? ocr.NOcrTrainBaseDatabase + : BaseDatabases[0]; + FontSizes = string.IsNullOrWhiteSpace(ocr.NOcrTrainFontSizes) ? "30, 40" : ocr.NOcrTrainFontSizes; CharactersToTrain = NOcrTrainer.DefaultTrainingCharacters; - MergedLetters = ocr.NOcrTrainMergedLetters; + MergedLetters = ocr.NOcrTrainMergedLetters ?? string.Empty; TrainBold = ocr.NOcrTrainBold; TrainItalic = ocr.NOcrTrainItalic; - FontSize = Math.Clamp(ocr.NOcrTrainFontSize, 20, 100); NumberOfSegments = Math.Clamp(ocr.NOcrTrainSegmentCount, 10, 500); StatusText = string.Empty; + LearnedText = string.Empty; + SkippedText = string.Empty; + IsNotTraining = true; TrainButtonText = Se.Language.Ocr.StartTraining; + + ApplyFontFilter(); + UpdateSelectedFontsText(); + HighlightedFont = Fonts.FirstOrDefault(f => f.IsSelected) ?? Fonts.FirstOrDefault(); + } + + private void OnFontsChanged(object? sender, NotifyCollectionChangedEventArgs e) + { + if (e.NewItems != null) + { + foreach (NOcrTrainFontItem font in e.NewItems) + { + font.PropertyChanged += (_, _) => UpdateSelectedFontsText(); + } + } + + ApplyFontFilter(); + UpdateSelectedFontsText(); + if (HighlightedFont == null || !Fonts.Contains(HighlightedFont)) + { + HighlightedFont = Fonts.FirstOrDefault(f => f.IsSelected) ?? Fonts.FirstOrDefault(); + } + } + + partial void OnFontSearchTextChanged(string value) => ApplyFontFilter(); + + partial void OnHighlightedFontChanged(NOcrTrainFontItem? value) => UpdatePreview(); + + partial void OnTrainBoldChanged(bool value) => UpdatePreview(); + + partial void OnTrainItalicChanged(bool value) => UpdatePreview(); + + partial void OnIsTrainingChanged(bool value) => IsNotTraining = !value; + + private void ApplyFontFilter() + { + var search = FontSearchText?.Trim() ?? string.Empty; + FilteredFonts.Clear(); + foreach (var font in Fonts) + { + if (search.Length == 0 || font.Name.Contains(search, StringComparison.OrdinalIgnoreCase)) + { + FilteredFonts.Add(font); + } + } + } + + private void UpdateSelectedFontsText() + { + SelectedFontsText = string.Format(Se.Language.Ocr.XFontsSelected, Fonts.Count(f => f.IsSelected)); + } + + /// + /// Renders the sample text like the trainer renders characters (white fill, black outline) + /// on a dark backdrop, once per style that will be trained. + /// + private void UpdatePreview() + { + var font = HighlightedFont; + PreviewFontName = font?.Name ?? string.Empty; + if (font == null) + { + PreviewImage = null; + return; + } + + var styles = new List<(bool Bold, bool Italic)> { (false, false) }; + if (TrainBold) + { + styles.Add((true, false)); + } + + if (TrainItalic) + { + styles.Add((false, true)); + } + + var rendered = new List(); + try + { + foreach (var (bold, italic) in styles) + { + var bitmap = NOcrTrainer.RenderCharacterImage(PreviewText, font.Name, PreviewFontSize, bold, italic); + if (bitmap != null) + { + rendered.Add(bitmap); + } + } + + if (rendered.Count == 0) + { + PreviewImage = null; + return; + } + + const int padding = 4; + using var preview = new SKBitmap(rendered.Max(b => b.Width) + padding * 2, rendered.Sum(b => b.Height) + padding * 2); + using (var canvas = new SKCanvas(preview)) + { + canvas.Clear(SKColors.Transparent); + var y = padding; + foreach (var bitmap in rendered) + { + canvas.DrawBitmap(bitmap, padding, y); + y += bitmap.Height; + } + } + + PreviewImage = preview.ToAvaloniaBitmap(); + } + catch (Exception exception) + { + Se.LogError(exception, $"nOCR train preview failed for font '{font.Name}'"); + PreviewImage = null; + } + finally + { + foreach (var bitmap in rendered) + { + bitmap.Dispose(); + } + } + } + + /// Parses "30, 40" into distinct sizes in range; anything else is ignored. + internal static List ParseFontSizes(string? text) + { + var sizes = new List(); + foreach (var part in (text ?? string.Empty).Split(new[] { ',', ';', ' ' }, StringSplitOptions.RemoveEmptyEntries)) + { + if (int.TryParse(part, NumberStyles.Integer, CultureInfo.InvariantCulture, out var size) && + size is >= 10 and <= 200 && !sizes.Contains(size)) + { + sizes.Add(size); + } + } + + return sizes; + } + + [RelayCommand] + private void SelectSubtitleFonts() + { + foreach (var font in Fonts) + { + font.IsSelected = SubtitleFontPreset.Contains(font.Name, StringComparer.OrdinalIgnoreCase); + } + + FontSearchText = string.Empty; + HighlightedFont = Fonts.FirstOrDefault(f => f.IsSelected) ?? HighlightedFont; + } + + [RelayCommand] + private void ClearFonts() + { + foreach (var font in Fonts) + { + font.IsSelected = false; + } + } + + [RelayCommand] + private void ResetCharacters() + { + CharactersToTrain = NOcrTrainer.DefaultTrainingCharacters; } [RelayCommand] @@ -94,9 +317,19 @@ await MessageBox.Show(Window!, Se.Language.Ocr.TrainNOcrDatabase, Se.Language.Oc return; } + var fontSizes = ParseFontSizes(FontSizes); + if (fontSizes.Count == 0) + { + await MessageBox.Show(Window!, Se.Language.Ocr.TrainNOcrDatabase, Se.Language.Ocr.FontSizesHint, + MessageBoxButtons.OK, MessageBoxIcon.Information); + return; + } + var databaseName = DatabaseName.Trim(); if (string.IsNullOrEmpty(databaseName) || databaseName.IndexOfAny(Path.GetInvalidFileNameChars()) >= 0) { + await MessageBox.Show(Window!, Se.Language.Ocr.TrainNOcrDatabase, Se.Language.Ocr.EnterDatabaseName, + MessageBoxButtons.OK, MessageBoxIcon.Information); return; } @@ -105,8 +338,9 @@ await MessageBox.Show(Window!, Se.Language.Ocr.TrainNOcrDatabase, Se.Language.Oc Directory.CreateDirectory(Se.OcrFolder); } + var baseDatabase = SelectedBaseDatabase == Se.Language.Ocr.EmptyDatabase ? null : SelectedBaseDatabase; var fileName = Path.Combine(Se.OcrFolder, databaseName + ".nocr"); - if (File.Exists(fileName) && TrainedDatabaseName != databaseName) + if (File.Exists(fileName) && TrainedDatabaseName != databaseName && baseDatabase != databaseName) { var answer = await MessageBox.Show(Window!, Se.Language.General.FileAlreadyExists, string.Format(Se.Language.General.FileXAlreadyExists, fileName), @@ -119,45 +353,85 @@ await MessageBox.Show(Window!, Se.Language.Ocr.TrainNOcrDatabase, Se.Language.Oc SaveSettings(fontNames); - var settings = new NOcrTrainerSettings - { - FontNames = fontNames, - FontSize = FontSize, - IncludeBold = TrainBold, - IncludeItalic = TrainItalic, - NumberOfLineSegments = NumberOfSegments, - CharactersToTrain = CharactersToTrain, - MergedLetterCombinations = MergedLetters, - }; + var characters = CharactersToTrain; + var mergedLetters = MergedLetters; + var bold = TrainBold; + var italic = TrainItalic; + var segments = NumberOfSegments; + var stepsPerSize = characters.Where(c => !char.IsWhiteSpace(c)).Distinct().Count() + + mergedLetters.Split(' ', StringSplitOptions.RemoveEmptyEntries).Distinct().Count(); + var totalSteps = Math.Max(1, fontNames.Count * fontSizes.Count * stepsPerSize); _abort = false; IsTraining = true; + IsProgressVisible = true; + ProgressValue = 0; + LearnedText = string.Empty; + SkippedText = string.Empty; TrainButtonText = Se.Language.Ocr.AbortTraining; await Task.Run(() => { try { - var db = new NOcrDb(fileName) - { - OcrCharacters = new(), - OcrCharactersExpanded = new(), - }; + var baseFileName = baseDatabase == null ? null : Path.Combine(Se.OcrFolder, baseDatabase + ".nocr"); + var db = baseFileName != null && File.Exists(baseFileName) + ? new NOcrDb(new NOcrDb(baseFileName), fileName) + : new NOcrDb(fileName) { OcrCharacters = new(), OcrCharactersExpanded = new() }; + var trainer = new NOcrTrainer(); - var learned = trainer.Train(settings, db, () => _abort, p => + var learned = 0; + var skipped = 0; + var stepsDone = 0; + foreach (var size in fontSizes) { - Dispatcher.UIThread.Post(() => + if (_abort) { - StatusText = string.Format(Se.Language.Ocr.TrainingXLearnedYSkipped, - p.CurrentFontName, p.CharactersLearned, p.CharactersSkipped); + break; + } + + var settings = new NOcrTrainerSettings + { + FontNames = fontNames, + FontSize = size, + IncludeBold = bold, + IncludeItalic = italic, + NumberOfLineSegments = segments, + CharactersToTrain = characters, + MergedLetterCombinations = mergedLetters, + }; + + var learnedBefore = learned; + var skippedBefore = skipped; + trainer.Train(settings, db, () => _abort, p => + { + stepsDone++; + learned = learnedBefore + p.CharactersLearned; + skipped = skippedBefore + p.CharactersSkipped; + var percent = Math.Min(100, stepsDone * 100.0 / totalSteps); + var learnedNow = learned; + var skippedNow = skipped; + Dispatcher.UIThread.Post(() => + { + ProgressValue = percent; + StatusText = string.Format(Se.Language.Ocr.TrainingFontXSizeYCharacterZ, p.CurrentFontName, size, p.CurrentText); + LearnedText = string.Format(Se.Language.Ocr.XLearned, learnedNow); + SkippedText = string.Format(Se.Language.Ocr.XSkipped, skippedNow); + }); }); - }); + } db.Save(); + var aborted = _abort; Dispatcher.UIThread.Post(() => { TrainedDatabaseName = databaseName; + if (!aborted) + { + ProgressValue = 100; + } + StatusText = string.Format(Se.Language.Ocr.TrainingDoneXLearned, learned); }); } @@ -187,7 +461,7 @@ private async Task ImportCharactersFromFile() return; } - var seen = new System.Collections.Generic.HashSet(); + var seen = new HashSet(); var sb = new StringBuilder(); foreach (var paragraph in subtitle.Paragraphs) { @@ -214,12 +488,13 @@ private void Done() Close(); } - private void SaveSettings(System.Collections.Generic.List fontNames) + private void SaveSettings(List fontNames) { var ocr = Se.Settings.Ocr; ocr.NOcrTrainFonts = string.Join(';', fontNames); ocr.NOcrTrainMergedLetters = MergedLetters; - ocr.NOcrTrainFontSize = FontSize; + ocr.NOcrTrainFontSizes = FontSizes; + ocr.NOcrTrainBaseDatabase = SelectedBaseDatabase == Se.Language.Ocr.EmptyDatabase ? string.Empty : SelectedBaseDatabase ?? string.Empty; ocr.NOcrTrainSegmentCount = NumberOfSegments; ocr.NOcrTrainBold = TrainBold; ocr.NOcrTrainItalic = TrainItalic; diff --git a/src/ui/Features/Ocr/NOcr/NOcrTrainWindow.cs b/src/ui/Features/Ocr/NOcr/NOcrTrainWindow.cs index 5b1db0beb0e..478d422fcdc 100644 --- a/src/ui/Features/Ocr/NOcr/NOcrTrainWindow.cs +++ b/src/ui/Features/Ocr/NOcr/NOcrTrainWindow.cs @@ -1,12 +1,15 @@ +using Avalonia; using Avalonia.Controls; using Avalonia.Controls.Primitives; using Avalonia.Controls.Templates; +using Avalonia.Input; using Avalonia.Data; using Avalonia.Data.Converters; using Avalonia.Layout; using Avalonia.Media; using Nikse.SubtitleEdit.Logic; using Nikse.SubtitleEdit.Logic.Config; +using Nikse.SubtitleEdit.Logic.ValueConverters; namespace Nikse.SubtitleEdit.Features.Ocr.NOcr; @@ -17,33 +20,130 @@ public NOcrTrainWindow(NOcrTrainViewModel vm) Title = Se.Language.Ocr.TrainNOcrDatabase.TrimEnd('.'); vm.Window = this; UiUtil.InitializeWindow(this, GetType().Name); - Width = 760; + Width = 940; SizeToContent = SizeToContent.Height; CanResize = false; WindowStartupLocation = WindowStartupLocation.CenterOwner; DataContext = vm; - var grid = new Grid + var buttonTrain = UiUtil.MakeButton(Se.Language.Ocr.StartTraining, vm.StartOrAbortTrainingCommand); + buttonTrain[!ContentControl.ContentProperty] = new Binding(nameof(vm.TrainButtonText)); + buttonTrain.Classes.Add("accent"); + var buttonDone = UiUtil.MakeButtonDone(vm.DoneCommand); + + var columns = new Grid + { + ColumnDefinitions = + { + new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) }, + new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) }, + }, + ColumnSpacing = 12, + }; + columns.Add(MakeCard(BuildFontsSection(vm)), 0, 0); + // The settings card stretches so both columns end level. + var right = new Grid { RowDefinitions = { - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, // fonts + options - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, // characters - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, // merged letters - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, // status - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, // buttons + new RowDefinition(GridLength.Auto), + new RowDefinition(GridLength.Star), }, - ColumnDefinitions = + RowSpacing = 12, + }; + right.Add(MakeCard(BuildPreviewSection(vm)), 0, 0); + right.Add(MakeCard(BuildSettingsSection(vm)), 1, 0); + columns.Add(right, 0, 1); + + var stack = new StackPanel + { + Orientation = Orientation.Vertical, + Spacing = 12, + Children = { - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) }, - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Auto) }, + BuildHeader(), + columns, + MakeCard(BuildCharactersSection(vm)), + BuildProgressSection(vm), + UiUtil.MakeButtonBar(buttonTrain, buttonDone), + }, + }; + + Content = new Border + { + Child = stack, + Padding = UiUtil.MakeWindowMargin(), + }; + + UiUtil.FocusOnFirstActivation(this, () => + { + buttonTrain.Focus(); // hack to make OnKeyDown work + }); + KeyDown += (_, e) => vm.KeyDown(e); + Closing += (_, _) => vm.OnClosing(); + } + + private static Control BuildHeader() + { + return new StackPanel + { + Orientation = Orientation.Vertical, + Children = + { + new TextBlock + { + Text = Se.Language.Ocr.TrainNOcrDatabase.TrimEnd('.'), + FontSize = UiUtil.ScaledFontSize(18), + FontWeight = FontWeight.SemiBold, + }, + new TextBlock + { + Text = Se.Language.Ocr.TrainNOcrSubtitle, + FontSize = UiUtil.ScaledFontSize(12), + Opacity = 0.75, + TextWrapping = TextWrapping.Wrap, + Margin = new Thickness(0, 2, 0, 0), + }, + }, + }; + } + + private static Control BuildFontsSection(NOcrTrainViewModel vm) + { + var title = MakeSectionTitle(Se.Language.General.Fonts); + var count = new TextBlock + { + FontSize = UiUtil.ScaledFontSize(12), + Opacity = 0.75, + VerticalAlignment = VerticalAlignment.Center, + HorizontalAlignment = HorizontalAlignment.Right, + [!TextBlock.TextProperty] = new Binding(nameof(vm.SelectedFontsText)), + }; + var titleRow = new Grid + { + ColumnDefinitions = { new ColumnDefinition(GridLength.Star), new ColumnDefinition(GridLength.Auto) }, + }; + titleRow.Add(title, 0, 0); + titleRow.Add(count, 0, 1); + + var search = new TextBox + { + PlaceholderText = Se.Language.General.Search, + [!TextBox.TextProperty] = new Binding(nameof(vm.FontSearchText)) { Mode = BindingMode.TwoWay }, + [!InputElement.IsEnabledProperty] = new Binding(nameof(vm.IsNotTraining)), + }; + + var buttons = new StackPanel + { + Orientation = Orientation.Horizontal, + Spacing = 6, + Children = + { + UiUtil.MakeButton(Se.Language.Ocr.SubtitleFonts, vm.SelectSubtitleFontsCommand).WithMargin(0), + UiUtil.MakeButton(Se.Language.General.Clear, vm.ClearFontsCommand).WithMargin(0), }, - Margin = UiUtil.MakeWindowMargin(), - ColumnSpacing = 10, - RowSpacing = 10, }; - // Fonts (checkbox list, each rendered in its own typeface). // The template must bind everything (no use of the build parameter): the virtualized // ListBox recycles containers with a null item, and reuses them for other items where // static values would go stale. Uniform row height keeps scroll extent estimation sane @@ -52,11 +152,12 @@ public NOcrTrainWindow(NOcrTrainViewModel vm) string.IsNullOrWhiteSpace(name) ? FontFamily.Default : FontFamilyHelper.Make(name)); var fontsListBox = new ListBox { - Height = 260, - ItemsSource = vm.Fonts, + Height = 330, + ItemsSource = vm.FilteredFonts, + [!SelectingItemsControl.SelectedItemProperty] = new Binding(nameof(vm.HighlightedFont)) { Mode = BindingMode.TwoWay }, ItemTemplate = new FuncDataTemplate((_, _) => new CheckBox { - [!CheckBox.IsCheckedProperty] = new Binding(nameof(NOcrTrainFontItem.IsSelected)), + [!ToggleButton.IsCheckedProperty] = new Binding(nameof(NOcrTrainFontItem.IsSelected)), [!ContentControl.ContentProperty] = new Binding(nameof(NOcrTrainFontItem.Name)), [!TemplatedControl.FontFamilyProperty] = new Binding(nameof(NOcrTrainFontItem.Name)) { @@ -67,67 +168,268 @@ public NOcrTrainWindow(NOcrTrainViewModel vm) ClipToBounds = true, }, supportsRecycling: true), }; - var fontsPanel = new StackPanel { Orientation = Orientation.Vertical, Spacing = 4 }; - fontsPanel.Children.Add(UiUtil.MakeLabel(Se.Language.General.Fonts)); - fontsPanel.Children.Add(UiUtil.MakeBorderForControlNoPadding(fontsListBox)); - var fontsGroup = fontsPanel; - - // Options - var optionsPanel = new StackPanel { Orientation = Orientation.Vertical, Spacing = 6 }; - optionsPanel.Children.Add(UiUtil.MakeLabel(Se.Language.General.Name)); - optionsPanel.Children.Add(UiUtil.MakeTextBox(220, vm, nameof(vm.DatabaseName))); - optionsPanel.Children.Add(UiUtil.MakeLabel(Se.Language.General.FontSize)); - optionsPanel.Children.Add(UiUtil.MakeNumericUpDownInt(20, 100, 30, 120, vm, nameof(vm.FontSize))); - optionsPanel.Children.Add(UiUtil.MakeLabel(Se.Language.Ocr.NumberOfLineSegments)); - optionsPanel.Children.Add(UiUtil.MakeNumericUpDownInt(10, 500, 60, 120, vm, nameof(vm.NumberOfSegments))); - optionsPanel.Children.Add(UiUtil.MakeCheckBox(Se.Language.Ocr.AlsoTrainBold, vm, nameof(vm.TrainBold))); - optionsPanel.Children.Add(UiUtil.MakeCheckBox(Se.Language.Ocr.AlsoTrainItalic, vm, nameof(vm.TrainItalic))); - var optionsOuterPanel = new StackPanel { Orientation = Orientation.Vertical, Spacing = 4 }; - optionsOuterPanel.Children.Add(UiUtil.MakeLabel(Se.Language.Ocr.TrainingOptions)); - optionsOuterPanel.Children.Add(UiUtil.MakeBorderForControl(optionsPanel)); - var optionsGroup = optionsOuterPanel; - - // Characters to train + import from subtitle file - var charactersPanel = new StackPanel { Orientation = Orientation.Vertical, Spacing = 6 }; - var charactersTextBox = UiUtil.MakeTextBox(710, vm, nameof(vm.CharactersToTrain)); + + return new StackPanel + { + Orientation = Orientation.Vertical, + Spacing = 8, + Children = + { + titleRow, + search, + buttons, + UiUtil.MakeBorderForControlNoPadding(fontsListBox), + }, + }; + } + + private static Control BuildPreviewSection(NOcrTrainViewModel vm) + { + var fontName = new TextBlock + { + FontSize = UiUtil.ScaledFontSize(12), + Opacity = 0.75, + VerticalAlignment = VerticalAlignment.Center, + HorizontalAlignment = HorizontalAlignment.Right, + [!TextBlock.TextProperty] = new Binding(nameof(vm.PreviewFontName)), + }; + var titleRow = new Grid + { + ColumnDefinitions = { new ColumnDefinition(GridLength.Star), new ColumnDefinition(GridLength.Auto) }, + }; + titleRow.Add(MakeSectionTitle(Se.Language.General.Preview), 0, 0); + titleRow.Add(fontName, 0, 1); + + var image = new Image + { + Stretch = Stretch.Uniform, + StretchDirection = StretchDirection.DownOnly, + HorizontalAlignment = HorizontalAlignment.Left, + MaxHeight = 150, + [!Image.SourceProperty] = new Binding(nameof(vm.PreviewImage)), + }; + var imageBorder = new Border + { + Child = image, + Background = new SolidColorBrush(Color.FromRgb(0x26, 0x2B, 0x33)), + CornerRadius = new CornerRadius(4), + ClipToBounds = true, + MinHeight = 60, + }; + + return new StackPanel + { + Orientation = Orientation.Vertical, + Spacing = 8, + Children = { titleRow, imageBorder }, + }; + } + + private static Control BuildSettingsSection(NOcrTrainViewModel vm) + { + var grid = new Grid + { + ColumnDefinitions = + { + new ColumnDefinition(GridLength.Auto), + new ColumnDefinition(GridLength.Star), + }, + ColumnSpacing = 12, + RowSpacing = 8, + }; + for (var i = 0; i < 6; i++) + { + grid.RowDefinitions.Add(new RowDefinition(GridLength.Auto)); + } + + var nameBox = UiUtil.MakeTextBox(220, vm, nameof(vm.DatabaseName)); + nameBox.HorizontalAlignment = HorizontalAlignment.Stretch; + nameBox.Width = double.NaN; + AddRow(grid, 0, Se.Language.General.Name, nameBox); + + var baseCombo = UiUtil.MakeComboBox(vm.BaseDatabases, vm, nameof(vm.SelectedBaseDatabase)); + baseCombo.HorizontalAlignment = HorizontalAlignment.Stretch; + SetHint(baseCombo, Se.Language.Ocr.StartFromDatabaseHint); + AddRow(grid, 1, Se.Language.Ocr.StartFromDatabase, baseCombo); + + var sizesBox = UiUtil.MakeTextBox(220, vm, nameof(vm.FontSizes)); + sizesBox.HorizontalAlignment = HorizontalAlignment.Stretch; + sizesBox.Width = double.NaN; + SetHint(sizesBox, Se.Language.Ocr.FontSizesHint); + AddRow(grid, 2, Se.Language.Ocr.FontSizes, sizesBox); + + var styles = new StackPanel + { + Orientation = Orientation.Horizontal, + Spacing = 16, + Children = + { + UiUtil.MakeCheckBox(Se.Language.General.Bold, vm, nameof(vm.TrainBold)), + UiUtil.MakeCheckBox(Se.Language.General.Italic, vm, nameof(vm.TrainItalic)), + }, + }; + AddRow(grid, 3, Se.Language.General.Styles, styles); + + var segments = UiUtil.MakeNumericUpDownInt(10, 500, 100, 130, vm, nameof(vm.NumberOfSegments)); + SetHint(segments, Se.Language.Ocr.NumberOfLineSegmentsHint); + AddRow(grid, 4, Se.Language.Ocr.NumberOfLineSegments, segments); + + return new StackPanel + { + Orientation = Orientation.Vertical, + Spacing = 10, + Children = { MakeSectionTitle(Se.Language.Ocr.TrainingOptions), grid }, + [!InputElement.IsEnabledProperty] = new Binding(nameof(vm.IsNotTraining)), + }; + } + + private static Control BuildCharactersSection(NOcrTrainViewModel vm) + { + var charactersTextBox = UiUtil.MakeTextBox(200, vm, nameof(vm.CharactersToTrain)); + charactersTextBox.Width = double.NaN; + charactersTextBox.HorizontalAlignment = HorizontalAlignment.Stretch; charactersTextBox.TextWrapping = TextWrapping.Wrap; charactersTextBox.AcceptsReturn = false; - charactersTextBox.Height = 60; - charactersPanel.Children.Add(charactersTextBox); - charactersPanel.Children.Add(UiUtil.MakeButton(Se.Language.Ocr.ImportCharactersFromSubtitleFile, vm.ImportCharactersFromFileCommand)); - var charactersOuterPanel = new StackPanel { Orientation = Orientation.Vertical, Spacing = 4 }; - charactersOuterPanel.Children.Add(UiUtil.MakeLabel(Se.Language.Ocr.CharactersToTrain)); - charactersOuterPanel.Children.Add(charactersPanel); - var charactersGroup = charactersOuterPanel; - - // Merged letter combinations - var mergedTextBox = UiUtil.MakeTextBox(710, vm, nameof(vm.MergedLetters)); - var mergedPanel = new StackPanel { Orientation = Orientation.Vertical, Spacing = 4 }; - mergedPanel.Children.Add(UiUtil.MakeLabel(Se.Language.Ocr.LetterCombinationsToTrain)); - mergedPanel.Children.Add(mergedTextBox); - var mergedGroup = mergedPanel; - - var labelStatus = UiUtil.MakeLabel(string.Empty).WithBindText(vm, nameof(vm.StatusText)); + charactersTextBox.Height = 56; - var buttonTrain = UiUtil.MakeButton(Se.Language.Ocr.StartTraining, vm.StartOrAbortTrainingCommand); - buttonTrain[!ContentControl.ContentProperty] = new Binding(nameof(vm.TrainButtonText)); - var buttonDone = UiUtil.MakeButtonDone(vm.DoneCommand); - var buttonBar = UiUtil.MakeButtonBar(buttonTrain, buttonDone); + var buttons = new StackPanel + { + Orientation = Orientation.Horizontal, + Spacing = 6, + Children = + { + UiUtil.MakeButton(Se.Language.General.Reset, vm.ResetCharactersCommand).WithMargin(0), + UiUtil.MakeButton(Se.Language.Ocr.ImportCharactersFromSubtitleFile, vm.ImportCharactersFromFileCommand).WithMargin(0), + }, + }; - grid.Add(fontsGroup, 0, 0); - grid.Add(optionsGroup, 0, 1); - grid.Add(charactersGroup, 1, 0, 1, 2); - grid.Add(mergedGroup, 2, 0, 1, 2); - grid.Add(labelStatus, 3, 0, 1, 2); - grid.Add(buttonBar, 4, 0, 1, 2); + var mergedTextBox = UiUtil.MakeTextBox(200, vm, nameof(vm.MergedLetters)); + mergedTextBox.Width = double.NaN; + mergedTextBox.HorizontalAlignment = HorizontalAlignment.Stretch; + mergedTextBox.PlaceholderText = "fi ff fl rn"; - Content = grid; + return new StackPanel + { + Orientation = Orientation.Vertical, + Spacing = 8, + Children = + { + MakeSectionTitle(Se.Language.Ocr.CharactersToTrain), + charactersTextBox, + buttons, + MakeSmallLabel(Se.Language.Ocr.LetterCombinationsToTrain), + mergedTextBox, + }, + [!InputElement.IsEnabledProperty] = new Binding(nameof(vm.IsNotTraining)), + }; + } - UiUtil.FocusOnFirstActivation(this, () => + private static Control BuildProgressSection(NOcrTrainViewModel vm) + { + var progressBar = new ProgressBar { - buttonTrain.Focus(); // hack to make OnKeyDown work - }); - KeyDown += (_, e) => vm.KeyDown(e); - Closing += (_, _) => vm.OnClosing(); + Minimum = 0, + Maximum = 100, + Height = 6, + MinHeight = 6, + [!RangeBase.ValueProperty] = new Binding(nameof(vm.ProgressValue)), + [!Visual.IsVisibleProperty] = new Binding(nameof(vm.IsProgressVisible)), + }; + + var status = new TextBlock + { + VerticalAlignment = VerticalAlignment.Center, + TextTrimming = TextTrimming.CharacterEllipsis, + [!TextBlock.TextProperty] = new Binding(nameof(vm.StatusText)), + }; + var learned = MakeBadge(nameof(vm.LearnedText), Color.FromRgb(0x3F, 0xB9, 0x50)); + var skipped = MakeBadge(nameof(vm.SkippedText), Color.FromRgb(0x8B, 0x94, 0x9E)); + var statusRow = new Grid + { + ColumnDefinitions = + { + new ColumnDefinition(GridLength.Star), + new ColumnDefinition(GridLength.Auto), + new ColumnDefinition(GridLength.Auto), + }, + ColumnSpacing = 8, + }; + statusRow.Add(status, 0, 0); + statusRow.Add(learned, 0, 1); + statusRow.Add(skipped, 0, 2); + + return new StackPanel + { + Orientation = Orientation.Vertical, + Spacing = 6, + Children = { progressBar, statusRow }, + }; + } + + private static Control MakeBadge(string textPath, Color color) + { + var text = new TextBlock + { + FontSize = UiUtil.ScaledFontSize(12), + Foreground = new SolidColorBrush(color), + [!TextBlock.TextProperty] = new Binding(textPath), + }; + return new Border + { + Child = text, + Padding = new Thickness(8, 2), + CornerRadius = new CornerRadius(10), + BorderThickness = new Thickness(1), + BorderBrush = new SolidColorBrush(Color.FromArgb(0x80, color.R, color.G, color.B)), + [!Visual.IsVisibleProperty] = new Binding(textPath) { Converter = StringConverters.IsNotNullOrEmpty }, + }; + } + + private static void AddRow(Grid grid, int row, string label, Control control) + { + var textBlock = MakeSmallLabel(label); + textBlock.VerticalAlignment = VerticalAlignment.Center; + grid.Add(textBlock, row, 0); + grid.Add(control, row, 1); + } + + private static TextBlock MakeSectionTitle(string text) + { + return new TextBlock + { + Text = text, + FontSize = UiUtil.ScaledFontSize(14), + FontWeight = FontWeight.SemiBold, + VerticalAlignment = VerticalAlignment.Center, + }; + } + + private static TextBlock MakeSmallLabel(string text) + { + return new TextBlock + { + Text = text, + Opacity = 0.85, + }; + } + + private static void SetHint(Control control, string hint) + { + if (Se.Settings.Appearance.ShowHints) + { + ToolTip.SetTip(control, hint); + } + } + + private static Border MakeCard(Control child) + { + return new Border + { + Child = child, + Padding = new Thickness(14), + CornerRadius = new CornerRadius(6), + BorderThickness = new Thickness(1), + BorderBrush = new SolidColorBrush(Color.FromArgb(0x40, 0x80, 0x80, 0x80)), + }; } } diff --git a/src/ui/Logic/Config/Language/Ocr/LanguageOcr.cs b/src/ui/Logic/Config/Language/Ocr/LanguageOcr.cs index edc9f60ef17..1661857e6f3 100644 --- a/src/ui/Logic/Config/Language/Ocr/LanguageOcr.cs +++ b/src/ui/Logic/Config/Language/Ocr/LanguageOcr.cs @@ -149,6 +149,19 @@ public class LanguageOcr public string StartTraining { get; set; } public string AbortTraining { get; set; } public string TrainingOptions { get; set; } + public string TrainNOcrSubtitle { get; set; } + public string XFontsSelected { get; set; } + public string SubtitleFonts { get; set; } + public string EmptyDatabase { get; set; } + public string StartFromDatabase { get; set; } + public string StartFromDatabaseHint { get; set; } + public string FontSizes { get; set; } + public string FontSizesHint { get; set; } + public string NumberOfLineSegmentsHint { get; set; } + public string EnterDatabaseName { get; set; } + public string TrainingFontXSizeYCharacterZ { get; set; } + public string XLearned { get; set; } + public string XSkipped { get; set; } public string CharactersToTrain { get; set; } public string LetterCombinationsToTrain { get; set; } public string ImportCharactersFromSubtitleFile { get; set; } @@ -312,6 +325,19 @@ public LanguageOcr() StartTraining = "Start training"; AbortTraining = "Abort training"; TrainingOptions = "Training options"; + TrainNOcrSubtitle = "Renders characters in installed fonts and teaches nOCR their shapes. Pick the fonts your subtitles use."; + XFontsSelected = "{0:#,##0} selected"; + SubtitleFonts = "Subtitle fonts"; + EmptyDatabase = "Empty database"; + StartFromDatabase = "Start from"; + StartFromDatabaseHint = "Add the trained characters to a copy of an existing database (recommended) or start from an empty one"; + FontSizes = "Font sizes"; + FontSizesHint = "Font sizes in pixels to render at, separated by commas, e.g. \"30, 40\""; + NumberOfLineSegmentsHint = "Lines checked per character - more is stricter but slower"; + EnterDatabaseName = "Enter a name for the database"; + TrainingFontXSizeYCharacterZ = "Training \"{0}\" at {1} px: {2}"; + XLearned = "{0:#,##0} learned"; + XSkipped = "{0:#,##0} skipped"; CharactersToTrain = "Characters to train"; LetterCombinationsToTrain = "Letter combinations that might be split to one image"; ImportCharactersFromSubtitleFile = "Import characters from subtitle file..."; diff --git a/src/ui/Logic/Config/SeOcr.cs b/src/ui/Logic/Config/SeOcr.cs index 6615ec6b768..6200fb1a142 100644 --- a/src/ui/Logic/Config/SeOcr.cs +++ b/src/ui/Logic/Config/SeOcr.cs @@ -86,7 +86,8 @@ public class SeOcr public bool UseWordSplitList { get; set; } public string NOcrTrainFonts { get; set; } public string NOcrTrainMergedLetters { get; set; } - public int NOcrTrainFontSize { get; set; } + public string NOcrTrainFontSizes { get; set; } + public string NOcrTrainBaseDatabase { get; set; } public int NOcrTrainSegmentCount { get; set; } public bool NOcrTrainBold { get; set; } public bool NOcrTrainItalic { get; set; } @@ -165,8 +166,9 @@ public SeOcr() NOcrTrainFonts = string.Empty; NOcrTrainMergedLetters = string.Empty; - NOcrTrainFontSize = 30; - NOcrTrainSegmentCount = 60; + NOcrTrainFontSizes = "30, 40"; + NOcrTrainBaseDatabase = "Latin"; + NOcrTrainSegmentCount = 100; VobSubUseCustomColors = false; VobSubColorBackground = "#00000000"; diff --git a/tests/UI/Features/Ocr/NOcrTrainViewModelTests.cs b/tests/UI/Features/Ocr/NOcrTrainViewModelTests.cs new file mode 100644 index 00000000000..ee63dc78cba --- /dev/null +++ b/tests/UI/Features/Ocr/NOcrTrainViewModelTests.cs @@ -0,0 +1,69 @@ +using Avalonia.Headless.XUnit; +using Microsoft.Extensions.DependencyInjection; +using Nikse.SubtitleEdit; +using Nikse.SubtitleEdit.Features.Ocr.NOcr; +using System.Collections.Generic; +using System.Linq; + +namespace UITests.Features.Ocr; + +public class NOcrTrainViewModelTests +{ + private static NOcrTrainViewModel MakeViewModel(params string[] fonts) + { + var services = new ServiceCollection(); + services.AddSubtitleEditServices(); + var vm = services.BuildServiceProvider().GetRequiredService(); + vm.Fonts.Clear(); + foreach (var font in fonts) + { + vm.Fonts.Add(new NOcrTrainFontItem(font, false)); + } + + return vm; + } + + [Theory] + [InlineData("30, 40", new[] { 30, 40 })] + [InlineData("40;30 40", new[] { 40, 30 })] + [InlineData("abc, 5, 300, 24", new[] { 24 })] + [InlineData("", new int[0])] + public void ParseFontSizes_KeepsDistinctSizesInRange(string text, int[] expected) + { + Assert.Equal(expected, NOcrTrainViewModel.ParseFontSizes(text)); + } + + [AvaloniaFact] + public void FontSearch_FiltersTheShownFonts() + { + var vm = MakeViewModel("Arial", "Arial Narrow", "Verdana"); + + vm.FontSearchText = "arial"; + + Assert.Equal(new List { "Arial", "Arial Narrow" }, vm.FilteredFonts.Select(f => f.Name).ToList()); + } + + [AvaloniaFact] + public void SubtitleFonts_SelectsOnlyThePreset_AndCountsThem() + { + var vm = MakeViewModel("Arial", "Comic Sans MS", "Verdana", "Zapfino"); + vm.Fonts[1].IsSelected = true; + + vm.SelectSubtitleFontsCommand.Execute(null); + + Assert.Equal(new List { "Arial", "Verdana" }, vm.Fonts.Where(f => f.IsSelected).Select(f => f.Name).ToList()); + Assert.StartsWith("2", vm.SelectedFontsText); + } + + [AvaloniaFact] + public void ClearFonts_UnselectsAll() + { + var vm = MakeViewModel("Arial", "Verdana"); + vm.Fonts[0].IsSelected = true; + + vm.ClearFontsCommand.Execute(null); + + Assert.DoesNotContain(vm.Fonts, f => f.IsSelected); + Assert.StartsWith("0", vm.SelectedFontsText); + } +} From 3bdfd5f3e5d020a52ec29f881d1a7680b3390a8e Mon Sep 17 00:00:00 2001 From: niksedk Date: Sat, 10 Oct 2026 08:18:22 +0200 Subject: [PATCH 009/306] Train nOCR window: icons, section badges, style toggles, play/stop button Co-Authored-By: Claude Opus 5.5 --- .../Features/Ocr/NOcr/NOcrTrainViewModel.cs | 4 + src/ui/Features/Ocr/NOcr/NOcrTrainWindow.cs | 299 ++++++++++++++---- src/ui/Logic/IconNames.cs | 4 + 3 files changed, 242 insertions(+), 65 deletions(-) diff --git a/src/ui/Features/Ocr/NOcr/NOcrTrainViewModel.cs b/src/ui/Features/Ocr/NOcr/NOcrTrainViewModel.cs index 1a35262b193..9bf8f9a2667 100644 --- a/src/ui/Features/Ocr/NOcr/NOcrTrainViewModel.cs +++ b/src/ui/Features/Ocr/NOcr/NOcrTrainViewModel.cs @@ -83,6 +83,7 @@ public partial class NOcrTrainViewModel : ObservableObject [ObservableProperty] private bool _isTraining; [ObservableProperty] private bool _isNotTraining; [ObservableProperty] private string _trainButtonText; + [ObservableProperty] private string _trainButtonIcon; [ObservableProperty] private double _progressValue; [ObservableProperty] private bool _isProgressVisible; [ObservableProperty] private string _learnedText; @@ -133,6 +134,7 @@ public NOcrTrainViewModel(IFileHelper fileHelper) SkippedText = string.Empty; IsNotTraining = true; TrainButtonText = Se.Language.Ocr.StartTraining; + TrainButtonIcon = IconNames.Play; ApplyFontFilter(); UpdateSelectedFontsText(); @@ -369,6 +371,7 @@ await MessageBox.Show(Window!, Se.Language.Ocr.TrainNOcrDatabase, Se.Language.Oc LearnedText = string.Empty; SkippedText = string.Empty; TrainButtonText = Se.Language.Ocr.AbortTraining; + TrainButtonIcon = IconNames.Stop; await Task.Run(() => { @@ -444,6 +447,7 @@ await Task.Run(() => IsTraining = false; TrainButtonText = Se.Language.Ocr.StartTraining; + TrainButtonIcon = IconNames.Play; } [RelayCommand] diff --git a/src/ui/Features/Ocr/NOcr/NOcrTrainWindow.cs b/src/ui/Features/Ocr/NOcr/NOcrTrainWindow.cs index 478d422fcdc..0a46befa271 100644 --- a/src/ui/Features/Ocr/NOcr/NOcrTrainWindow.cs +++ b/src/ui/Features/Ocr/NOcr/NOcrTrainWindow.cs @@ -1,34 +1,65 @@ using Avalonia; +using Avalonia.Automation; using Avalonia.Controls; using Avalonia.Controls.Primitives; using Avalonia.Controls.Templates; -using Avalonia.Input; using Avalonia.Data; using Avalonia.Data.Converters; +using Avalonia.Input; using Avalonia.Layout; using Avalonia.Media; +using CommunityToolkit.Mvvm.Input; using Nikse.SubtitleEdit.Logic; using Nikse.SubtitleEdit.Logic.Config; -using Nikse.SubtitleEdit.Logic.ValueConverters; +using Optris.Icons.Avalonia; namespace Nikse.SubtitleEdit.Features.Ocr.NOcr; public class NOcrTrainWindow : Window { + private static readonly Color PreviewBackground = Color.FromRgb(0x1E, 0x23, 0x2B); + private static readonly Color LearnedColor = Color.FromRgb(0x3F, 0xB9, 0x50); + private static readonly Color SkippedColor = Color.FromRgb(0x8B, 0x94, 0x9E); + public NOcrTrainWindow(NOcrTrainViewModel vm) { Title = Se.Language.Ocr.TrainNOcrDatabase.TrimEnd('.'); vm.Window = this; UiUtil.InitializeWindow(this, GetType().Name); - Width = 940; + Width = 960; SizeToContent = SizeToContent.Height; CanResize = false; WindowStartupLocation = WindowStartupLocation.CenterOwner; DataContext = vm; - var buttonTrain = UiUtil.MakeButton(Se.Language.Ocr.StartTraining, vm.StartOrAbortTrainingCommand); - buttonTrain[!ContentControl.ContentProperty] = new Binding(nameof(vm.TrainButtonText)); + var accent = GetAccentColor(); + + var buttonTrain = new Button + { + Content = new StackPanel + { + Orientation = Orientation.Horizontal, + Spacing = 6, + Children = + { + new Icon + { + FontSize = 14, + VerticalAlignment = VerticalAlignment.Center, + [!Optris.Icons.Avalonia.Icon.ValueProperty] = new Binding(nameof(vm.TrainButtonIcon)), + }, + new TextBlock + { + VerticalAlignment = VerticalAlignment.Center, + [!TextBlock.TextProperty] = new Binding(nameof(vm.TrainButtonText)), + }, + }, + }, + Command = vm.StartOrAbortTrainingCommand, + Padding = new Thickness(14, 6), + }; buttonTrain.Classes.Add("accent"); + AutomationProperties.SetName(buttonTrain, Se.Language.Ocr.StartTraining); var buttonDone = UiUtil.MakeButtonDone(vm.DoneCommand); var columns = new Grid @@ -40,7 +71,8 @@ public NOcrTrainWindow(NOcrTrainViewModel vm) }, ColumnSpacing = 12, }; - columns.Add(MakeCard(BuildFontsSection(vm)), 0, 0); + columns.Add(MakeCard(BuildFontsSection(vm, accent)), 0, 0); + // The settings card stretches so both columns end level. var right = new Grid { @@ -51,8 +83,8 @@ public NOcrTrainWindow(NOcrTrainViewModel vm) }, RowSpacing = 12, }; - right.Add(MakeCard(BuildPreviewSection(vm)), 0, 0); - right.Add(MakeCard(BuildSettingsSection(vm)), 1, 0); + right.Add(MakeCard(BuildPreviewSection(vm, accent)), 0, 0); + right.Add(MakeCard(BuildSettingsSection(vm, accent)), 1, 0); columns.Add(right, 0, 1); var stack = new StackPanel @@ -61,9 +93,9 @@ public NOcrTrainWindow(NOcrTrainViewModel vm) Spacing = 12, Children = { - BuildHeader(), + BuildHeader(accent), columns, - MakeCard(BuildCharactersSection(vm)), + MakeCard(BuildCharactersSection(vm, accent)), BuildProgressSection(vm), UiUtil.MakeButtonBar(buttonTrain, buttonDone), }, @@ -83,11 +115,38 @@ public NOcrTrainWindow(NOcrTrainViewModel vm) Closing += (_, _) => vm.OnClosing(); } - private static Control BuildHeader() + private static Color GetAccentColor() { - return new StackPanel + return Application.Current != null && + Application.Current.TryGetResource("SystemAccentColor", Application.Current.ActualThemeVariant, out var value) && + value is Color color + ? color + : Color.FromRgb(0x00, 0x78, 0xD4); + } + + private static Control BuildHeader(Color accent) + { + var badge = new Border + { + Width = 44, + Height = 44, + CornerRadius = new CornerRadius(10), + Background = new SolidColorBrush(accent, 0.18), + VerticalAlignment = VerticalAlignment.Center, + Child = new Icon + { + Value = IconNames.School, + FontSize = 24, + Foreground = new SolidColorBrush(accent), + HorizontalAlignment = HorizontalAlignment.Center, + VerticalAlignment = VerticalAlignment.Center, + }, + }; + + var texts = new StackPanel { Orientation = Orientation.Vertical, + VerticalAlignment = VerticalAlignment.Center, Children = { new TextBlock @@ -106,32 +165,45 @@ private static Control BuildHeader() }, }, }; + + return new StackPanel + { + Orientation = Orientation.Horizontal, + Spacing = 12, + Children = { badge, texts }, + }; } - private static Control BuildFontsSection(NOcrTrainViewModel vm) + private static Control BuildFontsSection(NOcrTrainViewModel vm, Color accent) { - var title = MakeSectionTitle(Se.Language.General.Fonts); - var count = new TextBlock + var count = new Border { - FontSize = UiUtil.ScaledFontSize(12), - Opacity = 0.75, + Padding = new Thickness(8, 1), + CornerRadius = new CornerRadius(10), + Background = new SolidColorBrush(accent, 0.18), VerticalAlignment = VerticalAlignment.Center, HorizontalAlignment = HorizontalAlignment.Right, - [!TextBlock.TextProperty] = new Binding(nameof(vm.SelectedFontsText)), + Child = new TextBlock + { + FontSize = UiUtil.ScaledFontSize(12), + Foreground = new SolidColorBrush(accent), + FontWeight = FontWeight.SemiBold, + [!TextBlock.TextProperty] = new Binding(nameof(vm.SelectedFontsText)), + }, }; var titleRow = new Grid { ColumnDefinitions = { new ColumnDefinition(GridLength.Star), new ColumnDefinition(GridLength.Auto) }, }; - titleRow.Add(title, 0, 0); + titleRow.Add(MakeSectionTitle(IconNames.FormatFont, Se.Language.General.Fonts, accent), 0, 0); titleRow.Add(count, 0, 1); var search = new TextBox { PlaceholderText = Se.Language.General.Search, [!TextBox.TextProperty] = new Binding(nameof(vm.FontSearchText)) { Mode = BindingMode.TwoWay }, - [!InputElement.IsEnabledProperty] = new Binding(nameof(vm.IsNotTraining)), - }; + }.WithSearchAndClearIcons(); + AutomationProperties.SetName(search, Se.Language.General.Search); var buttons = new StackPanel { @@ -139,8 +211,8 @@ private static Control BuildFontsSection(NOcrTrainViewModel vm) Spacing = 6, Children = { - UiUtil.MakeButton(Se.Language.Ocr.SubtitleFonts, vm.SelectSubtitleFontsCommand).WithMargin(0), - UiUtil.MakeButton(Se.Language.General.Clear, vm.ClearFontsCommand).WithMargin(0), + MakeIconTextButton(IconNames.AutoFix, Se.Language.Ocr.SubtitleFonts, vm.SelectSubtitleFontsCommand), + MakeIconTextButton(IconNames.Close, Se.Language.General.Clear, vm.ClearFontsCommand), }, }; @@ -152,8 +224,9 @@ private static Control BuildFontsSection(NOcrTrainViewModel vm) string.IsNullOrWhiteSpace(name) ? FontFamily.Default : FontFamilyHelper.Make(name)); var fontsListBox = new ListBox { - Height = 330, + Height = 336, ItemsSource = vm.FilteredFonts, + Background = Brushes.Transparent, [!SelectingItemsControl.SelectedItemProperty] = new Binding(nameof(vm.HighlightedFont)) { Mode = BindingMode.TwoWay }, ItemTemplate = new FuncDataTemplate((_, _) => new CheckBox { @@ -163,6 +236,7 @@ private static Control BuildFontsSection(NOcrTrainViewModel vm) { Converter = fontNameToFontFamily, }, + FontSize = UiUtil.ScaledFontSize(15), Height = 26, VerticalContentAlignment = VerticalAlignment.Center, ClipToBounds = true, @@ -172,7 +246,7 @@ private static Control BuildFontsSection(NOcrTrainViewModel vm) return new StackPanel { Orientation = Orientation.Vertical, - Spacing = 8, + Spacing = 10, Children = { titleRow, @@ -180,24 +254,27 @@ private static Control BuildFontsSection(NOcrTrainViewModel vm) buttons, UiUtil.MakeBorderForControlNoPadding(fontsListBox), }, + [!InputElement.IsEnabledProperty] = new Binding(nameof(vm.IsNotTraining)), }; } - private static Control BuildPreviewSection(NOcrTrainViewModel vm) + private static Control BuildPreviewSection(NOcrTrainViewModel vm, Color accent) { var fontName = new TextBlock { FontSize = UiUtil.ScaledFontSize(12), - Opacity = 0.75, + Opacity = 0.7, VerticalAlignment = VerticalAlignment.Center, HorizontalAlignment = HorizontalAlignment.Right, + TextTrimming = TextTrimming.CharacterEllipsis, + MaxWidth = 220, [!TextBlock.TextProperty] = new Binding(nameof(vm.PreviewFontName)), }; var titleRow = new Grid { ColumnDefinitions = { new ColumnDefinition(GridLength.Star), new ColumnDefinition(GridLength.Auto) }, }; - titleRow.Add(MakeSectionTitle(Se.Language.General.Preview), 0, 0); + titleRow.Add(MakeSectionTitle(IconNames.Eye, Se.Language.General.Preview, accent), 0, 0); titleRow.Add(fontName, 0, 1); var image = new Image @@ -205,27 +282,38 @@ private static Control BuildPreviewSection(NOcrTrainViewModel vm) Stretch = Stretch.Uniform, StretchDirection = StretchDirection.DownOnly, HorizontalAlignment = HorizontalAlignment.Left, + VerticalAlignment = VerticalAlignment.Center, MaxHeight = 150, [!Image.SourceProperty] = new Binding(nameof(vm.PreviewImage)), }; var imageBorder = new Border { Child = image, - Background = new SolidColorBrush(Color.FromRgb(0x26, 0x2B, 0x33)), - CornerRadius = new CornerRadius(4), + Padding = new Thickness(6), + Background = new LinearGradientBrush + { + StartPoint = new RelativePoint(0, 0, RelativeUnit.Relative), + EndPoint = new RelativePoint(1, 1, RelativeUnit.Relative), + GradientStops = + { + new GradientStop(Color.FromRgb(0x2A, 0x33, 0x40), 0), + new GradientStop(PreviewBackground, 1), + }, + }, + CornerRadius = new CornerRadius(6), ClipToBounds = true, - MinHeight = 60, + MinHeight = 64, }; return new StackPanel { Orientation = Orientation.Vertical, - Spacing = 8, + Spacing = 10, Children = { titleRow, imageBorder }, }; } - private static Control BuildSettingsSection(NOcrTrainViewModel vm) + private static Control BuildSettingsSection(NOcrTrainViewModel vm, Color accent) { var grid = new Grid { @@ -235,9 +323,9 @@ private static Control BuildSettingsSection(NOcrTrainViewModel vm) new ColumnDefinition(GridLength.Star), }, ColumnSpacing = 12, - RowSpacing = 8, + RowSpacing = 10, }; - for (var i = 0; i < 6; i++) + for (var i = 0; i < 5; i++) { grid.RowDefinitions.Add(new RowDefinition(GridLength.Auto)); } @@ -255,17 +343,24 @@ private static Control BuildSettingsSection(NOcrTrainViewModel vm) var sizesBox = UiUtil.MakeTextBox(220, vm, nameof(vm.FontSizes)); sizesBox.HorizontalAlignment = HorizontalAlignment.Stretch; sizesBox.Width = double.NaN; + sizesBox.InnerRightContent = new TextBlock + { + Text = "px", + Opacity = 0.5, + Margin = new Thickness(0, 0, 8, 0), + VerticalAlignment = VerticalAlignment.Center, + }; SetHint(sizesBox, Se.Language.Ocr.FontSizesHint); AddRow(grid, 2, Se.Language.Ocr.FontSizes, sizesBox); var styles = new StackPanel { Orientation = Orientation.Horizontal, - Spacing = 16, + Spacing = 6, Children = { - UiUtil.MakeCheckBox(Se.Language.General.Bold, vm, nameof(vm.TrainBold)), - UiUtil.MakeCheckBox(Se.Language.General.Italic, vm, nameof(vm.TrainItalic)), + MakeStyleToggle(IconNames.Bold, Se.Language.General.Bold, nameof(vm.TrainBold)), + MakeStyleToggle(IconNames.Italic, Se.Language.General.Italic, nameof(vm.TrainItalic)), }, }; AddRow(grid, 3, Se.Language.General.Styles, styles); @@ -277,13 +372,35 @@ private static Control BuildSettingsSection(NOcrTrainViewModel vm) return new StackPanel { Orientation = Orientation.Vertical, - Spacing = 10, - Children = { MakeSectionTitle(Se.Language.Ocr.TrainingOptions), grid }, + Spacing = 12, + Children = { MakeSectionTitle(IconNames.Tune, Se.Language.Ocr.TrainingOptions, accent), grid }, [!InputElement.IsEnabledProperty] = new Binding(nameof(vm.IsNotTraining)), }; } - private static Control BuildCharactersSection(NOcrTrainViewModel vm) + /// A toggle button with an icon and a label - a bolder look than a check box. + private static ToggleButton MakeStyleToggle(string iconName, string text, string isCheckedPath) + { + var toggle = new ToggleButton + { + Content = new StackPanel + { + Orientation = Orientation.Horizontal, + Spacing = 6, + Children = + { + new Icon { Value = iconName, FontSize = 16, VerticalAlignment = VerticalAlignment.Center }, + new TextBlock { Text = text, VerticalAlignment = VerticalAlignment.Center }, + }, + }, + Padding = new Thickness(10, 4), + [!ToggleButton.IsCheckedProperty] = new Binding(isCheckedPath) { Mode = BindingMode.TwoWay }, + }; + AutomationProperties.SetName(toggle, text); + return toggle; + } + + private static Control BuildCharactersSection(NOcrTrainViewModel vm, Color accent) { var charactersTextBox = UiUtil.MakeTextBox(200, vm, nameof(vm.CharactersToTrain)); charactersTextBox.Width = double.NaN; @@ -291,6 +408,8 @@ private static Control BuildCharactersSection(NOcrTrainViewModel vm) charactersTextBox.TextWrapping = TextWrapping.Wrap; charactersTextBox.AcceptsReturn = false; charactersTextBox.Height = 56; + charactersTextBox.FontSize = UiUtil.ScaledFontSize(15); + AutomationProperties.SetName(charactersTextBox, Se.Language.Ocr.CharactersToTrain); var buttons = new StackPanel { @@ -298,8 +417,8 @@ private static Control BuildCharactersSection(NOcrTrainViewModel vm) Spacing = 6, Children = { - UiUtil.MakeButton(Se.Language.General.Reset, vm.ResetCharactersCommand).WithMargin(0), - UiUtil.MakeButton(Se.Language.Ocr.ImportCharactersFromSubtitleFile, vm.ImportCharactersFromFileCommand).WithMargin(0), + MakeIconTextButton(IconNames.Restore, Se.Language.General.Reset, vm.ResetCharactersCommand), + MakeIconTextButton(IconNames.Import, Se.Language.Ocr.ImportCharactersFromSubtitleFile, vm.ImportCharactersFromFileCommand), }, }; @@ -307,6 +426,7 @@ private static Control BuildCharactersSection(NOcrTrainViewModel vm) mergedTextBox.Width = double.NaN; mergedTextBox.HorizontalAlignment = HorizontalAlignment.Stretch; mergedTextBox.PlaceholderText = "fi ff fl rn"; + AutomationProperties.SetName(mergedTextBox, Se.Language.Ocr.LetterCombinationsToTrain); return new StackPanel { @@ -314,10 +434,10 @@ private static Control BuildCharactersSection(NOcrTrainViewModel vm) Spacing = 8, Children = { - MakeSectionTitle(Se.Language.Ocr.CharactersToTrain), + MakeSectionTitle(IconNames.AlphabeticalVariant, Se.Language.Ocr.CharactersToTrain, accent), charactersTextBox, buttons, - MakeSmallLabel(Se.Language.Ocr.LetterCombinationsToTrain), + MakeSmallLabel(Se.Language.Ocr.LetterCombinationsToTrain).WithMarginTop(4), mergedTextBox, }, [!InputElement.IsEnabledProperty] = new Binding(nameof(vm.IsNotTraining)), @@ -332,6 +452,7 @@ private static Control BuildProgressSection(NOcrTrainViewModel vm) Maximum = 100, Height = 6, MinHeight = 6, + CornerRadius = new CornerRadius(3), [!RangeBase.ValueProperty] = new Binding(nameof(vm.ProgressValue)), [!Visual.IsVisibleProperty] = new Binding(nameof(vm.IsProgressVisible)), }; @@ -340,10 +461,9 @@ private static Control BuildProgressSection(NOcrTrainViewModel vm) { VerticalAlignment = VerticalAlignment.Center, TextTrimming = TextTrimming.CharacterEllipsis, + Opacity = 0.85, [!TextBlock.TextProperty] = new Binding(nameof(vm.StatusText)), }; - var learned = MakeBadge(nameof(vm.LearnedText), Color.FromRgb(0x3F, 0xB9, 0x50)); - var skipped = MakeBadge(nameof(vm.SkippedText), Color.FromRgb(0x8B, 0x94, 0x9E)); var statusRow = new Grid { ColumnDefinitions = @@ -355,8 +475,8 @@ private static Control BuildProgressSection(NOcrTrainViewModel vm) ColumnSpacing = 8, }; statusRow.Add(status, 0, 0); - statusRow.Add(learned, 0, 1); - statusRow.Add(skipped, 0, 2); + statusRow.Add(MakeBadge(IconNames.CheckCircle, nameof(vm.LearnedText), LearnedColor), 0, 1); + statusRow.Add(MakeBadge(IconNames.SkipNext, nameof(vm.SkippedText), SkippedColor), 0, 2); return new StackPanel { @@ -366,25 +486,57 @@ private static Control BuildProgressSection(NOcrTrainViewModel vm) }; } - private static Control MakeBadge(string textPath, Color color) + private static Control MakeBadge(string iconName, string textPath, Color color) { - var text = new TextBlock - { - FontSize = UiUtil.ScaledFontSize(12), - Foreground = new SolidColorBrush(color), - [!TextBlock.TextProperty] = new Binding(textPath), - }; + var brush = new SolidColorBrush(color); return new Border { - Child = text, Padding = new Thickness(8, 2), CornerRadius = new CornerRadius(10), + Background = new SolidColorBrush(color, 0.12), BorderThickness = new Thickness(1), - BorderBrush = new SolidColorBrush(Color.FromArgb(0x80, color.R, color.G, color.B)), + BorderBrush = new SolidColorBrush(color, 0.5), + Child = new StackPanel + { + Orientation = Orientation.Horizontal, + Spacing = 4, + Children = + { + new Icon { Value = iconName, FontSize = 12, Foreground = brush, VerticalAlignment = VerticalAlignment.Center }, + new TextBlock + { + FontSize = UiUtil.ScaledFontSize(12), + Foreground = brush, + VerticalAlignment = VerticalAlignment.Center, + [!TextBlock.TextProperty] = new Binding(textPath), + }, + }, + }, [!Visual.IsVisibleProperty] = new Binding(textPath) { Converter = StringConverters.IsNotNullOrEmpty }, }; } + private static Button MakeIconTextButton(string iconName, string text, IRelayCommand command) + { + var button = new Button + { + Content = new StackPanel + { + Orientation = Orientation.Horizontal, + Spacing = 6, + Children = + { + new Icon { Value = iconName, FontSize = 14, VerticalAlignment = VerticalAlignment.Center }, + new TextBlock { Text = text, VerticalAlignment = VerticalAlignment.Center }, + }, + }, + Command = command, + Padding = new Thickness(10, 4), + }; + AutomationProperties.SetName(button, text); + return button; + } + private static void AddRow(Grid grid, int row, string label, Control control) { var textBlock = MakeSmallLabel(label); @@ -393,14 +545,30 @@ private static void AddRow(Grid grid, int row, string label, Control control) grid.Add(control, row, 1); } - private static TextBlock MakeSectionTitle(string text) + private static Control MakeSectionTitle(string iconName, string text, Color accent) { - return new TextBlock + return new StackPanel { - Text = text, - FontSize = UiUtil.ScaledFontSize(14), - FontWeight = FontWeight.SemiBold, + Orientation = Orientation.Horizontal, + Spacing = 8, VerticalAlignment = VerticalAlignment.Center, + Children = + { + new Icon + { + Value = iconName, + FontSize = 16, + Foreground = new SolidColorBrush(accent), + VerticalAlignment = VerticalAlignment.Center, + }, + new TextBlock + { + Text = text, + FontSize = UiUtil.ScaledFontSize(14), + FontWeight = FontWeight.SemiBold, + VerticalAlignment = VerticalAlignment.Center, + }, + }, }; } @@ -426,10 +594,11 @@ private static Border MakeCard(Control child) return new Border { Child = child, - Padding = new Thickness(14), - CornerRadius = new CornerRadius(6), + Padding = new Thickness(16), + CornerRadius = new CornerRadius(8), BorderThickness = new Thickness(1), BorderBrush = new SolidColorBrush(Color.FromArgb(0x40, 0x80, 0x80, 0x80)), + Background = new SolidColorBrush(Color.FromArgb(0x0A, 0x80, 0x80, 0x80)), }; } } diff --git a/src/ui/Logic/IconNames.cs b/src/ui/Logic/IconNames.cs index 9fce312ecbf..d22185c91f5 100644 --- a/src/ui/Logic/IconNames.cs +++ b/src/ui/Logic/IconNames.cs @@ -7,6 +7,8 @@ internal class IconNames public const string Alert = "mdi-alert"; public const string AlignHorizontalCenter = "mdi-align-horizontal-center"; public const string AlignHorizontalDistribute = "mdi-align-horizontal-distribute"; + public const string AlphabeticalVariant = "mdi-alphabetical-variant"; + public const string AutoFix = "mdi-auto-fix"; public const string AnimationPlay = "mdi-animation-play"; public const string ArrowCollapseVertical = "mdi-arrow-collapse-vertical"; public const string ArrowDownThin = "mdi-arrow-down-thin"; @@ -116,6 +118,7 @@ internal class IconNames public const string Refresh = "mdi-refresh"; public const string Restore = "mdi-restore"; public const string ScaleBalance = "mdi-scale-balance"; + public const string School = "mdi-school-outline"; public const string SelectAll = "mdi-select-all"; public const string SetMerge = "mdi-set-merge"; public const string SetSplit = "mdi-set-split"; @@ -123,6 +126,7 @@ internal class IconNames public const string SkipNext = "mdi-skip-next"; public const string Sort = "mdi-sort"; public const string Spellcheck = "mdi-spellcheck"; + public const string Stop = "mdi-stop"; public const string StopCircle = "mdi-stop-circle"; public const string SwapVertical = "mdi-swap-vertical"; public const string PuzzleOutline = "mdi-puzzle-outline"; From 5a9605c7870b797e96fa7a5b5ee1f131d4a2e45d Mon Sep 17 00:00:00 2001 From: niksedk Date: Sat, 10 Oct 2026 08:19:39 +0200 Subject: [PATCH 010/306] Regenerate English.json Picks up the ASSA draw strings from the shape tool/library and context menus. Co-Authored-By: Claude Opus 5.5 --- src/ui/Assets/Languages/English.json | 75 +++++++++++++++++++++++++++- 1 file changed, 74 insertions(+), 1 deletion(-) diff --git a/src/ui/Assets/Languages/English.json b/src/ui/Assets/Languages/English.json index ad5126eb5d6..25e309fba23 100644 --- a/src/ui/Assets/Languages/English.json +++ b/src/ui/Assets/Languages/English.json @@ -4250,7 +4250,80 @@ "drawSelectedShape": "Selected shape", "drawSelectedLayer": "Selected layer", "drawToolX": "Tool: {0}", - "drawHelpText": "Click to add points • Enter/F8 to close shape • Shift+Drag to pan • Ctrl+Scroll to zoom", + "drawHelpText": "Click to add points • Right-click for options • Select tool: drag handles to scale/rotate (Shift = keep ratio/15°) • Enter/F8 to close shape • Shift+drag or middle mouse to pan • Ctrl+Scroll to zoom", + "drawMoveToLayer": "Move to layer", + "drawFlipHorizontal": "Flip horizontally", + "drawFlipVertical": "Flip vertically", + "drawHideShape": "Hide shape", + "drawShowShape": "Show shape", + "drawHideLayer": "Hide layer", + "drawShowLayer": "Show layer", + "drawDeleteLayer": "Delete layer", + "drawDeletePoint": "Delete point", + "drawCancelDrawing": "Cancel drawing", + "drawLayerX": "Layer {0}", + "drawImportSvg": "Import SVG image...", + "drawRotateClockwise": "Rotate 90° clockwise", + "drawConvertToCurve": "Convert segment to curve", + "drawBackground": "Background", + "drawPosition": "Position", + "drawZoom": "Zoom", + "drawShapeTool": "Shape tool", + "drawShapeLibrary": "Shape library", + "drawShapeLibraryHint": "Drag on the canvas to place a shape, or click for the default size. Shift keeps its proportions.", + "drawAddToLibrary": "Add to shape library...", + "drawRemoveFromLibrary": "Remove from shape library", + "drawShapeName": "Shape name", + "drawCategorySpeechBubbles": "Speech bubbles", + "drawCategoryArrows": "Arrows", + "drawCategoryBasicShapes": "Basic shapes", + "drawCategorySymbols": "Symbols", + "drawCategoryMyShapes": "My shapes", + "drawShapeSpeechBubble": "Speech bubble", + "drawShapeSpeechBubbleRight": "Speech bubble, tail right", + "drawShapeSpeechBubbleUp": "Speech bubble, tail up", + "drawShapeOvalBubble": "Oval speech bubble", + "drawShapeBoxBubble": "Box speech bubble", + "drawShapeThoughtBubble": "Thought bubble", + "drawShapeShoutBubble": "Shout bubble", + "drawShapeCaptionBox": "Caption box", + "drawShapeArrow": "Arrow", + "drawShapeDoubleArrow": "Double arrow", + "drawShapeChevron": "Chevron", + "drawShapeCurvedArrow": "Curved arrow", + "drawShapeRoundedRectangle": "Rounded rectangle", + "drawShapeTriangle": "Triangle", + "drawShapeDiamond": "Diamond", + "drawShapePentagon": "Pentagon", + "drawShapeHexagon": "Hexagon", + "drawShapeOctagon": "Octagon", + "drawShapeStar": "Star", + "drawShapeRing": "Ring", + "drawShapePlus": "Plus", + "drawShapeBanner": "Banner", + "drawShapeHeart": "Heart", + "drawShapeCheck": "Check mark", + "drawShapeMusicNote": "Music note", + "drawShapeMusicNotes": "Music notes", + "drawShapeLightning": "Lightning", + "drawShapeCloud": "Cloud", + "drawShapeMoon": "Moon", + "drawShapeBadge": "Badge", + "drawSize": "Size", + "drawLayer": "Layer", + "drawBackgroundVideoFrame": "Video frame (current position)", + "drawBackgroundVideoFrameAt": "Video frame at...", + "drawBackgroundImage": "Image file...", + "drawBackgroundNone": "No background", + "drawBackgroundStretch": "Stretch image to canvas", + "drawBackgroundOpacity": "Background opacity", + "drawImages": "Images", + "drawConvertToLine": "Convert segment to line", + "drawConvertShapeToCurves": "Convert all lines to curves", + "drawConvertShapeToLines": "Convert all curves to lines", + "drawRotateCounterClockwise": "Rotate 90° counter-clockwise", + "drawSvgImages": "SVG images", + "drawSvgNoShapes": "No filled shapes or strokes found in the SVG file.", "progressBarTitle": "ASSA progress bar", "progressBarSettings": "Progress bar", "mouseOverColor": "Mouse-over color", From 891438c38de957046478a57a81bd9a6b3e481303 Mon Sep 17 00:00:00 2001 From: niksedk Date: Sat, 10 Oct 2026 08:49:15 +0200 Subject: [PATCH 011/306] Update nuget --- src/ui/UI.csproj | 2 +- src/ui/packages.lock.json | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/ui/UI.csproj b/src/ui/UI.csproj index 113a3d830eb..ce972f69961 100644 --- a/src/ui/UI.csproj +++ b/src/ui/UI.csproj @@ -39,7 +39,7 @@ - + 00:00:02,000\nHello\n"); + var vm = new PointSyncViaOtherViewModel(new FileHelper(), new WindowService(new NullServiceProvider())); + + Assert.Null(await vm.TryLoadOtherFromContainer(fileName)); + } + finally + { + File.Delete(fileName); + } + } } From 2c69ebb19470dc87c17f6048937df7c52fa6d5f3 Mon Sep 17 00:00:00 2001 From: niksedk Date: Sun, 11 Oct 2026 06:47:03 +0200 Subject: [PATCH 048/306] Point sync via other: DVB tracks in mkv too (#15911) The DVB block parsing moves out of MainViewModel into MatroskaDvbHelper, so the main window's OCR import and point sync share it; point sync shows the DVB images like the other image tracks. Co-Authored-By: Claude Opus 5.5 --- docs/features/point-sync-via-other.md | 2 +- src/ui/Features/Main/MainViewModel.cs | 75 +------------- .../PointSyncViaOtherViewModel.Containers.cs | 10 +- src/ui/Logic/MatroskaDvbHelper.cs | 97 +++++++++++++++++++ 4 files changed, 105 insertions(+), 79 deletions(-) create mode 100644 src/ui/Logic/MatroskaDvbHelper.cs diff --git a/docs/features/point-sync-via-other.md b/docs/features/point-sync-via-other.md index da06d450064..a44add6f4b2 100644 --- a/docs/features/point-sync-via-other.md +++ b/docs/features/point-sync-via-other.md @@ -16,7 +16,7 @@ Synchronize a subtitle file using another subtitle file as reference. 5. Repeat for more sync points; they are listed in the middle and can be removed with right-click **Delete** or the Delete/Backspace key 6. Click **Apply** to apply the sync points and keep working, or **OK** to apply and close -An image based reference (a Blu-ray `.sup`, or a PGS/VobSub track in `.mkv`/`.mp4`) is not OCR'ed: sync points only need its time codes, so the right grid shows each line's image instead of text, the same way the OCR window does. Hover an image to see it full size. **Find text** is not available for an image based reference. +An image based reference (a Blu-ray `.sup`, a PGS/VobSub/DVB track in `.mkv` or a VobSub track in `.mp4`) is not OCR'ed: sync points only need its time codes, so the right grid shows each line's image instead of text, the same way the OCR window does. Hover an image to see it full size. **Find text** is not available for an image based reference. Both grids have a **Gap after** column with the silence after each line, the same as the main window's **Gap** column. Gaps of 3 seconds or more are shown as a green badge that gets stronger at 6, 12 and 25+ seconds, so the long silences stand out. **Find text** above each grid searches that subtitle. diff --git a/src/ui/Features/Main/MainViewModel.cs b/src/ui/Features/Main/MainViewModel.cs index ccfbc452d6c..a91557e58c1 100644 --- a/src/ui/Features/Main/MainViewModel.cs +++ b/src/ui/Features/Main/MainViewModel.cs @@ -27184,87 +27184,14 @@ private async Task LoadDvbFromMatroska(MatroskaTrackInfo matroskaSubtitleI var sub = await ExtractMatroskaSubtitleAsync(matroska, matroskaSubtitleInfo.TrackNumber); _subtitle.Paragraphs.Clear(); - var subtitleImages = new List(); var subtitle = new Subtitle(); Utilities.LoadMatroskaTextSubtitle(matroskaSubtitleInfo, matroska, sub, _subtitle); - for (var index = 0; index < sub.Count; index++) - { - try - { - var msub = sub[index]; - DvbSubPes? pes = null; - var data = msub.GetData(matroskaSubtitleInfo); - if (data != null && data.Length > 9 && data[0] == 15 && - data[1] >= SubtitleSegment.PageCompositionSegment && - data[1] <= SubtitleSegment.DisplayDefinitionSegment) // sync byte + segment id - { - var buffer = new byte[data.Length + 3]; - Buffer.BlockCopy(data, 0, buffer, 2, data.Length); - buffer[0] = 32; - buffer[1] = 0; - buffer[buffer.Length - 1] = 255; - pes = new DvbSubPes(0, buffer); - } - else if (VobSubParser.IsMpeg2PackHeader(data)) - { - pes = new DvbSubPes(data, Mpeg2Header.Length); - } - else if (VobSubParser.IsPrivateStream1(data, 0)) - { - pes = new DvbSubPes(data, 0); - } - else if (data!.Length > 9 && data[0] == 32 && data[1] == 0 && data[2] == 14 && data[3] == 16) - { - pes = new DvbSubPes(0, data); - } - - if (pes == null && subtitle.Paragraphs.Count > 0) - { - var last = subtitle.Paragraphs[subtitle.Paragraphs.Count - 1]; - if (last.DurationTotalMilliseconds < 100) - { - last.EndTime.TotalMilliseconds = msub.Start; - if (last.DurationTotalMilliseconds > - Se.Settings.General.SubtitleMaximumDisplayMilliseconds) - { - last.EndTime.TotalMilliseconds = last.StartTime.TotalMilliseconds + 3000; - } - } - } - - if (pes != null && pes.PageCompositions != null && pes.PageCompositions.Any(p => p.Regions.Count > 0)) - { - subtitleImages.Add(pes); - subtitle.Paragraphs.Add(new Paragraph(string.Empty, msub.Start, msub.End)); - } - } - catch - { - // continue - } - } - + var subtitleImages = MatroskaDvbHelper.ParseImages(matroskaSubtitleInfo, sub, subtitle); if (subtitleImages.Count == 0) { return false; } - for (var index = 0; index < subtitle.Paragraphs.Count; index++) - { - var p = subtitle.Paragraphs[index]; - if (p.DurationTotalMilliseconds < 200) - { - p.EndTime.TotalMilliseconds = p.StartTime.TotalMilliseconds + 3000; - } - - var next = subtitle.GetParagraphOrDefault(index + 1); - if (next != null && next.StartTime.TotalMilliseconds < p.EndTime.TotalMilliseconds) - { - p.EndTime.TotalMilliseconds = next.StartTime.TotalMilliseconds - - Se.Settings.General.MinimumBetweenLines.GetMilliseconds(); - } - } - Dispatcher.UIThread.Post(async () => { var result = await ShowDialogAsync(vm => { vm.Initialize(matroskaSubtitleInfo, subtitle, subtitleImages, fileName); }); diff --git a/src/ui/Features/Sync/PointSyncViaOther/PointSyncViaOtherViewModel.Containers.cs b/src/ui/Features/Sync/PointSyncViaOther/PointSyncViaOtherViewModel.Containers.cs index bb7fe630326..9cee94e7523 100644 --- a/src/ui/Features/Sync/PointSyncViaOther/PointSyncViaOtherViewModel.Containers.cs +++ b/src/ui/Features/Sync/PointSyncViaOther/PointSyncViaOtherViewModel.Containers.cs @@ -24,7 +24,7 @@ namespace Nikse.SubtitleEdit.Features.Sync.PointSyncViaOther; /// /// "Other subtitle" from a video container or a Blu-ray sup - SE 4 let you pick an embedded track /// here, SE 5 only took subtitle files and said "Unknown subtitle format" (issue #15911). Image -/// tracks are not OCR'ed: a sync point only needs the time codes, so the grid shows the bitmaps. +/// tracks (PGS, VobSub, DVB) are not OCR'ed: a sync point only needs the time codes, so the grid shows the bitmaps. /// public partial class PointSyncViaOtherViewModel { @@ -125,13 +125,15 @@ private async Task LoadOtherFromMatroska(string fileName) return MakeImageResult(new OcrSubtitleMkvBluRay(track, pcsData)); } + var blocks = await RunWithProgress(fileName, cb => matroska.GetSubtitle(track.TrackNumber, cb)); + if (track.CodecId.Equals("S_DVBSUB", StringComparison.OrdinalIgnoreCase)) { - return new OtherLoadResult { Error = Se.Language.General.UnknownSubtitleFormat }; + var dvbSubtitle = new Subtitle(); + var images = MatroskaDvbHelper.ParseImages(track, blocks, dvbSubtitle); + return MakeImageResult(new OcrSubtitleMkvDvb(track, dvbSubtitle, images)); } - var blocks = await RunWithProgress(fileName, cb => matroska.GetSubtitle(track.TrackNumber, cb)); - if (track.CodecId.Equals("S_VOBSUB", StringComparison.OrdinalIgnoreCase)) { return MakeImageResult(MakeMatroskaVobSub(track, blocks)); diff --git a/src/ui/Logic/MatroskaDvbHelper.cs b/src/ui/Logic/MatroskaDvbHelper.cs new file mode 100644 index 00000000000..3ddbc924f57 --- /dev/null +++ b/src/ui/Logic/MatroskaDvbHelper.cs @@ -0,0 +1,97 @@ +using Nikse.SubtitleEdit.Core.Common; +using Nikse.SubtitleEdit.Core.ContainerFormats.Matroska; +using Nikse.SubtitleEdit.Core.ContainerFormats.TransportStream; +using Nikse.SubtitleEdit.Core.VobSub; +using Nikse.SubtitleEdit.Logic.Config; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Nikse.SubtitleEdit.Logic; + +/// +/// Turns the blocks of a DVB subtitle track in a Matroska file into images + timings - shared by +/// the main window's import (which OCRs them) and "Point sync via other subtitle" (#15911). +/// +public static class MatroskaDvbHelper +{ + /// + /// One image per paragraph of , which is filled with the timings. + /// + public static List ParseImages(MatroskaTrackInfo track, List blocks, Subtitle subtitle) + { + var subtitleImages = new List(); + foreach (var block in blocks) + { + try + { + DvbSubPes? pes = null; + var data = block.GetData(track); + if (data != null && data.Length > 9 && data[0] == 15 && + data[1] >= SubtitleSegment.PageCompositionSegment && + data[1] <= SubtitleSegment.DisplayDefinitionSegment) // sync byte + segment id + { + var buffer = new byte[data.Length + 3]; + Buffer.BlockCopy(data, 0, buffer, 2, data.Length); + buffer[0] = 32; + buffer[1] = 0; + buffer[buffer.Length - 1] = 255; + pes = new DvbSubPes(0, buffer); + } + else if (VobSubParser.IsMpeg2PackHeader(data)) + { + pes = new DvbSubPes(data, Mpeg2Header.Length); + } + else if (VobSubParser.IsPrivateStream1(data, 0)) + { + pes = new DvbSubPes(data, 0); + } + else if (data!.Length > 9 && data[0] == 32 && data[1] == 0 && data[2] == 14 && data[3] == 16) + { + pes = new DvbSubPes(0, data); + } + + if (pes == null && subtitle.Paragraphs.Count > 0) + { + var last = subtitle.Paragraphs[subtitle.Paragraphs.Count - 1]; + if (last.DurationTotalMilliseconds < 100) + { + last.EndTime.TotalMilliseconds = block.Start; + if (last.DurationTotalMilliseconds > Se.Settings.General.SubtitleMaximumDisplayMilliseconds) + { + last.EndTime.TotalMilliseconds = last.StartTime.TotalMilliseconds + 3000; + } + } + } + + if (pes != null && pes.PageCompositions != null && pes.PageCompositions.Any(p => p.Regions.Count > 0)) + { + subtitleImages.Add(pes); + subtitle.Paragraphs.Add(new Paragraph(string.Empty, block.Start, block.End)); + } + } + catch + { + // continue + } + } + + for (var index = 0; index < subtitle.Paragraphs.Count; index++) + { + var p = subtitle.Paragraphs[index]; + if (p.DurationTotalMilliseconds < 200) + { + p.EndTime.TotalMilliseconds = p.StartTime.TotalMilliseconds + 3000; + } + + var next = subtitle.GetParagraphOrDefault(index + 1); + if (next != null && next.StartTime.TotalMilliseconds < p.EndTime.TotalMilliseconds) + { + p.EndTime.TotalMilliseconds = next.StartTime.TotalMilliseconds - + Se.Settings.General.MinimumBetweenLines.GetMilliseconds(); + } + } + + return subtitleImages; + } +} From dc9da4dc910cc71a5092b0a75539dddbf6c1a1db Mon Sep 17 00:00:00 2001 From: niksedk Date: Sun, 11 Oct 2026 06:55:05 +0200 Subject: [PATCH 049/306] Point sync via other: Ctrl+P image preview instead of hover tooltip (#15911) Same ShowImageWindow as the OCR window's Ctrl+P, arrow keys step through the images. Docs get a keyboard shortcuts table. Co-Authored-By: Claude Opus 5.5 --- docs/features/point-sync-via-other.md | 11 ++++- .../PointSyncViaOtherViewModel.cs | 45 +++++++++++++++++++ .../PointSyncViaOtherWindow.cs | 16 ++----- 3 files changed, 58 insertions(+), 14 deletions(-) diff --git a/docs/features/point-sync-via-other.md b/docs/features/point-sync-via-other.md index a44add6f4b2..310a29fcfad 100644 --- a/docs/features/point-sync-via-other.md +++ b/docs/features/point-sync-via-other.md @@ -16,7 +16,7 @@ Synchronize a subtitle file using another subtitle file as reference. 5. Repeat for more sync points; they are listed in the middle and can be removed with right-click **Delete** or the Delete/Backspace key 6. Click **Apply** to apply the sync points and keep working, or **OK** to apply and close -An image based reference (a Blu-ray `.sup`, a PGS/VobSub/DVB track in `.mkv` or a VobSub track in `.mp4`) is not OCR'ed: sync points only need its time codes, so the right grid shows each line's image instead of text, the same way the OCR window does. Hover an image to see it full size. **Find text** is not available for an image based reference. +An image based reference (a Blu-ray `.sup`, a PGS/VobSub/DVB track in `.mkv` or a VobSub track in `.mp4`) is not OCR'ed: sync points only need its time codes, so the right grid shows each line's image instead of text, the same way the OCR window does. Press **Ctrl+P** to see the selected image full size in its own window, where the arrow keys step to the previous/next image (same as the OCR window). **Find text** is not available for an image based reference. Both grids have a **Gap after** column with the silence after each line, the same as the main window's **Gap** column. Gaps of 3 seconds or more are shown as a green badge that gets stronger at 6, 12 and 25+ seconds, so the long silences stand out. **Find text** above each grid searches that subtitle. @@ -33,3 +33,12 @@ The sync point list in the middle is kept in subtitle order, and a line has at m **Set sync point via video...** opens the same window as [Point sync](point-sync.md#set-sync-point-window): the video with the subtitle drawn on it, playing the audio track selected in the main window, and a **Sync point time code** box that follows the video and can be edited by hand. Opened from here the window has no waveform pane. The window remembers its size and position between sessions. + +## Keyboard shortcuts + +| Shortcut | Action | +|----------|--------| +| Ctrl+P | View the selected image of an image based reference full size (arrow keys step to the previous/next image) | +| Delete / Backspace | Remove the selected sync point (sync point list focused) | +| F1 | Help | +| Escape | Cancel | diff --git a/src/ui/Features/Sync/PointSyncViaOther/PointSyncViaOtherViewModel.cs b/src/ui/Features/Sync/PointSyncViaOther/PointSyncViaOtherViewModel.cs index 340f2e5fe37..2be1a759136 100644 --- a/src/ui/Features/Sync/PointSyncViaOther/PointSyncViaOtherViewModel.cs +++ b/src/ui/Features/Sync/PointSyncViaOther/PointSyncViaOtherViewModel.cs @@ -8,6 +8,7 @@ using Nikse.SubtitleEdit.Features.Main; using Nikse.SubtitleEdit.Features.Shared; using Nikse.SubtitleEdit.Features.Shared.FindText; +using Nikse.SubtitleEdit.Features.Shared.ShowImage; using Nikse.SubtitleEdit.Features.Sync.PointSync.SetSyncPoint; using Nikse.SubtitleEdit.Logic; using Nikse.SubtitleEdit.Logic.Config; @@ -431,12 +432,56 @@ private void Cancel() Close(); } + /// + /// Shows the selected image of an image based other subtitle full size, in the same window as + /// the OCR window's Ctrl+P - Left/Right (or Up/Down) there steps to the previous/next image. + /// + [RelayCommand] + private async Task ViewSelectedOtherImage() + { + if (Window == null || SelectedOtherSubtitle is not PointSyncOtherImageLine line) + { + return; + } + + var title = $"{Se.Language.Ocr.ShowImage} - #{line.Number} {new TimeCode(line.StartTime).ToDisplayString()}"; + var result = await _windowService.ShowDialogAsync(Window, vm => + { + vm.Initialize(title, line.ImageItem.GetBitmapCropped()); + }); + + var index = Othersubtitles.IndexOf(line); + var nextIndex = result.LeftPressed ? index - 1 : result.RightPressed ? index + 1 : -1; + if (index >= 0 && nextIndex >= 0 && nextIndex < Othersubtitles.Count) + { + SelectedOtherSubtitle = Othersubtitles[nextIndex]; + ScrollOtherToLine?.Invoke(Othersubtitles[nextIndex]); + await ViewSelectedOtherImage(); + } + } + internal void OnKeyDown(KeyEventArgs e) { if (e.Key == Key.Escape) { Cancel(); } + else if (e.Key == Key.P && e.KeyModifiers == KeyModifiers.Control && IsOtherImageBased) + { + // Same shortcut as the OCR window's "Show image". + e.Handled = true; + Dispatcher.UIThread.Post(async void () => + { + try + { + await ViewSelectedOtherImage(); + } + catch (Exception exception) + { + Se.LogError(exception); + } + }); + } else if (UiUtil.IsHelp(e)) { e.Handled = true; diff --git a/src/ui/Features/Sync/PointSyncViaOther/PointSyncViaOtherWindow.cs b/src/ui/Features/Sync/PointSyncViaOther/PointSyncViaOtherWindow.cs index 362b799a7b8..3b820cd0694 100644 --- a/src/ui/Features/Sync/PointSyncViaOther/PointSyncViaOtherWindow.cs +++ b/src/ui/Features/Sync/PointSyncViaOther/PointSyncViaOtherWindow.cs @@ -312,23 +312,13 @@ private static SeTableViewColumn MakeImageColumn() Child = image, }; - // Cells are recycled, so the bitmap follows the data context. The thumbnail can - // be small, so the tooltip shows it full size. + // Cells are recycled, so the bitmap follows the data context. container.DataContextChanged += (_, _) => { - var bitmap = container.DataContext is PointSyncOtherImageLine line + image.Source = container.DataContext is PointSyncOtherImageLine line ? line.ImageItem.GetBitmapCropped() : null; - image.Source = bitmap; - container.IsVisible = bitmap != null; - ToolTip.SetTip(container, bitmap == null - ? null - : new Border - { - Background = previewBackground, - Padding = new Thickness(3), - Child = new Image { Source = bitmap, Stretch = Stretch.None }, - }); + container.IsVisible = image.Source != null; }; return container; From e2ffabf12f0f24d85e855f4f2b34bdb5f0648fbc Mon Sep 17 00:00:00 2001 From: niksedk Date: Sun, 11 Oct 2026 07:01:05 +0200 Subject: [PATCH 050/306] Point sync via other: Ctrl+Plus/Minus image zoom and Ctrl+G go to line (#15911) Same shortcuts as the OCR window. Ctrl+G works in the grid used last. Co-Authored-By: Claude Opus 5.5 --- docs/features/point-sync-via-other.md | 2 + .../PointSyncViaOtherViewModel.cs | 89 +++++++++++++++++++ .../PointSyncViaOtherWindow.cs | 28 ++++-- .../Sync/PointSync/PointSyncWindowTests.cs | 19 ++++ 4 files changed, 131 insertions(+), 7 deletions(-) diff --git a/docs/features/point-sync-via-other.md b/docs/features/point-sync-via-other.md index 310a29fcfad..7d1df34114f 100644 --- a/docs/features/point-sync-via-other.md +++ b/docs/features/point-sync-via-other.md @@ -39,6 +39,8 @@ The window remembers its size and position between sessions. | Shortcut | Action | |----------|--------| | Ctrl+P | View the selected image of an image based reference full size (arrow keys step to the previous/next image) | +| Ctrl+Plus / Ctrl+Minus | Make the images of an image based reference bigger/smaller | +| Ctrl+G | Go to line number, in the grid used last (the left one at first) | | Delete / Backspace | Remove the selected sync point (sync point list focused) | | F1 | Help | | Escape | Cancel | diff --git a/src/ui/Features/Sync/PointSyncViaOther/PointSyncViaOtherViewModel.cs b/src/ui/Features/Sync/PointSyncViaOther/PointSyncViaOtherViewModel.cs index 2be1a759136..a2f704c27b1 100644 --- a/src/ui/Features/Sync/PointSyncViaOther/PointSyncViaOtherViewModel.cs +++ b/src/ui/Features/Sync/PointSyncViaOther/PointSyncViaOtherViewModel.cs @@ -8,6 +8,7 @@ using Nikse.SubtitleEdit.Features.Main; using Nikse.SubtitleEdit.Features.Shared; using Nikse.SubtitleEdit.Features.Shared.FindText; +using Nikse.SubtitleEdit.Features.Shared.GoToLineNumber; using Nikse.SubtitleEdit.Features.Shared.ShowImage; using Nikse.SubtitleEdit.Features.Sync.PointSync.SetSyncPoint; using Nikse.SubtitleEdit.Logic; @@ -38,6 +39,17 @@ public partial class PointSyncViaOtherViewModel : ObservableObject public bool IsOtherTextBased => !IsOtherImageBased; + // Size of the other grid's images - Ctrl+Plus/Minus zoom, like the OCR window. + [ObservableProperty] private double _imageMaxHeight = DefaultImageMaxHeight; + [ObservableProperty] private double _imageMaxWidth = DefaultImageMaxHeight * 5; + + private const double DefaultImageMaxHeight = 60; + private const double MinImageMaxHeight = 25; + private const double MaxImageMaxHeight = 300; + + /// The grid Ctrl+G goes to a line in - the one focused last, the left one at first. + public bool IsOtherGridActive { get; set; } + public ObservableCollection SyncPoints { get; set; } [ObservableProperty] private SyncPoint? _selectedSyncPoint; @@ -55,6 +67,10 @@ public partial class PointSyncViaOtherViewModel : ObservableObject /// Set by the window; scrolls the "other subtitle" grid to a line without selecting it. public Action? ScrollOtherToLine { get; set; } + /// Set by the window; scrolls a grid to its (just selected) line and focuses it - Ctrl+G. + public Action? GoToLeftLine { get; set; } + public Action? GoToOtherLine { get; set; } + private readonly IFileHelper _fileHelper; private readonly IWindowService _windowService; @@ -460,12 +476,85 @@ private async Task ViewSelectedOtherImage() } } + /// Ctrl+G, like the OCR window - in the grid used last. + [RelayCommand] + private async Task GoToLine() + { + var other = IsOtherGridActive && Othersubtitles.Count > 0; + var lines = other ? Othersubtitles : Subtitles; + if (Window == null || lines.Count == 0) + { + return; + } + + var selected = other ? SelectedOtherSubtitle : SelectedSubtitle; + var result = await _windowService.ShowDialogAsync(Window, vm => + { + vm.Initialize(selected == null ? 1 : lines.IndexOf(selected) + 1, lines.Count); + }); + + if (!result.OkPressed || result.LineNumber is not { } lineNumber || lineNumber < 1 || lineNumber > lines.Count) + { + return; + } + + var line = lines[(int)lineNumber - 1]; + if (other) + { + SelectedOtherSubtitle = line; + GoToOtherLine?.Invoke(line); + } + else + { + SelectedSubtitle = line; + GoToLeftLine?.Invoke(line); + } + } + + private void ZoomImages(double factor) + { + var height = ImageMaxHeight * factor; + if (height < MinImageMaxHeight || height > MaxImageMaxHeight) + { + return; + } + + // TableView rows auto-size to their content, so the bound sizes re-measure the rows. + ImageMaxHeight = height; + ImageMaxWidth = height * 5; + } + internal void OnKeyDown(KeyEventArgs e) { if (e.Key == Key.Escape) { Cancel(); } + else if (e.Key == Key.G && e.KeyModifiers == KeyModifiers.Control) + { + e.Handled = true; + Dispatcher.UIThread.Post(async void () => + { + try + { + await GoToLine(); + } + catch (Exception exception) + { + Se.LogError(exception); + } + }); + } + else if (e.Key is Key.Add or Key.OemPlus && e.KeyModifiers.HasFlag(KeyModifiers.Control) && IsOtherImageBased) + { + e.Handled = true; + ZoomImages(1.1); + } + else if (e.Key is Key.Subtract or Key.OemMinus && e.KeyModifiers.HasFlag(KeyModifiers.Control) && IsOtherImageBased) + { + e.Handled = true; + ZoomImages(0.9); + } else if (e.Key == Key.P && e.KeyModifiers == KeyModifiers.Control && IsOtherImageBased) { // Same shortcut as the OCR window's "Show image". diff --git a/src/ui/Features/Sync/PointSyncViaOther/PointSyncViaOtherWindow.cs b/src/ui/Features/Sync/PointSyncViaOther/PointSyncViaOtherWindow.cs index 3b820cd0694..e660d636551 100644 --- a/src/ui/Features/Sync/PointSyncViaOther/PointSyncViaOtherWindow.cs +++ b/src/ui/Features/Sync/PointSyncViaOther/PointSyncViaOtherWindow.cs @@ -280,10 +280,7 @@ void UpdateBadge() } private const double ImageModeMinWindowWidth = 1400; - private const double ImageMaxWidth = 300; - private const double ImageMaxHeight = 60; - - private static SeTableViewColumn MakeImageColumn() + private static SeTableViewColumn MakeImageColumn(PointSyncViaOtherViewModel vm) { // One brush for every image cell, as in the OCR window. var previewBackground = ImagePreviewBackground.CreateBrush(); @@ -299,9 +296,10 @@ private static SeTableViewColumn MakeImageColumn() var image = new Image { Stretch = Stretch.Uniform, - MaxWidth = ImageMaxWidth, - MaxHeight = ImageMaxHeight, }; + // Ctrl+Plus/Minus zoom, like the OCR window. + image.Bind(Image.MaxWidthProperty, new Binding(nameof(vm.ImageMaxWidth)) { Source = vm }); + image.Bind(Image.MaxHeightProperty, new Binding(nameof(vm.ImageMaxHeight)) { Source = vm }); var container = new Border { Background = previewBackground, @@ -505,6 +503,15 @@ private static Grid MakeSubtitleView(PointSyncViaOtherViewModel vm) } }; + // Ctrl+G goes to a line in the grid used last. + dataGrid.GotFocus += (_, _) => vm.IsOtherGridActive = false; + vm.GoToLeftLine = line => + { + dataGrid.ScrollIntoView(line); + TableViewExtras.CenterRow(dataGrid, line); + TableViewExtras.FocusRow(dataGrid); + }; + grid.Add(panelHeader, 0); grid.Add(UiUtil.MakeBorderForControlNoPadding(dataGrid), 1); @@ -584,7 +591,7 @@ private static Grid MakeSubtitleOtherView(PointSyncViaOtherViewModel vm) // Image based other subtitle (sup, PGS/VobSub track in mkv/mp4): the bitmaps, shown like // the OCR window shows them (#15911). - var imageColumn = MakeImageColumn(); + var imageColumn = MakeImageColumn(vm); imageColumn.Bind(SeTableViewColumn.IsVisibleProperty, new Binding(nameof(vm.IsOtherImageBased)) { Source = vm, Mode = BindingMode.OneWay }); // A column with a bound visibility needs every column to go through a manager. @@ -622,6 +629,13 @@ private static Grid MakeSubtitleOtherView(PointSyncViaOtherViewModel vm) // Clicking a line in the left grid scrolls this grid to the matching time (#12529) // without touching its selection. vm.ScrollOtherToLine = line => dataGridSubtitle.ScrollIntoView(line); + dataGridSubtitle.GotFocus += (_, _) => vm.IsOtherGridActive = true; + vm.GoToOtherLine = line => + { + dataGridSubtitle.ScrollIntoView(line); + TableViewExtras.CenterRow(dataGridSubtitle, line); + TableViewExtras.FocusRow(dataGridSubtitle); + }; grid.Add(panelOtherHeader, 0); grid.Add(UiUtil.MakeBorderForControlNoPadding(dataGridSubtitle), 1); diff --git a/tests/UI/Features/Sync/PointSync/PointSyncWindowTests.cs b/tests/UI/Features/Sync/PointSync/PointSyncWindowTests.cs index cc5d0fdb2fe..9c0740d8c35 100644 --- a/tests/UI/Features/Sync/PointSync/PointSyncWindowTests.cs +++ b/tests/UI/Features/Sync/PointSync/PointSyncWindowTests.cs @@ -1,5 +1,6 @@ using Avalonia.Controls; using Avalonia.Headless.XUnit; +using Avalonia.Input; using Avalonia.LogicalTree; using Nikse.SubtitleEdit.Core.BluRaySup; using Nikse.SubtitleEdit.Features.Main; @@ -298,4 +299,22 @@ public async Task PointSyncViaOther_SubtitleFile_IsNotAContainer() File.Delete(fileName); } } + + [AvaloniaFact] + public void PointSyncViaOther_CtrlPlusMinus_ZoomsImagesOnlyWhenImageBased() + { + var vm = new PointSyncViaOtherViewModel(new FileHelper(), new WindowService(new NullServiceProvider())); + var start = vm.ImageMaxHeight; + + vm.OnKeyDown(new KeyEventArgs { Key = Key.OemPlus, KeyModifiers = KeyModifiers.Control }); + Assert.Equal(start, vm.ImageMaxHeight); + + vm.IsOtherImageBased = true; + vm.OnKeyDown(new KeyEventArgs { Key = Key.OemPlus, KeyModifiers = KeyModifiers.Control }); + Assert.True(vm.ImageMaxHeight > start); + + vm.OnKeyDown(new KeyEventArgs { Key = Key.OemMinus, KeyModifiers = KeyModifiers.Control }); + vm.OnKeyDown(new KeyEventArgs { Key = Key.OemMinus, KeyModifiers = KeyModifiers.Control }); + Assert.True(vm.ImageMaxHeight < start); + } } From 8a3efa1c5561299330f207cf0902640c651e22ec Mon Sep 17 00:00:00 2001 From: niksedk Date: Sun, 11 Oct 2026 07:17:44 +0200 Subject: [PATCH 051/306] Prepare v5.3.0-rc2 Co-Authored-By: Claude Opus 5.5 --- change-log.txt | 33 ++++++++++++++++++++++++++++ src/ui/Assets/Languages/English.json | 23 ++++++++++++++++++- src/ui/Logic/Config/Se.cs | 2 +- 3 files changed, 56 insertions(+), 2 deletions(-) diff --git a/change-log.txt b/change-log.txt index f00f02994d7..faacf7b788e 100644 --- a/change-log.txt +++ b/change-log.txt @@ -1,6 +1,39 @@  Subtitle Edit Changelog +----------------------------------------------------------------------------------------------------- + +v5.3.0-rc2 (11th of October 2026) + +* Add command palette (Ctrl+Shift+K) +* Add experimental mpv Metal video player on macOS +* ASSA draw: new layout with menus, undo/redo, scale/rotate, line/curve tools, backgrounds and SVG import +* ASSA draw: add shape tool with shape library (speech bubbles, arrows, symbols) and eyedropper +* Train nOCR window: new layout with preview, start from an existing database, several font sizes and progress +* Improve nOCR accuracy (scale-aware matching, auto spaces, shadow threshold, better Latin.nocr) +* Point sync via other subtitle: load subtitles embedded in mkv/mp4 and .sup files - thx mmmsh-125 +* Find/Replace: delete individual history entries - thx FunkyKnilch +* OCR: remember image pre-processing settings +* Format combo: show full format names in the drop-down +* Improve layout for long translated texts in Spell check and Batch convert - thx Slava46 +* Hide "Close video file" menu item when no video is loaded +* Improve performance of Netflix check, spell check, shot change extend, Video OCR, VobSub writing and more +* Fix "Could not convert to double" when typing a frame rate in Change frame rate - thx jojorne +* Fix Video OCR ignoring the llama.cpp external server setting - thx GitGerby +* Fix SSA losing [Events] header when [Fonts]/[Graphics] precede it +* Fix TTML italic/bold/underline inheritance +* Fix MS Office Workbook, D-Cinema Interop, Projection Subtitle List, FCP XML and IMSC/TTML frame-rate issues +* Fix Apply duration limits ignoring unticked rows and Modify selection duration defaults +* Fix OCR fix stripping accents from English loanwords/names +* Fix shot changes not sorted when assigned or loaded +* Fix nOCR trainer Stop button and closing saving a partial database +* Fix image export "Box per line" boxes shifted left +* Update SharpCompress to 0.50.5 +* Update German translation - thx FunkyKnilch +* Update Polish translation - thx potplayer-fanpack +* Update Russian translation - thx LazySwift + + ----------------------------------------------------------------------------------------------------- v5.3.0-rc1 (10th of October 2026) diff --git a/src/ui/Assets/Languages/English.json b/src/ui/Assets/Languages/English.json index 7be2c41cc24..3be4d1f8a2b 100644 --- a/src/ui/Assets/Languages/English.json +++ b/src/ui/Assets/Languages/English.json @@ -1,6 +1,6 @@ { "title": "Subtitle Edit", - "version": "v5.3.0-rc1", + "version": "v5.3.0-rc2", "translatedBy": "", "cultureName": "en-US", "general": { @@ -595,6 +595,8 @@ "showStartColumn": "Show \"Start\" column", "showHideColumn": "Show \"Hide\" column", "showHistory": "Show history", + "clearHistory": "Clear history", + "removeFromHistory": "Remove from history", "showLayerColumn": "Show \"Layer\" column", "showPreview": "Show preview", "showShotChangesList": "Show shot changes list", @@ -890,6 +892,7 @@ "multipleReplace": "_Multiple replace...", "fillSelectedLinesWithClipboard": "Fill selected lines with clipboard text", "goToLineNumber": "_Go to line number...", + "commandPalette": "Command _palette...", "rightToLeftMode": "R_ight-to-left mode", "modifySelectionDotDotDot": "Modify _selection...", "tools": "_Tools", @@ -1082,6 +1085,9 @@ "charactersPerSecond": "Chars/second: {0}", "chooseColumn": "Choose column", "columnPaste": "Column paste", + "commandPaletteTitle": "Command palette", + "commandPaletteSearchPlaceholder": "Type to search commands...", + "commandPaletteNoCommandsFound": "No matching commands", "createdEmptyTranslation": "Created empty translation from current subtitle", "deleteText": "Delete text", "deleteTextAndShiftCellsUp": "Delete text and shift cells up", @@ -4217,6 +4223,19 @@ "startTraining": "Start training", "abortTraining": "Abort training", "trainingOptions": "Training options", + "trainNOcrSubtitle": "Renders characters in installed fonts and teaches nOCR their shapes. Pick the fonts your subtitles use.", + "xFontsSelected": "{0:#,##0} selected", + "subtitleFonts": "Subtitle fonts", + "emptyDatabase": "Empty database", + "startFromDatabase": "Start from", + "startFromDatabaseHint": "Add the trained characters to a copy of an existing database (recommended) or start from an empty one", + "fontSizes": "Font sizes", + "fontSizesHint": "Font sizes in pixels to render at, separated by commas, e.g. \"30, 40\"", + "numberOfLineSegmentsHint": "Lines checked per character - more is stricter but slower", + "enterDatabaseName": "Enter a name for the database", + "trainingFontXSizeYCharacterZ": "Training \"{0}\" at {1} px: {2}", + "xLearned": "{0:#,##0} learned", + "xSkipped": "{0:#,##0} skipped", "charactersToTrain": "Characters to train", "letterCombinationsToTrain": "Letter combinations that might be split to one image", "importCharactersFromSubtitleFile": "Import characters from subtitle file...", @@ -4270,6 +4289,7 @@ "drawPosition": "Position", "drawZoom": "Zoom", "drawShapeTool": "Shape tool", + "drawColorPickerTool": "Pick color from a shape or the background", "drawShapeLibrary": "Shape library", "drawShapeLibraryHint": "Drag on the canvas to place a shape, or click for the default size. Shift keeps its proportions.", "drawAddToLibrary": "Add to shape library...", @@ -4318,6 +4338,7 @@ "drawBackgroundNone": "No background", "drawBackgroundStretch": "Stretch image to canvas", "drawBackgroundOpacity": "Background opacity", + "drawBackgroundVideoFrameFailed": "Could not get a video frame at {0}.", "drawImages": "Images", "drawConvertToLine": "Convert segment to line", "drawConvertShapeToCurves": "Convert all lines to curves", diff --git a/src/ui/Logic/Config/Se.cs b/src/ui/Logic/Config/Se.cs index a0b4ab50d67..3674f663fad 100644 --- a/src/ui/Logic/Config/Se.cs +++ b/src/ui/Logic/Config/Se.cs @@ -21,7 +21,7 @@ public class Se internal const int CurrentShortcutsMigrationVersion = 6; internal const int CurrentLayoutMigrationVersion = 2; - public static string Version { get; set; } = "v5.3.0-rc1"; + public static string Version { get; set; } = "v5.3.0-rc2"; public SeGeneral General { get; set; } = new(); public List Shortcuts { get; set; } = new(); From 18bf446c2dc69728e1b6832389ffbd05d6ddf99d Mon Sep 17 00:00:00 2001 From: niksedk Date: Sun, 11 Oct 2026 07:28:04 +0200 Subject: [PATCH 052/306] AI review: add Bonsai 27B 1-bit (Q1_0) llama.cpp model, English only PrismML's 1-bit Bonsai 27B (3.8 GB) runs on the pinned upstream llama.cpp b11425. Tested with SE's review protocol it found 8/8 planted English errors with no false edits (Qwen 3.5 4B: 7/8), but was weak in Danish and poor at translation, so it is added to ReviewModels only. NoThinking is required - with thinking on a 15-line batch took ~400 s. Co-Authored-By: Claude Opus 5.5 --- src/libuilogic/LlamaCpp/LlamaCppServerManager.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/libuilogic/LlamaCpp/LlamaCppServerManager.cs b/src/libuilogic/LlamaCpp/LlamaCppServerManager.cs index c403fb4760c..0c515a95be7 100644 --- a/src/libuilogic/LlamaCpp/LlamaCppServerManager.cs +++ b/src/libuilogic/LlamaCpp/LlamaCppServerManager.cs @@ -296,6 +296,15 @@ public static class LlamaCppServerManager new LlamaCppModel("Phi-4 mini 3.8B (Q4_K_M)", "microsoft_Phi-4-mini-instruct-Q4_K_M.gguf", "2.5 GB", "https://huggingface.co/bartowski/microsoft_Phi-4-mini-instruct-GGUF/resolve/main/microsoft_Phi-4-mini-instruct-Q4_K_M.gguf"), + // PrismML Bonsai 27B, 1-bit (Q1_0, upstream llama.cpp - the ternary Q2_0/PQ2_0/PTQ1_0 files + // need PrismML's fork). English only: tested 2026-10-11 on b11425 with the review protocol it + // found 8/8 planted English errors with no false edits (Qwen 3.5 4B: 7/8), but only 2/6 in + // Danish, and its translations were poor - hence review only, not in TranslateModels. + // NoThinking is required: with thinking on a 15-line batch took ~400 s. ~12 tok/s on an M4. + new LlamaCppModel("Bonsai 27B 1-bit (Q1_0) - English only", "Bonsai-27B-Q1_0.gguf", "3.8 GB", + "https://huggingface.co/prism-ml/Bonsai-27B-gguf/resolve/main/Bonsai-27B-Q1_0.gguf", + NoThinking: true), + // EuroLLM 2512 - all 24 official EU languages (incl. Danish/Swedish/Norwegian/Finnish, which // the Hy-MT2 family lacks entirely), so these are the best picks for proofreading European // subtitles. Replaces the older EuroLLM-9B-Instruct entry; anyone who already downloaded that From d04453c3818d2f97ef678e1ab9df222fe2acfc79 Mon Sep 17 00:00:00 2001 From: niksedk Date: Sun, 11 Oct 2026 07:33:30 +0200 Subject: [PATCH 053/306] nOCR: glyph text with italic tags sets its own formatting A glyph trained as "it" (italic i joined to an upright t) was appended as-is, nesting its tags inside the word's italic tag: "yadait?". Tagged glyphs now emit their own italic/upright runs, do not vote on the word's slant, and spaces between runs follow the text on both sides. Co-Authored-By: Claude Opus 5.5 --- src/libuilogic/Ocr/ItalicTextMerger.cs | 101 +++++++++++++++--- tests/libuilogic/Ocr/ItalicTextMergerTests.cs | 30 ++++++ 2 files changed, 119 insertions(+), 12 deletions(-) diff --git a/src/libuilogic/Ocr/ItalicTextMerger.cs b/src/libuilogic/Ocr/ItalicTextMerger.cs index 4d5a850545b..a1e59a5737f 100644 --- a/src/libuilogic/Ocr/ItalicTextMerger.cs +++ b/src/libuilogic/Ocr/ItalicTextMerger.cs @@ -186,26 +186,103 @@ private static List BuildSegments(List words) var segments = new List(words.Count); for (var i = 0; i < words.Count; i++) { - var isWhitespace = slants[i] == WordSlant.Whitespace; + if (slants[i] == WordSlant.Whitespace) + { + segments.Add(new TextSegment { Text = ConcatCharTexts(words[i].Chars), IsWhitespace = true }); + continue; + } - // Words and whitespace alternate, so whitespace is inside italic only when the words - // on both sides of it are italic. - var shouldBeItalic = isWhitespace - ? i > 0 && i < words.Count - 1 && italic[i - 1] && italic[i + 1] - : italic[i]; + if (!ContainsItalicTag(words[i].Chars)) + { + segments.Add(new TextSegment { Text = ConcatCharTexts(words[i].Chars), ShouldBeItalic = italic[i] }); + continue; + } - segments.Add(new TextSegment + foreach (var ch in words[i].Chars) { - Text = ConcatCharTexts(words[i].Chars), - ShouldBeItalic = shouldBeItalic, - IsWhitespace = isWhitespace - }); + if (HasItalicTag(ch.Text)) + { + AddTaggedRuns(segments, ch.Text); + } + else + { + segments.Add(new TextSegment { Text = ch.Text, ShouldBeItalic = italic[i] }); + } + } + } + + // Words and whitespace alternate, so whitespace is inside italic only when the text on + // both sides of it is italic. + for (var i = 0; i < segments.Count; i++) + { + if (segments[i].IsWhitespace) + { + segments[i].ShouldBeItalic = i > 0 && i < segments.Count - 1 && + segments[i - 1].ShouldBeItalic && segments[i + 1].ShouldBeItalic; + } } // Post-process to merge consecutive segments with same formatting return MergeConsecutiveSegments(segments); } + /// + /// A glyph trained with italic tags in its text (e.g. "<i>i</i>t" for an italic i + /// joined to an upright t) sets its own formatting: tagged parts are italic, the rest upright, + /// whatever the word around it is. Appending the text as-is nested the tags inside the word's + /// italic tag ("<i>yada<i>i</i>t?</i>"). + /// + private static void AddTaggedRuns(List segments, string text) + { + var inItalic = false; + var start = 0; + var i = 0; + while (i < text.Length) + { + var tagLength = text.Length - i >= 3 && string.Compare(text, i, "", 0, 3, StringComparison.OrdinalIgnoreCase) == 0 ? 3 + : text.Length - i >= 4 && string.Compare(text, i, "", 0, 4, StringComparison.OrdinalIgnoreCase) == 0 ? 4 + : 0; + if (tagLength == 0) + { + i++; + continue; + } + + if (i > start) + { + segments.Add(new TextSegment { Text = text.Substring(start, i - start), ShouldBeItalic = inItalic }); + } + + inItalic = tagLength == 3; + i += tagLength; + start = i; + } + + if (start < text.Length) + { + segments.Add(new TextSegment { Text = text.Substring(start), ShouldBeItalic = inItalic }); + } + } + + private static bool HasItalicTag(string text) + { + return text.Contains("", StringComparison.OrdinalIgnoreCase) || + text.Contains("", StringComparison.OrdinalIgnoreCase); + } + + private static bool ContainsItalicTag(List chars) + { + for (var i = 0; i < chars.Count; i++) + { + if (HasItalicTag(chars[i].Text)) + { + return true; + } + } + + return false; + } + /// + /// The subtitle to read. + /// Stop collecting texts once past this many characters. /// All non-empty lines, also those past the stop. private static string GetNonEmptyTexts(Subtitle subtitle, int stopAfterBytes, out int nonEmptyCount) { diff --git a/src/libse/Common/Utilities.cs b/src/libse/Common/Utilities.cs index 542e42fcc2f..eb9cfd23912 100644 --- a/src/libse/Common/Utilities.cs +++ b/src/libse/Common/Utilities.cs @@ -968,6 +968,8 @@ public static string UnbreakLine(string text) /// /// Removes ASSA override blocks ({\...}), the {Kara Effector...} block and \N, \n, \h. /// + /// The text to remove tags from. + /// Return an empty string when what is left is only a drawing ("m 0 0 l ..."). /// /// Also remove any other closed {...} block. ASSA renderers never draw a brace block, so /// fansubbers use {...} for comments (#15584); an unclosed '{' is drawn as text. Off by diff --git a/src/libse/VobSub/IfoParser.cs b/src/libse/VobSub/IfoParser.cs index e59e7338508..fa601455c4d 100644 --- a/src/libse/VobSub/IfoParser.cs +++ b/src/libse/VobSub/IfoParser.cs @@ -336,6 +336,8 @@ public static string GetIfoFileName(string vobFileName) /// A file in named ignoring case (a DVD /// copied to a case sensitive file system may be all lowercase: video_ts/vts_01_1.vob), or null. /// + /// The folder to look in. + /// The file name to find. /// The folder's files, listed on the first miss and reused. internal static string FindFile(string folder, string fileName, ref string[] folderFiles) { diff --git a/src/ui/Features/Video/TextToSpeech/SpeakFromLine/SpeakFromLineReader.cs b/src/ui/Features/Video/TextToSpeech/SpeakFromLine/SpeakFromLineReader.cs index 57ca7b6f2ca..3bcaa4c9a6b 100644 --- a/src/ui/Features/Video/TextToSpeech/SpeakFromLine/SpeakFromLineReader.cs +++ b/src/ui/Features/Video/TextToSpeech/SpeakFromLine/SpeakFromLineReader.cs @@ -48,7 +48,7 @@ public async Task RunAsync(IReadOnlyList texts, Action onLineStarti pending = GenerateAsync(texts[0], folder, cancellationToken); for (var i = 0; i < texts.Count; i++) { - var fileName = await pending; + var fileName = await pending!; pending = i + 1 < texts.Count ? GenerateAsync(texts[i + 1], folder, cancellationToken) : null; cancellationToken.ThrowIfCancellationRequested(); if (fileName == null) diff --git a/src/ui/Features/Video/TextToSpeech/TextToSpeechViewModel.cs b/src/ui/Features/Video/TextToSpeech/TextToSpeechViewModel.cs index fa8aceb983d..af80c611636 100644 --- a/src/ui/Features/Video/TextToSpeech/TextToSpeechViewModel.cs +++ b/src/ui/Features/Video/TextToSpeech/TextToSpeechViewModel.cs @@ -4203,7 +4203,7 @@ private async Task TranscribePerLineCloneClipsAsync() } } - if (Math.Abs(best.StartTime.TotalMilliseconds - paragraph.StartTime.TotalMilliseconds) > 500) + if (best == null || Math.Abs(best.StartTime.TotalMilliseconds - paragraph.StartTime.TotalMilliseconds) > 500) { return null; } diff --git a/src/ui/Features/Video/TextToSpeech/VoiceManager/VoiceManagerWindow.cs b/src/ui/Features/Video/TextToSpeech/VoiceManager/VoiceManagerWindow.cs index 5aa4fd76f1d..06a7ce19c06 100644 --- a/src/ui/Features/Video/TextToSpeech/VoiceManager/VoiceManagerWindow.cs +++ b/src/ui/Features/Video/TextToSpeech/VoiceManager/VoiceManagerWindow.cs @@ -216,7 +216,7 @@ private Control MakeToolbar(VoiceManagerViewModel vm) var searchBox = new TextBox { - Watermark = Se.Language.Video.TextToSpeech.SearchVoices, + PlaceholderText = Se.Language.Video.TextToSpeech.SearchVoices, MinWidth = 180, VerticalAlignment = VerticalAlignment.Center, [!TextBox.TextProperty] = new Binding(nameof(vm.FilterText)) { Mode = BindingMode.TwoWay }, diff --git a/src/ui/Logic/AccessibleLabels.cs b/src/ui/Logic/AccessibleLabels.cs index 2e5c4669929..b6236b3b60c 100644 --- a/src/ui/Logic/AccessibleLabels.cs +++ b/src/ui/Logic/AccessibleLabels.cs @@ -53,9 +53,9 @@ public static List Apply(Control root) } // A placeholder text is the label sighted users see in an unlabeled box. - if (control is TextBox { Watermark.Length: > 0 and <= 80 } textBox) + if (control is TextBox { PlaceholderText.Length: > 0 and <= 80 } textBox) { - AutomationProperties.SetName(control, textBox.Watermark); + AutomationProperties.SetName(control, textBox.PlaceholderText); labeled.Add(control); } } diff --git a/src/ui/Logic/TableViewExtras.cs b/src/ui/Logic/TableViewExtras.cs index 5b7ac092b99..997140803bc 100644 --- a/src/ui/Logic/TableViewExtras.cs +++ b/src/ui/Logic/TableViewExtras.cs @@ -38,10 +38,10 @@ public SeTableViewColumn() HorizontalContentAlignment = Avalonia.Layout.HorizontalAlignment.Stretch; } - public static readonly StyledProperty IsVisibleProperty = + public static new readonly StyledProperty IsVisibleProperty = AvaloniaProperty.Register(nameof(IsVisible), defaultValue: true); - public bool IsVisible + public new bool IsVisible { get => GetValue(IsVisibleProperty); set => SetValue(IsVisibleProperty, value); diff --git a/tests/UI/Controls/SkiaAudioVisualizerRenderTests.cs b/tests/UI/Controls/SkiaAudioVisualizerRenderTests.cs index c18e7df2b9f..109cc1eb736 100644 --- a/tests/UI/Controls/SkiaAudioVisualizerRenderTests.cs +++ b/tests/UI/Controls/SkiaAudioVisualizerRenderTests.cs @@ -131,7 +131,7 @@ private static byte[] Capture(Window window) using var frame = window.CaptureRenderedFrame()!; using var stream = new MemoryStream(); - frame.Save(stream); + frame.Save(stream, PngBitmapEncoderOptions.Default); return stream.ToArray(); } } diff --git a/tests/UI/Features/Files/Compare/CompareEditTests.cs b/tests/UI/Features/Files/Compare/CompareEditTests.cs index 26e251c2a70..10a3a8c344c 100644 --- a/tests/UI/Features/Files/Compare/CompareEditTests.cs +++ b/tests/UI/Features/Files/Compare/CompareEditTests.cs @@ -271,7 +271,7 @@ public void OpeningAndClosingTheEditor_DoesNotMoveTheList() scrollViewer.Offset = new Vector(0, 600); Settle(window); - var containers = vm.RowsView.GetRealizedContainers().ToList(); + var containers = vm.RowsView!.GetRealizedContainers().ToList(); // Near the top, so the opened editor fits in the view - focusing it must not need a scroll // (rows are taller with the Linux fonts on CI, and a lower row's editor would overflow). var row = (CompareRow)containers[2].DataContext!; @@ -351,7 +351,7 @@ public void ClosingTheEditor_KeepsTheView_WhenTheFocusedRowIsScrolledAway() var scrollViewer = vm.RowsView!.GetVisualDescendants().OfType().First(); vm.SelectRow(420); Settle(window); - ((Control)vm.RowsView.ContainerFromIndex(420)!).Focus(); + ((Control)vm.RowsView!.ContainerFromIndex(420)!).Focus(); Settle(window); while (FirstVisibleRow(vm, scrollViewer) < 500) { diff --git a/tests/UI/Features/Main/EditorStyleLayoutTests.cs b/tests/UI/Features/Main/EditorStyleLayoutTests.cs index d22f79c28eb..efca1b4e8b4 100644 --- a/tests/UI/Features/Main/EditorStyleLayoutTests.cs +++ b/tests/UI/Features/Main/EditorStyleLayoutTests.cs @@ -55,7 +55,7 @@ public void Layout14_PutsGridWithEditBoxAndVideoSideBySide_AboveTheTimeline() var timelineGrid = Assert.IsType(tracks.GetLogicalParent()); Assert.Same(timelineGrid, vm.AudioVisualizer!.GetLogicalParent()); Assert.Equal(0, Grid.GetRow(tracks)); - Assert.Equal(1, Grid.GetRow(vm.AudioVisualizer)); + Assert.Equal(1, Grid.GetRow(vm.AudioVisualizer!)); } finally { diff --git a/tests/UI/Features/Video/TextToSpeech/Engines/OpenAiCompatibleSpeechTests.cs b/tests/UI/Features/Video/TextToSpeech/Engines/OpenAiCompatibleSpeechTests.cs index c2e3f570b82..4a993dd6a57 100644 --- a/tests/UI/Features/Video/TextToSpeech/Engines/OpenAiCompatibleSpeechTests.cs +++ b/tests/UI/Features/Video/TextToSpeech/Engines/OpenAiCompatibleSpeechTests.cs @@ -91,7 +91,7 @@ public void ABadModelListIsEmptyRatherThanAThrow(string json) public async Task EachProviderKeepsItsOwnKeyAndModel() { var s = Se.Settings.Video.TextToSpeech; - var saved = (s.OpenAiCompatibleProvider, s.OpenAiApiKey, s.OpenRouterTtsApiKey, s.OpenAiModel, s.OpenRouterTtsModel); + var saved = (s.OpenAiCompatibleProvider, s.OpenAiApiKey, Se.Settings.Providers.OpenRouterApiKey, s.OpenAiModel, s.OpenRouterTtsModel); try { OpenAiCompatibleSpeech.SetApiKey(OpenAiCompatibleSpeech.ProviderOpenAi, "sk-openai"); @@ -119,7 +119,7 @@ public async Task EachProviderKeepsItsOwnKeyAndModel() } finally { - (s.OpenAiCompatibleProvider, s.OpenAiApiKey, s.OpenRouterTtsApiKey, s.OpenAiModel, s.OpenRouterTtsModel) = saved; + (s.OpenAiCompatibleProvider, s.OpenAiApiKey, Se.Settings.Providers.OpenRouterApiKey, s.OpenAiModel, s.OpenRouterTtsModel) = saved; } } diff --git a/tests/UI/Features/Video/TextToSpeech/VoiceManager/DownloadVoicePacksViewModelTests.cs b/tests/UI/Features/Video/TextToSpeech/VoiceManager/DownloadVoicePacksViewModelTests.cs index 6df97752d35..e48f65f2887 100644 --- a/tests/UI/Features/Video/TextToSpeech/VoiceManager/DownloadVoicePacksViewModelTests.cs +++ b/tests/UI/Features/Video/TextToSpeech/VoiceManager/DownloadVoicePacksViewModelTests.cs @@ -27,6 +27,7 @@ private sealed class FakeDownloadService : IVoicePackDownloadService private sealed class FakeCloneEngine : ITtsEngine { + public override string ToString() => Name; public List Imported { get; } = new(); public string Name => "Fake clone engine"; public string Description => string.Empty; diff --git a/tests/UI/Features/Video/TextToSpeech/VoiceManager/VoiceManagerViewModelTests.cs b/tests/UI/Features/Video/TextToSpeech/VoiceManager/VoiceManagerViewModelTests.cs index e84329d9228..dde36df5b46 100644 --- a/tests/UI/Features/Video/TextToSpeech/VoiceManager/VoiceManagerViewModelTests.cs +++ b/tests/UI/Features/Video/TextToSpeech/VoiceManager/VoiceManagerViewModelTests.cs @@ -20,6 +20,7 @@ public class VoiceManagerViewModelTests { private sealed class FakeCloneEngine : ITtsEngine { + public override string ToString() => Name; private readonly string _folder; public FakeCloneEngine(string folder) => _folder = folder; public string Name => "Fake clone engine"; diff --git a/tests/UI/Logic/AccessibleNamesTests.cs b/tests/UI/Logic/AccessibleNamesTests.cs index 204702352d5..31f97ed4659 100644 --- a/tests/UI/Logic/AccessibleNamesTests.cs +++ b/tests/UI/Logic/AccessibleNamesTests.cs @@ -174,9 +174,9 @@ private static string Describe(Control control) parts.Add($"name={control.Name}"); } - if (control is TextBox textBox && !string.IsNullOrEmpty(textBox.Watermark)) + if (control is TextBox textBox && !string.IsNullOrEmpty(textBox.PlaceholderText)) { - parts.Add($"watermark={textBox.Watermark}"); + parts.Add($"watermark={textBox.PlaceholderText}"); } if (control is ContentControl { Content: { } content }) diff --git a/tests/libse/ContainerFormats/TransportStreamTestWriter.cs b/tests/libse/ContainerFormats/TransportStreamTestWriter.cs index 78d7dcbe27b..f6295ec1804 100644 --- a/tests/libse/ContainerFormats/TransportStreamTestWriter.cs +++ b/tests/libse/ContainerFormats/TransportStreamTestWriter.cs @@ -23,7 +23,7 @@ internal sealed class TransportStreamTestWriter public static List ReadPesPackets(string fileName, int packetId) { var result = new List(); - List current = null; + List? current = null; var data = File.ReadAllBytes(fileName); for (var i = 0; i + 188 <= data.Length; i += 188) { diff --git a/tests/libse/SubtitleFormats/NonSubtitlePrecheckRoundTripTest.cs b/tests/libse/SubtitleFormats/NonSubtitlePrecheckRoundTripTest.cs index e44b4950928..00a46499427 100644 --- a/tests/libse/SubtitleFormats/NonSubtitlePrecheckRoundTripTest.cs +++ b/tests/libse/SubtitleFormats/NonSubtitlePrecheckRoundTripTest.cs @@ -21,7 +21,7 @@ public class NonSubtitlePrecheckRoundTripTest }; private static SubtitleFormat Create(string name) => - (SubtitleFormat)System.Activator.CreateInstance(typeof(SubtitleFormat).Assembly.GetType("Nikse.SubtitleEdit.Core.SubtitleFormats." + name)); + (SubtitleFormat)System.Activator.CreateInstance(typeof(SubtitleFormat).Assembly.GetType("Nikse.SubtitleEdit.Core.SubtitleFormats." + name, throwOnError: true)!)!; [Theory] [MemberData(nameof(Formats))] From ef9f8d579a3af27f0fa763eabcc3f5851a627d20 Mon Sep 17 00:00:00 2001 From: niksedk Date: Sun, 11 Oct 2026 14:17:27 +0200 Subject: [PATCH 064/306] Update llama.cpp to b11553 Co-Authored-By: Claude Opus 5.5 --- src/ui/Logic/Download/DownloadHashManager.cs | 51 ++++++++++++------- .../Logic/Download/LlamaCppDownloadService.cs | 2 +- 2 files changed, 35 insertions(+), 18 deletions(-) diff --git a/src/ui/Logic/Download/DownloadHashManager.cs b/src/ui/Logic/Download/DownloadHashManager.cs index 0dae4fd8a5f..e160ac1611e 100644 --- a/src/ui/Logic/Download/DownloadHashManager.cs +++ b/src/ui/Logic/Download/DownloadHashManager.cs @@ -1825,7 +1825,8 @@ public static class WhisperX // otherwise users will be prompted to "update" to the same version they just got. [LlamaCpp.WindowsCpu] = new[] { - "61ad965cd5e17f5026a05b7258d346cb309e7752490c7e11fbedf332e2040db5", // b11425 (current download URL) + "5b1d1648f176d981a22e45e069eb52732437f824ea6a6159ca78e4a4bf792b07", // b11553 (current download URL) + "61ad965cd5e17f5026a05b7258d346cb309e7752490c7e11fbedf332e2040db5", // b11425 "d1cb5f9ef7bbb7068954b4c9767d5b5309e20bcefeb61d4aafc47f9581f38752", // b11149 "2d3b273933027469a6c02529ab6b8dfb09b0357395227ecc1ae9a6277705fbda", // b11070 "7063dfc6b874e7eee0ddf601bdf8e70e6f4a3d708926641ffad046ec51e8e30b", // b10840 @@ -1847,7 +1848,8 @@ public static class WhisperX }, [LlamaCpp.WindowsVulkan] = new[] { - "a3287ac487ab5d128aefddc507db31c087f2a41c6e03b01f37fbe0bc45e8717d", // b11425 (current download URL) + "fd9b554903bf55d696f60c86e259027608cb67ebd2f632c3684cdcda044d7380", // b11553 (current download URL) + "a3287ac487ab5d128aefddc507db31c087f2a41c6e03b01f37fbe0bc45e8717d", // b11425 "ca432b775c5dcb5af85bbdcf22b06b6e2dca792f30334b19ef4a45ee4a512446", // b11149 "91487bd1d145dafb58d7b83fc45b72dc8fae771323191b995daddde984a2e099", // b11070 "a435eeaa106e4861559457fe5532aa1422f0877e7cd2bd6934c085d1d1388731", // b10840 @@ -1869,7 +1871,8 @@ public static class WhisperX }, [LlamaCpp.WindowsCuda] = new[] { - "0d2984647bb9864d8f5a28495605c68f27824e5faf8ad99768fc8b7597745698", // b11425 (current download URL) + "2696e34b159ffe5640452d4e37bb6c063307b3c611e0e549aa55b63f2a157d42", // b11553 (current download URL) + "0d2984647bb9864d8f5a28495605c68f27824e5faf8ad99768fc8b7597745698", // b11425 "d3140fe21ab2e665a706ca27923b27ca264f1c564b5837abea4566cc49c16096", // b11149 "a092b7455a7a856f8e25542e49189e50bf7edf045d4926df26b5210b4f6a043d", // b11070 "8cf247aeebf5f1c9d06e9476279a9c93cfaee71d112eb7bebd68966af8a7c9bc", // b10840 @@ -1894,7 +1897,8 @@ public static class WhisperX // going back to b9145. [LlamaCpp.WindowsCuda13] = new[] { - "039da91a2ddb9d99d7d235416eb895943d58040830c7c8bd075f88924bf732d1", // b11425 (current download URL) + "3f1e149ec3cbcfe75a542f4063f50b29d689ed0abcaea0f3a00021eec5a8b7db", // b11553 (current download URL) + "039da91a2ddb9d99d7d235416eb895943d58040830c7c8bd075f88924bf732d1", // b11425 "24fa117fa4d34e24d121e13d7d1a542adbfc0fca24c96409e0824e9b5666072c", // b11149 "a87b75f6e4db0c5b946566fcb40483744d7e06ff498658bc1f39b9d5747149d8", // b11070 "3e3e8c463d1d92beddca6f69d60956cdd5fad7b4d7a589ea0aecf00b51f54fbe", // b10840 @@ -1909,7 +1913,8 @@ public static class WhisperX [LlamaCpp.WindowsCudaRuntime] = new[] { // Identical bytes to b9297 — the CUDA 12.4 redistributable is unchanged across releases. - "8c79a9b226de4b3cacfd1f83d24f962d0773be79f1e7b75c6af4ded7e32ae1d6", // b11425 (current download URL) + "8c79a9b226de4b3cacfd1f83d24f962d0773be79f1e7b75c6af4ded7e32ae1d6", // b11553 (current download URL) + "8c79a9b226de4b3cacfd1f83d24f962d0773be79f1e7b75c6af4ded7e32ae1d6", // b11425 "8c79a9b226de4b3cacfd1f83d24f962d0773be79f1e7b75c6af4ded7e32ae1d6", // b11149 "8c79a9b226de4b3cacfd1f83d24f962d0773be79f1e7b75c6af4ded7e32ae1d6", // b11070 "8c79a9b226de4b3cacfd1f83d24f962d0773be79f1e7b75c6af4ded7e32ae1d6", // b10840 @@ -1929,7 +1934,8 @@ public static class WhisperX [LlamaCpp.WindowsCuda13Runtime] = new[] { // Identical bytes to b11070 — the CUDA 13.4 redistributable is unchanged across these releases. - "738f8c251ac22b70c3ae6f83a10cf222725df0395246a2cf58f32bdb85fbe668", // b11425 (current download URL) + "738f8c251ac22b70c3ae6f83a10cf222725df0395246a2cf58f32bdb85fbe668", // b11553 (current download URL) + "738f8c251ac22b70c3ae6f83a10cf222725df0395246a2cf58f32bdb85fbe668", // b11425 "738f8c251ac22b70c3ae6f83a10cf222725df0395246a2cf58f32bdb85fbe668", // b11149 "738f8c251ac22b70c3ae6f83a10cf222725df0395246a2cf58f32bdb85fbe668", // b11070 - upstream moved to the CUDA 13.4 redistributable // Identical bytes to b10142 — the CUDA 13.3 redistributable is unchanged across these releases. @@ -1944,7 +1950,8 @@ public static class WhisperX }, [LlamaCpp.LinuxCpu] = new[] { - "6a47856d08ecc4030b2fa8b8bf761c96d142d689567f69a0f75e42de430dc34c", // b11425 (current download URL) + "1fd58faf9f3740dad350fdba21b1afd4a393f9140d9ed8d68c6279820a4d25e3", // b11553 (current download URL) + "6a47856d08ecc4030b2fa8b8bf761c96d142d689567f69a0f75e42de430dc34c", // b11425 "214b9e26677221df9b6c84d396f236839c716a10acaf3eff8dde01fb26fbcd2c", // b11149 "dc1b0479392ffd35733f9271da3617ef1ba9cdc7a0273ae35b1696bc58ccb704", // b11070 "f19a877b0d2b16cfcf19612319d06194687e9310b19b23e35abb046f55903f67", // b10840 @@ -1966,7 +1973,8 @@ public static class WhisperX }, [LlamaCpp.LinuxVulkan] = new[] { - "5507e7e8b111874a72c3906bb662227a1735ef60c6a5b20e2ffd5287304fb9ea", // b11425 (current download URL) + "273828882608741a3ec065443176b5ed4922406744077d9da03821966cf7d496", // b11553 (current download URL) + "5507e7e8b111874a72c3906bb662227a1735ef60c6a5b20e2ffd5287304fb9ea", // b11425 "d93606c124863e26750f0fa02daba8ae73aa39395507534fcbb57f5e7bbe7b55", // b11149 "11d15f94a8a8e5d57787c51b65d2d159165df278d980b2de62d57e1ebf34e128", // b11070 "1ed6791cfe5921f8050b7af763d82aa0cb637bf97580aa4413b1b64d08eae06d", // b10840 @@ -1988,7 +1996,8 @@ public static class WhisperX }, [LlamaCpp.LinuxArm64Cpu] = new[] { - "82b9804017436f624c1751f54a74469a879b16c8edcb721c4d68ef88d472af12", // b11425 (current download URL) + "2f79bfc785f7284434dd5506470d12055b24ccc34707cdbabe1b1733aa8f83c1", // b11553 (current download URL) + "82b9804017436f624c1751f54a74469a879b16c8edcb721c4d68ef88d472af12", // b11425 "7a4ca8a91014a399dacb987efbd408621634e9079974f5a4fec80152d65b5e5b", // b11149 "4a75bdeb1337f7cd9221128b40f254817b3f9b53ac575fb66610f5016185980c", // b11070 "301b201d85cf7e76bbf7fef5e0569bb9166325c78635ad8893c57585216c8b3b", // b10840 @@ -2009,7 +2018,8 @@ public static class WhisperX }, [LlamaCpp.LinuxArm64Vulkan] = new[] { - "bea75091c4500e649f85b31cdf390ab97194b626766c035b68b4f6a3595c6c75", // b11425 (current download URL) + "b455cbef1595ff6026f64a3887e7008f7907ca89a74e8e8a7d00f2ecefb70161", // b11553 (current download URL) + "bea75091c4500e649f85b31cdf390ab97194b626766c035b68b4f6a3595c6c75", // b11425 "83ee53735830ab2650cbf71c931f4d038e5ea345c2c3c4a2350a1dc8abc646a5", // b11149 "a99d02bb083c9ef8e0355648f8072d1ce39c6518e8bad944eaef64e8bffbdedd", // b11070 "d198a87a93142299359e4117b7447b49372cd62073e8b165cd79f6cee3f2e10f", // b10840 @@ -2030,7 +2040,8 @@ public static class WhisperX }, [LlamaCpp.MacOsArm64] = new[] { - "14811f683abf2f32c62df48fcc36dbe4f0da307094080095eebb2b3906e705e4", // b11425 (current download URL) + "95ba87421fd1fa0b494b84a2fc92e4a22e4caa86b4f5ce4d8c6f9d4e9c363e12", // b11553 (current download URL) + "14811f683abf2f32c62df48fcc36dbe4f0da307094080095eebb2b3906e705e4", // b11425 "791eb0200a7c846ca925b6274fc21f0f21f537fda2924cc5a47402655816f56e", // b11149 "a398187d867fea5595de57bbec1ed464292f28b491f2d6063bd756bc453bd99f", // b11070 "848b6cc2817aa09e615fed0813b01fc3abbc43cd4d4773cc4aff4d7ef5733784", // b10840 @@ -2052,7 +2063,8 @@ public static class WhisperX }, [LlamaCpp.MacOsX64] = new[] { - "92743f79583a262832f80de0baf3dfcbb65a36f33e0fe2672db2ddacd32f57ad", // b11425 (current download URL) + "d75640a019922a38121b0f2b00cbdf7ee91a8f1edeb1c61b8f225509ee344e46", // b11553 (current download URL) + "92743f79583a262832f80de0baf3dfcbb65a36f33e0fe2672db2ddacd32f57ad", // b11425 "32f38e33825c2013c0ac9c0c9bdd9d5d6a4a96b3260a7e9b0ed3fc6679a1c270", // b11149 "f53f24e297d813a5073e91ddb06aadc06fa893ebe3b4e71c40808edd3a5db1d2", // b11070 "980f239850ddb6d27e35bc973fcbfa1912c744a6770dbb376669ec91168e26ce", // b10840 @@ -2078,7 +2090,8 @@ public static class WhisperX // three ship the identical dispatcher EXE that dynamically loads the backend DLLs. [LlamaCpp.WindowsExecutable] = new[] { - "4fdc1a67a8c1333429ea82186087a77db202fb588c2442dcdfa442b2182d0306", // b11425 (current download URL) + "cfc47f2e3b903af314158f4d19ec6ac728a86de823677163c58823795756583e", // b11553 (current download URL) + "4fdc1a67a8c1333429ea82186087a77db202fb588c2442dcdfa442b2182d0306", // b11425 "36e2803d3bc1c87ff21180dc1f1be1e53c6f04c515e91b46a480c1e1285471b4", // b11149 "48a4910c52b0ee302b9fe9d0c60fedb812cfa1cd8417323af9a343a84d831363", // b11070 "5f165219676fb9a6a2613d27a63ff344e2cf17b70b3bae815515a9c6380d3d96", // b10840 @@ -2100,7 +2113,8 @@ public static class WhisperX }, [LlamaCpp.LinuxExecutable] = new[] { - "c2788c13127c56c3e62b269240ae649712baacebd94af6d2136d0c54b296fdf3", // b11425 (current download URL) + "3f2085bf150f3517940705cbe02a4eb68a01334bb890d6a7613a44fe7a6f6fb4", // b11553 (current download URL) + "c2788c13127c56c3e62b269240ae649712baacebd94af6d2136d0c54b296fdf3", // b11425 "3675c087e54b23dd8e444989c978796d5093f8307719dec44da128fff81089fe", // b11149 "1f6b77fb5d1f4f0a3de2b2aad4d3a321466c19da9af2da73d1415982adcc10d3", // b11070 "a47aeb3ea2c61454e7f3209c43546e17192359121485daed67328968927c73b6", // b10840 @@ -2122,7 +2136,8 @@ public static class WhisperX }, [LlamaCpp.LinuxArm64Executable] = new[] { - "032ed1a8833ce31fab084d2a9c0e07745f8f88d7494cc726856bd71f00cd0269", // b11425 (current download URL) + "7fa45323bb0df00ee7262cd5d641c67ed04926fe3119bdf5b326e07e21a22c18", // b11553 (current download URL) + "032ed1a8833ce31fab084d2a9c0e07745f8f88d7494cc726856bd71f00cd0269", // b11425 "feaaa4a94c8e154ce5d9ab1ad96b418e2bcd50d2b13b0ab83bc5b57c8f938efb", // b11149 "4b44bc06a662c3fdc20a7b8f7ad887a8869fbb4b62d9a8e2d274eca6b5b8bf5e", // b11070 "50ce1afaa23ad46f83cf1522b8b88ce90fe21f9bcafcfaacde1303fc34915805", // b10840 @@ -2143,7 +2158,8 @@ public static class WhisperX }, [LlamaCpp.MacOsArm64Executable] = new[] { - "6a072f8bf308ea28144476441d25760ee01b84ed102f86be4662d21ace8d72a6", // b11425 (current download URL) + "1bf5602a2be50097c44282a6ecc63234d94ee8f96d39fd45fed6eff4e74b6b41", // b11553 (current download URL) + "6a072f8bf308ea28144476441d25760ee01b84ed102f86be4662d21ace8d72a6", // b11425 "41df13c126456f8e5fab2057c86a790067a85ea1dfd8fbc0071cc45fbba56262", // b11149 "4216ddf73348bd30d4ced17e510ee57edf597181ca009635d33a0bf26b33b5d2", // b11070 "d707b6db4c1397a7383176fba12d339e5b33c7513669d74c8fbc2a76f6979a72", // b10840 @@ -2167,7 +2183,8 @@ public static class WhisperX }, [LlamaCpp.MacOsX64Executable] = new[] { - "b4b04e4ebd969ad0a920b544c4a7bd8c193861aa20a42d5280c9fdd48b107693", // b11425 (current download URL) + "cdf2d31e4b75714ebad37ffeb8f3d51e9a7be45daa6fd884a6a360abaee04091", // b11553 (current download URL) + "b4b04e4ebd969ad0a920b544c4a7bd8c193861aa20a42d5280c9fdd48b107693", // b11425 "093b980ef4079cefd543ff1b00cecf264f468b0877a54461ef39d4b5fd0bb471", // b11149 "1d4fab1d895c8e594bb53909f467e56461be32949118ff706c1b25f5c37b0ac7", // b11070 "6a8e01dc4a888709308eb30fe8aad78b238ee90c10fd48705c1e2d6113e6f1a7", // b10840 diff --git a/src/ui/Logic/Download/LlamaCppDownloadService.cs b/src/ui/Logic/Download/LlamaCppDownloadService.cs index 7eb4b2c5e66..a42d96a4a2f 100644 --- a/src/ui/Logic/Download/LlamaCppDownloadService.cs +++ b/src/ui/Logic/Download/LlamaCppDownloadService.cs @@ -21,7 +21,7 @@ public class LlamaCppDownloadService(HttpClient httpClient) : ILlamaCppDownloadS /// The pinned llama.cpp release tag. Public so the engine settings dialog can show which build /// SE downloads; index 0 of every hash list must match it. /// - public const string ReleaseTag = "b11425"; + public const string ReleaseTag = "b11553"; private const string BaseUrl = "https://github.com/ggml-org/llama.cpp/releases/download/" + ReleaseTag + "/"; From 4dac25c54b63a341e9435462c8da95a88b279982 Mon Sep 17 00:00:00 2001 From: niksedk Date: Sun, 11 Oct 2026 16:10:18 +0200 Subject: [PATCH 065/306] TTS remove original speech: add Fast (HTDemucs) model option Advanced TTS settings get a model choice under "Remove original speech": Best quality (Mel-Band RoFormer, default) or Fast (HTDemucs f16, 81 MB), about three times quicker. CrispASR's --separate runs both; HTDemucs' drums/bass/other stems are summed back into the background with ffmpeg, and its per-chunk "separated" lines drive the progress bar. f16 only: the q8_0 model aborts CrispASR on Metal (CrispStrobe/CrispASR#537). Co-Authored-By: Claude Opus 5.5 --- .../SpeechToText/Engines/HtDemucsModel.cs | 39 +++++++++++++++++++ .../Engines/SpeechIsolationProgress.cs | 29 +++++++++++--- .../AdvancedTtsSettingsViewModel.cs | 10 +++++ .../AdvancedTtsSettingsWindow.cs | 9 +++++ .../TextToSpeech/TextToSpeechViewModel.cs | 34 +++++++++++++--- .../Language/Video/LanguageTextToSpeech.cs | 8 ++++ src/ui/Logic/Config/SeVideoTextToSpeech.cs | 3 ++ src/ui/Logic/Media/FfmpegGenerator.cs | 23 +++++++++++ .../Engines/SpeechIsolationProgressTests.cs | 25 ++++++++++++ 9 files changed, 169 insertions(+), 11 deletions(-) create mode 100644 src/ui/Features/Video/SpeechToText/Engines/HtDemucsModel.cs diff --git a/src/ui/Features/Video/SpeechToText/Engines/HtDemucsModel.cs b/src/ui/Features/Video/SpeechToText/Engines/HtDemucsModel.cs new file mode 100644 index 00000000000..4cc464db5ef --- /dev/null +++ b/src/ui/Features/Video/SpeechToText/Engines/HtDemucsModel.cs @@ -0,0 +1,39 @@ +using Nikse.SubtitleEdit.UiLogic.AudioToText; + +namespace Nikse.SubtitleEdit.Features.Video.SpeechToText.Engines; + +/// +/// The fast source separation model for text-to-speech "Remove original speech" (#15928). CrispASR's +/// "--separate" runs it like (the architecture is read from the +/// GGUF), about three times faster but with a little more of the music lost. +/// +/// +/// f16 only: the q8_0 file aborts CrispASR on Metal ("unsupported op 'CONT'") up to v0.8.42, see +/// CrispStrobe/CrispASR#537. +/// +public static class HtDemucsModel +{ + public const string FileName = "htdemucs-f16.gguf"; + public const string Url = "https://huggingface.co/cstr/htdemucs-GGUF/resolve/main/" + FileName; + public const string Size = "81 MB"; + public const string DisplayName = "HTDemucs"; + + /// + /// HTDemucs splits into drums, bass, other and vocals - everything but the speech is the first + /// three, which have to be mixed back together. + /// + public static readonly string[] BackgroundStems = { "drums", "bass", "other" }; + + /// The model's trained window: 7.8 s at 44.1 kHz. + public const int ChunkSamples = 343980; + + /// Windows advance by 75% of their length (25% overlap). + public const int StrideSamples = 257985; + + public static WhisperModel ToWhisperModel() => new() + { + Name = FileName, + Size = Size, + Urls = new[] { Url }, + }; +} diff --git a/src/ui/Features/Video/SpeechToText/Engines/SpeechIsolationProgress.cs b/src/ui/Features/Video/SpeechToText/Engines/SpeechIsolationProgress.cs index 01a2e1af13c..870d3d16fff 100644 --- a/src/ui/Features/Video/SpeechToText/Engines/SpeechIsolationProgress.cs +++ b/src/ui/Features/Video/SpeechToText/Engines/SpeechIsolationProgress.cs @@ -16,6 +16,8 @@ namespace Nikse.SubtitleEdit.Features.Video.SpeechToText.Engines; /// from the audio's length the same way the separator does: the input is resampled to 44.1 kHz /// and cut into 8 s windows with 25% overlap. The GPU path runs one fused graph per chunk and /// prints nothing per chunk, so there the progress simply stays at zero - it is also the fast one. +/// HTDemucs prints "htdemucs: separated (...)" when each chunk is done, on the CPU and GPU path +/// alike, and the same "segmented" summary at the end. /// public sealed partial class SpeechIsolationProgress { @@ -34,7 +36,10 @@ public sealed partial class SpeechIsolationProgress [GeneratedRegex(@"^mel_band_roformer: layer (\d+)/(\d+)", RegexOptions.CultureInvariant)] private static partial Regex LayerRegex(); - [GeneratedRegex(@"^mel_band_roformer: segmented \d+ samples into (\d+) chunks", RegexOptions.CultureInvariant)] + [GeneratedRegex(@"^htdemucs: separated ", RegexOptions.CultureInvariant)] + private static partial Regex ChunkDoneRegex(); + + [GeneratedRegex(@"^(?:mel_band_roformer|htdemucs): segmented \d+ samples into (\d+) chunks", RegexOptions.CultureInvariant)] private static partial Regex SegmentedRegex(); private readonly object _lock = new(); @@ -74,7 +79,7 @@ public double? Fraction public int? Percent => Fraction is { } f ? (int)Math.Floor(f * 100.0) : null; /// How many chunks the separator cuts of audio into. - public static int GetChunkCount(double durationSeconds) + public static int GetChunkCount(double durationSeconds, int chunkSamples = ChunkSamples, int strideSamples = StrideSamples) { if (double.IsNaN(durationSeconds) || durationSeconds <= 0) { @@ -82,25 +87,25 @@ public static int GetChunkCount(double durationSeconds) } var samples = (long)Math.Round(durationSeconds * SampleRate); - if (samples <= ChunkSamples) + if (samples <= chunkSamples) { return 1; // short audio is run whole, no chunking } - return (int)((samples + StrideSamples - 1) / StrideSamples); + return (int)((samples + strideSamples - 1) / strideSamples); } /// /// The chunk count for the wav the separator is about to read, or 0 when its header cannot be /// read - progress is then reported without a percentage. /// - public static int GetChunkCountFromWaveFile(string waveFileName) + public static int GetChunkCountFromWaveFile(string waveFileName, int chunkSamples = ChunkSamples, int strideSamples = StrideSamples) { try { using var stream = File.OpenRead(waveFileName); var header = new WaveHeader2(stream); - return GetChunkCount(header.LengthInSeconds); + return GetChunkCount(header.LengthInSeconds, chunkSamples, strideSamples); } catch { @@ -134,6 +139,18 @@ public bool TryUpdate(string? line) return true; } + if (ChunkDoneRegex().IsMatch(line)) + { + CurrentChunk++; + _layersDone = _layerCount = 1; // the line comes when the chunk is finished + if (ChunkCount > 0 && CurrentChunk > ChunkCount) + { + ChunkCount = CurrentChunk; + } + + return true; + } + var layer = LayerRegex().Match(line); if (layer.Success) { diff --git a/src/ui/Features/Video/TextToSpeech/AdvancedTtsSettings/AdvancedTtsSettingsViewModel.cs b/src/ui/Features/Video/TextToSpeech/AdvancedTtsSettings/AdvancedTtsSettingsViewModel.cs index 3c4300d2c1c..9a9a5c31547 100644 --- a/src/ui/Features/Video/TextToSpeech/AdvancedTtsSettings/AdvancedTtsSettingsViewModel.cs +++ b/src/ui/Features/Video/TextToSpeech/AdvancedTtsSettings/AdvancedTtsSettingsViewModel.cs @@ -16,6 +16,7 @@ public partial class AdvancedTtsSettingsViewModel : ObservableObject [ObservableProperty] private bool _doProAudioChain; [ObservableProperty] private bool _doAudioDucking; [ObservableProperty] private bool _doRemoveOriginalSpeech; + [ObservableProperty] private int _speechRemovalModelIndex; [ObservableProperty] private bool _doCleanCloneReferences; [ObservableProperty] private int _audioDuckingVolume; [ObservableProperty] private bool _doVadSilenceCompression; @@ -31,6 +32,13 @@ public partial class AdvancedTtsSettingsViewModel : ObservableObject [ObservableProperty] private bool _isEdgeTtsEngine; [ObservableProperty] private string _rubberbandStatus; + /// Index 0 is Mel-Band RoFormer (best quality), 1 is HTDemucs (fast). + public string[] SpeechRemovalModels { get; } = + { + Se.Language.Video.TextToSpeech.SpeechRemovalBestQuality, + Se.Language.Video.TextToSpeech.SpeechRemovalFast, + }; + public Window? Window { get; set; } public bool OkPressed { get; private set; } @@ -46,6 +54,7 @@ public AdvancedTtsSettingsViewModel(IFolderHelper folderHelper) DoProAudioChain = s.ProAudioChainEnabled; DoAudioDucking = s.AudioDuckingEnabled; DoRemoveOriginalSpeech = s.RemoveOriginalSpeech; + SpeechRemovalModelIndex = s.RemoveOriginalSpeechFast ? 1 : 0; DoCleanCloneReferences = s.CleanCloneReferences; AudioDuckingVolume = s.AudioDuckingOriginalVolume; DoVadSilenceCompression = s.VadSilenceCompressionEnabled; @@ -77,6 +86,7 @@ private void Ok() s.ProAudioChainEnabled = DoProAudioChain; s.AudioDuckingEnabled = DoAudioDucking; s.RemoveOriginalSpeech = DoRemoveOriginalSpeech; + s.RemoveOriginalSpeechFast = SpeechRemovalModelIndex == 1; s.CleanCloneReferences = DoCleanCloneReferences; s.AudioDuckingOriginalVolume = AudioDuckingVolume; s.VadSilenceCompressionEnabled = DoVadSilenceCompression; diff --git a/src/ui/Features/Video/TextToSpeech/AdvancedTtsSettings/AdvancedTtsSettingsWindow.cs b/src/ui/Features/Video/TextToSpeech/AdvancedTtsSettings/AdvancedTtsSettingsWindow.cs index 3d9e3cc53e9..68bc5422786 100644 --- a/src/ui/Features/Video/TextToSpeech/AdvancedTtsSettings/AdvancedTtsSettingsWindow.cs +++ b/src/ui/Features/Video/TextToSpeech/AdvancedTtsSettings/AdvancedTtsSettingsWindow.cs @@ -61,6 +61,15 @@ public AdvancedTtsSettingsWindow(AdvancedTtsSettingsViewModel vm) MakeRow(checkBoxRemoveSpeech, l.RemoveOriginalSpeechDescription), + MakeRow(UiUtil.MakeLabel(l.SpeechRemovalModel), l.SpeechRemovalModelDescription, + new ComboBox + { + Width = 240, + ItemsSource = vm.SpeechRemovalModels, + [!SelectingItemsControl.SelectedIndexProperty] = new Binding(nameof(vm.SpeechRemovalModelIndex)) { Mode = BindingMode.TwoWay }, + [!IsEnabledProperty] = new Binding(nameof(vm.DoRemoveOriginalSpeech)) { Mode = BindingMode.OneWay }, + }), + MakeRow(checkBoxVad, l.VadSilenceCompressionDescription, MakeField(l.MaxSilenceMs, UiUtil.MakeNumericUpDownInt(0, 5000, 150, 110, vm, nameof(vm.VadMaxSilenceMs)), diff --git a/src/ui/Features/Video/TextToSpeech/TextToSpeechViewModel.cs b/src/ui/Features/Video/TextToSpeech/TextToSpeechViewModel.cs index af80c611636..f8a9039048f 100644 --- a/src/ui/Features/Video/TextToSpeech/TextToSpeechViewModel.cs +++ b/src/ui/Features/Video/TextToSpeech/TextToSpeechViewModel.cs @@ -2387,6 +2387,12 @@ private async Task EnsureSpeechRemovalInstalled() return false; } + if (Se.Settings.Video.TextToSpeech.RemoveOriginalSpeechFast) + { + return await SpeechIsolationModelDownload.EnsureModelDownloadedAsync( + Window!, _windowService, new CrispAsrCohere(), HtDemucsModel.ToWhisperModel(), HtDemucsModel.DisplayName, featureName, "a source separation model"); + } + return await SpeechIsolationModelDownload.EnsureDownloadedAsync(Window!, _windowService, new CrispAsrCohere(), featureName); } @@ -2396,9 +2402,10 @@ private async Task EnsureSpeechRemovalInstalled() /// private async Task MakeSpeechFreeBackground(string workFolder, CancellationToken cancellationToken) { + var fast = Se.Settings.Video.TextToSpeech.RemoveOriginalSpeechFast; var crispAsr = new CrispAsrCohere(); var executable = crispAsr.GetExecutable(); - var modelFileName = crispAsr.GetModelForCmdLine(SpeechIsolationModel.FileName); + var modelFileName = crispAsr.GetModelForCmdLine(fast ? HtDemucsModel.FileName : SpeechIsolationModel.FileName); if (!File.Exists(executable) || !File.Exists(modelFileName)) { return null; @@ -2412,14 +2419,17 @@ private async Task EnsureSpeechRemovalInstalled() return null; } - var arguments = SpeechIsolationModel.BuildSeparateArguments(modelFileName, originalAudioFileName, workFolder, SpeechIsolationModel.BackgroundStem); + var stems = fast ? HtDemucsModel.BackgroundStems : new[] { SpeechIsolationModel.BackgroundStem }; + var arguments = SpeechIsolationModel.BuildSeparateArguments(modelFileName, originalAudioFileName, workFolder, string.Join(",", stems)); Se.WriteToolsLog($"{executable} {arguments}"); // The separator's per-chunk lines on stderr are the only measure of how far it is - and // on a machine without a GPU it is minutes per minute of video (#15176). They drive the // percentage and the time-left estimate; the GPU path prints none and is quick. The // output is also kept: when the separator fails it is the only clue to why. - var progress = new SpeechIsolationProgress(SpeechIsolationProgress.GetChunkCountFromWaveFile(originalAudioFileName)); + var progress = new SpeechIsolationProgress(fast + ? SpeechIsolationProgress.GetChunkCountFromWaveFile(originalAudioFileName, HtDemucsModel.ChunkSamples, HtDemucsModel.StrideSamples) + : SpeechIsolationProgress.GetChunkCountFromWaveFile(originalAudioFileName)); var separateLog = new StringBuilder(); DataReceivedEventHandler onLine = (_, args) => { @@ -2450,8 +2460,8 @@ private async Task EnsureSpeechRemovalInstalled() separateProcess.ErrorDataReceived += onLine; await separateProcess.StartAndWaitAsync(cancellationToken); - var backgroundFileName = SpeechIsolationModel.GetStemFileName(originalAudioFileName, workFolder, SpeechIsolationModel.BackgroundStem); - if (separateProcess.ExitCode != 0 || !File.Exists(backgroundFileName)) + var stemFileNames = stems.Select(stem => SpeechIsolationModel.GetStemFileName(originalAudioFileName, workFolder, stem)).ToList(); + if (separateProcess.ExitCode != 0 || !stemFileNames.All(File.Exists)) { lock (separateLog) { @@ -2461,6 +2471,20 @@ private async Task EnsureSpeechRemovalInstalled() return null; } + if (stemFileNames.Count == 1) + { + return stemFileNames[0]; + } + + var backgroundFileName = Path.Combine(workFolder, "background.wav"); + using var mixProcess = FfmpegGenerator.MixStems(stemFileNames, backgroundFileName); + await mixProcess.StartAndWaitAsync(cancellationToken); + if (mixProcess.ExitCode != 0 || !File.Exists(backgroundFileName)) + { + Se.WriteToolsLog($"TTS remove original speech: ffmpeg could not mix the stems (exit code {mixProcess.ExitCode})", true); + return null; + } + return backgroundFileName; } diff --git a/src/ui/Logic/Config/Language/Video/LanguageTextToSpeech.cs b/src/ui/Logic/Config/Language/Video/LanguageTextToSpeech.cs index c8ac6d38218..53deeb6f22d 100644 --- a/src/ui/Logic/Config/Language/Video/LanguageTextToSpeech.cs +++ b/src/ui/Logic/Config/Language/Video/LanguageTextToSpeech.cs @@ -168,6 +168,10 @@ public class LanguageTextToSpeech public string RemoveOriginalSpeechDescription { get; set; } public string RemovingOriginalSpeech { get; set; } public string RemoveOriginalSpeechFailed { get; set; } + public string SpeechRemovalModel { get; set; } + public string SpeechRemovalModelDescription { get; set; } + public string SpeechRemovalBestQuality { get; set; } + public string SpeechRemovalFast { get; set; } public string VideoTruncatedWarning { get; set; } public string OriginalVolumePercent { get; set; } public string VadSilenceCompression { get; set; } @@ -405,6 +409,10 @@ public LanguageTextToSpeech() RemoveOriginalSpeechDescription = "Keeps the music and sound effects of the original video but removes its speech, and mixes the new speech over that. Takes about as long as the video itself with a GPU, and many times longer without one. With audio ducking on, its volume applies to the music and effects. Only applies when the speech is added to the video file."; RemovingOriginalSpeech = "Removing original speech..."; RemoveOriginalSpeechFailed = "Could not remove the original speech - the speech was added to the video without it."; + SpeechRemovalModel = "Model"; + SpeechRemovalModelDescription = "Best quality keeps the most of the music and sound effects. Fast is about three times quicker, with a little more of the music lost."; + SpeechRemovalBestQuality = "Best quality (Mel-Band RoFormer)"; + SpeechRemovalFast = "Fast (HTDemucs)"; VideoTruncatedWarning = "The picture in the new video file stops after {0}, but the original video is {1} long - the picture will freeze while the sound continues.\n\nThe file was kept. See error-log.txt in the Subtitle Edit data folder for the ffmpeg output."; OriginalVolumePercent = "Original volume %"; VadSilenceCompression = "VAD silence compression"; diff --git a/src/ui/Logic/Config/SeVideoTextToSpeech.cs b/src/ui/Logic/Config/SeVideoTextToSpeech.cs index 47444e367ed..0f42078db9d 100644 --- a/src/ui/Logic/Config/SeVideoTextToSpeech.cs +++ b/src/ui/Logic/Config/SeVideoTextToSpeech.cs @@ -154,6 +154,9 @@ public class SeVideoTextToSpeech public int AudioDuckingOriginalVolume { get; set; } public bool RemoveOriginalSpeech { get; set; } + // Remove the original speech with HTDemucs (fast) instead of Mel-Band RoFormer (best quality). + public bool RemoveOriginalSpeechFast { get; set; } + // Run voice-clone references through Sidon (audio.cpp) first, to strip music and noise from // under the speech. See Features.Video.TextToSpeech.CloneReferenceCleaning.CloneReferenceCleaner. public bool CleanCloneReferences { get; set; } diff --git a/src/ui/Logic/Media/FfmpegGenerator.cs b/src/ui/Logic/Media/FfmpegGenerator.cs index 4b05a848aab..961d8c70413 100644 --- a/src/ui/Logic/Media/FfmpegGenerator.cs +++ b/src/ui/Logic/Media/FfmpegGenerator.cs @@ -1050,6 +1050,29 @@ public static Process ExtractAudioForSeparation(string inputFileName, string out return process; } + /// + /// Sums separated stems back into one track, e.g. HTDemucs' drums, bass and other into the + /// background without the speech. Written as float so the sum cannot clip. + /// + public static Process MixStems(IReadOnlyList stemFileNames, string outputWaveFileName, DataReceivedEventHandler? dataReceivedHandler = null) + { + var inputs = string.Join(" ", stemFileNames.Select(f => $"-i \"{f}\"")); + var process = new Process + { + StartInfo = + { + FileName = GetFfmpegLocation(), + Arguments = $"-nostdin -y {inputs} -filter_complex \"amix=inputs={stemFileNames.Count}:duration=longest:normalize=0\" -c:a pcm_f32le \"{outputWaveFileName}\"", + UseShellExecute = false, + CreateNoWindow = true + } + }; + + SetupDataReceiveHandler(dataReceivedHandler, process); + + return process; + } + /// [ObservableProperty] private bool _isEditingOutEffect; [ObservableProperty] private bool _canAnimate; - [ObservableProperty] private bool _isAnimationSlideConflict; [ObservableProperty] private bool _isPlayingAnimation; [ObservableProperty] private bool _hasAnyAnimation; [ObservableProperty] private bool _isAnimationExpanded = Se.Settings.Assa.DrawAnimationExpanded; @@ -185,7 +184,6 @@ private void UpdateAnimationState() AnimationSummary = AnimationIn == DrawAnimationEffect.None && AnimationOut == DrawAnimationEffect.None ? string.Empty : AssaDrawAnimationPanel.GetName(AnimationIn, isOut: false) + " · " + AssaDrawAnimationPanel.GetName(AnimationOut, isOut: true); - IsAnimationSlideConflict = DrawAnimation.IsSlide(AnimationIn) && DrawAnimation.IsSlide(AnimationOut); HasAnyAnimation = Shapes.Any(s => s.Animation is { IsNone: false }); if (!HasAnyAnimation && IsPlayingAnimation) { diff --git a/src/ui/Features/Assa/AssaDraw/AssaDrawViewModel.cs b/src/ui/Features/Assa/AssaDraw/AssaDrawViewModel.cs index f2fb8c11a2e..e43393a23c8 100644 --- a/src/ui/Features/Assa/AssaDraw/AssaDrawViewModel.cs +++ b/src/ui/Features/Assa/AssaDraw/AssaDrawViewModel.cs @@ -2811,9 +2811,18 @@ private Subtitle GenerateSubtitle(bool includeAll = false, bool hiddenAsComments if (finalText.Length > 0 && animation != null) { + var origin = string.Empty; + double outSlideDegrees = 0; + if (animation.IsDoubleSlide) + { + var (originX, originY, degrees) = animation.GetOutSlideRotation(anchor.X, anchor.Y, anchor.Bounds, CanvasWidth, CanvasHeight); + origin = $"\\org({FormatCoordinate(originX)},{FormatCoordinate(originY)})"; + outSlideDegrees = degrees; + } + finalText.Insert(0, "{\\an7" + animation.GetPositionTag(anchor.X, anchor.Y, LineDurationMs, anchor.Bounds, CanvasWidth, CanvasHeight) + - animation.GetEffectTags(LineDurationMs) + "}" + + origin + animation.GetEffectTags(LineDurationMs, 0, outSlideDegrees) + "}" + animation.FormatMarker(anchor.X, anchor.Y)); } @@ -2884,14 +2893,26 @@ private Subtitle GenerateSubtitle(bool includeAll = false, bool hiddenAsComments } /// - /// A text line follows its shape's animation: \move instead of \pos for a slide, and after its - /// tag block the marker and the effects, turning around the shape's anchor (\org). + /// A text line follows its shape's animation: it turns around the same \org as its drawing (the + /// anchor, or a double slide's far one), so its position is written to allow for its own \frz + /// turning around that point; \move instead of \pos for a slide. The marker and the effects go + /// after its tag block. /// private string AddTextAnimation(string textLine, DrawShape shape, DrawAnimation animation) { var anchor = GetAnimationAnchor(shape); var (left, top, right, bottom) = shape.GetTextBounds(); - var position = animation.GetPositionTag((left + right) / 2, (top + bottom) / 2, LineDurationMs, anchor.Bounds, CanvasWidth, CanvasHeight); + + // The \frz as the text line writes it (two decimals) - a far \org magnifies any difference + var baseFrz = (float)Math.Round(-shape.GetTextFrame().Angle, 2); + double originX = anchor.X, originY = anchor.Y, outSlideDegrees = 0; + if (animation.IsDoubleSlide) + { + (originX, originY, outSlideDegrees) = animation.GetOutSlideRotation(anchor.X, anchor.Y, anchor.Bounds, CanvasWidth, CanvasHeight); + } + + var position = animation.GetPositionTag((left + right) / 2, (top + bottom) / 2, LineDurationMs, anchor.Bounds, CanvasWidth, CanvasHeight, + (x, y) => DrawAnimation.MapAroundOrigin(x, y, originX, originY, baseFrz)); var posStart = textLine.IndexOf("\\pos(", StringComparison.Ordinal); if (posStart >= 0) { @@ -2900,11 +2921,13 @@ private string AddTextAnimation(string textLine, DrawShape shape, DrawAnimation } var blockEnd = textLine.IndexOf('}'); - var effects = "{\\org(" + anchor.X.ToString("0.##", CultureInfo.InvariantCulture) + "," + anchor.Y.ToString("0.##", CultureInfo.InvariantCulture) + ")" + - animation.GetEffectTags(LineDurationMs, -shape.GetTextFrame().Angle) + "}"; + var effects = $"{{\\org({FormatCoordinate(originX)},{FormatCoordinate(originY)})" + animation.GetEffectTags(LineDurationMs, baseFrz, outSlideDegrees) + "}"; return textLine[..(blockEnd + 1)] + animation.FormatMarker(anchor.X, anchor.Y) + effects + textLine[(blockEnd + 1)..]; } + private static string FormatCoordinate(double value) => + Math.Abs(value) < 0.005 ? "0" : value.ToString("0.##", CultureInfo.InvariantCulture); + /// /// The shape whose color names the layer's style: the first draw shape, or for a layer of only /// erase shapes (written as an \iclip-only line) the first erase shape. The style loop and the @@ -3486,7 +3509,7 @@ private void ImportLine(string text, int layer, Color color, bool hidden = false { animation = parsedAnimation.IsNone ? null : parsedAnimation; anchored = DrawAnimation.IsAnchoredDrawingLine(text); - text = DrawAnimation.RemoveFromLine(text, parsedAnimation); + text = DrawAnimation.RemoveFromLine(text); } if (AssaDrawTextLine.TryParse(text, out var parsedText)) diff --git a/src/ui/Features/Assa/AssaDraw/DrawAnimation.cs b/src/ui/Features/Assa/AssaDraw/DrawAnimation.cs index df101d0bfcf..8cb24c1d2c8 100644 --- a/src/ui/Features/Assa/AssaDraw/DrawAnimation.cs +++ b/src/ui/Features/Assa/AssaDraw/DrawAnimation.cs @@ -55,10 +55,16 @@ public sealed class DrawAnimation : IEquatable public static bool IsSlide(DrawAnimationEffect effect) => effect >= DrawAnimationEffect.Left; /// - /// The out effect as written: ASSA moves a line only once (one \move), so a slide out after a - /// slide in fades out instead. + /// A slide in and a slide out: ASSA has one \move per line, so the slide in uses it and the slide + /// out turns the line a tiny bit around an \org far away - a straight move over the frame. /// - public DrawAnimationEffect EffectiveOut => IsSlide(In) && IsSlide(Out) ? DrawAnimationEffect.Fade : Out; + public bool IsDoubleSlide => IsSlide(In) && IsSlide(Out); + + /// + /// How far away the \org of a double slide is: far enough that the arc is straight to a fraction + /// of a pixel over a frame width (2 px off at 2000 px), near enough for float renderers. + /// + private const double FarOriginDistance = 1_000_000; /// /// The in effect gets the start of the line and the out effect the end; on a short line they @@ -67,7 +73,7 @@ public sealed class DrawAnimation : IEquatable public (int InMs, int OutMs) GetDurations(int lineMs) { var inMs = In == DrawAnimationEffect.None ? 0 : Math.Max(1, InMs); - var outMs = EffectiveOut == DrawAnimationEffect.None ? 0 : Math.Max(1, OutMs); + var outMs = Out == DrawAnimationEffect.None ? 0 : Math.Max(1, OutMs); if (lineMs > 0 && inMs + outMs > lineMs) { var factor = lineMs / (double)(inMs + outMs); @@ -120,7 +126,7 @@ public DrawAnimationState GetState(double timeMs, int lineMs, (float Left, float { var q = Math.Clamp((timeMs - outStart) / outMs, 0, 1); var eased = (float)Math.Pow(q, EaseIn); - switch (EffectiveOut) + switch (Out) { case DrawAnimationEffect.Fade: opacity = (float)(1 - q); @@ -139,9 +145,11 @@ public DrawAnimationState GetState(double timeMs, int lineMs, (float Left, float rotation = -360 * eased; break; default: - var (sx, sy) = GetSlideOffset(EffectiveOut, bounds, frameWidth, frameHeight); - dx = sx * (float)q; - dy = sy * (float)q; + // \move is linear; the \org turn of a double slide is eased like the other out effects + var (sx, sy) = GetSlideOffset(Out, bounds, frameWidth, frameHeight); + var progress = IsDoubleSlide ? eased : (float)q; + dx = sx * progress; + dy = sy * progress; break; } } @@ -167,38 +175,75 @@ public static (float Dx, float Dy) GetSlideOffset(DrawAnimationEffect effect, (f } /// - /// The position tag: \move for a slide, else \pos. + /// The position tag: \move for a slide (the slide in when there are two), else \pos. + /// turns where something should show into the position to write (a line + /// with its own \frz and an \org elsewhere, see ). /// - public string GetPositionTag(float x, float y, int lineMs, (float Left, float Top, float Right, float Bottom) bounds, float frameWidth, float frameHeight) + public string GetPositionTag(float x, float y, int lineMs, (float Left, float Top, float Right, float Bottom) bounds, float frameWidth, float frameHeight, + Func? map = null) { + map ??= (px, py) => (px, py); var (inMs, outMs) = GetDurations(lineMs); + var rest = map(x, y); if (IsSlide(In)) { var (dx, dy) = GetSlideOffset(In, bounds, frameWidth, frameHeight); - return $"\\move({N(x + dx)},{N(y + dy)},{N(x)},{N(y)},0,{inMs})"; + var start = map(x + dx, y + dy); + return $"\\move({N(start.X)},{N(start.Y)},{N(rest.X)},{N(rest.Y)},0,{inMs})"; } - if (IsSlide(EffectiveOut)) + if (IsSlide(Out)) { - var (dx, dy) = GetSlideOffset(EffectiveOut, bounds, frameWidth, frameHeight); - return $"\\move({N(x)},{N(y)},{N(x + dx)},{N(y + dy)},{lineMs - outMs},{lineMs})"; + var (dx, dy) = GetSlideOffset(Out, bounds, frameWidth, frameHeight); + var end = map(x + dx, y + dy); + return $"\\move({N(rest.X)},{N(rest.Y)},{N(end.X)},{N(end.Y)},{lineMs - outMs},{lineMs})"; } - return $"\\pos({N(x)},{N(y)})"; + return $"\\pos({N(rest.X)},{N(rest.Y)})"; + } + + /// + /// The far \org of a double slide and how far (degrees, \frz) the line turns around it: the + /// anchor moves by the slide-out offset. + /// + public (double X, double Y, double Degrees) GetOutSlideRotation(float anchorX, float anchorY, (float Left, float Top, float Right, float Bottom) bounds, float frameWidth, float frameHeight) + { + var (dx, dy) = GetSlideOffset(Out, bounds, frameWidth, frameHeight); + var distance = Math.Sqrt(dx * dx + dy * dy); + + // \frz turns counter-clockwise on screen, so with the \org on this side of the anchor (at a + // right angle to the slide) a positive turn sets the anchor off in the slide direction + var originX = anchorX + FarOriginDistance * dy / distance; + var originY = anchorY - FarOriginDistance * dx / distance; + return (originX, originY, Math.Asin(distance / FarOriginDistance) * 180 / Math.PI); + } + + /// + /// Where to put a line with its own \frz () so it shows at (x, y) although + /// it turns around (originX, originY) instead of its position. + /// + public static (double X, double Y) MapAroundOrigin(double x, double y, double originX, double originY, double frz) + { + // libass turns counter-clockwise on screen (y down) by \frz; undo that turn + var radians = -frz * Math.PI / 180; + var vx = x - originX; + var vy = y - originY; + return (originX + vx * Math.Cos(radians) + vy * Math.Sin(radians), originY - vx * Math.Sin(radians) + vy * Math.Cos(radians)); } /// - /// \fad and the scale/rotation tags (everything but the position). is - /// the line's own \frz, which a spin turns from and back to. + /// \fad and the scale/rotation tags (everything but the position and \org). + /// is the line's own \frz, which a spin turns from and back to; + /// the turn of a double slide's slide out (). /// - public string GetEffectTags(int lineMs, float baseFrz = 0) + public string GetEffectTags(int lineMs, float baseFrz = 0, double outSlideDegrees = 0) { var (inMs, outMs) = GetDurations(lineMs); var outStart = lineMs - outMs; var sb = new StringBuilder(); var fadeIn = In == DrawAnimationEffect.Fade ? inMs : 0; - var fadeOut = EffectiveOut == DrawAnimationEffect.Fade ? outMs : 0; + var fadeOut = Out == DrawAnimationEffect.Fade ? outMs : 0; if (fadeIn > 0 || fadeOut > 0) { sb.Append(CultureInfo.InvariantCulture, $"\\fad({fadeIn},{fadeOut})"); @@ -218,7 +263,7 @@ public string GetEffectTags(int lineMs, float baseFrz = 0) break; } - switch (EffectiveOut) + switch (Out) { case DrawAnimationEffect.Zoom: sb.Append(CultureInfo.InvariantCulture, $"\\t({outStart},{lineMs},{N(EaseIn)},\\fscx0\\fscy0)"); @@ -229,13 +274,22 @@ public string GetEffectTags(int lineMs, float baseFrz = 0) break; case DrawAnimationEffect.Spin: sb.Append(CultureInfo.InvariantCulture, $"\\t({outStart},{lineMs},{N(EaseIn)},\\frz{N(baseFrz - 360)}\\fscx0\\fscy0)"); + break; + default: + if (IsDoubleSlide) + { + // Seven decimals: the far \org turns a thousandth of a degree into ~17 px + var degrees = (baseFrz + outSlideDegrees).ToString("0.#######", CultureInfo.InvariantCulture); + sb.Append(CultureInfo.InvariantCulture, $"\\t({outStart},{lineMs},{N(EaseIn)},\\frz{degrees})"); + } + break; } return sb.ToString(); } - public bool HasRotation => In == DrawAnimationEffect.Spin || EffectiveOut == DrawAnimationEffect.Spin; + public bool HasRotation => In == DrawAnimationEffect.Spin || Out == DrawAnimationEffect.Spin; private static readonly Regex MarkerRegex = new( @"\{assadraw-anim c=(-?[\d.]+),(-?[\d.]+) in=(\w+),(\d+) out=(\w+),(\d+)\}", @@ -271,14 +325,15 @@ public static bool TryParseMarker(string text, out DrawAnimation animation, out private static readonly Regex DrawingAnimationRegex = new(@"^\{\\an7[^}]*\}\{assadraw-anim [^}]*\}", RegexOptions.Compiled); private static readonly Regex TextAnimationRegex = new(@"\{assadraw-anim [^}]*\}(\{[^}]*\})?", RegexOptions.Compiled); - private static readonly Regex MoveRegex = new(@"\\move\(\s*(-?[\d.]+)\s*,\s*(-?[\d.]+)\s*,\s*(-?[\d.]+)\s*,\s*(-?[\d.]+)[^)]*\)", RegexOptions.Compiled); + private static readonly Regex MoveRegex = new(@"\\move\([^)]*\)", RegexOptions.Compiled); + private static readonly Regex PosRegex = new(@"\\pos\([^)]*\)", RegexOptions.Compiled); /// /// The line without its animation: a drawing line loses its leading position/effect block (its - /// coordinates are then relative to the anchor), a text line its effect block, and its \move - /// becomes the \pos it rests at. + /// coordinates are then relative to the anchor), a text line its effect block and position (its + /// text area comment says where it goes; the written position allows for the \org). /// - public static string RemoveFromLine(string text, DrawAnimation animation) + public static string RemoveFromLine(string text) { if (DrawingAnimationRegex.IsMatch(text)) { @@ -286,12 +341,8 @@ public static string RemoveFromLine(string text, DrawAnimation animation) } text = TextAnimationRegex.Replace(text, string.Empty, 1); - return MoveRegex.Replace(text, m => - { - var restX = IsSlide(animation.In) ? m.Groups[3].Value : m.Groups[1].Value; - var restY = IsSlide(animation.In) ? m.Groups[4].Value : m.Groups[2].Value; - return $"\\pos({restX},{restY})"; - }, 1); + text = MoveRegex.Replace(text, string.Empty, 1); + return PosRegex.Replace(text, string.Empty, 1); } /// diff --git a/src/ui/Logic/Config/Language/Assa/LanguageAssa.cs b/src/ui/Logic/Config/Language/Assa/LanguageAssa.cs index b0e9b1c386f..d0ac408fcfd 100644 --- a/src/ui/Logic/Config/Language/Assa/LanguageAssa.cs +++ b/src/ui/Logic/Config/Language/Assa/LanguageAssa.cs @@ -122,7 +122,6 @@ public class LanguageAssa public string DrawAnimationToBottom { get; set; } public string DrawAnimationPlay { get; set; } public string DrawAnimationStop { get; set; } - public string DrawAnimationSlideConflict { get; set; } public string DrawAnimationLayerHint { get; set; } public string DrawCategoryMyShapes { get; set; } public string DrawShapeSpeechBubble { get; set; } @@ -648,7 +647,6 @@ public LanguageAssa() DrawAnimationToBottom = "To bottom"; DrawAnimationPlay = "Play animation"; DrawAnimationStop = "Stop animation"; - DrawAnimationSlideConflict = "ASSA can move a line only once, so the out slide fades instead."; DrawAnimationLayerHint = "Applies to the whole layer (one subtitle line); a group moves as one."; DrawCategoryMyShapes = "My shapes"; DrawShapeSpeechBubble = "Speech bubble"; diff --git a/tests/UI/Features/Assa/AssaDrawAnimationTests.cs b/tests/UI/Features/Assa/AssaDrawAnimationTests.cs index cb16099f0a9..b61beef2751 100644 --- a/tests/UI/Features/Assa/AssaDrawAnimationTests.cs +++ b/tests/UI/Features/Assa/AssaDrawAnimationTests.cs @@ -74,19 +74,66 @@ public void GroupAnimation_IsWrittenAroundTheGroupCenter_AndOpensAgain() } [AvaloniaFact] - public void SlideIn_UsesMove_AndASecondSlideOutFadesInstead() + public void SlideInAndOut_MovesTwice_TheSlideOutTurnsAroundAFarOrigin() { var vm = MakeViewModel(SquareC); vm.SetSelection([vm.Shapes[0]]); vm.AnimationIn = DrawAnimationEffect.Left; vm.AnimationOut = DrawAnimationEffect.Bottom; - Assert.True(vm.IsAnimationSlideConflict); vm.OkCommand.Execute(null); var line = vm.ResultSubtitle.Paragraphs[0].Text; - // From just left of the frame (right edge 700 + 4) to the anchor 650,650, then a 400 ms fade out - Assert.StartsWith("{\\an7\\move(-54,650,650,650,0,400)\\fad(0,400)}", line); + // In: from just left of the frame (right edge 700 + 4) to the anchor 650,650 + Assert.StartsWith("{\\an7\\move(-54,650,650,650,0,400)\\org(", line); + + // Out: turning around the \org moves the anchor just under the frame (top 600 -> 1084 + 4 - 600 = 484 down) + var match = System.Text.RegularExpressions.Regex.Match(line, @"\\org\(([-\d.]+),([-\d.]+)\)\\t\(2600,3000,2,\\frz([-\d.]+)\)"); + Assert.True(match.Success, line); + var (x, y) = TurnLikeLibass(650, 650, double.Parse(match.Groups[1].Value, System.Globalization.CultureInfo.InvariantCulture), + double.Parse(match.Groups[2].Value, System.Globalization.CultureInfo.InvariantCulture), + double.Parse(match.Groups[3].Value, System.Globalization.CultureInfo.InvariantCulture)); + Assert.Equal(650, x, 0.5); + Assert.Equal(1134, y, 0.5); + + // The canvas plays the same: eased like the other out effects + var end = vm.GetCanvasAnimationTransform(vm.Shapes[0], 3000).State; + Assert.Equal(484, end.Dy, 0.01); + } + + /// + /// libass \frz: counter-clockwise on screen (y down) around the \org. + /// + private static (double X, double Y) TurnLikeLibass(double x, double y, double originX, double originY, double frz) + { + var radians = frz * Math.PI / 180; + var vx = x - originX; + var vy = y - originY; + return (originX + vx * Math.Cos(radians) + vy * Math.Sin(radians), originY - vx * Math.Sin(radians) + vy * Math.Cos(radians)); + } + + [AvaloniaFact] + public void TurnedText_InASpinningShape_StillShowsWhereItWas() + { + var vm = MakeViewModel(SquareA, SquareB); + vm.SetSelection([vm.Shapes[1]]); + vm.ShapeText = "Hi"; + vm.Shapes[1].SetTextBounds(310, 110, 390, 190, 30); + vm.SetSelection([vm.Shapes[0], vm.Shapes[1]]); + vm.GroupShapesCommand.Execute(null); + vm.AnimationIn = DrawAnimationEffect.Spin; + + vm.OkCommand.Execute(null); + var textLine = vm.ResultSubtitle.Paragraphs.Single(p => AssaDrawTextLine.IsTextLine(p.Text)).Text; + + // The text turns -30 (its own angle) around the group's center 250,150, so it is written + // where that turn brings it back to its center 350,150 + Assert.Contains("\\org(250,150)\\frz330", textLine); + var pos = System.Text.RegularExpressions.Regex.Match(textLine, @"\\pos\(([-\d.]+),([-\d.]+)\)"); + var (x, y) = TurnLikeLibass(double.Parse(pos.Groups[1].Value, System.Globalization.CultureInfo.InvariantCulture), + double.Parse(pos.Groups[2].Value, System.Globalization.CultureInfo.InvariantCulture), 250, 150, -30); + Assert.Equal(350, x, 0.05); + Assert.Equal(150, y, 0.05); } [AvaloniaFact] From 579160dc35dd32808c69c2396da60fa6cccaa086 Mon Sep 17 00:00:00 2001 From: niksedk Date: Fri, 16 Oct 2026 16:49:34 +0200 Subject: [PATCH 273/306] ASSA draw: social media logo clip arts YouTube, Instagram, LinkedIn, Facebook, X and TikTok, flat (Instagram's gradient as one pink). Co-Authored-By: Claude Opus 5.5 --- src/ui/Features/Assa/AssaDraw/ShapeLibrary.cs | 12 ++++++++++++ src/ui/Logic/Config/Language/Assa/LanguageAssa.cs | 12 ++++++++++++ tests/UI/Features/Assa/AssaDrawClipArtTests.cs | 2 +- 3 files changed, 25 insertions(+), 1 deletion(-) diff --git a/src/ui/Features/Assa/AssaDraw/ShapeLibrary.cs b/src/ui/Features/Assa/AssaDraw/ShapeLibrary.cs index 09e372d4dc7..e3ddb098aa3 100644 --- a/src/ui/Features/Assa/AssaDraw/ShapeLibrary.cs +++ b/src/ui/Features/Assa/AssaDraw/ShapeLibrary.cs @@ -531,6 +531,18 @@ private static List MakeBuiltIn() """"""), Clip(l.DrawClipArtChristmasTree, null, """"""), + Clip(l.DrawClipArtYouTube, null, + """"""), + Clip(l.DrawClipArtInstagram, null, + """"""), + Clip(l.DrawClipArtLinkedIn, null, + """"""), + Clip(l.DrawClipArtFacebook, null, + """"""), + Clip(l.DrawClipArtX, null, + """"""), + Clip(l.DrawClipArtTikTok, null, + """"""), ]; } } diff --git a/src/ui/Logic/Config/Language/Assa/LanguageAssa.cs b/src/ui/Logic/Config/Language/Assa/LanguageAssa.cs index d0ac408fcfd..258264ae8c8 100644 --- a/src/ui/Logic/Config/Language/Assa/LanguageAssa.cs +++ b/src/ui/Logic/Config/Language/Assa/LanguageAssa.cs @@ -104,6 +104,12 @@ public class LanguageAssa public string DrawClipArtCatFace { get; set; } public string DrawClipArtKey { get; set; } public string DrawClipArtChristmasTree { get; set; } + public string DrawClipArtYouTube { get; set; } + public string DrawClipArtInstagram { get; set; } + public string DrawClipArtLinkedIn { get; set; } + public string DrawClipArtFacebook { get; set; } + public string DrawClipArtX { get; set; } + public string DrawClipArtTikTok { get; set; } public string DrawAnimation { get; set; } public string DrawAnimationIn { get; set; } public string DrawAnimationOut { get; set; } @@ -629,6 +635,12 @@ public LanguageAssa() DrawClipArtCatFace = "Cat face"; DrawClipArtKey = "Key"; DrawClipArtChristmasTree = "Christmas tree"; + DrawClipArtYouTube = "YouTube"; + DrawClipArtInstagram = "Instagram"; + DrawClipArtLinkedIn = "LinkedIn"; + DrawClipArtFacebook = "Facebook"; + DrawClipArtX = "X"; + DrawClipArtTikTok = "TikTok"; DrawAnimation = "Animation"; DrawAnimationIn = "In"; DrawAnimationOut = "Out"; diff --git a/tests/UI/Features/Assa/AssaDrawClipArtTests.cs b/tests/UI/Features/Assa/AssaDrawClipArtTests.cs index 41d9bd05202..95a933d67bd 100644 --- a/tests/UI/Features/Assa/AssaDrawClipArtTests.cs +++ b/tests/UI/Features/Assa/AssaDrawClipArtTests.cs @@ -19,7 +19,7 @@ public class AssaDrawClipArtTests [Fact] public void ClipArt_KeepsItsColors_InAFewLayers() { - Assert.Equal(48, ClipArt.Count); + Assert.Equal(54, ClipArt.Count); foreach (var item in ClipArt) { Assert.True(item.HasOwnColors, item.Name); From c3c7906e7ab675ff08e79f8a5e339613cd767648 Mon Sep 17 00:00:00 2001 From: niksedk Date: Fri, 16 Oct 2026 16:58:59 +0200 Subject: [PATCH 274/306] ASSA draw: subtitle clip arts and star ratings Closed captions, speaker, translation, sign language, AI and headphones, and star ratings from 0 to 5 stars. Co-Authored-By: Claude Opus 5.5 --- src/ui/Features/Assa/AssaDraw/ShapeLibrary.cs | 38 +++++++++++++++++++ .../Config/Language/Assa/LanguageAssa.cs | 14 +++++++ .../UI/Features/Assa/AssaDrawClipArtTests.cs | 2 +- 3 files changed, 53 insertions(+), 1 deletion(-) diff --git a/src/ui/Features/Assa/AssaDraw/ShapeLibrary.cs b/src/ui/Features/Assa/AssaDraw/ShapeLibrary.cs index e3ddb098aa3..882fe5abfe8 100644 --- a/src/ui/Features/Assa/AssaDraw/ShapeLibrary.cs +++ b/src/ui/Features/Assa/AssaDraw/ShapeLibrary.cs @@ -326,6 +326,29 @@ private static ShapeLibraryItem Clip(string name, (float Left, float Top, float private static string F(double value) => value.ToString("0.##", CultureInfo.InvariantCulture); + /// + /// Five stars in a row, the first of them gold. + /// + private static string StarRatingSvg(int stars) + { + var sb = new StringBuilder(); + for (var i = 0; i < 5; i++) + { + var cx = 10 + i * 20; + sb.Append(""); + } + + return sb.ToString(); + } + private static string Circle(double cx, double cy, double r) => $"M{F(cx - r)} {F(cy)} A{F(r)} {F(r)} 0 1 0 {F(cx + r)} {F(cy)} A{F(r)} {F(r)} 0 1 0 {F(cx - r)} {F(cy)} Z"; @@ -543,6 +566,21 @@ private static List MakeBuiltIn() """"""), Clip(l.DrawClipArtTikTok, null, """"""), + Clip(l.DrawClipArtClosedCaptions, null, + """"""), + Clip(l.DrawClipArtSpeakerLabel, null, + """"""), + Clip(l.DrawClipArtTranslation, null, + """"""), + Clip(l.DrawClipArtSignLanguage, null, + """"""), + Clip(l.DrawClipArtAi, null, + """"""), + Clip(l.DrawClipArtHeadphones, null, + """"""), + + // Star ratings 0-5: gold stars for the rating, gray for the rest + .. Enumerable.Range(0, 6).Select(stars => Clip(string.Format(l.DrawClipArtStarRating, stars), null, StarRatingSvg(stars))), ]; } } diff --git a/src/ui/Logic/Config/Language/Assa/LanguageAssa.cs b/src/ui/Logic/Config/Language/Assa/LanguageAssa.cs index 258264ae8c8..075a06432f9 100644 --- a/src/ui/Logic/Config/Language/Assa/LanguageAssa.cs +++ b/src/ui/Logic/Config/Language/Assa/LanguageAssa.cs @@ -110,6 +110,13 @@ public class LanguageAssa public string DrawClipArtFacebook { get; set; } public string DrawClipArtX { get; set; } public string DrawClipArtTikTok { get; set; } + public string DrawClipArtClosedCaptions { get; set; } + public string DrawClipArtSpeakerLabel { get; set; } + public string DrawClipArtTranslation { get; set; } + public string DrawClipArtSignLanguage { get; set; } + public string DrawClipArtAi { get; set; } + public string DrawClipArtHeadphones { get; set; } + public string DrawClipArtStarRating { get; set; } public string DrawAnimation { get; set; } public string DrawAnimationIn { get; set; } public string DrawAnimationOut { get; set; } @@ -641,6 +648,13 @@ public LanguageAssa() DrawClipArtFacebook = "Facebook"; DrawClipArtX = "X"; DrawClipArtTikTok = "TikTok"; + DrawClipArtClosedCaptions = "Closed captions"; + DrawClipArtSpeakerLabel = "Speaker"; + DrawClipArtTranslation = "Translation"; + DrawClipArtSignLanguage = "Sign language"; + DrawClipArtAi = "AI"; + DrawClipArtHeadphones = "Headphones"; + DrawClipArtStarRating = "{0} of 5 stars"; DrawAnimation = "Animation"; DrawAnimationIn = "In"; DrawAnimationOut = "Out"; diff --git a/tests/UI/Features/Assa/AssaDrawClipArtTests.cs b/tests/UI/Features/Assa/AssaDrawClipArtTests.cs index 95a933d67bd..acbd62d11ae 100644 --- a/tests/UI/Features/Assa/AssaDrawClipArtTests.cs +++ b/tests/UI/Features/Assa/AssaDrawClipArtTests.cs @@ -19,7 +19,7 @@ public class AssaDrawClipArtTests [Fact] public void ClipArt_KeepsItsColors_InAFewLayers() { - Assert.Equal(54, ClipArt.Count); + Assert.Equal(66, ClipArt.Count); foreach (var item in ClipArt) { Assert.True(item.HasOwnColors, item.Name); From 238d95b48e28c72058430ab5b523fb571061fbf0 Mon Sep 17 00:00:00 2001 From: niksedk Date: Fri, 16 Oct 2026 17:17:30 +0200 Subject: [PATCH 275/306] ASSA draw: food reaction clip arts Yummy, drooling, disgusted, too spicy, chef's kiss and love it. Co-Authored-By: Claude Opus 5.5 --- src/ui/Features/Assa/AssaDraw/ShapeLibrary.cs | 13 +++++++++++++ src/ui/Logic/Config/Language/Assa/LanguageAssa.cs | 12 ++++++++++++ tests/UI/Features/Assa/AssaDrawClipArtTests.cs | 2 +- 3 files changed, 26 insertions(+), 1 deletion(-) diff --git a/src/ui/Features/Assa/AssaDraw/ShapeLibrary.cs b/src/ui/Features/Assa/AssaDraw/ShapeLibrary.cs index 882fe5abfe8..4ce583cc717 100644 --- a/src/ui/Features/Assa/AssaDraw/ShapeLibrary.cs +++ b/src/ui/Features/Assa/AssaDraw/ShapeLibrary.cs @@ -579,6 +579,19 @@ private static List MakeBuiltIn() Clip(l.DrawClipArtHeadphones, null, """"""), + Clip(l.DrawClipArtYummy, null, + """"""), + Clip(l.DrawClipArtDrooling, null, + """"""), + Clip(l.DrawClipArtDisgusted, null, + """"""), + Clip(l.DrawClipArtTooSpicy, null, + """"""), + Clip(l.DrawClipArtChefsKiss, null, + """"""), + Clip(l.DrawClipArtLoveIt, null, + """"""), + // Star ratings 0-5: gold stars for the rating, gray for the rest .. Enumerable.Range(0, 6).Select(stars => Clip(string.Format(l.DrawClipArtStarRating, stars), null, StarRatingSvg(stars))), ]; diff --git a/src/ui/Logic/Config/Language/Assa/LanguageAssa.cs b/src/ui/Logic/Config/Language/Assa/LanguageAssa.cs index 075a06432f9..c8a071e84cf 100644 --- a/src/ui/Logic/Config/Language/Assa/LanguageAssa.cs +++ b/src/ui/Logic/Config/Language/Assa/LanguageAssa.cs @@ -116,6 +116,12 @@ public class LanguageAssa public string DrawClipArtSignLanguage { get; set; } public string DrawClipArtAi { get; set; } public string DrawClipArtHeadphones { get; set; } + public string DrawClipArtYummy { get; set; } + public string DrawClipArtDrooling { get; set; } + public string DrawClipArtDisgusted { get; set; } + public string DrawClipArtTooSpicy { get; set; } + public string DrawClipArtChefsKiss { get; set; } + public string DrawClipArtLoveIt { get; set; } public string DrawClipArtStarRating { get; set; } public string DrawAnimation { get; set; } public string DrawAnimationIn { get; set; } @@ -654,6 +660,12 @@ public LanguageAssa() DrawClipArtSignLanguage = "Sign language"; DrawClipArtAi = "AI"; DrawClipArtHeadphones = "Headphones"; + DrawClipArtYummy = "Yummy"; + DrawClipArtDrooling = "Drooling"; + DrawClipArtDisgusted = "Disgusted"; + DrawClipArtTooSpicy = "Too spicy"; + DrawClipArtChefsKiss = "Chef's kiss"; + DrawClipArtLoveIt = "Love it"; DrawClipArtStarRating = "{0} of 5 stars"; DrawAnimation = "Animation"; DrawAnimationIn = "In"; diff --git a/tests/UI/Features/Assa/AssaDrawClipArtTests.cs b/tests/UI/Features/Assa/AssaDrawClipArtTests.cs index acbd62d11ae..fd6707e0c2d 100644 --- a/tests/UI/Features/Assa/AssaDrawClipArtTests.cs +++ b/tests/UI/Features/Assa/AssaDrawClipArtTests.cs @@ -19,7 +19,7 @@ public class AssaDrawClipArtTests [Fact] public void ClipArt_KeepsItsColors_InAFewLayers() { - Assert.Equal(66, ClipArt.Count); + Assert.Equal(72, ClipArt.Count); foreach (var item in ClipArt) { Assert.True(item.HasOwnColors, item.Name); From 7c32c5221d80cd18b9ebb10cd48116d3841f4503 Mon Sep 17 00:00:00 2001 From: niksedk Date: Fri, 16 Oct 2026 17:55:09 +0200 Subject: [PATCH 276/306] Video player: optional previous/next subtitle buttons (#16047) New "Previous subtitle" / "Next subtitle" items in Options > Video controls, hidden by default. They run the existing Play previous / Play next actions and show only in the main window's players (docked, undocked, full screen). Items missing from an older settings file now go right after their default predecessor instead of the end of the list. Co-Authored-By: Claude Opus 5.5 --- .../VideoPlayer/VideoPlayerControl.cs | 74 ++++++++++++++++++- .../Features/Main/Layout/InitVideoPlayer.cs | 3 + src/ui/Features/Main/MainViewModel.cs | 3 + .../VideoControlsItemDisplay.cs | 6 +- .../VideoControlsItemsWindow.cs | 1 + .../Language/Options/LanguageSettings.cs | 4 + src/ui/Logic/Config/SeVideoControlsItem.cs | 26 ++++++- .../Logic/Config/SeVideoControlsItemType.cs | 4 + .../Controls/VideoPlayerControlLayoutTests.cs | 30 +++++++- .../Logic/Config/SeVideoControlsItemTests.cs | 59 +++++++++++++++ 10 files changed, 202 insertions(+), 8 deletions(-) create mode 100644 tests/UI/Logic/Config/SeVideoControlsItemTests.cs diff --git a/src/ui/Controls/VideoPlayer/VideoPlayerControl.cs b/src/ui/Controls/VideoPlayer/VideoPlayerControl.cs index c77bfbaf9b0..f73c5fa378f 100644 --- a/src/ui/Controls/VideoPlayer/VideoPlayerControl.cs +++ b/src/ui/Controls/VideoPlayer/VideoPlayerControl.cs @@ -53,6 +53,15 @@ public class VideoPlayerControl : UserControl public static readonly StyledProperty FullScreenCommandProperty = AvaloniaProperty.Register(nameof(FullScreenCommand)); + public static readonly StyledProperty PreviousSubtitleCommandProperty = + AvaloniaProperty.Register(nameof(PreviousSubtitleCommand)); + + public static readonly StyledProperty NextSubtitleCommandProperty = + AvaloniaProperty.Register(nameof(NextSubtitleCommand)); + + public static readonly StyledProperty SubtitleNavigationIsVisibleProperty = + AvaloniaProperty.Register(nameof(SubtitleNavigationIsVisible)); + public static readonly StyledProperty StopIsVisibleProperty = AvaloniaProperty.Register(nameof(StopIsVisible)); @@ -127,6 +136,28 @@ public ICommand FullScreenCommand set => SetValue(FullScreenCommandProperty, value); } + public ICommand? PreviousSubtitleCommand + { + get => GetValue(PreviousSubtitleCommandProperty); + set => SetValue(PreviousSubtitleCommandProperty, value); + } + + public ICommand? NextSubtitleCommand + { + get => GetValue(NextSubtitleCommandProperty); + set => SetValue(NextSubtitleCommandProperty, value); + } + + /// + /// The previous/next subtitle buttons need a subtitle list, so only the main window's + /// players (and the controls preview) show them - when the controls layout does (#16047). + /// + public bool SubtitleNavigationIsVisible + { + get => GetValue(SubtitleNavigationIsVisibleProperty); + set => SetValue(SubtitleNavigationIsVisibleProperty, value); + } + public bool StopIsVisible { get => GetValue(StopIsVisibleProperty); @@ -183,6 +214,8 @@ public bool IsFullScreen private readonly Button _buttonFullScreen; private readonly Button _buttonFullScreenCollapse; private readonly Button _buttonStop; + private readonly Button _buttonPreviousSubtitle; + private readonly Button _buttonNextSubtitle; private readonly StackPanel _panelFullScreen; private readonly Slider _sliderPosition; private readonly StackPanel _panelVolume; @@ -525,6 +558,16 @@ public VideoPlayerControl(IVideoPlayer videoPlayerInstance) Source = this }); + // Previous/next subtitle (#16047) + _buttonPreviousSubtitle = MakeSubtitleNavigationButton( + Se.Language.Options.Settings.VideoControlsPreviousSubtitle, + "fa-solid fa-backward-step", + nameof(PreviousSubtitleCommand)); + _buttonNextSubtitle = MakeSubtitleNavigationButton( + Se.Language.Options.Settings.VideoControlsNextSubtitle, + "fa-solid fa-forward-step", + nameof(NextSubtitleCommand)); + // Fullscreen _buttonFullScreen = new NonSpaceButton { @@ -1030,6 +1073,8 @@ public void ApplyControlsLayout(IEnumerable? items) { SeVideoControlsItemType.Play => _buttonPlay, SeVideoControlsItemType.Stop => _buttonStop, + SeVideoControlsItemType.PreviousSubtitle => _buttonPreviousSubtitle, + SeVideoControlsItemType.NextSubtitle => _buttonNextSubtitle, SeVideoControlsItemType.FullScreen => _panelFullScreen, SeVideoControlsItemType.PositionSlider => _sliderPosition, SeVideoControlsItemType.Volume => _panelVolume, @@ -1116,10 +1161,37 @@ public void ApplyControlsLayout(IEnumerable? items) UpdateVideoFileNameMaxWidth(); } + private NonSpaceButton MakeSubtitleNavigationButton(string name, string iconName, string commandPropertyName) + { + var button = new NonSpaceButton + { + VerticalAlignment = VerticalAlignment.Center, + [AutomationProperties.NameProperty] = name, + }; + button.Bind(IsVisibleProperty, new Binding + { + Path = nameof(SubtitleNavigationIsVisible), + Source = this + }); + Attached.SetIcon(button, iconName); + if (Se.Settings.Appearance.ShowHints) + { + ToolTip.SetTip(button, name); + } + + button.Bind(Button.CommandProperty, new Binding + { + Path = commandPropertyName, + Source = this + }); + return button; + } + private static double GetControlsSpacing(SeVideoControlsItemType left, SeVideoControlsItemType right) { static bool IsButton(SeVideoControlsItemType type) => - type is SeVideoControlsItemType.Play or SeVideoControlsItemType.Stop or SeVideoControlsItemType.FullScreen; + type is SeVideoControlsItemType.Play or SeVideoControlsItemType.Stop or SeVideoControlsItemType.FullScreen or + SeVideoControlsItemType.PreviousSubtitle or SeVideoControlsItemType.NextSubtitle; if (IsButton(left) && IsButton(right)) { diff --git a/src/ui/Features/Main/Layout/InitVideoPlayer.cs b/src/ui/Features/Main/Layout/InitVideoPlayer.cs index 2a2e1b20e02..5928c912037 100644 --- a/src/ui/Features/Main/Layout/InitVideoPlayer.cs +++ b/src/ui/Features/Main/Layout/InitVideoPlayer.cs @@ -90,6 +90,9 @@ public static Grid MakeLayoutVideoPlayer(MainViewModel vm, Thickness nonFullScre } control.FullScreenCommand = vm.VideoFullScreenCommand; + control.PreviousSubtitleCommand = vm.PlayPreviousCommand; + control.NextSubtitleCommand = vm.PlayNextCommand; + control.SubtitleNavigationIsVisible = true; videoPlayerControl = control; vm.VideoPlayerControl = control; control.VideoPlayerDisplayTimeLeft = Se.Settings.Video.VideoPlayerDisplayTimeLeft; diff --git a/src/ui/Features/Main/MainViewModel.cs b/src/ui/Features/Main/MainViewModel.cs index 96bdb8c5009..c3a79386837 100644 --- a/src/ui/Features/Main/MainViewModel.cs +++ b/src/ui/Features/Main/MainViewModel.cs @@ -20064,6 +20064,9 @@ private void VideoFullScreen() _fullScreenVideoPlayerControl = InitVideoPlayer.MakeVideoPlayer(); _fullScreenVideoPlayerControl.IsFullScreen = true; + _fullScreenVideoPlayerControl.PreviousSubtitleCommand = PlayPreviousCommand; + _fullScreenVideoPlayerControl.NextSubtitleCommand = PlayNextCommand; + _fullScreenVideoPlayerControl.SubtitleNavigationIsVisible = true; // The fullscreen player is created bare (not via MakeLayoutVideoPlayer), so wire the // pause/stop cursor-freeze here as well (issue #12233). _fullScreenVideoPlayerControl.PlayPauseRequested += willPause => diff --git a/src/ui/Features/Options/Settings/VideoControlsItems/VideoControlsItemDisplay.cs b/src/ui/Features/Options/Settings/VideoControlsItems/VideoControlsItemDisplay.cs index d2ac920eb7d..87d1ec28ec7 100644 --- a/src/ui/Features/Options/Settings/VideoControlsItems/VideoControlsItemDisplay.cs +++ b/src/ui/Features/Options/Settings/VideoControlsItems/VideoControlsItemDisplay.cs @@ -32,11 +32,13 @@ private static string GetDisplayName(SeVideoControlsItemType type) SeVideoControlsItemType.PositionText => l.VideoControlsPositionText, SeVideoControlsItemType.VideoFileName => l.VideoControlsVideoFileName, SeVideoControlsItemType.PlayerName => l.VideoControlsPlayerName, + SeVideoControlsItemType.PreviousSubtitle => l.VideoControlsPreviousSubtitle, + SeVideoControlsItemType.NextSubtitle => l.VideoControlsNextSubtitle, _ => type.ToString(), }; } - // Play, stop, full screen and volume use the same icons as the video controls themselves. + // Play, stop, previous/next subtitle, full screen and volume use the same icons as the video controls themselves. private static string GetIconName(SeVideoControlsItemType type) { return type switch @@ -49,6 +51,8 @@ private static string GetIconName(SeVideoControlsItemType type) SeVideoControlsItemType.PositionText => "fa-solid fa-clock", SeVideoControlsItemType.VideoFileName => "fa-solid fa-file-video", SeVideoControlsItemType.PlayerName => "fa-solid fa-circle-info", + SeVideoControlsItemType.PreviousSubtitle => "fa-solid fa-backward-step", + SeVideoControlsItemType.NextSubtitle => "fa-solid fa-forward-step", _ => "fa-solid fa-circle", }; } diff --git a/src/ui/Features/Options/Settings/VideoControlsItems/VideoControlsItemsWindow.cs b/src/ui/Features/Options/Settings/VideoControlsItems/VideoControlsItemsWindow.cs index 27a25002bce..0ce1bbded0a 100644 --- a/src/ui/Features/Options/Settings/VideoControlsItems/VideoControlsItemsWindow.cs +++ b/src/ui/Features/Options/Settings/VideoControlsItems/VideoControlsItemsWindow.cs @@ -38,6 +38,7 @@ public VideoControlsItemsWindow(VideoControlsItemsViewModel vm) Focusable = false, StopIsVisible = true, FullScreenIsVisible = true, + SubtitleNavigationIsVisible = true, }; KeyboardNavigation.SetTabNavigation(preview, KeyboardNavigationMode.None); preview.SetPreviewCaptions("00:01:23.456 / 01:39:43.040", "movie.mkv", "libmpv"); diff --git a/src/ui/Logic/Config/Language/Options/LanguageSettings.cs b/src/ui/Logic/Config/Language/Options/LanguageSettings.cs index 4b8206ab2f3..a95ed84fcf6 100644 --- a/src/ui/Logic/Config/Language/Options/LanguageSettings.cs +++ b/src/ui/Logic/Config/Language/Options/LanguageSettings.cs @@ -193,6 +193,8 @@ public class LanguageSettings public string VideoControlsPositionText { get; set; } public string VideoControlsVideoFileName { get; set; } public string VideoControlsPlayerName { get; set; } + public string VideoControlsPreviousSubtitle { get; set; } + public string VideoControlsNextSubtitle { get; set; } public string FullscreenHideControls { get; set; } public string ShowSecondarySubtitleDialog { get; set; } public string RememberSecondarySubtitleFile { get; set; } @@ -552,6 +554,8 @@ public LanguageSettings() VideoControlsPositionText = "Position / duration text"; VideoControlsVideoFileName = "Video file name"; VideoControlsPlayerName = "Video player name"; + VideoControlsPreviousSubtitle = "Previous subtitle"; + VideoControlsNextSubtitle = "Next subtitle"; FullscreenHideControls = "Hide video controls in full-screen"; ShowSecondarySubtitleDialog = "Show style dialog when opening a second subtitle file"; RememberSecondarySubtitleFile = "Remember second subtitle file"; diff --git a/src/ui/Logic/Config/SeVideoControlsItem.cs b/src/ui/Logic/Config/SeVideoControlsItem.cs index 58c396b32c0..196f50e31cf 100644 --- a/src/ui/Logic/Config/SeVideoControlsItem.cs +++ b/src/ui/Logic/Config/SeVideoControlsItem.cs @@ -30,6 +30,8 @@ public static List MakeDefaults() [ new SeVideoControlsItem { Type = SeVideoControlsItemType.Play, SortOrder = 10 }, new SeVideoControlsItem { Type = SeVideoControlsItemType.Stop, SortOrder = 20 }, + new SeVideoControlsItem { Type = SeVideoControlsItemType.PreviousSubtitle, IsVisible = false, SortOrder = 22 }, + new SeVideoControlsItem { Type = SeVideoControlsItemType.NextSubtitle, IsVisible = false, SortOrder = 24 }, new SeVideoControlsItem { Type = SeVideoControlsItemType.FullScreen, SortOrder = 30 }, new SeVideoControlsItem { Type = SeVideoControlsItemType.PositionSlider, SortOrder = 40 }, new SeVideoControlsItem { Type = SeVideoControlsItemType.Volume, SortOrder = 50 }, @@ -42,7 +44,8 @@ public static List MakeDefaults() /// /// Returns the items in display order with exactly one entry per type: unknown and duplicate /// entries (hand-edited settings file) are dropped and types missing from an older settings - /// file are appended with their defaults. + /// file are added with their defaults, right after the item they follow in the defaults + /// (appended when there is none). /// public static List Normalize(IEnumerable? items) { @@ -55,12 +58,27 @@ public static List Normalize(IEnumerable p.Type == item.Type)) + var item = defaults[i]; + if (result.Exists(p => p.Type == item.Type)) { - result.Add(item); + continue; } + + var insertAt = result.Count; + for (var j = i - 1; j >= 0; j--) + { + var index = result.FindIndex(p => p.Type == defaults[j].Type); + if (index >= 0) + { + insertAt = index + 1; + break; + } + } + + result.Insert(insertAt, item); } for (var i = 0; i < result.Count; i++) diff --git a/src/ui/Logic/Config/SeVideoControlsItemType.cs b/src/ui/Logic/Config/SeVideoControlsItemType.cs index 1eaafac8146..af80a61f9b9 100644 --- a/src/ui/Logic/Config/SeVideoControlsItemType.cs +++ b/src/ui/Logic/Config/SeVideoControlsItemType.cs @@ -20,4 +20,8 @@ public enum SeVideoControlsItemType // Small label in the top right corner (e.g. "libmpv 2.5 sw"). PlayerName = 7, + + // Buttons that go to the previous/next subtitle and play it (#16047). Hidden by default. + PreviousSubtitle = 8, + NextSubtitle = 9, } diff --git a/tests/UI/Controls/VideoPlayerControlLayoutTests.cs b/tests/UI/Controls/VideoPlayerControlLayoutTests.cs index 50bd1e9a17b..b594e3243a9 100644 --- a/tests/UI/Controls/VideoPlayerControlLayoutTests.cs +++ b/tests/UI/Controls/VideoPlayerControlLayoutTests.cs @@ -64,12 +64,13 @@ public void Reordered_ButtonsFollowListOrder() var control = new VideoPlayerControl(new EmptyVideoPlayer()); control.ApplyControlsLayout(Order( SeVideoControlsItemType.PositionSlider, + SeVideoControlsItemType.Volume, SeVideoControlsItemType.Play, SeVideoControlsItemType.Stop)); Assert.Equal(0, Grid.GetColumn(GetPositionSlider(control))); - Assert.Equal(1, Grid.GetColumn(GetButton(control, Se.Language.General.Play))); - Assert.Equal(2, Grid.GetColumn(GetButton(control, Se.Language.General.Stop))); + Assert.Equal(2, Grid.GetColumn(GetButton(control, Se.Language.General.Play))); + Assert.Equal(3, Grid.GetColumn(GetButton(control, Se.Language.General.Stop))); } [AvaloniaFact] @@ -137,6 +138,31 @@ public void Normalize_DropsDuplicatesAndAddsMissing() Assert.Equal(result.Count, result.Select(p => p.Type).Distinct().Count()); } + [AvaloniaFact] + public void SubtitleNavigationButtons_ShownOnlyWhenEnabledAndInLayout() + { + var control = new VideoPlayerControl(new EmptyVideoPlayer()); + var items = SeVideoControlsItem.MakeDefaults(); + foreach (var item in items.Where(p => p.Type is SeVideoControlsItemType.PreviousSubtitle or SeVideoControlsItemType.NextSubtitle)) + { + item.IsVisible = true; + } + + control.ApplyControlsLayout(items); + var previous = GetButton(control, Se.Language.Options.Settings.VideoControlsPreviousSubtitle); + var next = GetButton(control, Se.Language.Options.Settings.VideoControlsNextSubtitle); + Assert.Equal(2, Grid.GetColumn(previous)); + Assert.Equal(3, Grid.GetColumn(next)); + Assert.False(previous.IsVisible); // dialogs without a subtitle list + + control.SubtitleNavigationIsVisible = true; + Assert.True(previous.IsVisible); + Assert.True(next.IsVisible); + + control.ApplyControlsLayout(SeVideoControlsItem.MakeDefaults()); + Assert.DoesNotContain(previous, GetControlsGrid(control).Children); + } + [Fact] public void Dialog_OkReturnsOrderAndVisibility() { diff --git a/tests/UI/Logic/Config/SeVideoControlsItemTests.cs b/tests/UI/Logic/Config/SeVideoControlsItemTests.cs new file mode 100644 index 00000000000..5c8dfdbf0e6 --- /dev/null +++ b/tests/UI/Logic/Config/SeVideoControlsItemTests.cs @@ -0,0 +1,59 @@ +using Nikse.SubtitleEdit.Logic.Config; + +namespace UITests.Logic.Config; + +public class SeVideoControlsItemTests +{ + [Fact] + public void MakeDefaults_SubtitleNavigationButtonsHidden() + { + var items = SeVideoControlsItem.MakeDefaults(); + + Assert.False(items.Single(p => p.Type == SeVideoControlsItemType.PreviousSubtitle).IsVisible); + Assert.False(items.Single(p => p.Type == SeVideoControlsItemType.NextSubtitle).IsVisible); + } + + [Fact] + public void Normalize_OlderSettings_InsertsMissingItemsAfterTheirDefaultPredecessor() + { + // A settings file from before #16047, with the user's own order (full screen first). + var items = new List + { + new() { Type = SeVideoControlsItemType.FullScreen, SortOrder = 10 }, + new() { Type = SeVideoControlsItemType.Play, SortOrder = 20 }, + new() { Type = SeVideoControlsItemType.Stop, SortOrder = 30 }, + new() { Type = SeVideoControlsItemType.PositionSlider, SortOrder = 40 }, + new() { Type = SeVideoControlsItemType.Volume, SortOrder = 50 }, + new() { Type = SeVideoControlsItemType.PositionText, SortOrder = 60 }, + new() { Type = SeVideoControlsItemType.VideoFileName, SortOrder = 70 }, + new() { Type = SeVideoControlsItemType.PlayerName, SortOrder = 80 }, + }; + + var result = SeVideoControlsItem.Normalize(items); + + Assert.Equal( + [ + SeVideoControlsItemType.FullScreen, + SeVideoControlsItemType.Play, + SeVideoControlsItemType.Stop, + SeVideoControlsItemType.PreviousSubtitle, + SeVideoControlsItemType.NextSubtitle, + SeVideoControlsItemType.PositionSlider, + SeVideoControlsItemType.Volume, + SeVideoControlsItemType.PositionText, + SeVideoControlsItemType.VideoFileName, + SeVideoControlsItemType.PlayerName, + ], result.Select(p => p.Type)); + Assert.False(result[3].IsVisible); + Assert.False(result[4].IsVisible); + Assert.Equal(Enumerable.Range(1, result.Count).Select(i => i * 10), result.Select(p => p.SortOrder)); + } + + [Fact] + public void Normalize_Null_ReturnsDefaults() + { + var result = SeVideoControlsItem.Normalize(null); + + Assert.Equal(SeVideoControlsItem.MakeDefaults().Select(p => p.Type), result.Select(p => p.Type)); + } +} From 8cc4264943bcbd253c4a4082a31404ee24be421c Mon Sep 17 00:00:00 2001 From: niksedk Date: Fri, 16 Oct 2026 17:57:04 +0200 Subject: [PATCH 277/306] Video player: optional toggle subtitles button (#16047) Hidden by default like previous/next subtitle; runs the existing Toggle subtitles on video player action (mpv + FFmpeg players). Co-Authored-By: Claude Opus 5.5 --- .../VideoPlayer/VideoPlayerControl.cs | 43 +++++++++++++------ .../Features/Main/Layout/InitVideoPlayer.cs | 3 +- src/ui/Features/Main/MainViewModel.cs | 3 +- .../VideoControlsItemDisplay.cs | 4 +- .../VideoControlsItemsWindow.cs | 2 +- src/ui/Logic/Config/SeVideoControlsItem.cs | 1 + .../Logic/Config/SeVideoControlsItemType.cs | 4 +- .../Controls/VideoPlayerControlLayoutTests.cs | 9 ++-- .../Logic/Config/SeVideoControlsItemTests.cs | 5 ++- 9 files changed, 52 insertions(+), 22 deletions(-) diff --git a/src/ui/Controls/VideoPlayer/VideoPlayerControl.cs b/src/ui/Controls/VideoPlayer/VideoPlayerControl.cs index f73c5fa378f..0c121b6bb94 100644 --- a/src/ui/Controls/VideoPlayer/VideoPlayerControl.cs +++ b/src/ui/Controls/VideoPlayer/VideoPlayerControl.cs @@ -59,8 +59,11 @@ public class VideoPlayerControl : UserControl public static readonly StyledProperty NextSubtitleCommandProperty = AvaloniaProperty.Register(nameof(NextSubtitleCommand)); - public static readonly StyledProperty SubtitleNavigationIsVisibleProperty = - AvaloniaProperty.Register(nameof(SubtitleNavigationIsVisible)); + public static readonly StyledProperty ToggleSubtitlesCommandProperty = + AvaloniaProperty.Register(nameof(ToggleSubtitlesCommand)); + + public static readonly StyledProperty SubtitleButtonsIsVisibleProperty = + AvaloniaProperty.Register(nameof(SubtitleButtonsIsVisible)); public static readonly StyledProperty StopIsVisibleProperty = AvaloniaProperty.Register(nameof(StopIsVisible)); @@ -148,14 +151,21 @@ public ICommand? NextSubtitleCommand set => SetValue(NextSubtitleCommandProperty, value); } + public ICommand? ToggleSubtitlesCommand + { + get => GetValue(ToggleSubtitlesCommandProperty); + set => SetValue(ToggleSubtitlesCommandProperty, value); + } + /// - /// The previous/next subtitle buttons need a subtitle list, so only the main window's - /// players (and the controls preview) show them - when the controls layout does (#16047). + /// The previous/next subtitle and toggle subtitles buttons need the main window's + /// subtitle, so only its players (and the controls preview) show them - when the + /// controls layout does (#16047). /// - public bool SubtitleNavigationIsVisible + public bool SubtitleButtonsIsVisible { - get => GetValue(SubtitleNavigationIsVisibleProperty); - set => SetValue(SubtitleNavigationIsVisibleProperty, value); + get => GetValue(SubtitleButtonsIsVisibleProperty); + set => SetValue(SubtitleButtonsIsVisibleProperty, value); } public bool StopIsVisible @@ -216,6 +226,7 @@ public bool IsFullScreen private readonly Button _buttonStop; private readonly Button _buttonPreviousSubtitle; private readonly Button _buttonNextSubtitle; + private readonly Button _buttonToggleSubtitles; private readonly StackPanel _panelFullScreen; private readonly Slider _sliderPosition; private readonly StackPanel _panelVolume; @@ -558,15 +569,19 @@ public VideoPlayerControl(IVideoPlayer videoPlayerInstance) Source = this }); - // Previous/next subtitle (#16047) - _buttonPreviousSubtitle = MakeSubtitleNavigationButton( + // Previous/next subtitle and toggle subtitles on the video (#16047) + _buttonPreviousSubtitle = MakeSubtitleButton( Se.Language.Options.Settings.VideoControlsPreviousSubtitle, "fa-solid fa-backward-step", nameof(PreviousSubtitleCommand)); - _buttonNextSubtitle = MakeSubtitleNavigationButton( + _buttonNextSubtitle = MakeSubtitleButton( Se.Language.Options.Settings.VideoControlsNextSubtitle, "fa-solid fa-forward-step", nameof(NextSubtitleCommand)); + _buttonToggleSubtitles = MakeSubtitleButton( + Se.Language.Video.ToggleSubtitlesOnVideoPlayer, + "fa-solid fa-closed-captioning", + nameof(ToggleSubtitlesCommand)); // Fullscreen _buttonFullScreen = new NonSpaceButton @@ -1075,6 +1090,7 @@ public void ApplyControlsLayout(IEnumerable? items) SeVideoControlsItemType.Stop => _buttonStop, SeVideoControlsItemType.PreviousSubtitle => _buttonPreviousSubtitle, SeVideoControlsItemType.NextSubtitle => _buttonNextSubtitle, + SeVideoControlsItemType.ToggleSubtitles => _buttonToggleSubtitles, SeVideoControlsItemType.FullScreen => _panelFullScreen, SeVideoControlsItemType.PositionSlider => _sliderPosition, SeVideoControlsItemType.Volume => _panelVolume, @@ -1161,7 +1177,7 @@ public void ApplyControlsLayout(IEnumerable? items) UpdateVideoFileNameMaxWidth(); } - private NonSpaceButton MakeSubtitleNavigationButton(string name, string iconName, string commandPropertyName) + private NonSpaceButton MakeSubtitleButton(string name, string iconName, string commandPropertyName) { var button = new NonSpaceButton { @@ -1170,7 +1186,7 @@ private NonSpaceButton MakeSubtitleNavigationButton(string name, string iconName }; button.Bind(IsVisibleProperty, new Binding { - Path = nameof(SubtitleNavigationIsVisible), + Path = nameof(SubtitleButtonsIsVisible), Source = this }); Attached.SetIcon(button, iconName); @@ -1191,7 +1207,8 @@ private static double GetControlsSpacing(SeVideoControlsItemType left, SeVideoCo { static bool IsButton(SeVideoControlsItemType type) => type is SeVideoControlsItemType.Play or SeVideoControlsItemType.Stop or SeVideoControlsItemType.FullScreen or - SeVideoControlsItemType.PreviousSubtitle or SeVideoControlsItemType.NextSubtitle; + SeVideoControlsItemType.PreviousSubtitle or SeVideoControlsItemType.NextSubtitle or + SeVideoControlsItemType.ToggleSubtitles; if (IsButton(left) && IsButton(right)) { diff --git a/src/ui/Features/Main/Layout/InitVideoPlayer.cs b/src/ui/Features/Main/Layout/InitVideoPlayer.cs index 5928c912037..c0e49191a3a 100644 --- a/src/ui/Features/Main/Layout/InitVideoPlayer.cs +++ b/src/ui/Features/Main/Layout/InitVideoPlayer.cs @@ -92,7 +92,8 @@ public static Grid MakeLayoutVideoPlayer(MainViewModel vm, Thickness nonFullScre control.FullScreenCommand = vm.VideoFullScreenCommand; control.PreviousSubtitleCommand = vm.PlayPreviousCommand; control.NextSubtitleCommand = vm.PlayNextCommand; - control.SubtitleNavigationIsVisible = true; + control.ToggleSubtitlesCommand = vm.ToggleSubtitlesOnVideoPlayerCommand; + control.SubtitleButtonsIsVisible = true; videoPlayerControl = control; vm.VideoPlayerControl = control; control.VideoPlayerDisplayTimeLeft = Se.Settings.Video.VideoPlayerDisplayTimeLeft; diff --git a/src/ui/Features/Main/MainViewModel.cs b/src/ui/Features/Main/MainViewModel.cs index c3a79386837..bc0668a06b2 100644 --- a/src/ui/Features/Main/MainViewModel.cs +++ b/src/ui/Features/Main/MainViewModel.cs @@ -20066,7 +20066,8 @@ private void VideoFullScreen() _fullScreenVideoPlayerControl.IsFullScreen = true; _fullScreenVideoPlayerControl.PreviousSubtitleCommand = PlayPreviousCommand; _fullScreenVideoPlayerControl.NextSubtitleCommand = PlayNextCommand; - _fullScreenVideoPlayerControl.SubtitleNavigationIsVisible = true; + _fullScreenVideoPlayerControl.ToggleSubtitlesCommand = ToggleSubtitlesOnVideoPlayerCommand; + _fullScreenVideoPlayerControl.SubtitleButtonsIsVisible = true; // The fullscreen player is created bare (not via MakeLayoutVideoPlayer), so wire the // pause/stop cursor-freeze here as well (issue #12233). _fullScreenVideoPlayerControl.PlayPauseRequested += willPause => diff --git a/src/ui/Features/Options/Settings/VideoControlsItems/VideoControlsItemDisplay.cs b/src/ui/Features/Options/Settings/VideoControlsItems/VideoControlsItemDisplay.cs index 87d1ec28ec7..a16c46edb26 100644 --- a/src/ui/Features/Options/Settings/VideoControlsItems/VideoControlsItemDisplay.cs +++ b/src/ui/Features/Options/Settings/VideoControlsItems/VideoControlsItemDisplay.cs @@ -34,11 +34,12 @@ private static string GetDisplayName(SeVideoControlsItemType type) SeVideoControlsItemType.PlayerName => l.VideoControlsPlayerName, SeVideoControlsItemType.PreviousSubtitle => l.VideoControlsPreviousSubtitle, SeVideoControlsItemType.NextSubtitle => l.VideoControlsNextSubtitle, + SeVideoControlsItemType.ToggleSubtitles => Se.Language.Video.ToggleSubtitlesOnVideoPlayer, _ => type.ToString(), }; } - // Play, stop, previous/next subtitle, full screen and volume use the same icons as the video controls themselves. + // Play, stop, previous/next subtitle, toggle subtitles, full screen and volume use the same icons as the video controls themselves. private static string GetIconName(SeVideoControlsItemType type) { return type switch @@ -53,6 +54,7 @@ private static string GetIconName(SeVideoControlsItemType type) SeVideoControlsItemType.PlayerName => "fa-solid fa-circle-info", SeVideoControlsItemType.PreviousSubtitle => "fa-solid fa-backward-step", SeVideoControlsItemType.NextSubtitle => "fa-solid fa-forward-step", + SeVideoControlsItemType.ToggleSubtitles => "fa-solid fa-closed-captioning", _ => "fa-solid fa-circle", }; } diff --git a/src/ui/Features/Options/Settings/VideoControlsItems/VideoControlsItemsWindow.cs b/src/ui/Features/Options/Settings/VideoControlsItems/VideoControlsItemsWindow.cs index 0ce1bbded0a..6932283fbc9 100644 --- a/src/ui/Features/Options/Settings/VideoControlsItems/VideoControlsItemsWindow.cs +++ b/src/ui/Features/Options/Settings/VideoControlsItems/VideoControlsItemsWindow.cs @@ -38,7 +38,7 @@ public VideoControlsItemsWindow(VideoControlsItemsViewModel vm) Focusable = false, StopIsVisible = true, FullScreenIsVisible = true, - SubtitleNavigationIsVisible = true, + SubtitleButtonsIsVisible = true, }; KeyboardNavigation.SetTabNavigation(preview, KeyboardNavigationMode.None); preview.SetPreviewCaptions("00:01:23.456 / 01:39:43.040", "movie.mkv", "libmpv"); diff --git a/src/ui/Logic/Config/SeVideoControlsItem.cs b/src/ui/Logic/Config/SeVideoControlsItem.cs index 196f50e31cf..4cf3fefe154 100644 --- a/src/ui/Logic/Config/SeVideoControlsItem.cs +++ b/src/ui/Logic/Config/SeVideoControlsItem.cs @@ -32,6 +32,7 @@ public static List MakeDefaults() new SeVideoControlsItem { Type = SeVideoControlsItemType.Stop, SortOrder = 20 }, new SeVideoControlsItem { Type = SeVideoControlsItemType.PreviousSubtitle, IsVisible = false, SortOrder = 22 }, new SeVideoControlsItem { Type = SeVideoControlsItemType.NextSubtitle, IsVisible = false, SortOrder = 24 }, + new SeVideoControlsItem { Type = SeVideoControlsItemType.ToggleSubtitles, IsVisible = false, SortOrder = 26 }, new SeVideoControlsItem { Type = SeVideoControlsItemType.FullScreen, SortOrder = 30 }, new SeVideoControlsItem { Type = SeVideoControlsItemType.PositionSlider, SortOrder = 40 }, new SeVideoControlsItem { Type = SeVideoControlsItemType.Volume, SortOrder = 50 }, diff --git a/src/ui/Logic/Config/SeVideoControlsItemType.cs b/src/ui/Logic/Config/SeVideoControlsItemType.cs index af80a61f9b9..776c2459ab2 100644 --- a/src/ui/Logic/Config/SeVideoControlsItemType.cs +++ b/src/ui/Logic/Config/SeVideoControlsItemType.cs @@ -21,7 +21,9 @@ public enum SeVideoControlsItemType // Small label in the top right corner (e.g. "libmpv 2.5 sw"). PlayerName = 7, - // Buttons that go to the previous/next subtitle and play it (#16047). Hidden by default. + // Buttons that go to the previous/next subtitle and play it, and one that shows/hides the + // subtitles on the video (#16047). Hidden by default. PreviousSubtitle = 8, NextSubtitle = 9, + ToggleSubtitles = 10, } diff --git a/tests/UI/Controls/VideoPlayerControlLayoutTests.cs b/tests/UI/Controls/VideoPlayerControlLayoutTests.cs index b594e3243a9..b6e9ff21cbe 100644 --- a/tests/UI/Controls/VideoPlayerControlLayoutTests.cs +++ b/tests/UI/Controls/VideoPlayerControlLayoutTests.cs @@ -139,11 +139,11 @@ public void Normalize_DropsDuplicatesAndAddsMissing() } [AvaloniaFact] - public void SubtitleNavigationButtons_ShownOnlyWhenEnabledAndInLayout() + public void SubtitleButtons_ShownOnlyWhenEnabledAndInLayout() { var control = new VideoPlayerControl(new EmptyVideoPlayer()); var items = SeVideoControlsItem.MakeDefaults(); - foreach (var item in items.Where(p => p.Type is SeVideoControlsItemType.PreviousSubtitle or SeVideoControlsItemType.NextSubtitle)) + foreach (var item in items.Where(p => p.Type is SeVideoControlsItemType.PreviousSubtitle or SeVideoControlsItemType.NextSubtitle or SeVideoControlsItemType.ToggleSubtitles)) { item.IsVisible = true; } @@ -153,11 +153,14 @@ public void SubtitleNavigationButtons_ShownOnlyWhenEnabledAndInLayout() var next = GetButton(control, Se.Language.Options.Settings.VideoControlsNextSubtitle); Assert.Equal(2, Grid.GetColumn(previous)); Assert.Equal(3, Grid.GetColumn(next)); + var toggle = GetButton(control, Se.Language.Video.ToggleSubtitlesOnVideoPlayer); + Assert.Equal(4, Grid.GetColumn(toggle)); Assert.False(previous.IsVisible); // dialogs without a subtitle list - control.SubtitleNavigationIsVisible = true; + control.SubtitleButtonsIsVisible = true; Assert.True(previous.IsVisible); Assert.True(next.IsVisible); + Assert.True(toggle.IsVisible); control.ApplyControlsLayout(SeVideoControlsItem.MakeDefaults()); Assert.DoesNotContain(previous, GetControlsGrid(control).Children); diff --git a/tests/UI/Logic/Config/SeVideoControlsItemTests.cs b/tests/UI/Logic/Config/SeVideoControlsItemTests.cs index 5c8dfdbf0e6..e2dde528e3a 100644 --- a/tests/UI/Logic/Config/SeVideoControlsItemTests.cs +++ b/tests/UI/Logic/Config/SeVideoControlsItemTests.cs @@ -5,12 +5,13 @@ namespace UITests.Logic.Config; public class SeVideoControlsItemTests { [Fact] - public void MakeDefaults_SubtitleNavigationButtonsHidden() + public void MakeDefaults_SubtitleButtonsHidden() { var items = SeVideoControlsItem.MakeDefaults(); Assert.False(items.Single(p => p.Type == SeVideoControlsItemType.PreviousSubtitle).IsVisible); Assert.False(items.Single(p => p.Type == SeVideoControlsItemType.NextSubtitle).IsVisible); + Assert.False(items.Single(p => p.Type == SeVideoControlsItemType.ToggleSubtitles).IsVisible); } [Fact] @@ -38,6 +39,7 @@ public void Normalize_OlderSettings_InsertsMissingItemsAfterTheirDefaultPredeces SeVideoControlsItemType.Stop, SeVideoControlsItemType.PreviousSubtitle, SeVideoControlsItemType.NextSubtitle, + SeVideoControlsItemType.ToggleSubtitles, SeVideoControlsItemType.PositionSlider, SeVideoControlsItemType.Volume, SeVideoControlsItemType.PositionText, @@ -46,6 +48,7 @@ public void Normalize_OlderSettings_InsertsMissingItemsAfterTheirDefaultPredeces ], result.Select(p => p.Type)); Assert.False(result[3].IsVisible); Assert.False(result[4].IsVisible); + Assert.False(result[5].IsVisible); Assert.Equal(Enumerable.Range(1, result.Count).Select(i => i * 10), result.Select(p => p.SortOrder)); } From 9d794c60c9f48b72859f95d65fffa5de6cc7912f Mon Sep 17 00:00:00 2001 From: niksedk Date: Fri, 16 Oct 2026 18:07:10 +0200 Subject: [PATCH 278/306] Video player: toggle subtitles button shows its state (#16047) NonSpaceToggleButton, checked with a filled CC icon while subtitles are shown on the video and an outline icon when hidden. The owner sets the state after the toggle action runs, so docked, undocked and full-screen players stay in step and a player without subtitle support leaves it. Co-Authored-By: Claude Opus 5.5 --- src/ui/Controls/NonSpaceToggleButton.cs | 43 +++++++++++++++++ .../VideoPlayer/VideoPlayerControl.cs | 46 ++++++++++++++++--- .../Features/Main/Layout/InitVideoPlayer.cs | 1 + src/ui/Features/Main/MainViewModel.cs | 21 +++++++++ .../Controls/VideoPlayerControlLayoutTests.cs | 5 ++ 5 files changed, 109 insertions(+), 7 deletions(-) create mode 100644 src/ui/Controls/NonSpaceToggleButton.cs diff --git a/src/ui/Controls/NonSpaceToggleButton.cs b/src/ui/Controls/NonSpaceToggleButton.cs new file mode 100644 index 00000000000..4a921f73ce0 --- /dev/null +++ b/src/ui/Controls/NonSpaceToggleButton.cs @@ -0,0 +1,43 @@ +using Avalonia.Controls.Primitives; +using Avalonia.Input; +using System; + +namespace Nikse.SubtitleEdit.Controls; + +/// +/// A that never reacts to the Space key (see ) +/// and does not flip itself when clicked: the owner sets it +/// from the state the click's command changed, so a command that does nothing (e.g. a player +/// without subtitle support) leaves the button as it was. +/// +public class NonSpaceToggleButton : ToggleButton +{ + // Keep the default ToggleButton styling instead of looking for a NonSpaceToggleButton style. + protected override Type StyleKeyOverride => typeof(ToggleButton); + + protected override void Toggle() + { + } + + protected override void OnKeyDown(KeyEventArgs e) + { + if (e.Key == Key.Space) + { + e.Handled = true; + return; + } + + base.OnKeyDown(e); + } + + protected override void OnKeyUp(KeyEventArgs e) + { + if (e.Key == Key.Space) + { + e.Handled = true; + return; + } + + base.OnKeyUp(e); + } +} diff --git a/src/ui/Controls/VideoPlayer/VideoPlayerControl.cs b/src/ui/Controls/VideoPlayer/VideoPlayerControl.cs index 0c121b6bb94..b11329899f9 100644 --- a/src/ui/Controls/VideoPlayer/VideoPlayerControl.cs +++ b/src/ui/Controls/VideoPlayer/VideoPlayerControl.cs @@ -62,6 +62,9 @@ public class VideoPlayerControl : UserControl public static readonly StyledProperty ToggleSubtitlesCommandProperty = AvaloniaProperty.Register(nameof(ToggleSubtitlesCommand)); + public static readonly StyledProperty SubtitlesOnVideoIsVisibleProperty = + AvaloniaProperty.Register(nameof(SubtitlesOnVideoIsVisible), true); + public static readonly StyledProperty SubtitleButtonsIsVisibleProperty = AvaloniaProperty.Register(nameof(SubtitleButtonsIsVisible)); @@ -157,6 +160,16 @@ public ICommand? ToggleSubtitlesCommand set => SetValue(ToggleSubtitlesCommandProperty, value); } + /// + /// Whether the subtitles are shown on the video - the checked state of the toggle + /// subtitles button. Set by the owner after runs. + /// + public bool SubtitlesOnVideoIsVisible + { + get => GetValue(SubtitlesOnVideoIsVisibleProperty); + set => SetValue(SubtitlesOnVideoIsVisibleProperty, value); + } + /// /// The previous/next subtitle and toggle subtitles buttons need the main window's /// subtitle, so only its players (and the controls preview) show them - when the @@ -226,7 +239,7 @@ public bool IsFullScreen private readonly Button _buttonStop; private readonly Button _buttonPreviousSubtitle; private readonly Button _buttonNextSubtitle; - private readonly Button _buttonToggleSubtitles; + private readonly NonSpaceToggleButton _buttonToggleSubtitles; private readonly StackPanel _panelFullScreen; private readonly Slider _sliderPosition; private readonly StackPanel _panelVolume; @@ -571,17 +584,27 @@ public VideoPlayerControl(IVideoPlayer videoPlayerInstance) // Previous/next subtitle and toggle subtitles on the video (#16047) _buttonPreviousSubtitle = MakeSubtitleButton( + new NonSpaceButton(), Se.Language.Options.Settings.VideoControlsPreviousSubtitle, "fa-solid fa-backward-step", nameof(PreviousSubtitleCommand)); _buttonNextSubtitle = MakeSubtitleButton( + new NonSpaceButton(), Se.Language.Options.Settings.VideoControlsNextSubtitle, "fa-solid fa-forward-step", nameof(NextSubtitleCommand)); + // Checked (and a filled icon) while the subtitles are shown on the video. _buttonToggleSubtitles = MakeSubtitleButton( + new NonSpaceToggleButton(), Se.Language.Video.ToggleSubtitlesOnVideoPlayer, "fa-solid fa-closed-captioning", nameof(ToggleSubtitlesCommand)); + _buttonToggleSubtitles.Bind(ToggleButton.IsCheckedProperty, new Binding + { + Path = nameof(SubtitlesOnVideoIsVisible), + Source = this, + Mode = BindingMode.OneWay, + }); // Fullscreen _buttonFullScreen = new NonSpaceButton @@ -1177,13 +1200,10 @@ public void ApplyControlsLayout(IEnumerable? items) UpdateVideoFileNameMaxWidth(); } - private NonSpaceButton MakeSubtitleButton(string name, string iconName, string commandPropertyName) + private T MakeSubtitleButton(T button, string name, string iconName, string commandPropertyName) where T : Button { - var button = new NonSpaceButton - { - VerticalAlignment = VerticalAlignment.Center, - [AutomationProperties.NameProperty] = name, - }; + button.VerticalAlignment = VerticalAlignment.Center; + AutomationProperties.SetName(button, name); button.Bind(IsVisibleProperty, new Binding { Path = nameof(SubtitleButtonsIsVisible), @@ -1203,6 +1223,18 @@ private NonSpaceButton MakeSubtitleButton(string name, string iconName, string c return button; } + protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) + { + base.OnPropertyChanged(change); + + if (change.Property == SubtitlesOnVideoIsVisibleProperty) + { + Attached.SetIcon(_buttonToggleSubtitles, SubtitlesOnVideoIsVisible + ? "fa-solid fa-closed-captioning" + : "fa-regular fa-closed-captioning"); + } + } + private static double GetControlsSpacing(SeVideoControlsItemType left, SeVideoControlsItemType right) { static bool IsButton(SeVideoControlsItemType type) => diff --git a/src/ui/Features/Main/Layout/InitVideoPlayer.cs b/src/ui/Features/Main/Layout/InitVideoPlayer.cs index c0e49191a3a..ad146351ba5 100644 --- a/src/ui/Features/Main/Layout/InitVideoPlayer.cs +++ b/src/ui/Features/Main/Layout/InitVideoPlayer.cs @@ -93,6 +93,7 @@ public static Grid MakeLayoutVideoPlayer(MainViewModel vm, Thickness nonFullScre control.PreviousSubtitleCommand = vm.PlayPreviousCommand; control.NextSubtitleCommand = vm.PlayNextCommand; control.ToggleSubtitlesCommand = vm.ToggleSubtitlesOnVideoPlayerCommand; + control.SubtitlesOnVideoIsVisible = vm.SubtitlesOnVideoPlayerVisible; control.SubtitleButtonsIsVisible = true; videoPlayerControl = control; vm.VideoPlayerControl = control; diff --git a/src/ui/Features/Main/MainViewModel.cs b/src/ui/Features/Main/MainViewModel.cs index bc0668a06b2..252bb3670d4 100644 --- a/src/ui/Features/Main/MainViewModel.cs +++ b/src/ui/Features/Main/MainViewModel.cs @@ -4869,6 +4869,24 @@ private void ToggleCurrentSubtitleWhilePlaying() } } + /// + /// Whether the user has the subtitles shown on the video (the toggle subtitles action). + /// + public bool SubtitlesOnVideoPlayerVisible => _mpvReloader.SubtitlesVisible; + + // The video controls' toggle subtitles button shows the state (#16047); the docked and the + // fullscreen player can both exist, so update every one. + private void UpdateSubtitlesOnVideoToggleButtons() + { + foreach (var control in new[] { VideoPlayerControl, _fullScreenVideoPlayerControl, _videoPlayerUndockedViewModel?.VideoPlayerControl }) + { + if (control != null) + { + control.SubtitlesOnVideoIsVisible = _mpvReloader.SubtitlesVisible; + } + } + } + [RelayCommand] private void ToggleSubtitlesOnVideoPlayer() { @@ -4877,6 +4895,7 @@ private void ToggleSubtitlesOnVideoPlayer() { _mpvReloader.SubtitlesVisible = !_mpvReloader.SubtitlesVisible; ffmpeg.PreviewSubtitlesVisible = _mpvReloader.SubtitlesEffectivelyVisible; + UpdateSubtitlesOnVideoToggleButtons(); ShowStatus(_mpvReloader.SubtitlesVisible ? Se.Language.Video.SubtitlesOnVideoPlayerOn : Se.Language.Video.SubtitlesOnVideoPlayerOff); _shortcutManager.ClearKeys(); return; @@ -4889,6 +4908,7 @@ private void ToggleSubtitlesOnVideoPlayer() _mpvReloader.SubtitlesVisible = !_mpvReloader.SubtitlesVisible; mpv.SetSubtitleVisibility(_mpvReloader.SubtitlesEffectivelyVisible); + UpdateSubtitlesOnVideoToggleButtons(); if (_mpvReloader.SubtitlesVisible) { @@ -20067,6 +20087,7 @@ private void VideoFullScreen() _fullScreenVideoPlayerControl.PreviousSubtitleCommand = PlayPreviousCommand; _fullScreenVideoPlayerControl.NextSubtitleCommand = PlayNextCommand; _fullScreenVideoPlayerControl.ToggleSubtitlesCommand = ToggleSubtitlesOnVideoPlayerCommand; + _fullScreenVideoPlayerControl.SubtitlesOnVideoIsVisible = _mpvReloader.SubtitlesVisible; _fullScreenVideoPlayerControl.SubtitleButtonsIsVisible = true; // The fullscreen player is created bare (not via MakeLayoutVideoPlayer), so wire the // pause/stop cursor-freeze here as well (issue #12233). diff --git a/tests/UI/Controls/VideoPlayerControlLayoutTests.cs b/tests/UI/Controls/VideoPlayerControlLayoutTests.cs index b6e9ff21cbe..9686b6513db 100644 --- a/tests/UI/Controls/VideoPlayerControlLayoutTests.cs +++ b/tests/UI/Controls/VideoPlayerControlLayoutTests.cs @@ -1,3 +1,4 @@ +using Nikse.SubtitleEdit.Controls; using System.Collections.Generic; using System.Linq; using Avalonia.Automation; @@ -161,6 +162,10 @@ public void SubtitleButtons_ShownOnlyWhenEnabledAndInLayout() Assert.True(previous.IsVisible); Assert.True(next.IsVisible); Assert.True(toggle.IsVisible); + var toggleButton = Assert.IsType(toggle); + Assert.True(toggleButton.IsChecked); + control.SubtitlesOnVideoIsVisible = false; + Assert.False(toggleButton.IsChecked); control.ApplyControlsLayout(SeVideoControlsItem.MakeDefaults()); Assert.DoesNotContain(previous, GetControlsGrid(control).Children); From 79380419862e5e5ad8b75599cb72889906cb5940 Mon Sep 17 00:00:00 2001 From: 12si27 <88251502+12si27@users.noreply.github.com> Date: Sat, 17 Oct 2026 01:24:59 +0900 Subject: [PATCH 279/306] Update Korean.json --- src/ui/Assets/Languages/Korean.json | 816 ++++++++++++++++++---------- 1 file changed, 516 insertions(+), 300 deletions(-) diff --git a/src/ui/Assets/Languages/Korean.json b/src/ui/Assets/Languages/Korean.json index 26a215d0046..5580861c1a6 100644 --- a/src/ui/Assets/Languages/Korean.json +++ b/src/ui/Assets/Languages/Korean.json @@ -96,8 +96,8 @@ "box": "상자", "boxColor": "상자 색상", "boxCornerRadius": "상자 모서리 반경", - "boxPadding": "상자 여백", - "boxPerLine": "한 줄에 한 칸씩", + "boxPadding": "상자 안쪽 여백", + "boxPerLine": "줄마다 상자 표시", "bridgeGaps": "간격 메우기", "bytes": "바이트", "cancel": "취소(_A)", @@ -134,17 +134,17 @@ "colorDotDotDot": "색상...", "colorGray": "회색", "colorGreen": "초록", - "colorHalfBlue": "반투명 파랑", - "colorHalfCyan": "반투명 청록", - "colorHalfGreen": "반투명 초록", - "colorHalfMagenta": "반투명 자홍", - "colorHalfRed": "반투명 빨강", - "colorHalfYellow": "반투명 노랑", + "colorHalfBlue": "파랑 (절반 밝기)", + "colorHalfCyan": "청록 (절반 밝기)", + "colorHalfGreen": "초록 (절반 밝기)", + "colorHalfMagenta": "자홍 (절반 밝기)", + "colorHalfRed": "빨강 (절반 밝기)", + "colorHalfYellow": "노랑 (절반 밝기)", "colorMagenta": "자홍", "colorRed": "빨강", "colorWhite": "하양", "colorYellow": "노랑", - "column": "칼럼", + "column": "열", "columns": "열", "columnsDotDotDot": "열...", "consoleLog": "콘솔 로그", @@ -184,7 +184,7 @@ "description": "설명", "dictionary": "사전", "diskSpace": "디스크 공간", - "doNoChange": "변경 금지", + "doNoChange": "변경 안 함", "done": "완료(_D)", "doubleWords": "중복 단어", "doubleLines": "중복 줄", @@ -254,7 +254,7 @@ "fixRightToLeft": "RTL 수정", "fixRightToLeftViaUnicodeTags": "유니코드 태그를 통해 RTL 수정", "fixedValue": "고정값", - "focusSelectedLine": "선택한 줄 강조 표시(목록에서 스크롤하여 표시)", + "focusSelectedLine": "선택한 줄로 이동 (목록에서 보이도록 스크롤)", "font": "글꼴", "fontColor": "글자 색상", "fontHeight": "글자 높이", @@ -262,7 +262,7 @@ "fontNameDotDotDot": "글꼴 이름...", "fontSize": "글자 크기", "fonts": "글꼴", - "footer": "하단", + "footer": "푸터", "forced": "강제됨", "foreground": "전경", "format": "형식", @@ -281,7 +281,7 @@ "generate": "생성", "generateImportShotChanges": "장면 전환 생성/가져오기", "generating": "생성 중...", - "generatingImageXofY": "{1:#,##0}의 이미지 {0:#,##0}을(를) 생성 중...", + "generatingImageXofY": "이미지 {1:#,##0}개 중 {0:#,##0}번째 생성 중...", "generatingAudioFile": "오디오 파일 생성 중...", "getAudioClips": "오디오 클립 받기", "goTo": "이동", @@ -342,7 +342,7 @@ "lockTimeCodes": "타임코드 고정", "logo": "로고", "lookUpX": "\"{0}\" 조회", - "margin": "여백", + "margin": "바깥 여백", "match": "일치", "maxCharactersPerSecond": "초당 최대 문자 수", "mediaInformation": "매체 정보", @@ -384,7 +384,7 @@ "newProfile": "새 프로필", "next": "다음", "newSubtitleStartKeyDownSetEndKeyUp": "자막 삽입: 키를 눌렀을 때 시작하고, 키를 뗐을 때 종료하도록 설정", - "setSubtitleStartAtVideoPositionSetEndAtKeyUpAndGoToNext": "비디오 시작 위치를 지정하고, 키를 뗐을 때 끝 위치를 지정하며 다음으로 이동", + "setSubtitleStartAtVideoPositionSetEndAtKeyUpAndGoToNext": "자막 시작을 현재 비디오 위치로 설정하고, 키를 놓으면 끝을 설정한 뒤 다음으로 이동", "no": "아니오", "noColor": "색상 없음", "noFilesToConvert": "변환할 파일이 없음", @@ -394,7 +394,7 @@ "noSymbolLines": "#줄", "noVideoLoaded": "불러온 비디오가 없습니다", "none": "없음", - "normalCasing": "일반 케이스", + "normalCasing": "일반 대소문자", "notAvailable": "해당 없음", "notInstalled": "설치되지 않음", "nothingSelected": "선택된 항목 없음", @@ -403,7 +403,7 @@ "ocrDotDotDot": "OCR...", "ocrPercentX": "OCR: {0}%", "offset": "오프셋", - "offsetTimeCodes": "오프셋 타임코드", + "offsetTimeCodes": "타임코드 오프셋 조정", "offsetX": "오프셋 X", "offsetY": "오프셋 Y", "ok": "확인(_O)", @@ -434,12 +434,12 @@ "overwriteExistingFiles": "기존 파일 덮어쓰기", "overwriteFilesInFolderX": "폴더 내 파일 덮어쓰기: \"{0}\"", "overwriteQuestion": "덮어쓰겠습니까?", - "padding": "여백", + "padding": "안쪽 여백", "parameters": "매개변수", "parsingXDotDotDot": "{0} 분석 중...", "paste": "붙여넣기", "pasteNewSelection": "선택 영역에 클립보드 텍스트 붙여넣기", - "pause": "중지", + "pause": "일시 중지", "percent": "퍼센트", "pickLayer": "레이어 설정", "pickOllamaModel": "Ollama 모델 선택", @@ -490,7 +490,7 @@ "recentFiles": "최근 파일", "recentlyUsedVideoOffsets": "최근 사용한 비디오 오프셋", "redo": "다시 실행", - "redownload": "재다운로드", + "redownload": "다시 다운로드...", "release": "릴리즈", "refresh": "새로 고침", "region": "지역", @@ -559,7 +559,7 @@ "selectedLines": "선택된 줄", "selectedlinesX": "선택된 줄: {0}", "sensitivity": "감도", - "separator": "구분선", + "separator": "구분자", "setEnd": "끝점 설정", "setEndAndGoToNext": "끝점 설정 후 다음으로 이동", "setEndAddNewAndGoToNew": "끝점 설정 후 새 줄 추가 및 이동", @@ -592,8 +592,8 @@ "showDurationColumn": "\"길이\" 열 표시", "showGapColumn": "\"간격\" 열 표시", "showForcedColumn": "\"강제 표시\" 열 표시", - "showStartColumn": "\"표시\" 열 표시", - "showHideColumn": "\"숨김\" 열 표시", + "showStartColumn": "\"시작\" 열 표시", + "showHideColumn": "\"끝\" 열 표시", "showHistory": "기록 보기", "clearHistory": "기록 비우기", "removeFromHistory": "기록에서 지우기", @@ -646,7 +646,7 @@ "splitLineAtVideoAndTextBoxPosition": "비디오와 입력칸 위치에서 줄 나누기", "splitLineAtVideoPosition": "비디오 위치에서 줄 나누기", "splitLineAtWaveformPosition": "파형 위치에서 줄 나누기", - "startFrom": "시작하기", + "startFrom": "시작 위치", "startTime": "시작 시간", "startingDotDotDot": "시작 중...", "status": "상태", @@ -655,7 +655,7 @@ "stop": "중지", "strikeout": "취소선", "style": "스타일", - "styleExaggeration": "과장된 스타일", + "styleExaggeration": "스타일 강조 정도", "styles": "스타일", "subtitleFile": "자막 파일", "subtitleFileChangedOutsideX": "파일 \"{0}\"이(가) Subtitle Edit 외부에서 변경되었습니다.\n\n현재 수정 내용을 버리고 다시 불러오시겠습니까?", @@ -681,21 +681,21 @@ "textColor": "텍스트 색상", "textFiles": "텍스트 파일", "textOrImage": "텍스트/이미지", - "threeLetterLanguageCode": "3자리 언어 코드", + "threeLetterLanguageCode": "알파벳 3자리 (ISO 639-2/T)", "threeLetterLanguageCodeBibliographic": "알파벳 3자리 (ISO 639-2/B)", "time": "시간", "timeCodes": "타임코드", "timing": "타이밍", "title": "타이틀", - "to": "을(를)", - "toggleCasing": "케이스 표시/숨기기", + "to": "대상", + "toggleCasing": "대소문자 전환", "toggleDirection": "방향 전환", "toggleForced": "강제 표시 설정", "markedXLinesAsForced": "{0}개 줄을 강제 표시로 지정함", "unmarkedXLinesAsForced": "{0}개 줄의 강제 표시를 해제함", "noForcedLinesFound": "강제 표시 줄을 찾을 수 없습니다", "saveForcedLinesAs": "강제 표시 줄 저장...", - "toggleShotChangesAtVideoPosition": "비디오 위치에서 장면 전환", + "toggleShotChangesAtVideoPosition": "현재 비디오 위치의 장면 전환 지점 추가/제거", "toolbar": "도구 모음", "tools": "도구", "topCenter": "중앙-상단", @@ -728,7 +728,7 @@ "updatedBy": "업데이트:", "upToDate": "최신 버전", "url": "URL", - "usages": "용도", + "usages": "사용 횟수", "use": "사용", "useAlways": "항상 사용", "useOnce": "한 번 사용", @@ -737,18 +737,18 @@ "useSourceResolution": "소스 해상도 사용", "userName": "사용자 이름", "vertical": "세로", - "video100MsBack": "비디오, 100밀리초 앞으로", - "video100MsForward": "비디오, 100밀리초 뒤로", - "video500MsBack": "비디오, 500밀리초 앞으로", - "video500MsForward": "비디오, 500밀리초 뒤로", - "videoCustom1BackX": "비디오, 사용자 정의 밀리초({0:#,###,##0}) 앞으로, 1", - "videoCustom1ForwardX": "비디오, 사용자 정의 밀리초({0:#,###,##0}) 뒤로, 1", - "videoCustom2BackX": "비디오, 사용자 정의 밀리초({0:#,###,##0}) 앞으로, 2", - "videoCustom2ForwardX": "비디오, 사용자 정의 밀리초({0:#,###,##0}) 뒤로, 2", - "videoCustom3BackX": "비디오, 사용자 정의 밀리초({0:#,###,##0}) 앞으로, 3", - "videoCustom3ForwardX": "비디오, 사용자 정의 밀리초({0:#,###,##0}) 뒤로, 3", - "videoCustom4BackX": "비디오, 사용자 정의 밀리초({0:#,###,##0}) 앞으로, 4", - "videoCustom4ForwardX": "비디오, 사용자 정의 밀리초({0:#,###,##0}) 뒤로, 4", + "video100MsBack": "비디오, 100밀리초 이전으로 이동", + "video100MsForward": "비디오, 100밀리초 이후로 이동", + "video500MsBack": "비디오, 500밀리초 이전으로 이동", + "video500MsForward": "비디오, 500밀리초 이후로 이동", + "videoCustom1BackX": "비디오, 사용자 정의 밀리초({0:#,###,##0}) 이전으로 이동, 1", + "videoCustom1ForwardX": "비디오, 사용자 정의 밀리초({0:#,###,##0}) 이후로 이동, 1", + "videoCustom2BackX": "비디오, 사용자 정의 밀리초({0:#,###,##0}) 이전으로 이동, 2", + "videoCustom2ForwardX": "비디오, 사용자 정의 밀리초({0:#,###,##0}) 이후로 이동, 2", + "videoCustom3BackX": "비디오, 사용자 정의 밀리초({0:#,###,##0}) 이전으로 이동, 3", + "videoCustom3ForwardX": "비디오, 사용자 정의 밀리초({0:#,###,##0}) 이후로 이동, 3", + "videoCustom4BackX": "비디오, 사용자 정의 밀리초({0:#,###,##0}) 이전으로 이동, 4", + "videoCustom4ForwardX": "비디오, 사용자 정의 밀리초({0:#,###,##0}) 이후로 이동, 4", "videoCustom1BackAndPauseX": "비디오, 사용자 정의 밀리초({0:#,###,##0}) 뒤로 이동 후 일시 정지, 1", "videoCustom1ForwardAndPauseX": "비디오, 사용자 정의 밀리초({0:#,###,##0}) 앞으로 이동 후 일시 정지, 1", "videoCustom2BackAndPauseX": "비디오, 사용자 정의 밀리초({0:#,###,##0}) 뒤로 이동 후 일시 정지, 2", @@ -769,12 +769,12 @@ "videoFiles": "비디오 파일", "videoAndAudioFiles": "비디오 및 오디오 파일", "videoOffset": "비디오 오프셋", - "videoOneFrameBack": "비디오, 1프레임 앞으로", - "videoOneFrameForward": "비디오, 1프레임 뒤로", + "videoOneFrameBack": "비디오, 1프레임 이전으로 이동", + "videoOneFrameForward": "비디오, 1프레임 이후로 이동", "videoOneFrameBackWithPlay": "비디오, 재생과 함께 한 프레임 뒤로", "videoOneFrameForwardWithPlay": "비디오, 재생과 함께 한 프레임 앞으로", - "videoOneSecondBack": "비디오, 1초 앞으로", - "videoOneSecondForward": "비디오, 1초 뒤로", + "videoOneSecondBack": "비디오, 1초 이전으로 이동", + "videoOneSecondForward": "비디오, 1초 이후로 이동", "videoPlayer": "비디오 플레이어", "videoPosition": "비디오 위치", "videoResolution": "비디오 해상도", @@ -785,19 +785,19 @@ "waveform": "파형", "waveformCenterOnVideoPosition": "비디오 위치에서 파형 중앙 정렬", "waveformPasteFromClipboard": "클립보드에서 붙여넣기", - "waveformSpectrogram": "파형/스펙트럼", + "waveformSpectrogram": "파형/스펙트로그램", "width": "너비", "wordsPerMin": "분당 단어 수", "pixelWidth": "픽셀 너비", "wpm": "분당 단어 수", "xFiles": "{0:#,###,##0} 파일", "xOfYFiles": "{0:#,###,##0} / {1:#,###,##0} 파일", - "xFilesConvertedInY": "{0:#,###,##0}개의 파일이 {1}에 변환", + "xFilesConvertedInY": "파일 {0:#,###,##0}개 변환 완료 (소요 시간: {1})", "xNoLinesUpdated": "{0}: 줄이 업데이트되지 않음", "xNotFound": "\"{0}\"을(를) 찾을 수 없음", "xOfYLinesUpdated": "{0}: {2} 중 {1} 줄이 업데이트됨", "xOfYLinesUpdatedAndZNeighborsAdjusted": "{0}: {2} 중 {1} 줄이 업데이트됨, {3} 개의 인접 줄이 조정됨", - "noBookmarksFound": "더 이상의 북마크를 찾을 수 없음", + "noBookmarksFound": "더 이상 즐겨찾기가 없습니다", "xRequiresAnApiKey": "{0}에는 API 키가 필요", "xRequiresAValidUrl": "{0}에 올바른 URL이 필요합니다(예: \"http://...\" 또는 \"https://...\")", "yes": "예", @@ -836,7 +836,7 @@ "googleCloudSttBucketName": "스토리지 버킷 (비워 두면 자동)", "googleCloudSttDynamicBatching": "동적 배치 처리 (비용 약 1/5, 지연 시간 보장 없음)", "onlineSttApiKeyMissing": "API 키가 필요합니다. API 키를 입력하고 다시 시도해주세요.", - "openAiCompatibleSttAutoTranscribeOnAudioSelection": "새 파형 선택을 음성 인식을 통해 텍스트 변환", + "openAiCompatibleSttAutoTranscribeOnAudioSelection": "새 파형 영역을 선택하면 음성을 자동으로 텍스트로 변환", "openAiCompatibleSttStream": "반환값 스트리밍하기", "openAiCompatibleSttStreamHint": "'stream=true'를 보내면 실시간 텍스트 델타(SSE)를 받을 수 있습니다. 'stream' 매개변수를 거부하는 서버(예: Groq)의 경우 비활성화하세요.", "openAiCompatibleSttAudioFormat": "오디오 업로드 형식", @@ -916,7 +916,7 @@ "evenlyDistributeLines": "줄을 고르게 분배 (CPS 기준)", "mergeShortLines": "짧은 줄을 합치기...", "mergeContinuationLines": "이어지는 줄 병합...", - "snapAllTimesToFrames": "모든 타임을 프레임에 고정", + "snapAllTimesToFrames": "모든 시간을 프레임에 맞추기", "renumber": "번호 다시 매기기...", "removeTextForHearingImpaired": "청각 장애인용 텍스트 제거(_R)...", "removeUnicodeCharacters": "유니코드 문자 제거/교체(_U)...", @@ -930,7 +930,7 @@ "assaChangeResolution": "해상도 변경(_R)...", "assaGenerateBackground": "배경 상자 생성(_B)...", "assaApplyAdvancedEffects": "고급 효과 적용(_E)...", - "assaApplyCustomOverrideTags": "사용자 정의 태그 적용(_O)...", + "assaApplyCustomOverrideTags": "서식 재정의 태그 적용(_O)...", "assaSetPosition": "위치 설정(_S)...", "assaImageColorPicker": "이미지 색상 선택기(_I)...", "assaDraw": "그리기(_D)...", @@ -990,7 +990,7 @@ "filterLayersForDisplayDotDotDot": "표시할 레이어 필터링...", "toggleSelectSubtitleWhilePlayingCurrentlyOn": "재생 중 자막 선택 전환 (현재: 켬)", "toggleSelectSubtitleWhilePlayingCurrentlyOff": "재생 중 자막 선택 전환 (현재: 끔)", - "selectSubtitleWhilePlaying": "재생 중 자막 선택 전환" + "selectSubtitleWhilePlaying": "재생 중 현재 자막 선택" }, "toolbar": { "newHint": "새 자막 파일 {0}", @@ -1093,7 +1093,7 @@ "deleteTextAndShiftCellsUp": "텍스트를 삭제하고 셀을 위로 이동", "endTimeMustBeAfterStartTime": "끝 시간은 시작 시간 이후여야 합니다.", "errorLoad7Zip": "이 파일은 7-Zip으로 압축된 파일인 것 같습니다.\n\nSubtitle Edit은 압축 파일을 열 수 없습니다.", - "errorLoadBinaryZeroes": "죄송합니다. 이 파일에는 0으로만 채워져 있습니다!\n\nSubtitle Edit로 이 파일을 편집했다면, 메뉴 항목 [파일] > [자동 백업 복원]을 통해 백업 파일을 찾을 수 있을 것입니다.", + "errorLoadBinaryZeroes": "이 파일은 이진 값 0으로만 채워져 있습니다!\n\nSubtitle Edit로 이 파일을 편집했다면, 메뉴 항목 [파일] > [자동 백업 복원]을 통해 백업 파일을 찾을 수 있을 것입니다.", "errorLoadGZip": "이 파일은 GZip으로 압축된 파일인 것 같습니다.\n\nSubtitle Edit은 압축 파일을 열 수 없습니다.", "errorLoadLargeFile": "자막 파일로 열기에 너무 큰 파일입니다.", "errorLoadVideoFilePrompt": "해당 파일은 비디오 파일인 것 같습니다:\n{0}\n\n\"비디오 -> 비디오 열기\"를 통해 비디오를 열거나 비디오 플레이어에 비디오를 끌어다 놓아 비디오를 열 수 있습니다.\n\n비디오 파일로 여시겠습니까?", @@ -1137,10 +1137,10 @@ "pasteFromClipboardDotDotDot": "클립보드에서 붙여넣기...", "redoPerformed": "다시 실행됨", "redoPerformedXActionLeft": "다시 실행됨(남은 작업: {0})", - "removedUnicodeControlCharactersX": "{0}번 줄에서 유니코드 제어 문자를 제거", + "removedUnicodeControlCharactersX": "{0}줄에서 유니코드 제어 문자 제거", "removedXBlankLines": "{0}개의 빈 줄을 제거", - "replacedXWithYCountZ": "\"{0}\"을 \"{1}\"로 바꿈 ({2}번 시행)", - "reversedStartAndEndingsForRightToLeftX": "{0}번 줄 분량의 RTL 텍스트의 시작과 끝을 뒤바꾸기", + "replacedXWithYCountZ": "\"{0}\"을(를) \"{1}\"(으)로 바꿈 ({2}개 항목)", + "reversedStartAndEndingsForRightToLeftX": "{0}줄의 RTL 텍스트 시작과 끝 반전", "ruleProfileIsX": "규칙 프로필이 이제 \"{0}\"", "saveLanguageFile": "언어 파일 저장", "saveXFileAs": "{0} 파일을 다음 이름으로 저장", @@ -1171,8 +1171,8 @@ "youtubeDlOutdatedDownloadNow": "\"yt-dlp\"가 오래되어 온라인 비디오에서 작동하지 않을 수 있습니다.\n\n지금 최신 버전을 다운로드하시겠습니까?", "insertUnicodeSymbol": "유니코드 기호 삽입", "trimmedXLines": "{0}개 줄 자름", - "pastedXLinesOverSelectedLines": "선택된 {0}줄을 붙여넣었습니다", - "pastedXOfYLinesOverSelectedLines": "선택된 {1}줄 중 {0}줄을 붙여넣었습니다 - 나머지는 선택에 맞지 않았습니다", + "pastedXLinesOverSelectedLines": "선택한 줄에 {0}줄을 덮어 붙여넣었습니다", + "pastedXOfYLinesOverSelectedLines": "{1}줄 중 {0}줄을 선택한 줄에 덮어 붙여넣었습니다 - 나머지는 선택 영역을 벗어납니다", "insertedXLinesFromClipboard": "클립보드에서 {0}줄 삽입", "openOriginalDifferentNumberOfSubtitlesXY": "원본 자막 파일의 자막 개수가 현재 자막 파일의 자막 개수와 일치하지 않습니다.\n\n• 원본 자막: {0}\n• 현재 자막: {1}", "originalTextReadOnly": "원본 텍스트 (읽기 전용)", @@ -1193,7 +1193,7 @@ "allowEditHintMatchingOnlyX": "원본을 저장하면 표시된 줄만 저장되며, 나머지 {0}개는 사라집니다.", "videoOpenedChangeLayoutQuestion": "비디오 파일이 열렸습니다.\n\n비디오 패널이 표시되도록 레이아웃을 변경하시겠습니까?", "sortedByStartTime": "\"시작\" 시간순으로 정렬", - "sortedByEndTime": "\"끝\" 시간 순으로 정렬", + "sortedByEndTime": "\"끝\" 시간순으로 정렬", "sortedByNumber": "번호순으로 정렬", "sortedByDuration": "길이 순 정렬", "sortedByGap": "간격 순 정렬", @@ -1207,10 +1207,10 @@ "sortedByStyle": "스타일 순 정렬", "videoBrightnessSetTo": "밝기: {0}", "videoContrastSetTo": "대비: {0}", - "colorHint": "선택한 선에 색상 지정", + "colorHint": "선택한 줄에 색상 지정", "removeFormattingHint": "선택한 줄의 서식 제거", "assaResolutionResamplerDone": "ASSA 해상도가 변경되었습니다.", - "snappedXTimesToFrames": "{0} 타임 코드를 {1:0.###} fps의 프레임에 고정함", + "snappedXTimesToFrames": "타임코드 {0}개를 {1:0.###} fps 프레임에 맞춤", "languageFileSavedToX": "언어 파일이 {0}에 저장", "fileExportedInFormatXToY": "파일을 {0} 형식으로 {1} 에 내보냄", "fileExportedInFormatXToFileY": "파일이 \"{0}\" 형식으로 \"{1}\" 파일로 내보내짐", @@ -1224,7 +1224,7 @@ "formatLimitWarningTitle": "형식 제한 초과", "formatLimitWarningXLinesExceedLimitsOfY": "{0}개의 자막이 \"{1}\" 형식의 제한을 초과했습니다:", "formatLimitWarningMaxXCharactersPerLine": "자막 줄당 최대 {0}자", - "formatLimitWarningMaxXLines": "자막 줄당 최대 {0}줄", + "formatLimitWarningMaxXLines": "자막 하나당 최대 {0}줄", "formatLimitWarningTextWillBeRewrapped": "저장하면 이 자막은 형식 제한에 맞게 줄이 다시 나뉘거나 잘립니다. 따라서 저장된 파일은 현재 표시 내용과 달라질 수 있습니다.", "formatLimitWarningLinesX": "자막 줄: {0}", "formatLimitWarningSaveAnyway": "그래도 저장", @@ -1249,7 +1249,7 @@ "title": "EBU 저장 옵션", "codePageNumber": "코드 페이지 번호", "diskFormatCode": "디스크 포맷 코드", - "displayStandardCode": "표준 코드 표시", + "displayStandardCode": "표시 표준 코드", "characterCodeTable": "문자표", "languageCode": "언어 코드", "originalProgramTitle": "원 프로그램 제목", @@ -1285,18 +1285,20 @@ "maxLengthErrorTeletextColored": "줄 {0}-{1}: 두 배 높이 색상 텍스트의 최대 문자 수는 36자여야 합니다(현재 {2}자)", "maxLengthErrorTeletextWhite": "줄 {0}-{1}: 두 배 높이 흰색 텍스트의 최대 문자 수는 37자여야 합니다(현재 {2}자)", "maxLengthErrorTeletextSingleHeight": "줄 {0}: 일반 높이 흰색 텍스트의 최대 문자 수는 38자여야 합니다(현재 {1}자)", - "discSequenceNumber": "디스크 시퀀스 번호" + "discSequenceNumber": "디스크 시퀀스 번호", + "programme": "프로그램", + "discAndDisplay": "디스크 및 표시" }, "import": { "importTimeCodes": "타임코드 가져오기...", "plainTextDotDotDot": "일반 텍스트...", "titleImportPlainText": "일반 텍스트 가져오기", - "csvXlsxCustomColumnsDotDotDot": "사용자 정의 컬럼 CSV/XLSX...", - "titleImportCsvXlsxCustomColumns": "사용자 정의 컬럼 CSV/XLSX 불러오기", + "csvXlsxCustomColumnsDotDotDot": "사용자 정의 열이 있는 CSV/XLSX/ODS...", + "titleImportCsvXlsxCustomColumns": "사용자 정의 열이 있는 CSV/XLSX/ODS 가져오기", "detectedSeparatorX": "감지된 구분자: {0}", - "csvXlsxFilterTitle": "CSV/XLSX", - "separatorTab": "Tab", - "separatorSpace": "스페이스", + "csvXlsxFilterTitle": "CSV/XLSX/ODS", + "separatorTab": "탭", + "separatorSpace": "공백", "imagesForOcrDotDotDot": "OCR용 이미지...", "timeCodesDotDotDot": "타임코드...", "subtitleWithManuallyChosenEncodingDotDotDot": "인코딩을 선택하여 자막 가져오기(_S)...", @@ -1329,16 +1331,16 @@ "multipleFiles": "여러 텍스트 파일에서 가져오기(파일 하나당 자막 하나)", "maxLineLength": "최대 줄 길이", "mergeShortLines": "짧은 줄 합치기", - "fixed": "수정됨", + "fixed": "고정", "numberOfSubtitlesX": "자막 수: {0}", "numberOfSubtitlesXForcedY": "자막 수: {0} (강제 표시: {1})", "gapMs": "간격(ms)", "useFixedDuration": "고정 시간 사용", "fixedDurationMs": "고정 시간(ms)", "alignViaSpeechToText": "음성 텍스트 변환을 통한 타임코드 정렬...", - "alignViaForcedAligner": "강제 정렬을 통한 타임 코드 정렬...", + "alignViaForcedAligner": "강제 정렬을 통한 타임코드 정렬...", "forcedAlignerProgress": "정렬 중... 윈도우 {0} / {1}, 줄 {2} / {3}", - "forcedAlignerSetupTitle": "강제 정렬을 통한 타임 코드 정렬", + "forcedAlignerSetupTitle": "강제 정렬을 통한 타임코드 정렬", "forcedAlignerSetupIntro": "강제 정렬은 음성을 텍스트로 변환하지 않고 기존 자막을 음성과 대조하여 타임코드를 맞춥니다. 긴 동영상도 여러 구간으로 나누어 처리할 수 있습니다.", "forcedAlignerEndsFromSpeech": "분리한 음성을 기준으로 종료 시간 설정 (느림)", "forcedAlignerEndsFromSpeechHint": "강제 정렬은 각 줄의 시작 위치만 찾으므로, 종료 시간은 보통 읽는 데 필요한 시간을 기준으로 정합니다. 이 옵션을 켜면 먼저 오디오에서 음악과 효과음을 제거하고, 각 줄의 음성이 끝나는 지점을 종료 시간으로 설정합니다. GPU를 사용해도 비디오 길이만큼 시간이 걸리며, GPU가 없으면 몇 배 더 오래 걸립니다.", @@ -1353,8 +1355,8 @@ "leftRightMargin": "왼쪽/오른쪽 여백", "topBottomMargin": "상단/하단 여백", "lineSpacingPercent": "줄 간격 %", - "paddingLeftRight": "왼쪽/오른쪽 패딩", - "paddingTopBottom": "상단/하단 패딩", + "paddingLeftRight": "왼쪽/오른쪽 안쪽 여백", + "paddingTopBottom": "상단/하단 안쪽 여백", "previewTitle": "미리 보기 - 현재 크기: {0}x{1}, 목표 크기: {2}x{3}, 확대/축소 비율: {4}%", "titleExportDvdSup": "DVD sup (MuxMan/Scenarist)", "titleExportVobSub": "VobSub(sub/idx)", @@ -1373,8 +1375,17 @@ "exportDvbTeletextSubtitleType": "자막 유형", "exportDvbTeletextSubtitleTypeNormal": "일반", "exportDvbTeletextSubtitleTypeHearingImpaired": "청각 장애인용", + "exportPacCodePage": "PAC 코드 페이지 선택", + "exportPacSecondaryCodePage": "보조 코드 페이지", "customTextFormatsDotDotDot": "사용자 정의 텍스트 형식(_C)...", "plainTextDotDotDot": "일반 텍스트(_P)...", + "dialogueListDotDotDot": "Excel 대사 목록 (.xlsx)...", + "titleExportDialogueList": "Excel 대사 목록 내보내기", + "dialogueListLineBreaksInCell": "셀 안의 줄 바꿈 유지 (해제하면 \" | \"로 줄 연결)", + "dialogueListHeaderRow": "머리글 행", + "dialogueListFramesFormat": "hh:mm:ss:ff (프레임)", + "dialogueListNoColumns": "열을 하나 이상 선택하세요.", + "dialogueListEnd": "끝", "customTextFormats": "사용자 정의 텍스트 형식", "titleExportCustomFormat": "사용자 정의 텍스트 형식으로 내보내기", "editCustomFormat": "사용자 정의 텍스트 서식 편집", @@ -1392,6 +1403,8 @@ "addLineBetweenSubtitles": "자막 사이에 줄 추가", "titleExportDCinemaInteropPng": "D-Cinema interop/png", "titleExportDCinemaSmpte2014Png": "D-Cinema SMPTE 2014/png", + "titleExportDCinemaSmpte2014Mxf": "D-Cinema SMPTE 2014 (MXF)", + "titleExportDCinemaSmpte2014PngMxf": "D-Cinema SMPTE 2014/png (MXF)", "imageBasedSubtitleSaved": "이미지 기반 자막 저장됨", "fullFrameImage": "전체 프레임 이미지", "fullFrameImageHint": "각 이미지를 비디오 프레임 크기로 만들고, 자막을 해당 위치에 배치합니다", @@ -1442,7 +1455,7 @@ "mostUsedWords": "가장 많이 사용된 단어", "nothingFound": "찾을 수 없음", "numberOfLinesX": "자막 줄 수: {0:#,##0}", - "lengthInFormatXinCharactersY": "{0}자: {1:#,###,##0}", + "lengthInFormatXinCharactersY": "{0} 형식의 문자 수: {1:#,###,##0}", "numberOfCharactersInTextOnly": "텍스트의 문자 수만: {0:#,###,##0}", "totalDuration": "모든 자막의 총 길이: {0:#,##0}", "totalCharsPerSecond": "초당 총 문자 수: {0:0.0}", @@ -1513,7 +1526,7 @@ "deleteAllSubtitleBackups": "자막 백업 파일을 모두 삭제하시겠습니까?", "deleteAll": "모두 삭제", "subtitles": "자막", - "settingsBackupInfo": "Subtitle Edit를 시작할 때 Settings.json을 백업합니다. 백업은 {0}일에 한 번까지만 수행하며, 최신 백업 {1}개를 보관합니다.", + "settingsBackupInfo": "설정이 변경된 경우 Subtitle Edit를 시작할 때 Settings.json을 백업합니다. 백업은 {0}일에 한 번까지만 수행하며, 최신 백업 {1}개를 보관합니다.", "settingsBackupInfoEveryStart": "설정이 변경된 경우 Subtitle Edit를 시작할 때마다 Settings.json을 백업합니다. 최근 백업 {0}개를 보관합니다.", "settingsBackupOff": "설정 자동 백업이 꺼져 있습니다 (옵션 → 설정 → 파일).", "lastSettingsBackupX": "마지막 백업: {0}", @@ -1537,7 +1550,7 @@ "reelNumber": "릴 번호", "issueDate": "발행일", "now": "지금", - "editRate": "비율 편집", + "editRate": "편집 레이트 (프레임 속도)", "timeCodeRate": "타임코드 속도", "startTime": "시작 시간", "fontId": "글꼴 ID", @@ -1549,16 +1562,22 @@ "fadeUpTime": "페이드업 시간", "fadeDownTime": "페이드다운 시간", "zPosition": "Z-포지션", - "zPositionHelp": "양수는 텍스트를 멀리 이동시키고, 음수는 텍스트를 더 가깝게 이동시킵니다. z-포지션이 0이면 2D입니다." + "zPositionHelp": "양수는 텍스트를 멀리 이동시키고, 음수는 텍스트를 더 가깝게 이동시킵니다. z-포지션이 0이면 2D입니다.", + "fontFile": "글꼴 파일", + "chooseFontFile": "글꼴 파일 선택", + "fontFileMissing": "MXF 트랙 파일에 포함할 TrueType/OpenType 글꼴 파일을 선택하세요.", + "reelDuration": "릴 길이", + "reelDurationHint": "HH:MM:SS:FF - DCP 릴의 모든 구성 요소에 동일하게 적용할 영상 길이입니다. 비워 두면 마지막 자막까지로 설정합니다.", + "reelDurationInvalid": "릴 길이는 비워 두거나 HH:MM:SS:FF 형식으로 입력하세요." }, "webVtt": { "setStylesForSelectedLinesTitle": "선택된 줄에 대한 WebVTT 스타일 설정", - "voice": "음성", - "showVoiceColumn": "\"음성\" 열 표시", - "voices": "음성", - "newVoiceDotDotDot": "새 음성...", - "removeVoices": "음성 제거", - "voiceName": "음성 이름", + "voice": "화자", + "showVoiceColumn": "\"화자\" 열 표시", + "voices": "화자", + "newVoiceDotDotDot": "새 화자...", + "removeVoices": "화자 제거", + "voiceName": "화자 이름", "browserPreview": "브라우저 미리보기", "importStylesTitle": "WebVTT 스타일 가져오기", "exportStylesTitle": "WebVTT 스타일 내보내기", @@ -1582,7 +1601,7 @@ "compareOnlyInOneFile": "하나의 파일에서만", "compareTextOrTimeDifference": "텍스트/시간 차이", "compareNumberDifference": "번호 차이", - "ignoreWhitespace": "띄어쓰기 무시", + "ignoreWhitespace": "공백 문자 무시", "ignoreWhitespaceHint": "띄어쓰기, 탭 또는 줄바꿈만 다른 줄은 서로 다른 것으로 처리하지 않습니다", "ignoreFormatting": "서식 무시", "ignoreFormattingHint": "서식 태그( 또는 {\\an8} 등)만 다른 줄은 서로 다른 것으로 처리하지 않습니다", @@ -1634,7 +1653,7 @@ "compareTimingDeltaHint": "참조 자막의 시작 시간과 종료 시간이 현재 자막보다 얼마나 늦거나(+) 빠른지(−) 표시합니다", "loadXFromFile": "파일에서 \"{0}\"을(를) 불러오기", "compareLoadSavedFile": "저장된 파일 불러오기", - "saveCompareHtmlTitle": "HTML 파일 저장 및 비교", + "saveCompareHtmlTitle": "비교 결과 HTML 파일 저장", "pickMatroskaTrackX": "Matroska 트랙 선택 - {0}", "pickTransportStreamTrackX": "전송 스트림 트랙 선택 - {0}", "chapters": "챕터", @@ -1694,10 +1713,12 @@ "hearingImpairedMusicSymbols": "음악 기호만 있는 줄", "hearingImpairedInterjections": "감탄사", "hearingImpairedSettingsHint": "자세한 설정은 '도구' > '청각 장애인용 텍스트 제거'에서 확인", + "selection": "선택 영역", + "hearingImpairedOther": "기타", "selectionNew": "새 선택 항목", "selectionAdd": "선택 항목에 추가", "selectionSubtract": "선택 항목에서 제외", - "selectionIntersect": "선택 항목과 교차" + "selectionIntersect": "기존 선택과 겹치는 항목만 선택" }, "multipleReplace": { "editRule": "규칙 편집", @@ -1713,7 +1734,7 @@ "xLinesAffected": "{0:#,##0}줄이 영향을 받음", "deleteCategoryConfirm": "카테고리 '{0}'을(를) 삭제하시겠습니까?", "deleteRuleConfirm": "규칙 '{0}'을(를) 삭제하시겠습니까?", - "findWhat": "찾아보기", + "findWhat": "찾을 내용", "descriptionOptional": "설명(선택 사항)", "invalidRegularExpressionX": "잘못된 정규 표현식: {0}", "regularExpressionTooSlowX": "정규 표현식 처리 시간이 {0}초를 초과했습니다 - 규칙이 건너뛰어졌습니다. 더 간단한 패턴을 시도해 보세요", @@ -1790,7 +1811,7 @@ "categoryPunctuation": "구두점", "categoryCasing": "대소문자 표기", "categoryOther": "기타", - "xNeedACloserLook": "{0} 개의 줄에 더 자세한 검토 필요", + "xNeedACloserLook": "제안 {0}개에 추가 검토 필요", "lineX": "{0}번째 줄", "linesXToY": "{0}-{1}번째 줄", "largeChangeWarning": "큰 변경 사항 - 다시 쓴 것일 수 있으므로 자세히 검토하세요.", @@ -1798,6 +1819,7 @@ "engineError": "AI 엔진에 접근할 수 없습니다: {0}", "playCurrentLineHint": "선택된 줄을 비디오 플레이어에서 재생하고 끝 부분에서 일시정지합니다 (줄을 두 번 클릭해도 됩니다)", "editAfterHint": "선택한 행의 텍스트를 클릭하여 적용 전 수정 사항을 편집하세요 (Enter로 저장, Esc로 취소)", + "prompt": "프롬프트", "template": "템플릿", "templatePlaceholder": "프롬프트를 바꿀 템플릿을 선택하세요", "templateStandard": "기본 교정", @@ -1853,7 +1875,7 @@ "fixCommas": "쉼표 수정", "fixMissingSpaces": "잘못된 띄어쓰기 수정", "breakLongLines": "긴 줄 나누기", - "removeLineBreaks": "짧은 텍스트의 줄바꿈 제거", + "removeLineBreaks": "한 문장으로 된 짧은 텍스트의 줄 바꿈 제거", "removeLineBreaksAll": "짧은 텍스트의 줄바꿈 제거 (대사를 제외한 전부)", "removeLineBreaksPixelWidth": "한 줄로 표시 가능한 텍스트의 줄바꿈 제거 (픽셀 너비 기준)", "fixUppercaseIInsideLowercaseWords": "소문자 단어 내의 대문자 ‘i’ 수정(OCR 오류)", @@ -1863,7 +1885,7 @@ "startWithUppercaseLetterAfterParagraph": "단락 뒤의 글자를 대문자로 시작", "startWithUppercaseLetterAfterPeriodInsideParagraph": "단락 내 마침표 뒤의 첫 글자를 대문자로 시작", "startWithUppercaseLetterAfterColon": "콜론/세미콜론 뒤에 대문자로 시작", - "fixLowercaseIToUppercaseI": "소문자 'i'를 'I'로 수정(영어)", + "fixLowercaseIToUppercaseI": "영어 대명사 i를 I로 수정 (축약형 포함)", "fixCommonOcrErrors": "일반적인 OCR 오류 수정(OCR 바꾸기 목록 사용)", "removeSpaceBetweenNumber": "숫자 사이의 띄어쓰기 제거", "breakDialogsOnOneLine": "한 줄에 대사 나누기", @@ -1916,18 +1938,18 @@ "fixLowercaseIToUppercaseIExample": "What do i care. → What do I care.", "startTimeLaterThanEndTime": "텍스트 번호 {0}: 시작 시간이 끝 시간보다 늦음: {4}{1} → {2} {3}", "unableToFixStartTimeLaterThanEndTime": "텍스트 번호 {0}을(를) 수정할 수 없음: 시작 시간이 끝 시간보다 늦음: {1}", - "xFixedToYZ": "{0}이 다음으로 고정됨: {1}{2}", + "xFixedToYZ": "{0}을(를) 다음으로 수정: {1}{2}", "unableToFixTextXY": "텍스트 번호 {0}을(를) 수정할 수 없음: {1}", - "unneededSpace": "불필요한 공간", + "unneededSpace": "불필요한 공백", "unneededPeriod": "불필요한 마침표", "fixMissingSpace": "잘못된 띄어쓰기 수정", "fixUppercaseIInsideLowercaseWord": "소문자 단어 속의 대문자 ‘i’ 수정", "fixMissingPeriodAtEndOfLine": "줄 끝에 누락된 마침표 추가", "refreshFixes": "사용 가능한 수정 사항 새로 고침", - "autoBreak": "자동 &br", + "autoBreak": "자동 줄 바꿈(&B)", "unbreak": "줄바꿈 해제(&U)", "fixDoubleDash": "'--' → '...'로 수정", - "fixDoubleGreaterThan": "제거 '>>'", + "fixDoubleGreaterThan": ">> 제거", "fixEllipsesStart": "앞에 있는 '...'을 제거", "fixMissingOpenBracket": "문장 내 누락된 [ 또는 ( 보정", "fixMusicNotation": "음악 기호(예: âTª)를 원하는 기호로 바꾸기", @@ -1940,12 +1962,12 @@ "fixSpaceBetweenNumbersExample": "1 100 → 1100", "fixDialogsOneLineExample": "Hi John! - Hi Ida! → Hi John!
- Hi Ida!", "removeDialogFirstInNonDialogsExample": "- How are you? → How are you?", - "fixContinuationStyleX": "연속 스타일 수정: {0}", + "fixContinuationStyleX": "이어짐 표기 방식 수정: {0}", "fixUnnecessaryLeadingDots": "불필요한 선행 점 제거", "fixCommonErrorsProfiles": "일반적인 오류 수정 프로필", "fixCommonOcrErrorsStep1": "일반적인 오류 수정, 1단계(수정 방법 선택)", "searchRulesDotDotDot": "규칙 찾기...", - "goToApplyFixes": "수정 사항을 적용하려면 이동", + "goToApplyFixes": "수정 적용 단계로 이동", "backToFixList": "수정 목록으로 돌아가기", "fixCommonOcrErrorsStep2": "일반적인 오류 수정, 2단계(수정 사항 적용)", "fixCommonOcrErrorsStep2FixesFoundX": "일반적인 오류 수정, 2단계 - 수정 사항: {0}", @@ -1962,8 +1984,8 @@ "recalculate": "다시 계산", "addSeconds": "초 추가", "note": "참고: 표시되는 시간은 다음 텍스트의 시작 시간과 겹치지 않음", - "fixed": "수정됨", - "extendOnly": "확장 전용", + "fixed": "고정 길이", + "extendOnly": "늘리기만", "recalculateRequiresOcrNote": "재계산이 불가합니다 - 하나 이상의 자막에 OCR 텍스트가 없습니다. 먼저 OCR을 실행하세요." }, "applyDurationLimits": { @@ -2000,10 +2022,10 @@ "changedCount": "변경", "shotChangesCount": "장면 전환 수", "changeXOfY": "변경 {0}/{1}", - "noChanges": "변경 사항 없음 - 모든 큐가 이미 대상에 위치합니다", + "noChanges": "변경 사항 없음 - 모든 자막의 시간이 이미 기준에 맞습니다", "snappedToShotChange": "장면 전환에 맞춤", "snappedToFrame": "프레임에 맞춤", - "minGapEnforced": "최소 간격 설정값", + "minGapEnforced": "최소 간격 적용", "minDurationEnforced": "최소 길이 적용", "maxDurationEnforced": "최대 길이 적용", "noReasonNote": "프로필 규칙에 따라 조정됨", @@ -2087,7 +2109,7 @@ }, "beautifyTimeCodesProfile": { "title": "타임코드 다듬기 프로필 편집", - "loadPreset": "프리셋 불러오기", + "loadPreset": "사전 설정 불러오기", "presetDefault": "기본", "presetNetflix": "Netflix", "presetSdi": "SDI", @@ -2108,8 +2130,8 @@ "outCueClosest": "아웃 큐가 가장 가까움", "treatAsConnected": "간격이 다음보다 짧으면 연결된 것으로 간주:", "chaining": "체이닝", - "inCueOnShot": "장면 전환 위의 인큐", - "outCueOnShot": "장면 전환 위의 아웃큐", + "inCueOnShot": "장면 전환 지점의 인큐", + "outCueOnShot": "장면 전환 지점의 아웃큐", "maxGap": "최대 간격:", "shotChangeBehavior": "사이에 장면 전환이 있을 경우:", "dontChain": "체이닝 안함", @@ -2131,7 +2153,7 @@ "calculateBridgeGapsSmallerThanDotDotDot": "프레임 레이트로 간격 메우기 기준 계산...", "useXMsAsBridgeGapsSmallerThan": "\"{0}\"밀리초보다 짧은 간격을 메우시겠습니까?", "numberOfSmallGapsBridgedX": "연결된 간격 수: {0}", - "percentFoPrevious": "이전 대비 간격(%)", + "percentFoPrevious": "이전 자막에 배분할 비율 (%)", "gapChange": "간격 바꾸기" }, "renumber": { @@ -2141,7 +2163,7 @@ "sortBy": { "title": "자막 정렬", "sortOrder": "정렬 순서", - "sortByEndTime": "숨김 시간" + "sortByEndTime": "끝 시간" }, "batchConvert": { "title": "일괄 변환", @@ -2151,8 +2173,8 @@ "outputFolderX": "결과물 폴더: {0}", "encodingXOverwriteY": "인코딩: {0}, 기존 파일 덮어쓰기: {1}", "targetFormatSettings": "대상 형식 설정", - "fileNameContainsDotDotDot": "파일 이름에 다음 내용이 포함되어 있습니다...", - "trackLanguageContainsDotDotDot": "트랙 언어에는 다음 내용이 포함됩니다...", + "fileNameContainsDotDotDot": "파일 이름에 포함된 문자열...", + "trackLanguageContainsDotDotDot": "트랙 언어에 포함된 문자열...", "batchConvertSettings": "일괄 변환 설정", "assaSource": "ASSA 소스", "addFormatting": "서식 적용", @@ -2168,13 +2190,13 @@ "editProperties": "속성 편집", "editAttachments": "첨부 파일 편집", "errorsExportedX": "내보낸 오류: {0}", - "slowFontSizeChange": "글자 크기 변경 속도가 느림", + "slowFontSizeChange": "글자 크기를 서서히 변경", "increaseFontKerning": "글자 간격 늘리기", "scrollUp": "위로 스크롤", "scrollDown": "아래로 스크롤", "rotateIn": "회전하여 넣기", "tiltBounce": "틸트 바운스", - "fontSizeBounceIn": "글자 크기 조정", + "fontSizeBounceIn": "글자 크기 바운스 인", "assaChangeResolutionOnlyAppliesToAssa": "Advanced Sub Station Alpha(ASSA) 자막에만 적용됨", "assaChangeStyleTitle": "스타일 변경", "assaEmbedFontsTitle": "글꼴 포함", @@ -2182,14 +2204,14 @@ "adjustImageColorsTitle": "이미지 밝기/알파/색상 조정", "adjustImageColorsInfo": "이미지 기반 자막을 이미지 기반 형식으로 변환할 때만 적용됩니다 (예: Blu-ray sup에서 Blu-ray sup으로)", "assaChangeStyleFromStyle": "다음 스타일에서 변경", - "assaChangeStyleToStyle": "을(를)", + "assaChangeStyleToStyle": "변경 후 스타일", "assaChangeStyleImportStyle": "스타일 불러오기...", "assaChangeStyleTrimUnusedStyles": "사용하지 않은 스타일 제거", "assaChangeStylePropertiesTitle": "스타일 속성 변경", "assaChangeStylePropertiesInfo": "파일의 모든 스타일에서 선택한 필드만 변경하고, 스타일 자체는 그대로 둡니다.", "assaChangeStylePropertiesSetSpacing": "자간 설정값", "assaChangeStylePropertiesSetAlignment": "정렬 설정값", - "convertColorsToDialogTitle": "색상을 다이얼로그로 변환", + "convertColorsToDialogTitle": "색상을 대사로 변환", "convertColorsToDialogRemoveColorTags": "색상 태그 제거", "convertColorsToDialogAddNewLines": "새 줄 추가", "convertColorsToDialogReBreakLines": "줄 바꿈 재설정", @@ -2284,29 +2306,30 @@ }, "splitSubtitle": { "title": "자막 분할", - "numberOfEqualParts": "동일한 부분의 수", + "numberOfEqualParts": "균등 분할 수", "saveSplitParts": "분할된 부분 저장(_S)", - "xPartsSavedInFormatYToFolder": "{0}개 부분이 {1} 형식으로 폴더에 저장됨:" + "xPartsSavedInFormatYToFolder": "{0}개 부분이 {1} 형식으로 폴더에 저장됨:", + "splitBy": "분할 기준" }, "splitBreakLongLines": { "title": "긴 줄 나누기/재조정", - "splitLongLines": "긴 줄 (여러 줄로)나누기", + "splitLongLines": "긴 줄 나누기 (여러 줄로)", "rebalanceLongLines": "긴 줄 재조정", "rebalanceOnlyLinesTooLong": "너무 긴 줄이 있는 자막만", "rebalanceOnlyLinesTooLongHint": "모든 줄이 이미 한 줄 최대 길이 내에 있는 경우 기존 줄 바꿈은 그대로 두십시오. 의도적으로 균형이 맞지 않는 자막은 편집 의도에 맞을 수 있습니다", "splitLongLine": "긴 줄 나누기", "rebalanceLongLine": "긴 줄 재조정", "splitIntoXLines": "{0}줄로 나누기: ‘{1}’ → '{2}...'", - "linesSplitX": "줄 바꿈: {0}", - "linesSplitXLinesRebalancedY": "줄 바꿈: {0}, 재조정된 줄: {1}", + "linesSplitX": "나눈 줄: {0}", + "linesSplitXLinesRebalancedY": "나눈 줄: {0}, 재조정한 줄: {1}", "unbreakLinesShorterThanHint": "이 길이보다 짧은 텍스트는 단일 줄에 유지되거나 병합됩니다. 단일 줄 최대 길이로 설정하여 한 줄에 맞는 모든 텍스트를 분할하지 않고 유지하세요." }, "mergeShortLines": { "title": "짧은 줄 합치기", - "highlightParts": "하이라이트 부분(가라오케)", + "highlightParts": "부분 강조 표시 (가라오케)", "mergedLineInfo": "{0}을(를) {1}에 합침 - {2}", "linesMergedX": "합쳐진 줄: {0}", - "maxCharacters": "문장당 최대 글자 수", + "maxCharacters": "자막 하나당 최대 문자 수", "maxMillisecondsBetweenLines": "줄 간 최대 밀리초", "onlyContinuationLines": "연속된 줄만" }, @@ -2315,7 +2338,7 @@ "candidatesFoundX": "감지된 이어지는 줄: {0}", "noCandidatesFound": "이어지는 줄을 찾을 수 없습니다", "maxMillisecondsBetweenLines": "줄 사이의 최대 간격 (ms)", - "maxCharacters": "줄당 최대 문자 수", + "maxCharacters": "자막 하나당 최대 문자 수", "columnMerge": "병합", "columnFirst": "첫 줄", "columnSecond": "두번째 줄", @@ -2324,7 +2347,7 @@ "mergeLinesWithSameText": { "maxMsBetweenLines": "줄 간 최대 밀리초", "maxFramesBetweenLines": "줄 사이 최대 간격 (프레임)", - "includeIncrementingLines": "숫자가 점점 늘어나는 텍스트가 포함된 줄을 포함", + "includeIncrementingLines": "텍스트가 점차 늘어나는 줄 포함", "includeRollUpCaptions": "롤업 (스크롤) 자막 포함" }, "mergeLinesWithSameTimeCodes": { @@ -2346,34 +2369,34 @@ "maxCharsSec": "초당 최대 문자 수", "maxDuration": "최대 길이", "maxLineLength": "최대 줄 길이", - "minDuration": "최소 기간", + "minDuration": "최소 길이", "maxNumberOfLines": "최대 줄 수", "oneToTenSpellOut": "1부터 10까지 글자로 쓰기", "shotChanges": "장면 전환", - "startNumberSpellOut": "시작 번호 글자로 표기", + "startNumberSpellOut": "문장 첫머리의 숫자를 글자로 표기", "textforHiUseBrackets": "HI용 텍스트, 괄호를 사용", "twoFrameGrap": "두 프레임 간격", "whiteSpace": "띄어쓰기", "childrensProgram": "어린이 프로그램", "sdh": "SDH (청각 장애인용 자막)", - "glyphCheckReport": "{1}번 줄에서 잘못된 문자 {0}이(가) 발견", - "whiteSpaceCheckForXReport": "{1}번 줄에서 띄어쓰기 문제({0})이(가) 발견", + "glyphCheckReport": "{1}번째 열에서 잘못된 문자 {0} 발견", + "whiteSpaceCheckForXReport": "{1}번째 열에서 공백 문제({0}) 발견", "whiteSpaceBeforePunctuation": "구두점 앞 누락", "whiteSpaceLineEnding": "줄 끝", "whiteSpaceCheckConsecutive": "2+ 연속", "twoLinesMaximum": "최대 두 줄", "textCanFitOnOneLine": "텍스트가 한 줄에 들어갈 수 있음", - "singleVerticalLineLengthMax11": "세로선 하나 길이 > 11", - "singleHorizontalLineLengthMax13": "가로선 하나 길이 > 13", + "singleVerticalLineLengthMax11": "세로쓰기 한 줄 길이 > 11", + "singleHorizontalLineLengthMax13": "가로쓰기 한 줄 길이 > 13", "singleLineLengthExceedsX": "한 줄 길이 > {0}", - "numbersOneToTenSpellOut": "1부터 10까지의 숫자는 하나, 둘, 셋 등과 같이 하나씩 써야 함", + "numbersOneToTenSpellOut": "1부터 10까지는 숫자 대신 하나, 둘, 셋처럼 글자로 표기", "useSquareBracketsForHiAndSoundEffects": "화자 ID 또는 오디오 효과를 대괄호 [ ]로 묶음", "shotChangeInCueWithinXFramesAfterSnap": "인큐는 장면 전환 후 {0} 프레임 이내에 위치하며, 인큐를 장면 전환 지점에 맞춤", "shotChangeInCue1ToXFramesBeforeSnap": "인큐는 장면 전환 1-{0} 프레임 전이며, 인큐를 장면 전환 시점에 맞춤", "shotChangeInCueXToYFramesBeforePull": "인큐는 장면 전환 전 {0}-{1} 프레임 지점에 위치하며, 인큐를 장면 전환 전 0.5초({2} 프레임) 지점으로 조정", - "shotChangeOutCueWithinXFramesAfterChange": "아웃큐는 샷 전환 후 {0} 프레임 이내에 있음", - "shotChangeOutCueWithinXFramesOfChange": "아웃큐는 샷 전환 후 {0} 프레임 이내에 있음", - "shotChangeOutCueOnShotChange": "아웃큐는 샷 전환 시점에 있으며, 2프레임 간격을 지켜야 함", + "shotChangeOutCueWithinXFramesAfterChange": "아웃큐가 장면 전환 후 {0}프레임 이내에 있음", + "shotChangeOutCueWithinXFramesOfChange": "아웃큐가 장면 전환 전 {0}프레임 이내에 있음", + "shotChangeOutCueOnShotChange": "아웃큐가 장면 전환 지점에 있으므로 2프레임 간격을 유지해야 함", "dualSpeakersHyphenWithoutSpace": "두 화자: 띄어쓰기 없이 하이픈(-)을 사용", "dualSpeakersHyphenWithSpace": "두 화자: 하이픈과 띄어쓰기를 함께 사용", "dualSpeakersHyphenWithSpaceSecondOnly": "두 화자: 두 번째 화자만 표시하려면 하이픈(-)과 띄어쓰기를 사용", @@ -2402,7 +2425,7 @@ "setText": "텍스트 설정", "screenWidth": "화면 너비", "screenHeight": "화면 높이", - "alphaThresholdInfo": "투명도가 임계값 미만인 픽셀은 완전히 투명해짐", + "alphaThresholdInfo": "알파값이 임계값보다 낮은 픽셀을 완전히 투명하게 처리", "resetToDefaults": "기본값으로 초기화", "alphaAdjustmentInfo": "알파 조정: 알파 채널에 값을 더하거나 뺍니다.\n미리 보기가 자동으로 업데이트되며, 투명도를 시각화하기 위해 체크무늬 배경으로 표시됩니다.", "adjustBrightness": "밝기 조절", @@ -2420,7 +2443,7 @@ "topAlignLines": "위쪽 정렬", "bottomAlignLines": "아래쪽 정렬", "appendSubtitleDotDotDot": "자막 추가...", - "shiftTimeCodes": "시간 코드 이동", + "shiftTimeCodes": "타임코드 이동량", "sortByStartTime": "시작 시간순 정렬", "videoPreview": "비디오", "positionMap": "위치 맵", @@ -2455,7 +2478,7 @@ }, "removeTextForHearingImpaired": { "interjections": "감탄사", - "skipIfStartWith": "다음과 같으면 건너뛰기", + "skipIfStartWith": "다음으로 시작하면 건너뛰기", "removeTextBetween": "사이의 텍스트 삭제", "brackets": "대괄호", "curlyBrackets": "중괄호", @@ -2483,9 +2506,9 @@ "colorPickerTitle": "색상 선택", "colorPickerBrightness": "밝기", "filterLayersTitle": "표시할 레이어 필터링", - "filterLayersHideFromWaveform": "파형/스펙트로그램 숨기기", - "filterLayersHideFromSubtitleGrid": "자막 목록 숨기기", - "filterLayersHideFromVideoPreview": "비디오 미리 보기 숨기기", + "filterLayersHideFromWaveform": "파형/스펙트로그램에서 숨기기", + "filterLayersHideFromSubtitleGrid": "자막 목록에서 숨기기", + "filterLayersHideFromVideoPreview": "비디오 미리 보기에서 숨기기", "pickSubtitleFormat": "자막 형식 선택", "pickSubtitleFormatImageBasedNoPreview": "이미지 기반의 자막 형식입니다.\n변환 과정에서 비트맵이 생성되며, 미리 보기 기능은 지원되지 않습니다.", "recentColors": "최근 사용한 색상", @@ -2507,8 +2530,8 @@ "wordNotFound": "단어를 찾을 수 없음", "loadSourceImage": "원본 이미지 보기...", "lineXofY": "맞춤법 검사기 - {1}줄 중 {0}번 줄", - "changeWordFromXToY": "'{0}‘을(를) ’{1}'(으)로 변경", - "changeAllWordsFromXToY": "모든 ‘{0}'을(를) ’{1}'(으)로 변경", + "changeWordFromXToY": "'{0}'을(를) '{1}'(으)로 변경", + "changeAllWordsFromXToY": "모든 '{0}'을(를) '{1}'(으)로 변경", "ignoreWordXOnce": "'{0}'이라는 단어를 한 번 무시", "ignoreWordXAlways": "'{0}'이라는 단어를 항상 무시", "wordXAddedToNamesList": "이름 목록에 '{0}'이(가) 추가됨", @@ -2551,29 +2574,32 @@ "video": { "burnIn": { "title": "자막이 삽입된 비디오 생성", - "targetFileSize": "대상 파일 크기(2단계 인코딩 필요)", + "targetFileSize": "목표 파일 크기 (인코더에 따라 2패스 사용)", "fileSizeMb": "파일 크기 (MB)", "matchSourceVideoSize": "원본 비디오 크기와 맞추기", "bitRate": "비트 전송률", "totalBitRateX": "총 비트 전송률: {0}", - "sampleRate": "샘플링 속도", + "sampleRate": "샘플레이트", "audio": "오디오", "preset": "사전 설정", "tune": "튜닝", "pixelFormat": "픽셀 형식", "crf": "CRF", "useSource": "소스 사용", - "fontSizeFactor": "글자 크기 요소", + "fontSizeFactor": "글자 크기 조정값", "boxType": "상자 유형", "fromTime": "시작 시간", "toTime": "종료 시간", "audioEncoding": "오디오 인코딩", "outputProperties": "결과물 속성...", "videoFileSize": "비디오 파일 크기", - "oneBox": "한 칸", + "oneBox": "하나의 상자", "logoInfo": "PNG 이미지를 선택하고 드래그하여 비디오 상의 원하는 위치에 배치하세요.", "noSubtitlesGenerateAnyway": "비디오에 넣을 자막이 없습니다.\n\n자막 없이 비디오를 생성하시겠습니까?", - "noSubtitlesInXItemsGenerateAnyway": "일괄 처리 항목 {0}개에 비디오에 넣을 자막이 없습니다.\n\n해당 비디오를 자막 없이 생성하시겠습니까?" + "noSubtitlesInXItemsGenerateAnyway": "일괄 처리 항목 {0}개에 비디오에 넣을 자막이 없습니다.\n\n해당 비디오를 자막 없이 생성하시겠습니까?", + "subtitleStyle": "자막 스타일", + "videoOutput": "비디오 출력", + "targetSize": "목표 파일 크기" }, "videoTransparent": { "title": "자막이 포함된 투명 비디오 생성" @@ -2598,9 +2624,9 @@ "detectSpeakersNeedsNewerCrispAsr": "\"화자 감지\"를 사용하려면 Crisp ASR v{0} 이상이 필요하지만, 현재 v{1}이 설치되어 있습니다.\n\n엔진 옆의 기어 버튼에서 Crisp ASR을 업데이트하거나 \"화자 감지\"를 끄세요.", "xNeedsNewerCrispAsr": "{0}에는 Crisp ASR v{1} 이상이 필요하지만 현재 v{2}이(가) 설치되어 있습니다.\n\n지금 업데이트를 다운로드하여 설치하시겠습니까?", "vad": "VAD", - "vadHint": "음성 활동 감지: 말하는 부분만 텍스트로 변환하고, 화자가 쉬는 지점에서 구간을 나눕니다.\n자동: Silero를 사용하며, 필요한 엔진(Cohere, Mega, Index-Echo)에만 적용합니다.\nSilero, FireRedVAD, WebRTC: 모든 Crisp ASR 엔진에 항상 적용합니다. FireRedVAD가 가장 정확합니다.\nIndex-Echo는 항상 Silero를 사용합니다. 고급 설정의 --vad, --vad-model, --chunk-seconds 옵션이 우선 적용됩니다.", + "vadHint": "음성 활동 감지: 말하는 부분만 텍스트로 변환하고, 화자가 쉬는 지점에서 구간을 나눕니다.\n자동: Silero를 사용하며, 필요한 엔진(Cohere, Mega, Index-Echo)에만 적용합니다.\nSilero, FireRedVAD, Whisper-VAD, WebRTC: 모든 Crisp ASR 엔진에 항상 적용합니다. FireRedVAD가 가장 정확하며, Whisper-VAD는 구간 분할이 가장 적고 깔끔하지만 가장 느립니다.\nIndex-Echo는 항상 Silero를 사용합니다. 고급 설정의 --vad, --vad-model, --chunk-seconds 옵션이 우선 적용됩니다.", "inputLanguage": "입력 언어", - "advancedWhisperSettings": "고급 Whisper 설정", + "advancedWhisperSettings": "고급 음성 텍스트 변환 매개변수", "downloadingSpeechToTextEngine": "STT 엔진 다운로드 중", "unpackingSpeechToTextEngine": "STT 엔진 압축 푸는 중", "enableVad": "VAD 활성화", @@ -2612,7 +2638,7 @@ "selectModel": "모델 선택", "addCustomModelDotDotDot": "사용자 정의 모델 추가...", "customModelHelp": "원하는 모델을 직접 사용할 수 있습니다: 모델 파일(whisper.cpp ggml '.bin') 또는 모델 폴더('model.bin'이 포함된 faster-whisper 폴더)를 선택하세요. 이 파일은 models 폴더로 복사되어 위 목록에 추가됩니다.", - "viewToolsLogFile": "Whisper 로그 파일 보기", + "viewToolsLogFile": "도구 로그 파일 보기", "updateXTitle": "{0}을(를) 업데이트하시겠습니까?", "updateXMessage": "{0}의 최신 버전을 사용할 수 있습니다.{1}{1}지금 업데이트를 다운로드하여 설치하시겠습니까?", "downloadingSpeechToTextModel": "STT 모델 다운로드 중", @@ -2624,7 +2650,7 @@ "fixCasing": "대/소문자 수정", "addPeriods": "마침표 추가", "changeUnderlineToColor": "밑줄 대신 색상으로 변경", - "removeNonSpeechLines": "대화가 아닌 자막 제거", + "removeNonSpeechLines": "음성 외 자막 제거", "removeNonSpeechLinesHint": "\"[음악]\"나 \"(파도 치는 소리)\"처럼 소리를 설명하는 자막을 제거합니다.", "removeRepeatedLines": "반복된 자막 제거", "removeRepeatedLinesHint": "이전 자막을 그대로 반복하는 자막을 제거합니다 (엔진 루프)", @@ -2641,7 +2667,7 @@ "qualityReportTooLongHint": "최대 표시 시간보다 길거나, 긴 시간 동안 단어가 거의 없음 (할루시네이션 가능성 높음)", "qualityReportOverlap": "겹침", "qualityReportOverlapHint": "다음 자막이 시작된 후에 끝남", - "qualityReportNonSpeech": "대화가 아닌 자막", + "qualityReportNonSpeech": "음성 외 자막", "qualityReportNonSpeechHint": "\"[음악]\"처럼 소리나 음악 등을 설명하는 자막", "qualityReportRepeated": "반복됨", "qualityReportRepeatedHint": "이전 자막과 동일한 텍스트 (엔진 루프)", @@ -2669,7 +2695,7 @@ "addAudioToVideoFile": "비디오 파일에 오디오 추가", "engineAndVoice": "엔진 및 음성", "output": "출력", - "xLinesFromY": "{1}개 중 {0}줄", + "xLinesFromY": "{1}의 자막 {0}줄", "xLines": "{0}줄", "xVoices": "{0}개 음성", "reviewAudioSegmentsHint": "최종 믹스 전 각 줄을 확인하세요", @@ -2765,17 +2791,17 @@ "useSpeechToTextDotDotDot": "음성 텍스트 변환(STT) 사용...", "autoCastMenuItem": "비디오에서 음성을 찾아 복제...", "autoCastSpeakersTitle": "비디오에서 찾은 음성", - "autoCastSpeakersSubtitle": "각 화자를 이름으로 지정하세요 - 이름은 자막의 배우와 복제된 음성의 이름이 됩니다. 두 화자에게 같은 이름을 주면 하나의 음성으로 병합됩니다.", + "autoCastSpeakersSubtitle": "각 화자의 이름을 지정하세요 - 이름은 자막의 등장인물과 복제된 음성의 이름이 됩니다. 두 화자에게 같은 이름을 주면 하나의 음성으로 병합됩니다.", "autoCastFoundXSpeakersInYLines": "{0}명의 화자, {1}줄", "autoCastLines": "줄", "autoCastAudio": "오디오", - "autoCastSays": "말함", + "autoCastSays": "대사", "autoCastNeedsVideo": "음성을 찾기 위해서는 비디오가 열려 있어야 합니다.", "autoCastNoSpeakersFound": "화자를 찾을 수 없습니다. 화자를 구분할 수 있는 음성-텍스트 엔진을 사용하여 생성되어야 합니다. (예: Crisp ASR MOSS Diarize)", "autoCastCloningX": "{0} 복제 중...", "autoCastDoneXVoices": "{0}개의 음성이 복제됨 - 더빙을 생성하려면 텍스트 음성 변환을 열어주세요", "autoCastNothingCloned": "음성을 복제할 수 없습니다 - 자세한 내용은 로그를 참조하세요.", - "autoCastReplaceSubtitleQuestion": "이 작업은 열려 있는 자막을 생성된 자막으로 교체합니다. 계속하시겠습니까?", + "autoCastReplaceSubtitleQuestion": "열려 있는 자막을 비디오의 음성 인식 결과로 바꿉니다. 계속하시겠습니까?", "cloneVoicePerLine": "비디오에서 복제 (각 줄의 음성)", "cloneVoicePerLinePreparing": "비디오에서 각 줄의 음성 가져오는 중...", "cloneVoicePerLineNeedsVideo": "각 줄의 음성을 복제하려면 자막이 속한 비디오를 열어야 합니다. 비디오를 열고 다시 시도하세요.", @@ -2804,15 +2830,15 @@ "downloadSidonTitle": "Sidon을 다운로드하시겠습니까?", "downloadSidonQuestionX": "음성 복제용 참조 오디오를 정리하려면 Sidon 음성 복원 모델({0})이 필요합니다.\n\n지금 다운로드하시겠습니까? 다운로드하지 않으면 참조 오디오를 그대로 사용합니다.", "proAudioPostProcessing": "프로 오디오 후처리", - "proAudioPostProcessingDescription": "각 구간에 EQ 웜, 노이즈 게이트, 컴프레션, 라우드니스 정규화(-16 LUFS) 및 페이드 인/아웃을 적용합니다.", + "proAudioPostProcessingDescription": "각 클립에 EQ, 노이즈 게이트, 컴프레션, 라우드니스 정규화(-16 LUFS) 및 짧은 페이드 인/아웃을 적용합니다.", "generatingSpeechSegmentXOfY": "음성 생성 중: {1}개 구간 중 {0}번째", "adjustingSpeedSegmentXOfY": "속도 조정 중: {1}개 구간 중 {0}번째", "mergingAudioSegmentXOfY": "오디오 합치는 중: {1}개 구간 중 {0}번째", "postProcessingSegmentXOfY": "후처리 중: {1}개 구간 중 {0}번째", "audioDucking": "오디오 덕킹", - "audioDuckingDescription": "원본 비디오 오디오 음량을 줄이고 TTS 오디오와 섞어서 원본 사운드트랙이 희미하게 들리도록 합니다.", + "audioDuckingDescription": "원본 비디오 오디오 음량을 줄이고 TTS 오디오와 섞어서 원본 사운드트랙이 희미하게 들리도록 합니다. 음성을 비디오 파일에 추가할 때만 적용됩니다.", "removeOriginalSpeech": "원본 음성 제거 (느림)", - "removeOriginalSpeechDescription": "원본 비디오의 음악과 효과음은 유지하고 음성만 제거한 뒤, 새 음성을 합칩니다. GPU를 사용해도 비디오 길이만큼 시간이 걸리며, GPU가 없으면 몇 배 더 오래 걸립니다. 오디오 더킹을 켜면 더킹 볼륨 설정이 음악과 효과음에 적용됩니다. 음성을 비디오 파일에 추가할 때만 적용됩니다.", + "removeOriginalSpeechDescription": "원본 비디오의 음악과 효과음은 유지하고 음성만 제거한 뒤, 새 음성을 합칩니다. GPU를 사용해도 비디오 길이만큼 시간이 걸리며, GPU가 없으면 몇 배 더 오래 걸립니다. 오디오 덕킹을 켜면 덕킹 볼륨 설정이 음악과 효과음에 적용됩니다. 음성을 비디오 파일에 추가할 때만 적용됩니다.", "removingOriginalSpeech": "원본 음성 제거 중...", "removeOriginalSpeechFailed": "원본 음성을 제거하지 못해 제거 과정 없이 새 음성을 비디오에 추가했습니다.", "speechRemovalModel": "모델", @@ -2822,16 +2848,16 @@ "videoTruncatedWarning": "원본 비디오의 길이는 {1}이지만, 새 비디오 파일의 영상은 {0} 이후로 멈춥니다. 소리는 계속 재생되지만 화면은 정지됩니다.\n\nFFmpeg 출력은 Subtitle Edit 데이터 폴더의 error-log.txt에서 확인하세요. 파일은 보존되어 있습니다.", "originalVolumePercent": "원본 음량 %", "vadSilenceCompression": "VAD 무음 압축", - "vadSilenceCompressionDescription": "템포를 변경하기 전에 단어 사이의 간격을 줄입니다. 음성 활동 감지(VAC)를 사용하여 음성은 그대로 두고 무음 구간만 압축합니다. 이는 음질 저하 없이 시간 간격을 줄여주기 때문에 가장 권장되는 첫 번째 단계입니다.", + "vadSilenceCompressionDescription": "음성 속도를 높이는 대신 단어 사이의 무음을 줄여 음질 저하 없이 클립 길이를 맞춥니다.", "maxSilenceMs": "최대 무음 시간 (ms)", - "highQualityTimeStretch": "고품질 오디오 늘리기 (WSOLA/rubberband)", - "highQualityTimeStretchDescription": "피치를 유지하면서 속도를 변경할 때 기본 atempo 필터 대신 러버밴드 알고리즘(WSOLA)을 사용합니다. 특히 빠른 속도에서 더욱 자연스러운 음성을 생성합니다. FFmpeg 빌드에 librubberband가 포함되어 있어야 하며, 사용할 수 없는 경우 자동으로 atempo로 대체됩니다.", + "highQualityTimeStretch": "고품질 오디오 시간 조절", + "highQualityTimeStretchDescription": "rubberband 알고리즘으로 음성 속도를 조절하여 기본 방식보다 자연스럽게 들립니다. FFmpeg에 librubberband가 없거나 실행에 실패하면 기본 방식을 사용합니다.", "rubberbandInstalled": "(설치됨)", "rubberbandNotFound": "(FFmpeg에서 찾을 수 없음)", - "silencePaddingMs": "무음 시간 (ms)", + "silencePaddingMs": "추가 무음 시간 (ms)", "silencePaddingMsDescription": "각 클립 끝에 짧은 무음을 넣습니다. 문장 사이에 숨 쉴 공간을 제공하는 데 유용합니다.", - "outputSampleRate": "출력 샘플링 속도(0=기본값)", - "outputSampleRateDescription": "모든 구간을 지정된 샘플링 속도(예: 44100, 48000)로 재샘플링합니다. 원래 속도를 유지하려면 0으로 설정하십시오.", + "outputSampleRate": "출력 샘플레이트 (0=원본 유지)", + "outputSampleRateDescription": "모든 구간을 지정된 샘플레이트(예: 44100, 48000)로 재샘플링합니다. 기존 샘플레이트를 유지하려면 0으로 설정하십시오.", "generationFolder": "생성 폴더", "generationFolderDescription": "실행 중에 생성된 오디오 클립의 폴더입니다. 시스템 임시 폴더를 사용하려면 비워 두십시오. 실행할 때마다 별도의 하위 폴더가 생성됩니다.", "deleteTempFiles": "닫을 때 생성된 클립 삭제", @@ -2858,24 +2884,24 @@ "voiceGender": "성별", "voiceAge": "나이", "voicePitch": "음높이", - "voiceAccent": "발음", + "voiceAccent": "억양", "voiceInstructionClonedVoiceNote": "음성 디자인은 \"기본\" 음성에만 영향을 미칩니다 - 복제된 음성은 자신의 특성을 유지합니다.", "actorVoicesTitle": "TTS - 배역", - "actorVoicesSubtitle": "각 등장인물이나 음성에 TTS 음성(및 선택적 음성 디자인 지침)을 할당합니다.", + "actorVoicesSubtitle": "각 등장인물이나 화자에 TTS 음성(및 선택적 음성 디자인 지침)을 할당합니다.", "actorVoicesAssignedXOfY": "{1}개 중 {0}개 할당됨", - "actorOrVoice": "등장인물 / 음성", + "actorOrVoice": "등장인물 / 화자", "applyDefaultToAll": "전체 기본값 적용", "clearAllAssignmentsConfirm": "모든 음성 할당을 삭제하시겠습니까?", "setupCast": "배역...", - "setupCastHint": "각 등장인물(ASSA) 또는 음성(WebVTT)에 TTS 음성을 할당합니다.", + "setupCastHint": "각 등장인물(ASSA) 또는 화자(WebVTT)에 TTS 음성을 할당합니다.", "actorVoicesRowSettingsTitle": "TTS - 음성 설정", "voiceSettingsForX": "\"{0}\"에 대한 음성 설정", "voiceInstructionFreeTextHint": "음성 톤을 형성하기 위해 엔진에서 사용하는 텍스트입니다.", "voiceDesign": "음성 디자인", - "noActorsFoundMessage": "배우를 찾을 수 없습니다. 먼저 자막 내에 등장인물 필드를 설정하세요.", - "noWebVttVoicesFoundMessage": "WebVTT 파일에 음성이 없습니다.", + "noActorsFoundMessage": "등장인물을 찾을 수 없습니다. 먼저 자막 줄에 등장인물 필드를 설정하세요.", + "noWebVttVoicesFoundMessage": "WebVTT 파일에 화자가 없습니다.", "mergeContinuationLinesPromptTitle": "이어지는 줄을 병합할까요?", - "mergeContinuationLinesPromptMessage": "일부 줄은 여러 자막에 걸쳐 분할된 단일 문장으로 나타납니다.\n\n생성 전에 이를 병합하면 TTS 엔진이 각 생각을 하나의 그룹으로 말하게 되어 일반적으로 더 자연스럽게 들립니다.\n\n지금 병합을 검토하고 적용하시겠습니까?", + "mergeContinuationLinesPromptMessage": "일부 문장이 여러 자막에 나뉘어 있는 것으로 보입니다.\n\n생성 전에 병합하면 TTS 엔진이 이어지는 문장을 한 호흡으로 읽게 되어 대체로 더 자연스럽게 들립니다.\n\n지금 병합을 검토하고 적용하시겠습니까?", "skipNoiseLinesPromptTitle": "소리 및 음악 줄을 건너뛸까요?", "skipNoiseLinesPromptMessage": "일부 줄에는 ♪ 또는 [문이 쾅 닫힘]처럼 소리나 음악만 들어 있습니다.\n\n음성 엔진은 이런 줄을 소리 내어 읽으려 하며, 종종 단어를 지어 냅니다.\n\n이 줄을 검토한 뒤 음성이 생성되지 않게 둘까요?", "skipNoiseLinesTitle": "소리나 음악만 있는 줄", @@ -2933,7 +2959,7 @@ "scaleTimes": "프레임 속도 변경", "scaleTimesDescription": "한 프레임 속도에서 다른 프레임 속도로 모든 챕터 시간을 조정합니다.", "fromFrameRate": "원본", - "toFrameRate": "을(를)", + "toFrameRate": "변경 후", "writeToVideo": "비디오에 기록하기...", "writeToVideoTitle": "챕터를 비디오에 기록하기", "writeToVideoDescription": "챕터는 비디오 파일의 복사본에 기록됩니다. 재인코딩되지 않으므로 빠르고 손실이 없습니다.", @@ -2999,7 +3025,7 @@ "backgroundMusic": { "title": "배경 음악 생성", "generateBackgroundMusicDotDotDot": "배경 음악 생성...", - "preset": "프리셋", + "preset": "사전 설정", "prompt": "프롬프트", "tempoBpm": "템포 (BPM)", "generateSeconds": "생성 길이 (초)", @@ -3037,6 +3063,7 @@ "outputLengthTextToSpeech": "생성된 음성 길이에 맞춰 음악이 끊김 없이 반복되며, 말하는 동안에는 음악 볼륨이 줄어듭니다", "addBackgroundMusic": "배경 음악 추가", "addBackgroundMusicHint": "ACE-Step 1.5로 로컬에서 음악을 생성하여 음성에 배경 음악을 추가합니다. 말하는 동안에는 음악 볼륨이 줄어듭니다", + "music": "음악", "generatingBackgroundMusicDotDotDot": "배경 음악 생성 중...", "mixingBackgroundMusicDotDotDot": "배경 음악 합치는 중..." }, @@ -3127,6 +3154,10 @@ "cutVideoTransitionRadial": "방사형", "cutVideoTransitionPixelize": "픽셀화", "cutVideoTransitionBlur": "흐림", + "cutVideoSegments": "구간", + "cutVideoTransitions": "전환", + "cutVideoOutput": "출력", + "cutVideoCutType": "잘라내기 방식", "importCurrentSubtitle": "현재 자막 가져오기", "addRemoveEmbeddedSubtitlesTitle": "내장 자막 추가/제거", "addRemoveEmbeddedSubtitlesMp4Title": "내장 자막 추가/제거 (MP4)", @@ -3138,11 +3169,12 @@ "embeddedTrackUnableToGenerateTitle": "비디오 생성 실패", "embeddedTrackUnableToGenerateMessage": "비디오가 생성되지 않았습니다: {0}{1} 매개변수: {2}", "embeddedTrackPreviewUnavailableTitle": "미리보기 사용 불가", - "embeddedTrackPreviewUnavailableMessage": "선택한 자막 스트림을 추출할 수 없습니다. ffmpeg가 SRT로 변환할 수 있는 텍스트 형식(예: 비트맵 기반 자막)이 아닐 수 있습니다.", + "embeddedTrackPreviewUnavailableMessage": "선택한 자막 스트림을 추출할 수 없습니다. 비트맵 기반 자막처럼 FFmpeg가 SRT로 변환할 수 없는 형식일 수 있습니다.", "embeddedTrackGeneratingVideoXY": "비디오 생성중... {0}% {1}", "embeddedTrackGeneratingVideo": "비디오 생성중...", "mp4FilesFilter": "MP4 파일", "embeddedTracksPickVideoHint": "비디오 파일을 선택하거나 여기에 끌어다 놓으면 자막 트랙을 볼 수 있습니다.", + "embeddedTracks": "트랙", "viewMatroskaTrackX": "Matroska 트랙 보기 - {0}", "resolutionSeparator": "x", "openFromUrlTitle": "URL에서 비디오 파일 열기", @@ -3156,6 +3188,10 @@ "openFromUrlDownloadSubtitles": "자막도 같이 다운로드", "openFromUrlDownloadVideo": "비디오 다운로드", "openFromUrlIncludeAutoGeneratedSubtitles": "자동 생성된 자막 포함 (원본 언어)", + "openFromUrlCookiesFromBrowser": "브라우저 쿠키", + "openFromUrlCookiesFromBrowserHint": "YouTube에 로그인한 브라우저의 쿠키를 전송합니다. YouTube에서 봇이 아닌지 확인을 요구할 때 필요합니다.", + "openFromUrlBotCheckHint": "YouTube에서 봇이 아닌지 확인을 요구합니다. 브라우저에서 YouTube에 로그인한 뒤, \"URL에서 비디오 파일 열기\" 창의 \"브라우저 쿠키\"에서 해당 브라우저를 선택하고 다시 시도하세요 (Firefox 권장). VPN을 끄는 것도 도움이 될 수 있습니다.", + "openFromUrlBotCheckHintWithBrowserX": "YouTube에서 계속 봇이 아닌지 확인을 요구합니다. {0}에서 YouTube에 로그인했는지 확인하세요. Windows의 Chrome과 Edge는 쿠키 읽기를 차단하는 경우가 많습니다. 먼저 브라우저를 닫거나 Firefox를 사용해 보세요.", "assaStyleWillBeUsed": "현재 ASSA 스타일을 사용합니다\n\n여기서 스타일을 설정하려면\n자막 형식을 변경하세요", "pickOnlineSubtitleTitle": "다운로드 할 자막을 선택", "outputSettingsTitle": "출력 설정", @@ -3172,12 +3208,12 @@ "indexTts25SettingsTitle": "IndexTTS 2.5 (audio.cpp) 설정", "indexTts25Emotion": "감정", "indexTts25EmotionStrength": "감정 강도", - "pickOnlineSubtitleFetching": "자막 다운로드 중....", + "pickOnlineSubtitleFetching": "자막 다운로드 중...", "pickOnlineSubtitleNoneFound": "해당 URL에서 자막을 찾을 수 없습니다.", "forcedAligner": "강제 정렬", "postProcessing": "후처리...", "toggleCurrentSubtitleWhilePlaying": "재생 중 현재 자막 표시/숨기기", - "toggleSubtitlesOnVideoPlayer": "비디오 플레이어에서 자막 토글", + "toggleSubtitlesOnVideoPlayer": "비디오 플레이어 자막 표시/숨기기", "subtitlesOnVideoPlayerOn": "비디오 플레이어에서 자막: 켜짐", "subtitlesOnVideoPlayerOff": "비디오 플레이어에서 자막: 꺼짐", "onlyMkvCanSupportEmbeddedSubtitleEditing": "내장 자막 편집에는 Matroska(.mkv, .webm) 파일만 지원됩니다.", @@ -3204,7 +3240,7 @@ "ttsEngineChatterboxCapable": "{0} (Chatterbox 사용 가능)" }, "waveform": { - "guessTimeCodes": "타임코드 추측", + "guessTimeCodes": "타임코드 추정", "guessTimeCodesScanBlockSize": "스캔 블록 크기(ms):", "guessTimeCodesScanBlockAverageMin": "스캔 블록 평균 최소값(최대값 대비 %):", "guessTimeCodesScanBlockAverageMax": "스캔 블록 평균 최대값(최대값 대비 %):", @@ -3214,7 +3250,7 @@ "seekSilence": "무음 탐색", "minSilenceDurationSeconds": "최소 무음 시간(초):", "maxSilenceVolume": "최대 무음 음량(0.0 - 1.0):", - "guessTimeCodesDotDotDot": "시간 코드 추측...", + "guessTimeCodesDotDotDot": "타임코드 추정...", "seekSilenceDotDotDot": "무음 탐색...", "extractAudioDotDotDot": "오디오 추출...", "cloneVoiceTo": "음성 복제 대상", @@ -3222,7 +3258,7 @@ "cloneVoiceExtractFailed": "선택된 줄의 오디오를 추출할 수 없습니다.", "lineXHasNoDuration": "{0}번째 줄은 길이가 없으므로 (종료 시간이 시작 시간보다 늦지 않음), 추출할 오디오가 없습니다.", "audioClipsSkippedLinesX": "다음 줄에서는 오디오를 추출할 수 없어 건너뛰었습니다: {0}", - "toggleShotChange": "장면 전환 토글", + "toggleShotChange": "장면 전환 지점 추가/제거", "resetWaveformZoomAndSpeed": "파형 확대/축소 및 속도 다시 설정", "showOriginalSubtitle": "원본 자막 표시", "timelineGroupTracksBy": "자막 트랙 그룹화 기준", @@ -3254,7 +3290,7 @@ "findText": "텍스트 찾기", "resolutionXDurationYFrameRateZ": "해상도: {0}, 재생 시간: {1}, 프레임 속도: {2}", "startSceneMustComeBeforeEndScene": "시작 장면은 끝 장면보다 먼저 나와야 함", - "goToSubPos": "하위 항목으로 이동", + "goToSubPos": "자막 위치로 이동", "speedInPercentage": "속도(%)", "fromDropFrameValue": "드롭 프레임 값에서", "toDropFrameValue": "드롭 프레임 값으로", @@ -3264,9 +3300,9 @@ "toFrameRate": "대상 프레임 속도", "fromFrameRate": "기존 프레임 속도", "videoXFrameRateY": "{0} - {1} fps", - "adjustAllTimes": "싱크 조정(빠르게/느리게)", - "showEarlier": "빠르게", - "showLater": "느리게", + "adjustAllTimes": "싱크 조정 (앞당기기/늦추기)", + "showEarlier": "앞당기기", + "showLater": "늦추기", "setSyncPoint": "동기화 지점 설정", "setSyncPointViaVideo": "비디오를 통해 동기화 지점 설정...", "syncPointTimeCode": "동기화 지점 타임코드", @@ -3278,8 +3314,8 @@ "adjustAllShortcuts": "키보드 단축키 :\n\n• Shift + ←/→: 0.01초 이동\n• Ctrl + ←/→: 0.1초 이동\n• Alt + ←/→: 0.5초 이동", "adjustAllShortcutsFrames": "키보드 단축키:\n\n• Shift + ←/→: 1 프레임 이동\n• Ctrl + ←/→: 10 프레임 이동\n• Alt + ←/→: 1초 이동", "offsetInSeconds": "초 단위 오프셋", - "speedFactor": "속도 요소", - "showEarlierLaterDotDotDot": "이전/다음 보기..." + "speedFactor": "속도 배율", + "showEarlierLaterDotDotDot": "표시 시간 앞당기기/늦추기..." }, "translate": { "translateViaCopyPaste": "복사/붙여넣기로 자동 번역", @@ -3334,7 +3370,7 @@ "useOnlyExtraServerParameters": "이 매개변수만 사용", "useOnlyExtraServerParametersHint": "Subtitle Edit의 자체 튜닝값 대신 위의 매개변수로 llama-server를 시작합니다. 모델, 호스트 및 포트는 Subtitle Edit에서 설정합니다", "serverRunningAtX": "서버가 {0}에서 실행 중", - "customPromptHint": "사용자 정의 인스트럭션 ({0} = 원본 언어, {1} = 대상 언어); 비워 두면 기본 프롬프트 사용", + "customPromptHint": "사용자 정의 지침 ({0} = 원본 언어, {1} = 대상 언어); 비워 두면 기본 프롬프트 사용", "editTranslationHint": "선택한 행의 번역을 클릭하여 편집하세요 (Enter로 저장, Esc로 취소)", "llamaCppDownloadEngineAndModelPrompt": "llama.cpp를 사용하려면 llama-server 엔진과 번역 모델을 다운로드해야 합니다. 지금 다운로드하시겠습니까?", "llamaCppDownloadEnginePrompt": "llama.cpp를 사용하려면 llama-server 엔진을 다운로드해야 합니다. 지금 다운로드하시겠습니까?", @@ -3365,15 +3401,15 @@ "cpsLineLengthStyleCalcCjk": "CJK 1, Latin 0.5", "cpsLineLengthStyleCalcCjkNoSpace": "CJK 1, Latin 0.5, 띄어쓰기 0", "cpsLineLengthStyleCalcIncludeCompositionCharacters": "구성 문자 포함", - "cpsLineLengthStyleCalcIncludeCompositionCharactersNotSpace": "띄어쓰기 없이 구성 문자 포함", - "cpsLineLengthStyleCalcNoSpaceOrPunctuation": "띄어쓰기나 구두점 없음 ()[]-:;,.!?", - "cpsLineLengthStyleCalcNoSpaceOrPunctuationCpsOnly": "띄어쓰기나 구두점 없이 CPS만", + "cpsLineLengthStyleCalcIncludeCompositionCharactersNotSpace": "공백을 제외하고 구성 문자 포함", + "cpsLineLengthStyleCalcNoSpaceOrPunctuation": "공백·구두점 제외 ()[]-:;,.!?", + "cpsLineLengthStyleCalcNoSpaceOrPunctuationCpsOnly": "공백·구두점 제외 (CPS만)", "cpsLineLengthStyleCalcIgnoreArabicDiacritics": "아랍어 발음 기호 무시", - "cpsLineLengthStyleCalcIgnoreArabicDiacriticsNoSpace": "띄어쓰기 없이 아랍어 발음 기호 무시", + "cpsLineLengthStyleCalcIgnoreArabicDiacriticsNoSpace": "아랍어 발음 기호와 공백 제외", "searchSettingsDotDotDot": "설정 검색...", "network": "네트워크", "fileTypeAssociations": "파일 확장자 연결", - "textBoxColorTags": "자막 입력칸에 색상 태그(HTML/ASSA) 적용", + "textBoxColorTags": "자막 입력칸의 태그(HTML/ASSA)에 색상 표시", "textBoxLiveSpellCheck": "자막 입력칸에서 실시간 맞춤법 검사", "textBoxCenterText": "자막 입력칸 가운데 정렬", "textBoxFontBold": "자막 입력칸 글자 굵게", @@ -3389,10 +3425,10 @@ "subtitleGridLiveSpellCheck": "자막 목록에서 실시간 맞춤법 검사", "subtitleGridCenterText": "자막 목록 텍스트 가운데 정렬", "subtitleGridShowFormatting": "서식이 지정된 (HTML/ASSA) 텍스트를 자막 목록에 표시", - "showUpDownStartTime": "\"표시\"에 대한 위/아래 조절 기능 표시", - "showUpDownEndTime": "\"숨김\"에 대한 위/아래 조절 기능 표시", + "showUpDownStartTime": "\"시작\" 시간의 위/아래 조절 기능 표시", + "showUpDownEndTime": "\"끝\" 시간의 위/아래 조절 기능 표시", "showUpDownDuration": "\"길이\"에 대한 위/아래 조절 기능 표시", - "showUpDownLabels": "위/아래 조절 버튼에 레이블 표시(표시/숨김/길이)", + "showUpDownLabels": "위/아래 조절 버튼에 레이블 표시 (시작/끝/길이)", "showButtonHints": "버튼 힌트 표시", "gridCompactMode": "목록을 사용할 때, 간소화 모드 사용", "gridAlternatingRows": "목록에서 교차 행 색상 사용", @@ -3444,22 +3480,22 @@ "autoBackupIntervalMinutes": "자동 백업 간격(분)", "autoBackupDeleteAfterDays": "자동 백업 보존 기간(일)", "settingsBackupOn": "설정 자동 백업", - "settingsBackupIntervalDays": "설정 백업 간격 (일)", + "settingsBackupIntervalDays": "설정 백업 간격 (일, 0=시작할 때마다)", "settingsBackupMaxCount": "보관할 설정 백업 수", "autoConvertToUtf8": "파일을 열 때, 인코딩을 UTF-8로 자동 변환", - "autoTrimWhiteSpace": "띄어쓰기 자동 수정", + "autoTrimWhiteSpace": "불필요한 공백 자동 정리", "removeBlankLinesWhenOpening": "자막을 열 때 빈 줄 제거", "defaultEncoding": "기본 인코딩", - "colorDurationTooShort": "너무 짧은 길이 표시", - "colorDurationTooLong": "너무 긴 길이 표시", - "colorTextTooLong": "너무 긴 텍스트 표시", - "colorTextTooWide": "너무 넓은 텍스트(픽셀) 표시", - "colorTextTooManyLines": "두 줄 이상인 텍스트 표시", + "colorDurationTooShort": "너무 짧은 길이에 색상 표시", + "colorDurationTooLong": "너무 긴 길이에 색상 표시", + "colorTextTooLong": "너무 긴 텍스트에 색상 표시", + "colorTextTooWide": "너무 넓은 텍스트에 색상 표시 (픽셀)", + "colorTextTooManyLines": "두 줄을 초과하는 텍스트에 색상 표시", "colorTextTooManyLinesX": "{0}줄을 넘으면 강조 표시", - "colorCharactersPerSecond": "초당 너무 많은 문자(CPS) 표시", - "colorWordsPerMinute": "분당 너무 많은 단어(WPM) 표시", - "colorOverlap": "겹치는 타임코드 표시", - "colorGapTooShort": "너무 짧은 간격 표시", + "colorCharactersPerSecond": "초당 문자 수(CPS)가 너무 많으면 색상 표시", + "colorWordsPerMinute": "분당 단어 수(WPM)가 너무 많으면 색상 표시", + "colorOverlap": "겹치는 타임코드에 색상 표시", + "colorGapTooShort": "너무 짧은 간격에 색상 표시", "errorBackgroundColor": "오류 표시 배경색", "waveformDrawGridLines": "격자선 그리기", "waveformUseSkiaRenderer": "실험적 고속 렌더러 사용", @@ -3510,7 +3546,7 @@ "defaultSaveAsFormat": "기본 \"다른 이름으로 저장\" 형식", "favoriteSubtitleFormats": "선호하는 자막 형식", "favoriteLanguages": "선호하는 언어", - "webVttUseXTimestampMap": "WebVTT: X-TIMESTAMP-MAP 사용 (로드 시 오프셋 시간 코드)", + "webVttUseXTimestampMap": "WebVTT: X-TIMESTAMP-MAP 사용 (불러올 때 타임코드 오프셋 적용)", "assaAutoSetResolution": "ASSA: 비디오를 열 때 비디오에서 해상도(PlayResX/PlayResY) 설정", "assaAutoSetResolutionPrompt": "ASSA: 비디오에 맞게 해상도를 변경하기 전에 확인 (해제시 자동으로 리샘플링)", "videoControls": "비디오 컨트롤", @@ -3534,8 +3570,8 @@ "mergeAutoBreak": "줄 합치기: 합친 텍스트 자동 줄 바꿈 (끄면 기존 줄 바꿈 유지)", "filesAndLogs": "파일 및 로그", "showErrorLogFile": "오류 로그 파일 표시", - "showToolsLogFile": "Whisper 로그 파일 표시", - "writeToolsLog": "툴 로그 작성", + "showToolsLogFile": "도구 로그 파일 표시", + "writeToolsLog": "도구 로그 기록", "showSettingsFile": "설정 파일 표시", "showAssaLayer": "ASSA 레이어 상자 표시", "waveformCursorColor": "파형 커서/헤드 색상", @@ -3543,7 +3579,7 @@ "waveformGridColor": "파형 격자 색상", "waveformParagraphLeftColor": "파형 왼쪽 테두리 색상", "waveformParagraphRightColor": "파형 오른쪽 테두리 색상", - "waveformFancyHighColor": "파형의 화려한 고채도", + "waveformFancyHighColor": "화려한 파형의 큰 진폭 색상", "waveformFocusOnMouseOver": "마우스를 올리면 초점 이동", "resetAllSettings": "모든 설정을 초기화", "resetShortcuts": "단축키 다시 설정", @@ -3562,9 +3598,9 @@ "addSpace": "띄어쓰기 추가", "processIfEndsWithComma": "문장이 쉼표로 끝나는 경우 처리", "removeComma": "쉼표 제거", - "editContinuationStyleCustom": "사용자 정의 연속 스타일 편집", + "editContinuationStyleCustom": "사용자 정의 이어짐 표기 방식 편집", "longGapThreshold": "긴 간격 임계값(ms)", - "afterLongGap": "긴 간격 끝에:", + "afterLongGap": "긴 간격 뒤에:", "resetSyntaxColoring": "구문 강조 표시 다시 설정", "resetWaveform": "파형 다시 설정", "resetRules": "규칙 다시 설정", @@ -3572,14 +3608,15 @@ "timeCodeModeTime": "시간 (hh:mm:ss,zzz)", "timeCodeModeFrames": "프레임 (hh:mm:ss:ff)", "timeCodeModeFrameNumbers": "프레임 번호", - "frameModeSnapToVideoFrames": "시간을 동영상 프레임에 맞춰 저장 (초 단위 내 프레임이 아님)", + "frameModeSnapToVideoFrames": "시간을 영상 프레임 기준으로 저장 (초별 프레임 기준 대신)", "textBoxLimitNewLines": "자막 입력칸 줄 수 제한", + "textBoxAutoWrapWhileTyping": "입력 중 자동 줄 바꿈", "mpvOpenGl": "libmpv - OpenGL", "mpvSoftwareRendering": "libmpv - 소프트웨어 렌더링(느림)", "mpvMetalRendering": "libmpv - Metal (실험적, 재시작 필요)", - "mpvWidRendering": "libmpv - 기본 창 ID 렌더링", - "waveFormsAndSpectrogramFoldersContainsX": "\"파형\" 및 \"스펙트로그램\" 폴더에는 {0}이(가) 포함되어 있음", - "deleteWaveformAndSpectrogramFoldersQuestion": "\"파형\" 및 \"스펙트로그램\" 파일을 삭제하시겠습니까?", + "mpvWidRendering": "libmpv - 네이티브 창 ID 렌더링", + "waveFormsAndSpectrogramFoldersContainsX": "\"Waveforms\" 및 \"Spectrograms\" 폴더에 {0}의 캐시 파일이 있음", + "deleteWaveformAndSpectrogramFoldersQuestion": "\"Waveforms\" 및 \"Spectrograms\" 폴더의 캐시 파일을 삭제하시겠습니까?", "waveformGenerateSpectrogram": "스펙트로그램 생성", "waveformAutoGenerate": "비디오를 열 때 파형 자동 생성", "waveformSpectrogramMode": "스펙트로그램 모드", @@ -3588,10 +3625,10 @@ "waveformExtractAudioFormat": "오디오 추출 형식", "waveformExtractAudioSampleRate": "오디오 추출 샘플레이트", "waveformExtractAudioSampleRateOriginal": "원본 (소스 유지)", - "waveformExtractAudioBitRate": "오디오 추출 비트레이트 (MP3/M4A)", + "waveformExtractAudioBitRate": "오디오 추출 비트 전송률 (MP3/M4A)", "ffmpegUseCenterChannelOnly": "5.1 오디오에서 전면 중앙 채널만 사용 (FFmpeg)", - "vlcWidRendering": "libVLC - 기본 창 ID 렌더링", - "ffmpegSoftwareRendering": "FFmpeg - 소프트웨어 렌더링", + "vlcWidRendering": "libVLC - 네이티브 창 ID 렌더링", + "ffmpegSoftwareRendering": "FFmpeg", "downloadFfmpegLibs": "FFmpeg 라이브러리 다운로드 (FFmpeg 비디오 플레이어용)", "subtitleGridEnterKeyAction": "자막 목록 엔터 키 동작", "subtitleSingleClickAction": "자막 목록 원클릭 동작", @@ -3607,7 +3644,7 @@ "saveAsAppendLanguageCode": "\"다른 이름으로 저장\" 시, 언어 코드 추가", "gridGoToSubtitleAndSetVideoPosition": "자막으로 이동하고 재생 위치 설정", "gridGoToNextLine": "다음 줄로 이동", - "gridGoToSubtitleOnlyWaveformOnly": "자막만 보기(파형만)", + "gridGoToSubtitleOnlyWaveformOnly": "자막 위치로 이동 (파형만)", "gridGoToSubtitleAndPause": "자막으로 이동하고 일시 중지", "gridGoToSubtitleAndPlay": "자막으로 이동하여 재생", "gridGoToSubtitleAndPauseAndFocusTextBox": "자막으로 이동하여 일시 중지하고 입력칸 포커스", @@ -3631,9 +3668,9 @@ "saveAsBehaviorUseSubtitleFileName": "자막 파일 이름 사용", "saveAsAppendLanguageCodeTwoLetter": "두 글자", "saveAsAppendLanguageCodeLanguageName": "언어 이름", - "splitOddLineActionWeightTop": "윗줄을 더 길게", - "splitOddLineActionWeightBottom": "아랫줄을 더 길게", - "splitOddLinesAction": "홀수 줄 분할 방식", + "splitOddLineActionWeightTop": "앞부분에 더 많은 줄", + "splitOddLineActionWeightBottom": "뒷부분에 더 많은 줄", + "splitOddLinesAction": "줄 수가 홀수일 때 분할 방식", "ocrUseWordSplitList": "OCR: 단어 분할 목록 사용", "ocrGuessUnknownWords": "OCR: 알 수 없는 단어 유추하기", "spellCheckRememberUseAlwaysList": "맞춤법 검사: \"항상 사용\" 목록 기억", @@ -3659,13 +3696,13 @@ "textBoxButtonShowRemoveFormatting": "입력칸: 서식 제거 버튼 표시", "textBoxButtonShowAiAssistant": "입력칸: AI 도우미 버튼 표시", "waveformSingleClickAction": "파형 원클릭 동작", - "waveformDoubleClickAction": "파형 더블클릭 동작(싱글클릭 동작 후)", + "waveformDoubleClickAction": "파형 더블클릭 동작 (원클릭 동작 후)", "allSettings": "모든 설정", "uiScale": "UI 배율(%)", "fontScale": "글꼴 배율 (%)", "textSelectionOpacity": "텍스트 선택 영역 불투명도 (%)", "waveformToolbarItems": "파형 도구 모음 항목", - "matchIconColorToDarkTheme": "다크 테마에 맞게 아이콘 색상 변경", + "matchIconColorToDarkTheme": "아이콘 색상을 다크 테마 전경색에 맞추기", "subtitlePreviewProperties": "자막 미리보기 속성", "usePositionFromSubtitleFile": "자막 파일(TTML/PAC/EBU STL)에서 위치 사용", "marginIsPartOfSubtitleArea": "여백을 자막 영역에 포함", @@ -3727,7 +3764,7 @@ "activeIn": "적용 위치", "activeInEverywhere": "어디에서나", "generalMergeSelectedLinesAndUnbreak": "선택한 줄을 합치고 줄바꿈 해제", - "generalMergeSelectedLinesAndUnbreakCjk": "선택한 줄을 합치고 줄바꿈 해제 (CJK)", + "generalMergeSelectedLinesAndUnbreakCjk": "선택한 줄을 합치고 공백 없이 줄 바꿈 제거 (CJK)", "generalUnbreakNoSpaceCjk": "공백 없이 줄바꿈 해제 (CJK)", "generalToggleBreak": "줄 바꿈 추가/제거", "generalMergeSelectedLinesBilingual": "선택한 줄을 이중 언어로 합치기", @@ -3758,13 +3795,13 @@ "fileExportEbuStl": "EBU STL 내보내기", "fileExportPac": "PAC 내보내기", "fileExit": "종료", - "openSeDataFolder": "Subtitle Edit 폴더 열기", + "openSeDataFolder": "Subtitle Edit 데이터 폴더 열기", "editFindPrevious": "이전 찾기", "fillSelectedLinesWithClipboard": "선택한 줄 클립보드 텍스트로 채우기", "listInverseSelection": "선택 반전", "listDeleteSelection": "선택 항목 삭제", "deleteSelectionEverywhere": "선택 영역 삭제 (모든 곳)", - "rippleDeleteSelection": "리플 선택 항목 삭제", + "rippleDeleteSelection": "선택 항목 리플 삭제", "assigned": "할당됨", "unassigned": "미지정", "pressedKeyX": "누른 키: {0}", @@ -3788,19 +3825,19 @@ "screenPrivacyFileNamesHidden": "화면 정보 숨기기: 파일 이름 숨김", "screenPrivacyFileNamesAndTextsHidden": "화면 정보 숨기기: 파일 이름과 텍스트 숨김", "screenPrivacyOff": "화면 정보 숨기기: 꺼짐", - "duplicateSelectedLines": "선택한 줄 복사", + "duplicateSelectedLines": "선택한 줄 복제", "sourceView": "소스 보기", "addOrEditBookmark": "즐겨찾기 추가/수정", - "toggleBookmark": "북마크 표시/숨기기(선택된 줄, 텍스트 없음)", + "toggleBookmark": "즐겨찾기 추가/해제 (선택한 줄, 메모 없음)", "toggleForcedSelectedLines": "강제 여부 전환 (선택한 줄)", "goToNextBookmark": "다음 즐겨찾기로 이동", - "clearBookmarks": "책갈피 지우기", + "clearBookmarks": "즐겨찾기 지우기", "goToNextEmptyLine": "다음 빈 줄로 이동", "toggleWaveformToolbar": "파형 도구 모음 표시/숨기기", - "toggleSubtitleGridFormatting": "자막 목록 서식 전환 (서식 표시/태그 표시/서식 없음)", - "waveformSetStartAndSetEndOfPreviousMinusGap": "이전 마이너스 간격의 시작과 끝을 설정", - "waveformSetEndAndStartOfNextAfterGap": "다음 플러스 간격의 끝과 시작을 설정", - "waveformSetEndAndStartOfNextAfterGapAndGoToNext": "다음 플러스 간격의 시작과 끝을 설정하고 다음으로 이동", + "toggleSubtitleGridFormatting": "자막 목록 서식 모드 전환", + "waveformSetStartAndSetEndOfPreviousMinusGap": "시작 설정 및 이전 줄 끝을 간격만큼 앞에 설정", + "waveformSetEndAndStartOfNextAfterGap": "끝 설정 및 다음 줄 시작을 간격만큼 뒤에 설정", + "waveformSetEndAndStartOfNextAfterGapAndGoToNext": "끝 설정 및 다음 줄 시작을 간격만큼 뒤에 설정한 후 다음 줄로 이동", "moveLastWordToNextSubtitle": "마지막 단어를 다음 자막으로 이동", "moveLastWordFromFirstLineDownCurrentSubtitle": "첫 번째 줄의 마지막 단어를 아래로 이동(현재 자막)", "moveFirstWordFromNextLineUpCurrentSubtitle": "다음 줄의 첫 단어(현재 자막)를 위로 이동", @@ -3810,7 +3847,7 @@ "breakAtFirstSpaceFromCursor": "커서 위치부터 첫 번째 공백에서 나누기", "breakAtFirstSpaceFromCursorAndGoToNext": "커서 위치부터 첫 번째 공백에서 나누고 다음으로 이동", "toggleFocusGridAndWaveform": "자막 목록과 파형/스펙트로그램 간에 포커스 전환", - "toggleFocusTextBoxAndWaveform": "입력칸과 파형/스펙트럼 간에 포커스 전환", + "toggleFocusTextBoxAndWaveform": "입력칸과 파형/스펙트로그램 사이에서 포커스 전환", "toggleFocusTextBoxAndGrid": "입력칸과 자막 목록 사이에서 포커스 전환", "goToPreviousLineAndSetVideoPosition": "이전 자막으로 이동하여 비디오 위치를 설정", "goToPreviousLineFromVideoPosition": "현재 비디오 위치에서 이전 자막으로 이동", @@ -3859,8 +3896,8 @@ "splitBreakLongLines": "긴 줄 나누기/다시 조정", "mergeShortLines": "짧은 줄 합치기", "mergeContinuationLines": "이어지는 줄 병합", - "snapAllTimesToFrames": "모든 타임을 프레임에 고정", - "joinSubtitles": "자막 추가", + "snapAllTimesToFrames": "모든 시간을 프레임에 맞추기", + "joinSubtitles": "자막 합치기", "splitSubtitle": "자막 분할", "spellCheck": "맞춤법 검사", "spellCheckGetDictionary": "맞춤법 검사 사전 가져오기", @@ -3899,8 +3936,8 @@ "switchOriginalAndTranslationSelectedLines": "원본과 번역본 전환(선택된 줄)", "mergeOriginalIntoTranslationSelectedLines": "원본과 번역본 합치기(선택된 줄)", "seekSilence": "무음 탐색", - "seekSilenceBack": "무음 앞 탐색", - "seekSilenceForward": "무음 뒤 탐색", + "seekSilenceBack": "이전 무음 탐색", + "seekSilenceForward": "다음 무음 탐색", "waveformGuessStart": "파형에서 시작 시간 추정", "waveformGuessEnd": "파형으로 종료 시간 추정", "waveformGuessStartAndEnd": "파형에서 시작 및 종료 시간 추정", @@ -3936,13 +3973,13 @@ "xShortcutsExportedToY": "{0} 단축키가 {1}(으)로 내보내짐", "importFromSe4": "SE 4에서 불러오기...", "importFromSe4Title": "Subtitle Edit 4 버전에서 단축키 불러오기", - "importFromSe4XImportedYSkipped": "{1}에서 가져온 바로가기 {0}개 ({2} 패스함 - SE 5 미대응)", + "importFromSe4XImportedYSkipped": "{1}에서 단축키 {0}개 가져옴 ({2}개 건너뜀 - 일치하는 SE 5 동작 없음)", "importImageSubtitleForEdit": "편집용 이미지 기반 자막 가져오기", "showPointSyncViaOther": "다른 자막을 통해 지점 동기화 표시", "showPointSync": "지점 동기화 표시", "showMediaInformation": "미디어 정보 표시", "chooseSubtitleFormat": "자막 형식 선택", - "trimWhitespaceSelectedLines": "띄어쓰기 제거 (선택한 줄)", + "trimWhitespaceSelectedLines": "불필요한 공백 정리 (선택한 줄)", "waveformInsertNewSelection": "파형 선택 영역에 새 자막 삽입", "waveformNewSelectionPasteFromClipboard": "파형 클립보드 텍스트를 새 선택 영역에 붙여넣기", "waveformHorizontalZoomInCommand": "가로 파형 확대", @@ -3958,8 +3995,8 @@ "textBoxGoToStart": "입력칸의 시작으로 이동", "textBoxGoToEnd": "입력칸의 끝으로 이동", "sortByNumber": "번호순으로 정렬", - "sortByStartTime": "\"표시\" 시간순으로 정렬", - "sortByEndTime": "\"숨기기\" 시간순으로 정렬", + "sortByStartTime": "\"시작\" 시간순으로 정렬", + "sortByEndTime": "\"끝\" 시간순으로 정렬", "sortByDuration": "길이 순 정렬", "sortByGap": "간격 순 정렬", "sortByText": "텍스트 순 정렬", @@ -3967,7 +4004,7 @@ "sortBySingleLineMaxLength": "텍스트 - 한 줄 최대 길이 순 정렬", "sortByCps": "초당 문자 수 순 정렬", "sortByWpm": "분당 단어 수 순 정렬", - "sortByNumberOfLines": "줄 갯수 순 정렬", + "sortByNumberOfLines": "줄 개수 순 정렬", "sortByActor": "등장인물 순 정렬", "sortByStyle": "스타일 순 정렬", "videoToggleBrightness": "비디오: 밝기 조절 (mpv 전용)", @@ -3975,6 +4012,8 @@ "duplicatesFound": "다음과 같은 중복 단축키 설정 발견:", "copyTextToClipboard": "텍스트를 클립보드에 복사 (선택한 줄)", "copyTextFromOriginalToClipboard": "원본 텍스트 복사 (선택한 줄)", + "copyPlainTextToClipboard": "일반 텍스트를 클립보드에 복사 (선택한 줄)", + "copyPlainTextFromOriginalToClipboard": "원본의 일반 텍스트를 클립보드에 복사 (선택한 줄)", "assaDraw": "ASSA 그리기", "assaGenerateProgressBar": "ASSA 진행률 표시줄 생성", "assaGenerateBackgroundBox": "ASSA 배경 상자 생성", @@ -3988,7 +4027,7 @@ "copyMsRelativeToCurrentSubtitleLineToClipboard": "현재 자막 줄을 기준으로 한 밀리초 수를 클립보드에 복사", "layoutZoomIn": "레이아웃 확대 (UI 크기 조정)", "layoutZoomOut": "레이아웃 축소 (UI 크기 조정)", - "goToPreviousBookmark": "이전 북마크로 가기", + "goToPreviousBookmark": "이전 즐겨찾기로 이동", "selectionToLower": "입력칸: 선택 항목을 소문자로", "selectionToUpper": "입력칸: 선택 항목을 대문자로", "selectionToSentenceCase": "입력칸: 선택 영역을 문장형으로 변환", @@ -4004,10 +4043,10 @@ "moveStartOneFrameForward": "시작을 한 프레임 앞으로 이동", "moveEndOneFrameBack": "끝을 한 프레임 뒤로 이동", "moveEndOneFrameForward": "끝을 한 프레임 앞으로 이동", - "moveStartOneFrameBackKeepGapPrev": "시작을 한 프레임 뒤로 이동 (이전 줄과의 간격 유지)", - "moveStartOneFrameForwardKeepGapPrev": "시작을 한 프레임 앞으로 이동 (이전 줄과의 간격 유지)", - "moveEndOneFrameBackKeepGapNext": "끝을 한 프레임 뒤로 이동 (다음 줄과의 간격 유지)", - "moveEndOneFrameForwardKeepGapNext": "끝을 한 프레임 앞으로 이동 (다음 줄과의 간격 유지)", + "moveStartOneFrameBackKeepGapPrev": "시작을 1프레임 이전으로 이동 (이전 줄이 가까우면 간격 유지)", + "moveStartOneFrameForwardKeepGapPrev": "시작을 1프레임 이후로 이동 (이전 줄이 가까우면 간격 유지)", + "moveEndOneFrameBackKeepGapNext": "끝을 1프레임 이전으로 이동 (다음 줄이 가까우면 간격 유지)", + "moveEndOneFrameForwardKeepGapNext": "끝을 1프레임 이후로 이동 (다음 줄이 가까우면 간격 유지)", "moveStartXMsBack": "시작 시간을 X ms 뒤로 이동 (X는 설정에서 지정, 화면 동기화에도 적용)", "moveStartXMsForward": "시작 시간을 X ms 앞으로 이동 (X는 설정에서 지정, 화면 동기화에도 적용)", "moveEndXMsBack": "종료 시간을 X ms 뒤로 이동 (X는 설정에서 지정, 화면 동기화에도 적용)", @@ -4032,7 +4071,7 @@ "userWords": "사용자 단어 목록", "ocrFixList": "OCR 수정 목록", "unableToAddItem": "항목을 추가할 수 없습니다. 이미 존재하는 항목일 가능성이 높습니다!", - "unableToRemoveItem": "아이템을 제거할 수 없습니다!" + "unableToRemoveItem": "항목을 제거할 수 없습니다!" }, "chooseLanguage": { "title": "UI 언어 선택" @@ -4072,7 +4111,13 @@ "doNotAskAgain": "다시 묻지 않음 (플러그인 관리에서 변경)", "applyPluginsTo": "플러그인을 적용할 대상", "askEachTime": "매번 묻기", - "allLines": "모든 줄" + "allLines": "모든 줄", + "runPlugin": "플러그인 실행", + "plugin": "플러그인", + "configurePlugin": "설정...", + "noBatchPluginsInstalled": "설치된 플러그인 중 일괄 변환을 지원하는 플러그인이 없습니다.", + "batchPluginInfo": "저장된 설정으로 각 파일에 플러그인을 실행하며, 플러그인 창은 표시하지 않습니다.", + "pluginXNotAvailable": "플러그인 '{0}'이(가) 설치되어 있지 않거나, 비활성화되어 있거나, 일괄 변환을 지원하지 않습니다." }, "help": { "aboutSubtitleEdit": "Subtitle Edit 소개", @@ -4081,7 +4126,11 @@ "checkForUpdatesUpToDate": "현재 최신 버전을 사용 중입니다.", "checkForUpdatesNewVersionAvailable": "새 버전 출시: {0}", "checkForUpdatesUnableToCheck": "업데이트를 확인할 수 없습니다.", - "checkForUpdatesDownloadNewVersion": "새 버전 다운로드" + "checkForUpdatesDownloadNewVersion": "새 버전 다운로드", + "checkForUpdatesInstallAndRestart": "설치 및 다시 시작", + "checkForUpdatesDownloadingX": "{0} 다운로드 중...", + "checkForUpdatesInstallFailed": "자동 업데이트에 실패했습니다. \"새 버전 다운로드\"를 이용하세요.", + "checkForUpdatesInstallsWhenClosed": "Subtitle Edit를 종료하면 업데이트가 설치됩니다." }, "ocr": { "linesToDraw": "그릴 선", @@ -4092,7 +4141,7 @@ "inspectLine": "자막 점검...", "ocrEngine": "OCR 엔진", "tesseractEngineMode": "엔진 모드", - "tesseractEngineModeLegacy": "기존 Tesseract만 (이탤릭체 감지 가능)", + "tesseractEngineModeLegacy": "기존 Tesseract만 (기울임 감지 가능)", "tesseractEngineModeNeural": "신경망 LSTM만", "tesseractEngineModeBoth": "Tesseract + LSTM", "tesseractEngineModeDefault": "기본, 사용 가능한 모드 기준", @@ -4107,8 +4156,8 @@ "autoSubmitFirstCharacter": "첫 글자 자동 입력", "editNOcrDatabase": "nOCR 데이터베이스 편집", "zoomFactorX": "확대/축소 비율: {0}배", - "expandInfoX": "전체 항목 수: {0}", - "editNOcrDatabaseXWithYItems": "nOCR 데이터베이스 {0}을 {1:#,###,##0}개 항목으로 편집", + "expandInfoX": "확장 횟수: {0}", + "editNOcrDatabaseXWithYItems": "nOCR 데이터베이스 {0} 편집 (항목 {1:#,###,##0}개)", "newNOcrDatabase": "새 nOCR 데이터베이스", "newBinaryImageCompareDatabase": "새 바이너리 이미지 비교 데이터베이스", "renameNOcrDatabase": "nOCR 데이터베이스 이름 변경", @@ -4120,11 +4169,11 @@ "addBinaryOcrCharacterTitle": "\"{3}\" 데이터베이스로 {0}번째 줄의 {2}개 문자 중 {1}번째 이진 OCR 문자 추가", "lineIndexX": "{0}번 줄", "inspectNOcrAdditions": "새 nOCR 항목 확인하기", - "ocrSelectedLines": "OCR 선택된 자막 줄", + "ocrSelectedLines": "선택한 자막 줄 OCR", "fillSelectedLinesWithClipboard": "선택한 줄을 클립보드 텍스트로 채우기", "showImage": "이미지 표시", "fixOcrErrors": "OCR 오류 수정", - "promptForUknownWords": "모르는 단어에 대한 안내", + "promptForUknownWords": "알 수 없는 단어 확인", "tryToGuessUnknownWords": "모르는 단어 맞춰 보기", "autoBreakIfMoreThanXLines": "{0}줄보다 많으면 자동 줄 바꿈", "unknownWords": "모르는 단어", @@ -4134,14 +4183,14 @@ "ocrX": "OCR - {0}", "addBetterMatch": "더 적합한 항목 추가", "nOcrInspectImageMatches": "nOCR - 이미지 일치 여부 확인", - "addToOcrPair": "OCR 대체 짝에 추가", - "addNameToOcrReplaceList": "OCR 대체 목록에 이름 추가", + "addToOcrPair": "OCR 바꾸기 쌍에 추가", + "addNameToOcrReplaceList": "OCR 바꾸기 목록에 이름 추가", "wordToAdd": "추가할 단어", "nameToAdd": "추가할 이름", - "changeWordFromTo": "단어 변경 from/to", - "clearBackground": "투명한 배경", + "changeWordFromTo": "바꿀 단어 / 바꾼 후 단어", + "clearBackground": "배경 선 지우기", "clearForeground": "전경 지우기", - "nOcrDrawHelp": "그리기 팁\n────────────────────\n• 줄을 계속하려면 Ctrl을 누르세요.\n• Ctrl+z=실행 취소, Ctrl+y=다시 실행", + "nOcrDrawHelp": "그리기 팁\n────────────────────\n• 선을 이어 그리려면 Ctrl을 누르세요.\n• Ctrl+z=실행 취소, Ctrl+y=다시 실행", "editWholeText": "전체 텍스트 편집", "editWordOnly": "단어만 편집", "imagePreProcessing": "이미지 전처리", @@ -4151,11 +4200,11 @@ "removeBorders": "테두리 제거", "binarize": "이진화", "borderSize": "테두리 크기", - "captureTopAlign": "상단 정렬 캡처", + "captureTopAlign": "ASSA 정렬 자동 감지", "showOnlyForcedSubtitles": "강제 표시 자막만 보여주기", "ocrImage": "OCR 이미지", "oneColor": "단색(흰색)", - "darknessThreshold": "어둠의 임계점", + "darknessThreshold": "어두움 임계값", "editExportDotDotDot": "편집/내보내기(_E)...", "editBinaryOcrDatabase": "\"바이너리 이미지 비교\" 데이터베이스 편집", "binaryImageCompareDatabase": "\"바이너리 이미지 비교\" 데이터베이스", @@ -4196,7 +4245,7 @@ "vobSubColorPattern": "패턴", "vobSubColorEmphasis1": "강조 1", "vobSubColorEmphasis2": "강조 2", - "vobSubColorsInvert": "배경 반전 / 강조 1", + "vobSubColorsInvert": "배경과 강조 1 색상 바꾸기", "vobSubIsolateColors": "OCR용 VobSub 색상 분리", "vobSubIsolateColorsHint": "OCR 전에 가장 많이 쓰인 색상(텍스트)만 남기고 윤곽선/안티앨리어싱 색상을 제거해 각 VobSub 이미지를 선명한 흑백 이미지로 다시 만듭니다. 회색 윤곽선이 글자와 섞여 인식이 어려운 VobSub/DVD 자막에서 인식률을 높일 수 있습니다.", "llamaCppOcrSettingsTitle": "llama.cpp OCR 설정", @@ -4215,7 +4264,7 @@ "fallbackOcrDatabase": "대체 OCR 데이터베이스", "showAllOllamaModels": "모든 모델 표시 (비전 미지원 모델 포함)", "ollamaModelLikelyWrong": "Ollama가 텍스트를 반환하지 않았습니다. 선택한 모델이 OCR / 비전을 지원하지 않을 수 있습니다.", - "llamaCppNotDownloaded": "llama.cpp 엔진/모델이 다운로드 되지 않았습니다 - 배치 변환 설정에서 받으세요", + "llamaCppNotDownloaded": "llama.cpp 엔진/모델이 다운로드되지 않았습니다 - 일괄 변환 설정에서 다운로드하세요", "llamaCppReturnedNoText": "llama.cpp 에서 텍스트가 반환되지 않았습니다 - 서버와 모델 설정을 확인하세요", "llamaCppDownloadEngineAndModelPrompt": "llama.cpp를 사용하려면 llama-server 엔진과 선택한 OCR 모델을 다운로드해야 합니다. 지금 다운로드하시겠습니까?", "llamaCppDownloadEnginePrompt": "llama.cpp를 사용하려면 llama-server 엔진을 다운로드해야 합니다. 지금 다운로드하시겠습니까?", @@ -4247,14 +4296,18 @@ "xLearned": "{0:#,##0}개 학습됨", "xSkipped": "{0:#,##0}개 건너뜀", "charactersToTrain": "훈련시킬 문자", - "letterCombinationsToTrain": "하나의 이미지로 분할될 수 있는 글자 조합", + "letterCombinationsToTrain": "하나의 이미지로 처리될 수 있는 글자 조합", "importCharactersFromSubtitleFile": "자막 파일에서 문자 가져오기...", - "numberOfLineSegments": "줄 세그먼트 수", - "alsoTrainBold": "볼드체도 훈련", - "alsoTrainItalic": "이탤릭체도 훈련", + "numberOfLineSegments": "선분 수", + "alsoTrainBold": "굵게도 훈련", + "alsoTrainItalic": "기울임도 훈련", "trainingXLearnedYSkipped": "\"{0}\" 훈련 - {1:#,##0} 문자 학습됨, {2:#,##0} 건너뜀", "trainingDoneXLearned": "훈련 완료 - {0:#,##0} 문자 학습됨", - "selectAtLeastOneFont": "하나 이상의 글꼴을 선택해 주세요" + "selectAtLeastOneFont": "하나 이상의 글꼴을 선택해 주세요", + "nOcrZoomIn": "확대", + "nOcrZoomOut": "축소", + "nOcrLineAlgorithm": "알고리즘", + "nOcrLineAlgorithmHint": "자동 그리기로 전경/배경 선을 생성할 때 사용할 알고리즘" }, "assa": { "assaDraw": "ASSA 그리기", @@ -4264,7 +4317,7 @@ "drawBezierTool": "베지어 곡선(F5)", "drawRectangleTool": "직사각형(F6)", "drawCircleTool": "동그라미(F7)", - "drawCloseShape": "닫힌 도형(F8/Enter)", + "drawCloseShape": "도형 닫기 (F8/Enter)", "drawDeleteShape": "도형 삭제(Del)", "drawChangeLayer": "레이어 변경", "drawClearAll": "모두 지우기(Ctrl+N)", @@ -4280,7 +4333,7 @@ "drawSelectedShape": "선택한 도형", "drawSelectedLayer": "선택한 레이어", "drawToolX": "도구: {0}", - "drawHelpText": "클릭하여 점을 추가 • Enter/F8 키를 눌러 도형을 닫음 • Shift 키를 누른 채 드래그하여 이동 • Ctrl 키를 누른 채 스크롤하여 확대/축소", + "drawHelpText": "클릭하여 점 추가 • 우클릭하여 옵션 표시 • 선택 도구: 핸들을 드래그하여 크기 조절/회전 (Shift = 비율 유지/15° 단위 회전) • Enter/F8로 도형 닫기 • Shift+드래그 또는 마우스 가운데 버튼으로 화면 이동 • Ctrl+스크롤로 확대/축소", "drawMoveToLayer": "레이어로 이동", "drawFlipHorizontal": "수평 뒤집기", "drawFlipVertical": "수직 뒤집기", @@ -4309,6 +4362,49 @@ "drawCategoryArrows": "화살표", "drawCategoryBasicShapes": "기본 도형", "drawCategorySymbols": "기호", + "drawCategoryClipArt": "클립 아트", + "drawClipArtRedHeart": "빨간 하트", + "drawClipArtAngerMark": "분노 표시", + "drawClipArtSweatDrop": "땀방울", + "drawClipArtSparkles": "반짝임", + "drawClipArtBlush": "홍조", + "drawClipArtSurpriseLines": "놀람 표시", + "drawClipArtBamBurst": "쾅! 효과", + "drawClipArtZzz": "잠자는 표시 (Zzz)", + "drawClipArtSun": "태양", + "drawClipArtFlame": "불꽃", + "drawClipArtLightBulb": "전구", + "drawClipArtLightningBolt": "번개", + "drawClipArtColoredMusicNotes": "알록달록한 음표", + "drawClipArtPhone": "전화기", + "drawClipArtQuestionMark": "물음표", + "drawClipArtExclamationMark": "느낌표", + "drawClipArtGreenCheck": "초록색 체크 표시", + "drawClipArtRedCross": "빨간 X 표시", + "drawClipArtThumbsUp": "엄지 척", + "drawClipArtGoldStar": "금색 별", + "drawClipArtBrokenHeart": "깨진 하트", + "drawClipArtGhost": "유령", + "drawClipArtSkull": "해골", + "drawClipArtSnowflake": "눈송이", + "drawClipArtMoonAndStars": "달과 별", + "drawClipArtRainCloud": "비구름", + "drawClipArtBomb": "폭탄", + "drawClipArtGift": "선물", + "drawClipArtCrown": "왕관", + "drawClipArtClock": "시계", + "drawClipArtClapperboard": "슬레이트", + "drawClipArtMicrophone": "마이크", + "drawClipArtWarningSign": "경고 표지", + "drawClipArtNoEntry": "진입 금지", + "drawClipArtPopcorn": "팝콘", + "drawClipArtCoffeeCup": "커피잔", + "drawClipArtRainbow": "무지개", + "drawClipArtTrophy": "트로피", + "drawClipArtRocket": "로켓", + "drawClipArtBalloon": "풍선", + "drawClipArtCamera": "카메라", + "drawClipArtPartyPopper": "축하 폭죽", "drawCategoryMyShapes": "내 도형", "drawShapeSpeechBubble": "말풍선", "drawShapeSpeechBubbleRight": "말풍선 (오른쪽 꼬리)", @@ -4318,10 +4414,16 @@ "drawShapeThoughtBubble": "생각 말풍선", "drawShapeShoutBubble": "외침 말풍선", "drawShapeCaptionBox": "자막 상자", + "drawShapeSpeechBubbleSide": "말풍선 (왼쪽 꼬리)", + "drawShapeRoundBubble": "원형 말풍선", + "drawShapePillBubble": "캡슐형 말풍선", + "drawShapeScreamBubble": "비명 말풍선", "drawShapeArrow": "화살표", "drawShapeDoubleArrow": "양방향 화살표", "drawShapeChevron": "갈매기형", "drawShapeCurvedArrow": "곡선 화살표", + "drawShapeRepeatArrow": "반복 화살표", + "drawShapeUTurnArrow": "유턴 화살표", "drawShapeRoundedRectangle": "둥근 모서리 직사각형", "drawShapeTriangle": "삼각형", "drawShapeDiamond": "마름모", @@ -4332,6 +4434,8 @@ "drawShapeRing": "고리", "drawShapePlus": "더하기 기호", "drawShapeBanner": "배너", + "drawShapeHalfCircle": "반원", + "drawShapeTeardrop": "물방울", "drawShapeHeart": "하트", "drawShapeCheck": "체크 표시", "drawShapeMusicNote": "음표", @@ -4340,6 +4444,10 @@ "drawShapeCloud": "구름", "drawShapeMoon": "달", "drawShapeBadge": "배지", + "drawShapeSpeaker": "스피커", + "drawShapeCross": "X 표시", + "drawShapeSmiley": "웃는 얼굴", + "drawShapeSadSmiley": "슬픈 얼굴", "drawSize": "크기", "drawLayer": "레이어", "drawBackgroundVideoFrame": "비디오 프레임 (현재 위치)", @@ -4356,19 +4464,55 @@ "drawRotateCounterClockwise": "시계 반대 방향으로 90° 회전", "drawSvgImages": "SVG 이미지", "drawSvgNoShapes": "SVG 파일에서 채워진 도형이나 선을 찾을 수 없습니다.", + "drawTextTool": "텍스트 도구", + "drawDefaultText": "텍스트", + "drawShrinkToFit": "상자에 맞춰 축소", + "drawTextColor": "텍스트 색상", + "drawTextBox": "텍스트 상자", + "drawDiscardChangesTitle": "변경 사항을 버리시겠습니까?", + "drawDiscardChanges": "그리기가 변경되었습니다.\n변경 사항을 버리시겠습니까?", + "drawColorPickerToolName": "색상 선택기", + "drawOpenDrawing": "ASSA 그리기 열기", + "drawSaveDrawing": "ASSA 그리기 저장", + "drawDrawingFiles": "ASSA 그리기 파일", + "drawAssFiles": "ASS 파일", + "drawPositionXY": "위치: {0}, {1}", + "drawXShapes": "도형 {0}개", + "drawXPoints": "점 {0}개", + "drawShapeXDraw": "도형 {0} (그리기)", + "drawShapeXErase": "도형 {0} (지우기)", "progressBarTitle": "ASSA 진행 표시줄", "progressBarSettings": "진행 표시줄", "mouseOverColor": "마우스 오버 색상", "clickedColor": "클릭한 색상", "progressBarForeColor": "전경색", - "progressBarSquareCorners": "뾰족한 모서리", + "progressBarSquareCorners": "직각 모서리", "progressBarRoundedCorners": "둥근 모서리", - "progressBarChapters": "목차", - "progressBarSplitterWidth": "스플리터 폭", - "progressBarSplitterHeight": "스플리터 높이", + "progressBarChapters": "챕터", + "progressBarSplitterWidth": "챕터 구분선 너비", + "progressBarSplitterHeight": "챕터 구분선 높이", "progressBarXAdjustment": "X축 조정", "progressBarYAdjustment": "Y축 조정", "progressBarTextAlignment": "텍스트 정렬", + "progressBarStyleCutCorners": "모서리 잘림", + "progressBarStyleArrow": "화살표", + "progressBarStyleSlanted": "기울어진 모양", + "progressBarStyleSegments": "분할 막대", + "progressBarStyleRoundedSegments": "둥근 분할 막대", + "progressBarStyleDots": "점", + "progressBarStyleDiamonds": "마름모", + "progressBarStyleChevrons": "갈매기형", + "progressBarStyleStripes": "줄무늬", + "progressBarStyleWave": "웨이브", + "progressBarStyleZigzag": "지그재그", + "progressBarStyleTapered": "한쪽 끝이 좁은 모양", + "progressBarStyleRuler": "눈금자", + "progressBarStyleDoubleLine": "이중 선", + "progressBarStyleEqualizer": "이퀄라이저", + "progressBarStyleStars": "별", + "progressBarPlayhead": "재생 위치 표시", + "progressBarEdgeMargin": "가장자리 여백", + "progressBarSplitter": "챕터 구분선", "fontCollectorTitle": "글꼴 수집기", "fontCollectorUsedIn": "사용된 곳", "fontCollectorFontFiles": "글꼴 파일", @@ -4392,15 +4536,15 @@ "fontCollectorAndXMoreFonts": "... 외 {0}개", "fontCollectorNoFontsToEmbed": "새로 포함할 글꼴이 없습니다. 필요한 글꼴이 이미 포함되어 있거나 글꼴 파일을 찾지 못했습니다.", "fontCollectorXFontFilesEmbedded": "글꼴 파일 {0}개를 자막에 포함했습니다.", - "fontCollectorTrimFonts": "사용된 문자만 포함 (폰트 자르기)", - "trimFontsDotDotDot": "사용된 문자로 폰트 자르기...", - "trimFontsPromptX": "{0}개의 포함된 폰트를 현재 사용 중인 문자만 포함하도록 자르시겠습니까?\n\n나중에 자막에 추가된 텍스트는 누락된 문자를 표시할 수 있으므로 마지막 단계로 자르세요.", - "trimFontsNoFontsToTrim": "자르기 위한 포함된 폰트가 없습니다.", - "trimFontsXFontsTrimmedSavedYZ": "{0}개의 폰트가 잘렸으며, {1}를 저장합니다.\n\n{2}", + "fontCollectorTrimFonts": "사용된 문자만 포함 (글꼴 정리)", + "trimFontsDotDotDot": "글꼴에서 사용된 문자만 남기기...", + "trimFontsPromptX": "포함된 글꼴 {0}개에서 현재 사용 중인 문자만 남기시겠습니까?\n\n나중에 추가한 글자가 표시되지 않을 수 있으므로 마지막 단계에서 글꼴을 정리하세요.", + "trimFontsNoFontsToTrim": "정리할 글꼴이 포함되어 있지 않습니다.", + "trimFontsXFontsTrimmedSavedYZ": "글꼴 {0}개를 정리하여 {1}의 용량을 줄였습니다.\n\n{2}", "trimFontsTotalSavingX": "총 절감: {0}", - "trimFontsReasonNotTrueType": "TrueType 폰트만 자를 수 있습니다", - "trimFontsReasonFontCollection": "폰트 컬렉션은 지원되지 않습니다", - "trimFontsReasonCouldNotParse": "폰트를 읽을 수 없습니다", + "trimFontsReasonNotTrueType": "TrueType 글꼴에서만 사용하지 않는 문자를 제거할 수 있습니다", + "trimFontsReasonFontCollection": "글꼴 컬렉션은 지원되지 않습니다", + "trimFontsReasonCouldNotParse": "글꼴을 읽을 수 없습니다", "trimFontsReasonNoSavings": "크기 감소 없음", "resolutionResamplerTitle": "해상도 변경", "resolutionResamplerSourceRes": "원본 해상도", @@ -4408,7 +4552,7 @@ "resolutionResamplerChangeMargins": "여백 변경", "resolutionResamplerChangeFontSize": "글자 크기 변경", "resolutionResamplerChangePositions": "위치 변경", - "resolutionResamplerChangeDrawing": "도면 변경", + "resolutionResamplerChangeDrawing": "그리기 도형 크기 변경", "resolutionResamplerFromVideo": "비디오에서...", "resolutionResamplerSourceAndTargetEqual": "원본 해상도와 목표 해상도가 동일하므로 별도의 조치가 필요하지 않습니다.", "resolutionResamplerNothingSelected": "변경할 옵션을 하나 이상 선택해 주세요.", @@ -4423,12 +4567,33 @@ "backgroundBoxWave": "웨이브", "backgroundBoxHexagon": "육각형", "backgroundBoxTornPaper": "찢어진 종이", - "backgroundBoxCloud": "클라우드", + "backgroundBoxCloud": "구름", "backgroundBoxTornPaperDouble": "찢어진 종이(상단 및 하단)", "backgroundBoxStarburst": "스타버스트", - "backgroundBoxScroll": "스크롤", + "backgroundBoxScroll": "두루마리·양피지", + "backgroundBoxPill": "캡슐형", + "backgroundBoxCutCorners": "모서리 잘림", + "backgroundBoxSlanted": "기울어진 모양", + "backgroundBoxTicket": "티켓", + "backgroundBoxSpeechBubble": "말풍선", + "backgroundBoxRibbon": "리본", + "backgroundBoxBrushStroke": "붓 자국", + "backgroundBoxCornerBrackets": "모서리 괄호", + "backgroundBoxUnderline": "밑줄 막대", + "backgroundBoxFrame": "테두리", + "backgroundBoxArrowTag": "화살표 태그", + "backgroundBoxSideBar": "측면 막대", "stylesInFile": "파일 내 스타일", "stylesSaved": "저장된 스타일", + "stylesSummary": "파일 내 스타일 {0}개 · 저장된 스타일 {1}개", + "stylesTransform": "변형", + "stylesEditing": "편집", + "stylesPreviewSampleText": "예제 텍스트", + "stylesPreviewSampleTextHint": "예제 텍스트 - \\N으로 줄 바꿈", + "stylesScale": "크기 비율 (%)", + "stylesPreviewDark": "어두운 배경", + "stylesPreviewChecker": "체크무늬 배경", + "stylesPreviewLight": "밝은 배경", "allCategories": "[전체]", "newCategory": "새 카테고리...", "renameCategory": "카테고리 이름 변경...", @@ -4438,12 +4603,12 @@ "stylesTitleX": "스타일 - {0}", "propertiesTitleX": "속성 - {0}", "attachmentsTitleX": "첨부 파일 - {0}", - "smartWrappingTopWide": "0: 스마트 래핑(상단 확장)", + "smartWrappingTopWide": "0: 스마트 줄 바꿈 (윗줄을 더 길게)", "endOfLineWrapping": "1: 줄 끝 단어 줄 바꿈, \\N만 줄 바꿈", "noWrapping": "2: 줄 바꿈 없음, \\N과 \\n 모두 줄 바꿈", - "smartWrappingBottomWide": "3: 스마트 래핑(하단 확장)", + "smartWrappingBottomWide": "3: 스마트 줄 바꿈 (아랫줄을 더 길게)", "fontsAndGraphics": "글꼴 및 그래픽", - "wrapStyle": "래핑 스타일", + "wrapStyle": "줄 바꿈 방식", "borderAndShadowScaling": "외곽선 및 그림자 크기 조정", "collisions": "충돌", "playDepth": "재생 깊이", @@ -4460,9 +4625,11 @@ "keepBoth": "둘 다 유지", "doThisForAllConflictingStyles": "충돌하는 모든 스타일에 이 동작 적용", "setStyleAsDefault": "이 스타일을 기본값으로 설정", - "takeUsagesFromDotDotDot": "사용법 가져오기...", + "takeUsagesFromDotDotDot": "다른 스타일의 적용 대상 가져오기...", "replaceStyleWithDotDotDot": "스타일을 다음으로 바꾸기...", "noAttachmentsFound": "선택한 ASSA 파일에서 첨부 파일을 찾을 수 없습니다.", + "attachmentsDropHint": "글꼴이나 이미지 파일을 여기에 끌어다 놓으면 첨부됩니다", + "attachmentsSummary": "글꼴 {0}개 · 이미지 {1}개 · {2}", "deleteStyleQuestion": "스타일을 삭제하시겠습니까?", "deleteStylesQuestion": "스타일을 삭제하시겠습니까?", "deleteXStylesQuestion": "스타일 {0}개를 삭제하시겠습니까?", @@ -4478,23 +4645,43 @@ "chooseOverrideTagToAdd": "추가할 재정의 태그를 선택", "fontSizeChange": "글자 크기 변경", "moveTextFromLeftToRight": "텍스트를 왼쪽에서 오른쪽으로 이동", - "colorFromWhiteToRed": "흰색에서 빨간색까지의 색상", + "colorFromWhiteToRed": "흰색에서 빨간색으로 색상 변경", "rotateXSlow": "X축 회전(천천히)", "rotateX": "X축 회전", "rotateY": "Y축 회전", "rotateTilt": "회전/기울이기", - "spaceIncrease": "공간 확대", + "spaceIncrease": "자간 늘리기", "popIn": "팝 인", "spinIn": "스핀 인", "setPosition": "위치 설정", "rotation": "회전", + "setPositionTopLeft": "왼쪽 위", + "setPositionTopCenter": "가운데 위", + "setPositionTopRight": "오른쪽 위", + "setPositionMiddleLeft": "왼쪽 가운데", + "setPositionCenter": "가운데", + "setPositionMiddleRight": "오른쪽 가운데", + "setPositionBottomLeft": "왼쪽 아래", + "setPositionBottomCenter": "가운데 아래", + "setPositionBottomRight": "오른쪽 아래", + "setPositionCustom": "사용자 정의", + "setPositionReset": "자막 줄의 위치로 초기화", + "setPositionResetRotation": "회전 초기화", + "setPositionGuides": "안내선", + "setPositionSafeArea": "안전 영역", + "setPositionCenterLines": "중심선", + "setPositionSnapToGuides": "안내선에 맞추기", + "setPositionNudgeHint": "방향키로 1픽셀씩 이동, Shift와 함께 누르면 10픽셀씩 이동", "scaleX": "가로 배율", "scaleY": "세로 배율", "spacing": "자간", "angle": "각도", "imageColorPicker": "이미지 색상 선택기", "copyColorAsHextoClipboard": "HEX 색상값으로 클립보드에 복사", - "generatingBackgroundBoxXOfY": "{1}의 배경 상자 {0}을(를) 생성 중...", + "imageColorPickerHint": "비디오 프레임을 클릭하여 색상을 선택하세요", + "copyColorAsAssaToClipboard": "ASSA 색상값(&HBBGGRR&)으로 클립보드에 복사", + "copyColorAsAssaTagToClipboard": "색상값을 ASSA \\c 태그로 클립보드에 복사", + "generatingBackgroundBoxXOfY": "배경 상자 {1}개 중 {0}번째 생성 중...", "applyAdvancedEffectTitle": "고급 효과 적용", "chooseEffect": "효과 선택", "advancedEffectTypewriter": "타자기", @@ -4506,7 +4693,7 @@ "advancedEffectKaraoke": "가라오케", "advancedEffectKaraokeDescription": "각 문자를 순서대로 강조 표시하여 노래방 가사처럼 왼쪽에서 오른쪽으로 텍스트를 출력", "advancedEffectFancyKaraoke": "가라오케 - 액티브 워드 팝", - "advancedEffectFancyKaraokeDescription": "미리 지정된 강조 단어(\\u1 밑줄 또는 강조 색상)를 사용하여 자막에 적용 — 사용하지 않는 단어는 흐리게 표시하고, 사용 중인 단어에는 팝 글로우 애니메이션을 적용", + "advancedEffectFancyKaraokeDescription": "미리 지정된 강조 단어(\\u1 밑줄 또는 강조 색상)를 사용하여 자막에 효과 적용 — 강조되지 않은 단어는 흐리게 하고, 강조 단어에는 팝·발광 애니메이션 적용", "advancedEffectScrambleReveal": "스크램블 공개", "advancedEffectScrambleRevealDescription": "뒤섞인 문자들이 왼쪽에서 오른쪽으로 해독되어 실제 텍스트로 출력", "advancedEffectRainbowPulse": "레인보우 펄스", @@ -4528,7 +4715,7 @@ "advancedEffectOldMovie": "고전 영화 효과(배경)", "advancedEffectOldMovieDescription": "노이즈, 비네팅, 스크래치 등 빈티지 35mm 필름 프로젝터의 느낌을 재현", "advancedEffectNeonBurst": "네온 버스트", - "advancedEffectNeonBurstDescription": "네온 블룸 효과와 확대/축소 가능한 '팝' 애니메이션이 적용된 고에너지 '숏츠' 스타일 캡션", + "advancedEffectNeonBurstDescription": "네온 발광 효과와 글자가 크게 나타났다가 원래 크기로 줄어드는 팝 애니메이션을 적용한 역동적인 쇼츠 스타일 자막", "advancedEffectSnowFlakeCount": "입자 수", "advancedEffectStarfieldStarCount": "별 개수", "advancedEffectGlitch": "글리치", @@ -4558,11 +4745,11 @@ "advancedEffectSlowZoomIn": "천천히 확대", "advancedEffectSlowZoomInDescription": "보통 크기로 시작하여 자막이 재생되는 동안 서서히 커짐.", "advancedEffectFadeIn": "전환 - 페이드 인", - "advancedEffectFadeInDescription": "화면이 검은색으로 시작하다가 한 줄씩 서서히 페이드인", + "advancedEffectFadeInDescription": "각 자막 구간에서 검은 화면이 서서히 걷히며 영상이 나타남", "advancedEffectFadeOut": "전환 - 페이드 아웃", - "advancedEffectFadeOutDescription": "화면이 검은색으로 변하며 한 줄씩 서서히 페이드 아웃", + "advancedEffectFadeOutDescription": "각 자막 구간의 끝에서 화면이 서서히 검게 변함", "advancedEffectTvClose": "화면 전환 - TV 화면 닫기", - "advancedEffectTvCloseDescription": "상단과 하단에서 검은색 막대가 안쪽으로 뻗어 나오고, 가운데 부분은 흰색으로 점차 희미해지고(상단 검정/가운데 흰색/하단 검정) 어두워짐", + "advancedEffectTvCloseDescription": "상단과 하단에서 검은색 막대가 안쪽으로 커지면서 가운데 부분은 흰색으로 변합니다 (상단 검정/가운데 흰색/하단 검정). 후반부는 완전히 검은 화면이 됩니다.", "advancedEffectSlideInLeft": "왼쪽에서 슬라이드 인", "advancedEffectSlideInLeftDescription": "화면 왼쪽 밖에서 들어와 잠시 멈춘 후 왼쪽으로 사라짐", "advancedEffectSlideInRight": "오른쪽에서 슬라이드 인", @@ -4575,7 +4762,36 @@ "advancedEffectLowerThird": "하단 배너", "advancedEffectLowerThirdDescription": "강조 막대가 슬라이드되어 나타나는 왼쪽 아래의 이름 및 역할 배너 — 인터뷰/다큐멘터리 스타일 (첫 번째 줄은 이름, 다음 줄부터는 역할)", "advancedEffectLowerThirdAccentColor": "강조 색상", - "overrideTagsHistory": "태그 기록 재정의" + "advancedEffectCategoryWords": "단어 및 가라오케", + "advancedEffectCategoryInOut": "등장 및 퇴장", + "advancedEffectCategoryTextFx": "텍스트 효과", + "advancedEffectCategoryScenes": "장면", + "advancedEffectCategoryTitles": "타이틀 및 크레딧", + "advancedEffectSearchWatermark": "효과 검색...", + "advancedEffectNoMatches": "검색어와 일치하는 효과가 없습니다", + "advancedEffectCustomize": "사용자 정의", + "advancedEffectNoSettings": "이 효과는 별도 설정 없이 그대로 적용됩니다.", + "advancedEffectAmount": "개수", + "advancedEffectPreviewNoVideo": "예제 미리 보기 - 비디오를 열면 실제 렌더링 결과를 볼 수 있습니다", + "advancedEffectHighlightBox": "강조 상자", + "advancedEffectHighlightBoxDescription": "대사에 맞춰 색상 상자가 단어마다 이동하는 CapCut 스타일 자막", + "advancedEffectSingleWordPop": "한 단어씩 팝", + "advancedEffectSingleWordPopDescription": "말하는 순서에 맞춰 큰 단어가 하나씩 튀어나오는 쇼츠 스타일 자막", + "advancedEffectBoldCaptions": "굵은 자막", + "advancedEffectBoldCaptionsDescription": "두꺼운 외곽선이 있는 굵은 자막 - 현재 말하는 단어가 강조 색상으로 빛나며 커짐", + "advancedEffectBoxColor": "상자 색상", + "advancedEffectHighlightColor": "강조 색상", + "advancedEffectUpperCase": "대문자", + "advancedEffectTextSizePercent": "텍스트 크기 (%)", + "advancedEffectSmoothFill": "부드러운 색상 채우기", + "advancedEffectSmoothFillDescription": "대사에 맞춰 각 단어가 강조 색상으로 부드럽게 채워짐", + "advancedEffectBlurInWords": "흐림에서 선명하게 등장", + "advancedEffectBlurInWordsDescription": "말하는 순서에 맞춰 단어가 하나씩 나타남 - 흐릿한 상태에서 선명해지며 제자리에 올라옴", + "advancedEffectRollingCaptions": "롤링 자막", + "advancedEffectRollingCaptionsDescription": "실시간 채팅이나 텔레프롬프터처럼 각 자막 줄이 위로 올라오며 이전 줄을 밀어 올림", + "advancedEffectFillColor": "채우기 색상", + "advancedEffectRise": "기준선에서 위로 솟아오르기", + "overrideTagsHistory": "서식 재정의 태그 기록" }, "about": { "title": "Subtitle Edit 정보", From 82be670c5eb23141b0655a70dbf68faf6061fd22 Mon Sep 17 00:00:00 2001 From: niksedk Date: Fri, 16 Oct 2026 18:32:28 +0200 Subject: [PATCH 280/306] Waveform: dragging an ASSA drawing moves all of its layers ASSA draw writes one line per layer, all with the same times, so a clip art is a stack of lines. Dragging it in the waveform moved only the top layer out of the picture. A drag on a drawing line now moves every drawing line with exactly the same times along with it; ordinary lines with the same times still move on their own. Co-Authored-By: Claude Opus 5.5 --- .../AudioVisualizerControl/AudioVisualizer.cs | 43 +++++++++++++++++++ tests/UI/Controls/AudioVisualizerDragTests.cs | 42 +++++++++++++++++- 2 files changed, 83 insertions(+), 2 deletions(-) diff --git a/src/ui/Controls/AudioVisualizerControl/AudioVisualizer.cs b/src/ui/Controls/AudioVisualizerControl/AudioVisualizer.cs index 45264efb668..4b7375ed9be 100644 --- a/src/ui/Controls/AudioVisualizerControl/AudioVisualizer.cs +++ b/src/ui/Controls/AudioVisualizerControl/AudioVisualizer.cs @@ -1411,6 +1411,18 @@ private void OnPointerPressed(object? sender, PointerPressedEventArgs e) _interactionMode = InteractionMode.MovingSelection; } + else if (GetDrawingStack(p) is { Count: > 1 } stack) + { + // An ASSA drawing (clip art) is one line per layer, all with the same times: + // dragging one moved just that layer out of the picture + _selectionMoveSnapshot.Clear(); + foreach (var layer in stack) + { + _selectionMoveSnapshot.Add((layer, layer.StartTime.TotalSeconds, layer.Duration.TotalSeconds)); + } + + _interactionMode = InteractionMode.MovingSelection; + } else { _interactionMode = InteractionMode.Moving; @@ -1424,6 +1436,37 @@ private void OnPointerPressed(object? sender, PointerPressedEventArgs e) } } + /// + /// The ASSA drawing lines (\p1 drawings and ASSA draw text) with exactly the times of + /// , itself included - the layers of one drawing; null when it is + /// not a drawing line. + /// + private List? GetDrawingStack(SubtitleLineViewModel paragraph) + { + if (!IsDrawingLine(paragraph)) + { + return null; + } + + var stack = new List(); + foreach (var p in _displayableParagraphs) + { + if (p.StartTime == paragraph.StartTime && p.EndTime == paragraph.EndTime && IsDrawingLine(p)) + { + stack.Add(p); + } + } + + return stack; + } + + private static bool IsDrawingLine(SubtitleLineViewModel paragraph) + { + var text = paragraph.Text; + return !string.IsNullOrEmpty(text) && + (text.Contains("\\p1", StringComparison.Ordinal) || text.Contains("{assadraw-text ", StringComparison.Ordinal)); + } + /// /// A drag can end without a PointerReleased - the window loses activation, a flyout grabs the /// pointer, the device disappears. Undo change detection must not stay suppressed afterwards, diff --git a/tests/UI/Controls/AudioVisualizerDragTests.cs b/tests/UI/Controls/AudioVisualizerDragTests.cs index 4e636f5e367..b664c34a855 100644 --- a/tests/UI/Controls/AudioVisualizerDragTests.cs +++ b/tests/UI/Controls/AudioVisualizerDragTests.cs @@ -66,9 +66,9 @@ private static WavePeakData2 MakePeaks(int seconds) return new WavePeakData2(SampleRate, peaks); } - private static SubtitleLineViewModel Line(double startSeconds, double endSeconds) => new() + private static SubtitleLineViewModel Line(double startSeconds, double endSeconds, string text = "text") => new() { - Text = "text", + Text = text, StartTime = TimeSpan.FromSeconds(startSeconds), EndTime = TimeSpan.FromSeconds(endSeconds), }; @@ -100,6 +100,44 @@ private static WavePeakData2 MakePeaks(int seconds) return (window, av, list); } + [AvaloniaFact] + public void Move_DrawingLayersWithTheSameTimes_MoveTogether() + { + // A clip art is one ASSA drawing line per layer, all 1-3 s; dragging one moved only that layer + var a = Line(1, 3, "{\\p1}m 0 0 l 10 0 10 10{\\p0}"); + var b = Line(1, 3, "{\\p1}m 5 5 l 15 5 15 15{\\p0}"); + var text = Line(1, 3, "{\\an5\\pos(5,5)}{assadraw-text box=0,0,10,10}Hi"); + var other = Line(1, 3, "Dialogue with the same times"); + var (window, _, _) = Open(other, a, b, text); + + window.MouseDown(new Point(252, 100), MouseButton.Left, RawInputModifiers.None); + window.MouseMove(new Point(378, 100), RawInputModifiers.None); + Dispatcher.UIThread.RunJobs(); + window.MouseUp(new Point(378, 100), MouseButton.Left, RawInputModifiers.None); + + // +126 px = +1 s for every drawing layer; an ordinary line with the same times stays + Assert.All(new[] { a, b, text }, line => Assert.Equal(2, line.StartTime.TotalSeconds, 2)); + Assert.All(new[] { a, b, text }, line => Assert.Equal(4, line.EndTime.TotalSeconds, 2)); + Assert.Equal(1, other.StartTime.TotalSeconds, 2); + window.Close(); + } + + [AvaloniaFact] + public void Move_OrdinaryLinesWithTheSameTimes_MovesJustTheOne() + { + var a = Line(1, 3); + var b = Line(1, 3); + var (window, _, _) = Open(a, b); + + window.MouseDown(new Point(252, 100), MouseButton.Left, RawInputModifiers.None); + window.MouseMove(new Point(378, 100), RawInputModifiers.None); + Dispatcher.UIThread.RunJobs(); + window.MouseUp(new Point(378, 100), MouseButton.Left, RawInputModifiers.None); + + Assert.Single(new[] { a, b }, line => Math.Abs(line.StartTime.TotalSeconds - 2) < 0.01); + window.Close(); + } + [AvaloniaFact] public void ResizeRight_FollowsPointer_WhileViewIsStatic() { From 37d1bcd15c77b3b7a64dec436003faafd370fe13 Mon Sep 17 00:00:00 2001 From: niksedk Date: Fri, 16 Oct 2026 18:37:11 +0200 Subject: [PATCH 281/306] Waveform: ASSA lines with the same times move and resize together Not just drawing lines: in ASSA, lines with exactly the same times (the layers of a drawing, a sign in several lines) belong together, so a drag moves all of them and an edge drag resizes all of them. They are not neighbours a drag stops at. Other formats keep moving one line. Co-Authored-By: Claude Opus 5.5 --- .../AudioVisualizerControl/AudioVisualizer.cs | 65 ++++++++++++++----- src/ui/Features/Main/Layout/InitWaveform.cs | 1 + tests/UI/Controls/AudioVisualizerDragTests.cs | 44 +++++++++---- 3 files changed, 82 insertions(+), 28 deletions(-) diff --git a/src/ui/Controls/AudioVisualizerControl/AudioVisualizer.cs b/src/ui/Controls/AudioVisualizerControl/AudioVisualizer.cs index 4b7375ed9be..cdf19e485dd 100644 --- a/src/ui/Controls/AudioVisualizerControl/AudioVisualizer.cs +++ b/src/ui/Controls/AudioVisualizerControl/AudioVisualizer.cs @@ -380,8 +380,15 @@ private int FindNeighborIndex(List paragraphs, int index, var isSameTrack = IsSameTrack; var paragraph = paragraphs[index]; + var stack = _dragStack; for (var i = index + step; i >= 0 && i < paragraphs.Count; i += step) { + // Lines dragged along are not neighbours to stop at + if (stack != null && stack.Contains(paragraphs[i])) + { + continue; + } + if (isSameTrack == null || isSameTrack(paragraph, paragraphs[i])) { return i; @@ -845,6 +852,8 @@ private void OnTapped(object? sender, TappedEventArgs e) } _activeParagraph = null; + + _dragStack = null; return; } @@ -1114,6 +1123,7 @@ private void OnPointerReleased(object? sender, PointerReleasedEventArgs e) _interactionMode = InteractionMode.None; _activeParagraph = null; + _dragStack = null; } var showContextMenu = false; @@ -1158,6 +1168,7 @@ private void OnPointerReleased(object? sender, PointerReleasedEventArgs e) { _interactionMode = InteractionMode.None; _activeParagraph = null; + _dragStack = null; return; } } @@ -1172,6 +1183,8 @@ private void OnPointerReleased(object? sender, PointerReleasedEventArgs e) // } _activeParagraph = null; + + _dragStack = null; return; } @@ -1204,6 +1217,8 @@ private void OnPointerReleased(object? sender, PointerReleasedEventArgs e) } _activeParagraph = null; + + _dragStack = null; return; } } @@ -1219,6 +1234,7 @@ private void OnPointerReleased(object? sender, PointerReleasedEventArgs e) _interactionMode = InteractionMode.None; _activeParagraph = null; + _dragStack = null; return; } @@ -1245,6 +1261,7 @@ InteractionMode.ResizeLeftAnd or _interactionMode = InteractionMode.None; _activeParagraph = null; + _dragStack = null; _selectionMoveSnapshot.Clear(); InvalidateVisual(); @@ -1314,6 +1331,7 @@ private void OnPointerPressed(object? sender, PointerPressedEventArgs e) _originalStartSeconds = p.StartTime.TotalSeconds; _originalEndSeconds = p.EndTime.TotalSeconds; _originalDurationSeconds = p.Duration.TotalSeconds; + _dragStack = GetSameTimeStack(p); var displayableParagraphs = _displayableParagraphs; if (displayableParagraphs.Count == 0) @@ -1411,10 +1429,10 @@ private void OnPointerPressed(object? sender, PointerPressedEventArgs e) _interactionMode = InteractionMode.MovingSelection; } - else if (GetDrawingStack(p) is { Count: > 1 } stack) + else if (_dragStack is { } stack) { - // An ASSA drawing (clip art) is one line per layer, all with the same times: - // dragging one moved just that layer out of the picture + // ASSA lines with the same times move together - a drawing (clip art) is one line + // per layer, and dragging one moved just that layer out of the picture _selectionMoveSnapshot.Clear(); foreach (var layer in stack) { @@ -1437,13 +1455,20 @@ private void OnPointerPressed(object? sender, PointerPressedEventArgs e) } /// - /// The ASSA drawing lines (\p1 drawings and ASSA draw text) with exactly the times of - /// , itself included - the layers of one drawing; null when it is - /// not a drawing line. + /// True when lines with exactly the same times belong together (ASSA: a drawing is one line + /// per layer, a sign often several lines) - a drag on one then moves or resizes all of them. /// - private List? GetDrawingStack(SubtitleLineViewModel paragraph) + internal Func? MovesSameTimeLinesTogether { get; set; } + + /// + /// The lines dragged along with the active one: the lines with exactly its times, itself + /// included; null for a drag of one line. + /// + private List? _dragStack; + + private List? GetSameTimeStack(SubtitleLineViewModel paragraph) { - if (!IsDrawingLine(paragraph)) + if (MovesSameTimeLinesTogether?.Invoke() != true) { return null; } @@ -1451,20 +1476,13 @@ private void OnPointerPressed(object? sender, PointerPressedEventArgs e) var stack = new List(); foreach (var p in _displayableParagraphs) { - if (p.StartTime == paragraph.StartTime && p.EndTime == paragraph.EndTime && IsDrawingLine(p)) + if (p.StartTime == paragraph.StartTime && p.EndTime == paragraph.EndTime) { stack.Add(p); } } - return stack; - } - - private static bool IsDrawingLine(SubtitleLineViewModel paragraph) - { - var text = paragraph.Text; - return !string.IsNullOrEmpty(text) && - (text.Contains("\\p1", StringComparison.Ordinal) || text.Contains("{assadraw-text ", StringComparison.Ordinal)); + return stack.Count > 1 ? stack : null; } /// @@ -1927,6 +1945,19 @@ private void OnPointerMoved(object? sender, PointerEventArgs e) break; } + // An edge drag resizes the lines with the same times along with it + if (_dragStack != null && _activeParagraph != null && _dragStack.Contains(_activeParagraph) && + _interactionMode is InteractionMode.ResizingLeft or InteractionMode.ResizingRight) + { + foreach (var line in _dragStack) + { + if (line != _activeParagraph) + { + line.SetTimes(_activeParagraph.StartTime, _activeParagraph.EndTime); + } + } + } + // Past the early returns above, every remaining interaction mode has just rewritten // the active paragraph's times - see IsEditingWithPointer. _lastPointerEditMs = Environment.TickCount64; diff --git a/src/ui/Features/Main/Layout/InitWaveform.cs b/src/ui/Features/Main/Layout/InitWaveform.cs index 071dab839e0..ca2e14acdfd 100644 --- a/src/ui/Features/Main/Layout/InitWaveform.cs +++ b/src/ui/Features/Main/Layout/InitWaveform.cs @@ -93,6 +93,7 @@ internal static Grid MakeWaveform(MainViewModel vm, bool withTimelineTracks, out vm.AudioVisualizer.Height = double.NaN; // Auto height vm.AudioVisualizer.WaveformDrawStyle = GetWaveformDrawStyle(settings.WaveformDrawStyle); vm.AudioVisualizer.MinGapSeconds = Se.Settings.General.MinimumBetweenLines.GetMilliseconds() / 1000.0; + vm.AudioVisualizer.MovesSameTimeLinesTogether = () => vm.IsFormatAssa; vm.AudioVisualizer.FocusOnMouseOver = settings.FocusOnMouseOver; vm.AudioVisualizer.IsReadOnly = Se.Settings.General.LockTimeCodes; vm.AudioVisualizer.WaveformHeightPercentage = settings.SpectrogramCombinedWaveformHeight; diff --git a/tests/UI/Controls/AudioVisualizerDragTests.cs b/tests/UI/Controls/AudioVisualizerDragTests.cs index b664c34a855..1d943f5d0a0 100644 --- a/tests/UI/Controls/AudioVisualizerDragTests.cs +++ b/tests/UI/Controls/AudioVisualizerDragTests.cs @@ -101,29 +101,51 @@ private static WavePeakData2 MakePeaks(int seconds) } [AvaloniaFact] - public void Move_DrawingLayersWithTheSameTimes_MoveTogether() + public void Assa_Move_LinesWithTheSameTimes_MoveTogether() { // A clip art is one ASSA drawing line per layer, all 1-3 s; dragging one moved only that layer var a = Line(1, 3, "{\\p1}m 0 0 l 10 0 10 10{\\p0}"); var b = Line(1, 3, "{\\p1}m 5 5 l 15 5 15 15{\\p0}"); - var text = Line(1, 3, "{\\an5\\pos(5,5)}{assadraw-text box=0,0,10,10}Hi"); - var other = Line(1, 3, "Dialogue with the same times"); - var (window, _, _) = Open(other, a, b, text); + var sign = Line(1, 3, "A sign with the same times"); + var neighbour = Line(3.5, 5); + var (window, av, _) = Open(a, b, sign, neighbour); + av.MovesSameTimeLinesTogether = () => true; window.MouseDown(new Point(252, 100), MouseButton.Left, RawInputModifiers.None); - window.MouseMove(new Point(378, 100), RawInputModifiers.None); + window.MouseMove(new Point(315, 100), RawInputModifiers.None); Dispatcher.UIThread.RunJobs(); - window.MouseUp(new Point(378, 100), MouseButton.Left, RawInputModifiers.None); + window.MouseUp(new Point(315, 100), MouseButton.Left, RawInputModifiers.None); + + // +63 px = +0.5 s for the whole stack; the line after it is not part of it + Assert.All(new[] { a, b, sign }, line => Assert.Equal(1.5, line.StartTime.TotalSeconds, 2)); + Assert.All(new[] { a, b, sign }, line => Assert.Equal(3.5, line.EndTime.TotalSeconds, 2)); + Assert.Equal(3.5, neighbour.StartTime.TotalSeconds, 2); + window.Close(); + } + + [AvaloniaTheory] + [InlineData(126, 1.5, 3)] // left edge at x = 1 s, +63 px -> 1.5 s + [InlineData(378, 1, 3.5)] // right edge at x = 3 s, +63 px -> 3.5 s + public void Assa_EdgeDrag_ResizesTheLinesWithTheSameTimes(double edgeX, double expectedStart, double expectedEnd) + { + var a = Line(1, 3); + var b = Line(1, 3); + var c = Line(1, 3); + var (window, av, _) = Open(a, b, c); + av.MovesSameTimeLinesTogether = () => true; + + window.MouseDown(new Point(edgeX, 100), MouseButton.Left, RawInputModifiers.None); + window.MouseMove(new Point(edgeX + 63, 100), RawInputModifiers.None); + Dispatcher.UIThread.RunJobs(); + window.MouseUp(new Point(edgeX + 63, 100), MouseButton.Left, RawInputModifiers.None); - // +126 px = +1 s for every drawing layer; an ordinary line with the same times stays - Assert.All(new[] { a, b, text }, line => Assert.Equal(2, line.StartTime.TotalSeconds, 2)); - Assert.All(new[] { a, b, text }, line => Assert.Equal(4, line.EndTime.TotalSeconds, 2)); - Assert.Equal(1, other.StartTime.TotalSeconds, 2); + Assert.All(new[] { a, b, c }, line => Assert.Equal(expectedStart, line.StartTime.TotalSeconds, 2)); + Assert.All(new[] { a, b, c }, line => Assert.Equal(expectedEnd, line.EndTime.TotalSeconds, 2)); window.Close(); } [AvaloniaFact] - public void Move_OrdinaryLinesWithTheSameTimes_MovesJustTheOne() + public void NotAssa_Move_LinesWithTheSameTimes_MovesJustTheOne() { var a = Line(1, 3); var b = Line(1, 3); From b07797988e6f10f4efc6e5d1d818240e9f785611 Mon Sep 17 00:00:00 2001 From: niksedk Date: Fri, 16 Oct 2026 18:50:10 +0200 Subject: [PATCH 282/306] ASSA draw: loop animations and animated clip art A Loop effect between the in and the out effect: spin, pulse, blink, bounce, shake or swing. ASSA has no loops, so it is written as one \t per cycle, fitted to whole cycles between in and out. Spin and shake turn around the anchor, swing around the top edge, and bounce lifts the line by turning it around an \org far away; a loop needing another \org than the in/out effect is left out (with a hint). Blink switches the scale, so translucent colors keep their alpha. The panel gets an In / Loop / Out switch with animated loop thumbnails. Six clip arts come with a loop: loading spinner, REC (the dot blinks), ringing phone, beating heart, bouncing arrow and gear. Co-Authored-By: Claude Opus 5.5 --- .../Assa/AssaDraw/AssaDrawAnimationPanel.cs | 129 +++++++-- .../AssaDraw/AssaDrawViewModel.Animation.cs | 75 +++++- .../Assa/AssaDraw/AssaDrawViewModel.cs | 22 +- .../Features/Assa/AssaDraw/DrawAnimation.cs | 247 ++++++++++++++++-- src/ui/Features/Assa/AssaDraw/ShapeLibrary.cs | 33 ++- .../Config/Language/Assa/LanguageAssa.cs | 26 ++ .../Features/Assa/AssaDrawAnimationTests.cs | 86 ++++++ .../UI/Features/Assa/AssaDrawClipArtTests.cs | 2 +- 8 files changed, 563 insertions(+), 57 deletions(-) diff --git a/src/ui/Features/Assa/AssaDraw/AssaDrawAnimationPanel.cs b/src/ui/Features/Assa/AssaDraw/AssaDrawAnimationPanel.cs index ca511469407..4a346bf7d47 100644 --- a/src/ui/Features/Assa/AssaDraw/AssaDrawAnimationPanel.cs +++ b/src/ui/Features/Assa/AssaDraw/AssaDrawAnimationPanel.cs @@ -11,6 +11,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.Linq; using static Nikse.SubtitleEdit.Logic.EditorChrome; namespace Nikse.SubtitleEdit.Features.Assa.AssaDraw; @@ -27,15 +28,18 @@ public static Border Create(AssaDrawViewModel vm) { var rows = new StackPanel { Spacing = 8, [!StackPanel.IsVisibleProperty] = new Binding(nameof(vm.IsAnimationExpanded)) }; - var inOut = new Grid { ColumnDefinitions = new ColumnDefinitions("*,*"), ColumnSpacing = 4 }; - var inButton = MakeSegment(Se.Language.Assa.DrawAnimationIn, vm.EditInEffectCommand, isOut: false); - var outButton = MakeSegment(Se.Language.Assa.DrawAnimationOut, vm.EditOutEffectCommand, isOut: true); - Grid.SetColumn(outButton, 1); + var inOut = new Grid { ColumnDefinitions = new ColumnDefinitions("*,*,*"), ColumnSpacing = 4 }; + var inButton = MakeSegment(Se.Language.Assa.DrawAnimationIn, vm.EditInEffectCommand, Part.In); + var loopButton = MakeSegment(Se.Language.Assa.DrawAnimationLoop, vm.EditLoopEffectCommand, Part.Loop); + var outButton = MakeSegment(Se.Language.Assa.DrawAnimationOut, vm.EditOutEffectCommand, Part.Out); + Grid.SetColumn(loopButton, 1); + Grid.SetColumn(outButton, 2); inOut.Children.Add(inButton); + inOut.Children.Add(loopButton); inOut.Children.Add(outButton); rows.Children.Add(inOut); - var gallery = new UniformGrid { Columns = 3 }; + var gallery = new UniformGrid { Columns = 3, [!UniformGrid.IsVisibleProperty] = new Binding(nameof(vm.IsEditingLoop)) { Converter = new FuncValueConverter(loop => !loop) } }; foreach (var effect in Effects) { gallery.Children.Add(MakeTile(vm, effect)); @@ -43,6 +47,14 @@ public static Border Create(AssaDrawViewModel vm) rows.Children.Add(gallery); + var loopGallery = new UniformGrid { Columns = 3, [!UniformGrid.IsVisibleProperty] = new Binding(nameof(vm.IsEditingLoop)) }; + foreach (var effect in Enum.GetValues()) + { + loopGallery.Children.Add(MakeLoopTile(vm, effect)); + } + + rows.Children.Add(loopGallery); + var duration = MakeField(nameof(vm.EditedEffectMs), 1, 60_000, prefix: "ms"); duration.Increment = 50; AutomationProperties.SetName(duration, Se.Language.General.Duration); @@ -75,6 +87,15 @@ public static Border Create(AssaDrawViewModel vm) rows.Children.Add(MakePropertyRow(Se.Language.General.Duration, duration, play)); + rows.Children.Add(new TextBlock + { + Text = Se.Language.Assa.DrawAnimationLoopBlocked, + FontSize = 11.5, + TextWrapping = TextWrapping.Wrap, + Foreground = new SolidColorBrush(Color.FromRgb(230, 160, 40)), + [!TextBlock.IsVisibleProperty] = new Binding(nameof(vm.IsLoopBlocked)), + }); + rows.Children.Add(new TextBlock { Text = Se.Language.Assa.DrawAnimationLayerHint, @@ -143,8 +164,28 @@ private static Button MakeHeader(AssaDrawViewModel vm) return header; } - private static Button MakeSegment(string text, System.Windows.Input.ICommand command, bool isOut) + private enum Part + { + In, + Loop, + Out, + } + + private static Button MakeSegment(string text, System.Windows.Input.ICommand command, Part part) { + // Selected when the loop flag and the out flag say this part + bool IsSelected(IReadOnlyList values) => + values is [bool loop, bool isOut] && (part == Part.Loop ? loop : !loop && isOut == (part == Part.Out)); + MultiBinding Selected(Func pick) => new() + { + Bindings = + { + new Binding(nameof(AssaDrawViewModel.IsEditingLoop)), + new Binding(nameof(AssaDrawViewModel.IsEditingOutEffect)), + }, + Converter = new FuncMultiValueConverter(values => pick(IsSelected(values.ToList()))), + }; + var button = new Button { Content = text, @@ -155,21 +196,66 @@ private static Button MakeSegment(string text, System.Windows.Input.ICommand com FontSize = 12, CornerRadius = new CornerRadius(6), BorderThickness = new Thickness(1), - [!Button.BackgroundProperty] = new Binding(nameof(AssaDrawViewModel.IsEditingOutEffect)) + [!Button.BackgroundProperty] = Selected(selected => selected ? AccentSoftBrush : Brushes.Transparent), + [!Button.BorderBrushProperty] = Selected(selected => selected ? AccentBrush : SeparatorBrush), + [!Button.FontWeightProperty] = Selected(selected => selected ? FontWeight.SemiBold : FontWeight.Normal), + }; + AutomationProperties.SetName(button, text); + return button; + } + + private static Button MakeLoopTile(AssaDrawViewModel vm, DrawLoopEffect effect) + { + var thumbnail = new AnimationThumbnail { LoopEffect = effect, Width = 64, Height = 36 }; + var name = GetLoopName(effect); + var label = new TextBlock + { + Text = name, + FontSize = 10.5, + HorizontalAlignment = HorizontalAlignment.Center, + TextTrimming = TextTrimming.CharacterEllipsis, + Foreground = DimTextBrush, + }; + var content = new StackPanel { Spacing = 2 }; + content.Children.Add(thumbnail); + content.Children.Add(label); + var tile = new Button + { + Content = content, + Command = vm.PickLoopEffectCommand, + CommandParameter = effect, + Padding = new Thickness(3), + Margin = new Thickness(2), + HorizontalAlignment = HorizontalAlignment.Stretch, + HorizontalContentAlignment = HorizontalAlignment.Center, + CornerRadius = new CornerRadius(6), + BorderThickness = new Thickness(1.5), + [!Button.BorderBrushProperty] = new Binding(nameof(vm.AnimationLoop)) { - Converter = new FuncValueConverter(editingOut => editingOut == isOut ? AccentSoftBrush : Brushes.Transparent), + Converter = new FuncValueConverter(e => e == effect ? AccentBrush : Brushes.Transparent), }, - [!Button.BorderBrushProperty] = new Binding(nameof(AssaDrawViewModel.IsEditingOutEffect)) + [!Button.BackgroundProperty] = new Binding(nameof(vm.AnimationLoop)) { - Converter = new FuncValueConverter(editingOut => editingOut == isOut ? AccentBrush : SeparatorBrush), + Converter = new FuncValueConverter(e => e == effect ? AccentSoftBrush : Brushes.Transparent), }, }; - AutomationProperties.SetName(button, text); - button.Bind(Button.FontWeightProperty, new Binding(nameof(AssaDrawViewModel.IsEditingOutEffect)) + AutomationProperties.SetName(tile, name); + return tile; + } + + public static string GetLoopName(DrawLoopEffect effect) + { + var l = Se.Language.Assa; + return effect switch { - Converter = new FuncValueConverter(editingOut => editingOut == isOut ? FontWeight.SemiBold : FontWeight.Normal), - }); - return button; + DrawLoopEffect.Spin => l.DrawAnimationSpin, + DrawLoopEffect.Pulse => l.DrawAnimationPulse, + DrawLoopEffect.Blink => l.DrawAnimationBlink, + DrawLoopEffect.Bounce => l.DrawAnimationBounce, + DrawLoopEffect.Shake => l.DrawAnimationShake, + DrawLoopEffect.Swing => l.DrawAnimationSwing, + _ => l.DrawAnimationNone, + }; } private static Button MakeTile(AssaDrawViewModel vm, DrawAnimationEffect effect) @@ -257,6 +343,11 @@ internal sealed class AnimationThumbnail : Control public DrawAnimationEffect Effect { get; set; } + /// + /// Shows this loop instead of the in/out effect. + /// + public DrawLoopEffect? LoopEffect { get; set; } + public bool IsOut { get => GetValue(IsOutProperty); @@ -310,9 +401,11 @@ public override void Render(DrawingContext context) var height = (float)Bounds.Height; context.FillRectangle(FrameBrush, new Rect(0, 0, width, height), 4); - var animation = IsOut - ? new DrawAnimation { Out = Effect, OutMs = EffectMs } - : new DrawAnimation { In = Effect, InMs = EffectMs }; + var animation = LoopEffect is { } loop + ? new DrawAnimation { Loop = loop, LoopMs = loop == DrawLoopEffect.Shake ? 400 : 900 } + : IsOut + ? new DrawAnimation { Out = Effect, OutMs = EffectMs } + : new DrawAnimation { In = Effect, InMs = EffectMs }; var time = Clock.Elapsed.TotalMilliseconds % LoopMs; var radius = height * 0.36f; var cx = width / 2; diff --git a/src/ui/Features/Assa/AssaDraw/AssaDrawViewModel.Animation.cs b/src/ui/Features/Assa/AssaDraw/AssaDrawViewModel.Animation.cs index 57c12ee419b..0ee40f051e8 100644 --- a/src/ui/Features/Assa/AssaDraw/AssaDrawViewModel.Animation.cs +++ b/src/ui/Features/Assa/AssaDraw/AssaDrawViewModel.Animation.cs @@ -10,7 +10,7 @@ namespace Nikse.SubtitleEdit.Features.Assa.AssaDraw; /// -/// In/out effects of the selected layers (a group's layers move as one) and playing them on the canvas. +/// In, loop and out effects of the selected layers (a group's layers move as one) and playing them on the canvas. /// public partial class AssaDrawViewModel { @@ -23,6 +23,18 @@ public partial class AssaDrawViewModel [ObservableProperty] private DrawAnimationEffect _animationOut; [ObservableProperty] private int _animationInMs = DrawAnimation.DefaultDurationMs; [ObservableProperty] private int _animationOutMs = DrawAnimation.DefaultDurationMs; + [ObservableProperty] private DrawLoopEffect _animationLoop; + [ObservableProperty] private int _animationLoopMs = DrawAnimation.DefaultLoopMs; + + /// + /// The effect gallery edits the loop (else the in or out effect). + /// + [ObservableProperty] private bool _isEditingLoop; + + /// + /// The loop needs another \org than the in/out effect, so it is left out. + /// + [ObservableProperty] private bool _isLoopBlocked; /// /// The effect gallery edits the out effect (false: the in effect). @@ -59,10 +71,14 @@ public DrawAnimationEffect EditedEffect public int EditedEffectMs { - get => IsEditingOutEffect ? AnimationOutMs : AnimationInMs; + get => IsEditingLoop ? AnimationLoopMs : IsEditingOutEffect ? AnimationOutMs : AnimationInMs; set { - if (IsEditingOutEffect) + if (IsEditingLoop) + { + AnimationLoopMs = value; + } + else if (IsEditingOutEffect) { AnimationOutMs = value; } @@ -93,10 +109,28 @@ internal List GetAnimationShapes() private void PickEffect(DrawAnimationEffect effect) => EditedEffect = effect; [RelayCommand] - private void EditInEffect() => IsEditingOutEffect = false; + private void PickLoopEffect(DrawLoopEffect effect) => AnimationLoop = effect; + + [RelayCommand] + private void EditInEffect() + { + IsEditingLoop = false; + IsEditingOutEffect = false; + } [RelayCommand] - private void EditOutEffect() => IsEditingOutEffect = true; + private void EditLoopEffect() + { + IsEditingOutEffect = false; + IsEditingLoop = true; + } + + [RelayCommand] + private void EditOutEffect() + { + IsEditingLoop = false; + IsEditingOutEffect = true; + } private void RefreshAnimationFields() { @@ -110,6 +144,8 @@ private void RefreshAnimationFields() AnimationOut = animation.Out; AnimationInMs = animation.InMs; AnimationOutMs = animation.OutMs; + AnimationLoop = animation.Loop; + AnimationLoopMs = animation.LoopMs; } finally { @@ -127,12 +163,18 @@ private void RefreshAnimationFields() partial void OnAnimationOutMsChanged(int value) => ApplyAnimation("animation-out-ms"); + partial void OnAnimationLoopChanged(DrawLoopEffect value) => ApplyAnimation("animation-loop"); + + partial void OnAnimationLoopMsChanged(int value) => ApplyAnimation("animation-loop-ms"); + partial void OnIsEditingOutEffectChanged(bool value) { OnPropertyChanged(nameof(EditedEffect)); OnPropertyChanged(nameof(EditedEffectMs)); } + partial void OnIsEditingLoopChanged(bool value) => OnPropertyChanged(nameof(EditedEffectMs)); + private void ApplyAnimation(string undoKey) { OnPropertyChanged(nameof(EditedEffect)); @@ -151,6 +193,8 @@ private void ApplyAnimation(string undoKey) Out = AnimationOut, InMs = Math.Clamp(AnimationInMs, 1, 60_000), OutMs = Math.Clamp(AnimationOutMs, 1, 60_000), + Loop = AnimationLoop, + LoopMs = Math.Clamp(AnimationLoopMs, 50, 60_000), }; if (shapes.Any(s => !Equals(s.Animation ?? new DrawAnimation(), animation))) @@ -181,9 +225,24 @@ private void ApplyAnimation(string undoKey) private void UpdateAnimationState() { - AnimationSummary = AnimationIn == DrawAnimationEffect.None && AnimationOut == DrawAnimationEffect.None - ? string.Empty - : AssaDrawAnimationPanel.GetName(AnimationIn, isOut: false) + " · " + AssaDrawAnimationPanel.GetName(AnimationOut, isOut: true); + var parts = new List(); + if (AnimationIn != DrawAnimationEffect.None) + { + parts.Add(AssaDrawAnimationPanel.GetName(AnimationIn, isOut: false)); + } + + if (AnimationLoop != DrawLoopEffect.None) + { + parts.Add(AssaDrawAnimationPanel.GetLoopName(AnimationLoop)); + } + + if (AnimationOut != DrawAnimationEffect.None) + { + parts.Add(AssaDrawAnimationPanel.GetName(AnimationOut, isOut: true)); + } + + AnimationSummary = string.Join(" · ", parts); + IsLoopBlocked = new DrawAnimation { In = AnimationIn, Out = AnimationOut, Loop = AnimationLoop }.IsLoopBlocked; HasAnyAnimation = Shapes.Any(s => s.Animation is { IsNone: false }); if (!HasAnyAnimation && IsPlayingAnimation) { diff --git a/src/ui/Features/Assa/AssaDraw/AssaDrawViewModel.cs b/src/ui/Features/Assa/AssaDraw/AssaDrawViewModel.cs index e43393a23c8..bc4612b2e12 100644 --- a/src/ui/Features/Assa/AssaDraw/AssaDrawViewModel.cs +++ b/src/ui/Features/Assa/AssaDraw/AssaDrawViewModel.cs @@ -422,6 +422,7 @@ public void InsertLibraryShape(CanvasInsertEventArgs e) shape.TextColor = ContrastColor(shape.ForeColor); shape.FontSize = DefaultFontSize(20f); shape.GroupId = groupId; + shape.Animation = item.AnimatedColor is not { } animated || shape.ForeColor == animated ? item.Animation?.Clone() : null; } Shapes.AddRange(shapes); @@ -2811,18 +2812,17 @@ private Subtitle GenerateSubtitle(bool includeAll = false, bool hiddenAsComments if (finalText.Length > 0 && animation != null) { + // A drawing turns around its \\pos (the anchor) unless the animation needs another \\org var origin = string.Empty; - double outSlideDegrees = 0; - if (animation.IsDoubleSlide) + if (animation.OriginKind != DrawAnimationOrigin.Anchor && + animation.GetOrigin(anchor.X, anchor.Y, anchor.Bounds, CanvasWidth, CanvasHeight) is { } org) { - var (originX, originY, degrees) = animation.GetOutSlideRotation(anchor.X, anchor.Y, anchor.Bounds, CanvasWidth, CanvasHeight); - origin = $"\\org({FormatCoordinate(originX)},{FormatCoordinate(originY)})"; - outSlideDegrees = degrees; + origin = $"\\org({FormatCoordinate(org.X)},{FormatCoordinate(org.Y)})"; } finalText.Insert(0, "{\\an7" + animation.GetPositionTag(anchor.X, anchor.Y, LineDurationMs, anchor.Bounds, CanvasWidth, CanvasHeight) + - origin + animation.GetEffectTags(LineDurationMs, 0, outSlideDegrees) + "}" + + origin + animation.GetEffectTags(LineDurationMs, 0, anchor.Bounds, CanvasWidth, CanvasHeight) + "}" + animation.FormatMarker(anchor.X, anchor.Y)); } @@ -2894,7 +2894,7 @@ private Subtitle GenerateSubtitle(bool includeAll = false, bool hiddenAsComments /// /// A text line follows its shape's animation: it turns around the same \org as its drawing (the - /// anchor, or a double slide's far one), so its position is written to allow for its own \frz + /// anchor, its top edge, or a far one), so its position is written to allow for its own \frz /// turning around that point; \move instead of \pos for a slide. The marker and the effects go /// after its tag block. /// @@ -2905,11 +2905,7 @@ private string AddTextAnimation(string textLine, DrawShape shape, DrawAnimation // The \frz as the text line writes it (two decimals) - a far \org magnifies any difference var baseFrz = (float)Math.Round(-shape.GetTextFrame().Angle, 2); - double originX = anchor.X, originY = anchor.Y, outSlideDegrees = 0; - if (animation.IsDoubleSlide) - { - (originX, originY, outSlideDegrees) = animation.GetOutSlideRotation(anchor.X, anchor.Y, anchor.Bounds, CanvasWidth, CanvasHeight); - } + var (originX, originY) = animation.GetOrigin(anchor.X, anchor.Y, anchor.Bounds, CanvasWidth, CanvasHeight) ?? (anchor.X, anchor.Y); var position = animation.GetPositionTag((left + right) / 2, (top + bottom) / 2, LineDurationMs, anchor.Bounds, CanvasWidth, CanvasHeight, (x, y) => DrawAnimation.MapAroundOrigin(x, y, originX, originY, baseFrz)); @@ -2921,7 +2917,7 @@ private string AddTextAnimation(string textLine, DrawShape shape, DrawAnimation } var blockEnd = textLine.IndexOf('}'); - var effects = $"{{\\org({FormatCoordinate(originX)},{FormatCoordinate(originY)})" + animation.GetEffectTags(LineDurationMs, baseFrz, outSlideDegrees) + "}"; + var effects = $"{{\\org({FormatCoordinate(originX)},{FormatCoordinate(originY)})" + animation.GetEffectTags(LineDurationMs, baseFrz, anchor.Bounds, CanvasWidth, CanvasHeight) + "}"; return textLine[..(blockEnd + 1)] + animation.FormatMarker(anchor.X, anchor.Y) + effects + textLine[(blockEnd + 1)..]; } diff --git a/src/ui/Features/Assa/AssaDraw/DrawAnimation.cs b/src/ui/Features/Assa/AssaDraw/DrawAnimation.cs index 8cb24c1d2c8..44516a9d2f2 100644 --- a/src/ui/Features/Assa/AssaDraw/DrawAnimation.cs +++ b/src/ui/Features/Assa/AssaDraw/DrawAnimation.cs @@ -21,6 +21,34 @@ public enum DrawAnimationEffect Bottom, } +/// +/// A repeating effect between the in and the out effect. ASSA has no loops, so it is written as +/// one \t per cycle for the length of the line. +/// +public enum DrawLoopEffect +{ + None, + Spin, + Pulse, + Blink, + Bounce, + Shake, + Swing, +} + +/// +/// The \org an animation turns around: none, the anchor (spin, shake), the top edge (swing), or one +/// far away (a slide out after a slide in, a bounce) - a line has one \org. +/// +public enum DrawAnimationOrigin +{ + None, + Anchor, + Top, + FarOut, + FarBounce, +} + /// /// Where a layer (or a group's layers) is at a moment of its animation: moved by Dx/Dy, scaled and /// turned around the anchor, faded to Opacity. Rotation is degrees clockwise on screen. @@ -31,7 +59,7 @@ public readonly record struct DrawAnimationState(float Dx, float Dy, float Scale } /// -/// The in and out effect of a line. It is written as ASSA override tags (\move, \fad, \t with \fscx, +/// The in, loop and out effect of a line. It is written as ASSA override tags (\move, \fad, \t with \fscx, /// \fscy and \frz) around an anchor - the center of the layer or of its group, so a group scales and /// turns as one - and kept in an {assadraw-anim ...} comment block so the editor can open it again. /// @@ -47,10 +75,69 @@ public sealed class DrawAnimation : IEquatable public int InMs { get; set; } = DefaultDurationMs; public DrawAnimationEffect Out { get; set; } public int OutMs { get; set; } = DefaultDurationMs; + public DrawLoopEffect Loop { get; set; } + + /// + /// Length of one cycle of the loop; fitted so whole cycles fill the time between in and out. + /// + public int LoopMs { get; set; } = DefaultLoopMs; + + public const int DefaultLoopMs = 1000; + private const float PulseScale = 1.1f; + private const float ShakeDegrees = 8; + private const float SwingDegrees = 14; + + public bool IsNone => In == DrawAnimationEffect.None && Out == DrawAnimationEffect.None && Loop == DrawLoopEffect.None; + + public DrawAnimation Clone() => new() { In = In, InMs = InMs, Out = Out, OutMs = OutMs, Loop = Loop, LoopMs = LoopMs }; - public bool IsNone => In == DrawAnimationEffect.None && Out == DrawAnimationEffect.None; + private DrawAnimationOrigin InOutOrigin => + IsDoubleSlide ? DrawAnimationOrigin.FarOut : + In == DrawAnimationEffect.Spin || Out == DrawAnimationEffect.Spin ? DrawAnimationOrigin.Anchor : + DrawAnimationOrigin.None; - public DrawAnimation Clone() => new() { In = In, InMs = InMs, Out = Out, OutMs = OutMs }; + private DrawAnimationOrigin LoopOrigin => Loop switch + { + DrawLoopEffect.Spin or DrawLoopEffect.Shake => DrawAnimationOrigin.Anchor, + DrawLoopEffect.Swing => DrawAnimationOrigin.Top, + DrawLoopEffect.Bounce => DrawAnimationOrigin.FarBounce, + _ => DrawAnimationOrigin.None, + }; + + /// + /// The loop turns around another \org than the in/out effect (a swing with a spin in, a bounce + /// with two slides) - a line has only one, so the loop is left out. + /// + public bool IsLoopBlocked => LoopOrigin != DrawAnimationOrigin.None && InOutOrigin != DrawAnimationOrigin.None && LoopOrigin != InOutOrigin; + + public DrawLoopEffect EffectiveLoop => IsLoopBlocked ? DrawLoopEffect.None : Loop; + + /// + /// The \org the line needs. + /// + public DrawAnimationOrigin OriginKind => InOutOrigin != DrawAnimationOrigin.None ? InOutOrigin : IsLoopBlocked ? DrawAnimationOrigin.None : LoopOrigin; + + /// + /// The loop's time (start, end) and whole cycles between the in and the out effect; 0 cycles for none. + /// + public (int Start, int End, int Cycles) GetLoopWindow(int lineMs) + { + var (inMs, outMs) = GetDurations(lineMs); + var start = inMs; + var end = lineMs - outMs; + if (EffectiveLoop == DrawLoopEffect.None || end - start < 2) + { + return (start, end, 0); + } + + return (start, end, Math.Clamp((int)Math.Round((end - start) / (double)Math.Max(10, LoopMs)), 1, 2000)); + } + + /// + /// How high a bounce goes: a tenth of the height, at least 8 px. + /// + private static float GetBounceHeight((float Left, float Top, float Right, float Bottom) bounds) => + Math.Max(8, (bounds.Bottom - bounds.Top) * 0.1f); public static bool IsSlide(DrawAnimationEffect effect) => effect >= DrawAnimationEffect.Left; @@ -154,6 +241,54 @@ public DrawAnimationState GetState(double timeMs, int lineMs, (float Left, float } } + else + { + var (loopStart, loopEnd, cycles) = GetLoopWindow(lineMs); + if (cycles > 0 && timeMs >= loopStart && timeMs <= loopEnd) + { + var period = (loopEnd - loopStart) / (double)cycles; + var u = (timeMs - loopStart) / period; + u -= Math.Floor(u); + switch (EffectiveLoop) + { + case DrawLoopEffect.Spin: + rotation = (float)(-360 * u); + break; + case DrawLoopEffect.Pulse: + scale = 1 + (PulseScale - 1) * (float)(u < 0.5 ? u * 2 : 2 - u * 2); + break; + case DrawLoopEffect.Blink: + scale = u < 0.5 ? 1 : 0; + break; + case DrawLoopEffect.Bounce: + // Up easing out, down easing in, like the \t accelerations + var height = u < 0.5 ? Math.Pow(u * 2, EaseOut) : 1 - Math.Pow(u * 2 - 1, EaseIn); + dy = -GetBounceHeight(bounds) * (float)height; + break; + case DrawLoopEffect.Shake: + rotation = ShakeDegrees * (float)(u < 0.25 ? u * 4 : u < 0.75 ? 1 - (u - 0.25) * 4 : (u - 0.75) * 4 - 1); + break; + case DrawLoopEffect.Swing: + var quarter = u * 4 - Math.Floor(u * 4); + var swing = (u * 4) switch + { + < 1 => Math.Pow(quarter, EaseOut), + < 2 => 1 - Math.Pow(quarter, EaseIn), + < 3 => -Math.Pow(quarter, EaseOut), + _ => -(1 - Math.Pow(quarter, EaseIn)), + }; + rotation = SwingDegrees * (float)swing; + + // Around the top edge: the anchor (the middle) swings out to the side + var halfHeight = (bounds.Bottom - bounds.Top) / 2; + var radians = -rotation * Math.PI / 180; + dx = (float)(-halfHeight * Math.Sin(radians)); + dy = (float)(halfHeight * (Math.Cos(radians) - 1)); + break; + } + } + } + // \frz turns counter-clockwise; the state is clockwise on screen like the canvas return new DrawAnimationState(dx, dy, Math.Max(0, scale), -rotation, Math.Clamp(opacity, 0, 1)); } @@ -218,6 +353,28 @@ public string GetPositionTag(float x, float y, int lineMs, (float Left, float To return (originX, originY, Math.Asin(distance / FarOriginDistance) * 180 / Math.PI); } + /// + /// The \org the line turns around (), or null for none. + /// + public (double X, double Y)? GetOrigin(float anchorX, float anchorY, (float Left, float Top, float Right, float Bottom) bounds, float frameWidth, float frameHeight) + { + switch (OriginKind) + { + case DrawAnimationOrigin.Anchor: + return (anchorX, anchorY); + case DrawAnimationOrigin.Top: + return (anchorX, bounds.Top); + case DrawAnimationOrigin.FarOut: + var (x, y, _) = GetOutSlideRotation(anchorX, anchorY, bounds, frameWidth, frameHeight); + return (x, y); + case DrawAnimationOrigin.FarBounce: + // To the left, so a positive (counter-clockwise) turn lifts the anchor + return (anchorX - FarOriginDistance, anchorY); + default: + return null; + } + } + /// /// Where to put a line with its own \frz () so it shows at (x, y) although /// it turns around (originX, originY) instead of its position. @@ -232,11 +389,12 @@ public static (double X, double Y) MapAroundOrigin(double x, double y, double or } /// - /// \fad and the scale/rotation tags (everything but the position and \org). - /// is the line's own \frz, which a spin turns from and back to; - /// the turn of a double slide's slide out (). + /// \fad and the scale/rotation tags (everything but the position and \org): the in effect, the + /// loop (one \t per cycle) and the out effect. is the line's own \frz, + /// which a spin turns from and back to. and the frame size give the + /// distances turned around a far \org (a slide out after a slide in, a bounce). /// - public string GetEffectTags(int lineMs, float baseFrz = 0, double outSlideDegrees = 0) + public string GetEffectTags(int lineMs, float baseFrz, (float Left, float Top, float Right, float Bottom) bounds, float frameWidth, float frameHeight) { var (inMs, outMs) = GetDurations(lineMs); var outStart = lineMs - outMs; @@ -263,6 +421,9 @@ public string GetEffectTags(int lineMs, float baseFrz = 0, double outSlideDegree break; } + // A spin loop leaves the line whole turns further; a spin out goes on from there + var restFrz = AppendLoopTags(sb, lineMs, baseFrz, bounds); + switch (Out) { case DrawAnimationEffect.Zoom: @@ -273,13 +434,13 @@ public string GetEffectTags(int lineMs, float baseFrz = 0, double outSlideDegree sb.Append(CultureInfo.InvariantCulture, $"\\t({outStart},{rise},\\fscx{N(PopOvershoot * 100)}\\fscy{N(PopOvershoot * 100)})\\t({rise},{lineMs},{N(EaseIn)},\\fscx0\\fscy0)"); break; case DrawAnimationEffect.Spin: - sb.Append(CultureInfo.InvariantCulture, $"\\t({outStart},{lineMs},{N(EaseIn)},\\frz{N(baseFrz - 360)}\\fscx0\\fscy0)"); + sb.Append(CultureInfo.InvariantCulture, $"\\t({outStart},{lineMs},{N(EaseIn)},\\frz{N(restFrz - 360)}\\fscx0\\fscy0)"); break; default: if (IsDoubleSlide) { - // Seven decimals: the far \org turns a thousandth of a degree into ~17 px - var degrees = (baseFrz + outSlideDegrees).ToString("0.#######", CultureInfo.InvariantCulture); + var outSlideDegrees = GetOutSlideRotation(0, 0, bounds, frameWidth, frameHeight).Degrees; + var degrees = Precise(baseFrz + outSlideDegrees); sb.Append(CultureInfo.InvariantCulture, $"\\t({outStart},{lineMs},{N(EaseIn)},\\frz{degrees})"); } @@ -289,17 +450,67 @@ public string GetEffectTags(int lineMs, float baseFrz = 0, double outSlideDegree return sb.ToString(); } - public bool HasRotation => In == DrawAnimationEffect.Spin || Out == DrawAnimationEffect.Spin; + /// + /// One \t per cycle of the loop; returns the \frz the loop leaves the line at. + /// + private float AppendLoopTags(StringBuilder sb, int lineMs, float baseFrz, (float Left, float Top, float Right, float Bottom) bounds) + { + var (start, end, cycles) = GetLoopWindow(lineMs); + if (cycles == 0) + { + return baseFrz; + } + + var period = (end - start) / (double)cycles; + int T(double cycle) => (int)Math.Round(start + cycle * period); + var pulse = N(PulseScale * 100); + var bounceDegrees = Precise(baseFrz + Math.Asin(GetBounceHeight(bounds) / FarOriginDistance) * 180 / Math.PI); + var restFrz = baseFrz; + for (var k = 0; k < cycles; k++) + { + switch (EffectiveLoop) + { + case DrawLoopEffect.Spin: + // Clockwise: \frz goes down a whole turn per cycle + restFrz = baseFrz - 360 * (k + 1); + sb.Append(CultureInfo.InvariantCulture, $"\\t({T(k)},{T(k + 1)},\\frz{N(restFrz)})"); + break; + case DrawLoopEffect.Pulse: + sb.Append(CultureInfo.InvariantCulture, $"\\t({T(k)},{T(k + 0.5)},\\fscx{pulse}\\fscy{pulse})\\t({T(k + 0.5)},{T(k + 1)},\\fscx100\\fscy100)"); + break; + case DrawLoopEffect.Blink: + // A \t with no length switches at once; the scale, so the colors' own alpha stays + sb.Append(CultureInfo.InvariantCulture, $"\\t({T(k + 0.5)},{T(k + 0.5)},\\fscx0\\fscy0)\\t({T(k + 1)},{T(k + 1)},\\fscx100\\fscy100)"); + break; + case DrawLoopEffect.Bounce: + sb.Append(CultureInfo.InvariantCulture, $"\\t({T(k)},{T(k + 0.5)},{N(EaseOut)},\\frz{bounceDegrees})\\t({T(k + 0.5)},{T(k + 1)},{N(EaseIn)},\\frz{N(baseFrz)})"); + break; + case DrawLoopEffect.Shake: + sb.Append(CultureInfo.InvariantCulture, $"\\t({T(k)},{T(k + 0.25)},\\frz{N(baseFrz + ShakeDegrees)})\\t({T(k + 0.25)},{T(k + 0.75)},\\frz{N(baseFrz - ShakeDegrees)})\\t({T(k + 0.75)},{T(k + 1)},\\frz{N(baseFrz)})"); + break; + case DrawLoopEffect.Swing: + sb.Append(CultureInfo.InvariantCulture, $"\\t({T(k)},{T(k + 0.25)},{N(EaseOut)},\\frz{N(baseFrz + SwingDegrees)})\\t({T(k + 0.25)},{T(k + 0.5)},{N(EaseIn)},\\frz{N(baseFrz)})"); + sb.Append(CultureInfo.InvariantCulture, $"\\t({T(k + 0.5)},{T(k + 0.75)},{N(EaseOut)},\\frz{N(baseFrz - SwingDegrees)})\\t({T(k + 0.75)},{T(k + 1)},{N(EaseIn)},\\frz{N(baseFrz)})"); + break; + } + } + + return restFrz; + } + + // Seven decimals: a far \org turns a thousandth of a degree into ~17 px + private static string Precise(double degrees) => degrees.ToString("0.#######", CultureInfo.InvariantCulture); private static readonly Regex MarkerRegex = new( - @"\{assadraw-anim c=(-?[\d.]+),(-?[\d.]+) in=(\w+),(\d+) out=(\w+),(\d+)\}", + @"\{assadraw-anim c=(-?[\d.]+),(-?[\d.]+) in=(\w+),(\d+) out=(\w+),(\d+)(?: loop=(\w+),(\d+))?\}", RegexOptions.Compiled); /// /// The comment block that keeps the animation and its anchor in the line. /// public string FormatMarker(float anchorX, float anchorY) => - $"{{assadraw-anim c={N(anchorX)},{N(anchorY)} in={In},{InMs.ToString(CultureInfo.InvariantCulture)} out={Out},{OutMs.ToString(CultureInfo.InvariantCulture)}}}"; + $"{{assadraw-anim c={N(anchorX)},{N(anchorY)} in={In},{InMs.ToString(CultureInfo.InvariantCulture)} out={Out},{OutMs.ToString(CultureInfo.InvariantCulture)}" + + (Loop == DrawLoopEffect.None ? string.Empty : $" loop={Loop},{LoopMs.ToString(CultureInfo.InvariantCulture)}") + "}"; /// /// Reads the marker of an animated line; false for a line without one. @@ -320,6 +531,12 @@ public static bool TryParseMarker(string text, out DrawAnimation animation, out animation.InMs = int.Parse(match.Groups[4].Value, CultureInfo.InvariantCulture); animation.Out = Enum.TryParse(match.Groups[5].Value, out var outEffect) ? outEffect : DrawAnimationEffect.None; animation.OutMs = int.Parse(match.Groups[6].Value, CultureInfo.InvariantCulture); + if (match.Groups[7].Success) + { + animation.Loop = Enum.TryParse(match.Groups[7].Value, out var loop) ? loop : DrawLoopEffect.None; + animation.LoopMs = int.Parse(match.Groups[8].Value, CultureInfo.InvariantCulture); + } + return true; } @@ -351,11 +568,11 @@ public static string RemoveFromLine(string text) public static bool IsAnchoredDrawingLine(string text) => DrawingAnimationRegex.IsMatch(text ?? string.Empty); public bool Equals(DrawAnimation? other) => - other != null && In == other.In && InMs == other.InMs && Out == other.Out && OutMs == other.OutMs; + other != null && In == other.In && InMs == other.InMs && Out == other.Out && OutMs == other.OutMs && Loop == other.Loop && LoopMs == other.LoopMs; public override bool Equals(object? obj) => Equals(obj as DrawAnimation); - public override int GetHashCode() => HashCode.Combine(In, InMs, Out, OutMs); + public override int GetHashCode() => HashCode.Combine(In, InMs, Out, OutMs, Loop, LoopMs); // No "-0" for a text with no angle of its own private static string N(double value) => Math.Abs(value) < 0.005 ? "0" : value.ToString("0.##", CultureInfo.InvariantCulture); diff --git a/src/ui/Features/Assa/AssaDraw/ShapeLibrary.cs b/src/ui/Features/Assa/AssaDraw/ShapeLibrary.cs index 4ce583cc717..2ca86762076 100644 --- a/src/ui/Features/Assa/AssaDraw/ShapeLibrary.cs +++ b/src/ui/Features/Assa/AssaDraw/ShapeLibrary.cs @@ -35,6 +35,16 @@ public sealed class ShapeLibraryItem /// public bool HasOwnColors { get; } + /// + /// Animated clip art: the animation the inserted shapes get (a spinner spins). + /// + public DrawAnimation? Animation { get; init; } + + /// + /// Only the parts of this color get (the dot of a REC badge); null for all. + /// + public Color? AnimatedColor { get; init; } + public ShapeLibraryItem(string name, string category, Func> build, SeAssaDrawShape? userShape = null, (float Left, float Top, float Right, float Bottom)? textArea = null, bool ownColors = false) { @@ -314,14 +324,19 @@ private static ShapeLibraryItem TextItem(string name, string category, (float Le /// Colored clip art: an SVG body for a 100 x 100 view box, imported like an SVG file /// (one layer per color run), with an optional text area in the same coordinates. /// - private static ShapeLibraryItem Clip(string name, (float Left, float Top, float Right, float Bottom)? textArea, string svgBody) + private static ShapeLibraryItem Clip(string name, (float Left, float Top, float Right, float Bottom)? textArea, string svgBody, DrawLoopEffect loop = DrawLoopEffect.None, + int loopMs = DrawAnimation.DefaultLoopMs, string? animatedColor = null) { var svg = "" + svgBody + ""; // Imported at 1000 x 1000, so the view box is scaled by 10 like the path data shapes (float, float, float, float)? area = textArea is { } a ? (a.Left * 10, a.Top * 10, a.Right * 10, a.Bottom * 10) : null; return new ShapeLibraryItem(name, Se.Language.Assa.DrawCategoryClipArt, () => SvgImporter.Import(svg, 1000, 1000, 0).Shapes, - textArea: area, ownColors: true); + textArea: area, ownColors: true) + { + Animation = loop == DrawLoopEffect.None ? null : new DrawAnimation { Loop = loop, LoopMs = loopMs }, + AnimatedColor = animatedColor == null ? null : Color.Parse(animatedColor), + }; } private static string F(double value) => value.ToString("0.##", CultureInfo.InvariantCulture); @@ -592,6 +607,20 @@ private static List MakeBuiltIn() Clip(l.DrawClipArtLoveIt, null, """"""), + // Animated: these come with a loop + Clip(l.DrawClipArtLoadingSpinner, null, + """""", DrawLoopEffect.Spin, 1000), + Clip(l.DrawClipArtRec, null, + """""", DrawLoopEffect.Blink, 1000, animatedColor: "#E53935"), + Clip(l.DrawClipArtRingingPhone, null, + """""", DrawLoopEffect.Shake, 400), + Clip(l.DrawClipArtBeatingHeart, null, + """""", DrawLoopEffect.Pulse, 800), + Clip(l.DrawClipArtBouncingArrow, null, + """""", DrawLoopEffect.Bounce, 900), + Clip(l.DrawClipArtGear, null, + """""", DrawLoopEffect.Spin, 3000), + // Star ratings 0-5: gold stars for the rating, gray for the rest .. Enumerable.Range(0, 6).Select(stars => Clip(string.Format(l.DrawClipArtStarRating, stars), null, StarRatingSvg(stars))), ]; diff --git a/src/ui/Logic/Config/Language/Assa/LanguageAssa.cs b/src/ui/Logic/Config/Language/Assa/LanguageAssa.cs index c8a071e84cf..4614c7b4189 100644 --- a/src/ui/Logic/Config/Language/Assa/LanguageAssa.cs +++ b/src/ui/Logic/Config/Language/Assa/LanguageAssa.cs @@ -142,6 +142,19 @@ public class LanguageAssa public string DrawAnimationPlay { get; set; } public string DrawAnimationStop { get; set; } public string DrawAnimationLayerHint { get; set; } + public string DrawAnimationLoop { get; set; } + public string DrawAnimationPulse { get; set; } + public string DrawAnimationBlink { get; set; } + public string DrawAnimationBounce { get; set; } + public string DrawAnimationShake { get; set; } + public string DrawAnimationSwing { get; set; } + public string DrawAnimationLoopBlocked { get; set; } + public string DrawClipArtLoadingSpinner { get; set; } + public string DrawClipArtRec { get; set; } + public string DrawClipArtRingingPhone { get; set; } + public string DrawClipArtBeatingHeart { get; set; } + public string DrawClipArtBouncingArrow { get; set; } + public string DrawClipArtGear { get; set; } public string DrawCategoryMyShapes { get; set; } public string DrawShapeSpeechBubble { get; set; } public string DrawShapeSpeechBubbleRight { get; set; } @@ -686,6 +699,19 @@ public LanguageAssa() DrawAnimationPlay = "Play animation"; DrawAnimationStop = "Stop animation"; DrawAnimationLayerHint = "Applies to the whole layer (one subtitle line); a group moves as one."; + DrawAnimationLoop = "Loop"; + DrawAnimationPulse = "Pulse"; + DrawAnimationBlink = "Blink"; + DrawAnimationBounce = "Bounce"; + DrawAnimationShake = "Shake"; + DrawAnimationSwing = "Swing"; + DrawAnimationLoopBlocked = "This loop turns around another point than the in/out effect, so it is left out."; + DrawClipArtLoadingSpinner = "Loading spinner"; + DrawClipArtRec = "REC"; + DrawClipArtRingingPhone = "Ringing phone"; + DrawClipArtBeatingHeart = "Beating heart"; + DrawClipArtBouncingArrow = "Bouncing arrow"; + DrawClipArtGear = "Gear"; DrawCategoryMyShapes = "My shapes"; DrawShapeSpeechBubble = "Speech bubble"; DrawShapeSpeechBubbleRight = "Speech bubble, tail right"; diff --git a/tests/UI/Features/Assa/AssaDrawAnimationTests.cs b/tests/UI/Features/Assa/AssaDrawAnimationTests.cs index b61beef2751..a3b5542c921 100644 --- a/tests/UI/Features/Assa/AssaDrawAnimationTests.cs +++ b/tests/UI/Features/Assa/AssaDrawAnimationTests.cs @@ -201,6 +201,92 @@ public void Undo_RestoresThePreviousAnimation() Assert.Null(vm.Shapes[0].Animation); } + [AvaloniaFact] + public void Loop_IsOneTPerCycle_FittedBetweenInAndOut_AndOpensAgain() + { + var vm = MakeViewModel(SquareA); + vm.SetSelection([vm.Shapes[0]]); + vm.AnimationIn = DrawAnimationEffect.Fade; + vm.AnimationOut = DrawAnimationEffect.Spin; + vm.AnimationLoop = DrawLoopEffect.Spin; + vm.AnimationLoopMs = 1000; + var text = vm.GetAssaDrawFileText(); + + vm.OkCommand.Execute(null); + var line = vm.ResultSubtitle.Paragraphs[0].Text; + + // 400-2600 ms between the effects: two whole turns of 1100 ms, then the spin out goes on from there + Assert.Contains("\\t(400,1500,\\frz-360)\\t(1500,2600,\\frz-720)\\t(2600,3000,2,\\frz-1080\\fscx0\\fscy0)", line); + Assert.Contains("loop=Spin,1000}", line); + + var loaded = MakeViewModel(); + loaded.LineDurationMs = vm.LineDurationMs; + loaded.LoadFromText(text); + Assert.Equal(DrawLoopEffect.Spin, loaded.Shapes[0].Animation?.Loop); + Assert.Equal(text, loaded.GetAssaDrawFileText()); + } + + [AvaloniaFact] + public void Loop_TurningAroundAnotherOrigin_IsLeftOut() + { + var vm = MakeViewModel(SquareA); + vm.SetSelection([vm.Shapes[0]]); + vm.AnimationIn = DrawAnimationEffect.Spin; + vm.AnimationLoop = DrawLoopEffect.Swing; + + Assert.True(vm.IsLoopBlocked); + vm.OkCommand.Execute(null); + Assert.DoesNotContain("\\frz14", vm.ResultSubtitle.Paragraphs[0].Text); + } + + [AvaloniaFact] + public void Swing_TurnsAroundTheTopEdge_BounceAroundAFarOrigin() + { + var vm = MakeViewModel(SquareA, SquareC); + vm.SetSelection([vm.Shapes[0]]); + vm.AnimationLoop = DrawLoopEffect.Swing; + vm.SetSelection([vm.Shapes[1]]); + vm.AnimationLoop = DrawLoopEffect.Bounce; + + vm.OkCommand.Execute(null); + var lines = vm.ResultSubtitle.Paragraphs; + + Assert.StartsWith("{\\an7\\pos(150,150)\\org(150,100)\\t(0,250,0.5,\\frz14)", lines[0].Text); + Assert.StartsWith("{\\an7\\pos(650,650)\\org(-999350,650)\\t(0,500,0.5,\\frz0.0005", lines[1].Text); + + // The canvas: the swing's middle goes out to the side, the bounce goes up a tenth of the height + Assert.Equal(50 * Math.Sin(14 * Math.PI / 180), vm.GetCanvasAnimationTransform(vm.Shapes[0], 250).State.Dx, 0.5); + Assert.Equal(-10, vm.GetCanvasAnimationTransform(vm.Shapes[1], 500).State.Dy, 0.01); + } + + [AvaloniaFact] + public void AnimatedClipArt_ComesWithItsLoop_OnlyOnItsAnimatedPart() + { + var vm = MakeViewModel(); + vm.CurrentLibraryShape = ShapeLibrary.BuiltIn.First(i => i.Name == Nikse.SubtitleEdit.Logic.Config.Se.Language.Assa.DrawClipArtRec); + vm.InsertLibraryShape(new CanvasInsertEventArgs(100, 100, 400, 200, isClick: false)); + + var dot = Assert.Single(vm.Shapes, s => s.Animation != null); + Assert.Equal(DrawLoopEffect.Blink, dot.Animation!.Loop); + Assert.Equal(Avalonia.Media.Color.Parse("#E53935"), dot.ForeColor); + + vm.CurrentLibraryShape = ShapeLibrary.BuiltIn.First(i => i.Name == Nikse.SubtitleEdit.Logic.Config.Se.Language.Assa.DrawClipArtLoadingSpinner); + var before = vm.Shapes.Count; + vm.InsertLibraryShape(new CanvasInsertEventArgs(600, 100, 200, 200, isClick: false)); + Assert.All(vm.Shapes.Skip(before), s => Assert.Equal(DrawLoopEffect.Spin, s.Animation?.Loop)); + } + + [Fact] + public void Blink_IsOffInTheSecondHalfOfEachCycle() + { + var animation = new DrawAnimation { Loop = DrawLoopEffect.Blink, LoopMs = 1000 }; + var bounds = (100f, 100f, 200f, 200f); + + Assert.Equal(1, animation.GetState(250, 3000, bounds, 1920, 1080).Scale); + Assert.Equal(0, animation.GetState(750, 3000, bounds, 1920, 1080).Scale); + Assert.Equal(1, animation.GetState(1250, 3000, bounds, 1920, 1080).Scale); + } + [Fact] public void State_MatchesTheTags_AtTheEdges() { diff --git a/tests/UI/Features/Assa/AssaDrawClipArtTests.cs b/tests/UI/Features/Assa/AssaDrawClipArtTests.cs index fd6707e0c2d..eed9744d401 100644 --- a/tests/UI/Features/Assa/AssaDrawClipArtTests.cs +++ b/tests/UI/Features/Assa/AssaDrawClipArtTests.cs @@ -19,7 +19,7 @@ public class AssaDrawClipArtTests [Fact] public void ClipArt_KeepsItsColors_InAFewLayers() { - Assert.Equal(72, ClipArt.Count); + Assert.Equal(78, ClipArt.Count); foreach (var item in ClipArt) { Assert.True(item.HasOwnColors, item.Name); From ee618f755f66272584d9fc3f96df4c37ca7e45fd Mon Sep 17 00:00:00 2001 From: niksedk Date: Fri, 16 Oct 2026 18:59:52 +0200 Subject: [PATCH 283/306] ASSA draw: ringing bell instead of the spinning gear clip art A bell swinging from its top (the swing loop) in place of the gear. Co-Authored-By: Claude Opus 5.5 --- src/ui/Features/Assa/AssaDraw/ShapeLibrary.cs | 4 ++-- src/ui/Logic/Config/Language/Assa/LanguageAssa.cs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/ui/Features/Assa/AssaDraw/ShapeLibrary.cs b/src/ui/Features/Assa/AssaDraw/ShapeLibrary.cs index 2ca86762076..4525dbc5959 100644 --- a/src/ui/Features/Assa/AssaDraw/ShapeLibrary.cs +++ b/src/ui/Features/Assa/AssaDraw/ShapeLibrary.cs @@ -618,8 +618,8 @@ private static List MakeBuiltIn() """""", DrawLoopEffect.Pulse, 800), Clip(l.DrawClipArtBouncingArrow, null, """""", DrawLoopEffect.Bounce, 900), - Clip(l.DrawClipArtGear, null, - """""", DrawLoopEffect.Spin, 3000), + Clip(l.DrawClipArtRingingBell, null, + """""", DrawLoopEffect.Swing, 1200), // Star ratings 0-5: gold stars for the rating, gray for the rest .. Enumerable.Range(0, 6).Select(stars => Clip(string.Format(l.DrawClipArtStarRating, stars), null, StarRatingSvg(stars))), diff --git a/src/ui/Logic/Config/Language/Assa/LanguageAssa.cs b/src/ui/Logic/Config/Language/Assa/LanguageAssa.cs index 4614c7b4189..0ff7f45d3c9 100644 --- a/src/ui/Logic/Config/Language/Assa/LanguageAssa.cs +++ b/src/ui/Logic/Config/Language/Assa/LanguageAssa.cs @@ -154,7 +154,7 @@ public class LanguageAssa public string DrawClipArtRingingPhone { get; set; } public string DrawClipArtBeatingHeart { get; set; } public string DrawClipArtBouncingArrow { get; set; } - public string DrawClipArtGear { get; set; } + public string DrawClipArtRingingBell { get; set; } public string DrawCategoryMyShapes { get; set; } public string DrawShapeSpeechBubble { get; set; } public string DrawShapeSpeechBubbleRight { get; set; } @@ -711,7 +711,7 @@ public LanguageAssa() DrawClipArtRingingPhone = "Ringing phone"; DrawClipArtBeatingHeart = "Beating heart"; DrawClipArtBouncingArrow = "Bouncing arrow"; - DrawClipArtGear = "Gear"; + DrawClipArtRingingBell = "Ringing bell"; DrawCategoryMyShapes = "My shapes"; DrawShapeSpeechBubble = "Speech bubble"; DrawShapeSpeechBubbleRight = "Speech bubble, tail right"; From 1db1c71d136b9ae474952a29be92222c93b50f5c Mon Sep 17 00:00:00 2001 From: niksedk Date: Fri, 16 Oct 2026 19:02:47 +0200 Subject: [PATCH 284/306] ASSA draw: six more animated clip arts Pointing hand and basketball (bounce), Wi-Fi and twinkling star (pulse), alarm clock (shake) and hourglass (slow spin). Co-Authored-By: Claude Opus 5.5 --- src/ui/Features/Assa/AssaDraw/ShapeLibrary.cs | 12 ++++++++++++ src/ui/Logic/Config/Language/Assa/LanguageAssa.cs | 12 ++++++++++++ tests/UI/Features/Assa/AssaDrawClipArtTests.cs | 2 +- 3 files changed, 25 insertions(+), 1 deletion(-) diff --git a/src/ui/Features/Assa/AssaDraw/ShapeLibrary.cs b/src/ui/Features/Assa/AssaDraw/ShapeLibrary.cs index 4525dbc5959..a59d985e628 100644 --- a/src/ui/Features/Assa/AssaDraw/ShapeLibrary.cs +++ b/src/ui/Features/Assa/AssaDraw/ShapeLibrary.cs @@ -620,6 +620,18 @@ private static List MakeBuiltIn() """""", DrawLoopEffect.Bounce, 900), Clip(l.DrawClipArtRingingBell, null, """""", DrawLoopEffect.Swing, 1200), + Clip(l.DrawClipArtPointingHand, null, + """""", DrawLoopEffect.Bounce, 800), + Clip(l.DrawClipArtWiFi, null, + """""", DrawLoopEffect.Pulse, 900), + Clip(l.DrawClipArtTwinklingStar, null, + """""", DrawLoopEffect.Pulse, 700), + Clip(l.DrawClipArtAlarmClock, null, + """""", DrawLoopEffect.Shake, 300), + Clip(l.DrawClipArtHourglass, null, + """""", DrawLoopEffect.Spin, 2000), + Clip(l.DrawClipArtBasketball, null, + """""", DrawLoopEffect.Bounce, 700), // Star ratings 0-5: gold stars for the rating, gray for the rest .. Enumerable.Range(0, 6).Select(stars => Clip(string.Format(l.DrawClipArtStarRating, stars), null, StarRatingSvg(stars))), diff --git a/src/ui/Logic/Config/Language/Assa/LanguageAssa.cs b/src/ui/Logic/Config/Language/Assa/LanguageAssa.cs index 0ff7f45d3c9..b1e912ce2a4 100644 --- a/src/ui/Logic/Config/Language/Assa/LanguageAssa.cs +++ b/src/ui/Logic/Config/Language/Assa/LanguageAssa.cs @@ -155,6 +155,12 @@ public class LanguageAssa public string DrawClipArtBeatingHeart { get; set; } public string DrawClipArtBouncingArrow { get; set; } public string DrawClipArtRingingBell { get; set; } + public string DrawClipArtPointingHand { get; set; } + public string DrawClipArtWiFi { get; set; } + public string DrawClipArtTwinklingStar { get; set; } + public string DrawClipArtAlarmClock { get; set; } + public string DrawClipArtHourglass { get; set; } + public string DrawClipArtBasketball { get; set; } public string DrawCategoryMyShapes { get; set; } public string DrawShapeSpeechBubble { get; set; } public string DrawShapeSpeechBubbleRight { get; set; } @@ -712,6 +718,12 @@ public LanguageAssa() DrawClipArtBeatingHeart = "Beating heart"; DrawClipArtBouncingArrow = "Bouncing arrow"; DrawClipArtRingingBell = "Ringing bell"; + DrawClipArtPointingHand = "Pointing hand"; + DrawClipArtWiFi = "Wi-Fi"; + DrawClipArtTwinklingStar = "Twinkling star"; + DrawClipArtAlarmClock = "Alarm clock"; + DrawClipArtHourglass = "Hourglass"; + DrawClipArtBasketball = "Basketball"; DrawCategoryMyShapes = "My shapes"; DrawShapeSpeechBubble = "Speech bubble"; DrawShapeSpeechBubbleRight = "Speech bubble, tail right"; diff --git a/tests/UI/Features/Assa/AssaDrawClipArtTests.cs b/tests/UI/Features/Assa/AssaDrawClipArtTests.cs index eed9744d401..edb5707c451 100644 --- a/tests/UI/Features/Assa/AssaDrawClipArtTests.cs +++ b/tests/UI/Features/Assa/AssaDrawClipArtTests.cs @@ -19,7 +19,7 @@ public class AssaDrawClipArtTests [Fact] public void ClipArt_KeepsItsColors_InAFewLayers() { - Assert.Equal(78, ClipArt.Count); + Assert.Equal(84, ClipArt.Count); foreach (var item in ClipArt) { Assert.True(item.HasOwnColors, item.Name); From 2f1cdcbb1e93296f40f7236b0e96c8ec41f40d00 Mon Sep 17 00:00:00 2001 From: niksedk Date: Fri, 16 Oct 2026 19:09:06 +0200 Subject: [PATCH 285/306] ASSA draw: flip loop for the hourglass, idea bulb instead of the basketball A Flip loop - a quick half turn, then a pause - makes the hourglass a loader (its sand now split evenly, so it looks the same after each flip). The basketball becomes an idea bulb whose rays blink. Co-Authored-By: Claude Opus 5.5 --- .../Assa/AssaDraw/AssaDrawAnimationPanel.cs | 1 + .../Features/Assa/AssaDraw/DrawAnimation.cs | 24 +++++++++++++++++-- src/ui/Features/Assa/AssaDraw/ShapeLibrary.cs | 6 ++--- .../Config/Language/Assa/LanguageAssa.cs | 6 +++-- .../Features/Assa/AssaDrawAnimationTests.cs | 12 ++++++++++ 5 files changed, 42 insertions(+), 7 deletions(-) diff --git a/src/ui/Features/Assa/AssaDraw/AssaDrawAnimationPanel.cs b/src/ui/Features/Assa/AssaDraw/AssaDrawAnimationPanel.cs index 4a346bf7d47..9fb0bd70df0 100644 --- a/src/ui/Features/Assa/AssaDraw/AssaDrawAnimationPanel.cs +++ b/src/ui/Features/Assa/AssaDraw/AssaDrawAnimationPanel.cs @@ -254,6 +254,7 @@ public static string GetLoopName(DrawLoopEffect effect) DrawLoopEffect.Bounce => l.DrawAnimationBounce, DrawLoopEffect.Shake => l.DrawAnimationShake, DrawLoopEffect.Swing => l.DrawAnimationSwing, + DrawLoopEffect.Flip => l.DrawAnimationFlip, _ => l.DrawAnimationNone, }; } diff --git a/src/ui/Features/Assa/AssaDraw/DrawAnimation.cs b/src/ui/Features/Assa/AssaDraw/DrawAnimation.cs index 44516a9d2f2..81456772921 100644 --- a/src/ui/Features/Assa/AssaDraw/DrawAnimation.cs +++ b/src/ui/Features/Assa/AssaDraw/DrawAnimation.cs @@ -34,6 +34,11 @@ public enum DrawLoopEffect Bounce, Shake, Swing, + + /// + /// A quick half turn, then a pause (an hourglass loader). + /// + Flip, } /// @@ -87,6 +92,11 @@ public sealed class DrawAnimation : IEquatable private const float ShakeDegrees = 8; private const float SwingDegrees = 14; + /// + /// Share of a flip cycle spent turning; the rest is the pause. + /// + private const double FlipTurnShare = 0.35; + public bool IsNone => In == DrawAnimationEffect.None && Out == DrawAnimationEffect.None && Loop == DrawLoopEffect.None; public DrawAnimation Clone() => new() { In = In, InMs = InMs, Out = Out, OutMs = OutMs, Loop = Loop, LoopMs = LoopMs }; @@ -98,7 +108,7 @@ public sealed class DrawAnimation : IEquatable private DrawAnimationOrigin LoopOrigin => Loop switch { - DrawLoopEffect.Spin or DrawLoopEffect.Shake => DrawAnimationOrigin.Anchor, + DrawLoopEffect.Spin or DrawLoopEffect.Shake or DrawLoopEffect.Flip => DrawAnimationOrigin.Anchor, DrawLoopEffect.Swing => DrawAnimationOrigin.Top, DrawLoopEffect.Bounce => DrawAnimationOrigin.FarBounce, _ => DrawAnimationOrigin.None, @@ -248,12 +258,17 @@ public DrawAnimationState GetState(double timeMs, int lineMs, (float Left, float { var period = (loopEnd - loopStart) / (double)cycles; var u = (timeMs - loopStart) / period; - u -= Math.Floor(u); + var cycle = Math.Min(Math.Floor(u), cycles - 1); + u -= cycle; switch (EffectiveLoop) { case DrawLoopEffect.Spin: rotation = (float)(-360 * u); break; + case DrawLoopEffect.Flip: + var turned = u < FlipTurnShare ? Math.Pow(u / FlipTurnShare, EaseOut) : 1; + rotation = (float)(-180 * (cycle + turned)); + break; case DrawLoopEffect.Pulse: scale = 1 + (PulseScale - 1) * (float)(u < 0.5 ? u * 2 : 2 - u * 2); break; @@ -475,6 +490,11 @@ private float AppendLoopTags(StringBuilder sb, int lineMs, float baseFrz, (float restFrz = baseFrz - 360 * (k + 1); sb.Append(CultureInfo.InvariantCulture, $"\\t({T(k)},{T(k + 1)},\\frz{N(restFrz)})"); break; + case DrawLoopEffect.Flip: + // Clockwise half turn easing out (accel 0.5), then a pause + restFrz = baseFrz - 180 * (k + 1); + sb.Append(CultureInfo.InvariantCulture, $"\\t({T(k)},{T(k + FlipTurnShare)},{N(EaseOut)},\\frz{N(restFrz)})"); + break; case DrawLoopEffect.Pulse: sb.Append(CultureInfo.InvariantCulture, $"\\t({T(k)},{T(k + 0.5)},\\fscx{pulse}\\fscy{pulse})\\t({T(k + 0.5)},{T(k + 1)},\\fscx100\\fscy100)"); break; diff --git a/src/ui/Features/Assa/AssaDraw/ShapeLibrary.cs b/src/ui/Features/Assa/AssaDraw/ShapeLibrary.cs index a59d985e628..f4250966f43 100644 --- a/src/ui/Features/Assa/AssaDraw/ShapeLibrary.cs +++ b/src/ui/Features/Assa/AssaDraw/ShapeLibrary.cs @@ -629,9 +629,9 @@ private static List MakeBuiltIn() Clip(l.DrawClipArtAlarmClock, null, """""", DrawLoopEffect.Shake, 300), Clip(l.DrawClipArtHourglass, null, - """""", DrawLoopEffect.Spin, 2000), - Clip(l.DrawClipArtBasketball, null, - """""", DrawLoopEffect.Bounce, 700), + """""", DrawLoopEffect.Flip, 1600), + Clip(l.DrawClipArtIdea, null, + """""", DrawLoopEffect.Blink, 800, animatedColor: "#FFD54F"), // Star ratings 0-5: gold stars for the rating, gray for the rest .. Enumerable.Range(0, 6).Select(stars => Clip(string.Format(l.DrawClipArtStarRating, stars), null, StarRatingSvg(stars))), diff --git a/src/ui/Logic/Config/Language/Assa/LanguageAssa.cs b/src/ui/Logic/Config/Language/Assa/LanguageAssa.cs index b1e912ce2a4..b51824e139a 100644 --- a/src/ui/Logic/Config/Language/Assa/LanguageAssa.cs +++ b/src/ui/Logic/Config/Language/Assa/LanguageAssa.cs @@ -148,6 +148,7 @@ public class LanguageAssa public string DrawAnimationBounce { get; set; } public string DrawAnimationShake { get; set; } public string DrawAnimationSwing { get; set; } + public string DrawAnimationFlip { get; set; } public string DrawAnimationLoopBlocked { get; set; } public string DrawClipArtLoadingSpinner { get; set; } public string DrawClipArtRec { get; set; } @@ -160,7 +161,7 @@ public class LanguageAssa public string DrawClipArtTwinklingStar { get; set; } public string DrawClipArtAlarmClock { get; set; } public string DrawClipArtHourglass { get; set; } - public string DrawClipArtBasketball { get; set; } + public string DrawClipArtIdea { get; set; } public string DrawCategoryMyShapes { get; set; } public string DrawShapeSpeechBubble { get; set; } public string DrawShapeSpeechBubbleRight { get; set; } @@ -711,6 +712,7 @@ public LanguageAssa() DrawAnimationBounce = "Bounce"; DrawAnimationShake = "Shake"; DrawAnimationSwing = "Swing"; + DrawAnimationFlip = "Flip"; DrawAnimationLoopBlocked = "This loop turns around another point than the in/out effect, so it is left out."; DrawClipArtLoadingSpinner = "Loading spinner"; DrawClipArtRec = "REC"; @@ -723,7 +725,7 @@ public LanguageAssa() DrawClipArtTwinklingStar = "Twinkling star"; DrawClipArtAlarmClock = "Alarm clock"; DrawClipArtHourglass = "Hourglass"; - DrawClipArtBasketball = "Basketball"; + DrawClipArtIdea = "Idea"; DrawCategoryMyShapes = "My shapes"; DrawShapeSpeechBubble = "Speech bubble"; DrawShapeSpeechBubbleRight = "Speech bubble, tail right"; diff --git a/tests/UI/Features/Assa/AssaDrawAnimationTests.cs b/tests/UI/Features/Assa/AssaDrawAnimationTests.cs index a3b5542c921..e3bcc1904de 100644 --- a/tests/UI/Features/Assa/AssaDrawAnimationTests.cs +++ b/tests/UI/Features/Assa/AssaDrawAnimationTests.cs @@ -276,6 +276,18 @@ public void AnimatedClipArt_ComesWithItsLoop_OnlyOnItsAnimatedPart() Assert.All(vm.Shapes.Skip(before), s => Assert.Equal(DrawLoopEffect.Spin, s.Animation?.Loop)); } + [Fact] + public void Flip_TurnsHalfWayEachCycle_ThenPauses() + { + var animation = new DrawAnimation { Loop = DrawLoopEffect.Flip, LoopMs = 1000 }; + var bounds = (100f, 100f, 200f, 200f); + + // A half turn in the first 35% of the cycle (clockwise on screen), then it stays + Assert.Equal("\\t(0,350,0.5,\\frz-180)\\t(1000,1350,0.5,\\frz-360)", animation.GetEffectTags(2000, 0, bounds, 1920, 1080)); + Assert.Equal(180, animation.GetState(700, 2000, bounds, 1920, 1080).Rotation, 0.01); + Assert.Equal(360, animation.GetState(1500, 2000, bounds, 1920, 1080).Rotation, 0.01); + } + [Fact] public void Blink_IsOffInTheSecondHalfOfEachCycle() { From 347c0af3166aea1bbcd2b2eabb0c5ef8b14e0170 Mon Sep 17 00:00:00 2001 From: niksedk Date: Fri, 16 Oct 2026 19:44:19 +0200 Subject: [PATCH 286/306] Move shared language strings to General Strings with the same text and meaning in several sections now live in LanguageGeneral: - 15 section keys that duplicated an existing General key now use it (Assa draw/styles, chapters scale, TTS model, improve time codes apply). - General.OpenAiCompatibleSttPrompt/Temperature were used as plain "Prompt"/"Temperature" labels; renamed to General.Prompt/Temperature and shared with AI review, background music and translate. - 39 new General keys replace 86 section duplicates (Preset, Output, Theme, Line {0}, Max line length, Play line, Original, Next/Previous change, Fix OCR errors, llama.cpp download prompt, ...). Existing translations are carried over into the new General keys in all 34 translation files (preferring the better translation when the sections differed). Pairs where translators used different words for a reason were left alone (All uppercase, Voices, count labels, start time, ...). Also fixes Bulgarian General.Background ("backstory") and French General.Size ("resize"). Co-Authored-By: Claude Opus 5.5 --- src/ui/Assets/Languages/Arabic.json | 41 +++++++++ src/ui/Assets/Languages/Azerbaijani.json | 41 +++++++++ src/ui/Assets/Languages/Bulgarian.json | 43 +++++++++- src/ui/Assets/Languages/Catalan.json | 40 +++++++++ src/ui/Assets/Languages/Central Kurdish.json | 40 +++++++++ .../Assets/Languages/ChineseSimplified.json | 41 +++++++++ .../Assets/Languages/ChineseTraditional.json | 41 +++++++++ src/ui/Assets/Languages/Czech.json | 40 +++++++++ src/ui/Assets/Languages/Danish.json | 38 ++++++++ src/ui/Assets/Languages/Dutch.json | 38 ++++++++ src/ui/Assets/Languages/Finnish.json | 41 +++++++++ src/ui/Assets/Languages/French.json | 40 ++++++++- src/ui/Assets/Languages/German.json | 37 ++++++++ src/ui/Assets/Languages/Greek.json | 41 +++++++++ src/ui/Assets/Languages/Hebrew.json | 41 +++++++++ src/ui/Assets/Languages/Hungarian.json | 41 +++++++++ src/ui/Assets/Languages/Indonesian.json | 37 ++++++++ src/ui/Assets/Languages/Italian.json | 38 ++++++++ src/ui/Assets/Languages/Japanese.json | 40 +++++++++ src/ui/Assets/Languages/Korean.json | 41 +++++++++ src/ui/Assets/Languages/Macedonian.json | 41 +++++++++ src/ui/Assets/Languages/Norwegian.json | 40 +++++++++ src/ui/Assets/Languages/Persian.json | 41 +++++++++ src/ui/Assets/Languages/Polish.json | 40 +++++++++ .../Assets/Languages/Portuguese (Brazil).json | 39 +++++++++ src/ui/Assets/Languages/Portuguese.json | 39 +++++++++ src/ui/Assets/Languages/Romanian.json | 38 ++++++++ src/ui/Assets/Languages/Russian.json | 41 +++++++++ src/ui/Assets/Languages/Spanish.json | 39 +++++++++ src/ui/Assets/Languages/Swedish.json | 39 +++++++++ src/ui/Assets/Languages/Thai.json | 40 +++++++++ src/ui/Assets/Languages/Turkish.json | 41 +++++++++ src/ui/Assets/Languages/Ukrainian.json | 41 +++++++++ src/ui/Assets/Languages/Vietnamese.json | 40 +++++++++ .../AssaApplyAdvancedEffectWindow.cs | 2 +- .../Assa/AssaDraw/AssaDrawAnimationPanel.cs | 4 +- .../Assa/AssaDraw/AssaDrawViewModel.cs | 4 +- .../Features/Assa/AssaDraw/AssaDrawWindow.cs | 16 ++-- .../AssaProgressBar/AssaProgressBarWindow.cs | 4 +- .../AssaSetPositionViewModel.cs | 2 +- src/ui/Features/Assa/AssaStylesWindow.cs | 4 +- .../FontCollector/FontCollectorViewModel.cs | 2 +- src/ui/Features/Edit/Find/FindResultText.cs | 2 +- .../HearingImpairedRuleSettingsWindow.cs | 4 +- .../ModifySelection/ModifySelectionRule.cs | 2 +- .../MultipleReplaceViewModel.cs | 2 +- .../EditCustomTextFormatWindow.cs | 6 +- .../ExportImageBasedWindow.cs | 2 +- .../ExportImageBased/TextEffectWindow.cs | 4 +- .../DCinemaInteropPropertiesWindow.cs | 8 +- .../DCinemaSmptePropertiesWindow.cs | 8 +- .../ItunesTimedTextPropertiesWindow.cs | 4 +- .../TimedText10PropertiesWindow.cs | 4 +- .../TimedTextImsc11PropertiesWindow.cs | 4 +- .../Files/ImportDvd/DvdTitleDisplay.cs | 2 +- .../Files/ImportDvd/ImportDvdWindow.cs | 2 +- .../ImportPlainTextViewModel.cs | 4 +- .../RestoreAutoBackupWindow.cs | 8 +- .../Files/Statistics/StatisticsWindow.cs | 2 +- .../CheckForUpdatesViewModel.cs | 2 +- src/ui/Features/Main/MainViewModel.cs | 8 +- .../BinaryOcrCharacterHistoryViewModel.cs | 2 +- .../Features/Ocr/LlamaCppOcrSettingsWindow.cs | 4 +- .../Ocr/NOcr/NOcrCharacterHistoryViewModel.cs | 2 +- src/ui/Features/Ocr/OcrHtmlExporter.cs | 2 +- src/ui/Features/Ocr/OcrViewModel.cs | 4 +- src/ui/Features/Ocr/OcrWindow.cs | 2 +- .../Ocr/PromptUnknownWordViewModel.cs | 4 +- .../Features/Ocr/PromptUnknownWordWindow.cs | 2 +- .../Options/Settings/ProfilesWindow.cs | 2 +- .../Features/Options/Settings/SettingsPage.cs | 6 +- .../CustomShortcutStepViewModel.cs | 2 +- .../BinaryAdjustBrightnessWindow.cs | 2 +- .../BinaryAdjustColorWindow.cs | 2 +- .../Shared/BinaryEdit/BinaryEditWindow.cs | 2 +- .../BinaryEdit/InitNativeMacMenuBinaryEdit.cs | 2 +- .../Shared/ColorPicker/ColorBrightnessBar.cs | 2 +- .../Shared/ColorPicker/ColorPickerWindow.cs | 2 +- .../Shared/ErrorList/ErrorListExport.cs | 2 +- .../Shared/ErrorList/ErrorListWindow.cs | 2 +- src/ui/Features/Shared/ErrorList/LineError.cs | 6 +- .../Shared/SourceView/SourceViewViewModel.cs | 2 +- .../SpellCheck/SpellCheckViewModel.cs | 2 +- .../Features/SpellCheck/SpellCheckWindow.cs | 2 +- .../Tools/AiReview/AiReviewPromptWindow.cs | 2 +- .../Tools/AiReview/AiReviewViewModel.cs | 9 +- .../Tools/AiReview/ReviewSuggestionItem.cs | 2 +- .../BatchConvertSettingsWindow.cs | 2 +- .../BatchConvertTsSettingsWindow.cs | 2 +- .../Tools/BatchConvert/BatchConvertWindow.cs | 6 +- .../BatchErrorList/BatchErrorListWindow.cs | 2 +- .../FunctionViews/ViewAdjustImageColors.cs | 2 +- .../FunctionViews/ViewSplitBreakLongLines.cs | 4 +- .../BeautifyTimeCodesViewModel.cs | 2 +- .../BeautifyTimeCodesWindow.cs | 8 +- .../ImproveTimeCodesViewModel.cs | 2 +- .../ImproveTimeCodesWindow.cs | 10 +-- .../MergeShortLines/MergeShortLinesWindow.cs | 2 +- .../MergeTwoSubtitlesWindow.cs | 4 +- .../InterjectionsWindow.cs | 6 +- .../RemoveTextForHearingImpairedWindow.cs | 4 +- .../SplitBreakLongLinesWindow.cs | 2 +- .../CopyPasteTranslateBlockViewModel.cs | 2 +- .../LlamaCppAdvancedSettingsWindow.cs | 4 +- .../Translate/LlamaCppDownloadHelper.cs | 2 +- .../BackgroundMusic/BackgroundMusicWindow.cs | 12 +-- .../Features/Video/BurnIn/BurnInViewModel.cs | 4 +- src/ui/Features/Video/BurnIn/BurnInWindow.cs | 4 +- .../Features/Video/Chapters/ChaptersWindow.cs | 5 +- .../Features/Video/CutVideo/CutVideoWindow.cs | 2 +- .../EmbeddedSubtitlesEditMp4ViewModel.cs | 2 +- .../ShotChanges/ShotChangeListViewModel.cs | 2 +- .../Video/ShotChanges/ShotChangesWindow.cs | 4 +- .../SpeechToTextQualityReportViewModel.cs | 8 +- .../SpeechToTextQualityReportWindow.cs | 2 +- .../Video/SpeechToText/SpeechToTextWindow.cs | 8 +- .../AdvancedTtsSettingsWindow.cs | 4 +- .../AutoCast/AutoCastSpeakersWindow.cs | 2 +- .../OpenAiCompatibleSettingsWindow.cs | 4 +- .../ReviewSpeech/ReviewSpeechWindow.cs | 4 +- .../Video/TextToSpeech/TextToSpeechWindow.cs | 2 +- .../VoicePacks/DownloadVoicePacksViewModel.cs | 2 +- .../Video/VideoOcr/VideoOcrViewModel.cs | 8 +- .../Features/Video/VideoOcr/VideoOcrWindow.cs | 2 +- .../Config/Language/Assa/LanguageAssa.cs | 30 ------- .../Config/Language/Edit/LanguageEditFind.cs | 2 - .../Language/Edit/LanguageModifySelection.cs | 6 -- .../Language/Edit/LanguageMultipleReplace.cs | 2 - .../Config/Language/File/LanguageExport.cs | 6 -- .../Config/Language/File/LanguageFile.cs | 4 - .../File/LanguageFilePropertiesDCinema.cs | 4 - .../Config/Language/File/LanguageImport.cs | 6 -- .../File/LanguageRestoreAutoBackup.cs | 2 - .../Language/File/LanguageStatistics.cs | 2 - .../Config/Language/Help/LanguageHelp.cs | 2 - .../Config/Language/LanguageErrorList.cs | 8 -- .../Logic/Config/Language/LanguageGeneral.cs | 86 ++++++++++++++++++- .../Config/Language/LanguageSourceView.cs | 2 - .../Config/Language/Main/LanguageMain.cs | 4 - .../Logic/Config/Language/Ocr/LanguageOcr.cs | 12 --- .../Language/Options/LanguageSettings.cs | 6 -- .../Options/LanguageSettingsShortcuts.cs | 2 - .../Language/SpellCheck/LanguageSpellCheck.cs | 2 - .../Config/Language/Tools/LanguageAiReview.cs | 6 -- .../Language/Tools/LanguageBatchConvert.cs | 6 -- .../Tools/LanguageBeautifyTimeCodes.cs | 8 -- .../Language/Tools/LanguageImageBasedEdit.cs | 4 - .../Tools/LanguageImproveTimeCodes.cs | 12 --- .../Tools/LanguageMergeTwoSubtitles.cs | 2 - .../Tools/LanguageNetflixCheckAndFix.cs | 8 -- .../LanguageRemoveTextForHearingImpaired.cs | 2 - .../Config/Language/Tools/LanguageTools.cs | 4 - .../Language/Translate/LanguageTranslate.cs | 6 -- .../Language/Video/LanguageAudioToText.cs | 8 -- .../Language/Video/LanguageBackgroundMusic.cs | 4 - .../Config/Language/Video/LanguageBurnIn.cs | 4 - .../Config/Language/Video/LanguageChapters.cs | 6 -- .../Language/Video/LanguageShotChanges.cs | 2 - .../Language/Video/LanguageTextToSpeech.cs | 14 --- .../Config/Language/Video/LanguageVideo.cs | 4 - .../Config/Language/Video/LanguageVideoOcr.cs | 6 -- .../NetflixQualityController.cs | 8 +- src/ui/Logic/ShortcutsMain.cs | 2 +- .../Features/Assa/AssaDrawBackgroundTests.cs | 2 +- .../Edit/ReplaceWindowFeedbackTests.cs | 8 +- .../Settings/SettingsSearchEnglishTests.cs | 6 +- .../Shared/ErrorList/ErrorListExportTests.cs | 2 +- .../ImproveTimeCodesViewModelTests.cs | 2 +- 168 files changed, 1619 insertions(+), 400 deletions(-) diff --git a/src/ui/Assets/Languages/Arabic.json b/src/ui/Assets/Languages/Arabic.json index ba159b2f623..99000da9185 100644 --- a/src/ui/Assets/Languages/Arabic.json +++ b/src/ui/Assets/Languages/Arabic.json @@ -4,6 +4,47 @@ "translatedBy": "", "cultureName": "ar", "general": { + "prompt": "المطالبة", + "temperature": "درجة الحرارة", + "preset": "الإعداد المسبق", + "output": "الإخراج", + "outputFormat": "تنسيق الإخراج", + "other": "أخرى", + "timeCodeFormat": "تنسيق الرمز الزمني", + "topBottomMargin": "الهامش العلوي/السفلي", + "theme": "السمة", + "brightness": "السطوع", + "chooseColor": "اختيار اللون", + "lineX": "السطر {0}", + "playLine": "تشغيل السطر", + "maxLineLength": "الحد الأقصى لطول السطر", + "maxCharsPerSec": "الحد الأقصى حرف/ثانية", + "maxNumberOfLines": "الحد الأقصى لعدد الأسطر", + "subtitles": "الترجمات", + "shotChanges": "تغييرات اللقطات", + "chapters": "الفصول", + "audio": "الصوت", + "interjections": "التعجّبات", + "blankLines": "أسطر فارغة", + "editWholeText": "تحرير النص بالكامل", + "noTextInClipboard": "لا يوجد نص في الحافظة", + "noSubtitlesFoundTitle": "لم يُعثر على ترجمة", + "replacedXOccurrences": "تم استبدال {0} من التكرارات", + "invalidRegularExpressionX": "تعبير نمطي غير صالح: {0}", + "discardChangesTitle": "تجاهل التغييرات؟", + "downloadingXDotDotDot": "جارٍ تنزيل {0}...", + "importTimeCodesDotDotDot": "استيراد الرموز الزمنية...", + "original": "الأصلي", + "nextChange": "التغيير التالي", + "previousChange": "التغيير السابق", + "changeXOfY": "التغيير {0} من {1}", + "tooLong": "طويل جداً", + "tooShort": "قصير جداً", + "overlapping": "متداخل", + "detail": "التفصيل", + "fixOcrErrors": "إصلاح أخطاء التعرف الضوئي على الحروف", + "runningOcrXY": "جارٍ تشغيل OCR... {0}/{1}", + "llamaCppDownloadEnginePrompt": "يتطلب llama.cpp تنزيل محرك llama-server. هل تريد التنزيل الآن؟", "abort": "إجهاض", "actor": "الممثل", "actors": "الممثلون", diff --git a/src/ui/Assets/Languages/Azerbaijani.json b/src/ui/Assets/Languages/Azerbaijani.json index 304a2191b84..55d1a3a0c86 100644 --- a/src/ui/Assets/Languages/Azerbaijani.json +++ b/src/ui/Assets/Languages/Azerbaijani.json @@ -4,6 +4,47 @@ "translatedBy": "", "cultureName": "az-AZ", "general": { + "prompt": "Təlimat", + "temperature": "Temperatur", + "preset": "Hazır parametr dəsti", + "output": "Çıxış", + "outputFormat": "Çıxış formatı", + "other": "Digər", + "timeCodeFormat": "Vaxt kodu formatı", + "topBottomMargin": "Üst/alt kənar boşluğu", + "theme": "Tema", + "brightness": "Parlaqlıq", + "chooseColor": "Rəng seç", + "lineX": "Sətir {0}", + "playLine": "Sətri oynat", + "maxLineLength": "Maksimum sətir uzunluğu", + "maxCharsPerSec": "Saniyədə maksimum simvol sayı", + "maxNumberOfLines": "Maksimum sətir sayı", + "subtitles": "Altyazılar", + "shotChanges": "Səhnə dəyişiklikləri", + "chapters": "Bölmələr", + "audio": "Səs", + "interjections": "Nidalar", + "blankLines": "Boş sətirlər", + "editWholeText": "Bütün mətni redaktə et", + "noTextInClipboard": "Mübadilə buferində mətn yoxdur", + "noSubtitlesFoundTitle": "Altyazı tapılmadı", + "replacedXOccurrences": "{0} uyğunluq əvəz edildi", + "invalidRegularExpressionX": "Yanlış müntəzəm ifadə: {0}", + "discardChangesTitle": "Dəyişikliklərdən imtina edilsin?", + "downloadingXDotDotDot": "{0} endirilir...", + "importTimeCodesDotDotDot": "Zaman kodlarını idxal et...", + "original": "Orijinal", + "nextChange": "Növbəti dəyişiklik", + "previousChange": "Əvvəlki dəyişiklik", + "changeXOfY": "Dəyişiklik {0} / {1}", + "tooLong": "Çox uzundur", + "tooShort": "Çox qısadır", + "overlapping": "Zamanların kəsişməsi", + "detail": "Təfərrüat", + "fixOcrErrors": "OCR xətalarını düzəlt", + "runningOcrXY": "OCR işləyir... {0}/{1}", + "llamaCppDownloadEnginePrompt": "llama.cpp üçün llama-server mühərriki endirilməlidir. İndi endirilsin?", "abort": "Dayandır", "actor": "Aktyor", "actors": "Aktyorlar", diff --git a/src/ui/Assets/Languages/Bulgarian.json b/src/ui/Assets/Languages/Bulgarian.json index 1059cdedb9e..6fbdba8c8b6 100644 --- a/src/ui/Assets/Languages/Bulgarian.json +++ b/src/ui/Assets/Languages/Bulgarian.json @@ -4,6 +4,47 @@ "translatedBy": "", "cultureName": "bg-BG", "general": { + "prompt": "Подкана", + "temperature": "Температура", + "preset": "Предварително зададено", + "output": "Изход", + "outputFormat": "Изходен формат", + "other": "Друго", + "timeCodeFormat": "Формат на времевия код", + "topBottomMargin": "Горен/долен марж", + "theme": "Тема", + "brightness": "Яркост", + "chooseColor": "Изберете цвят", + "lineX": "Ред {0}", + "playLine": "Пускане на реда", + "maxLineLength": "Максимална дължина на реда", + "maxCharsPerSec": "Максимален брой символи/сек", + "maxNumberOfLines": "Максимален брой редове", + "subtitles": "Субтитри", + "shotChanges": "Промени в кадъра", + "chapters": "Глави", + "audio": "Аудио", + "interjections": "Междуметия", + "blankLines": "Празни редове", + "editWholeText": "Редактиране на целия текст", + "noTextInClipboard": "Няма текст в клипборда", + "noSubtitlesFoundTitle": "Няма намерени субтитри", + "replacedXOccurrences": "Заменени срещания: {0}", + "invalidRegularExpressionX": "Невалиден регулярен израз: {0}", + "discardChangesTitle": "Да се отхвърлят ли промените?", + "downloadingXDotDotDot": "Изтегляне на {0}...", + "importTimeCodesDotDotDot": "Импортиране на времеви кодове...", + "original": "Оригинал", + "nextChange": "Следваща промяна", + "previousChange": "Предишна промяна", + "changeXOfY": "Промяна {0} от {1}", + "tooLong": "Твърде дълго", + "tooShort": "Твърде кратко", + "overlapping": "Припокриване", + "detail": "Детайл", + "fixOcrErrors": "Коригиране на OCR грешки", + "runningOcrXY": "Изпълнява се OCR... {0}/{1}", + "llamaCppDownloadEnginePrompt": "llama.cpp изисква изтегляне на двигателя llama-server. Да се изтегли ли сега?", "abort": "Прекрати", "actor": "Актьор", "actors": "Актьори", @@ -70,7 +111,7 @@ "autoTranslate": "Автоматичен превод", "autodetect": "Автоматично откриване", "backend": "Бекенд", - "background": "Предистория", + "background": "Фон", "backgroundColor": "Цвят на фона", "backward": "Назад", "batchMode": "Пакетен режим", diff --git a/src/ui/Assets/Languages/Catalan.json b/src/ui/Assets/Languages/Catalan.json index 59afbe5dc9f..5bc76d88b80 100644 --- a/src/ui/Assets/Languages/Catalan.json +++ b/src/ui/Assets/Languages/Catalan.json @@ -4,6 +4,46 @@ "translatedBy": "", "cultureName": "ca-ES", "general": { + "prompt": "Indicació", + "temperature": "Temperatura", + "preset": "Predefinit", + "output": "Sortida", + "outputFormat": "Format de sortida", + "other": "Altres", + "timeCodeFormat": "Format del codi de temps", + "topBottomMargin": "Marge superior/inferior", + "theme": "Tema", + "brightness": "Brillantor", + "chooseColor": "Trieu un color", + "lineX": "Línia {0}", + "playLine": "Reprodueix la línia", + "maxLineLength": "Llargària màxima de la línia", + "maxCharsPerSec": "Màxim de caràcters/s", + "maxNumberOfLines": "Nombre màxim de línies", + "subtitles": "Subtítols", + "shotChanges": "Canvis de pla", + "chapters": "Capítols", + "audio": "Àudio", + "interjections": "Interjeccions", + "blankLines": "Línies en blanc", + "editWholeText": "Edita tot el text", + "noTextInClipboard": "No hi ha text al porta-retalls", + "noSubtitlesFoundTitle": "No s'ha trobat cap subtítol", + "replacedXOccurrences": "S'han substituït {0} coincidències", + "invalidRegularExpressionX": "Expressió regular no vàlida: {0}", + "discardChangesTitle": "Voleu descartar els canvis?", + "downloadingXDotDotDot": "S'està baixant {0}...", + "importTimeCodesDotDotDot": "Importa codis de temps...", + "nextChange": "Canvi següent", + "previousChange": "Canvi anterior", + "changeXOfY": "Canvi {0} de {1}", + "tooLong": "Massa llarga", + "tooShort": "Massa curta", + "overlapping": "Superposició", + "detail": "Detall", + "fixOcrErrors": "Corregeix els errors d'OCR", + "runningOcrXY": "S'està executant l'OCR... {0}/{1}", + "llamaCppDownloadEnginePrompt": "El llama.cpp requereix baixar el motor llama-server. Voleu baixar-lo ara?", "abort": "Interromp", "actor": "Actor", "actors": "Actors", diff --git a/src/ui/Assets/Languages/Central Kurdish.json b/src/ui/Assets/Languages/Central Kurdish.json index d7726590353..7e93e5a052a 100644 --- a/src/ui/Assets/Languages/Central Kurdish.json +++ b/src/ui/Assets/Languages/Central Kurdish.json @@ -4,6 +4,46 @@ "translatedBy": "", "cultureName": "ckb-IQ", "general": { + "prompt": "پرۆمپت", + "preset": "پێش-ڕێکخستن", + "output": "دەرچوون", + "outputFormat": "فۆرماتی دەرچوون", + "other": "ئەوانی تر", + "timeCodeFormat": "فۆرماتی کۆدی کات", + "topBottomMargin": "پەراوێزی سەرەوە/خوارەوە", + "theme": "ڕووکار", + "brightness": "ڕووناکی", + "chooseColor": "هەڵبژاردنی ڕەنگ", + "lineX": "دێڕی {0}", + "playLine": "لێدانی دێڕ", + "maxLineLength": "زۆرترین درێژی دێڕ", + "maxCharsPerSec": "زۆرترین پیت/چرکە", + "maxNumberOfLines": "زۆرترین ژمارەی دێڕ", + "subtitles": "ژێرنووسەکان", + "shotChanges": "گۆڕانی دیمەنەکان", + "chapters": "بەشەکان", + "audio": "دەنگ", + "interjections": "هاوار و دەنگەکان", + "blankLines": "دێڕە بۆشەکان", + "editWholeText": "دەستکاری تەواوی دەق", + "noTextInClipboard": "هیچ دەقێک لە کلیپبۆرددا نییە", + "noSubtitlesFoundTitle": "هیچ ژێرنووسێک نەدۆزرایەوە", + "replacedXOccurrences": "{0} دووبارەبوونەوە جێگیرکرانەوە", + "invalidRegularExpressionX": "دەربڕینی ئاسایی هەڵەیە: {0}", + "discardChangesTitle": "پشتگوێخستنی گۆڕانکارییەکان؟", + "downloadingXDotDotDot": "داگرتنی {0}...", + "importTimeCodesDotDotDot": "هێنانەناوی کۆدەکانی کات...", + "original": "ڕەسەن", + "nextChange": "گۆڕانکاری دواتر", + "previousChange": "گۆڕانکاری پێشوو", + "changeXOfY": "گۆڕینی {0} لە {1}", + "tooLong": "زۆر درێژ", + "tooShort": "زۆر کورت", + "overlapping": "تێکەڵبوون", + "detail": "وردەکاری", + "fixOcrErrors": "چاککردنی هەڵەکانی OCR", + "runningOcrXY": "جێبەجێکردنی OCR... {0}/{1}", + "llamaCppDownloadEnginePrompt": "llama.cpp پێویستی بە داگرتنی بزوێنەری llama-server هەیە. ئێستا دابگریت؟", "abort": "وازهێنان", "actor": "ئەکتەر", "actors": "ئەکتەرەکان", diff --git a/src/ui/Assets/Languages/ChineseSimplified.json b/src/ui/Assets/Languages/ChineseSimplified.json index 6aee7a70ace..1853ee4ddda 100644 --- a/src/ui/Assets/Languages/ChineseSimplified.json +++ b/src/ui/Assets/Languages/ChineseSimplified.json @@ -4,6 +4,47 @@ "translatedBy": "wuwufei | FeiXJ | Leon Cheung | larsenlouis | VK | nkh0472 | 蒙太奇字幕组(MontageSubs) | emsb8888-prog", "cultureName": "zh-Hans", "general": { + "prompt": "提示词", + "temperature": "温度", + "preset": "预设", + "output": "输出", + "outputFormat": "输出格式", + "other": "其他", + "timeCodeFormat": "时间码格式", + "topBottomMargin": "上下边距", + "theme": "主题", + "brightness": "亮度", + "chooseColor": "选择颜色", + "lineX": "第 {0} 行", + "playLine": "播放行", + "maxLineLength": "单行最大长度", + "maxCharsPerSec": "最大每秒字频", + "maxNumberOfLines": "最多行数", + "subtitles": "字幕", + "shotChanges": "镜头切换", + "chapters": "章节", + "audio": "音频", + "interjections": "感叹语气词", + "blankLines": "空行", + "editWholeText": "编辑整段文本", + "noTextInClipboard": "剪贴板中无文本", + "noSubtitlesFoundTitle": "未检测到字幕内容", + "replacedXOccurrences": "已替换 {0} 处", + "invalidRegularExpressionX": "无效的正则表达式: {0}", + "discardChangesTitle": "放弃更改?", + "downloadingXDotDotDot": "正在下载 {0}...", + "importTimeCodesDotDotDot": "导入时间码...", + "original": "原始", + "nextChange": "下一处更改", + "previousChange": "上一处更改", + "changeXOfY": "第{0}处修改,共{1}处", + "tooLong": "过长", + "tooShort": "过短", + "overlapping": "重叠", + "detail": "详情", + "fixOcrErrors": "修复 OCR 错误", + "runningOcrXY": "正在执行 OCR... {0}/{1}", + "llamaCppDownloadEnginePrompt": "llama.cpp 需要下载 llama-server 引擎。是否立即下载?", "abort": "中止", "actor": "角色", "actors": "演员", diff --git a/src/ui/Assets/Languages/ChineseTraditional.json b/src/ui/Assets/Languages/ChineseTraditional.json index ac43ac02fa9..86a7ceb9a85 100644 --- a/src/ui/Assets/Languages/ChineseTraditional.json +++ b/src/ui/Assets/Languages/ChineseTraditional.json @@ -4,6 +4,47 @@ "translatedBy": "昇(以下舊譯者:BAWAN、風逸蘭、Jackie Lam、SiderealArt)", "cultureName": "zh-Hant", "general": { + "prompt": "提示詞 (Prompt)", + "temperature": "溫度 (Temperature)", + "preset": "預設集", + "output": "輸出", + "outputFormat": "輸出格式", + "other": "其他", + "timeCodeFormat": "時間碼格式", + "topBottomMargin": "頂端/底端 外邊距", + "theme": "主題", + "brightness": "亮度", + "chooseColor": "選擇顏色", + "lineX": "第 {0} 行", + "playLine": "播放該行", + "maxLineLength": "最大單行長度", + "maxCharsPerSec": "最大 字元/秒 (CPS)", + "maxNumberOfLines": "最大行數", + "subtitles": "字幕", + "shotChanges": "場景變更線", + "chapters": "章節", + "audio": "音訊", + "interjections": "感嘆詞", + "blankLines": "空白行", + "editWholeText": "編輯整個文字", + "noTextInClipboard": "剪貼簿中無文字", + "noSubtitlesFoundTitle": "沒有找到字幕", + "replacedXOccurrences": "已取代 {0} 處", + "invalidRegularExpressionX": "無效的正規表達式:{0}", + "discardChangesTitle": "拋棄變更?", + "downloadingXDotDotDot": "正在下載 {0}...", + "importTimeCodesDotDotDot": "匯入時間碼...", + "original": "原件", + "nextChange": "下一個變更", + "previousChange": "上一個變更", + "changeXOfY": "變更 {0} / {1}", + "tooLong": "太長", + "tooShort": "太短", + "overlapping": "重疊", + "detail": "詳情", + "fixOcrErrors": "修正 OCR 錯誤", + "runningOcrXY": "正在執行 OCR...{0}/{1}", + "llamaCppDownloadEnginePrompt": "llama.cpp 需要下載 llama-server 引擎。立刻下載?", "abort": "中止(_A)", "actor": "角色", "actors": "角色", diff --git a/src/ui/Assets/Languages/Czech.json b/src/ui/Assets/Languages/Czech.json index 6470a6f6234..18856ddc4c8 100644 --- a/src/ui/Assets/Languages/Czech.json +++ b/src/ui/Assets/Languages/Czech.json @@ -4,6 +4,46 @@ "translatedBy": "Matěj Fouma", "cultureName": "cs-CZ", "general": { + "prompt": "Výzva", + "temperature": "Teplota", + "preset": "Předvolba", + "output": "Výstup", + "outputFormat": "Výstupní formát", + "other": "Ostatní", + "timeCodeFormat": "Formát časového kódu", + "topBottomMargin": "Horní/dolní okraj", + "theme": "Motiv", + "brightness": "Jas", + "chooseColor": "Vybrat barvu", + "lineX": "Řádek {0}", + "playLine": "Přehrát řádek", + "maxLineLength": "Max. délka řádku", + "maxCharsPerSec": "Max. znaků/sek.", + "maxNumberOfLines": "Max. počet řádků", + "subtitles": "Titulky", + "shotChanges": "Změny záběrů", + "chapters": "Kapitoly", + "interjections": "Citoslovce", + "blankLines": "Prázdné řádky", + "editWholeText": "Upravit celý text", + "noTextInClipboard": "Ve schránce není žádný text", + "noSubtitlesFoundTitle": "Nenalezeny žádné titulky", + "replacedXOccurrences": "Nahrazeno {0} výskytů", + "invalidRegularExpressionX": "Neplatný regulární výraz: {0}", + "discardChangesTitle": "Zahodit změny?", + "downloadingXDotDotDot": "Stahování {0}…", + "importTimeCodesDotDotDot": "Importovat časové kódy…", + "original": "Originál", + "nextChange": "Následující změna", + "previousChange": "Předchozí změna", + "changeXOfY": "Změnit {0} z {1}", + "tooLong": "Příliš dlouhý", + "tooShort": "Příliš krátký", + "overlapping": "Překrytí", + "detail": "Podrobnosti", + "fixOcrErrors": "Opravit chyby OCR", + "runningOcrXY": "Spuštěno OCR… {0}/{1}", + "llamaCppDownloadEnginePrompt": "llama.cpp vyžaduje stažení modulu llama-server. Chcete jej stáhnout?", "abort": "Přerušit", "actor": "Herec", "actors": "Herci", diff --git a/src/ui/Assets/Languages/Danish.json b/src/ui/Assets/Languages/Danish.json index b9586712dc4..269bbe33088 100644 --- a/src/ui/Assets/Languages/Danish.json +++ b/src/ui/Assets/Languages/Danish.json @@ -4,6 +4,44 @@ "translatedBy": "", "cultureName": "da-DK", "general": { + "temperature": "Temperatur", + "preset": "Forudindstilling", + "outputFormat": "Outputformat", + "other": "Andet", + "timeCodeFormat": "Tidskodeformat", + "topBottomMargin": "Top/bund margin", + "theme": "Tema", + "brightness": "Lysstyrke", + "chooseColor": "Vælg farve", + "lineX": "Linje {0}", + "playLine": "Afspil linje", + "maxLineLength": "Max linjelængde", + "maxCharsPerSec": "Max tegn/sek", + "maxNumberOfLines": "Max antal linjer", + "subtitles": "Undertekster", + "shotChanges": "Sceneskift", + "chapters": "Kapitler", + "audio": "Lyd", + "interjections": "Interjektioner", + "blankLines": "Tomme linjer", + "editWholeText": "Rediger hele teksten", + "noTextInClipboard": "Ingen tekst i udklipsholderen", + "noSubtitlesFoundTitle": "Ingen undertekster fundet", + "replacedXOccurrences": "Erstattede {0} forekomster", + "invalidRegularExpressionX": "Ugyldigt regulært udtryk: {0}", + "discardChangesTitle": "Kassér ændringer?", + "downloadingXDotDotDot": "Downloader {0}...", + "importTimeCodesDotDotDot": "Importer tidskoder...", + "nextChange": "Næste ændring", + "previousChange": "Forrige ændring", + "changeXOfY": "Ændring {0} af {1}", + "tooLong": "For lang", + "tooShort": "For kort", + "overlapping": "Overlappende", + "detail": "Detalje", + "fixOcrErrors": "Ret OCR-fejl", + "runningOcrXY": "Kører OCR... {0}/{1}", + "llamaCppDownloadEnginePrompt": "llama.cpp kræver, at llama-server-motoren downloades. Vil du downloade nu?", "abort": "Afbryd", "actor": "Skuespiller", "actors": "Skuespillere", diff --git a/src/ui/Assets/Languages/Dutch.json b/src/ui/Assets/Languages/Dutch.json index 316fd5347a9..4c91d6dbdcd 100644 --- a/src/ui/Assets/Languages/Dutch.json +++ b/src/ui/Assets/Languages/Dutch.json @@ -4,6 +4,44 @@ "CultureName": "nl-NL", "version": "v5.3.0", "General": { + "Temperature": "Temperatuur", + "Preset": "Voorinstelling", + "Output": "Uitvoer", + "OutputFormat": "Uitvoerformaat", + "Other": "Overig", + "TimeCodeFormat": "Tijdcodeformaat", + "TopBottomMargin": "Marge boven/onder", + "Theme": "Thema", + "Brightness": "Helderheid", + "ChooseColor": "Kleur kiezen", + "LineX": "Regel {0}", + "PlayLine": "Rij afspelen", + "MaxLineLength": "Max. regellengte", + "MaxCharsPerSec": "Max. Kar/s", + "MaxNumberOfLines": "Max. aantal regels", + "Subtitles": "Ondertitels", + "ShotChanges": "Shot-wijzigingen", + "Chapters": "Hoofdstukken", + "Interjections": "Tussenwerpsels", + "BlankLines": "Lege rijen", + "EditWholeText": "Hele tekst bewerken", + "NoTextInClipboard": "Geen tekst op het klembord", + "NoSubtitlesFoundTitle": "Geen ondertitels gevonden", + "ReplacedXOccurrences": "{0} keer vervangen", + "InvalidRegularExpressionX": "Ongeldige reguliere expressie: {0}", + "DiscardChangesTitle": "Wijzigingen negeren?", + "DownloadingXDotDotDot": "{0} downloaden...", + "ImportTimeCodesDotDotDot": "Tijdcodes importeren...", + "Original": "Origineel", + "NextChange": "Volgende wijziging", + "PreviousChange": "Vorige wijziging", + "ChangeXOfY": "Wijziging {0} van {1}", + "TooLong": "Te lang", + "TooShort": "Te kort", + "Overlapping": "Overlappend", + "FixOcrErrors": "OCR-fouten herstellen", + "RunningOcrXY": "OCR wordt uitgevoerd... {0}/{1}", + "LlamaCppDownloadEnginePrompt": "Voor llama.cpp moet de llama-server-engine worden gedownload. Nu downloaden?", "AssistedMove": "Begeleid verplaatsen", "AssistedMoveChooseMove": "Klik op een optie of druk op het nummer ervan om woorden te verplaatsen", "AssistedMoveDotDotDot": "Begeleid verplaatsen...", diff --git a/src/ui/Assets/Languages/Finnish.json b/src/ui/Assets/Languages/Finnish.json index 5c4e248e1ff..209af61e36a 100644 --- a/src/ui/Assets/Languages/Finnish.json +++ b/src/ui/Assets/Languages/Finnish.json @@ -4,6 +4,47 @@ "translatedBy": "LibreTranslate", "cultureName": "fi-FI", "general": { + "prompt": "Kehote", + "temperature": "Lämpötila", + "preset": "Esiasetus", + "output": "Tuloste", + "outputFormat": "Tulostemuoto", + "other": "Muu", + "timeCodeFormat": "Aikakoodin muoto", + "topBottomMargin": "Ylä-/alamarginaali", + "theme": "Teema", + "brightness": "Kirkkaus", + "chooseColor": "Valitse väri", + "lineX": "Rivi {0}", + "playLine": "Toista rivi", + "maxLineLength": "Rivin enimmäispituus", + "maxCharsPerSec": "Merkkien enimmäismäärä sekunnissa", + "maxNumberOfLines": "Rivien enimmäismäärä", + "subtitles": "Tekstitykset", + "shotChanges": "Kuvanvaihdot", + "chapters": "Luvut", + "audio": "Ääni", + "interjections": "Huudahdukset", + "blankLines": "Tyhjät rivit", + "editWholeText": "Muokkaa koko tekstiä", + "noTextInClipboard": "Leikepöydällä ei ole tekstiä", + "noSubtitlesFoundTitle": "Tekstityksiä ei löytynyt", + "replacedXOccurrences": "Korvattiin {0} esiintymää", + "invalidRegularExpressionX": "Virheellinen säännöllinen lauseke: {0}", + "discardChangesTitle": "Hylätäänkö muutokset?", + "downloadingXDotDotDot": "Ladataan {0}...", + "importTimeCodesDotDotDot": "Tuo aikakoodit...", + "original": "Alkuperäinen", + "nextChange": "Seuraava muutos", + "previousChange": "Edellinen muutos", + "changeXOfY": "Muutos {0}/{1}", + "tooLong": "Liian pitkä", + "tooShort": "Liian lyhyt", + "overlapping": "Päällekkäisyys", + "detail": "Tiedot", + "fixOcrErrors": "Korjaa OCR-virheet", + "runningOcrXY": "OCR käynnissä... {0}/{1}", + "llamaCppDownloadEnginePrompt": "llama.cpp vaatii llama-server-moottorin lataamisen. Ladataanko nyt?", "assistedMove": "Avustettu siirto", "assistedMoveChooseMove": "Napsauta vaihtoehtoa tai paina sen numeroa siirtääksesi sanoja", "assistedMoveDotDotDot": "Avustettu siirto...", diff --git a/src/ui/Assets/Languages/French.json b/src/ui/Assets/Languages/French.json index b72ec8cff45..18e92d75aff 100644 --- a/src/ui/Assets/Languages/French.json +++ b/src/ui/Assets/Languages/French.json @@ -4,6 +4,44 @@ "translatedBy": "Need74 . (11.oct.2026)", "cultureName": "fr-FR", "general": { + "prompt": "Invite", + "temperature": "Température", + "preset": "Préréglage", + "output": "Sortie", + "outputFormat": "Format de sortie", + "other": "Autre", + "timeCodeFormat": "Format de l'horodatage", + "topBottomMargin": "Marges haut/bas", + "theme": "Thème", + "brightness": "Luminosité", + "chooseColor": "Choisir la couleur", + "lineX": "Ligne {0}", + "playLine": "Ligne de lecture", + "maxLineLength": "Longueur de lignes max.", + "maxCharsPerSec": "Car./sec max.", + "maxNumberOfLines": "Nbr max. de lignes", + "subtitles": "Sous-titres", + "shotChanges": "Changements de plan", + "chapters": "Chapitres", + "blankLines": "Lignes vides", + "editWholeText": "Éditer tout le texte", + "noTextInClipboard": "Aucun texte dans le presse-papiers", + "noSubtitlesFoundTitle": "Aucun sous-titre trouvé", + "replacedXOccurrences": "Remplacement de {0} occurrences", + "invalidRegularExpressionX": "Expression régulière invalide : {0}", + "discardChangesTitle": "Abandonner les modifications ?", + "downloadingXDotDotDot": "Téléchargement de {0}…", + "importTimeCodesDotDotDot": "Importer les horodatages…", + "nextChange": "Prochain changement", + "previousChange": "Changement précédent", + "changeXOfY": "Modification {0} sur {1}", + "tooLong": "Trop longtemps", + "tooShort": "Trop court", + "overlapping": "Chevauchement", + "detail": "Détail", + "fixOcrErrors": "Corriger les erreurs OCR", + "runningOcrXY": "OCR en cours… {0}/{1}", + "llamaCppDownloadEnginePrompt": "llama.cpp nécessite le téléchargement du moteur llama-server. Télécharger maintenant ?", "abort": "Abandonner", "actor": "Acteur", "actors": "Acteurs", @@ -611,7 +649,7 @@ "singleMode": "Mode simple", "textTotalLength": "Texte - longueur totale", "textSingleLineMaxLength": "Texte - longueur maximale sur une seule ligne", - "size": "Redimensionner", + "size": "Taille", "sizeX": "Redimensionner : {0}%", "skip": "Ignorer", "skipAll": "Ignorer tout", diff --git a/src/ui/Assets/Languages/German.json b/src/ui/Assets/Languages/German.json index 7288709411a..539dd114398 100644 --- a/src/ui/Assets/Languages/German.json +++ b/src/ui/Assets/Languages/German.json @@ -4,6 +4,43 @@ "translatedBy": "Übersetzt von Christoph Kitsche. Überarbeitet von Netspark, Stephan Lenhart und FunkyKnilch.", "cultureName": "de-DE", "general": { + "temperature": "Temperatur", + "preset": "Voreinstellung", + "output": "Ausgabe", + "outputFormat": "Ausgabeformat", + "other": "Sonstiges", + "timeCodeFormat": "Zeitstempelformat", + "topBottomMargin": "Oberer/unterer Rand", + "theme": "Design", + "brightness": "Helligkeit", + "chooseColor": "Farbe wählen", + "lineX": "Zeile {0}", + "playLine": "Zeile abspielen", + "maxLineLength": "Max. Zeilenlänge", + "maxCharsPerSec": "Max. Zeichen/Sek.", + "maxNumberOfLines": "Max. Zeilenanzahl", + "subtitles": "Untertitel", + "shotChanges": "Szenenwechsel", + "chapters": "Kapitel", + "interjections": "Interjektionen", + "blankLines": "Leere Zeilen", + "editWholeText": "Gesamten Text bearbeiten", + "noTextInClipboard": "Kein Text in der Zwischenablage", + "noSubtitlesFoundTitle": "Keine Untertitel gefunden", + "replacedXOccurrences": "{0} Ersetzungen vorgenommen", + "invalidRegularExpressionX": "Ungültiger regulärer Ausdruck: {0}", + "discardChangesTitle": "Änderungen verwerfen?", + "downloadingXDotDotDot": "{0} wird heruntergeladen...", + "importTimeCodesDotDotDot": "Zeitstempel importieren...", + "nextChange": "Nächste Änderung", + "previousChange": "Vorherige Änderung", + "changeXOfY": "Änderung {0} von {1}", + "tooLong": "Zu lang", + "tooShort": "Zu kurz", + "overlapping": "Überlappend", + "fixOcrErrors": "OCR-Fehler korrigieren", + "runningOcrXY": "OCR wird ausgeführt... {0}/{1}", + "llamaCppDownloadEnginePrompt": "llama.cpp erfordert das Herunterladen der llama-server-Engine. Jetzt herunterladen?", "abort": "Abbrechen", "actor": "Darsteller", "actors": "Darsteller", diff --git a/src/ui/Assets/Languages/Greek.json b/src/ui/Assets/Languages/Greek.json index da25f9325ba..06077ab7f66 100644 --- a/src/ui/Assets/Languages/Greek.json +++ b/src/ui/Assets/Languages/Greek.json @@ -4,6 +4,47 @@ "translatedBy": "Claude (Anthropic)", "cultureName": "el-GR", "general": { + "prompt": "Προτροπή", + "temperature": "Θερμοκρασία", + "preset": "Προεπιλογή", + "output": "Έξοδος", + "outputFormat": "Μορφή εξόδου", + "other": "Άλλα", + "timeCodeFormat": "Μορφή χρονικού κώδικα", + "topBottomMargin": "Πάνω/κάτω περιθώριο", + "theme": "Θέμα", + "brightness": "Φωτεινότητα", + "chooseColor": "Επιλογή χρώματος", + "lineX": "Γραμμή {0}", + "playLine": "Αναπαραγωγή γραμμής", + "maxLineLength": "Μέγιστο μήκος γραμμής", + "maxCharsPerSec": "Μέγ. χαρ./δευτ.", + "maxNumberOfLines": "Μέγιστος αριθμός γραμμών", + "subtitles": "Υπότιτλοι", + "shotChanges": "Αλλαγές πλάνου", + "chapters": "Κεφάλαια", + "audio": "Ήχος", + "interjections": "Επιφωνήματα", + "blankLines": "Κενές γραμμές", + "editWholeText": "Επεξεργασία ολόκληρου κειμένου", + "noTextInClipboard": "Δεν υπάρχει κείμενο στο πρόχειρο", + "noSubtitlesFoundTitle": "Δεν βρέθηκαν υπότιτλοι", + "replacedXOccurrences": "Αντικαταστάθηκαν {0} εμφανίσεις", + "invalidRegularExpressionX": "Μη έγκυρη κανονική έκφραση: {0}", + "discardChangesTitle": "Απόρριψη αλλαγών;", + "downloadingXDotDotDot": "Λήψη {0}...", + "importTimeCodesDotDotDot": "Εισαγωγή χρονικών κωδίκων...", + "original": "Αρχικό", + "nextChange": "Επόμενη αλλαγή", + "previousChange": "Προηγούμενη αλλαγή", + "changeXOfY": "Αλλαγή {0} από {1}", + "tooLong": "Πολύ μεγάλη", + "tooShort": "Πολύ σύντομη", + "overlapping": "Επικάλυψη", + "detail": "Λεπτομέρεια", + "fixOcrErrors": "Διόρθωση σφαλμάτων OCR", + "runningOcrXY": "Εκτέλεση OCR... {0}/{1}", + "llamaCppDownloadEnginePrompt": "Το llama.cpp απαιτεί τη λήψη της μηχανής llama-server. Λήψη τώρα;", "abort": "Ματαίωση", "actor": "Ηθοποιός", "actors": "Ηθοποιοί", diff --git a/src/ui/Assets/Languages/Hebrew.json b/src/ui/Assets/Languages/Hebrew.json index 3a4390536ed..04c4cffdddc 100644 --- a/src/ui/Assets/Languages/Hebrew.json +++ b/src/ui/Assets/Languages/Hebrew.json @@ -4,6 +4,47 @@ "translatedBy": "Antigravity & cdtauman", "cultureName": "he-IL", "general": { + "prompt": "הנחיה", + "temperature": "טמפרטורה", + "preset": "פרופיל הגדרות", + "output": "פלט", + "outputFormat": "פורמט פלט", + "other": "אחר", + "timeCodeFormat": "פורמט קוד זמן", + "topBottomMargin": "שוליים עליונים/תחתונים", + "theme": "ערכת נושא", + "brightness": "בהירות", + "chooseColor": "בחר צבע", + "lineX": "שורה {0}", + "playLine": "נגן שורה", + "maxLineLength": "אורך שורה מקסימלי", + "maxCharsPerSec": "קצב תווים מקסימלי לשנייה", + "maxNumberOfLines": "מספר שורות מקסימלי", + "subtitles": "כתוביות", + "shotChanges": "מעברי שוט", + "chapters": "פרקים", + "audio": "שמע", + "interjections": "מילות קריאה", + "blankLines": "שורות ריקות", + "editWholeText": "ערוך את כל הטקסט", + "noTextInClipboard": "אין טקסט בלוח הגזירים", + "noSubtitlesFoundTitle": "לא נמצאו כתוביות", + "replacedXOccurrences": "הוחלפו {0} מופעים", + "invalidRegularExpressionX": "ביטוי רגולרי לא חוקי: {0}", + "discardChangesTitle": "לבטל את השינויים?", + "downloadingXDotDotDot": "מוריד את {0}...", + "importTimeCodesDotDotDot": "ייבוא קודי זמן...", + "original": "מקור", + "nextChange": "השינוי הבא", + "previousChange": "השינוי הקודם", + "changeXOfY": "שינוי {0} מתוך {1}", + "tooLong": "ארוך מדי", + "tooShort": "קצר מדי", + "overlapping": "חופף", + "detail": "פרטים", + "fixOcrErrors": "תקן שגיאות OCR", + "runningOcrXY": "מריץ OCR... {0}/{1}", + "llamaCppDownloadEnginePrompt": "llama.cpp דורש הורדה של מנוע llama-server. להוריד כעת?", "assistedMove": "העברה מונחית", "assistedMoveChooseMove": "לחץ על אפשרות או הקש על מספרה כדי להעביר מילים", "assistedMoveDotDotDot": "העברה מונחית...", diff --git a/src/ui/Assets/Languages/Hungarian.json b/src/ui/Assets/Languages/Hungarian.json index 2539017b57f..8c1793c81da 100644 --- a/src/ui/Assets/Languages/Hungarian.json +++ b/src/ui/Assets/Languages/Hungarian.json @@ -4,6 +4,47 @@ "translatedBy": "Zityi's Translator Te@m", "cultureName": "hu-HU", "general": { + "prompt": "Utasítás", + "temperature": "Hőmérséklet", + "preset": "Előbeállítás", + "output": "Kimenet", + "outputFormat": "Kimeneti formátum", + "other": "Egyéb", + "timeCodeFormat": "Időkód formátum", + "topBottomMargin": "Felső/alsó margó", + "theme": "Téma", + "brightness": "Fényerő", + "chooseColor": "Szín kiválasztása", + "lineX": "{0}. sor", + "playLine": "Sor lejátszása", + "maxLineLength": "Max. sorhossz", + "maxCharsPerSec": "Max. karakter/mp", + "maxNumberOfLines": "Max. sorszám", + "subtitles": "Feliratok", + "shotChanges": "Vágásváltások", + "chapters": "Fejezetek", + "audio": "Hang", + "interjections": "Indulattszavak", + "blankLines": "Üres sorok", + "editWholeText": "Teljes szöveg szerkesztése", + "noTextInClipboard": "Nincs szöveg a vágólapon", + "noSubtitlesFoundTitle": "Nincsenek feliratok", + "replacedXOccurrences": "{0} előfordulás lecserélve", + "invalidRegularExpressionX": "Érvénytelen reguláris kifejezés: {0}", + "discardChangesTitle": "Elveti a módosításokat?", + "downloadingXDotDotDot": "{0} letöltése...", + "importTimeCodesDotDotDot": "Időkódok importálása...", + "original": "Eredeti", + "nextChange": "Következő módosítás", + "previousChange": "Előző módosítás", + "changeXOfY": "Módosítás: {0} / {1}", + "tooLong": "Túl hosszú", + "tooShort": "Túl rövid", + "overlapping": "Átfedő", + "detail": "Részlet", + "fixOcrErrors": "OCR hibák javítása", + "runningOcrXY": "OCR futtatása... {0}/{1}", + "llamaCppDownloadEnginePrompt": "A llama.cpp használatához le kell tölteni a llama-server motort. Letölti most?", "abort": "Megszakítás", "actor": "Szereplő", "actors": "Szereplők", diff --git a/src/ui/Assets/Languages/Indonesian.json b/src/ui/Assets/Languages/Indonesian.json index 5d5fa4ba90d..87195e3383e 100644 --- a/src/ui/Assets/Languages/Indonesian.json +++ b/src/ui/Assets/Languages/Indonesian.json @@ -4,6 +4,43 @@ "translatedBy": "Claude (Anthropic)", "cultureName": "id-ID", "general": { + "preset": "Prasetel", + "output": "Keluaran", + "outputFormat": "Format keluaran", + "other": "Lainnya", + "timeCodeFormat": "Format kode waktu", + "topBottomMargin": "Margin atas/bawah", + "theme": "Tema", + "brightness": "Kecerahan", + "chooseColor": "Pilih warna", + "lineX": "Baris {0}", + "playLine": "Putar baris", + "maxLineLength": "Panjang baris maks", + "maxCharsPerSec": "Maks karakter/dtk", + "maxNumberOfLines": "Jumlah baris maks", + "subtitles": "Subtitel", + "shotChanges": "Perubahan shot", + "chapters": "Bab", + "interjections": "Interjeksi", + "blankLines": "Baris kosong", + "editWholeText": "Edit seluruh teks", + "noTextInClipboard": "Tidak ada teks di papan klip", + "noSubtitlesFoundTitle": "Tidak ada subtitel ditemukan", + "replacedXOccurrences": "{0} kemunculan diganti", + "invalidRegularExpressionX": "Ekspresi reguler tidak valid: {0}", + "discardChangesTitle": "Buang perubahan?", + "downloadingXDotDotDot": "Mengunduh {0}...", + "importTimeCodesDotDotDot": "Impor kode waktu...", + "original": "Asli", + "nextChange": "Perubahan berikutnya", + "previousChange": "Perubahan sebelumnya", + "changeXOfY": "Perubahan {0} dari {1}", + "tooLong": "Terlalu panjang", + "tooShort": "Terlalu pendek", + "overlapping": "Tumpang tindih", + "fixOcrErrors": "Perbaiki kesalahan OCR", + "runningOcrXY": "Menjalankan OCR... {0}/{1}", + "llamaCppDownloadEnginePrompt": "llama.cpp memerlukan mesin llama-server untuk diunduh. Unduh sekarang?", "abort": "Batalkan", "actor": "Aktor", "actors": "Aktor", diff --git a/src/ui/Assets/Languages/Italian.json b/src/ui/Assets/Languages/Italian.json index 68152823a72..f40955c8b76 100644 --- a/src/ui/Assets/Languages/Italian.json +++ b/src/ui/Assets/Languages/Italian.json @@ -4,6 +4,44 @@ "translatedBy": "v. 12.10.2026 di bovirus e GitGianluc", "cultureName": "it-IT", "general": { + "temperature": "Temperatura", + "preset": "Profilo", + "outputFormat": "Formato destinazione", + "other": "Altro", + "timeCodeFormat": "Formato time code", + "topBottomMargin": "Margine superiore/inferiore", + "theme": "Tema", + "brightness": "Luminosità", + "chooseColor": "Scegli colore", + "lineX": "Riga {0}", + "playLine": "Riproduci riga", + "maxLineLength": "Lunghezza massima riga", + "maxCharsPerSec": "Caratteri/sec massimi", + "maxNumberOfLines": "Numero massimo di righe", + "subtitles": "Sottotitoli", + "shotChanges": "Cambiamenti inquadratura", + "chapters": "Capitoli", + "interjections": "Interiezioni", + "blankLines": "Righe vuote", + "editWholeText": "Modifica intero testo", + "noTextInClipboard": "Nessun testo negli appunti", + "noSubtitlesFoundTitle": "Nessun sottotitolo trovato", + "replacedXOccurrences": "Sostituite {0} occorrenze", + "invalidRegularExpressionX": "Espressione regolare non valida: {0}", + "discardChangesTitle": "Vuoi eliminare le modifiche?", + "downloadingXDotDotDot": "Download di {0}...", + "importTimeCodesDotDotDot": "Importa codici temporali...", + "original": "Originale", + "nextChange": "Modifica successiva", + "previousChange": "Modifica precedente", + "changeXOfY": "Modifica {0} / {1}", + "tooLong": "Troppo lunga", + "tooShort": "Troppo corta", + "overlapping": "Sovrapposizione", + "detail": "Dettagli", + "fixOcrErrors": "Correggi errori OCR", + "runningOcrXY": "Esecuzione OCR... {0}/{1}", + "llamaCppDownloadEnginePrompt": "Per usare llama.cpp è necessario scaricare il motore llama-server. Vuoi scaricarlo?", "abort": "Annulla", "actor": "Attore", "actors": "Attori", diff --git a/src/ui/Assets/Languages/Japanese.json b/src/ui/Assets/Languages/Japanese.json index cd2c274be89..865fe89bc9f 100644 --- a/src/ui/Assets/Languages/Japanese.json +++ b/src/ui/Assets/Languages/Japanese.json @@ -4,6 +4,46 @@ "translatedBy": "hisui3393", "cultureName": "jp-JP", "general": { + "prompt": "プロンプト", + "preset": "プリセット", + "output": "出力", + "outputFormat": "出力形式", + "other": "その他", + "timeCodeFormat": "タイムコード形式", + "topBottomMargin": "上下余白", + "theme": "テーマ", + "brightness": "明るさ", + "chooseColor": "色を選択", + "lineX": "行{0}", + "playLine": "行を再生", + "maxLineLength": "最大行長", + "maxCharsPerSec": "最大文字数/秒", + "maxNumberOfLines": "最大行数", + "subtitles": "字幕", + "shotChanges": "ショットチェンジ", + "chapters": "チャプター", + "audio": "音声", + "interjections": "感動詞", + "blankLines": "空行", + "editWholeText": "テキスト全体を編集", + "noTextInClipboard": "クリップボードにテキストがありません", + "noSubtitlesFoundTitle": "字幕が見つかりません", + "replacedXOccurrences": "{0}件を置換しました", + "invalidRegularExpressionX": "正規表現が無効です: {0}", + "discardChangesTitle": "変更を破棄しますか?", + "downloadingXDotDotDot": "{0} をダウンロード中...", + "importTimeCodesDotDotDot": "表示タイミングをインポート...", + "original": "元の状態", + "nextChange": "次の変更", + "previousChange": "前の変更", + "changeXOfY": "{1} 件中 {0} 件目の変更", + "tooLong": "長すぎる", + "tooShort": "短すぎる", + "overlapping": "重複している", + "detail": "詳細", + "fixOcrErrors": "OCRエラーを修正", + "runningOcrXY": "OCR処理中... {0}/{1}", + "llamaCppDownloadEnginePrompt": "llama.cpp を使用するには、llama-server エンジンのダウンロードが必要です。今すぐダウンロードしますか?", "abort": "中止", "actor": "声色", "actors": "声色", diff --git a/src/ui/Assets/Languages/Korean.json b/src/ui/Assets/Languages/Korean.json index 5580861c1a6..1b1436d3569 100644 --- a/src/ui/Assets/Languages/Korean.json +++ b/src/ui/Assets/Languages/Korean.json @@ -4,6 +4,47 @@ "translatedBy": "Hack茶ん, 12시27분(@12si27)", "cultureName": "ko-KR", "general": { + "prompt": "프롬프트", + "temperature": "온도 (Temperature)", + "preset": "사전 설정", + "output": "출력", + "outputFormat": "출력 형식", + "other": "기타", + "timeCodeFormat": "타임코드 형식", + "topBottomMargin": "상단/하단 여백", + "theme": "테마", + "brightness": "밝기", + "chooseColor": "색상 선택", + "lineX": "{0}번 줄", + "playLine": "줄 재생", + "maxLineLength": "최대 줄 길이", + "maxCharsPerSec": "초당 최대 문자 수", + "maxNumberOfLines": "최대 줄 수", + "subtitles": "자막", + "shotChanges": "장면 전환", + "chapters": "챕터", + "audio": "오디오", + "interjections": "감탄사", + "blankLines": "빈 줄", + "editWholeText": "전체 텍스트 편집", + "noTextInClipboard": "클립보드에 텍스트가 없음", + "noSubtitlesFoundTitle": "자막을 찾을 수 없음", + "replacedXOccurrences": "{0}개 항목이 교체되었습니다", + "invalidRegularExpressionX": "잘못된 정규 표현식: {0}", + "discardChangesTitle": "변경 사항을 취소하시겠습니까?", + "downloadingXDotDotDot": "{0} 다운로드 중...", + "importTimeCodesDotDotDot": "타임코드 가져오기...", + "original": "원본", + "nextChange": "다음 변경 사항", + "previousChange": "이전 변경 사항", + "changeXOfY": "변경 {0}/{1}", + "tooLong": "너무 긺", + "tooShort": "너무 짧음", + "overlapping": "겹침", + "detail": "상세 정보", + "fixOcrErrors": "OCR 오류 수정", + "runningOcrXY": "OCR 처리 중... {0}/{1}", + "llamaCppDownloadEnginePrompt": "llama.cpp를 사용하려면 llama-server 엔진을 다운로드해야 합니다. 지금 다운로드하시겠습니까?", "abort": "중단", "actor": "등장인물", "actors": "등장인물", diff --git a/src/ui/Assets/Languages/Macedonian.json b/src/ui/Assets/Languages/Macedonian.json index 74f51841195..40a250fbbe9 100644 --- a/src/ui/Assets/Languages/Macedonian.json +++ b/src/ui/Assets/Languages/Macedonian.json @@ -4,6 +4,47 @@ "translatedBy": "Кирил Жежоски", "cultureName": "mk-MK", "general": { + "prompt": "Подсетник", + "temperature": "Температура", + "preset": "Профил", + "output": "Излез", + "outputFormat": "Излезен формат", + "other": "Друго", + "timeCodeFormat": "Формат на временски код", + "topBottomMargin": "Горна/долна маргина", + "theme": "Тема", + "brightness": "Осветленост", + "chooseColor": "Избери боја", + "lineX": "Линија {0}", + "playLine": "Пушти линија", + "maxLineLength": "Максимум должина на линија", + "maxCharsPerSec": "Максимум знаци/сек", + "maxNumberOfLines": "Максимален број на линии", + "subtitles": "Титлови", + "shotChanges": "Промени на сцени", + "chapters": "Поглавја", + "audio": "Аудио", + "interjections": "Интерјекци", + "blankLines": "Празни линии", + "editWholeText": "Уреди цел текст", + "noTextInClipboard": "Нема текст во привремената меморија", + "noSubtitlesFoundTitle": "Не се пронајдени титлови", + "replacedXOccurrences": "Заменети {0} појавувања", + "invalidRegularExpressionX": "Невалиден регуларен израз: {0}", + "discardChangesTitle": "Да се отфрлат промените?", + "downloadingXDotDotDot": "Преземање {0}...", + "importTimeCodesDotDotDot": "Увези временски кодови...", + "original": "Оригинал", + "nextChange": "Следна промена", + "previousChange": "Претходна промена", + "changeXOfY": "Промена {0} од {1}", + "tooLong": "Предолго", + "tooShort": "Прекратко", + "overlapping": "Преклопување", + "detail": "Детал", + "fixOcrErrors": "Поправи OCR грешки", + "runningOcrXY": "Извршувам OCR... {0}/{1}", + "llamaCppDownloadEnginePrompt": "llama.cpp бара да се преземе llama-server моторот. Да преземам сега?", "assistedMove": "Помогнато поместување", "assistedMoveChooseMove": "Кликни на опција или притисни го нејзиниот број за да поместиш зборови", "assistedMoveDotDotDot": "Помогнато поместување...", diff --git a/src/ui/Assets/Languages/Norwegian.json b/src/ui/Assets/Languages/Norwegian.json index 33ec692656e..6ff0eb041c3 100644 --- a/src/ui/Assets/Languages/Norwegian.json +++ b/src/ui/Assets/Languages/Norwegian.json @@ -4,6 +4,46 @@ "CultureName": "nb-NO", "version": "v5.3.0", "General": { + "Prompt": "Ledetekst", + "Temperature": "Temperatur", + "Preset": "Forhåndsinnstilling", + "Output": "Utdata", + "OutputFormat": "Utdataformat", + "Other": "Annet", + "TimeCodeFormat": "Tidskodeformat", + "TopBottomMargin": "Topp/bunn marg", + "Theme": "Tema", + "Brightness": "Lysstyrke", + "ChooseColor": "Velg farge", + "LineX": "Linje {0}", + "PlayLine": "Spill av linje", + "MaxLineLength": "Maks linjelengde", + "MaxCharsPerSec": "Maks tegn/sek", + "MaxNumberOfLines": "Maks antall linjer", + "Subtitles": "Undertekster", + "ShotChanges": "Sceneskifter", + "Chapters": "Kapitler", + "Audio": "Lyd", + "Interjections": "Interjeksjoner", + "BlankLines": "Tomme linjer", + "EditWholeText": "Rediger hele teksten", + "NoTextInClipboard": "Ingen tekst på utklippstavlen", + "NoSubtitlesFoundTitle": "Ingen undertekster funnet", + "ReplacedXOccurrences": "Erstattet {0} forekomster", + "InvalidRegularExpressionX": "Ugyldig regulært uttrykk: {0}", + "DiscardChangesTitle": "Forkast endringer?", + "DownloadingXDotDotDot": "Laster ned {0}...", + "ImportTimeCodesDotDotDot": "Importer tidskoder...", + "NextChange": "Neste endring", + "PreviousChange": "Forrige endring", + "ChangeXOfY": "Endring {0} av {1}", + "TooLong": "For lang", + "TooShort": "For kort", + "Overlapping": "Overlappende", + "Detail": "Detalj", + "FixOcrErrors": "Fiks OCR-feil", + "RunningOcrXY": "Kjører OCR... {0}/{1}", + "LlamaCppDownloadEnginePrompt": "llama.cpp krever at llama-server-motoren lastes ned. Vil du laste ned nå?", "AssistedMove": "Assistert flytting", "AssistedMoveChooseMove": "Klikk et alternativ eller trykk nummeret for å flytte ord", "AssistedMoveDotDotDot": "Assistert flytting...", diff --git a/src/ui/Assets/Languages/Persian.json b/src/ui/Assets/Languages/Persian.json index ec44ede7dd6..132824dff0e 100644 --- a/src/ui/Assets/Languages/Persian.json +++ b/src/ui/Assets/Languages/Persian.json @@ -4,6 +4,47 @@ "translatedBy": "رامتین جوکار", "cultureName": "fa-IR", "general": { + "prompt": "پرامپت", + "temperature": "دما", + "preset": "پیش‌تنظیم", + "output": "خروجی", + "outputFormat": "قالب خروجی", + "other": "سایر", + "timeCodeFormat": "فرمت کد زمانی", + "topBottomMargin": "حاشیه بالا/پایین", + "theme": "پوسته", + "brightness": "روشنایی", + "chooseColor": "انتخاب رنگ", + "lineX": "خط {0}", + "playLine": "پخش خط", + "maxLineLength": "حداکثر طول خط", + "maxCharsPerSec": "حداکثر نویسه در ثانیه", + "maxNumberOfLines": "حداکثر تعداد خطوط", + "subtitles": "زیرنویس‌ها", + "shotChanges": "تغییرات پلان", + "chapters": "فصل‌ها", + "audio": "صدا", + "interjections": "اصوات نواخت‌ها و میان‌افکندها", + "blankLines": "خطوط خالی", + "editWholeText": "ویرایش کل متن", + "noTextInClipboard": "متنی در حافظه موقت وجود ندارد", + "noSubtitlesFoundTitle": "هیچ زیرنویسی یافت نشد", + "replacedXOccurrences": "{0} مورد جایگزین شد", + "invalidRegularExpressionX": "عبارت باقاعده نامعتبر: {0}", + "discardChangesTitle": "تغییرات نادیده گرفته شوند؟", + "downloadingXDotDotDot": "در حال دانلود {0}...", + "importTimeCodesDotDotDot": "وارد کردن کدهای زمانی...", + "original": "اصلی", + "nextChange": "تغییر بعدی", + "previousChange": "تغییر قبلی", + "changeXOfY": "تغییر {0} از {1}", + "tooLong": "مدت زمان نمایش خیلی طولانی است", + "tooShort": "مدت زمان نمایش خیلی کوتاه است", + "overlapping": "هم‌پوشانی", + "detail": "جزئیات", + "fixOcrErrors": "اصلاح خطاهای OCR", + "runningOcrXY": "در حال اجرای OCR... {0}/{1}", + "llamaCppDownloadEnginePrompt": "llama.cpp به موتور llama-server نیاز دارد که دانلود شود. اکنون دانلود شود؟", "abort": "لغو", "actor": "بازیگر/گوینده", "actors": "بازیگران/گویندگان", diff --git a/src/ui/Assets/Languages/Polish.json b/src/ui/Assets/Languages/Polish.json index a21bc88dfa7..ed8e88852b8 100644 --- a/src/ui/Assets/Languages/Polish.json +++ b/src/ui/Assets/Languages/Polish.json @@ -4,6 +4,46 @@ "translatedBy": "Adam Malich (ostatnia aktualizacja: 14.10.2026)", "cultureName": "pl-PL", "general": { + "prompt": "Instrukcja", + "temperature": "Temperatura", + "preset": "Ustawienie wstępne", + "output": "Wyjście", + "outputFormat": "Format wyjściowy", + "other": "Inne", + "timeCodeFormat": "Format kodu czasowego", + "topBottomMargin": "Margines górny/dolny", + "theme": "Motyw", + "brightness": "Jasność", + "chooseColor": "Wybierz kolor", + "lineX": "Linia {0}", + "playLine": "Odtwarzaj linię", + "maxLineLength": "Maksymalna długość linii", + "maxCharsPerSec": "Maksymalnie znaków na sekundę", + "maxNumberOfLines": "Maksymalna liczba linii", + "subtitles": "Napisy", + "shotChanges": "Zmiany ujęcia", + "chapters": "Rozdziały", + "interjections": "Wykrzykniki", + "blankLines": "Puste linie", + "editWholeText": "Edytuj cały tekst", + "noTextInClipboard": "Brak tekstu w schowku", + "noSubtitlesFoundTitle": "Nie znaleziono napisów", + "replacedXOccurrences": "Zastąpiono {0} wystąpień", + "invalidRegularExpressionX": "Nieprawidłowe wyrażenie regularne: {0}", + "discardChangesTitle": "Odrzucić zmiany?", + "downloadingXDotDotDot": "Pobieranie {0}...", + "importTimeCodesDotDotDot": "Importuj kody czasowe...", + "original": "Oryginał", + "nextChange": "Następna zmiana", + "previousChange": "Poprzednia zmiana", + "changeXOfY": "Zmiana {0} z {1}", + "tooLong": "Za długi", + "tooShort": "Za krótki", + "overlapping": "Nakładanie się", + "detail": "Szczegóły", + "fixOcrErrors": "Napraw błędy OCR", + "runningOcrXY": "Uruchamianie OCR... {0}/{1}", + "llamaCppDownloadEnginePrompt": "Aby uruchomić plik llama.cpp, konieczne jest pobranie silnika llama-server. Pobrać teraz?", "abort": "Przerwij", "actor": "Aktor", "actors": "Aktorzy", diff --git a/src/ui/Assets/Languages/Portuguese (Brazil).json b/src/ui/Assets/Languages/Portuguese (Brazil).json index ad84ca24fab..fdb04bae8b8 100644 --- a/src/ui/Assets/Languages/Portuguese (Brazil).json +++ b/src/ui/Assets/Languages/Portuguese (Brazil).json @@ -4,6 +4,45 @@ "translatedBy": "igorruckert", "cultureName": "pt-BR", "general": { + "temperature": "Temperatura", + "preset": "Predefinição", + "output": "Saída", + "outputFormat": "Formato de saída", + "other": "Outro", + "timeCodeFormat": "Formato de código de tempo", + "topBottomMargin": "Margem superior/inferior", + "theme": "Tema", + "brightness": "Brilho", + "chooseColor": "Escolher cor", + "lineX": "Linha {0}", + "playLine": "Reproduzir linha", + "maxLineLength": "Comprimento máximo da linha", + "maxCharsPerSec": "Máximo de caracteres/s", + "maxNumberOfLines": "Número máximo de linhas", + "subtitles": "Legendas", + "shotChanges": "Mudanças de cena", + "chapters": "Capítulos", + "audio": "Áudio", + "interjections": "Interjeições", + "blankLines": "Linhas em branco", + "editWholeText": "Editar todo o texto", + "noTextInClipboard": "Nenhum texto na área de transferência", + "noSubtitlesFoundTitle": "Nenhuma legenda encontrada", + "replacedXOccurrences": "{0} ocorrências substituídas", + "invalidRegularExpressionX": "Expressão regular inválida: {0}", + "discardChangesTitle": "Descartar alterações?", + "downloadingXDotDotDot": "Baixando {0}...", + "importTimeCodesDotDotDot": "Importar códigos de tempo...", + "nextChange": "Próxima alteração", + "previousChange": "Alteração anterior", + "changeXOfY": "Mudança {0} de {1}", + "tooLong": "Muito longo", + "tooShort": "Muito curto", + "overlapping": "Sobreposição", + "detail": "Detalhe", + "fixOcrErrors": "Corrigir erros de OCR", + "runningOcrXY": "Executando OCR... {0}/{1}", + "llamaCppDownloadEnginePrompt": "llama.cpp requer que o mecanismo do servidor llama seja baixado. Baixar agora?", "abort": "Cancelar", "actor": "Ator", "actors": "Atores", diff --git a/src/ui/Assets/Languages/Portuguese.json b/src/ui/Assets/Languages/Portuguese.json index dea39afb802..ba2113a1ffd 100644 --- a/src/ui/Assets/Languages/Portuguese.json +++ b/src/ui/Assets/Languages/Portuguese.json @@ -4,6 +4,45 @@ "translatedBy": "Hugo Carvalho (hugok79) e Filipe Mota (BlackSpirits)\nE-mail: hugokarvalho@hotmail.com / blackspirits@gmail.com\nAntigo tradutor: Marco Barbosa (moob)", "cultureName": "pt-PT", "general": { + "temperature": "Temperatura", + "preset": "Predefinição", + "output": "Saída", + "outputFormat": "Formato de saída", + "other": "Outras", + "timeCodeFormat": "Formato do tempo", + "topBottomMargin": "Margem superior/inferior", + "theme": "Tema", + "brightness": "Brilho", + "chooseColor": "Escolher cor", + "lineX": "Linha {0}", + "playLine": "Reproduzir linha", + "maxLineLength": "Comprimento máximo da linha", + "maxCharsPerSec": "Máximo de caracteres/s", + "maxNumberOfLines": "Número máximo de linhas", + "subtitles": "Legendas", + "shotChanges": "Mudanças de cena", + "chapters": "Capítulos", + "audio": "Áudio", + "interjections": "Interjeições", + "blankLines": "Linhas em branco", + "editWholeText": "Editar todo o texto", + "noTextInClipboard": "Nenhum texto na área de transferência", + "noSubtitlesFoundTitle": "Nenhuma legenda encontrada", + "replacedXOccurrences": "Substituídas {0} ocorrências", + "invalidRegularExpressionX": "Expressão regular inválida: {0}", + "discardChangesTitle": "Descartar alterações?", + "downloadingXDotDotDot": "A transferir {0}...", + "importTimeCodesDotDotDot": "Importar códigos de tempo...", + "nextChange": "Alteração seguinte", + "previousChange": "Alteração anterior", + "changeXOfY": "Mudança {0} de {1}", + "tooLong": "Demasiado longa", + "tooShort": "Demasiado curta", + "overlapping": "Sobreposta", + "detail": "Detalhe", + "fixOcrErrors": "Corrigir erros de OCR", + "runningOcrXY": "A executar OCR... {0}/{1}", + "llamaCppDownloadEnginePrompt": "O llama.cpp requer a transferência do motor llama-server. Transferir agora?", "assistedMove": "Mover assistido", "assistedMoveChooseMove": "Clique numa opção ou prima o respetivo número para mover palavras", "assistedMoveDotDotDot": "Mover assistido...", diff --git a/src/ui/Assets/Languages/Romanian.json b/src/ui/Assets/Languages/Romanian.json index d9fd42239d1..623f59f44d2 100644 --- a/src/ui/Assets/Languages/Romanian.json +++ b/src/ui/Assets/Languages/Romanian.json @@ -4,6 +4,44 @@ "translatedBy": "zildan v1 05.04.26", "cultureName": "ro-RO", "general": { + "temperature": "Temperatură", + "preset": "Presetat", + "output": "Ieșire", + "outputFormat": "Format de ieșire", + "other": "Altele", + "timeCodeFormat": "Formatul codului de timp", + "topBottomMargin": "Marja de sus/de jos", + "theme": "Temă", + "brightness": "Luminozitate", + "chooseColor": "Alege culoarea", + "lineX": "Linia {0}", + "playLine": "Redați linia", + "maxLineLength": "Lungime maximă a liniei", + "maxCharsPerSec": "Max caractere/sec", + "maxNumberOfLines": "Număr maxim de linii", + "subtitles": "Subtitrări", + "shotChanges": "Schimbări de plan", + "chapters": "Capitole", + "interjections": "Interjecții", + "blankLines": "Linii goale", + "editWholeText": "Editați textul întreg", + "noTextInClipboard": "Niciun text în clipboard", + "noSubtitlesFoundTitle": "Nu s-au găsit subtitrări", + "replacedXOccurrences": "S-au înlocuit {0} apariții", + "invalidRegularExpressionX": "Expresie regulată nevalidă: {0}", + "discardChangesTitle": "Renunțați la modificări?", + "downloadingXDotDotDot": "Se descarcă {0}...", + "importTimeCodesDotDotDot": "Importă coduri de timp...", + "nextChange": "Modificarea următoare", + "previousChange": "Modificarea anterioară", + "changeXOfY": "Modificarea {0} din {1}", + "tooLong": "Prea lungă", + "tooShort": "Prea scurtă", + "overlapping": "Se suprapune", + "detail": "Detaliu", + "fixOcrErrors": "Remediați erorile OCR", + "runningOcrXY": "Se execută OCR... {0}/{1}", + "llamaCppDownloadEnginePrompt": "llama.cpp necesită descărcarea motorului llama-server. Descărcați acum?", "assistedMove": "Mutare asistată", "assistedMoveChooseMove": "Faceți clic pe o opțiune sau apăsați numărul acesteia pentru a muta cuvintele", "assistedMoveDotDotDot": "Mutare asistată...", diff --git a/src/ui/Assets/Languages/Russian.json b/src/ui/Assets/Languages/Russian.json index 6943c5c066d..aa8f3046a6a 100644 --- a/src/ui/Assets/Languages/Russian.json +++ b/src/ui/Assets/Languages/Russian.json @@ -4,6 +4,47 @@ "translatedBy": "", "cultureName": "ru-RU", "general": { + "prompt": "Промпт", + "temperature": "Температура", + "preset": "Предустановка", + "output": "Вывод", + "outputFormat": "Выходной формат", + "other": "Другое", + "timeCodeFormat": "Формат временного кода", + "topBottomMargin": "Отступ сверху/снизу", + "theme": "Тема", + "brightness": "Яркость", + "chooseColor": "Выбрать цвет", + "lineX": "Строка {0}", + "playLine": "Воспроизвести строку", + "maxLineLength": "Максимальная длина строки", + "maxCharsPerSec": "Максимальное количество символов в секунду", + "maxNumberOfLines": "Максимальное количество строк", + "subtitles": "Субтитры", + "shotChanges": "Смены планов", + "chapters": "Главы", + "audio": "Аудио", + "interjections": "Междометия", + "blankLines": "Пустые строки", + "editWholeText": "Отредактировать весь текст", + "noTextInClipboard": "В буфере обмена нет текста", + "noSubtitlesFoundTitle": "Субтитры не найдены", + "replacedXOccurrences": "Заменено вхождений: {0}", + "invalidRegularExpressionX": "Недопустимое регулярное выражение: {0}", + "discardChangesTitle": "Отменить изменения?", + "downloadingXDotDotDot": "Скачивание {0}...", + "importTimeCodesDotDotDot": "Импорт временных кодов...", + "original": "Оригинал", + "nextChange": "Следующее изменение", + "previousChange": "Предыдущее изменение", + "changeXOfY": "Изменение {0} из {1}", + "tooLong": "Слишком длинные", + "tooShort": "Слишком короткие", + "overlapping": "Перекрывающиеся", + "detail": "Подробности", + "fixOcrErrors": "Исправить ошибки OCR", + "runningOcrXY": "Выполняется OCR... {0}/{1}", + "llamaCppDownloadEnginePrompt": "Для llama.cpp нужно скачать движок llama-server. Скачать сейчас?", "abort": "Отмена", "actor": "Актер", "actors": "Актеры", diff --git a/src/ui/Assets/Languages/Spanish.json b/src/ui/Assets/Languages/Spanish.json index e40467de7f8..c0129d7f21a 100644 --- a/src/ui/Assets/Languages/Spanish.json +++ b/src/ui/Assets/Languages/Spanish.json @@ -4,6 +4,45 @@ "CultureName": "es-ES", "version": "v5.3.0", "General": { + "Prompt": "Indicación", + "Temperature": "Temperatura", + "Preset": "Ajuste", + "Output": "Salida", + "OutputFormat": "Formato de salida", + "Other": "Otros", + "TimeCodeFormat": "Formato del código de tiempo", + "TopBottomMargin": "Margen sup./inf.", + "Theme": "Tema", + "Brightness": "Brillo", + "ChooseColor": "Elegir color", + "LineX": "Línea {0}", + "PlayLine": "Reproducir línea", + "MaxLineLength": "Longitud máxima de línea", + "MaxCharsPerSec": "CPS máximos", + "MaxNumberOfLines": "Número máx. de líneas", + "Subtitles": "Subtítulos", + "ShotChanges": "Cambios de plano", + "Chapters": "Capítulos", + "Interjections": "Interjecciones", + "BlankLines": "Líneas en blanco", + "EditWholeText": "Editar todo el texto", + "NoTextInClipboard": "No hay texto en el portapapeles", + "NoSubtitlesFoundTitle": "No se encontraron subtítulos", + "ReplacedXOccurrences": "Se reemplazaron {0} coincidencias", + "InvalidRegularExpressionX": "Expresión regular no válida: {0}", + "DiscardChangesTitle": "¿Descartar los cambios?", + "DownloadingXDotDotDot": "Descargando {0}...", + "ImportTimeCodesDotDotDot": "Importar códigos de tiempo...", + "NextChange": "Cambio siguiente", + "PreviousChange": "Cambio anterior", + "ChangeXOfY": "Cambio {0} de {1}", + "TooLong": "Demasiado larga", + "TooShort": "Demasiado corta", + "Overlapping": "Superposición", + "Detail": "Detalle", + "FixOcrErrors": "Corregir errores de OCR", + "RunningOcrXY": "Ejecutando OCR... {0}/{1}", + "LlamaCppDownloadEnginePrompt": "llama.cpp necesita que se descargue el motor llama-server. ¿Descargar ahora?", "AssistedMove": "Movimiento asistido", "AssistedMoveChooseMove": "Haga clic en una opción o pulse su número para mover palabras", "AssistedMoveDotDotDot": "Movimiento asistido...", diff --git a/src/ui/Assets/Languages/Swedish.json b/src/ui/Assets/Languages/Swedish.json index 58344968f6c..4d8923d1e93 100644 --- a/src/ui/Assets/Languages/Swedish.json +++ b/src/ui/Assets/Languages/Swedish.json @@ -4,6 +4,45 @@ "translatedBy": "Claude (Opus 4.7)", "cultureName": "sv-SE", "general": { + "temperature": "Temperatur", + "preset": "Förinställning", + "output": "Utdata", + "outputFormat": "Utdataformat", + "other": "Övrigt", + "timeCodeFormat": "Tidskodsformat", + "topBottomMargin": "Topp/botten marginal", + "theme": "Tema", + "brightness": "Ljusstyrka", + "chooseColor": "Välj färg", + "lineX": "Rad {0}", + "playLine": "Spela rad", + "maxLineLength": "Max radlängd", + "maxCharsPerSec": "Max tecken/sek", + "maxNumberOfLines": "Max antal rader", + "subtitles": "Undertexter", + "shotChanges": "Scenbyten", + "chapters": "Kapitel", + "audio": "Ljud", + "interjections": "Interjektioner", + "blankLines": "Tomma rader", + "editWholeText": "Redigera hela texten", + "noTextInClipboard": "Ingen text i urklipp", + "noSubtitlesFoundTitle": "Inga undertexter hittades", + "replacedXOccurrences": "Ersatte {0} förekomster", + "invalidRegularExpressionX": "Ogiltigt reguljärt uttryck: {0}", + "discardChangesTitle": "Kasta ändringar?", + "downloadingXDotDotDot": "Laddar ner {0}...", + "importTimeCodesDotDotDot": "Importera tidskoder...", + "nextChange": "Nästa ändring", + "previousChange": "Föregående ändring", + "changeXOfY": "Ändring {0} av {1}", + "tooLong": "För lång", + "tooShort": "För kort", + "overlapping": "Överlapp", + "detail": "Detalj", + "fixOcrErrors": "Åtgärda OCR-fel", + "runningOcrXY": "Kör OCR... {0}/{1}", + "llamaCppDownloadEnginePrompt": "llama.cpp kräver att llama-server-motorn laddas ner. Ladda ner nu?", "abort": "Avbryt", "actor": "Skådespelare", "actors": "Skådespelare", diff --git a/src/ui/Assets/Languages/Thai.json b/src/ui/Assets/Languages/Thai.json index 6aad6cda459..b8e2d7a19c1 100644 --- a/src/ui/Assets/Languages/Thai.json +++ b/src/ui/Assets/Languages/Thai.json @@ -4,6 +4,46 @@ "translatedBy": "Claude (Anthropic)", "cultureName": "th-TH", "general": { + "prompt": "พรอมต์", + "preset": "พรีเซ็ต", + "output": "ผลลัพธ์", + "outputFormat": "รูปแบบผลลัพธ์", + "other": "อื่นๆ", + "timeCodeFormat": "รูปแบบไทม์โค้ด", + "topBottomMargin": "ระยะขอบบน/ล่าง", + "theme": "ธีม", + "brightness": "ความสว่าง", + "chooseColor": "เลือกสี", + "lineX": "บรรทัด {0}", + "playLine": "เล่นบรรทัด", + "maxLineLength": "ความยาวบรรทัดสูงสุด", + "maxCharsPerSec": "อักขระ/วินาทีสูงสุด", + "maxNumberOfLines": "จำนวนบรรทัดสูงสุด", + "subtitles": "คำบรรยาย", + "shotChanges": "จุดเปลี่ยนฉาก", + "chapters": "บท", + "audio": "เสียง", + "interjections": "คำอุทาน", + "blankLines": "บรรทัดว่าง", + "editWholeText": "แก้ไขข้อความทั้งหมด", + "noTextInClipboard": "ไม่มีข้อความในคลิปบอร์ด", + "noSubtitlesFoundTitle": "ไม่พบคำบรรยาย", + "replacedXOccurrences": "แทนที่แล้ว {0} ตำแหน่ง", + "invalidRegularExpressionX": "นิพจน์ทั่วไปไม่ถูกต้อง: {0}", + "discardChangesTitle": "ละทิ้งการเปลี่ยนแปลง?", + "downloadingXDotDotDot": "กำลังดาวน์โหลด {0}...", + "importTimeCodesDotDotDot": "นำเข้ารหัสเวลา...", + "original": "ต้นฉบับ", + "nextChange": "การเปลี่ยนแปลงถัดไป", + "previousChange": "การเปลี่ยนแปลงก่อนหน้า", + "changeXOfY": "การเปลี่ยนแปลง {0} จาก {1}", + "tooLong": "ยาวเกินไป", + "tooShort": "สั้นเกินไป", + "overlapping": "ซ้อนทับกัน", + "detail": "รายละเอียด", + "fixOcrErrors": "แก้ไขข้อผิดพลาด OCR", + "runningOcrXY": "กำลังทำ OCR... {0}/{1}", + "llamaCppDownloadEnginePrompt": "llama.cpp ต้องดาวน์โหลดเอนจิน llama-server ดาวน์โหลดตอนนี้หรือไม่?", "abort": "ยกเลิกกลางคัน", "actor": "นักแสดง", "actors": "นักแสดง", diff --git a/src/ui/Assets/Languages/Turkish.json b/src/ui/Assets/Languages/Turkish.json index 49403eab7f9..2aa4046d878 100644 --- a/src/ui/Assets/Languages/Turkish.json +++ b/src/ui/Assets/Languages/Turkish.json @@ -4,6 +4,47 @@ "translatedBy": "Utku Gogus; Son güncelleme: Ahmet Murat Özhan (11.10.2026)", "cultureName": "tr-TR", "general": { + "prompt": "Komut istemi", + "temperature": "Sıcaklık (Temperature)", + "preset": "Hazır ayar", + "output": "Çıktı", + "outputFormat": "Çıktı biçimi", + "other": "Diğer", + "timeCodeFormat": "Zaman kodu biçimi", + "topBottomMargin": "Üst/alt kenar boşluğu", + "theme": "Tema", + "brightness": "Parlaklık", + "chooseColor": "Renk seç", + "lineX": "Satır {0}", + "playLine": "Satırı oynat", + "maxLineLength": "Maks. satır uzunluğu", + "maxCharsPerSec": "Maks Kar/sn", + "maxNumberOfLines": "Maksimum satır sayısı", + "subtitles": "Alt yazılar", + "shotChanges": "Çekim değişimleri", + "chapters": "Bölümler", + "audio": "Ses", + "interjections": "Ünlemler / Tepki sözcükleri", + "blankLines": "Boş satırlar", + "editWholeText": "Tüm metni düzenle", + "noTextInClipboard": "Panoda metin yok", + "noSubtitlesFoundTitle": "Alt yazı bulunamadı", + "replacedXOccurrences": "{0} eşleşme değiştirildi", + "invalidRegularExpressionX": "Geçersiz düzenli ifade: {0}", + "discardChangesTitle": "Değişiklikler yok sayılsın mı?", + "downloadingXDotDotDot": "{0} indiriliyor...", + "importTimeCodesDotDotDot": "Zaman kodlarını içe aktar...", + "original": "Orijinal", + "nextChange": "Sonraki değişiklik", + "previousChange": "Önceki değişiklik", + "changeXOfY": "Değişiklik: {0} / {1}", + "tooLong": "Çok uzun", + "tooShort": "Çok kısa", + "overlapping": "Çakışan", + "detail": "Ayrıntı", + "fixOcrErrors": "OCR hatalarını düzelt", + "runningOcrXY": "OCR çalışıyor... {0}/{1}", + "llamaCppDownloadEnginePrompt": "llama.cpp, llama-server motorunun indirilmesini gerektiriyor. Şimdi indirilsin mi?", "abort": "İptal", "actor": "Aktör", "actors": "Aktörler", diff --git a/src/ui/Assets/Languages/Ukrainian.json b/src/ui/Assets/Languages/Ukrainian.json index 503050b496b..8a1597582f5 100644 --- a/src/ui/Assets/Languages/Ukrainian.json +++ b/src/ui/Assets/Languages/Ukrainian.json @@ -4,6 +4,47 @@ "translatedBy": "Kilo Code", "cultureName": "uk-UA", "general": { + "prompt": "Підказка", + "temperature": "Температура", + "preset": "Попередньо встановлено", + "output": "Вивід", + "outputFormat": "Вихідний формат", + "other": "Інше", + "timeCodeFormat": "Формат коду часу", + "topBottomMargin": "Верхнє/нижнє поле", + "theme": "Тема", + "brightness": "Яскравість", + "chooseColor": "Вибрати колір", + "lineX": "Рядок {0}", + "playLine": "Відтворити рядок", + "maxLineLength": "Макс. довжина рядка", + "maxCharsPerSec": "Максимальні символи/с", + "maxNumberOfLines": "Максимальна кількість рядків", + "subtitles": "Субтитри", + "shotChanges": "Зміни кадрів", + "chapters": "Розділи", + "audio": "Аудіо", + "interjections": "Вигуки", + "blankLines": "Порожні рядки", + "editWholeText": "Редагувати весь текст", + "noTextInClipboard": "Немає тексту в буфері обміну", + "noSubtitlesFoundTitle": "Субтитри не знайдено", + "replacedXOccurrences": "Замінено входжень: {0}", + "invalidRegularExpressionX": "Неприпустимий регулярний вираз: {0}", + "discardChangesTitle": "Відхилити зміни?", + "downloadingXDotDotDot": "Завантаження {0}...", + "importTimeCodesDotDotDot": "Імпорт кодів часу...", + "original": "Оригінал", + "nextChange": "Наступна зміна", + "previousChange": "Попередня зміна", + "changeXOfY": "Зміна {0} з {1}", + "tooLong": "Задовго", + "tooShort": "Закоротко", + "overlapping": "Перекриття", + "detail": "Подробиці", + "fixOcrErrors": "Виправляти помилки OCR", + "runningOcrXY": "Виконання OCR... {0}/{1}", + "llamaCppDownloadEnginePrompt": "llama.cpp потребує завантаження двигуна llama-server. Завантажити зараз?", "assistedMove": "Кероване переміщення", "assistedMoveChooseMove": "Клацніть варіант або натисніть його номер, щоб перемістити слова", "assistedMoveDotDotDot": "Кероване переміщення...", diff --git a/src/ui/Assets/Languages/Vietnamese.json b/src/ui/Assets/Languages/Vietnamese.json index b1239cdec47..5855f2328fa 100644 --- a/src/ui/Assets/Languages/Vietnamese.json +++ b/src/ui/Assets/Languages/Vietnamese.json @@ -4,6 +4,46 @@ "translatedBy": "Claude (Anthropic)", "cultureName": "vi-VN", "general": { + "prompt": "Lời nhắc", + "preset": "Cấu hình sẵn", + "output": "Đầu ra", + "outputFormat": "Định dạng đầu ra", + "other": "Khác", + "timeCodeFormat": "Định dạng mã thời gian", + "topBottomMargin": "Lề trên/dưới", + "theme": "Chủ đề", + "brightness": "Độ sáng", + "chooseColor": "Chọn màu", + "lineX": "Dòng {0}", + "playLine": "Phát dòng", + "maxLineLength": "Độ dài dòng tối đa", + "maxCharsPerSec": "Số ký tự/giây tối đa", + "maxNumberOfLines": "Số dòng tối đa", + "subtitles": "Phụ đề", + "shotChanges": "Điểm chuyển cảnh", + "chapters": "Chương", + "audio": "Âm thanh", + "interjections": "Thán từ", + "blankLines": "Dòng trống", + "editWholeText": "Chỉnh sửa toàn bộ văn bản", + "noTextInClipboard": "Không có văn bản trong bộ nhớ tạm", + "noSubtitlesFoundTitle": "Không tìm thấy phụ đề", + "replacedXOccurrences": "Đã thay thế {0} lần xuất hiện", + "invalidRegularExpressionX": "Biểu thức chính quy không hợp lệ: {0}", + "discardChangesTitle": "Bỏ các thay đổi?", + "downloadingXDotDotDot": "Đang tải xuống {0}...", + "importTimeCodesDotDotDot": "Nhập mã thời gian...", + "original": "Gốc", + "nextChange": "Thay đổi tiếp theo", + "previousChange": "Thay đổi trước", + "changeXOfY": "Thay đổi {0} trên {1}", + "tooLong": "Quá dài", + "tooShort": "Quá ngắn", + "overlapping": "Chồng lấn", + "detail": "Chi tiết", + "fixOcrErrors": "Sửa lỗi OCR", + "runningOcrXY": "Đang chạy OCR... {0}/{1}", + "llamaCppDownloadEnginePrompt": "llama.cpp cần tải xuống công cụ llama-server. Tải xuống ngay?", "abort": "Hủy bỏ", "actor": "Diễn viên", "actors": "Diễn viên", diff --git a/src/ui/Features/Assa/AssaApplyAdvancedEffect/AssaApplyAdvancedEffectWindow.cs b/src/ui/Features/Assa/AssaApplyAdvancedEffect/AssaApplyAdvancedEffectWindow.cs index 9c8947f7513..449646e7417 100644 --- a/src/ui/Features/Assa/AssaApplyAdvancedEffect/AssaApplyAdvancedEffectWindow.cs +++ b/src/ui/Features/Assa/AssaApplyAdvancedEffect/AssaApplyAdvancedEffectWindow.cs @@ -578,7 +578,7 @@ void AddRow(string label, Control control) break; } case AdvancedEffectHighlightBox highlightBox: - AddRow(l.AdvancedEffectBoxColor, UiUtil.MakeColorPickerButton(highlightBox, nameof(AdvancedEffectHighlightBox.BoxColor), false)); + AddRow(Se.Language.General.BoxColor, UiUtil.MakeColorPickerButton(highlightBox, nameof(AdvancedEffectHighlightBox.BoxColor), false)); AddRow(l.AdvancedEffectFancyKaraokeActiveColor, UiUtil.MakeColorPickerButton(highlightBox, nameof(AdvancedEffectHighlightBox.ActiveTextColor), false)); AddRow(l.AdvancedEffectUpperCase, UiUtil.MakeCheckBox(highlightBox, nameof(AdvancedEffectHighlightBox.UpperCase))); break; diff --git a/src/ui/Features/Assa/AssaDraw/AssaDrawAnimationPanel.cs b/src/ui/Features/Assa/AssaDraw/AssaDrawAnimationPanel.cs index 9fb0bd70df0..85dffafc083 100644 --- a/src/ui/Features/Assa/AssaDraw/AssaDrawAnimationPanel.cs +++ b/src/ui/Features/Assa/AssaDraw/AssaDrawAnimationPanel.cs @@ -255,7 +255,7 @@ public static string GetLoopName(DrawLoopEffect effect) DrawLoopEffect.Shake => l.DrawAnimationShake, DrawLoopEffect.Swing => l.DrawAnimationSwing, DrawLoopEffect.Flip => l.DrawAnimationFlip, - _ => l.DrawAnimationNone, + _ => Se.Language.General.None, }; } @@ -318,7 +318,7 @@ public static string GetName(DrawAnimationEffect effect, bool isOut) DrawAnimationEffect.Right => isOut ? l.DrawAnimationToRight : l.DrawAnimationFromRight, DrawAnimationEffect.Top => isOut ? l.DrawAnimationToTop : l.DrawAnimationFromTop, DrawAnimationEffect.Bottom => isOut ? l.DrawAnimationToBottom : l.DrawAnimationFromBottom, - _ => l.DrawAnimationNone, + _ => Se.Language.General.None, }; } } diff --git a/src/ui/Features/Assa/AssaDraw/AssaDrawViewModel.cs b/src/ui/Features/Assa/AssaDraw/AssaDrawViewModel.cs index bc4612b2e12..a94e93a851d 100644 --- a/src/ui/Features/Assa/AssaDraw/AssaDrawViewModel.cs +++ b/src/ui/Features/Assa/AssaDraw/AssaDrawViewModel.cs @@ -2266,7 +2266,7 @@ private async Task BackgroundFromImage() } var fileName = await _fileHelper.PickOpenFile(Window, Se.Language.Assa.DrawBackgroundImage.TrimEnd('.'), - Se.Language.Assa.DrawImages, "*.png;*.jpg;*.jpeg;*.bmp;*.webp"); + Se.Language.General.Images, "*.png;*.jpg;*.jpeg;*.bmp;*.webp"); if (!string.IsNullOrEmpty(fileName) && !LoadImageBackground(fileName)) { await MessageBox.Show(Window, Se.Language.General.Error, Path.GetFileName(fileName), MessageBoxButtons.OK, MessageBoxIcon.Error); @@ -3174,7 +3174,7 @@ internal async Task ConfirmDiscardChanges() return true; } - var result = await MessageBox.Show(Window, Se.Language.Assa.DrawDiscardChangesTitle, Se.Language.Assa.DrawDiscardChanges, + var result = await MessageBox.Show(Window, Se.Language.General.DiscardChangesTitle, Se.Language.Assa.DrawDiscardChanges, MessageBoxButtons.YesNo, MessageBoxIcon.Question); return result == MessageBoxResult.Yes; } diff --git a/src/ui/Features/Assa/AssaDraw/AssaDrawWindow.cs b/src/ui/Features/Assa/AssaDraw/AssaDrawWindow.cs index c78bfe5a796..f608cc112cc 100644 --- a/src/ui/Features/Assa/AssaDraw/AssaDrawWindow.cs +++ b/src/ui/Features/Assa/AssaDraw/AssaDrawWindow.cs @@ -336,7 +336,7 @@ private void AddCanvasItems(List items) AddSeparator(items); items.Add(MakeMenuItem(Se.Language.Assa.DrawImportSvg, "fa-solid fa-file-import", _vm.ImportSvgCommand)); - var background = MakeMenuItem(Se.Language.Assa.DrawBackground, "fa-regular fa-image", null); + var background = MakeMenuItem(Se.Language.General.Background, "fa-regular fa-image", null); background.ItemsSource = MakeBackgroundItems(_vm); items.Add(background); @@ -436,7 +436,7 @@ private static Border CreateToolbar(AssaDrawViewModel vm) leftPanel.Children.Add(MakeToolbarSeparator()); // Background: video frame or image to trace over, with its opacity - var backgroundButton = CreateToolButton("fa-regular fa-image", Se.Language.Assa.DrawBackground, null); + var backgroundButton = CreateToolButton("fa-regular fa-image", Se.Language.General.Background, null); BindHighlight(backgroundButton, vm, () => vm.HasBackground, nameof(vm.HasBackground)); // Items depend on the current state (video loaded, background set), so the menu is built on // click - a MenuFlyout filled in its Opening event showed up empty. @@ -1006,17 +1006,17 @@ private static Border MakePropertySection(string title, Control content, string private static Border CreateShapePropertiesPanel(AssaDrawViewModel vm) { var rows = new StackPanel { Spacing = 6 }; - rows.Children.Add(MakePropertyRow(Se.Language.Assa.DrawPosition, + rows.Children.Add(MakePropertyRow(Se.Language.General.Position, MakeField(nameof(vm.ShapeX), -10000, 10000, prefix: "X", format: "0.#"), MakeField(nameof(vm.ShapeY), -10000, 10000, prefix: "Y", format: "0.#"))); - rows.Children.Add(MakePropertyRow(Se.Language.Assa.DrawSize, + rows.Children.Add(MakePropertyRow(Se.Language.General.Size, MakeField(nameof(vm.ShapeWidth), 0, 10000, prefix: "W", format: "0.#"), MakeField(nameof(vm.ShapeHeight), 0, 10000, prefix: "H", format: "0.#"))); var colorButton = UiUtil.MakeColorPickerButton(vm, nameof(vm.LayerColor)); colorButton.HorizontalAlignment = HorizontalAlignment.Left; colorButton.Bind(Button.IsVisibleProperty, new Binding(nameof(vm.IsDrawShapeSelected))); - rows.Children.Add(MakePropertyRow(Se.Language.Assa.DrawLayer, + rows.Children.Add(MakePropertyRow(Se.Language.General.Layer, MakeField(nameof(vm.ShapeLayer), 0, 1000), colorButton)); rows.Children.Add(MakeOutlineRow(vm)); @@ -1120,10 +1120,10 @@ private Border CreateTextPropertiesPanel(AssaDrawViewModel vm) textColor.HorizontalAlignment = HorizontalAlignment.Left; if (Se.Settings.Appearance.ShowHints) { - ToolTip.SetTip(textColor, Se.Language.Assa.DrawTextColor); + ToolTip.SetTip(textColor, Se.Language.General.TextColor); } - AutomationProperties.SetName(textColor, Se.Language.Assa.DrawTextColor); + AutomationProperties.SetName(textColor, Se.Language.General.TextColor); rows.Children.Add(MakePropertyRow(Se.Language.General.FontSize, sizeAndBold, textColor)); var shrink = new CheckBox @@ -1156,7 +1156,7 @@ private static Border CreateLayerPropertiesPanel(AssaDrawViewModel vm) private static Border CreatePointPropertiesPanel(AssaDrawViewModel vm) { - var row = MakePropertyRow(Se.Language.Assa.DrawPosition, + var row = MakePropertyRow(Se.Language.General.Position, MakeField(nameof(vm.PointX), -10000, 10000, prefix: "X", format: "0.#"), MakeField(nameof(vm.PointY), -10000, 10000, prefix: "Y", format: "0.#")); return MakePropertySection(Se.Language.Assa.DrawSelectedPoint, row, nameof(vm.IsPointSelected)); diff --git a/src/ui/Features/Assa/AssaProgressBar/AssaProgressBarWindow.cs b/src/ui/Features/Assa/AssaProgressBar/AssaProgressBarWindow.cs index b6046c53ad9..e686e93f75e 100644 --- a/src/ui/Features/Assa/AssaProgressBar/AssaProgressBarWindow.cs +++ b/src/ui/Features/Assa/AssaProgressBar/AssaProgressBarWindow.cs @@ -316,7 +316,7 @@ private static Border CreateChaptersSection(AssaProgressBarViewModel vm) var content = new StackPanel { Spacing = 8 }; content.Children.Add(listBox); content.Children.Add(editor); - return EditorChrome.MakeEditorSection(Se.Language.Assa.ProgressBarChapters, content, buttons); + return EditorChrome.MakeEditorSection(Se.Language.General.Chapters, content, buttons); } private static Border CreateChapterTextSection(AssaProgressBarViewModel vm) @@ -355,7 +355,7 @@ private static Border CreateChapterTextSection(AssaProgressBarViewModel vm) content.Children.Add(EditorChrome.MakePropertyRow(Se.Language.General.Size, size, alignment)); content.Children.Add(EditorChrome.MakePropertyRow(Se.Language.General.Position, x, y)); content.Children.Add(EditorChrome.MakePropertyRow(Se.Language.Assa.ProgressBarSplitter, splitterWidth, splitterHeight)); - return EditorChrome.MakeEditorSection(Se.Language.Assa.ProgressBarChapters + " - " + Se.Language.General.Text, content); + return EditorChrome.MakeEditorSection(Se.Language.General.Chapters + " - " + Se.Language.General.Text, content); } private static Border CreateButtonBar(AssaProgressBarViewModel vm) diff --git a/src/ui/Features/Assa/AssaSetPosition/AssaSetPositionViewModel.cs b/src/ui/Features/Assa/AssaSetPosition/AssaSetPositionViewModel.cs index d90b1bfcbe2..8e6300542ad 100644 --- a/src/ui/Features/Assa/AssaSetPosition/AssaSetPositionViewModel.cs +++ b/src/ui/Features/Assa/AssaSetPosition/AssaSetPositionViewModel.cs @@ -80,7 +80,7 @@ public partial class AssaSetPositionViewModel : ObservableObject public static IReadOnlyList AnchorNames => [ Se.Language.Assa.SetPositionTopLeft, Se.Language.Assa.SetPositionTopCenter, Se.Language.Assa.SetPositionTopRight, - Se.Language.Assa.SetPositionMiddleLeft, Se.Language.Assa.SetPositionCenter, Se.Language.Assa.SetPositionMiddleRight, + Se.Language.Assa.SetPositionMiddleLeft, Se.Language.General.Center, Se.Language.Assa.SetPositionMiddleRight, Se.Language.Assa.SetPositionBottomLeft, Se.Language.Assa.SetPositionBottomCenter, Se.Language.Assa.SetPositionBottomRight, ]; diff --git a/src/ui/Features/Assa/AssaStylesWindow.cs b/src/ui/Features/Assa/AssaStylesWindow.cs index a75737b2a05..bdd8562ae55 100644 --- a/src/ui/Features/Assa/AssaStylesWindow.cs +++ b/src/ui/Features/Assa/AssaStylesWindow.cs @@ -724,7 +724,7 @@ private static Control MakePreview(AssaStylesViewModel vm) VerticalAlignment = VerticalAlignment.Center, Margin = new Thickness(0, 0, 6, 0), }); - var names = new[] { Se.Language.Assa.StylesPreviewDark, Se.Language.Assa.StylesPreviewChecker, Se.Language.Assa.StylesPreviewLight }; + var names = new[] { Se.Language.General.Dark, Se.Language.Assa.StylesPreviewChecker, Se.Language.General.Light }; for (var i = 0; i < names.Length; i++) { var index = i; @@ -759,7 +759,7 @@ private static Control MakePreview(AssaStylesViewModel vm) VerticalAlignment = VerticalAlignment.Center, [!TextBox.TextProperty] = new Binding(nameof(vm.PreviewText)) { Mode = BindingMode.TwoWay }, }; - AutomationProperties.SetName(sampleText, Se.Language.Assa.StylesPreviewSampleText); + AutomationProperties.SetName(sampleText, Se.Language.General.SampleText); if (Se.Settings.Appearance.ShowHints) { ToolTip.SetTip(sampleText, Se.Language.Assa.StylesPreviewSampleTextHint); diff --git a/src/ui/Features/Assa/FontCollector/FontCollectorViewModel.cs b/src/ui/Features/Assa/FontCollector/FontCollectorViewModel.cs index 17ab6b53243..eb823b76c15 100644 --- a/src/ui/Features/Assa/FontCollector/FontCollectorViewModel.cs +++ b/src/ui/Features/Assa/FontCollector/FontCollectorViewModel.cs @@ -374,7 +374,7 @@ void AddUsage(string fontName, string usedIn) foreach (Match match in AssaFontEmbedder.FontNameTagRegex.Matches(paragraph.Text)) { - AddUsage(match.Groups["name"].Value, string.Format(Se.Language.Assa.FontCollectorInlineLineX, paragraph.Number)); + AddUsage(match.Groups["name"].Value, string.Format(Se.Language.General.LineX, paragraph.Number)); } } diff --git a/src/ui/Features/Edit/Find/FindResultText.cs b/src/ui/Features/Edit/Find/FindResultText.cs index e58c9ed2079..b3de7eb304c 100644 --- a/src/ui/Features/Edit/Find/FindResultText.cs +++ b/src/ui/Features/Edit/Find/FindResultText.cs @@ -30,7 +30,7 @@ public static string Replaced(int count) return count == 1 ? Se.Language.Edit.Find.ReplacedOneOccurrence - : string.Format(Se.Language.Edit.Find.ReplacedXOccurrences, count); + : string.Format(Se.Language.General.ReplacedXOccurrences, count); } /// A tick for a hit, an info mark for "nothing found". diff --git a/src/ui/Features/Edit/ModifySelection/HearingImpairedRuleSettingsWindow.cs b/src/ui/Features/Edit/ModifySelection/HearingImpairedRuleSettingsWindow.cs index 761f0a19d09..cdffbfe91be 100644 --- a/src/ui/Features/Edit/ModifySelection/HearingImpairedRuleSettingsWindow.cs +++ b/src/ui/Features/Edit/ModifySelection/HearingImpairedRuleSettingsWindow.cs @@ -60,7 +60,7 @@ public HearingImpairedRuleSettingsWindow(HearingImpairedRuleSettingsViewModel vm EditorChrome.MakeToggleRow(nameof(vm.IsUppercaseLineOn), l.HearingImpairedUppercaseLine), EditorChrome.MakeToggleRow(nameof(vm.IsLineContainsOn), l.HearingImpairedLineContains), EditorChrome.MakeToggleRow(nameof(vm.IsMusicSymbolsOn), l.HearingImpairedMusicSymbols), - EditorChrome.MakeToggleRow(nameof(vm.IsInterjectionsOn), l.HearingImpairedInterjections), + EditorChrome.MakeToggleRow(nameof(vm.IsInterjectionsOn), Se.Language.General.Interjections), } }; @@ -83,7 +83,7 @@ public HearingImpairedRuleSettingsWindow(HearingImpairedRuleSettingsViewModel vm var sections = new StackPanel { MinWidth = 320 }; sections.Children.Add(EditorChrome.MakeEditorSection(Se.Language.Tools.RemoveTextForHearingImpaired.RemoveTextBetween, betweenPanel, first: true)); - sections.Children.Add(EditorChrome.MakeEditorSection(l.HearingImpairedOther, otherPanel)); + sections.Children.Add(EditorChrome.MakeEditorSection(Se.Language.General.Other, otherPanel)); sections.Children.Add(hintSection); var buttonOk = UiUtil.MakeButtonOk(vm.OkCommand); diff --git a/src/ui/Features/Edit/ModifySelection/ModifySelectionRule.cs b/src/ui/Features/Edit/ModifySelection/ModifySelectionRule.cs index 4aba7342973..8bd515e6e5a 100644 --- a/src/ui/Features/Edit/ModifySelection/ModifySelectionRule.cs +++ b/src/ui/Features/Edit/ModifySelection/ModifySelectionRule.cs @@ -249,7 +249,7 @@ public static List List(List lines) new() { RuleType = RuleType.BlankLines, - Name = l.BlankLines, + Name = Se.Language.General.BlankLines, }, new() { diff --git a/src/ui/Features/Edit/MultipleReplace/MultipleReplaceViewModel.cs b/src/ui/Features/Edit/MultipleReplace/MultipleReplaceViewModel.cs index 069966aaf95..f61e97b77ea 100644 --- a/src/ui/Features/Edit/MultipleReplace/MultipleReplaceViewModel.cs +++ b/src/ui/Features/Edit/MultipleReplace/MultipleReplaceViewModel.cs @@ -1548,7 +1548,7 @@ private List BuildReplaceExpressions() } catch (ArgumentException exception) { - return string.Format(Se.Language.Edit.MultipleReplace.InvalidRegularExpressionX, exception.Message); + return string.Format(Se.Language.General.InvalidRegularExpressionX, exception.Message); } }); } diff --git a/src/ui/Features/Files/ExportCustomTextFormat/EditCustomTextFormatWindow.cs b/src/ui/Features/Files/ExportCustomTextFormat/EditCustomTextFormatWindow.cs index b3df064d971..bc52702e152 100644 --- a/src/ui/Features/Files/ExportCustomTextFormat/EditCustomTextFormatWindow.cs +++ b/src/ui/Features/Files/ExportCustomTextFormat/EditCustomTextFormatWindow.cs @@ -73,11 +73,11 @@ private static Grid MakeFormatsView(EditCustomTextFormatViewModel vm, out TextBo HorizontalAlignment = HorizontalAlignment.Stretch, MinWidth = 160, BorderBrush = EditorChrome.FieldBorderBrush, - PlaceholderText = Se.Language.File.Export.TimeCodeFormat, + PlaceholderText = Se.Language.General.TimeCodeFormat, ItemsSource = vm.TimeCodeList, [!AutoCompleteBox.TextProperty] = new Binding(nameof(vm.SelectedCustomFormat) + "." + nameof(CustomFormatItem.FormatTimeCode)), MinimumPrefixLength = 0, - }.WithAccessibleName(Se.Language.File.Export.TimeCodeFormat); + }.WithAccessibleName(Se.Language.General.TimeCodeFormat); var textBoxNewLine = new AutoCompleteBox { @@ -111,7 +111,7 @@ private static Grid MakeFormatsView(EditCustomTextFormatViewModel vm, out TextBo { (Se.Language.General.Name, textBoxName), (Se.Language.General.FileExtension, textBoxFileExtension), - (Se.Language.File.Export.TimeCodeFormat, textBoxTimeCode), + (Se.Language.General.TimeCodeFormat, textBoxTimeCode), (Se.Language.File.Export.NewLineFormat, textBoxNewLine), }; for (var i = 0; i < rows.Length; i++) diff --git a/src/ui/Features/Files/ExportImageBased/ExportImageBasedWindow.cs b/src/ui/Features/Files/ExportImageBased/ExportImageBasedWindow.cs index f8e4c22cce2..c7453ec52b5 100644 --- a/src/ui/Features/Files/ExportImageBased/ExportImageBasedWindow.cs +++ b/src/ui/Features/Files/ExportImageBased/ExportImageBasedWindow.cs @@ -368,7 +368,7 @@ private static Border MakePositionSection(ExportImageBasedViewModel vm) Row(Se.Language.General.Alignment, Combo(vm.Alignments, vm, nameof(vm.SelectedAlignment), Se.Language.General.Alignment)), Row(Se.Language.General.ContentAlignment, Combo(vm.ContentAlignments, vm, nameof(vm.SelectedContentAlignment), Se.Language.General.ContentAlignment)), Row(Se.Language.File.Export.LeftRightMargin, Combo(vm.LeftRightMargins, vm, nameof(vm.SelectedLeftRightMargin), Se.Language.File.Export.LeftRightMargin)), - Row(Se.Language.File.Export.TopBottomMargin, Combo(vm.TopBottomMargins, vm, nameof(vm.SelectedTopBottomMargin), Se.Language.File.Export.TopBottomMargin)), + Row(Se.Language.General.TopBottomMargin, Combo(vm.TopBottomMargins, vm, nameof(vm.SelectedTopBottomMargin), Se.Language.General.TopBottomMargin)), Row(Se.Language.File.Export.PaddingLeftRight, Combo(vm.PaddingsLeftRight, vm, nameof(vm.SelectedPaddingLeftRight), Se.Language.File.Export.PaddingLeftRight)), Row(Se.Language.File.Export.PaddingTopBottom, Combo(vm.PaddingsTopBottom, vm, nameof(vm.SelectedPaddingTopBottom), Se.Language.File.Export.PaddingTopBottom)))); } diff --git a/src/ui/Features/Files/ExportImageBased/TextEffectWindow.cs b/src/ui/Features/Files/ExportImageBased/TextEffectWindow.cs index 9e0173c0580..aa7aad97e54 100644 --- a/src/ui/Features/Files/ExportImageBased/TextEffectWindow.cs +++ b/src/ui/Features/Files/ExportImageBased/TextEffectWindow.cs @@ -35,7 +35,7 @@ public TextEffectWindow(TextEffectViewModel vm) var comboPreset = UiUtil.MakeComboBox(vm.Presets, vm, nameof(vm.SelectedPreset)); comboPreset.HorizontalAlignment = HorizontalAlignment.Stretch; - AutomationProperties.SetName(comboPreset, Se.Language.File.Export.TextEffectPreset); + AutomationProperties.SetName(comboPreset, Se.Language.General.Preset); var sliders = new StackPanel { Spacing = 4 }; sliders.Children.Add(MakeSliderRow(Se.Language.File.Export.TextEffectStrength, nameof(vm.Strength), 25, 300, "{0}%")); @@ -44,7 +44,7 @@ public TextEffectWindow(TextEffectViewModel vm) sliders.Children.Add(MakeSliderRow(Se.Language.File.Export.TextEffectWave, nameof(vm.Wave), 0, 100, "{0}")); var sections = new StackPanel(); - sections.Children.Add(EditorChrome.MakeEditorSection(Se.Language.File.Export.TextEffectPreset, comboPreset, first: true)); + sections.Children.Add(EditorChrome.MakeEditorSection(Se.Language.General.Preset, comboPreset, first: true)); sections.Children.Add(EditorChrome.MakeEditorSection(Se.Language.General.Settings, sliders)); var buttonReset = EditorChrome.MakeTextToolButton("fa-solid fa-rotate-left", Se.Language.General.Reset, vm.ResetCommand); diff --git a/src/ui/Features/Files/FormatProperties/DcinemaInteropProperties/DCinemaInteropPropertiesWindow.cs b/src/ui/Features/Files/FormatProperties/DcinemaInteropProperties/DCinemaInteropPropertiesWindow.cs index df7e05122b2..254f185d08c 100644 --- a/src/ui/Features/Files/FormatProperties/DcinemaInteropProperties/DCinemaInteropPropertiesWindow.cs +++ b/src/ui/Features/Files/FormatProperties/DcinemaInteropProperties/DCinemaInteropPropertiesWindow.cs @@ -45,11 +45,11 @@ public DCinemaInteropPropertiesWindow(DCinemaInteropPropertiesViewModel vm) // Font section var textBoxFontId = EditorChrome.AsTextField(UiUtil.MakeTextBox(200, vm, nameof(vm.FontId)), lang.FontId); var textBoxFontUri = EditorChrome.AsTextField(UiUtil.MakeTextBox(340, vm, nameof(vm.FontUri)), lang.FontUri); - var buttonFontColor = FormatPropertiesChrome.ColorButton(lang.ChooseColor, Se.Language.General.FontColor, vm.ChooseFontColorCommand, nameof(vm.FontColor)); + var buttonFontColor = FormatPropertiesChrome.ColorButton(Se.Language.General.ChooseColor, Se.Language.General.FontColor, vm.ChooseFontColorCommand, nameof(vm.FontColor)); var comboBoxFontEffect = FormatPropertiesChrome.Combo(UiUtil.MakeComboBox(vm.FontEffects, vm, nameof(vm.SelectedFontEffect)), lang.FontEffect, 140); - var buttonFontEffectColor = FormatPropertiesChrome.ColorButton(lang.ChooseColor, lang.EffectColor, vm.ChooseFontEffectColorCommand, nameof(vm.FontEffectColor)); + var buttonFontEffectColor = FormatPropertiesChrome.ColorButton(Se.Language.General.ChooseColor, lang.EffectColor, vm.ChooseFontEffectColorCommand, nameof(vm.FontEffectColor)); var numericUpDownFontSize = FormatPropertiesChrome.Number(UiUtil.MakeNumericUpDownInt(0, 250, 42, 90, vm, nameof(vm.FontSize)), Se.Language.General.FontSize); - var numericUpDownTopBottomMargin = FormatPropertiesChrome.Number(UiUtil.MakeNumericUpDownInt(1, 50, 8, 90, vm, nameof(vm.TopBottomMargin)), lang.TopBottomMargin); + var numericUpDownTopBottomMargin = FormatPropertiesChrome.Number(UiUtil.MakeNumericUpDownInt(1, 50, 8, 90, vm, nameof(vm.TopBottomMargin)), Se.Language.General.TopBottomMargin); var numericUpDownZPosition = FormatPropertiesChrome.Number(UiUtil.MakeNumericUpDownTwoDecimals(-10, 10, 90, vm, nameof(vm.ZPosition)), lang.ZPosition); var labelZPositionHelp = FormatPropertiesChrome.Hint(lang.ZPositionHelp, 300); var numericUpDownFadeUpTime = FormatPropertiesChrome.Number(UiUtil.MakeNumericUpDownInt(0, 50, 0, 90, vm, nameof(vm.FadeUpTime)), lang.FadeUpTime); @@ -60,7 +60,7 @@ public DCinemaInteropPropertiesWindow(DCinemaInteropPropertiesViewModel vm) FormatPropertiesChrome.Row(lang.FontUri, textBoxFontUri), FormatPropertiesChrome.Row(Se.Language.General.FontColor, buttonFontColor), FormatPropertiesChrome.Row(lang.FontEffect, comboBoxFontEffect, FormatPropertiesChrome.InlineLabel(lang.EffectColor), buttonFontEffectColor), - FormatPropertiesChrome.Row(Se.Language.General.FontSize, numericUpDownFontSize, FormatPropertiesChrome.InlineLabel(lang.TopBottomMargin), numericUpDownTopBottomMargin), + FormatPropertiesChrome.Row(Se.Language.General.FontSize, numericUpDownFontSize, FormatPropertiesChrome.InlineLabel(Se.Language.General.TopBottomMargin), numericUpDownTopBottomMargin), FormatPropertiesChrome.Row(lang.ZPosition, numericUpDownZPosition, labelZPositionHelp), FormatPropertiesChrome.Row(lang.FadeUpTime, numericUpDownFadeUpTime, FormatPropertiesChrome.Unit(Se.Language.General.Frames), FormatPropertiesChrome.InlineLabel(lang.FadeDownTime), numericUpDownFadeDownTime, FormatPropertiesChrome.Unit(Se.Language.General.Frames))); diff --git a/src/ui/Features/Files/FormatProperties/DcinemaSmpteProperties/DCinemaSmptePropertiesWindow.cs b/src/ui/Features/Files/FormatProperties/DcinemaSmpteProperties/DCinemaSmptePropertiesWindow.cs index 7ec192a61aa..4ea68d9c06c 100644 --- a/src/ui/Features/Files/FormatProperties/DcinemaSmpteProperties/DCinemaSmptePropertiesWindow.cs +++ b/src/ui/Features/Files/FormatProperties/DcinemaSmpteProperties/DCinemaSmptePropertiesWindow.cs @@ -64,11 +64,11 @@ public DCinemaSmptePropertiesWindow(DCinemaSmptePropertiesViewModel vm) var textBoxFontId = EditorChrome.AsTextField(UiUtil.MakeTextBox(200, vm, nameof(vm.FontId)), lang.FontId); var textBoxFontUri = EditorChrome.AsTextField(UiUtil.MakeTextBox(340, vm, nameof(vm.FontUri)), lang.FontUri); var buttonGenerateFontUri = FormatPropertiesChrome.MakeAction("fa-solid fa-wand-magic-sparkles", Se.Language.General.Generate, vm.GenerateFontUriCommand); - var buttonFontColor = FormatPropertiesChrome.ColorButton(lang.ChooseColor, Se.Language.General.FontColor, vm.ChooseFontColorCommand, nameof(vm.FontColor)); + var buttonFontColor = FormatPropertiesChrome.ColorButton(Se.Language.General.ChooseColor, Se.Language.General.FontColor, vm.ChooseFontColorCommand, nameof(vm.FontColor)); var comboBoxFontEffect = FormatPropertiesChrome.Combo(UiUtil.MakeComboBox(vm.FontEffects, vm, nameof(vm.SelectedFontEffect)), lang.FontEffect, 140); - var buttonFontEffectColor = FormatPropertiesChrome.ColorButton(lang.ChooseColor, lang.EffectColor, vm.ChooseFontEffectColorCommand, nameof(vm.FontEffectColor)); + var buttonFontEffectColor = FormatPropertiesChrome.ColorButton(Se.Language.General.ChooseColor, lang.EffectColor, vm.ChooseFontEffectColorCommand, nameof(vm.FontEffectColor)); var numericUpDownFontSize = FormatPropertiesChrome.Number(UiUtil.MakeNumericUpDownInt(0, 250, 42, 90, vm, nameof(vm.FontSize)), Se.Language.General.FontSize); - var numericUpDownTopBottomMargin = FormatPropertiesChrome.Number(UiUtil.MakeNumericUpDownInt(1, 50, 8, 90, vm, nameof(vm.TopBottomMargin)), lang.TopBottomMargin); + var numericUpDownTopBottomMargin = FormatPropertiesChrome.Number(UiUtil.MakeNumericUpDownInt(1, 50, 8, 90, vm, nameof(vm.TopBottomMargin)), Se.Language.General.TopBottomMargin); var numericUpDownFadeUpTime = FormatPropertiesChrome.Number(UiUtil.MakeNumericUpDownInt(0, 50, 0, 90, vm, nameof(vm.FadeUpTime)), lang.FadeUpTime); var numericUpDownFadeDownTime = FormatPropertiesChrome.Number(UiUtil.MakeNumericUpDownInt(0, 50, 0, 90, vm, nameof(vm.FadeDownTime)), lang.FadeDownTime); @@ -83,7 +83,7 @@ public DCinemaSmptePropertiesWindow(DCinemaSmptePropertiesViewModel vm) FormatPropertiesChrome.Row(lang.FontUri, textBoxFontUri, buttonGenerateFontUri), FormatPropertiesChrome.Row(Se.Language.General.FontColor, buttonFontColor), FormatPropertiesChrome.Row(lang.FontEffect, comboBoxFontEffect, FormatPropertiesChrome.InlineLabel(lang.EffectColor), buttonFontEffectColor), - FormatPropertiesChrome.Row(Se.Language.General.FontSize, numericUpDownFontSize, FormatPropertiesChrome.InlineLabel(lang.TopBottomMargin), numericUpDownTopBottomMargin), + FormatPropertiesChrome.Row(Se.Language.General.FontSize, numericUpDownFontSize, FormatPropertiesChrome.InlineLabel(Se.Language.General.TopBottomMargin), numericUpDownTopBottomMargin), FormatPropertiesChrome.Row(lang.FadeUpTime, numericUpDownFadeUpTime, FormatPropertiesChrome.Unit(Se.Language.General.Frames), FormatPropertiesChrome.InlineLabel(lang.FadeDownTime), numericUpDownFadeDownTime, FormatPropertiesChrome.Unit(Se.Language.General.Frames))); diff --git a/src/ui/Features/Files/FormatProperties/ItunesTimedTextProperties/ItunesTimedTextPropertiesWindow.cs b/src/ui/Features/Files/FormatProperties/ItunesTimedTextProperties/ItunesTimedTextPropertiesWindow.cs index fec35135c4b..3a5042acf6f 100644 --- a/src/ui/Features/Files/FormatProperties/ItunesTimedTextProperties/ItunesTimedTextPropertiesWindow.cs +++ b/src/ui/Features/Files/FormatProperties/ItunesTimedTextProperties/ItunesTimedTextPropertiesWindow.cs @@ -34,13 +34,13 @@ public ItunesTimedTextPropertiesWindow(ItunesTimedTextPropertiesViewModel vm) var textBoxFrameRate = EditorChrome.AsTextField(UiUtil.MakeTextBox(FieldWidth, vm, nameof(vm.SelectedFrameRate)), Se.Language.General.FrameRate); var textBoxFrameRateMultiplier = EditorChrome.AsTextField(UiUtil.MakeTextBox(FieldWidth, vm, nameof(vm.SelectedFrameRateMultiplier)), Se.Language.File.PropertyFrameRateMultiplier); var comboBoxDropMode = FormatPropertiesChrome.Combo(UiUtil.MakeComboBox(vm.DropModes, vm, nameof(vm.SelectedDropMode)), Se.Language.File.PropertyDropMode, FieldWidth); - var comboBoxTimeCodeFormat = FormatPropertiesChrome.Combo(UiUtil.MakeComboBox(vm.TimeCodeFormats, vm, nameof(vm.SelectedTimeCodeFormat)), Se.Language.File.PropertyTimeCodeFormat, FieldWidth); + var comboBoxTimeCodeFormat = FormatPropertiesChrome.Combo(UiUtil.MakeComboBox(vm.TimeCodeFormats, vm, nameof(vm.SelectedTimeCodeFormat)), Se.Language.General.TimeCodeFormat, FieldWidth); var timing = FormatPropertiesChrome.MakeRows( FormatPropertiesChrome.Row(Se.Language.File.PropertyTimeBase, comboBoxTimeBase), FormatPropertiesChrome.Row(Se.Language.General.FrameRate, textBoxFrameRate), FormatPropertiesChrome.Row(Se.Language.File.PropertyFrameRateMultiplier, textBoxFrameRateMultiplier), FormatPropertiesChrome.Row(Se.Language.File.PropertyDropMode, comboBoxDropMode), - FormatPropertiesChrome.Row(Se.Language.File.PropertyTimeCodeFormat, comboBoxTimeCodeFormat)); + FormatPropertiesChrome.Row(Se.Language.General.TimeCodeFormat, comboBoxTimeCodeFormat)); var comboBoxDefaultStyle = FormatPropertiesChrome.Combo(UiUtil.MakeComboBox(vm.DefaultStyles, vm, nameof(vm.SelectedDefaultStyle)), Se.Language.File.PropertyDefaultStyle, FieldWidth); var comboBoxDefaultRegion = FormatPropertiesChrome.Combo(UiUtil.MakeComboBox(vm.DefaultRegions, vm, nameof(vm.SelectedDefaultRegion)), Se.Language.File.PropertyDefaultRegion, FieldWidth); diff --git a/src/ui/Features/Files/FormatProperties/TimedText10Properties/TimedText10PropertiesWindow.cs b/src/ui/Features/Files/FormatProperties/TimedText10Properties/TimedText10PropertiesWindow.cs index 03f47983fdd..e34e947a466 100644 --- a/src/ui/Features/Files/FormatProperties/TimedText10Properties/TimedText10PropertiesWindow.cs +++ b/src/ui/Features/Files/FormatProperties/TimedText10Properties/TimedText10PropertiesWindow.cs @@ -34,13 +34,13 @@ public TimedText10PropertiesWindow(TimedText10PropertiesViewModel vm) var textBoxFrameRate = EditorChrome.AsTextField(UiUtil.MakeTextBox(FieldWidth, vm, nameof(vm.SelectedFrameRate)), Se.Language.General.FrameRate); var textBoxFrameRateMultiplier = EditorChrome.AsTextField(UiUtil.MakeTextBox(FieldWidth, vm, nameof(vm.SelectedFrameRateMultiplier)), Se.Language.File.PropertyFrameRateMultiplier); var comboBoxDropMode = FormatPropertiesChrome.Combo(UiUtil.MakeComboBox(vm.DropModes, vm, nameof(vm.SelectedDropMode)), Se.Language.File.PropertyDropMode, FieldWidth); - var comboBoxTimeCodeFormat = FormatPropertiesChrome.Combo(UiUtil.MakeComboBox(vm.TimeCodeFormats, vm, nameof(vm.SelectedTimeCodeFormat)), Se.Language.File.PropertyTimeCodeFormat, FieldWidth); + var comboBoxTimeCodeFormat = FormatPropertiesChrome.Combo(UiUtil.MakeComboBox(vm.TimeCodeFormats, vm, nameof(vm.SelectedTimeCodeFormat)), Se.Language.General.TimeCodeFormat, FieldWidth); var timing = FormatPropertiesChrome.MakeRows( FormatPropertiesChrome.Row(Se.Language.File.PropertyTimeBase, comboBoxTimeBase), FormatPropertiesChrome.Row(Se.Language.General.FrameRate, textBoxFrameRate), FormatPropertiesChrome.Row(Se.Language.File.PropertyFrameRateMultiplier, textBoxFrameRateMultiplier), FormatPropertiesChrome.Row(Se.Language.File.PropertyDropMode, comboBoxDropMode), - FormatPropertiesChrome.Row(Se.Language.File.PropertyTimeCodeFormat, comboBoxTimeCodeFormat)); + FormatPropertiesChrome.Row(Se.Language.General.TimeCodeFormat, comboBoxTimeCodeFormat)); var comboBoxDefaultStyle = FormatPropertiesChrome.Combo(UiUtil.MakeComboBox(vm.DefaultStyles, vm, nameof(vm.SelectedDefaultStyle)), Se.Language.File.PropertyDefaultStyle, FieldWidth); var comboBoxDefaultRegion = FormatPropertiesChrome.Combo(UiUtil.MakeComboBox(vm.DefaultRegions, vm, nameof(vm.SelectedDefaultRegion)), Se.Language.File.PropertyDefaultRegion, FieldWidth); diff --git a/src/ui/Features/Files/FormatProperties/TimedTextImsc11Properties/TimedTextImsc11PropertiesWindow.cs b/src/ui/Features/Files/FormatProperties/TimedTextImsc11Properties/TimedTextImsc11PropertiesWindow.cs index a36baf76353..a62b0789024 100644 --- a/src/ui/Features/Files/FormatProperties/TimedTextImsc11Properties/TimedTextImsc11PropertiesWindow.cs +++ b/src/ui/Features/Files/FormatProperties/TimedTextImsc11Properties/TimedTextImsc11PropertiesWindow.cs @@ -30,13 +30,13 @@ public TimedTextImsc11PropertiesWindow(TimedTextImsc11PropertiesViewModel vm) var textBoxFrameRate = EditorChrome.AsTextField(UiUtil.MakeTextBox(FieldWidth, vm, nameof(vm.SelectedFrameRate)), Se.Language.General.FrameRate); var textBoxFrameRateMultiplier = EditorChrome.AsTextField(UiUtil.MakeTextBox(FieldWidth, vm, nameof(vm.SelectedFrameRateMultiplier)), Se.Language.File.PropertyFrameRateMultiplier); var comboBoxDropMode = FormatPropertiesChrome.Combo(UiUtil.MakeComboBox(vm.DropModes, vm, nameof(vm.SelectedDropMode)), Se.Language.File.PropertyDropMode, FieldWidth); - var comboBoxTimeCodeFormat = FormatPropertiesChrome.Combo(UiUtil.MakeComboBox(vm.TimeCodeFormats, vm, nameof(vm.SelectedTimeCodeFormat)), Se.Language.File.PropertyTimeCodeFormat, FieldWidth); + var comboBoxTimeCodeFormat = FormatPropertiesChrome.Combo(UiUtil.MakeComboBox(vm.TimeCodeFormats, vm, nameof(vm.SelectedTimeCodeFormat)), Se.Language.General.TimeCodeFormat, FieldWidth); var timing = FormatPropertiesChrome.MakeRows( FormatPropertiesChrome.Row(Se.Language.File.PropertyTimeBase, comboBoxTimeBase), FormatPropertiesChrome.Row(Se.Language.General.FrameRate, textBoxFrameRate), FormatPropertiesChrome.Row(Se.Language.File.PropertyFrameRateMultiplier, textBoxFrameRateMultiplier), FormatPropertiesChrome.Row(Se.Language.File.PropertyDropMode, comboBoxDropMode), - FormatPropertiesChrome.Row(Se.Language.File.PropertyTimeCodeFormat, comboBoxTimeCodeFormat)); + FormatPropertiesChrome.Row(Se.Language.General.TimeCodeFormat, comboBoxTimeCodeFormat)); var comboBoxDefaultStyle = FormatPropertiesChrome.Combo(UiUtil.MakeComboBox(vm.DefaultStyles, vm, nameof(vm.SelectedDefaultStyle)), Se.Language.File.PropertyDefaultStyle, FieldWidth); var comboBoxDefaultRegion = FormatPropertiesChrome.Combo(UiUtil.MakeComboBox(vm.DefaultRegions, vm, nameof(vm.SelectedDefaultRegion)), Se.Language.File.PropertyDefaultRegion, FieldWidth); diff --git a/src/ui/Features/Files/ImportDvd/DvdTitleDisplay.cs b/src/ui/Features/Files/ImportDvd/DvdTitleDisplay.cs index 6dfaa01e0e0..51efa84d3e5 100644 --- a/src/ui/Features/Files/ImportDvd/DvdTitleDisplay.cs +++ b/src/ui/Features/Files/ImportDvd/DvdTitleDisplay.cs @@ -25,7 +25,7 @@ public DvdTitleDisplay(DvdTitle title, string name, string languages, string sta public override string ToString() { - var text = $"{Name}: {Duration:hh\\:mm\\:ss} ({Chapters} {Se.Language.File.Chapters.ToLowerInvariant()})"; + var text = $"{Name}: {Duration:hh\\:mm\\:ss} ({Chapters} {Se.Language.General.Chapters.ToLowerInvariant()})"; return string.IsNullOrEmpty(Status) ? text : $"{text} - {Status}"; } } \ No newline at end of file diff --git a/src/ui/Features/Files/ImportDvd/ImportDvdWindow.cs b/src/ui/Features/Files/ImportDvd/ImportDvdWindow.cs index a52f4e1a002..d045727119b 100644 --- a/src/ui/Features/Files/ImportDvd/ImportDvdWindow.cs +++ b/src/ui/Features/Files/ImportDvd/ImportDvdWindow.cs @@ -161,7 +161,7 @@ private static Control MakeTitleItem() duration.Bind(TextBlock.TextProperty, new Binding(nameof(DvdTitleDisplay.Duration)) { Converter = new TimeSpanToDisplayShortConverter() }); var chapters = new TextBlock { Opacity = 0.7, VerticalAlignment = VerticalAlignment.Center, Margin = new Thickness(12, 0, 0, 0) }; - chapters.Bind(TextBlock.TextProperty, new Binding(nameof(DvdTitleDisplay.Chapters)) { StringFormat = "{0} " + Se.Language.File.Chapters.ToLowerInvariant() }); + chapters.Bind(TextBlock.TextProperty, new Binding(nameof(DvdTitleDisplay.Chapters)) { StringFormat = "{0} " + Se.Language.General.Chapters.ToLowerInvariant() }); var status = new TextBlock { Foreground = Brushes.OrangeRed, VerticalAlignment = VerticalAlignment.Center, Margin = new Thickness(12, 0, 0, 0) }; status.Bind(TextBlock.TextProperty, new Binding(nameof(DvdTitleDisplay.Status))); diff --git a/src/ui/Features/Files/ImportPlainText/ImportPlainTextViewModel.cs b/src/ui/Features/Files/ImportPlainText/ImportPlainTextViewModel.cs index 57db8389f64..81064c10021 100644 --- a/src/ui/Features/Files/ImportPlainText/ImportPlainTextViewModel.cs +++ b/src/ui/Features/Files/ImportPlainText/ImportPlainTextViewModel.cs @@ -67,7 +67,7 @@ public ImportPlainTextViewModel(IFileHelper fileHelper, IWindowService windowSer SplitAtOptions = new ObservableCollection { Se.Language.General.Auto, - Se.Language.File.Import.BlankLines, + Se.Language.General.BlankLines, Se.Language.File.Import.OneLineIsOneSubtitle, Se.Language.File.Import.TwoLinesAreOneSubtitle, }; @@ -213,7 +213,7 @@ private static List UpdatePreviewText(string splitAtOptio { return PlainTextSplitter.AutomaticSplit(plainText, Se.Settings.General.MaxNumberOfLines, Se.Settings.General.SubtitleLineMaximumLength); } - else if (splitAtOption == Se.Language.File.Import.BlankLines) + else if (splitAtOption == Se.Language.General.BlankLines) { var blocks = plainText.SplitToLines(); var sb = new System.Text.StringBuilder(); diff --git a/src/ui/Features/Files/RestoreAutoBackup/RestoreAutoBackupWindow.cs b/src/ui/Features/Files/RestoreAutoBackup/RestoreAutoBackupWindow.cs index b393814f4bb..6630cc6def3 100644 --- a/src/ui/Features/Files/RestoreAutoBackup/RestoreAutoBackupWindow.cs +++ b/src/ui/Features/Files/RestoreAutoBackup/RestoreAutoBackupWindow.cs @@ -26,8 +26,6 @@ public RestoreAutoBackupWindow(RestoreAutoBackupViewModel vm) DataContext = vm; EditorChrome.ApplyWindow(this, EditorWindowStyles.Dialog); - var l = Se.Language.File.RestoreAutoBackup; - var subtitleGrid = MakeSubtitleGrid(vm); var settingsGrid = MakeSettingsGrid(vm); @@ -40,7 +38,7 @@ public RestoreAutoBackupWindow(RestoreAutoBackupViewModel vm) { new TabItem { - Header = MakeTabHeader(IconNames.ClosedCaption, l.Subtitles), + Header = MakeTabHeader(IconNames.ClosedCaption, Se.Language.General.Subtitles), Content = MakeSubtitlesView(vm, subtitleGrid), }, new TabItem @@ -69,7 +67,7 @@ public RestoreAutoBackupWindow(RestoreAutoBackupViewModel vm) private static TableView MakeSubtitleGrid(RestoreAutoBackupViewModel vm) { - var dataGrid = TableViewExtras.MakeTableView(multiSelect: false).WithAccessibleName(Se.Language.File.RestoreAutoBackup.Subtitles); // the tab header (#12087) + var dataGrid = TableViewExtras.MakeTableView(multiSelect: false).WithAccessibleName(Se.Language.General.Subtitles); // the tab header (#12087) dataGrid.Width = double.NaN; dataGrid.Height = double.NaN; dataGrid.DataContext = vm; @@ -139,7 +137,7 @@ private static Control MakeSubtitlesView(RestoreAutoBackupViewModel vm, TableVie buttonRestore.Classes.Add("primary"); buttonRestore.BindIsEnabled(vm, nameof(vm.IsOkButtonEnabled)); - return MakeTabLayout(l.Subtitles, dataGrid, null, buttonOpenFolder, summaryText, buttonDeleteAll, buttonRestore); + return MakeTabLayout(Se.Language.General.Subtitles, dataGrid, null, buttonOpenFolder, summaryText, buttonDeleteAll, buttonRestore); } private static Control MakeSettingsView(RestoreAutoBackupViewModel vm, TableView dataGrid) diff --git a/src/ui/Features/Files/Statistics/StatisticsWindow.cs b/src/ui/Features/Files/Statistics/StatisticsWindow.cs index 4a3f8420f34..9a628e2137a 100644 --- a/src/ui/Features/Files/Statistics/StatisticsWindow.cs +++ b/src/ui/Features/Files/Statistics/StatisticsWindow.cs @@ -125,7 +125,7 @@ private static StackPanel MakeDashboard(StatisticsViewModel vm) ColumnDefinitions = new ColumnDefinitions("*,*,*,*"), ColumnSpacing = 10, }; - kpiRow.Add(MakeKpiTile(l.Subtitles, vm.KpiSubtitles), 0, 0); + kpiRow.Add(MakeKpiTile(Se.Language.General.Subtitles, vm.KpiSubtitles), 0, 0); kpiRow.Add(MakeKpiTile(l.Words, vm.KpiWords), 0, 1); kpiRow.Add(MakeKpiTile(Se.Language.General.Characters, vm.KpiCharacters), 0, 2); kpiRow.Add(MakeKpiTile(l.TotalDurationShort, vm.KpiDuration), 0, 3); diff --git a/src/ui/Features/Help/CheckForUpdates/CheckForUpdatesViewModel.cs b/src/ui/Features/Help/CheckForUpdates/CheckForUpdatesViewModel.cs index 483a7d80174..ad84a72693c 100644 --- a/src/ui/Features/Help/CheckForUpdates/CheckForUpdatesViewModel.cs +++ b/src/ui/Features/Help/CheckForUpdates/CheckForUpdatesViewModel.cs @@ -103,7 +103,7 @@ private async Task InstallUpdate() IsInstallVisible = false; IsDownloading = true; DownloadProgress = 0; - StatusText = string.Format(Se.Language.Help.CheckForUpdatesDownloadingX, _latestVersion); + StatusText = string.Format(Se.Language.General.DownloadingXDotDotDot, _latestVersion); _downloadCancellation = new CancellationTokenSource(); var progress = new Progress(p => DownloadProgress = p * 100.0); diff --git a/src/ui/Features/Main/MainViewModel.cs b/src/ui/Features/Main/MainViewModel.cs index 252bb3670d4..0687ec57b32 100644 --- a/src/ui/Features/Main/MainViewModel.cs +++ b/src/ui/Features/Main/MainViewModel.cs @@ -7781,7 +7781,7 @@ private async Task ColumnPasteFromClipboard() var text = await ClipboardHelper.GetTextAsync(Window); if (string.IsNullOrEmpty(text)) { - ShowStatus(Se.Language.Main.NoTextInClipboard); + ShowStatus(Se.Language.General.NoTextInClipboard); return; } @@ -14213,7 +14213,7 @@ private async Task FillSelectedLinesWithClipboard() var text = await ClipboardHelper.GetTextAsync(Window); if (string.IsNullOrEmpty(text)) { - ShowStatus(Se.Language.Main.NoTextInClipboard); + ShowStatus(Se.Language.General.NoTextInClipboard); _shortcutManager.ClearKeys(); return; } @@ -18954,14 +18954,14 @@ await ShowDialogAsync(vm => // they were on - jumping to the top on every Apply lost their place, and Apply // is meant to be used several times in a row (issue #13822). ApplyFixedSubtitle(fixedSubtitle, rowByParagraphId, SelectedSubtitleIndex ?? 0, SelectedSubtitleFormat); - ShowStatus(string.Format(Se.Language.Main.ReplacedXOccurrences, count)); + ShowStatus(string.Format(Se.Language.General.ReplacedXOccurrences, count)); }; }); if (result.OkPressed) { ApplyFixedSubtitle(result.FixedSubtitle, rowByParagraphId, SelectedSubtitleIndex ?? 0, SelectedSubtitleFormat); - ShowStatus(string.Format(Se.Language.Main.ReplacedXOccurrences, result.TotalReplaced)); + ShowStatus(string.Format(Se.Language.General.ReplacedXOccurrences, result.TotalReplaced)); } } diff --git a/src/ui/Features/Ocr/BinaryOcr/BinaryOcrCharacterHistoryViewModel.cs b/src/ui/Features/Ocr/BinaryOcr/BinaryOcrCharacterHistoryViewModel.cs index d2dfba7fccc..44c2c628a3b 100644 --- a/src/ui/Features/Ocr/BinaryOcr/BinaryOcrCharacterHistoryViewModel.cs +++ b/src/ui/Features/Ocr/BinaryOcr/BinaryOcrCharacterHistoryViewModel.cs @@ -228,7 +228,7 @@ private void HistoryItemChanged() BinaryOcrBitmap = selectedItem.BinaryOcrBitmap; IsNewTextItalic = BinaryOcrBitmap.Italic; NewText = BinaryOcrBitmap.Text ?? string.Empty; - LineIndex = string.Format(Se.Language.Ocr.LineIndexX, selectedItem.LineIndex + 1); + LineIndex = string.Format(Se.Language.General.LineX, selectedItem.LineIndex + 1); if (BinaryOcrBitmap.ExpandCount > 1 && selectedItem.PreviewBitmap != null) { ResolutionAndTopMargin = string.Format(Se.Language.Ocr.ResolutionXYAndTopmarginZ, selectedItem.PreviewBitmap.Width, selectedItem.PreviewBitmap.Height, selectedItem.PreviewTopMargin); diff --git a/src/ui/Features/Ocr/LlamaCppOcrSettingsWindow.cs b/src/ui/Features/Ocr/LlamaCppOcrSettingsWindow.cs index cbb37642317..befb23d6795 100644 --- a/src/ui/Features/Ocr/LlamaCppOcrSettingsWindow.cs +++ b/src/ui/Features/Ocr/LlamaCppOcrSettingsWindow.cs @@ -63,7 +63,7 @@ public LlamaCppOcrSettingsWindow(LlamaCppOcrSettingsViewModel vm) DataContext = vm, }; textBoxPrompt.Classes.Add(EditorChrome.TextClass); - AutomationProperties.SetName(textBoxPrompt, Se.Language.General.OpenAiCompatibleSttPrompt); + AutomationProperties.SetName(textBoxPrompt, Se.Language.General.Prompt); textBoxPrompt.Bind(TextBox.TextProperty, new Binding(nameof(vm.Prompt)) { Mode = BindingMode.TwoWay, @@ -97,7 +97,7 @@ public LlamaCppOcrSettingsWindow(LlamaCppOcrSettingsViewModel vm) RowSpacing = 8, Margin = new Thickness(14, 12, 14, 14), }; - promptGrid.Add(EditorChrome.MakeSectionHeader(Se.Language.General.OpenAiCompatibleSttPrompt), 0, 0); + promptGrid.Add(EditorChrome.MakeSectionHeader(Se.Language.General.Prompt), 0, 0); promptGrid.Add(textBoxPrompt, 1, 0); promptGrid.Add(hintPrompt, 2, 0); diff --git a/src/ui/Features/Ocr/NOcr/NOcrCharacterHistoryViewModel.cs b/src/ui/Features/Ocr/NOcr/NOcrCharacterHistoryViewModel.cs index 0dab5b40282..58793821871 100644 --- a/src/ui/Features/Ocr/NOcr/NOcrCharacterHistoryViewModel.cs +++ b/src/ui/Features/Ocr/NOcr/NOcrCharacterHistoryViewModel.cs @@ -244,7 +244,7 @@ private void HistoryItemChanged() NOcrChar = selectedItem.NOcrChar; IsNewTextItalic = NOcrChar.Italic; NewText = NOcrChar.Text; - LineIndex = string.Format(Se.Language.Ocr.LineIndexX, selectedItem.LineIndex + 1); + LineIndex = string.Format(Se.Language.General.LineX, selectedItem.LineIndex + 1); ResolutionAndTopMargin = string.Format(Se.Language.Ocr.ResolutionXYAndTopmarginZ, NOcrChar.Width, NOcrChar.Height, NOcrChar.MarginTop); NOcrDrawingCanvas.BackgroundImage = CurrentBitmap; diff --git a/src/ui/Features/Ocr/OcrHtmlExporter.cs b/src/ui/Features/Ocr/OcrHtmlExporter.cs index 21cd5a22952..6fc5ae18e8b 100644 --- a/src/ui/Features/Ocr/OcrHtmlExporter.cs +++ b/src/ui/Features/Ocr/OcrHtmlExporter.cs @@ -188,7 +188,7 @@ private static string BuildHtml(string rows, int imageCount, int emptyCount, str sb.Append(""); sb.AppendLine(""); sb.Append("
"); + .Append(WebUtility.HtmlEncode(Se.Language.General.Theme)).AppendLine("\">"); sb.Append(""); sb.Append(""); sb.Append(""); diff --git a/src/ui/Features/Ocr/OcrViewModel.cs b/src/ui/Features/Ocr/OcrViewModel.cs index d44b857b4b1..429851204c8 100644 --- a/src/ui/Features/Ocr/OcrViewModel.cs +++ b/src/ui/Features/Ocr/OcrViewModel.cs @@ -821,7 +821,7 @@ private void UpdateOcrProgress(int currentItemNumber, int totalItems) var clampedItemNumber = Math.Clamp(currentItemNumber, 0, totalItems); OcrUiUpdates.EnqueueProgress( clampedItemNumber * 100.0 / totalItems, - string.Format(Se.Language.Ocr.RunningOcrDotDotDotXY, clampedItemNumber, totalItems)); + string.Format(Se.Language.General.RunningOcrXY, clampedItemNumber, totalItems)); } [RelayCommand] @@ -1657,7 +1657,7 @@ private async Task EnsureLlamaCppOcrReady() } else if (!engineInstalled) { - message = Se.Language.Ocr.LlamaCppDownloadEnginePrompt; + message = Se.Language.General.LlamaCppDownloadEnginePrompt; } else { diff --git a/src/ui/Features/Ocr/OcrWindow.cs b/src/ui/Features/Ocr/OcrWindow.cs index def55237777..fe7ea00b65c 100644 --- a/src/ui/Features/Ocr/OcrWindow.cs +++ b/src/ui/Features/Ocr/OcrWindow.cs @@ -984,7 +984,7 @@ private static Border MakeEditView(OcrViewModel vm) } }; - var switchFixOcrErrors = EditorChrome.MakeSwitchRow(nameof(vm.DoFixOcrErrors), Se.Language.Ocr.FixOcrErrors); + var switchFixOcrErrors = EditorChrome.MakeSwitchRow(nameof(vm.DoFixOcrErrors), Se.Language.General.FixOcrErrors); switchFixOcrErrors.Bind(Visual.IsVisibleProperty, new Binding(nameof(vm.IsDictionaryLoaded))); var switchPromptForUnknownWords = EditorChrome.MakeSwitchRow(nameof(vm.DoPromptForUnknownWords), Se.Language.Ocr.PromptForUknownWords); switchPromptForUnknownWords.Bind(Visual.IsVisibleProperty, new Binding(nameof(vm.IsDictionaryLoaded))); diff --git a/src/ui/Features/Ocr/PromptUnknownWordViewModel.cs b/src/ui/Features/Ocr/PromptUnknownWordViewModel.cs index 0aa1a5f63bb..88d0a3613a0 100644 --- a/src/ui/Features/Ocr/PromptUnknownWordViewModel.cs +++ b/src/ui/Features/Ocr/PromptUnknownWordViewModel.cs @@ -81,7 +81,7 @@ public void Initialize(Bitmap bitmap, string wholeText, UnknownWordItem word, Li WholeText = wholeText; _wordOriginal = word.Word.FixedWord; Word = word.Word.FixedWord; - EditButtonLabel = Se.Language.Ocr.EditWholeText; + EditButtonLabel = Se.Language.General.EditWholeText; Suggestions.AddRange(suggestions); Dispatcher.UIThread.Invoke(() => @@ -161,7 +161,7 @@ private void HighLightCurrentWord(UnknownWordItem word) partial void OnDoEditWholeTextChanged(bool value) { - EditButtonLabel = value ? Se.Language.Ocr.EditWordOnly : Se.Language.Ocr.EditWholeText; + EditButtonLabel = value ? Se.Language.Ocr.EditWordOnly : Se.Language.General.EditWholeText; } private bool CanChangeAll() => !DoEditWholeText && !string.IsNullOrWhiteSpace(Word) && Word != _wordOriginal; diff --git a/src/ui/Features/Ocr/PromptUnknownWordWindow.cs b/src/ui/Features/Ocr/PromptUnknownWordWindow.cs index 66a4bdc984a..b10e5ac135c 100644 --- a/src/ui/Features/Ocr/PromptUnknownWordWindow.cs +++ b/src/ui/Features/Ocr/PromptUnknownWordWindow.cs @@ -93,7 +93,7 @@ public PromptUnknownWordWindow(PromptUnknownWordViewModel vm) private static Border MakeWholeTextView(PromptUnknownWordViewModel vm) { vm.TextBoxWholeText.Bind(TextBox.TextProperty, new Binding(nameof(vm.WholeText)) { Mode = BindingMode.TwoWay }); - vm.TextBoxWholeText.WithAccessibleName(Se.Language.Ocr.EditWholeText); + vm.TextBoxWholeText.WithAccessibleName(Se.Language.General.EditWholeText); vm.TextBoxWholeText.Classes.Add("text"); vm.TextBoxWholeText .WithHorizontalAlignmentStretch() diff --git a/src/ui/Features/Options/Settings/ProfilesWindow.cs b/src/ui/Features/Options/Settings/ProfilesWindow.cs index 751091715e7..538e944ebd3 100644 --- a/src/ui/Features/Options/Settings/ProfilesWindow.cs +++ b/src/ui/Features/Options/Settings/ProfilesWindow.cs @@ -210,7 +210,7 @@ private static StackPanel MakeControlsGrid(ProfilesViewModel vm) Row(Se.Language.Options.Settings.MinDurationMs, numericUpDownMinDurationMs), Row(Se.Language.Options.Settings.MaxDurationMs, numericUpDownMaxDurationMs), Row(Se.Language.Options.Settings.MinGapMs, numericUpDownMinGapMs), - Row(Se.Language.Options.Settings.MaxLines, numericUpDownMaxLines), + Row(Se.Language.General.MaxNumberOfLines, numericUpDownMaxLines), Row(Se.Language.Options.Settings.UnbreakSubtitlesShortThan, numericUpDownUnbreakLinesShorterThan), Row(Se.Language.Options.Settings.DialogStyle, comboBoxDialogStyle), Row(Se.Language.Options.Settings.ContinuationStyle, comboBoxContinuationStyle), diff --git a/src/ui/Features/Options/Settings/SettingsPage.cs b/src/ui/Features/Options/Settings/SettingsPage.cs index 4fe920adade..dc890decbf4 100644 --- a/src/ui/Features/Options/Settings/SettingsPage.cs +++ b/src/ui/Features/Options/Settings/SettingsPage.cs @@ -422,7 +422,7 @@ private List CreateSections() new SettingsItem(Se.Language.Options.Settings.SingleLineMaxLength, () => MakeNumericUpDownInt(nameof(_vm.SingleLineMaxLength), _vm.RuleValueChanged)), new SettingsItem(Se.Language.Options.Settings.OptimalCharsPerSec, () => MakeNumericUpDown(nameof(_vm.OptimalCharsPerSec), _vm.RuleValueChanged)), - new SettingsItem(Se.Language.Options.Settings.MaxCharsPerSec, () => MakeNumericUpDown(nameof(_vm.MaxCharsPerSec), _vm.RuleValueChanged)), + new SettingsItem(Se.Language.General.MaxCharsPerSec, () => MakeNumericUpDown(nameof(_vm.MaxCharsPerSec), _vm.RuleValueChanged)), new SettingsItem(Se.Language.Options.Settings.MaxWordsPerMin, () => MakeNumericUpDown(nameof(_vm.MaxWordsPerMin), _vm.RuleValueChanged)), new SettingsItem(Se.Language.Options.Settings.MinDurationMs, () => MakeNumericUpDownInt(nameof(_vm.MinDurationMs), _vm.RuleValueChanged)), new SettingsItem(Se.Language.Options.Settings.MaxDurationMs, () => MakeNumericUpDownInt(nameof(_vm.MaxDurationMs), _vm.RuleValueChanged)), @@ -450,7 +450,7 @@ private List CreateSections() { EnglishLabel = EnglishLabelLookup.English.Options.Settings.MinGapMs + " " + EnglishLabelLookup.English.Options.Settings.MinGapFrames, }, - new SettingsItem(Se.Language.Options.Settings.MaxLines,() => MakeNumericUpDownInt(nameof(_vm.MaxLines), _vm.RuleValueChanged)), + new SettingsItem(Se.Language.General.MaxNumberOfLines,() => MakeNumericUpDownInt(nameof(_vm.MaxLines), _vm.RuleValueChanged)), new SettingsItem(Se.Language.Options.Settings.UnbreakSubtitlesShortThan, () => MakeNumericUpDownInt(nameof(_vm.UnbreakLinesShorterThan), _vm.RuleValueChanged)), new SettingsItem(Se.Language.Options.Settings.DialogStyle, () => MakeComboBoxDialogStyle()), @@ -978,7 +978,7 @@ private List CreateSections() sections.Add(new SettingsSection(Se.Language.General.Appearance, IconNames.EyeSettings, "#4fc3e8", [ - new SettingsItem(Se.Language.Options.Settings.Theme, () => UiUtil.MakeComboBox(_vm.Themes, _vm, nameof(_vm.SelectedTheme))), + new SettingsItem(Se.Language.General.Theme, () => UiUtil.MakeComboBox(_vm.Themes, _vm, nameof(_vm.SelectedTheme))), new SettingsItem(Se.Language.Options.Settings.IconTheme, () => UiUtil.MakeComboBox(_vm.IconThemes, _vm, nameof(_vm.SelectedIconTheme))), MakeCheckboxSetting(Se.Language.Options.Settings.MatchIconColorToDarkTheme, nameof(_vm.MatchIconColorToDarkTheme)), new SettingsItem(Se.Language.Options.Settings.UiScale, () => UiUtil.MakeNumericUpDownInt( diff --git a/src/ui/Features/Options/Shortcuts/CustomShortcuts/CustomShortcutStepViewModel.cs b/src/ui/Features/Options/Shortcuts/CustomShortcuts/CustomShortcutStepViewModel.cs index c378fc4c1e1..5283ac2668a 100644 --- a/src/ui/Features/Options/Shortcuts/CustomShortcuts/CustomShortcutStepViewModel.cs +++ b/src/ui/Features/Options/Shortcuts/CustomShortcuts/CustomShortcutStepViewModel.cs @@ -154,7 +154,7 @@ private async Task Ok() var regexError = CustomShortcutText.GetRegexError(step); if (regexError != null) { - await ShowError(string.Format(language.CustomShortcutInvalidRegexX, regexError)); + await ShowError(string.Format(Se.Language.General.InvalidRegularExpressionX, regexError)); return; } diff --git a/src/ui/Features/Shared/BinaryEdit/BinaryAdjustBrightness/BinaryAdjustBrightnessWindow.cs b/src/ui/Features/Shared/BinaryEdit/BinaryAdjustBrightness/BinaryAdjustBrightnessWindow.cs index c19b7559134..f520f6e9d17 100644 --- a/src/ui/Features/Shared/BinaryEdit/BinaryAdjustBrightness/BinaryAdjustBrightnessWindow.cs +++ b/src/ui/Features/Shared/BinaryEdit/BinaryAdjustBrightness/BinaryAdjustBrightnessWindow.cs @@ -61,7 +61,7 @@ private static StackPanel MakeControlsPanel(BinaryAdjustBrightnessViewModel vm, var panel = new StackPanel(); brightnessInputSlider = MakeSlider(nameof(vm.Brightness), -100, 100); - panel.Children.Add(BinaryEditChrome.MakeSliderSection(l.Brightness, brightnessInputSlider, nameof(vm.BrightnessDisplay), first: true)); + panel.Children.Add(BinaryEditChrome.MakeSliderSection(Se.Language.General.Brightness, brightnessInputSlider, nameof(vm.BrightnessDisplay), first: true)); panel.Children.Add(BinaryEditChrome.MakeSliderSection(l.Contrast, MakeSlider(nameof(vm.Contrast), -100, 100), nameof(vm.ContrastDisplay))); panel.Children.Add(BinaryEditChrome.MakeSliderSection(l.Gamma, MakeSlider(nameof(vm.Gamma), 1, 300), nameof(vm.GammaDisplay))); diff --git a/src/ui/Features/Shared/BinaryEdit/BinaryAdjustColor/BinaryAdjustColorWindow.cs b/src/ui/Features/Shared/BinaryEdit/BinaryAdjustColor/BinaryAdjustColorWindow.cs index 6059d164c10..b996d9a7a62 100644 --- a/src/ui/Features/Shared/BinaryEdit/BinaryAdjustColor/BinaryAdjustColorWindow.cs +++ b/src/ui/Features/Shared/BinaryEdit/BinaryAdjustColor/BinaryAdjustColorWindow.cs @@ -64,7 +64,7 @@ private static Border MakeControlsPanel(BinaryAdjustColorViewModel vm) [!Border.BackgroundProperty] = new Binding(nameof(vm.ColorSwatchBrush)), }, // The swatch is the whole content, so name the button or it reads as "Border" (#12087). - [AutomationProperties.NameProperty] = Se.Language.Tools.ColorPickerTitle, + [AutomationProperties.NameProperty] = Se.Language.General.ChooseColor, }; panel.Children.Add(swatchButton); panel.Children.Add(EditorChrome.MakeHint(Se.Language.Tools.ImageBasedEdit.ColorAdjustmentInfo)); diff --git a/src/ui/Features/Shared/BinaryEdit/BinaryEditWindow.cs b/src/ui/Features/Shared/BinaryEdit/BinaryEditWindow.cs index 743ba313597..f38c7c348d6 100644 --- a/src/ui/Features/Shared/BinaryEdit/BinaryEditWindow.cs +++ b/src/ui/Features/Shared/BinaryEdit/BinaryEditWindow.cs @@ -291,7 +291,7 @@ private static Menu MakeTopMenu(BinaryEditViewModel vm) new Separator(), new MenuItem { - Header = Se.Language.Tools.ImageBasedEdit.ImportTimeCodes, + Header = Se.Language.General.ImportTimeCodesDotDotDot, Command = vm.ImportTimeCodesCommand, }, new Separator(), diff --git a/src/ui/Features/Shared/BinaryEdit/InitNativeMacMenuBinaryEdit.cs b/src/ui/Features/Shared/BinaryEdit/InitNativeMacMenuBinaryEdit.cs index 34645181a17..55a88d396d8 100644 --- a/src/ui/Features/Shared/BinaryEdit/InitNativeMacMenuBinaryEdit.cs +++ b/src/ui/Features/Shared/BinaryEdit/InitNativeMacMenuBinaryEdit.cs @@ -32,7 +32,7 @@ public static void Setup(Window window, BinaryEditViewModel vm) Add(exportMenu, Se.Language.File.Export.TitleExportWebVttThumbnails, vm.ExportWebVttThumbnailCommand); fileMenu.Items.Add(new NativeMenuItem(Clean(l.Export)) { Menu = exportMenu }); fileMenu.Items.Add(new NativeMenuItemSeparator()); - Add(fileMenu, Se.Language.Tools.ImageBasedEdit.ImportTimeCodes, vm.ImportTimeCodesCommand); + Add(fileMenu, Se.Language.General.ImportTimeCodesDotDotDot, vm.ImportTimeCodesCommand); fileMenu.Items.Add(new NativeMenuItemSeparator()); Add(fileMenu, l.Exit, vm.CancelCommand); root.Items.Add(new NativeMenuItem(Clean(l.File)) { Menu = fileMenu }); diff --git a/src/ui/Features/Shared/ColorPicker/ColorBrightnessBar.cs b/src/ui/Features/Shared/ColorPicker/ColorBrightnessBar.cs index eca3a2e3913..45618ed6774 100644 --- a/src/ui/Features/Shared/ColorPicker/ColorBrightnessBar.cs +++ b/src/ui/Features/Shared/ColorPicker/ColorBrightnessBar.cs @@ -51,7 +51,7 @@ public ColorBrightnessBar() Width = 20 + MarkerWidth; Height = 200; Cursor = new Cursor(StandardCursorType.Hand); - AutomationProperties.SetName(this, Se.Language.Tools.ColorPickerBrightness); + AutomationProperties.SetName(this, Se.Language.General.Brightness); } private Rect BarRect => new Rect(0, 0, Math.Max(0, Bounds.Width - MarkerWidth), Bounds.Height); diff --git a/src/ui/Features/Shared/ColorPicker/ColorPickerWindow.cs b/src/ui/Features/Shared/ColorPicker/ColorPickerWindow.cs index 51558c34eea..4791ac25fd9 100644 --- a/src/ui/Features/Shared/ColorPicker/ColorPickerWindow.cs +++ b/src/ui/Features/Shared/ColorPicker/ColorPickerWindow.cs @@ -19,7 +19,7 @@ public class ColorPickerWindow : Window public ColorPickerWindow(ColorPickerViewModel vm) { UiUtil.InitializeWindow(this, GetType().Name); - Title = Se.Language.Tools.ColorPickerTitle; + Title = Se.Language.General.ChooseColor; CanResize = false; SizeToContent = SizeToContent.WidthAndHeight; WindowStartupLocation = WindowStartupLocation.CenterOwner; diff --git a/src/ui/Features/Shared/ErrorList/ErrorListExport.cs b/src/ui/Features/Shared/ErrorList/ErrorListExport.cs index 30047ca0246..964b91559f8 100644 --- a/src/ui/Features/Shared/ErrorList/ErrorListExport.cs +++ b/src/ui/Features/Shared/ErrorList/ErrorListExport.cs @@ -43,7 +43,7 @@ private static ReportExportData MakeData(IEnumerable items, strin Summary = summary, FileName = subtitleFileName, CategoryHeader = Se.Language.General.Error, - DetailHeader = l.Detail, + DetailHeader = Se.Language.General.Detail, AllLabel = l.All, AllColor = LineError.AllColor, Chips = Enum.GetValues().Select(type => new ReportExportChip diff --git a/src/ui/Features/Shared/ErrorList/ErrorListWindow.cs b/src/ui/Features/Shared/ErrorList/ErrorListWindow.cs index 54952cb8243..c15ee1f8361 100644 --- a/src/ui/Features/Shared/ErrorList/ErrorListWindow.cs +++ b/src/ui/Features/Shared/ErrorList/ErrorListWindow.cs @@ -244,7 +244,7 @@ private static Border MakeErrorsGridView(ErrorListViewModel vm) }); dataGrid.Columns.Add(new SeTableViewColumn { - Header = l.Detail, + Header = Se.Language.General.Detail, Binding = new Binding(nameof(ErrorListItem.Detail)), Width = new GridLength(200), CellTheme = UiUtil.TableViewCellTheme, diff --git a/src/ui/Features/Shared/ErrorList/LineError.cs b/src/ui/Features/Shared/ErrorList/LineError.cs index 1787616ceb9..649419ced02 100644 --- a/src/ui/Features/Shared/ErrorList/LineError.cs +++ b/src/ui/Features/Shared/ErrorList/LineError.cs @@ -32,11 +32,11 @@ public static string GetLabel(LineErrorType type) { LineErrorType.TooManyLines => l.TooManyLines, LineErrorType.CharactersPerSecond => l.CharactersPerSecond, - LineErrorType.DurationTooShort => l.DurationTooShort, - LineErrorType.DurationTooLong => l.DurationTooLong, + LineErrorType.DurationTooShort => Se.Language.General.TooShort, + LineErrorType.DurationTooLong => Se.Language.General.TooLong, LineErrorType.LineTooLong => l.LineTooLong, LineErrorType.LineTooWide => l.LineTooWide, - LineErrorType.Overlap => l.Overlap, + LineErrorType.Overlap => Se.Language.General.Overlapping, LineErrorType.GapTooShort => l.GapTooShort, _ => type.ToString(), }; diff --git a/src/ui/Features/Shared/SourceView/SourceViewViewModel.cs b/src/ui/Features/Shared/SourceView/SourceViewViewModel.cs index 9476d82f280..f4e1191770b 100644 --- a/src/ui/Features/Shared/SourceView/SourceViewViewModel.cs +++ b/src/ui/Features/Shared/SourceView/SourceViewViewModel.cs @@ -844,7 +844,7 @@ public async Task ConfirmDiscardAsync() var result = await MessageBox.Show( Window, - Se.Language.SourceView.DiscardChangesTitle, + Se.Language.General.DiscardChangesTitle, Se.Language.SourceView.DiscardChanges, MessageBoxButtons.YesNo, MessageBoxIcon.Question); diff --git a/src/ui/Features/SpellCheck/SpellCheckViewModel.cs b/src/ui/Features/SpellCheck/SpellCheckViewModel.cs index 77e17e66543..ce1c7af89ef 100644 --- a/src/ui/Features/SpellCheck/SpellCheckViewModel.cs +++ b/src/ui/Features/SpellCheck/SpellCheckViewModel.cs @@ -789,7 +789,7 @@ private async Task EditWholeText() return; } - PushUndo(Se.Language.SpellCheck.EditWholeText, SpellCheckUndoAction.ChangeWholeText, string.Empty); + PushUndo(Se.Language.General.EditWholeText, SpellCheckUndoAction.ChangeWholeText, string.Empty); selectedParagraph.Text = result.WholeText; DoSpellCheck(); } diff --git a/src/ui/Features/SpellCheck/SpellCheckWindow.cs b/src/ui/Features/SpellCheck/SpellCheckWindow.cs index 142ee3e5bc9..3ede5ca865e 100644 --- a/src/ui/Features/SpellCheck/SpellCheckWindow.cs +++ b/src/ui/Features/SpellCheck/SpellCheckWindow.cs @@ -124,7 +124,7 @@ private static Border MakeLineSection(SpellCheckViewModel vm) ToolTip.SetTip(buttonPlay, Se.Language.SpellCheck.PlayCurrentLineHint); } - var buttonEditWholeText = EditorChrome.CreateToolButton(IconNames.Pencil, Se.Language.SpellCheck.EditWholeText, vm.EditWholeTextCommand, 28); + var buttonEditWholeText = EditorChrome.CreateToolButton(IconNames.Pencil, Se.Language.General.EditWholeText, vm.EditWholeTextCommand, 28); var headerRight = new StackPanel { diff --git a/src/ui/Features/Tools/AiReview/AiReviewPromptWindow.cs b/src/ui/Features/Tools/AiReview/AiReviewPromptWindow.cs index 3063fd0a860..224167343ee 100644 --- a/src/ui/Features/Tools/AiReview/AiReviewPromptWindow.cs +++ b/src/ui/Features/Tools/AiReview/AiReviewPromptWindow.cs @@ -51,7 +51,7 @@ public AiReviewPromptWindow(AiReviewPromptViewModel vm) Children = { EditorChrome.MakeDimLabel(l.Template, new Thickness(0)), comboTemplate }, }; - var promptSection = MakeSection(l.Prompt, panelTemplate, l.PromptInfo, textBox, first: true); + var promptSection = MakeSection(Se.Language.General.Prompt, panelTemplate, l.PromptInfo, textBox, first: true); // ---------- reference context ---------- var buttonGenerate = UiUtil.MakeButton(l.GenerateContext, vm.GenerateContextCommand); diff --git a/src/ui/Features/Tools/AiReview/AiReviewViewModel.cs b/src/ui/Features/Tools/AiReview/AiReviewViewModel.cs index 50ba86ce0b8..3d7d99bc473 100644 --- a/src/ui/Features/Tools/AiReview/AiReviewViewModel.cs +++ b/src/ui/Features/Tools/AiReview/AiReviewViewModel.cs @@ -140,7 +140,7 @@ public AiReviewViewModel(IWindowService windowService) new() { Category = ReviewCategory.Grammar, Label = l.CategoryGrammar }, new() { Category = ReviewCategory.Punctuation, Label = l.CategoryPunctuation }, new() { Category = ReviewCategory.Casing, Label = l.CategoryCasing }, - new() { Category = ReviewCategory.Other, Label = l.CategoryOther }, + new() { Category = ReviewCategory.Other, Label = Se.Language.General.Other }, }; ApplyButtonText = string.Format(l.ApplyXFixes, 0); @@ -228,7 +228,7 @@ partial void OnSelectedSuggestionChanged(ReviewSuggestionItem? value) var unitLines = _allSuggestions.Where(s => s.UnitId == value.UnitId).Select(s => s.Number).OrderBy(n => n).ToList(); var who = unitLines.Count > 1 ? string.Format(l.LinesXToY, unitLines.First(), unitLines.Last()) - : string.Format(l.LineX, value.Number); + : string.Format(Se.Language.General.LineX, value.Number); ReasonText = string.IsNullOrEmpty(value.Reason) ? who : $"{who}: {value.Reason}"; UpdateContext(value.ParagraphIndex); } @@ -242,15 +242,14 @@ partial void OnSelectedSuggestionChanged(ReviewSuggestionItem? value) ///
private void UpdateContext(int paragraphIndex) { - var l = Se.Language.Tools.AiReview; var previousIndex = paragraphIndex - 1; var nextIndex = paragraphIndex + 1; var hasPrevious = previousIndex >= 0 && previousIndex < _subtitle.Paragraphs.Count; var hasNext = nextIndex >= 0 && nextIndex < _subtitle.Paragraphs.Count; - ContextPreviousLabel = hasPrevious ? string.Format(l.LineX, previousIndex + 1) : string.Empty; + ContextPreviousLabel = hasPrevious ? string.Format(Se.Language.General.LineX, previousIndex + 1) : string.Empty; ContextPreviousText = hasPrevious ? GetContextText(previousIndex) : string.Empty; - ContextNextLabel = hasNext ? string.Format(l.LineX, nextIndex + 1) : string.Empty; + ContextNextLabel = hasNext ? string.Format(Se.Language.General.LineX, nextIndex + 1) : string.Empty; ContextNextText = hasNext ? GetContextText(nextIndex) : string.Empty; HasContext = hasPrevious || hasNext; } diff --git a/src/ui/Features/Tools/AiReview/ReviewSuggestionItem.cs b/src/ui/Features/Tools/AiReview/ReviewSuggestionItem.cs index 0a19f4fcfe8..8057225dd33 100644 --- a/src/ui/Features/Tools/AiReview/ReviewSuggestionItem.cs +++ b/src/ui/Features/Tools/AiReview/ReviewSuggestionItem.cs @@ -31,7 +31,7 @@ public partial class ReviewSuggestionItem : ObservableObject ReviewCategory.Grammar => Se.Language.Tools.AiReview.CategoryGrammar, ReviewCategory.Punctuation => Se.Language.Tools.AiReview.CategoryPunctuation, ReviewCategory.Casing => Se.Language.Tools.AiReview.CategoryCasing, - _ => Se.Language.Tools.AiReview.CategoryOther, + _ => Se.Language.General.Other, }; public IBrush CategoryBrush => GetBrushForCategory(Category); diff --git a/src/ui/Features/Tools/BatchConvert/BatchConvertSettingsWindow.cs b/src/ui/Features/Tools/BatchConvert/BatchConvertSettingsWindow.cs index be49e3eee23..6ccad5e519f 100644 --- a/src/ui/Features/Tools/BatchConvert/BatchConvertSettingsWindow.cs +++ b/src/ui/Features/Tools/BatchConvert/BatchConvertSettingsWindow.cs @@ -161,7 +161,7 @@ public BatchConvertSettingsWindow(BatchConvertSettingsViewModel vm) Width = ColumnWidth, BorderBrush = EditorChrome.LineBrush, BorderThickness = new Thickness(0, 0, 1, 0), - Child = EditorChrome.MakeSectionStack(EditorChrome.MakeEditorSection(Se.Language.Tools.BatchConvert.SettingsSectionOutput, output, first: true), EditorChrome.MakeEditorSection(Se.Language.Tools.BatchConvert.SettingsSectionOther, other)), + Child = EditorChrome.MakeSectionStack(EditorChrome.MakeEditorSection(Se.Language.General.Output, output, first: true), EditorChrome.MakeEditorSection(Se.Language.General.Other, other)), }; var right = new Border { diff --git a/src/ui/Features/Tools/BatchConvert/BatchConvertTsSettingsWindow.cs b/src/ui/Features/Tools/BatchConvert/BatchConvertTsSettingsWindow.cs index f347d63b0f6..47408d8c937 100644 --- a/src/ui/Features/Tools/BatchConvert/BatchConvertTsSettingsWindow.cs +++ b/src/ui/Features/Tools/BatchConvert/BatchConvertTsSettingsWindow.cs @@ -135,7 +135,7 @@ public BatchConvertTsSettingsWindow(BatchConvertTsSettingsViewModel vm) buttonOk.Classes.Add("primary"); var buttonCancel = UiUtil.MakeButtonCancel(vm.CancelCommand); - var sections = EditorChrome.MakeSectionStack(new Border { Child = labelInfo, Padding = new Thickness(14, 12, 14, 0) }, EditorChrome.MakeEditorSection(Se.Language.General.Position, position, first: true), EditorChrome.MakeEditorSection(Se.Language.General.VideoResolution, videoSize), EditorChrome.MakeEditorSection(Se.Language.Tools.BatchConvert.SettingsSectionOutput, outputFiles)); + var sections = EditorChrome.MakeSectionStack(new Border { Child = labelInfo, Padding = new Thickness(14, 12, 14, 0) }, EditorChrome.MakeEditorSection(Se.Language.General.Position, position, first: true), EditorChrome.MakeEditorSection(Se.Language.General.VideoResolution, videoSize), EditorChrome.MakeEditorSection(Se.Language.General.Output, outputFiles)); sections.MinWidth = 600; Content = new StackPanel diff --git a/src/ui/Features/Tools/BatchConvert/BatchConvertWindow.cs b/src/ui/Features/Tools/BatchConvert/BatchConvertWindow.cs index 3c76638d414..e1ad1ff427a 100644 --- a/src/ui/Features/Tools/BatchConvert/BatchConvertWindow.cs +++ b/src/ui/Features/Tools/BatchConvert/BatchConvertWindow.cs @@ -533,8 +533,8 @@ private static Border MakeFunctionsPanel(BatchConvertViewModel vm) { var comboBoxPreset = UiUtil.MakeComboBox(vm.PresetNames, vm, nameof(vm.SelectedPresetName)); comboBoxPreset.HorizontalAlignment = HorizontalAlignment.Stretch; - comboBoxPreset.PlaceholderText = Se.Language.Tools.BatchConvert.Preset; - comboBoxPreset.WithAccessibleName(Se.Language.Tools.BatchConvert.Preset); + comboBoxPreset.PlaceholderText = Se.Language.General.Preset; + comboBoxPreset.WithAccessibleName(Se.Language.General.Preset); var buttonSavePreset = EditorChrome.CreateToolButton("fa-solid fa-floppy-disk", Se.Language.Tools.BatchConvert.SavePresetDotDotDot, vm.SavePresetCommand, 28); var buttonDeletePreset = EditorChrome.CreateToolButton("fa-solid fa-trash", Se.Language.Tools.BatchConvert.DeletePreset, vm.DeletePresetCommand, 28); var panelPresets = new Grid @@ -548,7 +548,7 @@ private static Border MakeFunctionsPanel(BatchConvertViewModel vm) new ColumnDefinition(GridLength.Auto), }, }; - panelPresets.Add(EditorChrome.MakeDimLabel(Se.Language.Tools.BatchConvert.Preset, new Thickness(0, 0, 4, 0)), 0, 0); + panelPresets.Add(EditorChrome.MakeDimLabel(Se.Language.General.Preset, new Thickness(0, 0, 4, 0)), 0, 0); panelPresets.Add(comboBoxPreset, 0, 1); panelPresets.Add(buttonSavePreset, 0, 2); panelPresets.Add(buttonDeletePreset, 0, 3); diff --git a/src/ui/Features/Tools/BatchConvert/BatchErrorList/BatchErrorListWindow.cs b/src/ui/Features/Tools/BatchConvert/BatchErrorList/BatchErrorListWindow.cs index 67982860d7a..c4029669fa4 100644 --- a/src/ui/Features/Tools/BatchConvert/BatchErrorList/BatchErrorListWindow.cs +++ b/src/ui/Features/Tools/BatchConvert/BatchErrorList/BatchErrorListWindow.cs @@ -172,7 +172,7 @@ private static Border MakeErrorsGridView(BatchErrorListViewModel vm) }); dataGrid.Columns.Add(new SeTableViewColumn { - Header = l.Detail, + Header = Se.Language.General.Detail, Binding = new Binding(nameof(BatchErrorListItem.Detail)), CellTheme = UiUtil.TableViewCellTheme, HeaderTheme = UiUtil.TableViewColumnHeaderTheme, diff --git a/src/ui/Features/Tools/BatchConvert/FunctionViews/ViewAdjustImageColors.cs b/src/ui/Features/Tools/BatchConvert/FunctionViews/ViewAdjustImageColors.cs index b712824f03d..835f2628a2a 100644 --- a/src/ui/Features/Tools/BatchConvert/FunctionViews/ViewAdjustImageColors.cs +++ b/src/ui/Features/Tools/BatchConvert/FunctionViews/ViewAdjustImageColors.cs @@ -19,7 +19,7 @@ public static Control Make(BatchConvertViewModel vm) EditorChrome.MakeHint(Se.Language.Tools.BatchConvert.AdjustImageColorsInfo), EditorChrome.MakeSwitchRow(nameof(vm.ImageAdjustBrightnessOn), Se.Language.Tools.ImageBasedEdit.AdjustBrightness), - MakePair(vm, Se.Language.Tools.ImageBasedEdit.Brightness, nameof(vm.ImageAdjustBrightness), nameof(vm.ImageAdjustBrightnessOn), -100, 100, 1), + MakePair(vm, Se.Language.General.Brightness, nameof(vm.ImageAdjustBrightness), nameof(vm.ImageAdjustBrightnessOn), -100, 100, 1), MakePair(vm, Se.Language.Tools.ImageBasedEdit.Contrast, nameof(vm.ImageAdjustContrast), nameof(vm.ImageAdjustBrightnessOn), -100, 100, 1), MakePair(vm, Se.Language.Tools.ImageBasedEdit.Gamma + " (%)", nameof(vm.ImageAdjustGamma), nameof(vm.ImageAdjustBrightnessOn), 20, 400, 5), diff --git a/src/ui/Features/Tools/BatchConvert/FunctionViews/ViewSplitBreakLongLines.cs b/src/ui/Features/Tools/BatchConvert/FunctionViews/ViewSplitBreakLongLines.cs index aaabfc2ff00..81b8504aaa4 100644 --- a/src/ui/Features/Tools/BatchConvert/FunctionViews/ViewSplitBreakLongLines.cs +++ b/src/ui/Features/Tools/BatchConvert/FunctionViews/ViewSplitBreakLongLines.cs @@ -18,7 +18,7 @@ public static Control Make(BatchConvertViewModel vm) switchRebalanceOnlyTooLong[!InputElement.IsEnabledProperty] = new Binding(nameof(vm.SplitBreakRebalanceLongLines)) { Source = vm }; var numericUpDownSingleLineMaxLength = FunctionViewChrome.MakeIntField(vm, nameof(vm.SplitBreakSingleLineMaxLength), 5, 1000, 10, Se.Language.Options.Settings.SingleLineMaxLength); - var numericUpDownMaxNumberOfLines = FunctionViewChrome.MakeIntField(vm, nameof(vm.SplitBreakMaxNumberOfLines), 1, 10, 2, Se.Language.Options.Settings.MaxLines); + var numericUpDownMaxNumberOfLines = FunctionViewChrome.MakeIntField(vm, nameof(vm.SplitBreakMaxNumberOfLines), 1, 10, 2, Se.Language.General.MaxNumberOfLines); var numericUpDownUnbreakLinesShorterThan = FunctionViewChrome.MakeIntField(vm, nameof(vm.SplitBreakUnbreakLinesShorterThan), 1, 1000, 33, Se.Language.Options.Settings.UnbreakSubtitlesShortThan); var rowUnbreakLinesShorterThan = FunctionViewChrome.MakeRow(Se.Language.Options.Settings.UnbreakSubtitlesShortThan, numericUpDownUnbreakLinesShorterThan); if (Se.Settings.Appearance.ShowHints) @@ -33,7 +33,7 @@ public static Control Make(BatchConvertViewModel vm) switchRebalanceLongLines, switchRebalanceOnlyTooLong, FunctionViewChrome.MakeRow(Se.Language.Options.Settings.SingleLineMaxLength, numericUpDownSingleLineMaxLength), - FunctionViewChrome.MakeRow(Se.Language.Options.Settings.MaxLines, numericUpDownMaxNumberOfLines), + FunctionViewChrome.MakeRow(Se.Language.General.MaxNumberOfLines, numericUpDownMaxNumberOfLines), rowUnbreakLinesShorterThan); } } diff --git a/src/ui/Features/Tools/BeautifyTimeCodes/BeautifyTimeCodesViewModel.cs b/src/ui/Features/Tools/BeautifyTimeCodes/BeautifyTimeCodesViewModel.cs index 93701aad988..ec4b2f4367a 100644 --- a/src/ui/Features/Tools/BeautifyTimeCodes/BeautifyTimeCodesViewModel.cs +++ b/src/ui/Features/Tools/BeautifyTimeCodes/BeautifyTimeCodesViewModel.cs @@ -253,7 +253,7 @@ private void UpdateChangeView() CanGoPrevious = _currentChangeIndex > 0; CanGoNext = _currentChangeIndex < _changedIndices.Count - 1; ChangePositionLabel = string.Format( - Se.Language.Tools.BeautifyTimeCodes.ChangeXOfY, + Se.Language.General.ChangeXOfY, _currentChangeIndex + 1, _changedIndices.Count); var idx = _changedIndices[_currentChangeIndex]; diff --git a/src/ui/Features/Tools/BeautifyTimeCodes/BeautifyTimeCodesWindow.cs b/src/ui/Features/Tools/BeautifyTimeCodes/BeautifyTimeCodesWindow.cs index 4ad0825f7fb..6bf50d4f915 100644 --- a/src/ui/Features/Tools/BeautifyTimeCodes/BeautifyTimeCodesWindow.cs +++ b/src/ui/Features/Tools/BeautifyTimeCodes/BeautifyTimeCodesWindow.cs @@ -138,7 +138,7 @@ private static Control BuildVisualizerArea(BeautifyTimeCodesViewModel vm) { var l = Se.Language.Tools.BeautifyTimeCodes; - var (labelOriginal, borderOriginal, avOriginal) = BuildLabeledVisualizer(l.Original); + var (labelOriginal, borderOriginal, avOriginal) = BuildLabeledVisualizer(Se.Language.General.Original); var (labelBeautified, borderBeautified, avBeautified) = BuildLabeledVisualizer(l.Beautified); vm.AudioVisualizerOriginal = avOriginal; @@ -203,14 +203,12 @@ private static (TextBlock label, Border border, Controls.AudioVisualizerControl. private static Control BuildChangeNavBar(BeautifyTimeCodesViewModel vm) { - var l = Se.Language.Tools.BeautifyTimeCodes; - // The same steps are on the keyboard (see BeautifyTimeCodesViewModel.OnKeyDown); the // tooltips are the only place that advertises it, so name the keys there. - var buttonPrev = EditorChrome.CreateToolButton("fa-solid fa-chevron-up", $"{l.PreviousChange} (Up / PageUp)", vm.PreviousChangeCommand, 28); + var buttonPrev = EditorChrome.CreateToolButton("fa-solid fa-chevron-up", $"{Se.Language.General.PreviousChange} (Up / PageUp)", vm.PreviousChangeCommand, 28); buttonPrev.Bind(Button.IsEnabledProperty, new Binding(nameof(vm.CanGoPrevious)) { Source = vm }); - var buttonNext = EditorChrome.CreateToolButton("fa-solid fa-chevron-down", $"{l.NextChange} (Down / PageDown)", vm.NextChangeCommand, 28); + var buttonNext = EditorChrome.CreateToolButton("fa-solid fa-chevron-down", $"{Se.Language.General.NextChange} (Down / PageDown)", vm.NextChangeCommand, 28); buttonNext.Bind(Button.IsEnabledProperty, new Binding(nameof(vm.CanGoNext)) { Source = vm }); var labelPosition = new TextBlock diff --git a/src/ui/Features/Tools/ImproveTimeCodes/ImproveTimeCodesViewModel.cs b/src/ui/Features/Tools/ImproveTimeCodes/ImproveTimeCodesViewModel.cs index cb105fca9ac..52de4a76b57 100644 --- a/src/ui/Features/Tools/ImproveTimeCodes/ImproveTimeCodesViewModel.cs +++ b/src/ui/Features/Tools/ImproveTimeCodes/ImproveTimeCodesViewModel.cs @@ -838,7 +838,7 @@ private void UpdateChangeNavigation() var position = SelectedRow != null ? changed.IndexOf(SelectedRow) : -1; ChangePositionLabel = position >= 0 - ? string.Format(Se.Language.Tools.ImproveTimeCodes.ChangeXOfY, position + 1, changed.Count) + ? string.Format(Se.Language.General.ChangeXOfY, position + 1, changed.Count) : string.Empty; } diff --git a/src/ui/Features/Tools/ImproveTimeCodes/ImproveTimeCodesWindow.cs b/src/ui/Features/Tools/ImproveTimeCodes/ImproveTimeCodesWindow.cs index c3c62b8d3d2..6dc42f08155 100644 --- a/src/ui/Features/Tools/ImproveTimeCodes/ImproveTimeCodesWindow.cs +++ b/src/ui/Features/Tools/ImproveTimeCodes/ImproveTimeCodesWindow.cs @@ -202,7 +202,7 @@ private static Control BuildVisualizerArea(ImproveTimeCodesViewModel vm) { var l = Se.Language.Tools.ImproveTimeCodes; - var (labelOriginal, borderOriginal, avOriginal) = BuildLabeledVisualizer(l.Original, OriginalTint); + var (labelOriginal, borderOriginal, avOriginal) = BuildLabeledVisualizer(Se.Language.General.Original, OriginalTint); var (labelAligned, borderAligned, avAligned) = BuildLabeledVisualizer(l.Aligned, AlignedTint); vm.AudioVisualizerOriginal = avOriginal; @@ -289,7 +289,7 @@ private static void MakeEditable(AudioVisualizer av, ImproveTimeCodesViewModel v av.OnDragEnded += (_, _) => vm.OnAlignedDragEnded(); var l = Se.Language.Tools.ImproveTimeCodes; - var menuPlay = new Avalonia.Controls.MenuItem { Header = l.PlayLine, Command = vm.PlayRowCommand }; + var menuPlay = new Avalonia.Controls.MenuItem { Header = Se.Language.General.PlayLine, Command = vm.PlayRowCommand }; var menuUndo = new Avalonia.Controls.MenuItem { Header = l.UndoAdjustment, Command = vm.UndoAdjustmentCommand }; var flyout = new MenuFlyout(); flyout.Items.Add(menuPlay); @@ -356,10 +356,10 @@ private static Control BuildChangeNavBar(ImproveTimeCodesViewModel vm) { var l = Se.Language.Tools.ImproveTimeCodes; - var buttonPrev = EditorChrome.CreateToolButton(IconNames.ArrowUpThin, $"{l.PreviousChange} (F7)", vm.PreviousChangeCommand, 28); + var buttonPrev = EditorChrome.CreateToolButton(IconNames.ArrowUpThin, $"{Se.Language.General.PreviousChange} (F7)", vm.PreviousChangeCommand, 28); buttonPrev.Bind(IsEnabledProperty, new Binding(nameof(vm.CanGoPrevious)) { Source = vm }); - var buttonNext = EditorChrome.CreateToolButton(IconNames.ArrowDownThin, $"{l.NextChange} (F8)", vm.NextChangeCommand, 28); + var buttonNext = EditorChrome.CreateToolButton(IconNames.ArrowDownThin, $"{Se.Language.General.NextChange} (F8)", vm.NextChangeCommand, 28); buttonNext.Bind(IsEnabledProperty, new Binding(nameof(vm.CanGoNext)) { Source = vm }); var labelPosition = new TextBlock @@ -447,7 +447,7 @@ private static Control BuildRowsView(ImproveTimeCodesViewModel vm) var applyColumn = new SeTableViewColumn { - Header = l.Apply, + Header = Se.Language.General.Apply, CellTheme = UiUtil.TableViewNoPaddingCellTheme, HeaderTheme = UiUtil.TableViewColumnHeaderTheme, CellTemplate = new FuncDataTemplate((_, _) => new Border diff --git a/src/ui/Features/Tools/MergeShortLines/MergeShortLinesWindow.cs b/src/ui/Features/Tools/MergeShortLines/MergeShortLinesWindow.cs index 5758549d556..1acf067c986 100644 --- a/src/ui/Features/Tools/MergeShortLines/MergeShortLinesWindow.cs +++ b/src/ui/Features/Tools/MergeShortLines/MergeShortLinesWindow.cs @@ -55,7 +55,7 @@ private Border MakeControlsView(MergeShortLinesViewModel vm) _numericUpDownSingleLineMaxLength = numericUpDownSingleLineMaxLength; numericUpDownSingleLineMaxLength.ValueChanged += (s, e) => vm.SetChanged(); - var labelMaxNumberOfLines = Se.Language.Options.Settings.MaxLines; + var labelMaxNumberOfLines = Se.Language.General.MaxNumberOfLines; var numericUpDownMaxNumberOfLines = EditorChrome.AsField(UiUtil.MakeNumericUpDownInt(2, 10, 2, 130, vm, nameof(vm.MaxNumberOfLines)), labelMaxNumberOfLines.TrimEnd(':'), 100, alignLeft: true, center: true); numericUpDownMaxNumberOfLines.ValueChanged += (s, e) => vm.SetChanged(); diff --git a/src/ui/Features/Tools/MergeTwoSubtitles/MergeTwoSubtitlesWindow.cs b/src/ui/Features/Tools/MergeTwoSubtitles/MergeTwoSubtitlesWindow.cs index 097ec1171f5..5c14ed99b22 100644 --- a/src/ui/Features/Tools/MergeTwoSubtitles/MergeTwoSubtitlesWindow.cs +++ b/src/ui/Features/Tools/MergeTwoSubtitles/MergeTwoSubtitlesWindow.cs @@ -210,10 +210,10 @@ private Border MakeOneListView(MergeTwoSubtitlesViewModel vm, private static StackPanel MakeFormatRow(MergeTwoSubtitlesViewModel vm) { - var label = EditorChrome.MakeDimLabel(Se.Language.Tools.MergeTwoSubtitles.OutputFormat, new Thickness(0)); + var label = EditorChrome.MakeDimLabel(Se.Language.General.OutputFormat, new Thickness(0)); var combo = UiUtil.MakeComboBox(vm.OutputFormats, vm, nameof(vm.SelectedOutputFormat)) .WithMinWidth(260) - .WithAccessibleName(Se.Language.Tools.MergeTwoSubtitles.OutputFormat); + .WithAccessibleName(Se.Language.General.OutputFormat); return new StackPanel { diff --git a/src/ui/Features/Tools/RemoveTextForHearingImpaired/InterjectionsWindow.cs b/src/ui/Features/Tools/RemoveTextForHearingImpaired/InterjectionsWindow.cs index 91c6a031754..1444122b0fa 100644 --- a/src/ui/Features/Tools/RemoveTextForHearingImpaired/InterjectionsWindow.cs +++ b/src/ui/Features/Tools/RemoveTextForHearingImpaired/InterjectionsWindow.cs @@ -18,7 +18,7 @@ public class InterjectionsWindow : Window public InterjectionsWindow(InterjectionsViewModel vm) { UiUtil.InitializeWindow(this, GetType().Name); - Title = Se.Language.Tools.RemoveTextForHearingImpaired.Interjections; + Title = Se.Language.General.Interjections; CanResize = false; Width = 700; Height = 600; @@ -28,7 +28,7 @@ public InterjectionsWindow(InterjectionsViewModel vm) vm.Window = this; DataContext = vm; - var textBoxInterjections = MakeListTextBox(nameof(vm.InterjectionsText), Se.Language.Tools.RemoveTextForHearingImpaired.Interjections); + var textBoxInterjections = MakeListTextBox(nameof(vm.InterjectionsText), Se.Language.General.Interjections); var textBoxSkipList = MakeListTextBox(nameof(vm.InterjectionsSkipStartText), Se.Language.Tools.RemoveTextForHearingImpaired.SkipIfStartWith); var buttonOk = UiUtil.MakeButtonOk(vm.OkCommand); @@ -43,7 +43,7 @@ public InterjectionsWindow(InterjectionsViewModel vm) new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) }, }, }; - sections.Add(MakeListSection(Se.Language.Tools.RemoveTextForHearingImpaired.Interjections, textBoxInterjections, false), 0, 0); + sections.Add(MakeListSection(Se.Language.General.Interjections, textBoxInterjections, false), 0, 0); sections.Add(MakeListSection(Se.Language.Tools.RemoveTextForHearingImpaired.SkipIfStartWith, textBoxSkipList, true), 0, 1); Content = EditorChrome.MakeRoot(sections, EditorChrome.MakeDockedButtonBar(null, buttonCancel, buttonOk)); diff --git a/src/ui/Features/Tools/RemoveTextForHearingImpaired/RemoveTextForHearingImpairedWindow.cs b/src/ui/Features/Tools/RemoveTextForHearingImpaired/RemoveTextForHearingImpairedWindow.cs index d842300f6b1..1d8c9aa1f75 100644 --- a/src/ui/Features/Tools/RemoveTextForHearingImpaired/RemoveTextForHearingImpairedWindow.cs +++ b/src/ui/Features/Tools/RemoveTextForHearingImpaired/RemoveTextForHearingImpairedWindow.cs @@ -187,7 +187,7 @@ private static Border MakeOtherView(RemoveTextForHearingImpairedViewModel vm) } }; - return EditorChrome.MakeEditorSection(Se.Language.Edit.ModifySelection.HearingImpairedOther, panel); + return EditorChrome.MakeEditorSection(Se.Language.General.Other, panel); } private static Border MakeInterjectionsView(RemoveTextForHearingImpairedViewModel vm) @@ -211,7 +211,7 @@ private static Border MakeInterjectionsView(RemoveTextForHearingImpairedViewMode } }; - return EditorChrome.MakeEditorSection(l.Interjections, panel, buttonEdit); + return EditorChrome.MakeEditorSection(Se.Language.General.Interjections, panel, buttonEdit); } /// diff --git a/src/ui/Features/Tools/SplitBreakLongLines/SplitBreakLongLinesWindow.cs b/src/ui/Features/Tools/SplitBreakLongLines/SplitBreakLongLinesWindow.cs index e3110c289f8..6d844ded6c4 100644 --- a/src/ui/Features/Tools/SplitBreakLongLines/SplitBreakLongLinesWindow.cs +++ b/src/ui/Features/Tools/SplitBreakLongLines/SplitBreakLongLinesWindow.cs @@ -68,7 +68,7 @@ private Border MakeControlsView(SplitBreakLongLinesViewModel vm) var numericUpDownSingleLineMaxLength = EditorChrome.AsField(UiUtil.MakeNumericUpDownInt(5, 1000, 10, 130, vm, nameof(vm.SingleLineMaxLength)), labelSingleLineMaxLength.TrimEnd(':'), 100, alignLeft: true, center: true); numericUpDownSingleLineMaxLength.ValueChanged += (s, e) => vm.SetChanged(); - var labelMaxNumberOfLines = Se.Language.Options.Settings.MaxLines; + var labelMaxNumberOfLines = Se.Language.General.MaxNumberOfLines; var numericUpDownMaxNumberOfLines = EditorChrome.AsField(UiUtil.MakeNumericUpDownInt(1, 10, 2, 130, vm, nameof(vm.MaxNumberOfLines)), labelMaxNumberOfLines.TrimEnd(':'), 100, alignLeft: true, center: true); numericUpDownMaxNumberOfLines.ValueChanged += (s, e) => vm.SetChanged(); diff --git a/src/ui/Features/Translate/CopyPasteTranslateBlockViewModel.cs b/src/ui/Features/Translate/CopyPasteTranslateBlockViewModel.cs index c4fdd0ce51d..10292c6e95c 100644 --- a/src/ui/Features/Translate/CopyPasteTranslateBlockViewModel.cs +++ b/src/ui/Features/Translate/CopyPasteTranslateBlockViewModel.cs @@ -47,7 +47,7 @@ private async Task CopyFromClipboard() if (TranslatedText == string.Empty) { - await MessageBox.Show(Window, Se.Language.General.Error, Se.Language.Translate.NoTextInClipboard, + await MessageBox.Show(Window, Se.Language.General.Error, Se.Language.General.NoTextInClipboard, MessageBoxButtons.OK, MessageBoxIcon.Error); return; diff --git a/src/ui/Features/Translate/LlamaCppAdvanced/LlamaCppAdvancedSettingsWindow.cs b/src/ui/Features/Translate/LlamaCppAdvanced/LlamaCppAdvancedSettingsWindow.cs index 29d14275713..17256342efd 100644 --- a/src/ui/Features/Translate/LlamaCppAdvanced/LlamaCppAdvancedSettingsWindow.cs +++ b/src/ui/Features/Translate/LlamaCppAdvanced/LlamaCppAdvancedSettingsWindow.cs @@ -165,8 +165,8 @@ private static StackPanel BuildSamplingSection(LlamaCppAdvancedSettingsViewModel RowSpacing = 10, }; - sampling.Add(Label(Se.Language.Translate.Temperature), 0, 0); - sampling.Add(Field(UiUtil.MakeNumericUpDownTwoDecimals(-1, 2, 110, vm, nameof(vm.Temperature)), Se.Language.Translate.Temperature), 0, 1); + sampling.Add(Label(Se.Language.General.Temperature), 0, 0); + sampling.Add(Field(UiUtil.MakeNumericUpDownTwoDecimals(-1, 2, 110, vm, nameof(vm.Temperature)), Se.Language.General.Temperature), 0, 1); sampling.Add(Label(Se.Language.Translate.TopP, new Thickness(16, 0, 0, 0)), 0, 2); sampling.Add(Field(UiUtil.MakeNumericUpDownTwoDecimals(-1, 1, 110, vm, nameof(vm.TopP)), Se.Language.Translate.TopP), 0, 3); diff --git a/src/ui/Features/Translate/LlamaCppDownloadHelper.cs b/src/ui/Features/Translate/LlamaCppDownloadHelper.cs index 57ad9fc410f..5adab15ea1f 100644 --- a/src/ui/Features/Translate/LlamaCppDownloadHelper.cs +++ b/src/ui/Features/Translate/LlamaCppDownloadHelper.cs @@ -304,7 +304,7 @@ await MessageBox.Show( } else if (!engineInstalled) { - message = Se.Language.Translate.LlamaCppDownloadEnginePrompt; + message = Se.Language.General.LlamaCppDownloadEnginePrompt; } else { diff --git a/src/ui/Features/Video/BackgroundMusic/BackgroundMusicWindow.cs b/src/ui/Features/Video/BackgroundMusic/BackgroundMusicWindow.cs index 732553308fc..ef44a4e35bb 100644 --- a/src/ui/Features/Video/BackgroundMusic/BackgroundMusicWindow.cs +++ b/src/ui/Features/Video/BackgroundMusic/BackgroundMusicWindow.cs @@ -40,7 +40,7 @@ public BackgroundMusicWindow(BackgroundMusicViewModel vm) comboBoxPreset.Width = double.NaN; comboBoxPreset.HorizontalAlignment = HorizontalAlignment.Stretch; comboBoxPreset.WithBindEnabled(nameof(vm.IsNotBusy)); - AutomationProperties.SetName(comboBoxPreset, l.Preset); + AutomationProperties.SetName(comboBoxPreset, Se.Language.General.Preset); var settings = new Grid { @@ -101,7 +101,7 @@ private static Border MakeMusicSection(BackgroundMusicViewModel vm, ComboBox com }; textBoxPrompt.Classes.Add("text"); textBoxPrompt.VerticalContentAlignment = VerticalAlignment.Top; - AutomationProperties.SetName(textBoxPrompt, l.Prompt); + AutomationProperties.SetName(textBoxPrompt, Se.Language.General.Prompt); var numericBpm = Field(nameof(vm.Bpm), 40, 200, l.TempoBpm); var numericGenerateSeconds = Field(nameof(vm.GenerateSeconds), 20, 240, l.GenerateSeconds); @@ -125,7 +125,7 @@ private static Border MakeMusicSection(BackgroundMusicViewModel vm, ComboBox com [!TextBlock.TextProperty] = new Binding(nameof(vm.OutputLengthText)), }; - var labelPrompt = EditorChrome.MakeDimLabel(l.Prompt, new Thickness(0, 6, 0, 0)); + var labelPrompt = EditorChrome.MakeDimLabel(Se.Language.General.Prompt, new Thickness(0, 6, 0, 0)); labelPrompt.VerticalAlignment = VerticalAlignment.Top; var promptRow = new Grid { @@ -136,7 +136,7 @@ private static Border MakeMusicSection(BackgroundMusicViewModel vm, ComboBox com promptRow.Add(textBoxPrompt, 0, 1); var rows = new StackPanel { Spacing = 8 }; - rows.Children.Add(EditorChrome.MakeLabeledRow(l.Preset, comboBoxPreset, LabelWidth, spacing: 6)); + rows.Children.Add(EditorChrome.MakeLabeledRow(Se.Language.General.Preset, comboBoxPreset, LabelWidth, spacing: 6)); rows.Children.Add(promptRow); rows.Children.Add(EditorChrome.MakeLabeledRow(l.TempoBpm, numericBpm, LabelWidth, spacing: 6)); rows.Children.Add(EditorChrome.MakeLabeledRow(l.GenerateSeconds, numericGenerateSeconds, LabelWidth, spacing: 6)); @@ -166,7 +166,7 @@ private static Border MakeMixSection(BackgroundMusicViewModel vm) rows.Children.Add(rowRemoveAudio); rows.Children.Add(rowOriginalVolume); - return EditorChrome.MakeEditorSection(Se.Language.Video.BurnIn.Audio, rows, first: true); + return EditorChrome.MakeEditorSection(Se.Language.General.Audio, rows, first: true); } /// @@ -229,7 +229,7 @@ private static Border MakeResultSection(BackgroundMusicViewModel vm) rows.Children.Add(status); rows.Children.Add(actions); - return EditorChrome.MakeEditorSection(Se.Language.Video.CutVideoOutput, rows); + return EditorChrome.MakeEditorSection(Se.Language.General.Output, rows); } private static Button MakeAction(string icon, string text, System.Windows.Input.ICommand command) diff --git a/src/ui/Features/Video/BurnIn/BurnInViewModel.cs b/src/ui/Features/Video/BurnIn/BurnInViewModel.cs index d5302c6b139..f1f8796d688 100644 --- a/src/ui/Features/Video/BurnIn/BurnInViewModel.cs +++ b/src/ui/Features/Video/BurnIn/BurnInViewModel.cs @@ -2314,7 +2314,7 @@ private void FillTune(string videoCodec) private void FillPreset(string videoCodec) { - VideoPresetText = Se.Language.Video.BurnIn.Preset; + VideoPresetText = Se.Language.General.Preset; var previousPreset = SelectedVideoPreset; SelectedVideoPreset = null; @@ -2405,7 +2405,7 @@ private void FillPreset(string videoCodec) defaultItem = string.Empty; - VideoPresetText = Se.Language.Video.BurnIn.Preset; + VideoPresetText = Se.Language.General.Preset; } VideoPresets.Clear(); diff --git a/src/ui/Features/Video/BurnIn/BurnInWindow.cs b/src/ui/Features/Video/BurnIn/BurnInWindow.cs index be920c1476d..5deafd9f3f0 100644 --- a/src/ui/Features/Video/BurnIn/BurnInWindow.cs +++ b/src/ui/Features/Video/BurnIn/BurnInWindow.cs @@ -640,7 +640,7 @@ private static Border MakeVideoSection(BurnInViewModel vm) var encoder = Combo(vm.VideoEncodings, vm, nameof(vm.SelectedVideoEncoding), Se.Language.General.Encoding); encoder.SelectionChanged += vm.VideoEncodingChanged; - var preset = Combo(vm.VideoPresets, vm, nameof(vm.SelectedVideoPreset), Se.Language.Video.BurnIn.Preset); + var preset = Combo(vm.VideoPresets, vm, nameof(vm.SelectedVideoPreset), Se.Language.General.Preset); // Only the NVIDIA encoders have a tuning mode, so the row is hidden for the rest var tune = Row(Se.Language.Video.BurnIn.Tune, Combo(vm.VideoTunes, vm, nameof(vm.SelectedVideoTune), Se.Language.Video.BurnIn.Tune)); @@ -680,7 +680,7 @@ private static Border MakeAudioSection(BurnInViewModel vm) rows.Children.Add(Row(Se.Language.Video.BurnIn.SampleRate, Combo(vm.AudioSampleRates, vm, nameof(vm.SelectedAudioSampleRate), Se.Language.Video.BurnIn.SampleRate))); rows.Children.Add(Row(Se.Language.Video.BurnIn.BitRate, Combo(vm.AudioBitRates, vm, nameof(vm.SelectedAudioBitRate), Se.Language.Video.BurnIn.BitRate))); rows.Children.Add(SwitchRow(Switch(nameof(vm.AudioIsStereo), Se.Language.General.Stereo), Se.Language.General.Stereo)); - return EditorChrome.MakeEditorSection(Se.Language.Video.BurnIn.Audio, rows, first: true); + return EditorChrome.MakeEditorSection(Se.Language.General.Audio, rows, first: true); } private static Border MakeTargetSizeSection(BurnInViewModel vm) diff --git a/src/ui/Features/Video/Chapters/ChaptersWindow.cs b/src/ui/Features/Video/Chapters/ChaptersWindow.cs index 2568bcb6853..380341ae8bd 100644 --- a/src/ui/Features/Video/Chapters/ChaptersWindow.cs +++ b/src/ui/Features/Video/Chapters/ChaptersWindow.cs @@ -24,9 +24,8 @@ public class ChaptersWindow : Window public ChaptersWindow(ChaptersViewModel vm) { - var language = Se.Language.Video.Chapters; UiUtil.InitializeWindow(this, GetType().Name); - Title = language.Title; + Title = Se.Language.General.Chapters; CanResize = true; Width = 980; Height = 660; @@ -305,7 +304,7 @@ private static Grid MakeChapterListPanel(ChaptersViewModel vm) actions.Children.Add(buttonDelete); actions.Children.Add(buttonClear); - var title = EditorChrome.MakeSectionHeader(language.Chapters); + var title = EditorChrome.MakeSectionHeader(Se.Language.General.Chapters); var badge = MakeCountBadge(new Binding(nameof(vm.ChapterCountDisplay))); var header = new DockPanel { Margin = new Thickness(0, 0, 0, 8) }; DockPanel.SetDock(actions, Dock.Right); diff --git a/src/ui/Features/Video/CutVideo/CutVideoWindow.cs b/src/ui/Features/Video/CutVideo/CutVideoWindow.cs index ed9e4a66f12..57167e97c8d 100644 --- a/src/ui/Features/Video/CutVideo/CutVideoWindow.cs +++ b/src/ui/Features/Video/CutVideo/CutVideoWindow.cs @@ -269,7 +269,7 @@ private static Border MakeOutputSection(CutVideoViewModel vm) rows.Children.Add(Row(Se.Language.General.Format, extension)); rows.Children.Add(encoding); rows.Children.Add(cutSubtitle); - return EditorChrome.MakeEditorSection(Se.Language.Video.CutVideoOutput, rows, first: true); + return EditorChrome.MakeEditorSection(Se.Language.General.Output, rows, first: true); } private static Border MakeTransitionsSection(CutVideoViewModel vm) diff --git a/src/ui/Features/Video/EmbeddedSubtitlesEdit/EmbeddedSubtitlesEditMp4ViewModel.cs b/src/ui/Features/Video/EmbeddedSubtitlesEdit/EmbeddedSubtitlesEditMp4ViewModel.cs index 654d8af3575..23791e29496 100644 --- a/src/ui/Features/Video/EmbeddedSubtitlesEdit/EmbeddedSubtitlesEditMp4ViewModel.cs +++ b/src/ui/Features/Video/EmbeddedSubtitlesEdit/EmbeddedSubtitlesEditMp4ViewModel.cs @@ -342,7 +342,7 @@ private async Task Add() { await MessageBox.Show( Window, - Se.Language.Video.EmbeddedTrackNoSubtitlesFoundTitle, + Se.Language.General.NoSubtitlesFoundTitle, Se.Language.Video.EmbeddedTrackNoSubtitlesFoundMessage, MessageBoxButtons.OK, MessageBoxIcon.Error); diff --git a/src/ui/Features/Video/ShotChanges/ShotChangeListViewModel.cs b/src/ui/Features/Video/ShotChanges/ShotChangeListViewModel.cs index 4250914842b..7e9fb07eeb0 100644 --- a/src/ui/Features/Video/ShotChanges/ShotChangeListViewModel.cs +++ b/src/ui/Features/Video/ShotChanges/ShotChangeListViewModel.cs @@ -84,7 +84,7 @@ private async Task Export() Window, new[] { - (Se.Language.Video.ShotChanges.ShotChanges, ".shotchanges"), + (Se.Language.General.ShotChanges, ".shotchanges"), (Se.Language.General.TextFiles, ".txt"), }, suggestedFileName, diff --git a/src/ui/Features/Video/ShotChanges/ShotChangesWindow.cs b/src/ui/Features/Video/ShotChanges/ShotChangesWindow.cs index 7b1fc8f5984..fc234d9e464 100644 --- a/src/ui/Features/Video/ShotChanges/ShotChangesWindow.cs +++ b/src/ui/Features/Video/ShotChanges/ShotChangesWindow.cs @@ -166,7 +166,7 @@ private static Grid MakeGenerateView(ShotChangesViewModel vm) new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, }, }; - grid.Add(EditorChrome.MakeListPane(Se.Language.Video.ShotChanges.ShotChanges, tableView, margin: new Thickness(6, 12, 6, 12)), 0); + grid.Add(EditorChrome.MakeListPane(Se.Language.General.ShotChanges, tableView, margin: new Thickness(6, 12, 6, 12)), 0); var settings = EditorChrome.MakeEditorSection(Se.Language.General.Settings, panelSensitivity); settings.Padding = new Thickness(6, 12, 6, 14); grid.Add(settings, 1); @@ -249,7 +249,7 @@ private static Grid MakeImportView(ShotChangesViewModel vm) new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, }, }; - grid.Add(EditorChrome.MakeListPane(Se.Language.Video.ShotChanges.ShotChanges, EditorChrome.MakeListFrame(textBoxImport), buttonImport, new Thickness(6, 12, 6, 12)), 0); + grid.Add(EditorChrome.MakeListPane(Se.Language.General.ShotChanges, EditorChrome.MakeListFrame(textBoxImport), buttonImport, new Thickness(6, 12, 6, 12)), 0); var settings = EditorChrome.MakeEditorSection(Se.Language.General.Settings, panelTimeCodeFormat); settings.Padding = new Thickness(6, 12, 6, 14); grid.Add(settings, 1); diff --git a/src/ui/Features/Video/SpeechToText/SpeechToTextQualityReportViewModel.cs b/src/ui/Features/Video/SpeechToText/SpeechToTextQualityReportViewModel.cs index 8c1e6d12860..94efa2dcf83 100644 --- a/src/ui/Features/Video/SpeechToText/SpeechToTextQualityReportViewModel.cs +++ b/src/ui/Features/Video/SpeechToText/SpeechToTextQualityReportViewModel.cs @@ -113,9 +113,9 @@ public static string GetLabel(SpeechToTextQualityIssueType type) var l = Se.Language.Video.AudioToText; return type switch { - SpeechToTextQualityIssueType.TooShort => l.QualityReportTooShort, - SpeechToTextQualityIssueType.TooLong => l.QualityReportTooLong, - SpeechToTextQualityIssueType.Overlap => l.QualityReportOverlap, + SpeechToTextQualityIssueType.TooShort => Se.Language.General.TooShort, + SpeechToTextQualityIssueType.TooLong => Se.Language.General.TooLong, + SpeechToTextQualityIssueType.Overlap => Se.Language.General.Overlapping, SpeechToTextQualityIssueType.NonSpeech => l.QualityReportNonSpeech, SpeechToTextQualityIssueType.Repeated => l.QualityReportRepeated, _ => string.Empty, @@ -340,7 +340,7 @@ private ReportExportData MakeExportData() Summary = Summary, FileName = _fileName, CategoryHeader = l.QualityReportIssue, - DetailHeader = l.QualityReportDetail, + DetailHeader = Se.Language.General.Detail, AllLabel = l.QualityReportAll, AllColor = AllColor, Chips = chips, diff --git a/src/ui/Features/Video/SpeechToText/SpeechToTextQualityReportWindow.cs b/src/ui/Features/Video/SpeechToText/SpeechToTextQualityReportWindow.cs index e878f077e39..ded6f72595a 100644 --- a/src/ui/Features/Video/SpeechToText/SpeechToTextQualityReportWindow.cs +++ b/src/ui/Features/Video/SpeechToText/SpeechToTextQualityReportWindow.cs @@ -123,7 +123,7 @@ public SpeechToTextQualityReportWindow(SpeechToTextQualityReportViewModel vm) }); table.Columns.Add(new SeTableViewColumn { - Header = l.QualityReportDetail, + Header = Se.Language.General.Detail, CellTheme = UiUtil.TableViewCellTheme, HeaderTheme = UiUtil.TableViewColumnHeaderTheme, Binding = new Binding(nameof(QualityReportDisplayItem.Detail)), diff --git a/src/ui/Features/Video/SpeechToText/SpeechToTextWindow.cs b/src/ui/Features/Video/SpeechToText/SpeechToTextWindow.cs index 227f979a5f2..ddf06a69912 100644 --- a/src/ui/Features/Video/SpeechToText/SpeechToTextWindow.cs +++ b/src/ui/Features/Video/SpeechToText/SpeechToTextWindow.cs @@ -422,7 +422,7 @@ private static Control[] MakeOpenAiCompatibleSttRows(SpeechToTextViewModel vm) ColumnDefinitions = { new ColumnDefinition(GridLength.Auto), new ColumnDefinition(GridLength.Auto) }, }; numbers.Add(OnlineRow(Se.Language.General.OpenAiCompatibleSttTimeout, OnlineNumber(vm, nameof(vm.OpenAiCompatibleSttTimeoutSeconds), 10, 3600, "F0")), 0, 0); - numbers.Add(OnlineRow(Se.Language.General.OpenAiCompatibleSttTemperature, OnlineNumber(vm, nameof(vm.OpenAiCompatibleSttTemperature), 0, 1, "F2", 0.1m)), 0, 1); + numbers.Add(OnlineRow(Se.Language.General.Temperature, OnlineNumber(vm, nameof(vm.OpenAiCompatibleSttTemperature), 0, 1, "F2", 0.1m)), 0, 1); return new Control[] { @@ -431,7 +431,7 @@ private static Control[] MakeOpenAiCompatibleSttRows(SpeechToTextViewModel vm) OnlineRow(Se.Language.General.Model, OnlineText(vm, nameof(vm.OpenAiCompatibleSttModel))), OnlineRow(Se.Language.General.OpenAiCompatibleSttLanguage, OnlineText(vm, nameof(vm.OpenAiCompatibleSttLanguage))), numbers, - OnlineRow(Se.Language.General.OpenAiCompatibleSttPrompt, OnlineText(vm, nameof(vm.OpenAiCompatibleSttPrompt))), + OnlineRow(Se.Language.General.Prompt, OnlineText(vm, nameof(vm.OpenAiCompatibleSttPrompt))), OnlineRow(Se.Language.General.OpenAiCompatibleSttExtraHeaders, textExtraHeaders), OnlineRow(Se.Language.General.OpenAiCompatibleSttAudioFormat, comboAudioFormat), OnlineSwitch(vm, nameof(vm.OpenAiCompatibleSttStream), Se.Language.General.OpenAiCompatibleSttStream, Se.Language.General.OpenAiCompatibleSttStreamHint), @@ -446,7 +446,7 @@ private static Control[] MakeOpenRouterSttRows(SpeechToTextViewModel vm) ColumnDefinitions = { new ColumnDefinition(GridLength.Auto), new ColumnDefinition(GridLength.Auto) }, }; numbers.Add(OnlineRow(Se.Language.General.OpenAiCompatibleSttTimeout, OnlineNumber(vm, nameof(vm.OpenRouterSttTimeoutSeconds), 10, 3600, "F0")), 0, 0); - numbers.Add(OnlineRow(Se.Language.General.OpenAiCompatibleSttTemperature, OnlineNumber(vm, nameof(vm.OpenRouterSttTemperature), 0, 1, "F2", 0.1m)), 0, 1); + numbers.Add(OnlineRow(Se.Language.General.Temperature, OnlineNumber(vm, nameof(vm.OpenRouterSttTemperature), 0, 1, "F2", 0.1m)), 0, 1); return new Control[] { @@ -454,7 +454,7 @@ private static Control[] MakeOpenRouterSttRows(SpeechToTextViewModel vm) OnlineRow(Se.Language.General.Model, OnlineText(vm, nameof(vm.OpenRouterSttModel))), OnlineRow(Se.Language.General.OpenAiCompatibleSttLanguage, OnlineText(vm, nameof(vm.OpenRouterSttLanguage))), numbers, - OnlineRow(Se.Language.General.OpenAiCompatibleSttPrompt, OnlineText(vm, nameof(vm.OpenRouterSttPrompt))), + OnlineRow(Se.Language.General.Prompt, OnlineText(vm, nameof(vm.OpenRouterSttPrompt))), }; } diff --git a/src/ui/Features/Video/TextToSpeech/AdvancedTtsSettings/AdvancedTtsSettingsWindow.cs b/src/ui/Features/Video/TextToSpeech/AdvancedTtsSettings/AdvancedTtsSettingsWindow.cs index 41c3697a779..488c90333fb 100644 --- a/src/ui/Features/Video/TextToSpeech/AdvancedTtsSettings/AdvancedTtsSettingsWindow.cs +++ b/src/ui/Features/Video/TextToSpeech/AdvancedTtsSettings/AdvancedTtsSettingsWindow.cs @@ -76,7 +76,7 @@ public AdvancedTtsSettingsWindow(AdvancedTtsSettingsViewModel vm) MakeRow(switchRemoveSpeech, l.RemoveOriginalSpeechDescription, describes: toggleRemoveSpeech), - MakeRow(MakeLabel(l.SpeechRemovalModel), l.SpeechRemovalModelDescription, speechRemovalModel), + MakeRow(MakeLabel(Se.Language.General.Model), l.SpeechRemovalModelDescription, speechRemovalModel), MakeRow(switchVad, l.VadSilenceCompressionDescription, MakeField(l.MaxSilenceMs, @@ -89,7 +89,7 @@ public AdvancedTtsSettingsWindow(AdvancedTtsSettingsViewModel vm) } }, first: true); - var groupOutput = EditorChrome.MakeEditorSection(l.AdvancedTtsOutput, new StackPanel + var groupOutput = EditorChrome.MakeEditorSection(Se.Language.General.Output, new StackPanel { Spacing = 8, Children = diff --git a/src/ui/Features/Video/TextToSpeech/AutoCast/AutoCastSpeakersWindow.cs b/src/ui/Features/Video/TextToSpeech/AutoCast/AutoCastSpeakersWindow.cs index f3c51c718d9..21c3a8f3054 100644 --- a/src/ui/Features/Video/TextToSpeech/AutoCast/AutoCastSpeakersWindow.cs +++ b/src/ui/Features/Video/TextToSpeech/AutoCast/AutoCastSpeakersWindow.cs @@ -167,7 +167,7 @@ private static Border BuildTable(AutoCastSpeakersViewModel vm) }); tableView.Columns.Add(new SeTableViewColumn { - Header = Se.Language.Video.TextToSpeech.AutoCastAudio, + Header = Se.Language.General.Audio, Binding = new Binding(nameof(AutoCastSpeakerRow.TotalDurationDisplay)), Width = new GridLength(80), CellTheme = UiUtil.TableViewCellTheme, diff --git a/src/ui/Features/Video/TextToSpeech/OpenAiCompatibleSettings/OpenAiCompatibleSettingsWindow.cs b/src/ui/Features/Video/TextToSpeech/OpenAiCompatibleSettings/OpenAiCompatibleSettingsWindow.cs index ba18c706e87..1a80e3dd2eb 100644 --- a/src/ui/Features/Video/TextToSpeech/OpenAiCompatibleSettings/OpenAiCompatibleSettingsWindow.cs +++ b/src/ui/Features/Video/TextToSpeech/OpenAiCompatibleSettings/OpenAiCompatibleSettingsWindow.cs @@ -55,7 +55,7 @@ public OpenAiCompatibleSettingsWindow(OpenAiCompatibleSettingsViewModel vm) var comboBoxResponseFormat = UiUtil.MakeComboBox(vm.ResponseFormats, vm, nameof(vm.SelectedResponseFormat)); comboBoxResponseFormat.MinWidth = 120; comboBoxResponseFormat.HorizontalAlignment = HorizontalAlignment.Left; - AutomationProperties.SetName(comboBoxResponseFormat, l.OutputFormat); + AutomationProperties.SetName(comboBoxResponseFormat, Se.Language.General.OutputFormat); var server = new StackPanel { Spacing = 8 }; server.Children.Add(Row(l.CustomServerUrl, textBoxUrl, l.CustomServerUrlHint)); @@ -65,7 +65,7 @@ public OpenAiCompatibleSettingsWindow(OpenAiCompatibleSettingsViewModel vm) var voice = new StackPanel { Spacing = 8 }; voice.Children.Add(Row(l.Instructions, textBoxInstructions, l.InstructionsHint)); voice.Children.Add(Row(Se.Language.General.Speed, numericSpeed, l.OpenAiSpeedHint)); - voice.Children.Add(Row(l.OutputFormat, comboBoxResponseFormat, l.OutputFormatHint)); + voice.Children.Add(Row(Se.Language.General.OutputFormat, comboBoxResponseFormat, l.OutputFormatHint)); var headerActions = new StackPanel { Orientation = Orientation.Horizontal, Spacing = 2 }; headerActions.Children.Add(EditorChrome.CreateToolButton(IconNames.Web, Se.Language.General.MoreInfo, vm.ShowMoreOnWebCommand, 26)); diff --git a/src/ui/Features/Video/TextToSpeech/ReviewSpeech/ReviewSpeechWindow.cs b/src/ui/Features/Video/TextToSpeech/ReviewSpeech/ReviewSpeechWindow.cs index f8d8ee179d9..8c4b8c9b4b1 100644 --- a/src/ui/Features/Video/TextToSpeech/ReviewSpeech/ReviewSpeechWindow.cs +++ b/src/ui/Features/Video/TextToSpeech/ReviewSpeech/ReviewSpeechWindow.cs @@ -176,7 +176,7 @@ private static Grid MakeLineGrid(ReviewSpeechViewModel vm) buttonHistory.CommandParameter = item; buttonHistory.Bind(Button.OpacityProperty, new Binding(nameof(ReviewRow.HistoryButtonOpacity))); - var buttonPlay = EditorChrome.CreateToolButton("fa-solid fa-play", Se.Language.Video.TextToSpeech.PlayLine, vm.PlayRowCommand, 26) + var buttonPlay = EditorChrome.CreateToolButton("fa-solid fa-play", Se.Language.General.PlayLine, vm.PlayRowCommand, 26) .WithBindIsVisible(nameof(item.IsPlaying), InverseBooleanConverter.Instance) .WithBindEnabled(nameof(item.IsPlayingEnabled)); buttonPlay.CommandParameter = item; @@ -657,7 +657,7 @@ private static Border MakeWaveform(ReviewSpeechViewModel vm) // Context menu: the row actions from the grid plus the two timing fixes that only make // sense here. The target is the row under the pointer (selected on open), so the items // take it as CommandParameter rather than relying on SelectedLine. - var menuPlay = new MenuItem { Header = Se.Language.Video.TextToSpeech.PlayLine, Command = vm.PlayRowCommand }; + var menuPlay = new MenuItem { Header = Se.Language.General.PlayLine, Command = vm.PlayRowCommand }; var menuRegenerate = new MenuItem { Header = Se.Language.Video.TextToSpeech.RegenerateAudio, Command = vm.RegenerateAudioCommand }; var menuHistory = new MenuItem { Header = Se.Language.General.ShowHistory, Command = vm.ShowHistoryCommand }; var menuFit = new MenuItem { Header = Se.Language.Video.TextToSpeech.FitDurationToGeneratedAudio, Command = vm.FitDurationToAudioCommand }; diff --git a/src/ui/Features/Video/TextToSpeech/TextToSpeechWindow.cs b/src/ui/Features/Video/TextToSpeech/TextToSpeechWindow.cs index aa699a12e07..eb7fd910a09 100644 --- a/src/ui/Features/Video/TextToSpeech/TextToSpeechWindow.cs +++ b/src/ui/Features/Video/TextToSpeech/TextToSpeechWindow.cs @@ -565,7 +565,7 @@ private static Border MakeSettingsControls(TextToSpeechViewModel vm) rows.Children.Add(panelAddBackgroundMusicWithHint); rows.Children.Add(buttonAdvanced); - var section = EditorChrome.MakeEditorSection(language.Output, rows, first: true); + var section = EditorChrome.MakeEditorSection(Se.Language.General.Output, rows, first: true); section.Width = 330; return section; } diff --git a/src/ui/Features/Video/TextToSpeech/VoiceManager/VoicePacks/DownloadVoicePacksViewModel.cs b/src/ui/Features/Video/TextToSpeech/VoiceManager/VoicePacks/DownloadVoicePacksViewModel.cs index 6a4a1038e99..599dbce6bef 100644 --- a/src/ui/Features/Video/TextToSpeech/VoiceManager/VoicePacks/DownloadVoicePacksViewModel.cs +++ b/src/ui/Features/Video/TextToSpeech/VoiceManager/VoicePacks/DownloadVoicePacksViewModel.cs @@ -131,7 +131,7 @@ private async Task DownloadAndInstall() { token.ThrowIfCancellationRequested(); ProgressValue = 0; - ProgressText = string.Format(Se.Language.Video.TextToSpeech.DownloadingXDotDotDot, item.Name); + ProgressText = string.Format(Se.Language.General.DownloadingXDotDotDot, item.Name); item.Status = Se.Language.General.Download + "..."; using var stream = new MemoryStream(); diff --git a/src/ui/Features/Video/VideoOcr/VideoOcrViewModel.cs b/src/ui/Features/Video/VideoOcr/VideoOcrViewModel.cs index 1f5774cc348..a2664913521 100644 --- a/src/ui/Features/Video/VideoOcr/VideoOcrViewModel.cs +++ b/src/ui/Features/Video/VideoOcr/VideoOcrViewModel.cs @@ -540,7 +540,7 @@ private async Task EnsureLlamaCppReady() } else if (!engineInstalled) { - message = Se.Language.Ocr.LlamaCppDownloadEnginePrompt; + message = Se.Language.General.LlamaCppDownloadEnginePrompt; } else { @@ -925,7 +925,7 @@ await MessageBox.Show( { await MessageBox.Show( Window!, - Se.Language.Video.VideoOcr.NoLinesFoundTitle, + Se.Language.General.NoSubtitlesFoundTitle, Se.Language.Video.VideoOcr.NoLinesFoundMessage, MessageBoxButtons.OK, MessageBoxIcon.Information); @@ -1078,7 +1078,7 @@ void ReportOcrProgress() var current = Interlocked.Increment(ref done); Dispatcher.UIThread.Post(() => { - ProgressText = string.Format(Se.Language.Video.VideoOcr.RunningOcrXY, current, ocrGroups.Count); + ProgressText = string.Format(Se.Language.General.RunningOcrXY, current, ocrGroups.Count); ProgressValue = current * 100.0 / ocrGroups.Count; }); } @@ -1107,7 +1107,7 @@ void AddPreviewLine(VideoOcrFrameGroup group) Dispatcher.UIThread.Post(() => { ProgressValue = 0; - ProgressText = string.Format(Se.Language.Video.VideoOcr.RunningOcrXY, 0, ocrGroups.Count); + ProgressText = string.Format(Se.Language.General.RunningOcrXY, 0, ocrGroups.Count); }); _ocrStoppedEarlyError = string.Empty; diff --git a/src/ui/Features/Video/VideoOcr/VideoOcrWindow.cs b/src/ui/Features/Video/VideoOcr/VideoOcrWindow.cs index 62a7e3cbbde..b5be20dd526 100644 --- a/src/ui/Features/Video/VideoOcr/VideoOcrWindow.cs +++ b/src/ui/Features/Video/VideoOcr/VideoOcrWindow.cs @@ -402,7 +402,7 @@ private Border MakeSettingsView(VideoOcrViewModel vm) NumberField(vm, nameof(vm.MinDurationMs), 0, 10_000, 250), Se.Language.Video.VideoOcr.MinDurationHint)); postRows.Children.Add(EditorChrome.MakeSwitchRow(nameof(vm.AddAssaPositionTag), Se.Language.Video.VideoOcr.AddAssaPositionTag)); - postRows.Children.Add(EditorChrome.MakeSwitchRow(nameof(vm.DoFixOcrErrors), Se.Language.Video.VideoOcr.FixOcrErrors)); + postRows.Children.Add(EditorChrome.MakeSwitchRow(nameof(vm.DoFixOcrErrors), Se.Language.General.FixOcrErrors)); var buttonDownloadDictionary = EditorChrome.CreateToolButton("fa-solid fa-ellipsis", Se.Language.Video.VideoOcr.DownloadDictionaryHint, vm.DownloadDictionaryCommand, 28) .WithBindEnabled(nameof(vm.DoFixOcrErrors)); diff --git a/src/ui/Logic/Config/Language/Assa/LanguageAssa.cs b/src/ui/Logic/Config/Language/Assa/LanguageAssa.cs index b51824e139a..fbe36629943 100644 --- a/src/ui/Logic/Config/Language/Assa/LanguageAssa.cs +++ b/src/ui/Logic/Config/Language/Assa/LanguageAssa.cs @@ -41,8 +41,6 @@ public class LanguageAssa public string DrawImportSvg { get; set; } public string DrawRotateClockwise { get; set; } public string DrawConvertToCurve { get; set; } - public string DrawBackground { get; set; } - public string DrawPosition { get; set; } public string DrawZoom { get; set; } public string DrawShapeTool { get; set; } public string DrawColorPickerTool { get; set; } @@ -126,7 +124,6 @@ public class LanguageAssa public string DrawAnimation { get; set; } public string DrawAnimationIn { get; set; } public string DrawAnimationOut { get; set; } - public string DrawAnimationNone { get; set; } public string DrawAnimationFade { get; set; } public string DrawAnimationPop { get; set; } public string DrawAnimationZoom { get; set; } @@ -205,8 +202,6 @@ public class LanguageAssa public string DrawShapeCross { get; set; } public string DrawShapeSmiley { get; set; } public string DrawShapeSadSmiley { get; set; } - public string DrawSize { get; set; } - public string DrawLayer { get; set; } public string DrawBackgroundVideoFrame { get; set; } public string DrawBackgroundVideoFrameAt { get; set; } public string DrawBackgroundImage { get; set; } @@ -214,7 +209,6 @@ public class LanguageAssa public string DrawBackgroundStretch { get; set; } public string DrawBackgroundOpacity { get; set; } public string DrawBackgroundVideoFrameFailed { get; set; } - public string DrawImages { get; set; } public string DrawConvertToLine { get; set; } public string DrawConvertShapeToCurves { get; set; } public string DrawConvertShapeToLines { get; set; } @@ -224,9 +218,7 @@ public class LanguageAssa public string DrawTextTool { get; set; } public string DrawDefaultText { get; set; } public string DrawShrinkToFit { get; set; } - public string DrawTextColor { get; set; } public string DrawTextBox { get; set; } - public string DrawDiscardChangesTitle { get; set; } public string DrawDiscardChanges { get; set; } public string DrawColorPickerToolName { get; set; } public string DrawOpenDrawing { get; set; } @@ -250,7 +242,6 @@ public class LanguageAssa public string ProgressBarForeColor { get; set; } public string ProgressBarSquareCorners { get; set; } public string ProgressBarRoundedCorners { get; set; } - public string ProgressBarChapters { get; set; } public string ProgressBarSplitterWidth { get; set; } public string ProgressBarSplitterHeight { get; set; } public string ProgressBarXAdjustment { get; set; } @@ -281,7 +272,6 @@ public class LanguageAssa public string FontCollectorUsedIn { get; set; } public string FontCollectorFontFiles { get; set; } public string FontCollectorStyleX { get; set; } - public string FontCollectorInlineLineX { get; set; } public string FontCollectorFound { get; set; } public string FontCollectorNotFound { get; set; } public string FontCollectorEmbedded { get; set; } @@ -357,12 +347,9 @@ public class LanguageAssa public string StylesSummary { get; set; } public string StylesTransform { get; set; } public string StylesEditing { get; set; } - public string StylesPreviewSampleText { get; set; } public string StylesPreviewSampleTextHint { get; set; } public string StylesScale { get; set; } - public string StylesPreviewDark { get; set; } public string StylesPreviewChecker { get; set; } - public string StylesPreviewLight { get; set; } public string AllCategories { get; set; } public string NewCategory { get; set; } public string RenameCategory { get; set; } @@ -428,7 +415,6 @@ public class LanguageAssa public string SetPositionTopCenter { get; set; } public string SetPositionTopRight { get; set; } public string SetPositionMiddleLeft { get; set; } - public string SetPositionCenter { get; set; } public string SetPositionMiddleRight { get; set; } public string SetPositionBottomLeft { get; set; } public string SetPositionBottomCenter { get; set; } @@ -550,7 +536,6 @@ public class LanguageAssa public string AdvancedEffectSingleWordPopDescription { get; set; } public string AdvancedEffectBoldCaptions { get; set; } public string AdvancedEffectBoldCaptionsDescription { get; set; } - public string AdvancedEffectBoxColor { get; set; } public string AdvancedEffectHighlightColor { get; set; } public string AdvancedEffectUpperCase { get; set; } public string AdvancedEffectTextSizePercent { get; set; } @@ -605,8 +590,6 @@ public LanguageAssa() DrawImportSvg = "Import SVG image..."; DrawRotateClockwise = "Rotate 90° clockwise"; DrawConvertToCurve = "Convert segment to curve"; - DrawBackground = "Background"; - DrawPosition = "Position"; DrawZoom = "Zoom"; DrawShapeTool = "Shape tool"; DrawColorPickerTool = "Pick color from a shape or the background"; @@ -690,7 +673,6 @@ public LanguageAssa() DrawAnimation = "Animation"; DrawAnimationIn = "In"; DrawAnimationOut = "Out"; - DrawAnimationNone = "None"; DrawAnimationFade = "Fade"; DrawAnimationPop = "Pop"; DrawAnimationZoom = "Zoom"; @@ -769,8 +751,6 @@ public LanguageAssa() DrawShapeCross = "Cross"; DrawShapeSmiley = "Smiley"; DrawShapeSadSmiley = "Sad smiley"; - DrawSize = "Size"; - DrawLayer = "Layer"; DrawBackgroundVideoFrame = "Video frame (current position)"; DrawBackgroundVideoFrameAt = "Video frame at..."; DrawBackgroundImage = "Image file..."; @@ -778,7 +758,6 @@ public LanguageAssa() DrawBackgroundStretch = "Stretch image to canvas"; DrawBackgroundOpacity = "Background opacity"; DrawBackgroundVideoFrameFailed = "Could not get a video frame at {0}."; - DrawImages = "Images"; DrawConvertToLine = "Convert segment to line"; DrawConvertShapeToCurves = "Convert all lines to curves"; DrawConvertShapeToLines = "Convert all curves to lines"; @@ -788,9 +767,7 @@ public LanguageAssa() DrawTextTool = "Text tool"; DrawDefaultText = "Text"; DrawShrinkToFit = "Shrink to fit"; - DrawTextColor = "Text color"; DrawTextBox = "Text box"; - DrawDiscardChangesTitle = "Discard changes?"; DrawDiscardChanges = "The drawing has been changed." + System.Environment.NewLine + "Discard the changes?"; DrawColorPickerToolName = "Color picker"; DrawOpenDrawing = "Open ASSA drawing"; @@ -812,7 +789,6 @@ public LanguageAssa() ProgressBarForeColor = "Foreground color"; ProgressBarSquareCorners = "Square corners"; ProgressBarRoundedCorners = "Rounded corners"; - ProgressBarChapters = "Chapters"; ProgressBarSplitterWidth = "Splitter width"; ProgressBarSplitterHeight = "Splitter height"; ProgressBarXAdjustment = "X adjustment"; @@ -843,7 +819,6 @@ public LanguageAssa() FontCollectorUsedIn = "Used in"; FontCollectorFontFiles = "Font file(s)"; FontCollectorStyleX = "Style: {0}"; - FontCollectorInlineLineX = "Line {0}"; FontCollectorFound = "Found"; FontCollectorNotFound = "Not found"; FontCollectorEmbedded = "Embedded"; @@ -918,12 +893,9 @@ public LanguageAssa() StylesSummary = "{0} styles in file · {1} saved"; StylesTransform = "Transform"; StylesEditing = "Editing"; - StylesPreviewSampleText = "Sample text"; StylesPreviewSampleTextHint = "Sample text - \\N for a new line"; StylesScale = "Scale %"; - StylesPreviewDark = "Dark"; StylesPreviewChecker = "Checker"; - StylesPreviewLight = "Light"; AllCategories = "[All]"; NewCategory = "New category..."; RenameCategory = "Rename category..."; @@ -989,7 +961,6 @@ public LanguageAssa() SetPositionTopCenter = "Top center"; SetPositionTopRight = "Top right"; SetPositionMiddleLeft = "Middle left"; - SetPositionCenter = "Center"; SetPositionMiddleRight = "Middle right"; SetPositionBottomLeft = "Bottom left"; SetPositionBottomCenter = "Bottom center"; @@ -1111,7 +1082,6 @@ public LanguageAssa() AdvancedEffectSingleWordPopDescription = "Shows one big word at a time, popping in as it is spoken - short-video caption style"; AdvancedEffectBoldCaptions = "Bold captions"; AdvancedEffectBoldCaptionsDescription = "Bold captions with a thick outline - the spoken word lights up in color and grows"; - AdvancedEffectBoxColor = "Box color"; AdvancedEffectHighlightColor = "Highlight color"; AdvancedEffectUpperCase = "Uppercase"; AdvancedEffectTextSizePercent = "Text size (%)"; diff --git a/src/ui/Logic/Config/Language/Edit/LanguageEditFind.cs b/src/ui/Logic/Config/Language/Edit/LanguageEditFind.cs index 6474f93eb0e..c92502243be 100644 --- a/src/ui/Logic/Config/Language/Edit/LanguageEditFind.cs +++ b/src/ui/Logic/Config/Language/Edit/LanguageEditFind.cs @@ -14,7 +14,6 @@ public class LanguageEditFind public string ReplaceInTextOnly { get; set; } public string ReplaceInOriginalOnly { get; set; } public string ReplacedOneOccurrence { get; set; } - public string ReplacedXOccurrences { get; set; } public LanguageEditFind() { @@ -30,6 +29,5 @@ public LanguageEditFind() ReplaceInTextOnly = "Text only"; ReplaceInOriginalOnly = "Original text only"; ReplacedOneOccurrence = "Replaced one occurrence"; - ReplacedXOccurrences = "Replaced {0} occurrences"; } } \ No newline at end of file diff --git a/src/ui/Logic/Config/Language/Edit/LanguageModifySelection.cs b/src/ui/Logic/Config/Language/Edit/LanguageModifySelection.cs index f48d08757a9..f68829a2eef 100644 --- a/src/ui/Logic/Config/Language/Edit/LanguageModifySelection.cs +++ b/src/ui/Logic/Config/Language/Edit/LanguageModifySelection.cs @@ -28,7 +28,6 @@ public class LanguageModifySelection public string Bookmarked { get; set; } public string Forced { get; set; } = string.Empty; public string BookmarkContains { get; set; } - public string BlankLines { get; set; } public string HearingImpaired { get; set; } public string HearingImpairedBrackets { get; set; } public string HearingImpairedCurlyBrackets { get; set; } @@ -38,10 +37,8 @@ public class LanguageModifySelection public string HearingImpairedUppercaseLine { get; set; } public string HearingImpairedLineContains { get; set; } public string HearingImpairedMusicSymbols { get; set; } - public string HearingImpairedInterjections { get; set; } public string HearingImpairedSettingsHint { get; set; } public string Selection { get; set; } - public string HearingImpairedOther { get; set; } public string SelectionNew { get; set; } public string SelectionAdd { get; set; } public string SelectionSubtract { get; set; } @@ -72,7 +69,6 @@ public LanguageModifySelection() Bookmarked = "Bookmarked"; Forced = "Forced"; BookmarkContains = "Bookmark contains"; - BlankLines = "Blank lines"; HearingImpaired = "Hearing impaired (SDH)"; HearingImpairedBrackets = "Brackets [...]"; HearingImpairedCurlyBrackets = "Curly brackets {...}"; @@ -82,10 +78,8 @@ public LanguageModifySelection() HearingImpairedUppercaseLine = "Uppercase line"; HearingImpairedLineContains = "Line contains"; HearingImpairedMusicSymbols = "Only music symbols"; - HearingImpairedInterjections = "Interjections"; HearingImpairedSettingsHint = "Details come from Tools > Remove text for hearing impaired"; Selection = "Selection"; - HearingImpairedOther = "Other"; SelectionNew = "New selection"; SelectionAdd = "Add to selection"; SelectionSubtract = "Subtract from selection"; diff --git a/src/ui/Logic/Config/Language/Edit/LanguageMultipleReplace.cs b/src/ui/Logic/Config/Language/Edit/LanguageMultipleReplace.cs index eb8a43b91cf..d51a1137f00 100644 --- a/src/ui/Logic/Config/Language/Edit/LanguageMultipleReplace.cs +++ b/src/ui/Logic/Config/Language/Edit/LanguageMultipleReplace.cs @@ -17,7 +17,6 @@ public class LanguageMultipleReplace public string DeleteRuleConfirm { get; set; } public string FindWhat { get; set; } public string DescriptionOptional { get; set; } - public string InvalidRegularExpressionX { get; set; } public string RegularExpressionTooSlowX { get; set; } public string MoveToCategory { get; set; } @@ -38,7 +37,6 @@ public LanguageMultipleReplace() DeleteRuleConfirm = "Delete rule '{0}'?"; FindWhat = "Find what"; DescriptionOptional = "Description (optional)"; - InvalidRegularExpressionX = "Invalid regular expression: {0}"; RegularExpressionTooSlowX = "Regular expression gave up after {0} seconds - the rule is skipped, try a simpler pattern"; MoveToCategory = "Move to category"; } diff --git a/src/ui/Logic/Config/Language/File/LanguageExport.cs b/src/ui/Logic/Config/Language/File/LanguageExport.cs index fa438c4a547..ec3821d7fe8 100644 --- a/src/ui/Logic/Config/Language/File/LanguageExport.cs +++ b/src/ui/Logic/Config/Language/File/LanguageExport.cs @@ -4,7 +4,6 @@ public class LanguageExport { public string ExportImagesProfiles { get; set; } public string LeftRightMargin { get; set; } - public string TopBottomMargin { get; set; } public string LineSpacingPercent { get; set; } public string PaddingLeftRight { get; set; } public string PaddingTopBottom { get; set; } @@ -42,7 +41,6 @@ public class LanguageExport public string EditCustomFormat { get; set; } public string NewCustomFormat { get; set; } public string DeleteSelectedCustomTextFormatX { get; set; } - public string TimeCodeFormat { get; set; } public string NewLineFormat { get; set; } public string PleaseEnterNameForTheCustomFormat { get; set; } public string TitleExportPlainText { get; set; } @@ -73,7 +71,6 @@ public class LanguageExport public string Plane3DXNoDepth { get; set; } public string TextEffect { get; set; } public string TextEffectSettingsTitle { get; set; } - public string TextEffectPreset { get; set; } public string TextEffectStrength { get; set; } public string TextEffectLetterSpacing { get; set; } public string TextEffectArcBend { get; set; } @@ -103,7 +100,6 @@ public LanguageExport() { ExportImagesProfiles = "Export images profiles"; LeftRightMargin = "Left/right margin"; - TopBottomMargin = "Top/bottom margin"; LineSpacingPercent = "Line spacing %"; PaddingLeftRight = "Padding left/right"; PaddingTopBottom = "Padding top/bottom"; @@ -141,7 +137,6 @@ public LanguageExport() EditCustomFormat = "Edit custom text format"; NewCustomFormat = "New custom text format"; DeleteSelectedCustomTextFormatX = "Delete selected custom text format \"{0}\"?"; - TimeCodeFormat = "Time code format"; NewLineFormat = "New line format"; PleaseEnterNameForTheCustomFormat = "Please enter name for the custom format"; TitleExportPlainText = "Export plain text"; @@ -172,7 +167,6 @@ public LanguageExport() Plane3DXNoDepth = "{0} (no depth values)"; TextEffect = "Text effect"; TextEffectSettingsTitle = "Text effect settings"; - TextEffectPreset = "Preset"; TextEffectStrength = "Strength"; TextEffectLetterSpacing = "Letter spacing"; TextEffectArcBend = "Curve"; diff --git a/src/ui/Logic/Config/Language/File/LanguageFile.cs b/src/ui/Logic/Config/Language/File/LanguageFile.cs index 2fbcf4af9e8..06ac3a665b3 100644 --- a/src/ui/Logic/Config/Language/File/LanguageFile.cs +++ b/src/ui/Logic/Config/Language/File/LanguageFile.cs @@ -78,7 +78,6 @@ public class LanguageFile public string SaveCompareHtmlTitle { get; set; } public string PickMatroskaTrackX { get; set; } public string PickTransportStreamTrackX { get; set; } - public string Chapters { get; set; } public string DvdVobFilesMissingX { get; set; } public string PickMp4TrackX { get; set; } public string PickMxfTrackX { get; set; } @@ -91,7 +90,6 @@ public class LanguageFile public string PropertyDefaultStyle { get; set; } public string PropertyDefaultRegion { get; set; } public string PropertyStyleAttributeName { get; set; } - public string PropertyTimeCodeFormat { get; set; } public string PropertyTopOrigin { get; set; } public string PropertyTopExtent { get; set; } public string PropertyBottomOrigin { get; set; } @@ -166,7 +164,6 @@ public LanguageFile() SaveCompareHtmlTitle = "Save compare HTML file"; PickMatroskaTrackX = "Pick Matroska track - {0}"; PickTransportStreamTrackX = "Pick transport stream track - {0}"; - Chapters = "Chapters"; DvdVobFilesMissingX = "VOB files missing ({0}% found)"; PickMp4TrackX = "Pick MP4 track - {0}"; PickMxfTrackX = "Pick MXF track - {0}"; @@ -179,7 +176,6 @@ public LanguageFile() PropertyDefaultStyle = "Default style"; PropertyDefaultRegion = "Default region"; PropertyStyleAttributeName = "Style attribute name"; - PropertyTimeCodeFormat = "Time code format"; PropertyTopOrigin = "Top origin"; PropertyTopExtent = "Top extent"; PropertyBottomOrigin = "Bottom origin"; diff --git a/src/ui/Logic/Config/Language/File/LanguageFilePropertiesDCinema.cs b/src/ui/Logic/Config/Language/File/LanguageFilePropertiesDCinema.cs index dc698a2a18b..7b471b1356d 100644 --- a/src/ui/Logic/Config/Language/File/LanguageFilePropertiesDCinema.cs +++ b/src/ui/Logic/Config/Language/File/LanguageFilePropertiesDCinema.cs @@ -15,10 +15,8 @@ public class LanguageFilePropertiesDCinema public string StartTime { get; internal set; } public string FontId { get; internal set; } public string FontUri { get; internal set; } - public string ChooseColor { get; internal set; } public string FontEffect { get; internal set; } public string EffectColor { get; internal set; } - public string TopBottomMargin { get; internal set; } public string FadeUpTime { get; internal set; } public string FadeDownTime { get; internal set; } public string ZPosition { get; internal set; } @@ -45,10 +43,8 @@ public LanguageFilePropertiesDCinema() StartTime = "Start time"; FontId = "Font ID"; FontUri = "Font URI"; - ChooseColor = "Choose color"; FontEffect = "Font effect"; EffectColor = "Effect color"; - TopBottomMargin = "Top/bottom margin"; FadeUpTime = "Fade up time"; FadeDownTime = "Fade down time"; ZPosition = "Z-position"; diff --git a/src/ui/Logic/Config/Language/File/LanguageImport.cs b/src/ui/Logic/Config/Language/File/LanguageImport.cs index 85719822e71..737d09764e0 100644 --- a/src/ui/Logic/Config/Language/File/LanguageImport.cs +++ b/src/ui/Logic/Config/Language/File/LanguageImport.cs @@ -2,7 +2,6 @@ public class LanguageImport { - public string ImportTimeCodes { get; set; } public string PlainTextDotDotDot { get; set; } public string TitleImportPlainText { get; set; } public string CsvXlsxCustomColumnsDotDotDot { get; set; } @@ -36,12 +35,10 @@ public class LanguageImport public string DvdMenuIfoNoTitleSetsX { get; set; } public string DvdEncryptedXOfY { get; set; } public string SplitTextAt { get; set; } - public string BlankLines { get; set; } public string OneLineIsOneSubtitle { get; set; } public string TwoLinesAreOneSubtitle { get; set; } public string ImportFilesDotDotDot { get; set; } public string MultipleFiles { get; set; } - public string MaxLineLength { get; set; } public string MergeShortLines { get; set; } public string Fixed { get; set; } public string NumberOfSubtitlesX { get; set; } @@ -66,7 +63,6 @@ public LanguageImport() { PlainTextDotDotDot = "Plain text..."; TitleImportPlainText = "Import plain text"; - ImportTimeCodes = "Import time codes..."; ImagesForOcrDotDotDot = "Images for OCR..."; TitleImportImages = "Import images"; TimeCodesDotDotDot = "Time codes..."; @@ -102,12 +98,10 @@ public LanguageImport() DvdMenuIfoNoTitleSetsX = "No title sets (VTS_xx_0.IFO) found next to {0}"; DvdEncryptedXOfY = "{0} of the {1} subtitle packets are still CSS encrypted - these VOB files were copied without decrypting, so some subtitle images will be garbled.\n\nDecrypt the DVD with a DVD ripping tool first for a clean result.\n\nContinue anyway?"; SplitTextAt = "Split text at"; - BlankLines = "Blank lines"; OneLineIsOneSubtitle = "One line is one subtitle"; TwoLinesAreOneSubtitle = "Two lines are one subtitle"; ImportFilesDotDotDot = "Import files..."; MultipleFiles = "Import from multiple text files (one file is one subtitle)"; - MaxLineLength = "Max line length"; MergeShortLines = "Merge short lines"; Fixed = "Fixed"; NumberOfSubtitlesX = "Number of subtitles: {0}"; diff --git a/src/ui/Logic/Config/Language/File/LanguageRestoreAutoBackup.cs b/src/ui/Logic/Config/Language/File/LanguageRestoreAutoBackup.cs index 3532b8cbc26..273a7113560 100644 --- a/src/ui/Logic/Config/Language/File/LanguageRestoreAutoBackup.cs +++ b/src/ui/Logic/Config/Language/File/LanguageRestoreAutoBackup.cs @@ -9,7 +9,6 @@ public class LanguageRestoreAutoBackup public string RestoreXFromY { get; set; } public string DeleteAllSubtitleBackups { get; set; } public string DeleteAll { get; set; } - public string Subtitles { get; set; } public string SettingsBackupInfo { get; set; } public string SettingsBackupInfoEveryStart { get; set; } public string SettingsBackupOff { get; set; } @@ -34,7 +33,6 @@ public LanguageRestoreAutoBackup() RestoreXFromY = "Do you want to restore \"{0}\" from {1}?"; DeleteAllSubtitleBackups = "Do you want to delete all subtitle backup files?"; DeleteAll = "Delete all"; - Subtitles = "Subtitles"; SettingsBackupInfo = "Settings.json is backed up when Subtitle Edit starts, at most once every {0} day(s) and only if the settings have changed. The newest {1} backups are kept."; SettingsBackupInfoEveryStart = "Settings.json is backed up every time Subtitle Edit starts, if the settings have changed. The newest {0} backups are kept."; SettingsBackupOff = "Automatic settings backup is turned off (Options → Settings → File)."; diff --git a/src/ui/Logic/Config/Language/File/LanguageStatistics.cs b/src/ui/Logic/Config/Language/File/LanguageStatistics.cs index d84e53133c1..0aa04804003 100644 --- a/src/ui/Logic/Config/Language/File/LanguageStatistics.cs +++ b/src/ui/Logic/Config/Language/File/LanguageStatistics.cs @@ -48,7 +48,6 @@ public class LanguageStatistics public string GapMaximum { get; set; } public string GapAverage { get; set; } public string GapExceedingMinimum { get; set; } - public string Subtitles { get; set; } public string Words { get; set; } public string TotalDurationShort { get; set; } public string TimingAndPacing { get; set; } @@ -113,7 +112,6 @@ public LanguageStatistics() GapMaximum = "Gap - maximum: {0:#,##0} ms"; GapAverage = "Gap - average: {0:#,##0.##} ms"; GapExceedingMinimum = "Gap - below minimum ({0:#,##0} ms): {1} ({2:0.00}%)"; - Subtitles = "Subtitles"; Words = "Words"; TotalDurationShort = "Total duration"; TimingAndPacing = "Timing and pacing"; diff --git a/src/ui/Logic/Config/Language/Help/LanguageHelp.cs b/src/ui/Logic/Config/Language/Help/LanguageHelp.cs index 3eef0642227..07a470f1f93 100644 --- a/src/ui/Logic/Config/Language/Help/LanguageHelp.cs +++ b/src/ui/Logic/Config/Language/Help/LanguageHelp.cs @@ -10,7 +10,6 @@ public class LanguageHelp public string CheckForUpdatesUnableToCheck { get; set; } public string CheckForUpdatesDownloadNewVersion { get; set; } public string CheckForUpdatesInstallAndRestart { get; set; } - public string CheckForUpdatesDownloadingX { get; set; } public string CheckForUpdatesInstallFailed { get; set; } public string CheckForUpdatesInstallsWhenClosed { get; set; } @@ -24,7 +23,6 @@ public LanguageHelp() CheckForUpdatesUnableToCheck = "Unable to check for updates."; CheckForUpdatesDownloadNewVersion = "Download new version"; CheckForUpdatesInstallAndRestart = "Install and restart"; - CheckForUpdatesDownloadingX = "Downloading {0}..."; CheckForUpdatesInstallFailed = "Automatic update failed - please use \"Download new version\" instead."; CheckForUpdatesInstallsWhenClosed = "The update will be installed when Subtitle Edit closes."; } diff --git a/src/ui/Logic/Config/Language/LanguageErrorList.cs b/src/ui/Logic/Config/Language/LanguageErrorList.cs index 4aa3cedd35e..258cd4117fa 100644 --- a/src/ui/Logic/Config/Language/LanguageErrorList.cs +++ b/src/ui/Logic/Config/Language/LanguageErrorList.cs @@ -9,11 +9,8 @@ public class LanguageErrorList public string All { get; set; } public string TooManyLines { get; set; } public string CharactersPerSecond { get; set; } - public string DurationTooShort { get; set; } - public string DurationTooLong { get; set; } public string LineTooLong { get; set; } public string LineTooWide { get; set; } - public string Overlap { get; set; } public string GapTooShort { get; set; } public string TooManyLinesHint { get; set; } public string CharactersPerSecondHint { get; set; } @@ -29,7 +26,6 @@ public class LanguageErrorList public string DetailOverlapToNext { get; set; } public string DetailGapToPrevious { get; set; } public string DetailGapToNext { get; set; } - public string Detail { get; set; } public string Tip { get; set; } public string ExportAsText { get; set; } public string ExportAsExcel { get; set; } @@ -47,11 +43,8 @@ public LanguageErrorList() All = "All"; TooManyLines = "Too many lines"; CharactersPerSecond = "Reading speed"; - DurationTooShort = "Too short"; - DurationTooLong = "Too long"; LineTooLong = "Line too long"; LineTooWide = "Line too wide"; - Overlap = "Overlapping"; GapTooShort = "Gap too short"; TooManyLinesHint = "More lines than the maximum number of lines"; CharactersPerSecondHint = "Characters per second above the maximum"; @@ -67,7 +60,6 @@ public LanguageErrorList() DetailOverlapToNext = "to next: {0} ms"; DetailGapToPrevious = "to previous: {0} < {1} ms"; DetailGapToNext = "to next: {0} < {1} ms"; - Detail = "Detail"; Tip = "Double-click a line (or press Enter) to go to it. Which checks run, and their limits, are set in Settings > General."; ExportAsText = "Text file (.txt)..."; ExportAsExcel = "Excel file (.xlsx)..."; diff --git a/src/ui/Logic/Config/Language/LanguageGeneral.cs b/src/ui/Logic/Config/Language/LanguageGeneral.cs index 313e6c1aba3..56252fe25f4 100644 --- a/src/ui/Logic/Config/Language/LanguageGeneral.cs +++ b/src/ui/Logic/Config/Language/LanguageGeneral.cs @@ -828,8 +828,6 @@ public class LanguageGeneral public string OpenAiCompatibleSttExtraHeaders { get; set; } public string OpenAiCompatibleSttTimeout { get; set; } public string OpenAiCompatibleSttLanguage { get; set; } - public string OpenAiCompatibleSttTemperature { get; set; } - public string OpenAiCompatibleSttPrompt { get; set; } public string OpenAiCompatibleSttModelRejectedHint { get; set; } public string DashScopeSttRegion { get; set; } public string DashScopeSttEnableWords { get; set; } @@ -856,6 +854,47 @@ public class LanguageGeneral public string TranscriptionProgressModelFormat { get; set; } public string TranscriptionProgressReceivedSegmentsFormat { get; set; } public string TranscriptionProgressModelAuto { get; set; } + public string Prompt { get; set; } + public string Temperature { get; set; } + public string Preset { get; set; } + public string Output { get; set; } + public string OutputFormat { get; set; } + public string Other { get; set; } + public string TimeCodeFormat { get; set; } + public string TopBottomMargin { get; set; } + public string Theme { get; set; } + public string Brightness { get; set; } + public string ChooseColor { get; set; } + public string LineX { get; set; } + public string PlayLine { get; set; } + public string MaxLineLength { get; set; } + public string MaxCharsPerSec { get; set; } + public string MaxNumberOfLines { get; set; } + public string Subtitles { get; set; } + public string ShotChanges { get; set; } + public string Chapters { get; set; } + public string Audio { get; set; } + public string Interjections { get; set; } + public string BlankLines { get; set; } + public string EditWholeText { get; set; } + public string NoTextInClipboard { get; set; } + public string NoSubtitlesFoundTitle { get; set; } + public string ReplacedXOccurrences { get; set; } + public string InvalidRegularExpressionX { get; set; } + public string DiscardChangesTitle { get; set; } + public string DownloadingXDotDotDot { get; set; } + public string ImportTimeCodesDotDotDot { get; set; } + public string Original { get; set; } + public string NextChange { get; set; } + public string PreviousChange { get; set; } + public string ChangeXOfY { get; set; } + public string TooLong { get; set; } + public string TooShort { get; set; } + public string Overlapping { get; set; } + public string Detail { get; set; } + public string FixOcrErrors { get; set; } + public string RunningOcrXY { get; set; } + public string LlamaCppDownloadEnginePrompt { get; set; } public LanguageGeneral() { @@ -1683,8 +1722,6 @@ public LanguageGeneral() OpenAiCompatibleSttExtraHeaders = "Extra Headers"; OpenAiCompatibleSttTimeout = "Timeout (seconds)"; OpenAiCompatibleSttLanguage = "Language Hint"; - OpenAiCompatibleSttTemperature = "Temperature"; - OpenAiCompatibleSttPrompt = "Prompt"; OpenAiCompatibleSttModelRejectedHint = "The server rejected the model name. Not every OpenAI compatible endpoint takes a model - xAI's https://api.x.ai/v1/stt, for one, has no 'model' parameter at all. Try clearing the Model field."; DashScopeSttRegion = "Region"; DashScopeSttEnableWords = "Word-level timestamps"; @@ -1711,5 +1748,46 @@ public LanguageGeneral() TranscriptionProgressModelFormat = "Model: {0}"; TranscriptionProgressReceivedSegmentsFormat = "Received Segments ({0})"; TranscriptionProgressModelAuto = "(auto)"; + Prompt = "Prompt"; + Temperature = "Temperature"; + Preset = "Preset"; + Output = "Output"; + OutputFormat = "Output format"; + Other = "Other"; + TimeCodeFormat = "Time code format"; + TopBottomMargin = "Top/bottom margin"; + Theme = "Theme"; + Brightness = "Brightness"; + ChooseColor = "Choose color"; + LineX = "Line {0}"; + PlayLine = "Play line"; + MaxLineLength = "Max line length"; + MaxCharsPerSec = "Max chars/sec"; + MaxNumberOfLines = "Max number of lines"; + Subtitles = "Subtitles"; + ShotChanges = "Shot changes"; + Chapters = "Chapters"; + Audio = "Audio"; + Interjections = "Interjections"; + BlankLines = "Blank lines"; + EditWholeText = "Edit whole text"; + NoTextInClipboard = "No text in clipboard"; + NoSubtitlesFoundTitle = "No subtitles found"; + ReplacedXOccurrences = "Replaced {0} occurrences"; + InvalidRegularExpressionX = "Invalid regular expression: {0}"; + DiscardChangesTitle = "Discard changes?"; + DownloadingXDotDotDot = "Downloading {0}..."; + ImportTimeCodesDotDotDot = "Import time codes..."; + Original = "Original"; + NextChange = "Next change"; + PreviousChange = "Previous change"; + ChangeXOfY = "Change {0} of {1}"; + TooLong = "Too long"; + TooShort = "Too short"; + Overlapping = "Overlapping"; + Detail = "Detail"; + FixOcrErrors = "Fix OCR errors"; + RunningOcrXY = "Running OCR... {0}/{1}"; + LlamaCppDownloadEnginePrompt = "llama.cpp requires the llama-server engine to be downloaded. Download now?"; } } \ No newline at end of file diff --git a/src/ui/Logic/Config/Language/LanguageSourceView.cs b/src/ui/Logic/Config/Language/LanguageSourceView.cs index 96809031fea..910c30ce8e3 100644 --- a/src/ui/Logic/Config/Language/LanguageSourceView.cs +++ b/src/ui/Logic/Config/Language/LanguageSourceView.cs @@ -2,7 +2,6 @@ namespace Nikse.SubtitleEdit.Logic.Config.Language; public class LanguageSourceView { - public string DiscardChangesTitle { get; set; } public string DiscardChanges { get; set; } public string XLinesYSubtitles { get; set; } public string XLinesYSubtitlesZErrors { get; set; } @@ -20,7 +19,6 @@ public class LanguageSourceView public LanguageSourceView() { - DiscardChangesTitle = "Discard changes?"; DiscardChanges = "The source text has been changed." + System.Environment.NewLine + "Discard the changes?"; XLinesYSubtitles = "{0} lines, {1} subtitles"; XLinesYSubtitlesZErrors = "{0} lines, {1} subtitles, {2} error(s)"; diff --git a/src/ui/Logic/Config/Language/Main/LanguageMain.cs b/src/ui/Logic/Config/Language/Main/LanguageMain.cs index cf7fd325bd0..f61fdfbeaa0 100644 --- a/src/ui/Logic/Config/Language/Main/LanguageMain.cs +++ b/src/ui/Logic/Config/Language/Main/LanguageMain.cs @@ -49,7 +49,6 @@ public class LanguageMain public string LoadingWaveInfoFromCache { get; set; } public string ClickToGenerateWaveform { get; set; } public string WaveformFfmpegNotFoundClickToSetUp { get; set; } - public string NoTextInClipboard { get; set; } public string NumberOfLinesEvenlyDistributedX { get; set; } public string OneLineCopiedFromOriginal { get; set; } public string OneLineCopiedToOriginal { get; set; } @@ -145,7 +144,6 @@ public class LanguageMain public string ToolXChangedYLines { get; set; } public string TranscriptionCompletedWithXLines { get; set; } public string OpenSubtitleFileX { get; set; } - public string ReplacedXOccurrences { get; set; } public string FfmpegDownloadedAndInstalledToX { get; set; } public string NothingToSave { get; set; } public string FormatLimitWarningTitle { get; set; } @@ -216,7 +214,6 @@ public LanguageMain() LoadingWaveInfoFromCache = "Loading wave info from cache..."; ClickToGenerateWaveform = "Click to generate waveform"; WaveformFfmpegNotFoundClickToSetUp = "FFmpeg not found - click the waveform to set up FFmpeg and generate the waveform"; - NoTextInClipboard = "No text in clipboard"; NumberOfLinesEvenlyDistributedX = "Evenly distributed {0} lines"; OneLineCopiedFromOriginal = "One line copied from original subtitle"; OneLineCopiedToOriginal = "One line copied to original subtitle"; @@ -312,7 +309,6 @@ public LanguageMain() ToolXChangedYLines = "{0}: {1} lines changed"; TranscriptionCompletedWithXLines = "Transcription completed with {0} lines"; OpenSubtitleFileX = "Open subtitle file \"{0}\"?"; - ReplacedXOccurrences = "Replaced {0} occurrences"; FfmpegDownloadedAndInstalledToX = "ffmpeg downloaded and installed to {0}"; NothingToSave = "Nothing to save"; FormatLimitWarningTitle = "Format limits exceeded"; diff --git a/src/ui/Logic/Config/Language/Ocr/LanguageOcr.cs b/src/ui/Logic/Config/Language/Ocr/LanguageOcr.cs index b5f95703963..f5a786dd137 100644 --- a/src/ui/Logic/Config/Language/Ocr/LanguageOcr.cs +++ b/src/ui/Logic/Config/Language/Ocr/LanguageOcr.cs @@ -22,7 +22,6 @@ public class LanguageOcr public string NumberOfPixelsIsSpace { get; set; } public string InspectImageMatches { get; set; } public string ResolutionXYAndTopmarginZ { get; set; } - public string RunningOcrDotDotDotXY { get; set; } public string RunningOcrDotDotDot { get; set; } public string AutoSubmitFirstCharacter { get; set; } public string EditNOcrDatabase { get; set; } @@ -38,12 +37,10 @@ public class LanguageOcr public string AddNewCharcter { get; set; } public string AddNOcrCharacterTitle { get; set; } public string AddBinaryOcrCharacterTitle { get; set; } - public string LineIndexX { get; set; } public string InspectNOcrAdditions { get; set; } public string OcrSelectedLines { get; set; } public string FillSelectedLinesWithClipboard { get; set; } public string ShowImage { get; set; } - public string FixOcrErrors { get; set; } public string PromptForUknownWords { get; set; } public string TryToGuessUnknownWords { get; set; } public string AutoBreakIfMoreThanXLines { get; set; } @@ -62,7 +59,6 @@ public class LanguageOcr public string ClearBackground { get; set; } public string ClearForeground { get; set; } public string NOcrDrawHelp { get; set; } - public string EditWholeText { get; set; } public string EditWordOnly { get; set; } public string ImagePreProcessing { get; set; } public string PreProcessingTitle { get; set; } @@ -107,7 +103,6 @@ public class LanguageOcr public string HtmlExportNoMatches { get; set; } public string HtmlExportImageBackground { get; set; } public string HtmlExportCheckerboard { get; set; } - public string HtmlExportTheme { get; set; } public string HtmlExportGeneratedBySubtitleEdit { get; set; } public string VobSubColors { get; set; } public string VobSubColorsTitle { get; set; } @@ -138,7 +133,6 @@ public class LanguageOcr public string LlamaCppNotDownloaded { get; set; } public string LlamaCppReturnedNoText { get; set; } public string LlamaCppDownloadEngineAndModelPrompt { get; set; } - public string LlamaCppDownloadEnginePrompt { get; set; } public string LlamaCppDownloadModelPrompt { get; set; } public string CrispEmbedNotDownloaded { get; set; } public string CrispEmbedReturnedNoText { get; set; } @@ -200,7 +194,6 @@ public LanguageOcr() NumberOfPixelsIsSpace = "Number of pixels is space"; InspectImageMatches = "Inspect image matches"; ResolutionXYAndTopmarginZ = "Resolution {0}x{1}, top margin {2}"; - RunningOcrDotDotDotXY = "Running OCR... {0}/{1}"; RunningOcrDotDotDot = "Running OCR..."; AutoSubmitFirstCharacter = "Auto submit first character"; EditNOcrDatabase = "Edit nOCR database"; @@ -216,12 +209,10 @@ public LanguageOcr() AddNewCharcter = "Add new character"; AddNOcrCharacterTitle = "Add nOCR character for line {0}, character {1} of {2} using database \"{3}\""; AddBinaryOcrCharacterTitle = "Add binary OCR character for line {0}, character {1} of {2} using database \"{3}\""; - LineIndexX = "Line {0}"; InspectNOcrAdditions = "Inspect new nOCR additions"; OcrSelectedLines = "OCR selected lines"; FillSelectedLinesWithClipboard = "Fill selected lines with clipboard text"; ShowImage = "Show image"; - FixOcrErrors = "Fix OCR errors"; PromptForUknownWords = "Prompt for unknown words"; TryToGuessUnknownWords = "Try to guess unknown words"; AutoBreakIfMoreThanXLines = "Auto break if more than {0} lines"; @@ -240,7 +231,6 @@ public LanguageOcr() ClearBackground = "Clear background"; ClearForeground = "Clear foreground"; NOcrDrawHelp = "Tips for drawing\r\n────────────────────\r\n• Hold Ctrl down to continue line\r\n• Ctrl+z=undo, Ctrl+y=redo"; - EditWholeText = "Edit whole text"; EditWordOnly = "Edit word only"; ImagePreProcessing = "Image pre-processing"; PreProcessingTitle = "Pre-processing"; @@ -287,7 +277,6 @@ public LanguageOcr() HtmlExportNoMatches = "No lines match the filter"; HtmlExportImageBackground = "Image background"; HtmlExportCheckerboard = "Checkerboard"; - HtmlExportTheme = "Theme"; HtmlExportGeneratedBySubtitleEdit = "Generated by Subtitle Edit"; VobSubColors = "VobSub/DVD colors..."; VobSubColorsTitle = "VobSub/DVD colors"; @@ -321,7 +310,6 @@ public LanguageOcr() LlamaCppNotDownloaded = "llama.cpp engine/model not downloaded - download via batch convert settings"; LlamaCppReturnedNoText = "llama.cpp returned no text - check the server and model"; LlamaCppDownloadEngineAndModelPrompt = "llama.cpp requires the llama-server engine and the selected OCR model to be downloaded. Download now?"; - LlamaCppDownloadEnginePrompt = "llama.cpp requires the llama-server engine to be downloaded. Download now?"; LlamaCppDownloadModelPrompt = "llama.cpp requires the selected OCR model to be downloaded. Download now?"; CrispEmbedNotDownloaded = "CrispEmbed engine/model not downloaded - download via batch convert settings"; CrispEmbedReturnedNoText = "CrispEmbed returned no text - check the model"; diff --git a/src/ui/Logic/Config/Language/Options/LanguageSettings.cs b/src/ui/Logic/Config/Language/Options/LanguageSettings.cs index a95ed84fcf6..5d1f12a7b83 100644 --- a/src/ui/Logic/Config/Language/Options/LanguageSettings.cs +++ b/src/ui/Logic/Config/Language/Options/LanguageSettings.cs @@ -66,7 +66,6 @@ public class LanguageSettings public string GridAlternatingRowColor { get; set; } public string GridAlternatingRowColorDark { get; set; } public string UiFont { get; set; } - public string Theme { get; set; } public string IconTheme { get; set; } public string DarkThemeForegroundColor { get; set; } public string DarkThemeBackgroundColor { get; set; } @@ -76,7 +75,6 @@ public class LanguageSettings public string SpellCheckHighlightColor { get; set; } public string SingleLineMaxLength { get; set; } public string OptimalCharsPerSec { get; set; } - public string MaxCharsPerSec { get; set; } public string MaxWordsPerMin { get; set; } public string MinDurationMs { get; set; } public string MaxDurationMs { get; set; } @@ -87,7 +85,6 @@ public class LanguageSettings public string MinGapCalculateFrames { get; set; } public string MinGapCalculateXFramesAtYGivesZMs { get; set; } public string MinGapCalculateUseXAsNewGap { get; set; } - public string MaxLines { get; set; } public string UnbreakSubtitlesShortThan { get; set; } public string AutoBreakLineEndingEarly { get; set; } public string AutoBreakCommaBreakEarly { get; set; } @@ -434,7 +431,6 @@ public LanguageSettings() GridAlternatingRowColor = "Alternating row color (light theme)"; GridAlternatingRowColorDark = "Alternating row color (dark theme)"; UiFont = "UI font"; - Theme = "Theme"; IconTheme = "Icon theme"; DarkThemeForegroundColor = "Dark theme foreground color"; DarkThemeBackgroundColor = "Dark theme background color"; @@ -444,7 +440,6 @@ public LanguageSettings() SpellCheckHighlightColor = "Spell check highlight color"; SingleLineMaxLength = "Single line max length"; OptimalCharsPerSec = "Optimal chars/sec"; - MaxCharsPerSec = "Max chars/sec"; MaxWordsPerMin = "Max words/min"; MinDurationMs = "Min duration (ms)"; MaxDurationMs = "Max duration (ms)"; @@ -455,7 +450,6 @@ public LanguageSettings() MinGapCalculateFrames = "Min. gap in frames"; MinGapCalculateXFramesAtYGivesZMs = "{0} frames at a frame rate of {1} gives {2} milliseconds."; MinGapCalculateUseXAsNewGap = "Use \"{0}\" milliseconds as new minimum gap?"; - MaxLines = "Max number of lines"; UnbreakSubtitlesShortThan = "Unbreak subtitles shorter than"; AutoBreakLineEndingEarly = "Auto-break early for end of sentence (.!?)"; AutoBreakCommaBreakEarly = "Auto-break early for comma"; diff --git a/src/ui/Logic/Config/Language/Options/LanguageSettingsShortcuts.cs b/src/ui/Logic/Config/Language/Options/LanguageSettingsShortcuts.cs index 2456956a941..281c660c0a0 100644 --- a/src/ui/Logic/Config/Language/Options/LanguageSettingsShortcuts.cs +++ b/src/ui/Logic/Config/Language/Options/LanguageSettingsShortcuts.cs @@ -34,7 +34,6 @@ public class LanguageSettingsShortcuts public string CustomShortcutPickCommand { get; set; } public string CustomShortcutEnterText { get; set; } public string CustomShortcutEnterFind { get; set; } - public string CustomShortcutInvalidRegexX { get; set; } public string CustomShortcutStepsHint { get; set; } public string ActiveIn { get; set; } public string ActiveInEverywhere { get; set; } @@ -376,7 +375,6 @@ public LanguageSettingsShortcuts() CustomShortcutPickCommand = "Please select a command to run."; CustomShortcutEnterText = "Please enter the text to insert."; CustomShortcutEnterFind = "Please enter the text to find."; - CustomShortcutInvalidRegexX = "Invalid regular expression: {0}"; CustomShortcutStepsHint = "Steps run from top to bottom. Text steps work on the selected lines; commands that open a window continue after it closes."; ActiveIn = "Active in"; ActiveInEverywhere = "Everywhere"; diff --git a/src/ui/Logic/Config/Language/SpellCheck/LanguageSpellCheck.cs b/src/ui/Logic/Config/Language/SpellCheck/LanguageSpellCheck.cs index 0e97dc0b79d..6d89ea52e5b 100644 --- a/src/ui/Logic/Config/Language/SpellCheck/LanguageSpellCheck.cs +++ b/src/ui/Logic/Config/Language/SpellCheck/LanguageSpellCheck.cs @@ -35,7 +35,6 @@ public class LanguageSpellCheck public string PickLiveSpellCheckDictionary { get; set; } public string UndoX { get; set; } public string PlayCurrentLineHint { get; set; } - public string EditWholeText { get; set; } public string EditWholeTextTitle { get; set; } public string ContinueFromCurrentLine { get; set; } public string ContinueFromTop { get; set; } @@ -94,7 +93,6 @@ public LanguageSpellCheck() PickLiveSpellCheckDictionary = "Pick live spell check dictionary"; UndoX = "Undo: {0}"; PlayCurrentLineHint = "Play the current line in the video player and pause at its end"; - EditWholeText = "Edit whole text"; EditWholeTextTitle = "Spell check - Edit whole text"; ContinueFromCurrentLine = "Continue spell check from the current line?\n\nYes = continue from the current line\nNo = start from the beginning"; ContinueFromTop = "Spell check reached the end of the subtitle.\n\nContinue from the top?"; diff --git a/src/ui/Logic/Config/Language/Tools/LanguageAiReview.cs b/src/ui/Logic/Config/Language/Tools/LanguageAiReview.cs index 24538388479..06a3e4829e2 100644 --- a/src/ui/Logic/Config/Language/Tools/LanguageAiReview.cs +++ b/src/ui/Logic/Config/Language/Tools/LanguageAiReview.cs @@ -18,16 +18,13 @@ public class LanguageAiReview public string CategoryGrammar { get; set; } public string CategoryPunctuation { get; set; } public string CategoryCasing { get; set; } - public string CategoryOther { get; set; } public string XNeedACloserLook { get; set; } - public string LineX { get; set; } public string LinesXToY { get; set; } public string LargeChangeWarning { get; set; } public string MismatchWarning { get; set; } public string EngineError { get; set; } public string PlayCurrentLineHint { get; set; } public string EditAfterHint { get; set; } - public string Prompt { get; set; } public string Template { get; set; } public string TemplatePlaceholder { get; set; } public string TemplateStandard { get; set; } @@ -62,16 +59,13 @@ public LanguageAiReview() CategoryGrammar = "Grammar"; CategoryPunctuation = "Punctuation"; CategoryCasing = "Casing"; - CategoryOther = "Other"; XNeedACloserLook = "{0} suggestion(s) need a closer look"; - LineX = "Line {0}"; LinesXToY = "Lines {0}-{1}"; LargeChangeWarning = "Large change - looks like a rewrite, review carefully"; MismatchWarning = "Does not resemble the original - may belong to a different line"; EngineError = "The AI engine could not be reached: {0}"; PlayCurrentLineHint = "Play the selected line in the video player and pause at its end (also double-click a line)"; EditAfterHint = "Click the selected row's text to edit the fix before applying it (Enter saves, Escape cancels)"; - Prompt = "Prompt"; Template = "Template"; TemplatePlaceholder = "Choose a template to replace the prompt"; TemplateStandard = "Standard proofreading"; diff --git a/src/ui/Logic/Config/Language/Tools/LanguageBatchConvert.cs b/src/ui/Logic/Config/Language/Tools/LanguageBatchConvert.cs index 75ddb4f277c..5ef33510648 100644 --- a/src/ui/Logic/Config/Language/Tools/LanguageBatchConvert.cs +++ b/src/ui/Logic/Config/Language/Tools/LanguageBatchConvert.cs @@ -67,10 +67,8 @@ public class LanguageBatchConvert public string AddVideoFiles { get; set; } public string SkipVideoFiles { get; set; } public string DoNotAskAgainVideoFiles { get; set; } - public string SettingsSectionOutput { get; set; } public string SettingsSectionAddingFiles { get; set; } public string SettingsSectionImageBasedSubtitles { get; set; } - public string SettingsSectionOther { get; set; } public string KeepSourceFileTimestamp { get; set; } public string PreventSleepWhileConverting { get; set; } public string ScanningFolderX { get; set; } @@ -95,7 +93,6 @@ public class LanguageBatchConvert public string XNotProcessed { get; set; } public string RemoveUnicodeControlCharactersTitle { get; set; } public string RemoveUnicodeControlCharactersInfo { get; set; } - public string Preset { get; set; } public string SavePresetDotDotDot { get; set; } public string DeletePreset { get; set; } public string PresetName { get; set; } @@ -165,10 +162,8 @@ public LanguageBatchConvert() AddVideoFiles = "Add video files"; SkipVideoFiles = "Subtitle files only"; DoNotAskAgainVideoFiles = "Do not ask again (change in Batch convert settings)"; - SettingsSectionOutput = "Output"; SettingsSectionAddingFiles = "Adding files"; SettingsSectionImageBasedSubtitles = "Image-based subtitles (OCR)"; - SettingsSectionOther = "Other"; KeepSourceFileTimestamp = "Keep source file date/time on output files"; PreventSleepWhileConverting = "Prevent computer from sleeping while converting"; ScanningFolderX = "Scanning {0}..."; @@ -193,7 +188,6 @@ public LanguageBatchConvert() XNotProcessed = "{0:#,###,##0} not processed"; RemoveUnicodeControlCharactersTitle = "Remove Unicode control characters"; RemoveUnicodeControlCharactersInfo = "Removes the invisible Unicode direction control characters (LRM, RLM, LRE, RLE, PDF, LRO and RLO) from the text, and replaces no-break spaces with normal spaces."; - Preset = "Preset"; SavePresetDotDotDot = "Save preset..."; DeletePreset = "Delete preset"; PresetName = "Preset name"; diff --git a/src/ui/Logic/Config/Language/Tools/LanguageBeautifyTimeCodes.cs b/src/ui/Logic/Config/Language/Tools/LanguageBeautifyTimeCodes.cs index 19128bf0fa4..4e7e21ed05e 100644 --- a/src/ui/Logic/Config/Language/Tools/LanguageBeautifyTimeCodes.cs +++ b/src/ui/Logic/Config/Language/Tools/LanguageBeautifyTimeCodes.cs @@ -5,12 +5,10 @@ public class LanguageBeautifyTimeCodes public string Title { get; set; } public string SnapToFrames { get; set; } public string MinDuration { get; set; } - public string Original { get; set; } public string Beautified { get; set; } public string SubtitlesCount { get; set; } public string ChangedCount { get; set; } public string ShotChangesCount { get; set; } - public string ChangeXOfY { get; set; } public string NoChanges { get; set; } public string SnappedToShotChange { get; set; } public string SnappedToFrame { get; set; } @@ -18,8 +16,6 @@ public class LanguageBeautifyTimeCodes public string MinDurationEnforced { get; set; } public string MaxDurationEnforced { get; set; } public string NoReasonNote { get; set; } - public string PreviousChange { get; set; } - public string NextChange { get; set; } public string EditProfile { get; set; } public string BatchSnapToShotChanges { get; set; } public string BatchFrameRateFromVideo { get; set; } @@ -38,12 +34,10 @@ public LanguageBeautifyTimeCodes() Title = "Beautify time codes"; SnapToFrames = "Snap to Frames"; MinDuration = "Min Duration (ms)"; - Original = "Original"; Beautified = "Beautified"; SubtitlesCount = "Subtitles"; ChangedCount = "Changed"; ShotChangesCount = "Shot changes"; - ChangeXOfY = "Change {0} of {1}"; NoChanges = "No changes — every cue is already on target"; SnappedToShotChange = "snapped to shot change"; SnappedToFrame = "snapped to frame"; @@ -51,8 +45,6 @@ public LanguageBeautifyTimeCodes() MinDurationEnforced = "min. duration enforced"; MaxDurationEnforced = "max. duration enforced"; NoReasonNote = "refined by profile rules"; - PreviousChange = "Previous change"; - NextChange = "Next change"; EditProfile = "Edit profile..."; BatchSnapToShotChanges = "Snap cues to shot changes (if available)"; BatchFrameRateFromVideo = "Use frame rate from video file with same name as subtitle file"; diff --git a/src/ui/Logic/Config/Language/Tools/LanguageImageBasedEdit.cs b/src/ui/Logic/Config/Language/Tools/LanguageImageBasedEdit.cs index 7b0ba8329d6..8a3404eed38 100644 --- a/src/ui/Logic/Config/Language/Tools/LanguageImageBasedEdit.cs +++ b/src/ui/Logic/Config/Language/Tools/LanguageImageBasedEdit.cs @@ -10,7 +10,6 @@ public class LanguageImageBasedEdit public string AdjustBrightnessDotDotDot { get; set; } public string AdjustAlphaDotDotDot { get; set; } public string CropImages { get; set; } - public string ImportTimeCodes { get; set; } public string SetText { get; set; } public string ScreenWidth { get; set; } public string ScreenHeight { get; set; } @@ -18,7 +17,6 @@ public class LanguageImageBasedEdit public string ResetToDefaults { get; set; } public string AlphaAdjustmentInfo { get; set; } public string AdjustBrightness { get; set; } - public string Brightness { get; set; } public string Contrast { get; set; } public string Gamma { get; set; } public string BrightnessAdjustmentInfo { get; set; } @@ -73,7 +71,6 @@ public LanguageImageBasedEdit() AdjustBrightnessDotDotDot = "Adjust brightness..."; AdjustAlphaDotDotDot = "Adjust alpha..."; CropImages = "Crop images"; - ImportTimeCodes = "Import time codes..."; SetText = "Set text"; ScreenWidth = "Screen width"; ScreenHeight = "Screen height"; @@ -81,7 +78,6 @@ public LanguageImageBasedEdit() ResetToDefaults = "Reset to defaults"; AlphaAdjustmentInfo = "Alpha adjustment: Add/subtract from alpha channel.\nPreview updates automatically and shows with checkered background to visualize transparency."; AdjustBrightness = "Adjust brightness"; - Brightness = "Brightness"; Contrast = "Contrast"; Gamma = "Gamma"; BrightnessAdjustmentInfo = "Adjust the sliders to modify brightness, contrast, and gamma.\nPreview updates automatically and shows the first selected subtitle."; diff --git a/src/ui/Logic/Config/Language/Tools/LanguageImproveTimeCodes.cs b/src/ui/Logic/Config/Language/Tools/LanguageImproveTimeCodes.cs index 0d763e72a33..d5d27cf5116 100644 --- a/src/ui/Logic/Config/Language/Tools/LanguageImproveTimeCodes.cs +++ b/src/ui/Logic/Config/Language/Tools/LanguageImproveTimeCodes.cs @@ -30,7 +30,6 @@ public class LanguageImproveTimeCodes public string ShowSpeechOnly { get; set; } public string ShowSpeechOnlyHint { get; set; } public string Align { get; set; } - public string Original { get; set; } public string Aligned { get; set; } public string Intro { get; set; } public string ExtractingAudio { get; set; } @@ -42,9 +41,6 @@ public class LanguageImproveTimeCodes public string ToCheckX { get; set; } public string LargeMovesToCheckX { get; set; } public string LinesToCheckX { get; set; } - public string ChangeXOfY { get; set; } - public string PreviousChange { get; set; } - public string NextChange { get; set; } public string StartShift { get; set; } public string EndShift { get; set; } public string StatusRetimed { get; set; } @@ -56,12 +52,10 @@ public class LanguageImproveTimeCodes public string StatusFailed { get; set; } public string StatusAdjustedByHand { get; set; } public string AlignedWaveformHint { get; set; } - public string PlayLine { get; set; } public string UndoAdjustment { get; set; } public string PlayPause { get; set; } public string PlayAligned { get; set; } public string PlayAlignedHint { get; set; } - public string Apply { get; set; } public string ApplyHint { get; set; } public LanguageImproveTimeCodes() @@ -94,7 +88,6 @@ public LanguageImproveTimeCodes() ShowSpeechOnly = "Show speech only"; ShowSpeechOnlyHint = "Draw the aligned waveform from the audio with music and sound effects removed; the original waveform stays as it is, for comparison. Available once the speech has been isolated - here, or with \"Show speech only\" in the main window's waveform."; Align = "Align"; - Original = "Original"; Aligned = "Aligned"; Intro = "Pick an aligner and press \"Align\" to listen for every line in the audio and tighten its time codes."; ExtractingAudio = "Extracting audio..."; @@ -106,9 +99,6 @@ public LanguageImproveTimeCodes() ToCheckX = "To check: {0}"; LargeMovesToCheckX = "{0} line(s) would move a long way on their own and are left unticked - listen to them (F5 plays the aligned line, Shift+F5 the original) and tick the ones that are right."; LinesToCheckX = "{0} line(s) are left unticked to check - listen to them (F5 plays the aligned line, Shift+F5 the original) and tick the ones that are right."; - ChangeXOfY = "Change {0} of {1}"; - PreviousChange = "Previous change"; - NextChange = "Next change"; StartShift = "Start"; EndShift = "End"; StatusRetimed = "Re-timed"; @@ -120,12 +110,10 @@ public LanguageImproveTimeCodes() StatusFailed = "Kept - aligner failed"; StatusAdjustedByHand = "Adjusted by hand"; AlignedWaveformHint = "Drag a line to move it, or drag its edges to change its start or end."; - PlayLine = "Play line"; UndoAdjustment = "Undo adjustment"; PlayPause = "Play / pause"; PlayAligned = "Aligned"; PlayAlignedHint = "Play the selected line with its aligned time codes"; - Apply = "Apply"; ApplyHint = "Untick a line to keep its original time codes. Double-click a line to play it. Lines can also be moved or resized in the aligned waveform."; } } diff --git a/src/ui/Logic/Config/Language/Tools/LanguageMergeTwoSubtitles.cs b/src/ui/Logic/Config/Language/Tools/LanguageMergeTwoSubtitles.cs index 91b4887e18e..b9940d10e5b 100644 --- a/src/ui/Logic/Config/Language/Tools/LanguageMergeTwoSubtitles.cs +++ b/src/ui/Logic/Config/Language/Tools/LanguageMergeTwoSubtitles.cs @@ -8,7 +8,6 @@ public class LanguageMergeTwoSubtitles public string LoadFromCurrentText { get; set; } public string LoadFromCurrentTranslation { get; set; } public string LoadFromFile { get; set; } - public string OutputFormat { get; set; } public string OutputFormatSubRip { get; set; } public string OutputFormatAssa { get; set; } public string Style1 { get; set; } @@ -23,7 +22,6 @@ public LanguageMergeTwoSubtitles() LoadFromCurrentText = "Load from current (text)"; LoadFromCurrentTranslation = "Load from current (translation)"; LoadFromFile = "Load from file..."; - OutputFormat = "Output format"; OutputFormatSubRip = "SubRip (.srt)"; OutputFormatAssa = "Advanced Sub Station Alpha (.ass)"; Style1 = "Style 1"; diff --git a/src/ui/Logic/Config/Language/Tools/LanguageNetflixCheckAndFix.cs b/src/ui/Logic/Config/Language/Tools/LanguageNetflixCheckAndFix.cs index 5ab383a07a1..61749a00e24 100644 --- a/src/ui/Logic/Config/Language/Tools/LanguageNetflixCheckAndFix.cs +++ b/src/ui/Logic/Config/Language/Tools/LanguageNetflixCheckAndFix.cs @@ -12,13 +12,9 @@ public class LanguageNetflixCheckAndFix public string EllipsesNotThreeDots { get; set; } public string OnlyAllowedGlyphs { get; set; } public string Italics { get; set; } - public string MaxCharsSec { get; set; } public string MaxDuration { get; set; } - public string MaxLineLength { get; set; } public string MinDuration { get; set; } - public string MaxNumberOfLines { get; set; } public string OneToTenSpellOut { get; set; } - public string ShotChanges { get; set; } public string StartNumberSpellOut { get; set; } public string TextforHiUseBrackets { get; set; } public string TwoFrameGrap { get; set; } @@ -74,13 +70,9 @@ public LanguageNetflixCheckAndFix() EllipsesNotThreeDots = "Use ellipses (not three dots)"; OnlyAllowedGlyphs = "Only allowed glyphs"; Italics = "Italics"; - MaxCharsSec = "Max chars/sec"; MaxDuration = "Max duration"; - MaxLineLength = "Max line length"; MinDuration = "Min duration"; - MaxNumberOfLines = "Max number of lines"; OneToTenSpellOut = "One to ten spell out"; - ShotChanges = "Shot changes"; StartNumberSpellOut = "Start number spell out"; TextforHiUseBrackets = "Text for HI, use brackets"; TwoFrameGrap = "Two frame gap"; diff --git a/src/ui/Logic/Config/Language/Tools/LanguageRemoveTextForHearingImpaired.cs b/src/ui/Logic/Config/Language/Tools/LanguageRemoveTextForHearingImpaired.cs index 7da17b2f968..af9595e1774 100644 --- a/src/ui/Logic/Config/Language/Tools/LanguageRemoveTextForHearingImpaired.cs +++ b/src/ui/Logic/Config/Language/Tools/LanguageRemoveTextForHearingImpaired.cs @@ -2,7 +2,6 @@ namespace Nikse.SubtitleEdit.Logic.Config.Language.Tools; public class LanguageRemoveTextForHearingImpaired { - public string Interjections { get; set; } public string SkipIfStartWith { get; set; } public string RemoveTextBetween { get; set; } public string Brackets { get; set; } @@ -22,7 +21,6 @@ public class LanguageRemoveTextForHearingImpaired public LanguageRemoveTextForHearingImpaired() { - Interjections = "Interjections"; SkipIfStartWith = "Skip if start with"; RemoveTextBetween = "Remove text between"; Brackets = "Brackets"; diff --git a/src/ui/Logic/Config/Language/Tools/LanguageTools.cs b/src/ui/Logic/Config/Language/Tools/LanguageTools.cs index 64f66e83c2f..8689e34c42a 100644 --- a/src/ui/Logic/Config/Language/Tools/LanguageTools.cs +++ b/src/ui/Logic/Config/Language/Tools/LanguageTools.cs @@ -33,8 +33,6 @@ public class LanguageTools public string PickFontNameTitle { get; set; } public string PickFontNameInstalledFonts { get; set; } public string PickFontNameCollectedFonts { get; set; } - public string ColorPickerTitle { get; set; } - public string ColorPickerBrightness { get; set; } public string FilterLayersTitle { get; set; } public string FilterLayersHideFromWaveform { get; set; } public string FilterLayersHideFromSubtitleGrid { get; set; } @@ -49,8 +47,6 @@ public LanguageTools() PickFontNameTitle = "Choose font name"; PickFontNameInstalledFonts = "Installed fonts"; PickFontNameCollectedFonts = "Collected fonts"; - ColorPickerTitle = "Choose color"; - ColorPickerBrightness = "Brightness"; FilterLayersTitle = "Filter layers for display"; FilterLayersHideFromWaveform = "Hide from waveform/spectrogram"; FilterLayersHideFromSubtitleGrid = "Hide from subtitle grid"; diff --git a/src/ui/Logic/Config/Language/Translate/LanguageTranslate.cs b/src/ui/Logic/Config/Language/Translate/LanguageTranslate.cs index bdee3bb52e1..bbb39d0fef9 100644 --- a/src/ui/Logic/Config/Language/Translate/LanguageTranslate.cs +++ b/src/ui/Logic/Config/Language/Translate/LanguageTranslate.cs @@ -9,7 +9,6 @@ public class LanguageTranslate public string BlockCopyGetFromClipboard { get; set; } public string ReCopyTextToClipboard { get; set; } public string BlockXOfY { get; set; } - public string NoTextInClipboard { get; set; } public string TextInClipboardIsSameAsSourceText { get; set; } public string LineMerge { get; set; } public string DelayInSecondsBetweenRequests { get; set; } @@ -43,7 +42,6 @@ public class LanguageTranslate public string KeepLineBreaks { get; set; } public string SamplingParameters { get; set; } public string MinusOneMeansDefault { get; set; } - public string Temperature { get; set; } public string TopP { get; set; } public string TopK { get; set; } public string RepeatPenalty { get; set; } @@ -57,7 +55,6 @@ public class LanguageTranslate public string CustomPromptHint { get; set; } public string EditTranslationHint { get; set; } public string LlamaCppDownloadEngineAndModelPrompt { get; set; } - public string LlamaCppDownloadEnginePrompt { get; set; } public string LlamaCppDownloadModelPrompt { get; set; } public LanguageTranslate() @@ -69,7 +66,6 @@ public LanguageTranslate() BlockCopyGetFromClipboard = "Get text from clipboard (from translator/AI)"; ReCopyTextToClipboard = "Re-copy text to clipboard (for translator/AI)"; BlockXOfY = "Block {0} of {1}"; - NoTextInClipboard = "No text in clipboard"; TextInClipboardIsSameAsSourceText = "The text in clipboard is the same as the source text, please try again."; LineMerge = "Line merge"; DelayInSecondsBetweenRequests = "Delay in seconds between requests"; @@ -103,7 +99,6 @@ public LanguageTranslate() KeepLineBreaks = "Keep line breaks"; SamplingParameters = "Sampling parameters"; MinusOneMeansDefault = "-1 = model/server default"; - Temperature = "Temperature"; TopP = "Top-p"; TopK = "Top-k"; RepeatPenalty = "Repeat penalty"; @@ -117,7 +112,6 @@ public LanguageTranslate() CustomPromptHint = "Custom instructions ({0} = source language, {1} = target language); empty = built-in prompt"; EditTranslationHint = "Click the selected row's translation to edit it (Enter saves, Escape cancels)"; LlamaCppDownloadEngineAndModelPrompt = "llama.cpp requires the llama-server engine and a translation model to be downloaded. Download now?"; - LlamaCppDownloadEnginePrompt = "llama.cpp requires the llama-server engine to be downloaded. Download now?"; LlamaCppDownloadModelPrompt = "llama.cpp requires the selected translation model to be downloaded. Download now?"; } } \ No newline at end of file diff --git a/src/ui/Logic/Config/Language/Video/LanguageAudioToText.cs b/src/ui/Logic/Config/Language/Video/LanguageAudioToText.cs index 48b7e0f4e2b..a7e35614840 100644 --- a/src/ui/Logic/Config/Language/Video/LanguageAudioToText.cs +++ b/src/ui/Logic/Config/Language/Video/LanguageAudioToText.cs @@ -58,11 +58,8 @@ public class LanguageAudioToText public string QualityReportLinesChecked { get; set; } public string QualityReportIssuesFound { get; set; } public string QualityReportLinesRemoved { get; set; } - public string QualityReportTooShort { get; set; } public string QualityReportTooShortHint { get; set; } - public string QualityReportTooLong { get; set; } public string QualityReportTooLongHint { get; set; } - public string QualityReportOverlap { get; set; } public string QualityReportOverlapHint { get; set; } public string QualityReportNonSpeech { get; set; } public string QualityReportNonSpeechHint { get; set; } @@ -71,7 +68,6 @@ public class LanguageAudioToText public string QualityReportRemoved { get; set; } public string QualityReportAll { get; set; } public string QualityReportIssue { get; set; } - public string QualityReportDetail { get; set; } public string QualityReportTip { get; set; } public string QualityReportDoNotShowAgain { get; set; } @@ -139,11 +135,8 @@ public LanguageAudioToText() QualityReportLinesChecked = "Lines checked"; QualityReportIssuesFound = "Issues found"; QualityReportLinesRemoved = "Lines removed"; - QualityReportTooShort = "Too short"; QualityReportTooShortHint = "Shorter than the minimum display time, or reading speed above the maximum"; - QualityReportTooLong = "Too long"; QualityReportTooLongHint = "Longer than the maximum display time, or very few words over a long time (often a hallucination)"; - QualityReportOverlap = "Overlapping"; QualityReportOverlapHint = "Ends after the next line starts"; QualityReportNonSpeech = "Non-speech"; QualityReportNonSpeechHint = "Only a sound or music description, like \"[Music]\""; @@ -152,7 +145,6 @@ public LanguageAudioToText() QualityReportRemoved = "Removed"; QualityReportAll = "All"; QualityReportIssue = "Issue"; - QualityReportDetail = "Detail"; QualityReportTip = "Lines are listed with their numbers in the new subtitle. Use \"Fix common errors\" or the post-processing settings to fix more automatically, or try another model/engine if many lines are affected."; QualityReportDoNotShowAgain = "Do not show this report again"; diff --git a/src/ui/Logic/Config/Language/Video/LanguageBackgroundMusic.cs b/src/ui/Logic/Config/Language/Video/LanguageBackgroundMusic.cs index a56a13cd4fc..89c13206221 100644 --- a/src/ui/Logic/Config/Language/Video/LanguageBackgroundMusic.cs +++ b/src/ui/Logic/Config/Language/Video/LanguageBackgroundMusic.cs @@ -4,8 +4,6 @@ public class LanguageBackgroundMusic { public string Title { get; set; } public string GenerateBackgroundMusicDotDotDot { get; set; } - public string Preset { get; set; } - public string Prompt { get; set; } public string TempoBpm { get; set; } public string GenerateSeconds { get; set; } public string Seed { get; set; } @@ -50,8 +48,6 @@ public LanguageBackgroundMusic() { Title = "Generate background music"; GenerateBackgroundMusicDotDotDot = "Generate background music..."; - Preset = "Preset"; - Prompt = "Prompt"; TempoBpm = "Tempo (BPM)"; GenerateSeconds = "Generate (seconds)"; Seed = "Seed"; diff --git a/src/ui/Logic/Config/Language/Video/LanguageBurnIn.cs b/src/ui/Logic/Config/Language/Video/LanguageBurnIn.cs index e7f418b1c43..231e61591cc 100644 --- a/src/ui/Logic/Config/Language/Video/LanguageBurnIn.cs +++ b/src/ui/Logic/Config/Language/Video/LanguageBurnIn.cs @@ -9,8 +9,6 @@ public class LanguageBurnIn public string BitRate { get; set; } public string TotalBitRateX { get; set; } public string SampleRate { get; set; } - public string Audio { get; set; } - public string Preset { get; set; } public string Tune { get; set; } public string PixelFormat { get; set; } public string Crf { get; set; } @@ -39,8 +37,6 @@ public LanguageBurnIn() BitRate = "Bit rate"; TotalBitRateX = "Total bit rate: {0}"; SampleRate = "Sample rate"; - Audio = "Audio"; - Preset = "Preset"; Tune = "Tune"; PixelFormat = "Pixel format"; Crf = "CRF"; diff --git a/src/ui/Logic/Config/Language/Video/LanguageChapters.cs b/src/ui/Logic/Config/Language/Video/LanguageChapters.cs index 8c38c2af7c3..6b5696f0747 100644 --- a/src/ui/Logic/Config/Language/Video/LanguageChapters.cs +++ b/src/ui/Logic/Config/Language/Video/LanguageChapters.cs @@ -2,9 +2,7 @@ namespace Nikse.SubtitleEdit.Logic.Config.Language; public class LanguageChapters { - public string Title { get; set; } public string ChaptersDotDotDot { get; set; } - public string Chapters { get; set; } public string SelectedChapter { get; set; } public string ChapterTitle { get; set; } public string StartTime { get; set; } @@ -25,7 +23,6 @@ public class LanguageChapters public string AdjustTimes { get; set; } public string ShiftAllTimes { get; set; } public string ShiftAllTimesDescription { get; set; } - public string ScaleTimes { get; set; } public string ScaleTimesDescription { get; set; } public string FromFrameRate { get; set; } public string ToFrameRate { get; set; } @@ -42,9 +39,7 @@ public class LanguageChapters public LanguageChapters() { - Title = "Chapters"; ChaptersDotDotDot = "Chapters..."; - Chapters = "Chapters"; SelectedChapter = "Selected chapter"; ChapterTitle = "Chapter title"; StartTime = "Start time"; @@ -65,7 +60,6 @@ public LanguageChapters() AdjustTimes = "Adjust times"; ShiftAllTimes = "Shift all times"; ShiftAllTimesDescription = "Move every chapter forward or backward by the same amount."; - ScaleTimes = "Change frame rate"; ScaleTimesDescription = "Scale every chapter time from one frame rate to another."; FromFrameRate = "From"; ToFrameRate = "To"; diff --git a/src/ui/Logic/Config/Language/Video/LanguageShotChanges.cs b/src/ui/Logic/Config/Language/Video/LanguageShotChanges.cs index c4e508ff923..b5ca1a7468e 100644 --- a/src/ui/Logic/Config/Language/Video/LanguageShotChanges.cs +++ b/src/ui/Logic/Config/Language/Video/LanguageShotChanges.cs @@ -12,7 +12,6 @@ public class LanguageShotChanges public string ShotChangesList { get; set; } public string ShotChangesClearQuestion { get; set; } public string DeleteSelectedShotChangeQuestion { get; set; } - public string ShotChanges { get; set; } public string ExportShotChanges { get; set; } public LanguageShotChanges() @@ -27,7 +26,6 @@ public LanguageShotChanges() ShotChangesList = "Shot changes list"; ShotChangesClearQuestion = "Are you sure you want to clear all shot changes?"; DeleteSelectedShotChangeQuestion = "Are you sure you want to delete the selected shot change?"; - ShotChanges = "Shot changes"; ExportShotChanges = "Export shot changes"; } } \ No newline at end of file diff --git a/src/ui/Logic/Config/Language/Video/LanguageTextToSpeech.cs b/src/ui/Logic/Config/Language/Video/LanguageTextToSpeech.cs index 53deeb6f22d..e69086cc9c8 100644 --- a/src/ui/Logic/Config/Language/Video/LanguageTextToSpeech.cs +++ b/src/ui/Logic/Config/Language/Video/LanguageTextToSpeech.cs @@ -14,7 +14,6 @@ public class LanguageTextToSpeech public string TestVoice { get; set; } public string AddAudioToVideoFile { get; set; } public string EngineAndVoice { get; set; } - public string Output { get; set; } public string XLinesFromY { get; set; } public string XLines { get; set; } public string XVoices { get; set; } @@ -39,11 +38,9 @@ public class LanguageTextToSpeech public string Instructions { get; set; } public string InstructionsHint { get; set; } public string OpenAiSpeedHint { get; set; } - public string OutputFormat { get; set; } public string OutputFormatHint { get; set; } public string RegenerateAudio { get; set; } public string AutoContinuePlaying { get; set; } - public string PlayLine { get; set; } public string FitDurationToGeneratedAudio { get; set; } public string ResetTiming { get; set; } public string AddingAudioToVideoFileDotDotDot { get; set; } @@ -106,7 +103,6 @@ public class LanguageTextToSpeech public string InstallToEngine { get; set; } public string DownloadAndInstall { get; set; } public string SelectAtLeastOneVoicePack { get; set; } - public string DownloadingXDotDotDot { get; set; } public string InstallingXDotDotDot { get; set; } public string XVoicesInstalledYSkipped { get; set; } public string UseSpeechToTextDotDotDot { get; set; } @@ -117,7 +113,6 @@ public class LanguageTextToSpeech public string AutoCastSpeakersSubtitle { get; set; } public string AutoCastFoundXSpeakersInYLines { get; set; } public string AutoCastLines { get; set; } - public string AutoCastAudio { get; set; } public string AutoCastSays { get; set; } public string AutoCastNeedsVideo { get; set; } public string AutoCastNoSpeakersFound { get; set; } @@ -149,7 +144,6 @@ public class LanguageTextToSpeech public string VoiceCloneConsentDeclined { get; set; } public string AdvancedTtsSettings { get; set; } public string AdvancedTtsAudioProcessing { get; set; } - public string AdvancedTtsOutput { get; set; } public string AdvancedTtsVoiceCloning { get; set; } public string CleanCloneReferences { get; set; } public string CleanCloneReferencesDescription { get; set; } @@ -168,7 +162,6 @@ public class LanguageTextToSpeech public string RemoveOriginalSpeechDescription { get; set; } public string RemovingOriginalSpeech { get; set; } public string RemoveOriginalSpeechFailed { get; set; } - public string SpeechRemovalModel { get; set; } public string SpeechRemovalModelDescription { get; set; } public string SpeechRemovalBestQuality { get; set; } public string SpeechRemovalFast { get; set; } @@ -262,7 +255,6 @@ public LanguageTextToSpeech() TestVoice = "Test voice"; AddAudioToVideoFile = "Add audio to video file"; EngineAndVoice = "Engine & voice"; - Output = "Output"; XLinesFromY = "{0} lines from {1}"; XLines = "{0} lines"; XVoices = "{0} voices"; @@ -287,11 +279,9 @@ public LanguageTextToSpeech() Instructions = "Instructions"; InstructionsHint = "Optional speaking style, e.g. \"Speak in a calm, warm voice\". Sent only to models that support it (not tts-1/tts-1-hd)"; OpenAiSpeedHint = "Speech speed from 0.25 to 4.0 (1.0 is normal). Not every model supports it"; - OutputFormat = "Output format"; OutputFormatHint = "Audio format requested from the server. \"Auto\" asks for MP3 and switches to PCM for models that only return PCM (e.g. Gemini TTS)"; RegenerateAudio = "Regenerate audio"; AutoContinuePlaying = "Auto-continue playing"; - PlayLine = "Play line"; FitDurationToGeneratedAudio = "Fit duration to generated audio"; ResetTiming = "Reset timing"; AddingAudioToVideoFileDotDotDot = "Adding audio to video file..."; @@ -353,7 +343,6 @@ public LanguageTextToSpeech() InstallToEngine = "Install to engine"; DownloadAndInstall = "Download and install"; SelectAtLeastOneVoicePack = "Select at least one voice pack"; - DownloadingXDotDotDot = "Downloading {0}..."; InstallingXDotDotDot = "Installing {0}..."; XVoicesInstalledYSkipped = "{0} voices installed ({1} already present)"; UseSpeechToTextDotDotDot = "Use speech-to-text..."; @@ -362,7 +351,6 @@ public LanguageTextToSpeech() AutoCastSpeakersSubtitle = "Name each speaker - the name becomes the actor in the subtitle and the name of the cloned voice. Give two speakers the same name to merge them into one voice."; AutoCastFoundXSpeakersInYLines = "{0} speakers in {1} lines"; AutoCastLines = "Lines"; - AutoCastAudio = "Audio"; AutoCastSays = "Says"; AutoCastNeedsVideo = "Finding the voices needs an open video to listen to."; AutoCastNoSpeakersFound = "No speakers were found. The transcription has to come from a speech-to-text engine that tells speakers apart, such as Crisp ASR MOSS Diarize."; @@ -390,7 +378,6 @@ public LanguageTextToSpeech() VoiceCloneConsentDeclined = "Voice cloning is not available until these terms are accepted."; AdvancedTtsSettings = "Advanced TTS settings"; AdvancedTtsAudioProcessing = "Audio processing"; - AdvancedTtsOutput = "Output"; AdvancedTtsVoiceCloning = "Voice cloning"; CleanCloneReferences = "Clean voice-clone references (Sidon)"; CleanCloneReferencesDescription = "Removes music, noise and reverb from the audio a voice is cloned from: clips taken from the video and recordings imported in the voice manager. Uses the Sidon speech restoration model on audio.cpp (940 MB download). The cloned voice no longer copies the background, but very quiet or overlapping speech can come out muffled."; @@ -409,7 +396,6 @@ public LanguageTextToSpeech() RemoveOriginalSpeechDescription = "Keeps the music and sound effects of the original video but removes its speech, and mixes the new speech over that. Takes about as long as the video itself with a GPU, and many times longer without one. With audio ducking on, its volume applies to the music and effects. Only applies when the speech is added to the video file."; RemovingOriginalSpeech = "Removing original speech..."; RemoveOriginalSpeechFailed = "Could not remove the original speech - the speech was added to the video without it."; - SpeechRemovalModel = "Model"; SpeechRemovalModelDescription = "Best quality keeps the most of the music and sound effects. Fast is about three times quicker, with a little more of the music lost."; SpeechRemovalBestQuality = "Best quality (Mel-Band RoFormer)"; SpeechRemovalFast = "Fast (HTDemucs)"; diff --git a/src/ui/Logic/Config/Language/Video/LanguageVideo.cs b/src/ui/Logic/Config/Language/Video/LanguageVideo.cs index 3112c8e86a0..100c780075d 100644 --- a/src/ui/Logic/Config/Language/Video/LanguageVideo.cs +++ b/src/ui/Logic/Config/Language/Video/LanguageVideo.cs @@ -110,13 +110,11 @@ public class LanguageVideo public string CutVideoTransitionBlur { get; set; } public string CutVideoSegments { get; set; } public string CutVideoTransitions { get; set; } - public string CutVideoOutput { get; set; } public string CutVideoCutType { get; set; } public string ImportCurrentSubtitle { get; set; } public string AddRemoveEmbeddedSubtitlesTitle { get; set; } public string AddRemoveEmbeddedSubtitlesMp4Title { get; set; } public string AddCurrentSubtitle { get; set; } - public string EmbeddedTrackNoSubtitlesFoundTitle { get; set; } public string EmbeddedTrackNoSubtitlesFoundMessage { get; set; } public string EmbeddedTrackNoTracksTitle { get; set; } public string EmbeddedTrackNoTracksMessage { get; set; } @@ -293,13 +291,11 @@ public LanguageVideo() CutVideoTransitionBlur = "Blur"; CutVideoSegments = "Segments"; CutVideoTransitions = "Transitions"; - CutVideoOutput = "Output"; CutVideoCutType = "Cut type"; ImportCurrentSubtitle = "Import current subtitle"; AddRemoveEmbeddedSubtitlesTitle = "Add/remove embedded subtitles"; AddRemoveEmbeddedSubtitlesMp4Title = "Add/remove embedded subtitles (MP4)"; AddCurrentSubtitle = "Add current subtitle"; - EmbeddedTrackNoSubtitlesFoundTitle = "No subtitles found"; EmbeddedTrackNoSubtitlesFoundMessage = "The selected subtitle file does not contain any subtitles."; EmbeddedTrackNoTracksTitle = "No tracks added"; EmbeddedTrackNoTracksMessage = "Add one or more subtitle tracks, or load a video that already has embedded subtitles."; diff --git a/src/ui/Logic/Config/Language/Video/LanguageVideoOcr.cs b/src/ui/Logic/Config/Language/Video/LanguageVideoOcr.cs index 006b889c366..9e918965cb3 100644 --- a/src/ui/Logic/Config/Language/Video/LanguageVideoOcr.cs +++ b/src/ui/Logic/Config/Language/Video/LanguageVideoOcr.cs @@ -20,13 +20,10 @@ public class LanguageVideoOcr public string StartOcr { get; set; } public string ExtractingFramesX { get; set; } public string AnalyzingFramesXY { get; set; } - public string RunningOcrXY { get; set; } - public string NoLinesFoundTitle { get; set; } public string NoLinesFoundMessage { get; set; } public string OcrStoppedEarlyMessage { get; set; } public string LinesFoundX { get; set; } public string RefiningTimingXY { get; set; } - public string FixOcrErrors { get; set; } public string EditLineX { get; set; } public string DownloadDictionaryHint { get; set; } public string FramesPerSecondHint { get; set; } @@ -72,13 +69,10 @@ public LanguageVideoOcr() StartOcr = "Start OCR"; ExtractingFramesX = "Extracting image frames... {0}%"; AnalyzingFramesXY = "Analyzing frames... {0}/{1}"; - RunningOcrXY = "Running OCR... {0}/{1}"; - NoLinesFoundTitle = "No subtitles found"; NoLinesFoundMessage = "No text was found in the scan area - try adjusting the scan area, engine, or brightness minimum."; OcrStoppedEarlyMessage = "The OCR engine stopped before all frames were read, so the lines found so far are kept and the rest are missing.{0}{0}{1}"; LinesFoundX = "{0} lines found"; RefiningTimingXY = "Refining timing... {0}/{1}"; - FixOcrErrors = "Fix OCR errors"; EditLineX = "Edit line {0}"; DownloadDictionaryHint = "Download a spell check dictionary..."; FramesPerSecondHint = "How many frames per second are scanned. 5 covers normal subtitles; higher catches very short lines and tightens grouping, but costs more OCR time."; diff --git a/src/ui/Logic/NetflixQualityCheck/NetflixQualityController.cs b/src/ui/Logic/NetflixQualityCheck/NetflixQualityController.cs index 8d488629798..6e7148b8d0e 100644 --- a/src/ui/Logic/NetflixQualityCheck/NetflixQualityController.cs +++ b/src/ui/Logic/NetflixQualityCheck/NetflixQualityController.cs @@ -299,13 +299,13 @@ public static List GetAllCheckers() new NetflixCheckEllipsesNotThreeDots(Se.Language.Tools.NetflixCheckAndFix.EllipsesNotThreeDots), new NetflixCheckGlyph(Se.Language.Tools.NetflixCheckAndFix.OnlyAllowedGlyphs), new NetflixCheckItalics(Se.Language.Tools.NetflixCheckAndFix.Italics), - new NetflixCheckMaxCps(Se.Language.Tools.NetflixCheckAndFix.MaxCharsSec), + new NetflixCheckMaxCps(Se.Language.General.MaxCharsPerSec), new NetflixCheckMaxDuration(Se.Language.Tools.NetflixCheckAndFix.MaxDuration), - new NetflixCheckMaxLineLength(Se.Language.Tools.NetflixCheckAndFix.MaxLineLength), + new NetflixCheckMaxLineLength(Se.Language.General.MaxLineLength), new NetflixCheckMinDuration(Se.Language.Tools.NetflixCheckAndFix.MinDuration), - new NetflixCheckNumberOfLines(Se.Language.Tools.NetflixCheckAndFix.MaxNumberOfLines), + new NetflixCheckNumberOfLines(Se.Language.General.MaxNumberOfLines), new NetflixCheckNumbersOneToTenSpellOut(Se.Language.Tools.NetflixCheckAndFix.OneToTenSpellOut), - new NetflixCheckShotChange(Se.Language.Tools.NetflixCheckAndFix.ShotChanges), + new NetflixCheckShotChange(Se.Language.General.ShotChanges), new NetflixCheckStartNumberSpellOut(Se.Language.Tools.NetflixCheckAndFix.StartNumberSpellOut), new NetflixCheckTextForHiUseBrackets(Se.Language.Tools.NetflixCheckAndFix.TextforHiUseBrackets), new NetflixCheckTimedTextFrameRate(Se.Language.General.FrameRate), diff --git a/src/ui/Logic/ShortcutsMain.cs b/src/ui/Logic/ShortcutsMain.cs index 4a9c3b9b093..d265c3c3d18 100644 --- a/src/ui/Logic/ShortcutsMain.cs +++ b/src/ui/Logic/ShortcutsMain.cs @@ -484,7 +484,7 @@ private static Dictionary BuildCommandTranslations() { nameof(MainViewModel.ToggleShotChangesAtVideoPositionCommand), Se.Language.General.ToggleShotChangesAtVideoPosition }, { nameof(MainViewModel.GoToPreviousShotChangeCommand), Se.Language.General.GoToPreviousShotChange }, { nameof(MainViewModel.GoToNextShotChangeCommand), Se.Language.General.GoToNextShotChange }, - { nameof(MainViewModel.ShowVideoChaptersCommand), Se.Language.Video.Chapters.Title }, + { nameof(MainViewModel.ShowVideoChaptersCommand), Se.Language.General.Chapters }, { nameof(MainViewModel.ToggleChapterAtVideoPositionCommand), Se.Language.Video.Chapters.ToggleChapterAtVideoPosition }, { nameof(MainViewModel.GoToPreviousChapterCommand), Se.Language.Video.Chapters.GoToPreviousChapter }, { nameof(MainViewModel.GoToNextChapterCommand), Se.Language.Video.Chapters.GoToNextChapter }, diff --git a/tests/UI/Features/Assa/AssaDrawBackgroundTests.cs b/tests/UI/Features/Assa/AssaDrawBackgroundTests.cs index f4508347c2b..87eddfa94a3 100644 --- a/tests/UI/Features/Assa/AssaDrawBackgroundTests.cs +++ b/tests/UI/Features/Assa/AssaDrawBackgroundTests.cs @@ -135,7 +135,7 @@ public void BackgroundButton_OpensMenu_AndImageItemSetsBackground() Dispatcher.UIThread.RunJobs(); try { - var button = window.GetVisualDescendants().OfType internal (DrawAnimationState State, float AnchorX, float AnchorY) GetCanvasAnimationTransform(DrawShape styleShape, double timeMs) { - if (styleShape.Animation is not { IsNone: false } animation) + if (GetLayerAnimation(styleShape.Layer) is not { } animation) { return (DrawAnimationState.Rest, 0, 0); } diff --git a/src/ui/Features/Assa/AssaDraw/AssaDrawViewModel.cs b/src/ui/Features/Assa/AssaDraw/AssaDrawViewModel.cs index a94e93a851d..0400d7ac84d 100644 --- a/src/ui/Features/Assa/AssaDraw/AssaDrawViewModel.cs +++ b/src/ui/Features/Assa/AssaDraw/AssaDrawViewModel.cs @@ -195,14 +195,20 @@ public void Initialize(Subtitle subtitle, List selectedLi { _subtitle = subtitle; _videoFileName = videoFileName; - if (selectedLines.Count > 0 && selectedLines[0].Duration.TotalMilliseconds >= 1) + // The main window writes the result into the selected drawing rows, which keep their own + // times, so the animation must be timed by the first drawing row - a 2 s dialogue line + // selected above a 5 s drawing made the out-animation fire after 2 s. Without a drawing + // row the new rows get the times of the last selected line. + var timingLine = selectedLines.FirstOrDefault(p => MainViewModel.IsAssaDrawingText(p.Text)) ?? + selectedLines.LastOrDefault(); + if (timingLine != null && timingLine.Duration.TotalMilliseconds >= 1) { - LineDurationMs = (int)Math.Round(selectedLines[0].Duration.TotalMilliseconds); + LineDurationMs = (int)Math.Round(timingLine.Duration.TotalMilliseconds); } // Same frame Set position uses: the paused player position when it is inside the line - _videoSeconds = selectedLines.Count > 0 - ? AssaSetPositionViewModel.GetScreenshotSeconds(selectedLines[0], videoPositionSeconds) + _videoSeconds = timingLine != null + ? AssaSetPositionViewModel.GetScreenshotSeconds(timingLine, videoPositionSeconds) : videoPositionSeconds ?? 0; // Drawing coordinates live in script (PlayRes) space, so the script resolution wins. The main @@ -1582,9 +1588,10 @@ private async Task ChangeLayer() var newLayer = vm.Layer; SaveUndo(); - // Get color from existing shapes in the new layer (if any) + // Get color and animation from existing shapes in the new layer (if any) var existingShapeInNewLayer = Shapes.FirstOrDefault(s => s.Layer == newLayer); var newColor = existingShapeInNewLayer?.ForeColor; + var newAnimation = existingShapeInNewLayer != null ? GetLayerAnimation(newLayer) : null; if (SelectedTreeItem.IsLayer) { @@ -1595,6 +1602,7 @@ private async Task ChangeLayer() if (newColor.HasValue) { shape.ForeColor = newColor.Value; + shape.Animation = newAnimation?.Clone(); } } } @@ -1605,6 +1613,7 @@ private async Task ChangeLayer() if (newColor.HasValue) { SelectedTreeItem.Shape.ForeColor = newColor.Value; + SelectedTreeItem.Shape.Animation = newAnimation?.Clone(); } } @@ -1849,14 +1858,16 @@ private void MoveShapeToLayer(int layer) SaveUndo(); - // Same rule as Change layer: a shape joining a layer takes that layer's color + // Same rule as Change layer: a shape joining a layer takes that layer's color and animation var existing = Shapes.FirstOrDefault(s => s.Layer == layer); + var layerAnimation = existing != null ? GetLayerAnimation(layer) : null; foreach (var shape in shapes) { shape.Layer = layer; if (existing != null) { shape.ForeColor = existing.ForeColor; + shape.Animation = layerAnimation?.Clone(); } } @@ -1884,6 +1895,13 @@ private void DeletePoint() return; } + // RemovePoint refuses a point that isn't in the shape - check first, so no empty undo step + // is saved (it also cleared the redo stack) + if (!shape.Points.Contains(point)) + { + return; + } + SaveUndo(); if (!shape.RemovePoint(point)) { @@ -2767,7 +2785,7 @@ private Subtitle GenerateSubtitle(bool includeAll = false, bool hiddenAsComments sbErase.Clear(); var firstShape = GetLayerStyleShape(layer); - var animation = withAnimation ? GetLineAnimation(firstShape) : null; + var animation = withAnimation && firstShape != null ? GetLayerAnimation(layerKey) : null; var anchor = animation != null ? GetAnimationAnchor(firstShape!) : default; // Collect draw shapes (normal shapes) - relative to the anchor when animated @@ -2777,7 +2795,11 @@ private Subtitle GenerateSubtitle(bool includeAll = false, bool hiddenAsComments sbDraw.Append(" "); } - // Collect erase shapes (iclip shapes) + // Collect erase shapes (iclip shapes). These stay in absolute coordinates even when the + // layer is animated: libass never moves, scales or turns a vector \iclip with \pos/\move, + // \fscx or \frz, and \t can't animate one. They erase the right spot when the animation + // is at rest (and always for a fade or blink), so the clip is kept rather than dropped; the + // canvas animation preview shows them standing still the same way. foreach (var shape in layer.Where(p => p.IsEraser && !p.IsTextBox)) { sbErase.Append(shape.ToAssa()); @@ -2849,7 +2871,7 @@ private Subtitle GenerateSubtitle(bool includeAll = false, bool hiddenAsComments // layer on top, so the text shows over its bubble and under the layers above foreach (var shape in layer.Where(s => s.HasText && s.Points.Count > 0)) { - var textAnimation = withAnimation ? GetLineAnimation(shape) : null; + var textAnimation = withAnimation ? GetLayerAnimation(layerKey) : null; var textLine = AssaDrawTextLine.Format(shape); if (textAnimation != null) { @@ -2876,8 +2898,13 @@ private Subtitle GenerateSubtitle(bool includeAll = false, bool hiddenAsComments /// public int LineDurationMs { get; set; } = 2000; - private static DrawAnimation? GetLineAnimation(DrawShape? shape) => - shape?.Animation is { IsNone: false } animation ? animation : null; + /// + /// A layer is one line, so it has one animation: the first one set on any of its shapes. The + /// output, the canvas preview and the animation panel all read it this way - the output used + /// the first draw shape's, so a shape without one at the top of the layer dropped the effect. + /// + internal DrawAnimation? GetLayerAnimation(int layer) => + Shapes.Where(s => s.Layer == layer).Select(s => s.Animation).FirstOrDefault(a => a is { IsNone: false }); /// /// What an animation scales and turns around, and what it slides out of the frame: the shape's @@ -4435,7 +4462,8 @@ private void UpdateUndoState() [RelayCommand] private void SelectAllShapes() { - SelectedShapes = Shapes.ToList(); + // Like the rubber band: hidden shapes can't be seen, so they aren't moved or deleted with the rest + SelectedShapes = Shapes.Where(s => !s.Hidden).ToList(); Canvas?.InvalidateVisual(); } diff --git a/src/ui/Features/Assa/AssaImageColorPicker/AssaImageColorPickerViewModel.cs b/src/ui/Features/Assa/AssaImageColorPicker/AssaImageColorPickerViewModel.cs index 672095b95c2..9edd435ec1a 100644 --- a/src/ui/Features/Assa/AssaImageColorPicker/AssaImageColorPickerViewModel.cs +++ b/src/ui/Features/Assa/AssaImageColorPicker/AssaImageColorPickerViewModel.cs @@ -83,6 +83,19 @@ partial void OnScreenshotChanged(Bitmap value) _screenshotPixels = null; } + /// + /// The Skia copy of the frame and the magnifier bitmap are only freed when replaced - free + /// them when the window closes too. + /// + public void OnClosed() + { + _screenshotPixels?.Dispose(); + _screenshotPixels = null; + var magnifier = Magnifier; + Magnifier = null; + magnifier?.Dispose(); + } + /// /// A Skia copy of the frame for the magnifier (made once, not on every pointer move). /// diff --git a/src/ui/Features/Assa/AssaImageColorPicker/AssaImageColorPickerWindow.cs b/src/ui/Features/Assa/AssaImageColorPicker/AssaImageColorPickerWindow.cs index ef04ab53e54..eecf01fc401 100644 --- a/src/ui/Features/Assa/AssaImageColorPicker/AssaImageColorPickerWindow.cs +++ b/src/ui/Features/Assa/AssaImageColorPicker/AssaImageColorPickerWindow.cs @@ -49,6 +49,7 @@ public AssaImageColorPickerWindow(AssaImageColorPickerViewModel vm) UiUtil.FocusOnFirstActivation(this, buttonDone); KeyDown += vm.KeyDown; + Closed += (_, _) => vm.OnClosed(); } private static Control MakeFrame(AssaImageColorPickerViewModel vm) diff --git a/src/ui/Features/Assa/AssaProgressBar/AssaProgressBarViewModel.cs b/src/ui/Features/Assa/AssaProgressBar/AssaProgressBarViewModel.cs index 94e628c36d0..dc65cc4a8e0 100644 --- a/src/ui/Features/Assa/AssaProgressBar/AssaProgressBarViewModel.cs +++ b/src/ui/Features/Assa/AssaProgressBar/AssaProgressBarViewModel.cs @@ -515,21 +515,18 @@ private int GetTextPosition(int position, int chapterIndex, double percentOfDura positionNextX = (int)Math.Round(_videoWidth * percentNext / 100.0); } - if (CornerStyleIndex == 1 && chapterIndex == Chapters.Count - 1) - { - return (int)Math.Round((double)(positionNextX - BarHeight + XAdjustment)); - } - - return (int)Math.Round(positionNextX - 10.0 + XAdjustment); - } - - // Left - if (CornerStyleIndex == 1 && chapterIndex == 0) - { - return (int)Math.Round(position + (double)BarHeight + SplitterWidth + XAdjustment); + // The last chapter's text keeps clear of the bar's right end where its shape narrows + var rightInset = chapterIndex == Chapters.Count - 1 + ? Math.Max(10.0, ProgressBarStyles.GetEndCapInset(CornerStyleIndex, BarHeight).Right) + : 10.0; + return (int)Math.Round(positionNextX - rightInset + XAdjustment); } - return (int)Math.Round(position + 8.0 + SplitterWidth + XAdjustment); + // Left - the first chapter's text keeps clear of the bar's left end where its shape narrows + var leftInset = chapterIndex == 0 + ? Math.Max(8.0, ProgressBarStyles.GetEndCapInset(CornerStyleIndex, BarHeight).Left) + : 8.0; + return (int)Math.Round(position + leftInset + SplitterWidth + XAdjustment); } private static string ToAssaColor(Color color) diff --git a/src/ui/Features/Assa/AssaProgressBar/ProgressBarStyles.cs b/src/ui/Features/Assa/AssaProgressBar/ProgressBarStyles.cs index 987d51ec08d..8768b05d864 100644 --- a/src/ui/Features/Assa/AssaProgressBar/ProgressBarStyles.cs +++ b/src/ui/Features/Assa/AssaProgressBar/ProgressBarStyles.cs @@ -75,6 +75,25 @@ public static string Drawing(int style, int width, int height) }; } + /// + /// How far chapter text keeps from the bar's left and right ends for a style whose ends narrow + /// (rounded, cut, pointed, slanted) - 0 for a square end, where the normal small margin is enough. + /// Only rounded corners had an inset, so text on the other shaped ends ran over the edge. + /// + public static (double Left, double Right) GetEndCapInset(int style, int height) + { + var h = (double)Math.Max(2, height); + return style switch + { + Rounded or 6 or 7 => (h, h), // rounded corners, rounded segments, dots + 2 => (h / 3.0, h / 3.0), // cut corners + 3 => (0.0, h / 2.0), // arrow: pointed right end + 4 => (h * 0.6, h * 0.6), // slanted + 8 or 9 => (h / 2.0, h / 2.0), // diamonds, chevrons: pointed pieces + _ => (0.0, 0.0), + }; + } + private static string N(double value) => Math.Round(value, 1).ToString("0.#", CultureInfo.InvariantCulture); private static string Rect(double left, double top, double right, double bottom) => diff --git a/src/ui/Features/Assa/AssaSetPosition/AssaSetPositionViewModel.cs b/src/ui/Features/Assa/AssaSetPosition/AssaSetPositionViewModel.cs index 8e6300542ad..ef86f764cbc 100644 --- a/src/ui/Features/Assa/AssaSetPosition/AssaSetPositionViewModel.cs +++ b/src/ui/Features/Assa/AssaSetPosition/AssaSetPositionViewModel.cs @@ -218,6 +218,11 @@ private void MoveAnchorTo(int? x, int? y) { ScreenshotY += (int)Math.Round((y.Value - ResultY) * (double)_renderHeight / Math.Max(1, SourceHeight)); } + + // The text box moves in whole render pixels, so a typed script-space value can round to no + // move at all (PlayRes larger than the video) - the field then showed 961 while OK wrote + // 960. Show the value that will be written. + UpdatePositionFields(); } partial void OnShowSafeAreaChanged(bool value) => Se.Settings.Assa.SetPositionShowSafeArea = value; diff --git a/src/ui/Features/Assa/AssaStylesViewModel.cs b/src/ui/Features/Assa/AssaStylesViewModel.cs index 43c5e7697d4..464778d7cd7 100644 --- a/src/ui/Features/Assa/AssaStylesViewModel.cs +++ b/src/ui/Features/Assa/AssaStylesViewModel.cs @@ -956,9 +956,10 @@ private void StorageDuplicate() return; } - var category = CategoryForNewStyle(); foreach (var selectedStyle in selectedItems) { + // A duplicate stays in its source style's category + var category = selectedStyle.Category ?? string.Empty; var name = selectedStyle.Name + " - " + Se.Language.General.Copy; if (StorageStylesInCategory(category).Any(p => p.Name.Equals(name, StringComparison.OrdinalIgnoreCase))) { @@ -1068,8 +1069,21 @@ private bool IsStyleInSelectedCategory(StyleDisplay style) => IsSameCategoryLabel(SelectedStorageCategory, AllCategoriesLabel) || IsSameCategoryLabel(StoredToCategoryLabel(style.Category), SelectedStorageCategory); + /// + /// Category a new, copied or imported saved style goes to: the filtered category, or - as the + /// window has no category picker and always shows all saved styles - the category of the + /// selected saved style, else Default. Everything landed in Default before. + /// private string CategoryForNewStyle() - => SelectedStorageCategory == AllCategoriesLabel ? string.Empty : CategoryLabelToStored(SelectedStorageCategory); + { + if (!IsSameCategoryLabel(SelectedStorageCategory, AllCategoriesLabel)) + { + return CategoryLabelToStored(SelectedStorageCategory); + } + + var selected = SelectedStorageStyle ?? StorageStyleGrid?.SelectedItem as StyleDisplay; + return selected?.Category ?? string.Empty; + } // Categories are independent style sets, so a style name only has to be unique within its // category - the same name in another category is not a clash (#15332). @@ -1257,9 +1271,13 @@ private async Task MoveToCategory() MoveStylesToCategory(selectedItems, stored); + // The filter is left alone (the window always shows all saved styles, and switching it + // rebuilt the list twice and dropped the selection) - the category column updates itself RebuildStorageCategories(); - SelectedStorageCategory = StorageCategories.FirstOrDefault(c => IsSameCategoryLabel(c, label)) ?? SelectedStorageCategory; - RefreshStorageStylesView(); + if (!IsSameCategoryLabel(SelectedStorageCategory, AllCategoriesLabel)) + { + RefreshStorageStylesView(); + } } /// diff --git a/src/ui/Features/Files/ExportImageBased/ExportImageBasedViewModel.cs b/src/ui/Features/Files/ExportImageBased/ExportImageBasedViewModel.cs index d7f012eb7ef..22c919c790e 100644 --- a/src/ui/Features/Files/ExportImageBased/ExportImageBasedViewModel.cs +++ b/src/ui/Features/Files/ExportImageBased/ExportImageBasedViewModel.cs @@ -460,9 +460,20 @@ private async Task Export() { await ExportRun(fileOrFolderName, cancellationToken); } + catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested) + { + // cancelled by the user or a new run + } catch (Exception exception) { Se.LogError(exception); + if (Window != null) + { + // e.g. a D-Cinema MXF with more images than one track file can hold + await MessageBox.Show(Window, Se.Language.General.Error, + string.Format(Se.Language.General.CouldNotSaveFileXErrorY, fileOrFolderName, exception.Message), + MessageBoxButtons.OK, MessageBoxIcon.Error); + } } finally { diff --git a/src/ui/Features/Files/FormatProperties/DcinemaSmpteProperties/DCinemaSmptePropertiesViewModel.cs b/src/ui/Features/Files/FormatProperties/DcinemaSmpteProperties/DCinemaSmptePropertiesViewModel.cs index 83bdadffd27..84d3f3f8e90 100644 --- a/src/ui/Features/Files/FormatProperties/DcinemaSmpteProperties/DCinemaSmptePropertiesViewModel.cs +++ b/src/ui/Features/Files/FormatProperties/DcinemaSmpteProperties/DCinemaSmptePropertiesViewModel.cs @@ -465,7 +465,7 @@ private async Task Ok() return; } - if (!string.IsNullOrWhiteSpace(ReelDuration) && !TimedTextMxfWriter.TryParseTimeCode(ReelDuration, 1, out _)) + if (!string.IsNullOrWhiteSpace(ReelDuration) && !TimedTextMxfWriter.TryParseTimeCode(ReelDuration, GetEditRateFrames(), out _)) { await MessageBox.Show(Window!, Se.Language.General.Error, Se.Language.File.PropertiesDCinema.ReelDurationInvalid, MessageBoxButtons.OK, MessageBoxIcon.Error); return; @@ -519,7 +519,15 @@ internal void InitializeMxfExport(Subtitle subtitle, TimeSpan? videoDuration) InitializeCore(subtitle, null); if (videoDuration is { TotalMilliseconds: > 0 } duration) { - ReelDuration = TimedTextMxfWriter.ToTimeCode(duration, GetEditRateFrames()); + // The picture reel has duration x the video's real rate frames (1 h of 23.976 = 86314, + // not 86400) - count those, then express them as edit rate time code. + var editRateFrames = GetEditRateFrames(); + var videoFrameRate = Se.Settings.General.CurrentFrameRate; + var frameRate = videoFrameRate > 0 && (int)Math.Round(videoFrameRate, MidpointRounding.AwayFromZero) == editRateFrames + ? videoFrameRate + : editRateFrames; + var frames = (long)Math.Round(duration.TotalSeconds * frameRate, MidpointRounding.AwayFromZero); + ReelDuration = TimedTextMxfWriter.ToTimeCode(frames, editRateFrames); } WindowTitle = string.Format(Se.Language.File.XProperties, Se.Language.File.Export.TitleExportDCinemaSmpte2014Mxf); diff --git a/src/ui/Features/Help/CheckForUpdates/AutoUpdateService.cs b/src/ui/Features/Help/CheckForUpdates/AutoUpdateService.cs index f509d30e87b..deaa66b006f 100644 --- a/src/ui/Features/Help/CheckForUpdates/AutoUpdateService.cs +++ b/src/ui/Features/Help/CheckForUpdates/AutoUpdateService.cs @@ -43,6 +43,9 @@ public interface IAutoUpdateService { AutoUpdateKind Kind { get; } + /// True once the install helper has been started in this session - it is already waiting for exit. + bool IsInstallPending { get; } + /// Downloads the release file for this platform and verifies its SHA-256; returns the local path. Task DownloadAsync(string version, IProgress? progress, CancellationToken cancellationToken); @@ -74,6 +77,10 @@ public class AutoUpdateService : IAutoUpdateService private readonly HttpClient _httpClient; private readonly Lazy _kind; + // Static: the dialog (and its view model) is created anew each time, but only one helper + // may wait for this process - a second one would install the update twice. + private static volatile bool _helperStarted; + public AutoUpdateService(HttpClient httpClient) { _httpClient = httpClient; @@ -82,6 +89,8 @@ public AutoUpdateService(HttpClient httpClient) public AutoUpdateKind Kind => _kind.Value; + public bool IsInstallPending => _helperStarted; + private static AutoUpdateKind DetectCurrentKind() { try @@ -203,13 +212,8 @@ public async Task DownloadAsync(string version, IProgress? progre var asset = await GetReleaseAssetAsync(version, assetName, cancellationToken); - var folder = Path.Combine(Path.GetTempPath(), "SubtitleEdit-update"); - Directory.CreateDirectory(folder); + var folder = CreateStagingFolder(); var path = Path.Combine(folder, asset.Name); - if (File.Exists(path)) - { - File.Delete(path); - } await DownloadHelper.DownloadFileAsync(_httpClient, asset.DownloadUrl, path, progress, cancellationToken); @@ -236,6 +240,42 @@ public async Task DownloadAsync(string version, IProgress? progre return path; } + /// + /// A fresh, per-user staging folder. Not a fixed name in the shared temp folder: on Linux + /// that is /tmp, where another user could pre-create the folder and swap files in before + /// the helper runs at exit. The per-user data folder is owned by the user; the staging + /// folder is also created owner-only off Windows. + /// + private static string CreateStagingFolder() + { + var parent = OperatingSystem.IsWindows() ? Path.GetTempPath() : Se.DataFolder; + var folder = Path.Combine(parent, "SubtitleEdit-update"); + if (Directory.Exists(folder)) + { + try + { + Directory.Delete(folder, true); + } + catch (Exception exception) + { + // e.g. a file still locked by a previous attempt - stage in a new folder instead + Se.LogError(exception, "AutoUpdateService.CreateStagingFolder"); + folder += "-" + Guid.NewGuid().ToString("N"); + } + } + + if (OperatingSystem.IsWindows()) + { + Directory.CreateDirectory(folder); + } + else + { + Directory.CreateDirectory(folder, UnixFileMode.UserRead | UnixFileMode.UserWrite | UnixFileMode.UserExecute); + } + + return folder; + } + private async Task GetReleaseAssetAsync(string version, string assetName, CancellationToken cancellationToken) { using var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); @@ -446,6 +486,11 @@ private static Task RunAsync(string fileName, CancellationToken cancellationToke public void StartInstallAfterExit(string downloadedFile) { + if (_helperStarted) + { + return; // a helper already waits for this process to exit + } + var processId = Environment.ProcessId; var exeFolder = AppContext.BaseDirectory.TrimEnd(Path.DirectorySeparatorChar); var scriptFolder = Path.GetDirectoryName(downloadedFile)!; @@ -472,6 +517,7 @@ public void StartInstallAfterExit(string downloadedFile) } Process.Start(startInfo); + _helperStarted = true; break; } case AutoUpdateKind.LinuxTarball: @@ -489,6 +535,7 @@ public void StartInstallAfterExit(string downloadedFile) } Process.Start(startInfo); + _helperStarted = true; break; } case AutoUpdateKind.MacAppBundle: @@ -505,6 +552,7 @@ public void StartInstallAfterExit(string downloadedFile) } Process.Start(startInfo); + _helperStarted = true; break; } default: @@ -529,12 +577,12 @@ public void StartInstallAfterExit(string downloadedFile) /// /// Waits for Subtitle Edit to exit, extracts the tarball over the install folder and starts the - /// app again. Ignores SIGHUP so closing the terminal Subtitle Edit was started from does not - /// stop it halfway. + /// app again. Ignores SIGHUP and SIGTERM so closing the terminal Subtitle Edit was started + /// from, or logging out, does not stop the extraction halfway and leave a half-updated install. /// internal const string LinuxScript = """ #!/bin/sh - trap '' HUP + trap '' HUP TERM pid="$1"; archive="$2"; dir="$3"; app="$4" while kill -0 "$pid" 2>/dev/null; do sleep 1; done if tar -xzf "$archive" -C "$dir"; then rm -f "$archive"; fi diff --git a/src/ui/Features/Help/CheckForUpdates/CheckForUpdatesViewModel.cs b/src/ui/Features/Help/CheckForUpdates/CheckForUpdatesViewModel.cs index ad84a72693c..0654382fb18 100644 --- a/src/ui/Features/Help/CheckForUpdates/CheckForUpdatesViewModel.cs +++ b/src/ui/Features/Help/CheckForUpdates/CheckForUpdatesViewModel.cs @@ -63,7 +63,14 @@ public async Task CheckForUpdates() return; } - if (result.IsNewVersionAvailable) + if (result.IsNewVersionAvailable && _autoUpdateService.IsInstallPending) + { + // Downloaded and handed to the helper earlier in this session (the user stayed + // at the unsaved-changes prompt) - it installs when Subtitle Edit closes. + StatusText = Se.Language.Help.CheckForUpdatesInstallsWhenClosed; + IsDownloadLinkVisible = true; + } + else if (result.IsNewVersionAvailable) { StatusText = string.Format(Se.Language.Help.CheckForUpdatesNewVersionAvailable, result.LatestVersion); IsDownloadLinkVisible = true; @@ -100,6 +107,13 @@ private async Task InstallUpdate() return; } + if (_autoUpdateService.IsInstallPending) + { + IsInstallVisible = false; + StatusText = Se.Language.Help.CheckForUpdatesInstallsWhenClosed; + return; + } + IsInstallVisible = false; IsDownloading = true; DownloadProgress = 0; @@ -112,11 +126,11 @@ private async Task InstallUpdate() var file = await _autoUpdateService.DownloadAsync(_latestVersion, progress, _downloadCancellation.Token); _autoUpdateService.StartInstallAfterExit(file); } - catch (OperationCanceledException) + catch (OperationCanceledException) when (_downloadCancellation?.IsCancellationRequested == true) { return; // dialog closed while downloading } - catch (Exception exception) + catch (Exception exception) // also timeouts, which surface as OperationCanceledException { Se.LogError(exception, "Automatic update"); IsDownloading = false; diff --git a/src/ui/Features/Main/MainViewModel.cs b/src/ui/Features/Main/MainViewModel.cs index 0687ec57b32..b2721791a8a 100644 --- a/src/ui/Features/Main/MainViewModel.cs +++ b/src/ui/Features/Main/MainViewModel.cs @@ -1635,7 +1635,12 @@ private void PlayNext() return; } - var next = Subtitles.FirstOrDefault(s => s.StartTime.TotalSeconds >= vp.Position); + // Time order, not list order (the grid may be sorted by text), and strictly after the + // position: after a seek onto a subtitle's start, ">=" handed back that same subtitle. + var position = vp.Position; + var next = Subtitles + .OrderBy(s => s.StartTime.TotalMilliseconds) + .FirstOrDefault(s => s.StartTime.TotalSeconds > position + PlayPreviousNextToleranceSeconds); if (next == null) { return; @@ -1712,18 +1717,35 @@ private void PlayPrevious() return; } - var previous = Subtitles.LastOrDefault(s => s.StartTime.TotalSeconds < vp.Position); - if (previous == null) + // Like a media player's "previous track": well into the current subtitle (the one playing, + // or the last one that started before the position) restarts it, near its start goes to + // the one before - picking "last start before the position" alone restarted the playing + // subtitle forever. Time order, not list order (the grid may be sorted by text). + var position = vp.Position; + var sorted = Subtitles.OrderBy(s => s.StartTime.TotalMilliseconds).ToList(); + var currentIndex = sorted.FindLastIndex(s => s.StartTime.TotalSeconds <= position + PlayPreviousNextToleranceSeconds); + if (currentIndex < 0) { return; } + var previous = position - sorted[currentIndex].StartTime.TotalSeconds > PlayPreviousRestartThresholdSeconds || currentIndex == 0 + ? sorted[currentIndex] + : sorted[currentIndex - 1]; + SeekVideoPlayer(vp, previous.StartTime.TotalSeconds); PinPlayheadTo(previous.StartTime.TotalSeconds); SelectAndScrollToSubtitle(previous); PlayVideo(vp); } + // A seek lands on (or a hair off) the requested start, so "at the start" allows this much. + private const double PlayPreviousNextToleranceSeconds = 0.01; + + // Play previous within this long after the current subtitle's start goes to the one before; + // later it restarts the current one. + private const double PlayPreviousRestartThresholdSeconds = 1.0; + [RelayCommand] private void PlayPreviousAndStop() { diff --git a/src/ui/Features/Shared/BinaryEdit/BinaryEditViewModel.cs b/src/ui/Features/Shared/BinaryEdit/BinaryEditViewModel.cs index a8b1430c30e..1cae5e4dd98 100644 --- a/src/ui/Features/Shared/BinaryEdit/BinaryEditViewModel.cs +++ b/src/ui/Features/Shared/BinaryEdit/BinaryEditViewModel.cs @@ -1323,7 +1323,20 @@ private async Task DoExport(IExportHandler exportHandler, string extension return false; } - WriteExport(exportHandler, fileOrFolderName); + try + { + WriteExport(exportHandler, fileOrFolderName); + } + catch (Exception exception) + { + // e.g. a D-Cinema MXF with more images than one track file can hold + Se.LogError(exception); + await MessageBox.Show(Window, Se.Language.General.Error, + string.Format(Se.Language.General.CouldNotSaveFileXErrorY, fileOrFolderName, exception.Message), + MessageBoxButtons.OK, MessageBoxIcon.Error); + return false; + } + _isDirty = false; return true; } diff --git a/src/ui/Features/Shared/TextBoxUtils/TextBoxAutoWrap.cs b/src/ui/Features/Shared/TextBoxUtils/TextBoxAutoWrap.cs index 4d32fea5a25..ca38410e0a0 100644 --- a/src/ui/Features/Shared/TextBoxUtils/TextBoxAutoWrap.cs +++ b/src/ui/Features/Shared/TextBoxUtils/TextBoxAutoWrap.cs @@ -63,7 +63,7 @@ internal static bool TryWrap(string text, int caretIndex, int maxLength, Func= text.Length) + newCaretIndex = caretIndex >= text.Length + ? wrapped.Length + : MapCaret(text, caretIndex, wrapped); + + return true; + } + + // The last space that is not inside an HTML tag () or an ASSA override + // block ({\fn Times New Roman}) - breaking there would corrupt the tag. -1 when none. + private static int LastSpaceOutsideTags(string text) + { + var lastSpace = -1; + var inHtmlTag = false; + var inAssaTag = false; + for (var i = 0; i < text.Length; i++) { - newCaretIndex = wrapped.Length; + var c = text[i]; + if (inHtmlTag) + { + inHtmlTag = c != '>'; + } + else if (inAssaTag) + { + inAssaTag = c != '}'; + } + else if (c == '<') + { + inHtmlTag = true; + } + else if (c == '{') + { + inAssaTag = true; + } + else if (c == ' ') + { + lastSpace = i; + } } - else if (caretIndex > breakIndex) + + return lastSpace; + } + + // Re-breaking can add, remove or move whitespace anywhere in the line, so map the caret by the + // number of non-whitespace characters in front of it rather than by the length difference. + private static int MapCaret(string oldText, int caretIndex, string newText) + { + var count = 0; + for (var i = 0; i < caretIndex && i < oldText.Length; i++) { - newCaretIndex = Math.Min(wrapped.Length, caretIndex + wrapped.Length - text.Length); + if (!char.IsWhiteSpace(oldText[i])) + { + count++; + } } - return true; + var index = 0; + var seen = 0; + while (index < newText.Length && seen < count) + { + if (!char.IsWhiteSpace(newText[index])) + { + seen++; + } + + index++; + } + + // The caret was after whitespace (e.g. the space just typed, now a line break) - keep it + // after the whitespace in the new text too, so it sits at the start of the next word. + if (caretIndex > 0 && char.IsWhiteSpace(oldText[caretIndex - 1])) + { + while (index < newText.Length && char.IsWhiteSpace(newText[index])) + { + index++; + } + } + + return Math.Min(index, newText.Length); } } diff --git a/src/ui/Features/Translate/LlamaCppAdvanced/AdvancedTranslatorBase.cs b/src/ui/Features/Translate/LlamaCppAdvanced/AdvancedTranslatorBase.cs index d6c0d008a8f..8e0ee240b71 100644 --- a/src/ui/Features/Translate/LlamaCppAdvanced/AdvancedTranslatorBase.cs +++ b/src/ui/Features/Translate/LlamaCppAdvanced/AdvancedTranslatorBase.cs @@ -195,7 +195,7 @@ private async Task> TranslateLinesAsync(List> TranslateLinesAsync(List - internal static string DescribeUnusableReply(string reply, bool fromReasoning) + internal static string DescribeUnusableReply(string reply, bool fromReasoning, bool requestDisabledThinking = true) { if (string.IsNullOrWhiteSpace(reply)) { return "the server returned an empty reply (no \"content\" and no \"reasoning_content\")"; } - return fromReasoning + if (!fromReasoning) + { + return reply; + } + + // Only claim the model ignored "enable_thinking" off when this request actually sent it - + // the first reasoning-only reply is what switches it on for the following requests. + return requestDisabledThinking ? "the server left \"content\" empty and answered in \"reasoning_content\" only - the model looks to be in thinking mode and kept thinking with \"enable_thinking\" off, turn thinking off on the server (e.g. \"--reasoning off\"): " + reply - : reply; + : "the server left \"content\" empty and answered in \"reasoning_content\" only - the model looks to be in thinking mode, the next requests ask it to skip thinking (\"enable_thinking\" off); if that does not help, turn thinking off on the server (e.g. \"--reasoning off\"): " + reply; } /// diff --git a/src/ui/Features/Translate/LlamaCppAdvanced/LlamaCppAdvancedClient.cs b/src/ui/Features/Translate/LlamaCppAdvanced/LlamaCppAdvancedClient.cs index ad1ec79acb3..d8ef04d3518 100644 --- a/src/ui/Features/Translate/LlamaCppAdvanced/LlamaCppAdvancedClient.cs +++ b/src/ui/Features/Translate/LlamaCppAdvanced/LlamaCppAdvancedClient.cs @@ -53,6 +53,13 @@ public class LlamaCppAdvancedClient : IDisposable /// public bool DisableThinking { get; private set; } + /// + /// True when the request that produced the last reply (or failure) carried + /// chat_template_kwargs.enable_thinking=false - can be set by + /// that very reply, so it does not tell whether the server already had the flag. + /// + public bool LastRequestDisabledThinking { get; private set; } + public LlamaCppAdvancedClient() { _httpClient = HttpClientFactoryWithProxy.CreateHttpClientWithProxy(); @@ -64,18 +71,18 @@ public async Task ChatAsync(string url, string systemPrompt, string user Error = string.Empty; ReplyFromReasoning = false; - var response = await PostAsync(url, BuildRequestJson(systemPrompt, userContent, responseFormatJson, model, defaultMaxTokens, temperatureBump, DisableThinking), cancellationToken); + var response = await PostWithThinkingFallbackAsync(url, systemPrompt, userContent, responseFormatJson, model, defaultMaxTokens, temperatureBump, cancellationToken); // A stall is not a format rejection - retrying the same prompt with a looser format // would just burn another watchdog period, so only the format fallbacks run here. if (!response.ok && !response.stalled && responseFormatJson != null && !cancellationToken.IsCancellationRequested) { - response = await PostAsync(url, BuildRequestJson(systemPrompt, userContent, "{\"type\":\"json_object\"}", model, defaultMaxTokens, temperatureBump, DisableThinking), cancellationToken); + response = await PostWithThinkingFallbackAsync(url, systemPrompt, userContent, "{\"type\":\"json_object\"}", model, defaultMaxTokens, temperatureBump, cancellationToken); } if (!response.ok && !response.stalled && responseFormatJson != null && !cancellationToken.IsCancellationRequested) { - response = await PostAsync(url, BuildRequestJson(systemPrompt, userContent, null, model, defaultMaxTokens, temperatureBump, DisableThinking), cancellationToken); + response = await PostWithThinkingFallbackAsync(url, systemPrompt, userContent, null, model, defaultMaxTokens, temperatureBump, cancellationToken); } if (!response.ok) @@ -96,6 +103,34 @@ public async Task ChatAsync(string url, string systemPrompt, string user return reply; } + /// + /// Posts one request with the current flag. A strict + /// OpenAI-compatible server can reject the unknown chat_template_kwargs field with a 4xx, so + /// such a rejection is retried once without it; the flag stays off only when that retry gets + /// through - when it fails too, the field was not the problem (e.g. a json_schema rejection + /// that the format fallbacks handle) and the flag is kept. + /// + private async Task<(bool ok, bool stalled, bool isAssembledContent, string body, string reasoning, int status)> PostWithThinkingFallbackAsync(string url, string systemPrompt, string userContent, string? responseFormatJson, string? model, int defaultMaxTokens, double temperatureBump, CancellationToken cancellationToken) + { + LastRequestDisabledThinking = DisableThinking; + var response = await PostAsync(url, BuildRequestJson(systemPrompt, userContent, responseFormatJson, model, defaultMaxTokens, temperatureBump, DisableThinking), cancellationToken); + if (response.ok || response.stalled || !DisableThinking || response.status < 400 || response.status >= 500 || cancellationToken.IsCancellationRequested) + { + return response; + } + + var retry = await PostAsync(url, BuildRequestJson(systemPrompt, userContent, responseFormatJson, model, defaultMaxTokens, temperatureBump, false), cancellationToken); + if (!retry.ok) + { + return response; + } + + SeLogger.Error("llama.cpp advanced translate: server rejected chat_template_kwargs.enable_thinking, sending requests without it: " + response.body); + DisableThinking = false; + LastRequestDisabledThinking = false; + return retry; + } + /// /// The "model" field is only written when the caller supplies one (Ollama requires it; /// llama-server serves the single model it was started with, and sending one would only @@ -200,7 +235,7 @@ private static void WriteNumberIfSet(Utf8JsonWriter writer, string name, double /// with a different response format. A user cancellation propagates as /// instead. /// - private async Task<(bool ok, bool stalled, bool isAssembledContent, string body, string reasoning)> PostAsync(string url, string json, CancellationToken cancellationToken) + private async Task<(bool ok, bool stalled, bool isAssembledContent, string body, string reasoning, int status)> PostAsync(string url, string json, CancellationToken cancellationToken) { using var overall = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); overall.CancelAfter(TotalTimeout); @@ -220,7 +255,7 @@ private static void WriteNumberIfSet(Utf8JsonWriter writer, string name, double if (!result.IsSuccessStatusCode) { var errorBody = await result.Content.ReadAsStringAsync(idle.Token).ConfigureAwait(false); - return (false, false, false, errorBody, string.Empty); + return (false, false, false, errorBody, string.Empty, (int)result.StatusCode); } await using var stream = await result.Content.ReadAsStreamAsync(idle.Token).ConfigureAwait(false); @@ -257,13 +292,13 @@ private static void WriteNumberIfSet(Utf8JsonWriter writer, string name, double sawSseData = true; if (!TryAppendDelta(payload, assembled, reasoning, out var chunkError)) { - return (false, false, false, chunkError, string.Empty); + return (false, false, false, chunkError, string.Empty, 0); } } return sawSseData - ? (true, false, true, assembled.ToString(), reasoning.ToString()) - : (true, false, false, raw.ToString(), string.Empty); + ? (true, false, true, assembled.ToString(), reasoning.ToString(), (int)result.StatusCode) + : (true, false, false, raw.ToString(), string.Empty, (int)result.StatusCode); } catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested) { @@ -274,11 +309,11 @@ private static void WriteNumberIfSet(Utf8JsonWriter writer, string name, double return (false, true, false, "The server stopped sending data (generation looks stalled) and the request was aborted - " + "the model may not fit in GPU memory, or it may be stuck in a generation loop.", - string.Empty); + string.Empty, 0); } catch (Exception e) { - return (false, false, false, e.Message, string.Empty); + return (false, false, false, e.Message, string.Empty, 0); } } diff --git a/src/ui/Features/Video/SyncAudioTrack/SyncAudioTrackViewModel.cs b/src/ui/Features/Video/SyncAudioTrack/SyncAudioTrackViewModel.cs index b8f683971be..1fe4b407036 100644 --- a/src/ui/Features/Video/SyncAudioTrack/SyncAudioTrackViewModel.cs +++ b/src/ui/Features/Video/SyncAudioTrack/SyncAudioTrackViewModel.cs @@ -86,8 +86,11 @@ public void Initialize(string videoFileName, double startPositionSeconds) /// internal void SetMediaInfo(FfmpegMediaInfo2 mediaInfo) { - _streamIndexes = mediaInfo.Tracks.Select(t => t.StreamIndex).Where(i => i >= 0).ToList(); - if (_streamIndexes.Count != mediaInfo.Tracks.Count) + // Data streams (MOV timecode, SCTE-35, ...) have no codec - "-c copy" cannot mux them and + // ffmpeg aborts, so they are left out of the copy. + var copiedTracks = mediaInfo.Tracks.Where(t => !IsDataStream(t)).ToList(); + _streamIndexes = copiedTracks.Select(t => t.StreamIndex).Where(i => i >= 0).ToList(); + if (_streamIndexes.Count != copiedTracks.Count) { _streamIndexes.Clear(); // not all indexes known - the remux then appends the track } @@ -112,6 +115,10 @@ internal void SetMediaInfo(FfmpegMediaInfo2 mediaInfo) SelectedAudioTrack = AudioTracks.FirstOrDefault(); } + private static bool IsDataStream(FfmpegTrackInfo track) => + track.TrackType == FfmpegTrackType.Other && + track.TypeName is "Data" or "Unknown"; + partial void OnOffsetMsChanged(int value) { VideoPlayer.VideoPlayer.SetAudioDelay(value / 1000.0); @@ -181,11 +188,12 @@ await MessageBox.Show(Window, Se.Language.General.Error, ProgressValue = 0; ProgressText = Se.Language.Video.SyncAudioTrackSaving; int exitCode; + Process? process = null; try { var dropNegativePackets = OffsetMs < 0 && await Task.Run(FfmpegGenerator.NoiseBitstreamFilterCanDrop); - using var process = FfmpegGenerator.ShiftAudioTrack(_videoFileName, outputFileName, _streamIndexes, - SelectedAudioTrack.StreamIndex, OffsetMs / 1000.0, dropNegativePackets, (_, e) => + process = FfmpegGenerator.ShiftAudioTrack(_videoFileName, outputFileName, _streamIndexes, + SelectedAudioTrack.StreamIndex, SelectedAudioTrack.AudioNumber, OffsetMs / 1000.0, dropNegativePackets, (_, e) => { if (string.IsNullOrEmpty(e.Data)) { @@ -207,11 +215,19 @@ await MessageBox.Show(Window, Se.Language.General.Error, } catch (OperationCanceledException) { + // The cancel kills ffmpeg without waiting for it to exit - until it has, the partial + // output is still open and (on Windows) cannot be deleted. + if (process != null) + { + await Task.Run(() => WaitForExitNoError(process, 2000)); + } + TryDelete(outputFileName); return; } finally { + process?.Dispose(); IsSaving = false; ProgressText = string.Empty; } @@ -245,6 +261,18 @@ private static string LastLines(string text, int count) return string.Join(Environment.NewLine, lines.Skip(Math.Max(0, lines.Count - count))); } + private static void WaitForExitNoError(Process process, int milliseconds) + { + try + { + process.WaitForExit(milliseconds); + } + catch + { + // not started, or already gone + } + } + private static void TryDelete(string fileName) { try diff --git a/src/ui/Logic/Media/FfmpegGenerator.cs b/src/ui/Logic/Media/FfmpegGenerator.cs index 5b9eff4b1d2..6bf3bee50ef 100644 --- a/src/ui/Logic/Media/FfmpegGenerator.cs +++ b/src/ui/Logic/Media/FfmpegGenerator.cs @@ -994,16 +994,20 @@ private static string GetAddAudioTrackEncodingString(string audioEncoding) /// (ffmpeg 5.1+ "noise" bsf drop option) the audio /// packets before zero are dropped instead. /// - /// All stream indexes of the input in order ("Stream #0:N"); empty - /// = unknown, then the shifted track is added last instead of in its old place. - public static Process ShiftAudioTrack(string inputFileName, string outputFileName, IReadOnlyList streamIndexes, int audioStreamIndex, double offsetSeconds, bool dropNegativePackets, DataReceivedEventHandler? dataReceivedHandler = null) + /// The stream indexes of the input to copy, in order ("Stream #0:N"), + /// without data streams (they have no codec and make the muxer abort); empty = unknown, then + /// the shifted track is added last instead of in its old place. + /// The ffmpeg stream index of the audio track to shift. + /// 1-based position of that track among the audio streams - used + /// when the stream indexes are unknown. + public static Process ShiftAudioTrack(string inputFileName, string outputFileName, IReadOnlyList streamIndexes, int audioStreamIndex, int audioNumber, double offsetSeconds, bool dropNegativePackets, DataReceivedEventHandler? dataReceivedHandler = null) { var process = new Process { StartInfo = { FileName = GetFfmpegLocation(), - Arguments = GetShiftAudioTrackArguments(inputFileName, outputFileName, streamIndexes, audioStreamIndex, offsetSeconds, dropNegativePackets), + Arguments = GetShiftAudioTrackArguments(inputFileName, outputFileName, streamIndexes, audioStreamIndex, audioNumber, offsetSeconds, dropNegativePackets), UseShellExecute = false, CreateNoWindow = true } @@ -1013,7 +1017,7 @@ public static Process ShiftAudioTrack(string inputFileName, string outputFileNam return process; } - internal static string GetShiftAudioTrackArguments(string inputFileName, string outputFileName, IReadOnlyList streamIndexes, int audioStreamIndex, double offsetSeconds, bool dropNegativePackets) + internal static string GetShiftAudioTrackArguments(string inputFileName, string outputFileName, IReadOnlyList streamIndexes, int audioStreamIndex, int audioNumber, double offsetSeconds, bool dropNegativePackets) { var offset = offsetSeconds.ToString("0.000", CultureInfo.InvariantCulture); @@ -1037,7 +1041,10 @@ internal static string GetShiftAudioTrackArguments(string inputFileName, string } else { - maps.Append($"-map 0 -map -0:{audioStreamIndex} -map 1:{audioStreamIndex} "); + // Relative audio specifier: without known stream indexes there may be no absolute one. + // "-map -0:d" leaves out data streams, which "-c copy" cannot mux. + var audio = $"a:{Math.Max(0, audioNumber - 1)}"; + maps.Append($"-map 0 -map -0:{audio} -map -0:d -map 1:{audio} "); } var negative = string.Empty; diff --git a/src/ui/Logic/Media/FfmpegMediainfo2.cs b/src/ui/Logic/Media/FfmpegMediainfo2.cs index edfe1a05623..6bd0484212c 100644 --- a/src/ui/Logic/Media/FfmpegMediainfo2.cs +++ b/src/ui/Logic/Media/FfmpegMediainfo2.cs @@ -222,7 +222,7 @@ internal static FfmpegMediaInfo2 ParseLog(string log) } else { - info.Tracks.Add(new FfmpegTrackInfo { TrackType = FfmpegTrackType.Other, TrackInfo = trackInfo, Language = language, StreamIndex = streamIndex }); + info.Tracks.Add(new FfmpegTrackInfo { TrackType = FfmpegTrackType.Other, TypeName = trackType, TrackInfo = trackInfo, Language = language, StreamIndex = streamIndex }); } } } diff --git a/tests/UI/Controls/AudioVisualizerDragTests.cs b/tests/UI/Controls/AudioVisualizerDragTests.cs index 1d943f5d0a0..a720f998ad9 100644 --- a/tests/UI/Controls/AudioVisualizerDragTests.cs +++ b/tests/UI/Controls/AudioVisualizerDragTests.cs @@ -107,7 +107,7 @@ public void Assa_Move_LinesWithTheSameTimes_MoveTogether() var a = Line(1, 3, "{\\p1}m 0 0 l 10 0 10 10{\\p0}"); var b = Line(1, 3, "{\\p1}m 5 5 l 15 5 15 15{\\p0}"); var sign = Line(1, 3, "A sign with the same times"); - var neighbour = Line(3.5, 5); + var neighbour = Line(4, 5); var (window, av, _) = Open(a, b, sign, neighbour); av.MovesSameTimeLinesTogether = () => true; @@ -119,6 +119,28 @@ public void Assa_Move_LinesWithTheSameTimes_MoveTogether() // +63 px = +0.5 s for the whole stack; the line after it is not part of it Assert.All(new[] { a, b, sign }, line => Assert.Equal(1.5, line.StartTime.TotalSeconds, 2)); Assert.All(new[] { a, b, sign }, line => Assert.Equal(3.5, line.EndTime.TotalSeconds, 2)); + Assert.Equal(4, neighbour.StartTime.TotalSeconds, 2); + window.Close(); + } + + [AvaloniaFact] + public void Assa_Move_LinesWithTheSameTimes_StopAtTheNeighbour() + { + // A dialogue line with a Comment twin at the same times is a stack too - it must still keep + // the minimum gap to the next line instead of sliding over it + var line = Line(1, 3, "Dialogue"); + var twin = Line(1, 3, "Dialogue (original)"); + var neighbour = Line(3.5, 5); + var (window, av, _) = Open(line, twin, neighbour); + av.MovesSameTimeLinesTogether = () => true; + + window.MouseDown(new Point(252, 100), MouseButton.Left, RawInputModifiers.None); + window.MouseMove(new Point(315, 100), RawInputModifiers.None); + Dispatcher.UIThread.RunJobs(); + window.MouseUp(new Point(315, 100), MouseButton.Left, RawInputModifiers.None); + + Assert.All(new[] { line, twin }, l => Assert.Equal(3.4, l.EndTime.TotalSeconds, 2)); + Assert.All(new[] { line, twin }, l => Assert.Equal(1.4, l.StartTime.TotalSeconds, 2)); Assert.Equal(3.5, neighbour.StartTime.TotalSeconds, 2); window.Close(); } diff --git a/tests/UI/Features/TextBoxAutoWrapTests.cs b/tests/UI/Features/TextBoxAutoWrapTests.cs index cb9f275e580..febc34cabc3 100644 --- a/tests/UI/Features/TextBoxAutoWrapTests.cs +++ b/tests/UI/Features/TextBoxAutoWrapTests.cs @@ -54,6 +54,20 @@ public void TryWrap_CaretBeforeBreak_StaysPut() Assert.Equal(4, caret); } + [Theory] + [InlineData("", "")] + [InlineData("{\\fn Times New Roman}", "")] + public void TryWrap_NoSpaceOutsideTags_LeavesTagsAlone(string startTag, string endTag) + { + // CJK text has no spaces - the only one is inside the tag, and breaking there corrupts it + var text = startTag + new string('あ', 35) + endTag; + + var wrapped = TextBoxAutoWrap.TryWrap(text, text.Length, 30, () => "ja", out var newText, out _); + + Assert.False(wrapped); + Assert.Equal(text, newText); + } + [Theory] [InlineData("Short line")] [InlineData("This is a line that is too long\r\nbut already has two lines")] diff --git a/tests/UI/Logic/FfmpegShiftAudioTrackTests.cs b/tests/UI/Logic/FfmpegShiftAudioTrackTests.cs index 834bc48d0ec..b65637d0c33 100644 --- a/tests/UI/Logic/FfmpegShiftAudioTrackTests.cs +++ b/tests/UI/Logic/FfmpegShiftAudioTrackTests.cs @@ -15,7 +15,7 @@ public class FfmpegShiftAudioTrackTests [Fact] public void Later_KeepsStreamOrder_TakesTrackFromShiftedInput() { - var args = FfmpegGenerator.GetShiftAudioTrackArguments("in.mkv", "out.mkv", Streams, 2, 0.5, dropNegativePackets: true); + var args = FfmpegGenerator.GetShiftAudioTrackArguments("in.mkv", "out.mkv", Streams, 2, 2, 0.5, dropNegativePackets: true); Assert.Contains("-i \"in.mkv\" -itsoffset 0.500 -i \"in.mkv\" -map 0:0 -map 0:1 -map 1:2 -map 0:3 -c copy ", args); Assert.DoesNotContain("avoid_negative_ts", args); @@ -31,11 +31,11 @@ public void Later_KeepsStreamOrder_TakesTrackFromShiftedInput() [Fact] public void Earlier_AllowsNegativeTimestamps_AndDropsThemWhenSupported() { - var withDrop = FfmpegGenerator.GetShiftAudioTrackArguments("in.mkv", "out.mkv", Streams, 2, -0.25, dropNegativePackets: true); + var withDrop = FfmpegGenerator.GetShiftAudioTrackArguments("in.mkv", "out.mkv", Streams, 2, 2, -0.25, dropNegativePackets: true); Assert.Contains("-itsoffset -0.250 ", withDrop); Assert.Contains("-bsf:2 \"noise=drop=lt(pts*tb\\,0)\" -avoid_negative_ts disabled ", withDrop); - var withoutDrop = FfmpegGenerator.GetShiftAudioTrackArguments("in.mkv", "out.mkv", Streams, 2, -0.25, dropNegativePackets: false); + var withoutDrop = FfmpegGenerator.GetShiftAudioTrackArguments("in.mkv", "out.mkv", Streams, 2, 2, -0.25, dropNegativePackets: false); Assert.Contains("-c copy -avoid_negative_ts disabled -map_metadata", withoutDrop); Assert.DoesNotContain("-bsf", withoutDrop); } @@ -53,9 +53,9 @@ public void NoiseHelp_DropOption_NotDropAmount() [Fact] public void UnknownStreamIndexes_AppendsTheShiftedTrack() { - var args = FfmpegGenerator.GetShiftAudioTrackArguments("in.mp4", "out.mp4", Array.Empty(), 1, 1.0, dropNegativePackets: true); + var args = FfmpegGenerator.GetShiftAudioTrackArguments("in.mp4", "out.mp4", Array.Empty(), 1, 1, 1.0, dropNegativePackets: true); - Assert.Contains("-map 0 -map -0:1 -map 1:1 -c copy ", args); + Assert.Contains("-map 0 -map -0:a:0 -map -0:d -map 1:a:0 -c copy ", args); } [Fact] diff --git a/tests/libse/Common/RegexUtilsTest.cs b/tests/libse/Common/RegexUtilsTest.cs index f81a3587cb5..7ce98c3d719 100644 --- a/tests/libse/Common/RegexUtilsTest.cs +++ b/tests/libse/Common/RegexUtilsTest.cs @@ -131,6 +131,8 @@ public void BuildWholeWordPattern_Matches(string searchText, string text, bool e [InlineData(@"(\w+)", "word", @"\\u$1", @"\uword")] [InlineData(@"(\w+)", "word", @"a\b$1", @"a\bword")] [InlineData(@"(?\w+)", "word", @"\U${w}", "WORD")] + [InlineData(@"(.*?)", "word", @"{\u1}$1{\u0}", @"{\u1}word{\u0}")] + [InlineData(@"(\w+)", "word", @"{\u1}\u$1", @"{\u1}Word")] public void ReplaceNewLineSafe_CaseConversion(string pattern, string text, string replaceWith, string expected) { var regex = new Regex(pattern, RegexOptions.Multiline); diff --git a/tests/libse/ContainerFormats/TimedTextMxfWriterTest.cs b/tests/libse/ContainerFormats/TimedTextMxfWriterTest.cs index 3d5497bbe86..ec9e7b36b60 100644 --- a/tests/libse/ContainerFormats/TimedTextMxfWriterTest.cs +++ b/tests/libse/ContainerFormats/TimedTextMxfWriterTest.cs @@ -127,6 +127,24 @@ public void ResourceMissingFromTrackThrows() Assert.Contains(ImageId.ToString(), exception.Message); } + [Fact] + public void TooManyResourcesThrows() + { + // 4096 sub-descriptor ids no longer fit the 16-bit local set length of the batch + var xml = File.ReadAllText(FilePath("sample_mxf_429-5_asdcplib.xml")); + var writer = new TimedTextMxfWriter(); + writer.Resources.Add(new TimedTextMxfResource(FontId, TimedTextMxfResource.MimeTypeFont, Font)); + writer.Resources.Add(new TimedTextMxfResource(ImageId, TimedTextMxfResource.MimeTypePng, Png)); + while (writer.Resources.Count <= TimedTextMxfWriter.MaxResources) + { + writer.Resources.Add(new TimedTextMxfResource(Guid.NewGuid(), TimedTextMxfResource.MimeTypePng, Png)); + } + + var exception = Assert.Throws(() => writer.Write(new MemoryStream(), xml)); + Assert.Contains("4095", exception.Message); + Assert.Equal(4095, TimedTextMxfWriter.MaxResources); + } + [Fact] public void GetLoadFontIds() { @@ -144,11 +162,23 @@ public void TryParseTimeCode(string timeCode, int frameRate, long expected) Assert.False(TimedTextMxfWriter.TryParseTimeCode("12.5", frameRate, out _)); } + [Theory] + [InlineData("00:60:00:00", 24)] + [InlineData("00:00:60:00", 24)] + [InlineData("00:00:00:24", 24)] + [InlineData("00:00:00:25", 25)] + [InlineData("00:00:01:00", 0)] + public void TryParseTimeCodeOutOfRange(string timeCode, int frameRate) + { + Assert.False(TimedTextMxfWriter.TryParseTimeCode(timeCode, frameRate, out _)); + } + [Fact] public void ToTimeCode() { Assert.Equal("00:00:12:00", TimedTextMxfWriter.ToTimeCode(TimeSpan.FromSeconds(12), 24)); Assert.Equal("01:02:03:12", TimedTextMxfWriter.ToTimeCode(new TimeSpan(0, 1, 2, 3, 500), 24)); + Assert.Equal("00:59:56:10", TimedTextMxfWriter.ToTimeCode(86314L, 24)); // 1 h of 23.976 picture } private static List<(long Offset, byte[] Key, byte[] Value)> ReadKlvs(byte[] data) From c5f3fe5813e082b191bd2b15ab1f9bb5d0519487 Mon Sep 17 00:00:00 2001 From: niksedk Date: Sat, 17 Oct 2026 13:04:31 +0200 Subject: [PATCH 297/306] ASSA draw: pointing hand clip art points with the index finger The finger came out of the middle of the fist; it is now the index finger along the side, with folded fingers and a thumb. Co-Authored-By: Claude Opus 5.5 --- src/ui/Features/Assa/AssaDraw/ShapeLibrary.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ui/Features/Assa/AssaDraw/ShapeLibrary.cs b/src/ui/Features/Assa/AssaDraw/ShapeLibrary.cs index f4250966f43..8775c61c2d1 100644 --- a/src/ui/Features/Assa/AssaDraw/ShapeLibrary.cs +++ b/src/ui/Features/Assa/AssaDraw/ShapeLibrary.cs @@ -621,7 +621,7 @@ private static List MakeBuiltIn() Clip(l.DrawClipArtRingingBell, null, """""", DrawLoopEffect.Swing, 1200), Clip(l.DrawClipArtPointingHand, null, - """""", DrawLoopEffect.Bounce, 800), + """""", DrawLoopEffect.Bounce, 800), Clip(l.DrawClipArtWiFi, null, """""", DrawLoopEffect.Pulse, 900), Clip(l.DrawClipArtTwinklingStar, null, From 70090c3542ab860eeafb68e58bdd9b06052dfb5f Mon Sep 17 00:00:00 2001 From: Nikolaj Olsson Date: Sat, 17 Oct 2026 13:52:59 +0200 Subject: [PATCH 298/306] Layout window: Up/Down move between rows, Enter works with focused button Co-Authored-By: Claude Opus 5.5 --- src/ui/Features/Main/Layout/LayoutWindow.cs | 65 +++++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/src/ui/Features/Main/Layout/LayoutWindow.cs b/src/ui/Features/Main/Layout/LayoutWindow.cs index 6928814846f..7f1a2b9e18a 100644 --- a/src/ui/Features/Main/Layout/LayoutWindow.cs +++ b/src/ui/Features/Main/Layout/LayoutWindow.cs @@ -2,6 +2,7 @@ using Avalonia.Automation; using Avalonia.Controls; using Avalonia.Input; +using Avalonia.Interactivity; using Avalonia.Layout; using Avalonia.Media; using Avalonia.Media.Imaging; @@ -131,6 +132,70 @@ public LayoutWindow(LayoutViewModel viewViewModel) buttonCancel.Click += (_, _) => Close(); Content = EditorChrome.MakeRoot(new ScrollViewer { Content = wrapPanel }, EditorChrome.MakeDockedButtonBar(null, buttonCancel)); + + // Tunnel, so the scroll viewer doesn't take Up/Down for scrolling and a focused button doesn't take Enter + AddHandler(KeyDownEvent, (_, e) => + { + if (e.KeyModifiers != KeyModifiers.None) + { + return; + } + + if (e.Key is Key.Up or Key.Down) + { + e.Handled = true; + MoveVertically(e.Key == Key.Up ? -1 : 1); + } + else if (e.Key == Key.Enter && _focusedLayout >= 0) + { + e.Handled = true; + _vm.SelectedLayout = _focusedLayout + 1; + _vm.OkPressed = true; + Close(); + } + }, RoutingStrategies.Tunnel); + } + + /// + /// Moves the picked card one row up/down in the wrap panel, keeping the column. + /// + private void MoveVertically(int direction) + { + if (_focusedLayout < 0) + { + ResetOldLayout(); + _focusedLayout = direction > 0 ? 0 : LayoutCount - 1; + SetHighlight(_borders[_focusedLayout], true); + _borders[_focusedLayout].BringIntoView(); + return; + } + + var columns = 0; + foreach (var border in _borders) + { + if (Math.Abs(border.Bounds.Y - _borders[0].Bounds.Y) > 1) + { + break; + } + + columns++; + } + + if (columns <= 0) + { + return; + } + + var newIndex = _focusedLayout + direction * columns; + if (newIndex < 0 || newIndex >= LayoutCount) + { + return; + } + + ResetOldLayout(); + _focusedLayout = newIndex; + SetHighlight(_borders[_focusedLayout], true); + _borders[_focusedLayout].BringIntoView(); } /// From d8793626e78773555437329a6f430d7a38ff8ef7 Mon Sep 17 00:00:00 2001 From: Nikolaj Olsson Date: Sat, 17 Oct 2026 13:56:33 +0200 Subject: [PATCH 299/306] Add "split line ... and play" shortcuts (#9180) Split at video position, at the text box cursor, or at both, then go to the second part and play from the cut. A split at the video position while the video is playing keeps playing. The SE4 shortcut MainTextBoxSplitAtCursorAndVideoPosPlay now imports to the play variant. Co-Authored-By: Claude Opus 5.5 --- src/ui/Features/Main/MainViewModel.cs | 64 +++++++++++++++++++ .../Options/Shortcuts/Se4ShortcutsImporter.cs | 2 +- .../Options/LanguageSettingsShortcuts.cs | 6 ++ src/ui/Logic/ShortcutsMain.cs | 6 ++ 4 files changed, 77 insertions(+), 1 deletion(-) diff --git a/src/ui/Features/Main/MainViewModel.cs b/src/ui/Features/Main/MainViewModel.cs index b2721791a8a..b6673e5fccf 100644 --- a/src/ui/Features/Main/MainViewModel.cs +++ b/src/ui/Features/Main/MainViewModel.cs @@ -20635,6 +20635,70 @@ private void SplitAtVideoPositionAndTextBoxCursorPosition() SplitSelectedLine(true, true); } + [RelayCommand] + private void SplitAtVideoPositionAndPlay() + { + SplitSelectedLineAndPlay(true, false); + } + + [RelayCommand] + private void SplitAtTextBoxCursorPositionAndPlay() + { + SplitSelectedLineAndPlay(false, true); + } + + [RelayCommand] + private void SplitAtVideoPositionAndTextBoxCursorPositionAndPlay() + { + SplitSelectedLineAndPlay(true, true); + } + + /// + /// SE 4's "split ... and play" (#9180): split, move on to the second part and play from where + /// the line was cut, so a long block can be chopped up in one pass without play/pause per cut. + /// A split at the video position while the video plays keeps playing undisturbed - the cut is + /// where the video already is. + /// + private void SplitSelectedLineAndPlay(bool atVideoPosition, bool atTextBoxPosition) + { + var s = SelectedSubtitle; + if (s == null) + { + return; + } + + var countBefore = Subtitles.Count; + SplitLine(atVideoPosition, atTextBoxPosition, s); + if (Subtitles.Count <= countBefore) + { + return; + } + + var secondIndex = Subtitles.IndexOf(s) + 1; + var second = Subtitles.GetOrNull(secondIndex); + if (second == null) + { + return; + } + + SelectAndScrollToRow(secondIndex); + + var vp = GetVideoPlayerControl(); + if (vp == null || string.IsNullOrEmpty(_videoFileName)) + { + return; + } + + if (atVideoPosition && vp.IsPlaying) + { + return; + } + + SeekVideoPlayer(vp, second.StartTime.TotalSeconds); + PinPlayheadTo(second.StartTime.TotalSeconds); + PlayVideo(vp); + } + [RelayCommand] private async Task AssistedSplit() { diff --git a/src/ui/Features/Options/Shortcuts/Se4ShortcutsImporter.cs b/src/ui/Features/Options/Shortcuts/Se4ShortcutsImporter.cs index ea9056a9e41..d149e88bc71 100644 --- a/src/ui/Features/Options/Shortcuts/Se4ShortcutsImporter.cs +++ b/src/ui/Features/Options/Shortcuts/Se4ShortcutsImporter.cs @@ -251,7 +251,7 @@ public sealed class ImportResult ["MainTextBoxSplitAtCursor"] = nameof(MainViewModel.SplitAtTextBoxCursorPositionCommand), ["MainTextBoxSplitAtCursorAndAutoBr"] = nameof(MainViewModel.SplitAtTextBoxCursorPositionCommand), ["MainTextBoxSplitAtCursorAndVideoPos"] = nameof(MainViewModel.SplitAtVideoPositionAndTextBoxCursorPositionCommand), - ["MainTextBoxSplitAtCursorAndVideoPosPlay"] = nameof(MainViewModel.SplitAtVideoPositionAndTextBoxCursorPositionCommand), + ["MainTextBoxSplitAtCursorAndVideoPosPlay"] = nameof(MainViewModel.SplitAtVideoPositionAndTextBoxCursorPositionAndPlayCommand), ["MainTextBoxMoveLastWordDown"] = nameof(MainViewModel.MoveLastWordToNextSubtitleCommand), ["MainTextBoxMoveLastWordDownCurrent"] = nameof(MainViewModel.MoveLastWordFromFirstLineDownCurrentSubtitleCommand), ["MainTextBoxMoveFirstWordUpCurrent"] = nameof(MainViewModel.MoveFirstWordFromNextLineUpCurrentSubtitleCommand), diff --git a/src/ui/Logic/Config/Language/Options/LanguageSettingsShortcuts.cs b/src/ui/Logic/Config/Language/Options/LanguageSettingsShortcuts.cs index 281c660c0a0..8c69e67d094 100644 --- a/src/ui/Logic/Config/Language/Options/LanguageSettingsShortcuts.cs +++ b/src/ui/Logic/Config/Language/Options/LanguageSettingsShortcuts.cs @@ -124,6 +124,9 @@ public class LanguageSettingsShortcuts public string MoveTextFromCursorToNextAndGoToNext { get; set; } public string MoveTextFromCursorToNextAndGoToNextAndPlay { get; set; } public string MoveTextFromCursorToNextAndGoToNextAndPlayAndPause { get; set; } + public string SplitAtVideoPositionAndPlay { get; set; } + public string SplitAtTextBoxCursorPositionAndPlay { get; set; } + public string SplitAtVideoAndTextBoxPositionAndPlay { get; set; } public string BreakAtFirstSpaceFromCursor { get; set; } public string BreakAtFirstSpaceFromCursorAndGoToNext { get; set; } public string ToggleFocusGridAndWaveform { get; set; } @@ -470,6 +473,9 @@ public LanguageSettingsShortcuts() MoveTextFromCursorToNextAndGoToNext = "Move text after cursor position to next subtitle and go to next"; MoveTextFromCursorToNextAndGoToNextAndPlay = "Move text after cursor position to next subtitle, go to next and play"; MoveTextFromCursorToNextAndGoToNextAndPlayAndPause = "Move text after cursor position to next subtitle, go to next, play it and pause at its end"; + SplitAtVideoPositionAndPlay = "Split line at video position, go to second part and play"; + SplitAtTextBoxCursorPositionAndPlay = "Split line at text box cursor position, go to second part and play"; + SplitAtVideoAndTextBoxPositionAndPlay = "Split line at video and text box position, go to second part and play"; BreakAtFirstSpaceFromCursor = "Break at first space from cursor position"; BreakAtFirstSpaceFromCursorAndGoToNext = "Break at first space from cursor position and go to next"; ToggleFocusGridAndWaveform = "Toggle focus between subtitle grid and waveform/spectrogram"; diff --git a/src/ui/Logic/ShortcutsMain.cs b/src/ui/Logic/ShortcutsMain.cs index d265c3c3d18..9e2b41db4ae 100644 --- a/src/ui/Logic/ShortcutsMain.cs +++ b/src/ui/Logic/ShortcutsMain.cs @@ -426,6 +426,9 @@ private static Dictionary BuildCommandTranslations() { nameof(MainViewModel.SplitAtVideoPositionCommand), Se.Language.General.SplitLineAtVideoPosition }, { nameof(MainViewModel.SplitAtTextBoxCursorPositionCommand), Se.Language.General.SplitAtTextBoxCursorPosition }, { nameof(MainViewModel.SplitAtVideoPositionAndTextBoxCursorPositionCommand), Se.Language.General.SplitLineAtVideoAndTextBoxPosition }, + { nameof(MainViewModel.SplitAtVideoPositionAndPlayCommand), Se.Language.Options.Shortcuts.SplitAtVideoPositionAndPlay }, + { nameof(MainViewModel.SplitAtTextBoxCursorPositionAndPlayCommand), Se.Language.Options.Shortcuts.SplitAtTextBoxCursorPositionAndPlay }, + { nameof(MainViewModel.SplitAtVideoPositionAndTextBoxCursorPositionAndPlayCommand), Se.Language.Options.Shortcuts.SplitAtVideoAndTextBoxPositionAndPlay }, { nameof(MainViewModel.TextBoxDeleteForwardCommand), Se.Language.Options.Shortcuts.TextBoxDeleteForward }, { nameof(MainViewModel.TextBoxCutCommand), Se.Language.Options.Shortcuts.TextBoxCut }, { nameof(MainViewModel.TextBoxCut2Command), Se.Language.Options.Shortcuts.TextBoxCut2 }, @@ -931,6 +934,9 @@ private static List GetAllAvailableShortcuts(MainViewModel vm AddShortcut(shortcuts, vm.SplitAtVideoPositionCommand, nameof(vm.SplitAtVideoPositionCommand), ShortcutCategory.General); AddShortcut(shortcuts, vm.SplitAtTextBoxCursorPositionCommand, nameof(vm.SplitAtTextBoxCursorPositionCommand), ShortcutCategory.TextBox); AddShortcut(shortcuts, vm.SplitAtVideoPositionAndTextBoxCursorPositionCommand, nameof(vm.SplitAtVideoPositionAndTextBoxCursorPositionCommand), ShortcutCategory.General); + AddShortcut(shortcuts, vm.SplitAtVideoPositionAndPlayCommand, nameof(vm.SplitAtVideoPositionAndPlayCommand), ShortcutCategory.General); + AddShortcut(shortcuts, vm.SplitAtTextBoxCursorPositionAndPlayCommand, nameof(vm.SplitAtTextBoxCursorPositionAndPlayCommand), ShortcutCategory.TextBox); + AddShortcut(shortcuts, vm.SplitAtVideoPositionAndTextBoxCursorPositionAndPlayCommand, nameof(vm.SplitAtVideoPositionAndTextBoxCursorPositionAndPlayCommand), ShortcutCategory.General); AddShortcut(shortcuts, vm.TextBoxDeleteForwardCommand, nameof(vm.TextBoxDeleteForwardCommand), ShortcutCategory.TextBox); AddShortcut(shortcuts, vm.TextBoxCutCommand, nameof(vm.TextBoxCutCommand), ShortcutCategory.TextBox); AddShortcut(shortcuts, vm.TextBoxCut2Command, nameof(vm.TextBoxCut2Command), ShortcutCategory.TextBox); From bff3deeef942af4595f8e57c880f7df01dcf13ab Mon Sep 17 00:00:00 2001 From: Nikolaj Olsson Date: Sat, 17 Oct 2026 13:56:33 +0200 Subject: [PATCH 300/306] Add "move lines one frame back/forward" shortcuts (#9367) Selected lines, selected lines and all following, and all lines, each by one frame of the current frame rate. Co-Authored-By: Claude Opus 5.5 --- src/ui/Features/Main/MainViewModel.cs | 19 +++++++++++++++++++ .../Options/LanguageSettingsShortcuts.cs | 12 ++++++++++++ src/ui/Logic/ShortcutsMain.cs | 12 ++++++++++++ 3 files changed, 43 insertions(+) diff --git a/src/ui/Features/Main/MainViewModel.cs b/src/ui/Features/Main/MainViewModel.cs index b6673e5fccf..4d27c85ea2e 100644 --- a/src/ui/Features/Main/MainViewModel.cs +++ b/src/ui/Features/Main/MainViewModel.cs @@ -16531,6 +16531,25 @@ private static string StripLeadingDashes(string s) [RelayCommand] private void MoveAllLinesXMsForward() => MoveLinesByStep(1, MoveLinesScope.All); + // #9367: the same moves by exactly one frame of the current frame rate. + [RelayCommand] + private void MoveSelectedLinesOneFrameBack() => MoveLinesByMs(FramesToMilliseconds(-1), MoveLinesScope.Selected); + + [RelayCommand] + private void MoveSelectedLinesOneFrameForward() => MoveLinesByMs(FramesToMilliseconds(1), MoveLinesScope.Selected); + + [RelayCommand] + private void MoveSelectedLinesAndForwardOneFrameBack() => MoveLinesByMs(FramesToMilliseconds(-1), MoveLinesScope.SelectedAndForward); + + [RelayCommand] + private void MoveSelectedLinesAndForwardOneFrameForward() => MoveLinesByMs(FramesToMilliseconds(1), MoveLinesScope.SelectedAndForward); + + [RelayCommand] + private void MoveAllLinesOneFrameBack() => MoveLinesByMs(FramesToMilliseconds(-1), MoveLinesScope.All); + + [RelayCommand] + private void MoveAllLinesOneFrameForward() => MoveLinesByMs(FramesToMilliseconds(1), MoveLinesScope.All); + // Per-shortcut steps, configured with the gear button in Options > Shortcuts (like the custom // video seek amounts). A slot's value is shared by its back and forward commands. [RelayCommand] diff --git a/src/ui/Logic/Config/Language/Options/LanguageSettingsShortcuts.cs b/src/ui/Logic/Config/Language/Options/LanguageSettingsShortcuts.cs index 8c69e67d094..7f7ac71a1fb 100644 --- a/src/ui/Logic/Config/Language/Options/LanguageSettingsShortcuts.cs +++ b/src/ui/Logic/Config/Language/Options/LanguageSettingsShortcuts.cs @@ -340,6 +340,12 @@ public class LanguageSettingsShortcuts public string MoveSelectedLinesAndForwardXMsForward { get; set; } public string MoveAllLinesXMsBack { get; set; } public string MoveAllLinesXMsForward { get; set; } + public string MoveSelectedLinesOneFrameBack { get; set; } + public string MoveSelectedLinesOneFrameForward { get; set; } + public string MoveSelectedLinesAndForwardOneFrameBack { get; set; } + public string MoveSelectedLinesAndForwardOneFrameForward { get; set; } + public string MoveAllLinesOneFrameBack { get; set; } + public string MoveAllLinesOneFrameForward { get; set; } public string MoveSelectedLinesCustomMsBack { get; set; } public string MoveSelectedLinesCustomMsForward { get; set; } public string MoveSelectedLinesAndForwardCustomMsBack { get; set; } @@ -689,6 +695,12 @@ public LanguageSettingsShortcuts() MoveSelectedLinesAndForwardXMsForward = "Move selected lines and all following X ms forward (X set in Settings)"; MoveAllLinesXMsBack = "Move all lines X ms back (X set in Settings)"; MoveAllLinesXMsForward = "Move all lines X ms forward (X set in Settings)"; + MoveSelectedLinesOneFrameBack = "Move selected lines one frame back"; + MoveSelectedLinesOneFrameForward = "Move selected lines one frame forward"; + MoveSelectedLinesAndForwardOneFrameBack = "Move selected lines and all following one frame back"; + MoveSelectedLinesAndForwardOneFrameForward = "Move selected lines and all following one frame forward"; + MoveAllLinesOneFrameBack = "Move all lines one frame back"; + MoveAllLinesOneFrameForward = "Move all lines one frame forward"; MoveSelectedLinesCustomMsBack = "Move selected lines, custom milliseconds ({0:#,###,##0}) back, {1}"; MoveSelectedLinesCustomMsForward = "Move selected lines, custom milliseconds ({0:#,###,##0}) forward, {1}"; MoveSelectedLinesAndForwardCustomMsBack = "Move selected lines and all following, custom milliseconds ({0:#,###,##0}) back, {1}"; diff --git a/src/ui/Logic/ShortcutsMain.cs b/src/ui/Logic/ShortcutsMain.cs index 9e2b41db4ae..bc567cb58fa 100644 --- a/src/ui/Logic/ShortcutsMain.cs +++ b/src/ui/Logic/ShortcutsMain.cs @@ -551,6 +551,12 @@ private static Dictionary BuildCommandTranslations() { nameof(MainViewModel.MoveSelectedLinesAndForwardXMsForwardCommand), Se.Language.Options.Shortcuts.MoveSelectedLinesAndForwardXMsForward }, { nameof(MainViewModel.MoveAllLinesXMsBackCommand), Se.Language.Options.Shortcuts.MoveAllLinesXMsBack }, { nameof(MainViewModel.MoveAllLinesXMsForwardCommand), Se.Language.Options.Shortcuts.MoveAllLinesXMsForward }, + { nameof(MainViewModel.MoveSelectedLinesOneFrameBackCommand), Se.Language.Options.Shortcuts.MoveSelectedLinesOneFrameBack }, + { nameof(MainViewModel.MoveSelectedLinesOneFrameForwardCommand), Se.Language.Options.Shortcuts.MoveSelectedLinesOneFrameForward }, + { nameof(MainViewModel.MoveSelectedLinesAndForwardOneFrameBackCommand), Se.Language.Options.Shortcuts.MoveSelectedLinesAndForwardOneFrameBack }, + { nameof(MainViewModel.MoveSelectedLinesAndForwardOneFrameForwardCommand), Se.Language.Options.Shortcuts.MoveSelectedLinesAndForwardOneFrameForward }, + { nameof(MainViewModel.MoveAllLinesOneFrameBackCommand), Se.Language.Options.Shortcuts.MoveAllLinesOneFrameBack }, + { nameof(MainViewModel.MoveAllLinesOneFrameForwardCommand), Se.Language.Options.Shortcuts.MoveAllLinesOneFrameForward }, { nameof(MainViewModel.MoveSelectedLinesCustom1BackCommand), GetMoveLinesCustomTitle(MoveLinesScope.Selected, 1, back: true) }, { nameof(MainViewModel.MoveSelectedLinesCustom1ForwardCommand), GetMoveLinesCustomTitle(MoveLinesScope.Selected, 1, back: false) }, { nameof(MainViewModel.MoveSelectedLinesCustom2BackCommand), GetMoveLinesCustomTitle(MoveLinesScope.Selected, 2, back: true) }, @@ -1122,6 +1128,12 @@ private static List GetAllAvailableShortcuts(MainViewModel vm AddShortcut(shortcuts, vm.MoveSelectedLinesAndForwardXMsForwardCommand, nameof(vm.MoveSelectedLinesAndForwardXMsForwardCommand), ShortcutCategory.General); AddShortcut(shortcuts, vm.MoveAllLinesXMsBackCommand, nameof(vm.MoveAllLinesXMsBackCommand), ShortcutCategory.General); AddShortcut(shortcuts, vm.MoveAllLinesXMsForwardCommand, nameof(vm.MoveAllLinesXMsForwardCommand), ShortcutCategory.General); + AddShortcut(shortcuts, vm.MoveSelectedLinesOneFrameBackCommand, nameof(vm.MoveSelectedLinesOneFrameBackCommand), ShortcutCategory.General); + AddShortcut(shortcuts, vm.MoveSelectedLinesOneFrameForwardCommand, nameof(vm.MoveSelectedLinesOneFrameForwardCommand), ShortcutCategory.General); + AddShortcut(shortcuts, vm.MoveSelectedLinesAndForwardOneFrameBackCommand, nameof(vm.MoveSelectedLinesAndForwardOneFrameBackCommand), ShortcutCategory.General); + AddShortcut(shortcuts, vm.MoveSelectedLinesAndForwardOneFrameForwardCommand, nameof(vm.MoveSelectedLinesAndForwardOneFrameForwardCommand), ShortcutCategory.General); + AddShortcut(shortcuts, vm.MoveAllLinesOneFrameBackCommand, nameof(vm.MoveAllLinesOneFrameBackCommand), ShortcutCategory.General); + AddShortcut(shortcuts, vm.MoveAllLinesOneFrameForwardCommand, nameof(vm.MoveAllLinesOneFrameForwardCommand), ShortcutCategory.General); AddShortcut(shortcuts, vm.MoveSelectedLinesCustom1BackCommand, nameof(vm.MoveSelectedLinesCustom1BackCommand), ShortcutCategory.General); AddShortcut(shortcuts, vm.MoveSelectedLinesCustom1ForwardCommand, nameof(vm.MoveSelectedLinesCustom1ForwardCommand), ShortcutCategory.General); AddShortcut(shortcuts, vm.MoveSelectedLinesCustom2BackCommand, nameof(vm.MoveSelectedLinesCustom2BackCommand), ShortcutCategory.General); From 1f1983ce73a1c9632ad44c806966b375d1c4d040 Mon Sep 17 00:00:00 2001 From: Nikolaj Olsson Date: Sat, 17 Oct 2026 14:02:04 +0200 Subject: [PATCH 301/306] Add "move text before cursor position to previous subtitle" (#9537) The mirror of "move text after cursor position to next subtitle": the text before the caret is appended to the previous line and the rest stays on the current one. Works on whichever text box has focus. Co-Authored-By: Claude Opus 5.5 --- src/ui/Features/Main/MainViewModel.cs | 55 +++++++++++++ .../Options/LanguageSettingsShortcuts.cs | 2 + src/ui/Logic/ShortcutsMain.cs | 2 + .../Features/Main/MainWordMoveFocusTests.cs | 79 +++++++++++++++++++ 4 files changed, 138 insertions(+) diff --git a/src/ui/Features/Main/MainViewModel.cs b/src/ui/Features/Main/MainViewModel.cs index 4d27c85ea2e..27c079d07ff 100644 --- a/src/ui/Features/Main/MainViewModel.cs +++ b/src/ui/Features/Main/MainViewModel.cs @@ -22210,6 +22210,61 @@ private void MoveTextFromCursorToNextAndGoToNextAndPlayAndPause() WithoutReferenceOnlyRows(() => MoveTextFromCursorToNext(play: true, pauseAtEnd: true)); } + [RelayCommand] + private void MoveTextBeforeCursorToPrevious() + { + WithoutReferenceOnlyRows(MoveTextBeforeCursorToPreviousCore); + } + + /// + /// The mirror of (#9537): the text before the caret is + /// appended to the previous subtitle and the rest stays on the current line, which stays + /// selected with the caret at its start - a speech-to-text line that started too early is + /// fixed in one keystroke instead of a split plus a merge. + /// + private void MoveTextBeforeCursorToPreviousCore() + { + var s = SelectedSubtitle; + if (s == null || s.IsReferenceOnly) + { + return; + } + + var index = Subtitles.IndexOf(s); + var prev = index < 0 ? null : GetPreviousWorkingRow(index); + if (prev == null) + { + return; + } + + var isOriginal = EditTextBoxOriginal.IsFocused && CanEditOriginal; + var textBox = isOriginal ? EditTextBoxOriginal : EditTextBox; + var currentText = (isOriginal ? s.OriginalText : s.Text) ?? string.Empty; + + var caret = Math.Clamp(textBox.SelectionStart, 0, currentText.Length); + var textBefore = currentText.Substring(0, caret).Trim(); + var textAfter = currentText.Substring(caret).Trim(); + if (textBefore.Length == 0) + { + return; + } + + if (isOriginal) + { + prev.OriginalText = ((prev.OriginalText ?? string.Empty).Trim() + Environment.NewLine + textBefore).Trim(); + s.OriginalText = textAfter; + } + else + { + prev.Text = (prev.Text.Trim() + Environment.NewLine + textBefore).Trim(); + s.Text = textAfter; + } + + // The text box picks up the new text through its binding; put the caret at the start after that. + Dispatcher.UIThread.Post(() => textBox.Select(0, 0)); + _updateAudioVisualizer = true; + } + private void MoveTextFromCursorToNext(bool play, bool pauseAtEnd = false) { var s = SelectedSubtitle; diff --git a/src/ui/Logic/Config/Language/Options/LanguageSettingsShortcuts.cs b/src/ui/Logic/Config/Language/Options/LanguageSettingsShortcuts.cs index 7f7ac71a1fb..c53f6e59a61 100644 --- a/src/ui/Logic/Config/Language/Options/LanguageSettingsShortcuts.cs +++ b/src/ui/Logic/Config/Language/Options/LanguageSettingsShortcuts.cs @@ -124,6 +124,7 @@ public class LanguageSettingsShortcuts public string MoveTextFromCursorToNextAndGoToNext { get; set; } public string MoveTextFromCursorToNextAndGoToNextAndPlay { get; set; } public string MoveTextFromCursorToNextAndGoToNextAndPlayAndPause { get; set; } + public string MoveTextBeforeCursorToPrevious { get; set; } public string SplitAtVideoPositionAndPlay { get; set; } public string SplitAtTextBoxCursorPositionAndPlay { get; set; } public string SplitAtVideoAndTextBoxPositionAndPlay { get; set; } @@ -479,6 +480,7 @@ public LanguageSettingsShortcuts() MoveTextFromCursorToNextAndGoToNext = "Move text after cursor position to next subtitle and go to next"; MoveTextFromCursorToNextAndGoToNextAndPlay = "Move text after cursor position to next subtitle, go to next and play"; MoveTextFromCursorToNextAndGoToNextAndPlayAndPause = "Move text after cursor position to next subtitle, go to next, play it and pause at its end"; + MoveTextBeforeCursorToPrevious = "Move text before cursor position to previous subtitle"; SplitAtVideoPositionAndPlay = "Split line at video position, go to second part and play"; SplitAtTextBoxCursorPositionAndPlay = "Split line at text box cursor position, go to second part and play"; SplitAtVideoAndTextBoxPositionAndPlay = "Split line at video and text box position, go to second part and play"; diff --git a/src/ui/Logic/ShortcutsMain.cs b/src/ui/Logic/ShortcutsMain.cs index bc567cb58fa..f71a2a2c684 100644 --- a/src/ui/Logic/ShortcutsMain.cs +++ b/src/ui/Logic/ShortcutsMain.cs @@ -591,6 +591,7 @@ private static Dictionary BuildCommandTranslations() { nameof(MainViewModel.MoveTextFromCursorToNextAndGoToNextCommand), Se.Language.Options.Shortcuts.MoveTextFromCursorToNextAndGoToNext }, { nameof(MainViewModel.MoveTextFromCursorToNextAndGoToNextAndPlayCommand), Se.Language.Options.Shortcuts.MoveTextFromCursorToNextAndGoToNextAndPlay }, { nameof(MainViewModel.MoveTextFromCursorToNextAndGoToNextAndPlayAndPauseCommand), Se.Language.Options.Shortcuts.MoveTextFromCursorToNextAndGoToNextAndPlayAndPause }, + { nameof(MainViewModel.MoveTextBeforeCursorToPreviousCommand), Se.Language.Options.Shortcuts.MoveTextBeforeCursorToPrevious }, { nameof(MainViewModel.BreakAtFirstSpaceFromCursorCommand), Se.Language.Options.Shortcuts.BreakAtFirstSpaceFromCursor }, { nameof(MainViewModel.BreakAtFirstSpaceFromCursorAndGoToNextCommand), Se.Language.Options.Shortcuts.BreakAtFirstSpaceFromCursorAndGoToNext }, { nameof(MainViewModel.ToggleFocusGridAndWaveformCommand), Se.Language.Options.Shortcuts.ToggleFocusGridAndWaveform }, @@ -1160,6 +1161,7 @@ private static List GetAllAvailableShortcuts(MainViewModel vm AddShortcut(shortcuts, vm.MoveTextFromCursorToNextAndGoToNextCommand, nameof(vm.MoveTextFromCursorToNextAndGoToNextCommand), ShortcutCategory.General); AddShortcut(shortcuts, vm.MoveTextFromCursorToNextAndGoToNextAndPlayCommand, nameof(vm.MoveTextFromCursorToNextAndGoToNextAndPlayCommand), ShortcutCategory.General); AddShortcut(shortcuts, vm.MoveTextFromCursorToNextAndGoToNextAndPlayAndPauseCommand, nameof(vm.MoveTextFromCursorToNextAndGoToNextAndPlayAndPauseCommand), ShortcutCategory.General); + AddShortcut(shortcuts, vm.MoveTextBeforeCursorToPreviousCommand, nameof(vm.MoveTextBeforeCursorToPreviousCommand), ShortcutCategory.General); AddShortcut(shortcuts, vm.ToggleFocusGridAndWaveformCommand, nameof(vm.ToggleFocusGridAndWaveformCommand), ShortcutCategory.General); AddShortcut(shortcuts, vm.ToggleFocusTextBoxAndWaveformCommand, nameof(vm.ToggleFocusTextBoxAndWaveformCommand), ShortcutCategory.General); AddShortcut(shortcuts, vm.ToggleFocusTextBoxAndSubtitleGridCommand, nameof(vm.ToggleFocusTextBoxAndSubtitleGridCommand), ShortcutCategory.General); diff --git a/tests/UI/Features/Main/MainWordMoveFocusTests.cs b/tests/UI/Features/Main/MainWordMoveFocusTests.cs index 876d57decc4..494e5e01f42 100644 --- a/tests/UI/Features/Main/MainWordMoveFocusTests.cs +++ b/tests/UI/Features/Main/MainWordMoveFocusTests.cs @@ -331,6 +331,85 @@ public void MoveFirstWordToPrevious_IsRegisteredAsShortcutAndMappedFromSe4() /// Swaps in an original text box wrapper that reports the requested focus state. Headless /// windows cannot reliably move keyboard focus, and the commands only read IsFocused. /// + [AvaloniaFact] + public void MoveTextBeforeCursorToPrevious_TranslationFocused_AppendsTextBeforeCaretToPrevious() + { + var (window, vm) = CreateMainViewModel(); + try + { + var first = AddLine(vm, "Hello", "Hej"); + var second = AddLine(vm, "big world again", "store verden igen", 2000, 4000); + vm.SelectedSubtitle = second; + FocusOriginal(vm, editable: true, focused: false); + vm.EditTextBox = new FocusStubTextBoxWrapper(new TextBox { Text = second.Text }, isFocused: true); + vm.EditTextBox.SelectionStart = "big world".Length; + vm.EditTextBox.SelectionEnd = "big world".Length; + + vm.MoveTextBeforeCursorToPreviousCommand.Execute(null); + + Assert.Equal("Hello" + Environment.NewLine + "big world", first.Text); + Assert.Equal("again", second.Text); + Assert.Equal("Hej", first.OriginalText); + Assert.Equal("store verden igen", second.OriginalText); + } + finally + { + CloseWindow(window, vm); + } + } + + [AvaloniaFact] + public void MoveTextBeforeCursorToPrevious_OriginalFocused_MovesOriginalTextOnly() + { + var (window, vm) = CreateMainViewModel(); + try + { + var first = AddLine(vm, "Hello", "Hej"); + var second = AddLine(vm, "big world", "store verden", 2000, 4000); + vm.SelectedSubtitle = second; + vm.ShowColumnOriginalText = true; + vm.IsOriginalReadOnly = false; + vm.EditTextBoxOriginal = new FocusStubTextBoxWrapper(new TextBox { Text = second.OriginalText }, isFocused: true); + vm.EditTextBoxOriginal.SelectionStart = "store".Length; + vm.EditTextBoxOriginal.SelectionEnd = "store".Length; + + vm.MoveTextBeforeCursorToPreviousCommand.Execute(null); + + Assert.Equal("Hej" + Environment.NewLine + "store", first.OriginalText); + Assert.Equal("verden", second.OriginalText); + Assert.Equal("Hello", first.Text); + Assert.Equal("big world", second.Text); + } + finally + { + CloseWindow(window, vm); + } + } + + [AvaloniaFact] + public void MoveTextBeforeCursorToPrevious_FirstLine_DoesNothing() + { + var (window, vm) = CreateMainViewModel(); + try + { + var first = AddLine(vm, "big world", "store verden"); + vm.SelectedSubtitle = first; + FocusOriginal(vm, editable: true, focused: false); + vm.EditTextBox = new FocusStubTextBoxWrapper(new TextBox { Text = first.Text }, isFocused: true); + vm.EditTextBox.SelectionStart = 3; + vm.EditTextBox.SelectionEnd = 3; + + vm.MoveTextBeforeCursorToPreviousCommand.Execute(null); + + Assert.Equal("big world", first.Text); + Assert.Single(vm.Subtitles); + } + finally + { + CloseWindow(window, vm); + } + } + private static void FocusOriginal(MainViewModel vm, bool editable, bool focused = true) { vm.ShowColumnOriginalText = true; From 77b7fe921ea0980a30329178994fe00fc09a098e Mon Sep 17 00:00:00 2001 From: Nikolaj Olsson Date: Sat, 17 Oct 2026 14:02:04 +0200 Subject: [PATCH 302/306] Modify selection: add "Has errors" rule (#9621) Selects the lines the subtitle grid highlights as errors (the same rules as List errors / Go to next error), so they can be fixed in one go with e.g. Break/split long lines. Co-Authored-By: Claude Opus 5.5 --- .../ModifySelection/ModifySelectionRule.cs | 13 ++++++++- .../ModifySelectionViewModel.cs | 17 ++++++++--- .../Features/Edit/ModifySelection/RuleType.cs | 1 + .../Language/Edit/LanguageModifySelection.cs | 2 ++ .../ModifySelectionViewModelTests.cs | 29 +++++++++++++++++++ 5 files changed, 57 insertions(+), 5 deletions(-) diff --git a/src/ui/Features/Edit/ModifySelection/ModifySelectionRule.cs b/src/ui/Features/Edit/ModifySelection/ModifySelectionRule.cs index 2285f8e6b4b..9d7b5f307d3 100644 --- a/src/ui/Features/Edit/ModifySelection/ModifySelectionRule.cs +++ b/src/ui/Features/Edit/ModifySelection/ModifySelectionRule.cs @@ -247,6 +247,11 @@ public static List List(List lines) Name = l.Forced, }, new() + { + RuleType = RuleType.HasErrors, + Name = l.HasErrors, + }, + new() { RuleType = RuleType.BlankLines, Name = Se.Language.General.BlankLines, @@ -288,7 +293,9 @@ public static List List(List lines) return list; } - internal bool IsMatch(SubtitleLineViewModel item) + /// The line before (gap and overlap errors), if any. + /// The line after (gap and overlap errors), if any. + internal bool IsMatch(SubtitleLineViewModel item, SubtitleLineViewModel? prev = null, SubtitleLineViewModel? next = null) { // Evaluate based on RuleType var text = item.Text ?? string.Empty; @@ -434,6 +441,10 @@ internal bool IsMatch(SubtitleLineViewModel item) case RuleType.Forced: return item.Forced; + // The same rules that color a line in the subtitle grid (#9621). + case RuleType.HasErrors: + return item.HasErrors(prev, next); + case RuleType.BlankLines: return string.IsNullOrWhiteSpace(text); diff --git a/src/ui/Features/Edit/ModifySelection/ModifySelectionViewModel.cs b/src/ui/Features/Edit/ModifySelection/ModifySelectionViewModel.cs index 69b4c1a8db3..6a4cb0015ae 100644 --- a/src/ui/Features/Edit/ModifySelection/ModifySelectionViewModel.cs +++ b/src/ui/Features/Edit/ModifySelection/ModifySelectionViewModel.cs @@ -99,9 +99,10 @@ private void UpdatePreview() Dispatcher.UIThread.Post(() => { var matches = new List(); - foreach (var item in _allSubtitles) + for (var i = 0; i < _allSubtitles.Count; i++) { - if (rule.IsMatch(item)) + var item = _allSubtitles[i]; + if (IsMatch(rule, i)) { matches.Add(new PreviewItem(item.Number, true, item.StartTime, item.Duration, item.Text, item)); } @@ -111,6 +112,13 @@ private void UpdatePreview() }); } + private bool IsMatch(ModifySelectionRule rule, int index) + { + var prev = index > 0 ? _allSubtitles[index - 1] : null; + var next = index < _allSubtitles.Count - 1 ? _allSubtitles[index + 1] : null; + return rule.IsMatch(_allSubtitles[index], prev, next); + } + private void LoadSettings() { var selectionMode = Se.Settings.Edit.ModifySelectionMode.ToLowerInvariant(); @@ -185,9 +193,10 @@ private void Ok() { var excluded = new HashSet( Subtitles.Where(s => !s.Apply).Select(s => s.Subtitle)); - foreach (var item in _allSubtitles) + for (var i = 0; i < _allSubtitles.Count; i++) { - if (rule.IsMatch(item) && !excluded.Contains(item)) + var item = _allSubtitles[i]; + if (IsMatch(rule, i) && !excluded.Contains(item)) { Selection.Add(item); } diff --git a/src/ui/Features/Edit/ModifySelection/RuleType.cs b/src/ui/Features/Edit/ModifySelection/RuleType.cs index ab856ef3545..40883db775e 100644 --- a/src/ui/Features/Edit/ModifySelection/RuleType.cs +++ b/src/ui/Features/Edit/ModifySelection/RuleType.cs @@ -25,6 +25,7 @@ public enum RuleType Bookmarked, BookmarkContains, Forced, + HasErrors, BlankLines, HearingImpaired, Style, diff --git a/src/ui/Logic/Config/Language/Edit/LanguageModifySelection.cs b/src/ui/Logic/Config/Language/Edit/LanguageModifySelection.cs index f68829a2eef..ba44287de29 100644 --- a/src/ui/Logic/Config/Language/Edit/LanguageModifySelection.cs +++ b/src/ui/Logic/Config/Language/Edit/LanguageModifySelection.cs @@ -27,6 +27,7 @@ public class LanguageModifySelection public string MoreThanTwoLines { get; set; } public string Bookmarked { get; set; } public string Forced { get; set; } = string.Empty; + public string HasErrors { get; set; } = string.Empty; public string BookmarkContains { get; set; } public string HearingImpaired { get; set; } public string HearingImpairedBrackets { get; set; } @@ -68,6 +69,7 @@ public LanguageModifySelection() MoreThanTwoLines = "More than two lines"; Bookmarked = "Bookmarked"; Forced = "Forced"; + HasErrors = "Has errors (highlighted in the list)"; BookmarkContains = "Bookmark contains"; HearingImpaired = "Hearing impaired (SDH)"; HearingImpairedBrackets = "Brackets [...]"; diff --git a/tests/UI/Features/Edit/ModifySelection/ModifySelectionViewModelTests.cs b/tests/UI/Features/Edit/ModifySelection/ModifySelectionViewModelTests.cs index 1ff56b0b433..a7db3111d45 100644 --- a/tests/UI/Features/Edit/ModifySelection/ModifySelectionViewModelTests.cs +++ b/tests/UI/Features/Edit/ModifySelection/ModifySelectionViewModelTests.cs @@ -170,4 +170,33 @@ public void Ok_OnNonNumericRule_DoesNotOverwriteSavedNumericThreshold() Se.Settings = originalSettings; } } + + [Fact] + public void HasErrorsRule_MatchesLinesTheGridHighlights() + { + var originalSettings = Se.Settings; + try + { + Se.Settings = new Se(); + Se.Settings.General.ColorDurationTooShort = true; + Se.Settings.General.SubtitleMinimumDisplayMilliseconds = 1000; + Se.Settings.General.ColorTimeCodeOverlap = true; + + var ok = new SubtitleLineViewModel(new Nikse.SubtitleEdit.Core.Common.Paragraph("Fine", 0, 2000), null!); + var tooShort = new SubtitleLineViewModel(new Nikse.SubtitleEdit.Core.Common.Paragraph("Short", 3000, 3200), null!); + var overlapped = new SubtitleLineViewModel(new Nikse.SubtitleEdit.Core.Common.Paragraph("Overlap", 5000, 7000), null!); + var overlapping = new SubtitleLineViewModel(new Nikse.SubtitleEdit.Core.Common.Paragraph("Overlap 2", 6500, 9000), null!); + + var rule = ModifySelectionRule.List(new List()).Single(r => r.RuleType == RuleType.HasErrors); + + Assert.False(rule.IsMatch(ok, null, tooShort)); + Assert.True(rule.IsMatch(tooShort, ok, overlapped)); + Assert.True(rule.IsMatch(overlapped, tooShort, overlapping)); // overlap is a neighbour error + Assert.False(rule.IsMatch(overlapped)); // ...that needs the neighbour + } + finally + { + Se.Settings = originalSettings; + } + } } From c2676bdfc883aa1b1b4e29d646ef337faecf2571 Mon Sep 17 00:00:00 2001 From: Nikolaj Olsson Date: Sat, 17 Oct 2026 14:02:05 +0200 Subject: [PATCH 303/306] Spell check: remember window position (#9762) Co-Authored-By: Claude Opus 5.5 --- src/ui/Features/SpellCheck/SpellCheckWindow.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/ui/Features/SpellCheck/SpellCheckWindow.cs b/src/ui/Features/SpellCheck/SpellCheckWindow.cs index 3ede5ca865e..ab5336b5ac4 100644 --- a/src/ui/Features/SpellCheck/SpellCheckWindow.cs +++ b/src/ui/Features/SpellCheck/SpellCheckWindow.cs @@ -78,6 +78,11 @@ public SpellCheckWindow(SpellCheckViewModel vm) // bare Space instead. UiUtil.FocusOnFirstActivation(this, textBoxWord); // hack to make OnKeyDown work KeyDown += (_, e) => vm.OnKeyDown(e); + + // Remember where the user parked the window, e.g. off to the side of the text (#9762). + // The size is not restored: the window is not resizable and sizes to its content. + Closing += delegate { UiUtil.SaveWindowPosition(this); }; + Loaded += delegate { UiUtil.RestoreWindowPosition(this); }; } private static TextBox MakeWordTextBox(SpellCheckViewModel vm) From f64ab159d2966af5308303cdd0346b79418514b8 Mon Sep 17 00:00:00 2001 From: Nikolaj Olsson Date: Sat, 17 Oct 2026 14:14:58 +0200 Subject: [PATCH 304/306] Add custom error rules: regex rules that color lines as errors (#10136) A new "Custom error rules" window manages a list of rules (name, regular expression, case sensitive, color, enabled) plus an on/off switch. A line whose text matches an enabled rule gets the rule's color in the text cell and counts as an error, so List errors, Go to next error and Modify selection > Has errors find it too - e.g. "[CHECK]" markers left by a translation pipeline. Open it from Settings > Syntax coloring or the new "Show custom error rules" shortcut; "Toggle custom error rules coloring on/off" switches the highlighting. Co-Authored-By: Claude Opus 5.5 --- src/ui/DependencyInjectionExtensions.cs | 2 + src/ui/Features/Main/MainViewModel.cs | 43 ++++ src/ui/Features/Main/SubtitleLineViewModel.cs | 43 +++- .../CustomErrorRules/CustomErrorRuleItem.cs | 64 +++++ .../CustomErrorRulesViewModel.cs | 189 +++++++++++++++ .../CustomErrorRulesWindow.cs | 220 ++++++++++++++++++ .../SettingsImportExportViewModel.cs | 2 + .../Features/Options/Settings/SettingsPage.cs | 6 + .../Options/Settings/SettingsViewModel.cs | 37 +++ src/ui/Features/Shared/ErrorList/LineError.cs | 7 + .../Config/Language/LanguageErrorList.cs | 4 + .../Language/Options/LanguageSettings.cs | 18 ++ .../Options/LanguageSettingsShortcuts.cs | 4 + src/ui/Logic/Config/SeCustomErrorRule.cs | 26 +++ src/ui/Logic/Config/SeGeneral.cs | 3 + src/ui/Logic/CustomErrorRuleMatcher.cs | 148 ++++++++++++ src/ui/Logic/ShortcutsMain.cs | 4 + tests/UI/Logic/CustomErrorRuleMatcherTests.cs | 143 ++++++++++++ 18 files changed, 961 insertions(+), 2 deletions(-) create mode 100644 src/ui/Features/Options/CustomErrorRules/CustomErrorRuleItem.cs create mode 100644 src/ui/Features/Options/CustomErrorRules/CustomErrorRulesViewModel.cs create mode 100644 src/ui/Features/Options/CustomErrorRules/CustomErrorRulesWindow.cs create mode 100644 src/ui/Logic/Config/SeCustomErrorRule.cs create mode 100644 src/ui/Logic/CustomErrorRuleMatcher.cs create mode 100644 tests/UI/Logic/CustomErrorRuleMatcherTests.cs diff --git a/src/ui/DependencyInjectionExtensions.cs b/src/ui/DependencyInjectionExtensions.cs index f4b125aa495..084bf4e3b3e 100644 --- a/src/ui/DependencyInjectionExtensions.cs +++ b/src/ui/DependencyInjectionExtensions.cs @@ -55,6 +55,7 @@ using Nikse.SubtitleEdit.Features.Ocr.FixEngine; using Nikse.SubtitleEdit.Features.Ocr.NOcr; using Nikse.SubtitleEdit.Features.Ocr.VobSubColorChooser; +using Nikse.SubtitleEdit.Features.Options.CustomErrorRules; using Nikse.SubtitleEdit.Features.Options.DoNotBreakAfterList; using Nikse.SubtitleEdit.Features.Options.Language; using Nikse.SubtitleEdit.Features.Options.Plugins; @@ -631,6 +632,7 @@ public static void AddSubtitleEditServices(this IServiceCollection collection) collection.AddTransient(); collection.AddTransient(); collection.AddTransient(); + collection.AddTransient(); collection.AddTransient(); collection.AddTransient(); collection.AddTransient(); diff --git a/src/ui/Features/Main/MainViewModel.cs b/src/ui/Features/Main/MainViewModel.cs index 27c079d07ff..02aaa516891 100644 --- a/src/ui/Features/Main/MainViewModel.cs +++ b/src/ui/Features/Main/MainViewModel.cs @@ -80,6 +80,7 @@ using Nikse.SubtitleEdit.Features.Main.Layout; using Nikse.SubtitleEdit.Features.Main.MainHelpers; using Nikse.SubtitleEdit.Features.Ocr; +using Nikse.SubtitleEdit.Features.Options.CustomErrorRules; using Nikse.SubtitleEdit.Features.Options.Shortcuts.CustomShortcuts; using Nikse.SubtitleEdit.Features.Options.Language; using Nikse.SubtitleEdit.Features.Options.Plugins; @@ -2111,6 +2112,47 @@ private async Task ShowHelp() UiUtil.ShowHelp("features/main-window"); } + /// The user's regex rules that color a line as an error (#10136). + [RelayCommand] + private async Task ShowCustomErrorRules() + { + var general = Se.Settings.General; + var result = await ShowDialogAsync(vm => + { + vm.Initialize(general.ColorCustomErrorRules, general.CustomErrorRules); + }); + + if (!result.OkPressed) + { + return; + } + + general.ColorCustomErrorRules = result.IsHighlightEnabled; + general.CustomErrorRules = result.GetRules(); + Se.SaveSettings(); + RefreshCustomErrorRules(); + } + + [RelayCommand] + private void ToggleCustomErrorRules() + { + var general = Se.Settings.General; + general.ColorCustomErrorRules = !general.ColorCustomErrorRules; + RefreshCustomErrorRules(); + ShowStatus(general.ColorCustomErrorRules + ? Se.Language.Options.Settings.CustomErrorRulesOn + : Se.Language.Options.Settings.CustomErrorRulesOff); + } + + private void RefreshCustomErrorRules() + { + CustomErrorRuleMatcher.Invalidate(); + foreach (var row in Subtitles) + { + row.RefreshAfterSettingsChanged(); + } + } + [RelayCommand] private async Task ShowSourceView() { @@ -15039,6 +15081,7 @@ public void ApplySettings() UpdateVideoOffsetStatus(); SetLibSeSettings(); SubtitleLineViewModel.ErrorColor = Se.Settings.General.ErrorColor.FromHexToColor(); + CustomErrorRuleMatcher.Invalidate(); // Settings that feed into CPS / line-length / colour calculations // (CpsLineLengthStrategy, SubtitleLineMaximumLength, the various diff --git a/src/ui/Features/Main/SubtitleLineViewModel.cs b/src/ui/Features/Main/SubtitleLineViewModel.cs index bca6c1746f5..d7343d65e6a 100644 --- a/src/ui/Features/Main/SubtitleLineViewModel.cs +++ b/src/ui/Features/Main/SubtitleLineViewModel.cs @@ -626,6 +626,31 @@ public double WordsPerMinute // WPM private TextErrorSettings _textErrorCacheSettings; private bool _textErrorCacheValue; + // The same memo for the user's custom error rules (#10136): keyed on the text and the + // compiled rule set, which is a new instance whenever the rules or their switch change. + private string? _customRuleCacheText; + private CustomErrorRuleMatcher.RuleSet? _customRuleCacheRules; + private CustomErrorRuleMatcher.CompiledRule? _customRuleCacheValue; + + /// The first custom error rule whose pattern matches the text, or null. + public CustomErrorRuleMatcher.CompiledRule? GetMatchingCustomErrorRule() + { + var rules = CustomErrorRuleMatcher.Current; + if (rules.Rules.Count == 0) + { + return null; + } + + if (!ReferenceEquals(_customRuleCacheText, Text) || !ReferenceEquals(_customRuleCacheRules, rules)) + { + _customRuleCacheText = Text; + _customRuleCacheRules = rules; + _customRuleCacheValue = rules.Match(Text); + } + + return _customRuleCacheValue; + } + private readonly record struct TextErrorSettings( bool ColorTextTooLong, int MaxLineLength, @@ -658,7 +683,10 @@ public static TextErrorSettings Current() } } - public IBrush TextBackgroundBrush => HasTextRuleError() ? _errorBrush : _transparentBrush; + // A built-in text error wins; otherwise a matching custom rule paints the cell in its own color. + public IBrush TextBackgroundBrush => HasTextRuleError() + ? _errorBrush + : GetMatchingCustomErrorRule()?.Brush ?? _transparentBrush; /// /// The memoized "text too long / too wide / too many lines" verdict behind @@ -996,6 +1024,11 @@ void Add(string error) Add(UseTeletextLineLength ? "text too long or wide for teletext" : "text too long or wide"); } + if (GetMatchingCustomErrorRule() is { } customRule) + { + Add("matches rule " + customRule.DisplayName); + } + return errors?.ToString() ?? string.Empty; } } @@ -1197,6 +1230,7 @@ internal void ClearStrippedTextCaches() _cpsCacheText = null; _cpsOriginalCacheText = null; _textErrorCacheText = null; + _customRuleCacheText = null; } public void RefreshAfterSettingsChanged() @@ -1382,7 +1416,7 @@ public bool HasErrors(SubtitleLineViewModel? prev, SubtitleLineViewModel? next) // Last, because these are the only rules that touch the text (and, with // "text too wide" on, shape every line with HarfBuzz). - return HasTextRuleError(); + return HasTextRuleError() || GetMatchingCustomErrorRule() != null; } private static bool HasGapError(SeGeneral general, double gapMs) @@ -1512,6 +1546,11 @@ public List GetErrorList(SubtitleLineViewModel? prev, SubtitleLineVie } } + if (GetMatchingCustomErrorRule() is { } customRule) + { + errors.Add(new LineError(LineErrorType.CustomRule, customRule.DisplayName)); + } + return errors; } diff --git a/src/ui/Features/Options/CustomErrorRules/CustomErrorRuleItem.cs b/src/ui/Features/Options/CustomErrorRules/CustomErrorRuleItem.cs new file mode 100644 index 00000000000..d1d833675b7 --- /dev/null +++ b/src/ui/Features/Options/CustomErrorRules/CustomErrorRuleItem.cs @@ -0,0 +1,64 @@ +using Avalonia.Media; +using CommunityToolkit.Mvvm.ComponentModel; +using Nikse.SubtitleEdit.Logic; +using Nikse.SubtitleEdit.Logic.Config; + +namespace Nikse.SubtitleEdit.Features.Options.CustomErrorRules; + +/// One editable row in . +public partial class CustomErrorRuleItem : ObservableObject +{ + [ObservableProperty] private bool _isEnabled; + [ObservableProperty] private string _name; + [ObservableProperty] private string _pattern; + [ObservableProperty] private bool _matchCase; + [ObservableProperty] private Color _color; + [ObservableProperty] private bool _isPatternValid; + + public CustomErrorRuleItem() + { + _isEnabled = true; + _name = string.Empty; + _pattern = string.Empty; + _color = SeCustomErrorRule.DefaultColor.FromHexToColor(); + _isPatternValid = true; + } + + public static CustomErrorRuleItem FromSetting(SeCustomErrorRule rule) + { + var item = new CustomErrorRuleItem + { + IsEnabled = rule.Enabled, + Name = rule.Name ?? string.Empty, + Pattern = rule.Pattern ?? string.Empty, + MatchCase = rule.MatchCase, + Color = (string.IsNullOrEmpty(rule.Color) ? SeCustomErrorRule.DefaultColor : rule.Color).FromHexToColor(), + }; + item.Validate(); + return item; + } + + public SeCustomErrorRule ToSetting() + { + return new SeCustomErrorRule + { + Enabled = IsEnabled, + Name = Name.Trim(), + Pattern = Pattern, + MatchCase = MatchCase, + Color = Color.FromColorToHex(), + }; + } + + partial void OnPatternChanged(string value) => Validate(); + + partial void OnMatchCaseChanged(bool value) => Validate(); + + private void Validate() + { + IsPatternValid = string.IsNullOrEmpty(Pattern) || CustomErrorRuleMatcher.TryCreateRegex(Pattern, MatchCase) != null; + } + + // Screen readers announce list items through ToString(). + public override string ToString() => string.IsNullOrWhiteSpace(Name) ? Pattern : Name; +} diff --git a/src/ui/Features/Options/CustomErrorRules/CustomErrorRulesViewModel.cs b/src/ui/Features/Options/CustomErrorRules/CustomErrorRulesViewModel.cs new file mode 100644 index 00000000000..9f73f57a198 --- /dev/null +++ b/src/ui/Features/Options/CustomErrorRules/CustomErrorRulesViewModel.cs @@ -0,0 +1,189 @@ +using Avalonia.Controls; +using Avalonia.Input; +using CommunityToolkit.Mvvm.ComponentModel; +using CommunityToolkit.Mvvm.Input; +using Nikse.SubtitleEdit.Logic; +using Nikse.SubtitleEdit.Logic.Config; +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.ComponentModel; +using System.Linq; +using System.Text.RegularExpressions; + +namespace Nikse.SubtitleEdit.Features.Options.CustomErrorRules; + +/// +/// Edits the user's custom error rules (#10136): regular expressions that mark a line as an +/// error when its text matches, e.g. "[CHECK]" markers left by a translation pipeline. The +/// caller writes the result back, so the settings window can keep it until its own OK. +/// +public partial class CustomErrorRulesViewModel : ObservableObject +{ + [ObservableProperty] private ObservableCollection _rules; + [ObservableProperty] private CustomErrorRuleItem? _selectedRule; + [ObservableProperty] private bool _hasSelectedRule; + [ObservableProperty] private bool _isHighlightEnabled; + [ObservableProperty] private string _testText; + [ObservableProperty] private string _testResult; + + public Window? Window { get; set; } + public bool OkPressed { get; private set; } + + public CustomErrorRulesViewModel() + { + _rules = new ObservableCollection(); + _isHighlightEnabled = true; + _testText = string.Empty; + _testResult = string.Empty; + } + + public void Initialize(bool isHighlightEnabled, IEnumerable rules) + { + IsHighlightEnabled = isHighlightEnabled; + Rules.Clear(); + foreach (var rule in rules) + { + Rules.Add(CustomErrorRuleItem.FromSetting(rule)); + } + + SelectedRule = Rules.FirstOrDefault(); + } + + /// The edited rules; rows without a pattern are dropped. + public List GetRules() + { + return Rules + .Where(p => !string.IsNullOrEmpty(p.Pattern)) + .Select(p => p.ToSetting()) + .ToList(); + } + + partial void OnSelectedRuleChanged(CustomErrorRuleItem? oldValue, CustomErrorRuleItem? newValue) + { + if (oldValue != null) + { + oldValue.PropertyChanged -= SelectedRulePropertyChanged; + } + + if (newValue != null) + { + newValue.PropertyChanged += SelectedRulePropertyChanged; + } + + HasSelectedRule = newValue != null; + UpdateTestResult(); + } + + private void SelectedRulePropertyChanged(object? sender, PropertyChangedEventArgs e) + { + if (e.PropertyName is nameof(CustomErrorRuleItem.Pattern) or nameof(CustomErrorRuleItem.MatchCase)) + { + UpdateTestResult(); + } + } + + partial void OnTestTextChanged(string value) => UpdateTestResult(); + + private void UpdateTestResult() + { + var rule = SelectedRule; + if (rule == null || string.IsNullOrEmpty(rule.Pattern) || string.IsNullOrEmpty(TestText)) + { + TestResult = string.Empty; + return; + } + + var regex = CustomErrorRuleMatcher.TryCreateRegex(rule.Pattern, rule.MatchCase); + if (regex == null) + { + TestResult = Se.Language.SourceView.InvalidRegularExpression; + return; + } + + var l = Se.Language.Options.Settings; + try + { + TestResult = regex.IsMatch(TestText) ? l.CustomErrorRuleTestMatch : l.CustomErrorRuleTestNoMatch; + } + catch (RegexMatchTimeoutException) + { + TestResult = l.CustomErrorRuleTestNoMatch; + } + } + + [RelayCommand] + private void Add() + { + var item = new CustomErrorRuleItem(); + Rules.Add(item); + SelectedRule = item; + } + + [RelayCommand] + private void Remove() + { + var item = SelectedRule; + if (item == null) + { + return; + } + + var index = Rules.IndexOf(item); + Rules.Remove(item); + SelectedRule = Rules.Count == 0 ? null : Rules[Math.Min(index, Rules.Count - 1)]; + } + + [RelayCommand] + private void MoveUp() => Move(-1); + + [RelayCommand] + private void MoveDown() => Move(1); + + // The first matching rule decides the color, so the order matters. + private void Move(int delta) + { + var item = SelectedRule; + if (item == null) + { + return; + } + + var index = Rules.IndexOf(item); + var newIndex = index + delta; + if (index < 0 || newIndex < 0 || newIndex >= Rules.Count) + { + return; + } + + Rules.Move(index, newIndex); + SelectedRule = item; + } + + [RelayCommand] + private void Ok() + { + OkPressed = true; + Window?.Close(); + } + + [RelayCommand] + private void Cancel() + { + Window?.Close(); + } + + internal void OnKeyDown(KeyEventArgs e) + { + if (e.Key == Key.Escape) + { + e.Handled = true; + Window?.Close(); + } + else if (UiUtil.IsHelp(e)) + { + e.Handled = true; + UiUtil.ShowHelp("features/settings", "syntax-coloring"); + } + } +} diff --git a/src/ui/Features/Options/CustomErrorRules/CustomErrorRulesWindow.cs b/src/ui/Features/Options/CustomErrorRules/CustomErrorRulesWindow.cs new file mode 100644 index 00000000000..1b8bd84d6e7 --- /dev/null +++ b/src/ui/Features/Options/CustomErrorRules/CustomErrorRulesWindow.cs @@ -0,0 +1,220 @@ +using Avalonia; +using Avalonia.Automation; +using Avalonia.Controls; +using Avalonia.Controls.Primitives; +using Avalonia.Controls.Templates; +using Avalonia.Data; +using Avalonia.Data.Converters; +using Avalonia.Layout; +using Nikse.SubtitleEdit.Logic; +using Nikse.SubtitleEdit.Logic.Config; + +namespace Nikse.SubtitleEdit.Features.Options.CustomErrorRules; + +/// +/// Custom error rules (#10136) in the editor look (see ): the on/off +/// switch, the rule list with its tool buttons, the selected rule's fields and a test box, +/// Cancel + OK in the button bar. +/// +public class CustomErrorRulesWindow : Window +{ + private const double LabelWidth = 140; + + public CustomErrorRulesWindow(CustomErrorRulesViewModel vm) + { + var l = Se.Language.Options.Settings; + UiUtil.InitializeWindow(this, GetType().Name); + Title = l.CustomErrorRules; + CanResize = true; + Width = 760; + Height = 760; + MinWidth = 560; + MinHeight = 520; + EditorChrome.ApplyWindow(this, EditorWindowStyles.Dialog | EditorWindowStyles.CompactListRows); + vm.Window = this; + DataContext = vm; + + var generalContent = new StackPanel + { + Spacing = 8, + Children = + { + EditorChrome.MakeToggleRow(nameof(vm.IsHighlightEnabled), l.CustomErrorRulesHighlight), + EditorChrome.MakeWrappingDimLabel(l.CustomErrorRulesHint), + }, + }; + + var toolButtons = new StackPanel + { + Orientation = Orientation.Horizontal, + Spacing = 4, + Children = + { + EditorChrome.MakeTextToolButton(IconNames.Plus, Se.Language.General.Add, vm.AddCommand), + EditorChrome.MakeTextToolButton(IconNames.Trash, Se.Language.General.Remove, vm.RemoveCommand) + .WithBindIsEnabled(nameof(vm.HasSelectedRule)), + EditorChrome.MakeTextToolButton(IconNames.ArrowUpThin, Se.Language.General.MoveUp, vm.MoveUpCommand) + .WithBindIsEnabled(nameof(vm.HasSelectedRule)), + EditorChrome.MakeTextToolButton(IconNames.ArrowDownThin, Se.Language.General.MoveDown, vm.MoveDownCommand) + .WithBindIsEnabled(nameof(vm.HasSelectedRule)), + }, + }; + + var rulesList = new Border + { + Child = MakeRulesGrid(vm), + Height = 200, + }; + + var selectedRuleEditor = MakeSelectedRuleEditor(vm, out var textBoxName); + var sections = new StackPanel + { + Children = + { + EditorChrome.MakeEditorSection(l.CustomErrorRules, generalContent, first: true), + EditorChrome.MakeEditorSection(Se.Language.General.Rules, rulesList, toolButtons), + EditorChrome.MakeEditorSection(l.CustomErrorRuleSelected, selectedRuleEditor), + EditorChrome.MakeEditorSection(l.CustomErrorRuleTest, MakeTestSection(vm)), + }, + }; + + var scrollViewer = new ScrollViewer + { + Content = sections, + HorizontalScrollBarVisibility = ScrollBarVisibility.Disabled, + VerticalScrollBarVisibility = ScrollBarVisibility.Auto, + }; + + var buttonOk = UiUtil.MakeButtonOk(vm.OkCommand); + buttonOk.Classes.Add("primary"); + var buttonCancel = UiUtil.MakeButtonCancel(vm.CancelCommand); + + Content = EditorChrome.MakeRoot(scrollViewer, EditorChrome.MakeDockedButtonBar(null, buttonCancel, buttonOk)); + + UiUtil.FocusOnFirstActivation(this, textBoxName); // initial focus on an input, not an action button - a focused button clicks on bare Space + KeyDown += (_, e) => vm.OnKeyDown(e); + } + + private static Border MakeRulesGrid(CustomErrorRulesViewModel vm) + { + var grid = TableViewExtras.MakeTableView(); + grid.ItemsSource = vm.Rules; + + grid.Columns.Add(new SeTableViewColumn + { + Header = Se.Language.General.Enabled, + Width = new GridLength(80), + CellTheme = UiUtil.TableViewNoPaddingCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, + CellTemplate = new FuncDataTemplate((_, _) => + { + var checkBox = new CheckBox + { + HorizontalAlignment = HorizontalAlignment.Center, + VerticalAlignment = VerticalAlignment.Center, + [!ToggleButton.IsCheckedProperty] = new Binding(nameof(CustomErrorRuleItem.IsEnabled)) { Mode = BindingMode.TwoWay }, + }; + AutomationProperties.SetName(checkBox, Se.Language.General.Enabled); + return checkBox; + }), + }); + grid.Columns.Add(new SeTableViewColumn + { + Header = Se.Language.General.Name, + Binding = new Binding(nameof(CustomErrorRuleItem.Name)), + Width = new GridLength(1, GridUnitType.Star), + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, + }); + grid.Columns.Add(new SeTableViewColumn + { + Header = Se.Language.General.RegularExpression, + Binding = new Binding(nameof(CustomErrorRuleItem.Pattern)), + Width = new GridLength(2, GridUnitType.Star), + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, + }); + + grid.Bind(TableView.SelectedItemProperty, new Binding(nameof(vm.SelectedRule)) + { + Source = vm, + Mode = BindingMode.TwoWay, + }); + + AutomationProperties.SetName(grid, Se.Language.Options.Settings.CustomErrorRules); + return EditorChrome.MakeListFrame(grid, clearListBackground: true); + } + + private static StackPanel MakeSelectedRuleEditor(CustomErrorRulesViewModel vm, out TextBox textBoxName) + { + textBoxName = new TextBox + { + HorizontalAlignment = HorizontalAlignment.Stretch, + [!TextBox.TextProperty] = new Binding($"{nameof(vm.SelectedRule)}.{nameof(CustomErrorRuleItem.Name)}") { Mode = BindingMode.TwoWay }, + }.AsTextField(Se.Language.General.Name); + + var textBoxPattern = new TextBox + { + HorizontalAlignment = HorizontalAlignment.Stretch, + [!TextBox.TextProperty] = new Binding($"{nameof(vm.SelectedRule)}.{nameof(CustomErrorRuleItem.Pattern)}") { Mode = BindingMode.TwoWay }, + }.AsTextField(Se.Language.General.RegularExpression); + + var labelInvalid = new TextBlock + { + Text = Se.Language.SourceView.InvalidRegularExpression, + Foreground = EditorChrome.DimTextBrush, + [!IsVisibleProperty] = new Binding($"{nameof(vm.SelectedRule)}.{nameof(CustomErrorRuleItem.IsPatternValid)}") + { + Converter = BoolConverters.Not, + FallbackValue = false, + }, + }; + + var toggleMatchCase = EditorChrome.MakeToggleRow($"{nameof(vm.SelectedRule)}.{nameof(CustomErrorRuleItem.MatchCase)}", Se.Language.General.CaseSensitive); + + var colorButton = UiUtil.MakeColorPickerButton(vm, $"{nameof(vm.SelectedRule)}.{nameof(CustomErrorRuleItem.Color)}"); + colorButton.HorizontalAlignment = HorizontalAlignment.Left; + AutomationProperties.SetName(colorButton, Se.Language.General.Color); + + var panel = new StackPanel + { + Spacing = 8, + Children = + { + EditorChrome.MakeLabeledRow(Se.Language.General.Name, textBoxName, LabelWidth), + EditorChrome.MakeLabeledRow(Se.Language.General.RegularExpression, textBoxPattern, LabelWidth), + labelInvalid, + toggleMatchCase, + EditorChrome.MakeLabeledRow(Se.Language.General.Color, colorButton, LabelWidth), + }, + }; + panel.Bind(IsEnabledProperty, new Binding(nameof(vm.HasSelectedRule))); + return panel; + } + + private static StackPanel MakeTestSection(CustomErrorRulesViewModel vm) + { + var textBoxTest = new TextBox + { + HorizontalAlignment = HorizontalAlignment.Stretch, + AcceptsReturn = true, + Height = 60, + [!TextBox.TextProperty] = new Binding(nameof(vm.TestText)) { Mode = BindingMode.TwoWay }, + }.AsTextField(Se.Language.General.SampleText); + + var labelResult = new TextBlock + { + [!TextBlock.TextProperty] = new Binding(nameof(vm.TestResult)), + }; + + return new StackPanel + { + Spacing = 8, + Children = + { + EditorChrome.MakeLabeledRow(Se.Language.General.SampleText, textBoxTest, LabelWidth), + labelResult, + }, + }; + } +} diff --git a/src/ui/Features/Options/Settings/SettingsImportExport/SettingsImportExportViewModel.cs b/src/ui/Features/Options/Settings/SettingsImportExport/SettingsImportExportViewModel.cs index 84af8012b3d..8f3dfca1a6f 100644 --- a/src/ui/Features/Options/Settings/SettingsImportExport/SettingsImportExportViewModel.cs +++ b/src/ui/Features/Options/Settings/SettingsImportExport/SettingsImportExportViewModel.cs @@ -483,6 +483,8 @@ private void ImportSettings() to.ColorTimeCodeOverlap = from.ColorTimeCodeOverlap; to.ColorGapTooShort = from.ColorGapTooShort; to.ErrorColor = from.ErrorColor; + to.ColorCustomErrorRules = from.ColorCustomErrorRules; + to.CustomErrorRules = from.CustomErrorRules; } if (ExportImportAll || ExportImportWaveform) diff --git a/src/ui/Features/Options/Settings/SettingsPage.cs b/src/ui/Features/Options/Settings/SettingsPage.cs index dc890decbf4..c065b4f2227 100644 --- a/src/ui/Features/Options/Settings/SettingsPage.cs +++ b/src/ui/Features/Options/Settings/SettingsPage.cs @@ -647,6 +647,12 @@ private List CreateSections() MakeSeparator(), MakeCheckboxSetting(Se.Language.Options.Settings.ColorGapTooShort, nameof(_vm.ColorGapTooShort)), MakeSeparator(), + new SettingsItem(Se.Language.Options.Settings.CustomErrorRules, () => EditorChrome.MakeSwitch(nameof(_vm.ColorCustomErrorRules), null, _vm)) + { + IsSwitch = true, + TrailingFactory = () => UiUtil.MakeButtonBrowse(_vm.EditCustomErrorRulesCommand, accessibleName: Se.Language.Options.Settings.CustomErrorRules), + }, + MakeSeparator(), new SettingsItem(Se.Language.Options.Settings.ErrorBackgroundColor, () => UiUtil.MakeColorPickerButton(_vm, nameof(_vm.ErrorColor))), ])); diff --git a/src/ui/Features/Options/Settings/SettingsViewModel.cs b/src/ui/Features/Options/Settings/SettingsViewModel.cs index 291d12e1efa..571d545f0d9 100644 --- a/src/ui/Features/Options/Settings/SettingsViewModel.cs +++ b/src/ui/Features/Options/Settings/SettingsViewModel.cs @@ -17,6 +17,7 @@ using Nikse.SubtitleEdit.Features.Assa; using Nikse.SubtitleEdit.Features.Help.CheckForUpdates; using Nikse.SubtitleEdit.Features.Main; +using Nikse.SubtitleEdit.Features.Options.CustomErrorRules; using Nikse.SubtitleEdit.Features.Options.DoNotBreakAfterList; using Nikse.SubtitleEdit.Features.Options.Settings.MinGapCalculate; using Nikse.SubtitleEdit.Features.Options.Settings.SyntaxColorTooWideSettings; @@ -270,6 +271,8 @@ partial void OnSelectedTimeCodeModeChanged(TimeCodeModeDisplay value) [ObservableProperty] private bool _colorWordsPerMinute; [ObservableProperty] private bool _colorOverlap; [ObservableProperty] private bool _colorGapTooShort; + [ObservableProperty] private bool _colorCustomErrorRules; + private List _customErrorRules = new(); [ObservableProperty] private Color _errorColor; [ObservableProperty] private ObservableCollection _videoPlayers; @@ -1125,6 +1128,8 @@ private void LoadSettings() ColorOverlap = general.ColorTimeCodeOverlap; ColorGapTooShort = general.ColorGapTooShort; ErrorColor = general.ErrorColor.FromHexToColor(); + ColorCustomErrorRules = general.ColorCustomErrorRules; + _customErrorRules = general.CustomErrorRules.Select(CopyCustomErrorRule).ToList(); _editCustomContinuationStyle = new CustomContinuationStyle(general.CustomContinuationStyle); @@ -2009,6 +2014,8 @@ private void SaveSettings() general.ColorTimeCodeOverlap = ColorOverlap; general.ColorGapTooShort = ColorGapTooShort; general.ErrorColor = ErrorColor.FromColorToHex(); + general.ColorCustomErrorRules = ColorCustomErrorRules; + general.CustomErrorRules = _customErrorRules.Select(CopyCustomErrorRule).ToList(); general.CustomContinuationStyle = new CustomContinuationStyle(_editCustomContinuationStyle); @@ -2377,6 +2384,35 @@ private static Task RunFadeAnimation(Control control, double from, double to, Ti return animation.RunAsync(control); } + [RelayCommand] + private async Task EditCustomErrorRules() + { + if (Window == null) + { + return; + } + + var viewModel = await _windowService.ShowDialogAsync(Window, vm => + { + vm.Initialize(ColorCustomErrorRules, _customErrorRules); + }); + + if (viewModel.OkPressed) + { + ColorCustomErrorRules = viewModel.IsHighlightEnabled; + _customErrorRules = viewModel.GetRules(); + } + } + + private static SeCustomErrorRule CopyCustomErrorRule(SeCustomErrorRule rule) => new() + { + Enabled = rule.Enabled, + Name = rule.Name, + Pattern = rule.Pattern, + MatchCase = rule.MatchCase, + Color = rule.Color, + }; + [RelayCommand] private async Task EditTextTooWideSettings() { @@ -3051,6 +3087,7 @@ private async Task ResetAllSettings() Se.Settings.General.ColorTimeCodeOverlap = g.ColorTimeCodeOverlap; Se.Settings.General.ColorGapTooShort = g.ColorGapTooShort; Se.Settings.General.ErrorColor = g.ErrorColor; + Se.Settings.General.ColorCustomErrorRules = g.ColorCustomErrorRules; // the rules themselves are user data and stay } } diff --git a/src/ui/Features/Shared/ErrorList/LineError.cs b/src/ui/Features/Shared/ErrorList/LineError.cs index 649419ced02..fe21725e928 100644 --- a/src/ui/Features/Shared/ErrorList/LineError.cs +++ b/src/ui/Features/Shared/ErrorList/LineError.cs @@ -15,6 +15,7 @@ public enum LineErrorType LineTooWide, Overlap, GapTooShort, + CustomRule, } /// One error on one line: the class plus a short detail such as "27.3 > 25". @@ -38,6 +39,7 @@ public static string GetLabel(LineErrorType type) LineErrorType.LineTooWide => l.LineTooWide, LineErrorType.Overlap => Se.Language.General.Overlapping, LineErrorType.GapTooShort => l.GapTooShort, + LineErrorType.CustomRule => l.CustomRule, _ => type.ToString(), }; } @@ -55,6 +57,7 @@ public static string GetHint(LineErrorType type) LineErrorType.LineTooWide => l.LineTooWideHint, LineErrorType.Overlap => l.OverlapHint, LineErrorType.GapTooShort => l.GapTooShortHint, + LineErrorType.CustomRule => l.CustomRuleHint, _ => string.Empty, }; } @@ -67,6 +70,7 @@ public static string GetHint(LineErrorType type) private const string LineTooWideColor = "#2980B9"; private const string OverlapColor = "#E74C3C"; private const string GapColor = "#1ABC9C"; + private const string CustomRuleColor = "#FFA500"; public const string AllColor = "#5D8AA8"; private static readonly IBrush TooManyLinesBrush = new ImmutableSolidColorBrush(Color.Parse(TooManyLinesColor)); @@ -77,6 +81,7 @@ public static string GetHint(LineErrorType type) private static readonly IBrush LineTooWideBrush = new ImmutableSolidColorBrush(Color.Parse(LineTooWideColor)); private static readonly IBrush OverlapBrush = new ImmutableSolidColorBrush(Color.Parse(OverlapColor)); private static readonly IBrush GapBrush = new ImmutableSolidColorBrush(Color.Parse(GapColor)); + private static readonly IBrush CustomRuleBrush = new ImmutableSolidColorBrush(Color.Parse(CustomRuleColor)); public static readonly IBrush AllBrush = new ImmutableSolidColorBrush(Color.Parse(AllColor)); /// The dot colour as "#RRGGBB" - the html export paints the same palette as the window. @@ -92,6 +97,7 @@ public static string GetColor(LineErrorType type) LineErrorType.LineTooWide => LineTooWideColor, LineErrorType.Overlap => OverlapColor, LineErrorType.GapTooShort => GapColor, + LineErrorType.CustomRule => CustomRuleColor, _ => AllColor, }; } @@ -108,6 +114,7 @@ public static IBrush GetBrush(LineErrorType type) LineErrorType.LineTooWide => LineTooWideBrush, LineErrorType.Overlap => OverlapBrush, LineErrorType.GapTooShort => GapBrush, + LineErrorType.CustomRule => CustomRuleBrush, _ => AllBrush, }; } diff --git a/src/ui/Logic/Config/Language/LanguageErrorList.cs b/src/ui/Logic/Config/Language/LanguageErrorList.cs index 258cd4117fa..22c210e1ec2 100644 --- a/src/ui/Logic/Config/Language/LanguageErrorList.cs +++ b/src/ui/Logic/Config/Language/LanguageErrorList.cs @@ -12,6 +12,7 @@ public class LanguageErrorList public string LineTooLong { get; set; } public string LineTooWide { get; set; } public string GapTooShort { get; set; } + public string CustomRule { get; set; } public string TooManyLinesHint { get; set; } public string CharactersPerSecondHint { get; set; } public string DurationTooShortHint { get; set; } @@ -20,6 +21,7 @@ public class LanguageErrorList public string LineTooWideHint { get; set; } public string OverlapHint { get; set; } public string GapTooShortHint { get; set; } + public string CustomRuleHint { get; set; } public string DetailXGreaterThanY { get; set; } public string DetailXLessThanY { get; set; } public string DetailOverlapFromPrevious { get; set; } @@ -46,6 +48,7 @@ public LanguageErrorList() LineTooLong = "Line too long"; LineTooWide = "Line too wide"; GapTooShort = "Gap too short"; + CustomRule = "Custom rule"; TooManyLinesHint = "More lines than the maximum number of lines"; CharactersPerSecondHint = "Characters per second above the maximum"; DurationTooShortHint = "Shorter than the minimum display time"; @@ -54,6 +57,7 @@ public LanguageErrorList() LineTooWideHint = "A line is wider (in pixels) than the maximum width"; OverlapHint = "Overlaps the previous or next line"; GapTooShortHint = "Gap to the previous or next line is below the minimum"; + CustomRuleHint = "The text matches one of your custom error rules"; DetailXGreaterThanY = "{0} > {1}"; DetailXLessThanY = "{0} < {1}"; DetailOverlapFromPrevious = "from previous: {0} ms"; diff --git a/src/ui/Logic/Config/Language/Options/LanguageSettings.cs b/src/ui/Logic/Config/Language/Options/LanguageSettings.cs index 5d1f12a7b83..48667dd8c65 100644 --- a/src/ui/Logic/Config/Language/Options/LanguageSettings.cs +++ b/src/ui/Logic/Config/Language/Options/LanguageSettings.cs @@ -125,6 +125,15 @@ public class LanguageSettings public string ColorOverlap { get; set; } public string ColorGapTooShort { get; set; } public string ErrorBackgroundColor { get; set; } + public string CustomErrorRules { get; set; } + public string CustomErrorRulesHighlight { get; set; } + public string CustomErrorRulesHint { get; set; } + public string CustomErrorRuleSelected { get; set; } + public string CustomErrorRuleTest { get; set; } + public string CustomErrorRuleTestMatch { get; set; } + public string CustomErrorRuleTestNoMatch { get; set; } + public string CustomErrorRulesOn { get; set; } + public string CustomErrorRulesOff { get; set; } public string WaveformDrawGridLines { get; set; } public string WaveformUseSkiaRenderer { get; set; } public string WaveformCenterVideoPositionAlsoWhenPaused { get; set; } @@ -490,6 +499,15 @@ public LanguageSettings() ColorOverlap = "Color time code overlap"; ColorGapTooShort = "Color if gap is too short"; ErrorBackgroundColor = "Error background color"; + CustomErrorRules = "Custom error rules"; + CustomErrorRulesHighlight = "Color lines matching a custom error rule"; + CustomErrorRulesHint = "A line whose text matches an enabled rule's regular expression is colored with the rule's color and counts as an error (List errors, Go to next error). The first matching rule decides the color."; + CustomErrorRuleSelected = "Selected rule"; + CustomErrorRuleTest = "Test"; + CustomErrorRuleTestMatch = "The selected rule matches the sample text"; + CustomErrorRuleTestNoMatch = "The selected rule does not match the sample text"; + CustomErrorRulesOn = "Custom error rules: on"; + CustomErrorRulesOff = "Custom error rules: off"; WaveformDrawGridLines = "Draw grid lines"; WaveformUseSkiaRenderer = "Use experimental fast renderer"; WaveformFocusOnMouseOver = "Focus on mouse over"; diff --git a/src/ui/Logic/Config/Language/Options/LanguageSettingsShortcuts.cs b/src/ui/Logic/Config/Language/Options/LanguageSettingsShortcuts.cs index c53f6e59a61..d21d372e5f2 100644 --- a/src/ui/Logic/Config/Language/Options/LanguageSettingsShortcuts.cs +++ b/src/ui/Logic/Config/Language/Options/LanguageSettingsShortcuts.cs @@ -125,6 +125,8 @@ public class LanguageSettingsShortcuts public string MoveTextFromCursorToNextAndGoToNextAndPlay { get; set; } public string MoveTextFromCursorToNextAndGoToNextAndPlayAndPause { get; set; } public string MoveTextBeforeCursorToPrevious { get; set; } + public string ShowCustomErrorRules { get; set; } + public string ToggleCustomErrorRules { get; set; } public string SplitAtVideoPositionAndPlay { get; set; } public string SplitAtTextBoxCursorPositionAndPlay { get; set; } public string SplitAtVideoAndTextBoxPositionAndPlay { get; set; } @@ -481,6 +483,8 @@ public LanguageSettingsShortcuts() MoveTextFromCursorToNextAndGoToNextAndPlay = "Move text after cursor position to next subtitle, go to next and play"; MoveTextFromCursorToNextAndGoToNextAndPlayAndPause = "Move text after cursor position to next subtitle, go to next, play it and pause at its end"; MoveTextBeforeCursorToPrevious = "Move text before cursor position to previous subtitle"; + ShowCustomErrorRules = "Show custom error rules"; + ToggleCustomErrorRules = "Toggle custom error rules coloring on/off"; SplitAtVideoPositionAndPlay = "Split line at video position, go to second part and play"; SplitAtTextBoxCursorPositionAndPlay = "Split line at text box cursor position, go to second part and play"; SplitAtVideoAndTextBoxPositionAndPlay = "Split line at video and text box position, go to second part and play"; diff --git a/src/ui/Logic/Config/SeCustomErrorRule.cs b/src/ui/Logic/Config/SeCustomErrorRule.cs new file mode 100644 index 00000000000..7adf4840a59 --- /dev/null +++ b/src/ui/Logic/Config/SeCustomErrorRule.cs @@ -0,0 +1,26 @@ +using Avalonia.Media; + +namespace Nikse.SubtitleEdit.Logic.Config; + +/// +/// A user rule that marks a line as an error when its text matches a regular expression, +/// e.g. review markers like "[CHECK]" left by a translation pipeline (#10136). +/// +public class SeCustomErrorRule +{ + public bool Enabled { get; set; } + public string Name { get; set; } + public string Pattern { get; set; } + public bool MatchCase { get; set; } + public string Color { get; set; } + + public static string DefaultColor => Avalonia.Media.Color.FromArgb(90, 255, 165, 0).FromColorToHex(); + + public SeCustomErrorRule() + { + Enabled = true; + Name = string.Empty; + Pattern = string.Empty; + Color = DefaultColor; + } +} diff --git a/src/ui/Logic/Config/SeGeneral.cs b/src/ui/Logic/Config/SeGeneral.cs index fe4bf222689..38f2ad04cfe 100644 --- a/src/ui/Logic/Config/SeGeneral.cs +++ b/src/ui/Logic/Config/SeGeneral.cs @@ -184,6 +184,8 @@ public bool UseFrameMode public bool ColorTimeCodeOverlap { get; set; } public bool ColorGapTooShort { get; set; } public string ErrorColor { get; set; } + public bool ColorCustomErrorRules { get; set; } + public List CustomErrorRules { get; set; } = new(); public string FfmpegPath { get; set; } public bool FfmpegUseCenterChannelOnly { get; set; } @@ -353,6 +355,7 @@ public SeGeneral() ColorTimeCodeOverlap = true; ColorGapTooShort = true; ErrorColor = Color.FromArgb(50, 255, 0, 0).FromColorToHex(); + ColorCustomErrorRules = true; FfmpegPath = string.Empty; LibMpvPath = string.Empty; diff --git a/src/ui/Logic/CustomErrorRuleMatcher.cs b/src/ui/Logic/CustomErrorRuleMatcher.cs new file mode 100644 index 00000000000..8358f1bbfda --- /dev/null +++ b/src/ui/Logic/CustomErrorRuleMatcher.cs @@ -0,0 +1,148 @@ +using Avalonia.Media; +using Avalonia.Media.Immutable; +using Nikse.SubtitleEdit.Logic.Config; +using System; +using System.Collections.Generic; +using System.Text.RegularExpressions; + +namespace Nikse.SubtitleEdit.Logic; + +/// +/// The compiled form of (#10136). The grid asks for +/// every visible row on every refresh, so the patterns are compiled once and the result is +/// shared until the rules change () or another settings object is loaded. +/// +public static class CustomErrorRuleMatcher +{ + /// + /// Far below the find/replace timeout: a pattern is run against every line while the grid + /// paints, so a runaway one must give up quickly rather than freeze the UI. + /// + private static readonly TimeSpan MatchTimeout = TimeSpan.FromMilliseconds(100); + + public sealed class CompiledRule + { + public CompiledRule(SeCustomErrorRule rule, Regex regex) + { + Rule = rule; + Regex = regex; + Brush = new ImmutableSolidColorBrush(rule.Color.FromHexToColor()); + } + + public SeCustomErrorRule Rule { get; } + public Regex Regex { get; } + public IBrush Brush { get; } + + public string DisplayName => string.IsNullOrWhiteSpace(Rule.Name) ? Rule.Pattern : Rule.Name; + } + + /// A rule set compiled for one settings state; rows compare it by reference to memoize. + public sealed class RuleSet + { + public static readonly RuleSet Empty = new(new List()); + + public RuleSet(IReadOnlyList rules) + { + Rules = rules; + } + + public IReadOnlyList Rules { get; } + + /// The first enabled rule matching , or null. + public CompiledRule? Match(string? text) + { + if (Rules.Count == 0 || string.IsNullOrEmpty(text)) + { + return null; + } + + foreach (var rule in Rules) + { + try + { + if (rule.Regex.IsMatch(text)) + { + return rule; + } + } + catch (RegexMatchTimeoutException) + { + // Too slow for this line - no match, rather than hanging the grid. + } + } + + return null; + } + } + + private static RuleSet? _current; + private static SeGeneral? _currentFor; + + /// The rules in effect now; when custom rules are off. + public static RuleSet Current + { + get + { + var general = Se.Settings.General; + var current = _current; + if (current != null && ReferenceEquals(_currentFor, general)) + { + return current; + } + + current = Compile(general); + _current = current; + _currentFor = general; + return current; + } + } + + /// Call after or its on/off switch changed. + public static void Invalidate() + { + _current = null; + } + + public static Regex? TryCreateRegex(string? pattern, bool matchCase) + { + if (string.IsNullOrEmpty(pattern)) + { + return null; + } + + try + { + var options = matchCase ? RegexOptions.None : RegexOptions.IgnoreCase; + return new Regex(pattern, options | RegexOptions.Compiled, MatchTimeout); + } + catch (ArgumentException) + { + return null; + } + } + + private static RuleSet Compile(SeGeneral general) + { + if (!general.ColorCustomErrorRules || general.CustomErrorRules == null || general.CustomErrorRules.Count == 0) + { + return RuleSet.Empty; + } + + var rules = new List(); + foreach (var rule in general.CustomErrorRules) + { + if (rule == null || !rule.Enabled) + { + continue; + } + + var regex = TryCreateRegex(rule.Pattern, rule.MatchCase); + if (regex != null) + { + rules.Add(new CompiledRule(rule, regex)); + } + } + + return rules.Count == 0 ? RuleSet.Empty : new RuleSet(rules); + } +} diff --git a/src/ui/Logic/ShortcutsMain.cs b/src/ui/Logic/ShortcutsMain.cs index f71a2a2c684..576a1aa9375 100644 --- a/src/ui/Logic/ShortcutsMain.cs +++ b/src/ui/Logic/ShortcutsMain.cs @@ -592,6 +592,8 @@ private static Dictionary BuildCommandTranslations() { nameof(MainViewModel.MoveTextFromCursorToNextAndGoToNextAndPlayCommand), Se.Language.Options.Shortcuts.MoveTextFromCursorToNextAndGoToNextAndPlay }, { nameof(MainViewModel.MoveTextFromCursorToNextAndGoToNextAndPlayAndPauseCommand), Se.Language.Options.Shortcuts.MoveTextFromCursorToNextAndGoToNextAndPlayAndPause }, { nameof(MainViewModel.MoveTextBeforeCursorToPreviousCommand), Se.Language.Options.Shortcuts.MoveTextBeforeCursorToPrevious }, + { nameof(MainViewModel.ShowCustomErrorRulesCommand), Se.Language.Options.Shortcuts.ShowCustomErrorRules }, + { nameof(MainViewModel.ToggleCustomErrorRulesCommand), Se.Language.Options.Shortcuts.ToggleCustomErrorRules }, { nameof(MainViewModel.BreakAtFirstSpaceFromCursorCommand), Se.Language.Options.Shortcuts.BreakAtFirstSpaceFromCursor }, { nameof(MainViewModel.BreakAtFirstSpaceFromCursorAndGoToNextCommand), Se.Language.Options.Shortcuts.BreakAtFirstSpaceFromCursorAndGoToNext }, { nameof(MainViewModel.ToggleFocusGridAndWaveformCommand), Se.Language.Options.Shortcuts.ToggleFocusGridAndWaveform }, @@ -1162,6 +1164,8 @@ private static List GetAllAvailableShortcuts(MainViewModel vm AddShortcut(shortcuts, vm.MoveTextFromCursorToNextAndGoToNextAndPlayCommand, nameof(vm.MoveTextFromCursorToNextAndGoToNextAndPlayCommand), ShortcutCategory.General); AddShortcut(shortcuts, vm.MoveTextFromCursorToNextAndGoToNextAndPlayAndPauseCommand, nameof(vm.MoveTextFromCursorToNextAndGoToNextAndPlayAndPauseCommand), ShortcutCategory.General); AddShortcut(shortcuts, vm.MoveTextBeforeCursorToPreviousCommand, nameof(vm.MoveTextBeforeCursorToPreviousCommand), ShortcutCategory.General); + AddShortcut(shortcuts, vm.ShowCustomErrorRulesCommand, nameof(vm.ShowCustomErrorRulesCommand), ShortcutCategory.General); + AddShortcut(shortcuts, vm.ToggleCustomErrorRulesCommand, nameof(vm.ToggleCustomErrorRulesCommand), ShortcutCategory.General); AddShortcut(shortcuts, vm.ToggleFocusGridAndWaveformCommand, nameof(vm.ToggleFocusGridAndWaveformCommand), ShortcutCategory.General); AddShortcut(shortcuts, vm.ToggleFocusTextBoxAndWaveformCommand, nameof(vm.ToggleFocusTextBoxAndWaveformCommand), ShortcutCategory.General); AddShortcut(shortcuts, vm.ToggleFocusTextBoxAndSubtitleGridCommand, nameof(vm.ToggleFocusTextBoxAndSubtitleGridCommand), ShortcutCategory.General); diff --git a/tests/UI/Logic/CustomErrorRuleMatcherTests.cs b/tests/UI/Logic/CustomErrorRuleMatcherTests.cs new file mode 100644 index 00000000000..0da4415af0c --- /dev/null +++ b/tests/UI/Logic/CustomErrorRuleMatcherTests.cs @@ -0,0 +1,143 @@ +using Nikse.SubtitleEdit.Core.Common; +using Nikse.SubtitleEdit.Features.Main; +using Nikse.SubtitleEdit.Features.Options.CustomErrorRules; +using Nikse.SubtitleEdit.Features.Shared.ErrorList; +using Nikse.SubtitleEdit.Logic; +using Nikse.SubtitleEdit.Logic.Config; + +namespace UITests.Logic; + +/// +/// Custom error rules (#10136): a line whose text matches an enabled rule is an error - +/// colored in the grid, listed by List errors, found by Go to next error. +/// +public class CustomErrorRuleMatcherTests +{ + [Fact] + public void MatchingLine_IsErrorWithRuleName() + { + RunWithRules(general => + { + general.CustomErrorRules.Add(new SeCustomErrorRule { Name = "Review", Pattern = @"\[(CHECK|TODO)\]" }); + + var marked = MakeLine("[CHECK] Hello there"); + var clean = MakeLine("Hello there"); + + Assert.True(marked.HasErrors(null, null)); + Assert.False(clean.HasErrors(null, null)); + + var error = Assert.Single(marked.GetErrorList(null, null)); + Assert.Equal(LineErrorType.CustomRule, error.Type); + Assert.Equal("Review", error.Detail); + Assert.Contains("Review", marked.AccessibleErrorText); + }); + } + + [Fact] + public void DisabledRuleOrSwitchOff_DoesNotMatch() + { + RunWithRules(general => + { + general.CustomErrorRules.Add(new SeCustomErrorRule { Pattern = "TODO", Enabled = false }); + Assert.False(MakeLine("TODO fix").HasErrors(null, null)); + + general.CustomErrorRules[0].Enabled = true; + CustomErrorRuleMatcher.Invalidate(); + Assert.True(MakeLine("TODO fix").HasErrors(null, null)); + + general.ColorCustomErrorRules = false; + CustomErrorRuleMatcher.Invalidate(); + Assert.False(MakeLine("TODO fix").HasErrors(null, null)); + }); + } + + [Fact] + public void MatchCase_IsHonored_AndInvalidPatternIsIgnored() + { + RunWithRules(general => + { + general.CustomErrorRules.Add(new SeCustomErrorRule { Pattern = "[unclosed" }); + general.CustomErrorRules.Add(new SeCustomErrorRule { Pattern = "Verify", MatchCase = true }); + + Assert.True(MakeLine("Verify this").HasErrors(null, null)); + Assert.False(MakeLine("verify this").HasErrors(null, null)); + }); + } + + [Fact] + public void FirstMatchingRule_DecidesTheColor() + { + RunWithRules(general => + { + general.CustomErrorRules.Add(new SeCustomErrorRule { Name = "A", Pattern = "x", Color = "#ff00ff00" }); + general.CustomErrorRules.Add(new SeCustomErrorRule { Name = "B", Pattern = "x", Color = "#ffff0000" }); + + Assert.Equal("A", MakeLine("x").GetMatchingCustomErrorRule()!.DisplayName); + }); + } + + [Fact] + public void ViewModel_DropsRulesWithoutPattern_AndFlagsInvalidPattern() + { + var vm = new CustomErrorRulesViewModel(); + vm.Initialize(true, [new SeCustomErrorRule { Name = "Keep", Pattern = "abc" }]); + vm.AddCommand.Execute(null); + + Assert.Equal(2, vm.Rules.Count); + Assert.Same(vm.Rules[1], vm.SelectedRule); + + vm.SelectedRule!.Pattern = "("; + Assert.False(vm.SelectedRule.IsPatternValid); + vm.SelectedRule.Pattern = string.Empty; + + var rules = vm.GetRules(); + Assert.Equal("Keep", Assert.Single(rules).Name); + } + + [Fact] + public void ViewModel_TestText_ReportsMatch() + { + var vm = new CustomErrorRulesViewModel(); + vm.Initialize(true, [new SeCustomErrorRule { Pattern = @"\[CHECK\]" }]); + + vm.TestText = "[CHECK] hi"; + Assert.Equal(Se.Language.Options.Settings.CustomErrorRuleTestMatch, vm.TestResult); + + vm.TestText = "hi"; + Assert.Equal(Se.Language.Options.Settings.CustomErrorRuleTestNoMatch, vm.TestResult); + } + + private static SubtitleLineViewModel MakeLine(string text) + { + return new SubtitleLineViewModel(new Paragraph(text, 0, 2000), null!); + } + + // Only the custom rules may color a line here, so every built-in rule is switched off. + private static void RunWithRules(Action test) + { + var originalSettings = Se.Settings; + try + { + Se.Settings = new Se(); + var general = Se.Settings.General; + general.ColorDurationTooShort = false; + general.ColorDurationTooLong = false; + general.ColorTextTooLong = false; + general.ColorTextTooWide = false; + general.ColorTextTooManyLines = false; + general.ColorCharactersPerSecond = false; + general.ColorWordsPerMinute = false; + general.ColorTimeCodeOverlap = false; + general.ColorGapTooShort = false; + general.ColorCustomErrorRules = true; + CustomErrorRuleMatcher.Invalidate(); + + test(general); + } + finally + { + Se.Settings = originalSettings; + CustomErrorRuleMatcher.Invalidate(); + } + } +} From 153501dc4d87cfbf1699868f24af61df5143d765 Mon Sep 17 00:00:00 2001 From: Nikolaj Olsson Date: Sat, 17 Oct 2026 14:14:59 +0200 Subject: [PATCH 305/306] Regenerate English.json Co-Authored-By: Claude Opus 5.5 --- src/ui/Assets/Languages/English.json | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/ui/Assets/Languages/English.json b/src/ui/Assets/Languages/English.json index 32c4073d3d4..50ac98d62a1 100644 --- a/src/ui/Assets/Languages/English.json +++ b/src/ui/Assets/Languages/English.json @@ -1727,6 +1727,7 @@ "moreThanTwoLines": "More than two lines", "bookmarked": "Bookmarked", "forced": "Forced", + "hasErrors": "Has errors (highlighted in the list)", "bookmarkContains": "Bookmark contains", "hearingImpaired": "Hearing impaired (SDH)", "hearingImpairedBrackets": "Brackets [...]", @@ -3471,6 +3472,15 @@ "colorOverlap": "Color time code overlap", "colorGapTooShort": "Color if gap is too short", "errorBackgroundColor": "Error background color", + "customErrorRules": "Custom error rules", + "customErrorRulesHighlight": "Color lines matching a custom error rule", + "customErrorRulesHint": "A line whose text matches an enabled rule's regular expression is colored with the rule's color and counts as an error (List errors, Go to next error). The first matching rule decides the color.", + "customErrorRuleSelected": "Selected rule", + "customErrorRuleTest": "Test", + "customErrorRuleTestMatch": "The selected rule matches the sample text", + "customErrorRuleTestNoMatch": "The selected rule does not match the sample text", + "customErrorRulesOn": "Custom error rules: on", + "customErrorRulesOff": "Custom error rules: off", "waveformDrawGridLines": "Draw grid lines", "waveformUseSkiaRenderer": "Use experimental fast renderer", "waveformCenterVideoPositionAlsoWhenPaused": "Center video position also while paused", @@ -3819,6 +3829,12 @@ "moveTextFromCursorToNextAndGoToNext": "Move text after cursor position to next subtitle and go to next", "moveTextFromCursorToNextAndGoToNextAndPlay": "Move text after cursor position to next subtitle, go to next and play", "moveTextFromCursorToNextAndGoToNextAndPlayAndPause": "Move text after cursor position to next subtitle, go to next, play it and pause at its end", + "moveTextBeforeCursorToPrevious": "Move text before cursor position to previous subtitle", + "showCustomErrorRules": "Show custom error rules", + "toggleCustomErrorRules": "Toggle custom error rules coloring on/off", + "splitAtVideoPositionAndPlay": "Split line at video position, go to second part and play", + "splitAtTextBoxCursorPositionAndPlay": "Split line at text box cursor position, go to second part and play", + "splitAtVideoAndTextBoxPositionAndPlay": "Split line at video and text box position, go to second part and play", "breakAtFirstSpaceFromCursor": "Break at first space from cursor position", "breakAtFirstSpaceFromCursorAndGoToNext": "Break at first space from cursor position and go to next", "toggleFocusGridAndWaveform": "Toggle focus between subtitle grid and waveform/spectrogram", @@ -4032,6 +4048,12 @@ "moveSelectedLinesAndForwardXMsForward": "Move selected lines and all following X ms forward (X set in Settings)", "moveAllLinesXMsBack": "Move all lines X ms back (X set in Settings)", "moveAllLinesXMsForward": "Move all lines X ms forward (X set in Settings)", + "moveSelectedLinesOneFrameBack": "Move selected lines one frame back", + "moveSelectedLinesOneFrameForward": "Move selected lines one frame forward", + "moveSelectedLinesAndForwardOneFrameBack": "Move selected lines and all following one frame back", + "moveSelectedLinesAndForwardOneFrameForward": "Move selected lines and all following one frame forward", + "moveAllLinesOneFrameBack": "Move all lines one frame back", + "moveAllLinesOneFrameForward": "Move all lines one frame forward", "moveSelectedLinesCustomMsBack": "Move selected lines, custom milliseconds ({0:#,###,##0}) back, {1}", "moveSelectedLinesCustomMsForward": "Move selected lines, custom milliseconds ({0:#,###,##0}) forward, {1}", "moveSelectedLinesAndForwardCustomMsBack": "Move selected lines and all following, custom milliseconds ({0:#,###,##0}) back, {1}", @@ -4836,6 +4858,7 @@ "lineTooLong": "Line too long", "lineTooWide": "Line too wide", "gapTooShort": "Gap too short", + "customRule": "Custom rule", "tooManyLinesHint": "More lines than the maximum number of lines", "charactersPerSecondHint": "Characters per second above the maximum", "durationTooShortHint": "Shorter than the minimum display time", @@ -4844,6 +4867,7 @@ "lineTooWideHint": "A line is wider (in pixels) than the maximum width", "overlapHint": "Overlaps the previous or next line", "gapTooShortHint": "Gap to the previous or next line is below the minimum", + "customRuleHint": "The text matches one of your custom error rules", "detailXGreaterThanY": "{0} > {1}", "detailXLessThanY": "{0} < {1}", "detailOverlapFromPrevious": "from previous: {0} ms", From 0b5704a192e0e096b7c631b78e160067d336bb50 Mon Sep 17 00:00:00 2001 From: pdjdev Date: Sat, 17 Oct 2026 21:37:55 +0900 Subject: [PATCH 306/306] Use image color picker in the color dialog --- .../AssaImageColorPickerViewModel.cs | 23 ++++++++++++++- .../AssaImageColorPickerWindow.cs | 21 ++++++++++++-- src/ui/Features/Main/MainViewModel.cs | 29 +++++++++++++++++++ .../ColorPicker/ColorPickerViewModel.cs | 19 ++++++++++++ .../Shared/ColorPicker/ColorPickerWindow.cs | 27 +++++++++++++++++ 5 files changed, 116 insertions(+), 3 deletions(-) diff --git a/src/ui/Features/Assa/AssaImageColorPicker/AssaImageColorPickerViewModel.cs b/src/ui/Features/Assa/AssaImageColorPicker/AssaImageColorPickerViewModel.cs index 9edd435ec1a..bb4d63e43ec 100644 --- a/src/ui/Features/Assa/AssaImageColorPicker/AssaImageColorPickerViewModel.cs +++ b/src/ui/Features/Assa/AssaImageColorPicker/AssaImageColorPickerViewModel.cs @@ -25,6 +25,8 @@ public partial class AssaImageColorPickerViewModel : ObservableObject { public Window? Window { get; internal set; } public bool OkPressed { get; private set; } + public bool IsColorSelection { get; private set; } + public Color? SelectedColor => HasPickedColor ? ClickedColor.Color : null; public Image? ScreenshotImage { get; set; } public Image? ScreenshotOverlayImage { get; set; } public Grid? VideoGrid { get; set; } @@ -260,6 +262,19 @@ public void Initialize(Subtitle subtitle, SubtitleLineViewModel line, string? vi TargetHeight = 1080; } + LoadFrame(videoFileName, AssaSetPositionViewModel.GetScreenshotSeconds(line, videoPositionSeconds)); + } + + public void InitializeForColorSelection(string? videoFileName, int videoWidth, int videoHeight, double positionSeconds) + { + IsColorSelection = true; + TargetWidth = videoWidth; + TargetHeight = videoHeight; + LoadFrame(videoFileName, positionSeconds); + } + + private void LoadFrame(string? videoFileName, double positionSeconds) + { if (string.IsNullOrEmpty(videoFileName)) { Screenshot = BinaryAdjustAlphaViewModel.CreateCheckeredBackground(TargetWidth, TargetHeight); @@ -268,7 +283,7 @@ public void Initialize(Subtitle subtitle, SubtitleLineViewModel line, string? vi // Pass exact seconds (not ToDisplayString, which honors the HH:MM:SS:FF time-code format // setting and would feed ffmpeg an unparseable "-ss 00:01:23:15", blanking the preview - #12182). - var fileName = FfmpegGenerator.GetScreenShot(videoFileName, AssaSetPositionViewModel.GetScreenshotSeconds(line, videoPositionSeconds).ToString("0.###", CultureInfo.InvariantCulture)); + var fileName = FfmpegGenerator.GetScreenShot(videoFileName, positionSeconds.ToString("0.###", CultureInfo.InvariantCulture)); if (System.IO.File.Exists(fileName)) { try @@ -290,6 +305,11 @@ public void Initialize(Subtitle subtitle, SubtitleLineViewModel line, string? vi [RelayCommand] private async Task Ok() { + if (IsColorSelection && !HasPickedColor) + { + return; + } + OkPressed = true; Close(); } @@ -312,6 +332,7 @@ internal void KeyDown(object? sender, KeyEventArgs e) { if (e.Key == Key.Escape) { + e.Handled = true; Close(); } else if (UiUtil.IsHelp(e)) diff --git a/src/ui/Features/Assa/AssaImageColorPicker/AssaImageColorPickerWindow.cs b/src/ui/Features/Assa/AssaImageColorPicker/AssaImageColorPickerWindow.cs index eecf01fc401..1fa0b26f791 100644 --- a/src/ui/Features/Assa/AssaImageColorPicker/AssaImageColorPickerWindow.cs +++ b/src/ui/Features/Assa/AssaImageColorPicker/AssaImageColorPickerWindow.cs @@ -250,12 +250,29 @@ private static Border MakeColorsPanel(AssaImageColorPickerViewModel vm) private static Border MakeButtonBar(AssaImageColorPickerViewModel vm, out Button buttonDone) { - buttonDone = UiUtil.MakeButtonDone(vm.OkCommand); + buttonDone = vm.IsColorSelection ? UiUtil.MakeButtonOk(vm.OkCommand) : UiUtil.MakeButtonDone(vm.OkCommand); buttonDone.Classes.Add("primary"); buttonDone.HorizontalAlignment = HorizontalAlignment.Right; + if (vm.IsColorSelection) + { + buttonDone.Bind(IsEnabledProperty, new Binding(nameof(vm.HasPickedColor))); + } + + var buttons = new StackPanel + { + Orientation = Orientation.Horizontal, + HorizontalAlignment = HorizontalAlignment.Right, + Spacing = 8, + }; + if (vm.IsColorSelection) + { + buttons.Children.Add(UiUtil.MakeButtonCancel(vm.CancelCommand)); + } + + buttons.Children.Add(buttonDone); return new Border { - Child = buttonDone, + Child = buttons, Background = EditorChrome.ButtonBarBrush, BorderBrush = EditorChrome.LineBrush, BorderThickness = new Thickness(0, 1, 0, 0), diff --git a/src/ui/Features/Main/MainViewModel.cs b/src/ui/Features/Main/MainViewModel.cs index 02aaa516891..4c83b251474 100644 --- a/src/ui/Features/Main/MainViewModel.cs +++ b/src/ui/Features/Main/MainViewModel.cs @@ -2960,6 +2960,35 @@ private async Task ShowAssaImageColorPicker() RefreshSubtitlePreview(); } + internal bool CanPickColorFromImage => !string.IsNullOrEmpty(_videoFileName) && + _mediaInfo?.Dimension is { Width: > 0, Height: > 0 }; + + internal async Task PickColorFromImageAsync(Window owner) + { + if (!CanPickColorFromImage) + { + return null; + } + + var player = GetVideoPlayerControl(); + if (player != null) + { + PauseVideoAndFreezePlayhead(player); + } + + var position = player?.Position ?? 0; + if (!await FfmpegRequirement.EnsureAsync(owner, + async () => (await _windowService.ShowDialogAsync(owner)).FfmpegFileName)) + { + return null; + } + + var result = await ShowDialogAsync( + vm => vm.InitializeForColorSelection(_videoFileName!, _mediaInfo!.Dimension.Width, _mediaInfo.Dimension.Height, position), + owner: owner); + return result.OkPressed ? result.SelectedColor : null; + } + [RelayCommand] private async Task ShowAssaSetPosition() { diff --git a/src/ui/Features/Shared/ColorPicker/ColorPickerViewModel.cs b/src/ui/Features/Shared/ColorPicker/ColorPickerViewModel.cs index a17f5c82dc8..3ed42c442a6 100644 --- a/src/ui/Features/Shared/ColorPicker/ColorPickerViewModel.cs +++ b/src/ui/Features/Shared/ColorPicker/ColorPickerViewModel.cs @@ -9,6 +9,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Threading.Tasks; namespace Nikse.SubtitleEdit.Features.Shared.ColorPicker; @@ -33,6 +34,9 @@ public partial class ColorPickerViewModel : ObservableObject [ObservableProperty] private Color _alphaGradientEnd = Colors.White; [ObservableProperty] private bool _showAlpha = true; + [ObservableProperty] private bool _canSelectFromImage; + + internal Func>? PickFromImageAsync { get; set; } // HSV state for the color wheel (hue/saturation) and the brightness bar. Kept separately // from RGB so hue and saturation survive dragging the brightness down to black and back. @@ -69,6 +73,21 @@ public void Initialize(Color initialColor) _isUpdating = false; } + [RelayCommand] + private async Task SelectFromImage() + { + if (!CanSelectFromImage || Window == null || PickFromImageAsync == null) + { + return; + } + + var color = await PickFromImageAsync(Window); + if (color is { } picked) + { + Initialize(Color.FromArgb(Alpha, picked.R, picked.G, picked.B)); + } + } + partial void OnRedChanged(byte value) { if (!_isUpdating) diff --git a/src/ui/Features/Shared/ColorPicker/ColorPickerWindow.cs b/src/ui/Features/Shared/ColorPicker/ColorPickerWindow.cs index 4791ac25fd9..50a8578c724 100644 --- a/src/ui/Features/Shared/ColorPicker/ColorPickerWindow.cs +++ b/src/ui/Features/Shared/ColorPicker/ColorPickerWindow.cs @@ -5,8 +5,11 @@ using Avalonia.Input; using Avalonia.Layout; using Avalonia.Media; +using Avalonia.VisualTree; +using Nikse.SubtitleEdit.Features.Main; using Nikse.SubtitleEdit.Logic; using Nikse.SubtitleEdit.Logic.Config; +using System.Linq; namespace Nikse.SubtitleEdit.Features.Shared.ColorPicker; @@ -37,6 +40,24 @@ public ColorPickerWindow(ColorPickerViewModel vm) UiUtil.FocusOnFirstActivation(this, hexTextBox); // initial focus on an input, not an action button - a focused button clicks on bare Space KeyDown += (_, e) => vm.OnKeyDown(e); + Opened += (_, _) => ConfigureImagePicker(vm); + } + + private void ConfigureImagePicker(ColorPickerViewModel vm) + { + // Use this dialog's editor, including when opened through a style/settings dialog + // or from a second editor window. + for (var owner = Owner; owner != null; owner = owner.Owner) + { + var main = owner.DataContext as MainViewModel ?? + owner.GetVisualDescendants().OfType().FirstOrDefault()?.DataContext as MainViewModel; + if (main != null) + { + vm.CanSelectFromImage = main.CanPickColorFromImage; + vm.PickFromImageAsync = main.PickColorFromImageAsync; + break; + } + } } private static StackPanel MakeColorView(ColorPickerViewModel vm, out TextBox hexTextBox) @@ -265,6 +286,12 @@ private static StackPanel CreateSlidersPanel(ColorPickerViewModel vm, out TextBo panel.Children.Add(alphaSlider); panel.Children.Add(hexPanel); + var selectFromImage = UiUtil.MakeButton(Se.Language.Assa.ImageColorPicker + "...", vm.SelectFromImageCommand); + selectFromImage.HorizontalAlignment = HorizontalAlignment.Left; + selectFromImage.Margin = new Thickness(0, 8, 0, 0); + selectFromImage.Bind(IsVisibleProperty, new Binding(nameof(vm.CanSelectFromImage))); + panel.Children.Add(selectFromImage); + return panel; }
/// Add audio track to video, mixed over a separate background track (the original sound with /// the speech removed) instead of over the video's own audio. diff --git a/tests/UI/Features/Video/SpeechToText/Engines/SpeechIsolationProgressTests.cs b/tests/UI/Features/Video/SpeechToText/Engines/SpeechIsolationProgressTests.cs index 1e414438271..c9ed05a5d18 100644 --- a/tests/UI/Features/Video/SpeechToText/Engines/SpeechIsolationProgressTests.cs +++ b/tests/UI/Features/Video/SpeechToText/Engines/SpeechIsolationProgressTests.cs @@ -65,6 +65,31 @@ public void ChunkLinesAndLayerLinesMoveTheFraction() Assert.Equal(100, progress.Percent); } + [Theory] + [InlineData(120.0, 21)] // CrispASR 0.8.41: "segmented 5292000 samples into 21 chunks of 343980 (stride 257985)" + [InlineData(7.0, 1)] + public void HtDemucsChunkCountMatchesTheSeparator(double seconds, int expected) + { + Assert.Equal(expected, SpeechIsolationProgress.GetChunkCount(seconds, HtDemucsModel.ChunkSamples, HtDemucsModel.StrideSamples)); + } + + [Fact] + public void HtDemucsChunkDoneLinesMoveTheFraction() + { + var progress = new SpeechIsolationProgress(4); + Assert.False(progress.TryUpdate("htdemucs: encoder OK, output (384, 8, 336)")); + Assert.Null(progress.Fraction); + + Assert.True(progress.TryUpdate("htdemucs: separated 343980 samples → 4 sources (2 ch @ 44100 Hz)")); + Assert.Equal(0.25, progress.Fraction!.Value, 6); + + Assert.True(progress.TryUpdate("htdemucs: separated 343980 samples → 4 sources (2 ch @ 44100 Hz)")); + Assert.Equal(50, progress.Percent); + + Assert.True(progress.TryUpdate("htdemucs: segmented 1031940 samples into 4 chunks of 343980 (stride 257985)")); + Assert.Equal(1.0, progress.Fraction!.Value, 6); + } + [Fact] public void UnknownChunkCountGivesNoPercentage() { From 8e9dcd2d70c0df781234b200a98d34d7d5cefd96 Mon Sep 17 00:00:00 2001 From: niksedk Date: Sun, 11 Oct 2026 17:13:26 +0200 Subject: [PATCH 066/306] Docs and help review: F1 coverage, shortcut fixes, 5.3 docs - Help: https URLs, fix 5 wrong help targets, F1 help in 35 more dialogs - Shortcut docs: numpad vertical zoom, macOS defaults, F9, command palette, grid navigation keys, configurable Help key - What's New in 5.3 section - New pages: command palette, background music, merge continuation lines; DVD import, reload-on-change and Close translation in File - Stale pages: batch convert, settings, compare, translate, shot changes, video player, FAQ, overview, TTS, STT, menu paths Co-Authored-By: Claude Opus 5.5 --- docs/faq.md | 40 ++++--- docs/features/apply-duration-limits.md | 6 +- docs/features/assa-attachments.md | 2 +- docs/features/assa-image-color-picker.md | 2 +- docs/features/assa-override-tags.md | 2 +- docs/features/assa-progress-bar.md | 2 +- docs/features/assa-properties.md | 2 +- docs/features/assa-resolution-resampler.md | 2 +- docs/features/assa-set-background.md | 2 +- docs/features/assa-set-position.md | 2 +- docs/features/assa-styles.md | 2 +- docs/features/assisted-split.md | 2 +- docs/features/audio-visualizer.md | 6 +- docs/features/auto-translate-advanced.md | 6 +- docs/features/auto-translate.md | 14 ++- docs/features/background-music.md | 91 +++++++++++++++ docs/features/batch-convert.md | 41 +++++-- docs/features/blank-video.md | 2 +- docs/features/burn-in.md | 6 +- docs/features/change-frame-rate.md | 4 +- docs/features/command-palette.md | 39 +++++++ docs/features/compare.md | 16 ++- docs/features/cut-video.md | 2 +- docs/features/edit.md | 16 ++- docs/features/embedded-subtitles.md | 2 +- docs/features/file.md | 45 +++++++- docs/features/list-errors.md | 2 +- docs/features/main-window.md | 24 ++-- docs/features/merge-continuation-lines.md | 56 +++++++++ docs/features/ocr.md | 16 +-- docs/features/point-sync-via-other.md | 4 +- docs/features/point-sync.md | 2 +- docs/features/re-encode-video.md | 2 +- docs/features/remove-unicode-characters.md | 2 +- docs/features/remux-video.md | 2 +- docs/features/renumber.md | 2 +- docs/features/settings.md | 39 +++++-- docs/features/shortcuts.md | 15 ++- docs/features/shot-changes.md | 17 ++- docs/features/speech-to-text.md | 7 +- docs/features/spell-check.md | 2 +- docs/features/statistics.md | 2 +- docs/features/subtitle-grid.md | 22 +++- docs/features/text-editor.md | 2 + docs/features/text-to-speech.md | 18 ++- docs/features/transparent-subtitles.md | 2 +- docs/features/video-player.md | 6 +- docs/features/visual-sync.md | 2 +- docs/features/webvtt-styles.md | 2 +- docs/features/whats-new-in-se5.md | 108 +++++++++++++++++- docs/features/word-lists.md | 2 +- docs/index.md | 5 +- docs/overview.md | 16 ++- docs/reference/command-line.md | 4 +- docs/reference/keyboard-shortcuts.md | 30 +++-- docs/reference/mouse-controls.md | 4 +- .../MultipleReplaceViewModel.cs | 2 +- .../Files/ImportDvd/ImportDvdViewModel.cs | 6 + .../ManualChosenEncodingViewModel.cs | 5 + .../BinaryOcrCharacterAddViewModel.cs | 5 + .../BinaryOcrCharacterHistoryViewModel.cs | 5 + .../Ocr/NOcr/NOcrCharacterAddViewModel.cs | 5 + .../Ocr/NOcr/NOcrCharacterHistoryViewModel.cs | 5 + .../Settings/ProfilesExportViewModel.cs | 6 + .../Settings/SettingsResetViewModel.cs | 6 + .../CustomSearch/CustomSearchViewModel.cs | 2 +- .../SurroundWith/SurroundWithViewModel.cs | 2 +- .../BinaryAdjustAllTimesViewModel.cs | 6 + .../BinaryAdjustAlphaViewModel.cs | 5 + .../BinaryAdjustBrightnessViewModel.cs | 5 + .../BinaryAdjustColorViewModel.cs | 5 + .../BinaryAdjustDurationViewModel.cs | 5 + .../BinaryAppendSubtitleViewModel.cs | 6 + .../BinaryApplyDurationLimitsViewModel.cs | 6 + .../BinaryChangeResolutionViewModel.cs | 5 + .../BinaryMoveCaptionsViewModel.cs | 5 + .../BinaryResizeImagesViewModel.cs | 5 + .../BinarySettings/BinarySettingsViewModel.cs | 6 + .../BinaryEdit/SetText/SetTextWindow.cs | 5 + .../Bookmarks/BookmarksListViewModel.cs | 5 + .../ColumnPaste/ColumnPasteViewModel.cs | 6 + .../CommandPalette/CommandPaletteViewModel.cs | 8 ++ .../OpenOriginalMismatchViewModel.cs | 6 + .../SetVideoOffset/SetVideoOffsetViewModel.cs | 6 + .../Sync/VisualSync/ManualSyncViewModel.cs | 2 +- .../MergeContinuationLinesViewModel.cs | 5 + .../Translate/TranslationErrorViewModel.cs | 6 + .../BackgroundMusicViewModel.cs | 5 + .../DownloadVideoFromUrlViewModel.cs | 5 + .../PickOnlineSubtitleViewModel.cs | 5 + .../EncodingSettingsViewModel.cs | 2 +- .../SpeakFromLine/SpeakFromLineViewModel.cs | 5 + .../VoicePacks/DownloadVoicePacksViewModel.cs | 5 + src/ui/Logic/UiUtil.cs | 4 +- 94 files changed, 822 insertions(+), 139 deletions(-) create mode 100644 docs/features/background-music.md create mode 100644 docs/features/command-palette.md create mode 100644 docs/features/merge-continuation-lines.md diff --git a/docs/faq.md b/docs/faq.md index fbd89329b71..3392042f5fc 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -46,10 +46,11 @@ You can use **File → Import → Image-based subtitle for edit...** to work/exp ### What video players are supported? Subtitle Edit supports: -- **libmpv** — High-quality, wide format support -- **VLC** - Popular player, limited support in SE +- **libmpv** — High-quality, wide format support (default). Several rendering modes: OpenGL, Native Window ID (not macOS), Software rendering, and on macOS **libmpv - Metal (experimental, needs restart)** +- **libVLC** — Popular player, limited support in SE (Windows and Linux only) +- **FFmpeg** — Plays video through FFmpeg (all platforms) -Configure the video player in **Options → Settings → Video Player**. +Configure the video player in **Options → Settings → Video player**. If you have problems with a video, try **Video → More → Re-encode video for better subtitling**. @@ -70,14 +71,16 @@ You can also download FFmpeg in **Options → Settings**. ### What speech-to-text engines are supported? Subtitle Edit supports several local and downloadable speech-to-text engines: -- **Whisper.cpp** — Cross-platform, CPU-based -- **Whisper.cpp (cuBLAS)** — GPU-accelerated (Windows, NVIDIA) -- **Whisper.cpp (Vulkan)** — GPU-accelerated via Vulkan (Windows) -- **Purfview's Faster-Whisper-XXL** — Fast GPU/CPU-based (Windows, Linux) -- **CTranslate2** — Fast CPU/GPU-based -- **Const-me's Whisper** — DirectX-based (Windows) +- **Whisper CPP** — Cross-platform, CPU-based. On Windows the cuBLAS (NVIDIA) and Vulkan GPU backends are picked from its **Backend** drop-down +- **Purfview Faster Whisper XXL** — Fast GPU/CPU-based (Windows, Linux x64) +- **Whisper CTranslate2** — Fast CPU/GPU-based +- **Whisper Const-me** — DirectX-based (Windows) - **WhisperX** — Faster-Whisper with wav2vec2 word-level alignment, standalone build (Windows x64, Linux x64, macOS Apple Silicon) -- **OpenAI Whisper** — Original Python implementation +- **Whisper OpenAI** — Original Python implementation +- **Qwen3 ASR CPP** and **Crisp ASR** (many backends: Parakeet, Canary, Cohere, Qwen3 and more) — local engines +- **OpenAI Compatible Server**, **OpenRouter**, **Alibaba Qwen3-ASR** and **Google Cloud Speech-to-Text** — online services + +See [Speech to Text](features/speech-to-text.md) for details. - **OpenAI-compatible STT** — Send audio to any OpenAI-compatible speech endpoint - **Qwen3 ASR CPP** — Local GGUF-based engine (Windows, Linux, macOS) - **Crisp ASR** — Local engine with multiple backends (Parakeet, Canary, Cohere, Fire Red, Fun-ASR Nano, GigaAM, GLM, Granite, Qwen3, Mega, MOSS Diarize, Omni, Kyutai, SenseVoice, ARK, Voxtral) and a Forced-aligner picker (built-in / Canary CTC / Qwen3 / 12 wav2vec2 language aligners) for word-level timestamps @@ -135,7 +138,7 @@ Use **Synchronization → Change frame rate...** and select the source and targe Go to **Spell check → Get dictionaries...** and download the dictionary for your language. Dictionaries are based on Hunspell/OpenOffice format. ### How do I add words to the dictionary? -During spell check, click **Add to dictionary** to add a word. You can also manage custom words in **Options → Word lists**. +During spell check, click **Add to user dictionary** to add a word. You can also manage custom words in **Options → Word lists**. --- @@ -163,16 +166,25 @@ See the [Keyboard Shortcuts Reference](reference/keyboard-shortcuts.md) for the ### Subtitle Edit won't play video - Ensure you have a video player installed (libmpv or VLC) -- Check that the video player path is correctly set in **Options → Settings → Video Player** +- Check that the video player path is correctly set in **Options → Settings → Video player** - Try to re-encode the video for better compatibility via **Video → More → Re-encode video for better subtitling** ### Video is black with no sound, but the waveform works The waveform is made with FFmpeg while the picture comes from libmpv, so this points at libmpv failing to load rather than at the file. - Update your graphics driver from Intel/NVIDIA/AMD directly — the generic Microsoft Basic Display driver has no usable OpenGL -- Re-download libmpv via **Options → Settings → Video Player → Download libmpv** +- Re-download libmpv via **Options → Settings → Video player → Download libmpv** - If you installed libmpv by hand, make sure `vulkan-1.dll` sits next to `libmpv-2.dll` — recent libmpv builds do not load without it (see [Third-Party Components](third-party-components.md)) - Check that your antivirus is not blocking `libmpv-2.dll` -- As a cross-check, switch to VLC under **Options → Settings → Video Player**; if VLC plays the file, the problem is libmpv/OpenGL +- As a cross-check, switch to VLC under **Options → Settings → Video player**; if VLC plays the file, the problem is libmpv/OpenGL + +### Video does not play on Linux +With the native `.tar.gz` builds, Subtitle Edit uses the system libmpv. If it is not installed (common on Fedora), the video area stays empty, with no picture or sound. +- Install libmpv and FFmpeg with your package manager, e.g. `sudo apt install libmpv-dev ffmpeg` (Debian/Ubuntu) or `sudo dnf install mpv-libs ffmpeg-free` (Fedora) - see the [README](https://github.com/SubtitleEdit/subtitleedit#linux) for other distributions +- Or use the Flatpak, which bundles mpv and FFmpeg +- As a fallback, switch to **FFmpeg** under **Options → Settings → Video player** + +### Video does not play on macOS +The `.dmg` bundles libmpv and FFmpeg, so nothing needs to be installed. If video still does not show, try another player under **Options → Settings → Video player** (e.g. **FFmpeg**, or **libmpv - Metal (experimental, needs restart)** - restart Subtitle Edit after switching to it). ### Waveform is not showing - Ensure FFmpeg is installed and the path is set in settings diff --git a/docs/features/apply-duration-limits.md b/docs/features/apply-duration-limits.md index ce364bcd654..d925ee31882 100644 --- a/docs/features/apply-duration-limits.md +++ b/docs/features/apply-duration-limits.md @@ -9,8 +9,10 @@ Enforce minimum and maximum duration limits on subtitle lines. ## Options -- **Fix min duration** — Enable the minimum limit and set the minimum display time in milliseconds +- **Fix minimum duration (ms)** — Enable the minimum limit and set the minimum display time in milliseconds - **Do not go past shot change** — Shown when shot changes are loaded: a line extended to reach the minimum duration stops at the next shot change -- **Fix max duration** — Enable the maximum limit and set the maximum display time in milliseconds +- **Fix maximum duration (ms)** — Enable the maximum limit and set the maximum display time in milliseconds + +When the global *Time code mode* setting is one of the frame modes, the labels read **Fix minimum duration (frames)** / **Fix maximum duration (frames)** and both values are entered in frames at the current frame rate (like [Bridge gaps](bridge-gaps.md) and [Apply min. gap](apply-min-gap.md)). The values are still stored in milliseconds, and a frame count you leave unchanged keeps the saved millisecond value. The window shows two lists that update live as you change values: the proposed fixes, each with an **Apply** checkbox, and the lines that cannot be fixed (because there is not enough room before the next subtitle). The counts are reported under the lists. diff --git a/docs/features/assa-attachments.md b/docs/features/assa-attachments.md index 059a981ba21..d67181df01e 100644 --- a/docs/features/assa-attachments.md +++ b/docs/features/assa-attachments.md @@ -45,5 +45,5 @@ Manage fonts and images embedded as attachments in an Advanced SubStation Alpha | Shortcut | Action | |----------|--------| -| F1 | Show help | +| F1 | Show help (default key, [changeable](shortcuts.md#worth-knowing)) | | Escape | Close dialog | diff --git a/docs/features/assa-image-color-picker.md b/docs/features/assa-image-color-picker.md index a093d4b798b..2e7a17fa049 100644 --- a/docs/features/assa-image-color-picker.md +++ b/docs/features/assa-image-color-picker.md @@ -37,5 +37,5 @@ Pick colors directly from a video frame screenshot for use in ASSA subtitle styl | Shortcut | Action | |----------|--------| -| F1 | Show help | +| F1 | Show help (default key, [changeable](shortcuts.md#worth-knowing)) | | Escape | Close dialog | diff --git a/docs/features/assa-override-tags.md b/docs/features/assa-override-tags.md index 43b516153bb..d4e30cbfdad 100644 --- a/docs/features/assa-override-tags.md +++ b/docs/features/assa-override-tags.md @@ -41,5 +41,5 @@ Apply ASSA override tags to subtitle lines with a live video preview. | Shortcut | Action | |----------|--------| -| F1 | Show help | +| F1 | Show help (default key, [changeable](shortcuts.md#worth-knowing)) | | Escape | Close dialog | diff --git a/docs/features/assa-progress-bar.md b/docs/features/assa-progress-bar.md index e6e761848d5..421979b826e 100644 --- a/docs/features/assa-progress-bar.md +++ b/docs/features/assa-progress-bar.md @@ -43,5 +43,5 @@ Generate an animated progress bar overlay in ASSA format, with optional chapter | Shortcut | Action | |----------|--------| -| F1 | Show help | +| F1 | Show help (default key, [changeable](shortcuts.md#worth-knowing)) | | Escape | Close dialog | diff --git a/docs/features/assa-properties.md b/docs/features/assa-properties.md index 724592366d4..0348ad48325 100644 --- a/docs/features/assa-properties.md +++ b/docs/features/assa-properties.md @@ -43,5 +43,5 @@ Edit the script info header properties of an Advanced SubStation Alpha (ASS/SSA) | Shortcut | Action | |----------|--------| -| F1 | Show help | +| F1 | Show help (default key, [changeable](shortcuts.md#worth-knowing)) | | Escape | Close dialog | diff --git a/docs/features/assa-resolution-resampler.md b/docs/features/assa-resolution-resampler.md index 79165dc7569..7543c910354 100644 --- a/docs/features/assa-resolution-resampler.md +++ b/docs/features/assa-resolution-resampler.md @@ -35,7 +35,7 @@ Resample ASSA subtitle styles and positions from one video resolution to another | Shortcut | Action | |----------|--------| -| F1 | Show help | +| F1 | Show help (default key, [changeable](shortcuts.md#worth-knowing)) | | Escape | Close dialog | ## Resolution set from the video diff --git a/docs/features/assa-set-background.md b/docs/features/assa-set-background.md index a58d6305010..cb848d6dfe5 100644 --- a/docs/features/assa-set-background.md +++ b/docs/features/assa-set-background.md @@ -43,5 +43,5 @@ Add styled background boxes behind subtitle text using ASSA drawing commands. | Shortcut | Action | |----------|--------| -| F1 | Show help | +| F1 | Show help (default key, [changeable](shortcuts.md#worth-knowing)) | | Escape | Close dialog | diff --git a/docs/features/assa-set-position.md b/docs/features/assa-set-position.md index cac505f0f33..3a661ed795d 100644 --- a/docs/features/assa-set-position.md +++ b/docs/features/assa-set-position.md @@ -35,5 +35,5 @@ Visually position subtitles on screen by dragging them over a video screenshot, | Shortcut | Action | |----------|--------| -| F1 | Show help | +| F1 | Show help (default key, [changeable](shortcuts.md#worth-knowing)) | | Escape | Close dialog | diff --git a/docs/features/assa-styles.md b/docs/features/assa-styles.md index 85e40850813..aaba413c676 100644 --- a/docs/features/assa-styles.md +++ b/docs/features/assa-styles.md @@ -52,5 +52,5 @@ Manage Advanced SubStation Alpha (ASS/SSA) subtitle styles, including file style | Shortcut | Action | |----------|--------| -| F1 | Show help | +| F1 | Show help (default key, [changeable](shortcuts.md#worth-knowing)) | | Escape | Close dialog | diff --git a/docs/features/assisted-split.md b/docs/features/assisted-split.md index 9d0edb41200..e268613b6b7 100644 --- a/docs/features/assisted-split.md +++ b/docs/features/assisted-split.md @@ -87,4 +87,4 @@ A sentence "continues" when the earlier text does not end with `.`, `!`, `?` or |-----|--------| | 1-5 (split) / 1-6 (move) | Apply that candidate | | Escape | Close without changes | -| F1 | Open help | +| F1 | Show help (default key, [changeable](shortcuts.md#worth-knowing)) | diff --git a/docs/features/audio-visualizer.md b/docs/features/audio-visualizer.md index 576207b74b4..ed48e8b05d8 100644 --- a/docs/features/audio-visualizer.md +++ b/docs/features/audio-visualizer.md @@ -50,10 +50,10 @@ What a single click and a double-click do is configurable: **Waveform single-cli | Shortcut | Action | |----------|--------| -| Set start | Set the start time of the selected subtitle | -| Set end | Set the end time of the selected subtitle | +| Set start | Set the start time of the selected subtitle (F11; F9 on macOS) | +| Set end | Set the end time of the selected subtitle (F12) | | Set end and go to next | Set end time and move to next subtitle | -| Set start and offset the rest | Set start and shift all following subtitles | +| Set start and offset the rest | Set start and shift all following subtitles (F9 on Windows / Linux; no default on macOS) | | Set end and offset the rest | Set end and shift all following subtitles | | Center on video position | Center the waveform on the current video position | | Insert at position (focus text) | Insert a new subtitle at video position and focus the text editor | diff --git a/docs/features/auto-translate-advanced.md b/docs/features/auto-translate-advanced.md index 6b3dcb007eb..1b64713ad9c 100644 --- a/docs/features/auto-translate-advanced.md +++ b/docs/features/auto-translate-advanced.md @@ -1,6 +1,8 @@ # Auto Translate: Advanced Local Engines -The **llama.cpp advanced (local LLM)** and **Ollama advanced (local LLM)** engines translate in batches with surrounding context, a synopsis, and a glossary — giving more consistent names, pronouns, and terminology than line-by-line translation. This page covers their **Advanced...** settings dialog and the llama.cpp engine settings. +The **llama.cpp advanced (local LLM)**, **Ollama advanced (local LLM)** and **LM Studio advanced (local LLM)** engines translate in batches with surrounding context, a synopsis, and a glossary — giving more consistent names, pronouns, and terminology than line-by-line translation. This page covers their **Advanced...** settings dialog and the llama.cpp engine settings. + +LM Studio advanced talks to LM Studio's OpenAI-compatible endpoint (default `http://localhost:1234/v1/chat/completions`). LM Studio can have several models loaded; leave the model empty to use the loaded one. See [Auto Translate](auto-translate.md) for the translation window itself. @@ -27,7 +29,7 @@ With an advanced engine selected, click **Advanced...** in the engine settings r - **Temperature** — Randomness. When unset, Subtitle Edit uses the curated value for the model, or 0.2 — low temperatures keep terminology consistent across batches - **Top-p**, **Top-k**, **Repeat penalty** — Standard sampling options, passed through when set - **Max tokens per reply** — Response length cap; unset means no cap is sent -- **Server context size (tokens)** — The context window for the locally started llama.cpp server (default 16384). Bigger batches, more history, and a long synopsis/glossary need more context. Only affects the local llama.cpp server; Ollama manages its own context +- **Server context size (tokens)** — The context window for the locally started llama.cpp server (default 16384). Bigger batches, more history, and a long synopsis/glossary need more context. Only affects the local llama.cpp server; Ollama and LM Studio manage their own context - **Extra server parameters** — Additional `llama-server` command-line arguments, e.g. `-ngl 30 --no-mmap`, applied when the local server starts. They are appended after Subtitle Edit's own flags, so a repeated flag overrides Subtitle Edit's value - **Use only these parameters** — Start `llama-server` with the parameters above instead of Subtitle Edit's own tuning; only the model, host and port are still set. Ignored while the field is empty diff --git a/docs/features/auto-translate.md b/docs/features/auto-translate.md index 8c4b308b27f..87cd9de55c4 100644 --- a/docs/features/auto-translate.md +++ b/docs/features/auto-translate.md @@ -17,6 +17,12 @@ Automatically translate subtitles using various translation engines and AI servi 5. Review the translations in the grid 6. Click **OK** to apply +### Translating selected lines + +Right-click selected lines in the grid and choose **Auto-translate** to translate only those lines. With no original column open, the dialog offers **Translate in place (do not create original)**: the translated text replaces the selected lines' text instead of turning the subtitle into the original. Otherwise the whole subtitle becomes the original, as with a whole-file translation. + +The shortcut **Auto-translate selected lines (no prompt, use last engine/languages)** (no default key; set it in [Shortcuts](shortcuts.md)) starts right away with the last used engine and languages; the dialog only shows progress and closes when done. Until a translation has been run once, it prompts as usual. + ## Supported Engines - **Google Translate V1 API** — Free Google Translate. When Google answers with its "unusual traffic" block page, Subtitle Edit retries the request through a fallback endpoint (`clients5.google.com`) before giving up; a block is on Google's side and is usually lifted again after minutes to a few hours @@ -27,6 +33,7 @@ Automatically translate subtitles using various translation engines and AI servi - **MyMemory Translate** — Free translation memory - **ChatGPT** — OpenAI AI translation (requires API key) - **LM Studio (local LLM)** — Local LLM translation +- **LM Studio advanced (local LLM)** — LM Studio translated in batches with surrounding context, a synopsis and a glossary; see [Advanced local engines](auto-translate-advanced.md) - **Ollama (local LLM)** — Local LLM-based translation - **Ollama advanced (local LLM)** — Ollama translated in batches with surrounding context, a synopsis and a glossary; see [Advanced local engines](auto-translate-advanced.md) - **llama.cpp (local LLM)** — Server-managed local LLM translation; Subtitle Edit downloads llama.cpp and a curated model (TranslateGemma, Gemma 4, Qwen, Aya Expanse, MiLMMT, Hy-MT2 or TranslatePsy-AfriSLM for English ↔ 19 African languages) and runs a local `llama-server` for you. See [Using your own model](#llamacpp-using-your-own-model) to run a model we don't ship, such as TranslateGemma 27B @@ -35,6 +42,7 @@ Automatically translate subtitles using various translation engines and AI servi - **Anthropic Claude** — AI translation (requires API key) - **Groq** — AI translation (requires API key) - **OpenRouter** — AI translation (requires API key) +- **API Route** — AI translation through [api-route.com](https://www.api-route.com) (requires API key) - **Lara** — AI translation (requires API key) - **Perplexity** — AI translation (requires API key) - **Google Gemini** — AI translation (requires API key) @@ -105,9 +113,9 @@ Depending on the selected engine, you may need to provide: - **API URL** — Custom endpoint URL (for self-hosted services) - **Model** — Specific model to use (for AI engines) -The gear button opens the engine's **Settings** dialog with **Line merge**, **Delay in seconds between requests**, **Max bytes per request** and, for engines that have one, the **Prompt text** with a reset-to-default button. +The gear button opens the engine's **Settings** dialog with **Line merge**, **Delay in seconds between requests**, **Max bytes per request** and, for engines that have one, the **Prompt text** with a reset-to-default button. **Do not translate lines in music symbols (♪ ... ♪)** leaves song lines untranslated. -The **llama.cpp advanced** and **Ollama advanced** engines translate in batches with context, synopsis, and glossary support — see [Advanced Local Engines](auto-translate-advanced.md). +The **llama.cpp advanced**, **Ollama advanced** and **LM Studio advanced** engines translate in batches with context, synopsis, and glossary support — see [Advanced Local Engines](auto-translate-advanced.md). ## Translation Review @@ -122,4 +130,4 @@ Switching engine to compare results keeps the source and target languages you pi | Key | Action | |-----|--------| | Escape | Close / Cancel | -| F1 | Open help | +| F1 | Show help (default key, [changeable](shortcuts.md#worth-knowing)) | diff --git a/docs/features/background-music.md b/docs/features/background-music.md new file mode 100644 index 00000000000..03717ddb5b7 --- /dev/null +++ b/docs/features/background-music.md @@ -0,0 +1,91 @@ +# Generate Background Music + +Generate royalty-free instrumental background music from a text prompt, loop it seamlessly to the length of the video, and put it on the video - or mix it under the speech made by [Text to Speech](text-to-speech.md). + +- **Menu:** Video → More → Generate background music... +- **Shortcut:** None (menu only) + + + +The music is made on your own computer by **ACE-Step 1.5 Turbo** (MIT license) running on the audio.cpp runtime - nothing is sent to an online service. The **More** submenu is shown while a video is loaded, and ffmpeg is required (you are offered to download it when it is missing). + +## How to Use + +1. Open **Video → More → Generate background music...** +2. Pick a **Preset**, or choose *Custom* and write your own **Prompt**. +3. Click **Generate**. The first time, the runtime and the model are downloaded (see [Requirements](#requirements)). The progress bar shows the current step - loading the model, composing, generating audio, decoding audio, creating a seamless loop - and the estimated time left. +4. Click **Play** to listen (click **Stop** to stop). Not happy? Click **Generate** again - with **Random** seed checked, every run gives a different piece. +5. Click **Add to video...** to write a copy of the video with the music, or **Save audio...** to save the music as a `.wav` file. + +The window is not modal, so the main window stays usable while it is open; only one background music window can be open at a time. **Cancel** while generating or adding stops the work; otherwise the button reads **Close**. + +## Presets + +Each preset fills in a prompt and a tempo that were tried to give music without vocals that loops cleanly: + +- Cooking / baking (120 BPM) +- Corporate / explainer (100 BPM) +- Forest / nature (84 BPM) +- Lo-fi vlog (85 BPM) +- Relaxing piano (72 BPM) +- Tech / gadget review (110 BPM) +- Custom - keeps the prompt you write + +Preset prompts are in English, since they are input for the model. They all end with a phrase that keeps the music unobtrusive and instrumental, for example "unobtrusive underscore for someone talking, instrumental, no vocals, steady tempo". Keep a similar ending in your own prompts. Vocals, singing, choir, rap, distorted guitar, heavy drums and dark or horror moods are always asked to be left out. + +## Options + +| Option | Description | Default | +|--------|-------------|---------| +| Preset | A starting point for the prompt and tempo; editing the prompt afterwards is kept | Cooking / baking | +| Prompt | Description of the music: genre, instruments, mood | (from the preset) | +| Tempo (BPM) | Beats per minute, 40-200 | 120 | +| Generate (seconds) | How much music the model makes, 20-240. A longer video loops this clip; a short video only needs a little more than its own length, so less is generated | 60 | +| Seed | The random seed. The same prompt, tempo, length and seed give the same music | 42 | +| Random | Pick a new seed for each **Generate** (the seed used is shown in the Seed field, so a piece you like can be made again) | On | +| Output length | Shows the final length - the video's length, as the music loops seamlessly | - | +| Music volume (%) | Level of the music, 0-200 | 100 | +| Remove existing audio tracks | The music replaces all the video's audio | On | +| Original audio volume (%) | Shown when **Remove existing audio tracks** is off: level of the video's own audio, which the music is mixed under | 100 | + +All settings are remembered. + +## Looping + +The model makes a clip of **Generate (seconds)**. Subtitle Edit then finds a loop in it that is aligned to the bars of the measured tempo and repeats it to the full length, with a fade-out at the end, and normalizes the level. After generating, the result line shows the loop's length, the measured tempo and how well the seam matches ("Loop: ... at 120 BPM, seam match 94%"), or "Music ready: ..." with the length when no loop is needed. + +## Add to Video + +**Add to video...** asks for the output file (by default the video name with `_music` added, in the same format) and runs ffmpeg: + +- The video stream is copied, not re-encoded, so it is quick and lossless. +- With **Remove existing audio tracks**, the music becomes the only audio track. +- Without it, the video's first audio track is kept at **Original audio volume** and the music is mixed under it. +- The audio is encoded as AAC (192 kb/s), or Opus for a `.webm` output. +- The output file cannot be the input video. + +When done, a file-saved prompt offers to open the containing folder or play the video. + +## With Text to Speech + +In the [Text to Speech](text-to-speech.md) window, check **Add background music** to mix generated music under the dubbed speech. The settings button next to it opens this window in a text to speech mode: + +- **Add to video...**, **Remove existing audio tracks** and **Original audio volume** are hidden, and an **OK** button is shown instead. +- **Music volume** is the level between lines (30% by default, saved separately). The music dips automatically while someone talks and comes back up in the gaps. +- The output length follows the generated speech. +- Music you generate here is reused by the text to speech run when the prompt, tempo and length are unchanged, so the piece you listened to is the one you get. **OK** checks **Add background music**. + +## Requirements + +- **audio.cpp runtime** - shared with the audio.cpp text to speech engines (IndexTTS 2.5, Higgs Audio v3, Fish Audio S2 Pro, ...). If it is missing, or too old to have ACE-Step, you are asked to download it. On Windows and Linux you pick the CPU, Vulkan or CUDA build; a GPU build is much faster. On macOS it needs an Apple Silicon Mac and uses Metal. +- **ACE-Step 1.5 Turbo model** (Q8_0, 5.8 GB) - downloaded after asking, the first time you click **Generate**. +- **ffmpeg** - for adding the music to the video. + +Generating takes a minute or two on a fast machine, including about 20 seconds to load the model; it is slower on the CPU. + +## Keyboard Shortcuts + +| Key | Action | +|-----|--------| +| Escape | Cancel generating / close | +| F1 | Show help (default key, [changeable](shortcuts.md#worth-knowing)) | diff --git a/docs/features/batch-convert.md b/docs/features/batch-convert.md index 18052290bf9..75ace9efce1 100644 --- a/docs/features/batch-convert.md +++ b/docs/features/batch-convert.md @@ -16,6 +16,12 @@ Convert multiple subtitle files between formats and apply various transformation 5. Choose an output folder 6. Click **Convert** +Use **Filter** above the file list to show only some files: **File name contains...**, **Track language contains...**, **Format contains...**, **Extension is...**, **Status is error** or **Forced tracks only**. + +### Presets + +The function choices and their options can be saved as a named preset: pick **Save preset...**, enter a name, and choose it later from the **Preset** drop-down to restore everything. **Delete preset** removes the selected one. + ## Available Functions You can chain multiple conversion functions: @@ -40,6 +46,10 @@ You can chain multiple conversion functions: - Unbreak lines — join a cue's lines into one, optionally only for short ones - Auto-balance lines - Fix right-to-left +- Remove Unicode control characters — remove the invisible direction control characters (LRM, RLM, LRE, RLE, PDF, LRO and RLO) and replace no-break spaces with normal spaces +- Convert actors — convert actor/voice labels from one style to another (e.g. inline `[NAME]` text to the Actor property), like [Convert actors](convert-actors.md) +- Merge continuation lines +- Renumber - Sort by - Change resolution — the [ASSA resolution resampler](assa-resolution-resampler.md) applied to every file - Change style — swap one ASSA style for another, or import styles from a file, optionally trimming unused ones @@ -54,15 +64,19 @@ You can chain multiple conversion functions: Batch Convert can machine-translate files as part of the conversion. Supported engines: - Ollama +- OpenAI Compatible API - Ollama advanced (local LLM) — the batch/context engine described in [Advanced local engines](auto-translate-advanced.md), talking to Ollama's OpenAI-compatible endpoint - LibreTranslate -- LM Studio +- LM Studio (local LLM) +- LM Studio advanced (local LLM) — the batch/context engine described in [Advanced local engines](auto-translate-advanced.md) - llama.cpp — fully managed: Batch Convert reuses an already-running local `llama-server`, or downloads llama.cpp plus a curated translation model (e.g. TranslateGemma) and starts the server for you. Point it at your own server via the remote-server option in [Auto-translate](auto-translate.md) settings. - llama.cpp advanced (local LLM) — the batch/context engine described in [Advanced local engines](auto-translate-advanced.md); it gets the context size configured there - NLLB (nllb-serve and nllb-api) - DeepL (API key required) - CrispASR MADLAD +To translate into several languages in one run, click **Add** next to the **To** language and pick more target languages (**Remove** drops one). Every file is translated into each language and saved as its own output file, with the target language code as the language post fix (e.g. `movie.de.srt`, `movie.fr.srt`). + The same feature is available headlessly in `seconv` via `--translate-to` — see the [command line documentation](../reference/command-line.md). ## OCR in Batch Convert @@ -112,11 +126,24 @@ Speech-to-text batch mode can transcribe multiple media files and save the resul ## Settings -- **Output format** — Choose from 380+ subtitle formats -- **Output folder** — Where converted files are saved -- **Overwrite existing** — Whether to overwrite files -- **Encoding** — Text encoding for output files -- **Keep source file timestamp** — Give the output file the modification time of the source file -- **Language post fix** — Append the detected language code (2- or 3-letter) to the output file name, e.g. `movie.en.srt` +- **Target format** — Choose from 380+ subtitle formats +- **Target format settings** — shown for formats with export options: ASSA (styles, properties, attachments), EBU STL header, PAC code page, Cavena 890 titles/translator/start of programme, DVB Teletext page number/language/hearing impaired, custom text format, and the image formats (Blu-ray sup, BDN XML, VobSub, D-Cinema, DOST, FCP, images) + +The **Settings** button opens **Batch convert settings**: + +- **Output** + - **Target encoding** — Text encoding for output files + - **Use source folder** / **Use output folder** — Where converted files are saved + - **Overwrite existing files** + - **Keep source file date/time on output files** + - **Language post-fix (mkv/mp4/auto-translate)** — Append the detected language code (2- or 3-letter) to the output file name, e.g. `movie.en.srt` +- **Adding files** + - **Include subfolders when adding a folder** + - **Video files (mkv, mp4, ts...) when adding a folder** — **Ask** (default; a prompt offers **Add video files** or **Subtitle files only**, with **Do not ask again**), **Add their subtitle tracks**, or **Skip** +- **Image-based subtitles (OCR)** — OCR engine and its language/database/model, and VobSub color isolation +- **Other** + - **Prevent computer from sleeping while converting** + +Text formats are saved with CR+LF line breaks when **Force CR+LF on save (text subtitle files)** is on in [Settings](settings.md). For headless batch conversion, see [Command Line (seconv)](../reference/command-line.md). diff --git a/docs/features/blank-video.md b/docs/features/blank-video.md index e64e9b1c40d..093f1c1d9eb 100644 --- a/docs/features/blank-video.md +++ b/docs/features/blank-video.md @@ -33,4 +33,4 @@ Generate a blank video file for testing or subtitle preview purposes. | Key | Action | |-----|--------| | Escape | Close / Cancel | -| F1 | Open help | +| F1 | Show help (default key, [changeable](shortcuts.md#worth-knowing)) | diff --git a/docs/features/burn-in.md b/docs/features/burn-in.md index b6bbead2f96..ff445c9ffaf 100644 --- a/docs/features/burn-in.md +++ b/docs/features/burn-in.md @@ -18,6 +18,10 @@ Hardcode (burn-in) subtitles permanently into a video file using FFmpeg. 6. Select an output folder 7. Click **Generate** to start encoding (or use the **Generate** split-button item **Prompt for ffmpeg parameters and generate** to edit the FFmpeg command line first) +### Burn in selected lines only + +To burn in just part of the subtitle, select the lines in the subtitle grid, right-click and choose **Selected lines → Generate video with burned-in subtitles...**. The window opens with only the selected lines, and **Cut** is set from the first selected line's start to the last one's end, so a short clip needs no manual cut times. If no video is open you are asked to open one first. + ## Font Settings - **Font name** — Select the subtitle font @@ -145,4 +149,4 @@ Click **Batch mode** to queue several video/subtitle pairs (**Single mode** swit | Key | Action | |-----|--------| | Escape | Close / Cancel | -| F1 | Open help | +| F1 | Show help (default key, [changeable](shortcuts.md#worth-knowing)) | diff --git a/docs/features/change-frame-rate.md b/docs/features/change-frame-rate.md index a2f7433cf26..fde8e8cf45b 100644 --- a/docs/features/change-frame-rate.md +++ b/docs/features/change-frame-rate.md @@ -23,8 +23,10 @@ Convert subtitle timings from one frame rate to another. - **Video line** — When a video is open, the top of the window shows its file name and detected frame rate (hover for the full path) - **From frame rate** — Preset to the current frame rate (the toolbar value, which follows the open video), as in Subtitle Edit 4: a subtitle taken from a video was made at that video's frame rate - **To frame rate** — Remembers the last used value; if it equals the from rate, the PAL/NTSC-film counterpart (25 ↔ 23.976) is suggested instead -- **From frame rate** / **To frame rate** — Pick from the list, or click the browse button next to either to choose a video file; its frame rate is read and selected +- **From frame rate** / **To frame rate** — Pick from the list, type any other rate (e.g. `47.952`; a comma works as decimal separator too), or click the browse button next to either to choose a video file; its frame rate is read and selected. A typed rate is applied on Enter or when you leave the box; invalid input is reverted - The swap button exchanges the from and to values - **OK** applies the conversion; **Cancel** closes without changes +The **Frame rate** box on the main toolbar takes a typed rate the same way (Enter or leaving the box applies it, Escape reverts). + The window remembers its size and position between sessions. diff --git a/docs/features/command-palette.md b/docs/features/command-palette.md new file mode 100644 index 00000000000..58054f60687 --- /dev/null +++ b/docs/features/command-palette.md @@ -0,0 +1,39 @@ +# Command Palette + +Find and run any main-window command by typing part of its name, without hunting through the menus. + +- **Menu:** Edit → Command palette... +- **Shortcut:** `Ctrl+Shift+K` (`Cmd+Shift+K` on macOS), configurable + + + +The palette lists the same commands as [Options → Shortcuts](shortcuts.md), so commands that have no menu entry and no shortcut can still be reached from the keyboard. Each row shows the command name, its group (File, Edit, Tools, Video, ...) and its current shortcut, if it has one. + +## How to Use + +1. Press `Ctrl+Shift+K` (or open **Edit → Command palette...**). +2. Type part of a command name. The list narrows with every key press, and the best match is selected. +3. Use the arrow keys to pick another match if needed. +4. Press **Enter** (or double-click a row, or click **OK**) to close the palette and run the command. + +## Searching + +- **Every word must match.** Type several words separated by spaces, in any order - `merge short` and `short merge` both find *Merge short lines*. A word can match the command name, its group name, or its shortcut keys, so `ctrl+s` or `tools` also work. +- **Initials.** A single word can be the first letters of the command's words: `fce` finds *Fix common errors*, `bc` finds *Batch convert*. +- **Ranking.** Commands whose name starts with what you typed come first, then initials matches, then names where every word starts a word in the name, then names that contain the text anywhere. +- **Recently used first.** With an empty search box, the commands you ran from the palette most recently are listed at the top (the last 10), followed by the rest in alphabetical order. Among equally good matches, recently used commands also win. The recent list lasts until Subtitle Edit is closed. +- When nothing matches, "No matching commands" is shown. + +Text box commands (the ones that only act on a focused text box, like the shortcuts in the text box category) are not listed, because the palette itself has the focus when you pick a command. The palette does not list itself. + +## Keyboard Shortcuts + +| Key | Action | +|-----|--------| +| Enter | Run the selected command | +| Up / Down | Select the previous / next command | +| Page Up / Page Down | Move the selection 10 commands up / down | +| Escape | Close without running anything | +| F1 | Show help (default key, [changeable](shortcuts.md#worth-knowing)) | + +To change the palette's own shortcut, look for **Command palette** in [Options → Shortcuts](shortcuts.md). diff --git a/docs/features/compare.md b/docs/features/compare.md index 94de646b727..7eaaafead85 100644 --- a/docs/features/compare.md +++ b/docs/features/compare.md @@ -13,8 +13,11 @@ Compare two subtitle files side by side to identify differences in text, timing, 3. Use the file picker on either side (or drag-and-drop) to load a second subtitle file as the **Reference** on the right. 4. Each row holds a matching pair of lines, so the two sides always stay aligned. Differences are highlighted with colors: - **Red:** Lines present in only one side (added or removed). - - **Green:** Cells where the start time, end time, or text differs. - - **Orange:** Cells where the line number differs. + - **Green:** Rows where the start time, end time, or text differs. The changed words are highlighted inside the text. + - **Orange:** Rows where only the line number differs. + - **Violet:** Lines you changed in the compare window. + + When the timing differs, a time delta is shown next to the time codes, e.g. `Δ +120ms → −40ms`: how much later (+) or earlier (−) the reference starts and ends than the current line. 5. Use **Previous difference** / **Next difference** (F8 / Shift+F8) to jump between differing rows, or click the overview strip on the right to jump to any part of the file. 6. Use the tabs (**All**, **Differences**, **Text differences**) and the options to refine the comparison. Each tab shows how many rows it holds. @@ -35,7 +38,7 @@ The two lines are now always shown as a pair, marked with a link icon between th The current subtitle can be edited right in the compare window. The reference is read-only. -- **Take from reference:** The arrow button between the two sides copies the reference line's text and timing to the current line. For a line that exists only in the reference, it inserts the line into the current subtitle. +- **Take from reference:** The arrow button between the two sides copies the reference line's text and timing to the current line. For a line that exists only in the reference, it inserts the line into the current subtitle. Right-click the arrow (or the row; Ctrl+Click on macOS) and choose **Take text** or **Take timing** to copy only one part. - **Delete:** For a line that exists only in the current subtitle, the trash button deletes it. - **Edit a line:** Double-click a row, press F2, or click the pencil to edit the text and timing inline. **Take text** and **Take timing** copy just one part from the reference. - **Undo:** Every change can be undone, one step at a time. @@ -47,8 +50,9 @@ Editing is only available while the left side is the loaded subtitle. If you loa ### Comparison Options - **Ignore formatting:** Compare text only, ignoring formatting tags. -- **Ignore white space:** Ignore differences in whitespace. +- **Ignore whitespace:** Lines that differ only in spaces, tabs or line breaks do not count as different. - **Ignore numbering:** Lines that differ only in their number do not count as different. +- **Time tolerance (ms):** Start and end times that differ by this many milliseconds or less do not count as different. ### Visual Comparison - Side-by-side subtitle display, one aligned pair per row. @@ -57,14 +61,14 @@ Editing is only available while the left side is the loaded subtitle. If you loa - View modes: **All**, **Differences**, **Text differences**. ### Actions -- **Reload right from file:** Reload the right pane from the same file as the left pane (useful for diffing in-memory edits against the saved file). +- **Load saved file:** Load the saved file of the current subtitle as the reference (useful for diffing in-memory edits against the saved file). - **Export:** Export the comparison as an HTML file. ## Keyboard Shortcuts | Shortcut | Action | |----------|--------| -| F1 | Show help | +| F1 | Show help (default key, [changeable](shortcuts.md#worth-knowing)) | | F8 / Shift+F8 | Next / previous difference | | F2 | Edit the selected line | | Ctrl+Enter | Save the line being edited | diff --git a/docs/features/cut-video.md b/docs/features/cut-video.md index b9da59d1db7..3aaf1f46d15 100644 --- a/docs/features/cut-video.md +++ b/docs/features/cut-video.md @@ -59,4 +59,4 @@ The built-in audio visualizer helps you precisely identify cut points by showing |-----|--------| | Escape | Close / Cancel | | Space | Toggle video play/pause | -| F1 | Open help | +| F1 | Show help (default key, [changeable](shortcuts.md#worth-knowing)) | diff --git a/docs/features/edit.md b/docs/features/edit.md index d3b902c7d4a..b310e9cc364 100644 --- a/docs/features/edit.md +++ b/docs/features/edit.md @@ -52,7 +52,7 @@ Find and replace text in the subtitle. - **Menu:** Edit → Replace - **Shortcut:** `Ctrl+H` (`Cmd+Option+F` on macOS, since Cmd+H hides the app) -The buttons have Alt accelerators, underlined while Alt is held: `Alt+F` **Find next**, `Alt+R` **Replace & find next**, `Alt+A` **Replace all**. As in Subtitle Edit 4, the bare letter (`F`, `R`, `A`) also works once the focus has left the text boxes, e.g. after clicking a button. The Find window has `Alt+F` **Find next** and `Alt+P` **Find previous** the same way. The history button next to the search box lists recent searches; `Ctrl+Delete` removes the current search text from the search history. +The buttons have Alt accelerators, underlined while Alt is held: `Alt+F` **Find next**, `Alt+R` **Replace & find next**, `Alt+A` **Replace all**. As in Subtitle Edit 4, the bare letter (`F`, `R`, `A`) also works once the focus has left the text boxes, e.g. after clicking a button. The Find window has `Alt+F` **Find next** and `Alt+P` **Find previous** the same way. The history button next to the search box lists recent searches. Each entry has a **✕** to delete it (in the history menu and in the search box's drop-down; `Delete` on a highlighted entry does the same), and **Clear history** at the bottom of the menu removes them all. `Ctrl+Delete` removes the current search text from the search history. The line under the buttons reports the outcome: **Count** shows how many matches there are, **Replace all** shows how many occurrences were replaced, and **Replace & find next** keeps a running total of the occurrences replaced with the current search text. Typing a new search text starts the count over. @@ -111,6 +111,8 @@ Each rule has one of three match types, shown as an icon in the tree: | Case sensitive | Plain text match, exact case | | Regular expression | Full .NET regex syntax. Use `\n` to match a line break between two lines (`\r\n` and `\r` are accepted too and treated as `\n`). | +The **Whole word** checkbox in the rule dialog makes a plain-text rule match only whole words (it is disabled for regular expression rules, where `\b` does the same). + Unlike Find and Replace, regular expression rules here are matched in multiline mode: `^` and `$` match at the start and the end of *every* line, so `^- ` strips the dash from both lines of a two-line subtitle. Put `(?-m)` in front of the pattern to anchor to the whole subtitle text instead. The replacement text follows the same rules as in Replace above - `$1` and `\n` work, other backslash escapes do not. > **Slow patterns:** the same five-second match timeout as in Find applies to each rule on each line. A rule that runs out of time is retired for the rest of the pass and flagged in the tree with "Regular expression gave up after 5 seconds - the rule is skipped, try a simpler pattern", so a catastrophically backtracking pattern shows up as an error rather than as a rule that silently matches nothing. The timeout is fixed; there is no setting for it. @@ -171,7 +173,7 @@ Rules can also be exported to and imported from **CSV** (choose the `.csv` type The CSV has one row per rule with this header: ```csv -Category,Find,ReplaceWith,Description,Active,Type +Category,Find,ReplaceWith,Description,Active,Type,WholeWord ``` | Column | Description | @@ -182,6 +184,7 @@ Category,Find,ReplaceWith,Description,Active,Type | `Description` | Optional note | | `Active` | `true` or `false` — whether the rule is enabled | | `Type` | `CaseInsensitive`, `CaseSensitive`, or `RegularExpression` | +| `WholeWord` | `true` or `false` — optional; older files without this column import as `false` | Values are quoted per RFC 4180, so `Find`/`ReplaceWith` may contain commas, double quotes (written as `""`) and line breaks. The header row is optional on import; unknown `Type` values fall back to `CaseInsensitive`. Files are written as UTF-8 (with BOM) so non-ASCII rules open correctly in spreadsheet apps. @@ -200,6 +203,15 @@ Jump to a subtitle by its line number. - **Menu:** Edit → Go to line number... - **Shortcut:** `Ctrl+G` (`Control+G` on macOS, where Cmd+G is find next) +## Command Palette + +Search every command by name and run it from the keyboard. + +- **Menu:** Edit → Command palette... +- **Shortcut:** `Ctrl+Shift+K` (`Cmd+Shift+K` on macOS) + +See [Command palette](command-palette.md). + ## Modify Selection Select or deselect subtitle lines based on rules (e.g., text contains, duration, etc.). diff --git a/docs/features/embedded-subtitles.md b/docs/features/embedded-subtitles.md index 945297f753c..f494ee3af7d 100644 --- a/docs/features/embedded-subtitles.md +++ b/docs/features/embedded-subtitles.md @@ -78,7 +78,7 @@ When the loaded video is `.mp4`, `.m4v` or `.mov`, **Video → Add/remove embedd | Insert | Add an external subtitle file | | Delete | Mark the selected track for deletion / undo deletion mark | | Escape | Close or cancel generation | -| F1 | Open help | +| F1 | Show help (default key, [changeable](shortcuts.md#worth-knowing)) | ## Notes diff --git a/docs/features/file.md b/docs/features/file.md index 13ae6276d53..03af24df13c 100644 --- a/docs/features/file.md +++ b/docs/features/file.md @@ -67,12 +67,26 @@ Close the secondary (original) subtitle file in translation mode. ## Close Translation -Shown while an editable original is open: discards the translation and makes the original the working subtitle (you are asked to save unsaved changes first). Not offered when the original is a read-only reference. +Shown while an editable original is open: closes the translation and makes the original the working subtitle. The translation file on disk is not changed. + +- You are always asked first: with unsaved changes you get the usual save prompt, otherwise a confirmation ("Close the translation ...? The original subtitle ... becomes the subtitle you edit."), since the item sits right next to **Close original** in the menu. +- When the original lines up with the translation, the rows keep their time codes and the original text moves into the text column. +- When the original does **not** line up 1:1 (see [Translation Mode](main-window.md#translation-mode)), the original subtitle itself - with its own lines and time codes, and any edits you made to the original text - becomes the working subtitle. The translation's timing is not carried over. +- Not offered when the original is a read-only reference. ## Format Properties Formats with their own settings (ASSA, EBU STL, PAC, ...) get a ** properties...** item here, opening the same dialog as the gear button next to the format combo box. +## Reload When Changed Outside Subtitle Edit + +When the open subtitle file is changed by another program - another editor, a script, a sync tool - Subtitle Edit notices it when you switch back to its main window: + +- **No unsaved changes:** the file is reloaded right away, and the status bar shows "Subtitle reloaded (changed outside Subtitle Edit)". +- **Unsaved changes:** you are asked "The file ... was changed outside Subtitle Edit. Reload it and lose your changes?" Answer **No** to keep your edits; you are not asked again until the file changes once more. + +The reload keeps the video, the selected line, the encoding, the original subtitle in translation mode, the video offset and SMPTE timing. There is no live file watcher: the file's time stamp and size are compared when the window is activated. A converted import (for example a file opened from a video container) is not checked, and a file that is missing or empty (likely still being written) is skipped until the next time. There is no setting for this. + ## Recent Files Quick access to recently opened subtitle files. @@ -101,6 +115,35 @@ Import image files and create subtitle entries from them. Read the subtitles out of an image-based file (Blu-ray `.sup`, VobSub `.sub`, `.ts`, BDN xml) and run [OCR](ocr.md) on them to get editable text. +### Import subtitles from DVD + +Rip the subtitles of a DVD title straight from its IFO and VOB files, then [OCR](ocr.md) them to text. + +- **Menu:** File → Import → Subtitles from DVD (IFO/VOB)... + + + +1. Click the folder button next to **IFO file** and open the title set's IFO file (for example `VIDEO_TS\VTS_01_0.IFO`), or click **Add VOB files** - or drop IFO/VOB files on the window +2. With an IFO, pick the **Title**. Each title shows its duration, number of chapters and subtitle languages; the longest complete title is selected. Opening `VIDEO_TS.IFO` lists the titles of every title set. A title whose VOB files are not all there is marked "VOB files missing (x% found)" +3. Check the **VOB files** list - the title's VOB files are filled in. **Remove**, **Move up**, **Move down** and **Clear** edit the list +4. Check the **Video standard** - *PAL (25 fps)* or *NTSC (29.97 fps)*, taken from the IFO when there is one +5. Click **Start ripping**. **Abort** (or Escape) stops it +6. When the DVD has several subtitle streams, pick the language in the next window; then the [OCR](ocr.md) window opens with the chosen stream + +Ripping a title from its IFO reads only that title's cells, in play order. If you edit the VOB list, all listed VOB files are read instead. The palette and the stream languages come from the IFO (also for loose VOB files when the title set's IFO is next to them), and the language is passed on to OCR. + +Subtitle Edit does not decrypt CSS. If the VOB files were copied from a DVD without decrypting, a warning tells how many subtitle packets are still encrypted (their images would be garbled) and asks whether to continue - decrypt the DVD with a ripping tool first for a clean result. + +**File → Open** on an `.ifo` file opens this window with it, and opening a single `.vob` file with subtitles goes straight to the language pick and OCR. + +### Other sources opened with File → Open + +These are recognised when opened with **File → Open** (or **Import → Image-based subtitle for OCR...**): + +- **PSP UMD Video** - `.mps` and `.pmf` files (and `.subs` dumps of their subtitles): the subtitle images are read, you pick the stream when there are several, and they go to [OCR](ocr.md) +- **HD-DVD `.sup`** - HD-DVD subpicture files are read and sent to [OCR](ocr.md) +- **CEA-608 closed captions in MPEG video** - DVD `.vob` files without subpictures, program streams (`.mpg`, `.mpeg`, `.m2p`) and bare MPEG video (`.m2v`, `.m1v`, `.mpv`) are searched for Line 21 / ATSC A/53 / SCTE 20 closed captions; you pick the caption track when there are several, and the captions are loaded as text + ### Import image-based subtitle for edit Open an image-based subtitle in the [image-based subtitle editor](binary-edit.md) — moving, resizing and re-colouring the bitmaps — without converting them to text. diff --git a/docs/features/list-errors.md b/docs/features/list-errors.md index 30a2f81ec72..8a30d3fcdff 100644 --- a/docs/features/list-errors.md +++ b/docs/features/list-errors.md @@ -61,4 +61,4 @@ Every target exports the rows exactly as shown, so an active card filter is part | Enter / double-click | Go to the selected line | | Home / End | First / last row | | Escape | Close | -| F1 | Open help | +| F1 | Show help (default key, [changeable](shortcuts.md#worth-knowing)) | diff --git a/docs/features/main-window.md b/docs/features/main-window.md index 8cb7ef166ea..5cf51648ebe 100644 --- a/docs/features/main-window.md +++ b/docs/features/main-window.md @@ -19,11 +19,11 @@ The menu bar provides access to all features organized into categories: | Menu | Description | |------|-------------| | **File** | New, New (keep video), New window, Open, Open (keep video), Open original, Edit original subtitle, Close original, Close translation, Reopen, Restore auto-backup, Save, Save as, Save forced lines as, format properties, Open containing folder, Compare, Statistics, Import, Export, Exit | -| **Edit** | Undo, Redo, Show history for undo, Find, Find next, Replace, Multiple replace, Go to line number, Right-to-left mode and the RTL tools (fix RTL via Unicode control chars, remove Unicode control chars, reverse RTL start/end), Modify selection, Invert selection, Select all | -| **Tools** | Adjust durations, AI review, Apply duration limits, Apply min. gap between subtitles, Batch convert, Beautify time codes, Bridge gaps, Change casing, Change formatting, Check and fix Netflix errors, Convert actors, Fix common errors, List errors, Make new empty translation from current subtitle, Merge continuation lines, Merge lines with same text, Merge lines with same time codes, Merge short lines, Merge two subtitles, Remove text for hearing impaired, Remove/replace Unicode characters, Renumber, Snap all times to frames, Sort subtitles, Split/rebalance long lines, and below a separator Join subtitles and Split subtitle | +| **Edit** | Undo, Redo, Show history for undo, Find, Find next, Replace, Multiple replace, Go to line number, [Command palette](command-palette.md), Right-to-left mode and the RTL tools (fix RTL via Unicode control chars, remove Unicode control chars, reverse RTL start/end), Modify selection, Invert selection, Select all | +| **Tools** | Adjust durations, AI review, Apply duration limits, Apply min. gap between subtitles, Batch convert, Beautify time codes, [Improve time codes (forced alignment)](improve-time-codes.md), Bridge gaps, Change casing, Change formatting, Check and fix Netflix errors, Convert actors, Fix common errors, List errors, Make new empty translation from current subtitle, Merge continuation lines, Merge lines with same text, Merge lines with same time codes, Merge short lines, Merge two subtitles, Remove text for hearing impaired, Remove/replace Unicode characters, Renumber, Snap all times to frames, Sort subtitles, Split/rebalance long lines, and below a separator Join subtitles and Split subtitle | | **Plugins** | Run installed plugins; manage installed plugins (only shown when **Options → Settings → Appearance → Show Plugins menu** is on) | | **Spell check** | Spell check, Find double words, Find double lines, Add name to names list, Get dictionaries | -| **Video** | Open video, Open video from URL, Open recent video, Close video file, Open/remove second subtitle file, Audio tracks, Go to video position, Speech to text, Text to speech, OCR burned-in subtitle, Generate video with burned-in subtitles, Generate transparent video with subtitles, Generate blank video, Embed subtitles, Generate/import shot changes, List shot changes, Undock/Dock video controls, Toggle select subtitle while playing, and a **More** submenu (Chapters, Cut video, Find voices in video and clone, Re-encode video, Remux video, Set video offset, SMPTE timing, Toggle waveform toolbar) shown while a video is loaded | +| **Video** | Open video, Open video from URL, Open recent video, Close video file, Open second subtitle file, Edit second subtitle settings, Remove second subtitle file, Audio tracks, Go to video position, Speech to text, Text to speech, Speak from current line, Play with speech, TTS voice manager, OCR burned-in subtitle, Generate video with burned-in subtitles, Generate transparent video with subtitles, Generate blank video, Embed subtitles, Generate/import shot changes, List shot changes, Undock/Dock video controls, Toggle select subtitle while playing, and a **More** submenu (Chapters, Cut video, Find voices in video and clone, [Generate background music](background-music.md), Re-encode video, Remux video, Set video offset, SMPTE timing, Toggle waveform toolbar) shown while a video is loaded | | **Synchronization** | Adjust all times, Visual sync, Point sync, Point sync via other subtitle, Change frame rate, Change speed | | **Translate** | Auto-translate, Auto-translate via copy-paste | | **Options** | Settings, Shortcuts, Word lists, Choose UI language | @@ -81,13 +81,15 @@ The subtitle grid shows all subtitle lines in a table format. Each row represent | Column | Description | |--------|-------------| | **#** | Line number | -| **Start time** | When the subtitle appears (e.g., `00:01:23,456`) | -| **End time** | When the subtitle disappears | +| **Show** | When the subtitle appears (e.g., `00:01:23,456`) | +| **Hide** | When the subtitle disappears | | **Duration** | Display duration | -| **CPS** | Characters per second (reading speed) | -| **WPM** | Words per minute | +| **Chars/sec** | Characters per second (reading speed) | +| **Words/min** | Words per minute | | **Text** | The subtitle text content | -| **Original** | Original text (visible in translation mode) | +| **Original text** | Original text (visible in translation mode) | + +More columns (**Gap**, **Style**, **Actor**, **Layer**, **Pixel width**, **Forced**, ...) can be turned on or off by right-clicking the column header, e.g. **Show "Gap" column**, or with **Columns...**. **Mouse:** - **Click** a row to select it and load its text into the text editor @@ -354,12 +356,12 @@ Panel sizes can be adjusted by dragging the splitters (dividers) between areas. You can undock the video player or audio visualizer into separate windows for multi-monitor setups: - **Undock video controls** — Move video player to its own window -- **Redock video controls** — Return video player to the main window +- **Dock video controls** — Return video player to the main window ## Translation Mode When you open an original subtitle file (**File → Open original subtitle**), the main window enters translation mode: -- The subtitle grid shows an additional **Original** column +- The subtitle grid shows an additional **Original text** column - The text editor shows both the original and translation text - This allows side-by-side translation work @@ -399,7 +401,7 @@ Subtitle Edit accepts a few command-line arguments at startup, useful for deskto | `/video:` | Open the given video file alongside the subtitle. `--video:` and `--video ` are accepted too. | | `/batchconvertui` | Launch directly into the standalone **Batch Convert** window instead of the full editor. `--batchconvertui` is accepted too. | -The video file is always loaded when supplied via `/video:` (or `--video`), even if **Options → Settings → Video → Auto-open** is disabled — a flag on the command line is treated as an explicit instruction. +The video file is always loaded when supplied via `/video:` (or `--video`), even if **Options → Settings → Video player → Auto-open video file when opening subtitle** is disabled — a flag on the command line is treated as an explicit instruction. Examples (Windows shell): diff --git a/docs/features/merge-continuation-lines.md b/docs/features/merge-continuation-lines.md new file mode 100644 index 00000000000..a2de9566188 --- /dev/null +++ b/docs/features/merge-continuation-lines.md @@ -0,0 +1,56 @@ +# Merge Continuation Lines + +Merge subtitles where one sentence has been split across two or more consecutive lines, with a preview of every merge before anything is changed. + +- **Menu:** Tools → Merge continuation lines... +- **Shortcut:** Configurable (no default) + + + +Unlike [Merge Short Lines](merge-short-lines.md), which merges by length, this tool looks at punctuation: a line that does not end a sentence is joined with the line after it. This is useful for subtitles made by speech recognition or for scripts that will be read by [Text to Speech](text-to-speech.md), where a sentence spoken as one unit sounds more natural. + +## How to Use + +1. Open **Tools → Merge continuation lines...** +2. Adjust **Max gap between lines (ms)** and **Max characters per paragraph** - the list of candidates updates as you change them. +3. Uncheck any merge in the **Merge** column you do not want. **Select all** and **Invert selection** change all the check boxes at once. +4. Click **OK** to apply the checked merges. + +The status line shows how many candidates were found ("Continuation candidates found: 12") or "No continuation candidates found". The merge can be undone with **Edit → Undo**. + +## What Counts as a Continuation + +Two consecutive lines are offered for merging when all of these hold: + +- The first line does **not end a sentence** - it does not end with `.`, `!`, `?` or a sentence-ending mark of the subtitle's language (detected automatically), also when a closing quote follows the mark. A line ending with an ellipsis (`...` or `…`) counts as unfinished and *is* merged. Formatting tags are ignored when checking. +- The **gap** from the end of the first line to the start of the next is less than **Max gap between lines (ms)**. +- The two texts together (without formatting tags) are shorter than **Max characters per paragraph**. +- Neither line is a **dialog line** starting with a dash (`-`, `‐`, `–`, `—`) - those are two speakers, not one sentence. +- Neither side is a **music line** - the first line ending with `♪`/`♫`, or the next one starting with it. + +An empty first line also qualifies. When several lines in a row qualify, they are all merged into one subtitle. + +## The Merge + +The merged subtitle starts at the first line's start time and ends at the last line's end time. The texts are joined with a space and re-broken with auto-break for the subtitle's language. The **Merged** column shows exactly the text that will be written. + +## Options + +| Option | Description | Default | +|--------|-------------|---------| +| Max gap between lines (ms) | Lines further apart than this are never merged (0-10000) | 500 | +| Max characters per paragraph | Upper limit for the merged text (20-1000) | Max line length × max number of lines from Options → Settings | + +The values are remembered for the next time the tool is opened from the Tools menu. + +## Other Places + +- **[Text to Speech](text-to-speech.md#merge-continuation-lines)** - before generating, Subtitle Edit offers to merge continuation lines (using a 500 ms gap and 500 characters), so each sentence is spoken in one go. Turn the prompt off with **Text to speech: prompt to merge continuation lines** in **Options → Settings → Tools**. +- **[Batch Convert](batch-convert.md)** - *Merge continuation lines* is one of the batch functions, with its own max gap and max characters. + +## Keyboard Shortcuts + +| Key | Action | +|-----|--------| +| Escape | Close without changes | +| F1 | Show help (default key, [changeable](shortcuts.md#worth-knowing)) | diff --git a/docs/features/ocr.md b/docs/features/ocr.md index c783659a8cb..228bd883d7a 100644 --- a/docs/features/ocr.md +++ b/docs/features/ocr.md @@ -167,7 +167,7 @@ number, time codes, duration and image size. | F3 / Shift+F3 | Find next / Find previous | | Ctrl++ | Zoom in (images in grid) | | Ctrl+- | Zoom out (images in grid) | -| F1 | Show help | +| F1 | Show help (default key, [changeable](shortcuts.md#worth-knowing)) | ### Subtitle Grid @@ -190,12 +190,14 @@ number, time codes, duration and image size. ## Pre-processing -Before OCR, you can apply image pre-processing: -- Crop transparent colors -- Remove borders (with configurable border size) -- Invert colors -- To one color (keep pixels brighter than a darkness threshold) -- Binarize (convert to black/white using Otsu thresholding) +Before OCR, you can apply image pre-processing via the **Image pre-processing** toggle button in the OCR window (it shows as pressed while any pre-processing is active): +- **Crop transparent colors** +- **Remove borders** (with configurable **Border size**) +- **Inverse colors** +- **One color (white)** (keep pixels brighter than a **Darkness threshold**) +- **Binarize** (convert to black/white using Otsu thresholding) + +The settings are remembered between sessions and applied automatically the next time you open the OCR window. **Reset** in the **Pre-processing** window sets all options back to their defaults (all off, border size 2, darkness threshold 128). ## Batch OCR diff --git a/docs/features/point-sync-via-other.md b/docs/features/point-sync-via-other.md index 7d1df34114f..9d16498d59a 100644 --- a/docs/features/point-sync-via-other.md +++ b/docs/features/point-sync-via-other.md @@ -10,7 +10,7 @@ Synchronize a subtitle file using another subtitle file as reference. ## How to Use 1. Open the subtitle you want to sync -2. Open **Sync → Point sync via other subtitle...** +2. Open **Synchronization → Point sync via other subtitle...** 3. Click the browse button in the right pane to load the reference subtitle - a subtitle file, a Blu-ray `.sup`, or a subtitle track in an `.mkv` or `.mp4` (pick the track when there are several) 4. Select a line in your subtitle and the matching line in the other subtitle, then click **Set sync point** — or **Set sync point via video...** to pick the time from the video for a line the other subtitle does not cover 5. Repeat for more sync points; they are listed in the middle and can be removed with right-click **Delete** or the Delete/Backspace key @@ -42,5 +42,5 @@ The window remembers its size and position between sessions. | Ctrl+Plus / Ctrl+Minus | Make the images of an image based reference bigger/smaller | | Ctrl+G | Go to line number, in the grid used last (the left one at first) | | Delete / Backspace | Remove the selected sync point (sync point list focused) | -| F1 | Help | +| F1 | Show help (default key, [changeable](shortcuts.md#worth-knowing)) | | Escape | Cancel | diff --git a/docs/features/point-sync.md b/docs/features/point-sync.md index 52c057927f4..70c9bba49ff 100644 --- a/docs/features/point-sync.md +++ b/docs/features/point-sync.md @@ -9,7 +9,7 @@ Synchronize subtitles using multiple reference points. ## How to Use -1. Open **Sync → Point sync...** +1. Open **Synchronization → Point sync...** 2. Select a subtitle line in the list and click **Set sync point** to set its correct video position 3. Repeat for additional sync points as needed for accuracy; a sync point can be removed with right-click **Delete** on the list or the Delete/Backspace key 4. Click **OK** to apply diff --git a/docs/features/re-encode-video.md b/docs/features/re-encode-video.md index e4dc43680dd..b286224037b 100644 --- a/docs/features/re-encode-video.md +++ b/docs/features/re-encode-video.md @@ -26,4 +26,4 @@ Re-encode a video file with a different frame rate or container format using FFm | Key | Action | |-----|--------| | Escape | Close / Cancel | -| F1 | Open help | +| F1 | Show help (default key, [changeable](shortcuts.md#worth-knowing)) | diff --git a/docs/features/remove-unicode-characters.md b/docs/features/remove-unicode-characters.md index d5b047f0dd3..db0b0b92f4a 100644 --- a/docs/features/remove-unicode-characters.md +++ b/docs/features/remove-unicode-characters.md @@ -48,4 +48,4 @@ Every non-empty **Replace with** is saved when you click OK and filled in again |-----|--------| | Space | Toggle Apply for the selected rows | | Escape | Close without changes | -| F1 | Open help | +| F1 | Show help (default key, [changeable](shortcuts.md#worth-knowing)) | diff --git a/docs/features/remux-video.md b/docs/features/remux-video.md index f0185fc777a..71d744e132f 100644 --- a/docs/features/remux-video.md +++ b/docs/features/remux-video.md @@ -76,4 +76,4 @@ The window remembers its size and position. | Ctrl+Up / Ctrl+Down | Move the selected audio or subtitle file up / down | | Delete | Remove the selected file from its list | | Escape | Close (not while remuxing) | -| F1 | Open help | +| F1 | Show help (default key, [changeable](shortcuts.md#worth-knowing)) | diff --git a/docs/features/renumber.md b/docs/features/renumber.md index 052586209c2..fe3c6d61f0b 100644 --- a/docs/features/renumber.md +++ b/docs/features/renumber.md @@ -28,4 +28,4 @@ Subtitle Edit updates the visible subtitle numbers in order. Timing, text, forma |-----|--------| | Enter | Apply | | Escape | Close / Cancel | -| F1 | Open help | +| F1 | Show help (default key, [changeable](shortcuts.md#worth-knowing)) | diff --git a/docs/features/settings.md b/docs/features/settings.md index d1440c9c519..286472ea84f 100644 --- a/docs/features/settings.md +++ b/docs/features/settings.md @@ -22,7 +22,7 @@ The window is split into sections; the list below follows the order they appear The subtitle rules that drive error checking, the grid's warning colors, and tools such as [Fix common errors](fix-common-errors.md). -- **Profiles** — Rules are stored per profile, so you can switch between e.g. Netflix, broadcast and default. Profiles can be exported and imported +- **Profiles** — Rules are stored per profile, so you can switch between e.g. Netflix, Amazon Prime, Arte, broadcast and default. Built-in profiles include **TikTok/YouTube-shorts (9:16)** (24 chars, 2 lines) and **TikTok/YouTube-shorts (1-3 words)** (one short line per cue). Profiles can be exported and imported - **Single line max length**, **Optimal chars/sec**, **Max chars/sec**, **Max words/min** - **Min duration (ms)**, **Max duration (ms)** - **Min gap (ms)** — The "..." button opens a calculator: pick a frame rate and a number of frames and it works out the milliseconds. It opens on the current video's frame rate and the frame count already configured. Shown in millisecond mode only; in frame mode the value is entered in frames @@ -34,20 +34,24 @@ The subtitle rules that drive error checking, the grid's warning colors, and too - **Default new subtitle duration (ms)** - The duration a newly inserted subtitle gets, e.g. when inserting at the video position - **Time up/down increment (ms)** - The step of the start/end/duration up-down boxes in millisecond mode +- **Move selected lines shortcut step (ms)** and **Move start/end shortcut step (ms)** - How far the move shortcuts shift lines or their start/end +- **Move lines: shorten previous/next line instead of overlapping it** - **Prompt before delete**, **Lock time codes**, **Remember window position and size** - **Show full file path in title bar** — Show the subtitle file's full path in the main window title instead of only its name. Off by default +- **Show recent files (for reopen)** — Turn off to stop recording recent files; this also hides the Reopen menus and clears the lists - **Time code mode** — How times are shown and typed: *Time (hh:mm:ss,zzz)* with milliseconds, *Frames (hh:mm:ss:ff)*, or *Frame numbers* - the absolute frame number at the current frame rate (e.g. 15230), also for durations and the video position, so a sync offset is just "video frame - start frame". Both frame modes snap edited times to frames. The mode is also shown in the status bar - click it (or use the "Toggle time code mode" shortcut) to cycle through the three. - **Limit number of lines in subtitle text box** - **Open last recent file on start** -- **Auto-convert encoding to UTF-8 on open**, **Force CR+LF on save**, **Auto-trim white-space** +- **Auto-convert encoding to UTF-8 on open**, **Force CR+LF on save (text subtitle files)** (also used by [Batch convert](batch-convert.md)), **Auto-trim white-space** - **Warn on save when lines exceed the format's limits (e.g. SCC 32 chars/line)** - **Remove blank lines when opening a subtitle** — Off by default +- **Copy text via xclip/wl-copy (keeps accented characters)** — Linux only - **Default encoding** - **Subtitle grid Enter-key / single-click / double-click action** — What each gesture does to the video position and focus - **Subtitle grid, center when selecting prev/next row** - **Save as behavior**, **Save as: append language code**, **Default save location** (with a custom folder) - **Auto-save** — Save the open file while editing -- **Auto-backup** — Automatic backups at a set interval, with a restore dialog, and **Auto-backup retention (days)** for how long they are kept +- **Auto-backup** — Automatic backups every **Auto-backup interval (minutes)**, with a restore dialog, and **Auto-backup retention (days)** for how long they are kept - **Auto-backup settings** — Keeps dated copies of `Settings.json` (settings and shortcuts). **Settings backup interval (days)** sets how often: the check runs when Subtitle Edit starts, and `0` means every start. A backup is skipped when nothing but the recent files lists has changed since the newest backup. **Settings backups to keep** sets how many copies are kept (default 30). Restore them from File → Restore auto-backup → Settings ## Subtitle Formats @@ -68,8 +72,10 @@ The subtitle rules that drive error checking, the grid's warning colors, and too ## Video Player -- **Video player** — Which player to use +- **Video player** — Which player to use: **libmpv - OpenGL**, **libmpv - Native Window ID rendering** (not macOS), **libmpv - Software rendering (slow)**, **libVLC - Native Window ID rendering** (Windows and Linux), **FFmpeg**, and **libmpv - Metal (experimental, needs restart)** (macOS only) - **Video controls** (order and visibility of the controls under the video, see [Video Player](video-player.md#video-controls)), **Hide video controls in full-screen**, **Auto-open video file when opening subtitle** +- **Show style dialog when opening a second subtitle file**, **Remember second subtitle file** +- **Deinterlace video** — **Off**, **Auto (interlaced frames only)** or **Always** (mpv) - **Subtitle preview properties** — Font name, size and bold, primary/outline/shadow colors, border style and outline/shadow width for the subtitle drawn on the video - **Download mpv** / **Download VLC** / **Download FFmpeg libraries** — Grouped at the bottom; fetch the library a player needs when it is not installed @@ -79,6 +85,7 @@ The subtitle rules that drive error checking, the grid's warning colors, and too - **Show toolbar** and **Toolbar items** — Which timing buttons the waveform toolbar shows, and in which order - **Waveform single-click / double-click action** - **Extract audio format, sample rate and bitrate** — What the audio Subtitle Edit extracts for the waveform looks like +- **Use only front center channel for 5.1 audio (FFmpeg)** — Extract only the dialog channel of 5.1 audio for a cleaner waveform - **Select subtitle on right click**, **Allow overlap (when moving/resizing)**, **Set video position when moving start/end** - **Snap to shot changes (hold Shift to override)** and **Snap to frames** - **Snap distance when dragging (pixels)** — how close a dragged cue has to come to a shot change before it snaps; in pixels, so it feels the same at every zoom @@ -87,7 +94,10 @@ The subtitle rules that drive error checking, the grid's warning colors, and too - **Shot changes auto-generate** - **Focus on mouse over**, **Focus text box after insert** - **Invert mouse-wheel**, **Mouse-wheel sets video position**, **Mouse-wheel video position step** -- **Center video position also while paused**, **Draw grid lines** +- **Center video position also while paused**, **Select current subtitle also while paused**, **Draw grid lines** +- **Remember zoom (horizontal and vertical)** — Off by default +- **Use experimental fast renderer** +- **Show # + Duration** and **Show Chars/sec** — The number/duration footer and chars/sec line drawn on each cue in the waveform - **Waveform text font size**, **Waveform text font bold** and the full color set — text, waveform, subtitle background, background, selected subtitle background, selected, cursor/head, shot change, left/right border, fancy high color. Color themes can be imported and exported - **Auto-transcribe new waveform selection via speech-to-text** - **Download ffmpeg** and a **disk space** readout for the extracted audio @@ -101,22 +111,23 @@ The subtitle rules that drive error checking, the grid's warning colors, and too - **Merge lines: keep end time (allow overlap with next subtitle)**, and the variant that limits it to ASSA files - **Merge lines: auto-break merged text** - On by default; turn it off to keep the merged lines' own line breaks when merging from menus, the waveform or *Merge selected lines* (the explicit "unbreak" and "keep breaks" shortcuts are not affected) - **Auto-break** — Break early for end of sentence, comma or dash; break by pixel width; prefer bottom heavy and its **bottom heavy percentage**; **use do-not-break-after list** (with an editor for the list); **split odd lines action** -- **Spell check engine**, and *treat words ending in 'in'' as 'ing'* (English only) +- **Spell check engine**, and *treat words ending in 'in'' as 'ing'* (English only), **Spell check: remember "Use always" list** - **OCR: use word split list**, **OCR: try to guess unknown words** - **Speech to text: prompt for language/engine first time only** - **Multiple replace: show context menu buttons** - **Grid: focus text box after insert new subtitle** +- **Undo/redo: go to changed line (and video position)** - **Text to speech: prompt to merge continuation lines** - **Text to speech: prompt to skip sound/music lines**, **Text to speech: prompt to detect speaker names in the text** -- **Fix common errors: skip step 1 (choose fixes)** +- **Fix common errors: skip step 1 (choose fixes)**, **Fix common errors: fix short display times may move start time** - **Music symbol** and **music symbols to replace** ## Appearance -- **Theme**, **icon theme**, **match icon color to dark theme foreground color**, **UI scale (%)** +- **Theme**, **icon theme**, **match icon color to dark theme foreground color**, **UI scale (%)**, **Font scale (%)** - **Text selection opacity (%)** — How strong the selection highlight in text boxes is. 100 (default) is the theme's solid highlight with white text; lower values make it see-through and keep the text in its own color - **Dark theme foreground / background color**, **focused button background color** -- **UI font**, and a separate font for the subtitle text box and grid +- **UI font**, and a separate font for the subtitle text box and grid, with **Font size in subtitle grid** and **Font size in subtitle text box** - **Grid** — Show subtitle text as single line (with the separator to use), text fit, [show formatted text](subtitle-grid.md#formatting-display), live spell check, **Center text in subtitle grid** (centers the text column, as Subtitle Edit 4 could), compact mode, alternating row colors (light and dark), grid lines, bookmark color - **Spell check highlight color** - The color of the unknown word highlighted in the spell check and OCR unknown-word windows (red by default; pick a lighter color for the dark theme) - **Subtitle text box** — Bold text, color tags, live spell check, centered text, and which buttons are shown (auto-break, unbreak, italic, color, remove formatting, AI assistant), the up/down start/end/duration controls and their labels @@ -133,8 +144,8 @@ Proxy settings for every download and online engine: **address**, **username**, ## Updates -- **Check for updates on startup** — Hidden for store-managed installs (e.g. Flatpak), which update through the store -- **Update channel** — Stable, or stable and beta +- **Check for updates when the app starts** — Hidden for store-managed installs (e.g. Flatpak), which update through the store +- **Notify about** — **Stable versions only**, or **Stable and beta versions** ## File Type Associations @@ -144,9 +155,13 @@ Windows only. Tick the subtitle file types Subtitle Edit should open by default. Links to the **error log**, the **tools log** and the **settings file** (each enabled only when the file exists), plus **write tools log** to turn the tools log on. +## Reset, Import and Export + +The **Reset** button at the bottom opens **Reset settings**, where you tick what to reset: **Reset all settings**, or only **Reset rules**, **Reset shortcuts**, **Reset recent files**, **Reset auto-translate**, **Reset appearance**, **Reset waveform**, **Reset syntax coloring** or **Reset window position and size**. The arrow next to **Reset** has **Import...** and **Export...** to move settings between computers (all settings, or chosen parts such as rules, appearance, waveform, syntax coloring, shortcuts and auto-translate). **Apply** saves the changes without closing the window. + ## Keyboard Shortcuts | Key | Action | |-----|--------| | Escape | Close settings | -| F1 | Open help | +| F1 | Show help (default key, [changeable](shortcuts.md#worth-knowing)) | diff --git a/docs/features/shortcuts.md b/docs/features/shortcuts.md index 11bcd256c46..99ae373a703 100644 --- a/docs/features/shortcuts.md +++ b/docs/features/shortcuts.md @@ -37,7 +37,7 @@ Independent of the thematic category, each command has a scope shown in the **Ac ## Assigning a Shortcut 1. Select a command from the list -2. Check the desired modifier keys: **Ctrl**, **Alt**, **Shift**, **Win** +2. Check the desired modifier keys: **Ctrl**, **Alt**, **Shift**, **Win** — and on Windows also **AltGr** (the right Alt key). A shortcut with AltGr only fires when the right Alt key is held, so Ctrl+Alt shortcuts no longer swallow the characters AltGr types (€, @, …) on many keyboard layouts. Ctrl/Alt and AltGr cannot be combined 3. Select the key from the dropdown 4. The shortcut is applied immediately in the list 5. Alternatively, **double-click a command in the list** to open the key capture dialog, then press the desired key combination directly @@ -49,6 +49,7 @@ Some commands have additional configuration beyond the shortcut key: - **Set color 1–8** — Choose a color for each color shortcut - **Surround with 1–8** — Define the left/right text to surround selected text with (this replaces the *Shortcut toggle custom start/end* setting from Subtitle Edit 4), and its **Behavior**: *Toggle* adds the text, or removes it when it is already there; *Add* adds it every time, so pressing the shortcut twice adds it twice; *Remove (all)* only removes it, all at once; *Remove (one each time)* removes one pair per press, so it undoes *Add* step by step. **Works on** picks *Selection, else whole text* (the selected part of the text box, otherwise the whole text of each selected subtitle) or *Each line* (every line of a subtitle gets its own pair, e.g. `[Hello]` / `[Bye]`) - **Video move custom 1–4 back/forward** — Set the number of milliseconds to skip. Each one also has a **…and pause** variant that uses the same milliseconds but pauses playback first, so a jog/shuttle step leaves the video paused on the new position +- **Move selected lines / Move selected lines and forward / Move all lines, custom 1–2 back/forward** — Set the number of milliseconds the lines are moved; the command name shows the current value - **Set actor 1–10** — Define the actor name assigned by each actor shortcut - **Custom search 1–5** — Set the name and URL for each search slot - **Custom shortcut 1–8** — Build your own shortcut from steps (see below) @@ -68,6 +69,16 @@ Examples: *Insert text* `\N` at end of text to lift a subtitle one line per key **Active in** sets where the key works: *Everywhere*, *Subtitle list view*, *Text box*, *Subtitle list view & text box* or *Waveform* — so the same key can do something else elsewhere. For text box areas use a key with Ctrl/Alt, as a plain key would no longer type. Handy commands for steps: **Focus subtitle list view**, **Focus text box**, **Focus original text box**, **Focus waveform**, **Text box, go to start** and **Text box, go to end** — each step waits for focus to move before the next one runs. Assign a key like for any other command. The text changes of one run are undone in a single step. A custom shortcut cannot run another custom shortcut; a slot without steps does nothing. +## Worth Knowing + +- **Help** — F1 by default. It can be changed here like any other shortcut, and the same key works in every dialog, where it opens the help page for that dialog +- **Command palette** — Ctrl+Shift+K (Cmd+Shift+K on macOS) by default; search for any command by name and run it, see [Command palette](command-palette.md) +- **Toggle break/unbreak** — Unbreaks the text if it has a line break, otherwise forces a break (no default key) +- **Save original** — Saves only the original subtitle in translation mode (no default key; Ctrl+S saves both) +- **Toggle screen privacy (hide file names, then also texts)** — Each press cycles: hide file names → also blur the subtitle texts → off. Handy for screen sharing and recordings (no default key) + +See [Keyboard Shortcuts](../reference/keyboard-shortcuts.md) for the full list of default keys. + ## Resetting Shortcuts - **Reset** (next to the assignment controls) — Clear the shortcut for the selected command @@ -106,4 +117,4 @@ This is useful for sharing shortcut configurations between machines or team memb | Key | Action | |-----|--------| | Escape | Close shortcuts window | -| F1 | Open help | \ No newline at end of file +| F1 | Show help (default key, [changeable](shortcuts.md#worth-knowing)) | \ No newline at end of file diff --git a/docs/features/shot-changes.md b/docs/features/shot-changes.md index 9637760265c..91c5a3b8c2a 100644 --- a/docs/features/shot-changes.md +++ b/docs/features/shot-changes.md @@ -5,7 +5,7 @@ Detect scene/shot changes in a video using FFmpeg, or import them from a file. - **Menu:** Video → Generate/import shot changes - **Shortcut:** Configurable -A second menu item, **Show shot changes list**, appears under the Video menu once the loaded video has any shot changes. It lists the current shot changes with **Go to** (also double-click), **Clear**, and a right-click **Delete** for the selected entry. +A second menu item, **Show shot changes list**, appears under the Video menu once the loaded video has any shot changes - see [Shot changes list](#shot-changes-list). ![Shot Changes](../screenshots/shot-changes.png) @@ -35,9 +35,22 @@ The window has two tabs: **Generate shot changes** and **Import shot changes**. Shot changes are displayed as vertical lines on the audio visualizer waveform. +## Shot Changes List + +**Video → Show shot changes list** opens the **Shot changes list** window with every shot change of the loaded video (number and time): + +- **Go to** (or double-click) — Move the video position to the selected shot change +- **Clear** — Remove all shot changes +- Right-click → **Copy** (or Ctrl+C) copies the selected times; **Delete** removes the selected shot change +- **Home** / **End** jump to the first / last entry + +## Export Shot Changes + +**Export...** in the shot changes list saves the shot changes to a `.shotchanges` or `.txt` file (one time in seconds per line, the same format Subtitle Edit stores and imports). The file name defaults to the video's name. + ## Keyboard Shortcuts | Key | Action | |-----|--------| | Escape | Close / Cancel | -| F1 | Open help | +| F1 | Show help (default key, [changeable](shortcuts.md#worth-knowing)) | diff --git a/docs/features/speech-to-text.md b/docs/features/speech-to-text.md index 16213202d1c..e5b3deeb0c1 100644 --- a/docs/features/speech-to-text.md +++ b/docs/features/speech-to-text.md @@ -22,7 +22,7 @@ Subtitle Edit can automatically transcribe audio to text using Whisper-based and | Alibaba Qwen3-ASR | All | Online Qwen3-ASR via Alibaba Model Studio (DashScope) | | Google Cloud Speech-to-Text | All | Online Google Cloud Speech-to-Text v2 (Chirp models) with word-level timings. Needs a service account key file (JSON) - the v2 API does not accept API keys | | Qwen3 ASR CPP | Windows, Linux, macOS | Local Qwen3 ASR engine with downloadable GGUF models | -| Crisp ASR | Windows, Linux, macOS | Single engine with selectable backends: Parakeet, Canary, Cohere, Fire Red, Fun-ASR Nano, GigaAM, GLM, Granite, Qwen3, Mega, MOSS Diarize, Omni, Kyutai, SenseVoice, ARK, Voxtral | +| Crisp ASR | Windows, Linux, macOS | Single engine with selectable backends: Parakeet, Canary, Cohere, Fire Red, Fun-ASR Nano, GigaAM, GLM, Granite, Index-Echo, Qwen3, Mega, MOSS Diarize, Omni, Kyutai, SenseVoice, ARK, Voxtral | Engines and models are downloaded automatically on first use. @@ -30,8 +30,10 @@ Engines and models are downloaded automatically on first use. - **Whisper CPP** is shown as a single entry; the CPU / cuBLAS / Vulkan backends are selected from a secondary dropdown when Whisper CPP is selected. - **Qwen3 ASR CPP** includes 0.6B and 1.7B model options, plus a forced-aligner model used for timing workflows. -- **Crisp ASR** is exposed as one engine that wraps multiple backends (Parakeet, Canary, Cohere, Fire Red, Fun-ASR Nano, GigaAM, GLM, Granite, Qwen3, Mega, MOSS Diarize, Omni, Kyutai, SenseVoice, ARK, Voxtral). Pick the backend from the Crisp ASR backend dropdown - see [Crisp ASR backends](#crisp-asr-backends) for what each one is good at. +- **Crisp ASR** is exposed as one engine that wraps multiple backends (Parakeet, Canary, Cohere, Fire Red, Fun-ASR Nano, GigaAM, GLM, Granite, Index-Echo, Qwen3, Mega, MOSS Diarize, Omni, Kyutai, SenseVoice, ARK, Voxtral). Pick the backend from the Crisp ASR backend dropdown - see [Crisp ASR backends](#crisp-asr-backends) for what each one is good at. - A **Forced aligner** option is shown for Crisp ASR backends and exposes the built-in aligner, Canary CTC, Qwen3, and the wav2vec2 zoo (12 language-specific CTC aligners that run on top of any Crisp ASR backend). +- **Detect speakers** (Crisp ASR backends, except MOSS Diarize which labels speakers itself) starts each line with the speaker, like `(speaker 0)`, using NVIDIA's Sortformer diarization (up to 8 speakers). The Nemotron-3-Diarization (Sortformer) model (107 MB) is downloaded on first use, and Crisp ASR v0.8.37 or newer is needed. Works best with backends that split lines where the speaker changes, like Parakeet and Cohere. +- **Purfview Faster Whisper XXL** has a **Multilingual (detect per segment)** language entry below **Auto detect**: the language is detected for every segment instead of once for the whole file, which helps with audio that switches between spoken languages. - Automatic language detection: the Whisper engines (Whisper CPP, Purfview Faster Whisper XXL, CTranslate2, WhisperX, Const-me and OpenAI) and every Crisp ASR backend have an **Auto detect** entry at the top of the language list - except the Russian-only GigaAM and MOSS Diarize, which keep their fixed languages. - **Google Cloud Speech-to-Text** is Google's v2 API, which does not take API keys. In the Google Cloud console, create a project with billing, enable the Speech-to-Text API, create a service account with the *Cloud Speech Client* and *Storage Admin* roles, add a JSON key to it and pick that file as the **Key file**. If your organisation does not allow service account keys, leave the key file empty, run `gcloud auth application-default login` and fill in the **Project ID**. The other fields are **Region** (`us` or `eu` for chirp_3, or a specific region for other models), **Model** (`chirp_3`, `chirp_2`, `long`, `latest_long`...), a **Language** hint (leave it empty for automatic detection with the Chirp models), the Cloud Storage **bucket name** long audio is uploaded to (one named `-subtitle-edit-stt` is created on first use, and uploads are deleted after each run), **Dynamic batching** (on by default - about a fifth of the price, but Google gives no latency guarantee) and a timeout. - Each engine can have separate advanced command-line parameters. @@ -57,6 +59,7 @@ The languages column counts what the backend dropdown offers (an *auto* entry is | **GLM** | 17 | Needs aligner | 1.3 - 4.5 GB | GLM-ASR Nano, Chinese-first with a wide second tier of languages | | **SenseVoice** | 5 (zh, yue, en, ja, ko) | Needs aligner | 136 - 469 MB | Tiny and quick for CJK audio - useful on machines where the larger backends are too slow | | **Fun-ASR Nano** | 5 (en, zh, yue, ja, ko) | Needs aligner | 0.90 - 1.98 GB | CJK-focused Fun-ASR | +| **Index-Echo** | 3 target languages (Chinese → en, ja, es) | Native timings | 2.7 - 5.1 GB (model + decoder pair) | Speech *translation*: Chinese audio in, English, Japanese or Spanish subtitles out. The language dropdown picks the target language. Needs Crisp ASR v0.8.41 or newer | | **Mega** | 2 (en, zh) | Needs aligner | 1.3 - 4.4 GB | Mega-ASR 1.7B. VAD is on by default (see the VAD section below) | | **Granite** | 6 (en, fr, de, es, pt, ja) | Needs aligner | 1.54 - 5.58 GB | IBM Granite Speech 4.1 2B. The `plus` models are the newer revision; `mini` and `f16enc` trade encoder precision for size | | **ARK** | 19 (European + zh, ja, ko) | Needs aligner | 3.52 - 7.51 GB | A 3B model - the heaviest backend here, so only worth it when the smaller ones fall short | diff --git a/docs/features/spell-check.md b/docs/features/spell-check.md index 94d7ecc771a..dea820279d1 100644 --- a/docs/features/spell-check.md +++ b/docs/features/spell-check.md @@ -79,4 +79,4 @@ Spell check requires a dictionary to be installed. If no dictionary is found, yo | Key | Action | |-----|--------| | Escape | Close spell check | -| F1 | Open help | +| F1 | Show help (default key, [changeable](shortcuts.md#worth-knowing)) | diff --git a/docs/features/statistics.md b/docs/features/statistics.md index 2e4918a96a4..91cde4ffdc7 100644 --- a/docs/features/statistics.md +++ b/docs/features/statistics.md @@ -39,5 +39,5 @@ View detailed statistics about the currently loaded subtitle file, including lin | Shortcut | Action | |----------|--------| -| F1 | Show help | +| F1 | Show help (default key, [changeable](shortcuts.md#worth-knowing)) | | Escape | Close dialog | diff --git a/docs/features/subtitle-grid.md b/docs/features/subtitle-grid.md index b7cd0b39e8f..9b011d48af9 100644 --- a/docs/features/subtitle-grid.md +++ b/docs/features/subtitle-grid.md @@ -25,6 +25,8 @@ The grid can show the columns below. A few are always visible; the others can be | **Pixel width** | | Rendered pixel width of the text | | **Forced** | | Check mark on lines marked as forced narrative (set with **Toggle forced** in the context menu) | | **Layer** | | Layer number (only available for formats that support layers, e.g. ASS/SSA) | +| **Shot in** | | Signed distance from the start time to the nearest shot change (milliseconds, or frames in frame mode). Highlighted when [Beautify time codes](beautify-time-codes.md) would move the cue. Empty without a video and shot changes | +| **Shot out** | | The same for the end time | ## Customizing Visible Columns @@ -50,7 +52,7 @@ Right-click a line to access: - Delete - Insert before / Insert after - Insert subtitle after current line... (insert a whole subtitle file) -- Column (delete text, insert text, paste from clipboard, shift cells up/down, text up/down) +- Column (delete text, insert text, paste from clipboard, shift cells up/down, text up/down) — see [Column paste](#column-paste) - Split line / Assisted split... / Assisted move... - Merge before / Merge after / Merge selected / Merge selected as dialog - Extend to line before / Extend to line after @@ -98,18 +100,34 @@ The actor order is alphabetical when a file is opened. Actors added while workin | `Ctrl+C` | Copy | | `Ctrl+V` | Paste | | `Ctrl+X` | Cut | +| `Ctrl+Shift+V` | Fill selected lines with clipboard text (also works on one line) | | `Enter` | Go to subtitle and set video position (default; **Options → Settings** can change it to *Go to next line*) | | `Up/Down` | Navigate lines | +| `PageUp/PageDown` | Move the selection one page up / down | +| `Home/End` | Go to the first / last line | +| `Shift+Up/Down/PageUp/PageDown/Home/End` | Extend the selection (`Ctrl+Shift`, or `Cmd+Shift` on macOS, works too when that chord has no shortcut of its own) | +| `0`–`9` | Type a line number to jump to it — digits typed within a second add up, so `1` then `2` goes to line 12 | + +The navigation keys and the line-number type-ahead are built in. A shortcut you assign to a bare digit in **Options → Shortcuts** wins over the type-ahead. ### Pasting over several lines -With **one** line selected, `Ctrl+V` inserts the clipboard content below it. With **several** lines selected, the clipboard is pasted *over* the selection instead — handy for translating: copy the lines out, translate them elsewhere, select the same lines here and paste. +With **one** line selected, `Ctrl+V` inserts the clipboard content as new lines below it, and the status bar says "Inserted N line(s) from the clipboard". To replace the text of that one line instead, use `Ctrl+Shift+V` (**Fill selected lines with clipboard text**): it puts the whole clipboard text into every selected line, time codes untouched. With **several** lines selected, the clipboard is pasted *over* the selection instead — handy for translating: copy the lines out, translate them elsewhere, select the same lines here and paste. - Clipboard holds a subtitle (SRT, ASSA, …): the selected lines are replaced by the pasted ones, time codes included. The number of pasted lines does not have to match the selection. - Clipboard holds plain text: one clipboard line goes into each selected line's text, and the time codes are left alone. Clipboard lines that go past the end of the selection are not pasted. Both are a normal edit, so `Ctrl+Z` undoes them. +### Column paste + +**Column → Paste from clipboard...** in the context menu (shortcut "Column, paste", no default key) pastes the clipboard into one column, starting at the first selected line and going down. It never adds lines: pasting stops at the last line of the subtitle. + +- **Choose column** — **All** (time codes and text), **Time codes only**, or **Text only**. When the clipboard holds plain text rather than a subtitle, only **Text only** is available and each clipboard line goes into one line's text. +- **Overwrite/shift cells down** — **Overwrite existing cells** replaces the cells, **Shift text cells down** moves the existing cells down one row per pasted line first (the cells pushed past the last line are dropped). + +Like SE 4's column paste, this is useful for bringing timings from one file into another, or a translated text block into existing time codes. + ## Formatting Display How the grid treats HTML/ASSA markup is a five-way choice — **Show formatted (HTML/ASSA) text in subtitle grid** in **Options → Settings → Appearance**: diff --git a/docs/features/text-editor.md b/docs/features/text-editor.md index 5b01e34c9b2..6ef341ac239 100644 --- a/docs/features/text-editor.md +++ b/docs/features/text-editor.md @@ -36,6 +36,8 @@ Additional formatting options: | `Ctrl+A` | Select all text | | `Ctrl+Z` | Undo | +On macOS, **Option+Backspace** deletes the previous word and **Cmd+Backspace** (or Cmd+Delete) deletes to the start of the line, like in other Mac apps. + ## Split Line - **Split at cursor** — Split the subtitle at the text cursor position diff --git a/docs/features/text-to-speech.md b/docs/features/text-to-speech.md index 5bea77315c1..d48f4d2a594 100644 --- a/docs/features/text-to-speech.md +++ b/docs/features/text-to-speech.md @@ -26,8 +26,9 @@ By default **Add audio to video file** replaces the video's sound with the new s **Remove original speech (slow)** (also under **Advanced...**) gets rid of just the voices: it splits the video's sound into speech and everything else, throws the speech away, and mixes the new speech over the music and sound effects. That is what a dub normally sounds like. -- The first use downloads the CrispASR runtime (if no CrispASR engine is installed yet) and a source separation model (Mel-Band RoFormer, 457 MB). Speech to text's **Isolate speech** uses the same model. -- It is slow: about as long as the video itself on a GPU (Metal, CUDA, Vulkan), and many times longer on CPU only. +- **Model** (below the checkbox) picks the separation model: **Best quality (Mel-Band RoFormer)** (default) keeps the most of the music and sound effects; **Fast (HTDemucs)** is about three times quicker, with a little more of the music lost. +- The first use downloads the CrispASR runtime (if no CrispASR engine is installed yet) and the selected separation model (Mel-Band RoFormer 457 MB, HTDemucs 81 MB). Speech to text's **Isolate speech** uses the Mel-Band RoFormer model too. +- It is slow: with **Best quality**, about as long as the video itself on a GPU (Metal, CUDA, Vulkan), and many times longer on CPU only. - The music and effects play at full volume. Turn on **Audio ducking** as well to lower them - its volume then applies to the music and effects. - The video's first audio track is used. - If the separation fails, the speech is added to the video the normal way and the tools log says why. @@ -185,6 +186,10 @@ The points that matter: Declining just means "not now" — nothing is changed, the clone is refused, and you are asked again the next time. The answer is remembered per terms version, so you are asked again if the terms change. +### Clean Voice-Clone References + +**Clean voice-clone references (Sidon)** under **Advanced...** removes music, noise and reverb from the audio a voice is cloned from before the engine sees it: clips taken from the video (**Clone voice to**, **Clone from video**, **Find voices in video and clone**) and recordings imported in the voice manager. It uses the Sidon speech restoration model on audio.cpp (940 MB download, asked for on first use). The cloned voice no longer copies the background, but very quiet or overlapping speech can come out muffled. If the download is declined or cleaning fails, the reference is used as it is. + ### Renaming and Deleting Imported Voices Right-click the voice combo box for **Rename voice...** and **Delete voice...**. Both work on voices you imported or cloned - the engines list those straight from their `voices` folder, one reference recording per voice - and are disabled for engine presets, built-in speakers and the *Clone from video* entry. @@ -291,10 +296,11 @@ You can control pausing and pacing in ElevenLabs output directly from the subtit Subtitle Edit sends your text to ElevenLabs with **text normalization turned off**, so these pause cues are preserved instead of being collapsed or rewritten. -Behavior depends on the selected model: +The models offered are `eleven_v4_turbo` (the default), `eleven_v4`, `eleven_turbo_v2_5`, `eleven_v3` and `eleven_multilingual_v2`. Behavior depends on the selected model: -- **Standard models** (`eleven_turbo_v2_5`, `eleven_multilingual_v2`, …) honor SSML `` tags and punctuation pauses. -- **Eleven v3** does not support SSML `` tags. Subtitle Edit automatically converts each `` into the nearest v3 audio pause tag — `[short pause]` (under 0.75 s), `[pause]` (up to 1.5 s), or `[long pause]` (longer) — so the same subtitle text keeps working when you switch to v3. +- **Standard models** (`eleven_turbo_v2_5`, `eleven_multilingual_v2`) honor SSML `` tags and punctuation pauses. +- **Eleven v4, v4 Turbo and v3** do not support SSML `` tags. Subtitle Edit automatically converts each `` into the nearest audio pause tag — `[short pause]` (under 0.75 s), `[pause]` (up to 1.5 s), or `[long pause]` (longer) — so the same subtitle text keeps working when you switch model. +- These three models also understand expressive audio tags such as `[whispers]` or `[laughs]`. With one of them selected, a help button next to the **Model** drop-down in the review window opens ElevenLabs' guide to audio tags. ## Review Audio Clips @@ -370,4 +376,4 @@ From there you can play, edit text, regenerate single lines, and finish the sess | R / Ctrl+R | Regenerate selected line | | Space | Play / pause the selected line | | Escape | Close / Cancel | -| F1 | Open help | +| F1 | Show help (default key, [changeable](shortcuts.md#worth-knowing)) | diff --git a/docs/features/transparent-subtitles.md b/docs/features/transparent-subtitles.md index 0c0b38f5f77..89d2575de76 100644 --- a/docs/features/transparent-subtitles.md +++ b/docs/features/transparent-subtitles.md @@ -57,4 +57,4 @@ Click **Batch mode** to queue several subtitle files (**Single mode** switches b | Key | Action | |-----|--------| | Escape | Close / Cancel | -| F1 | Open help | +| F1 | Show help (default key, [changeable](shortcuts.md#worth-knowing)) | diff --git a/docs/features/video-player.md b/docs/features/video-player.md index fd6a0ec5ac9..f43a3d24aca 100644 --- a/docs/features/video-player.md +++ b/docs/features/video-player.md @@ -70,11 +70,11 @@ Jump to a specific time code position in the video. - **Shortcut:** Configurable - Toggle full-screen video playback -## Undock / Redock +## Undock / Dock You can undock the video player into a separate window for multi-monitor setups: - **Undock video controls** -- **Redock video controls** +- **Dock video controls** ## Secondary Subtitles @@ -95,6 +95,8 @@ Configure the video player backend in **Options → Settings → Video player**: - **libmpv - Native Window ID rendering** — default on Windows (not available on macOS) - **libmpv - Software rendering (slow)** - **libVLC - Native Window ID rendering** — alternative backend (Windows and Linux only) +- **FFmpeg** — plays video through FFmpeg, no libmpv/libVLC needed (all platforms) +- **libmpv - Metal (experimental, needs restart)** — macOS only The same settings page also has: - **Video controls** — see [Video Controls](#video-controls) diff --git a/docs/features/visual-sync.md b/docs/features/visual-sync.md index 9e3f5695455..9e379c31102 100644 --- a/docs/features/visual-sync.md +++ b/docs/features/visual-sync.md @@ -13,7 +13,7 @@ The Visual sync window shows two video player panes ("Start scene" and "End scen The waveform under each video only appears when the main window has a waveform to share. Drag the handle between the video and the waveform to resize the waveform - both panes follow, and the height is remembered between sessions. -1. Open **Sync → Visual sync...** +1. Open **Synchronization → Visual sync...** 2. In the **Start scene** pane, pick a subtitle line near the beginning and play the video to the position where that line should start 3. In the **End scene** pane, pick a subtitle line near the end and play the video to the position where that line should start 4. Click **Sync** to apply (or use **Manual sync...** from the Sync split-button for a manual offset/speed adjustment) diff --git a/docs/features/webvtt-styles.md b/docs/features/webvtt-styles.md index ba3594f69d4..6f9a9bad936 100644 --- a/docs/features/webvtt-styles.md +++ b/docs/features/webvtt-styles.md @@ -70,7 +70,7 @@ With a video loaded in a browser-playable container (`.mp4`, `.m4v`, `.webm`, `. | Shortcut | Action | |----------|--------| -| F1 | Show help | +| F1 | Show help (default key, [changeable](shortcuts.md#worth-knowing)) | | Ctrl+Up / Ctrl+Down | Move the selected style up / down | | Escape | Close dialog | diff --git a/docs/features/whats-new-in-se5.md b/docs/features/whats-new-in-se5.md index fc1022f0f9a..873cede61db 100644 --- a/docs/features/whats-new-in-se5.md +++ b/docs/features/whats-new-in-se5.md @@ -2,6 +2,108 @@ Subtitle Edit 5 is the Avalonia-based, cross-platform version of Subtitle Edit. It keeps the familiar subtitle editing workflow from the Windows Forms version, but many features were expanded. +The first section lists the highlights of 5.3. The sections after it describe what Subtitle Edit 5 (up to 5.2) added compared to Subtitle Edit 4. + +## What's New in 5.3 + +### Editing + +- **Command palette** - **Edit → Command palette...** (Ctrl+Shift+K) finds any command by name (with its shortcut) and runs it (see [Command palette](command-palette.md)). +- **Custom shortcut slots** built from steps (run commands, insert text, find and replace), and **Surround with** slots with toggle/add/remove behavior, like SE 4 (see [Custom shortcuts](shortcuts.md#custom-shortcuts)). +- **AltGr** as a shortcut modifier on Windows, and new shortcuts: *Toggle break/unbreak*, *Save original*, *Remove all formatting*, *Delete selection (everywhere)*, *Set style 1-10*, *Toggle screen privacy* and more. +- **Tools → Merge continuation lines...** merges lines unless a line ends a sentence (see [Merge continuation lines](merge-continuation-lines.md)). +- **Find/Replace** shows the replace count and a search history whose entries can be deleted one by one; **Multiple replace** rules get a *Whole word* option. +- **Actor picker**, *Rename...* in the grid's Actors menu, and *Remove/replace Unicode characters* and *Convert actors* for selected lines. +- Tools report "*N lines changed*" and get their own named undo step; optional *go to changed line* on undo/redo. +- Built-in interjection lists for 11 languages, a spell check editor for the *Use always* list, and the British English word list. +- *Merge lines without auto-break* setting, and *Lock time codes* is respected by ripple delete, recalculate duration and the shot-change commands. + +### Main window and layout + +- **Layout 14** - an editor-style layout with a timeline: a video thumbnail row and subtitle rows (by layer, actor or style) above the waveform (see [Layouts](main-window.md#layouts)). +- **Layouts 12 and 13** put the text box below the video player, and a **Font scale (%)** setting sits next to UI scale. +- **Time code mode "Frame numbers"** shows absolute frame numbers, switchable in Settings or from the status bar (see [Settings → General](settings.md#general)). +- **Reload the subtitle** when the file is changed outside Subtitle Edit. +- Optional **Shot in / Shot out** grid columns, a *Show recent files* option, the full file path in the title bar, and a text selection opacity setting. +- Settings show one category at a time (Ctrl+PageUp/Down, Alt+Left/Right), and Settings.json gets automatic backups. +- Taskbar/Dock progress for speech to text, text to speech, AI review, auto-translate, burn-in and shot-change generation. +- Screen reader names for lists, grids, combo boxes and buttons across the tool windows. + +### Video and waveform + +- **FFmpeg video player** - plays video through FFmpeg with hardware decoding, real frame stepping (also variable frame rate), sound on Linux and an audio track picker (see [Supported video players](video-player.md#supported-video-players)). +- **Remember waveform zoom** across restarts (Options → Settings → Waveform), a waveform grid color, and a **Show speech only** waveform (see [Speech-only waveform](audio-visualizer.md#speech-only-waveform)). +- Customizable order and visibility of the video player controls, a deinterlace setting, and an experimental mpv **Metal** player on macOS. +- New video shortcuts: *move X ms back/forward*, *custom milliseconds back/forward and pause*, and *Guess start and end time from waveform*. +- The waveform toolbar wraps to a second row, and can show the selected line's initial text and *move lines* buttons. +- **Video → More → Generate background music...** makes local ACE-Step music looped to the video's length (see [Background music](background-music.md)). +- **Video → More → Remux video...** puts video, several audio tracks and subtitles in one container, can mix audio files and embed CEA-608 captions (see [Remux video](remux-video.md)). +- **Cut video** opens and generates much faster, with [transitions and fade from/to black](cut-video.md#transitions). +- **Burn-in** of selected lines from the grid context menu, and burn-in into half side-by-side / top-bottom [3D video](burn-in.md#3d-video). + +### Speech to text + +- **Detect speakers** (Sortformer diarization), a choice of VAD (Silero, FireRedVAD, WebRTC) and **Isolate speech** for Crisp ASR (see [Isolate speech](speech-to-text.md#isolate-speech-crisp-asr)). +- New Crisp ASR models: Index-Echo (Chinese speech to English/Japanese/Spanish text), Parakeet Ultra, Phonon-2, ReazonSpeech and Voxtral F16. +- A *Multilingual* option for Purfview Faster-Whisper XXL, a new standalone WhisperX build with live output, and vLLM support in the OpenAI-compatible engine. +- Transcribing selected lines keeps the lines that worked and offers to retry the failed ones; the computer no longer sleeps while it runs. + +### Text to speech + +- **Play with speech** speaks the lines in sync while the video plays, and **Speak from current line** reads aloud from the selected line (see [Play with speech](text-to-speech.md#play-with-speech) and [Speak from current line](text-to-speech.md#speak-from-current-line)). +- **KugelAudio** (23 European languages), **Supertonic-3** and an **OpenAI-compatible** engine (OpenAI, OpenRouter, custom server) (see [Supported engines](text-to-speech.md#supported-engines)). +- **ElevenLabs Eleven v4** and **v4 Turbo** (the new default), with `` tags turned into audio pause tags (see [ElevenLabs pauses](text-to-speech.md#elevenlabs-pauses-and-pacing)). +- **Remove original speech** keeps the music and effects under the new voices, now with a **Fast (HTDemucs)** model option (see [Remove the original speech](text-to-speech.md#remove-the-original-speech-from-the-video)). +- Optional **Sidon cleaning** of voice-clone reference audio, and a **Voice manager** to copy voices between engines and share voice packs (see [Voice manager](text-to-speech.md#voice-manager)). + +### Auto-translate + +- New engines: **LM Studio advanced**, **API Route**, and a *Browse models* button for OpenAI-compatible and LM Studio. +- **Translate in place**, a model drop-down, 22 more languages for the AI engines, and an option to leave music lines untranslated. +- A shortcut to translate the selected lines without the prompt, and MyMemory works without an API key. +- More llama.cpp models (Index-Translate, TranslatePsy-AfriSLM), and lines that come back untranslated are retried. + +### OCR + +- **nOCR** is more accurate (scale-aware matching, automatic spaces, shadow threshold, a better Latin database), and glyphs can carry their own italic (see [nOCR](ocr.md#nocr-nikse-ocr)). +- The **Train nOCR** window has a preview, can start from an existing database, trains several font sizes, and a database can be optimized. +- **Find and Replace** in the OCR window, remembered [pre-processing](ocr.md#pre-processing) settings, and the spell check dictionary remembered per language (see [Find and Replace](ocr.md#find-and-replace)). + +### Sync and timing + +- **Tools → Improve time codes (forced alignment)...** re-times a roughly synced subtitle with a Crisp ASR aligner and can check large moves with speech to text (see [Improve time codes](improve-time-codes.md)). +- **Point sync via other subtitle** loads subtitles from mkv/mp4/.sup, shows gap badges and a *Gap after* column (see [Point sync via other subtitle](point-sync-via-other.md)). +- *Move start/end X ms* shortcuts, also in Visual sync, and *Extend to next/previous shot change* resolves overlaps. +- Custom **Beautify time codes** profiles can be saved, loaded and deleted, and a frame rate can be typed in *Change frame rate*. + +### Formats and import + +- **File → Import → Subtitles from DVD (IFO/VOB)...** with a title picker, fast rip and exact cell timing (see [Import subtitles from DVD](file.md#import-subtitles-from-dvd)). +- CEA-608/708 closed captions from `.ts`, `.mkv`, `.mp4`, `.mxf`, `.vob`/`.mpg` and `.m2v`, with a channel/service picker. +- New readers: EZTitles `.ezt`, PSP UMD Video, HD-DVD sup, Cheetah Caption ASC, ELR Studio and more SE 4 formats that open again. +- PAC code page detection and a secondary code page, and transport streams saved with a video extension open. +- The format drop-down shows full format names, and Source view colors WebVTT, LRC and SCC. + +### ASSA + +- **ASSA draw** has a new layout with menus, undo/redo, scale/rotate, a shape library, backgrounds and SVG import (see [ASSA draw](assa-draw.md)). +- Styles: overwrite or keep both on import, *Replace style with*, move up/down, and a *Set style...* quick-pick shortcut. + +### Batch convert + +- **Translate into several languages** in one run, **named presets**, and new filters (format, extension, error status, forced tracks). +- New functions: *Remove Unicode control characters*, *Renumber*, *Merge continuation lines* and *Convert actors* (see [Available functions](batch-convert.md#available-functions)). +- Asks before adding video files from a folder, adds many files faster, and shows converted/failed/not processed counts. + +### Compare + +- **Time tolerance** option and how far the times are off, a merge view to edit while comparing, and right-click on the gutter arrow to take only text or timing (see [Compare](compare.md#comparison-options)). + +### macOS and Linux + +- Search help topics from the macOS **Help** menu, macOS-standard default shortcuts, and a new rounded app icon. +- Flatpak runtime moved to freedesktop 26.08, and Finnish spell checking with Voikko in the Flatpak and macOS builds. + ## Application Platform - Cross-platform Avalonia UI for Windows, Linux, and **macOS** — macOS is a new supported platform in SE 5. @@ -47,9 +149,9 @@ See [Spell Check](spell-check.md) for details. ## Video -- New **Video → Re-encode** tool that re-encodes a video into a format better suited for subtitling work. -- New **Video → Cut video** tool for trimming video segments directly from Subtitle Edit. -- **Video → Burn-in with logo** — a logo/watermark image can now be included when burning subtitles into video. +- New **Video → More → Re-encode video for better subtitling...** tool that re-encodes a video into a format better suited for subtitling work (see [Re-encode video](re-encode-video.md)). +- New **Video → More → Cut video...** tool for trimming video segments directly from Subtitle Edit (see [Cut video](cut-video.md)). +- **Logo in burn-in** — **Video → Generate video with burned-in subtitles...** can include a logo/watermark image (see [Effect and logo](burn-in.md#effect-and-logo)). - Many new speech-to-text engines (see [Speech to Text](#speech-to-text) below). - New **Video → OCR burned-in subtitle** tool that extracts hardcoded subtitles from video via OCR (see [Video OCR](video-ocr.md)). - Improved reading of subtitles embedded in MP4 files. diff --git a/docs/features/word-lists.md b/docs/features/word-lists.md index 1752a980a43..523ef78149a 100644 --- a/docs/features/word-lists.md +++ b/docs/features/word-lists.md @@ -47,4 +47,4 @@ Word lists are organized by language. The language dropdown shows all languages | Key | Action | |-----|--------| | Escape | Close | -| F1 | Open help | +| F1 | Show help (default key, [changeable](shortcuts.md#worth-knowing)) | diff --git a/docs/index.md b/docs/index.md index d29a01e5e1e..e47f430031c 100644 --- a/docs/index.md +++ b/docs/index.md @@ -19,6 +19,7 @@ Subtitle Edit is a free, open-source editor for video subtitles. This is the doc ### Editing - [Edit Menu](features/edit.md) — Find, Replace, Multiple Replace, History +- [Command Palette](features/command-palette.md) — Find and run any command by typing part of its name (Ctrl+Shift+K) - [Source View](features/source-view.md) — Edit the raw subtitle source: syntax coloring, live parse check, find/replace, line commands - [Modify Selection](features/modify-selection.md) — Select lines by rules: text, hearing-impaired text, duration, CPS, gaps, styles, actors - [Assisted Split and Assisted Move](features/assisted-split.md) — Ranked one-click split points and word moves with full previews @@ -43,6 +44,7 @@ Subtitle Edit is a free, open-source editor for video subtitles. This is the doc - [Bridge Gaps](features/bridge-gaps.md) — Bridge gaps between subtitles - [Apply Minimum Gap](features/apply-min-gap.md) — Set minimum gap between subtitles - [Merge Short Lines](features/merge-short-lines.md) — Merge short subtitle lines +- [Merge Continuation Lines](features/merge-continuation-lines.md) — Merge sentences split across several subtitles, with a preview - [Merge Lines with Same Text](features/merge-same-text.md) — Merge duplicate text lines - [Merge Lines with Same Time Codes](features/merge-same-timecodes.md) — Merge lines with identical timings - [Merge Two Subtitles](features/merge-two-subtitles.md) — Combine two subtitles into one bilingual subtitle @@ -78,10 +80,11 @@ Subtitle Edit is a free, open-source editor for video subtitles. This is the doc - [Re-encode Video](features/re-encode-video.md) — Re-encode video files - [Cut Video](features/cut-video.md) — Cut video segments - [Remux Video](features/remux-video.md) — Repackage video with new audio tracks and soft subtitles, without re-encoding +- [Generate Background Music](features/background-music.md) — Generate looping instrumental music locally (ACE-Step) and add it to the video or under TTS speech ### Translation - [Auto Translate](features/auto-translate.md) — Automatic subtitle translation -- [Auto Translate: Advanced Local Engines](features/auto-translate-advanced.md) — Batch/context settings for the llama.cpp and Ollama advanced engines +- [Auto Translate: Advanced Local Engines](features/auto-translate-advanced.md) — Batch/context settings for the llama.cpp, Ollama and LM Studio advanced engines - [Copy/Paste Translate](features/copy-paste-translate.md) — Translate via copy/paste workflow ### Spell Check diff --git a/docs/overview.md b/docs/overview.md index 847cba1da3b..473ab00ed61 100644 --- a/docs/overview.md +++ b/docs/overview.md @@ -26,15 +26,25 @@ Subtitle Edit is a free, open-source editor for video subtitles. It allows you t ## System Requirements - Windows 10 (version 22H2 / build 19045) or newer, Linux, or macOS 12 (Monterey) or newer (macOS 14 / Sonoma or newer recommended - that is the oldest macOS .NET 10 is supported on) -- [FFmpeg](https://ffmpeg.org/) (for audio/video processing) +- [FFmpeg](https://ffmpeg.org/) (for audio/video processing, waveforms, burn-in etc.) - [libmpv](https://mpv.io/) for video playback +How you get FFmpeg and libmpv depends on the platform: +- **Windows** — not included; Subtitle Edit offers to download them the first time they are needed (libmpv when you open a video, FFmpeg for waveforms and other video features). Both can also be downloaded from **Options → Settings**. +- **macOS** — the `.dmg` is self-contained: libmpv and FFmpeg are bundled inside `Subtitle Edit.app`. +- **Linux** — the Flatpak bundles mpv and FFmpeg. The native `.tar.gz` builds use the system libraries, so install libmpv and FFmpeg with your package manager (see the [README](https://github.com/SubtitleEdit/subtitleedit#linux) for the package names per distribution). + +All builds are self-contained and do not need a separate .NET installation. + ## Getting Started Follow these simple steps to begin working with subtitles in Subtitle Edit: 1. **Install Subtitle Edit** - Download and install the latest version from the official website. + Download the latest version from the [Releases](https://github.com/SubtitleEdit/subtitleedit/releases) page: + - **Windows** — `SubtitleEdit-Windows-x64-Setup.exe` (installer), or `SubtitleEdit-Windows-x64.zip` / `SubtitleEdit-Windows-ARM64.zip` (portable - unzip and run `SubtitleEdit.exe`; settings are kept next to the program) + - **macOS** — `SubtitleEdit-macOS-ARM64.dmg` (Apple Silicon) or `SubtitleEdit-macOS-x64.dmg` (Intel). Open the `.dmg` and drag `Subtitle Edit.app` into `Applications`; it is signed and notarized, so it opens normally + - **Linux** — `SubtitleEdit-linux-x64.flatpak` (any distribution, install with `flatpak install SubtitleEdit-linux-x64.flatpak`), or the native `SubtitleEdit-Linux-x64.tar.gz` / `SubtitleEdit-Linux-ARM64.tar.gz` builds (need libmpv and FFmpeg from your package manager, see the [README](https://github.com/SubtitleEdit/subtitleedit#linux)) 2. **Open a video file** Go to **Video → Open video...** and select your video. @@ -71,7 +81,7 @@ The main window consists of several key areas: - **Video Player** — Preview video with subtitles - **Audio Visualizer** — Waveform/spectrogram for precise timing adjustments -Subtitle Edit offers **12 different layouts** for arranging these areas. Choose one with the **Layout** button on the toolbar or its configurable shortcut. +Subtitle Edit offers **15 different layouts** for arranging these areas. Choose one with the **Layout** button on the toolbar or its configurable shortcut. For a detailed guide to each area, mouse interactions, and keyboard shortcuts, see the **[Main Window](features/main-window.md)** documentation. diff --git a/docs/reference/command-line.md b/docs/reference/command-line.md index f9a4edec309..f41c227d982 100644 --- a/docs/reference/command-line.md +++ b/docs/reference/command-line.md @@ -437,14 +437,14 @@ a no-op. `ollama` and `lmstudio` have no per-model template, so it is simply | Option | Description | |---|---| -| `--multiple-replace:` | Multiple-replace rules applied per paragraph after operations. Accepts the legacy SE *MultipleSearchAndReplaceGroups* XML **and** the file the SE5 GUI exports from *Tools → Multiple replace → export* — either `.template` (JSON) or `.csv`. Supports case-insensitive, `CaseSensitive`, and `RegularExpression` rules; only active rules are applied. The format is chosen by extension, then by content | +| `--multiple-replace:` | Multiple-replace rules applied per paragraph after operations. Accepts the legacy SE *MultipleSearchAndReplaceGroups* XML **and** the file the SE5 GUI exports from *Edit → Multiple replace → export* — either `.template` (JSON) or `.csv`. Supports case-insensitive, `CaseSensitive`, and `RegularExpression` rules; only active rules are applied. The format is chosen by extension, then by content | | `--custom-format:` | SE *CustomFormatItem* XML (use with `--format customtext`) | | `--settings:` | JSON file overlaying `Configuration.Settings` (general / tools / removeTextForHearingImpaired) plus image-output styling (exportImages). Optional `profiles` map for named overlays. The `tools` section also carries the auto-translate prompts (`llamaCppPrompt`, `ollamaPrompt`, `lmStudioPrompt`), so a `profiles` entry can hold a per-target-language prompt. `tools.ocrUseWordSplitList: true` lets *Fix common OCR errors* split run-together words via the word split list (off by default, as in the GUI, because it also splits valid words missing from the dictionary) | | `--profile:` | Selects a named overlay from the settings file's `profiles` map. Requires `--settings` | #### Multiple-replace rule files -The quickest way to share rules between the GUI and `seconv`: in Subtitle Edit open **Tools → Multiple replace**, then **export** the rules as `.template` (JSON) or `.csv`, and pass that file to `--multiple-replace`. Both are read directly — no conversion needed. +The quickest way to share rules between the GUI and `seconv`: in Subtitle Edit open **Edit → Multiple replace**, then **export** the rules as `.template` (JSON) or `.csv`, and pass that file to `--multiple-replace`. Both are read directly — no conversion needed. ```bash seconv *.srt subrip --multiple-replace:my-rules.csv # exported from the GUI diff --git a/docs/reference/keyboard-shortcuts.md b/docs/reference/keyboard-shortcuts.md index 8ffff260d42..53c54e0cee5 100644 --- a/docs/reference/keyboard-shortcuts.md +++ b/docs/reference/keyboard-shortcuts.md @@ -19,6 +19,8 @@ A few defaults differ on macOS so the standard macOS shortcuts keep working and | Delete selected line(s) | Delete | Cmd+Backspace | The Delete key on Apple keyboards is a backspace | | Extend selected line to previous | Alt+Shift+E | Cmd+Option+Shift+E | Option+Shift+E types a character (È on Italian keyboards) | | Set start time | F11 | F9 | F11 shows the desktop | +| Set start and offset the rest | F9 | No default | F9 is set start time on macOS | +| Open data folder | Ctrl+Alt+Shift+D | Control+Cmd+Option+Shift+D | Cmd+Option+Shift+D alone never reaches the app | | Toggle play/pause (secondary) | Ctrl+Space | No default | Cmd+Space opens Spotlight | Settings that still had the old defaults are moved to the new ones on first start. Shortcuts you assigned yourself are left alone. @@ -48,8 +50,15 @@ Settings that still had the old defaults are moved to the new ones on first star | Alt+Down | Go to next line | | Ctrl+Home | Go to first line | | Ctrl+End | Go to last line | -| F1 | Show help | +| Ctrl+Shift+K | [Command palette](../features/command-palette.md) — search and run any command | +| F1 | Show help (every window opens its own help page) | | F2 | Show source view | +| F10 | Activate the main menu bar (press F10 or Escape again to return to editing) | +| Ctrl+F8 | List errors | +| F8 / Shift+F8 | Go to next / previous error | +| Alt+F7 | Spell check | + +F1 is the default for the **Help** action and can be changed in **Options** → **Shortcuts** like any other shortcut. The same key works in every dialog, and opens the help page for that dialog. ## Editing Lines @@ -76,6 +85,8 @@ Active while the subtitle text box has focus. | Ctrl+A | Select all text | | Shift+Delete | Cut (Windows-standard gesture; Subtitle Edit binds it because Avalonia does not) | | Shift+Backspace | Delete selection or next character — forward delete. Default on macOS only, where the Delete key is a backspace; assign it yourself elsewhere | +| Option+Backspace | Delete the previous word (macOS only, fixed) | +| Cmd+Backspace / Cmd+Delete | Delete to the start of the line (macOS only, fixed) | > **Note:** "Text box: Cut / Copy / Paste (alternative)" exist as separate, unbound actions in **Options** → **Shortcuts** for anyone who wants a second gesture (e.g. Ctrl+Insert / Shift+Insert). @@ -88,10 +99,14 @@ Active while the subtitle text box has focus. | Ctrl+Shift+I | Inverse selection | | Ctrl+I | Toggle italic on selected lines or text | | Ctrl+X / Ctrl+C / Ctrl+V | Cut / copy / paste selected lines | -| Ctrl+Shift+V | Fill selected lines with clipboard | -| Ctrl+F8 | List errors | -| F8 / Shift+F8 | Go to next / previous error | -| Alt+F7 | Spell check | +| Ctrl+Shift+V | Fill selected lines with clipboard text (also on a single line) | +| Up / Down | Previous / next line | +| PageUp / PageDown | Move the selection one page up / down | +| Home / End | First / last line | +| Shift+Up / Down / PageUp / PageDown / Home / End | Extend the selection (Ctrl+Shift, or Cmd+Shift on macOS, also works when that chord has no shortcut of its own) | +| 0–9 | Type a line number to jump to that line (digits typed within a second add up, e.g. 1 then 2 is line 12) | + +The navigation keys and the line-number type-ahead are fixed, not part of **Options** → **Shortcuts**. A shortcut you assign to a bare digit wins over the type-ahead. ## Source View @@ -134,6 +149,7 @@ Inside the [source view](../features/source-view.md) window (F2), which edits th |----------|--------| | F11 | Set start time | | F12 | Set end time | +| F9 | Set start and offset the rest (Windows / Linux; no default on macOS) | "Set end time and go to next line" has no default shortcut: bare F10 activates the main menu bar (the Windows standard). Assign F10 to the action in Options → @@ -146,8 +162,8 @@ wins over the menu activation. |----------|--------| | Ctrl+V | Paste lines from clipboard at waveform position | | Ctrl+C | Copy the selected subtitle to the clipboard ("Copy (text only)" has no default key) | -| Shift++ | Vertical zoom in | -| Shift+- | Vertical zoom out | +| Shift+Numpad + | Vertical zoom in (waveform focused) | +| Shift+Numpad − | Vertical zoom out (waveform focused) | ## Tools diff --git a/docs/reference/mouse-controls.md b/docs/reference/mouse-controls.md index 5aac4e09155..76eb84ad51a 100644 --- a/docs/reference/mouse-controls.md +++ b/docs/reference/mouse-controls.md @@ -22,7 +22,7 @@ Full screen is toggled with Alt+Enter — see [Keyboard Shortcuts](keyboard-shor | Left-click + drag in empty area | Create a new selection | | Shift + Click | Set the selected subtitle's start time to the clicked position | | Ctrl (or Cmd) + Click | Set the selected subtitle's end time to the clicked position | -| Ctrl + Shift + Click | Set the selected subtitle's start time and offset all following subtitles by the same amount | +| Ctrl (Cmd on macOS) + Shift + Click | Set the selected subtitle's start time and offset all following subtitles by the same amount | | Alt + Click | Move the selected subtitle's start to the clicked position, keeping its duration | | Right-click | Select the subtitle under the cursor ("Select subtitle on right click", on by default), move the video position there, and open the context menu. On macOS, Ctrl + left-click opens the same menu | | Scroll wheel | Scroll waveform horizontally — or seek the video by one step per notch when "Mouse-wheel sets video position" is on; "Invert mouse-wheel" flips the direction (Options → Settings → Waveform/spectrogram) | @@ -31,6 +31,8 @@ Full screen is toggled with Alt+Enter — see [Keyboard Shortcuts](keyboard-shor | Ctrl (or Cmd) + Scroll wheel | Scroll waveform and seek video to cursor | | Double-click | Nothing by default — set "Waveform double-click action" in Options → Settings → Waveform/spectrogram to select the subtitle, center on it, pause, or play | +With the waveform focused, the Delete key deletes the selected subtitle(s). On Apple keyboards that is the forward delete, Fn+Delete — the key labeled Delete is a backspace. + ## Subtitle Grid | Action | Effect | diff --git a/src/ui/Features/Edit/MultipleReplace/MultipleReplaceViewModel.cs b/src/ui/Features/Edit/MultipleReplace/MultipleReplaceViewModel.cs index 582172ce0a3..069966aaf95 100644 --- a/src/ui/Features/Edit/MultipleReplace/MultipleReplaceViewModel.cs +++ b/src/ui/Features/Edit/MultipleReplace/MultipleReplaceViewModel.cs @@ -1177,7 +1177,7 @@ internal void OnKeyDown(object? sender, KeyEventArgs e) else if (UiUtil.IsHelp(e)) { e.Handled = true; - UiUtil.ShowHelp("features/edit"); + UiUtil.ShowHelp("features/edit", "multiple-replace"); } } diff --git a/src/ui/Features/Files/ImportDvd/ImportDvdViewModel.cs b/src/ui/Features/Files/ImportDvd/ImportDvdViewModel.cs index fcdf9ba056f..c02e3786d98 100644 --- a/src/ui/Features/Files/ImportDvd/ImportDvdViewModel.cs +++ b/src/ui/Features/Files/ImportDvd/ImportDvdViewModel.cs @@ -5,6 +5,7 @@ using CommunityToolkit.Mvvm.Input; using Nikse.SubtitleEdit.Core.VobSub; using Nikse.SubtitleEdit.Features.Shared; +using Nikse.SubtitleEdit.Logic; using Nikse.SubtitleEdit.Logic.Config; using Nikse.SubtitleEdit.Logic.Media; using SkiaSharp; @@ -437,6 +438,11 @@ internal void OnKeyDown(object? sender, KeyEventArgs e) Cancel(); } } + else if (UiUtil.IsHelp(e)) + { + e.Handled = true; + UiUtil.ShowHelp("features/file", "import-subtitles-from-dvd"); + } } internal void OnDragOver(object? sender, DragEventArgs e) diff --git a/src/ui/Features/Files/ManualChosenEncoding/ManualChosenEncodingViewModel.cs b/src/ui/Features/Files/ManualChosenEncoding/ManualChosenEncodingViewModel.cs index 5497c4323f1..2c43b0e1d1c 100644 --- a/src/ui/Features/Files/ManualChosenEncoding/ManualChosenEncodingViewModel.cs +++ b/src/ui/Features/Files/ManualChosenEncoding/ManualChosenEncodingViewModel.cs @@ -132,6 +132,11 @@ internal void OnKeyDown(KeyEventArgs e) e.Handled = true; Window?.Close(); } + else if (UiUtil.IsHelp(e)) + { + e.Handled = true; + UiUtil.ShowHelp("features/file", "import-subtitle-with-manually-chosen-encoding"); + } } internal void Initialize(string fileName) diff --git a/src/ui/Features/Ocr/BinaryOcr/BinaryOcrCharacterAddViewModel.cs b/src/ui/Features/Ocr/BinaryOcr/BinaryOcrCharacterAddViewModel.cs index 759a86a7d83..3db3ad0acae 100644 --- a/src/ui/Features/Ocr/BinaryOcr/BinaryOcrCharacterAddViewModel.cs +++ b/src/ui/Features/Ocr/BinaryOcr/BinaryOcrCharacterAddViewModel.cs @@ -405,6 +405,11 @@ public void KeyDown(KeyEventArgs e) { _isWinDown = true; } + else if (UiUtil.IsHelp(e)) + { + e.Handled = true; + UiUtil.ShowHelp("features/ocr", "binary-ocr"); + } } public void KeyUp(KeyEventArgs e) diff --git a/src/ui/Features/Ocr/BinaryOcr/BinaryOcrCharacterHistoryViewModel.cs b/src/ui/Features/Ocr/BinaryOcr/BinaryOcrCharacterHistoryViewModel.cs index 6b7cdf05543..d2dfba7fccc 100644 --- a/src/ui/Features/Ocr/BinaryOcr/BinaryOcrCharacterHistoryViewModel.cs +++ b/src/ui/Features/Ocr/BinaryOcr/BinaryOcrCharacterHistoryViewModel.cs @@ -184,6 +184,11 @@ public void KeyDown(KeyEventArgs e) { _isControlDown = true; } + else if (UiUtil.IsHelp(e)) + { + e.Handled = true; + UiUtil.ShowHelp("features/ocr", "binary-ocr"); + } } public void KeyUp(KeyEventArgs e) diff --git a/src/ui/Features/Ocr/NOcr/NOcrCharacterAddViewModel.cs b/src/ui/Features/Ocr/NOcr/NOcrCharacterAddViewModel.cs index 9f96551b710..6d0fd30d4ae 100644 --- a/src/ui/Features/Ocr/NOcr/NOcrCharacterAddViewModel.cs +++ b/src/ui/Features/Ocr/NOcr/NOcrCharacterAddViewModel.cs @@ -630,6 +630,11 @@ public void KeyDown(KeyEventArgs e) { _isWinDown = true; } + else if (UiUtil.IsHelp(e)) + { + e.Handled = true; + UiUtil.ShowHelp("features/ocr", "nocr-nikse-ocr"); + } } public void KeyUp(KeyEventArgs e) diff --git a/src/ui/Features/Ocr/NOcr/NOcrCharacterHistoryViewModel.cs b/src/ui/Features/Ocr/NOcr/NOcrCharacterHistoryViewModel.cs index 82600d05a9d..0dab5b40282 100644 --- a/src/ui/Features/Ocr/NOcr/NOcrCharacterHistoryViewModel.cs +++ b/src/ui/Features/Ocr/NOcr/NOcrCharacterHistoryViewModel.cs @@ -199,6 +199,11 @@ public void KeyDown(KeyEventArgs e) _isControlDown = true; NOcrDrawingCanvas.IsControlDown = _isControlDown; } + else if (UiUtil.IsHelp(e)) + { + e.Handled = true; + UiUtil.ShowHelp("features/ocr", "nocr-nikse-ocr"); + } } public void KeyUp(KeyEventArgs e) diff --git a/src/ui/Features/Options/Settings/ProfilesExportViewModel.cs b/src/ui/Features/Options/Settings/ProfilesExportViewModel.cs index 62c3d0f9dfe..34af2b8e34d 100644 --- a/src/ui/Features/Options/Settings/ProfilesExportViewModel.cs +++ b/src/ui/Features/Options/Settings/ProfilesExportViewModel.cs @@ -4,6 +4,7 @@ using CommunityToolkit.Mvvm.Input; using Nikse.SubtitleEdit.Features.Edit.MultipleReplace; using Nikse.SubtitleEdit.Features.Shared; +using Nikse.SubtitleEdit.Logic; using Nikse.SubtitleEdit.Logic.Config; using System; using System.Collections.Generic; @@ -73,5 +74,10 @@ internal void KeyDown(object? sender, KeyEventArgs e) e.Handled = true; Window?.Close(); } + else if (UiUtil.IsHelp(e)) + { + e.Handled = true; + UiUtil.ShowHelp("features/settings", "rules"); + } } } \ No newline at end of file diff --git a/src/ui/Features/Options/Settings/SettingsResetViewModel.cs b/src/ui/Features/Options/Settings/SettingsResetViewModel.cs index 9868f68736c..d2b5d65e450 100644 --- a/src/ui/Features/Options/Settings/SettingsResetViewModel.cs +++ b/src/ui/Features/Options/Settings/SettingsResetViewModel.cs @@ -2,6 +2,7 @@ using Avalonia.Input; using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; +using Nikse.SubtitleEdit.Logic; namespace Nikse.SubtitleEdit.Features.Options.Settings; @@ -47,5 +48,10 @@ internal void KeyDown(object? sender, KeyEventArgs e) e.Handled = true; Window?.Close(); } + else if (UiUtil.IsHelp(e)) + { + e.Handled = true; + UiUtil.ShowHelp("features/settings"); + } } } \ No newline at end of file diff --git a/src/ui/Features/Options/Shortcuts/CustomSearch/CustomSearchViewModel.cs b/src/ui/Features/Options/Shortcuts/CustomSearch/CustomSearchViewModel.cs index 97b90b2aae2..e90f09680b1 100644 --- a/src/ui/Features/Options/Shortcuts/CustomSearch/CustomSearchViewModel.cs +++ b/src/ui/Features/Options/Shortcuts/CustomSearch/CustomSearchViewModel.cs @@ -53,7 +53,7 @@ internal void OnKeyDown(KeyEventArgs e) else if (UiUtil.IsHelp(e)) { e.Handled = true; - UiUtil.ShowHelp("features/shortcuts"); + UiUtil.ShowHelp("features/shortcuts", "configurable-commands"); } } } diff --git a/src/ui/Features/Options/Shortcuts/SurroundWith/SurroundWithViewModel.cs b/src/ui/Features/Options/Shortcuts/SurroundWith/SurroundWithViewModel.cs index 62e312926c4..ddcd73ec28b 100644 --- a/src/ui/Features/Options/Shortcuts/SurroundWith/SurroundWithViewModel.cs +++ b/src/ui/Features/Options/Shortcuts/SurroundWith/SurroundWithViewModel.cs @@ -78,7 +78,7 @@ internal void OnKeyDown(KeyEventArgs e) else if (UiUtil.IsHelp(e)) { e.Handled = true; - UiUtil.ShowHelp("features/shortcuts"); + UiUtil.ShowHelp("features/shortcuts", "configurable-commands"); } } } diff --git a/src/ui/Features/Shared/BinaryEdit/BinaryAdjustAllTimes/BinaryAdjustAllTimesViewModel.cs b/src/ui/Features/Shared/BinaryEdit/BinaryAdjustAllTimes/BinaryAdjustAllTimesViewModel.cs index 028a78d6c98..243358cd54f 100644 --- a/src/ui/Features/Shared/BinaryEdit/BinaryAdjustAllTimes/BinaryAdjustAllTimesViewModel.cs +++ b/src/ui/Features/Shared/BinaryEdit/BinaryAdjustAllTimes/BinaryAdjustAllTimesViewModel.cs @@ -5,6 +5,7 @@ using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; using Nikse.SubtitleEdit.Core.Common; +using Nikse.SubtitleEdit.Logic; using Nikse.SubtitleEdit.Logic.Config; namespace Nikse.SubtitleEdit.Features.Shared.BinaryEdit.BinaryAdjustAllTimes; @@ -146,5 +147,10 @@ public void OnKeyDown(KeyEventArgs e) e.Handled = true; Window?.Close(); } + else if (UiUtil.IsHelp(e)) + { + e.Handled = true; + UiUtil.ShowHelp("features/binary-edit", "tools"); + } } } diff --git a/src/ui/Features/Shared/BinaryEdit/BinaryAdjustAlpha/BinaryAdjustAlphaViewModel.cs b/src/ui/Features/Shared/BinaryEdit/BinaryAdjustAlpha/BinaryAdjustAlphaViewModel.cs index 8bb15db8ccb..4a73d35dc6c 100644 --- a/src/ui/Features/Shared/BinaryEdit/BinaryAdjustAlpha/BinaryAdjustAlphaViewModel.cs +++ b/src/ui/Features/Shared/BinaryEdit/BinaryAdjustAlpha/BinaryAdjustAlphaViewModel.cs @@ -186,6 +186,11 @@ internal void OnKeyDown(KeyEventArgs e) e.Handled = true; Window?.Close(); } + else if (UiUtil.IsHelp(e)) + { + e.Handled = true; + UiUtil.ShowHelp("features/binary-edit", "tools"); + } } /// diff --git a/src/ui/Features/Shared/BinaryEdit/BinaryAdjustBrightness/BinaryAdjustBrightnessViewModel.cs b/src/ui/Features/Shared/BinaryEdit/BinaryAdjustBrightness/BinaryAdjustBrightnessViewModel.cs index c4b421290bb..e329e0a3beb 100644 --- a/src/ui/Features/Shared/BinaryEdit/BinaryAdjustBrightness/BinaryAdjustBrightnessViewModel.cs +++ b/src/ui/Features/Shared/BinaryEdit/BinaryAdjustBrightness/BinaryAdjustBrightnessViewModel.cs @@ -164,6 +164,11 @@ internal void OnKeyDown(KeyEventArgs e) e.Handled = true; Window?.Close(); } + else if (UiUtil.IsHelp(e)) + { + e.Handled = true; + UiUtil.ShowHelp("features/binary-edit", "tools"); + } } /// diff --git a/src/ui/Features/Shared/BinaryEdit/BinaryAdjustColor/BinaryAdjustColorViewModel.cs b/src/ui/Features/Shared/BinaryEdit/BinaryAdjustColor/BinaryAdjustColorViewModel.cs index cde956afd3b..a94ff3be636 100644 --- a/src/ui/Features/Shared/BinaryEdit/BinaryAdjustColor/BinaryAdjustColorViewModel.cs +++ b/src/ui/Features/Shared/BinaryEdit/BinaryAdjustColor/BinaryAdjustColorViewModel.cs @@ -153,6 +153,11 @@ internal void OnKeyDown(KeyEventArgs e) e.Handled = true; Window?.Close(); } + else if (UiUtil.IsHelp(e)) + { + e.Handled = true; + UiUtil.ShowHelp("features/binary-edit", "tools"); + } } /// diff --git a/src/ui/Features/Shared/BinaryEdit/BinaryAdjustDuration/BinaryAdjustDurationViewModel.cs b/src/ui/Features/Shared/BinaryEdit/BinaryAdjustDuration/BinaryAdjustDurationViewModel.cs index 7591cbbd747..a171bf2211b 100644 --- a/src/ui/Features/Shared/BinaryEdit/BinaryAdjustDuration/BinaryAdjustDurationViewModel.cs +++ b/src/ui/Features/Shared/BinaryEdit/BinaryAdjustDuration/BinaryAdjustDurationViewModel.cs @@ -334,5 +334,10 @@ internal void OnKeyDown(KeyEventArgs e) e.Handled = true; Window?.Close(); } + else if (UiUtil.IsHelp(e)) + { + e.Handled = true; + UiUtil.ShowHelp("features/binary-edit", "tools"); + } } } diff --git a/src/ui/Features/Shared/BinaryEdit/BinaryAppendSubtitle/BinaryAppendSubtitleViewModel.cs b/src/ui/Features/Shared/BinaryEdit/BinaryAppendSubtitle/BinaryAppendSubtitleViewModel.cs index bc4e91af583..42e6e115e38 100644 --- a/src/ui/Features/Shared/BinaryEdit/BinaryAppendSubtitle/BinaryAppendSubtitleViewModel.cs +++ b/src/ui/Features/Shared/BinaryEdit/BinaryAppendSubtitle/BinaryAppendSubtitleViewModel.cs @@ -2,6 +2,7 @@ using Avalonia.Input; using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; +using Nikse.SubtitleEdit.Logic; using Nikse.SubtitleEdit.Logic.Config; using System; @@ -64,5 +65,10 @@ internal void OnKeyDown(KeyEventArgs e) e.Handled = true; Cancel(); } + else if (UiUtil.IsHelp(e)) + { + e.Handled = true; + UiUtil.ShowHelp("features/binary-edit", "tools"); + } } } diff --git a/src/ui/Features/Shared/BinaryEdit/BinaryApplyDurationLimits/BinaryApplyDurationLimitsViewModel.cs b/src/ui/Features/Shared/BinaryEdit/BinaryApplyDurationLimits/BinaryApplyDurationLimitsViewModel.cs index 6c3fec3594e..3fbdb9f28f8 100644 --- a/src/ui/Features/Shared/BinaryEdit/BinaryApplyDurationLimits/BinaryApplyDurationLimitsViewModel.cs +++ b/src/ui/Features/Shared/BinaryEdit/BinaryApplyDurationLimits/BinaryApplyDurationLimitsViewModel.cs @@ -5,6 +5,7 @@ using Avalonia.Input; using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; +using Nikse.SubtitleEdit.Logic; using Nikse.SubtitleEdit.Logic.Config; namespace Nikse.SubtitleEdit.Features.Shared.BinaryEdit.BinaryApplyDurationLimits; @@ -53,6 +54,11 @@ public void OnKeyDown(KeyEventArgs e) e.Handled = true; Ok(); } + else if (UiUtil.IsHelp(e)) + { + e.Handled = true; + UiUtil.ShowHelp("features/binary-edit", "tools"); + } } public void ApplyLimits(List subtitles, List? selectedIndices = null) diff --git a/src/ui/Features/Shared/BinaryEdit/BinaryChangeResolution/BinaryChangeResolutionViewModel.cs b/src/ui/Features/Shared/BinaryEdit/BinaryChangeResolution/BinaryChangeResolutionViewModel.cs index 1e233d26cd5..07196a0bdfc 100644 --- a/src/ui/Features/Shared/BinaryEdit/BinaryChangeResolution/BinaryChangeResolutionViewModel.cs +++ b/src/ui/Features/Shared/BinaryEdit/BinaryChangeResolution/BinaryChangeResolutionViewModel.cs @@ -262,6 +262,11 @@ internal void OnKeyDown(KeyEventArgs e) e.Handled = true; Window?.Close(); } + else if (UiUtil.IsHelp(e)) + { + e.Handled = true; + UiUtil.ShowHelp("features/binary-edit", "tools"); + } } public void OnClosingCleanup() diff --git a/src/ui/Features/Shared/BinaryEdit/BinaryMoveCaptions/BinaryMoveCaptionsViewModel.cs b/src/ui/Features/Shared/BinaryEdit/BinaryMoveCaptions/BinaryMoveCaptionsViewModel.cs index 44d1b1b805b..0299b7ee1a6 100644 --- a/src/ui/Features/Shared/BinaryEdit/BinaryMoveCaptions/BinaryMoveCaptionsViewModel.cs +++ b/src/ui/Features/Shared/BinaryEdit/BinaryMoveCaptions/BinaryMoveCaptionsViewModel.cs @@ -206,5 +206,10 @@ internal void OnKeyDown(KeyEventArgs e) e.Handled = true; Window?.Close(); } + else if (UiUtil.IsHelp(e)) + { + e.Handled = true; + UiUtil.ShowHelp("features/binary-edit", "tools"); + } } } diff --git a/src/ui/Features/Shared/BinaryEdit/BinaryResizeImages/BinaryResizeImagesViewModel.cs b/src/ui/Features/Shared/BinaryEdit/BinaryResizeImages/BinaryResizeImagesViewModel.cs index f4072844b11..15d4f560ba9 100644 --- a/src/ui/Features/Shared/BinaryEdit/BinaryResizeImages/BinaryResizeImagesViewModel.cs +++ b/src/ui/Features/Shared/BinaryEdit/BinaryResizeImages/BinaryResizeImagesViewModel.cs @@ -190,6 +190,11 @@ internal void OnKeyDown(KeyEventArgs e) e.Handled = true; Window?.Close(); } + else if (UiUtil.IsHelp(e)) + { + e.Handled = true; + UiUtil.ShowHelp("features/binary-edit", "tools"); + } } /// diff --git a/src/ui/Features/Shared/BinaryEdit/BinarySettings/BinarySettingsViewModel.cs b/src/ui/Features/Shared/BinaryEdit/BinarySettings/BinarySettingsViewModel.cs index 1596e52da49..f905022e852 100644 --- a/src/ui/Features/Shared/BinaryEdit/BinarySettings/BinarySettingsViewModel.cs +++ b/src/ui/Features/Shared/BinaryEdit/BinarySettings/BinarySettingsViewModel.cs @@ -2,6 +2,7 @@ using Avalonia.Input; using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; +using Nikse.SubtitleEdit.Logic; using Nikse.SubtitleEdit.Logic.Config; using System.Threading.Tasks; @@ -98,5 +99,10 @@ internal void OnKeyDown(KeyEventArgs e) e.Handled = true; Window?.Close(); } + else if (UiUtil.IsHelp(e)) + { + e.Handled = true; + UiUtil.ShowHelp("features/binary-edit", "tools"); + } } } diff --git a/src/ui/Features/Shared/BinaryEdit/SetText/SetTextWindow.cs b/src/ui/Features/Shared/BinaryEdit/SetText/SetTextWindow.cs index 50e5c7ddef5..9c357613fc9 100644 --- a/src/ui/Features/Shared/BinaryEdit/SetText/SetTextWindow.cs +++ b/src/ui/Features/Shared/BinaryEdit/SetText/SetTextWindow.cs @@ -58,6 +58,11 @@ public SetTextWindow(SetTextViewModel vm) vm.CancelCommand.Execute(null); e.Handled = true; } + else if (UiUtil.IsHelp(e)) + { + e.Handled = true; + UiUtil.ShowHelp("features/binary-edit", "tools"); + } }; Closing += (_, _) => vm.Closing(); } diff --git a/src/ui/Features/Shared/Bookmarks/BookmarksListViewModel.cs b/src/ui/Features/Shared/Bookmarks/BookmarksListViewModel.cs index 7a78e29924e..ffae7a70ba9 100644 --- a/src/ui/Features/Shared/Bookmarks/BookmarksListViewModel.cs +++ b/src/ui/Features/Shared/Bookmarks/BookmarksListViewModel.cs @@ -113,6 +113,11 @@ internal void OnKeyDown(KeyEventArgs e) e.Handled = true; Window?.Close(); } + else if (UiUtil.IsHelp(e)) + { + e.Handled = true; + UiUtil.ShowHelp("features/subtitle-grid", "bookmarks"); + } } internal void Initialize(List subtitleLineViewModels) diff --git a/src/ui/Features/Shared/ColumnPaste/ColumnPasteViewModel.cs b/src/ui/Features/Shared/ColumnPaste/ColumnPasteViewModel.cs index 9856e60ae04..9e2b121d4ff 100644 --- a/src/ui/Features/Shared/ColumnPaste/ColumnPasteViewModel.cs +++ b/src/ui/Features/Shared/ColumnPaste/ColumnPasteViewModel.cs @@ -3,6 +3,7 @@ using Avalonia.Threading; using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; +using Nikse.SubtitleEdit.Logic; namespace Nikse.SubtitleEdit.Features.Shared.ColumnPaste; @@ -69,5 +70,10 @@ internal void KeyDown(object? sender, KeyEventArgs e) { Close(); } + else if (UiUtil.IsHelp(e)) + { + e.Handled = true; + UiUtil.ShowHelp("features/subtitle-grid", "column-paste"); + } } } diff --git a/src/ui/Features/Shared/CommandPalette/CommandPaletteViewModel.cs b/src/ui/Features/Shared/CommandPalette/CommandPaletteViewModel.cs index 2661e70fd97..53cb076799b 100644 --- a/src/ui/Features/Shared/CommandPalette/CommandPaletteViewModel.cs +++ b/src/ui/Features/Shared/CommandPalette/CommandPaletteViewModel.cs @@ -2,6 +2,7 @@ using Avalonia.Input; using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; +using Nikse.SubtitleEdit.Logic; using System; using System.Collections.Generic; using System.Collections.ObjectModel; @@ -176,6 +177,13 @@ private void Cancel() public void OnKeyDown(KeyEventArgs e) { + if (UiUtil.IsHelp(e)) + { + e.Handled = true; + UiUtil.ShowHelp("features/command-palette"); + return; + } + switch (e.Key) { case Key.Escape: diff --git a/src/ui/Features/Shared/OpenOriginalMismatch/OpenOriginalMismatchViewModel.cs b/src/ui/Features/Shared/OpenOriginalMismatch/OpenOriginalMismatchViewModel.cs index e07b7ff4f0f..52643a8bf50 100644 --- a/src/ui/Features/Shared/OpenOriginalMismatch/OpenOriginalMismatchViewModel.cs +++ b/src/ui/Features/Shared/OpenOriginalMismatch/OpenOriginalMismatchViewModel.cs @@ -2,6 +2,7 @@ using Avalonia.Input; using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; +using Nikse.SubtitleEdit.Logic; using Nikse.SubtitleEdit.Logic.Config; namespace Nikse.SubtitleEdit.Features.Shared.OpenOriginalMismatch; @@ -125,5 +126,10 @@ internal void OnKeyDown(KeyEventArgs e) e.Handled = true; Ok(); } + else if (UiUtil.IsHelp(e)) + { + e.Handled = true; + UiUtil.ShowHelp("features/main-window", "when-the-original-does-not-line-up-11"); + } } } diff --git a/src/ui/Features/Shared/SetVideoOffset/SetVideoOffsetViewModel.cs b/src/ui/Features/Shared/SetVideoOffset/SetVideoOffsetViewModel.cs index 185cbef741e..d0aabe673d9 100644 --- a/src/ui/Features/Shared/SetVideoOffset/SetVideoOffsetViewModel.cs +++ b/src/ui/Features/Shared/SetVideoOffset/SetVideoOffsetViewModel.cs @@ -6,6 +6,7 @@ using Avalonia.Input; using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; +using Nikse.SubtitleEdit.Logic; using Nikse.SubtitleEdit.Logic.Config; namespace Nikse.SubtitleEdit.Features.Shared.SetVideoOffset; @@ -102,6 +103,11 @@ public void OnKeyDown(KeyEventArgs e) e.Handled = true; // the OK button is IsDefault and would run OK again on the same Enter Ok(); } + else if (UiUtil.IsHelp(e)) + { + e.Handled = true; + UiUtil.ShowHelp("features/video-player", "video-menu-options"); + } } private void ApplyCurrentOffset() diff --git a/src/ui/Features/Sync/VisualSync/ManualSyncViewModel.cs b/src/ui/Features/Sync/VisualSync/ManualSyncViewModel.cs index 7d7e292ccee..e15d7ba7682 100644 --- a/src/ui/Features/Sync/VisualSync/ManualSyncViewModel.cs +++ b/src/ui/Features/Sync/VisualSync/ManualSyncViewModel.cs @@ -47,7 +47,7 @@ internal void OnKeyDown(KeyEventArgs e) else if (UiUtil.IsHelp(e)) { e.Handled = true; - UiUtil.ShowHelp("features/change-speed"); + UiUtil.ShowHelp("features/visual-sync"); } } diff --git a/src/ui/Features/Tools/MergeContinuationLines/MergeContinuationLinesViewModel.cs b/src/ui/Features/Tools/MergeContinuationLines/MergeContinuationLinesViewModel.cs index a0036ffeb09..811c6755a93 100644 --- a/src/ui/Features/Tools/MergeContinuationLines/MergeContinuationLinesViewModel.cs +++ b/src/ui/Features/Tools/MergeContinuationLines/MergeContinuationLinesViewModel.cs @@ -183,6 +183,11 @@ internal void KeyDown(object? sender, KeyEventArgs e) e.Handled = true; Window?.Close(); } + else if (UiUtil.IsHelp(e)) + { + e.Handled = true; + UiUtil.ShowHelp("features/merge-continuation-lines"); + } } internal void Loaded() diff --git a/src/ui/Features/Translate/TranslationErrorViewModel.cs b/src/ui/Features/Translate/TranslationErrorViewModel.cs index bb21bcfd227..cefca26f04e 100644 --- a/src/ui/Features/Translate/TranslationErrorViewModel.cs +++ b/src/ui/Features/Translate/TranslationErrorViewModel.cs @@ -2,6 +2,7 @@ using Avalonia.Input; using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; +using Nikse.SubtitleEdit.Logic; using Nikse.SubtitleEdit.Logic.Config; namespace Nikse.SubtitleEdit.Features.Translate; @@ -62,5 +63,10 @@ public void KeyDown(KeyEventArgs e) e.Handled = true; // the OK button is IsDefault and would run OK again on the same Enter Ok(); } + else if (UiUtil.IsHelp(e)) + { + e.Handled = true; + UiUtil.ShowHelp("features/auto-translate"); + } } } diff --git a/src/ui/Features/Video/BackgroundMusic/BackgroundMusicViewModel.cs b/src/ui/Features/Video/BackgroundMusic/BackgroundMusicViewModel.cs index d3e551e90bb..f0d45ff4298 100644 --- a/src/ui/Features/Video/BackgroundMusic/BackgroundMusicViewModel.cs +++ b/src/ui/Features/Video/BackgroundMusic/BackgroundMusicViewModel.cs @@ -772,6 +772,11 @@ internal void OnKeyDown(KeyEventArgs e) e.Handled = true; Cancel(); } + else if (UiUtil.IsHelp(e)) + { + e.Handled = true; + UiUtil.ShowHelp("features/background-music"); + } } private static void TryDelete(string fileName) diff --git a/src/ui/Features/Video/OpenFromUrl/DownloadVideoFromUrlViewModel.cs b/src/ui/Features/Video/OpenFromUrl/DownloadVideoFromUrlViewModel.cs index b4fa28b4b18..bf91596e239 100644 --- a/src/ui/Features/Video/OpenFromUrl/DownloadVideoFromUrlViewModel.cs +++ b/src/ui/Features/Video/OpenFromUrl/DownloadVideoFromUrlViewModel.cs @@ -387,5 +387,10 @@ internal void OnKeyDown(KeyEventArgs e) e.Handled = true; CommandCancel(); } + else if (UiUtil.IsHelp(e)) + { + e.Handled = true; + UiUtil.ShowHelp("features/video-player", "opening-video"); + } } } diff --git a/src/ui/Features/Video/OpenFromUrl/PickOnlineSubtitle/PickOnlineSubtitleViewModel.cs b/src/ui/Features/Video/OpenFromUrl/PickOnlineSubtitle/PickOnlineSubtitleViewModel.cs index 3a48eccaded..bcbaef8befe 100644 --- a/src/ui/Features/Video/OpenFromUrl/PickOnlineSubtitle/PickOnlineSubtitleViewModel.cs +++ b/src/ui/Features/Video/OpenFromUrl/PickOnlineSubtitle/PickOnlineSubtitleViewModel.cs @@ -220,6 +220,11 @@ internal void OnKeyDownHandler(object? sender, KeyEventArgs e) Ok(); e.Handled = true; } + else if (UiUtil.IsHelp(e)) + { + e.Handled = true; + UiUtil.ShowHelp("features/video-player", "opening-video"); + } } /// diff --git a/src/ui/Features/Video/TextToSpeech/EncodingSettings/EncodingSettingsViewModel.cs b/src/ui/Features/Video/TextToSpeech/EncodingSettings/EncodingSettingsViewModel.cs index 2acdcf52b45..ad248d7330d 100644 --- a/src/ui/Features/Video/TextToSpeech/EncodingSettings/EncodingSettingsViewModel.cs +++ b/src/ui/Features/Video/TextToSpeech/EncodingSettings/EncodingSettingsViewModel.cs @@ -75,7 +75,7 @@ internal void OnKeyDown(KeyEventArgs e) else if (UiUtil.IsHelp(e)) { e.Handled = true; - UiUtil.ShowHelp("features/text-to-speech", "exporting-clips"); + UiUtil.ShowHelp("features/text-to-speech", "how-to-use"); } } } \ No newline at end of file diff --git a/src/ui/Features/Video/TextToSpeech/SpeakFromLine/SpeakFromLineViewModel.cs b/src/ui/Features/Video/TextToSpeech/SpeakFromLine/SpeakFromLineViewModel.cs index 29bad10a290..86837d145df 100644 --- a/src/ui/Features/Video/TextToSpeech/SpeakFromLine/SpeakFromLineViewModel.cs +++ b/src/ui/Features/Video/TextToSpeech/SpeakFromLine/SpeakFromLineViewModel.cs @@ -258,5 +258,10 @@ internal void OnKeyDown(KeyEventArgs e) e.Handled = true; Window?.Close(); } + else if (UiUtil.IsHelp(e)) + { + e.Handled = true; + UiUtil.ShowHelp("features/text-to-speech", IsPlayWithSpeech ? "play-with-speech" : "speak-from-current-line"); + } } } diff --git a/src/ui/Features/Video/TextToSpeech/VoiceManager/VoicePacks/DownloadVoicePacksViewModel.cs b/src/ui/Features/Video/TextToSpeech/VoiceManager/VoicePacks/DownloadVoicePacksViewModel.cs index e7a72c47818..6a4a1038e99 100644 --- a/src/ui/Features/Video/TextToSpeech/VoiceManager/VoicePacks/DownloadVoicePacksViewModel.cs +++ b/src/ui/Features/Video/TextToSpeech/VoiceManager/VoicePacks/DownloadVoicePacksViewModel.cs @@ -264,6 +264,11 @@ internal void OnKeyDown(KeyEventArgs e) e.Handled = true; Cancel(); } + else if (UiUtil.IsHelp(e)) + { + e.Handled = true; + UiUtil.ShowHelp("features/text-to-speech", "voice-manager"); + } } internal void OnClosing() diff --git a/src/ui/Logic/UiUtil.cs b/src/ui/Logic/UiUtil.cs index dd1af44c839..fc40e72c17d 100644 --- a/src/ui/Logic/UiUtil.cs +++ b/src/ui/Logic/UiUtil.cs @@ -3603,7 +3603,7 @@ public static void RestoreWindowPosition(Window? window) public static void ShowHelp(string helpName, string section = "") { - var helpUrl = $"http://subtitleedit.github.io/subtitleedit/{helpName}.html"; + var helpUrl = $"https://subtitleedit.github.io/subtitleedit/{helpName}.html"; if (!string.IsNullOrEmpty(section)) { helpUrl += $"#{section}"; @@ -3614,7 +3614,7 @@ public static void ShowHelp(string helpName, string section = "") public static void ShowHelp() { - OpenUrl("http://subtitleedit.github.io/subtitleedit"); + OpenUrl("https://subtitleedit.github.io/subtitleedit"); } public static void OpenUrl(string url) From cffce53f43dc585a5823a6fa08c508ee9245d1a6 Mon Sep 17 00:00:00 2001 From: niksedk Date: Sun, 11 Oct 2026 17:28:23 +0200 Subject: [PATCH 067/306] Docs: add the 11 missing screenshots Co-Authored-By: Claude Opus 5.5 --- docs/features/assisted-split.md | 4 ++-- docs/features/auto-translate-advanced.md | 2 +- docs/features/background-music.md | 2 +- docs/features/binary-edit.md | 2 +- docs/features/command-palette.md | 2 +- docs/features/file.md | 2 +- docs/features/list-errors.md | 2 +- docs/features/merge-continuation-lines.md | 2 +- docs/features/remove-unicode-characters.md | 2 +- docs/features/remux-video.md | 2 +- docs/screenshots/README.md | 18 ++++++++++++++++-- docs/screenshots/assisted-move.png | Bin 0 -> 29414 bytes docs/screenshots/assisted-split.png | Bin 0 -> 29320 bytes docs/screenshots/auto-translate-advanced.png | Bin 0 -> 29391 bytes docs/screenshots/background-music.png | Bin 0 -> 22971 bytes docs/screenshots/binary-edit.png | Bin 0 -> 37320 bytes docs/screenshots/command-palette.png | Bin 0 -> 8191 bytes docs/screenshots/import-dvd.png | Bin 0 -> 16417 bytes docs/screenshots/list-errors.png | Bin 0 -> 33390 bytes docs/screenshots/merge-continuation-lines.png | Bin 0 -> 20394 bytes .../screenshots/remove-unicode-characters.png | Bin 0 -> 12495 bytes docs/screenshots/remux-video.png | Bin 0 -> 21825 bytes 22 files changed, 27 insertions(+), 13 deletions(-) create mode 100644 docs/screenshots/assisted-move.png create mode 100644 docs/screenshots/assisted-split.png create mode 100644 docs/screenshots/auto-translate-advanced.png create mode 100644 docs/screenshots/background-music.png create mode 100644 docs/screenshots/binary-edit.png create mode 100644 docs/screenshots/command-palette.png create mode 100644 docs/screenshots/import-dvd.png create mode 100644 docs/screenshots/list-errors.png create mode 100644 docs/screenshots/merge-continuation-lines.png create mode 100644 docs/screenshots/remove-unicode-characters.png create mode 100644 docs/screenshots/remux-video.png diff --git a/docs/features/assisted-split.md b/docs/features/assisted-split.md index e268613b6b7..8c8979e4a87 100644 --- a/docs/features/assisted-split.md +++ b/docs/features/assisted-split.md @@ -11,7 +11,7 @@ The previews are produced by running the real split or move on a copy of the lin ## Assisted Split - +![Assisted Split](../screenshots/assisted-split.png) Where **Split line** cuts the selected line at the middle (or at the text cursor), assisted split shows the sensible cut points first. @@ -42,7 +42,7 @@ Applying a card uses the same rules as **Split line**: the time codes are divide ## Assisted Move - +![Assisted Move](../screenshots/assisted-move.png) Moves words across the boundary between the selected line and the previous or next line, or between the two lines of the selected subtitle. The list is context aware: word moves across a boundary are only offered when the sentence actually continues across it, so words are never pushed into a line that starts a new sentence or a new dialog. diff --git a/docs/features/auto-translate-advanced.md b/docs/features/auto-translate-advanced.md index 1b64713ad9c..2106c8085e7 100644 --- a/docs/features/auto-translate-advanced.md +++ b/docs/features/auto-translate-advanced.md @@ -10,7 +10,7 @@ See [Auto Translate](auto-translate.md) for the translation window itself. With an advanced engine selected, click **Advanced...** in the engine settings row. - +![Advanced Settings](../screenshots/auto-translate-advanced.png) ### Context diff --git a/docs/features/background-music.md b/docs/features/background-music.md index 03717ddb5b7..495cca2ed41 100644 --- a/docs/features/background-music.md +++ b/docs/features/background-music.md @@ -5,7 +5,7 @@ Generate royalty-free instrumental background music from a text prompt, loop it - **Menu:** Video → More → Generate background music... - **Shortcut:** None (menu only) - +![Generate Background Music](../screenshots/background-music.png) The music is made on your own computer by **ACE-Step 1.5 Turbo** (MIT license) running on the audio.cpp runtime - nothing is sent to an online service. The **More** submenu is shown while a video is loaded, and ffmpeg is required (you are offered to download it when it is missing). diff --git a/docs/features/binary-edit.md b/docs/features/binary-edit.md index 28bf96b9ded..e5952a89ea1 100644 --- a/docs/features/binary-edit.md +++ b/docs/features/binary-edit.md @@ -6,7 +6,7 @@ Edit image-based subtitles — Blu-ray SUP, VobSub, DVB, BDN XML — directly, w - **Also:** The **Edit/export...** button in the [OCR window](ocr.md) opens the loaded subtitle here - **Shortcut:** Configurable (no default) - +![Edit Image-Based Subtitle](../screenshots/binary-edit.png) ## Supported Files diff --git a/docs/features/command-palette.md b/docs/features/command-palette.md index 58054f60687..9d45922b453 100644 --- a/docs/features/command-palette.md +++ b/docs/features/command-palette.md @@ -5,7 +5,7 @@ Find and run any main-window command by typing part of its name, without hunting - **Menu:** Edit → Command palette... - **Shortcut:** `Ctrl+Shift+K` (`Cmd+Shift+K` on macOS), configurable - +![Command Palette](../screenshots/command-palette.png) The palette lists the same commands as [Options → Shortcuts](shortcuts.md), so commands that have no menu entry and no shortcut can still be reached from the keyboard. Each row shows the command name, its group (File, Edit, Tools, Video, ...) and its current shortcut, if it has one. diff --git a/docs/features/file.md b/docs/features/file.md index 03af24df13c..1c44bb2a8a8 100644 --- a/docs/features/file.md +++ b/docs/features/file.md @@ -121,7 +121,7 @@ Rip the subtitles of a DVD title straight from its IFO and VOB files, then [OCR] - **Menu:** File → Import → Subtitles from DVD (IFO/VOB)... - +![Import Subtitles from DVD](../screenshots/import-dvd.png) 1. Click the folder button next to **IFO file** and open the title set's IFO file (for example `VIDEO_TS\VTS_01_0.IFO`), or click **Add VOB files** - or drop IFO/VOB files on the window 2. With an IFO, pick the **Title**. Each title shows its duration, number of chapters and subtitle languages; the longest complete title is selected. Opening `VIDEO_TS.IFO` lists the titles of every title set. A title whose VOB files are not all there is marked "VOB files missing (x% found)" diff --git a/docs/features/list-errors.md b/docs/features/list-errors.md index 8a30d3fcdff..3107f0f4048 100644 --- a/docs/features/list-errors.md +++ b/docs/features/list-errors.md @@ -5,7 +5,7 @@ List every line that breaks a rule - reading speed, duration, line length, overl - **Menu:** Tools → List errors... - **Shortcut:** Ctrl+F8 (Cmd+F8 on macOS) - +![List Errors](../screenshots/list-errors.png) The checks are the same ones that color cells in the [subtitle grid](subtitle-grid.md): which checks run is set by the **Syntax coloring** switches in [Settings](settings.md#syntax-coloring), and their limits come from the active rules profile in **Settings → Rules**. diff --git a/docs/features/merge-continuation-lines.md b/docs/features/merge-continuation-lines.md index a2de9566188..770de388ad8 100644 --- a/docs/features/merge-continuation-lines.md +++ b/docs/features/merge-continuation-lines.md @@ -5,7 +5,7 @@ Merge subtitles where one sentence has been split across two or more consecutive - **Menu:** Tools → Merge continuation lines... - **Shortcut:** Configurable (no default) - +![Merge Continuation Lines](../screenshots/merge-continuation-lines.png) Unlike [Merge Short Lines](merge-short-lines.md), which merges by length, this tool looks at punctuation: a line that does not end a sentence is joined with the line after it. This is useful for subtitles made by speech recognition or for scripts that will be read by [Text to Speech](text-to-speech.md), where a sentence spoken as one unit sounds more natural. diff --git a/docs/features/remove-unicode-characters.md b/docs/features/remove-unicode-characters.md index db0b0b92f4a..df3867ccc88 100644 --- a/docs/features/remove-unicode-characters.md +++ b/docs/features/remove-unicode-characters.md @@ -5,7 +5,7 @@ Find every character outside Latin-1 (code point above 255) in the subtitle and - **Menu:** Tools → Remove/replace Unicode characters... - **Shortcut:** None (menu only) - +![Remove/Replace Unicode Characters](../screenshots/remove-unicode-characters.png) Typical targets are typographic quotes and apostrophes, the `…` ellipsis, em and en dashes, music notes and emoji - characters that some players, encoders or delivery formats cannot show. Accented Latin-1 letters such as `é`, `ü` and `ñ` are below the limit and are not listed. diff --git a/docs/features/remux-video.md b/docs/features/remux-video.md index 71d744e132f..1e217015dd2 100644 --- a/docs/features/remux-video.md +++ b/docs/features/remux-video.md @@ -5,7 +5,7 @@ Repackage the video with new audio tracks and optional soft subtitles into an MP - **Menu:** Video → More → Remux video... - **Shortcut:** None (menu only) - +![Remux Video](../screenshots/remux-video.png) Remuxing copies the streams into a new container, so it is quick and lossless. Use it to attach a dubbed or translated audio track (for example from [Text to Speech](text-to-speech.md)), to add subtitle tracks that can be switched on and off in the player, to embed CEA-608 closed captions, or to convert between MP4, MKV, MOV and MPG. To draw the subtitle into the picture instead, see [Burn-In Subtitles](burn-in.md). diff --git a/docs/screenshots/README.md b/docs/screenshots/README.md index ab7fa318fac..9dcef9269af 100644 --- a/docs/screenshots/README.md +++ b/docs/screenshots/README.md @@ -27,6 +27,7 @@ All listed files exist on disk. Filenames are stable and may be linked from `../ - `file-menu.png` — File menu - `compare.png` — Compare window - `import-plain-text.png` — Import plain text window +- `import-dvd.png` — Import subtitles from DVD window - `export-image-based.png` — Export image based window ## Edit @@ -38,6 +39,8 @@ All listed files exist on disk. Filenames are stable and may be linked from `../ - `modify-selection.png` — Modify selection window - `show-history.png` — Show history window - `ai-assistant.png` — AI assistant window +- `assisted-split.png` — Assisted split window +- `assisted-move.png` — Assisted move window ## Tools @@ -65,6 +68,10 @@ All listed files exist on disk. Filenames are stable and may be linked from `../ - `renumber.png` — Renumber window - `remove-text-hi.png` — Remove text for hearing impaired window - `ai-review.png` - AI review window +- `list-errors.png` — List errors window +- `merge-continuation-lines.png` — Merge continuation lines window +- `remove-unicode-characters.png` — Remove/replace Unicode characters window +- `improve-time-codes.png` — Improve time codes window ## Synchronization @@ -91,11 +98,14 @@ All listed files exist on disk. Filenames are stable and may be linked from `../ - `re-encode-video.png` — Re-encode video window - `video-ocr.png` — Video OCR window - `chapters.png` — Chapters window +- `remux-video.png` — Remux video window +- `background-music.png` — Generate background music window ## Translation - `auto-translate.png` — Auto translate window - `copy-paste-translate.png` — Copy/paste translate window +- `auto-translate-advanced.png` — Advanced settings for the advanced local LLM translate engines ## Spell Check @@ -111,6 +121,7 @@ All listed files exist on disk. Filenames are stable and may be linked from `../ - `ocr.png` — OCR window - `ocr-preprocessing.png` — OCR pre-processing window +- `binary-edit.png` — Edit image-based subtitle window ## ASSA — Tool Windows @@ -118,6 +129,8 @@ All listed files exist on disk. Filenames are stable and may be linked from `../ - `assa-properties.png` — ASSA properties window - `assa-attachments.png` — ASSA attachments window - `assa-draw.png` — ASSA draw window +- `assa-draw-shape-library.png` — ASSA draw shape library +- `assa-draw-context-menu.png` — ASSA draw right-click menu - `assa-set-position.png` — ASSA set position window - `assa-set-background.png` — ASSA set background window - `assa-progress-bar.png` — ASSA progress bar window @@ -152,6 +165,7 @@ Used by `../reference/assa-override-tags.md` to illustrate individual override t - `subtitle-grid-column-menu.png` — Subtitle grid column menu - `text-editor.png` — Text editor standalone - `statistics.png` — Statistics window +- `command-palette.png` — Command palette with a search typed ## Options @@ -161,9 +175,9 @@ Used by `../reference/assa-override-tags.md` to illustrate individual override t ## Counts -- Top-level screenshots: **96** +- Top-level screenshots: **110** - ASSA override-tag examples (`assa/`): **35** -- Total: **131** +- Total: **145** ## How the dialog screenshots are made diff --git a/docs/screenshots/assisted-move.png b/docs/screenshots/assisted-move.png new file mode 100644 index 0000000000000000000000000000000000000000..86a2ee0360f91eda5e3d3abfd2375d762e467a96 GIT binary patch literal 29414 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%0A)w3VCl&Y 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�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 Yyr1R~U9tu84U;GeGOE&*k|rVl0nZy50{{R3 literal 0 HcmV?d00001 diff --git a/docs/screenshots/auto-translate-advanced.png b/docs/screenshots/auto-translate-advanced.png new file mode 100644 index 0000000000000000000000000000000000000000..b0ba23ce6fe9b59fe88e48199367f2f90fcba962 GIT binary patch literal 29391 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 literal 0 HcmV?d00001 diff --git a/docs/screenshots/binary-edit.png b/docs/screenshots/binary-edit.png new file mode 100644 index 0000000000000000000000000000000000000000..1273c4a3df9d63d842a470288023c4e77d94f070 GIT binary patch literal 37320 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 Z*Eb)qbjXul3#{%LJYD@<);T3K0RRIgoL>L{ literal 0 HcmV?d00001 diff --git a/docs/screenshots/command-palette.png b/docs/screenshots/command-palette.png new file mode 100644 index 0000000000000000000000000000000000000000..9d977baf2e6baa785f063dbc93ab378ae6bbf117 GIT binary patch literal 8191 zcmch6cU)7;_Akf*57-VuL=i;k1Oe&2cWD6Ku5)6><{ z*WKOS)z#J6+4=3;w~o$kEEe0*(b0~e(MC8+WmG#Xu8 zTwGLCghHW^NMr%JA{SMLKp+YW3kwPg^7HdE3(WG5yjCL|=p$H<#eMqpDK<7XCMM=% za&~l5R&-Kk)aT5|#Egi9^p77uhQ+0Z#wLfvBu7U_M@2wF=}ULXKQO~V`KB?%^PcL>({SeTUlAXdiBcE($d1h z0&3@}Z{wkB?FP1T(XwkLZJo*2KxH?dU|@g zy1F_#IuHm13^3U%pgUR#s9{ zQdCq_P*9MUmzR^1la-Z~hFD5!TS#b`i-F8UG)zS_Om12f--4zHsTm8X81gC`a4YI@ z%IV0+$gs#6b+A>Qth7A|fL6B5G73 z5Mg0qAt50_K|uil0e*gdK0ZEPUS1v^9&T>#7cX9LadB~Saz1_`!@jn$Jb@&gW=}v$5!UejFtLW>joOj#v{#QTNB;5)Z zxY9f1Rwbx1uH-|35|@0~oAuEn%~|xB_yW8Eypc(tS^)HGKJh5vr%gVP$F<)S${q*? zKJj`XC|I)#V&N~2n_wPo&CfTp6-Exo0~4(8L{pus%VM~i2V8sR3uRU)9k`>RWc_lO zewbRIhnTx#N<#F7)ZrUb^&30Gm!&B1z%dE1a zhShSO6;ns?cK9+`x`;CR+5oVhS(7)8I!#4ROH0R9d9 z+o>~n(PTesZR#}9hZz_Ev&_Dq@Aw{OdCtsS6$rD`5`D*Pl|~Hy?Z28!$^X3{3S4wh z;{7D^vEE9FXgr+ookOk#U_(e~AZ^ICFgqiVXgaJ#7BKqODN|H`Gz;Dj!xNrQ{^g{3 zWy0yioToLe1{N;Eqkl85UpVRAxjqVRZPSH`;brJE&+Z(<__N6Ki=;2VQ=-^#(d_J{ z>>Ll)L@2W<;bitIw}RgP3?>Htc|Pm&$0-hDVxMO&m2%Qg#gFpXwNO`bT)OA3-sC&c zFUod^%V?LC$GV=+S2ak8d+c4#Htwcqj02mU_A;)N7Z*fxfHTKxO{+1EhAzP|*C8b) zF5?ZU*)v~_PWe9t>$U6Ag2KCl(L0Z_^0mKSbpR#cqfMa*r5{u`pLTYm^9I6o1^Yd z80Vh2jRyzZTiTYI?0CyaLtgj0IKvxFN;_AGN&?4{4W;nLWb-Q?W0=zEe8O)hR;Ud#(Lds83al=UkruL zrkoGR-c!58O?wRqT~4zcr8GF+`?(t|HP^2p;j&U$izqyk+`kCqF>0wjN6{h+cXp=c ze;0e6cp12CrmnR!7l>FspW7~&Njrbsi*>&?5LDWc5H@P@W80OzT<)X0JjVzjPA4$S z5`As^$I^8@9)hg-3&C5+)2Pq;T+UHmwG*;vRglF$j2fA{Wf;w79a1t0kot&B^@Qsp1EA} z?;hSloX*dN3pvbD9zD6MaMipwU%>Ie@1gJBUTDDS<(+e`5?}M#p1NyX&E0BhW)~-m zi%&K8oc*2-yfO(L06GuMrGoZkwgERx?i2meB`Nt|x(AlS6MvF_>Q&GIM4osc@|S%i z_|riCg^+R7Dzn;K^t>==G-;8u@0ywX+3XUT?BAQTC0c#?UANB^#UGufkY!ZS{}VK{ zBu3@O-TXUOpBZ3OZy`45`!aEk(YmpI-m*9Sj<)mAAK2FgQ@TZVzg0A;Wv|?p*n=o< zjaum{JQ+=<&f8=DY5y4S&AAiz$pi1*d3s$)P&1^0Bh=N@Q~TUj!0_VI0| z-cBbg2@6y$bD3AA(n3Tb{4B|9F!ljE++aS4s~ zF3oX@&FD{JoP~+;mLj+=&0=0MIx)0>$C2l)t&uxgy?X|*O~~ao#d2@6?D|;CaEA)U zjP&6BvM^(;r?w=PImaBaX|xu*pr#4fk{`(>=K&+Q7wTr#ss)9Yt2?4$mMFb(UCT0u zAsWS_W1AokX1xW?FJRlR46vH8CcTVz@rY_egupn5P8cL*54m;Ty9Y{SFE)vPseBt@@0{U$PYXF>GboL9 zI|!ky<%PNq@WvZ(;{Rk6?Vk)J~d?N@BX+*-6AV^eOHl=mUO8J;g?bXUnzXKQ)YC=Z?k3x$;~sote*h=!2*b% z;AYZ4^Z+@GlqfRe9h8La+1)Bb5`3uI&FBrW-}}a=D02(oKX+=^RP za0!=ZSGUQ{fHQNen~T*<5R0<#0KpQEYc^q*;))x|xVMCkiRKiQ`ApxQp}OA{Tv$iNt+H%_x3 zr7YZl8gsA<>y%2)Eiy6l?UH9$}y6$&H6IP;3#Fk3Prsasu7H67G;+>iyhw*g++q zFI3e_Pc-ppwD=hT)&zN9mrUaYD#)_dtR)Tb`DwqLr2_?T6zdDOJjut>!$3Y^u3*q(EV-Cm6 zmLff`YS&8Jfa}b7XOA?rdSj66U_IU08A%Q}@@*rE`Z-pQv_Ich1bzi>m;1-*m-pYM zF;>9f76ZIbo|vpnmh6upXXE)oAVobWtri0-)aoQU0i%rhxRiXY5Vj=n;}k03Wob6l za3e`~JPHmRHmLv6U_QG9Wf|QbNF+zr6*Yri$jWL9VGPRaGjz(jUiv!V-UYQGTdGQ* zx0MaY*|=E7m6RG33K@`%M1L@>4zdm3W@gXocpc)dbB_D~?|X&PDzZ89?Sy8ae1RW_ zH;9XCNm44R7FxaW(~+1F7Q% zxw6tIu%ioU5k(2mDMOYtGir>4kFtabx#FNH%nQa(I0dlJoa^kO5Mx3^&$geEPkL+;EF#cWk>j54^#l>V=VZp7G!0*YQGmDc9V3W!zjGqaQc>&p z95ulPUb+I4JQ)m*nX*pBr*_F`yls%`DD=mv;A($Y;BjxN@E7|mowlI64t9dzcKERU z9XG@3e3A2PV0q4End=u0`|bYGoi`0D4;sp93Zu68v?|NCmP)Sf2sNUP>g$(zTn8=O zG9NVct(C}*^|qfyPCK`qm1M*A&aN0roONI`XR#0eZ~pObkm65|>Fo3i)M(cFG zp|fL+R(e~7cuLLt!r;E6`lvQGkUs0_6)73rwcU zq<;OTpxXfXF#J&|q(?>`KX)od%0kM-Cw5WQ=AMLK_#@Zf!L0Q#S@U;AsCnPP;X&^? zRagfSImS13hS_sG2~(Dm?XT%`?6=BOyVq5T2R{>n!Hkq1_0O}OGRdwe`msOkz6CN? zVa(|m*#@XQ821iuznz&)pOv|BpR965G`gzqR-tt>s)=hz`ZvOlpg$Zp8y`qvxFoZl z!fvzt4#02M|Au^jiD>?5$M9(IpIA3<{%z;x8#P$1+tA}2vyoizM%Ho6vrtZTe9iN) zM%I3jOcoGL=B84BL)!9h&5Th#me%4XIK`}5#Mm1dX+F9w((%^yX6U-r#--qt1E?}?&( z0hD0YuQ~p-m0FUHUU2k16DgW&FKTSeCMh;AeZh5&osI>4%uQ)?O@S5)Y4@v?6i8Q# zTJwq;RR*em=cO7I6;CaBf;RtlQa`aEdett@B7?csMb%=c$Rzw4MemcL6BkJV$YA*L z`b4r%Ed0oP?fH%HH8byecs%p2Wk>b6$c2M_j82)vPWtzH2W3`Jcg1ts8grCTYoRp> zNnaTY!b2*R8E(q6f(k{|Vn1k=Hp!uC^T#3pk4NH8qg)kbq|f{SJub*jgD;@>ntR;7 zFwG~!z-@S;5Z{!vsA9@FwUCLpZ_#Iye1X&V{XIf%jZR5Ad%gaow0l?R$M?zBjfe!q zf2^Y_0(qA6!!-x%>>zMRF+;Jl-~=#Pi7;oON~_ej+9%^+7zdfsoa>$~8Zyi%Sq$2L z;!r!fa{0Cpzet_Bqv>QdH<~X?L{;!8Zjm=qD-RJg%Hb8vG_-6_$1M|%Z$ynn z-x;)x?~iiVmH9G5bgb<=R9-J0v(jcyPb?$4A*|+P%tT25OlKe^eh2qazWsmLgMX9% z_+>lrW~A>T=skw5Sfzx`z9It`!OUs|TR%!$q$eOH9=Ffmrb$5d+4xcL_TvyAHUW73 zq-CTOa$FYx06u+WyH6&f$~wmBSh%^TucOcBXR6l{w!Ij3E|_Ir*e%@Hr?a=JR}*iH zmQiL(>Z^2ql}%>&CSSIhdze4BTpttJX85K8468GBpc_>i*Ukp6Hz7Bb8H{|XfDLx# zr0iWPZN?vl~%yXjv7l zyd23^t{q9W_P-HD>5gi;qKN^l2C>MDmq7;^dlAjC8tsW%K0XKNJYD z2?r4|Qd+L+$7r9gQVpI4m}RF_glW+k7G=N;tKqhXG+nS1LnRM6)H_dP!Dqam$^e__hu;qYW&tmr|OdmR+%a|Rv>@US7xXE&WlbSkGT z=8FPHfXO@~jMF0PY8OH-z|aF~tuVZON2(&fwl(VE$Ez`J)K1V9r48(`3W2C5^7XUk zEYRS{5{AEtHGC)LX}9B*M4B%sUH){2)nT-J%i+RifN67qUQ%SSZvEETdR8I}WrVvT zmtPKEyo>KT&rtfNBI~I8U)dgc=7)SX@A8c2gjP0d^>AcImUSm=GKw!Z%#gzqP~U$j zR4GqD-ddYn8orf+*yE@qwE~EmB(gFySdixU)A0GW;eoYGvcazi zO01}+pt6`?%7{N?>rzII9e1BADcy4)fYFFFN(o>oam9H;huFKBLN762Id3`35CdXy70O@46?7JGz|I z?f{DYp~t^LnD31S`i+wkKfWo% z_?EnY?9V*oe`(c!o_WM;AOsLtF_Dy+#ko?a4Q@UJ$oCMm8)1TDo%ffa20gEM_tL9v z8DN};gw~4s9ufwH# z86%G&a<=`dH%voPYW@y3Kt{{3>*3N#K&w-#PP@xk4~q`AAa)2tKIANAE~_lD^^i|- zB3dP_#~o}}a*HE4Vmyts?7xYl_r2sp^~3Q95)xK9FNdm|r6 z8*gP^_AR!K9y9zmtT$NU9Boa`{s$Wfc(-4!;KFGGKPoBsRF$)%q#DudNi33(SzFqQ z!u|fz_ly{Vv{snue(Xa$6Thd$eRf~`?3@=!O;V~Pfat(vJObqG5#$FNKZhO}&S^n8 zB*ckfau}rmtw)Z_o*C03Qv_b^Orhzy=l>oj)pVV5Eb-{cw-HFFI9W8+7ER5TM&LQ4+DTFOZNnY^2ezL3ZY4vG@LS-R zs4$Uo^X=OvsbCrg0WZpjq(p^v0`gO?^ZM{Ml?XUPTMS{!vPxtUqXdu6Z7NoN?RM1S zeBs!E&>a6KS}Mtg$qQtDvqy0{!0)6>?DE_dk~`az+TRq3IW(`$Ourr z3j*O#5jL{pwDE4A=qOeC^nUYQO`FgoLZ3At8XqM$25^ND{z|g{Q&{zvs5u+Y`7;p@ z{6+qScjG_l_TRVpYxiHNtp8u1`1_xQ|7<<~I4?&};0N&3P7f?Mhzevqrpay^Y1#tfHqJde?p2`zFJQ#s? z9sZ~g5qIp%T|F{y$~Evla`>AMfOLHZe)?d0TzZUHE?t2eOmQDq94Vzwx3zhBWq z^ty1}Ov@RvM4u>qcgJZq*(y&Wi|WB$m;7XPVL^=WswY?u<2DAQ*jkBmDPD+hNpe3< zR&5$o~0jE+2d!zQUU&|`^UI?akdK2HJp9b z8j_5;cYZ0M-yj?Enb0z6R=jm*Hty{#bKT(1bsl9lJutlo=-KMm%%_QBC0d-R(B(FW zw}K9S;8S_&{;k*Vnxfn(+;c>XbuH1@3lSkB54H;p@~_O8lC6X@{3N37PruaT)`~9~ zl9bYdDLe%D<^v3CHOV&ld0!3G@bTisJZUyZ+`ooR!PuHeJC;sC`wTEn5c&O9lBBtl zSzJ-}EOoxFs%N9>%fka6PAI5Md_=Er2e9-l^P4Xw*!fH!WsOYc?TRV)ZLpZ5fE7Ie z`sp@bt3*HFEzrZDnsYMn+c`NO#uq9CtCj~Ju2B2-?LVkNyyTqCy|BJaW$U&8o>x$` zbE})wAC8{jO7b9bn7d!vEeUAW)W9 KmqW{#zW-manQ@Z< literal 0 HcmV?d00001 diff --git a/docs/screenshots/import-dvd.png b/docs/screenshots/import-dvd.png new file mode 100644 index 0000000000000000000000000000000000000000..a04e8081074996aca78c440b704557cbbd1b4a93 GIT binary patch literal 16417 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 IZXEFc00a}l{Qv*} literal 0 HcmV?d00001 diff --git a/docs/screenshots/merge-continuation-lines.png b/docs/screenshots/merge-continuation-lines.png new file mode 100644 index 0000000000000000000000000000000000000000..341a68260c14fe3dccf284a208d9ffe4d55df060 GIT binary patch literal 20394 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 Of%d??-HUbwUi>%hxR~Pr literal 0 HcmV?d00001 diff --git a/docs/screenshots/remove-unicode-characters.png b/docs/screenshots/remove-unicode-characters.png new file mode 100644 index 0000000000000000000000000000000000000000..04a73e569f33ee35b85239892eb4236fde0eb0b7 GIT binary patch literal 12495 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 RpDQ3CNs7ve6u$oe|380aTz~)o literal 0 HcmV?d00001 diff --git a/docs/screenshots/remux-video.png b/docs/screenshots/remux-video.png new file mode 100644 index 0000000000000000000000000000000000000000..a974782cc91dfe8fdd5c99d8d15a8c01b233f919 GIT binary patch literal 21825 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 Date: Sun, 11 Oct 2026 18:16:34 +0200 Subject: [PATCH 068/306] Blu-ray sup: bridge gaps shorter than half a frame The typewriter effect left 1 ms between its steps (issue #15933). On the frame grid such a gap usually vanishes, but when it straddles the middle of a frame the end rounds down and the next start rounds up, so one frame shows nothing: the text flickers, and with a full frame image the video shows through. On the reporter's 206-step sample at 23.976 fps this left 4 empty frames. When the next subtitle starts less than half a frame after the pending ones end, and they would land on different frames, the pending subtitles that end last now stay up until the next one starts. Their display sets are written again with the new end. Co-Authored-By: Claude Opus 5.5 --- src/libse/BluRaySup/BluRaySupPicture.cs | 7 +-- .../Export/ExportHandlerBluRaySup.cs | 38 ++++++++++++++- .../ExportHandlerBluRaySupOverlapTests.cs | 47 +++++++++++++++++++ 3 files changed, 88 insertions(+), 4 deletions(-) diff --git a/src/libse/BluRaySup/BluRaySupPicture.cs b/src/libse/BluRaySup/BluRaySupPicture.cs index 735d63d694a..3d3a24381ca 100644 --- a/src/libse/BluRaySup/BluRaySupPicture.cs +++ b/src/libse/BluRaySup/BluRaySupPicture.cs @@ -1141,9 +1141,10 @@ private static byte[] CreateSupFrame(BluRaySupPicture pic, IList= 0; long pts; long endPts; diff --git a/src/libuilogic/Export/ExportHandlerBluRaySup.cs b/src/libuilogic/Export/ExportHandlerBluRaySup.cs index 9b68f039a97..b224cb8f97c 100644 --- a/src/libuilogic/Export/ExportHandlerBluRaySup.cs +++ b/src/libuilogic/Export/ExportHandlerBluRaySup.cs @@ -44,6 +44,12 @@ private sealed record PendingCue(byte[] Buffer, BluRaySupPicture Picture, SKColo private long _pendingStartPts; private long _pendingEndPts; + /// + /// The pending subtitles' times were changed after made their + /// display sets, so even a single one has to be written again. + /// + private bool _pendingRetimed; + public void WriteHeader(string fileOrFolderName, ImageParameter imageParameter) { _width = imageParameter.ScreenWidth; @@ -78,6 +84,7 @@ public void WriteParagraph(ImageParameter param) startPts < _pendingEndPts && endPts > _pendingStartPts; if (_pending.Count > 0 && !overlapsPending) { + BridgeGapShorterThanHalfAFrame(picture.StartTime, startPts, param.FramesPerSecond); FlushPending(); } @@ -114,7 +121,7 @@ private void FlushPending() try { - if (_pending.Count == 1) + if (_pending.Count == 1 && !_pendingRetimed) { // Nothing to compose - the display sets CreateParagraph made are the file. var buffer = _pending[0].Buffer; @@ -128,9 +135,38 @@ private void FlushPending() finally { _pending.Clear(); + _pendingRetimed = false; } } + /// + /// A gap shorter than half a frame is not a pause anyone means: the typewriter effect left + /// 1 ms between its steps (issue #15933). On the frame grid such a gap usually vanishes, but + /// when it straddles the middle of a frame the end rounds down and the next start rounds up, + /// and that frame shows nothing. The text flickers, and with a full frame image the video + /// shows through. The pending subtitles that end last are kept up until the next one starts. + /// + private void BridgeGapShorterThanHalfAFrame(long nextStartMs, long nextStartPts, double fps) + { + var gapMs = nextStartMs - _pendingEndMs; + if (fps <= 0 || gapMs <= 0 || gapMs >= 500.0 / fps || _pendingEndPts >= nextStartPts) + { + return; + } + + foreach (var cue in _pending) + { + if (cue.Picture.EndTime == _pendingEndMs) + { + cue.Picture.EndTime = nextStartMs; + } + } + + _pendingEndMs = nextStartMs; + _pendingEndPts = nextStartPts; + _pendingRetimed = true; + } + /// /// A subtitle as it sits in the frame, ready to be composed with others. /// diff --git a/tests/libuilogic/Export/ExportHandlerBluRaySupOverlapTests.cs b/tests/libuilogic/Export/ExportHandlerBluRaySupOverlapTests.cs index db72c6402b5..c9f6aa36936 100644 --- a/tests/libuilogic/Export/ExportHandlerBluRaySupOverlapTests.cs +++ b/tests/libuilogic/Export/ExportHandlerBluRaySupOverlapTests.cs @@ -247,6 +247,53 @@ public void LinesTouchingEndToStart_AreNotComposed() Assert.Equal(new[] { 1, 0, 1, 0 }, segments.Where(s => s.Type == Pcs).Select(ObjectCount)); } + /// + /// A gap shorter than half a frame that straddles the middle of a frame: the end rounds down + /// and the next start up, which left one frame with nothing on screen (issue #15933, the + /// typewriter effect's 1 ms gaps). The first line now stays up until the second one starts. + /// + [Fact] + public void GapShorterThanHalfAFrame_AcrossAFrame_LeavesNoEmptyFrame() + { + // At 25 fps 3.019 s rounds to the frame at 3.000 s and 3.021 s to the one at 3.040 s. + var segments = ReadSegments(Export( + Cue(0, 1, 3.019, ExportAlignment.BottomCenter, SKColors.White), + Cue(1, 3.021, 5, ExportAlignment.BottomCenter, SKColors.White))); + + var pcs = segments.Where(s => s.Type == Pcs).ToList(); + Assert.Equal(new long[] { 1000 * 90, 3040 * 90, 3040 * 90, 5000 * 90 }, pcs.Select(p => p.Pts)); + Assert.Equal(new[] { 1, 0, 1, 0 }, pcs.Select(ObjectCount)); + Assert.Single(segments.Where(s => s.Type == Ods && s.Pts == 1000 * 90)); + } + + [Fact] + public void OneMillisecondGap_At23976_LeavesNoEmptyFrame() + { + // Two typewriter steps from the issue #15933 sample, 55.367 s and 55.368 s. + var a = Cue(0, 54.000, 55.367, ExportAlignment.BottomCenter, SKColors.White); + var b = Cue(1, 55.368, 57.000, ExportAlignment.BottomCenter, SKColors.White); + a.FramesPerSecond = 23.976; + b.FramesPerSecond = 23.976; + + var segments = ReadSegments(Export(a, b)); + + var pcs = segments.Where(s => s.Type == Pcs).ToList(); + Assert.Equal(new[] { 1, 0, 1, 0 }, pcs.Select(ObjectCount)); + Assert.Equal(BluRaySupPicture.MillisecondsToPts(55368, 23.976), pcs[1].Pts); + Assert.Equal(pcs[2].Pts, pcs[1].Pts); + } + + [Fact] + public void GapOfHalfAFrameOrMore_IsKept() + { + var segments = ReadSegments(Export( + Cue(0, 1, 3.000, ExportAlignment.BottomCenter, SKColors.White), + Cue(1, 3.020, 5, ExportAlignment.BottomCenter, SKColors.White))); + + var pcs = segments.Where(s => s.Type == Pcs).ToList(); + Assert.Equal(new long[] { 1000 * 90, 3000 * 90, 3040 * 90, 5000 * 90 }, pcs.Select(p => p.Pts)); + } + [Fact] public void LinesOnTopOfEachOther_BecomeOneObject() { From 49c541bb68bfda1a9c167af0df18e3c023a38469 Mon Sep 17 00:00:00 2001 From: niksedk Date: Sun, 11 Oct 2026 18:17:20 +0200 Subject: [PATCH 069/306] Improve time codes / Import plain text: fixes from a real-media review Tested on Friends S01E01-E02 (VideoHelp thread 419443). Improve time codes: - Only a large input overlap (>= 0.5 s and half the shorter line) counts as meant; smaller ones are mistiming and are closed (80-100 survived per episode before, 0-4 now) - Song lyrics, positioned signs ({\pos}/{\move}) and ASSA comments are not aligned as speech - Frame mode: moved lines are snapped to frames, keeping the minimum gap Import plain text: - Ends are held for the maximum reading speed, room permitting (lines over max CPS: 46 -> 17, none of them with room) - Alignment can be cancelled (Cancel/Esc, closing the window); OK and the align buttons are disabled while it runs; ffmpeg output is drained - The warning about lines placed by reading time now shows (EstimatedLines) - Both align modes use the dialog's gap instead of the general setting - Unexpected errors are logged and shown instead of escaping the command Co-Authored-By: Claude Opus 5.5 --- docs/features/import-plain-text.md | 4 +- docs/features/improve-time-codes.md | 8 +- src/ui/Assets/Languages/English.json | 7 +- .../Files/ImportPlainText/ForcedAligner.cs | 39 +++++-- .../ImportPlainTextViewModel.cs | 107 +++++++++++++++--- .../ImportPlainText/ImportPlainTextWindow.cs | 11 +- .../ImportPlainText/ScriptSyncService.cs | 5 +- .../ImproveTimeCodesViewModel.cs | 8 +- .../Tools/ImproveTimeCodes/RoughSync.cs | 6 +- .../Tools/ImproveTimeCodes/SubtitleRetimer.cs | 101 ++++++++++++++++- .../Config/Language/File/LanguageImport.cs | 2 + .../ImportPlainText/SpeechEnvelopeTests.cs | 33 ++++++ .../ImproveTimeCodes/SubtitleRetimerTests.cs | 72 +++++++++++- 13 files changed, 359 insertions(+), 44 deletions(-) diff --git a/docs/features/import-plain-text.md b/docs/features/import-plain-text.md index 0d2152e177c..d0c6f524147 100644 --- a/docs/features/import-plain-text.md +++ b/docs/features/import-plain-text.md @@ -40,7 +40,9 @@ Setup dialog: - **Engine** — Uses the Crisp ASR engine; click **Download / update engine...** if it is not installed yet - **Aligner model** — Pick an alignment model. The wav2vec2 aligners are language-specific and small (~200–300 MB); the Canary CTC and Qwen3 forced aligner models are multilingual -Missing models are downloaded automatically when you press OK. Progress is shown per window and line while aligning. If the script is longer than the speech in the video, the trailing lines cannot be aligned and a warning tells you how many lines were matched. +Missing models are downloaded automatically when you press OK. Progress is shown per window and line while aligning; **Cancel** (or Esc) stops the alignment and leaves the lines as they were, and OK is unavailable until it has finished. If the script is longer than the speech in the video, the lines that cannot be aligned are placed by reading time and a warning tells you how many lines are only estimated. + +Ends are set from reading time (see below), but a line always stays up long enough for the maximum characters per second from Settings → General, as long as that does not run into the next line. Both align buttons use the **Gap (ms)** set in this dialog. #### Set end times from isolated speech diff --git a/docs/features/improve-time-codes.md b/docs/features/improve-time-codes.md index b9e12a8adfb..b15160d49d0 100644 --- a/docs/features/improve-time-codes.md +++ b/docs/features/improve-time-codes.md @@ -56,7 +56,7 @@ One row per subtitle line, with the shift of its start and end in milliseconds a | Heard where it was | Speech-to-text heard the line where it already was, not where the aligner moved it. The aligner's time codes are offered **unticked**. | | Moved with the sync | The subtitle was synced first, and the aligner left this line where the sync put it - a line with no speech, one already in place after the sync, or one the aligner could not place. | | Already in place | The aligner agrees with the current time codes. | -| No speech | Nothing to listen for - blank lines, `[sound descriptions]`, `(sighs)`, `♪`. These lines are never moved. | +| No speech | Nothing to listen for - blank lines, `[sound descriptions]`, `(sighs)`, song lyrics (a line starting or ending with `♪`/`♫`, or wrapped in `#`), positioned signs (`{\pos}`/`{\move}`) and ASSA comments. These lines are never moved by the aligner. | | Kept - shift too large | The start would have moved more than *Max shift*, so the line was left alone. | | Kept - aligner failed | The aligner could not process this group of lines. | @@ -106,6 +106,8 @@ If speech-to-text hears less than 30% of the subtitle's words, the status line a - The start is where the first word of the line begins. - The end is where the last word ends - but a line is never cut shorter than the minimum display time or the optimal reading speed from Settings → General, as long as that does not show it for longer than it used to be shown or run into the next line. -- Overlaps that were in the subtitle on purpose are left alone; the tool never creates new ones. +- A line is held long enough for the maximum reading speed (characters per second) from Settings → General, room permitting. +- Overlaps that were in the subtitle on purpose - at least half a second and half of the shorter line, as with two speakers at once or a sign over dialogue - are left alone. Smaller overlaps are mistiming and are closed, keeping the minimum gap. The tool never creates new ones. +- In frame mode (Settings → General) the moved lines are put on whole frames, keeping the minimum gap. -Run [Beautify time codes](beautify-time-codes.md) afterwards to snap the result to frames and shot changes. +Run [Beautify time codes](beautify-time-codes.md) afterwards to snap the result to shot changes. diff --git a/src/ui/Assets/Languages/English.json b/src/ui/Assets/Languages/English.json index d1592fc2736..57bf534a852 100644 --- a/src/ui/Assets/Languages/English.json +++ b/src/ui/Assets/Languages/English.json @@ -1345,7 +1345,8 @@ "forcedAlignerIsolatingSpeech": "Isolating speech... {0}", "forcedAlignerIsolatingSpeechFailed": "Could not isolate the speech - end times were set from reading time instead.", "forcedAlignerModel": "Aligner model", - "forcedAlignerDownloadEngine": "Download / update engine..." + "forcedAlignerDownloadEngine": "Download / update engine...", + "forcedAlignerEstimatedXOfY": "{0} of {1} lines could not be aligned and were placed by reading time - their time codes are only estimated. The script is probably longer than what is spoken in the video." }, "export": { "exportImagesProfiles": "Export images profiles", @@ -2814,6 +2815,10 @@ "removeOriginalSpeechDescription": "Keeps the music and sound effects of the original video but removes its speech, and mixes the new speech over that. Takes about as long as the video itself with a GPU, and many times longer without one. With audio ducking on, its volume applies to the music and effects. Only applies when the speech is added to the video file.", "removingOriginalSpeech": "Removing original speech...", "removeOriginalSpeechFailed": "Could not remove the original speech - the speech was added to the video without it.", + "speechRemovalModel": "Model", + "speechRemovalModelDescription": "Best quality keeps the most of the music and sound effects. Fast is about three times quicker, with a little more of the music lost.", + "speechRemovalBestQuality": "Best quality (Mel-Band RoFormer)", + "speechRemovalFast": "Fast (HTDemucs)", "videoTruncatedWarning": "The picture in the new video file stops after {0}, but the original video is {1} long - the picture will freeze while the sound continues.\n\nThe file was kept. See error-log.txt in the Subtitle Edit data folder for the ffmpeg output.", "originalVolumePercent": "Original volume %", "vadSilenceCompression": "VAD silence compression", diff --git a/src/ui/Features/Files/ImportPlainText/ForcedAligner.cs b/src/ui/Features/Files/ImportPlainText/ForcedAligner.cs index 8c6025b0903..388d96b5248 100644 --- a/src/ui/Features/Files/ImportPlainText/ForcedAligner.cs +++ b/src/ui/Features/Files/ImportPlainText/ForcedAligner.cs @@ -41,7 +41,11 @@ public interface IAudioSource /// public sealed record Progress(int WindowIndex, int WindowCount, int LinesAligned, int LinesTotal, double Percent); - public sealed record Result(int TotalLines, int AlignedLines) + /// + /// Lines the aligner could not place, laid out by reading time instead - their time codes + /// are a guess. They are not counted in . + /// + public sealed record Result(int TotalLines, int AlignedLines, int EstimatedLines = 0) { public int UnalignedLines => TotalLines - AlignedLines; } @@ -50,17 +54,20 @@ public sealed record Result(int TotalLines, int AlignedLines) private readonly IAudioSource _audio; private readonly ForcedAlignPlanner.Options _options; private readonly SpeechEnvelope? _speechEnvelope; + private readonly double? _minGapMs; /// /// The isolated speech of the same audio, when the caller has it: line ends are then taken /// from where the speech goes quiet instead of from reading time. /// - public ForcedAligner(IRunner runner, IAudioSource audio, ForcedAlignPlanner.Options? options = null, SpeechEnvelope? speechEnvelope = null) + /// The gap between lines; null for the general setting. + public ForcedAligner(IRunner runner, IAudioSource audio, ForcedAlignPlanner.Options? options = null, SpeechEnvelope? speechEnvelope = null, double? minGapMs = null) { _runner = runner; _audio = audio; _options = options ?? new ForcedAlignPlanner.Options(); _speechEnvelope = speechEnvelope; + _minGapMs = minGapMs; } public async Task AlignAsync( @@ -306,8 +313,8 @@ public async Task AlignAsync( aligned = await RefineAsync(aligned, texts, readingCps, estimated, progress, cancellationToken).ConfigureAwait(false); - ApplyTimeCodes(lines, aligned, texts, alignable, _audio.TotalSeconds, _speechEnvelope); - return new Result(allTexts.Count, aligned.Count); + ApplyTimeCodes(lines, aligned, texts, alignable, _audio.TotalSeconds, _speechEnvelope, _minGapMs); + return new Result(allTexts.Count, aligned.Count - estimated.Count, estimated.Count); } /// @@ -329,7 +336,7 @@ public async Task AlignAsync( List<(double Start, double End)> aligned, IReadOnlyList texts, double readingCps, - IReadOnlySet estimated, + HashSet estimated, IProgress? progress, CancellationToken cancellationToken) { @@ -407,6 +414,9 @@ public async Task AlignAsync( Math.Min(newEnd, newStart + (texts[index].Length / readingCps * 2.0))); refined[index] = (newStart, cappedEnd); + + // Measured now, no longer a guess. + estimated.Remove(index); } progress?.Report(new Progress( @@ -516,7 +526,10 @@ private double SnapWindowEnd(double target, IReadOnlyList): reading time for the text, clamped to the - /// configured minimum and maximum display duration. + /// configured minimum and maximum display duration. Speech that is over before the line + /// can be read still leaves the line up long enough for the maximum reading speed, room + /// permitting - ending it with the speech left 46 of a sitcom episode's 414 lines over the + /// maximum, 29 of them with silence to spare after them. /// internal static void ApplyTimeCodes( IList lines, @@ -524,12 +537,14 @@ internal static void ApplyTimeCodes( IReadOnlyList texts, IReadOnlyList alignable, double audioSeconds, - SpeechEnvelope? speechEnvelope = null) + SpeechEnvelope? speechEnvelope = null, + double? minGapMilliseconds = null) { - var minGapMs = Se.Settings.General.MinimumBetweenLines.GetMilliseconds(); + var minGapMs = minGapMilliseconds ?? Se.Settings.General.MinimumBetweenLines.GetMilliseconds(); var minDurationMs = (double)Se.Settings.General.SubtitleMinimumDisplayMilliseconds; var maxDurationMs = (double)Se.Settings.General.SubtitleMaximumDisplayMilliseconds; var optimalCps = Se.Settings.General.SubtitleOptimalCharactersPerSeconds; + var maxCps = Se.Settings.General.SubtitleMaximumCharactersPerSeconds; var previousEndMs = double.NegativeInfinity; for (var i = 0; i < aligned.Count && i < alignable.Count; i++) @@ -553,7 +568,11 @@ internal static void ApplyTimeCodes( // Whichever is shorter: how long the line actually took, or how long it takes // to read. A cue must never outlast the silence the aligner stretched it over. var durationMs = Math.Min(spokenMs > 0 ? spokenMs : readingMs, readingMs); - durationMs = Math.Clamp(durationMs, minDurationMs, maxDurationMs > 0 ? maxDurationMs : durationMs); + + // Never faster than the maximum reading speed - that is a rule, not a wish. + var fastestMs = maxCps > 0 ? (double)texts[i].CountCharacters(true) / maxCps * 1000.0 : 0; + durationMs = Math.Max(durationMs, fastestMs); + durationMs = Math.Clamp(durationMs, minDurationMs, maxDurationMs > 0 ? Math.Max(minDurationMs, maxDurationMs) : Math.Max(minDurationMs, durationMs)); // With the isolated speech at hand the line ends where it actually goes quiet. // Measured against hand-timed subtitles over loud music, with the default minimum @@ -567,7 +586,7 @@ internal static void ApplyTimeCodes( var spokenEnd = speechEnvelope.FindSpeechEnd(startSeconds, startSeconds + (readingMs * 2.0 / 1000.0)); if (spokenEnd.HasValue) { - durationMs = Math.Clamp((spokenEnd.Value * 1000.0) - startMs, minDurationMs, maxDurationMs > 0 ? maxDurationMs : double.MaxValue); + durationMs = Math.Clamp(Math.Max((spokenEnd.Value * 1000.0) - startMs, fastestMs), minDurationMs, maxDurationMs > 0 ? Math.Max(minDurationMs, maxDurationMs) : double.MaxValue); } } diff --git a/src/ui/Features/Files/ImportPlainText/ImportPlainTextViewModel.cs b/src/ui/Features/Files/ImportPlainText/ImportPlainTextViewModel.cs index 60e63580807..57db8389f64 100644 --- a/src/ui/Features/Files/ImportPlainText/ImportPlainTextViewModel.cs +++ b/src/ui/Features/Files/ImportPlainText/ImportPlainTextViewModel.cs @@ -273,6 +273,11 @@ private static List UpdatePreviewText(string splitAtOptio [RelayCommand] private void Ok() { + if (IsAligning) + { + return; + } + // Remember what was picked - reopening the dialog used to come back at "Auto" and the // global default duration every time, while Settings.json carried a dozen SE4-shaped // import keys that nothing read. @@ -291,6 +296,13 @@ private void Ok() [RelayCommand] private void Cancel() { + // Cancel stops a running alignment first - the lines keep the times they had. + if (IsAligning) + { + _alignCancellation?.Cancel(); + return; + } + Close(); } @@ -331,7 +343,7 @@ private async Task AlignScriptWithTimestampsFromWhisper() return; } - var result = ScriptSyncService.SyncScript(Subtitles.ToList(), whisperVm.TranscribedSubtitle); + var result = ScriptSyncService.SyncScript(Subtitles.ToList(), whisperVm.TranscribedSubtitle, MinGapMs); // If more than a quarter of the script couldn't be matched to the transcription, // it's likely the transcription is for the wrong audio / language / model. Tell @@ -403,8 +415,9 @@ private async Task AlignScriptWithForcedAligner() ///

private Process? _isolateSpeechProcess; private volatile bool _windowClosing; + private System.Threading.CancellationTokenSource? _alignCancellation; - private async Task IsolateSpeechEnvelopeAsync(string executable, string audioFileName, string workFolder) + private async Task IsolateSpeechEnvelopeAsync(string executable, string audioFileName, string workFolder, System.Threading.CancellationToken cancellationToken) { try { @@ -441,6 +454,15 @@ private async Task AlignScriptWithForcedAligner() var stopwatch = Stopwatch.StartNew(); while (!process.HasExited) { + if (cancellationToken.IsCancellationRequested) + { +#pragma warning disable CA1416 + process.Kill(true); +#pragma warning restore CA1416 + await process.WaitForExitAsync(); + cancellationToken.ThrowIfCancellationRequested(); + } + var elapsed = new TimeCode(stopwatch.ElapsedMilliseconds).ToShortDisplayString(); AlignProgress = string.Format( Se.Language.File.Import.ForcedAlignerIsolatingSpeech, @@ -470,6 +492,10 @@ private async Task AlignScriptWithForcedAligner() Se.WriteToolsLog($"Forced aligner: speech isolation failed (exit code {process.ExitCode})", true); } + catch (OperationCanceledException) + { + throw; + } catch (Exception exception) { Se.LogError(exception, "Forced aligner: speech isolation failed"); @@ -485,6 +511,8 @@ private async Task RunForcedAlignerAsync(string executable, string alignerPath, var workFolder = Path.Combine(Path.GetTempPath(), "se-forced-align-" + Guid.NewGuid().ToString("N")); Directory.CreateDirectory(workFolder); var audioFileName = Path.Combine(workFolder, "audio.wav"); + _alignCancellation = new System.Threading.CancellationTokenSource(); + var cancellationToken = _alignCancellation.Token; try { @@ -492,7 +520,7 @@ private async Task RunForcedAlignerAsync(string executable, string alignerPath, AlignPercent = 0; AlignProgress = "Extracting audio..."; - if (!await ExtractAudioForAlignmentAsync(audioFileName)) + if (!await ExtractAudioForAlignmentAsync(audioFileName, cancellationToken)) { await MessageBox.Show( Window!, @@ -519,9 +547,9 @@ await MessageBox.Show( using var audio = new FfmpegWindowAudioSource(GetFfmpegPath(), audioFileName, totalSeconds, workFolder); var runner = new CrispAsrAlignOnlyRunner(executable, alignerPath, Se.WriteToolsLog); var speechEnvelope = endsFromIsolatedSpeech - ? await IsolateSpeechEnvelopeAsync(executable, audioFileName, workFolder) + ? await IsolateSpeechEnvelopeAsync(executable, audioFileName, workFolder, cancellationToken) : null; - var forcedAligner = new ForcedAligner(runner, audio, speechEnvelope: speechEnvelope); + var forcedAligner = new ForcedAligner(runner, audio, speechEnvelope: speechEnvelope, minGapMs: MinGapMs); var progress = new Progress(p => Dispatcher.UIThread.Post(() => { @@ -531,23 +559,24 @@ await MessageBox.Show( AlignPercent = p.Percent; })); - var result = await forcedAligner.AlignAsync(lines, progress); + var result = await forcedAligner.AlignAsync(lines, progress, cancellationToken); RefreshSubtitlesFrom(lines); - if (result.UnalignedLines > 0) + if (result.EstimatedLines > 0) { await MessageBox.Show( Window!, Se.Language.General.Warning, - $"Aligned {result.AlignedLines} of {result.TotalLines} lines." - + Environment.NewLine - + $"{result.UnalignedLines} line(s) at the end kept their previous time codes - the script " - + "is probably longer than what is spoken in the video.", + string.Format(Se.Language.File.Import.ForcedAlignerEstimatedXOfY, result.EstimatedLines, result.TotalLines), MessageBoxButtons.OK, MessageBoxIcon.Warning); } } + catch (OperationCanceledException) + { + // Stopped by the user, or the window closed: the lines keep the times they had. + } catch (ForcedAlignerException exception) { await MessageBox.Show( @@ -557,9 +586,25 @@ await MessageBox.Show( MessageBoxButtons.OK, MessageBoxIcon.Error); } + catch (Exception exception) + { + // ffmpeg or the engine could not be started, a temp file could not be written... + Se.LogError(exception, "Forced aligner failed"); + if (!_windowClosing) + { + await MessageBox.Show( + Window!, + Se.Language.General.Error, + exception.Message, + MessageBoxButtons.OK, + MessageBoxIcon.Error); + } + } finally { IsAligning = false; + _alignCancellation?.Dispose(); + _alignCancellation = null; AlignProgress = string.Empty; AlignPercent = 0; TryDeleteFolder(workFolder); @@ -578,12 +623,12 @@ private void RefreshSubtitlesFrom(List lines) } } - private async Task ExtractAudioForAlignmentAsync(string audioFileName) + private async Task ExtractAudioForAlignmentAsync(string audioFileName, System.Threading.CancellationToken cancellationToken) { // 16 kHz mono PCM - what every CTC aligner expects. "aresample=async=1:first_pts=0" keeps // gaps in the audio timestamps as silence, or the aligned times after a gap come out early (#15385). var arguments = - $"-hide_banner -nostats -y -i \"{_videoFileName}\" -vn -af aresample=async=1:first_pts=0 -ar 16000 -ac 1 -acodec pcm_s16le \"{audioFileName}\""; + $"-hide_banner -nostats -loglevel error -y -i \"{_videoFileName}\" -vn -af aresample=async=1:first_pts=0 -ar 16000 -ac 1 -acodec pcm_s16le \"{audioFileName}\""; using var process = new System.Diagnostics.Process { @@ -597,7 +642,30 @@ private async Task ExtractAudioForAlignmentAsync(string audioFileName) }; process.Start(); - await process.WaitForExitAsync(); + + // Read, not just redirected: a full pipe would stall ffmpeg and with it this dialog. + var stdErr = process.StandardError.ReadToEndAsync(); + var stdOut = process.StandardOutput.ReadToEndAsync(); + try + { + await process.WaitForExitAsync(cancellationToken); + } + catch (OperationCanceledException) + { + try + { + process.Kill(true); + await process.WaitForExitAsync(); + } + catch + { + // already gone + } + + throw; + } + + await Task.WhenAll(stdErr, stdOut); return process.ExitCode == 0 && File.Exists(audioFileName); } @@ -715,6 +783,15 @@ public void OnClosingCleanup() // The separation runs for minutes; nothing waits for it once the window is gone. _windowClosing = true; + try + { + _alignCancellation?.Cancel(); + } + catch (ObjectDisposedException) + { + // the alignment just finished + } + try { var process = _isolateSpeechProcess; @@ -735,7 +812,7 @@ internal void KeyDown(object? sender, KeyEventArgs e) { if (e.Key == Key.Escape) { - Close(); + Cancel(); } else if (UiUtil.IsHelp(e)) { diff --git a/src/ui/Features/Files/ImportPlainText/ImportPlainTextWindow.cs b/src/ui/Features/Files/ImportPlainText/ImportPlainTextWindow.cs index 0a6437ca1ee..e63c870bdbd 100644 --- a/src/ui/Features/Files/ImportPlainText/ImportPlainTextWindow.cs +++ b/src/ui/Features/Files/ImportPlainText/ImportPlainTextWindow.cs @@ -106,9 +106,14 @@ public ImportPlainTextWindow(ImportPlainTextViewModel vm) Children = { progressBarAlign, labelAlignProgress }, }; - var buttonAlignViaWhisper = UiUtil.MakeButton(Se.Language.File.Import.AlignViaSpeechToText, vm.AlignScriptWithTimestampsFromWhisperCommand); - var buttonAlignViaForcedAligner = UiUtil.MakeButton(Se.Language.File.Import.AlignViaForcedAligner, vm.AlignScriptWithForcedAlignerCommand); - var buttonOk = UiUtil.MakeButtonOk(vm.OkCommand); + // While aligning, Cancel stops the alignment; nothing else may start or close the window + // with lines the aligner is still writing to. + var buttonAlignViaWhisper = UiUtil.MakeButton(Se.Language.File.Import.AlignViaSpeechToText, vm.AlignScriptWithTimestampsFromWhisperCommand) + .WithBindEnabled(nameof(vm.IsAligning), InverseBooleanConverter.Instance); + var buttonAlignViaForcedAligner = UiUtil.MakeButton(Se.Language.File.Import.AlignViaForcedAligner, vm.AlignScriptWithForcedAlignerCommand) + .WithBindEnabled(nameof(vm.IsAligning), InverseBooleanConverter.Instance); + var buttonOk = UiUtil.MakeButtonOk(vm.OkCommand) + .WithBindEnabled(nameof(vm.IsAligning), InverseBooleanConverter.Instance); var buttonCancel = UiUtil.MakeButtonCancel(vm.CancelCommand); var panelButtons = UiUtil.MakeButtonBar(buttonAlignViaForcedAligner, buttonAlignViaWhisper, buttonOk, buttonCancel); diff --git a/src/ui/Features/Files/ImportPlainText/ScriptSyncService.cs b/src/ui/Features/Files/ImportPlainText/ScriptSyncService.cs index f1d0f5fc623..af92b9c471c 100644 --- a/src/ui/Features/Files/ImportPlainText/ScriptSyncService.cs +++ b/src/ui/Features/Files/ImportPlainText/ScriptSyncService.cs @@ -44,11 +44,12 @@ public readonly record struct SyncResult(int TotalLines, int UnmatchedLines) public int MatchedLines => TotalLines - UnmatchedLines; } - public static SyncResult SyncScript(List scriptLines, Subtitle whisperSubtitle) + /// The gap between lines; null for the general setting. + public static SyncResult SyncScript(List scriptLines, Subtitle whisperSubtitle, double? minGapMilliseconds = null) { var minDurationMs = (double)Se.Settings.General.SubtitleMinimumDisplayMilliseconds; var maxDurationMs = (double)Se.Settings.General.SubtitleMaximumDisplayMilliseconds; - var minGapMs = (double)Se.Settings.General.MinimumBetweenLines.GetMilliseconds(); + var minGapMs = minGapMilliseconds ?? Se.Settings.General.MinimumBetweenLines.GetMilliseconds(); var whisperWords = ExtractWordTimestamps(whisperSubtitle); if (whisperWords.Count == 0) diff --git a/src/ui/Features/Tools/ImproveTimeCodes/ImproveTimeCodesViewModel.cs b/src/ui/Features/Tools/ImproveTimeCodes/ImproveTimeCodesViewModel.cs index fdf0aec7b5f..cb105fca9ac 100644 --- a/src/ui/Features/Tools/ImproveTimeCodes/ImproveTimeCodesViewModel.cs +++ b/src/ui/Features/Tools/ImproveTimeCodes/ImproveTimeCodesViewModel.cs @@ -525,8 +525,9 @@ private async Task Align() MinGapSeconds = MinGapSeconds, }; + // An ASSA comment is not shown, let alone spoken: with no text it is left where it is. var lines = _originalSubtitles - .Select(p => new SubtitleRetimer.Line(p.Text ?? string.Empty, p.StartTime.TotalSeconds, p.EndTime.TotalSeconds)) + .Select(p => new SubtitleRetimer.Line(p.IsComment ? string.Empty : p.Text ?? string.Empty, p.StartTime.TotalSeconds, p.EndTime.TotalSeconds)) .ToList(); // Further out than the aligner may reach: the heard words bring it close enough first. @@ -695,6 +696,11 @@ private void RebuildAligned() } SubtitleRetimer.Settle(original, placements, MinGapSeconds); + if (Se.Settings.General.UseFrameMode) + { + SubtitleRetimer.SnapToFrames(original, placements, Se.Settings.General.CurrentFrameRate, MinGapSeconds); + } + _placed = placements; for (var i = 0; i < count; i++) diff --git a/src/ui/Features/Tools/ImproveTimeCodes/RoughSync.cs b/src/ui/Features/Tools/ImproveTimeCodes/RoughSync.cs index 18538341383..e2edbdf3364 100644 --- a/src/ui/Features/Tools/ImproveTimeCodes/RoughSync.cs +++ b/src/ui/Features/Tools/ImproveTimeCodes/RoughSync.cs @@ -230,12 +230,14 @@ private static double[] Interpolate(IReadOnlyList lines, L var synced = lines.Select((l, i) => l with { StartSeconds = l.StartSeconds + sync.Offsets[i], EndSeconds = l.EndSeconds + sync.Offsets[i] }).ToList(); for (var i = 0; i + 1 < synced.Count; i++) { - var gap = lines[i + 1].StartSeconds - lines[i].EndSeconds; - if (gap < 0) + if (SubtitleRetimer.OverlapsOnPurpose(lines[i], lines[i + 1])) { continue; } + // A small overlap is mistiming, not meant - it is closed rather than carried along. + var gap = Math.Max(0, lines[i + 1].StartSeconds - lines[i].EndSeconds); + var latestEnd = synced[i + 1].StartSeconds - gap; if (synced[i].EndSeconds > latestEnd) { diff --git a/src/ui/Features/Tools/ImproveTimeCodes/SubtitleRetimer.cs b/src/ui/Features/Tools/ImproveTimeCodes/SubtitleRetimer.cs index b228834291f..49f28c0a35f 100644 --- a/src/ui/Features/Tools/ImproveTimeCodes/SubtitleRetimer.cs +++ b/src/ui/Features/Tools/ImproveTimeCodes/SubtitleRetimer.cs @@ -552,14 +552,15 @@ internal static void Tidy(IReadOnlyList lines, LineResult[] results, Optio var start = result.StartSeconds; var end = result.EndSeconds; - // Overlaps the user made on purpose are left alone; only ones introduced here are undone. - if (i > 0 && lines[i].StartSeconds >= lines[i - 1].EndSeconds) + // Overlaps the user made on purpose are left alone; only ones introduced here, or + // small ones that are just mistiming, are undone. + if (i > 0 && !OverlapsOnPurpose(lines[i - 1], lines[i])) { start = Math.Max(start, SettledEnd(lines, results, i - 1) + options.MinGapSeconds); } var room = double.MaxValue; - if (i + 1 < results.Length && lines[i + 1].StartSeconds >= lines[i].EndSeconds) + if (i + 1 < results.Length && !OverlapsOnPurpose(lines[i], lines[i + 1])) { room = SettledStart(lines, results, i + 1) - options.MinGapSeconds; } @@ -665,13 +666,88 @@ private static double SettledEnd(IReadOnlyList lines, LineResult[] results ? results[index].EndSeconds : lines[index].EndSeconds; + /// + /// Whether two neighbouring lines overlapped on purpose in the subtitle as it came in - two + /// speakers at once, or a sign over dialogue. Such an overlap is large: at least half a + /// second and half the shorter line. A smaller one is the mistiming this tool is there to + /// fix (measured on a re-timed episode: 80 such overlaps of 0.2 s median were kept when + /// every input overlap counted as meant), so it is cleared like any other. + /// + internal static bool OverlapsOnPurpose(Line earlier, Line later) + { + var overlap = earlier.EndSeconds - later.StartSeconds; + if (overlap <= 0) + { + return false; + } + + var shorter = Math.Min(earlier.EndSeconds - earlier.StartSeconds, later.EndSeconds - later.StartSeconds); + return overlap >= 0.5 && overlap >= shorter / 2.0; + } + + /// + /// Puts every line that was moved on whole frames, keeping the minimum gap - in frame mode a + /// time between two frames is shown a frame early or late, and a gap of two frames can come + /// out as one. A line left where it was is not touched. + /// + public static void SnapToFrames(IReadOnlyList original, Placement[] placements, double framesPerSecond, double minGapSeconds) + { + if (framesPerSecond < 1) + { + return; + } + + var frame = 1.0 / framesPerSecond; + var moved = new bool[placements.Length]; + for (var i = 0; i < placements.Length; i++) + { + var p = placements[i]; + moved[i] = Math.Abs(p.StartSeconds - original[i].StartSeconds) >= 0.0005 || + Math.Abs(p.EndSeconds - original[i].EndSeconds) >= 0.0005; + if (!moved[i]) + { + continue; + } + + var start = Math.Round(p.StartSeconds * framesPerSecond, MidpointRounding.AwayFromZero) * frame; + var end = Math.Round(p.EndSeconds * framesPerSecond, MidpointRounding.AwayFromZero) * frame; + placements[i] = p with { StartSeconds = start, EndSeconds = Math.Max(end, start + frame) }; + } + + // Rounding each side on its own can take a frame off the gap: the moved earlier line + // ends a frame sooner, never the later one starts a frame later. + var gapFrames = Math.Ceiling((minGapSeconds * framesPerSecond) - 0.01); + for (var i = 1; i < placements.Length; i++) + { + if (!moved[i - 1] || OverlapsOnPurpose(original[i - 1], original[i])) + { + continue; + } + + var previous = placements[i - 1]; + var gap = Math.Round((placements[i].StartSeconds - previous.EndSeconds) * framesPerSecond); + // An overlap still here was left by Settle on purpose (neither line could give way). + if (gap >= gapFrames || gap < 0) + { + continue; + } + + var end = placements[i].StartSeconds - (gapFrames * frame); + if (end >= previous.StartSeconds + frame) + { + placements[i - 1] = previous with { EndSeconds = end }; + } + } + } + /// One line's times on the final timeline, and whether they may still be changed. public record struct Placement(double StartSeconds, double EndSeconds, bool CanMove); /// /// Makes the lines as they will actually be saved - each one moved or not, as the user /// ticked it - keep clear of one another by at least . - /// Lines that overlapped on the way in (two speakers at once) are left overlapping. + /// Lines that overlapped on the way in by a lot (two speakers at once, a sign over + /// dialogue - see ) are left overlapping. /// /// Only lines that may move are changed. An end gives way first: it marks when the line /// comes down, which can be any time after the speech, while the start is where the speech @@ -684,7 +760,7 @@ public static void Settle(IReadOnlyList original, Placement[] placements, const double shortestSeconds = 0.1; for (var i = 1; i < placements.Length; i++) { - if (original[i].StartSeconds < original[i - 1].EndSeconds) + if (OverlapsOnPurpose(original[i - 1], original[i])) { continue; } @@ -728,7 +804,22 @@ internal static string GetSpokenText(string? text) return string.Empty; } + // Positioned text is a sign or a caption on screen, not speech. + if (text.Contains("\\pos(", StringComparison.Ordinal) || text.Contains("\\move(", StringComparison.Ordinal)) + { + return string.Empty; + } + var s = HtmlUtil.RemoveHtmlTags(text, true); + + // Song lyrics: sung words are no use to a speech aligner, and a song is timed to the music. + var trimmed = s.Trim(); + if (trimmed.StartsWith('♪') || trimmed.StartsWith('♫') || trimmed.EndsWith('♪') || trimmed.EndsWith('♫') || + (trimmed.Length > 1 && trimmed.StartsWith('#') && trimmed.EndsWith('#'))) + { + return string.Empty; + } + s = SoundDescriptionRegex().Replace(s, " "); var sb = new StringBuilder(s.Length); diff --git a/src/ui/Logic/Config/Language/File/LanguageImport.cs b/src/ui/Logic/Config/Language/File/LanguageImport.cs index 379b74f5235..85719822e71 100644 --- a/src/ui/Logic/Config/Language/File/LanguageImport.cs +++ b/src/ui/Logic/Config/Language/File/LanguageImport.cs @@ -60,6 +60,7 @@ public class LanguageImport public string ForcedAlignerIsolatingSpeechFailed { get; set; } public string ForcedAlignerModel { get; set; } public string ForcedAlignerDownloadEngine { get; set; } + public string ForcedAlignerEstimatedXOfY { get; set; } public LanguageImport() { @@ -125,6 +126,7 @@ public LanguageImport() ForcedAlignerIsolatingSpeechFailed = "Could not isolate the speech - end times were set from reading time instead."; ForcedAlignerModel = "Aligner model"; ForcedAlignerDownloadEngine = "Download / update engine..."; + ForcedAlignerEstimatedXOfY = "{0} of {1} lines could not be aligned and were placed by reading time - their time codes are only estimated. The script is probably longer than what is spoken in the video."; CsvXlsxCustomColumnsDotDotDot = "CSV/XLSX/ODS with custom columns..."; TitleImportCsvXlsxCustomColumns = "Import CSV/XLSX/ODS with custom columns"; DetectedSeparatorX = "Detected separator: {0}"; diff --git a/tests/UI/Features/Files/ImportPlainText/SpeechEnvelopeTests.cs b/tests/UI/Features/Files/ImportPlainText/SpeechEnvelopeTests.cs index 35258b382a5..5347342fc85 100644 --- a/tests/UI/Features/Files/ImportPlainText/SpeechEnvelopeTests.cs +++ b/tests/UI/Features/Files/ImportPlainText/SpeechEnvelopeTests.cs @@ -1,5 +1,6 @@ using Nikse.SubtitleEdit.Features.Files.ImportPlainText; using Nikse.SubtitleEdit.Features.Main; +using Nikse.SubtitleEdit.Logic.Config; namespace UITests.Features.Files.ImportPlainText; @@ -145,6 +146,38 @@ private static void Apply(List lines, SpeechEnvelope? env ForcedAligner.ApplyTimeCodes(lines, Aligned, texts, [0, 1, 2], 20, envelope); } + [Fact] + public void Aligner_QuickSpeech_StillHoldsTheLineForTheMaximumReadingSpeed() + { + using var _ = new SettingsScope( + "General.SubtitleMaximumCharactersPerSeconds", + "General.SubtitleOptimalCharactersPerSeconds", + "General.SubtitleMinimumDisplayMilliseconds", + "General.SubtitleMaximumDisplayMilliseconds"); + Se.Settings.General.SubtitleMaximumCharactersPerSeconds = 20; + Se.Settings.General.SubtitleOptimalCharactersPerSeconds = 15; + Se.Settings.General.SubtitleMinimumDisplayMilliseconds = 500; + Se.Settings.General.SubtitleMaximumDisplayMilliseconds = 8000; + + // 31 characters said in 0.8 s, with seconds of silence after it: 1.55 s at 20 cps. + var lines = new List { new() { Text = "It's just some guy I work with." }, new() { Text = "Come on." } }; + ForcedAligner.ApplyTimeCodes(lines, [(1.0, 1.8), (6.0, 7.0)], lines.Select(l => l.Text).ToList(), [0, 1], 20); + + Assert.InRange(lines[0].EndTime.TotalSeconds, 2.5, 2.6); + } + + [Fact] + public void Aligner_UsesTheGapItIsGiven() + { + var lines = ThreeLines(); + var texts = lines.Select(l => l.Text).ToList(); + + // Line one's speech runs right up to line two: the end gives way by the gap asked for. + ForcedAligner.ApplyTimeCodes(lines, [(1.0, 9.0), (3.0, 5.0), (12.0, 15.0)], texts, [0, 1, 2], 20, null, 500); + + Assert.InRange(lines[0].EndTime.TotalSeconds, 2.49, 2.51); + } + [Fact] public void Aligner_EndsTheLineWhereItsSpeechEnds() { diff --git a/tests/UI/Features/Tools/ImproveTimeCodes/SubtitleRetimerTests.cs b/tests/UI/Features/Tools/ImproveTimeCodes/SubtitleRetimerTests.cs index 871ec82af10..3b59898bc63 100644 --- a/tests/UI/Features/Tools/ImproveTimeCodes/SubtitleRetimerTests.cs +++ b/tests/UI/Features/Tools/ImproveTimeCodes/SubtitleRetimerTests.cs @@ -101,11 +101,25 @@ public void PlanBatches_FullBatchesBorrowSentinels_GapsDoNot() [InlineData("[door slams]", "")] [InlineData("♪ ♪", "")] [InlineData("(sighs) Fine.", "Fine.")] - [InlineData("♪ Let it be ♪", "Let it be")] + [InlineData("♪ Let it be ♪", "")] // sung, timed to the music - not for a speech aligner + [InlineData("♪ Smelly cat, smelly cat", "")] + [InlineData("# Smelly cat #", "")] + [InlineData("{\\an8}{\\pos(320,40)}CENTRAL PERK", "")] // a sign on screen + [InlineData("{\\move(0,0,10,10)}Moving", "")] + [InlineData("#1 fan, that's me.", "1 fan, that's me.")] [InlineData("", "")] public void GetSpokenText_KeepsOnlyWhatIsSaid(string text, string expected) => Assert.Equal(expected, SubtitleRetimer.GetSpokenText(text)); + [Theory] + [InlineData(10, 13, 12, 14, true)] // a second over, half the shorter line: two speakers at once + [InlineData(10, 20, 12, 14, true)] // a line inside a long one: a sign over dialogue + [InlineData(10, 12.2, 12, 14, false)] // 0.2 s: mistiming + [InlineData(10, 12.6, 12, 16, false)] // 0.6 s of a 2.6 s line: still mistiming + [InlineData(10, 12, 12.1, 14, false)] + public void OverlapsOnPurpose_OnlyALargeOverlapIsMeant(double aStart, double aEnd, double bStart, double bEnd, bool expected) + => Assert.Equal(expected, SubtitleRetimer.OverlapsOnPurpose(new("a", aStart, aEnd), new("b", bStart, bEnd))); + [Fact] public async Task Retime_MovesLinesOntoTheAlignedStarts_AndDiscardsSentinels() { @@ -471,6 +485,62 @@ public void Settle_LeavesOverlapsTheSubtitleCameInWith() Assert.Equal(12.1, placements[1].StartSeconds, 3); } + [Fact] + public void Settle_ClosesASmallOverlapTheSubtitleCameInWith() + { + // Mistimed on the way in: b started 0.3 s before a ended. Both moved; the overlap is not kept. + var original = new List { new("a", 10, 12.3), new("b", 12, 14) }; + var placements = new[] { new SubtitleRetimer.Placement(10.2, 12.5, true), new SubtitleRetimer.Placement(12.3, 14.2, true) }; + + SubtitleRetimer.Settle(original, placements, 0.1); + + Assert.Equal(12.2, placements[0].EndSeconds, 3); + Assert.Equal(12.3, placements[1].StartSeconds, 3); + } + + [Fact] + public async Task Retime_ClosesASmallOverlapTheSubtitleCameInWith() + { + // Chained lines; line 4 came in starting 0.3 s before line 3 ended. Its speech starts + // where it should, so the overlap was mistiming and must not survive the re-time. + var lines = MakeLines(8, spacing: 2.0, duration: 2.0); + lines[4] = lines[4] with { StartSeconds = lines[4].StartSeconds - 0.3 }; + var audio = new FakeAudio(_folder); + var runner = new OracleRunner(audio); + for (var i = 0; i < lines.Count; i++) + { + var start = 10.0 + (i * 2.0) + 0.1; + runner.Truth[lines[i].Text] = (start, start + 1.7); + } + + var results = await new SubtitleRetimer(runner, audio, new SubtitleRetimer.Options { MinGapSeconds = 0.1 }) + .RetimeAsync(lines, null, TestContext.Current.CancellationToken); + + Assert.Equal(18.1, results[4].StartSeconds, 2); + Assert.True(results[4].StartSeconds - results[3].EndSeconds >= 0.099, $"line 3 ends {results[3].EndSeconds}, line 4 starts {results[4].StartSeconds}"); + } + + [Fact] + public void SnapToFrames_PutsMovedLinesOnFrames_AndKeepsTheGap() + { + // 25 fps, gap two frames (80 ms). Rounded on its own, a's end would land one frame from b. + var original = new List { new("a", 10, 12), new("b", 12.5, 14), new("c", 20, 22) }; + var placements = new[] + { + new SubtitleRetimer.Placement(10.21, 12.299, true), + new SubtitleRetimer.Placement(12.379, 14.03, true), + new SubtitleRetimer.Placement(20, 22, false), // not moved - left as it is + }; + + SubtitleRetimer.SnapToFrames(original, placements, 25, 0.08); + + Assert.Equal(10.2, placements[0].StartSeconds, 6); + Assert.Equal(12.28, placements[0].EndSeconds, 6); + Assert.Equal(12.36, placements[1].StartSeconds, 6); + Assert.Equal(14.04, placements[1].EndSeconds, 6); + Assert.Equal(20.0, placements[2].StartSeconds, 6); + } + [Fact] public void Settle_TwoLinesThatMayNotMove_AreLeftAlone() { From a583a2f220c021d832ce5f3dafd26935a789320f Mon Sep 17 00:00:00 2001 From: Nikolaj Olsson Date: Sun, 11 Oct 2026 18:49:24 +0200 Subject: [PATCH 070/306] =?UTF-8?q?nOCR:=20add=20Cyrillic.nocr=20and=20Cyr?= =?UTF-8?q?illic=20case/=D1=8B=20handling?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Ship Ocr/Cyrillic.nocr (Russian/Ukrainian/Belarusian/Serbian/Macedonian letters, digits, punctuation, ♪♫, touching pairs like "гу"/"жд"), trained from ten subtitle fonts at 30/40 px, regular/bold/italic. - Matcher: Cyrillic case twins (о/О, т/Т, и/И, ...) join the existing Latin case-twin resolver. - Splitter: each glyph gets the line's baseline (median glyph bottom). - Case fixer: Cyrillic twins and same-case twin pairs are cased by height above the baseline, so descender letters (д, у, ц, щ) no longer read as capitals; lowercase entries trained cap-high (the "∂"-like д) keep their case; й/Й use the accent rule. - ы/Ы: a soft sign directly followed by a dotless bar of the same height is composed into one letter (heavy outlined fonts are too wide for trained entries). - rus OCR fix list: ьІ/ьі -> ы, word-initial 6 -> б. - OcrInitializer: unpack OCR files missing on disk even when the full unpack is skipped (a kept, user-trained Latin.nocr), without overwriting anything. Latin.nocr output is byte-identical on the English/Polish/Spanish/French/Italian/ Danish samples; all changes only trigger on Cyrillic text. Co-Authored-By: Claude Opus 5.5 --- Dictionaries/rus_OCRFixReplaceList.xml | 5 + Ocr/Cyrillic.nocr | Bin 0 -> 271303 bytes src/libuilogic/Ocr/ImageSplitterItem2.cs | 12 ++ src/libuilogic/Ocr/NOcrCaseFixer.cs | 69 +++++++- src/libuilogic/Ocr/NOcrDb.cs | 56 ++++++- .../Ocr/NikseBitmapImageSplitter2.cs | 36 ++++ src/ui/Logic/Initializers/OcrInitializer.cs | 37 +++++ tests/libuilogic/Ocr/NOcrCyrillicTests.cs | 154 ++++++++++++++++++ 8 files changed, 357 insertions(+), 12 deletions(-) create mode 100644 Ocr/Cyrillic.nocr create mode 100644 tests/libuilogic/Ocr/NOcrCyrillicTests.cs diff --git a/Dictionaries/rus_OCRFixReplaceList.xml b/Dictionaries/rus_OCRFixReplaceList.xml index 8568bb5d79a..ccb57bb671e 100644 --- a/Dictionaries/rus_OCRFixReplaceList.xml +++ b/Dictionaries/rus_OCRFixReplaceList.xml @@ -243,6 +243,8 @@ + + @@ -282,5 +284,8 @@ + + \ No newline at end of file diff --git a/Ocr/Cyrillic.nocr b/Ocr/Cyrillic.nocr new file mode 100644 index 0000000000000000000000000000000000000000..e9f840891ce82cf85766b15b7b0eb8844cca2958 GIT binary patch literal 271303 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
Hmj=HDwl9X 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±g#+wzv(?U& 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 z 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 F1^{$2F%$p* literal 0 HcmV?d00001 diff --git a/src/libuilogic/Ocr/ImageSplitterItem2.cs b/src/libuilogic/Ocr/ImageSplitterItem2.cs index 7d5768e82c6..e28707d9ac6 100644 --- a/src/libuilogic/Ocr/ImageSplitterItem2.cs +++ b/src/libuilogic/Ocr/ImageSplitterItem2.cs @@ -11,6 +11,18 @@ public class ImageSplitterItem2 public string? SpecialCharacter { get; set; } public bool CouldBeSpaceBefore { get; set; } + ///

+ /// Where the line's baseline is, in the same coordinates as (null when the + /// splitter didn't work it out). The part of a glyph above it tells "д" from "Д" and "у" from + /// "У", where the descender makes the whole glyph about as tall as a capital. + /// + public int? Baseline { get; set; } + + /// The glyph's height above the line's baseline, or its whole height when that's unknown. + public int HeightAboveBaseline => Baseline.HasValue && NikseBitmap != null + ? Math.Min(NikseBitmap.Height, Baseline.Value - Top) + : NikseBitmap?.Height ?? 0; + public ImageSplitterItem2(int x, int y, NikseBitmap2 bitmap, bool couldBeSpaceBefore = false) { X = x; diff --git a/src/libuilogic/Ocr/NOcrCaseFixer.cs b/src/libuilogic/Ocr/NOcrCaseFixer.cs index 6c31b05d96b..e4abd9832d5 100644 --- a/src/libuilogic/Ocr/NOcrCaseFixer.cs +++ b/src/libuilogic/Ocr/NOcrCaseFixer.cs @@ -2,16 +2,29 @@ public class NOcrCaseFixer : INOcrCaseFixer { + /// + /// Cyrillic letters whose lowercase is a smaller copy of the uppercase in the usual subtitle + /// fonts, as lowercase/uppercase pairs. "д", "у", "ц" and "щ" have a descender, so their case + /// shows in the height above the baseline, not in the whole height. + /// + public const string CyrillicCaseTwins = "вВгГдДжЖзЗиИкКлЛмМнНоОпПсСтТуУхХцЦчЧшШщЩъЪыЫьЬэЭюЮяЯєЄѕЅљЉњЊџЏ"; + private static readonly HashSet UppercaseLikeLowercase = new() { "V", "W", "U", "S", "Z", "O", "X", "Ø", "C" }; private static readonly HashSet LowercaseLikeUppercase = new() { "v", "w", "u", "s", "z", "o", "x", "ø", "c" }; - private static readonly HashSet UppercaseWithAccent = new() { "Č", "Š", "Ž", "Ś", "Ż", "Ö", "Ü", "Ú", "Ï", "Í", "Ç", "Ì", "Ò", "Ù", "Ó" }; - private static readonly HashSet LowercaseWithAccent = new() { "č", "š", "ž", "ś", "ż", "ö", "ü", "ú", "ï", "í", "ç", "ì", "ò", "ù", "ó" }; + private static readonly HashSet UppercaseWithAccent = new() { "Č", "Š", "Ž", "Ś", "Ż", "Ö", "Ü", "Ú", "Ï", "Í", "Ç", "Ì", "Ò", "Ù", "Ó", "Й" }; + private static readonly HashSet LowercaseWithAccent = new() { "č", "š", "ž", "ś", "ż", "ö", "ü", "ú", "ï", "í", "ç", "ì", "ò", "ù", "ó", "й" }; private long _ocrLowercaseHeightsTotal; private int _ocrLowercaseHeightsTotalCount; private long _ocrUppercaseHeightsTotal; private int _ocrUppercaseHeightsTotalCount; + // Heights above the baseline of x-height and cap-height reference letters. + private long _xHeightTotal; + private int _xHeightCount; + private long _capHeightTotal; + private int _capHeightCount; + public bool HasWarmedUp { get; set; } /// @@ -24,7 +37,8 @@ public string FixUppercaseLowercaseIssues(ImageSplitterItem2 targetItem, NOcrCha return result.Text; } - if (result.Text is "e" or "a" or "d" or "t") + // Reference heights: letters with no lookalike in the other case. + if (result.Text is "e" or "a" or "d" or "t" or "а" or "е") { _ocrLowercaseHeightsTotalCount++; _ocrLowercaseHeightsTotal += targetItem.NikseBitmap.Height; @@ -35,7 +49,7 @@ public string FixUppercaseLowercaseIssues(ImageSplitterItem2 targetItem, NOcrCha } } - if (result.Text is "E" or "H" or "R" or "D" or "T" or "M") + if (result.Text is "E" or "H" or "R" or "D" or "T" or "M" or "Б" or "Е" or "Р" or "А") { _ocrUppercaseHeightsTotalCount++; _ocrUppercaseHeightsTotal += targetItem.NikseBitmap.Height; @@ -46,12 +60,31 @@ public string FixUppercaseLowercaseIssues(ImageSplitterItem2 targetItem, NOcrCha } } + if (result.Text is "e" or "a" or "а" or "е") + { + _xHeightTotal += targetItem.HeightAboveBaseline; + _xHeightCount++; + } + else if (result.Text is "E" or "H" or "R" or "D" or "T" or "M" or "Б" or "Е" or "Р" or "А") + { + _capHeightTotal += targetItem.HeightAboveBaseline; + _capHeightCount++; + } + + // Merged letter pairs ("гу", "жд" drawn as one connected glyph) are case twins as a + // whole when both letters are twins of the same case. + if (result.Text.Length > 0 && result.Text.All(c => CyrillicCaseTwins.Contains(c)) && + (result.Text.All(char.IsLower) || result.Text.All(char.IsUpper))) + { + return FixCyrillicCase(targetItem, result); + } + if (_ocrLowercaseHeightsTotalCount <= 2 || _ocrUppercaseHeightsTotalCount <= 2) { return result.Text; } - // Latin letters where lowercase versions look like uppercase version + // Latin letters where lowercase versions look like uppercase version if (UppercaseLikeLowercase.Contains(result.Text)) { var averageLowercase = _ocrLowercaseHeightsTotal / _ocrLowercaseHeightsTotalCount; @@ -98,4 +131,28 @@ public string FixUppercaseLowercaseIssues(ImageSplitterItem2 targetItem, NOcrCha return result.Text; } -} \ No newline at end of file + + /// + /// Picks the case of a Cyrillic case-twin letter from its height above the baseline: closer + /// to the x-height of "а"/"е" is lowercase, closer to the cap height of "Б"/"Е"/"Р"/"А" is + /// uppercase. Until both have been seen a few times the matcher's choice stands, and so does + /// a lowercase entry that was trained as tall as a capital: that shape is lowercase only (the + /// "∂"-like "д" of some fonts, which has no descender and reaches the cap height). + /// + private string FixCyrillicCase(ImageSplitterItem2 targetItem, NOcrChar result) + { + var text = result.Text; + if (_xHeightCount <= 2 || _capHeightCount <= 2 || + char.IsLower(text[0]) && result.MarginTop * 10 <= result.Height) + { + return text; + } + + var xHeight = _xHeightTotal / (double)_xHeightCount; + var capHeight = _capHeightTotal / (double)_capHeightCount; + var height = targetItem.HeightAboveBaseline; + return Math.Abs(height - xHeight) < Math.Abs(height - capHeight) + ? text.ToLowerInvariant() + : text.ToUpperInvariant(); + } +} diff --git a/src/libuilogic/Ocr/NOcrDb.cs b/src/libuilogic/Ocr/NOcrDb.cs index 0dd65d54816..68a43437f7a 100644 --- a/src/libuilogic/Ocr/NOcrDb.cs +++ b/src/libuilogic/Ocr/NOcrDb.cs @@ -506,12 +506,12 @@ private static OcrPoint GetTotalSize(int listIndex, List ite { if (cached.ExactMatch != null) { - return cached.ExactMatch; + return ComposeYery(cached.ExactMatch, list, item, deepSeek, maxWrongPixels, lastDitch); } // The expanded scan depends on the neighboring splitter items and the parent // bitmap, so it can't be cached per glyph bitmap - always evaluate it live. - return GetMatchExpanded(parentBitmap, item, list.IndexOf(item), list) ?? cached.SingleMatch; + return GetMatchExpanded(parentBitmap, item, list.IndexOf(item), list) ?? ComposeYery(cached.SingleMatch, list, item, deepSeek, maxWrongPixels, lastDitch); } // A perfect single match (exact size, 0 errors) means the user has explicitly added an @@ -520,7 +520,7 @@ private static OcrPoint GetTotalSize(int listIndex, List ite if (exactSingle != null) { CacheMatch(key, new MatchCacheEntry { ExactMatch = exactSingle }); - return exactSingle; + return ComposeYery(exactSingle, list, item, deepSeek, maxWrongPixels, lastDitch); } var expandedResult = GetMatchExpanded(parentBitmap, item, list.IndexOf(item), list); @@ -553,7 +553,50 @@ private static OcrPoint GetTotalSize(int listIndex, List ite } CacheMatch(key, new MatchCacheEntry { SingleMatch = single }); - return single; + return ComposeYery(single, list, item, deepSeek, maxWrongPixels, lastDitch); + } + + /// + /// "ы"/"Ы" is a soft sign plus a separate bar. The trained two-part entries only fit fonts + /// with a similar gap and weight - in a heavy outlined font the pair is far wider than any + /// trained one and came out as "ьІ". A soft sign directly followed (no space) by a dotless bar + /// of the same height and top is that letter in any font: a capital "І" after a lowercase + /// "ь" is taller, and the dotted "і" isn't a bar. + /// + private NOcrChar? ComposeYery(NOcrChar? softSign, List list, ImageSplitterItem2 item, bool deepSeek, int maxWrongPixels, bool lastDitch) + { + if (softSign?.Text is not ("ь" or "Ь") || item.NikseBitmap == null) + { + return softSign; + } + + var index = list.IndexOf(item); + var next = index >= 0 && index + 1 < list.Count ? list[index + 1] : null; + if (next?.NikseBitmap == null) + { + return softSign; + } + + var height = item.NikseBitmap.Height; + var tolerance = Math.Max(2, height / 8); + if (Math.Abs(next.NikseBitmap.Height - height) > tolerance || + Math.Abs(next.Top - item.Top) > tolerance || + next.NikseBitmap.Width >= item.NikseBitmap.Width) + { + return softSign; + } + + var bar = GetMatchSingle(next.NikseBitmap, next.Top, deepSeek, maxWrongPixels, lastDitch); + if (bar?.Text is not ("І" or "l" or "I" or "|" or "/" or "1")) + { + return softSign; + } + + return new NOcrChar(softSign) + { + Text = softSign.Text == "ь" ? "ы" : "Ы", + ExpandCount = 2, + }; } private void CacheMatch(MatchCacheKey key, MatchCacheEntry entry) @@ -938,8 +981,9 @@ private readonly struct MatchPass }; // Letters whose upper and lower case differ only in size ("o"/"O", "s"/"S", ...): scaled to - // the glyph they fit equally well, so pixels alone can't pick the case. - private const string CaseTwinLetters = "cCoOsSuUvVwWxXzZøØ"; + // the glyph they fit equally well, so pixels alone can't pick the case. Cyrillic has many + // more of them than Latin ("о"/"О", "т"/"Т", "и"/"И", ...). + private const string CaseTwinLetters = "cCoOsSuUvVwWxXzZøØ" + NOcrCaseFixer.CyrillicCaseTwins; private const double CaseTwinRateMargin = 0.01; private static bool IsCaseTwinLetter(string text) => text.Length == 1 && CaseTwinLetters.Contains(text[0]); diff --git a/src/libuilogic/Ocr/NikseBitmapImageSplitter2.cs b/src/libuilogic/Ocr/NikseBitmapImageSplitter2.cs index a9c5957fbfb..7ef9837edea 100644 --- a/src/libuilogic/Ocr/NikseBitmapImageSplitter2.cs +++ b/src/libuilogic/Ocr/NikseBitmapImageSplitter2.cs @@ -539,6 +539,7 @@ public static List SplitBitmapToLettersNew(NikseBitmap2 bmp, list.Add(new ImageSplitterItem2(Environment.NewLine)); } + var lineStart = list.Count; var horizontalSplit = SplitHorizontalNew(b, xOrMorePixelsMakesSpace); if (rightToLeft) { @@ -561,11 +562,46 @@ public static List SplitBitmapToLettersNew(NikseBitmap2 bmp, list.Add(item); } } + + SetBaseline(list, lineStart); } return list; } + /// + /// Sets on the line's glyphs from + /// on: the median glyph bottom, as descenders and raised marks + /// (apostrophes, quotes) are the minority in any line of text. + /// + private static void SetBaseline(List list, int lineStart) + { + var bottoms = new List(); + for (var i = lineStart; i < list.Count; i++) + { + var bitmap = list[i].NikseBitmap; + if (bitmap != null) + { + bottoms.Add(list[i].Top + bitmap.Height); + } + } + + if (bottoms.Count == 0) + { + return; + } + + bottoms.Sort(); + var baseline = bottoms[bottoms.Count / 2]; + for (var i = lineStart; i < list.Count; i++) + { + if (list[i].NikseBitmap != null) + { + list[i].Baseline = baseline; + } + } + } + /// /// split into lines diff --git a/src/ui/Logic/Initializers/OcrInitializer.cs b/src/ui/Logic/Initializers/OcrInitializer.cs index 840490f812b..84c4e026c1f 100644 --- a/src/ui/Logic/Initializers/OcrInitializer.cs +++ b/src/ui/Logic/Initializers/OcrInitializer.cs @@ -3,6 +3,7 @@ using Nikse.SubtitleEdit.Logic.Config; using System; using System.IO; +using System.IO.Compression; using System.Threading.Tasks; namespace Nikse.SubtitleEdit.Logic.Initializers; @@ -21,6 +22,42 @@ public async Task UpdateOcrDictionariesIfNeeded() await Unpack(); WriteNewVersionFile(); } + else + { + UnpackMissingFiles(); + } + } + + /// + /// A kept (user-trained) Latin.nocr skips the full unpack, so databases added in a later + /// version (e.g. Cyrillic.nocr) would never arrive. Add those without touching existing files. + /// + private static void UnpackMissingFiles() + { + try + { + var outputDir = Se.OcrFolder; + using var zipStream = AssetLoader.Open(new Uri("avares://SubtitleEdit/Assets/Ocr.zip")); + using var archive = new ZipArchive(zipStream, ZipArchiveMode.Read); + foreach (var entry in archive.Entries) + { + if (string.IsNullOrEmpty(entry.Name) || entry.FullName.Contains("..")) + { + continue; + } + + var fileName = Path.Combine(outputDir, entry.FullName); + if (!File.Exists(fileName)) + { + Directory.CreateDirectory(Path.GetDirectoryName(fileName)!); + entry.ExtractToFile(fileName); + } + } + } + catch (Exception exception) + { + Se.LogError(exception, "Could not unpack missing OCR files"); + } } private static void WriteNewVersionFile() diff --git a/tests/libuilogic/Ocr/NOcrCyrillicTests.cs b/tests/libuilogic/Ocr/NOcrCyrillicTests.cs new file mode 100644 index 00000000000..d8eef888e75 --- /dev/null +++ b/tests/libuilogic/Ocr/NOcrCyrillicTests.cs @@ -0,0 +1,154 @@ +using Nikse.SubtitleEdit.UiLogic.Ocr; +using SkiaSharp; +using System.Text; + +namespace LibUiLogicTests.Ocr; + +/// +/// Cyrillic in nOCR: case twins ("о"/"О", "т"/"Т", ...) resolved by the matcher and the case +/// fixer, descender letters ("д", "у") cased by their height above the baseline, and "ы" read +/// as one letter although it splits into a soft sign and a bar. +/// +public class NOcrCyrillicTests +{ + private const float FontSize = 50f; + private const int MaxWrongPixels = 25; + private const int PixelsAreSpace = 12; + + private static string TestFontName => SKTypeface.Default.FamilyName; + + private static NOcrDb Train(string characters) + { + var db = new NOcrDb(Path.Combine(Path.GetTempPath(), Path.GetRandomFileName() + ".nocr")); + var learned = new NOcrTrainer().Train(new NOcrTrainerSettings + { + FontNames = { TestFontName }, + FontSize = FontSize, + CharactersToTrain = characters, + }, db); + Assert.True(learned > 0, "training should learn at least one character"); + return db; + } + + private static List Split(string text, out NikseBitmap2 parent) + { + using var bmp = NOcrTrainer.RenderCharacterImage(text, TestFontName, FontSize, false, false); + Assert.NotNull(bmp); + parent = new NikseBitmap2(bmp!); + parent.MakeTwoColor(200); + parent.CropTop(0, new SKColor(0, 0, 0, 0)); + return NikseBitmapImageSplitter2.SplitBitmapToLettersNew(parent, PixelsAreSpace, false, false, 25, false); + } + + private static string Recognize(NOcrDb db, string text, INOcrCaseFixer? caseFixer = null) + { + var letters = Split(text, out var parent); + var sb = new StringBuilder(); + for (var i = 0; i < letters.Count; i++) + { + var item = letters[i]; + if (item.NikseBitmap == null) + { + sb.Append(item.SpecialCharacter); + continue; + } + + var match = db.GetMatch(parent, letters, item, item.Top, true, MaxWrongPixels); + if (match is { ExpandCount: > 0 }) + { + i += match.ExpandCount - 1; + } + + sb.Append(match == null ? "*" : caseFixer?.FixUppercaseLowercaseIssues(item, match) ?? match.Text); + } + + return sb.ToString().Trim(); + } + + [Fact] + public void Matcher_ReadsCyrillicCaseTwinsInTheirCase() + { + var db = Train("МмОоЯяТтЕеВвАаСсНнИиКк"); + + Assert.Equal("Моя тетя", Recognize(db, "Моя тетя")); + Assert.Equal("Вас Нет", Recognize(db, "Вас Нет")); + } + + [Fact] + public void SoftSignFollowedBySameHeightBar_IsReadAsYery() + { + // Only "ь" and "І" are trained - no two-part "ы" entry to match. + var db = Train("ьЬІ"); + + Assert.Equal("ы", Recognize(db, "ы")); + Assert.Equal("Ы", Recognize(db, "Ы")); + } + + [Fact] + public void SoftSignFollowedByTallerBar_StaysTwoLetters() + { + var db = Train("ьЬІ"); + + Assert.Equal("ьІ", Recognize(db, "ьІ")); + } + + [Fact] + public void Splitter_SetsLineBaseline_DescenderAboveBaselineIsXHeight() + { + var letters = Split("Ааау", out _).Where(p => p.NikseBitmap != null).ToList(); + + Assert.Equal(4, letters.Count); + Assert.All(letters, p => Assert.Equal(letters[0].Baseline, p.Baseline)); + var capital = letters[0]; + var a = letters[1]; + var u = letters[3]; + Assert.True(u.NikseBitmap!.Height > u.HeightAboveBaseline, "the descender is below the baseline"); + Assert.InRange(u.HeightAboveBaseline, a.HeightAboveBaseline - 2, a.HeightAboveBaseline + 2); + Assert.True(capital.HeightAboveBaseline > a.HeightAboveBaseline + 5); + } + + private static ImageSplitterItem2 Glyph(int height, int top, int baseline = 40) => + new(0, top, new NikseBitmap2(20, height)) { Top = top, Baseline = baseline }; + + private static NOcrCaseFixer WarmedUpFixer() + { + // x-height 20 ("а"/"е"), cap height 30 ("Б"/"Е"/"Р"), baseline at 40. + var fixer = new NOcrCaseFixer(); + for (var i = 0; i < 3; i++) + { + fixer.FixUppercaseLowercaseIssues(Glyph(20, 20), new NOcrChar("а")); + fixer.FixUppercaseLowercaseIssues(Glyph(20, 20), new NOcrChar("е")); + fixer.FixUppercaseLowercaseIssues(Glyph(30, 10), new NOcrChar("Б")); + fixer.FixUppercaseLowercaseIssues(Glyph(30, 10), new NOcrChar("Р")); + } + + return fixer; + } + + [Theory] + [InlineData("О", 20, 20, "о")] + [InlineData("о", 30, 10, "О")] + [InlineData("Т", 20, 20, "т")] + [InlineData("Д", 26, 20, "д")] // descender: whole glyph almost cap high, x-high above the baseline + [InlineData("У", 27, 20, "у")] + [InlineData("д", 36, 10, "Д")] + [InlineData("ГУ", 27, 20, "гу")] // merged pair + [InlineData("жд", 30, 10, "ЖД")] + [InlineData("б", 30, 10, "б")] // no case twin + public void CaseFixer_CyrillicTwin_CaseFromHeightAboveBaseline(string matched, int height, int top, string expected) + { + var fixer = WarmedUpFixer(); + + Assert.Equal(expected, fixer.FixUppercaseLowercaseIssues(Glyph(height, top), new NOcrChar(matched) { Height = 20, MarginTop = 10 })); + } + + [Fact] + public void CaseFixer_LowercaseEntryTrainedCapHigh_StaysLowercase() + { + // The "∂"-like "д" of some fonts: no descender, as tall as a capital. + var fixer = WarmedUpFixer(); + var tallLowercase = new NOcrChar("д") { Height = 30, MarginTop = 0 }; + + Assert.Equal("д", fixer.FixUppercaseLowercaseIssues(Glyph(30, 10), tallLowercase)); + } +} From cfb8f68c5c8a034cd86eee90827cf180cd05e722 Mon Sep 17 00:00:00 2001 From: niksedk Date: Sun, 11 Oct 2026 19:15:54 +0200 Subject: [PATCH 071/306] =?UTF-8?q?Fix=20flaky=20nOCR=20=D1=8B=20test:=20g?= =?UTF-8?q?ive=20the=20glyph=20line=20context?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A lone "ы" cropped to its own top is just a smaller "Ы", so the case was decided by the randomly trained line segments (failed ~2 of 3 runs). A leading "І" gives the line a cap height to case it against. Co-Authored-By: Claude Opus 5.5 --- tests/libuilogic/Ocr/NOcrCyrillicTests.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/libuilogic/Ocr/NOcrCyrillicTests.cs b/tests/libuilogic/Ocr/NOcrCyrillicTests.cs index d8eef888e75..2bd92ebeab3 100644 --- a/tests/libuilogic/Ocr/NOcrCyrillicTests.cs +++ b/tests/libuilogic/Ocr/NOcrCyrillicTests.cs @@ -80,8 +80,8 @@ public void SoftSignFollowedBySameHeightBar_IsReadAsYery() // Only "ь" and "І" are trained - no two-part "ы" entry to match. var db = Train("ьЬІ"); - Assert.Equal("ы", Recognize(db, "ы")); - Assert.Equal("Ы", Recognize(db, "Ы")); + Assert.Equal("Іы", Recognize(db, "Іы")); + Assert.Equal("ІЫ", Recognize(db, "ІЫ")); } [Fact] From 66a6f8fac0cef540be2ac5b0f5f9a258bc651928 Mon Sep 17 00:00:00 2001 From: niksedk Date: Sun, 11 Oct 2026 19:36:27 +0200 Subject: [PATCH 072/306] Bug hunt 10-11: nOCR optimizer/unpack, Cyrillic baseline, retimer snap, mkv picker MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - OCR unpack: keep any Ocr.zip file the user changed (hash recorded at unpack) instead of only a Latin.nocr over 1.7 MB, so an optimized or lightly trained database survives an upgrade. - nOCR splitter: lower median for the line baseline; on a two-glyph line the upper median was the descender ("Ну" read as "НУ"). - nOCR optimizer: keep lookalikes apart under the last-ditch budget (2x), and in the OCR window under the selected max wrong pixels too. - nOCR optimizer: never overwrite an existing backup (.bak, .2.bak, ...). - seconv optimize-nocr: /outputs/x.nocr is the input, not --output; a bare --output with no value is an error instead of optimizing in place. - Improve time codes: SnapToFrames rounds away from an unmoved neighbour off the frame grid instead of into it (ms overlaps). - Pick Matroska track: OK and Export disabled while the preview parses the file, which the caller reads (and disposes) once the dialog closes. Co-Authored-By: Claude Opus 5.5 --- src/libuilogic/Ocr/NOcrDbOptimizer.cs | 23 +++- .../Ocr/NikseBitmapImageSplitter2.cs | 5 +- src/seconv/Program.cs | 21 ++-- src/ui/Features/Ocr/OcrViewModel.cs | 12 ++- .../PickMatroskaTrackViewModel.cs | 37 ++++++- .../Tools/ImproveTimeCodes/SubtitleRetimer.cs | 30 +++++- src/ui/Logic/Initializers/OcrInitializer.cs | 102 +++++++++++++----- .../ImproveTimeCodes/SubtitleRetimerTests.cs | 21 ++++ tests/libuilogic/Ocr/NOcrCyrillicTests.cs | 13 +++ tests/libuilogic/Ocr/NOcrDbOptimizerTests.cs | 22 ++++ 10 files changed, 245 insertions(+), 41 deletions(-) diff --git a/src/libuilogic/Ocr/NOcrDbOptimizer.cs b/src/libuilogic/Ocr/NOcrDbOptimizer.cs index 44f08038d1c..8833e766e92 100644 --- a/src/libuilogic/Ocr/NOcrDbOptimizer.cs +++ b/src/libuilogic/Ocr/NOcrDbOptimizer.cs @@ -15,8 +15,11 @@ public sealed class NOcrDbOptimizerSettings /// public int MinLines { get; set; } = 52; - /// The loosest wrong-pixel budget a lookalike must stay above (the deep-seek default). - public int MaxWrongPixels { get; set; } = 25; + /// + /// The loosest wrong-pixel budget a lookalike must stay above: the last-ditch pass (batch + /// convert, seconv, binary OCR fallback) doubles the deep-seek default of 25. + /// + public int MaxWrongPixels { get; set; } = 50; public static readonly string[] DefaultFontNames = { @@ -55,6 +58,22 @@ public static class NOcrDbOptimizer private sealed record Glyph(string Text, bool[] Ink, int Width, int Height); + /// + /// A backup file name for that doesn't exist yet + /// (Latin.nocr.bak, then Latin.nocr.2.bak, ...), so optimizing again never overwrites the + /// backup of the unoptimized database. + /// + public static string GetFreeBackupFileName(string fileName) + { + var backupFileName = fileName + ".bak"; + for (var i = 2; File.Exists(backupFileName); i++) + { + backupFileName = fileName + "." + i + ".bak"; + } + + return backupFileName; + } + /// /// Optimizes the single-part entries of in place (call /// afterwards). Call from a background thread. Returns null, with diff --git a/src/libuilogic/Ocr/NikseBitmapImageSplitter2.cs b/src/libuilogic/Ocr/NikseBitmapImageSplitter2.cs index 7ef9837edea..6c5ea323df9 100644 --- a/src/libuilogic/Ocr/NikseBitmapImageSplitter2.cs +++ b/src/libuilogic/Ocr/NikseBitmapImageSplitter2.cs @@ -592,7 +592,10 @@ private static void SetBaseline(List list, int lineStart) } bottoms.Sort(); - var baseline = bottoms[bottoms.Count / 2]; + + // Lower median: with an even count the upper one is a descender in a two-glyph line + // ("Ну"), which made the other glyph look capital-height. + var baseline = bottoms[(bottoms.Count - 1) / 2]; for (var i = lineStart; i < list.Count; i++) { if (list[i].NikseBitmap != null) diff --git a/src/seconv/Program.cs b/src/seconv/Program.cs index 7c5e97e6609..c9591b10a93 100644 --- a/src/seconv/Program.cs +++ b/src/seconv/Program.cs @@ -667,17 +667,22 @@ private static int RunOptimizeNOcrCommand(string[] args) { var arg = args[i]; var name = arg.TrimStart('/', '-'); - var isOption = LooksLikeOption(arg) || arg.StartsWith("/output", StringComparison.OrdinalIgnoreCase); - if (isOption && name.StartsWith("output", StringComparison.OrdinalIgnoreCase)) + + // Only "output" itself, optionally with :value or =value, so a Unix path like + // /outputs/Latin.nocr stays the input file. + var rest = name.StartsWith("output", StringComparison.OrdinalIgnoreCase) ? name.Substring("output".Length) : null; + var isOutputOption = rest != null && (rest.Length == 0 || rest[0] is ':' or '=') && + (LooksLikeOption(arg) || arg.StartsWith("/output", StringComparison.OrdinalIgnoreCase)); + if (isOutputOption) { - var rest = name.Substring("output".Length); - if (rest.Length > 0 && rest[0] is ':' or '=') + if (rest!.Length > 0) { output = rest.Substring(1); } - else if (rest.Length == 0 && i + 1 < args.Length) + else { - output = args[++i]; + // A bare --output with no value is an error, not "optimize in place". + output = i + 1 < args.Length ? args[++i] : string.Empty; } continue; @@ -720,8 +725,8 @@ private static int RunOptimizeNOcrCommand(string[] args) string? backup = null; if (output == null) { - backup = input + ".bak"; - File.Copy(input, backup, true); + backup = Nikse.SubtitleEdit.UiLogic.Ocr.NOcrDbOptimizer.GetFreeBackupFileName(input); + File.Copy(input, backup, false); output = input; } diff --git a/src/ui/Features/Ocr/OcrViewModel.cs b/src/ui/Features/Ocr/OcrViewModel.cs index 1ea2b22d4a2..0509137d7e9 100644 --- a/src/ui/Features/Ocr/OcrViewModel.cs +++ b/src/ui/Features/Ocr/OcrViewModel.cs @@ -4310,7 +4310,7 @@ private async Task OptimizeNOcrDb() var fileName = _nOcrDb.FileName; var name = Path.GetFileNameWithoutExtension(fileName); - var backupFileName = fileName + ".bak"; + var backupFileName = NOcrDbOptimizer.GetFreeBackupFileName(fileName); var title = Se.Language.Ocr.OptimizeNOcrDatabase.TrimEnd('.'); var answer = await MessageBox.Show( Window, @@ -4323,13 +4323,19 @@ private async Task OptimizeNOcrDb() return; } + var maxWrongPixels = SelectedNOcrMaxWrongPixels; try { var (db, result) = await Task.Run(() => { - File.Copy(fileName, backupFileName, true); + File.Copy(fileName, backupFileName, false); var optimizedDb = new NOcrDb(fileName); - var optimizeResult = NOcrDbOptimizer.Optimize(optimizedDb, new NOcrDbOptimizerSettings(), null, (done, total) => + + // Keep what tells lookalikes apart under the loosest budget OCR will use here + // (the binary OCR fallback runs the last-ditch pass, which doubles it). + var settings = new NOcrDbOptimizerSettings(); + settings.MaxWrongPixels = Math.Max(settings.MaxWrongPixels, maxWrongPixels * 2); + var optimizeResult = NOcrDbOptimizer.Optimize(optimizedDb, settings, null, (done, total) => { if (done % 50 == 0 || done == total) { diff --git a/src/ui/Features/Shared/PickMatroskaTrack/PickMatroskaTrackViewModel.cs b/src/ui/Features/Shared/PickMatroskaTrack/PickMatroskaTrackViewModel.cs index beec4027060..a4255609c60 100644 --- a/src/ui/Features/Shared/PickMatroskaTrack/PickMatroskaTrackViewModel.cs +++ b/src/ui/Features/Shared/PickMatroskaTrack/PickMatroskaTrackViewModel.cs @@ -52,6 +52,10 @@ public partial class PickMatroskaTrackViewModel : ObservableObject private readonly SemaphoreSlim _previewLock = new(1, 1); private int _trackChangeToken; + // OK and Export are disabled while a preview is parsing: the caller (and Export) read the + // same MatroskaFile, and the caller disposes it once the dialog closes. + private int _previewsRunning; + // GetSubtitle reads (and caches) the whole cluster data on its first call; that is the only // slow part, so the progress window is only shown until that initial read has completed. private bool _clusterLoaded; @@ -114,7 +118,9 @@ private void Close() return _previewBluRayTrackNumber == trackNumber ? _previewBluRaySubtitles : null; } - [RelayCommand] + private bool IsFileFree() => _previewsRunning == 0; + + [RelayCommand(CanExecute = nameof(IsFileFree))] private async Task Export() { if (Window == null) @@ -302,9 +308,15 @@ private async Task WriteTextSubtitleFile(Window window, MatroskaTrackInfo trackI } } - [RelayCommand] + [RelayCommand(CanExecute = nameof(IsFileFree))] private void Ok() { + // Enter and double-click call this directly, past the disabled button. + if (!IsFileFree()) + { + return; + } + SelectedMatroskaTrack = SelectedTrack?.MatroskaTrackInfo; OkPressed = true; Close(); @@ -353,6 +365,27 @@ private async Task TrackChangedAsync() return; } + _previewsRunning++; + NotifyFileFreeChanged(); + try + { + await BuildPreviewAsync(trackInfo, matroskaFile, token); + } + finally + { + _previewsRunning--; + NotifyFileFreeChanged(); + } + } + + private void NotifyFileFreeChanged() + { + OkCommand.NotifyCanExecuteChanged(); + ExportCommand.NotifyCanExecuteChanged(); + } + + private async Task BuildPreviewAsync(MatroskaTrackInfo trackInfo, MatroskaFile matroskaFile, int token) + { await _previewLock.WaitAsync(); try { diff --git a/src/ui/Features/Tools/ImproveTimeCodes/SubtitleRetimer.cs b/src/ui/Features/Tools/ImproveTimeCodes/SubtitleRetimer.cs index 49f28c0a35f..b9266defd4d 100644 --- a/src/ui/Features/Tools/ImproveTimeCodes/SubtitleRetimer.cs +++ b/src/ui/Features/Tools/ImproveTimeCodes/SubtitleRetimer.cs @@ -704,14 +704,42 @@ public static void SnapToFrames(IReadOnlyList original, Placement[] placem var p = placements[i]; moved[i] = Math.Abs(p.StartSeconds - original[i].StartSeconds) >= 0.0005 || Math.Abs(p.EndSeconds - original[i].EndSeconds) >= 0.0005; + } + + for (var i = 0; i < placements.Length; i++) + { if (!moved[i]) { continue; } + var p = placements[i]; var start = Math.Round(p.StartSeconds * framesPerSecond, MidpointRounding.AwayFromZero) * frame; var end = Math.Round(p.EndSeconds * framesPerSecond, MidpointRounding.AwayFromZero) * frame; - placements[i] = p with { StartSeconds = start, EndSeconds = Math.Max(end, start + frame) }; + + // A line left where it was need not sit on a frame, so rounding to the nearest frame + // could cross the gap Settle kept to it: round away from it instead. + if (i > 0 && !moved[i - 1] && !OverlapsOnPurpose(original[i - 1], original[i])) + { + var earliest = placements[i - 1].EndSeconds + minGapSeconds; + if (start < earliest - 0.0005 && p.StartSeconds >= earliest - 0.0005) + { + start = Math.Ceiling(earliest * framesPerSecond - 0.0001) * frame; + } + } + + end = Math.Max(end, start + frame); + if (i + 1 < placements.Length && !moved[i + 1] && !OverlapsOnPurpose(original[i], original[i + 1])) + { + var latest = placements[i + 1].StartSeconds - minGapSeconds; + if (end > latest + 0.0005 && p.EndSeconds <= latest + 0.0005) + { + var flooredEnd = Math.Floor(latest * framesPerSecond + 0.0001) * frame; + end = flooredEnd >= start + frame ? flooredEnd : Math.Max(p.EndSeconds, start + 0.001); + } + } + + placements[i] = p with { StartSeconds = start, EndSeconds = end }; } // Rounding each side on its own can take a frame off the gap: the moved earlier line diff --git a/src/ui/Logic/Initializers/OcrInitializer.cs b/src/ui/Logic/Initializers/OcrInitializer.cs index 84c4e026c1f..ec1c50c17a8 100644 --- a/src/ui/Logic/Initializers/OcrInitializer.cs +++ b/src/ui/Logic/Initializers/OcrInitializer.cs @@ -1,9 +1,11 @@ using Avalonia.Platform; -using Nikse.SubtitleEdit.Logic.Compression; using Nikse.SubtitleEdit.Logic.Config; using System; +using System.Collections.Generic; using System.IO; using System.IO.Compression; +using System.Linq; +using System.Security.Cryptography; using System.Threading.Tasks; namespace Nikse.SubtitleEdit.Logic.Initializers; @@ -13,30 +15,41 @@ public interface IOcrInitializer Task UpdateOcrDictionariesIfNeeded(); } -public class OcrInitializer(IZipUnpacker zipUnpacker) : IOcrInitializer +public class OcrInitializer : IOcrInitializer { + /// + /// Records the hash of every file last unpacked from Ocr.zip. A file whose hash no longer + /// matches was trained or optimized by the user and survives a version upgrade. + /// + private const string ShippedHashesFileName = "shipped-hashes.txt"; + public async Task UpdateOcrDictionariesIfNeeded() { if (await NeedsUpdate()) { - await Unpack(); + Unpack(onlyMissing: false); WriteNewVersionFile(); } else { - UnpackMissingFiles(); + // Databases added in a later version (e.g. Cyrillic.nocr) still need to arrive. + Unpack(onlyMissing: true); } } /// - /// A kept (user-trained) Latin.nocr skips the full unpack, so databases added in a later - /// version (e.g. Cyrillic.nocr) would never arrive. Add those without touching existing files. + /// Unpacks Ocr.zip into the OCR folder. Existing files are left alone when + /// is set, or when the user changed them since they were + /// unpacked (training, Optimize database), so an upgrade never discards that work. /// - private static void UnpackMissingFiles() + private static void Unpack(bool onlyMissing) { try { var outputDir = Se.OcrFolder; + Directory.CreateDirectory(outputDir); + var shippedHashes = ReadShippedHashes(outputDir); + var changed = false; using var zipStream = AssetLoader.Open(new Uri("avares://SubtitleEdit/Assets/Ocr.zip")); using var archive = new ZipArchive(zipStream, ZipArchiveMode.Read); foreach (var entry in archive.Entries) @@ -47,19 +60,73 @@ private static void UnpackMissingFiles() } var fileName = Path.Combine(outputDir, entry.FullName); - if (!File.Exists(fileName)) + if (File.Exists(fileName) && (onlyMissing || IsUserModified(entry.FullName, fileName, shippedHashes))) { - Directory.CreateDirectory(Path.GetDirectoryName(fileName)!); - entry.ExtractToFile(fileName); + continue; } + + Directory.CreateDirectory(Path.GetDirectoryName(fileName)!); + entry.ExtractToFile(fileName, overwrite: true); + shippedHashes[entry.FullName] = GetFileHash(fileName); + changed = true; + } + + if (changed) + { + WriteShippedHashes(outputDir, shippedHashes); } } catch (Exception exception) { - Se.LogError(exception, "Could not unpack missing OCR files"); + Se.LogError(exception, "Could not unpack OCR files"); } } + private static bool IsUserModified(string entryName, string fileName, Dictionary shippedHashes) + { + if (shippedHashes.TryGetValue(entryName, out var shippedHash)) + { + return !string.Equals(GetFileHash(fileName), shippedHash, StringComparison.OrdinalIgnoreCase); + } + + // Unpacked before hashes were recorded: a Latin.nocr well above the shipped size was trained. + return entryName.Equals("Latin.nocr", StringComparison.OrdinalIgnoreCase) && + new FileInfo(fileName).Length > 1_700_000; + } + + private static string GetFileHash(string fileName) + { + using var stream = File.OpenRead(fileName); + return Convert.ToHexString(SHA256.HashData(stream)); + } + + private static Dictionary ReadShippedHashes(string outputDir) + { + var result = new Dictionary(StringComparer.OrdinalIgnoreCase); + var fileName = Path.Combine(outputDir, ShippedHashesFileName); + if (!File.Exists(fileName)) + { + return result; + } + + foreach (var line in File.ReadAllLines(fileName)) + { + var parts = line.Split('\t'); + if (parts.Length == 2 && parts[0].Length > 0 && parts[1].Length > 0) + { + result[parts[0]] = parts[1]; + } + } + + return result; + } + + private static void WriteShippedHashes(string outputDir, Dictionary shippedHashes) + { + var lines = shippedHashes.OrderBy(p => p.Key, StringComparer.OrdinalIgnoreCase).Select(p => p.Key + "\t" + p.Value); + File.WriteAllLines(Path.Combine(outputDir, ShippedHashesFileName), lines); + } + private static void WriteNewVersionFile() { string outputDir = Se.OcrFolder; @@ -88,12 +155,6 @@ private static async Task NeedsUpdate() return true; } - var latinNOcrFileName = Path.Combine(outputDir, "Latin.nocr"); - if (File.Exists(latinNOcrFileName) && new FileInfo(latinNOcrFileName).Length > 1_700_000) - { - return false; - } - var versionFileName = Path.Combine(outputDir, "version.txt"); if (!File.Exists(versionFileName)) { @@ -112,11 +173,4 @@ private static async Task NeedsUpdate() return false; } - - private async Task Unpack() - { - var zipUri = new Uri("avares://SubtitleEdit/Assets/Ocr.zip"); - await using var zipStream = AssetLoader.Open(zipUri); - zipUnpacker.UnpackZipStream(zipStream, Se.OcrFolder); - } } diff --git a/tests/UI/Features/Tools/ImproveTimeCodes/SubtitleRetimerTests.cs b/tests/UI/Features/Tools/ImproveTimeCodes/SubtitleRetimerTests.cs index 3b59898bc63..d6b8d411cc1 100644 --- a/tests/UI/Features/Tools/ImproveTimeCodes/SubtitleRetimerTests.cs +++ b/tests/UI/Features/Tools/ImproveTimeCodes/SubtitleRetimerTests.cs @@ -541,6 +541,27 @@ public void SnapToFrames_PutsMovedLinesOnFrames_AndKeepsTheGap() Assert.Equal(20.0, placements[2].StartSeconds, 6); } + [Fact] + public void SnapToFrames_NextToALineLeftOffTheFrames_RoundsAwayFromIt() + { + // 25 fps, no minimum gap. a and c were not moved and sit between frames; rounding b to the + // nearest frame would start it 10 ms inside a and end it 5 ms inside c. + var original = new List { new("a", 8, 10.05), new("b", 10.2, 11), new("c", 11.035, 13) }; + var placements = new[] + { + new SubtitleRetimer.Placement(8, 10.05, false), + new SubtitleRetimer.Placement(10.055, 11.03, true), + new SubtitleRetimer.Placement(11.035, 13, false), + }; + + SubtitleRetimer.SnapToFrames(original, placements, 25, 0); + + Assert.Equal(10.08, placements[1].StartSeconds, 6); + Assert.Equal(11.0, placements[1].EndSeconds, 6); + Assert.True(placements[1].StartSeconds >= placements[0].EndSeconds); + Assert.True(placements[1].EndSeconds <= placements[2].StartSeconds); + } + [Fact] public void Settle_TwoLinesThatMayNotMove_AreLeftAlone() { diff --git a/tests/libuilogic/Ocr/NOcrCyrillicTests.cs b/tests/libuilogic/Ocr/NOcrCyrillicTests.cs index 2bd92ebeab3..4633d885736 100644 --- a/tests/libuilogic/Ocr/NOcrCyrillicTests.cs +++ b/tests/libuilogic/Ocr/NOcrCyrillicTests.cs @@ -107,6 +107,19 @@ public void Splitter_SetsLineBaseline_DescenderAboveBaselineIsXHeight() Assert.True(capital.HeightAboveBaseline > a.HeightAboveBaseline + 5); } + [Fact] + public void Splitter_TwoGlyphLineWithDescender_BaselineIsTheLetterBottom() + { + // With two glyphs the upper median is the descender's bottom, which made "у" look cap high. + var letters = Split("Ну", out _).Where(p => p.NikseBitmap != null).ToList(); + + Assert.Equal(2, letters.Count); + var capital = letters[0]; + var u = letters[1]; + Assert.Equal(capital.Top + capital.NikseBitmap!.Height, capital.Baseline); + Assert.True(u.NikseBitmap!.Height > u.HeightAboveBaseline, "the descender is below the baseline"); + } + private static ImageSplitterItem2 Glyph(int height, int top, int baseline = 40) => new(0, top, new NikseBitmap2(20, height)) { Top = top, Baseline = baseline }; diff --git a/tests/libuilogic/Ocr/NOcrDbOptimizerTests.cs b/tests/libuilogic/Ocr/NOcrDbOptimizerTests.cs index e9e24a70887..0767ba61e22 100644 --- a/tests/libuilogic/Ocr/NOcrDbOptimizerTests.cs +++ b/tests/libuilogic/Ocr/NOcrDbOptimizerTests.cs @@ -139,4 +139,26 @@ public void AbortLeavesDatabaseUnchanged() Assert.Null(result); Assert.Equal(before, db.OcrCharacters.Sum(LineCount)); } + + [Fact] + public void GetFreeBackupFileName_NeverReturnsAnExistingBackup() + { + var folder = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); + Directory.CreateDirectory(folder); + try + { + var fileName = Path.Combine(folder, "Latin.nocr"); + Assert.Equal(fileName + ".bak", NOcrDbOptimizer.GetFreeBackupFileName(fileName)); + + File.WriteAllText(fileName + ".bak", "original"); + Assert.Equal(fileName + ".2.bak", NOcrDbOptimizer.GetFreeBackupFileName(fileName)); + + File.WriteAllText(fileName + ".2.bak", "second"); + Assert.Equal(fileName + ".3.bak", NOcrDbOptimizer.GetFreeBackupFileName(fileName)); + } + finally + { + Directory.Delete(folder, true); + } + } } From 2052eaeea6e76f7acf098d25ea60e2f46bc85cc6 Mon Sep 17 00:00:00 2001 From: TOKUZO Date: Mon, 12 Oct 2026 03:11:09 +0900 Subject: [PATCH 073/306] Update Japanese translation for rc-2 --- src/ui/Assets/Languages/Japanese.json | 162 +++++++++++++++++++++++++- 1 file changed, 159 insertions(+), 3 deletions(-) diff --git a/src/ui/Assets/Languages/Japanese.json b/src/ui/Assets/Languages/Japanese.json index 30086cb3e72..c74dfc3d9bd 100644 --- a/src/ui/Assets/Languages/Japanese.json +++ b/src/ui/Assets/Languages/Japanese.json @@ -1,6 +1,6 @@ { "title": "Subtitle Edit", - "version": "v5.3.0-beta23", + "version": "v5.3.0-rc2", "translatedBy": "", "cultureName": "jp-JP", "general": { @@ -372,6 +372,8 @@ "moveAllShotChangeOneFrameForward": "全てのカット切替を1フレーム前に移動", "multiMode": "マルチモード", "moveDown": "下に移動", + "increase": "増加", + "decrease": "減少", "moveUp": "上に移動", "moveToTop": "先頭に移動", "moveToBottom": "末尾に移動", @@ -593,6 +595,8 @@ "showStartColumn": "\"スタート\"列を表示", "showHideColumn": "\"非表示\"列を表示", "showHistory": "履歴を表示", + "clearHistory": "履歴を消去", + "removeFromHistory": "履歴から削除", "showLayerColumn": "\"レイヤー\" 列を表示", "showPreview": "プレビューを表示", "showShotChangesList": "ショット変更リストを表示", @@ -654,12 +658,14 @@ "styleExaggeration": "スタイルの誇張", "styles": "スタイル", "subtitleFile": "字幕ファイル", + "subtitleFileChangedOutsideX": "ファイル「{0}」がSubtitle Edit以外で変更されました。\n\n再読み込みしますか?(現在の変更内容は失われます)", "subtitleFileName": "字幕ファイル名", "subtitleFileSaved": "字幕ファイルが保存されました", "subtitleFileSavedToX": "字幕ファイルが{0}に保存されました", "subtitleFiles": "字幕ファイル", "subtitleFormats": "字幕フォーマット", "subtitleLoadedX": "字幕が読み込まれました: {0}", + "subtitleReloadedChangedOutsideX": "外部で変更された字幕を再読み込みしました", "suffix": "接尾語", "suggestions": "候補", "sync": "同期", @@ -743,6 +749,14 @@ "videoCustom3ForwardX": "動画、カスタム移動({0:#,###,##0} ms)進む 3", "videoCustom4BackX": "動画、カスタム移動({0:#,###,##0} ms)戻る 4", "videoCustom4ForwardX": "動画、カスタム移動({0:#,###,##0} ms)進む 4", + "videoCustom1BackAndPauseX": "動画:指定したミリ秒({0:#,###,##0})戻って一時停止 1", + "videoCustom1ForwardAndPauseX": "動画:指定したミリ秒({0:#,###,##0})進んで一時停止 1", + "videoCustom2BackAndPauseX": "動画:指定したミリ秒({0:#,###,##0})戻って一時停止 2", + "videoCustom2ForwardAndPauseX": "動画:指定したミリ秒({0:#,###,##0})進んで一時停止 2", + "videoCustom3BackAndPauseX": "動画:指定したミリ秒({0:#,###,##0})戻って一時停止 3", + "videoCustom3ForwardAndPauseX": "動画:指定したミリ秒({0:#,###,##0})進んで一時停止 3", + "videoCustom4BackAndPauseX": "動画:指定したミリ秒({0:#,###,##0})戻って一時停止 4", + "videoCustom4ForwardAndPauseX": "動画:指定したミリ秒({0:#,###,##0})進んで一時停止 4", "videoExtension": "動画ファイルの拡張子", "videoFile": "動画ファイル", "videoFileGenerated": "動画ファイルを生成しました", @@ -878,6 +892,7 @@ "multipleReplace": "_複数置換", "fillSelectedLinesWithClipboard": "選択行にクリップボードのテキストを入力", "goToLineNumber": "_行番号へ移動", + "commandPalette": "コマンドパレット...", "rightToLeftMode": "右から左モード", "modifySelectionDotDotDot": "選択範囲を変更", "tools": " ツール", @@ -1070,6 +1085,9 @@ "charactersPerSecond": "文字/秒: {0}", "chooseColumn": "列を選択", "columnPaste": "列を貼り付け", + "commandPaletteTitle": "コマンドパレット", + "commandPaletteSearchPlaceholder": "コマンドを検索...", + "commandPaletteNoCommandsFound": "一致するコマンドがありません", "createdEmptyTranslation": "現在の行と同じ空行の作成", "deleteText": "テキストを削除", "deleteTextAndShiftCellsUp": "テキストを削除してセルを上に移動", @@ -1162,6 +1180,7 @@ "originalIsReadOnlyNotSaved": "別字幕は読み取り専用の参照として開かれ、保存されません", "originalIsEmptyNotSaved": "別字幕にテキストがないため、保存されませんでした", "originalIsReadOnlyReference": "別字幕を読み取り専用の参照として開いています", + "closeTranslationQuestionXY": "作業字幕「{0}」を閉じますか?\n\n別字幕「{1}」が編集字幕になります。", "allowEditOfOriginalSubtitle": "別字幕の編集を許可", "showAllOriginalLinesX": "別字幕の全行を表示({0}行は一致する行がありません)", "showAllOriginalLinesHint": "一致しない行は追加行として表示されるため、翻訳に不足している部分を確認できます。", @@ -2178,6 +2197,18 @@ "addFolderRecursiveDotDotDot": "サブフォルダーも含めて追加...", "selectFolderToConvert": "変換するファイルが入ったフォルダーを選択", "includeSubfolders": "フォルダーの追加時にサブフォルダーを含める", + "videoFilesWhenAddingFolder": "フォルダー追加時の動画ファイル(mkv、mp4、tsなど)", + "videoFilesAsk": "確認する", + "videoFilesInclude": "動画に含まれる字幕トラックも追加", + "videoFilesSkip": "スキップ", + "folderContainsXVideoFiles": "このフォルダーには{0}個の動画ファイル(mkv、mp4、tsなど)があります。\n\n動画に含まれる字幕トラックも追加しますか?それとも字幕ファイルのみ追加しますか?", + "addVideoFiles": "動画ファイルを追加", + "skipVideoFiles": "字幕ファイルのみ", + "doNotAskAgainVideoFiles": "次回から確認しない(Batch convertの設定で変更できます)", + "settingsSectionOutput": "出力", + "settingsSectionAddingFiles": "ファイルの追加", + "settingsSectionImageBasedSubtitles": "画像ベースの字幕(OCR)", + "settingsSectionOther": "その他", "keepSourceFileTimestamp": "出力ファイルの日付・時刻を元ファイルの日時に維持", "preventSleepWhileConverting": "変換中はコンピューターをスリープさせない", "scanningFolderX": "{0} をスキャン中...", @@ -2500,7 +2531,21 @@ "correctWordsX": "正しい単語: {0}", "namesX": "固有名詞: {0}", "addedToDictionaryX": "辞書に追加: {0}", - "doNotShowThisAgain": "今後このメッセージを表示しない" + "doNotShowThisAgain": "今後このメッセージを表示しない", + "editUseAlwaysList": "「常に使用」リストを編集...", + "useAlwaysListTitle": "スペルチェック - 「常に使用」リスト", + "useAlwaysListHeader": "「常に使用」リスト", + "useAlwaysListDescription": "スペルチェックで自動的に置換する単語です。「すべて変更」または「常に使用」で保存されます。ペアは、単語がスペルミスの場合のみ使用されます。", + "useAlwaysWord": "スペルミスの単語", + "xPairs": "{0:#,##0}件は未使用", + "xUnusedPairs": "{0:#,##0}件は未使用", + "useAlwaysUnusedHint": "未使用 - この単語は正しく綴られているか、名前やユーザー辞書の単語のため、スペルチェックでスペルミスとして検出されません", + "removeUnused": "未使用を削除", + "useAlwaysEmpty": "この言語には「常に使用」ペアがまだありません", + "useAlwaysEmptyHint": "スペルチェックで「すべて変更」または「常に使用」をクリックすると、ペアが追加されます。", + "useAlwaysNoMatch": "検索に一致するペアはありません", + "useAlwaysSameWord": "置換後の単語は元の単語と異なる必要があります。", + "useAlwaysRememberOff": "設定で「『常に使用』リストを記憶」がオフになっているため、スペルチェックではこのリストを使用または保存しません。" }, "video": { "burnIn": { @@ -2681,6 +2726,12 @@ "speakFromCurrentLineHint": "現在の行から順に字幕を読み上げます。停止するにはEscキーを押すか、別の行をクリックしてください。", "stopSpeaking": "読み上げを停止", "speakingLineX": "行 {0} を読み上げ中...(Escで停止)", + "playWithSpeechTitle": "読み上げ再生", + "playWithSpeechDotDotDot": "読み上げ再生...", + "playWithSpeechHint": "動画を再生し、各字幕行のタイミングでその行を読み上げます。一時停止やシークは通常どおり操作できます。停止するにはEscキーを押します。", + "lowerVideoVolumeWhileSpeaking": "読み上げ中は動画音量を下げる", + "pauseVideoWhenSpeechRunsLate": "読み上げが次の字幕行に遅れたら動画を一時停止", + "waitingForSpeechLineX": "字幕行 {0} の読み上げを生成中...(Escキーで停止)", "voiceKindClone": "クローン音声", "voiceKindPreset": "組み込み", "voiceKindModel": "音声モデル", @@ -2728,6 +2779,7 @@ "cloneVoicePerLinePreparing": "動画から各行の声を取得しています...", "cloneVoicePerLineNeedsVideo": "各行の声をクローンするには、その字幕に対応する動画が必要です。動画を開いてもう一度試してください。", "cloneVoicePerLineNoClips": "動画から音声を取得できなかったため、クローンする声がありません。動画に音声トラックがあることを確認してください。", + "cloneVoicePerLineTranscribeClipsXYZ": "{0}は、動画のその行で何が話されているか分かっている場合にのみ、その行の声をクローンできます。{2}行中{1}行について内容が分かっていません。通常は元の言語の字幕(ファイル > 別字幕を開く...)から取得されます。\n\nこれらの行を音声認識で動画から文字起こししますか?\n\nはい:動画で話されている言語と音声認識エンジンを選択し、読み上げを生成する前に参照クリップを文字起こしします。\nいいえ:そのまま生成します。この場合、該当する行は動画内の声ではなく通常の音声で読み上げられます。", "voiceCloneConsentTitle": "音声クローン - 始める前に", "voiceCloneConsentHeader": "音声をクローンしようとしています", "voiceCloneConsentIntro": "音声クローンは実在する人物の声を複製します。多くの地域でこれには規則があり、EUではSubtitle Editではなく、あなた自身に法的義務が生じます。必ず一度お読みください。", @@ -2744,6 +2796,12 @@ "advancedTtsSettings": "高度なTTS設定", "advancedTtsAudioProcessing": "音声処理", "advancedTtsOutput": "出力", + "advancedTtsVoiceCloning": "音声のクローン作成", + "cleanCloneReferences": "音声クローンの元音声をクリーニング(Sidon)", + "cleanCloneReferencesDescription": "動画から切り出した音声や、音声マネージャーに読み込んだ録音から、音楽・ノイズ・残響を除去します。audio.cpp の Sidon 音声復元モデルを使用します(940 MB のダウンロードが必要)。背景音まで含めて声が複製されるのを防ぎますが、非常に小さな声や複数の声が重なった部分は、こもった音になることがあります。", + "cleaningCloneReferencesDotDotDot": "音声クローンの元音声をクリーニング中...", + "downloadSidonTitle": "Sidonをダウンロードしますか?", + "downloadSidonQuestionX": "音声クローンの元音声をクリーニングするには、Sidon 音声復元モデル({0})が必要です。\n\n今すぐダウンロードしますか? ダウンロードしない場合は、元音声をそのまま使用します。", "proAudioPostProcessing": "プロ音声後処理", "proAudioPostProcessingDescription": "各セグメントにEQによる音質補正、ノイズゲート、コンプレッサー、ラウドネス正規化(-16 LUFS)、フェードイン/アウトを適用します", "generatingSpeechSegmentXOfY": "音声を生成中:{1}セグメント中{0}セグメント", @@ -3210,6 +3268,7 @@ "syncPoints": "同期ポイント", "pointSync": "ポイント同期", "pointSyncViaOther": "他の字幕を基準にポイント同期", + "syncPointCandidateAfterInfo": "後に{0}秒以上の無音が続く字幕行を強調表示します。信頼できる同期ポイントになりやすい行です", "gapAfter": "後ろの空白", "adjustAllShortcuts": "キーボード ショートカット:\r\n\r\n• Shift + 左/右: 10ミリ秒 移動\r\n• Ctrl + 左/右: 100ミリ秒 移動\r\n• Alt + 左/右: 500ミリ秒 移動", "adjustAllShortcutsFrames": "キーボードショートカット:\r\n\r\n• Shift + ←/→: 1フレーム移動\r\n• Ctrl + ←/→: 10フレーム移動\r\n• Alt + ←/→: 1秒移動", @@ -3373,6 +3432,7 @@ "promptBeforeDelete": "削除前に確認メッセージを表示する", "rememberPositionAndSize": "ウィンドウの位置とサイズを記憶する", "titleBarFullFileName": "タイトルバーにファイルのフルパスを表示", + "showRecentFiles": "最近使ったファイルを表示(再度開く)", "openLastFileOnStart": "起動時に最後に開いたファイルを開く", "autoSave": "自動保存(編集中のファイルを自動的に保存)", "autoBackupOn": "自動バックアップ", @@ -3400,6 +3460,7 @@ "waveformUseSkiaRenderer": "実験的な高速レンダラーを使用", "waveformCenterVideoPositionAlsoWhenPaused": "一時停止中も再生位置を中央に保つ", "waveformSelectCurrentSubtitleWhilePaused": "一時停止中も現在の字幕行を選択", + "waveformRememberZoom": "ズームを記憶(水平・垂直)", "waveformShowToolbar": "ツールバーを表示", "waveformShowToolbarEdit": "ツールバーを編集", "waveformShowToolbarEditLabel": "波形/スペクトログラムのツールバー", @@ -3509,6 +3570,7 @@ "textBoxLimitNewLines": "テキストボックスの行数を制限します", "mpvOpenGl": "GPU (OpenGL):高速,滑らか", "mpvSoftwareRendering": "CPUのみ:互換性重視(低速)", + "mpvMetalRendering": "libmpv - Metal(試験的機能、再起動が必要)", "mpvWidRendering": "OS処理 (環境によっては非対応)", "waveFormsAndSpectrogramFoldersContainsX": "Waveformsフォルダとspectrogramフォルダに{0}が含まれています", "deleteWaveformAndSpectrogramFoldersQuestion": "WaveformsファイルとSpectrogramファイルを削除しますか?", @@ -3602,6 +3664,10 @@ "usePositionFromSubtitleFile": "字幕ファイルの位置を使用(TTML/PAC/EBU STL)", "marginIsPartOfSubtitleArea": "余白は字幕領域の一部です", "textJustify": "行を均等配置", + "deinterlace": "動画のインターレース解除", + "deinterlaceOff": "オフ", + "deinterlaceAuto": "自動(インターレース映像のみ)", + "deinterlaceAlways": "常に有効", "pixelWidthInfo": "緑色の線 = 最大幅制限 | 赤色の領域 = テキストが制限を超えています", "spellCheckEngineHunSpelll": "Hunspell", "spellCheckEngineMsWord": "MS Word", @@ -4157,6 +4223,19 @@ "startTraining": "学習開始", "abortTraining": "学習中止", "trainingOptions": "学習オプション", + "trainNOcrSubtitle": "インストール済みのフォントで文字を描画し、その形状をnOCRに学習させます。字幕に使用されているフォントを選択してください。", + "xFontsSelected": "{0:#,##0} 個選択", + "subtitleFonts": "字幕フォント", + "emptyDatabase": "空のデータベース", + "startFromDatabase": "開始元", + "startFromDatabaseHint": "既存のデータベースのコピーに学習した文字を追加する(推奨)か、空のデータベースから開始します", + "fontSizes": "フォントサイズ", + "fontSizesHint": "描画するフォントサイズをピクセル単位でカンマ区切りで指定します。例: \"30, 40\"", + "numberOfLineSegmentsHint": "文字ごとに確認する線分の数。多いほど判定が厳密になりますが、処理は遅くなります", + "enterDatabaseName": "データベース名を入力", + "trainingFontXSizeYCharacterZ": "「{0}」を {1} px で学習中: {2}", + "xLearned": "{0:#,##0} 個学習済み", + "xSkipped": "{0:#,##0} 個スキップ", "charactersToTrain": "学習する文字", "letterCombinationsToTrain": "1つの画像に分割される可能性のある文字の組み合わせ", "importCharactersFromSubtitleFile": "字幕ファイルから文字をインポート...", @@ -4183,6 +4262,8 @@ "drawZoomOut": "縮小 (Ctrl+-)", "drawResetView": "ビューのリセット (Ctrl+0)", "drawToggleGrid": "グリッドの表示/非表示 (Ctrl+G)", + "drawTogglePreview": "プレビューの切り替え (F9)", + "drawPreviewFailed": "プレビューに失敗しました - libass(字幕フィルター)対応のffmpegが必要です", "drawCopyToClipboard": "クリップボードにコピー (Ctrl+C)", "drawShapes": "図形", "drawSelectedPoint": "選択点", @@ -4190,6 +4271,81 @@ "drawSelectedLayer": "選択レイヤー", "drawToolX": "ツール: {0}", "drawHelpText": "クリックして点を追加 • Enter/F8で図形を閉じる • Shiftキーを押しながらドラッグでパン • Ctrlキーを押しながらスクロールでズーム", + "drawMoveToLayer": "レイヤーに移動", + "drawFlipHorizontal": "左右反転", + "drawFlipVertical": "上下反転", + "drawHideShape": "図形を非表示", + "drawShowShape": "図形を表示", + "drawHideLayer": "レイヤーを非表示", + "drawShowLayer": "レイヤーを表示", + "drawDeleteLayer": "レイヤーを削除", + "drawDeletePoint": "点を削除", + "drawCancelDrawing": "描画をキャンセル", + "drawLayerX": "レイヤー {0}", + "drawImportSvg": "SVG画像をインポート...", + "drawRotateClockwise": "時計回りに90°回転", + "drawConvertToCurve": "線分を曲線に変換", + "drawBackground": "背景", + "drawPosition": "位置", + "drawZoom": "ズーム", + "drawShapeTool": "図形ツール", + "drawColorPickerTool": "図形または背景から色を取得", + "drawShapeLibrary": "図形ライブラリ", + "drawShapeLibraryHint": "キャンバス上をドラッグして図形を配置します。クリックすると既定のサイズで配置されます。Shiftキーを押すと縦横比が維持されます。", + "drawAddToLibrary": "図形ライブラリに追加...", + "drawRemoveFromLibrary": "図形ライブラリから削除", + "drawShapeName": "図形名", + "drawCategorySpeechBubbles": "吹き出し", + "drawCategoryArrows": "矢印", + "drawCategoryBasicShapes": "基本図形", + "drawCategorySymbols": "記号", + "drawCategoryMyShapes": "マイ図形", + "drawShapeSpeechBubble": "吹き出し", + "drawShapeSpeechBubbleRight": "右向きのしっぽ付き吹き出し", + "drawShapeSpeechBubbleUp": "上向きのしっぽ付き吹き出し", + "drawShapeOvalBubble": "楕円形の吹き出し", + "drawShapeBoxBubble": "四角い吹き出し", + "drawShapeThoughtBubble": "思考の吹き出し", + "drawShapeShoutBubble": "叫びの吹き出し", + "drawShapeCaptionBox": "字幕枠", + "drawShapeArrow": "矢印", + "drawShapeDoubleArrow": "両矢印", + "drawShapeChevron": "山形", + "drawShapeCurvedArrow": "曲線矢印", + "drawShapeRoundedRectangle": "角丸長方形", + "drawShapeTriangle": "三角形", + "drawShapeDiamond": "ひし形", + "drawShapePentagon": "五角形", + "drawShapeHexagon": "六角形", + "drawShapeOctagon": "八角形", + "drawShapeStar": "星形", + "drawShapeRing": "リング", + "drawShapePlus": "プラス記号", + "drawShapeBanner": "バナー", + "drawShapeHeart": "ハート", + "drawShapeCheck": "チェックマーク", + "drawShapeMusicNote": "音符", + "drawShapeMusicNotes": "音符(複数)", + "drawShapeLightning": "稲妻", + "drawShapeCloud": "雲", + "drawShapeMoon": "月", + "drawShapeBadge": "バッジ", + "drawSize": "サイズ", + "drawLayer": "レイヤー", + "drawBackgroundVideoFrame": "動画フレーム(現在位置)", + "drawBackgroundVideoFrameAt": "動画フレームの位置を指定...", + "drawBackgroundImage": "画像ファイル...", + "drawBackgroundNone": "背景なし", + "drawBackgroundStretch": "画像をキャンバスに合わせて引き伸ばす", + "drawBackgroundOpacity": "背景の不透明度", + "drawBackgroundVideoFrameFailed": "{0} の位置から動画フレームを取得できませんでした。", + "drawImages": "画像", + "drawConvertToLine": "セグメントを直線に変換", + "drawConvertShapeToCurves": "すべての直線を曲線に変換", + "drawConvertShapeToLines": "すべての曲線を直線に変換", + "drawRotateCounterClockwise": "反時計回りに90°回転", + "drawSvgImages": "SVG画像", + "drawSvgNoShapes": "SVGファイルに塗りつぶされた図形や線が見つかりませんでした。", "progressBarTitle": "ASS進行状況バー", "progressBarSettings": "進行状況バー", "mouseOverColor": "マウスオーバー時の色", @@ -4460,4 +4616,4 @@ "exportGeneratedX": "生成日時: {0}", "exportMadeWithSubtitleEdit": "Subtitle Editで作成" } -} \ No newline at end of file +} From d6cea7367ed5d32f8d616fc08cefe931c78e11ee Mon Sep 17 00:00:00 2001 From: niksedk Date: Sun, 11 Oct 2026 20:17:19 +0200 Subject: [PATCH 074/306] Point sync via other: free image thumbnails instead of caching full frames Each image row's cell called OcrSubtitleItem.GetBitmapCropped, which caches the full decoded frame on the item for the life of the window (1.6 MB per 720x576 DVB frame), plus a new Avalonia bitmap per recycle that was never disposed. Scrolling a long DVB/PGS track could hold gigabytes. New GetBitmapCroppedUncached decodes fresh and frees the frame and crop; the cell disposes the bitmap it showed before when it is recycled. The Ctrl+P preview uses it too. Co-Authored-By: Claude Opus 5.5 --- src/ui/Features/Ocr/OcrSubtitleItem.cs | 22 +++++++ .../PointSyncViaOtherViewModel.cs | 2 +- .../PointSyncViaOtherWindow.cs | 7 ++- .../Ocr/OcrSubtitleItemUncachedBitmapTests.cs | 57 +++++++++++++++++++ 4 files changed, 85 insertions(+), 3 deletions(-) create mode 100644 tests/UI/Features/Ocr/OcrSubtitleItemUncachedBitmapTests.cs diff --git a/src/ui/Features/Ocr/OcrSubtitleItem.cs b/src/ui/Features/Ocr/OcrSubtitleItem.cs index f8c3ae690fc..a07348b3bf8 100644 --- a/src/ui/Features/Ocr/OcrSubtitleItem.cs +++ b/src/ui/Features/Ocr/OcrSubtitleItem.cs @@ -89,6 +89,28 @@ public Bitmap GetBitmapCropped(byte alphaThreshold = 0) return cropped.ToAvaloniaBitmap(); } + /// + /// Like , but decodes the image fresh and keeps nothing: for + /// lists that only show thumbnails, where caching the full frame (1.6 MB for a 720x576 DVB + /// image) for every row scrolled past adds up to gigabytes. + /// + public Bitmap GetBitmapCroppedUncached(byte alphaThreshold = 0) + { + using var source = GetSkBitmapClean(); + var cropped = source.CropTransparentColors(alphaThreshold); + try + { + return cropped.ToAvaloniaBitmap(); + } + finally + { + if (!ReferenceEquals(cropped, source)) + { + cropped.Dispose(); + } + } + } + public bool IsForced => _ocrSubtitle.GetIsForced(_index); /// Index into the underlying IOcrSubtitle source - stable across filtering/deletion of the view collection. diff --git a/src/ui/Features/Sync/PointSyncViaOther/PointSyncViaOtherViewModel.cs b/src/ui/Features/Sync/PointSyncViaOther/PointSyncViaOtherViewModel.cs index a2f704c27b1..bebc626fc7d 100644 --- a/src/ui/Features/Sync/PointSyncViaOther/PointSyncViaOtherViewModel.cs +++ b/src/ui/Features/Sync/PointSyncViaOther/PointSyncViaOtherViewModel.cs @@ -463,7 +463,7 @@ private async Task ViewSelectedOtherImage() var title = $"{Se.Language.Ocr.ShowImage} - #{line.Number} {new TimeCode(line.StartTime).ToDisplayString()}"; var result = await _windowService.ShowDialogAsync(Window, vm => { - vm.Initialize(title, line.ImageItem.GetBitmapCropped()); + vm.Initialize(title, line.ImageItem.GetBitmapCroppedUncached()); }); var index = Othersubtitles.IndexOf(line); diff --git a/src/ui/Features/Sync/PointSyncViaOther/PointSyncViaOtherWindow.cs b/src/ui/Features/Sync/PointSyncViaOther/PointSyncViaOtherWindow.cs index e660d636551..a3df1f08a8b 100644 --- a/src/ui/Features/Sync/PointSyncViaOther/PointSyncViaOtherWindow.cs +++ b/src/ui/Features/Sync/PointSyncViaOther/PointSyncViaOtherWindow.cs @@ -310,12 +310,15 @@ private static SeTableViewColumn MakeImageColumn(PointSyncViaOtherViewModel vm) Child = image, }; - // Cells are recycled, so the bitmap follows the data context. + // Cells are recycled, so the bitmap follows the data context. Each cell owns its + // bitmap: decoded fresh, and the one it showed before is freed. container.DataContextChanged += (_, _) => { + var previous = image.Source as IDisposable; image.Source = container.DataContext is PointSyncOtherImageLine line - ? line.ImageItem.GetBitmapCropped() + ? line.ImageItem.GetBitmapCroppedUncached() : null; + previous?.Dispose(); container.IsVisible = image.Source != null; }; diff --git a/tests/UI/Features/Ocr/OcrSubtitleItemUncachedBitmapTests.cs b/tests/UI/Features/Ocr/OcrSubtitleItemUncachedBitmapTests.cs new file mode 100644 index 00000000000..2bf23d6d44d --- /dev/null +++ b/tests/UI/Features/Ocr/OcrSubtitleItemUncachedBitmapTests.cs @@ -0,0 +1,57 @@ +using Avalonia.Headless.XUnit; +using Nikse.SubtitleEdit.Features.Ocr; +using Nikse.SubtitleEdit.Features.Ocr.OcrSubtitle; +using SkiaSharp; + +namespace UITests.Features.Ocr; + +/// +/// Point sync shows a thumbnail per image row; the cached GetBitmapCropped kept every row's full +/// frame alive (gigabytes for a long DVB track), so the thumbnails decode fresh and free it. +/// +public class OcrSubtitleItemUncachedBitmapTests +{ + private sealed class FakeOcrSubtitle : IOcrSubtitle + { + public List Decoded { get; } = new(); + + public int Count => 1; + + public SKBitmap GetBitmap(int index) + { + // A 720x576 frame with a 100x40 opaque block in it, decoded fresh on every call. + var bitmap = new SKBitmap(720, 576); + bitmap.Erase(SKColors.Transparent); + using (var canvas = new SKCanvas(bitmap)) + using (var paint = new SKPaint { Color = SKColors.White }) + { + canvas.DrawRect(new SKRect(300, 500, 400, 540), paint); + } + + Decoded.Add(bitmap); + return bitmap; + } + + public TimeSpan GetStartTime(int index) => TimeSpan.Zero; + public TimeSpan GetEndTime(int index) => TimeSpan.FromSeconds(1); + public List MakeOcrSubtitleItems() => new() { new OcrSubtitleItem(this, 0) }; + public bool GetIsForced(int index) => false; + public SKPointI GetPosition(int index) => new(0, 0); + public SKSizeI GetScreenSize(int index) => new(720, 576); + } + + [AvaloniaFact] + public void GetBitmapCroppedUncached_CropsAndFreesTheDecodedFrame() + { + var source = new FakeOcrSubtitle(); + var item = new OcrSubtitleItem(source, 0); + + using var first = item.GetBitmapCroppedUncached(); + using var second = item.GetBitmapCroppedUncached(); + + Assert.Equal(100, first.PixelSize.Width); + Assert.Equal(40, first.PixelSize.Height); + Assert.Equal(2, source.Decoded.Count); // nothing cached + Assert.All(source.Decoded, b => Assert.Equal(IntPtr.Zero, b.Handle)); // every frame freed + } +} From 638903e994db792f34c2e01fb0d4cab4e3a73702 Mon Sep 17 00:00:00 2001 From: Curious Insatiable <113294705+Need74@users.noreply.github.com> Date: Sun, 11 Oct 2026 20:19:49 +0200 Subject: [PATCH 075/306] Update French.json 5.30-rc3 Update French.json 5.30-rc3 --- src/ui/Assets/Languages/French.json | 470 ++++++++++++++++++++++++++-- 1 file changed, 444 insertions(+), 26 deletions(-) diff --git a/src/ui/Assets/Languages/French.json b/src/ui/Assets/Languages/French.json index a4ef86757db..dad398caef7 100644 --- a/src/ui/Assets/Languages/French.json +++ b/src/ui/Assets/Languages/French.json @@ -1,6 +1,6 @@ { "title": "Subtitle Edit", - "version": "v5.3.0-bêta16", + "version": "v5.3.0-rc3", "translatedBy": "Need74 . (29.sept.2026)", "cultureName": "fr-FR", "general": { @@ -10,12 +10,21 @@ "setActorDotDotDot": "Définir l'acteur…", "actorPickerTitle": "Définir l'acteur", "actorPickerFilterHint": "Tapez pour filtrer ou saisissez le nom d'un nouvel acteur", - "ignoreNumbering": "Ignorer la numérotation", - "ignoreNumberingHint": "Les lignes qui ne diffèrent que par leur numéro ne sont pas considérées comme différentes", - "actorPickerKeysHint": "Appuyez sur un chiffre ou sur \"Entrée\" pour définir l'acteur.\r\nAlt+Haut/Bas modifie l'ordre, \"Suppr\" supprime l'acteur.", "actorPickerNewActorX": "Nouvel acteur \"{0}", "actorPickerLinesSelectedX": "Lignes sélectionnées : {0}", + "setStyleDotDotDot": "Définir le style...", + "stylePickerTitle": "Définir le style", + "stylePickerFilterHint": "Tapez pour filtrer ou tapez un nouveau nom de style", + "stylePickerKeysHint": "Appuyez sur un chiffre (1-9, 0) pour définir le style sur toutes les lignes sélectionnées.", + "stylePickerNewStyleX": "Nouveau style \"{0}", + "stylePickerNewStyleInfo": "Appuyez sur \"Entrée\" pour ajouter ce style avec les paramètres par défaut et définissez-le sur les lignes sélectionnées.\r\nUtilisez \"Gérer les styles...\" pour modifier ultérieurement sa police, ses couleurs et sa position.", + "stylePickerCurrentStyleX": "Style actuel : {0}", + "stylePickerLinesUsingStyleX": "Lignes utilisant ce style : {0}", + "stylePickerMarginsXYZ": "Gauche {0}, droite {1}, verticale {2}", + "stylePickerSetStyle": "_Définir le style", + "stylePickerManageStyles": "Gérer les styles...", + "stylePickerManageStylesHint": "Fermer cette fenêtre et ouvrir le gestionnaire de styles pour modifier les polices, les couleurs, la position et plus encore.", "add": "Ajouter", "addDotDotDot": "Ajouter…", "addToNamesListCaseSensitive": "Ajouter à la liste des noms (sensible à la casse)", @@ -434,6 +443,7 @@ "percent": "Pourcentage", "pickLayer": "Définir la couche", "pickOllamaModel": "Choisir le modèle Ollama", + "pickModel": "Choisir le modèle", "llamaCppUseRemoteServer": "Utiliser un serveur externe (URL)", "llamaCppEngineSettings": "Paramètres du moteur llama.cpp", "llamaCppEngineSettingsSubtitle": "Serveur local llama.cpp", @@ -510,6 +520,7 @@ "reset": "Réinitialiser", "resizePanels": "Redimensionner les panneaux", "resolution": "Résolution", + "retry": "Réessayer", "reverseRightToLeftStartEnd": "Inverser début/fin RTL (lignes sélectionnées)", "right": "Droite", "rightToLeft": "De droite à gauche", @@ -571,6 +582,8 @@ "shadowColor": "Couleur d'ombre", "shadowWidth": "Largeur d'ombre", "shiftLineBy": "Décaler la ligne de", + "shotIn": "Tourné dans", + "shotOut": "Tiré", "shortcut": "Raccourci", "shortcuts": "Raccourcis", "show": "Afficher", @@ -582,9 +595,13 @@ "showStartColumn": "Afficher la colonne « Début »", "showHideColumn": "Afficher la colonne « Masquer »", "showHistory": "Afficher l'historique", + "clearHistory": "Effacer l'historique", + "removeFromHistory": "Supprimer de l'historique", "showLayerColumn": "Afficher la colonne « Couche »", "showPreview": "Afficher l'aperçu", "showShotChangesList": "Afficher la liste des changement de plan", + "showShotInColumn": "Afficher la colonne \"Tourné dans\"", + "showShotOutColumn": "Afficher la colonne \"Tiré\"", "showStyleColumn": "Afficher la colonne « Style »", "showTeletext": "Afficher la colonne « Télétexte » en mode liste", "showTimeCodes": "Afficher l'horodatage", @@ -641,12 +658,14 @@ "styleExaggeration": "Exagération du style", "styles": "Styles", "subtitleFile": "Fichier sous-titre", + "subtitleFileChangedOutsideX": "Le fichier \"{0}\" a été modifié en dehors de la modification des sous-titres.\r\n\r\nLe recharger et perdre vos modifications ?", "subtitleFileName": "Nom du fichier sous-titre", "subtitleFileSaved": "Fichier sous-titre sauvegardé", "subtitleFileSavedToX": "Fichier sous-titre sauvegardé vers {0}", "subtitleFiles": "Fichiers sous-titre", "subtitleFormats": "Formats de sous-titres", "subtitleLoadedX": "Sous-titres chargés : {0}", + "subtitleReloadedChangedOutsideX": "Sous-titre rechargé (modifié en dehors de la modification des sous-titres) : {0}", "suffix": "Suffixe", "suggestions": "Suggestions", "sync": "Synchro.", @@ -730,10 +749,23 @@ "videoCustom3ForwardX": "Vidéo, avance personnalisée de ({0:#,###,##0}) ms, 3", "videoCustom4BackX": "Vidéo, recul personnalisée de ({0:#,###,##0}) ms, 4", "videoCustom4ForwardX": "Vidéo, avance personnalisée de ({0:#,###,##0}) ms, 4", + "videoCustom1BackAndPauseX": "Vidéo, recul personnalisée de ({0:#,###,##0}) ms et en pause, 1", + "videoCustom1ForwardAndPauseX": "Vidéo, avance personnalisée de ({0:#,###,##0}) ms et en pause, 1", + "videoCustom2BackAndPauseX": "Vidéo, recul personnalisée de ({0:#,###,##0}) ms et en pause, 2", + "videoCustom2ForwardAndPauseX": "Vidéo, avance personnalisée de ({0:#,###,##0}) ms et en pause, 2", + "videoCustom3BackAndPauseX": "Vidéo, recul personnalisée de ({0:#,###,##0}) ms et en pause, 3", + "videoCustom3ForwardAndPauseX": "Vidéo, avance personnalisée de ({0:#,###,##0}) ms et en pause, 3", + "videoCustom4BackAndPauseX": "Vidéo, recul personnalisée de ({0:#,###,##0}) ms et en pause, 4", + "videoCustom4ForwardAndPauseX": "Vidéo, avance personnalisée de ({0:#,###,##0}) ms et en pause, 4", "videoExtension": "Extension du fichier vidéo", "videoFile": "Fichier vidéo", "videoFileGenerated": "Fichier vidéo généré", "videoFileGeneratedX": "Fichier vidéo généré : \"{0}\"", + "videoFilesGenerated": "Fichiers vidéo générés", + "xVideosGenerated": "{0} vidéos générées", + "xOfYVideosGenerated": "{0} vidéos sur {1} générées", + "savedInX": "Enregistré dans {0}", + "savedInXFolders": "Enregistré dans {0} dossiers", "videoFiles": "Fichiers vidéo", "videoAndAudioFiles": "Fichiers vidéo et audio", "videoOffset": "Décalage vidéo", @@ -860,6 +892,7 @@ "multipleReplace": "Remplacements _multiples…", "fillSelectedLinesWithClipboard": "Remplir les lignes sélectionnées avec le texte du presse-papiers", "goToLineNumber": "_Aller à la ligne N°…", + "commandPalette": "_Palette de commandes...", "rightToLeftMode": "Mode de dro_ite à gauche (RTL)", "modifySelectionDotDotDot": "Modifier la _sélection…", "tools": "_Outils", @@ -1052,6 +1085,9 @@ "charactersPerSecond": "Car./sec : {0}", "chooseColumn": "Choisir une colonne", "columnPaste": "Coller en colonnes", + "commandPaletteTitle": "Palette de commandes", + "commandPaletteSearchPlaceholder": "Tapez pour rechercher des commandes...", + "commandPaletteNoCommandsFound": "Aucune commande correspondante", "createdEmptyTranslation": "Créer une traduction vide à partir du sous-titre actuel", "deleteText": "Supprimer le texte", "deleteTextAndShiftCellsUp": "Supprimer le texte et décaler les cellules vers le haut", @@ -1097,6 +1133,7 @@ "overwriteExistingCells": "Écraser les cellules existantes", "overwriteOrShiftCellsDown": "Écraser/décaler les cellules vers le bas", "parsingMatroskaFile": "Analyse du fichier Matroska…", + "readingDvdSubtitles": "Lecture des sous-titres d'un DVD...", "pasteFromClipboardDotDotDot": "Coller depuis le presse-papiers", "redoPerformed": "Rétablissement effectué", "redoPerformedXActionLeft": "Rétablissement effectué (actions restantes : {0})", @@ -1136,12 +1173,14 @@ "trimmedXLines": "{0} lignes de sous-titre supprimées", "pastedXLinesOverSelectedLines": "Collé {0} ligne(s) sur les lignes sélectionnées", "pastedXOfYLinesOverSelectedLines": "Collé {0} de {1} ligne(s) sur les lignes sélectionnées : le reste ne correspondait pas à la sélection", + "insertedXLinesFromClipboard": "{0} ligne(s) insérée(s) à partir du presse-papiers", "openOriginalDifferentNumberOfSubtitlesXY": "Le fichier de sous-titres d'origine ne contient pas le même nombre de sous-titres que le fichier de sous-titres actuel.\n\n• Sous-titres d'origine : {0}\n• Sous-titres actuels : {1}", "originalTextReadOnly": "Texte original (lecture seule)", "originalTextEditMode": "Texte original (mode édition)", "originalIsReadOnlyNotSaved": "Le sous-titre original est une référence en lecture seule et n'a pas été enregistré", "originalIsEmptyNotSaved": "Le sous-titre original ne contient aucun texte et n'a pas été enregistré", "originalIsReadOnlyReference": "Le sous-titre original est ouvert en tant que référence en lecture seule", + "closeTranslationQuestionXY": "Fermer la traduction \"{0}\" ?\r\n\r\nLe sous-titre original \"{1}\" devient le sous-titre que vous modifiez. Le fichier de traduction lui-même n'est pas modifié.", "allowEditOfOriginalSubtitle": "Autoriser la modification du sous-titre original", "showAllOriginalLinesX": "Afficher toutes les lignes originales ({0} n'ont pas de correspondance ici)", "showAllOriginalLinesHint": "Les lignes sans correspondance sont affichées comme des lignes supplémentaires, afin que vous puissiez voir ce qui manque à la traduction.", @@ -1176,6 +1215,7 @@ "fileExportedInFormatXToY": "Fichier exporté au format {0} vers {1}", "fileExportedInFormatXToFileY": "Fichier exporté au format \"{0}\" vers le fichier \"{1}\"", "fixedXLines": "{0} lignes corrigées", + "toolXChangedYLines": "{0} : {1} lignes modifiées", "transcriptionCompletedWithXLines": "Transcription terminée avec {0} lignes", "openSubtitleFileX": "Ouvrir le fichier de sous-titre \"{0}\" ?", "replacedXOccurrences": "{0} occurrences remplacées", @@ -1266,6 +1306,21 @@ "formattingDotDotDot": "Mise en forme…", "imageBasedSubtitleForEditDotDotDot": "Sous-titre basé sur une image pour l'édition…", "imageBasedSubtitleForOcrDotDotDot": "Sous-titre basé sur une image pour l'OCR…", + "dvdSubtitlesDotDotDot": "Sous-titres d'un DVD (IFO/VOB)...", + "titleImportDvd": "Importer des sous-titres à partir d'un DVD", + "dvdIfoFile": "Fichier IFO", + "dvdVobFiles": "Fichiers VOB", + "dvdVideoStandard": "Norme vidéo", + "dvdStartRipping": "Commencer à déchirer", + "dvdRippingAborted": "Extraction interrompue", + "dvdOpenIfoFile": "Ouvrir le fichier DVD IFO", + "dvdAddVobFiles": "Ajouter des fichiers VOB", + "dvdIfoFiles": "Fichiers DVD IFO", + "dvdVobFileType": "Fichiers DVD VOB", + "dvdDropHint": "Ouvrez un fichier IFO (VIDEO_TS\\VTS_01_0.IFO) ou ajoutez des fichiers VOB - ou déposez-les ici", + "dvdNotAnIfoFileX": "{0} n'est pas un fichier DVD IFO", + "dvdMenuIfoNoTitleSetsX": "Aucun ensemble de titres (VTS_xx_0.IFO) trouvé à côté de {0}", + "dvdEncryptedXOfY": "{0} des {1} paquets de sous-titres sont toujours cryptés en CSS : ces fichiers VOB ont été copiés sans déchiffrement, de sorte que certaines images de sous-titres seront tronquées.\r\nDécryptez d'abord le DVD avec un outil d'extraction de DVD pour un résultat propre.\r\nContinuer quand même ?", "splitTextAt": "Scinder le texte à", "blankLines": "Lignes vides", "oneLineIsOneSubtitle": "Une ligne correspond à un sous-titre", @@ -1290,7 +1345,8 @@ "forcedAlignerIsolatingSpeech": "Isoler le discours… {0}", "forcedAlignerIsolatingSpeechFailed": "Impossible d'isoler le discours - les heures de fin ont été définies à partir de l'heure de lecture.", "forcedAlignerModel": "Modèle d'alignement", - "forcedAlignerDownloadEngine": "Télécharger / mettre à jour le moteur…" + "forcedAlignerDownloadEngine": "Télécharger / mettre à jour le moteur…", + "forcedAlignerEstimatedXOfY": "{0} lignes sur {1} n'ont pas pu être alignées et ont été placées en fonction de l'heure de lecture ; leurs horodatages ne sont qu'une estimation.\r\nLe script est probablement plus long que ce qui est dit dans la vidéo." }, "export": { "exportImagesProfiles": "Exporter les profils d'images", @@ -1528,16 +1584,64 @@ "compareNumberDifference": "Différence de nombre", "ignoreWhitespace": "Ignorer les espaces blancs", "ignoreWhitespaceHint": "Les lignes qui diffèrent uniquement par les espaces, les tabulations ou les sauts de ligne ne sont pas considérées comme différentes.", - "ignoreNumbering": "Ignorer la numérotation", - "ignoreNumberingHint": "Les lignes qui diffèrent uniquement par leur numéro ne sont pas considérées comme différentes", "ignoreFormatting": "Ignorer la mise en forme", "ignoreFormattingHint": "Les lignes qui diffèrent uniquement par les balises de formatage, comme ou {\\an8}, ne sont pas considérées comme différentes", "showOnlyDifferencesInText": "Afficher uniquement les différences dans le texte", + "ignoreNumbering": "Ignorer la numérotation", + "ignoreNumberingHint": "Les lignes qui diffèrent uniquement par leur numéro ne sont pas considérées comme différentes", + "ignoreTimeDifferenceMs": "Tolérance de temps (ms)", + "ignoreTimeDifferenceMsHint": "Les heures de début et de fin qui diffèrent de ce nombre de millisecondes ou moins ne sont pas considérées comme différentes.", + "compareDifferences": "Différences", + "compareTextDifferences": "Différences de texte", + "compareCurrent": "Actuel", + "compareReference": "Référence", + "compareEditable": "Éditable", + "compareEditableHint": "Les modifications apportées ici reviennent au sous-titre lorsque vous cliquez sur Appliquer", + "compareReadOnly": "Lecture seule", + "compareTakeFromReference": "Prendre le texte et le timing de la référence", + "compareInsertFromReference": "Insérer cette ligne dans le sous-titre actuel", + "compareDeleteFromCurrent": "Supprimer cette ligne du sous-titre actuel", + "compareTakeText": "Prendre du texte", + "compareTakeTiming": "Prendre le temps", + "compareEditLine": "Éditer la ligne", + "compareEditHint": "\"Ctrl+Entrée\" pour enregistrer, \"Échap\" pour annuler", + "compareEdited": "Édité", + "compareOnePendingChange": "1 modification en attente", + "compareXPendingChanges": "{0} modifications en attente", + "compareDiscardXChanges": "Supprimer les {0} modifications apportées dans \"Compare\" ?", + "compareXChangesApplied": "Comparer : {0} modification(s) appliquée(s)", + "compareChangeTextX": "#{0} texte depuis la référence", + "compareChangeTimingX": "#{0} synchronisation depuis la référence", + "compareChangeTextAndTimingX": "#{0} texte et synchronisation depuis la référence", + "compareChangeEditedX": "#{0} éditié", + "compareChangeInsertedX": "#{0} inséré", + "compareChangeDeletedX": "#{0} supprimé", + "compareSyncPoint": "Point de synchro", + "compareSyncPointHint": "Point de synchro : ces deux lignes sont toujours affichées par paire, et les lignes au-dessus et en dessous sont alignées individuellement", + "compareSyncPickCurrent": "Point de synchro : utiliser cette ligne actuelle", + "compareSyncPickReference": "Point de synchro : utiliser cette ligne de référence", + "compareSyncRemove": "Supprimer le point de synchro", + "compareClearXSyncPoints": "Effacer les points de synchro ({0})", + "compareSyncCurrentPickedX": "N° {0} actuel choisi comme point de synchronisation : sélectionner la ligne de référence correspondante et cliquer sur \"Synchroniser\".", + "compareSyncReferencePickedX": "Référence n° {0} choisie comme point de synchronisation : sélectionner la ligne actuelle correspondante et cliquer sur Synchroniser.", + "compareSyncWithCurrentX": "Synchroniser avec le numéro actuel {0}", + "compareSyncWithReferenceX": "Synchroniser avec la référence #{0}", + "compareSyncApply": "Synchro", + "compareSyncPointSetXY": "Ensemble de points de synchro : le numéro {0} actuel et le numéro de référence {1} sont désormais affichés sous forme de paire.", + "compareSyncAlreadyPairedXY": "Le numéro {0} actuel et le numéro de référence {1} sont déjà affichés par paire avec le même début – rien à synchroniser", + "compareSyncPointShiftedXYZW": "Ensemble de points de synchronisation : #{0}-#{1} actuel décalé de {2} pour commencer par la référence #{3}", + "compareChangeSyncShiftXYZ": "#{0}-#{1} décalé de {2} (point de synchronisation)", + "compareTimingDeltaHint": "Combien de temps plus tard (+) ou plus tôt (−), commence et se termine la référence par rapport à la ligne actuelle", "loadXFromFile": "Charger \"{0}\" depuis un fichier", + "compareLoadSavedFile": "Charger le fichier enregistré", "saveCompareHtmlTitle": "Enregistrer le fichier HTML de comparaison", "pickMatroskaTrackX": "Choisir la piste Matroska - {0}", "pickTransportStreamTrackX": "Choisir la piste du flux de transport - {0}", + "chapters": "Chapitres", + "dvdVobFilesMissingX": "Fichiers VOB manquants ({0} % trouvés)", "pickMp4TrackX": "Choisir la piste MP4 - {0}", + "pickMxfTrackX": "Choisir une piste MXF – {0}", + "pickMpegTrackX": "Choisir une piste MPEG – {0}", "rosettaProperties": "Propriétés IMSC Rosetta du texte temporisé", "rosettaFontSize": "Taille de police (hauteur de ligne)", "propertyTimeBase": "Base de temps", @@ -1612,7 +1716,8 @@ "findWhat": "Rechercher quoi", "descriptionOptional": "Description (facultative)", "invalidRegularExpressionX": "Expression régulière invalide : {0}", - "regularExpressionTooSlowX": "L'expression régulière a abandonné après {0} secondes ; la règle est ignorée, essayez un motif plus simple" + "regularExpressionTooSlowX": "L'expression régulière a abandonné après {0} secondes ; la règle est ignorée, essayez un motif plus simple", + "moveToCategory": "Passer à la catégorie" }, "find": { "searchTextWatermark": "Texte de recherche…", @@ -1692,7 +1797,22 @@ "mismatchWarning": "Ne ressemble pas à l'original - peut appartenir à une autre ligne", "engineError": "Impossible de joindre le moteur d'IA : {0}", "playCurrentLineHint": "Lire la ligne sélectionnée dans le lecteur vidéo et mettre en pause à la fin (ou double-cliquer sur une ligne)", - "editAfterHint": "Cliquer sur le texte de la ligne sélectionnée pour modifier le correctif avant de l'appliquer (\"Entrée\" fait les sauvegardes, \"Échapp\" les annules)" + "editAfterHint": "Cliquer sur le texte de la ligne sélectionnée pour modifier le correctif avant de l'appliquer (\"Entrée\" fait les sauvegardes, \"Échap\" les annules)", + "template": "Modèle", + "templatePlaceholder": "Choisiir un modèle pour remplacer l'invite", + "templateStandard": "Relecture standard", + "templateSpeechToText": "Nettoyage de la reconnaissance vocale (STT)", + "templateMachineTranslation": "Correction de la traduction automatique", + "templateSdh": "SDH / malentendant", + "referenceContext": "Contexte de référence", + "referenceContextInfo": "Facultatif.\r\nNoms, personnages, glossaire, synopsis ou notes de style - le modèle considère cela comme l'autorité pour les noms et les termes.\r\nEnvoyé avec chaque lot.", + "referenceContextPlaceholder": "Noms : Kaito, tante Mei, Dr Hollis\r\nTermes : Shinkansen, noyau de distorsion\r\nSynopsis : Kaito se rend à Tokyo pour retrouver son père disparu.", + "generateContext": "Générer avec l'IA", + "generateContextHint": "Laisser le moteur d'IA sélectionné lire les sous-titres et les projets de noms, les termes et un synopsis - vérifier le résultat avant utilisation", + "generatingContextPartXOfY": "Lecture de la partie {0} de {1}...", + "generatingContextSynopsis": "Rédaction du synopsis...", + "replaceContextQuestion": "Remplacer le contexte de référence actuel ?", + "noContextGenerated": "Le moteur d'IA n'a renvoyé aucune information de référence utilisable." }, "aiAssistant": { "title": "Assistant IA", @@ -1738,6 +1858,7 @@ "removeLineBreaksPixelWidth": "Afficher les sous-titres tenant sur une seule ligne (largeur en pixels)", "fixUppercaseIInsideLowercaseWords": "Corriger les 'i' majuscule à l'intérieur des mots minuscules (erreur OCR)", "fixDoubleApostrophes": "Corriger les doubles apostrophes (' ') en apostrophes guillemet simple (\")", + "fixMisreadQuotes": "Correction des apostrophes mal lues sous forme de guillemets doubles (OCR, anglais)", "addPeriods": "Ajouter un point après les lignes où la ligne suivante commence par une majuscule", "startWithUppercaseLetterAfterParagraph": "Commencer par une majuscule après un paragraphe", "startWithUppercaseLetterAfterPeriodInsideParagraph": "Commencer par une majuscule après le point à l'intérieur d'un paragraphe.", @@ -1760,6 +1881,7 @@ "fixCommasExample": ",, -> ,", "removeLineBreaksExample": "Foo
bar ! -> Foo bar !", "fixDoubleApostrophesExample": "''Contient des guillemets-simples doubles'' -> \"Contient des guillemets-doubles simples\"", + "fixMisreadQuotesExample": "\"Bonjour\" -> \"Bonjour", "addPeriodsExample": "Bonjour le monde -> Bonjour le monde.", "startWithUppercaseLetterAfterParagraphExample": "p1 : Foobar ! || p2 : foobar ! -> p1 : Foobar ! || p2 : Foobar !", "startWithUppercaseLetterAfterPeriodInsideParagraphExample": "Bonjour ! comment allez-vous ? -> Bonjour ! Comment allez-vous ?", @@ -1849,6 +1971,8 @@ "fixMinDurationMs": "Fixer la durée minimale (ms)", "doNotGoPastShotChange": "Ne pas dépasser le changement de plan", "fixMaxDurationMs": "Fixer la durée maximale (ms)", + "fixMinDurationFrames": "Fixer la durée minimale (images)", + "fixMaxDurationFrames": "Fixer la durée maximale (images)", "minimumDurationMilliseconds": "Durée minimale (ms) :", "maximumDurationMilliseconds": "Durée maximale (ms) :", "maxDurationShouldBeHigherThanMinDuration": "La durée maximale doit être supérieure à la durée minimale", @@ -1910,6 +2034,20 @@ "isolateSpeechHint": "Supprime la musique et les effets sonores avant l'alignement, de sorte que l'aligneur n'entend que le dialogue.\r\nAide pour les lignes prononcées sur une musique forte ou une action, mais prend environ autant de temps que l'audio lui-même avec un GPU - et plusieurs fois plus longtemps sans un.", "isolatingSpeech": "Discours isolant… (cela prend du temps)", "isolateSpeechFailed": "Impossible d'isoler le discours - aligné à la place sur l'audio d'origine.", + "checkWithSpeechToText": "Vérifiez avec la synthèse vocale", + "checkWithSpeechToTextHint": "Transcrit également l'audio et vérifie chaque mouvement par rapport à l'endroit où les mots ont été réellement entendus.\r\nLes grands mouvements confirmés par le discours sont cochés pour vous, et les mouvements éloignés de l'endroit où une ligne est entendue sont laissés décochés pour que vous puissiez les vérifier.\r\nIndique également la part de chaque ligne qui a été entendue : une part faible signifie que le texte diffère de ce qui est dit.\r\nUn sous-titre qui est plus désynchronisé que le décalage maximum (un décalage constant, une dérive due à une inadéquation de fréquence d'images ou des sauts là où les scènes ont été coupées) est d'abord synchronisé par les mots entendus.\r\nUtilise Crisp ASR Parakeet, qui prend quelques minutes pour un long métrage avec GPU.", + "checkWithSpeechToTextNotAvailableX": "La vérification de la synthèse vocale n'est pas disponible pour {0}", + "transcribingToCheck": "Transcrire pour vérifier l'alignement...", + "speechToTextFailed": "La reconnaissance vocale a échoué : les horodatages n'ont pas été vérifiés.", + "speechToTextX": "Reconnaissance vocale : {0}", + "speechToTextConfirmedXDisputedY": "Discours : {0} confirmé, {1} contesté", + "hardlyHeardX": "La synthèse vocale n'a entendu que {0} % des mots du sous-titre.\r\nLe sous-titre est-il dans la langue parlée ?\r\nL'aligneur ne peut pas effectuer de translation.", + "heard": "Entendu", + "heardHint": "Quelle proportion de reconnaissance vocale a été entendue.\r\nUne part faible signifie que le texte diffère de ce qui est dit - là où l'aligneur est le plus susceptible de se tromper.", + "statusConfirmedBySpeech": "Confirmé par discours", + "statusDisputedBySpeech": "J'ai entendu où c'était", + "statusMovedWithSync": "Déplacé avec la synchronisation", + "syncedFirstXY": "Synchronisé en premier : {0} s au début, {1} s à la fin", "showSpeechOnly": "Afficher uniquement le discours", "showSpeechOnlyHint": "Dessinez la forme d'onde alignée à partir de l'audio en supprimant la musique et les effets sonores ;\r\nla forme d'onde originale reste telle quelle, à titre de comparaison.\r\nDisponible une fois la parole isolée - ici, ou avec \"Afficher la parole uniquement\" dans la forme d'onde de la fenêtre principale.", "align": "Aligner", @@ -1924,6 +2062,7 @@ "meanShiftX": "Décalage moyen : {0} ms", "toCheckX": "Pour vérifier : {0}", "largeMovesToCheckX": "{0} ligne(s) se déplaceraient très loin d'elles-mêmes et ne seraient pas cochées - jouez Original / Aligné et cochez celles qui sont correctes.", + "linesToCheckX": "{0} lignes sont non cochées pour vérification - écoutez-les (F5 lit la ligne alignée, Maj+F5 l'original) et cochez celles qui sont correctes.", "changeXOfY": "Modifier {0} sur {1}", "previousChange": "Changement précédent", "nextChange": "Prochain changement", @@ -1936,10 +2075,12 @@ "statusNoSpeech": "Pas de discours", "statusShiftTooLarge": "Conservé - décalage trop important", "statusFailed": "Conservé - l'aligneur a échoué", + "statusAdjustedByHand": "Ajusté à la main", + "alignedWaveformHint": "Faites glisser une ligne pour la déplacer ou faites glisser ses bords pour modifier son début ou sa fin.", + "playLine": "Ligne de lecture", + "undoAdjustment": "Annuler l'ajustement", "playPause": "Lecture/pause", - "playOriginal": "Original", "playAligned": "Aligné", - "playOriginalHint": "Jouer la ligne sélectionnée avec ses horodatages d'origine", "playAlignedHint": "Jouer la ligne sélectionnée avec ses horodatages alignés", "apply": "Appliquer", "applyHint": "Décocher une ligne pour conserver ses horodatages d'origine.\r\nDouble-cliquer sur une ligne pour la lire." @@ -2022,6 +2163,7 @@ "addColor": "Ajouter de la couleur", "deleteLinesWithSpecificActorsOrStyles": "Supprimer les lignes avec des acteurs ou des styles spécifiques (séparer les multiples par une virgule)", "useSourceStylesIfPossible": "Utiliser les styles source si possible", + "keepSourceEmbeddedFonts": "Conserver les polices sources intégrées (pied de page)", "editStyles": "Éditer les styles", "editProperties": "Éditer les propriétés", "editAttachments": "Éditer les pièces jointes", @@ -2056,6 +2198,18 @@ "addFolderRecursiveDotDotDot": "Ajouter un dossier récursif…", "selectFolderToConvert": "Sélectionner le dossier contenant les fichiers à convertir", "includeSubfolders": "Inclure les sous-dossiers lors de l'ajout d'un dossier", + "videoFilesWhenAddingFolder": "Fichiers vidéo (mkv, mp4, ts...) lors de l'ajout d'un dossier", + "videoFilesAsk": "Demander", + "videoFilesInclude": "Ajouter leurs pistes de sous-titres", + "videoFilesSkip": "Sauter", + "folderContainsXVideoFiles": "Le dossier contient {0} fichier(s) vidéo (mkv, mp4, ts...).\r\n\r\nAjouter également leurs pistes de sous-titres intégrées, ou uniquement les fichiers de sous-titres ?", + "addVideoFiles": "Ajouter des fichiers vidéo", + "skipVideoFiles": "Fichiers de sous-titres uniquement", + "doNotAskAgainVideoFiles": "Ne plus demander (modification des paramètres de conversion par lots)", + "settingsSectionOutput": "Sortir", + "settingsSectionAddingFiles": "Ajout de fichiers", + "settingsSectionImageBasedSubtitles": "Sous-titres basés sur des images (OCR)", + "settingsSectionOther": "Autre", "keepSourceFileTimestamp": "Conserver la date/heure du fichier source sur les fichiers de sortie", "preventSleepWhileConverting": "Empêcher l'ordinateur de dormir pendant la conversion", "scanningFolderX": "Analyse de {0}…", @@ -2071,7 +2225,20 @@ "transportStreamOnlyTeletext": "Uniquement le télétexte", "transportStreamGetSizeFromVideo": "Obtenez la taille de la vidéo…", "twoLetterLanguageCodeUppercase": "Code de langue à deux lettres (majuscules)", - "threeLetterLanguageCodeUppercase": "Code de langue à trois lettres (majuscules)" + "threeLetterLanguageCodeUppercase": "Code de langue à trois lettres (majuscules)", + "formatContainsDotDotDot": "Le format contient...", + "extensionIsDotDotDot": "La prolongation est...", + "statusIsError": "Le statut est en erreur", + "forcedTracksOnly": "Pistes forcées uniquement", + "xConvertedYFailedInZ": "{0:#,###,##0} conversion, {1:#,###,##0} a échoué dans {2}", + "xNotProcessed": "{0:#,###,##0} non traité", + "removeUnicodeControlCharactersTitle": "Supprimer les caractères de contrôle Unicode", + "removeUnicodeControlCharactersInfo": "Supprime les caractères de contrôle de direction Unicode invisibles (LRM, RLM, LRE, RLE, PDF, LRO et RLO) du texte et remplace les espaces insécables par des espaces normaux.", + "preset": "Préréglage", + "savePresetDotDotDot": "Enregistrer le préréglage...", + "deletePreset": "Supprimer le préréglage", + "presetName": "Nom du préréglage", + "deletePresetX": "Supprimer le préréglage \"{0}\" ?" }, "changeCasing": { "fixNames": "Corriger les noms", @@ -2156,11 +2323,13 @@ }, "mergeLinesWithSameText": { "maxMsBetweenLines": "Écart max. (ms) entre les lignes", + "maxFramesBetweenLines": "Nombre maximum d'images entre les lignes", "includeIncrementingLines": "Inclure les lignes avec un texte incrémentiel", "includeRollUpCaptions": "Inclure des sous-titres déroulants (défileants)" }, "mergeLinesWithSameTimeCodes": { "maxMsDifference": "Différence max. (ms)", + "maxFramesDifference": "Différence maximale (images)", "makeDialog": "Créer des dialogues" }, "netflixCheckAndFix": { @@ -2275,7 +2444,14 @@ "xInTopBar": "{0} dans la barre supérieure", "xInBottomBar": "{0} dans la barre inférieure", "noImageSubtitlesLoaded": "Aucun sous-titre d'image chargé", - "imageBasedFormatNotSupported": "Format de sous-titre basé sur une image introuvable/pris en charge." + "imageBasedFormatNotSupported": "Format de sous-titre basé sur une image introuvable/pris en charge.", + "frameRateDeclaredInFile": "Fréquence d'images déclarée dans le fichier Blu-ray sup", + "frameRateDetectedFromTimeCodesX": "Fréquence d'images détectée à partir des codes temporels Blu-ray sup (le fichier déclare {0})", + "frameRateFromVideo": "Fréquence d'images de la vidéo", + "frameRateCurrent": "Fréquence d'images actuelle", + "frameRateSetManually": "Fréquence d'images réglée manuellement", + "frameRateUsageInfo": "Les exportations Blu-ray sup, BDN XML, DOST et FCP sont écrites à cette fréquence d'images. Cela ne change pas à chaque fois - utilisez \"Modifier la fréquence d'images\" pour cela.", + "useVideoFrameRateXInsteadOfY": "La fréquence d'images de la vidéo est {0}, mais le sous-titre est défini sur {1}.\r\n\r\nUtiliser la fréquence d'images de la vidéo ({0}) ?" }, "removeTextForHearingImpaired": { "interjections": "Interjections", @@ -2305,6 +2481,7 @@ "pickFontNameInstalledFonts": "Polices installées", "pickFontNameCollectedFonts": "Polices collectées", "colorPickerTitle": "Choisir la couleur de police", + "colorPickerBrightness": "Luminosité", "filterLayersTitle": "Filtrer les calques à afficher", "filterLayersHideFromWaveform": "Masquer le calque de la forme d'onde/du spectrogramme", "filterLayersHideFromSubtitleGrid": "Masquer le calque de la grille des sous-titres", @@ -2355,7 +2532,21 @@ "correctWordsX": "Mots corrects : {0}", "namesX": "Noms : {0}", "addedToDictionaryX": "Ajoutés au dictionnaire : {0}", - "doNotShowThisAgain": "Ne plus afficher ce message" + "doNotShowThisAgain": "Ne plus afficher ce message", + "editUseAlwaysList": "Modifier la liste \"Utiliser toujours\"...", + "useAlwaysListTitle": "Vérification orthographique : liste \"Toujours utiliser ", + "useAlwaysListHeader": "Liste \"Toujours utiliser\"", + "useAlwaysListDescription": "La vérification orthographique des mots est automatiquement remplacée, enregistrée par \"Tout modifier\" et \"Toujours utiliser\". Une paire n'est utilisée que lorsque le mot est mal orthographié.", + "useAlwaysWord": "Mot mal orthographié", + "xPairs": "{0:#,##0} paires", + "xUnusedPairs": "{0:#,##0} jamais utilisé", + "useAlwaysUnusedHint": "Jamais utilisé : ce mot est correctement orthographié (ou est un nom ou un mot utilisateur), donc la vérification orthographique ne le signale jamais", + "removeUnused": "Supprimer les inutilisés", + "useAlwaysEmpty": "Aucune paire \"Toujours utiliser\" pour cette langue pour l'instant", + "useAlwaysEmptyHint": "Les paires sont ajoutées lorsque vous cliquez sur \"Tout modifier  ou \"Toujours utiliser  dans la vérification orthographique.", + "useAlwaysNoMatch": "Aucune paire ne correspond à la recherche", + "useAlwaysSameWord": "Le remplacement doit être différent du mot.", + "useAlwaysRememberOff": "\"N'oubliez pas , la liste \"Toujours utiliser  est désactivé dans les paramètres, la vérification orthographique n'utilise pas cette liste ni ne l'enregistre." }, "video": { "burnIn": { @@ -2393,13 +2584,21 @@ "translateToEnglish": "Traduction en anglais", "transcribing": "Transcription…", "transcribingXOfY": "Transcription de {0} sur {1}…", + "linesTranscribedXOfYFailedZ": "{0} lignes sur {1} ont été transcrites ; {2} ont échoué ou n'avaient pas de parole.\r\n\r\nLes lignes échouées conservent leur texte actuel. Appliquer les lignes transcrites ou réessayer celles qui ont échoué (vous pouvez d'abord modifier le moteur ou les paramètres) ?", + "linesTranscribedXOfYCancelled": "La transcription a été annulée après {0} lignes sur {1}.\r\n\r\nAppliquer les lignes transcrites ? Les autres lignes conservent leur texte actuel.", + "noLinesTranscribed": "Aucune des lignes {0} n'a pu être transcrite.\r\nVeuillez consulter le journal des outils pour plus de détails.", + "applyTranscribedLines": "Appliquer la transcription", + "retryFailedLines": "La nouvelle tentative a échoué", "isolateSpeech": "Isoler la parole (lent)", "isolateSpeechHint": "Supprimer la musique et les effets sonores avant la transcription.\r\nDonne moins de lignes manquées et un meilleur timing sur l'audio avec de la musique forte, mais prend environ aussi longtemps que l'audio lui-même avec un GPU - et bien plus longtemps sans.", "isolatingSpeech": "Discours isolant…", "isolateSpeechFailed": "Impossible d'isoler le discours - transcrire l'audio original à la place.", "detectSpeakers": "Détecter les haut-parleurs", "detectSpeakersHint": "Commencer chaque ligne avec le haut-parleur, comme \"(haut-parleur 0)\", en utilisant la diarisation Sortformer de NVIDIA (jusqu'à 8 haut-parleurs).\r\nFonctionne mieux avec les backends qui divisent les lignes là où le haut-parleur change, comme Parakeet et Cohere.", - "detectSpeakersNeedsNewerCrispAsr": "\"Détecter les haut-parleurs\" nécessite Crisp ASR v{0} ou une version plus récente, mais v{1} est installé.\r\nMettez à jour Crisp ASR (le bouton d'engrenage à côté du moteur) ou désactivez « Détecter les haut-parleurs ».", + "detectSpeakersNeedsNewerCrispAsr": "\"Détecter les haut-parleurs\" nécessite Crisp ASR v{0} ou une version plus récente, mais v{1} est installé.\r\nMettez à jour Crisp ASR (le bouton d'engrenage à côté du moteur) ou désactivez \"Détecter les haut-parleurs .", + "xNeedsNewerCrispAsr": "{0} a besoin de Crisp ASR v{1} ou plus récent, mais v{2} est installé.\r\n\r\nTélécharger et installer la mise à jour maintenant ?", + "vad": "VAD", + "vadHint": "Détection de l'activité vocale : seule la parole est retranscrite, coupée là où l'orateur fait une pause.\r\nAuto : Silero, et uniquement pour les moteurs qui en ont besoin (Cohere, Mega, Index-Echo).\r\nSilero, FireRedVAD, WebRTC : toujours activé, pour chaque moteur Crisp ASR. FireRedVAD est le plus précis.\r\nIndex-Echo utilise toujours Silero. --vad, --vad-model ou --chunk-seconds dans les paramètres avancés sont prioritaires.", "inputLanguage": "Langue d’entrée", "advancedWhisperSettings": "Paramètres Whisper avancés", "downloadingSpeechToTextEngine": "Téléchargement du moteur de reconnaissance vocale", @@ -2450,7 +2649,7 @@ "qualityReportAll": "Tous", "qualityReportIssue": "Problème", "qualityReportDetail": "Détail", - "qualityReportTip": "Les lignes sont répertoriées avec leurs numéros dans le nouveau sous-titre.\r\nUtiliser « Corriger les erreurs courantes » ou les paramètres de post-traitement pour corriger plus automatiquement, ou essayer un autre modèle/moteur si de nombreuses lignes sont affectées.", + "qualityReportTip": "Les lignes sont répertoriées avec leurs numéros dans le nouveau sous-titre.\r\nUtiliser \"Corriger les erreurs courantes  ou les paramètres de post-traitement pour corriger plus automatiquement, ou essayer un autre modèle/moteur si de nombreuses lignes sont affectées.", "qualityReportDoNotShowAgain": "Ne plus afficher ce rapport", "engineSettings": "Paramètres du moteur parole vers texte", "engineSettingsSubtitle": "Moteur parole vers texte", @@ -2488,12 +2687,14 @@ "customServerUrl": "URL du serveur personnalisé", "customServerUrlHint": "Point de terminaison de parole complète d'un serveur compatible OpenAI, utilisé lorsque le fournisseur est \"Personnalisé\", par ex.\r\nhttp://localhost:8880/v1/audio/speech", "customModels": "Modèles personnalisés", - "customModelsHint": "Noms de modèles séparés par des virgules proposés lorsque le fournisseur est « Personnalisé »", + "customModelsHint": "Noms de modèles séparés par des virgules proposés lorsque le fournisseur est \"Personnalisé ", "customVoices": "Voix personnalisées", "customVoicesHint": "Noms de voix séparés par des virgules, utilisés par le fournisseur « Personnalisé » et par les modèles OpenRouter qui ne publient aucune liste de voix", "instructions": "Instructions", "instructionsHint": "Style de parole facultatif, par ex.\r\n\"Parlez d'une voix calme et chaleureuse\".\r\nEnvoyé uniquement aux modèles qui le prennent en charge (pas tts-1/tts-1-hd)", "openAiSpeedHint": "Vitesse de parole de 0,25 à 4,0 (1,0 est normal).\r\nTous les modèles ne le prennent pas en charge", + "outputFormat": "Format de sortie", + "outputFormatHint": "Format audio demandé au serveur. \"Auto\" demande MP3 et passe en PCM pour les modèles qui ne renvoient que PCM (par exemple Gemini TTS)", "regenerateAudio": "Régénérer l'audio", "autoContinuePlaying": "Reprise automatique de la lecture", "playLine": "Ligne de jeu", @@ -2521,6 +2722,17 @@ "voiceManagerTitle": "TTS - Gestionnaire vocal", "voiceManagerDotDotDot": "Gestionnaire de voix…", "voiceManagerMenuItem": "Gestionnaire vocal TTS…", + "speakFromCurrentLineTitle": "Parler depuis la ligne actuelle", + "speakFromCurrentLineDotDotDot": "Parler depuis la ligne actuelle...", + "speakFromCurrentLineHint": "Lit les lignes à voix haute les unes après les autres, en commençant par la ligne actuelle. Appuyez sur \"Échap\" ou cliquez sur une autre ligne pour arrêter.", + "stopSpeaking": "Arrête de parler", + "speakingLineX": "Ligne parlante {0}... (\"Échap\" pour arrêter)", + "playWithSpeechTitle": "Jouer avec la parole", + "playWithSpeechDotDotDot": "Jouez avec la parole...", + "playWithSpeechHint": "Lit la vidéo et prononce chaque ligne lorsque la vidéo l'atteint. Faites une pause et cherchez comme d'habitude ; appuyez sur \"Échap\" pour arrêter.", + "lowerVideoVolumeWhileSpeaking": "Baisser le volume vidéo pendant que vous parlez", + "pauseVideoWhenSpeechRunsLate": "Mettre la vidéo en pause lorsque la parole passe à la ligne suivante", + "waitingForSpeechLineX": "Génération de la parole pour la ligne {0}... (\"Échap\" pour arrêter)", "voiceKindClone": "Voix clonée", "voiceKindPreset": "Intégré", "voiceKindModel": "Modèle de voix", @@ -2585,14 +2797,29 @@ "advancedTtsSettings": "Paramètres TTS avancés", "advancedTtsAudioProcessing": "Traitement audio", "advancedTtsOutput": "Sortir", + "advancedTtsVoiceCloning": "Clonage vocal", + "cleanCloneReferences": "Nettoyage des références de \"clone vocal\" (Sidon)", + "cleanCloneReferencesDescription": "Supprime la musique, le bruit et la réverbération de l'audio à partir duquel une voix est clonée : clips extraits de la vidéo et enregistrements importés dans le gestionnaire de voix. Utilise le modèle de restauration vocale Sidon sur audio.cpp (téléchargement de 940 Mo). La voix clonée ne copie plus l'arrière-plan, mais une parole très faible ou qui se chevauche peut paraître étouffée.", + "cleaningCloneReferencesDotDotDot": "Nettoyage des références de \"clone vocal\"...", + "downloadSidonTitle": "Télécharger Sidon ?", + "downloadSidonQuestionX": "Le nettoyage des références de clone vocal nécessite le modèle de restauration vocale Sidon ({0}).\r\n\r\nLe télécharger maintenant ? Sans cela, les références sont utilisées telles quelles.", "proAudioPostProcessing": "Post-traitement audio professionnel", "proAudioPostProcessingDescription": "Applique une égalisation chaleureuse, un noise gate, une compression, une normalisation du volume (-16 LUFS) et un fondu en entrée/sortie à chaque segment.", + "generatingSpeechSegmentXOfY": "Génération de parole : segment {0} sur {1}", + "adjustingSpeedSegmentXOfY": "Réglage de la vitesse : segment {0} sur {1}", + "mergingAudioSegmentXOfY": "Fusion audio : segment {0} sur {1}", + "postProcessingSegmentXOfY": "Post-traitement : segment {0} sur {1}", "audioDucking": "Atténuation du volume audio", "audioDuckingDescription": "Réduit le volume audio d'origine de la vidéo et le mélange avec l'audio TTS, de sorte que la bande-son originale reste légèrement audible.", "removeOriginalSpeech": "Supprimer le discours original (lent)", "removeOriginalSpeechDescription": "Conserve la musique et les effets sonores de la vidéo originale mais supprime son discours et mélange le nouveau discours par-dessus.\r\nCela prend environ autant de temps que la vidéo elle-même avec un GPU, et bien plus longtemps sans GPU.\r\nAvec l'atténuation audio activée, son volume s'applique à la musique et aux effets.\r\nS'applique uniquement lorsque le discours est ajouté au fichier vidéo.", "removingOriginalSpeech": "Suppression du discours original…", "removeOriginalSpeechFailed": "Impossible de supprimer le discours d'origine : le discours a été ajouté à la vidéo sans celui-ci.", + "speechRemovalModel": "Modèle", + "speechRemovalModelDescription": "La meilleure qualité permet de conserver le maximum de musique et d'effets sonores.\r\nRapide est environ trois fois plus rapide, avec un peu plus de musique perdue.", + "speechRemovalBestQuality": "Meilleure qualité (Mel-Band RoFormer)", + "speechRemovalFast": "Rapide (HTDemucs)", + "videoTruncatedWarning": "L'image du nouveau fichier vidéo s'arrête après {0}, mais la vidéo d'origine est longue de {1} : l'image se fige pendant que le son continue.\r\n\r\nLe dossier a été conservé. Voir error-log.txt dans le dossier de données Subtitle Edit pour la sortie ffmpeg.", "originalVolumePercent": "Volume original %", "vadSilenceCompression": "Compression des silences VAD", "vadSilenceCompressionDescription": "Réduit les pauses entre les mots avant les changements de rythme. Utilise la détection d'activité vocale pour compresser uniquement les silences, sans altérer la parole. C'est la première étape recommandée : elle réduit la durée sans perte de qualité.", @@ -2608,14 +2835,14 @@ "generationFolder": "Dossier de génération", "generationFolderDescription": "Dossier pour les clips audio générés lors d'une exécution en cours.\r\nLaisser vide pour utiliser le dossier temporaire du système.\r\nChaque exécution obtient son propre sous-dossier.", "deleteTempFiles": "Supprimer les clips générés à la fermeture", - "deleteTempFilesDescription": "Supprime le dossier de génération de l'exécution lorsque la fenêtre « Synthèse vocale » se ferme.\r\nLes fichiers audio et vidéo enregistrés ne sont pas affectés.\r\nDésactiver cette option pour conserver les clips individuels.", + "deleteTempFilesDescription": "Supprime le dossier de génération de l'exécution lorsque la fenêtre \"Synthèse vocale  se ferme.\r\nLes fichiers audio et vidéo enregistrés ne sont pas affectés.\r\nDésactiver cette option pour conserver les clips individuels.", "edgeTtsRate": "Débit de synthèse vocale Edge-TTS", "edgeTtsRateDescription": "Débit de parole par défaut pour Edge-TTS, (p. ex. \"+50%\", \"-30%\" ou \"+0%\").", "edgeTtsPitch": "Hauteur de la voix Edge-TTS", "edgeTtsPitchDescription": "Réglage de la hauteur de la voix par défaut pour Edge-TTS, (p. ex. \"+10Hz\", \"-5Hz\" ou \"+0Hz\").", "edgeTtsVolume": "Volume de la synthèse vocale Edge-TTS", "edgeTtsVolumeDescription": "Réglage du volume de la synthèse vocale par défaut pour Edge-TTS, (p. ex. \"+20%\", \"-10%\" ou \"+0%\").", - "downloadPiperPrompt": "\"La synthèse vocale » nécessite Piper.\r\n\r\nTélécharger et utiliser Piper ?", + "downloadPiperPrompt": "\"La synthèse vocale  nécessite Piper.\r\n\r\nTélécharger et utiliser Piper ?", "omniVoiceTtsSettings": "Paramètres OmniVoice TTS", "reDownloadOmniVoiceTts": "Retélécharger OmniVoice TTS", "selectTheBuildToDownload": "\r\nSélectionner la version à télécharger :", @@ -2655,7 +2882,7 @@ "skipNoiseLinesFoundX": "{0} lignes contiennent uniquement des sons ou de la musique ; les lignes cochées restent silencieuses", "skipNoiseLinesColumnSkip": "Sauter", "detectSpeakersPromptTitle": "Noms des intervenants trouvés dans le texte ?", - "detectSpeakersPromptMessage": "Certaines lignes commencent par le nom d'un locuteur, comme « MIKE : » ou « [NARRATEUR] ».\r\n\r\n\r\nDéplacé dans le champ des acteurs, chaque locuteur peut obtenir sa propre voix via « Configurer la distribution\", et le nom n'est pas lu à haute voix.\r\n\r\n\r\nRevoir les noms maintenant ?\r\n(seule la génération vocale est affectée - le sous-titre lui-même n'est pas modifié)", + "detectSpeakersPromptMessage": "Certaines lignes commencent par le nom d'un locuteur, comme « MIKE : » ou « [NARRATEUR] ».\r\n\r\n\r\nDéplacé dans le champ des acteurs, chaque locuteur peut obtenir sa propre voix via \"Configurer la distribution\", et le nom n'est pas lu à haute voix.\r\n\r\n\r\nRevoir les noms maintenant ?\r\n(seule la génération vocale est affectée - le sous-titre lui-même n'est pas modifié)", "detectSpeakersTitle": "Noms des intervenants dans le texte", "detectSpeakersFoundX": "{0} tags de locuteurs trouvés ({1} locuteurs) : les noms cochés deviennent des acteurs et ne sont pas lus à haute voix", "detectSpeakersColumnUse": "Utiliser", @@ -2675,7 +2902,9 @@ "timeCodeFormatColon": "Format de l'horodatage :", "shotChangesList": "Liste des modifications de plan", "shotChangesClearQuestion": "Êtes-vous sûr de vouloir effacer tous les changements de plan ?", - "deleteSelectedShotChangeQuestion": "Êtes-vous sûr de vouloir supprimer le changement de plan sélectionné ?" + "deleteSelectedShotChangeQuestion": "Êtes-vous sûr de vouloir supprimer le changement de plan sélectionné ?", + "shotChanges": "Changements de plan", + "exportShotChanges": "Exporter les modifications de prise de vue" }, "chapters": { "title": "Chapitres", @@ -2738,6 +2967,7 @@ "runningOcrXY": "Exécution de l'OCR… {0}/{1}", "noLinesFoundTitle": "Aucun sous-titre trouvé", "noLinesFoundMessage": "Aucun texte n'a été trouvé dans la zone d'analyse ; essayer d'ajuster la zone d'analyse, le moteur ou la luminosité minimale.", + "ocrStoppedEarlyMessage": "Le moteur OCR s'est arrêté avant que toutes les images aient été lues. Les lignes trouvées jusqu'à présent sont donc conservées et le reste est manquant.{0}{0}{1}", "linesFoundX": "{0} lignes trouvées", "refiningTimingXY": "Affiner le timing… {0}/{1}", "fixOcrErrors": "Corriger les erreurs OCR", @@ -2840,6 +3070,13 @@ "remuxVideoFailed": "Échec du remuxage de la vidéo.", "remuxVideoPleaseSelectBoth": "Veuillez sélectionner les fichiers vidéo et audio.", "remuxVideoAssRequiresMkv": "Les sous-titres ASS/SSA nécessitent le conteneur MKV pour conserver tous les formats et styles.", + "remuxVideoSccRequiresMov": "Les sous-titres \"Scenarist\" (.scc) sont intégrés sous forme de sous-titres codés CEA-608, qui nécessitent le conteneur MOV ou MPG. Le format de sortie a été automatiquement basculé vers .mov.", + "remuxVideoSccNotWithWebM": "Les sous-titres codés \"Scenarist\" (.scc) nécessitent le conteneur MOV, qui ne peut pas contenir la vidéo VP9/AV1 d'un fichier .webm. Supprimez le fichier .scc ou convertissez-le dans un autre format de sous-titres.", + "remuxVideoMpgMaxTwoCaptionTracks": "Un fichier .mpg contient au plus deux pistes de sous-titres (CC1 et CC3) - supprimez les fichiers de sous-titres pour qu'il en reste une ou deux.", + "remuxVideoMccRequiresMpg": "Les sous-titres \"MacCaption\" (.mcc) sont intégrés sous forme de sous-titres CEA-608/708 dans la vidéo MPEG-2, ce qui nécessite le conteneur MPG", + "remuxVideoMpgReencodeVideoX": "Les sous-titres codés dans un fichier .mpg nécessitent une vidéo MPEG-2 et la vidéo est {0}. Ré-encoder la vidéo en MPEG-2 ? Cela prend plus de temps et diminue un peu la qualité.", + "remuxVideoFileHasNoAudioX": "\"{0}\" n'a pas de piste audio, il ne peut donc pas être utilisé comme source audio.", + "remuxVideoAddingClosedCaptions": "Ajout de sous-titres codés...", "remuxVideoSelectAudioTrack": "Sélectionnez la piste audio", "remuxVideoSelectAudioTrackFor": "Sélectionnez la piste audio pour {0}", "remuxVideoSelectAudioTrackPrompt": "Le fichier '{0}' contient {1} pistes audio.\r\nVeuillez sélectionner la piste audio à remuxer :", @@ -2852,6 +3089,7 @@ "remuxVideoMixAudio": "Mélanger l'audio sur une seule piste", "remuxVideoVolumePercent": "Volume %:", "remuxVideoVolumeX": "Volume {0} %", + "remuxVideoFastStart": "Optimiser pour le streaming Web (déplacer l'index vers l'avant - plus lent)", "embedSubtitlesDotDotDot": "Ajouter/retirer des sous-titres embarqués et générer une vidéo…", "generateTimeCodes": "Générer les horodatages", "checkeredImage": "Image à damier", @@ -2925,7 +3163,7 @@ "ttsDownloadEngineTitle": "TTS - Télécharger le moteur", "indexTts25LicenseTitle": "IndexTTS 2.5 - licence du modèle", "indexTts25LicenseHeader": "Le modèle IndexTTS 2.5 dispose de sa propre licence", - "indexTts25LicenseIntro": "Le moteur audio.cpp est sous licence Apache-2.0, mais les poids du modèle IndexTTS 2.5 sont concédés sous licence par bilibili selon des conditions qui ne sont pas « open source ». Veuillez prendre connaissance des points principaux avant de procéder au téléchargement.", + "indexTts25LicenseIntro": "Le moteur audio.cpp est sous licence Apache-2.0, mais les poids du modèle IndexTTS 2.5 sont concédés sous licence par bilibili selon des conditions qui ne sont pas « Open source ». Veuillez prendre connaissance des points principaux avant de procéder au téléchargement.", "indexTts25LicenseReadFullText": "Lire le texte intégral de la licence", "indexTts25LicenseModelPage": "Page du modèle", "indexTts25LicenseAcceptCheckBox": "J'ai lu et j'accepte le contrat de licence d'utilisation du modèle bilibili", @@ -2951,6 +3189,10 @@ "reEncodeGeneratingDone": "Génération terminée", "reEncodeGeneratedFilesX": "Fichiers générés ({0}) :", "reEncodeFfmpegParameters": "Paramètres ffmpeg", + "analyzingVideoX": "Analyse de la vidéo... {0}% {1}", + "analyzingVideoXofY": "Analyse de la vidéo {0}/{1}... {2} % {3}", + "backgroundImageFileNotSelected": "Fichier image d'arrière-plan non sélectionné", + "backgroundImageFileDoesNotExistX": "Le fichier d'image d'arrière-plan n'existe pas : {0}", "voices": "Voix", "presets": "Préréglages", "talkerX": "Locuteur {0}", @@ -3031,7 +3273,8 @@ "syncPoints": "Points de synchronisation", "pointSync": "Synchronisation des points", "pointSyncViaOther": "Synchronisation des points via un autre sous-titre", - "syncPointCandidateInfo": "Les lignes commençant après {0}+ secondes de silence sont mises en surbrillance : elles constituent souvent des points de synchronisation fiables.", + "syncPointCandidateAfterInfo": "Les lignes suivies de {0}+ secondes de silence sont mises en surbrillance : elles constituent souvent des points de synchronisation fiables", + "gapAfter": "Écart après", "adjustAllShortcuts": "Raccourcis clavier :\r\n\r\n• Maj + Gauche/Droite : Déplacement de 10 ms\r\n• Ctrl + Gauche/Droite : Déplacement de 100 ms\r\n• Alt + Gauche/Droite : Déplacement de 500 ms", "adjustAllShortcutsFrames": "Raccourcis clavier :\r\n\r\n• Maj + Gauche/Droite : Déplacer d'une image\r\n• Ctrl + Gauche/Droite : Déplacer de 10 images\r\n• Alt + Gauche/Droite : Déplacer d'une seconde", "offsetInSeconds": "Décalage en secondes", @@ -3092,7 +3335,7 @@ "useOnlyExtraServerParametersHint": "Démarrez llama-server avec les paramètres ci-dessus au lieu du réglage propre de Subtitle Edit - seuls le modèle, l'hôte et le port resterons définis", "serverRunningAtX": "Serveur exécuté sur {0}", "customPromptHint": "Instructions personnalisées ({0} = langue source, {1} = langue cible) ; vide = invite intégrée", - "editTranslationHint": "Cliquer sur la traduction de la ligne sélectionnée pour la modifier (\"Entrée\" fait les sauvegardes, \"Échapp\" les annules)", + "editTranslationHint": "Cliquer sur la traduction de la ligne sélectionnée pour la modifier (\"Entrée\" fait les sauvegardes, \"Échap\" les annules)", "llamaCppDownloadEngineAndModelPrompt": "llama.cpp nécessite le téléchargement du moteur llama-server et d'un modèle de traduction. Télécharger maintenant ?", "llamaCppDownloadEnginePrompt": "llama.cpp nécessite le téléchargement du moteur llama-server. Télécharger maintenant ?", "llamaCppDownloadModelPrompt": "llama.cpp nécessite le téléchargement du modèle de traduction sélectionné. Télécharger maintenant ?" @@ -3189,9 +3432,12 @@ "newEmptyDefaultMs": "Durée par défaut des nouveaux sous-titres (ms)", "timeCodeUpDownStepMs": "Incrément de modification de l'horodatage (ms)", "moveSelectedLinesStepMs": "Déplacer le raccourci des lignes sélectionnées (ms)", + "moveStartEndStepMs": "Déplacer l'étape de raccourci de début/fin (ms)", "moveLinesShortenNeighbor": "Déplacer les lignes : raccourcir la ligne précédente/suivante au lieu de la superposer", "promptBeforeDelete": "Demander confirmation avant suppression", "rememberPositionAndSize": "Mémoriser la position et la taille de la fenêtre", + "titleBarFullFileName": "Afficher le chemin complet du fichier dans la barre de titre", + "showRecentFiles": "Afficher les fichiers récents (pour la réouverture)", "openLastFileOnStart": "Ouvrir le dernier fichier récent au démarrage", "autoSave": "Enregistrement automatique (sauvegarder le fichier ouvert pendant l'édition)", "autoBackupOn": "Sauvegarde automatique activée", @@ -3218,6 +3464,8 @@ "waveformDrawGridLines": "Dessiner les lignes de la grille", "waveformUseSkiaRenderer": "Utiliser un moteur de rendu rapide expérimental", "waveformCenterVideoPositionAlsoWhenPaused": "Centrer la vidéo également en pause", + "waveformSelectCurrentSubtitleWhilePaused": "Sélectionnez également le sous-titre actuel en pause", + "waveformRememberZoom": "N'oubliez pas le zoom (horizontal et vertical)", "waveformShowToolbar": "Afficher la barre d'outils", "waveformShowToolbarEdit": "Éditer la barre d'outils…", "waveformShowToolbarEditLabel": "Éléments de la barre d'outils", @@ -3283,6 +3531,7 @@ "adjustAllTimesRememberLineSelectionChoice": "Ajuster tous les horodatages en mémorisant le choix de sélection de ligne", "mergeKeepEndTime": "Fusionner les lignes : conserver l'heure de fin (autoriser le chevauchement avec le sous-titre suivant)", "mergeKeepEndTimeOnlyAssa": "Fusionner les lignes : conserver l'heure de fin uniquement pour « Advanced Sub Station Alpha »", + "mergeAutoBreak": "Fusionner les lignes : texte fusionné avec saut automatique (désactivé pour conserver les propres sauts de ligne des lignes)", "filesAndLogs": "Fichiers et journaux", "showErrorLogFile": "Afficher le fichier journal des erreurs", "showToolsLogFile": "Afficher le fichier journal des outils", @@ -3291,6 +3540,7 @@ "showAssaLayer": "Afficher la boîte de dialogue des calques ASSA", "waveformCursorColor": "Couleur de la tête du curseur de lecture, de la forme d'onde", "waveformShotChangeColor": "Couleur de changement de prise de vue de la forme d'onde", + "waveformGridColor": "Couleur de la grille de forme d'onde", "waveformParagraphLeftColor": "Couleur de la bordure gauche de la forme d'onde", "waveformParagraphRightColor": "Couleur de la bordure droite de la forme d'onde", "waveformFancyHighColor": "Couleur haute de la forme d'onde fantaisie", @@ -3318,10 +3568,14 @@ "resetSyntaxColoring": "Réinitialiser la coloration syntaxique", "resetWaveform": "Réinitialiser la forme d'onde", "resetRules": "Réinitialiser les règles", - "useFrameMode": "Utiliser le mode image (hh.mm.ss.ff)", + "timeCodeMode": "Mode code temporel", + "timeCodeModeTime": "Heure (hh:mm:ss,zzz)", + "timeCodeModeFrames": "Cadres (hh:mm:ss:ff)", + "timeCodeModeFrameNumbers": "Numéros de cadre", "textBoxLimitNewLines": "Limiter le nombre de lignes dans la zone de texte du sous-titre", "mpvOpenGl": "libmpv - OpenGL", "mpvSoftwareRendering": "libmpv - Rendu logiciel (lent)", + "mpvMetalRendering": "libmpv - Metal (expérimental, doit être redémarré)", "mpvWidRendering": "libmpv - Rendu par ID de fenêtre natif", "waveFormsAndSpectrogramFoldersContainsX": "Les dossiers « Waveforms » et « Spectrogram » contiennent {0}", "deleteWaveformAndSpectrogramFoldersQuestion": "Supprimer les fichiers « Waveforms » et « Spectrogram » ?", @@ -3395,6 +3649,7 @@ "useFocusedButtonBackgroundColor": "Utiliser la couleur d'arrière-plan du bouton actif", "focusedButtonBackgroundColor": "Couleur d'arrière-plan du bouton actif", "forceCrLfOnSave": "Forcer CR+LF à la sauvegarde (fichiers de sous-titre texte)", + "linuxClipboardUseExternalTool": "Copier le texte via xclip/wl-copy (conserve les caractères accentués)", "showFormatLimitWarning": "Avertir lors de l'enregistrement lorsque les lignes dépassent les limites du format (par exemple SCC 32 caractères/ligne)", "textBoxButtonShowAutoBreak": "Zone de texte : afficher le bouton « Césure automatique »", "textBoxButtonShowUnbreak": "Zone de texte : afficher le bouton « Retour au texte »", @@ -3407,12 +3662,17 @@ "allSettings": "Tous les paramètres", "uiScale": "Échelle de l’interface utilisateur (%)", "fontScale": "Échelle de police (%)", + "textSelectionOpacity": "Opacité de la sélection du texte (%)", "waveformToolbarItems": "Éléments de la barre d’outils « Forme d'onde »", "matchIconColorToDarkTheme": "Adapter la couleur des icônes à la couleur de premier plan du thème sombre", "subtitlePreviewProperties": "Propriétés de l’aperçu des sous-titres", "usePositionFromSubtitleFile": "Utiliser la position du fichier de sous-titres (TTML/PAC/EBU STL)", "marginIsPartOfSubtitleArea": "La marge fait partie de la zone de sous-titres", "textJustify": "Justifier les lignes", + "deinterlace": "Vidéo désentrelacée", + "deinterlaceOff": "Désactivé", + "deinterlaceAuto": "Auto (images entrelacées uniquement)", + "deinterlaceAlways": "Toujours", "pixelWidthInfo": "Lignes vertes = limite de largeur maximale | Zone rouge = texte dépassant la limite", "spellCheckEngineHunSpelll": "Hunspell", "spellCheckEngineMsWord": "MS Word", @@ -3441,11 +3701,34 @@ "categoryVideo": "Vidéo", "categorySync": "Synchronisation", "categoryAi": "IA", + "categoryCustom": "Personnalisé", + "editCustomShortcut": "Modifier le raccourci personnalisé", + "customShortcutTextBoxKeyHint": "Utilisez une touche avec Ctrl/Alt ici - une touche simple ne serait plus saisie dans la zone de texte.", + "customShortcutNumberX": "Raccourci personnalisé n° {0}", + "customShortcutNumberXY": "Raccourci personnalisé n° {0} : {1}", + "customShortcutStep": "Étape", + "customShortcutSteps": "Étapes", + "customShortcutStepRunCommand": "Exécuter la commande", + "customShortcutStepInsertText": "Insérer du texte", + "customShortcutStepReplace": "Rechercher et remplacer", + "customShortcutInsertAtCursor": "Au curseur", + "customShortcutInsertAtStart": "Au début du texte", + "customShortcutInsertAtEnd": "À la fin du texte", + "customShortcutSummaryRunCommandX": "Exécuter : {0}", + "customShortcutSummaryInsertXY": "Insérer \"{0}\" ({1})", + "customShortcutSummaryReplaceXY": "Remplacer \"{0}\" par \"{1}\"", + "customShortcutSummaryReplaceRegexXY": "Remplacer l'expression régulière \"{0}\" par \"{1}\"", + "customShortcutPickCommand": "Veuillez sélectionner une commande à exécuter.", + "customShortcutEnterText": "Veuillez saisir le texte à insérer.", + "customShortcutEnterFind": "Veuillez saisir le texte pour trouver.", + "customShortcutInvalidRegexX": "Expression régulière non valide : {0}", + "customShortcutStepsHint": "Les marches vont de haut en bas. Les étapes de texte fonctionnent sur les lignes sélectionnées ; les commandes qui ouvrent une fenêtre continuent après sa fermeture.", "activeIn": "Actif dans", "activeInEverywhere": "Partout", "generalMergeSelectedLinesAndUnbreak": "Fusionner les lignes sélectionnées et rétablir la ligne", "generalMergeSelectedLinesAndUnbreakCjk": "Fusionner les lignes sélectionnées et supprimer le saut de ligne sans espace (CJK)", "generalUnbreakNoSpaceCjk": "Supprimer le saut de ligne sans espace(CJK)", + "generalToggleBreak": "Basculer pause/déconnexion", "generalMergeSelectedLinesBilingual": "Fusionner les lignes sélectionnées bilingues", "generalToggleTranslationMode": "Activer/désactiver le mode traduction", "generalToggleTranslationAndOriginalInPreviews": "Basculer la traduction et l'original dans l'aperçu vidéo/audio", @@ -3467,6 +3750,7 @@ "fileOpenKeepVideo": "Ouvrir (conserver la vidéo)", "fileSave": "Enregistrer", "fileSaveAs": "Enregistrer sous", + "fileSaveOriginal": "Enregistrer l'original", "fileCompare": "Comparer", "fileStatistics": "Statistiques", "fileImportPlainText": "Importer le texte brut", @@ -3487,6 +3771,7 @@ "detectKey": "Détecter la touche", "control": "Ctrl", "alt": "Alt", + "altGr": "AltGr", "win": "Win", "shift": "Maj", "controlMac": "⌃", @@ -3497,6 +3782,11 @@ "resetShortcutsDetail": "Voulez-vous réinitialiser tous les raccourcis à leurs valeurs par défaut ?", "togglePlayPause": "Lecture/pause", "toggleLockTimeCodes": "Verrouiller/déverrouiller les horodatages", + "toggleTimeCodeMode": "Basculer le mode time code (heure/images/numéros d'images)", + "toggleScreenPrivacy": "Activer/désactiver la confidentialité de l'écran (masquer les noms de fichiers, puis également les textes)", + "screenPrivacyFileNamesHidden": "Confidentialité de l'écran : noms de fichiers masqués", + "screenPrivacyFileNamesAndTextsHidden": "Confidentialité de l'écran : noms de fichiers et textes masqués", + "screenPrivacyOff": "Confidentialité de l'écran : désactivée", "duplicateSelectedLines": "Dupliquer les lignes sélectionnées", "sourceView": "Afficher l'icône « Vue source »", "addOrEditBookmark": "Ajouter ou modifier un signet", @@ -3545,6 +3835,7 @@ "listViewColumnTextUp": "Colonne, remonter le texte", "listViewColumnTextDown": "Colonne, descendre le texte", "autoTranslateSelectedLines": "Traduction automatique des lignes sélectionnées…", + "autoTranslateSelectedLinesNoPrompt": "Traduire automatiquement les lignes sélectionnées (pas d'invite, utiliser le dernier moteur/langues)", "setAssaResolution": "Définir la résolution ASSA (PlayResX/PlayResY)", "setShortcutForX": "Raccourci pour \"{0}\"", "commandFileNewKeepVideo": "Nouveau (conserver la vidéo)", @@ -3625,6 +3916,14 @@ "surroundWith": "Entourer avec…", "surroundWithXY": "Entourer avec {0}/{1}", "surroundWithNumberX": "Entourer de #{0}", + "surroundWithBehavior": "Comportement", + "surroundWithBehaviorToggle": "Basculer (ajouter ou supprimer si présent)", + "surroundWithBehaviorAdd": "Ajouter (à chaque fois)", + "surroundWithBehaviorRemove": "Supprimer (tout)", + "surroundWithBehaviorRemoveOnce": "Supprimer (un à chaque fois)", + "surroundWithWorksOn": "Fonctionne sur", + "surroundWithScopeSelectionOrText": "Sélection, sinon texte entier", + "surroundWithScopeEachLine": "Chaque ligne", "searchVia": "Rechercher via", "searchViaX": "Rechercher via {0}", "searchViaNumberX": "Rechercher via #{0}", @@ -3652,6 +3951,11 @@ "copySubtitlePathToClipboard": "Copier le chemin du sous-titre dans le presse-papiers", "copySubtitleOriginalPathToClipboard": "Copier le chemin du sous-titre original dans le presse-papiers", "focusTextBox": "Zone de texte de sélection", + "focusSubtitleListView": "Vue de la liste des sous-titres Focus", + "focusWaveform": "Forme d'onde de mise au point", + "focusOriginalTextBox": "Focus sur la zone de texte d'origine", + "textBoxGoToStart": "Zone de texte, aller au début", + "textBoxGoToEnd": "Zone de texte, aller à la fin", "sortByNumber": "Trier par numéro", "sortByStartTime": "Heure de départ", "sortByEndTime": "Heure de fin", @@ -3691,6 +3995,7 @@ "googleIt": "Recherche Google (texte sélectionné)", "setActorX": "Définir l'acteur : {0}", "setNewActor": "Définir un nouvel acteur…", + "setStyleX": "Définir le style : {0}", "toggleDialogDashes": "Basculer les tirets de dialogue", "mergeWithLineBeforeAsDialog": "Joindre à la ligne précédente comme dialogue", "mergeWithLineAfterAsDialog": "Joindre à la ligne suivante comme dialogue", @@ -3702,6 +4007,10 @@ "moveStartOneFrameForwardKeepGapPrev": "Avancer le début d'une image (garder l'écart au précédent s'il est proche)", "moveEndOneFrameBackKeepGapNext": "Reculer la fin d'une image (garder l'écart au suivant s'il est proche)", "moveEndOneFrameForwardKeepGapNext": "Avancer la fin d'une image (garder l'écart au suivant s'il est proche)", + "moveStartXMsBack": "Reculer le début de X ms (X défini dans Paramètres, également dans Visual Sync)", + "moveStartXMsForward": "Avancer le début de X ms (X défini dans Paramètres, également dans Visual Sync)", + "moveEndXMsBack": "Reculer la fin de X ms (X défini dans Paramètres, également dans Visual Sync)", + "moveEndXMsForward": "Avancer la fin de X ms (X défini dans Paramètres, également dans Visual Sync)", "moveSelectedLinesXMsBack": "Déplacer les lignes sélectionnées de X ms en arrière (X défini dans Paramètres)", "moveSelectedLinesXMsForward": "Avancer les lignes sélectionnées de X ms (X défini dans Paramètres)", "moveSelectedLinesAndForwardXMsBack": "Déplacer les lignes sélectionnées et toutes les X ms suivantes en arrière (X défini dans Paramètres)", @@ -3856,6 +4165,8 @@ "binaryImageCompareInspectImageMatches": "Comparaison d'images binaires - Inspection des correspondances d'images", "saveBlankTextTitle": "Enregistrer le texte vide ?", "saveBlankTextPrompt": "Enregistrer le texte vide pour l'image ?", + "drawnLinesDoNotMatchTitle": "Les lignes ne correspondent pas à l'image", + "drawnLinesDoNotMatchPrompt": "Certaines des lignes tracées ne correspondent pas à l'image (les lignes vertes doivent être sur le caractère, les lignes rouges doivent être en dehors), ce caractère ne sera donc pas reconnu.\r\n\r\nEnregistrer quand même ?", "yesAndNeverAskAgain": "Oui, et ne plus jamais poser la question", "importTextFromSubtitleDotDotDot": "Importer le texte du sous-titre", "importTextFromSubtitleOverwritePrompt": "Certaines lignes contiennent déjà du texte OCR. Voulez-vous écraser le texte existant ?", @@ -3914,9 +4225,26 @@ "crispEmbedSettingsTitle": "Paramètres CrispEmbed", "crispEmbedDescription": "Moteur OCR local avec plusieurs backends de modèles - téléchargez le moteur et les modèles ici.", "trainNOcrDatabase": "Entraîner la base de données nOCR…", + "optimizeNOcrDatabase": "Optimiser la base de données nOCR...", + "optimizeNOcrDatabaseQuestionXY": "Supprimer les lignes qui ne distinguent pas les caractères des caractères similaires dans la base de données nOCR « {0} » ?\r\nCela rend la base de données plus petite et l'OCR plus rapide, généralement avec moins d'erreurs.\r\nCela prend jusqu'à une minute.\r\nUne sauvegarde est enregistrée sous \"{1}\".", + "optimizingNOcrDatabaseXY": "Optimisation de la base de données nOCR... {0}/{1}", + "nOcrDatabaseOptimizedXYZ": "Base de données nOCR \"{0}\" optimisée : {1:#,##0} -> {2:#,##0} lignes.", "startTraining": "Lancer l'entraînement", "abortTraining": "Arrêter l'entraînement", "trainingOptions": "Options d'entraînement", + "trainNOcrSubtitle": "Restitue les caractères dans les polices installées et apprend à nOCR leurs formes.\r\nChoisissez les polices utilisées par vos sous-titres.", + "xFontsSelected": "{0:#,##0} sélectionné", + "subtitleFonts": "", + "emptyDatabase": "", + "startFromDatabase": "", + "startFromDatabaseHint": "", + "fontSizes": "", + "fontSizesHint": "", + "numberOfLineSegmentsHint": "", + "enterDatabaseName": "", + "trainingFontXSizeYCharacterZ": "", + "xLearned": "", + "xSkipped": "", "charactersToTrain": "Caractères à entraîner", "letterCombinationsToTrain": "Combinaisons de lettres pouvant être scindées en une seule image", "importCharactersFromSubtitleFile": "Importer des caractères depuis un fichier de sous-titres…", @@ -3943,13 +4271,90 @@ "drawZoomOut": "Zoom arrière (Ctrl+-)", "drawResetView": "Réinitialiser la vue (Ctrl+0)", "drawToggleGrid": "Afficher/Masquer la grille (Ctrl+G)", + "drawTogglePreview": "", + "drawPreviewFailed": "", "drawCopyToClipboard": "Copier dans le presse-papiers (Ctrl+C)", "drawShapes": "Formes", "drawSelectedPoint": "Point sélectionné", "drawSelectedShape": "Forme sélectionnée", "drawSelectedLayer": "Calque sélectionné", "drawToolX": "Outil : {0}", - "drawHelpText": "Cliquer pour ajouter des points • Entrée/F8 pour fermer la forme • Maj+Glisser pour se déplacer • Ctrl+Molette pour zoomer", + "drawHelpText": "Cliquer pour ajouter des points • F8/Entrée pour fermer la forme • Maj+Glisser pour se déplacer • Ctrl+Molette pour zoomer", + "drawMoveToLayer": "", + "drawFlipHorizontal": "", + "drawFlipVertical": "", + "drawHideShape": "", + "drawShowShape": "", + "drawHideLayer": "", + "drawShowLayer": "", + "drawDeleteLayer": "", + "drawDeletePoint": "", + "drawCancelDrawing": "", + "drawLayerX": "", + "drawImportSvg": "", + "drawRotateClockwise": "", + "drawConvertToCurve": "", + "drawBackground": "", + "drawPosition": "", + "drawZoom": "", + "drawShapeTool": "", + "drawColorPickerTool": "", + "drawShapeLibrary": "", + "drawShapeLibraryHint": "", + "drawAddToLibrary": "", + "drawRemoveFromLibrary": "", + "drawShapeName": "", + "drawCategorySpeechBubbles": "", + "drawCategoryArrows": "", + "drawCategoryBasicShapes": "", + "drawCategorySymbols": "", + "drawCategoryMyShapes": "", + "drawShapeSpeechBubble": "", + "drawShapeSpeechBubbleRight": "", + "drawShapeSpeechBubbleUp": "", + "drawShapeOvalBubble": "", + "drawShapeBoxBubble": "", + "drawShapeThoughtBubble": "", + "drawShapeShoutBubble": "", + "drawShapeCaptionBox": "", + "drawShapeArrow": "", + "drawShapeDoubleArrow": "", + "drawShapeChevron": "", + "drawShapeCurvedArrow": "", + "drawShapeRoundedRectangle": "", + "drawShapeTriangle": "", + "drawShapeDiamond": "", + "drawShapePentagon": "", + "drawShapeHexagon": "", + "drawShapeOctagon": "", + "drawShapeStar": "", + "drawShapeRing": "", + "drawShapePlus": "", + "drawShapeBanner": "", + "drawShapeHeart": "", + "drawShapeCheck": "", + "drawShapeMusicNote": "", + "drawShapeMusicNotes": "", + "drawShapeLightning": "", + "drawShapeCloud": "", + "drawShapeMoon": "", + "drawShapeBadge": "", + "drawSize": "", + "drawLayer": "", + "drawBackgroundVideoFrame": "", + "drawBackgroundVideoFrameAt": "", + "drawBackgroundImage": "", + "drawBackgroundNone": "", + "drawBackgroundStretch": "", + "drawBackgroundOpacity": "", + "drawBackgroundVideoFrameFailed": "", + "drawImages": "", + "drawConvertToLine": "", + "drawConvertShapeToCurves": "", + "drawConvertShapeToLines": "", + "drawRotateCounterClockwise": "", + "drawSvgImages": "", + "drawSvgNoShapes": "", "progressBarTitle": "Barre de progression ASSA", "progressBarSettings": "Barre de progression", "mouseOverColor": "Couleur au survol", @@ -4046,13 +4451,26 @@ "graphics": "Graphismes", "copyToStorageStyles": "Copier les styles de stockage", "copyToFileStyles": "Copier les styles de fichier", + "styleXAlreadyExistsInStorage": "Un style nommé \"{0}\" existe déjà dans le stockage.\r\n\r\nL'écraser ou conserver les deux (la copie reçoit un nouveau nom) ?", + "styleXAlreadyExistsInFile": "Un style nommé \"{0}\" existe déjà dans le fichier.\r\n\r\nL'écraser ou conserver les deux (la copie reçoit un nouveau nom) ?", + "styleNameCannotBeEmpty": "Un nom de style ne peut pas être vide.", + "styleNameXIsUsedMoreThanOnce": "Le nom de style « {0} » est utilisé par plusieurs styles dans le fichier.", + "overwrite": "Écraser", + "keepBoth": "Gardez les deux", + "doThisForAllConflictingStyles": "Faites ceci pour tous les styles contradictoires", "setStyleAsDefault": "Définir le style par défaut", "takeUsagesFromDotDotDot": "Récupérer les utilisations de…", "replaceStyleWithDotDotDot": "Remplacer le style avec…", "noAttachmentsFound": "Aucune pièce jointe trouvée dans le fichier ASSA sélectionné.", "deleteStyleQuestion": "Supprimer le style ?", "deleteStylesQuestion": "Supprimer les styles ?", + "deleteXStylesQuestion": "Voulez-vous supprimer {0} styles ?", + "deleteXStylesFromStorageQuestion": "Voulez-vous supprimer {0} styles du stockage ?", + "deleteStyleXFromStorageQuestion": "Voulez-vous supprimer le style « {0} » du stockage ?", + "deleteStyleXFromFileQuestion": "Voulez-vous supprimer le style « {0} » du fichier actuel ?", "openStyleImportFile": "Ouvrir le fichier de sous-titres pour importer les styles", + "styleImportFiles": "Fichiers de style (.ass, .sty, Subtitle Edit 4 catégories .template)", + "ssaStyleImportFiles": "Fichiers de styles (.ssa, .ass, .sty)", "primary": "Primaire", "secondary": "Secondaire", "applyOverrideTags": "Appliquer les balises de remplacement", From d26485e478a6e2710cbf8a64e7fe6f106eecc1ab Mon Sep 17 00:00:00 2001 From: Curious Insatiable <113294705+Need74@users.noreply.github.com> Date: Sun, 11 Oct 2026 20:29:27 +0200 Subject: [PATCH 076/306] Update print statement from 'Hello' to 'Goodbye' --- src/ui/Assets/Languages/French.json | 472 ++++++++++++++++++++++++++-- 1 file changed, 445 insertions(+), 27 deletions(-) diff --git a/src/ui/Assets/Languages/French.json b/src/ui/Assets/Languages/French.json index a4ef86757db..3890b9b7fd5 100644 --- a/src/ui/Assets/Languages/French.json +++ b/src/ui/Assets/Languages/French.json @@ -1,7 +1,7 @@ { "title": "Subtitle Edit", - "version": "v5.3.0-bêta16", - "translatedBy": "Need74 . (29.sept.2026)", + "version": "v5.3.0-rc3", + "translatedBy": "Need74 . (11.oct.2026)", "cultureName": "fr-FR", "general": { "abort": "Abandonner", @@ -10,12 +10,21 @@ "setActorDotDotDot": "Définir l'acteur…", "actorPickerTitle": "Définir l'acteur", "actorPickerFilterHint": "Tapez pour filtrer ou saisissez le nom d'un nouvel acteur", - "ignoreNumbering": "Ignorer la numérotation", - "ignoreNumberingHint": "Les lignes qui ne diffèrent que par leur numéro ne sont pas considérées comme différentes", - "actorPickerKeysHint": "Appuyez sur un chiffre ou sur \"Entrée\" pour définir l'acteur.\r\nAlt+Haut/Bas modifie l'ordre, \"Suppr\" supprime l'acteur.", "actorPickerNewActorX": "Nouvel acteur \"{0}", "actorPickerLinesSelectedX": "Lignes sélectionnées : {0}", + "setStyleDotDotDot": "Définir le style…", + "stylePickerTitle": "Définir le style", + "stylePickerFilterHint": "Tapez pour filtrer ou tapez un nouveau nom de style", + "stylePickerKeysHint": "Appuyez sur un chiffre (1-9, 0) pour définir le style sur toutes les lignes sélectionnées.", + "stylePickerNewStyleX": "Nouveau style \"{0}", + "stylePickerNewStyleInfo": "Appuyez sur \"Entrée\" pour ajouter ce style avec les paramètres par défaut et définissez-le sur les lignes sélectionnées.\r\nUtilisez \"Gérer les styles…\" pour modifier ultérieurement sa police, ses couleurs et sa position.", + "stylePickerCurrentStyleX": "Style actuel : {0}", + "stylePickerLinesUsingStyleX": "Lignes utilisant ce style : {0}", + "stylePickerMarginsXYZ": "Gauche {0}, droite {1}, verticale {2}", + "stylePickerSetStyle": "_Définir le style", + "stylePickerManageStyles": "Gérer les styles…", + "stylePickerManageStylesHint": "Fermer cette fenêtre et ouvrir le gestionnaire de styles pour modifier les polices, les couleurs, la position et plus encore.", "add": "Ajouter", "addDotDotDot": "Ajouter…", "addToNamesListCaseSensitive": "Ajouter à la liste des noms (sensible à la casse)", @@ -434,6 +443,7 @@ "percent": "Pourcentage", "pickLayer": "Définir la couche", "pickOllamaModel": "Choisir le modèle Ollama", + "pickModel": "Choisir le modèle", "llamaCppUseRemoteServer": "Utiliser un serveur externe (URL)", "llamaCppEngineSettings": "Paramètres du moteur llama.cpp", "llamaCppEngineSettingsSubtitle": "Serveur local llama.cpp", @@ -510,6 +520,7 @@ "reset": "Réinitialiser", "resizePanels": "Redimensionner les panneaux", "resolution": "Résolution", + "retry": "Réessayer", "reverseRightToLeftStartEnd": "Inverser début/fin RTL (lignes sélectionnées)", "right": "Droite", "rightToLeft": "De droite à gauche", @@ -571,6 +582,8 @@ "shadowColor": "Couleur d'ombre", "shadowWidth": "Largeur d'ombre", "shiftLineBy": "Décaler la ligne de", + "shotIn": "Tourné dans", + "shotOut": "Tiré", "shortcut": "Raccourci", "shortcuts": "Raccourcis", "show": "Afficher", @@ -582,9 +595,13 @@ "showStartColumn": "Afficher la colonne « Début »", "showHideColumn": "Afficher la colonne « Masquer »", "showHistory": "Afficher l'historique", + "clearHistory": "Effacer l'historique", + "removeFromHistory": "Supprimer de l'historique", "showLayerColumn": "Afficher la colonne « Couche »", "showPreview": "Afficher l'aperçu", "showShotChangesList": "Afficher la liste des changement de plan", + "showShotInColumn": "Afficher la colonne \"Tourné dans\"", + "showShotOutColumn": "Afficher la colonne \"Tiré\"", "showStyleColumn": "Afficher la colonne « Style »", "showTeletext": "Afficher la colonne « Télétexte » en mode liste", "showTimeCodes": "Afficher l'horodatage", @@ -641,12 +658,14 @@ "styleExaggeration": "Exagération du style", "styles": "Styles", "subtitleFile": "Fichier sous-titre", + "subtitleFileChangedOutsideX": "Le fichier \"{0}\" a été modifié en dehors de la modification des sous-titres.\r\n\r\nLe recharger et perdre vos modifications ?", "subtitleFileName": "Nom du fichier sous-titre", "subtitleFileSaved": "Fichier sous-titre sauvegardé", "subtitleFileSavedToX": "Fichier sous-titre sauvegardé vers {0}", "subtitleFiles": "Fichiers sous-titre", "subtitleFormats": "Formats de sous-titres", "subtitleLoadedX": "Sous-titres chargés : {0}", + "subtitleReloadedChangedOutsideX": "Sous-titre rechargé (modifié en dehors de la modification des sous-titres) : {0}", "suffix": "Suffixe", "suggestions": "Suggestions", "sync": "Synchro.", @@ -730,10 +749,23 @@ "videoCustom3ForwardX": "Vidéo, avance personnalisée de ({0:#,###,##0}) ms, 3", "videoCustom4BackX": "Vidéo, recul personnalisée de ({0:#,###,##0}) ms, 4", "videoCustom4ForwardX": "Vidéo, avance personnalisée de ({0:#,###,##0}) ms, 4", + "videoCustom1BackAndPauseX": "Vidéo, recul personnalisée de ({0:#,###,##0}) ms et en pause, 1", + "videoCustom1ForwardAndPauseX": "Vidéo, avance personnalisée de ({0:#,###,##0}) ms et en pause, 1", + "videoCustom2BackAndPauseX": "Vidéo, recul personnalisée de ({0:#,###,##0}) ms et en pause, 2", + "videoCustom2ForwardAndPauseX": "Vidéo, avance personnalisée de ({0:#,###,##0}) ms et en pause, 2", + "videoCustom3BackAndPauseX": "Vidéo, recul personnalisée de ({0:#,###,##0}) ms et en pause, 3", + "videoCustom3ForwardAndPauseX": "Vidéo, avance personnalisée de ({0:#,###,##0}) ms et en pause, 3", + "videoCustom4BackAndPauseX": "Vidéo, recul personnalisée de ({0:#,###,##0}) ms et en pause, 4", + "videoCustom4ForwardAndPauseX": "Vidéo, avance personnalisée de ({0:#,###,##0}) ms et en pause, 4", "videoExtension": "Extension du fichier vidéo", "videoFile": "Fichier vidéo", "videoFileGenerated": "Fichier vidéo généré", "videoFileGeneratedX": "Fichier vidéo généré : \"{0}\"", + "videoFilesGenerated": "Fichiers vidéo générés", + "xVideosGenerated": "{0} vidéos générées", + "xOfYVideosGenerated": "{0} vidéos sur {1} générées", + "savedInX": "Enregistré dans {0}", + "savedInXFolders": "Enregistré dans {0} dossiers", "videoFiles": "Fichiers vidéo", "videoAndAudioFiles": "Fichiers vidéo et audio", "videoOffset": "Décalage vidéo", @@ -860,6 +892,7 @@ "multipleReplace": "Remplacements _multiples…", "fillSelectedLinesWithClipboard": "Remplir les lignes sélectionnées avec le texte du presse-papiers", "goToLineNumber": "_Aller à la ligne N°…", + "commandPalette": "_Palette de commandes…", "rightToLeftMode": "Mode de dro_ite à gauche (RTL)", "modifySelectionDotDotDot": "Modifier la _sélection…", "tools": "_Outils", @@ -1052,6 +1085,9 @@ "charactersPerSecond": "Car./sec : {0}", "chooseColumn": "Choisir une colonne", "columnPaste": "Coller en colonnes", + "commandPaletteTitle": "Palette de commandes", + "commandPaletteSearchPlaceholder": "Tapez pour rechercher des commandes…", + "commandPaletteNoCommandsFound": "Aucune commande correspondante", "createdEmptyTranslation": "Créer une traduction vide à partir du sous-titre actuel", "deleteText": "Supprimer le texte", "deleteTextAndShiftCellsUp": "Supprimer le texte et décaler les cellules vers le haut", @@ -1097,6 +1133,7 @@ "overwriteExistingCells": "Écraser les cellules existantes", "overwriteOrShiftCellsDown": "Écraser/décaler les cellules vers le bas", "parsingMatroskaFile": "Analyse du fichier Matroska…", + "readingDvdSubtitles": "Lecture des sous-titres d'un DVD…", "pasteFromClipboardDotDotDot": "Coller depuis le presse-papiers", "redoPerformed": "Rétablissement effectué", "redoPerformedXActionLeft": "Rétablissement effectué (actions restantes : {0})", @@ -1136,12 +1173,14 @@ "trimmedXLines": "{0} lignes de sous-titre supprimées", "pastedXLinesOverSelectedLines": "Collé {0} ligne(s) sur les lignes sélectionnées", "pastedXOfYLinesOverSelectedLines": "Collé {0} de {1} ligne(s) sur les lignes sélectionnées : le reste ne correspondait pas à la sélection", + "insertedXLinesFromClipboard": "{0} ligne(s) insérée(s) à partir du presse-papiers", "openOriginalDifferentNumberOfSubtitlesXY": "Le fichier de sous-titres d'origine ne contient pas le même nombre de sous-titres que le fichier de sous-titres actuel.\n\n• Sous-titres d'origine : {0}\n• Sous-titres actuels : {1}", "originalTextReadOnly": "Texte original (lecture seule)", "originalTextEditMode": "Texte original (mode édition)", "originalIsReadOnlyNotSaved": "Le sous-titre original est une référence en lecture seule et n'a pas été enregistré", "originalIsEmptyNotSaved": "Le sous-titre original ne contient aucun texte et n'a pas été enregistré", "originalIsReadOnlyReference": "Le sous-titre original est ouvert en tant que référence en lecture seule", + "closeTranslationQuestionXY": "Fermer la traduction \"{0}\" ?\r\n\r\nLe sous-titre original \"{1}\" devient le sous-titre que vous modifiez. Le fichier de traduction lui-même n'est pas modifié.", "allowEditOfOriginalSubtitle": "Autoriser la modification du sous-titre original", "showAllOriginalLinesX": "Afficher toutes les lignes originales ({0} n'ont pas de correspondance ici)", "showAllOriginalLinesHint": "Les lignes sans correspondance sont affichées comme des lignes supplémentaires, afin que vous puissiez voir ce qui manque à la traduction.", @@ -1176,6 +1215,7 @@ "fileExportedInFormatXToY": "Fichier exporté au format {0} vers {1}", "fileExportedInFormatXToFileY": "Fichier exporté au format \"{0}\" vers le fichier \"{1}\"", "fixedXLines": "{0} lignes corrigées", + "toolXChangedYLines": "{0} : {1} lignes modifiées", "transcriptionCompletedWithXLines": "Transcription terminée avec {0} lignes", "openSubtitleFileX": "Ouvrir le fichier de sous-titre \"{0}\" ?", "replacedXOccurrences": "{0} occurrences remplacées", @@ -1266,6 +1306,21 @@ "formattingDotDotDot": "Mise en forme…", "imageBasedSubtitleForEditDotDotDot": "Sous-titre basé sur une image pour l'édition…", "imageBasedSubtitleForOcrDotDotDot": "Sous-titre basé sur une image pour l'OCR…", + "dvdSubtitlesDotDotDot": "Sous-titres d'un DVD (IFO/VOB)…", + "titleImportDvd": "Importer des sous-titres à partir d'un DVD", + "dvdIfoFile": "Fichier IFO", + "dvdVobFiles": "Fichiers VOB", + "dvdVideoStandard": "Norme vidéo", + "dvdStartRipping": "Commencer à déchirer", + "dvdRippingAborted": "Extraction interrompue", + "dvdOpenIfoFile": "Ouvrir le fichier DVD IFO", + "dvdAddVobFiles": "Ajouter des fichiers VOB", + "dvdIfoFiles": "Fichiers DVD IFO", + "dvdVobFileType": "Fichiers DVD VOB", + "dvdDropHint": "Ouvrez un fichier IFO (VIDEO_TS\\VTS_01_0.IFO) ou ajoutez des fichiers VOB - ou déposez-les ici", + "dvdNotAnIfoFileX": "{0} n'est pas un fichier DVD IFO", + "dvdMenuIfoNoTitleSetsX": "Aucun ensemble de titres (VTS_xx_0.IFO) trouvé à côté de {0}", + "dvdEncryptedXOfY": "{0} des {1} paquets de sous-titres sont toujours cryptés en CSS : ces fichiers VOB ont été copiés sans déchiffrement, de sorte que certaines images de sous-titres seront tronquées.\r\nDécryptez d'abord le DVD avec un outil d'extraction de DVD pour un résultat propre.\r\nContinuer quand même ?", "splitTextAt": "Scinder le texte à", "blankLines": "Lignes vides", "oneLineIsOneSubtitle": "Une ligne correspond à un sous-titre", @@ -1290,7 +1345,8 @@ "forcedAlignerIsolatingSpeech": "Isoler le discours… {0}", "forcedAlignerIsolatingSpeechFailed": "Impossible d'isoler le discours - les heures de fin ont été définies à partir de l'heure de lecture.", "forcedAlignerModel": "Modèle d'alignement", - "forcedAlignerDownloadEngine": "Télécharger / mettre à jour le moteur…" + "forcedAlignerDownloadEngine": "Télécharger / mettre à jour le moteur…", + "forcedAlignerEstimatedXOfY": "{0} lignes sur {1} n'ont pas pu être alignées et ont été placées en fonction de l'heure de lecture ; leurs horodatages ne sont qu'une estimation.\r\nLe script est probablement plus long que ce qui est dit dans la vidéo." }, "export": { "exportImagesProfiles": "Exporter les profils d'images", @@ -1528,16 +1584,64 @@ "compareNumberDifference": "Différence de nombre", "ignoreWhitespace": "Ignorer les espaces blancs", "ignoreWhitespaceHint": "Les lignes qui diffèrent uniquement par les espaces, les tabulations ou les sauts de ligne ne sont pas considérées comme différentes.", - "ignoreNumbering": "Ignorer la numérotation", - "ignoreNumberingHint": "Les lignes qui diffèrent uniquement par leur numéro ne sont pas considérées comme différentes", "ignoreFormatting": "Ignorer la mise en forme", "ignoreFormattingHint": "Les lignes qui diffèrent uniquement par les balises de formatage, comme ou {\\an8}, ne sont pas considérées comme différentes", "showOnlyDifferencesInText": "Afficher uniquement les différences dans le texte", + "ignoreNumbering": "Ignorer la numérotation", + "ignoreNumberingHint": "Les lignes qui diffèrent uniquement par leur numéro ne sont pas considérées comme différentes", + "ignoreTimeDifferenceMs": "Tolérance de temps (ms)", + "ignoreTimeDifferenceMsHint": "Les heures de début et de fin qui diffèrent de ce nombre de millisecondes ou moins ne sont pas considérées comme différentes.", + "compareDifferences": "Différences", + "compareTextDifferences": "Différences de texte", + "compareCurrent": "Actuel", + "compareReference": "Référence", + "compareEditable": "Éditable", + "compareEditableHint": "Les modifications apportées ici reviennent au sous-titre lorsque vous cliquez sur Appliquer", + "compareReadOnly": "Lecture seule", + "compareTakeFromReference": "Prendre le texte et le timing de la référence", + "compareInsertFromReference": "Insérer cette ligne dans le sous-titre actuel", + "compareDeleteFromCurrent": "Supprimer cette ligne du sous-titre actuel", + "compareTakeText": "Prendre du texte", + "compareTakeTiming": "Prendre le temps", + "compareEditLine": "Éditer la ligne", + "compareEditHint": "\"Ctrl+Entrée\" pour enregistrer, \"Échap\" pour annuler", + "compareEdited": "Édité", + "compareOnePendingChange": "1 modification en attente", + "compareXPendingChanges": "{0} modifications en attente", + "compareDiscardXChanges": "Supprimer les {0} modifications apportées dans \"Compare\" ?", + "compareXChangesApplied": "Comparer : {0} modification(s) appliquée(s)", + "compareChangeTextX": "#{0} texte depuis la référence", + "compareChangeTimingX": "#{0} synchronisation depuis la référence", + "compareChangeTextAndTimingX": "#{0} texte et synchronisation depuis la référence", + "compareChangeEditedX": "#{0} éditié", + "compareChangeInsertedX": "#{0} inséré", + "compareChangeDeletedX": "#{0} supprimé", + "compareSyncPoint": "Point de synchro", + "compareSyncPointHint": "Point de synchro : ces deux lignes sont toujours affichées par paire, et les lignes au-dessus et en dessous sont alignées individuellement", + "compareSyncPickCurrent": "Point de synchro : utiliser cette ligne actuelle", + "compareSyncPickReference": "Point de synchro : utiliser cette ligne de référence", + "compareSyncRemove": "Supprimer le point de synchro", + "compareClearXSyncPoints": "Effacer les points de synchro ({0})", + "compareSyncCurrentPickedX": "N° {0} actuel choisi comme point de synchronisation : sélectionner la ligne de référence correspondante et cliquer sur \"Synchroniser\".", + "compareSyncReferencePickedX": "Référence n° {0} choisie comme point de synchronisation : sélectionner la ligne actuelle correspondante et cliquer sur Synchroniser.", + "compareSyncWithCurrentX": "Synchroniser avec le numéro actuel {0}", + "compareSyncWithReferenceX": "Synchroniser avec la référence #{0}", + "compareSyncApply": "Synchro", + "compareSyncPointSetXY": "Ensemble de points de synchro : le numéro {0} actuel et le numéro de référence {1} sont désormais affichés sous forme de paire.", + "compareSyncAlreadyPairedXY": "Le numéro {0} actuel et le numéro de référence {1} sont déjà affichés par paire avec le même début – rien à synchroniser", + "compareSyncPointShiftedXYZW": "Ensemble de points de synchronisation : #{0}-#{1} actuel décalé de {2} pour commencer par la référence #{3}", + "compareChangeSyncShiftXYZ": "#{0}-#{1} décalé de {2} (point de synchronisation)", + "compareTimingDeltaHint": "Combien de temps plus tard (+) ou plus tôt (−), commence et se termine la référence par rapport à la ligne actuelle", "loadXFromFile": "Charger \"{0}\" depuis un fichier", + "compareLoadSavedFile": "Charger le fichier enregistré", "saveCompareHtmlTitle": "Enregistrer le fichier HTML de comparaison", "pickMatroskaTrackX": "Choisir la piste Matroska - {0}", "pickTransportStreamTrackX": "Choisir la piste du flux de transport - {0}", + "chapters": "Chapitres", + "dvdVobFilesMissingX": "Fichiers VOB manquants ({0} % trouvés)", "pickMp4TrackX": "Choisir la piste MP4 - {0}", + "pickMxfTrackX": "Choisir une piste MXF – {0}", + "pickMpegTrackX": "Choisir une piste MPEG – {0}", "rosettaProperties": "Propriétés IMSC Rosetta du texte temporisé", "rosettaFontSize": "Taille de police (hauteur de ligne)", "propertyTimeBase": "Base de temps", @@ -1612,7 +1716,8 @@ "findWhat": "Rechercher quoi", "descriptionOptional": "Description (facultative)", "invalidRegularExpressionX": "Expression régulière invalide : {0}", - "regularExpressionTooSlowX": "L'expression régulière a abandonné après {0} secondes ; la règle est ignorée, essayez un motif plus simple" + "regularExpressionTooSlowX": "L'expression régulière a abandonné après {0} secondes ; la règle est ignorée, essayez un motif plus simple", + "moveToCategory": "Passer à la catégorie" }, "find": { "searchTextWatermark": "Texte de recherche…", @@ -1692,7 +1797,22 @@ "mismatchWarning": "Ne ressemble pas à l'original - peut appartenir à une autre ligne", "engineError": "Impossible de joindre le moteur d'IA : {0}", "playCurrentLineHint": "Lire la ligne sélectionnée dans le lecteur vidéo et mettre en pause à la fin (ou double-cliquer sur une ligne)", - "editAfterHint": "Cliquer sur le texte de la ligne sélectionnée pour modifier le correctif avant de l'appliquer (\"Entrée\" fait les sauvegardes, \"Échapp\" les annules)" + "editAfterHint": "Cliquer sur le texte de la ligne sélectionnée pour modifier le correctif avant de l'appliquer (\"Entrée\" fait les sauvegardes, \"Échap\" les annules)", + "template": "Modèle", + "templatePlaceholder": "Choisiir un modèle pour remplacer l'invite", + "templateStandard": "Relecture standard", + "templateSpeechToText": "Nettoyage de la reconnaissance vocale (STT)", + "templateMachineTranslation": "Correction de la traduction automatique", + "templateSdh": "SDH / malentendant", + "referenceContext": "Contexte de référence", + "referenceContextInfo": "Facultatif.\r\nNoms, personnages, glossaire, synopsis ou notes de style - le modèle considère cela comme l'autorité pour les noms et les termes.\r\nEnvoyé avec chaque lot.", + "referenceContextPlaceholder": "Noms : Kaito, tante Mei, Dr Hollis\r\nTermes : Shinkansen, noyau de distorsion\r\nSynopsis : Kaito se rend à Tokyo pour retrouver son père disparu.", + "generateContext": "Générer avec l'IA", + "generateContextHint": "Laisser le moteur d'IA sélectionné lire les sous-titres et les projets de noms, les termes et un synopsis - vérifier le résultat avant utilisation", + "generatingContextPartXOfY": "Lecture de la partie {0} de {1}…", + "generatingContextSynopsis": "Rédaction du synopsis…", + "replaceContextQuestion": "Remplacer le contexte de référence actuel ?", + "noContextGenerated": "Le moteur d'IA n'a renvoyé aucune information de référence utilisable." }, "aiAssistant": { "title": "Assistant IA", @@ -1738,6 +1858,7 @@ "removeLineBreaksPixelWidth": "Afficher les sous-titres tenant sur une seule ligne (largeur en pixels)", "fixUppercaseIInsideLowercaseWords": "Corriger les 'i' majuscule à l'intérieur des mots minuscules (erreur OCR)", "fixDoubleApostrophes": "Corriger les doubles apostrophes (' ') en apostrophes guillemet simple (\")", + "fixMisreadQuotes": "Correction des apostrophes mal lues sous forme de guillemets doubles (OCR, anglais)", "addPeriods": "Ajouter un point après les lignes où la ligne suivante commence par une majuscule", "startWithUppercaseLetterAfterParagraph": "Commencer par une majuscule après un paragraphe", "startWithUppercaseLetterAfterPeriodInsideParagraph": "Commencer par une majuscule après le point à l'intérieur d'un paragraphe.", @@ -1760,6 +1881,7 @@ "fixCommasExample": ",, -> ,", "removeLineBreaksExample": "Foo
bar ! -> Foo bar !", "fixDoubleApostrophesExample": "''Contient des guillemets-simples doubles'' -> \"Contient des guillemets-doubles simples\"", + "fixMisreadQuotesExample": "\"Bonjour\" -> \"Bonjour", "addPeriodsExample": "Bonjour le monde -> Bonjour le monde.", "startWithUppercaseLetterAfterParagraphExample": "p1 : Foobar ! || p2 : foobar ! -> p1 : Foobar ! || p2 : Foobar !", "startWithUppercaseLetterAfterPeriodInsideParagraphExample": "Bonjour ! comment allez-vous ? -> Bonjour ! Comment allez-vous ?", @@ -1849,6 +1971,8 @@ "fixMinDurationMs": "Fixer la durée minimale (ms)", "doNotGoPastShotChange": "Ne pas dépasser le changement de plan", "fixMaxDurationMs": "Fixer la durée maximale (ms)", + "fixMinDurationFrames": "Fixer la durée minimale (images)", + "fixMaxDurationFrames": "Fixer la durée maximale (images)", "minimumDurationMilliseconds": "Durée minimale (ms) :", "maximumDurationMilliseconds": "Durée maximale (ms) :", "maxDurationShouldBeHigherThanMinDuration": "La durée maximale doit être supérieure à la durée minimale", @@ -1910,6 +2034,20 @@ "isolateSpeechHint": "Supprime la musique et les effets sonores avant l'alignement, de sorte que l'aligneur n'entend que le dialogue.\r\nAide pour les lignes prononcées sur une musique forte ou une action, mais prend environ autant de temps que l'audio lui-même avec un GPU - et plusieurs fois plus longtemps sans un.", "isolatingSpeech": "Discours isolant… (cela prend du temps)", "isolateSpeechFailed": "Impossible d'isoler le discours - aligné à la place sur l'audio d'origine.", + "checkWithSpeechToText": "Vérifiez avec la synthèse vocale", + "checkWithSpeechToTextHint": "Transcrit également l'audio et vérifie chaque mouvement par rapport à l'endroit où les mots ont été réellement entendus.\r\nLes grands mouvements confirmés par le discours sont cochés pour vous, et les mouvements éloignés de l'endroit où une ligne est entendue sont laissés décochés pour que vous puissiez les vérifier.\r\nIndique également la part de chaque ligne qui a été entendue : une part faible signifie que le texte diffère de ce qui est dit.\r\nUn sous-titre qui est plus désynchronisé que le décalage maximum (un décalage constant, une dérive due à une inadéquation de fréquence d'images ou des sauts là où les scènes ont été coupées) est d'abord synchronisé par les mots entendus.\r\nUtilise Crisp ASR Parakeet, qui prend quelques minutes pour un long métrage avec GPU.", + "checkWithSpeechToTextNotAvailableX": "La vérification de la synthèse vocale n'est pas disponible pour {0}", + "transcribingToCheck": "Transcrire pour vérifier l'alignement…", + "speechToTextFailed": "La reconnaissance vocale a échoué : les horodatages n'ont pas été vérifiés.", + "speechToTextX": "Reconnaissance vocale : {0}", + "speechToTextConfirmedXDisputedY": "Discours : {0} confirmé, {1} contesté", + "hardlyHeardX": "La synthèse vocale n'a entendu que {0} % des mots du sous-titre.\r\nLe sous-titre est-il dans la langue parlée ?\r\nL'aligneur ne peut pas effectuer de translation.", + "heard": "Entendu", + "heardHint": "Quelle proportion de reconnaissance vocale a été entendue.\r\nUne part faible signifie que le texte diffère de ce qui est dit - là où l'aligneur est le plus susceptible de se tromper.", + "statusConfirmedBySpeech": "Confirmé par discours", + "statusDisputedBySpeech": "J'ai entendu où c'était", + "statusMovedWithSync": "Déplacé avec la synchronisation", + "syncedFirstXY": "Synchronisé en premier : {0} s au début, {1} s à la fin", "showSpeechOnly": "Afficher uniquement le discours", "showSpeechOnlyHint": "Dessinez la forme d'onde alignée à partir de l'audio en supprimant la musique et les effets sonores ;\r\nla forme d'onde originale reste telle quelle, à titre de comparaison.\r\nDisponible une fois la parole isolée - ici, ou avec \"Afficher la parole uniquement\" dans la forme d'onde de la fenêtre principale.", "align": "Aligner", @@ -1924,6 +2062,7 @@ "meanShiftX": "Décalage moyen : {0} ms", "toCheckX": "Pour vérifier : {0}", "largeMovesToCheckX": "{0} ligne(s) se déplaceraient très loin d'elles-mêmes et ne seraient pas cochées - jouez Original / Aligné et cochez celles qui sont correctes.", + "linesToCheckX": "{0} lignes sont non cochées pour vérification - écoutez-les (F5 lit la ligne alignée, Maj+F5 l'original) et cochez celles qui sont correctes.", "changeXOfY": "Modifier {0} sur {1}", "previousChange": "Changement précédent", "nextChange": "Prochain changement", @@ -1936,10 +2075,12 @@ "statusNoSpeech": "Pas de discours", "statusShiftTooLarge": "Conservé - décalage trop important", "statusFailed": "Conservé - l'aligneur a échoué", + "statusAdjustedByHand": "Ajusté à la main", + "alignedWaveformHint": "Faites glisser une ligne pour la déplacer ou faites glisser ses bords pour modifier son début ou sa fin.", + "playLine": "Ligne de lecture", + "undoAdjustment": "Annuler l'ajustement", "playPause": "Lecture/pause", - "playOriginal": "Original", "playAligned": "Aligné", - "playOriginalHint": "Jouer la ligne sélectionnée avec ses horodatages d'origine", "playAlignedHint": "Jouer la ligne sélectionnée avec ses horodatages alignés", "apply": "Appliquer", "applyHint": "Décocher une ligne pour conserver ses horodatages d'origine.\r\nDouble-cliquer sur une ligne pour la lire." @@ -2022,6 +2163,7 @@ "addColor": "Ajouter de la couleur", "deleteLinesWithSpecificActorsOrStyles": "Supprimer les lignes avec des acteurs ou des styles spécifiques (séparer les multiples par une virgule)", "useSourceStylesIfPossible": "Utiliser les styles source si possible", + "keepSourceEmbeddedFonts": "Conserver les polices sources intégrées (pied de page)", "editStyles": "Éditer les styles", "editProperties": "Éditer les propriétés", "editAttachments": "Éditer les pièces jointes", @@ -2056,6 +2198,18 @@ "addFolderRecursiveDotDotDot": "Ajouter un dossier récursif…", "selectFolderToConvert": "Sélectionner le dossier contenant les fichiers à convertir", "includeSubfolders": "Inclure les sous-dossiers lors de l'ajout d'un dossier", + "videoFilesWhenAddingFolder": "Fichiers vidéo (mkv, mp4, ts…) lors de l'ajout d'un dossier", + "videoFilesAsk": "Demander", + "videoFilesInclude": "Ajouter leurs pistes de sous-titres", + "videoFilesSkip": "Sauter", + "folderContainsXVideoFiles": "Le dossier contient {0} fichier(s) vidéo (mkv, mp4, ts…).\r\n\r\nAjouter également leurs pistes de sous-titres intégrées, ou uniquement les fichiers de sous-titres ?", + "addVideoFiles": "Ajouter des fichiers vidéo", + "skipVideoFiles": "Fichiers de sous-titres uniquement", + "doNotAskAgainVideoFiles": "Ne plus demander (modification des paramètres de conversion par lots)", + "settingsSectionOutput": "Sortir", + "settingsSectionAddingFiles": "Ajout de fichiers", + "settingsSectionImageBasedSubtitles": "Sous-titres basés sur des images (OCR)", + "settingsSectionOther": "Autre", "keepSourceFileTimestamp": "Conserver la date/heure du fichier source sur les fichiers de sortie", "preventSleepWhileConverting": "Empêcher l'ordinateur de dormir pendant la conversion", "scanningFolderX": "Analyse de {0}…", @@ -2071,7 +2225,20 @@ "transportStreamOnlyTeletext": "Uniquement le télétexte", "transportStreamGetSizeFromVideo": "Obtenez la taille de la vidéo…", "twoLetterLanguageCodeUppercase": "Code de langue à deux lettres (majuscules)", - "threeLetterLanguageCodeUppercase": "Code de langue à trois lettres (majuscules)" + "threeLetterLanguageCodeUppercase": "Code de langue à trois lettres (majuscules)", + "formatContainsDotDotDot": "Le format contient…", + "extensionIsDotDotDot": "La prolongation est…", + "statusIsError": "Le statut est en erreur", + "forcedTracksOnly": "Pistes forcées uniquement", + "xConvertedYFailedInZ": "{0:#,###,##0} conversion, {1:#,###,##0} a échoué dans {2}", + "xNotProcessed": "{0:#,###,##0} non traité", + "removeUnicodeControlCharactersTitle": "Supprimer les caractères de contrôle Unicode", + "removeUnicodeControlCharactersInfo": "Supprime les caractères de contrôle de direction Unicode invisibles (LRM, RLM, LRE, RLE, PDF, LRO et RLO) du texte et remplace les espaces insécables par des espaces normaux.", + "preset": "Préréglage", + "savePresetDotDotDot": "Enregistrer le préréglage…", + "deletePreset": "Supprimer le préréglage", + "presetName": "Nom du préréglage", + "deletePresetX": "Supprimer le préréglage \"{0}\" ?" }, "changeCasing": { "fixNames": "Corriger les noms", @@ -2156,11 +2323,13 @@ }, "mergeLinesWithSameText": { "maxMsBetweenLines": "Écart max. (ms) entre les lignes", + "maxFramesBetweenLines": "Nombre maximum d'images entre les lignes", "includeIncrementingLines": "Inclure les lignes avec un texte incrémentiel", "includeRollUpCaptions": "Inclure des sous-titres déroulants (défileants)" }, "mergeLinesWithSameTimeCodes": { "maxMsDifference": "Différence max. (ms)", + "maxFramesDifference": "Différence maximale (images)", "makeDialog": "Créer des dialogues" }, "netflixCheckAndFix": { @@ -2275,7 +2444,14 @@ "xInTopBar": "{0} dans la barre supérieure", "xInBottomBar": "{0} dans la barre inférieure", "noImageSubtitlesLoaded": "Aucun sous-titre d'image chargé", - "imageBasedFormatNotSupported": "Format de sous-titre basé sur une image introuvable/pris en charge." + "imageBasedFormatNotSupported": "Format de sous-titre basé sur une image introuvable/pris en charge.", + "frameRateDeclaredInFile": "Fréquence d'images déclarée dans le fichier Blu-ray sup", + "frameRateDetectedFromTimeCodesX": "Fréquence d'images détectée à partir des codes temporels Blu-ray sup (le fichier déclare {0})", + "frameRateFromVideo": "Fréquence d'images de la vidéo", + "frameRateCurrent": "Fréquence d'images actuelle", + "frameRateSetManually": "Fréquence d'images réglée manuellement", + "frameRateUsageInfo": "Les exportations Blu-ray sup, BDN XML, DOST et FCP sont écrites à cette fréquence d'images. Cela ne change pas à chaque fois - utilisez \"Modifier la fréquence d'images\" pour cela.", + "useVideoFrameRateXInsteadOfY": "La fréquence d'images de la vidéo est {0}, mais le sous-titre est défini sur {1}.\r\n\r\nUtiliser la fréquence d'images de la vidéo ({0}) ?" }, "removeTextForHearingImpaired": { "interjections": "Interjections", @@ -2305,6 +2481,7 @@ "pickFontNameInstalledFonts": "Polices installées", "pickFontNameCollectedFonts": "Polices collectées", "colorPickerTitle": "Choisir la couleur de police", + "colorPickerBrightness": "Luminosité", "filterLayersTitle": "Filtrer les calques à afficher", "filterLayersHideFromWaveform": "Masquer le calque de la forme d'onde/du spectrogramme", "filterLayersHideFromSubtitleGrid": "Masquer le calque de la grille des sous-titres", @@ -2355,7 +2532,21 @@ "correctWordsX": "Mots corrects : {0}", "namesX": "Noms : {0}", "addedToDictionaryX": "Ajoutés au dictionnaire : {0}", - "doNotShowThisAgain": "Ne plus afficher ce message" + "doNotShowThisAgain": "Ne plus afficher ce message", + "editUseAlwaysList": "Modifier la liste \"Utiliser toujours\"…", + "useAlwaysListTitle": "Vérification orthographique : liste \"Toujours utiliser ", + "useAlwaysListHeader": "Liste \"Toujours utiliser\"", + "useAlwaysListDescription": "La vérification orthographique des mots est automatiquement remplacée, enregistrée par \"Tout modifier\" et \"Toujours utiliser\". Une paire n'est utilisée que lorsque le mot est mal orthographié.", + "useAlwaysWord": "Mot mal orthographié", + "xPairs": "{0:#,##0} paires", + "xUnusedPairs": "{0:#,##0} jamais utilisé", + "useAlwaysUnusedHint": "Jamais utilisé : ce mot est correctement orthographié (ou est un nom ou un mot utilisateur), donc la vérification orthographique ne le signale jamais", + "removeUnused": "Supprimer les inutilisés", + "useAlwaysEmpty": "Aucune paire \"Toujours utiliser\" pour cette langue pour l'instant", + "useAlwaysEmptyHint": "Les paires sont ajoutées lorsque vous cliquez sur \"Tout modifier  ou \"Toujours utiliser  dans la vérification orthographique.", + "useAlwaysNoMatch": "Aucune paire ne correspond à la recherche", + "useAlwaysSameWord": "Le remplacement doit être différent du mot.", + "useAlwaysRememberOff": "\"N'oubliez pas , la liste \"Toujours utiliser  est désactivé dans les paramètres, la vérification orthographique n'utilise pas cette liste ni ne l'enregistre." }, "video": { "burnIn": { @@ -2393,13 +2584,21 @@ "translateToEnglish": "Traduction en anglais", "transcribing": "Transcription…", "transcribingXOfY": "Transcription de {0} sur {1}…", + "linesTranscribedXOfYFailedZ": "{0} lignes sur {1} ont été transcrites ; {2} ont échoué ou n'avaient pas de parole.\r\n\r\nLes lignes échouées conservent leur texte actuel. Appliquer les lignes transcrites ou réessayer celles qui ont échoué (vous pouvez d'abord modifier le moteur ou les paramètres) ?", + "linesTranscribedXOfYCancelled": "La transcription a été annulée après {0} lignes sur {1}.\r\n\r\nAppliquer les lignes transcrites ? Les autres lignes conservent leur texte actuel.", + "noLinesTranscribed": "Aucune des lignes {0} n'a pu être transcrite.\r\nVeuillez consulter le journal des outils pour plus de détails.", + "applyTranscribedLines": "Appliquer la transcription", + "retryFailedLines": "La nouvelle tentative a échoué", "isolateSpeech": "Isoler la parole (lent)", "isolateSpeechHint": "Supprimer la musique et les effets sonores avant la transcription.\r\nDonne moins de lignes manquées et un meilleur timing sur l'audio avec de la musique forte, mais prend environ aussi longtemps que l'audio lui-même avec un GPU - et bien plus longtemps sans.", "isolatingSpeech": "Discours isolant…", "isolateSpeechFailed": "Impossible d'isoler le discours - transcrire l'audio original à la place.", "detectSpeakers": "Détecter les haut-parleurs", "detectSpeakersHint": "Commencer chaque ligne avec le haut-parleur, comme \"(haut-parleur 0)\", en utilisant la diarisation Sortformer de NVIDIA (jusqu'à 8 haut-parleurs).\r\nFonctionne mieux avec les backends qui divisent les lignes là où le haut-parleur change, comme Parakeet et Cohere.", - "detectSpeakersNeedsNewerCrispAsr": "\"Détecter les haut-parleurs\" nécessite Crisp ASR v{0} ou une version plus récente, mais v{1} est installé.\r\nMettez à jour Crisp ASR (le bouton d'engrenage à côté du moteur) ou désactivez « Détecter les haut-parleurs ».", + "detectSpeakersNeedsNewerCrispAsr": "\"Détecter les haut-parleurs\" nécessite Crisp ASR v{0} ou une version plus récente, mais v{1} est installé.\r\nMettez à jour Crisp ASR (le bouton d'engrenage à côté du moteur) ou désactivez \"Détecter les haut-parleurs .", + "xNeedsNewerCrispAsr": "{0} a besoin de Crisp ASR v{1} ou plus récent, mais v{2} est installé.\r\n\r\nTélécharger et installer la mise à jour maintenant ?", + "vad": "VAD", + "vadHint": "Détection de l'activité vocale : seule la parole est retranscrite, coupée là où l'orateur fait une pause.\r\nAuto : Silero, et uniquement pour les moteurs qui en ont besoin (Cohere, Mega, Index-Echo).\r\nSilero, FireRedVAD, WebRTC : toujours activé, pour chaque moteur Crisp ASR. FireRedVAD est le plus précis.\r\nIndex-Echo utilise toujours Silero. --vad, --vad-model ou --chunk-seconds dans les paramètres avancés sont prioritaires.", "inputLanguage": "Langue d’entrée", "advancedWhisperSettings": "Paramètres Whisper avancés", "downloadingSpeechToTextEngine": "Téléchargement du moteur de reconnaissance vocale", @@ -2450,7 +2649,7 @@ "qualityReportAll": "Tous", "qualityReportIssue": "Problème", "qualityReportDetail": "Détail", - "qualityReportTip": "Les lignes sont répertoriées avec leurs numéros dans le nouveau sous-titre.\r\nUtiliser « Corriger les erreurs courantes » ou les paramètres de post-traitement pour corriger plus automatiquement, ou essayer un autre modèle/moteur si de nombreuses lignes sont affectées.", + "qualityReportTip": "Les lignes sont répertoriées avec leurs numéros dans le nouveau sous-titre.\r\nUtiliser \"Corriger les erreurs courantes  ou les paramètres de post-traitement pour corriger plus automatiquement, ou essayer un autre modèle/moteur si de nombreuses lignes sont affectées.", "qualityReportDoNotShowAgain": "Ne plus afficher ce rapport", "engineSettings": "Paramètres du moteur parole vers texte", "engineSettingsSubtitle": "Moteur parole vers texte", @@ -2488,12 +2687,14 @@ "customServerUrl": "URL du serveur personnalisé", "customServerUrlHint": "Point de terminaison de parole complète d'un serveur compatible OpenAI, utilisé lorsque le fournisseur est \"Personnalisé\", par ex.\r\nhttp://localhost:8880/v1/audio/speech", "customModels": "Modèles personnalisés", - "customModelsHint": "Noms de modèles séparés par des virgules proposés lorsque le fournisseur est « Personnalisé »", + "customModelsHint": "Noms de modèles séparés par des virgules proposés lorsque le fournisseur est \"Personnalisé ", "customVoices": "Voix personnalisées", "customVoicesHint": "Noms de voix séparés par des virgules, utilisés par le fournisseur « Personnalisé » et par les modèles OpenRouter qui ne publient aucune liste de voix", "instructions": "Instructions", "instructionsHint": "Style de parole facultatif, par ex.\r\n\"Parlez d'une voix calme et chaleureuse\".\r\nEnvoyé uniquement aux modèles qui le prennent en charge (pas tts-1/tts-1-hd)", "openAiSpeedHint": "Vitesse de parole de 0,25 à 4,0 (1,0 est normal).\r\nTous les modèles ne le prennent pas en charge", + "outputFormat": "Format de sortie", + "outputFormatHint": "Format audio demandé au serveur. \"Auto\" demande MP3 et passe en PCM pour les modèles qui ne renvoient que PCM (par exemple Gemini TTS)", "regenerateAudio": "Régénérer l'audio", "autoContinuePlaying": "Reprise automatique de la lecture", "playLine": "Ligne de jeu", @@ -2521,6 +2722,17 @@ "voiceManagerTitle": "TTS - Gestionnaire vocal", "voiceManagerDotDotDot": "Gestionnaire de voix…", "voiceManagerMenuItem": "Gestionnaire vocal TTS…", + "speakFromCurrentLineTitle": "Parler depuis la ligne actuelle", + "speakFromCurrentLineDotDotDot": "Parler depuis la ligne actuelle…", + "speakFromCurrentLineHint": "Lit les lignes à voix haute les unes après les autres, en commençant par la ligne actuelle. Appuyez sur \"Échap\" ou cliquez sur une autre ligne pour arrêter.", + "stopSpeaking": "Arrête de parler", + "speakingLineX": "Ligne parlante {0}… (\"Échap\" pour arrêter)", + "playWithSpeechTitle": "Jouer avec la parole", + "playWithSpeechDotDotDot": "Jouez avec la parole…", + "playWithSpeechHint": "Lit la vidéo et prononce chaque ligne lorsque la vidéo l'atteint. Faites une pause et cherchez comme d'habitude ; appuyez sur \"Échap\" pour arrêter.", + "lowerVideoVolumeWhileSpeaking": "Baisser le volume vidéo pendant que vous parlez", + "pauseVideoWhenSpeechRunsLate": "Mettre la vidéo en pause lorsque la parole passe à la ligne suivante", + "waitingForSpeechLineX": "Génération de la parole pour la ligne {0}… (\"Échap\" pour arrêter)", "voiceKindClone": "Voix clonée", "voiceKindPreset": "Intégré", "voiceKindModel": "Modèle de voix", @@ -2585,14 +2797,29 @@ "advancedTtsSettings": "Paramètres TTS avancés", "advancedTtsAudioProcessing": "Traitement audio", "advancedTtsOutput": "Sortir", + "advancedTtsVoiceCloning": "Clonage vocal", + "cleanCloneReferences": "Nettoyage des références de \"clone vocal\" (Sidon)", + "cleanCloneReferencesDescription": "Supprime la musique, le bruit et la réverbération de l'audio à partir duquel une voix est clonée : clips extraits de la vidéo et enregistrements importés dans le gestionnaire de voix. Utilise le modèle de restauration vocale Sidon sur audio.cpp (téléchargement de 940 Mo). La voix clonée ne copie plus l'arrière-plan, mais une parole très faible ou qui se chevauche peut paraître étouffée.", + "cleaningCloneReferencesDotDotDot": "Nettoyage des références de \"clone vocal\"…", + "downloadSidonTitle": "Télécharger Sidon ?", + "downloadSidonQuestionX": "Le nettoyage des références de clone vocal nécessite le modèle de restauration vocale Sidon ({0}).\r\n\r\nLe télécharger maintenant ? Sans cela, les références sont utilisées telles quelles.", "proAudioPostProcessing": "Post-traitement audio professionnel", "proAudioPostProcessingDescription": "Applique une égalisation chaleureuse, un noise gate, une compression, une normalisation du volume (-16 LUFS) et un fondu en entrée/sortie à chaque segment.", + "generatingSpeechSegmentXOfY": "Génération de parole : segment {0} sur {1}", + "adjustingSpeedSegmentXOfY": "Réglage de la vitesse : segment {0} sur {1}", + "mergingAudioSegmentXOfY": "Fusion audio : segment {0} sur {1}", + "postProcessingSegmentXOfY": "Post-traitement : segment {0} sur {1}", "audioDucking": "Atténuation du volume audio", "audioDuckingDescription": "Réduit le volume audio d'origine de la vidéo et le mélange avec l'audio TTS, de sorte que la bande-son originale reste légèrement audible.", "removeOriginalSpeech": "Supprimer le discours original (lent)", "removeOriginalSpeechDescription": "Conserve la musique et les effets sonores de la vidéo originale mais supprime son discours et mélange le nouveau discours par-dessus.\r\nCela prend environ autant de temps que la vidéo elle-même avec un GPU, et bien plus longtemps sans GPU.\r\nAvec l'atténuation audio activée, son volume s'applique à la musique et aux effets.\r\nS'applique uniquement lorsque le discours est ajouté au fichier vidéo.", "removingOriginalSpeech": "Suppression du discours original…", "removeOriginalSpeechFailed": "Impossible de supprimer le discours d'origine : le discours a été ajouté à la vidéo sans celui-ci.", + "speechRemovalModel": "Modèle", + "speechRemovalModelDescription": "La meilleure qualité permet de conserver le maximum de musique et d'effets sonores.\r\nRapide est environ trois fois plus rapide, avec un peu plus de musique perdue.", + "speechRemovalBestQuality": "Meilleure qualité (Mel-Band RoFormer)", + "speechRemovalFast": "Rapide (HTDemucs)", + "videoTruncatedWarning": "L'image du nouveau fichier vidéo s'arrête après {0}, mais la vidéo d'origine est longue de {1} : l'image se fige pendant que le son continue.\r\n\r\nLe dossier a été conservé. Voir error-log.txt dans le dossier de données Subtitle Edit pour la sortie ffmpeg.", "originalVolumePercent": "Volume original %", "vadSilenceCompression": "Compression des silences VAD", "vadSilenceCompressionDescription": "Réduit les pauses entre les mots avant les changements de rythme. Utilise la détection d'activité vocale pour compresser uniquement les silences, sans altérer la parole. C'est la première étape recommandée : elle réduit la durée sans perte de qualité.", @@ -2608,14 +2835,14 @@ "generationFolder": "Dossier de génération", "generationFolderDescription": "Dossier pour les clips audio générés lors d'une exécution en cours.\r\nLaisser vide pour utiliser le dossier temporaire du système.\r\nChaque exécution obtient son propre sous-dossier.", "deleteTempFiles": "Supprimer les clips générés à la fermeture", - "deleteTempFilesDescription": "Supprime le dossier de génération de l'exécution lorsque la fenêtre « Synthèse vocale » se ferme.\r\nLes fichiers audio et vidéo enregistrés ne sont pas affectés.\r\nDésactiver cette option pour conserver les clips individuels.", + "deleteTempFilesDescription": "Supprime le dossier de génération de l'exécution lorsque la fenêtre \"Synthèse vocale  se ferme.\r\nLes fichiers audio et vidéo enregistrés ne sont pas affectés.\r\nDésactiver cette option pour conserver les clips individuels.", "edgeTtsRate": "Débit de synthèse vocale Edge-TTS", "edgeTtsRateDescription": "Débit de parole par défaut pour Edge-TTS, (p. ex. \"+50%\", \"-30%\" ou \"+0%\").", "edgeTtsPitch": "Hauteur de la voix Edge-TTS", "edgeTtsPitchDescription": "Réglage de la hauteur de la voix par défaut pour Edge-TTS, (p. ex. \"+10Hz\", \"-5Hz\" ou \"+0Hz\").", "edgeTtsVolume": "Volume de la synthèse vocale Edge-TTS", "edgeTtsVolumeDescription": "Réglage du volume de la synthèse vocale par défaut pour Edge-TTS, (p. ex. \"+20%\", \"-10%\" ou \"+0%\").", - "downloadPiperPrompt": "\"La synthèse vocale » nécessite Piper.\r\n\r\nTélécharger et utiliser Piper ?", + "downloadPiperPrompt": "\"La synthèse vocale  nécessite Piper.\r\n\r\nTélécharger et utiliser Piper ?", "omniVoiceTtsSettings": "Paramètres OmniVoice TTS", "reDownloadOmniVoiceTts": "Retélécharger OmniVoice TTS", "selectTheBuildToDownload": "\r\nSélectionner la version à télécharger :", @@ -2655,7 +2882,7 @@ "skipNoiseLinesFoundX": "{0} lignes contiennent uniquement des sons ou de la musique ; les lignes cochées restent silencieuses", "skipNoiseLinesColumnSkip": "Sauter", "detectSpeakersPromptTitle": "Noms des intervenants trouvés dans le texte ?", - "detectSpeakersPromptMessage": "Certaines lignes commencent par le nom d'un locuteur, comme « MIKE : » ou « [NARRATEUR] ».\r\n\r\n\r\nDéplacé dans le champ des acteurs, chaque locuteur peut obtenir sa propre voix via « Configurer la distribution\", et le nom n'est pas lu à haute voix.\r\n\r\n\r\nRevoir les noms maintenant ?\r\n(seule la génération vocale est affectée - le sous-titre lui-même n'est pas modifié)", + "detectSpeakersPromptMessage": "Certaines lignes commencent par le nom d'un locuteur, comme « MIKE : » ou « [NARRATEUR] ».\r\n\r\n\r\nDéplacé dans le champ des acteurs, chaque locuteur peut obtenir sa propre voix via \"Configurer la distribution\", et le nom n'est pas lu à haute voix.\r\n\r\n\r\nRevoir les noms maintenant ?\r\n(seule la génération vocale est affectée - le sous-titre lui-même n'est pas modifié)", "detectSpeakersTitle": "Noms des intervenants dans le texte", "detectSpeakersFoundX": "{0} tags de locuteurs trouvés ({1} locuteurs) : les noms cochés deviennent des acteurs et ne sont pas lus à haute voix", "detectSpeakersColumnUse": "Utiliser", @@ -2675,7 +2902,9 @@ "timeCodeFormatColon": "Format de l'horodatage :", "shotChangesList": "Liste des modifications de plan", "shotChangesClearQuestion": "Êtes-vous sûr de vouloir effacer tous les changements de plan ?", - "deleteSelectedShotChangeQuestion": "Êtes-vous sûr de vouloir supprimer le changement de plan sélectionné ?" + "deleteSelectedShotChangeQuestion": "Êtes-vous sûr de vouloir supprimer le changement de plan sélectionné ?", + "shotChanges": "Changements de plan", + "exportShotChanges": "Exporter les modifications de prise de vue" }, "chapters": { "title": "Chapitres", @@ -2738,6 +2967,7 @@ "runningOcrXY": "Exécution de l'OCR… {0}/{1}", "noLinesFoundTitle": "Aucun sous-titre trouvé", "noLinesFoundMessage": "Aucun texte n'a été trouvé dans la zone d'analyse ; essayer d'ajuster la zone d'analyse, le moteur ou la luminosité minimale.", + "ocrStoppedEarlyMessage": "Le moteur OCR s'est arrêté avant que toutes les images aient été lues. Les lignes trouvées jusqu'à présent sont donc conservées et le reste est manquant.{0}{0}{1}", "linesFoundX": "{0} lignes trouvées", "refiningTimingXY": "Affiner le timing… {0}/{1}", "fixOcrErrors": "Corriger les erreurs OCR", @@ -2840,6 +3070,13 @@ "remuxVideoFailed": "Échec du remuxage de la vidéo.", "remuxVideoPleaseSelectBoth": "Veuillez sélectionner les fichiers vidéo et audio.", "remuxVideoAssRequiresMkv": "Les sous-titres ASS/SSA nécessitent le conteneur MKV pour conserver tous les formats et styles.", + "remuxVideoSccRequiresMov": "Les sous-titres \"Scenarist\" (.scc) sont intégrés sous forme de sous-titres codés CEA-608, qui nécessitent le conteneur MOV ou MPG. Le format de sortie a été automatiquement basculé vers .mov.", + "remuxVideoSccNotWithWebM": "Les sous-titres codés \"Scenarist\" (.scc) nécessitent le conteneur MOV, qui ne peut pas contenir la vidéo VP9/AV1 d'un fichier .webm. Supprimez le fichier .scc ou convertissez-le dans un autre format de sous-titres.", + "remuxVideoMpgMaxTwoCaptionTracks": "Un fichier .mpg contient au plus deux pistes de sous-titres (CC1 et CC3) - supprimez les fichiers de sous-titres pour qu'il en reste une ou deux.", + "remuxVideoMccRequiresMpg": "Les sous-titres \"MacCaption\" (.mcc) sont intégrés sous forme de sous-titres CEA-608/708 dans la vidéo MPEG-2, ce qui nécessite le conteneur MPG", + "remuxVideoMpgReencodeVideoX": "Les sous-titres codés dans un fichier .mpg nécessitent une vidéo MPEG-2 et la vidéo est {0}. Ré-encoder la vidéo en MPEG-2 ? Cela prend plus de temps et diminue un peu la qualité.", + "remuxVideoFileHasNoAudioX": "\"{0}\" n'a pas de piste audio, il ne peut donc pas être utilisé comme source audio.", + "remuxVideoAddingClosedCaptions": "Ajout de sous-titres codés…", "remuxVideoSelectAudioTrack": "Sélectionnez la piste audio", "remuxVideoSelectAudioTrackFor": "Sélectionnez la piste audio pour {0}", "remuxVideoSelectAudioTrackPrompt": "Le fichier '{0}' contient {1} pistes audio.\r\nVeuillez sélectionner la piste audio à remuxer :", @@ -2852,6 +3089,7 @@ "remuxVideoMixAudio": "Mélanger l'audio sur une seule piste", "remuxVideoVolumePercent": "Volume %:", "remuxVideoVolumeX": "Volume {0} %", + "remuxVideoFastStart": "Optimiser pour le streaming Web (déplacer l'index vers l'avant - plus lent)", "embedSubtitlesDotDotDot": "Ajouter/retirer des sous-titres embarqués et générer une vidéo…", "generateTimeCodes": "Générer les horodatages", "checkeredImage": "Image à damier", @@ -2925,7 +3163,7 @@ "ttsDownloadEngineTitle": "TTS - Télécharger le moteur", "indexTts25LicenseTitle": "IndexTTS 2.5 - licence du modèle", "indexTts25LicenseHeader": "Le modèle IndexTTS 2.5 dispose de sa propre licence", - "indexTts25LicenseIntro": "Le moteur audio.cpp est sous licence Apache-2.0, mais les poids du modèle IndexTTS 2.5 sont concédés sous licence par bilibili selon des conditions qui ne sont pas « open source ». Veuillez prendre connaissance des points principaux avant de procéder au téléchargement.", + "indexTts25LicenseIntro": "Le moteur audio.cpp est sous licence Apache-2.0, mais les poids du modèle IndexTTS 2.5 sont concédés sous licence par bilibili selon des conditions qui ne sont pas « Open source ». Veuillez prendre connaissance des points principaux avant de procéder au téléchargement.", "indexTts25LicenseReadFullText": "Lire le texte intégral de la licence", "indexTts25LicenseModelPage": "Page du modèle", "indexTts25LicenseAcceptCheckBox": "J'ai lu et j'accepte le contrat de licence d'utilisation du modèle bilibili", @@ -2951,6 +3189,10 @@ "reEncodeGeneratingDone": "Génération terminée", "reEncodeGeneratedFilesX": "Fichiers générés ({0}) :", "reEncodeFfmpegParameters": "Paramètres ffmpeg", + "analyzingVideoX": "Analyse de la vidéo… {0}% {1}", + "analyzingVideoXofY": "Analyse de la vidéo {0}/{1}… {2} % {3}", + "backgroundImageFileNotSelected": "Fichier image d'arrière-plan non sélectionné", + "backgroundImageFileDoesNotExistX": "Le fichier d'image d'arrière-plan n'existe pas : {0}", "voices": "Voix", "presets": "Préréglages", "talkerX": "Locuteur {0}", @@ -3031,7 +3273,8 @@ "syncPoints": "Points de synchronisation", "pointSync": "Synchronisation des points", "pointSyncViaOther": "Synchronisation des points via un autre sous-titre", - "syncPointCandidateInfo": "Les lignes commençant après {0}+ secondes de silence sont mises en surbrillance : elles constituent souvent des points de synchronisation fiables.", + "syncPointCandidateAfterInfo": "Les lignes suivies de {0}+ secondes de silence sont mises en surbrillance : elles constituent souvent des points de synchronisation fiables", + "gapAfter": "Écart après", "adjustAllShortcuts": "Raccourcis clavier :\r\n\r\n• Maj + Gauche/Droite : Déplacement de 10 ms\r\n• Ctrl + Gauche/Droite : Déplacement de 100 ms\r\n• Alt + Gauche/Droite : Déplacement de 500 ms", "adjustAllShortcutsFrames": "Raccourcis clavier :\r\n\r\n• Maj + Gauche/Droite : Déplacer d'une image\r\n• Ctrl + Gauche/Droite : Déplacer de 10 images\r\n• Alt + Gauche/Droite : Déplacer d'une seconde", "offsetInSeconds": "Décalage en secondes", @@ -3092,7 +3335,7 @@ "useOnlyExtraServerParametersHint": "Démarrez llama-server avec les paramètres ci-dessus au lieu du réglage propre de Subtitle Edit - seuls le modèle, l'hôte et le port resterons définis", "serverRunningAtX": "Serveur exécuté sur {0}", "customPromptHint": "Instructions personnalisées ({0} = langue source, {1} = langue cible) ; vide = invite intégrée", - "editTranslationHint": "Cliquer sur la traduction de la ligne sélectionnée pour la modifier (\"Entrée\" fait les sauvegardes, \"Échapp\" les annules)", + "editTranslationHint": "Cliquer sur la traduction de la ligne sélectionnée pour la modifier (\"Entrée\" fait les sauvegardes, \"Échap\" les annules)", "llamaCppDownloadEngineAndModelPrompt": "llama.cpp nécessite le téléchargement du moteur llama-server et d'un modèle de traduction. Télécharger maintenant ?", "llamaCppDownloadEnginePrompt": "llama.cpp nécessite le téléchargement du moteur llama-server. Télécharger maintenant ?", "llamaCppDownloadModelPrompt": "llama.cpp nécessite le téléchargement du modèle de traduction sélectionné. Télécharger maintenant ?" @@ -3189,9 +3432,12 @@ "newEmptyDefaultMs": "Durée par défaut des nouveaux sous-titres (ms)", "timeCodeUpDownStepMs": "Incrément de modification de l'horodatage (ms)", "moveSelectedLinesStepMs": "Déplacer le raccourci des lignes sélectionnées (ms)", + "moveStartEndStepMs": "Déplacer l'étape de raccourci de début/fin (ms)", "moveLinesShortenNeighbor": "Déplacer les lignes : raccourcir la ligne précédente/suivante au lieu de la superposer", "promptBeforeDelete": "Demander confirmation avant suppression", "rememberPositionAndSize": "Mémoriser la position et la taille de la fenêtre", + "titleBarFullFileName": "Afficher le chemin complet du fichier dans la barre de titre", + "showRecentFiles": "Afficher les fichiers récents (pour la réouverture)", "openLastFileOnStart": "Ouvrir le dernier fichier récent au démarrage", "autoSave": "Enregistrement automatique (sauvegarder le fichier ouvert pendant l'édition)", "autoBackupOn": "Sauvegarde automatique activée", @@ -3218,6 +3464,8 @@ "waveformDrawGridLines": "Dessiner les lignes de la grille", "waveformUseSkiaRenderer": "Utiliser un moteur de rendu rapide expérimental", "waveformCenterVideoPositionAlsoWhenPaused": "Centrer la vidéo également en pause", + "waveformSelectCurrentSubtitleWhilePaused": "Sélectionnez également le sous-titre actuel en pause", + "waveformRememberZoom": "N'oubliez pas le zoom (horizontal et vertical)", "waveformShowToolbar": "Afficher la barre d'outils", "waveformShowToolbarEdit": "Éditer la barre d'outils…", "waveformShowToolbarEditLabel": "Éléments de la barre d'outils", @@ -3283,6 +3531,7 @@ "adjustAllTimesRememberLineSelectionChoice": "Ajuster tous les horodatages en mémorisant le choix de sélection de ligne", "mergeKeepEndTime": "Fusionner les lignes : conserver l'heure de fin (autoriser le chevauchement avec le sous-titre suivant)", "mergeKeepEndTimeOnlyAssa": "Fusionner les lignes : conserver l'heure de fin uniquement pour « Advanced Sub Station Alpha »", + "mergeAutoBreak": "Fusionner les lignes : texte fusionné avec saut automatique (désactivé pour conserver les propres sauts de ligne des lignes)", "filesAndLogs": "Fichiers et journaux", "showErrorLogFile": "Afficher le fichier journal des erreurs", "showToolsLogFile": "Afficher le fichier journal des outils", @@ -3291,6 +3540,7 @@ "showAssaLayer": "Afficher la boîte de dialogue des calques ASSA", "waveformCursorColor": "Couleur de la tête du curseur de lecture, de la forme d'onde", "waveformShotChangeColor": "Couleur de changement de prise de vue de la forme d'onde", + "waveformGridColor": "Couleur de la grille de forme d'onde", "waveformParagraphLeftColor": "Couleur de la bordure gauche de la forme d'onde", "waveformParagraphRightColor": "Couleur de la bordure droite de la forme d'onde", "waveformFancyHighColor": "Couleur haute de la forme d'onde fantaisie", @@ -3318,10 +3568,14 @@ "resetSyntaxColoring": "Réinitialiser la coloration syntaxique", "resetWaveform": "Réinitialiser la forme d'onde", "resetRules": "Réinitialiser les règles", - "useFrameMode": "Utiliser le mode image (hh.mm.ss.ff)", + "timeCodeMode": "Mode code temporel", + "timeCodeModeTime": "Heure (hh:mm:ss,zzz)", + "timeCodeModeFrames": "Cadres (hh:mm:ss:ff)", + "timeCodeModeFrameNumbers": "Numéros de cadre", "textBoxLimitNewLines": "Limiter le nombre de lignes dans la zone de texte du sous-titre", "mpvOpenGl": "libmpv - OpenGL", "mpvSoftwareRendering": "libmpv - Rendu logiciel (lent)", + "mpvMetalRendering": "libmpv - Metal (expérimental, doit être redémarré)", "mpvWidRendering": "libmpv - Rendu par ID de fenêtre natif", "waveFormsAndSpectrogramFoldersContainsX": "Les dossiers « Waveforms » et « Spectrogram » contiennent {0}", "deleteWaveformAndSpectrogramFoldersQuestion": "Supprimer les fichiers « Waveforms » et « Spectrogram » ?", @@ -3395,6 +3649,7 @@ "useFocusedButtonBackgroundColor": "Utiliser la couleur d'arrière-plan du bouton actif", "focusedButtonBackgroundColor": "Couleur d'arrière-plan du bouton actif", "forceCrLfOnSave": "Forcer CR+LF à la sauvegarde (fichiers de sous-titre texte)", + "linuxClipboardUseExternalTool": "Copier le texte via xclip/wl-copy (conserve les caractères accentués)", "showFormatLimitWarning": "Avertir lors de l'enregistrement lorsque les lignes dépassent les limites du format (par exemple SCC 32 caractères/ligne)", "textBoxButtonShowAutoBreak": "Zone de texte : afficher le bouton « Césure automatique »", "textBoxButtonShowUnbreak": "Zone de texte : afficher le bouton « Retour au texte »", @@ -3407,12 +3662,17 @@ "allSettings": "Tous les paramètres", "uiScale": "Échelle de l’interface utilisateur (%)", "fontScale": "Échelle de police (%)", + "textSelectionOpacity": "Opacité de la sélection du texte (%)", "waveformToolbarItems": "Éléments de la barre d’outils « Forme d'onde »", "matchIconColorToDarkTheme": "Adapter la couleur des icônes à la couleur de premier plan du thème sombre", "subtitlePreviewProperties": "Propriétés de l’aperçu des sous-titres", "usePositionFromSubtitleFile": "Utiliser la position du fichier de sous-titres (TTML/PAC/EBU STL)", "marginIsPartOfSubtitleArea": "La marge fait partie de la zone de sous-titres", "textJustify": "Justifier les lignes", + "deinterlace": "Vidéo désentrelacée", + "deinterlaceOff": "Désactivé", + "deinterlaceAuto": "Auto (images entrelacées uniquement)", + "deinterlaceAlways": "Toujours", "pixelWidthInfo": "Lignes vertes = limite de largeur maximale | Zone rouge = texte dépassant la limite", "spellCheckEngineHunSpelll": "Hunspell", "spellCheckEngineMsWord": "MS Word", @@ -3441,11 +3701,34 @@ "categoryVideo": "Vidéo", "categorySync": "Synchronisation", "categoryAi": "IA", + "categoryCustom": "Personnalisé", + "editCustomShortcut": "Modifier le raccourci personnalisé", + "customShortcutTextBoxKeyHint": "Utilisez une touche avec Ctrl/Alt ici - une touche simple ne serait plus saisie dans la zone de texte.", + "customShortcutNumberX": "Raccourci personnalisé n° {0}", + "customShortcutNumberXY": "Raccourci personnalisé n° {0} : {1}", + "customShortcutStep": "Étape", + "customShortcutSteps": "Étapes", + "customShortcutStepRunCommand": "Exécuter la commande", + "customShortcutStepInsertText": "Insérer du texte", + "customShortcutStepReplace": "Rechercher et remplacer", + "customShortcutInsertAtCursor": "Au curseur", + "customShortcutInsertAtStart": "Au début du texte", + "customShortcutInsertAtEnd": "À la fin du texte", + "customShortcutSummaryRunCommandX": "Exécuter : {0}", + "customShortcutSummaryInsertXY": "Insérer \"{0}\" ({1})", + "customShortcutSummaryReplaceXY": "Remplacer \"{0}\" par \"{1}\"", + "customShortcutSummaryReplaceRegexXY": "Remplacer l'expression régulière \"{0}\" par \"{1}\"", + "customShortcutPickCommand": "Veuillez sélectionner une commande à exécuter.", + "customShortcutEnterText": "Veuillez saisir le texte à insérer.", + "customShortcutEnterFind": "Veuillez saisir le texte pour trouver.", + "customShortcutInvalidRegexX": "Expression régulière non valide : {0}", + "customShortcutStepsHint": "Les marches vont de haut en bas. Les étapes de texte fonctionnent sur les lignes sélectionnées ; les commandes qui ouvrent une fenêtre continuent après sa fermeture.", "activeIn": "Actif dans", "activeInEverywhere": "Partout", "generalMergeSelectedLinesAndUnbreak": "Fusionner les lignes sélectionnées et rétablir la ligne", "generalMergeSelectedLinesAndUnbreakCjk": "Fusionner les lignes sélectionnées et supprimer le saut de ligne sans espace (CJK)", "generalUnbreakNoSpaceCjk": "Supprimer le saut de ligne sans espace(CJK)", + "generalToggleBreak": "Basculer pause/déconnexion", "generalMergeSelectedLinesBilingual": "Fusionner les lignes sélectionnées bilingues", "generalToggleTranslationMode": "Activer/désactiver le mode traduction", "generalToggleTranslationAndOriginalInPreviews": "Basculer la traduction et l'original dans l'aperçu vidéo/audio", @@ -3467,6 +3750,7 @@ "fileOpenKeepVideo": "Ouvrir (conserver la vidéo)", "fileSave": "Enregistrer", "fileSaveAs": "Enregistrer sous", + "fileSaveOriginal": "Enregistrer l'original", "fileCompare": "Comparer", "fileStatistics": "Statistiques", "fileImportPlainText": "Importer le texte brut", @@ -3487,6 +3771,7 @@ "detectKey": "Détecter la touche", "control": "Ctrl", "alt": "Alt", + "altGr": "AltGr", "win": "Win", "shift": "Maj", "controlMac": "⌃", @@ -3497,6 +3782,11 @@ "resetShortcutsDetail": "Voulez-vous réinitialiser tous les raccourcis à leurs valeurs par défaut ?", "togglePlayPause": "Lecture/pause", "toggleLockTimeCodes": "Verrouiller/déverrouiller les horodatages", + "toggleTimeCodeMode": "Basculer le mode time code (heure/images/numéros d'images)", + "toggleScreenPrivacy": "Activer/désactiver la confidentialité de l'écran (masquer les noms de fichiers, puis également les textes)", + "screenPrivacyFileNamesHidden": "Confidentialité de l'écran : noms de fichiers masqués", + "screenPrivacyFileNamesAndTextsHidden": "Confidentialité de l'écran : noms de fichiers et textes masqués", + "screenPrivacyOff": "Confidentialité de l'écran : désactivée", "duplicateSelectedLines": "Dupliquer les lignes sélectionnées", "sourceView": "Afficher l'icône « Vue source »", "addOrEditBookmark": "Ajouter ou modifier un signet", @@ -3545,6 +3835,7 @@ "listViewColumnTextUp": "Colonne, remonter le texte", "listViewColumnTextDown": "Colonne, descendre le texte", "autoTranslateSelectedLines": "Traduction automatique des lignes sélectionnées…", + "autoTranslateSelectedLinesNoPrompt": "Traduire automatiquement les lignes sélectionnées (pas d'invite, utiliser le dernier moteur/langues)", "setAssaResolution": "Définir la résolution ASSA (PlayResX/PlayResY)", "setShortcutForX": "Raccourci pour \"{0}\"", "commandFileNewKeepVideo": "Nouveau (conserver la vidéo)", @@ -3625,6 +3916,14 @@ "surroundWith": "Entourer avec…", "surroundWithXY": "Entourer avec {0}/{1}", "surroundWithNumberX": "Entourer de #{0}", + "surroundWithBehavior": "Comportement", + "surroundWithBehaviorToggle": "Basculer (ajouter ou supprimer si présent)", + "surroundWithBehaviorAdd": "Ajouter (à chaque fois)", + "surroundWithBehaviorRemove": "Supprimer (tout)", + "surroundWithBehaviorRemoveOnce": "Supprimer (un à chaque fois)", + "surroundWithWorksOn": "Fonctionne sur", + "surroundWithScopeSelectionOrText": "Sélection, sinon texte entier", + "surroundWithScopeEachLine": "Chaque ligne", "searchVia": "Rechercher via", "searchViaX": "Rechercher via {0}", "searchViaNumberX": "Rechercher via #{0}", @@ -3652,6 +3951,11 @@ "copySubtitlePathToClipboard": "Copier le chemin du sous-titre dans le presse-papiers", "copySubtitleOriginalPathToClipboard": "Copier le chemin du sous-titre original dans le presse-papiers", "focusTextBox": "Zone de texte de sélection", + "focusSubtitleListView": "Vue de la liste des sous-titres Focus", + "focusWaveform": "Forme d'onde de mise au point", + "focusOriginalTextBox": "Focus sur la zone de texte d'origine", + "textBoxGoToStart": "Zone de texte, aller au début", + "textBoxGoToEnd": "Zone de texte, aller à la fin", "sortByNumber": "Trier par numéro", "sortByStartTime": "Heure de départ", "sortByEndTime": "Heure de fin", @@ -3691,6 +3995,7 @@ "googleIt": "Recherche Google (texte sélectionné)", "setActorX": "Définir l'acteur : {0}", "setNewActor": "Définir un nouvel acteur…", + "setStyleX": "Définir le style : {0}", "toggleDialogDashes": "Basculer les tirets de dialogue", "mergeWithLineBeforeAsDialog": "Joindre à la ligne précédente comme dialogue", "mergeWithLineAfterAsDialog": "Joindre à la ligne suivante comme dialogue", @@ -3702,6 +4007,10 @@ "moveStartOneFrameForwardKeepGapPrev": "Avancer le début d'une image (garder l'écart au précédent s'il est proche)", "moveEndOneFrameBackKeepGapNext": "Reculer la fin d'une image (garder l'écart au suivant s'il est proche)", "moveEndOneFrameForwardKeepGapNext": "Avancer la fin d'une image (garder l'écart au suivant s'il est proche)", + "moveStartXMsBack": "Reculer le début de X ms (X défini dans Paramètres, également dans Visual Sync)", + "moveStartXMsForward": "Avancer le début de X ms (X défini dans Paramètres, également dans Visual Sync)", + "moveEndXMsBack": "Reculer la fin de X ms (X défini dans Paramètres, également dans Visual Sync)", + "moveEndXMsForward": "Avancer la fin de X ms (X défini dans Paramètres, également dans Visual Sync)", "moveSelectedLinesXMsBack": "Déplacer les lignes sélectionnées de X ms en arrière (X défini dans Paramètres)", "moveSelectedLinesXMsForward": "Avancer les lignes sélectionnées de X ms (X défini dans Paramètres)", "moveSelectedLinesAndForwardXMsBack": "Déplacer les lignes sélectionnées et toutes les X ms suivantes en arrière (X défini dans Paramètres)", @@ -3856,6 +4165,8 @@ "binaryImageCompareInspectImageMatches": "Comparaison d'images binaires - Inspection des correspondances d'images", "saveBlankTextTitle": "Enregistrer le texte vide ?", "saveBlankTextPrompt": "Enregistrer le texte vide pour l'image ?", + "drawnLinesDoNotMatchTitle": "Les lignes ne correspondent pas à l'image", + "drawnLinesDoNotMatchPrompt": "Certaines des lignes tracées ne correspondent pas à l'image (les lignes vertes doivent être sur le caractère, les lignes rouges doivent être en dehors), ce caractère ne sera donc pas reconnu.\r\n\r\nEnregistrer quand même ?", "yesAndNeverAskAgain": "Oui, et ne plus jamais poser la question", "importTextFromSubtitleDotDotDot": "Importer le texte du sous-titre", "importTextFromSubtitleOverwritePrompt": "Certaines lignes contiennent déjà du texte OCR. Voulez-vous écraser le texte existant ?", @@ -3914,9 +4225,26 @@ "crispEmbedSettingsTitle": "Paramètres CrispEmbed", "crispEmbedDescription": "Moteur OCR local avec plusieurs backends de modèles - téléchargez le moteur et les modèles ici.", "trainNOcrDatabase": "Entraîner la base de données nOCR…", + "optimizeNOcrDatabase": "Optimiser la base de données nOCR…", + "optimizeNOcrDatabaseQuestionXY": "Supprimer les lignes qui ne distinguent pas les caractères des caractères similaires dans la base de données nOCR « {0} » ?\r\nCela rend la base de données plus petite et l'OCR plus rapide, généralement avec moins d'erreurs.\r\nCela prend jusqu'à une minute.\r\nUne sauvegarde est enregistrée sous \"{1}\".", + "optimizingNOcrDatabaseXY": "Optimisation de la base de données nOCR… {0}/{1}", + "nOcrDatabaseOptimizedXYZ": "Base de données nOCR \"{0}\" optimisée : {1:#,##0} -> {2:#,##0} lignes.", "startTraining": "Lancer l'entraînement", "abortTraining": "Arrêter l'entraînement", "trainingOptions": "Options d'entraînement", + "trainNOcrSubtitle": "Restitue les caractères dans les polices installées et apprend à nOCR leurs formes.\r\nChoisissez les polices utilisées par vos sous-titres.", + "xFontsSelected": "{0:#,##0} sélectionné", + "subtitleFonts": "", + "emptyDatabase": "", + "startFromDatabase": "", + "startFromDatabaseHint": "", + "fontSizes": "", + "fontSizesHint": "", + "numberOfLineSegmentsHint": "", + "enterDatabaseName": "", + "trainingFontXSizeYCharacterZ": "", + "xLearned": "", + "xSkipped": "", "charactersToTrain": "Caractères à entraîner", "letterCombinationsToTrain": "Combinaisons de lettres pouvant être scindées en une seule image", "importCharactersFromSubtitleFile": "Importer des caractères depuis un fichier de sous-titres…", @@ -3943,13 +4271,90 @@ "drawZoomOut": "Zoom arrière (Ctrl+-)", "drawResetView": "Réinitialiser la vue (Ctrl+0)", "drawToggleGrid": "Afficher/Masquer la grille (Ctrl+G)", + "drawTogglePreview": "", + "drawPreviewFailed": "", "drawCopyToClipboard": "Copier dans le presse-papiers (Ctrl+C)", "drawShapes": "Formes", "drawSelectedPoint": "Point sélectionné", "drawSelectedShape": "Forme sélectionnée", "drawSelectedLayer": "Calque sélectionné", "drawToolX": "Outil : {0}", - "drawHelpText": "Cliquer pour ajouter des points • Entrée/F8 pour fermer la forme • Maj+Glisser pour se déplacer • Ctrl+Molette pour zoomer", + "drawHelpText": "Cliquer pour ajouter des points • F8/Entrée pour fermer la forme • Maj+Glisser pour se déplacer • Ctrl+Molette pour zoomer", + "drawMoveToLayer": "", + "drawFlipHorizontal": "", + "drawFlipVertical": "", + "drawHideShape": "", + "drawShowShape": "", + "drawHideLayer": "", + "drawShowLayer": "", + "drawDeleteLayer": "", + "drawDeletePoint": "", + "drawCancelDrawing": "", + "drawLayerX": "", + "drawImportSvg": "", + "drawRotateClockwise": "", + "drawConvertToCurve": "", + "drawBackground": "", + "drawPosition": "", + "drawZoom": "", + "drawShapeTool": "", + "drawColorPickerTool": "", + "drawShapeLibrary": "", + "drawShapeLibraryHint": "", + "drawAddToLibrary": "", + "drawRemoveFromLibrary": "", + "drawShapeName": "", + "drawCategorySpeechBubbles": "", + "drawCategoryArrows": "", + "drawCategoryBasicShapes": "", + "drawCategorySymbols": "", + "drawCategoryMyShapes": "", + "drawShapeSpeechBubble": "", + "drawShapeSpeechBubbleRight": "", + "drawShapeSpeechBubbleUp": "", + "drawShapeOvalBubble": "", + "drawShapeBoxBubble": "", + "drawShapeThoughtBubble": "", + "drawShapeShoutBubble": "", + "drawShapeCaptionBox": "", + "drawShapeArrow": "", + "drawShapeDoubleArrow": "", + "drawShapeChevron": "", + "drawShapeCurvedArrow": "", + "drawShapeRoundedRectangle": "", + "drawShapeTriangle": "", + "drawShapeDiamond": "", + "drawShapePentagon": "", + "drawShapeHexagon": "", + "drawShapeOctagon": "", + "drawShapeStar": "", + "drawShapeRing": "", + "drawShapePlus": "", + "drawShapeBanner": "", + "drawShapeHeart": "", + "drawShapeCheck": "", + "drawShapeMusicNote": "", + "drawShapeMusicNotes": "", + "drawShapeLightning": "", + "drawShapeCloud": "", + "drawShapeMoon": "", + "drawShapeBadge": "", + "drawSize": "", + "drawLayer": "", + "drawBackgroundVideoFrame": "", + "drawBackgroundVideoFrameAt": "", + "drawBackgroundImage": "", + "drawBackgroundNone": "", + "drawBackgroundStretch": "", + "drawBackgroundOpacity": "", + "drawBackgroundVideoFrameFailed": "", + "drawImages": "", + "drawConvertToLine": "", + "drawConvertShapeToCurves": "", + "drawConvertShapeToLines": "", + "drawRotateCounterClockwise": "", + "drawSvgImages": "", + "drawSvgNoShapes": "", "progressBarTitle": "Barre de progression ASSA", "progressBarSettings": "Barre de progression", "mouseOverColor": "Couleur au survol", @@ -4046,13 +4451,26 @@ "graphics": "Graphismes", "copyToStorageStyles": "Copier les styles de stockage", "copyToFileStyles": "Copier les styles de fichier", + "styleXAlreadyExistsInStorage": "Un style nommé \"{0}\" existe déjà dans le stockage.\r\n\r\nL'écraser ou conserver les deux (la copie reçoit un nouveau nom) ?", + "styleXAlreadyExistsInFile": "Un style nommé \"{0}\" existe déjà dans le fichier.\r\n\r\nL'écraser ou conserver les deux (la copie reçoit un nouveau nom) ?", + "styleNameCannotBeEmpty": "Un nom de style ne peut pas être vide.", + "styleNameXIsUsedMoreThanOnce": "Le nom de style « {0} » est utilisé par plusieurs styles dans le fichier.", + "overwrite": "Écraser", + "keepBoth": "Gardez les deux", + "doThisForAllConflictingStyles": "Faites ceci pour tous les styles contradictoires", "setStyleAsDefault": "Définir le style par défaut", "takeUsagesFromDotDotDot": "Récupérer les utilisations de…", "replaceStyleWithDotDotDot": "Remplacer le style avec…", "noAttachmentsFound": "Aucune pièce jointe trouvée dans le fichier ASSA sélectionné.", "deleteStyleQuestion": "Supprimer le style ?", "deleteStylesQuestion": "Supprimer les styles ?", + "deleteXStylesQuestion": "Voulez-vous supprimer {0} styles ?", + "deleteXStylesFromStorageQuestion": "Voulez-vous supprimer {0} styles du stockage ?", + "deleteStyleXFromStorageQuestion": "Voulez-vous supprimer le style « {0} » du stockage ?", + "deleteStyleXFromFileQuestion": "Voulez-vous supprimer le style « {0} » du fichier actuel ?", "openStyleImportFile": "Ouvrir le fichier de sous-titres pour importer les styles", + "styleImportFiles": "Fichiers de style (.ass, .sty, Subtitle Edit 4 catégories .template)", + "ssaStyleImportFiles": "Fichiers de styles (.ssa, .ass, .sty)", "primary": "Primaire", "secondary": "Secondaire", "applyOverrideTags": "Appliquer les balises de remplacement", From 80d5740838a2160d032ef20500693d862e37aaa0 Mon Sep 17 00:00:00 2001 From: GitGianluc <16348719+GitGianluc@users.noreply.github.com> Date: Mon, 12 Oct 2026 02:10:06 +0200 Subject: [PATCH 077/306] Fixed placeholder text for NewLineFormat MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In the “New Custom Text Format” dialogue box (accessible via `File > Export > Custom Text Format…`), the “New line format” field – if left blank – displays the incorrect placeholder: “Time code format” instead of “New line format”. This pull request resolves this minor issue with a small change to the `EditCustomTextFormatWindow.cs` file. It’s just a triviality, but my skills as a coder don’t go much further than that! 😅 --- .../Files/ExportCustomTextFormat/EditCustomTextFormatWindow.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ui/Features/Files/ExportCustomTextFormat/EditCustomTextFormatWindow.cs b/src/ui/Features/Files/ExportCustomTextFormat/EditCustomTextFormatWindow.cs index 227897d8ac2..e888a18d4d5 100644 --- a/src/ui/Features/Files/ExportCustomTextFormat/EditCustomTextFormatWindow.cs +++ b/src/ui/Features/Files/ExportCustomTextFormat/EditCustomTextFormatWindow.cs @@ -138,7 +138,7 @@ private static Grid MakeFormatsView(EditCustomTextFormatViewModel vm, out TextBo VerticalAlignment = VerticalAlignment.Center, MinWidth = 200, Margin = new Thickness(0, 0, 0, 3), - PlaceholderText = Se.Language.File.Export.TimeCodeFormat, + PlaceholderText = Se.Language.File.Export.NewLineFormat, ItemsSource = vm.NewLineList, [!AutoCompleteBox.TextProperty] = new Binding(nameof(vm.SelectedCustomFormat) + "." + nameof(CustomFormatItem.FormatNewLine)), MinimumPrefixLength = 0, From d9c6d7a96f687014e38bb0092627b6459d5467e2 Mon Sep 17 00:00:00 2001 From: niksedk Date: Mon, 12 Oct 2026 03:08:15 +0200 Subject: [PATCH 078/306] Batch convert: VobSub uses the profile's font and outline color Text to VobSub rebuilt the image parameters with a hard-coded white text / black outline, which VobSub turns into its 4-color palette. Text drawn in the profile's color (red, blue, green...) was then quantized to the nearest of those - usually the black outline - so every subtitle came out black. Take the colors the text was rendered with instead. Fixes #15942 Co-Authored-By: Claude Opus 5.5 --- .../OcrSubtitle/OcrSubtitleImageParameter.cs | 13 ++++ .../Tools/BatchConvert/BatchConverter.cs | 10 +++ .../BatchConverterVobSubColorTests.cs | 70 +++++++++++++++++++ 3 files changed, 93 insertions(+) create mode 100644 tests/UI/Features/Tools/BatchConvert/BatchConverterVobSubColorTests.cs diff --git a/src/ui/Features/Ocr/OcrSubtitle/OcrSubtitleImageParameter.cs b/src/ui/Features/Ocr/OcrSubtitle/OcrSubtitleImageParameter.cs index 92dabece6c6..2a9b023297d 100644 --- a/src/ui/Features/Ocr/OcrSubtitle/OcrSubtitleImageParameter.cs +++ b/src/ui/Features/Ocr/OcrSubtitle/OcrSubtitleImageParameter.cs @@ -18,6 +18,19 @@ public OcrSubtitleImageParameter(List imageParameters) Count = imageParameters.Count; } + /// + /// The parameters the text was rendered with, or null for an index out of range. + /// + public ImageParameter? GetImageParameter(int index) + { + if (index < 0 || index >= _imageParameterList.Count) + { + return null; + } + + return _imageParameterList[index]; + } + public SKBitmap GetBitmap(int index) { if (index < 0 || index >= _imageParameterList.Count) diff --git a/src/ui/Features/Tools/BatchConvert/BatchConverter.cs b/src/ui/Features/Tools/BatchConvert/BatchConverter.cs index b15d7c4b980..702256ff2fd 100644 --- a/src/ui/Features/Tools/BatchConvert/BatchConverter.cs +++ b/src/ui/Features/Tools/BatchConvert/BatchConverter.cs @@ -2020,6 +2020,16 @@ private void WriteToImageBasedFormat(BatchConvertItem item, IOcrSubtitle? imageS Depth3D = profile.Depth3D, Plane3D = plane3D, }; + // Text rendered from the export-images profile: the colors it was drawn with, not + // white/black. VobSub builds its 4-color palette from these, so with white/black a + // red or blue text was quantized to the black outline color (#15942). + if (imageSubtitle is OcrSubtitleImageParameter textImages && + textImages.GetImageParameter(i) is { } rendered) + { + param.FontColor = rendered.FontColor; + param.OutlineColor = rendered.OutlineColor; + } + var position = imageSubtitle.GetPosition(i); if (imageSubtitle is OcrSubtitleTransportStream) { diff --git a/tests/UI/Features/Tools/BatchConvert/BatchConverterVobSubColorTests.cs b/tests/UI/Features/Tools/BatchConvert/BatchConverterVobSubColorTests.cs new file mode 100644 index 00000000000..e0c75e3684f --- /dev/null +++ b/tests/UI/Features/Tools/BatchConvert/BatchConverterVobSubColorTests.cs @@ -0,0 +1,70 @@ +using Nikse.SubtitleEdit.Core.Common; +using Nikse.SubtitleEdit.Core.SubtitleFormats; +using Nikse.SubtitleEdit.Features.Tools.BatchConvert; +using Nikse.SubtitleEdit.Logic.Config; +using Nikse.SubtitleEdit.UiLogic.BatchConvert; + +namespace UITests.Features.Tools.BatchConvert; + +/// +/// Issue #15942: batch text → VobSub built the 4-color palette from a hard-coded white text / +/// black outline instead of the export-images profile, so a red (or blue, green...) font was +/// quantized to the black outline color and every subtitle came out black. +/// +public class BatchConverterVobSubColorTests +{ + private const string InputSrt = "1\n00:00:01,000 --> 00:00:03,000\nRed text.\n"; + + [Fact] + public async Task VobSub_PaletteUsesProfileFontAndOutlineColor() + { + var exportImages = Se.Settings.File.ExportImages; + var oldProfiles = exportImages.Profiles; + var oldLastProfileName = exportImages.LastProfileName; + var dir = Directory.CreateTempSubdirectory("se-batch-vobsub-color-test"); + try + { + exportImages.Profiles = + [ + new SeExportImagesProfile + { + ProfileName = "Red", + FontColor = "#ffff0000", + OutlineColor = "#ff0000ff", + }, + ]; + exportImages.LastProfileName = "Red"; + + var inputFile = Path.Combine(dir.FullName, "movie.srt"); + await File.WriteAllTextAsync(inputFile, InputSrt, TestContext.Current.CancellationToken); + + var converter = new BatchConverter(null!, null!, null!); + converter.Initialize(new BatchConvertConfig + { + TargetFormatName = BatchConverter.FormatVobSub, + SaveInSourceFolder = true, + Overwrite = true, + }); + + var subtitle = new Subtitle(); + new SubRip().LoadSubtitle(subtitle, InputSrt.SplitToLines(), inputFile); + var item = new BatchConvertItem(inputFile, 1, new SubRip().Name, subtitle); + await converter.Convert(item, TestContext.Current.CancellationToken); + + var idxFile = Path.Combine(dir.FullName, "movie.idx"); + Assert.True(File.Exists(idxFile), $"no .idx written, status: {item.Status}"); + var paletteLine = File.ReadAllLines(idxFile).Single(l => l.StartsWith("palette:", StringComparison.Ordinal)); + var palette = paletteLine["palette:".Length..].Split(',').Select(c => c.Trim()).ToArray(); + + // Index 1 = pattern (text), index 2 = emphasis 1 (outline) - see VobSubWriter.WriteColors. + Assert.Equal("ff0000", palette[1]); + Assert.Equal("0000ff", palette[2]); + } + finally + { + exportImages.Profiles = oldProfiles; + exportImages.LastProfileName = oldLastProfileName; + dir.Delete(recursive: true); + } + } +} From d65b602472a17f47a4ba5f449d7d35eb9863bb06 Mon Sep 17 00:00:00 2001 From: niksedk Date: Mon, 12 Oct 2026 03:11:26 +0200 Subject: [PATCH 079/306] Batch convert: keep {\fad} fades in Blu-ray sup Text to Blu-ray sup rebuilt the image parameters without the fade keyframes parsed from {\fad(..)} when the text was rendered, so the palette-update fades the main window's export writes were dropped. Co-Authored-By: Claude Opus 5.5 --- .../Tools/BatchConvert/BatchConverter.cs | 5 +- .../BatchConverterBluRaySupFadeTests.cs | 76 +++++++++++++++++++ 2 files changed, 80 insertions(+), 1 deletion(-) create mode 100644 tests/UI/Features/Tools/BatchConvert/BatchConverterBluRaySupFadeTests.cs diff --git a/src/ui/Features/Tools/BatchConvert/BatchConverter.cs b/src/ui/Features/Tools/BatchConvert/BatchConverter.cs index 702256ff2fd..91e6b0c8d70 100644 --- a/src/ui/Features/Tools/BatchConvert/BatchConverter.cs +++ b/src/ui/Features/Tools/BatchConvert/BatchConverter.cs @@ -2022,12 +2022,15 @@ private void WriteToImageBasedFormat(BatchConvertItem item, IOcrSubtitle? imageS }; // Text rendered from the export-images profile: the colors it was drawn with, not // white/black. VobSub builds its 4-color palette from these, so with white/black a - // red or blue text was quantized to the black outline color (#15942). + // red or blue text was quantized to the black outline color (#15942). "{\fad(..)}" + // was parsed into fade keyframes when it was rendered - Blu-ray sup writes them as + // palette updates, the same as the main window's export. if (imageSubtitle is OcrSubtitleImageParameter textImages && textImages.GetImageParameter(i) is { } rendered) { param.FontColor = rendered.FontColor; param.OutlineColor = rendered.OutlineColor; + param.FadeKeyframes = rendered.FadeKeyframes; } var position = imageSubtitle.GetPosition(i); diff --git a/tests/UI/Features/Tools/BatchConvert/BatchConverterBluRaySupFadeTests.cs b/tests/UI/Features/Tools/BatchConvert/BatchConverterBluRaySupFadeTests.cs new file mode 100644 index 00000000000..a4a365acf1d --- /dev/null +++ b/tests/UI/Features/Tools/BatchConvert/BatchConverterBluRaySupFadeTests.cs @@ -0,0 +1,76 @@ +using Nikse.SubtitleEdit.Core.Common; +using Nikse.SubtitleEdit.Core.SubtitleFormats; +using Nikse.SubtitleEdit.Features.Tools.BatchConvert; +using Nikse.SubtitleEdit.UiLogic.BatchConvert; + +namespace UITests.Features.Tools.BatchConvert; + +/// +/// Batch text → Blu-ray sup rebuilt the image parameters without the fade keyframes parsed from +/// "{\fad(..)}", so the fade the main window's export writes (palette update display sets) was +/// silently dropped. +/// +public class BatchConverterBluRaySupFadeTests +{ + private const byte PresentationCompositionSegment = 0x16; + + private static async Task CountCompositionSegmentsAsync(string text) + { + var srt = "1\n00:00:01,000 --> 00:00:03,000\n" + text + "\n"; + var dir = Directory.CreateTempSubdirectory("se-batch-sup-fade-test"); + try + { + var inputFile = Path.Combine(dir.FullName, "movie.srt"); + await File.WriteAllTextAsync(inputFile, srt, TestContext.Current.CancellationToken); + + var converter = new BatchConverter(null!, null!, null!); + converter.Initialize(new BatchConvertConfig + { + TargetFormatName = BatchConverter.FormatBluRaySup, + SaveInSourceFolder = true, + Overwrite = true, + }); + + var subtitle = new Subtitle(); + new SubRip().LoadSubtitle(subtitle, srt.SplitToLines(), inputFile); + var item = new BatchConvertItem(inputFile, 1, new SubRip().Name, subtitle); + await converter.Convert(item, TestContext.Current.CancellationToken); + + var supFile = Path.Combine(dir.FullName, "movie.sup"); + Assert.True(File.Exists(supFile), $"no .sup written, status: {item.Status}"); + + // "PG" + PTS(4) + DTS(4) + type(1) + size(2) + payload + var sup = await File.ReadAllBytesAsync(supFile, TestContext.Current.CancellationToken); + var count = 0; + var position = 0; + while (position + 13 <= sup.Length) + { + Assert.Equal((byte)'P', sup[position]); + Assert.Equal((byte)'G', sup[position + 1]); + if (sup[position + 10] == PresentationCompositionSegment) + { + count++; + } + + position += 13 + (sup[position + 11] << 8 | sup[position + 12]); + } + + return count; + } + finally + { + dir.Delete(recursive: true); + } + } + + [Fact] + public async Task BluRaySup_FadeTagWritesPaletteUpdates() + { + var plain = await CountCompositionSegmentsAsync("Fade me."); + var faded = await CountCompositionSegmentsAsync("{\\fad(500,500)}Fade me."); + + // Plain: show + clear. Faded: the same plus a palette update per fade step. + Assert.Equal(2, plain); + Assert.True(faded > plain, $"expected fade palette updates, got {faded} composition segments"); + } +} From 2eb6d6e1d121b33747ddccf7c6c351cb29e8c8bb Mon Sep 17 00:00:00 2001 From: niksedk Date: Mon, 12 Oct 2026 03:16:09 +0200 Subject: [PATCH 080/306] Fix settings export/import/reset gaps (#15944) - "All settings" export/import now carries Edit (Multiple Replace rules), Assa (stored styles), Ssa, Ocr, Synchronization, BeautifyTimeCodes, Formats, Plugins, Providers and File (without recent files). A new exportIncludesAllSections marker keeps older exports, which held defaults for these sections, from wiping the user's own values; a plain Settings.json backup (no markers) is trusted. - ApplySettings re-reads grid column visibility/order from the settings, so an import or reset shows at once and the close-time write-back no longer restores the old columns. An import also applies when the Settings dialog is then cancelled. - The Columns dialog's Reset restores default visibility, not just order. - ApplySettings reloads the UI language when it changed; Se.LoadLanguage now drops a loaded translation when the setting is back to English. Co-Authored-By: Claude Opus 5.5 --- .../Main/GridColumns/GridColumnDisplay.cs | 4 + .../Main/GridColumns/GridColumnsViewModel.cs | 7 + src/ui/Features/Main/MainViewModel.cs | 82 ++++++-- .../SettingsImportExportViewModel.cs | 111 ++++++++++- .../Options/Settings/SettingsViewModel.cs | 7 + src/ui/Logic/Config/Se.cs | 25 ++- src/ui/Logic/Config/SeGeneral.cs | 4 +- .../SettingsImportExportAllSectionsTests.cs | 179 ++++++++++++++++++ 8 files changed, 397 insertions(+), 22 deletions(-) create mode 100644 tests/UI/Features/Options/Settings/SettingsImportExportAllSectionsTests.cs diff --git a/src/ui/Features/Main/GridColumns/GridColumnDisplay.cs b/src/ui/Features/Main/GridColumns/GridColumnDisplay.cs index dcf7a305cb8..f765d0fcdb8 100644 --- a/src/ui/Features/Main/GridColumns/GridColumnDisplay.cs +++ b/src/ui/Features/Main/GridColumns/GridColumnDisplay.cs @@ -22,11 +22,15 @@ public partial class GridColumnDisplay : ObservableObject /// Grid column keys (SubtitleGridColumnKeys) this entry covers, in default order. public string[] Keys { get; } + /// The factory visibility the dialog's Reset button restores. + public bool DefaultIsVisible { get; init; } + public GridColumnDisplay(string name, bool canToggle, bool isVisible, params string[] keys) { Name = name; CanToggle = canToggle; IsVisible = isVisible; + DefaultIsVisible = isVisible; Keys = keys; } diff --git a/src/ui/Features/Main/GridColumns/GridColumnsViewModel.cs b/src/ui/Features/Main/GridColumns/GridColumnsViewModel.cs index 55f5db47db8..f4b2d26cd58 100644 --- a/src/ui/Features/Main/GridColumns/GridColumnsViewModel.cs +++ b/src/ui/Features/Main/GridColumns/GridColumnsViewModel.cs @@ -127,6 +127,13 @@ private void Reset() Columns.Clear(); foreach (var entry in _defaultOrder) { + // Reset used to restore only the order, so after hiding or showing columns the + // button seemed to do nothing (#15944). + if (entry.CanToggle) + { + entry.IsVisible = entry.DefaultIsVisible; + } + Columns.Add(entry); } diff --git a/src/ui/Features/Main/MainViewModel.cs b/src/ui/Features/Main/MainViewModel.cs index a91557e58c1..2b7ba0f6896 100644 --- a/src/ui/Features/Main/MainViewModel.cs +++ b/src/ui/Features/Main/MainViewModel.cs @@ -14627,7 +14627,7 @@ private async Task CommandShowSettings() var viewModel = await ShowDialogAsync( vm => { vm.Initialize(this); }); - if (!viewModel.OkPressed) + if (!viewModel.OkPressed && !viewModel.SettingsImported) { return; } @@ -14771,6 +14771,24 @@ public void ApplySettings() // editing was lost on Apply and on OK after Apply (issue #14421). var selectedIndex = SubtitleGrid.SelectedIndex; + // An import or a reset can change the interface language, which Options > Language + // applies at once but this path never did - the old language stayed until a restart + // (#15944). The layout and toolbar rebuilds below pick up the new strings. + var wasRightToLeft = SeGeneral.IsLanguageRightToLeft(Se.LoadedLanguageName); + var videoPosition = GetVideoPlayerControl()?.VideoPlayer?.Position ?? 0; + var languageChanged = Se.LoadLanguage(); + if (languageChanged) + { + SettingsViewModel.ReloadLanguageMaps(); + InitMenu.Make(this); + if (OperatingSystem.IsMacOS()) + { + Layout.InitNativeMacMenu.Rebuild(this); + } + + ReloadShortcuts(); + } + UiUtil.SetFontName(Se.Settings.Appearance.FontName); UiTheme.SetCurrentTheme(); InitMenu.ApplyFontSize(Menu); @@ -14889,6 +14907,7 @@ public void ApplySettings() _errorColor = Se.Settings.General.ErrorColor.FromHexToColor(); _errorBrush = new ImmutableSolidColorBrush(_errorColor); SubtitleTextInfoHelper.RefreshErrorBrush(); + RefreshGridColumnsFromSettings(); if (AreVideoControlsUndocked) { VideoUndockControls(); @@ -14969,6 +14988,38 @@ public void ApplySettings() RefreshSubtitlePreview(); SelectAndScrollToRow(selectedIndex); + + if (languageChanged && Se.Settings.General.IsLanguageRightToLeft() != wasRightToLeft) + { + ApplyLayoutDirectionForCurrentLanguage(videoPosition); + } + } + + /// + /// Re-reads the grid column visibility and order from the settings. An import or reset + /// replaces them in , but the grid binds to the view model's own + /// copies - so nothing changed until a restart, and closing the window even wrote the old + /// copies back over the imported values (#15944). + /// + private void RefreshGridColumnsFromSettings() + { + var general = Se.Settings.General; + ShowColumnStartTime = general.ShowColumnStartTime; + ShowColumnEndTime = general.ShowColumnEndTime; + ShowColumnDuration = general.ShowColumnDuration; + ShowColumnTeletext = general.ShowColumnTeletext; + TeletextAlignmentPreview = general.TeletextAlignmentPreview; + ShowColumnGap = general.ShowColumnGap; + ShowColumnShotIn = general.ShowColumnShotIn; + ShowColumnShotOut = general.ShowColumnShotOut; + ShowColumnActor = general.ShowColumnActor; + ShowColumnStyle = general.ShowColumnStyle; + ShowColumnCps = general.ShowColumnCps; + ShowColumnWpm = general.ShowColumnWpm; + ShowColumnPixelWidth = general.ShowColumnPixelWidth; + ShowColumnLayer = general.ShowColumnLayer && IsFormatAssa; // the layer column is ASSA only + ShowColumnForced = general.ShowColumnForced; + SubtitleGridColumnManager?.ApplyOrder(general.SubtitleGridColumnOrder); } public VideoPlayerControl? GetVideoPlayerControl() @@ -15774,25 +15825,26 @@ private async Task ShowGridColumnsDialog() return; } + var defaults = new SeGeneral(); var entries = new List { new(Se.Language.General.NumberSymbol, false, true, InitListViewAndEditBox.SubtitleGridColumnKeys.Number), - new(Se.Language.General.Show, true, ShowColumnStartTime, InitListViewAndEditBox.SubtitleGridColumnKeys.Start), - new(Se.Language.General.Hide, true, ShowColumnEndTime, InitListViewAndEditBox.SubtitleGridColumnKeys.End), - new(Se.Language.General.Duration, true, ShowColumnDuration, InitListViewAndEditBox.SubtitleGridColumnKeys.Duration), - new(Se.Language.File.EbuSaveOptions.Teletext, true, ShowColumnTeletext, InitListViewAndEditBox.SubtitleGridColumnKeys.Teletext), - new(Se.Language.General.Forced, true, ShowColumnForced, InitListViewAndEditBox.SubtitleGridColumnKeys.Forced), + new(Se.Language.General.Show, true, ShowColumnStartTime, InitListViewAndEditBox.SubtitleGridColumnKeys.Start) { DefaultIsVisible = defaults.ShowColumnStartTime }, + new(Se.Language.General.Hide, true, ShowColumnEndTime, InitListViewAndEditBox.SubtitleGridColumnKeys.End) { DefaultIsVisible = defaults.ShowColumnEndTime }, + new(Se.Language.General.Duration, true, ShowColumnDuration, InitListViewAndEditBox.SubtitleGridColumnKeys.Duration) { DefaultIsVisible = defaults.ShowColumnDuration }, + new(Se.Language.File.EbuSaveOptions.Teletext, true, ShowColumnTeletext, InitListViewAndEditBox.SubtitleGridColumnKeys.Teletext) { DefaultIsVisible = defaults.ShowColumnTeletext }, + new(Se.Language.General.Forced, true, ShowColumnForced, InitListViewAndEditBox.SubtitleGridColumnKeys.Forced) { DefaultIsVisible = defaults.ShowColumnForced }, new(Se.Language.General.Text, false, true, InitListViewAndEditBox.SubtitleGridColumnKeys.Text), new(Se.Language.General.OriginalText, false, ShowColumnOriginalText, InitListViewAndEditBox.SubtitleGridColumnKeys.OriginalText), - new(Se.Language.General.Style, true, ShowColumnStyle, InitListViewAndEditBox.SubtitleGridColumnKeys.Style, InitListViewAndEditBox.SubtitleGridColumnKeys.WebVttStyle), - new(Se.Language.General.Gap, true, ShowColumnGap, InitListViewAndEditBox.SubtitleGridColumnKeys.Gap), - new(Se.Language.General.ShotIn, true, ShowColumnShotIn, InitListViewAndEditBox.SubtitleGridColumnKeys.ShotIn), - new(Se.Language.General.ShotOut, true, ShowColumnShotOut, InitListViewAndEditBox.SubtitleGridColumnKeys.ShotOut), - new(Se.Language.General.Actor, true, ShowColumnActor, InitListViewAndEditBox.SubtitleGridColumnKeys.Actor, InitListViewAndEditBox.SubtitleGridColumnKeys.WebVttVoice), - new(Se.Language.General.Cps, true, ShowColumnCps, InitListViewAndEditBox.SubtitleGridColumnKeys.Cps), - new(Se.Language.General.Wpm, true, ShowColumnWpm, InitListViewAndEditBox.SubtitleGridColumnKeys.Wpm), - new(Se.Language.General.PixelWidth, true, ShowColumnPixelWidth, InitListViewAndEditBox.SubtitleGridColumnKeys.PixelWidth), - new(Se.Language.General.Layer, true, ShowColumnLayer, InitListViewAndEditBox.SubtitleGridColumnKeys.Layer), + new(Se.Language.General.Style, true, ShowColumnStyle, InitListViewAndEditBox.SubtitleGridColumnKeys.Style, InitListViewAndEditBox.SubtitleGridColumnKeys.WebVttStyle) { DefaultIsVisible = defaults.ShowColumnStyle }, + new(Se.Language.General.Gap, true, ShowColumnGap, InitListViewAndEditBox.SubtitleGridColumnKeys.Gap) { DefaultIsVisible = defaults.ShowColumnGap }, + new(Se.Language.General.ShotIn, true, ShowColumnShotIn, InitListViewAndEditBox.SubtitleGridColumnKeys.ShotIn) { DefaultIsVisible = defaults.ShowColumnShotIn }, + new(Se.Language.General.ShotOut, true, ShowColumnShotOut, InitListViewAndEditBox.SubtitleGridColumnKeys.ShotOut) { DefaultIsVisible = defaults.ShowColumnShotOut }, + new(Se.Language.General.Actor, true, ShowColumnActor, InitListViewAndEditBox.SubtitleGridColumnKeys.Actor, InitListViewAndEditBox.SubtitleGridColumnKeys.WebVttVoice) { DefaultIsVisible = defaults.ShowColumnActor }, + new(Se.Language.General.Cps, true, ShowColumnCps, InitListViewAndEditBox.SubtitleGridColumnKeys.Cps) { DefaultIsVisible = defaults.ShowColumnCps }, + new(Se.Language.General.Wpm, true, ShowColumnWpm, InitListViewAndEditBox.SubtitleGridColumnKeys.Wpm) { DefaultIsVisible = defaults.ShowColumnWpm }, + new(Se.Language.General.PixelWidth, true, ShowColumnPixelWidth, InitListViewAndEditBox.SubtitleGridColumnKeys.PixelWidth) { DefaultIsVisible = defaults.ShowColumnPixelWidth }, + new(Se.Language.General.Layer, true, ShowColumnLayer, InitListViewAndEditBox.SubtitleGridColumnKeys.Layer) { DefaultIsVisible = defaults.ShowColumnLayer }, }; var result = await _windowService.ShowDialogAsync(Window, vm => diff --git a/src/ui/Features/Options/Settings/SettingsImportExport/SettingsImportExportViewModel.cs b/src/ui/Features/Options/Settings/SettingsImportExport/SettingsImportExportViewModel.cs index 6cf8b9b80ad..84af8012b3d 100644 --- a/src/ui/Features/Options/Settings/SettingsImportExport/SettingsImportExportViewModel.cs +++ b/src/ui/Features/Options/Settings/SettingsImportExport/SettingsImportExportViewModel.cs @@ -107,6 +107,7 @@ public bool IsShortcutsEnabled private bool _importHasCustomSearchSlots; private bool _importHasSurroundOptions; private bool _importHasCustomShortcuts; + private bool _importHasAllSections; // Set instead of _importData when the picked file is an SE 4 Settings.xml (#14309): SE 4 has // no Settings.json, so the only file a user migrating from 4.x can point at is the classic @@ -123,6 +124,13 @@ public bool IsShortcutsEnabled // defaults for those, so without the marker the importer must leave them alone // rather than reset the user's own to factory values. private const string ExportShortcutSlotsProperty = "exportIncludesShortcutSlots"; + + // Third marker: set when an "all settings" file carries the sections that were left out + // before #15944 (Edit with the Multiple Replace rules, Assa with the stored styles, Ocr, ...). + // Older exports serialized those as blocks of *defaults*, so importing them would wipe the + // user's own rules and styles - only a file with this marker, or a plain Settings.json + // backup (no export markers at all), is trusted for them. + private const string ExportAllSectionsProperty = "exportIncludesAllSections"; public bool OkPressed { get; set; } public Window? Window { get; set; } private readonly IFileHelper _fileHelper; @@ -198,6 +206,8 @@ public async Task PromptAndLoadImportFile() _importHasCustomSearchSlots = TryReadHasTopLevelProperty(json, nameof(Se.CustomSearch1Name)); _importHasSurroundOptions = TryReadHasTopLevelProperty(json, nameof(Se.Surround1Behavior)); _importHasCustomShortcuts = TryReadHasTopLevelProperty(json, nameof(Se.CustomShortcuts)); + _importHasAllSections = TryReadHasTopLevelProperty(json, ExportAllSectionsProperty) || + !TryReadHasTopLevelProperty(json, ExportSourceOsProperty); IsRulesEnabled = _importData.General != null; IsAppearanceEnabled = _importData.Appearance != null; @@ -257,6 +267,7 @@ private bool LoadSe4ImportFile(string xml) _importHasCustomSearchSlots = false; _importHasSurroundOptions = false; _importHasCustomShortcuts = false; + _importHasAllSections = false; IsRulesEnabled = se4.HasRules; IsAppearanceEnabled = se4.HasAppearance; @@ -381,6 +392,32 @@ private async Task ExportSettings() exportData.AutoTranslate = ExportImportAll || ExportImportAutoTranslate ? currentSettings.AutoTranslate : null!; exportData.SpellCheck = ExportImportAll ? currentSettings.SpellCheck : null!; + // These sections were never exported, so the Multiple Replace rules (Edit), the stored + // ASSA styles (Assa), the OCR options and the rest could not be backed up (#15944). + exportData.Edit = ExportImportAll ? currentSettings.Edit : null!; + exportData.Synchronization = ExportImportAll ? currentSettings.Synchronization : null!; + exportData.Assa = ExportImportAll ? currentSettings.Assa : null!; + exportData.Ssa = ExportImportAll ? currentSettings.Ssa : null!; + exportData.BeautifyTimeCodes = ExportImportAll ? currentSettings.BeautifyTimeCodes : null!; + exportData.Formats = ExportImportAll ? currentSettings.Formats : null!; + exportData.Ocr = ExportImportAll ? currentSettings.Ocr : null!; + exportData.Plugins = ExportImportAll ? currentSettings.Plugins : null!; + exportData.Providers = ExportImportAll ? currentSettings.Providers : null!; + + // File holds the export options and custom text formats, but also the recent files - + // local paths that should not travel, same as the recent videos below. + exportData.File = null!; + if (ExportImportAll && currentSettings.File != null) + { + var fileJson = JsonSerializer.Serialize(currentSettings.File); + var fileExport = JsonSerializer.Deserialize(fileJson); + if (fileExport != null) + { + fileExport.RecentFiles.Clear(); + exportData.File = fileExport; + } + } + // Video was never assigned, so an "all settings" file carried a default Video block // that the importer applied - silently resetting the player choice, the mpv preview // style and the custom seek amounts. Exclude RecentFiles so local recent video paths are not exported. @@ -400,7 +437,7 @@ private async Task ExportSettings() } var json = JsonSerializer.Serialize(exportData, new JsonSerializerOptions { WriteIndented = true }); - var jsonWithSource = InjectExportMarkers(json, GetCurrentOsName(), exportShortcuts); + var jsonWithSource = InjectExportMarkers(json, GetCurrentOsName(), exportShortcuts, ExportImportAll); await File.WriteAllTextAsync(fileName, jsonWithSource); } @@ -479,6 +516,11 @@ private void ImportSettings() { Se.Settings.SpellCheck = importData.SpellCheck; } + + if (_importHasAllSections) + { + ImportAllSections(importData, Se.Settings); + } } if (ExportImportAll || ExportImportAppearance) @@ -514,6 +556,66 @@ private void ImportSettings() Se.SaveSettings(); } + /// + /// The sections only an "all settings" import brings: the Multiple Replace rules (Edit), the + /// stored ASSA styles (Assa), the OCR options and the rest that never travelled before #15944. + /// The local recent files stay as they are. + /// + internal static void ImportAllSections(Se from, Se to) + { + if (from.Edit != null) + { + to.Edit = from.Edit; + } + + if (from.Synchronization != null) + { + to.Synchronization = from.Synchronization; + } + + if (from.Assa != null) + { + to.Assa = from.Assa; + } + + if (from.Ssa != null) + { + to.Ssa = from.Ssa; + } + + if (from.BeautifyTimeCodes != null) + { + to.BeautifyTimeCodes = from.BeautifyTimeCodes; + } + + if (from.Formats != null) + { + to.Formats = from.Formats; + } + + if (from.Ocr != null) + { + to.Ocr = from.Ocr; + } + + if (from.Plugins != null) + { + to.Plugins = from.Plugins; + } + + if (from.Providers != null) + { + to.Providers = from.Providers; + } + + if (from.File != null) + { + var existingRecentFiles = to.File.RecentFiles; + to.File = from.File; + to.File.RecentFiles = existingRecentFiles; + } + } + // The SE 4 side of the import. Same checkboxes, but every category is copied field by field // into the current settings instead of replacing a whole section: SE 4 has no counterpart for // most of what an SE 5 section holds, and a section-level assignment would reset all of it. @@ -704,7 +806,7 @@ private static void CopyShortcutSlots(Se? from, Se to, bool includeCustomSearch // Adds the top-level marker properties to the serialized JSON without touching the Se // type. Se has no such properties, so System.Text.Json silently ignores them on import. - private static string InjectExportMarkers(string json, string osName, bool includesShortcutSlots) + private static string InjectExportMarkers(string json, string osName, bool includesShortcutSlots, bool includesAllSections) { try { @@ -724,6 +826,11 @@ private static string InjectExportMarkers(string json, string osName, bool inclu writer.WriteBoolean(ExportShortcutSlotsProperty, true); } + if (includesAllSections) + { + writer.WriteBoolean(ExportAllSectionsProperty, true); + } + foreach (var prop in doc.RootElement.EnumerateObject()) { prop.WriteTo(writer); diff --git a/src/ui/Features/Options/Settings/SettingsViewModel.cs b/src/ui/Features/Options/Settings/SettingsViewModel.cs index f79c1c6cb76..208751dd0f0 100644 --- a/src/ui/Features/Options/Settings/SettingsViewModel.cs +++ b/src/ui/Features/Options/Settings/SettingsViewModel.cs @@ -468,6 +468,12 @@ private static int ParseMouseWheelStep(string display) ///
public string? AppliedSettingsSnapshot { get; private set; } + /// + /// True once an import has replaced (and saved) the settings. They are live from then on even + /// if the dialog is cancelled, so the main window still has to apply them (#15944). + /// + public bool SettingsImported { get; private set; } + public Window? Window { get; internal set; } public ScrollViewer ScrollView { get; internal set; } public List Sections { get; internal set; } @@ -3267,6 +3273,7 @@ private async Task ImportSettings() { // Reload the settings from disk since they were updated LoadSettings(); + SettingsImported = true; } } diff --git a/src/ui/Logic/Config/Se.cs b/src/ui/Logic/Config/Se.cs index 3674f663fad..e5aab3691c2 100644 --- a/src/ui/Logic/Config/Se.cs +++ b/src/ui/Logic/Config/Se.cs @@ -1081,13 +1081,24 @@ internal static void MigrateMpvAudioBuffer(SeVideo video) /// bar is constructed at startup and reads directly, so the translation /// has to be in place by then or the menu bar renders in English (issue #11505). English — or a /// missing/unreadable translation file — leaves the built-in English defaults untouched. + /// Returns true when changed. ///
- public static void LoadLanguage() + public static bool LoadLanguage() { Settings.General.Language ??= "English"; if (Settings.General.Language == "English") { - return; + // The built-in strings are English, but a translation loaded earlier in the session + // stays in place until dropped - a reset or an import back to English otherwise only + // took effect after a restart (#15944). + if (_loadedLanguage == null || _loadedLanguage == "English") + { + return false; + } + + Language = new SeLanguage(); + _loadedLanguage = null; + return true; } // MainView.Build() calls this again as a safety net for windows created via other entry @@ -1096,7 +1107,7 @@ public static void LoadLanguage() // translation isn't already the requested one. if (_loadedLanguage == Settings.General.Language) { - return; + return false; } try @@ -1104,7 +1115,7 @@ public static void LoadLanguage() var jsonFileName = Path.Combine(TranslationFolder, Settings.General.Language + ".json"); if (!System.IO.File.Exists(jsonFileName)) { - return; + return false; } using var stream = System.IO.File.OpenRead(jsonFileName); @@ -1113,14 +1124,20 @@ public static void LoadLanguage() { Language = language; _loadedLanguage = Settings.General.Language; + return true; } } catch (Exception exception) { Se.LogError(exception, "Failed to load UI language"); } + + return false; } + /// The translation currently in ; null means the built-in English. + public static string? LoadedLanguageName => _loadedLanguage; + /// /// Loads a translation file chosen at runtime (Options > Language) into . /// Goes through here rather than assigning directly so the diff --git a/src/ui/Logic/Config/SeGeneral.cs b/src/ui/Logic/Config/SeGeneral.cs index c84ac2eda6e..73b2aceff9a 100644 --- a/src/ui/Logic/Config/SeGeneral.cs +++ b/src/ui/Logic/Config/SeGeneral.cs @@ -751,7 +751,9 @@ public static void AddExtraProfiles(List profiles) }); } - internal bool IsLanguageRightToLeft() => Language is + internal bool IsLanguageRightToLeft() => IsLanguageRightToLeft(Language); + + internal static bool IsLanguageRightToLeft(string? language) => language is "Arabic" or "Dari" or "Hebrew" or diff --git a/tests/UI/Features/Options/Settings/SettingsImportExportAllSectionsTests.cs b/tests/UI/Features/Options/Settings/SettingsImportExportAllSectionsTests.cs new file mode 100644 index 00000000000..0877213e467 --- /dev/null +++ b/tests/UI/Features/Options/Settings/SettingsImportExportAllSectionsTests.cs @@ -0,0 +1,179 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Headless.XUnit; +using Nikse.SubtitleEdit.Features.Main.GridColumns; +using Nikse.SubtitleEdit.Features.Options.Settings.SettingsImportExport; +using Nikse.SubtitleEdit.Logic.Config; +using Nikse.SubtitleEdit.Logic.Media; +using System.Text.Json; + +namespace UITests.Features.Options.Settings; + +/// +/// "All settings" export/import skipped whole sections - the Multiple Replace rules (Edit), the +/// stored ASSA styles (Assa), the OCR options and more - and the Columns dialog's Reset only +/// restored the order, not which columns are shown (#15944). +/// +public class SettingsImportExportAllSectionsTests +{ + private sealed class FakeFileHelper : IFileHelper + { + private readonly string _path; + + public FakeFileHelper(string path) => _path = path; + + public Task PickOpenFile(Visual sender, string title, string extensionTitle, string extension, string extensionTitle2 = "", string extension2 = "", string? suggestedStartFolder = null) => Task.FromResult(_path); + public Task PickSaveFile(Visual sender, string extension, string suggestedFileName, string title) => Task.FromResult(_path); + + public Task PickOpenFiles(Visual sender, string title, string extensionTitle, List extensions, string extensionTitle2, List extensions2) => throw new NotSupportedException(); + public Task PickOpenSubtitleFile(Visual sender, string title, bool includeVideoFiles = true, string? lastOpenedFilePath = null, bool includeSpreadsheets = false) => throw new NotSupportedException(); + public Task PickOpenSubtitleFiles(Visual sender, string title, bool includeVideoFiles = true, string? lastOpenedFilePath = null) => throw new NotSupportedException(); + public Task PickSaveSubtitleFile(Visual sender, Nikse.SubtitleEdit.Core.SubtitleFormats.SubtitleFormat currentFormat, string suggestedFileName, string title) => throw new NotSupportedException(); + public Task PickSaveSubtitleFileAs(Visual sender, Nikse.SubtitleEdit.Core.SubtitleFormats.SubtitleFormat currentFormat, string suggestedFileName, string title) => throw new NotSupportedException(); + public Task PickSaveSubtitleFile(Visual sender, string extension, string suggestedFileName, string title) => throw new NotSupportedException(); + public Task PickSaveFile(Visual sender, string extension, string extensionTitle, string suggestedFileName, string title) => throw new NotSupportedException(); + public Task PickSaveFile(Visual sender, IReadOnlyList<(string Name, string Extension)> fileTypes, string suggestedFileName, string title) => throw new NotSupportedException(); + public Task PickOpenVideoFile(Visual sender, string title, string? lastOpenedFilePath = null) => throw new NotSupportedException(); + public Task PickOpenVideoFiles(Visual sender, string title) => throw new NotSupportedException(); + public Task PickOpenImageFile(Visual sender, string title) => throw new NotSupportedException(); + } + + private static async Task ExportAll(string path, Se settings) + { + var previous = Se.Settings; + Se.Settings = settings; + try + { + var vm = new SettingsImportExportViewModel(new FakeFileHelper(path)) { Window = new Window() }; + vm.SetIsExport(true); + vm.ExportImportAll = true; + await vm.OkCommand.ExecuteAsync(null); + } + finally + { + Se.Settings = previous; + } + + return await File.ReadAllTextAsync(path); + } + + private static async Task ImportAllInto(string path, Se settings) + { + var previous = Se.Settings; + Se.Settings = settings; + try + { + var vm = new SettingsImportExportViewModel(new FakeFileHelper(path)) { Window = new Window() }; + vm.SetIsExport(false); + Assert.True(await vm.PromptAndLoadImportFile()); + vm.ExportImportAll = true; + await vm.OkCommand.ExecuteAsync(null); + } + finally + { + Se.Settings = previous; + } + } + + private static string TempFile() => + Path.Combine(SettingsIsolationFixture.SettingsDirectory, Guid.NewGuid().ToString("N") + ".json"); + + private static Se MakeCustomizedSettings() + { + var settings = new Se(); + settings.Edit.MultipleReplace.Categories.Add(new SeEditMultipleReplace.MultipleReplaceCategory { Name = "My rules", IsActive = true }); + var style = SeAssaStyle.MakeStorageDefault(); + style.Name = "My style"; + settings.Assa.StoredStyles.Add(style); + settings.Ocr.NOcrPixelsAreSpace = 7; + settings.Ocr.DoTryToGuessUnknownWords = false; + settings.File.ExportDvbTeletextPageNumber = 777; + settings.File.RecentFiles.Add(new RecentFile { SubtitleFileName = "source-recent.srt" }); + return settings; + } + + private static Se MakeTargetSettings() + { + var settings = new Se(); + settings.File.RecentFiles.Add(new RecentFile { SubtitleFileName = "target-recent.srt" }); + return settings; + } + + [AvaloniaFact] + public async Task ExportImportAllCarriesMultipleReplaceAssaStylesAndOcr() + { + Directory.CreateDirectory(SettingsIsolationFixture.SettingsDirectory); + var path = TempFile(); + + var json = await ExportAll(path, MakeCustomizedSettings()); + Assert.DoesNotContain("source-recent.srt", json); + + var target = MakeTargetSettings(); + await ImportAllInto(path, target); + + Assert.Contains(target.Edit.MultipleReplace.Categories, c => c.Name == "My rules"); + Assert.Contains(target.Assa.StoredStyles, s => s.Name == "My style"); + Assert.Equal(7, target.Ocr.NOcrPixelsAreSpace); + Assert.False(target.Ocr.DoTryToGuessUnknownWords); + Assert.Equal(777, target.File.ExportDvbTeletextPageNumber); + Assert.Equal("target-recent.srt", Assert.Single(target.File.RecentFiles).SubtitleFileName); + } + + [AvaloniaFact] + public async Task ImportOfPlainSettingsBackupCarriesAllSections() + { + Directory.CreateDirectory(SettingsIsolationFixture.SettingsDirectory); + var path = TempFile(); + await File.WriteAllTextAsync(path, JsonSerializer.Serialize(MakeCustomizedSettings())); + + var target = MakeTargetSettings(); + await ImportAllInto(path, target); + + Assert.Contains(target.Edit.MultipleReplace.Categories, c => c.Name == "My rules"); + Assert.Contains(target.Assa.StoredStyles, s => s.Name == "My style"); + Assert.Equal(7, target.Ocr.NOcrPixelsAreSpace); + Assert.Equal("target-recent.srt", Assert.Single(target.File.RecentFiles).SubtitleFileName); + } + + [AvaloniaFact] + public async Task ImportOfOlderExportLeavesTheNewSectionsAlone() + { + // Exports from before #15944 carry the source-OS marker but serialized these sections as + // defaults - importing them must not wipe the user's own rules and styles. + Directory.CreateDirectory(SettingsIsolationFixture.SettingsDirectory); + var path = TempFile(); + await File.WriteAllTextAsync(path, """ + { + "exportSourceOs": "Windows", + "Edit": { "MultipleReplace": { "Categories": [] } }, + "Ocr": { "NOcrPixelsAreSpace": 0 } + } + """); + + var target = MakeCustomizedSettings(); + await ImportAllInto(path, target); + + Assert.Contains(target.Edit.MultipleReplace.Categories, c => c.Name == "My rules"); + Assert.Equal(7, target.Ocr.NOcrPixelsAreSpace); + } + + [Fact] + public void GridColumnsResetRestoresDefaultVisibility() + { + var entries = new List + { + new("Start", true, false, "start") { DefaultIsVisible = true }, + new("Text", false, true, "text"), + new("Cps", true, true, "cps") { DefaultIsVisible = false }, + }; + + var vm = new GridColumnsViewModel(); + vm.Initialize(entries, new List { "cps", "text", "start" }); + vm.ResetCommand.Execute(null); + + Assert.Equal(new[] { "start", "text", "cps" }, vm.Columns.SelectMany(c => c.Keys)); + Assert.True(vm.Columns[0].IsVisible); + Assert.True(vm.Columns[1].IsVisible); + Assert.False(vm.Columns[2].IsVisible); + } +} From 2d7fa81ef77c55633ff5254a0059f3640cfe4d0a Mon Sep 17 00:00:00 2001 From: niksedk Date: Mon, 12 Oct 2026 03:14:06 +0200 Subject: [PATCH 081/306] Batch convert: keep {\an} alignment in D-Cinema PNG Text to D-Cinema Interop/SMPTE rebuilt the image parameters with a bottom-center alignment. The D-Cinema handlers place the image only by its alignment (Valign/Halign), so {\an8} and other non-bottom lines came out at the bottom, unlike the main window's export. Co-Authored-By: Claude Opus 5.5 --- .../Tools/BatchConvert/BatchConverter.cs | 5 +- .../BatchConverterDCinemaAlignmentTests.cs | 61 +++++++++++++++++++ 2 files changed, 65 insertions(+), 1 deletion(-) create mode 100644 tests/UI/Features/Tools/BatchConvert/BatchConverterDCinemaAlignmentTests.cs diff --git a/src/ui/Features/Tools/BatchConvert/BatchConverter.cs b/src/ui/Features/Tools/BatchConvert/BatchConverter.cs index 91e6b0c8d70..782ab4ac138 100644 --- a/src/ui/Features/Tools/BatchConvert/BatchConverter.cs +++ b/src/ui/Features/Tools/BatchConvert/BatchConverter.cs @@ -2024,13 +2024,16 @@ private void WriteToImageBasedFormat(BatchConvertItem item, IOcrSubtitle? imageS // white/black. VobSub builds its 4-color palette from these, so with white/black a // red or blue text was quantized to the black outline color (#15942). "{\fad(..)}" // was parsed into fade keyframes when it was rendered - Blu-ray sup writes them as - // palette updates, the same as the main window's export. + // palette updates, the same as the main window's export. Also the alignment from + // "{\an8}" & co.: D-Cinema writes it as Valign/Halign and has no use for the override + // position the other handlers place the image with, so top lines ended up at the bottom. if (imageSubtitle is OcrSubtitleImageParameter textImages && textImages.GetImageParameter(i) is { } rendered) { param.FontColor = rendered.FontColor; param.OutlineColor = rendered.OutlineColor; param.FadeKeyframes = rendered.FadeKeyframes; + param.Alignment = rendered.Alignment; } var position = imageSubtitle.GetPosition(i); diff --git a/tests/UI/Features/Tools/BatchConvert/BatchConverterDCinemaAlignmentTests.cs b/tests/UI/Features/Tools/BatchConvert/BatchConverterDCinemaAlignmentTests.cs new file mode 100644 index 00000000000..4be29c86ea3 --- /dev/null +++ b/tests/UI/Features/Tools/BatchConvert/BatchConverterDCinemaAlignmentTests.cs @@ -0,0 +1,61 @@ +using Nikse.SubtitleEdit.Core.Common; +using Nikse.SubtitleEdit.Core.SubtitleFormats; +using Nikse.SubtitleEdit.Features.Tools.BatchConvert; +using Nikse.SubtitleEdit.UiLogic.BatchConvert; + +namespace UITests.Features.Tools.BatchConvert; + +/// +/// Batch text → D-Cinema PNG rebuilt the image parameters with a bottom-center alignment. The +/// D-Cinema handlers place the image only by its alignment (Valign/Halign), so "{\an8}" lines +/// that the main window's export puts at the top came out at the bottom. +/// +public class BatchConverterDCinemaAlignmentTests +{ + private const string InputSrt = + "1\n00:00:01,000 --> 00:00:03,000\n{\\an8}Top line.\n\n" + + "2\n00:00:04,000 --> 00:00:06,000\n{\\an4}Middle left.\n\n" + + "3\n00:00:07,000 --> 00:00:09,000\nBottom line.\n"; + + [Theory] + [InlineData(BatchConverter.FormatDCinemaInterop)] + [InlineData(BatchConverter.FormatDCinemaSmpte2014)] + public async Task DCinema_KeepsAlignmentTags(string targetFormat) + { + var dir = Directory.CreateTempSubdirectory("se-batch-dcinema-align-test"); + try + { + var inputFile = Path.Combine(dir.FullName, "movie.srt"); + await File.WriteAllTextAsync(inputFile, InputSrt, TestContext.Current.CancellationToken); + + var converter = new BatchConverter(null!, null!, null!); + converter.Initialize(new BatchConvertConfig + { + TargetFormatName = targetFormat, + SaveInSourceFolder = true, + Overwrite = true, + }); + + var subtitle = new Subtitle(); + new SubRip().LoadSubtitle(subtitle, InputSrt.SplitToLines(), inputFile); + var item = new BatchConvertItem(inputFile, 1, new SubRip().Name, subtitle); + await converter.Convert(item, TestContext.Current.CancellationToken); + + var xmlFile = Directory.GetFiles(dir.FullName, "*.xml", SearchOption.AllDirectories).SingleOrDefault(); + Assert.True(xmlFile != null, $"no .xml written, status: {item.Status}"); + var xml = (await File.ReadAllTextAsync(xmlFile, TestContext.Current.CancellationToken)).ToLowerInvariant(); + + // Interop writes VAlign/HAlign, SMPTE Valign/Halign - compare case-insensitively. + var images = xml.Split(" Date: Mon, 12 Oct 2026 03:53:43 +0200 Subject: [PATCH 082/306] Settings search: also match English names with a translated UI (#15945) With a non-English UI the Settings search only matched translated labels, so settings named in English docs/tutorials could not be found. Build a translated -> English lookup by walking the loaded translation and the built-in English strings (source-generated JSON, built once per loaded language) and let SettingsItem.Filter match either. The composed min-gap label gets an explicit EnglishLabel. Co-Authored-By: Claude Opus 5.5 --- .../Options/Settings/EnglishLabelLookup.cs | 96 +++++++++++++++++++ .../Features/Options/Settings/SettingsItem.cs | 23 ++++- .../Features/Options/Settings/SettingsPage.cs | 7 +- .../Settings/SettingsSearchEnglishTests.cs | 55 +++++++++++ 4 files changed, 177 insertions(+), 4 deletions(-) create mode 100644 src/ui/Features/Options/Settings/EnglishLabelLookup.cs create mode 100644 tests/UI/Features/Options/Settings/SettingsSearchEnglishTests.cs diff --git a/src/ui/Features/Options/Settings/EnglishLabelLookup.cs b/src/ui/Features/Options/Settings/EnglishLabelLookup.cs new file mode 100644 index 00000000000..7413d21edc3 --- /dev/null +++ b/src/ui/Features/Options/Settings/EnglishLabelLookup.cs @@ -0,0 +1,96 @@ +using System; +using System.Collections.Generic; +using System.Text.Json; +using Nikse.SubtitleEdit.Logic.Config; +using Nikse.SubtitleEdit.Logic.Config.Language; + +namespace Nikse.SubtitleEdit.Features.Options.Settings; + +/// +/// Maps translated UI strings back to the built-in English ones, so the Settings search also +/// finds a setting by the English name used in docs and tutorials (#15945). +/// +public static class EnglishLabelLookup +{ + private static Dictionary? _map; + private static SeLanguage? _mapLanguage; + + /// The built-in English strings. + public static SeLanguage English { get; } = new(); + + /// + /// The English text of , or null when the UI is English, the label + /// is not a translated UI string, or the translation equals the English text. Several English + /// strings with the same translation are joined by a newline. + /// + public static string? GetEnglish(string? label) + { + if (string.IsNullOrEmpty(label) || Se.LoadedLanguageName == null) + { + return null; + } + + if (_map == null || !ReferenceEquals(_mapLanguage, Se.Language)) + { + _map = Build(Se.Language, English); + _mapLanguage = Se.Language; + } + + return _map.TryGetValue(label, out var english) ? english : null; + } + + internal static Dictionary Build(SeLanguage translated, SeLanguage english) + { + var map = new Dictionary(StringComparer.Ordinal); + try + { + var translatedRoot = JsonSerializer.SerializeToElement(translated, SeLanguageJsonContext.Default.SeLanguage); + var englishRoot = JsonSerializer.SerializeToElement(english, SeLanguageJsonContext.Default.SeLanguage); + Walk(translatedRoot, englishRoot, map); + } + catch (Exception exception) + { + Se.LogError(exception, "Failed to build English label lookup"); + } + + return map; + } + + private static void Walk(JsonElement translated, JsonElement english, Dictionary map) + { + if (translated.ValueKind == JsonValueKind.Object && english.ValueKind == JsonValueKind.Object) + { + foreach (var property in translated.EnumerateObject()) + { + if (english.TryGetProperty(property.Name, out var englishValue)) + { + Walk(property.Value, englishValue, map); + } + } + + return; + } + + if (translated.ValueKind != JsonValueKind.String || english.ValueKind != JsonValueKind.String) + { + return; + } + + var translatedText = translated.GetString(); + var englishText = english.GetString(); + if (string.IsNullOrEmpty(translatedText) || string.IsNullOrEmpty(englishText) || + translatedText == englishText) + { + return; + } + + if (!map.TryGetValue(translatedText, out var existing)) + { + map[translatedText] = englishText; + } + else if (Array.IndexOf(existing.Split('\n'), englishText) < 0) + { + map[translatedText] = existing + "\n" + englishText; + } + } +} diff --git a/src/ui/Features/Options/Settings/SettingsItem.cs b/src/ui/Features/Options/Settings/SettingsItem.cs index ac9fc5e3cef..2b064d3ad25 100644 --- a/src/ui/Features/Options/Settings/SettingsItem.cs +++ b/src/ui/Features/Options/Settings/SettingsItem.cs @@ -21,6 +21,12 @@ public class SettingsItem public bool IsHidden { get; set; } = false; public bool IsFullWidth { get; init; } + /// + /// English text the search also matches, for a label not found as one translated string + /// (e.g. built from several). Otherwise looked up via . + /// + public string? EnglishLabel { get; init; } + public SettingsItem(string label, Func controlFactory, string? isVisibleBinding = null, bool isHidden = false) { _label = label; @@ -53,8 +59,21 @@ public void Filter(string filter) return; } - IsVisible = string.IsNullOrWhiteSpace(filter) || _label != null && - _label.Contains(filter, StringComparison.OrdinalIgnoreCase); + if (string.IsNullOrWhiteSpace(filter)) + { + IsVisible = true; + return; + } + + if (_label != null && _label.Contains(filter, StringComparison.OrdinalIgnoreCase)) + { + IsVisible = true; + return; + } + + // With a translated UI, also match the English name used in docs and tutorials (#15945). + var english = EnglishLabel ?? EnglishLabelLookup.GetEnglish(_label); + IsVisible = english != null && english.Contains(filter, StringComparison.OrdinalIgnoreCase); } public Control Build(bool includeLabel = true) diff --git a/src/ui/Features/Options/Settings/SettingsPage.cs b/src/ui/Features/Options/Settings/SettingsPage.cs index 41d70d06216..76e962f1156 100644 --- a/src/ui/Features/Options/Settings/SettingsPage.cs +++ b/src/ui/Features/Options/Settings/SettingsPage.cs @@ -404,8 +404,11 @@ private List CreateSections() [!TextBlock.TextProperty] = new Binding(nameof(_vm.MinGapFramesAsMs)) { Source = _vm }, }.WithBindIsVisible(_vm, nameof(_vm.UseFrameMode)), } - }), - new SettingsItem(Se.Language.Options.Settings.MaxLines, () => MakeNumericUpDownInt(nameof(_vm.MaxLines), _vm.RuleValueChanged)), + }) + { + EnglishLabel = EnglishLabelLookup.English.Options.Settings.MinGapMs + " " + EnglishLabelLookup.English.Options.Settings.MinGapFrames, + }, + new SettingsItem(Se.Language.Options.Settings.MaxLines,() => MakeNumericUpDownInt(nameof(_vm.MaxLines), _vm.RuleValueChanged)), new SettingsItem(Se.Language.Options.Settings.UnbreakSubtitlesShortThan, () => MakeNumericUpDownInt(nameof(_vm.UnbreakLinesShorterThan), _vm.RuleValueChanged)), new SettingsItem(Se.Language.Options.Settings.DialogStyle, () => MakeComboBoxDialogStyle()), diff --git a/tests/UI/Features/Options/Settings/SettingsSearchEnglishTests.cs b/tests/UI/Features/Options/Settings/SettingsSearchEnglishTests.cs new file mode 100644 index 00000000000..b5f3480467a --- /dev/null +++ b/tests/UI/Features/Options/Settings/SettingsSearchEnglishTests.cs @@ -0,0 +1,55 @@ +using Nikse.SubtitleEdit.Features.Options.Settings; +using Nikse.SubtitleEdit.Logic.Config.Language; + +namespace UITests.Features.Options.Settings; + +/// +/// With a translated UI the Settings search also matches the English names used in docs and +/// tutorials (#15945). +/// +public class SettingsSearchEnglishTests +{ + [Fact] + public void Build_MapsTranslatedStringsToEnglish() + { + var english = new SeLanguage(); + var translated = new SeLanguage(); + translated.Options.Settings.MinGapMs = "Mindestabstand (ms)"; + translated.General.Profiles = "Profile"; + + var map = EnglishLabelLookup.Build(translated, english); + + Assert.Equal(english.Options.Settings.MinGapMs, map["Mindestabstand (ms)"]); + Assert.Equal(english.General.Profiles, map["Profile"]); + Assert.DoesNotContain(english.Options.Settings.MaxLines, map.Keys); // untranslated = same text + } + + [Fact] + public void Build_JoinsSeveralEnglishStringsWithSameTranslation() + { + var english = new SeLanguage(); + var translated = new SeLanguage(); + translated.Options.Settings.MinGapMs = "X"; + translated.Options.Settings.MaxLines = "X"; + + var map = EnglishLabelLookup.Build(translated, english); + + Assert.Contains(english.Options.Settings.MinGapMs, map["X"].Split('\n')); + Assert.Contains(english.Options.Settings.MaxLines, map["X"].Split('\n')); + } + + [Fact] + public void Filter_MatchesExplicitEnglishLabel() + { + var item = new SettingsItem("Mindestabstand", () => null!) { EnglishLabel = "Min gap (ms)" }; + + item.Filter("gap"); + Assert.True(item.IsVisible); + + item.Filter("abstand"); + Assert.True(item.IsVisible); + + item.Filter("duration"); + Assert.False(item.IsVisible); + } +} From e7c1887e48a0a1dd8b1e46715fea0bcfd013e61d Mon Sep 17 00:00:00 2001 From: niksedk Date: Mon, 12 Oct 2026 04:01:03 +0200 Subject: [PATCH 083/306] Release v5.3.0-rc3 Co-Authored-By: Claude Opus 5.5 --- change-log.txt | 28 ++++++++++++++++++++++++++++ src/ui/Assets/Languages/English.json | 2 +- src/ui/Logic/Config/Se.cs | 2 +- 3 files changed, 30 insertions(+), 2 deletions(-) diff --git a/change-log.txt b/change-log.txt index faacf7b788e..8b84e4a1ea5 100644 --- a/change-log.txt +++ b/change-log.txt @@ -1,6 +1,34 @@  Subtitle Edit Changelog +----------------------------------------------------------------------------------------------------- + +v5.3.0-rc3 (12th of October 2026) + +* Add nOCR Cyrillic database (Cyrillic.nocr) with case and "ы" handling +* Add nOCR "Optimize database" (OCR window + seconv optimize-nocr) +* Add "Fast (HTDemucs)" model option for TTS "Remove original speech" - thx invio-a11y +* Improve time codes: fix mistimed overlaps kept, skip lyrics/positioned signs/comments, snap to frames in frame mode +* Import plain text: hold ends for max CPS, use the Gap setting, allow cancel of alignment +* Improve Latin.nocr (drop lines that don't tell glyphs apart from lookalikes) +* Improve help: F1 help in 35 more dialogs, docs updates +* Batch convert: VobSub uses the profile's font and outline color - thx mehdittttt +* Batch convert: keep fades in Blu-ray sup and alignment in D-Cinema PNG +* Blu-ray sup: bridge gaps shorter than half a frame +* Fix settings export/import/reset missing sections - thx FunkyKnilch +* Fix list scroll bars covering item content (e.g. command palette shortcuts) - thx GrampaWildWilly +* Fix OCR upgrade replacing user-trained/optimized nOCR databases +* Fix high memory use with image subtitles in Point sync via other +* Fix crash risk when clicking OK while the Matroska track picker preview is loading +* Fix "New line format" placeholder in Custom text format - thx GitGianluc +* Update llama.cpp to b11553 +* Update French translation - thx Need74 +* Update Italian translation - thx bovirus +* Update Japanese translation - thx hisui3393 +* Update Polish translation - thx potplayer-fanpack +* Update Turkish translation - thx bilimiyorum + + ----------------------------------------------------------------------------------------------------- v5.3.0-rc2 (11th of October 2026) diff --git a/src/ui/Assets/Languages/English.json b/src/ui/Assets/Languages/English.json index 57bf534a852..ebd05b6d095 100644 --- a/src/ui/Assets/Languages/English.json +++ b/src/ui/Assets/Languages/English.json @@ -1,6 +1,6 @@ { "title": "Subtitle Edit", - "version": "v5.3.0-rc2", + "version": "v5.3.0-rc3", "translatedBy": "", "cultureName": "en-US", "general": { diff --git a/src/ui/Logic/Config/Se.cs b/src/ui/Logic/Config/Se.cs index e5aab3691c2..d1d12e7bd52 100644 --- a/src/ui/Logic/Config/Se.cs +++ b/src/ui/Logic/Config/Se.cs @@ -21,7 +21,7 @@ public class Se internal const int CurrentShortcutsMigrationVersion = 6; internal const int CurrentLayoutMigrationVersion = 2; - public static string Version { get; set; } = "v5.3.0-rc2"; + public static string Version { get; set; } = "v5.3.0-rc3"; public SeGeneral General { get; set; } = new(); public List Shortcuts { get; set; } = new(); From b026edf0604b5ad5356091ae134b436d18d5fc19 Mon Sep 17 00:00:00 2001 From: 12si27 <88251502+12si27@users.noreply.github.com> Date: Mon, 12 Oct 2026 11:29:57 +0900 Subject: [PATCH 084/306] Update Korean.json --- src/ui/Assets/Languages/Korean.json | 120 ++++++++++++++++++++++++++-- 1 file changed, 112 insertions(+), 8 deletions(-) diff --git a/src/ui/Assets/Languages/Korean.json b/src/ui/Assets/Languages/Korean.json index e8a86869c05..268b1bce262 100644 --- a/src/ui/Assets/Languages/Korean.json +++ b/src/ui/Assets/Languages/Korean.json @@ -1,6 +1,6 @@ { "title": "Subtitle Edit", - "version": "v5.3.0-beta26", + "version": "v5.3.0-rc3", "translatedBy": "Hack茶ん, 12시27분(@12si27)", "cultureName": "ko-KR", "general": { @@ -295,7 +295,7 @@ "help": "도움말", "hex": "헥스", "hide": "숨김", - "history": "연혁", + "history": "기록", "horizontal": "가로", "id": "ID", "image": "이미지", @@ -594,7 +594,9 @@ "showForcedColumn": "\"강제 표시\" 열 표시", "showStartColumn": "\"표시\" 열 표시", "showHideColumn": "\"숨김\" 열 표시", - "showHistory": "이력 보기", + "showHistory": "기록 보기", + "clearHistory": "기록 비우기", + "removeFromHistory": "기록에서 지우기", "showLayerColumn": "\"레이어\" 열 표시", "showPreview": "미리 보기 표시", "showShotChangesList": "장면 전환 목록 표시", @@ -883,13 +885,14 @@ "edit": "편집(_E)", "undo": "실행 취소(_U)", "redo": "다시 실행(_D)", - "showHistory": "실행 취소 내역 표시(_S)...", + "showHistory": "실행 취소 기록 표시(_S)...", "find": "찾기(_F)...", "findNext": "다음 찾기(_N)", "replace": "바꾸기(_R)...", "multipleReplace": "일괄 바꾸기(_M)...", "fillSelectedLinesWithClipboard": "선택한 줄을 클립보드 텍스트로 채우기", "goToLineNumber": "줄 번호로 이동(_G)...", + "commandPalette": "명령 팔레트(_P)...", "rightToLeftMode": "RTL 모드(_I)", "modifySelectionDotDotDot": "선택 항목 수정(_S)...", "tools": "도구(_T)", @@ -1082,6 +1085,9 @@ "charactersPerSecond": "문자/초: {0}", "chooseColumn": "열 선택", "columnPaste": "열 붙여넣기", + "commandPaletteTitle": "명령 팔레트", + "commandPaletteSearchPlaceholder": "명령을 검색하려면 입력하세요...", + "commandPaletteNoCommandsFound": "일치하는 명령이 없습니다", "createdEmptyTranslation": "현재 자막을 기반으로 빈 번역본 생성", "deleteText": "텍스트 삭제", "deleteTextAndShiftCellsUp": "텍스트를 삭제하고 셀을 위로 이동", @@ -1339,7 +1345,8 @@ "forcedAlignerIsolatingSpeech": "음성 분리 중... {0}", "forcedAlignerIsolatingSpeechFailed": "음성을 분리하지 못해 읽는 데 필요한 시간을 기준으로 종료 시간을 설정했습니다.", "forcedAlignerModel": "정렬기 모델", - "forcedAlignerDownloadEngine": "엔진 다운로드 / 업데이트..." + "forcedAlignerDownloadEngine": "엔진 다운로드 / 업데이트...", + "forcedAlignerEstimatedXOfY": "{1}줄 중 {0}줄을 정렬하지 못해 읽는 데 필요한 시간을 기준으로 배치했습니다. 해당 줄의 타임코드는 추정값입니다. 대본이 비디오에서 실제로 말한 내용보다 길 수 있습니다." }, "export": { "exportImagesProfiles": "이미지 프로필 내보내기", @@ -2054,7 +2061,7 @@ "summaryXRetimedYKeptZSkipped": "시간 조정: {0} · 유지: {1} · 음성 없음: {2}", "meanShiftX": "평균 이동 시간: {0}밀리초", "toCheckX": "확인 필요: {0}", - "largeMovesToCheckX": "주변 줄과 달리 크게 이동해야 하는 {0}개 줄은 체크를 해제했습니다. 원본과 정렬 후 결과를 재생해 보고 올바른 줄을 체크하세요.", + "largeMovesToCheckX": "주변 줄과 달리 크게 이동해야 하는 {0}개 줄은 체크를 해제했습니다. 원본과 정렬 후 결과를 재생해 보고(F5는 정렬 후 줄, Shift+F5는 원래 줄 재생) 올바른 줄을 체크하세요.", "linesToCheckX": "{0}줄은 확인이 필요하여 체크하지 않았습니다. 대사를 들어 보고(F5는 정렬 후 줄, Shift+F5는 원래 줄 재생) 올바른 줄을 체크하세요.", "changeXOfY": "변경 사항 {0}/{1}", "previousChange": "이전 변경 사항", @@ -2076,7 +2083,7 @@ "playAligned": "정렬 후", "playAlignedHint": "선택한 줄을 정렬 후 타임코드로 재생", "apply": "적용", - "applyHint": "줄의 체크를 해제하면 원래 타임코드를 유지합니다. 줄을 두 번 클릭하면 재생합니다." + "applyHint": "줄의 체크를 해제하면 원래 타임코드를 유지합니다. 두 번 클릭하면 재생합니다. 정렬된 파형에서 이동하거나 크기를 조정할 수 있습니다." }, "beautifyTimeCodesProfile": { "title": "타임코드 다듬기 프로필 편집", @@ -2155,7 +2162,7 @@ "addAlignment": "정렬 적용", "addColor": "색상 적용", "deleteLinesWithSpecificActorsOrStyles": "등장인물 또는 스타일이 포함된 줄을 삭제(여러 개는 쉼표로 구분)", - "useSourceStylesIfPossible": "가능하다면 소스 스타일을 사용", + "useSourceStylesIfPossible": "가능하다면 소스 스타일을 사용 (아래 헤더는 ASSA가 아닌 입력에만 사용)", "keepSourceEmbeddedFonts": "원본에 포함된 글꼴 유지 (푸터)", "editStyles": "스타일 편집", "editProperties": "속성 편집", @@ -2808,6 +2815,10 @@ "removeOriginalSpeechDescription": "원본 비디오의 음악과 효과음은 유지하고 음성만 제거한 뒤, 새 음성을 합칩니다. GPU를 사용해도 비디오 길이만큼 시간이 걸리며, GPU가 없으면 몇 배 더 오래 걸립니다. 오디오 더킹을 켜면 더킹 볼륨 설정이 음악과 효과음에 적용됩니다. 음성을 비디오 파일에 추가할 때만 적용됩니다.", "removingOriginalSpeech": "원본 음성 제거 중...", "removeOriginalSpeechFailed": "원본 음성을 제거하지 못해 제거 과정 없이 새 음성을 비디오에 추가했습니다.", + "speechRemovalModel": "모델", + "speechRemovalModelDescription": "최고 품질은 음악과 효과음을 최대한 유지합니다. 빠름은 약 3배 빠르지만 음악이 조금 더 손실됩니다.", + "speechRemovalBestQuality": "최고 품질 (Mel-Band RoFormer)", + "speechRemovalFast": "빠름 (HTDemucs)", "videoTruncatedWarning": "원본 비디오의 길이는 {1}이지만, 새 비디오 파일의 영상은 {0} 이후로 멈춥니다. 소리는 계속 재생되지만 화면은 정지됩니다.\n\nFFmpeg 출력은 Subtitle Edit 데이터 폴더의 error-log.txt에서 확인하세요. 파일은 보존되어 있습니다.", "originalVolumePercent": "원본 음량 %", "vadSilenceCompression": "VAD 무음 압축", @@ -3564,6 +3575,7 @@ "textBoxLimitNewLines": "자막 입력칸 줄 수 제한", "mpvOpenGl": "libmpv - OpenGL", "mpvSoftwareRendering": "libmpv - 소프트웨어 렌더링(느림)", + "mpvMetalRendering": "libmpv - Metal (실험적, 재시작 필요)", "mpvWidRendering": "libmpv - 기본 창 ID 렌더링", "waveFormsAndSpectrogramFoldersContainsX": "\"파형\" 및 \"스펙트로그램\" 폴더에는 {0}이(가) 포함되어 있음", "deleteWaveformAndSpectrogramFoldersQuestion": "\"파형\" 및 \"스펙트로그램\" 파일을 삭제하시겠습니까?", @@ -4213,9 +4225,26 @@ "crispEmbedSettingsTitle": "CrispEmbed 설정", "crispEmbedDescription": "여러 모델 백엔드를 지원하는 로컬 OCR 엔진 - 여기에서 엔진과 모델을 다운로드하세요.", "trainNOcrDatabase": "nOCR 데이터베이스 훈련...", + "optimizeNOcrDatabase": "nOCR 데이터베이스 최적화...", + "optimizeNOcrDatabaseQuestionXY": "nOCR 데이터베이스 \"{0}\"에서 비슷한 문자를 구별하는 데 도움이 되지 않는 선을 제거하시겠습니까?\n\n데이터베이스 크기가 줄고 OCR이 빨라지며, 대체로 오류도 줄어듭니다. 최대 1분 정도 걸립니다.\n\n백업은 \"{1}\"에 저장됩니다.", + "optimizingNOcrDatabaseXY": "nOCR 데이터베이스 최적화 중... {0}/{1}", + "nOcrDatabaseOptimizedXYZ": "nOCR 데이터베이스 \"{0}\" 최적화 완료: 선 {1:#,##0}개 -> {2:#,##0}개.", "startTraining": "훈련 시작", "abortTraining": "훈련 중단", "trainingOptions": "훈련 설정", + "trainNOcrSubtitle": "설치된 글꼴로 문자를 그려 nOCR이 문자 모양을 학습하도록 합니다. 자막에 사용된 글꼴을 선택하세요.", + "xFontsSelected": "{0:#,##0}개 선택됨", + "subtitleFonts": "자막 글꼴", + "emptyDatabase": "빈 데이터베이스", + "startFromDatabase": "기준 데이터베이스", + "startFromDatabaseHint": "기존 데이터베이스의 복사본에 훈련한 문자를 추가하거나(권장), 빈 데이터베이스에서 시작합니다", + "fontSizes": "글자 크기", + "fontSizesHint": "문자를 그릴 크기(픽셀)를 쉼표로 구분하여 입력하세요. 예: \"30, 40\"", + "numberOfLineSegmentsHint": "문자당 검사할 선 수 - 많을수록 판별이 엄격해지지만 느려집니다", + "enterDatabaseName": "데이터베이스 이름을 입력하세요", + "trainingFontXSizeYCharacterZ": "\"{0}\" 훈련 중, {1} px: {2}", + "xLearned": "{0:#,##0}개 학습됨", + "xSkipped": "{0:#,##0}개 건너뜀", "charactersToTrain": "훈련시킬 문자", "letterCombinationsToTrain": "하나의 이미지로 분할될 수 있는 글자 조합", "importCharactersFromSubtitleFile": "자막 파일에서 문자 가져오기...", @@ -4251,6 +4280,81 @@ "drawSelectedLayer": "선택한 레이어", "drawToolX": "도구: {0}", "drawHelpText": "클릭하여 점을 추가 • Enter/F8 키를 눌러 도형을 닫음 • Shift 키를 누른 채 드래그하여 이동 • Ctrl 키를 누른 채 스크롤하여 확대/축소", + "drawMoveToLayer": "레이어로 이동", + "drawFlipHorizontal": "수평 뒤집기", + "drawFlipVertical": "수직 뒤집기", + "drawHideShape": "도형 숨기기", + "drawShowShape": "도형 표시", + "drawHideLayer": "레이어 숨기기", + "drawShowLayer": "레이어 표시", + "drawDeleteLayer": "레이어 삭제", + "drawDeletePoint": "점 삭제", + "drawCancelDrawing": "그리기 취소", + "drawLayerX": "레이어 {0}", + "drawImportSvg": "SVG 이미지 가져오기...", + "drawRotateClockwise": "시계 방향으로 90° 회전", + "drawConvertToCurve": "선분을 곡선으로 변환", + "drawBackground": "배경", + "drawPosition": "위치", + "drawZoom": "확대/축소", + "drawShapeTool": "도형 도구", + "drawColorPickerTool": "도형이나 배경에서 색상 선택", + "drawShapeLibrary": "도형 라이브러리", + "drawShapeLibraryHint": "캔버스에서 드래그하여 도형을 배치하거나, 클릭하여 기본 크기로 배치하세요. Shift 키를 누르면 비율이 유지됩니다.", + "drawAddToLibrary": "도형 라이브러리에 추가...", + "drawRemoveFromLibrary": "도형 라이브러리에서 제거", + "drawShapeName": "도형 이름", + "drawCategorySpeechBubbles": "말풍선", + "drawCategoryArrows": "화살표", + "drawCategoryBasicShapes": "기본 도형", + "drawCategorySymbols": "기호", + "drawCategoryMyShapes": "내 도형", + "drawShapeSpeechBubble": "말풍선", + "drawShapeSpeechBubbleRight": "말풍선 (오른쪽 꼬리)", + "drawShapeSpeechBubbleUp": "말풍선 (위쪽 꼬리)", + "drawShapeOvalBubble": "타원형 말풍선", + "drawShapeBoxBubble": "상자형 말풍선", + "drawShapeThoughtBubble": "생각 말풍선", + "drawShapeShoutBubble": "외침 말풍선", + "drawShapeCaptionBox": "자막 상자", + "drawShapeArrow": "화살표", + "drawShapeDoubleArrow": "양방향 화살표", + "drawShapeChevron": "갈매기형", + "drawShapeCurvedArrow": "곡선 화살표", + "drawShapeRoundedRectangle": "둥근 모서리 직사각형", + "drawShapeTriangle": "삼각형", + "drawShapeDiamond": "마름모", + "drawShapePentagon": "오각형", + "drawShapeHexagon": "육각형", + "drawShapeOctagon": "팔각형", + "drawShapeStar": "별", + "drawShapeRing": "고리", + "drawShapePlus": "더하기 기호", + "drawShapeBanner": "배너", + "drawShapeHeart": "하트", + "drawShapeCheck": "체크 표시", + "drawShapeMusicNote": "음표", + "drawShapeMusicNotes": "여러 음표", + "drawShapeLightning": "번개", + "drawShapeCloud": "구름", + "drawShapeMoon": "달", + "drawShapeBadge": "배지", + "drawSize": "크기", + "drawLayer": "레이어", + "drawBackgroundVideoFrame": "비디오 프레임 (현재 위치)", + "drawBackgroundVideoFrameAt": "지정한 시간의 비디오 프레임...", + "drawBackgroundImage": "이미지 파일...", + "drawBackgroundNone": "배경 없음", + "drawBackgroundStretch": "이미지를 캔버스 크기로 늘리기", + "drawBackgroundOpacity": "배경 불투명도", + "drawBackgroundVideoFrameFailed": "{0} 위치의 비디오 프레임을 가져오지 못했습니다.", + "drawImages": "이미지", + "drawConvertToLine": "선분을 직선으로 변환", + "drawConvertShapeToCurves": "모든 직선을 곡선으로 변환", + "drawConvertShapeToLines": "모든 곡선을 직선으로 변환", + "drawRotateCounterClockwise": "시계 반대 방향으로 90° 회전", + "drawSvgImages": "SVG 이미지", + "drawSvgNoShapes": "SVG 파일에서 채워진 도형이나 선을 찾을 수 없습니다.", "progressBarTitle": "ASSA 진행 표시줄", "progressBarSettings": "진행 표시줄", "mouseOverColor": "마우스 오버 색상", From a696fab673d8d7d8c4930fd09c13a275778b038b Mon Sep 17 00:00:00 2001 From: niksedk Date: Mon, 12 Oct 2026 06:02:40 +0200 Subject: [PATCH 085/306] =?UTF-8?q?Update=20Czech=20-=20thx=20Mat=C4=9Bj?= =?UTF-8?q?=20:)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/ui/Assets/Languages/Czech.json | 478 +++++++++++++++++++++++++++-- 1 file changed, 446 insertions(+), 32 deletions(-) diff --git a/src/ui/Assets/Languages/Czech.json b/src/ui/Assets/Languages/Czech.json index afe9ff11429..04404a909d0 100644 --- a/src/ui/Assets/Languages/Czech.json +++ b/src/ui/Assets/Languages/Czech.json @@ -1,6 +1,6 @@ { "title": "Subtitle Edit", - "version": "v5.3.0-beta15", + "version": "v5.3.0-rc2", "translatedBy": "Matěj Fouma", "cultureName": "cs-CZ", "general": { @@ -13,6 +13,18 @@ "actorPickerKeysHint": "Stisknutím čísla nebo klávesy Enter herce nastavíte. Klávesová zkratka Alt + šipka nahoru/dolů mění pořadí, klávesa Delete herce odebere.", "actorPickerNewActorX": "Nový herec \"{0}\"", "actorPickerLinesSelectedX": "Řádků vybráno: {0}", + "setStyleDotDotDot": "Nastavit styl…", + "stylePickerTitle": "Nastavit styl", + "stylePickerFilterHint": "Zadejte text pro filtrování nebo napište název nového stylu", + "stylePickerKeysHint": "Stiskněte číslici (1–9, 0) pro nastavení stylu na všech vybraných řádcích.", + "stylePickerNewStyleX": "Nový styl \"{0}\"", + "stylePickerNewStyleInfo": "Stisknutím klávesy Enter přidáte tento styl s výchozím nastavením a použijete jej na vybrané řádky. Pomocí \"Spravovat styly…\" můžete později změnit jeho písmo, barvy a umístění.", + "stylePickerCurrentStyleX": "Aktuální styl: {0}", + "stylePickerLinesUsingStyleX": "Řádky používající tento styl: {0}", + "stylePickerMarginsXYZ": "Vlevo {0}, vpravo {1}, svisle {2}", + "stylePickerSetStyle": "_Nastavit styl", + "stylePickerManageStyles": "Spravovat styly…", + "stylePickerManageStylesHint": "Zavřete toto okno a otevřete správce stylů, kde můžete upravit písma, barvy, umístění a další parametry", "add": "Přidat", "addDotDotDot": "Přidat…", "addToNamesListCaseSensitive": "Přidat na seznam jmen (rozlišuje velikost písmen)", @@ -187,7 +199,7 @@ "noDataReceived": "Nebyla přijata žádná data", "unknownError": "Neznámá chyba", "downloadComplete": "Stahování dokončeno…", - "downloadCompleteButCouldNotDeleteFile": "Stahování dokončeno, ale nepodařilo se odstranit existující soubor.", + "downloadCompleteButCouldNotDeleteFile": "Stahování dokončeno, ale nepodařilo se smazat existující soubor.", "pleaseRestartSeToUseTheNewLibmpv": "Pro použití nové knihovny libmpv prosím restartujte SE.", "pickVobSubLanguageTitle": "Vyberte jazyk VobSub – {0}", "duplicate": "Duplikovat", @@ -282,7 +294,7 @@ "height": "Výška", "help": "Nápověda", "hex": "Hex", - "hide": "Konec", + "hide": "Skrýt", "history": "Historie", "horizontal": "Vodorovný", "id": "ID", @@ -413,7 +425,7 @@ "options": "Volby", "originalSubtitleClosed": "Originální titulky zavřeny", "originalSubtitleLoadedX": "Originální titulky načteny: {0}", - "originalText": "Originální titulky", + "originalText": "Originální text", "outline": "Obrys", "outlineColor": "Barva obrysu", "outlineWidth": "Šířka obrysu", @@ -431,6 +443,7 @@ "percent": "Procent", "pickLayer": "Nastavit vrstvu", "pickOllamaModel": "Vybrat model Ollama", + "pickModel": "Vybrat model", "llamaCppUseRemoteServer": "Použít externí server (URL)", "llamaCppEngineSettings": "Nastavení modulu llama.cpp", "llamaCppEngineSettingsSubtitle": "Lokální kompilace serveru llama.cpp", @@ -507,6 +520,7 @@ "reset": "Resetovat", "resizePanels": "Změnit velikost panelů", "resolution": "Rozlišení", + "retry": "Zkusit znovu", "reverseRightToLeftStartEnd": "Obrátit začátek/konec zprava doleva", "right": "Vpravo", "rightToLeft": "Zprava doleva", @@ -568,6 +582,8 @@ "shadowColor": "Barva stínu", "shadowWidth": "Šířka stínu", "shiftLineBy": "Posunout řádek o", + "shotIn": "Začátek záběru", + "shotOut": "Konec záběru", "shortcut": "Klávesová zkratka", "shortcuts": "Klávesové zkratky", "show": "Začátek", @@ -579,9 +595,13 @@ "showStartColumn": "Zobrazit buňku \"Začátek\"", "showHideColumn": "Zobrazit buňku \"Konec\"", "showHistory": "Zobrazit historii", + "clearHistory": "Vyčistit historii", + "removeFromHistory": "Odebrat z historie", "showLayerColumn": "Zobrazit buňku \"Vrstva\"", "showPreview": "Zobrazit náhled", "showShotChangesList": "Zobrazit seznam změn záběrů", + "showShotInColumn": "Zobrazit buňku \"Začátek záběru\"", + "showShotOutColumn": "Zobrazit buňku \"Konec záběru\"", "showStyleColumn": "Zobrazit buňku \"Styl\"", "showTeletext": "Zobrazit buňku teletext v seznamovém zobrazení", "showTimeCodes": "Zobrazit časové kódy", @@ -638,12 +658,14 @@ "styleExaggeration": "Zesílení stylu", "styles": "Styly", "subtitleFile": "Soubor titulků", + "subtitleFileChangedOutsideX": "Soubor \"{0}\" byl změněn mimo Subtitle Edit.\n\nZnovu jej načíst a ztratit své úpravy?", "subtitleFileName": "Název souboru titulků", "subtitleFileSaved": "Soubor titulků uložen", "subtitleFileSavedToX": "Soubor titulků uložen do {0}", "subtitleFiles": "Soubory titulků", "subtitleFormats": "Formáty titulků", "subtitleLoadedX": "Načtené titulky: {0}", + "subtitleReloadedChangedOutsideX": "Titulky znovu načteny (změněny mimo Subtitle Edit): {0}", "suffix": "Sufix", "suggestions": "Návrhy", "sync": "Synchronizovat", @@ -727,10 +749,23 @@ "videoCustom3ForwardX": "Video, vlastních milisekund ({0:#,###,##0}) vpřed, 3", "videoCustom4BackX": "Video, vlastních milisekund ({0:#,###,##0}) zpět, 4", "videoCustom4ForwardX": "Video, vlastních milisekund ({0:#,###,##0}) vpřed, 4", + "videoCustom1BackAndPauseX": "Video, vlastních milisekund ({0:#,###,##0}) zpět a pozastavit, 1", + "videoCustom1ForwardAndPauseX": "Video, vlastních milisekund ({0:#,###,##0}) vpřed a pozastavit, 1", + "videoCustom2BackAndPauseX": "Video, vlastních milisekund ({0:#,###,##0}) zpět a pozastavit, 2", + "videoCustom2ForwardAndPauseX": "Video, vlastních milisekund ({0:#,###,##0}) vpřed a pozastavit, 2", + "videoCustom3BackAndPauseX": "Video, vlastních milisekund ({0:#,###,##0}) zpět a pozastavit, 3", + "videoCustom3ForwardAndPauseX": "Video, vlastních milisekund ({0:#,###,##0}) vpřed a pozastavit, 3", + "videoCustom4BackAndPauseX": "Video, vlastních milisekund ({0:#,###,##0}) zpět a pozastavit, 4", + "videoCustom4ForwardAndPauseX": "Video, vlastních milisekund ({0:#,###,##0}) vpřed a pozastavit, 4", "videoExtension": "Přípona souboru videa", "videoFile": "Soubor videa", "videoFileGenerated": "Vygenerovaný soubor videa", "videoFileGeneratedX": "Vygenerovaný soubor videa: \"{0}\"", + "videoFilesGenerated": "Soubory videa vygenerovány", + "xVideosGenerated": "Vygenerováno {0} videí", + "xOfYVideosGenerated": "Vygenerováno {0} z {1} videí", + "savedInX": "Uloženo v {0}", + "savedInXFolders": "Uloženo v {0} složkách", "videoFiles": "Soubory videa", "videoAndAudioFiles": "Soubory videa a audia", "videoOffset": "Posun videa", @@ -857,6 +892,7 @@ "multipleReplace": "Hro_madné nahrazení…", "fillSelectedLinesWithClipboard": "Vyplnit vybrané řádky textem ze schránky", "goToLineNumber": "_Přejít na řádek číslo…", + "commandPalette": "S_ada příkazů", "rightToLeftMode": "_Režim zprava doleva", "modifySelectionDotDotDot": "Upravit _výběr…", "tools": "_Nástroje", @@ -1047,10 +1083,13 @@ "audioTrackX": "Audio stopa {0}", "autoBreakHint": "Automaticky zalomit vybrané řádky", "charactersPerSecond": "Znaků/sek.: {0}", - "chooseColumn": "Vybrat buňky", + "chooseColumn": "Vybrat buňku", "columnPaste": "Vložit buňku", + "commandPaletteTitle": "Sada příkazů", + "commandPaletteSearchPlaceholder": "Zadejte text pro vyhledání příkazů…", + "commandPaletteNoCommandsFound": "Žádné odpovídající příkazy", "createdEmptyTranslation": "Vytvořen prázdný překlad z aktuálních titulků", - "deleteText": "Smazat titulek", + "deleteText": "Smazat text", "deleteTextAndShiftCellsUp": "Smazat text a posunout buňky nahoru", "endTimeMustBeAfterStartTime": "Čas konce musí být před časem začátku.", "errorLoad7Zip": "Zdá se, že tento soubor je komprimovaný soubor .7-Zip.\n\nSubtitle Edit neumí otevřít komprimované soubory.", @@ -1094,6 +1133,7 @@ "overwriteExistingCells": "Přepsat stávající buňky", "overwriteOrShiftCellsDown": "Přepsat/posunout buňky dolů", "parsingMatroskaFile": "Analýza souboru Matroska…", + "readingDvdSubtitles": "Čtení titulků z DVD…", "pasteFromClipboardDotDotDot": "Vložit ze schránky…", "redoPerformed": "Znovu provedeno", "redoPerformedXActionLeft": "Znovu provedeno (zbývá akcí: {0})", @@ -1133,12 +1173,14 @@ "trimmedXLines": "Ořezáno {0} řádků titulků", "pastedXLinesOverSelectedLines": "Přes vybrané řádky vloženo {0} řádků", "pastedXOfYLinesOverSelectedLines": "Přes vybrané řádky vloženo {0} z {1} řádků – zbytek se do výběru nevešel", + "insertedXLinesFromClipboard": "Vloženo {0} řádků ze schránky", "openOriginalDifferentNumberOfSubtitlesXY": "Originální soubor titulků nemá stejný počet řádků jako aktuální soubor titulků.\n\n• Originální titulky: {0}\n• Aktuální titulky: {1}", "originalTextReadOnly": "Originální text (pouze pro čtení)", "originalTextEditMode": "Originální text (režim úprav)", "originalIsReadOnlyNotSaved": "Originální titulky jsou referenční a nebyly uloženy", "originalIsEmptyNotSaved": "Originální titulky neobsahují žádný text a nebyly uloženy", "originalIsReadOnlyReference": "Originální titulky jsou otevřeny jako referenční a pouze pro čtení", + "closeTranslationQuestionXY": "Zavřít překlad \"{0}\"?\n\nPůvodní titulek \"{1}\" se stane titulkem, který upravujete. Samotný překladový soubor se nezmění.", "allowEditOfOriginalSubtitle": "Povolit úpravu originálních titulků", "showAllOriginalLinesX": "Zobrazit všechny originální řádky ({0} zde nemá žádnou shodu)", "showAllOriginalLinesHint": "Řádky bez shody se zobrazují samostatně, abyste viděli, co v překladu chybí.", @@ -1173,6 +1215,7 @@ "fileExportedInFormatXToY": "Soubor exportován ve formátu {0} do {1}", "fileExportedInFormatXToFileY": "Soubor exportován ve formátu \"{0}\" do souboru \"{1}\"", "fixedXLines": "Opraveno {0} řádků", + "toolXChangedYLines": "{0}: Změněno {1} řádků", "transcriptionCompletedWithXLines": "Přepis dokončen s {0} řádky", "openSubtitleFileX": "Otevřít soubor titulků \"{0}\"?", "replacedXOccurrences": "Nahrazeno {0} výskytů", @@ -1263,6 +1306,21 @@ "formattingDotDotDot": "Formátování…", "imageBasedSubtitleForEditDotDotDot": "Titulky založené na obrázcích pro úpravu…", "imageBasedSubtitleForOcrDotDotDot": "Titulky založené na obrázcích pro OCR…", + "dvdSubtitlesDotDotDot": "Titulky z DVD (IFO/VOB)…", + "titleImportDvd": "Importovat titulky z DVD", + "dvdIfoFile": "Soubor IFO", + "dvdVobFiles": "Soubory VOB", + "dvdVideoStandard": "Video standard", + "dvdStartRipping": "Spustit ripování", + "dvdRippingAborted": "Ripování přerušeno", + "dvdOpenIfoFile": "Otevřít soubor IFO z DVD", + "dvdAddVobFiles": "Přidat soubory VOB…", + "dvdIfoFiles": "Soubory DVD IFO", + "dvdVobFileType": "Soubory DVD VOB", + "dvdDropHint": "Otevřete soubor IFO (VIDEO_TS\\VTS_01_0.IFO) nebo přidejte soubory VOB – případně je přetáhněte sem", + "dvdNotAnIfoFileX": "{0} není soubor IFO z DVD", + "dvdMenuIfoNoTitleSetsX": "Vedle souboru {0} nebyly nalezeny žádné sady titulků (VTS_xx_0.IFO)", + "dvdEncryptedXOfY": "{0} z {1} balíčků titulků je stále šifrováno pomocí CSS – tyto soubory VOB byly zkopírovány bez dešifrování, takže některé obrázky titulků budou nečitelné.\n\nPro čistý výsledek nejprve DVD dešifrujte pomocí nástroje pro ripování DVD.\n\nPřesto pokračovat?", "splitTextAt": "Rozdělit text na", "blankLines": "Prázdné řádky", "oneLineIsOneSubtitle": "Jeden řádek je jeden titulek", @@ -1528,11 +1586,61 @@ "ignoreFormatting": "Ignorovat formátování", "ignoreFormattingHint": "Řádky, které se liší pouze formátovacími značkami, jako nebo {\\an8}, se nepovažují za odlišné", "showOnlyDifferencesInText": "Pouze rozdíly v textu", + "ignoreNumbering": "Ignorovat číslování", + "ignoreNumberingHint": "Řádky, které se liší pouze číslem, se nepovažují za odlišné", + "ignoreTimeDifferenceMs": "Časová tolerance (ms)", + "ignoreTimeDifferenceMsHint": "Časy začátku a konce, které se liší o tento počet milisekund nebo méně, se nepovažují za odlišné", + "compareDifferences": "Rozdíly", + "compareTextDifferences": "Rozdíly v textu", + "compareCurrent": "Aktuální", + "compareReference": "Reference", + "compareEditable": "Upravitelné", + "compareEditableHint": "Změny zde provedené se promítnou do titulků po kliknutí na Použít", + "compareReadOnly": "Jen pro čtení", + "compareTakeFromReference": "Převzít text a časování z reference", + "compareInsertFromReference": "Vložit tento řádek do aktuálních titulků", + "compareDeleteFromCurrent": "Smazat tento řádek z aktuálních titulků", + "compareTakeText": "Převzít text", + "compareTakeTiming": "Převzít časování", + "compareEditLine": "Upravit řádek", + "compareEditHint": "Ctrl + Enter pro uložení, Esc pro zrušení", + "compareEdited": "Upraveno", + "compareOnePendingChange": "1 čekající změna", + "compareXPendingChanges": "{0} čekajících změn", + "compareDiscardXChanges": "Zrušit {0} změn provedených v okně Porovnat?", + "compareXChangesApplied": "Porovnání: {0} změn použito", + "compareChangeTextX": "#{0} textu z reference", + "compareChangeTimingX": "#{0} časování z reference", + "compareChangeTextAndTimingX": "#{0} textu a časování z reference", + "compareChangeEditedX": "#{0} upraveno", + "compareChangeInsertedX": "#{0} vloženo", + "compareChangeDeletedX": "#{0} smazáno", + "compareSyncPoint": "Bod synchronizace", + "compareSyncPointHint": "Bod synchronizace – tyto dva řádky se vždy zobrazují jako pár a řádky nad a pod nimi jsou zarovnány samostatně", + "compareSyncPickCurrent": "Bod synchronizace: použít tento aktuální řádek", + "compareSyncPickReference": "Bod synchronizace: použít tento referenční řádek", + "compareSyncRemove": "Odebrat bod synchronizace", + "compareClearXSyncPoints": "Vymazat body synchronizace ({0})", + "compareSyncCurrentPickedX": "Aktuální #{0} vybráno jako bod synchronizace – vyberte odpovídající referenční řádek a klikněte na Synchronizovat", + "compareSyncReferencePickedX": "Referenční #{0} vybráno jako bod synchronizace – vyberte odpovídající aktuální řádek a klikněte na Synchronizovat", + "compareSyncWithCurrentX": "Synchronizovat s aktuálním #{0}", + "compareSyncWithReferenceX": "Synchronizovat s referenčním #{0}", + "compareSyncApply": "Synchronizovat", + "compareSyncPointSetXY": "Bod synchronizace nastaven: aktuální #{0} a referenční #{1} se nyní zobrazují jako pár", + "compareSyncAlreadyPairedXY": "Aktuální #{0} a referenční #{1} se již zobrazují jako pár se stejným začátkem – není co synchronizovat", + "compareSyncPointShiftedXYZW": "Bod synchronizace nastaven: aktuální #{0}–#{1} posunut o {2}, aby začínal s referencí #{3}", + "compareChangeSyncShiftXYZ": "#{0}–#{1} posunut o {2} (bod synchronizace)", + "compareTimingDeltaHint": "O kolik později (+) nebo dříve (−) začíná a končí reference oproti aktuálnímu řádku", "loadXFromFile": "Načíst \"{0}\" ze souboru", + "compareLoadSavedFile": "Načíst uložený soubor", "saveCompareHtmlTitle": "Uložit porovnání souboru HTML", - "pickMatroskaTrackX": "Vybrat stopy Matrosky – {0}", - "pickTransportStreamTrackX": "Vybrat stopy přenosového toku – {0}", + "pickMatroskaTrackX": "Vybrat stopu Matrosky – {0}", + "pickTransportStreamTrackX": "Vybrat stopu přenosového toku – {0}", + "chapters": "Kapitoly", + "dvdVobFilesMissingX": "Chybí soubory VOB (nalezeno {0} %)", "pickMp4TrackX": "Vybrat stopy MP4 – {0}", + "pickMxfTrackX": "Vybrat stopu MXF – {0}", + "pickMpegTrackX": "Vybrat stopu MPEG – {0}", "rosettaProperties": "Vlastnosti Timed Text Rosetta IMSC", "rosettaFontSize": "Velikost písma (výška řádku)", "propertyTimeBase": "Časová osa", @@ -1607,7 +1715,8 @@ "findWhat": "Najít co", "descriptionOptional": "Popis (volitelné)", "invalidRegularExpressionX": "Neplatný regulární výraz: {0}", - "regularExpressionTooSlowX": "Regulární výraz se po {0} sekundách vzdal – pravidlo je přeskočeno, zkuste jednodušší vzor" + "regularExpressionTooSlowX": "Regulární výraz se po {0} sekundách vzdal – pravidlo je přeskočeno, zkuste jednodušší vzor", + "moveToCategory": "Přesunout do kategorie" }, "find": { "searchTextWatermark": "Vyhledat text…", @@ -1687,7 +1796,22 @@ "mismatchWarning": "Nepodobá se originálu – může patřit k jinému řádku", "engineError": "Nebylo možné se připojit k AI modulu: {0}", "playCurrentLineHint": "Přehrát vybraný řádek v přehrávači a pozastavit na jeho konci (také dvojitým kliknutím na řádek)", - "editAfterHint": "Klikněte na text ve vybraném řádku pro úpravu opravy před jejím použitím (Enterem potvrdit, Escapem zrušit)" + "editAfterHint": "Klikněte na text ve vybraném řádku pro úpravu opravy před jejím použitím (Enterem potvrdit, Escapem zrušit)", + "template": "Šablona", + "templatePlaceholder": "Vyberte šablonu pro nahrazení výzvy", + "templateStandard": "Standardní korektura", + "templateSpeechToText": "Očištění převodu řeči na text", + "templateMachineTranslation": "Oprava strojového překladu", + "templateSdh": "SDH / pro neslyšící", + "referenceContext": "Kontext reference", + "referenceContextInfo": "Volitelné. Jména, postavy, slovníček, přehled nebo stylové poznámky – model je považuje za primární zdroj pro jména a výrazy. Odesíláno s každou dávkou.", + "referenceContextPlaceholder": "Jména: Kaito, teta Mei, Dr. Hollis\nVýrazy: Shinkansen, warp jádro\nPřehled: Kaito cestuje do Tokia, aby našel svého pohřešovaného otce.", + "generateContext": "Vygenerovat pomocí AI", + "generateContextHint": "Nechte vybraný AI modul přečíst titulky a navrhnout jména, termíny a přehled – před použitím zkontrolujte výsledek", + "generatingContextPartXOfY": "Čtení části {0} z {1}…", + "generatingContextSynopsis": "Psaní přehledu…", + "replaceContextQuestion": "Nahradit aktuální referenční kontext?", + "noContextGenerated": "AI modul nevrátil žádné použitelné referenční informace." }, "aiAssistant": { "title": "AI asistent", @@ -1733,6 +1857,7 @@ "removeLineBreaksPixelWidth": "Zrušit zalomení titulků, které se vejdou na jeden řádek (šířka v pixelech)", "fixUppercaseIInsideLowercaseWords": "Opravit velké 'i' uprostřed slova psaného malými písmeny (chyba OCR)", "fixDoubleApostrophes": "Opravit dva znaky apostrofu ('') na uvozovky (\")", + "fixMisreadQuotes": "Opravit apostrofy nesprávně rozpoznané jako uvozovky (OCR, angličtina)", "addPeriods": "Přidat tečku na konec řádku, začíná-li další řádek velkým písmenem", "startWithUppercaseLetterAfterParagraph": "Začít velkým písmenem po odstavci", "startWithUppercaseLetterAfterPeriodInsideParagraph": "Začít velkým písmenem po tečce uvnitř odstavce", @@ -1755,6 +1880,7 @@ "fixCommasExample": ",, → ,", "removeLineBreaksExample": "Foo
bar! → Foo bar!", "fixDoubleApostrophesExample": "''Má dvě jednoduché uvozovky'' → \"Má jedny dvojité uvozovky\"", + "fixMisreadQuotesExample": "\"Ahoj'\" → \"Ahoj\"", "addPeriodsExample": "Ahoj, světe → Ahoj, světe.", "startWithUppercaseLetterAfterParagraphExample": "p1: Foobar! || p2: foobar! → p1: Foobar! || p2: Foobar!", "startWithUppercaseLetterAfterPeriodInsideParagraphExample": "Nazdar! jak se máš? → Nazdar! Jak se máš?", @@ -1844,6 +1970,8 @@ "fixMinDurationMs": "Opravit minimální trvání (ms)", "doNotGoPastShotChange": "Nejít za změnu záběru", "fixMaxDurationMs": "Opravit maximální trvání (ms)", + "fixMinDurationFrames": "Opravit minimální trvání (snímků)", + "fixMaxDurationFrames": "Opravit maximální trvání (snímků)", "minimumDurationMilliseconds": "Minimální délka (milisekund):", "maximumDurationMilliseconds": "Maximální délka (milisekund):", "maxDurationShouldBeHigherThanMinDuration": "Maximální trvání by mělo být delší než minimální trvání", @@ -1905,6 +2033,20 @@ "isolateSpeechHint": "Před zarovnáním odstraní hudbu a zvukové efekty, takže nástroj pro zarovnání slyší pouze dialog. Pomáhá u řádků pronášených přes hlasitou hudbu nebo akci, ale s GPU to trvá přibližně stejně dlouho jako samotný zvuk – a bez něj mnohonásobně déle.", "isolatingSpeech": "Izolování řeči… (chvíli to potrvá)", "isolateSpeechFailed": "Nepodařilo se izolovat řeč – místo toho bylo provedeno zarovnání s originálním audiem.", + "checkWithSpeechToText": "Ověřit pomocí převodu řeči na text", + "checkWithSpeechToTextHint": "Provede také přepis zvuku a porovná každý posun s místem, kde byla slova skutečně slyšet. Větší posuny, které řeč potvrdí, se zaškrtnou, a posuny směrem od místa, kde byla věta slyšet, zůstávají nezaškrtnuté, abyste je mohli zkontrolovat. Zobrazuje také, jak velká část každé věty byla slyšet – nízký podíl znamená, že se text liší od toho, co bylo řečeno. Titulky, které jsou nesynchronizované víc, než je maximální posun – ať už jde o konstantní posun, odchylku způsobenou nesouladem snímkové frekvence nebo skoky v místech střihu – se synchronizují nejprve podle zaslechnutých slov. Používá Crisp ASR Parakeet, což u celovečerního filmu trvá GPU několik minut.", + "checkWithSpeechToTextNotAvailableX": "Kontrola pomocí převodu řeči na text není pro {0} k dispozici", + "transcribingToCheck": "Probíhá přepis pro kontrolu zarovnání…", + "speechToTextFailed": "Převod řeči na text selhal – časové kódy s ním nebyly porovnány.", + "speechToTextX": "Převod řeči na text: {0}", + "speechToTextConfirmedXDisputedY": "Řeč: {0} potvrzeno, {1} sporných", + "hardlyHeardX": "Převod řeči na text zachytil pouze {0} % slov v titulcích. Jsou titulky v jazyce, kterým se mluví? Nástroj pro zarovnání nemůže umístit překlad.", + "heard": "Rozpoznáno", + "heardHint": "Jak velká část řádku byla rozpoznána při převodu řeči na text. Nízký podíl znamená, že se text liší od toho, co bylo řečeno – v těchto místech s největší pravděpodobností nástroj pro zarovnání chyboval.", + "statusConfirmedBySpeech": "Potvrzeno řečí", + "statusDisputedBySpeech": "Zaslechnuto na správném místě", + "statusMovedWithSync": "Posunuto se synchronizací", + "syncedFirstXY": "Nejprve synchronizováno: {0} s na začátku, {1} s na konci", "showSpeechOnly": "Zobrazit pouze řeč", "showSpeechOnlyHint": "Zobrazí zarovnanou vlnu z audia po odstranění hudby a zvukových efektů; původní vlna zůstává beze změny pro srovnání. K dispozici po izolaci řeči – zde, nebo pomocí možnosti \"Zobrazit pouze řeč\" ve vlně hlavního okna.", "align": "Zarovnat", @@ -1918,7 +2060,8 @@ "summaryXRetimedYKeptZSkipped": "Přečasováno: {0} · Zachováno: {1} · Bez řeči: {2}", "meanShiftX": "Průměrný posun: {0} ms", "toCheckX": "K ověření: {0}", - "largeMovesToCheckX": "{0} řádků by se o hodně samy posunuly a zůstávají nezaškrtnuté – přehrajte Originál / Zarovnaný a zaškrtněte ty, které jsou správné.", + "largeMovesToCheckX": "{0} řádků by se o hodně samo posunulo a zůstávají nezaškrtnuté – poslechněte si je (F5 přehraje synchronizovaný řádek, Shift + F5 originál) a zaškrtněte ty, které jsou správné.", + "linesToCheckX": "{0} řádků zůstává nezaškrtnutých k ověření – poslechněte si je (F5 přehraje synchronizovaný řádek, Shift + F5 originál) a zaškrtněte ty, které jsou správné.", "changeXOfY": "Změnit {0} z {1}", "previousChange": "Předchozí změna", "nextChange": "Následující změna", @@ -1931,13 +2074,15 @@ "statusNoSpeech": "Bez řeči", "statusShiftTooLarge": "Zachováno – posun příliš velký", "statusFailed": "Zachováno – selhání zarovnání", + "statusAdjustedByHand": "Upraveno ručně", + "alignedWaveformHint": "Přetažením řádku jej posuňte, nebo přetažením jeho okrajů změňte jeho začátek či konec.", + "playLine": "Přehrát řádek", + "undoAdjustment": "Zrušit úpravu", "playPause": "Přehrát / pozastavit", - "playOriginal": "Originál", "playAligned": "Zarovnaný", - "playOriginalHint": "Přehrát vybraný řádek s originálními časovými kódy", "playAlignedHint": "Přehrát vybraný řádek se zarovnanými časovými kódy", "apply": "Použít", - "applyHint": "Odškrtněte řádek, chcete-li zachovat jeho původní časové kódy. Dvojitým kliknutím na řádek jej přehrajete." + "applyHint": "Odškrtněte řádek, chcete-li zachovat jeho původní časové kódy. Dvojitým kliknutím na řádek jej přehrajete. Řádky lze také posouvat nebo měnit jejich velikost ve zarovnané vlně." }, "beautifyTimeCodesProfile": { "title": "Upravit profil zkrášlení časových kódů", @@ -2016,7 +2161,8 @@ "addAlignment": "Přidat zarovnání", "addColor": "Přidat barvu", "deleteLinesWithSpecificActorsOrStyles": "Smazat řádky s herci nebo styly (více položek oddělit čárkou)", - "useSourceStylesIfPossible": "Použít zdrojové styly, je-li to možné", + "useSourceStylesIfPossible": "Použít zdrojové styly, je-li to možné (záhlaví níže se pak použije pouze pro vstupy jiné než ASSA)", + "keepSourceEmbeddedFonts": "Zachovat zdrojová vložená písma (zápatí)", "editStyles": "Upravit styly", "editProperties": "Upravit vlastnosti", "editAttachments": "Upravit přílohy", @@ -2051,6 +2197,18 @@ "addFolderRecursiveDotDotDot": "Přidat složku včetně podsložek…", "selectFolderToConvert": "Vyberte složku se soubory k převodu", "includeSubfolders": "Při přidávání složky zahrnout podsložky", + "videoFilesWhenAddingFolder": "Soubory videa (MKV, MP4, TS…) při přidávání složky", + "videoFilesAsk": "Zeptat se", + "videoFilesInclude": "Přidat jejich titulkové stopy", + "videoFilesSkip": "Přeskočit", + "folderContainsXVideoFiles": "Složka obsahuje {0} souborů videa (MKV, MP4, TS…).\n\nPřidat také jejich vložené titulkové stopy, nebo pouze soubory titulků?", + "addVideoFiles": "Přidat soubory videa", + "skipVideoFiles": "Pouze soubory titulků", + "doNotAskAgainVideoFiles": "Už se znovu neptat (změnit v nastavení Dávkového převodu)", + "settingsSectionOutput": "Výstup", + "settingsSectionAddingFiles": "Přidávání souborů", + "settingsSectionImageBasedSubtitles": "Titulky založené na obrázcích (OCR)", + "settingsSectionOther": "Ostatní", "keepSourceFileTimestamp": "Zachovat datum/čas zdrojového souboru ve výstupních souborech", "preventSleepWhileConverting": "Zabránit počítači v přechodu do režimu spánku během převodu", "scanningFolderX": "Skenování {0}…", @@ -2066,7 +2224,20 @@ "transportStreamOnlyTeletext": "Pouze teletext", "transportStreamGetSizeFromVideo": "Získat velikost z videa…", "twoLetterLanguageCodeUppercase": "Dvoupísmenný jazykový kód (velká písmena)", - "threeLetterLanguageCodeUppercase": "Třípísmenný jazykový kód (velká písmena)" + "threeLetterLanguageCodeUppercase": "Třípísmenný jazykový kód (velká písmena)", + "formatContainsDotDotDot": "Formát obsahuje…", + "extensionIsDotDotDot": "Přípona je…", + "statusIsError": "Stav je chyba", + "forcedTracksOnly": "Pouze vynucené stopy", + "xConvertedYFailedInZ": "{0:#,###,##0} převedeno, {1:#,###,##0} selhalo v {2}", + "xNotProcessed": "{0:#,###,##0} nebylo zpracováno", + "removeUnicodeControlCharactersTitle": "Odebrat řídicí znaky Unicode", + "removeUnicodeControlCharactersInfo": "Odebere z textu neviditelné řídicí znaky Unicode pro směr (LRM, RLM, LRE, RLE, PDF, LRO a RLO) a nahradí pevné mezery běžnými mezerami.", + "preset": "Předvolba", + "savePresetDotDotDot": "Uložit předvolbu…", + "deletePreset": "Smazat předvolbu", + "presetName": "Název předvolby", + "deletePresetX": "Smazat předvolbu \"{0}\"?" }, "changeCasing": { "fixNames": "Opravit jména", @@ -2151,11 +2322,13 @@ }, "mergeLinesWithSameText": { "maxMsBetweenLines": "Max. milisekund mezi řádky", + "maxFramesBetweenLines": "Max. snímků mezi řádky", "includeIncrementingLines": "Zahrnout řádky s přídatným textem", "includeRollUpCaptions": "Zahrnout rolovací titulky" }, "mergeLinesWithSameTimeCodes": { "maxMsDifference": "Max. rozdíl (milisekund)", + "maxFramesDifference": "Max. rozdíl (snímků)", "makeDialog": "Vytvořit dialogy" }, "netflixCheckAndFix": { @@ -2270,7 +2443,14 @@ "xInTopBar": "{0} v horním pruhu", "xInBottomBar": "{0} v dolním pruhu", "noImageSubtitlesLoaded": "Nebyly načteny žádné obrazové titulky", - "imageBasedFormatNotSupported": "Formát obrazových titulků nebyl nalezen / není podporován." + "imageBasedFormatNotSupported": "Formát obrazových titulků nebyl nalezen / není podporován.", + "frameRateDeclaredInFile": "Snímková frekvence deklarovaná v souboru Blu-ray SUP", + "frameRateDetectedFromTimeCodesX": "Snímková frekvence detekovaná z časových kódů souboru Blu-ray SUP (soubor deklaruje {0})", + "frameRateFromVideo": "Snímková frekvence videa", + "frameRateCurrent": "Aktuální snímková frekvence", + "frameRateSetManually": "Ručně nastavená snímková frekvence", + "frameRateUsageInfo": "Exporty do formátů Blu-ray SUP, BDN XML, DOST a FCP se zapíšou s touto snímkovou frekvencí. Nezmění to žádné časy – k tomu použijte funkci \"Změnit snímkovou frekvenci\".", + "useVideoFrameRateXInsteadOfY": "Snímková frekvence videa je {0}, ale titulky jsou nastaveny na {1}.\n\nPoužít snímkovou frekvenci videa ({0})?" }, "removeTextForHearingImpaired": { "interjections": "Citoslovce", @@ -2300,6 +2480,7 @@ "pickFontNameInstalledFonts": "Nainstalovaná písma", "pickFontNameCollectedFonts": "Shromážděná písma", "colorPickerTitle": "Vybrat barvu", + "colorPickerBrightness": "Jas", "filterLayersTitle": "Filtrovat vrstvy pro zobrazení", "filterLayersHideFromWaveform": "Skrýt z vlny/spektrogramu", "filterLayersHideFromSubtitleGrid": "Skrýt z mřížky titulků", @@ -2350,7 +2531,21 @@ "correctWordsX": "Správná slova: {0}", "namesX": "Jména: {0}", "addedToDictionaryX": "Přidaných do slovníku: {0}", - "doNotShowThisAgain": "Tuto zprávu již nezobrazovat" + "doNotShowThisAgain": "Tuto zprávu již nezobrazovat", + "editUseAlwaysList": "Upravit seznam \"Použít vždy\"…", + "useAlwaysListTitle": "Kontrola pravopisu – seznam \"Použít vždy\"", + "useAlwaysListHeader": "Seznam \"Použít vždy\"", + "useAlwaysListDescription": "Slova, která kontrola pravopisu automaticky nahrazuje, uložená pomocí \"Změnit vše\" a \"Použít vždy\". Dvojice se použije pouze v případě, že je slovo napsáno nesprávně.", + "useAlwaysWord": "Nesprávně napsané slovo", + "xPairs": "{0:#,##0} dvojic", + "xUnusedPairs": "{0:#,##0} nikdy nepoužito", + "useAlwaysUnusedHint": "Nikdy nepoužito – toto slovo je napsáno správně (nebo se jedná o jméno či uživatelský výraz), takže kontrola pravopisu jej nikdy neoznačí", + "removeUnused": "Odebrat nepoužívané", + "useAlwaysEmpty": "Pro tento jazyk zatím neexistují žádné dvojice v seznamu \"Použít vždy\"", + "useAlwaysEmptyHint": "Dvojice se přidávají, když v kontrole pravopisu kliknete na \"Změnit vše\" nebo \"Použít vždy\".", + "useAlwaysNoMatch": "Žádné dvojice neodpovídají vyhledávání", + "useAlwaysSameWord": "Náhrada se musí lišit od daného slova.", + "useAlwaysRememberOff": "Možnost \"Zapamatovat seznam 'Použít vždy'\" je v nastaveních vypnutá, takže kontrola pravopisu tento seznam nepoužívá ani neukládá." }, "video": { "burnIn": { @@ -2388,6 +2583,11 @@ "translateToEnglish": "Přeložit do angličtiny", "transcribing": "Přepisování…", "transcribingXOfY": "Přepisování {0} z {1}…", + "linesTranscribedXOfYFailedZ": "Bylo přepsáno {0} z {1} řádků – {2} selhalo nebo neobsahovalo řeč.\n\nU neúspěšných řádků zůstane zachovaný aktuální text. Chcete použít přepsané řádky nebo zkusit neúspěšné znovu (můžete nejprve změnit modul nebo nastavení)?", + "linesTranscribedXOfYCancelled": "Přepis byl zrušen po {0} z {1} řádků.\n\nChcete použít přepsané řádky? U ostatních řádků zůstane zachovaný aktuální text.", + "noLinesTranscribed": "Žádný z {0} řádků se nepodařilo přepsat.\nPodrobnosti najdete v protokolu nástrojů.", + "applyTranscribedLines": "Použít přepsané", + "retryFailedLines": "Zkusit znovu neúspěšné", "isolateSpeech": "Izolovat řeč (pomalé)", "isolateSpeechHint": "Před přepisem odebere hudbu a zvukové efekty. Vede k menšímu počtu vynechaných řádků a lepšímu načasování u audia s hlasitou hudbou, ale s GPU to trvá přibližně stejně dlouho jako samotný zvuk – a bez něj mnohonásobně déle.", "isolatingSpeech": "Izolování řeči…", @@ -2395,6 +2595,9 @@ "detectSpeakers": "Detekce mluvčích", "detectSpeakersHint": "Každý řádek začíná označením mluvčího, například \"(mluvčí 0)\", s využitím diarizace Sortformer od NVIDIA (až 8 mluvčích). Funguje nejlépe s backendy, které rozdělují řádky v místech, kde se mění mluvčí, například Parakeet či Cohere.", "detectSpeakersNeedsNewerCrispAsr": "Funkce \"Detekce mluvčích\" vyžaduje Crisp ASR v{0} nebo novější, ale je nainstalována verze v{1}.\n\nAktualizujte Crisp ASR (tlačítko ozubeného kolečka vedle enginu) nebo vypněte funkci \"Detekce mluvčích\".", + "xNeedsNewerCrispAsr": "{0} vyžaduje Crisp ASR v{1} nebo novější, ale je nainstalována verze v{2}.\n\nStáhnout a nainstalovat aktualizaci nyní?", + "vad": "DHA", + "vadHint": "Detekce hlasové aktivity: přepisuje se pouze řeč, v místech, kde mluvčí udělá pauzu, se záznam ořízne.\nAuto: Silero, a to pouze pro moduly, které to vyžadují (Cohere, Mega, Index-Echo).\nSilero, FireRedVAD, WebRTC: vždy zapnuto, pro všechny moduly Crisp ASR. FireRedVAD je nejpřesnější.\nIndex-Echo vždy používá Silero. V pokročilém nastavení mají přednost parametry --vad, --vad-model nebo --chunk-seconds.", "inputLanguage": "Vstupní jazyk", "advancedWhisperSettings": "Pokročilé parametry převodu řeči na text", "downloadingSpeechToTextEngine": "Stahování modulu řeči na text", @@ -2489,6 +2692,8 @@ "instructions": "Pokyny", "instructionsHint": "Volitelný styl projevu, např. \"Mluv klidným, vřelým hlasem\". Odesíláno pouze modelům, které jej podporují (ne tts-1/tts-1-hd)", "openAiSpeedHint": "Rychlost řeči od 0,25 do 4,0 (1,0 je normální). Ne každý model tuto funkci podporuje", + "outputFormat": "Výstupní formát", + "outputFormatHint": "Formát zvuku vyžádaný od serveru. \"Auto\" vyžádá MP3 a přepne na PCM u modelů, které vrací pouze PCM (např. Gemini TTS)", "regenerateAudio": "Znovu generovat audio", "autoContinuePlaying": "Automatické pokračování přehrávání", "playLine": "Přehrát řádek", @@ -2516,6 +2721,17 @@ "voiceManagerTitle": "TTS – Správce hlasů", "voiceManagerDotDotDot": "Správce hlasů…", "voiceManagerMenuItem": "Správce hlasů TTS", + "speakFromCurrentLineTitle": "Číst od aktuálního řádku", + "speakFromCurrentLineDotDotDot": "Číst od aktuálního řádku…", + "speakFromCurrentLineHint": "Čte řádky nahlas jeden po druhém, počínaje aktuálním řádkem. Stisknutím Escape nebo kliknutím na jiný řádek přehrávání zastavíte.", + "stopSpeaking": "Zastavit čtení", + "speakingLineX": "Čtení řádku {0}… (Stiskněte Escape pro zastavení)", + "playWithSpeechTitle": "Přehrát s hlasovým doprovodem", + "playWithSpeechDotDotDot": "Přehrát s hlasovým doprovodem…", + "playWithSpeechHint": "Přehrává video a přečte každý řádek, jakmile se video k němu dostane. Pozastavit a přeskočit jako obvykle; Stiskněte Escape pro zastavení.", + "lowerVideoVolumeWhileSpeaking": "Snížit hlasitost videa během čtení", + "pauseVideoWhenSpeechRunsLate": "Pozastavit video, když čtení přejde na další řádek", + "waitingForSpeechLineX": "Generování řeči pro řádek {0}… (Stiskněte Escape pro zastavení)", "voiceKindClone": "Naklonovaný hlas", "voiceKindPreset": "Vestavěný", "voiceKindModel": "Model hlasu", @@ -2580,14 +2796,25 @@ "advancedTtsSettings": "Pokročilé nastavení TTS", "advancedTtsAudioProcessing": "Zpracování audia", "advancedTtsOutput": "Výstup", + "advancedTtsVoiceCloning": "Klonování hlasu", + "cleanCloneReferences": "Vyčistit reference pro klonování hlasu (Sidon)", + "cleanCloneReferencesDescription": "Odstraní hudbu, šum a dozvuk ze zvukového záznamu, ze kterého se hlas klonuje: klipy pořízené z videa a nahrávky importované do správce hlasů. Používá model obnovy řeči Sidon v souboru audio.cpp (stažení 940 MB). Klonovaný hlas již nekopíruje pozadí, ale velmi tichá nebo překrývající se řeč může znít tlumeně.", + "cleaningCloneReferencesDotDotDot": "Čištění referencí pro klonování hlasu…", + "downloadSidonTitle": "Stáhnout Sidon?", + "downloadSidonQuestionX": "Čištění referencí pro klonování hlasu vyžaduje model obnovy řeči Sidon ({0}).\n\nStáhnout nyní? Bez něj se reference použijí tak, jak jsou.", "proAudioPostProcessing": "Pro postprodukce audia", "proAudioPostProcessingDescription": "Přidá ekvalizér, šumovou bránu, kompresi, normalizaci hlasitosti (-16 LUFS) a krátké postupné zesilování/ztlumení ke každému klipu.", + "generatingSpeechSegmentXOfY": "Generování řeči: segment {0} z {1}", + "adjustingSpeedSegmentXOfY": "Úprava rychlosti: segment {0} z {1}", + "mergingAudioSegmentXOfY": "Slučování audia: segment {0} z {1}", + "postProcessingSegmentXOfY": "Postprodukce: segment {0} z {1}", "audioDucking": "Potlačení hlasitosti pozadí", "audioDuckingDescription": "Ztlumí originální zvuk videa a namixuje do něj řeč tak, aby původní zvuková stopa zůstala slabě slyšitelná. Použije se pouze v případě, že je řeč přidána do souboru videa.", "removeOriginalSpeech": "Odebrat originální řeč (pomalé)", "removeOriginalSpeechDescription": "Zachová hudbu a zvukové efekty originálního videa, ale odebere z něj řeč a namísto ní namixuje novou řeč. S GPU to trvá přibližně stejně dlouho jako samotné video – a bez něj mnohonásobně déle. Je-li zapnuto potlačení hlasitosti pozadí, jeho hlasitost se vztáhne na hudbu a efekty. Použije se pouze v případě, že je řeč přidána do souboru videa.", "removingOriginalSpeech": "Odebírání originální řeči…", "removeOriginalSpeechFailed": "Originální řeč se nepodařilo odebrat – řeč byla přidána do videa bez ní.", + "videoTruncatedWarning": "Obraz v novém souboru videa se zastaví po {0}, ale původní video má délku {1} – obraz zamrzne, zatímco zvuk bude pokračovat.\n\nSoubor byl zachován. Výstup ffmpegu najdete v souboru error-log.txt ve složce s daty Subtitle Editu.", "originalVolumePercent": "Původní hlasitost v %", "vadSilenceCompression": "Komprese ticha pomocí VAD", "vadSilenceCompressionDescription": "Zkracuje pauzy mezi slovy namísto zrychlení řeči, takže klip zapadne bez ztráty kvality.", @@ -2670,7 +2897,9 @@ "timeCodeFormatColon": "Formát časového kódu:", "shotChangesList": "Seznam změn záběrů", "shotChangesClearQuestion": "Opravdu chcete vymazat všechny změny záběrů?", - "deleteSelectedShotChangeQuestion": "Opravdu chcete smazat vybranou změnu záběru?" + "deleteSelectedShotChangeQuestion": "Opravdu chcete smazat vybranou změnu záběru?", + "shotChanges": "Změny záběrů", + "exportShotChanges": "Exportovat změny záběrů" }, "chapters": { "title": "Kapitoly", @@ -2733,6 +2962,7 @@ "runningOcrXY": "Spuštěno OCR… {0}/{1}", "noLinesFoundTitle": "Nenalezeny žádné titulky", "noLinesFoundMessage": "V oblasti skenování nebyl nalezen žádný text – zkuste upravit oblast skenování, modul nebo minimální jas.", + "ocrStoppedEarlyMessage": "Modul OCR se zastavil před načtením všech snímků, takže dosud nalezené řádky jsou zachovány a zbytek chybí.{0}{0}{1}", "linesFoundX": "Nalezeno {0} řádků", "refiningTimingXY": "Upřesnění časování… {0}/{1}", "fixOcrErrors": "Opravit chyby OCR", @@ -2835,6 +3065,13 @@ "remuxVideoFailed": "Remuxování videa se nezdařilo.", "remuxVideoPleaseSelectBoth": "Vyberte prosím soubory videa i audia.", "remuxVideoAssRequiresMkv": "Pro zachování veškerého formátování a stylů titulků ASS/SSA je nutný kontejner MKV.", + "remuxVideoSccRequiresMov": "Titulky ve formátu Scenarist (.SCC) jsou vloženy jako skryté titulky CEA-608, které vyžadují kontejner MOV nebo MPG. Výstupní formát byl automaticky přepnut na .MOV.", + "remuxVideoSccNotWithWebM": "Skryté titulky ve formátu Scenarist (.SCC) vyžadují kontejner MOV, který však nemůže obsahovat video ve formátu VP9/AV1 ze souboru .WEBM. Odeberte soubor .SCC nebo jej převeďte do jiného formátu titulků.", + "remuxVideoMpgMaxTwoCaptionTracks": "Soubor .MPG obsahuje maximálně dvě stopy skrytých titulků (CC1 a CC3) – odeberte soubory titulků tak, aby zůstala pouze jedna nebo dvě.", + "remuxVideoMccRequiresMpg": "Titulky MacCaption (.MCC) jsou vloženy jako skryté titulky CEA-608/708 do videa MPEG-2, což vyžaduje kontejner MPG. Výstupní formát byl automaticky přepnut na .MPG.", + "remuxVideoMpgReencodeVideoX": "Skryté titulky v souboru .MPG vyžadují video MPEG-2 a video je {0}. Překódovat video do MPEG-2? Potrvá to déle a mírně sníží kvalitu.", + "remuxVideoFileHasNoAudioX": "\"{0}\" neobsahuje audio stopu, proto jej nelze použít jako zdroj zvuku.", + "remuxVideoAddingClosedCaptions": "Přidávání skrytých titulků…", "remuxVideoSelectAudioTrack": "Vybrat stopu audia", "remuxVideoSelectAudioTrackFor": "Vybrat zvukovou stopu pro {0}", "remuxVideoSelectAudioTrackPrompt": "Soubor '{0}' obsahuje {1} zvukových stop. Vyberte prosím zvukovou stopu k remuxování:", @@ -2847,6 +3084,7 @@ "remuxVideoMixAudio": "Namixovat audio do jedné stopy", "remuxVideoVolumePercent": "Hlasitost v %:", "remuxVideoVolumeX": "Hlasitost {0} %", + "remuxVideoFastStart": "Optimalizovat pro streamování na webu (přesunout index na začátek – pomalejší)", "embedSubtitlesDotDotDot": "Přidat/odebrat vložené titulky…", "generateTimeCodes": "Vygenerovat časové kódy", "checkeredImage": "Šachovnice", @@ -2946,6 +3184,10 @@ "reEncodeGeneratingDone": "Generování dokončeno", "reEncodeGeneratedFilesX": "Generování souborů ({0}):", "reEncodeFfmpegParameters": "Parametry ffmpeg", + "analyzingVideoX": "Analyzování videa… {0} % {1}", + "analyzingVideoXofY": "Analyzování videa {0}/{1}… {2} % {3}", + "backgroundImageFileNotSelected": "Soubor obrázku na pozadí nebyl vybrán", + "backgroundImageFileDoesNotExistX": "Soubor obrázku na pozadí neexistuje: {0}", "voices": "Hlasy", "presets": "Předvolby", "talkerX": "Mluvčí {0}", @@ -3026,7 +3268,8 @@ "syncPoints": "Body synchronizace", "pointSync": "Bodová synchronizace", "pointSyncViaOther": "Bodová synchronizace pomocí jiných titulků", - "syncPointCandidateInfo": "Řádky začínající po {0}+ sekundách ticha jsou zvýrazněny – často představují spolehlivé synchronizační body", + "syncPointCandidateAfterInfo": "Řádky, po kterých následuje {0}+ sekund ticha, jsou zvýrazněny – často představují spolehlivé synchronizační body", + "gapAfter": "Mezera po", "adjustAllShortcuts": "Klávesové zkratky:\n\n• Shift + Vlevo/Vpravo: Posun o 10 ms\r\n• Ctrl + Vlevo/Vpravo: Posun o 100 ms\r\n• Alt + Vlevo/Vpravo: Posunout 500 ms", "adjustAllShortcutsFrames": "Klávesové zkratky:\n\n• Shift + Vlevo/Vpravo: Posun o 1 snímek\r\n• Ctrl + Vlevo/Vpravo: Posun o 10 snímků\r\n• Alt + Vlevo/Vpravo: Posun o 1 sekundu", "offsetInSeconds": "Posun v sekundách", @@ -3184,9 +3427,12 @@ "newEmptyDefaultMs": "Výchozí doba trvání nového titulku (ms)", "timeCodeUpDownStepMs": "Posun času nahoru/dolů (ms)", "moveSelectedLinesStepMs": "Krok zkratky pro posun vybraných řádků (ms)", + "moveStartEndStepMs": "Krok zkratky pro posun začátku/konce (ms)", "moveLinesShortenNeighbor": "Posunout řádky: zkrátit předchozí/následující řádek namísto jeho překrytí", "promptBeforeDelete": "Výzva před smazáním", "rememberPositionAndSize": "Zapamatovat si pozici a velikost okna", + "titleBarFullFileName": "Zobrazit úplnou cestu k souboru v záhlaví", + "showRecentFiles": "Zobrazit poslední soubory (při opětovném otevření)", "openLastFileOnStart": "Při spuštění otevřít poslední nedávný soubor", "autoSave": "Automatické ukládání (ukládání otevřeného souboru během úprav)", "autoBackupOn": "Automatická záloha", @@ -3213,6 +3459,8 @@ "waveformDrawGridLines": "Vykreslit mřížku", "waveformUseSkiaRenderer": "Použít experimentální rychlé vykreslování", "waveformCenterVideoPositionAlsoWhenPaused": "Vystředit pozici videa i při pozastavení", + "waveformSelectCurrentSubtitleWhilePaused": "Vybrat aktuální titulek i při pozastavení", + "waveformRememberZoom": "Zapamatovat si přiblížení (vodorovné a svislé)", "waveformShowToolbar": "Zobrazit panel nástrojů", "waveformShowToolbarEdit": "Upravit panel nástrojů", "waveformShowToolbarEditLabel": "Položky panelu nástrojů", @@ -3263,7 +3511,7 @@ "videoControls": "Ovládání videa", "videoControlsEdit": "Upravit ovládání videa…", "videoControlsPositionText": "Pozice/trvání textu", - "videoControlsVideoFileName": "Názvu souboru videa", + "videoControlsVideoFileName": "Název souboru videa", "videoControlsPlayerName": "Název přehrávače videa", "fullscreenHideControls": "Skrýt ovládání videa v režimu celé obrazovky", "showSecondarySubtitleDialog": "Zobrazit dialogové okno se styly při otevření druhého souboru titulků", @@ -3278,6 +3526,7 @@ "adjustAllTimesRememberLineSelectionChoice": "Upravit všechny časy, zapamatovat výběr řádku", "mergeKeepEndTime": "Sloučit řádky: zachovat čas ukončení (povolit překrytí s dalším titulkem)", "mergeKeepEndTimeOnlyAssa": "Sloučit řádky: zachovat čas ukončení pouze pro \"Advanced Sub Station Alpha\"", + "mergeAutoBreak": "Sloučit řádky: automaticky zalomit sloučený text (vypnuto zachová vlastní konce řádků)", "filesAndLogs": "Soubory a protokoly", "showErrorLogFile": "Zobrazit soubor protokolu chyb", "showToolsLogFile": "Zobrazit soubor protokolu nástrojů", @@ -3286,6 +3535,7 @@ "showAssaLayer": "Zobrazit pole vrstev ASSA", "waveformCursorColor": "Barva kurzoru/ukazatele vlny", "waveformShotChangeColor": "Barva změny záběru vlny", + "waveformGridColor": "Barva mřížky vlny", "waveformParagraphLeftColor": "Barva levého okraje vlny", "waveformParagraphRightColor": "Barva pravého okraje vlny", "waveformFancyHighColor": "Barevné zvýraznění vlny", @@ -3313,10 +3563,14 @@ "resetSyntaxColoring": "Obnovit obarvení syntaxe", "resetWaveform": "Obnovit vlnu", "resetRules": "Obnovit pravidla", - "useFrameMode": "Použít režim snímků (hh.mm.ss.ff)", + "timeCodeMode": "Režim časového kódu", + "timeCodeModeTime": "Čas (hh:mm:ss,zzz)", + "timeCodeModeFrames": "Snímky (hh:mm:ss:ff)", + "timeCodeModeFrameNumbers": "Čísla snímků", "textBoxLimitNewLines": "Omezit počet řádků v textovém poli titulků", "mpvOpenGl": "libmpv – OpenGL", "mpvSoftwareRendering": "libmpv – softwarové vykreslování (pomalé)", + "mpvMetalRendering": "libmpv – Metal (experimentální, vyžaduje restart)", "mpvWidRendering": "libmpv – nativní vykreslování pomocí ID okna", "waveFormsAndSpectrogramFoldersContainsX": "Složky \"Vlny\" a \"Spektrogram\" obsahují {0}", "deleteWaveformAndSpectrogramFoldersQuestion": "Mám smazat soubory ve složkách \"Vlny\" a \"Spektrogram\"?", @@ -3390,6 +3644,7 @@ "useFocusedButtonBackgroundColor": "Použít barvu pozadí tlačítka zaměření", "focusedButtonBackgroundColor": "Barva pozadí tlačítka zaměření", "forceCrLfOnSave": "Vynutit CR+LF při ukládání (textové soubory titulků)", + "linuxClipboardUseExternalTool": "Kopírovat text přes xclip/wl-copy (zachovává znaky s diakritikou)", "showFormatLimitWarning": "Upozornit při ukládání, pokud řádky překračují limity formátu (např. SCC 32 znaků/řádek)", "textBoxButtonShowAutoBreak": "Textové pole: zobrazit tlačítko automatické zalomení", "textBoxButtonShowUnbreak": "Textové pole: zobrazit tlačítko zrušit zalomení", @@ -3402,12 +3657,17 @@ "allSettings": "Všechna nastavení", "uiScale": "Škála UI (%)", "fontScale": "Škála písma (%)", + "textSelectionOpacity": "Průhlednost výběru textu (%)", "waveformToolbarItems": "Položky panelu nástrojů vlny", "matchIconColorToDarkTheme": "Přizpůsobit barvu ikon barvě popředí v tmavém motivu", "subtitlePreviewProperties": "Vlastnosti náhledu titulku", "usePositionFromSubtitleFile": "Použít pozici ze souboru titulků (TTML/PAC/EBU STL)", "marginIsPartOfSubtitleArea": "Okraj je součástí oblasti titulků", "textJustify": "Vyrovnat řádky", + "deinterlace": "Odstranění prokládání videa", + "deinterlaceOff": "Vypnuto", + "deinterlaceAuto": "Automaticky (pouze prokládané snímky)", + "deinterlaceAlways": "Vždy", "pixelWidthInfo": "Zelené čáry = limit maximální šířky | Červená oblast = text překračuje limit", "spellCheckEngineHunSpelll": "Hunspell", "spellCheckEngineMsWord": "MS Word", @@ -3436,11 +3696,34 @@ "categoryVideo": "Video", "categorySync": "Synchronizace", "categoryAi": "AI", + "categoryCustom": "Vlastní", + "editCustomShortcut": "Upravit vlastní klávesovou zkratku", + "customShortcutTextBoxKeyHint": "Zde použijte klávesu s Ctrl/Alt – samostatná klávesa by již nezadávala text do textového pole.", + "customShortcutNumberX": "Vlastní klávesová zkratka #{0}", + "customShortcutNumberXY": "Vlastní klávesová zkratka #{0}: {1}", + "customShortcutStep": "Krok", + "customShortcutSteps": "Kroky", + "customShortcutStepRunCommand": "Spustit příkaz", + "customShortcutStepInsertText": "Vložit text", + "customShortcutStepReplace": "Najít a nahradit", + "customShortcutInsertAtCursor": "Na pozici kurzoru", + "customShortcutInsertAtStart": "Na začátku textu", + "customShortcutInsertAtEnd": "Na konci textu", + "customShortcutSummaryRunCommandX": "Spustit: {0}", + "customShortcutSummaryInsertXY": "Vložit \"{0}\" ({1})", + "customShortcutSummaryReplaceXY": "Nahradit \"{0}\" za \"{1}\"", + "customShortcutSummaryReplaceRegexXY": "Nahradit regulární výraz \"{0}\" za \"{1}\"", + "customShortcutPickCommand": "Vyberte prosím příkaz, který chcete spustit.", + "customShortcutEnterText": "Zadejte prosím text, který chcete vložit.", + "customShortcutEnterFind": "Zadejte prosím text, který chcete vyhledat.", + "customShortcutInvalidRegexX": "Neplatný regulární výraz: {0}", + "customShortcutStepsHint": "Kroky se provádějí odshora dolů. Textové kroky se provádějí na vybraných řádcích; příkazy, které otevírají okno, pokračují i po jeho zavření.", "activeIn": "Aktivní v", "activeInEverywhere": "Všude", "generalMergeSelectedLinesAndUnbreak": "Sloučit vybrané řádky a zrušit zalomení", "generalMergeSelectedLinesAndUnbreakCjk": "Sloučit vybrané řádky a zrušit zalomení bez mezery (CJK)", "generalUnbreakNoSpaceCjk": "Zrušit zalomení bez mezery (CJK)", + "generalToggleBreak": "Přepnout zalomení/sjednocení", "generalMergeSelectedLinesBilingual": "Sloučit vybrané řádky dvojjazyčně", "generalToggleTranslationMode": "Přepnout režim překládání", "generalToggleTranslationAndOriginalInPreviews": "Přepnout překlad a originální text v náhledu videa/audia", @@ -3462,6 +3745,7 @@ "fileOpenKeepVideo": "Otevřít (zachovat video)", "fileSave": "Uložit", "fileSaveAs": "Uložit jako", + "fileSaveOriginal": "Uložit originál", "fileCompare": "Porovnat", "fileStatistics": "Statistika", "fileImportPlainText": "Importovat prostý text", @@ -3482,6 +3766,7 @@ "detectKey": "Detekovat klávesu", "control": "Ctrl", "alt": "Alt", + "altGr": "AltGr", "win": "Win", "shift": "Shift", "controlMac": "⌃", @@ -3492,6 +3777,11 @@ "resetShortcutsDetail": "Chcete obnovit výchozí nastavení všech klávesových zkratek?", "togglePlayPause": "Přepnout přehrávání/pozastavení", "toggleLockTimeCodes": "Přepnout zamknutí časových kódů", + "toggleTimeCodeMode": "Přepnout režim časového kódu (čas/snímky/čísla snímků)", + "toggleScreenPrivacy": "Přepnout skrytí obsahu obrazovky (skrýt názvy souborů, poté i texty)", + "screenPrivacyFileNamesHidden": "Ochrana soukromí na obrazovce: skryté názvy souborů", + "screenPrivacyFileNamesAndTextsHidden": "Ochrana soukromí na obrazovce: skryté názvy souborů a texty", + "screenPrivacyOff": "Ochrana soukromí na obrazovce: vypnuto", "duplicateSelectedLines": "Duplikovat vybrané řádky", "sourceView": "Zobrazení zdroje", "addOrEditBookmark": "Přidat nebo upravit záložku", @@ -3540,6 +3830,7 @@ "listViewColumnTextUp": "Sloupec, text nahoru", "listViewColumnTextDown": "Sloupec, text dolů", "autoTranslateSelectedLines": "Automaticky přeložit vybrané řádky…", + "autoTranslateSelectedLinesNoPrompt": "Automaticky přeložit vybrané řádky (bez výzvy, použít poslední překladový modul/jazyky)", "setAssaResolution": "Nastavit rozlišení ASSA (PlayResX/PlayResY)", "setShortcutForX": "Nastavit zkratku pro \"{0}\"", "commandFileNewKeepVideo": "Nový (zachovat video)", @@ -3620,6 +3911,14 @@ "surroundWith": "Obklopit…", "surroundWithXY": "Obklopit {0}/{1}", "surroundWithNumberX": "Obklopit #{0}", + "surroundWithBehavior": "Chování", + "surroundWithBehaviorToggle": "Přepnout (přidat, nebo odebrat, pokud existuje)", + "surroundWithBehaviorAdd": "Přidat (pokaždé)", + "surroundWithBehaviorRemove": "Odebrat (vše)", + "surroundWithBehaviorRemoveOnce": "Odebrat (po jednom)", + "surroundWithWorksOn": "Funguje na", + "surroundWithScopeSelectionOrText": "Výběr, jinak celý text", + "surroundWithScopeEachLine": "Každý řádek", "searchVia": "Vyhledat pomocí", "searchViaX": "Vyhledat pomocí {0}", "searchViaNumberX": "Vyhledat pomocí #{0}", @@ -3647,6 +3946,11 @@ "copySubtitlePathToClipboard": "Kopírovat cestu k titulkům do schránky", "copySubtitleOriginalPathToClipboard": "Kopírovat cestu k originálním titulkům do schránky", "focusTextBox": "Zaměřit se na textové pole", + "focusSubtitleListView": "Zaměřit se na seznamové zobrazení titulků", + "focusWaveform": "Zaměřit se na vlnu", + "focusOriginalTextBox": "Zaměřit se na originální textové pole", + "textBoxGoToStart": "Textové pole, přejít na začátek", + "textBoxGoToEnd": "Textové pole, přejít na konec", "sortByNumber": "Seřadit podle čísla", "sortByStartTime": "Seřadit podle času \"Začátku\"", "sortByEndTime": "Seřadit podle času \"Konce\"", @@ -3686,6 +3990,7 @@ "googleIt": "Vygooglit (vybraný text)", "setActorX": "Nastavit herce: {0}", "setNewActor": "Nastavit nového herce…", + "setStyleX": "Nastavit styl: {0}", "toggleDialogDashes": "Přepnout dialogové pomlčky", "mergeWithLineBeforeAsDialog": "Sloučit s předchozím řádkem jako dialog", "mergeWithLineAfterAsDialog": "Sloučit s následujícím řádkem jako dialog", @@ -3697,12 +4002,16 @@ "moveStartOneFrameForwardKeepGapPrev": "Posunout začátek o jeden snímek vpřed (zachovat mezeru u předchozího, pokud je blízko)", "moveEndOneFrameBackKeepGapNext": "Posunout konec o jeden snímek zpět (zachovat mezeru u dalšího, pokud je blízko)", "moveEndOneFrameForwardKeepGapNext": "Posunout konec o jeden snímek vpřed (zachovat mezeru u dalšího, pokud je blízko)", - "moveSelectedLinesXMsBack": "Posunout vybrané řádky o X ms zpět (X nastaveno v nastavení)", - "moveSelectedLinesXMsForward": "Posunout vybrané řádky o X ms vpřed (X nastaveno v nastavení)", - "moveSelectedLinesAndForwardXMsBack": "Posunout vybrané řádky a všechny následující o X ms zpět (X nastaveno v nastavení)", - "moveSelectedLinesAndForwardXMsForward": "Posunout vybrané řádky a všechny následující o X ms vpřed (X nastaveno v nastavení)", - "moveAllLinesXMsBack": "Posunout všechny řádky o X ms zpět (X nastaveno v nastavení)", - "moveAllLinesXMsForward": "Posunout všechny řádky o X ms vpřed (X nastaveno v nastavení)", + "moveStartXMsBack": "Posunout začátek o X ms zpět (X nastaveno v Nastavení i Vizuální synchronizaci)", + "moveStartXMsForward": "Posunout začátek o X ms vpřed (X nastaveno v Nastavení i Vizuální synchronizaci)", + "moveEndXMsBack": "Posunout konec o X ms zpět (hodnota X je nastavena v Nastavení i Vizuální synchronizaci)", + "moveEndXMsForward": "Posunout konec o X ms dopředu (hodnota X je nastavena v Nastavení i Vizuální synchronizaci)", + "moveSelectedLinesXMsBack": "Posunout vybrané řádky o X ms zpět (X nastaveno v Nastavení)", + "moveSelectedLinesXMsForward": "Posunout vybrané řádky o X ms vpřed (X nastaveno v Nastavení)", + "moveSelectedLinesAndForwardXMsBack": "Posunout vybrané řádky a všechny následující o X ms zpět (X nastaveno v Nastavení)", + "moveSelectedLinesAndForwardXMsForward": "Posunout vybrané řádky a všechny následující o X ms vpřed (X nastaveno v Nastavení)", + "moveAllLinesXMsBack": "Posunout všechny řádky o X ms zpět (X nastaveno v Nastavení)", + "moveAllLinesXMsForward": "Posunout všechny řádky o X ms vpřed (X nastaveno v Nastavení)", "moveSelectedLinesCustomMsBack": "Posunout vybrané řádky, vlastních milisekund ({0:#,###,##0}) zpět, {1}", "moveSelectedLinesCustomMsForward": "Posunout vybrané řádky, vlastních milisekund ({0:#,###,##0}) vpřed, {1}", "moveSelectedLinesAndForwardCustomMsBack": "Posunout vybrané řádky a všechny následující, vlastních milisekund ({0:#,###,##0}) zpět, {1}", @@ -3772,8 +4081,8 @@ "linesToDraw": "Řádky k vykreslení", "currentImage": "Aktuální obrázek", "autoDrawAgain": "Znovu automaticky vykreslit", - "startOcr": "Zahájit OCR", - "pauseOcr": "Pozastavit OCR", + "startOcr": "_Zahájit OCR", + "pauseOcr": "_Pozastavit OCR", "inspectLine": "Zkontrolovat řádek…", "ocrEngine": "Modul OCR", "tesseractEngineMode": "Režim modulu", @@ -3841,7 +4150,7 @@ "ocrImage": "OCR obrázku", "oneColor": "Jedna barva (bílá)", "darknessThreshold": "Práh tmavosti", - "editExportDotDotDot": "Upravit/exportovat…", + "editExportDotDotDot": "_Upravit/exportovat…", "editBinaryOcrDatabase": "Upravit databázi \"binárního porovnání obrázků\"", "binaryImageCompareDatabase": "Databáze \"binárního porovnání obrázků\"", "removeXFromUnknownWordsList": "Odebrat \"{0}\" ze seznamu neznámých slov", @@ -3851,6 +4160,8 @@ "binaryImageCompareInspectImageMatches": "\"Porovnání binárního obrázku\" – Zkontrolovat shody obrázků", "saveBlankTextTitle": "Uložit prázdný text?", "saveBlankTextPrompt": "Uložit prázdný text pro obrázek?", + "drawnLinesDoNotMatchTitle": "Čáry neodpovídají obrázku", + "drawnLinesDoNotMatchPrompt": "Některé nakreslené čáry neodpovídají obrázku (zelené čáry musí být na znaku, červené čáry mimo něj), takže tento znak nebude rozpoznán.\n\nPřesto uložit?", "yesAndNeverAskAgain": "Ano a už se znovu neptat", "importTextFromSubtitleDotDotDot": "Importovat text z titulků…", "importTextFromSubtitleOverwritePrompt": "Některé řádky již obsahují text z OCR. Přepsat stávající text?", @@ -3912,6 +4223,19 @@ "startTraining": "Spustit trénování", "abortTraining": "Přerušit trénování", "trainingOptions": "Možnosti trénování", + "trainNOcrSubtitle": "Vykresluje znaky v nainstalovaných písmech a učí nOCR jejich tvary. Vyberte písma, která vaše titulky používají.", + "xFontsSelected": "{0:#,##0} vybráno", + "subtitleFonts": "Písma titulků", + "emptyDatabase": "Prázdná databáze", + "startFromDatabase": "Začít od", + "startFromDatabaseHint": "Přidejte naučené znaky do kopie existující databáze (doporučeno) nebo začněte s prázdnou databází", + "fontSizes": "Velikosti písma", + "fontSizesHint": "Velikosti písma v pixelech, ve kterých se mají vykreslovat, oddělené čárkami, např. \"30, 40\"", + "numberOfLineSegmentsHint": "Zkontrolované řádky na znak – vyšší počet znamená přísnější kontrolu, ale pomalejší zpracování", + "enterDatabaseName": "Zadejte název databáze", + "trainingFontXSizeYCharacterZ": "Trénování \"{0}\" při {1} px: {2}", + "xLearned": "{0:#,##0} naučeno", + "xSkipped": "{0:#,##0} přeskočeno", "charactersToTrain": "Znaky k trénování", "letterCombinationsToTrain": "Kombinace písmen, které mohou být rozděleny do jednoho obrázku", "importCharactersFromSubtitleFile": "Importovat znaky ze souboru titulků…", @@ -3938,13 +4262,90 @@ "drawZoomOut": "Oddálit (Ctrl + -)", "drawResetView": "Obnovit zobrazení (Ctrl + 0)", "drawToggleGrid": "Přepnout mřížku (Ctrl + G)", + "drawTogglePreview": "Přepnout náhled (F9)", + "drawPreviewFailed": "Náhled se nezdařil – je vyžadován ffmpeg s libass (filtr titulků)", "drawCopyToClipboard": "Kopírovat do schránky (Ctrl + C)", "drawShapes": "Tvary", "drawSelectedPoint": "Vybraný bod", "drawSelectedShape": "Vybraný tvar", "drawSelectedLayer": "Vybraná vrstva", "drawToolX": "Nástroj: {0}", - "drawHelpText": "Kliknutím přidáte body • Enter/F8 pro uzavření tvaru • Shift + přetažení pro posun • Ctrl + rolování pro přiblížení", + "drawHelpText": "Kliknutím přidejte body • Pravým tlačítkem myši zobrazíte možnosti • Nástroj výběru: tažením za úchyty měňte měřítko/otáčejte (Shift = zachovat poměr stran/15°) • Enter/F8 pro uzavření tvaru • Shift + tažení nebo prostřední tlačítko myši pro posun • Ctrl + kolečko pro přiblížení/oddálení", + "drawMoveToLayer": "Přesunout na vrstvu", + "drawFlipHorizontal": "Otočit vodorovně", + "drawFlipVertical": "Otočit svisle", + "drawHideShape": "Skrýt tvar", + "drawShowShape": "Zobrazit tvar", + "drawHideLayer": "Skrýt vrstvu", + "drawShowLayer": "Zobrazit vrstvu", + "drawDeleteLayer": "Smazat vrstvu", + "drawDeletePoint": "Smazat bod", + "drawCancelDrawing": "Zrušit kreslení", + "drawLayerX": "Vrstva {0}", + "drawImportSvg": "Importovat obrázek SVG…", + "drawRotateClockwise": "Otočit o 90° ve směru hodinových ručiček", + "drawConvertToCurve": "Převést úsečku na křivku", + "drawBackground": "Pozadí", + "drawPosition": "Pozice", + "drawZoom": "Přiblížení", + "drawShapeTool": "Nástroj pro tvary", + "drawColorPickerTool": "Vybrat barvu z tvaru nebo pozadí", + "drawShapeLibrary": "Knihovna tvarů", + "drawShapeLibraryHint": "Přetažením na plátně umístěte tvar, nebo kliknutím použijte výchozí velikost. Shift zachová proporce.", + "drawAddToLibrary": "Přidat do knihovny tvarů…", + "drawRemoveFromLibrary": "Odebrat z knihovny tvarů", + "drawShapeName": "Název tvaru", + "drawCategorySpeechBubbles": "Bubliny", + "drawCategoryArrows": "Šipky", + "drawCategoryBasicShapes": "Základní tvary", + "drawCategorySymbols": "Symboly", + "drawCategoryMyShapes": "Moje tvary", + "drawShapeSpeechBubble": "Bublina", + "drawShapeSpeechBubbleRight": "Bublina, ocas vpravo", + "drawShapeSpeechBubbleUp": "Bublina, ocas nahoru", + "drawShapeOvalBubble": "Oválná bublina", + "drawShapeBoxBubble": "Čtvercová bublina", + "drawShapeThoughtBubble": "Myšlenková bublina", + "drawShapeShoutBubble": "Bublina výkřiku", + "drawShapeCaptionBox": "Rámeček s popiskem", + "drawShapeArrow": "Šipka", + "drawShapeDoubleArrow": "Dvojitá šipka", + "drawShapeChevron": "Háček", + "drawShapeCurvedArrow": "Zakřivená šipka", + "drawShapeRoundedRectangle": "Zaoblený obdélník", + "drawShapeTriangle": "Trojúhelník", + "drawShapeDiamond": "Kosočtverec", + "drawShapePentagon": "Pětiúhelník", + "drawShapeHexagon": "Šestiúhelník", + "drawShapeOctagon": "Osmiúhelník", + "drawShapeStar": "Hvězda", + "drawShapeRing": "Kruh", + "drawShapePlus": "Plus", + "drawShapeBanner": "Poutač", + "drawShapeHeart": "Srdce", + "drawShapeCheck": "Fajfka", + "drawShapeMusicNote": "Nota", + "drawShapeMusicNotes": "Noty", + "drawShapeLightning": "Blesk", + "drawShapeCloud": "Mrak", + "drawShapeMoon": "Měsíc", + "drawShapeBadge": "Odznak", + "drawSize": "Velikost", + "drawLayer": "Vrstva", + "drawBackgroundVideoFrame": "Snímek videa (aktuální pozice)", + "drawBackgroundVideoFrameAt": "Snímek videa v čase…", + "drawBackgroundImage": "Soubor obrázku…", + "drawBackgroundNone": "Bez pozadí", + "drawBackgroundStretch": "Roztáhnout obrázek na plátno", + "drawBackgroundOpacity": "Průhlednost pozadí", + "drawBackgroundVideoFrameFailed": "Nelze získat snímek videa na {0}.", + "drawImages": "Obrázky", + "drawConvertToLine": "Převést úsečku na čáru", + "drawConvertShapeToCurves": "Převést všechny čáry na křivky", + "drawConvertShapeToLines": "Převést všechny křivky na čáry", + "drawRotateCounterClockwise": "Otočit o 90° proti směru hodinových ručiček", + "drawSvgImages": "SVG obrázky", + "drawSvgNoShapes": "V souboru SVG nebyly nalezeny žádné vyplněné tvary ani tahy.", "progressBarTitle": "Ukazatel průběhu ASSA", "progressBarSettings": "Ukazatel průběhu", "mouseOverColor": "Barva při najetí myší", @@ -4041,13 +4442,26 @@ "graphics": "Grafika", "copyToStorageStyles": "Zkopírovat do úložiště stylů", "copyToFileStyles": "Zkopírovat do souboru stylů", + "styleXAlreadyExistsInStorage": "V úložišti již existuje styl s názvem \"{0}\".\n\nPřepsat, nebo ponechat oba (kopie dostane nový název)?", + "styleXAlreadyExistsInFile": "V souboru již existuje styl s názvem \"{0}\".\n\nPřepsat, nebo ponechat oba (kopie dostane nový název)?", + "styleNameCannotBeEmpty": "Název stylu nemůže zůstat prázdný.", + "styleNameXIsUsedMoreThanOnce": "Název stylu \"{0}\" je použit u více než jednoho stylu v souboru.", + "overwrite": "Přepsat", + "keepBoth": "Ponechat oba", + "doThisForAllConflictingStyles": "Provést u všech konfliktních stylů", "setStyleAsDefault": "Nastavit styl jako výchozí", "takeUsagesFromDotDotDot": "Vzít použití z…", "replaceStyleWithDotDotDot": "Nahradit styl…", "noAttachmentsFound": "Ve vybraném souboru ASSA nebyly nalezeny žádné přílohy.", "deleteStyleQuestion": "Smazat styl?", "deleteStylesQuestion": "Smazat styly?", + "deleteXStylesQuestion": "Chcete smazat {0} stylů?", + "deleteXStylesFromStorageQuestion": "Chcete smazat {0} stylů z úložiště?", + "deleteStyleXFromStorageQuestion": "Chcete smazat styl \"{0}\" z úložiště?", + "deleteStyleXFromFileQuestion": "Chcete smazat styl \"{0}\" z aktuálního souboru?", "openStyleImportFile": "Otevřít soubor titulků, ze kterého se budou styly importovat", + "styleImportFiles": "Soubory stylů (.ASS, .STY, kategorie Subtitle Edit 4 .template)", + "ssaStyleImportFiles": "Soubory stylů (.SSA, .ASS, .STY)", "primary": "Primární", "secondary": "Sekundární", "applyOverrideTags": "Použít přepisové značky", From 4277ca974625aec4485d8e87f0dc5fb9e2706bbd Mon Sep 17 00:00:00 2001 From: bovirus <1262554+bovirus@users.noreply.github.com> Date: Mon, 12 Oct 2026 09:01:59 +0200 Subject: [PATCH 086/306] Update Italian language --- src/ui/Assets/Languages/Italian.json | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/ui/Assets/Languages/Italian.json b/src/ui/Assets/Languages/Italian.json index 31e54582699..d7cab6e1e29 100644 --- a/src/ui/Assets/Languages/Italian.json +++ b/src/ui/Assets/Languages/Italian.json @@ -1,7 +1,7 @@ { "title": "SubtitleEdit", - "version": "v. 5.3.0-rc2", - "translatedBy": "v. 11.10.2026 di bovirus e GitGianluc", + "version": "v. 5.3.0-rc3", + "translatedBy": "v. 12.10.2026 di bovirus e GitGianluc", "cultureName": "it-IT", "general": { "abort": "Annulla", @@ -18,7 +18,7 @@ "stylePickerFilterHint": "Digita per filtrare o digita un nuovo nome stile", "stylePickerKeysHint": "Per impostare lo stile su tutte le linee selezionate premi un numero (1-9, 0).", "stylePickerNewStyleX": "Nuovo stile '{0}'", - "stylePickerNewStyleInfo": "Premi Invio per aggiungere questo stile con le impostazioni predefinite e impostarlo sulle linee selezionate. Per modificare in un secondo momento la font, i colori e la posizione usa 'Gestisci stili...'.", + "stylePickerNewStyleInfo": "Per aggiungere questo stile con le impostazioni predefinite e impostarlo sulle linee selezionate premi 'Invio'. Per modificare in un secondo momento la font, i colori e la posizione usa 'Gestisci stili...'.", "stylePickerCurrentStyleX": "Stile attuale: {0}", "stylePickerLinesUsingStyleX": "Righe che usano questo stile: {0}", "stylePickerMarginsXYZ": "Sinistra {0}, destra {1}, verticale {2}", @@ -1345,7 +1345,8 @@ "forcedAlignerIsolatingSpeech": "Parlato isolato... {0}", "forcedAlignerIsolatingSpeechFailed": "Impossibile isolare il parlato: i punti finali sono stati invece impostati dal tempo di lettura.", "forcedAlignerModel": "Modello allineamento", - "forcedAlignerDownloadEngine": "Download/aggiornamento motore..." + "forcedAlignerDownloadEngine": "Download/aggiornamento motore...", + "forcedAlignerEstimatedXOfY": "Non è stato possibile allineare '{0}' righe su '{1}' che sono state posizionate in base al tempo di lettura: i relativi timecode sono solo stimati. Lo script è probabilmente più lungo di quanto detto nel video." }, "export": { "exportImagesProfiles": "Esporta profili immagini", @@ -2814,6 +2815,10 @@ "removeOriginalSpeechDescription": "Mantieni la musica e gli effetti sonori del video originale ma rimuove il parlato e vi aggiunge il nuovo parlato. Impiega con una GPU circa lo stesso tempo del video stesso e molte volte di più senza GPU. Con l'audio ducking attivo, il suo volume si applica alla musica e agli effetti. Si applica solo quando il parlato viene aggiunto al file video.", "removingOriginalSpeech": "Rimozione parlato originale...", "removeOriginalSpeechFailed": "Impossibile rimuovere il parlato originale: il parlato è stato aggiunto al video senza di esso.", + "speechRemovalModel": "Modello", + "speechRemovalModelDescription": "'Migliore qualità' mantiene la maggior parte della musica e degli effetti sonori. 'Veloce' è circa tre volte più veloce, con un po' più di musica persa.", + "speechRemovalBestQuality": "Migliore qualità (Mel-Band RoFormer)", + "speechRemovalFast": "Veloce (HTDemucs)", "videoTruncatedWarning": "L'immagine nel nuovo file video si interrompe dopo '{0}', ma il video originale è lungo '{1}': l'immagine si bloccherà mentre l'audio continua.\r\nIl file è stato conservato.\r\nPer l'output di ffmpeg vedi 'error-log.txt' nella cartella dati di Subtitle Edit.", "originalVolumePercent": "% volume originale", "vadSilenceCompression": "Compressione silenzio VAD", From ea1bac9c8206fc7b82a9c65e796a11912d9f3798 Mon Sep 17 00:00:00 2001 From: niksedk Date: Mon, 12 Oct 2026 09:15:24 +0200 Subject: [PATCH 087/306] Add Whisper-VAD to the Crisp ASR VAD choices (#15958) cstr/whisper-vad-encdec-asmr-GGUF (MIT, 25 MB Q4_K) is a Whisper-base encoder + frame classifier VAD. CrispASR (pinned v0.8.42) already loads it when the --vad-model path contains "whisper", "vad" and ".gguf", and lowers its threshold to 0.30 unless -vt is given, so it only needs a combo entry with a download. Co-Authored-By: Claude Opus 5.5 --- docs/features/whats-new-in-se5.md | 2 +- src/ui/Assets/Languages/English.json | 2 +- .../Video/SpeechToText/Engines/CrispAsrVadModel.cs | 11 +++++++++++ .../Config/Language/Video/LanguageAudioToText.cs | 2 +- .../Video/SpeechToText/CrispAsrVadModelTests.cs | 8 ++++++++ 5 files changed, 22 insertions(+), 3 deletions(-) diff --git a/docs/features/whats-new-in-se5.md b/docs/features/whats-new-in-se5.md index 873cede61db..f8d0fdc7787 100644 --- a/docs/features/whats-new-in-se5.md +++ b/docs/features/whats-new-in-se5.md @@ -43,7 +43,7 @@ The first section lists the highlights of 5.3. The sections after it describe wh ### Speech to text -- **Detect speakers** (Sortformer diarization), a choice of VAD (Silero, FireRedVAD, WebRTC) and **Isolate speech** for Crisp ASR (see [Isolate speech](speech-to-text.md#isolate-speech-crisp-asr)). +- **Detect speakers** (Sortformer diarization), a choice of VAD (Silero, FireRedVAD, Whisper-VAD, WebRTC) and **Isolate speech** for Crisp ASR (see [Isolate speech](speech-to-text.md#isolate-speech-crisp-asr)). - New Crisp ASR models: Index-Echo (Chinese speech to English/Japanese/Spanish text), Parakeet Ultra, Phonon-2, ReazonSpeech and Voxtral F16. - A *Multilingual* option for Purfview Faster-Whisper XXL, a new standalone WhisperX build with live output, and vLLM support in the OpenAI-compatible engine. - Transcribing selected lines keeps the lines that worked and offers to retry the failed ones; the computer no longer sleeps while it runs. diff --git a/src/ui/Assets/Languages/English.json b/src/ui/Assets/Languages/English.json index ebd05b6d095..f34611422bf 100644 --- a/src/ui/Assets/Languages/English.json +++ b/src/ui/Assets/Languages/English.json @@ -2598,7 +2598,7 @@ "detectSpeakersNeedsNewerCrispAsr": "\"Detect speakers\" needs Crisp ASR v{0} or newer, but v{1} is installed.\n\nUpdate Crisp ASR (the gear button next to the engine) or turn off \"Detect speakers\".", "xNeedsNewerCrispAsr": "{0} needs Crisp ASR v{1} or newer, but v{2} is installed.\n\nDownload and install the update now?", "vad": "VAD", - "vadHint": "Voice activity detection: only the speech is transcribed, cut where the speaker pauses.\nAuto: Silero, and only for the engines that need it (Cohere, Mega, Index-Echo).\nSilero, FireRedVAD, WebRTC: always on, for every Crisp ASR engine. FireRedVAD is the most accurate.\nIndex-Echo always uses Silero. --vad, --vad-model or --chunk-seconds in the advanced settings take precedence.", + "vadHint": "Voice activity detection: only the speech is transcribed, cut where the speaker pauses.\nAuto: Silero, and only for the engines that need it (Cohere, Mega, Index-Echo).\nSilero, FireRedVAD, Whisper-VAD, WebRTC: always on, for every Crisp ASR engine. FireRedVAD is the most accurate, Whisper-VAD gives the fewest, cleanest cuts but is the slowest.\nIndex-Echo always uses Silero. --vad, --vad-model or --chunk-seconds in the advanced settings take precedence.", "inputLanguage": "Input language", "advancedWhisperSettings": "Advanced speech-to-text parameters", "downloadingSpeechToTextEngine": "Downloading speech-to-text engine", diff --git a/src/ui/Features/Video/SpeechToText/Engines/CrispAsrVadModel.cs b/src/ui/Features/Video/SpeechToText/Engines/CrispAsrVadModel.cs index d3d4e1065a3..146b6f8aa35 100644 --- a/src/ui/Features/Video/SpeechToText/Engines/CrispAsrVadModel.cs +++ b/src/ui/Features/Video/SpeechToText/Engines/CrispAsrVadModel.cs @@ -42,6 +42,8 @@ public sealed class CrispAsrVadOption /// each one on its own, so the choice of VAD does not depend on the model. It tells the detectors /// apart by file name - a "--vad-model" path with "firered" and "vad" in it is FireRedVAD - and /// "webrtc" needs no file at all. MarbleNet is left out: on a test clip it dropped nearly all speech. +/// Whisper-VAD (#15958) is a Whisper-base encoder with a frame classifier, picked by "whisper", +/// "vad" and ".gguf" in the path; CrispASR lowers its threshold to 0.30 unless "-vt" is given. /// Index-Echo is the exception: it loads the VAD model itself, as Silero, to place its own windows. /// TEN VAD is not in CrispASR (upstream PLAN.md #106, held back by its licence). /// @@ -51,6 +53,7 @@ public static class CrispAsrVadModel public const string Automatic = "auto"; public const string Silero = "silero"; public const string FireRed = "firered"; + public const string WhisperVad = "whisper-vad"; public const string WebRtc = "webrtc"; public static IReadOnlyList Options { get; } = new List @@ -65,6 +68,14 @@ public static class CrispAsrVadModel Url = "https://huggingface.co/cstr/firered-vad-GGUF/resolve/main/firered-vad.gguf", Size = "2.4 MB", }, + new() + { + Choice = WhisperVad, + DisplayName = "Whisper-VAD", + FileName = "whisper-vad-asmr-q4_k.gguf", + Url = "https://huggingface.co/cstr/whisper-vad-encdec-asmr-GGUF/resolve/main/whisper-vad-asmr-q4_k.gguf", + Size = "25 MB", + }, new() { Choice = WebRtc, DisplayName = "WebRTC" }, }; diff --git a/src/ui/Logic/Config/Language/Video/LanguageAudioToText.cs b/src/ui/Logic/Config/Language/Video/LanguageAudioToText.cs index b92f3e54f07..48b7e0f4e2b 100644 --- a/src/ui/Logic/Config/Language/Video/LanguageAudioToText.cs +++ b/src/ui/Logic/Config/Language/Video/LanguageAudioToText.cs @@ -102,7 +102,7 @@ public LanguageAudioToText() DetectSpeakersNeedsNewerCrispAsr = "\"Detect speakers\" needs Crisp ASR v{0} or newer, but v{1} is installed.\n\nUpdate Crisp ASR (the gear button next to the engine) or turn off \"Detect speakers\"."; XNeedsNewerCrispAsr = "{0} needs Crisp ASR v{1} or newer, but v{2} is installed.\n\nDownload and install the update now?"; Vad = "VAD"; - VadHint = "Voice activity detection: only the speech is transcribed, cut where the speaker pauses.\nAuto: Silero, and only for the engines that need it (Cohere, Mega, Index-Echo).\nSilero, FireRedVAD, WebRTC: always on, for every Crisp ASR engine. FireRedVAD is the most accurate.\nIndex-Echo always uses Silero. --vad, --vad-model or --chunk-seconds in the advanced settings take precedence."; + VadHint = "Voice activity detection: only the speech is transcribed, cut where the speaker pauses.\nAuto: Silero, and only for the engines that need it (Cohere, Mega, Index-Echo).\nSilero, FireRedVAD, Whisper-VAD, WebRTC: always on, for every Crisp ASR engine. FireRedVAD is the most accurate, Whisper-VAD gives the fewest, cleanest cuts but is the slowest.\nIndex-Echo always uses Silero. --vad, --vad-model or --chunk-seconds in the advanced settings take precedence."; InputLanguage = "Input language"; AdvancedWhisperSettings = "Advanced speech-to-text parameters"; DownloadingSpeechToTextEngine = "Downloading speech-to-text engine"; diff --git a/tests/UI/Features/Video/SpeechToText/CrispAsrVadModelTests.cs b/tests/UI/Features/Video/SpeechToText/CrispAsrVadModelTests.cs index f7f0cbbda1c..73a9c1cc7c4 100644 --- a/tests/UI/Features/Video/SpeechToText/CrispAsrVadModelTests.cs +++ b/tests/UI/Features/Video/SpeechToText/CrispAsrVadModelTests.cs @@ -41,6 +41,14 @@ public void ModelFileNamesAreTheOnesCrispAsrRecognises() var fireRed = CrispAsrVadModel.Get(CrispAsrVadModel.FireRed).FileName!; Assert.Contains("firered", fireRed); Assert.Contains("vad", fireRed); + + // Checked after FireRedVAD and MarbleNet, so it must not look like either (#15958). + var whisperVad = CrispAsrVadModel.Get(CrispAsrVadModel.WhisperVad).FileName!; + Assert.Contains("whisper", whisperVad); + Assert.Contains("vad", whisperVad); + Assert.EndsWith(".gguf", whisperVad); + Assert.DoesNotContain("firered", whisperVad); + Assert.DoesNotContain("marblenet", whisperVad); } [Fact] From b53d9863825a9b14c8085503c54a70a3d0203063 Mon Sep 17 00:00:00 2001 From: niksedk Date: Mon, 12 Oct 2026 09:51:11 +0200 Subject: [PATCH 088/306] Bump CrispASR to v0.8.43 All 14 archives and their executables move together (28 hash lists); archive digests checked against the release API. Inner folder names and the Windows CUDA zips' bundled runtime DLLs are unchanged. No CLI flag or backend was removed (help and --list-backends diffed against v0.8.42); Parakeet gives identical output on both versions. Download sizes are unchanged. v0.8.43 fixes corrupted UTF-8 (e.g. Chinese) in the post-decode repetition cleanup on macOS and the HTDemucs q8_0 Metal abort (CrispStrobe/CrispASR#537); SE keeps using the f16 HTDemucs model for now. Fixes #15961 Co-Authored-By: Claude Opus 5.5 --- .../Engines/CrispAsrEngineBase.cs | 2 +- .../SpeechToText/Engines/HtDemucsModel.cs | 2 +- .../SpeechToText/SpeechToTextViewModel.cs | 2 +- .../Video/TextToSpeech/TtsVoiceInstaller.cs | 4 +- .../Logic/Download/CrispAsrDownloadService.cs | 28 +++---- src/ui/Logic/Download/DownloadHashManager.cs | 84 ++++++++++++------- 6 files changed, 75 insertions(+), 47 deletions(-) diff --git a/src/ui/Features/Video/SpeechToText/Engines/CrispAsrEngineBase.cs b/src/ui/Features/Video/SpeechToText/Engines/CrispAsrEngineBase.cs index 17d294ec7b3..3b23c0cc7e1 100644 --- a/src/ui/Features/Video/SpeechToText/Engines/CrispAsrEngineBase.cs +++ b/src/ui/Features/Video/SpeechToText/Engines/CrispAsrEngineBase.cs @@ -28,7 +28,7 @@ public abstract class CrispAsrEngineBase : ICrispAsrEngine /// CrispASR ships several builds per platform whose sizes differ by two orders of magnitude, /// and the variant is picked at download time rather than now — so Windows and Linux get a /// range rather than a single number that would either understate the GPU bundles or scare - /// users off the CPU one. Figures are the v0.8.42 release assets — + /// users off the CPU one. Figures are the v0.8.43 release assets — /// /// holds the pin; they drift with every release, so treat them as indicative. ///
diff --git a/src/ui/Features/Video/SpeechToText/Engines/HtDemucsModel.cs b/src/ui/Features/Video/SpeechToText/Engines/HtDemucsModel.cs index 4cc464db5ef..36f9749f1a0 100644 --- a/src/ui/Features/Video/SpeechToText/Engines/HtDemucsModel.cs +++ b/src/ui/Features/Video/SpeechToText/Engines/HtDemucsModel.cs @@ -8,7 +8,7 @@ namespace Nikse.SubtitleEdit.Features.Video.SpeechToText.Engines; /// GGUF), about three times faster but with a little more of the music lost. ///
/// -/// f16 only: the q8_0 file aborts CrispASR on Metal ("unsupported op 'CONT'") up to v0.8.42, see +/// f16 only: the q8_0 file aborts CrispASR on Metal ("unsupported op 'CONT'") up to v0.8.42 (fixed in v0.8.43), see /// CrispStrobe/CrispASR#537. /// public static class HtDemucsModel diff --git a/src/ui/Features/Video/SpeechToText/SpeechToTextViewModel.cs b/src/ui/Features/Video/SpeechToText/SpeechToTextViewModel.cs index 1c566ffdd4d..848bb2b3c95 100644 --- a/src/ui/Features/Video/SpeechToText/SpeechToTextViewModel.cs +++ b/src/ui/Features/Video/SpeechToText/SpeechToTextViewModel.cs @@ -1069,7 +1069,7 @@ internal static bool ShouldForceCrispAsrVad(ISpeechToTextEngine engine, string? /// was the crispasr v0.8.29 GPU packages, built with AVX-512 against a CI runner that had it /// (CrispASR #374) - every CPU without AVX-512 got this on the CUDA/Vulkan build while the CPU /// build ran fine, so naming the installed package is most of the answer. That build flaw is - /// fixed from v0.8.30 (SE now pins v0.8.42), but the message still earns its keep: a pre-AVX2 CPU + /// fixed from v0.8.30 (SE now pins v0.8.43), but the message still earns its keep: a pre-AVX2 CPU /// hits the same silent death on the AVX2 CPU package, and an install predating the pin bump /// keeps the broken GPU binary until the user downloads the engine again. ///
diff --git a/src/ui/Features/Video/TextToSpeech/TtsVoiceInstaller.cs b/src/ui/Features/Video/TextToSpeech/TtsVoiceInstaller.cs index 377782be8ce..36815436892 100644 --- a/src/ui/Features/Video/TextToSpeech/TtsVoiceInstaller.cs +++ b/src/ui/Features/Video/TextToSpeech/TtsVoiceInstaller.cs @@ -188,7 +188,7 @@ public static Task EnsureCrispAsrForOmniVoice(Window? window, IWindowServi /// /// Ensures the CrispASR runtime that MOSS-TTS (CrispASR) runs on is installed. - /// The moss-tts backend ships in CrispASR v0.8.13 and newer (SE pins v0.8.42). + /// The moss-tts backend ships in CrispASR v0.8.13 and newer (SE pins v0.8.43). /// public static Task EnsureCrispAsrForMossTts(Window? window, IWindowService windowService, bool forceRedownload) => EnsureCrispAsrAsync(window, windowService, forceRedownload, @@ -198,7 +198,7 @@ public static Task EnsureCrispAsrForMossTts(Window? window, IWindowService /// /// Ensures the CrispASR runtime that dots.tts (CrispASR) runs on is installed. - /// The dots-tts backend ships in CrispASR v0.8.25 and newer (SE pins v0.8.42); the version + /// The dots-tts backend ships in CrispASR v0.8.25 and newer (SE pins v0.8.43); the version /// note names that floor because older builds have no dots-tts backend at all and abort on /// the unknown --backend value. /// diff --git a/src/ui/Logic/Download/CrispAsrDownloadService.cs b/src/ui/Logic/Download/CrispAsrDownloadService.cs index eabacb2350b..bff4aa395a8 100644 --- a/src/ui/Logic/Download/CrispAsrDownloadService.cs +++ b/src/ui/Logic/Download/CrispAsrDownloadService.cs @@ -26,18 +26,18 @@ public class CrispAsrDownloadService : ICrispAsrDownloadService { private readonly HttpClient _httpClient; - private const string WindowsCudaUrl = "https://github.com/CrispStrobe/CrispASR/releases/download/v0.8.42/crispasr-windows-x86_64-cuda.zip"; + private const string WindowsCudaUrl = "https://github.com/CrispStrobe/CrispASR/releases/download/v0.8.43/crispasr-windows-x86_64-cuda.zip"; /// /// The CUDA 13 build, added upstream in v0.8.31 next to the CUDA 12 one rather than /// replacing it. Offered as its own option because CUDA 13 needs a newer NVIDIA driver than /// CUDA 12 - repointing at it would have broken everyone still /// on an older driver. Mirrors the Linux pair, which has had both since v0.8.30. /// - private const string WindowsCuda13Url = "https://github.com/CrispStrobe/CrispASR/releases/download/v0.8.42/crispasr-windows-x86_64-cuda13.zip"; - private const string WindowsVulkanUrl = "https://github.com/CrispStrobe/CrispASR/releases/download/v0.8.42/crispasr-windows-x86_64-vulkan.zip"; - private const string WindowsCpuUrl = "https://github.com/CrispStrobe/CrispASR/releases/download/v0.8.42/crispasr-windows-x86_64-cpu.zip"; - private const string WindowsCpuLegacyUrl = "https://github.com/CrispStrobe/CrispASR/releases/download/v0.8.42/crispasr-windows-x86_64-cpu-legacy.zip"; - private const string MacUrl = "https://github.com/CrispStrobe/CrispASR/releases/download/v0.8.42/crispasr-macos.tar.gz"; + private const string WindowsCuda13Url = "https://github.com/CrispStrobe/CrispASR/releases/download/v0.8.43/crispasr-windows-x86_64-cuda13.zip"; + private const string WindowsVulkanUrl = "https://github.com/CrispStrobe/CrispASR/releases/download/v0.8.43/crispasr-windows-x86_64-vulkan.zip"; + private const string WindowsCpuUrl = "https://github.com/CrispStrobe/CrispASR/releases/download/v0.8.43/crispasr-windows-x86_64-cpu.zip"; + private const string WindowsCpuLegacyUrl = "https://github.com/CrispStrobe/CrispASR/releases/download/v0.8.43/crispasr-windows-x86_64-cpu-legacy.zip"; + private const string MacUrl = "https://github.com/CrispStrobe/CrispASR/releases/download/v0.8.43/crispasr-macos.tar.gz"; /// /// Intel Macs. Upstream's crispasr-macos.tar.gz is arm64-only (issue #13559: "Bad CPU type @@ -49,14 +49,14 @@ public class CrispAsrDownloadService : ICrispAsrDownloadService /// . Its inner folder is crispasr-macos-x86_64, not /// crispasr-macos - see . /// - private const string MacIntelUrl = "https://github.com/CrispStrobe/CrispASR/releases/download/v0.8.42/crispasr-macos-x86_64.tar.gz"; + private const string MacIntelUrl = "https://github.com/CrispStrobe/CrispASR/releases/download/v0.8.43/crispasr-macos-x86_64.tar.gz"; /// /// Intel Macs without AVX2/FMA (pre-Haswell, i.e. 2012 and older models) and the x64 build /// under Rosetta, where /// would die with an illegal instruction. /// - private const string MacIntelLegacyUrl = "https://github.com/CrispStrobe/CrispASR/releases/download/v0.8.42/crispasr-macos-x86_64-cpu-legacy.tar.gz"; + private const string MacIntelLegacyUrl = "https://github.com/CrispStrobe/CrispASR/releases/download/v0.8.43/crispasr-macos-x86_64-cpu-legacy.tar.gz"; /// /// True when an x64 process on macOS should get the portable legacy Intel build. Upstream's @@ -79,12 +79,12 @@ public class CrispAsrDownloadService : ICrispAsrDownloadService RuntimeInformation.ProcessArchitecture == Architecture.Arm64 ? "crispasr-macos" : MacIntelUseLegacy ? "crispasr-macos-x86_64-cpu-legacy" : "crispasr-macos-x86_64"; - private const string LinuxUrl = "https://github.com/CrispStrobe/CrispASR/releases/download/v0.8.42/crispasr-linux-x86_64.tar.gz"; - private const string LinuxCudaUrl = "https://github.com/CrispStrobe/CrispASR/releases/download/v0.8.42/crispasr-linux-x86_64-cuda.tar.gz"; - private const string LinuxCuda13Url = "https://github.com/CrispStrobe/CrispASR/releases/download/v0.8.42/crispasr-linux-x86_64-cuda13.tar.gz"; - private const string LinuxVulkanUrl = "https://github.com/CrispStrobe/CrispASR/releases/download/v0.8.42/crispasr-linux-x86_64-vulkan.tar.gz"; - private const string LinuxHipUrl = "https://github.com/CrispStrobe/CrispASR/releases/download/v0.8.42/crispasr-linux-x86_64-hip.tar.gz"; - private const string LinuxArmUrl = "https://github.com/CrispStrobe/CrispASR/releases/download/v0.8.42/crispasr-linux-arm64.tar.gz"; + private const string LinuxUrl = "https://github.com/CrispStrobe/CrispASR/releases/download/v0.8.43/crispasr-linux-x86_64.tar.gz"; + private const string LinuxCudaUrl = "https://github.com/CrispStrobe/CrispASR/releases/download/v0.8.43/crispasr-linux-x86_64-cuda.tar.gz"; + private const string LinuxCuda13Url = "https://github.com/CrispStrobe/CrispASR/releases/download/v0.8.43/crispasr-linux-x86_64-cuda13.tar.gz"; + private const string LinuxVulkanUrl = "https://github.com/CrispStrobe/CrispASR/releases/download/v0.8.43/crispasr-linux-x86_64-vulkan.tar.gz"; + private const string LinuxHipUrl = "https://github.com/CrispStrobe/CrispASR/releases/download/v0.8.43/crispasr-linux-x86_64-hip.tar.gz"; + private const string LinuxArmUrl = "https://github.com/CrispStrobe/CrispASR/releases/download/v0.8.43/crispasr-linux-arm64.tar.gz"; public CrispAsrDownloadService(HttpClient httpClient) { diff --git a/src/ui/Logic/Download/DownloadHashManager.cs b/src/ui/Logic/Download/DownloadHashManager.cs index e160ac1611e..cdd4e72fc3d 100644 --- a/src/ui/Logic/Download/DownloadHashManager.cs +++ b/src/ui/Logic/Download/DownloadHashManager.cs @@ -554,7 +554,8 @@ public static class WhisperX // (CrispStrobe/CrispASR#339, repaired in v0.8.28). [CrispAsr.WindowsCuda] = new[] { - "62b0806f15f744d231e62b52371dc5c67dc80d7c35c196f0d1c99aa14a12dfb2", // v0.8.42 (current download URL) + "94c292069a339e76109e17cc7218b17ceeb70eabf15eb8e1b1afdf7b9ee2b1b0", // v0.8.43 (current download URL) + "62b0806f15f744d231e62b52371dc5c67dc80d7c35c196f0d1c99aa14a12dfb2", // v0.8.42 "78b2989d832ae2d540636bade42feaf865d3d0a398018c09777bad0448e1d921", // v0.8.41 "5bca3b6095f6167b43d81491201d1365b93b8dccc772e4823e80c26bdd7f6ec8", // v0.8.40 "95be171adefc2df2c3149591a90f9129bd7030c4e871a7f0ae9380f411197e67", // v0.8.39 @@ -610,7 +611,8 @@ public static class WhisperX // rather than replacing it, so this list starts at v0.8.31. Archive hashes. [CrispAsr.WindowsCuda13] = new[] { - "0c6851dd954e0a83deba861fb2ef59dbf5a480d4dae310f4f86230448926db3a", // v0.8.42 (current download URL) + "3cc113e6bf5efd4481ed666dc7d46946f06c9ef31a9340c6abb8d3d747bdf812", // v0.8.43 (current download URL) + "0c6851dd954e0a83deba861fb2ef59dbf5a480d4dae310f4f86230448926db3a", // v0.8.42 "a7a6791a179084782f2e9630f33db7f90b6a45364c32784ac411ae1e3d4d1e39", // v0.8.41 "cc81c55d24be9a5759a7887245db1b3842c8c57141757cb45fae9c6b7767b117", // v0.8.40 "f98ee10599a1f9c08c535569c49e63305aa83bcb7cf4abf6a379b3ed51936db5", // v0.8.39 @@ -624,7 +626,8 @@ public static class WhisperX }, [CrispAsr.WindowsVulkan] = new[] { - "9239e36be77daad03e02a4d6479445a06657e831daf9ca254a5c776937e541fa", // v0.8.42 (current download URL) + "9294c7c6cca319714da2ae913a8839d81d53e2e9583020162ff9573c47654fc8", // v0.8.43 (current download URL) + "9239e36be77daad03e02a4d6479445a06657e831daf9ca254a5c776937e541fa", // v0.8.42 "146a38edb97a83fbc1b526e7767d3e25ab03bf6f4118c41342eda09abe9af6a0", // v0.8.41 "d78135b46d7881aec909aa398def315427ad784a268979accd18fa569a3b5ce9", // v0.8.40 "49f8bf9cf835aa97ee18f12a8ba6dd0a6087858294895f53ad7f0fc8b8ce0877", // v0.8.39 @@ -678,7 +681,8 @@ public static class WhisperX }, [CrispAsr.WindowsCpu] = new[] { - "b8d9102f695238c04d5c676098c8af60d40010396ff25ac5f9aadcc3d00ea7c5", // v0.8.42 (current download URL) + "997bc39f541e5d5d0bd14144159c038bd95d6ab8b523718c34a54e03ab404f47", // v0.8.43 (current download URL) + "b8d9102f695238c04d5c676098c8af60d40010396ff25ac5f9aadcc3d00ea7c5", // v0.8.42 "c803e79d1c0dc1b7160bd4b402268673803727029946c33687b33929584f7b83", // v0.8.41 "86b67ec6483aebf73cc79f4d3ee6dcf19ff1a3ce5d3097a1bce95b78fc24f8c2", // v0.8.40 "ae54e89c9f53e153aa5015f9d319c746ce0a5128630af3cd6b7b8471782c170f", // v0.8.39 @@ -725,7 +729,8 @@ public static class WhisperX }, [CrispAsr.WindowsCpuLegacy] = new[] { - "b27923bbd836656facf2d3dd6ec46fd26802f3172a7c3a2c6c768e3f3c493072", // v0.8.42 (current download URL) + "be0f4978d0613999954215090c2345e613c055d94ab59de0f436487933eae9ba", // v0.8.43 (current download URL) + "b27923bbd836656facf2d3dd6ec46fd26802f3172a7c3a2c6c768e3f3c493072", // v0.8.42 "738c634360d9f7e1d13bc6d9e05def5e221a24858752536c69c9617b54efb87a", // v0.8.41 "1b3aca854b3ec2c4cb92685eb5a2f786916f5a2f02b9dd35d4d5bd9b5f892b93", // v0.8.40 "33b8e71b349bb1c8bab1a5a2bec1cce26e7e924492f3a59896fc392aefd18cc7", // v0.8.39 @@ -778,7 +783,8 @@ public static class WhisperX }, [CrispAsr.MacOsX64] = new[] { - "cdbcdc60af6c5579965bdf6463eb6789f1b008e5ab8c83b878b696920e33ee1d", // v0.8.42 (current download URL) + "26c7ed772472330a354f08122a59107fff6830f9e7a784df8ab9c1d7dfc4b023", // v0.8.43 (current download URL) + "cdbcdc60af6c5579965bdf6463eb6789f1b008e5ab8c83b878b696920e33ee1d", // v0.8.42 "f8ab9490345d1c7880e64282386ee3c63218790e5101158e4c8176b9dbdda4d6", // v0.8.41 "f1413a8a1fc5e5e7a65622d42d2c73d547257f932d6b7353501d4d22b51ccb46", // v0.8.40 "7ff91661aca72787a1536dace26d94291f22a9d2647349d906902a0635c5b49f", // v0.8.39 @@ -795,13 +801,15 @@ public static class WhisperX // Intel Macs without AVX2/FMA and x64 under Rosetta; upstream split this archive off in v0.8.40. [CrispAsr.MacOsX64Legacy] = new[] { - "a828d2f85b67a2e2d5e638023a880bc9439e8fb00f3ed9ed43f83963e1499121", // v0.8.42 (current download URL) + "f632510b55552545ff0c18d6fe716f5c890c948fe96c5ef25cec49aeba9d275a", // v0.8.43 (current download URL) + "a828d2f85b67a2e2d5e638023a880bc9439e8fb00f3ed9ed43f83963e1499121", // v0.8.42 "a6ec60d94f86ed315cbfde61a7b37c1b0c1344f077562fc75ff5cf72571ed3eb", // v0.8.41 "9e2ca49bbd80963f19919e2e7019fcecfe2a1508065b8d89d3267636733f6e55", // v0.8.40 }, [CrispAsr.MacOs] = new[] { - "ad0d520ba9c899f44c2f838d076de9c1289ff5600fa4262b6bdcd5cb86ab8668", // v0.8.42 (current download URL) + "354b49ba525f1f3b29620add2506d738e7a5b5a1a33225ac613f95a69237c78b", // v0.8.43 (current download URL) + "ad0d520ba9c899f44c2f838d076de9c1289ff5600fa4262b6bdcd5cb86ab8668", // v0.8.42 "e3c1f1be6493f6247dfc11d9209b8287e3955f5a4012c3c2c56465dad6282823", // v0.8.41 "dc1d656585efbe65c0026e735f44c9d32f32113b64f7c1d198ad85862feaf0e2", // v0.8.40 "45c93a1783b11d8a1a97045f7019c79074cd9213356dec985e800360b872c592", // v0.8.39 @@ -855,7 +863,8 @@ public static class WhisperX }, [CrispAsr.Linux] = new[] { - "1b942bb9bd85f9e732bb3c222dbc97a644d50a8f3a4378b1507d121fae39e91f", // v0.8.42 (current download URL) + "e81b597cb80e49d8687a4bceb1eb24bf55928c55ff1292a9cde12e9b4fe0eb57", // v0.8.43 (current download URL) + "1b942bb9bd85f9e732bb3c222dbc97a644d50a8f3a4378b1507d121fae39e91f", // v0.8.42 "d6c33300836c7747450b7156e8b4b06a5079e5c1e02fd063e41ef553c13344cc", // v0.8.41 "dcb322648516fe3de9695e5d264b0ee79a43ee6b0d80f739eddbf6aff132aace", // v0.8.40 "c4667dbd7cd8c3e74b312716e0ad327cbd71f5585b11612145a14fb53ac487eb", // v0.8.39 @@ -909,7 +918,8 @@ public static class WhisperX }, [CrispAsr.LinuxCuda] = new[] { - "40e90cd1fdcbf815c8def1e0db0a3dc6becf512d23411aa1fd6766369b9dd90c", // v0.8.42 (current download URL) + "abeb01ea8c5183685ef05d5f167ee2b51dfcf83a67aedfbdd2e0802683f0a178", // v0.8.43 (current download URL) + "40e90cd1fdcbf815c8def1e0db0a3dc6becf512d23411aa1fd6766369b9dd90c", // v0.8.42 "fb7e615478d15853959a155fbb8f25296ad5e41a5eeb85bd614d8f19ed7fae88", // v0.8.41 "5eab290968dd2cd6dfbd3b3adba47fe9ec9ca24ee1fcef9f6e5069c96b6db182", // v0.8.40 "2b3563fcfbac3c16a5a2e5908723d859577684bd6e73a02b6bd4f213af88789b", // v0.8.39 @@ -953,7 +963,8 @@ public static class WhisperX }, [CrispAsr.LinuxCuda13] = new[] { - "8bfb828151fd3c9c15e49bbd5a7946ccb1c332be68eb9b3b029fcc4e3e4ce4d2", // v0.8.42 (current download URL) + "fea397a3d7eb13741e3c65027fb1f51d0b362f9098257fbfe2d0e97e6c4fdf54", // v0.8.43 (current download URL) + "8bfb828151fd3c9c15e49bbd5a7946ccb1c332be68eb9b3b029fcc4e3e4ce4d2", // v0.8.42 "7b663795bba48f267f451b8c5cad8ee3fbc4277fb69cf92db7845f1bae2cc020", // v0.8.41 "f28bdc911c6d9b470d71239bebba92923c34cd9c6734c461b5efcd181f0794af", // v0.8.40 "7fbe2a0617ad3dd09be11b7e39afcc0e835fcfa5d228b86e63a135dda20d7b7b", // v0.8.39 @@ -974,7 +985,8 @@ public static class WhisperX }, [CrispAsr.LinuxVulkan] = new[] { - "355f6af387e4073aecbda95465da8f605c1621a74671ede7935f95e2aad42fe0", // v0.8.42 (current download URL) + "d5c676e023df6fac61971dcef06fb7cce22ff3de95367f8e01efb8743c11ca46", // v0.8.43 (current download URL) + "355f6af387e4073aecbda95465da8f605c1621a74671ede7935f95e2aad42fe0", // v0.8.42 "4bed13dc20c018d7cca978dd92e78d8ded25d4defe8ea66c2986ba6d8cd76d1c", // v0.8.41 "062273658d9dd8a38ffb355c4d18ae682e28a5090cf73fd911e6472283421483", // v0.8.40 "5d5815ed9a59b1d68033f61b8d85ff31389e23d92cd3a9a0e6c7c9ed47670118", // v0.8.39 @@ -996,7 +1008,8 @@ public static class WhisperX }, [CrispAsr.LinuxHip] = new[] { - "672b1a9483681ea8d772159391cfa85f2642a6d4ce123ddd391b068e190f1267", // v0.8.42 (current download URL) + "09a5dfb68b9d2d6d0b135eed4f42b9aefed66a39424ed583154d1336f9d753a8", // v0.8.43 (current download URL) + "672b1a9483681ea8d772159391cfa85f2642a6d4ce123ddd391b068e190f1267", // v0.8.42 "ed2491969d09ee0e4ff54da3ae45931bcf43b4e52baaf29f5b91b5966c72fec0", // v0.8.41 "36a2f63e263daa0ac3266e50ec646ce399f9c069a18036307b7076b7db47f8f5", // v0.8.40 "871becd18666c6fb2f8271c08e75786e574281efc4b91dda71e7b7b9c3cdf2f9", // v0.8.39 @@ -1017,7 +1030,8 @@ public static class WhisperX }, [CrispAsr.LinuxArm] = new[] { - "2d75644e943dc2d02f519b3c24fd44d190af85c32eb89ae7c560ca11a7408ce1", // v0.8.42 (current download URL) + "ae6e3f13e795fa6dec55d20c8bf1c96fca0bd581eab1495f344cb9f9195ec2d2", // v0.8.43 (current download URL) + "2d75644e943dc2d02f519b3c24fd44d190af85c32eb89ae7c560ca11a7408ce1", // v0.8.42 "a0c66f1108735b68569a02b6f245799749cc9968613241733fdf83e706ba77b9", // v0.8.41 "8b555f1e2fff44848ebdcf2c17caef46d24888e0f744fea5245860f703471efa", // v0.8.40 "e07c83a0e1c2168e8ee09ebc6f71d00987c62df159437db699e81cb2213c6ecb", // v0.8.39 @@ -1321,7 +1335,8 @@ public static class WhisperX // SHA-256 of crispasr.exe / crispasr extracted from each archive above. [CrispAsr.WindowsCudaExecutable] = new[] { - "6d77e667f142079476dbbff157b069fa2f30285d4c12244c988f9ac4893d2355", // v0.8.42 (current download URL) + "77e3175d8235c9e8f6e18913f6a5e8b3d01436ac3e30d9c0097546a122dae450", // v0.8.43 (current download URL) + "6d77e667f142079476dbbff157b069fa2f30285d4c12244c988f9ac4893d2355", // v0.8.42 "de9008a6f7a15967589d382b7d3b9af0ff63d33d1a6cf6a93016eac2017b6bda", // v0.8.41 "aa0b3e2742e85156fd7db35c2321bca2600896e2b4a01b0a0c73cbd778c6a5a2", // v0.8.40 "32acef17bcc141a32f7e4863c3ca12cf5247923aae0d2f6a26ee132b64e6861f", // v0.8.39 @@ -1362,7 +1377,8 @@ public static class WhisperX // rather than replacing it, so this list starts at v0.8.31. Executable hashes. [CrispAsr.WindowsCuda13Executable] = new[] { - "e87f1600b7eeb91976add4de3b81acddcc241e6a4e4b55d9b3dae8a77d7492b9", // v0.8.42 (current download URL) + "7ad5f078b417b871c58e445c32d71f001518a8d91d30db40eaaf9b082de352b1", // v0.8.43 (current download URL) + "e87f1600b7eeb91976add4de3b81acddcc241e6a4e4b55d9b3dae8a77d7492b9", // v0.8.42 "4d3f06e3d79936212dc3b3f1092609b9ce27db48ab26d7973f5199a988b0d4bf", // v0.8.41 "cba50282b8f055fa0542e3de7301511ccaabc20c618ff35f2b990454cf87783d", // v0.8.40 "75158a4078424867214777fe00aa117e660e04ce2cc61294ba299a12360fd26b", // v0.8.39 @@ -1376,7 +1392,8 @@ public static class WhisperX }, [CrispAsr.WindowsVulkanExecutable] = new[] { - "ccef0b980c350f36a67067d5e1e6b0f216b1a475252a00eb0983c48ac9d68a92", // v0.8.42 (current download URL) + "2251c0d8e938aedbca5a79c233caefd22b193f8203a921bc43e4528abc36b0f0", // v0.8.43 (current download URL) + "ccef0b980c350f36a67067d5e1e6b0f216b1a475252a00eb0983c48ac9d68a92", // v0.8.42 "07dbe65c4adbea6e977bc0253d0991175cbd92ee871e5a5cf33ac4eda5303658", // v0.8.41 "7ca4c0ace1f6750ae8ea8adfed6b8828de5dd44628b72711b94461a87fe53c30", // v0.8.40 "73b32e87f2fcdfc5c0cf283ff310a6e67182c3e7ad478797bde02619c1523bd4", // v0.8.39 @@ -1430,7 +1447,8 @@ public static class WhisperX }, [CrispAsr.WindowsCpuExecutable] = new[] { - "9ea7f63ae35adceaa19d76e5be9dff34eb5c970375905103d1488c15af1469ca", // v0.8.42 (current download URL) + "c9e6c7e51276165e2a63ce383f35652139466a359671e0af8b037f15cfb66d8a", // v0.8.43 (current download URL) + "9ea7f63ae35adceaa19d76e5be9dff34eb5c970375905103d1488c15af1469ca", // v0.8.42 "1e4301169b6167baa23fbcc2b02e1802c3a2b2d5d5c9e756e4c99afbbd57b53a", // v0.8.41 "caa2e879531fac922616dcf09bf5cca34bd8b4d7e6af71c589ff5e1ed28e259e", // v0.8.40 "d1ad0b70dcf0c1deb17b79080303bea3d2aa16b06aa2106969fb5b2122547a7a", // v0.8.39 @@ -1477,7 +1495,8 @@ public static class WhisperX }, [CrispAsr.WindowsCpuLegacyExecutable] = new[] { - "2f3c0038b5b90eb5f198eaa06837896c1788d67e96fd16abd7965b126f493680", // v0.8.42 (current download URL) + "489f946035ed70e86ca4be16de4adc22a8f4a598619177c97f38bb51d7de3693", // v0.8.43 (current download URL) + "2f3c0038b5b90eb5f198eaa06837896c1788d67e96fd16abd7965b126f493680", // v0.8.42 "098595f1b1b90b5469c1d539f972a5040281c60cb161f1598945148d4c5f60cb", // v0.8.41 "0c996114aacb06cead199445084caa6afbf89acf954db1af402ab4aa0e28e691", // v0.8.40 "b6a4b9c383819d94e93f6d5f6ed3929ec3cab88a5736ed6ff0b99b57fd8f21a7", // v0.8.39 @@ -1530,7 +1549,8 @@ public static class WhisperX }, [CrispAsr.LinuxExecutable] = new[] { - "7fee41d3399787277ce420d61907e7bf673778394254c3cbf4bae37863ff9953", // v0.8.42 (current download URL) + "7081edc80d7948007ae4bb8fa5572c4f8ef24d347cc6620f4963c8010ffc6691", // v0.8.43 (current download URL) + "7fee41d3399787277ce420d61907e7bf673778394254c3cbf4bae37863ff9953", // v0.8.42 "52d6ba9e9cb23b4f3baa6835b29910807108077455ba7ee67a93200b88b256ae", // v0.8.41 "146ca7f8f2aeea4f135a3dc35eefcf049b90eef017efec45de409781e9e243fa", // v0.8.40 "eb95f74bd3ee6782347f11ce29a85b36a14d98b25d600d8a58df20740e29a34f", // v0.8.39 @@ -1584,7 +1604,8 @@ public static class WhisperX }, [CrispAsr.MacOsX64Executable] = new[] { - "9bb13b784a14d4dfd6e154e945d6040a1b75b5bb127d58436c30d3f10109c282", // v0.8.42 (current download URL) + "499bd7ff0b06db88e55c3626b4ed9e7167f12d403910a0a145a85c9a660f63e0", // v0.8.43 (current download URL) + "9bb13b784a14d4dfd6e154e945d6040a1b75b5bb127d58436c30d3f10109c282", // v0.8.42 "8cc5c25827a7a07b969ea45035bc22674547789d59c09f9c8ffaded8637aa898", // v0.8.41 "6381c15e83e0679e531ecc4e76a7a764ef960d526879231f98bbfe9380f072ab", // v0.8.40 "8135cbe3f294c3a44d74deecec887742a80baaa63cd732b69c5cf0030f7f7686", // v0.8.39 @@ -1602,7 +1623,8 @@ public static class WhisperX // resolves to the legacy key still reads as "update available" rather than unknown. [CrispAsr.MacOsX64LegacyExecutable] = new[] { - "2c6251ec39e0d6eba1f786effe9d66a3c59a9a4a5e848f3a53ae7bb35ea62dc2", // v0.8.42 (current download URL) + "862b3b059a8c77a278362354748a43dc6779255cf17197bf036cf8a3e6670b56", // v0.8.43 (current download URL) + "2c6251ec39e0d6eba1f786effe9d66a3c59a9a4a5e848f3a53ae7bb35ea62dc2", // v0.8.42 "484011c22bdfa8494cd6e075210aad495cf20acb8d6e52dd58ad4849cd86b917", // v0.8.41 "40a314e7e29c4cde69f91abe5b871f68fcf56590fb375d562fc70692999f8281", // v0.8.40 "8135cbe3f294c3a44d74deecec887742a80baaa63cd732b69c5cf0030f7f7686", // v0.8.39 @@ -1618,7 +1640,8 @@ public static class WhisperX }, [CrispAsr.MacOsExecutable] = new[] { - "cadf8b5603128651d5828ab86206390377c4b703b6a26d35b6af97c5942c62ec", // v0.8.42 (current download URL) + "3e3babb8b1b88602713a2574156b66339d6b0a1988aff164e830d5edadb9d4bd", // v0.8.43 (current download URL) + "cadf8b5603128651d5828ab86206390377c4b703b6a26d35b6af97c5942c62ec", // v0.8.42 "be4b837c3534eae81e84482666045b3d7778689ae86da3fc7df5f20d9ed79cbc", // v0.8.41 "d6c42473a61daab13f61f0d8b5912e1728e9b36d55d966367cb5581a7451a94c", // v0.8.40 "3df6bf45ccb38fa58eb1dee607a6dc2e5141c6de03122c8e6c869dcca8bd7eeb", // v0.8.39 @@ -1672,7 +1695,8 @@ public static class WhisperX }, [CrispAsr.LinuxCudaExecutable] = new[] { - "30418d8d6388a8d10acc83c67becdabb54ed18db4315746ecdbf19a0edf2d2a9", // v0.8.42 (current download URL) + "b543ccb012bfbd2689d1cc253dccc0d971109e79c75c2520f867652c41d9761b", // v0.8.43 (current download URL) + "30418d8d6388a8d10acc83c67becdabb54ed18db4315746ecdbf19a0edf2d2a9", // v0.8.42 "5701c0722d6e82de68fbeece519f40567b8c09fc058f85a0752f584d79a0bbf2", // v0.8.41 "f186b5215ebb958d3b408fbee52e83a9f2f8f639e4986ea8dccb4459b169e8a2", // v0.8.40 "5f2c5ad1ee5a657b4d66904b42f78b8fde72dc362d35023718fe6d1a79de4e54", // v0.8.39 @@ -1716,7 +1740,8 @@ public static class WhisperX }, [CrispAsr.LinuxCuda13Executable] = new[] { - "5cb8ea4ef6a2b2c5c306118e222dffafce4358ca682374ae7fe79b80d0c2db4b", // v0.8.42 (current download URL) + "df6d8fcaba0871b9d3dca1d650a4011d21efd2e4ba5012ac26b78fc61f61a813", // v0.8.43 (current download URL) + "5cb8ea4ef6a2b2c5c306118e222dffafce4358ca682374ae7fe79b80d0c2db4b", // v0.8.42 "c7ce5998d96948be463a5b9cef74a273ca21af6c02078530f1a7d5204133a954", // v0.8.41 "bae51531c305a999db4cbefb27380a6c3137133e53aa99fd26afc6d80d8ba568", // v0.8.40 "2b59d5c8305b240f16ff5b24568d1953b57bac6b12909974042a66fc77a63e74", // v0.8.39 @@ -1737,7 +1762,8 @@ public static class WhisperX }, [CrispAsr.LinuxVulkanExecutable] = new[] { - "c3072ac68c64ba1eacb36f922b272cd5254af036fb2b605c0b4bf370d46e02b1", // v0.8.42 (current download URL) + "00d9393f999f83b500e5f6e9d35b33e8e54326ee2420a179c8a29c8ce52851d3", // v0.8.43 (current download URL) + "c3072ac68c64ba1eacb36f922b272cd5254af036fb2b605c0b4bf370d46e02b1", // v0.8.42 "83557a13547933a67d96b6b3b137d6d391b6d85924384647e77eed28a3c58ca6", // v0.8.41 "2cb84ade75b43876955bda113b574bd1bf14f73483f11efed36f821e289b90e9", // v0.8.40 "41f310bced1222ed35e752373ef11b6866fc3b8cc7bcb80d7c645f00267d1c6e", // v0.8.39 @@ -1759,7 +1785,8 @@ public static class WhisperX }, [CrispAsr.LinuxHipExecutable] = new[] { - "b30e13cf7202b1ba5c11d49d72ccd5c0b2a7c73c8887fdfaa91e5017bb5dc46a", // v0.8.42 (current download URL) + "1d46b7b631e52a6acf13efc3a6b8d7d9daed8ea05302984713c97ee353080f0d", // v0.8.43 (current download URL) + "b30e13cf7202b1ba5c11d49d72ccd5c0b2a7c73c8887fdfaa91e5017bb5dc46a", // v0.8.42 "f03b0fc63758076f3db377846026c79a68aba965d2fbab1306e88d2524df3015", // v0.8.41 "6ac6f669ba782328ca7f0683eb65967557ab8885632d6329979e536421cd0e71", // v0.8.40 "5927b18920b614fc6daa86bb14c3ce1784ec9073e9720b0167f8d381c435dade", // v0.8.39 @@ -1780,7 +1807,8 @@ public static class WhisperX }, [CrispAsr.LinuxArmExecutable] = new[] { - "d93d17a2a92e7f3079ea32570e752905dc967f4ea0a56b58e2abc7756961a3cc", // v0.8.42 (current download URL) + "ea0297684bb5a8a5bdf4f57520654015d5fc828b27547dd761388dd2997aeca4", // v0.8.43 (current download URL) + "d93d17a2a92e7f3079ea32570e752905dc967f4ea0a56b58e2abc7756961a3cc", // v0.8.42 "2e70e488f8f1ba06fffb28d622bafa70971171d72f31e5e1f49e0c101259b9ad", // v0.8.41 "c3bb8b3dec08b636a3aa63b38b9acd285f25d344a925f76f5e16d8921ba46b13", // v0.8.40 "99849607a624b0d66a6abef7efed91498afc17a1d40cfc3384357f114fc84de8", // v0.8.39 From 677e3be34ef2b8441f2d7e2362f9c166fc6e0c27 Mon Sep 17 00:00:00 2001 From: niksedk Date: Mon, 12 Oct 2026 10:13:36 +0200 Subject: [PATCH 089/306] Filmstrip thumbnails: decode keyframes only, low priority (#15956) Each thumbnail ran an accurate ffmpeg seek, decoding every frame from the previous keyframe with all cores - continuous during playback in layout 14, starving the video/audio output. Now the keyframe before the spot is decoded alone (-skip_frame nokey), and only when it is more than half a tile away is the exact frame decoded, on one thread. ffmpeg runs at below normal priority. 4K HEVC, 10 s GOP, 5 thumbnails: 22 s CPU -> 0.5 s CPU. Co-Authored-By: Claude Opus 5.5 --- .../TimelineThumbnailCache.cs | 110 +++++++++++++++--- .../AudioVisualizerControl/TimelineTracks.cs | 5 +- 2 files changed, 96 insertions(+), 19 deletions(-) diff --git a/src/ui/Controls/AudioVisualizerControl/TimelineThumbnailCache.cs b/src/ui/Controls/AudioVisualizerControl/TimelineThumbnailCache.cs index 6e417e4dbac..234c05f1073 100644 --- a/src/ui/Controls/AudioVisualizerControl/TimelineThumbnailCache.cs +++ b/src/ui/Controls/AudioVisualizerControl/TimelineThumbnailCache.cs @@ -5,6 +5,7 @@ using System.Diagnostics; using System.Globalization; using System.IO; +using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; @@ -14,6 +15,12 @@ namespace Nikse.SubtitleEdit.Controls.AudioVisualizerControl; /// Small video frames for the filmstrip row of . Frames are pulled /// out one at a time by ffmpeg on a background task, newest request first, so scrolling past a /// stretch of the video costs nothing once the view has moved on. +/// +/// A frame is first taken from the keyframe before it, decoding nothing else. An exact seek +/// decodes every frame from that keyframe on, with all cores, which starved video playback +/// (#15956). Only when the keyframe is too far away for the tile is the exact frame decoded, +/// on one thread. ffmpeg runs at below normal priority either way. +/// /// internal sealed class TimelineThumbnailCache { @@ -24,9 +31,11 @@ internal sealed class TimelineThumbnailCache private const int MaxConsecutiveFailures = 5; private const int FfmpegTimeoutMs = 15_000; + private static readonly Regex PtsTimeRegex = new(@"pts_time:\s*(-?[0-9.]+)", RegexOptions.CultureInvariant); + private readonly Lock _lock = new(); private readonly Dictionary _thumbnails = new(); - private readonly List _pending = new(); + private readonly List<(long Milliseconds, long ToleranceMilliseconds)> _pending = new(); private string _videoFileName = string.Empty; private int _generation; private int _consecutiveFailures; @@ -38,9 +47,10 @@ internal sealed class TimelineThumbnailCache /// /// The frame at , or null while it is not there yet (it is /// then queued). Null as well for a file ffmpeg cannot read - after a few failures in a row - /// the file is left alone. + /// the file is left alone. A keyframe up to before + /// the spot is good enough. /// - public Bitmap? Get(string? videoFileName, long milliseconds) + public Bitmap? Get(string? videoFileName, long milliseconds, long toleranceMilliseconds) { if (string.IsNullOrEmpty(videoFileName)) { @@ -70,8 +80,8 @@ internal sealed class TimelineThumbnailCache return null; } - _pending.Remove(milliseconds); - _pending.Add(milliseconds); + _pending.RemoveAll(p => p.Milliseconds == milliseconds); + _pending.Add((milliseconds, toleranceMilliseconds)); if (_pending.Count > MaxPendingRequests) { _pending.RemoveAt(0); @@ -93,6 +103,7 @@ private void Work() { string videoFileName; long milliseconds; + long toleranceMilliseconds; int generation; lock (_lock) { @@ -102,13 +113,15 @@ private void Work() return; } - milliseconds = _pending[^1]; + (milliseconds, toleranceMilliseconds) = _pending[^1]; _pending.RemoveAt(_pending.Count - 1); videoFileName = _videoFileName; generation = _generation; } - var bitmap = ExtractFrame(videoFileName, milliseconds, out var failed); + var failed = false; + var bitmap = ExtractKeyframe(videoFileName, milliseconds, toleranceMilliseconds) ?? + ExtractFrame(videoFileName, milliseconds, out failed); lock (_lock) { @@ -149,30 +162,77 @@ private void Work() } } + /// + /// The keyframe at or before , decoding keyframes only. Null + /// when ffmpeg gives nothing, or the keyframe is more than + /// away. + /// + private static Bitmap? ExtractKeyframe(string videoFileName, long milliseconds, long toleranceMilliseconds) + { + if (!File.Exists(videoFileName)) + { + return null; + } + + // "-noaccurate_seek" keeps the frames before the seek point and "-skip_frame nokey" + // decodes keyframes only, so the first frame out is the keyframe the seek landed on. + // showinfo logs its time relative to the seek point (negative when before it). + var arguments = $"-hide_banner -nostats -noaccurate_seek -skip_frame nokey -threads 1 -ss {ToSeconds(milliseconds)} " + + $"-i \"{videoFileName}\" -frames:v 1 -an -sn -dn " + + $"-vf showinfo,scale=-2:{ThumbnailHeight} -f image2pipe -c:v png -"; + var bitmap = RunFfmpeg(arguments, readLog: true, out _, out var log); + if (bitmap == null) + { + return null; + } + + var match = PtsTimeRegex.Match(log); + if (match.Success && + double.TryParse(match.Groups[1].Value, NumberStyles.Float, CultureInfo.InvariantCulture, out var offsetSeconds) && + Math.Abs(offsetSeconds * 1000.0) <= toleranceMilliseconds) + { + return bitmap; + } + + return null; + } + + /// The exact frame at , decoded on one thread. private static Bitmap? ExtractFrame(string videoFileName, long milliseconds, out bool failed) { failed = true; - try + if (!File.Exists(videoFileName)) { - if (!File.Exists(videoFileName)) - { - return null; - } + return null; + } + + // stderr is silenced rather than redirected, so only one pipe has to be drained + var arguments = $"-v quiet -threads 1 -ss {ToSeconds(milliseconds)} -i \"{videoFileName}\" -frames:v 1 -an -sn -dn " + + $"-vf scale=-2:{ThumbnailHeight} -f image2pipe -c:v png -"; + return RunFfmpeg(arguments, readLog: false, out failed, out _); + } + + private static string ToSeconds(long milliseconds) + { + return (milliseconds / 1000.0).ToString("0.###", CultureInfo.InvariantCulture); + } - var seconds = (milliseconds / 1000.0).ToString("0.###", CultureInfo.InvariantCulture); + private static Bitmap? RunFfmpeg(string arguments, bool readLog, out bool failed, out string log) + { + failed = true; + log = string.Empty; + try + { using var process = new Process { StartInfo = { FileName = FfmpegHelper.GetFfmpegLocation(), - // Input seeking ("-ss" before "-i") lands on a nearby keyframe, which is - // both fast and plenty for a thumbnail. stderr is silenced rather than - // redirected, so only one pipe has to be drained. - Arguments = $"-v quiet -ss {seconds} -i \"{videoFileName}\" -frames:v 1 -an -sn " + - $"-vf scale=-2:{ThumbnailHeight} -f image2pipe -c:v png -", + Arguments = arguments, UseShellExecute = false, CreateNoWindow = true, RedirectStandardOutput = true, + RedirectStandardError = readLog, } }; @@ -180,6 +240,19 @@ private void Work() process.Start(); #pragma warning restore CA1416 + try + { + // Thumbnails are a nice-to-have - the video player comes first + process.PriorityClass = ProcessPriorityClass.BelowNormal; + } + catch + { + // not allowed, or already gone + } + + // stderr is drained alongside stdout, or a full pipe would block ffmpeg + var logTask = readLog ? process.StandardError.ReadToEndAsync() : Task.FromResult(string.Empty); + using var timeout = new CancellationTokenSource(FfmpegTimeoutMs); using var registration = timeout.Token.Register(() => { @@ -196,6 +269,7 @@ private void Work() using var stream = new MemoryStream(); process.StandardOutput.BaseStream.CopyTo(stream); process.WaitForExit(); + log = logTask.GetAwaiter().GetResult(); if (stream.Length == 0) { // ffmpeg exits with 0 and writes nothing when the seek is past the last frame diff --git a/src/ui/Controls/AudioVisualizerControl/TimelineTracks.cs b/src/ui/Controls/AudioVisualizerControl/TimelineTracks.cs index edabad19f61..cf8637c7b82 100644 --- a/src/ui/Controls/AudioVisualizerControl/TimelineTracks.cs +++ b/src/ui/Controls/AudioVisualizerControl/TimelineTracks.cs @@ -561,6 +561,9 @@ private void DrawFilmstrip(DrawingContext context, AudioVisualizer source, Rect var tileWidth = step * pixelsPerSecond; var firstTile = Math.Max(0, (long)Math.Floor(startSeconds / step)); + // A keyframe within half a tile is the right picture for it, and much cheaper to get + var toleranceMilliseconds = (long)Math.Round(step * 500.0); + // No tiles past the end of the media (as far as the waveform knows it): there is no // picture there, only an ffmpeg run per tile to find that out. var lastTileSeconds = endSeconds; @@ -575,7 +578,7 @@ private void DrawFilmstrip(DrawingContext context, AudioVisualizer source, Rect var tileRect = new Rect(x, row.Y, Math.Max(1, tileWidth - 1), row.Height); context.DrawRectangle(_tileBrush, null, tileRect); - var bitmap = _thumbnails.Get(videoFileName, GetFilmstripFrameMilliseconds(tile, step)); + var bitmap = _thumbnails.Get(videoFileName, GetFilmstripFrameMilliseconds(tile, step), toleranceMilliseconds); if (bitmap == null || bitmap.Size.Height <= 0) { continue; From 63eb73c3c4b716a0e239442028c1024e7713df91 Mon Sep 17 00:00:00 2001 From: niksedk Date: Mon, 12 Oct 2026 11:09:04 +0200 Subject: [PATCH 090/306] Update whisper.cpp to v1.9.5 (b5454) Point the Windows BLAS/CUDA downloads at the upstream b5454 release and the mac / Windows-Vulkan / Linux-Vulkan / Linux-CUDA downloads at the new SubtitleEdit/support-files whispercpp-195 release, built from that ref. Upstream renamed the Windows CUDA asset from whisper-cublas-12.4.0-bin-x64.zip to whisper-bin-win-cuda-12.4.0-x64.zip; the archive layout (Release/) is unchanged. All six archive hashes and the matching whisper-cli / whisper-cli.exe hashes are regenerated with v1.9.5 at index 0; the older entries stay listed so an existing install is still recognised and offered the update. Unlike 194, both Linux whisper-cli binaries changed this release, so the Linux executable-hash test now pins the 195 hashes as current and the 194 ones as superseded. Fixes #15962 Co-Authored-By: Claude Opus 5.5 --- src/ui/Logic/Download/DownloadHashManager.cs | 38 ++++++++++++------- .../Logic/Download/WhisperDownloadService.cs | 21 +++++----- .../WhisperCppLinuxExecutableHashTests.cs | 8 +++- 3 files changed, 42 insertions(+), 25 deletions(-) diff --git a/src/ui/Logic/Download/DownloadHashManager.cs b/src/ui/Logic/Download/DownloadHashManager.cs index e160ac1611e..d07a6dd9a2c 100644 --- a/src/ui/Logic/Download/DownloadHashManager.cs +++ b/src/ui/Logic/Download/DownloadHashManager.cs @@ -2782,7 +2782,8 @@ public static class WhisperX // whatever URL WhisperDownloadService.cs is pinned to. [WhisperCpp.WindowsBlas] = new[] { - "55c06d09e8b9b6cfb2b0b47ddedc71803054f0e48be1f41848b3141c06c703a9", // b5130 / v1.9.4 (current download URL — fetched directly from ggml-org/whisper.cpp) + "b1158bd10edf92c9799149a6fc5d3e58da3f495241d6fb3423088130e88ffbf3", // b5454 / v1.9.5 (current download URL — fetched directly from ggml-org/whisper.cpp) + "55c06d09e8b9b6cfb2b0b47ddedc71803054f0e48be1f41848b3141c06c703a9", // b5130 / v1.9.4 "78568aa80b361382cb303438a7be3b05669651f2ca8258910394679e049d26ea", // b4938 / v1.9.3 "ffe5b47ca8e53a7677949f23a9c4641bbec4eee8a5714c3d14b67bb8d7b24a78", // v1.9.2 "3c319eab3e87f85883e1ff3d14426c0a1986c661c5eb5985e8af431ed9c4f71f", // v1.9.1 @@ -2792,7 +2793,8 @@ public static class WhisperX }, [WhisperCpp.WindowsCuBlas] = new[] { - "af520ddd034d985b55dfeea3e465ed93653ba2aee1a55e865033edc548c272a7", // b5130 / v1.9.4 (current download URL — fetched directly from ggml-org/whisper.cpp) + "afef0b881c500958921c3f5523b50e59ee2ec9b6f5cbd25b324c51ed308a957a", // b5454 / v1.9.5 (whisper-bin-win-cuda-12.4.0-x64.zip) (current download URL — fetched directly from ggml-org/whisper.cpp) + "af520ddd034d985b55dfeea3e465ed93653ba2aee1a55e865033edc548c272a7", // b5130 / v1.9.4 (whisper-cublas-12.4.0-bin-x64.zip) "c1b17166e1e31a91cc8e9c1f910d3785e3ce757bb2958bf9dce13fdb4880005f", // b4938 / v1.9.3 "443110ddaad70d4290ab2e77179e31cf712035bbc4fad56bb4519a90c917b39c", // v1.9.2 "106a2030eff8998e4ef320fe72e263a78449e9040386ee27c41ea80b001b601b", // v1.9.1 @@ -2802,7 +2804,8 @@ public static class WhisperX }, [WhisperCpp.WindowsVulkan] = new[] { - "b576bd3cc7282478ad49b32692416f02c0987b7cbc736b3b63cd39207a7a57af", // whispercpp-194 / v1.9.4 (current download URL) + "89f2c36455939ad42e08b2107bd6b47105b6d9c1a2b0916cbb500ec27deb520a", // whispercpp-195 / v1.9.5 (current download URL) + "b576bd3cc7282478ad49b32692416f02c0987b7cbc736b3b63cd39207a7a57af", // whispercpp-194 / v1.9.4 "2b3829c69c64b3bbd117ef3fa3ae1de2574cab36a4a572199bf3c291404ff1cc", // whispercpp-193 / v1.9.3 "a220f2063bf60c18406beb37d2001d7da6cfd2253d5b38b4c78719a56bedd2c0", // whispercpp-192 / v1.9.2 "3e70fccfab278c7bb7c78efd7d101ba6e507b668ceccf3610b2d1c54d6d9f119", // whispercpp-191 / v1.9.1 @@ -2812,7 +2815,8 @@ public static class WhisperX }, [WhisperCpp.MacOs] = new[] { - "185f78870a9495813714a0a515add589f19f1a51a67b4376f2e6f8795d7cf773", // whispercpp-194 / v1.9.4 (current download URL) + "ee9ff7613cf67dab2096073e64ced125845e13202240c70dce3b0b1dae3a733b", // whispercpp-195 / v1.9.5 (current download URL) + "185f78870a9495813714a0a515add589f19f1a51a67b4376f2e6f8795d7cf773", // whispercpp-194 / v1.9.4 "dc6371c8f039705aef24b1eb70aa20cbc3058045e1690cc0302badd35f165ac9", // whispercpp-193 / v1.9.3 "6654f5b628f59efb04a0ef6e6b8ece06595df1f2acf7635609d32052e7b6c906", // whispercpp-192 / v1.9.2 "3bbeed3e91cf07657e0d59c38de0cea15a276d59cd07e630951ef42927474983", // whispercpp-191 / v1.9.1 @@ -2826,7 +2830,8 @@ public static class WhisperX // listed so an existing install is still identified rather than reported as unknown. [WhisperCpp.LinuxVulkan] = new[] { - "bee7c18bbeb0bad00c2092338656e2591bf4bdbe884b74543ce477d910b4c86e", // whispercpp-194 / v1.9.4 (current download URL) + "64d5d302b40c40d9573066ba20a5e17cffc62024f613d67c424ce93e67860344", // whispercpp-195 / v1.9.5 (current download URL) + "bee7c18bbeb0bad00c2092338656e2591bf4bdbe884b74543ce477d910b4c86e", // whispercpp-194 / v1.9.4 "14555fa34fff496f69252e2761dfe8c8fb710863d0213c9e5c3f294490775af3", // whispercpp-193 / v1.9.3 "ef74909722eca7e422e21ef41a0060d38efadd055398697c17ce12d80c41d467", // whispercpp-192 / v1.9.2 "0943f97f58ca98aafa26659e7c4d8d87f474c74f966f0d9ae91491989a32cf2b", // whispercpp-191-r2 / v1.9.1 @@ -2837,7 +2842,8 @@ public static class WhisperX }, [WhisperCpp.LinuxCuda] = new[] { - "c42ca4652796be5aff451c64f55c74c7aca17e5902cd47924f752e99b3ce32c7", // whispercpp-194 / v1.9.4 (current download URL) + "a2c71e97f2e90a449cb261bfdd969e0182c5b15e9023b777dd882d32dcefd9a2", // whispercpp-195 / v1.9.5 (current download URL) + "c42ca4652796be5aff451c64f55c74c7aca17e5902cd47924f752e99b3ce32c7", // whispercpp-194 / v1.9.4 "f63bdee178d45121cd6b6d754914fb58e3cb71fcd6a493a9724aa200e83dc425", // whispercpp-193 / v1.9.3 "dcf676854f473e9e3f0622b4725fa7123f18313e772002b1d3bb31efe4dc98b9", // whispercpp-192 / v1.9.2 "02129bac653d00d85ef110c24c37eb56d5470d4fee432a09f55ac9fbc63ff768", // whispercpp-191-r2 / v1.9.1 @@ -2850,7 +2856,8 @@ public static class WhisperX // SHA-256 of whisper-cli / whisper-cli.exe extracted from each archive above. [WhisperCpp.WindowsBlasExecutable] = new[] { - "98d0df798385cc7ba713b56b2bdb7ffb5b3133cc1cfa63506fa2152d8b4d7f50", // b5130 / v1.9.4 (current download URL — fetched directly from ggml-org/whisper.cpp) + "4e2940cf2dc6b41eac8d0ccc0274062d2bf9f6b6aca597416dc5e849e6a6f6d0", // b5454 / v1.9.5 (current download URL — fetched directly from ggml-org/whisper.cpp) + "98d0df798385cc7ba713b56b2bdb7ffb5b3133cc1cfa63506fa2152d8b4d7f50", // b5130 / v1.9.4 "b6b27a4b7ce9543382344ba273ea82bac35f821ca4317cf16710e16b26791e0f", // b4938 / v1.9.3 "225aa686b74010c0259e8d28763aa89a078a0141091f525779fae6892e8e6097", // v1.9.2 "254ee898dd8c3b16fa87583113320dad3f8e3787e15d8f14e245fcb3b487fc39", // v1.9.1 @@ -2860,7 +2867,8 @@ public static class WhisperX }, [WhisperCpp.WindowsCuBlasExecutable] = new[] { - "2360ff153d9ab40254b9b5497a0f30ec7c5ef97d96d4b10a9521fa9b98e08c3a", // b5130 / v1.9.4 (current download URL — fetched directly from ggml-org/whisper.cpp) + "6535f1d390f28565c70c0be4867088413494cfc4a59b645708354833081c9926", // b5454 / v1.9.5 (current download URL — fetched directly from ggml-org/whisper.cpp) + "2360ff153d9ab40254b9b5497a0f30ec7c5ef97d96d4b10a9521fa9b98e08c3a", // b5130 / v1.9.4 "41a586cac5863ebfc198cdc8ffb1642795543c1a4506c6be974e55adf301dccb", // b4938 / v1.9.3 "92205834c7e2dfa35fac242fb438c285f70bcf942474f80f7dcf5fdac0d7a6dc", // v1.9.2 "789fddb0f05c0c28043b3c4f3bcf15a0ae839df24292c60f90c4edb8d02a5ab5", // v1.9.1 @@ -2870,7 +2878,8 @@ public static class WhisperX }, [WhisperCpp.WindowsVulkanExecutable] = new[] { - "26a64c217a57e23da96c56a38f4fb35a3a27c9488631086f799a4e32fd1331e1", // whispercpp-194 / v1.9.4 (current download URL) + "1ef8f16edf2f8df429e7e1d18c9af073f66d594813e9aae739e5bdf015a61985", // whispercpp-195 / v1.9.5 (current download URL) + "26a64c217a57e23da96c56a38f4fb35a3a27c9488631086f799a4e32fd1331e1", // whispercpp-194 / v1.9.4 "a99280f4497f867bb17d4e4e138b3694c930a42a897609974a19dc5b651ac2c5", // whispercpp-193 / v1.9.3 "d003f2229418e8fd59c1e0aee62d7f996a768ad7ebda3da502bbfca2063b84a7", // whispercpp-192 / v1.9.2 "011d5f4d5d58eb2d3d0cafe64fd22d377bc066b2e6d2d91fd5d58c95be0b7244", // whispercpp-191 / v1.9.1 @@ -2880,7 +2889,8 @@ public static class WhisperX }, [WhisperCpp.MacOsExecutable] = new[] { - "bc42d7bdde6ab5f318872a00e8a853ac07e7d70c14c7e5b59618428a200aac96", // whispercpp-194 / v1.9.4 (current download URL) + "8ca31bad4acaa925a7e199e487bcc39f1c8db97962f2b53b8679e47b6948ee9d", // whispercpp-195 / v1.9.5 (current download URL) + "bc42d7bdde6ab5f318872a00e8a853ac07e7d70c14c7e5b59618428a200aac96", // whispercpp-194 / v1.9.4 "fa5843c03ab0ae86771e5cadea81b5c34ef04d5a37a068b4b386ed46f9442fd6", // whispercpp-193 / v1.9.3 "ebaf5389eb619a376023e1ad9684409596dedc16869a59a3234191bb440a96fd", // whispercpp-192 / v1.9.2 "278e45caa50d4dc2e92d04e7fa9c19d3439350a7bd0c6bc40cc91b41d5cc72a5", // whispercpp-191 / v1.9.1 @@ -2893,10 +2903,11 @@ public static class WhisperX // single whisper-cli for both backends. See the note on LinuxVulkanExecutable. [WhisperCpp.LinuxVulkanExecutable] = new[] { + "7cac3ce34947b128bdc129d303cf95ec27a454fc51dd04920d3b87f7efe82766", // whispercpp-195 / v1.9.5 (current download URL) // whisper-cli in whispercpp-194 is byte-identical to the -192 one (the v1.9.4 changes // live in libwhisper.so.1 / libggml-vulkan.so), so the archive hash above is what // tells those two apart. - "3e142b4a8c99a4d5cf00cdd4e15fb77c2a842dd0bd83f647675fe1d303e91a03", // whispercpp-194 / v1.9.4 (current download URL) and whispercpp-192 / v1.9.2 + "3e142b4a8c99a4d5cf00cdd4e15fb77c2a842dd0bd83f647675fe1d303e91a03", // whispercpp-194 / v1.9.4 and whispercpp-192 / v1.9.2 "f57c4dbd4478f73720533f477311e354dc04ac818ca558e5c9855d5978075894", // whispercpp-193 / v1.9.3 "da61c0c1910c103cf8dea855855f072249689ee2310f375e2b79615cbd012c05", // whispercpp-191-r2 / v1.9.1 "782fac61b9bcfe8f6db22564bb5a2cda2c22550b9ef38064ec5f188bc86dfe79", // whispercpp-191 / v1.9.1 (missing shared libraries) @@ -2906,9 +2917,10 @@ public static class WhisperX }, [WhisperCpp.LinuxCudaExecutable] = new[] { - // whisper-cli is byte-identical in whispercpp-192, -193 and -194; the per-release changes + // whisper-cli was byte-identical in whispercpp-192, -193 and -194; the per-release changes // live in libggml-cuda.so, so the archive hash above is what tells them apart. - "9c93f819e170d0f00a08c256ac2299f819a8e94f3a6f116bac2657c6ab8de62a", // whispercpp-194 / v1.9.4 (current download URL) and whispercpp-193 / v1.9.3 and whispercpp-192 / v1.9.2 + "683be7436dee6784c62a23dc02472ab959d72a31285b7293f1406a8a09ad8287", // whispercpp-195 / v1.9.5 (current download URL) + "9c93f819e170d0f00a08c256ac2299f819a8e94f3a6f116bac2657c6ab8de62a", // whispercpp-194 / v1.9.4 and whispercpp-193 / v1.9.3 and whispercpp-192 / v1.9.2 "3a4d717745c2d8cf19ca7a954c29c0349a7f158d93a6597bb86e155f0735474a", // whispercpp-191-r2 / v1.9.1 "16a838ae67e248020b9bc65b8584fcc113cf18167c9cfc0a2d98e194fa52cf95", // whispercpp-191 / v1.9.1 (missing shared libraries) "330ff60cbabd8e77137000d05905cabca90e299c455a3282948c9edddf28bcd7", // whispercpp-186 / v1.8.6 (missing shared libraries) diff --git a/src/ui/Logic/Download/WhisperDownloadService.cs b/src/ui/Logic/Download/WhisperDownloadService.cs index 6013a5c897d..bbc359b865c 100644 --- a/src/ui/Logic/Download/WhisperDownloadService.cs +++ b/src/ui/Logic/Download/WhisperDownloadService.cs @@ -23,21 +23,22 @@ public interface IWhisperDownloadService public class WhisperDownloadService : IWhisperDownloadService { private readonly HttpClient _httpClient; - // Upstream now tags nightly builds llama.cpp style (b5130) and only attaches binaries to - // those; the semantic v1.9.4 tag points at the same commit but carries no assets. - private const string WindowsUrl = "https://github.com/ggml-org/whisper.cpp/releases/download/b5130/whisper-blas-bin-x64.zip"; - private const string MacArmUrl = "https://github.com/SubtitleEdit/support-files/releases/download/whispercpp-194/whisper-mac.zip"; - private const string MacX64Url = "https://github.com/SubtitleEdit/support-files/releases/download/whispercpp-194/whisper-mac.zip"; + // Upstream now tags nightly builds llama.cpp style (b5454) and only attaches binaries to + // those; the semantic v1.9.5 tag points at the same commit. + private const string WindowsUrl = "https://github.com/ggml-org/whisper.cpp/releases/download/b5454/whisper-blas-bin-x64.zip"; + private const string MacArmUrl = "https://github.com/SubtitleEdit/support-files/releases/download/whispercpp-195/whisper-mac.zip"; + private const string MacX64Url = "https://github.com/SubtitleEdit/support-files/releases/download/whispercpp-195/whisper-mac.zip"; // The Linux archives are SE rebuilds (upstream ships no Linux binaries). They must carry // libwhisper.so.1 / libggml.so.0 next to whisper-cli, staged under their SONAMEs with // $ORIGIN RPATHs - whispercpp-184 through -191 shipped without them and the engine died on // startup with "error while loading shared libraries" (issue #13680). The build workflow in - // SubtitleEdit/support-files verifies this now, so plain whispercpp-194 is fine here. - private const string LinuxUrl = "https://github.com/SubtitleEdit/support-files/releases/download/whispercpp-194/whisper-vulkan-linux64.zip"; + // SubtitleEdit/support-files verifies this now, so plain whispercpp-195 is fine here. + private const string LinuxUrl = "https://github.com/SubtitleEdit/support-files/releases/download/whispercpp-195/whisper-vulkan-linux64.zip"; - private const string WindowsUrlCuBlass = "https://github.com/ggml-org/whisper.cpp/releases/download/b5130/whisper-cublas-12.4.0-bin-x64.zip"; - private const string WindowsUrlCppVulkan = "https://github.com/SubtitleEdit/support-files/releases/download/whispercpp-194/whisper-vulkan-x64.zip"; - private const string LinuxUrlCuBlass = "https://github.com/SubtitleEdit/support-files/releases/download/whispercpp-194/whisper-cuda-linux64.zip"; + // Upstream renamed this asset in b5454 (was whisper-cublas-12.4.0-bin-x64.zip); same Release/ layout. + private const string WindowsUrlCuBlass = "https://github.com/ggml-org/whisper.cpp/releases/download/b5454/whisper-bin-win-cuda-12.4.0-x64.zip"; + private const string WindowsUrlCppVulkan = "https://github.com/SubtitleEdit/support-files/releases/download/whispercpp-195/whisper-vulkan-x64.zip"; + private const string LinuxUrlCuBlass = "https://github.com/SubtitleEdit/support-files/releases/download/whispercpp-195/whisper-cuda-linux64.zip"; private const string DownloadUrlConstMe = "https://github.com/Const-me/Whisper/releases/download/1.12.0/cli.zip"; private const string SileroVadUrl = "https://github.com/SubtitleEdit/support-files/releases/download/whispercpp-184/ggml-silero-v6.2.0.zip"; diff --git a/tests/UI/Logic/Download/WhisperCppLinuxExecutableHashTests.cs b/tests/UI/Logic/Download/WhisperCppLinuxExecutableHashTests.cs index 3d352a78321..a5024b73aa9 100644 --- a/tests/UI/Logic/Download/WhisperCppLinuxExecutableHashTests.cs +++ b/tests/UI/Logic/Download/WhisperCppLinuxExecutableHashTests.cs @@ -16,11 +16,13 @@ namespace UITests.Logic.Download; public class WhisperCppLinuxExecutableHashTests { // whisper-cli extracted from whisper-vulkan-linux64.zip / whisper-cuda-linux64.zip - private const string VulkanCurrent = "3e142b4a8c99a4d5cf00cdd4e15fb77c2a842dd0bd83f647675fe1d303e91a03"; + private const string VulkanCurrent = "7cac3ce34947b128bdc129d303cf95ec27a454fc51dd04920d3b87f7efe82766"; + private const string Vulkan194 = "3e142b4a8c99a4d5cf00cdd4e15fb77c2a842dd0bd83f647675fe1d303e91a03"; private const string Vulkan193 = "f57c4dbd4478f73720533f477311e354dc04ac818ca558e5c9855d5978075894"; private const string VulkanR2 = "da61c0c1910c103cf8dea855855f072249689ee2310f375e2b79615cbd012c05"; private const string VulkanBroken191 = "782fac61b9bcfe8f6db22564bb5a2cda2c22550b9ef38064ec5f188bc86dfe79"; - private const string CudaCurrent = "9c93f819e170d0f00a08c256ac2299f819a8e94f3a6f116bac2657c6ab8de62a"; + private const string CudaCurrent = "683be7436dee6784c62a23dc02472ab959d72a31285b7293f1406a8a09ad8287"; + private const string Cuda194 = "9c93f819e170d0f00a08c256ac2299f819a8e94f3a6f116bac2657c6ab8de62a"; private const string CudaR2 = "3a4d717745c2d8cf19ca7a954c29c0349a7f158d93a6597bb86e155f0735474a"; private const string CudaBroken191 = "16a838ae67e248020b9bc65b8584fcc113cf18167c9cfc0a2d98e194fa52cf95"; private const string Shared184 = "315f46514fc09a4fefe2f7b6ae95cfac5441a03b8be04eed1b5f567d5ccc38de"; @@ -44,6 +46,8 @@ public void GetStatus_BuildWithMissingSharedLibraries_OffersUpdate(string key, s } [Theory] + [InlineData(DownloadHashManager.WhisperCpp.LinuxVulkanExecutable, Vulkan194)] + [InlineData(DownloadHashManager.WhisperCpp.LinuxCudaExecutable, Cuda194)] [InlineData(DownloadHashManager.WhisperCpp.LinuxVulkanExecutable, Vulkan193)] [InlineData(DownloadHashManager.WhisperCpp.LinuxVulkanExecutable, VulkanR2)] [InlineData(DownloadHashManager.WhisperCpp.LinuxCudaExecutable, CudaR2)] From 8dd1bab923dee48da11dc57bfc94e446ec650633 Mon Sep 17 00:00:00 2001 From: Curious Insatiable <113294705+Need74@users.noreply.github.com> Date: Mon, 12 Oct 2026 13:30:49 +0200 Subject: [PATCH 091/306] Fix JSON syntax errors in French language file Just a few oversights --- src/ui/Assets/Languages/French.json | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/ui/Assets/Languages/French.json b/src/ui/Assets/Languages/French.json index 3890b9b7fd5..0d8b9e85188 100644 --- a/src/ui/Assets/Languages/French.json +++ b/src/ui/Assets/Languages/French.json @@ -11,13 +11,13 @@ "actorPickerTitle": "Définir l'acteur", "actorPickerFilterHint": "Tapez pour filtrer ou saisissez le nom d'un nouvel acteur", "actorPickerKeysHint": "Appuyez sur un chiffre ou sur \"Entrée\" pour définir l'acteur.\r\nAlt+Haut/Bas modifie l'ordre, \"Suppr\" supprime l'acteur.", - "actorPickerNewActorX": "Nouvel acteur \"{0}", + "actorPickerNewActorX": "Nouvel acteur \"{0}\"", "actorPickerLinesSelectedX": "Lignes sélectionnées : {0}", "setStyleDotDotDot": "Définir le style…", "stylePickerTitle": "Définir le style", "stylePickerFilterHint": "Tapez pour filtrer ou tapez un nouveau nom de style", "stylePickerKeysHint": "Appuyez sur un chiffre (1-9, 0) pour définir le style sur toutes les lignes sélectionnées.", - "stylePickerNewStyleX": "Nouveau style \"{0}", + "stylePickerNewStyleX": "Nouveau style \"{0}\"", "stylePickerNewStyleInfo": "Appuyez sur \"Entrée\" pour ajouter ce style avec les paramètres par défaut et définissez-le sur les lignes sélectionnées.\r\nUtilisez \"Gérer les styles…\" pour modifier ultérieurement sa police, ses couleurs et sa position.", "stylePickerCurrentStyleX": "Style actuel : {0}", "stylePickerLinesUsingStyleX": "Lignes utilisant ce style : {0}", @@ -1881,7 +1881,7 @@ "fixCommasExample": ",, -> ,", "removeLineBreaksExample": "Foo
bar ! -> Foo bar !", "fixDoubleApostrophesExample": "''Contient des guillemets-simples doubles'' -> \"Contient des guillemets-doubles simples\"", - "fixMisreadQuotesExample": "\"Bonjour\" -> \"Bonjour", + "fixMisreadQuotesExample": "\"Bonjour' -> \"Bonjour\"", "addPeriodsExample": "Bonjour le monde -> Bonjour le monde.", "startWithUppercaseLetterAfterParagraphExample": "p1 : Foobar ! || p2 : foobar ! -> p1 : Foobar ! || p2 : Foobar !", "startWithUppercaseLetterAfterPeriodInsideParagraphExample": "Bonjour ! comment allez-vous ? -> Bonjour ! Comment allez-vous ?", @@ -2533,8 +2533,8 @@ "namesX": "Noms : {0}", "addedToDictionaryX": "Ajoutés au dictionnaire : {0}", "doNotShowThisAgain": "Ne plus afficher ce message", - "editUseAlwaysList": "Modifier la liste \"Utiliser toujours\"…", - "useAlwaysListTitle": "Vérification orthographique : liste \"Toujours utiliser ", + "editUseAlwaysList": "Modifier la liste \"Toujours utiliser\"…", + "useAlwaysListTitle": "Vérification orthographique : liste \"Toujours utiliser\"", "useAlwaysListHeader": "Liste \"Toujours utiliser\"", "useAlwaysListDescription": "La vérification orthographique des mots est automatiquement remplacée, enregistrée par \"Tout modifier\" et \"Toujours utiliser\". Une paire n'est utilisée que lorsque le mot est mal orthographié.", "useAlwaysWord": "Mot mal orthographié", @@ -2543,10 +2543,10 @@ "useAlwaysUnusedHint": "Jamais utilisé : ce mot est correctement orthographié (ou est un nom ou un mot utilisateur), donc la vérification orthographique ne le signale jamais", "removeUnused": "Supprimer les inutilisés", "useAlwaysEmpty": "Aucune paire \"Toujours utiliser\" pour cette langue pour l'instant", - "useAlwaysEmptyHint": "Les paires sont ajoutées lorsque vous cliquez sur \"Tout modifier  ou \"Toujours utiliser  dans la vérification orthographique.", + "useAlwaysEmptyHint": "Les paires sont ajoutées lorsque vous cliquez sur \"Tout modifier\" ou \"Toujours utiliser\" dans la vérification orthographique.", "useAlwaysNoMatch": "Aucune paire ne correspond à la recherche", "useAlwaysSameWord": "Le remplacement doit être différent du mot.", - "useAlwaysRememberOff": "\"N'oubliez pas , la liste \"Toujours utiliser  est désactivé dans les paramètres, la vérification orthographique n'utilise pas cette liste ni ne l'enregistre." + "useAlwaysRememberOff": "\"N'oubliez pas\", la liste \"Toujours utiliser\" est désactivé dans les paramètres, la vérification orthographique n'utilise pas cette liste ni ne l'enregistre." }, "video": { "burnIn": { @@ -2595,7 +2595,7 @@ "isolateSpeechFailed": "Impossible d'isoler le discours - transcrire l'audio original à la place.", "detectSpeakers": "Détecter les haut-parleurs", "detectSpeakersHint": "Commencer chaque ligne avec le haut-parleur, comme \"(haut-parleur 0)\", en utilisant la diarisation Sortformer de NVIDIA (jusqu'à 8 haut-parleurs).\r\nFonctionne mieux avec les backends qui divisent les lignes là où le haut-parleur change, comme Parakeet et Cohere.", - "detectSpeakersNeedsNewerCrispAsr": "\"Détecter les haut-parleurs\" nécessite Crisp ASR v{0} ou une version plus récente, mais v{1} est installé.\r\nMettez à jour Crisp ASR (le bouton d'engrenage à côté du moteur) ou désactivez \"Détecter les haut-parleurs .", + "detectSpeakersNeedsNewerCrispAsr": "\"Détecter les haut-parleurs\" nécessite Crisp ASR v{0} ou une version plus récente, mais v{1} est installé.\r\nMettez à jour Crisp ASR (le bouton d'engrenage à côté du moteur) ou désactivez \"Détecter les haut-parleurs\".", "xNeedsNewerCrispAsr": "{0} a besoin de Crisp ASR v{1} ou plus récent, mais v{2} est installé.\r\n\r\nTélécharger et installer la mise à jour maintenant ?", "vad": "VAD", "vadHint": "Détection de l'activité vocale : seule la parole est retranscrite, coupée là où l'orateur fait une pause.\r\nAuto : Silero, et uniquement pour les moteurs qui en ont besoin (Cohere, Mega, Index-Echo).\r\nSilero, FireRedVAD, WebRTC : toujours activé, pour chaque moteur Crisp ASR. FireRedVAD est le plus précis.\r\nIndex-Echo utilise toujours Silero. --vad, --vad-model ou --chunk-seconds dans les paramètres avancés sont prioritaires.", @@ -2649,7 +2649,7 @@ "qualityReportAll": "Tous", "qualityReportIssue": "Problème", "qualityReportDetail": "Détail", - "qualityReportTip": "Les lignes sont répertoriées avec leurs numéros dans le nouveau sous-titre.\r\nUtiliser \"Corriger les erreurs courantes  ou les paramètres de post-traitement pour corriger plus automatiquement, ou essayer un autre modèle/moteur si de nombreuses lignes sont affectées.", + "qualityReportTip": "Les lignes sont répertoriées avec leurs numéros dans le nouveau sous-titre.\r\nUtiliser \"Corriger les erreurs courantes\" ou les paramètres de post-traitement pour corriger plus automatiquement, ou essayer un autre modèle/moteur si de nombreuses lignes sont affectées.", "qualityReportDoNotShowAgain": "Ne plus afficher ce rapport", "engineSettings": "Paramètres du moteur parole vers texte", "engineSettingsSubtitle": "Moteur parole vers texte", @@ -2687,7 +2687,7 @@ "customServerUrl": "URL du serveur personnalisé", "customServerUrlHint": "Point de terminaison de parole complète d'un serveur compatible OpenAI, utilisé lorsque le fournisseur est \"Personnalisé\", par ex.\r\nhttp://localhost:8880/v1/audio/speech", "customModels": "Modèles personnalisés", - "customModelsHint": "Noms de modèles séparés par des virgules proposés lorsque le fournisseur est \"Personnalisé ", + "customModelsHint": "Noms de modèles séparés par des virgules proposés lorsque le fournisseur est \"Personnalisé\"", "customVoices": "Voix personnalisées", "customVoicesHint": "Noms de voix séparés par des virgules, utilisés par le fournisseur « Personnalisé » et par les modèles OpenRouter qui ne publient aucune liste de voix", "instructions": "Instructions", @@ -2835,14 +2835,14 @@ "generationFolder": "Dossier de génération", "generationFolderDescription": "Dossier pour les clips audio générés lors d'une exécution en cours.\r\nLaisser vide pour utiliser le dossier temporaire du système.\r\nChaque exécution obtient son propre sous-dossier.", "deleteTempFiles": "Supprimer les clips générés à la fermeture", - "deleteTempFilesDescription": "Supprime le dossier de génération de l'exécution lorsque la fenêtre \"Synthèse vocale  se ferme.\r\nLes fichiers audio et vidéo enregistrés ne sont pas affectés.\r\nDésactiver cette option pour conserver les clips individuels.", + "deleteTempFilesDescription": "Supprime le dossier de génération de l'exécution lorsque la fenêtre \"Synthèse vocale\" se ferme.\r\nLes fichiers audio et vidéo enregistrés ne sont pas affectés.\r\nDésactiver cette option pour conserver les clips individuels.", "edgeTtsRate": "Débit de synthèse vocale Edge-TTS", "edgeTtsRateDescription": "Débit de parole par défaut pour Edge-TTS, (p. ex. \"+50%\", \"-30%\" ou \"+0%\").", "edgeTtsPitch": "Hauteur de la voix Edge-TTS", "edgeTtsPitchDescription": "Réglage de la hauteur de la voix par défaut pour Edge-TTS, (p. ex. \"+10Hz\", \"-5Hz\" ou \"+0Hz\").", "edgeTtsVolume": "Volume de la synthèse vocale Edge-TTS", "edgeTtsVolumeDescription": "Réglage du volume de la synthèse vocale par défaut pour Edge-TTS, (p. ex. \"+20%\", \"-10%\" ou \"+0%\").", - "downloadPiperPrompt": "\"La synthèse vocale  nécessite Piper.\r\n\r\nTélécharger et utiliser Piper ?", + "downloadPiperPrompt": "\"La synthèse vocale\" nécessite Piper.\r\n\r\nTélécharger et utiliser Piper ?", "omniVoiceTtsSettings": "Paramètres OmniVoice TTS", "reDownloadOmniVoiceTts": "Retélécharger OmniVoice TTS", "selectTheBuildToDownload": "\r\nSélectionner la version à télécharger :", @@ -4116,7 +4116,7 @@ "drawMode": "Mode de dessin :", "addNewCharcter": "Ajouter un nouveau caractère", "addNOcrCharacterTitle": "Ajouter le caractère nOCR pour la ligne {0}, le caractère {1} de {2} en utilisant la base de données \"{3}", - "addBinaryOcrCharacterTitle": "Ajouter un caractère OCR binaire pour la ligne {0}, le caractère {1} de {2} à l'aide de la base de données \"{3}", + "addBinaryOcrCharacterTitle": "Ajouter un caractère OCR binaire pour la ligne {0}, le caractère {1} de {2} à l'aide de la base de données \"{3}\"", "lineIndexX": "Ligne {0}", "inspectNOcrAdditions": "Inspecter les nouveaux ajouts nOCR", "ocrSelectedLines": "Lignes sélectionnées OCR", From d605f0ab4228b2004fa5faecf9a9237eba513987 Mon Sep 17 00:00:00 2001 From: niksedk Date: Mon, 12 Oct 2026 15:18:22 +0200 Subject: [PATCH 092/306] Context menus: full size on first open (#15965) Avalonia 12.1.4 (AvaloniaUI/Avalonia#22366) stopped updating a popup placed at the pointer after its first layout pass, and that update is also what resizes the popup window. A context menu still grows after its first layout pass the first time it is shown, so it kept a too small size and the submenu arrows ended up outside the menu, reachable only by scrolling sideways. While a pointer-placed popup is open, swap its placement for anchor and gravity at the same pointer point (what pointer placement uses), which still resizes; restore the popup's own placement when it closes. Co-Authored-By: Claude Opus 5.5 --- src/ui/Logic/PointerPopupPlacement.cs | 92 ++++++++++++++++++++ src/ui/Program.cs | 3 + tests/UI/Logic/PointerPopupPlacementTests.cs | 59 +++++++++++++ 3 files changed, 154 insertions(+) create mode 100644 src/ui/Logic/PointerPopupPlacement.cs create mode 100644 tests/UI/Logic/PointerPopupPlacementTests.cs diff --git a/src/ui/Logic/PointerPopupPlacement.cs b/src/ui/Logic/PointerPopupPlacement.cs new file mode 100644 index 00000000000..dbb11e5ee2c --- /dev/null +++ b/src/ui/Logic/PointerPopupPlacement.cs @@ -0,0 +1,92 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Controls.Primitives; +using Avalonia.Controls.Primitives.PopupPositioning; +using Avalonia.Input; +using Avalonia.Interactivity; +using Avalonia.LogicalTree; +using System.Runtime.CompilerServices; + +namespace Nikse.SubtitleEdit.Logic; + +/// +/// Avalonia 12.1.4 (AvaloniaUI/Avalonia#22366) stopped updating a popup placed at the pointer +/// after its first layout pass, and that update is also what resizes the popup window. A context +/// menu still grows after its first layout pass the first time it is shown, so it kept a too +/// small size: the submenu arrows ended up outside the menu (#15965). While such a popup is open, +/// its pointer placement is swapped for an anchor-and-gravity placement at the same point, which +/// still resizes, and restored when it closes. +/// +public static class PointerPopupPlacement +{ + private sealed record SavedPlacement(PlacementMode Placement, Rect? PlacementRect, PopupAnchor Anchor, PopupGravity Gravity); + + private static readonly ConditionalWeakTable Saved = new(); + private static PixelPoint? _lastPointerScreenPosition; + private static bool _initialized; + + public static void Initialize() + { + if (_initialized) + { + return; + } + + _initialized = true; + + // TopLevel.LastPointerPosition, which pointer placement uses, is internal + InputElement.PointerMovedEvent.AddClassHandler(TrackPointer, RoutingStrategies.Tunnel, handledEventsToo: true); + InputElement.PointerPressedEvent.AddClassHandler(TrackPointer, RoutingStrategies.Tunnel, handledEventsToo: true); + + // Runs after Popup's own handler, so the popup has been shown and positioned at the pointer + Popup.IsOpenProperty.Changed.AddClassHandler((popup, e) => + { + if (e.NewValue is true) + { + ReplacePointerPlacement(popup); + } + else + { + RestorePlacement(popup); + } + }); + } + + private static void TrackPointer(TopLevel topLevel, PointerEventArgs e) + { + _lastPointerScreenPosition = topLevel.PointToScreen(e.GetPosition(topLevel)); + } + + private static void ReplacePointerPlacement(Popup popup) + { + if (popup.Placement != PlacementMode.Pointer || + _lastPointerScreenPosition is not { } screenPosition || + (popup.PlacementTarget ?? popup.FindLogicalAncestorOfType()) is not { } target || + TopLevel.GetTopLevel(target) == null) + { + return; + } + + Saved.AddOrUpdate(popup, new SavedPlacement(popup.Placement, popup.PlacementRect, popup.PlacementAnchor, popup.PlacementGravity)); + + // Same anchor and gravity as pointer placement uses + popup.PlacementRect = new Rect(target.PointToClient(screenPosition), new Size(1, 1)); + popup.PlacementAnchor = PopupAnchor.TopLeft; + popup.PlacementGravity = PopupGravity.BottomRight; + popup.Placement = PlacementMode.AnchorAndGravity; + } + + private static void RestorePlacement(Popup popup) + { + if (!Saved.TryGetValue(popup, out var saved)) + { + return; + } + + Saved.Remove(popup); + popup.Placement = saved.Placement; + popup.PlacementRect = saved.PlacementRect; + popup.PlacementAnchor = saved.Anchor; + popup.PlacementGravity = saved.Gravity; + } +} diff --git a/src/ui/Program.cs b/src/ui/Program.cs index 8067018a990..fcd23eac69e 100644 --- a/src/ui/Program.cs +++ b/src/ui/Program.cs @@ -479,6 +479,9 @@ private static void ConfigureApplication(AppBuilder b, ClassicDesktopStyleApplic // Shift+Delete cut in every text box, in every window (#13711). NativeKeymap.AddShiftDeleteCut(); + + // Context menus kept their first, too small size (#15965). + PointerPopupPlacement.Initialize(); } private static void SetupNativeMenu(Application app, ClassicDesktopStyleApplicationLifetime lifetime) diff --git a/tests/UI/Logic/PointerPopupPlacementTests.cs b/tests/UI/Logic/PointerPopupPlacementTests.cs new file mode 100644 index 00000000000..e42b644c7d4 --- /dev/null +++ b/tests/UI/Logic/PointerPopupPlacementTests.cs @@ -0,0 +1,59 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Controls.Primitives; +using Avalonia.Controls.Primitives.PopupPositioning; +using Avalonia.Headless; +using Avalonia.Headless.XUnit; +using Avalonia.Input; +using Avalonia.LogicalTree; +using Avalonia.Media; +using Avalonia.Threading; +using Nikse.SubtitleEdit.Logic; + +namespace UITests.Logic; + +/// +/// Avalonia 12.1.4 no longer resizes a pointer-placed popup after its first layout pass, so +/// context menus kept a too small first size (#15965). While open, the popup is placed with +/// anchor and gravity at the pointer instead, and its own placement comes back on close. +/// +public class PointerPopupPlacementTests +{ + [AvaloniaFact] + public void ContextFlyout_OpenedAtPointer_IsAnchoredAtPointerWhileOpen() + { + PointerPopupPlacement.Initialize(); + + var flyout = new MenuFlyout(); + flyout.Items.Add(new MenuItem { Header = "Delete" }); + var host = new Border { Width = 300, Height = 200, Background = Brushes.Gray, ContextFlyout = flyout }; + var window = new Window { Width = 400, Height = 300, Content = host }; + window.Show(); + window.UpdateLayout(); + + try + { + window.MouseMove(new Point(50, 60)); + flyout.ShowAt(host, showAtPointer: true); + AvaloniaHeadlessPlatform.ForceRenderTimerTick(); + Dispatcher.UIThread.RunJobs(); + + var popup = ((MenuItem)flyout.Items[0]!).FindLogicalAncestorOfType(); + Assert.NotNull(popup); + Assert.Equal(PlacementMode.AnchorAndGravity, popup!.Placement); + Assert.Equal(PopupAnchor.TopLeft, popup.PlacementAnchor); + Assert.Equal(PopupGravity.BottomRight, popup.PlacementGravity); + Assert.Equal(new Rect(window.TranslatePoint(new Point(50, 60), host)!.Value, new Size(1, 1)), popup.PlacementRect); + + flyout.Hide(); + Dispatcher.UIThread.RunJobs(); + + Assert.Equal(PlacementMode.Pointer, popup.Placement); + Assert.Null(popup.PlacementRect); + } + finally + { + window.Close(); + } + } +} From a1f0682ca6a2024e2ff7432057968d755c5d7fb2 Mon Sep 17 00:00:00 2001 From: niksedk Date: Mon, 12 Oct 2026 16:43:22 +0200 Subject: [PATCH 093/306] Dispose mpv-opengl players from Closing, not Closed (#15971) Avalonia detaches the visual tree - firing the OpenGL deinit, the only place mpv's render context can be freed - before it raises Closed. The fullscreen window, the video controls preview and the Cut video preview disposed their player from Closed, so MarkForDispose came too late, the deinit was a no-op and each window close leaked an mpv core with its GPU memory (~600 MiB per fullscreen round trip on Linux/NVIDIA). Co-Authored-By: Claude Opus 5.5 --- .../VideoControlsItemsWindow.cs | 4 +++- .../Features/Shared/FullScreenVideoPlayer.cs | 19 +++++++++++-------- .../CutVideo/CutVideoPreviewViewModel.cs | 5 ++--- .../Video/CutVideo/CutVideoPreviewWindow.cs | 1 - 4 files changed, 16 insertions(+), 13 deletions(-) diff --git a/src/ui/Features/Options/Settings/VideoControlsItems/VideoControlsItemsWindow.cs b/src/ui/Features/Options/Settings/VideoControlsItems/VideoControlsItemsWindow.cs index 542573670bc..c3f9b689d13 100644 --- a/src/ui/Features/Options/Settings/VideoControlsItems/VideoControlsItemsWindow.cs +++ b/src/ui/Features/Options/Settings/VideoControlsItems/VideoControlsItemsWindow.cs @@ -48,7 +48,9 @@ public VideoControlsItemsWindow(VideoControlsItemsViewModel vm) UpdatePreview(); vm.LayoutChanged += UpdatePreview; - Closed += (_, _) => + // Closing, not Closed: the OpenGL deinit that frees mpv's render context runs before + // Closed, so disposing there leaked the preview's mpv core (issue #15971). + Closing += (_, _) => { vm.LayoutChanged -= UpdatePreview; preview.CloseAndDisposePlayer(); diff --git a/src/ui/Features/Shared/FullScreenVideoPlayer.cs b/src/ui/Features/Shared/FullScreenVideoPlayer.cs index 61cadd6099a..130f8637390 100644 --- a/src/ui/Features/Shared/FullScreenVideoPlayer.cs +++ b/src/ui/Features/Shared/FullScreenVideoPlayer.cs @@ -200,15 +200,18 @@ public FullScreenVideoWindow( _mouseMoveDetectionTimer = null; videoPlayer.FullscreenCollapseRequested -= Close; videoPlayer.VideoPlayer.CloseFile(); - }; - // A fresh player is built for every fullscreen session and this one is never used again, - // so destroy it instead of only unloading the file - otherwise each enter/leave fullscreen - // leaked a live player core plus its 50 ms position timer (same root cause as issue #13048). - // Done from Closed, not Closing: it drops the embedded render host, which is better left - // until the window is actually gone. onClose above has already copied position and volume - // back to the docked player. - Closed += (_, _) => videoPlayer.CloseAndDisposePlayer(); + // A fresh player is built for every fullscreen session and this one is never used + // again, so destroy it instead of only unloading the file - otherwise each enter/leave + // fullscreen leaked a live player core plus its 50 ms position timer (same root cause + // as issue #13048). onClose above has already copied position and volume back to the + // docked player. + // Done from Closing, not Closed: Avalonia detaches the visual tree - which fires the + // OpenGL deinit, the only place mpv's render context can be freed - before it raises + // Closed. Disposing from Closed marked the player too late, so the deinit was a no-op + // and every round trip leaked an mpv core with ~600 MiB of VRAM (issue #15971). + videoPlayer.CloseAndDisposePlayer(); + }; UiUtil.FocusOnFirstActivation(this, () => { Focus(); }); // hack to make OnKeyDown work Loaded += async(_, _) => diff --git a/src/ui/Features/Video/CutVideo/CutVideoPreviewViewModel.cs b/src/ui/Features/Video/CutVideo/CutVideoPreviewViewModel.cs index f678734bddf..d879cae84c5 100644 --- a/src/ui/Features/Video/CutVideo/CutVideoPreviewViewModel.cs +++ b/src/ui/Features/Video/CutVideo/CutVideoPreviewViewModel.cs @@ -94,10 +94,9 @@ internal void OnClosing() { VideoPlayer.VideoPlayer.CloseFile(); UiUtil.SaveWindowPosition(Window); - } - internal void OnClosed() - { + // Here, not from Closed: the OpenGL deinit that frees mpv's render context runs before + // Closed, so disposing there leaked the preview's mpv core (issue #15971). VideoPlayer.CloseAndDisposePlayer(); } } diff --git a/src/ui/Features/Video/CutVideo/CutVideoPreviewWindow.cs b/src/ui/Features/Video/CutVideo/CutVideoPreviewWindow.cs index f5145409abb..8121b405d1d 100644 --- a/src/ui/Features/Video/CutVideo/CutVideoPreviewWindow.cs +++ b/src/ui/Features/Video/CutVideo/CutVideoPreviewWindow.cs @@ -44,7 +44,6 @@ public CutVideoPreviewWindow(CutVideoPreviewViewModel vm) Loaded += (_, _) => vm.OnLoaded(); Closing += (_, _) => vm.OnClosing(); - Closed += (_, _) => vm.OnClosed(); KeyDown += (_, e) => vm.OnKeyDown(e); } } From 14816361aea51deaa8ec5886fcb2d51164d737a9 Mon Sep 17 00:00:00 2001 From: niksedk Date: Mon, 12 Oct 2026 20:24:56 +0200 Subject: [PATCH 094/306] Create Whisper OpenAI model folder before download (#15975) ~/.cache/whisper only exists after Python whisper has run, so downloading a model failed with "Could not find a part of the path". Co-Authored-By: Claude Opus 5.5 --- .../Video/SpeechToText/Engines/WhisperEngineOpenAi.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/ui/Features/Video/SpeechToText/Engines/WhisperEngineOpenAi.cs b/src/ui/Features/Video/SpeechToText/Engines/WhisperEngineOpenAi.cs index 183185fb583..f302bfe113c 100644 --- a/src/ui/Features/Video/SpeechToText/Engines/WhisperEngineOpenAi.cs +++ b/src/ui/Features/Video/SpeechToText/Engines/WhisperEngineOpenAi.cs @@ -48,7 +48,14 @@ public string GetAndCreateWhisperFolder() public string GetAndCreateWhisperModelFolder(WhisperModel? whisperModel) { + // ~/.cache/whisper only exists once Python whisper has run, so create it here - + // otherwise downloading a model fails with "Could not find a part of the path". var folder = new WhisperModel().ModelFolder; + if (!Directory.Exists(folder)) + { + Directory.CreateDirectory(folder); + } + return folder; } From 43447a88d6fd533b013bf75a0374533be4803ca1 Mon Sep 17 00:00:00 2001 From: TOKUZO Date: Tue, 13 Oct 2026 04:47:52 +0900 Subject: [PATCH 095/306] Japanese translation for rc-4 --- src/ui/Assets/Languages/Japanese.json | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/ui/Assets/Languages/Japanese.json b/src/ui/Assets/Languages/Japanese.json index c74dfc3d9bd..44cddc24aee 100644 --- a/src/ui/Assets/Languages/Japanese.json +++ b/src/ui/Assets/Languages/Japanese.json @@ -1,7 +1,7 @@ { "title": "Subtitle Edit", - "version": "v5.3.0-rc2", - "translatedBy": "", + "version": "v5.3.0-rc4", + "translatedBy": "hisui3393", "cultureName": "jp-JP", "general": { "abort": "中止", @@ -1345,7 +1345,8 @@ "forcedAlignerIsolatingSpeech": "音声を分離中... {0}", "forcedAlignerIsolatingSpeechFailed": "音声を分離できませんでした。代わりに読み上げ時間から終了時間を設定しました。", "forcedAlignerModel": "音声照合モデル", - "forcedAlignerDownloadEngine": "エンジンをダウンロード/更新..." + "forcedAlignerDownloadEngine": "エンジンをダウンロード/更新...", + "forcedAlignerEstimatedXOfY": "{0}行のうち{1}行は位置合わせができなかったため、読み上げ時間を基に配置しました。これらのタイムコードは推定値にすぎません。おそらく、台本のほうが動画内で実際に話されている内容より長くなっています。" }, "export": { "exportImagesProfiles": "画像書き出しプロファイル", @@ -2814,6 +2815,10 @@ "removeOriginalSpeechDescription": "元の動画の音楽や効果音を残したまま、話し声だけを除去し、新しい音声を重ねます。GPU使用時でも動画とほぼ同じ時間がかかり、GPUがない場合は何倍も時間がかかります。音量のダッキングを有効にすると、その音量は音楽や効果音にも適用されます。音声を動画ファイルに追加する場合のみ適用されます。", "removingOriginalSpeech": "元の音声を除去中...", "removeOriginalSpeechFailed": "元の音声を除去できませんでした。代わりに元の音声を残したまま動画に追加しました。", + "speechRemovalModel": "モデル", + "speechRemovalModelDescription": "「最高品質」は音楽や効果音をできるだけ多く残します。「高速」は処理が約3倍速くなりますが、音楽がやや多く失われます。", + "speechRemovalBestQuality": "最高品質(Mel-Band RoFormer)", + "speechRemovalFast": "高速(HTDemucs)", "videoTruncatedWarning": "新しい動画ファイルの映像は{0}で終了していますが、元の動画は{1}あります。このため、音声が続いている間も映像は静止します。\n\nファイルはそのまま残しています。ffmpegの出力については、Subtitle Editのデータフォルダーにあるerror-log.txtを確認してください。", "originalVolumePercent": "元音声の音量(%)", "vadSilenceCompression": "無音圧縮(VAD)", @@ -4220,6 +4225,10 @@ "crispEmbedSettingsTitle": "CrispEmbedの設定", "crispEmbedDescription": "複数のモデルに対応したローカルOCRエンジン - ここからエンジンとモデルをダウンロードできます。", "trainNOcrDatabase": "nOCRデータベースを学習...", + "optimizeNOcrDatabase": "nOCRデータベースを最適化...", + "optimizeNOcrDatabaseQuestionXY": "nOCRデータベース「{0}」から、類似した文字と区別するための情報を持たない行を削除しますか?\n\nデータベースのサイズが小さくなり、OCRが高速化します。通常、認識エラーも減少します。処理には最大1分かかります。\n\nバックアップは「{1}」として保存されます。", + "optimizingNOcrDatabaseXY": "nOCRデータベースを最適化中... {0}/{1}", + "nOcrDatabaseOptimizedXYZ": "nOCRデータベース「{0}」を最適化しました:{1:#,##0}行 → {2:#,##0}行。", "startTraining": "学習開始", "abortTraining": "学習中止", "trainingOptions": "学習オプション", From b5f11bca4237161f014aa6743e720875c7ff7adc Mon Sep 17 00:00:00 2001 From: FunkyKnilch Date: Mon, 12 Oct 2026 22:20:25 +0200 Subject: [PATCH 096/306] Update German Translation --- src/ui/Assets/Languages/German.json | 64 ++++++++++++++++++++++------- 1 file changed, 50 insertions(+), 14 deletions(-) diff --git a/src/ui/Assets/Languages/German.json b/src/ui/Assets/Languages/German.json index c01b528a596..cb224d73fdf 100644 --- a/src/ui/Assets/Languages/German.json +++ b/src/ui/Assets/Languages/German.json @@ -31,7 +31,7 @@ "addToUserDictionary": "Zum Benutzerwörterbuch hinzufügen", "addVideoFiles": "Videodateien hinzufügen...", "adjustAlpha": "Alpha (Transparenz) anpassen", - "adjustDisplayDuration": "Längen anpassen", + "adjustDisplayDuration": "Dauer anpassen", "adjustment": "Anpassung", "advancedSettings": "Erweiterte Einstellungen", "after": "Nachher", @@ -203,7 +203,7 @@ "pleaseRestartSeToUseTheNewLibmpv": "Bitte starten Sie SE neu, um das neue libmpv zu verwenden.", "pickVobSubLanguageTitle": "VobSub-Sprache wählen - {0}", "duplicate": "Duplizieren", - "duration": "Länge", + "duration": "Dauer", "durationMinutes": "Dauer in Minuten", "edit": "Bearbeiten", "editDotDotDot": "Bearbeiten...", @@ -580,7 +580,7 @@ "shotIn": "Schnitt ein", "shotOut": "Schnitt aus", "shortcut": "Tastenkombination", - "shortcuts": "Tastenkürzel", + "shortcuts": "Tastenkombinationen", "show": "Anzeigen", "showActorColumn": "Spalte \"Darsteller\" anzeigen", "showCpsColumn": "Spalte \"Zeichen/Sekunde\" anzeigen", @@ -720,7 +720,7 @@ "url": "URL", "usages": "Verwendungen", "use": "Verwenden", - "useAlways": "Immer anwenden", + "useAlways": "Immer verwenden", "useOnce": "Einmal verwenden", "useOutputFolder": "Ausgabeordner verwenden", "useSourceFolder": "Quellordner verwenden", @@ -766,7 +766,7 @@ "width": "Breite", "wordsPerMin": "Wörter/Min.", "pixelWidth": "Pixelbreite", - "wpm": "Wörter/Min", + "wpm": "Wörter/Min.", "xFiles": "{0:#,###,##0} Dateien", "xOfYFiles": "{0:#,###,##0} von {1:#,###,##0} Dateien", "xFilesConvertedInY": "{0:#,###,##0} Dateien konvertiert in {1}", @@ -1052,6 +1052,9 @@ "charactersPerSecond": "Zeichen/Sek: {0}", "chooseColumn": "Spalte auswählen", "columnPaste": "Spalte einfügen", + "commandPaletteTitle": "Befehlsübersicht", + "commandPaletteSearchPlaceholder": "Tippen, um Befehle zu suchen...", + "commandPaletteNoCommandsFound": "Keine Befehle gefunden", "createdEmptyTranslation": "Leere Übersetzung des aktuellen Untertitels erstellt", "deleteText": "Text löschen", "deleteTextAndShiftCellsUp": "Text löschen und Zellen nach oben verschieben", @@ -1867,7 +1870,7 @@ "fixDialogsOnOneLine": "Dialoge auf einer Zeile korrigieren" }, "adjustDurations": { - "title": "Längen anpassen", + "title": "Dauer anpassen", "adjustVia": "Anpassen über", "recalculate": "Neuberechnen", "addSeconds": "Sekunden hinzufügen", @@ -2100,8 +2103,21 @@ "convertColorsToDialogReBreakLines": "Zeilen neu umbrechen", "snapTimeCodesToFramesInfo": "Rundet jede Start- und Endzeit auf den nächsten Frame. Die Bildrate wird aus einer Videodatei mit demselben Namen wie die Untertiteldatei gelesen, sofern keine feste Bildrate gewählt ist.", "addFolderDotDotDot": "Ordner hinzufügen...", + "addFolderRecursiveDotDotDot": "Ordner inkl. Unterordner hinzufügen...", "selectFolderToConvert": "Ordner mit zu konvertierenden Dateien wählen", "includeSubfolders": "Unterordner einbeziehen, wenn ein Ordner hinzugefügt wird", + "videoFilesWhenAddingFolder": "Videodateien (mkv, mp4, ts …) beim Hinzufügen eines Ordners", + "videoFilesAsk": "Fragen", + "videoFilesInclude": "Enthaltene Untertitelspuren hinzufügen", + "videoFilesSkip": "Überspringen", + "folderContainsXVideoFiles": "Der Ordner enthält {0} Videodatei(en) (mkv, mp4, ts …).\n\nSollen die eingebetteten Untertitelspuren ebenfalls hinzugefügt werden oder nur die Untertiteldateien?", + "addVideoFiles": "Videodateien hinzufügen", + "skipVideoFiles": "Nur Untertiteldateien", + "doNotAskAgainVideoFiles": "Nicht erneut fragen (in Stapelverarbeitungs-Einstellungen änderbar)", + "settingsSectionOutput": "Ausgabe", + "settingsSectionAddingFiles": "Dateien hinzufügen", + "settingsSectionImageBasedSubtitles": "Bildbasierte Untertitel (OCR)", + "settingsSectionOther": "Andere", "keepSourceFileTimestamp": "Datum/Uhrzeit der Quelldatei für Ausgabedateien beibehalten", "preventSleepWhileConverting": "Ruhezustand des Computers während der Konvertierung verhindern", "scanningFolderX": "{0} wird durchsucht...", @@ -2405,7 +2421,21 @@ "correctWordsX": "Korrekte Wörter: {0}", "namesX": "Namen: {0}", "addedToDictionaryX": "Zum Wörterbuch hinzugefügt: {0}", - "doNotShowThisAgain": "Diese Meldung nicht mehr anzeigen" + "doNotShowThisAgain": "Diese Meldung nicht mehr anzeigen", + "editUseAlwaysList": "\"Immer verwenden\"-Liste bearbeiten...", + "useAlwaysListTitle": "Rechtschreibprüfung - \"Immer verwenden\"-Liste", + "useAlwaysListHeader": "\"Immer verwenden\"-Liste", + "useAlwaysListDescription": "Automatische Ersetzungen der Rechtschreibprüfung (gespeichert durch \"Alle ändern\" und \"Immer verwenden\"). Ein Paar wird nur angewendet, wenn das Wort falsch geschrieben ist.", + "useAlwaysWord": "Falsch geschriebenes Wort", + "xPairs": "{0:#,##0} Paare", + "xUnusedPairs": "{0:#,##0} Nie verwendet", + "useAlwaysUnusedHint": "Nie verwendet – Dieses Wort ist korrekt geschrieben (oder ist ein Name bzw. ein Benutzerwort), daher wird es von der Rechtschreibprüfung nie beanstandet.", + "removeUnused": "Ungenutzte Einträge löschen", + "useAlwaysEmpty": "Noch keine \"Immer verwenden\"-Paare für diese Sprache vorhanden", + "useAlwaysEmptyHint": "Paare werden hinzugefügt, wenn Sie in der Rechtschreibprüfung auf \"Alle ändern\" oder \"Immer verwenden\" klicken.", + "useAlwaysNoMatch": "Keine passenden Ersetzungspaare", + "useAlwaysSameWord": "Die Ersetzung muss sich vom ursprünglichen Wort unterscheiden.", + "useAlwaysRememberOff": "\"Liste 'Immer verwenden' merken\" ist in den Einstellungen deaktiviert, daher wird diese Liste von der Rechtschreibprüfung weder verwendet noch gespeichert." }, "video": { "burnIn": { @@ -2894,7 +2924,7 @@ "speechToTextSelectedLinesDotDotDot": "Sprache-zu-Text für ausgewählte Zeilen...", "speechToTextNewSelectionDotDotDot": "Sprache-zu-Text für neue Auswahl...", "seekSilence": "Stille suchen", - "minSilenceDurationSeconds": "Mindestlänge der Stille (Sekunden)", + "minSilenceDurationSeconds": "Mindestdauer der Stille (Sekunden)", "maxSilenceVolume": "Max. Stille-Lautstärke (0,0 - 1,0):", "guessTimeCodesDotDotDot": "Zeitstempel ermitteln...", "seekSilenceDotDotDot": "Stille suchen...", @@ -2950,7 +2980,7 @@ "pointSyncViaOther": "Punkt-Synchronisation über anderen Untertitel", "gapAfter": "Pause danach", "syncPointCandidateInfo": "Zeilen, auf die mindestens {0}+ Sekunden Stille folgt, werden hervorgehoben – diese eignen sich oft als zuverlässige Synchronisationspunkte.", - "adjustAllShortcuts": "Tastenkürzel:\n\n• Umschalt + Links/Rechts: 10 ms verschieben\n• Strg + Links/Rechts: 100 ms verschieben\n• Alt + Links/Rechts: 500 ms verschieben", + "adjustAllShortcuts": "Tastenkombination:\n\n• Umschalt + Links/Rechts: 10 ms verschieben\n• Strg + Links/Rechts: 100 ms verschieben\n• Alt + Links/Rechts: 500 ms verschieben", "adjustAllShortcutsFrames": "Tastenkombinationen:\n\n• Umschalt + Links/Rechts: 1 Frame verschieben\n• Strg + Links/Rechts: 10 Frames verschieben\n• Alt + Links/Rechts: 1 Sekunde verschieben", "offsetInSeconds": "Versatz in Sekunden", "speedFactor": "Geschwindigkeitsfaktor", @@ -2969,6 +2999,7 @@ "lineMerge": "Zeilen zusammenführen", "delayInSecondsBetweenRequests": "Verzögerung in Sekunden zwischen Anfragen", "maxBytesPerRequest": "Max. Bytes pro Anfrage", + "keepMusicLinesUntranslated": "Zeilen in Musiksymbolen (♪ ... ♪) nicht übersetzen", "promptText": "Prompt-Text", "resetPromptToDefault": "Prompt auf Standard zurücksetzen", "translateEachLineSeparately": "Jede Zeile separat übersetzen", @@ -3105,8 +3136,8 @@ "useDoNotBreakAfterList": "Liste \"Nicht umbrechen nach\" verwenden", "newEmptyDefaultMs": "Standarddauer neuer Untertitel (ms)", "timeCodeUpDownStepMs": "Schrittweite für Zeit hoch/runter (ms)", - "moveSelectedLinesStepMs": "Schrittweite für Tastenkürzel \"Ausgewählte Zeilen verschieben\" (ms)", - "moveStartEndStepMs": "Schrittweite für Tastenkürzel \"Start/Ende verschieben\" (ms)", + "moveSelectedLinesStepMs": "Schrittweite für Tastenkombination \"Ausgewählte Zeilen verschieben\" (ms)", + "moveStartEndStepMs": "Schrittweite für Tastenkombination \"Start/Ende verschieben\" (ms)", "moveLinesShortenNeighbor": "Zeilen verschieben: Vorherige/Nächste Zeile kürzen anstatt zu überlappen", "promptBeforeDelete": "Vor dem Löschen fragen", "rememberPositionAndSize": "Fensterposition und Größe speichern", @@ -3201,7 +3232,7 @@ "downloadMpv": "mpv herunterladen", "downloadVlc": "VLC herunterladen", "allowSingleLetterShortcutsInTextbox": "Einzelbuchstaben-Hotkeys im Textfeld erlauben", - "allowTextNavigationShortcutsInTextbox": "Tastenkürzel auf Textnavigations-Tasten (Strg+Links/Rechts, Pos1/Ende) im Textfeld zulassen", + "allowTextNavigationShortcutsInTextbox": "Tastenkombination auf Textnavigations-Tasten (Strg+Links/Rechts, Pos1/Ende) im Textfeld zulassen", "spellCheckEnglishTreatInApostropheAsIng": "Rechtschreibprüfung: Wortendung \"in\" als \"ing\" behandeln (nur Englisch)", "goToLineNumberSetsVideoPosition": "Gehe-zu-Zeile legt auch die Videoposition fest", "adjustAllTimesRememberLineSelectionChoice": "Alle Zeiten anpassen, Auswahl der Zeilen merken", @@ -3374,6 +3405,7 @@ "generalMergeSelectedLinesAndUnbreak": "Ausgewählte Zeilen zusammenführen und aufheben", "generalMergeSelectedLinesAndUnbreakCjk": "Ausgewählte Zeilen zusammenführen und ohne Leerzeichen aufheben (CJK)", "generalUnbreakNoSpaceCjk": "Ohne Leerzeichen aufheben (CJK)", + "generalToggleBreak": "Zeilenumbruch umschalten (umbrechen/verbinden)", "generalMergeSelectedLinesBilingual": "Ausgewählte Zeilen zweisprachig zusammenführen", "generalToggleTranslationMode": "Wechsel zum Übersetzungsmodus", "generalToggleTranslationAndOriginalInPreviews": "Übersetzung und Original in Video-/Audiovorschau umschalten", @@ -3390,6 +3422,7 @@ "generalToggleItalic": "Kursiv umschalten", "generalToggleBold": "Fett umschalten", "generalToggleUnderline": "Unterstreichung umschalten", + "generalToggleBox": "Box umschalten (EBU STL)", "fileOpen": "Öffnen", "fileOpenKeepVideo": "Öffnen (Video behalten)", "fileSave": "Speichern", @@ -3405,7 +3438,8 @@ "fillSelectedLinesWithClipboard": "Ausgewählte Zeilen mit Text aus Zwischenablage füllen", "listInverseSelection": "Auswahl umkehren", "listDeleteSelection": "Auswahl löschen", - "rippleDeleteSelection": "Ripple-Löschen der Auswahl", + "deleteSelectionEverywhere": "Auswahl löschen (überall)", + "rippleDeleteSelection": "Auswahl löschen und aufrücken", "assigned": "Zugewiesen", "unassigned": "Nicht zugewiesen", "pressedKeyX": "Gedrückte Taste: {0}", @@ -3423,6 +3457,8 @@ "resetShortcutsDetail": "Möchten Sie alle Tastenkombinationen auf die Standardwerte zurücksetzen?", "togglePlayPause": "Abspielen/Pause", "toggleLockTimeCodes": "Sperrzeitcodes umschalten", + "toggleTimeCodeMode": "Zeitcode-Modus umschalten (Zeit/Frames/Frame-Nummern)", + "toggleScreenPrivacy": "Bildschirm-Privatsphäre umschalten (Dateinamen ausblenden, dann auch Texte)", "duplicateSelectedLines": "Zeile duplizieren", "sourceView": "Quelltext anzeigen", "addOrEditBookmark": "Lesezeichen hinzufügen oder bearbeiten", @@ -3484,7 +3520,7 @@ "exportPlainText": "Nur-Text exportieren", "toggleRightToLeft": "Rechts-nach-links umschalten", "modifySelection": "Auswahl ändern", - "adjustDurations": "Längen anpassen", + "adjustDurations": "Dauer anpassen", "applyDurationLimits": "Dauergrenzen anwenden", "batchConvert": "Stapelkonvertierung", "applyMinGap": "Minimale Anzeigezeit zwischen Untertitel", From fa7e02f2b93f2dffed2b2e14ceda00fe03129963 Mon Sep 17 00:00:00 2001 From: niksedk Date: Tue, 13 Oct 2026 04:54:13 +0200 Subject: [PATCH 097/306] Auto-translate advanced settings: scroll cards when window is clamped (#15973) At high display scaling the window is clamped to the screen height and the lower settings and OK/Cancel were cut off. The cards now sit in a ScrollViewer with the header and buttons fixed. Co-Authored-By: Claude Opus 5.5 --- .../LlamaCppAdvancedSettingsWindow.cs | 25 ++++++++++++++++--- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/src/ui/Features/Translate/LlamaCppAdvanced/LlamaCppAdvancedSettingsWindow.cs b/src/ui/Features/Translate/LlamaCppAdvanced/LlamaCppAdvancedSettingsWindow.cs index 1d431eea10f..9891a353f3f 100644 --- a/src/ui/Features/Translate/LlamaCppAdvanced/LlamaCppAdvancedSettingsWindow.cs +++ b/src/ui/Features/Translate/LlamaCppAdvanced/LlamaCppAdvancedSettingsWindow.cs @@ -58,22 +58,39 @@ private static Border BuildContent(LlamaCppAdvancedSettingsViewModel vm) var buttonCancel = UiUtil.MakeButtonCancel(vm.CancelCommand); var buttonPanel = UiUtil.MakeButtonBar(buttonOk, buttonCancel); - var stack = new StackPanel + var cards = new StackPanel { Orientation = Orientation.Vertical, Spacing = 12, Children = { - header, MakeCard(BuildContextSection(vm)), MakeCard(BuildSamplingSection(vm)), - buttonPanel, }, }; + // The cards scroll while the header and OK/Cancel stay put: at high display scaling + // (e.g. 175%) the window is clamped to the screen height and the lower settings and + // buttons were cut off with no way to reach them (#15973). + var scrollViewer = new ScrollViewer + { + Content = cards, + HorizontalScrollBarVisibility = Avalonia.Controls.Primitives.ScrollBarVisibility.Disabled, + VerticalScrollBarVisibility = Avalonia.Controls.Primitives.ScrollBarVisibility.Auto, + }; + + var grid = new Grid + { + RowDefinitions = new RowDefinitions("Auto,*,Auto"), + RowSpacing = 12, + }; + grid.Add(header, 0, 0); + grid.Add(scrollViewer, 1, 0); + grid.Add(buttonPanel, 2, 0); + return new Border { - Child = stack, + Child = grid, Padding = UiUtil.MakeWindowMargin(), }; } From 33f6ea8715f280851914a728ccf5fd33e8bfa1c2 Mon Sep 17 00:00:00 2001 From: niksedk Date: Tue, 13 Oct 2026 05:21:29 +0200 Subject: [PATCH 098/306] macOS: show Help search field for translated menu bars (#15980) AppKit only adds the search field to the menu it recognizes as the Help menu. Nothing set NSApplication.helpMenu, so it fell back to matching the title "Help", which fails when the UI language is not English. Register the Help submenu explicitly on each window activation, next to the Window menu registration. Co-Authored-By: Claude Opus 5.5 --- .../Features/Main/Layout/InitNativeMacMenu.cs | 1 + .../Main/Layout/MacHelpSearchInterop.cs | 64 +++++++++++++++++++ 2 files changed, 65 insertions(+) diff --git a/src/ui/Features/Main/Layout/InitNativeMacMenu.cs b/src/ui/Features/Main/Layout/InitNativeMacMenu.cs index 6050be07727..bb2c938ac51 100644 --- a/src/ui/Features/Main/Layout/InitNativeMacMenu.cs +++ b/src/ui/Features/Main/Layout/InitNativeMacMenu.cs @@ -148,6 +148,7 @@ public static void MakeStructure(NativeMenu root, Window window) UpdateWindowMenus(); } + MacHelpSearchInterop.TryRegisterHelpMenu(Clean(Se.Language.Main.Menu.HelpTitle)); InstallDockMenu(); }, Avalonia.Threading.DispatcherPriority.Background); }; diff --git a/src/ui/Features/Main/Layout/MacHelpSearchInterop.cs b/src/ui/Features/Main/Layout/MacHelpSearchInterop.cs index be3ad496a6f..e47e53ff5fe 100644 --- a/src/ui/Features/Main/Layout/MacHelpSearchInterop.cs +++ b/src/ui/Features/Main/Layout/MacHelpSearchInterop.cs @@ -17,6 +17,11 @@ namespace Nikse.SubtitleEdit.Features.Main.Layout; /// with objc_allocateClassPair and [UnmanagedCallersOnly] method implementations. Every /// step is defensive: on any failure the Help search simply keeps its menu-items-only /// behavior. +/// +/// AppKit only adds the search field to the menu it knows is the Help menu. Without +/// NSApplication.helpMenu it guesses from the title, which only matches "Help" in the +/// app's development language, so registers it +/// explicitly for translated menu bars (#15980). ///
internal static unsafe partial class MacHelpSearchInterop { @@ -52,6 +57,12 @@ internal static unsafe partial class MacHelpSearchInterop [LibraryImport(LibObjC, EntryPoint = "objc_msgSend")] private static partial void SendVoidPtr(IntPtr receiver, IntPtr sel, IntPtr arg); + [LibraryImport(LibObjC, EntryPoint = "objc_msgSend")] + private static partial IntPtr SendPtrLong(IntPtr receiver, IntPtr sel, long arg); + + [LibraryImport(LibObjC, EntryPoint = "objc_msgSend")] + private static partial long SendLong(IntPtr receiver, IntPtr sel); + [LibraryImport(LibObjC, StringMarshalling = StringMarshalling.Utf8, EntryPoint = "objc_msgSend")] private static partial IntPtr SendPtrUtf8(IntPtr receiver, IntPtr sel, string arg); @@ -128,6 +139,59 @@ public static void TryRegister() } } + /// + /// Finds the top-level menu named in the current + /// NSMenuBar and registers it as NSApplication.helpMenu, so the Help search field + /// shows up whatever the UI language. Each editor window has its own NSMenuBar, so + /// this runs again per activation. Returns true when the Help menu is registered. + /// + public static bool TryRegisterHelpMenu(string helpMenuTitle) + { + if (!OperatingSystem.IsMacOS()) + { + return false; + } + + try + { + var nsApp = SendPtr(objc_getClass("NSApplication"), sel_registerName("sharedApplication")); + var mainMenu = nsApp == IntPtr.Zero ? IntPtr.Zero : SendPtr(nsApp, sel_registerName("mainMenu")); + if (mainMenu == IntPtr.Zero) + { + return false; + } + + var submenu = IntPtr.Zero; + var count = SendLong(mainMenu, sel_registerName("numberOfItems")); + for (long i = 0; i < count; i++) + { + var item = SendPtrLong(mainMenu, sel_registerName("itemAtIndex:"), i); + if (item != IntPtr.Zero && + string.Equals(ToManagedString(SendPtr(item, sel_registerName("title"))), helpMenuTitle, StringComparison.Ordinal)) + { + submenu = SendPtr(item, sel_registerName("submenu")); + break; + } + } + + if (submenu == IntPtr.Zero) + { + return false; + } + + if (SendPtr(nsApp, sel_registerName("helpMenu")) != submenu) + { + SendVoidPtr(nsApp, sel_registerName("setHelpMenu:"), submenu); + } + + return SendPtr(nsApp, sel_registerName("helpMenu")) == submenu; + } + catch + { + return false; + } + } + // AppKit calls this on a background queue for every keystroke in the Help search // field. The block must be called exactly once, so failures still report "no hits". [UnmanagedCallersOnly] From 80b6e124d7afb8865b285ad11d5fbac7d23a2512 Mon Sep 17 00:00:00 2001 From: niksedk Date: Tue, 13 Oct 2026 05:37:19 +0200 Subject: [PATCH 099/306] Add Breeze TTS 2 (audio.cpp) TTS engine (#15957) BreezeBlue's Breeze TTS 2 (family breeze_tts) on the shared audio.cpp runtime: English and Chinese at 24 kHz. The "Default" voice is designed from the free-text voice instruction; a cloned voice uses the reference and its transcript, and the instruction then directs the delivery. The instruction goes through the existing instruction box, Cast and Review plumbing (TtsInstructionSwap). The weights and the generated audio are research / non-commercial only (BreezeBlue Research and Non-Commercial License), so the first download goes through the model-licence window, like Fish S2 Pro. No new language strings: the licence window reuses the shared button and link texts. Q8_0 (5.1 GB) is the default and BF16 the alternative, both size- and SHA-256-checked. No F32: upstream warns it mispronounces in f32. Co-Authored-By: Claude Opus 5.5 --- docs/features/text-to-speech.md | 11 +- docs/third-party-components.md | 6 +- src/ui/DependencyInjectionExtensions.cs | 1 + .../ActorVoices/ActorVoiceMappingViewModel.cs | 4 +- .../ActorVoiceRowSettingsViewModel.cs | 3 +- .../AudioCppTtsSettingsViewModel.cs | 14 + .../DownloadTts/DownloadTtsViewModel.cs | 135 +++ .../TextToSpeech/Engines/BreezeTtsAudioCpp.cs | 938 ++++++++++++++++++ .../TextToSpeech/Engines/TtsEngineCatalog.cs | 5 + .../Engines/VoiceCloneImporter.cs | 1 + .../Engines/VoiceReferenceTranscript.cs | 4 +- .../ReviewSpeech/ReviewSpeechViewModel.cs | 4 +- .../TextToSpeech/TextToSpeechViewModel.cs | 32 +- .../Video/TextToSpeech/TextToSpeechWindow.cs | 2 + .../Video/TextToSpeech/TtsEngineInstaller.cs | 13 + .../TextToSpeech/TtsEngineSettingsDialog.cs | 5 + .../Video/TextToSpeech/TtsInstructionSwap.cs | 7 + .../Video/TextToSpeech/TtsVoiceInstaller.cs | 1 + .../VoiceSettings/VoiceSettingsViewModel.cs | 25 + src/ui/Logic/Config/SeVideoTextToSpeech.cs | 7 + .../BreezeTtsAudioCppDownloadService.cs | 123 +++ src/ui/Logic/Download/DownloadHashManager.cs | 18 + .../Engines/AudioCppRuntimeBuildInfoTests.cs | 16 +- .../Engines/BreezeTtsAudioCppTests.cs | 88 ++ 24 files changed, 1448 insertions(+), 15 deletions(-) create mode 100644 src/ui/Features/Video/TextToSpeech/Engines/BreezeTtsAudioCpp.cs create mode 100644 src/ui/Logic/Download/BreezeTtsAudioCppDownloadService.cs create mode 100644 tests/UI/Features/Video/TextToSpeech/Engines/BreezeTtsAudioCppTests.cs diff --git a/docs/features/text-to-speech.md b/docs/features/text-to-speech.md index d48f4d2a594..24e3968df1b 100644 --- a/docs/features/text-to-speech.md +++ b/docs/features/text-to-speech.md @@ -124,6 +124,7 @@ The next few lines are generated ahead while the video plays. If the engine fall - **Pocket TTS (CrispASR)** — Kyutai Pocket TTS 100M; the smallest and fastest cloning engine (one 124-365 MB GGUF per language, 6 languages), and the reference is sent per request - **Higgs Audio v3 (audio.cpp)** — Boson AI Higgs Audio v3 4B on the audio.cpp runtime; zero-shot cloning in 100+ languages, per-request reference so switching voice does not restart the server - **Fish Audio S2 Pro (audio.cpp)** — Fish Audio S2 Pro on the audio.cpp runtime; zero-shot cloning in 80+ languages at 44.1 kHz, per-request reference +- **Breeze TTS 2 (audio.cpp)** — BreezeBlue's Breeze TTS 2 on the audio.cpp runtime; English and Chinese only, at 24 kHz. The **Default** voice is designed from the voice instruction (for example "A warm female narrator with calm pacing"). With a cloned voice, the instruction directs the delivery instead (tone, emotion, pace), and the reference needs its transcript. Per-request reference. The weights are licensed for research and non-commercial use only, and that also covers the audio it generates, so it shows a licence window before the first download - **FireRedTTS3 (audio.cpp)** — FireRedTTS3-Base on the audio.cpp runtime; zero-shot cloning in 24 languages at 24 kHz with the best published speaker similarity of the open models, per-request reference. Pick the language explicitly (there is no auto-detection); numbers and dates are only spelled out for English, Chinese and Cantonese. Every reference needs its transcript — without one the model produces noise, so a voice imported without a transcript is asked for it when picked, and [Clone from video](#clone-from-video-voice-of-each-line) needs the original subtitle loaded. It clones best when the reference and the text are in the same language; for dubbing a video into another language, Fish Audio S2 Pro or CosyVoice3 hold up much better Local downloadable engines are installed into the Subtitle Edit data folder when you accept the download prompt. @@ -160,7 +161,7 @@ Notes on picking one: - **Highest output rate:** VoxCPM2 and dots.tts at 48 kHz, then Zonos and Supertonic at 44.1 kHz. - **MOSS-TTS is by far the largest** because its Qwen3-8B backbone needs a ~3.5 GB codec companion on top of the backbone quant. Check free disk space before selecting it. - Quantized engines follow the same rule as the speech-to-text models: `Q4_K` is the small fast default, `Q8_0` is close to full precision, and `F16` is rarely worth the extra gigabytes. -- **Most of the CrispASR engines load their reference voice at server start**, so switching voice reloads the model. The exceptions are **Pocket TTS**, **VibeVoice**, **MOSS-TTS**, **CosyVoice3** and **VoxCPM2** (per-request reference) and **Qwen3 TTS** with the Voice clone model — those, the four audio.cpp engines (**IndexTTS 2.5**, **Higgs Audio v3**, **Fish Audio S2 Pro**, **FireRedTTS3**) and the standalone OmniVoice TTS engine are what [Clone From Video (Voice of Each Line)](#clone-from-video-voice-of-each-line) can use. +- **Most of the CrispASR engines load their reference voice at server start**, so switching voice reloads the model. The exceptions are **Pocket TTS**, **VibeVoice**, **MOSS-TTS**, **CosyVoice3** and **VoxCPM2** (per-request reference) and **Qwen3 TTS** with the Voice clone model — those, the audio.cpp cloning engines (**IndexTTS 2.5**, **Higgs Audio v3**, **Fish Audio S2 Pro**, **Breeze TTS 2**, **FireRedTTS3**) and the standalone OmniVoice TTS engine are what [Clone From Video (Voice of Each Line)](#clone-from-video-voice-of-each-line) can use. ## Engine Settings @@ -197,7 +198,7 @@ Right-click the voice combo box for **Rename voice...** and **Delete voice...**. - **Rename voice...** moves the recording together with its sidecar files (the `.txt` transcript, engine JSON) and the cached prepared copy, so nothing is left behind as an orphan. Spaces are stored as underscores, which is what the engines show as spaces. - **Delete voice...** asks for confirmation, then removes the recording and its files from disk. -Supported for every file-backed cloning voice: the CrispASR engines (Chatterbox, Confucius4-TTS, CosyVoice3, dots.tts, IndexTTS, MOSS-TTS, OmniVoice, Pocket TTS, Qwen3 TTS, VibeVoice, VoxCPM2), the standalone OmniVoice TTS, and the audio.cpp engines (IndexTTS 2.5, Higgs Audio v3, Fish Audio S2 Pro, FireRedTTS3). +Supported for every file-backed cloning voice: the CrispASR engines (Chatterbox, Confucius4-TTS, CosyVoice3, dots.tts, IndexTTS, MOSS-TTS, OmniVoice, Pocket TTS, Qwen3 TTS, VibeVoice, VoxCPM2), the standalone OmniVoice TTS, and the audio.cpp engines (IndexTTS 2.5, Higgs Audio v3, Fish Audio S2 Pro, Breeze TTS 2, FireRedTTS3). ### Voice Manager @@ -205,7 +206,7 @@ Click the voice-manager button (the head-and-microphone icon next to the voice c - **Engines** that support voice cloning are listed on the left; pick one to list its voices with type (cloned voice or built-in preset), duration, format and whether a transcript is present. Search filters the list. - **Play** (Space, or double-click) plays a cloned voice's reference recording; the waveform below shows the whole clip with a playhead, and clicking the waveform seeks. -- **Transcript** - for engines that read the transcript of the reference recording (ref-text), the text is shown under the waveform and can be edited and saved, or filled in with **Use speech-to-text...**. The hint says whether the engine requires it (CosyVoice3, Fish Audio S2 Pro, Qwen3 TTS, OmniVoice TTS) or only benefits from it (F5-TTS, FireRedTTS3, Higgs Audio, MOSS-TTS, OmniVoice CrispASR, VoxCPM2). Engines that clone from a speaker embedding do not use one. +- **Transcript** - for engines that read the transcript of the reference recording (ref-text), the text is shown under the waveform and can be edited and saved, or filled in with **Use speech-to-text...**. The hint says whether the engine requires it (CosyVoice3, Fish Audio S2 Pro, Breeze TTS 2, Qwen3 TTS, OmniVoice TTS) or only benefits from it (F5-TTS, FireRedTTS3, Higgs Audio, MOSS-TTS, OmniVoice CrispASR, VoxCPM2). Engines that clone from a speaker embedding do not use one. - **Rename** (F2) and **Delete** (Del) work as in the combo box menu. - **Copy voice to engine...** imports the recording and its transcript into another cloning engine, resampled to what that engine wants. A transcript is asked for when the target requires one and none is stored. Voices with the same name in the target are confirmed first. - **Import voice...** adds a recording (or, for Piper, a voice model) to the current engine, with the same transcript prompt as the voice settings dialog. @@ -245,8 +246,8 @@ For dubbing a video with several speakers there is a faster way than cloning eac Before generating, Subtitle Edit cuts one short reference clip per line out of the video's audio. Lines shorter than about three seconds are grown into the silence around them, but never into the neighbouring line — a reference with two speakers in it would clone the wrong person. -- **Supported engines:** OmniVoice TTS, Pocket TTS (CrispASR), VibeVoice (CrispASR), MOSS-TTS (CrispASR), CosyVoice3 (CrispASR), VoxCPM2 (CrispASR), Qwen3 TTS (CrispASR) with the Voice clone model, and the audio.cpp engines IndexTTS 2.5, Higgs Audio v3, Fish Audio S2 Pro and FireRedTTS3. The entry only appears for engines that accept a reference for each line; engines that load their reference when their server starts would have to reload the model for every line. -- **Reference text:** if you have the original subtitle loaded next to the translation, its lines are used as the transcript of the clips — that is what the video actually says, and it makes cloning noticeably better. Without an original loaded the clips have no transcript (the translated line is never used as one, since telling the model the clip already says the target text makes it replay the clip instead of speaking the line). What happens then depends on the engine: CosyVoice3 transcribes the clip itself, IndexTTS 2.5, Higgs Audio v3 and Fish Audio S2 Pro clone from the audio alone, and Qwen3 TTS and FireRedTTS3 need the transcript — FireRedTTS3 refuses to start without an original subtitle loaded, and Qwen3 speaks such lines with the first ordinary voice. +- **Supported engines:** OmniVoice TTS, Pocket TTS (CrispASR), VibeVoice (CrispASR), MOSS-TTS (CrispASR), CosyVoice3 (CrispASR), VoxCPM2 (CrispASR), Qwen3 TTS (CrispASR) with the Voice clone model, and the audio.cpp engines IndexTTS 2.5, Higgs Audio v3, Fish Audio S2 Pro, Breeze TTS 2 and FireRedTTS3. The entry only appears for engines that accept a reference for each line; engines that load their reference when their server starts would have to reload the model for every line. +- **Reference text:** if you have the original subtitle loaded next to the translation, its lines are used as the transcript of the clips — that is what the video actually says, and it makes cloning noticeably better. Without an original loaded the clips have no transcript (the translated line is never used as one, since telling the model the clip already says the target text makes it replay the clip instead of speaking the line). What happens then depends on the engine: CosyVoice3 transcribes the clip itself, IndexTTS 2.5, Higgs Audio v3, Fish Audio S2 Pro and Breeze TTS 2 clone from the audio alone, and Qwen3 TTS and FireRedTTS3 need the transcript — FireRedTTS3 refuses to start without an original subtitle loaded, and Qwen3 speaks such lines with the first ordinary voice. - **Language:** the engines clone best when the voice in the video speaks the language you are generating. Fish Audio S2 Pro and CosyVoice3 handle a video in one language dubbed into another well; FireRedTTS3 does not (its single language tag covers both the clip's transcript and the text, and the result garbles or mixes the two languages). - **Test voice** previews the clone taken from the longest line of the subtitle. - Quality depends on the source audio. Loud music or two people talking over each other in a line makes that line's clone worse. Longer subtitle lines clone better than very short ones, so a subtitle segmented into full sentences gives the best result. diff --git a/docs/third-party-components.md b/docs/third-party-components.md index c42d3997011..6c6aefdf89b 100644 --- a/docs/third-party-components.md +++ b/docs/third-party-components.md @@ -150,12 +150,12 @@ Subtitle Edit 5 can download local TTS servers and models from the **Text to spe * **Piper:** Stored in `[Data Folder]/TextToSpeech/Piper`. A custom voice is an `.onnx` model with its `.onnx.json` beside it in that folder (imported from the voice settings dialog or copied in by hand). ### audio.cpp (Text-to-Speech runtime) -The runtime behind **IndexTTS 2.5**, **Higgs Audio v3**, **Fish Audio S2 Pro**, **FireRedTTS3** and **KugelAudio**. One install serves all five engines, and each of them prompts for it on first use. +The runtime behind **IndexTTS 2.5**, **Higgs Audio v3**, **Fish Audio S2 Pro**, **Breeze TTS 2**, **FireRedTTS3** and **KugelAudio**. One install serves all six engines, and each of them prompts for it on first use. * **Download:** Built by Subtitle Edit's own workflow from upstream audio.cpp and published in [SubtitleEdit/support-files releases](https://github.com/SubtitleEdit/support-files/releases) (upstream only ships Windows builds). CPU, CUDA and Vulkan builds for Windows and Linux x64, and a Metal build for macOS Apple Silicon. * **Destination:** `[Data Folder]/audio.cpp` - `audiocpp_server.exe` (`audiocpp_server` on Linux/macOS) plus a `BUILD-INFO.txt` listing the model families the build was compiled with. Subtitle Edit reads that file and offers an update when an engine needs a family the installed build lacks, so keep it next to the binary if you install by hand. -* **Models:** GGUF weights come straight from Hugging Face (`audio-cpp/audio.cpp-gguf`) and go into `[Data Folder]/audio.cpp/models/-GGUF`: `IndexTTS2.5-GGUF`, `Higgs-Audio-v3-TTS-4B-GGUF`, `Fish-Audio-S2-Pro-GGUF`, `FireRedTTS3-Base-GGUF` and `KugelAudio-0-Open-GGUF`. IndexTTS 2.5, Higgs Audio v3 and Fish Audio S2 Pro show a licence window before the first download. -* **Voices:** Reference recordings are kept per engine under `[Data Folder]/TextToSpeech/IndexTts25AudioCpp`, `HiggsTtsAudioCpp`, `FishTtsAudioCpp` and `FireRedTts3AudioCpp`. KugelAudio has no reference recordings: its four preset voices are inside the model file. +* **Models:** GGUF weights come straight from Hugging Face (`audio-cpp/audio.cpp-gguf`) and go into `[Data Folder]/audio.cpp/models/-GGUF`: `IndexTTS2.5-GGUF`, `Higgs-Audio-v3-TTS-4B-GGUF`, `Fish-Audio-S2-Pro-GGUF`, `Breeze-TTS-2-GGUF`, `FireRedTTS3-Base-GGUF` and `KugelAudio-0-Open-GGUF`. IndexTTS 2.5, Higgs Audio v3, Fish Audio S2 Pro and Breeze TTS 2 show a licence window before the first download. +* **Voices:** Reference recordings are kept per engine under `[Data Folder]/TextToSpeech/IndexTts25AudioCpp`, `HiggsTtsAudioCpp`, `FishTtsAudioCpp`, `BreezeTtsAudioCpp` and `FireRedTts3AudioCpp`. KugelAudio has no reference recordings: its four preset voices are inside the model file. These are examples, not the full set — many more local engines are downloadable from the Text to speech window (IndexTTS, CosyVoice3, dots.tts, VoxCPM2, MOSS-TTS, Zonos, VibeVoice, Confucius4-TTS, Pocket TTS, Supertonic, and more), following the same layout: CrispASR-based engines share the `[Data Folder]/CrispASR` cache, the audio.cpp engines share `[Data Folder]/audio.cpp`, and the rest live under `[Data Folder]/TextToSpeech/`. diff --git a/src/ui/DependencyInjectionExtensions.cs b/src/ui/DependencyInjectionExtensions.cs index 2459c47f8fd..34afe8ba35f 100644 --- a/src/ui/DependencyInjectionExtensions.cs +++ b/src/ui/DependencyInjectionExtensions.cs @@ -327,6 +327,7 @@ public static void AddSubtitleEditServices(this IServiceCollection collection) collection.AddHttpClientWithProxy(); collection.AddHttpClientWithProxy(); collection.AddHttpClientWithProxy(); + collection.AddHttpClientWithProxy(); collection.AddHttpClientWithProxy(); collection.AddHttpClientWithProxy(); collection.AddHttpClientWithProxy(); diff --git a/src/ui/Features/Video/TextToSpeech/ActorVoices/ActorVoiceMappingViewModel.cs b/src/ui/Features/Video/TextToSpeech/ActorVoices/ActorVoiceMappingViewModel.cs index 4335a6f37e2..2718e16406d 100644 --- a/src/ui/Features/Video/TextToSpeech/ActorVoices/ActorVoiceMappingViewModel.cs +++ b/src/ui/Features/Video/TextToSpeech/ActorVoices/ActorVoiceMappingViewModel.cs @@ -313,7 +313,7 @@ private static bool EngineSupportsInstruction(ITtsEngine? engine) // Mirrors the engines that opt into RefreshInstructionVisibility in the main TTS VM. We // expose the field unconditionally for these engines - the cast dialog doesn't know which // sub-model the row will use, and an unused instruction is harmless. - return engine is Qwen3TtsCpp or Qwen3TtsCrispAsr or OmniVoiceTtsCpp; + return engine is Qwen3TtsCpp or Qwen3TtsCrispAsr or OmniVoiceTtsCpp or BreezeTtsAudioCpp; } // True when opening the row-settings sub-window would actually surface usable controls for @@ -322,6 +322,7 @@ private static bool EngineSupportsInstruction(ITtsEngine? engine) // - Qwen3: only if the VoiceDesign model is selected (other Qwen3 models ignore the // instruction field) // - OmniVoice: always (cloned voices still get the disabled picker with a note) + // - Breeze: always (voice design or delivery direction) // - others: never private static bool HasAdvancedSettingsForRow(ITtsEngine? engine, string? model) { @@ -330,6 +331,7 @@ private static bool HasAdvancedSettingsForRow(ITtsEngine? engine, string? model) Qwen3TtsCpp => Qwen3TtsCpp.IsVoiceDesignModel(model), Qwen3TtsCrispAsr => Qwen3TtsCrispAsr.IsVoiceDesignModel(model), OmniVoiceTtsCpp => true, + BreezeTtsAudioCpp => true, _ => false, }; } diff --git a/src/ui/Features/Video/TextToSpeech/ActorVoices/ActorVoiceRowSettingsViewModel.cs b/src/ui/Features/Video/TextToSpeech/ActorVoices/ActorVoiceRowSettingsViewModel.cs index fee7bcf2a6b..d568b2e3143 100644 --- a/src/ui/Features/Video/TextToSpeech/ActorVoices/ActorVoiceRowSettingsViewModel.cs +++ b/src/ui/Features/Video/TextToSpeech/ActorVoices/ActorVoiceRowSettingsViewModel.cs @@ -76,7 +76,8 @@ public void Initialize(ActorVoiceRow row) // selected because omnivoice-tts ignores --instruct in that case. IsInstructionTextVisible = (row.SelectedEngine is Qwen3TtsCpp && Qwen3TtsCpp.IsVoiceDesignModel(row.SelectedModel)) - || (row.SelectedEngine is Qwen3TtsCrispAsr && Qwen3TtsCrispAsr.IsVoiceDesignModel(row.SelectedModel)); + || (row.SelectedEngine is Qwen3TtsCrispAsr && Qwen3TtsCrispAsr.IsVoiceDesignModel(row.SelectedModel)) + || row.SelectedEngine is BreezeTtsAudioCpp; IsOmniVoicePickerVisible = row.SelectedEngine is OmniVoiceTtsCpp; var isDefaultOmniVoice = row.SelectedVoice?.EngineVoice is OmniVoice ov && string.IsNullOrEmpty(ov.FilePath); diff --git a/src/ui/Features/Video/TextToSpeech/AudioCppTtsSettings/AudioCppTtsSettingsViewModel.cs b/src/ui/Features/Video/TextToSpeech/AudioCppTtsSettings/AudioCppTtsSettingsViewModel.cs index 069756df263..f281eb8635f 100644 --- a/src/ui/Features/Video/TextToSpeech/AudioCppTtsSettings/AudioCppTtsSettingsViewModel.cs +++ b/src/ui/Features/Video/TextToSpeech/AudioCppTtsSettings/AudioCppTtsSettingsViewModel.cs @@ -74,6 +74,20 @@ public static class AudioCppTtsSettingsAdapters LicenseDefinition: FishTtsAudioCpp.LicenseDefinition, StartDownloadModels: (vm, modelKey) => vm.StartDownloadFishTtsAudioCppModels(modelKey)); + public static AudioCppTtsSettingsAdapter Breeze { get; } = new( + EngineName: "Breeze TTS 2 (audio.cpp)", + FamilyName: BreezeTtsAudioCpp.FamilyName, + Description: new BreezeTtsAudioCpp().Description, + ModelKeyDefault: BreezeTtsAudioCpp.ModelKeyQ8_0, + ModelKeyAlt: BreezeTtsAudioCpp.ModelKeyBf16, + ResolveModelKey: BreezeTtsAudioCpp.ResolveModelKey, + AreModelsInstalled: BreezeTtsAudioCpp.AreModelsInstalled, + GetModelsFolder: BreezeTtsAudioCpp.GetSetModelsFolder, + GetVoicesFolder: BreezeTtsAudioCpp.GetSetVoicesFolder, + IsLicenseAccepted: BreezeTtsAudioCpp.IsLicenseAccepted, + LicenseDefinition: BreezeTtsAudioCpp.LicenseDefinition, + StartDownloadModels: (vm, modelKey) => vm.StartDownloadBreezeTtsAudioCppModels(modelKey)); + public static AudioCppTtsSettingsAdapter FireRedTts3 { get; } = new( EngineName: "FireRedTTS3 (audio.cpp)", FamilyName: FireRedTts3AudioCpp.FamilyName, diff --git a/src/ui/Features/Video/TextToSpeech/DownloadTts/DownloadTtsViewModel.cs b/src/ui/Features/Video/TextToSpeech/DownloadTts/DownloadTtsViewModel.cs index d88485f69a6..76ce8788aa9 100644 --- a/src/ui/Features/Video/TextToSpeech/DownloadTts/DownloadTtsViewModel.cs +++ b/src/ui/Features/Video/TextToSpeech/DownloadTts/DownloadTtsViewModel.cs @@ -112,6 +112,10 @@ public partial class DownloadTtsViewModel : ObservableObject private Task? _downloadTaskFireRedTts3AudioCppModels; private readonly MemoryStream _downloadStreamFireRedTts3AudioCppVoices; private Task? _downloadTaskFireRedTts3AudioCppVoices; + private readonly IBreezeTtsAudioCppDownloadService _breezeTtsAudioCppDownloadService; + private Task? _downloadTaskBreezeTtsAudioCppModels; + private readonly MemoryStream _downloadStreamBreezeTtsAudioCppVoices; + private Task? _downloadTaskBreezeTtsAudioCppVoices; private readonly IKugelAudioAudioCppDownloadService _kugelAudioAudioCppDownloadService; private Task? _downloadTaskKugelAudioAudioCppModels; private readonly ISidonAudioCppDownloadService _sidonAudioCppDownloadService; @@ -160,6 +164,7 @@ public DownloadTtsViewModel(ITtsDownloadService ttsDownloadService, IZipUnpacker IHiggsTtsAudioCppDownloadService higgsTtsAudioCppDownloadService, IFishTtsAudioCppDownloadService fishTtsAudioCppDownloadService, IFireRedTts3AudioCppDownloadService fireRedTts3AudioCppDownloadService, + IBreezeTtsAudioCppDownloadService breezeTtsAudioCppDownloadService, IKugelAudioAudioCppDownloadService kugelAudioAudioCppDownloadService, ISidonAudioCppDownloadService sidonAudioCppDownloadService, ICosyVoice3CrispAsrDownloadService cosyVoice3CrispAsrDownloadService, @@ -190,6 +195,8 @@ public DownloadTtsViewModel(ITtsDownloadService ttsDownloadService, IZipUnpacker _downloadStreamFishTtsAudioCppVoices = new MemoryStream(); _fireRedTts3AudioCppDownloadService = fireRedTts3AudioCppDownloadService; _downloadStreamFireRedTts3AudioCppVoices = new MemoryStream(); + _breezeTtsAudioCppDownloadService = breezeTtsAudioCppDownloadService; + _downloadStreamBreezeTtsAudioCppVoices = new MemoryStream(); _kugelAudioAudioCppDownloadService = kugelAudioAudioCppDownloadService; _sidonAudioCppDownloadService = sidonAudioCppDownloadService; _cosyVoice3CrispAsrDownloadService = cosyVoice3CrispAsrDownloadService; @@ -1335,6 +1342,111 @@ private void OnTimerOnElapsed(object? sender, ElapsedEventArgs args) return; } + if (_downloadTaskBreezeTtsAudioCppModels is { IsCompletedSuccessfully: true }) + { + _timer.Stop(); + _downloadTaskBreezeTtsAudioCppModels = null; + + // Chain the shared reference-voice pack (the same voices.zip the other cloning + // engines use), so there are voices to clone besides the designed "Default". + var voicesFolder = BreezeTtsAudioCpp.GetSetVoicesFolder(); + var voicesAlreadyInstalled = Directory.Exists(voicesFolder) + && Directory.EnumerateFiles(voicesFolder, "*.wav").Any(); + if (voicesAlreadyInstalled) + { + OkPressed = true; + Close(); + return; + } + + TitleText = string.Format(Se.Language.General.DownloadingX, "Breeze TTS 2 reference voices"); + ProgressValue = 0; + ProgressText = Se.Language.General.StartingDotDotDot; + var voicesProgress = new Progress(number => + { + var percentage = (int)Math.Round(number * 100.0, MidpointRounding.AwayFromZero); + var pctString = percentage.ToString(CultureInfo.InvariantCulture); + ProgressValue = percentage; + ProgressText = string.Format(Se.Language.General.DownloadingXPercent, pctString); + }); + _downloadTaskBreezeTtsAudioCppVoices = _qwen3TtsCppDownloadService.DownloadVoices( + _downloadStreamBreezeTtsAudioCppVoices, voicesProgress, _cancellationTokenSource.Token); + // OnClosing disposes the timer, so restarting it from a chained download + // step threw ObjectDisposedException on a thread-pool thread (#12739). + if (!_isClosing) + { + _timer.Start(); + } + return; + } + + if (_downloadTaskBreezeTtsAudioCppVoices is { IsCompletedSuccessfully: true }) + { + _timer.Stop(); + _downloadTaskBreezeTtsAudioCppVoices = null; + if (_downloadStreamBreezeTtsAudioCppVoices.Length > 0) + { + var voicesFolder = BreezeTtsAudioCpp.GetSetVoicesFolder(); + try + { + _downloadStreamBreezeTtsAudioCppVoices.Position = 0; + _zipUnpacker.UnpackZipStream(_downloadStreamBreezeTtsAudioCppVoices, voicesFolder, string.Empty, false, new List(), null); + // The pack ships at 16 kHz; the Breeze codec runs at 24 kHz. + ResampleVoicesTo24kHz(voicesFolder); + // The pack's .txt files are attribution blurbs, not transcripts, and + // this engine passes a .txt sidecar as reference_text — a blurb there + // conditions the clone on text nobody spoke. Same cleanup as Qwen3. + Qwen3TtsCrispAsr.NormalizeVoiceTranscripts(voicesFolder); + } + catch (Exception ex) + { + Se.LogError(ex); + } + + _downloadStreamBreezeTtsAudioCppVoices.Dispose(); + } + + OkPressed = true; + Close(); + return; + } + + if (_downloadTaskBreezeTtsAudioCppVoices is { IsFaulted: true }) + { + _timer.Stop(); + _downloadTaskBreezeTtsAudioCppVoices = null; + if (_cancellationTokenSource.IsCancellationRequested) + { + ProgressText = Se.Language.General.DownloadCanceled; + Close(); + return; + } + + // A missing voice pack is not fatal — the model is already installed and the + // user can import their own reference WAV. + OkPressed = true; + Close(); + return; + } + + if (_downloadTaskBreezeTtsAudioCppModels is { IsFaulted: true }) + { + _timer.Stop(); + var ex = _downloadTaskBreezeTtsAudioCppModels.Exception?.InnerException ?? _downloadTaskBreezeTtsAudioCppModels.Exception; + if (ex is OperationCanceledException) + { + ProgressText = Se.Language.General.DownloadCanceled; + Close(); + } + else + { + ProgressText = Se.Language.General.DownloadFailed; + Error = ex?.Message ?? Se.Language.General.UnknownError; + } + + return; + } + if (_downloadTaskDotsTtsCrispAsrModels is { IsCompletedSuccessfully: true }) { // Unlike IndexTTS there is no voice pack to fetch afterwards: dots.tts clones @@ -2857,6 +2969,29 @@ public void StartDownloadFireRedTts3AudioCppModels(string? modelKey = null) _fireRedTts3AudioCppDownloadService.DownloadModels(resolved, downloadProgress, titleProgress, _cancellationTokenSource.Token); } + public void StartDownloadBreezeTtsAudioCppModels(string? modelKey = null) + { + var resolved = BreezeTtsAudioCpp.ResolveModelKey(modelKey); + var fileName = BreezeTtsAudioCpp.GetModelFileName(resolved); + TitleText = string.Format(Se.Language.General.DownloadingX, $"Breeze TTS 2 model ({resolved}): {fileName}"); + + var downloadProgress = new Progress(number => + { + var percentage = (int)Math.Round(number * 100.0, MidpointRounding.AwayFromZero); + var pctString = percentage.ToString(CultureInfo.InvariantCulture); + ProgressValue = percentage; + ProgressText = string.Format(Se.Language.General.DownloadingXPercent, pctString); + }); + + var titleProgress = new Action(title => + { + Dispatcher.UIThread.Post(() => TitleText = title); + }); + + _downloadTaskBreezeTtsAudioCppModels = + _breezeTtsAudioCppDownloadService.DownloadModels(resolved, downloadProgress, titleProgress, _cancellationTokenSource.Token); + } + public void StartDownloadKugelAudioAudioCppModels(string? modelKey = null) { var resolved = KugelAudioAudioCpp.ResolveModelKey(modelKey); diff --git a/src/ui/Features/Video/TextToSpeech/Engines/BreezeTtsAudioCpp.cs b/src/ui/Features/Video/TextToSpeech/Engines/BreezeTtsAudioCpp.cs new file mode 100644 index 00000000000..b539758f308 --- /dev/null +++ b/src/ui/Features/Video/TextToSpeech/Engines/BreezeTtsAudioCpp.cs @@ -0,0 +1,938 @@ +using Nikse.SubtitleEdit.Features.Video.TextToSpeech.ModelLicense; +using Nikse.SubtitleEdit.Features.Video.TextToSpeech.Voices; +using Nikse.SubtitleEdit.Logic.Config; +using Nikse.SubtitleEdit.Logic.Download; +using Nikse.SubtitleEdit.Logic.Media; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Net.Sockets; +using System.Text; +using System.Text.Json; +using System.Threading; +using System.Threading.Tasks; + +namespace Nikse.SubtitleEdit.Features.Video.TextToSpeech.Engines; + +/// +/// BreezeBlue's Breeze TTS 2 run through the audio.cpp runtime — the same pure C++/ggml install +/// uses (see ), so the binaries +/// are downloaded once and shared. English and Chinese only, 24 kHz output. +/// +/// Two ways to pick a voice, both steered by the free-text instruction: +/// +/// The "Default" voice has no reference WAV: the instruction describes the voice and +/// the model designs it ("voice design"). +/// A reference WAV clones that voice, and the instruction then directs the delivery +/// (tone, emotion, pace). Cloning needs the clip's transcript as reference_text. +/// +/// +/// Like the other audio.cpp engines, the server honours a per-request voice_ref, so +/// switching voice does NOT restart the server — only a model or backend change does — which +/// is also what makes per-line cloning ("Clone from video") free here. +/// +/// Licence note: the audio.cpp binaries are Apache-2.0, but the Breeze TTS 2 *weights* are +/// under the BreezeBlue Research and Non-Commercial License, which also covers the generated +/// audio, so it has to be accepted by the user before the first download — see +/// . +/// +public class BreezeTtsAudioCpp : ITtsEngine, IPerLineCloneEngine +{ + public string Name => "Breeze TTS 2 (audio.cpp)"; + public string Description => "Breeze TTS 2 voice design and voice cloning in English and Chinese, via audio.cpp"; + public bool HasLanguageParameter => false; + public bool HasApiKey => false; + public bool HasRegion => false; + public bool HasModel => true; + public bool HasKeyFile => false; + public bool SupportsVoiceCloning => true; + public bool SupportsPerLineVoiceCloning => true; + + // Q8_0 is the default: upstream measured no audible loss against BF16, and it is faster. + // There is no F32 option on purpose - upstream warns the model mispronounces and repeats + // itself in f32, as it was trained in bf16. + public const string ModelKeyQ8_0 = "Q8_0 (~5.1 GB)"; + public const string ModelKeyBf16 = "BF16 (~7.3 GB)"; + public const string DefaultModelKey = ModelKeyQ8_0; + + public const string ModelQ8_0FileName = "breeze-tts-2-q8_0.gguf"; + public const string ModelBf16FileName = "breeze-tts-2-bf16.gguf"; + + /// Family name audio.cpp registers Breeze TTS 2 under. + public const string FamilyName = "breeze_tts"; + + /// Model id used in the generated server config and in each request body. + private const string ServerModelId = "breezetts2"; + + /// The voice with no reference WAV: designed from the instruction alone. + public const string DefaultVoiceName = "Default"; + + /// + /// Exact byte sizes on the audio-cpp/audio.cpp-gguf HuggingFace repo. A truncated GGUF is + /// the single most common failure here — a download that dies partway leaves a file the + /// loader rejects with "GGUF tensor data range is out of bounds", so size is checked + /// before the server is ever started. Same guard as . + /// + private static readonly Dictionary ExpectedFileSizes = new(StringComparer.OrdinalIgnoreCase) + { + [ModelQ8_0FileName] = 5079668352L, + [ModelBf16FileName] = 7342916800L, + }; + + /// + /// Bumping this re-prompts every user with the licence window. Keep it in step with the + /// summary text in . + /// + public const string LicenseVersion = "breezeblue-research-non-commercial-1.1"; + + public static bool IsLicenseAccepted() => + string.Equals( + Se.Settings.Video.TextToSpeech.BreezeTtsAudioCppLicenseAccepted, + LicenseVersion, + StringComparison.Ordinal); + + public static void AcceptLicense() => + Se.Settings.Video.TextToSpeech.BreezeTtsAudioCppLicenseAccepted = LicenseVersion; + + /// + /// What the first-run licence gate shows. Stricter than the other gated models: the + /// non-commercial terms cover the generated audio too, not only the weights. + /// + public static ModelLicenseDefinition LicenseDefinition { get; } = new( + DialogTitle: "Breeze TTS 2 - model license", + Header: "The Breeze TTS 2 model has its own license", + Intro: "The audio.cpp engine is Apache-2.0, but the Breeze TTS 2 model weights are " + + "licensed under the BreezeBlue Research and Non-Commercial License, which is not " + + "open source. Please read the main points before downloading.", + SummaryPoints: new[] + { + "The model is free of charge for research and personal, non-commercial use only. This also covers the audio it generates.", + "Commercial use needs a separate written license from BreezeBlue (contact@breeze.blue). That includes dubbing or voice-over for a client or for a fee, and audio for monetized videos, ads or sponsored content. There is no exception for creators or small businesses.", + "If you pass the model on to someone else, it must go with a complete copy of the license and the notice: \"Breeze TTS 2 is licensed under the BreezeBlue Research and Non-Commercial License Agreement. Copyright (c) 2026 RESONIA, INC. All Rights Reserved.\"", + "You are responsible for having the right to clone any voice you use as a reference. Do not imitate a real person without their explicit consent.", + }, + LicenseUrl: "https://huggingface.co/BreezeBlue/Breeze-TTS-2/blob/main/LICENSE", + ModelPageUrl: "https://huggingface.co/BreezeBlue/Breeze-TTS-2", + AcceptCheckBoxText: "I have read and accept the BreezeBlue Research and Non-Commercial License", + Accept: AcceptLicense); + + public static string ResolveModelKey(string? modelKey) + { + if (string.IsNullOrEmpty(modelKey)) + { + var saved = Se.Settings.Video.TextToSpeech.BreezeTtsAudioCppModel; + return string.IsNullOrEmpty(saved) ? DefaultModelKey : ResolveModelKey(saved); + } + + return modelKey == ModelKeyBf16 ? ModelKeyBf16 : ModelKeyQ8_0; + } + + public static string GetModelFileName(string? modelKey) => + ResolveModelKey(modelKey) == ModelKeyBf16 ? ModelBf16FileName : ModelQ8_0FileName; + + /// + /// What goes in reference_text when the reference clip's transcript is unknown: the + /// server refuses a missing or empty transcript and accepts this. + /// + internal const string UnknownReferenceTextPlaceholder = " "; + + private static readonly HttpClient HttpClient = new() + { + Timeout = TimeSpan.FromMinutes(10), + }; + + private static readonly SemaphoreSlim ServerLock = new(1, 1); + private static Process? _serverProcess; + private static int _serverPort; + private static string? _serverLaunchCommand; + // Only the model and the backend are baked into the running server — voice is per request. + private static string? _serverModelKey; + private static string? _serverBackend; + private static string? _serverExeStamp; + private static bool _processExitHooked; + private static readonly StringBuilder _serverLog = new(); + + private static string ServerBaseUrl => $"http://127.0.0.1:{_serverPort}"; + + public Task IsInstalled(string? region) => Task.FromResult(File.Exists(AudioCppRuntime.GetServerExecutable())); + + public override string ToString() => Name; + + /// + /// Per-engine working folder under TextToSpeech (voices, synthesis output). The audio.cpp + /// binaries are shared (); this is not. + /// + public static string GetSetFolder() + { + if (!Directory.Exists(Se.TextToSpeechFolder)) + { + Directory.CreateDirectory(Se.TextToSpeechFolder); + } + + var folder = Path.Combine(Se.TextToSpeechFolder, "BreezeTtsAudioCpp"); + if (!Directory.Exists(folder)) + { + Directory.CreateDirectory(folder); + } + + return folder; + } + + /// + /// audio.cpp is pointed at a directory, not a file, so the GGUF gets its own folder under + /// the shared models root: <data>/audio.cpp/models/Breeze-TTS-2-GGUF/. + /// + public static string GetSetModelsFolder() + { + var folder = Path.Combine(AudioCppRuntime.GetSetEngineFolder(), "models", "Breeze-TTS-2-GGUF"); + if (!Directory.Exists(folder)) + { + Directory.CreateDirectory(folder); + } + + return folder; + } + + public static string GetSetVoicesFolder() + { + var folder = Path.Combine(GetSetFolder(), "voices"); + if (!Directory.Exists(folder)) + { + Directory.CreateDirectory(folder); + } + + SeedVoicesFromQwen3TtsCppIfEmpty(folder); + NormalizeVoiceTranscriptsOnce(folder); + return folder; + } + + private static bool _voiceSeedAttempted; + private static bool _voicesNormalized; + + /// + /// One-time per session: drop unusable ref-text sidecars (the shared pack ships Wikimedia + /// attribution blurbs, not transcripts) and backfill missing transcriptions from the + /// sibling OmniVoice pack. This engine passes the .txt sidecar as reference_text, so a + /// blurb there would condition the clone on text nobody spoke — same cleanup CosyVoice3 + /// and MOSS-TTS run. + /// + private static void NormalizeVoiceTranscriptsOnce(string voicesFolder) + { + if (_voicesNormalized) + { + return; + } + _voicesNormalized = true; + + Qwen3TtsCrispAsr.NormalizeVoiceTranscripts(voicesFolder); + } + + /// + /// One-time best-effort seed of reference WAVs (plus their transcript sidecars) from the + /// shared Qwen3 voice pack, so the voice combo is not empty on first run — this engine + /// has no built-in voices besides the instruction-designed "Default". Transcripts matter: + /// audio.cpp refuses a Breeze clone without reference_text. The pack ships at 16 kHz; the + /// Breeze codec runs at 24 kHz, so resample on seed. + /// + private static void SeedVoicesFromQwen3TtsCppIfEmpty(string voicesFolder) + { + if (_voiceSeedAttempted) + { + return; + } + + _voiceSeedAttempted = true; + + try + { + if (Directory.EnumerateFiles(voicesFolder, "*.wav").Any()) + { + return; + } + + var sourceFolder = Qwen3TtsCpp.GetSetVoicesFolder(); + if (!Directory.Exists(sourceFolder) || !Directory.EnumerateFiles(sourceFolder, "*.wav").Any()) + { + return; + } + + foreach (var src in Directory.GetFiles(sourceFolder, "*.wav")) + { + var dest = Path.Combine(voicesFolder, Path.GetFileName(src)); + VoiceSeedHelper.CopyOrResample(src, dest, 24000, "Breeze TTS 2 (audio.cpp)"); + + // Bring the transcript along; NormalizeVoiceTranscriptsOnce then drops the + // attribution blurbs and backfills real transcripts where a sibling has them. + var sidecar = Path.ChangeExtension(src, ".txt"); + var sidecarDest = Path.ChangeExtension(dest, ".txt"); + if (File.Exists(sidecar) && !File.Exists(sidecarDest) && File.Exists(dest)) + { + File.Copy(sidecar, sidecarDest); + } + } + } + catch (Exception ex) + { + Se.LogError(ex, "Breeze TTS 2 (audio.cpp): voice seeding from the shared voice pack failed"); + } + } + + public static string GetModelPath(string? modelKey = null) => + Path.Combine(GetSetModelsFolder(), GetModelFileName(modelKey)); + + public static bool IsValidLocalModelFile(string path, string fileName) + { + if (!File.Exists(path)) + { + return false; + } + + if (!ExpectedFileSizes.TryGetValue(fileName, out var expected)) + { + return true; + } + + try + { + var info = new FileInfo(path); + + // FileInfo.Length reports the size of the *link* for a symlink, not of its target, + // so a symlinked GGUF (a reasonable way to share a 5 GB model between apps or + // put it on another disk) would look truncated — and the caller deletes files it + // considers truncated. Resolve to the final target before measuring. + var length = info.ResolveLinkTarget(returnFinalTarget: true) is FileInfo target + ? target.Length + : info.Length; + + return length == expected; + } + catch + { + return false; + } + } + + public static bool AreModelsInstalled(string? modelKey = null) => + IsValidLocalModelFile(GetModelPath(modelKey), GetModelFileName(modelKey)); + + public static DownloadHashManager.UpdateStatus GetEngineUpdateStatus() + { + var exe = AudioCppRuntime.GetServerExecutable(); + if (!File.Exists(exe)) + { + return DownloadHashManager.UpdateStatus.Unknown; + } + + var folder = Path.GetDirectoryName(exe); + return string.IsNullOrEmpty(folder) + ? DownloadHashManager.UpdateStatus.Unknown + : DownloadHashManager.GetSidecarStatus(folder); + } + + public async Task GetVoices(string language) + { + // "Default" has no reference: the instruction designs the voice. Every WAV in the + // voices folder is a clone reference. + var result = new List + { + new Voice(new IndexTtsVoice(DefaultVoiceName, string.Empty)), + }; + + var voicesFolder = await Task.Run(GetSetVoicesFolder); + if (Directory.Exists(voicesFolder)) + { + foreach (var file in Directory.GetFiles(voicesFolder, "*.wav")) + { + var name = Path.GetFileNameWithoutExtension(file).Replace('_', ' '); + result.Add(new Voice(new IndexTtsVoice(name, file))); + } + } + + return result.ToArray(); + } + + public bool IsVoiceInstalled(Voice voice) => true; + + public Task GetRegions() => Task.FromResult(Array.Empty()); + + public Task GetModels() => Task.FromResult(new[] { ModelKeyQ8_0, ModelKeyBf16 }); + + // The model reads English or Chinese from the text itself, so there is no language pick. + public Task GetLanguages(Voice voice, string? model) => + Task.FromResult(Array.Empty()); + + public Task RefreshVoices(string language, CancellationToken cancellationToken) => + GetVoices(language); + + /// + /// : the server takes voice_ref as a path per request, + /// so the voice simply points at the cut clip - nothing is staged into this engine's own + /// folders. audio.cpp resamples the reference itself, so the 24 kHz clip is used as cut. + /// A clip with no transcript beside it (no original-language subtitle loaded, so what the + /// video says is unknown) is still a usable reference: sends the blank + /// placeholder transcript for it, which clones fine. + /// + public Voice? MakePerLineCloneVoice(string clipFileName, string voiceName) => + new Voice(new IndexTtsVoice(voiceName, clipFileName)); + + /// The clip's own path, which is exactly what the voice carries. + public string? GetPerLineReferenceClip(Voice voice) => + voice.EngineVoice is IndexTtsVoice indexVoice && !string.IsNullOrEmpty(indexVoice.FilePath) + ? indexVoice.FilePath + : null; + + /// + /// : nothing is ever staged (the voice points straight at + /// the clip), so there is nothing to clear between runs. + /// + public void ResetStagedPerLineReferences() + { + } + + public async Task Speak( + string text, + string outputFolder, + Voice voice, + TtsLanguage? language, + string? region, + string? model, + CancellationToken cancellationToken) + { + if (voice.EngineVoice is not IndexTtsVoice indexVoice) + { + throw new ArgumentException("Voice is not an IndexTtsVoice"); + } + + if (!IsLicenseAccepted()) + { + throw new InvalidOperationException( + "The Breeze TTS 2 model licence (BreezeBlue Research and Non-Commercial License) has not been accepted."); + } + + var modelKey = ResolveModelKey(model); + await EnsureServerRunningAsync(modelKey, cancellationToken); + + var outputFileName = Path.Combine(TtsOutputFolder.Resolve(outputFolder, GetSetFolder), Guid.NewGuid() + ".wav"); + + // Voice description without a reference, delivery direction with one. + var instruction = Se.Settings.Video.TextToSpeech.BreezeTtsAudioCppInstruction; + + string? referenceText = null; + if (!string.IsNullOrEmpty(indexVoice.FilePath)) + { + // The transcript of the reference WAV, from the shared .txt sidecar convention. + referenceText = ChatterboxTtsCpp.TryReadReferenceTranscript(indexVoice.FilePath); + if (string.IsNullOrWhiteSpace(referenceText)) + { + Se.WriteToolsLog($"Breeze TTS 2 (audio.cpp): no transcript beside reference '{indexVoice.FilePath}', cloning with a blank reference_text"); + } + } + + var payload = BuildSpeechPayload(text, indexVoice.FilePath, referenceText, instruction); + var body = JsonSerializer.Serialize(payload); + using var content = new StringContent(body, Encoding.UTF8, "application/json"); + Se.WriteToolsLog($"Breeze TTS 2 (audio.cpp): POST {ServerBaseUrl}/v1/audio/speech (voice={indexVoice}, textLen={text.Length})"); + + HttpResponseMessage response; + try + { + response = await HttpClient.PostAsync($"{ServerBaseUrl}/v1/audio/speech", content, cancellationToken); + } + catch (HttpRequestException ex) + { + var serverLog = SnapshotServerLog(); + var launchCommand = _serverLaunchCommand; + var died = _serverProcess?.HasExited == true; + if (died) + { + StopServerInternal(); + } + + var failMsg = $"Breeze TTS 2 (audio.cpp) request failed — Voice: {indexVoice}, Text: {text}, " + + $"RequestJson: {body}, ServerExited: {died}, ServerLog: {serverLog}" + + LaunchCmdSuffix(launchCommand); + Se.LogError(ex, failMsg); + Se.WriteToolsLog(failMsg); + + throw new InvalidOperationException( + (died + ? "Breeze TTS 2 (audio.cpp) — the audiocpp_server process crashed during synthesis." + : "Breeze TTS 2 (audio.cpp) request failed — the connection to audiocpp_server was dropped.") + + (string.IsNullOrEmpty(serverLog) ? string.Empty : $"{Environment.NewLine}Server log:{Environment.NewLine}{serverLog}") + + LaunchCmdSuffix(launchCommand), + ex); + } + + using (response) + { + if (!response.IsSuccessStatusCode) + { + var errorBody = await SafeReadErrorAsync(response, cancellationToken); + var serverLog = SnapshotServerLog(); + var launchCommand = _serverLaunchCommand; + var errMsg = $"Breeze TTS 2 (audio.cpp) server error {(int)response.StatusCode} {response.StatusCode} — " + + $"Voice: {indexVoice}, Text: {text}, RequestJson: {body}, " + + $"ResponseBody: {errorBody}, ServerLog: {serverLog}" + + LaunchCmdSuffix(launchCommand); + Se.LogError(errMsg); + Se.WriteToolsLog(errMsg); + throw new InvalidOperationException( + $"Breeze TTS 2 (audio.cpp) synthesis failed ({(int)response.StatusCode}): {errorBody}" + + (string.IsNullOrEmpty(serverLog) ? string.Empty : $"{Environment.NewLine}Server log:{Environment.NewLine}{serverLog}") + + LaunchCmdSuffix(launchCommand)); + } + + await using var fileStream = File.Create(outputFileName); + await using var contentStream = await response.Content.ReadAsStreamAsync(cancellationToken); + await contentStream.CopyToAsync(fileStream, cancellationToken); + } + + return new TtsResult(outputFileName, text); + } + + /// + /// audio.cpp's OpenAI-style speech payload. Voice cloning goes through voice_ref, + /// honoured per request, so switching voice needs no server restart. Without a reference + /// the same tts model designs the voice from the instruction. + /// + internal static Dictionary BuildSpeechPayload(string text, string? referencePath, string? referenceText, string? instruction) + { + var options = new Dictionary(); + + // audio.cpp falls back to "Speak clearly and naturally." when there is none. The option + // is `instruction` in the pinned build; upstream later renamed it `instruct` but still + // accepts this name. + var trimmedInstruction = (instruction ?? string.Empty).Trim(); + if (trimmedInstruction.Length > 0) + { + options["instruction"] = trimmedInstruction; + } + + var payload = new Dictionary + { + ["model"] = ServerModelId, + ["input"] = text, + ["response_format"] = "wav", + }; + + if (!string.IsNullOrEmpty(referencePath)) + { + // audio.cpp refuses a Breeze clone with a missing or empty reference_text + // ("BreezeTTS clone requires reference_text"). A single space passes that check, so + // an unknown transcript - a per-line clip cut from the video with no + // original-language subtitle loaded - is sent as that placeholder, like Fish S2 Pro. + // What must NEVER be sent in its place is the text being spoken: the transcript and + // the target text share one prompt, and when they are the same line the model can + // replay the clip instead of speaking the line (#14480). + options["reference_text"] = string.IsNullOrWhiteSpace(referenceText) + ? UnknownReferenceTextPlaceholder + : referenceText.Trim(); + payload["voice_ref"] = new Dictionary + { + ["type"] = "path", + ["path"] = referencePath, + }; + } + + if (options.Count > 0) + { + payload["options"] = options; + } + + return payload; + } + + private static async Task EnsureServerRunningAsync(string modelKey, CancellationToken ct) + { + var backend = AudioCppRuntime.GetBackend(); + var exeStamp = AudioCppRuntime.GetServerExecutableStamp(); + + if (_serverProcess is { HasExited: false } && _serverPort != 0 + && string.Equals(_serverModelKey, modelKey, StringComparison.OrdinalIgnoreCase) + && string.Equals(_serverBackend, backend, StringComparison.OrdinalIgnoreCase) + && string.Equals(_serverExeStamp, exeStamp, StringComparison.Ordinal)) + { + return; + } + + await ServerLock.WaitAsync(ct); + try + { + if (_serverProcess is { HasExited: false } && _serverPort != 0 + && string.Equals(_serverModelKey, modelKey, StringComparison.OrdinalIgnoreCase) + && string.Equals(_serverBackend, backend, StringComparison.OrdinalIgnoreCase) + && string.Equals(_serverExeStamp, exeStamp, StringComparison.Ordinal)) + { + return; + } + + if (_serverProcess != null) + { + StopServerInternal(); + } + + var exe = AudioCppRuntime.GetServerExecutable(); + if (!File.Exists(exe)) + { + throw new FileNotFoundException( + "audio.cpp server not found. Download the Breeze TTS 2 engine first.", exe); + } + + var modelFileName = GetModelFileName(modelKey); + var modelPath = GetModelPath(modelKey); + if (!IsValidLocalModelFile(modelPath, modelFileName)) + { + throw new FileNotFoundException( + File.Exists(modelPath) + ? $"The Breeze TTS 2 model file is incomplete: {modelPath}. Delete it and download again." + : $"The Breeze TTS 2 model file is missing: {modelPath}", + modelPath); + } + + var port = FindFreeLoopbackPort(); + var configPath = WriteServerConfig(port, backend, modelKey); + + var psi = new ProcessStartInfo + { + WorkingDirectory = Path.GetDirectoryName(exe) ?? AudioCppRuntime.GetSetEngineFolder(), + FileName = exe, + UseShellExecute = false, + CreateNoWindow = true, + RedirectStandardError = true, + RedirectStandardOutput = true, + // The server writes UTF-8. Without these the reader decodes it in the OS default + // codepage, and non-ASCII text in the captured log - the line being synthesised, + // upstream's em dashes - reaches bug reports as mojibake (#13572). + StandardOutputEncoding = Encoding.UTF8, + StandardErrorEncoding = Encoding.UTF8, + }; + psi.ArgumentList.Add("--config"); + psi.ArgumentList.Add(configPath); + + var process = Process.Start(psi) + ?? throw new InvalidOperationException("Failed to start audiocpp_server (breeze_tts)"); + + var launchCommand = FormatLaunchCommand(exe, psi.ArgumentList); + _serverLaunchCommand = launchCommand; + Se.WriteToolsLog($"Breeze TTS 2 (audio.cpp) server starting — PID: {process.Id}, Cmd: {launchCommand}"); + + lock (_serverLog) _serverLog.Clear(); + process.ErrorDataReceived += (_, e) => + { + if (e.Data != null) lock (_serverLog) _serverLog.AppendLine(e.Data); + }; + process.OutputDataReceived += (_, e) => + { + if (e.Data != null) lock (_serverLog) _serverLog.AppendLine(e.Data); + }; + process.BeginErrorReadLine(); + process.BeginOutputReadLine(); + + _serverProcess = process; + _serverPort = port; + _serverModelKey = modelKey; + _serverBackend = backend; + _serverExeStamp = AudioCppRuntime.GetServerExecutableStamp(); + HookProcessExitOnce(); + + // The config uses lazy_load, so /health answers within a second or two — the 5 GB + // model is only read on the first synthesis request. A long deadline here would just + // hide a crash-at-startup (e.g. a Vulkan build on a box with no Vulkan driver, which + // dies in the loader with 0xC0000135 before printing anything). + var deadline = DateTime.UtcNow.AddMinutes(2); + while (DateTime.UtcNow < deadline) + { + ct.ThrowIfCancellationRequested(); + if (process.HasExited) + { + var tail = SnapshotServerLog(); + var exitCode = process.ExitCode; + var exitedLaunchCommand = _serverLaunchCommand; + _serverProcess = null; + _serverPort = 0; + _serverLaunchCommand = null; + _serverModelKey = null; + _serverBackend = null; + _serverExeStamp = null; + throw new InvalidOperationException( + $"audiocpp_server exited during startup (code {exitCode}). " + + AudioCppRuntime.DescribeStartupExit(exitCode, backend) + + $" Output: {tail}" + + LaunchCmdSuffix(exitedLaunchCommand)); + } + + if (await ProbeHealthAsync(port, TimeSpan.FromSeconds(2), ct)) + { + return; + } + + await Task.Delay(TimeSpan.FromSeconds(1), ct); + } + + var lastOutput = SnapshotServerLog(); + var timeoutLaunchCommand = _serverLaunchCommand; + StopServerInternal(); + throw new TimeoutException( + $"audiocpp_server did not report healthy within 2 minutes. Last output: {lastOutput}" + + LaunchCmdSuffix(timeoutLaunchCommand)); + } + finally + { + ServerLock.Release(); + } + } + + /// + /// Writes the audio.cpp server config next to the binary. lazy_load keeps startup instant; + /// the model is read on first use and then stays resident until the server is stopped. + /// + /// + /// The model entry names the GGUF file, not its folder. Given a folder, audio.cpp picks + /// model.gguf or the sole *.gguf and refuses a folder holding several - so a user who had + /// downloaded both quantizations could not start the server until one was moved out of + /// sight (#14480). The file path is unambiguous, and auxiliary paths still resolve against + /// its parent. + /// + private static string WriteServerConfig(int port, string backend, string modelKey) + { + var config = new Dictionary + { + ["host"] = "127.0.0.1", + ["port"] = port, + ["backend"] = backend, + ["threads"] = Math.Max(1, Math.Min(8, Environment.ProcessorCount / 2)), + ["lazy_load"] = true, + ["models"] = new[] + { + new Dictionary + { + ["id"] = ServerModelId, + ["family"] = FamilyName, + ["path"] = GetModelPath(modelKey), + ["task"] = "tts", + ["mode"] = "offline", + }, + }, + }; + + var configPath = Path.Combine(AudioCppRuntime.GetSetEngineFolder(), "breeze-tts-server.json"); + File.WriteAllText( + configPath, + JsonSerializer.Serialize(config, new JsonSerializerOptions { WriteIndented = true }), + Encoding.UTF8); + + Se.WriteToolsLog($"Breeze TTS 2 (audio.cpp): server config written to {configPath} (model={modelKey}, backend={backend})"); + return configPath; + } + + private static string FormatLaunchCommand(string exe, System.Collections.ObjectModel.Collection args) + { + static string Quote(string s) => + !string.IsNullOrEmpty(s) && s.IndexOfAny(new[] { ' ', '\t' }) >= 0 + ? "\"" + s.Replace("\"", "\\\"") + "\"" + : s; + + var sb = new StringBuilder(Quote(exe)); + foreach (var a in args) + { + sb.Append(' ').Append(Quote(a)); + } + + return sb.ToString(); + } + + private static string LaunchCmdSuffix(string? launchCommand) => + string.IsNullOrEmpty(launchCommand) + ? string.Empty + : $"{Environment.NewLine}Launch command: {launchCommand}"; + + private static async Task SafeReadErrorAsync(HttpResponseMessage response, CancellationToken ct) + { + try + { + return await response.Content.ReadAsStringAsync(ct); + } + catch (Exception ex) + { + return $""; + } + } + + private static string SnapshotServerLog() + { + lock (_serverLog) + { + var s = _serverLog.ToString().TrimEnd(); + return s.Length > 2000 ? s[^2000..] : s; + } + } + + private static async Task ProbeHealthAsync(int port, TimeSpan timeout, CancellationToken ct) + { + try + { + using var cts = CancellationTokenSource.CreateLinkedTokenSource(ct); + cts.CancelAfter(timeout); + using var resp = await HttpClient.GetAsync($"http://127.0.0.1:{port}/health", cts.Token); + return resp.IsSuccessStatusCode; + } + catch + { + return false; + } + } + + private static int FindFreeLoopbackPort() + { + var listener = new TcpListener(IPAddress.Loopback, 0); + listener.Start(); + try + { + return ((IPEndPoint)listener.LocalEndpoint).Port; + } + finally + { + listener.Stop(); + } + } + + private static void HookProcessExitOnce() + { + if (_processExitHooked) + { + return; + } + + _processExitHooked = true; + AppDomain.CurrentDomain.ProcessExit += (_, _) => StopServerInternal(); + } + + /// + /// Stop the audio.cpp server if running, releasing the loaded model's working set. + /// audio.cpp never unloads a model on its own once loaded, so this is the only way the + /// memory comes back. + /// + public static void StopServer() => StopServerInternal(); + + private static void StopServerInternal() + { + var p = _serverProcess; + _serverProcess = null; + _serverPort = 0; + _serverLaunchCommand = null; + _serverModelKey = null; + _serverBackend = null; + _serverExeStamp = null; + if (p == null) + { + return; + } + + try + { + if (!p.HasExited) + { + p.Kill(entireProcessTree: true); + p.WaitForExit(2000); + } + } + catch + { + // best effort + } + finally + { + p.Dispose(); + } + } + + private static string GetUniqueDestinationFileName(string folder, string baseName) + { + var candidate = Path.Combine(folder, baseName + ".wav"); + if (!File.Exists(candidate)) + { + return candidate; + } + + var number = 1; + do + { + candidate = Path.Combine(folder, $"{baseName}_{number}.wav"); + number++; + } while (File.Exists(candidate)); + + return candidate; + } + + public bool ImportVoice(string fileName) => ImportVoice(fileName, string.Empty); + + /// + /// Import with the reference transcript — the overload + /// routes to. audio.cpp cannot clone Breeze without the transcript, so getting it written + /// as the .txt sidecar at import time is what makes the voice clone well. + /// + public bool ImportVoice(string fileName, string transcript) + { + if (string.IsNullOrEmpty(fileName) || !File.Exists(fileName)) + { + return false; + } + + var voicesFolder = GetSetVoicesFolder(); + var baseName = Path.GetFileNameWithoutExtension(fileName); + var destinationFileName = GetUniqueDestinationFileName(voicesFolder, baseName); + + // audio.cpp resamples the reference itself, but importing at 24 kHz mono matches the + // Breeze codec's native rate, so the reference is only resampled once, here. + try + { + var process = FfmpegGenerator.ConvertToMono24kHzWav(fileName, destinationFileName); + if (!process.Start()) + { + return false; + } + + process.WaitForExit(); + } + catch (Exception ex) + { + Se.LogError(ex, "Breeze TTS 2 (audio.cpp) voice import failed (ffmpeg conversion)."); + return false; + } + + if (!File.Exists(destinationFileName)) + { + return false; + } + + // Caller-supplied transcript wins; otherwise fall back to a sibling .txt next to the + // source WAV. Either way the sidecar lands next to the imported voice, where Speak + // reads it back as reference_text. + try + { + var destSidecar = Path.ChangeExtension(destinationFileName, ".txt"); + if (!string.IsNullOrWhiteSpace(transcript)) + { + File.WriteAllText(destSidecar, transcript.Trim()); + } + else + { + var sourceSidecar = Path.ChangeExtension(fileName, ".txt"); + if (File.Exists(sourceSidecar) && !File.Exists(destSidecar)) + { + File.Copy(sourceSidecar, destSidecar); + } + } + } + catch (Exception ex) + { + Se.LogError(ex, "Breeze TTS 2 (audio.cpp) voice import: failed to write .txt sidecar"); + } + + return true; + } +} diff --git a/src/ui/Features/Video/TextToSpeech/Engines/TtsEngineCatalog.cs b/src/ui/Features/Video/TextToSpeech/Engines/TtsEngineCatalog.cs index a1c9591c77d..bd7d6f86370 100644 --- a/src/ui/Features/Video/TextToSpeech/Engines/TtsEngineCatalog.cs +++ b/src/ui/Features/Video/TextToSpeech/Engines/TtsEngineCatalog.cs @@ -121,6 +121,11 @@ public static List CreateVoiceCloningEngines() // under the Fish Audio Research License (first-run accept window). new FishTtsAudioCpp(), + // Breeze TTS 2 (audio.cpp) — English and Chinese only, but voice design from a + // free-text description and cloning steered by the same instruction. Same shared + // runtime; weights and output are research / non-commercial (first-run accept window). + new BreezeTtsAudioCpp(), + // FireRedTTS3-Base (audio.cpp) — cloning across 24 languages with the best published // speaker similarity of the open models (arXiv 2608.17492). Same shared runtime; // weights are Apache-2.0, so no licence gate. Needs an explicit language pick diff --git a/src/ui/Features/Video/TextToSpeech/Engines/VoiceCloneImporter.cs b/src/ui/Features/Video/TextToSpeech/Engines/VoiceCloneImporter.cs index a5053aab502..c8996d021b0 100644 --- a/src/ui/Features/Video/TextToSpeech/Engines/VoiceCloneImporter.cs +++ b/src/ui/Features/Video/TextToSpeech/Engines/VoiceCloneImporter.cs @@ -36,6 +36,7 @@ public static bool Import(ITtsEngine engine, string fileName, string? transcript F5TtsCrispAsr e => e.ImportVoice(fileName, text), FireRedTts3AudioCpp e => e.ImportVoice(fileName, text), FishTtsAudioCpp e => e.ImportVoice(fileName, text), + BreezeTtsAudioCpp e => e.ImportVoice(fileName, text), HiggsTtsAudioCpp e => e.ImportVoice(fileName, text), MossTtsCrispAsr e => e.ImportVoice(fileName, text), OmniVoiceCrispAsr e => e.ImportVoice(fileName, text), diff --git a/src/ui/Features/Video/TextToSpeech/Engines/VoiceReferenceTranscript.cs b/src/ui/Features/Video/TextToSpeech/Engines/VoiceReferenceTranscript.cs index df3ba21acdd..4774192c683 100644 --- a/src/ui/Features/Video/TextToSpeech/Engines/VoiceReferenceTranscript.cs +++ b/src/ui/Features/Video/TextToSpeech/Engines/VoiceReferenceTranscript.cs @@ -37,9 +37,11 @@ public static TranscriptRequirement GetRequirement(ITtsEngine? engine) { // CosyVoice3's s3tok tokenizer and the Qwen3 Base backend load the sidecar as ref-text // and error without it; audio.cpp answers a Fish voice_ref without reference_text with - // a server error; OmniVoice (tts.cpp) silently falls back to its default voice. + // a server error, and refuses a Breeze clone without one; OmniVoice (tts.cpp) silently + // falls back to its default voice. CosyVoice3CrispAsr => TranscriptRequirement.Required, FishTtsAudioCpp => TranscriptRequirement.Required, + BreezeTtsAudioCpp => TranscriptRequirement.Required, Qwen3TtsCrispAsr => TranscriptRequirement.Required, OmniVoiceTtsCpp => TranscriptRequirement.Required, diff --git a/src/ui/Features/Video/TextToSpeech/ReviewSpeech/ReviewSpeechViewModel.cs b/src/ui/Features/Video/TextToSpeech/ReviewSpeech/ReviewSpeechViewModel.cs index 628a3e512b6..8208af6973a 100644 --- a/src/ui/Features/Video/TextToSpeech/ReviewSpeech/ReviewSpeechViewModel.cs +++ b/src/ui/Features/Video/TextToSpeech/ReviewSpeech/ReviewSpeechViewModel.cs @@ -2364,7 +2364,8 @@ private void UpdateInstructionVisibility() IsInstructionTextVisible = (engine is Qwen3TtsCpp && Qwen3TtsCpp.IsVoiceDesignModel(model)) - || (engine is Qwen3TtsCrispAsr && Qwen3TtsCrispAsr.IsVoiceDesignModel(model)); + || (engine is Qwen3TtsCrispAsr && Qwen3TtsCrispAsr.IsVoiceDesignModel(model)) + || engine is BreezeTtsAudioCpp; IsInstructionPickerVisible = engine is OmniVoiceTtsCpp; HasInstruction = IsInstructionTextVisible || IsInstructionPickerVisible; @@ -2380,6 +2381,7 @@ private void LoadInstructionForEngine() Qwen3TtsCpp => Se.Settings.Video.TextToSpeech.Qwen3TtsCppInstruction ?? string.Empty, Qwen3TtsCrispAsr => Se.Settings.Video.TextToSpeech.Qwen3TtsCppInstruction ?? string.Empty, OmniVoiceTtsCpp => Se.Settings.Video.TextToSpeech.OmniVoiceTtsCppInstruction ?? string.Empty, + BreezeTtsAudioCpp => Se.Settings.Video.TextToSpeech.BreezeTtsAudioCppInstruction ?? string.Empty, _ => string.Empty, }; diff --git a/src/ui/Features/Video/TextToSpeech/TextToSpeechViewModel.cs b/src/ui/Features/Video/TextToSpeech/TextToSpeechViewModel.cs index f8a9039048f..f1c18329c01 100644 --- a/src/ui/Features/Video/TextToSpeech/TextToSpeechViewModel.cs +++ b/src/ui/Features/Video/TextToSpeech/TextToSpeechViewModel.cs @@ -407,6 +407,11 @@ private void SaveSettings() { Se.Settings.Video.TextToSpeech.FishTtsAudioCppModel = SelectedModel ?? FishTtsAudioCpp.DefaultModelKey; } + else if (SelectedEngine is BreezeTtsAudioCpp) + { + Se.Settings.Video.TextToSpeech.BreezeTtsAudioCppModel = SelectedModel ?? BreezeTtsAudioCpp.DefaultModelKey; + Se.Settings.Video.TextToSpeech.BreezeTtsAudioCppInstruction = (Instruction ?? string.Empty).Trim(); + } else if (SelectedEngine is FireRedTts3AudioCpp) { Se.Settings.Video.TextToSpeech.FireRedTts3AudioCppModel = SelectedModel ?? FireRedTts3AudioCpp.DefaultModelKey; @@ -495,6 +500,7 @@ private void SaveSettings() Qwen3TtsCpp => Se.Settings.Video.TextToSpeech.Qwen3TtsCppInstruction ?? string.Empty, Qwen3TtsCrispAsr => Se.Settings.Video.TextToSpeech.Qwen3TtsCppInstruction ?? string.Empty, OmniVoiceTtsCpp => Se.Settings.Video.TextToSpeech.OmniVoiceTtsCppInstruction ?? string.Empty, + BreezeTtsAudioCpp => Se.Settings.Video.TextToSpeech.BreezeTtsAudioCppInstruction ?? string.Empty, _ => string.Empty, }; @@ -515,12 +521,14 @@ private void ApplyInstructionForEngine(ITtsEngine? engine) } // Qwen3's free-text instruction only does anything on the VoiceDesign model - the 0.6B and - // 1.7B Base models are not instruction-tuned. OmniVoice always shows its keyword picker. + // 1.7B Base models are not instruction-tuned. Breeze TTS 2 always takes one (voice design + // for "Default", delivery direction for a clone). OmniVoice always shows its keyword picker. private void RefreshInstructionVisibility() { IsInstructionTextVisible = (SelectedEngine is Qwen3TtsCpp && Qwen3TtsCpp.IsVoiceDesignModel(SelectedModel)) - || (SelectedEngine is Qwen3TtsCrispAsr && Qwen3TtsCrispAsr.IsVoiceDesignModel(SelectedModel)); + || (SelectedEngine is Qwen3TtsCrispAsr && Qwen3TtsCrispAsr.IsVoiceDesignModel(SelectedModel)) + || SelectedEngine is BreezeTtsAudioCpp; IsInstructionPickerVisible = SelectedEngine is OmniVoiceTtsCpp; HasInstruction = IsInstructionTextVisible || IsInstructionPickerVisible; } @@ -1501,6 +1509,10 @@ private static void StopOtherCrispAsrServers(ITtsEngine? keepAlive) { FishTtsAudioCpp.StopServer(); } + if (keepAlive is not BreezeTtsAudioCpp) + { + BreezeTtsAudioCpp.StopServer(); + } if (keepAlive is not FireRedTts3AudioCpp) { FireRedTts3AudioCpp.StopServer(); @@ -1835,6 +1847,9 @@ private async Task RedownloadSelectedModel(ITtsEngine engine) case FishTtsAudioCpp: await _windowService.ShowDialogAsync(Window!, vm => vm.StartDownloadFishTtsAudioCppModels(FishTtsAudioCpp.ResolveModelKey(SelectedModel))); break; + case BreezeTtsAudioCpp: + await _windowService.ShowDialogAsync(Window!, vm => vm.StartDownloadBreezeTtsAudioCppModels(BreezeTtsAudioCpp.ResolveModelKey(SelectedModel))); + break; case FireRedTts3AudioCpp: await _windowService.ShowDialogAsync(Window!, vm => vm.StartDownloadFireRedTts3AudioCppModels(FireRedTts3AudioCpp.ResolveModelKey(SelectedModel))); break; @@ -1932,6 +1947,9 @@ public DownloadDotStatus GetModelDotStatus(ITtsEngine? engine, string? modelKey) FishTtsAudioCpp => FishTtsAudioCpp.AreModelsInstalled(modelKey) ? DownloadDotStatus.UpToDate : DownloadDotStatus.NotInstalled, + BreezeTtsAudioCpp => BreezeTtsAudioCpp.AreModelsInstalled(modelKey) + ? DownloadDotStatus.UpToDate + : DownloadDotStatus.NotInstalled, FireRedTts3AudioCpp => FireRedTts3AudioCpp.AreModelsInstalled(modelKey) ? DownloadDotStatus.UpToDate : DownloadDotStatus.NotInstalled, @@ -5145,6 +5163,16 @@ internal void SelectedEngineChanged(object? sender, SelectionChangedEventArgs e) IsEngineSettingsVisible = true; IsModelDownloadVisible = true; } + else if (SelectedEngine is BreezeTtsAudioCpp) + { + SelectedModel = Models.FirstOrDefault(p => p == Se.Settings.Video.TextToSpeech.BreezeTtsAudioCppModel); + if (string.IsNullOrEmpty(SelectedModel)) + { + SelectedModel = Models.FirstOrDefault(); + } + IsEngineSettingsVisible = true; + IsModelDownloadVisible = true; + } else if (SelectedEngine is FireRedTts3AudioCpp) { SelectedModel = Models.FirstOrDefault(p => p == Se.Settings.Video.TextToSpeech.FireRedTts3AudioCppModel); diff --git a/src/ui/Features/Video/TextToSpeech/TextToSpeechWindow.cs b/src/ui/Features/Video/TextToSpeech/TextToSpeechWindow.cs index a521bad9689..ce293a4890f 100644 --- a/src/ui/Features/Video/TextToSpeech/TextToSpeechWindow.cs +++ b/src/ui/Features/Video/TextToSpeech/TextToSpeechWindow.cs @@ -201,6 +201,8 @@ private static DownloadDotStatus GetTtsEngineDotStatus(ITtsEngine engine) return StatusDots.From(engine.IsInstalled(null).Result, FishTtsAudioCpp.GetEngineUpdateStatus()); case FireRedTts3AudioCpp: return StatusDots.From(engine.IsInstalled(null).Result, FireRedTts3AudioCpp.GetEngineUpdateStatus()); + case BreezeTtsAudioCpp: + return StatusDots.From(engine.IsInstalled(null).Result, BreezeTtsAudioCpp.GetEngineUpdateStatus()); case KugelAudioAudioCpp: return StatusDots.From(engine.IsInstalled(null).Result, KugelAudioAudioCpp.GetEngineUpdateStatus()); case CosyVoice3CrispAsr: diff --git a/src/ui/Features/Video/TextToSpeech/TtsEngineInstaller.cs b/src/ui/Features/Video/TextToSpeech/TtsEngineInstaller.cs index bb00be43821..820caf2ae31 100644 --- a/src/ui/Features/Video/TextToSpeech/TtsEngineInstaller.cs +++ b/src/ui/Features/Video/TextToSpeech/TtsEngineInstaller.cs @@ -503,6 +503,19 @@ await Dispatcher.UIThread.InvokeAsync(async () => startDownloadModels: (vm, key) => vm.StartDownloadFishTtsAudioCppModels(key)); } + if (engine is BreezeTtsAudioCpp) + { + return await EnsureAudioCppEngineWithLicense( + window, windowService, refreshVoices, + engineDisplayName: "Breeze TTS 2", + requiredFamily: BreezeTtsAudioCpp.FamilyName, + licenseDefinition: BreezeTtsAudioCpp.LicenseDefinition, + isLicenseAccepted: BreezeTtsAudioCpp.IsLicenseAccepted, + modelKey: BreezeTtsAudioCpp.ResolveModelKey(model), + areModelsInstalled: BreezeTtsAudioCpp.AreModelsInstalled, + startDownloadModels: (vm, key) => vm.StartDownloadBreezeTtsAudioCppModels(key)); + } + if (engine is FireRedTts3AudioCpp) { // Apache-2.0 weights: no licence gate, otherwise the same runtime + model flow. diff --git a/src/ui/Features/Video/TextToSpeech/TtsEngineSettingsDialog.cs b/src/ui/Features/Video/TextToSpeech/TtsEngineSettingsDialog.cs index 1c6687005c6..3cea46e439e 100644 --- a/src/ui/Features/Video/TextToSpeech/TtsEngineSettingsDialog.cs +++ b/src/ui/Features/Video/TextToSpeech/TtsEngineSettingsDialog.cs @@ -46,6 +46,7 @@ VibeVoiceCrispAsr or IndexTts25AudioCpp or HiggsTtsAudioCpp or FishTtsAudioCpp or + BreezeTtsAudioCpp or FireRedTts3AudioCpp or KugelAudioAudioCpp or IndexTtsCrispAsr or @@ -98,6 +99,10 @@ public static async Task ShowAsync(ITtsEngine? engine, Window window, IWindowSer { await windowService.ShowDialogAsync(window, vm => vm.Initialize(AudioCppTtsSettingsAdapters.FireRedTts3)); } + else if (engine is BreezeTtsAudioCpp) + { + await windowService.ShowDialogAsync(window, vm => vm.Initialize(AudioCppTtsSettingsAdapters.Breeze)); + } else if (engine is KugelAudioAudioCpp) { await windowService.ShowDialogAsync(window, vm => vm.Initialize(AudioCppTtsSettingsAdapters.KugelAudio)); diff --git a/src/ui/Features/Video/TextToSpeech/TtsInstructionSwap.cs b/src/ui/Features/Video/TextToSpeech/TtsInstructionSwap.cs index 4aa7f901193..fb6cd883b61 100644 --- a/src/ui/Features/Video/TextToSpeech/TtsInstructionSwap.cs +++ b/src/ui/Features/Video/TextToSpeech/TtsInstructionSwap.cs @@ -60,6 +60,10 @@ public static async Task RunAsync(ITtsEngine? engine, string? instruction, var prevQwen = s.Qwen3TtsCppInstruction ?? string.Empty; s.Qwen3TtsCppInstruction = instruction ?? string.Empty; return prevQwen; + case BreezeTtsAudioCpp: + var prevBreeze = s.BreezeTtsAudioCppInstruction ?? string.Empty; + s.BreezeTtsAudioCppInstruction = instruction ?? string.Empty; + return prevBreeze; case OmniVoiceTtsCpp: var prevOmni = s.OmniVoiceTtsCppInstruction ?? string.Empty; s.OmniVoiceTtsCppInstruction = instruction ?? string.Empty; @@ -83,6 +87,9 @@ private static void Restore(ITtsEngine? engine, string? previous) case Qwen3TtsCrispAsr: s.Qwen3TtsCppInstruction = previous; break; + case BreezeTtsAudioCpp: + s.BreezeTtsAudioCppInstruction = previous; + break; case OmniVoiceTtsCpp: s.OmniVoiceTtsCppInstruction = previous; break; diff --git a/src/ui/Features/Video/TextToSpeech/TtsVoiceInstaller.cs b/src/ui/Features/Video/TextToSpeech/TtsVoiceInstaller.cs index 36815436892..e5fcb11e31b 100644 --- a/src/ui/Features/Video/TextToSpeech/TtsVoiceInstaller.cs +++ b/src/ui/Features/Video/TextToSpeech/TtsVoiceInstaller.cs @@ -374,6 +374,7 @@ await MessageBox.Show( IndexTts25AudioCpp.StopServer(); HiggsTtsAudioCpp.StopServer(); FishTtsAudioCpp.StopServer(); + BreezeTtsAudioCpp.StopServer(); FireRedTts3AudioCpp.StopServer(); KugelAudioAudioCpp.StopServer(); diff --git a/src/ui/Features/Video/TextToSpeech/VoiceSettings/VoiceSettingsViewModel.cs b/src/ui/Features/Video/TextToSpeech/VoiceSettings/VoiceSettingsViewModel.cs index 6f902e97a6b..ee1d8111a0c 100644 --- a/src/ui/Features/Video/TextToSpeech/VoiceSettings/VoiceSettingsViewModel.cs +++ b/src/ui/Features/Video/TextToSpeech/VoiceSettings/VoiceSettingsViewModel.cs @@ -289,6 +289,31 @@ private async Task ImportVoiceFromFileAsync(string fileName) ok = fishEngine.ImportVoice(fileName, result.Text.Trim()); } + else if (_engine is BreezeTtsAudioCpp breezeEngine) + { + // Breeze TTS 2 REQUIRES the transcript at synth time, like Fish S2 Pro: audio.cpp + // refuses a Breeze clone without reference_text. Same prompt with sibling autofill. + var transcript = TryReadSiblingTranscript(fileName) ?? string.Empty; + var audioFileName = fileName; + var result = await _windowService.ShowDialogAsync(Window!, vm => + { + vm.Initialize( + Se.Language.Video.TextToSpeech.VoiceCloneTranscriptTitle, + transcript, + 500, + 150); + vm.ConfigureExtraButton( + Se.Language.Video.TextToSpeech.UseSpeechToTextDotDotDot, + () => RunSpeechToTextAsync(audioFileName)); + }); + + if (!result.OkPressed || string.IsNullOrWhiteSpace(result.Text)) + { + return; + } + + ok = breezeEngine.ImportVoice(fileName, result.Text.Trim()); + } else if (_engine is HiggsTtsAudioCpp higgsEngine) { // Higgs clones zero-shot from audio alone; a transcription (reference_text) is diff --git a/src/ui/Logic/Config/SeVideoTextToSpeech.cs b/src/ui/Logic/Config/SeVideoTextToSpeech.cs index 0f42078db9d..0be8f83e267 100644 --- a/src/ui/Logic/Config/SeVideoTextToSpeech.cs +++ b/src/ui/Logic/Config/SeVideoTextToSpeech.cs @@ -101,6 +101,10 @@ public class SeVideoTextToSpeech public string FireRedTts3AudioCppModel { get; set; } public string FireRedTts3AudioCppLanguage { get; set; } public string KugelAudioAudioCppModel { get; set; } + public string BreezeTtsAudioCppModel { get; set; } + public string BreezeTtsAudioCppInstruction { get; set; } + // Holds the accepted BreezeBlue licence version; see BreezeTtsAudioCpp.LicenseVersion. + public string BreezeTtsAudioCppLicenseAccepted { get; set; } public string KugelAudioAudioCppVoice { get; set; } public string CosyVoice3CrispAsrModel { get; set; } public double CosyVoice3CrispAsrSpeed { get; set; } @@ -269,6 +273,9 @@ public SeVideoTextToSpeech() FireRedTts3AudioCppModel = "Q8_0 (~3.9 GB)"; FireRedTts3AudioCppLanguage = string.Empty; KugelAudioAudioCppModel = "Q4_K (~5.3 GB)"; + BreezeTtsAudioCppModel = "Q8_0 (~5.1 GB)"; + BreezeTtsAudioCppInstruction = string.Empty; + BreezeTtsAudioCppLicenseAccepted = string.Empty; KugelAudioAudioCppVoice = "default"; CosyVoice3CrispAsrModel = "Q4_K (~1.6 GB total)"; CosyVoice3CrispAsrSpeed = 1.0; diff --git a/src/ui/Logic/Download/BreezeTtsAudioCppDownloadService.cs b/src/ui/Logic/Download/BreezeTtsAudioCppDownloadService.cs new file mode 100644 index 00000000000..200ee040fa1 --- /dev/null +++ b/src/ui/Logic/Download/BreezeTtsAudioCppDownloadService.cs @@ -0,0 +1,123 @@ +using Nikse.SubtitleEdit.Features.Video.TextToSpeech.Engines; +using Nikse.SubtitleEdit.UiLogic; +using System; +using System.IO; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; + +namespace Nikse.SubtitleEdit.Logic.Download; + +public interface IBreezeTtsAudioCppDownloadService +{ + /// Downloads the selected Breeze TTS 2 GGUF into audio.cpp's models folder. + Task DownloadModels(string modelKey, IProgress? progress, Action? titleProgress, CancellationToken cancellationToken); +} + +/// +/// Model weights only: the audio.cpp engine binaries are shared with the other audio.cpp +/// engines and downloaded through . The GGUF +/// comes straight from HuggingFace and is never redistributed by us — the weights carry the +/// BreezeBlue Research and Non-Commercial License, which the user accepts in the model-licence +/// window first (see +/// ). +/// +public class BreezeTtsAudioCppDownloadService : IBreezeTtsAudioCppDownloadService +{ + private readonly HttpClient _httpClient; + + private const string ModelUrlBase = + "https://huggingface.co/audio-cpp/audio.cpp-gguf/resolve/main/Breeze-TTS-2-GGUF/"; + + public BreezeTtsAudioCppDownloadService(HttpClient httpClient) + { + _httpClient = httpClient; + } + + public async Task DownloadModels(string modelKey, IProgress? progress, Action? titleProgress, CancellationToken cancellationToken) + { + var fileName = BreezeTtsAudioCpp.GetModelFileName(modelKey); + var finalPath = BreezeTtsAudioCpp.GetModelPath(modelKey); + + // A partial GGUF is worse than none: audio.cpp rejects it with "GGUF tensor data range + // is out of bounds", which reads like a corrupt model rather than a short download. + // Size-check on the threadpool — this runs from the download dialog's init callback. + await Task.Run(() => EnsureRemovedIfInvalid(finalPath, fileName), cancellationToken); + + if (BreezeTtsAudioCpp.IsValidLocalModelFile(finalPath, fileName)) + { + return; + } + + titleProgress?.Invoke($"Downloading Breeze TTS 2 model: {fileName}"); + + var partPath = finalPath + ".part"; + try + { + await DownloadHelper.DownloadFileAsync(_httpClient, ModelUrlBase + fileName, partPath, progress, cancellationToken); + await VerifyFile(partPath, GetHashKey(fileName), fileName, cancellationToken); + File.Move(partPath, finalPath); + } + catch + { + TryDelete(partPath); + throw; + } + } + + private static string? GetHashKey(string fileName) + { + if (string.Equals(fileName, BreezeTtsAudioCpp.ModelQ8_0FileName, StringComparison.OrdinalIgnoreCase)) + { + return DownloadHashManager.BreezeTtsAudioCpp.ModelQ8_0; + } + + if (string.Equals(fileName, BreezeTtsAudioCpp.ModelBf16FileName, StringComparison.OrdinalIgnoreCase)) + { + return DownloadHashManager.BreezeTtsAudioCpp.ModelBf16; + } + + return null; + } + + private static async Task VerifyFile(string filePath, string? key, string fileName, CancellationToken cancellationToken) + { + if (string.IsNullOrEmpty(key)) + { + return; + } + + var expected = DownloadHashManager.GetLatestKnownHash(key); + if (string.IsNullOrEmpty(expected)) + { + return; + } + + string actual; + await using (var stream = File.OpenRead(filePath)) + { + actual = await Sha256Util.ComputeSha256Async(stream, cancellationToken); + } + + if (!string.Equals(expected, actual, StringComparison.OrdinalIgnoreCase)) + { + throw new IOException( + $"Breeze TTS 2 model {fileName} failed integrity check (expected SHA-256 {expected}, got {actual})."); + } + } + + private static void TryDelete(string path) + { + try { File.Delete(path); } catch { /* best-effort cleanup */ } + } + + private static void EnsureRemovedIfInvalid(string path, string fileName) + { + if (!File.Exists(path) || BreezeTtsAudioCpp.IsValidLocalModelFile(path, fileName)) + { + return; + } + + TryDelete(path); + } +} diff --git a/src/ui/Logic/Download/DownloadHashManager.cs b/src/ui/Logic/Download/DownloadHashManager.cs index 5f36832e3af..66b87b25fb0 100644 --- a/src/ui/Logic/Download/DownloadHashManager.cs +++ b/src/ui/Logic/Download/DownloadHashManager.cs @@ -333,6 +333,14 @@ public static class FireRedTts3AudioCpp public const string ModelOrig = "FireRedTts3AudioCpp.ModelOrig"; } + public static class BreezeTtsAudioCpp + { + // SHA-256 of the Breeze TTS 2 GGUFs on audio-cpp/audio.cpp-gguf (HF LFS oid). + // The engine binaries are the shared audio.cpp archives keyed under IndexTts25AudioCpp. + public const string ModelQ8_0 = "BreezeTtsAudioCpp.ModelQ8_0"; + public const string ModelBf16 = "BreezeTtsAudioCpp.ModelBf16"; + } + public static class KugelAudioAudioCpp { // SHA-256 of the KugelAudio-0-Open GGUFs on audio-cpp/audio.cpp-gguf (HF LFS oid). @@ -2526,6 +2534,16 @@ public static class WhisperX "1af06f341044121ddebb389c1e6e5181a43f65e2591f77c7eda2ca5810e484c8", // fireredtts3-base-orig.gguf }, + // Breeze TTS 2 weights, from audio-cpp/audio.cpp-gguf (HF LFS oid). + [BreezeTtsAudioCpp.ModelQ8_0] = new[] + { + "0de52d61560f9f6b2dfeca79f9100f8fce0c2b17c52ec30622e23e150df1ad88", // breeze-tts-2-q8_0.gguf + }, + [BreezeTtsAudioCpp.ModelBf16] = new[] + { + "a00c9f678b4c5ae03d1dcd228f636b329352cda200823faef4e01d3bd97c0a89", // breeze-tts-2-bf16.gguf + }, + // KugelAudio-0-Open weights, from audio-cpp/audio.cpp-gguf (HF LFS oid). [KugelAudioAudioCpp.ModelQ4_K] = new[] { diff --git a/tests/UI/Features/Video/TextToSpeech/Engines/AudioCppRuntimeBuildInfoTests.cs b/tests/UI/Features/Video/TextToSpeech/Engines/AudioCppRuntimeBuildInfoTests.cs index b98c67cf8c5..ebfb0f681db 100644 --- a/tests/UI/Features/Video/TextToSpeech/Engines/AudioCppRuntimeBuildInfoTests.cs +++ b/tests/UI/Features/Video/TextToSpeech/Engines/AudioCppRuntimeBuildInfoTests.cs @@ -42,6 +42,13 @@ public class AudioCppRuntimeBuildInfoTests "models : index_tts2,higgs_audio_tts,fish_audio,fireredtts3,ace_step,kugelaudio,sidon\n" + "backend : metal (arm64)\n"; + private const string BuildInfo2026_10_13 = + "audio.cpp build for SubtitleEdit's IndexTTS-2.5 engine\n" + + "source : https://github.com/0xShug0/audio.cpp\n" + + "ref : f04de8ec38e69c5b97c024542395278608b1450b\n" + + "models : index_tts2,higgs_audio_tts,fish_audio,fireredtts3,ace_step,kugelaudio,sidon,breeze_tts\n" + + "backend : metal (arm64)\n"; + [Fact] public void ParseBuiltModelFamilies_ReadsTheModelsLine() { @@ -73,19 +80,26 @@ public void ParseBuiltModelFamilies_NoModelsLine_IsUnknown() [Fact] public void EveryAudioCppEngineFamily_IsInThePinnedBuild() { - var families = AudioCppRuntime.ParseBuiltModelFamilies(BuildInfo2026_10_08b)!; + var families = AudioCppRuntime.ParseBuiltModelFamilies(BuildInfo2026_10_13)!; Assert.Contains(IndexTts25AudioCpp.FamilyName, families); Assert.Contains(HiggsTtsAudioCpp.FamilyName, families); Assert.Contains(FishTtsAudioCpp.FamilyName, families); Assert.Contains(FireRedTts3AudioCpp.FamilyName, families); Assert.Contains(KugelAudioAudioCpp.FamilyName, families); + Assert.Contains(BreezeTtsAudioCpp.FamilyName, families); // Video > Generate background music and the TTS window's background music. Assert.Contains(AceStepAudioCpp.FamilyName, families); // Text to speech > Advanced > Clean voice-clone references. Assert.Contains(SidonAudioCpp.FamilyName, families); } + [Fact] + public void PreviousBuild_LacksBreeze_SoItAsksForTheUpdate() + { + Assert.DoesNotContain(BreezeTtsAudioCpp.FamilyName, AudioCppRuntime.ParseBuiltModelFamilies(BuildInfo2026_10_08b)!); + } + [Fact] public void PreviousBuild_LacksSidon_SoCleaningAsksForTheUpdate() { diff --git a/tests/UI/Features/Video/TextToSpeech/Engines/BreezeTtsAudioCppTests.cs b/tests/UI/Features/Video/TextToSpeech/Engines/BreezeTtsAudioCppTests.cs new file mode 100644 index 00000000000..1b69c06ab70 --- /dev/null +++ b/tests/UI/Features/Video/TextToSpeech/Engines/BreezeTtsAudioCppTests.cs @@ -0,0 +1,88 @@ +using System.Text.Json; +using Nikse.SubtitleEdit.Features.Video.TextToSpeech.Engines; + +namespace UITests.Features.Video.TextToSpeech.Engines; + +/// +/// Breeze TTS 2 runs one audio.cpp tts model two ways: without a reference the instruction +/// designs the voice, with one it clones and the instruction directs the delivery. audio.cpp +/// refuses a clone without a non-empty reference_text, so these pin what SE sends. +/// +public class BreezeTtsAudioCppTests +{ + [Fact] + public void Engine_ClonesAndHasNoLanguagePick() + { + var engine = new BreezeTtsAudioCpp(); + + Assert.False(engine.HasLanguageParameter); + Assert.True(engine.HasModel); + Assert.True(engine.SupportsVoiceCloning); + Assert.True(engine.SupportsPerLineVoiceCloning); + Assert.Contains(TtsEngineCatalog.CreateVoiceCloningEngines(), e => e is BreezeTtsAudioCpp); + } + + [Fact] + public void BuildSpeechPayload_DefaultVoice_IsVoiceDesignWithoutReference() + { + var json = JsonSerializer.Serialize(BreezeTtsAudioCpp.BuildSpeechPayload( + "Welcome.", string.Empty, null, " A warm female narrator. ")); + using var doc = JsonDocument.Parse(json); + var root = doc.RootElement; + + Assert.Equal("breezetts2", root.GetProperty("model").GetString()); + Assert.Equal("Welcome.", root.GetProperty("input").GetString()); + Assert.Equal("A warm female narrator.", root.GetProperty("options").GetProperty("instruction").GetString()); + Assert.False(root.GetProperty("options").TryGetProperty("reference_text", out _)); + Assert.False(root.TryGetProperty("voice_ref", out _)); + } + + [Fact] + public void BuildSpeechPayload_NoInstructionAndNoReference_SendsNoOptions() + { + var json = JsonSerializer.Serialize(BreezeTtsAudioCpp.BuildSpeechPayload("Hi.", null, null, " ")); + using var doc = JsonDocument.Parse(json); + + Assert.False(doc.RootElement.TryGetProperty("options", out _)); + } + + [Fact] + public void BuildSpeechPayload_Clone_SendsReferenceTranscriptAndDirection() + { + var json = JsonSerializer.Serialize(BreezeTtsAudioCpp.BuildSpeechPayload( + "Line to speak.", "/voices/ada.wav", " What the clip says. ", "Whisper it.")); + using var doc = JsonDocument.Parse(json); + var root = doc.RootElement; + var options = root.GetProperty("options"); + + Assert.Equal("path", root.GetProperty("voice_ref").GetProperty("type").GetString()); + Assert.Equal("/voices/ada.wav", root.GetProperty("voice_ref").GetProperty("path").GetString()); + Assert.Equal("What the clip says.", options.GetProperty("reference_text").GetString()); + Assert.Equal("Whisper it.", options.GetProperty("instruction").GetString()); + } + + [Theory] + [InlineData(null)] + [InlineData("")] + [InlineData(" ")] + public void BuildSpeechPayload_CloneWithUnknownTranscript_SendsThePlaceholderNotTheLine(string? referenceText) + { + var json = JsonSerializer.Serialize(BreezeTtsAudioCpp.BuildSpeechPayload( + "Line to speak.", "/voices/clip.wav", referenceText, null)); + using var doc = JsonDocument.Parse(json); + + var sent = doc.RootElement.GetProperty("options").GetProperty("reference_text").GetString(); + Assert.Equal(BreezeTtsAudioCpp.UnknownReferenceTextPlaceholder, sent); + Assert.False(string.IsNullOrEmpty(sent)); + } + + [Theory] + [InlineData(null, BreezeTtsAudioCpp.ModelQ8_0FileName)] + [InlineData(BreezeTtsAudioCpp.ModelKeyQ8_0, BreezeTtsAudioCpp.ModelQ8_0FileName)] + [InlineData(BreezeTtsAudioCpp.ModelKeyBf16, BreezeTtsAudioCpp.ModelBf16FileName)] + [InlineData("something old", BreezeTtsAudioCpp.ModelQ8_0FileName)] + public void GetModelFileName_DefaultsToQ8_0(string? modelKey, string expected) + { + Assert.Equal(expected, BreezeTtsAudioCpp.GetModelFileName(modelKey)); + } +} From a591074ea98eaa765c55393bcb0d166e4ee59318 Mon Sep 17 00:00:00 2001 From: niksedk Date: Tue, 13 Oct 2026 06:12:53 +0200 Subject: [PATCH 100/306] Prepare v5.3.0-rc4 Co-Authored-By: Claude Opus 5.5 --- change-log.txt | 21 +++++++++++++++++++++ src/ui/Assets/Languages/English.json | 2 +- src/ui/Logic/Config/Se.cs | 2 +- 3 files changed, 23 insertions(+), 2 deletions(-) diff --git a/change-log.txt b/change-log.txt index 8b84e4a1ea5..677ca598495 100644 --- a/change-log.txt +++ b/change-log.txt @@ -1,6 +1,27 @@  Subtitle Edit Changelog +----------------------------------------------------------------------------------------------------- + +v5.3.0-rc4 (13th of October 2026) + +* Add Whisper-VAD to the Crisp ASR VAD choices - thx jodilife +* macOS: show "Search help topics" field with translated menus - thx GitGianluc +* Fix jitter/crackling in layout 14 (filmstrip thumbnails decode keyframes only) - thx ruonghieu +* Fix context menu submenu icons not showing on first open - thx GitGianluc +* Fix Linux/OpenGL fullscreen leaving an mpv instance alive (VRAM leak) - thx vitonique +* Fix Whisper OpenAI model download "path not found" - thx Shaima-Almarzooqi +* Fix Auto-translate advanced settings cut off at high UI scaling - thx mmmsh-125 +* Update whisper.cpp to v1.9.5 +* Update Crisp ASR to v0.8.43 +* Update Czech translation - thx Matěj +* Update French translation - thx Need74 +* Update German translation - thx FunkyKnilch +* Update Italian translation - thx bovirus +* Update Japanese translation - thx hisui3393 +* Update Korean translation - thx 12si27 + + ----------------------------------------------------------------------------------------------------- v5.3.0-rc3 (12th of October 2026) diff --git a/src/ui/Assets/Languages/English.json b/src/ui/Assets/Languages/English.json index f34611422bf..bc0f1224a33 100644 --- a/src/ui/Assets/Languages/English.json +++ b/src/ui/Assets/Languages/English.json @@ -1,6 +1,6 @@ { "title": "Subtitle Edit", - "version": "v5.3.0-rc3", + "version": "v5.3.0-rc4", "translatedBy": "", "cultureName": "en-US", "general": { diff --git a/src/ui/Logic/Config/Se.cs b/src/ui/Logic/Config/Se.cs index d1d12e7bd52..a3f845683c1 100644 --- a/src/ui/Logic/Config/Se.cs +++ b/src/ui/Logic/Config/Se.cs @@ -21,7 +21,7 @@ public class Se internal const int CurrentShortcutsMigrationVersion = 6; internal const int CurrentLayoutMigrationVersion = 2; - public static string Version { get; set; } = "v5.3.0-rc3"; + public static string Version { get; set; } = "v5.3.0-rc4"; public SeGeneral General { get; set; } = new(); public List Shortcuts { get; set; } = new(); From 41c943a5eb0affac10b457c336a952a5b781ce8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=AC=99=E6=AD=8C?= Date: Tue, 13 Oct 2026 12:53:41 +0800 Subject: [PATCH 101/306] Update ChineseTraditional.json for v5.3.0-rc4 - Translation and proofreading of strings --- .../Assets/Languages/ChineseTraditional.json | 370 ++++++++++++------ 1 file changed, 244 insertions(+), 126 deletions(-) diff --git a/src/ui/Assets/Languages/ChineseTraditional.json b/src/ui/Assets/Languages/ChineseTraditional.json index 0cf14f3b4b3..2f685a80fcf 100644 --- a/src/ui/Assets/Languages/ChineseTraditional.json +++ b/src/ui/Assets/Languages/ChineseTraditional.json @@ -1,6 +1,6 @@ { "title": "Subtitle Edit", - "version": "v5.3.0-beta25", + "version": "v5.3.0-rc4", "translatedBy": "昇(以下舊譯者:BAWAN、風逸蘭、Jackie Lam、SiderealArt)", "cultureName": "zh-Hant", "general": { @@ -62,7 +62,7 @@ "attachDotDotDot": "附加...", "audioFileSaved": "音訊檔案已儲存", "audioFiles": "音訊檔案", - "audioVisualizer": "音訊視覺化", + "audioVisualizer": "音訊可視化工具", "auto": "自動", "autoBreak": "文本自動斷行", "autoBalanceLines": "自動平衡每行字數", @@ -232,7 +232,7 @@ "extendSelectedToPrevious": "將已選取項延伸至上一項", "extendPreviousEndToSelectedStart": "將上一行的終點延伸至已選取項的起點", "extendNextStartToSelectedEnd": "延伸下一行的起點至已選取項的終點", - "extractingAudioClips": "正在提取音訊片段...", + "extractingAudioClips": "正在摘錄音訊片段...", "exitFullScreen": "退出全螢幕", "fade": "淡化", "fetchFirstWordFromNextSubtitle": "從下一則字幕中取得第一個單字", @@ -309,8 +309,8 @@ "include": "包括", "information": "資訊", "insertAfter": "於後方插入", - "insertAtPositionAndFocusTextBox": "於視訊位置插入字幕並聚焦文字框", - "insertAtPositionNoFocusTextBox": "於視訊位置插入字幕 (不聚焦文字框)", + "insertAtPositionAndFocusTextBox": "於視訊位置插入字幕並聚焦打字框", + "insertAtPositionNoFocusTextBox": "於視訊位置插入字幕 (不聚焦打字框)", "insertBefore": "於前方插入", "insertLine": "插入一行", "insertNewSelection": "插入新選取範圍", @@ -370,7 +370,7 @@ "moreInfo": "更多資訊", "moveAllShotChangeOneFrameBack": "將所有 場景變更線 倒轉一影格", "moveAllShotChangeOneFrameForward": "將所有 場景變更線 快轉一影格", - "multiMode": "多重模式", + "multiMode": "多模式", "moveDown": "下移", "increase": "增加", "decrease": "減少", @@ -559,11 +559,11 @@ "selectedLines": "已選取行", "selectedlinesX": "已選取行:{0}", "sensitivity": "靈敏度", - "separator": "分離器", + "separator": "分隔符號", "setEnd": "設置終點", "setEndAndGoToNext": "設置終點並跳至下一項", "setEndAddNewAndGoToNew": "設置終點,新增後跳至新項", - "setEndAddNewAndGoToNewNoFocusTextBox": "設置終點,新增後跳至新項(不聚焦文字框)", + "setEndAddNewAndGoToNewNoFocusTextBox": "設置終點,新增後跳至新項(不聚焦打字框)", "setEndAndOffsetTheRest": "設置終點並偏移後續行", "setFontDotDotDot": "設置字型...", "setInCueToClosestShotChangeLeftGreenZone": "設置進聲提示點至最近的場景變更線 (貼齊至左側綠色區域)", @@ -595,6 +595,8 @@ "showStartColumn": "顯示「起點」欄", "showHideColumn": "顯示「隱藏」欄", "showHistory": "顯示歷史記錄", + "clearHistory": "清除歷史記錄", + "removeFromHistory": "從歷史記錄移除", "showLayerColumn": "顯示「圖層」欄", "showPreview": "顯示預覽", "showShotChangesList": "顯示 場景變更線 列表", @@ -637,13 +639,13 @@ "splitAtDialogDash": "拆分在對話破折號處", "splitAtLineBreak": "拆分在斷行處", "splitAtSentenceEnd": "拆分在句尾處", - "splitAtTextBoxCursorPosition": "拆分在文字游標位置處", + "splitAtTextBoxCursorPosition": "拆分在文字游標位置", "splitLine": "拆分此行", - "splitNearMiddle": "拆分在最接近中間位置的空白處", - "splitLineAtTextBoxCursorPosition": "拆分此行於游標位置", - "splitLineAtVideoAndTextBoxPosition": "拆分此行於視訊與文字框位置", - "splitLineAtVideoPosition": "拆分此行於視訊位置", - "splitLineAtWaveformPosition": "拆分此行於波形圖游標位置", + "splitNearMiddle": "拆分在最接近中間的空白處", + "splitLineAtTextBoxCursorPosition": "拆分此行在游標位置", + "splitLineAtVideoAndTextBoxPosition": "拆分此行在視訊與打字框位置", + "splitLineAtVideoPosition": "拆分此行在視訊位置", + "splitLineAtWaveformPosition": "拆分此行在波形頭部", "startFrom": "起點從", "startTime": "起頭時間", "startingDotDotDot": "正在啟動...", @@ -656,12 +658,14 @@ "styleExaggeration": "誇張風格", "styles": "樣式", "subtitleFile": "字幕檔案", + "subtitleFileChangedOutsideX": "檔案「{0}」已在 Subtitle Edit 外部被變更。\n\n要重新載入並放棄您的變更嗎?", "subtitleFileName": "字幕檔名", "subtitleFileSaved": "字幕檔已儲存", "subtitleFileSavedToX": "字幕檔已儲存至 {0}", "subtitleFiles": "字幕檔案", "subtitleFormats": "字幕格式", "subtitleLoadedX": "已讀取字幕:{0}", + "subtitleReloadedChangedOutsideX": "已重新載入字幕(在 Subtitle Edit 外部被變更):{0}", "suffix": "字尾", "suggestions": "建議", "sync": "同步", @@ -815,7 +819,7 @@ "manualSync": "手動同步", "unsupportedVideoFormat": "不支援的視訊格式", "playbackSpeed": "重播速度", - "hoursMinutesSecondsMilliseconds": "時:分:SS.FFF", + "hoursMinutesSecondsMilliseconds": "時:分:秒.FFF", "imageFiles": "圖像檔案", "openAiCompatibleSttEndpoint": "STT 端點 URL", "openAiCompatibleSttApiKey": "API金鑰", @@ -888,6 +892,7 @@ "multipleReplace": "多重取代(_M)...", "fillSelectedLinesWithClipboard": "用剪貼簿文字填滿已選取行", "goToLineNumber": "跳至行號(_G)...", + "commandPalette": "命令面板(_P)...", "rightToLeftMode": "由右至左書寫模式(_I)", "modifySelectionDotDotDot": "修改選取範圍(_S)...", "tools": "工具(_T)", @@ -1009,7 +1014,7 @@ "layoutHint": "變更工具列與面板佈局 {0}", "helpHint": "開啟說明網站 {0}", "autoBreakHint": "自動將過長行斷行 {0}", - "unbreakHint": "將多行字幕合併為單行 {0}", + "unbreakHint": "將多行字幕合併進單行 {0}", "assaStylesHint": "ASSA樣式", "assaPropertiesHint": "ASSA屬性", "assaAttachmentsHint": "ASSA附件", @@ -1033,7 +1038,7 @@ "zoomVerticalHint": "垂直縮放 {0}", "selectCurrentLineWhilePlayingHint": "播放時選取目前字幕 {0}", "videoPosition": "從目前的視訊位置 {0}", - "videoPositionTextBox": "視訊位置文字框 {0}", + "videoPositionTextBox": "視訊位置打字框 {0}", "toolbarLineBreakX": "斷行處 {0}(工具列其餘部分會在新一列繼續)", "initialText": "已選取行的初始文字", "initialTextHint": "當該行被選取時,已選取行的文字原本樣子", @@ -1080,6 +1085,9 @@ "charactersPerSecond": "字數/秒: {0}", "chooseColumn": "選擇欄位", "columnPaste": "整欄貼上", + "commandPaletteTitle": "指令選擇區", + "commandPaletteSearchPlaceholder": "輸入以搜尋指令...", + "commandPaletteNoCommandsFound": "找不到符合的指令", "createdEmptyTranslation": "已從目前字幕建立空白翻譯", "deleteText": "刪除文字", "deleteTextAndShiftCellsUp": "刪除文字並將儲存格向上位移", @@ -1092,13 +1100,13 @@ "errorLoadJpg": "此檔案似乎是 JPG圖像檔案。\n\nSubtitle Edit 無法開啟圖像檔案。", "errorLoadPng": "此檔案似乎是 PNG圖像檔案。\n\nSubtitle Edit 無法開啟圖像檔案。", "errorLoadRar": "此檔案似乎是 RAR壓縮檔案。\n\nSubtitle Edit 無法開啟壓縮檔案。", - "errorLoadRawPgsPrompt": "此檔案似乎是沒有 Blu-ray SUP「PG」標頭的原始 PGS 串流,因此不包含時間戳記。\n\n這通常發生在以「原始 (raw)」模式提取 Matroska PGS 軌道時。若要取得正確的時間軸,請以一般軌道模式重新匯出該軌道(例如執行「mkvextract tracks」時不要加上 --raw 參數),或直接在 Subtitle Edit 中開啟原件 Matroska 檔案。\n\n無論如何,您仍要匯入字幕圖像嗎(將使用必須手動修正的自動產生占位時間碼)?", + "errorLoadRawPgsPrompt": "此檔案似乎是缺少 Blu-ray SUP「PG」標頭的原始 PGS 串流,因此不包含時間戳記。\n\n這通常發生在 Matroska PGS 軌道在「原始 (raw)」模式被摘錄時。若要取得正確的時間軸,請以一般軌道模式重新摘錄該軌道(例如:缺少 --raw 的「mkvextract tracks」),或直接在 Subtitle Edit 中開啟原件 Matroska 檔案。\n\n不管怎樣您都想要匯入字幕圖像嗎(將使用必須手動修正的自動產生占位時間碼)?", "errorLoadSrr": "此檔案似乎是 ReScene SRR 檔案。\n\nSubtitle Edit 無法開啟 SRR 檔案。", "errorLoadTorrent": "此檔案似乎是 BitTorrent 種子檔案。\n\nSubtitle Edit 無法開啟種子檔案。", "errorLoadZip": "此檔案似乎是壓縮的 ZIP 檔案。\n\nSubtitle Edit 無法開啟壓縮檔案。", - "extractingWaveInfo": "正在提取音訊波形資訊...", - "extractingShotChanges": "正在提取場景變更線...", - "failedToExtractWaveInfo": "提取音訊波形資訊失敗。", + "extractingWaveInfo": "正在摘錄波形資訊...", + "extractingShotChanges": "正在摘錄場景變更線...", + "failedToExtractWaveInfo": "摘錄音訊波形資訊失敗。", "fixedRightToLeftUsingUnicodeControlCharactersX": "已在 {0} 行中利用 Unicode 控制字元修正 RTL", "generatingSpectrogramDotDotDot": "正在建立頻譜圖...", "generatingWaveformDotDotDot": "正在產生波形...", @@ -1164,7 +1172,7 @@ "insertUnicodeSymbol": "插入 Unicode 符號", "trimmedXLines": "已裁切 {0} 行字幕", "pastedXLinesOverSelectedLines": "已將 {0} 行貼上已選取行", - "pastedXOfYLinesOverSelectedLines": "已將 {1} 行中的 {0} 行貼上已選取行,剩下的超出選取範圍", + "pastedXOfYLinesOverSelectedLines": "已貼上 {1} 行中的 {0} 行在已選取行之上 - 剩下的超出選取範圍", "insertedXLinesFromClipboard": "已從剪貼簿插入 {0} 行", "openOriginalDifferentNumberOfSubtitlesXY": "原件字幕檔案的字幕數量與目前字幕檔案的字幕數量不同。\n\n• 原件字幕:{0}\n• 目前字幕:{1}", "originalTextReadOnly": "原件文字(唯讀)", @@ -1285,7 +1293,7 @@ "titleImportPlainText": "匯入純文字", "csvXlsxCustomColumnsDotDotDot": "帶有自訂列的 CSV/XLSX...", "titleImportCsvXlsxCustomColumns": "使用自訂列匯入 CSV/XLSX", - "detectedSeparatorX": "偵測到的分隔符號:{0}", + "detectedSeparatorX": "偵測到分隔符號:{0}", "csvXlsxFilterTitle": "CSV/XLSX/ODS", "separatorTab": "Tab 定位字元", "separatorSpace": "空格字元", @@ -1294,7 +1302,7 @@ "subtitleWithManuallyChosenEncodingDotDotDot": "字幕(手動所選編碼)(_S)...", "titleImportImages": "匯入圖像", "importFileLabel": "選擇要匯入的圖像(支援檔名中包含時間碼)", - "importFilesInfo": "使用內含時間碼的檔名:\nstart_HH_MM_SS_MMM__end_HH_MM_SS_MMM[_index].ext\n\n範例:\n0_00_01_042__0_00_03_919_0001.png\n0_00_01_042__0_00_03_919.png\n\n規則:\n• 起始與結尾時間使用 HH_MM_SS_MMM\n• 使用雙底線分隔 起點/終點\n• 結尾時間後選填索引", + "importFilesInfo": "使用內含時間碼的檔名:\nstart_HH_MM_SS_MMM__end_HH_MM_SS_MMM[_index].ext\n\n範例:\n0_00_01_042__0_00_03_919_0001.png\n0_00_01_042__0_00_03_919.png\n\n規則:\n• 起始與結尾時間使用 HH_MM_SS_MMM\n• 使用雙底線隔開 起點/終點\n• 結尾時間後選填索引", "formattingDotDotDot": "格式排版...", "imageBasedSubtitleForEditDotDotDot": "編輯基於圖像的字幕...", "imageBasedSubtitleForOcrDotDotDot": "OCR 基於圖像的字幕...", @@ -1318,7 +1326,7 @@ "oneLineIsOneSubtitle": "一行為一欄字幕", "twoLinesAreOneSubtitle": "兩行為一欄字幕", "importFilesDotDotDot": "匯入檔案...", - "multipleFiles": "從多個文字檔案匯入(一個檔案一份字幕)", + "multipleFiles": "從多項文字檔案匯入(一個檔案一份字幕)", "maxLineLength": "最大單行長度", "mergeShortLines": "合併短行", "fixed": "固定", @@ -1337,7 +1345,8 @@ "forcedAlignerIsolatingSpeech": "正在隔離語音... {0}", "forcedAlignerIsolatingSpeechFailed": "無法隔離語音 - 結尾時間已改為從閱覽時間設置。", "forcedAlignerModel": "對齊器模型", - "forcedAlignerDownloadEngine": "下載/更新引擎..." + "forcedAlignerDownloadEngine": "下載/更新引擎...", + "forcedAlignerEstimatedXOfY": "有 {0} / {1} 行未能對齊,已依閱覽時間配置 - 其時間碼僅為估算值。劇本可能比視訊中實際朗讀內容更長。" }, "export": { "exportImagesProfiles": "匯出圖像設定檔", @@ -1708,7 +1717,7 @@ "descriptionOptional": "說明(選填)", "invalidRegularExpressionX": "無效的正規表達式:{0}", "regularExpressionTooSlowX": "正規表達式在 {0} 秒後逾時 - 已略過此規則,請嘗試更簡單範本", -"moveToCategory": "移至分類" + "moveToCategory": "移至類別" }, "find": { "searchTextWatermark": "搜尋文字...", @@ -1813,7 +1822,7 @@ "fitReadingSpeed": "符合閱覽速度", "makeFormal": "更正式", "makeInformal": "更口語", - "askPlaceholder": "詢問關於此行的問題,或要求進行修改...", + "askPlaceholder": "詢問關於此行的問題,或要求進行變更...", "ask": "詢問", "result": "建議", "apply": "應用至此行", @@ -1970,7 +1979,7 @@ "changedDurationFromXToYCommentZ": "已將持續時間從 {0} 變更為 {1} {2}", "onlyPartialFixed": "(僅部分修正)", "unfixableX": "無法修復:{0}", - "fixedX": "已修復:{0}", + "fixedX": "已修正:{0}", "fixedXImprovedY": "修正項:{0},改進項:{1}", "noChangesNeeded": "無需變更" }, @@ -2006,8 +2015,8 @@ "batchFrameRateFixed": "使用固定影格率", "batchInfo": "若找到與字幕檔同名的視訊檔案,將從中讀取場景變更線資訊。", "useExactTimeCodes": "使用視訊的精確時間碼", - "extractTimeCodes": "提取時間碼", - "extractingTimeCodes": "正在提取時間碼...", + "extractTimeCodes": "摘錄時間碼", + "extractingTimeCodes": "正在摘錄時間碼...", "noTimeCodesLoaded": "未讀取到時間碼", "xTimeCodesLoaded": "已讀取 {0} 個時間碼", "timeCodesExtractFailed": "無法從視訊中讀取時間碼", @@ -2045,9 +2054,9 @@ "original": "原件", "aligned": "已對齊", "intro": "挑選對齊器並按下「對齊」,在音訊中聆聽每行字幕並上緊時間碼。", - "extractingAudio": "正在提取音訊...", + "extractingAudio": "正在摘錄音訊...", "aligningBatchXOfY": "正在對齊... 第 {0}/{1} 批次", - "extractAudioFailed": "無法使用 ffmpeg 提取音訊。", + "extractAudioFailed": "未能以 ffmpeg 摘錄音訊。", "modelWillBeDownloaded": "將會下載 {0}", "summaryXRetimedYKeptZSkipped": "重新對齊時間軸:{0} · 保留:{1} · 無語音:{2}", "meanShiftX": "平均偏移量:{0} 毫秒", @@ -2152,7 +2161,7 @@ "addUnderline": "增加下劃線", "addAlignment": "增加對齊方式", "addColor": "增加顏色", - "deleteLinesWithSpecificActorsOrStyles": "刪除特定角色或樣式的字幕行(多個項目請以逗號分隔)", + "deleteLinesWithSpecificActorsOrStyles": "刪除特定角色或樣式的字幕行(以逗號隔開多項)", "useSourceStylesIfPossible": "盡可能使用來源樣式(下方的標頭僅用於 非ASSA 輸入)", "keepSourceEmbeddedFonts": "保留來源內嵌字型(註腳)", "editStyles": "編輯樣式", @@ -2177,7 +2186,7 @@ "assaChangeStyleImportStyle": "匯入樣式...", "assaChangeStyleTrimUnusedStyles": "裁切未使用的樣式", "assaChangeStylePropertiesTitle": "變更樣式屬性", - "assaChangeStylePropertiesInfo": "變更檔案中每個樣式的所選欄槽,而不修改樣式本身。", + "assaChangeStylePropertiesInfo": "變更檔案中每個樣式的所選欄槽,不干涉樣式本身。", "assaChangeStylePropertiesSetSpacing": "設定間距至", "assaChangeStylePropertiesSetAlignment": "設定對齊方式至", "convertColorsToDialogTitle": "將顏色轉換為對話", @@ -2206,13 +2215,13 @@ "scanningFolderX": "正在掃描 {0}……", "transportStreamSettings": "傳輸串流設定", "transportStreamSettingsDotDotDot": "傳輸串流設定…", - "transportStreamSettingsInfo": "適用於從傳輸串流檔案(.ts/.m2ts)的已提取字幕軌道。位置和視訊尺寸僅會影響匯出為視訊格式的 DVB 圖像字幕軌道。", + "transportStreamSettingsInfo": "適用於從傳輸串流檔案(.ts/.m2ts)的已摘錄字幕軌道。位置和視訊尺寸僅會影響匯出為視訊格式的 DVB 圖像字幕軌道。", "transportStreamOverrideXPosition": "覆寫原件 X 座標", "transportStreamOverrideYPosition": "覆寫原件 Y 座標", "transportStreamOverrideVideoSize": "覆寫原件視訊尺寸", "transportStreamBottomMargin": "底部邊距", "transportStreamFileNameEnding": "檔案名稱結尾", - "transportStreamFileNameEndingInfo": "會新增在每個已提取軌道的副檔名之前。留空則使用常規語言後綴。", + "transportStreamFileNameEndingInfo": "新增在每個被摘錄軌道的副檔名之前。留空則使用正規語言後綴。", "transportStreamOnlyTeletext": "僅電傳視訊", "transportStreamGetSizeFromVideo": "從視訊取得尺寸…", "twoLetterLanguageCodeUppercase": "兩字母語言代碼(大寫)", @@ -2236,7 +2245,7 @@ "namesX": "名稱:{0:#,##0}", "hitsX": "符合項:{0:#,##0}", "extraNames": "額外名稱", - "enterExtraNamesHint": "輸入要修正的額外名稱,以逗號分隔", + "enterExtraNamesHint": "輸入要修正的額外名稱,以逗號隔開", "onlyFixUppercaseLines": "僅修正大寫行", "fixNamesOnly": "僅修正名稱", "allUppercase": "全部大寫", @@ -2244,9 +2253,9 @@ }, "changeFormatting": {}, "convertActors": { - "title": "轉換角色", + "title": "角色轉換", "convertActorFrom": "將角色從", - "convertActorTo": "變更角色為", + "convertActorTo": "角色轉換為", "inlineActorViaX": "經由 {0} 置入角色名", "setColor": "設置顏色", "onlyNames": "僅限名稱", @@ -2261,11 +2270,11 @@ }, "mergeTwoSubtitles": { "title": "合併兩份字幕", - "subtitle1": "字幕1", - "subtitle2": "字幕2", - "loadFromCurrentText": "從目前加載(文字)", - "loadFromCurrentTranslation": "從目前字幕載入(翻譯)", - "loadFromFile": "從檔案加載...", + "subtitle1": "字幕 1", + "subtitle2": "字幕 2", + "loadFromCurrentText": "從目前(文字)讀取", + "loadFromCurrentTranslation": "從目前(翻譯)讀取", + "loadFromFile": "從檔案讀取...", "outputFormat": "輸出格式", "outputFormatSubRip": "SubRip(.srt)", "outputFormatAssa": "ASSA(.ass)", @@ -2452,12 +2461,12 @@ "curlyBrackets": "花括號", "parentheses": "圓括號", "and": "和", - "onlySeparateLines": "僅獨立行", + "onlySeparateLines": "僅隔開行", "removeTextBeforeColon": "移除冒號前的文字", "onlyIfTextIsUppercase": "僅當文字為大寫時", - "onlyOnSeparateLine": "僅在獨立行時", + "onlyOnSeparateLine": "僅在隔開行", "ifLineIsUppercase": "若該行是大寫", - "keepUppercaseWords": "保留這些單字(用逗號分隔)", + "keepUppercaseWords": "保留這些單字(逗號隔開的)", "ifLineContains": "若該行包含", "ifLineOnlyContainsMusicSymbols": "若該行僅包含音樂符號", "removeInterjections": "移除感嘆詞", @@ -2678,9 +2687,9 @@ "customServerUrl": "自訂伺服器 URL", "customServerUrlHint": "相容 OpenAI 伺服器的完整語音端點,提供者為「自訂」時使用,例如 http://localhost:8880/v1/audio/speech", "customModels": "自訂模型", - "customModelsHint": "提供者為「自訂」時提供的模型名稱(以逗號分隔)", + "customModelsHint": "當提供者是「Custom」時,提供逗號隔開的模型名稱。", "customVoices": "自訂語音", - "customVoicesHint": "語音名稱(以逗號分隔),由「自訂」提供者以及未公布語音列表的 OpenRouter 模型使用", + "customVoicesHint": "逗號隔開的語音名稱,供「Custom」提供者以及未公布語音列表的 OpenRouter 模型使用", "instructions": "指令", "instructionsHint": "選填的說話風格,例如「以平靜、溫和聲音朗讀」。僅傳送給支援此功能的模型(不適用於 tts-1/tts-1-hd)", "openAiSpeedHint": "語音速度,範圍從 0.25 到 4.0(1.0 為正常)。非所有模型都支援", @@ -2715,12 +2724,12 @@ "voiceManagerMenuItem": "TTS 語音管理器...", "speakFromCurrentLineTitle": "從目前行朗讀", "speakFromCurrentLineDotDotDot": "從目前行朗讀...", - "speakFromCurrentLineHint": "開始於目前行,依序朗讀各行字幕。按 Esc 鍵或點擊其它行停止。", + "speakFromCurrentLineHint": "開始於目前行,依序朗讀各行字幕。\n按 Esc 鍵或點擊其它行停止。", "stopSpeaking": "停止朗讀", "speakingLineX": "正在朗讀第 {0} 行...(Esc 鍵停止)", "playWithSpeechTitle": "以朗讀播放", "playWithSpeechDotDotDot": "以朗讀播放...", - "playWithSpeechHint": "播放視訊並於到達各行時逐句朗讀字幕。暫停與照常跳轉;按 Esc 鍵即可停止。", + "playWithSpeechHint": "播放視訊並於到達各行時逐句朗讀字幕。\n照常來暫停及跳轉;按 Esc 鍵即可停止。", "lowerVideoVolumeWhileSpeaking": "朗讀時降低視訊音量", "pauseVideoWhenSpeechRunsLate": "朗讀超時影響至下一行時暫停視訊", "waitingForSpeechLineX": "正在產生第 {0} 行的語音...(Esc 鍵停止)", @@ -2788,6 +2797,12 @@ "advancedTtsSettings": "進階 TTS 設定", "advancedTtsAudioProcessing": "音訊處理", "advancedTtsOutput": "輸出", + "advancedTtsVoiceCloning": "正在仿造聲音", + "cleanCloneReferences": "清理「聲音仿造用參考音訊」(Sidon)", + "cleanCloneReferencesDescription": "從用於仿造語音的音訊中移除音樂、噪音與殘響:包含從視訊提取的片段以及匯入語音管理器的錄音。透過 audio.cpp 使用 Sidon 語音修復模型(需要下載 940 MB)。被仿造語音不再複製背景音,但極輕微或重疊的語音可能會變得含糊不清。", + "cleaningCloneReferencesDotDotDot": "進行清理「聲音仿造用參考音訊」...", + "downloadSidonTitle": "要下載 Sidon 嗎?", + "downloadSidonQuestionX": "進行清理「聲音仿造用參考音訊」需要 Sidon 語音修復模型 ({0})。\n\n要立即下載嗎?缺少它將照原樣使用參考音訊。", "proAudioPostProcessing": "專業音訊後處理", "proAudioPostProcessingDescription": "對每個片段應用 EQ 溫暖度、雜訊門、壓縮、響度標準化 (-16 LUFS) 和淡入/淡出。", "generatingSpeechSegmentXOfY": "正在生成語音:區段 {0} / {1}", @@ -2800,6 +2815,10 @@ "removeOriginalSpeechDescription": "保留原件視訊的音樂與音效並移除其語音,再將新的語音混音疊加於其上。使用 GPU 耗時大約與視訊同樣時長;而沒用 GPU 則耗時更長。開啟音訊閃避時,其音量適用於音樂與音效。僅於語音被新增至視訊檔案時適用。", "removingOriginalSpeech": "正在移除原件語音...", "removeOriginalSpeechFailed": "無法移除原件語音 - 該語音被新增至視訊中,但未移除原件語音。", + "speechRemovalModel": "模型", + "speechRemovalModelDescription": "「最佳品質」保留大部分音樂及音效。「快速」則約三倍快迅速,但會稍微多損失一些音樂。", + "speechRemovalBestQuality": "最佳品質(Mel-Band RoFormer)", + "speechRemovalFast": "快速(HTDemucs)", "videoTruncatedWarning": "新視訊檔案中的畫面在 {0} 後即停止,但原視訊長度為 {1} - 畫面將會凍結,而聲音繼續。\n\n檔案已保留。請參閱 Subtitle Edit 數據資料夾中的 error-log.txt 有關 ffmpeg 的輸出。", "originalVolumePercent": "原件音量百分比 %", "vadSilenceCompression": "VAD 靜音壓縮", @@ -2851,7 +2870,7 @@ "setupCastHint": "為每個角色 (ASSA) 或語音 (WebVTT) 指定 TTS 語音。", "actorVoicesRowSettingsTitle": "TTS - 語音設定", "voiceSettingsForX": "「{0}」的語音設定", - "voiceInstructionFreeTextHint": "引擎用於塑造語音語調的自訂文字。", + "voiceInstructionFreeTextHint": "引擎用於塑造語音語調的隨意文字。", "voiceDesign": "語音設計", "noActorsFoundMessage": "找不到任何角色。請先在字幕行設置角色欄槽。", "noWebVttVoicesFoundMessage": "在 WebVTT 檔案中找不到 語音。", @@ -2867,9 +2886,9 @@ "detectSpeakersTitle": "文本內的說話者名稱", "detectSpeakersFoundX": "找到 {0} 個說話者標籤(共 {1} 位說話者) - 被勾選名稱會成為角色,且不會被朗讀出聲", "detectSpeakersColumnUse": "使用", - "detectSpeakersSticky": "沒有名稱的行會沿用上一位說話者", - "subtitleUpdatedFromReviewSingular": "已根據校對語音更新 1 行字幕", - "subtitleUpdatedFromReviewPlural": "已根據校對語音更新 {0} 行字幕", + "detectSpeakersSticky": "缺少名稱的字幕行延續前一位說話者", + "subtitleUpdatedFromReviewSingular": "根據校對語音更新 1 行字幕", + "subtitleUpdatedFromReviewPlural": "根據校對語音更新 {0} 行字幕", "subtitleMergedLinesAppliedSingular": "已應用 1 行文字轉語音的字幕行合併", "subtitleMergedLinesAppliedPlural": "已應用 {0} 行文字轉語音的字幕行合併" }, @@ -2943,7 +2962,7 @@ "minDurationMs": "最小持續時間(毫秒)", "addAssaPositionTag": "新增 ASSA 位置標籤 (例如 {\\an8})", "startOcr": "開始 OCR", - "extractingFramesX": "正在提取圖像影格... {0}%", + "extractingFramesX": "正在摘錄圖像影格... {0}%", "analyzingFramesXY": "正在分析影格... {0}/{1}", "runningOcrXY": "正在執行 OCR... {0}/{1}", "noLinesFoundTitle": "未找到字幕", @@ -3034,7 +3053,7 @@ "editSecondarySubtitleSettingsDotDotDot": "編輯第二份字幕設定...", "secondarySubtitleRememberSettings": "記住這些設定", "secondarySubtitleDoNotShowAgain": "請勿再顯示此對話", - "secondarySubtitleJustifyLines": "行左右對齊", + "secondarySubtitleJustifyLines": "行對齊", "openRecentVideo": "開啟近期視訊", "clearRecentVideos": "清除近期視訊", "cutVideoTitle": "剪輯視訊", @@ -3042,7 +3061,7 @@ "remuxVideoTitle": "重新封裝視訊", "remuxVideoDotDotDot": "重新封裝視訊…", "remuxVideoInputVideo": "輸入視訊:", - "remuxVideoMultipleTracksRequiresMkv": "多個音訊或字幕軌道需要使用 MKV 容器。輸出格式已自動切換為 .mkv。", + "remuxVideoMultipleTracksRequiresMkv": "多項音訊或字幕軌道需要使用 MKV 容器。輸出格式已自動切換為 .mkv。", "remuxVideoOutputFormat": "輸出格式:", "remuxVideoOutputFile": "輸出檔案:", "remuxVideoRemuxing": "正在重新封裝視訊和音訊…", @@ -3119,7 +3138,7 @@ "embeddedTrackUnableToGenerateTitle": "未能產生視訊", "embeddedTrackUnableToGenerateMessage": "未產生輸出視訊檔:{0}{1}參數:{2}", "embeddedTrackPreviewUnavailableTitle": "預覽不可用", - "embeddedTrackPreviewUnavailableMessage": "無法提取已選取字幕串流。該編碼可能不是 ffmpeg 能轉換為 SRT 的文字格式(例如:基於點陣圖字幕)。", + "embeddedTrackPreviewUnavailableMessage": "未能摘錄已選取字幕串流。該編碼可能不是 ffmpeg 能轉換為 SRT 的文字格式(例如:基於點陣圖字幕)。", "embeddedTrackGeneratingVideoXY": "正在產生視訊... {0}% {1}", "embeddedTrackGeneratingVideo": "正在產生視訊...", "mp4FilesFilter": "MP4 檔案", @@ -3197,12 +3216,12 @@ "maxSilenceVolume": "最大靜音音量(0.0 - 1.0):", "guessTimeCodesDotDotDot": "猜測時間碼...", "seekSilenceDotDotDot": "定位靜音處...", - "extractAudioDotDotDot": "提取音訊...", + "extractAudioDotDotDot": "摘錄音訊...", "cloneVoiceTo": "將語音仿造至", "cloneVoiceNameTitle": "新語音名稱", - "cloneVoiceExtractFailed": "無法提取已選取行的音訊。", - "lineXHasNoDuration": "第 {0} 列沒有持續時間(結尾時間尚未晚於起頭時間),因此無法提取音訊。", - "audioClipsSkippedLinesX": "無法提取這些列的音訊,因此已略過:{0}", + "cloneVoiceExtractFailed": "未能摘錄已選取行的音訊。", + "lineXHasNoDuration": "第 {0} 列沒有持續時間(結尾時間尚未晚於起頭時間),因此無法摘錄音訊。", + "audioClipsSkippedLinesX": "這些行無音訊可被摘錄,所以被略過:{0}", "toggleShotChange": "標記場景變更線", "resetWaveformZoomAndSpeed": "重置波形縮放&速度", "showOriginalSubtitle": "顯示原件字幕", @@ -3263,9 +3282,9 @@ "showEarlierLaterDotDotDot": "提早/延後顯示..." }, "translate": { - "translateViaCopyPaste": "經由複製/貼上進行自動翻譯", + "translateViaCopyPaste": "經由複製/貼上自動翻譯", "maxBlockSize": "最大區塊大小", - "lineSeparator": "換行分隔符", + "lineSeparator": "行分隔符號", "blockCopyInfo": "跳至翻譯工具或 AI 並貼上文字(已在剪貼簿中),並將翻譯結果複製回剪貼簿,最後點擊下方按鈕。", "blockCopyGetFromClipboard": "從剪貼簿取得文字(來自翻譯工具/AI)", "reCopyTextToClipboard": "再次複製文字至剪貼簿(供翻譯工具/AI 使用)", @@ -3278,7 +3297,7 @@ "keepMusicLinesUntranslated": "請勿翻譯音樂符號的行 (♪ ... ♪)", "promptText": "提示詞文字", "resetPromptToDefault": "重置提示詞為預設值", - "translateEachLineSeparately": "逐行分開翻譯", + "translateEachLineSeparately": "請逐行分開翻譯", "translationError": "翻譯錯誤", "translationFailedMessage": "{0} 翻譯失敗。", "translationFailedHint": "這可能是由於網路問題、無效的 API 金鑰或 API 使用量限制所致。\n請檢查您的網路連線與 API 設定,而後再試一次。", @@ -3354,16 +3373,16 @@ "searchSettingsDotDotDot": "搜尋設定...", "network": "網路", "fileTypeAssociations": "檔案類型關聯", - "textBoxColorTags": "字幕文字框內顏色標籤 (HTML/ASSA)", - "textBoxLiveSpellCheck": "字幕文字框內即時拼字檢查", - "textBoxCenterText": "字幕文字框內文本置中", - "textBoxFontBold": "字幕文字框內文字加粗", - "textBoxFontSize": "字幕文字框內字型大小", - "subtitleTextBoxAndGridFontName": "字幕文字框與網格內的介面字型", + "textBoxColorTags": "字幕打字框內顏色標籤 (HTML/ASSA)", + "textBoxLiveSpellCheck": "字幕打字框內即時拼字檢查", + "textBoxCenterText": "字幕打字框內文本置中", + "textBoxFontBold": "字幕打字框內文字加粗", + "textBoxFontSize": "字幕打字框內字型大小", + "subtitleTextBoxAndGridFontName": "字幕打字框與網格內的介面字型", "subtitleGridFontSize": "字幕網格內字型大小", "subtitleGridTextSingleLine": "網格內字幕文字以單行顯示", "subtitleGridTextSingleLineSeparator": "單行分隔符號 (例如
)", - "subtitleGridTextDisplay": "網格內字幕文字合適", + "subtitleGridTextDisplay": "字幕文字容納網格內", "subtitleGridTextDisplayClip": "片段 (單行)", "subtitleGridTextDisplayWrap": "自動換行以符合視窗", "subtitleGridTextDisplayEllipsis": "單行並顯示刪節號", @@ -3427,7 +3446,7 @@ "settingsBackupOn": "自動備份設定", "settingsBackupIntervalDays": "設定備份間隔(天,0 = 每次啟動)", "settingsBackupMaxCount": "保留的設定備份數量", - "autoConvertToUtf8": "開啟時自動將編碼轉換為 UTF-8", + "autoConvertToUtf8": "開啟後編碼自動轉換至 UTF-8", "autoTrimWhiteSpace": "自動修剪空白字元", "removeBlankLinesWhenOpening": "開啟字幕時移除空白行", "defaultEncoding": "預設編碼", @@ -3451,7 +3470,7 @@ "waveformShowToolbarEdit": "編輯工具列...", "waveformShowToolbarEditLabel": "工具列項目", "waveformSpectrogramCombinedWaveformHeight": "波形/頻譜圖雙拼,波形高度 %", - "waveformFocusTextboxAfterInsertNew": "插入之後聚焦文字框", + "waveformFocusTextboxAfterInsertNew": "插入之後聚焦打字框", "waveformInvertMouseWheel": "反轉滑鼠滾輪方向", "waveformMouseWheelSetsVideoPosition": "滑鼠滾輪用於調整視訊位置", "waveformMouseWheelVideoPositionStep": "滑鼠滾輪視訊位置調整步進值", @@ -3481,7 +3500,7 @@ "proxyDomain": "網域", "proxyUseSystemCredentials": "使用系統憑證", "proxyBypassList": "繞過代理伺服器", - "proxyBypassListHint": "直接連線的主機名稱,以分號區隔,例如「internal.company.com;example.org」", + "proxyBypassListHint": "直接連線由分號隔開的主機名稱,例如「internal.company.com;example.org」", "updates": "更新", "checkForUpdatesOnStartup": "應用程式啟動時檢查是否有更新", "checkForUpdatesChannel": "通知版本", @@ -3505,8 +3524,8 @@ "autoOpenVideoFile": "開啟字幕時自動開啟視訊檔案", "downloadMpv": "下載MPV", "downloadVlc": "下載VLC", - "allowSingleLetterShortcutsInTextbox": "在文字框中允許單一字母快捷鍵", - "allowTextNavigationShortcutsInTextbox": "允許在文字框中使用文字導航鍵的快捷鍵(Ctrl+左/右、Home/End)", + "allowSingleLetterShortcutsInTextbox": "在打字框中允許單一字母快捷鍵", + "allowTextNavigationShortcutsInTextbox": "允許在打字框中使用文字導航鍵的快捷鍵(Ctrl+左/右、Home/End)", "spellCheckEnglishTreatInApostropheAsIng": "拼字檢查:將以 'in'' 結尾的單字視為 'ing'(僅限英文)", "goToLineNumberSetsVideoPosition": "移至行號時也設置視訊位置", "adjustAllTimesRememberLineSelectionChoice": "調整所有時間,並記住行選取範圍的選擇", @@ -3552,10 +3571,11 @@ "timeCodeMode": "時間碼模式", "timeCodeModeTime": "時間 (hh:mm:ss,zzz)", "timeCodeModeFrames": "影格 (hh:mm:ss:ff)", - "timeCodeModeFrameNumbers": "影格編號", - "textBoxLimitNewLines": "限制字幕文字框中的行數", + "timeCodeModeFrameNumbers": "影格數", + "textBoxLimitNewLines": "限制字幕打字框中的行數", "mpvOpenGl": "libmpv - OpenGL", "mpvSoftwareRendering": "libmpv - 軟體彩現(慢)", + "mpvMetalRendering": "libmpv - Metal (實驗性,需重新啟動)", "mpvWidRendering": "libmpv - 原始 視窗ID 彩現", "waveFormsAndSpectrogramFoldersContainsX": "波形「Waveforms」與頻譜圖「spectrogram」資料夾內含量 {0}", "deleteWaveformAndSpectrogramFoldersQuestion": "要刪除「Waveforms」與「Spectrogram」內檔案嗎?", @@ -3564,10 +3584,10 @@ "waveformSpectrogramMode": "頻譜圖模式", "waveformRightClickSelectsSubtitle": "右鍵點擊來選取字幕", "waveformDrawStyle": "波形繪製樣式", - "waveformExtractAudioFormat": "提取音訊格式", - "waveformExtractAudioSampleRate": "提取音訊取樣率", + "waveformExtractAudioFormat": "摘錄音訊格式", + "waveformExtractAudioSampleRate": "摘錄音訊取樣率", "waveformExtractAudioSampleRateOriginal": "原件(保持來源)", - "waveformExtractAudioBitRate": "提取音訊位元率(MP3/M4A)", + "waveformExtractAudioBitRate": "摘錄音訊位元率(MP3/M4A)", "ffmpegUseCenterChannelOnly": "5.1 音訊僅使用前置中央聲道(FFmpeg)", "vlcWidRendering": "libVLC - 原始 視窗ID 彩現", "ffmpegSoftwareRendering": "FFmpeg", @@ -3589,8 +3609,8 @@ "gridGoToSubtitleOnlyWaveformOnly": "僅跳至字幕(僅波形)", "gridGoToSubtitleAndPause": "跳至字幕並暫停", "gridGoToSubtitleAndPlay": "跳至字幕並播放", - "gridGoToSubtitleAndPauseAndFocusTextBox": "跳至字幕、暫停和聚焦文字框", - "gridGoToSubtitleAndPlayAndFocusTextBox": "跳至字幕播放並聚焦文字框", + "gridGoToSubtitleAndPauseAndFocusTextBox": "跳至字幕、暫停和聚焦打字框", + "gridGoToSubtitleAndPlayAndFocusTextBox": "跳至字幕播放並聚焦打字框", "subtitleListActionVideoGoToPositionAndPlayCurrentAndPause": "跳至視訊位置、播放目前字幕之後暫停", "subtitleListActionVideoGoToPositionMinus1SecAndPause": "跳至視訊位置 - 1秒 並暫停", "subtitleListActionVideoGoToPositionMinusHalfSecAndPause": "跳至視訊位置 - 0.5秒 並暫停", @@ -3619,10 +3639,10 @@ "fixShortDisplayTimesAllowMoveStartTime": "修正常見錯誤:修正過短的顯示時間可能會移動起頭時間", "speechToTextSelectedLinesPromptFirstTimeOnly": "語音轉文字:已選取行,僅第一次詢問語言/引擎", "multipleReplaceShowDotDotDotButtons": "多重取代:顯示快顯功能表按鈕", - "gridFocusTextboxAfterInsertNew": "網格:插入新字幕之後聚焦文字框", + "gridFocusTextboxAfterInsertNew": "網格:插入新字幕之後聚焦打字框", "undoRedoGoToChangedLine": "復原/重做:跳至已變更行(及視訊位置)", "musicSymbol": "音樂符號", - "musicSymbolsToReplace": "要取代的音樂符號(以逗號分隔)", + "musicSymbolsToReplace": "要取代的音樂符號(以逗號隔開)", "textToSpeechPromptMergeContinuationLines": "文字轉語音:合併續接行的提示詞", "textToSpeechPromptSkipNoiseLines": "文字轉語音:略過音效/音樂行列的提示詞", "textToSpeechPromptDetectSpeakers": "文字轉語音:偵測內文中說話者名稱的提示詞", @@ -3631,12 +3651,12 @@ "forceCrLfOnSave": "儲存時強制 CR+LF(文字字幕檔案)", "linuxClipboardUseExternalTool": "經由 xclip/wl-copy 複製文字(可保留變音符號字元)", "showFormatLimitWarning": "儲存時,若行數超過格式限制則顯示警告(例:SCC 32 字元/行)", - "textBoxButtonShowAutoBreak": "文字框:顯示自動斷行按鈕", - "textBoxButtonShowUnbreak": "文字框:顯示取消斷行按鈕", - "textBoxButtonShowItalic": "文字框:顯示斜體按鈕", - "textBoxButtonShowColor": "文字框:顯示顏色按鈕", - "textBoxButtonShowRemoveFormatting": "文字框:顯示移除格式排版按鈕", - "textBoxButtonShowAiAssistant": "文字框:顯示 AI 助理按鈕", + "textBoxButtonShowAutoBreak": "打字框:顯示自動斷行按鈕", + "textBoxButtonShowUnbreak": "打字框:顯示取消斷行按鈕", + "textBoxButtonShowItalic": "打字框:顯示斜體按鈕", + "textBoxButtonShowColor": "打字框:顯示顏色按鈕", + "textBoxButtonShowRemoveFormatting": "打字框:顯示移除格式排版按鈕", + "textBoxButtonShowAiAssistant": "打字框:顯示 AI 助理按鈕", "waveformSingleClickAction": "波形 單擊操作", "waveformDoubleClickAction": "波形 雙擊操作(單擊操作後)", "allSettings": "所有設定", @@ -3648,7 +3668,11 @@ "subtitlePreviewProperties": "字幕預覽屬性", "usePositionFromSubtitleFile": "使用字幕檔案中的位置(TTML/PAC/EBU STL)", "marginIsPartOfSubtitleArea": "外邊距屬於字幕區域的一部分", - "textJustify": "行左右對齊", + "textJustify": "行對齊", + "deinterlace": "視訊去交錯", + "deinterlaceOff": "關閉", + "deinterlaceAuto": "自動(僅限交錯影格)", + "deinterlaceAlways": "總是", "pixelWidthInfo": "綠線 = 最大寬度限制 | 紅區 = 文字超出限制", "spellCheckEngineHunSpelll": "Hunspell", "spellCheckEngineMsWord": "微軟Word", @@ -3670,16 +3694,16 @@ }, "shortcuts": { "searchShortcuts": "搜尋快捷鍵...", - "categorySubtitleGridAndTextBox": "字幕列表視圖 & 文字框", + "categorySubtitleGridAndTextBox": "字幕列表視圖 & 打字框", "categorySubtitleGrid": "字幕列表視圖", - "categoryTextBox": "文字框", + "categoryTextBox": "打字框", "categoryFile": "檔案", "categoryVideo": "視訊", "categorySync": "同步", "categoryAi": "AI", "categoryCustom": "自訂", "editCustomShortcut": "編輯自訂快捷鍵", - "customShortcutTextBoxKeyHint": "此處請搭配 Ctrl 或 Alt 鍵使用 - 使用普通單鍵將不能夠在文字框中輸入。", + "customShortcutTextBoxKeyHint": "搭配 Ctrl/Alt 使用按鍵 - 普通單鍵將不能夠在打字框中輸入。", "customShortcutNumberX": "自訂快捷鍵 #{0}", "customShortcutNumberXY": "自訂快捷鍵 #{0}:{1}", "customShortcutStep": "步驟", @@ -3758,7 +3782,7 @@ "resetShortcutsDetail": "確定要將所有快捷鍵重置為預設值嗎?", "togglePlayPause": "切換 播放/暫停", "toggleLockTimeCodes": "切換鎖定時間碼", - "toggleTimeCodeMode": "切換時間碼模式(時間/影格/影格編號)", + "toggleTimeCodeMode": "切換時間碼模式(時間/影格/影格數)", "toggleScreenPrivacy": "切換隱私畫面(隱藏檔案名稱,接著還有文字)", "screenPrivacyFileNamesHidden": "隱私畫面:已隱藏檔案名稱", "screenPrivacyFileNamesAndTextsHidden": "隱私畫面:已隱藏檔案名稱及文字", @@ -3785,22 +3809,22 @@ "breakAtFirstSpaceFromCursor": "從游標位置的第一個空格處斷行", "breakAtFirstSpaceFromCursorAndGoToNext": "從游標位置的第一個空格處斷行並前往下一個", "toggleFocusGridAndWaveform": "切換聚焦(字幕網格、波形/頻譜圖)", - "toggleFocusTextBoxAndWaveform": "切換聚焦(文字框、波形/頻譜圖)", - "toggleFocusTextBoxAndGrid": "切換聚焦(文字框、字幕網格)", + "toggleFocusTextBoxAndWaveform": "切換聚焦(打字框、波形/頻譜圖)", + "toggleFocusTextBoxAndGrid": "切換聚焦(打字框、字幕網格)", "goToPreviousLineAndSetVideoPosition": "跳至上一則字幕並設置視訊位置", "goToPreviousLineFromVideoPosition": "跳至上一則字幕(從目前視訊位置)", "goToNextLineFromVideoPosition": "跳至下一則字幕(從目前視訊位置)", "videoGoToPreviousTimeCode": "跳至上一個時間碼", "videoGoToNextTimeCode": "跳至下一個時間碼", "goToNextLineAndSetVideoPosition": "跳至下一則字幕並設置視訊位置", - "textBoxDeleteForward": "文字框:刪除選取範圍或下一個字元(往後刪除)", - "textBoxCut": "文字框:剪下", - "textBoxCut2": "文字框:剪下(備選)", - "textBoxPaste": "文字框:貼上", - "textBoxPaste2": "文字框:貼上(備選)", - "textBoxCopy": "文字框:複製", - "textBoxCopy2": "文字框:複製(備選)", - "textBoxSelectAll": "文字框:全選", + "textBoxDeleteForward": "打字框:刪除選取範圍或下一個字元(往後刪除)", + "textBoxCut": "打字框:剪下", + "textBoxCut2": "打字框:剪下(備選)", + "textBoxPaste": "打字框:貼上", + "textBoxPaste2": "打字框:貼上(備選)", + "textBoxCopy": "打字框:複製", + "textBoxCopy2": "打字框:複製(備選)", + "textBoxSelectAll": "打字框:全選", "subtitleGridCut": "字幕網格:剪下", "subtitleGridCopy": "字幕網格:複製", "subtitleGridPaste": "字幕網格:貼上", @@ -3862,11 +3886,11 @@ "videoFullScreen": "視訊全螢幕", "copyTextFromOriginalSelectedLines": "從原件複製文字(已選取行)", "copyTextToOriginalSelectedLines": "將文本複製至原件(已選取行)", - "textBoxRemoveAllFormatting": "文字框:移除所有格式排版", - "textBoxItalic": "文字框:斜體", - "textBoxBold": "文字框:粗體", - "textBoxUnderline": "文字框:底線", - "textBoxBox": "文字框區塊(EBU STL)", + "textBoxRemoveAllFormatting": "打字框:移除所有格式排版", + "textBoxItalic": "打字框:斜體", + "textBoxBold": "打字框:粗體", + "textBoxUnderline": "打字框:底線", + "textBoxBox": "打字框區塊(EBU STL)", "resetWaveformZoomAndSpeed": "重置波形縮放和重播速度(播放速率)", "togglePlaybackSpeed": "切換重播速度(播放速率)", "playbackSpeedSlower": "重播速度較慢(播放速率)", @@ -3926,12 +3950,12 @@ "waveformVerticalZoomOutCommand": "波形垂直縮小", "copySubtitlePathToClipboard": "複製字幕路徑至剪貼簿", "copySubtitleOriginalPathToClipboard": "複製原件字幕路徑至剪貼簿", - "focusTextBox": "聚焦文字框", + "focusTextBox": "聚焦打字框", "focusSubtitleListView": "聚焦字幕列表檢視", "focusWaveform": "聚焦波形", - "focusOriginalTextBox": "聚焦原件文字框", - "textBoxGoToStart": "文字框:跳至起點", - "textBoxGoToEnd": "文字框:跳至終點", + "focusOriginalTextBox": "聚焦原件打字框", + "textBoxGoToStart": "打字框:跳至起點", + "textBoxGoToEnd": "打字框:跳至終點", "sortByNumber": "依編號排序", "sortByStartTime": "依「演出」時間排序", "sortByEndTime": "依「隱藏」時間排序", @@ -3964,10 +3988,10 @@ "layoutZoomIn": "放大佈局(縮放 UI)", "layoutZoomOut": "縮小佈局(縮放 UI)", "goToPreviousBookmark": "跳至上一個書籤", - "selectionToLower": "文字框:選取範圍改小寫", - "selectionToUpper": "文字框:選取範圍改大寫", - "selectionToSentenceCase": "文字框:將選取範圍改句首大寫", - "selectionToggleCasing": "文字框:切換大小寫(大寫 -> 小寫 -> 標題大寫)", + "selectionToLower": "打字框:選取範圍改成小寫", + "selectionToUpper": "打字框:選取範圍改成大寫", + "selectionToSentenceCase": "打字框:選取範圍改成句首大寫", + "selectionToggleCasing": "打字框:切換大小寫(大寫 -> 小寫 -> 標題大寫)", "googleIt": "Google 搜尋(已選取文字)", "setActorX": "設置角色 {0}", "setNewActor": "設置新角色...", @@ -4199,11 +4223,28 @@ "crispEmbedReturnedNoText": "CrispEmbed 無文字回傳 - 請檢查模型", "appleVisionReturnedNoText": "Apple Vision 無文字回傳 - 請檢查語言", "crispEmbedSettingsTitle": "CrispEmbed 設置", - "crispEmbedDescription": "具備多種模型後端的本機 OCR 引擎 - 可在此下載引擎與模型。", + "crispEmbedDescription": "具備多項模型後端的本機 OCR 引擎 - 可在此下載引擎與模型。", "trainNOcrDatabase": "訓練 nOCR 資料庫...", + "optimizeNOcrDatabase": "最佳化 nOCR 資料庫...", + "optimizeNOcrDatabaseQuestionXY": "要移除 nOCR 資料庫「{0}」中無法將字元與相似字元區分開來的行嗎?\n\n這會讓資料庫更小及 OCR 速度更快,通常也能減少錯誤。大約需要一分鐘。\n\n備份將儲存為「{1}」。", + "optimizingNOcrDatabaseXY": "正在最佳化 nOCR 資料庫... {0}/{1}", + "nOcrDatabaseOptimizedXYZ": "nOCR 資料庫「{0}」已最佳化:{1:#,##0} -> {2:#,##0} 行。", "startTraining": "開始訓練", "abortTraining": "中止訓練", "trainingOptions": "訓練選項", + "trainNOcrSubtitle": "使用已安裝的字型繪製字元,並教導 nOCR 辨識其圖形。請挑選您字幕所使用字型。", + "xFontsSelected": "已選取 {0:#,##0} 個", + "subtitleFonts": "字幕字型", + "emptyDatabase": "空白資料庫", + "startFromDatabase": "開始從", + "startFromDatabaseHint": "將訓練的字元新增至現有資料庫的複本(建議)或從空白資料庫開始", + "fontSizes": "字型大小", + "fontSizesHint": "要用於繪製的字型大小(像素),以逗號隔開,例如「30, 40」", + "numberOfLineSegmentsHint": "依字元檢查的行數 - 數值越高越嚴格但速度較慢", + "enterDatabaseName": "輸入資料庫名稱", + "trainingFontXSizeYCharacterZ": "正在以 {1} 像素訓練「{0}」:{2}", + "xLearned": "已學習 {0:#,##0} 個", + "xSkipped": "已略過 {0:#,##0} 個", "charactersToTrain": "要訓練的字元", "letterCombinationsToTrain": "可能被分割成單一圖像的雙聯字母", "importCharactersFromSubtitleFile": "從字幕檔匯入字元...", @@ -4219,7 +4260,7 @@ "drawSelectTool": "選取(移動點)", "drawUseShapeForErase": "使用形狀進行擦除(iclip)", "drawLineTool": "直線工具 (F4)", - "drawBezierTool": "貝塞爾曲線 (F5)", + "drawBezierTool": "貝茲曲線 (F5)", "drawRectangleTool": "矩形 (F6)", "drawCircleTool": "圓形", "drawCloseShape": "閉合形狀(F8/Enter)", @@ -4230,6 +4271,8 @@ "drawZoomOut": "縮小 (Ctrl+-)", "drawResetView": "重置視圖 (Ctrl+0)", "drawToggleGrid": "切換網格 (Ctrl+G)", + "drawTogglePreview": "切換預覽 (F9)", + "drawPreviewFailed": "預覽失敗 - 需要使用帶有 libass(字幕濾鏡)的 ffmpeg。", "drawCopyToClipboard": "剪貼簿", "drawShapes": "形狀", "drawSelectedPoint": "已選取點", @@ -4237,6 +4280,81 @@ "drawSelectedLayer": "已選取圖層", "drawToolX": "工具:{0}", "drawHelpText": "點擊以新增控制點 • Enter/F8 閉合形狀 • Shift+拖曳 以平移 • Ctrl+滾輪 以縮放", + "drawMoveToLayer": "移至圖層", + "drawFlipHorizontal": "水平翻轉", + "drawFlipVertical": "垂直翻轉", + "drawHideShape": "隱藏圖形", + "drawShowShape": "顯示圖形", + "drawHideLayer": "隱藏圖層", + "drawShowLayer": "顯示圖層", + "drawDeleteLayer": "刪除圖層", + "drawDeletePoint": "刪除點", + "drawCancelDrawing": "取消繪製", + "drawLayerX": "圖層 {0}", + "drawImportSvg": "匯入 SVG 影像...", + "drawRotateClockwise": "順時針旋轉 90°", + "drawConvertToCurve": "將線段轉換為曲線", + "drawBackground": "背景", + "drawPosition": "位置", + "drawZoom": "縮放", + "drawShapeTool": "圖形工具", + "drawColorPickerTool": "從圖形或背景挑選顏色", + "drawShapeLibrary": "圖形庫", + "drawShapeLibraryHint": "在畫布上拖曳以放置圖形,或點擊以使用預設大小。按住 Shift 鍵可維持比例。", + "drawAddToLibrary": "新增至圖形庫...", + "drawRemoveFromLibrary": "從圖形庫移除", + "drawShapeName": "圖形名稱", + "drawCategorySpeechBubbles": "對話泡泡", + "drawCategoryArrows": "箭頭", + "drawCategoryBasicShapes": "基本形狀", + "drawCategorySymbols": "符號", + "drawCategoryMyShapes": "我的形狀", + "drawShapeSpeechBubble": "對話泡泡", + "drawShapeSpeechBubbleRight": "對話泡泡(尾巴向右)", + "drawShapeSpeechBubbleUp": "對話泡泡(尾巴向上)", + "drawShapeOvalBubble": "橢圓對話泡泡", + "drawShapeBoxBubble": "方框對話泡泡", + "drawShapeThoughtBubble": "想法泡泡", + "drawShapeShoutBubble": "吶喊泡泡", + "drawShapeCaptionBox": "說明框", + "drawShapeArrow": "箭頭", + "drawShapeDoubleArrow": "雙向箭頭", + "drawShapeChevron": "V字形", + "drawShapeCurvedArrow": "彎曲箭頭", + "drawShapeRoundedRectangle": "圓角矩形", + "drawShapeTriangle": "三角形", + "drawShapeDiamond": "菱形", + "drawShapePentagon": "五邊形", + "drawShapeHexagon": "六邊形", + "drawShapeOctagon": "八邊形", + "drawShapeStar": "星形", + "drawShapeRing": "環形", + "drawShapePlus": "加號", + "drawShapeBanner": "橫幅", + "drawShapeHeart": "愛心", + "drawShapeCheck": "打勾符號", + "drawShapeMusicNote": "音符", + "drawShapeMusicNotes": "多個音符", + "drawShapeLightning": "閃電", + "drawShapeCloud": "雲朵", + "drawShapeMoon": "月亮", + "drawShapeBadge": "徽章", + "drawSize": "大小", + "drawLayer": "圖層", + "drawBackgroundVideoFrame": "視訊影格(目前位置)", + "drawBackgroundVideoFrameAt": "視訊影格在...", + "drawBackgroundImage": "圖像檔案...", + "drawBackgroundNone": "無背景", + "drawBackgroundStretch": "將圖像伸縮至畫布", + "drawBackgroundOpacity": "背景不透明度", + "drawBackgroundVideoFrameFailed": "未能夠在 {0} 取得視訊畫面。", + "drawImages": "圖像", + "drawConvertToLine": "將線段轉換為直線", + "drawConvertShapeToCurves": "將所有直線轉換為曲線", + "drawConvertShapeToLines": "將所有曲線轉換為直線", + "drawRotateCounterClockwise": "逆時針旋轉 90°", + "drawSvgImages": "SVG 圖像", + "drawSvgNoShapes": "SVG 檔案中沒找到被填滿形狀或筆畫。", "progressBarTitle": "ASSA 進度條", "progressBarSettings": "進度條", "mouseOverColor": "滑鼠懸停顏色", @@ -4293,7 +4411,7 @@ "resolutionResamplerFromVideo": "從視訊...", "resolutionResamplerSourceAndTargetEqual": "來源和目標解析度相同,無需執行任何操作。", "resolutionResamplerNothingSelected": "請至少選取一個選項進行變更。", - "resolutionResamplerVideoDiffers": "字幕解析度 ({0}x{1}) 與視訊解析度 ({2}x{3}) 不符。\n要將字幕修改成與視訊相符嗎?", + "resolutionResamplerVideoDiffers": "字幕解析度 ({0}x{1}) 與視訊解析度 ({2}x{3}) 不符。\n要將字幕變更至與視訊相符嗎?", "resolutionResamplerAskOnVideoOpen": "開啟解析度不同的視訊時進行詢問", "backgroundBoxGenerator": "產生背景框", "backgroundBoxFillWidth": "填滿寬度", @@ -4425,7 +4543,7 @@ "advancedEffectConfetti": "彩色紙屑爆裂", "advancedEffectConfettiDescription": "每則字幕都會從角落的爆裂器與中央星爆效果中爆發出色彩繽紛、旋轉飛舞的紙屑", "advancedEffectHearts": "心(雨)", - "advancedEffectHeartsDescription": "以貝茲曲線繪製的三種形狀愛心,從畫面頂端輕柔飄落,在每則字幕顯示期間旋轉翻滾並逐漸淡出", + "advancedEffectHeartsDescription": "三種形狀的貝茲曲線心形從畫面頂部緩緩落下,在每個字幕播放過程中翻滾、消逝", "advancedEffectWordSpacing": "字間距", "advancedEffectWordSpacingDescription": "使用 \\fsp 標籤增加單字之間的間距以提高可讀性", "advancedEffectKaraokeRightToLeft": "由右至左", From c6e7fddd832aad3ec7ad9f86562b4bf9e2084f46 Mon Sep 17 00:00:00 2001 From: niksedk Date: Tue, 13 Oct 2026 07:23:27 +0200 Subject: [PATCH 102/306] Pin audio.cpp runtime 2026-10-13 with the breeze_tts family support-files release audiocpp-indextts25-2026-10-13 (run 38342021471) is the 2026-10-08b build (upstream v0.9.1 f04de8ec) plus breeze_tts. Existing installs get the "audio.cpp update required" prompt when Breeze TTS 2 is picked, because their BUILD-INFO lacks the family. Also lists Breeze in the voice-cloning consent test map: its voices are IndexTtsVoice, so a reference WAV is a clone and the designed "Default" voice is not. Co-Authored-By: Claude Opus 5.5 --- src/ui/Logic/Download/DownloadHashManager.cs | 9 ++++++++- .../Download/IndexTts25AudioCppDownloadService.cs | 10 ++++------ .../TextToSpeech/Engines/VoiceCloningConsentTests.cs | 1 + 3 files changed, 13 insertions(+), 7 deletions(-) diff --git a/src/ui/Logic/Download/DownloadHashManager.cs b/src/ui/Logic/Download/DownloadHashManager.cs index 66b87b25fb0..ddb795847b5 100644 --- a/src/ui/Logic/Download/DownloadHashManager.cs +++ b/src/ui/Logic/Download/DownloadHashManager.cs @@ -2555,10 +2555,11 @@ public static class WhisperX }, // audio.cpp engine archives we build in SubtitleEdit/support-files - // (audiocpp-indextts25-2026-10-08b). Newest first — index 0 is the pinned release, + // (audiocpp-indextts25-2026-10-13). Newest first — index 0 is the pinned release, // so anything older prompts an update instead of being treated as current. [IndexTts25AudioCpp.EngineMacArm64] = new[] { + "a1481228f53e4b2e9cd765894fdffdfebaec559eca0b03a1e61c0edfe50c212c", // audiocpp-indextts25-macos-arm64.tar.gz (2026-10-13, upstream v0.9.1 f04de8ec + index_tts2 + higgs_audio_tts + fish_audio + fireredtts3 + ace_step + kugelaudio + sidon + breeze_tts) "29f75844449b9e41afeb41e77d2dc7e4136dd950f93d85bd16903ded8a86c470", // audiocpp-indextts25-macos-arm64.tar.gz (2026-10-08b, upstream v0.9.1 f04de8ec + index_tts2 + higgs_audio_tts + fish_audio + fireredtts3 + ace_step + kugelaudio + sidon) "6b4fc8a20f1a9e4986cce074a3c69343d5096b7b571f59c566de26e3f606defc", // audiocpp-indextts25-macos-arm64.tar.gz (2026-10-08, upstream v0.9.1 f04de8ec + index_tts2 + higgs_audio_tts + fish_audio + fireredtts3 + ace_step + kugelaudio) "4ac231614a13a5e9c50900b7f43c6e85c9ae451077139e79612370ade84c3de9", // audiocpp-indextts25-macos-arm64.tar.gz (2026-10-04, upstream main d3ab9df2 + index_tts2 + higgs_audio_tts + fish_audio + fireredtts3 + ace_step + kugelaudio) @@ -2575,6 +2576,7 @@ public static class WhisperX }, [IndexTts25AudioCpp.EngineWindowsCpu] = new[] { + "2d5d3ffdb9e95c816f214ecdfdb40697ccce8d24dfd742befc05ac79fde93daf", // audiocpp-indextts25-windows-x86_64-cpu.zip (2026-10-13, upstream v0.9.1 f04de8ec + index_tts2 + higgs_audio_tts + fish_audio + fireredtts3 + ace_step + kugelaudio + sidon + breeze_tts) "96a43e292dd761e02e7e42f579c5f7f72a86c98603db94ade3772aea960666c7", // audiocpp-indextts25-windows-x86_64-cpu.zip (2026-10-08b, upstream v0.9.1 f04de8ec + index_tts2 + higgs_audio_tts + fish_audio + fireredtts3 + ace_step + kugelaudio + sidon) "6e4545c005948a249f1d737759ac70dba5d886c2bc274bb50cea7828b2be7c00", // audiocpp-indextts25-windows-x86_64-cpu.zip (2026-10-08, upstream v0.9.1 f04de8ec + index_tts2 + higgs_audio_tts + fish_audio + fireredtts3 + ace_step + kugelaudio) "0ef62caffc352d5c3f42474223ccddd4bd6dc1e6841155d8d3e3175c3c03d1bd", // audiocpp-indextts25-windows-x86_64-cpu.zip (2026-10-04, upstream main d3ab9df2 + index_tts2 + higgs_audio_tts + fish_audio + fireredtts3 + ace_step + kugelaudio) @@ -2591,6 +2593,7 @@ public static class WhisperX }, [IndexTts25AudioCpp.EngineWindowsVulkan] = new[] { + "d79517505418d4cbdf0d1d266a99cb983515920ec81b45891c50abe6a8202a48", // audiocpp-indextts25-windows-x86_64-vulkan.zip (2026-10-13, upstream v0.9.1 f04de8ec + index_tts2 + higgs_audio_tts + fish_audio + fireredtts3 + ace_step + kugelaudio + sidon + breeze_tts) "23c7aabf2df7b3c8513e3b39f9fb7069007a7a0ac12ac0982bc984754f475ba1", // audiocpp-indextts25-windows-x86_64-vulkan.zip (2026-10-08b, upstream v0.9.1 f04de8ec + index_tts2 + higgs_audio_tts + fish_audio + fireredtts3 + ace_step + kugelaudio + sidon) "7c47845bcadaa007ec2b77c15b95a55b09f183a1f4afe60633ae8dabb6bd957e", // audiocpp-indextts25-windows-x86_64-vulkan.zip (2026-10-08, upstream v0.9.1 f04de8ec + index_tts2 + higgs_audio_tts + fish_audio + fireredtts3 + ace_step + kugelaudio) "c1ab6720ef637afd79467d39384411a8aea7a869cc0a4527e56a6ed1590cd5ba", // audiocpp-indextts25-windows-x86_64-vulkan.zip (2026-10-04, upstream main d3ab9df2 + index_tts2 + higgs_audio_tts + fish_audio + fireredtts3 + ace_step + kugelaudio) @@ -2607,6 +2610,7 @@ public static class WhisperX }, [IndexTts25AudioCpp.EngineWindowsCuda] = new[] { + "bcb0cc492fedb356398ca1a10708ca92ec4e5f8cbda439b6eee40c5ad820b0a4", // audiocpp-indextts25-windows-x86_64-cuda.zip (2026-10-13, upstream v0.9.1 f04de8ec + index_tts2 + higgs_audio_tts + fish_audio + fireredtts3 + ace_step + kugelaudio + sidon + breeze_tts) "6b6544b231449b44cb94ef5319b64601abd7d0b32105b3e62b7af1a4c8ad34dd", // audiocpp-indextts25-windows-x86_64-cuda.zip (2026-10-08b, upstream v0.9.1 f04de8ec + index_tts2 + higgs_audio_tts + fish_audio + fireredtts3 + ace_step + kugelaudio + sidon) "29990fbd2f74241005cbdc2568cbf523dd32a0cd1361bf495ece3f2a7d1de106", // audiocpp-indextts25-windows-x86_64-cuda.zip (2026-10-08, upstream v0.9.1 f04de8ec + index_tts2 + higgs_audio_tts + fish_audio + fireredtts3 + ace_step + kugelaudio) "686733a0f015f9cb38f724aaa539233e5df50ccfd28a10ef0e9aca4daac8d2c0", // audiocpp-indextts25-windows-x86_64-cuda.zip (2026-10-04, upstream main d3ab9df2 + index_tts2 + higgs_audio_tts + fish_audio + fireredtts3 + ace_step + kugelaudio) @@ -2623,6 +2627,7 @@ public static class WhisperX }, [IndexTts25AudioCpp.EngineLinuxCpu] = new[] { + "b43a004bf850b6b8b6f58bfdada8d0d38586f5c9b20666e9dc0acec1d0f5d316", // audiocpp-indextts25-linux-x86_64.tar.gz (2026-10-13, upstream v0.9.1 f04de8ec + index_tts2 + higgs_audio_tts + fish_audio + fireredtts3 + ace_step + kugelaudio + sidon + breeze_tts) "f9d6686c63014e2aebb3313fb34a19bd1785a7ba3410cb42c9573c437bbb55f1", // audiocpp-indextts25-linux-x86_64.tar.gz (2026-10-08b, upstream v0.9.1 f04de8ec + index_tts2 + higgs_audio_tts + fish_audio + fireredtts3 + ace_step + kugelaudio + sidon) "3e11910f574356c185c5403821ceea5ea740094f84691fb4207252e26edfe0c7", // audiocpp-indextts25-linux-x86_64.tar.gz (2026-10-08, upstream v0.9.1 f04de8ec + index_tts2 + higgs_audio_tts + fish_audio + fireredtts3 + ace_step + kugelaudio) "cbc6bf9dbc9751299c9aa9844622ce2e6f88f58cab0f73a6917630119895f651", // audiocpp-indextts25-linux-x86_64.tar.gz (2026-10-04, upstream main d3ab9df2 + index_tts2 + higgs_audio_tts + fish_audio + fireredtts3 + ace_step + kugelaudio) @@ -2639,6 +2644,7 @@ public static class WhisperX }, [IndexTts25AudioCpp.EngineLinuxVulkan] = new[] { + "c438fb2def4f83b8bf10260254ba43c4749979227621d5b82b8e445db8392737", // audiocpp-indextts25-linux-x86_64-vulkan.tar.gz (2026-10-13, upstream v0.9.1 f04de8ec + index_tts2 + higgs_audio_tts + fish_audio + fireredtts3 + ace_step + kugelaudio + sidon + breeze_tts) "45e567e6df3d81ebb465307bf19f3bc782b9a8f8ad50978e7de79c635f658207", // audiocpp-indextts25-linux-x86_64-vulkan.tar.gz (2026-10-08b, upstream v0.9.1 f04de8ec + index_tts2 + higgs_audio_tts + fish_audio + fireredtts3 + ace_step + kugelaudio + sidon) "7106206e6cd72ee6576fc3a2b184d75927d6906dbd2140c0a434fcb3b1946a48", // audiocpp-indextts25-linux-x86_64-vulkan.tar.gz (2026-10-08, upstream v0.9.1 f04de8ec + index_tts2 + higgs_audio_tts + fish_audio + fireredtts3 + ace_step + kugelaudio) "aec391dc5bcd277565e8a600de847014726c32760022aa752687b1250fb976c3", // audiocpp-indextts25-linux-x86_64-vulkan.tar.gz (2026-10-04, upstream main d3ab9df2 + index_tts2 + higgs_audio_tts + fish_audio + fireredtts3 + ace_step + kugelaudio) @@ -2655,6 +2661,7 @@ public static class WhisperX }, [IndexTts25AudioCpp.EngineLinuxCuda] = new[] { + "2838ccee80719d30de5b53f3acbd11cb00bd9740ef4a029938007d248d68c10a", // audiocpp-indextts25-linux-x86_64-cuda.tar.gz (2026-10-13, upstream v0.9.1 f04de8ec + index_tts2 + higgs_audio_tts + fish_audio + fireredtts3 + ace_step + kugelaudio + sidon + breeze_tts) "92dff4c562f3842e83b94cf562e79c15e1fafa2239576b0ff62089b74f9f26d1", // audiocpp-indextts25-linux-x86_64-cuda.tar.gz (2026-10-08b, upstream v0.9.1 f04de8ec + index_tts2 + higgs_audio_tts + fish_audio + fireredtts3 + ace_step + kugelaudio + sidon) "ef6385905fe3d9d2ae1c9d81e9c70b9afe6a1623782864a4cf726d259fcb4838", // audiocpp-indextts25-linux-x86_64-cuda.tar.gz (2026-10-08, upstream v0.9.1 f04de8ec + index_tts2 + higgs_audio_tts + fish_audio + fireredtts3 + ace_step + kugelaudio) "6f330a710af530490e92590beadc98fad537b2f4e0638b59fa438b3149b58667", // audiocpp-indextts25-linux-x86_64-cuda.tar.gz (2026-10-04, upstream main d3ab9df2 + index_tts2 + higgs_audio_tts + fish_audio + fireredtts3 + ace_step + kugelaudio) diff --git a/src/ui/Logic/Download/IndexTts25AudioCppDownloadService.cs b/src/ui/Logic/Download/IndexTts25AudioCppDownloadService.cs index 1efa4de081d..ea2e67f17c6 100644 --- a/src/ui/Logic/Download/IndexTts25AudioCppDownloadService.cs +++ b/src/ui/Logic/Download/IndexTts25AudioCppDownloadService.cs @@ -33,12 +33,10 @@ public class IndexTts25AudioCppDownloadService : IIndexTts25AudioCppDownloadServ { private readonly HttpClient _httpClient; - // 2026-10-08b: upstream v0.9.1 @ f04de8ec, compiled with the index_tts2 + higgs_audio_tts + - // fish_audio + fireredtts3 + ace_step + kugelaudio families plus sidon for "Clean - // voice-clone references". Windows binaries now use the UTF-8 process code page (non-ASCII - // model/voice paths). Higgs, FireRedTTS3, IndexTTS 2.5 and KugelAudio output byte-identical - // to 2026-10-04 on Metal. - private const string ReleaseTag = "audiocpp-indextts25-2026-10-08b"; + // 2026-10-13: the 2026-10-08b build (upstream v0.9.1 @ f04de8ec, index_tts2 + higgs_audio_tts + // + fish_audio + fireredtts3 + ace_step + kugelaudio + sidon) plus breeze_tts for Breeze + // TTS 2. Same upstream commit, so the other families are unchanged. + private const string ReleaseTag = "audiocpp-indextts25-2026-10-13"; private const string ReleaseBase = "https://github.com/SubtitleEdit/support-files/releases/download/" + ReleaseTag + "/"; diff --git a/tests/UI/Features/Video/TextToSpeech/Engines/VoiceCloningConsentTests.cs b/tests/UI/Features/Video/TextToSpeech/Engines/VoiceCloningConsentTests.cs index 7529c4146b1..2a882d4592a 100644 --- a/tests/UI/Features/Video/TextToSpeech/Engines/VoiceCloningConsentTests.cs +++ b/tests/UI/Features/Video/TextToSpeech/Engines/VoiceCloningConsentTests.cs @@ -158,6 +158,7 @@ public void IsCloneVoice_RecognisesEveryCloningEngineInTheCatalog() [typeof(CosyVoice3CrispAsr)] = () => new CosyVoice3Voice("Ada", "/voices/ada.wav", string.Empty), [typeof(DotsTtsCrispAsr)] = () => new DotsTtsVoice("Ada", "/voices/ada.wav"), [typeof(FireRedTts3AudioCpp)] = () => new IndexTtsVoice("Ada", "/voices/ada.wav"), + [typeof(BreezeTtsAudioCpp)] = () => new IndexTtsVoice("Ada", "/voices/ada.wav"), [typeof(FishTtsAudioCpp)] = () => new IndexTtsVoice("Ada", "/voices/ada.wav"), [typeof(HiggsTtsAudioCpp)] = () => new IndexTtsVoice("Ada", "/voices/ada.wav"), [typeof(IndexTts25AudioCpp)] = () => new IndexTtsVoice("Ada", "/voices/ada.wav"), From f1bc4cb4cc30b699f6618be33998e01f25b0b5e1 Mon Sep 17 00:00:00 2001 From: GitGianluc <16348719+GitGianluc@users.noreply.github.com> Date: Tue, 13 Oct 2026 10:31:59 +0200 Subject: [PATCH 103/306] `general.bluRaySup` string fixed in Italian.json Follow up to https://github.com/SubtitleEdit/subtitleedit/issues/15933#issuecomment-6112856168 --- src/ui/Assets/Languages/Italian.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ui/Assets/Languages/Italian.json b/src/ui/Assets/Languages/Italian.json index d7cab6e1e29..45723a54092 100644 --- a/src/ui/Assets/Languages/Italian.json +++ b/src/ui/Assets/Languages/Italian.json @@ -78,7 +78,7 @@ "bdnXml8Bit": "BDN/xml 8-bit", "before": "Prima", "beginning": "Inizio", - "bluRaySup": "Blu-ray (superiore)", + "bluRaySup": "Blu-ray (sup)", "bold": "Grassetto", "bookmarkAdd": "Aggiungi segnalibro", "bookmarkAddForSelectedLinesX": "Aggiungi segnalibro per le {0} righe selezionate", From aaa5119dccfec68507a5559d3bb763e36286e3a9 Mon Sep 17 00:00:00 2001 From: niksedk Date: Tue, 13 Oct 2026 15:10:52 +0200 Subject: [PATCH 104/306] Add Japanese to Crisp ASR Parakeet languages The ja TDT, ja CTC and ReazonSpeech models are Japanese-only, but the language list only had the 25 European v3 languages, so Japanese could not be picked and post-processing ran with the wrong language rules. Fixes #15991 Co-Authored-By: Claude Opus 5.5 --- .../Features/Video/SpeechToText/Engines/CrispAsrParakeet.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/ui/Features/Video/SpeechToText/Engines/CrispAsrParakeet.cs b/src/ui/Features/Video/SpeechToText/Engines/CrispAsrParakeet.cs index 525e15b3669..38abecda207 100644 --- a/src/ui/Features/Video/SpeechToText/Engines/CrispAsrParakeet.cs +++ b/src/ui/Features/Video/SpeechToText/Engines/CrispAsrParakeet.cs @@ -17,7 +17,8 @@ public class CrispAsrParakeet : CrispAsrEngineBase public override bool IncludeLanguage => true; public override bool HasNativeTimestamps => true; - // The 25 European languages of parakeet-tdt-0.6b-v3 (also Parakeet Ultra). + // The 25 European languages of parakeet-tdt-0.6b-v3 (also Parakeet Ultra), plus + // Japanese for the ja TDT, ja CTC and ReazonSpeech models (#15991). public override List Languages => new() { @@ -35,6 +36,7 @@ public class CrispAsrParakeet : CrispAsrEngineBase new WhisperLanguage("el", "greek"), new WhisperLanguage("hu", "hungarian"), new WhisperLanguage("it", "italian"), + new WhisperLanguage("ja", "japanese"), new WhisperLanguage("lv", "latvian"), new WhisperLanguage("lt", "lithuanian"), new WhisperLanguage("mt", "maltese"), From 35223f5bf5cdd6dcde146a9914471bd99a9a06ec Mon Sep 17 00:00:00 2001 From: niksedk Date: Tue, 13 Oct 2026 18:48:40 +0200 Subject: [PATCH 105/306] Apply duration limits: keep the minimum gap when extending (#15994) The minimum gap was only subtracted when the extended end would overlap the next line, so a short extension (e.g. 1000 -> 1050 ms with an 80 ms gap) shrank the gap below the minimum. Cap at next start minus the minimum gap whenever the wanted end passes it, like libse FixDurationLimits and the Binary edit version already do. Co-Authored-By: Claude Opus 5.5 --- .../ApplyDurationLimitsViewModel.cs | 11 ++++-- .../ApplyDurationLimitsViewModelTests.cs | 38 +++++++++++++++++++ 2 files changed, 46 insertions(+), 3 deletions(-) diff --git a/src/ui/Features/Tools/ApplyDurationLimits/ApplyDurationLimitsViewModel.cs b/src/ui/Features/Tools/ApplyDurationLimits/ApplyDurationLimitsViewModel.cs index 072c06f826e..6e374cb2aac 100644 --- a/src/ui/Features/Tools/ApplyDurationLimits/ApplyDurationLimitsViewModel.cs +++ b/src/ui/Features/Tools/ApplyDurationLimits/ApplyDurationLimitsViewModel.cs @@ -181,10 +181,15 @@ private void BuildPreview() var wantedEndTime = TimeSpan.FromMilliseconds(item.StartTime.TotalMilliseconds + minMs); var allowedEndTime = wantedEndTime; - // Never overlap the next line. - if (next != null && wantedEndTime > next.StartTime) + // Never overlap the next line or eat into the minimum gap - only checking for an + // overlap let a short extension shrink the gap below the minimum (#15994). + if (next != null) { - allowedEndTime = TimeSpan.FromMilliseconds(next.StartTime.TotalMilliseconds - Se.Settings.General.MinimumBetweenLines.GetMilliseconds()); + var latestEndTime = TimeSpan.FromMilliseconds(next.StartTime.TotalMilliseconds - Se.Settings.General.MinimumBetweenLines.GetMilliseconds()); + if (wantedEndTime > latestEndTime) + { + allowedEndTime = latestEndTime; + } } allowedEndTime = CapAtShotChange(item, allowedEndTime); diff --git a/tests/UI/Features/Tools/ApplyDurationLimits/ApplyDurationLimitsViewModelTests.cs b/tests/UI/Features/Tools/ApplyDurationLimits/ApplyDurationLimitsViewModelTests.cs index d6f39d276ba..49e8417af04 100644 --- a/tests/UI/Features/Tools/ApplyDurationLimits/ApplyDurationLimitsViewModelTests.cs +++ b/tests/UI/Features/Tools/ApplyDurationLimits/ApplyDurationLimitsViewModelTests.cs @@ -77,6 +77,44 @@ public async Task ShorteningStillWorksWhenTheUnusedMinimumIsHigherThanTheMaximum Assert.Equal(2000, vm.AllSubtitlesFixed[1].Duration.TotalMilliseconds); } + [AvaloniaFact] + public async Task ExtendingNeverShrinksTheGapBelowTheMinimum() + { + // #15994: a 1000 ms line 80 ms before the next one was extended to 1050 ms, leaving a + // 30 ms gap - the minimum gap was only respected when the extension would overlap. + using var settings = new SettingsScope( + "General.UseFrameMode", + "General.MinimumBetweenLines.Milliseconds", + "Tools.ApplyDurationLimitsMinDurationMs", + "Tools.ApplyDurationLimitsMaxDurationMs"); + Se.Settings.General.UseFrameMode = false; + Se.Settings.General.MinimumBetweenLines.Milliseconds = 80; + + var vm = new ApplyDurationLimitsViewModel(); + var window = new ApplyDurationLimitsWindow(vm); + _windows.Add(window); + window.Show(); + vm.Initialize(new List + { + // 80 ms gap to the next line: no room to grow. + new() { Text = "At gap", StartTime = TimeSpan.FromMilliseconds(1000), EndTime = TimeSpan.FromMilliseconds(2000) }, + // 120 ms gap to the next line: may grow 40 ms. + new() { Text = "Partial", StartTime = TimeSpan.FromMilliseconds(2080), EndTime = TimeSpan.FromMilliseconds(3080) }, + new() { Text = "Next", StartTime = TimeSpan.FromMilliseconds(3200), EndTime = TimeSpan.FromMilliseconds(6000) }, + }, new List()); + Dispatcher.UIThread.RunJobs(); + vm.FixMinDurationMs = true; + vm.MinDurationMsOrFrames = 1050; + vm.FixMaxDurationMs = false; + vm.MaxDurationMsOrFrames = 6000; + + await vm.OkCommand.ExecuteAsync(null); + + Assert.Equal(1000, vm.AllSubtitlesFixed[0].Duration.TotalMilliseconds); + Assert.Equal(1040, vm.AllSubtitlesFixed[1].Duration.TotalMilliseconds); + Assert.Equal(new[] { "At gap" }, vm.Subtitles.Select(p => p.Text)); + } + [AvaloniaFact] public async Task OnlySelectedIdsAreFixedAndTheRestPassThroughUnchanged() { From 277eca9b650055eda166c4836d5a576916e8c23a Mon Sep 17 00:00:00 2001 From: niksedk Date: Tue, 13 Oct 2026 18:54:28 +0200 Subject: [PATCH 106/306] Burn-in: let ffmpeg write MP4 edit lists, faststart only for MP4/MOV "-use_editlist 0" made the muxer shift the video by the B-frame reorder delay (first frame at 83 ms at 23.976 fps while audio starts at 0), and with h264_nvenc + AAC it stretched the first video sample by one AAC frame, which Adobe Audition plays back as black video (#15992). Edit lists are now left to ffmpeg's default, and "-movflags +faststart" is only passed for the MP4/QuickTime family via the existing (unused) OutputContainer.IsMp4Family. Co-Authored-By: Claude Opus 5.5 --- src/ui/Logic/Media/FfmpegGenerator.cs | 10 ++++++- .../Video/FfmpegBurnInParametersTests.cs | 29 +++++++++++++++++++ 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/src/ui/Logic/Media/FfmpegGenerator.cs b/src/ui/Logic/Media/FfmpegGenerator.cs index 961d8c70413..c0f3e72d0c5 100644 --- a/src/ui/Logic/Media/FfmpegGenerator.cs +++ b/src/ui/Logic/Media/FfmpegGenerator.cs @@ -298,6 +298,14 @@ public static string GenerateHardcodedVideoFile(string inputVideoFileName, strin tuneParameter = $" -tune {tune}"; } + // "+faststart" only means something to the MP4/QuickTime muxer. Edit lists are left to + // ffmpeg: "-use_editlist 0" shifted the video by the B-frame delay (two frames late + // against the audio), and with AAC's encoder delay it could also stretch the first + // video sample, which Adobe Audition plays back as black video (#15992). + var movFlags = Features.Video.BurnIn.OutputContainer.IsMp4Family(Path.GetExtension(outputVideoFileName)) + ? " -movflags +faststart" + : string.Empty; + outputVideoFileName = $"\"{outputVideoFileName}\""; var passSettings = string.Empty; @@ -418,7 +426,7 @@ public static string GenerateHardcodedVideoFile(string inputVideoFileName, strin // Without it ffmpeg hits "File ... already exists. Exiting." and writes nothing - and as // the old file is still there, the burn-in looked like it succeeded (issue #14210). return - $"-y{cutStart}-i \"{inputVideoFileName}\"{canvasInput}{imageSubtitleInput}{logoInput}{cutEnd} {filterParameter} -g 30 -bf 2 -s {width}x{height} {videoEncodingSettings} {passSettings} {presetSettings}{tuneParameter} {crfSettings} {pixelFormat} {audioSettings} -use_editlist 0 -movflags +faststart{shortestParameter} {outputVideoFileName}"; + $"-y{cutStart}-i \"{inputVideoFileName}\"{canvasInput}{imageSubtitleInput}{logoInput}{cutEnd} {filterParameter} -g 30 -bf 2 -s {width}x{height} {videoEncodingSettings} {passSettings} {presetSettings}{tuneParameter} {crfSettings} {pixelFormat} {audioSettings}{movFlags}{shortestParameter} {outputVideoFileName}"; } /// diff --git a/tests/UI/Features/Video/FfmpegBurnInParametersTests.cs b/tests/UI/Features/Video/FfmpegBurnInParametersTests.cs index a9304699cc7..3d40fe215bc 100644 --- a/tests/UI/Features/Video/FfmpegBurnInParametersTests.cs +++ b/tests/UI/Features/Video/FfmpegBurnInParametersTests.cs @@ -54,6 +54,35 @@ public void Nvenc_LosslessTune_LeavesOutTheCqValue() Assert.DoesNotContain("-cq", parameters); } + /// + /// Issue #15992: "-use_editlist 0" delayed the video by the B-frame reorder delay and, with + /// AAC, could stretch the first video sample - Adobe Audition then played black video. + /// + [Theory] + [InlineData("output.mp4")] + [InlineData("output.mov")] + [InlineData("output.mkv")] + public void EditListsAreLeftToFfmpeg(string outputFileName) + { + var parameters = Generate("h264_nvenc", "aac", outputFileName); + + Assert.DoesNotContain("-use_editlist", parameters); + } + + [Theory] + [InlineData("output.mp4", true)] + [InlineData("output.m4v", true)] + [InlineData("output.mov", true)] + [InlineData("output.mkv", false)] + [InlineData("output.webm", false)] + [InlineData("output.ts", false)] + public void Faststart_OnlyForMp4Family(string outputFileName, bool expected) + { + var parameters = Generate("libx264", "aac", outputFileName); + + Assert.Equal(expected, parameters.Contains("-movflags +faststart")); + } + [Fact] public void NoTune_WritesNoTuneArgument() { From c995e5b2e5dea18cc3a8c5d88f15e0df93d918f0 Mon Sep 17 00:00:00 2001 From: MohamedAhmedEz Date: Tue, 13 Oct 2026 21:02:24 +0300 Subject: [PATCH 107/306] Apply screen privacy blur to unselected lines and images in OCR windows --- src/ui/Features/Ocr/OcrWindow.cs | 42 +++++++++++++++++-- .../Features/Video/VideoOcr/VideoOcrWindow.cs | 35 ++++++++++++++-- 2 files changed, 70 insertions(+), 7 deletions(-) diff --git a/src/ui/Features/Ocr/OcrWindow.cs b/src/ui/Features/Ocr/OcrWindow.cs index a92610bd0de..3e71d8e2029 100644 --- a/src/ui/Features/Ocr/OcrWindow.cs +++ b/src/ui/Features/Ocr/OcrWindow.cs @@ -600,6 +600,15 @@ double ColumnWidth(string header, string widestCellText) => Padding = new Thickness(3), Child = image, }; + imageContainer[!Visual.EffectProperty] = new MultiBinding + { + Converter = ScreenPrivacyBlurConverter.Instance, + Bindings = + { + new Binding(), + new Binding(nameof(dataGridSubtitle.SelectedItem)) { Source = dataGridSubtitle } + } + }; stackPanel.Children.Add(imageContainer); } @@ -617,19 +626,29 @@ double ColumnWidth(string header, string widestCellText) => { var contentPresenter = new ContentPresenter { - VerticalAlignment = VerticalAlignment.Center + VerticalAlignment = VerticalAlignment.Center, + Margin = new Thickness(3, 1, 3, 1) }; - contentPresenter.Margin = new Thickness(3, 1, 3, 1); - // Bind to HasFormattedText to trigger updates when FixResult changes + // The existing text rendering binding var binding = new Binding(nameof(OcrSubtitleItem.HasFormattedText)) { Source = item, Converter = new FuncValueConverter(hasFormatted => item.CreateFormattedText()) }; - contentPresenter.Bind(ContentPresenter.ContentProperty, binding); + + contentPresenter[!Visual.EffectProperty] = new MultiBinding + { + Converter = ScreenPrivacyBlurConverter.Instance, + Bindings = + { + new Binding(), // Source 1: The row's own data + new Binding(nameof(dataGridSubtitle.SelectedItem)) { Source = dataGridSubtitle } // Source 2: The grid's selection + } + }; + return contentPresenter; }) }, @@ -1355,4 +1374,19 @@ private static FuncDataTemplate MakeCrispEmbedModelItemT ? DownloadDotStatus.UpToDate : DownloadDotStatus.NotInstalled); } + private class ScreenPrivacyBlurConverter : IMultiValueConverter + { + public static readonly ScreenPrivacyBlurConverter Instance = new(); + private static readonly IEffect Blur = new BlurEffect { Radius = 8 }; + + public object? Convert(System.Collections.Generic.IList values, Type targetType, object? parameter, CultureInfo culture) + { + if (!UiUtil.HideTexts || values.Count != 2) return null; + + var currentItem = values[0]; + var selectedItem = values[1]; + + return currentItem != null && currentItem != selectedItem ? Blur : null; + } + } } \ No newline at end of file diff --git a/src/ui/Features/Video/VideoOcr/VideoOcrWindow.cs b/src/ui/Features/Video/VideoOcr/VideoOcrWindow.cs index ed08cd6009a..939ffd0e6cb 100644 --- a/src/ui/Features/Video/VideoOcr/VideoOcrWindow.cs +++ b/src/ui/Features/Video/VideoOcr/VideoOcrWindow.cs @@ -15,7 +15,7 @@ using Nikse.SubtitleEdit.Logic.ValueConverters; using System.Linq; using Nikse.SubtitleEdit.UiLogic.LlamaCpp; - +using System; namespace Nikse.SubtitleEdit.Features.Video.VideoOcr; public class VideoOcrWindow : Window @@ -590,11 +590,25 @@ private static Border MakeLinesView(VideoOcrViewModel vm) HeaderTheme = UiUtil.TableViewColumnHeaderTheme, Width = new GridLength(1, GridUnitType.Star), CellTemplate = new Avalonia.Controls.Templates.FuncDataTemplate((_, _) => - new ContentControl + { + var contentControl = new ContentControl { VerticalContentAlignment = VerticalAlignment.Center, [!ContentControl.ContentProperty] = new Binding(nameof(VideoOcrLineItem.FormattedText)), - }, supportsRecycling: true), + }; + + contentControl[!Visual.EffectProperty] = new MultiBinding + { + Converter = ScreenPrivacyBlurConverter.Instance, + Bindings = + { + new Binding(), + new Binding(nameof(tableView.SelectedItem)) { Source = tableView } + } + }; + + return contentControl; + }, supportsRecycling: true), }); var flyout = new MenuFlyout(); @@ -748,4 +762,19 @@ private static Grid MakeProgressView(VideoOcrViewModel vm) return grid; } + private class ScreenPrivacyBlurConverter : Avalonia.Data.Converters.IMultiValueConverter + { + public static readonly ScreenPrivacyBlurConverter Instance = new(); + private static readonly Avalonia.Media.IEffect Blur = new Avalonia.Media.BlurEffect { Radius = 8 }; + + public object? Convert(System.Collections.Generic.IList values, Type targetType, object? parameter, System.Globalization.CultureInfo culture) + { + if (!UiUtil.HideTexts || values.Count != 2) return null; + + var currentItem = values[0]; + var selectedItem = values[1]; + + return currentItem != null && currentItem != selectedItem ? Blur : null; + } + } } From b010fdd98e6963c2e0e167a420cd97446e3c79d3 Mon Sep 17 00:00:00 2001 From: Nikolaj Olsson Date: Tue, 13 Oct 2026 20:14:20 +0200 Subject: [PATCH 108/306] Settings search: fix English names never matching with a translated UI (#15945) LanguageTranslate.BlockCopyGetFromClipboard was typed object. A loaded translation deserializes it to a JsonElement, which the source-generated SeLanguageJsonContext can't serialize, so EnglishLabelLookup.Build threw, logged, and returned an empty map - English search terms matched nothing with any shipped translation (34 of them carry that key). It is the only non-string leaf property in the language classes. Added a test that round-trips every shipped translation and requires a non-empty lookup map. Co-Authored-By: Claude Opus 5.5 --- .../Language/Translate/LanguageTranslate.cs | 2 +- .../Settings/SettingsSearchEnglishTests.cs | 50 +++++++++++++++++++ 2 files changed, 51 insertions(+), 1 deletion(-) diff --git a/src/ui/Logic/Config/Language/Translate/LanguageTranslate.cs b/src/ui/Logic/Config/Language/Translate/LanguageTranslate.cs index d0cf47b8173..bdee3bb52e1 100644 --- a/src/ui/Logic/Config/Language/Translate/LanguageTranslate.cs +++ b/src/ui/Logic/Config/Language/Translate/LanguageTranslate.cs @@ -6,7 +6,7 @@ public class LanguageTranslate public string MaxBlockSize { get; set; } public string LineSeparator { get; set; } public string BlockCopyInfo { get; set; } - public object BlockCopyGetFromClipboard { get; set; } + public string BlockCopyGetFromClipboard { get; set; } public string ReCopyTextToClipboard { get; set; } public string BlockXOfY { get; set; } public string NoTextInClipboard { get; set; } diff --git a/tests/UI/Features/Options/Settings/SettingsSearchEnglishTests.cs b/tests/UI/Features/Options/Settings/SettingsSearchEnglishTests.cs index b5f3480467a..002a584f98b 100644 --- a/tests/UI/Features/Options/Settings/SettingsSearchEnglishTests.cs +++ b/tests/UI/Features/Options/Settings/SettingsSearchEnglishTests.cs @@ -1,5 +1,9 @@ using Nikse.SubtitleEdit.Features.Options.Settings; using Nikse.SubtitleEdit.Logic.Config.Language; +using System; +using System.IO; +using System.Linq; +using System.Text.Json; namespace UITests.Features.Options.Settings; @@ -52,4 +56,50 @@ public void Filter_MatchesExplicitEnglishLabel() item.Filter("duration"); Assert.False(item.IsVisible); } + + /// + /// The lookup round-trips a loaded translation through . + /// A non-string language property (an object deserializes to a JsonElement the + /// source-generated context can't write) made that throw, so the map was silently empty and + /// English names never matched with any shipped translation. + /// + [Theory] + [MemberData(nameof(TranslationFiles))] + public void Build_WorksForShippedTranslation(string fileName) + { + using var stream = File.OpenRead(Path.Combine(LanguagesFolder(), fileName)); + var translated = JsonSerializer.Deserialize(stream, SeLanguageJsonContext.Default.SeLanguage)!; + + JsonSerializer.SerializeToElement(translated, SeLanguageJsonContext.Default.SeLanguage); // throws naming the bad property + var map = EnglishLabelLookup.Build(translated, new SeLanguage()); + + Assert.NotEmpty(map); + } + + public static TheoryData TranslationFiles() + { + var data = new TheoryData(); + foreach (var path in Directory.GetFiles(LanguagesFolder(), "*.json").Order()) + { + var fileName = Path.GetFileName(path); + if (fileName != "English.json") + { + data.Add(fileName); + } + } + + return data; + } + + private static string LanguagesFolder() + { + var dir = new DirectoryInfo(AppContext.BaseDirectory); + while (dir != null && !Directory.Exists(Path.Combine(dir.FullName, "src", "ui"))) + { + dir = dir.Parent; + } + + return Path.Combine(dir?.FullName ?? throw new DirectoryNotFoundException("Could not find repo root"), + "src", "ui", "Assets", "Languages"); + } } From 8ab57c36511337e0fbc03ad68b6910493f374f85 Mon Sep 17 00:00:00 2001 From: niksedk Date: Tue, 13 Oct 2026 20:23:50 +0200 Subject: [PATCH 109/306] Layout 14: no thumbnail extraction while the video plays (#15956) On macOS .NET starts a process with a full fork(), and malloc is locked for every thread while it runs - mpv's core, the render thread and the UI thread were all caught in _xzm_fork_lock_wait during playback. With one ffmpeg run per filmstrip tile that still gave jitter and crackling audio in layout 14 with an idle CPU, on every mpv renderer. The thumbnail cache now has a Paused switch, set from the waveform's playing state on each render and on the 500 ms track timer. Frames asked for while playing are queued and extracted once paused, newest first. While paused, the next screen of tiles is fetched as well, so playback finds its frames. Co-Authored-By: Claude Opus 5.5 --- .../TimelineThumbnailCache.cs | 48 ++++++++++++++++--- .../AudioVisualizerControl/TimelineTracks.cs | 41 +++++++++++++++- 2 files changed, 82 insertions(+), 7 deletions(-) diff --git a/src/ui/Controls/AudioVisualizerControl/TimelineThumbnailCache.cs b/src/ui/Controls/AudioVisualizerControl/TimelineThumbnailCache.cs index 234c05f1073..83ce5a1761d 100644 --- a/src/ui/Controls/AudioVisualizerControl/TimelineThumbnailCache.cs +++ b/src/ui/Controls/AudioVisualizerControl/TimelineThumbnailCache.cs @@ -21,6 +21,12 @@ namespace Nikse.SubtitleEdit.Controls.AudioVisualizerControl; /// (#15956). Only when the keyframe is too far away for the tile is the exact frame decoded, /// on one thread. ffmpeg runs at below normal priority either way. /// +/// +/// No ffmpeg is started while is set (the video playing): on macOS .NET +/// starts a process with a full fork(), and malloc is locked for every thread meanwhile - mpv's +/// included - which still gave jitter and crackling audio with an idle CPU (#15956). Frames +/// asked for meanwhile wait in the queue. +/// /// internal sealed class TimelineThumbnailCache { @@ -40,10 +46,34 @@ internal sealed class TimelineThumbnailCache private int _generation; private int _consecutiveFailures; private bool _workerRunning; + private bool _paused; /// Raised on a background thread when a requested frame has arrived. public event Action? ThumbnailReady; + /// + /// While set, no new frames are extracted (a run already going finishes). Clearing it works + /// off the frames asked for meanwhile, newest first. + /// + public bool Paused + { + get + { + lock (_lock) + { + return _paused; + } + } + set + { + lock (_lock) + { + _paused = value; + StartWorkerIfNeeded(); + } + } + } + /// /// The frame at , or null while it is not there yet (it is /// then queued). Null as well for a file ffmpeg cannot read - after a few failures in a row @@ -87,16 +117,22 @@ internal sealed class TimelineThumbnailCache _pending.RemoveAt(0); } - if (!_workerRunning) - { - _workerRunning = true; - _ = Task.Run(Work); - } + StartWorkerIfNeeded(); } return null; } + // Called with _lock held + private void StartWorkerIfNeeded() + { + if (!_workerRunning && !_paused && _pending.Count > 0) + { + _workerRunning = true; + _ = Task.Run(Work); + } + } + private void Work() { while (true) @@ -107,7 +143,7 @@ private void Work() int generation; lock (_lock) { - if (_pending.Count == 0) + if (_pending.Count == 0 || _paused) { _workerRunning = false; return; diff --git a/src/ui/Controls/AudioVisualizerControl/TimelineTracks.cs b/src/ui/Controls/AudioVisualizerControl/TimelineTracks.cs index cf8637c7b82..4d45566f671 100644 --- a/src/ui/Controls/AudioVisualizerControl/TimelineTracks.cs +++ b/src/ui/Controls/AudioVisualizerControl/TimelineTracks.cs @@ -139,7 +139,14 @@ public TimelineTracks() // nothing announces. Looking twice a second is far cheaper than looking per frame, and // unlike Render it is allowed to change the control's height. _trackTimer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(500) }; - _trackTimer.Tick += (_, _) => UpdateTracks(); + _trackTimer.Tick += (_, _) => + { + UpdateTracks(); + + // Nothing may be drawn right after playback stops, and the frames queued while it + // played should still come. + UpdateThumbnailsPaused(); + }; PointerEntered += (_, e) => Forward(e, e.GetPosition(this)); PointerMoved += (_, e) => Forward(e, e.GetPosition(this)); @@ -563,6 +570,7 @@ private void DrawFilmstrip(DrawingContext context, AudioVisualizer source, Rect // A keyframe within half a tile is the right picture for it, and much cheaper to get var toleranceMilliseconds = (long)Math.Round(step * 500.0); + var isPlaying = UpdateThumbnailsPaused(); // No tiles past the end of the media (as far as the waveform knows it): there is no // picture there, only an ffmpeg run per tile to find that out. @@ -572,6 +580,22 @@ private void DrawFilmstrip(DrawingContext context, AudioVisualizer source, Rect lastTileSeconds = Math.Min(endSeconds, wavePeaks.LengthInSeconds); } + // While paused, the next screen is fetched too, so playback finds its frames there. It is + // asked for first: the newest request is served first, so the visible tiles go before it. + if (!isPlaying) + { + var aheadEndSeconds = endSeconds + (endSeconds - startSeconds); + if (source.WavePeaks is { LengthInSeconds: > 0 } peaks) + { + aheadEndSeconds = Math.Min(aheadEndSeconds, peaks.LengthInSeconds); + } + + for (var tile = Math.Max(firstTile, (long)Math.Ceiling(endSeconds / step)); tile * step < aheadEndSeconds; tile++) + { + _thumbnails.Get(videoFileName, GetFilmstripFrameMilliseconds(tile, step), toleranceMilliseconds); + } + } + for (var tile = firstTile; tile * step < lastTileSeconds; tile++) { var x = Math.Round((tile * step - startSeconds) * pixelsPerSecond); @@ -603,6 +627,21 @@ private void DrawFilmstrip(DrawingContext context, AudioVisualizer source, Rect } } + /// + /// No frames are extracted while the video plays (see ). + /// Returns whether it plays. + /// + private bool UpdateThumbnailsPaused() + { + var isPlaying = _source?.GetIsVideoPlaying?.Invoke() == true; + if (_thumbnails.Paused != isPlaying) + { + _thumbnails.Paused = isPlaying; + } + + return isPlaying; + } + private void DrawSubtitles(DrawingContext context, AudioVisualizer source, double width, double startSeconds, double pixelsPerSecond) { source.CopyDisplayableParagraphs(_paragraphs); From e9998bc56b17751bd3a70a4c480992cb47017bbe Mon Sep 17 00:00:00 2001 From: Nikolaj Olsson Date: Tue, 13 Oct 2026 20:26:06 +0200 Subject: [PATCH 110/306] Save as over the original's file keeps the translation (#15999) After a translation, "Save as" onto the file the translation was made from wrote the translation, and the same Ctrl+S then saved the dirty original to its old name - the same file - wiping the translation. Picking the original's file now detaches the original (untitled, still prompted for on close), and Ctrl+S no longer saves it while the translation occupies that file. "Save original as" onto the translation's file mirrors this by leaving the translation untitled. Co-Authored-By: Claude Opus 5.5 --- src/ui/Features/Main/MainViewModel.cs | 37 +++++- .../Main/MainTranslationOriginalTests.cs | 115 +++++++++++++++++- 2 files changed, 150 insertions(+), 2 deletions(-) diff --git a/src/ui/Features/Main/MainViewModel.cs b/src/ui/Features/Main/MainViewModel.cs index 2b7ba0f6896..c1588abe0d4 100644 --- a/src/ui/Features/Main/MainViewModel.cs +++ b/src/ui/Features/Main/MainViewModel.cs @@ -724,6 +724,12 @@ private List GetSelectedEditableSubtitles() // setting - that setting answers "no name yet, what do we suggest?", and here there is // one. Consumed and cleared by the next "Save as". private string? _saveAsFileNameSuggestion; + + // The file the translation was "saved as" over, taking it from the original (#15999). While + // the translation still lives there and the original is untitled, Ctrl+S saves the + // translation only instead of asking where the original goes every time; closing still + // prompts for the original's unsaved changes. + private string? _originalFileReplacedByTranslation; private Subtitle _subtitle; private Subtitle? _subtitleSecondary; // The second subtitle as parsed from its file, before styling. "Edit second subtitle @@ -4649,11 +4655,18 @@ private async Task CommandFileSave() var saved = await SaveSubtitle(); var savedOriginal = false; + // "Save as" may have written the translation over the original's own file (now, or on an + // earlier save), which leaves the original untitled. Saving it here would ask for a file + // name on every Ctrl+S - or, answered with the same file, put the original straight back + // over the translation (#15999). + var originalDetached = string.IsNullOrEmpty(_subtitleFileNameOriginal) && + IsSameFile(_subtitleFileName, _originalFileReplacedByTranslation); + // Only when the subtitle itself was written. Ctrl+S is one gesture: after a translation // _converted is set, so SaveSubtitle routes to "Save as" and returns false when the user // dismisses the picker - and the original was written anyway, over the file the // translation was made from, by a save the user had just cancelled. - if (saved && ShowColumnOriginalText && _changeSubtitleHashOriginal != GetFastHashOriginal()) + if (saved && !originalDetached && ShowColumnOriginalText && _changeSubtitleHashOriginal != GetFastHashOriginal()) { savedOriginal = await SaveSubtitleOriginal(); } @@ -28566,6 +28579,20 @@ private async Task SaveSubtitleAs() _formatChangedByUser = false; } + // Picking the file the translation was made from replaces it with the translation, so the + // original no longer has a file of its own. Keeping the name made the next original save + // (the same Ctrl+S, or a later one) write the original back over the translation (#15999). + // Untitled, the original stays in memory and its unsaved changes are still prompted for. + if (ShowColumnOriginalText && IsSameFile(saveAsResult.FileName, _subtitleFileNameOriginal)) + { + _originalFileReplacedByTranslation = saveAsResult.FileName; + _subtitleFileNameOriginal = string.Empty; + if (_subtitleOriginal != null) + { + _subtitleOriginal.FileName = string.Empty; + } + } + _subtitleFileName = saveAsResult.FileName; _subtitle.FileName = saveAsResult.FileName; _converted = false; @@ -28782,6 +28809,14 @@ private async Task SaveSubtitleOriginalAs() return false; } + // The mirror of SaveSubtitleAs: the original now occupies the translation's file, so the + // next Ctrl+S must ask where the translation goes instead of writing over it (#15999). + if (IsSameFile(fileName, _subtitleFileName)) + { + _subtitleFileName = string.Empty; + _subtitle.FileName = string.Empty; + } + AddToRecentFiles(true); return true; } diff --git a/tests/UI/Features/Main/MainTranslationOriginalTests.cs b/tests/UI/Features/Main/MainTranslationOriginalTests.cs index cc67fa50f58..2516b6830d2 100644 --- a/tests/UI/Features/Main/MainTranslationOriginalTests.cs +++ b/tests/UI/Features/Main/MainTranslationOriginalTests.cs @@ -1,13 +1,16 @@ using System.Reflection; +using Avalonia; using Avalonia.Controls; using Avalonia.Headless.XUnit; using Avalonia.Threading; using Microsoft.Extensions.DependencyInjection; using Nikse.SubtitleEdit; using Nikse.SubtitleEdit.Core.Common; +using Nikse.SubtitleEdit.Core.SubtitleFormats; using Nikse.SubtitleEdit.Features.Main; using Nikse.SubtitleEdit.Features.Main.MainHelpers; using Nikse.SubtitleEdit.Logic; +using Nikse.SubtitleEdit.Logic.Media; namespace UITests.Features.Main; @@ -209,10 +212,120 @@ public void RemoveReferenceOnlyRows_LeavesTheRowsAlignedWithTheTranslatedSubtitl } } - private static (Window Window, MainViewModel Vm) CreateMainViewModel() + /// + /// "Save as" onto the file the translation was made from replaces that file with the + /// translation. The same Ctrl+S then saved the (unsaved) original to its old file name - the + /// same file - so the translation was overwritten straight away and the file looked untouched + /// (issue #15999). + /// + [AvaloniaFact] + public async Task Save_TranslationSavedAsOverTheOriginalFile_KeepsTheTranslation() + { + var fileName = Path.Combine(Path.GetTempPath(), $"se-translation-over-original-{Guid.NewGuid():N}.srt"); + await File.WriteAllTextAsync(fileName, "1\r\n00:00:00,000 --> 00:00:02,000\r\nSource line one\r\n\r\n"); + var (window, vm) = CreateMainViewModel(new PickFileHelper(fileName)); + + try + { + SetUpTranslationOf(vm, fileName); + + await vm.CommandFileSaveCommand.ExecuteAsync(null); + + var onDisk = await File.ReadAllTextAsync(fileName); + Assert.Contains("Vertaalde regel een", onDisk); + Assert.DoesNotContain("Source line one", onDisk); + Assert.Equal(string.Empty, GetField("_subtitleFileNameOriginal").GetValue(vm)); + + // A second Ctrl+S must not bring the original back over the translation either. + await vm.CommandFileSaveCommand.ExecuteAsync(null); + + onDisk = await File.ReadAllTextAsync(fileName); + Assert.Contains("Vertaalde regel een", onDisk); + Assert.DoesNotContain("Source line one", onDisk); + + // The original was never written anywhere, so closing must still offer to save it. + Assert.NotEqual(GetField("_changeSubtitleHashOriginal").GetValue(vm), vm.GetFastHashOriginal()); + } + finally + { + CloseWindow(window, vm); + File.Delete(fileName); + } + } + + /// + /// The mirror: "Save original as" onto the translation's file hands that file to the original, + /// so the translation becomes untitled and the next Ctrl+S asks where it goes (#15999). + /// + [AvaloniaFact] + public async Task SaveOriginalAs_OverTheTranslationFile_DetachesTheTranslation() + { + var translationFileName = Path.Combine(Path.GetTempPath(), $"se-translation-{Guid.NewGuid():N}.srt"); + var (window, vm) = CreateMainViewModel(new PickFileHelper(translationFileName)); + + try + { + SetUpTranslationOf(vm, string.Empty); + SetPrivateField(vm, "_subtitleFileName", translationFileName); + SetPrivateField(vm, "_converted", false); + + var method = typeof(MainViewModel).GetMethod( + "SaveSubtitleOriginalAs", BindingFlags.Instance | BindingFlags.NonPublic) + ?? throw new InvalidOperationException("SaveSubtitleOriginalAs not found"); + var saved = await (Task)method.Invoke(vm, null)!; + + Assert.True(saved); + Assert.Contains("Source line one", await File.ReadAllTextAsync(translationFileName)); + Assert.Equal(string.Empty, GetField("_subtitleFileName").GetValue(vm)); + Assert.Equal(translationFileName, GetField("_subtitleFileNameOriginal").GetValue(vm)); + } + finally + { + CloseWindow(window, vm); + File.Delete(translationFileName); + } + } + + // The state a translation made from the loaded file leaves behind: the rows hold the + // translation, the original column (and original subtitle) the source text, and the original + // points at the source file while the translation is still untitled. + private static void SetUpTranslationOf(MainViewModel vm, string originalFileName) + { + vm.SelectedSubtitleFormat = vm.SubtitleFormats.First(f => f is SubRip); + AddLine(vm, "Vertaalde regel een", "Source line one", 0, 2000); + vm.IsOriginalReadOnly = false; + vm.ShowColumnOriginalText = true; + InvokeCaptureOriginalFromTranslatedRows(vm); + SetPrivateField(vm, "_subtitleFileNameOriginal", originalFileName); + SetPrivateField(vm, "_subtitleFileName", string.Empty); + SetPrivateField(vm, "_converted", true); + } + + private sealed class PickFileHelper : StubFileHelper + { + private readonly string _fileName; + + public PickFileHelper(string fileName) + { + _fileName = fileName; + } + + public override Task PickSaveSubtitleFileAs(Visual sender, SubtitleFormat currentFormat, string suggestedFileName, string title) + => Task.FromResult(new FileHelperSubtitleSavePickerResult { FileName = _fileName, SubtitleFormat = currentFormat }); + + public override Task PickSaveSubtitleFile(Visual sender, SubtitleFormat currentFormat, string suggestedFileName, string title) + => Task.FromResult(_fileName); + } + + private static (Window Window, MainViewModel Vm) CreateMainViewModel(IFileHelper? fileHelper = null) { var services = new ServiceCollection(); services.AddSubtitleEditServices(); + if (fileHelper != null) + { + services.AddSingleton(fileHelper); + } + Locator.Services = services.BuildServiceProvider(); var window = new Window { Width = 1200, Height = 800 }; From f5b2013374c088af57f8bade0a86730ccbfb4916 Mon Sep 17 00:00:00 2001 From: Chirag Jangir <184008125+chiragjangiir@users.noreply.github.com> Date: Tue, 13 Oct 2026 23:57:50 +0530 Subject: [PATCH 111/306] IMSC 1.1: keep trailing
forced line breaks The reader built each cue as ReadParagraph(...).TrimEnd(), which also removed the newlines a trailing
adds - so "C


" loaded as "C" and the forced breaks were lost on import (#15289). Trim only the text behind the last forced break instead: everything before it already ends in a line break and is content, while the tail is still source formatting. A paragraph without a
trims exactly as before, and nested spans are still read raw, so a space inside the last span survives. Part of #15289 Generated with Codebuff Co-Authored-By: Codebuff --- src/libse/SubtitleFormats/TimedTextImsc11.cs | 32 ++++++++- .../SubtitleFormats/TimedTextImsc11Test.cs | 65 +++++++++++++++++++ 2 files changed, 95 insertions(+), 2 deletions(-) diff --git a/src/libse/SubtitleFormats/TimedTextImsc11.cs b/src/libse/SubtitleFormats/TimedTextImsc11.cs index 63cbad402ea..77396d900cf 100644 --- a/src/libse/SubtitleFormats/TimedTextImsc11.cs +++ b/src/libse/SubtitleFormats/TimedTextImsc11.cs @@ -626,7 +626,7 @@ public override void LoadSubtitle(Subtitle subtitle, List lines, string assStyle = GetAssStyleFromRegion(region.InnerText, headIndex); } - var text = assStyle + ReadParagraph(node, headIndex).TrimEnd(); + var text = assStyle + ReadParagraph(node, headIndex); // Keep the region name: the alignment tag above only snaps to the screen thirds, // the video preview positions the line from the region box itself. @@ -637,9 +637,30 @@ public override void LoadSubtitle(Subtitle subtitle, List lines, string subtitle.Renumber(); } + /// + /// Reads one TTML p element into subtitle text. Trailing source whitespace is + /// trimmed here, but the line breaks a trailing
adds are content: in TTML they are + /// forced breaks with layout meaning, so dropping them changes the cue (#15289). Nested + /// spans are read raw - only the finished paragraph is trimmed, so a space inside the + /// last span still survives. + ///
private static string ReadParagraph(XmlNode node, TtmlHeadIndex headIndex) + { + var raw = BuildParagraph(node, headIndex, out var forcedBreakEnd); + if (forcedBreakEnd < 0) + { + return raw.TrimEnd(); + } + + // Everything before the last forced break already ends in a line break, so only the + // text behind it can be source formatting - that tail is trimmed as before. + return raw[..forcedBreakEnd] + raw[forcedBreakEnd..].TrimEnd(); + } + + private static string BuildParagraph(XmlNode node, TtmlHeadIndex headIndex, out int forcedBreakEnd) { var pText = new StringBuilder(); + forcedBreakEnd = -1; foreach (XmlNode child in node.ChildNodes) { if (child.NodeType == XmlNodeType.Text) @@ -649,6 +670,7 @@ private static string ReadParagraph(XmlNode node, TtmlHeadIndex headIndex) else if (child.Name == "br" || child.Name == "tt:br") { pText.AppendLine(); + forcedBreakEnd = pText.Length; } else if (child.Name == "#significant-whitespace" || child.Name == "tt:#significant-whitespace") { @@ -787,7 +809,13 @@ private static string ReadParagraph(XmlNode node, TtmlHeadIndex headIndex) pText.Append(">"); } - pText.Append(ReadParagraph(child, headIndex)); + var contentStart = pText.Length; + var content = BuildParagraph(child, headIndex, out var nestedBreakEnd); + pText.Append(content); + if (nestedBreakEnd >= 0) + { + forcedBreakEnd = contentStart + nestedBreakEnd; + } if (!string.IsNullOrEmpty(fontFamily) || !string.IsNullOrEmpty(color)) { diff --git a/tests/libse/SubtitleFormats/TimedTextImsc11Test.cs b/tests/libse/SubtitleFormats/TimedTextImsc11Test.cs index 84d34c78d02..b3abbf34f95 100644 --- a/tests/libse/SubtitleFormats/TimedTextImsc11Test.cs +++ b/tests/libse/SubtitleFormats/TimedTextImsc11Test.cs @@ -128,4 +128,69 @@ public void IsMine_Accepts_All_Imsc_Extensions_And_Keeps_Consecutive_Line_Breaks Configuration.Settings.SubtitleSettings.TimedTextImsc11FileExtension = oldExtension; } } + + private static string TrailingBreaksXml() => + "" + + "" + + "
" + + "

A


B

" + + "

C


" + + "
"; + + [Fact] + public void Trailing_Forced_Line_Breaks_Are_Preserved() + { + // #15289: the reader ran TrimEnd() over ReadParagraph(), so every trailing
was + // dropped - three of them is a layout-significant blank line in TTML. + var sut = new TimedTextImsc11(); + var subtitle = new Subtitle(); + + sut.LoadSubtitle(subtitle, TrailingBreaksXml().SplitToLines(), "test.xml"); + + Assert.Equal(2, subtitle.Paragraphs.Count); + Assert.Equal("A" + Environment.NewLine + Environment.NewLine + Environment.NewLine + "B", + subtitle.Paragraphs[0].Text); + Assert.Equal("C" + Environment.NewLine + Environment.NewLine + Environment.NewLine, + subtitle.Paragraphs[1].Text); + } + + [Fact] + public void Trailing_Source_Whitespace_Is_Still_Trimmed() + { + // The TrimEnd() also kept source formatting out of the text: an inline trailing space and + // an indented paragraph must both come back without trailing whitespace. + var sut = new TimedTextImsc11(); + var compact = TrailingBreaksXml().Replace("C


", "C "); + var indented = "" + + "" + + "
\n" + + "

Hello world

\n" + + "
"; + + var compactSubtitle = new Subtitle(); + sut.LoadSubtitle(compactSubtitle, compact.SplitToLines(), "test.xml"); + var indentedSubtitle = new Subtitle(); + sut.LoadSubtitle(indentedSubtitle, indented.SplitToLines(), "test.xml"); + + Assert.Equal("C", compactSubtitle.Paragraphs[1].Text); + Assert.Equal("Hello world", indentedSubtitle.Paragraphs[0].Text); + } + + [Fact] + public void LoadSaveLoad_Keeps_Trailing_Forced_Line_Breaks() + { + var sut = new TimedTextImsc11(); + var subtitle = new Subtitle(); + sut.LoadSubtitle(subtitle, TrailingBreaksXml().SplitToLines(), "test.xml"); + + var saved = sut.ToText(subtitle, "title"); + var reloaded = new Subtitle(); + new TimedTextImsc11().LoadSubtitle(reloaded, saved.SplitToLines(), "test.xml"); + + Assert.Equal(subtitle.Paragraphs.Count, reloaded.Paragraphs.Count); + Assert.Equal("A" + Environment.NewLine + Environment.NewLine + Environment.NewLine + "B", + reloaded.Paragraphs[0].Text); + Assert.Equal("C" + Environment.NewLine + Environment.NewLine + Environment.NewLine, + reloaded.Paragraphs[1].Text); + } } From 563905b75014a25394a1928e630a98d7340f5cb9 Mon Sep 17 00:00:00 2001 From: niksedk Date: Tue, 13 Oct 2026 20:30:06 +0200 Subject: [PATCH 112/306] Choose UI language: keep dragged panel sizes (#16001) OK in Choose UI language rebuilt the layout without saving and restoring the layout positions like Options/Settings does, so the video/waveform/list splitters snapped back to defaults. Layout 15 kept its size only because the edit box height is tracked separately. Take the positions before loading the language and restore them after both the language rebuild and the right-to-left direction rebuild. While the video controls are undocked, re-run the undock flow instead of SetLayout, same as closing Options/Settings. Co-Authored-By: Claude Opus 5.5 --- src/ui/Features/Main/MainViewModel.cs | 45 +++++++++++++++++++++++---- 1 file changed, 39 insertions(+), 6 deletions(-) diff --git a/src/ui/Features/Main/MainViewModel.cs b/src/ui/Features/Main/MainViewModel.cs index 2b7ba0f6896..e4d94032ee0 100644 --- a/src/ui/Features/Main/MainViewModel.cs +++ b/src/ui/Features/Main/MainViewModel.cs @@ -14991,7 +14991,8 @@ public void ApplySettings() if (languageChanged && Se.Settings.General.IsLanguageRightToLeft() != wasRightToLeft) { - ApplyLayoutDirectionForCurrentLanguage(videoPosition); + // Undocked, the sizes were not taken above (no docked panels to keep). + ApplyLayoutDirectionForCurrentLanguage(videoPosition, AreVideoControlsUndocked ? null : Se.Settings.Appearance.CurrentLayoutPositions); } } @@ -15613,7 +15614,12 @@ private async Task CommandShowSettingsLanguage() // taken here to be restored once the new layout is in place. var videoPosition = GetVideoPlayerControl()?.VideoPlayer?.Position ?? 0; - await LoadLanguage(viewModel.SelectedLanguage.FileName); + // The panel sizes the user dragged are lost by the rebuild as well, so note + // them now too - once, before the first rebuild, as the direction rebuild + // below runs later, when the new grids may not be measured yet (#16001). + var layoutPositions = InitLayout.SaveLayoutPositions(ContentGrid.Children.FirstOrDefault() as Grid); + + await LoadLanguage(viewModel.SelectedLanguage.FileName, layoutPositions); // Strings update live already. When the new language flips the layout // direction (between a right to left and a left to right language), @@ -15621,12 +15627,12 @@ private async Task CommandShowSettingsLanguage() // correct side at once, instead of asking the user to restart. if (Se.Settings.General.IsLanguageRightToLeft() != wasRightToLeft) { - ApplyLayoutDirectionForCurrentLanguage(videoPosition); + ApplyLayoutDirectionForCurrentLanguage(videoPosition, layoutPositions); } } } - private async Task LoadLanguage(string jsonFileName) + private async Task LoadLanguage(string jsonFileName, InitLayout.LayoutPositions? layoutPositions = null) { await Se.LoadLanguageFromFileAsync(jsonFileName); @@ -15639,7 +15645,23 @@ private async Task LoadLanguage(string jsonFileName) { Layout.InitNativeMacMenu.Rebuild(this); } - SetLayout(Se.Settings.General.LayoutNumber); + + // Same as closing Options/Settings: re-run the undock flow while the video + // controls are undocked (SetLayout would dock fresh panels next to the open + // undocked windows, #12520), else rebuild and put back the panel sizes the + // user dragged, which the fresh grids would otherwise reset (#16001). + if (AreVideoControlsUndocked) + { + VideoUndockControls(); + } + else + { + SetLayout(Se.Settings.General.LayoutNumber); + if (layoutPositions != null) + { + InitLayout.RestoreLayoutPositions(layoutPositions, ContentGrid.Children.FirstOrDefault() as Grid); + } + } RebuildToolbar(); @@ -19073,7 +19095,11 @@ private void RefreshVideoPreviewAfterRightToLeftChange() /// loading the language rebuilds the layout, so by now the player is a fresh one /// sitting at the beginning. /// - private void ApplyLayoutDirectionForCurrentLanguage(double videoPosition) + /// + /// The panel sizes taken before the language was loaded, restored after the rebuild, + /// or null to keep the default sizes. + /// + private void ApplyLayoutDirectionForCurrentLanguage(double videoPosition, InitLayout.LayoutPositions? layoutPositions) { if (Window == null) { @@ -19111,6 +19137,13 @@ private void ApplyLayoutDirectionForCurrentLanguage(double videoPosition) // layout is built after the flow direction is set). SetLayout(Se.Settings.General.LayoutNumber); + // The rebuilt grids start at the default sizes; put back the panel sizes the + // user dragged (the column order is the same in both directions, #16001). + if (layoutPositions != null) + { + InitLayout.RestoreLayoutPositions(layoutPositions, ContentGrid.Children.FirstOrDefault() as Grid); + } + // Apply the text and grid direction for the new mode in both directions // (SetLayout only re-applies it for right to left), so the list is never // left in the previous direction when switching back to left to right. From 6833a6df0acb1b69b348f39377055a269a2a1845 Mon Sep 17 00:00:00 2001 From: niksedk Date: Tue, 13 Oct 2026 20:31:28 +0200 Subject: [PATCH 113/306] OCR privacy blur: share one immutable converter, skip bindings when off - Move ScreenPrivacyBlurConverter to Logic/ValueConverters (was duplicated in OcrWindow and VideoOcrWindow), with ImmutableBlurEffect like the main window and a BlurUnlessSelected helper - Only bind when texts are hidden - the level can't change while these modal dialogs are open - Restore the HasFormattedText comment, tidy usings, newline at EOF - Converter tests Co-Authored-By: Claude Opus 5.5 --- src/ui/Features/Ocr/OcrWindow.cs | 42 ++------------ .../Features/Video/VideoOcr/VideoOcrWindow.cs | 28 +--------- .../ScreenPrivacyBlurConverter.cs | 55 +++++++++++++++++++ .../ScreenPrivacyBlurConverterTests.cs | 37 +++++++++++++ 4 files changed, 99 insertions(+), 63 deletions(-) create mode 100644 src/ui/Logic/ValueConverters/ScreenPrivacyBlurConverter.cs create mode 100644 tests/UI/Logic/ValueConverters/ScreenPrivacyBlurConverterTests.cs diff --git a/src/ui/Features/Ocr/OcrWindow.cs b/src/ui/Features/Ocr/OcrWindow.cs index 3e71d8e2029..267ce7c632c 100644 --- a/src/ui/Features/Ocr/OcrWindow.cs +++ b/src/ui/Features/Ocr/OcrWindow.cs @@ -600,15 +600,7 @@ double ColumnWidth(string header, string widestCellText) => Padding = new Thickness(3), Child = image, }; - imageContainer[!Visual.EffectProperty] = new MultiBinding - { - Converter = ScreenPrivacyBlurConverter.Instance, - Bindings = - { - new Binding(), - new Binding(nameof(dataGridSubtitle.SelectedItem)) { Source = dataGridSubtitle } - } - }; + ScreenPrivacyBlurConverter.BlurUnlessSelected(imageContainer, dataGridSubtitle); stackPanel.Children.Add(imageContainer); } @@ -630,25 +622,16 @@ double ColumnWidth(string header, string widestCellText) => Margin = new Thickness(3, 1, 3, 1) }; - // The existing text rendering binding + // Bind to HasFormattedText to trigger updates when FixResult changes var binding = new Binding(nameof(OcrSubtitleItem.HasFormattedText)) { Source = item, Converter = new FuncValueConverter(hasFormatted => item.CreateFormattedText()) }; - contentPresenter.Bind(ContentPresenter.ContentProperty, binding); - - contentPresenter[!Visual.EffectProperty] = new MultiBinding - { - Converter = ScreenPrivacyBlurConverter.Instance, - Bindings = - { - new Binding(), // Source 1: The row's own data - new Binding(nameof(dataGridSubtitle.SelectedItem)) { Source = dataGridSubtitle } // Source 2: The grid's selection - } - }; + contentPresenter.Bind(ContentPresenter.ContentProperty, binding); + ScreenPrivacyBlurConverter.BlurUnlessSelected(contentPresenter, dataGridSubtitle); return contentPresenter; }) }, @@ -1374,19 +1357,4 @@ private static FuncDataTemplate MakeCrispEmbedModelItemT ? DownloadDotStatus.UpToDate : DownloadDotStatus.NotInstalled); } - private class ScreenPrivacyBlurConverter : IMultiValueConverter - { - public static readonly ScreenPrivacyBlurConverter Instance = new(); - private static readonly IEffect Blur = new BlurEffect { Radius = 8 }; - - public object? Convert(System.Collections.Generic.IList values, Type targetType, object? parameter, CultureInfo culture) - { - if (!UiUtil.HideTexts || values.Count != 2) return null; - - var currentItem = values[0]; - var selectedItem = values[1]; - - return currentItem != null && currentItem != selectedItem ? Blur : null; - } - } -} \ No newline at end of file +} diff --git a/src/ui/Features/Video/VideoOcr/VideoOcrWindow.cs b/src/ui/Features/Video/VideoOcr/VideoOcrWindow.cs index 939ffd0e6cb..c62f2839d60 100644 --- a/src/ui/Features/Video/VideoOcr/VideoOcrWindow.cs +++ b/src/ui/Features/Video/VideoOcr/VideoOcrWindow.cs @@ -15,7 +15,7 @@ using Nikse.SubtitleEdit.Logic.ValueConverters; using System.Linq; using Nikse.SubtitleEdit.UiLogic.LlamaCpp; -using System; + namespace Nikse.SubtitleEdit.Features.Video.VideoOcr; public class VideoOcrWindow : Window @@ -597,16 +597,7 @@ private static Border MakeLinesView(VideoOcrViewModel vm) [!ContentControl.ContentProperty] = new Binding(nameof(VideoOcrLineItem.FormattedText)), }; - contentControl[!Visual.EffectProperty] = new MultiBinding - { - Converter = ScreenPrivacyBlurConverter.Instance, - Bindings = - { - new Binding(), - new Binding(nameof(tableView.SelectedItem)) { Source = tableView } - } - }; - + ScreenPrivacyBlurConverter.BlurUnlessSelected(contentControl, tableView); return contentControl; }, supportsRecycling: true), }); @@ -762,19 +753,4 @@ private static Grid MakeProgressView(VideoOcrViewModel vm) return grid; } - private class ScreenPrivacyBlurConverter : Avalonia.Data.Converters.IMultiValueConverter - { - public static readonly ScreenPrivacyBlurConverter Instance = new(); - private static readonly Avalonia.Media.IEffect Blur = new Avalonia.Media.BlurEffect { Radius = 8 }; - - public object? Convert(System.Collections.Generic.IList values, Type targetType, object? parameter, System.Globalization.CultureInfo culture) - { - if (!UiUtil.HideTexts || values.Count != 2) return null; - - var currentItem = values[0]; - var selectedItem = values[1]; - - return currentItem != null && currentItem != selectedItem ? Blur : null; - } - } } diff --git a/src/ui/Logic/ValueConverters/ScreenPrivacyBlurConverter.cs b/src/ui/Logic/ValueConverters/ScreenPrivacyBlurConverter.cs new file mode 100644 index 00000000000..a720beeaf59 --- /dev/null +++ b/src/ui/Logic/ValueConverters/ScreenPrivacyBlurConverter.cs @@ -0,0 +1,55 @@ +using Avalonia; +using Avalonia.Data; +using Avalonia.Data.Converters; +using Avalonia.Media; +using Avalonia.Media.Immutable; +using System; +using System.Collections.Generic; +using System.Globalization; + +namespace Nikse.SubtitleEdit.Logic.ValueConverters; + +/// +/// Screen privacy blur for grid cells in dialogs (#13311): values[0] is the row's item, +/// values[1] is the grid's SelectedItem. Every row except the selected one is blurred, so the +/// line being fixed stays readable while the rest of the subtitle is hidden. +/// +public class ScreenPrivacyBlurConverter : IMultiValueConverter +{ + public static readonly ScreenPrivacyBlurConverter Instance = new(); + + private static readonly IEffect Blur = new ImmutableBlurEffect(8); + + /// + /// Blurs unless its row is selected in . + /// Does nothing when texts are not hidden - the privacy level is toggled from the main + /// window, so it cannot change while a modal dialog is open. + /// + public static void BlurUnlessSelected(Visual cell, AvaloniaObject grid) + { + if (!UiUtil.HideTexts) + { + return; + } + + cell[!Visual.EffectProperty] = new MultiBinding + { + Converter = Instance, + Bindings = + { + new Binding(), + new Binding("SelectedItem") { Source = grid }, + }, + }; + } + + public object? Convert(IList values, Type targetType, object? parameter, CultureInfo culture) + { + if (values.Count != 2 || values[0] == null) + { + return null; + } + + return ReferenceEquals(values[0], values[1]) ? null : Blur; + } +} diff --git a/tests/UI/Logic/ValueConverters/ScreenPrivacyBlurConverterTests.cs b/tests/UI/Logic/ValueConverters/ScreenPrivacyBlurConverterTests.cs new file mode 100644 index 00000000000..4cae3367564 --- /dev/null +++ b/tests/UI/Logic/ValueConverters/ScreenPrivacyBlurConverterTests.cs @@ -0,0 +1,37 @@ +using Avalonia.Media; +using Nikse.SubtitleEdit.Logic.ValueConverters; +using System.Globalization; + +namespace UITests.Logic.ValueConverters; + +public class ScreenPrivacyBlurConverterTests +{ + private static object? Convert(object? item, object? selectedItem) => + ScreenPrivacyBlurConverter.Instance.Convert([item, selectedItem], typeof(IEffect), null, CultureInfo.InvariantCulture); + + [Fact] + public void UnselectedRow_IsBlurred() + { + Assert.IsAssignableFrom(Convert(new object(), new object())); + } + + [Fact] + public void NoSelection_IsBlurred() + { + Assert.IsAssignableFrom(Convert(new object(), null)); + } + + [Fact] + public void SelectedRow_IsSharp() + { + var item = new object(); + + Assert.Null(Convert(item, item)); + } + + [Fact] + public void RowWithoutItem_IsSharp() + { + Assert.Null(Convert(null, new object())); + } +} From 1c90969597e2d6643240775647d42f5b054c07ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Igor=20R=C3=BCckert?= Date: Tue, 13 Oct 2026 16:25:36 -0300 Subject: [PATCH 114/306] Update Portuguese (Brazil).json --- .../Assets/Languages/Portuguese (Brazil).json | 431 +++++++++++++++++- 1 file changed, 418 insertions(+), 13 deletions(-) diff --git a/src/ui/Assets/Languages/Portuguese (Brazil).json b/src/ui/Assets/Languages/Portuguese (Brazil).json index 592ee80d946..c6ca873fd91 100644 --- a/src/ui/Assets/Languages/Portuguese (Brazil).json +++ b/src/ui/Assets/Languages/Portuguese (Brazil).json @@ -1,6 +1,6 @@ { "title": "Subtitle Edit", - "version": "v5.3.0-beta19", + "version": "v5.3.0-rc4", "translatedBy": "igorruckert", "cultureName": "pt-BR", "general": { @@ -13,6 +13,18 @@ "actorPickerKeysHint": "Pressione um número ou Enter para definir o ator. Alt+Cima/Baixo altera a ordem, Delete remove o ator.", "actorPickerNewActorX": "Novo ator \"{0}", "actorPickerLinesSelectedX": "Linhas selecionadas: {0}", + "setStyleDotDotDot": "Definir estilo...", + "stylePickerTitle": "Definir estilo", + "stylePickerFilterHint": "Digite para filtrar ou digite um novo nome de estilo", + "stylePickerKeysHint": "Pressione um número (1-9, 0) para definir o estilo em todas as linhas selecionadas.", + "stylePickerNewStyleX": "Novo estilo \"{0}", + "stylePickerNewStyleInfo": "Pressione Enter para adicionar este estilo com configurações padrão e configurá-lo nas linhas selecionadas. Use \"Gerenciar estilos...\" para alterar sua fonte, cores e posição posteriormente.", + "stylePickerCurrentStyleX": "Estilo atual: {0}", + "stylePickerLinesUsingStyleX": "Linhas que usam este estilo: {0}", + "stylePickerMarginsXYZ": "Esquerda {0}, direita {1}, vertical {2}", + "stylePickerSetStyle": "Definir e_stilo", + "stylePickerManageStyles": "Gerenciar estilos...", + "stylePickerManageStylesHint": "Feche esta janela e abra o gerenciador de estilos para editar fontes, cores, posição e muito mais", "add": "Adicionar", "addDotDotDot": "Adicionar...", "addToNamesListCaseSensitive": "Adicionar à lista de nomes (diferencia maiúsculas de minúsculas)", @@ -431,6 +443,7 @@ "percent": "Porcentagem", "pickLayer": "Definir camada", "pickOllamaModel": "Escolher modelo Ollama", + "pickModel": "Escolha o modelo", "llamaCppUseRemoteServer": "Usar servidor externo (URL)", "llamaCppEngineSettings": "Configurações do mecanismo llama.cpp", "llamaCppEngineSettingsSubtitle": "Compilação do servidor local llama.cpp", @@ -507,6 +520,7 @@ "reset": "Redefinir", "resizePanels": "Redimensionar painéis", "resolution": "Resolução", + "retry": "Repetir", "reverseRightToLeftStartEnd": "Início/fim RTL reverso", "right": "Direita", "rightToLeft": "Direita para esquerda", @@ -568,6 +582,8 @@ "shadowColor": "Cor da sombra", "shadowWidth": "Largura da sombra", "shiftLineBy": "Deslocar linha em", + "shotIn": "Início da cena", + "shotOut": "Fim da cena", "shortcut": "Atalho", "shortcuts": "Atalhos", "show": "Mostrar", @@ -579,9 +595,13 @@ "showStartColumn": "Mostrar coluna \"Início\"", "showHideColumn": "Mostrar coluna \"Ocultar\"", "showHistory": "Mostrar histórico", + "clearHistory": "Limpar histórico", + "removeFromHistory": "Remover do histórico", "showLayerColumn": "Mostrar coluna \"Camada\"", "showPreview": "Mostrar prévia", "showShotChangesList": "Mostrar lista de mudanças de cena", + "showShotInColumn": "Mostrar coluna \"Início da cena\"", + "showShotOutColumn": "Mostrar coluna \"Fim da cena\"", "showStyleColumn": "Mostrar coluna \"Estilo\"", "showTeletext": "Mostrar TT", "showTimeCodes": "Mostrar códigos de tempo", @@ -638,12 +658,14 @@ "styleExaggeration": "Exagero de estilo", "styles": "Estilos", "subtitleFile": "Arquivo de legenda", + "subtitleFileChangedOutsideX": "O arquivo \"{0}\" foi alterado fora do Subtitle Edit.\r\n\r\nRecarregá-lo e perder suas alterações?", "subtitleFileName": "Nome do arquivo de legenda", "subtitleFileSaved": "Arquivo de legenda salvo", "subtitleFileSavedToX": "Arquivo de legenda salvo em {0}", "subtitleFiles": "Arquivos de legenda", "subtitleFormats": "Formatos de legenda", "subtitleLoadedX": "Legenda carregada: {0}", + "subtitleReloadedChangedOutsideX": "Legenda recarregada (alterada fora do Subtitle Edit): {0}", "suffix": "Sufixo:", "suggestions": "Sugestões", "sync": "Sincronizar", @@ -727,10 +749,23 @@ "videoCustom3ForwardX": "Vídeo, milissegundos personalizados ({0:#,###,##0}) adiante, 3", "videoCustom4BackX": "Vídeo, milissegundos personalizados ({0:#,###,##0}) atrás, 4", "videoCustom4ForwardX": "Vídeo, milissegundos personalizados ({0:#,###,##0}) adiante, 4", + "videoCustom1BackAndPauseX": "Vídeo, milissegundos personalizados ({0:#,###,##0}) atrás e pausar, 1", + "videoCustom1ForwardAndPauseX": "Vídeo, milissegundos personalizados ({0:#,###,##0}) adiante e pausar, 1", + "videoCustom2BackAndPauseX": "Vídeo, milissegundos personalizados ({0:#,###,##0}) atrás e pausar, 2", + "videoCustom2ForwardAndPauseX": "Vídeo, milissegundos personalizados ({0:#,###,##0}) adiante e pausar, 2", + "videoCustom3BackAndPauseX": "Vídeo, milissegundos personalizados ({0:#,###,##0}) atrás e pausar, 3", + "videoCustom3ForwardAndPauseX": "Vídeo, milissegundos personalizados ({0:#,###,##0}) adiante e pausar, 3", + "videoCustom4BackAndPauseX": "Vídeo, milissegundos personalizados ({0:#,###,##0}) atrás e pausar, 4", + "videoCustom4ForwardAndPauseX": "Vídeo, milissegundos personalizados ({0:#,###,##0}) adiante e pausar, 4", "videoExtension": "Extensão do arquivo de vídeo", "videoFile": "Arquivo de vídeo", "videoFileGenerated": "Arquivo de vídeo gerado", "videoFileGeneratedX": "Arquivo de vídeo gerado: \"{0}\"", + "videoFilesGenerated": "Arquivos de vídeo gerados", + "xVideosGenerated": "{0} vídeos gerados", + "xOfYVideosGenerated": "{0} de {1} vídeos gerados", + "savedInX": "Salvo em {0}", + "savedInXFolders": "Salvo em {0} pastas", "videoFiles": "Arquivos de vídeo", "videoAndAudioFiles": "Arquivos de vídeo e áudio", "videoOffset": "Deslocamento de vídeo", @@ -857,6 +892,7 @@ "multipleReplace": "Substituição _múltipla...", "fillSelectedLinesWithClipboard": "Preencher linhas selecionadas com o texto da área de transferência", "goToLineNumber": "Ir para _linha número...", + "commandPalette": "_Paleta de comandos...", "rightToLeftMode": "Modo d_ireita-para-esquerda", "modifySelectionDotDotDot": "Modificar _seleção...", "tools": "_Ferramentas", @@ -1049,6 +1085,9 @@ "charactersPerSecond": "Caracteres/seg: {0:0.00}", "chooseColumn": "Escolha a coluna", "columnPaste": "Antes de colar coluna", + "commandPaletteTitle": "Paleta de comandos", + "commandPaletteSearchPlaceholder": "Digite para pesquisar comandos...", + "commandPaletteNoCommandsFound": "Nenhum comando correspondente", "createdEmptyTranslation": "Criada uma tradução vazia da legenda atual", "deleteText": "Excluir texto", "deleteTextAndShiftCellsUp": "Excluir texto e mover células para cima", @@ -1134,12 +1173,14 @@ "trimmedXLines": "{0} linhas de legenda cortadas", "pastedXLinesOverSelectedLines": "Coladas {0} linha(s) sobre as linhas selecionadas", "pastedXOfYLinesOverSelectedLines": "Coladas {0} de {1} linha(s) sobre as linhas selecionadas - o restante não cabia na seleção", + "insertedXLinesFromClipboard": "Inseridas {0} linha(s) da área de transferência", "openOriginalDifferentNumberOfSubtitlesXY": "O arquivo de legenda original não possui o mesmo número de legendas que o arquivo de legenda atual.\r\n\r\n• Legendas originais: {0}\r\n• Legendas atuais: {1}", "originalTextReadOnly": "Texto original (somente leitura)", "originalTextEditMode": "Texto original (modo de edição)", "originalIsReadOnlyNotSaved": "A legenda original é uma referência somente leitura e não foi salva", "originalIsEmptyNotSaved": "A legenda original não tem texto algum e não foi salva", "originalIsReadOnlyReference": "A legenda original está aberta como referência somente leitura", + "closeTranslationQuestionXY": "Fechar a tradução \"{0}\"?\r\nA legenda original \"{1}\" se torna a legenda que você edita. O arquivo de tradução em si não é alterado.", "allowEditOfOriginalSubtitle": "Permitir edição da legenda original", "showAllOriginalLinesX": "Mostrar todas as linhas originais ({0} não possuem correspondências aqui)", "showAllOriginalLinesHint": "As linhas sem correspondência são mostradas como linhas extras, para que você possa ver o que está faltando na tradução.", @@ -1174,6 +1215,7 @@ "fileExportedInFormatXToY": "Arquivo exportado no formato {0} em {1}", "fileExportedInFormatXToFileY": "Arquivo exportado no formato \"{0}\" para o arquivo \"{1}\"", "fixedXLines": "{0} linhas corrigidas", + "toolXChangedYLines": "{0}: {1} linhas alteradas", "transcriptionCompletedWithXLines": "Transcrição concluída com {0} linhas", "openSubtitleFileX": "Abrir arquivo de legenda \"{0}\"?", "replacedXOccurrences": "Substituídas {0} ocorrências", @@ -1303,7 +1345,8 @@ "forcedAlignerIsolatingSpeech": "Isolando a fala... {0}", "forcedAlignerIsolatingSpeechFailed": "Não foi possível isolar a fala - os horários de término foram definidos a partir do horário de leitura.", "forcedAlignerModel": "Modelo de alinhador", - "forcedAlignerDownloadEngine": "Baixar/atualizar mecanismo..." + "forcedAlignerDownloadEngine": "Baixar/atualizar mecanismo...", + "forcedAlignerEstimatedXOfY": "{0} de {1} linhas não puderam ser alinhadas e foram posicionadas por tempo de leitura - seus códigos de tempo são apenas estimados. O roteiro provavelmente é mais longo do que o falado no vídeo." }, "export": { "exportImagesProfiles": "Exportar perfis de imagens", @@ -1544,13 +1587,61 @@ "ignoreFormatting": "Ignorar formatação", "ignoreFormattingHint": "Linhas que diferem apenas nas tags de formatação, como ou {\\an8}, não contam como diferentes", "showOnlyDifferencesInText": "Somente diferenças no texto", + "ignoreNumbering": "Ignorar numeração", + "ignoreNumberingHint": "Linhas que diferem apenas em seu número não contam como diferentes", + "ignoreTimeDifferenceMs": "Tolerância de tempo (ms)", + "ignoreTimeDifferenceMsHint": "Os horários de início e término que diferem em tantos milissegundos ou menos não contam como diferentes", + "compareDifferences": "Diferenças", + "compareTextDifferences": "Diferenças de texto", + "compareCurrent": "Atual", + "compareReference": "Referência", + "compareEditable": "Editável", + "compareEditableHint": "As alterações feitas aqui voltam para a legenda quando você clica em Aplicar", + "compareReadOnly": "Somente leitura", + "compareTakeFromReference": "Obter texto e tempo da referência", + "compareInsertFromReference": "Inserir esta linha na legenda atual", + "compareDeleteFromCurrent": "Excluir esta linha da legenda atual", + "compareTakeText": "Obter texto", + "compareTakeTiming": "Obter tempo", + "compareEditLine": "Editar linha", + "compareEditHint": "Ctrl+Enter para salvar, Esc para cancelar", + "compareEdited": "Editado", + "compareOnePendingChange": "1 alteração pendente", + "compareXPendingChanges": "{0} alterações pendentes", + "compareDiscardXChanges": "Descartar as {0} alterações feitas em Comparar?", + "compareXChangesApplied": "Comparar: {0} alterações aplicadas", + "compareChangeTextX": "#{0} texto da referência", + "compareChangeTimingX": "#{0} tempo a partir da referência", + "compareChangeTextAndTimingX": "#{0} texto e tempo da referência", + "compareChangeEditedX": "#{0} editado", + "compareChangeInsertedX": "#{0} inserido", + "compareChangeDeletedX": "#{0} excluído", + "compareSyncPoint": "Ponto de sincronia", + "compareSyncPointHint": "Ponto de sincronia - essas duas linhas são sempre mostradas como um par, e as linhas acima e abaixo são alinhadas sozinhas", + "compareSyncPickCurrent": "Ponto de sincronia: usar esta linha atual", + "compareSyncPickReference": "Ponto de sincronia: usar esta linha de referência", + "compareSyncRemove": "Remover ponto de sincronia", + "compareClearXSyncPoints": "Limpar pontos de sincronia ({0})", + "compareSyncCurrentPickedX": "Atual #{0} escolhido como ponto de sincronia - selecione a linha de referência correspondente e clique em Sincronizar", + "compareSyncReferencePickedX": "Referência #{0} escolhida como ponto de sincronia - selecione a linha atual correspondente e clique em Sincronizar", + "compareSyncWithCurrentX": "Sincronizar com atual #{0}", + "compareSyncWithReferenceX": "Sincronizar com referência #{0}", + "compareSyncApply": "Sincronizar", + "compareSyncPointSetXY": "Conjunto de pontos de sincronia: #{0} atual e #{1} de referência agora são mostrados como um par", + "compareSyncAlreadyPairedXY": "Atual #{0} e referência #{1} já são mostradas como um par com o mesmo início - nada para sincronizar", + "compareSyncPointShiftedXYZW": "Conjunto de pontos de sincronia: atual #{0}-#{1} deslocado {2} para começar com a referência #{3}", + "compareChangeSyncShiftXYZ": "#{0}-#{1} deslocado {2} (ponto de sincronia)", + "compareTimingDeltaHint": "Quanto mais tarde (+) ou antes (-) a referência começa e termina em relação à linha atual", "loadXFromFile": "Carregar \"{0}\" do arquivo", + "compareLoadSavedFile": "Carregar arquivo salvo", "saveCompareHtmlTitle": "Salvar arquivo HTML de comparação", "pickMatroskaTrackX": "Escolher a faixa Matroska - {0}", "pickTransportStreamTrackX": "Escolha a faixa do fluxo de transporte - {0}", "chapters": "Capítulos", "dvdVobFilesMissingX": "Arquivos VOB ausentes ({0}% encontrados)", - "pickMp4TrackX": "Escolha faixa MP4 - {0}", + "pickMp4TrackX": "Escolher faixa MP4 - {0}", + "pickMxfTrackX": "Escolher faixa MXF - {0}", + "pickMpegTrackX": "Escolher faixa MPEG - {0}", "rosettaProperties": "Propriedades do Rosetta IMSC de texto cronometrado", "rosettaFontSize": "Tamanho da fonte (altura da linha)", "propertyTimeBase": "Base de tempo", @@ -1625,7 +1716,8 @@ "findWhat": "Localizar o que", "descriptionOptional": "Descrição (opcional)", "invalidRegularExpressionX": "Expressão regular inválida: {0}", - "regularExpressionTooSlowX": "A expressão regular desistiu após {0} segundos - a regra foi ignorada, tente um padrão mais simples" + "regularExpressionTooSlowX": "A expressão regular desistiu após {0} segundos - a regra foi ignorada, tente um padrão mais simples", + "moveToCategory": "Mover para categoria" }, "find": { "searchTextWatermark": "Pesquisar texto...", @@ -1705,7 +1797,22 @@ "mismatchWarning": "Não se parece com o original – pode pertencer a uma linha diferente", "engineError": "Não foi possível acessar o mecanismo de IA: {0}", "playCurrentLineHint": "Reproduzir linha selecionada no player de vídeo e pausar no final (também dê duplo clique em uma linha)", - "editAfterHint": "Clique no texto da linha selecionada para editar a correção antes de aplicá-la (Enter salva, Esc cancela)" + "editAfterHint": "Clique no texto da linha selecionada para editar a correção antes de aplicá-la (Enter salva, Esc cancela)", + "template": "Modelo", + "templatePlaceholder": "Escolha um modelo para substituir o prompt", + "templateStandard": "Revisão padrão", + "templateSpeechToText": "Limpeza de fala para texto", + "templateMachineTranslation": "Correção de tradução automática", + "templateSdh": "DSS/deficientes auditivos", + "referenceContext": "Contexto de referência", + "referenceContextInfo": "Opcional. Nomes, personagens, glossário, sinopse ou notas de estilo – o modelo trata isso como autoridade para nomes e termos. Enviado com cada lote.", + "referenceContextPlaceholder": "Nomes: Kaito, Tia Mei, Dr. Hollis\r\nTermos: Shinkansen, warp core\r\nSinopse: Kaito viaja para Tóquio para encontrar seu pai desaparecido.", + "generateContext": "Gerar com IA", + "generateContextHint": "Deixe o mecanismo de IA selecionado ler o subtítulo e os nomes dos rascunhos, os termos e uma sinopse - verifique o resultado antes de usar", + "generatingContextPartXOfY": "Lendo parte {0} de {1}...", + "generatingContextSynopsis": "Escrevendo sinopse...", + "replaceContextQuestion": "Substituir o contexto de referência atual?", + "noContextGenerated": "O mecanismo de IA não retornou nenhuma informação de referência utilizável." }, "aiAssistant": { "title": "Assistente de IA", @@ -1751,6 +1858,7 @@ "removeLineBreaksPixelWidth": "Remover quebra de legendas que podem caber em uma linha (largura de pixel)", "fixUppercaseIInsideLowercaseWords": "Corrigir 'i' maiúsculo em palavras em minúsculo (erro de OCR)", "fixDoubleApostrophes": "Substituir apóstrofe dupla ('') por aspas (\")", + "fixMisreadQuotes": "Corrigir apóstrofos mal interpretados como aspas duplas (OCR, inglês)", "addPeriods": "Adicionar ponto depois de linhas quando a linha seguinte começar com maiúscula", "startWithUppercaseLetterAfterParagraph": "Iniciar parágrafo com maiúscula", "startWithUppercaseLetterAfterPeriodInsideParagraph": "Iniciar com maiúscula depois de ponto dentro do parágrafo", @@ -1773,6 +1881,7 @@ "fixCommasExample": ",, -> ,", "removeLineBreaksExample": "Foo
bar! -> Foo bar!", "fixDoubleApostrophesExample": "''Has double single quotes'' -> \"Has single double quote\"", + "fixMisreadQuotesExample": "\"Olá' -> \"Olá\"", "addPeriodsExample": "Hello world -> Hello world.", "startWithUppercaseLetterAfterParagraphExample": "p1: Foobar! || p2: foobar! -> p1: Foobar! || p2: Foobar!", "startWithUppercaseLetterAfterPeriodInsideParagraphExample": "Hello there! how are you? -> Hello there! How are you?", @@ -1862,6 +1971,8 @@ "fixMinDurationMs": "Corrigir duração mínima (ms)", "doNotGoPastShotChange": "Não ultrapassar mudança de cena", "fixMaxDurationMs": "Corrigir duração máxima (ms)", + "fixMinDurationFrames": "Corrigir duração mínima (frames)", + "fixMaxDurationFrames": "Corrigir duração máxima (frames)", "minimumDurationMilliseconds": "Duração mínima (milissegundos):", "maximumDurationMilliseconds": "Duração máxima (milissegundos):", "maxDurationShouldBeHigherThanMinDuration": "A duração máxima deve ser superior à duração mínima", @@ -1923,6 +2034,20 @@ "isolateSpeechHint": "Remove música e efeitos sonoros antes do alinhamento, para que o alinhador ouça apenas o diálogo. Ajuda em falas com música ou ação alta, mas leva quase tanto tempo quanto o próprio áudio com uma GPU - e muitas vezes mais sem ela.", "isolatingSpeech": "Isolando a fala... (isso demora um pouco)", "isolateSpeechFailed": "Não foi possível isolar a fala. Em vez disso, alinhe-a com o áudio original.", + "checkWithSpeechToText": "Verificar com fala para texto", + "checkWithSpeechToTextHint": "Também transcreve o áudio e verifica cada movimento em relação ao local onde as palavras foram realmente ouvidas. Os movimentos grandes que a fala confirma são marcados para você, e os movimentos para longe de onde uma linha é ouvida são deixados desmarcados para você verificar. Também mostra o quanto de cada linha foi ouvida - uma participação baixa significa que o texto difere do que foi dito. Uma legenda que está mais fora de sincronia do que a mudança máxima - um deslocamento constante, um desvio de uma incompatibilidade de taxa de quadros ou saltos onde as cenas foram cortadas - é sincronizada primeiro pelas palavras ouvidas. Usa Crisp ASR Parakeet, que leva alguns minutos para um longa-metragem com GPU.", + "checkWithSpeechToTextNotAvailableX": "A verificação de fala para texto não está disponível para {0}", + "transcribingToCheck": "Transcrevendo para verificar o alinhamento...", + "speechToTextFailed": "Falha na conversão de fala em texto - os códigos de tempo não foram verificados.", + "speechToTextX": "Conversão de fala para texto: {0}", + "speechToTextConfirmedXDisputedY": "Discurso: {0} confirmado, {1} contestado", + "hardlyHeardX": "A conversão de fala em texto ouviu apenas {0}% das palavras da legenda. A legenda está no idioma falado? O alinhador não pode colocar uma translação.", + "heard": "Ouvido", + "heardHint": "Quanto da linha de fala para texto foi ouvida. Uma participação baixa significa que o texto difere do que é dito – onde o alinhador tem maior probabilidade de estar errado.", + "statusConfirmedBySpeech": "Confirmado pela fala", + "statusDisputedBySpeech": "Ouvido onde estava", + "statusMovedWithSync": "Movido com a sincronia", + "syncedFirstXY": "Sincronizado primeiro: {0} s no início, {1} s no final", "showSpeechOnly": "Mostrar apenas a fala", "showSpeechOnlyHint": "Desenhe a forma de onda alinhada do áudio com música e efeitos sonoros removidos; a forma de onda original permanece como está, para comparação. Disponível quando a fala for isolada - aqui, ou com \"Mostrar apenas a fala\" na forma de onda da janela principal.", "align": "Alinhar", @@ -1937,6 +2062,7 @@ "meanShiftX": "Mudança média: {0} ms", "toCheckX": "Para verificar: {0}", "largeMovesToCheckX": "{0} linha(s) se moveriam muito sozinhas e ficariam desmarcadas - toque Original / Alinhado e marque as que estão certas.", + "linesToCheckX": "{0} linha(s) ficam desmarcadas para verificação - ouça-as (F5 toca a linha alinhada, Shift+F5 a original) e marque as que estão certas.", "changeXOfY": "Alteração {0} de {1}", "previousChange": "Alteração anterior", "nextChange": "Próxima alteração", @@ -1949,10 +2075,12 @@ "statusNoSpeech": "Sem fala", "statusShiftTooLarge": "Mantido - deslocamento muito grande", "statusFailed": "Mantido - alinhador falhou", + "statusAdjustedByHand": "Ajustado manualmente", + "alignedWaveformHint": "Arraste uma linha para movê-la ou arraste suas bordas para alterar seu início ou fim.", + "playLine": "Reproduzir linha", + "undoAdjustment": "Desfazer ajuste", "playPause": "Reproduzir / pausar", - "playOriginal": "Original", "playAligned": "Alinhado", - "playOriginalHint": "Reproduzir linha selecionada com seus códigos de tempo originais", "playAlignedHint": "Reproduzir linha selecionada com seus códigos de tempo alinhados", "apply": "Aplicar", "applyHint": "Desmarque uma linha para manter seus códigos de tempo originais. Clique duas vezes em uma linha para reproduzi-la." @@ -2035,6 +2163,7 @@ "addColor": "Adicionar cor", "deleteLinesWithSpecificActorsOrStyles": "Excluir linhas", "useSourceStylesIfPossible": "Use estilos de origem, se possível", + "keepSourceEmbeddedFonts": "Manter fontes incorporadas de origem (rodapé)", "editStyles": "Editar estilos", "editProperties": "Editar propriedades", "editAttachments": "Editar anexos", @@ -2069,6 +2198,18 @@ "addFolderRecursiveDotDotDot": "Adicionar pasta recursiva...", "selectFolderToConvert": "Selecione a pasta com os arquivos para converter", "includeSubfolders": "Incluir subpastas ao adicionar uma pasta", + "videoFilesWhenAddingFolder": "Arquivos de vídeo (mkv, mp4, ts...) ao adicionar uma pasta", + "videoFilesAsk": "Perguntar", + "videoFilesInclude": "Adicionar suas faixas de legenda", + "videoFilesSkip": "Pular", + "folderContainsXVideoFiles": "A pasta contém {0} arquivo(s) de vídeo (mkv, mp4, ts...).\r\n\r\nAdicionar também as faixas de legendas incorporadas ou apenas os arquivos de legenda?", + "addVideoFiles": "Adicionar arquivos de vídeo", + "skipVideoFiles": "Apenas arquivos de legenda", + "doNotAskAgainVideoFiles": "Não perguntar novamente (altere nas configurações de conversão em lote)", + "settingsSectionOutput": "Saída", + "settingsSectionAddingFiles": "Adicionando arquivos", + "settingsSectionImageBasedSubtitles": "Legendas baseadas em imagens (OCR)", + "settingsSectionOther": "Outro", "keepSourceFileTimestamp": "Manter a data/hora do arquivo de origem nos arquivos de saída", "preventSleepWhileConverting": "Impedir suspender o computador durante a conversão", "scanningFolderX": "Verificando {0}...", @@ -2084,7 +2225,20 @@ "transportStreamOnlyTeletext": "Apenas teletexto", "transportStreamGetSizeFromVideo": "Obter tamanho do vídeo...", "twoLetterLanguageCodeUppercase": "Código de idioma de duas letras (maiúsculas)", - "threeLetterLanguageCodeUppercase": "Código de idioma de três letras (maiúsculas)" + "threeLetterLanguageCodeUppercase": "Código de idioma de três letras (maiúsculas)", + "formatContainsDotDotDot": "O formato contém...", + "extensionIsDotDotDot": "A extensão é...", + "statusIsError": "O status é erro", + "forcedTracksOnly": "Somente faixas forçadas", + "xConvertedYFailedInZ": "{0:#,###,##0} convertido, {1:#,###,##0} falhou em {2}", + "xNotProcessed": "{0:#,###,##0} não processado", + "removeUnicodeControlCharactersTitle": "Remover caracteres de controle Unicode", + "removeUnicodeControlCharactersInfo": "Remove os caracteres de controle de direção Unicode invisíveis (LRM, RLM, LRE, RLE, PDF, LRO e RLO) do texto e substitui espaços sem quebra por espaços normais.", + "preset": "Predefinição", + "savePresetDotDotDot": "Salvar predefinição...", + "deletePreset": "Excluir predefinição", + "presetName": "Nome da predefinição", + "deletePresetX": "Excluir predefinição \"{0}\"?" }, "changeCasing": { "fixNames": "Nome", @@ -2169,11 +2323,13 @@ }, "mergeLinesWithSameText": { "maxMsBetweenLines": "Máximo de milissegundos entre linhas", + "maxFramesBetweenLines": "Máximo de quadros entre linhas", "includeIncrementingLines": "Incluir linhas com texto incremental", "includeRollUpCaptions": "Incluir legendas acumuladas (rolagem)" }, "mergeLinesWithSameTimeCodes": { "maxMsDifference": "Diferença máxima (milissegundos)", + "maxFramesDifference": "Diferença máxima (quadros)", "makeDialog": "Criar diálogos" }, "netflixCheckAndFix": { @@ -2288,7 +2444,14 @@ "xInTopBar": "{0} na barra superior", "xInBottomBar": "{0} na barra inferior", "noImageSubtitlesLoaded": "Nenhuma legenda de imagem carregada", - "imageBasedFormatNotSupported": "Formato de legenda baseado em imagem não encontrado/suportado." + "imageBasedFormatNotSupported": "Formato de legenda baseado em imagem não encontrado/suportado.", + "frameRateDeclaredInFile": "Taxa de quadros declarada no arquivo sup do Blu-ray", + "frameRateDetectedFromTimeCodesX": "Taxa de quadros detectada nos códigos de tempo sup do Blu-ray (o arquivo declara {0})", + "frameRateFromVideo": "Taxa de quadros do vídeo", + "frameRateCurrent": "Taxa de quadros atual", + "frameRateSetManually": "Taxa de quadros definida manualmente", + "frameRateUsageInfo": "As exportações de Blu-ray sup, BDN XML, DOST e FCP são gravadas nesta taxa de quadros. Ele não muda em nenhum momento - use \"Alterar taxa de quadros\" para isso.", + "useVideoFrameRateXInsteadOfY": "A taxa de quadros do vídeo é {0}, mas a legenda está definida como {1}.\r\n\r\nUsar a taxa de quadros do vídeo ({0})?" }, "removeTextForHearingImpaired": { "interjections": "Interjeições", @@ -2318,6 +2481,7 @@ "pickFontNameInstalledFonts": "Fontes instaladas", "pickFontNameCollectedFonts": "Fontes coletadas", "colorPickerTitle": "Escolha a cor", + "colorPickerBrightness": "Brilho", "filterLayersTitle": "Filtrar camadas para exibição", "filterLayersHideFromWaveform": "Ocultar da forma de onda/espectrograma", "filterLayersHideFromSubtitleGrid": "Ocultar da grade de legendas", @@ -2368,7 +2532,21 @@ "correctWordsX": "Palavras corretas: {0}", "namesX": "Nomes: {0}", "addedToDictionaryX": "Adicionado ao dicionário: {0}", - "doNotShowThisAgain": "Não exibir novamente" + "doNotShowThisAgain": "Não exibir novamente", + "editUseAlwaysList": "Editar lista \"Usar sempre\"...", + "useAlwaysListTitle": "Verificação ortográfica - lista \"Usar sempre\"", + "useAlwaysListHeader": "Lista \"Usar sempre\"", + "useAlwaysListDescription": "A verificação ortográfica das palavras é substituída automaticamente, salva por \"Alterar tudo\" e \"Usar sempre\". Um par só é usado quando a palavra está escrita incorretamente.", + "useAlwaysWord": "Palavra com erro ortográfico", + "xPairs": "{0:#,##0} pares", + "xUnusedPairs": "{0:#,##0} nunca usado", + "useAlwaysUnusedHint": "Nunca usado - esta palavra está escrita corretamente (ou é um nome ou palavra de usuário), então a verificação ortográfica nunca a sinaliza", + "removeUnused": "Remover não usados", + "useAlwaysEmpty": "Ainda não há pares \"Usar sempre\" para este idioma", + "useAlwaysEmptyHint": "Os pares são adicionados quando você clica em \"Alterar tudo\" ou \"Usar sempre\" na verificação ortográfica.", + "useAlwaysNoMatch": "Nenhum par corresponde à pesquisa", + "useAlwaysSameWord": "A substituição deve ser diferente da palavra.", + "useAlwaysRememberOff": "\"Lembrar a lista 'Usar sempre'\" está desativado em Configurações, portanto, a verificação ortográfica não usa nem salva esta lista." }, "video": { "burnIn": { @@ -2406,6 +2584,11 @@ "translateToEnglish": "Traduzir para inglês", "transcribing": "Transcrevendo...", "transcribingXOfY": "Transcrevendo {0} de {1}...", + "linesTranscribedXOfYFailedZ": "{0} de {1} linhas foram transcritas - {2} falhou ou não há fala.\r\n\r\nAs linhas com falha mantêm o texto atual. Aplicar as linhas transcritas ou tentar novamente as que falharam (você pode alterar o mecanismo ou as configurações primeiro)?", + "linesTranscribedXOfYCancelled": "A transcrição foi cancelada após {0} de {1} linhas.\r\n\r\nAplicar as linhas transcritas? As demais linhas mantêm o texto atual.", + "noLinesTranscribed": "Nenhuma das {0} linhas pôde ser transcrita.\r\nPor favor, verifique o registro de ferramentas para obter detalhes.", + "applyTranscribedLines": "Aplicar transcritas", + "retryFailedLines": "Repetir com falhas", "isolateSpeech": "Isolar a fala (lento)", "isolateSpeechHint": "Remove música e efeitos sonoros antes de transcrever. Oferece menos linhas perdidas e melhor tempo de áudio com música alta, mas leva quase tanto tempo quanto o próprio áudio com uma GPU - e muitas vezes mais sem ela.", "isolatingSpeech": "Isolando a fala...", @@ -2413,6 +2596,9 @@ "detectSpeakers": "Detectar locutores", "detectSpeakersHint": "Inicia cada linha com o locutor, como \"(locutor 0)\", usando a diarização Sortformer da NVIDIA (até 8 locutores). Funciona melhor com backends que dividem linhas onde o locutor muda, como Parakeet e Cohere.", "detectSpeakersNeedsNewerCrispAsr": "\"Detectar locutores\" precisa do Crisp ASR v{0} ou mais recente, mas v{1} está instalado.\r\n\r\nAtualize o Crisp ASR (o botão de engrenagem próximo ao mecanismo) ou desative \"Detectar locutores\".", + "xNeedsNewerCrispAsr": "{0} precisa do Crisp ASR v{1} ou mais recente, mas v{2} está instalado.\r\n\r\nBaixar e instalar a atualização agora?", + "vad": "VAD", + "vadHint": "Detecção de atividade de voz: apenas a fala é transcrita, cortada onde o locutor faz uma pausa.\r\nAutomático: Silero, e somente para os motores que necessitam (Cohere, Mega, Index-Echo).\r\nSilero, FireRedVAD, Whisper-VAD, WebRTC: sempre ativado, para todos os mecanismos Crisp ASR. FireRedVAD é o mais preciso, Whisper-VAD oferece o menor número de cortes mais limpos, mas é o mais lento.\r\nIndex-Echo sempre usa Silero. --vad, --vad-model ou --chunk-seconds nas configurações avançadas têm precedência.", "inputLanguage": "Idioma de entrada", "advancedWhisperSettings": "Configurações avançadas do Whisper", "downloadingSpeechToTextEngine": "Baixando mecanismo de fala para texto", @@ -2507,6 +2693,8 @@ "instructions": "Instruções", "instructionsHint": "Estilo de fala opcional, por ex. “Fale com uma voz calma e calorosa”. Enviado apenas para modelos compatíveis (não tts-1/tts-1-hd)", "openAiSpeedHint": "Velocidade de fala de 0,25 a 4,0 (1,0 é normal). Nem todo modelo suporta isso", + "outputFormat": "Formato de saída", + "outputFormatHint": "Formato de áudio solicitado ao servidor. \"Automático\" pede MP3 e muda para PCM para modelos que retornam apenas PCM (por exemplo, Gemini TTS)", "regenerateAudio": "Regenerar áudio", "autoContinuePlaying": "Continuar reproduzindo automaticamente", "playLine": "Reproduzir linha", @@ -2534,6 +2722,17 @@ "voiceManagerTitle": "TTS - Gerenciador de voz", "voiceManagerDotDotDot": "Gerenciador de voz...", "voiceManagerMenuItem": "Gerenciador de voz TTS...", + "speakFromCurrentLineTitle": "Falar da linha atual", + "speakFromCurrentLineDotDotDot": "Falar da linha atual...", + "speakFromCurrentLineHint": "Lê as linhas em voz alta, uma após a outra, começando na linha atual. Pressione Esc ou clique em outra linha para parar.", + "stopSpeaking": "Parar de falar", + "speakingLineX": "Falando linha {0}... (Esc para parar)", + "playWithSpeechTitle": "Reproduzir com fala", + "playWithSpeechDotDotDot": "Reproduzir com fala...", + "playWithSpeechHint": "Reproduz o vídeo e fala cada linha quando o vídeo chega até ela. Faça uma pausa e busque como de costume; pressione Esc para parar.", + "lowerVideoVolumeWhileSpeaking": "Diminuir volume do vídeo durante fala", + "pauseVideoWhenSpeechRunsLate": "Pausar vídeo quando a fala passar para a próxima linha", + "waitingForSpeechLineX": "Gerando fala para a linha {0}... (Esc para parar)", "voiceKindClone": "Voz clonada", "voiceKindPreset": "Integrada", "voiceKindModel": "Modelo de voz", @@ -2598,14 +2797,29 @@ "advancedTtsSettings": "Configurações avançadas de TTS", "advancedTtsAudioProcessing": "Processamento de áudio", "advancedTtsOutput": "Saída", + "advancedTtsVoiceCloning": "Clonagem de voz", + "cleanCloneReferences": "Referências limpas de clones de voz (Sidon)", + "cleanCloneReferencesDescription": "Remove música, ruído e reverberação do áudio do qual uma voz é clonada: clipes retirados do vídeo e gravações importadas no gerenciador de voz. Usa o modelo de restauração de fala Sidon em audio.cpp (download de 940 MB). A voz clonada não copia mais o fundo, mas uma fala muito baixa ou sobreposta pode sair abafada.", + "cleaningCloneReferencesDotDotDot": "Limpando referências de clone de voz...", + "downloadSidonTitle": "Baixar Sídon?", + "downloadSidonQuestionX": "A limpeza de referências de clones de voz precisa do modelo de restauração de fala Sidon ({0}).\r\nBaixar agora? Sem ele, as referências são usadas como estão.", "proAudioPostProcessing": "Pós-processamento de áudio profissional", "proAudioPostProcessingDescription": "Aplica calor de equalização, gate de ruído, compressão, normalização de volume (-16 LUFS) e fade in/out a cada segmento.", + "generatingSpeechSegmentXOfY": "Gerando fala: segmento {0} de {1}", + "adjustingSpeedSegmentXOfY": "Ajustando velocidade: segmento {0} de {1}", + "mergingAudioSegmentXOfY": "Mesclando áudio: segmento {0} de {1}", + "postProcessingSegmentXOfY": "Pós-processamento: segmento {0} de {1}", "audioDucking": "Ducking de áudio", "audioDuckingDescription": "Reduz o volume do áudio do vídeo original e mistura-o com o áudio TTS, para que a faixa de áudio original ainda seja ligeiramente audível.", "removeOriginalSpeech": "Remover fala original (lento)", "removeOriginalSpeechDescription": "Mantém a música e os efeitos sonoros do vídeo original, mas remove a fala e mistura a nova fala sobre ela. Demora quase tanto quanto o próprio vídeo com uma GPU e muitas vezes mais sem ela. Com a redução de áudio ativada, seu volume se aplica à música e aos efeitos. Aplica-se apenas quando a fala é adicionada ao arquivo de vídeo.", "removingOriginalSpeech": "Removendo a fala original...", "removeOriginalSpeechFailed": "Não foi possível remover a fala original – a fala foi adicionada ao vídeo sem ele.", + "speechRemovalModel": "Modelo", + "speechRemovalModelDescription": "A melhor qualidade mantém a maior parte da música e dos efeitos sonoros. Rápido é cerca de três vezes mais rápido, com um pouco mais de música perdida.", + "speechRemovalBestQuality": "Melhor qualidade (Mel-Band RoFormer)", + "speechRemovalFast": "Rápido (HTDemucs)", + "videoTruncatedWarning": "A imagem no novo arquivo de vídeo para depois de {0}, mas o vídeo original tem {1} de comprimento - a imagem irá congelar enquanto o som continua.\r\n\r\nO arquivo foi mantido. Consulte error-log.txt na pasta de dados do Subtitle Edit para obter a saída do ffmpeg.", "originalVolumePercent": "% do volume original", "vadSilenceCompression": "Compressão de silêncio VAD", "vadSilenceCompressionDescription": "Encurta as pausas entre as palavras antes de alterar o andamento. Usa detecção de atividade de voz para compactar apenas as pausas de silêncio, mantendo a fala intacta. Este é o primeiro passo preferido – reduz a duração sem qualquer perda de qualidade.", @@ -2688,7 +2902,9 @@ "timeCodeFormatColon": "Formato do código de tempo:", "shotChangesList": "Lista de mudanças de cena", "shotChangesClearQuestion": "Tem certeza de que deseja limpar todas as mudanças de cena?", - "deleteSelectedShotChangeQuestion": "Tem certeza de que deseja excluir a mudança de cena selecionada?" + "deleteSelectedShotChangeQuestion": "Tem certeza de que deseja excluir a mudança de cena selecionada?", + "shotChanges": "Mudanças de cena", + "exportShotChanges": "Exportar mudanças de cena" }, "chapters": { "title": "Capítulos", @@ -2751,6 +2967,7 @@ "runningOcrXY": "Executando OCR... {0}/{1}", "noLinesFoundTitle": "Nenhuma legenda encontrada", "noLinesFoundMessage": "Nenhum texto foi encontrado - tente ajustar a área de análise, o mecanismo ou o brilho mínimo.", + "ocrStoppedEarlyMessage": "O mecanismo de OCR parou antes que todos os quadros fossem lidos, portanto as linhas encontradas até o momento são mantidas e o restante desaparece.{0}{0}{1}", "linesFoundX": "{0} linhas encontradas", "refiningTimingXY": "Refinando tempo... {0}/{1}", "fixOcrErrors": "Corrigir erros de OCR", @@ -2853,6 +3070,13 @@ "remuxVideoFailed": "Falha ao remuxar o vídeo.", "remuxVideoPleaseSelectBoth": "Selecione arquivos de vídeo e áudio.", "remuxVideoAssRequiresMkv": "As legendas ASS/SSA requerem o contêiner MKV para preservar toda a formatação e estilos.", + "remuxVideoSccRequiresMov": "As legendas dscenarist (.scc) são incorporadas como legendas ocultas CEA-608, que requerem o contêiner MOV ou MPG.\r\nO formato de saída foi automaticamente alterado para .mov.", + "remuxVideoSccNotWithWebM": "As legendas closed caption scenarist (.scc) precisam do contêiner MOV, que não pode conter o vídeo VP9/AV1 de um arquivo .webm.\r\nRemova o arquivo .scc ou converta-o para outro formato de legenda.", + "remuxVideoMpgMaxTwoCaptionTracks": "Um arquivo .mpg contém no máximo duas faixas de legenda oculta (CC1 e CC3) - remova os arquivos de legenda para que restem um ou dois.", + "remuxVideoMccRequiresMpg": "As legendas MacCaption (.mcc) são incorporadas como legendas ocultas CEA-608/708 no vídeo MPEG-2, o que requer o contêiner MPG.\r\nO formato de saída foi alterado automaticamente para .mpg.", + "remuxVideoMpgReencodeVideoX": "Closed captions em um arquivo .mpg precisam de vídeo MPEG-2 e o vídeo é {0}. Recodificar o vídeo para MPEG-2? Isso demora mais e diminui um pouco a qualidade.", + "remuxVideoFileHasNoAudioX": "\"{0}\" não possui faixa de áudio, portanto não pode ser usado como fonte de áudio.", + "remuxVideoAddingClosedCaptions": "Adicionando closed captions...", "remuxVideoSelectAudioTrack": "Selecione a faixa de áudio", "remuxVideoSelectAudioTrackFor": "Selecione a faixa de áudio para {0}", "remuxVideoSelectAudioTrackPrompt": "O arquivo '{0}' contém {1} faixas de áudio. Selecione a faixa de áudio para remuxar:", @@ -2865,6 +3089,7 @@ "remuxVideoMixAudio": "Mixar áudio em uma faixa", "remuxVideoVolumePercent": "% de volume:", "remuxVideoVolumeX": "% do volume {0}", + "remuxVideoFastStart": "Otimizar para streaming na web (mover o índice para frente - mais lento)", "embedSubtitlesDotDotDot": "Adicionar/remover legendas incorporadas...", "generateTimeCodes": "Gerar códigos de tempo", "checkeredImage": "Imagem quadriculada", @@ -2964,6 +3189,10 @@ "reEncodeGeneratingDone": "Geração concluída", "reEncodeGeneratedFilesX": "Arquivos gerados ({0}):", "reEncodeFfmpegParameters": "parâmetros ffmpeg", + "analyzingVideoX": "Analisando vídeo... {0}% {1}", + "analyzingVideoXofY": "Analisando vídeo {0}/{1}... {2}% {3}", + "backgroundImageFileNotSelected": "Arquivo de imagem de fundo não selecionado", + "backgroundImageFileDoesNotExistX": "O arquivo de imagem de fundo não existe: {0}", "voices": "Vozes", "presets": "Predefinições", "talkerX": "Locutor {0}", @@ -3044,7 +3273,8 @@ "syncPoints": "Pontos de sincronização", "pointSync": "Ponto de sincronização...", "pointSyncViaOther": "Pontos de sincronização via outra legenda", - "syncPointCandidateInfo": "As linhas que começam após {0}+ segundos de silêncio são destacadas. Elas geralmente formam pontos de sincronização confiáveis", + "syncPointCandidateAfterInfo": "Linhas seguidas por {0}+ segundos de silêncio são destacadas. Elas geralmente formam pontos de sincronização confiáveis", + "gapAfter": "Intervalo após", "adjustAllShortcuts": "Atalhos de teclado:\r\n\r\n• Shift + Esquerda/Direita: Mover 10 ms\r\n• Ctrl + Esquerda/Direita: Mover 100 ms\r\n• Alt + Esquerda/Direita: Mover 500 ms", "adjustAllShortcutsFrames": "Atalhos de teclado:\r\n\r\n• Shift + Esquerda/Direita: Move 1 quadro\r\n• Ctrl + Esquerda/Direita: Move 10 quadros\r\n• Alt + Esquerda/Direita: Mover 1 segundo", "offsetInSeconds": "Deslocamento em segundos", @@ -3202,9 +3432,12 @@ "newEmptyDefaultMs": "Duração padrão da nova legenda (ms)", "timeCodeUpDownStepMs": "Incremento de tempo para cima/baixo (ms)", "moveSelectedLinesStepMs": "Etapa de atalho para mover linhas selecionadas (ms)", + "moveStartEndStepMs": "Mover passo de atalho de início/fim (ms)", "moveLinesShortenNeighbor": "Mover linhas: reduza a linha anterior/seguinte em vez de sobrepô-la", "promptBeforeDelete": "Avisar antes de excluir", "rememberPositionAndSize": "Lembrar tamanho e posição da janela principal", + "titleBarFullFileName": "Mostrar caminho completo do arquivo na barra de título", + "showRecentFiles": "Mostrar arquivos recentes (para reabrir)", "openLastFileOnStart": "Abrir último arquivo recente ao iniciar", "autoSave": "Salvar automático (salvar arquivo aberto ao editar)", "autoBackupOn": "Backup automático", @@ -3231,6 +3464,8 @@ "waveformDrawGridLines": "Desenhar linhas de grade", "waveformUseSkiaRenderer": "Use renderizador rápido experimental", "waveformCenterVideoPositionAlsoWhenPaused": "Centralizar a posição do vídeo também quando pausado", + "waveformSelectCurrentSubtitleWhilePaused": "Selecionar legenda atual também durante a pausa", + "waveformRememberZoom": "Lembrar do zoom (horizontal e vertical)", "waveformShowToolbar": "Mostrar barra de ferramentas", "waveformShowToolbarEdit": "Editar barra de ferramentas...", "waveformShowToolbarEditLabel": "Barra de ferramentas forma de onda/espectrograma", @@ -3296,6 +3531,7 @@ "adjustAllTimesRememberLineSelectionChoice": "Ajuste todos os tempos, lembrar da escolha de seleção de linha", "mergeKeepEndTime": "Mesclar linhas: manter o horário de término (permitir sobreposição com a próxima legenda)", "mergeKeepEndTimeOnlyAssa": "Mesclar linhas: manter o horário de término apenas para \"Advanced Sub Station Alpha\"", + "mergeAutoBreak": "Mesclar linhas: quebra automática de texto mesclado (desativado mantém as próprias quebras de linha das linhas)", "filesAndLogs": "Arquivos e registros", "showErrorLogFile": "Mostrar arquivo de registro de erros", "showToolsLogFile": "Mostrar arquivo de registro de ferramentas", @@ -3304,6 +3540,7 @@ "showAssaLayer": "Mostrar caixa de camada ASSA", "waveformCursorColor": "Cursor da forma de onda/cor da cabeça", "waveformShotChangeColor": "Cor da forma de onda na mudança de cena", + "waveformGridColor": "Cor da grade da forma de onda", "waveformParagraphLeftColor": "Cor da borda esquerda da forma de onda", "waveformParagraphRightColor": "Cor da borda direita da forma de onda", "waveformFancyHighColor": "Cor alta da forma de onda sofisticada", @@ -3331,10 +3568,14 @@ "resetSyntaxColoring": "Redefinir coloração de sintaxe", "resetWaveform": "Redefinir forma de onda", "resetRules": "Redefinir regras", - "useFrameMode": "Usar modo de quadro (hh.mm.ss.ff)", + "timeCodeMode": "Modo de código de tempo", + "timeCodeModeTime": "Hora (hh:mm:ss,zzz)", + "timeCodeModeFrames": "Quadros (hh:mm:ss:ff)", + "timeCodeModeFrameNumbers": "Números de quadros", "textBoxLimitNewLines": "Limitar o número de linhas na caixa de texto da legenda", "mpvOpenGl": "libmpv-OpenGL", "mpvSoftwareRendering": "libmpv - Renderização de software (lenta)", + "mpvMetalRendering": "libmpv - Metal (experimental, precisa ser reiniciado)", "mpvWidRendering": "libmpv - Renderização de ID de janela nativa", "waveFormsAndSpectrogramFoldersContainsX": "As pastas \"Waveforms\" e \"spectrogram\" contêm {0}", "deleteWaveformAndSpectrogramFoldersQuestion": "Excluir arquivos \"Waveforms\" e \"Spectrogram\"?", @@ -3408,6 +3649,7 @@ "useFocusedButtonBackgroundColor": "Use a cor de fundo do botão em foco", "focusedButtonBackgroundColor": "Cor de fundo do botão em foco", "forceCrLfOnSave": "Forçar CR+LF ao salvar (arquivos de legenda de texto)", + "linuxClipboardUseExternalTool": "Copiar texto via xclip/wl-copy (mantém caracteres acentuados)", "showFormatLimitWarning": "Avisar ao salvar quando as linhas excederem os limites do formato (por exemplo, SCC 32 caracteres/linha)", "textBoxButtonShowAutoBreak": "Caixa de texto: mostrar botão de interrupção automática", "textBoxButtonShowUnbreak": "Caixa de texto: mostrar botão de desbloqueio", @@ -3420,12 +3662,17 @@ "allSettings": "Todas as configurações", "uiScale": "Escala da interface (%)", "fontScale": "Tamanho da fonte (%)", + "textSelectionOpacity": "Opacidade da seleção de texto (%)", "waveformToolbarItems": "Itens da barra de ferramentas da forma de onda", "matchIconColorToDarkTheme": "Combinar cor do ícone com a cor de primeiro plano do tema escuro", "subtitlePreviewProperties": "Propriedades de pré-visualização de legendas", "usePositionFromSubtitleFile": "Usar posição do arquivo de legenda (TTML/PAC/EBU STL)", "marginIsPartOfSubtitleArea": "A margem faz parte da área de legenda", "textJustify": "Justificar linhas", + "deinterlace": "Desentrelaçar vídeo", + "deinterlaceOff": "Não", + "deinterlaceAuto": "Automático (somente quadros entrelaçados)", + "deinterlaceAlways": "Sempre", "pixelWidthInfo": "Linhas verdes = limite de largura máxima | Área vermelha = texto excede o limite", "spellCheckEngineHunSpelll": "Hunspell", "spellCheckEngineMsWord": "Microsoft Word", @@ -3454,11 +3701,34 @@ "categoryVideo": "Vídeo", "categorySync": "Sincronização", "categoryAi": "IA", + "categoryCustom": "Personalizado", + "editCustomShortcut": "Editar atalho personalizado", + "customShortcutTextBoxKeyHint": "Use uma tecla com Ctrl/Alt aqui - uma única tecla não digita mais na caixa de texto.", + "customShortcutNumberX": "Atalho personalizado #{0}", + "customShortcutNumberXY": "Atalho personalizado nº{0}: {1}", + "customShortcutStep": "Etapa", + "customShortcutSteps": "Etapas", + "customShortcutStepRunCommand": "Executar comando", + "customShortcutStepInsertText": "Inserir texto", + "customShortcutStepReplace": "Localizar e substituir", + "customShortcutInsertAtCursor": "No cursor", + "customShortcutInsertAtStart": "No início do texto", + "customShortcutInsertAtEnd": "No final do texto", + "customShortcutSummaryRunCommandX": "Executar: {0}", + "customShortcutSummaryInsertXY": "Inserir \"{0}\" ({1})", + "customShortcutSummaryReplaceXY": "Substituir \"{0}\" por \"{1}", + "customShortcutSummaryReplaceRegexXY": "Substituir expressão regular \"{0}\" por \"{1}", + "customShortcutPickCommand": "Selecione um comando para executar.", + "customShortcutEnterText": "Digite o texto para ser inserido.", + "customShortcutEnterFind": "Digite o texto para ser localizado.", + "customShortcutInvalidRegexX": "Expressão regular inválida: {0}", + "customShortcutStepsHint": "As etapas vão de cima para baixo. As etapas de texto funcionam nas linhas selecionadas; os comandos que abrem uma janela continuam após ela ser fechada.", "activeIn": "Ativo em", "activeInEverywhere": "Em todo lugar", "generalMergeSelectedLinesAndUnbreak": "Mesclar linhas selecionadas e desfazer a quebra", "generalMergeSelectedLinesAndUnbreakCjk": "Mesclar linhas selecionadas e desquebrar CJK", "generalUnbreakNoSpaceCjk": "Remover quebra sem espaço (CJK)", + "generalToggleBreak": "Alternar quebrar/unir", "generalMergeSelectedLinesBilingual": "Mesclar linhas selecionadas bilingue", "generalToggleTranslationMode": "Alternar modo de tradução", "generalToggleTranslationAndOriginalInPreviews": "Alternar tradução e original na visualização de vídeo/áudio", @@ -3480,6 +3750,7 @@ "fileOpenKeepVideo": "Abrir (manter vídeo)", "fileSave": "Salvar", "fileSaveAs": "Salvar como", + "fileSaveOriginal": "Salvar original", "fileCompare": "Comparar", "fileStatistics": "Estatísticas", "fileImportPlainText": "Importar texto simples", @@ -3500,6 +3771,7 @@ "detectKey": "Detectar tecla", "control": "Control", "alt": "Alt.", + "altGr": "AltGr", "win": "Win", "shift": "Shift", "controlMac": "⌃", @@ -3510,6 +3782,11 @@ "resetShortcutsDetail": "Deseja redefinir todos os atalhos para os valores padrão?", "togglePlayPause": "Alternar reproduzir/pausar", "toggleLockTimeCodes": "Alternar códigos de tempo de bloqueio", + "toggleTimeCodeMode": "Alternar modo de código de tempo (tempo/quadros/números de quadros)", + "toggleScreenPrivacy": "Alternar privacidade da tela (ocultar nomes de arquivos e também textos)", + "screenPrivacyFileNamesHidden": "Privacidade da tela: nomes de arquivos ocultos", + "screenPrivacyFileNamesAndTextsHidden": "Privacidade da tela: nomes de arquivos e textos ocultos", + "screenPrivacyOff": "Privacidade da tela: desativada", "duplicateSelectedLines": "Duplicar linhas selecionadas", "sourceView": "Visualização de origem", "addOrEditBookmark": "Adicionar ou editar marcador", @@ -3558,6 +3835,7 @@ "listViewColumnTextUp": "Coluna, texto para cima", "listViewColumnTextDown": "Coluna, texto para baixo", "autoTranslateSelectedLines": "Tradução automática das linhas selecionadas...", + "autoTranslateSelectedLinesNoPrompt": "Traduzir automaticamente as linhas selecionadas (sem aviso, use o último mecanismo/idiomas)", "setAssaResolution": "Definir resolução ASSA (PlayResX/PlayResY)", "setShortcutForX": "Definir atalho para \"{0}", "commandFileNewKeepVideo": "Novo (manter vídeo)", @@ -3638,6 +3916,14 @@ "surroundWith": "Cercar com...", "surroundWithXY": "Cercar com {0}/{1}", "surroundWithNumberX": "Cercar com #{0}", + "surroundWithBehavior": "Comportamento", + "surroundWithBehaviorToggle": "Alternar (adicionar ou remover, se presente)", + "surroundWithBehaviorAdd": "Adicionar (sempre)", + "surroundWithBehaviorRemove": "Remover (todos)", + "surroundWithBehaviorRemoveOnce": "Remover (um de cada vez)", + "surroundWithWorksOn": "Funciona em", + "surroundWithScopeSelectionOrText": "Seleção, caso contrário, texto inteiro", + "surroundWithScopeEachLine": "Cada linha", "searchVia": "Pesquisar pelo", "searchViaX": "Pesquisar pelo {0}", "searchViaNumberX": "Pesquisar pelo #{0}", @@ -3665,6 +3951,11 @@ "copySubtitlePathToClipboard": "Copie o caminho da legenda para a área de transferência", "copySubtitleOriginalPathToClipboard": "Copie o caminho da legenda do original para a área de transferência", "focusTextBox": "Focar caixa de texto", + "focusSubtitleListView": "Focar visualização da lista de legendas", + "focusWaveform": "Focar forma de onda", + "focusOriginalTextBox": "Focar caixa de texto original", + "textBoxGoToStart": "Caixa de texto, ir para início", + "textBoxGoToEnd": "Caixa de texto, ir para final", "sortByNumber": "Classificar por número", "sortByStartTime": "Classificar por tempo de \"Mostrar\"", "sortByEndTime": "Classificar por tempo de \"Ocultar\"", @@ -3704,6 +3995,7 @@ "googleIt": "Pesquisar no Google (texto selecionado)", "setActorX": "Definir ator: {0}", "setNewActor": "Definir novo ator...", + "setStyleX": "Definir estilo: {0}", "toggleDialogDashes": "Alternar traços de diálogo", "mergeWithLineBeforeAsDialog": "Mesclar com linha anterior como diálogo", "mergeWithLineAfterAsDialog": "Mesclar com linha após a caixa de diálogo", @@ -3715,6 +4007,10 @@ "moveStartOneFrameForwardKeepGapPrev": "Mover início um quadro para frente (manter pausa em relação ao anterior se estiver próximo)", "moveEndOneFrameBackKeepGapNext": "Mover final um quadro para trás (manter pausa para o próximo se estiver próximo)", "moveEndOneFrameForwardKeepGapNext": "Mover final um quadro para frente (manter pausa para o próximo se estiver próximo)", + "moveStartXMsBack": "Mover início X ms para trás (X definido em Configurações, também no Visual Sync)", + "moveStartXMsForward": "Mover início X ms para frente (X definido em Configurações, também no Visual Sync)", + "moveEndXMsBack": "Mover final X ms para trás (X definido em Configurações, também no Visual Sync)", + "moveEndXMsForward": "Mover final X ms para frente (X definido em Configurações, também no Visual Sync)", "moveSelectedLinesXMsBack": "Mover as linhas selecionadas X ms para trás (X definido em Configurações)", "moveSelectedLinesXMsForward": "Mover as linhas selecionadas X ms para frente (X definido em Configurações)", "moveSelectedLinesAndForwardXMsBack": "Mover as linhas selecionadas e todos os X ms seguintes para trás (X definido em Configurações)", @@ -3869,6 +4165,8 @@ "binaryImageCompareInspectImageMatches": "\"Comparação de imagens binárias\" - inspecionar correspondências de imagens", "saveBlankTextTitle": "Salvar texto em branco?", "saveBlankTextPrompt": "Salvar texto em branco para imagem?", + "drawnLinesDoNotMatchTitle": "As linhas não correspondem à imagem", + "drawnLinesDoNotMatchPrompt": "Algumas das linhas desenhadas não correspondem à imagem (as linhas verdes devem estar no caractere, as linhas vermelhas devem estar fora dele), portanto este caractere não será reconhecido.\r\n\r\nSalvar mesmo assim?", "yesAndNeverAskAgain": "Sim e não perguntar novamente", "importTextFromSubtitleDotDotDot": "Importar texto da legenda...", "importTextFromSubtitleOverwritePrompt": "Algumas linhas já possuem texto OCR. Substituir o texto existente?", @@ -3927,9 +4225,26 @@ "crispEmbedSettingsTitle": "Configurações do CrispEmbed", "crispEmbedDescription": "Mecanismo de OCR local com backends de vários modelos - baixe o mecanismo e os modelos aqui.", "trainNOcrDatabase": "Banco de dados de treino nOCR...", + "optimizeNOcrDatabase": "Otimizar banco de dados nOCR...", + "optimizeNOcrDatabaseQuestionXY": "Remover as linhas que não diferenciam caracteres de caracteres semelhantes no banco de dados nOCR \"{0}\"?\r\n\r\nIsso torna o banco de dados menor e o OCR mais rápido, geralmente com menos erros. Leva até um minuto.\r\n\r\nUm backup é salvo como \"{1}\".", + "optimizingNOcrDatabaseXY": "Otimizando banco de dados nOCR... {0}/{1}", + "nOcrDatabaseOptimizedXYZ": "Banco de dados nOCR \"{0}\" otimizado: {1:#,##0} -> {2:#,##0} linhas.", "startTraining": "Iniciar treinamento", "abortTraining": "Parar treinamento", "trainingOptions": "Opções de treinamento", + "trainNOcrSubtitle": "Renderiza caracteres em fontes instaladas e ensina suas formas ao nOCR. Escolha as fontes que suas legendas usam.", + "xFontsSelected": "{0:#,##0} selecionado", + "subtitleFonts": "Fontes de legenda", + "emptyDatabase": "Esvaziar banco de dados", + "startFromDatabase": "Começar de", + "startFromDatabaseHint": "Adicione os caracteres treinados a uma cópia de um banco de dados existente (recomendado) ou comece a partir de um banco de dados vazio", + "fontSizes": "Tamanhos de fonte", + "fontSizesHint": "Tamanhos de fonte em pixels para renderização, separados por vírgulas, por exemplo. \"30, 40\"", + "numberOfLineSegmentsHint": "Linhas verificadas por caractere - mais é mais rigoroso, mas mais lento", + "enterDatabaseName": "Insira um nome para o banco de dados", + "trainingFontXSizeYCharacterZ": "Treinando \"{0}\" em {1} px: {2}", + "xLearned": "{0:#,##0} aprendido", + "xSkipped": "{0:#,##0} ignorado", "charactersToTrain": "Caracteres para treinar", "letterCombinationsToTrain": "Combinações de letras que podem ser divididas em uma imagem", "importCharactersFromSubtitleFile": "Importar caracteres do arquivo de legenda...", @@ -3956,6 +4271,8 @@ "drawZoomOut": "Diminuir zoom (Ctrl+-)", "drawResetView": "Redefinir visualização (Ctrl+0)", "drawToggleGrid": "Alternar grade (Ctrl+G)", + "drawTogglePreview": "Alternar pré-visualização (F9)", + "drawPreviewFailed": "Falha na pré-visualização - ffmpeg com libass (filtro de legendas) é necessário", "drawCopyToClipboard": "Copiar para a área de transferência (Ctrl+C)", "drawShapes": "Formas", "drawSelectedPoint": "Ponto selecionado", @@ -3963,6 +4280,81 @@ "drawSelectedLayer": "Camada selecionada", "drawToolX": "Ferramenta: {0}", "drawHelpText": "Clique para adicionar pontos • Enter/F8 para fechar a forma • Shift+Arrastar para deslocar • Ctrl+Scroll para ampliar", + "drawMoveToLayer": "Mover para camada", + "drawFlipHorizontal": "Virar horizontalmente", + "drawFlipVertical": "Virar verticalmente", + "drawHideShape": "Ocultar forma", + "drawShowShape": "Mostrar forma", + "drawHideLayer": "Ocultar camada", + "drawShowLayer": "Mostrar camada", + "drawDeleteLayer": "Excluir camada", + "drawDeletePoint": "Excluir ponto", + "drawCancelDrawing": "Cancelar desenho", + "drawLayerX": "Camada {0}", + "drawImportSvg": "Importar imagem SVG...", + "drawRotateClockwise": "Girar 90° no sentido horário", + "drawConvertToCurve": "Converter segmento em curva", + "drawBackground": "Fundo", + "drawPosition": "Posição", + "drawZoom": "Zoom", + "drawShapeTool": "Ferramenta de forma", + "drawColorPickerTool": "Escolha a cor de uma forma ou do fundo", + "drawShapeLibrary": "Biblioteca de formas", + "drawShapeLibraryHint": "Arraste na tela para colocar uma forma ou clique para obter o tamanho padrão. Shift mantém suas proporções.", + "drawAddToLibrary": "Adicionar à biblioteca de formas...", + "drawRemoveFromLibrary": "Remover da biblioteca de formas", + "drawShapeName": "Nome da forma", + "drawCategorySpeechBubbles": "Bolhas de fala", + "drawCategoryArrows": "Setas", + "drawCategoryBasicShapes": "Formas básicas", + "drawCategorySymbols": "Símbolos", + "drawCategoryMyShapes": "Minhas formas", + "drawShapeSpeechBubble": "Bolha de fala", + "drawShapeSpeechBubbleRight": "Balão de fala, rabicho na direita", + "drawShapeSpeechBubbleUp": "Bolha de fala, rabicho para cima", + "drawShapeOvalBubble": "Bolha de fala oval", + "drawShapeBoxBubble": "Bolha de fala de caixa", + "drawShapeThoughtBubble": "Balão de pensamento", + "drawShapeShoutBubble": "Bolha de grito", + "drawShapeCaptionBox": "Caixa de legenda", + "drawShapeArrow": "Seta", + "drawShapeDoubleArrow": "Seta dupla", + "drawShapeChevron": "Chevron", + "drawShapeCurvedArrow": "Seta curvada", + "drawShapeRoundedRectangle": "Retângulo arredondado", + "drawShapeTriangle": "Triângulo", + "drawShapeDiamond": "Diamante", + "drawShapePentagon": "Pentágono", + "drawShapeHexagon": "Hexágono", + "drawShapeOctagon": "Octógono", + "drawShapeStar": "Estrela", + "drawShapeRing": "Anel", + "drawShapePlus": "+", + "drawShapeBanner": "Banner", + "drawShapeHeart": "Coração", + "drawShapeCheck": "Marca de verificação", + "drawShapeMusicNote": "Nota musical", + "drawShapeMusicNotes": "Notas musicais", + "drawShapeLightning": "Raio", + "drawShapeCloud": "Nuvem", + "drawShapeMoon": "Lua", + "drawShapeBadge": "Selo", + "drawSize": "Tamanho", + "drawLayer": "Camada", + "drawBackgroundVideoFrame": "Quadro de vídeo (posição atual)", + "drawBackgroundVideoFrameAt": "Quadro de vídeo em...", + "drawBackgroundImage": "Arquivo de imagem...", + "drawBackgroundNone": "Sem fundo", + "drawBackgroundStretch": "Esticar imagem para tela", + "drawBackgroundOpacity": "Opacidade de fundo", + "drawBackgroundVideoFrameFailed": "Não foi possível obter um quadro de vídeo em {0}.", + "drawImages": "Imagens", + "drawConvertToLine": "Converter segmento em linha", + "drawConvertShapeToCurves": "Converter todas as linhas em curvas", + "drawConvertShapeToLines": "Converter todas as curvas em linhas", + "drawRotateCounterClockwise": "Girar 90° no sentido anti-horário", + "drawSvgImages": "Imagens SVG", + "drawSvgNoShapes": "Nenhuma forma preenchida ou traçado encontrado no arquivo SVG.", "progressBarTitle": "Barra de progresso ASSA", "progressBarSettings": "Barra de progresso", "mouseOverColor": "Cor ao passar o mouse", @@ -4059,13 +4451,26 @@ "graphics": "Gráficos", "copyToStorageStyles": "Copiar para estilos de armazenamento", "copyToFileStyles": "Copiar para estilos de arquivo", + "styleXAlreadyExistsInStorage": "Um estilo chamado \"{0}\" já existe no armazenamento.\r\n\r\nSubstituí-lo ou manter ambos (a cópia ganha um novo nome)?", + "styleXAlreadyExistsInFile": "Um estilo chamado \"{0}\" já existe no arquivo.\r\n\r\nSubstituí-lo ou manter ambos (a cópia ganha um novo nome)?", + "styleNameCannotBeEmpty": "Um nome de estilo não pode estar vazio.", + "styleNameXIsUsedMoreThanOnce": "O nome do estilo \"{0}\" é usado por mais de um estilo no arquivo.", + "overwrite": "Substituir", + "keepBoth": "Manter ambos", + "doThisForAllConflictingStyles": "Fazer para todos os estilos com conflito", "setStyleAsDefault": "Definir estilo como padrão", "takeUsagesFromDotDotDot": "Faça uso de ...", "replaceStyleWithDotDotDot": "Substituir estilo por...", "noAttachmentsFound": "Nenhum anexo encontrado no arquivo ASSA selecionado.", "deleteStyleQuestion": "Excluir estilo?", "deleteStylesQuestion": "Excluir estilos?", + "deleteXStylesQuestion": "Deseja excluir {0} estilos?", + "deleteXStylesFromStorageQuestion": "Deseja excluir {0} estilos do armazenamento?", + "deleteStyleXFromStorageQuestion": "Deseja excluir o estilo \"{0}\" do armazenamento?", + "deleteStyleXFromFileQuestion": "Deseja excluir o estilo \"{0}\" do arquivo atual?", "openStyleImportFile": "Abra o arquivo de legenda para importar estilos de", + "styleImportFiles": "Arquivos de estilo (.ass, .sty, .template de categorias do Subtitle Edit 4)", + "ssaStyleImportFiles": "Arquivos de estilo (.ssa, .ass, .sty)", "primary": "Primário", "secondary": "Secundário", "applyOverrideTags": "Aplicar tags de substituição", From 9ae5dc874d02e769a19a413248c1da46eb8ab4d1 Mon Sep 17 00:00:00 2001 From: niksedk Date: Tue, 13 Oct 2026 22:15:38 +0200 Subject: [PATCH 115/306] Frame mode: option to snap times to video frames (#16007) In hh:mm:ss:ff mode a time is whole seconds plus the frame within the second, which at 23.976/29.97 drifts up to a frame from the video: 00:01:58:22 is stored as 118918 ms, while the video frame - and the Blu-ray sup PTS since #15478 - is at 118910 ms. An .srt checked in a player then no longer matches the exported sup. New setting (off by default) under Options > General, enabled for the "Frames (hh:mm:ss:ff)" time code mode: re-timed lines and typed time codes go to the video frame with that time code. Times already on a video frame stay put. Co-Authored-By: Claude Opus 5.5 --- src/ui/Controls/TimeCodeUpDown.cs | 4 +- .../Features/Options/Settings/SettingsPage.cs | 2 + .../Options/Settings/SettingsViewModel.cs | 10 +++- .../Language/Options/LanguageSettings.cs | 2 + src/ui/Logic/Config/SeGeneral.cs | 8 +++ src/ui/Logic/FrameModeTimeSnapper.cs | 46 +++++++++++++++- tests/UI/Logic/FrameModeTimeSnapperTests.cs | 53 +++++++++++++++++++ 7 files changed, 121 insertions(+), 4 deletions(-) diff --git a/src/ui/Controls/TimeCodeUpDown.cs b/src/ui/Controls/TimeCodeUpDown.cs index 799a4986ae5..3a3a8d3eb07 100644 --- a/src/ui/Controls/TimeCodeUpDown.cs +++ b/src/ui/Controls/TimeCodeUpDown.cs @@ -540,7 +540,7 @@ private bool TryParsePastedValue(string? clipboardText, out TimeSpan value) try { ms = useFrameMode - ? TimeCode.ParseHHMMSSFFToMilliseconds(text) + ? FrameModeTimeSnapper.ToVideoFrame(TimeSpan.FromMilliseconds(TimeCode.ParseHHMMSSFFToMilliseconds(text))).TotalMilliseconds : TimeCode.ParseToMilliseconds(text); } catch (Exception exception) when (exception is OverflowException or ArgumentOutOfRangeException) @@ -585,7 +585,7 @@ private static TimeSpan ParseUnsignedTime(string text) int.TryParse(frameParts[3], out var frames)) { var frameMs = SubtitleFormat.FramesToMillisecondsMax999(frames); - return new TimeSpan(0, frameHours, frameMinutes, frameSeconds, frameMs); + return FrameModeTimeSnapper.ToVideoFrame(new TimeSpan(0, frameHours, frameMinutes, frameSeconds, frameMs)); } return TimeSpan.Zero; diff --git a/src/ui/Features/Options/Settings/SettingsPage.cs b/src/ui/Features/Options/Settings/SettingsPage.cs index 76e962f1156..dfe0eb697e9 100644 --- a/src/ui/Features/Options/Settings/SettingsPage.cs +++ b/src/ui/Features/Options/Settings/SettingsPage.cs @@ -435,6 +435,8 @@ private List CreateSections() MakeCheckboxSetting(Se.Language.Options.Settings.MoveLinesShortenNeighbor, nameof(_vm.MoveLinesShortenNeighbor)), MakeCheckboxSetting(Se.Language.Options.Settings.PromptBeforeDelete, nameof(_vm.PromptBeforeDelete)), new SettingsItem(Se.Language.Options.Settings.TimeCodeMode, () => MakeComboBoxTimeCodeMode()), + MakeCheckboxSetting(Se.Language.Options.Settings.FrameModeSnapToVideoFrames, nameof(_vm.FrameModeSnapToVideoFrames), + new Binding(nameof(_vm.IsFramesTimeCodeMode)) { Source = _vm }), MakeCheckboxSetting(Se.Language.Options.Settings.TextBoxLimitNewLines, nameof(_vm.TextBoxLimitNewLines)), MakeCheckboxSetting(Se.Language.General.LockTimeCodes, nameof(_vm.LockTimeCodes)), MakeCheckboxSetting(Se.Language.Options.Settings.RememberPositionAndSize, nameof(_vm.RememberPositionAndSize)), diff --git a/src/ui/Features/Options/Settings/SettingsViewModel.cs b/src/ui/Features/Options/Settings/SettingsViewModel.cs index 208751dd0f0..9387eef51a4 100644 --- a/src/ui/Features/Options/Settings/SettingsViewModel.cs +++ b/src/ui/Features/Options/Settings/SettingsViewModel.cs @@ -140,7 +140,13 @@ partial void OnMaxLinesChanged(int? value) private bool _useFrameMode; [ObservableProperty] private ObservableCollection _timeCodeModes; - [ObservableProperty] private TimeCodeModeDisplay _selectedTimeCodeMode; + [ObservableProperty] + [NotifyPropertyChangedFor(nameof(IsFramesTimeCodeMode))] + private TimeCodeModeDisplay _selectedTimeCodeMode; + + [ObservableProperty] private bool _frameModeSnapToVideoFrames; + + public bool IsFramesTimeCodeMode => SelectedTimeCodeMode?.Mode == TimeCodeMode.Frames; // Frames and frame numbers are both frame mode (frame stepping, snapping, min gap in frames); // frame numbers only changes how a time code is shown and typed. @@ -829,6 +835,7 @@ private void LoadSettings() ? TimeCodeMode.Time : general.UseFrameNumbersPersisted ? TimeCodeMode.FrameNumbers : TimeCodeMode.Frames; SelectedTimeCodeMode = TimeCodeModes.First(p => p.Mode == timeCodeMode); + FrameModeSnapToVideoFrames = general.FrameModeSnapToVideoFrames; TextBoxLimitNewLines = general.SubtitleTextBoxLimitNewLines; NewEmptyDefaultMs = general.NewEmptyDefaultMs; TimeCodeUpDownStepMs = general.TimeCodeUpDownStepMs; @@ -1743,6 +1750,7 @@ private void SaveSettings() general.UseFrameMode = UseFrameMode; general.UseFrameNumbersPersisted = SelectedTimeCodeMode.Mode == TimeCodeMode.FrameNumbers; + general.FrameModeSnapToVideoFrames = FrameModeSnapToVideoFrames; general.SubtitleTextBoxLimitNewLines = TextBoxLimitNewLines; general.NewEmptyDefaultMs = NewEmptyDefaultMs ?? general.NewEmptyDefaultMs; general.TimeCodeUpDownStepMs = TimeCodeUpDownStepMs ?? general.TimeCodeUpDownStepMs; diff --git a/src/ui/Logic/Config/Language/Options/LanguageSettings.cs b/src/ui/Logic/Config/Language/Options/LanguageSettings.cs index a7c0bbc1ab1..e9285022254 100644 --- a/src/ui/Logic/Config/Language/Options/LanguageSettings.cs +++ b/src/ui/Logic/Config/Language/Options/LanguageSettings.cs @@ -248,6 +248,7 @@ public class LanguageSettings public string TimeCodeModeTime { get; set; } public string TimeCodeModeFrames { get; set; } public string TimeCodeModeFrameNumbers { get; set; } + public string FrameModeSnapToVideoFrames { get; set; } public string TextBoxLimitNewLines { get; set; } public string MpvOpenGl { get; set; } public string MpvSoftwareRendering { get; set; } @@ -611,6 +612,7 @@ public LanguageSettings() TimeCodeModeTime = "Time (hh:mm:ss,zzz)"; TimeCodeModeFrames = "Frames (hh:mm:ss:ff)"; TimeCodeModeFrameNumbers = "Frame numbers"; + FrameModeSnapToVideoFrames = "Frames: snap times to video frames (as Blu-ray sup)"; TextBoxLimitNewLines = "Limit number of lines in subtitle text box"; MpvOpenGl = "libmpv - OpenGL"; MpvWidRendering = "libmpv - Native Window ID rendering"; diff --git a/src/ui/Logic/Config/SeGeneral.cs b/src/ui/Logic/Config/SeGeneral.cs index 73b2aceff9a..0420616cdfe 100644 --- a/src/ui/Logic/Config/SeGeneral.cs +++ b/src/ui/Logic/Config/SeGeneral.cs @@ -70,6 +70,14 @@ public bool UseFrameMode [JsonIgnore] public bool UseFrameNumbers => UseFrameMode && UseFrameNumbersPersisted; + /// + /// In hh:mm:ss:ff frame mode, store a time as the video frame's own time instead of whole + /// seconds plus the frame within the second (#16007). At 23.976/29.97 the hh:mm:ss:ff count + /// restarts every second and drifts up to a frame from the video, so 00:01:58:22 is 118918 ms + /// while the video frame - and the Blu-ray sup PTS - is at 118910 ms. + /// + public bool FrameModeSnapToVideoFrames { get; set; } + public double DefaultFrameRate { get; set; } public double CurrentFrameRate { get; set; } public string DefaultSubtitleFormat { get; set; } diff --git a/src/ui/Logic/FrameModeTimeSnapper.cs b/src/ui/Logic/FrameModeTimeSnapper.cs index 69e2f7d53b4..28c680ab12a 100644 --- a/src/ui/Logic/FrameModeTimeSnapper.cs +++ b/src/ui/Logic/FrameModeTimeSnapper.cs @@ -33,8 +33,52 @@ public static TimeSpan Snap(TimeSpan time) return FrameNumbers.Snap(time); } + // Already on a video frame (the video position, a frame step): keep it - near the edge + // of a time code frame two video frames can show the same hh:mm:ss:ff. + if (IsSnappingToVideoFrames() && FrameNumbers.Snap(time) == time) + { + return time; + } + var timeCode = new TimeCode(time.TotalMilliseconds); - return TimeSpan.FromMilliseconds(TimeCode.ParseHHMMSSFFToMilliseconds(timeCode.ToHHMMSSFF())); + return ToVideoFrame(TimeSpan.FromMilliseconds(TimeCode.ParseHHMMSSFFToMilliseconds(timeCode.ToHHMMSSFF()))); + } + + /// + /// With "snap times to video frames" on, moves a time on the hh:mm:ss:ff grid (whole seconds + /// plus the frame within the second) to the video frame nearest to it (#16007), the frame a + /// Blu-ray sup export puts it on. Every video frame keeps its own hh:mm:ss:ff, so typing the + /// displayed time code back in lands on the same frame. Otherwise the time is returned as is. + /// + public static TimeSpan ToVideoFrame(TimeSpan time) + { + if (!IsSnappingToVideoFrames() || time < TimeSpan.Zero || time.IsMaxTime()) + { + return time; + } + + // The nearest video frame, unless that shows as another hh:mm:ss:ff - the time code's + // whole milliseconds can sit right between two frames, and the neighbour is the one with + // this time code. + var timeCode = new TimeCode(time.TotalMilliseconds).ToHHMMSSFF(); + var nearest = FrameNumbers.FromMilliseconds(time.TotalMilliseconds); + foreach (var frame in new[] { nearest, nearest - 1, nearest + 1 }) + { + var frameMs = FrameNumbers.ToMilliseconds(frame); + if (frame >= 0 && new TimeCode(frameMs).ToHHMMSSFF() == timeCode) + { + return TimeSpan.FromMilliseconds(frameMs); + } + } + + return TimeSpan.FromMilliseconds(FrameNumbers.ToMilliseconds(nearest)); + } + + private static bool IsSnappingToVideoFrames() + { + var general = Config.Se.Settings.General; + return general.FrameModeSnapToVideoFrames && !general.UseFrameNumbers && + Configuration.Settings.General.CurrentFrameRate >= 1; } /// diff --git a/tests/UI/Logic/FrameModeTimeSnapperTests.cs b/tests/UI/Logic/FrameModeTimeSnapperTests.cs index d167b52188a..584b7ccb411 100644 --- a/tests/UI/Logic/FrameModeTimeSnapperTests.cs +++ b/tests/UI/Logic/FrameModeTimeSnapperTests.cs @@ -1,6 +1,7 @@ using Nikse.SubtitleEdit.Core.Common; using Nikse.SubtitleEdit.Features.Main; using Nikse.SubtitleEdit.Logic; +using Nikse.SubtitleEdit.Logic.Config; namespace UITests.Logic; @@ -11,10 +12,12 @@ namespace UITests.Logic; public class FrameModeTimeSnapperTests : IDisposable { private readonly double _currentFrameRate = Configuration.Settings.General.CurrentFrameRate; + private readonly bool _snapToVideoFrames = Se.Settings.General.FrameModeSnapToVideoFrames; public void Dispose() { Configuration.Settings.General.CurrentFrameRate = _currentFrameRate; + Se.Settings.General.FrameModeSnapToVideoFrames = _snapToVideoFrames; } private static SubtitleLineViewModel MakeLine(double startMs, double endMs) => @@ -100,4 +103,54 @@ public void SnapChangedLines_SkipsReferenceOnlyRows() Assert.Equal(0, FrameModeTimeSnapper.SnapChangedLines(new[] { line }, Array.Empty())); Assert.Equal(1013, line.StartTime.TotalMilliseconds); } + + [Theory] + [InlineData(23.976, 118918, 118910)] // 00:01:58:22 - the frame the Blu-ray sup export uses (#16007) + [InlineData(23.976, 126334, 126335)] // 00:02:06:08 + [InlineData(23.976, 118910, 118910)] + [InlineData(25, 1013, 1000)] + public void Snap_ToVideoFrames_LandsOnTheVideoFrame(double frameRate, double ms, double expectedMs) + { + Configuration.Settings.General.CurrentFrameRate = frameRate; + Se.Settings.General.FrameModeSnapToVideoFrames = true; + + Assert.Equal(expectedMs, FrameModeTimeSnapper.Snap(TimeSpan.FromMilliseconds(ms)).TotalMilliseconds); + } + + [Theory] + [InlineData(23.976)] + [InlineData(29.97)] + [InlineData(59.94)] + [InlineData(25)] + public void Snap_ToVideoFrames_KeepsVideoFramesAndTimeCodes(double frameRate) + { + Configuration.Settings.General.CurrentFrameRate = frameRate; + Se.Settings.General.FrameModeSnapToVideoFrames = true; + + // Three hours, so the drift between the time code and the video frames goes round many times. + var lastFrame = FrameNumbers.FromMilliseconds(3 * 3600 * 1000); + for (var frame = 0; frame <= lastFrame; frame += 7) + { + var frameTime = TimeSpan.FromMilliseconds(FrameNumbers.ToMilliseconds(frame)); + var snapped = FrameModeTimeSnapper.Snap(frameTime); + + Assert.Equal(frameTime, snapped); + + // Typing the shown time code back in lands on a video frame with that time code (two + // video frames can share one, as times are whole milliseconds). + var timeCode = new TimeCode(frameTime.TotalMilliseconds).ToHHMMSSFF(); + var typed = FrameModeTimeSnapper.Snap(TimeSpan.FromMilliseconds(TimeCode.ParseHHMMSSFFToMilliseconds(timeCode))); + Assert.Equal(typed, FrameNumbers.Snap(typed)); + Assert.Equal(timeCode, new TimeCode(typed.TotalMilliseconds).ToHHMMSSFF()); + } + } + + [Fact] + public void Snap_ToVideoFramesOff_KeepsTheTimeCodeGrid() + { + Configuration.Settings.General.CurrentFrameRate = 23.976; + Se.Settings.General.FrameModeSnapToVideoFrames = false; + + Assert.Equal(118918, FrameModeTimeSnapper.Snap(TimeSpan.FromMilliseconds(118918)).TotalMilliseconds); + } } From a91de366b289813ee7625cde11d0a41361181d0d Mon Sep 17 00:00:00 2001 From: niksedk Date: Tue, 13 Oct 2026 22:22:25 +0200 Subject: [PATCH 116/306] Frame mode: clearer label for the video frame option Co-Authored-By: Claude Opus 5.5 --- src/ui/Logic/Config/Language/Options/LanguageSettings.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ui/Logic/Config/Language/Options/LanguageSettings.cs b/src/ui/Logic/Config/Language/Options/LanguageSettings.cs index e9285022254..fadc7cd5912 100644 --- a/src/ui/Logic/Config/Language/Options/LanguageSettings.cs +++ b/src/ui/Logic/Config/Language/Options/LanguageSettings.cs @@ -612,7 +612,7 @@ public LanguageSettings() TimeCodeModeTime = "Time (hh:mm:ss,zzz)"; TimeCodeModeFrames = "Frames (hh:mm:ss:ff)"; TimeCodeModeFrameNumbers = "Frame numbers"; - FrameModeSnapToVideoFrames = "Frames: snap times to video frames (as Blu-ray sup)"; + FrameModeSnapToVideoFrames = "Store times on video frames (not frames within each second)"; TextBoxLimitNewLines = "Limit number of lines in subtitle text box"; MpvOpenGl = "libmpv - OpenGL"; MpvWidRendering = "libmpv - Native Window ID rendering"; From 0cf059eb5f82f41d0c9f9e7b98e30ebc64d5f67c Mon Sep 17 00:00:00 2001 From: niksedk Date: Tue, 13 Oct 2026 22:29:07 +0200 Subject: [PATCH 117/306] Docs: explain storing frame-mode times on video frames (#16007) Co-Authored-By: Claude Opus 5.5 --- docs/features/settings.md | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/docs/features/settings.md b/docs/features/settings.md index 286472ea84f..815c3dd652c 100644 --- a/docs/features/settings.md +++ b/docs/features/settings.md @@ -40,6 +40,7 @@ The subtitle rules that drive error checking, the grid's warning colors, and too - **Show full file path in title bar** — Show the subtitle file's full path in the main window title instead of only its name. Off by default - **Show recent files (for reopen)** — Turn off to stop recording recent files; this also hides the Reopen menus and clears the lists - **Time code mode** — How times are shown and typed: *Time (hh:mm:ss,zzz)* with milliseconds, *Frames (hh:mm:ss:ff)*, or *Frame numbers* - the absolute frame number at the current frame rate (e.g. 15230), also for durations and the video position, so a sync offset is just "video frame - start frame". Both frame modes snap edited times to frames. The mode is also shown in the status bar - click it (or use the "Toggle time code mode" shortcut) to cycle through the three. +- **Store times on video frames (not frames within each second)** — Only for *Frames (hh:mm:ss:ff)*, off by default. Stores each time at the exact time of a real video frame, so the milliseconds in a saved `.srt` match the video and a Blu-ray sup export. See [Frames and video frames](#frames-and-video-frames) below. - **Limit number of lines in subtitle text box** - **Open last recent file on start** - **Auto-convert encoding to UTF-8 on open**, **Force CR+LF on save (text subtitle files)** (also used by [Batch convert](batch-convert.md)), **Auto-trim white-space** @@ -54,6 +55,27 @@ The subtitle rules that drive error checking, the grid's warning colors, and too - **Auto-backup** — Automatic backups every **Auto-backup interval (minutes)**, with a restore dialog, and **Auto-backup retention (days)** for how long they are kept - **Auto-backup settings** — Keeps dated copies of `Settings.json` (settings and shortcuts). **Settings backup interval (days)** sets how often: the check runs when Subtitle Edit starts, and `0` means every start. A backup is skipped when nothing but the recent files lists has changed since the newest backup. **Settings backups to keep** sets how many copies are kept (default 30). Restore them from File → Restore auto-backup → Settings +### Frames and video frames + +At 25, 24, 30 or 50 fps a second holds a whole number of frames, so there is nothing to choose here. At **23.976, 29.97 and 59.94 fps** it does not, and the two ways of counting frames end up slightly different. + +- **The video** counts its frames from the start without stopping: frame 0, 1, 2, ... At 23.976 fps each frame is about 41.708 ms, and every frame has one exact time. +- **hh:mm:ss:ff** shows whole seconds plus the frame within that second (0-23). Since a real second holds a little fewer than 24 frames, this count slowly slides away from the video's frames, up to one frame, and then lines up again. + +By default, a time typed or snapped in frame mode is stored on the hh:mm:ss:ff count: `00:01:58:22` is stored as 118 s + 22 frames = **00:01:58,918**. The video frame shown at that point starts at **00:01:58,910**. Turn on **Store times on video frames (not frames within each second)** and `00:01:58:22` is stored as **00:01:58,910**. The display does not change, only the stored milliseconds do. + +Turn it on when the milliseconds have to match the video exactly, for example: + +- You check an `.srt` in a player (MPC-HC, mpv, ...) and then export a **Blu-ray sup**. The sup export always puts times on video frames, so with this option the `.srt` and the `.sup` show each subtitle on the same frame. +- Another tool reads the times as real video time. + +Good to know: + +- Only lines you add or re-time are stored this way. Existing lines keep their times; use **Tools → Snap all times to frames** to put them all on video frames. +- Because times are whole milliseconds, two neighbouring video frames can now and then show the same hh:mm:ss:ff. Typing that time code picks the nearer one; step the frames with the up/down arrows of the time box to reach the other. +- With **SMPTE timing** on (Video → More), frames are exactly 1/24 (1/30, 1/60) s each, so both counts are the same and this option makes no difference. SMPTE timing is a different idea: times follow the broadcast time code clock instead of real video time. +- *Frame numbers* mode always uses video frames. + ## Subtitle Formats - **Default format** and **default save-as format** From 87db7c2a76a8e3f5fcceeb2e09a3c7c4b2444894 Mon Sep 17 00:00:00 2001 From: niksedk Date: Tue, 13 Oct 2026 22:50:50 +0200 Subject: [PATCH 118/306] ASSA draw: outline export, text tool, text in speech bubbles - Outline width/color (layer for drawings, own for text boxes) is now written as \bord/\3c on the drawing line and read back; shown on canvas. - Text tool (T): drag or click a text box; written as a centered \q2 line with its own AssaDrawText style. - Any shape can hold text; library bubbles have a text area on their body. Shrink to fit + word wrap measured like libass; soft \n for wraps, \N for typed breaks. A {assadraw-text ...} comment keeps the text area so the dialog reattaches the text to its bubble when opened again. Co-Authored-By: Claude Opus 5.5 --- .../Features/Assa/AssaDraw/AssaDrawCanvas.cs | 133 ++++- .../Assa/AssaDraw/AssaDrawTextLine.cs | 228 ++++++++ .../Assa/AssaDraw/AssaDrawViewModel.cs | 509 ++++++++++++++++-- .../Features/Assa/AssaDraw/AssaDrawWindow.cs | 136 ++++- .../Features/Assa/AssaDraw/AssaTextLayout.cs | 170 ++++++ src/ui/Features/Assa/AssaDraw/DrawShape.cs | 106 +++- src/ui/Features/Assa/AssaDraw/DrawingTool.cs | 3 + src/ui/Features/Assa/AssaDraw/ShapeLibrary.cs | 46 +- src/ui/Features/Main/MainViewModel.cs | 7 +- .../Config/Language/Assa/LanguageAssa.cs | 10 + tests/UI/Features/Assa/AssaDrawTextTests.cs | 271 ++++++++++ 11 files changed, 1520 insertions(+), 99 deletions(-) create mode 100644 src/ui/Features/Assa/AssaDraw/AssaDrawTextLine.cs create mode 100644 src/ui/Features/Assa/AssaDraw/AssaTextLayout.cs create mode 100644 tests/UI/Features/Assa/AssaDrawTextTests.cs diff --git a/src/ui/Features/Assa/AssaDraw/AssaDrawCanvas.cs b/src/ui/Features/Assa/AssaDraw/AssaDrawCanvas.cs index 9d09ce27a41..870a199f615 100644 --- a/src/ui/Features/Assa/AssaDraw/AssaDrawCanvas.cs +++ b/src/ui/Features/Assa/AssaDraw/AssaDrawCanvas.cs @@ -38,6 +38,7 @@ public class AssaDrawCanvas : Control private List? _transformShapes; private List<(float X, float Y)[]>? _transformOriginal; private bool[]? _transformReversed; + private (float Left, float Top, float Right, float Bottom)[]? _transformTextAreas; private (float Left, float Top, float Right, float Bottom) _transformBounds; private Point _transformStart; private bool _transformStarted; @@ -498,8 +499,15 @@ public override void Render(DrawingContext context) } } + // Text tool: the dragged text box + if (_insertStart != null && CurrentTool == DrawingTool.Text && GetInsertRect() is { IsClick: false } textInsert) + { + context.DrawRectangle(GetBrush(Color.FromArgb(40, AccentColor.R, AccentColor.G, AccentColor.B)), GetPen(AccentColor, 1.5, dashed: true), + new Rect(ToZoomFactorX(textInsert.X), ToZoomFactorY(textInsert.Y), textInsert.Width * _zoomFactor, textInsert.Height * _zoomFactor)); + } + // Shape tool: outline of the library shape in the dragged rectangle - if (_insertStart != null && InsertShape != null && GetInsertRect() is { IsClick: false } insert) + if (_insertStart != null && CurrentTool == DrawingTool.Shape && InsertShape != null && GetInsertRect() is { IsClick: false } insert) { var previewPen = GetPen(AccentColor, 1.5, lineJoin: PenLineJoin.Round, dashed: true); foreach (var shape in InsertShape.CreateShapes(insert.X, insert.Y, insert.Width, insert.Height, Colors.White, 0)) @@ -608,6 +616,22 @@ private void DrawShape(DrawingContext context, DrawShape shape, bool isActive, b var isClosed = shape.Points.Count > 2 && isInShapes; var geometry = BuildGeometry(shape, isClosed); + if (shape.IsTextBox) + { + // Nothing is drawn for a text box - just a dashed frame to grab it by + var framePen = GetPen(isActive || isSelected ? AccentColor : Color.FromArgb(150, 200, 200, 200), isActive || isSelected ? 1.5 : 1, dashed: true); + context.DrawGeometry(null, framePen, BuildGeometry(shape, true)); + return; + } + + if (isActive && isInShapes && shape.HasText && shape.Points.Count > 2) + { + // Where the text of a bubble goes + var (textLeft, textTop, textRight, textBottom) = shape.GetTextBounds(); + context.DrawRectangle(null, GetPen(GuideColor, 1, dashed: true), + new Rect(new Point(ToZoomFactorX(textLeft), ToZoomFactorY(textTop)), new Point(ToZoomFactorX(textRight), ToZoomFactorY(textBottom)))); + } + IBrush? fillBrush = null; if (fill && isClosed && !shape.IsEraser) { @@ -647,28 +671,85 @@ private void DrawShape(DrawingContext context, DrawShape shape, bool isActive, b /// /// Fills each layer as one geometry, like libass renders a layer's line: the shapes' contours /// combine with non-zero winding (so holes of imported SVGs stay open) and the layer's eraser - /// shapes (\iclip) are cut out of it. + /// shapes (\iclip) are cut out of it. The layer's outline goes under the fill (libass draws + /// \bord outside the shape), and the layer's text on top of it. /// private void DrawLayerFills(DrawingContext context, List shapes) { foreach (var layer in shapes.Where(s => !s.Hidden && s.Points.Count > 2).GroupBy(s => s.Layer).OrderBy(g => g.Key)) { - var drawShapes = layer.Where(s => !s.IsEraser).ToList(); - if (drawShapes.Count == 0) + var drawShapes = layer.Where(s => !s.IsEraser && !s.IsTextBox).ToList(); + if (drawShapes.Count > 0) + { + Geometry geometry = BuildLayerGeometry(drawShapes); + var eraserShapes = layer.Where(s => s.IsEraser).ToList(); + if (eraserShapes.Count > 0) + { + geometry = new CombinedGeometry(GeometryCombineMode.Exclude, geometry, BuildLayerGeometry(eraserShapes)); + } + + var styleShape = drawShapes[0]; + if (styleShape.OutlineWidth > 0) + { + var outline = styleShape.OutlineColor; + var outlineAlpha = (byte)Math.Round(outline.A * 215 / 255.0); + context.DrawGeometry(null, GetPen(Color.FromArgb(outlineAlpha, outline.R, outline.G, outline.B), + styleShape.OutlineWidth * 2 * _zoomFactor, lineJoin: PenLineJoin.Round), geometry); + } + + var color = styleShape.ForeColor; + var alpha = (byte)Math.Round(color.A * 215 / 255.0); + context.DrawGeometry(GetBrush(Color.FromArgb(alpha, color.R, color.G, color.B)), null, geometry); + } + + foreach (var shape in layer.Where(s => s.HasText)) + { + DrawShapeText(context, shape); + } + } + } + + /// + /// The shape's text, wrapped and sized like the generated line (the F9 preview shows the real libass render). + /// + private void DrawShapeText(DrawingContext context, DrawShape shape) + { + var (left, top, right, bottom) = shape.GetTextBounds(); + if (right - left < 1 || bottom - top < 1) + { + return; + } + + var layout = AssaTextLayout.Layout(shape.Text, shape.FontName, shape.FontSize, shape.FontBold, right - left, bottom - top, shape.ShrinkToFit); + var typeface = new Typeface(new FontFamily(AssaTextLayout.GetSkiaFontName(shape.FontName)), FontStyle.Normal, shape.FontBold ? FontWeight.Bold : FontWeight.Normal); + var emSize = layout.EmSize * _zoomFactor; + if (emSize < 1) + { + return; + } + + var lineHeight = layout.FontSize * _zoomFactor; + var centerX = ToZoomFactorX((left + right) / 2); + var blockTop = ToZoomFactorY((top + bottom) / 2) - lineHeight * layout.Lines.Count / 2; + var brush = GetBrush(shape.TextColor); + var outlinePen = shape.IsTextBox && shape.OutlineWidth > 0 + ? GetPen(shape.OutlineColor, shape.OutlineWidth * 2 * _zoomFactor, lineJoin: PenLineJoin.Round) + : null; + for (var i = 0; i < layout.Lines.Count; i++) + { + if (layout.Lines[i].Length == 0) { continue; } - Geometry geometry = BuildLayerGeometry(drawShapes); - var eraserShapes = layer.Where(s => s.IsEraser).ToList(); - if (eraserShapes.Count > 0) + var text = new FormattedText(layout.Lines[i], System.Globalization.CultureInfo.CurrentCulture, FlowDirection.LeftToRight, typeface, emSize, brush); + var origin = new Point(centerX - text.Width / 2, blockTop + i * lineHeight + (lineHeight - text.Height) / 2); + if (outlinePen != null && text.BuildGeometry(origin) is { } textGeometry) { - geometry = new CombinedGeometry(GeometryCombineMode.Exclude, geometry, BuildLayerGeometry(eraserShapes)); + context.DrawGeometry(null, outlinePen, textGeometry); } - var color = drawShapes[0].ForeColor; - var alpha = (byte)Math.Round(color.A * 215 / 255.0); - context.DrawGeometry(GetBrush(Color.FromArgb(alpha, color.R, color.G, color.B)), null, geometry); + context.DrawText(text, origin); } } @@ -834,6 +915,8 @@ private void ApplyTransform(Point screenPoint, KeyModifiers modifiers) Func map; var mirrored = false; + var mirrorX = false; + var mirrorY = false; if (_transformHandle == RotateHandle) { var startAngle = Math.Atan2(FromZoomFactorY((float)_transformStart.Y) - centerY, FromZoomFactorX((float)_transformStart.X) - centerX); @@ -890,6 +973,8 @@ private void ApplyTransform(Point screenPoint, KeyModifiers modifiers) var sy = scaleY; map = (x, y) => (anchorX + (x - anchorX) * sx, anchorY + (y - anchorY) * sy); mirrored = sx * sy < 0; + mirrorX = sx < 0; + mirrorY = sy < 0; _transformLabel = $"{Math.Abs(width * sx):0} × {Math.Abs(height * sy):0}"; } @@ -907,6 +992,10 @@ private void ApplyTransform(Point screenPoint, KeyModifiers modifiers) _transformReversed[i] = mirrored; } + // Dragging an edge past the opposite one mirrors the shape - its text area goes along + shape.TextArea = _transformTextAreas![i]; + shape.MirrorTextArea(mirrorX, mirrorY); + var points = shape.Points; if (points.Count != original.Length) { @@ -925,6 +1014,12 @@ private void ApplyTransform(Point screenPoint, KeyModifiers modifiers) private void DrawShapePoints(DrawingContext context, DrawShape shape, bool isActive) { + if (shape.IsTextBox) + { + // A text box is moved and sized with the selection handles + return; + } + foreach (var point in shape.Points) { var center = ToZoomFactorPoint(point); @@ -1001,8 +1096,8 @@ protected override void OnPointerPressed(PointerPressedEventArgs e) return; } - // Shape tool: drag out the rectangle the library shape goes into - if (CurrentTool == DrawingTool.Shape && properties.IsLeftButtonPressed && InsertShape != null) + // Shape tool: drag out the rectangle the library shape goes into (text tool: the text box) + if (properties.IsLeftButtonPressed && IsInsertTool) { _insertStart = (x, y); _insertEnd = (x, y); @@ -1021,6 +1116,7 @@ protected override void OnPointerPressed(PointerPressedEventArgs e) _transformShapes = targets; _transformOriginal = targets.Select(s => s.Points.Select(p => (p.X, p.Y)).ToArray()).ToList(); _transformReversed = new bool[targets.Count]; + _transformTextAreas = targets.Select(s => s.TextArea).ToArray(); _transformBounds = GetUnionBounds(targets); _transformStart = point; _transformStarted = false; @@ -1245,6 +1341,7 @@ protected override void OnPointerReleased(PointerReleasedEventArgs e) _transformShapes = null; _transformOriginal = null; _transformReversed = null; + _transformTextAreas = null; _transformHandle = -1; _transformStarted = false; _transformLabel = null; @@ -1256,13 +1353,15 @@ protected override void OnPointerReleased(PointerReleasedEventArgs e) InvalidateVisual(); } + private bool IsInsertTool => CurrentTool == DrawingTool.Text || (CurrentTool == DrawingTool.Shape && InsertShape != null); + /// /// The rectangle the shape tool drag describes; a click (tiny drag) means "default size here". /// Dragging past the start point flips the rectangle, Shift keeps the shape's proportions. /// private CanvasInsertEventArgs? GetInsertRect() { - if (_insertStart is not { } start || InsertShape == null) + if (_insertStart is not { } start || !IsInsertTool) { return null; } @@ -1274,7 +1373,7 @@ protected override void OnPointerReleased(PointerReleasedEventArgs e) return new CanvasInsertEventArgs(start.X, start.Y, 0, 0, isClick: true); } - if (_insertConstrain) + if (_insertConstrain && CurrentTool == DrawingTool.Shape && InsertShape != null) { var aspect = InsertShape.AspectRatio; if (Math.Abs(width) * aspect > Math.Abs(height)) @@ -1324,7 +1423,7 @@ protected override void OnPointerReleased(PointerReleasedEventArgs e) for (var i = shapes.Count - 1; i >= 0; i--) { var shape = shapes[i]; - if (!shape.Hidden && !shape.IsEraser && shape.Points.Count > 2 && shape.HitTest(x, y, 0)) + if (!shape.Hidden && !shape.IsEraser && !shape.IsTextBox && shape.Points.Count > 2 && shape.HitTest(x, y, 0)) { return shape.ForeColor; } @@ -1406,7 +1505,7 @@ protected override void OnPointerWheelChanged(PointerWheelEventArgs e) DrawCoordinate? closest = null; var minDist = float.MaxValue; - foreach (var shape in Shapes.Where(s => !s.Hidden)) + foreach (var shape in Shapes.Where(s => !s.Hidden && !s.IsTextBox)) { foreach (var point in shape.Points) { diff --git a/src/ui/Features/Assa/AssaDraw/AssaDrawTextLine.cs b/src/ui/Features/Assa/AssaDraw/AssaDrawTextLine.cs new file mode 100644 index 00000000000..61481526521 --- /dev/null +++ b/src/ui/Features/Assa/AssaDraw/AssaDrawTextLine.cs @@ -0,0 +1,228 @@ +using System; +using System.Globalization; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using Avalonia.Media; + +namespace Nikse.SubtitleEdit.Features.Assa.AssaDraw; + +/// +/// The ASSA lines ASSA draw writes for text and outlines. +/// +/// Text goes on its own line, on the layer of its shape and right after the drawing, so it shows +/// on top of the bubble: {\an5\pos(x,y)\q2\fnArial\fs48\c&H000000&\bord0\shad0}{assadraw-text box=l,t,r,b fit=48}Hi\nthere. +/// The comment block keeps the text area (and the largest size for shrink to fit) so the dialog +/// can open the text again. Wrapping is done here (\q2): \n is a wrap, \N a line break the user typed. +/// +/// +public static class AssaDrawTextLine +{ + public const string StyleName = "AssaDrawText"; + + private static readonly Regex MarkerRegex = new( + @"\{assadraw-text box=(-?[\d.]+),(-?[\d.]+),(-?[\d.]+),(-?[\d.]+)(?: fit=([\d.]+))?\}", + RegexOptions.Compiled); + private static readonly Regex TagBlockRegex = new(@"\{[^}]*\}", RegexOptions.Compiled); + private static readonly Regex PosRegex = new(@"\\pos\(\s*(-?[\d.]+)\s*,\s*(-?[\d.]+)\s*\)", RegexOptions.Compiled); + private static readonly Regex FontNameRegex = new(@"\\fn([^\\}]+)", RegexOptions.Compiled); + private static readonly Regex FontSizeRegex = new(@"\\fs([\d.]+)", RegexOptions.Compiled); + private static readonly Regex BoldRegex = new(@"\\b(\d+)", RegexOptions.Compiled); + private static readonly Regex PrimaryColorRegex = new(@"\\1?c&H([0-9a-fA-F]+)&?", RegexOptions.Compiled); + private static readonly Regex PrimaryAlphaRegex = new(@"\\1a&H([0-9a-fA-F]+)&?", RegexOptions.Compiled); + private static readonly Regex OutlineWidthRegex = new(@"\\bord([\d.]+)", RegexOptions.Compiled); + private static readonly Regex OutlineColorRegex = new(@"\\3c&H([0-9a-fA-F]+)&?", RegexOptions.Compiled); + private static readonly Regex OutlineAlphaRegex = new(@"\\3a&H([0-9a-fA-F]+)&?", RegexOptions.Compiled); + + public sealed class Parsed + { + public string Text { get; set; } = string.Empty; + public (float Left, float Top, float Right, float Bottom) Box { get; set; } + public string FontName { get; set; } = "Arial"; + public float FontSize { get; set; } = 48; + public bool Bold { get; set; } + public bool ShrinkToFit { get; set; } + public Color? Color { get; set; } + public float OutlineWidth { get; set; } + public Color OutlineColor { get; set; } = Colors.Black; + } + + /// + /// A line written by ASSA draw for text (it carries the text area comment). + /// + public static bool IsTextLine(string text) => !string.IsNullOrEmpty(text) && MarkerRegex.IsMatch(text); + + /// + /// The text line for a shape with text; outline only for a text box (a bubble's outline is the bubble's). + /// + public static string Format(DrawShape shape) + { + var (left, top, right, bottom) = shape.GetTextBounds(); + var layout = AssaTextLayout.Layout(shape.Text, shape.FontName, shape.FontSize, shape.FontBold, right - left, bottom - top, shape.ShrinkToFit); + + var sb = new StringBuilder(); + sb.Append(CultureInfo.InvariantCulture, $"{{\\an5\\pos({N((left + right) / 2)},{N((top + bottom) / 2)})\\q2\\fn{shape.FontName}\\fs{N(layout.FontSize)}"); + if (shape.FontBold) + { + sb.Append("\\b1"); + } + + sb.Append("\\c").Append(ToAssaColor(shape.TextColor)); + if (shape.TextColor.A < 255) + { + sb.Append("\\1a").Append(ToAssaAlpha(shape.TextColor)); + } + + AppendOutline(sb, shape.IsTextBox ? shape.OutlineWidth : 0, shape.OutlineColor); + sb.Append("\\shad0}"); + + sb.Append(CultureInfo.InvariantCulture, $"{{assadraw-text box={N(left)},{N(top)},{N(right)},{N(bottom)}"); + if (shape.ShrinkToFit) + { + sb.Append(CultureInfo.InvariantCulture, $" fit={N(shape.FontSize)}"); + } + + sb.Append('}'); + + // Wrapped lines of one paragraph are joined with the soft \n, paragraphs with \N + var paragraphs = shape.Text.Replace("\r\n", "\n").Split('\n'); + var lineIndex = 0; + for (var p = 0; p < paragraphs.Length; p++) + { + if (p > 0) + { + sb.Append("\\N"); + } + + var wordCount = paragraphs[p].Split(' ', StringSplitOptions.RemoveEmptyEntries).Length; + var first = true; + var words = 0; + while (lineIndex < layout.Lines.Count && (first || words < wordCount)) + { + var line = layout.Lines[lineIndex++]; + if (!first) + { + sb.Append("\\n"); + } + + sb.Append(Escape(line)); + words += line.Split(' ', StringSplitOptions.RemoveEmptyEntries).Length; + first = false; + } + } + + return sb.ToString(); + } + + /// + /// "\bord2\3c&H000000&" (and \3a when translucent), or "\bord0". + /// + public static void AppendOutline(StringBuilder sb, float width, Color color) + { + sb.Append(CultureInfo.InvariantCulture, $"\\bord{N(Math.Max(0, width))}"); + if (width > 0) + { + sb.Append("\\3c").Append(ToAssaColor(color)); + if (color.A < 255) + { + sb.Append("\\3a").Append(ToAssaAlpha(color)); + } + } + } + + public static bool TryParse(string text, out Parsed result) + { + result = new Parsed(); + var marker = MarkerRegex.Match(text ?? string.Empty); + if (!marker.Success) + { + return false; + } + + var box = (F(marker.Groups[1].Value), F(marker.Groups[2].Value), F(marker.Groups[3].Value), F(marker.Groups[4].Value)); + var tags = string.Concat(TagBlockRegex.Matches(text!).Select(m => m.Value)); + + // \pos wins over the comment, so a line moved in the grid (or resampled) keeps its new place + var pos = PosRegex.Match(tags); + if (pos.Success) + { + var dx = F(pos.Groups[1].Value) - (box.Item1 + box.Item3) / 2; + var dy = F(pos.Groups[2].Value) - (box.Item2 + box.Item4) / 2; + box = (box.Item1 + dx, box.Item2 + dy, box.Item3 + dx, box.Item4 + dy); + } + + result.Box = box; + if (FontNameRegex.Match(tags) is { Success: true } fontName) + { + result.FontName = fontName.Groups[1].Value.Trim(); + } + + if (marker.Groups[5].Success) + { + result.ShrinkToFit = true; + result.FontSize = F(marker.Groups[5].Value); + } + else if (FontSizeRegex.Match(tags) is { Success: true } fontSize) + { + result.FontSize = F(fontSize.Groups[1].Value); + } + + result.Bold = BoldRegex.Match(tags) is { Success: true } bold && bold.Groups[1].Value != "0"; + + if (PrimaryColorRegex.Match(tags) is { Success: true } color) + { + var alpha = PrimaryAlphaRegex.Match(tags); + result.Color = FromAssaColor(color.Groups[1].Value, alpha.Success ? alpha.Groups[1].Value : null); + } + + ParseOutline(tags, out var outlineWidth, out var outlineColor); + result.OutlineWidth = outlineWidth; + result.OutlineColor = outlineColor; + + var body = TagBlockRegex.Replace(text!, string.Empty); + result.Text = body.Replace("\\N", "\n").Replace("\\n", " ").Replace("\\h", " ").Replace('\u29F5', '\\'); + return true; + } + + /// + /// \bord and \3c (\3a) from a line's tags; width 0 when there are none. + /// + public static void ParseOutline(string text, out float width, out Color color) + { + width = 0; + color = Colors.Black; + var tags = string.Concat(TagBlockRegex.Matches(text).Select(m => m.Value)); + if (OutlineWidthRegex.Match(tags) is { Success: true } bord) + { + width = F(bord.Groups[1].Value); + } + + if (OutlineColorRegex.Match(tags) is { Success: true } outline) + { + var alpha = OutlineAlphaRegex.Match(tags); + color = FromAssaColor(outline.Groups[1].Value, alpha.Success ? alpha.Groups[1].Value : null); + } + } + + public static string ToAssaColor(Color color) => $"&H{color.B:X2}{color.G:X2}{color.R:X2}&"; + + public static string ToAssaAlpha(Color color) => $"&H{255 - color.A:X2}&"; + + private static Color FromAssaColor(string hex, string? alphaHex) + { + var value = uint.TryParse(hex, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var parsed) ? parsed : 0; + var alpha = alphaHex != null && byte.TryParse(alphaHex, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var a) + ? (byte)(255 - a) + : (byte)255; + return Color.FromArgb(alpha, (byte)(value & 0xFF), (byte)((value >> 8) & 0xFF), (byte)((value >> 16) & 0xFF)); + } + + /// + /// Braces would start an override block, a backslash a tag. + /// + private static string Escape(string text) => text.Replace('{', '(').Replace('}', ')').Replace("\\", "\u29F5"); + + private static string N(float value) => value.ToString("0.##", CultureInfo.InvariantCulture); + + private static float F(string value) => float.TryParse(value, NumberStyles.Float, CultureInfo.InvariantCulture, out var f) ? f : 0; +} diff --git a/src/ui/Features/Assa/AssaDraw/AssaDrawViewModel.cs b/src/ui/Features/Assa/AssaDraw/AssaDrawViewModel.cs index 15255c54df5..f4371783f0b 100644 --- a/src/ui/Features/Assa/AssaDraw/AssaDrawViewModel.cs +++ b/src/ui/Features/Assa/AssaDraw/AssaDrawViewModel.cs @@ -69,6 +69,18 @@ public partial class AssaDrawViewModel : ObservableObject [ObservableProperty] private float _shapeWidth; [ObservableProperty] private float _shapeHeight; [ObservableProperty] private int _shapeLayer; + [ObservableProperty] private float _shapeOutlineWidth; + [ObservableProperty] private Color _shapeOutlineColor = Colors.Black; + [ObservableProperty] private bool _isTextBoxSelected; + [ObservableProperty] private bool _isDrawShapeSelected; + [ObservableProperty] private bool _canEditText; + [ObservableProperty] private bool _canEditOutline; + [ObservableProperty] private string _shapeText = string.Empty; + [ObservableProperty] private string _textFontName = "Arial"; + [ObservableProperty] private float _textFontSize = 48; + [ObservableProperty] private bool _textBold; + [ObservableProperty] private Color _textColor = Colors.Black; + [ObservableProperty] private bool _textShrinkToFit = true; [ObservableProperty] private string _selectionText = string.Empty; [ObservableProperty] private string _codePreview = string.Empty; [ObservableProperty] private double _zoomPercent = 100; @@ -84,6 +96,42 @@ public partial class AssaDrawViewModel : ObservableObject public Subtitle ResultSubtitle { get; set; } = new Subtitle(); + /// + /// A text box or bubble was just placed: the window puts the cursor in the text field. + /// + public event EventHandler? TextEditRequested; + + private List? _fontNames; + + /// + /// Fonts by the names libass knows them by. + /// + public List FontNames + { + get + { + if (_fontNames == null) + { + try + { + _fontNames = FontHelper.GetLibAssaFonts().Distinct().OrderBy(f => f).ToList(); + } + catch (Exception exception) + { + Se.LogError(exception, "ASSA draw: could not list fonts"); + _fontNames = []; + } + + if (!_fontNames.Contains("Arial")) + { + _fontNames.Insert(0, "Arial"); + } + } + + return _fontNames; + } + } + private float _currentX = float.MinValue; private float _currentY = float.MinValue; private readonly Regex _regexStart = new(@"\{[^{]*\\p1[^}]*\}"); @@ -174,30 +222,10 @@ public void Initialize(Subtitle subtitle, List selectedLi foreach (var line in selectedLines) { var style = styles.FirstOrDefault(s => s.Name.Equals(line.Style, StringComparison.OrdinalIgnoreCase)) ?? new SsaStyle(); - var color = style.Primary.ToAvaloniaColor(); - - // 1. Process iclip (Vector Mask) - var iclipMatch = _regexIclip.Match(line.Text); - if (iclipMatch.Success) - { - // Extract only the drawing commands inside the parentheses - var clipCommands = iclipMatch.Groups[1].Value; - ImportAssaDrawingFromText(clipCommands, line.Layer, color, true); - } - - // 2. Process \p1 Drawing (the \p1 can share its tag block with \pos, \an etc.) - if (_regexStart.IsMatch(line.Text)) - { - // Remove all tags (anything inside curly braces) to get raw vector data - string drawingOnly = Regex.Replace(line.Text, @"\{[^}]+\}", string.Empty).Trim(); - - if (!string.IsNullOrWhiteSpace(drawingOnly)) - { - ImportAssaDrawingFromText(drawingOnly, line.Layer, color, false); - } - } + ImportLine(line.Text, line.Layer, style.Primary.ToAvaloniaColor()); } + AttachTextLines(); RefreshTreeView(); Canvas?.InvalidateVisual(); } @@ -239,6 +267,9 @@ partial void OnCurrentLibraryShapeChanged(ShapeLibraryItem? value) [RelayCommand] private void ColorPickerTool() => SetTool(DrawingTool.ColorPicker); + [RelayCommand] + private void TextTool() => SetTool(DrawingTool.Text); + /// /// Eyedropper result: becomes the current color - new shapes get it, and a selected shape's /// (or layer's) color changes to it. @@ -256,7 +287,7 @@ public void PickColor(Color color) /// private int GetLayerForColor(Color color) { - var existing = Shapes.FirstOrDefault(s => s.ForeColor == color && !s.IsEraser); + var existing = Shapes.FirstOrDefault(s => s.ForeColor == color && !s.IsEraser && !s.IsTextBox); if (existing != null) { return existing.Layer; @@ -280,7 +311,69 @@ private void PickLibraryShape(ShapeLibraryItem? item) SetTool(DrawingTool.Shape); } - private void OnInsertRequested(object? sender, CanvasInsertEventArgs e) => InsertLibraryShape(e); + private void OnInsertRequested(object? sender, CanvasInsertEventArgs e) + { + if (CurrentTool == DrawingTool.Text) + { + InsertTextBox(e); + } + else + { + InsertLibraryShape(e); + } + } + + /// + /// Text tool: a text box on a new layer above the drawing, in the current color with a black + /// outline like a subtitle, selected with its text ready to be typed over. + /// + public DrawShape InsertTextBox(CanvasInsertEventArgs e) + { + var x = e.X; + var y = e.Y; + var width = e.Width; + var height = e.Height; + if (e.IsClick) + { + width = CanvasWidth * 0.4f; + height = CanvasHeight * 0.12f; + x -= width / 2f; + y -= height / 2f; + } + + CancelDrawing(); + SaveUndo(); + var layer = Shapes.Count == 0 ? 0 : Shapes.Max(s => s.Layer) + 1; + var shape = MakeRectangle(x, y, width, height, layer, LayerColor); + shape.IsTextBox = true; + shape.TextArea = (0, 0, 1, 1); + shape.Text = Se.Language.Assa.DrawDefaultText; + shape.TextColor = LayerColor; + shape.FontSize = DefaultFontSize(16f); + shape.OutlineWidth = 2; + shape.OutlineColor = Colors.Black; + Shapes.Add(shape); + SetTool(DrawingTool.Select); + RefreshTreeView(); + SelectShape(shape); + Canvas?.InvalidateVisual(); + TextEditRequested?.Invoke(this, EventArgs.Empty); + return shape; + } + + /// + /// A font size that suits the frame: a fraction of its height (54 for 1080p with 20). + /// + private float DefaultFontSize(float linesPerFrame) => MathF.Max(AssaTextLayout.MinFontSize, MathF.Round(CanvasHeight / linesPerFrame)); + + /// + /// Black text on light shapes, white on dark ones. + /// + private static Color ContrastColor(Color color) + { + var luminance = 0.299 * color.R + 0.587 * color.G + 0.114 * color.B; + return luminance > 140 ? Colors.Black : Colors.White; + } /// /// Places the current library shape on a new layer above the drawing, in the current color, @@ -317,12 +410,22 @@ public void InsertLibraryShape(CanvasInsertEventArgs e) SaveUndo(); var firstLayer = Shapes.Count == 0 ? 0 : Shapes.Max(s => s.Layer) + 1; var shapes = item.CreateShapes(x, y, width, height, LayerColor, firstLayer); + foreach (var shape in shapes) + { + shape.TextColor = ContrastColor(shape.ForeColor); + shape.FontSize = DefaultFontSize(20f); + } + Shapes.AddRange(shapes); SetTool(DrawingTool.Select); RefreshTreeView(); if (shapes.Count == 1) { SelectShape(shapes[0]); + if (item.TextArea != null) + { + TextEditRequested?.Invoke(this, EventArgs.Empty); + } } else { @@ -518,6 +621,23 @@ private void UpdateSelectionInfo() ShapeWidth = MathF.Round(right - left, 1); ShapeHeight = MathF.Round(bottom - top, 1); ShapeLayer = shape.Layer; + ShapeText = shape.Text; + TextFontName = shape.FontName; + TextFontSize = shape.FontSize; + TextBold = shape.FontBold; + TextColor = shape.TextColor; + TextShrinkToFit = shape.ShrinkToFit; + if (!FontNames.Contains(shape.FontName)) + { + FontNames.Add(shape.FontName); + } + } + + var outlineShape = GetOutlineShapes().FirstOrDefault(); + if (outlineShape != null) + { + ShapeOutlineWidth = outlineShape.OutlineWidth; + ShapeOutlineColor = outlineShape.OutlineColor; } } finally @@ -525,6 +645,11 @@ private void UpdateSelectionInfo() _updatingShapeFields = false; } + IsTextBoxSelected = shape?.IsTextBox == true; + IsDrawShapeSelected = shape != null && !shape.IsTextBox; + CanEditText = shape != null && !shape.IsEraser; + CanEditOutline = GetOutlineShapes().Count > 0; + var item = SelectedTreeItem; SelectionText = SelectedShapes.Count > 1 ? $"{SelectedShapes.Count} shapes" @@ -536,8 +661,7 @@ private void UpdateSelectionInfo() ? item.Name : string.Empty; - var code = GenerateAssaCode(); - CodePreview = code.Length > 400 ? code[..400] + "..." : code; + UpdateCodePreview(); } partial void OnShapeXChanged(float value) => MoveTargetShape(value - (TargetShape?.GetBounds().Left ?? value), 0, "shape-x"); @@ -592,6 +716,130 @@ private void ResizeTargetShape(float? width, float? height) Canvas?.InvalidateVisual(); } + /// + /// What the outline fields change: a selected text box, or else the drawing shapes of the + /// selected layer (or of the selected shape's layer) - the layer is one ASSA line with one outline. + /// + private List GetOutlineShapes() + { + var shape = TargetShape; + if (shape?.IsTextBox == true) + { + return [shape]; + } + + int? layer = SelectedTreeItem?.IsLayer == true ? SelectedTreeItem.Layer : shape?.Layer; + return layer == null ? [] : Shapes.Where(s => s.Layer == layer && !s.IsEraser && !s.IsTextBox).ToList(); + } + + partial void OnShapeOutlineWidthChanged(float value) => ApplyOutline(); + + partial void OnShapeOutlineColorChanged(Color value) => ApplyOutline(); + + private void ApplyOutline() + { + if (_updatingShapeFields) + { + return; + } + + var width = Math.Max(0, ShapeOutlineWidth); + var color = ShapeOutlineColor; + var shapes = GetOutlineShapes(); + if (!shapes.Any(s => Math.Abs(s.OutlineWidth - width) > 0.001f || s.OutlineColor != color)) + { + return; + } + + SaveUndo($"outline-{RuntimeHelpers.GetHashCode(shapes[0])}"); + foreach (var shape in shapes) + { + shape.OutlineWidth = width; + shape.OutlineColor = color; + } + + UpdateCodePreview(); + Canvas?.InvalidateVisual(); + } + + partial void OnShapeTextChanged(string value) => EditText("text", shape => + { + // First text in a plain shape: pick a color that shows on it + if (string.IsNullOrEmpty(shape.Text) && !shape.IsTextBox && shape.TextColor == Colors.Black) + { + shape.TextColor = ContrastColor(shape.ForeColor); + SetTextColorField(shape.TextColor); + } + + if (string.IsNullOrEmpty(shape.Text) && shape.FontSize == 48) + { + shape.FontSize = DefaultFontSize(20f); + SetTextFontSizeField(shape.FontSize); + } + + shape.Text = value ?? string.Empty; + }); + + partial void OnTextFontNameChanged(string value) => EditText("font", shape => shape.FontName = string.IsNullOrWhiteSpace(value) ? "Arial" : value); + + partial void OnTextFontSizeChanged(float value) => EditText("font-size", shape => shape.FontSize = Math.Clamp(value, AssaTextLayout.MinFontSize, 1000)); + + partial void OnTextBoldChanged(bool value) => EditText("bold", shape => shape.FontBold = value); + + partial void OnTextColorChanged(Color value) => EditText("text-color", shape => shape.TextColor = value); + + partial void OnTextShrinkToFitChanged(bool value) => EditText("shrink", shape => shape.ShrinkToFit = value); + + private void EditText(string undoKey, Action apply) + { + var shape = TargetShape; + if (_updatingShapeFields || shape == null || shape.IsEraser) + { + return; + } + + SaveUndo($"{undoKey}-{RuntimeHelpers.GetHashCode(shape)}"); + apply(shape); + if (undoKey == "text") + { + UpdateShapeTreeName(shape); + } + + Canvas?.InvalidateVisual(); + } + + private void SetTextColorField(Color color) + { + _updatingShapeFields = true; + try + { + TextColor = color; + } + finally + { + _updatingShapeFields = false; + } + } + + private void SetTextFontSizeField(float size) + { + _updatingShapeFields = true; + try + { + TextFontSize = size; + } + finally + { + _updatingShapeFields = false; + } + } + + private void UpdateCodePreview() + { + var code = GenerateAssaCode(); + CodePreview = code.Length > 400 ? code[..400] + "..." : code; + } + partial void OnShapeLayerChanged(int value) { if (!_updatingShapeFields && TargetShape != null && TargetShape.Layer != value && value >= 0) @@ -1911,6 +2159,24 @@ private Subtitle GenerateSubtitle(bool includeAll = false) } } + var hasText = layers.Any(l => l.Any(s => s.HasText)); + if (hasText) + { + // Own style for the text lines: centered, no margins, outline or shadow - so a Default + // style with an opaque box or big margins doesn't change them + subtitle.Header = AdvancedSubStationAlpha.AddSsaStyle(new SsaStyle + { + Name = AssaDrawTextLine.StyleName, + Alignment = "5", + MarginVertical = 0, + MarginLeft = 0, + MarginRight = 0, + ShadowWidth = 0, + OutlineWidth = 0, + BorderStyle = "1", + }, subtitle.Header); + } + // Generate paragraphs with style names var sbDraw = new StringBuilder(); var sbErase = new StringBuilder(); @@ -1922,14 +2188,14 @@ private Subtitle GenerateSubtitle(bool includeAll = false) var firstShape = GetLayerStyleShape(layer); // Collect draw shapes (normal shapes) - foreach (var shape in layer.Where(p => !p.IsEraser)) + foreach (var shape in layer.Where(p => !p.IsEraser && !p.IsTextBox)) { sbDraw.Append(shape.ToAssa()); sbDraw.Append(" "); } // Collect erase shapes (iclip shapes) - foreach (var shape in layer.Where(p => p.IsEraser)) + foreach (var shape in layer.Where(p => p.IsEraser && !p.IsTextBox)) { sbErase.Append(shape.ToAssa()); sbErase.Append(" "); @@ -1949,10 +2215,16 @@ private Subtitle GenerateSubtitle(bool includeAll = false) finalText.Append($"{{\\iclip({eraseText})}}"); } - // Add draw shapes + // Add draw shapes, with the layer's outline if (!string.IsNullOrEmpty(drawText)) { - finalText.Append($"{{\\p1}}{drawText}{{\\p0}}"); + finalText.Append('{'); + if (firstShape is { IsEraser: false, OutlineWidth: > 0 }) + { + AssaDrawTextLine.AppendOutline(finalText, firstShape.OutlineWidth, firstShape.OutlineColor); + } + + finalText.Append($"\\p1}}{drawText}{{\\p0}}"); } if (finalText.Length > 0) @@ -1971,6 +2243,17 @@ private Subtitle GenerateSubtitle(bool includeAll = false) subtitle.Paragraphs.Add(p); } } + + // Text after the layer's drawing, on the same layer: libass draws later lines of a + // layer on top, so the text shows over its bubble and under the layers above + foreach (var shape in layer.Where(s => s.HasText && s.Points.Count > 0)) + { + subtitle.Paragraphs.Add(new Paragraph(AssaDrawTextLine.Format(shape), 0, 10000) + { + Layer = layer.Key, + Extra = AssaDrawTextLine.StyleName, + }); + } } return subtitle; @@ -1986,6 +2269,11 @@ private Subtitle GenerateSubtitle(bool includeAll = false) DrawShape? firstEraser = null; foreach (var shape in layer) { + if (shape.IsTextBox) + { + continue; + } + if (!shape.IsEraser) { return shape; @@ -2057,14 +2345,14 @@ private string GenerateAssaCode() } var sbDraw = new StringBuilder(); - foreach (var shape in Shapes.Where(s => !s.IsEraser && !s.Hidden)) + foreach (var shape in Shapes.Where(s => !s.IsEraser && !s.Hidden && !s.IsTextBox)) { sbDraw.Append(shape.ToAssa()); sbDraw.Append(' '); } var sbErase = new StringBuilder(); - foreach (var shape in Shapes.Where(s => s.IsEraser && !s.Hidden)) + foreach (var shape in Shapes.Where(s => s.IsEraser && !s.Hidden && !s.IsTextBox)) { sbErase.Append(shape.ToAssa()); sbErase.Append(' '); @@ -2085,7 +2373,14 @@ private string GenerateAssaCode() } if (!string.IsNullOrEmpty(drawCode)) { - result.Append($"{{\\p1}}{drawCode}{{\\p0}}"); + result.Append('{'); + var outlineShape = Shapes.FirstOrDefault(s => !s.IsEraser && !s.Hidden && !s.IsTextBox); + if (outlineShape is { OutlineWidth: > 0 }) + { + AssaDrawTextLine.AppendOutline(result, outlineShape.OutlineWidth, outlineShape.OutlineColor); + } + + result.Append($"\\p1}}{drawCode}{{\\p0}}"); } return result.ToString(); @@ -2137,15 +2432,16 @@ private void RefreshTreeView() shapeNumber++; var shapeItem = new ShapeTreeItem { - Name = $"Shape {shapeNumber} ({(shape.IsEraser ? "erase" : "draw")})", + Name = GetShapeTreeName(shape, shapeNumber), Shape = shape, IsExpanded = shape.Expanded, - IconName = shape.IsEraser ? "fa-solid fa-eraser" : "fa-solid fa-draw-polygon", + IconName = shape.IsTextBox ? "fa-solid fa-font" : shape.IsEraser ? "fa-solid fa-eraser" : "fa-solid fa-draw-polygon", IsHidden = shape.Hidden, - Meta = shape.Points.Count.ToString(CultureInfo.InvariantCulture), + Meta = shape.IsTextBox ? string.Empty : shape.Points.Count.ToString(CultureInfo.InvariantCulture), }; - foreach (var point in shape.Points) + // A text box is sized with the handles, its corners aren't points to edit + foreach (var point in shape.IsTextBox ? [] : shape.Points) { var isControlPoint = point.DrawType is DrawCoordinateType.BezierCurveSupport1 or DrawCoordinateType.BezierCurveSupport2; shapeItem.Children.Add(new ShapeTreeItem @@ -2186,6 +2482,41 @@ private void RefreshTreeView() } } + private static string GetShapeTreeName(DrawShape shape, int shapeNumber) + { + var text = shape.Text.Replace("\r", string.Empty).Replace('\n', ' ').Trim(); + if (text.Length > 24) + { + text = text[..24] + "…"; + } + + if (shape.IsTextBox) + { + return text.Length > 0 ? $"{Se.Language.Assa.DrawTextBox}: {text}" : Se.Language.Assa.DrawTextBox; + } + + var name = $"Shape {shapeNumber} ({(shape.IsEraser ? "erase" : "draw")})"; + return text.Length > 0 && !shape.IsEraser ? $"{name}: {text}" : name; + } + + /// + /// Typing renames the shape's tree row - rebuilding the tree per key press would drop the text field's focus. + /// + private void UpdateShapeTreeName(DrawShape shape) + { + foreach (var layerItem in ShapeTreeItems) + { + for (var i = 0; i < layerItem.Children.Count; i++) + { + if (layerItem.Children[i].Shape == shape) + { + layerItem.Children[i].Name = GetShapeTreeName(shape, i + 1); + return; + } + } + } + } + /// /// The tree item for a point, shape or layer, with its parents expanded so it can be shown selected. /// @@ -2231,6 +2562,84 @@ private void ImportAssaDrawingFromText(string text, int layer, Color color, bool Shapes.AddRange(DrawShape.ParseAssa(text, layer, color, isEraser)); } + private readonly List<(int Layer, AssaDrawTextLine.Parsed Text)> _importedTexts = []; + + /// + /// One line of a script: its \iclip erase shapes, its \p1 drawing (with the line's outline), + /// or an ASSA draw text line - those are attached to their shapes by . + /// + private void ImportLine(string text, int layer, Color color) + { + if (AssaDrawTextLine.TryParse(text, out var parsedText)) + { + _importedTexts.Add((layer, parsedText)); + return; + } + + // 1. Process iclip (Vector Mask) + var iclipMatch = _regexIclip.Match(text); + if (iclipMatch.Success) + { + // Extract only the drawing commands inside the parentheses + ImportAssaDrawingFromText(iclipMatch.Groups[1].Value, layer, color, true); + } + + // 2. Process \p1 Drawing (the \p1 can share its tag block with \pos, \an etc.) + if (_regexStart.IsMatch(text)) + { + // Remove all tags (anything inside curly braces) to get raw vector data + var drawingOnly = Regex.Replace(text, @"\{[^}]+\}", string.Empty).Trim(); + if (!string.IsNullOrWhiteSpace(drawingOnly)) + { + var shapes = DrawShape.ParseAssa(drawingOnly, layer, color, false); + AssaDrawTextLine.ParseOutline(text, out var outlineWidth, out var outlineColor); + foreach (var shape in shapes) + { + shape.OutlineWidth = outlineWidth; + shape.OutlineColor = outlineColor; + } + + Shapes.AddRange(shapes); + } + } + } + + /// + /// Imported text goes back into the shape it was written for: the smallest drawing shape of + /// its layer that holds the center of the text area. Text without one becomes a text box. + /// + private void AttachTextLines() + { + foreach (var (layer, parsed) in _importedTexts) + { + var (left, top, right, bottom) = parsed.Box; + var centerX = (left + right) / 2; + var centerY = (top + bottom) / 2; + var host = Shapes + .Where(s => s.Layer == layer && !s.IsEraser && !s.IsTextBox && !s.HasText && s.Points.Count > 2 && s.HitTest(centerX, centerY, 0)) + .OrderBy(s => { var b = s.GetBounds(); return (b.Right - b.Left) * (b.Bottom - b.Top); }) + .FirstOrDefault(); + if (host == null) + { + host = MakeRectangle(left, top, right - left, bottom - top, layer, parsed.Color ?? Colors.White); + host.IsTextBox = true; + host.OutlineWidth = parsed.OutlineWidth; + host.OutlineColor = parsed.OutlineColor; + Shapes.Add(host); + } + + host.Text = parsed.Text; + host.FontName = parsed.FontName; + host.FontSize = parsed.FontSize; + host.FontBold = parsed.Bold; + host.ShrinkToFit = parsed.ShrinkToFit; + host.TextColor = parsed.Color ?? Colors.White; + host.SetTextBounds(left, top, right, bottom); + } + + _importedTexts.Clear(); + } + private static bool TryGetPlayRes(string? header, out int width, out int height) { width = 0; @@ -2280,25 +2689,10 @@ private void LoadFromText(string text) } } - // Handle iclip - var iclipMatch = _regexIclip.Match(paragraph.Text); - if (iclipMatch.Success) - { - ImportAssaDrawingFromText(iclipMatch.Groups[1].Value, paragraph.Layer, color, true); - } - - // Handle \p1 Drawing (the \p1 can share its tag block with \pos, \an etc.) - if (_regexStart.IsMatch(paragraph.Text)) - { - // Strip tags to avoid parsing non-coordinate text - string drawingOnly = Regex.Replace(paragraph.Text, @"\{[^}]+\}", string.Empty).Trim(); - if (!string.IsNullOrWhiteSpace(drawingOnly)) - { - ImportAssaDrawingFromText(drawingOnly, paragraph.Layer, color, false); - } - } + ImportLine(paragraph.Text, paragraph.Layer, color); } + AttachTextLines(); RefreshTreeView(); Canvas?.InvalidateVisual(); } @@ -2317,6 +2711,10 @@ internal async void OnKeyDown(KeyEventArgs e) e.Handled = true; Window?.Close(); } + else if (IsTextInput(e) && e.Key is Key.Enter or Key.Delete or Key.Back) + { + // Typing in the text field (or a number box) - not a canvas command + } else if (e.Key == Key.Enter) { CloseShape(); @@ -2425,7 +2823,7 @@ internal async void OnKeyDown(KeyEventArgs e) break; } } - else if (e.KeyModifiers == KeyModifiers.None && !IsTextInput(e) && e.Key is Key.V or Key.L or Key.B or Key.R or Key.C or Key.S or Key.I) + else if (e.KeyModifiers == KeyModifiers.None && !IsTextInput(e) && e.Key is Key.V or Key.L or Key.B or Key.R or Key.C or Key.S or Key.I or Key.T) { // Single-key tools like other drawing programs (not while typing in a number box) SetTool(e.Key switch @@ -2436,6 +2834,7 @@ internal async void OnKeyDown(KeyEventArgs e) Key.R => DrawingTool.Rectangle, Key.S => DrawingTool.Shape, Key.I => DrawingTool.ColorPicker, + Key.T => DrawingTool.Text, _ => DrawingTool.Circle, }); e.Handled = true; diff --git a/src/ui/Features/Assa/AssaDraw/AssaDrawWindow.cs b/src/ui/Features/Assa/AssaDraw/AssaDrawWindow.cs index 4c2b7b1b946..1482d523485 100644 --- a/src/ui/Features/Assa/AssaDraw/AssaDrawWindow.cs +++ b/src/ui/Features/Assa/AssaDraw/AssaDrawWindow.cs @@ -742,6 +742,7 @@ void AddTool(string icon, string tooltip, System.Windows.Input.ICommand command, }; panel.Children.Add(shapeButton); panel.Children.Add(palette); + AddTool("fa-solid fa-font", Se.Language.Assa.DrawTextTool, vm.TextToolCommand, DrawingTool.Text, "T"); AddTool("fa-solid fa-eye-dropper", Se.Language.Assa.DrawColorPickerTool, vm.ColorPickerToolCommand, DrawingTool.ColorPicker, "I"); panel.Children.Add(MakeStripSeparator()); panel.Children.Add(CreateToolButton("fa-solid fa-magnifying-glass-plus", Se.Language.Assa.DrawZoomIn, vm.ZoomInCommand, 38)); @@ -950,8 +951,6 @@ private Border CreateSidePanel(AssaDrawViewModel vm) new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, new RowDefinition { Height = new GridLength(1, GridUnitType.Star) }, new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, }, }; @@ -1069,17 +1068,22 @@ private Border CreateSidePanel(AssaDrawViewModel vm) Grid.SetRow(treeView, 1); panelGrid.Children.Add(treeView); - var shapePanel = CreateShapePropertiesPanel(vm); - Grid.SetRow(shapePanel, 2); - panelGrid.Children.Add(shapePanel); - - var layerPanel = CreateLayerPropertiesPanel(vm); - Grid.SetRow(layerPanel, 3); - panelGrid.Children.Add(layerPanel); + // Properties scroll when shape, text and point sections don't fit under the tree + var properties = new StackPanel(); + properties.Children.Add(CreateShapePropertiesPanel(vm)); + properties.Children.Add(CreateTextPropertiesPanel(vm)); + properties.Children.Add(CreateLayerPropertiesPanel(vm)); + properties.Children.Add(CreatePointPropertiesPanel(vm)); + var propertiesScroll = new ScrollViewer + { + Content = properties, + HorizontalScrollBarVisibility = ScrollBarVisibility.Disabled, + }; + Grid.SetRow(propertiesScroll, 2); + panelGrid.Children.Add(propertiesScroll); - var pointPanel = CreatePointPropertiesPanel(vm); - Grid.SetRow(pointPanel, 4); - panelGrid.Children.Add(pointPanel); + // An Auto row never scrolls - cap it so the tree keeps some room + panelGrid.SizeChanged += (_, e) => propertiesScroll.MaxHeight = Math.Max(120, e.NewSize.Height - header.Bounds.Height - 140); return new Border { @@ -1135,9 +1139,11 @@ private static Border CreateShapePropertiesPanel(AssaDrawViewModel vm) var colorButton = UiUtil.MakeColorPickerButton(vm, nameof(vm.LayerColor)); colorButton.HorizontalAlignment = HorizontalAlignment.Left; + colorButton.Bind(Button.IsVisibleProperty, new Binding(nameof(vm.IsDrawShapeSelected))); rows.Children.Add(MakePropertyRow(Se.Language.Assa.DrawLayer, MakeField(nameof(vm.ShapeLayer), 0, 1000), colorButton)); + rows.Children.Add(MakeOutlineRow(vm)); var eraser = new ToggleSwitch { @@ -1148,7 +1154,12 @@ private static Border CreateShapePropertiesPanel(AssaDrawViewModel vm) [!ToggleSwitch.IsCheckedProperty] = new Binding(nameof(vm.ShapeIsEraser)) { Mode = BindingMode.TwoWay }, }; AutomationProperties.SetName(eraser, Se.Language.Assa.DrawUseShapeForErase); - var eraserRow = new StackPanel { Orientation = Orientation.Horizontal, Spacing = 8 }; + var eraserRow = new StackPanel + { + Orientation = Orientation.Horizontal, + Spacing = 8, + [!StackPanel.IsVisibleProperty] = new Binding(nameof(vm.IsDrawShapeSelected)), + }; eraserRow.Children.Add(eraser); eraserRow.Children.Add(new TextBlock { Text = Se.Language.Assa.DrawUseShapeForErase, VerticalAlignment = VerticalAlignment.Center, FontSize = 12.5 }); rows.Children.Add(eraserRow); @@ -1156,6 +1167,100 @@ private static Border CreateShapePropertiesPanel(AssaDrawViewModel vm) return MakePropertySection(Se.Language.Assa.DrawSelectedShape, rows, nameof(vm.IsShapeSelected)); } + /// + /// Outline width and color: of the layer for drawing shapes, of the text for a text box. + /// + private static Grid MakeOutlineRow(AssaDrawViewModel vm) + { + var outlineColor = UiUtil.MakeColorPickerButton(vm, nameof(vm.ShapeOutlineColor)); + outlineColor.HorizontalAlignment = HorizontalAlignment.Left; + var width = MakeField(nameof(vm.ShapeOutlineWidth), 0, 100, format: "0.#"); + width.Increment = 0.5m; + AutomationProperties.SetName(width, Se.Language.General.Outline); + var row = MakePropertyRow(Se.Language.General.Outline, width, outlineColor); + row.Bind(Grid.IsVisibleProperty, new Binding(nameof(vm.CanEditOutline))); + return row; + } + + private Border CreateTextPropertiesPanel(AssaDrawViewModel vm) + { + var rows = new StackPanel { Spacing = 6 }; + + var textBox = new TextBox + { + AcceptsReturn = true, + TextWrapping = TextWrapping.Wrap, + MinHeight = 56, + MaxHeight = 120, + FontSize = 13, + Background = FieldBrush, + BorderBrush = FieldBorderBrush, + CornerRadius = new CornerRadius(6), + [!TextBox.TextProperty] = new Binding(nameof(vm.ShapeText)) { Mode = BindingMode.TwoWay }, + }; + AutomationProperties.SetName(textBox, Se.Language.General.Text); + rows.Children.Add(textBox); + vm.TextEditRequested += (_, _) => Avalonia.Threading.Dispatcher.UIThread.Post(() => + { + textBox.Focus(); + textBox.SelectAll(); + }); + + var fontCombo = new ComboBox + { + ItemsSource = vm.FontNames, + HorizontalAlignment = HorizontalAlignment.Stretch, + MinHeight = 28, + FontSize = 12.5, + [!ComboBox.SelectedItemProperty] = new Binding(nameof(vm.TextFontName)) { Mode = BindingMode.TwoWay }, + }; + AutomationProperties.SetName(fontCombo, Se.Language.General.FontName); + rows.Children.Add(MakePropertyRow(Se.Language.General.FontName, fontCombo)); + + var bold = new ToggleButton + { + Content = new Optris.Icons.Avalonia.Icon { Value = "fa-solid fa-bold", FontSize = 12 }, + Width = 28, + Height = 28, + Padding = new Thickness(0), + HorizontalContentAlignment = HorizontalAlignment.Center, + VerticalContentAlignment = VerticalAlignment.Center, + HorizontalAlignment = HorizontalAlignment.Left, + [!ToggleButton.IsCheckedProperty] = new Binding(nameof(vm.TextBold)) { Mode = BindingMode.TwoWay }, + }; + AutomationProperties.SetName(bold, Se.Language.General.Bold); + if (Se.Settings.Appearance.ShowHints) + { + ToolTip.SetTip(bold, Se.Language.General.Bold); + } + + var size = MakeField(nameof(vm.TextFontSize), AssaTextLayout.MinFontSize, 1000, format: "0.#"); + AutomationProperties.SetName(size, Se.Language.General.FontSize); + var sizeAndBold = new StackPanel { Orientation = Orientation.Horizontal, Spacing = 6 }; + size.Width = 70; + sizeAndBold.Children.Add(size); + sizeAndBold.Children.Add(bold); + var textColor = UiUtil.MakeColorPickerButton(vm, nameof(vm.TextColor)); + textColor.HorizontalAlignment = HorizontalAlignment.Left; + if (Se.Settings.Appearance.ShowHints) + { + ToolTip.SetTip(textColor, Se.Language.Assa.DrawTextColor); + } + + AutomationProperties.SetName(textColor, Se.Language.Assa.DrawTextColor); + rows.Children.Add(MakePropertyRow(Se.Language.General.FontSize, sizeAndBold, textColor)); + + var shrink = new CheckBox + { + Content = Se.Language.Assa.DrawShrinkToFit, + FontSize = 12.5, + [!CheckBox.IsCheckedProperty] = new Binding(nameof(vm.TextShrinkToFit)) { Mode = BindingMode.TwoWay }, + }; + rows.Children.Add(shrink); + + return MakePropertySection(Se.Language.General.Text, rows, nameof(vm.CanEditText)); + } + private static Border CreateLayerPropertiesPanel(AssaDrawViewModel vm) { var row = new StackPanel { Orientation = Orientation.Horizontal, Spacing = 8 }; @@ -1167,7 +1272,10 @@ private static Border CreateLayerPropertiesPanel(AssaDrawViewModel vm) VerticalAlignment = VerticalAlignment.Center, }; row.Children.Add(changeLayer); - return MakePropertySection(Se.Language.Assa.DrawSelectedLayer, row, nameof(vm.IsLayerSelected)); + var rows = new StackPanel { Spacing = 8 }; + rows.Children.Add(row); + rows.Children.Add(MakeOutlineRow(vm)); + return MakePropertySection(Se.Language.Assa.DrawSelectedLayer, rows, nameof(vm.IsLayerSelected)); } private static Border CreatePointPropertiesPanel(AssaDrawViewModel vm) diff --git a/src/ui/Features/Assa/AssaDraw/AssaTextLayout.cs b/src/ui/Features/Assa/AssaDraw/AssaTextLayout.cs new file mode 100644 index 00000000000..f17b29a3d5d --- /dev/null +++ b/src/ui/Features/Assa/AssaDraw/AssaTextLayout.cs @@ -0,0 +1,170 @@ +using System; +using System.Collections.Generic; +using Nikse.SubtitleEdit.Logic; +using SkiaSharp; + +namespace Nikse.SubtitleEdit.Features.Assa.AssaDraw; + +/// +/// Wraps (and with shrink to fit, sizes) text for a text area the way libass will show it, so the +/// canvas and the generated \q2 line agree. libass scales a font so that ascent + descent equals +/// \fs, and each line of text is \fs high. +/// +public static class AssaTextLayout +{ + public const float MinFontSize = 6; + private const float MeasureSize = 100; + + private static readonly object CacheLock = new(); + private static readonly Dictionary<(string Font, bool Bold, string Word), float> WidthCache = new(); + private static readonly Dictionary<(string Font, bool Bold), float> HeightCache = new(); + private static readonly Dictionary SkiaNameCache = new(); + + public sealed class Result + { + public List Lines { get; } + + /// + /// The \fs used (smaller than asked for when shrunk to fit). + /// + public float FontSize { get; } + + /// + /// Font em size that gives a line height of , for drawing the text. + /// + public float EmSize { get; } + + public Result(List lines, float fontSize, float emSize) + { + Lines = lines; + FontSize = fontSize; + EmSize = emSize; + } + } + + public static Result Layout(string text, string fontName, float fontSize, bool bold, float width, float height, bool shrinkToFit) + { + var size = MathF.Max(MinFontSize, MathF.Round(fontSize)); + var heightAt100 = MeasureHeight(fontName, bold); + while (true) + { + var scale = size / heightAt100; + var lines = Wrap(text, fontName, bold, width / scale, out var widest); + var fits = lines.Count * size <= height + 0.5f && widest * scale <= width + 0.5f; + if (!shrinkToFit || fits || size <= MinFontSize) + { + return new Result(lines, size, MeasureSize * scale); + } + + size = MathF.Max(MinFontSize, size - (size > 40 ? 2 : 1)); + } + } + + /// + /// Greedy word wrap; and are widths at the measuring size. + /// User line breaks (\n) are kept. A word longer than the line gets a line of its own. + /// + private static List Wrap(string text, string fontName, bool bold, float maxWidth, out float widest) + { + var lines = new List(); + widest = 0; + var spaceWidth = MeasureWord(" ", fontName, bold); + foreach (var paragraph in text.Replace("\r\n", "\n").Split('\n')) + { + var words = paragraph.Split(' ', StringSplitOptions.RemoveEmptyEntries); + if (words.Length == 0) + { + lines.Add(string.Empty); + continue; + } + + var line = words[0]; + var lineWidth = MeasureWord(words[0], fontName, bold); + for (var i = 1; i < words.Length; i++) + { + var wordWidth = MeasureWord(words[i], fontName, bold); + if (lineWidth + spaceWidth + wordWidth > maxWidth) + { + lines.Add(line); + widest = MathF.Max(widest, lineWidth); + line = words[i]; + lineWidth = wordWidth; + } + else + { + line += " " + words[i]; + lineWidth += spaceWidth + wordWidth; + } + } + + lines.Add(line); + widest = MathF.Max(widest, lineWidth); + } + + return lines; + } + + private static float MeasureWord(string word, string fontName, bool bold) + { + lock (CacheLock) + { + var key = (fontName, bold, word); + if (!WidthCache.TryGetValue(key, out var width)) + { + width = TextMeasurer.MeasureString(word, GetSkiaFontName(fontName), MeasureSize, bold ? SKFontStyleWeight.Bold : SKFontStyleWeight.Normal).Width; + if (WidthCache.Count > 20_000) + { + WidthCache.Clear(); + } + + WidthCache[key] = width; + } + + return width; + } + } + + private static float MeasureHeight(string fontName, bool bold) + { + lock (CacheLock) + { + if (!HeightCache.TryGetValue((fontName, bold), out var height)) + { + height = TextMeasurer.MeasureString("Xg", GetSkiaFontName(fontName), MeasureSize, bold ? SKFontStyleWeight.Bold : SKFontStyleWeight.Normal).Height; + if (height < 1) + { + height = MeasureSize * 1.15f; + } + + HeightCache[(fontName, bold)] = height; + } + + return height; + } + } + + /// + /// The family name Skia/Avalonia know for a libass (GDI) font name. + /// + public static string GetSkiaFontName(string fontName) + { + lock (CacheLock) + { + if (!SkiaNameCache.TryGetValue(fontName, out var name)) + { + try + { + name = FontHelper.GetSkiaFontNameFromLibAssaFontName(fontName); + } + catch + { + name = fontName; + } + + SkiaNameCache[fontName] = string.IsNullOrEmpty(name) ? fontName : name; + } + + return SkiaNameCache[fontName]; + } + } +} diff --git a/src/ui/Features/Assa/AssaDraw/DrawShape.cs b/src/ui/Features/Assa/AssaDraw/DrawShape.cs index 555dc66fdb3..2edad3120a4 100644 --- a/src/ui/Features/Assa/AssaDraw/DrawShape.cs +++ b/src/ui/Features/Assa/AssaDraw/DrawShape.cs @@ -16,12 +16,46 @@ public class DrawShape public List Points { get; set; } = []; public Color ForeColor { get; set; } = Colors.White; public Color OutlineColor { get; set; } = Colors.Black; - public int OutlineWidth { get; set; } + + /// + /// ASSA \bord. Drawing shapes share their layer's outline (one line per layer); a text box + /// outlines its own text. + /// + public float OutlineWidth { get; set; } public int Layer { get; set; } public bool IsEraser { get; set; } public bool Hidden { get; set; } public bool Expanded { get; set; } + /// + /// A text-only object: its rectangle is where the text goes, nothing is drawn for it. + /// + public bool IsTextBox { get; set; } + + /// + /// Text written inside the shape (a speech bubble) or the text box; "\n" = line break. + /// + public string Text { get; set; } = string.Empty; + public string FontName { get; set; } = "Arial"; + + /// + /// ASSA \fs - with the largest size, made smaller until the text fits. + /// + public float FontSize { get; set; } = 48; + public bool FontBold { get; set; } + public Color TextColor { get; set; } = Colors.Black; + public bool ShrinkToFit { get; set; } = true; + + /// + /// Where the text goes, as fractions of the shape's bounds - so moving and scaling the shape + /// takes the text area along (a bubble's body, not its tail). + /// + public (float Left, float Top, float Right, float Bottom) TextArea { get; set; } = DefaultTextArea; + + public static readonly (float Left, float Top, float Right, float Bottom) DefaultTextArea = (0.1f, 0.1f, 0.9f, 0.9f); + + public bool HasText => !IsEraser && !string.IsNullOrWhiteSpace(Text); + public DrawShape() { } @@ -35,6 +69,14 @@ public DrawShape(DrawShape other) IsEraser = other.IsEraser; Hidden = other.Hidden; Expanded = other.Expanded; + IsTextBox = other.IsTextBox; + Text = other.Text; + FontName = other.FontName; + FontSize = other.FontSize; + FontBold = other.FontBold; + TextColor = other.TextColor; + ShrinkToFit = other.ShrinkToFit; + TextArea = other.TextArea; foreach (var point in other.Points) { @@ -198,6 +240,55 @@ public bool HitTest(float x, float y, float tolerance) return (left, top, right, bottom); } + /// + /// The text area in canvas coordinates. + /// + public (float Left, float Top, float Right, float Bottom) GetTextBounds() + { + var (left, top, right, bottom) = GetBounds(); + var (l, t, r, b) = TextArea; + var width = right - left; + var height = bottom - top; + return (left + l * width, top + t * height, left + r * width, top + b * height); + } + + /// + /// Sets the text area from canvas coordinates (stored relative to the current bounds). + /// + public void SetTextBounds(float left, float top, float right, float bottom) + { + var (bl, bt, br, bb) = GetBounds(); + var width = br - bl; + var height = bb - bt; + if (width < 0.01f || height < 0.01f) + { + TextArea = DefaultTextArea; + return; + } + + TextArea = ((left - bl) / width, (top - bt) / height, (right - bl) / width, (bottom - bt) / height); + } + + /// + /// Keeps the text area on the same part of a mirrored shape (a bubble flipped upside down has + /// its body at the bottom). + /// + public void MirrorTextArea(bool horizontal, bool vertical) + { + var (l, t, r, b) = TextArea; + if (horizontal) + { + (l, r) = (1 - r, 1 - l); + } + + if (vertical) + { + (t, b) = (1 - b, 1 - t); + } + + TextArea = (l, t, r, b); + } + public void Offset(float x, float y) { foreach (var point in Points) @@ -225,6 +316,17 @@ public void Rotate(float degrees) point.X = MathF.Round(centerX + x * cos - y * sin, 2); point.Y = MathF.Round(centerY + x * sin + y * cos, 2); } + + // Quarter turns turn the text area with the shape; the text itself stays upright + var quarterTurns = (int)MathF.Round(degrees / 90f); + if (MathF.Abs(degrees - quarterTurns * 90f) < 0.01f) + { + for (var i = 0; i < ((quarterTurns % 4) + 4) % 4; i++) + { + var (l, t, r, b) = TextArea; + TextArea = (1 - b, l, 1 - t, r); + } + } } /// @@ -239,6 +341,7 @@ public void FlipHorizontal() point.X = left + right - point.X; } + MirrorTextArea(horizontal: true, vertical: false); ReverseWinding(); } @@ -250,6 +353,7 @@ public void FlipVertical() point.Y = top + bottom - point.Y; } + MirrorTextArea(horizontal: false, vertical: true); ReverseWinding(); } diff --git a/src/ui/Features/Assa/AssaDraw/DrawingTool.cs b/src/ui/Features/Assa/AssaDraw/DrawingTool.cs index 2e31131154f..5edde06b2b5 100644 --- a/src/ui/Features/Assa/AssaDraw/DrawingTool.cs +++ b/src/ui/Features/Assa/AssaDraw/DrawingTool.cs @@ -25,4 +25,7 @@ public enum DrawingTool /// Place a shape from the shape library Shape, + + /// Place a text box + Text, } diff --git a/src/ui/Features/Assa/AssaDraw/ShapeLibrary.cs b/src/ui/Features/Assa/AssaDraw/ShapeLibrary.cs index 1e42195edf1..f9e0c40dd8c 100644 --- a/src/ui/Features/Assa/AssaDraw/ShapeLibrary.cs +++ b/src/ui/Features/Assa/AssaDraw/ShapeLibrary.cs @@ -25,12 +25,19 @@ public sealed class ShapeLibraryItem /// public SeAssaDrawShape? UserShape { get; } - public ShapeLibraryItem(string name, string category, Func> build, SeAssaDrawShape? userShape = null) + /// + /// Where text goes (a speech bubble's body), in the shape's own coordinates; null = no text area. + /// + public (float Left, float Top, float Right, float Bottom)? TextArea { get; } + + public ShapeLibraryItem(string name, string category, Func> build, SeAssaDrawShape? userShape = null, + (float Left, float Top, float Right, float Bottom)? textArea = null) { Name = name; Category = category; _build = build; UserShape = userShape; + TextArea = textArea; } /// @@ -96,6 +103,17 @@ public List CreateShapes(float x, float y, float width, float height, result.Add(shape); } + // The text area belongs to the biggest part (the bubble, not the dots of a thought bubble) + if (TextArea is { } area && result.Count > 0) + { + var host = result.OrderByDescending(s => { var b = s.GetBounds(); return (b.Right - b.Left) * (b.Bottom - b.Top); }).First(); + host.SetTextBounds( + x + (area.Left - left) * scaleX, + y + (area.Top - top) * scaleY, + x + (area.Right - left) * scaleX, + y + (area.Bottom - top) * scaleY); + } + return result; } @@ -212,6 +230,16 @@ private static ShapeLibraryItem Item(string name, string category, string[] unio return new ShapeLibraryItem(name, category, () => SvgImporter.FromPathData(union, subtract)); } + /// + /// A shape with room for text, the text area given in the same 100-unit coordinates as the path data. + /// + private static ShapeLibraryItem TextItem(string name, string category, (float Left, float Top, float Right, float Bottom) textArea, string[] union) + { + // Path data is scaled by 10 when built (see SvgImporter.FromPathData) + var (l, t, r, b) = textArea; + return new ShapeLibraryItem(name, category, () => SvgImporter.FromPathData(union), textArea: (l * 10, t * 10, r * 10, b * 10)); + } + private static string F(double value) => value.ToString("0.##", CultureInfo.InvariantCulture); private static string Circle(double cx, double cy, double r) => @@ -244,19 +272,19 @@ private static List MakeBuiltIn() return [ // Speech bubbles - Item(l.DrawShapeSpeechBubble, bubbles, ["M12 0 H88 A12 12 0 0 1 100 12 V58 A12 12 0 0 1 88 70 H38 L14 94 L22 70 H12 A12 12 0 0 1 0 58 V12 A12 12 0 0 1 12 0 Z"]), - Item(l.DrawShapeSpeechBubbleRight, bubbles, ["M12 0 H88 A12 12 0 0 1 100 12 V58 A12 12 0 0 1 88 70 H78 L86 94 L62 70 H12 A12 12 0 0 1 0 58 V12 A12 12 0 0 1 12 0 Z"]), - Item(l.DrawShapeSpeechBubbleUp, bubbles, ["M12 24 H22 L30 0 L46 24 H88 A12 12 0 0 1 100 36 V82 A12 12 0 0 1 88 94 H12 A12 12 0 0 1 0 82 V36 A12 12 0 0 1 12 24 Z"]), - Item(l.DrawShapeOvalBubble, bubbles, ["M0 38 A50 38 0 1 1 100 38 A50 38 0 1 1 0 38 Z", "M22 62 L8 96 L44 72 Z"]), - Item(l.DrawShapeBoxBubble, bubbles, ["M0 0 H100 V70 H60 L50 94 L40 70 H0 Z"]), - Item(l.DrawShapeThoughtBubble, bubbles, + TextItem(l.DrawShapeSpeechBubble, bubbles, (8, 6, 92, 64), ["M12 0 H88 A12 12 0 0 1 100 12 V58 A12 12 0 0 1 88 70 H38 L14 94 L22 70 H12 A12 12 0 0 1 0 58 V12 A12 12 0 0 1 12 0 Z"]), + TextItem(l.DrawShapeSpeechBubbleRight, bubbles, (8, 6, 92, 64), ["M12 0 H88 A12 12 0 0 1 100 12 V58 A12 12 0 0 1 88 70 H78 L86 94 L62 70 H12 A12 12 0 0 1 0 58 V12 A12 12 0 0 1 12 0 Z"]), + TextItem(l.DrawShapeSpeechBubbleUp, bubbles, (8, 30, 92, 88), ["M12 24 H22 L30 0 L46 24 H88 A12 12 0 0 1 100 36 V82 A12 12 0 0 1 88 94 H12 A12 12 0 0 1 0 82 V36 A12 12 0 0 1 12 24 Z"]), + TextItem(l.DrawShapeOvalBubble, bubbles, (14, 11, 86, 65), ["M0 38 A50 38 0 1 1 100 38 A50 38 0 1 1 0 38 Z", "M22 62 L8 96 L44 72 Z"]), + TextItem(l.DrawShapeBoxBubble, bubbles, (6, 6, 94, 64), ["M0 0 H100 V70 H60 L50 94 L40 70 H0 Z"]), + TextItem(l.DrawShapeThoughtBubble, bubbles, (16, 16, 84, 64), [ Circle(26, 34, 20), Circle(46, 22, 22), Circle(68, 24, 20), Circle(84, 40, 16), Circle(70, 56, 18), Circle(46, 58, 20), Circle(22, 54, 17), Circle(50, 40, 26), Circle(18, 82, 7), Circle(8, 95, 4), ]), - Item(l.DrawShapeShoutBubble, bubbles, [Polygon(50, 50, 24, -90, 50, 36, 46, 34, 50, 38, 44, 35)]), - Item(l.DrawShapeCaptionBox, bubbles, ["M6 0 H94 A6 6 0 0 1 100 6 V24 A6 6 0 0 1 94 30 H6 A6 6 0 0 1 0 24 V6 A6 6 0 0 1 6 0 Z"]), + TextItem(l.DrawShapeShoutBubble, bubbles, (27, 30, 73, 70), [Polygon(50, 50, 24, -90, 50, 36, 46, 34, 50, 38, 44, 35)]), + TextItem(l.DrawShapeCaptionBox, bubbles, (4, 3, 96, 27), ["M6 0 H94 A6 6 0 0 1 100 6 V24 A6 6 0 0 1 94 30 H6 A6 6 0 0 1 0 24 V6 A6 6 0 0 1 6 0 Z"]), // Arrows Item(l.DrawShapeArrow, arrows, ["M0 35 H60 V15 L100 50 L60 85 V65 H0 Z"]), diff --git a/src/ui/Features/Main/MainViewModel.cs b/src/ui/Features/Main/MainViewModel.cs index e44f2f36d8c..6da0e1aff1c 100644 --- a/src/ui/Features/Main/MainViewModel.cs +++ b/src/ui/Features/Main/MainViewModel.cs @@ -2621,8 +2621,9 @@ private async Task ShowSsaAttachments() private static readonly Regex AssaTagBlockRegex = new(@"\{[^}]*\}", RegexOptions.Compiled); /// - /// A row the ASSA draw dialog imports as drawing: a \p1 drawing, or a mask-only line - /// (just an \iclip, no text) as the dialog writes for a layer of erase shapes. + /// A row the ASSA draw dialog imports as drawing: a \p1 drawing, a mask-only line + /// (just an \iclip, no text) as the dialog writes for a layer of erase shapes, or a text + /// line it wrote for a bubble or text box. /// internal static bool IsAssaDrawingText(string text) { @@ -2631,7 +2632,7 @@ internal static bool IsAssaDrawingText(string text) return false; } - if (AssaDrawingTagRegex.IsMatch(text)) + if (AssaDrawingTagRegex.IsMatch(text) || AssaDrawTextLine.IsTextLine(text)) { return true; } diff --git a/src/ui/Logic/Config/Language/Assa/LanguageAssa.cs b/src/ui/Logic/Config/Language/Assa/LanguageAssa.cs index ca3f4cab630..0247e5c7bb2 100644 --- a/src/ui/Logic/Config/Language/Assa/LanguageAssa.cs +++ b/src/ui/Logic/Config/Language/Assa/LanguageAssa.cs @@ -102,6 +102,11 @@ public class LanguageAssa public string DrawRotateCounterClockwise { get; set; } public string DrawSvgImages { get; set; } public string DrawSvgNoShapes { get; set; } + public string DrawTextTool { get; set; } + public string DrawDefaultText { get; set; } + public string DrawShrinkToFit { get; set; } + public string DrawTextColor { get; set; } + public string DrawTextBox { get; set; } // Progress Bar Generator public string ProgressBarTitle { get; set; } @@ -436,6 +441,11 @@ public LanguageAssa() DrawRotateCounterClockwise = "Rotate 90° counter-clockwise"; DrawSvgImages = "SVG images"; DrawSvgNoShapes = "No filled shapes or strokes found in the SVG file."; + DrawTextTool = "Text tool"; + DrawDefaultText = "Text"; + DrawShrinkToFit = "Shrink to fit"; + DrawTextColor = "Text color"; + DrawTextBox = "Text box"; // Progress Bar Generator ProgressBarTitle = "ASSA progress bar"; diff --git a/tests/UI/Features/Assa/AssaDrawTextTests.cs b/tests/UI/Features/Assa/AssaDrawTextTests.cs new file mode 100644 index 00000000000..1c64b215558 --- /dev/null +++ b/tests/UI/Features/Assa/AssaDrawTextTests.cs @@ -0,0 +1,271 @@ +using Avalonia.Media; +using Nikse.SubtitleEdit.Core.Common; +using Nikse.SubtitleEdit.Core.SubtitleFormats; +using Nikse.SubtitleEdit.Features.Assa.AssaDraw; +using Nikse.SubtitleEdit.Features.Main; +using Nikse.SubtitleEdit.Logic.Config; +using Nikse.SubtitleEdit.Logic.Media; + +namespace UITests.Features.Assa; + +/// +/// ASSA draw outlines, text boxes and text in shapes (speech bubbles): written as ASSA and read back. +/// +public class AssaDrawTextTests +{ + private static AssaDrawViewModel MakeViewModel(params Paragraph[] paragraphs) + { + var vm = new AssaDrawViewModel(new FileHelper(), new StubWindowService()); + var lines = paragraphs.Select(p => new SubtitleLineViewModel(p, new AdvancedSubStationAlpha())).ToList(); + vm.Initialize(new Subtitle { Header = AdvancedSubStationAlpha.DefaultHeader }, lines, 1920, 1080); + return vm; + } + + private static Subtitle Ok(AssaDrawViewModel vm) + { + vm.OkCommand.Execute(null); + return vm.ResultSubtitle; + } + + /// + /// Opens the dialog's result again, like selecting the written rows and opening ASSA draw. + /// + private static AssaDrawViewModel Reopen(Subtitle result) + { + var vm = new AssaDrawViewModel(new FileHelper(), new StubWindowService()); + var lines = result.Paragraphs.Select(p => new SubtitleLineViewModel(p, new AdvancedSubStationAlpha())).ToList(); + vm.Initialize(result, lines, 1920, 1080); + return vm; + } + + [Fact] + public void Outline_IsWrittenOnTheLayerLine_AndReadBack() + { + var vm = MakeViewModel(new Paragraph("{\\p1}m 0 0 l 100 0 100 100 0 100{\\p0}", 0, 2000) { Extra = "Default" }); + vm.SelectShape(vm.Shapes[0]); + vm.ShapeOutlineWidth = 3.5f; + vm.ShapeOutlineColor = Color.FromRgb(0x11, 0x22, 0x33); + + var line = Assert.Single(Ok(vm).Paragraphs); + Assert.Contains("\\bord3.5\\3c&H332211&\\p1}", line.Text); + Assert.Contains("\\bord3.5", vm.AssaDrawingCode); + + var reopened = Reopen(vm.ResultSubtitle); + Assert.Equal(3.5f, reopened.Shapes[0].OutlineWidth); + Assert.Equal(Color.FromRgb(0x11, 0x22, 0x33), reopened.Shapes[0].OutlineColor); + } + + [Fact] + public void NoOutline_WritesNoBordTag() + { + var vm = MakeViewModel(new Paragraph("{\\p1}m 0 0 l 100 0 100 100{\\p0}", 0, 2000) { Extra = "Default" }); + var line = Assert.Single(Ok(vm).Paragraphs); + Assert.DoesNotContain("\\bord", line.Text); + } + + [Fact] + public void OutlineOfALayer_AppliesToAllItsShapes() + { + var vm = MakeViewModel(new Paragraph("{\\p1}m 0 0 l 100 0 100 100 m 200 0 l 300 0 300 100{\\p0}", 0, 2000) { Extra = "Default" }); + Assert.Equal(2, vm.Shapes.Count); + vm.SelectShape(vm.Shapes[1]); + vm.ShapeOutlineWidth = 2; + + Assert.All(vm.Shapes, s => Assert.Equal(2, s.OutlineWidth)); + } + + [Fact] + public void TextBox_IsWrittenAsCenteredTextLine_AndReopensAsTextBox() + { + var vm = MakeViewModel(); + vm.LayerColor = Colors.Yellow; + var textBox = vm.InsertTextBox(new CanvasInsertEventArgs(100, 200, 800, 200, isClick: false)); + Assert.True(textBox.IsTextBox); + Assert.Same(textBox, vm.SelectedTreeItem?.Shape); + Assert.Equal(Se.Language.Assa.DrawDefaultText, vm.ShapeText); + + vm.ShapeText = "Hello\nworld"; + vm.TextBold = true; + vm.TextShrinkToFit = false; + vm.TextFontSize = 40; + + var result = Ok(vm); + var line = Assert.Single(result.Paragraphs); + Assert.Equal(AssaDrawTextLine.StyleName, line.Extra); + Assert.StartsWith("{\\an5\\pos(500,300)\\q2\\fnArial\\fs40\\b1\\c&H00FFFF&\\bord2\\3c&H000000&\\shad0}", line.Text); + Assert.EndsWith("}Hello\\Nworld", line.Text); + Assert.Contains(AdvancedSubStationAlpha.GetSsaStylesFromHeader(result.Header), s => s.Name == AssaDrawTextLine.StyleName); + Assert.True(MainViewModel.IsAssaDrawingText(line.Text)); + + var reopened = Reopen(result); + var shape = Assert.Single(reopened.Shapes); + Assert.True(shape.IsTextBox); + Assert.Equal("Hello\nworld", shape.Text); + Assert.True(shape.FontBold); + Assert.False(shape.ShrinkToFit); + Assert.Equal(40, shape.FontSize); + Assert.Equal(Colors.Yellow, shape.TextColor); + Assert.Equal(2, shape.OutlineWidth); + var (left, top, right, bottom) = shape.GetTextBounds(); + Assert.Equal(100, left, 1); + Assert.Equal(200, top, 1); + Assert.Equal(900, right, 1); + Assert.Equal(400, bottom, 1); + } + + [Fact] + public void TextBox_DrawsNothing_SoTheResultHasOnlyTheTextLine() + { + var vm = MakeViewModel(); + vm.InsertTextBox(new CanvasInsertEventArgs(960, 540, 0, 0, isClick: true)); + + var result = Ok(vm); + Assert.DoesNotContain(result.Paragraphs, p => p.Text.Contains("\\p1")); + Assert.DoesNotContain(AdvancedSubStationAlpha.GetSsaStylesFromHeader(result.Header), s => s.Name.StartsWith("AssaDraw", StringComparison.Ordinal) && s.Name != AssaDrawTextLine.StyleName); + } + + [Fact] + public void SpeechBubble_TextGoesInTheBody_AfterTheDrawing_OnTheSameLayer() + { + var vm = MakeViewModel(); + vm.LayerColor = Colors.White; + vm.CurrentLibraryShape = ShapeLibrary.BuiltIn.First(i => i.Name == Se.Language.Assa.DrawShapeSpeechBubble); + vm.InsertLibraryShape(new CanvasInsertEventArgs(100, 100, 1000, 940, isClick: false)); + var bubble = Assert.Single(vm.Shapes); + Assert.Same(bubble, vm.SelectedTreeItem?.Shape); + + vm.ShapeText = "Hi there"; + Assert.Equal(Colors.Black, bubble.TextColor); + + // The body is the top 70 of the 94 high bubble: text area 6..64 -> 160..740 + var (_, textTop, _, textBottom) = bubble.GetTextBounds(); + Assert.Equal(160, textTop, 0); + Assert.Equal(740, textBottom, 0); + + var result = Ok(vm); + Assert.Equal(2, result.Paragraphs.Count); + Assert.Contains("\\p1", result.Paragraphs[0].Text); + Assert.True(AssaDrawTextLine.IsTextLine(result.Paragraphs[1].Text)); + Assert.Equal(result.Paragraphs[0].Layer, result.Paragraphs[1].Layer); + Assert.Contains("\\pos(600,450)", result.Paragraphs[1].Text); + Assert.Contains("\\bord0", result.Paragraphs[1].Text); + + // Opened again, the text is the bubble's text, not a separate text box + var reopened = Reopen(result); + var shape = Assert.Single(reopened.Shapes); + Assert.False(shape.IsTextBox); + Assert.Equal("Hi there", shape.Text); + Assert.Equal(160, shape.GetTextBounds().Top, 0); + } + + [Fact] + public void ThoughtBubble_TextAreaIsOnTheBigPart() + { + var item = ShapeLibrary.BuiltIn.First(i => i.Name == Se.Language.Assa.DrawShapeThoughtBubble); + var shapes = item.CreateShapes(0, 0, 1000, 1000, Colors.White, 0); + var host = shapes.OrderByDescending(s => { var b = s.GetBounds(); return (b.Right - b.Left) * (b.Bottom - b.Top); }).First(); + Assert.NotEqual(DrawShape.DefaultTextArea, host.TextArea); + Assert.All(shapes.Where(s => s != host), s => Assert.Equal(DrawShape.DefaultTextArea, s.TextArea)); + } + + [Fact] + public void ShrinkToFit_MakesLongTextSmaller_AndWraps() + { + var text = string.Join(' ', Enumerable.Repeat("word", 40)); + var big = AssaTextLayout.Layout(text, "Arial", 80, false, 400, 200, shrinkToFit: false); + var fitted = AssaTextLayout.Layout(text, "Arial", 80, false, 400, 200, shrinkToFit: true); + + Assert.Equal(80, big.FontSize); + Assert.True(big.Lines.Count * big.FontSize > 200); + Assert.True(fitted.FontSize < 80); + Assert.True(fitted.Lines.Count > 1); + Assert.True(fitted.Lines.Count * fitted.FontSize <= 200.5f); + } + + [Fact] + public void WrappedLines_UseSoftBreaks_UserBreaksStayHard_AndReadBackAsTyped() + { + var shape = new DrawShape { IsTextBox = true, TextArea = (0, 0, 1, 1), ShrinkToFit = false, FontSize = 40 }; + shape.AddPoint(DrawCoordinateType.Line, 0, 0, Colors.White); + shape.AddPoint(DrawCoordinateType.Line, 300, 0, Colors.White); + shape.AddPoint(DrawCoordinateType.Line, 300, 400, Colors.White); + shape.AddPoint(DrawCoordinateType.Line, 0, 400, Colors.White); + shape.Text = "one two three four five six seven eight\nnine"; + + var line = AssaDrawTextLine.Format(shape); + Assert.Contains("\\n", line); + Assert.EndsWith("\\Nnine", line); + + Assert.True(AssaDrawTextLine.TryParse(line, out var parsed)); + Assert.Equal(shape.Text, parsed.Text); + } + + [Fact] + public void BracesAndBackslashes_InText_DoNotBecomeTags() + { + var shape = new DrawShape { IsTextBox = true, TextArea = (0, 0, 1, 1), Text = "{\\i1}x" }; + shape.AddPoint(DrawCoordinateType.Line, 0, 0, Colors.White); + shape.AddPoint(DrawCoordinateType.Line, 2000, 0, Colors.White); + shape.AddPoint(DrawCoordinateType.Line, 2000, 400, Colors.White); + shape.AddPoint(DrawCoordinateType.Line, 0, 400, Colors.White); + + var line = AssaDrawTextLine.Format(shape); + Assert.DoesNotContain("{\\i1}", line); + Assert.True(AssaDrawTextLine.TryParse(line, out var parsed)); + Assert.Equal("(\\i1)x", parsed.Text); + } + + [Fact] + public void MovedInTheGrid_PosWinsOverTheStoredTextArea() + { + const string text = "{\\an5\\pos(1000,600)\\q2\\fnArial\\fs40\\c&HFFFFFF&\\bord0\\shad0}{assadraw-text box=100,200,300,400}Hi"; + Assert.True(AssaDrawTextLine.TryParse(text, out var parsed)); + Assert.Equal((900f, 500f, 1100f, 700f), parsed.Box); + } + + [Fact] + public void FlipAndRotate_KeepTheTextAreaOnTheBody() + { + var shape = new DrawShape { TextArea = (0.1f, 0.0f, 0.9f, 0.7f) }; + shape.AddPoint(DrawCoordinateType.Line, 0, 0, Colors.White); + shape.AddPoint(DrawCoordinateType.Line, 100, 0, Colors.White); + shape.AddPoint(DrawCoordinateType.Line, 100, 100, Colors.White); + + shape.FlipVertical(); + Assert.Equal((0.1f, 0.3f, 0.9f, 1f), shape.TextArea, new AreaComparer()); + + shape.FlipVertical(); + shape.Rotate(90); + Assert.Equal((0.3f, 0.1f, 1f, 0.9f), shape.TextArea, new AreaComparer()); + } + + [Fact] + public void TextInAPlainShape_IsSupportedToo() + { + var vm = MakeViewModel(new Paragraph("{\\p1}m 0 0 l 400 0 400 200 0 200{\\p0}", 0, 2000) { Extra = "Default" }); + vm.SelectShape(vm.Shapes[0]); + Assert.True(vm.CanEditText); + vm.ShapeText = "Sign"; + + var result = Ok(vm); + Assert.Equal(2, result.Paragraphs.Count); + Assert.Contains("\\pos(200,100)", result.Paragraphs[1].Text); + } + + [Fact] + public void EraserShape_CantHaveText() + { + var vm = MakeViewModel(new Paragraph("{\\iclip(m 0 0 l 100 0 100 100)}", 0, 2000) { Extra = "Default" }); + vm.SelectShape(vm.Shapes[0]); + Assert.False(vm.CanEditText); + } + + private sealed class AreaComparer : IEqualityComparer<(float, float, float, float)> + { + public bool Equals((float, float, float, float) x, (float, float, float, float) y) => + Math.Abs(x.Item1 - y.Item1) < 0.001f && Math.Abs(x.Item2 - y.Item2) < 0.001f && + Math.Abs(x.Item3 - y.Item3) < 0.001f && Math.Abs(x.Item4 - y.Item4) < 0.001f; + + public int GetHashCode((float, float, float, float) obj) => 0; + } +} From d2237bf2b943f224008f08c7b11a62f0a71b3953 Mon Sep 17 00:00:00 2001 From: niksedk Date: Tue, 13 Oct 2026 22:56:36 +0200 Subject: [PATCH 119/306] ASSA draw: text rotates with its shape (\frz) The text area is now a frame (center, size, angle) that follows every transform of the shape: move, nudge, resize, handles, rotate and flips. Rotation is written as \frz around \pos and read back; flips keep the text readable. The canvas draws the text and its guide rotated. Co-Authored-By: Claude Opus 5.5 --- .../Features/Assa/AssaDraw/AssaDrawCanvas.cs | 42 +++++-- .../Assa/AssaDraw/AssaDrawTextLine.cs | 18 +++ .../Assa/AssaDraw/AssaDrawViewModel.cs | 25 ++-- src/ui/Features/Assa/AssaDraw/DrawShape.cs | 118 +++++++++++------- tests/UI/Features/Assa/AssaDrawTextTests.cs | 90 ++++++++++--- 5 files changed, 200 insertions(+), 93 deletions(-) diff --git a/src/ui/Features/Assa/AssaDraw/AssaDrawCanvas.cs b/src/ui/Features/Assa/AssaDraw/AssaDrawCanvas.cs index 870a199f615..be7c877d41f 100644 --- a/src/ui/Features/Assa/AssaDraw/AssaDrawCanvas.cs +++ b/src/ui/Features/Assa/AssaDraw/AssaDrawCanvas.cs @@ -38,7 +38,7 @@ public class AssaDrawCanvas : Control private List? _transformShapes; private List<(float X, float Y)[]>? _transformOriginal; private bool[]? _transformReversed; - private (float Left, float Top, float Right, float Bottom)[]? _transformTextAreas; + private DrawTextFrame?[]? _transformTextFrames; private (float Left, float Top, float Right, float Bottom) _transformBounds; private Point _transformStart; private bool _transformStarted; @@ -628,8 +628,11 @@ private void DrawShape(DrawingContext context, DrawShape shape, bool isActive, b { // Where the text of a bubble goes var (textLeft, textTop, textRight, textBottom) = shape.GetTextBounds(); - context.DrawRectangle(null, GetPen(GuideColor, 1, dashed: true), - new Rect(new Point(ToZoomFactorX(textLeft), ToZoomFactorY(textTop)), new Point(ToZoomFactorX(textRight), ToZoomFactorY(textBottom)))); + using (PushTextRotation(context, shape.GetTextFrame())) + { + context.DrawRectangle(null, GetPen(GuideColor, 1, dashed: true), + new Rect(new Point(ToZoomFactorX(textLeft), ToZoomFactorY(textTop)), new Point(ToZoomFactorX(textRight), ToZoomFactorY(textBottom)))); + } } IBrush? fillBrush = null; @@ -735,6 +738,7 @@ private void DrawShapeText(DrawingContext context, DrawShape shape) var outlinePen = shape.IsTextBox && shape.OutlineWidth > 0 ? GetPen(shape.OutlineColor, shape.OutlineWidth * 2 * _zoomFactor, lineJoin: PenLineJoin.Round) : null; + using var rotation = PushTextRotation(context, shape.GetTextFrame()); for (var i = 0; i < layout.Lines.Count; i++) { if (layout.Lines[i].Length == 0) @@ -753,6 +757,24 @@ private void DrawShapeText(DrawingContext context, DrawShape shape) } } + /// + /// Turns what is drawn next around the text area's center, like \frz does around \pos. + /// + private DrawingContext.PushedState? PushTextRotation(DrawingContext context, DrawTextFrame frame) + { + if (Math.Abs(frame.Angle) < 0.01f) + { + return null; + } + + var centerX = ToZoomFactorX(frame.CenterX); + var centerY = ToZoomFactorY(frame.CenterY); + return context.PushTransform( + Matrix.CreateTranslation(-centerX, -centerY) * + Matrix.CreateRotation(frame.Angle * Math.PI / 180) * + Matrix.CreateTranslation(centerX, centerY)); + } + private StreamGeometry BuildLayerGeometry(List shapes) { var geometry = new StreamGeometry(); @@ -915,8 +937,6 @@ private void ApplyTransform(Point screenPoint, KeyModifiers modifiers) Func map; var mirrored = false; - var mirrorX = false; - var mirrorY = false; if (_transformHandle == RotateHandle) { var startAngle = Math.Atan2(FromZoomFactorY((float)_transformStart.Y) - centerY, FromZoomFactorX((float)_transformStart.X) - centerX); @@ -973,8 +993,6 @@ private void ApplyTransform(Point screenPoint, KeyModifiers modifiers) var sy = scaleY; map = (x, y) => (anchorX + (x - anchorX) * sx, anchorY + (y - anchorY) * sy); mirrored = sx * sy < 0; - mirrorX = sx < 0; - mirrorY = sy < 0; _transformLabel = $"{Math.Abs(width * sx):0} × {Math.Abs(height * sy):0}"; } @@ -992,9 +1010,9 @@ private void ApplyTransform(Point screenPoint, KeyModifiers modifiers) _transformReversed[i] = mirrored; } - // Dragging an edge past the opposite one mirrors the shape - its text area goes along - shape.TextArea = _transformTextAreas![i]; - shape.MirrorTextArea(mirrorX, mirrorY); + // The text area turns, scales and mirrors with the shape + shape.TextFrame = _transformTextFrames![i]; + shape.TransformText(map, mirrored); var points = shape.Points; if (points.Count != original.Length) @@ -1116,7 +1134,7 @@ protected override void OnPointerPressed(PointerPressedEventArgs e) _transformShapes = targets; _transformOriginal = targets.Select(s => s.Points.Select(p => (p.X, p.Y)).ToArray()).ToList(); _transformReversed = new bool[targets.Count]; - _transformTextAreas = targets.Select(s => s.TextArea).ToArray(); + _transformTextFrames = targets.Select(s => s.TextFrame).ToArray(); _transformBounds = GetUnionBounds(targets); _transformStart = point; _transformStarted = false; @@ -1341,7 +1359,7 @@ protected override void OnPointerReleased(PointerReleasedEventArgs e) _transformShapes = null; _transformOriginal = null; _transformReversed = null; - _transformTextAreas = null; + _transformTextFrames = null; _transformHandle = -1; _transformStarted = false; _transformLabel = null; diff --git a/src/ui/Features/Assa/AssaDraw/AssaDrawTextLine.cs b/src/ui/Features/Assa/AssaDraw/AssaDrawTextLine.cs index 61481526521..6749613a4ee 100644 --- a/src/ui/Features/Assa/AssaDraw/AssaDrawTextLine.cs +++ b/src/ui/Features/Assa/AssaDraw/AssaDrawTextLine.cs @@ -26,6 +26,7 @@ public static class AssaDrawTextLine private static readonly Regex TagBlockRegex = new(@"\{[^}]*\}", RegexOptions.Compiled); private static readonly Regex PosRegex = new(@"\\pos\(\s*(-?[\d.]+)\s*,\s*(-?[\d.]+)\s*\)", RegexOptions.Compiled); private static readonly Regex FontNameRegex = new(@"\\fn([^\\}]+)", RegexOptions.Compiled); + private static readonly Regex RotationRegex = new(@"\\frz?(-?[\d.]+)", RegexOptions.Compiled); private static readonly Regex FontSizeRegex = new(@"\\fs([\d.]+)", RegexOptions.Compiled); private static readonly Regex BoldRegex = new(@"\\b(\d+)", RegexOptions.Compiled); private static readonly Regex PrimaryColorRegex = new(@"\\1?c&H([0-9a-fA-F]+)&?", RegexOptions.Compiled); @@ -40,6 +41,11 @@ public sealed class Parsed public (float Left, float Top, float Right, float Bottom) Box { get; set; } public string FontName { get; set; } = "Arial"; public float FontSize { get; set; } = 48; + + /// + /// Degrees clockwise on screen (\frz is counter-clockwise). + /// + public float Angle { get; set; } public bool Bold { get; set; } public bool ShrinkToFit { get; set; } public Color? Color { get; set; } @@ -58,10 +64,17 @@ public sealed class Parsed public static string Format(DrawShape shape) { var (left, top, right, bottom) = shape.GetTextBounds(); + var angle = shape.GetTextFrame().Angle; var layout = AssaTextLayout.Layout(shape.Text, shape.FontName, shape.FontSize, shape.FontBold, right - left, bottom - top, shape.ShrinkToFit); var sb = new StringBuilder(); sb.Append(CultureInfo.InvariantCulture, $"{{\\an5\\pos({N((left + right) / 2)},{N((top + bottom) / 2)})\\q2\\fn{shape.FontName}\\fs{N(layout.FontSize)}"); + if (Math.Abs(angle) >= 0.01f) + { + // Turns around \pos (the center) - the text area is stored unrotated + sb.Append(CultureInfo.InvariantCulture, $"\\frz{N(-angle)}"); + } + if (shape.FontBold) { sb.Append("\\b1"); @@ -167,6 +180,11 @@ public static bool TryParse(string text, out Parsed result) result.FontSize = F(fontSize.Groups[1].Value); } + if (RotationRegex.Match(tags) is { Success: true } rotation) + { + result.Angle = -F(rotation.Groups[1].Value); + } + result.Bold = BoldRegex.Match(tags) is { Success: true } bold && bold.Groups[1].Value != "0"; if (PrimaryColorRegex.Match(tags) is { Success: true } color) diff --git a/src/ui/Features/Assa/AssaDraw/AssaDrawViewModel.cs b/src/ui/Features/Assa/AssaDraw/AssaDrawViewModel.cs index f4371783f0b..736d92bcf10 100644 --- a/src/ui/Features/Assa/AssaDraw/AssaDrawViewModel.cs +++ b/src/ui/Features/Assa/AssaDraw/AssaDrawViewModel.cs @@ -346,7 +346,7 @@ public DrawShape InsertTextBox(CanvasInsertEventArgs e) var layer = Shapes.Count == 0 ? 0 : Shapes.Max(s => s.Layer) + 1; var shape = MakeRectangle(x, y, width, height, layer, LayerColor); shape.IsTextBox = true; - shape.TextArea = (0, 0, 1, 1); + shape.SetTextBounds(x, y, x + width, y + height); shape.Text = Se.Language.Assa.DrawDefaultText; shape.TextColor = LayerColor; shape.FontSize = DefaultFontSize(16f); @@ -712,6 +712,8 @@ private void ResizeTargetShape(float? width, float? height) point.Y = top + (point.Y - top) * scaleY; } + shape.TransformText((x, y) => (left + (x - left) * scaleX, top + (y - top) * scaleY), mirrored: false); + RefreshTreeView(); Canvas?.InvalidateVisual(); } @@ -2623,6 +2625,7 @@ private void AttachTextLines() { host = MakeRectangle(left, top, right - left, bottom - top, layer, parsed.Color ?? Colors.White); host.IsTextBox = true; + host.Rotate(parsed.Angle); host.OutlineWidth = parsed.OutlineWidth; host.OutlineColor = parsed.OutlineColor; Shapes.Add(host); @@ -2634,7 +2637,7 @@ private void AttachTextLines() host.FontBold = parsed.Bold; host.ShrinkToFit = parsed.ShrinkToFit; host.TextColor = parsed.Color ?? Colors.White; - host.SetTextBounds(left, top, right, bottom); + host.SetTextBounds(left, top, right, bottom, parsed.Angle); } _importedTexts.Clear(); @@ -2897,11 +2900,7 @@ private void AdjustPosition(float xAdjust, float yAdjust) { foreach (var shape in SelectedShapes) { - foreach (var point in shape.Points) - { - point.X += xAdjust; - point.Y += yAdjust; - } + shape.Offset(xAdjust, yAdjust); } RefreshTreeView(); Canvas?.InvalidateVisual(); @@ -2911,11 +2910,7 @@ private void AdjustPosition(float xAdjust, float yAdjust) // Check if a shape is selected in the tree view if (SelectedTreeItem?.Shape != null) { - foreach (var point in SelectedTreeItem.Shape.Points) - { - point.X += xAdjust; - point.Y += yAdjust; - } + SelectedTreeItem.Shape.Offset(xAdjust, yAdjust); RefreshTreeView(); Canvas?.InvalidateVisual(); return; @@ -2924,11 +2919,7 @@ private void AdjustPosition(float xAdjust, float yAdjust) // Fall back to active shape (currently being drawn) if (ActiveShape != null) { - foreach (var point in ActiveShape.Points) - { - point.X += xAdjust; - point.Y += yAdjust; - } + ActiveShape.Offset(xAdjust, yAdjust); RefreshTreeView(); Canvas?.InvalidateVisual(); } diff --git a/src/ui/Features/Assa/AssaDraw/DrawShape.cs b/src/ui/Features/Assa/AssaDraw/DrawShape.cs index 2edad3120a4..704a251e118 100644 --- a/src/ui/Features/Assa/AssaDraw/DrawShape.cs +++ b/src/ui/Features/Assa/AssaDraw/DrawShape.cs @@ -47,12 +47,10 @@ public class DrawShape public bool ShrinkToFit { get; set; } = true; /// - /// Where the text goes, as fractions of the shape's bounds - so moving and scaling the shape - /// takes the text area along (a bubble's body, not its tail). + /// Where the text goes (a bubble's body, not its tail), in canvas coordinates. It is moved, + /// scaled, rotated and mirrored with the shape. Null = inside the shape's bounds. /// - public (float Left, float Top, float Right, float Bottom) TextArea { get; set; } = DefaultTextArea; - - public static readonly (float Left, float Top, float Right, float Bottom) DefaultTextArea = (0.1f, 0.1f, 0.9f, 0.9f); + public DrawTextFrame? TextFrame { get; set; } public bool HasText => !IsEraser && !string.IsNullOrWhiteSpace(Text); @@ -76,7 +74,7 @@ public DrawShape(DrawShape other) FontBold = other.FontBold; TextColor = other.TextColor; ShrinkToFit = other.ShrinkToFit; - TextArea = other.TextArea; + TextFrame = other.TextFrame; foreach (var point in other.Points) { @@ -241,52 +239,82 @@ public bool HitTest(float x, float y, float tolerance) } /// - /// The text area in canvas coordinates. + /// The text area: the one set, or else the middle 80% of the bounds. /// - public (float Left, float Top, float Right, float Bottom) GetTextBounds() + public DrawTextFrame GetTextFrame() { + if (TextFrame is { } frame) + { + return frame; + } + var (left, top, right, bottom) = GetBounds(); - var (l, t, r, b) = TextArea; - var width = right - left; - var height = bottom - top; - return (left + l * width, top + t * height, left + r * width, top + b * height); + return new DrawTextFrame((left + right) / 2, (top + bottom) / 2, (right - left) * 0.8f, (bottom - top) * 0.8f, 0); } /// - /// Sets the text area from canvas coordinates (stored relative to the current bounds). + /// The text area before rotation (rotated by around its center). /// - public void SetTextBounds(float left, float top, float right, float bottom) + public (float Left, float Top, float Right, float Bottom) GetTextBounds() { - var (bl, bt, br, bb) = GetBounds(); - var width = br - bl; - var height = bb - bt; - if (width < 0.01f || height < 0.01f) - { - TextArea = DefaultTextArea; - return; - } + var frame = GetTextFrame(); + return (frame.CenterX - frame.Width / 2, frame.CenterY - frame.Height / 2, frame.CenterX + frame.Width / 2, frame.CenterY + frame.Height / 2); + } - TextArea = ((left - bl) / width, (top - bt) / height, (right - bl) / width, (bottom - bt) / height); + public void SetTextBounds(float left, float top, float right, float bottom, float angle = 0) + { + TextFrame = new DrawTextFrame((left + right) / 2, (top + bottom) / 2, Math.Abs(right - left), Math.Abs(bottom - top), angle); } /// - /// Keeps the text area on the same part of a mirrored shape (a bubble flipped upside down has - /// its body at the bottom). + /// Moves the text area along with a transform of the points. A mirror keeps the text readable: + /// of the two possible directions the one closest to the old angle is used. /// - public void MirrorTextArea(bool horizontal, bool vertical) + public void TransformText(Func map, bool mirrored) { - var (l, t, r, b) = TextArea; - if (horizontal) + if (TextFrame is not { } frame) + { + return; + } + + var radians = frame.Angle * MathF.PI / 180f; + var ux = MathF.Cos(radians) * frame.Width / 2; + var uy = MathF.Sin(radians) * frame.Width / 2; + var vx = -MathF.Sin(radians) * frame.Height / 2; + var vy = MathF.Cos(radians) * frame.Height / 2; + var center = map(frame.CenterX, frame.CenterY); + var u = map(frame.CenterX + ux, frame.CenterY + uy); + var v = map(frame.CenterX + vx, frame.CenterY + vy); + var newUx = u.X - center.X; + var newUy = u.Y - center.Y; + var newVx = v.X - center.X; + var newVy = v.Y - center.Y; + + var angle = frame.Width > 0.001f ? MathF.Atan2(newUy, newUx) * 180f / MathF.PI : frame.Angle; + if (mirrored && Math.Abs(NormalizeAngle(angle + 180 - frame.Angle)) < Math.Abs(NormalizeAngle(angle - frame.Angle))) { - (l, r) = (1 - r, 1 - l); + angle += 180; } - if (vertical) + TextFrame = new DrawTextFrame(center.X, center.Y, + 2 * MathF.Sqrt(newUx * newUx + newUy * newUy), + 2 * MathF.Sqrt(newVx * newVx + newVy * newVy), + MathF.Round(NormalizeAngle(angle), 2)); + } + + private static float NormalizeAngle(float degrees) + { + degrees %= 360; + if (degrees > 180) + { + degrees -= 360; + } + else if (degrees <= -180) { - (t, b) = (1 - b, 1 - t); + degrees += 360; } - TextArea = (l, t, r, b); + return degrees; } public void Offset(float x, float y) @@ -296,6 +324,11 @@ public void Offset(float x, float y) point.X += x; point.Y += y; } + + if (TextFrame is { } frame) + { + TextFrame = frame with { CenterX = frame.CenterX + x, CenterY = frame.CenterY + y }; + } } /// @@ -317,16 +350,7 @@ public void Rotate(float degrees) point.Y = MathF.Round(centerY + x * sin + y * cos, 2); } - // Quarter turns turn the text area with the shape; the text itself stays upright - var quarterTurns = (int)MathF.Round(degrees / 90f); - if (MathF.Abs(degrees - quarterTurns * 90f) < 0.01f) - { - for (var i = 0; i < ((quarterTurns % 4) + 4) % 4; i++) - { - var (l, t, r, b) = TextArea; - TextArea = (1 - b, l, 1 - t, r); - } - } + TransformText((x, y) => (centerX + (x - centerX) * cos - (y - centerY) * sin, centerY + (x - centerX) * sin + (y - centerY) * cos), mirrored: false); } /// @@ -341,7 +365,7 @@ public void FlipHorizontal() point.X = left + right - point.X; } - MirrorTextArea(horizontal: true, vertical: false); + TransformText((x, y) => (left + right - x, y), mirrored: true); ReverseWinding(); } @@ -353,7 +377,7 @@ public void FlipVertical() point.Y = top + bottom - point.Y; } - MirrorTextArea(horizontal: false, vertical: true); + TransformText((x, y) => (x, top + bottom - y), mirrored: true); ReverseWinding(); } @@ -641,3 +665,9 @@ private static float DistanceToSegment(float px, float py, float ax, float ay, f return MathF.Sqrt(cx * cx + cy * cy); } } + +/// +/// A text area: center and size in canvas coordinates, turned degrees +/// clockwise (on screen) around the center. +/// +public readonly record struct DrawTextFrame(float CenterX, float CenterY, float Width, float Height, float Angle); diff --git a/tests/UI/Features/Assa/AssaDrawTextTests.cs b/tests/UI/Features/Assa/AssaDrawTextTests.cs index 1c64b215558..9789dd17b8d 100644 --- a/tests/UI/Features/Assa/AssaDrawTextTests.cs +++ b/tests/UI/Features/Assa/AssaDrawTextTests.cs @@ -164,8 +164,8 @@ public void ThoughtBubble_TextAreaIsOnTheBigPart() var item = ShapeLibrary.BuiltIn.First(i => i.Name == Se.Language.Assa.DrawShapeThoughtBubble); var shapes = item.CreateShapes(0, 0, 1000, 1000, Colors.White, 0); var host = shapes.OrderByDescending(s => { var b = s.GetBounds(); return (b.Right - b.Left) * (b.Bottom - b.Top); }).First(); - Assert.NotEqual(DrawShape.DefaultTextArea, host.TextArea); - Assert.All(shapes.Where(s => s != host), s => Assert.Equal(DrawShape.DefaultTextArea, s.TextArea)); + Assert.NotNull(host.TextFrame); + Assert.All(shapes.Where(s => s != host), s => Assert.Null(s.TextFrame)); } [Fact] @@ -185,11 +185,12 @@ public void ShrinkToFit_MakesLongTextSmaller_AndWraps() [Fact] public void WrappedLines_UseSoftBreaks_UserBreaksStayHard_AndReadBackAsTyped() { - var shape = new DrawShape { IsTextBox = true, TextArea = (0, 0, 1, 1), ShrinkToFit = false, FontSize = 40 }; + var shape = new DrawShape { IsTextBox = true, ShrinkToFit = false, FontSize = 40 }; shape.AddPoint(DrawCoordinateType.Line, 0, 0, Colors.White); shape.AddPoint(DrawCoordinateType.Line, 300, 0, Colors.White); shape.AddPoint(DrawCoordinateType.Line, 300, 400, Colors.White); shape.AddPoint(DrawCoordinateType.Line, 0, 400, Colors.White); + shape.SetTextBounds(0, 0, 300, 400); shape.Text = "one two three four five six seven eight\nnine"; var line = AssaDrawTextLine.Format(shape); @@ -203,11 +204,12 @@ public void WrappedLines_UseSoftBreaks_UserBreaksStayHard_AndReadBackAsTyped() [Fact] public void BracesAndBackslashes_InText_DoNotBecomeTags() { - var shape = new DrawShape { IsTextBox = true, TextArea = (0, 0, 1, 1), Text = "{\\i1}x" }; + var shape = new DrawShape { IsTextBox = true, Text = "{\\i1}x" }; shape.AddPoint(DrawCoordinateType.Line, 0, 0, Colors.White); shape.AddPoint(DrawCoordinateType.Line, 2000, 0, Colors.White); shape.AddPoint(DrawCoordinateType.Line, 2000, 400, Colors.White); shape.AddPoint(DrawCoordinateType.Line, 0, 400, Colors.White); + shape.SetTextBounds(0, 0, 2000, 400); var line = AssaDrawTextLine.Format(shape); Assert.DoesNotContain("{\\i1}", line); @@ -223,20 +225,77 @@ public void MovedInTheGrid_PosWinsOverTheStoredTextArea() Assert.Equal((900f, 500f, 1100f, 700f), parsed.Box); } - [Fact] - public void FlipAndRotate_KeepTheTextAreaOnTheBody() + private static DrawShape MakeBubbleLikeShape() { - var shape = new DrawShape { TextArea = (0.1f, 0.0f, 0.9f, 0.7f) }; + // 100 x 100, text area in the top part (the "body") + var shape = new DrawShape { Text = "Hi" }; shape.AddPoint(DrawCoordinateType.Line, 0, 0, Colors.White); shape.AddPoint(DrawCoordinateType.Line, 100, 0, Colors.White); shape.AddPoint(DrawCoordinateType.Line, 100, 100, Colors.White); + shape.AddPoint(DrawCoordinateType.Line, 0, 100, Colors.White); + shape.SetTextBounds(10, 0, 90, 70); + return shape; + } - shape.FlipVertical(); - Assert.Equal((0.1f, 0.3f, 0.9f, 1f), shape.TextArea, new AreaComparer()); + [Fact] + public void Flip_MirrorsTheTextArea_ButKeepsTheTextReadable() + { + var shape = MakeBubbleLikeShape(); shape.FlipVertical(); - shape.Rotate(90); - Assert.Equal((0.3f, 0.1f, 1f, 0.9f), shape.TextArea, new AreaComparer()); + Assert.Equal(new DrawTextFrame(50, 65, 80, 70, 0), shape.TextFrame); + + shape.FlipHorizontal(); + Assert.Equal(new DrawTextFrame(50, 65, 80, 70, 0), shape.TextFrame); + } + + [Fact] + public void Rotate_TurnsTheTextAreaAndTheText() + { + var shape = MakeBubbleLikeShape(); + + shape.Rotate(30); + + var frame = shape.TextFrame!.Value; + Assert.Equal(30, frame.Angle, 1); + Assert.Equal(80, frame.Width, 1); + Assert.Equal(70, frame.Height, 1); + + // The area's center turned around the shape's center (50, 50): (50, 35) -> 15 up, rotated 30° + Assert.Equal(50 + 15 * MathF.Sin(MathF.PI / 6), frame.CenterX, 1); + Assert.Equal(50 - 15 * MathF.Cos(MathF.PI / 6), frame.CenterY, 1); + } + + [Fact] + public void RotatedText_IsWrittenWithFrz_AndReadBack() + { + var vm = MakeViewModel(); + var textBox = vm.InsertTextBox(new CanvasInsertEventArgs(100, 200, 800, 200, isClick: false)); + textBox.Rotate(-45); + + var result = Ok(vm); + var line = Assert.Single(result.Paragraphs); + Assert.Contains("\\pos(500,300)", line.Text); + Assert.Contains("\\frz45\\", line.Text); + + var shape = Assert.Single(Reopen(result).Shapes); + var frame = shape.TextFrame!.Value; + Assert.Equal(-45, frame.Angle, 1); + Assert.Equal(800, frame.Width, 1); + Assert.Equal(200, frame.Height, 1); + } + + [Fact] + public void ResizeAndNudge_MoveTheTextAreaToo() + { + var vm = MakeViewModel(); + var textBox = vm.InsertTextBox(new CanvasInsertEventArgs(100, 200, 800, 200, isClick: false)); + + vm.ShapeWidth = 400; + Assert.Equal((100f, 200f, 500f, 400f), textBox.GetTextBounds()); + + vm.ShapeX = 0; + Assert.Equal((0f, 200f, 400f, 400f), textBox.GetTextBounds()); } [Fact] @@ -259,13 +318,4 @@ public void EraserShape_CantHaveText() vm.SelectShape(vm.Shapes[0]); Assert.False(vm.CanEditText); } - - private sealed class AreaComparer : IEqualityComparer<(float, float, float, float)> - { - public bool Equals((float, float, float, float) x, (float, float, float, float) y) => - Math.Abs(x.Item1 - y.Item1) < 0.001f && Math.Abs(x.Item2 - y.Item2) < 0.001f && - Math.Abs(x.Item3 - y.Item3) < 0.001f && Math.Abs(x.Item4 - y.Item4) < 0.001f; - - public int GetHashCode((float, float, float, float) obj) => 0; - } } From 0c8c381d6393d5596be6e42291628e5bb585c5f3 Mon Sep 17 00:00:00 2001 From: niksedk Date: Tue, 13 Oct 2026 23:01:13 +0200 Subject: [PATCH 120/306] ASSA draw: ask before Esc/Cancel discards changes, five more bubbles - Esc and Cancel compare the drawing with how it was opened and ask "Discard changes?" when it differs. - New speech bubbles with text areas: tail left side, round, pill, scream (jagged with tail) and radio/phone (lightning tail). Co-Authored-By: Claude Opus 5.5 --- .../Assa/AssaDraw/AssaDrawViewModel.cs | 50 +++++++++++++++++-- src/ui/Features/Assa/AssaDraw/ShapeLibrary.cs | 10 ++++ .../Config/Language/Assa/LanguageAssa.cs | 14 ++++++ tests/UI/Features/Assa/AssaDrawTextTests.cs | 28 +++++++++++ 4 files changed, 99 insertions(+), 3 deletions(-) diff --git a/src/ui/Features/Assa/AssaDraw/AssaDrawViewModel.cs b/src/ui/Features/Assa/AssaDraw/AssaDrawViewModel.cs index 736d92bcf10..8debcd07ba5 100644 --- a/src/ui/Features/Assa/AssaDraw/AssaDrawViewModel.cs +++ b/src/ui/Features/Assa/AssaDraw/AssaDrawViewModel.cs @@ -140,6 +140,7 @@ public List FontNames private readonly IFileHelper _fileHelper; private readonly IWindowService _windowService; private string _fileName = string.Empty; + private string? _openedState; private Subtitle? _subtitle; private string? _videoFileName; private double _videoSeconds; @@ -166,6 +167,7 @@ public AssaDrawViewModel(IFileHelper fileHelper, IWindowService windowService) public void Initialize() { + _openedState = GetChangeState(); UiUtil.RestoreWindowPosition(Window); ZoomToFitCurrentVideoResolution(); RefreshTreeView(); @@ -2398,9 +2400,51 @@ private void Ok() } [RelayCommand] - private void Cancel() + private async Task Cancel() { - Window?.Close(); + if (await ConfirmDiscardChanges()) + { + Window?.Close(); + } + } + + /// + /// The drawing as it would be written (text, outlines, hidden shapes and canvas size included), + /// to tell whether anything changed since the dialog opened. + /// + private string GetChangeState() + { + var subtitle = GenerateSubtitle(includeAll: true); + var sb = new StringBuilder(); + sb.Append(CanvasWidth).Append('x').Append(CanvasHeight).Append('\n'); + foreach (var p in subtitle.Paragraphs) + { + sb.Append(p.Layer).Append(' ').Append(p.Extra).Append(' ').Append(p.Text).Append('\n'); + } + + foreach (var shape in Shapes.Where(s => s.Hidden)) + { + sb.Append("hidden ").Append(Shapes.IndexOf(shape)).Append('\n'); + } + + return sb.ToString(); + } + + public bool HasChanges => _openedState != null && _openedState != GetChangeState(); + + /// + /// True when the dialog may close without OK: nothing changed, or the user agreed to discard. + /// + internal async Task ConfirmDiscardChanges() + { + if (Window == null || !HasChanges) + { + return true; + } + + var result = await MessageBox.Show(Window, Se.Language.Assa.DrawDiscardChangesTitle, Se.Language.Assa.DrawDiscardChanges, + MessageBoxButtons.YesNo, MessageBoxIcon.Question); + return result == MessageBoxResult.Yes; } private void RefreshTreeView() @@ -2712,7 +2756,7 @@ internal async void OnKeyDown(KeyEventArgs e) } e.Handled = true; - Window?.Close(); + await Cancel(); } else if (IsTextInput(e) && e.Key is Key.Enter or Key.Delete or Key.Back) { diff --git a/src/ui/Features/Assa/AssaDraw/ShapeLibrary.cs b/src/ui/Features/Assa/AssaDraw/ShapeLibrary.cs index f9e0c40dd8c..26cc9a44ac5 100644 --- a/src/ui/Features/Assa/AssaDraw/ShapeLibrary.cs +++ b/src/ui/Features/Assa/AssaDraw/ShapeLibrary.cs @@ -284,6 +284,16 @@ private static List MakeBuiltIn() Circle(18, 82, 7), Circle(8, 95, 4), ]), TextItem(l.DrawShapeShoutBubble, bubbles, (27, 30, 73, 70), [Polygon(50, 50, 24, -90, 50, 36, 46, 34, 50, 38, 44, 35)]), + TextItem(l.DrawShapeSpeechBubbleSide, bubbles, (28, 6, 92, 64), + ["M32 0 H88 A12 12 0 0 1 100 12 V58 A12 12 0 0 1 88 70 H32 A12 12 0 0 1 20 58 V50 L0 66 L20 34 V12 A12 12 0 0 1 32 0 Z"]), + TextItem(l.DrawShapeRoundBubble, bubbles, (21, 13, 79, 71), [Circle(50, 42, 42), "M30 74 L12 100 L52 82 Z"]), + TextItem(l.DrawShapePillBubble, bubbles, (12, 6, 88, 44), ["M25 0 H75 A25 25 0 0 1 75 50 H42 L20 76 L28 50 H25 A25 25 0 0 1 25 0 Z"]), + TextItem(l.DrawShapeScreamBubble, bubbles, (27, 24, 73, 68), [Polygon(50, 46, 22, -90, 46, 34), "M32 70 L12 100 L50 76 Z"]), + TextItem(l.DrawShapeElectricBubble, bubbles, (8, 6, 92, 64), + [ + "M12 0 H88 A12 12 0 0 1 100 12 V58 A12 12 0 0 1 88 70 H12 A12 12 0 0 1 0 58 V12 A12 12 0 0 1 12 0 Z", + "M26 64 H44 L34 79 H42 L14 100 L24 84 H16 Z", + ]), TextItem(l.DrawShapeCaptionBox, bubbles, (4, 3, 96, 27), ["M6 0 H94 A6 6 0 0 1 100 6 V24 A6 6 0 0 1 94 30 H6 A6 6 0 0 1 0 24 V6 A6 6 0 0 1 6 0 Z"]), // Arrows diff --git a/src/ui/Logic/Config/Language/Assa/LanguageAssa.cs b/src/ui/Logic/Config/Language/Assa/LanguageAssa.cs index 0247e5c7bb2..264555f52e9 100644 --- a/src/ui/Logic/Config/Language/Assa/LanguageAssa.cs +++ b/src/ui/Logic/Config/Language/Assa/LanguageAssa.cs @@ -64,6 +64,11 @@ public class LanguageAssa public string DrawShapeThoughtBubble { get; set; } public string DrawShapeShoutBubble { get; set; } public string DrawShapeCaptionBox { get; set; } + public string DrawShapeSpeechBubbleSide { get; set; } + public string DrawShapeRoundBubble { get; set; } + public string DrawShapePillBubble { get; set; } + public string DrawShapeScreamBubble { get; set; } + public string DrawShapeElectricBubble { get; set; } public string DrawShapeArrow { get; set; } public string DrawShapeDoubleArrow { get; set; } public string DrawShapeChevron { get; set; } @@ -107,6 +112,8 @@ public class LanguageAssa public string DrawShrinkToFit { get; set; } public string DrawTextColor { get; set; } public string DrawTextBox { get; set; } + public string DrawDiscardChangesTitle { get; set; } + public string DrawDiscardChanges { get; set; } // Progress Bar Generator public string ProgressBarTitle { get; set; } @@ -403,6 +410,11 @@ public LanguageAssa() DrawShapeThoughtBubble = "Thought bubble"; DrawShapeShoutBubble = "Shout bubble"; DrawShapeCaptionBox = "Caption box"; + DrawShapeSpeechBubbleSide = "Speech bubble, tail left side"; + DrawShapeRoundBubble = "Round bubble"; + DrawShapePillBubble = "Pill bubble"; + DrawShapeScreamBubble = "Scream bubble"; + DrawShapeElectricBubble = "Radio/phone bubble"; DrawShapeArrow = "Arrow"; DrawShapeDoubleArrow = "Double arrow"; DrawShapeChevron = "Chevron"; @@ -446,6 +458,8 @@ public LanguageAssa() DrawShrinkToFit = "Shrink to fit"; DrawTextColor = "Text color"; DrawTextBox = "Text box"; + DrawDiscardChangesTitle = "Discard changes?"; + DrawDiscardChanges = "The drawing has been changed." + System.Environment.NewLine + "Discard the changes?"; // Progress Bar Generator ProgressBarTitle = "ASSA progress bar"; diff --git a/tests/UI/Features/Assa/AssaDrawTextTests.cs b/tests/UI/Features/Assa/AssaDrawTextTests.cs index 9789dd17b8d..66f00c69017 100644 --- a/tests/UI/Features/Assa/AssaDrawTextTests.cs +++ b/tests/UI/Features/Assa/AssaDrawTextTests.cs @@ -318,4 +318,32 @@ public void EraserShape_CantHaveText() vm.SelectShape(vm.Shapes[0]); Assert.False(vm.CanEditText); } + + [Fact] + public void EveryBubble_HasATextAreaInsideIt() + { + foreach (var item in ShapeLibrary.BuiltIn.Where(i => i.Category == Se.Language.Assa.DrawCategorySpeechBubbles)) + { + Assert.NotNull(item.TextArea); + var shapes = item.CreateShapes(0, 0, 1000, 1000, Colors.White, 0); + var host = Assert.Single(shapes, s => s.TextFrame != null); + var frame = host.TextFrame!.Value; + Assert.True(frame.Width > 300 && frame.Height > 150, item.Name); + Assert.True(host.HitTest(frame.CenterX, frame.CenterY, 0), item.Name); + } + } + + [Fact] + public void HasChanges_IsFalseWhenOpened_TrueAfterAnEdit_FalseAfterUndo() + { + var vm = MakeViewModel(new Paragraph("{\\p1}m 0 0 l 100 0 100 100{\\p0}", 0, 2000) { Extra = "Default" }); + vm.Initialize(); + Assert.False(vm.HasChanges); + + vm.InsertTextBox(new CanvasInsertEventArgs(960, 540, 0, 0, isClick: true)); + Assert.True(vm.HasChanges); + + vm.UndoCommand.Execute(null); + Assert.False(vm.HasChanges); + } } From a8645861a392ea904cbfc0ac38a15443f996d98b Mon Sep 17 00:00:00 2001 From: niksedk Date: Tue, 13 Oct 2026 23:04:17 +0200 Subject: [PATCH 121/306] ASSA draw: X button asks before discarding changes; drop radio bubble Co-Authored-By: Claude Opus 5.5 --- .../Assa/AssaDraw/AssaDrawViewModel.cs | 31 +++++++++++++++++-- .../Features/Assa/AssaDraw/AssaDrawWindow.cs | 13 +++++++- src/ui/Features/Assa/AssaDraw/ShapeLibrary.cs | 5 --- .../Config/Language/Assa/LanguageAssa.cs | 2 -- 4 files changed, 41 insertions(+), 10 deletions(-) diff --git a/src/ui/Features/Assa/AssaDraw/AssaDrawViewModel.cs b/src/ui/Features/Assa/AssaDraw/AssaDrawViewModel.cs index 8debcd07ba5..689be0ebf3d 100644 --- a/src/ui/Features/Assa/AssaDraw/AssaDrawViewModel.cs +++ b/src/ui/Features/Assa/AssaDraw/AssaDrawViewModel.cs @@ -2402,9 +2402,36 @@ private void Ok() [RelayCommand] private async Task Cancel() { - if (await ConfirmDiscardChanges()) + await CloseAfterConfirm(); + } + + private bool _closeConfirmed; + private bool _askingToClose; + + /// + /// Closing without OK (X button) while there are changes - the window cancels the close and asks first. + /// + public bool ShouldAskBeforeClosing => !OkPressed && !_closeConfirmed && HasChanges; + + internal async Task CloseAfterConfirm() + { + if (_askingToClose) + { + return; + } + + _askingToClose = true; + try + { + if (await ConfirmDiscardChanges()) + { + _closeConfirmed = true; + Window?.Close(); + } + } + finally { - Window?.Close(); + _askingToClose = false; } } diff --git a/src/ui/Features/Assa/AssaDraw/AssaDrawWindow.cs b/src/ui/Features/Assa/AssaDraw/AssaDrawWindow.cs index 1482d523485..5bce0611a0a 100644 --- a/src/ui/Features/Assa/AssaDraw/AssaDrawWindow.cs +++ b/src/ui/Features/Assa/AssaDraw/AssaDrawWindow.cs @@ -117,7 +117,18 @@ public AssaDrawWindow(AssaDrawViewModel vm) AddHandler(DragDrop.DropEvent, vm.OnDrop, RoutingStrategies.Bubble); Loaded += OnLoaded; - Closing += (_, e) => vm.OnClosing(); + Closing += (_, e) => + { + // The X button asks before discarding changes too (OK and a confirmed Cancel/Esc don't) + if (vm.ShouldAskBeforeClosing) + { + e.Cancel = true; + _ = vm.CloseAfterConfirm(); + return; + } + + vm.OnClosing(); + }; KeyDown += (_, e) => vm.OnKeyDown(e); } diff --git a/src/ui/Features/Assa/AssaDraw/ShapeLibrary.cs b/src/ui/Features/Assa/AssaDraw/ShapeLibrary.cs index 26cc9a44ac5..b24c1f7a9d2 100644 --- a/src/ui/Features/Assa/AssaDraw/ShapeLibrary.cs +++ b/src/ui/Features/Assa/AssaDraw/ShapeLibrary.cs @@ -289,11 +289,6 @@ private static List MakeBuiltIn() TextItem(l.DrawShapeRoundBubble, bubbles, (21, 13, 79, 71), [Circle(50, 42, 42), "M30 74 L12 100 L52 82 Z"]), TextItem(l.DrawShapePillBubble, bubbles, (12, 6, 88, 44), ["M25 0 H75 A25 25 0 0 1 75 50 H42 L20 76 L28 50 H25 A25 25 0 0 1 25 0 Z"]), TextItem(l.DrawShapeScreamBubble, bubbles, (27, 24, 73, 68), [Polygon(50, 46, 22, -90, 46, 34), "M32 70 L12 100 L50 76 Z"]), - TextItem(l.DrawShapeElectricBubble, bubbles, (8, 6, 92, 64), - [ - "M12 0 H88 A12 12 0 0 1 100 12 V58 A12 12 0 0 1 88 70 H12 A12 12 0 0 1 0 58 V12 A12 12 0 0 1 12 0 Z", - "M26 64 H44 L34 79 H42 L14 100 L24 84 H16 Z", - ]), TextItem(l.DrawShapeCaptionBox, bubbles, (4, 3, 96, 27), ["M6 0 H94 A6 6 0 0 1 100 6 V24 A6 6 0 0 1 94 30 H6 A6 6 0 0 1 0 24 V6 A6 6 0 0 1 6 0 Z"]), // Arrows diff --git a/src/ui/Logic/Config/Language/Assa/LanguageAssa.cs b/src/ui/Logic/Config/Language/Assa/LanguageAssa.cs index 264555f52e9..9f68848a3b7 100644 --- a/src/ui/Logic/Config/Language/Assa/LanguageAssa.cs +++ b/src/ui/Logic/Config/Language/Assa/LanguageAssa.cs @@ -68,7 +68,6 @@ public class LanguageAssa public string DrawShapeRoundBubble { get; set; } public string DrawShapePillBubble { get; set; } public string DrawShapeScreamBubble { get; set; } - public string DrawShapeElectricBubble { get; set; } public string DrawShapeArrow { get; set; } public string DrawShapeDoubleArrow { get; set; } public string DrawShapeChevron { get; set; } @@ -414,7 +413,6 @@ public LanguageAssa() DrawShapeRoundBubble = "Round bubble"; DrawShapePillBubble = "Pill bubble"; DrawShapeScreamBubble = "Scream bubble"; - DrawShapeElectricBubble = "Radio/phone bubble"; DrawShapeArrow = "Arrow"; DrawShapeDoubleArrow = "Double arrow"; DrawShapeChevron = "Chevron"; From da8df9a3e567888b82a592a1fd6c9dfa1c5bd914 Mon Sep 17 00:00:00 2001 From: niksedk Date: Tue, 13 Oct 2026 23:05:21 +0200 Subject: [PATCH 122/306] Update English.json (generated from test) Co-Authored-By: Claude Opus 5.5 --- src/ui/Assets/Languages/English.json | 1 + 1 file changed, 1 insertion(+) diff --git a/src/ui/Assets/Languages/English.json b/src/ui/Assets/Languages/English.json index bc0f1224a33..b59e930fa55 100644 --- a/src/ui/Assets/Languages/English.json +++ b/src/ui/Assets/Languages/English.json @@ -3572,6 +3572,7 @@ "timeCodeModeTime": "Time (hh:mm:ss,zzz)", "timeCodeModeFrames": "Frames (hh:mm:ss:ff)", "timeCodeModeFrameNumbers": "Frame numbers", + "frameModeSnapToVideoFrames": "Store times on video frames (not frames within each second)", "textBoxLimitNewLines": "Limit number of lines in subtitle text box", "mpvOpenGl": "libmpv - OpenGL", "mpvSoftwareRendering": "libmpv - Software rendering (slow)", From d986ebe4cf9fffe38061bee772a1b1f6b064c0d6 Mon Sep 17 00:00:00 2001 From: niksedk Date: Tue, 13 Oct 2026 23:07:28 +0200 Subject: [PATCH 123/306] ASSA draw: Speaker and Cross symbols Co-Authored-By: Claude Opus 5.5 --- src/ui/Features/Assa/AssaDraw/ShapeLibrary.cs | 7 +++++++ src/ui/Logic/Config/Language/Assa/LanguageAssa.cs | 4 ++++ 2 files changed, 11 insertions(+) diff --git a/src/ui/Features/Assa/AssaDraw/ShapeLibrary.cs b/src/ui/Features/Assa/AssaDraw/ShapeLibrary.cs index b24c1f7a9d2..fea2c2de2a1 100644 --- a/src/ui/Features/Assa/AssaDraw/ShapeLibrary.cs +++ b/src/ui/Features/Assa/AssaDraw/ShapeLibrary.cs @@ -330,6 +330,13 @@ private static List MakeBuiltIn() Item(l.DrawShapeCloud, symbols, [Circle(30, 58, 22), Circle(52, 42, 28), Circle(76, 58, 22), "M30 58 H76 V80 H30 Z"]), Item(l.DrawShapeMoon, symbols, [Circle(50, 50, 50)], [Circle(70, 36, 42)]), Item(l.DrawShapeBadge, symbols, [Polygon(50, 50, 32, -90, 50, 43)]), + Item(l.DrawShapeSpeaker, symbols, + [ + "M0 35 H22 L50 10 V90 L22 65 H0 Z", + "M65.56 34.44 A22 22 0 0 1 65.56 65.56 L59.9 59.9 A14 14 0 0 0 59.9 40.1 Z", + "M78.28 21.72 A40 40 0 0 1 78.28 78.28 L72.63 72.63 A32 32 0 0 0 72.63 27.37 Z", + ]), + Item(l.DrawShapeCross, symbols, ["M15 0 L50 35 L85 0 L100 15 L65 50 L100 85 L85 100 L50 65 L15 100 L0 85 L35 50 L0 15 Z"]), ]; } } diff --git a/src/ui/Logic/Config/Language/Assa/LanguageAssa.cs b/src/ui/Logic/Config/Language/Assa/LanguageAssa.cs index 9f68848a3b7..fc72deb3ace 100644 --- a/src/ui/Logic/Config/Language/Assa/LanguageAssa.cs +++ b/src/ui/Logic/Config/Language/Assa/LanguageAssa.cs @@ -90,6 +90,8 @@ public class LanguageAssa public string DrawShapeCloud { get; set; } public string DrawShapeMoon { get; set; } public string DrawShapeBadge { get; set; } + public string DrawShapeSpeaker { get; set; } + public string DrawShapeCross { get; set; } public string DrawSize { get; set; } public string DrawLayer { get; set; } public string DrawBackgroundVideoFrame { get; set; } @@ -435,6 +437,8 @@ public LanguageAssa() DrawShapeCloud = "Cloud"; DrawShapeMoon = "Moon"; DrawShapeBadge = "Badge"; + DrawShapeSpeaker = "Speaker"; + DrawShapeCross = "Cross"; DrawSize = "Size"; DrawLayer = "Layer"; DrawBackgroundVideoFrame = "Video frame (current position)"; From 63e2d4cffb7dfbb28a609c5b08024953b16354f3 Mon Sep 17 00:00:00 2001 From: niksedk Date: Tue, 13 Oct 2026 23:11:16 +0200 Subject: [PATCH 124/306] ASSA draw: Smiley and Sad smiley symbols Co-Authored-By: Claude Opus 5.5 --- src/ui/Features/Assa/AssaDraw/ShapeLibrary.cs | 6 ++++++ src/ui/Logic/Config/Language/Assa/LanguageAssa.cs | 4 ++++ tests/UI/Features/Assa/AssaDrawShapeLibraryTests.cs | 4 ++++ 3 files changed, 14 insertions(+) diff --git a/src/ui/Features/Assa/AssaDraw/ShapeLibrary.cs b/src/ui/Features/Assa/AssaDraw/ShapeLibrary.cs index fea2c2de2a1..2069716f670 100644 --- a/src/ui/Features/Assa/AssaDraw/ShapeLibrary.cs +++ b/src/ui/Features/Assa/AssaDraw/ShapeLibrary.cs @@ -337,6 +337,12 @@ private static List MakeBuiltIn() "M78.28 21.72 A40 40 0 0 1 78.28 78.28 L72.63 72.63 A32 32 0 0 0 72.63 27.37 Z", ]), Item(l.DrawShapeCross, symbols, ["M15 0 L50 35 L85 0 L100 15 L65 50 L100 85 L85 100 L50 65 L15 100 L0 85 L35 50 L0 15 Z"]), + + // Faces: eyes and mouth are cut out, so the background shows through + Item(l.DrawShapeSmiley, symbols, [Circle(50, 50, 50)], + [Circle(34, 36, 8), Circle(66, 36, 8), "M26 58 A24 24 0 0 0 74 58 L64 58 A14 14 0 0 1 36 58 Z"]), + Item(l.DrawShapeSadSmiley, symbols, [Circle(50, 50, 50)], + [Circle(34, 36, 8), Circle(66, 36, 8), "M26 82 A24 24 0 0 1 74 82 L64 82 A14 14 0 0 0 36 82 Z"]), ]; } } diff --git a/src/ui/Logic/Config/Language/Assa/LanguageAssa.cs b/src/ui/Logic/Config/Language/Assa/LanguageAssa.cs index fc72deb3ace..e25467c8e21 100644 --- a/src/ui/Logic/Config/Language/Assa/LanguageAssa.cs +++ b/src/ui/Logic/Config/Language/Assa/LanguageAssa.cs @@ -92,6 +92,8 @@ public class LanguageAssa public string DrawShapeBadge { get; set; } public string DrawShapeSpeaker { get; set; } public string DrawShapeCross { get; set; } + public string DrawShapeSmiley { get; set; } + public string DrawShapeSadSmiley { get; set; } public string DrawSize { get; set; } public string DrawLayer { get; set; } public string DrawBackgroundVideoFrame { get; set; } @@ -439,6 +441,8 @@ public LanguageAssa() DrawShapeBadge = "Badge"; DrawShapeSpeaker = "Speaker"; DrawShapeCross = "Cross"; + DrawShapeSmiley = "Smiley"; + DrawShapeSadSmiley = "Sad smiley"; DrawSize = "Size"; DrawLayer = "Layer"; DrawBackgroundVideoFrame = "Video frame (current position)"; diff --git a/tests/UI/Features/Assa/AssaDrawShapeLibraryTests.cs b/tests/UI/Features/Assa/AssaDrawShapeLibraryTests.cs index fd38ab7176e..339ccf3035f 100644 --- a/tests/UI/Features/Assa/AssaDrawShapeLibraryTests.cs +++ b/tests/UI/Features/Assa/AssaDrawShapeLibraryTests.cs @@ -41,6 +41,10 @@ public void EveryBuiltInShape_HasClosedContours_AndAThumbnail() } Assert.Equal(2, ShapeLibrary.BuiltIn.First(i => i.Name == Se.Language.Assa.DrawShapeRing).Template.Count); + + // Face + two eyes + mouth: the eyes and mouth are holes + Assert.Equal(4, ShapeLibrary.BuiltIn.First(i => i.Name == Se.Language.Assa.DrawShapeSmiley).Template.Count); + Assert.Equal(4, ShapeLibrary.BuiltIn.First(i => i.Name == Se.Language.Assa.DrawShapeSadSmiley).Template.Count); Assert.True(ShapeLibrary.BuiltIn.Count(i => i.Category == Se.Language.Assa.DrawCategorySpeechBubbles) >= 6); } From af3e798ea046b0a7d52a62a657a10fa0fb78376f Mon Sep 17 00:00:00 2001 From: niksedk Date: Tue, 13 Oct 2026 23:13:50 +0200 Subject: [PATCH 125/306] ASSA draw: Thin arrow and U-turn arrow Co-Authored-By: Claude Opus 5.5 --- src/ui/Features/Assa/AssaDraw/ShapeLibrary.cs | 2 ++ src/ui/Logic/Config/Language/Assa/LanguageAssa.cs | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/src/ui/Features/Assa/AssaDraw/ShapeLibrary.cs b/src/ui/Features/Assa/AssaDraw/ShapeLibrary.cs index 2069716f670..41932daf51c 100644 --- a/src/ui/Features/Assa/AssaDraw/ShapeLibrary.cs +++ b/src/ui/Features/Assa/AssaDraw/ShapeLibrary.cs @@ -296,6 +296,8 @@ private static List MakeBuiltIn() Item(l.DrawShapeDoubleArrow, arrows, ["M0 50 L30 20 V38 H70 V20 L100 50 L70 80 V62 H30 V80 Z"]), Item(l.DrawShapeChevron, arrows, ["M0 0 H40 L80 50 L40 100 H0 L40 50 Z"]), Item(l.DrawShapeCurvedArrow, arrows, ["M10 90 A60 60 0 0 1 70 30 V10 L100 40 L70 70 V50 A40 40 0 0 0 30 90 Z"]), + Item(l.DrawShapeThinArrow, arrows, ["M0 44 H76 V30 L100 50 L76 70 V56 H0 Z"]), + Item(l.DrawShapeUTurnArrow, arrows, ["M8 100 V40 A34 34 0 0 1 76 40 V70 H86 L68 100 L50 70 H60 V40 A18 18 0 0 0 24 40 V100 Z"]), // Basic shapes Item(l.DrawShapeRoundedRectangle, basic, ["M15 0 H85 A15 15 0 0 1 100 15 V85 A15 15 0 0 1 85 100 H15 A15 15 0 0 1 0 85 V15 A15 15 0 0 1 15 0 Z"]), diff --git a/src/ui/Logic/Config/Language/Assa/LanguageAssa.cs b/src/ui/Logic/Config/Language/Assa/LanguageAssa.cs index e25467c8e21..8f7bfcfa79c 100644 --- a/src/ui/Logic/Config/Language/Assa/LanguageAssa.cs +++ b/src/ui/Logic/Config/Language/Assa/LanguageAssa.cs @@ -72,6 +72,8 @@ public class LanguageAssa public string DrawShapeDoubleArrow { get; set; } public string DrawShapeChevron { get; set; } public string DrawShapeCurvedArrow { get; set; } + public string DrawShapeThinArrow { get; set; } + public string DrawShapeUTurnArrow { get; set; } public string DrawShapeRoundedRectangle { get; set; } public string DrawShapeTriangle { get; set; } public string DrawShapeDiamond { get; set; } @@ -421,6 +423,8 @@ public LanguageAssa() DrawShapeDoubleArrow = "Double arrow"; DrawShapeChevron = "Chevron"; DrawShapeCurvedArrow = "Curved arrow"; + DrawShapeThinArrow = "Thin arrow"; + DrawShapeUTurnArrow = "U-turn arrow"; DrawShapeRoundedRectangle = "Rounded rectangle"; DrawShapeTriangle = "Triangle"; DrawShapeDiamond = "Diamond"; From 72c2ffd4505b50f0592073de31a81b45dbf6ab50 Mon Sep 17 00:00:00 2001 From: niksedk Date: Tue, 13 Oct 2026 23:16:19 +0200 Subject: [PATCH 126/306] ASSA draw: Repeat arrow replaces Thin arrow; Parallelogram and Trapezoid Co-Authored-By: Claude Opus 5.5 --- src/ui/Features/Assa/AssaDraw/ShapeLibrary.cs | 6 +++++- src/ui/Logic/Config/Language/Assa/LanguageAssa.cs | 8 ++++++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/ui/Features/Assa/AssaDraw/ShapeLibrary.cs b/src/ui/Features/Assa/AssaDraw/ShapeLibrary.cs index 41932daf51c..984ee7dd711 100644 --- a/src/ui/Features/Assa/AssaDraw/ShapeLibrary.cs +++ b/src/ui/Features/Assa/AssaDraw/ShapeLibrary.cs @@ -296,7 +296,9 @@ private static List MakeBuiltIn() Item(l.DrawShapeDoubleArrow, arrows, ["M0 50 L30 20 V38 H70 V20 L100 50 L70 80 V62 H30 V80 Z"]), Item(l.DrawShapeChevron, arrows, ["M0 0 H40 L80 50 L40 100 H0 L40 50 Z"]), Item(l.DrawShapeCurvedArrow, arrows, ["M10 90 A60 60 0 0 1 70 30 V10 L100 40 L70 70 V50 A40 40 0 0 0 30 90 Z"]), - Item(l.DrawShapeThinArrow, arrows, ["M0 44 H76 V30 L100 50 L76 70 V56 H0 Z"]), + // 300° ring (clockwise) ending in an arrowhead at the top + Item(l.DrawShapeRepeatArrow, arrows, + ["M78.28 16.29 A44 44 0 1 1 34.95 8.65 L32.22 1.14 L56.15 8.39 L42.48 29.33 L39.74 21.81 A30 30 0 1 0 69.28 27.02 Z"]), Item(l.DrawShapeUTurnArrow, arrows, ["M8 100 V40 A34 34 0 0 1 76 40 V70 H86 L68 100 L50 70 H60 V40 A18 18 0 0 0 24 40 V100 Z"]), // Basic shapes @@ -310,6 +312,8 @@ private static List MakeBuiltIn() Item(l.DrawShapeRing, basic, [Circle(50, 50, 50)], [Circle(50, 50, 32)]), Item(l.DrawShapePlus, basic, ["M35 0 H65 V35 H100 V65 H65 V100 H35 V65 H0 V35 H35 Z"]), Item(l.DrawShapeBanner, basic, ["M0 20 H100 L88 45 L100 70 H0 L12 45 Z"]), + Item(l.DrawShapeParallelogram, basic, ["M25 0 H100 L75 70 H0 Z"]), + Item(l.DrawShapeTrapezoid, basic, ["M22 0 H78 L100 70 H0 Z"]), // Symbols Item(l.DrawShapeHeart, symbols, ["M50 92 C20 70 0 52 0 30 C0 12 13 0 28 0 C38 0 46 6 50 14 C54 6 62 0 72 0 C87 0 100 12 100 30 C100 52 80 70 50 92 Z"]), diff --git a/src/ui/Logic/Config/Language/Assa/LanguageAssa.cs b/src/ui/Logic/Config/Language/Assa/LanguageAssa.cs index 8f7bfcfa79c..9343bc5912e 100644 --- a/src/ui/Logic/Config/Language/Assa/LanguageAssa.cs +++ b/src/ui/Logic/Config/Language/Assa/LanguageAssa.cs @@ -72,7 +72,7 @@ public class LanguageAssa public string DrawShapeDoubleArrow { get; set; } public string DrawShapeChevron { get; set; } public string DrawShapeCurvedArrow { get; set; } - public string DrawShapeThinArrow { get; set; } + public string DrawShapeRepeatArrow { get; set; } public string DrawShapeUTurnArrow { get; set; } public string DrawShapeRoundedRectangle { get; set; } public string DrawShapeTriangle { get; set; } @@ -84,6 +84,8 @@ public class LanguageAssa public string DrawShapeRing { get; set; } public string DrawShapePlus { get; set; } public string DrawShapeBanner { get; set; } + public string DrawShapeParallelogram { get; set; } + public string DrawShapeTrapezoid { get; set; } public string DrawShapeHeart { get; set; } public string DrawShapeCheck { get; set; } public string DrawShapeMusicNote { get; set; } @@ -423,7 +425,7 @@ public LanguageAssa() DrawShapeDoubleArrow = "Double arrow"; DrawShapeChevron = "Chevron"; DrawShapeCurvedArrow = "Curved arrow"; - DrawShapeThinArrow = "Thin arrow"; + DrawShapeRepeatArrow = "Repeat arrow"; DrawShapeUTurnArrow = "U-turn arrow"; DrawShapeRoundedRectangle = "Rounded rectangle"; DrawShapeTriangle = "Triangle"; @@ -435,6 +437,8 @@ public LanguageAssa() DrawShapeRing = "Ring"; DrawShapePlus = "Plus"; DrawShapeBanner = "Banner"; + DrawShapeParallelogram = "Parallelogram"; + DrawShapeTrapezoid = "Trapezoid"; DrawShapeHeart = "Heart"; DrawShapeCheck = "Check mark"; DrawShapeMusicNote = "Music note"; From 5809a58430e0fffb01e56b835b839d5d27c7cffa Mon Sep 17 00:00:00 2001 From: niksedk Date: Tue, 13 Oct 2026 23:19:28 +0200 Subject: [PATCH 127/306] ASSA draw: Half circle and Teardrop instead of Parallelogram and Trapezoid Co-Authored-By: Claude Opus 5.5 --- src/ui/Features/Assa/AssaDraw/ShapeLibrary.cs | 4 ++-- src/ui/Logic/Config/Language/Assa/LanguageAssa.cs | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/ui/Features/Assa/AssaDraw/ShapeLibrary.cs b/src/ui/Features/Assa/AssaDraw/ShapeLibrary.cs index 984ee7dd711..df4609fbe83 100644 --- a/src/ui/Features/Assa/AssaDraw/ShapeLibrary.cs +++ b/src/ui/Features/Assa/AssaDraw/ShapeLibrary.cs @@ -312,8 +312,8 @@ private static List MakeBuiltIn() Item(l.DrawShapeRing, basic, [Circle(50, 50, 50)], [Circle(50, 50, 32)]), Item(l.DrawShapePlus, basic, ["M35 0 H65 V35 H100 V65 H65 V100 H35 V65 H0 V35 H35 Z"]), Item(l.DrawShapeBanner, basic, ["M0 20 H100 L88 45 L100 70 H0 L12 45 Z"]), - Item(l.DrawShapeParallelogram, basic, ["M25 0 H100 L75 70 H0 Z"]), - Item(l.DrawShapeTrapezoid, basic, ["M22 0 H78 L100 70 H0 Z"]), + Item(l.DrawShapeHalfCircle, basic, ["M0 50 A50 50 0 0 1 100 50 Z"]), + Item(l.DrawShapeTeardrop, basic, ["M50 0 C50 0 90 45 90 70 A40 40 0 0 1 10 70 C10 45 50 0 50 0 Z"]), // Symbols Item(l.DrawShapeHeart, symbols, ["M50 92 C20 70 0 52 0 30 C0 12 13 0 28 0 C38 0 46 6 50 14 C54 6 62 0 72 0 C87 0 100 12 100 30 C100 52 80 70 50 92 Z"]), diff --git a/src/ui/Logic/Config/Language/Assa/LanguageAssa.cs b/src/ui/Logic/Config/Language/Assa/LanguageAssa.cs index 9343bc5912e..317344ab2b1 100644 --- a/src/ui/Logic/Config/Language/Assa/LanguageAssa.cs +++ b/src/ui/Logic/Config/Language/Assa/LanguageAssa.cs @@ -84,8 +84,8 @@ public class LanguageAssa public string DrawShapeRing { get; set; } public string DrawShapePlus { get; set; } public string DrawShapeBanner { get; set; } - public string DrawShapeParallelogram { get; set; } - public string DrawShapeTrapezoid { get; set; } + public string DrawShapeHalfCircle { get; set; } + public string DrawShapeTeardrop { get; set; } public string DrawShapeHeart { get; set; } public string DrawShapeCheck { get; set; } public string DrawShapeMusicNote { get; set; } @@ -437,8 +437,8 @@ public LanguageAssa() DrawShapeRing = "Ring"; DrawShapePlus = "Plus"; DrawShapeBanner = "Banner"; - DrawShapeParallelogram = "Parallelogram"; - DrawShapeTrapezoid = "Trapezoid"; + DrawShapeHalfCircle = "Half circle"; + DrawShapeTeardrop = "Teardrop"; DrawShapeHeart = "Heart"; DrawShapeCheck = "Check mark"; DrawShapeMusicNote = "Music note"; From fc5e7c56cdc490a3d82f32e4af4c86ee5e488a73 Mon Sep 17 00:00:00 2001 From: niksedk Date: Tue, 13 Oct 2026 23:24:31 +0200 Subject: [PATCH 128/306] ASSA draw: drag shapes from the palette onto the canvas A drag (instead of a click) on a palette shape drops it at default size, centered where it is released. Also tests that moving shapes, bubbles with text and text boxes by dragging works. Co-Authored-By: Claude Opus 5.5 --- .../Features/Assa/AssaDraw/AssaDrawCanvas.cs | 5 ++ .../Assa/AssaDraw/AssaDrawViewModel.cs | 47 ++++++++++++++ .../Features/Assa/AssaDraw/AssaDrawWindow.cs | 49 +++++++++++++++ .../Features/Assa/AssaDrawCanvasUndoTests.cs | 62 +++++++++++++++++++ .../Assa/AssaDrawShapeLibraryTests.cs | 17 +++++ 5 files changed, 180 insertions(+) diff --git a/src/ui/Features/Assa/AssaDraw/AssaDrawCanvas.cs b/src/ui/Features/Assa/AssaDraw/AssaDrawCanvas.cs index be7c877d41f..0c536fe54a6 100644 --- a/src/ui/Features/Assa/AssaDraw/AssaDrawCanvas.cs +++ b/src/ui/Features/Assa/AssaDraw/AssaDrawCanvas.cs @@ -341,6 +341,11 @@ public void ZoomAt(float zoom, Point anchor) ZoomFactor = newZoom; } + /// + /// A position on the control in drawing (canvas) coordinates. + /// + public (float X, float Y) ToCanvasPoint(Point position) => (FromZoomFactorX((float)position.X), FromZoomFactorY((float)position.Y)); + private float ToZoomFactorX(float v) => v * _zoomFactor + _panX; private float ToZoomFactorY(float v) => v * _zoomFactor + _panY; private float FromZoomFactorX(float v) => (v - _panX) / _zoomFactor; diff --git a/src/ui/Features/Assa/AssaDraw/AssaDrawViewModel.cs b/src/ui/Features/Assa/AssaDraw/AssaDrawViewModel.cs index 689be0ebf3d..3f764bf44a8 100644 --- a/src/ui/Features/Assa/AssaDraw/AssaDrawViewModel.cs +++ b/src/ui/Features/Assa/AssaDraw/AssaDrawViewModel.cs @@ -1,3 +1,4 @@ +using Avalonia; using Avalonia.Controls; using Avalonia.Input; using Avalonia.Media; @@ -2051,17 +2052,63 @@ public bool ImportSvgText(string svgText) private static readonly string[] BackgroundImageExtensions = [".png", ".jpg", ".jpeg", ".bmp", ".webp"]; + /// + /// A shape dragged from the shape palette (same process only). + /// + public static readonly DataFormat LibraryShapeFormat = DataFormat.CreateInProcessFormat("se-assa-draw-library-shape"); + internal void OnDragOver(object? sender, DragEventArgs e) { + if (e.DataTransfer.Contains(LibraryShapeFormat)) + { + e.DragEffects = GetCanvasDropPoint(e) != null ? DragDropEffects.Copy : DragDropEffects.None; + e.Handled = true; + return; + } + e.DragEffects = GetDroppedFile(e) != null ? DragDropEffects.Copy : DragDropEffects.None; e.Handled = true; } + /// + /// Where on the drawing a drag is, or null when it is not over the canvas. + /// + private (float X, float Y)? GetCanvasDropPoint(DragEventArgs e) + { + if (Canvas == null) + { + return null; + } + + var position = e.GetPosition(Canvas); + return new Rect(Canvas.Bounds.Size).Contains(position) ? Canvas.ToCanvasPoint(position) : null; + } + + /// + /// A shape dropped from the palette: default size, centered where it was dropped, selected. + /// + public void DropLibraryShape(ShapeLibraryItem item, float x, float y) + { + CurrentLibraryShape = item; + InsertLibraryShape(new CanvasInsertEventArgs(x, y, 0, 0, isClick: true)); + } + /// /// An .svg is imported as shapes, an image becomes the background. /// internal void OnDrop(object? sender, DragEventArgs e) { + if (e.DataTransfer.TryGetValue(LibraryShapeFormat) is { } item) + { + e.Handled = true; + if (GetCanvasDropPoint(e) is { } point) + { + DropLibraryShape(item, point.X, point.Y); + } + + return; + } + var fileName = GetDroppedFile(e); if (fileName == null) { diff --git a/src/ui/Features/Assa/AssaDraw/AssaDrawWindow.cs b/src/ui/Features/Assa/AssaDraw/AssaDrawWindow.cs index 5bce0611a0a..68b84e4f438 100644 --- a/src/ui/Features/Assa/AssaDraw/AssaDrawWindow.cs +++ b/src/ui/Features/Assa/AssaDraw/AssaDrawWindow.cs @@ -856,6 +856,7 @@ void AddCategory(string title, IEnumerable items) vm.PickLibraryShapeCommand.Execute(item); close(); }; + AddPaletteDrag(button, item, close); if (item.UserShape != null) { @@ -903,6 +904,54 @@ void AddCategory(string title, IEnumerable items) }; } + /// + /// Dragging a palette shape (instead of clicking it) drops it on the canvas where it is released. + /// + private static void AddPaletteDrag(Button button, ShapeLibraryItem item, Action close) + { + PointerPressedEventArgs? press = null; + Point start = default; + button.AddHandler(PointerPressedEvent, (_, e) => + { + if (e.GetCurrentPoint(button).Properties.IsLeftButtonPressed) + { + press = e; + start = e.GetPosition(button); + } + }, RoutingStrategies.Tunnel); + button.AddHandler(PointerReleasedEvent, (_, _) => press = null, RoutingStrategies.Tunnel); + button.AddHandler(PointerMovedEvent, async (_, e) => + { + if (press == null || !e.GetCurrentPoint(button).Properties.IsLeftButtonPressed) + { + return; + } + + var position = e.GetPosition(button); + if (Math.Abs(position.X - start.X) < 6 && Math.Abs(position.Y - start.Y) < 6) + { + return; + } + + var pressed = press; + press = null; + try + { + var transfer = new DataTransfer(); + transfer.Add(DataTransferItem.Create(AssaDrawViewModel.LibraryShapeFormat, item)); + await DragDrop.DoDragDropAsync(pressed, transfer, DragDropEffects.Copy); + } + catch (Exception exception) + { + Se.LogError(exception, "ASSA draw: could not drag shape from palette"); + } + finally + { + close(); + } + }, RoutingStrategies.Tunnel); + } + private static Border MakeStripSeparator() { return new Border diff --git a/tests/UI/Features/Assa/AssaDrawCanvasUndoTests.cs b/tests/UI/Features/Assa/AssaDrawCanvasUndoTests.cs index 7d9caffd210..aab2fbafe1a 100644 --- a/tests/UI/Features/Assa/AssaDrawCanvasUndoTests.cs +++ b/tests/UI/Features/Assa/AssaDrawCanvasUndoTests.cs @@ -104,4 +104,66 @@ public void PointDrag_IsOneUndoStep() DrawSettings.ShowGrid = oldGrid; } } + + private static void Drag(AssaDrawWindow window, AssaDrawCanvas canvas, float x1, float y1, float x2, float y2) + { + var start = ToWindow(window, canvas, x1, y1); + window.MouseMove(start); + window.MouseDown(start, MouseButton.Left); + window.MouseMove(ToWindow(window, canvas, (x1 + x2) / 2, (y1 + y2) / 2), RawInputModifiers.LeftMouseButton); + window.MouseMove(ToWindow(window, canvas, x2, y2), RawInputModifiers.LeftMouseButton); + window.MouseUp(ToWindow(window, canvas, x2, y2), MouseButton.Left); + Dispatcher.UIThread.RunJobs(); + } + + [AvaloniaFact] + public void DragShape_WithSelectTool_MovesIt() + { + var oldGrid = DrawSettings.ShowGrid; + var (window, vm) = Open("{\\p1}m 400 400 l 800 400 800 800 400 800{\\p0}"); + try + { + vm.SelectToolCommand.Execute(null); + Drag(window, vm.Canvas!, 600, 600, 700, 650); + Assert.Equal(500, vm.Shapes[0].GetBounds().Left, 0); + + // Second drag, now that it is selected + Drag(window, vm.Canvas!, 700, 650, 800, 650); + Assert.Equal(600, vm.Shapes[0].GetBounds().Left, 0); + } + finally + { + window.Close(); + DrawSettings.ShowGrid = oldGrid; + } + } + + [AvaloniaFact] + public void DragBubbleWithText_AndTextBox_MovesThem() + { + var oldGrid = DrawSettings.ShowGrid; + var (window, vm) = Open(string.Empty); + try + { + vm.PickLibraryShapeCommand.Execute(ShapeLibrary.BuiltIn[0]); + Drag(window, vm.Canvas!, 400, 300, 1000, 800); + Assert.Single(vm.Shapes); + var left = vm.Shapes[0].GetBounds().Left; + vm.ShapeText = "Hello"; + Dispatcher.UIThread.RunJobs(); + Drag(window, vm.Canvas!, 700, 450, 800, 450); + Assert.Equal(left + 100, vm.Shapes[0].GetBounds().Left, 0); + + vm.TextToolCommand.Execute(null); + Drag(window, vm.Canvas!, 100, 100, 500, 200); + Assert.Equal(2, vm.Shapes.Count); + Drag(window, vm.Canvas!, 300, 150, 300, 250); + Assert.Equal(200, vm.Shapes[1].GetBounds().Top, 0); + } + finally + { + window.Close(); + DrawSettings.ShowGrid = oldGrid; + } + } } diff --git a/tests/UI/Features/Assa/AssaDrawShapeLibraryTests.cs b/tests/UI/Features/Assa/AssaDrawShapeLibraryTests.cs index 339ccf3035f..a7e358e22ae 100644 --- a/tests/UI/Features/Assa/AssaDrawShapeLibraryTests.cs +++ b/tests/UI/Features/Assa/AssaDrawShapeLibraryTests.cs @@ -163,4 +163,21 @@ Point ToWindow(double x, double y) window.Close(); } } + + [Fact] + public void DropFromPalette_PlacesDefaultSize_CenteredOnTheDrop_AndSelectsIt() + { + var vm = MakeViewModel(); + var item = ShapeLibrary.BuiltIn.First(i => i.Name == Se.Language.Assa.DrawShapeArrow); + + vm.DropLibraryShape(item, 600, 400); + + var shape = Assert.Single(vm.Shapes); + var (left, top, right, bottom) = shape.GetBounds(); + Assert.Equal(600, (left + right) / 2, 0); + Assert.Equal(400, (top + bottom) / 2, 0); + Assert.Equal(1920 / 5f, right - left, 0); + Assert.Same(item, vm.CurrentLibraryShape); + Assert.Same(shape, vm.SelectedTreeItem?.Shape); + } } From b59e62e361e52b9d2c7c1e82d8f47d8acb73dc58 Mon Sep 17 00:00:00 2001 From: niksedk Date: Tue, 13 Oct 2026 23:26:17 +0200 Subject: [PATCH 129/306] ASSA draw: palette drag carries the shape name as text (macOS crash) macOS aborts on a drag whose pasteboard is empty, which an in-process format alone leaves it. The text also lets the drop find the shape when the platform only hands the text back. Co-Authored-By: Claude Opus 5.5 --- .../Assa/AssaDraw/AssaDrawViewModel.cs | 23 +++++++++++++++++-- .../Features/Assa/AssaDraw/AssaDrawWindow.cs | 7 +++++- 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/src/ui/Features/Assa/AssaDraw/AssaDrawViewModel.cs b/src/ui/Features/Assa/AssaDraw/AssaDrawViewModel.cs index 3f764bf44a8..45f944868c6 100644 --- a/src/ui/Features/Assa/AssaDraw/AssaDrawViewModel.cs +++ b/src/ui/Features/Assa/AssaDraw/AssaDrawViewModel.cs @@ -2059,7 +2059,7 @@ public bool ImportSvgText(string svgText) internal void OnDragOver(object? sender, DragEventArgs e) { - if (e.DataTransfer.Contains(LibraryShapeFormat)) + if (GetDraggedLibraryShape(e) != null) { e.DragEffects = GetCanvasDropPoint(e) != null ? DragDropEffects.Copy : DragDropEffects.None; e.Handled = true; @@ -2070,6 +2070,25 @@ internal void OnDragOver(object? sender, DragEventArgs e) e.Handled = true; } + /// + /// The palette shape being dragged: the in-process item, or (if the platform only hands back + /// the text that went along) the library shape with that name. + /// + private static ShapeLibraryItem? GetDraggedLibraryShape(DragEventArgs e) + { + if (e.DataTransfer.Contains(LibraryShapeFormat) && e.DataTransfer.TryGetValue(LibraryShapeFormat) is { } item) + { + return item; + } + + if (!e.DataTransfer.Contains(DataFormat.Text) || e.DataTransfer.TryGetText() is not { } name) + { + return null; + } + + return ShapeLibrary.BuiltIn.Concat(ShapeLibrary.GetUserShapes()).FirstOrDefault(i => i.Name == name); + } + /// /// Where on the drawing a drag is, or null when it is not over the canvas. /// @@ -2098,7 +2117,7 @@ public void DropLibraryShape(ShapeLibraryItem item, float x, float y) /// internal void OnDrop(object? sender, DragEventArgs e) { - if (e.DataTransfer.TryGetValue(LibraryShapeFormat) is { } item) + if (GetDraggedLibraryShape(e) is { } item) { e.Handled = true; if (GetCanvasDropPoint(e) is { } point) diff --git a/src/ui/Features/Assa/AssaDraw/AssaDrawWindow.cs b/src/ui/Features/Assa/AssaDraw/AssaDrawWindow.cs index 68b84e4f438..baa265237ae 100644 --- a/src/ui/Features/Assa/AssaDraw/AssaDrawWindow.cs +++ b/src/ui/Features/Assa/AssaDraw/AssaDrawWindow.cs @@ -937,8 +937,13 @@ private static void AddPaletteDrag(Button button, ShapeLibraryItem item, Action press = null; try { + // macOS aborts the app on a drag with nothing it can put on its pasteboard (an + // in-process format only) - the shape's name goes along as text + var dataItem = new DataTransferItem(); + dataItem.Set(AssaDrawViewModel.LibraryShapeFormat, item); + dataItem.SetText(item.Name); var transfer = new DataTransfer(); - transfer.Add(DataTransferItem.Create(AssaDrawViewModel.LibraryShapeFormat, item)); + transfer.Add(dataItem); await DragDrop.DoDragDropAsync(pressed, transfer, DragDropEffects.Copy); } catch (Exception exception) From 1d1c1baf6aafc26aa5147b3dda6576418c11388f Mon Sep 17 00:00:00 2001 From: niksedk Date: Wed, 14 Oct 2026 05:53:22 +0200 Subject: [PATCH 130/306] Deep-bind HarfBuzz when distro packages move the native lib (#16014) HarfBuzzNativeFix only looked for libHarfBuzzSharp.so next to the app. The Arch AUR package installs the single-file binary to /usr/bin and the native library to /usr/lib, so the fix silently fell back to the default load without RTLD_DEEPBIND and the system libharfbuzz (pulled in by libmpv) interposed hb_* calls again - a SIGSEGV when shaping Indic text. Also look next to the symlink-resolved executable, and finally dlopen the bare library name so the dynamic loader's own search finds it. Co-Authored-By: Claude Opus 5.5 --- src/libuilogic/HarfBuzzNativeFix.cs | 59 +++++++++++++++++++++++++---- 1 file changed, 51 insertions(+), 8 deletions(-) diff --git a/src/libuilogic/HarfBuzzNativeFix.cs b/src/libuilogic/HarfBuzzNativeFix.cs index aadf074d4c3..ad2f4fa4f90 100644 --- a/src/libuilogic/HarfBuzzNativeFix.cs +++ b/src/libuilogic/HarfBuzzNativeFix.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.IO; using System.Runtime.InteropServices; @@ -21,6 +22,7 @@ public static class HarfBuzzNativeFix { private const int RtldNow = 2; private const int RtldDeepBind = 8; + private const string LibraryFileName = "libHarfBuzzSharp.so"; [UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate IntPtr DlopenDelegate(string fileName, int flags); @@ -68,7 +70,8 @@ public static void Apply() foreach (var path in CandidatePaths()) { - if (!File.Exists(path)) + // A bare file name (no directory) is resolved by the dynamic loader's own search. + if (Path.IsPathRooted(path) && !File.Exists(path)) { continue; } @@ -112,13 +115,53 @@ public static void Apply() return null; } - private static string[] CandidatePaths() + private static List CandidatePaths() { - var baseDir = AppContext.BaseDirectory; - return - [ - Path.Combine(baseDir, "libHarfBuzzSharp.so"), - Path.Combine(baseDir, "runtimes", "linux-x64", "native", "libHarfBuzzSharp.so"), - ]; + var paths = new List(); + AddDirectory(paths, AppContext.BaseDirectory); + + // Packages may symlink the executable into /usr/bin (AppContext.BaseDirectory is then + // the symlink's folder), so also look next to the real executable. + try + { + var processPath = Environment.ProcessPath; + if (!string.IsNullOrEmpty(processPath)) + { + var target = File.ResolveLinkTarget(processPath, returnFinalTarget: true)?.FullName ?? processPath; + AddDirectory(paths, Path.GetDirectoryName(target)); + } + } + catch + { + // Ignore - the other candidates still apply. + } + + // Distro packages may move the native library out of the app folder entirely, e.g. the + // Arch AUR package installs the binary to /usr/bin and libHarfBuzzSharp.so to /usr/lib + // (issue #16014). A bare name lets dlopen search LD_LIBRARY_PATH, the ld cache and the + // default library folders - the same lookup .NET's default resolution would end up using. + paths.Add(LibraryFileName); + + return paths; + } + + private static void AddDirectory(List paths, string? directory) + { + if (string.IsNullOrEmpty(directory)) + { + return; + } + + foreach (var path in new[] + { + Path.Combine(directory, LibraryFileName), + Path.Combine(directory, "runtimes", "linux-x64", "native", LibraryFileName), + }) + { + if (!paths.Contains(path)) + { + paths.Add(path); + } + } } } From ba09d7d465b053312850b8c12d6ff78c32e6e553 Mon Sep 17 00:00:00 2001 From: niksedk Date: Wed, 14 Oct 2026 05:54:39 +0200 Subject: [PATCH 131/306] Run grid/text box shortcuts when waveform or video player has focus Clicking a line in the waveform (or a video player button/slider) moved keyboard focus there, and "Subtitle list view & text box" shortcuts such as Toggle dialog dashes were only dispatched with the grid or a text box focused - so they did nothing until the grid or text box was clicked again. They act on the grid selection, so dispatch them from these controls too (after waveform shortcuts), like SE 4 (#16013). Co-Authored-By: Claude Opus 5.5 --- src/ui/Features/Main/MainViewModel.cs | 18 ++- .../Main/WaveformFocusGridShortcutTests.cs | 129 ++++++++++++++++++ 2 files changed, 146 insertions(+), 1 deletion(-) create mode 100644 tests/UI/Features/Main/WaveformFocusGridShortcutTests.cs diff --git a/src/ui/Features/Main/MainViewModel.cs b/src/ui/Features/Main/MainViewModel.cs index e44f2f36d8c..93e1bc58ec5 100644 --- a/src/ui/Features/Main/MainViewModel.cs +++ b/src/ui/Features/Main/MainViewModel.cs @@ -33438,7 +33438,8 @@ internal void OnKeyDownHandler(object? sender, KeyEventArgs keyEventArgs) } } - if (AudioVisualizer != null && AudioVisualizer.IsFocused) + var isWaveformFocused = AudioVisualizer != null && AudioVisualizer.IsFocused; + if (isWaveformFocused) { var relayCommand = _shortcutManager.CheckShortcuts(keyEventArgs, CategoryWaveform); if (relayCommand != null) @@ -33449,6 +33450,21 @@ internal void OnKeyDownHandler(object? sender, KeyEventArgs keyEventArgs) } } + // Selecting a line by clicking the waveform (or using the video player's buttons/sliders) + // moves focus there, which left SubtitleGridAndTextBox shortcuts like "Toggle dialog + // dashes" dead until the grid or text box was clicked again. They act on the grid + // selection, so run them from these controls too, like SE 4 did (#16013). + if (isWaveformFocused || GetVideoPlayerControl()?.IsKeyboardFocusWithin == true) + { + var relayCommand = _shortcutManager.CheckShortcuts(keyEventArgs, CategorySubtitleGridAndTextBox); + if (relayCommand != null) + { + keyEventArgs.Handled = true; + relayCommand.Execute(null); + return; + } + } + // Route SubtitleGridAndTextBox shortcuts when either edit box is focused — the // original/translation box should behave the same as the normal one (e.g. so // "Toggle dialog dashes" works in whichever text box has focus, like SE 4). diff --git a/tests/UI/Features/Main/WaveformFocusGridShortcutTests.cs b/tests/UI/Features/Main/WaveformFocusGridShortcutTests.cs new file mode 100644 index 00000000000..d1b8ff610cd --- /dev/null +++ b/tests/UI/Features/Main/WaveformFocusGridShortcutTests.cs @@ -0,0 +1,129 @@ +using Avalonia.Controls; +using Avalonia.Headless; +using Avalonia.Headless.XUnit; +using Avalonia.Input; +using Avalonia.Threading; +using Avalonia.VisualTree; +using Microsoft.Extensions.DependencyInjection; +using Nikse.SubtitleEdit; +using Nikse.SubtitleEdit.Core.Common; +using Nikse.SubtitleEdit.Features.Main; +using Nikse.SubtitleEdit.Features.Options.Shortcuts; +using Nikse.SubtitleEdit.Logic; +using Nikse.SubtitleEdit.Logic.Config; + +namespace UITests.Features.Main; + +/// +/// Selecting a line by clicking the waveform moves keyboard focus to the waveform, and the +/// "Subtitle list view & text box" shortcuts (e.g. Toggle dialog dashes) were then only checked +/// when the grid or a text box had focus - so they did nothing until the user clicked the grid or +/// text box again (#16013). SE 4 ran them regardless, and they act on the grid selection. +/// +public class WaveformFocusGridShortcutTests +{ + private static readonly string[] Chord = ["Control", "Shift", nameof(Key.F11)]; + + [AvaloniaFact] + public void ToggleDialogDashes_Runs_WhenWaveformIsFocused() + { + WithShortcut(nameof(MainViewModel.ToggleDialogDashesCommand), Chord, () => + { + var (window, vm) = ShowMainWindowWithOneLine("Hi!" + Environment.NewLine + "Hello."); + try + { + Assert.NotNull(vm.AudioVisualizer); + vm.AudioVisualizer!.Focus(); + Settle(window); + Assert.True(vm.AudioVisualizer.IsFocused); + + window.KeyPressQwerty(PhysicalKey.F11, RawInputModifiers.Control | RawInputModifiers.Shift); + Settle(window); + + Assert.StartsWith("-", vm.Subtitles[0].Text); + } + finally + { + window.Close(); + } + }); + } + + [AvaloniaFact] + public void ToggleDialogDashes_Runs_WhenVideoPlayerControlHasFocus() + { + WithShortcut(nameof(MainViewModel.ToggleDialogDashesCommand), Chord, () => + { + var (window, vm) = ShowMainWindowWithOneLine("Hi!" + Environment.NewLine + "Hello."); + try + { + var videoPlayer = vm.GetVideoPlayerControl(); + Assert.NotNull(videoPlayer); + var slider = videoPlayer!.GetVisualDescendants().OfType().First(s => s.Focusable); + slider.Focus(); + Settle(window); + Assert.True(videoPlayer.IsKeyboardFocusWithin); + + window.KeyPressQwerty(PhysicalKey.F11, RawInputModifiers.Control | RawInputModifiers.Shift); + Settle(window); + + Assert.StartsWith("-", vm.Subtitles[0].Text); + } + finally + { + window.Close(); + } + }); + } + + private static void WithShortcut(string actionName, string[] keys, Action test) + { + var original = Se.Settings.Shortcuts.Where(s => s.ActionName == actionName).ToList(); + Se.Settings.Shortcuts.RemoveAll(s => s.ActionName == actionName); + Se.Settings.Shortcuts.Add(new SeShortCut(actionName, [.. keys], ShortcutCategory.SubtitleGridAndTextBox.ToString())); + try + { + test(); + } + finally + { + Se.Settings.Shortcuts.RemoveAll(s => s.ActionName == actionName); + Se.Settings.Shortcuts.AddRange(original); + } + } + + private static (Window Window, MainViewModel Vm) ShowMainWindowWithOneLine(string text) + { + var services = new ServiceCollection(); + services.AddSubtitleEditServices(); + Locator.Services = services.BuildServiceProvider(); + + var window = new Window { Width = 1400, Height = 900 }; + MainView.NextHostWindow = window; + var view = new MainView(); + window.Content = view; + window.Show(); + Dispatcher.UIThread.RunJobs(); + window.UpdateLayout(); + + var vm = (MainViewModel)view.DataContext!; + window.SuppressSaveChangesPromptOnClose(vm); + vm.Subtitles.Add(new SubtitleLineViewModel(new Paragraph(text, 0, 2000), null!) { Number = 1 }); + Settle(window); + + vm.SelectedSubtitleIndex = 0; + vm.SubtitleGrid.SelectedItem = vm.Subtitles[0]; + Settle(window); + + return (window, vm); + } + + private static void Settle(Window window) + { + for (var pump = 0; pump < 5; pump++) + { + Dispatcher.UIThread.RunJobs(); + window.UpdateLayout(); + } + } +} From f24fd9e8123c8e9de8759718ed65849e4967e33d Mon Sep 17 00:00:00 2001 From: niksedk Date: Wed, 14 Oct 2026 05:58:17 +0200 Subject: [PATCH 132/306] Surround with: keep whole line selected even without text box focus (#16015) Co-Authored-By: Claude Opus 5.5 --- src/ui/Features/Main/MainViewModel.cs | 9 ++++++--- .../TextBoxUtils/TextBoxSurroundToggler.cs | 16 +++++++++++----- .../Shared/TextBoxSurroundTogglerTests.cs | 19 +++++++++++++++++++ 3 files changed, 36 insertions(+), 8 deletions(-) diff --git a/src/ui/Features/Main/MainViewModel.cs b/src/ui/Features/Main/MainViewModel.cs index e44f2f36d8c..b0707895195 100644 --- a/src/ui/Features/Main/MainViewModel.cs +++ b/src/ui/Features/Main/MainViewModel.cs @@ -17899,8 +17899,11 @@ private void SurroundWith(string surroundLeft, string surroundRight, SurroundWit return; } - var focusedTextBox = selectedItems.Count == 1 ? GetFocusedTextBoxWrapper() : null; - var reselectAll = TextBoxSurroundToggler.IsWholeTextSelected(focusedTextBox); + // Not gated on focus: the edit box keeps (and shows) its selection without focus, and on + // Windows the box can briefly report no focus during an Alt shortcut - the stale range + // then cut off the last characters again (#16015). + var editTextBox = selectedItems.Count == 1 ? GetFocusedTextBoxWrapper() ?? EditTextBox : null; + var reselectAll = TextBoxSurroundToggler.IsWholeTextSelected(editTextBox); var newTexts = TextBoxSurroundToggler.ApplyToTexts( behavior, scope, surroundLeft, selectedItems.Select(p => p.Text), surroundRight); @@ -17911,7 +17914,7 @@ private void SurroundWith(string surroundLeft, string surroundRight, SurroundWit if (reselectAll) { - TextBoxSurroundToggler.SelectAllAfterTextChange(focusedTextBox!); + TextBoxSurroundToggler.SelectAllAfterTextChange(editTextBox!); } _updateAudioVisualizer = true; diff --git a/src/ui/Features/Shared/TextBoxUtils/TextBoxSurroundToggler.cs b/src/ui/Features/Shared/TextBoxUtils/TextBoxSurroundToggler.cs index 5ee6e0b0717..efcdd584363 100644 --- a/src/ui/Features/Shared/TextBoxUtils/TextBoxSurroundToggler.cs +++ b/src/ui/Features/Shared/TextBoxUtils/TextBoxSurroundToggler.cs @@ -81,14 +81,20 @@ public static bool IsWholeTextSelected(ITextBoxWrapper? tb) /// Selects the whole text again once the new subtitle text has reached the text box. Without /// this the text box keeps the old selection range, which no longer covers the end of the /// (now longer) text - so the next surround lands before the last characters (#15693). + /// Selected right away (the binding has already updated the text) and once more after the + /// rest of the key handling has run, so nothing later in the same key press can leave the + /// old range behind (#16015). /// public static void SelectAllAfterTextChange(ITextBoxWrapper tb) { - Dispatcher.UIThread.Post(() => - { - tb.SelectionStart = 0; - tb.SelectionEnd = tb.Text?.Length ?? 0; - }); + SelectAll(tb); + Dispatcher.UIThread.Post(() => SelectAll(tb), DispatcherPriority.Background); + } + + private static void SelectAll(ITextBoxWrapper tb) + { + tb.SelectionStart = 0; + tb.SelectionEnd = tb.Text?.Length ?? 0; } /// diff --git a/tests/UI/Features/Shared/TextBoxSurroundTogglerTests.cs b/tests/UI/Features/Shared/TextBoxSurroundTogglerTests.cs index 0b456897b15..7ac80fcb233 100644 --- a/tests/UI/Features/Shared/TextBoxSurroundTogglerTests.cs +++ b/tests/UI/Features/Shared/TextBoxSurroundTogglerTests.cs @@ -141,6 +141,25 @@ public void SelectAllAfterTextChange_WholeTextSelected_SelectionCoversLongerText Assert.True(TextBoxSurroundToggler.IsWholeTextSelected(wrapper)); } + // #16015: the whole text is selected right away, and again after the rest of the key press - + // so a selection reset later in the same key handling does not leave the stale range behind. + [AvaloniaFact] + public void SelectAllAfterTextChange_SelectsAtOnceAndAgainAfterLaterReset() + { + var textBox = MakeTextBox("\"Hello?\"", 0, 6); + var wrapper = new TextBoxWrapper(textBox); + + TextBoxSurroundToggler.SelectAllAfterTextChange(wrapper); + Assert.Equal(0, textBox.SelectionStart); + Assert.Equal(textBox.Text!.Length, textBox.SelectionEnd); + + textBox.SelectionEnd = 6; + Dispatcher.UIThread.RunJobs(); + + Assert.Equal(0, textBox.SelectionStart); + Assert.Equal(textBox.Text.Length, textBox.SelectionEnd); + } + [AvaloniaFact] public void ToggleSelection_OnlyWhiteSpaceSelected_ReturnsFalseAndKeepsText() { From b3205afea81d630eee6d480fcf015a8847faf76d Mon Sep 17 00:00:00 2001 From: niksedk Date: Wed, 14 Oct 2026 06:16:25 +0200 Subject: [PATCH 133/306] Release v5.3.0: sync all language files, fix formatting, changelog Version - Se.Version and English.json set to v5.3.0; every translation file's "version" set to v5.3.0. - change-log.txt: new v5.3.0 overview section (changes since v5.2.0). Language sync (all 34 translation files vs English.json) - ~18,500 missing strings added and translated into each native language, matching each file's existing terminology and register. Keys are inserted in English.json order and follow each file's key casing (Dutch/Norwegian/Spanish stay PascalCase). - Obsolete keys no longer present in English.json removed. - Every file verified: no missing keys, no empty strings, and the same placeholders ({0}, {0:#,##0}...) and tags (,
, ...) as English. Formatting / tag fixes - English source string errorLoadBinaryZeroes showed a literal ">" in the message box; now "->". HTML entities (>/<) removed from all translations too. - French: 88 empty strings (ASSA draw, nOCR trainer) translated. - Romanian: 18 error/help messages were cut off after the first paragraph; restored in full. errorLoadRar said "7-Zip". - Portuguese (Brazil): xLinesMerged lost {0} again, maxLines held an unrelated string again, charactersPerSecond used {0:0.00} on a string. - Japanese subtitleReloadedChangedOutsideX dropped {0}. - German fixInvalidItalicTagsExample lost the broken it illustrates; empty fixCommonErrorsSettingsTitle translated. - Bulgarian removeLineBreaksExample lost its
. - Danish/Dutch: extra blank line in errorLoadBinaryZeroes. - Stray leading/trailing whitespace trimmed; indentation normalized. Co-Authored-By: Claude Opus 5.5 --- change-log.txt | 73 + src/ui/Assets/Languages/Arabic.json | 1224 ++++++++++----- src/ui/Assets/Languages/Azerbaijani.json | 870 ++++++++++- src/ui/Assets/Languages/Bulgarian.json | 870 ++++++++++- src/ui/Assets/Languages/Catalan.json | 793 +++++++++- src/ui/Assets/Languages/Central Kurdish.json | 874 ++++++++++- .../Assets/Languages/ChineseSimplified.json | 246 ++- .../Assets/Languages/ChineseTraditional.json | 63 +- src/ui/Assets/Languages/Czech.json | 16 +- src/ui/Assets/Languages/Danish.json | 1222 ++++++++++----- src/ui/Assets/Languages/Dutch.json | 847 +++++----- src/ui/Assets/Languages/English.json | 4 +- src/ui/Assets/Languages/Finnish.json | 1220 ++++++++++----- src/ui/Assets/Languages/French.json | 185 +-- src/ui/Assets/Languages/German.json | 549 ++++++- src/ui/Assets/Languages/Greek.json | 1224 ++++++++++----- src/ui/Assets/Languages/Hebrew.json | 1220 ++++++++++----- src/ui/Assets/Languages/Hungarian.json | 525 ++++++- src/ui/Assets/Languages/Indonesian.json | 1224 ++++++++++----- src/ui/Assets/Languages/Italian.json | 25 +- src/ui/Assets/Languages/Japanese.json | 13 +- src/ui/Assets/Languages/Korean.json | 3 +- src/ui/Assets/Languages/Macedonian.json | 1220 ++++++++++----- src/ui/Assets/Languages/Norwegian.json | 1236 ++++++++++----- src/ui/Assets/Languages/Persian.json | 881 ++++++++++- src/ui/Assets/Languages/Polish.json | 54 +- .../Assets/Languages/Portuguese (Brazil).json | 9 +- src/ui/Assets/Languages/Portuguese.json | 1220 ++++++++++----- src/ui/Assets/Languages/Romanian.json | 1300 ++++++++++------ src/ui/Assets/Languages/Russian.json | 803 +++++++++- src/ui/Assets/Languages/Spanish.json | 1236 ++++++++++----- src/ui/Assets/Languages/Swedish.json | 1218 ++++++++++----- src/ui/Assets/Languages/Thai.json | 1224 ++++++++++----- src/ui/Assets/Languages/Turkish.json | 1368 +++++++++-------- src/ui/Assets/Languages/Ukrainian.json | 1222 ++++++++++----- src/ui/Assets/Languages/Vietnamese.json | 1204 ++++++++++----- .../Config/Language/Main/LanguageMain.cs | 2 +- src/ui/Logic/Config/Se.cs | 2 +- 38 files changed, 19956 insertions(+), 7533 deletions(-) diff --git a/change-log.txt b/change-log.txt index 677ca598495..6e8d217f15c 100644 --- a/change-log.txt +++ b/change-log.txt @@ -3,6 +3,79 @@ Subtitle Edit Changelog ----------------------------------------------------------------------------------------------------- +v5.3.0 (14th of October 2026) + +Subtitle Edit 5.3 collects the work of 26 betas and 4 release candidates. +A summary of the changes since v5.2.0 (full details in the pre-release entries below): + +New features: +* Command palette (Ctrl+Shift+K) +* Import subtitles directly from DVD (IFO/VOB) with title picker, fast rip and exact cell timing +* Improve time codes (forced alignment) - re-time a roughly synced subtitle, with a speech-to-text check and waveform preview +* Remux video - video, multi-track audio (with mixing) and soft subtitles, also embedded CEA-608 captions +* Cut video transitions and fades, burn-in for selected lines, and 3D (half side-by-side / top-bottom) burn-in and image export +* Generate background music locally (ACE-Step) +* Compare: merge view, sync points, time tolerance and taking only text or timing per row +* Custom shortcut slots built from steps, AltGr modifier, per-plugin shortcuts and many new shortcuts +* Actor picker, "Set style..." quick pick, and "Shot in"/"Shot out" grid columns +* Reload the subtitle when it is changed outside Subtitle Edit, and "Frame numbers" time code mode +* Taskbar/Dock progress and prevent sleep for long jobs (speech to text, text to speech, burn-in, batch convert) + +Video player and waveform: +* New FFmpeg video player with hardware decoding, audio track picker, real frame stepping and Linux sound +* Experimental mpv Metal video player on macOS, deinterlace setting, and customizable player controls +* Editor-style layout 14 with a track timeline, and layouts 12 and 13 (text box below the video) +* Experimental fast waveform renderer, "Show speech only" waveform, grid color and remembered zoom +* Waveform toolbar: initial text box, move-lines buttons, line breaks and wrapping + +Speech to text: +* Crisp ASR: speaker detection (Sortformer), VAD choice (Silero, FireRedVAD, WebRTC, Whisper-VAD) and "Isolate speech" +* New models: Parakeet Ultra, Phonon-2, ReazonSpeech/Japanese Parakeet, Voxtral F16 and Index-Echo +* OpenAI-compatible API works with vLLM; retry of failed clips on selected lines + +Text to speech: +* New engines: OpenAI-compatible TTS, ElevenLabs v4/v4 Turbo, KugelAudio and Supertonic-3 +* Voice manager with transcripts, voice packs and copying voices between engines +* "Remove original speech", "Speak from current line" and "Play with speech" +* Optional Sidon cleaning of voice-clone reference audio + +Auto-translate and AI: +* New engines: LM Studio advanced and API Route, model browsing, and new llama.cpp models (Index-Translate, TranslatePsy-AfriSLM) +* Translate in place, translate one file into several languages in batch convert, and a no-prompt shortcut +* 22 more languages for the AI engines, MyMemory without an API key, and line breaks kept for ChatGPT/Anthropic +* AI review: prompt templates and reference context + +OCR: +* nOCR: much better accuracy, new Cyrillic database, "Optimize database", and a redesigned trainer +* Find and Replace in the OCR window, remembered pre-processing settings, and a per-language spell check dictionary +* Romanian and Croatian OCR fix lists; better word splitting + +Subtitle formats: +* Read CEA-608/708 closed captions from .ts, .mkv, .mp4, .mxf, .vob, .mpg and .m2v +* New: EZTitles binary (.ezt), PSP UMD Video, HD-DVD sup, Cheetah Caption ASC, ELR Studio and more SE 4 formats opened again +* Transport stream, MP4 caption, CEA-708 and Blu-ray sup timing fixes; PAC code page detection and secondary code page +* ASSA draw: redesigned with layers, shape library, curve tools, SVG import and live preview +* ASSA styles: overwrite/keep both on import, reorder, font collector; Source view syntax highlighting + +Batch convert and seconv: +* Batch convert: named presets, new functions and filters, multi-language translate, OCR language per file, and many format settings +* seconv: DVD sup and IMSC image outputs, openai-compatible and DeepL translate, nOCR optimize, and more SE 4 formats + +Accessibility and UI: +* Screen reader names across settings and tool windows, F1 help in 145 more dialogs, and remembered dialog sizes/positions +* Settings show one category at a time with search and back/forward navigation; settings auto-backup +* macOS-standard default shortcuts and a new rounded app icon + +Translations and engines: +* All language files synced with English +* Updated engines: Crisp ASR v0.8.43, whisper.cpp v1.9.5, llama.cpp b11553, CrispEmbed v0.17.12, audio.cpp v0.9.1, WhisperX build 103, FFmpeg 9.0.2 (Windows), libVLC 3.0.24 (Windows), Avalonia 12.1.4 and Flatpak runtime 26.08 + +A big thank you to everyone who tested the pre-releases, reported issues and +contributed code and translations - and a special thanks to Anthropic for +sponsoring a Claude subscription used in the development of this release :) + +----------------------------------------------------------------------------------------------------- + v5.3.0-rc4 (13th of October 2026) * Add Whisper-VAD to the Crisp ASR VAD choices - thx jodilife diff --git a/src/ui/Assets/Languages/Arabic.json b/src/ui/Assets/Languages/Arabic.json index c953db993de..ba159b2f623 100644 --- a/src/ui/Assets/Languages/Arabic.json +++ b/src/ui/Assets/Languages/Arabic.json @@ -1,12 +1,30 @@ { "title": "العربية", - "version": "v5.2.0-rc7", + "version": "v5.3.0", "translatedBy": "", "cultureName": "ar", "general": { "abort": "إجهاض", "actor": "الممثل", "actors": "الممثلون", + "setActorDotDotDot": "تعيين الممثل...", + "actorPickerTitle": "تعيين الممثل", + "actorPickerFilterHint": "اكتب للتصفية، أو اكتب اسم ممثل جديد", + "actorPickerKeysHint": "اضغط رقمًا أو Enter لتعيين الممثل. Alt+أعلى/أسفل يغيّر الترتيب، وDelete يزيل الممثل.", + "actorPickerNewActorX": "ممثل جديد \"{0}\"", + "actorPickerLinesSelectedX": "الأسطر المحددة: {0}", + "setStyleDotDotDot": "تعيين النمط...", + "stylePickerTitle": "تعيين النمط", + "stylePickerFilterHint": "اكتب للتصفية، أو اكتب اسم نمط جديد", + "stylePickerKeysHint": "اضغط رقمًا (1-9، 0) لتعيين النمط على جميع الأسطر المحددة.", + "stylePickerNewStyleX": "نمط جديد \"{0}\"", + "stylePickerNewStyleInfo": "اضغط Enter لإضافة هذا النمط بالإعدادات الافتراضية وتعيينه على الأسطر المحددة. استخدم \"إدارة الأنماط...\" لتغيير خطه وألوانه وموضعه لاحقًا.", + "stylePickerCurrentStyleX": "النمط الحالي: {0}", + "stylePickerLinesUsingStyleX": "الأسطر التي تستخدم هذا النمط: {0}", + "stylePickerMarginsXYZ": "يسار {0}، يمين {1}، عمودي {2}", + "stylePickerSetStyle": "_تعيين النمط", + "stylePickerManageStyles": "إدارة الأنماط...", + "stylePickerManageStylesHint": "إغلاق هذه النافذة وفتح مدير الأنماط لتحرير الخطوط والألوان والموضع والمزيد", "add": "إضافة", "addDotDotDot": "إضافة...", "addToNamesListCaseSensitive": "إضافة إلى قائمة الأسماء (حساس لحالة الأحرف)", @@ -15,7 +33,6 @@ "adjustAlpha": "ضبط ألفا (الشفافية)", "adjustDisplayDuration": "ضبط مدة العرض", "adjustment": "الضبط", - "advanced": "متقدم", "advancedSettings": "الإعدادات المتقدمة", "after": "بعد", "alignment": "المحاذاة", @@ -60,7 +77,6 @@ "bdnXml": "BDN/xml", "bdnXml8Bit": "BDN/xml 8-bit", "before": "قبل", - "beforeX": "قبل \"{0}\"", "beginning": "البداية", "bluRaySup": "Blu-ray (sup)", "bold": "عريض", @@ -72,7 +88,6 @@ "bookmarkEdit": "تحرير العلامة المرجعية", "bookmarkEditForSelectedLinesX": "تحرير العلامة المرجعية لـ {0} من الأسطر المحددة", "bookmarksList": "سرد العلامات المرجعية", - "borderColor": "لون الحدود", "borderStyle": "نمط الحدود", "bottomCenter": "أسفل، وسط", "bottomLeft": "أسفل، يسار", @@ -99,7 +114,6 @@ "changeCasing": "تغيير حالة الأحرف", "changeFormatting": "تغيير التنسيق", "changeFrameRate": "تغيير معدل الإطارات", - "changeLanguageFilter": "تغيير عامل تصفية اللغة...", "changeOnce": "تغيير مرة واحدة", "changeSpeed": "تغيير السرعة", "character": "الحرف", @@ -141,7 +155,6 @@ "conversionCancelledByUser": "أُلغي التحويل من قبل المستخدم", "convert": "تحويل", "converted": "حُوِّل", - "convertingDotDotDot": "جارٍ التحويل...", "convertingXofYDotDoDot": "جارٍ تحويل {0:#,###,##0}/{1:#,###,##0}...", "copy": "نسخ", "copyImageToClipboard": "نسخ الصورة إلى الحافظة", @@ -152,7 +165,6 @@ "couldNotOpenFileXErrorY": "تعذر فتح الملف \"{0}\". خطأ: {1}", "count": "العدد", "cps": "حرف/ثانية", - "currentSubtitle": "الترجمة الحالية", "cudaRuntime": "وقت تشغيل CUDA", "currentVideoPosition": "موضع الفيديو الحالي", "custom": "مخصص", @@ -212,7 +224,6 @@ "expand": "توسيع", "export": "تصدير", "exportDotDotDot": "تصدير...", - "exportToX": "تصدير إلى {0}", "extendAfter": "تمديد/تقصير إلى السطر التالي", "extendBefore": "تمديد/تقصير إلى السطر السابق", "extendSelectedLinesToNextShotChangeOrNextSubtitle": "تمديد الأسطر المحددة إلى تغيير اللقطة التالي (أو الترجمة التالية)", @@ -229,7 +240,6 @@ "fileExtension": "امتداد الملف", "fileName": "اسم الملف", "fileNameX": "اسم الملف: {0}", - "fileNameXAndSize": "اسم الملف: {0} ({1})", "fileSaved": "حُفِظ الملف", "fileSavedToX": "حُفِظ الملف في {0}", "fileXAlreadyExists": "الملف \"{0}\" موجود بالفعل.", @@ -239,8 +249,6 @@ "find": "بحث", "findNext": "البحث عن التالي", "findTextX": "البحث عن نص - {0}", - "fiveHundredMilliseconds": "500 ms", - "fiveSeconds": "خمس ثوانٍ", "fix": "إصلاح", "fixCommonErrors": "إصلاح الأخطاء الشائعة", "fixRightToLeft": "إصلاح الاتجاه من اليمين إلى اليسار", @@ -264,14 +272,12 @@ "foundXInLineYZ": "عُثر على \"{0}\" في السطر {1}، الموضع {2}", "foundXMatches": "عُثر على {0} تطابق", "frameRate": "معدل الإطارات", - "frameRateX": "معدل الإطارات: {0:0.0###}", "frames": "الإطارات", "from": "من", "fromCurrentVideoPosition": "من موضع الفيديو الحالي", "fullScreen": "ملء الشاشة", "gap": "الفجوة", "general": "عام", - "generalText": "عام", "generate": "توليد", "generateImportShotChanges": "توليد/استيراد تغييرات اللقطات", "generating": "جارٍ التوليد...", @@ -289,12 +295,8 @@ "help": "مساعدة", "hex": "ست عشري", "hide": "إخفاء", - "hidePreview": "إخفاء المعاينة", "history": "السجل", "horizontal": "أفقي", - "hourMinutesSecondsDecimalSeparatorMilliseconds": "ساعة:دقيقة:ثانية{0}ms", - "hourMinutesSecondsFrames": "ساعة:دقيقة:ثانية:إطارات", - "ip": "IP", "id": "ID", "image": "الصورة", "imageSaved": "حُفِظت الصورة", @@ -355,6 +357,7 @@ "mergeSelectedLinesDialog": "دمج الأسطر المحددة كحوار", "mergeWithLineAfterAndAutoBreak": "دمج مع السطر التالي مع الكسر التلقائي", "mergeWithLineAfterKeepBreaks": "دمج مع السطر التالي (مع الاحتفاظ بالكسور)", + "mergeWithLineAfterAndUnbreak": "الدمج مع السطر التالي وإلغاء فواصل الأسطر", "mergeWithLineBeforeAndAutoBreak": "دمج مع السطر السابق مع الكسر التلقائي", "mergeWithLineBeforeKeepBreaks": "دمج مع السطر السابق (مع الاحتفاظ بالكسور)", "middleCenter": "وسط، وسط", @@ -369,13 +372,13 @@ "moveAllShotChangeOneFrameForward": "تحريك كل تغييرات اللقطات إطاراً واحداً للأمام", "multiMode": "الوضع المتعدد", "moveDown": "تحريك لأسفل", + "increase": "زيادة", + "decrease": "إنقاص", "moveUp": "تحريك لأعلى", "moveToTop": "نقل إلى الأعلى", "moveToBottom": "نقل إلى الأسفل", "multipleReplace": "استبدال متعدد", - "mute": "كتم", "name": "الاسم", - "negative": "سالب", "new": "جديد", "newDotDotDot": "جديد...", "newProfile": "ملف تعريف جديد", @@ -405,20 +408,15 @@ "offsetY": "الإزاحة Y", "ok": "_موافق", "oneFile": "ملف واحد", - "oneHundredMilliseconds": "100 ms", - "oneSecond": "ثانية واحدة", "oneSecondBack": "ثانية واحدة للخلف", "oneSecondForward": "ثانية واحدة للأمام", - "onlineVideoFeatureNotAvailable": "الميزة غير متوفرة للفيديو عبر الإنترنت", "openContainingFolder": "فتح المجلد المحتوي", "openDictionaryFolder": "فتح مجلد القاموس", "openFile": "فتح ملف", "openImageBasedSubtitle": "فتح ترجمة قائمة على الصور", "openImageFile": "فتح ملف صورة", - "openOriginalSubtitleFile": "فتح ملف الترجمة الأصلية...", "openOriginalSubtitleFileTitle": "فتح ملف الترجمة الأصلية", "openOutputFolder": "فتح مجلد الإخراج", - "openSubtitle": "فتح ترجمة...", "openSubtitleFileTitle": "فتح ملف الترجمة", "openSubtitles": "فتح ترجمات...", "openVideoFile": "فتح ملف فيديو...", @@ -433,11 +431,6 @@ "outlineWidth": "عرض المخطط الخارجي", "outputFolder": "مجلد الإخراج", "outputProperties": "خصائص الإخراج", - "overlap": "التداخل", - "overlapNextX": "تداخل التالي ({0:#;##0.###})", - "overlapPreviousLineX": "تداخل السطر السابق ({0:#;##0.###})", - "overlapStartAndEnd": "تداخل البداية والنهاية", - "overlapX": "تداخل ({0:#;##0.###})", "overwriteExistingFiles": "الكتابة فوق الملفات الموجودة", "overwriteFilesInFolderX": "الكتابة فوق الملفات في المجلد: \"{0}\"", "overwriteQuestion": "الكتابة فوق الملف؟", @@ -450,6 +443,7 @@ "percent": "النسبة المئوية", "pickLayer": "تعيين الطبقة", "pickOllamaModel": "اختيار نموذج Ollama", + "pickModel": "اختيار النموذج", "llamaCppUseRemoteServer": "استخدام خادم خارجي (URL)", "llamaCppEngineSettings": "إعدادات محرك llama.cpp", "llamaCppEngineSettingsSubtitle": "إصدار خادم llama.cpp المحلي", @@ -501,7 +495,6 @@ "refresh": "تحديث", "region": "المنطقة", "regularExpression": "التعبير النمطي", - "regularExpressionIsNotValid": "التعبير النمطي غير صالح!", "relativeToCurrentVideoPosition": "بالنسبة إلى موضع الفيديو الحالي", "remove": "إزالة", "removeAlignment": "إزالة المحاذاة", @@ -527,7 +520,7 @@ "reset": "إعادة تعيين", "resizePanels": "تغيير حجم اللوحات", "resolution": "الدقة", - "resolutionX": "الدقة: {0}", + "retry": "إعادة المحاولة", "reverseRightToLeftStartEnd": "عكس بداية/نهاية الكتابة من اليمين إلى اليسار", "right": "يمين", "rightToLeft": "من اليمين إلى اليسار", @@ -567,8 +560,6 @@ "selectedlinesX": "الأسطر المحددة: {0}", "sensitivity": "الحساسية", "separator": "الفاصل", - "sessionKey": "مفتاح الجلسة", - "sessionKeyGenerate": "توليد مفتاح جديد", "setEnd": "تعيين النهاية", "setEndAndGoToNext": "تعيين النهاية والانتقال إلى التالي", "setEndAddNewAndGoToNew": "تعيين النهاية وإضافة سطر جديد والانتقال إليه", @@ -591,8 +582,9 @@ "shadowColor": "لون الظل", "shadowWidth": "عرض الظل", "shiftLineBy": "إزاحة السطر بمقدار", + "shotIn": "بداية اللقطة", + "shotOut": "نهاية اللقطة", "shortcut": "الاختصار", - "shortcutX": "الاختصار: {0}", "shortcuts": "الاختصارات", "show": "الظهور", "showActorColumn": "إظهار عمود \"الممثل\"", @@ -603,17 +595,19 @@ "showStartColumn": "إظهار عمود \"البداية\"", "showHideColumn": "إظهار عمود \"الإخفاء\"", "showHistory": "إظهار السجل", + "clearHistory": "مسح السجل", + "removeFromHistory": "إزالة من السجل", "showLayerColumn": "إظهار عمود \"الطبقة\"", "showPreview": "إظهار المعاينة", "showShotChangesList": "إظهار قائمة تغييرات اللقطات", + "showShotInColumn": "إظهار عمود \"بداية اللقطة\"", + "showShotOutColumn": "إظهار عمود \"نهاية اللقطة\"", "showStyleColumn": "إظهار عمود \"النمط\"", "showTeletext": "إظهار عمود التلتكست في عرض القائمة", "showTimeCodes": "إظهار الرموز الزمنية", "showWpmColumn": "إظهار عمود \"كلمة/دقيقة\"", "showPixelWidthColumn": "إظهار عمود \"عرض البكسل\"", "shrink": "تصغير", - "singleBox": "مربع واحد", - "singleLineLengths": "طول السطر الواحد:", "singleMode": "الوضع الفردي", "textTotalLength": "النص - الطول الإجمالي", "textSingleLineMaxLength": "النص - الحد الأقصى لطول السطر الواحد", @@ -662,16 +656,16 @@ "strikeout": "يتوسطه خط", "style": "النمط", "styleExaggeration": "مبالغة النمط", - "styleLanguage": "النمط / اللغة", "styles": "الأنماط", "subtitleFile": "ملف الترجمة", + "subtitleFileChangedOutsideX": "تم تغيير الملف \"{0}\" خارج Subtitle Edit.\n\nهل تريد إعادة تحميله وفقدان تغييراتك؟", "subtitleFileName": "اسم ملف الترجمة", "subtitleFileSaved": "تم حفظ ملف الترجمة", "subtitleFileSavedToX": "تم حفظ ملف الترجمة في {0}", "subtitleFiles": "ملفات الترجمة", "subtitleFormats": "صيغ الترجمة", "subtitleLoadedX": "تم تحميل الترجمة: {0}", - "subtitleXOfY": "الترجمة {0} من {1}", + "subtitleReloadedChangedOutsideX": "تمت إعادة تحميل الترجمة (تم تغييرها خارج Subtitle Edit): {0}", "suffix": "اللاحقة", "suggestions": "الاقتراحات", "sync": "المزامنة", @@ -683,7 +677,6 @@ "teletextColor": "لون التلتكست", "teletextLine": "سطر التلتكست", "tenHours": "10 ساعات", - "tenMilliseconds": "10 مللي ثانية", "text": "النص", "textColor": "لون النص", "textFiles": "الملفات النصية", @@ -710,9 +703,6 @@ "topRight": "أعلى-يمين", "top": "أعلى", "totalAdjustmentX": "إجمالي التعديل: {0}", - "totalFramesX": "إجمالي الإطارات: {0:#;##0.##}", - "totalLengthX": "الطول الإجمالي: {0}", - "totalLengthXSplitLine": "الطول الإجمالي: {0} (تقسيم السطر!)", "translate": "ترجمة", "translatePercentX": "جارٍ الترجمة: {0}%", "translateRow": "ترجمة الصف", @@ -721,6 +711,7 @@ "type": "النوع", "unbreak": "إلغاء فصل النص", "unbreakLines": "إلغاء فصل الأسطر", + "undelete": "استعادة المحذوف", "underline": "تسطير", "undo": "تراجع", "unknown": "غير معروف", @@ -740,13 +731,11 @@ "usages": "الاستخدامات", "use": "استخدام", "useAlways": "الاستخدام دائمًا", - "useLargerFontForThisWindow": "استخدام خط أكبر لهذه النافذة", "useOnce": "الاستخدام مرة واحدة", "useOutputFolder": "استخدام مجلد الإخراج", "useSourceFolder": "استخدام المجلد المصدر", "useSourceResolution": "استخدام دقة المصدر", "userName": "اسم المستخدم", - "userNameAlreadyInUse": "اسم المستخدم مستخدم بالفعل", "vertical": "عمودي", "video100MsBack": "الفيديو، 100 مللي ثانية للخلف", "video100MsForward": "الفيديو، 100 مللي ثانية للأمام", @@ -760,14 +749,25 @@ "videoCustom3ForwardX": "الفيديو، مللي ثانية مخصصة ({0:#,###,##0}) للأمام، 3", "videoCustom4BackX": "الفيديو، مللي ثانية مخصصة ({0:#,###,##0}) للخلف، 4", "videoCustom4ForwardX": "الفيديو، مللي ثانية مخصصة ({0:#,###,##0}) للأمام، 4", - "videoEncodingX": "ترميز الفيديو: {0}", + "videoCustom1BackAndPauseX": "الفيديو، أجزاء من الثانية مخصصة ({0:#,###,##0}) للخلف ثم إيقاف مؤقت، 1", + "videoCustom1ForwardAndPauseX": "الفيديو، أجزاء من الثانية مخصصة ({0:#,###,##0}) للأمام ثم إيقاف مؤقت، 1", + "videoCustom2BackAndPauseX": "الفيديو، أجزاء من الثانية مخصصة ({0:#,###,##0}) للخلف ثم إيقاف مؤقت، 2", + "videoCustom2ForwardAndPauseX": "الفيديو، أجزاء من الثانية مخصصة ({0:#,###,##0}) للأمام ثم إيقاف مؤقت، 2", + "videoCustom3BackAndPauseX": "الفيديو، أجزاء من الثانية مخصصة ({0:#,###,##0}) للخلف ثم إيقاف مؤقت، 3", + "videoCustom3ForwardAndPauseX": "الفيديو، أجزاء من الثانية مخصصة ({0:#,###,##0}) للأمام ثم إيقاف مؤقت، 3", + "videoCustom4BackAndPauseX": "الفيديو، أجزاء من الثانية مخصصة ({0:#,###,##0}) للخلف ثم إيقاف مؤقت، 4", + "videoCustom4ForwardAndPauseX": "الفيديو، أجزاء من الثانية مخصصة ({0:#,###,##0}) للأمام ثم إيقاف مؤقت، 4", "videoExtension": "امتداد ملف الفيديو", "videoFile": "ملف الفيديو", "videoFileGenerated": "تم إنشاء ملف الفيديو", "videoFileGeneratedX": "تم إنشاء ملف الفيديو: \"{0}\"", + "videoFilesGenerated": "تم توليد ملفات الفيديو", + "xVideosGenerated": "تم توليد {0} من مقاطع الفيديو", + "xOfYVideosGenerated": "تم توليد {0} من {1} من مقاطع الفيديو", + "savedInX": "تم الحفظ في {0}", + "savedInXFolders": "تم الحفظ في {0} من المجلدات", "videoFiles": "ملفات الفيديو", "videoAndAudioFiles": "ملفات الفيديو والصوت", - "videoInformation": "معلومات الفيديو", "videoOffset": "إزاحة الفيديو", "videoOneFrameBack": "الفيديو، إطار واحد للخلف", "videoOneFrameForward": "الفيديو، إطار واحد للأمام", @@ -778,7 +778,6 @@ "videoPlayer": "مشغل الفيديو", "videoPosition": "موضع الفيديو", "videoResolution": "دقة الفيديو", - "viewX": "عرض {0}", "visible": "مرئي", "voice": "الصوت", "volume": "مستوى الصوت", @@ -787,9 +786,7 @@ "waveformCenterOnVideoPosition": "توسيط الموجة الصوتية على موضع الفيديو", "waveformPasteFromClipboard": "لصق من الحافظة", "waveformSpectrogram": "الموجة الصوتية/الطيف الترددي", - "webServiceUrl": "URL خدمة الويب", "width": "العرض", - "windowPositionAndSize": "موضع النافذة وحجمها", "wordsPerMin": "كلمة/دقيقة", "pixelWidth": "عرض البكسل", "wpm": "كلمة/دقيقة", @@ -803,8 +800,6 @@ "noBookmarksFound": "لم يُعثر على المزيد من العلامات المرجعية", "xRequiresAnApiKey": "{0} يتطلب مفتاح API", "xRequiresAValidUrl": "{0} يتطلب URL صالحًا (مثل \"http://...\" أو \"https://...\")", - "xSeconds": "{0:0.0##} ثانية", - "xSubtitles": "{0:#,###,##0} ترجمة", "yes": "نعم", "zeroBytes": "0 بايت", "copyToClipboard": "نسخ إلى الحافظة", @@ -826,10 +821,8 @@ "playbackSpeed": "سرعة التشغيل", "hoursMinutesSecondsMilliseconds": "HH:MM:SS.FFF", "imageFiles": "ملفات الصور", - "openAiCompatibleStt": "STT متوافق مع OpenAI", "openAiCompatibleSttEndpoint": "URL نقطة نهاية STT", "openAiCompatibleSttApiKey": "مفتاح API", - "openAiCompatibleSttModel": "النموذج", "openAiCompatibleSttExtraHeaders": "ترويسات إضافية", "openAiCompatibleSttTimeout": "المهلة (ثوانٍ)", "openAiCompatibleSttLanguage": "تلميح اللغة", @@ -839,6 +832,9 @@ "dashScopeSttRegion": "المنطقة", "dashScopeSttEnableWords": "طوابع زمنية على مستوى الكلمة", "dashScopeSttRegionKeyHint": "ملاحظة: مفاتيح API الخاصة بـ Alibaba Cloud Model Studio مرتبطة بالمنطقة، تأكد من أن المنطقة المحددة تطابق المنطقة التي أُنشئ فيها مفتاح API (الصين مقابل الدولية).", + "googleCloudSttProjectId": "معرّف المشروع (فقط لـ Application Default Credentials)", + "googleCloudSttBucketName": "حاوية التخزين (فارغ = تلقائي)", + "googleCloudSttDynamicBatching": "التجميع الديناميكي (أرخص بنحو 5 مرات، دون ضمان لزمن الاستجابة)", "onlineSttApiKeyMissing": "مفتاح API مطلوب. الرجاء إدخال مفتاح API والمحاولة مرة أخرى.", "openAiCompatibleSttAutoTranscribeOnAudioSelection": "التفريغ التلقائي لتحديد الموجة الصوتية الجديد عبر تحويل الكلام إلى نص", "openAiCompatibleSttStream": "بث الاستجابة", @@ -848,11 +844,9 @@ "configurationRequired": "التهيئة مطلوبة", "transcriptionError": "خطأ في التفريغ الصوتي", "transcriptionComplete": "اكتمل التفريغ الصوتي", - "transcribingWithOpenAi": "جارٍ التفريغ الصوتي باستخدام خدمة OpenAI...", "processingResponse": "جارٍ معالجة الاستجابة...", "unauthorizedApiKey": "غير مصرح: مفتاح API غير صالح أو أذونات غير كافية.", "requestTimeout": "انتهت مهلة الطلب: استغرق الخادم وقتًا طويلاً للاستجابة.", - "noSegmentsReturned": "لم تُرجع أي مقاطع من خدمة التفريغ الصوتي.", "openAiCompatibleSttUrlMissing": "URL نقطة نهاية STT المتوافقة مع OpenAI مطلوب. الرجاء إدخال URL والمحاولة مرة أخرى.", "openAiCompatibleSttUrlNotResponding": "لم تستجب نقطة نهاية STT المتوافقة مع OpenAI:\n{0}\n\nالرجاء التحقق من URL ومن أن الخادم قيد التشغيل.", "transcriptionFailed": "فشل التفريغ الصوتي", @@ -898,14 +892,15 @@ "multipleReplace": "استبدال _متعدد...", "fillSelectedLinesWithClipboard": "ملء الأسطر المحددة بنص الحافظة", "goToLineNumber": "الانت_قال إلى رقم السطر...", + "commandPalette": "_لوحة الأوامر...", "rightToLeftMode": "وضع اليم_ين إلى اليسار", "modifySelectionDotDotDot": "تعديل الت_حديد...", "tools": "أ_دوات", - "toolsSelectedLines": "أ_دوات (الأسطر المحددة)", "adjustDurations": "_ضبط المدد...", "applyDurationLimits": "تطبيق _حدود المدة...", "batchConvert": "تحويل د_فعات...", "beautifyTimeCodes": "تحسين الرموز الزمنية...", + "improveTimeCodes": "تحسين الرموز الزمنية (المحاذاة القسرية)...", "bridgeGaps": "جسر ال_فجوات بين الأسطر...", "applyMinGap": "تطبيق الحد الأدنى للفجوة بين الأسطر...", "changeCasing": "تغيير _حالة الأحرف...", @@ -1044,6 +1039,9 @@ "selectCurrentLineWhilePlayingHint": "تحديد السطر الحالي أثناء التشغيل {0}", "videoPosition": "موضع الفيديو {0}", "videoPositionTextBox": "مربع نص موضع الفيديو {0}", + "toolbarLineBreakX": "فاصل سطر {0} (يستمر باقي شريط الأدوات في صف جديد)", + "initialText": "النص الأولي للسطر المحدد", + "initialTextHint": "نص السطر المحدد كما كان عند تحديد السطر", "hideWaveformToolbar": "إخفاء شريط الأدوات {0}", "resetZoomAndSpeed": "إعادة تعيين التكبير وسرعة التشغيل {0}", "removeBlankLines": "إزالة الأسطر الفارغة {0}", @@ -1057,6 +1055,15 @@ "textPlayHint": "تشغيل الترجمة الحالية والتوقف عند النهاية {0}", "textPauseHint": "إيقاف التشغيل مؤقتًا {0}", "textNextHint": "تشغيل الترجمة التالية والتوقف عند النهاية {0}", + "moveSelectedLines": "نقل الأسطر المحددة (أزرار للخلف/للأمام)", + "moveSelectedLinesAndFollowing": "نقل الأسطر المحددة وكل ما يليها (أزرار للخلف/للأمام)", + "moveAllLines": "نقل جميع الأسطر (أزرار للخلف/للأمام)", + "moveSelectedLinesBackHint": "نقل الأسطر المحددة {0} مللي ثانية للخلف {1}", + "moveSelectedLinesForwardHint": "نقل الأسطر المحددة {0} مللي ثانية للأمام {1}", + "moveSelectedLinesAndFollowingBackHint": "نقل الأسطر المحددة وكل ما يليها {0} مللي ثانية للخلف {1}", + "moveSelectedLinesAndFollowingForwardHint": "نقل الأسطر المحددة وكل ما يليها {0} مللي ثانية للأمام {1}", + "moveAllLinesBackHint": "نقل جميع الأسطر {0} مللي ثانية للخلف {1}", + "moveAllLinesForwardHint": "نقل جميع الأسطر {0} مللي ثانية للأمام {1}", "guessStartNoLineSelected": "تخمين البداية: لم يُحدَّد أي سطر", "guessStartTimeCodesLocked": "تخمين البداية: الرموز الزمنية مقفلة", "guessStartNoWaveform": "تخمين البداية: لا يوجد مخطط موجي", @@ -1078,12 +1085,15 @@ "charactersPerSecond": "حرف/ثانية: {0}", "chooseColumn": "اختيار العمود", "columnPaste": "لصق في عمود", + "commandPaletteTitle": "لوحة الأوامر", + "commandPaletteSearchPlaceholder": "اكتب للبحث عن الأوامر...", + "commandPaletteNoCommandsFound": "لا توجد أوامر مطابقة", "createdEmptyTranslation": "أُنشئت ترجمة فارغة من الملف الحالي", "deleteText": "حذف النص", "deleteTextAndShiftCellsUp": "حذف النص وإزاحة الخلايا لأعلى", "endTimeMustBeAfterStartTime": "يجب أن يكون وقت النهاية بعد وقت البداية.", "errorLoad7Zip": "يبدو أن هذا الملف مضغوط بصيغة 7-Zip.\n\nلا يستطيع Subtitle Edit فتح الملفات المضغوطة.", - "errorLoadBinaryZeroes": "عذراً، هذا الملف لا يحتوي إلا على أصفار ثنائية!\n\nإذا كنت قد حررت هذا الملف باستخدام Subtitle Edit فقد تجد نسخة احتياطية عبر القائمة ملف -> استعادة النسخة الاحتياطية التلقائية...", + "errorLoadBinaryZeroes": "عذراً، هذا الملف لا يحتوي إلا على أصفار ثنائية!\n\nإذا كنت قد حررت هذا الملف باستخدام Subtitle Edit فقد تجد نسخة احتياطية عبر القائمة ملف -> استعادة النسخة الاحتياطية التلقائية...", "errorLoadGZip": "يبدو أن هذا الملف مضغوط بصيغة GZip.\n\nلا يستطيع Subtitle Edit فتح الملفات المضغوطة.", "errorLoadLargeFile": "هذا الملف أكبر من أن يُفتح كملف ترجمة.", "errorLoadVideoFilePrompt": "يبدو أن هذا الملف ملف فيديو:\n{0}\n\nيمكنك فتح فيديو عبر \"فيديو -> فتح فيديو\" أو بسحب الفيديو وإفلاته على مشغّل الفيديو.\n\nهل تريد فتحه كملف فيديو؟", @@ -1116,18 +1126,20 @@ "numberOfLinesEvenlyDistributedX": "وُزّع {0} من الأسطر بالتساوي", "oneLineCopiedFromOriginal": "نُسخ سطر واحد من الملف الأصلي", "oneLineCopiedToOriginal": "نُسخ سطر واحد إلى الترجمة الأصلية", + "onlineOnlyVideo": "فيديو متاح عبر الإنترنت فقط", + "onlineOnlyVideoXSizeYDownloadAndOpen": "ملف الفيديو \"{0}\" ({1}) مخزّن عبر الإنترنت فقط، على سبيل المثال في Dropbox أو iCloud Drive، ويجب تنزيله قبل أن يمكن فتحه.\n\nهل تريد تنزيله وفتحه الآن؟", "oneLineMerged": "دُمج سطر واحد", "oneLineSwitched": "بُدّل سطر واحد", "overwriteExistingCells": "الكتابة فوق الخلايا الموجودة", "overwriteOrShiftCellsDown": "الكتابة فوق الخلايا أو إزاحتها لأسفل", "parsingMatroskaFile": "جارٍ تحليل ملف Matroska...", + "readingDvdSubtitles": "جارٍ قراءة ترجمات DVD...", "pasteFromClipboardDotDotDot": "لصق من الحافظة...", "redoPerformed": "نُفّذت الإعادة", "redoPerformedXActionLeft": "نُفّذت الإعادة (الإجراءات المتبقية: {0})", "removedUnicodeControlCharactersX": "أُزيلت محارف يونيكود للتحكم من {0} من الأسطر", "removedXBlankLines": "أُزيل {0} من الأسطر الفارغة", "replacedXWithYCountZ": "استُبدل \"{0}\" بـ\"{1}\" ({2} من المواضع)", - "replacedXWithYInLineZ": "استُبدل \"{0}\" بـ\"{1}\" في السطر {2}", "reversedStartAndEndingsForRightToLeftX": "عُكست البدايات والنهايات لليمين إلى اليسار في {0} من الأسطر", "ruleProfileIsX": "ملف قواعد التحقق الآن \"{0}\"", "saveLanguageFile": "حفظ ملف اللغة", @@ -1135,7 +1147,6 @@ "shiftTextCellsDown": "إزاحة خلايا النص لأسفل", "singleLineLength": "طول السطر: ", "speedIsNowX": "السرعة الآن \"{0}\"", - "spellCheckResult": "اكتمل التدقيق الإملائي. \n\n• الكلمات المغيّرة: {0}\n• الكلمات المتخطاة: {1}", "subtitleGridFormattingX": "تنسيق قائمة الترجمة: {0}", "subtitleImportedFromMatroskaFile": "استُوردت الترجمة من ملف Matroska", "textDown": "النص لأسفل", @@ -1162,13 +1173,14 @@ "trimmedXLines": "شُذّب {0} من أسطر الترجمة", "pastedXLinesOverSelectedLines": "لُصق {0} من الأسطر فوق الأسطر المحددة", "pastedXOfYLinesOverSelectedLines": "لُصق {0} من أصل {1} سطر فوق الأسطر المحددة - لم يتسع الباقي للتحديد", + "insertedXLinesFromClipboard": "تم إدراج {0} سطر (أسطر) من الحافظة", "openOriginalDifferentNumberOfSubtitlesXY": "ملف الترجمة الأصلي لا يحتوي على نفس عدد الأسطر في ملف الترجمة الحالي.\n\n• أسطر الملف الأصلي: {0}\n• أسطر الملف الحالي: {1}", - "importXMatchingOriginalLines": "استيراد {0} من الأسطر الأصلية المتطابقة؟", "originalTextReadOnly": "النص الأصلي (للقراءة فقط)", "originalTextEditMode": "النص الأصلي (وضع التحرير)", "originalIsReadOnlyNotSaved": "الترجمة الأصلية مرجع للقراءة فقط ولم تُحفظ", "originalIsEmptyNotSaved": "الترجمة الأصلية لا تحتوي على أي نص ولم تُحفظ", "originalIsReadOnlyReference": "الترجمة الأصلية مفتوحة كمرجع للقراءة فقط", + "closeTranslationQuestionXY": "هل تريد إغلاق الترجمة \"{0}\"؟\n\nتصبح الترجمة الأصلية \"{1}\" هي الترجمة التي تحررها. لا يتم تغيير ملف الترجمة نفسه.", "allowEditOfOriginalSubtitle": "السماح بتحرير الترجمة الأصلية", "showAllOriginalLinesX": "إظهار كل الأسطر الأصلية ({0} بلا مطابق هنا)", "showAllOriginalLinesHint": "تظهر الأسطر التي لا مطابق لها كصفوف إضافية، لترى ما ينقص الترجمة.", @@ -1203,6 +1215,7 @@ "fileExportedInFormatXToY": "صُدّر الملف بصيغة {0} إلى {1}", "fileExportedInFormatXToFileY": "صُدّر الملف بصيغة \"{0}\" إلى الملف \"{1}\"", "fixedXLines": "أُصلح {0} من الأسطر", + "toolXChangedYLines": "{0}: تم تغيير {1} سطر", "transcriptionCompletedWithXLines": "اكتمل التفريغ الصوتي بعدد {0} من الأسطر", "openSubtitleFileX": "فتح ملف الترجمة \"{0}\"؟", "replacedXOccurrences": "استُبدل {0} من المواضع", @@ -1234,13 +1247,9 @@ "file": { "ebuSaveOptions": { "title": "خيارات حفظ EBU", - "generalSubtitleInformation": "معلومات عامة عن الترجمة", "codePageNumber": "رقم صفحة الترميز", "diskFormatCode": "رمز صيغة القرص", "displayStandardCode": "رمز معيار العرض", - "colorRequiresTeletext": "الألوان تتطلب التلتكست!", - "alignmentRequiresTeletext": "المحاذاة تتطلب التلتكست!", - "teletextCharsShouldBe38": "يجب أن يكون 'الحد الأقصى لعدد الأحرف في كل صف' للتلتكست 38!", "characterCodeTable": "جدول الأحرف", "languageCode": "رمز اللغة", "originalProgramTitle": "عنوان البرنامج الأصلي", @@ -1257,7 +1266,6 @@ "maxNumberOfDisplayableRows": "الحد الأقصى لعدد الصفوف", "diskSequenceNumber": "الرقم التسلسلي للقرص", "totalNumberOfDisks": "إجمالي عدد الأقراص", - "import": "استيراد...", "textAndTimingInformation": "معلومات النص والتوقيت", "justificationCode": "رمز المحاذاة", "verticalPosition": "الموضع العمودي", @@ -1274,11 +1282,6 @@ "errors": "الأخطاء", "errorsX": "الأخطاء: {0}", "maxLengthError": "السطر {0} يتجاوز الحد الأقصى للطول ({1}) بمقدار {2}: {3}", - "textUnchangedPresentation": "عرض دون تغيير", - "textLeftJustifiedText": "نص محاذى لليسار", - "textCenteredText": "نص موسّط", - "textRightJustifiedText": "نص محاذى لليمين", - "useBoxForOneNewLine": "حدد 'استخدام إطار حول النص' لسطر جديد واحد فقط", "maxLengthErrorTeletextColored": "السطر {0}-{1}: يجب أن يكون الحد الأقصى 36 حرفاً (لا {2}) للنص الملون مزدوج الارتفاع", "maxLengthErrorTeletextWhite": "السطر {0}-{1}: يجب أن يكون الحد الأقصى 37 حرفاً (لا {2}) للنص الأبيض مزدوج الارتفاع", "maxLengthErrorTeletextSingleHeight": "السطر {0}: يجب أن يكون الحد الأقصى 38 حرفاً (لا {1}) للنص الأبيض أحادي الارتفاع", @@ -1303,22 +1306,29 @@ "formattingDotDotDot": "التنسيق...", "imageBasedSubtitleForEditDotDotDot": "ترجمة قائمة على الصور للتحرير...", "imageBasedSubtitleForOcrDotDotDot": "ترجمة قائمة على الصور لـ OCR...", + "dvdSubtitlesDotDotDot": "ترجمات من DVD (IFO/VOB)...", + "titleImportDvd": "استيراد ترجمات من DVD", + "dvdIfoFile": "ملف IFO", + "dvdVobFiles": "ملفات VOB", + "dvdVideoStandard": "معيار الفيديو", + "dvdStartRipping": "بدء الاستخراج", + "dvdRippingAborted": "تم إلغاء الاستخراج", + "dvdOpenIfoFile": "فتح ملف DVD IFO", + "dvdAddVobFiles": "إضافة ملفات VOB", + "dvdIfoFiles": "ملفات DVD IFO", + "dvdVobFileType": "ملفات DVD VOB", + "dvdDropHint": "افتح ملف IFO (VIDEO_TS\\VTS_01_0.IFO) أو أضف ملفات VOB - أو أفلتها هنا", + "dvdNotAnIfoFileX": "{0} ليس ملف DVD IFO", + "dvdMenuIfoNoTitleSetsX": "لم يتم العثور على مجموعات عناوين (VTS_xx_0.IFO) بجوار {0}", + "dvdEncryptedXOfY": "لا تزال {0} من أصل {1} من حزم الترجمة مشفرة بـ CSS - تم نسخ ملفات VOB هذه دون فك التشفير، لذا ستكون بعض صور الترجمة مشوهة.\n\nفك تشفير DVD باستخدام أداة استخراج DVD أولاً للحصول على نتيجة نظيفة.\n\nهل تريد المتابعة على أي حال؟", "splitTextAt": "تقسيم النص عند", "blankLines": "الأسطر الفارغة", "oneLineIsOneSubtitle": "كل سطر ترجمة واحدة", "twoLinesAreOneSubtitle": "كل سطرين ترجمة واحدة", "importFilesDotDotDot": "استيراد الملفات...", "multipleFiles": "استيراد من ملفات نصية متعددة (كل ملف ترجمة واحدة)", - "importOptions": "خيارات الاستيراد", - "autoSplitText": "تقسيم النص تلقائيًا", - "lineMode": "وضع السطر", "maxLineLength": "الحد الأقصى لطول السطر", - "maxLinesPerSubtitle": "الحد الأقصى للأسطر في كل ترجمة", - "minGapBetweenSubtitles": "الحد الأدنى للفجوة بين الترجمات", "mergeShortLines": "دمج الأسطر القصيرة", - "removeLinesWithoutLetters": "إزالة الأسطر الخالية من الأحرف", - "splitAtEndCharsSetting": "التقسيم عند أحرف النهاية", - "takeTimeFromCurrentFile": "أخذ التوقيت من الملف الحالي", "fixed": "ثابت", "numberOfSubtitlesX": "عدد الترجمات: {0}", "numberOfSubtitlesXForcedY": "عدد الترجمات: {0} ({1} مُجبَرة)", @@ -1330,14 +1340,18 @@ "forcedAlignerProgress": "جارٍ المحاذاة... النافذة {0} من {1}، {2} من {3} سطر", "forcedAlignerSetupTitle": "محاذاة الرموز الزمنية عبر المحاذي القسري", "forcedAlignerSetupIntro": "يطابق المحاذي القسري نص الترجمة الذي لديك بالفعل مع الصوت، دون نسخه أولاً. تُحاذى مقاطع الفيديو الطويلة على أجزاء، لذا فأي طول يعمل.", + "forcedAlignerEndsFromSpeech": "تعيين أوقات النهاية من الكلام المعزول (بطيء)", + "forcedAlignerEndsFromSpeechHint": "يجد المحاذي القسري موضع بداية السطر، لا موضع نهايته، لذا تأتي أوقات النهاية عادةً من وقت القراءة. عند تفعيل هذا الخيار، تُزال الموسيقى والمؤثرات الصوتية من الصوت أولاً وينتهي كل سطر حيث يصمت كلامه. يستغرق ذلك تقريبًا مدة الفيديو نفسه مع GPU - وأضعافها بدونه.", + "forcedAlignerIsolatingSpeech": "جارٍ عزل الكلام... {0}", + "forcedAlignerIsolatingSpeechFailed": "تعذّر عزل الكلام - تم تعيين أوقات النهاية من وقت القراءة بدلاً من ذلك.", "forcedAlignerModel": "نموذج المحاذاة", - "forcedAlignerDownloadEngine": "تنزيل / تحديث المحرك..." + "forcedAlignerDownloadEngine": "تنزيل / تحديث المحرك...", + "forcedAlignerEstimatedXOfY": "تعذّرت محاذاة {0} من أصل {1} سطر ووُضعت حسب وقت القراءة - رموزها الزمنية تقديرية فقط. على الأرجح النص أطول مما يُنطق في الفيديو." }, "export": { "exportImagesProfiles": "تصدير ملفات تعريف الصور", "leftRightMargin": "الهامش الأيسر/الأيمن", "topBottomMargin": "الهامش العلوي/السفلي", - "titleExportBluRaySup": "تصدير Blu-ray (sup)", "lineSpacingPercent": "تباعد الأسطر %", "paddingLeftRight": "الحشو الأيسر/الأيمن", "paddingTopBottom": "الحشو العلوي/السفلي", @@ -1378,10 +1392,21 @@ "titleExportDCinemaInteropPng": "D-Cinema interop/png", "titleExportDCinemaSmpte2014Png": "D-Cinema SMPTE 2014/png", "imageBasedSubtitleSaved": "تم حفظ الترجمة القائمة على الصور", - "titleExportBdnXml": "تصدير BDN/xml", "titleExportWebVttThumbnails": "تصدير WebVTT (صور مصغرة)", "fullFrameImage": "صورة بحجم الإطار الكامل", "fullFrameImageHint": "جعل كل صورة بحجم إطار الفيديو، مع وضع الترجمة في مكانها", + "stereo3D": "3D", + "stereo3DHint": "للفيديو ثلاثي الأبعاد: رسم الترجمة مرة واحدة في نصف الإطار الخاص بكل عين", + "halfSideBySide": "نصف جنبًا إلى جنب", + "halfTopBottom": "نصف أعلى/أسفل", + "depth3D": "العمق", + "depth3DHint": "مدى بروز الترجمة خارج الشاشة - القيمة الموجبة تقرّبها، والسالبة تدفعها للخلف", + "plane3D": "3D-Plane", + "plane3DHint": "إعطاء كل ترجمة العمق الذي يخزّنه قرص 3D Blu-ray لها، من ملف 3D-Plane (.ofs) أنشأه BD3D2MK3D أو OFSExtractor. يُستخدم العمق المحدد حيث لا يحتوي 3D-Plane على قيمة", + "openPlane3DTitle": "فتح ملف 3D-Plane", + "unableToLoadPlane3DX": "تعذّر تحميل 3D-Plane: {0}", + "plane3DXDepthYToZ": "{0} (العمق من {1} إلى {2})", + "plane3DXNoDepth": "{0} (لا توجد قيم عمق)", "textEffect": "تأثير النص", "textEffectSettingsTitle": "إعدادات تأثير النص", "textEffectPreset": "إعداد مسبق", @@ -1413,13 +1438,10 @@ "statistics": { "title": "الإحصائيات", "titleWithFileName": "الإحصائيات - {0}", - "generalStatistics": "إحصائيات عامة", - "mostUsed": "الأكثر استخدامًا...", "mostUsedLines": "الأسطر الأكثر استخدامًا", "mostUsedWords": "الكلمات الأكثر استخدامًا", "nothingFound": "لم يُعثر على شيء", "numberOfLinesX": "عدد أسطر الترجمة: {0:#,##0}", - "numberOfFilesX": "عدد ملفات الترجمة: {0:#,##0}", "lengthInFormatXinCharactersY": "عدد الأحرف كـ {0}: {1:#,###,##0}", "numberOfCharactersInTextOnly": "عدد الأحرف في النص فقط: {0:#,###,##0}", "totalDuration": "إجمالي مدة جميع الترجمات: {0:#,##0}", @@ -1460,7 +1482,6 @@ "gapMaximum": "الفجوة - الحد الأقصى: {0:#,##0} ms", "gapAverage": "الفجوة - المتوسط: {0:#,##0.##} ms", "gapExceedingMinimum": "الفجوة - أقل من الحد الأدنى ({0:#,##0} ms): {1} ({2:0.00}%)", - "export": "تصدير...", "subtitles": "الترجمات", "words": "الكلمات", "totalDurationShort": "المدة الإجمالية", @@ -1490,7 +1511,22 @@ "restoreAutoBackupFile": "استعادة ملف النسخة الاحتياطية التلقائية", "restoreXFromY": "هل تريد استعادة \"{0}\" من {1}؟", "deleteAllSubtitleBackups": "هل تريد حذف جميع ملفات النسخ الاحتياطية للترجمة؟", - "deleteAll": "حذف الكل" + "deleteAll": "حذف الكل", + "subtitles": "الترجمات", + "settingsBackupInfo": "يتم نسخ Settings.json احتياطيًا عند بدء Subtitle Edit، بحد أقصى مرة كل {0} يوم (أيام) وفقط إذا تغيرت الإعدادات. يتم الاحتفاظ بأحدث {1} نسخة احتياطية.", + "settingsBackupInfoEveryStart": "يتم نسخ Settings.json احتياطيًا في كل مرة يبدأ فيها Subtitle Edit، إذا تغيرت الإعدادات. يتم الاحتفاظ بأحدث {0} نسخة احتياطية.", + "settingsBackupOff": "النسخ الاحتياطي التلقائي للإعدادات متوقف (الخيارات → الإعدادات → ملف).", + "lastSettingsBackupX": "آخر نسخة احتياطية: {0}", + "noSettingsBackupsYet": "لا توجد نسخ احتياطية للإعدادات بعد", + "openSettingsBackupFolder": "فتح مجلد النسخ الاحتياطية للإعدادات", + "backUpSettingsNow": "نسخ احتياطي الآن", + "settingsBackedUp": "تم نسخ الإعدادات الحالية احتياطيًا.", + "restoreSettings": "استعادة الإعدادات", + "restoreSettingsFromX": "هل تريد استبدال إعداداتك الحالية بالنسخة الاحتياطية من {0}؟\n\nسيتم نسخ إعداداتك الحالية احتياطيًا أولاً. بعض التغييرات لا تسري إلا بعد إعادة تشغيل Subtitle Edit.", + "settingsRestored": "تمت استعادة الإعدادات. يرجى إعادة تشغيل Subtitle Edit لتسري جميع التغييرات.", + "settingsRestoreFailed": "تعذّرت استعادة الإعدادات: {0}", + "settingsBackupNotValid": "النسخة الاحتياطية ليست ملف إعدادات صالحًا، لذا تم الاحتفاظ بإعداداتك الحالية", + "deleteAllSettingsBackups": "هل تريد حذف جميع ملفات النسخ الاحتياطية للإعدادات؟" }, "propertiesDCinema": { "title": "خصائص D-Cinema SMPTE", @@ -1504,22 +1540,16 @@ "editRate": "معدل التحرير", "timeCodeRate": "معدل الرمز الزمني", "startTime": "وقت البداية", - "font": "الخط", "fontId": "ID الخط", "fontUri": "URI الخط", - "generate": "إنشاء", - "fontColor": "لون الخط", "chooseColor": "اختيار اللون", "fontEffect": "تأثير الخط", "effectColor": "لون التأثير", - "fontSize": "حجم الخط", "topBottomMargin": "الهامش العلوي/السفلي", "fadeUpTime": "وقت الظهور التدريجي", "fadeDownTime": "وقت الاختفاء التدريجي", - "frames": "الإطارات", "zPosition": "الموضع Z", - "zPositionHelp": "الأرقام الموجبة تُبعد النص، والأرقام السالبة تُقرّبه، وإذا كان الموضع Z صفراً فالنص ثنائي الأبعاد", - "export": "تصدير..." + "zPositionHelp": "الأرقام الموجبة تُبعد النص، والأرقام السالبة تُقرّبه، وإذا كان الموضع Z صفراً فالنص ثنائي الأبعاد" }, "webVtt": { "setStylesForSelectedLinesTitle": "تعيين أنماط WebVTT للأسطر المحددة", @@ -1552,20 +1582,66 @@ "compareOnlyInOneFile": "في ملف واحد فقط", "compareTextOrTimeDifference": "اختلاف النص/الوقت", "compareNumberDifference": "اختلاف الأرقام", - "ignoreLineBreaks": "تجاهل فواصل الأسطر", "ignoreWhitespace": "تجاهل المسافات البيضاء", "ignoreWhitespaceHint": "الأسطر التي تختلف في المسافات أو علامات الجدولة أو كسور الأسطر فقط لا تُعدّ مختلفة", "ignoreFormatting": "تجاهل التنسيق", - "onlyLookForDifferencesInText": "البحث عن الاختلافات في النص فقط", - "cannotCompareWithImageBasedSubtitles": "لا يمكن المقارنة مع الترجمات القائمة على الصور", - "statisticsTitle": "الإحصائيات", "ignoreFormattingHint": "الأسطر التي تختلف في وسوم التنسيق فقط، مثل أو {\\an8}، لا تُعدّ مختلفة", "showOnlyDifferencesInText": "الاختلافات في النص فقط", + "ignoreNumbering": "تجاهل الترقيم", + "ignoreNumberingHint": "الأسطر التي تختلف في رقمها فقط لا تُعد مختلفة", + "ignoreTimeDifferenceMs": "هامش الوقت المسموح (مللي ثانية)", + "ignoreTimeDifferenceMsHint": "أوقات البداية والنهاية التي تختلف بهذا العدد من أجزاء الثانية أو أقل لا تُعد مختلفة", + "compareDifferences": "الاختلافات", + "compareTextDifferences": "اختلافات النص", + "compareCurrent": "الحالي", + "compareReference": "المرجع", + "compareEditable": "قابل للتحرير", + "compareEditableHint": "التغييرات التي تُجرى هنا تعود إلى الترجمة عند النقر على تطبيق", + "compareReadOnly": "للقراءة فقط", + "compareTakeFromReference": "أخذ النص والتوقيت من المرجع", + "compareInsertFromReference": "إدراج هذا السطر في الترجمة الحالية", + "compareDeleteFromCurrent": "حذف هذا السطر من الترجمة الحالية", + "compareTakeText": "أخذ النص", + "compareTakeTiming": "أخذ التوقيت", + "compareEditLine": "تحرير السطر", + "compareEditHint": "Ctrl+Enter للحفظ، Esc للإلغاء", + "compareEdited": "محرَّر", + "compareOnePendingChange": "تغيير واحد معلّق", + "compareXPendingChanges": "{0} تغييرات معلّقة", + "compareDiscardXChanges": "هل تريد تجاهل {0} تغيير (تغييرات) أُجريت في المقارنة؟", + "compareXChangesApplied": "المقارنة: تم تطبيق {0} تغيير (تغييرات)", + "compareChangeTextX": "#{0} النص من المرجع", + "compareChangeTimingX": "#{0} التوقيت من المرجع", + "compareChangeTextAndTimingX": "#{0} النص والتوقيت من المرجع", + "compareChangeEditedX": "#{0} محرَّر", + "compareChangeInsertedX": "#{0} مُدرج", + "compareChangeDeletedX": "#{0} محذوف", + "compareSyncPoint": "نقطة مزامنة", + "compareSyncPointHint": "نقطة مزامنة - يُعرض هذان السطران دائمًا كزوج، وتُصفّ الأسطر التي فوقهما وتحتهما بشكل مستقل", + "compareSyncPickCurrent": "نقطة مزامنة: استخدام هذا السطر الحالي", + "compareSyncPickReference": "نقطة مزامنة: استخدام هذا السطر المرجعي", + "compareSyncRemove": "إزالة نقطة المزامنة", + "compareClearXSyncPoints": "مسح نقاط المزامنة ({0})", + "compareSyncCurrentPickedX": "تم اختيار الحالي #{0} كنقطة مزامنة - حدد السطر المرجعي المطابق وانقر على مزامنة", + "compareSyncReferencePickedX": "تم اختيار المرجع #{0} كنقطة مزامنة - حدد السطر الحالي المطابق وانقر على مزامنة", + "compareSyncWithCurrentX": "مزامنة مع الحالي #{0}", + "compareSyncWithReferenceX": "مزامنة مع المرجع #{0}", + "compareSyncApply": "المزامنة", + "compareSyncPointSetXY": "تم تعيين نقطة المزامنة: يُعرض الآن الحالي #{0} والمرجع #{1} كزوج", + "compareSyncAlreadyPairedXY": "الحالي #{0} والمرجع #{1} معروضان بالفعل كزوج بنفس البداية - لا شيء للمزامنة", + "compareSyncPointShiftedXYZW": "تم تعيين نقطة المزامنة: تمت إزاحة الحالي #{0}-#{1} بمقدار {2} ليبدأ مع المرجع #{3}", + "compareChangeSyncShiftXYZ": "تمت إزاحة #{0}-#{1} بمقدار {2} (نقطة مزامنة)", + "compareTimingDeltaHint": "مقدار تأخر (+) أو تقدم (−) بداية ونهاية المرجع عن السطر الحالي", "loadXFromFile": "تحميل \"{0}\" من الملف", + "compareLoadSavedFile": "تحميل الملف المحفوظ", "saveCompareHtmlTitle": "حفظ ملف مقارنة HTML", "pickMatroskaTrackX": "اختيار مسار Matroska - {0}", "pickTransportStreamTrackX": "اختيار مسار تدفق النقل - {0}", + "chapters": "الفصول", + "dvdVobFilesMissingX": "ملفات VOB مفقودة (تم العثور على {0}%)", "pickMp4TrackX": "اختيار مسار MP4 - {0}", + "pickMxfTrackX": "اختيار مسار MXF - {0}", + "pickMpegTrackX": "اختيار مسار MPEG - {0}", "rosettaProperties": "خصائص Timed Text Rosetta IMSC", "rosettaFontSize": "حجم الخط (ارتفاع الصف)", "propertyTimeBase": "الأساس الزمني", @@ -1575,7 +1651,6 @@ "propertyDefaultRegion": "المنطقة الافتراضية", "propertyStyleAttributeName": "اسم سمة النمط", "propertyTimeCodeFormat": "تنسيق الرمز الزمني", - "propertyFileExtension": "امتداد الملف", "propertyTopOrigin": "أصل الأعلى", "propertyTopExtent": "امتداد الأعلى", "propertyBottomOrigin": "أصل الأسفل", @@ -1625,7 +1700,6 @@ "selectionIntersect": "تقاطع مع المُحدد" }, "multipleReplace": { - "title": "استبدال متعدد", "editRule": "تعديل القاعدة", "newRule": "قاعدة جديدة", "editCategory": "تعديل الفئة", @@ -1642,24 +1716,23 @@ "findWhat": "البحث عن", "descriptionOptional": "الوصف (اختياري)", "invalidRegularExpressionX": "تعبير نمطي غير صالح: {0}", - "regularExpressionTooSlowX": "توقف التعبير النمطي بعد {0} ثانية - تم تخطي القاعدة، جرّب نمطاً أبسط" + "regularExpressionTooSlowX": "توقف التعبير النمطي بعد {0} ثانية - تم تخطي القاعدة، جرّب نمطاً أبسط", + "moveToCategory": "نقل إلى فئة" }, "find": { "searchTextWatermark": "نص البحث...", "wholeWord": "كلمة كاملة", - "caseSensitive": "حساس لحالة الأحرف", - "caseInsensitive": "غير حساس لحالة الأحرف", "findPrevious": "البحث عن ال_سابق", "findNext": "_البحث عن التالي", "replaceAndFindNext": "_استبدال والبحث عن التالي", "replaceAll": "استبدال ال_كل", - "replaceTitle": "استبدال", - "replaceWith": "استبدال بـ", "replaceTextWatermark": "استبدال بـ...", "replaceIn": "الاستبدال/البحث في", "replaceInTextAndOriginal": "النص والنص الأصلي", "replaceInTextOnly": "النص فقط", - "replaceInOriginalOnly": "النص الأصلي فقط" + "replaceInOriginalOnly": "النص الأصلي فقط", + "replacedOneOccurrence": "تم استبدال تكرار واحد", + "replacedXOccurrences": "تم استبدال {0} من التكرارات" }, "regularExpressionContextMenu": { "wordBoundary": "حد كلمة (\\b)", @@ -1687,6 +1760,7 @@ "xLinesYSubtitles": "{0} سطر، {1} ترجمة", "xLinesYSubtitlesZErrors": "{0} سطر، {1} ترجمة، {2} خطأ", "couldNotParseAsX": "تعذر التحليل بصيغة {0}", + "readAsXUseAsY": "تعذّرت قراءة المصدر كـ {1}، لكن أمكن قراءته كـ {0}.\nهل تريد استخدام الأسطر ومواصلة الحفظ كـ {1}؟", "selectedXCharactersYLines": "المحدد: {0} حرف في {1} سطر", "replacedXOccurrences": "تم استبدال {0} تطابق", "invalidRegularExpression": "تعبير نمطي غير صالح", @@ -1701,7 +1775,6 @@ "aiReview": { "title": "مراجعة بالذكاء الاصطناعي", "review": "مراجعة", - "stop": "إيقاف", "editPrompt": "تحرير الموجّه...", "editPromptTitle": "تحرير موجّه المراجعة", "promptInfo": "التعليمات المُرسلة إلى النموذج، يُستبدل {language} بلغة الترجمة.", @@ -1712,7 +1785,6 @@ "reviewingLineXOfY": "مراجعة السطر {0} من {1}...", "reviewDone": "اكتملت المراجعة، {0} اقتراحات في {1} أسطر", "noIssuesFound": "لم يُعثر على مشكلات", - "categoryAll": "الكل", "categorySpelling": "الإملاء", "categoryGrammar": "القواعد", "categoryPunctuation": "علامات الترقيم", @@ -1724,8 +1796,23 @@ "largeChangeWarning": "تغيير كبير، يبدو كإعادة صياغة، راجعه بعناية", "mismatchWarning": "لا يشبه الأصل - قد ينتمي إلى سطر آخر", "engineError": "تعذّر الوصول إلى محرك الذكاء الاصطناعي: {0}", - "setupHint": "يرجى اختيار محرك ونموذج في الأدوات -> مراجعة بالذكاء الاصطناعي أولاً.", - "playCurrentLineHint": "تشغيل السطر المحدد في مشغل الفيديو والإيقاف عند نهايته (أو انقر نقراً مزدوجاً على سطر)" + "playCurrentLineHint": "تشغيل السطر المحدد في مشغل الفيديو والإيقاف عند نهايته (أو انقر نقراً مزدوجاً على سطر)", + "editAfterHint": "انقر على نص الصف المحدد لتحرير الإصلاح قبل تطبيقه (Enter للحفظ، Escape للإلغاء)", + "template": "القالب", + "templatePlaceholder": "اختر قالبًا لاستبدال المطالبة", + "templateStandard": "تدقيق لغوي قياسي", + "templateSpeechToText": "تنظيف تحويل الكلام إلى نص", + "templateMachineTranslation": "إصلاح الترجمة الآلية", + "templateSdh": "SDH / ضعاف السمع", + "referenceContext": "السياق المرجعي", + "referenceContextInfo": "اختياري. الأسماء أو الشخصيات أو المسرد أو الملخص أو ملاحظات الأسلوب - يعامل النموذج هذا كمرجع معتمد للأسماء والمصطلحات. يُرسل مع كل دفعة.", + "referenceContextPlaceholder": "الأسماء: كايتو، العمة مي، د. هوليس\nالمصطلحات: شينكانسن، قلب الالتواء\nالملخص: يسافر كايتو إلى طوكيو للعثور على والده المفقود.", + "generateContext": "التوليد بالذكاء الاصطناعي", + "generateContextHint": "دع محرك الذكاء الاصطناعي المحدد يقرأ الترجمة ويصوغ الأسماء والمصطلحات وملخصًا - تحقق من النتيجة قبل الاستخدام", + "generatingContextPartXOfY": "جارٍ قراءة الجزء {0} من {1}...", + "generatingContextSynopsis": "جارٍ كتابة الملخص...", + "replaceContextQuestion": "هل تريد استبدال السياق المرجعي الحالي؟", + "noContextGenerated": "لم يُرجع محرك الذكاء الاصطناعي أي معلومات مرجعية قابلة للاستخدام." }, "aiAssistant": { "title": "مساعد الذكاء الاصطناعي", @@ -1743,22 +1830,14 @@ "showReasoning": "إظهار استدلال النموذج" }, "fixCommonErrors": { - "title": "إصلاح الأخطاء الشائعة", - "step1": "الخطوة 1/2، اختيار الأخطاء المراد إصلاحها", - "whatToFix": "ما المراد إصلاحه", - "example": "مثال", - "selectAll": "تحديد الكل", - "inverseSelection": "عكس التحديد", "back": "< &رجوع", "next": "&التالي >", - "step2": "الخطوة 2/2، التحقق من الإصلاحات", "fixes": "الإصلاحات", "ruleDetailsDotDotDot": "تفاصيل القاعدة...", "showOnlyThisRule": "إظهار إصلاحات هذه القاعدة فقط", "appliedRulesForLineX": "القواعد المطبقة على السطر {0}", "ruleX": "القاعدة: {0}", "log": "السجل", - "function": "الوظيفة", "removedEmptyLine": "إزالة السطر الفارغ", "removedEmptyLineAtTop": "إزالة السطر الفارغ في الأعلى", "removedEmptyLineAtBottom": "إزالة السطر الفارغ في الأسفل", @@ -1779,13 +1858,13 @@ "removeLineBreaksPixelWidth": "إلغاء كسر الترجمات التي يمكن أن تتسع في سطر واحد (عرض البكسل)", "fixUppercaseIInsideLowercaseWords": "إصلاح حرف 'i' الكبير داخل الكلمات الصغيرة (خطأ OCR)", "fixDoubleApostrophes": "إصلاح حرفي الفاصلة العليا المزدوجين ('') إلى علامة اقتباس مفردة (\")", + "fixMisreadQuotes": "إصلاح الفواصل العليا المقروءة خطأً كعلامات اقتباس مزدوجة (OCR، الإنجليزية)", "addPeriods": "إضافة نقطة بعد الأسطر التي يبدأ سطرها التالي بحرف كبير", "startWithUppercaseLetterAfterParagraph": "البدء بحرف كبير بعد الفقرة", "startWithUppercaseLetterAfterPeriodInsideParagraph": "البدء بحرف كبير بعد النقطة داخل الفقرة", "startWithUppercaseLetterAfterColon": "البدء بحرف كبير بعد النقطتين/الفاصلة المنقوطة", "fixLowercaseIToUppercaseI": "إصلاح حرف 'i' الصغير المنفرد إلى 'I' (الإنجليزية)", "fixCommonOcrErrors": "إصلاح أخطاء OCR الشائعة (باستخدام قائمة استبدال OCR)", - "commonOcrErrorsFixed": "أخطاء OCR الشائعة المُصلَحة (باستخدام ملف OcrReplaceList): {0}", "removeSpaceBetweenNumber": "إزالة المسافة بين الأرقام", "breakDialogsOnOneLine": "تقسيم الحوارات في سطر واحد", "removeDialogFirstInNonDialogs": "إزالة الشرطة الأولى في السطر الأول لغير الحوارات", @@ -1802,18 +1881,17 @@ "fixCommasExample": ",, -> ,", "removeLineBreaksExample": "Foo
bar! -> Foo bar!", "fixDoubleApostrophesExample": "''Has double single quotes'' -> \"Has single double quote\"", + "fixMisreadQuotesExample": "\"Hello' -> \"Hello\"", "addPeriodsExample": "Hello world -> Hello world.", "startWithUppercaseLetterAfterParagraphExample": "p1: Foobar! || p2: foobar! -> p1: Foobar! || p2: Foobar!", "startWithUppercaseLetterAfterPeriodInsideParagraphExample": "Hello there! how are you? -> Hello there! How are you?", "startWithUppercaseLetterAfterColonExample": "Speaker: hello world! -> Speaker: Hello world!", "removeHyphensSingleLineExample": "- Foobar. -> Foobar.", "fix3PlusLinesExample": "Foo
bar
baz! -> Foo bar baz!", - "xMissingQuotesAdded": "علامات الاقتباس الناقصة المُضافة: {0}", "fix3PlusLine": "إصلاح ترجمة تحتوي على أكثر من سطرين", "fix3PlusLines": "إصلاح الترجمات التي تحتوي على أكثر من سطرين", "analysing": "جارٍ التحليل...", "nothingToFix": "لا شيء لإصلاحه :)", - "fixesFoundX": "الإصلاحات الموجودة: {0}", "xFixesApplied": "الإصلاحات المطبَّقة: {0}", "xFixesYSelected": "{0} إصلاحات، {1} محدد", "nothingFixableBut": "تعذّر إصلاح أي شيء تلقائيًا. تحتوي الترجمة على أخطاء، راجع السجل للتفاصيل", @@ -1822,7 +1900,6 @@ "fixFirstLetterToUppercaseAfterParagraph": "تحويل الحرف الأول إلى كبير بعد الفقرة", "mergeShortLine": "دمج السطر القصير (جملة واحدة)", "mergeShortLineAll": "دمج السطر القصير (الكل عدا الحوارات)", - "unbreakShortLinePixelWidth": "إلغاء كسر السطر القصير (عرض البكسل)", "breakLongLine": "كسر السطر الطويل", "fixLongDisplayTime": "إصلاح وقت العرض الطويل", "fixInvalidItalicTag": "إصلاح وسم المائل غير الصالح", @@ -1845,7 +1922,6 @@ "fixUppercaseIInsideLowercaseWord": "إصلاح حرف 'i' الكبير داخل كلمة صغيرة", "fixMissingPeriodAtEndOfLine": "إضافة النقطة الناقصة في نهاية السطر", "refreshFixes": "تحديث الإصلاحات المتاحة", - "applyFixes": "تطبيق الإصلاحات المحددة والإغلاق", "autoBreak": "&كسر تلقائي", "unbreak": "&إلغاء الكسر", "fixDoubleDash": "إصلاح '--' -> '...'", @@ -1864,8 +1940,6 @@ "fixSpaceBetweenNumbersExample": "1 100 -> 1100", "fixDialogsOneLineExample": "Hi John! - Hi Ida! -> Hi John!
- Hi Ida!", "removeDialogFirstInNonDialogsExample": "- How are you? -> How are you?", - "selectDefault": "تحديد الافتراضي", - "setDefault": "تعيين الإصلاحات الحالية كافتراضية", "fixContinuationStyleX": "إصلاح نمط الاستمرار: {0}", "fixUnnecessaryLeadingDots": "إزالة النقاط البادئة غير الضرورية", "fixCommonErrorsProfiles": "ملفات قواعد إصلاح الأخطاء الشائعة", @@ -1873,7 +1947,6 @@ "searchRulesDotDotDot": "بحث في القواعد...", "goToApplyFixes": "الانتقال إلى تطبيق الإصلاحات", "backToFixList": "العودة إلى قائمة الإصلاحات", - "applyFixesAndClose": "تطبيق الإصلاحات والإغلاق", "fixCommonOcrErrorsStep2": "إصلاح الأخطاء الشائعة، الخطوة 2 (تطبيق الإصلاحات)", "fixCommonOcrErrorsStep2FixesFoundX": "إصلاح الأخطاء الشائعة، الخطوة 2، الإصلاحات الموجودة: {0}", "action": "الإجراء", @@ -1886,18 +1959,11 @@ "adjustDurations": { "title": "ضبط المدد", "adjustVia": "الضبط عبر", - "seconds": "ثوانٍ", - "percent": "نسبة مئوية", "recalculate": "إعادة الحساب", "addSeconds": "إضافة ثوانٍ", - "setAsPercent": "التعيين كنسبة مئوية من المدة", "note": "ملاحظة: لن يتداخل وقت العرض مع وقت بداية النص التالي", "fixed": "ثابت", - "milliseconds": "ميلي ثانية", "extendOnly": "التمديد فقط", - "enforceDurationLimits": "فرض الحد الأدنى والأقصى للمدة", - "checkShotChanges": "عدم التمديد بعد تغييرات اللقطات", - "batchCheckShotChanges": "مراعاة تغييرات اللقطات (إن وُجدت)", "recalculateRequiresOcrNote": "إعادة الحساب غير متاحة، إحدى الترجمات أو أكثر لا تحتوي على نص OCR. شغّل OCR أولًا." }, "applyDurationLimits": { @@ -1905,6 +1971,8 @@ "fixMinDurationMs": "إصلاح الحد الأدنى للمدة (ms)", "doNotGoPastShotChange": "عدم التجاوز بعد تغيير اللقطة", "fixMaxDurationMs": "إصلاح الحد الأقصى للمدة (ms)", + "fixMinDurationFrames": "إصلاح الحد الأدنى للمدة (إطارات)", + "fixMaxDurationFrames": "إصلاح الحد الأقصى للمدة (إطارات)", "minimumDurationMilliseconds": "المدة الدنيا (بالمللي ثانية):", "maximumDurationMilliseconds": "المدة القصوى (بالمللي ثانية):", "maxDurationShouldBeHigherThanMinDuration": "يجب أن يكون الحد الأقصى للمدة أعلى من الحد الأدنى للمدة", @@ -1924,12 +1992,8 @@ }, "beautifyTimeCodes": { "title": "تجميل الرموز الزمنية", - "beautifySettings": "إعدادات التجميل", "snapToFrames": "المحاذاة إلى الإطارات", - "frameGap": "فجوة الإطارات", "minDuration": "الحد الأدنى للمدة (ms)", - "shotChangeThreshold": "عتبة تغيير اللقطة (ms)", - "shotChangeOffset": "إزاحة تغيير اللقطة (إطارات)", "original": "الأصلي", "beautified": "المُجمَّل", "subtitlesCount": "الترجمات", @@ -1958,13 +2022,80 @@ "timeCodesExtractFailed": "تعذر قراءة الرموز الزمنية من الفيديو", "timeCodesHint": "محاذاة الإشارات إلى أوقات الإطارات الحقيقية للفيديو بدلاً من افتراض معدل إطارات ثابت. يتطلب قراءة الفيديو كاملاً مرة واحدة؛ وتُخزَّن النتيجة مؤقتاً." }, + "improveTimeCodes": { + "title": "تحسين الرموز الزمنية (المحاذاة القسرية)", + "aligner": "المحاذي", + "alignerHint": "النموذج الذي يستمع لكل سطر في الصوت. أفضل الخيارات للغة الترجمة مدرجة أولاً.", + "maxShift": "أقصى إزاحة (ثوانٍ)", + "maxShiftHint": "مدى عدم تزامن الترجمة المحتمل. السطر الذي ستتحرك بدايته أبعد من ذلك يُترك كما هو ويُعلَّم. أيًّا كانت القيمة، فإن السطر المنفرد الذي يريد التحرك أكثر من نصف ثانية أبعد من الأسطر المحيطة به لا يُنقل عشوائيًا: فهو يتبع جيرانه إذا تحركوا جميعًا، وإلا يُعرض غير محدد لتتحقق منه.", + "adjustStartTimes": "ضبط أوقات البداية", + "adjustEndTimes": "ضبط أوقات النهاية", + "isolateSpeech": "عزل الكلام أولاً (بطيء)", + "isolateSpeechHint": "يزيل الموسيقى والمؤثرات الصوتية قبل المحاذاة، بحيث لا يسمع المحاذي سوى الحوار. يفيد في الأسطر المنطوقة فوق موسيقى صاخبة أو مشاهد حركة، لكنه يستغرق تقريبًا مدة الصوت نفسه مع GPU - وأضعافها بدونه.", + "isolatingSpeech": "جارٍ عزل الكلام... (يستغرق هذا بعض الوقت)", + "isolateSpeechFailed": "تعذّر عزل الكلام - تمت المحاذاة مقابل الصوت الأصلي بدلاً من ذلك.", + "checkWithSpeechToText": "التحقق باستخدام تحويل الكلام إلى نص", + "checkWithSpeechToTextHint": "يقوم بنسخ الصوت أيضًا، ويتحقق من كل نقل مقابل الموضع الذي سُمعت فيه الكلمات فعليًا. النقلات الكبيرة التي يؤكدها الكلام تُحدَّد لك، والنقلات بعيدًا عن موضع سماع السطر تُترك غير محددة لتتحقق منها. يُظهر أيضًا مقدار ما سُمع من كل سطر - النسبة المنخفضة تعني أن النص يختلف عما يُقال. الترجمة غير المتزامنة بأكثر من أقصى إزاحة - إزاحة ثابتة، أو انحراف بسبب عدم تطابق معدل الإطارات، أو قفزات حيث قُصّت المشاهد - تُزامَن أولاً حسب الكلمات المسموعة. يستخدم Crisp ASR Parakeet، الذي يستغرق بضع دقائق لفيلم روائي طويل مع GPU.", + "checkWithSpeechToTextNotAvailableX": "التحقق بتحويل الكلام إلى نص غير متاح لـ {0}", + "transcribingToCheck": "جارٍ النسخ للتحقق من المحاذاة...", + "speechToTextFailed": "فشل تحويل الكلام إلى نص - لم يتم التحقق من الرموز الزمنية مقابله.", + "speechToTextX": "تحويل الكلام إلى نص: {0}", + "speechToTextConfirmedXDisputedY": "الكلام: {0} مؤكد، {1} متنازع عليه", + "hardlyHeardX": "لم يسمع تحويل الكلام إلى نص سوى {0}% من كلمات الترجمة. هل الترجمة باللغة المنطوقة؟ لا يستطيع المحاذي وضع ترجمة بلغة أخرى.", + "heard": "مسموع", + "heardHint": "مقدار ما سمعه تحويل الكلام إلى نص من السطر. النسبة المنخفضة تعني أن النص يختلف عما يُقال - وهنا يكون المحاذي على الأرجح مخطئًا.", + "statusConfirmedBySpeech": "مؤكد بالكلام", + "statusDisputedBySpeech": "سُمع حيث كان", + "statusMovedWithSync": "نُقل مع المزامنة", + "syncedFirstXY": "تمت المزامنة أولاً: {0} ث في البداية، {1} ث في النهاية", + "showSpeechOnly": "إظهار الكلام فقط", + "showSpeechOnlyHint": "رسم الموجة الصوتية المحاذاة من الصوت بعد إزالة الموسيقى والمؤثرات الصوتية؛ تبقى الموجة الصوتية الأصلية كما هي للمقارنة. متاح بعد عزل الكلام - هنا، أو باستخدام \"إظهار الكلام فقط\" في الموجة الصوتية بالنافذة الرئيسية.", + "align": "محاذاة", + "original": "الأصلي", + "aligned": "المحاذى", + "intro": "اختر محاذيًا واضغط \"محاذاة\" للاستماع لكل سطر في الصوت وإحكام رموزه الزمنية.", + "extractingAudio": "جارٍ استخراج الصوت...", + "aligningBatchXOfY": "جارٍ المحاذاة... الدفعة {0} من {1}", + "extractAudioFailed": "تعذّر استخراج الصوت باستخدام ffmpeg.", + "modelWillBeDownloaded": "سيتم تنزيل {0}", + "summaryXRetimedYKeptZSkipped": "أُعيد توقيته: {0} · أُبقي: {1} · لا كلام: {2}", + "meanShiftX": "متوسط الإزاحة: {0} مللي ثانية", + "toCheckX": "للتحقق: {0}", + "largeMovesToCheckX": "{0} سطر (أسطر) ستتحرك مسافة كبيرة بمفردها وتُركت غير محددة - استمع إليها (F5 يشغّل السطر المحاذى، وShift+F5 الأصلي) وحدد الصحيحة منها.", + "linesToCheckX": "{0} سطر (أسطر) تُركت غير محددة للتحقق - استمع إليها (F5 يشغّل السطر المحاذى، وShift+F5 الأصلي) وحدد الصحيحة منها.", + "changeXOfY": "التغيير {0} من {1}", + "previousChange": "التغيير السابق", + "nextChange": "التغيير التالي", + "startShift": "البداية", + "endShift": "النهاية", + "statusRetimed": "أُعيد توقيته", + "statusMovedWithNeighbours": "نُقل مع جيرانه", + "statusLargeMoveUnconfirmed": "نقل كبير - استمع، وحدد للتطبيق", + "statusUnchanged": "في مكانه بالفعل", + "statusNoSpeech": "لا كلام", + "statusShiftTooLarge": "أُبقي - الإزاحة كبيرة جدًا", + "statusFailed": "أُبقي - فشل المحاذي", + "statusAdjustedByHand": "ضُبط يدويًا", + "alignedWaveformHint": "اسحب سطرًا لنقله، أو اسحب حوافه لتغيير بدايته أو نهايته.", + "playLine": "تشغيل السطر", + "undoAdjustment": "التراجع عن الضبط", + "playPause": "تشغيل / إيقاف مؤقت", + "playAligned": "المحاذى", + "playAlignedHint": "تشغيل السطر المحدد برموزه الزمنية المحاذاة", + "apply": "تطبيق", + "applyHint": "ألغِ تحديد سطر للإبقاء على رموزه الزمنية الأصلية. انقر نقرًا مزدوجًا على سطر لتشغيله. يمكن أيضًا نقل الأسطر أو تغيير حجمها في الموجة الصوتية المحاذاة." + }, "beautifyTimeCodesProfile": { "title": "تحرير ملف قواعد تجميل الرموز الزمنية", "loadPreset": "تحميل الإعداد المسبق", "presetDefault": "الافتراضي", "presetNetflix": "Netflix", "presetSdi": "SDI", - "general": "عام", + "saveAsProfile": "حفظ كملف تعريف...", + "deleteProfile": "حذف ملف التعريف", + "profileName": "اسم ملف التعريف", + "overwriteProfileX": "يوجد ملف تعريف باسم \"{0}\" بالفعل. هل تريد استبداله؟", + "deleteProfileX": "هل تريد حذف ملف التعريف \"{0}\"؟", "gap": "الفجوة", "gapSuffix": "إطارات (يُحدّث أيضًا حقول الفجوة غير الصفرية أدناه)", "milliseconds": "ms", @@ -1993,9 +2124,12 @@ "hintChaining": "كيفية التعامل مع الترجمات المتلامسة تقريبًا: تمديدها معًا أو إبعادها، حسب وجود تغيير لقطة بينهما." }, "bridgeGaps": { - "title": "سدّ الفجوات", "bridgeGapsSmallerThan": "سدّ الفجوات الأصغر من (ms)", "minGap": "الحد الأدنى للفجوة (ms)", + "bridgeGapsSmallerThanFrames": "سد الفجوات الأصغر من (إطارات)", + "minGapFrames": "الحد الأدنى للفجوة (إطارات)", + "calculateBridgeGapsSmallerThanDotDotDot": "حساب حد سد الفجوات من معدل إطارات...", + "useXMsAsBridgeGapsSmallerThan": "سد الفجوات الأصغر من \"{0}\" مللي ثانية؟", "numberOfSmallGapsBridgedX": "عدد الفجوات الصغيرة المسدودة: {0}", "percentFoPrevious": "الفجوة للسابق (%)", "gapChange": "تغيير الفجوة" @@ -2007,8 +2141,6 @@ "sortBy": { "title": "ترتيب الترجمات", "sortOrder": "ترتيب الفرز", - "sortByNumber": "الرقم", - "sortByStartTime": "وقت البداية", "sortByEndTime": "وقت النهاية" }, "batchConvert": { @@ -2031,6 +2163,7 @@ "addColor": "إضافة اللون", "deleteLinesWithSpecificActorsOrStyles": "حذف الأسطر ذات الممثلين أو الأنماط (افصل المتعدد بفاصلة)", "useSourceStylesIfPossible": "استخدام أنماط المصدر إن أمكن", + "keepSourceEmbeddedFonts": "الإبقاء على الخطوط المضمّنة في المصدر (التذييل)", "editStyles": "تحرير الأنماط", "editProperties": "تحرير الخصائص", "editAttachments": "تحرير المرفقات", @@ -2051,7 +2184,6 @@ "assaChangeStyleFromStyle": "تغيير النمط من", "assaChangeStyleToStyle": "إلى", "assaChangeStyleImportStyle": "استيراد النمط...", - "assaChangeStyleImportedX": "المستورد: {0}", "assaChangeStyleTrimUnusedStyles": "إزالة الأنماط غير المستخدمة", "assaChangeStylePropertiesTitle": "تغيير خصائص النمط", "assaChangeStylePropertiesInfo": "يغيّر الحقول المختارة في كل نمط في الملف، مع ترك الأنماط نفسها دون تغيير.", @@ -2063,13 +2195,52 @@ "convertColorsToDialogReBreakLines": "إعادة كسر الأسطر", "snapTimeCodesToFramesInfo": "يقرّب كل وقت بداية ونهاية إلى أقرب إطار. يُقرأ معدل الإطارات من ملف فيديو يحمل اسم ملف الترجمة نفسه، ما لم يُختَر معدل إطارات ثابت.", "addFolderDotDotDot": "إضافة مجلد...", + "addFolderRecursiveDotDotDot": "إضافة مجلد مع المجلدات الفرعية...", "selectFolderToConvert": "اختر مجلداً يحتوي على ملفات للتحويل", "includeSubfolders": "تضمين المجلدات الفرعية عند إضافة مجلد", + "videoFilesWhenAddingFolder": "ملفات الفيديو (mkv، mp4، ts...) عند إضافة مجلد", + "videoFilesAsk": "السؤال", + "videoFilesInclude": "إضافة مسارات الترجمة الخاصة بها", + "videoFilesSkip": "تخطي", + "folderContainsXVideoFiles": "يحتوي المجلد على {0} ملف (ملفات) فيديو (mkv، mp4، ts...).\n\nهل تريد إضافة مسارات الترجمة المضمّنة فيها أيضًا، أم ملفات الترجمة فقط؟", + "addVideoFiles": "إضافة ملفات الفيديو", + "skipVideoFiles": "ملفات الترجمة فقط", + "doNotAskAgainVideoFiles": "عدم السؤال مجددًا (يمكن تغييره في إعدادات التحويل الدُّفعي)", + "settingsSectionOutput": "المخرجات", + "settingsSectionAddingFiles": "إضافة الملفات", + "settingsSectionImageBasedSubtitles": "الترجمات القائمة على الصور (OCR)", + "settingsSectionOther": "أخرى", "keepSourceFileTimestamp": "الاحتفاظ بتاريخ/وقت الملف المصدر في ملفات الإخراج", - "scanningFolderX": "جارٍ فحص {0}..." + "preventSleepWhileConverting": "منع الكمبيوتر من السكون أثناء التحويل", + "scanningFolderX": "جارٍ فحص {0}...", + "transportStreamSettings": "إعدادات Transport Stream", + "transportStreamSettingsDotDotDot": "إعدادات Transport Stream...", + "transportStreamSettingsInfo": "ينطبق على مسارات الترجمة المستخرجة من ملفات Transport Stream (.ts/.m2ts). يؤثر الموضع وحجم الفيديو فقط على مسارات صور DVB المصدّرة إلى تنسيق قائم على الصور.", + "transportStreamOverrideXPosition": "تجاوز موضع X الأصلي", + "transportStreamOverrideYPosition": "تجاوز موضع Y الأصلي", + "transportStreamOverrideVideoSize": "تجاوز حجم الفيديو الأصلي", + "transportStreamBottomMargin": "الهامش السفلي", + "transportStreamFileNameEnding": "نهاية اسم الملف", + "transportStreamFileNameEndingInfo": "يُضاف قبل الامتداد لكل مسار مستخرج. اتركه فارغًا لاستخدام لاحقة اللغة العادية.", + "transportStreamOnlyTeletext": "Teletext فقط", + "transportStreamGetSizeFromVideo": "الحصول على الحجم من الفيديو...", + "twoLetterLanguageCodeUppercase": "رمز اللغة المكون من حرفين (أحرف كبيرة)", + "threeLetterLanguageCodeUppercase": "رمز اللغة المكون من ثلاثة أحرف (أحرف كبيرة)", + "formatContainsDotDotDot": "التنسيق يحتوي على...", + "extensionIsDotDotDot": "الامتداد هو...", + "statusIsError": "الحالة خطأ", + "forcedTracksOnly": "المسارات الإجبارية فقط", + "xConvertedYFailedInZ": "تم تحويل {0:#,###,##0}، وفشل {1:#,###,##0} في {2}", + "xNotProcessed": "لم تتم معالجة {0:#,###,##0}", + "removeUnicodeControlCharactersTitle": "إزالة أحرف التحكم في Unicode", + "removeUnicodeControlCharactersInfo": "يزيل أحرف التحكم في الاتجاه غير المرئية في Unicode (LRM وRLM وLRE وRLE وPDF وLRO وRLO) من النص، ويستبدل المسافات غير القابلة للكسر بمسافات عادية.", + "preset": "الإعداد المسبق", + "savePresetDotDotDot": "حفظ الإعداد المسبق...", + "deletePreset": "حذف الإعداد المسبق", + "presetName": "اسم الإعداد المسبق", + "deletePresetX": "هل تريد حذف الإعداد المسبق \"{0}\"؟" }, "changeCasing": { - "title": "تغيير حالة الأحرف", "fixNames": "إصلاح الأسماء", "namesX": "الأسماء: {0:#,##0}", "hitsX": "النتائج: {0:#,##0}", @@ -2080,9 +2251,7 @@ "allUppercase": "الكل بأحرف كبيرة", "allLowercase": "الكل بأحرف صغيرة" }, - "changeFormatting": { - "title": "تغيير التنسيق" - }, + "changeFormatting": {}, "convertActors": { "title": "تحويل الممثلين", "convertActorFrom": "تحويل الممثل من", @@ -2111,18 +2280,12 @@ "outputFormatAssa": "Advanced Sub Station Alpha (.ass)", "style1": "النمط 1", "style2": "النمط 2", - "outlineWidth": "عرض الحدّ الخارجي", - "shadowWidth": "عرض الظل", - "alignTop": "أعلى", - "alignBottom": "أسفل", - "merge": "_دمج", - "pleaseLoadTwoSubtitles": "الرجاء تحميل ترجمتين" + "merge": "_دمج" }, "splitSubtitle": { "title": "تقسيم الترجمة", "numberOfEqualParts": "عدد الأجزاء المتساوية", "saveSplitParts": "_حفظ الأجزاء المقسّمة", - "subtitleSplitIntoXParts": "قُسّمت الترجمة إلى {0} أجزاء.", "xPartsSavedInFormatYToFolder": "حُفظ {0} أجزاء بالصيغة {1} إلى المجلد:" }, "splitBreakLongLines": { @@ -2156,18 +2319,17 @@ "columnMerge": "دمج", "columnFirst": "السطر الأول", "columnSecond": "السطر الثاني", - "columnMerged": "المدمج", - "selectAll": "تحديد الكل", - "inverseSelection": "عكس التحديد" + "columnMerged": "المدمج" }, "mergeLinesWithSameText": { - "title": "دمج الأسطر ذات النص نفسه", "maxMsBetweenLines": "الحد الأقصى للميلي ثانية بين الأسطر", - "includeIncrementingLines": "تضمين الأسطر ذات النص المتزايد" + "maxFramesBetweenLines": "أقصى عدد إطارات بين الأسطر", + "includeIncrementingLines": "تضمين الأسطر ذات النص المتزايد", + "includeRollUpCaptions": "تضمين التسميات التوضيحية المتدحرجة (Roll-up)" }, "mergeLinesWithSameTimeCodes": { - "title": "دمج الأسطر ذات الرموز الزمنية نفسها", "maxMsDifference": "الحد الأقصى للفرق (ميلي ثانية)", + "maxFramesDifference": "أقصى فرق (إطارات)", "makeDialog": "إنشاء حوارات" }, "netflixCheckAndFix": { @@ -2190,7 +2352,6 @@ "shotChanges": "تغييرات اللقطات", "startNumberSpellOut": "كتابة الرقم في البداية", "textforHiUseBrackets": "الترميزات المتعلقة بضعاف السمع، استخدم الأقواس", - "frameRate": "معدل الإطارات", "twoFrameGrap": "فجوة إطارين", "whiteSpace": "المسافة البيضاء", "childrensProgram": "برنامج أطفال", @@ -2228,7 +2389,6 @@ "italicsFixed": "أُصلح المائل", "italicsNotAllowed": "المائل غير مسموح به", "bridgeGapsXToYFrames": "فجوة 3-{0} إطارات => فجوة إطارين", - "startNumberSpellOutComment": "عندما يبدأ رقم جملة، يجب دائمًا كتابته بالحروف", "maximumXCharactersPerSecond": "الحد الأقصى {0} حرف في الثانية" }, "imageBasedEdit": { @@ -2237,10 +2397,8 @@ "resizeImagesDotDotDot": "تغيير حجم الصور...", "adjustBrightnessDotDotDot": "ضبط السطوع...", "adjustAlphaDotDotDot": "ضبط ألفا...", - "centerHorizontally": "التوسيط أفقيًا", "cropImages": "اقتصاص الصور", "importTimeCodes": "استيراد الرموز الزمنية...", - "setTextForSubtitle": "تعيين نص للترجمة", "setText": "تعيين النص", "screenWidth": "عرض الشاشة", "screenHeight": "ارتفاع الشاشة", @@ -2271,15 +2429,31 @@ "letterboxCustom": "ارتفاع شريط مخصص", "barHeightPx": "ارتفاع الشريط (بكسل)", "titleSafePercent": "منطقة أمان العنوان (%)", + "moveCaptions": "نقل التسميات التوضيحية", + "moveCaptionsDotDotDot": "نقل التسميات التوضيحية...", + "moveCaptionsTo": "نقل التسميات التوضيحية", + "moveCaptionsIntoBars": "إلى الأشرطة السوداء (خارج الصورة)", + "moveCaptionsIntoPicture": "داخل الصورة (خارج الأشرطة)", + "moveCaptionsOffset": "الإزاحة من الحافة (px)", + "letterboxBarHeightXPxYOfZCaptionsWillMove": "ارتفاع الشريط: {0} px - ستتحرك {1} من {2} تسمية توضيحية", + "removeFadeInOut": "إزالة التلاشي للداخل/للخارج", + "removeFadeInOutXLinesRemoved": "تم دمج {0} سطر تلاشٍ في الأسطر التي تنتمي إليها", + "removeFadeInOutNothingFound": "لم يتم العثور على أسطر تلاشٍ للداخل/للخارج", "positionSummary": "{0}×{1} - {2} ترجمة - ارتفاع الشريط: {3} بكسل", "xInActivePicture": "{0} داخل الصورة", "xInTopBar": "{0} في الشريط العلوي", "xInBottomBar": "{0} في الشريط السفلي", "noImageSubtitlesLoaded": "لم تُحمَّل ترجمات مستندة إلى الصور", - "imageBasedFormatNotSupported": "تنسيق الترجمة القائم على الصور غير موجود/غير مدعوم." + "imageBasedFormatNotSupported": "تنسيق الترجمة القائم على الصور غير موجود/غير مدعوم.", + "frameRateDeclaredInFile": "معدل الإطارات المعلن في ملف Blu-ray sup", + "frameRateDetectedFromTimeCodesX": "معدل الإطارات المكتشف من الرموز الزمنية لـ Blu-ray sup (يعلن الملف {0})", + "frameRateFromVideo": "معدل إطارات الفيديو", + "frameRateCurrent": "معدل الإطارات الحالي", + "frameRateSetManually": "معدل الإطارات المعيّن يدويًا", + "frameRateUsageInfo": "تُكتب صادرات Blu-ray sup وBDN XML وDOST وFCP بمعدل الإطارات هذا. لا يغيّر أي أوقات - استخدم \"تغيير معدل الإطارات\" لذلك.", + "useVideoFrameRateXInsteadOfY": "معدل إطارات الفيديو هو {0}، لكن الترجمة مضبوطة على {1}.\n\nهل تريد استخدام معدل إطارات الفيديو ({0})؟" }, "removeTextForHearingImpaired": { - "title": "إزالة الترميزات المتعلقة بضعاف السمع", "interjections": "التعجّبات", "skipIfStartWith": "التخطي إذا بدأ بـ", "removeTextBetween": "إزالة النص بين", @@ -2298,7 +2472,6 @@ "removeInterjections": "إزالة التعجّبات", "linesFoundX": "الأسطر الموجودة: {0}" }, - "pickAlignmentTitle": "اختيار المحاذاة", "removeUnicodeCharacters": { "title": "إزالة/استبدال أحرف Unicode", "charactersFoundX": "أحرف Unicode التي عُثر عليها: {0}", @@ -2308,13 +2481,13 @@ "pickFontNameInstalledFonts": "الخطوط المثبتة", "pickFontNameCollectedFonts": "الخطوط المُجمَّعة", "colorPickerTitle": "اختيار اللون", + "colorPickerBrightness": "السطوع", "filterLayersTitle": "تصفية الطبقات للعرض", "filterLayersHideFromWaveform": "الإخفاء من الموجة الصوتية/الطيف الترددي", "filterLayersHideFromSubtitleGrid": "الإخفاء من شبكة الترجمة", "filterLayersHideFromVideoPreview": "الإخفاء من معاينة الفيديو", "pickSubtitleFormat": "اختيار صيغة الترجمة", "pickSubtitleFormatImageBasedNoPreview": "صيغة ترجمة مستندة إلى الصور.\nتُنشأ الصور النقطية أثناء التحويل، لا معاينة نصية.", - "pickLayerTitle": "تعيين الطبقة", "recentColors": "الألوان الأخيرة", "fixCommonErrorsSettingsTitle": "إعدادات إصلاح الأخطاء الشائعة" }, @@ -2359,59 +2532,51 @@ "correctWordsX": "الكلمات الصحيحة: {0}", "namesX": "الأسماء: {0}", "addedToDictionaryX": "المُضاف إلى القاموس: {0}", - "doNotShowThisAgain": "عدم إظهار هذه الرسالة مرة أخرى" + "doNotShowThisAgain": "عدم إظهار هذه الرسالة مرة أخرى", + "editUseAlwaysList": "تحرير قائمة \"الاستخدام دائمًا\"...", + "useAlwaysListTitle": "التدقيق الإملائي - قائمة \"الاستخدام دائمًا\"", + "useAlwaysListHeader": "قائمة \"الاستخدام دائمًا\"", + "useAlwaysListDescription": "كلمات يستبدلها التدقيق الإملائي تلقائيًا، محفوظة عبر \"تغيير الكل\" و\"الاستخدام دائمًا\". لا يُستخدم الزوج إلا عندما تكون الكلمة خاطئة إملائيًا.", + "useAlwaysWord": "الكلمة الخاطئة إملائيًا", + "xPairs": "{0:#,##0} زوج", + "xUnusedPairs": "{0:#,##0} لم يُستخدم أبدًا", + "useAlwaysUnusedHint": "لم يُستخدم أبدًا - هذه الكلمة مكتوبة بشكل صحيح (أو هي اسم أو كلمة مستخدم)، لذا لا يعلّمها التدقيق الإملائي أبدًا", + "removeUnused": "إزالة غير المستخدم", + "useAlwaysEmpty": "لا توجد أزواج \"الاستخدام دائمًا\" لهذه اللغة بعد", + "useAlwaysEmptyHint": "تُضاف الأزواج عند النقر على \"تغيير الكل\" أو \"الاستخدام دائمًا\" في التدقيق الإملائي.", + "useAlwaysNoMatch": "لا توجد أزواج تطابق البحث", + "useAlwaysSameWord": "يجب أن يكون البديل مختلفًا عن الكلمة.", + "useAlwaysRememberOff": "خيار \"تذكر قائمة 'الاستخدام دائمًا'\" متوقف في الإعدادات، لذا لا يستخدم التدقيق الإملائي هذه القائمة ولا يحفظها." }, "video": { "burnIn": { "title": "إنشاء فيديو بترجمة مدمجة", - "infoAssaOff": "ملاحظة: تنسيق Advanced SubStation Alpha مدعوم.", - "infoAssaOn": "ملاحظة: سيُستخدم تنسيق Advanced SubStation Alpha :)", - "xGeneratedWithBurnedInSubsInX": "تم إنشاء \"{0}\" بترجمة مدمجة في {1}.", - "timeRemainingMinutes": "الوقت المتبقي: {0} دقيقة", - "timeRemainingOneMinute": "الوقت المتبقي: دقيقة واحدة", - "timeRemainingSeconds": "الوقت المتبقي: {0} ثانية", - "timeRemainingAFewSeconds": "الوقت المتبقي: بضع ثوانٍ", - "timeRemainingMinutesAndSeconds": "الوقت المتبقي: {0} دقيقة و{1} ثانية", - "timeRemainingOneMinuteAndSeconds": "الوقت المتبقي: دقيقة واحدة و{0} ثانية", - "targetFileName": "اسم الملف الهدف: {0}", "targetFileSize": "حجم الملف الهدف (يتطلب ترميزًا بمرورين)", "fileSizeMb": "حجم الملف بالميغابايت", "matchSourceVideoSize": "مطابقة حجم فيديو المصدر", - "passX": "المرور {0}", - "encoding": "الترميز", "bitRate": "معدل البِت", "totalBitRateX": "إجمالي معدل البِت: {0}", "sampleRate": "معدل العينات", "audio": "الصوت", - "stereo": "ستيريو", "preset": "الإعداد المسبق", + "tune": "الضبط (Tune)", "pixelFormat": "تنسيق البكسل", "crf": "CRF", - "tuneFor": "الضبط من أجل", - "alignRight": "محاذاة لليمين", - "getStartPosition": "الحصول على موضع البداية", - "getEndPosition": "الحصول على موضع النهاية", "useSource": "استخدام المصدر", - "useSourceFolder": "استخدام مجلد المصدر", - "useSourceResolution": "استخدام دقة المصدر", - "outputSettings": "ملف/مجلد الإخراج...", "fontSizeFactor": "معامل حجم الخط", "boxType": "نوع المربع", - "fixRightToLeft": "إصلاح الكتابة من اليمين إلى اليسار", - "cut": "قص", "fromTime": "من الوقت", "toTime": "إلى الوقت", "audioEncoding": "ترميز الصوت", "outputProperties": "خصائص الإخراج...", "videoFileSize": "حجم ملف الفيديو", "oneBox": "مربع واحد", - "boxPerLine": "مربع لكل سطر", - "logoInfo": "اختر صورة PNG واسحبها لتحديد موضعها على الفيديو." + "logoInfo": "اختر صورة PNG واسحبها لتحديد موضعها على الفيديو.", + "noSubtitlesGenerateAnyway": "لا توجد أسطر ترجمة لدمجها.\n\nهل تريد توليد الفيديو بدون ترجمة؟", + "noSubtitlesInXItemsGenerateAnyway": "{0} عنصر (عناصر) دفعي لا يحتوي على أسطر ترجمة لدمجها.\n\nهل تريد توليد مقاطع الفيديو هذه بدون ترجمة؟" }, "videoTransparent": { - "title": "إنشاء فيديو شفاف مع الترجمة", - "infoAssaOff": "ملاحظة: تنسيق Advanced SubStation Alpha مدعوم.", - "infoAssaOn": "ملاحظة: سيُستخدم تنسيق Advanced SubStation Alpha :)" + "title": "إنشاء فيديو شفاف مع الترجمة" }, "audioToText": { "title": "تحويل الكلام إلى نص", @@ -2419,6 +2584,21 @@ "translateToEnglish": "الترجمة إلى الإنجليزية", "transcribing": "جارٍ التفريغ الصوتي...", "transcribingXOfY": "جارٍ تفريغ {0} من {1}...", + "linesTranscribedXOfYFailedZ": "تم نسخ {0} من {1} سطر - {2} فشل أو لم يحتوِ على كلام.\n\nتحتفظ الأسطر الفاشلة بنصها الحالي. هل تريد تطبيق الأسطر المنسوخة، أم إعادة محاولة الفاشلة (يمكنك تغيير المحرك أو الإعدادات أولاً)؟", + "linesTranscribedXOfYCancelled": "تم إلغاء النسخ بعد {0} من {1} سطر.\n\nهل تريد تطبيق الأسطر المنسوخة؟ تحتفظ الأسطر الأخرى بنصها الحالي.", + "noLinesTranscribed": "تعذّر نسخ أي من الأسطر الـ {0}.\nيرجى مراجعة سجل الأدوات للتفاصيل.", + "applyTranscribedLines": "تطبيق المنسوخ", + "retryFailedLines": "إعادة محاولة الفاشل", + "isolateSpeech": "عزل الكلام (بطيء)", + "isolateSpeechHint": "يزيل الموسيقى والمؤثرات الصوتية قبل النسخ. يقلل الأسطر المفقودة ويحسّن التوقيت في الصوت ذي الموسيقى الصاخبة، لكنه يستغرق تقريبًا مدة الصوت نفسه مع GPU - وأضعافها بدونه.", + "isolatingSpeech": "جارٍ عزل الكلام...", + "isolateSpeechFailed": "تعذّر عزل الكلام - يتم نسخ الصوت الأصلي بدلاً من ذلك.", + "detectSpeakers": "اكتشاف المتحدثين", + "detectSpeakersHint": "يبدأ كل سطر بالمتحدث، مثل \"(speaker 0)\"، باستخدام تمييز المتحدثين Sortformer من NVIDIA (حتى 8 متحدثين). يعمل بشكل أفضل مع المحركات التي تقسم الأسطر عند تغيّر المتحدث، مثل Parakeet وCohere.", + "detectSpeakersNeedsNewerCrispAsr": "تتطلب ميزة \"اكتشاف المتحدثين\" Crisp ASR v{0} أو أحدث، لكن الإصدار v{1} هو المثبت.\n\nحدّث Crisp ASR (زر الترس بجوار المحرك) أو أوقف \"اكتشاف المتحدثين\".", + "xNeedsNewerCrispAsr": "يتطلب {0} Crisp ASR v{1} أو أحدث، لكن الإصدار v{2} هو المثبت.\n\nهل تريد تنزيل التحديث وتثبيته الآن؟", + "vad": "VAD", + "vadHint": "اكتشاف النشاط الصوتي: يُنسخ الكلام فقط، ويُقطع حيث يتوقف المتحدث.\nتلقائي: Silero، وفقط للمحركات التي تحتاجه (Cohere وMega وIndex-Echo).\nSilero وFireRedVAD وWhisper-VAD وWebRTC: مفعّل دائمًا، لكل محركات Crisp ASR. FireRedVAD هو الأدق، وWhisper-VAD يعطي أقل وأنظف التقطيعات لكنه الأبطأ.\nيستخدم Index-Echo دائمًا Silero. الخيارات --vad أو --vad-model أو --chunk-seconds في الإعدادات المتقدمة لها الأولوية.", "inputLanguage": "لغة الإدخال", "advancedWhisperSettings": "معاملات متقدمة لتحويل الكلام إلى نص", "downloadingSpeechToTextEngine": "جارٍ تنزيل محرك تحويل الكلام إلى نص", @@ -2433,8 +2613,6 @@ "addCustomModelDotDotDot": "إضافة نموذج مخصص...", "customModelHelp": "يمكنك استخدام نموذجك الخاص: اختر ملف النموذج (whisper.cpp ggml '.bin') أو مجلد النموذج (مجلد faster-whisper يحتوي على 'model.bin' بداخله). سيُنسخ إلى مجلد النماذج ويُضاف إلى القائمة أعلاه.", "viewToolsLogFile": "عرض ملف سجل الأدوات", - "reDownloadX": "إعادة تنزيل {0}", - "downloadX": "تنزيل {0}", "updateXTitle": "تحديث {0}؟", "updateXMessage": "يتوفر إصدار أحدث من {0}.{1}{1}هل تريد تنزيل التحديث وتثبيته الآن؟", "downloadingSpeechToTextModel": "جارٍ تنزيل نموذج تحويل الكلام إلى نص", @@ -2446,14 +2624,6 @@ "fixCasing": "إصلاح حالة الأحرف", "addPeriods": "إضافة النقاط", "changeUnderlineToColor": "تغيير التسطير إلى لون", - "cueBuilding": "بناء المقاطع (MLX Whisper)", - "cueRebuild": "إعادة بناء المقاطع من الطوابع الزمنية للكلمات", - "cueMaxChars": "الحد الأقصى للأحرف لكل مقطع", - "cueMaxSeconds": "الحد الأقصى للثواني لكل مقطع", - "cueMaxCps": "الحد الأقصى للأحرف في الثانية", - "vocabularyPrompt": "موجّه المفردات (الأسماء، الأماكن، المصطلحات)", - "beamSize": "حجم الشعاع", - "beamSizeNote": "القيمة 0 تفك التشفير بأسرع سرعة. القيمة 5 تعطي أفضل دقة (مثل Faster Whisper) مقابل زيادة في الوقت بنحو 20-30%. نادرًا ما تفيد القيم الأعلى من 5.", "removeNonSpeechLines": "إزالة الأسطر غير الكلامية", "removeNonSpeechLinesHint": "إسقاط الأسطر التي تصف الصوت فقط، مثل \"[موسيقى]\" أو \"(تلاطم الأمواج)\"", "removeRepeatedLines": "إزالة الأسطر المكررة", @@ -2489,14 +2659,11 @@ }, "textToSpeech": { "title": "تحويل النص إلى كلام", - "textToSpeechEngine": "محرك تحويل النص إلى كلام", "reviewAudioSegments": "TTS - مراجعة المقاطع الصوتية", "reviewAudioSegmentsHistory": "TTS - مراجعة سجل الصوت", "stability": "الثبات", "similarity": "التشابه", "speakerBoost": "تعزيز المتحدث", - "styleExaggeration": "المبالغة في النمط", - "regenerateAudioSelectedLine": "إعادة توليد الصوت للسطر المحدد", "generateSpeechFromText": "توليد كلام من النص", "testVoice": "اختبار الصوت", "addAudioToVideoFile": "إضافة الصوت إلى ملف الفيديو", @@ -2515,6 +2682,19 @@ "videoEncodingSettings": "TTS - إعدادات ترميز الفيديو", "elevenLabsSettings": "TTS - إعدادات ElevenLabs", "elevenLabsSettingsResetHint": "إعادة تعيين إعدادات ElevenLabs إلى القيم الافتراضية", + "provider": "المزوّد", + "openAiCompatibleSettings": "TTS - إعدادات متوافقة مع OpenAI", + "customServerUrl": "عنوان URL للخادم المخصص", + "customServerUrlHint": "نقطة نهاية الكلام الكاملة لخادم متوافق مع OpenAI، تُستخدم عندما يكون المزوّد \"مخصص\"، مثل http://localhost:8880/v1/audio/speech", + "customModels": "نماذج مخصصة", + "customModelsHint": "أسماء النماذج مفصولة بفواصل، تُعرض عندما يكون المزوّد \"مخصص\"", + "customVoices": "أصوات مخصصة", + "customVoicesHint": "أسماء الأصوات مفصولة بفواصل، يستخدمها المزوّد \"مخصص\" ونماذج OpenRouter التي لا تنشر قائمة أصوات", + "instructions": "التعليمات", + "instructionsHint": "أسلوب نطق اختياري، مثل \"تحدث بصوت هادئ ودافئ\". يُرسل فقط إلى النماذج التي تدعمه (ليس tts-1/tts-1-hd)", + "openAiSpeedHint": "سرعة الكلام من 0.25 إلى 4.0 (1.0 هي العادية). لا تدعمها جميع النماذج", + "outputFormat": "تنسيق الإخراج", + "outputFormatHint": "تنسيق الصوت المطلوب من الخادم. \"تلقائي\" يطلب MP3 ويتحول إلى PCM للنماذج التي لا تُرجع سوى PCM (مثل Gemini TTS)", "regenerateAudio": "إعادة توليد الصوت", "autoContinuePlaying": "المتابعة التلقائية للتشغيل", "playLine": "تشغيل السطر", @@ -2523,6 +2703,13 @@ "addingAudioToVideoFileDotDotDot": "جارٍ إضافة الصوت إلى ملف الفيديو...", "preparingMergeDotDotDot": "جارٍ تحضير الدمج...", "importVoiceDotDotDot": "استيراد صوت...", + "renameVoiceDotDotDot": "إعادة تسمية الصوت...", + "renameVoiceTitle": "إعادة تسمية الصوت", + "voiceXCouldNotBeRenamedX": "تعذّرت إعادة تسمية الصوت '{0}': {1}", + "deleteVoiceDotDotDot": "حذف الصوت...", + "deleteVoiceTitle": "حذف الصوت", + "deleteVoiceXQuestion": "هل تريد حذف الصوت '{0}'؟\n\nستُزال التسجيل المرجعي وملفاته من القرص.", + "voiceXCouldNotBeDeletedX": "تعذّر حذف الصوت '{0}': {1}", "voiceImportSuccessTitle": "تم استيراد الصوت", "voiceXImported": "تم استيراد الصوت '{0}' بنجاح", "voiceXCouldNotBeImported": "تعذر استيراد الصوت '{0}' - راجع السجل للتفاصيل", @@ -2532,6 +2719,49 @@ "dropAudioFileHereToImportVoice": "أفلِت ملف الصوت هنا لاستيراد الصوت", "dropAudioFileHereHint": ".wav أو .mp3", "voiceCloneTranscriptTitle": "أدخل نص تفريغ الصوت (مطلوب لاستنساخ الصوت)", + "voiceManagerTitle": "TTS - مدير الأصوات", + "voiceManagerDotDotDot": "مدير الأصوات...", + "voiceManagerMenuItem": "مدير أصوات TTS...", + "speakFromCurrentLineTitle": "النطق من السطر الحالي", + "speakFromCurrentLineDotDotDot": "النطق من السطر الحالي...", + "speakFromCurrentLineHint": "يقرأ الأسطر بصوت عالٍ واحدًا تلو الآخر، بدءًا من السطر الحالي. اضغط Escape أو انقر على سطر آخر للإيقاف.", + "stopSpeaking": "إيقاف النطق", + "speakingLineX": "جارٍ نطق السطر {0}... (Escape للإيقاف)", + "playWithSpeechTitle": "التشغيل مع الكلام", + "playWithSpeechDotDotDot": "التشغيل مع الكلام...", + "playWithSpeechHint": "يشغّل الفيديو وينطق كل سطر عندما يصل إليه الفيديو. أوقف مؤقتًا وانتقل كالمعتاد؛ اضغط Escape للإيقاف.", + "lowerVideoVolumeWhileSpeaking": "خفض صوت الفيديو أثناء النطق", + "pauseVideoWhenSpeechRunsLate": "إيقاف الفيديو مؤقتًا عندما يمتد الكلام إلى السطر التالي", + "waitingForSpeechLineX": "جارٍ توليد الكلام للسطر {0}... (Escape للإيقاف)", + "voiceKindClone": "صوت مستنسخ", + "voiceKindPreset": "مدمج", + "voiceKindModel": "نموذج صوت", + "voiceKindOnline": "عبر الإنترنت", + "transcript": "النص المنسوخ", + "transcriptRequiredHint": "مطلوب - يقرأ المحرك هذا النص المنسوخ للتسجيل (ref-text) عند الاستنساخ", + "transcriptOptionalHint": "اختياري - النص المنسوخ الدقيق للتسجيل يحسّن الاستنساخ", + "saveTranscript": "حفظ النص المنسوخ", + "searchVoices": "البحث في الأصوات", + "loadingVoicesDotDotDot": "جارٍ تحميل الأصوات...", + "noVoicesForEngine": "لا توجد أصوات - استورد تسجيلاً أو نزّل حزمة أصوات", + "noVoicesMatchFilter": "لا توجد أصوات تطابق البحث", + "copyVoiceTo": "نسخ الصوت إلى", + "copyVoiceToDotDotDot": "نسخ الصوت إلى محرك...", + "copyingVoiceXToYDotDotDot": "جارٍ نسخ الصوت '{0}' إلى {1}...", + "voiceXCopiedToY": "تم نسخ الصوت '{0}' إلى {1}", + "voiceXCouldNotBeCopiedToY": "تعذّر نسخ الصوت '{0}' إلى {1} - راجع السجل للتفاصيل", + "voiceXAlreadyExistsInYContinue": "يوجد صوت باسم '{0}' بالفعل في {1}.\n\nهل تريد النسخ على أي حال؟", + "openVoicesFolder": "فتح مجلد الأصوات", + "downloadVoicePacksDotDotDot": "تنزيل حزم الأصوات...", + "downloadVoicePacksTitle": "TTS - تنزيل حزم الأصوات", + "voicePackIntro": "حزم الأصوات هي مجموعات من التسجيلات القصيرة والنظيفة مع نصوصها المنسوخة، جاهزة للاستنساخ منها. تُستورد كل حزمة إلى المحرك المختار (تُتخطى الأصوات الموجودة بالفعل) وتتضمن ملف ATTRIBUTION.txt بترخيصها ومصادرها.", + "voicePackDetailsXVoicesYSizeZLicense": "{0} صوت · {1} · {2}", + "installToEngine": "التثبيت في المحرك", + "downloadAndInstall": "التنزيل والتثبيت", + "selectAtLeastOneVoicePack": "حدد حزمة أصوات واحدة على الأقل", + "downloadingXDotDotDot": "جارٍ تنزيل {0}...", + "installingXDotDotDot": "جارٍ تثبيت {0}...", + "xVoicesInstalledYSkipped": "تم تثبيت {0} صوت ({1} موجود بالفعل)", "useSpeechToTextDotDotDot": "استخدام تحويل الكلام إلى نص...", "autoCastMenuItem": "البحث عن أصوات في الفيديو واستنساخها...", "autoCastSpeakersTitle": "الأصوات التي عُثر عليها في الفيديو", @@ -2550,7 +2780,7 @@ "cloneVoicePerLinePreparing": "جارٍ أخذ صوت كل سطر من الفيديو...", "cloneVoicePerLineNeedsVideo": "يتطلب استنساخ صوت كل سطر الفيديو الذي تنتمي إليه الترجمة. افتح الفيديو وحاول مرة أخرى.", "cloneVoicePerLineNoClips": "تعذر أخذ أي صوت من الفيديو، لذا لا يوجد ما يُستنسخ منه. تأكد من أن الفيديو يحتوي على مسار صوتي.", - "cloneVoicePerLineNeedsOriginalSubtitleX": "لا يستطيع {0} استنساخ سطر إلا إذا عرف ما يُقال في الفيديو عند ذلك السطر، وهو ما يأتي من ترجمة اللغة الأصلية. لا توجد ترجمة أصلية محمّلة، لذا سيُنطق كل سطر بأول صوت مستورد بدلاً من الصوت الموجود في الفيديو.\n\nافتح الترجمة الأصلية (ملف > فتح الأصل...) بجوار الترجمة، ثم ولّد مرة أخرى.", + "cloneVoicePerLineTranscribeClipsXYZ": "لا يستطيع {0} استنساخ سطر إلا إذا عرف ما يُقال في الفيديو عند ذلك السطر. هذا غير معروف لـ {1} من {2} سطر - وعادةً يأتي من ترجمة اللغة الأصلية (ملف > فتح الأصلي...).\n\nهل تريد نسخ تلك الأسطر من الفيديو باستخدام تحويل الكلام إلى نص الآن؟\n\nنعم: اختر المحرك واللغة المنطوقة في الفيديو، وتُنسخ المقاطع المرجعية قبل توليد الكلام.\nلا: التوليد على أي حال - تُنطق تلك الأسطر بصوت عادي بدلاً من الصوت الموجود في الفيديو.", "voiceCloneConsentTitle": "استنساخ الصوت - قبل أن تبدأ", "voiceCloneConsentHeader": "أنت على وشك استنساخ صوت", "voiceCloneConsentIntro": "الاستنساخ ينسخ صوت شخص حقيقي. ولهذا قواعد في معظم الأماكن، وفي الاتحاد الأوروبي واجبات قانونية تقع عليك أنت، لا على Subtitle Edit. يُرجى قراءة هذا مرة واحدة.", @@ -2567,10 +2797,29 @@ "advancedTtsSettings": "إعدادات TTS المتقدمة", "advancedTtsAudioProcessing": "معالجة الصوت", "advancedTtsOutput": "الإخراج", + "advancedTtsVoiceCloning": "استنساخ الصوت", + "cleanCloneReferences": "تنظيف مراجع استنساخ الصوت (Sidon)", + "cleanCloneReferencesDescription": "يزيل الموسيقى والضوضاء والصدى من الصوت الذي يُستنسخ منه الصوت: المقاطع المأخوذة من الفيديو والتسجيلات المستوردة في مدير الأصوات. يستخدم نموذج استعادة الكلام Sidon على audio.cpp (تنزيل 940 MB). لم يعد الصوت المستنسخ ينسخ الخلفية، لكن الكلام الهادئ جدًا أو المتداخل قد يخرج مكتومًا.", + "cleaningCloneReferencesDotDotDot": "جارٍ تنظيف مراجع استنساخ الصوت...", + "downloadSidonTitle": "تنزيل Sidon؟", + "downloadSidonQuestionX": "يتطلب تنظيف مراجع استنساخ الصوت نموذج استعادة الكلام Sidon ({0}).\n\nهل تريد تنزيله الآن؟ بدونه تُستخدم المراجع كما هي.", "proAudioPostProcessing": "معالجة صوتية لاحقة احترافية", "proAudioPostProcessingDescription": "يطبّق دفء الموازن، وبوابة الضوضاء، والضغط، وتسوية مستوى الصوت (-16 LUFS)، والتلاشي للداخل/للخارج على كل مقطع.", + "generatingSpeechSegmentXOfY": "جارٍ توليد الكلام: المقطع {0} من {1}", + "adjustingSpeedSegmentXOfY": "جارٍ ضبط السرعة: المقطع {0} من {1}", + "mergingAudioSegmentXOfY": "جارٍ دمج الصوت: المقطع {0} من {1}", + "postProcessingSegmentXOfY": "المعالجة اللاحقة: المقطع {0} من {1}", "audioDucking": "خفض الصوت التلقائي", "audioDuckingDescription": "يخفض مستوى صوت الفيديو الأصلي ويمزجه مع صوت TTS، بحيث يظل المسار الصوتي الأصلي مسموعًا بخفوت.", + "removeOriginalSpeech": "إزالة الكلام الأصلي (بطيء)", + "removeOriginalSpeechDescription": "يحتفظ بالموسيقى والمؤثرات الصوتية للفيديو الأصلي لكنه يزيل كلامه، ويمزج الكلام الجديد فوقها. يستغرق تقريبًا مدة الفيديو نفسه مع GPU، وأضعافها بدونه. عند تفعيل خفض الصوت التلقائي، يُطبق مستواه على الموسيقى والمؤثرات. لا ينطبق إلا عند إضافة الكلام إلى ملف الفيديو.", + "removingOriginalSpeech": "جارٍ إزالة الكلام الأصلي...", + "removeOriginalSpeechFailed": "تعذّرت إزالة الكلام الأصلي - أُضيف الكلام إلى الفيديو بدون ذلك.", + "speechRemovalModel": "النموذج", + "speechRemovalModelDescription": "أفضل جودة تحتفظ بأكبر قدر من الموسيقى والمؤثرات الصوتية. السريع أسرع بنحو ثلاث مرات، مع فقدان قدر أكبر قليلاً من الموسيقى.", + "speechRemovalBestQuality": "أفضل جودة (Mel-Band RoFormer)", + "speechRemovalFast": "سريع (HTDemucs)", + "videoTruncatedWarning": "الصورة في ملف الفيديو الجديد تتوقف بعد {0}، لكن طول الفيديو الأصلي {1} - ستتجمد الصورة بينما يستمر الصوت.\n\nتم الاحتفاظ بالملف. راجع error-log.txt في مجلد بيانات Subtitle Edit لمخرجات ffmpeg.", "originalVolumePercent": "نسبة مستوى الصوت الأصلي %", "vadSilenceCompression": "ضغط الصمت بـ VAD", "vadSilenceCompressionDescription": "يقصّر فترات التوقف بين الكلمات قبل تغيير الإيقاع. يستخدم كشف نشاط الصوت لضغط فجوات الصمت فقط مع إبقاء الكلام دون تغيير. هذه هي الخطوة الأولى المفضّلة، إذ تقلل المدة دون أي فقدان في الجودة.", @@ -2644,7 +2893,6 @@ "subtitleMergedLinesAppliedPlural": "تم تطبيق {0} عملية دمج أسطر من تحويل النص إلى كلام" }, "shotChanges": { - "titleGenerateOrImport": "إنشاء/استيراد تغييرات اللقطات", "generateShotChanges": "إنشاء تغييرات اللقطات", "importShotChanges": "استيراد تغييرات اللقطات", "generateShotChangesWithFfmpeg": "إنشاء تغييرات اللقطات باستخدام ffmpeg", @@ -2654,7 +2902,9 @@ "timeCodeFormatColon": "صيغة الرمز الزمني:", "shotChangesList": "قائمة تغييرات اللقطات", "shotChangesClearQuestion": "هل أنت متأكد أنك تريد مسح جميع تغييرات اللقطات؟", - "deleteSelectedShotChangeQuestion": "هل أنت متأكد أنك تريد حذف تغيير اللقطة المحدد؟" + "deleteSelectedShotChangeQuestion": "هل أنت متأكد أنك تريد حذف تغيير اللقطة المحدد؟", + "shotChanges": "تغييرات اللقطات", + "exportShotChanges": "تصدير تغييرات اللقطات" }, "chapters": { "title": "الفصول", @@ -2684,7 +2934,6 @@ "scaleTimesDescription": "تحجيم وقت كل فصل من معدل إطارات إلى آخر.", "fromFrameRate": "من", "toFrameRate": "إلى", - "apply": "تطبيق", "writeToVideo": "كتابة إلى الفيديو...", "writeToVideoTitle": "كتابة الفصول إلى الفيديو", "writeToVideoDescription": "تُكتب الفصول في نسخة من ملف الفيديو. لا يُعاد ترميز أي شيء، لذا فالعملية سريعة وبلا فقد.", @@ -2694,7 +2943,6 @@ "writtenToX": "كُتبت الفصول إلى {0}", "unableToWriteChapters": "تعذرت كتابة الفصول في ملف الفيديو.", "chaptersSavedToX": "حُفظت الفصول في {0}", - "noVideoLoaded": "لم يُحمَّل أي فيديو", "emptyListCallToAction": "لا توجد فصول بعد - أضف فصلاً عند موضع الفيديو، أو استوردها من فيديو أو ملف." }, "videoOcr": { @@ -2705,10 +2953,6 @@ "bottomHalf": "النصف السفلي", "fullFrame": "الإطار الكامل", "engine": "محرك OCR", - "language": "اللغة", - "url": "URL", - "model": "النموذج", - "apiKey": "مفتاح API", "scan": "مسح", "framesPerSecond": "الإطارات في الثانية", "textBrightnessMinimum": "الحد الأدنى لسطوع النص (0=إيقاف)", @@ -2723,10 +2967,10 @@ "runningOcrXY": "جارٍ تشغيل OCR... {0}/{1}", "noLinesFoundTitle": "لم يُعثر على ترجمة", "noLinesFoundMessage": "لم يُعثر على نص في منطقة المسح، جرّب ضبط منطقة المسح أو المحرك أو الحد الأدنى للسطوع.", + "ocrStoppedEarlyMessage": "توقف محرك OCR قبل قراءة جميع الإطارات، لذا تم الاحتفاظ بالأسطر التي عُثر عليها حتى الآن والباقي مفقود.{0}{0}{1}", "linesFoundX": "تم العثور على {0} سطر", "refiningTimingXY": "جارٍ تحسين التوقيت... {0}/{1}", "fixOcrErrors": "إصلاح أخطاء التعرف الضوئي على الحروف", - "dictionary": "القاموس", "editLineX": "تحرير السطر {0}", "downloadDictionaryHint": "تنزيل قاموس تدقيق إملائي...", "framesPerSecondHint": "عدد الإطارات التي تُفحص في الثانية. القيمة 5 تكفي للترجمات العادية؛ والقيم الأعلى تلتقط الأسطر القصيرة جداً وتُحكم التجميع، لكنها تكلف وقت تعرف ضوئي أكبر.", @@ -2743,7 +2987,58 @@ "testOcr": "اختبار الإطار الحالي", "testOcrRunning": "جارٍ اختبار OCR على الإطار الحالي...", "testOcrResultX": "نتيجة الاختبار: {0}", - "testOcrNoTextFound": "الاختبار: لم يُعثر على نص في منطقة المسح" + "testOcrNoTextFound": "الاختبار: لم يُعثر على نص في منطقة المسح", + "engineSettings": "إعدادات محرك OCR", + "engineBuiltIn": "مدمج في نظام التشغيل", + "engineExternalServer": "خادم خارجي (مثبت ومُدار خارج Subtitle Edit)", + "engineCloudApi": "Cloud API (يتطلب مفتاح API)", + "engineNothingToInstall": "لا شيء للتثبيت", + "engineModelsMissing": "المحرك مثبت، والنماذج مفقودة", + "website": "الموقع الإلكتروني" + }, + "backgroundMusic": { + "title": "توليد موسيقى الخلفية", + "generateBackgroundMusicDotDotDot": "توليد موسيقى الخلفية...", + "preset": "الإعداد المسبق", + "prompt": "المطالبة", + "tempoBpm": "الإيقاع (BPM)", + "generateSeconds": "التوليد (ثوانٍ)", + "seed": "البذرة", + "randomSeed": "عشوائي", + "outputLength": "طول الإخراج", + "outputLengthVideoX": "{0} (طول الفيديو - تتكرر الموسيقى بسلاسة)", + "musicVolumePercent": "مستوى صوت الموسيقى (%)", + "removeExistingAudioTracks": "إزالة المسارات الصوتية الموجودة", + "originalAudioVolumePercent": "مستوى الصوت الأصلي (%)", + "saveAudioDotDotDot": "حفظ الصوت...", + "addToVideoDotDotDot": "إضافة إلى الفيديو...", + "engineInfo": "ACE-Step 1.5 Turbo عبر audio.cpp (ترخيص MIT) - يعمل محليًا", + "presetCooking": "الطبخ / الخَبز", + "presetTech": "التقنية / مراجعة الأجهزة", + "presetForest": "الغابة / الطبيعة", + "presetRelaxingPiano": "بيانو مريح", + "presetLofiVlog": "مدونة فيديو Lo-fi", + "presetCorporate": "الشركات / الشروحات", + "loadingModel": "جارٍ تحميل النموذج...", + "composing": "جارٍ التأليف...", + "generatingAudio": "جارٍ توليد الصوت...", + "decodingAudio": "جارٍ فك ترميز الصوت...", + "creatingLoop": "جارٍ إنشاء حلقة سلسة...", + "resultLoopXYZ": "الحلقة: {0} عند {1} BPM، تطابق الوصلة {2}%", + "resultNoLoopX": "الموسيقى جاهزة: {0}", + "addingMusicToVideo": "جارٍ إضافة الموسيقى إلى الفيديو...", + "downloadModelTitle": "تنزيل نموذج الموسيقى؟", + "downloadModelQuestionX": "يتطلب توليد الموسيقى نموذج ACE-Step 1.5 ({0}).\n\nهل تريد تنزيل النموذج؟", + "downloadingModel": "جارٍ تنزيل نموذج ACE-Step 1.5...", + "saveAudioTitle": "حفظ موسيقى الخلفية", + "audioFileSavedX": "تم حفظ الملف الصوتي: {0}", + "unableToGenerateMusic": "تعذّر توليد الموسيقى", + "unableToAddMusicToVideo": "تعذّرت إضافة الموسيقى إلى الفيديو", + "outputLengthTextToSpeech": "يتبع الكلام المولَّد - تتكرر الموسيقى بسلاسة وتنخفض أثناء حديث أحدهم", + "addBackgroundMusic": "إضافة موسيقى خلفية", + "addBackgroundMusicHint": "توليد موسيقى محليًا باستخدام ACE-Step 1.5 ومزجها تحت الكلام - تنخفض أثناء حديث أحدهم", + "generatingBackgroundMusicDotDotDot": "جارٍ توليد موسيقى الخلفية...", + "mixingBackgroundMusicDotDotDot": "جارٍ مزج موسيقى الخلفية..." }, "goToVideoPosition": "الانتقال إلى موضع الفيديو", "goToVideoPositionDotDotDot": "الانتقال إلى موضع الفيديو...", @@ -2754,30 +3049,88 @@ "openSecondarySubtitleOnVideoPlayerDotDotDot": "الترجمة الثانوية على مشغل الفيديو، فتح...", "openSecondarySubtitleOnVideoPlayer": "الترجمة الثانوية (على مشغل الفيديو)", "removeSecondarySubtitleOnVideoPlayer": "الترجمة الثانوية على مشغل الفيديو، إزالة", + "editSecondarySubtitleSettings": "تحرير إعدادات الترجمة الثانية", + "editSecondarySubtitleSettingsDotDotDot": "تحرير إعدادات الترجمة الثانية...", + "secondarySubtitleRememberSettings": "تذكر هذه الإعدادات", + "secondarySubtitleDoNotShowAgain": "عدم إظهار مربع الحوار هذا مجددًا", + "secondarySubtitleJustifyLines": "ضبط محاذاة الأسطر", "openRecentVideo": "فتح فيديو حديث", "clearRecentVideos": "مسح مقاطع الفيديو الحديثة", "cutVideoTitle": "قص الفيديو", "cutVideoDotDotDot": "قص الفيديو...", + "remuxVideoTitle": "إعادة تغليف الفيديو (Remux)", + "remuxVideoDotDotDot": "إعادة تغليف الفيديو (Remux)...", + "remuxVideoInputVideo": "فيديو الإدخال:", + "remuxVideoMultipleTracksRequiresMkv": "تتطلب المسارات الصوتية أو مسارات الترجمة المتعددة حاوية MKV. تم تحويل تنسيق الإخراج تلقائيًا إلى .mkv.", + "remuxVideoOutputFormat": "تنسيق الإخراج:", + "remuxVideoOutputFile": "ملف الإخراج:", + "remuxVideoRemuxing": "جارٍ إعادة تغليف الفيديو والصوت...", + "remuxVideoFinalizing": "جارٍ إنهاء ملف الإخراج (نقل الفهرس إلى البداية)...", + "remuxVideoCompleted": "تمت إعادة تغليف الفيديو بنجاح.", + "remuxVideoFailed": "فشلت إعادة تغليف الفيديو.", + "remuxVideoPleaseSelectBoth": "يرجى تحديد ملفي الفيديو والصوت كليهما.", + "remuxVideoAssRequiresMkv": "تتطلب ترجمات ASS/SSA حاوية MKV للحفاظ على كل التنسيقات والأنماط.", + "remuxVideoSccRequiresMov": "تُضمَّن ترجمات Scenarist (.scc) كتسميات توضيحية مغلقة CEA-608، والتي تتطلب حاوية MOV أو MPG. تم تحويل تنسيق الإخراج تلقائيًا إلى .mov.", + "remuxVideoSccNotWithWebM": "تحتاج التسميات التوضيحية المغلقة Scenarist (.scc) إلى حاوية MOV، والتي لا يمكنها احتواء فيديو VP9/AV1 لملف .webm. أزل ملف .scc أو حوّله إلى تنسيق ترجمة آخر.", + "remuxVideoMpgMaxTwoCaptionTracks": "يحتوي ملف .mpg على مسارين كحد أقصى للتسميات التوضيحية المغلقة (CC1 وCC3) - أزل ملفات ترجمة بحيث يتبقى واحد أو اثنان.", + "remuxVideoMccRequiresMpg": "تُضمَّن ترجمات MacCaption (.mcc) كتسميات توضيحية مغلقة CEA-608/708 في فيديو MPEG-2، الذي يتطلب حاوية MPG. تم تحويل تنسيق الإخراج تلقائيًا إلى .mpg.", + "remuxVideoMpgReencodeVideoX": "تحتاج التسميات التوضيحية المغلقة في ملف .mpg إلى فيديو MPEG-2، والفيديو هو {0}. هل تريد إعادة ترميز الفيديو إلى MPEG-2؟ يستغرق ذلك وقتًا أطول ويخفض الجودة قليلاً.", + "remuxVideoFileHasNoAudioX": "\"{0}\" لا يحتوي على مسار صوتي، لذا لا يمكن استخدامه كمصدر صوت.", + "remuxVideoAddingClosedCaptions": "جارٍ إضافة التسميات التوضيحية المغلقة...", + "remuxVideoSelectAudioTrack": "تحديد المسار الصوتي", + "remuxVideoSelectAudioTrackFor": "تحديد المسار الصوتي لـ {0}", + "remuxVideoSelectAudioTrackPrompt": "يحتوي الملف '{0}' على {1} مسارات صوتية. يرجى تحديد المسار الصوتي لإعادة التغليف:", + "remuxVideoVideoFile": "فيديو", + "remuxVideoAudioFile": "صوت", + "remuxVideoAudioFiles": "الملفات الصوتية:", + "remuxVideoSubtitleFiles": "ملفات الترجمة (اختياري، ترجمات منفصلة):", + "remuxVideoSelectAudioTrackDotDotDot": "تحديد المسار الصوتي...", + "remuxVideoFilesX": "{0} ملفات", + "remuxVideoMixAudio": "مزج الصوت في مسار واحد", + "remuxVideoVolumePercent": "مستوى الصوت %:", + "remuxVideoVolumeX": "مستوى الصوت {0}%", + "remuxVideoFastStart": "التحسين للبث عبر الويب (نقل الفهرس إلى المقدمة - أبطأ)", "embedSubtitlesDotDotDot": "إضافة/إزالة الترجمة المضمّنة...", "generateTimeCodes": "إنشاء الرموز الزمنية", "checkeredImage": "صورة مربّعات", - "saveVideoAsTitle": "حفظ الفيديو باسم", "promptForFfmpegParamsAndGenerate": "طلب معاملات ffmpeg والإنشاء", "cutVideoCutSegments": "قص المقاطع", "cutVideoMergeSegments": "دمج المقاطع", "cutVideoSplitSegments": "حفظ المقاطع كلٌ على حدة", - "cutVideoType": "نوع القص", - "mpvRenderAuto": "تلقائي", - "mpvRenderNative": "أصلي", - "mpvRenderOpenGl": "OpenGL", - "mpvRenderSoftware": "برمجي (بطيء)", "cutVideoAlsoCutSubtitle": "قص الترجمة أيضاً", "cutVideoSubtitleFileGeneratedX": "تم توليد ملف الترجمة: {0}", + "cutVideoTransition": "الانتقال بين المقاطع", + "cutVideoTransitionDurationSeconds": "المدة (ثوانٍ)", + "cutVideoFadeInSeconds": "التلاشي من الأسود في البداية (ثوانٍ)", + "cutVideoFadeOutSeconds": "التلاشي إلى الأسود في النهاية (ثوانٍ)", + "cutVideoPreviewTransition": "معاينة الانتقال", + "cutVideoPreviewTransitionTitle": "معاينة الانتقال", + "cutVideoPreviewNeedsJoin": "لا يوجد وصل للمعاينة - أضف مقطعين على الأقل للدمج، أو مقطعًا لقصه من المنتصف.", + "cutVideoTransitionInfoX": "يتداخل كل انتقال مع الأجزاء التي يصلها، لذا يقصر الإخراج بمقدار {0} ثانية لكل وصل. يُعاد توقيت الترجمة المقصوصة لتتطابق.", + "cutVideoTransitionFade": "تلاشٍ", + "cutVideoTransitionFadeBlack": "تلاشٍ عبر الأسود", + "cutVideoTransitionFadeWhite": "تلاشٍ عبر الأبيض", + "cutVideoTransitionFadeGrays": "تلاشٍ عبر الرمادي", + "cutVideoTransitionDissolve": "ذوبان", + "cutVideoTransitionWipeLeft": "مسح لليسار", + "cutVideoTransitionWipeRight": "مسح لليمين", + "cutVideoTransitionWipeUp": "مسح للأعلى", + "cutVideoTransitionWipeDown": "مسح للأسفل", + "cutVideoTransitionSlideLeft": "انزلاق لليسار", + "cutVideoTransitionSlideRight": "انزلاق لليمين", + "cutVideoTransitionSlideUp": "انزلاق للأعلى", + "cutVideoTransitionSlideDown": "انزلاق للأسفل", + "cutVideoTransitionSmoothLeft": "انسيابي لليسار", + "cutVideoTransitionSmoothRight": "انسيابي لليمين", + "cutVideoTransitionCircleOpen": "فتح دائري", + "cutVideoTransitionCircleClose": "إغلاق دائري", + "cutVideoTransitionRadial": "شعاعي", + "cutVideoTransitionPixelize": "تقطيع إلى بكسلات", + "cutVideoTransitionBlur": "تمويه", "importCurrentSubtitle": "استيراد الترجمة الحالية", "addRemoveEmbeddedSubtitlesTitle": "إضافة/إزالة الترجمة المضمّنة", "addRemoveEmbeddedSubtitlesMp4Title": "إضافة/إزالة الترجمة المضمّنة (MP4)", "addCurrentSubtitle": "إضافة الترجمة الحالية", - "embeddedTrackColumnNew": "جديد", "embeddedTrackNoSubtitlesFoundTitle": "لم يُعثر على ترجمة", "embeddedTrackNoSubtitlesFoundMessage": "ملف الترجمة المحدد لا يحتوي على أي ترجمة.", "embeddedTrackNoTracksTitle": "لم تُضَف أي مسارات", @@ -2789,7 +3142,7 @@ "embeddedTrackGeneratingVideoXY": "جارٍ إنشاء الفيديو... {0}% {1}", "embeddedTrackGeneratingVideo": "جارٍ إنشاء الفيديو...", "mp4FilesFilter": "ملفات MP4", - "titleOrLanguage": "العنوان/اللغة", + "embeddedTracksPickVideoHint": "اختر ملف فيديو، أو أفلته هنا، لرؤية مسارات الترجمة الخاصة به.", "viewMatroskaTrackX": "عرض مسار Matroska - {0}", "resolutionSeparator": "x", "openFromUrlTitle": "فتح ملف فيديو من URL", @@ -2799,9 +3152,7 @@ "openFromUrlDownloadAndOpen": "تنزيل وفتح", "openFromUrlDownloadAndOpenDescription": "حفظ الفيديو محليًا ثم فتحه.", "openFromUrlDownloadAndOpenNote": "مطلوب لتحويل الكلام إلى نص وقمم الموجة الصوتية.", - "openFromUrlMissing": "الرجاء إدخال عنوان URL للفيديو أولًا.", "openFromUrlDownloadingTitle": "جارٍ تنزيل الفيديو", - "openFromUrlSaveAs": "حفظ الفيديو باسم", "openFromUrlDownloadSubtitles": "تنزيل الترجمة أيضًا", "openFromUrlDownloadVideo": "تنزيل الفيديو", "openFromUrlIncludeAutoGeneratedSubtitles": "تضمين الترجمات المولّدة تلقائياً (اللغة الأصلية)", @@ -2838,12 +3189,15 @@ "reEncodeGeneratingDone": "اكتمل الإنشاء", "reEncodeGeneratedFilesX": "الملفات المُنشأة ({0}):", "reEncodeFfmpegParameters": "معاملات ffmpeg", + "analyzingVideoX": "جارٍ تحليل الفيديو... {0}% {1}", + "analyzingVideoXofY": "جارٍ تحليل الفيديو {0}/{1}... {2}% {3}", + "backgroundImageFileNotSelected": "لم يتم تحديد ملف صورة الخلفية", + "backgroundImageFileDoesNotExistX": "ملف صورة الخلفية غير موجود: {0}", "voices": "الأصوات", "presets": "الإعدادات المسبقة", "talkerX": "المتحدث {0}", "baseModel": "النموذج الأساسي", "turboModel": "النموذج السريع", - "ttsUpdateX": "تحديث {0}", "ttsEngineNotInstalled": "{0} غير مثبت", "ttsEngineUpdateAvailable": "{0} - يتوفر تحديث", "ttsEngineTooOldUpdateRequired": "{0} قديم جداً - التحديث مطلوب", @@ -2866,19 +3220,25 @@ "cloneVoiceTo": "استنساخ الصوت إلى", "cloneVoiceNameTitle": "اسم الصوت الجديد", "cloneVoiceExtractFailed": "تعذر استخراج الصوت للسطر المحدد.", + "lineXHasNoDuration": "السطر {0} ليس له مدة (وقت النهاية ليس بعد وقت البداية)، لذا لا يوجد صوت لاستخراجه.", + "audioClipsSkippedLinesX": "تعذّر استخراج أي صوت لهذه الأسطر، لذا تم تخطيها: {0}", "toggleShotChange": "تبديل تغيير اللقطة", "resetWaveformZoomAndSpeed": "إعادة تعيين تكبير الموجة الصوتية وسرعتها", "showOriginalSubtitle": "إظهار الترجمة الأصلية", + "timelineGroupTracksBy": "تجميع مسارات الترجمة حسب", + "timelineShowThumbnails": "إظهار الصور المصغرة للفيديو", + "showSpeechOnly": "إظهار الكلام فقط (بطيء التوليد)", + "isolatingSpeechForWaveformX": "جارٍ عزل الكلام للموجة الصوتية... {0}", + "isolatingSpeechForWaveformFailed": "تعذّر عزل الكلام للموجة الصوتية - راجع سجل الأدوات", + "speechOnlyWaveformReady": "تُظهر الموجة الصوتية الآن الكلام فقط", "showOnlyWaveform": "عرض الموجة الصوتية فقط", "showOnlySpectrogram": "عرض الطيف الترددي فقط", "showWaveformAndSpectrogram": "عرض الموجة الصوتية والطيف الترددي", - "spectrogramClassic": "كلاسيكي", "spectrogramClassicViridis": "Viridis", "spectrogramClassicPlasma": "Plasma", "spectrogramClassicInferno": "Inferno", "spectrogramClassicTurbo": "Turbo", "spectrogramNeon": "Neon", - "waveformDrawStyleClassic": "كلاسيكي", "waveformDrawStyleFancy": "مزخرف", "setVideoPositionAndPauseAndSelectSubtitle": "تعيين موضع الفيديو والإيقاف المؤقت وتحديد الترجمة", "setVideopositionAndPauseAndSelectSubtitleAndCenter": "تعيين موضع الفيديو والإيقاف المؤقت وتحديد الترجمة والتوسيط", @@ -2888,7 +3248,6 @@ }, "sync": { "visualSync": "المزامنة المرئية", - "sync": "المزامنة", "startScene": "مشهد البداية", "endScene": "مشهد النهاية", "playTwoSecondsAndBack": "تشغيل ثانيتين والرجوع", @@ -2904,19 +3263,18 @@ "adjustSelectedLinesAndForward": "ضبط الأسطر المحددة وما بعدها", "toFrameRate": "إلى معدل الإطارات", "fromFrameRate": "من معدل الإطارات", + "videoXFrameRateY": "{0} - {1} fps", "adjustAllTimes": "ضبط كل الأوقات (الظهور مبكراً/متأخراً)", "showEarlier": "الظهور مبكراً", "showLater": "الظهور متأخراً", - "changeFrameRate": "تغيير معدل الإطارات", "setSyncPoint": "تعيين نقطة مزامنة", "setSyncPointViaVideo": "تعيين نقطة المزامنة عبر الفيديو...", "syncPointTimeCode": "الرمز الزمني لنقطة المزامنة", "syncPoints": "نقاط المزامنة", "pointSync": "المزامنة بالنقاط", "pointSyncViaOther": "المزامنة بالنقاط عبر ترجمة أخرى", - "adjustmentX": "الضبط: {0}", - "totalAdjustmentX": "إجمالي الضبط: {0}", - "syncPointCandidateInfo": "تُميَّز الأسطر التي تبدأ بعد {0}+ ثانية من الصمت - فهي غالباً ما تشكّل نقاط مزامنة موثوقة", + "syncPointCandidateAfterInfo": "تُبرز الأسطر التي يليها {0}+ ثانية من الصمت - فهي غالبًا نقاط مزامنة موثوقة", + "gapAfter": "الفجوة بعد", "adjustAllShortcuts": "اختصارات لوحة المفاتيح:\r\n\r\n• Shift + يسار/يمين: تحريك 10 ms\r\n• Ctrl + يسار/يمين: تحريك 100 ms\r\n• Alt + يسار/يمين: تحريك 500 ms", "adjustAllShortcutsFrames": "اختصارات لوحة المفاتيح:\r\n\r\n• Shift + يسار/يمين: تحريك إطار واحد\r\n• Ctrl + يسار/يمين: تحريك 10 إطارات\r\n• Alt + يسار/يمين: تحريك ثانية واحدة", "offsetInSeconds": "الإزاحة بالثواني", @@ -2936,6 +3294,7 @@ "lineMerge": "دمج الأسطر", "delayInSecondsBetweenRequests": "التأخير بالثواني بين الطلبات", "maxBytesPerRequest": "أقصى عدد بايت لكل طلب", + "keepMusicLinesUntranslated": "عدم ترجمة الأسطر داخل رموز الموسيقى (♪ ... ♪)", "promptText": "نص الموجِّه", "resetPromptToDefault": "إعادة تعيين الموجّه إلى الافتراضي", "translateEachLineSeparately": "ترجمة كل سطر على حدة", @@ -2949,11 +3308,10 @@ "readyToTranslate": "جاهز للترجمة", "translating": "جارٍ الترجمة...", "translationComplete": "اكتملت الترجمة", + "translateInPlaceNoOriginal": "الترجمة في المكان (دون إنشاء أصلي)", "translationCancelled": "أُلغيت الترجمة", "swapLanguages": "تبديل لغتي المصدر والهدف", "xIsAlreadyDownloadedReDownload": "{0} مُنزَّل بالفعل. إعادة التنزيل؟", - "advancedDotDotDot": "متقدم...", - "advancedSettings": "الإعدادات المتقدمة", "advancedSettingsSubtitle": "خيارات الدفعات والسياق وأخذ العينات لمحرك llama.cpp المتقدم", "context": "السياق", "synopsis": "الملخص", @@ -2977,6 +3335,7 @@ "useOnlyExtraServerParametersHint": "بدء llama-server بالمعاملات أعلاه بدلاً من ضبط Subtitle Edit الخاص - يبقى تعيين النموذج والمضيف والمنفذ فقط", "serverRunningAtX": "الخادم يعمل على {0}", "customPromptHint": "تعليمات مخصصة ({0} = اللغة المصدر، {1} = اللغة الهدف)؛ الفراغ = الموجّه المدمج", + "editTranslationHint": "انقر على ترجمة الصف المحدد لتحريرها (Enter للحفظ، Escape للإلغاء)", "llamaCppDownloadEngineAndModelPrompt": "يتطلب llama.cpp تنزيل محرك llama-server ونموذج ترجمة. هل تريد التنزيل الآن؟", "llamaCppDownloadEnginePrompt": "يتطلب llama.cpp تنزيل محرك llama-server. هل تريد التنزيل الآن؟", "llamaCppDownloadModelPrompt": "يتطلب llama.cpp تنزيل نموذج الترجمة المحدد. هل تريد التنزيل الآن؟" @@ -2989,7 +3348,6 @@ "dialogStyleDashBothLinesWithSpace": "شرطة في كلا السطرين مع مسافة", "dialogStyleDashBothLinesWithoutSpace": "شرطة في كلا السطرين بدون مسافة", "continuationStyle": "نمط الاستمرار", - "continuationStyleNone": "بلا", "continuationStyleNoneTrailingDots": "بلا، نقاط للوقفات (في النهاية فقط)", "continuationStyleNoneLeadingTrailingDots": "بلا، نقاط للوقفات", "continuationStyleNoneTrailingEllipsis": "بلا، علامة حذف للوقفات (في النهاية فقط)", @@ -3000,7 +3358,6 @@ "continuationStyleLeadingTrailingEllipsis": "علامة حذف", "continuationStyleLeadingTrailingDash": "شرطة", "continuationStyleLeadingTrailingDashDots": "شرطة، لكن نقاط للوقفات", - "continuationStyleCustom": "مخصص", "cpsLineLengthStyle": "حرف/ثانية / طول السطر", "cpsLineLengthStyleCalcAll": "عد جميع الأحرف", "cpsLineLengthStyleCalcNoSpaceCpsOnly": "عد الكل عدا المسافة، حرف/ثانية فقط", @@ -3013,14 +3370,6 @@ "cpsLineLengthStyleCalcNoSpaceOrPunctuationCpsOnly": "بلا مسافة أو علامات ترقيم، حرف/ثانية فقط", "cpsLineLengthStyleCalcIgnoreArabicDiacritics": "تجاهل التشكيل العربي", "cpsLineLengthStyleCalcIgnoreArabicDiacriticsNoSpace": "تجاهل التشكيل العربي، بلا مسافة", - "timeCodeModeHhMmSsMs": "HH:MM:SS:MS", - "timeCodeModeHhMmSsFf": "HH:MM:SS:FF", - "splitBehaviorPrevious": "إضافة فجوة على يسار نقطة التقسيم (التركيز على اليمين)", - "splitBehaviorHalf": "إضافة فجوة في منتصف نقطة التقسيم (التركيز على اليسار)", - "splitBehaviorNext": "إضافة فجوة على يمين نقطة التقسيم (التركيز على اليسار)", - "subtitleListActionNothing": "لا شيء", - "subtitleListActionVideoGoToPositionAndPause": "الانتقال إلى موضع الفيديو والإيقاف المؤقت", - "subtitleListActionVideoGoToPositionAndPlay": "الانتقال إلى موضع الفيديو والتشغيل", "subtitleGridCenterText": "توسيط النص في شبكة الترجمة", "minGapCalculateDotDotDot": "حساب الفجوة الدنيا من معدل إطارات...", "minGapCalculateTitle": "الفجوة الدنيا بين الترجمات بالمللي ثانية", @@ -3052,8 +3401,14 @@ "checkForUpdatesChannelStableAndBeta": "الإصدارات المستقرة والتجريبية", "assaAutoSetResolution": "ASSA: تعيين الدقة (PlayResX/PlayResY) من الفيديو عند فتح فيديو", "assaAutoSetResolutionPrompt": "ASSA: السؤال قبل تغيير الدقة لتطابق الفيديو (وإلا يُعاد التحجيم تلقائياً)", + "videoControls": "عناصر التحكم بالفيديو", + "videoControlsEdit": "تحرير عناصر التحكم بالفيديو...", + "videoControlsPositionText": "نص الموضع / المدة", + "videoControlsVideoFileName": "اسم ملف الفيديو", + "videoControlsPlayerName": "اسم مشغل الفيديو", "mergeKeepEndTime": "دمج الأسطر: الاحتفاظ بوقت النهاية (السماح بالتداخل مع الترجمة التالية)", "mergeKeepEndTimeOnlyAssa": "دمج الأسطر: الاحتفاظ بوقت النهاية لتنسيق \"Advanced Sub Station Alpha\" فقط", + "mergeAutoBreak": "دمج الأسطر: فاصل تلقائي للنص المدمج (عند إيقافه تُحفظ فواصل الأسطر الأصلية)", "defaultSaveLocation": "موقع الحفظ الافتراضي", "defaultSaveLocationSourceFileFolder": "مجلد ملف الترجمة/الفيديو", "defaultSaveLocationLastUsedFolder": "آخر مجلد مستخدم", @@ -3061,18 +3416,10 @@ "defaultSaveLocationSubtitleFileFolder": "مجلد ملف الترجمة", "defaultSaveLocationCustomFolder": "مجلد مخصص", "subtitleListActionVideoGoToPositionAndPlayCurrentAndPause": "الانتقال إلى موضع الفيديو، تشغيل الحالي، ثم الإيقاف المؤقت", - "subtitleListActionEditText": "الانتقال إلى مربع تحرير النص", "subtitleListActionVideoGoToPositionMinus1SecAndPause": "الانتقال إلى موضع الفيديو - 1 ثانية والإيقاف المؤقت", "subtitleListActionVideoGoToPositionMinusHalfSecAndPause": "الانتقال إلى موضع الفيديو - 0.5 ثانية والإيقاف المؤقت", "subtitleListActionVideoGoToPositionMinus1SecAndPlay": "الانتقال إلى موضع الفيديو - 1 ثانية والتشغيل", - "subtitleListActionEditTextAndPause": "الانتقال إلى مربع تحرير النص، والإيقاف المؤقت عند موضع الفيديو", - "autoBackupEveryMinute": "كل دقيقة", - "autoBackupEveryXthMinute": "كل {0} دقيقة", - "profiles": "ملفات التعريف", - "autoBackupDeleteAfterXMonths": "حذف النسخ الاحتياطية التلقائية بعد {0} أشهر", "searchSettingsDotDotDot": "البحث في الإعدادات...", - "syntaxColoring": "تلوين الصياغة", - "waveformSpectrogram": "الموجة الصوتية / الطيف الترددي", "network": "الشبكة", "fileTypeAssociations": "اقترانات أنواع الملفات", "textBoxColorTags": "وسوم الألوان (HTML/ASSA) في مربع نص الترجمة", @@ -3105,10 +3452,9 @@ "darkThemeForegroundColor": "لون المقدمة للسمة الداكنة", "darkThemeBackgroundColor": "لون الخلفية للسمة الداكنة", "showGridLines": "إظهار خطوط الشبكة", - "resetSettings": "إعادة تعيين الإعدادات؟", - "resetSettingsDetail": "سيؤدي هذا إلى إعادة تعيين جميع الإعدادات إلى قيمها الافتراضية.\n\nهل تريد المتابعة؟", "showHorizontalLineAboveToolbar": "إظهار خط أفقي فوق شريط الأدوات", "bookmarkColor": "لون العلامة المرجعية", + "spellCheckHighlightColor": "لون تمييز التدقيق الإملائي", "singleLineMaxLength": "الحد الأقصى لطول السطر الواحد", "optimalCharsPerSec": "المعدل الأمثل حرف/ثانية", "maxCharsPerSec": "الحد الأقصى حرف/ثانية", @@ -3121,13 +3467,21 @@ "unbreakSubtitlesShortThan": "إلغاء فصل أسطر الترجمات الأقصر من", "newEmptyDefaultMs": "المدة الافتراضية للترجمة الجديدة (ms)", "timeCodeUpDownStepMs": "خطوة زيادة/إنقاص الوقت (ms)", + "moveSelectedLinesStepMs": "خطوة اختصار نقل الأسطر المحددة (مللي ثانية)", + "moveStartEndStepMs": "خطوة اختصار نقل البداية/النهاية (مللي ثانية)", + "moveLinesShortenNeighbor": "نقل الأسطر: تقصير السطر السابق/التالي بدلاً من التداخل معه", "promptBeforeDelete": "المطالبة قبل الحذف", "rememberPositionAndSize": "تذكر موضع النافذة وحجمها", + "titleBarFullFileName": "إظهار المسار الكامل للملف في شريط العنوان", + "showRecentFiles": "إظهار الملفات الأخيرة (لإعادة الفتح)", "openLastFileOnStart": "فتح آخر ملف حديث عند البدء", "autoSave": "الحفظ التلقائي (حفظ الملف المفتوح أثناء التحرير)", "autoBackupOn": "النسخ الاحتياطي التلقائي", "autoBackupIntervalMinutes": "الفاصل الزمني للنسخ الاحتياطي التلقائي (دقائق)", "autoBackupDeleteAfterDays": "مدة الاحتفاظ بالنسخ الاحتياطية التلقائية (أيام)", + "settingsBackupOn": "النسخ الاحتياطي التلقائي للإعدادات", + "settingsBackupIntervalDays": "الفاصل الزمني للنسخ الاحتياطي للإعدادات (أيام، 0 = كل بدء)", + "settingsBackupMaxCount": "عدد النسخ الاحتياطية للإعدادات المحتفظ بها", "autoConvertToUtf8": "تحويل الترميز تلقائيًا إلى UTF-8 عند الفتح", "autoTrimWhiteSpace": "اقتطاع المسافات البيضاء تلقائيًا", "defaultEncoding": "الترميز الافتراضي", @@ -3143,23 +3497,14 @@ "colorGapTooShort": "تلوين إذا كانت الفجوة قصيرة جدًا", "errorBackgroundColor": "لون خلفية الخطأ", "waveformDrawGridLines": "رسم خطوط الشبكة", - "waveformCenterVideoPosition": "توسيط موضع الفيديو", + "waveformUseSkiaRenderer": "استخدام العارض السريع التجريبي", "waveformCenterVideoPositionAlsoWhenPaused": "توسيط موضع الفيديو أيضًا أثناء الإيقاف المؤقت", + "waveformSelectCurrentSubtitleWhilePaused": "تحديد الترجمة الحالية أيضًا أثناء الإيقاف المؤقت", + "waveformRememberZoom": "تذكر التكبير (الأفقي والعمودي)", "waveformShowToolbar": "إظهار شريط الأدوات", "waveformShowToolbarEdit": "تحرير شريط الأدوات...", "waveformShowToolbarEditLabel": "عناصر شريط الأدوات", "waveformSpectrogramCombinedWaveformHeight": "الموجة الصوتية / الطيف الترددي مدمجان، ارتفاع الموجة الصوتية %", - "showWaveformToolbarPlay": "شريط الأدوات: إظهار زر التشغيل", - "showWaveformToolbarRepeat": "شريط الأدوات: إظهار زر التكرار", - "showWaveformToolbarRemoveBlankLines": "شريط الأدوات: إظهار زر إزالة الأسطر الفارغة", - "showWaveformToolbarNew": "شريط الأدوات: إظهار زر الترجمة الجديدة", - "showWaveformToolbarSetStart": "شريط الأدوات: إظهار زر تعيين البداية", - "showWaveformToolbarSetEnd": "شريط الأدوات: إظهار زر تعيين النهاية", - "showWaveformToolbarStartAndOffsetTheRest": "شريط الأدوات: إظهار زر تعيين البداية وإزاحة الباقي", - "showWaveformToolbarHorizontalZoom": "شريط الأدوات: إظهار شريط تمرير التكبير الأفقي", - "showWaveformToolbarVerticalZoom": "شريط الأدوات: إظهار شريط تمرير التكبير الرأسي", - "showWaveformToolbarVideoPositionSlider": "شريط الأدوات: إظهار شريط تمرير موضع الفيديو", - "showWaveformToolbarPlaybackSpeed": "شريط الأدوات: إظهار سرعة التشغيل", "waveformFocusTextboxAfterInsertNew": "تركيز مربع النص بعد الإدراج", "waveformInvertMouseWheel": "عكس عجلة الفأرة", "waveformMouseWheelSetsVideoPosition": "عجلة الفأرة تحدد موضع الفيديو", @@ -3180,20 +3525,20 @@ "fixCommonErrorsSkipStep1": "إصلاح الأخطاء الشائعة: تخطي الخطوة 1 (اختيار الإصلاحات)", "showPluginsMenu": "إظهار قائمة الإضافات", "proxyAddress": "عنوان الوكيل", - "username": "اسم المستخدم", "password": "كلمة المرور", "defaultFormat": "الصيغة الافتراضية", "defaultSaveAsFormat": "صيغة \"حفظ باسم\" الافتراضية", "favoriteSubtitleFormats": "صيغ الترجمة المفضلة", "favoriteLanguages": "اللغات المفضلة", "webVttUseXTimestampMap": "WebVTT: استخدام X-TIMESTAMP-MAP (إزاحة الرموز الزمنية عند التحميل)", - "showStopButton": "إظهار زر الإيقاف", - "showFullscreenButton": "إظهار زر ملء الشاشة", "fullscreenHideControls": "إخفاء عناصر تحكم الفيديو في وضع ملء الشاشة", + "showSecondarySubtitleDialog": "إظهار مربع حوار النمط عند فتح ملف ترجمة ثانٍ", + "rememberSecondarySubtitleFile": "تذكر ملف الترجمة الثاني", "autoOpenVideoFile": "فتح ملف الفيديو تلقائيًا عند فتح الترجمة", "downloadMpv": "تنزيل mpv", "downloadVlc": "تنزيل VLC", "allowSingleLetterShortcutsInTextbox": "السماح باختصارات الحرف الواحد في مربع النص", + "allowTextNavigationShortcutsInTextbox": "السماح بالاختصارات على مفاتيح التنقل في النص (Ctrl+Left/Right، Home/End) في مربع النص", "spellCheckEnglishTreatInApostropheAsIng": "التدقيق الإملائي: معاملة الكلمات المنتهية بـ 'in'' كأنها 'ing' (الإنجليزية فقط)", "goToLineNumberSetsVideoPosition": "الانتقال إلى رقم السطر يحدد أيضًا موضع الفيديو", "adjustAllTimesRememberLineSelectionChoice": "ضبط جميع الأوقات، تذكر خيار تحديد الأسطر", @@ -3205,6 +3550,7 @@ "showAssaLayer": "إظهار مربع طبقة ASSA", "waveformCursorColor": "لون مؤشر/رأس الموجة الصوتية", "waveformShotChangeColor": "لون تغيير اللقطة في الموجة الصوتية", + "waveformGridColor": "لون شبكة الموجة الصوتية", "waveformParagraphLeftColor": "لون الحد الأيسر للموجة الصوتية", "waveformParagraphRightColor": "لون الحد الأيمن للموجة الصوتية", "waveformFancyHighColor": "اللون العالي المزخرف للموجة الصوتية", @@ -3232,27 +3578,31 @@ "resetSyntaxColoring": "إعادة تعيين تلوين الصياغة", "resetWaveform": "إعادة تعيين الموجة الصوتية", "resetRules": "إعادة تعيين القواعد", - "useFrameMode": "استخدام وضع الإطارات (hh.mm.ss.ff)", + "timeCodeMode": "وضع الرمز الزمني", + "timeCodeModeTime": "الوقت (hh:mm:ss,zzz)", + "timeCodeModeFrames": "الإطارات (hh:mm:ss:ff)", + "timeCodeModeFrameNumbers": "أرقام الإطارات", + "frameModeSnapToVideoFrames": "تخزين الأوقات على إطارات الفيديو (وليس الإطارات داخل كل ثانية)", "textBoxLimitNewLines": "تحديد عدد الأسطر في مربع نص الترجمة", - "mpvVideoOutput": "خرج الفيديو (mpv)", "mpvOpenGl": "libmpv - OpenGL", "mpvSoftwareRendering": "libmpv - العرض البرمجي (بطيء)", + "mpvMetalRendering": "libmpv - Metal (تجريبي، يتطلب إعادة التشغيل)", "mpvWidRendering": "libmpv - عرض بمعرّف النافذة الأصلي", "waveFormsAndSpectrogramFoldersContainsX": "مجلدا \"Waveforms\" و\"spectrogram\" يحتويان على {0}", "deleteWaveformAndSpectrogramFoldersQuestion": "حذف ملفات \"Waveforms\" و\"Spectrogram\"؟", "waveformGenerateSpectrogram": "توليد الطيف الترددي", "waveformAutoGenerate": "توليد الموجة الصوتية تلقائيًا عند فتح فيديو", "waveformSpectrogramMode": "وضع الطيف الترددي", - "waveformCenterOnSingleClick": "التوسيط عند النقر المفرد", - "waveformSingleClickSelectsSubtitle": "تحديد الترجمة عند النقر المفرد", "waveformRightClickSelectsSubtitle": "تحديد الترجمة عند النقر بالزر الأيمن", - "waveformPauseOnSingleClick": "الإيقاف المؤقت عند النقر المفرد", "waveformDrawStyle": "نمط رسم الموجة الصوتية", "waveformExtractAudioFormat": "صيغة استخراج الصوت", "waveformExtractAudioSampleRate": "معدل عينات استخراج الصوت", "waveformExtractAudioSampleRateOriginal": "الأصلي (الاحتفاظ بالمصدر)", "waveformExtractAudioBitRate": "معدل بت استخراج الصوت (MP3/M4A)", + "ffmpegUseCenterChannelOnly": "استخدام القناة الأمامية الوسطى فقط لصوت 5.1 (FFmpeg)", "vlcWidRendering": "libVLC - عرض بمعرّف النافذة الأصلي", + "ffmpegSoftwareRendering": "FFmpeg", + "downloadFfmpegLibs": "تنزيل مكتبات FFmpeg (لمشغل الفيديو FFmpeg)", "subtitleGridEnterKeyAction": "إجراء مفتاح Enter في شبكة الترجمة", "subtitleSingleClickAction": "إجراء النقر المفرد في شبكة الترجمة", "subtitleDoubleClickAction": "إجراء النقر المزدوج في شبكة الترجمة", @@ -3270,6 +3620,7 @@ "subtitleGridFormattingShowFormatting": "إظهار التنسيق", "subtitleGridFormattingShowTags": "إظهار الوسوم", "subtitleGridFormattingHideTags": "إخفاء الوسوم", + "subtitleGridFormattingShowFormattingKeepTags": "إظهار التنسيق، مع الإبقاء على الوسوم غير المرئية", "waveformParagraphBackgroundColor": "لون خلفية الترجمة في الموجة الصوتية", "waveformParagraphSelectedBackgroundColor": "لون خلفية الترجمة المحددة في الموجة الصوتية", "waveformAllowOverlap": "السماح بالتداخل (عند النقل/تغيير الحجم)", @@ -3279,17 +3630,18 @@ "saveAsBehaviorUseVideoFileName": "استخدام اسم ملف الفيديو", "saveAsBehaviorUseSubtitleFileName": "استخدام اسم ملف الترجمة", "saveAsAppendLanguageCodeTwoLetter": "حرفان", - "saveAsAppendLanguageCodeThreeLetter": "ثلاثة أحرف (ISO 639-2/T)", - "saveAsAppendLanguageCodeThreeLetterBibliographic": "ثلاثة أحرف (ISO 639-2/B)", "saveAsAppendLanguageCodeLanguageName": "اسم اللغة", "splitOddLineActionWeightTop": "ترجيح الأعلى", "splitOddLineActionWeightBottom": "ترجيح الأسفل", "splitOddLinesAction": "إجراء تقسيم الأسطر الفردية", "ocrUseWordSplitList": "OCR: استخدام قائمة تقسيم الكلمات", "ocrGuessUnknownWords": "OCR: محاولة تخمين الكلمات غير المعروفة", + "spellCheckRememberUseAlwaysList": "التدقيق الإملائي: تذكر قائمة \"الاستخدام دائمًا\"", + "fixShortDisplayTimesAllowMoveStartTime": "إصلاح الأخطاء الشائعة: قد يؤدي إصلاح أوقات العرض القصيرة إلى نقل وقت البداية", "speechToTextSelectedLinesPromptFirstTimeOnly": "تحويل الكلام إلى نص: الأسطر المحددة، المطالبة باللغة/المحرك في المرة الأولى فقط", "multipleReplaceShowDotDotDotButtons": "الاستبدال المتعدد: إظهار أزرار قائمة السياق", "gridFocusTextboxAfterInsertNew": "الشبكة: تركيز مربع النص بعد إدراج ترجمة جديدة", + "undoRedoGoToChangedLine": "تراجع/إعادة: الانتقال إلى السطر الذي تغيّر (وموضع الفيديو)", "musicSymbol": "رمز الموسيقى", "musicSymbolsToReplace": "رموز الموسيقى المراد استبدالها (مفصولة بفاصلة)", "textToSpeechPromptMergeContinuationLines": "تحويل النص إلى كلام: المطالبة بدمج أسطر الاستمرار", @@ -3298,8 +3650,7 @@ "useFocusedButtonBackgroundColor": "استخدام لون خلفية الزر المركّز عليه", "focusedButtonBackgroundColor": "لون خلفية الزر المركّز عليه", "forceCrLfOnSave": "فرض CR+LF عند الحفظ (ملفات الترجمة النصية)", - "showWaveformToolbarPlayNext": "شريط الأدوات: إظهار زر تشغيل التالي", - "showWaveformToolbarPlaySelection": "شريط الأدوات: إظهار زر تشغيل المُحدد", + "linuxClipboardUseExternalTool": "نسخ النص عبر xclip/wl-copy (يحافظ على الأحرف المشكّلة)", "showFormatLimitWarning": "التحذير عند الحفظ إذا تجاوزت الأسطر حدود التنسيق (مثل 32 حرفاً/سطر في SCC)", "textBoxButtonShowAutoBreak": "مربع النص: إظهار زر الفصل التلقائي", "textBoxButtonShowUnbreak": "مربع النص: إظهار زر إلغاء الفصل", @@ -3311,12 +3662,18 @@ "waveformDoubleClickAction": "إجراء النقر المزدوج في الموجة الصوتية (بعد إجراء النقر المفرد)", "allSettings": "جميع الإعدادات", "uiScale": "مقياس الواجهة (%)", + "fontScale": "مقياس الخط (%)", + "textSelectionOpacity": "شفافية تحديد النص (%)", "waveformToolbarItems": "عناصر شريط أدوات الموجة الصوتية", "matchIconColorToDarkTheme": "مطابقة لون الأيقونة مع لون مقدمة السمة الداكنة", "subtitlePreviewProperties": "خصائص معاينة الترجمة", "usePositionFromSubtitleFile": "استخدام الموضع من ملف الترجمة (TTML/PAC/EBU STL)", "marginIsPartOfSubtitleArea": "الهامش جزء من منطقة الترجمة", "textJustify": "ضبط الأسطر", + "deinterlace": "إزالة التشابك من الفيديو", + "deinterlaceOff": "إيقاف", + "deinterlaceAuto": "تلقائي (الإطارات المتشابكة فقط)", + "deinterlaceAlways": "دائمًا", "pixelWidthInfo": "الخطوط الخضراء = حد العرض الأقصى | المنطقة الحمراء = النص يتجاوز الحد", "spellCheckEngineHunSpelll": "Hunspell", "spellCheckEngineMsWord": "MS Word", @@ -3325,117 +3682,89 @@ "waveformColorTheme": "سمة الألوان:", "waveformSaveAsCustomTheme": "حفظ كنمط مخصص...", "waveformLoadThemeDotDotDot": "تحميل نمط...", - "waveformExportThemeDotDotDot": "تصدير النمط..." + "waveformExportThemeDotDotDot": "تصدير النمط...", + "waveformSaveCustomThemeTitle": "حفظ سمة مخصصة", + "waveformThemeFileType": "سمة الموجة الصوتية لـ Subtitle Edit", + "waveformThemeHighContrast": "تباين عالٍ", + "waveformThemeOceanBlue": "أزرق المحيط", + "waveformThemeWarmSunset": "غروب دافئ", + "waveformThemeForest": "الغابة", + "waveformThemeMidnightPurple": "أرجواني منتصف الليل", + "waveformThemeRetroAmber": "كهرماني قديم", + "waveformThemeIce": "الجليد" }, "shortcuts": { - "title": "الاختصارات", "searchShortcuts": "البحث في الاختصارات...", - "filter": "تصفية", - "categoryGeneral": "عام", "categorySubtitleGridAndTextBox": "عرض قائمة الترجمة ومربع النص", "categorySubtitleGrid": "عرض قائمة الترجمة", - "categoryWaveform": "الموجة الصوتية", "categoryTextBox": "مربع النص", "categoryFile": "ملف", "categoryVideo": "فيديو", "categorySync": "المزامنة", - "categoryTranslate": "ترجمة", - "categorySearch": "بحث", - "categoryTools": "الأدوات", "categoryAi": "الذكاء الاصطناعي", + "categoryCustom": "مخصص", + "editCustomShortcut": "تحرير الاختصار المخصص", + "customShortcutTextBoxKeyHint": "استخدم مفتاحًا مع Ctrl/Alt هنا - فالمفتاح العادي لن يكتب بعد ذلك في مربع النص.", + "customShortcutNumberX": "اختصار مخصص #{0}", + "customShortcutNumberXY": "اختصار مخصص #{0}: {1}", + "customShortcutStep": "الخطوة", + "customShortcutSteps": "الخطوات", + "customShortcutStepRunCommand": "تشغيل أمر", + "customShortcutStepInsertText": "إدراج نص", + "customShortcutStepReplace": "بحث واستبدال", + "customShortcutInsertAtCursor": "عند المؤشر", + "customShortcutInsertAtStart": "في بداية النص", + "customShortcutInsertAtEnd": "في نهاية النص", + "customShortcutSummaryRunCommandX": "تشغيل: {0}", + "customShortcutSummaryInsertXY": "إدراج \"{0}\" ({1})", + "customShortcutSummaryReplaceXY": "استبدال \"{0}\" بـ \"{1}\"", + "customShortcutSummaryReplaceRegexXY": "استبدال التعبير النمطي \"{0}\" بـ \"{1}\"", + "customShortcutPickCommand": "يرجى تحديد أمر لتشغيله.", + "customShortcutEnterText": "يرجى إدخال النص المراد إدراجه.", + "customShortcutEnterFind": "يرجى إدخال النص المراد البحث عنه.", + "customShortcutInvalidRegexX": "تعبير نمطي غير صالح: {0}", + "customShortcutStepsHint": "تُنفذ الخطوات من الأعلى إلى الأسفل. تعمل خطوات النص على الأسطر المحددة؛ والأوامر التي تفتح نافذة تستمر بعد إغلاقها.", "activeIn": "نشط في", "activeInEverywhere": "في كل مكان", - "generalSetupLikeSe4": "الإعداد مثل Subtitle Edit 4 (النمط، الاختصارات، قواعد الاستبدال)", - "generalMergeSelectedLines": "دمج الأسطر المحددة", - "generalMergeWithPrevious": "دمج مع السابق", - "generalMergeWithNext": "دمج مع التالي", - "generalMergeWithPreviousAndUnbreak": "دمج مع السابق وإلغاء الفصل", - "generalMergeWithNextAndUnbreak": "دمج مع التالي وإلغاء الفصل", - "generalMergeWithPreviousAndAutoBreak": "دمج مع السابق والفصل التلقائي", - "generalMergeWithNextAndAutoBreak": "دمج مع التالي والفصل التلقائي", - "generalMergeSelectedLinesAndAutoBreak": "دمج الأسطر المحددة والفصل التلقائي", "generalMergeSelectedLinesAndUnbreak": "دمج الأسطر المحددة وإلغاء الفصل", "generalMergeSelectedLinesAndUnbreakCjk": "دمج الأسطر المحددة وإلغاء الفصل بدون مسافة (CJK)", "generalUnbreakNoSpaceCjk": "إلغاء الفصل بدون مسافة (CJK)", - "generalMergeSelectedLinesOnlyFirstText": "دمج الأسطر المحددة مع النص الأول فقط", + "generalToggleBreak": "تبديل الفاصل/إلغاء الفاصل", "generalMergeSelectedLinesBilingual": "دمج الأسطر المحددة ثنائي اللغة", - "generalMergeWithPreviousBilingual": "دمج مع السابق ثنائي اللغة", - "generalMergeWithNextBilingual": "دمج مع التالي ثنائي اللغة", - "generalMergeOriginalAndTranslation": "دمج الأصلي والترجمة", "generalToggleTranslationMode": "تبديل وضع الترجمة", - "generalSwitchOriginalAndTranslation": "تبديل الأصلي والترجمة", - "generalSwitchOriginalAndTranslationTextBoxes": "تبديل مربعي نص الأصلي والترجمة", "generalToggleTranslationAndOriginalInPreviews": "تبديل الترجمة والأصل في معاينة الفيديو/الصوت", "generalChooseLayout": "اختيار التخطيط", - "generalLayoutChooseX": "التخطيط {0}", - "generalPlayFirstSelected": "تشغيل أول محدد", - "generalGoToFirstSelectedLine": "الانتقال إلى أول سطر محدد", - "generalGoToNextEmptyLine": "الانتقال إلى السطر الفارغ التالي", "generalGoToNextSubtitle": "الانتقال إلى الترجمة التالية", "generalGoToNextSubtitlePlayTranslate": "الانتقال إلى الترجمة التالية (تشغيل الترجمة)", "generalGoToNextSubtitleCursorAtEnd": "الانتقال إلى الترجمة التالية (المؤشر في النهاية)", "generalGoToPrevSubtitle": "الانتقال إلى الترجمة السابقة", "generalGoToPrevSubtitlePlayTranslate": "الانتقال إلى الترجمة السابقة (تشغيل الترجمة)", - "generalGoToStartOfCurrentSubtitle": "الانتقال إلى بداية الترجمة الحالية", - "generalGoToEndOfCurrentSubtitle": "الانتقال إلى نهاية الترجمة الحالية", - "generalGoToPreviousSubtitleAndFocusVideo": "الانتقال إلى الترجمة السابقة والتركيز على الفيديو", - "generalGoToNextSubtitleAndFocusVideo": "الانتقال إلى الترجمة التالية والتركيز على الفيديو", - "generalGoToPrevSubtitleAndPlay": "الانتقال إلى الترجمة السابقة والتشغيل", - "generalGoToNextSubtitleAndPlay": "الانتقال إلى الترجمة التالية والتشغيل", - "generalGoToPreviousSubtitleAndFocusWaveform": "الانتقال إلى الترجمة السابقة والتركيز على الموجة الصوتية", - "generalGoToNextSubtitleAndFocusWaveform": "الانتقال إلى الترجمة التالية والتركيز على الموجة الصوتية", - "generalGoToLineNumber": "الانتقال إلى رقم السطر", "generalGoToFirstLine": "الانتقال إلى السطر الأول", "generalGoToLastLine": "الانتقال إلى السطر الأخير", "alsoSetVideoPosition": "تعيين موضع الفيديو أيضاً", "generalGoToVideoPosition": "الانتقال إلى موضع الفيديو", "generalToggleItalic": "تبديل المائل", "generalToggleBold": "تبديل العريض", - "generalToggleBookmarks": "تبديل العلامات المرجعية", - "generalFocusTextBox": "التركيز على مربع النص", - "generalToggleBookmarksWithText": "تبديل العلامات المرجعية مع النص", - "generalEditBookmarks": "تحرير العلامات المرجعية", - "fileNew": "جديد", "generalToggleUnderline": "تبديل التسطير", + "generalToggleBox": "تبديل المربع (EBU STL)", "fileOpen": "فتح", "fileOpenKeepVideo": "فتح (مع الاحتفاظ بالفيديو)", "fileSave": "حفظ", "fileSaveAs": "حفظ باسم", - "fileSaveAll": "حفظ الكل", "fileSaveOriginal": "حفظ الأصلي", - "fileSaveOriginalAs": "حفظ الأصلي باسم", - "fileOpenOriginalSubtitle": "فتح الترجمة الأصلية", - "fileCloseOriginalSubtitle": "إغلاق الترجمة الأصلية", - "fileTranslatedSubtitle": "الترجمة المُترجمة", "fileCompare": "مقارنة", "fileStatistics": "إحصائيات", "fileImportPlainText": "استيراد نص عادي", - "fileImportBluRaySupForOcr": "استيراد Blu-ray SUP لأجل OCR", - "fileImportBluRaySupForEdit": "استيراد Blu-ray SUP للتحرير", - "fileImportTimeCodes": "استيراد الرموز الزمنية", "fileExportEbuStl": "تصدير EBU STL", "fileExportPac": "تصدير PAC", - "fileExportEdlClipName": "تصدير اسم مقطع EDL", - "fileExportPlainText": "تصدير نص عادي", - "fileExportCustomTextFormat1": "تصدير صيغة نص مخصصة 1", - "fileExportCustomTextFormat2": "تصدير صيغة نص مخصصة 2", - "fileExportCustomTextFormat3": "تصدير صيغة نص مخصصة 3", "fileExit": "خروج", "openSeDataFolder": "فتح مجلد Subtitle Edit", - "editFind": "بحث", - "editFindNext": "بحث عن التالي", "editFindPrevious": "بحث عن السابق", - "editReplace": "استبدال", - "editMultipleReplace": "استبدال متعدد", "fillSelectedLinesWithClipboard": "ملء الأسطر المحددة بنص الحافظة", - "editModifySelection": "تعديل التحديد", - "listSelectAll": "تحديد الكل", - "listSelectFirst": "تحديد الأول", - "listSelectLast": "تحديد الأخير", "listInverseSelection": "عكس التحديد", "listDeleteSelection": "حذف التحديد", + "deleteSelectionEverywhere": "حذف التحديد (في كل مكان)", "rippleDeleteSelection": "حذف متتابع للتحديد", - "settings": "الإعدادات", "assigned": "معيّن", "unassigned": "غير معيّن", "pressedKeyX": "المفتاح المضغوط: {0}", @@ -3443,6 +3772,7 @@ "detectKey": "اكتشاف المفتاح", "control": "Control", "alt": "Alt", + "altGr": "AltGr", "win": "Win", "shift": "Shift", "controlMac": "⌃", @@ -3453,11 +3783,14 @@ "resetShortcutsDetail": "هل تريد إعادة تعيين جميع الاختصارات إلى القيم الافتراضية؟", "togglePlayPause": "تبديل التشغيل/الإيقاف المؤقت", "toggleLockTimeCodes": "تبديل قفل الرموز الزمنية", + "toggleTimeCodeMode": "تبديل وضع الرمز الزمني (الوقت/الإطارات/أرقام الإطارات)", + "toggleScreenPrivacy": "تبديل خصوصية الشاشة (إخفاء أسماء الملفات، ثم النصوص أيضًا)", + "screenPrivacyFileNamesHidden": "خصوصية الشاشة: أسماء الملفات مخفية", + "screenPrivacyFileNamesAndTextsHidden": "خصوصية الشاشة: أسماء الملفات والنصوص مخفية", + "screenPrivacyOff": "خصوصية الشاشة: متوقفة", "duplicateSelectedLines": "تكرار الأسطر المحددة", "sourceView": "عرض المصدر", - "showAlignmentPicker": "المحاذاة", "addOrEditBookmark": "إضافة أو تحرير علامة مرجعية", - "listBookmarks": "سرد العلامات المرجعية", "toggleBookmark": "تبديل العلامة المرجعية (الأسطر المحددة، بدون نص)", "toggleForcedSelectedLines": "تبديل مُجبَر (الأسطر المحددة)", "goToNextBookmark": "الانتقال إلى العلامة المرجعية التالية", @@ -3468,12 +3801,12 @@ "waveformSetStartAndSetEndOfPreviousMinusGap": "تعيين البداية وتعيين نهاية السابق ناقص الفجوة", "waveformSetEndAndStartOfNextAfterGap": "تعيين النهاية وبداية التالي زائد الفجوة", "waveformSetEndAndStartOfNextAfterGapAndGoToNext": "تعيين النهاية وبداية التالي زائد الفجوة والانتقال إلى التالي", - "fetchFirstWordFromNextSubtitle": "جلب الكلمة الأولى من الترجمة التالية", "moveLastWordToNextSubtitle": "نقل الكلمة الأخيرة إلى الترجمة التالية", "moveLastWordFromFirstLineDownCurrentSubtitle": "نقل الكلمة الأخيرة من السطر الأول للأسفل (الترجمة الحالية)", "moveFirstWordFromNextLineUpCurrentSubtitle": "نقل الكلمة الأولى من السطر التالي للأعلى (الترجمة الحالية)", "moveTextFromCursorToNextAndGoToNext": "نقل النص بعد موضع المؤشر إلى الترجمة التالية والانتقال إلى التالي", "moveTextFromCursorToNextAndGoToNextAndPlay": "نقل النص بعد موضع المؤشر إلى الترجمة التالية، الانتقال إلى التالي والتشغيل", + "moveTextFromCursorToNextAndGoToNextAndPlayAndPause": "نقل النص بعد موضع المؤشر إلى الترجمة التالية، والانتقال إليها، وتشغيلها والإيقاف المؤقت عند نهايتها", "breakAtFirstSpaceFromCursor": "الكسر عند أول مسافة من موضع المؤشر", "breakAtFirstSpaceFromCursorAndGoToNext": "الكسر عند أول مسافة من موضع المؤشر والانتقال إلى التالي", "toggleFocusGridAndWaveform": "تبديل التركيز بين شبكة الترجمة والموجة الصوتية/الطيف الترددي", @@ -3485,7 +3818,6 @@ "videoGoToPreviousTimeCode": "الانتقال إلى الرمز الزمني السابق", "videoGoToNextTimeCode": "الانتقال إلى الرمز الزمني التالي", "goToNextLineAndSetVideoPosition": "الانتقال إلى الترجمة التالية وتعيين موضع الفيديو", - "textBoxDeleteSelectionNoClipboard": "مربع النص: حذف التحديد (بدون الحافظة)", "textBoxDeleteForward": "مربع النص: حذف التحديد أو الحرف التالي (حذف للأمام)", "textBoxCut": "مربع النص: قص", "textBoxCut2": "مربع النص: قص (بديل)", @@ -3504,6 +3836,7 @@ "listViewColumnTextUp": "العمود، النص للأعلى", "listViewColumnTextDown": "العمود، النص للأسفل", "autoTranslateSelectedLines": "ترجمة الأسطر المحددة تلقائياً...", + "autoTranslateSelectedLinesNoPrompt": "الترجمة التلقائية للأسطر المحددة (دون سؤال، باستخدام آخر محرك/لغات)", "setAssaResolution": "تعيين دقة ASSA (PlayResX/PlayResY)", "setShortcutForX": "تعيين اختصار لـ \"{0}\"", "commandFileNewKeepVideo": "جديد (مع الاحتفاظ بالفيديو)", @@ -3511,29 +3844,22 @@ "fileCloseOriginal": "إغلاق الأصلي", "fileCloseTranslation": "إغلاق الترجمة", "restoreAutoBackup": "استعادة النسخة الاحتياطية التلقائية", - "openContainingFolder": "فتح المجلد المحتوي", "importTimeCodes": "استيراد الرموز الزمنية", "importSubtitleWithManuallyChosenEncoding": "استيراد الترجمة بترميز مختار يدوياً", "exportBluRaySup": "تصدير Blu-ray SUP", "exportCustomTextFormat": "تصدير صيغة نص مخصصة", "exportPlainText": "تصدير نص عادي", - "showHistory": "إظهار السجل", "toggleRightToLeft": "تبديل من اليمين إلى اليسار", "modifySelection": "تعديل التحديد", "adjustDurations": "ضبط المدد", "applyDurationLimits": "تطبيق حدود المدة", "batchConvert": "تحويل دفعي", - "bridgeGaps": "سد الفجوات", "applyMinGap": "تطبيق الحد الأدنى للفجوة", - "fixCommonErrors": "إصلاح الأخطاء الشائعة", "makeEmptyTranslationFromCurrentSubtitle": "إنشاء ترجمة فارغة من الترجمة الحالية", - "mergeLinesWithSameText": "دمج الأسطر ذات النص نفسه", - "mergeLinesWithSameTimeCodes": "دمج الأسطر ذات الرموز الزمنية نفسها", "splitBreakLongLines": "تقسيم/إعادة توازن الأسطر الطويلة", "mergeShortLines": "دمج الأسطر القصيرة", "mergeContinuationLines": "دمج أسطر التكملة", "snapAllTimesToFrames": "محاذاة جميع الأوقات إلى الإطارات", - "removeTextForHearingImpaired": "إزالة الترميزات المتعلقة بضعاف السمع", "joinSubtitles": "ضم الترجمات", "splitSubtitle": "تقسيم الترجمة", "spellCheck": "التدقيق الإملائي", @@ -3555,7 +3881,6 @@ "adjustAllTimes": "ضبط جميع الأوقات", "visualSync": "المزامنة المرئية", "translateViaCopyPaste": "الترجمة عبر النسخ واللصق", - "shortcuts": "الاختصارات", "wordLists": "قوائم الكلمات", "chooseUiLanguage": "اختيار لغة الواجهة", "chooseRuleProfile": "اختيار ملف القواعد", @@ -3566,6 +3891,7 @@ "textBoxItalic": "مائل مربع النص", "textBoxBold": "مربع النص: عريض", "textBoxUnderline": "مربع النص: تسطير", + "textBoxBox": "مربع في مربع النص (EBU STL)", "resetWaveformZoomAndSpeed": "إعادة تعيين تكبير الموجة الصوتية وسرعة التشغيل (معدل التشغيل)", "togglePlaybackSpeed": "تبديل سرعة التشغيل (معدل التشغيل)", "playbackSpeedSlower": "إبطاء سرعة التشغيل (معدل التشغيل)", @@ -3577,22 +3903,28 @@ "seekSilenceForward": "البحث عن الصمت للأمام", "waveformGuessStart": "تخمين وقت البداية من المخطط الموجي", "waveformGuessEnd": "تخمين وقت النهاية من المخطط الموجي", + "waveformGuessStartAndEnd": "تخمين وقت البداية والنهاية من الموجة الصوتية", "setVideoPositionCurrentSubtitleStart": "تعيين موضع الفيديو إلى بداية السطر الحالي", "goToSubtitlePositionAndPause": "الانتقال إلى موضع الترجمة والإيقاف المؤقت", "setVideoPositionCurrentSubtitleEnd": "تعيين موضع الفيديو إلى نهاية السطر الحالي", "toggleAudioTracks": "تبديل المسارات الصوتية", - "listErrors": "سرد الأخطاء", "goToNextError": "الانتقال إلى الخطأ التالي", "goToPreviousError": "الانتقال إلى الخطأ السابق", "addNameToNameList": "إضافة اسم إلى قائمة الأسماء", "findDoubleWords": "البحث عن الكلمات المكررة", "findDoubleLines": "البحث عن الأسطر المكررة", "colorX": "اللون {0}", - "removeColor": "إزالة اللون", "surroundWith": "إحاطة بـ...", "surroundWithXY": "إحاطة بـ {0}/{1}", - "repeatLine": "تكرار السطر", "surroundWithNumberX": "الإحاطة بـ #{0}", + "surroundWithBehavior": "السلوك", + "surroundWithBehaviorToggle": "تبديل (إضافة، أو إزالة إن وُجد)", + "surroundWithBehaviorAdd": "إضافة (في كل مرة)", + "surroundWithBehaviorRemove": "إزالة (الكل)", + "surroundWithBehaviorRemoveOnce": "إزالة (واحد في كل مرة)", + "surroundWithWorksOn": "يعمل على", + "surroundWithScopeSelectionOrText": "التحديد، وإلا النص بالكامل", + "surroundWithScopeEachLine": "كل سطر", "searchVia": "البحث عبر", "searchViaX": "البحث عبر {0}", "searchViaNumberX": "البحث عبر #{0}", @@ -3620,6 +3952,11 @@ "copySubtitlePathToClipboard": "نسخ مسار الترجمة إلى الحافظة", "copySubtitleOriginalPathToClipboard": "نسخ مسار ترجمة الأصلي إلى الحافظة", "focusTextBox": "التركيز على مربع النص", + "focusSubtitleListView": "التركيز على عرض قائمة الترجمة", + "focusWaveform": "التركيز على الموجة الصوتية", + "focusOriginalTextBox": "التركيز على مربع النص الأصلي", + "textBoxGoToStart": "مربع النص، الانتقال إلى البداية", + "textBoxGoToEnd": "مربع النص، الانتقال إلى النهاية", "sortByNumber": "الترتيب حسب الرقم", "sortByStartTime": "الترتيب حسب وقت \"الظهور\"", "sortByEndTime": "الترتيب حسب وقت \"الإخفاء\"", @@ -3644,7 +3981,6 @@ "assaStyles": "أنماط ASSA", "assaProperties": "خصائص ASSA", "assaAttachments": "مرفقات ASSA", - "assaVideoColorPicker": "منتقي ألوان فيديو ASSA", "recalculateDurationSelectedLines": "إعادة حساب المدة (الأسطر المحددة)", "setDurationMaxCpsSelectedLines": "تعيين المدة إلى أقصى حرف/ثانية (الأسطر المحددة)", "toggleWaveformAndSpectrogramHeight": "تبديل الارتفاع المقسّم للموجة الصوتية/الطيف الترددي", @@ -3658,9 +3994,9 @@ "selectionToSentenceCase": "مربع النص: تحويل التحديد إلى حالة الجملة", "selectionToggleCasing": "مربع النص: تبديل حالة الأحرف (كبيرة -> صغيرة -> حالة العنوان)", "googleIt": "البحث في Google (النص المحدد)", - "setActorXY": "تعيين الممثل {0}: {1}", + "setActorX": "تعيين الممثل: {0}", "setNewActor": "تعيين ممثل جديد...", - "setActorSlotXTitle": "خانة الممثل {0}", + "setStyleX": "تعيين النمط: {0}", "toggleDialogDashes": "تبديل شرطات الحوار", "mergeWithLineBeforeAsDialog": "دمج مع السطر السابق كحوار", "mergeWithLineAfterAsDialog": "دمج مع السطر اللاحق كحوار", @@ -3671,13 +4007,27 @@ "moveStartOneFrameBackKeepGapPrev": "نقل البداية إطاراً واحداً للخلف (الحفاظ على الفجوة مع السابق إن كانت قريبة)", "moveStartOneFrameForwardKeepGapPrev": "نقل البداية إطاراً واحداً للأمام (الحفاظ على الفجوة مع السابق إن كانت قريبة)", "moveEndOneFrameBackKeepGapNext": "نقل النهاية إطاراً واحداً للخلف (الحفاظ على الفجوة مع التالي إن كانت قريبة)", - "moveEndOneFrameForwardKeepGapNext": "نقل النهاية إطاراً واحداً للأمام (الحفاظ على الفجوة مع التالي إن كانت قريبة)" + "moveEndOneFrameForwardKeepGapNext": "نقل النهاية إطاراً واحداً للأمام (الحفاظ على الفجوة مع التالي إن كانت قريبة)", + "moveStartXMsBack": "نقل البداية X مللي ثانية للخلف (تُضبط X في الإعدادات، وأيضًا في المزامنة المرئية)", + "moveStartXMsForward": "نقل البداية X مللي ثانية للأمام (تُضبط X في الإعدادات، وأيضًا في المزامنة المرئية)", + "moveEndXMsBack": "نقل النهاية X مللي ثانية للخلف (تُضبط X في الإعدادات، وأيضًا في المزامنة المرئية)", + "moveEndXMsForward": "نقل النهاية X مللي ثانية للأمام (تُضبط X في الإعدادات، وأيضًا في المزامنة المرئية)", + "moveSelectedLinesXMsBack": "نقل الأسطر المحددة X مللي ثانية للخلف (تُضبط X في الإعدادات)", + "moveSelectedLinesXMsForward": "نقل الأسطر المحددة X مللي ثانية للأمام (تُضبط X في الإعدادات)", + "moveSelectedLinesAndForwardXMsBack": "نقل الأسطر المحددة وكل ما يليها X مللي ثانية للخلف (تُضبط X في الإعدادات)", + "moveSelectedLinesAndForwardXMsForward": "نقل الأسطر المحددة وكل ما يليها X مللي ثانية للأمام (تُضبط X في الإعدادات)", + "moveAllLinesXMsBack": "نقل جميع الأسطر X مللي ثانية للخلف (تُضبط X في الإعدادات)", + "moveAllLinesXMsForward": "نقل جميع الأسطر X مللي ثانية للأمام (تُضبط X في الإعدادات)", + "moveSelectedLinesCustomMsBack": "نقل الأسطر المحددة، أجزاء من الثانية مخصصة ({0:#,###,##0}) للخلف، {1}", + "moveSelectedLinesCustomMsForward": "نقل الأسطر المحددة، أجزاء من الثانية مخصصة ({0:#,###,##0}) للأمام، {1}", + "moveSelectedLinesAndForwardCustomMsBack": "نقل الأسطر المحددة وكل ما يليها، أجزاء من الثانية مخصصة ({0:#,###,##0}) للخلف، {1}", + "moveSelectedLinesAndForwardCustomMsForward": "نقل الأسطر المحددة وكل ما يليها، أجزاء من الثانية مخصصة ({0:#,###,##0}) للأمام، {1}", + "moveAllLinesCustomMsBack": "نقل جميع الأسطر، أجزاء من الثانية مخصصة ({0:#,###,##0}) للخلف، {1}", + "moveAllLinesCustomMsForward": "نقل جميع الأسطر، أجزاء من الثانية مخصصة ({0:#,###,##0}) للأمام، {1}" }, "wordLists": { "title": "قوائم الكلمات", "addName": "إضافة اسم", - "addWord": "إضافة كلمة", - "addPair": "إضافة زوج", "nameAndIgnoreList": "قائمة الأسماء/التجاهل", "userWords": "قائمة كلمات المستخدم", "ocrFixList": "قائمة إصلاح OCR", @@ -3694,7 +4044,6 @@ "noPluginsInstalled": "لا توجد إضافات مثبَّتة", "getPluginsOnline": "الحصول على إضافات عبر الإنترنت...", "getPluginsTitle": "الحصول على الإضافات", - "enabled": "مفعّل", "disabled": "معطّل", "notSupportedOnThisOs": "غير مدعوم على نظام التشغيل هذا", "removePluginXConfirm": "إزالة الإضافة '{0}'؟ هذا يحذف مجلد الإضافة:{1}", @@ -3711,17 +4060,19 @@ "downloadingXPercent": "جارٍ التنزيل {0}%...", "pluginXInstalled": "تم تثبيت '{0}'.", "couldNotInstallPluginX": "تعذّر تثبيت '{0}': {1}", - "notInstalled": "غير مثبَّت", "install": "تثبيت", "reinstall": "إعادة التثبيت", - "update": "تحديث", "installedX": "مثبَّت ({0})", "updateAvailableXToY": "تحديث متاح ({0} → {1})", "applyPluginToWhichLinesX": "تطبيق '{0}' على:", "applyToSelectedLinesX": "الأسطر المحددة ({0})", "applyToAllLinesX": "كل الأسطر ({0})", "updateAllXAvailable": "تحديث الكل ({0})", - "updatingXOfY": "جارٍ التحديث {0}/{1}..." + "updatingXOfY": "جارٍ التحديث {0}/{1}...", + "doNotAskAgain": "عدم السؤال مجددًا (يمكن تغييره في إدارة الإضافات)", + "applyPluginsTo": "تطبيق الإضافات على", + "askEachTime": "السؤال في كل مرة", + "allLines": "جميع الأسطر" }, "help": { "aboutSubtitleEdit": "حول Subtitle Edit", @@ -3765,6 +4116,8 @@ "nOcrDatabase": "قاعدة بيانات nOCR", "drawMode": "وضع الرسم:", "addNewCharcter": "إضافة حرف جديد", + "addNOcrCharacterTitle": "إضافة حرف nOCR للسطر {0}، الحرف {1} من {2} باستخدام قاعدة البيانات \"{3}\"", + "addBinaryOcrCharacterTitle": "إضافة حرف OCR ثنائي للسطر {0}، الحرف {1} من {2} باستخدام قاعدة البيانات \"{3}\"", "lineIndexX": "السطر {0}", "inspectNOcrAdditions": "فحص إضافات nOCR الجديدة", "ocrSelectedLines": "إجراء OCR على الأسطر المحددة", @@ -3813,6 +4166,8 @@ "binaryImageCompareInspectImageMatches": "\"مقارنة الصور الثنائية\" - فحص تطابقات الصور", "saveBlankTextTitle": "حفظ نص فارغ؟", "saveBlankTextPrompt": "حفظ نص فارغ للصورة؟", + "drawnLinesDoNotMatchTitle": "الخطوط لا تطابق الصورة", + "drawnLinesDoNotMatchPrompt": "بعض الخطوط المرسومة لا تطابق الصورة (يجب أن تكون الخطوط الخضراء على الحرف، والحمراء خارجه)، لذا لن يتم التعرف على هذا الحرف.\n\nهل تريد الحفظ على أي حال؟", "yesAndNeverAskAgain": "نعم ولا تسأل مرة أخرى أبدًا", "importTextFromSubtitleDotDotDot": "استيراد نص من الترجمة...", "importTextFromSubtitleOverwritePrompt": "بعض الأسطر تحتوي بالفعل على نص OCR. هل تريد استبدال النص الحالي؟", @@ -3838,7 +4193,6 @@ "vobSubColorsTitle": "ألوان VobSub/DVD", "vobSubColorsHeader": "تخصيص ألوان VobSub الأربعة", "vobSubColorsDescription": "تستخدم ترجمات VobSub/DVD أربعة ألوان مفهرسة: الخلفية، والنقش، والتوكيد 1 والتوكيد 2. اختر كل لون أدناه لتجاوز اللوحة المدمجة، وهو مفيد عندما تعطي الألوان الأصلية تباين OCR ضعيفًا.", - "vobSubColorBackground": "الخلفية", "vobSubColorPattern": "النقش", "vobSubColorEmphasis1": "التوكيد 1", "vobSubColorEmphasis2": "التوكيد 2", @@ -3872,9 +4226,26 @@ "crispEmbedSettingsTitle": "إعدادات CrispEmbed", "crispEmbedDescription": "محرك تعرف ضوئي محلي بواجهات نماذج متعددة - نزّل المحرك والنماذج من هنا.", "trainNOcrDatabase": "تدريب قاعدة بيانات nOCR...", + "optimizeNOcrDatabase": "تحسين قاعدة بيانات nOCR...", + "optimizeNOcrDatabaseQuestionXY": "هل تريد إزالة الخطوط التي لا تميّز الأحرف عن الأحرف المشابهة في قاعدة بيانات nOCR \"{0}\"؟\n\nيجعل هذا قاعدة البيانات أصغر وOCR أسرع، وعادةً بأخطاء أقل. يستغرق حتى دقيقة.\n\nتُحفظ نسخة احتياطية باسم \"{1}\".", + "optimizingNOcrDatabaseXY": "جارٍ تحسين قاعدة بيانات nOCR... {0}/{1}", + "nOcrDatabaseOptimizedXYZ": "تم تحسين قاعدة بيانات nOCR \"{0}\": {1:#,##0} -> {2:#,##0} خط.", "startTraining": "بدء التدريب", "abortTraining": "إلغاء التدريب", "trainingOptions": "خيارات التدريب", + "trainNOcrSubtitle": "يرسم الأحرف بالخطوط المثبتة ويعلّم nOCR أشكالها. اختر الخطوط التي تستخدمها ترجماتك.", + "xFontsSelected": "{0:#,##0} محدد", + "subtitleFonts": "خطوط الترجمة", + "emptyDatabase": "قاعدة بيانات فارغة", + "startFromDatabase": "البدء من", + "startFromDatabaseHint": "إضافة الأحرف المدرَّبة إلى نسخة من قاعدة بيانات موجودة (موصى به) أو البدء من قاعدة فارغة", + "fontSizes": "أحجام الخط", + "fontSizesHint": "أحجام الخط بالبكسل للرسم بها، مفصولة بفواصل، مثل \"30, 40\"", + "numberOfLineSegmentsHint": "الخطوط المفحوصة لكل حرف - العدد الأكبر أكثر صرامة لكنه أبطأ", + "enterDatabaseName": "أدخل اسمًا لقاعدة البيانات", + "trainingFontXSizeYCharacterZ": "جارٍ تدريب \"{0}\" عند {1} px: {2}", + "xLearned": "{0:#,##0} تم تعلّمه", + "xSkipped": "{0:#,##0} تم تخطيه", "charactersToTrain": "الأحرف المراد تدريبها", "letterCombinationsToTrain": "تركيبات الأحرف التي قد تنقسم إلى صورة واحدة", "importCharactersFromSubtitleFile": "استيراد الأحرف من ملف ترجمة...", @@ -3901,6 +4272,8 @@ "drawZoomOut": "تصغير (Ctrl+-)", "drawResetView": "إعادة تعيين العرض (Ctrl+0)", "drawToggleGrid": "تبديل الشبكة (Ctrl+G)", + "drawTogglePreview": "تبديل المعاينة (F9)", + "drawPreviewFailed": "فشلت المعاينة - يلزم ffmpeg مع libass (مرشح subtitles)", "drawCopyToClipboard": "نسخ إلى الحافظة (Ctrl+C)", "drawShapes": "الأشكال", "drawSelectedPoint": "النقطة المحددة", @@ -3908,16 +4281,86 @@ "drawSelectedLayer": "الطبقة المحددة", "drawToolX": "الأداة: {0}", "drawHelpText": "انقر لإضافة النقاط • Enter/F8 لإغلاق الشكل • Shift+سحب للتحريك • Ctrl+تمرير للتكبير", + "drawMoveToLayer": "نقل إلى طبقة", + "drawFlipHorizontal": "قلب أفقي", + "drawFlipVertical": "قلب عمودي", + "drawHideShape": "إخفاء الشكل", + "drawShowShape": "إظهار الشكل", + "drawHideLayer": "إخفاء الطبقة", + "drawShowLayer": "إظهار الطبقة", + "drawDeleteLayer": "حذف الطبقة", + "drawDeletePoint": "حذف النقطة", + "drawCancelDrawing": "إلغاء الرسم", + "drawLayerX": "الطبقة {0}", + "drawImportSvg": "استيراد صورة SVG...", + "drawRotateClockwise": "تدوير 90° باتجاه عقارب الساعة", + "drawConvertToCurve": "تحويل المقطع إلى منحنى", + "drawBackground": "الخلفية", + "drawPosition": "الموضع", + "drawZoom": "تكبير", + "drawShapeTool": "أداة الأشكال", + "drawColorPickerTool": "اختيار لون من شكل أو من الخلفية", + "drawShapeLibrary": "مكتبة الأشكال", + "drawShapeLibraryHint": "اسحب على اللوحة لوضع شكل، أو انقر للحجم الافتراضي. Shift يحافظ على تناسبه.", + "drawAddToLibrary": "إضافة إلى مكتبة الأشكال...", + "drawRemoveFromLibrary": "إزالة من مكتبة الأشكال", + "drawShapeName": "اسم الشكل", + "drawCategorySpeechBubbles": "فقاعات الكلام", + "drawCategoryArrows": "الأسهم", + "drawCategoryBasicShapes": "الأشكال الأساسية", + "drawCategorySymbols": "الرموز", + "drawCategoryMyShapes": "أشكالي", + "drawShapeSpeechBubble": "فقاعة كلام", + "drawShapeSpeechBubbleRight": "فقاعة كلام، الذيل لليمين", + "drawShapeSpeechBubbleUp": "فقاعة كلام، الذيل للأعلى", + "drawShapeOvalBubble": "فقاعة كلام بيضاوية", + "drawShapeBoxBubble": "فقاعة كلام مربعة", + "drawShapeThoughtBubble": "فقاعة تفكير", + "drawShapeShoutBubble": "فقاعة صراخ", + "drawShapeCaptionBox": "مربع تعليق", + "drawShapeArrow": "سهم", + "drawShapeDoubleArrow": "سهم مزدوج", + "drawShapeChevron": "شيفرون", + "drawShapeCurvedArrow": "سهم منحنٍ", + "drawShapeRoundedRectangle": "مستطيل مستدير الزوايا", + "drawShapeTriangle": "مثلث", + "drawShapeDiamond": "معيّن", + "drawShapePentagon": "خماسي", + "drawShapeHexagon": "سداسي", + "drawShapeOctagon": "ثماني", + "drawShapeStar": "نجمة", + "drawShapeRing": "حلقة", + "drawShapePlus": "علامة زائد", + "drawShapeBanner": "لافتة", + "drawShapeHeart": "قلب", + "drawShapeCheck": "علامة صح", + "drawShapeMusicNote": "نوتة موسيقية", + "drawShapeMusicNotes": "نوتات موسيقية", + "drawShapeLightning": "برق", + "drawShapeCloud": "سحابة", + "drawShapeMoon": "قمر", + "drawShapeBadge": "شارة", + "drawSize": "الحجم", + "drawLayer": "الطبقة", + "drawBackgroundVideoFrame": "إطار الفيديو (الموضع الحالي)", + "drawBackgroundVideoFrameAt": "إطار الفيديو عند...", + "drawBackgroundImage": "ملف صورة...", + "drawBackgroundNone": "بدون خلفية", + "drawBackgroundStretch": "تمديد الصورة إلى اللوحة", + "drawBackgroundOpacity": "شفافية الخلفية", + "drawBackgroundVideoFrameFailed": "تعذّر الحصول على إطار فيديو عند {0}.", + "drawImages": "الصور", + "drawConvertToLine": "تحويل المقطع إلى خط", + "drawConvertShapeToCurves": "تحويل كل الخطوط إلى منحنيات", + "drawConvertShapeToLines": "تحويل كل المنحنيات إلى خطوط", + "drawRotateCounterClockwise": "تدوير 90° عكس اتجاه عقارب الساعة", + "drawSvgImages": "صور SVG", + "drawSvgNoShapes": "لم يتم العثور على أشكال معبأة أو حدود في ملف SVG.", "progressBarTitle": "شريط تقدم ASSA", "progressBarSettings": "شريط التقدم", - "progressBarPosition": "الموضع", - "progressBarBottom": "الأسفل", - "progressBarTop": "الأعلى", "mouseOverColor": "لون تمرير الماوس", "clickedColor": "لون النقر", "progressBarForeColor": "لون المقدمة", - "progressBarBackColor": "لون الخلفية", - "progressBarStyle": "النمط", "progressBarSquareCorners": "زوايا مربعة", "progressBarRoundedCorners": "زوايا دائرية", "progressBarChapters": "الفصول", @@ -3926,8 +4369,6 @@ "progressBarXAdjustment": "تعديل X", "progressBarYAdjustment": "تعديل Y", "progressBarTextAlignment": "محاذاة النص", - "progressBarTakePosFromVideo": "أخذ الموضع من الفيديو", - "progressBarPreview": "معاينة", "fontCollectorTitle": "مُجمِّع الخطوط", "fontCollectorUsedIn": "مستخدم في", "fontCollectorFontFiles": "ملفات الخطوط", @@ -3974,9 +4415,7 @@ "resolutionResamplerVideoDiffers": "دقة الترجمة ({0}x{1}) تختلف عن دقة الفيديو ({2}x{3}).\nهل تريد تغيير الترجمة لتطابق الفيديو؟", "resolutionResamplerAskOnVideoOpen": "السؤال عند فتح فيديو بدقة مختلفة", "backgroundBoxGenerator": "توليد مربع الخلفية", - "backgroundBoxPadding": "التباعد الداخلي", "backgroundBoxFillWidth": "ملء العرض", - "backgroundBoxBoxColor": "لون المربع", "backgroundBoxRadius": "نصف القطر", "backgroundBoxCircle": "دائرة", "backgroundBoxSpikes": "أشواك", @@ -3988,12 +4427,9 @@ "backgroundBoxTornPaperDouble": "ورق ممزق (أعلى وأسفل)", "backgroundBoxStarburst": "انفجار نجمي", "backgroundBoxScroll": "لفافة / رَقّ", - "stylesTitle": "أنماط Advanced Sub Station Alpha", "stylesInFile": "الأنماط في الملف", "stylesSaved": "الأنماط المحفوظة", - "category": "الفئة", "allCategories": "[الكل]", - "defaultCategory": "الافتراضية", "newCategory": "فئة جديدة...", "renameCategory": "إعادة تسمية الفئة...", "deleteCategory": "حذف الفئة", @@ -4016,13 +4452,26 @@ "graphics": "الرسومات", "copyToStorageStyles": "نسخ إلى أنماط التخزين", "copyToFileStyles": "نسخ إلى أنماط الملف", + "styleXAlreadyExistsInStorage": "يوجد نمط باسم \"{0}\" بالفعل في التخزين.\n\nهل تريد استبداله، أم الإبقاء على كليهما (تحصل النسخة على اسم جديد)؟", + "styleXAlreadyExistsInFile": "يوجد نمط باسم \"{0}\" بالفعل في الملف.\n\nهل تريد استبداله، أم الإبقاء على كليهما (تحصل النسخة على اسم جديد)؟", + "styleNameCannotBeEmpty": "لا يمكن أن يكون اسم النمط فارغًا.", + "styleNameXIsUsedMoreThanOnce": "اسم النمط \"{0}\" مستخدم من قِبل أكثر من نمط واحد في الملف.", + "overwrite": "استبدال", + "keepBoth": "الإبقاء على كليهما", + "doThisForAllConflictingStyles": "تطبيق هذا على جميع الأنماط المتعارضة", "setStyleAsDefault": "تعيين النمط كافتراضي", "takeUsagesFromDotDotDot": "أخذ الاستخدامات من...", "replaceStyleWithDotDotDot": "استبدال النمط بـ...", "noAttachmentsFound": "لم يتم العثور على مرفقات في ملف ASSA المحدد.", "deleteStyleQuestion": "حذف النمط؟", "deleteStylesQuestion": "حذف الأنماط؟", + "deleteXStylesQuestion": "هل تريد حذف {0} من الأنماط؟", + "deleteXStylesFromStorageQuestion": "هل تريد حذف {0} من الأنماط من التخزين؟", + "deleteStyleXFromStorageQuestion": "هل تريد حذف النمط \"{0}\" من التخزين؟", + "deleteStyleXFromFileQuestion": "هل تريد حذف النمط \"{0}\" من الملف الحالي؟", "openStyleImportFile": "افتح ملف ترجمة لاستيراد الأنماط منه", + "styleImportFiles": "ملفات الأنماط (.ass، .sty، فئات Subtitle Edit 4 بصيغة .template)", + "ssaStyleImportFiles": "ملفات الأنماط (.ssa، .ass، .sty)", "primary": "أساسي", "secondary": "ثانوي", "applyOverrideTags": "تطبيق وسوم التجاوز", @@ -4037,7 +4486,6 @@ "spaceIncrease": "زيادة التباعد", "popIn": "ظهور مفاجئ", "spinIn": "ظهور دوراني", - "playCurrent": "تشغيل الحالي", "setPosition": "تعيين الموضع", "rotation": "التدوير", "scaleX": "التحجيم X", @@ -4081,10 +4529,8 @@ "advancedEffectOldMovieDescription": "يحاكي جهاز عرض أفلام 35mm عتيقًا مع تشويش وتظليل حوافي وخدوش", "advancedEffectNeonBurst": "انفجار نيون", "advancedEffectNeonBurstDescription": "ترجمات عالية الطاقة بنمط 'Shorts' مع توهج نيون متوهج وحركة 'ظهور' بتكبير", - "advancedEffectSettings": "الإعدادات", "advancedEffectSnowFlakeCount": "عدد الرقاقات", "advancedEffectStarfieldStarCount": "عدد النجوم", - "advancedEffectStarfieldSpeed": "السرعة", "advancedEffectGlitch": "خلل", "advancedEffectGlitchDescription": "خلل رقمي مع زيغ لوني ووميض أبيض وطبقات تشويه", "advancedEffectBounceIn": "ظهور ارتدادي", @@ -4093,8 +4539,6 @@ "advancedEffectFirefliesDescription": "نقاط صفراء كهرمانية دافئة متوهجة تنجرف عضويًا عبر الشاشة مع توهج نابض", "advancedEffectMatrix": "ماتريكس", "advancedEffectMatrixDescription": "أعمدة أحرف خضراء متساقطة مع نص الترجمة يتكون حرفًا حرفًا من المطر", - "advancedEffectAudioBars": "أشرطة صوتية (موجة صوتية)", - "advancedEffectAudioBarsDescription": "أشرطة معادل صوتي متحركة مدفوعة بالموجة الصوتية، تنزلق الأشرطة إلى اليسار كموجة صوتية وتنبض مع الصوت", "advancedEffectAudioPulse": "نبض النص الصوتي", "advancedEffectAudioPulseDescription": "يتوهج نص الترجمة وينبض مع مستوى الصوت، تُظهر المقاطع الهادئة نصًا عاديًا، وتضيف الذُرى العالية هالة ملونة وتكبيرًا طفيفًا", "advancedEffectConfetti": "انفجار قصاصات", @@ -4108,7 +4552,6 @@ "advancedEffectFancyKaraokeGlow": "توهج الكلمة النشطة", "advancedEffectFancyKaraokeActiveColor": "اللون النشط", "advancedEffectFancyKaraokeInactiveColor": "اللون غير النشط", - "advancedEffectFancyKaraokeInactiveOpacity": "شفافية غير النشط (0-255)", "advancedEffectWordSpacingPixels": "التباعد (بكسل)", "advancedEffectSlowZoomOut": "تصغير بطيء", "advancedEffectSlowZoomOutDescription": "يبدأ النص أكبر قليلًا ويتقلص برفق إلى الحجم العادي على مدى مدة الترجمة", @@ -4124,7 +4567,6 @@ "advancedEffectSlideInLeftDescription": "ينزلق النص إلى الداخل من خارج الشاشة يسارًا، يثبت، ثم يخرج عائدًا إلى اليسار", "advancedEffectSlideInRight": "انزلاق من اليمين", "advancedEffectSlideInRightDescription": "ينزلق النص إلى الداخل من خارج الشاشة يمينًا، يثبت، ثم يخرج عائدًا إلى اليمين", - "advancedEffectFadeInOut": "ظهور/اختفاء تدريجي", "advancedEffectFadeInOutDescription": "يظهر النص تدريجيًا في البداية ويختفي تدريجيًا في نهاية كل ترجمة", "advancedEffectWordFlip3D": "قلب الكلمات ثلاثي الأبعاد", "advancedEffectWordFlip3DDescription": "تنطوي كل كلمة بقلب ثلاثي الأبعاد ووثبة مرنة عند نطقها — أسلوب تعليقات 'Shorts' الحديث", diff --git a/src/ui/Assets/Languages/Azerbaijani.json b/src/ui/Assets/Languages/Azerbaijani.json index 3acdecc39d9..304a2191b84 100644 --- a/src/ui/Assets/Languages/Azerbaijani.json +++ b/src/ui/Assets/Languages/Azerbaijani.json @@ -1,12 +1,30 @@ { "title": "Subtitle Edit", - "version": "v5.2.0-rc6", + "version": "v5.3.0", "translatedBy": "", "cultureName": "az-AZ", "general": { "abort": "Dayandır", "actor": "Aktyor", "actors": "Aktyorlar", + "setActorDotDotDot": "Aktyoru təyin et...", + "actorPickerTitle": "Aktyoru təyin et", + "actorPickerFilterHint": "Süzmək üçün yazın və ya yeni aktyor adı daxil edin", + "actorPickerKeysHint": "Aktyoru təyin etmək üçün rəqəm və ya Enter düyməsini basın. Alt+Up/Down sıranı dəyişir, Delete aktyoru silir.", + "actorPickerNewActorX": "Yeni aktyor \"{0}\"", + "actorPickerLinesSelectedX": "Seçilmiş sətirlər: {0}", + "setStyleDotDotDot": "Üslubu təyin et...", + "stylePickerTitle": "Üslubu təyin et", + "stylePickerFilterHint": "Süzmək üçün yazın və ya yeni üslub adı daxil edin", + "stylePickerKeysHint": "Üslubu bütün seçilmiş sətirlərə təyin etmək üçün rəqəm (1-9, 0) basın.", + "stylePickerNewStyleX": "Yeni üslub \"{0}\"", + "stylePickerNewStyleInfo": "Bu üslubu standart parametrlərlə əlavə etmək və seçilmiş sətirlərə təyin etmək üçün Enter basın. Sonradan onun şriftini, rənglərini və mövqeyini dəyişmək üçün \"Üslubları idarə et...\" istifadə edin.", + "stylePickerCurrentStyleX": "Cari üslub: {0}", + "stylePickerLinesUsingStyleX": "Bu üslubdan istifadə edən sətirlər: {0}", + "stylePickerMarginsXYZ": "Sol {0}, sağ {1}, şaquli {2}", + "stylePickerSetStyle": "Üslubu _təyin et", + "stylePickerManageStyles": "Üslubları idarə et...", + "stylePickerManageStylesHint": "Bu pəncərəni bağla və şriftləri, rəngləri, mövqeyi və s. redaktə etmək üçün üslub menecerini aç", "add": "Əlavə et", "addDotDotDot": "Əlavə et...", "addToNamesListCaseSensitive": "Adlar siyahısına əlavə et (böyük və kiçik hərflərə həssas)", @@ -339,6 +357,7 @@ "mergeSelectedLinesDialog": "Seçilmiş sətirləri dialoq kimi birləşdir", "mergeWithLineAfterAndAutoBreak": "Sonrakı sətirlə birləşdir və avtomatik sətirlərə böl", "mergeWithLineAfterKeepBreaks": "Sonrakı sətirlə birləşdir (sətir keçidlərini saxla)", + "mergeWithLineAfterAndUnbreak": "Sonrakı sətirlə birləşdir və sətir keçidlərini çıxar", "mergeWithLineBeforeAndAutoBreak": "Əvvəlki sətirlə birləşdir və avtomatik sətirlərə böl", "mergeWithLineBeforeKeepBreaks": "Əvvəlki sətirlə birləşdir (sətir keçidlərini saxla)", "middleCenter": "Ortada mərkəzdə", @@ -353,6 +372,8 @@ "moveAllShotChangeOneFrameForward": "Bütün səhnə dəyişikliklərini bir kadr irəli çək", "multiMode": "Çoxlu rejim", "moveDown": "Aşağı köçür", + "increase": "Artır", + "decrease": "Azalt", "moveUp": "Yuxarı köçür", "moveToTop": "Ən yuxarıya köçür", "moveToBottom": "Ən aşağıya köçür", @@ -422,6 +443,7 @@ "percent": "Faiz", "pickLayer": "Qatı təyin et", "pickOllamaModel": "Ollama modelini seç", + "pickModel": "Model seç", "llamaCppUseRemoteServer": "Xarici serverdən istifadə et (URL)", "llamaCppEngineSettings": "llama.cpp mühərrikinin parametrləri", "llamaCppEngineSettingsSubtitle": "Lokal llama.cpp serverinin yığımı", @@ -498,6 +520,7 @@ "reset": "Sıfırla", "resizePanels": "Panellərin ölçüsünü dəyiş", "resolution": "Görüntü ölçüsü", + "retry": "Yenidən cəhd et", "reverseRightToLeftStartEnd": "Sağdan sola yazılan mətnin başlanğıcını və sonunu tərsinə çevir", "right": "Sağ", "rightToLeft": "Sağdan sola", @@ -559,6 +582,8 @@ "shadowColor": "Kölgənin rəngi", "shadowWidth": "Kölgənin eni", "shiftLineBy": "Sətrin sürüşmə miqdarı", + "shotIn": "Səhnə girişi", + "shotOut": "Səhnə çıxışı", "shortcut": "Düymə birləşməsi", "shortcuts": "Düymə birləşmələri", "show": "Göstər", @@ -570,9 +595,13 @@ "showStartColumn": "\"Başlama\" sütununu göstər", "showHideColumn": "\"Gizlət\" sütununu göstər", "showHistory": "Tarixçəni göstər", + "clearHistory": "Tarixçəni təmizlə", + "removeFromHistory": "Tarixçədən çıxar", "showLayerColumn": "\"Qat\" sütununu göstər", "showPreview": "Önbaxışı göstər", "showShotChangesList": "Səhnə dəyişikliklərinin siyahısını göstər", + "showShotInColumn": "\"Səhnə girişi\" sütununu göstər", + "showShotOutColumn": "\"Səhnə çıxışı\" sütununu göstər", "showStyleColumn": "\"Üslub\" sütununu göstər", "showTeletext": "Siyahı görünüşündə teletekst sütununu göstər", "showTimeCodes": "Vaxt kodlarını göstər", @@ -629,12 +658,14 @@ "styleExaggeration": "Üslubun şişirdilməsi", "styles": "Üslublar", "subtitleFile": "Altyazı faylı", + "subtitleFileChangedOutsideX": "\"{0}\" faylı Subtitle Edit xaricində dəyişdirilib.\n\nOnu yenidən yükləyib dəyişikliklərinizi itirmək istəyirsiniz?", "subtitleFileName": "Altyazı faylının adı", "subtitleFileSaved": "Altyazı faylı yadda saxlanıldı", "subtitleFileSavedToX": "Altyazı faylı {0} məkanında yadda saxlanıldı", "subtitleFiles": "Altyazı faylları", "subtitleFormats": "Altyazı formatları", "subtitleLoadedX": "Altyazı yükləndi: {0}", + "subtitleReloadedChangedOutsideX": "Altyazı yenidən yükləndi (Subtitle Edit xaricində dəyişdirilib): {0}", "suffix": "Sonluq", "suggestions": "Təkliflər", "sync": "Sinxronlaşdırma", @@ -680,6 +711,7 @@ "type": "Növ", "unbreak": "Mətndəki sətir keçidlərini çıxar", "unbreakLines": "Sətir keçidlərini çıxar", + "undelete": "Silinməni geri al", "underline": "Altından xətt çəkilmiş", "undo": "Geri al", "unknown": "Naməlum", @@ -717,10 +749,23 @@ "videoCustom3ForwardX": "Video, fərdi müddət ({0:#,###,##0} millisaniyə) irəli, 3", "videoCustom4BackX": "Video, fərdi müddət ({0:#,###,##0} millisaniyə) geri, 4", "videoCustom4ForwardX": "Video, fərdi müddət ({0:#,###,##0} millisaniyə) irəli, 4", + "videoCustom1BackAndPauseX": "Video, xüsusi millisaniyə ({0:#,###,##0}) geri və fasilə, 1", + "videoCustom1ForwardAndPauseX": "Video, xüsusi millisaniyə ({0:#,###,##0}) irəli və fasilə, 1", + "videoCustom2BackAndPauseX": "Video, xüsusi millisaniyə ({0:#,###,##0}) geri və fasilə, 2", + "videoCustom2ForwardAndPauseX": "Video, xüsusi millisaniyə ({0:#,###,##0}) irəli və fasilə, 2", + "videoCustom3BackAndPauseX": "Video, xüsusi millisaniyə ({0:#,###,##0}) geri və fasilə, 3", + "videoCustom3ForwardAndPauseX": "Video, xüsusi millisaniyə ({0:#,###,##0}) irəli və fasilə, 3", + "videoCustom4BackAndPauseX": "Video, xüsusi millisaniyə ({0:#,###,##0}) geri və fasilə, 4", + "videoCustom4ForwardAndPauseX": "Video, xüsusi millisaniyə ({0:#,###,##0}) irəli və fasilə, 4", "videoExtension": "Video faylının genişlənməsi", "videoFile": "Video faylı", "videoFileGenerated": "Video faylı yaradıldı", "videoFileGeneratedX": "Video faylı yaradıldı: \"{0}\"", + "videoFilesGenerated": "Video faylları yaradıldı", + "xVideosGenerated": "{0} video yaradıldı", + "xOfYVideosGenerated": "{1} videodan {0} yaradıldı", + "savedInX": "{0} yerində yadda saxlanıldı", + "savedInXFolders": "{0} qovluqda yadda saxlanıldı", "videoFiles": "Video faylları", "videoAndAudioFiles": "Video və səs faylları", "videoOffset": "Videonun vaxt sürüşməsi", @@ -778,7 +823,6 @@ "imageFiles": "Şəkil faylları", "openAiCompatibleSttEndpoint": "STT xidmətinin URL ünvanı", "openAiCompatibleSttApiKey": "API açarı", - "openAiCompatibleSttModel": "Model", "openAiCompatibleSttExtraHeaders": "Əlavə başlıqlar", "openAiCompatibleSttTimeout": "Gözləmə müddəti (saniyə)", "openAiCompatibleSttLanguage": "Dil göstərişi", @@ -788,6 +832,9 @@ "dashScopeSttRegion": "Region", "dashScopeSttEnableWords": "Söz səviyyəsində vaxt nişanları", "dashScopeSttRegionKeyHint": "Qeyd: Alibaba Cloud Model Studio API açarları regiona bağlıdır. Seçilmiş regionun API açarının yaradıldığı regionla (Çin və ya beynəlxalq) uyğun olduğunu yoxlayın.", + "googleCloudSttProjectId": "Layihə ID-si (yalnız Application Default Credentials üçün)", + "googleCloudSttBucketName": "Saxlama bucket-i (boş = avtomatik)", + "googleCloudSttDynamicBatching": "Dinamik paketləmə (təxminən 5 dəfə ucuz, gecikmə zəmanəti yoxdur)", "onlineSttApiKeyMissing": "API açarı tələb olunur. API açarınızı daxil edib yenidən cəhd edin.", "openAiCompatibleSttAutoTranscribeOnAudioSelection": "Səs dalğası qrafikində yeni seçilmiş hissədəki nitqi avtomatik mətnə çevir", "openAiCompatibleSttStream": "Cavabı axın şəklində al", @@ -845,6 +892,7 @@ "multipleReplace": "_Çoxsaylı əvəzetmə...", "fillSelectedLinesWithClipboard": "Seçilmiş sətirləri mübadilə buferindəki mətnlə doldur", "goToLineNumber": "_Sətir nömrəsinə keç...", + "commandPalette": "Əmr _palitrası...", "rightToLeftMode": "S_ağdan sola yazı rejimi", "modifySelectionDotDotDot": "_Seçimi dəyiş...", "tools": "_Alətlər", @@ -852,6 +900,7 @@ "applyDurationLimits": "Müddət _məhdudiyyətlərini tətbiq et...", "batchConvert": "_Toplu çevir...", "beautifyTimeCodes": "Vaxt kodlarını nizamla...", + "improveTimeCodes": "Vaxt kodlarını yaxşılaşdır (məcburi uyğunlaşdırma)...", "bridgeGaps": "_Boşluqları bağla...", "applyMinGap": "Altyazılar arasında minimal boşluq tətbiq et...", "changeCasing": "_Böyük və kiçik hərfləri dəyiş...", @@ -990,6 +1039,9 @@ "selectCurrentLineWhilePlayingHint": "Oynatma zamanı cari altyazını seç {0}", "videoPosition": "Video mövqeyi {0}", "videoPositionTextBox": "Video mövqeyi üçün mətn xanası {0}", + "toolbarLineBreakX": "Sətir keçidi {0} (alətlər panelinin qalanı yeni sırada davam edir)", + "initialText": "Seçilmiş sətrin ilkin mətni", + "initialTextHint": "Seçilmiş sətrin seçildiyi andakı mətni", "hideWaveformToolbar": "Alətlər panelini gizlət {0}", "resetZoomAndSpeed": "Miqyası və oynatma sürətini sıfırla {0}", "removeBlankLines": "Boş sətirləri çıxar {0}", @@ -1003,6 +1055,15 @@ "textPlayHint": "Cari altyazını oynat və sonunda dayan {0}", "textPauseHint": "Oynatmaya fasilə ver {0}", "textNextHint": "Növbəti altyazını oynat və sonunda dayan {0}", + "moveSelectedLines": "Seçilmiş sətirləri sürüşdür (geri/irəli düymələri)", + "moveSelectedLinesAndFollowing": "Seçilmiş sətirləri və bütün sonrakıları sürüşdür (geri/irəli düymələri)", + "moveAllLines": "Bütün sətirləri sürüşdür (geri/irəli düymələri)", + "moveSelectedLinesBackHint": "Seçilmiş sətirləri {0} ms geri sürüşdür {1}", + "moveSelectedLinesForwardHint": "Seçilmiş sətirləri {0} ms irəli sürüşdür {1}", + "moveSelectedLinesAndFollowingBackHint": "Seçilmiş sətirləri və bütün sonrakıları {0} ms geri sürüşdür {1}", + "moveSelectedLinesAndFollowingForwardHint": "Seçilmiş sətirləri və bütün sonrakıları {0} ms irəli sürüşdür {1}", + "moveAllLinesBackHint": "Bütün sətirləri {0} ms geri sürüşdür {1}", + "moveAllLinesForwardHint": "Bütün sətirləri {0} ms irəli sürüşdür {1}", "guessStartNoLineSelected": "Başlanğıcı təxmin et: sətir seçilməyib", "guessStartTimeCodesLocked": "Başlanğıcı təxmin et: vaxt kodları kilidlənib", "guessStartNoWaveform": "Başlanğıcı təxmin et: səs dalğası qrafiki yoxdur", @@ -1024,12 +1085,15 @@ "charactersPerSecond": "Simvol/saniyə: {0}", "chooseColumn": "Sütun seç", "columnPaste": "Sütun üzrə yapışdır", + "commandPaletteTitle": "Əmr palitrası", + "commandPaletteSearchPlaceholder": "Əmrləri axtarmaq üçün yazın...", + "commandPaletteNoCommandsFound": "Uyğun əmr yoxdur", "createdEmptyTranslation": "Cari altyazıdan boş tərcümə yaradıldı", "deleteText": "Mətni sil", "deleteTextAndShiftCellsUp": "Mətni sil və xanaları yuxarı sürüşdür", "endTimeMustBeAfterStartTime": "Bitmə vaxtı başlama vaxtından sonra olmalıdır.", "errorLoad7Zip": "Görünür, bu fayl sıxılmış 7-Zip faylıdır.\n\nSubtitle Edit sıxılmış faylları aça bilmir.", - "errorLoadBinaryZeroes": "Təəssüf ki, bu faylda yalnız ikilik sıfırlar var!\n\nBu faylı Subtitle Edit ilə redaktə etmisinizsə, Fayl > Avtomatik ehtiyat nüsxəni bərpa et... menyusundan ehtiyat nüsxə tapa bilərsiniz.", + "errorLoadBinaryZeroes": "Təəssüf ki, bu faylda yalnız ikilik sıfırlar var!\n\nBu faylı Subtitle Edit ilə redaktə etmisinizsə, Fayl > Avtomatik ehtiyat nüsxəni bərpa et... menyusundan ehtiyat nüsxə tapa bilərsiniz.", "errorLoadGZip": "Görünür, bu fayl sıxılmış GZip faylıdır.\n\nSubtitle Edit sıxılmış faylları aça bilmir.", "errorLoadLargeFile": "Bu fayl altyazı faylı kimi açılmaq üçün çox böyükdür.", "errorLoadVideoFilePrompt": "Görünür, bu fayl video faylıdır:\n{0}\n\nVideonu \"Video > Videonu aç\" menyusundan və ya videonu pleyerin üzərinə sürükləyib buraxaraq aça bilərsiniz.\n\nOnu video faylı kimi açmaq istəyirsiniz?", @@ -1062,11 +1126,14 @@ "numberOfLinesEvenlyDistributedX": "{0} sətir bərabər paylandı", "oneLineCopiedFromOriginal": "Orijinal altyazıdan bir sətir kopyalandı", "oneLineCopiedToOriginal": "Orijinal altyazıya bir sətir kopyalandı", + "onlineOnlyVideo": "Yalnız onlayn olan video", + "onlineOnlyVideoXSizeYDownloadAndOpen": "\"{0}\" video faylı ({1}) yalnız onlayn saxlanılır, məsələn, Dropbox və ya iCloud Drive-da, və açılmazdan əvvəl endirilməlidir.\n\nİndi endirilib açılsın?", "oneLineMerged": "Bir sətir birləşdirildi", "oneLineSwitched": "Bir sətrin yeri dəyişdirildi", "overwriteExistingCells": "Mövcud xanaların üzərinə yaz", "overwriteOrShiftCellsDown": "Üzərinə yaz/xanaları aşağı sürüşdür", "parsingMatroskaFile": "Matroska faylı təhlil edilir...", + "readingDvdSubtitles": "DVD altyazıları oxunur...", "pasteFromClipboardDotDotDot": "Mübadilə buferindən yapışdır...", "redoPerformed": "Əməliyyat yenidən icra edildi", "redoPerformedXActionLeft": "Əməliyyat yenidən icra edildi (qalan əməliyyat: {0})", @@ -1106,12 +1173,14 @@ "trimmedXLines": "{0} altyazı sətri qısaldıldı", "pastedXLinesOverSelectedLines": "Seçilmiş sətirlərin üzərinə {0} sətir yapışdırıldı", "pastedXOfYLinesOverSelectedLines": "{1} sətirdən {0} sətir seçilmiş sətirlərin üzərinə yapışdırıldı, qalanları seçilmiş sahəyə sığmadı", + "insertedXLinesFromClipboard": "Mübadilə buferindən {0} sətir daxil edildi", "openOriginalDifferentNumberOfSubtitlesXY": "Orijinal və cari altyazı fayllarındakı altyazıların sayı eyni deyil.\n\n• Orijinal altyazılar: {0}\n• Cari altyazılar: {1}", "originalTextReadOnly": "Orijinal mətn (yalnız oxumaq üçün)", "originalTextEditMode": "Orijinal mətn (redaktə rejimi)", "originalIsReadOnlyNotSaved": "Orijinal altyazı yalnız oxumaq üçün istinaddır və yadda saxlanılmadı", "originalIsEmptyNotSaved": "Orijinal altyazıda mətn yoxdur və yadda saxlanılmadı", "originalIsReadOnlyReference": "Orijinal altyazı yalnız oxumaq üçün istinad kimi açılıb", + "closeTranslationQuestionXY": "\"{0}\" tərcüməsi bağlansın?\n\n\"{1}\" orijinal altyazısı redaktə etdiyiniz altyazıya çevriləcək. Tərcümə faylının özü dəyişdirilmir.", "allowEditOfOriginalSubtitle": "Orijinal altyazının redaktəsinə icazə ver", "showAllOriginalLinesX": "Bütün orijinal sətirləri göstər (burada {0} sətrin qarşılığı yoxdur)", "showAllOriginalLinesHint": "Tərcümədə nəyin çatışmadığını görə bilməyiniz üçün qarşılığı olmayan sətirlər əlavə sətirlər kimi göstərilir.", @@ -1146,6 +1215,7 @@ "fileExportedInFormatXToY": "Fayl {0} formatında {1} məkanına ixrac edildi", "fileExportedInFormatXToFileY": "Fayl \"{0}\" formatında \"{1}\" faylına ixrac edildi", "fixedXLines": "{0} sətir düzəldildi", + "toolXChangedYLines": "{0}: {1} sətir dəyişdirildi", "transcriptionCompletedWithXLines": "Transkripsiya tamamlandı, {0} sətir yaradıldı", "openSubtitleFileX": "\"{0}\" altyazı faylı açılsın?", "replacedXOccurrences": "{0} uyğunluq əvəz edildi", @@ -1236,6 +1306,21 @@ "formattingDotDotDot": "Formatlaşdırma...", "imageBasedSubtitleForEditDotDotDot": "Redaktə üçün şəkil əsaslı altyazı...", "imageBasedSubtitleForOcrDotDotDot": "OCR üçün şəkil əsaslı altyazı...", + "dvdSubtitlesDotDotDot": "DVD-dən altyazılar (IFO/VOB)...", + "titleImportDvd": "DVD-dən altyazıları idxal et", + "dvdIfoFile": "IFO faylı", + "dvdVobFiles": "VOB faylları", + "dvdVideoStandard": "Video standartı", + "dvdStartRipping": "Çıxarmağa başla", + "dvdRippingAborted": "Çıxarma dayandırıldı", + "dvdOpenIfoFile": "DVD IFO faylını aç", + "dvdAddVobFiles": "VOB faylları əlavə et", + "dvdIfoFiles": "DVD IFO faylları", + "dvdVobFileType": "DVD VOB faylları", + "dvdDropHint": "IFO faylını açın (VIDEO_TS\\VTS_01_0.IFO) və ya VOB faylları əlavə edin - ya da onları bura sürükləyin", + "dvdNotAnIfoFileX": "{0} DVD IFO faylı deyil", + "dvdMenuIfoNoTitleSetsX": "{0} yanında başlıq dəstləri (VTS_xx_0.IFO) tapılmadı", + "dvdEncryptedXOfY": "{1} altyazı paketindən {0} hələ də CSS ilə şifrələnib - bu VOB faylları şifrəsi açılmadan kopyalanıb, buna görə bəzi altyazı şəkilləri pozulmuş olacaq.\n\nTəmiz nəticə üçün əvvəlcə DVD-nin şifrəsini DVD çıxarma aləti ilə açın.\n\nYenə də davam edilsin?", "splitTextAt": "Mətni bölmə meyarı", "blankLines": "Boş sətirlər", "oneLineIsOneSubtitle": "Bir sətir bir altyazıdır", @@ -1255,8 +1340,13 @@ "forcedAlignerProgress": "Uyğunlaşdırılır... pəncərə {0} / {1}, sətir {2} / {3}", "forcedAlignerSetupTitle": "Vaxt kodlarını məcburi uyğunlaşdırıcı vasitəsilə uyğunlaşdır", "forcedAlignerSetupIntro": "Məcburi uyğunlaşdırıcı səsi əvvəlcədən mətnə çevirmədən mövcud altyazı mətnini səslə uyğunlaşdırır. Uzun videolar hissələr üzrə uyğunlaşdırıldığı üçün istənilən uzunluqda video ilə işləyir.", + "forcedAlignerEndsFromSpeech": "Son vaxtları ayrılmış nitqdən təyin et (yavaş)", + "forcedAlignerEndsFromSpeechHint": "Məcburi uyğunlaşdırıcı sətrin harada bitdiyini deyil, harada başladığını tapır, buna görə son vaxtlar adətən oxuma vaxtından götürülür. Bu açıq olduqda əvvəlcə səsdən musiqi və səs effektləri çıxarılır və hər sətir nitqin susduğu yerdə bitir. GPU ilə təxminən videonun özü qədər, GPU olmadan isə dəfələrlə çox vaxt aparır.", + "forcedAlignerIsolatingSpeech": "Nitq ayrılır... {0}", + "forcedAlignerIsolatingSpeechFailed": "Nitqi ayırmaq mümkün olmadı - son vaxtlar əvəzinə oxuma vaxtından təyin edildi.", "forcedAlignerModel": "Uyğunlaşdırıcı modeli", - "forcedAlignerDownloadEngine": "Mühərriki endir / yenilə..." + "forcedAlignerDownloadEngine": "Mühərriki endir / yenilə...", + "forcedAlignerEstimatedXOfY": "{1} sətirdən {0} uyğunlaşdırıla bilmədi və oxuma vaxtına görə yerləşdirildi - onların vaxt kodları yalnız təxminidir. Ehtimal ki, ssenari videoda deyiləndən uzundur." }, "export": { "exportImagesProfiles": "Şəkil ixracı profilləri", @@ -1305,6 +1395,18 @@ "imageBasedSubtitleSaved": "Şəkil əsaslı altyazı yadda saxlanıldı", "fullFrameImage": "Tam kadr ölçüsündə şəkil", "fullFrameImageHint": "Altyazının mövqeyini saxlayaraq hər şəkli video kadrının ölçüsünə uyğunlaşdır", + "stereo3D": "3D", + "stereo3DHint": "3D video üçün: altyazını kadrın hər gözə aid yarısında bir dəfə çək", + "halfSideBySide": "Yarım yan-yana", + "halfTopBottom": "Yarım yuxarı/aşağı", + "depth3D": "Dərinlik", + "depth3DHint": "Altyazının ekrandan nə qədər önə çıxması - müsbət onu yaxınlaşdırır, mənfi geri itələyir", + "plane3D": "3D-Plane", + "plane3DHint": "BD3D2MK3D və ya OFSExtractor ilə yaradılmış 3D-Plane (.ofs) faylından hər altyazıya 3D Blu-ray-in onun üçün saxladığı dərinliyi ver. 3D-Plane-də dərinlik olmayan yerlərdə Dərinlik istifadə olunur", + "openPlane3DTitle": "3D-Plane faylını aç", + "unableToLoadPlane3DX": "3D-Plane yüklənə bilmədi: {0}", + "plane3DXDepthYToZ": "{0} (dərinlik {1} - {2})", + "plane3DXNoDepth": "{0} (dərinlik dəyərləri yoxdur)", "textEffect": "Mətn effekti", "textEffectSettingsTitle": "Mətn effekti parametrləri", "textEffectPreset": "Hazır parametr dəsti", @@ -1409,7 +1511,22 @@ "restoreAutoBackupFile": "Avtomatik ehtiyat nüsxə faylını bərpa et", "restoreXFromY": "\"{0}\" faylını {1} tarixli nüsxədən bərpa etmək istəyirsiniz?", "deleteAllSubtitleBackups": "Bütün altyazı ehtiyat nüsxə fayllarını silmək istəyirsiniz?", - "deleteAll": "Hamısını sil" + "deleteAll": "Hamısını sil", + "subtitles": "Altyazılar", + "settingsBackupInfo": "Settings.json faylının ehtiyat nüsxəsi Subtitle Edit başladıqda, ən çoxu {0} gündə bir dəfə və yalnız parametrlər dəyişibsə çıxarılır. Ən yeni {1} ehtiyat nüsxə saxlanılır.", + "settingsBackupInfoEveryStart": "Parametrlər dəyişibsə, Settings.json faylının ehtiyat nüsxəsi Subtitle Edit hər dəfə başladıqda çıxarılır. Ən yeni {0} ehtiyat nüsxə saxlanılır.", + "settingsBackupOff": "Parametrlərin avtomatik ehtiyat nüsxəsi söndürülüb (Seçimlər → Parametrlər → Fayl).", + "lastSettingsBackupX": "Son ehtiyat nüsxə: {0}", + "noSettingsBackupsYet": "Hələ parametrlərin ehtiyat nüsxəsi yoxdur", + "openSettingsBackupFolder": "Parametrlərin ehtiyat nüsxə qovluğunu aç", + "backUpSettingsNow": "İndi ehtiyat nüsxə çıxar", + "settingsBackedUp": "Cari parametrlərin ehtiyat nüsxəsi çıxarıldı.", + "restoreSettings": "Parametrləri bərpa et", + "restoreSettingsFromX": "Cari parametrlərinizi {0} tarixli ehtiyat nüsxə ilə əvəz etmək istəyirsiniz?\n\nƏvvəlcə cari parametrlərinizin ehtiyat nüsxəsi çıxarılacaq. Bəzi dəyişikliklər yalnız Subtitle Edit yenidən başladıldıqdan sonra qüvvəyə minir.", + "settingsRestored": "Parametrlər bərpa edildi. Bütün dəyişikliklərin qüvvəyə minməsi üçün Subtitle Edit-i yenidən başladın.", + "settingsRestoreFailed": "Parametrləri bərpa etmək mümkün olmadı: {0}", + "settingsBackupNotValid": "ehtiyat nüsxə etibarlı parametrlər faylı deyil, buna görə cari parametrləriniz saxlanıldı", + "deleteAllSettingsBackups": "Bütün parametr ehtiyat nüsxə fayllarını silmək istəyirsiniz?" }, "propertiesDCinema": { "title": "DCinema SMPTE xüsusiyyətləri", @@ -1423,22 +1540,16 @@ "editRate": "Montaj sürəti", "timeCodeRate": "Vaxt kodu sürəti", "startTime": "Başlama vaxtı", - "font": "Şrift", "fontId": "Şrift identifikatoru", "fontUri": "Şriftin resurs ünvanı (URI)", - "generate": "Yarat", - "fontColor": "Şrift rəngi", "chooseColor": "Rəng seç", "fontEffect": "Şrift effekti", "effectColor": "Effekt rəngi", - "fontSize": "Şrift ölçüsü", "topBottomMargin": "Üst/alt kənar boşluğu", "fadeUpTime": "Tədricən görünmə müddəti", "fadeDownTime": "Tədricən itmə müddəti", - "frames": "Kadrlar", "zPosition": "Z mövqeyi", - "zPositionHelp": "Müsbət ədədlər mətni uzaqlaşdırır, mənfi ədədlər yaxınlaşdırır. Z mövqeyi sıfır olduqda mətn 2D olur", - "export": "İxrac et..." + "zPositionHelp": "Müsbət ədədlər mətni uzaqlaşdırır, mənfi ədədlər yaxınlaşdırır. Z mövqeyi sıfır olduqda mətn 2D olur" }, "webVtt": { "setStylesForSelectedLinesTitle": "Seçilmiş sətirlər üçün WebVTT üslublarını təyin et", @@ -1476,11 +1587,61 @@ "ignoreFormatting": "Formatlaşdırmanı nəzərə alma", "ignoreFormattingHint": "Yalnız və ya {\\an8} kimi formatlaşdırma etiketləri ilə fərqlənən sətirlər fərqli sayılmır", "showOnlyDifferencesInText": "Yalnız mətndəki fərqlər", + "ignoreNumbering": "Nömrələməni nəzərə alma", + "ignoreNumberingHint": "Yalnız nömrəsi ilə fərqlənən sətirlər fərqli sayılmır", + "ignoreTimeDifferenceMs": "Vaxt tolerantlığı (ms)", + "ignoreTimeDifferenceMsHint": "Bu qədər və ya daha az millisaniyə fərqlənən başlanğıc və son vaxtları fərqli sayılmır", + "compareDifferences": "Fərqlər", + "compareTextDifferences": "Mətn fərqləri", + "compareCurrent": "Cari", + "compareReference": "İstinad", + "compareEditable": "Redaktə edilə bilən", + "compareEditableHint": "Burada edilən dəyişikliklər Tətbiq et düyməsini basdıqda altyazıya qaytarılır", + "compareReadOnly": "Yalnız oxunuş", + "compareTakeFromReference": "Mətni və vaxtlamanı istinaddan götür", + "compareInsertFromReference": "Bu sətri cari altyazıya daxil et", + "compareDeleteFromCurrent": "Bu sətri cari altyazıdan sil", + "compareTakeText": "Mətni götür", + "compareTakeTiming": "Vaxtlamanı götür", + "compareEditLine": "Sətri redaktə et", + "compareEditHint": "Yadda saxlamaq üçün Ctrl+Enter, ləğv etmək üçün Esc", + "compareEdited": "Redaktə edilib", + "compareOnePendingChange": "1 gözləyən dəyişiklik", + "compareXPendingChanges": "{0} gözləyən dəyişiklik", + "compareDiscardXChanges": "Müqayisədə edilən {0} dəyişiklik ləğv edilsin?", + "compareXChangesApplied": "Müqayisə: {0} dəyişiklik tətbiq edildi", + "compareChangeTextX": "#{0} mətn istinaddan", + "compareChangeTimingX": "#{0} vaxtlama istinaddan", + "compareChangeTextAndTimingX": "#{0} mətn və vaxtlama istinaddan", + "compareChangeEditedX": "#{0} redaktə edildi", + "compareChangeInsertedX": "#{0} daxil edildi", + "compareChangeDeletedX": "#{0} silindi", + "compareSyncPoint": "Sinxronlaşdırma nöqtəsi", + "compareSyncPointHint": "Sinxronlaşdırma nöqtəsi - bu iki sətir həmişə cüt kimi göstərilir, yuxarı və aşağıdakı sətirlər isə ayrıca düzülür", + "compareSyncPickCurrent": "Sinxronlaşdırma nöqtəsi: bu cari sətirdən istifadə et", + "compareSyncPickReference": "Sinxronlaşdırma nöqtəsi: bu istinad sətrindən istifadə et", + "compareSyncRemove": "Sinxronlaşdırma nöqtəsini sil", + "compareClearXSyncPoints": "Sinxronlaşdırma nöqtələrini təmizlə ({0})", + "compareSyncCurrentPickedX": "Cari #{0} sinxronlaşdırma nöqtəsi kimi seçildi - uyğun istinad sətrini seçin və Sinxronlaşdır düyməsini basın", + "compareSyncReferencePickedX": "İstinad #{0} sinxronlaşdırma nöqtəsi kimi seçildi - uyğun cari sətri seçin və Sinxronlaşdır düyməsini basın", + "compareSyncWithCurrentX": "Cari #{0} ilə sinxronlaşdır", + "compareSyncWithReferenceX": "İstinad #{0} ilə sinxronlaşdır", + "compareSyncApply": "Sinxronlaşdır", + "compareSyncPointSetXY": "Sinxronlaşdırma nöqtəsi təyin edildi: cari #{0} və istinad #{1} indi cüt kimi göstərilir", + "compareSyncAlreadyPairedXY": "Cari #{0} və istinad #{1} artıq eyni başlanğıcla cüt kimi göstərilir - sinxronlaşdırmağa ehtiyac yoxdur", + "compareSyncPointShiftedXYZW": "Sinxronlaşdırma nöqtəsi təyin edildi: cari #{0}-#{1} istinad #{3} ilə başlamaq üçün {2} sürüşdürüldü", + "compareChangeSyncShiftXYZ": "#{0}-#{1} {2} sürüşdürüldü (sinxronlaşdırma nöqtəsi)", + "compareTimingDeltaHint": "İstinadın cari sətirdən nə qədər gec (+) və ya tez (−) başlayıb bitdiyi", "loadXFromFile": "\"{0}\" faylını yüklə", + "compareLoadSavedFile": "Yadda saxlanılmış faylı yüklə", "saveCompareHtmlTitle": "Müqayisə HTML faylını yadda saxla", "pickMatroskaTrackX": "Matroska cığırını seç: {0}", "pickTransportStreamTrackX": "Nəqliyyat axını cığırını seç: {0}", + "chapters": "Bölmələr", + "dvdVobFilesMissingX": "VOB faylları çatışmır ({0}% tapıldı)", "pickMp4TrackX": "MP4 cığırını seç: {0}", + "pickMxfTrackX": "MXF trekini seç - {0}", + "pickMpegTrackX": "MPEG trekini seç - {0}", "rosettaProperties": "Timed Text Rosetta IMSC xüsusiyyətləri", "rosettaFontSize": "Şrift ölçüsü (sətrin hündürlüyü)", "propertyTimeBase": "Vaxt bazası", @@ -1490,7 +1651,6 @@ "propertyDefaultRegion": "Standart bölgə", "propertyStyleAttributeName": "Üslub atributunun adı", "propertyTimeCodeFormat": "Vaxt kodu formatı", - "propertyFileExtension": "Fayl genişlənməsi", "propertyTopOrigin": "Üst bölgənin başlanğıc nöqtəsi", "propertyTopExtent": "Üst bölgənin ölçüsü", "propertyBottomOrigin": "Alt bölgənin başlanğıc nöqtəsi", @@ -1556,7 +1716,8 @@ "findWhat": "Axtarılan mətn", "descriptionOptional": "Təsvir (istəyə bağlı)", "invalidRegularExpressionX": "Yanlış müntəzəm ifadə: {0}", - "regularExpressionTooSlowX": "Müntəzəm ifadənin icrası {0} saniyədən sonra dayandı. Qayda ötürülür, daha sadə nümunə sınayın" + "regularExpressionTooSlowX": "Müntəzəm ifadənin icrası {0} saniyədən sonra dayandı. Qayda ötürülür, daha sadə nümunə sınayın", + "moveToCategory": "Kateqoriyaya köçür" }, "find": { "searchTextWatermark": "Axtarılacaq mətn...", @@ -1569,7 +1730,9 @@ "replaceIn": "Əvəzetmə/axtarış sahəsi", "replaceInTextAndOriginal": "Mətn və orijinal mətn", "replaceInTextOnly": "Yalnız mətn", - "replaceInOriginalOnly": "Yalnız orijinal mətn" + "replaceInOriginalOnly": "Yalnız orijinal mətn", + "replacedOneOccurrence": "Bir uyğunluq əvəz edildi", + "replacedXOccurrences": "{0} uyğunluq əvəz edildi" }, "regularExpressionContextMenu": { "wordBoundary": "Söz sərhədi (\\b)", @@ -1597,6 +1760,7 @@ "xLinesYSubtitles": "{0} sətir, {1} altyazı", "xLinesYSubtitlesZErrors": "{0} sətir, {1} altyazı, {2} xəta", "couldNotParseAsX": "{0} kimi təhlil etmək mümkün olmadı", + "readAsXUseAsY": "Mənbə {1} kimi oxuna bilmədi, lakin {0} kimi oxuna bildi.\nSətirlərdən istifadə edib {1} kimi yadda saxlamağa davam edilsin?", "selectedXCharactersYLines": "Seçilib: {1} sətirdə {0} simvol", "replacedXOccurrences": "{0} uyğunluq əvəz edildi", "invalidRegularExpression": "Yanlış müntəzəm ifadə", @@ -1632,7 +1796,23 @@ "largeChangeWarning": "Böyük dəyişiklikdir, mətn yenidən yazılmış kimi görünür. Diqqətlə yoxlayın", "mismatchWarning": "Orijinala bənzəmir, başqa sətrə aid ola bilər", "engineError": "Süni intellekt mühərriki ilə əlaqə yaratmaq mümkün olmadı: {0}", - "playCurrentLineHint": "Seçilmiş sətri video pleyerdə oynat və sonunda fasilə ver (sətrə iki dəfə klikləməklə də mümkündür)" + "playCurrentLineHint": "Seçilmiş sətri video pleyerdə oynat və sonunda fasilə ver (sətrə iki dəfə klikləməklə də mümkündür)", + "editAfterHint": "Tətbiq etməzdən əvvəl düzəlişi redaktə etmək üçün seçilmiş sıranın mətninə klikləyin (Enter yadda saxlayır, Escape ləğv edir)", + "template": "Şablon", + "templatePlaceholder": "Təlimatı əvəz etmək üçün şablon seçin", + "templateStandard": "Standart korrektura", + "templateSpeechToText": "Nitqdən mətnə nəticəsinin təmizlənməsi", + "templateMachineTranslation": "Maşın tərcüməsinin düzəldilməsi", + "templateSdh": "SDH / eşitmə məhdudiyyətli şəxslər üçün", + "referenceContext": "İstinad konteksti", + "referenceContextInfo": "İxtiyari. Adlar, personajlar, terminlər lüğəti, xülasə və ya üslub qeydləri - model bunu adlar və terminlər üçün əsas mənbə kimi qəbul edir. Hər paketlə göndərilir.", + "referenceContextPlaceholder": "Adlar: Kaito, Mei xala, Dr. Hollis\nTerminlər: Şinkansen, warp nüvəsi\nXülasə: Kaito itkin atasını tapmaq üçün Tokioya gedir.", + "generateContext": "Süni intellektlə yarat", + "generateContextHint": "Seçilmiş süni intellekt mühərriki altyazını oxuyub adlar, terminlər və xülasə layihəsini hazırlasın - istifadə etməzdən əvvəl nəticəni yoxlayın", + "generatingContextPartXOfY": "{1} hissədən {0} oxunur...", + "generatingContextSynopsis": "Xülasə yazılır...", + "replaceContextQuestion": "Cari istinad konteksti əvəz edilsin?", + "noContextGenerated": "Süni intellekt mühərriki istifadəyə yararlı istinad məlumatı qaytarmadı." }, "aiAssistant": { "title": "Süni intellekt köməkçisi", @@ -1678,6 +1858,7 @@ "removeLineBreaksPixelWidth": "Bir sətrə sığan altyazıların sətirlərini birləşdir (piksel eni)", "fixUppercaseIInsideLowercaseWords": "Kiçik hərflə yazılmış sözlərdəki böyük 'I' hərfini düzəlt (OCR xətası)", "fixDoubleApostrophes": "Qoşa apostrof işarələrini ('') bir dırnaq işarəsi (\") ilə əvəz et", + "fixMisreadQuotes": "Qoşa dırnaq kimi səhv oxunmuş apostrofları düzəlt (OCR, ingilis dili)", "addPeriods": "Növbəti sətir böyük hərflə başlayırsa, əvvəlki sətrin sonuna nöqtə əlavə et", "startWithUppercaseLetterAfterParagraph": "Abzasdan sonra böyük hərflə başla", "startWithUppercaseLetterAfterPeriodInsideParagraph": "Abzas daxilində nöqtədən sonra böyük hərflə başla", @@ -1700,6 +1881,7 @@ "fixCommasExample": ",, → ,", "removeLineBreaksExample": "Salam
dünya! → Salam dünya!", "fixDoubleApostrophesExample": "''Qoşa apostroflar var'' → \"Bir cüt dırnaq işarəsi var\"", + "fixMisreadQuotesExample": "\"Hello' -> \"Hello\"", "addPeriodsExample": "Salam dünya → Salam dünya.", "startWithUppercaseLetterAfterParagraphExample": "p1: Salam! || p2: salam! → p1: Salam! || p2: Salam!", "startWithUppercaseLetterAfterPeriodInsideParagraphExample": "Salam! necəsən? → Salam! Necəsən?", @@ -1789,6 +1971,8 @@ "fixMinDurationMs": "Minimum müddəti düzəlt (ms)", "doNotGoPastShotChange": "Səhnə dəyişikliyini keçmə", "fixMaxDurationMs": "Maksimum müddəti düzəlt (ms)", + "fixMinDurationFrames": "Minimum müddəti düzəlt (kadr)", + "fixMaxDurationFrames": "Maksimum müddəti düzəlt (kadr)", "minimumDurationMilliseconds": "Minimum müddət (millisaniyə):", "maximumDurationMilliseconds": "Maksimum müddət (millisaniyə):", "maxDurationShouldBeHigherThanMinDuration": "Maksimum müddət minimum müddətdən çox olmalıdır", @@ -1838,13 +2022,80 @@ "timeCodesExtractFailed": "Videodan vaxt kodlarını oxumaq mümkün olmadı", "timeCodesHint": "Altyazı vaxtlarını sabit kadr sürəti fərziyyəsinə əsaslanmadan videonun həqiqi kadr vaxtlarına uyğunlaşdır. Bütün videonu bir dəfə oxumaq lazımdır; nəticə keşdə saxlanılır." }, + "improveTimeCodes": { + "title": "Vaxt kodlarını yaxşılaşdır (məcburi uyğunlaşdırma)", + "aligner": "Uyğunlaşdırıcı", + "alignerHint": "Səsdə hər sətri dinləyən model. Altyazının dili üçün ən yaxşı seçimlər əvvəldə göstərilir.", + "maxShift": "Maksimum sürüşmə (saniyə)", + "maxShiftHint": "Altyazının nə qədər sinxrondan çıxa biləcəyi. Başlanğıcı bundan daha çox sürüşəcək sətir olduğu kimi saxlanılır və işarələnir. Dəyərdən asılı olmayaraq, ətrafdakı sətirlərdən yarım saniyədən çox sürüşmək istəyən tək sətir kor-koranə sürüşdürülmür: qonşuları hamısı sürüşübsə, onlarla birlikdə sürüşür, əks halda yoxlamağınız üçün işarəsiz təklif olunur.", + "adjustStartTimes": "Başlanğıc vaxtlarını tənzimlə", + "adjustEndTimes": "Son vaxtları tənzimlə", + "isolateSpeech": "Əvvəlcə nitqi ayır (yavaş)", + "isolateSpeechHint": "Uyğunlaşdırmadan əvvəl musiqi və səs effektlərini çıxarır ki, uyğunlaşdırıcı yalnız dialoqu eşitsin. Yüksək musiqi və ya hərəkət fonunda deyilən sətirlərdə kömək edir, lakin GPU ilə təxminən səsin özü qədər, GPU olmadan isə dəfələrlə çox vaxt aparır.", + "isolatingSpeech": "Nitq ayrılır... (bu bir qədər vaxt aparır)", + "isolateSpeechFailed": "Nitqi ayırmaq mümkün olmadı - əvəzində orijinal səslə uyğunlaşdırıldı.", + "checkWithSpeechToText": "Nitqi mətnə çevirmə ilə yoxla", + "checkWithSpeechToTextHint": "Səsi həm də mətnə çevirir və hər sürüşməni sözlərin həqiqətən eşidildiyi yerlə yoxlayır. Nitqin təsdiqlədiyi böyük sürüşmələr sizin üçün işarələnir, sətrin eşidildiyi yerdən uzaqlaşan sürüşmələr isə yoxlamağınız üçün işarəsiz qalır. Həmçinin hər sətrin nə qədərinin eşidildiyini göstərir - aşağı pay mətnin deyiləndən fərqləndiyini bildirir. Maksimum sürüşmədən daha çox sinxrondan çıxmış altyazı - sabit fərq, kadr sürəti uyğunsuzluğundan yaranan sürüşmə və ya səhnələrin kəsildiyi yerlərdəki sıçrayışlar - əvvəlcə eşidilən sözlərə görə sinxronlaşdırılır. Crisp ASR Parakeet istifadə edir, bu da GPU ilə tammetrajlı film üçün bir neçə dəqiqə çəkir.", + "checkWithSpeechToTextNotAvailableX": "{0} üçün nitqi mətnə çevirmə yoxlaması mövcud deyil", + "transcribingToCheck": "Uyğunlaşdırmanı yoxlamaq üçün mətnə çevrilir...", + "speechToTextFailed": "Nitqi mətnə çevirmə alınmadı - vaxt kodları onunla yoxlanılmadı.", + "speechToTextX": "Nitqi mətnə çevirmə: {0}", + "speechToTextConfirmedXDisputedY": "Nitq: {0} təsdiqləndi, {1} şübhəli", + "hardlyHeardX": "Nitqi mətnə çevirmə altyazı sözlərinin yalnız {0}%-ni eşitdi. Altyazı danışılan dildədirmi? Uyğunlaşdırıcı tərcüməni yerləşdirə bilmir.", + "heard": "Eşidilən", + "heardHint": "Sətrin nə qədərini nitqi mətnə çevirmə eşitdi. Aşağı pay mətnin deyiləndən fərqləndiyini bildirir - uyğunlaşdırıcının ən çox səhv edə biləcəyi yer.", + "statusConfirmedBySpeech": "Nitqlə təsdiqləndi", + "statusDisputedBySpeech": "Olduğu yerdə eşidildi", + "statusMovedWithSync": "Sinxronlaşdırma ilə sürüşdürüldü", + "syncedFirstXY": "Əvvəlcə sinxronlaşdırıldı: əvvəldə {0} s, sonda {1} s", + "showSpeechOnly": "Yalnız nitqi göstər", + "showSpeechOnlyHint": "Uyğunlaşdırılmış səs dalğasını musiqi və səs effektləri çıxarılmış səsdən çək; orijinal səs dalğası müqayisə üçün olduğu kimi qalır. Nitq ayrıldıqdan sonra mövcuddur - burada və ya əsas pəncərənin səs dalğasındakı \"Yalnız nitqi göstər\" ilə.", + "align": "Uyğunlaşdır", + "original": "Orijinal", + "aligned": "Uyğunlaşdırılmış", + "intro": "Səsdə hər sətri dinləmək və vaxt kodlarını dəqiqləşdirmək üçün uyğunlaşdırıcı seçin və \"Uyğunlaşdır\" düyməsini basın.", + "extractingAudio": "Səs çıxarılır...", + "aligningBatchXOfY": "Uyğunlaşdırılır... {1} paketdən {0}", + "extractAudioFailed": "ffmpeg ilə səsi çıxarmaq mümkün olmadı.", + "modelWillBeDownloaded": "{0} endiriləcək", + "summaryXRetimedYKeptZSkipped": "Yenidən vaxtlandı: {0} · Saxlanıldı: {1} · Nitq yoxdur: {2}", + "meanShiftX": "Orta sürüşmə: {0} ms", + "toCheckX": "Yoxlanılacaq: {0}", + "largeMovesToCheckX": "{0} sətir təkbaşına çox uzağa sürüşəcək və işarəsiz saxlanılıb - onları dinləyin (F5 uyğunlaşdırılmış sətri, Shift+F5 orijinalı oxudur) və düzgün olanları işarələyin.", + "linesToCheckX": "{0} sətir yoxlamaq üçün işarəsiz saxlanılıb - onları dinləyin (F5 uyğunlaşdırılmış sətri, Shift+F5 orijinalı oxudur) və düzgün olanları işarələyin.", + "changeXOfY": "{1} dəyişiklikdən {0}", + "previousChange": "Əvvəlki dəyişiklik", + "nextChange": "Növbəti dəyişiklik", + "startShift": "Başlanğıc", + "endShift": "Son", + "statusRetimed": "Yenidən vaxtlandı", + "statusMovedWithNeighbours": "Qonşuları ilə birlikdə sürüşdürüldü", + "statusLargeMoveUnconfirmed": "Böyük sürüşmə - dinləyin, tətbiq etmək üçün işarələyin", + "statusUnchanged": "Artıq yerindədir", + "statusNoSpeech": "Nitq yoxdur", + "statusShiftTooLarge": "Saxlanıldı - sürüşmə çox böyükdür", + "statusFailed": "Saxlanıldı - uyğunlaşdırıcı uğursuz oldu", + "statusAdjustedByHand": "Əl ilə tənzimləndi", + "alignedWaveformHint": "Sətri sürüşdürmək üçün onu sürükləyin və ya başlanğıcını və ya sonunu dəyişmək üçün kənarlarını sürükləyin.", + "playLine": "Sətri oynat", + "undoAdjustment": "Tənzimləməni geri al", + "playPause": "Oynat / fasilə", + "playAligned": "Uyğunlaşdırılmış", + "playAlignedHint": "Seçilmiş sətri uyğunlaşdırılmış vaxt kodları ilə oynat", + "apply": "Tətbiq et", + "applyHint": "Orijinal vaxt kodlarını saxlamaq üçün sətrin işarəsini götürün. Sətri oynatmaq üçün ona iki dəfə klikləyin. Sətirləri uyğunlaşdırılmış səs dalğasında da sürüşdürmək və ya ölçüsünü dəyişmək olar." + }, "beautifyTimeCodesProfile": { "title": "Vaxt kodlarının təkmilləşdirilməsi profilini redaktə et", "loadPreset": "Hazır parametr dəstini yüklə", "presetDefault": "Standart", "presetNetflix": "Netflix", "presetSdi": "SDI", - "general": "Ümumi", + "saveAsProfile": "Profil kimi yadda saxla...", + "deleteProfile": "Profili sil", + "profileName": "Profil adı", + "overwriteProfileX": "\"{0}\" adlı profil artıq mövcuddur. Üzərinə yazılsın?", + "deleteProfileX": "\"{0}\" profili silinsin?", "gap": "Fasilə", "gapSuffix": "kadr (aşağıdakı sıfırdan fərqli fasilə sahələrini də yeniləyir)", "milliseconds": "ms", @@ -1875,6 +2126,10 @@ "bridgeGaps": { "bridgeGapsSmallerThan": "Bu dəyərdən kiçik aralıqları doldur (ms)", "minGap": "Minimum aralıq (ms)", + "bridgeGapsSmallerThanFrames": "Bundan kiçik boşluqları birləşdir (kadr)", + "minGapFrames": "Minimum boşluq (kadr)", + "calculateBridgeGapsSmallerThanDotDotDot": "Boşluq birləşdirmə həddini kadr sürətindən hesabla...", + "useXMsAsBridgeGapsSmallerThan": "\"{0}\" millisaniyədən kiçik boşluqlar birləşdirilsin?", "numberOfSmallGapsBridgedX": "Doldurulmuş kiçik aralıqların sayı: {0}", "percentFoPrevious": "Əvvəlki altyazı üçün aralıq (%)", "gapChange": "Aralıq dəyişikliyi" @@ -1908,6 +2163,7 @@ "addColor": "Rəng əlavə et", "deleteLinesWithSpecificActorsOrStyles": "Bu aktyor və ya üslubların olduğu sətirləri sil (bir neçə dəyəri vergüllə ayırın)", "useSourceStylesIfPossible": "Mümkün olduqda mənbə üslublarından istifadə et", + "keepSourceEmbeddedFonts": "Mənbəyə daxil edilmiş şriftləri saxla (son hissə)", "editStyles": "Üslubları redaktə et", "editProperties": "Xassələri redaktə et", "editAttachments": "Qoşmaları redaktə et", @@ -1939,10 +2195,50 @@ "convertColorsToDialogReBreakLines": "Sətirləri yenidən böl", "snapTimeCodesToFramesInfo": "Hər başlama və bitmə vaxtını ən yaxın kadra yuvarlaqlaşdırır. Sabit kadr sürəti seçilmədikdə, kadr sürəti altyazı faylı ilə eyniadlı video faylından oxunur.", "addFolderDotDotDot": "Qovluq əlavə et...", + "addFolderRecursiveDotDotDot": "Qovluğu alt qovluqlarla əlavə et...", "selectFolderToConvert": "Çevriləcək faylların olduğu qovluğu seçin", "includeSubfolders": "Qovluq əlavə edərkən alt qovluqları da daxil et", + "videoFilesWhenAddingFolder": "Qovluq əlavə edərkən video faylları (mkv, mp4, ts...)", + "videoFilesAsk": "Soruş", + "videoFilesInclude": "Onların altyazı treklərini əlavə et", + "videoFilesSkip": "Ötür", + "folderContainsXVideoFiles": "Qovluqda {0} video faylı var (mkv, mp4, ts...).\n\nOnların daxili altyazı trekləri də əlavə edilsin, yoxsa yalnız altyazı faylları?", + "addVideoFiles": "Video faylları əlavə et", + "skipVideoFiles": "Yalnız altyazı faylları", + "doNotAskAgainVideoFiles": "Bir daha soruşma (Toplu çevirmə parametrlərində dəyişin)", + "settingsSectionOutput": "Çıxış", + "settingsSectionAddingFiles": "Faylların əlavə edilməsi", + "settingsSectionImageBasedSubtitles": "Şəkil əsaslı altyazılar (OCR)", + "settingsSectionOther": "Digər", "keepSourceFileTimestamp": "Çıxış fayllarında mənbə faylının tarixini/vaxtını saxla", - "scanningFolderX": "{0} yoxlanılır..." + "preventSleepWhileConverting": "Çevirmə zamanı kompüterin yuxu rejiminə keçməsinin qarşısını al", + "scanningFolderX": "{0} yoxlanılır...", + "transportStreamSettings": "Transport Stream parametrləri", + "transportStreamSettingsDotDotDot": "Transport Stream parametrləri...", + "transportStreamSettingsInfo": "Transport Stream fayllarından (.ts/.m2ts) çıxarılan altyazı treklərinə aiddir. Mövqe və video ölçüsü yalnız şəkil əsaslı formata ixrac edilən DVB şəkil treklərinə təsir edir.", + "transportStreamOverrideXPosition": "Orijinal X mövqeyini əvəz et", + "transportStreamOverrideYPosition": "Orijinal Y mövqeyini əvəz et", + "transportStreamOverrideVideoSize": "Orijinal video ölçüsünü əvəz et", + "transportStreamBottomMargin": "Alt kənar boşluğu", + "transportStreamFileNameEnding": "Fayl adının sonu", + "transportStreamFileNameEndingInfo": "Hər çıxarılan trek üçün genişlənmədən əvvəl əlavə olunur. Adi dil şəkilçisindən istifadə etmək üçün boş buraxın.", + "transportStreamOnlyTeletext": "Yalnız teletekst", + "transportStreamGetSizeFromVideo": "Ölçünü videodan götür...", + "twoLetterLanguageCodeUppercase": "İki hərfli dil kodu (böyük hərflərlə)", + "threeLetterLanguageCodeUppercase": "Üç hərfli dil kodu (böyük hərflərlə)", + "formatContainsDotDotDot": "Format ehtiva edir...", + "extensionIsDotDotDot": "Genişlənmə budur...", + "statusIsError": "Vəziyyət xətadır", + "forcedTracksOnly": "Yalnız məcburi treklər", + "xConvertedYFailedInZ": "{2} ərzində {0:#,###,##0} çevrildi, {1:#,###,##0} uğursuz oldu", + "xNotProcessed": "{0:#,###,##0} emal edilmədi", + "removeUnicodeControlCharactersTitle": "Unicode idarəetmə simvollarını çıxar", + "removeUnicodeControlCharactersInfo": "Mətndən görünməz Unicode istiqamət idarəetmə simvollarını (LRM, RLM, LRE, RLE, PDF, LRO və RLO) çıxarır və bölünməz boşluqları adi boşluqlarla əvəz edir.", + "preset": "Hazır parametr dəsti", + "savePresetDotDotDot": "Hazır parametr dəstini yadda saxla...", + "deletePreset": "Hazır parametr dəstini sil", + "presetName": "Hazır parametr dəstinin adı", + "deletePresetX": "\"{0}\" hazır parametr dəsti silinsin?" }, "changeCasing": { "fixNames": "Adları düzəlt", @@ -2027,10 +2323,13 @@ }, "mergeLinesWithSameText": { "maxMsBetweenLines": "Sətirlər arasında maksimum aralıq (millisaniyə)", - "includeIncrementingLines": "Mətni tədricən artan sətirləri də daxil et" + "maxFramesBetweenLines": "Sətirlər arasında maksimum kadr", + "includeIncrementingLines": "Mətni tədricən artan sətirləri də daxil et", + "includeRollUpCaptions": "Sürüşən (roll-up) titrləri daxil et" }, "mergeLinesWithSameTimeCodes": { "maxMsDifference": "Maksimum fərq (millisaniyə)", + "maxFramesDifference": "Maksimum fərq (kadr)", "makeDialog": "Dialoqlar yarat" }, "netflixCheckAndFix": { @@ -2130,12 +2429,29 @@ "letterboxCustom": "Fərdi zolaq hündürlüyü", "barHeightPx": "Zolağın hündürlüyü (px)", "titleSafePercent": "Başlıq üçün təhlükəsiz sahə (%)", + "moveCaptions": "Titrləri sürüşdür", + "moveCaptionsDotDotDot": "Titrləri sürüşdür...", + "moveCaptionsTo": "Titrləri sürüşdür", + "moveCaptionsIntoBars": "Qara zolaqların içinə (təsvirdən kənara)", + "moveCaptionsIntoPicture": "Təsvirin içinə (zolaqlardan kənara)", + "moveCaptionsOffset": "Kənardan məsafə (px)", + "letterboxBarHeightXPxYOfZCaptionsWillMove": "Zolağın hündürlüyü: {0} px - {2} titrdən {1} sürüşdürüləcək", + "removeFadeInOut": "Tədricən görünmə/itməni çıxar", + "removeFadeInOutXLinesRemoved": "{0} keçid sətri aid olduğu sətirlərlə birləşdirildi", + "removeFadeInOutNothingFound": "Tədricən görünmə/itmə sətirləri tapılmadı", "positionSummary": "{0}×{1}, {2} altyazı, zolağın hündürlüyü: {3} px", "xInActivePicture": "Görüntüdə: {0}", "xInTopBar": "Üst zolaqda: {0}", "xInBottomBar": "Alt zolaqda: {0}", "noImageSubtitlesLoaded": "Şəkil əsaslı altyazı yüklənməyib", - "imageBasedFormatNotSupported": "Şəkil əsaslı altyazı formatı tapılmadı və ya dəstəklənmir." + "imageBasedFormatNotSupported": "Şəkil əsaslı altyazı formatı tapılmadı və ya dəstəklənmir.", + "frameRateDeclaredInFile": "Blu-ray sup faylında göstərilən kadr sürəti", + "frameRateDetectedFromTimeCodesX": "Blu-ray sup vaxt kodlarından aşkarlanan kadr sürəti (faylda {0} göstərilib)", + "frameRateFromVideo": "Videonun kadr sürəti", + "frameRateCurrent": "Cari kadr sürəti", + "frameRateSetManually": "Əl ilə təyin edilmiş kadr sürəti", + "frameRateUsageInfo": "Blu-ray sup, BDN XML, DOST və FCP ixracları bu kadr sürəti ilə yazılır. Bu heç bir vaxtı dəyişmir - bunun üçün \"Kadr sürətini dəyiş\" istifadə edin.", + "useVideoFrameRateXInsteadOfY": "Videonun kadr sürəti {0}, lakin altyazı üçün {1} təyin edilib.\n\nVideonun kadr sürətindən ({0}) istifadə edilsin?" }, "removeTextForHearingImpaired": { "interjections": "Nidalar", @@ -2165,6 +2481,7 @@ "pickFontNameInstalledFonts": "Quraşdırılmış şriftlər", "pickFontNameCollectedFonts": "Toplanmış şriftlər", "colorPickerTitle": "Rəng seç", + "colorPickerBrightness": "Parlaqlıq", "filterLayersTitle": "Göstəriləcək qatları süzgəcdən keçir", "filterLayersHideFromWaveform": "Səs dalğasının qrafikində/spektroqramda gizlət", "filterLayersHideFromSubtitleGrid": "Altyazı cədvəlində gizlət", @@ -2215,7 +2532,21 @@ "correctWordsX": "Düzgün sözlər: {0}", "namesX": "Adlar: {0}", "addedToDictionaryX": "Lüğətə əlavə edilənlər: {0}", - "doNotShowThisAgain": "Bu mesajı bir daha göstərmə" + "doNotShowThisAgain": "Bu mesajı bir daha göstərmə", + "editUseAlwaysList": "\"Həmişə istifadə et\" siyahısını redaktə et...", + "useAlwaysListTitle": "Orfoqrafiya yoxlaması - \"Həmişə istifadə et\" siyahısı", + "useAlwaysListHeader": "\"Həmişə istifadə et\" siyahısı", + "useAlwaysListDescription": "Orfoqrafiya yoxlamasının avtomatik əvəz etdiyi sözlər, \"Hamısını dəyiş\" və \"Həmişə istifadə et\" ilə yadda saxlanılır. Cüt yalnız söz səhv yazıldıqda istifadə olunur.", + "useAlwaysWord": "Səhv yazılmış söz", + "xPairs": "{0:#,##0} cüt", + "xUnusedPairs": "{0:#,##0} heç vaxt istifadə olunmayıb", + "useAlwaysUnusedHint": "Heç vaxt istifadə olunmayıb - bu söz düzgün yazılıb (və ya ad, yaxud istifadəçi sözüdür), buna görə orfoqrafiya yoxlaması onu heç vaxt işarələmir", + "removeUnused": "İstifadə olunmayanları sil", + "useAlwaysEmpty": "Bu dil üçün hələ \"Həmişə istifadə et\" cütləri yoxdur", + "useAlwaysEmptyHint": "Cütlər orfoqrafiya yoxlamasında \"Hamısını dəyiş\" və ya \"Həmişə istifadə et\" düyməsini basdıqda əlavə olunur.", + "useAlwaysNoMatch": "Axtarışa uyğun cüt yoxdur", + "useAlwaysSameWord": "Əvəzləyici söz sözün özündən fərqli olmalıdır.", + "useAlwaysRememberOff": "Parametrlərdə \"'Həmişə istifadə et' siyahısını yadda saxla\" söndürülüb, buna görə orfoqrafiya yoxlaması bu siyahıdan istifadə etmir və onu yadda saxlamır." }, "video": { "burnIn": { @@ -2228,6 +2559,7 @@ "sampleRate": "Nümunələmə tezliyi", "audio": "Səs", "preset": "Hazır parametr dəsti", + "tune": "Tənzimləmə (tune)", "pixelFormat": "Piksel formatı", "crf": "CRF", "useSource": "Mənbədən istifadə et", @@ -2239,7 +2571,9 @@ "outputProperties": "Çıxış xüsusiyyətləri...", "videoFileSize": "Video faylının ölçüsü", "oneBox": "Bir çərçivə", - "logoInfo": "PNG şəkli seçin və videoda yerləşdirmək üçün sürükləyin." + "logoInfo": "PNG şəkli seçin və videoda yerləşdirmək üçün sürükləyin.", + "noSubtitlesGenerateAnyway": "Həkk ediləcək altyazı sətri yoxdur.\n\nVideo altyazısız yaradılsın?", + "noSubtitlesInXItemsGenerateAnyway": "{0} toplu elementdə həkk ediləcək altyazı sətri yoxdur.\n\nHəmin videolar altyazısız yaradılsın?" }, "videoTransparent": { "title": "Altyazılı şəffaf video yarat" @@ -2250,6 +2584,21 @@ "translateToEnglish": "İngilis dilinə tərcümə et", "transcribing": "Mətnə çevrilir...", "transcribingXOfY": "Mətnə çevrilir: {0} / {1}...", + "linesTranscribedXOfYFailedZ": "{1} sətirdən {0} mətnə çevrildi - {2} uğursuz oldu və ya nitq yox idi.\n\nUğursuz sətirlər cari mətnini saxlayır. Mətnə çevrilmiş sətirlər tətbiq edilsin, yoxsa uğursuz olanlar yenidən cəhd edilsin (əvvəlcə mühərriki və ya parametrləri dəyişə bilərsiniz)?", + "linesTranscribedXOfYCancelled": "Mətnə çevirmə {1} sətirdən {0} sonra ləğv edildi.\n\nMətnə çevrilmiş sətirlər tətbiq edilsin? Digər sətirlər cari mətnini saxlayır.", + "noLinesTranscribed": "{0} sətrin heç biri mətnə çevrilə bilmədi.\nTəfərrüatlar üçün alətlər jurnalını yoxlayın.", + "applyTranscribedLines": "Mətnə çevrilənləri tətbiq et", + "retryFailedLines": "Uğursuzları yenidən cəhd et", + "isolateSpeech": "Nitqi ayır (yavaş)", + "isolateSpeechHint": "Mətnə çevirmədən əvvəl musiqi və səs effektlərini çıxarır. Yüksək musiqili səsdə daha az buraxılmış sətir və daha yaxşı vaxtlama verir, lakin GPU ilə təxminən səsin özü qədər, GPU olmadan isə dəfələrlə çox vaxt aparır.", + "isolatingSpeech": "Nitq ayrılır...", + "isolateSpeechFailed": "Nitqi ayırmaq mümkün olmadı - əvəzində orijinal səs mətnə çevrilir.", + "detectSpeakers": "Danışanları aşkarla", + "detectSpeakersHint": "NVIDIA-nın Sortformer diarizasiyasından (8 danışana qədər) istifadə edərək hər sətri danışanla başladır, məsələn \"(speaker 0)\". Danışan dəyişdikdə sətirləri bölən icra mühitləri ilə, məsələn Parakeet və Cohere ilə ən yaxşı işləyir.", + "detectSpeakersNeedsNewerCrispAsr": "\"Danışanları aşkarla\" üçün Crisp ASR v{0} və ya daha yeni versiya lazımdır, lakin v{1} quraşdırılıb.\n\nCrisp ASR-i yeniləyin (mühərrikin yanındakı dişli düyməsi) və ya \"Danışanları aşkarla\" seçimini söndürün.", + "xNeedsNewerCrispAsr": "{0} üçün Crisp ASR v{1} və ya daha yeni versiya lazımdır, lakin v{2} quraşdırılıb.\n\nYeniləmə indi endirilib quraşdırılsın?", + "vad": "VAD", + "vadHint": "Səs aktivliyinin aşkarlanması: yalnız nitq mətnə çevrilir, danışanın fasilə verdiyi yerlərdən kəsilir.\nAvtomatik: Silero, yalnız ona ehtiyacı olan mühərriklər üçün (Cohere, Mega, Index-Echo).\nSilero, FireRedVAD, Whisper-VAD, WebRTC: hər Crisp ASR mühərriki üçün həmişə açıqdır. FireRedVAD ən dəqiqdir, Whisper-VAD ən az və ən təmiz kəsiklər verir, lakin ən yavaşdır.\nIndex-Echo həmişə Silero istifadə edir. Əlavə parametrlərdəki --vad, --vad-model və ya --chunk-seconds üstünlük təşkil edir.", "inputLanguage": "Giriş dili", "advancedWhisperSettings": "Nitqin mətnə çevrilməsi üçün genişləndirilmiş parametrlər", "downloadingSpeechToTextEngine": "Nitqi mətnə çevirən mühərrik endirilir", @@ -2333,6 +2682,19 @@ "videoEncodingSettings": "TTS: videonun kodlaşdırma parametrləri", "elevenLabsSettings": "TTS: ElevenLabs parametrləri", "elevenLabsSettingsResetHint": "ElevenLabs parametrlərinin standart dəyərlərini bərpa et", + "provider": "Provayder", + "openAiCompatibleSettings": "TTS - OpenAI ilə uyğun parametrlər", + "customServerUrl": "Xüsusi serverin URL ünvanı", + "customServerUrlHint": "OpenAI ilə uyğun serverin tam nitq ünvanı, provayder \"Custom\" olduqda istifadə olunur, məsələn http://localhost:8880/v1/audio/speech", + "customModels": "Xüsusi modellər", + "customModelsHint": "Provayder \"Custom\" olduqda təklif olunan, vergüllə ayrılmış model adları", + "customVoices": "Xüsusi səslər", + "customVoicesHint": "Vergüllə ayrılmış səs adları, \"Custom\" provayderi və səs siyahısı dərc etməyən OpenRouter modelləri tərəfindən istifadə olunur", + "instructions": "Təlimatlar", + "instructionsHint": "İxtiyari danışıq üslubu, məsələn \"Sakit, isti səslə danış\". Yalnız bunu dəstəkləyən modellərə göndərilir (tts-1/tts-1-hd deyil)", + "openAiSpeedHint": "Nitq sürəti 0.25-dən 4.0-a qədər (1.0 normaldır). Bunu hər model dəstəkləmir", + "outputFormat": "Çıxış formatı", + "outputFormatHint": "Serverdən tələb olunan səs formatı. \"Auto\" MP3 istəyir və yalnız PCM qaytaran modellər üçün PCM-ə keçir (məsələn Gemini TTS)", "regenerateAudio": "Səsi yenidən yarat", "autoContinuePlaying": "Oxutmanı avtomatik davam etdir", "playLine": "Sətri oxut", @@ -2341,6 +2703,13 @@ "addingAudioToVideoFileDotDotDot": "Video faylına səs əlavə edilir...", "preparingMergeDotDotDot": "Birləşdirməyə hazırlıq görülür...", "importVoiceDotDotDot": "Səs idxal et...", + "renameVoiceDotDotDot": "Səsin adını dəyiş...", + "renameVoiceTitle": "Səsin adını dəyiş", + "voiceXCouldNotBeRenamedX": "'{0}' səsinin adı dəyişdirilə bilmədi: {1}", + "deleteVoiceDotDotDot": "Səsi sil...", + "deleteVoiceTitle": "Səsi sil", + "deleteVoiceXQuestion": "'{0}' səsi silinsin?\n\nİstinad yazısı və onun faylları diskdən silinir.", + "voiceXCouldNotBeDeletedX": "'{0}' səsi silinə bilmədi: {1}", "voiceImportSuccessTitle": "Səs idxal edildi", "voiceXImported": "'{0}' səsi uğurla idxal edildi", "voiceXCouldNotBeImported": "'{0}' səsi idxal edilə bilmədi. Təfərrüatlar üçün jurnala baxın", @@ -2350,6 +2719,49 @@ "dropAudioFileHereToImportVoice": "Səsi idxal etmək üçün səs faylını buraya buraxın", "dropAudioFileHereHint": ".wav və ya .mp3", "voiceCloneTranscriptTitle": "Səs yazısının mətnini daxil edin (səsin klonlanması üçün tələb olunur)", + "voiceManagerTitle": "TTS - Səs meneceri", + "voiceManagerDotDotDot": "Səs meneceri...", + "voiceManagerMenuItem": "TTS səs meneceri...", + "speakFromCurrentLineTitle": "Cari sətirdən səsləndir", + "speakFromCurrentLineDotDotDot": "Cari sətirdən səsləndir...", + "speakFromCurrentLineHint": "Cari sətirdən başlayaraq sətirləri bir-birinin ardınca ucadan oxuyur. Dayandırmaq üçün Escape basın və ya başqa sətrə klikləyin.", + "stopSpeaking": "Səsləndirməni dayandır", + "speakingLineX": "{0} sətri səsləndirilir... (dayandırmaq üçün Escape)", + "playWithSpeechTitle": "Nitqlə oynat", + "playWithSpeechDotDotDot": "Nitqlə oynat...", + "playWithSpeechHint": "Videonu oynadır və video hər sətrə çatdıqda onu səsləndirir. Fasilə verin və həmişəki kimi keçid edin; dayandırmaq üçün Escape basın.", + "lowerVideoVolumeWhileSpeaking": "Səsləndirmə zamanı videonun səsini azalt", + "pauseVideoWhenSpeechRunsLate": "Nitq növbəti sətrə keçdikdə videonu fasiləyə al", + "waitingForSpeechLineX": "{0} sətri üçün nitq yaradılır... (dayandırmaq üçün Escape)", + "voiceKindClone": "Klonlanmış səs", + "voiceKindPreset": "Daxili", + "voiceKindModel": "Səs modeli", + "voiceKindOnline": "Onlayn", + "transcript": "Transkript", + "transcriptRequiredHint": "Tələb olunur - mühərrik klonlama zamanı yazının bu transkriptini (ref-text) oxuyur", + "transcriptOptionalHint": "İxtiyari - yazının dəqiq transkripti klonlamanı yaxşılaşdırır", + "saveTranscript": "Transkripti yadda saxla", + "searchVoices": "Səsləri axtar", + "loadingVoicesDotDotDot": "Səslər yüklənir...", + "noVoicesForEngine": "Səs yoxdur - yazı idxal edin və ya səs paketi endirin", + "noVoicesMatchFilter": "Axtarışa uyğun səs yoxdur", + "copyVoiceTo": "Səsi buraya kopyala", + "copyVoiceToDotDotDot": "Səsi mühərrikə kopyala...", + "copyingVoiceXToYDotDotDot": "'{0}' səsi {1} mühərrikinə kopyalanır...", + "voiceXCopiedToY": "'{0}' səsi {1} mühərrikinə kopyalandı", + "voiceXCouldNotBeCopiedToY": "'{0}' səsi {1} mühərrikinə kopyalana bilmədi - təfərrüatlar üçün jurnala baxın", + "voiceXAlreadyExistsInYContinue": "{1} mühərrikində '{0}' adlı səs artıq mövcuddur.\n\nYenə də kopyalansın?", + "openVoicesFolder": "Səslər qovluğunu aç", + "downloadVoicePacksDotDotDot": "Səs paketlərini endir...", + "downloadVoicePacksTitle": "TTS - Səs paketlərini endir", + "voicePackIntro": "Səs paketləri transkriptləri olan, klonlamağa hazır qısa, təmiz yazılar dəstləridir. Hər paket seçilmiş mühərrikə idxal olunur (artıq mövcud olan səslər ötürülür) və lisenziyası və mənbələri ilə birlikdə ATTRIBUTION.txt faylını ehtiva edir.", + "voicePackDetailsXVoicesYSizeZLicense": "{0} səs · {1} · {2}", + "installToEngine": "Mühərrikə quraşdır", + "downloadAndInstall": "Endir və quraşdır", + "selectAtLeastOneVoicePack": "Ən azı bir səs paketi seçin", + "downloadingXDotDotDot": "{0} endirilir...", + "installingXDotDotDot": "{0} quraşdırılır...", + "xVoicesInstalledYSkipped": "{0} səs quraşdırıldı ({1} artıq mövcud idi)", "useSpeechToTextDotDotDot": "Nitqi mətnə çevirmə funksiyasından istifadə et...", "autoCastMenuItem": "Videodakı səsləri tap və klonla...", "autoCastSpeakersTitle": "Videoda tapılan səslər", @@ -2368,7 +2780,7 @@ "cloneVoicePerLinePreparing": "Hər sətrin səsi videodan götürülür...", "cloneVoicePerLineNeedsVideo": "Hər sətrin səsini klonlamaq üçün altyazının aid olduğu video lazımdır. Videonu açın və yenidən cəhd edin.", "cloneVoicePerLineNoClips": "Videodan səs götürülə bilmədi, buna görə klonlama üçün mənbə yoxdur. Videoda səs cığırının olub olmadığını yoxlayın.", - "cloneVoicePerLineNeedsOriginalSubtitleX": "{0} yalnız həmin sətirdə videoda nə deyildiyini bildikdə sətri klonlaya bilər. Bu məlumat orijinal dildəki altyazıdan alınır. Orijinal altyazı yüklənmədiyinə görə hər sətir videodakı səslə deyil, ilk idxal edilmiş səslə oxunacaq.\n\nTərcümənin yanında orijinal altyazını açın (Fayl > Orijinalı aç...), sonra yenidən yaradın.", + "cloneVoicePerLineTranscribeClipsXYZ": "{0} sətri yalnız videoda həmin sətirdə nə deyildiyini bildikdə klonlaya bilər. Bu, {2} sətirdən {1} üçün məlum deyil - adətən orijinal dildəki altyazıdan götürülür (Fayl > Orijinalı aç...).\n\nHəmin sətirlər indi videodan nitqi mətnə çevirmə ilə transkripsiya edilsin?\n\nBəli: mühərriki və videoda danışılan dili seçin, istinad klipləri nitq yaradılmazdan əvvəl mətnə çevriləcək.\nXeyr: yenə də yarat - həmin sətirlər videodakı səslə deyil, adi səslə səsləndiriləcək.", "voiceCloneConsentTitle": "Səsin klonlanması: başlamazdan əvvəl", "voiceCloneConsentHeader": "Səsi klonlamağa hazırlaşırsınız", "voiceCloneConsentIntro": "Klonlama real insanın səsini kopyalayır. Bununla bağlı əksər yerlərdə qaydalar, Avropa İttifaqında isə sizin üzərinizə düşən hüquqi öhdəliklər var. Bu öhdəliklər Subtitle Edit üzərinə düşmür. Zəhmət olmasa, bu mətni bir dəfə oxuyun.", @@ -2385,10 +2797,29 @@ "advancedTtsSettings": "Genişləndirilmiş TTS parametrləri", "advancedTtsAudioProcessing": "Səsin emalı", "advancedTtsOutput": "Çıxış", + "advancedTtsVoiceCloning": "Səs klonlama", + "cleanCloneReferences": "Səs klonlama istinadlarını təmizlə (Sidon)", + "cleanCloneReferencesDescription": "Səsin klonlandığı audiodan musiqini, küyü və əks-sədanı çıxarır: videodan götürülmüş kliplər və səs menecerində idxal edilmiş yazılar. audio.cpp üzərində Sidon nitq bərpası modelindən istifadə edir (940 MB endirmə). Klonlanmış səs artıq fonu təkrarlamır, lakin çox sakit və ya üst-üstə düşən nitq boğuq çıxa bilər.", + "cleaningCloneReferencesDotDotDot": "Səs klonlama istinadları təmizlənir...", + "downloadSidonTitle": "Sidon endirilsin?", + "downloadSidonQuestionX": "Səs klonlama istinadlarını təmizləmək üçün Sidon nitq bərpası modeli ({0}) lazımdır.\n\nİndi endirilsin? Onsuz istinadlar olduğu kimi istifadə olunur.", "proAudioPostProcessing": "Yaradılmış səsin peşəkar emalı", "proAudioPostProcessingDescription": "Hər hissəyə EQ, küy qapısı, sıxılma, səs ucalığının normallaşdırılması (-16 LUFS), başlanğıcda qısa tədrici yüksəlmə və sonda qısa tədrici azalma tətbiq edir.", + "generatingSpeechSegmentXOfY": "Nitq yaradılır: {1} seqmentdən {0}", + "adjustingSpeedSegmentXOfY": "Sürət tənzimlənir: {1} seqmentdən {0}", + "mergingAudioSegmentXOfY": "Səs birləşdirilir: {1} seqmentdən {0}", + "postProcessingSegmentXOfY": "Sonrakı emal: {1} seqmentdən {0}", "audioDucking": "Fon səsinin azaldılması", "audioDuckingDescription": "Videonun orijinal səsini azaldır və nitqlə qarışdırır, beləliklə orijinal səs cığırı zəif eşidilir. Yalnız nitq video faylına əlavə edildikdə tətbiq olunur.", + "removeOriginalSpeech": "Orijinal nitqi çıxar (yavaş)", + "removeOriginalSpeechDescription": "Orijinal videonun musiqisini və səs effektlərini saxlayır, lakin nitqini çıxarır və yeni nitqi onun üzərinə qarışdırır. GPU ilə təxminən videonun özü qədər, GPU olmadan isə dəfələrlə çox vaxt aparır. Fon səsinin azaldılması açıq olduqda onun səs səviyyəsi musiqiyə və effektlərə tətbiq olunur. Yalnız nitq video faylına əlavə edildikdə tətbiq olunur.", + "removingOriginalSpeech": "Orijinal nitq çıxarılır...", + "removeOriginalSpeechFailed": "Orijinal nitqi çıxarmaq mümkün olmadı - nitq videoya onu çıxarmadan əlavə edildi.", + "speechRemovalModel": "Model", + "speechRemovalModelDescription": "Ən yaxşı keyfiyyət musiqinin və səs effektlərinin ən çoxunu saxlayır. Sürətli təxminən üç dəfə tezdir, lakin musiqinin bir az çoxu itir.", + "speechRemovalBestQuality": "Ən yaxşı keyfiyyət (Mel-Band RoFormer)", + "speechRemovalFast": "Sürətli (HTDemucs)", + "videoTruncatedWarning": "Yeni video faylında təsvir {0} sonra dayanır, lakin orijinal video {1} uzunluğundadır - səs davam edərkən təsvir donacaq.\n\nFayl saxlanıldı. ffmpeg çıxışı üçün Subtitle Edit məlumat qovluğundakı error-log.txt faylına baxın.", "originalVolumePercent": "Orijinal səs səviyyəsi %", "vadSilenceCompression": "VAD ilə səssizliyin qısaldılması", "vadSilenceCompressionDescription": "Nitqi sürətləndirmək əvəzinə sözlər arasındakı fasilələri qısaldır ki, səs hissəsi keyfiyyət itkisi olmadan ayrılmış müddətə sığsın.", @@ -2471,7 +2902,9 @@ "timeCodeFormatColon": "Vaxt kodunun formatı:", "shotChangesList": "Səhnə dəyişmələrinin siyahısı", "shotChangesClearQuestion": "Bütün səhnə dəyişmələrini təmizləmək istədiyinizə əminsiniz?", - "deleteSelectedShotChangeQuestion": "Seçilmiş səhnə dəyişməsini silmək istədiyinizə əminsiniz?" + "deleteSelectedShotChangeQuestion": "Seçilmiş səhnə dəyişməsini silmək istədiyinizə əminsiniz?", + "shotChanges": "Səhnə dəyişiklikləri", + "exportShotChanges": "Səhnə dəyişikliklərini ixrac et" }, "chapters": { "title": "Bölmələr", @@ -2501,7 +2934,6 @@ "scaleTimesDescription": "Hər bölmənin vaxtını bir kadr sürətindən digərinə uyğunlaşdır.", "fromFrameRate": "İlkin", "toFrameRate": "Hədəf", - "apply": "Tətbiq et", "writeToVideo": "Videoya yaz...", "writeToVideoTitle": "Bölmələri videoya yaz", "writeToVideoDescription": "Bölmələr video faylının nüsxəsinə yazılır. Yenidən kodlaşdırma aparılmadığı üçün əməliyyat sürətli və itkisizdir.", @@ -2511,7 +2943,6 @@ "writtenToX": "Bölmələrin yazıldığı fayl: {0}", "unableToWriteChapters": "Bölmələri video faylına yazmaq mümkün olmadı.", "chaptersSavedToX": "Bölmələrin yadda saxlandığı fayl: {0}", - "noVideoLoaded": "Video yüklənməyib", "emptyListCallToAction": "Hələ fəsil yoxdur. Videonun cari mövqeyində fəsil əlavə edin və ya videodan, yaxud fayldan idxal edin." }, "videoOcr": { @@ -2536,10 +2967,10 @@ "runningOcrXY": "OCR icra olunur... {0}/{1}", "noLinesFoundTitle": "Altyazı tapılmadı", "noLinesFoundMessage": "Skan sahəsində mətn tapılmadı. Skan sahəsini, mühərriki və ya minimum parlaqlığı dəyişməyə çalışın.", + "ocrStoppedEarlyMessage": "OCR mühərriki bütün kadrlar oxunmazdan əvvəl dayandı, buna görə indiyədək tapılan sətirlər saxlanılır, qalanları isə çatışmır.{0}{0}{1}", "linesFoundX": "{0} sətir tapıldı", "refiningTimingXY": "Vaxtlar dəqiqləşdirilir... {0}/{1}", "fixOcrErrors": "OCR xətalarını düzəlt", - "dictionary": "Lüğət", "editLineX": "{0} nömrəli sətri redaktə et", "downloadDictionaryHint": "Orfoqrafiya yoxlaması üçün lüğət endir...", "framesPerSecondHint": "Saniyədə neçə kadrın skan edildiyini göstərir. Adi altyazılar üçün 5 kifayətdir. Daha yüksək qiymət çox qısa sətirləri aşkar etməyə və qruplaşdırmanı dəqiqləşdirməyə imkan verir, lakin OCR daha çox vaxt aparır.", @@ -2556,7 +2987,58 @@ "testOcr": "Cari kadrı sına", "testOcrRunning": "Cari kadrda OCR sınanır...", "testOcrResultX": "Sınaq nəticəsi: {0}", - "testOcrNoTextFound": "Sınaq: skan sahəsində mətn tapılmadı" + "testOcrNoTextFound": "Sınaq: skan sahəsində mətn tapılmadı", + "engineSettings": "OCR mühərrikinin parametrləri", + "engineBuiltIn": "Əməliyyat sisteminə daxildir", + "engineExternalServer": "Xarici server (Subtitle Edit xaricində quraşdırılır və idarə olunur)", + "engineCloudApi": "Bulud API (API açarı tələb olunur)", + "engineNothingToInstall": "Quraşdırılacaq heç nə yoxdur", + "engineModelsMissing": "Mühərrik quraşdırılıb, modellər çatışmır", + "website": "Veb sayt" + }, + "backgroundMusic": { + "title": "Fon musiqisi yarat", + "generateBackgroundMusicDotDotDot": "Fon musiqisi yarat...", + "preset": "Hazır parametr dəsti", + "prompt": "Təlimat", + "tempoBpm": "Temp (BPM)", + "generateSeconds": "Yarat (saniyə)", + "seed": "Seed", + "randomSeed": "Təsadüfi", + "outputLength": "Çıxışın uzunluğu", + "outputLengthVideoX": "{0} (videonun uzunluğu - musiqi fasiləsiz təkrarlanır)", + "musicVolumePercent": "Musiqinin səs səviyyəsi (%)", + "removeExistingAudioTracks": "Mövcud səs treklərini çıxar", + "originalAudioVolumePercent": "Orijinal səsin səviyyəsi (%)", + "saveAudioDotDotDot": "Səsi yadda saxla...", + "addToVideoDotDotDot": "Videoya əlavə et...", + "engineInfo": "audio.cpp vasitəsilə ACE-Step 1.5 Turbo (MIT lisenziyası) - lokal işləyir", + "presetCooking": "Yemək bişirmə / çörək bişirmə", + "presetTech": "Texnologiya / qadcet icmalı", + "presetForest": "Meşə / təbiət", + "presetRelaxingPiano": "Rahatladıcı piano", + "presetLofiVlog": "Lo-fi vloq", + "presetCorporate": "Korporativ / izahedici", + "loadingModel": "Model yüklənir...", + "composing": "Bəstələnir...", + "generatingAudio": "Səs yaradılır...", + "decodingAudio": "Səs dekodlaşdırılır...", + "creatingLoop": "Fasiləsiz təkrar yaradılır...", + "resultLoopXYZ": "Təkrar: {0}, {1} BPM, birləşmə uyğunluğu {2}%", + "resultNoLoopX": "Musiqi hazırdır: {0}", + "addingMusicToVideo": "Musiqi videoya əlavə edilir...", + "downloadModelTitle": "Musiqi modeli endirilsin?", + "downloadModelQuestionX": "Musiqi yaratmaq üçün ACE-Step 1.5 modeli ({0}) tələb olunur.\n\nModel endirilsin?", + "downloadingModel": "ACE-Step 1.5 modeli endirilir...", + "saveAudioTitle": "Fon musiqisini yadda saxla", + "audioFileSavedX": "Səs faylı yadda saxlanıldı: {0}", + "unableToGenerateMusic": "Musiqi yaratmaq mümkün olmadı", + "unableToAddMusicToVideo": "Musiqini videoya əlavə etmək mümkün olmadı", + "outputLengthTextToSpeech": "Yaradılmış nitqə uyğunlaşır - musiqi fasiləsiz təkrarlanır və kimsə danışarkən səsi azalır", + "addBackgroundMusic": "Fon musiqisi əlavə et", + "addBackgroundMusicHint": "ACE-Step 1.5 ilə lokal olaraq musiqi yarat və onu nitqin altına qarışdır - kimsə danışarkən səsi azalır", + "generatingBackgroundMusicDotDotDot": "Fon musiqisi yaradılır...", + "mixingBackgroundMusicDotDotDot": "Fon musiqisi qarışdırılır..." }, "goToVideoPosition": "Video mövqeyinə keç", "goToVideoPositionDotDotDot": "Video mövqeyinə keç...", @@ -2567,10 +3049,47 @@ "openSecondarySubtitleOnVideoPlayerDotDotDot": "İkinci altyazı faylını aç...", "openSecondarySubtitleOnVideoPlayer": "İkinci altyazı faylı (video pleyerdə)", "removeSecondarySubtitleOnVideoPlayer": "İkinci altyazı faylını çıxar", + "editSecondarySubtitleSettings": "İkinci altyazının parametrlərini redaktə et", + "editSecondarySubtitleSettingsDotDotDot": "İkinci altyazının parametrlərini redaktə et...", + "secondarySubtitleRememberSettings": "Bu parametrləri yadda saxla", + "secondarySubtitleDoNotShowAgain": "Bu dialoqu bir daha göstərmə", + "secondarySubtitleJustifyLines": "Sətirləri hər iki kənara düzləndir", "openRecentVideo": "Son açılan videonu aç", "clearRecentVideos": "Son açılan videoların siyahısını təmizlə", "cutVideoTitle": "Videonu kəs", "cutVideoDotDotDot": "Videonu kəs...", + "remuxVideoTitle": "Videonu yenidən paketlə (remux)", + "remuxVideoDotDotDot": "Videonu yenidən paketlə (remux)...", + "remuxVideoInputVideo": "Giriş videosu:", + "remuxVideoMultipleTracksRequiresMkv": "Bir neçə səs və ya altyazı treki MKV konteyneri tələb edir. Çıxış formatı avtomatik olaraq .mkv-yə dəyişdirildi.", + "remuxVideoOutputFormat": "Çıxış formatı:", + "remuxVideoOutputFile": "Çıxış faylı:", + "remuxVideoRemuxing": "Video və səs yenidən paketlənir...", + "remuxVideoFinalizing": "Çıxış faylı yekunlaşdırılır (indeks əvvələ köçürülür)...", + "remuxVideoCompleted": "Video uğurla yenidən paketləndi.", + "remuxVideoFailed": "Videonu yenidən paketləmək mümkün olmadı.", + "remuxVideoPleaseSelectBoth": "Həm video, həm də səs fayllarını seçin.", + "remuxVideoAssRequiresMkv": "ASS/SSA altyazıları bütün formatlaşdırmanı və üslubları qorumaq üçün MKV konteyneri tələb edir.", + "remuxVideoSccRequiresMov": "Scenarist (.scc) altyazıları MOV və ya MPG konteyneri tələb edən CEA-608 qapalı titrlər kimi daxil edilir. Çıxış formatı avtomatik olaraq .mov-a dəyişdirildi.", + "remuxVideoSccNotWithWebM": "Scenarist (.scc) qapalı titrləri .webm faylının VP9/AV1 videosunu saxlaya bilməyən MOV konteyneri tələb edir. .scc faylını çıxarın və ya onu başqa altyazı formatına çevirin.", + "remuxVideoMpgMaxTwoCaptionTracks": ".mpg faylı ən çoxu iki qapalı titr treki (CC1 və CC3) saxlaya bilər - bir və ya iki qalması üçün altyazı fayllarını çıxarın.", + "remuxVideoMccRequiresMpg": "MacCaption (.mcc) altyazıları MPEG-2 videoda CEA-608/708 qapalı titrlər kimi daxil edilir, bu da MPG konteyneri tələb edir. Çıxış formatı avtomatik olaraq .mpg-yə dəyişdirildi.", + "remuxVideoMpgReencodeVideoX": ".mpg faylındakı qapalı titrlər MPEG-2 video tələb edir, video isə {0}-dir. Video MPEG-2-yə yenidən kodlaşdırılsın? Bu daha uzun çəkir və keyfiyyəti bir az aşağı salır.", + "remuxVideoFileHasNoAudioX": "\"{0}\" faylında səs treki yoxdur, buna görə səs mənbəyi kimi istifadə oluna bilməz.", + "remuxVideoAddingClosedCaptions": "Qapalı titrlər əlavə edilir...", + "remuxVideoSelectAudioTrack": "Səs trekini seç", + "remuxVideoSelectAudioTrackFor": "{0} üçün səs trekini seç", + "remuxVideoSelectAudioTrackPrompt": "'{0}' faylında {1} səs treki var. Yenidən paketləmək üçün səs trekini seçin:", + "remuxVideoVideoFile": "video", + "remuxVideoAudioFile": "səs", + "remuxVideoAudioFiles": "Səs faylları:", + "remuxVideoSubtitleFiles": "Altyazı faylları (ixtiyari, yumşaq altyazılar):", + "remuxVideoSelectAudioTrackDotDotDot": "Səs trekini seç...", + "remuxVideoFilesX": "{0} fayl", + "remuxVideoMixAudio": "Səsi bir trekə qarışdır", + "remuxVideoVolumePercent": "Səs səviyyəsi %:", + "remuxVideoVolumeX": "Səs səviyyəsi {0}%", + "remuxVideoFastStart": "Veb yayım üçün optimallaşdır (indeksi əvvələ köçür - daha yavaş)", "embedSubtitlesDotDotDot": "Daxili altyazıları əlavə et/çıxar...", "generateTimeCodes": "Vaxt kodları yarat", "checkeredImage": "Damalı şəkil", @@ -2580,6 +3099,34 @@ "cutVideoSplitSegments": "Hissələri ayrıca yadda saxla", "cutVideoAlsoCutSubtitle": "Altyazını da kəs", "cutVideoSubtitleFileGeneratedX": "Altyazı faylı yaradıldı: {0}", + "cutVideoTransition": "Hissələr arasında keçid", + "cutVideoTransitionDurationSeconds": "Müddət (saniyə)", + "cutVideoFadeInSeconds": "Əvvəldə qaradan tədricən görünmə (saniyə)", + "cutVideoFadeOutSeconds": "Sonda tədricən qaralma (saniyə)", + "cutVideoPreviewTransition": "Keçidə ön baxış", + "cutVideoPreviewTransitionTitle": "Keçidə ön baxış", + "cutVideoPreviewNeedsJoin": "Ön baxış üçün birləşmə yoxdur - birləşdirmək üçün ən azı iki hissə və ya ortadan kəsiləcək bir hissə əlavə edin.", + "cutVideoTransitionInfoX": "Hər keçid birləşdirdiyi hissələri üst-üstə salır, buna görə çıxış hər birləşmədə {0} saniyə qısalır. Kəsilmiş altyazı buna uyğun yenidən vaxtlanır.", + "cutVideoTransitionFade": "Tədricən keçid", + "cutVideoTransitionFadeBlack": "Qara vasitəsilə keçid", + "cutVideoTransitionFadeWhite": "Ağ vasitəsilə keçid", + "cutVideoTransitionFadeGrays": "Boz vasitəsilə keçid", + "cutVideoTransitionDissolve": "Əriyərək keçid", + "cutVideoTransitionWipeLeft": "Sola silinmə", + "cutVideoTransitionWipeRight": "Sağa silinmə", + "cutVideoTransitionWipeUp": "Yuxarı silinmə", + "cutVideoTransitionWipeDown": "Aşağı silinmə", + "cutVideoTransitionSlideLeft": "Sola sürüşmə", + "cutVideoTransitionSlideRight": "Sağa sürüşmə", + "cutVideoTransitionSlideUp": "Yuxarı sürüşmə", + "cutVideoTransitionSlideDown": "Aşağı sürüşmə", + "cutVideoTransitionSmoothLeft": "Sola hamar keçid", + "cutVideoTransitionSmoothRight": "Sağa hamar keçid", + "cutVideoTransitionCircleOpen": "Dairəvi açılma", + "cutVideoTransitionCircleClose": "Dairəvi bağlanma", + "cutVideoTransitionRadial": "Radial", + "cutVideoTransitionPixelize": "Pikselləşmə", + "cutVideoTransitionBlur": "Bulanıqlaşma", "importCurrentSubtitle": "Cari altyazını idxal et", "addRemoveEmbeddedSubtitlesTitle": "Daxili altyazıları əlavə et/çıxar", "addRemoveEmbeddedSubtitlesMp4Title": "Daxili altyazıları əlavə et/çıxar (MP4)", @@ -2595,6 +3142,7 @@ "embeddedTrackGeneratingVideoXY": "Video yaradılır... {0}% {1}", "embeddedTrackGeneratingVideo": "Video yaradılır...", "mp4FilesFilter": "MP4 faylları", + "embeddedTracksPickVideoHint": "Altyazı treklərini görmək üçün video faylı seçin və ya onu bura sürükləyin.", "viewMatroskaTrackX": "Matroska cığırına bax: {0}", "resolutionSeparator": "x", "openFromUrlTitle": "URL vasitəsilə video faylı aç", @@ -2641,12 +3189,15 @@ "reEncodeGeneratingDone": "Yaradılma tamamlandı", "reEncodeGeneratedFilesX": "Yaradılmış fayllar ({0}):", "reEncodeFfmpegParameters": "FFmpeg parametrləri", + "analyzingVideoX": "Video təhlil edilir... {0}% {1}", + "analyzingVideoXofY": "Video təhlil edilir {0}/{1}... {2}% {3}", + "backgroundImageFileNotSelected": "Fon şəkli faylı seçilməyib", + "backgroundImageFileDoesNotExistX": "Fon şəkli faylı mövcud deyil: {0}", "voices": "Səslər", "presets": "Hazır parametr dəstləri", "talkerX": "Danışan {0}", "baseModel": "Əsas model", "turboModel": "Turbo model", - "ttsUpdateX": "Yenilə: {0}", "ttsEngineNotInstalled": "{0} quraşdırılmayıb", "ttsEngineUpdateAvailable": "{0}: yeniləmə mövcuddur", "ttsEngineTooOldUpdateRequired": "{0} çox köhnədir, yeniləmə tələb olunur", @@ -2669,9 +3220,17 @@ "cloneVoiceTo": "Səsi buraya klonla", "cloneVoiceNameTitle": "Yeni səsin adı", "cloneVoiceExtractFailed": "Seçilmiş sətir üçün səs çıxarılmadı.", + "lineXHasNoDuration": "{0} sətrinin müddəti yoxdur (son vaxt başlanğıc vaxtından sonra deyil), buna görə çıxarılacaq səs yoxdur.", + "audioClipsSkippedLinesX": "Bu sətirlər üçün səs çıxarmaq mümkün olmadı, buna görə onlar ötürüldü: {0}", "toggleShotChange": "Səhnə dəyişikliyini əlavə et/çıxar", "resetWaveformZoomAndSpeed": "Səs dalğası qrafikinin miqyasını və sürətini sıfırla", "showOriginalSubtitle": "Orijinal altyazını göstər", + "timelineGroupTracksBy": "Altyazı treklərini qruplaşdır", + "timelineShowThumbnails": "Video miniatürlərini göstər", + "showSpeechOnly": "Yalnız nitqi göstər (yaradılması yavaşdır)", + "isolatingSpeechForWaveformX": "Səs dalğası üçün nitq ayrılır... {0}", + "isolatingSpeechForWaveformFailed": "Səs dalğası üçün nitqi ayırmaq mümkün olmadı - alətlər jurnalına baxın", + "speechOnlyWaveformReady": "Səs dalğası indi yalnız nitqi göstərir", "showOnlyWaveform": "Yalnız səs dalğası qrafikini göstər", "showOnlySpectrogram": "Yalnız spektroqramı göstər", "showWaveformAndSpectrogram": "Səs dalğası qrafikini və spektroqramı göstər", @@ -2704,6 +3263,7 @@ "adjustSelectedLinesAndForward": "Seçilmiş və sonrakı sətirləri tənzimlə", "toFrameRate": "Hədəf kadr sürəti", "fromFrameRate": "Başlanğıc kadr sürəti", + "videoXFrameRateY": "{0} - {1} kadr/san", "adjustAllTimes": "Bütün vaxtları tənzimlə (daha tez/gec göstər)", "showEarlier": "Daha tez göstər", "showLater": "Daha gec göstər", @@ -2713,7 +3273,8 @@ "syncPoints": "Sinxronlaşdırma nöqtələri", "pointSync": "Nöqtələr üzrə sinxronlaşdırma", "pointSyncViaOther": "Başqa altyazı vasitəsilə nöqtələr üzrə sinxronlaşdırma", - "syncPointCandidateInfo": "{0}+ saniyəlik səssizlikdən sonra başlayan sətirlər vurğulanır. Onlar çox vaxt etibarlı sinxronlaşdırma nöqtələri olur", + "syncPointCandidateAfterInfo": "Ardınca {0}+ saniyə sükut gələn sətirlər vurğulanır - onlar çox vaxt etibarlı sinxronlaşdırma nöqtələri olur", + "gapAfter": "Sonrakı boşluq", "adjustAllShortcuts": "Klaviatura qısayolları:\n\n• Shift + sol/sağ: 10 ms sürüşdür\n• Ctrl + sol/sağ: 100 ms sürüşdür\n• Alt + sol/sağ: 500 ms sürüşdür", "adjustAllShortcutsFrames": "Klaviatura qısayolları:\n\n• Shift + sol/sağ: 1 kadr sürüşdür\n• Ctrl + sol/sağ: 10 kadr sürüşdür\n• Alt + sol/sağ: 1 saniyə sürüşdür", "offsetInSeconds": "Saniyə ilə sürüşmə", @@ -2733,6 +3294,7 @@ "lineMerge": "Sətirlərin birləşdirilməsi", "delayInSecondsBetweenRequests": "Sorğular arasında saniyə ilə gecikmə", "maxBytesPerRequest": "Hər sorğu üçün maksimum bayt sayı", + "keepMusicLinesUntranslated": "Musiqi simvolları (♪ ... ♪) içindəki sətirləri tərcümə etmə", "promptText": "Təlimat mətni", "resetPromptToDefault": "Təlimatı standart mətnə qaytar", "translateEachLineSeparately": "Hər sətri ayrıca tərcümə et", @@ -2746,11 +3308,10 @@ "readyToTranslate": "Tərcüməyə hazırdır", "translating": "Tərcümə edilir...", "translationComplete": "Tərcümə tamamlandı", + "translateInPlaceNoOriginal": "Yerində tərcümə et (orijinal yaratma)", "translationCancelled": "Tərcümə ləğv edildi", "swapLanguages": "Mənbə və hədəf dillərinin yerini dəyiş", "xIsAlreadyDownloadedReDownload": "{0} artıq endirilib. Yenidən endirilsin?", - "advancedDotDotDot": "Əlavə...", - "advancedSettings": "Əlavə parametrlər", "advancedSettingsSubtitle": "Təkmilləşdirilmiş llama.cpp mühərriki üçün toplu emal, kontekst və nümunə seçmə parametrləri", "context": "Kontekst", "synopsis": "Xülasə", @@ -2774,6 +3335,7 @@ "useOnlyExtraServerParametersHint": "llama-server mühərrikini Subtitle Edit proqramının öz parametrləri əvəzinə yuxarıdakı parametrlərlə başlat. Yalnız model, şəbəkə ünvanı və port əvvəlki qaydada təyin olunur", "serverRunningAtX": "Server {0} ünvanında işləyir", "customPromptHint": "Fərdi təlimatlar ({0} = mənbə dili, {1} = hədəf dil); boş = daxili təlimat", + "editTranslationHint": "Redaktə etmək üçün seçilmiş sıranın tərcüməsinə klikləyin (Enter yadda saxlayır, Escape ləğv edir)", "llamaCppDownloadEngineAndModelPrompt": "llama.cpp üçün llama-server mühərriki və tərcümə modeli endirilməlidir. İndi endirilsin?", "llamaCppDownloadEnginePrompt": "llama.cpp üçün llama-server mühərriki endirilməlidir. İndi endirilsin?", "llamaCppDownloadModelPrompt": "llama.cpp üçün seçilmiş tərcümə modeli endirilməlidir. İndi endirilsin?" @@ -2844,6 +3406,7 @@ "showGridLines": "Cədvəl xətlərini göstər", "showHorizontalLineAboveToolbar": "Alətlər panelinin üstündə üfüqi xətt göstər", "bookmarkColor": "Əlfəcin rəngi", + "spellCheckHighlightColor": "Orfoqrafiya yoxlamasında vurğulama rəngi", "singleLineMaxLength": "Bir sətrin maksimum uzunluğu", "optimalCharsPerSec": "Saniyədə optimal simvol sayı", "maxCharsPerSec": "Saniyədə maksimum simvol sayı", @@ -2868,13 +3431,21 @@ "useDoNotBreakAfterList": "Ardınca sətir bölünməyən sözlər siyahısından istifadə et", "newEmptyDefaultMs": "Yeni altyazının standart müddəti (ms)", "timeCodeUpDownStepMs": "Vaxtı artırma/azaltma addımı (ms)", + "moveSelectedLinesStepMs": "Seçilmiş sətirləri sürüşdürmə düymə birləşməsinin addımı (ms)", + "moveStartEndStepMs": "Başlanğıcı/sonu sürüşdürmə düymə birləşməsinin addımı (ms)", + "moveLinesShortenNeighbor": "Sətirləri sürüşdürmə: əvvəlki/sonrakı sətirlə üst-üstə düşmək əvəzinə onu qısalt", "promptBeforeDelete": "Silməzdən əvvəl soruş", "rememberPositionAndSize": "Pəncərənin mövqeyini və ölçüsünü xatırla", + "titleBarFullFileName": "Başlıq panelində faylın tam yolunu göstər", + "showRecentFiles": "Son faylları göstər (yenidən açmaq üçün)", "openLastFileOnStart": "Başladıqda son istifadə olunan faylı aç", "autoSave": "Avtomatik yadda saxla (redaktə zamanı açıq faylı yadda saxla)", "autoBackupOn": "Avtomatik ehtiyat nüsxələmə", "autoBackupIntervalMinutes": "Avtomatik ehtiyat nüsxələmə intervalı (dəqiqə)", "autoBackupDeleteAfterDays": "Avtomatik ehtiyat nüsxələrin saxlanma müddəti (gün)", + "settingsBackupOn": "Parametrlərin avtomatik ehtiyat nüsxəsi", + "settingsBackupIntervalDays": "Parametrlərin ehtiyat nüsxə intervalı (gün, 0 = hər başlanğıcda)", + "settingsBackupMaxCount": "Saxlanılacaq parametr ehtiyat nüsxələri", "autoConvertToUtf8": "Açarkən kodlaşdırmanı avtomatik UTF-8 formatına çevir", "autoTrimWhiteSpace": "Kənar boşluqları avtomatik təmizlə", "removeBlankLinesWhenOpening": "Altyazını açarkən boş sətirləri çıxar", @@ -2891,7 +3462,10 @@ "colorGapTooShort": "Ara çox qısadırsa, rənglə göstər", "errorBackgroundColor": "Xətanın fon rəngi", "waveformDrawGridLines": "Şəbəkə xətlərini çək", + "waveformUseSkiaRenderer": "Eksperimental sürətli renderer istifadə et", "waveformCenterVideoPositionAlsoWhenPaused": "Video mövqeyini fasilə zamanı da mərkəzdə saxla", + "waveformSelectCurrentSubtitleWhilePaused": "Fasilə zamanı da cari altyazını seç", + "waveformRememberZoom": "Miqyası yadda saxla (üfüqi və şaquli)", "waveformShowToolbar": "Alətlər panelini göstər", "waveformShowToolbarEdit": "Alətlər panelini redaktə et...", "waveformShowToolbarEditLabel": "Alətlər panelinin elementləri", @@ -2939,18 +3513,25 @@ "webVttUseXTimestampMap": "WebVTT: X-TIMESTAMP-MAP sahəsindən istifadə et (yükləyərkən vaxt kodlarını sürüşdür)", "assaAutoSetResolution": "ASSA: video açıldıqda ayırdetməni (PlayResX/PlayResY) videoya əsasən təyin et", "assaAutoSetResolutionPrompt": "ASSA: ayırdetməni videoya uyğunlaşdırmazdan əvvəl soruş (əks halda avtomatik yenidən miqyaslandır)", - "showStopButton": "Dayandırma düyməsini göstər", - "showFullscreenButton": "Tam ekran düyməsini göstər", + "videoControls": "Video idarəetmə elementləri", + "videoControlsEdit": "Video idarəetmə elementlərini redaktə et...", + "videoControlsPositionText": "Mövqe / müddət mətni", + "videoControlsVideoFileName": "Video faylının adı", + "videoControlsPlayerName": "Video pleyerin adı", "fullscreenHideControls": "Tam ekranda video idarələrini gizlət", + "showSecondarySubtitleDialog": "İkinci altyazı faylını açarkən üslub dialoqunu göstər", + "rememberSecondarySubtitleFile": "İkinci altyazı faylını yadda saxla", "autoOpenVideoFile": "Altyazını açarkən video faylını avtomatik aç", "downloadMpv": "MPV proqramını endir", "downloadVlc": "VLC proqramını endir", "allowSingleLetterShortcutsInTextbox": "Mətn xanasında tək hərfli qısayollara icazə ver", + "allowTextNavigationShortcutsInTextbox": "Mətn xanasında mətn naviqasiyası düymələrində (Ctrl+Left/Right, Home/End) düymə birləşmələrinə icazə ver", "spellCheckEnglishTreatInApostropheAsIng": "Yazılış yoxlaması: 'in'' ilə bitən sözləri 'ing' kimi qəbul et (yalnız ingilis dili)", "goToLineNumberSetsVideoPosition": "Sətir nömrəsinə keçərkən video mövqeyini də təyin et", "adjustAllTimesRememberLineSelectionChoice": "Bütün vaxtları tənzimləyərkən sətir seçimi tərcihini xatırla", "mergeKeepEndTime": "Sətirləri birləşdir: bitmə vaxtını saxla (növbəti altyazı ilə vaxtların kəsişməsinə icazə ver)", "mergeKeepEndTimeOnlyAssa": "Sətirləri birləşdir: bitmə vaxtını yalnız \"Advanced Sub Station Alpha\" üçün saxla", + "mergeAutoBreak": "Sətirləri birləşdirmə: birləşdirilmiş mətni avtomatik sətirlərə böl (söndürüldükdə sətirlərin öz sətir keçidləri saxlanılır)", "filesAndLogs": "Fayllar və jurnallar", "showErrorLogFile": "Xəta jurnalı faylını göstər", "showToolsLogFile": "Alətlər jurnalı faylını göstər", @@ -2959,6 +3540,7 @@ "showAssaLayer": "ASSA qat xanasını göstər", "waveformCursorColor": "Səs dalğası kursorunun/oxutma göstəricisinin rəngi", "waveformShotChangeColor": "Səs dalğasında səhnə dəyişməsinin rəngi", + "waveformGridColor": "Səs dalğası şəbəkəsinin rəngi", "waveformParagraphLeftColor": "Səs dalğasının sol sərhəd rəngi", "waveformParagraphRightColor": "Səs dalğasının sağ sərhəd rəngi", "waveformFancyHighColor": "Səs dalğasının bəzəkli görünüşündə yüksək səviyyə rəngi", @@ -2986,10 +3568,15 @@ "resetSyntaxColoring": "Sintaksis rənglərini sıfırla", "resetWaveform": "Səs dalğasını sıfırla", "resetRules": "Qaydaları sıfırla", - "useFrameMode": "Kadr rejimindən istifadə et (hh.mm.ss.ff)", + "timeCodeMode": "Vaxt kodu rejimi", + "timeCodeModeTime": "Vaxt (hh:mm:ss,zzz)", + "timeCodeModeFrames": "Kadrlar (hh:mm:ss:ff)", + "timeCodeModeFrameNumbers": "Kadr nömrələri", + "frameModeSnapToVideoFrames": "Vaxtları video kadrlarında saxla (hər saniyə daxilindəki kadrlarda deyil)", "textBoxLimitNewLines": "Altyazı mətn xanasında sətir sayını məhdudlaşdır", "mpvOpenGl": "libmpv: OpenGL", "mpvSoftwareRendering": "libmpv: proqram vasitəsilə görüntüləmə (yavaş)", + "mpvMetalRendering": "libmpv - Metal (eksperimental, yenidən başlatma tələb olunur)", "mpvWidRendering": "libmpv: sistem pəncərəsinin ID dəyəri vasitəsilə görüntüləmə", "waveFormsAndSpectrogramFoldersContainsX": "Səs dalğası və spektroqram qovluqlarında {0} var", "deleteWaveformAndSpectrogramFoldersQuestion": "Səs dalğası və spektroqram faylları silinsin?", @@ -3002,7 +3589,10 @@ "waveformExtractAudioSampleRate": "Çıxarılan səsin nümunələmə tezliyi", "waveformExtractAudioSampleRateOriginal": "Orijinal (mənbəni saxla)", "waveformExtractAudioBitRate": "Çıxarılan səsin bit sürəti (MP3/M4A)", + "ffmpegUseCenterChannelOnly": "5.1 səs üçün yalnız ön mərkəzi kanaldan istifadə et (FFmpeg)", "vlcWidRendering": "libVLC: sistem pəncərəsinin ID dəyəri vasitəsilə görüntüləmə", + "ffmpegSoftwareRendering": "FFmpeg", + "downloadFfmpegLibs": "FFmpeg kitabxanalarını endir (FFmpeg video pleyeri üçün)", "subtitleGridEnterKeyAction": "Altyazı cədvəlində Enter düyməsinin əməliyyatı", "subtitleSingleClickAction": "Altyazı cədvəlində tək klik əməliyyatı", "subtitleDoubleClickAction": "Altyazı cədvəlində ikiqat klik əməliyyatı", @@ -3030,6 +3620,7 @@ "subtitleGridFormattingShowFormatting": "Formatlamanı göstər", "subtitleGridFormattingShowTags": "Etiketləri göstər", "subtitleGridFormattingHideTags": "Etiketləri gizlət", + "subtitleGridFormattingShowFormattingKeepTags": "Formatlaşdırmanı göstər, görünməyən etiketləri saxla", "waveformParagraphBackgroundColor": "Səs dalğasında altyazının fon rəngi", "waveformParagraphSelectedBackgroundColor": "Səs dalğasında seçilmiş altyazının fon rəngi", "waveformAllowOverlap": "Vaxtların kəsişməsinə icazə ver (yerini/ölçüsünü dəyişərkən)", @@ -3045,9 +3636,12 @@ "splitOddLinesAction": "Sətir sayı tək olduqda bölmə əməliyyatı", "ocrUseWordSplitList": "OCR: söz bölmə siyahısından istifadə et", "ocrGuessUnknownWords": "OCR: naməlum sözləri təxmin etməyə çalış", + "spellCheckRememberUseAlwaysList": "Orfoqrafiya yoxlaması: \"Həmişə istifadə et\" siyahısını yadda saxla", + "fixShortDisplayTimesAllowMoveStartTime": "Yayılmış xətaları düzəlt: qısa nümayiş vaxtlarının düzəldilməsi başlanğıc vaxtını sürüşdürə bilər", "speechToTextSelectedLinesPromptFirstTimeOnly": "Nitqi mətnə çevirmə: seçilmiş sətirlər üçün dili/mühərriki yalnız ilk dəfə soruş", "multipleReplaceShowDotDotDotButtons": "Çoxsaylı əvəzetmə: kontekst menyusu düymələrini göstər", "gridFocusTextboxAfterInsertNew": "Cədvəl: yeni altyazı əlavə etdikdən sonra mətn xanasına fokuslan", + "undoRedoGoToChangedLine": "Geri al/yenidən et: dəyişdirilmiş sətrə (və video mövqeyinə) keç", "musicSymbol": "Musiqi simvolu", "musicSymbolsToReplace": "Əvəz ediləcək musiqi simvolları (vergüllə ayrılır)", "textToSpeechPromptMergeContinuationLines": "Mətni nitqə çevirmə: davam sətirlərini birləşdirmək üçün soruş", @@ -3056,6 +3650,7 @@ "useFocusedButtonBackgroundColor": "Fokuslanmış düymənin fon rəngindən istifadə et", "focusedButtonBackgroundColor": "Fokuslanmış düymənin fon rəngi", "forceCrLfOnSave": "Yadda saxlayarkən CR+LF istifadəsini məcburi et (mətn altyazı faylları)", + "linuxClipboardUseExternalTool": "Mətni xclip/wl-copy vasitəsilə kopyala (diakritik işarəli simvolları saxlayır)", "showFormatLimitWarning": "Yadda saxlayarkən sətirlər formatın hədlərini keçirsə, xəbərdar et (məsələn, SCC üçün sətirdə 32 simvol)", "textBoxButtonShowAutoBreak": "Mətn xanası: avtomatik sətir bölmə düyməsini göstər", "textBoxButtonShowUnbreak": "Mətn xanası: sətir bölünmələrini ləğv etmə düyməsini göstər", @@ -3067,12 +3662,18 @@ "waveformDoubleClickAction": "Səs dalğasında ikiqat klik əməliyyatı (tək klik əməliyyatından sonra)", "allSettings": "Bütün parametrlər", "uiScale": "İnterfeys miqyası (%)", + "fontScale": "Şrift miqyası (%)", + "textSelectionOpacity": "Mətn seçiminin qeyri-şəffaflığı (%)", "waveformToolbarItems": "Səs dalğası alətlər panelinin elementləri", "matchIconColorToDarkTheme": "Nişan rəngini qaranlıq temanın ön plan rənginə uyğunlaşdır", "subtitlePreviewProperties": "Altyazı önbaxışının xüsusiyyətləri", "usePositionFromSubtitleFile": "Altyazı faylındakı mövqedən istifadə et (TTML/PAC/EBU STL)", "marginIsPartOfSubtitleArea": "Kənar boşluq altyazı sahəsinə daxildir", "textJustify": "Sətirləri hər iki kənara düzləndir", + "deinterlace": "Videonun hörülməsini aradan qaldır (deinterlace)", + "deinterlaceOff": "Söndürülüb", + "deinterlaceAuto": "Avtomatik (yalnız hörülmüş kadrlar)", + "deinterlaceAlways": "Həmişə", "pixelWidthInfo": "Yaşıl xətlər = maksimum en həddi | Qırmızı sahə = mətn həddi keçir", "spellCheckEngineHunSpelll": "Hunspell", "spellCheckEngineMsWord": "MS Word", @@ -3081,7 +3682,16 @@ "waveformColorTheme": "Rəng teması:", "waveformSaveAsCustomTheme": "Fərdi tema kimi yadda saxla...", "waveformLoadThemeDotDotDot": "Temanı yüklə...", - "waveformExportThemeDotDotDot": "Temanı ixrac et..." + "waveformExportThemeDotDotDot": "Temanı ixrac et...", + "waveformSaveCustomThemeTitle": "Xüsusi temanı yadda saxla", + "waveformThemeFileType": "Subtitle Edit səs dalğası teması", + "waveformThemeHighContrast": "Yüksək kontrast", + "waveformThemeOceanBlue": "Okean mavisi", + "waveformThemeWarmSunset": "İsti qürub", + "waveformThemeForest": "Meşə", + "waveformThemeMidnightPurple": "Gecə yarısı bənövşəyi", + "waveformThemeRetroAmber": "Retro kəhrəba", + "waveformThemeIce": "Buz" }, "shortcuts": { "searchShortcuts": "Qısayollar üzrə axtarış...", @@ -3092,11 +3702,34 @@ "categoryVideo": "Video", "categorySync": "Sinxronlaşdırma", "categoryAi": "Süni intellekt", + "categoryCustom": "Xüsusi", + "editCustomShortcut": "Xüsusi düymə birləşməsini redaktə et", + "customShortcutTextBoxKeyHint": "Burada Ctrl/Alt ilə düymə istifadə edin - adi düymə artıq mətn xanasında yazmayacaq.", + "customShortcutNumberX": "Xüsusi düymə birləşməsi #{0}", + "customShortcutNumberXY": "Xüsusi düymə birləşməsi #{0}: {1}", + "customShortcutStep": "Addım", + "customShortcutSteps": "Addımlar", + "customShortcutStepRunCommand": "Əmri icra et", + "customShortcutStepInsertText": "Mətn daxil et", + "customShortcutStepReplace": "Tap və əvəz et", + "customShortcutInsertAtCursor": "Kursorun yerində", + "customShortcutInsertAtStart": "Mətnin əvvəlində", + "customShortcutInsertAtEnd": "Mətnin sonunda", + "customShortcutSummaryRunCommandX": "İcra et: {0}", + "customShortcutSummaryInsertXY": "\"{0}\" daxil et ({1})", + "customShortcutSummaryReplaceXY": "\"{0}\" ifadəsini \"{1}\" ilə əvəz et", + "customShortcutSummaryReplaceRegexXY": "\"{0}\" müntəzəm ifadəsini \"{1}\" ilə əvəz et", + "customShortcutPickCommand": "İcra ediləcək əmri seçin.", + "customShortcutEnterText": "Daxil ediləcək mətni yazın.", + "customShortcutEnterFind": "Axtarılacaq mətni yazın.", + "customShortcutInvalidRegexX": "Yanlış müntəzəm ifadə: {0}", + "customShortcutStepsHint": "Addımlar yuxarıdan aşağıya icra olunur. Mətn addımları seçilmiş sətirlərdə işləyir; pəncərə açan əmrlərdən sonra icra pəncərə bağlandıqdan sonra davam edir.", "activeIn": "Aktiv olduğu sahə", "activeInEverywhere": "Hər yerdə", "generalMergeSelectedLinesAndUnbreak": "Seçilmiş sətirləri birləşdir və sətir bölünmələrini ləğv et", "generalMergeSelectedLinesAndUnbreakCjk": "Seçilmiş sətirləri birləşdir və sətir bölünmələrini boşluqsuz ləğv et (CJK)", "generalUnbreakNoSpaceCjk": "Sətir bölünmələrini boşluqsuz ləğv et (CJK)", + "generalToggleBreak": "Sətirlərə böl/sətir keçidlərini çıxar arasında keçid et", "generalMergeSelectedLinesBilingual": "Seçilmiş sətirləri iki dildə birləşdir", "generalToggleTranslationMode": "Tərcümə rejimini dəyiş", "generalToggleTranslationAndOriginalInPreviews": "Video/səs önbaxışında tərcümə ilə orijinal arasında keçid et", @@ -3113,10 +3746,12 @@ "generalToggleItalic": "Kursivi aç/bağla", "generalToggleBold": "Qalın yazını aç/bağla", "generalToggleUnderline": "Altından xətt çəkməni aç/bağla", + "generalToggleBox": "Qutunu aç/bağla (EBU STL)", "fileOpen": "Aç", "fileOpenKeepVideo": "Aç (videonu saxla)", "fileSave": "Yadda saxla", "fileSaveAs": "Başqa adla yadda saxla", + "fileSaveOriginal": "Orijinalı yadda saxla", "fileCompare": "Müqayisə et", "fileStatistics": "Statistika", "fileImportPlainText": "Sadə mətn idxal et", @@ -3128,6 +3763,7 @@ "fillSelectedLinesWithClipboard": "Seçilmiş sətirləri mübadilə buferindəki mətnlə doldur", "listInverseSelection": "Seçimi tərsinə çevir", "listDeleteSelection": "Seçimi sil", + "deleteSelectionEverywhere": "Seçimi sil (hər yerdə)", "rippleDeleteSelection": "Seçimi sil və yaranan boşluğu bağla", "assigned": "Təyin edildi", "unassigned": "Təyin olunmayıb", @@ -3136,6 +3772,7 @@ "detectKey": "Düyməni müəyyən et", "control": "Ctrl", "alt": "Alt", + "altGr": "AltGr", "win": "Win", "shift": "Shift", "controlMac": "⌃", @@ -3146,6 +3783,11 @@ "resetShortcutsDetail": "Bütün qısayolları standart dəyərlərə sıfırlamaq istəyirsiniz?", "togglePlayPause": "Oynat/fasilə ver", "toggleLockTimeCodes": "Vaxt kodlarının kilidini aç/bağla", + "toggleTimeCodeMode": "Vaxt kodu rejimini dəyiş (vaxt/kadrlar/kadr nömrələri)", + "toggleScreenPrivacy": "Ekran məxfiliyini dəyiş (fayl adlarını, sonra mətnləri də gizlət)", + "screenPrivacyFileNamesHidden": "Ekran məxfiliyi: fayl adları gizlədilib", + "screenPrivacyFileNamesAndTextsHidden": "Ekran məxfiliyi: fayl adları və mətnlər gizlədilib", + "screenPrivacyOff": "Ekran məxfiliyi: söndürülüb", "duplicateSelectedLines": "Seçilmiş sətirlərin surətini yarat", "sourceView": "Mənbə görünüşü", "addOrEditBookmark": "Əlfəcin əlavə et və ya redaktə et", @@ -3164,6 +3806,7 @@ "moveFirstWordFromNextLineUpCurrentSubtitle": "Növbəti sətirdəki ilk sözü yuxarı köçür (cari altyazı)", "moveTextFromCursorToNextAndGoToNext": "Kursordan sonrakı mətni növbəti altyazıya köçür və növbətiyə keç", "moveTextFromCursorToNextAndGoToNextAndPlay": "Kursordan sonrakı mətni növbəti altyazıya köçür, növbətiyə keç və oynat", + "moveTextFromCursorToNextAndGoToNextAndPlayAndPause": "Kursordan sonrakı mətni növbəti altyazıya köçür, növbətiyə keç, onu oynat və sonunda dayandır", "breakAtFirstSpaceFromCursor": "Kursordan sonrakı ilk boşluqdan böl", "breakAtFirstSpaceFromCursorAndGoToNext": "Kursordan sonrakı ilk boşluqdan böl və növbətiyə keç", "toggleFocusGridAndWaveform": "Fokusu altyazı cədvəli ilə səs dalğası qrafiki/spektroqram arasında dəyiş", @@ -3193,6 +3836,7 @@ "listViewColumnTextUp": "Sütun: mətni yuxarı köçür", "listViewColumnTextDown": "Sütun: mətni aşağı köçür", "autoTranslateSelectedLines": "Seçilmiş sətirləri avtomatik tərcümə et...", + "autoTranslateSelectedLinesNoPrompt": "Seçilmiş sətirləri avtomatik tərcümə et (soruşmadan, son mühərrik/dillərdən istifadə et)", "setAssaResolution": "ASSA təsvir ölçüsünü təyin et (PlayResX/PlayResY)", "setShortcutForX": "\"{0}\" üçün qısayol təyin et", "commandFileNewKeepVideo": "Yeni (videonu saxla)", @@ -3247,6 +3891,7 @@ "textBoxItalic": "Mətn sahəsi: kursiv yazı", "textBoxBold": "Mətn sahəsi: qalın yazı", "textBoxUnderline": "Mətn sahəsi: altından xətt çək", + "textBoxBox": "Mətn xanası qutusu (EBU STL)", "resetWaveformZoomAndSpeed": "Səs dalğası qrafikinin miqyasını və oynatma sürətini sıfırla (ifa sürəti)", "togglePlaybackSpeed": "Oynatma sürətini dəyiş (ifa sürəti)", "playbackSpeedSlower": "Oynatma sürətini azalt (ifa sürəti)", @@ -3258,6 +3903,7 @@ "seekSilenceForward": "İrəliyə doğru səssiz hissəni tap", "waveformGuessStart": "Səs dalğası qrafikinə əsasən başlanğıc vaxtını təxmin et", "waveformGuessEnd": "Səs dalğası qrafikinə əsasən bitmə vaxtını təxmin et", + "waveformGuessStartAndEnd": "Başlanğıc və son vaxtı səs dalğasından təxmin et", "setVideoPositionCurrentSubtitleStart": "Videodakı mövqeyi cari sətrin başlanğıcına qoy", "goToSubtitlePositionAndPause": "Altyazının mövqeyinə keç və fasilə ver", "setVideoPositionCurrentSubtitleEnd": "Videodakı mövqeyi cari sətrin sonuna qoy", @@ -3271,6 +3917,14 @@ "surroundWith": "Bu işarələrlə əhatələ...", "surroundWithXY": "{0}/{1} ilə əhatələ", "surroundWithNumberX": "#{0} ilə əhatələ", + "surroundWithBehavior": "Davranış", + "surroundWithBehaviorToggle": "Keçid (əlavə et, varsa sil)", + "surroundWithBehaviorAdd": "Əlavə et (hər dəfə)", + "surroundWithBehaviorRemove": "Sil (hamısını)", + "surroundWithBehaviorRemoveOnce": "Sil (hər dəfə birini)", + "surroundWithWorksOn": "Tətbiq sahəsi", + "surroundWithScopeSelectionOrText": "Seçim, əks halda bütün mətn", + "surroundWithScopeEachLine": "Hər sətir", "searchVia": "Axtarış vasitəsi", "searchViaX": "{0} vasitəsilə axtarış", "searchViaNumberX": "#{0} vasitəsilə axtarış", @@ -3298,6 +3952,11 @@ "copySubtitlePathToClipboard": "Altyazının yolunu mübadilə buferinə kopyala", "copySubtitleOriginalPathToClipboard": "Orijinal altyazının yolunu mübadilə buferinə kopyala", "focusTextBox": "Fokusu mətn sahəsinə keçir", + "focusSubtitleListView": "Altyazı siyahısına fokuslan", + "focusWaveform": "Səs dalğasına fokuslan", + "focusOriginalTextBox": "Orijinal mətn xanasına fokuslan", + "textBoxGoToStart": "Mətn xanası, əvvələ keç", + "textBoxGoToEnd": "Mətn xanası, sona keç", "sortByNumber": "Nömrəyə görə sırala", "sortByStartTime": "\"Göstər\" vaxtına görə sırala", "sortByEndTime": "\"Gizlət\" vaxtına görə sırala", @@ -3335,9 +3994,9 @@ "selectionToSentenceCase": "Mətn sahəsi: seçimi cümlə qaydasına uyğunlaşdır", "selectionToggleCasing": "Mətn sahəsi: hərf registrini dəyiş (böyük hərflər → kiçik hərflər → sözlərin ilk hərfi böyük)", "googleIt": "Google ilə axtarış (seçilmiş mətn)", - "setActorXY": "{0} nömrəli danışanı təyin et: {1}", + "setActorX": "Aktyoru təyin et: {0}", "setNewActor": "Yeni danışan təyin et...", - "setActorSlotXTitle": "{0} nömrəli danışan yeri", + "setStyleX": "Üslubu təyin et: {0}", "toggleDialogDashes": "Dialoq tirelərini aç/bağla", "mergeWithLineBeforeAsDialog": "Əvvəlki sətirlə dialoq kimi birləşdir", "mergeWithLineAfterAsDialog": "Növbəti sətirlə dialoq kimi birləşdir", @@ -3348,7 +4007,23 @@ "moveStartOneFrameBackKeepGapPrev": "Başlanğıcı bir kadr geri çək (əvvəlki yaxındırsa, aradakı fasiləni saxla)", "moveStartOneFrameForwardKeepGapPrev": "Başlanğıcı bir kadr irəli çək (əvvəlki yaxındırsa, aradakı fasiləni saxla)", "moveEndOneFrameBackKeepGapNext": "Sonu bir kadr geri çək (növbəti yaxındırsa, aradakı fasiləni saxla)", - "moveEndOneFrameForwardKeepGapNext": "Sonu bir kadr irəli çək (növbəti yaxındırsa, aradakı fasiləni saxla)" + "moveEndOneFrameForwardKeepGapNext": "Sonu bir kadr irəli çək (növbəti yaxındırsa, aradakı fasiləni saxla)", + "moveStartXMsBack": "Başlanğıcı X ms geri sürüşdür (X Parametrlərdə, həmçinin Vizual sinxronlaşdırmada təyin edilir)", + "moveStartXMsForward": "Başlanğıcı X ms irəli sürüşdür (X Parametrlərdə, həmçinin Vizual sinxronlaşdırmada təyin edilir)", + "moveEndXMsBack": "Sonu X ms geri sürüşdür (X Parametrlərdə, həmçinin Vizual sinxronlaşdırmada təyin edilir)", + "moveEndXMsForward": "Sonu X ms irəli sürüşdür (X Parametrlərdə, həmçinin Vizual sinxronlaşdırmada təyin edilir)", + "moveSelectedLinesXMsBack": "Seçilmiş sətirləri X ms geri sürüşdür (X Parametrlərdə təyin edilir)", + "moveSelectedLinesXMsForward": "Seçilmiş sətirləri X ms irəli sürüşdür (X Parametrlərdə təyin edilir)", + "moveSelectedLinesAndForwardXMsBack": "Seçilmiş sətirləri və bütün sonrakıları X ms geri sürüşdür (X Parametrlərdə təyin edilir)", + "moveSelectedLinesAndForwardXMsForward": "Seçilmiş sətirləri və bütün sonrakıları X ms irəli sürüşdür (X Parametrlərdə təyin edilir)", + "moveAllLinesXMsBack": "Bütün sətirləri X ms geri sürüşdür (X Parametrlərdə təyin edilir)", + "moveAllLinesXMsForward": "Bütün sətirləri X ms irəli sürüşdür (X Parametrlərdə təyin edilir)", + "moveSelectedLinesCustomMsBack": "Seçilmiş sətirləri sürüşdür, xüsusi millisaniyə ({0:#,###,##0}) geri, {1}", + "moveSelectedLinesCustomMsForward": "Seçilmiş sətirləri sürüşdür, xüsusi millisaniyə ({0:#,###,##0}) irəli, {1}", + "moveSelectedLinesAndForwardCustomMsBack": "Seçilmiş sətirləri və bütün sonrakıları sürüşdür, xüsusi millisaniyə ({0:#,###,##0}) geri, {1}", + "moveSelectedLinesAndForwardCustomMsForward": "Seçilmiş sətirləri və bütün sonrakıları sürüşdür, xüsusi millisaniyə ({0:#,###,##0}) irəli, {1}", + "moveAllLinesCustomMsBack": "Bütün sətirləri sürüşdür, xüsusi millisaniyə ({0:#,###,##0}) geri, {1}", + "moveAllLinesCustomMsForward": "Bütün sətirləri sürüşdür, xüsusi millisaniyə ({0:#,###,##0}) irəli, {1}" }, "wordLists": { "title": "Söz siyahıları", @@ -3393,7 +4068,11 @@ "applyToSelectedLinesX": "Seçilmiş sətirlər ({0})", "applyToAllLinesX": "Bütün sətirlər ({0})", "updateAllXAvailable": "Hamısını yenilə ({0})", - "updatingXOfY": "{0}/{1} yenilənir..." + "updatingXOfY": "{0}/{1} yenilənir...", + "doNotAskAgain": "Bir daha soruşma (Plaginləri idarə et bölməsində dəyişin)", + "applyPluginsTo": "Plaginləri buna tətbiq et", + "askEachTime": "Hər dəfə soruş", + "allLines": "Bütün sətirlər" }, "help": { "aboutSubtitleEdit": "Subtitle Edit haqqında", @@ -3437,6 +4116,8 @@ "nOcrDatabase": "nOCR verilənlər bazası", "drawMode": "Çəkmə rejimi:", "addNewCharcter": "Yeni simvol əlavə et", + "addNOcrCharacterTitle": "\"{3}\" verilənlər bazasından istifadə edərək {0} sətri üçün nOCR simvolu əlavə et, {2} simvoldan {1}", + "addBinaryOcrCharacterTitle": "\"{3}\" verilənlər bazasından istifadə edərək {0} sətri üçün ikili OCR simvolu əlavə et, {2} simvoldan {1}", "lineIndexX": "Sətir {0}", "inspectNOcrAdditions": "Yeni nOCR əlavələrini yoxla", "ocrSelectedLines": "Seçilmiş sətirlərə OCR tətbiq et", @@ -3485,6 +4166,8 @@ "binaryImageCompareInspectImageMatches": "\"İkili şəkil müqayisəsi\": şəkil uyğunluqlarını yoxla", "saveBlankTextTitle": "Boş mətn yadda saxlanılsın?", "saveBlankTextPrompt": "Şəkil üçün boş mətn yadda saxlanılsın?", + "drawnLinesDoNotMatchTitle": "Xətlər şəkillə uyğun gəlmir", + "drawnLinesDoNotMatchPrompt": "Çəkilmiş xətlərin bəziləri şəkillə uyğun gəlmir (yaşıl xətlər simvolun üzərində, qırmızı xətlər isə ondan kənarda olmalıdır), buna görə bu simvol tanınmayacaq.\n\nYenə də yadda saxlanılsın?", "yesAndNeverAskAgain": "Bəli, bir daha soruşma", "importTextFromSubtitleDotDotDot": "Altyazıdan mətn idxal et...", "importTextFromSubtitleOverwritePrompt": "Bəzi sətirlərdə artıq OCR mətni var. Mövcud mətnin üzərinə yazılsın?", @@ -3543,9 +4226,26 @@ "crispEmbedSettingsTitle": "CrispEmbed parametrləri", "crispEmbedDescription": "Bir neçə model icra mühitini dəstəkləyən lokal OCR mühərriki. Mühərriki və modelləri buradan endirin.", "trainNOcrDatabase": "nOCR verilənlər bazasını öyrət...", + "optimizeNOcrDatabase": "nOCR verilənlər bazasını optimallaşdır...", + "optimizeNOcrDatabaseQuestionXY": "\"{0}\" nOCR verilənlər bazasında simvolları oxşar simvollardan fərqləndirməyən xətlər silinsin?\n\nBu, verilənlər bazasını kiçildir və OCR-i sürətləndirir, adətən daha az xəta ilə. Bir dəqiqəyədək çəkir.\n\nEhtiyat nüsxə \"{1}\" kimi yadda saxlanılır.", + "optimizingNOcrDatabaseXY": "nOCR verilənlər bazası optimallaşdırılır... {0}/{1}", + "nOcrDatabaseOptimizedXYZ": "\"{0}\" nOCR verilənlər bazası optimallaşdırıldı: {1:#,##0} -> {2:#,##0} xətt.", "startTraining": "Öyrətməni başlat", "abortTraining": "Öyrətməni dayandır", "trainingOptions": "Öyrətmə seçimləri", + "trainNOcrSubtitle": "Simvolları quraşdırılmış şriftlərlə çəkir və nOCR-ə onların formalarını öyrədir. Altyazılarınızın istifadə etdiyi şriftləri seçin.", + "xFontsSelected": "{0:#,##0} seçilib", + "subtitleFonts": "Altyazı şriftləri", + "emptyDatabase": "Boş verilənlər bazası", + "startFromDatabase": "Başlanğıc nöqtəsi", + "startFromDatabaseHint": "Öyrədilmiş simvolları mövcud verilənlər bazasının surətinə əlavə edin (tövsiyə olunur) və ya boş bazadan başlayın", + "fontSizes": "Şrift ölçüləri", + "fontSizesHint": "Çəkiləcək şrift ölçüləri piksellə, vergüllə ayrılmış, məsələn \"30, 40\"", + "numberOfLineSegmentsHint": "Hər simvol üçün yoxlanılan xətlər - çox olduqda daha sərt, lakin daha yavaş", + "enterDatabaseName": "Verilənlər bazası üçün ad daxil edin", + "trainingFontXSizeYCharacterZ": "\"{0}\" {1} px ölçüdə öyrədilir: {2}", + "xLearned": "{0:#,##0} öyrənildi", + "xSkipped": "{0:#,##0} ötürüldü", "charactersToTrain": "Öyrədiləcək simvollar", "letterCombinationsToTrain": "Bir şəkil kimi ayrıla biləcək hərf birləşmələri", "importCharactersFromSubtitleFile": "Altyazı faylından simvollar idxal et...", @@ -3572,6 +4272,8 @@ "drawZoomOut": "Kiçilt (Ctrl+-)", "drawResetView": "Görünüşü sıfırla (Ctrl+0)", "drawToggleGrid": "Toru göstər/gizlət (Ctrl+G)", + "drawTogglePreview": "Ön baxışı aç/bağla (F9)", + "drawPreviewFailed": "Ön baxış alınmadı - libass ilə ffmpeg (subtitles filtri) tələb olunur", "drawCopyToClipboard": "Mübadilə buferinə kopyala (Ctrl+C)", "drawShapes": "Fiqurlar", "drawSelectedPoint": "Seçilmiş nöqtə", @@ -3579,6 +4281,81 @@ "drawSelectedLayer": "Seçilmiş qat", "drawToolX": "Alət: {0}", "drawHelpText": "Nöqtə əlavə etmək üçün klikləyin • Fiquru bağlamaq üçün Enter/F8 düyməsini basın • Görünüşü sürüşdürmək üçün Shift düyməsini basılı saxlayaraq siçanla dartın • Miqyası dəyişmək üçün Ctrl düyməsini basılı saxlayaraq siçanın çarxını fırladın", + "drawMoveToLayer": "Qata köçür", + "drawFlipHorizontal": "Üfüqi çevir", + "drawFlipVertical": "Şaquli çevir", + "drawHideShape": "Fiquru gizlət", + "drawShowShape": "Fiquru göstər", + "drawHideLayer": "Qatı gizlət", + "drawShowLayer": "Qatı göstər", + "drawDeleteLayer": "Qatı sil", + "drawDeletePoint": "Nöqtəni sil", + "drawCancelDrawing": "Çəkməni ləğv et", + "drawLayerX": "Qat {0}", + "drawImportSvg": "SVG şəkli idxal et...", + "drawRotateClockwise": "Saat əqrəbi istiqamətində 90° fırlat", + "drawConvertToCurve": "Seqmenti əyriyə çevir", + "drawBackground": "Fon", + "drawPosition": "Mövqe", + "drawZoom": "Miqyas", + "drawShapeTool": "Fiqur aləti", + "drawColorPickerTool": "Rəngi fiqurdan və ya fondan götür", + "drawShapeLibrary": "Fiqur kitabxanası", + "drawShapeLibraryHint": "Fiquru yerləşdirmək üçün kətan üzərində sürükləyin və ya standart ölçü üçün klikləyin. Shift onun nisbətlərini saxlayır.", + "drawAddToLibrary": "Fiqur kitabxanasına əlavə et...", + "drawRemoveFromLibrary": "Fiqur kitabxanasından çıxar", + "drawShapeName": "Fiqurun adı", + "drawCategorySpeechBubbles": "Danışıq qabarcıqları", + "drawCategoryArrows": "Oxlar", + "drawCategoryBasicShapes": "Əsas fiqurlar", + "drawCategorySymbols": "Simvollar", + "drawCategoryMyShapes": "Mənim fiqurlarım", + "drawShapeSpeechBubble": "Danışıq qabarcığı", + "drawShapeSpeechBubbleRight": "Danışıq qabarcığı, quyruq sağda", + "drawShapeSpeechBubbleUp": "Danışıq qabarcığı, quyruq yuxarıda", + "drawShapeOvalBubble": "Oval danışıq qabarcığı", + "drawShapeBoxBubble": "Düzbucaqlı danışıq qabarcığı", + "drawShapeThoughtBubble": "Fikir qabarcığı", + "drawShapeShoutBubble": "Qışqırıq qabarcığı", + "drawShapeCaptionBox": "Yazı qutusu", + "drawShapeArrow": "Ox", + "drawShapeDoubleArrow": "İkitərəfli ox", + "drawShapeChevron": "Şevron", + "drawShapeCurvedArrow": "Əyri ox", + "drawShapeRoundedRectangle": "Yumru künclü düzbucaqlı", + "drawShapeTriangle": "Üçbucaq", + "drawShapeDiamond": "Romb", + "drawShapePentagon": "Beşbucaq", + "drawShapeHexagon": "Altıbucaq", + "drawShapeOctagon": "Səkkizbucaq", + "drawShapeStar": "Ulduz", + "drawShapeRing": "Halqa", + "drawShapePlus": "Artı", + "drawShapeBanner": "Banner", + "drawShapeHeart": "Ürək", + "drawShapeCheck": "Təsdiq işarəsi", + "drawShapeMusicNote": "Not", + "drawShapeMusicNotes": "Notlar", + "drawShapeLightning": "İldırım", + "drawShapeCloud": "Bulud", + "drawShapeMoon": "Ay", + "drawShapeBadge": "Nişan", + "drawSize": "Ölçü", + "drawLayer": "Qat", + "drawBackgroundVideoFrame": "Video kadrı (cari mövqe)", + "drawBackgroundVideoFrameAt": "Bu vaxtdakı video kadrı...", + "drawBackgroundImage": "Şəkil faylı...", + "drawBackgroundNone": "Fon yoxdur", + "drawBackgroundStretch": "Şəkli kətana uyğun dart", + "drawBackgroundOpacity": "Fonun qeyri-şəffaflığı", + "drawBackgroundVideoFrameFailed": "{0} vaxtında video kadrını almaq mümkün olmadı.", + "drawImages": "Şəkillər", + "drawConvertToLine": "Seqmenti düz xəttə çevir", + "drawConvertShapeToCurves": "Bütün düz xətləri əyrilərə çevir", + "drawConvertShapeToLines": "Bütün əyriləri düz xətlərə çevir", + "drawRotateCounterClockwise": "Saat əqrəbinin əksinə 90° fırlat", + "drawSvgImages": "SVG şəkilləri", + "drawSvgNoShapes": "SVG faylında doldurulmuş fiqur və ya kontur tapılmadı.", "progressBarTitle": "ASSA gedişat zolağı", "progressBarSettings": "Gedişat zolağı", "mouseOverColor": "Siçan göstəricisi üzərinə gətirildikdə rəng", @@ -3675,13 +4452,26 @@ "graphics": "Qrafika", "copyToStorageStyles": "Saxlanılan üslublara kopyala", "copyToFileStyles": "Fayldakı üslublara kopyala", + "styleXAlreadyExistsInStorage": "Saxlanılan üslublarda \"{0}\" adlı üslub artıq mövcuddur.\n\nÜzərinə yazılsın, yoxsa hər ikisi saxlanılsın (surətə yeni ad veriləcək)?", + "styleXAlreadyExistsInFile": "Faylda \"{0}\" adlı üslub artıq mövcuddur.\n\nÜzərinə yazılsın, yoxsa hər ikisi saxlanılsın (surətə yeni ad veriləcək)?", + "styleNameCannotBeEmpty": "Üslub adı boş ola bilməz.", + "styleNameXIsUsedMoreThanOnce": "\"{0}\" üslub adı faylda birdən çox üslub tərəfindən istifadə olunur.", + "overwrite": "Üzərinə yaz", + "keepBoth": "Hər ikisini saxla", + "doThisForAllConflictingStyles": "Bütün ziddiyyətli üslublar üçün bunu et", "setStyleAsDefault": "Üslubu standart olaraq təyin et", "takeUsagesFromDotDotDot": "İstifadə yerlərini buradan götür...", "replaceStyleWithDotDotDot": "Üslubu bununla əvəz et...", "noAttachmentsFound": "Seçilmiş ASSA faylında qoşma tapılmadı.", "deleteStyleQuestion": "Üslub silinsin?", "deleteStylesQuestion": "Üslublar silinsin?", + "deleteXStylesQuestion": "{0} üslubu silmək istəyirsiniz?", + "deleteXStylesFromStorageQuestion": "{0} üslubu saxlanılan üslublardan silmək istəyirsiniz?", + "deleteStyleXFromStorageQuestion": "\"{0}\" üslubunu saxlanılan üslublardan silmək istəyirsiniz?", + "deleteStyleXFromFileQuestion": "\"{0}\" üslubunu cari fayldan silmək istəyirsiniz?", "openStyleImportFile": "Üslubların idxal ediləcəyi altyazı faylını aç", + "styleImportFiles": "Üslub faylları (.ass, .sty, Subtitle Edit 4 kateqoriyaları .template)", + "ssaStyleImportFiles": "Üslub faylları (.ssa, .ass, .sty)", "primary": "Əsas", "secondary": "İkinci", "applyOverrideTags": "Üslubu dəyişən etiketləri tətbiq et", diff --git a/src/ui/Assets/Languages/Bulgarian.json b/src/ui/Assets/Languages/Bulgarian.json index a592ae49a8a..1059cdedb9e 100644 --- a/src/ui/Assets/Languages/Bulgarian.json +++ b/src/ui/Assets/Languages/Bulgarian.json @@ -1,12 +1,30 @@ { "title": "Subtitle Edit", - "version": "v5.2.0-rc7", + "version": "v5.3.0", "translatedBy": "", "cultureName": "bg-BG", "general": { "abort": "Прекрати", "actor": "Актьор", "actors": "Актьори", + "setActorDotDotDot": "Задаване на актьор...", + "actorPickerTitle": "Задаване на актьор", + "actorPickerFilterHint": "Пишете, за да филтрирате, или въведете ново име на актьор", + "actorPickerKeysHint": "Натиснете цифра или Enter, за да зададете актьора. Alt+Up/Down променя реда, Delete премахва актьора.", + "actorPickerNewActorX": "Нов актьор \"{0}\"", + "actorPickerLinesSelectedX": "Избрани редове: {0}", + "setStyleDotDotDot": "Задаване на стил...", + "stylePickerTitle": "Задаване на стил", + "stylePickerFilterHint": "Пишете, за да филтрирате, или въведете ново име на стил", + "stylePickerKeysHint": "Натиснете цифра (1-9, 0), за да зададете стила на всички избрани редове.", + "stylePickerNewStyleX": "Нов стил \"{0}\"", + "stylePickerNewStyleInfo": "Натиснете Enter, за да добавите този стил с настройки по подразбиране и да го зададете на избраните редове. Използвайте \"Управление на стилове...\", за да промените по-късно шрифта, цветовете и позицията му.", + "stylePickerCurrentStyleX": "Текущ стил: {0}", + "stylePickerLinesUsingStyleX": "Редове с този стил: {0}", + "stylePickerMarginsXYZ": "Ляво {0}, дясно {1}, вертикално {2}", + "stylePickerSetStyle": "_Задаване на стил", + "stylePickerManageStyles": "Управление на стилове...", + "stylePickerManageStylesHint": "Затваряне на този прозорец и отваряне на управлението на стилове за редактиране на шрифтове, цветове, позиция и др.", "add": "Добавяне", "addDotDotDot": "Добави...", "addToNamesListCaseSensitive": "Добавяне към списъка с имена (с малки и главни букви)", @@ -339,6 +357,7 @@ "mergeSelectedLinesDialog": "Обедини избраните редове като диалог", "mergeWithLineAfterAndAutoBreak": "Сливане с реда след и автоматично прекъсване", "mergeWithLineAfterKeepBreaks": "Сливане с реда след (запазване на разделителите)", + "mergeWithLineAfterAndUnbreak": "Обединяване със следващия ред и премахване на пренасянето", "mergeWithLineBeforeAndAutoBreak": "Сливане с предишния ред и автоматично прекъсване", "mergeWithLineBeforeKeepBreaks": "Сливане с реда отпред (запазване на раздели)", "middleCenter": "Средно център", @@ -353,6 +372,8 @@ "moveAllShotChangeOneFrameForward": "Преместване на всички промени в кадрите с един кадър напред", "multiMode": "Многорежимно", "moveDown": "Премести надолу", + "increase": "Увеличаване", + "decrease": "Намаляване", "moveUp": "Премести нагоре", "moveToTop": "Премести нагоре", "moveToBottom": "Премести надолу", @@ -422,6 +443,7 @@ "percent": "Процент", "pickLayer": "Задаване на слой", "pickOllamaModel": "Избери модел Ollama", + "pickModel": "Избор на модел", "llamaCppUseRemoteServer": "Използвайте външен сървър (URL адрес)", "llamaCppEngineSettings": "настройки на енджина llama.cpp", "llamaCppEngineSettingsSubtitle": "Локална компилация на сървъра llama.cpp", @@ -498,6 +520,7 @@ "reset": "Нулиране", "resizePanels": "Преоразмеряване на панелите", "resolution": "Резолюция", + "retry": "Повторен опит", "reverseRightToLeftStartEnd": "Обратно начало/край надясно", "right": "Дясно", "rightToLeft": "Отдясно наляво", @@ -559,6 +582,8 @@ "shadowColor": "Цвят на сянката", "shadowWidth": "Ширина на сянката", "shiftLineBy": "Измести реда с", + "shotIn": "Начало на кадър", + "shotOut": "Край на кадър", "shortcut": "Пряк път", "shortcuts": "Преки пътища", "show": "Начално", @@ -570,9 +595,13 @@ "showStartColumn": "Показване на колона \"Начално\"", "showHideColumn": "Показване на колоната \"Крайно\"", "showHistory": "Покажи историята", + "clearHistory": "Изчистване на историята", + "removeFromHistory": "Премахване от историята", "showLayerColumn": "Показване на колона \"Слой\"", "showPreview": "Показване на предварителен преглед", "showShotChangesList": "Покажи списъка с промени на кадрите", + "showShotInColumn": "Показване на колона \"Начало на кадър\"", + "showShotOutColumn": "Показване на колона \"Край на кадър\"", "showStyleColumn": "Показване на колона \"Стил\"", "showTeletext": "Покажи ТТ", "showTimeCodes": "Показване на времеви кодове", @@ -629,12 +658,14 @@ "styleExaggeration": "Преувеличение в стила", "styles": "Стилове", "subtitleFile": "Файл със субтитри", + "subtitleFileChangedOutsideX": "Файлът \"{0}\" е променен извън Subtitle Edit.\n\nДа се зареди ли отново, като загубите промените си?", "subtitleFileName": "Име на файла със субтитри", "subtitleFileSaved": "Файлът със субтитри е запазен", "subtitleFileSavedToX": "Файлът със субтитри е запазен в {0}", "subtitleFiles": "Файлове със субтитри", "subtitleFormats": "Формати на субтитри", "subtitleLoadedX": "Заредени субтитри: {0}", + "subtitleReloadedChangedOutsideX": "Субтитрите са заредени отново (променени извън Subtitle Edit): {0}", "suffix": "Суфикс", "suggestions": "Предложения", "sync": "Синхронизиране", @@ -680,6 +711,7 @@ "type": "Тип", "unbreak": "Разделяне на текста", "unbreakLines": "Разделяне на редовете", + "undelete": "Възстановяване на изтритото", "underline": "Подчертаване", "undo": "Отмяна", "unknown": "Неизвестен", @@ -717,10 +749,23 @@ "videoCustom3ForwardX": "Видео, персонализирани милисекунди ({0:#,###,##0}) напред, 3", "videoCustom4BackX": "Видео, персонализирани милисекунди ({0:#,###,##0}) назад, 4", "videoCustom4ForwardX": "Видео, персонализирани милисекунди ({0:#,###,##0}) напред, 4", + "videoCustom1BackAndPauseX": "Видео, потребителски милисекунди ({0:#,###,##0}) назад и пауза, 1", + "videoCustom1ForwardAndPauseX": "Видео, потребителски милисекунди ({0:#,###,##0}) напред и пауза, 1", + "videoCustom2BackAndPauseX": "Видео, потребителски милисекунди ({0:#,###,##0}) назад и пауза, 2", + "videoCustom2ForwardAndPauseX": "Видео, потребителски милисекунди ({0:#,###,##0}) напред и пауза, 2", + "videoCustom3BackAndPauseX": "Видео, потребителски милисекунди ({0:#,###,##0}) назад и пауза, 3", + "videoCustom3ForwardAndPauseX": "Видео, потребителски милисекунди ({0:#,###,##0}) напред и пауза, 3", + "videoCustom4BackAndPauseX": "Видео, потребителски милисекунди ({0:#,###,##0}) назад и пауза, 4", + "videoCustom4ForwardAndPauseX": "Видео, потребителски милисекунди ({0:#,###,##0}) напред и пауза, 4", "videoExtension": "Разширение на видео файл", "videoFile": "Видео файл", "videoFileGenerated": "Генериран видео файл", "videoFileGeneratedX": "Генериран видео файл: \"{0}\"", + "videoFilesGenerated": "Генерирани видео файлове", + "xVideosGenerated": "Генерирани видеоклипове: {0}", + "xOfYVideosGenerated": "Генерирани {0} от {1} видеоклипа", + "savedInX": "Запазено в {0}", + "savedInXFolders": "Запазено в {0} папки", "videoFiles": "Видео файлове", "videoAndAudioFiles": "Видео и аудио файлове", "videoOffset": "Видео отместване", @@ -778,7 +823,6 @@ "imageFiles": "Файлове с изображения", "openAiCompatibleSttEndpoint": "URL адрес на крайната точка на STT", "openAiCompatibleSttApiKey": "API ключ", - "openAiCompatibleSttModel": "Модел", "openAiCompatibleSttExtraHeaders": "Допълнителни заглавки", "openAiCompatibleSttTimeout": "Време за изчакване (секунди)", "openAiCompatibleSttLanguage": "Подсказка за език", @@ -788,6 +832,9 @@ "dashScopeSttRegion": "Регион", "dashScopeSttEnableWords": "Времеви марки на ниво дума", "dashScopeSttRegionKeyHint": "Забележка: Ключовете за API на Alibaba Cloud Model Studio са специфични за региона - уверете се, че избраният регион съответства на региона, където е създаден ключът за API (Китай срещу Международен).", + "googleCloudSttProjectId": "ID на проекта (само за Application Default Credentials)", + "googleCloudSttBucketName": "Storage bucket (празно = автоматично)", + "googleCloudSttDynamicBatching": "Динамично групиране (около 5 пъти по-евтино, без гаранция за забавянето)", "onlineSttApiKeyMissing": "Изисква се API ключ. Моля, въведете вашия API ключ и опитайте отново.", "openAiCompatibleSttAutoTranscribeOnAudioSelection": "Автоматично транскрибиране при избор на аудио", "openAiCompatibleSttStream": "Поточен отговор", @@ -845,6 +892,7 @@ "multipleReplace": "_Множествена замяна...", "fillSelectedLinesWithClipboard": "Запълни избраните редове с текст от клипборда", "goToLineNumber": "_Отиди на номер на ред...", + "commandPalette": "Палитра с _команди...", "rightToLeftMode": "Режим отдясно наляво", "modifySelectionDotDotDot": "Промяна на _селекция...", "tools": "_Инструменти", @@ -852,6 +900,7 @@ "applyDurationLimits": "Прилагане на ограничения за Траене...", "batchConvert": "_Пакетно конвертиране...", "beautifyTimeCodes": "Разкрасяване на времевите кодове...", + "improveTimeCodes": "Подобряване на времевите кодове (принудително подравняване)...", "bridgeGaps": "Запълване _на празнини...", "applyMinGap": "Приложи мин. разстояние между субтитрите...", "changeCasing": "_Смяна на главни и малки букви...", @@ -990,6 +1039,9 @@ "selectCurrentLineWhilePlayingHint": "Маркиране на текущ субтитър при възпроизвеждане {0}", "videoPosition": "Позиция на видеото {0}", "videoPositionTextBox": "Текстово поле за позиция на видеоклипа {0}", + "toolbarLineBreakX": "Нов ред {0} (останалата част от лентата с инструменти продължава на нов ред)", + "initialText": "Първоначален текст на избрания ред", + "initialTextHint": "Текстът на избрания ред такъв, какъвто е бил при избирането на реда", "hideWaveformToolbar": "Скриване на лентата с инструменти {0}", "resetZoomAndSpeed": "Нулиране на мащаба и скоростта на възпроизвеждане {0}", "removeBlankLines": "Премахване на празни редове {0}", @@ -1003,6 +1055,15 @@ "textPlayHint": "Пусни текущия субтитър и спри в края {0}", "textPauseHint": "Пауза на възпроизвеждането {0}", "textNextHint": "Пусни следващия субтитър и спри в края {0}", + "moveSelectedLines": "Преместване на избраните редове (бутони назад/напред)", + "moveSelectedLinesAndFollowing": "Преместване на избраните редове и всички следващи (бутони назад/напред)", + "moveAllLines": "Преместване на всички редове (бутони назад/напред)", + "moveSelectedLinesBackHint": "Преместване на избраните редове {0} ms назад {1}", + "moveSelectedLinesForwardHint": "Преместване на избраните редове {0} ms напред {1}", + "moveSelectedLinesAndFollowingBackHint": "Преместване на избраните редове и всички следващи {0} ms назад {1}", + "moveSelectedLinesAndFollowingForwardHint": "Преместване на избраните редове и всички следващи {0} ms напред {1}", + "moveAllLinesBackHint": "Преместване на всички редове {0} ms назад {1}", + "moveAllLinesForwardHint": "Преместване на всички редове {0} ms напред {1}", "guessStartNoLineSelected": "Начало на предположението: няма избран ред", "guessStartTimeCodesLocked": "Начало на предположението: времевите кодове са заключени", "guessStartNoWaveform": "Начало на предположението: няма вълнова форма", @@ -1024,6 +1085,9 @@ "charactersPerSecond": "Знаци/секунда: {0}", "chooseColumn": "Избери колона", "columnPaste": "Вмъкни колони", + "commandPaletteTitle": "Палитра с команди", + "commandPaletteSearchPlaceholder": "Пишете, за да търсите команди...", + "commandPaletteNoCommandsFound": "Няма съвпадащи команди", "createdEmptyTranslation": "Създаден е празен превод от текущия субтитър", "deleteText": "Изтриване на текст", "deleteTextAndShiftCellsUp": "Изтриване на текст и преместване на клетките нагоре", @@ -1062,11 +1126,14 @@ "numberOfLinesEvenlyDistributedX": "Равномерно разпределени {0} реда", "oneLineCopiedFromOriginal": "Един ред, копиран от оригиналния субтитър", "oneLineCopiedToOriginal": "Един ред е копиран в оригиналния субтитър", + "onlineOnlyVideo": "Видео само онлайн", + "onlineOnlyVideoXSizeYDownloadAndOpen": "Видео файлът \"{0}\" ({1}) се съхранява само онлайн, например в Dropbox или iCloud Drive, и трябва да бъде изтеглен, преди да може да се отвори.\n\nДа се изтегли и отвори ли сега?", "oneLineMerged": "Един ред е обединен", "oneLineSwitched": "Един превключен ред", "overwriteExistingCells": "Презаписване на съществуващи клетки", "overwriteOrShiftCellsDown": "Презаписване/преместване на клетки надолу", "parsingMatroskaFile": "Разбор на Matroska файл...", + "readingDvdSubtitles": "Четене на DVD субтитри...", "pasteFromClipboardDotDotDot": "Поставяне от клипборда...", "redoPerformed": "Повторно извършено", "redoPerformedXActionLeft": "Възстановяване на изпълнението (оставащи действия: {0})", @@ -1106,12 +1173,14 @@ "trimmedXLines": "Съкратени са {0} реда със субтитри", "pastedXLinesOverSelectedLines": "Поставени са {0} реда върху избраните редове", "pastedXOfYLinesOverSelectedLines": "Поставени са {0} от {1} реда върху избраните редове - останалите не се побират в селекцията", + "insertedXLinesFromClipboard": "Вмъкнати {0} ред(а) от клипборда", "openOriginalDifferentNumberOfSubtitlesXY": "Оригиналният файл със субтитри няма същия брой субтитри като текущия файл със субтитри.\n\n• Оригинални субтитри: {0}\n• Текущи субтитри: {1}", "originalTextReadOnly": "Оригинален текст (само за четене)", "originalTextEditMode": "Оригинален текст (режим на редактиране)", "originalIsReadOnlyNotSaved": "Оригиналният субтитър е само за четене и не е запазен", "originalIsEmptyNotSaved": "Оригиналният субтитър изобщо няма текст и не е запазен", "originalIsReadOnlyReference": "Оригиналният субтитър е отворен само за четене", + "closeTranslationQuestionXY": "Затваряне на превода \"{0}\"?\n\nОригиналните субтитри \"{1}\" стават субтитрите, които редактирате. Самият файл с превода не се променя.", "allowEditOfOriginalSubtitle": "Разрешаване на редактиране на оригиналните субтитри", "showAllOriginalLinesX": "Показване на всички оригинални редове ({0} нямат съвпадение тук)", "showAllOriginalLinesHint": "Редовете без съвпадение се показват като допълнителни редове, за да можете да видите какво липсва в превода.", @@ -1146,6 +1215,7 @@ "fileExportedInFormatXToY": "Файлът е експортиран във формат {0} към {1}", "fileExportedInFormatXToFileY": "Файлът е експортиран във формат \"{0}\" към файл \"{1}\"", "fixedXLines": "Фиксирани {0} редове", + "toolXChangedYLines": "{0}: променени редове: {1}", "transcriptionCompletedWithXLines": "Транскрипцията е завършена с {0} реда", "openSubtitleFileX": "Да се отвори ли файлът със субтитри \"{0}\"?", "replacedXOccurrences": "Заменени са {0} съвпадения", @@ -1236,6 +1306,21 @@ "formattingDotDotDot": "Форматиране...", "imageBasedSubtitleForEditDotDotDot": "Субтитри, базирани на изображения, за редактиране...", "imageBasedSubtitleForOcrDotDotDot": "Субтитри, базирани на изображения, за OCR...", + "dvdSubtitlesDotDotDot": "Субтитри от DVD (IFO/VOB)...", + "titleImportDvd": "Импортиране на субтитри от DVD", + "dvdIfoFile": "IFO файл", + "dvdVobFiles": "VOB файлове", + "dvdVideoStandard": "Видео стандарт", + "dvdStartRipping": "Започване на извличането", + "dvdRippingAborted": "Извличането е прекратено", + "dvdOpenIfoFile": "Отваряне на DVD IFO файл", + "dvdAddVobFiles": "Добавяне на VOB файлове", + "dvdIfoFiles": "DVD IFO файлове", + "dvdVobFileType": "DVD VOB файлове", + "dvdDropHint": "Отворете IFO файл (VIDEO_TS\\VTS_01_0.IFO) или добавете VOB файлове - или ги пуснете тук", + "dvdNotAnIfoFileX": "{0} не е DVD IFO файл", + "dvdMenuIfoNoTitleSetsX": "Не са намерени набори от заглавия (VTS_xx_0.IFO) до {0}", + "dvdEncryptedXOfY": "{0} от {1} пакета със субтитри все още са CSS криптирани - тези VOB файлове са копирани без декриптиране, така че някои изображения на субтитрите ще бъдат повредени.\n\nЗа чист резултат първо декриптирайте DVD диска с програма за извличане на DVD.\n\nПродължаване въпреки това?", "splitTextAt": "Разделяне на текста на", "blankLines": "Празни редове", "oneLineIsOneSubtitle": "Един ред е един субтитър", @@ -1255,8 +1340,13 @@ "forcedAlignerProgress": "Подравняване... прозорец {0} от {1}, {2} от {3} реда", "forcedAlignerSetupTitle": "Подравняване на времевите кодове чрез принудително подравняване", "forcedAlignerSetupIntro": "Принудителното подравняване съпоставя текста на субтитрите, който вече имате, със звука, без първо да го транскрибира. Дългите видеоклипове се подравняват на части, така че всяка дължина е подходяща.", + "forcedAlignerEndsFromSpeech": "Задаване на крайните времена от изолираната реч (бавно)", + "forcedAlignerEndsFromSpeechHint": "Принудителното подравняване намира къде започва даден ред, но не и къде свършва, затова крайните времена обикновено идват от времето за четене. Когато това е включено, първо музиката и звуковите ефекти се премахват от звука и всеки ред свършва там, където речта му заглъхне. Отнема приблизително колкото продължителността на самото видео с GPU - и многократно повече без такъв.", + "forcedAlignerIsolatingSpeech": "Изолиране на речта... {0}", + "forcedAlignerIsolatingSpeechFailed": "Речта не можа да бъде изолирана - крайните времена бяха зададени от времето за четене.", "forcedAlignerModel": "Модел на подравняване", - "forcedAlignerDownloadEngine": "Изтегляне / актуализиране на двигателя..." + "forcedAlignerDownloadEngine": "Изтегляне / актуализиране на двигателя...", + "forcedAlignerEstimatedXOfY": "{0} от {1} реда не можаха да бъдат подравнени и бяха поставени според времето за четене - времевите им кодове са само приблизителни. Сценарият вероятно е по-дълъг от изговореното във видеото." }, "export": { "exportImagesProfiles": "Експорт на профили с изображения", @@ -1305,6 +1395,18 @@ "imageBasedSubtitleSaved": "Запазени са субтитри, базирани на изображение", "fullFrameImage": "Изображение в цял кадър", "fullFrameImageHint": "Направете всяко изображение с размера на видеокадра, като поставите субтитрите на негово място", + "stereo3D": "3D", + "stereo3DHint": "За 3D видео: субтитрите се изчертават веднъж във всяка половина на кадъра (за всяко око)", + "halfSideBySide": "Половин един до друг", + "halfTopBottom": "Половин горе/долу", + "depth3D": "Дълбочина", + "depth3DHint": "Колко изпъкват субтитрите от екрана - положителна стойност ги приближава, отрицателна ги отдалечава", + "plane3D": "3D-Plane", + "plane3DHint": "Задава на всеки субтитър дълбочината, която 3D Blu-ray съхранява за него, от 3D-Plane (.ofs) файл, създаден с BD3D2MK3D или OFSExtractor. Дълбочината се използва там, където 3D-Plane няма стойност", + "openPlane3DTitle": "Отваряне на 3D-Plane файл", + "unableToLoadPlane3DX": "Не може да се зареди 3D-Plane: {0}", + "plane3DXDepthYToZ": "{0} (дълбочина от {1} до {2})", + "plane3DXNoDepth": "{0} (без стойности за дълбочина)", "textEffect": "Текстов ефект", "textEffectSettingsTitle": "Настройки на текстовите ефекти", "textEffectPreset": "Предварително зададено", @@ -1409,7 +1511,22 @@ "restoreAutoBackupFile": "Възстановяване на файл с автоматично архивиране", "restoreXFromY": "Искате ли да възстановите \"{0}\" от {1}?", "deleteAllSubtitleBackups": "Искате ли да изтриете всички резервни копия на субтитрите?", - "deleteAll": "Изтрий всички" + "deleteAll": "Изтрий всички", + "subtitles": "Субтитри", + "settingsBackupInfo": "Settings.json се архивира при стартиране на Subtitle Edit, най-много веднъж на {0} ден(а) и само ако настройките са променени. Пазят се най-новите {1} архива.", + "settingsBackupInfoEveryStart": "Settings.json се архивира при всяко стартиране на Subtitle Edit, ако настройките са променени. Пазят се най-новите {0} архива.", + "settingsBackupOff": "Автоматичното архивиране на настройките е изключено (Опции → Настройки → Файл).", + "lastSettingsBackupX": "Последен архив: {0}", + "noSettingsBackupsYet": "Все още няма архиви на настройките", + "openSettingsBackupFolder": "Отваряне на папката с архиви на настройките", + "backUpSettingsNow": "Архивиране сега", + "settingsBackedUp": "Текущите настройки бяха архивирани.", + "restoreSettings": "Възстановяване на настройките", + "restoreSettingsFromX": "Искате ли да замените текущите си настройки с архива от {0}?\n\nПърво ще бъде направен архив на текущите ви настройки. Някои промени влизат в сила едва след рестартиране на Subtitle Edit.", + "settingsRestored": "Настройките бяха възстановени. Моля, рестартирайте Subtitle Edit, за да влязат в сила всички промени.", + "settingsRestoreFailed": "Настройките не можаха да бъдат възстановени: {0}", + "settingsBackupNotValid": "архивът не е валиден файл с настройки, затова текущите ви настройки бяха запазени", + "deleteAllSettingsBackups": "Искате ли да изтриете всички архивни файлове с настройки?" }, "propertiesDCinema": { "title": "D-Cinema SMPTE Properties", @@ -1423,22 +1540,16 @@ "editRate": "Редактиране на цената", "timeCodeRate": "Скорост на времевия код", "startTime": "Начално време", - "font": "Шрифт", "fontId": "Идентификатор на шрифта", "fontUri": "URI на шрифта", - "generate": "Генериране", - "fontColor": "Цвят на шрифта", "chooseColor": "Изберете цвят", "fontEffect": "Ефект на шрифта", "effectColor": "Цвят на ефекта", - "fontSize": "Размер на шрифта", "topBottomMargin": "Горен/долен марж", "fadeUpTime": "Време за затихване", "fadeDownTime": "Време за затихване", - "frames": "Рамки", "zPosition": "Z-позиция", - "zPositionHelp": "Положителните числа отдалечават текста, отрицателните числа го приближават. Ако z-позицията е нула, тогава е 2D", - "export": "Експорт..." + "zPositionHelp": "Положителните числа отдалечават текста, отрицателните числа го приближават. Ако z-позицията е нула, тогава е 2D" }, "webVtt": { "setStylesForSelectedLinesTitle": "Задаване на WebVTT стилове за избрани линии", @@ -1476,11 +1587,61 @@ "ignoreFormatting": "Игнориране на форматирането", "ignoreFormattingHint": "Редове, които се различават само по таговете за форматиране, като или {\\an8}, не се броят за различни", "showOnlyDifferencesInText": "Само разлики в текста", + "ignoreNumbering": "Игнориране на номерацията", + "ignoreNumberingHint": "Редовете, които се различават само по номера си, не се смятат за различни", + "ignoreTimeDifferenceMs": "Толеранс на времето (ms)", + "ignoreTimeDifferenceMsHint": "Началните и крайните времена, които се различават с толкова милисекунди или по-малко, не се смятат за различни", + "compareDifferences": "Разлики", + "compareTextDifferences": "Разлики в текста", + "compareCurrent": "Текущ", + "compareReference": "Референтен", + "compareEditable": "Редактируем", + "compareEditableHint": "Промените, направени тук, се връщат в субтитрите, когато щракнете върху Приложи", + "compareReadOnly": "Само за четене", + "compareTakeFromReference": "Вземане на текста и времената от референтния", + "compareInsertFromReference": "Вмъкване на този ред в текущите субтитри", + "compareDeleteFromCurrent": "Изтриване на този ред от текущите субтитри", + "compareTakeText": "Вземане на текста", + "compareTakeTiming": "Вземане на времената", + "compareEditLine": "Редактиране на реда", + "compareEditHint": "Ctrl+Enter за запазване, Esc за отказ", + "compareEdited": "Редактиран", + "compareOnePendingChange": "1 чакаща промяна", + "compareXPendingChanges": "{0} чакащи промени", + "compareDiscardXChanges": "Отхвърляне на {0} промяна(и), направени в Сравнение?", + "compareXChangesApplied": "Сравнение: приложени {0} промяна(и)", + "compareChangeTextX": "#{0} текст от референтния", + "compareChangeTimingX": "#{0} времена от референтния", + "compareChangeTextAndTimingX": "#{0} текст и времена от референтния", + "compareChangeEditedX": "#{0} редактиран", + "compareChangeInsertedX": "#{0} вмъкнат", + "compareChangeDeletedX": "#{0} изтрит", + "compareSyncPoint": "Точка на синхронизация", + "compareSyncPointHint": "Точка на синхронизация - тези два реда винаги се показват като двойка, а редовете над и под тях се подравняват самостоятелно", + "compareSyncPickCurrent": "Точка на синхронизация: използване на този текущ ред", + "compareSyncPickReference": "Точка на синхронизация: използване на този референтен ред", + "compareSyncRemove": "Премахване на точката на синхронизация", + "compareClearXSyncPoints": "Изчистване на точките на синхронизация ({0})", + "compareSyncCurrentPickedX": "Текущ #{0} е избран за точка на синхронизация - изберете съответния референтен ред и щракнете върху Синхронизиране", + "compareSyncReferencePickedX": "Референтен #{0} е избран за точка на синхронизация - изберете съответния текущ ред и щракнете върху Синхронизиране", + "compareSyncWithCurrentX": "Синхронизиране с текущ #{0}", + "compareSyncWithReferenceX": "Синхронизиране с референтен #{0}", + "compareSyncApply": "Синхронизиране", + "compareSyncPointSetXY": "Точката на синхронизация е зададена: текущ #{0} и референтен #{1} вече се показват като двойка", + "compareSyncAlreadyPairedXY": "Текущ #{0} и референтен #{1} вече се показват като двойка със същото начало - няма какво да се синхронизира", + "compareSyncPointShiftedXYZW": "Точката на синхронизация е зададена: текущи #{0}-#{1} са изместени с {2}, за да започват с референтен #{3}", + "compareChangeSyncShiftXYZ": "#{0}-#{1} изместени с {2} (точка на синхронизация)", + "compareTimingDeltaHint": "Колко по-късно (+) или по-рано (−) започва и свършва референтният ред спрямо текущия", "loadXFromFile": "Зареждане на \"{0}\" от файл", + "compareLoadSavedFile": "Зареждане на запазения файл", "saveCompareHtmlTitle": "Запазване на HTML файл за сравнение", "pickMatroskaTrackX": "Избери песен на Matroska - {0}", "pickTransportStreamTrackX": "Изберете траектория на транспортния поток - {0}", + "chapters": "Глави", + "dvdVobFilesMissingX": "Липсват VOB файлове (намерени {0}%)", "pickMp4TrackX": "Избор на MP4 запис - {0}", + "pickMxfTrackX": "Избор на MXF писта - {0}", + "pickMpegTrackX": "Избор на MPEG писта - {0}", "rosettaProperties": "Свойства на IMSC с времеви текст Rosetta", "rosettaFontSize": "Размер на шрифта (височина на реда)", "propertyTimeBase": "Времева база", @@ -1490,7 +1651,6 @@ "propertyDefaultRegion": "Регион по подразбиране", "propertyStyleAttributeName": "Име на атрибута на стила", "propertyTimeCodeFormat": "Формат на времевия код", - "propertyFileExtension": "Разширение на файла", "propertyTopOrigin": "Най-често срещан произход", "propertyTopExtent": "Най-висока степен", "propertyBottomOrigin": "Долен произход", @@ -1556,7 +1716,8 @@ "findWhat": "Намери какво", "descriptionOptional": "Описание (по избор)", "invalidRegularExpressionX": "Невалиден регулярен израз: {0}", - "regularExpressionTooSlowX": "Регулярният израз се отказа след {0} секунди - правилото е пропуснато, опитайте по-прост модел" + "regularExpressionTooSlowX": "Регулярният израз се отказа след {0} секунди - правилото е пропуснато, опитайте по-прост модел", + "moveToCategory": "Преместване в категория" }, "find": { "searchTextWatermark": "Търсене на текст...", @@ -1569,7 +1730,9 @@ "replaceIn": "Замяна/търсене в", "replaceInTextAndOriginal": "Текст и оригинален текст", "replaceInTextOnly": "Само текст", - "replaceInOriginalOnly": "Само оригинален текст" + "replaceInOriginalOnly": "Само оригинален текст", + "replacedOneOccurrence": "Заменено е едно срещане", + "replacedXOccurrences": "Заменени срещания: {0}" }, "regularExpressionContextMenu": { "wordBoundary": "Граница на думата (\\b)", @@ -1597,6 +1760,7 @@ "xLinesYSubtitles": "{0} реда, {1} субтитри", "xLinesYSubtitlesZErrors": "{0} реда, {1} субтитри, {2} грешка(и)", "couldNotParseAsX": "Не можа да се анализира като {0}", + "readAsXUseAsY": "Източникът не можа да бъде прочетен като {1}, но можа да бъде прочетен като {0}.\nДа се използват ли редовете и да продължи запазването като {1}?", "selectedXCharactersYLines": "Избрани: {0} знака в {1} ред(а)", "replacedXOccurrences": "Заменени {0} съвпадения", "invalidRegularExpression": "Невалиден регулярен израз", @@ -1632,7 +1796,23 @@ "largeChangeWarning": "Голяма промяна - изглежда като пренаписване, прегледайте внимателно", "mismatchWarning": "Не прилича на оригинала - може да принадлежи към друга линия", "engineError": "AI енджина не можа да бъде достигнат: {0}", - "playCurrentLineHint": "Възпроизвеждане на избрания ред във видеоплейъра и пауза в края му (също така щракнете двукратно върху ред)" + "playCurrentLineHint": "Възпроизвеждане на избрания ред във видеоплейъра и пауза в края му (също така щракнете двукратно върху ред)", + "editAfterHint": "Щракнете върху текста на избрания ред, за да редактирате поправката, преди да я приложите (Enter запазва, Escape отказва)", + "template": "Шаблон", + "templatePlaceholder": "Изберете шаблон, който да замени подканата", + "templateStandard": "Стандартна коректура", + "templateSpeechToText": "Почистване след реч в текст", + "templateMachineTranslation": "Поправка на машинен превод", + "templateSdh": "SDH / за хора с увреден слух", + "referenceContext": "Референтен контекст", + "referenceContextInfo": "По избор. Имена, персонажи, речник, синопсис или бележки за стила - моделът приема това за меродавно за имена и термини. Изпраща се с всяка партида.", + "referenceContextPlaceholder": "Имена: Кайто, леля Мей, д-р Холис\nТермини: Шинкансен, уорп ядро\nСинопсис: Кайто пътува до Токио, за да намери изчезналия си баща.", + "generateContext": "Генериране с ИИ", + "generateContextHint": "Избраният ИИ двигател прочита субтитрите и изготвя чернова с имена, термини и синопсис - проверете резултата преди употреба", + "generatingContextPartXOfY": "Четене на част {0} от {1}...", + "generatingContextSynopsis": "Писане на синопсис...", + "replaceContextQuestion": "Да се замени ли текущият референтен контекст?", + "noContextGenerated": "ИИ двигателят не върна използваема референтна информация." }, "aiAssistant": { "title": "Асистент с изкуствен интелект", @@ -1678,6 +1858,7 @@ "removeLineBreaksPixelWidth": "Разделяне на субтитри, които се побират на един ред (ширина на пиксели)", "fixUppercaseIInsideLowercaseWords": "Коригирано е главното \"i\" в думи с малки букви (грешка в OCR)", "fixDoubleApostrophes": "Коригиране на двойните апострофи ('') в единични кавички (\")", + "fixMisreadQuotes": "Поправяне на апострофи, разпознати погрешно като двойни кавички (OCR, английски)", "addPeriods": "Добавяне на точка след редовете, където следващият ред започва с главна буква", "startWithUppercaseLetterAfterParagraph": "Започвайте с главна буква след параграфа", "startWithUppercaseLetterAfterPeriodInsideParagraph": "Започва се с главна буква след точка в параграфа", @@ -1698,8 +1879,9 @@ "addMissingQuotesExample": "\"Как си? -> \"Как си?\"", "removedEmptyLinesUnusedLineBreaksExample": "Има само един валиден ред!
-> Има само един валиден ред!", "fixCommasExample": ",, -> ,", - "removeLineBreaksExample": "Фу бар! -> Фу бар!", + "removeLineBreaksExample": "Фу
бар! -> Фу бар!", "fixDoubleApostrophesExample": "''Има двойни единични кавички'' -> \"Има единични двойни кавички\"", + "fixMisreadQuotesExample": "\"Hello' -> \"Hello\"", "addPeriodsExample": "Здравей, свят -> Здравей, свят.", "startWithUppercaseLetterAfterParagraphExample": "p1: Фубар! || p2: Фубар! -> p1: Фубар! || p2: Фубар!", "startWithUppercaseLetterAfterPeriodInsideParagraphExample": "Здравей! Как си? -> Здравей! Как си?", @@ -1789,6 +1971,8 @@ "fixMinDurationMs": "Фиксирано минимално Траене (мс)", "doNotGoPastShotChange": "Не пропущай при смяната на кадър", "fixMaxDurationMs": "Фиксирано максимално Траене (мс)", + "fixMinDurationFrames": "Поправяне на минималното траене (кадри)", + "fixMaxDurationFrames": "Поправяне на максималното траене (кадри)", "minimumDurationMilliseconds": "Минимална продължителност (милисекунди):", "maximumDurationMilliseconds": "Максимална продължителност (милисекунди):", "maxDurationShouldBeHigherThanMinDuration": "Максималното Траене трябва да е по-голямо от минималното", @@ -1838,13 +2022,80 @@ "timeCodesExtractFailed": "Не можах да прочета времевите кодове от видеото", "timeCodesHint": "Прихваща сигнали към реалното време на кадрите на видеото, вместо да приема постоянна честота на кадрите. Изисква еднократно прочитане на цялото видео; резултатът се кешира." }, + "improveTimeCodes": { + "title": "Подобряване на времевите кодове (принудително подравняване)", + "aligner": "Подравняващ модел", + "alignerHint": "Моделът, който търси всеки ред в звука. Най-добрите избори за езика на субтитрите са изброени първи.", + "maxShift": "Макс. изместване (секунди)", + "maxShiftHint": "Колко може да са разсинхронизирани субтитрите. Ред, чието начало би се преместило повече, се оставя както е и се маркира. Независимо от стойността, отделен ред, който иска да се премести с повече от половин секунда повече от редовете около него, не се премества сляпо: той следва съседите си, ако всички те са се преместили, а иначе се предлага без отметка, за да го проверите.", + "adjustStartTimes": "Коригиране на началните времена", + "adjustEndTimes": "Коригиране на крайните времена", + "isolateSpeech": "Първо изолиране на речта (бавно)", + "isolateSpeechHint": "Премахва музиката и звуковите ефекти преди подравняването, така че подравняващият модел чува само диалога. Помага при редове, изговорени върху силна музика или екшън, но отнема приблизително колкото продължителността на самия звук с GPU - и многократно повече без такъв.", + "isolatingSpeech": "Изолиране на речта... (това отнема известно време)", + "isolateSpeechFailed": "Речта не можа да бъде изолирана - подравнено е спрямо оригиналния звук.", + "checkWithSpeechToText": "Проверка с реч в текст", + "checkWithSpeechToTextHint": "Транскрибира и звука и проверява всяко преместване спрямо мястото, където думите действително са чути. Големите премествания, потвърдени от речта, се отмятат автоматично, а преместванията встрани от мястото, където се чува редът, остават без отметка, за да ги проверите. Показва също колко от всеки ред е чуто - малкият дял означава, че текстът се различава от казаното. Субтитри, които са разсинхронизирани повече от макс. изместване - постоянно отместване, отклонение поради несъответствие в честотата на кадрите или скокове при изрязани сцени - първо се синхронизират по чутите думи. Използва Crisp ASR Parakeet, което отнема няколко минути за пълнометражен филм с GPU.", + "checkWithSpeechToTextNotAvailableX": "Проверката с реч в текст не е налична за {0}", + "transcribingToCheck": "Транскрибиране за проверка на подравняването...", + "speechToTextFailed": "Реч в текст не успя - времевите кодове не бяха проверени спрямо нея.", + "speechToTextX": "Реч в текст: {0}", + "speechToTextConfirmedXDisputedY": "Реч: {0} потвърдени, {1} оспорени", + "hardlyHeardX": "Реч в текст чу само {0}% от думите в субтитрите. На езика, който се говори, ли са субтитрите? Подравняващият модел не може да позиционира превод.", + "heard": "Чуто", + "heardHint": "Колко от реда е чул модулът реч в текст. Малкият дял означава, че текстът се различава от казаното - там подравняващият модел най-вероятно греши.", + "statusConfirmedBySpeech": "Потвърдено от речта", + "statusDisputedBySpeech": "Чуто там, където беше", + "statusMovedWithSync": "Преместено със синхронизацията", + "syncedFirstXY": "Първо синхронизирано: {0} s в началото, {1} s в края", + "showSpeechOnly": "Показване само на речта", + "showSpeechOnlyHint": "Изчертава подравнената форма на вълната от звука с премахнати музика и звукови ефекти; оригиналната форма на вълната остава непроменена за сравнение. Налично, след като речта е изолирана - тук или с \"Показване само на речта\" във формата на вълната на главния прозорец.", + "align": "Подравняване", + "original": "Оригинал", + "aligned": "Подравнено", + "intro": "Изберете подравняващ модел и натиснете \"Подравняване\", за да бъде потърсен всеки ред в звука и времевите му кодове да бъдат прецизирани.", + "extractingAudio": "Извличане на звука...", + "aligningBatchXOfY": "Подравняване... партида {0} от {1}", + "extractAudioFailed": "Звукът не можа да бъде извлечен с ffmpeg.", + "modelWillBeDownloaded": "{0} ще бъде изтеглен", + "summaryXRetimedYKeptZSkipped": "Пренастроени: {0} · Запазени: {1} · Без реч: {2}", + "meanShiftX": "Средно изместване: {0} ms", + "toCheckX": "За проверка: {0}", + "largeMovesToCheckX": "{0} ред(а) биха се преместили много самостоятелно и са оставени без отметка - чуйте ги (F5 пуска подравнения ред, Shift+F5 оригинала) и отметнете правилните.", + "linesToCheckX": "{0} ред(а) са оставени без отметка за проверка - чуйте ги (F5 пуска подравнения ред, Shift+F5 оригинала) и отметнете правилните.", + "changeXOfY": "Промяна {0} от {1}", + "previousChange": "Предишна промяна", + "nextChange": "Следваща промяна", + "startShift": "Начало", + "endShift": "Край", + "statusRetimed": "Пренастроен", + "statusMovedWithNeighbours": "Преместен заедно със съседите си", + "statusLargeMoveUnconfirmed": "Голямо преместване - чуйте, отметнете за прилагане", + "statusUnchanged": "Вече е на мястото си", + "statusNoSpeech": "Без реч", + "statusShiftTooLarge": "Запазен - твърде голямо изместване", + "statusFailed": "Запазен - подравняването не успя", + "statusAdjustedByHand": "Коригиран ръчно", + "alignedWaveformHint": "Плъзнете ред, за да го преместите, или плъзнете краищата му, за да промените началото или края му.", + "playLine": "Пускане на реда", + "undoAdjustment": "Отмяна на корекцията", + "playPause": "Пускане / пауза", + "playAligned": "Подравнено", + "playAlignedHint": "Пускане на избрания ред с подравнените му времеви кодове", + "apply": "Приложи", + "applyHint": "Махнете отметката от ред, за да запазите оригиналните му времеви кодове. Щракнете двукратно върху ред, за да го пуснете. Редовете могат също да се преместват или преоразмеряват в подравнената форма на вълната." + }, "beautifyTimeCodesProfile": { "title": "Редактиране на профил за разкрасяване на времеви кодове", "loadPreset": "Зареждане на предварително зададена настройка", "presetDefault": "По подразбиране", "presetNetflix": "Netflix", "presetSdi": "SDI", - "general": "Общи", + "saveAsProfile": "Запазване като профил...", + "deleteProfile": "Изтриване на профила", + "profileName": "Име на профила", + "overwriteProfileX": "Профил с име \"{0}\" вече съществува. Да бъде ли презаписан?", + "deleteProfileX": "Изтриване на профил \"{0}\"?", "gap": "Интервал", "gapSuffix": "рамки (също така актуализира полетата с ненулева празнина по-долу)", "milliseconds": "мс", @@ -1875,6 +2126,10 @@ "bridgeGaps": { "bridgeGapsSmallerThan": "Пролуки между мостовете по-малки от (ms)", "minGap": "Минимална празнина (ms)", + "bridgeGapsSmallerThanFrames": "Запълване на интервали, по-малки от (кадри)", + "minGapFrames": "Минимален интервал (кадри)", + "calculateBridgeGapsSmallerThanDotDotDot": "Изчисляване на границата за запълване на интервали от честота на кадрите...", + "useXMsAsBridgeGapsSmallerThan": "Запълване на интервали, по-малки от \"{0}\" милисекунди?", "numberOfSmallGapsBridgedX": "Брой преодоляни малки пролуки: {0}", "percentFoPrevious": "Разлика спрямо предишния период (%)", "gapChange": "Промяна на разликата" @@ -1908,6 +2163,7 @@ "addColor": "Добавете цвят", "deleteLinesWithSpecificActorsOrStyles": "Изтриване на редове с актьори или стилове (разделете няколкото със запетая)", "useSourceStylesIfPossible": "Използвайте стилове на изходния код, ако е възможно", + "keepSourceEmbeddedFonts": "Запазване на вградените шрифтове от източника (footer)", "editStyles": "Редактиране на стилове", "editProperties": "Редактиране на свойства", "editAttachments": "Редактиране на прикачени файлове", @@ -1939,10 +2195,50 @@ "convertColorsToDialogReBreakLines": "Прекъсване на линиите", "snapTimeCodesToFramesInfo": "Закръглява всяко начално и крайно време до най-близкия кадър. Честотата на кадрите се чете от видеофайл със същото име като файла със субтитрите, освен ако не е избрана фиксирана честота на кадрите.", "addFolderDotDotDot": "Добавяне на папка...", + "addFolderRecursiveDotDotDot": "Добавяне на папка рекурсивно...", "selectFolderToConvert": "Изберете папка с файлове за конвертиране", "includeSubfolders": "Включване на подпапки при добавяне на папка", + "videoFilesWhenAddingFolder": "Видео файлове (mkv, mp4, ts...) при добавяне на папка", + "videoFilesAsk": "Питане", + "videoFilesInclude": "Добавяне на субтитровите им писти", + "videoFilesSkip": "Пропускане", + "folderContainsXVideoFiles": "Папката съдържа {0} видео файл(а) (mkv, mp4, ts...).\n\nДа се добавят ли и вградените им субтитрови писти, или само файловете със субтитри?", + "addVideoFiles": "Добавяне на видео файловете", + "skipVideoFiles": "Само файлове със субтитри", + "doNotAskAgainVideoFiles": "Без повторно питане (промяна в настройките на Пакетно конвертиране)", + "settingsSectionOutput": "Изход", + "settingsSectionAddingFiles": "Добавяне на файлове", + "settingsSectionImageBasedSubtitles": "Субтитри, базирани на изображения (OCR)", + "settingsSectionOther": "Други", "keepSourceFileTimestamp": "Запазване на датата/часа на изходния файл в изходните файлове", - "scanningFolderX": "Сканиране на {0}..." + "preventSleepWhileConverting": "Предотвратяване на заспиването на компютъра по време на конвертиране", + "scanningFolderX": "Сканиране на {0}...", + "transportStreamSettings": "Настройки на Transport Stream", + "transportStreamSettingsDotDotDot": "Настройки на Transport Stream...", + "transportStreamSettingsInfo": "Отнася се за субтитрови писти, извлечени от Transport Stream файлове (.ts/.m2ts). Позицията и размерът на видеото засягат само DVB писти с изображения, експортирани във формат, базиран на изображения.", + "transportStreamOverrideXPosition": "Замяна на оригиналната позиция X", + "transportStreamOverrideYPosition": "Замяна на оригиналната позиция Y", + "transportStreamOverrideVideoSize": "Замяна на оригиналния размер на видеото", + "transportStreamBottomMargin": "Долно поле", + "transportStreamFileNameEnding": "Окончание на името на файла", + "transportStreamFileNameEndingInfo": "Добавя се преди разширението за всяка извлечена писта. Оставете празно, за да се използва обичайната езикова наставка.", + "transportStreamOnlyTeletext": "Само телетекст", + "transportStreamGetSizeFromVideo": "Вземане на размера от видеото...", + "twoLetterLanguageCodeUppercase": "Двубуквен код на езика (главни букви)", + "threeLetterLanguageCodeUppercase": "Трибуквен код на езика (главни букви)", + "formatContainsDotDotDot": "Форматът съдържа...", + "extensionIsDotDotDot": "Разширението е...", + "statusIsError": "Статусът е грешка", + "forcedTracksOnly": "Само принудителни писти", + "xConvertedYFailedInZ": "{0:#,###,##0} конвертирани, {1:#,###,##0} неуспешни за {2}", + "xNotProcessed": "{0:#,###,##0} необработени", + "removeUnicodeControlCharactersTitle": "Премахване на Unicode контролни знаци", + "removeUnicodeControlCharactersInfo": "Премахва невидимите Unicode знаци за управление на посоката (LRM, RLM, LRE, RLE, PDF, LRO и RLO) от текста и заменя неразделящите интервали с обикновени интервали.", + "preset": "Предварителна настройка", + "savePresetDotDotDot": "Запазване на предварителна настройка...", + "deletePreset": "Изтриване на предварителната настройка", + "presetName": "Име на предварителната настройка", + "deletePresetX": "Изтриване на предварителна настройка \"{0}\"?" }, "changeCasing": { "fixNames": "Коригиране на имена", @@ -2027,10 +2323,13 @@ }, "mergeLinesWithSameText": { "maxMsBetweenLines": "Максимален брой милисекунди между редовете", - "includeIncrementingLines": "Включване на редове с нарастващ текст" + "maxFramesBetweenLines": "Макс. кадри между редовете", + "includeIncrementingLines": "Включване на редове с нарастващ текст", + "includeRollUpCaptions": "Включване на roll-up (превъртащи се) надписи" }, "mergeLinesWithSameTimeCodes": { "maxMsDifference": "Максимална разлика (милисекунди)", + "maxFramesDifference": "Макс. разлика (кадри)", "makeDialog": "Създавайте диалози" }, "netflixCheckAndFix": { @@ -2130,12 +2429,29 @@ "letterboxCustom": "Височина на лентата по избор", "barHeightPx": "Височина на лентата (px)", "titleSafePercent": "Безопасно за заглавие (%)", + "moveCaptions": "Преместване на надписите", + "moveCaptionsDotDotDot": "Преместване на надписите...", + "moveCaptionsTo": "Преместване на надписите", + "moveCaptionsIntoBars": "В черните ленти (извън картината)", + "moveCaptionsIntoPicture": "Вътре в картината (извън лентите)", + "moveCaptionsOffset": "Отместване от ръба (px)", + "letterboxBarHeightXPxYOfZCaptionsWillMove": "Височина на лентата: {0} px - {1} от {2} надписа ще бъдат преместени", + "removeFadeInOut": "Премахване на избледняване/появяване", + "removeFadeInOutXLinesRemoved": "{0} реда с избледняване са обединени с редовете, към които принадлежат", + "removeFadeInOutNothingFound": "Не са намерени редове с избледняване/появяване", "positionSummary": "{0}×{1} - {2} субтитри - височина на лентата: {3} пиксела", "xInActivePicture": "{0} на снимката", "xInTopBar": "{0} в горната лента", "xInBottomBar": "{0} в долната лента", "noImageSubtitlesLoaded": "Няма заредени субтитри за изображението", - "imageBasedFormatNotSupported": "Форматът на субтитрите, базиран на изображение, не е намерен/не се поддържа." + "imageBasedFormatNotSupported": "Форматът на субтитрите, базиран на изображение, не е намерен/не се поддържа.", + "frameRateDeclaredInFile": "Честота на кадрите, обявена в Blu-ray sup файла", + "frameRateDetectedFromTimeCodesX": "Честота на кадрите, открита от времевите кодове на Blu-ray sup (файлът обявява {0})", + "frameRateFromVideo": "Честота на кадрите на видеото", + "frameRateCurrent": "Текуща честота на кадрите", + "frameRateSetManually": "Ръчно зададена честота на кадрите", + "frameRateUsageInfo": "Експортите в Blu-ray sup, BDN XML, DOST и FCP се записват с тази честота на кадрите. Тя не променя никакви времена - за това използвайте \"Промяна на честотата на кадрите\".", + "useVideoFrameRateXInsteadOfY": "Честотата на кадрите на видеото е {0}, но за субтитрите е зададена {1}.\n\nДа се използва ли честотата на кадрите на видеото ({0})?" }, "removeTextForHearingImpaired": { "interjections": "Междуметия", @@ -2165,6 +2481,7 @@ "pickFontNameInstalledFonts": "Инсталирани шрифтове", "pickFontNameCollectedFonts": "Събрани шрифтове", "colorPickerTitle": "Изберете цвят", + "colorPickerBrightness": "Яркост", "filterLayersTitle": "Филтриране на слоеве за показване", "filterLayersHideFromWaveform": "Скриване от формата на вълната/спектрограмата", "filterLayersHideFromSubtitleGrid": "Скриване от мрежата със субтитри", @@ -2215,7 +2532,21 @@ "correctWordsX": "Правилни думи: {0}", "namesX": "Имена: {0}", "addedToDictionaryX": "Добавено към речника: {0}", - "doNotShowThisAgain": "Не показвай това съобщение отново" + "doNotShowThisAgain": "Не показвай това съобщение отново", + "editUseAlwaysList": "Редактиране на списъка \"Използвай винаги\"...", + "useAlwaysListTitle": "Проверка на правописа - списък \"Използвай винаги\"", + "useAlwaysListHeader": "Списък \"Използвай винаги\"", + "useAlwaysListDescription": "Думи, които проверката на правописа заменя автоматично, запазени чрез \"Промени всички\" и \"Използвай винаги\". Двойката се използва само когато думата е изписана грешно.", + "useAlwaysWord": "Грешно изписана дума", + "xPairs": "{0:#,##0} двойки", + "xUnusedPairs": "{0:#,##0} никога неизползвани", + "useAlwaysUnusedHint": "Никога не е използвана - тази дума е изписана правилно (или е име или потребителска дума), затова проверката на правописа никога не я отбелязва", + "removeUnused": "Премахване на неизползваните", + "useAlwaysEmpty": "Все още няма двойки \"Използвай винаги\" за този език", + "useAlwaysEmptyHint": "Двойките се добавят, когато щракнете върху \"Промени всички\" или \"Използвай винаги\" в проверката на правописа.", + "useAlwaysNoMatch": "Няма двойки, отговарящи на търсенето", + "useAlwaysSameWord": "Заместителят трябва да се различава от думата.", + "useAlwaysRememberOff": "\"Запомняне на списъка 'Използвай винаги'\" е изключено в Настройки, затова проверката на правописа не използва и не запазва този списък." }, "video": { "burnIn": { @@ -2228,6 +2559,7 @@ "sampleRate": "Честота на дискретизация", "audio": "Аудио", "preset": "Предварително зададено", + "tune": "Tune", "pixelFormat": "Пикселов формат", "crf": "Хронична ренална хипертония (ХРХ)", "useSource": "Използвай източника", @@ -2239,7 +2571,9 @@ "outputProperties": "Изходни свойства...", "videoFileSize": "Размер на видеофайла", "oneBox": "Една кутия", - "logoInfo": "Изберете PNG изображение и го плъзнете, за да го позиционирате върху видеото." + "logoInfo": "Изберете PNG изображение и го плъзнете, за да го позиционирате върху видеото.", + "noSubtitlesGenerateAnyway": "Няма редове със субтитри за вграждане.\n\nДа се генерира ли видеото без субтитри?", + "noSubtitlesInXItemsGenerateAnyway": "{0} елемент(а) от пакета нямат редове със субтитри за вграждане.\n\nДа се генерират ли тези видеоклипове без субтитри?" }, "videoTransparent": { "title": "Генериране на прозрачно видео със субтитри" @@ -2250,6 +2584,21 @@ "translateToEnglish": "Превод на английски", "transcribing": "Транскрибиране...", "transcribingXOfY": "Транскрибиране на {0} от {1}...", + "linesTranscribedXOfYFailedZ": "Транскрибирани са {0} от {1} реда - {2} са неуспешни или без реч.\n\nНеуспешните редове запазват текущия си текст. Да се приложат ли транскрибираните редове, или да се опита отново с неуспешните (можете първо да смените двигателя или настройките)?", + "linesTranscribedXOfYCancelled": "Транскрибирането беше отменено след {0} от {1} реда.\n\nДа се приложат ли транскрибираните редове? Останалите редове запазват текущия си текст.", + "noLinesTranscribed": "Нито един от {0} реда не можа да бъде транскрибиран.\nМоля, проверете дневника на инструментите за подробности.", + "applyTranscribedLines": "Прилагане на транскрибираните", + "retryFailedLines": "Нов опит с неуспешните", + "isolateSpeech": "Изолиране на речта (бавно)", + "isolateSpeechHint": "Премахва музиката и звуковите ефекти преди транскрибирането. Дава по-малко пропуснати редове и по-добро тайминг при звук със силна музика, но отнема приблизително колкото продължителността на самия звук с GPU - и многократно повече без такъв.", + "isolatingSpeech": "Изолиране на речта...", + "isolateSpeechFailed": "Речта не можа да бъде изолирана - транскрибира се оригиналният звук.", + "detectSpeakers": "Разпознаване на говорещите", + "detectSpeakersHint": "Започва всеки ред с говорещия, например \"(speaker 0)\", чрез диаризацията Sortformer на NVIDIA (до 8 говорещи). Работи най-добре с бекендове, които разделят редовете при смяна на говорещия, като Parakeet и Cohere.", + "detectSpeakersNeedsNewerCrispAsr": "\"Разпознаване на говорещите\" изисква Crisp ASR v{0} или по-нова версия, но е инсталирана v{1}.\n\nАктуализирайте Crisp ASR (бутона със зъбно колело до двигателя) или изключете \"Разпознаване на говорещите\".", + "xNeedsNewerCrispAsr": "{0} изисква Crisp ASR v{1} или по-нова версия, но е инсталирана v{2}.\n\nДа се изтегли и инсталира ли актуализацията сега?", + "vad": "VAD", + "vadHint": "Откриване на гласова активност: транскрибира се само речта, разделена там, където говорещият прави пауза.\nАвтоматично: Silero и само за двигателите, които се нуждаят от него (Cohere, Mega, Index-Echo).\nSilero, FireRedVAD, Whisper-VAD, WebRTC: винаги включени, за всеки Crisp ASR двигател. FireRedVAD е най-точен, Whisper-VAD дава най-малко и най-чисти разрези, но е най-бавен.\nIndex-Echo винаги използва Silero. --vad, --vad-model или --chunk-seconds в разширените настройки имат предимство.", "inputLanguage": "Език за въвеждане", "advancedWhisperSettings": "Разширени настройки на \"Шепот\"", "downloadingSpeechToTextEngine": "Изтегляне на преобразуване реч в текст", @@ -2333,6 +2682,19 @@ "videoEncodingSettings": "TTS - Настройки за видео кодиране", "elevenLabsSettings": "TTS - настройки на ElevenLabs", "elevenLabsSettingsResetHint": "Нулиране на настройките на ElevenLabs до стойностите по подразбиране", + "provider": "Доставчик", + "openAiCompatibleSettings": "TTS - настройки за съвместими с OpenAI", + "customServerUrl": "URL адрес на потребителски сървър", + "customServerUrlHint": "Пълен адрес за реч на съвместим с OpenAI сървър, използван, когато доставчикът е \"Custom\", напр. http://localhost:8880/v1/audio/speech", + "customModels": "Потребителски модели", + "customModelsHint": "Разделени със запетая имена на модели, предлагани, когато доставчикът е \"Custom\"", + "customVoices": "Потребителски гласове", + "customVoicesHint": "Разделени със запетая имена на гласове, използвани от доставчика \"Custom\" и от модели на OpenRouter, които не публикуват списък с гласове", + "instructions": "Инструкции", + "instructionsHint": "Незадължителен стил на говорене, напр. \"Говори със спокоен, топъл глас\". Изпраща се само към модели, които го поддържат (не tts-1/tts-1-hd)", + "openAiSpeedHint": "Скорост на речта от 0.25 до 4.0 (1.0 е нормална). Не всеки модел я поддържа", + "outputFormat": "Изходен формат", + "outputFormatHint": "Аудио формат, изискван от сървъра. \"Auto\" иска MP3 и превключва на PCM за модели, които връщат само PCM (напр. Gemini TTS)", "regenerateAudio": "Регенериране на аудио", "autoContinuePlaying": "Автоматично продължаване на играта", "playLine": "Линия за игра", @@ -2341,6 +2703,13 @@ "addingAudioToVideoFileDotDotDot": "Добавяне на аудио към видеофайл...", "preparingMergeDotDotDot": "Подготовка на сливане...", "importVoiceDotDotDot": "Импортиране на глас...", + "renameVoiceDotDotDot": "Преименуване на гласа...", + "renameVoiceTitle": "Преименуване на гласа", + "voiceXCouldNotBeRenamedX": "Гласът '{0}' не можа да бъде преименуван: {1}", + "deleteVoiceDotDotDot": "Изтриване на гласа...", + "deleteVoiceTitle": "Изтриване на гласа", + "deleteVoiceXQuestion": "Изтриване на гласа '{0}'?\n\nРеферентният запис и файловете му се премахват от диска.", + "voiceXCouldNotBeDeletedX": "Гласът '{0}' не можа да бъде изтрит: {1}", "voiceImportSuccessTitle": "Гласът е импортиран", "voiceXImported": "Гласът \"{0}\" е импортиран успешно", "voiceXCouldNotBeImported": "Гласът „{0}“ не можа да бъде импортиран - вижте лога за подробности", @@ -2350,6 +2719,49 @@ "dropAudioFileHereToImportVoice": "Пуснете аудио файл тук, за да импортирате глас", "dropAudioFileHereHint": ".wav или .mp3", "voiceCloneTranscriptTitle": "Въведете препис на аудиото (необходим за клониране на глас)", + "voiceManagerTitle": "TTS - Управление на гласове", + "voiceManagerDotDotDot": "Управление на гласове...", + "voiceManagerMenuItem": "Управление на TTS гласове...", + "speakFromCurrentLineTitle": "Четене от текущия ред", + "speakFromCurrentLineDotDotDot": "Четене от текущия ред...", + "speakFromCurrentLineHint": "Чете редовете на глас един след друг, започвайки от текущия ред. Натиснете Escape или щракнете върху друг ред, за да спрете.", + "stopSpeaking": "Спиране на четенето", + "speakingLineX": "Четене на ред {0}... (Escape за спиране)", + "playWithSpeechTitle": "Пускане с реч", + "playWithSpeechDotDotDot": "Пускане с реч...", + "playWithSpeechHint": "Пуска видеото и изговаря всеки ред, когато видеото стигне до него. Паузирайте и превъртайте както обикновено; натиснете Escape, за да спрете.", + "lowerVideoVolumeWhileSpeaking": "Намаляване на звука на видеото по време на речта", + "pauseVideoWhenSpeechRunsLate": "Пауза на видеото, когато речта навлезе в следващия ред", + "waitingForSpeechLineX": "Генериране на реч за ред {0}... (Escape за спиране)", + "voiceKindClone": "Клониран глас", + "voiceKindPreset": "Вграден", + "voiceKindModel": "Гласов модел", + "voiceKindOnline": "Онлайн", + "transcript": "Транскрипция", + "transcriptRequiredHint": "Задължително - двигателят чете тази транскрипция на записа (ref-text) при клониране", + "transcriptOptionalHint": "По избор - точната транскрипция на записа подобрява клонирането", + "saveTranscript": "Запазване на транскрипцията", + "searchVoices": "Търсене на гласове", + "loadingVoicesDotDotDot": "Зареждане на гласове...", + "noVoicesForEngine": "Няма гласове - импортирайте запис или изтеглете гласов пакет", + "noVoicesMatchFilter": "Няма гласове, отговарящи на търсенето", + "copyVoiceTo": "Копиране на гласа в", + "copyVoiceToDotDotDot": "Копиране на гласа в двигател...", + "copyingVoiceXToYDotDotDot": "Копиране на гласа '{0}' в {1}...", + "voiceXCopiedToY": "Гласът '{0}' е копиран в {1}", + "voiceXCouldNotBeCopiedToY": "Гласът '{0}' не можа да бъде копиран в {1} - вижте дневника за подробности", + "voiceXAlreadyExistsInYContinue": "Глас с име '{0}' вече съществува в {1}.\n\nКопиране въпреки това?", + "openVoicesFolder": "Отваряне на папката с гласове", + "downloadVoicePacksDotDotDot": "Изтегляне на гласови пакети...", + "downloadVoicePacksTitle": "TTS - Изтегляне на гласови пакети", + "voicePackIntro": "Гласовите пакети са набори от кратки, чисти записи с транскрипции, готови за клониране. Всеки пакет се импортира в избрания двигател (вече наличните гласове се пропускат) и включва ATTRIBUTION.txt с лиценза и източниците си.", + "voicePackDetailsXVoicesYSizeZLicense": "{0} гласа · {1} · {2}", + "installToEngine": "Инсталиране в двигател", + "downloadAndInstall": "Изтегляне и инсталиране", + "selectAtLeastOneVoicePack": "Изберете поне един гласов пакет", + "downloadingXDotDotDot": "Изтегляне на {0}...", + "installingXDotDotDot": "Инсталиране на {0}...", + "xVoicesInstalledYSkipped": "Инсталирани гласове: {0} ({1} вече налични)", "useSpeechToTextDotDotDot": "Използване на преобразуване на реч в текст...", "autoCastMenuItem": "Намери гласове във видео и клонирай...", "autoCastSpeakersTitle": "Гласове, открити във видеото", @@ -2368,7 +2780,7 @@ "cloneVoicePerLinePreparing": "Вземи гласа за всеки ред от видеото...", "cloneVoicePerLineNeedsVideo": "Клонирането на гласа на всеки ред изисква видеото, към което принадлежи субтитърът. Отворете видеото и опитайте отново.", "cloneVoicePerLineNoClips": "Не можа да се вземе аудио от видеото, така че няма от какво да се клонира. Проверете дали видеото има аудио запис.", - "cloneVoicePerLineNeedsOriginalSubtitleX": "{0} може да клонира ред само когато знае какво се казва във видеото на този ред, което произлиза от субтитрите на оригиналния език. Не се зареждат оригинални субтитри, така че всеки ред ще бъде произнесен от първия импортиран глас, вместо от гласа във видеото.\n\nОтвори оригиналните субтитри (Файл > Отваряне на оригинала...) до превода, след което генерирайте отново.", + "cloneVoicePerLineTranscribeClipsXYZ": "{0} може да клонира ред само когато знае какво се казва във видеото на този ред. Това не е известно за {1} от {2} реда - обикновено идва от субтитрите на оригиналния език (Файл > Отвори оригинала...).\n\nДа се транскрибират ли тези редове от видеото с реч в текст сега?\n\nДа: изберете двигателя и езика, който се говори във видеото, и референтните клипове се транскрибират преди генерирането на речта.\nНе: генериране въпреки това - тези редове се изговарят с обикновен глас вместо с гласа от видеото.", "voiceCloneConsentTitle": "Клониране на глас - преди да започнете", "voiceCloneConsentHeader": "На път сте да клонирате глас", "voiceCloneConsentIntro": "Клонирането копира гласа на истински човек. Това е свързано с правила на повечето места, а в ЕС с правни задължения, които са ваша отговорност, а не на редактора на субтитри. Моля, прочетете това веднъж.", @@ -2385,10 +2797,29 @@ "advancedTtsSettings": "Разширени настройки за синтез на говор", "advancedTtsAudioProcessing": "Обработка на аудио", "advancedTtsOutput": "Изход", + "advancedTtsVoiceCloning": "Клониране на глас", + "cleanCloneReferences": "Почистване на референциите за клониране на глас (Sidon)", + "cleanCloneReferencesDescription": "Премахва музиката, шума и ехото от звука, от който се клонира глас: клипове, взети от видеото, и записи, импортирани в управлението на гласове. Използва модела за възстановяване на реч Sidon в audio.cpp (изтегляне от 940 MB). Клонираният глас вече не копира фона, но много тиха или застъпваща се реч може да прозвучи приглушено.", + "cleaningCloneReferencesDotDotDot": "Почистване на референциите за клониране на глас...", + "downloadSidonTitle": "Изтегляне на Sidon?", + "downloadSidonQuestionX": "Почистването на референциите за клониране на глас изисква модела за възстановяване на реч Sidon ({0}).\n\nДа се изтегли ли сега? Без него референциите се използват такива, каквито са.", "proAudioPostProcessing": "Професионална аудио последваща обработка", "proAudioPostProcessingDescription": "Прилага EQ топлина, noise gate, компресия, нормализиране на силата на звука (-16 LUFS) и постепенно увеличаване/угасване към всеки сегмент.", + "generatingSpeechSegmentXOfY": "Генериране на реч: сегмент {0} от {1}", + "adjustingSpeedSegmentXOfY": "Коригиране на скоростта: сегмент {0} от {1}", + "mergingAudioSegmentXOfY": "Обединяване на звука: сегмент {0} от {1}", + "postProcessingSegmentXOfY": "Последваща обработка: сегмент {0} от {1}", "audioDucking": "Заглушаване на звука", "audioDuckingDescription": "Намалява силата на звука на оригиналното видео и го смесва с TTS аудиото, така че оригиналният саундтрак все още е слабо чуваем.", + "removeOriginalSpeech": "Премахване на оригиналната реч (бавно)", + "removeOriginalSpeechDescription": "Запазва музиката и звуковите ефекти на оригиналното видео, но премахва речта му и смесва новата реч върху тях. Отнема приблизително колкото продължителността на самото видео с GPU и многократно повече без такъв. При включено приглушаване на звука силата му се прилага към музиката и ефектите. Прилага се само когато речта се добавя към видео файла.", + "removingOriginalSpeech": "Премахване на оригиналната реч...", + "removeOriginalSpeechFailed": "Оригиналната реч не можа да бъде премахната - речта беше добавена към видеото без това.", + "speechRemovalModel": "Модел", + "speechRemovalModelDescription": "Най-доброто качество запазва най-много от музиката и звуковите ефектите. Бързият е около три пъти по-бърз, като се губи малко повече от музиката.", + "speechRemovalBestQuality": "Най-добро качество (Mel-Band RoFormer)", + "speechRemovalFast": "Бърз (HTDemucs)", + "videoTruncatedWarning": "Картината в новия видео файл спира след {0}, но оригиналното видео е с продължителност {1} - картината ще замръзне, докато звукът продължава.\n\nФайлът беше запазен. Вижте error-log.txt в папката с данни на Subtitle Edit за изхода на ffmpeg.", "originalVolumePercent": "Първоначален обем %", "vadSilenceCompression": "VAD компресия на тишината", "vadSilenceCompressionDescription": "Скъсява паузите между думите преди промяна на темпото. Използва разпознаване на гласова активност, за да компресира само паузите в заглушаването, като същевременно запазва речта непокътната. Това е предпочитаната първа стъпка — намалява Траене без загуба на качество.", @@ -2471,7 +2902,9 @@ "timeCodeFormatColon": "Формат на времевия код:", "shotChangesList": "Списък с промени в кадри", "shotChangesClearQuestion": "Сигурни ли сте, че искате да изчистите всички промени в кадрите?", - "deleteSelectedShotChangeQuestion": "Сигурни ли сте, че искате да изтриете избраната промяна на кадъра?" + "deleteSelectedShotChangeQuestion": "Сигурни ли сте, че искате да изтриете избраната промяна на кадъра?", + "shotChanges": "Промени в кадъра", + "exportShotChanges": "Експортиране на промените в кадъра" }, "chapters": { "title": "Глави", @@ -2501,7 +2934,6 @@ "scaleTimesDescription": "Мащабирай времето на всяка глава от една кадрова честота към друга.", "fromFrameRate": "От", "toFrameRate": "До", - "apply": "Приложи", "writeToVideo": "Запис във видеото...", "writeToVideoTitle": "Запис глави към видео", "writeToVideoDescription": "Главите се записват в копие на видеофайла. Нищо не се прекодира, така че това е бързо и без загуби.", @@ -2511,7 +2943,6 @@ "writtenToX": "Глави, написани за {0}", "unableToWriteChapters": "Не е възможно да се запишат глави във видеофайла.", "chaptersSavedToX": "Главите са запазени в {0}", - "noVideoLoaded": "Няма заредено видео", "emptyListCallToAction": "Все още няма глави - добавете една на позицията на видеото или ги импортирайте от видео или файл." }, "videoOcr": { @@ -2536,10 +2967,10 @@ "runningOcrXY": "Изпълнява се OCR... {0}/{1}", "noLinesFoundTitle": "Няма намерени субтитри", "noLinesFoundMessage": "Не е намерен текст в областта за сканиране - опитайте да регулирате областта за сканиране, двигателя или минималната яркост.", + "ocrStoppedEarlyMessage": "OCR двигателят спря, преди да бъдат прочетени всички кадри, затова намерените досега редове са запазени, а останалите липсват.{0}{0}{1}", "linesFoundX": "Намерени са {0} реда", "refiningTimingXY": "Уточняване на времето... {0}/{1}", "fixOcrErrors": "Коригиране на OCR грешки", - "dictionary": "Речник", "editLineX": "Редактиране на ред {0}", "downloadDictionaryHint": "Изтеглете речник за проверка на правописа...", "framesPerSecondHint": "Колко кадъра в секунда се сканират. 5 обхваща нормални субтитри; по-високата стойност улавя много къси редове и затяга групирането, но изисква повече време за OCR.", @@ -2556,7 +2987,58 @@ "testOcr": "Тест на текущия кадър", "testOcrRunning": "Тестване на OCR върху текущия кадър...", "testOcrResultX": "Резултат от теста: {0}", - "testOcrNoTextFound": "Тест: не е намерен текст в областта за сканиране" + "testOcrNoTextFound": "Тест: не е намерен текст в областта за сканиране", + "engineSettings": "Настройки на OCR двигателя", + "engineBuiltIn": "Вграден в операционната система", + "engineExternalServer": "Външен сървър (инсталиран и управляван извън Subtitle Edit)", + "engineCloudApi": "Облачен API (изисква API ключ)", + "engineNothingToInstall": "Няма нищо за инсталиране", + "engineModelsMissing": "Двигателят е инсталиран, липсват модели", + "website": "Уебсайт" + }, + "backgroundMusic": { + "title": "Генериране на фонова музика", + "generateBackgroundMusicDotDotDot": "Генериране на фонова музика...", + "preset": "Предварителна настройка", + "prompt": "Подкана", + "tempoBpm": "Темпо (BPM)", + "generateSeconds": "Генериране (секунди)", + "seed": "Seed", + "randomSeed": "Случаен", + "outputLength": "Дължина на изхода", + "outputLengthVideoX": "{0} (дължина на видеото - музиката се повтаря безпроблемно)", + "musicVolumePercent": "Сила на звука на музиката (%)", + "removeExistingAudioTracks": "Премахване на съществуващите аудио писти", + "originalAudioVolumePercent": "Сила на оригиналния звук (%)", + "saveAudioDotDotDot": "Запазване на звука...", + "addToVideoDotDotDot": "Добавяне към видеото...", + "engineInfo": "ACE-Step 1.5 Turbo чрез audio.cpp (лиценз MIT) - работи локално", + "presetCooking": "Готвене / печене", + "presetTech": "Технологии / ревю на джаджи", + "presetForest": "Гора / природа", + "presetRelaxingPiano": "Релаксиращо пиано", + "presetLofiVlog": "Lo-fi влог", + "presetCorporate": "Корпоративно / обяснително", + "loadingModel": "Зареждане на модела...", + "composing": "Композиране...", + "generatingAudio": "Генериране на звука...", + "decodingAudio": "Декодиране на звука...", + "creatingLoop": "Създаване на безпроблемен цикъл...", + "resultLoopXYZ": "Цикъл: {0} при {1} BPM, съвпадение на прехода {2}%", + "resultNoLoopX": "Музиката е готова: {0}", + "addingMusicToVideo": "Добавяне на музика към видеото...", + "downloadModelTitle": "Изтегляне на музикалния модел?", + "downloadModelQuestionX": "Генерирането на музика изисква модела ACE-Step 1.5 ({0}).\n\nИзтегляне на модела?", + "downloadingModel": "Изтегляне на модела ACE-Step 1.5...", + "saveAudioTitle": "Запазване на фоновата музика", + "audioFileSavedX": "Аудио файлът е запазен: {0}", + "unableToGenerateMusic": "Не може да се генерира музика", + "unableToAddMusicToVideo": "Не може да се добави музика към видеото", + "outputLengthTextToSpeech": "Следва генерираната реч - музиката се повтаря безпроблемно и се приглушава, докато някой говори", + "addBackgroundMusic": "Добавяне на фонова музика", + "addBackgroundMusicHint": "Генериране на музика локално с ACE-Step 1.5 и смесването ѝ под речта - тя се приглушава, докато някой говори", + "generatingBackgroundMusicDotDotDot": "Генериране на фонова музика...", + "mixingBackgroundMusicDotDotDot": "Смесване на фоновата музика..." }, "goToVideoPosition": "Отиди на позиция видео", "goToVideoPositionDotDotDot": "Отидете на позицията на видеото...", @@ -2567,10 +3049,47 @@ "openSecondarySubtitleOnVideoPlayerDotDotDot": "Вторични субтитри за видеоплейъра, отвори...", "openSecondarySubtitleOnVideoPlayer": "Вторични субтитри (за видеоплейъра)", "removeSecondarySubtitleOnVideoPlayer": "Вторични субтитри за видеоплейъра, премахни", + "editSecondarySubtitleSettings": "Редактиране на настройките на вторите субтитри", + "editSecondarySubtitleSettingsDotDotDot": "Редактиране на настройките на вторите субтитри...", + "secondarySubtitleRememberSettings": "Запомняне на тези настройки", + "secondarySubtitleDoNotShowAgain": "Този диалог да не се показва повече", + "secondarySubtitleJustifyLines": "Двустранно подравняване на редовете", "openRecentVideo": "Отвори скорошно видео", "clearRecentVideos": "Изчистване на скорошни видеоклипове", "cutVideoTitle": "Изрежи видео", "cutVideoDotDotDot": "Изрязване на видео...", + "remuxVideoTitle": "Ремуксиране на видео", + "remuxVideoDotDotDot": "Ремуксиране на видео...", + "remuxVideoInputVideo": "Входно видео:", + "remuxVideoMultipleTracksRequiresMkv": "Няколко аудио или субтитрови писти изискват контейнера MKV. Изходният формат беше автоматично превключен на .mkv.", + "remuxVideoOutputFormat": "Изходен формат:", + "remuxVideoOutputFile": "Изходен файл:", + "remuxVideoRemuxing": "Ремуксиране на видеото и звука...", + "remuxVideoFinalizing": "Финализиране на изходния файл (преместване на индекса в началото)...", + "remuxVideoCompleted": "Видеото беше ремуксирано успешно.", + "remuxVideoFailed": "Ремуксирането на видеото не успя.", + "remuxVideoPleaseSelectBoth": "Моля, изберете и видео, и аудио файлове.", + "remuxVideoAssRequiresMkv": "ASS/SSA субтитрите изискват контейнера MKV, за да се запази цялото форматиране и стиловете.", + "remuxVideoSccRequiresMov": "Scenarist (.scc) субтитрите се вграждат като CEA-608 скрити надписи, които изискват контейнера MOV или MPG. Изходният формат беше автоматично превключен на .mov.", + "remuxVideoSccNotWithWebM": "Scenarist (.scc) скритите надписи изискват контейнера MOV, който не може да съдържа VP9/AV1 видеото на .webm файл. Премахнете .scc файла или го конвертирайте в друг формат на субтитри.", + "remuxVideoMpgMaxTwoCaptionTracks": "Един .mpg файл съдържа най-много две писти със скрити надписи (CC1 и CC3) - премахнете файлове със субтитри, така че да останат един или два.", + "remuxVideoMccRequiresMpg": "MacCaption (.mcc) субтитрите се вграждат като CEA-608/708 скрити надписи в MPEG-2 видеото, което изисква контейнера MPG. Изходният формат беше автоматично превключен на .mpg.", + "remuxVideoMpgReencodeVideoX": "Скритите надписи в .mpg файл изискват MPEG-2 видео, а видеото е {0}. Да се прекодира ли видеото в MPEG-2? Това отнема повече време и леко намалява качеството.", + "remuxVideoFileHasNoAudioX": "\"{0}\" няма аудио писта, затова не може да се използва като източник на звук.", + "remuxVideoAddingClosedCaptions": "Добавяне на скрити надписи...", + "remuxVideoSelectAudioTrack": "Избор на аудио писта", + "remuxVideoSelectAudioTrackFor": "Избор на аудио писта за {0}", + "remuxVideoSelectAudioTrackPrompt": "Файлът '{0}' съдържа {1} аудио писти. Моля, изберете аудио пистата за ремуксиране:", + "remuxVideoVideoFile": "видео", + "remuxVideoAudioFile": "аудио", + "remuxVideoAudioFiles": "Аудио файлове:", + "remuxVideoSubtitleFiles": "Файлове със субтитри (по избор, меки субтитри):", + "remuxVideoSelectAudioTrackDotDotDot": "Избор на аудио писта...", + "remuxVideoFilesX": "{0} файла", + "remuxVideoMixAudio": "Смесване на звука в една писта", + "remuxVideoVolumePercent": "Сила на звука %:", + "remuxVideoVolumeX": "Сила на звука {0}%", + "remuxVideoFastStart": "Оптимизиране за уеб стрийминг (преместване на индекса в началото - по-бавно)", "embedSubtitlesDotDotDot": "Добавяне/премахване на вградени субтитри...", "generateTimeCodes": "Генериране на времеви кодове", "checkeredImage": "Карирано изображение", @@ -2580,6 +3099,34 @@ "cutVideoSplitSegments": "Запазване на сегменти поотделно", "cutVideoAlsoCutSubtitle": "Също така изрязани субтитри", "cutVideoSubtitleFileGeneratedX": "Генериран файл със субтитри: {0}", + "cutVideoTransition": "Преход между сегментите", + "cutVideoTransitionDurationSeconds": "Траене (секунди)", + "cutVideoFadeInSeconds": "Появяване от черно в началото (секунди)", + "cutVideoFadeOutSeconds": "Избледняване до черно в края (секунди)", + "cutVideoPreviewTransition": "Преглед на прехода", + "cutVideoPreviewTransitionTitle": "Преглед на прехода", + "cutVideoPreviewNeedsJoin": "Няма съединяване за преглед - добавете поне два сегмента за обединяване или сегмент за изрязване от средата.", + "cutVideoTransitionInfoX": "Всеки преход застъпва частите, които съединява, затова изходът става с {0} секунди по-къс за всяко съединяване. Изрязаните субтитри се пренастройват съответно.", + "cutVideoTransitionFade": "Избледняване", + "cutVideoTransitionFadeBlack": "Избледняване през черно", + "cutVideoTransitionFadeWhite": "Избледняване през бяло", + "cutVideoTransitionFadeGrays": "Избледняване през сиво", + "cutVideoTransitionDissolve": "Разтваряне", + "cutVideoTransitionWipeLeft": "Изтриване наляво", + "cutVideoTransitionWipeRight": "Изтриване надясно", + "cutVideoTransitionWipeUp": "Изтриване нагоре", + "cutVideoTransitionWipeDown": "Изтриване надолу", + "cutVideoTransitionSlideLeft": "Плъзгане наляво", + "cutVideoTransitionSlideRight": "Плъзгане надясно", + "cutVideoTransitionSlideUp": "Плъзгане нагоре", + "cutVideoTransitionSlideDown": "Плъзгане надолу", + "cutVideoTransitionSmoothLeft": "Плавно наляво", + "cutVideoTransitionSmoothRight": "Плавно надясно", + "cutVideoTransitionCircleOpen": "Отваряне в кръг", + "cutVideoTransitionCircleClose": "Затваряне в кръг", + "cutVideoTransitionRadial": "Радиален", + "cutVideoTransitionPixelize": "Пикселизиране", + "cutVideoTransitionBlur": "Размазване", "importCurrentSubtitle": "Импортиране на текущи субтитри", "addRemoveEmbeddedSubtitlesTitle": "Добавяне/премахване на вградени субтитри", "addRemoveEmbeddedSubtitlesMp4Title": "Добавяне/премахване на вградени субтитри (MP4)", @@ -2595,6 +3142,7 @@ "embeddedTrackGeneratingVideoXY": "Генериране на видео... {0}% {1}", "embeddedTrackGeneratingVideo": "Генериране на видео...", "mp4FilesFilter": "MP4 файлове", + "embeddedTracksPickVideoHint": "Изберете видео файл или го пуснете тук, за да видите субтитровите му писти.", "viewMatroskaTrackX": "Преглед на Matroska трак- {0}", "resolutionSeparator": "х", "openFromUrlTitle": "Отваряне на видео файл от URL адрес", @@ -2641,12 +3189,15 @@ "reEncodeGeneratingDone": "Генерирането е завършено", "reEncodeGeneratedFilesX": "Генерирани файлове ({0}):", "reEncodeFfmpegParameters": "параметри на ffmpeg", + "analyzingVideoX": "Анализиране на видеото... {0}% {1}", + "analyzingVideoXofY": "Анализиране на видео {0}/{1}... {2}% {3}", + "backgroundImageFileNotSelected": "Не е избран файл с фоново изображение", + "backgroundImageFileDoesNotExistX": "Файлът с фоново изображение не съществува: {0}", "voices": "Гласове", "presets": "Предварителни настройки", "talkerX": "Говорител {0}", "baseModel": "Базов модел", "turboModel": "Турбо модел", - "ttsUpdateX": "Актуализиране на {0}", "ttsEngineNotInstalled": "{0} не е инсталиран", "ttsEngineUpdateAvailable": "{0} - налична е актуализация", "ttsEngineTooOldUpdateRequired": "{0} е твърде старо - необходима е актуализация", @@ -2669,9 +3220,17 @@ "cloneVoiceTo": "Клониране на глас към", "cloneVoiceNameTitle": "Име на новия глас", "cloneVoiceExtractFailed": "Не можа да се извлече аудио за избрания ред.", + "lineXHasNoDuration": "Ред {0} няма траене (крайното време не е след началното), затова няма звук за извличане.", + "audioClipsSkippedLinesX": "Не можа да бъде извлечен звук за тези редове, затова те бяха пропуснати: {0}", "toggleShotChange": "Превключви за промяната на кадъра", "resetWaveformZoomAndSpeed": "Нулиране мащабирането и скоростта на вълновата форма", "showOriginalSubtitle": "Показвай оригиналните субтитри", + "timelineGroupTracksBy": "Групиране на субтитровите писти по", + "timelineShowThumbnails": "Показване на миниатюри от видеото", + "showSpeechOnly": "Показване само на речта (бавно генериране)", + "isolatingSpeechForWaveformX": "Изолиране на речта за формата на вълната... {0}", + "isolatingSpeechForWaveformFailed": "Речта за формата на вълната не можа да бъде изолирана - вижте дневника на инструментите", + "speechOnlyWaveformReady": "Формата на вълната вече показва само речта", "showOnlyWaveform": "Показване само на вълнова форма", "showOnlySpectrogram": "Показване само на спектрограма", "showWaveformAndSpectrogram": "Показване на форма на вълната и спектрограма", @@ -2704,6 +3263,7 @@ "adjustSelectedLinesAndForward": "Коригиране на избраните редове и пренасочване напред", "toFrameRate": "До честота на кадрите", "fromFrameRate": "От честотата на кадрите", + "videoXFrameRateY": "{0} - {1} fps", "adjustAllTimes": "Коригиране на всички часове (показване по-рано/по-късно)", "showEarlier": "Покажи по-рано", "showLater": "Покажи по-късно", @@ -2713,7 +3273,8 @@ "syncPoints": "Точки за синхронизация", "pointSync": "Синхронизиране на точки", "pointSyncViaOther": "Синхронизиране на точки чрез други субтитри", - "syncPointCandidateInfo": "Редовете, започващи след {0}+ секунди тишина, са маркирани - те често представляват надеждни точки на синхронизация", + "syncPointCandidateAfterInfo": "Редовете, последвани от {0}+ секунди тишина, са маркирани - те често са надеждни точки на синхронизация", + "gapAfter": "Интервал след", "adjustAllShortcuts": "Клавишни комбинации:\n\n• Shift+ Left/Right: Мести с 10 ms\n• Ctrl+ Left/Right: Мести с 100 ms\n• Alt+ Left/Right: Мести с 500 ms", "adjustAllShortcutsFrames": "Клавишни комбинации:\n\n• Shift+ Left/Right: Мести с 1 кадър\n• Ctrl+ Left/Right: Мести с 10 кадъра\n• Alt+ Left/Right: Мести с 1 секунда", "offsetInSeconds": "Отместване в секунди", @@ -2733,6 +3294,7 @@ "lineMerge": "Сливане на редове", "delayInSecondsBetweenRequests": "Забавяне в секунди между заявките", "maxBytesPerRequest": "Максимален брой байтове на заявка", + "keepMusicLinesUntranslated": "Без превод на редове в музикални символи (♪ ... ♪)", "promptText": "Подканен текст", "resetPromptToDefault": "Възстановяване на подканата към по подразбиране", "translateEachLineSeparately": "Преведи всеки ред поотделно", @@ -2746,11 +3308,10 @@ "readyToTranslate": "Готов за превод", "translating": "Превежда се...", "translationComplete": "Преводът е завършен", + "translateInPlaceNoOriginal": "Превод на място (без създаване на оригинал)", "translationCancelled": "Преводът е анулиран", "swapLanguages": "Размяна на изходния и целевия език", "xIsAlreadyDownloadedReDownload": "{0} вече е изтеглен. Да се изтегли ли отново?", - "advancedDotDotDot": "Разширено...", - "advancedSettings": "Разширени настройки", "advancedSettingsSubtitle": "Опции за пакетно, контекстно и семплиращо обработване за усъвършенствания енджин llama.cpp", "context": "Context", "synopsis": "Синопсис", @@ -2774,6 +3335,7 @@ "useOnlyExtraServerParametersHint": "Стартирай llama-server с горните параметри, вместо със собствените настройки на Subtitle Edit - само моделът, хостът и портът са зададени", "serverRunningAtX": "Сървърът работи на адрес {0}", "customPromptHint": "Персонализирани инструкции ({0} = изходен език, {1} = целеви език); празно = вградена подкана", + "editTranslationHint": "Щракнете върху превода на избрания ред, за да го редактирате (Enter запазва, Escape отказва)", "llamaCppDownloadEngineAndModelPrompt": "llama.cpp изисква изтегляне на двигателя llama-server и модела за превод. Да се изтегли ли сега?", "llamaCppDownloadEnginePrompt": "llama.cpp изисква изтегляне на двигателя llama-server. Да се изтегли ли сега?", "llamaCppDownloadModelPrompt": "llama.cpp изисква избраният модел за превод да бъде изтеглен. Да се изтегли ли сега?" @@ -2844,6 +3406,7 @@ "showGridLines": "Показване на редовете на мрежата", "showHorizontalLineAboveToolbar": "Показване на хоризонталния ред над лентата с инструменти", "bookmarkColor": "Цвят на отметката", + "spellCheckHighlightColor": "Цвят на маркирането при проверка на правописа", "singleLineMaxLength": "Максимална дължина на един ред", "optimalCharsPerSec": "Оптимални символи/сек", "maxCharsPerSec": "Максимален брой символи/сек", @@ -2868,13 +3431,21 @@ "useDoNotBreakAfterList": "Използвайте списък \"не се прекъсва след\"", "newEmptyDefaultMs": "Стандартно Траене на новите субтитри (мс)", "timeCodeUpDownStepMs": "Време за ускорение/скъсяване (ms)", + "moveSelectedLinesStepMs": "Стъпка на клавишната комбинация за преместване на избраните редове (ms)", + "moveStartEndStepMs": "Стъпка на клавишната комбинация за преместване на начало/край (ms)", + "moveLinesShortenNeighbor": "Преместване на редове: скъсяване на предишния/следващия ред вместо застъпване", "promptBeforeDelete": "Подкана преди изтриване", "rememberPositionAndSize": "Запомни позицията и размера на прозореца", + "titleBarFullFileName": "Показване на пълния път до файла в заглавната лента", + "showRecentFiles": "Показване на последните файлове (за повторно отваряне)", "openLastFileOnStart": "Отваряне на последния скорошен файл при стартиране", "autoSave": "Автоматично запазване (запазване на отворения файл по време на редактиране)", "autoBackupOn": "Автоматично архивиране", "autoBackupIntervalMinutes": "Интервал за автоматично архивиране (минути)", "autoBackupDeleteAfterDays": "Запазване на автоматично архивиране (дни)", + "settingsBackupOn": "Автоматично архивиране на настройките", + "settingsBackupIntervalDays": "Интервал на архивиране на настройките (дни, 0 = при всяко стартиране)", + "settingsBackupMaxCount": "Брой архиви на настройките за пазене", "autoConvertToUtf8": "Автоматично конвертиране на кодирането в UTF-8 при отваряне", "autoTrimWhiteSpace": "Автоматично изрязване на празните пространства", "removeBlankLinesWhenOpening": "Премахване на празни редове при отваряне на субтитри", @@ -2891,7 +3462,10 @@ "colorGapTooShort": "Оцвети, ако разстоянието е твърде късо", "errorBackgroundColor": "Цвят на фона на грешката", "waveformDrawGridLines": "Начертайте редове на мрежата", + "waveformUseSkiaRenderer": "Използване на експериментален бърз рендерер", "waveformCenterVideoPositionAlsoWhenPaused": "Центриране на видеото и по време на пауза", + "waveformSelectCurrentSubtitleWhilePaused": "Избиране на текущия субтитър и по време на пауза", + "waveformRememberZoom": "Запомняне на мащаба (хоризонтален и вертикален)", "waveformShowToolbar": "Показване на лентата с инструменти", "waveformShowToolbarEdit": "Редактиране на лентата с инструменти...", "waveformShowToolbarEditLabel": "Панел с инструменти за вълнова форма/спектрограма", @@ -2939,18 +3513,25 @@ "webVttUseXTimestampMap": "WebVTT: използване на X-TIMESTAMP-MAP (отместване на времеви кодове при зареждане)", "assaAutoSetResolution": "ASSA: задаване на резолюция (PlayResX/PlayResY) от видеото, когато то се отвори", "assaAutoSetResolutionPrompt": "ASSA: питай преди да промениш резолюцията, за да съответства на видеото (в противен случай се преразглежда автоматично)", - "showStopButton": "Показване на бутона за спиране", - "showFullscreenButton": "Бутон за показване на цял екран", + "videoControls": "Видео контроли", + "videoControlsEdit": "Редактиране на видео контролите...", + "videoControlsPositionText": "Текст за позиция / траене", + "videoControlsVideoFileName": "Име на видео файла", + "videoControlsPlayerName": "Име на видео плейъра", "fullscreenHideControls": "Скриване на контролите за видео на цял екран", + "showSecondarySubtitleDialog": "Показване на диалога за стил при отваряне на втори файл със субтитри", + "rememberSecondarySubtitleFile": "Запомняне на втория файл със субтитри", "autoOpenVideoFile": "Автоматично отваряне на видеофайл при отваряне на субтитри", "downloadMpv": "Изтегляне на mpv", "downloadVlc": "Изтегляне на VLC", "allowSingleLetterShortcutsInTextbox": "Позволяване на еднобуквени клавишни комбинации в текстово поле", + "allowTextNavigationShortcutsInTextbox": "Разрешаване на клавишни комбинации с клавишите за навигация в текста (Ctrl+Left/Right, Home/End) в текстовото поле", "spellCheckEnglishTreatInApostropheAsIng": "Проверка на правописа: Думите, завършващи на \"in\", да се третират като \"ing\" (само на английски език)", "goToLineNumberSetsVideoPosition": "Номерът на реда за преминаване също задава позицията на видеото", "adjustAllTimesRememberLineSelectionChoice": "Настрой всички времена, запомни избора на ред", "mergeKeepEndTime": "Обединяване на редове: запазване на крайния час (позволява се припокриване със следващия субтитър)", "mergeKeepEndTimeOnlyAssa": "Обединяване на редове: запазване на крайния час само за \"Разширена подстанция Алфа\"", + "mergeAutoBreak": "Обединяване на редове: автоматично пренасяне на обединения текст (изключено запазва собствените нови редове)", "filesAndLogs": "Файлове и лог файлове", "showErrorLogFile": "Показване на лог файла с грешки", "showToolsLogFile": "Показване на лог файла на инструментите", @@ -2959,6 +3540,7 @@ "showAssaLayer": "Показване на кутията със слой ASSA", "waveformCursorColor": "Цвят на курсора/главата на вълновата форма", "waveformShotChangeColor": "Промяна на цвета на кадъра на вълновата форма", + "waveformGridColor": "Цвят на мрежата на формата на вълната", "waveformParagraphLeftColor": "Цвят на лявата рамка на вълновата форма", "waveformParagraphRightColor": "Цвят на дясната рамка на вълновата форма", "waveformFancyHighColor": "Вълнова форма, фантастичен висок цвят", @@ -2986,10 +3568,15 @@ "resetSyntaxColoring": "Нулиране на оцветяването на синтаксиса", "resetWaveform": "Нулиране на вълновата форма", "resetRules": "Нулиране на правилата", - "useFrameMode": "Използване на режим на кадриране (чч.мм.сс.фф)", + "timeCodeMode": "Режим на времевия код", + "timeCodeModeTime": "Време (hh:mm:ss,zzz)", + "timeCodeModeFrames": "Кадри (hh:mm:ss:ff)", + "timeCodeModeFrameNumbers": "Номера на кадри", + "frameModeSnapToVideoFrames": "Съхраняване на времената по видео кадри (не кадри в рамките на всяка секунда)", "textBoxLimitNewLines": "Ограничаване на броя редове в текстовото поле на субтитрите", "mpvOpenGl": "libmpv - OpenGL", "mpvSoftwareRendering": "libmpv - Софтуерно рендиране (бавно)", + "mpvMetalRendering": "libmpv - Metal (експериментално, изисква рестартиране)", "mpvWidRendering": "libmpv - Native Window ID рендиране (по-бързо на Windows/Linux)", "waveFormsAndSpectrogramFoldersContainsX": "Папките \"Waveforms\" и \"spectrogram\" съдържат {0}", "deleteWaveformAndSpectrogramFoldersQuestion": "Да се изтрият ли файловете \"Вълнови форми\" и \"Спектрограма\"?", @@ -3002,7 +3589,10 @@ "waveformExtractAudioSampleRate": "Извличане на аудио честота на дискретизация", "waveformExtractAudioSampleRateOriginal": "Оригинал (запази източника)", "waveformExtractAudioBitRate": "Извличане на аудио битрейт (MP3/M4A)", + "ffmpegUseCenterChannelOnly": "Използване само на предния централен канал за 5.1 звук (FFmpeg)", "vlcWidRendering": "libVLC - рендиране на Native Window ID", + "ffmpegSoftwareRendering": "FFmpeg", + "downloadFfmpegLibs": "Изтегляне на библиотеките на FFmpeg (за видео плейъра FFmpeg)", "subtitleGridEnterKeyAction": "Мрежа със субтитри Действие с клавиша Enter", "subtitleSingleClickAction": "Действие с едно щракване върху мрежата със субтитри", "subtitleDoubleClickAction": "Двойно щракване върху мрежата със субтитри", @@ -3030,6 +3620,7 @@ "subtitleGridFormattingShowFormatting": "Показване на форматирането", "subtitleGridFormattingShowTags": "Показване на етикети", "subtitleGridFormattingHideTags": "Скриване на етикети", + "subtitleGridFormattingShowFormattingKeepTags": "Показване на форматирането, запазване на невизуалните тагове", "waveformParagraphBackgroundColor": "Цвят на фона на субтитрите на вълновата форма", "waveformParagraphSelectedBackgroundColor": "Цвят на фона на избраните субтитри за вълнова форма", "waveformAllowOverlap": "Позволява се припокриване (при преместване/преоразмеряване)", @@ -3045,9 +3636,12 @@ "splitOddLinesAction": "Действие за разделяне на нечетни редове", "ocrUseWordSplitList": "OCR: използване на списък с разделяне на думи", "ocrGuessUnknownWords": "OCR: опитайте се да познаете непознати думи", + "spellCheckRememberUseAlwaysList": "Проверка на правописа: запомняне на списъка \"Използвай винаги\"", + "fixShortDisplayTimesAllowMoveStartTime": "Поправка на често срещани грешки: поправката на кратки времена за показване може да мести началното време", "speechToTextSelectedLinesPromptFirstTimeOnly": "Реч към текст: избрани редове, подкана за език/енджин само при първия път", "multipleReplaceShowDotDotDotButtons": "Множествена замяна: показване на бутони от контекстното меню", "gridFocusTextboxAfterInsertNew": "Мрежа: фокус върху текстовото поле след вмъкване на нов субтитър", + "undoRedoGoToChangedLine": "Отмяна/повтаряне: отиване на променения ред (и позицията във видеото)", "musicSymbol": "Музикален символ", "musicSymbolsToReplace": "Музикални символи за заместване (разделени със запетая)", "textToSpeechPromptMergeContinuationLines": "Текст към реч: подкана за сливане на редове за продължаване", @@ -3056,6 +3650,7 @@ "useFocusedButtonBackgroundColor": "Използвай цвета на фона на фокусирания бутон", "focusedButtonBackgroundColor": "Цвят на фона на фокусирания бутон", "forceCrLfOnSave": "Принудително CR+LF при запазване (файлове със субтитри)", + "linuxClipboardUseExternalTool": "Копиране на текст чрез xclip/wl-copy (запазва знаците с ударения)", "showFormatLimitWarning": "Предупреждение при запазване, когато редовете надвишават ограниченията на формата (напр. SCC 32 знака/ред)", "textBoxButtonShowAutoBreak": "Текстово поле: показване на бутон за автоматично прекъсване", "textBoxButtonShowUnbreak": "Текстово поле: показване на бутон за отмяна на разделянето", @@ -3067,12 +3662,18 @@ "waveformDoubleClickAction": "Двойно щракване върху вълнова форма (след еднократно щракване)", "allSettings": "Всички настройки", "uiScale": "Скала на потребителския интерфейс (%)", + "fontScale": "Мащаб на шрифта (%)", + "textSelectionOpacity": "Непрозрачност на маркирания текст (%)", "waveformToolbarItems": "Елементи на лентата с инструменти за вълнови форми", "matchIconColorToDarkTheme": "Съчетайте цвета на иконата с цвета на предния план на тъмната тема", "subtitlePreviewProperties": "Свойства за преглед на субтитрите", "usePositionFromSubtitleFile": "Използване на позиция от файла със субтитри (TTML/PAC/EBU STL)", "marginIsPartOfSubtitleArea": "Маржът е част от областта на субтитрите", "textJustify": "Подравняване на редовете", + "deinterlace": "Деинтерлейсинг на видеото", + "deinterlaceOff": "Изключено", + "deinterlaceAuto": "Автоматично (само презредови кадри)", + "deinterlaceAlways": "Винаги", "pixelWidthInfo": "Зелени редове = макс ширина | Червена зона = текста превишава", "spellCheckEngineHunSpelll": "Hunspell", "spellCheckEngineMsWord": "MS Word", @@ -3081,7 +3682,16 @@ "waveformColorTheme": "Цветова тема:", "waveformSaveAsCustomTheme": "Запазване като персонализирана тема...", "waveformLoadThemeDotDotDot": "Зареждане на тема...", - "waveformExportThemeDotDotDot": "Експортиране на тема..." + "waveformExportThemeDotDotDot": "Експортиране на тема...", + "waveformSaveCustomThemeTitle": "Запазване на потребителска тема", + "waveformThemeFileType": "Тема за формата на вълната на Subtitle Edit", + "waveformThemeHighContrast": "Висок контраст", + "waveformThemeOceanBlue": "Океанско синьо", + "waveformThemeWarmSunset": "Топъл залез", + "waveformThemeForest": "Гора", + "waveformThemeMidnightPurple": "Среднощно лилаво", + "waveformThemeRetroAmber": "Ретро кехлибар", + "waveformThemeIce": "Лед" }, "shortcuts": { "searchShortcuts": "Търсене на преки пътища...", @@ -3092,11 +3702,34 @@ "categoryVideo": "Видео", "categorySync": "Синхронизация", "categoryAi": "Изкуствен интелект", + "categoryCustom": "Потребителски", + "editCustomShortcut": "Редактиране на потребителска клавишна комбинация", + "customShortcutTextBoxKeyHint": "Използвайте тук клавиш с Ctrl/Alt - обикновен клавиш вече няма да пише в текстовото поле.", + "customShortcutNumberX": "Потребителска клавишна комбинация #{0}", + "customShortcutNumberXY": "Потребителска клавишна комбинация #{0}: {1}", + "customShortcutStep": "Стъпка", + "customShortcutSteps": "Стъпки", + "customShortcutStepRunCommand": "Изпълнение на команда", + "customShortcutStepInsertText": "Вмъкване на текст", + "customShortcutStepReplace": "Търсене и замяна", + "customShortcutInsertAtCursor": "При курсора", + "customShortcutInsertAtStart": "В началото на текста", + "customShortcutInsertAtEnd": "В края на текста", + "customShortcutSummaryRunCommandX": "Изпълнение: {0}", + "customShortcutSummaryInsertXY": "Вмъкване на \"{0}\" ({1})", + "customShortcutSummaryReplaceXY": "Замяна на \"{0}\" с \"{1}\"", + "customShortcutSummaryReplaceRegexXY": "Замяна на регулярен израз \"{0}\" с \"{1}\"", + "customShortcutPickCommand": "Моля, изберете команда за изпълнение.", + "customShortcutEnterText": "Моля, въведете текста за вмъкване.", + "customShortcutEnterFind": "Моля, въведете текста за търсене.", + "customShortcutInvalidRegexX": "Невалиден регулярен израз: {0}", + "customShortcutStepsHint": "Стъпките се изпълняват отгоре надолу. Текстовите стъпки работят върху избраните редове; командите, които отварят прозорец, продължават след затварянето му.", "activeIn": "Активен в", "activeInEverywhere": "Навсякъде", "generalMergeSelectedLinesAndUnbreak": "Обедини избраните редове и премахни прекъсването", "generalMergeSelectedLinesAndUnbreakCjk": "Обедини избраните редове и възстанови прекъсване CJK", "generalUnbreakNoSpaceCjk": "Не разделяй без интервал (CJK)", + "generalToggleBreak": "Превключване на пренасяне/премахване на пренасяне", "generalMergeSelectedLinesBilingual": "Обедини избраните двуезични редове", "generalToggleTranslationMode": "Превключване на режима на превод", "generalToggleTranslationAndOriginalInPreviews": "Превключване между превода и оригинала при преглед на видео/аудио", @@ -3113,10 +3746,12 @@ "generalToggleItalic": "Превключване на курсива", "generalToggleBold": "Превключване на удебелен текст", "generalToggleUnderline": "Превключване на подчертаването", + "generalToggleBox": "Превключване на кутия (EBU STL)", "fileOpen": "Отворено", "fileOpenKeepVideo": "Отвори (запази видеото)", "fileSave": "Запазване", "fileSaveAs": "Запази като", + "fileSaveOriginal": "Запазване на оригинала", "fileCompare": "Сравнение на субтитри", "fileStatistics": "Статистика", "fileImportPlainText": "Импортиране на обикновен текст", @@ -3128,6 +3763,7 @@ "fillSelectedLinesWithClipboard": "Запълни избраните редове с текст от клипборда", "listInverseSelection": "Обратен избор", "listDeleteSelection": "Изтриване на селекцията", + "deleteSelectionEverywhere": "Изтриване на селекцията (навсякъде)", "rippleDeleteSelection": "Изтриване на селекция от Ripple", "assigned": "Присвоено", "unassigned": "Неразпределено", @@ -3136,6 +3772,7 @@ "detectKey": "Detect key", "control": "Control", "alt": "Alt", + "altGr": "AltGr", "win": "Win", "shift": "Shift", "controlMac": "⌃", @@ -3146,6 +3783,11 @@ "resetShortcutsDetail": "Искате ли да възстановите всички преки пътища до стойностите им по подразбиране?", "togglePlayPause": "Превключване между възпроизвеждане/пауза", "toggleLockTimeCodes": "Превключи времевите кодове за заключване", + "toggleTimeCodeMode": "Превключване на режима на времевия код (време/кадри/номера на кадри)", + "toggleScreenPrivacy": "Превключване на поверителност на екрана (скриване на имената на файлове, след това и на текстовете)", + "screenPrivacyFileNamesHidden": "Поверителност на екрана: имената на файлове са скрити", + "screenPrivacyFileNamesAndTextsHidden": "Поверителност на екрана: имената на файлове и текстовете са скрити", + "screenPrivacyOff": "Поверителност на екрана: изключена", "duplicateSelectedLines": "Дублиране на избраните редове", "sourceView": "Изглед на източника", "addOrEditBookmark": "Добавяне или редактиране на отметка", @@ -3164,6 +3806,7 @@ "moveFirstWordFromNextLineUpCurrentSubtitle": "Преместване на първата дума от следващия ред нагоре (текущ субтитър)", "moveTextFromCursorToNextAndGoToNext": "Премести текста след позицията на курсора до следващия субтитър и премини към следващия", "moveTextFromCursorToNextAndGoToNextAndPlay": "Преместване на текст след позицията на курсора до следващия субтитър, преминаване към следващия и възпроизвеждане", + "moveTextFromCursorToNextAndGoToNextAndPlayAndPause": "Преместване на текста след курсора в следващия субтитър, отиване на следващия, пускането му и пауза в края му", "breakAtFirstSpaceFromCursor": "Прекъсване на първия интервал от позицията на курсора", "breakAtFirstSpaceFromCursorAndGoToNext": "Прекъсване на първия интервал от позицията на курсора и преминаване към следващия", "toggleFocusGridAndWaveform": "Превключване на фокуса между мрежата на субтитрите и формата на вълната/спектрограмата", @@ -3193,6 +3836,7 @@ "listViewColumnTextUp": "Колона, текст нагоре", "listViewColumnTextDown": "Колона, текст надолу", "autoTranslateSelectedLines": "Автоматичен превод на избраните редове...", + "autoTranslateSelectedLinesNoPrompt": "Автоматичен превод на избраните редове (без питане, с последния двигател/езици)", "setAssaResolution": "Задаване на ASSA резолюция (PlayResX/PlayResY)", "setShortcutForX": "Задаване на пряк път за \"{0}\"", "commandFileNewKeepVideo": "Ново (запазване на видеото)", @@ -3247,6 +3891,7 @@ "textBoxItalic": "Курсив на текстовото поле", "textBoxBold": "Удебелен текст в текстовото поле", "textBoxUnderline": "Подчертаване на текстовото поле", + "textBoxBox": "Кутия в текстовото поле (EBU STL)", "resetWaveformZoomAndSpeed": "Нулиране на мащабирането на вълновата форма и скоростта на възпроизвеждане (честота на възпроизвеждане)", "togglePlaybackSpeed": "Превключване на скоростта на възпроизвеждане (честота на възпроизвеждане)", "playbackSpeedSlower": "По-бавна скорост на възпроизвеждане (честота на възпроизвеждане)", @@ -3258,6 +3903,7 @@ "seekSilenceForward": "Търси тишина напред", "waveformGuessStart": "Предполага се начално време от вълновата форма", "waveformGuessEnd": "Предполага се крайният час от вълновата форма", + "waveformGuessStartAndEnd": "Отгатване на началното и крайното време от формата на вълната", "setVideoPositionCurrentSubtitleStart": "Задаване на позицията на видеото на началото на текущия ред", "goToSubtitlePositionAndPause": "Отидете на подпозиция и направете пауза", "setVideoPositionCurrentSubtitleEnd": "Задаване на позицията на видеото до края на текущия ред", @@ -3271,6 +3917,14 @@ "surroundWith": "Обгради с...", "surroundWithXY": "Обграждане с {0}/{1}", "surroundWithNumberX": "Обградете с #{0}", + "surroundWithBehavior": "Поведение", + "surroundWithBehaviorToggle": "Превключване (добавяне или премахване, ако има)", + "surroundWithBehaviorAdd": "Добавяне (всеки път)", + "surroundWithBehaviorRemove": "Премахване (всички)", + "surroundWithBehaviorRemoveOnce": "Премахване (по едно всеки път)", + "surroundWithWorksOn": "Работи върху", + "surroundWithScopeSelectionOrText": "Селекцията, иначе целия текст", + "surroundWithScopeEachLine": "Всеки ред", "searchVia": "Търсене чрез", "searchViaX": "Търсене чрез {0}", "searchViaNumberX": "Търсене чрез #{0}", @@ -3298,6 +3952,11 @@ "copySubtitlePathToClipboard": "Копиране на пътя към субтитрите в клипборда", "copySubtitleOriginalPathToClipboard": "Копиране на пътя на субтитрите на оригинала в клипборда", "focusTextBox": "Фокусиране върху текстово поле", + "focusSubtitleListView": "Фокус върху списъка със субтитри", + "focusWaveform": "Фокус върху формата на вълната", + "focusOriginalTextBox": "Фокус върху текстовото поле с оригинала", + "textBoxGoToStart": "Текстово поле, отиване в началото", + "textBoxGoToEnd": "Текстово поле, отиване в края", "sortByNumber": "Сортиране по номер", "sortByStartTime": "Сортиране по време на \"Показване\"", "sortByEndTime": "Сортиране по време на \"скриване\"", @@ -3335,9 +3994,9 @@ "selectionToSentenceCase": "Текстово поле: Избор на регистър на изречението", "selectionToggleCasing": "Текстово поле: Превключване между главни и малки букви (ГОРНИ -> долни -> Главни букви)", "googleIt": "Потърси в Google (избран текст)", - "setActorXY": "Задаване на актьор {0}: {1}", + "setActorX": "Задаване на актьор: {0}", "setNewActor": "Задайте нов актьор...", - "setActorSlotXTitle": "Слот за актьор {0}", + "setStyleX": "Задаване на стил: {0}", "toggleDialogDashes": "Превключи диалогови тирета", "mergeWithLineBeforeAsDialog": "Обедини с реда преди диалога", "mergeWithLineAfterAsDialog": "Обедини с реда след диалога", @@ -3348,7 +4007,23 @@ "moveStartOneFrameBackKeepGapPrev": "Премести началото с един кадър назад (запази разстоянието до предишния кадър, ако е близо)", "moveStartOneFrameForwardKeepGapPrev": "Премести началото с един кадър напред (запази разстоянието до предишния кадър, ако е близо)", "moveEndOneFrameBackKeepGapNext": "Премести края с един кадър назад (запази разстоянието до следващия, ако е близо)", - "moveEndOneFrameForwardKeepGapNext": "Премести края с един кадър напред (запази разстоянието до следващия, ако е близо)" + "moveEndOneFrameForwardKeepGapNext": "Премести края с един кадър напред (запази разстоянието до следващия, ако е близо)", + "moveStartXMsBack": "Преместване на началото X ms назад (X се задава в Настройки, също и във Визуална синхронизация)", + "moveStartXMsForward": "Преместване на началото X ms напред (X се задава в Настройки, също и във Визуална синхронизация)", + "moveEndXMsBack": "Преместване на края X ms назад (X се задава в Настройки, също и във Визуална синхронизация)", + "moveEndXMsForward": "Преместване на края X ms напред (X се задава в Настройки, също и във Визуална синхронизация)", + "moveSelectedLinesXMsBack": "Преместване на избраните редове X ms назад (X се задава в Настройки)", + "moveSelectedLinesXMsForward": "Преместване на избраните редове X ms напред (X се задава в Настройки)", + "moveSelectedLinesAndForwardXMsBack": "Преместване на избраните редове и всички следващи X ms назад (X се задава в Настройки)", + "moveSelectedLinesAndForwardXMsForward": "Преместване на избраните редове и всички следващи X ms напред (X се задава в Настройки)", + "moveAllLinesXMsBack": "Преместване на всички редове X ms назад (X се задава в Настройки)", + "moveAllLinesXMsForward": "Преместване на всички редове X ms напред (X се задава в Настройки)", + "moveSelectedLinesCustomMsBack": "Преместване на избраните редове, потребителски милисекунди ({0:#,###,##0}) назад, {1}", + "moveSelectedLinesCustomMsForward": "Преместване на избраните редове, потребителски милисекунди ({0:#,###,##0}) напред, {1}", + "moveSelectedLinesAndForwardCustomMsBack": "Преместване на избраните редове и всички следващи, потребителски милисекунди ({0:#,###,##0}) назад, {1}", + "moveSelectedLinesAndForwardCustomMsForward": "Преместване на избраните редове и всички следващи, потребителски милисекунди ({0:#,###,##0}) напред, {1}", + "moveAllLinesCustomMsBack": "Преместване на всички редове, потребителски милисекунди ({0:#,###,##0}) назад, {1}", + "moveAllLinesCustomMsForward": "Преместване на всички редове, потребителски милисекунди ({0:#,###,##0}) напред, {1}" }, "wordLists": { "title": "Списъци с думи", @@ -3393,7 +4068,11 @@ "applyToSelectedLinesX": "Избрани редове ({0})", "applyToAllLinesX": "Всички редове ({0})", "updateAllXAvailable": "Актуализирай всички ({0})", - "updatingXOfY": "Актуализиране на {0}/{1}..." + "updatingXOfY": "Актуализиране на {0}/{1}...", + "doNotAskAgain": "Без повторно питане (промяна в Управление на плъгини)", + "applyPluginsTo": "Прилагане на плъгините към", + "askEachTime": "Питане всеки път", + "allLines": "Всички редове" }, "help": { "aboutSubtitleEdit": "Относно Subtitle Edit", @@ -3437,6 +4116,8 @@ "nOcrDatabase": "nOCR база данни", "drawMode": "Режим на рисуване:", "addNewCharcter": "Добавяне на нов герой", + "addNOcrCharacterTitle": "Добавяне на nOCR знак за ред {0}, знак {1} от {2}, с база данни \"{3}\"", + "addBinaryOcrCharacterTitle": "Добавяне на двоичен OCR знак за ред {0}, знак {1} от {2}, с база данни \"{3}\"", "lineIndexX": "Ред {0}", "inspectNOcrAdditions": "Провери новите допълнения на nOCR", "ocrSelectedLines": "OCR избрани редове", @@ -3485,6 +4166,8 @@ "binaryImageCompareInspectImageMatches": "\"Сравняване на двоични изображения\" - Проверка на съвпаденията на изображенията", "saveBlankTextTitle": "Запазване на празен текст?", "saveBlankTextPrompt": "Запазване на празен текст за изображение?", + "drawnLinesDoNotMatchTitle": "Линиите не съответстват на изображението", + "drawnLinesDoNotMatchPrompt": "Някои от начертаните линии не съответстват на изображението (зелените линии трябва да са върху знака, червените - извън него), затова този знак няма да бъде разпознат.\n\nЗапазване въпреки това?", "yesAndNeverAskAgain": "Да и никога повече да не пита", "importTextFromSubtitleDotDotDot": "Импортиране на текст от субтитри...", "importTextFromSubtitleOverwritePrompt": "Някои редове вече съдържат OCR текст. Да се презапише ли съществуващият текст?", @@ -3543,9 +4226,26 @@ "crispEmbedSettingsTitle": "Настройки на CrispEmbed", "crispEmbedDescription": "Локален OCR енджин с множество бекендове за модели - изтеглете енджина и моделите тук.", "trainNOcrDatabase": "Обучение на nOCR база данни...", + "optimizeNOcrDatabase": "Оптимизиране на nOCR базата данни...", + "optimizeNOcrDatabaseQuestionXY": "Да се премахнат ли линиите, които не различават знаците от подобни знаци, в nOCR базата данни \"{0}\"?\n\nТова прави базата данни по-малка и OCR по-бърз, обикновено с по-малко грешки. Отнема до минута.\n\nАрхив се запазва като \"{1}\".", + "optimizingNOcrDatabaseXY": "Оптимизиране на nOCR базата данни... {0}/{1}", + "nOcrDatabaseOptimizedXYZ": "nOCR базата данни \"{0}\" е оптимизирана: {1:#,##0} -> {2:#,##0} линии.", "startTraining": "Започнете обучението", "abortTraining": "Прекратяване на обучението", "trainingOptions": "Опции за обучение", + "trainNOcrSubtitle": "Изобразява знаци с инсталираните шрифтове и учи nOCR на формите им. Изберете шрифтовете, които използват субтитрите ви.", + "xFontsSelected": "{0:#,##0} избрани", + "subtitleFonts": "Шрифтове на субтитрите", + "emptyDatabase": "Празна база данни", + "startFromDatabase": "Начало от", + "startFromDatabaseHint": "Добавяне на обучените знаци към копие на съществуваща база данни (препоръчително) или започване от празна", + "fontSizes": "Размери на шрифта", + "fontSizesHint": "Размери на шрифта в пиксели за изобразяване, разделени със запетаи, напр. \"30, 40\"", + "numberOfLineSegmentsHint": "Проверявани линии на знак - повече е по-строго, но по-бавно", + "enterDatabaseName": "Въведете име за базата данни", + "trainingFontXSizeYCharacterZ": "Обучение на \"{0}\" при {1} px: {2}", + "xLearned": "{0:#,##0} научени", + "xSkipped": "{0:#,##0} пропуснати", "charactersToTrain": "Герои за обучение", "letterCombinationsToTrain": "Комбинации от букви, които могат да бъдат разделени на едно изображение", "importCharactersFromSubtitleFile": "Импортиране на символи от файл със субтитри...", @@ -3572,6 +4272,8 @@ "drawZoomOut": "Намаляване на мащаба (Ctrl+-)", "drawResetView": "Нулиране на изгледа (Ctrl+0)", "drawToggleGrid": "Превключване на мрежата (Ctrl+G)", + "drawTogglePreview": "Превключване на прегледа (F9)", + "drawPreviewFailed": "Прегледът не успя - изисква се ffmpeg с libass (филтър subtitles)", "drawCopyToClipboard": "Копиране в клипборда (Ctrl+C)", "drawShapes": "Форми", "drawSelectedPoint": "Избрана точка", @@ -3579,6 +4281,81 @@ "drawSelectedLayer": "Избран слой", "drawToolX": "Инструмент: {0}", "drawHelpText": "Щракнете, за да добавите точки • Enter/F8, за да затворите формата • Shift+плъзгане, за да преместите панорамно • Ctrl+превъртане, за да увеличите мащаба", + "drawMoveToLayer": "Преместване в слой", + "drawFlipHorizontal": "Хоризонтално обръщане", + "drawFlipVertical": "Вертикално обръщане", + "drawHideShape": "Скриване на фигурата", + "drawShowShape": "Показване на фигурата", + "drawHideLayer": "Скриване на слоя", + "drawShowLayer": "Показване на слоя", + "drawDeleteLayer": "Изтриване на слоя", + "drawDeletePoint": "Изтриване на точката", + "drawCancelDrawing": "Отказ от рисуването", + "drawLayerX": "Слой {0}", + "drawImportSvg": "Импортиране на SVG изображение...", + "drawRotateClockwise": "Завъртане на 90° по часовниковата стрелка", + "drawConvertToCurve": "Преобразуване на сегмента в крива", + "drawBackground": "Фон", + "drawPosition": "Позиция", + "drawZoom": "Мащаб", + "drawShapeTool": "Инструмент за фигури", + "drawColorPickerTool": "Вземане на цвят от фигура или от фона", + "drawShapeLibrary": "Библиотека с фигури", + "drawShapeLibraryHint": "Плъзнете върху платното, за да поставите фигура, или щракнете за размера по подразбиране. Shift запазва пропорциите ѝ.", + "drawAddToLibrary": "Добавяне към библиотеката с фигури...", + "drawRemoveFromLibrary": "Премахване от библиотеката с фигури", + "drawShapeName": "Име на фигурата", + "drawCategorySpeechBubbles": "Балончета за реч", + "drawCategoryArrows": "Стрелки", + "drawCategoryBasicShapes": "Основни фигури", + "drawCategorySymbols": "Символи", + "drawCategoryMyShapes": "Моите фигури", + "drawShapeSpeechBubble": "Балонче за реч", + "drawShapeSpeechBubbleRight": "Балонче за реч, опашка надясно", + "drawShapeSpeechBubbleUp": "Балонче за реч, опашка нагоре", + "drawShapeOvalBubble": "Овално балонче за реч", + "drawShapeBoxBubble": "Правоъгълно балонче за реч", + "drawShapeThoughtBubble": "Балонче за мисъл", + "drawShapeShoutBubble": "Балонче за вик", + "drawShapeCaptionBox": "Каре за надпис", + "drawShapeArrow": "Стрелка", + "drawShapeDoubleArrow": "Двойна стрелка", + "drawShapeChevron": "Шеврон", + "drawShapeCurvedArrow": "Извита стрелка", + "drawShapeRoundedRectangle": "Заоблен правоъгълник", + "drawShapeTriangle": "Триъгълник", + "drawShapeDiamond": "Ромб", + "drawShapePentagon": "Петоъгълник", + "drawShapeHexagon": "Шестоъгълник", + "drawShapeOctagon": "Осмоъгълник", + "drawShapeStar": "Звезда", + "drawShapeRing": "Пръстен", + "drawShapePlus": "Плюс", + "drawShapeBanner": "Банер", + "drawShapeHeart": "Сърце", + "drawShapeCheck": "Отметка", + "drawShapeMusicNote": "Музикална нота", + "drawShapeMusicNotes": "Музикални ноти", + "drawShapeLightning": "Мълния", + "drawShapeCloud": "Облак", + "drawShapeMoon": "Луна", + "drawShapeBadge": "Значка", + "drawSize": "Размер", + "drawLayer": "Слой", + "drawBackgroundVideoFrame": "Видео кадър (текуща позиция)", + "drawBackgroundVideoFrameAt": "Видео кадър при...", + "drawBackgroundImage": "Файл с изображение...", + "drawBackgroundNone": "Без фон", + "drawBackgroundStretch": "Разтягане на изображението до платното", + "drawBackgroundOpacity": "Непрозрачност на фона", + "drawBackgroundVideoFrameFailed": "Не можа да бъде взет видео кадър при {0}.", + "drawImages": "Изображения", + "drawConvertToLine": "Преобразуване на сегмента в линия", + "drawConvertShapeToCurves": "Преобразуване на всички линии в криви", + "drawConvertShapeToLines": "Преобразуване на всички криви в линии", + "drawRotateCounterClockwise": "Завъртане на 90° обратно на часовниковата стрелка", + "drawSvgImages": "SVG изображения", + "drawSvgNoShapes": "В SVG файла не са намерени запълнени фигури или контури.", "progressBarTitle": "Лента за напредък на ASSA", "progressBarSettings": "Лента за напредък", "mouseOverColor": "Цвят при задържане на мишката", @@ -3675,13 +4452,26 @@ "graphics": "Графика", "copyToStorageStyles": "Копиране в стилове за съхранение", "copyToFileStyles": "Копиране във файл със стилове", + "styleXAlreadyExistsInStorage": "Стил с име \"{0}\" вече съществува в хранилището.\n\nДа бъде ли презаписан, или да се запазят и двата (копието получава ново име)?", + "styleXAlreadyExistsInFile": "Стил с име \"{0}\" вече съществува във файла.\n\nДа бъде ли презаписан, или да се запазят и двата (копието получава ново име)?", + "styleNameCannotBeEmpty": "Името на стила не може да бъде празно.", + "styleNameXIsUsedMoreThanOnce": "Името на стил \"{0}\" се използва от повече от един стил във файла.", + "overwrite": "Презаписване", + "keepBoth": "Запазване и на двата", + "doThisForAllConflictingStyles": "Същото за всички конфликтни стилове", "setStyleAsDefault": "Задаване на стил по подразбиране", "takeUsagesFromDotDotDot": "Вземи използвани от...", "replaceStyleWithDotDotDot": "Замени стила с...", "noAttachmentsFound": "Не са намерени прикачени файлове в избрания ASSA файл.", "deleteStyleQuestion": "Изтриване на стила?", "deleteStylesQuestion": "Да се изтрият ли стиловете?", + "deleteXStylesQuestion": "Искате ли да изтриете {0} стила?", + "deleteXStylesFromStorageQuestion": "Искате ли да изтриете {0} стила от хранилището?", + "deleteStyleXFromStorageQuestion": "Искате ли да изтриете стил \"{0}\" от хранилището?", + "deleteStyleXFromFileQuestion": "Искате ли да изтриете стил \"{0}\" от текущия файл?", "openStyleImportFile": "Отвори файл със субтитри, за импорт на стилове от", + "styleImportFiles": "Файлове със стилове (.ass, .sty, категории .template на Subtitle Edit 4)", + "ssaStyleImportFiles": "Файлове със стилове (.ssa, .ass, .sty)", "primary": "Основно", "secondary": "Вторично", "applyOverrideTags": "Прилагане на етикети за презаписване", diff --git a/src/ui/Assets/Languages/Catalan.json b/src/ui/Assets/Languages/Catalan.json index a00c0072684..59afbe5dc9f 100644 --- a/src/ui/Assets/Languages/Catalan.json +++ b/src/ui/Assets/Languages/Catalan.json @@ -1,12 +1,30 @@ { "title": "Subtitle Edit", - "version": "v5.2.0-rc7", + "version": "v5.3.0", "translatedBy": "", "cultureName": "ca-ES", "general": { "abort": "Interromp", "actor": "Actor", "actors": "Actors", + "setActorDotDotDot": "Estableix l'actor...", + "actorPickerTitle": "Estableix l'actor", + "actorPickerFilterHint": "Escriviu per filtrar o escriviu el nom d'un actor nou", + "actorPickerKeysHint": "Premeu un número o Enter per establir l'actor. Alt+Amunt/Avall canvia l'ordre, Supr elimina l'actor.", + "actorPickerNewActorX": "Actor nou «{0}»", + "actorPickerLinesSelectedX": "Línies seleccionades: {0}", + "setStyleDotDotDot": "Estableix l'estil...", + "stylePickerTitle": "Estableix l'estil", + "stylePickerFilterHint": "Escriviu per filtrar o escriviu el nom d'un estil nou", + "stylePickerKeysHint": "Premeu un número (1-9, 0) per establir l'estil a totes les línies seleccionades.", + "stylePickerNewStyleX": "Estil nou «{0}»", + "stylePickerNewStyleInfo": "Premeu Enter per afegir aquest estil amb la configuració per defecte i establir-lo a les línies seleccionades. Utilitzeu «Gestiona els estils...» per canviar-ne més endavant la lletra, els colors i la posició.", + "stylePickerCurrentStyleX": "Estil actual: {0}", + "stylePickerLinesUsingStyleX": "Línies que utilitzen aquest estil: {0}", + "stylePickerMarginsXYZ": "Esquerre {0}, dret {1}, vertical {2}", + "stylePickerSetStyle": "_Estableix l'estil", + "stylePickerManageStyles": "Gestiona els estils...", + "stylePickerManageStylesHint": "Tanca aquesta finestra i obre el gestor d'estils per editar les lletres, els colors, la posició i més", "add": "Afegeix", "addDotDotDot": "Afegeix...", "addToNamesListCaseSensitive": "Afegeix a la llista de noms (distingeix majúscules)", @@ -339,6 +357,7 @@ "mergeSelectedLinesDialog": "Fusiona les línies seleccionades com a diàleg", "mergeWithLineAfterAndAutoBreak": "Fusiona amb la línia següent i salta automàticament", "mergeWithLineAfterKeepBreaks": "Fusiona amb la línia següent (mantén els salts)", + "mergeWithLineAfterAndUnbreak": "Fusiona amb la línia següent i desfés el salt de línia", "mergeWithLineBeforeAndAutoBreak": "Fusiona amb la línia anterior i salta automàticament", "mergeWithLineBeforeKeepBreaks": "Fusiona amb la línia anterior (mantén els salts)", "middleCenter": "Mig-Centre", @@ -424,6 +443,7 @@ "percent": "Percentatge", "pickLayer": "Estableix la capa", "pickOllamaModel": "Trieu un model d'Ollama", + "pickModel": "Trieu el model", "llamaCppUseRemoteServer": "Utilitza un servidor extern (URL)", "llamaCppEngineSettings": "Configuració del motor llama.cpp", "llamaCppEngineSettingsSubtitle": "Compilació local del servidor llama.cpp", @@ -500,6 +520,7 @@ "reset": "Restableix", "resizePanels": "Redimensiona els plafons", "resolution": "Resolució", + "retry": "Torna-ho a provar", "reverseRightToLeftStartEnd": "Inverteix l'inici/final RTL", "right": "Dreta", "rightToLeft": "De dreta a esquerra", @@ -561,6 +582,8 @@ "shadowColor": "Color de l'ombra", "shadowWidth": "Ample de l'ombra", "shiftLineBy": "Desplaça la línia per", + "shotIn": "Entrada de pla", + "shotOut": "Sortida de pla", "shortcut": "Drecera", "shortcuts": "Dreceres", "show": "Mostra", @@ -572,9 +595,13 @@ "showStartColumn": "Mostra la columna «Inici»", "showHideColumn": "Mostra la columna «Amaga»", "showHistory": "Mostra l'historial", + "clearHistory": "Neteja l'historial", + "removeFromHistory": "Elimina de l'historial", "showLayerColumn": "Mostra la columna «Capa»", "showPreview": "Mostra la vista prèvia", "showShotChangesList": "Mostra la llista de canvis de pla", + "showShotInColumn": "Mostra la columna «Entrada de pla»", + "showShotOutColumn": "Mostra la columna «Sortida de pla»", "showStyleColumn": "Mostra la columna «Estil»", "showTeletext": "Mostra la columna de teletext a la vista de llista", "showTimeCodes": "Mostra els codis de temps", @@ -631,12 +658,14 @@ "styleExaggeration": "Exageració de l'estil", "styles": "Estils", "subtitleFile": "Fitxer de subtítols", + "subtitleFileChangedOutsideX": "El fitxer «{0}» s'ha modificat fora del Subtitle Edit.\n\nVoleu tornar-lo a carregar i perdre els canvis?", "subtitleFileName": "Nom del fitxer del subtítol", "subtitleFileSaved": "S'ha desat el fitxer de subtítols", "subtitleFileSavedToX": "S'ha desat el fitxer de subtítols a {0}", "subtitleFiles": "Fitxers de subtítols", "subtitleFormats": "Formats de subtítols", "subtitleLoadedX": "S'ha carregat el subtítol: {0}", + "subtitleReloadedChangedOutsideX": "S'ha tornat a carregar el subtítol (modificat fora del Subtitle Edit): {0}", "suffix": "Sufix", "suggestions": "Suggeriments", "sync": "Sincronitza", @@ -682,6 +711,7 @@ "type": "Tipus", "unbreak": "Desfés el salt de línia del text", "unbreakLines": "Desfés el salt de línia", + "undelete": "Recupera", "underline": "Subratllat", "undo": "Desfés", "unknown": "Desconegut", @@ -719,10 +749,23 @@ "videoCustom3ForwardX": "Vídeo, mil·lisegons personalitzats ({0:#,###,##0}) endavant, 3", "videoCustom4BackX": "Vídeo, mil·lisegons personalitzats ({0:#,###,##0}) enrere, 4", "videoCustom4ForwardX": "Vídeo, mil·lisegons personalitzats ({0:#,###,##0}) endavant, 4", + "videoCustom1BackAndPauseX": "Vídeo, mil·lisegons personalitzats ({0:#,###,##0}) enrere i pausa, 1", + "videoCustom1ForwardAndPauseX": "Vídeo, mil·lisegons personalitzats ({0:#,###,##0}) endavant i pausa, 1", + "videoCustom2BackAndPauseX": "Vídeo, mil·lisegons personalitzats ({0:#,###,##0}) enrere i pausa, 2", + "videoCustom2ForwardAndPauseX": "Vídeo, mil·lisegons personalitzats ({0:#,###,##0}) endavant i pausa, 2", + "videoCustom3BackAndPauseX": "Vídeo, mil·lisegons personalitzats ({0:#,###,##0}) enrere i pausa, 3", + "videoCustom3ForwardAndPauseX": "Vídeo, mil·lisegons personalitzats ({0:#,###,##0}) endavant i pausa, 3", + "videoCustom4BackAndPauseX": "Vídeo, mil·lisegons personalitzats ({0:#,###,##0}) enrere i pausa, 4", + "videoCustom4ForwardAndPauseX": "Vídeo, mil·lisegons personalitzats ({0:#,###,##0}) endavant i pausa, 4", "videoExtension": "Extensió del fitxer de vídeo", "videoFile": "Fitxer de vídeo", "videoFileGenerated": "S'ha generat el fitxer de vídeo", "videoFileGeneratedX": "S'ha generat el fitxer de vídeo: \"{0}\"", + "videoFilesGenerated": "S'han generat els fitxers de vídeo", + "xVideosGenerated": "S'han generat {0} vídeos", + "xOfYVideosGenerated": "S'han generat {0} de {1} vídeos", + "savedInX": "Desat a {0}", + "savedInXFolders": "Desat a {0} carpetes", "videoFiles": "Fitxers de vídeo", "videoAndAudioFiles": "Fitxers de vídeo i àudio", "videoOffset": "Desplaçament del vídeo", @@ -780,7 +823,6 @@ "imageFiles": "Fitxer d'imatge", "openAiCompatibleSttEndpoint": "URL del punt final STT", "openAiCompatibleSttApiKey": "Clau API", - "openAiCompatibleSttModel": "Model", "openAiCompatibleSttExtraHeaders": "Capçaleres addicionals", "openAiCompatibleSttTimeout": "Temps d'espera (segons)", "openAiCompatibleSttLanguage": "Pista de llengua", @@ -790,6 +832,9 @@ "dashScopeSttRegion": "Regió", "dashScopeSttEnableWords": "Marques de temps per paraula", "dashScopeSttRegionKeyHint": "Nota: Les claus API d'Alibaba Cloud Model Studio són específiques de la regió - assegureu-vos que la regió seleccionada coincideix amb la regió on es va crear la clau API (Xina vs. Internacional).", + "googleCloudSttProjectId": "ID del projecte (només per a Application Default Credentials)", + "googleCloudSttBucketName": "Bucket d'emmagatzematge (buit = automàtic)", + "googleCloudSttDynamicBatching": "Processament per lots dinàmic (unes 5 vegades més barat, sense garantia de latència)", "onlineSttApiKeyMissing": "Es requereix una clau API. Introduïu la vostra clau API i torneu-ho a provar.", "openAiCompatibleSttAutoTranscribeOnAudioSelection": "Transcriu automàticament la selecció nova de la forma d'ona amb veu a text", "openAiCompatibleSttStream": "Resposta en flux", @@ -847,6 +892,7 @@ "multipleReplace": "Substitució _múltiple...", "fillSelectedLinesWithClipboard": "Omple les línies seleccionades amb el text del porta-retalls", "goToLineNumber": "_Ves al número de línia...", + "commandPalette": "_Paleta d'ordres...", "rightToLeftMode": "Mode de dreta a _esquerra", "modifySelectionDotDotDot": "Modifica la _selecció...", "tools": "E_ines", @@ -854,6 +900,7 @@ "applyDurationLimits": "Aplica els _límits de durada...", "batchConvert": "Conversió per _lots...", "beautifyTimeCodes": "Embelleix els codis de temps...", + "improveTimeCodes": "Millora els codis de temps (alineació forçada)...", "bridgeGaps": "Uneix els _buits...", "applyMinGap": "Aplica l'interval mínim entre subtítols...", "changeCasing": "_Canvia les majúscules i minúscules...", @@ -992,6 +1039,9 @@ "selectCurrentLineWhilePlayingHint": "Selecciona el subtítol actual durant la reproducció {0}", "videoPosition": "Posició del vídeo {0}", "videoPositionTextBox": "Quadre de text de la posició del vídeo {0}", + "toolbarLineBreakX": "Salt de línia {0} (la resta de la barra d'eines continua en una fila nova)", + "initialText": "Text inicial de la línia seleccionada", + "initialTextHint": "El text de la línia seleccionada tal com era quan es va seleccionar la línia", "hideWaveformToolbar": "Amaga la barra d'eines {0}", "resetZoomAndSpeed": "Restableix el zoom i la velocitat de reproducció {0}", "removeBlankLines": "Elimina les línies en blanc {0}", @@ -1005,6 +1055,15 @@ "textPlayHint": "Reprodueix el subtítol actual i atura't al final {0}", "textPauseHint": "Posa en pausa la reproducció {0}", "textNextHint": "Reprodueix el subtítol següent i atura't al final {0}", + "moveSelectedLines": "Mou les línies seleccionades (botons enrere/endavant)", + "moveSelectedLinesAndFollowing": "Mou les línies seleccionades i totes les següents (botons enrere/endavant)", + "moveAllLines": "Mou totes les línies (botons enrere/endavant)", + "moveSelectedLinesBackHint": "Mou les línies seleccionades {0} ms enrere {1}", + "moveSelectedLinesForwardHint": "Mou les línies seleccionades {0} ms endavant {1}", + "moveSelectedLinesAndFollowingBackHint": "Mou les línies seleccionades i totes les següents {0} ms enrere {1}", + "moveSelectedLinesAndFollowingForwardHint": "Mou les línies seleccionades i totes les següents {0} ms endavant {1}", + "moveAllLinesBackHint": "Mou totes les línies {0} ms enrere {1}", + "moveAllLinesForwardHint": "Mou totes les línies {0} ms endavant {1}", "guessStartNoLineSelected": "Endevina l'inici: no hi ha cap línia seleccionada", "guessStartTimeCodesLocked": "Endevina l'inici: els codis de temps estan blocats", "guessStartNoWaveform": "Endevina l'inici: no hi ha forma d'ona", @@ -1026,12 +1085,15 @@ "charactersPerSecond": "Caràcters/segon: {0}", "chooseColumn": "Trieu la columna", "columnPaste": "Enganxa la columna", + "commandPaletteTitle": "Paleta d'ordres", + "commandPaletteSearchPlaceholder": "Escriviu per cercar ordres...", + "commandPaletteNoCommandsFound": "No hi ha cap ordre coincident", "createdEmptyTranslation": "S'ha creat una traducció buida a partir del subtítol actual", "deleteText": "Suprimeix el text", "deleteTextAndShiftCellsUp": "Suprimeix el text i desplaça les cel·les amunt", "endTimeMustBeAfterStartTime": "El temps final ha de ser posterior al temps d'inici.", "errorLoad7Zip": "Sembla que aquest fitxer és un fitxer comprimit 7-Zip.\n\nEl Subtitle Edit no pot obrir fitxers comprimits.", - "errorLoadBinaryZeroes": "Aquest fitxer només conté zeros binaris!\n\nSi heu editat aquest fitxer amb el Subtitle Edit, potser podeu trobar-ne una còpia de seguretat a l'element de menú Fitxer -> Restaura la còpia de seguretat automàtica...", + "errorLoadBinaryZeroes": "Aquest fitxer només conté zeros binaris!\n\nSi heu editat aquest fitxer amb el Subtitle Edit, potser podeu trobar-ne una còpia de seguretat a l'element de menú Fitxer -> Restaura la còpia de seguretat automàtica...", "errorLoadGZip": "Sembla que aquest fitxer és un fitxer comprimit GZip.\n\nEl Subtitle Edit no pot obrir fitxers comprimits.", "errorLoadLargeFile": "Aquest fitxer és massa gran per obrir-lo com a fitxer de subtítols.", "errorLoadVideoFilePrompt": "Sembla que aquest fitxer és un fitxer de vídeo:\n{0}\n\nPodeu obrir un vídeo amb \"Vídeo -> Obre el vídeo\" o arrossegant-lo i deixant-lo anar al reproductor de vídeo.\n\nVoleu obrir-lo com a fitxer de vídeo?", @@ -1064,11 +1126,14 @@ "numberOfLinesEvenlyDistributedX": "S'han distribuït uniformement {0} línies", "oneLineCopiedFromOriginal": "S'ha copiat una línia del subtítol original", "oneLineCopiedToOriginal": "S'ha copiat una línia al subtítol original", + "onlineOnlyVideo": "Vídeo només en línia", + "onlineOnlyVideoXSizeYDownloadAndOpen": "El fitxer de vídeo «{0}» ({1}) només està emmagatzemat en línia, per exemple al Dropbox o a l'iCloud Drive, i s'ha de baixar abans de poder-lo obrir.\n\nVoleu baixar-lo i obrir-lo ara?", "oneLineMerged": "S'ha fusionat una línia", "oneLineSwitched": "S'ha intercanviat una línia", "overwriteExistingCells": "Sobreescriu les cel·les existents", "overwriteOrShiftCellsDown": "Sobreescriu/desplaça les cel·les avall", "parsingMatroskaFile": "S'està analitzant el fitxer Matroska...", + "readingDvdSubtitles": "S'estan llegint els subtítols del DVD...", "pasteFromClipboardDotDotDot": "Enganxa del porta-retalls...", "redoPerformed": "S'ha refet", "redoPerformedXActionLeft": "S'ha refet (accions restants: {0})", @@ -1108,12 +1173,14 @@ "trimmedXLines": "S'han retallat {0} línies de subtítol", "pastedXLinesOverSelectedLines": "S'han enganxat {0} línia/línies sobre les línies seleccionades", "pastedXOfYLinesOverSelectedLines": "S'han enganxat {0} de {1} línia/línies sobre les línies seleccionades - la resta no cabia a la selecció", + "insertedXLinesFromClipboard": "S'han inserit {0} línies des del porta-retalls", "openOriginalDifferentNumberOfSubtitlesXY": "El fitxer de subtítols original no té el mateix nombre de subtítols que el fitxer de subtítols actual.\n\n• Subtítols originals: {0}\n• Subtítols actuals: {1}", "originalTextReadOnly": "Text original (només de lectura)", "originalTextEditMode": "Text original (mode d'edició)", "originalIsReadOnlyNotSaved": "El subtítol original és una referència només de lectura i no s'ha desat", "originalIsEmptyNotSaved": "El subtítol original no té gens de text i no s'ha desat", "originalIsReadOnlyReference": "El subtítol original està obert com a referència només de lectura", + "closeTranslationQuestionXY": "Voleu tancar la traducció «{0}»?\n\nEl subtítol original «{1}» passa a ser el subtítol que editeu. El fitxer de traducció en si no es modifica.", "allowEditOfOriginalSubtitle": "Permet editar el subtítol original", "showAllOriginalLinesX": "Mostra totes les línies originals ({0} no tenen coincidència aquí)", "showAllOriginalLinesHint": "Les línies sense coincidència es mostren com a files addicionals, per veure què li falta a la traducció.", @@ -1148,6 +1215,7 @@ "fileExportedInFormatXToY": "S'ha exportat el fitxer en format {0} a {1}", "fileExportedInFormatXToFileY": "S'ha exportat el fitxer en format \"{0}\" al fitxer \"{1}\"", "fixedXLines": "S'han corregit {0} línies", + "toolXChangedYLines": "{0}: s'han canviat {1} línies", "transcriptionCompletedWithXLines": "S'ha completat la transcripció amb {0} línies", "openSubtitleFileX": "Voleu obrir el fitxer de subtítols \"{0}\"?", "replacedXOccurrences": "S'han substituït {0} coincidències", @@ -1238,6 +1306,21 @@ "formattingDotDotDot": "Format...", "imageBasedSubtitleForEditDotDotDot": "Subtítol basat en imatges per editar...", "imageBasedSubtitleForOcrDotDotDot": "Subtítol basat en imatges per a OCR...", + "dvdSubtitlesDotDotDot": "Subtítols de DVD (IFO/VOB)...", + "titleImportDvd": "Importa subtítols de DVD", + "dvdIfoFile": "Fitxer IFO", + "dvdVobFiles": "Fitxers VOB", + "dvdVideoStandard": "Estàndard de vídeo", + "dvdStartRipping": "Comença l'extracció", + "dvdRippingAborted": "S'ha interromput l'extracció", + "dvdOpenIfoFile": "Obre un fitxer IFO de DVD", + "dvdAddVobFiles": "Afegeix fitxers VOB", + "dvdIfoFiles": "Fitxers IFO de DVD", + "dvdVobFileType": "Fitxers VOB de DVD", + "dvdDropHint": "Obriu un fitxer IFO (VIDEO_TS\\VTS_01_0.IFO) o afegiu fitxers VOB, o deixeu-los anar aquí", + "dvdNotAnIfoFileX": "{0} no és un fitxer IFO de DVD", + "dvdMenuIfoNoTitleSetsX": "No s'ha trobat cap conjunt de títols (VTS_xx_0.IFO) al costat de {0}", + "dvdEncryptedXOfY": "{0} dels {1} paquets de subtítols encara estan xifrats amb CSS: aquests fitxers VOB es van copiar sense desxifrar, de manera que algunes imatges dels subtítols estaran malmeses.\n\nDesxifreu primer el DVD amb una eina d'extracció de DVD per obtenir un resultat net.\n\nVoleu continuar igualment?", "splitTextAt": "Divideix el text a", "blankLines": "Línies en blanc", "oneLineIsOneSubtitle": "Una línia és un subtítol", @@ -1257,8 +1340,13 @@ "forcedAlignerProgress": "S'està alineant... finestra {0} de {1}, {2} de {3} línies", "forcedAlignerSetupTitle": "Alinea els codis de temps amb un alineador forçat", "forcedAlignerSetupIntro": "Un alineador forçat fa coincidir el text de subtítols que ja teniu amb l'àudio, sense transcriure'l abans. Els vídeos llargs s'alineen per fragments, de manera que funciona amb qualsevol durada.", + "forcedAlignerEndsFromSpeech": "Estableix els temps finals a partir de la veu aïllada (lent)", + "forcedAlignerEndsFromSpeechHint": "Un alineador forçat troba on comença una línia, no on acaba, de manera que els temps finals normalment provenen del temps de lectura. Amb aquesta opció activada, primer s'eliminen la música i els efectes de so de l'àudio i cada línia acaba on la seva veu queda en silenci. Triga aproximadament el mateix que el vídeo amb una GPU, i moltes vegades més sense.", + "forcedAlignerIsolatingSpeech": "S'està aïllant la veu... {0}", + "forcedAlignerIsolatingSpeechFailed": "No s'ha pogut aïllar la veu; els temps finals s'han establert a partir del temps de lectura.", "forcedAlignerModel": "Model d'alineador", - "forcedAlignerDownloadEngine": "Baixa / actualitza el motor..." + "forcedAlignerDownloadEngine": "Baixa / actualitza el motor...", + "forcedAlignerEstimatedXOfY": "{0} de {1} línies no s'han pogut alinear i s'han col·locat segons el temps de lectura; els seus codis de temps només són estimats. Probablement el guió és més llarg que el que es diu al vídeo." }, "export": { "exportImagesProfiles": "Perfils d'exportació d'imatges", @@ -1308,8 +1396,17 @@ "fullFrameImage": "Imatge de fotograma complet", "fullFrameImageHint": "Fes que cada imatge tingui la mida del fotograma del vídeo, amb el subtítol al seu lloc", "stereo3D": "3D", + "stereo3DHint": "Per a vídeo 3D: dibuixa el subtítol una vegada a cada meitat del fotograma (una per ull)", + "halfSideBySide": "Mig costat a costat", + "halfTopBottom": "Mig a dalt/a baix", "depth3D": "Profunditat", + "depth3DHint": "Quant sobresurt el subtítol de la pantalla: un valor positiu l'apropa, un de negatiu l'allunya", + "plane3D": "Pla 3D", + "plane3DHint": "Dona a cada subtítol la profunditat que hi desa un Blu-ray 3D, a partir d'un fitxer de pla 3D (.ofs) creat amb BD3D2MK3D o OFSExtractor. S'utilitza la profunditat on el pla 3D no en té", + "openPlane3DTitle": "Obre un fitxer de pla 3D", + "unableToLoadPlane3DX": "No s'ha pogut carregar el pla 3D: {0}", "plane3DXDepthYToZ": "{0} (profunditat {1} a {2})", + "plane3DXNoDepth": "{0} (sense valors de profunditat)", "textEffect": "Efecte de text", "textEffectSettingsTitle": "Configuració de l'efecte de text", "textEffectPreset": "Predefinit", @@ -1416,9 +1513,20 @@ "deleteAllSubtitleBackups": "Voleu suprimir tots els fitxers de còpia de seguretat de subtítols?", "deleteAll": "Suprimeix-ho tot", "subtitles": "Subtítols", + "settingsBackupInfo": "Es fa una còpia de seguretat de Settings.json quan s'inicia el Subtitle Edit, com a màxim una vegada cada {0} dies i només si la configuració ha canviat. Es conserven les {1} còpies més recents.", + "settingsBackupInfoEveryStart": "Es fa una còpia de seguretat de Settings.json cada vegada que s'inicia el Subtitle Edit, si la configuració ha canviat. Es conserven les {0} còpies més recents.", + "settingsBackupOff": "La còpia de seguretat automàtica de la configuració està desactivada (Opcions → Configuració → Fitxer).", + "lastSettingsBackupX": "Última còpia de seguretat: {0}", + "noSettingsBackupsYet": "Encara no hi ha còpies de seguretat de la configuració", + "openSettingsBackupFolder": "Obre la carpeta de còpies de seguretat de la configuració", "backUpSettingsNow": "Fes una còpia de seguretat ara", "settingsBackedUp": "S'ha fet una còpia de seguretat de la configuració actual.", - "restoreSettings": "Restaura la configuració" + "restoreSettings": "Restaura la configuració", + "restoreSettingsFromX": "Voleu substituir la configuració actual per la còpia de seguretat de {0}?\n\nPrimer es farà una còpia de seguretat de la configuració actual. Alguns canvis només tenen efecte després de reiniciar el Subtitle Edit.", + "settingsRestored": "S'ha restaurat la configuració. Reinicieu el Subtitle Edit perquè tots els canvis tinguin efecte.", + "settingsRestoreFailed": "No s'ha pogut restaurar la configuració: {0}", + "settingsBackupNotValid": "la còpia de seguretat no és un fitxer de configuració vàlid, de manera que s'ha mantingut la configuració actual", + "deleteAllSettingsBackups": "Voleu suprimir tots els fitxers de còpia de seguretat de la configuració?" }, "propertiesDCinema": { "title": "Propietats de D-Cinema SMPTE", @@ -1432,22 +1540,16 @@ "editRate": "Taxa d'edició", "timeCodeRate": "Taxa del codi de temps", "startTime": "Temps d'inici", - "font": "Lletra", "fontId": "ID de la lletra", "fontUri": "URI de la lletra", - "generate": "Genera", - "fontColor": "Color de la lletra", "chooseColor": "Trieu un color", "fontEffect": "Efecte de la lletra", "effectColor": "Color de l'efecte", - "fontSize": "Mida de la lletra", "topBottomMargin": "Marge superior/inferior", "fadeUpTime": "Temps d'aparició gradual", "fadeDownTime": "Temps de desaparició gradual", - "frames": "Fotogrames", "zPosition": "Posició Z", - "zPositionHelp": "Els números positius allunyen el text, els negatius l'acosten; si la posició Z és zero, aleshores és 2D", - "export": "Exporta..." + "zPositionHelp": "Els números positius allunyen el text, els negatius l'acosten; si la posició Z és zero, aleshores és 2D" }, "webVtt": { "setStylesForSelectedLinesTitle": "Estableix els estils WebVTT per a les línies seleccionades", @@ -1485,11 +1587,61 @@ "ignoreFormatting": "Ignora el format", "ignoreFormattingHint": "Les línies que només difereixen en etiquetes de format, com o {\\an8}, no compten com a diferents", "showOnlyDifferencesInText": "Només les diferències en el text", + "ignoreNumbering": "Ignora la numeració", + "ignoreNumberingHint": "Les línies que només difereixen en el número no compten com a diferents", + "ignoreTimeDifferenceMs": "Tolerància de temps (ms)", + "ignoreTimeDifferenceMsHint": "Els temps d'inici i final que difereixen aquests mil·lisegons o menys no compten com a diferents", + "compareDifferences": "Diferències", + "compareTextDifferences": "Diferències de text", + "compareCurrent": "Actual", + "compareReference": "Referència", + "compareEditable": "Editable", + "compareEditableHint": "Els canvis fets aquí s'apliquen al subtítol quan feu clic a Aplica", + "compareReadOnly": "Només de lectura", + "compareTakeFromReference": "Agafa el text i la temporització de la referència", + "compareInsertFromReference": "Insereix aquesta línia al subtítol actual", + "compareDeleteFromCurrent": "Suprimeix aquesta línia del subtítol actual", + "compareTakeText": "Agafa el text", + "compareTakeTiming": "Agafa la temporització", + "compareEditLine": "Edita la línia", + "compareEditHint": "Ctrl+Enter per desar, Esc per cancel·lar", + "compareEdited": "Editat", + "compareOnePendingChange": "1 canvi pendent", + "compareXPendingChanges": "{0} canvis pendents", + "compareDiscardXChanges": "Voleu descartar els {0} canvis fets a Compara?", + "compareXChangesApplied": "Compara: s'han aplicat {0} canvis", + "compareChangeTextX": "#{0} text de la referència", + "compareChangeTimingX": "#{0} temporització de la referència", + "compareChangeTextAndTimingX": "#{0} text i temporització de la referència", + "compareChangeEditedX": "#{0} editat", + "compareChangeInsertedX": "#{0} inserit", + "compareChangeDeletedX": "#{0} suprimit", + "compareSyncPoint": "Punt de sincronització", + "compareSyncPointHint": "Punt de sincronització: aquestes dues línies sempre es mostren com a parella, i les línies de sobre i de sota s'alineen pel seu compte", + "compareSyncPickCurrent": "Punt de sincronització: utilitza aquesta línia actual", + "compareSyncPickReference": "Punt de sincronització: utilitza aquesta línia de referència", + "compareSyncRemove": "Elimina el punt de sincronització", + "compareClearXSyncPoints": "Neteja els punts de sincronització ({0})", + "compareSyncCurrentPickedX": "S'ha triat l'actual #{0} com a punt de sincronització; seleccioneu la línia de referència corresponent i feu clic a Sincronitza", + "compareSyncReferencePickedX": "S'ha triat la referència #{0} com a punt de sincronització; seleccioneu la línia actual corresponent i feu clic a Sincronitza", + "compareSyncWithCurrentX": "Sincronitza amb l'actual #{0}", + "compareSyncWithReferenceX": "Sincronitza amb la referència #{0}", + "compareSyncApply": "Sincronitza", + "compareSyncPointSetXY": "Punt de sincronització establert: l'actual #{0} i la referència #{1} ara es mostren com a parella", + "compareSyncAlreadyPairedXY": "L'actual #{0} i la referència #{1} ja es mostren com a parella amb el mateix inici; no hi ha res per sincronitzar", + "compareSyncPointShiftedXYZW": "Punt de sincronització establert: l'actual #{0}-#{1} s'ha desplaçat {2} per començar amb la referència #{3}", + "compareChangeSyncShiftXYZ": "#{0}-#{1} desplaçat {2} (punt de sincronització)", + "compareTimingDeltaHint": "Quant més tard (+) o més d'hora (−) comença i acaba la referència respecte a la línia actual", "loadXFromFile": "Carrega \"{0}\" des d'un fitxer", + "compareLoadSavedFile": "Carrega el fitxer desat", "saveCompareHtmlTitle": "Desa el fitxer HTML de comparació", "pickMatroskaTrackX": "Trieu la pista Matroska - {0}", "pickTransportStreamTrackX": "Trieu la pista del flux de transport - {0}", + "chapters": "Capítols", + "dvdVobFilesMissingX": "Falten fitxers VOB (trobat el {0}%)", "pickMp4TrackX": "Trieu la pista MP4 - {0}", + "pickMxfTrackX": "Trieu la pista MXF - {0}", + "pickMpegTrackX": "Trieu la pista MPEG - {0}", "rosettaProperties": "Propietats de Timed Text Rosetta IMSC", "rosettaFontSize": "Mida de la lletra (alçada de la fila)", "propertyTimeBase": "Base de temps", @@ -1499,7 +1651,6 @@ "propertyDefaultRegion": "Regió per defecte", "propertyStyleAttributeName": "Nom de l'atribut d'estil", "propertyTimeCodeFormat": "Format del codi de temps", - "propertyFileExtension": "Extensió del fitxer", "propertyTopOrigin": "Origen superior", "propertyTopExtent": "Extensió superior", "propertyBottomOrigin": "Origen inferior", @@ -1565,7 +1716,8 @@ "findWhat": "Cerca", "descriptionOptional": "Descripció (opcional)", "invalidRegularExpressionX": "Expressió regular no vàlida: {0}", - "regularExpressionTooSlowX": "L'expressió regular s'ha aturat després de {0} segons - s'omet la regla, proveu un patró més senzill" + "regularExpressionTooSlowX": "L'expressió regular s'ha aturat després de {0} segons - s'omet la regla, proveu un patró més senzill", + "moveToCategory": "Mou a la categoria" }, "find": { "searchTextWatermark": "Cerca text...", @@ -1578,7 +1730,9 @@ "replaceIn": "Substitueix/cerca a", "replaceInTextAndOriginal": "Text i text original", "replaceInTextOnly": "Només el text", - "replaceInOriginalOnly": "Només el text original" + "replaceInOriginalOnly": "Només el text original", + "replacedOneOccurrence": "S'ha substituït una coincidència", + "replacedXOccurrences": "S'han substituït {0} coincidències" }, "regularExpressionContextMenu": { "wordBoundary": "Límit de paraula (\\b)", @@ -1606,6 +1760,7 @@ "xLinesYSubtitles": "{0} línies, {1} subtítols", "xLinesYSubtitlesZErrors": "{0} línies, {1} subtítols, {2} error(s)", "couldNotParseAsX": "No s'ha pogut analitzar com a {0}", + "readAsXUseAsY": "No s'ha pogut llegir l'origen com a {1}, però sí com a {0}.\nVoleu utilitzar les línies i continuar desant com a {1}?", "selectedXCharactersYLines": "Seleccionats: {0} caràcters en {1} línia/línies", "replacedXOccurrences": "S'han substituït {0} coincidència/coincidències", "invalidRegularExpression": "Expressió regular no vàlida", @@ -1641,7 +1796,23 @@ "largeChangeWarning": "Canvi gran - sembla una reescriptura, reviseu-ho amb atenció", "mismatchWarning": "No s'assembla a l'original - pot correspondre a una altra línia", "engineError": "No s'ha pogut contactar amb el motor d'IA: {0}", - "playCurrentLineHint": "Reprodueix la línia seleccionada al reproductor de vídeo i atura't al final (també podeu fer doble clic a una línia)" + "playCurrentLineHint": "Reprodueix la línia seleccionada al reproductor de vídeo i atura't al final (també podeu fer doble clic a una línia)", + "editAfterHint": "Feu clic al text de la fila seleccionada per editar la correcció abans d'aplicar-la (Enter desa, Esc cancel·la)", + "template": "Plantilla", + "templatePlaceholder": "Trieu una plantilla per substituir la indicació", + "templateStandard": "Revisió estàndard", + "templateSpeechToText": "Neteja de veu a text", + "templateMachineTranslation": "Correcció de traducció automàtica", + "templateSdh": "SDH / discapacitat auditiva", + "referenceContext": "Context de referència", + "referenceContextInfo": "Opcional. Noms, personatges, glossari, sinopsi o notes d'estil: el model ho tracta com a autoritat per als noms i termes. S'envia amb cada lot.", + "referenceContextPlaceholder": "Noms: Kaito, tia Mei, Dr. Hollis\nTermes: Shinkansen, nucli de curvatura\nSinopsi: el Kaito viatja a Tòquio per trobar el seu pare desaparegut.", + "generateContext": "Genera amb IA", + "generateContextHint": "Deixa que el motor d'IA seleccionat llegeixi el subtítol i redacti noms, termes i una sinopsi; comproveu el resultat abans d'utilitzar-lo", + "generatingContextPartXOfY": "S'està llegint la part {0} de {1}...", + "generatingContextSynopsis": "S'està escrivint la sinopsi...", + "replaceContextQuestion": "Voleu substituir el context de referència actual?", + "noContextGenerated": "El motor d'IA no ha retornat cap informació de referència utilitzable." }, "aiAssistant": { "title": "Assistent d'IA", @@ -1687,6 +1858,7 @@ "removeLineBreaksPixelWidth": "Desfés el salt dels subtítols que caben en una línia (amplada en píxels)", "fixUppercaseIInsideLowercaseWords": "Corregeix la 'i' majúscula dins de paraules en minúscules (error d'OCR)", "fixDoubleApostrophes": "Corregeix els apòstrofs dobles ('') per una cometa doble (\")", + "fixMisreadQuotes": "Corregeix els apòstrofs llegits erròniament com a cometes dobles (OCR, anglès)", "addPeriods": "Afegeix un punt després de les línies on la línia següent comença amb majúscula", "startWithUppercaseLetterAfterParagraph": "Comença amb majúscula després d'un paràgraf", "startWithUppercaseLetterAfterPeriodInsideParagraph": "Comença amb majúscula després d'un punt dins del paràgraf", @@ -1709,6 +1881,7 @@ "fixCommasExample": ",, -> ,", "removeLineBreaksExample": "Foo
bar! -> Foo bar!", "fixDoubleApostrophesExample": "''Has double single quotes'' -> \"Has single double quote\"", + "fixMisreadQuotesExample": "\"Hello' -> \"Hello\"", "addPeriodsExample": "Hello world -> Hello world.", "startWithUppercaseLetterAfterParagraphExample": "p1: Foobar! || p2: foobar! -> p1: Foobar! || p2: Foobar!", "startWithUppercaseLetterAfterPeriodInsideParagraphExample": "Hello there! how are you? -> Hello there! How are you?", @@ -1798,6 +1971,8 @@ "fixMinDurationMs": "Corregeix la durada mínima (ms)", "doNotGoPastShotChange": "No passis del canvi de pla", "fixMaxDurationMs": "Corregeix la durada màxima (ms)", + "fixMinDurationFrames": "Corregeix la durada mínima (fotogrames)", + "fixMaxDurationFrames": "Corregeix la durada màxima (fotogrames)", "minimumDurationMilliseconds": "Durada mínima (mil·lisegons):", "maximumDurationMilliseconds": "Durada màxima (mil·lisegons):", "maxDurationShouldBeHigherThanMinDuration": "La durada màxima hauria de ser superior a la durada mínima", @@ -1848,16 +2023,67 @@ "timeCodesHint": "Ajusta els cues als temps reals dels fotogrames del vídeo en lloc de suposar uns fotogrames per segon constants. Cal llegir tot el vídeo una vegada; el resultat es desa a la memòria cau." }, "improveTimeCodes": { + "title": "Millora els codis de temps (alineació forçada)", + "aligner": "Alineador", + "alignerHint": "El model que escolta cada línia a l'àudio. Les millors opcions per a la llengua del subtítol es mostren primer.", + "maxShift": "Desplaçament màxim (segons)", + "maxShiftHint": "Fins a quin punt el subtítol pot estar desincronitzat. Una línia l'inici de la qual es desplaçaria més es deixa com està i es marca. Sigui quin sigui el valor, una sola línia que es vulgui moure més de mig segon més que les línies del voltant no es mou a cegues: segueix les veïnes quan totes s'han mogut, i en cas contrari s'ofereix desmarcada perquè la comproveu.", + "adjustStartTimes": "Ajusta els temps d'inici", + "adjustEndTimes": "Ajusta els temps finals", + "isolateSpeech": "Aïlla primer la veu (lent)", + "isolateSpeechHint": "Elimina la música i els efectes de so abans d'alinear, de manera que l'alineador només sent el diàleg. Ajuda en línies dites sobre música forta o acció, però triga aproximadament el mateix que l'àudio amb una GPU, i moltes vegades més sense.", + "isolatingSpeech": "S'està aïllant la veu... (pot trigar una estona)", + "isolateSpeechFailed": "No s'ha pogut aïllar la veu; s'ha alineat amb l'àudio original.", + "checkWithSpeechToText": "Comprova amb veu a text", + "checkWithSpeechToTextHint": "També transcriu l'àudio i comprova cada moviment respecte a on realment s'han sentit les paraules. Els moviments grans que la veu confirma es marquen automàticament, i els moviments que s'allunyen d'on se sent una línia es deixen desmarcats perquè els comproveu. També mostra quina part de cada línia s'ha sentit: una proporció baixa vol dir que el text difereix del que es diu. Un subtítol més desincronitzat que el desplaçament màxim (un desfasament constant, una deriva per fotogrames per segon incorrectes o salts on s'han tallat escenes) es sincronitza primer amb les paraules sentides. Utilitza Crisp ASR Parakeet, que triga uns minuts per a un llargmetratge amb una GPU.", + "checkWithSpeechToTextNotAvailableX": "La comprovació amb veu a text no està disponible per a {0}", + "transcribingToCheck": "S'està transcrivint per comprovar l'alineació...", + "speechToTextFailed": "Ha fallat la veu a text; els codis de temps no s'hi han comprovat.", + "speechToTextX": "Veu a text: {0}", + "speechToTextConfirmedXDisputedY": "Veu: {0} confirmades, {1} qüestionades", + "hardlyHeardX": "La veu a text només ha sentit el {0}% de les paraules del subtítol. El subtítol està en la llengua que es parla? L'alineador no pot col·locar una traducció.", + "heard": "Sentit", + "heardHint": "Quina part de la línia ha sentit la veu a text. Una proporció baixa vol dir que el text difereix del que es diu, que és on és més probable que l'alineador s'equivoqui.", + "statusConfirmedBySpeech": "Confirmat per la veu", + "statusDisputedBySpeech": "Sentit on era", + "statusMovedWithSync": "Mogut amb la sincronització", + "syncedFirstXY": "Sincronitzat primer: {0} s a l'inici, {1} s al final", + "showSpeechOnly": "Mostra només la veu", + "showSpeechOnlyHint": "Dibuixa la forma d'ona alineada a partir de l'àudio sense música ni efectes de so; la forma d'ona original es manté igual, per comparar. Disponible un cop s'ha aïllat la veu, aquí o amb «Mostra només la veu» a la forma d'ona de la finestra principal.", "align": "Alineació", "original": "Original", "aligned": "Alineat", + "intro": "Trieu un alineador i premeu «Alinea» per escoltar cada línia a l'àudio i ajustar-ne els codis de temps.", + "extractingAudio": "S'està extraient l'àudio...", + "aligningBatchXOfY": "S'està alineant... lot {0} de {1}", + "extractAudioFailed": "No s'ha pogut extreure l'àudio amb ffmpeg.", + "modelWillBeDownloaded": "Es baixarà {0}", + "summaryXRetimedYKeptZSkipped": "Retemporitzades: {0} · Mantingudes: {1} · Sense veu: {2}", + "meanShiftX": "Desplaçament mitjà: {0} ms", + "toCheckX": "Per comprovar: {0}", + "largeMovesToCheckX": "{0} línies es mourien molt per si soles i es deixen desmarcades; escolteu-les (F5 reprodueix la línia alineada, Shift+F5 l'original) i marqueu les que siguin correctes.", + "linesToCheckX": "{0} línies es deixen desmarcades per comprovar; escolteu-les (F5 reprodueix la línia alineada, Shift+F5 l'original) i marqueu les que siguin correctes.", + "changeXOfY": "Canvi {0} de {1}", "previousChange": "Canvi anterior", "nextChange": "Canvi següent", "startShift": "Inici", "endShift": "Final", + "statusRetimed": "Retemporitzada", + "statusMovedWithNeighbours": "Moguda amb les veïnes", + "statusLargeMoveUnconfirmed": "Moviment gran: escolteu-la i marqueu-la per aplicar", + "statusUnchanged": "Ja és al seu lloc", "statusNoSpeech": "Sense veu", + "statusShiftTooLarge": "Mantinguda: desplaçament massa gran", + "statusFailed": "Mantinguda: ha fallat l'alineador", + "statusAdjustedByHand": "Ajustada manualment", + "alignedWaveformHint": "Arrossegueu una línia per moure-la, o arrossegueu-ne les vores per canviar-ne l'inici o el final.", + "playLine": "Reprodueix la línia", + "undoAdjustment": "Desfés l'ajust", + "playPause": "Reprodueix / pausa", "playAligned": "Alineat", - "apply": "Aplica" + "playAlignedHint": "Reprodueix la línia seleccionada amb els codis de temps alineats", + "apply": "Aplica", + "applyHint": "Desmarqueu una línia per mantenir-ne els codis de temps originals. Feu doble clic en una línia per reproduir-la. Les línies també es poden moure o redimensionar a la forma d'ona alineada." }, "beautifyTimeCodesProfile": { "title": "Edita el perfil d'embelliment dels codis de temps", @@ -1865,7 +2091,11 @@ "presetDefault": "Per defecte", "presetNetflix": "Netflix", "presetSdi": "SDI", - "general": "General", + "saveAsProfile": "Desa com a perfil...", + "deleteProfile": "Suprimeix el perfil", + "profileName": "Nom del perfil", + "overwriteProfileX": "Ja existeix un perfil anomenat «{0}». Voleu sobreescriure'l?", + "deleteProfileX": "Voleu suprimir el perfil «{0}»?", "gap": "Interval", "gapSuffix": "fotogrames (també actualitza els camps d'interval diferents de zero de sota)", "milliseconds": "ms", @@ -1896,6 +2126,10 @@ "bridgeGaps": { "bridgeGapsSmallerThan": "Uneix els buits menors de (ms)", "minGap": "Interval mínim (ms)", + "bridgeGapsSmallerThanFrames": "Uneix els buits més petits que (fotogrames)", + "minGapFrames": "Interval mínim (fotogrames)", + "calculateBridgeGapsSmallerThanDotDotDot": "Calcula el límit d'unió de buits a partir dels fotogrames per segon...", + "useXMsAsBridgeGapsSmallerThan": "Voleu unir els buits més petits que «{0}» mil·lisegons?", "numberOfSmallGapsBridgedX": "Nombre de buits petits units: {0}", "percentFoPrevious": "Interval per a l'anterior (%)", "gapChange": "Canvi d'interval" @@ -1929,6 +2163,7 @@ "addColor": "Afegeix color", "deleteLinesWithSpecificActorsOrStyles": "Suprimeix les línies amb aquests actors o estils (separeu-los amb comes)", "useSourceStylesIfPossible": "Utilitza els estils d'origen si és possible", + "keepSourceEmbeddedFonts": "Mantén les lletres incrustades de l'origen (peu)", "editStyles": "Edita els estils", "editProperties": "Edita les propietats", "editAttachments": "Edita els adjunts", @@ -1960,17 +2195,50 @@ "convertColorsToDialogReBreakLines": "Torna a saltar les línies", "snapTimeCodesToFramesInfo": "Arrodoneix tots els temps d'inici i final al fotograma més proper. Els fotogrames per segon es llegeixen d'un fitxer de vídeo amb el mateix nom que el fitxer de subtítols, tret que se'n triï un valor fix.", "addFolderDotDotDot": "Afegeix una carpeta...", + "addFolderRecursiveDotDotDot": "Afegeix una carpeta de manera recursiva...", "selectFolderToConvert": "Seleccioneu la carpeta amb els fitxers a convertir", "includeSubfolders": "Inclou les subcarpetes en afegir una carpeta", + "videoFilesWhenAddingFolder": "Fitxers de vídeo (mkv, mp4, ts...) en afegir una carpeta", + "videoFilesAsk": "Pregunta", + "videoFilesInclude": "Afegeix-ne les pistes de subtítols", + "videoFilesSkip": "Omet", + "folderContainsXVideoFiles": "La carpeta conté {0} fitxers de vídeo (mkv, mp4, ts...).\n\nVoleu afegir també les pistes de subtítols incrustades, o només els fitxers de subtítols?", + "addVideoFiles": "Afegeix els fitxers de vídeo", + "skipVideoFiles": "Només fitxers de subtítols", + "doNotAskAgainVideoFiles": "No ho tornis a preguntar (es pot canviar a la configuració de la conversió per lots)", + "settingsSectionOutput": "Sortida", + "settingsSectionAddingFiles": "Addició de fitxers", + "settingsSectionImageBasedSubtitles": "Subtítols basats en imatges (OCR)", + "settingsSectionOther": "Altres", "keepSourceFileTimestamp": "Mantén la data/hora del fitxer d'origen als fitxers de sortida", + "preventSleepWhileConverting": "Evita que l'ordinador entri en repòs durant la conversió", "scanningFolderX": "S'està analitzant {0}...", + "transportStreamSettings": "Configuració del flux de transport", + "transportStreamSettingsDotDotDot": "Configuració del flux de transport...", + "transportStreamSettingsInfo": "S'aplica a les pistes de subtítols extretes de fitxers de flux de transport (.ts/.m2ts). La posició i la mida del vídeo només afecten les pistes d'imatge DVB exportades a un format basat en imatges.", "transportStreamOverrideXPosition": "Sobreescriu la posició X original", "transportStreamOverrideYPosition": "Sobreescriu la posició Y original", "transportStreamOverrideVideoSize": "Sobreescriu la mida original del vídeo", "transportStreamBottomMargin": "Marge inferior", + "transportStreamFileNameEnding": "Final del nom del fitxer", + "transportStreamFileNameEndingInfo": "S'afegeix abans de l'extensió per a cada pista extreta. Deixeu-ho buit per utilitzar el sufix de llengua habitual.", "transportStreamOnlyTeletext": "Només teletext", + "transportStreamGetSizeFromVideo": "Obtén la mida del vídeo...", "twoLetterLanguageCodeUppercase": "Codi de llengua de dues lletres (en majúscules)", - "threeLetterLanguageCodeUppercase": "Codi de llengua de tres lletres (en majúscules)" + "threeLetterLanguageCodeUppercase": "Codi de llengua de tres lletres (en majúscules)", + "formatContainsDotDotDot": "El format conté...", + "extensionIsDotDotDot": "L'extensió és...", + "statusIsError": "L'estat és error", + "forcedTracksOnly": "Només pistes forçades", + "xConvertedYFailedInZ": "{0:#,###,##0} convertits, {1:#,###,##0} fallits en {2}", + "xNotProcessed": "{0:#,###,##0} no processats", + "removeUnicodeControlCharactersTitle": "Elimina els caràcters de control Unicode", + "removeUnicodeControlCharactersInfo": "Elimina del text els caràcters invisibles de control de direcció Unicode (LRM, RLM, LRE, RLE, PDF, LRO i RLO) i substitueix els espais no separables per espais normals.", + "preset": "Predefinit", + "savePresetDotDotDot": "Desa el predefinit...", + "deletePreset": "Suprimeix el predefinit", + "presetName": "Nom del predefinit", + "deletePresetX": "Voleu suprimir el predefinit «{0}»?" }, "changeCasing": { "fixNames": "Corregeix els noms", @@ -1983,6 +2251,7 @@ "allUppercase": "Tot en majúscules", "allLowercase": "Tot en minúscules" }, + "changeFormatting": {}, "convertActors": { "title": "Converteix els actors", "convertActorFrom": "Converteix l'actor de", @@ -2054,10 +2323,13 @@ }, "mergeLinesWithSameText": { "maxMsBetweenLines": "Màxim de mil·lisegons entre línies", - "includeIncrementingLines": "Inclou les línies amb text incremental" + "maxFramesBetweenLines": "Màxim de fotogrames entre línies", + "includeIncrementingLines": "Inclou les línies amb text incremental", + "includeRollUpCaptions": "Inclou els subtítols roll-up (desplaçables)" }, "mergeLinesWithSameTimeCodes": { "maxMsDifference": "Diferència màxima (mil·lisegons)", + "maxFramesDifference": "Diferència màxima (fotogrames)", "makeDialog": "Crea diàlegs" }, "netflixCheckAndFix": { @@ -2157,12 +2429,29 @@ "letterboxCustom": "Alçada de banda personalitzada", "barHeightPx": "Alçada de la banda (px)", "titleSafePercent": "Zona segura del títol (%)", + "moveCaptions": "Mou els subtítols", + "moveCaptionsDotDotDot": "Mou els subtítols...", + "moveCaptionsTo": "Mou els subtítols", + "moveCaptionsIntoBars": "A les bandes negres (fora de la imatge)", + "moveCaptionsIntoPicture": "Dins de la imatge (fora de les bandes)", + "moveCaptionsOffset": "Distància des de la vora (px)", + "letterboxBarHeightXPxYOfZCaptionsWillMove": "Alçada de la banda: {0} px - es mouran {1} de {2} subtítols", + "removeFadeInOut": "Elimina l'aparició/desaparició gradual", + "removeFadeInOutXLinesRemoved": "S'han fusionat {0} línies de transició amb les línies a què pertanyen", + "removeFadeInOutNothingFound": "No s'ha trobat cap línia d'aparició/desaparició gradual", "positionSummary": "{0}×{1} - {2} subtítols - alçada de la banda: {3} px", "xInActivePicture": "{0} a la imatge", "xInTopBar": "{0} a la banda superior", "xInBottomBar": "{0} a la banda inferior", "noImageSubtitlesLoaded": "No s'ha carregat cap subtítol d'imatge", - "imageBasedFormatNotSupported": "Format de subtítols basat en imatges no trobat o no compatible." + "imageBasedFormatNotSupported": "Format de subtítols basat en imatges no trobat o no compatible.", + "frameRateDeclaredInFile": "Fotogrames per segon declarats al fitxer sup de Blu-ray", + "frameRateDetectedFromTimeCodesX": "Fotogrames per segon detectats a partir dels codis de temps del sup de Blu-ray (el fitxer declara {0})", + "frameRateFromVideo": "Fotogrames per segon del vídeo", + "frameRateCurrent": "Fotogrames per segon actuals", + "frameRateSetManually": "Fotogrames per segon establerts manualment", + "frameRateUsageInfo": "Les exportacions a Blu-ray sup, BDN XML, DOST i FCP s'escriuen amb aquests fotogrames per segon. No canvia cap temps; per a això utilitzeu «Canvia els fotogrames per segon».", + "useVideoFrameRateXInsteadOfY": "Els fotogrames per segon del vídeo són {0}, però el subtítol està establert a {1}.\n\nVoleu utilitzar els fotogrames per segon del vídeo ({0})?" }, "removeTextForHearingImpaired": { "interjections": "Interjeccions", @@ -2192,6 +2481,7 @@ "pickFontNameInstalledFonts": "Lletres instal·lades", "pickFontNameCollectedFonts": "Lletres recollides", "colorPickerTitle": "Trieu un color", + "colorPickerBrightness": "Brillantor", "filterLayersTitle": "Filtra les capes per mostrar", "filterLayersHideFromWaveform": "Amaga a la forma d'ona/l'espectrograma", "filterLayersHideFromSubtitleGrid": "Amaga a la graella de subtítols", @@ -2242,7 +2532,21 @@ "correctWordsX": "Paraules correctes: {0}", "namesX": "Noms: {0}", "addedToDictionaryX": "Afegides al diccionari: {0}", - "doNotShowThisAgain": "No tornis a mostrar aquest missatge" + "doNotShowThisAgain": "No tornis a mostrar aquest missatge", + "editUseAlwaysList": "Edita la llista «Utilitza sempre»...", + "useAlwaysListTitle": "Verificació ortogràfica - llista «Utilitza sempre»", + "useAlwaysListHeader": "Llista «Utilitza sempre»", + "useAlwaysListDescription": "Paraules que la verificació ortogràfica substitueix automàticament, desades amb «Canvia-ho tot» i «Utilitza sempre». Un parell només s'utilitza quan la paraula està mal escrita.", + "useAlwaysWord": "Paraula mal escrita", + "xPairs": "{0:#,##0} parells", + "xUnusedPairs": "{0:#,##0} mai utilitzats", + "useAlwaysUnusedHint": "Mai utilitzat: aquesta paraula està ben escrita (o és un nom o una paraula de l'usuari), de manera que la verificació ortogràfica no la marca mai", + "removeUnused": "Elimina els no utilitzats", + "useAlwaysEmpty": "Encara no hi ha parells «Utilitza sempre» per a aquesta llengua", + "useAlwaysEmptyHint": "Els parells s'afegeixen quan feu clic a «Canvia-ho tot» o «Utilitza sempre» a la verificació ortogràfica.", + "useAlwaysNoMatch": "Cap parell coincideix amb la cerca", + "useAlwaysSameWord": "La substitució ha de ser diferent de la paraula.", + "useAlwaysRememberOff": "«Recorda la llista 'Utilitza sempre'» està desactivat a la configuració, de manera que la verificació ortogràfica no utilitza ni desa aquesta llista." }, "video": { "burnIn": { @@ -2255,6 +2559,7 @@ "sampleRate": "Freqüència de mostreig", "audio": "Àudio", "preset": "Predefinit", + "tune": "Ajust (tune)", "pixelFormat": "Format de píxel", "crf": "CRF", "useSource": "Utilitza l'origen", @@ -2266,7 +2571,9 @@ "outputProperties": "Propietats de la sortida...", "videoFileSize": "Mida del fitxer de vídeo", "oneBox": "Una caixa", - "logoInfo": "Trieu una imatge PNG i arrossegueu-la per situar-la al vídeo." + "logoInfo": "Trieu una imatge PNG i arrossegueu-la per situar-la al vídeo.", + "noSubtitlesGenerateAnyway": "No hi ha línies de subtítols per incrustar.\n\nVoleu generar el vídeo sense subtítols?", + "noSubtitlesInXItemsGenerateAnyway": "{0} elements del lot no tenen línies de subtítols per incrustar.\n\nVoleu generar aquests vídeos sense subtítols?" }, "videoTransparent": { "title": "Genera un vídeo transparent amb subtítols" @@ -2277,6 +2584,21 @@ "translateToEnglish": "Tradueix a l'anglès", "transcribing": "S'està transcrivint...", "transcribingXOfY": "S'està transcrivint {0} de {1}...", + "linesTranscribedXOfYFailedZ": "S'han transcrit {0} de {1} línies; {2} han fallat o no tenien veu.\n\nLes línies fallides mantenen el text actual. Voleu aplicar les línies transcrites o tornar a provar les fallides (primer podeu canviar el motor o la configuració)?", + "linesTranscribedXOfYCancelled": "S'ha cancel·lat la transcripció després de {0} de {1} línies.\n\nVoleu aplicar les línies transcrites? Les altres línies mantenen el text actual.", + "noLinesTranscribed": "No s'ha pogut transcriure cap de les {0} línies.\nConsulteu el registre de les eines per obtenir-ne més detalls.", + "applyTranscribedLines": "Aplica les transcrites", + "retryFailedLines": "Torna a provar les fallides", + "isolateSpeech": "Aïlla la veu (lent)", + "isolateSpeechHint": "Elimina la música i els efectes de so abans de transcriure. Fa que es perdin menys línies i millora la temporització en àudio amb música forta, però triga aproximadament el mateix que l'àudio amb una GPU, i moltes vegades més sense.", + "isolatingSpeech": "S'està aïllant la veu...", + "isolateSpeechFailed": "No s'ha pogut aïllar la veu; es transcriu l'àudio original.", + "detectSpeakers": "Detecta els parlants", + "detectSpeakersHint": "Comença cada línia amb el parlant, com ara «(speaker 0)», mitjançant la diarització Sortformer de NVIDIA (fins a 8 parlants). Funciona millor amb motors que divideixen les línies on canvia el parlant, com Parakeet i Cohere.", + "detectSpeakersNeedsNewerCrispAsr": "«Detecta els parlants» necessita Crisp ASR v{0} o posterior, però hi ha instal·lada la v{1}.\n\nActualitzeu Crisp ASR (el botó d'engranatge al costat del motor) o desactiveu «Detecta els parlants».", + "xNeedsNewerCrispAsr": "{0} necessita Crisp ASR v{1} o posterior, però hi ha instal·lada la v{2}.\n\nVoleu baixar i instal·lar l'actualització ara?", + "vad": "VAD", + "vadHint": "Detecció d'activitat de veu: només es transcriu la veu, tallada on el parlant fa pauses.\nAuto: Silero, i només per als motors que ho necessiten (Cohere, Mega, Index-Echo).\nSilero, FireRedVAD, Whisper-VAD, WebRTC: sempre activat, per a tots els motors Crisp ASR. FireRedVAD és el més precís; Whisper-VAD fa menys talls i més nets, però és el més lent.\nIndex-Echo sempre utilitza Silero. --vad, --vad-model o --chunk-seconds a la configuració avançada tenen prioritat.", "inputLanguage": "Llengua d'entrada", "advancedWhisperSettings": "Paràmetres avançats de veu a text", "downloadingSpeechToTextEngine": "S'està baixant el motor de veu a text", @@ -2360,6 +2682,19 @@ "videoEncodingSettings": "TTS - Configuració de la codificació de vídeo", "elevenLabsSettings": "TTS - Configuració d'ElevenLabs", "elevenLabsSettingsResetHint": "Restableix la configuració d'ElevenLabs als valors per defecte", + "provider": "Proveïdor", + "openAiCompatibleSettings": "TTS - Configuració compatible amb OpenAI", + "customServerUrl": "URL del servidor personalitzat", + "customServerUrlHint": "Punt final de veu complet d'un servidor compatible amb OpenAI, utilitzat quan el proveïdor és «Custom», p. ex. http://localhost:8880/v1/audio/speech", + "customModels": "Models personalitzats", + "customModelsHint": "Noms de models separats per comes que s'ofereixen quan el proveïdor és «Custom»", + "customVoices": "Veus personalitzades", + "customVoicesHint": "Noms de veus separats per comes, utilitzats pel proveïdor «Custom» i pels models d'OpenRouter que no publiquen cap llista de veus", + "instructions": "Instruccions", + "instructionsHint": "Estil de parla opcional, p. ex. «Parla amb una veu tranquil·la i càlida». Només s'envia als models que ho admeten (no tts-1/tts-1-hd)", + "openAiSpeedHint": "Velocitat de la veu de 0,25 a 4,0 (1,0 és normal). No tots els models ho admeten", + "outputFormat": "Format de sortida", + "outputFormatHint": "Format d'àudio sol·licitat al servidor. «Auto» demana MP3 i canvia a PCM per als models que només retornen PCM (p. ex. Gemini TTS)", "regenerateAudio": "Torna a generar l'àudio", "autoContinuePlaying": "Continua reproduint automàticament", "playLine": "Reprodueix la línia", @@ -2370,8 +2705,11 @@ "importVoiceDotDotDot": "Importa una veu...", "renameVoiceDotDotDot": "Canvia el nom de la veu...", "renameVoiceTitle": "Canvia el nom de la veu", + "voiceXCouldNotBeRenamedX": "No s'ha pogut canviar el nom de la veu «{0}»: {1}", "deleteVoiceDotDotDot": "Suprimeix la veu...", "deleteVoiceTitle": "Suprimeix la veu", + "deleteVoiceXQuestion": "Voleu suprimir la veu «{0}»?\n\nL'enregistrament de referència i els seus fitxers s'eliminen del disc.", + "voiceXCouldNotBeDeletedX": "No s'ha pogut suprimir la veu «{0}»: {1}", "voiceImportSuccessTitle": "Veu importada", "voiceXImported": "La veu «{0}» s'ha importat correctament", "voiceXCouldNotBeImported": "No s'ha pogut importar la veu «{0}» - consulteu el registre per als detalls", @@ -2384,24 +2722,46 @@ "voiceManagerTitle": "TTS - Gestor de veu", "voiceManagerDotDotDot": "Gestor de veu...", "voiceManagerMenuItem": "Gestor de veu TTS...", + "speakFromCurrentLineTitle": "Llegeix en veu alta des de la línia actual", + "speakFromCurrentLineDotDotDot": "Llegeix en veu alta des de la línia actual...", + "speakFromCurrentLineHint": "Llegeix les línies en veu alta una darrere l'altra, començant per la línia actual. Premeu Esc o feu clic a una altra línia per aturar-ho.", + "stopSpeaking": "Atura la lectura", + "speakingLineX": "S'està llegint la línia {0}... (Esc per aturar)", + "playWithSpeechTitle": "Reprodueix amb veu", + "playWithSpeechDotDotDot": "Reprodueix amb veu...", + "playWithSpeechHint": "Reprodueix el vídeo i llegeix cada línia quan el vídeo hi arriba. Feu pausa i cerqueu com sempre; premeu Esc per aturar.", + "lowerVideoVolumeWhileSpeaking": "Abaixa el volum del vídeo mentre es parla", + "pauseVideoWhenSpeechRunsLate": "Posa en pausa el vídeo quan la veu s'allargui fins a la línia següent", + "waitingForSpeechLineX": "S'està generant la veu per a la línia {0}... (Esc per aturar)", "voiceKindClone": "Veu clonada", "voiceKindPreset": "Integrat", "voiceKindModel": "Model de veu", "voiceKindOnline": "En línia", "transcript": "Transcripció", + "transcriptRequiredHint": "Obligatori: el motor llegeix aquesta transcripció de l'enregistrament (ref-text) en clonar", + "transcriptOptionalHint": "Opcional: una transcripció precisa de l'enregistrament millora la clonació", "saveTranscript": "Desa la transcripció", "searchVoices": "Cerca veus", "loadingVoicesDotDotDot": "S'estan carregant veus...", + "noVoicesForEngine": "No hi ha veus: importeu un enregistrament o baixeu un paquet de veu", + "noVoicesMatchFilter": "Cap veu coincideix amb la cerca", "copyVoiceTo": "Copia la veu a", "copyVoiceToDotDotDot": "Copia la veu al motor...", + "copyingVoiceXToYDotDotDot": "S'està copiant la veu «{0}» a {1}...", + "voiceXCopiedToY": "S'ha copiat la veu «{0}» a {1}", + "voiceXCouldNotBeCopiedToY": "No s'ha pogut copiar la veu «{0}» a {1}; consulteu el registre per obtenir-ne més detalls", + "voiceXAlreadyExistsInYContinue": "Ja existeix una veu anomenada «{0}» a {1}.\n\nVoleu copiar-la igualment?", "openVoicesFolder": "Obre la carpeta de veus", "downloadVoicePacksDotDotDot": "Baixa els paquets de veu...", "downloadVoicePacksTitle": "TTS - Baixa els paquets de veu", + "voicePackIntro": "Els paquets de veu són conjunts d'enregistraments curts i nets amb transcripcions, preparats per clonar. Cada paquet s'importa al motor triat (les veus que ja hi són s'ometen) i inclou un ATTRIBUTION.txt amb la seva llicència i fonts.", "voicePackDetailsXVoicesYSizeZLicense": "{0} veus · {1} · {2}", "installToEngine": "Instal·la al motor", "downloadAndInstall": "Baixa i instal·la", + "selectAtLeastOneVoicePack": "Seleccioneu almenys un paquet de veu", "downloadingXDotDotDot": "S'està baixant {0}...", "installingXDotDotDot": "S'està instal·lant {0}...", + "xVoicesInstalledYSkipped": "S'han instal·lat {0} veus ({1} ja hi eren)", "useSpeechToTextDotDotDot": "Utilitza veu a text...", "autoCastMenuItem": "Cerca veus al vídeo i clona-les...", "autoCastSpeakersTitle": "Veus trobades al vídeo", @@ -2420,7 +2780,7 @@ "cloneVoicePerLinePreparing": "S'està prenent la veu de cada línia del vídeo...", "cloneVoicePerLineNeedsVideo": "Per clonar la veu de cada línia cal el vídeo al qual pertany el subtítol. Obriu el vídeo i torneu-ho a provar.", "cloneVoicePerLineNoClips": "No s'ha pogut prendre àudio del vídeo, així que no hi ha res per clonar. Comproveu que el vídeo tingui una pista d'àudio.", - "cloneVoicePerLineNeedsOriginalSubtitleX": "{0} només pot clonar una línia quan sap què es diu al vídeo en aquella línia, cosa que prové del subtítol en la llengua original. No hi ha cap subtítol original carregat, així que totes les línies les diria la primera veu importada en lloc de la veu del vídeo.\n\nObriu el subtítol original (Fitxer > Obre l'original...) al costat de la traducció i torneu a generar.", + "cloneVoicePerLineTranscribeClipsXYZ": "{0} només pot clonar una línia quan sap què es diu al vídeo en aquella línia. Això no se sap per a {1} de {2} línies; normalment prové del subtítol en la llengua original (Fitxer > Obre l'original...).\n\nVoleu transcriure ara aquestes línies del vídeo amb veu a text?\n\nSí: trieu el motor i la llengua que es parla al vídeo, i els fragments de referència es transcriuran abans de generar la veu.\nNo: genera igualment; aquestes línies les dirà una veu normal en lloc de la veu del vídeo.", "voiceCloneConsentTitle": "Clonació de veu - abans de començar", "voiceCloneConsentHeader": "Esteu a punt de clonar una veu", "voiceCloneConsentIntro": "La clonació copia la veu d'una persona real. Això comporta normes a la majoria de llocs i, a la UE, obligacions legals que recauen sobre vós, no sobre el Subtitle Edit. Llegiu això una vegada.", @@ -2437,10 +2797,29 @@ "advancedTtsSettings": "Configuració avançada de TTS", "advancedTtsAudioProcessing": "Processament de l'àudio", "advancedTtsOutput": "Sortida", + "advancedTtsVoiceCloning": "Clonació de veu", + "cleanCloneReferences": "Neteja les referències de clonació de veu (Sidon)", + "cleanCloneReferencesDescription": "Elimina la música, el soroll i la reverberació de l'àudio a partir del qual es clona una veu: fragments presos del vídeo i enregistraments importats al gestor de veu. Utilitza el model de restauració de veu Sidon amb audio.cpp (baixada de 940 MB). La veu clonada ja no copia el fons, però la veu molt baixa o superposada pot sortir esmorteïda.", + "cleaningCloneReferencesDotDotDot": "S'estan netejant les referències de clonació de veu...", + "downloadSidonTitle": "Voleu baixar Sidon?", + "downloadSidonQuestionX": "Per netejar les referències de clonació de veu cal el model de restauració de veu Sidon ({0}).\n\nVoleu baixar-lo ara? Sense ell, les referències s'utilitzen tal com són.", "proAudioPostProcessing": "Postprocessament d'àudio professional", "proAudioPostProcessingDescription": "Afegeix EQ, porta de soroll, compressió, normalització de sonoritat (-16 LUFS) i una breu aparició/desaparició gradual a cada tall.", + "generatingSpeechSegmentXOfY": "S'està generant la veu: segment {0} de {1}", + "adjustingSpeedSegmentXOfY": "S'està ajustant la velocitat: segment {0} de {1}", + "mergingAudioSegmentXOfY": "S'està fusionant l'àudio: segment {0} de {1}", + "postProcessingSegmentXOfY": "Postprocessament: segment {0} de {1}", "audioDucking": "Atenuació de l'àudio", "audioDuckingDescription": "Abaixa el so original del vídeo i hi mescla la veu a sobre, de manera que la pista original queda lleugerament audible. Només s'aplica quan la veu s'afegeix al fitxer de vídeo.", + "removeOriginalSpeech": "Elimina la veu original (lent)", + "removeOriginalSpeechDescription": "Manté la música i els efectes de so del vídeo original però n'elimina la veu, i hi barreja la veu nova a sobre. Triga aproximadament el mateix que el vídeo amb una GPU, i moltes vegades més sense. Amb l'atenuació de l'àudio activada, el seu volum s'aplica a la música i els efectes. Només s'aplica quan la veu s'afegeix al fitxer de vídeo.", + "removingOriginalSpeech": "S'està eliminant la veu original...", + "removeOriginalSpeechFailed": "No s'ha pogut eliminar la veu original; la veu s'ha afegit al vídeo sense fer-ho.", + "speechRemovalModel": "Model", + "speechRemovalModelDescription": "La millor qualitat conserva més música i efectes de so. El ràpid és unes tres vegades més ràpid, amb una mica més de pèrdua de música.", + "speechRemovalBestQuality": "Millor qualitat (Mel-Band RoFormer)", + "speechRemovalFast": "Ràpid (HTDemucs)", + "videoTruncatedWarning": "La imatge del fitxer de vídeo nou s'atura després de {0}, però el vídeo original dura {1}: la imatge es quedarà congelada mentre el so continua.\n\nS'ha conservat el fitxer. Consulteu error-log.txt a la carpeta de dades del Subtitle Edit per veure la sortida de ffmpeg.", "originalVolumePercent": "% del volum original", "vadSilenceCompression": "Compressió de silencis VAD", "vadSilenceCompressionDescription": "Escurça les pauses entre paraules en lloc d'accelerar la parla, de manera que un tall hi cap sense cap pèrdua de qualitat.", @@ -2523,7 +2902,9 @@ "timeCodeFormatColon": "Format del codi de temps:", "shotChangesList": "Llista de canvis de pla", "shotChangesClearQuestion": "Segur que voleu buidar tots els canvis de pla?", - "deleteSelectedShotChangeQuestion": "Segur que voleu suprimir el canvi de pla seleccionat?" + "deleteSelectedShotChangeQuestion": "Segur que voleu suprimir el canvi de pla seleccionat?", + "shotChanges": "Canvis de pla", + "exportShotChanges": "Exporta els canvis de pla" }, "chapters": { "title": "Capítols", @@ -2553,7 +2934,6 @@ "scaleTimesDescription": "Escala tots els temps dels capítols d'uns fotogrames per segon a uns altres.", "fromFrameRate": "De", "toFrameRate": "A", - "apply": "Aplica", "writeToVideo": "Escriu al vídeo...", "writeToVideoTitle": "Escriu els capítols al vídeo", "writeToVideoDescription": "Els capítols s'escriuen en una còpia del fitxer de vídeo. No es torna a codificar res, així que és ràpid i sense pèrdua.", @@ -2563,7 +2943,6 @@ "writtenToX": "S'han escrit els capítols a {0}", "unableToWriteChapters": "No s'han pogut escriure els capítols al fitxer de vídeo.", "chaptersSavedToX": "S'han desat els capítols a {0}", - "noVideoLoaded": "No s'ha carregat cap vídeo", "emptyListCallToAction": "Encara no hi ha capítols - afegiu-ne un a la posició del vídeo o importeu-los d'un vídeo o fitxer." }, "videoOcr": { @@ -2588,10 +2967,10 @@ "runningOcrXY": "S'està executant l'OCR... {0}/{1}", "noLinesFoundTitle": "No s'ha trobat cap subtítol", "noLinesFoundMessage": "No s'ha trobat text a l'àrea d'escaneig - proveu d'ajustar l'àrea d'escaneig, el motor o la brillantor mínima.", + "ocrStoppedEarlyMessage": "El motor d'OCR s'ha aturat abans de llegir tots els fotogrames, de manera que es conserven les línies trobades fins ara i falta la resta.{0}{0}{1}", "linesFoundX": "S'han trobat {0} línies", "refiningTimingXY": "S'està refinant la temporització... {0}/{1}", "fixOcrErrors": "Corregeix els errors d'OCR", - "dictionary": "Diccionari", "editLineX": "Edita la línia {0}", "downloadDictionaryHint": "Baixeu un diccionari de verificació ortogràfica...", "framesPerSecondHint": "Quants fotogrames per segon s'escanegen. 5 cobreix els subtítols normals; un valor més alt capta línies molt curtes i ajusta millor l'agrupació, però costa més temps d'OCR.", @@ -2609,19 +2988,57 @@ "testOcrRunning": "S'està provant l'OCR al fotograma actual...", "testOcrResultX": "Resultat de la prova: {0}", "testOcrNoTextFound": "Prova: no s'ha trobat text a l'àrea d'escaneig", + "engineSettings": "Configuració del motor d'OCR", "engineBuiltIn": "Integrat en el sistema operatiu", + "engineExternalServer": "Servidor extern (instal·lat i gestionat fora del Subtitle Edit)", + "engineCloudApi": "API al núvol (requereix una clau API)", + "engineNothingToInstall": "No cal instal·lar res", + "engineModelsMissing": "Motor instal·lat, falten models", "website": "Lloc web" }, "backgroundMusic": { "title": "Genera música de fons", "generateBackgroundMusicDotDotDot": "Genera música de fons...", + "preset": "Predefinit", + "prompt": "Indicació", + "tempoBpm": "Tempo (BPM)", "generateSeconds": "Genera (segons)", "seed": "Llavor", "randomSeed": "Aleatori", "outputLength": "Longitud de la sortida", + "outputLengthVideoX": "{0} (durada del vídeo; la música es repeteix sense interrupcions)", "musicVolumePercent": "Volum de música (%)", + "removeExistingAudioTracks": "Elimina les pistes d'àudio existents", + "originalAudioVolumePercent": "Volum de l'àudio original (%)", "saveAudioDotDotDot": "Desa l'àudio...", - "addToVideoDotDotDot": "Afegeix al vídeo..." + "addToVideoDotDotDot": "Afegeix al vídeo...", + "engineInfo": "ACE-Step 1.5 Turbo amb audio.cpp (llicència MIT): s'executa localment", + "presetCooking": "Cuina / rebosteria", + "presetTech": "Tecnologia / ressenya de gadgets", + "presetForest": "Bosc / natura", + "presetRelaxingPiano": "Piano relaxant", + "presetLofiVlog": "Vlog lo-fi", + "presetCorporate": "Corporatiu / explicatiu", + "loadingModel": "S'està carregant el model...", + "composing": "S'està component...", + "generatingAudio": "S'està generant l'àudio...", + "decodingAudio": "S'està descodificant l'àudio...", + "creatingLoop": "S'està creant un bucle continu...", + "resultLoopXYZ": "Bucle: {0} a {1} BPM, coincidència de la unió {2}%", + "resultNoLoopX": "Música a punt: {0}", + "addingMusicToVideo": "S'està afegint la música al vídeo...", + "downloadModelTitle": "Voleu baixar el model de música?", + "downloadModelQuestionX": "Per generar música cal el model ACE-Step 1.5 ({0}).\n\nVoleu baixar el model?", + "downloadingModel": "S'està baixant el model ACE-Step 1.5...", + "saveAudioTitle": "Desa la música de fons", + "audioFileSavedX": "S'ha desat el fitxer d'àudio: {0}", + "unableToGenerateMusic": "No s'ha pogut generar la música", + "unableToAddMusicToVideo": "No s'ha pogut afegir la música al vídeo", + "outputLengthTextToSpeech": "Segueix la veu generada: la música es repeteix sense interrupcions i baixa mentre algú parla", + "addBackgroundMusic": "Afegeix música de fons", + "addBackgroundMusicHint": "Genera música localment amb ACE-Step 1.5 i barreja-la sota la veu; baixa mentre algú parla", + "generatingBackgroundMusicDotDotDot": "S'està generant la música de fons...", + "mixingBackgroundMusicDotDotDot": "S'està barrejant la música de fons..." }, "goToVideoPosition": "Ves a la posició del vídeo", "goToVideoPositionDotDotDot": "Ves a la posició del vídeo...", @@ -2632,20 +3049,47 @@ "openSecondarySubtitleOnVideoPlayerDotDotDot": "Obre un segon fitxer de subtítols...", "openSecondarySubtitleOnVideoPlayer": "Segon fitxer de subtítols (al reproductor de vídeo)", "removeSecondarySubtitleOnVideoPlayer": "Elimina el segon fitxer de subtítols", + "editSecondarySubtitleSettings": "Edita la configuració del segon subtítol", + "editSecondarySubtitleSettingsDotDotDot": "Edita la configuració del segon subtítol...", + "secondarySubtitleRememberSettings": "Recorda aquesta configuració", + "secondarySubtitleDoNotShowAgain": "No tornis a mostrar aquest diàleg", + "secondarySubtitleJustifyLines": "Justifica les línies", "openRecentVideo": "Obre un vídeo recent", "clearRecentVideos": "Neteja els vídeos recents", "cutVideoTitle": "Retalla el vídeo", "cutVideoDotDotDot": "Retalla el vídeo...", + "remuxVideoTitle": "Remultiplexa el vídeo", + "remuxVideoDotDotDot": "Remultiplexa el vídeo...", "remuxVideoInputVideo": "Vídeo d'entrada:", + "remuxVideoMultipleTracksRequiresMkv": "Diverses pistes d'àudio o de subtítols requereixen el contenidor MKV. El format de sortida s'ha canviat automàticament a .mkv.", "remuxVideoOutputFormat": "Format de sortida:", "remuxVideoOutputFile": "Fitxer de sortida:", + "remuxVideoRemuxing": "S'estan remultiplexant el vídeo i l'àudio...", + "remuxVideoFinalizing": "S'està finalitzant el fitxer de sortida (movent l'índex al principi)...", + "remuxVideoCompleted": "S'ha remultiplexat el vídeo correctament.", + "remuxVideoFailed": "No s'ha pogut remultiplexar el vídeo.", + "remuxVideoPleaseSelectBoth": "Seleccioneu tant el fitxer de vídeo com el d'àudio.", + "remuxVideoAssRequiresMkv": "Els subtítols ASS/SSA requereixen el contenidor MKV per conservar tot el format i els estils.", + "remuxVideoSccRequiresMov": "Els subtítols Scenarist (.scc) s'incrusten com a subtítols tancats CEA-608, que requereixen el contenidor MOV o MPG. El format de sortida s'ha canviat automàticament a .mov.", + "remuxVideoSccNotWithWebM": "Els subtítols tancats Scenarist (.scc) necessiten el contenidor MOV, que no pot contenir el vídeo VP9/AV1 d'un fitxer .webm. Elimineu el fitxer .scc o convertiu-lo a un altre format de subtítols.", + "remuxVideoMpgMaxTwoCaptionTracks": "Un fitxer .mpg conté com a màxim dues pistes de subtítols tancats (CC1 i CC3); elimineu fitxers de subtítols perquè en quedin un o dos.", + "remuxVideoMccRequiresMpg": "Els subtítols MacCaption (.mcc) s'incrusten com a subtítols tancats CEA-608/708 al vídeo MPEG-2, que requereix el contenidor MPG. El format de sortida s'ha canviat automàticament a .mpg.", + "remuxVideoMpgReencodeVideoX": "Els subtítols tancats en un fitxer .mpg necessiten vídeo MPEG-2, i el vídeo és {0}. Voleu tornar a codificar el vídeo a MPEG-2? Triga més i redueix una mica la qualitat.", + "remuxVideoFileHasNoAudioX": "«{0}» no té cap pista d'àudio, de manera que no es pot utilitzar com a font d'àudio.", + "remuxVideoAddingClosedCaptions": "S'estan afegint els subtítols tancats...", "remuxVideoSelectAudioTrack": "Selecciona la pista d'àudio", "remuxVideoSelectAudioTrackFor": "Selecciona la pista d'àudio per a {0}", + "remuxVideoSelectAudioTrackPrompt": "El fitxer «{0}» conté {1} pistes d'àudio. Seleccioneu la pista d'àudio que voleu remultiplexar:", "remuxVideoVideoFile": "vídeo", "remuxVideoAudioFile": "àudio", "remuxVideoAudioFiles": "Fitxers d'àudio:", "remuxVideoSubtitleFiles": "Fitxers de subtítols (opcionals, subtítols suaus):", + "remuxVideoSelectAudioTrackDotDotDot": "Selecciona la pista d'àudio...", "remuxVideoFilesX": "{0} fitxers", + "remuxVideoMixAudio": "Barreja l'àudio en una sola pista", + "remuxVideoVolumePercent": "Volum %:", + "remuxVideoVolumeX": "Volum {0}%", + "remuxVideoFastStart": "Optimitza per a la reproducció en línia (mou l'índex al principi; més lent)", "embedSubtitlesDotDotDot": "Afegeix/elimina subtítols incrustats...", "generateTimeCodes": "Genera codis de temps", "checkeredImage": "Imatge de quadres", @@ -2655,6 +3099,34 @@ "cutVideoSplitSegments": "Desa els segments per separat", "cutVideoAlsoCutSubtitle": "Retalla també el subtítol", "cutVideoSubtitleFileGeneratedX": "S'ha generat el fitxer de subtítols: {0}", + "cutVideoTransition": "Transició entre segments", + "cutVideoTransitionDurationSeconds": "Durada (segons)", + "cutVideoFadeInSeconds": "Fosa des del negre a l'inici (segons)", + "cutVideoFadeOutSeconds": "Fosa a negre al final (segons)", + "cutVideoPreviewTransition": "Previsualitza la transició", + "cutVideoPreviewTransitionTitle": "Vista prèvia de la transició", + "cutVideoPreviewNeedsJoin": "No hi ha cap unió per previsualitzar: afegiu almenys dos segments per fusionar, o un segment per retallar del mig.", + "cutVideoTransitionInfoX": "Cada transició se superposa a les parts que uneix, de manera que la sortida és {0} segons més curta per cada unió. Un subtítol retallat es retemporitza en conseqüència.", + "cutVideoTransitionFade": "Fosa", + "cutVideoTransitionFadeBlack": "Fosa a negre", + "cutVideoTransitionFadeWhite": "Fosa a blanc", + "cutVideoTransitionFadeGrays": "Fosa a gris", + "cutVideoTransitionDissolve": "Encadenat", + "cutVideoTransitionWipeLeft": "Cortina cap a l'esquerra", + "cutVideoTransitionWipeRight": "Cortina cap a la dreta", + "cutVideoTransitionWipeUp": "Cortina cap amunt", + "cutVideoTransitionWipeDown": "Cortina cap avall", + "cutVideoTransitionSlideLeft": "Lliscament cap a l'esquerra", + "cutVideoTransitionSlideRight": "Lliscament cap a la dreta", + "cutVideoTransitionSlideUp": "Lliscament cap amunt", + "cutVideoTransitionSlideDown": "Lliscament cap avall", + "cutVideoTransitionSmoothLeft": "Suau cap a l'esquerra", + "cutVideoTransitionSmoothRight": "Suau cap a la dreta", + "cutVideoTransitionCircleOpen": "Cercle que s'obre", + "cutVideoTransitionCircleClose": "Cercle que es tanca", + "cutVideoTransitionRadial": "Radial", + "cutVideoTransitionPixelize": "Pixelització", + "cutVideoTransitionBlur": "Desenfocament", "importCurrentSubtitle": "Importa el subtítol actual", "addRemoveEmbeddedSubtitlesTitle": "Afegeix/elimina subtítols incrustats", "addRemoveEmbeddedSubtitlesMp4Title": "Afegeix/elimina subtítols incrustats (MP4)", @@ -2670,6 +3142,7 @@ "embeddedTrackGeneratingVideoXY": "S'està generant el vídeo... {0}% {1}", "embeddedTrackGeneratingVideo": "S'està generant el vídeo...", "mp4FilesFilter": "Fitxers MP4", + "embeddedTracksPickVideoHint": "Trieu un fitxer de vídeo, o deixeu-lo anar aquí, per veure'n les pistes de subtítols.", "viewMatroskaTrackX": "Mostra la pista Matroska - {0}", "resolutionSeparator": "x", "openFromUrlTitle": "Obre un fitxer de vídeo des d'un URL", @@ -2716,12 +3189,15 @@ "reEncodeGeneratingDone": "Generació acabada", "reEncodeGeneratedFilesX": "Fitxers generats ({0}):", "reEncodeFfmpegParameters": "Paràmetres de ffmpeg", + "analyzingVideoX": "S'està analitzant el vídeo... {0}% {1}", + "analyzingVideoXofY": "S'està analitzant el vídeo {0}/{1}... {2}% {3}", + "backgroundImageFileNotSelected": "No s'ha seleccionat cap fitxer d'imatge de fons", + "backgroundImageFileDoesNotExistX": "El fitxer d'imatge de fons no existeix: {0}", "voices": "Veus", "presets": "Predefinits", "talkerX": "Parlant {0}", "baseModel": "Model base", "turboModel": "Model turbo", - "ttsUpdateX": "Actualitza {0}", "ttsEngineNotInstalled": "{0} no està instal·lat", "ttsEngineUpdateAvailable": "{0} - hi ha una actualització disponible", "ttsEngineTooOldUpdateRequired": "{0} és massa antic - cal actualitzar-lo", @@ -2744,9 +3220,17 @@ "cloneVoiceTo": "Clona la veu a", "cloneVoiceNameTitle": "Nom de la veu nova", "cloneVoiceExtractFailed": "No s'ha pogut extreure l'àudio de la línia seleccionada.", + "lineXHasNoDuration": "La línia {0} no té durada (el temps final no és posterior al temps d'inici), de manera que no hi ha àudio per extreure.", + "audioClipsSkippedLinesX": "No s'ha pogut extreure àudio per a aquestes línies, de manera que s'han omès: {0}", "toggleShotChange": "Commuta el canvi de pla", "resetWaveformZoomAndSpeed": "Restableix el zoom i la velocitat de la forma d'ona", "showOriginalSubtitle": "Mostra el subtítol original", + "timelineGroupTracksBy": "Agrupa les pistes de subtítols per", + "timelineShowThumbnails": "Mostra les miniatures del vídeo", + "showSpeechOnly": "Mostra només la veu (lent de generar)", + "isolatingSpeechForWaveformX": "S'està aïllant la veu per a la forma d'ona... {0}", + "isolatingSpeechForWaveformFailed": "No s'ha pogut aïllar la veu per a la forma d'ona; consulteu el registre de les eines", + "speechOnlyWaveformReady": "La forma d'ona ara només mostra la veu", "showOnlyWaveform": "Mostra només la forma d'ona", "showOnlySpectrogram": "Mostra només l'espectrograma", "showWaveformAndSpectrogram": "Mostra la forma d'ona i l'espectrograma", @@ -2779,6 +3263,7 @@ "adjustSelectedLinesAndForward": "Ajusta les línies seleccionades i les següents", "toFrameRate": "Als fotogrames per segon", "fromFrameRate": "Dels fotogrames per segon", + "videoXFrameRateY": "{0} - {1} fps", "adjustAllTimes": "Ajusta tots els temps (mostra abans/després)", "showEarlier": "Mostra abans", "showLater": "Mostra després", @@ -2788,7 +3273,8 @@ "syncPoints": "Punts de sincronització", "pointSync": "Sincronització per punts", "pointSyncViaOther": "Sincronització per punts amb un altre subtítol", - "syncPointCandidateInfo": "Les línies que comencen després de {0}+ segons de silenci es ressalten - sovint són bons punts de sincronització", + "syncPointCandidateAfterInfo": "Es ressalten les línies seguides de {0} segons o més de silenci: sovint són punts de sincronització fiables", + "gapAfter": "Interval després", "adjustAllShortcuts": "Dreceres de teclat:\n\n• Maj + Esquerra/Dreta: mou 10 ms\n• Ctrl + Esquerra/Dreta: mou 100 ms\n• Alt + Esquerra/Dreta: mou 500 ms", "adjustAllShortcutsFrames": "Dreceres de teclat:\n\n• Maj + Esquerra/Dreta: mou 1 fotograma\n• Ctrl + Esquerra/Dreta: mou 10 fotogrames\n• Alt + Esquerra/Dreta: mou 1 segon", "offsetInSeconds": "Desplaçament en segons", @@ -2808,6 +3294,7 @@ "lineMerge": "Fusió de línies", "delayInSecondsBetweenRequests": "Retard en segons entre peticions", "maxBytesPerRequest": "Màxim de bytes per petició", + "keepMusicLinesUntranslated": "No tradueixis les línies entre símbols musicals (♪ ... ♪)", "promptText": "Text de la indicació", "resetPromptToDefault": "Restableix la indicació al valor per defecte", "translateEachLineSeparately": "Tradueix cada línia per separat", @@ -2821,11 +3308,10 @@ "readyToTranslate": "A punt per traduir", "translating": "S'està traduint...", "translationComplete": "Traducció completada", + "translateInPlaceNoOriginal": "Tradueix in situ (no creïs l'original)", "translationCancelled": "Traducció cancel·lada", "swapLanguages": "Intercanvia les llengües d'origen i de destí", "xIsAlreadyDownloadedReDownload": "{0} ja s'ha baixat. Voleu tornar-lo a baixar?", - "advancedDotDotDot": "Avançat...", - "advancedSettings": "Configuració avançada", "advancedSettingsSubtitle": "Opcions de lots, context i mostreig per al motor avançat llama.cpp", "context": "Context", "synopsis": "Sinopsi", @@ -2849,6 +3335,7 @@ "useOnlyExtraServerParametersHint": "Inicia llama-server amb els paràmetres de dalt en lloc de l'ajust propi del Subtitle Edit - només s'estableixen el model, l'amfitrió i el port", "serverRunningAtX": "El servidor s'executa a {0}", "customPromptHint": "Instruccions personalitzades ({0} = llengua d'origen, {1} = llengua de destí); buit = indicació integrada", + "editTranslationHint": "Feu clic a la traducció de la fila seleccionada per editar-la (Enter desa, Esc cancel·la)", "llamaCppDownloadEngineAndModelPrompt": "El llama.cpp requereix baixar el motor llama-server i un model de traducció. Voleu baixar-los ara?", "llamaCppDownloadEnginePrompt": "El llama.cpp requereix baixar el motor llama-server. Voleu baixar-lo ara?", "llamaCppDownloadModelPrompt": "El llama.cpp requereix baixar el model de traducció seleccionat. Voleu baixar-lo ara?" @@ -2919,6 +3406,7 @@ "showGridLines": "Mostra les línies de la graella", "showHorizontalLineAboveToolbar": "Mostra una línia horitzontal sobre la barra d'eines", "bookmarkColor": "Color dels marcadors", + "spellCheckHighlightColor": "Color de ressaltat de la verificació ortogràfica", "singleLineMaxLength": "Llargària màxima d'una línia", "optimalCharsPerSec": "Caràcters/s òptims", "maxCharsPerSec": "Màxim de caràcters/s", @@ -2943,13 +3431,21 @@ "useDoNotBreakAfterList": "Utilitza la llista de no-saltar-després", "newEmptyDefaultMs": "Durada per defecte dels subtítols nous (ms)", "timeCodeUpDownStepMs": "Increment de temps amunt/avall (ms)", + "moveSelectedLinesStepMs": "Pas de la drecera per moure les línies seleccionades (ms)", + "moveStartEndStepMs": "Pas de la drecera per moure l'inici/final (ms)", + "moveLinesShortenNeighbor": "Mou les línies: escurça la línia anterior/següent en lloc de superposar-s'hi", "promptBeforeDelete": "Demana confirmació abans de suprimir", "rememberPositionAndSize": "Recorda la posició i la mida de la finestra", + "titleBarFullFileName": "Mostra el camí complet del fitxer a la barra de títol", + "showRecentFiles": "Mostra els fitxers recents (per tornar-los a obrir)", "openLastFileOnStart": "Obre l'últim fitxer recent en iniciar", "autoSave": "Desat automàtic (desa el fitxer obert mentre s'edita)", "autoBackupOn": "Còpia de seguretat automàtica", "autoBackupIntervalMinutes": "Interval de la còpia de seguretat automàtica (minuts)", "autoBackupDeleteAfterDays": "Retenció de les còpies de seguretat automàtiques (dies)", + "settingsBackupOn": "Còpia de seguretat automàtica de la configuració", + "settingsBackupIntervalDays": "Interval de còpia de seguretat de la configuració (dies, 0 = a cada inici)", + "settingsBackupMaxCount": "Còpies de seguretat de la configuració que es conserven", "autoConvertToUtf8": "Converteix automàticament la codificació a UTF-8 en obrir", "autoTrimWhiteSpace": "Retalla automàticament els espais en blanc", "removeBlankLinesWhenOpening": "Elimina les línies en blanc en obrir un subtítol", @@ -2966,7 +3462,10 @@ "colorGapTooShort": "Acoloreix si l'interval és massa curt", "errorBackgroundColor": "Color de fons dels errors", "waveformDrawGridLines": "Dibuixa les línies de la graella", + "waveformUseSkiaRenderer": "Utilitza el renderitzador ràpid experimental", "waveformCenterVideoPositionAlsoWhenPaused": "Centra la posició del vídeo també en pausa", + "waveformSelectCurrentSubtitleWhilePaused": "Selecciona el subtítol actual també mentre està en pausa", + "waveformRememberZoom": "Recorda el zoom (horitzontal i vertical)", "waveformShowToolbar": "Mostra la barra d'eines", "waveformShowToolbarEdit": "Edita la barra d'eines...", "waveformShowToolbarEditLabel": "Elements de la barra d'eines", @@ -3014,18 +3513,25 @@ "webVttUseXTimestampMap": "WebVTT: utilitza X-TIMESTAMP-MAP (desplaça els codis de temps en carregar)", "assaAutoSetResolution": "ASSA: estableix la resolució (PlayResX/PlayResY) a partir del vídeo quan se n'obre un", "assaAutoSetResolutionPrompt": "ASSA: pregunta abans de canviar la resolució perquè coincideixi amb el vídeo (si no, remostreja automàticament)", - "showStopButton": "Mostra el botó d'aturada", - "showFullscreenButton": "Mostra el botó de pantalla completa", + "videoControls": "Controls de vídeo", + "videoControlsEdit": "Edita els controls de vídeo...", + "videoControlsPositionText": "Text de posició / durada", + "videoControlsVideoFileName": "Nom del fitxer de vídeo", + "videoControlsPlayerName": "Nom del reproductor de vídeo", "fullscreenHideControls": "Amaga els controls de vídeo a pantalla completa", + "showSecondarySubtitleDialog": "Mostra el diàleg d'estil en obrir un segon fitxer de subtítols", + "rememberSecondarySubtitleFile": "Recorda el segon fitxer de subtítols", "autoOpenVideoFile": "Obre automàticament el fitxer de vídeo en obrir el subtítol", "downloadMpv": "Baixa l'mpv", "downloadVlc": "Baixa el VLC", "allowSingleLetterShortcutsInTextbox": "Permet dreceres d'una sola lletra al quadre de text", + "allowTextNavigationShortcutsInTextbox": "Permet dreceres a les tecles de navegació de text (Ctrl+Left/Right, Home/End) al quadre de text", "spellCheckEnglishTreatInApostropheAsIng": "Verificació ortogràfica: tracta les paraules acabades en 'in'' com a 'ing' (només anglès)", "goToLineNumberSetsVideoPosition": "Ves al número de línia també estableix la posició del vídeo", "adjustAllTimesRememberLineSelectionChoice": "Ajusta tots els temps, recorda la tria de selecció de línies", "mergeKeepEndTime": "Fusiona les línies: mantén el temps final (permet superposar-se al subtítol següent)", "mergeKeepEndTimeOnlyAssa": "Fusiona les línies: mantén el temps final només per a «Advanced Sub Station Alpha»", + "mergeAutoBreak": "Fusiona les línies: salt automàtic del text fusionat (desactivat manté els salts de línia propis de les línies)", "filesAndLogs": "Fitxers i registres", "showErrorLogFile": "Mostra el fitxer de registre d'errors", "showToolsLogFile": "Mostra el fitxer de registre de les eines", @@ -3034,6 +3540,7 @@ "showAssaLayer": "Mostra el quadre de capa ASSA", "waveformCursorColor": "Color del cursor/capçal de la forma d'ona", "waveformShotChangeColor": "Color dels canvis de pla de la forma d'ona", + "waveformGridColor": "Color de la graella de la forma d'ona", "waveformParagraphLeftColor": "Color de la vora esquerra de la forma d'ona", "waveformParagraphRightColor": "Color de la vora dreta de la forma d'ona", "waveformFancyHighColor": "Color alt elegant de la forma d'ona", @@ -3061,10 +3568,15 @@ "resetSyntaxColoring": "Restableix la coloració de la sintaxi", "resetWaveform": "Restableix la forma d'ona", "resetRules": "Restableix les regles", - "useFrameMode": "Utilitza el mode de fotogrames (hh.mm.ss.ff)", + "timeCodeMode": "Mode de codi de temps", + "timeCodeModeTime": "Temps (hh:mm:ss,zzz)", + "timeCodeModeFrames": "Fotogrames (hh:mm:ss:ff)", + "timeCodeModeFrameNumbers": "Números de fotograma", + "frameModeSnapToVideoFrames": "Desa els temps en fotogrames del vídeo (no en fotogrames dins de cada segon)", "textBoxLimitNewLines": "Limita el nombre de línies al quadre de text del subtítol", "mpvOpenGl": "libmpv - OpenGL", "mpvSoftwareRendering": "libmpv - Renderització per programari (lenta)", + "mpvMetalRendering": "libmpv - Metal (experimental, cal reiniciar)", "mpvWidRendering": "libmpv - Renderització amb ID de finestra natiu", "waveFormsAndSpectrogramFoldersContainsX": "Les carpetes «Waveforms» i «spectrogram» contenen {0}", "deleteWaveformAndSpectrogramFoldersQuestion": "Voleu suprimir els fitxers de «Waveforms» i «Spectrogram»?", @@ -3077,8 +3589,10 @@ "waveformExtractAudioSampleRate": "Freqüència de mostreig d'extracció de l'àudio", "waveformExtractAudioSampleRateOriginal": "Original (mantén l'origen)", "waveformExtractAudioBitRate": "Taxa de bits d'extracció de l'àudio (MP3/M4A)", + "ffmpegUseCenterChannelOnly": "Utilitza només el canal central frontal per a l'àudio 5.1 (FFmpeg)", "vlcWidRendering": "libVLC - Renderització amb ID de finestra natiu", "ffmpegSoftwareRendering": "FFmpeg", + "downloadFfmpegLibs": "Baixa les biblioteques FFmpeg (per al reproductor de vídeo FFmpeg)", "subtitleGridEnterKeyAction": "Acció de la tecla Retorn a la graella de subtítols", "subtitleSingleClickAction": "Acció del clic simple a la graella de subtítols", "subtitleDoubleClickAction": "Acció del doble clic a la graella de subtítols", @@ -3106,6 +3620,7 @@ "subtitleGridFormattingShowFormatting": "Mostra el format", "subtitleGridFormattingShowTags": "Mostra les etiquetes", "subtitleGridFormattingHideTags": "Amaga les etiquetes", + "subtitleGridFormattingShowFormattingKeepTags": "Mostra el format, mantén les etiquetes no visuals", "waveformParagraphBackgroundColor": "Color de fons dels subtítols a la forma d'ona", "waveformParagraphSelectedBackgroundColor": "Color de fons del subtítol seleccionat a la forma d'ona", "waveformAllowOverlap": "Permet la superposició (en moure/redimensionar)", @@ -3121,9 +3636,12 @@ "splitOddLinesAction": "Acció en dividir línies senars", "ocrUseWordSplitList": "OCR: utilitza la llista de divisió de paraules", "ocrGuessUnknownWords": "OCR: intenta endevinar les paraules desconegudes", + "spellCheckRememberUseAlwaysList": "Verificació ortogràfica: recorda la llista «Utilitza sempre»", + "fixShortDisplayTimesAllowMoveStartTime": "Corregeix els errors comuns: corregir els temps de visualització curts pot moure el temps d'inici", "speechToTextSelectedLinesPromptFirstTimeOnly": "Veu a text: línies seleccionades, demana la llengua/el motor només el primer cop", "multipleReplaceShowDotDotDotButtons": "Substitució múltiple: mostra els botons del menú contextual", "gridFocusTextboxAfterInsertNew": "Graella: enfoca el quadre de text després d'inserir un subtítol nou", + "undoRedoGoToChangedLine": "Desfés/refés: ves a la línia canviada (i a la posició del vídeo)", "musicSymbol": "Símbol musical", "musicSymbolsToReplace": "Símbols musicals a substituir (separats per comes)", "textToSpeechPromptMergeContinuationLines": "Text a veu: demana si cal fusionar les línies de continuació", @@ -3132,6 +3650,7 @@ "useFocusedButtonBackgroundColor": "Utilitza un color de fons per al botó enfocat", "focusedButtonBackgroundColor": "Color de fons del botó enfocat", "forceCrLfOnSave": "Força CR+LF en desar (fitxers de subtítols de text)", + "linuxClipboardUseExternalTool": "Copia el text amb xclip/wl-copy (conserva els caràcters accentuats)", "showFormatLimitWarning": "Avisa en desar quan les línies superin els límits del format (p. ex. SCC 32 car./línia)", "textBoxButtonShowAutoBreak": "Quadre de text: mostra el botó de salt automàtic", "textBoxButtonShowUnbreak": "Quadre de text: mostra el botó de desfés el salt", @@ -3143,12 +3662,18 @@ "waveformDoubleClickAction": "Acció del doble clic a la forma d'ona (després de l'acció del clic simple)", "allSettings": "Tota la configuració", "uiScale": "Escala de la interfície (%)", + "fontScale": "Escala de la lletra (%)", + "textSelectionOpacity": "Opacitat de la selecció de text (%)", "waveformToolbarItems": "Elements de la barra d'eines de la forma d'ona", "matchIconColorToDarkTheme": "Fes coincidir el color de les icones amb el color de primer pla del tema fosc", "subtitlePreviewProperties": "Propietats de la vista prèvia del subtítol", "usePositionFromSubtitleFile": "Utilitza la posició del fitxer de subtítols (TTML/PAC/EBU STL)", "marginIsPartOfSubtitleArea": "El marge forma part de l'àrea del subtítol", "textJustify": "Justifica les línies", + "deinterlace": "Desentrellaça el vídeo", + "deinterlaceOff": "Desactivat", + "deinterlaceAuto": "Automàtic (només fotogrames entrellaçats)", + "deinterlaceAlways": "Sempre", "pixelWidthInfo": "Línies verdes = límit d'amplada màxima | Àrea vermella = el text supera el límit", "spellCheckEngineHunSpelll": "Hunspell", "spellCheckEngineMsWord": "MS Word", @@ -3157,7 +3682,16 @@ "waveformColorTheme": "Tema de color:", "waveformSaveAsCustomTheme": "Desa com a tema personalitzat...", "waveformLoadThemeDotDotDot": "Carrega un tema...", - "waveformExportThemeDotDotDot": "Exporta el tema..." + "waveformExportThemeDotDotDot": "Exporta el tema...", + "waveformSaveCustomThemeTitle": "Desa el tema personalitzat", + "waveformThemeFileType": "Tema de forma d'ona del Subtitle Edit", + "waveformThemeHighContrast": "Contrast alt", + "waveformThemeOceanBlue": "Blau oceà", + "waveformThemeWarmSunset": "Posta de sol càlida", + "waveformThemeForest": "Bosc", + "waveformThemeMidnightPurple": "Porpra de mitjanit", + "waveformThemeRetroAmber": "Ambre retro", + "waveformThemeIce": "Gel" }, "shortcuts": { "searchShortcuts": "Cerca dreceres...", @@ -3168,11 +3702,34 @@ "categoryVideo": "Vídeo", "categorySync": "Sincronització", "categoryAi": "IA", + "categoryCustom": "Personalitzat", + "editCustomShortcut": "Edita la drecera personalitzada", + "customShortcutTextBoxKeyHint": "Utilitzeu aquí una tecla amb Ctrl/Alt: una tecla sola ja no escriuria al quadre de text.", + "customShortcutNumberX": "Drecera personalitzada #{0}", + "customShortcutNumberXY": "Drecera personalitzada #{0}: {1}", + "customShortcutStep": "Pas", + "customShortcutSteps": "Passos", + "customShortcutStepRunCommand": "Executa una ordre", + "customShortcutStepInsertText": "Insereix text", + "customShortcutStepReplace": "Cerca i substitueix", + "customShortcutInsertAtCursor": "Al cursor", + "customShortcutInsertAtStart": "A l'inici del text", + "customShortcutInsertAtEnd": "Al final del text", + "customShortcutSummaryRunCommandX": "Executa: {0}", + "customShortcutSummaryInsertXY": "Insereix «{0}» ({1})", + "customShortcutSummaryReplaceXY": "Substitueix «{0}» per «{1}»", + "customShortcutSummaryReplaceRegexXY": "Substitueix l'expressió regular «{0}» per «{1}»", + "customShortcutPickCommand": "Seleccioneu una ordre per executar.", + "customShortcutEnterText": "Introduïu el text que voleu inserir.", + "customShortcutEnterFind": "Introduïu el text que voleu cercar.", + "customShortcutInvalidRegexX": "Expressió regular no vàlida: {0}", + "customShortcutStepsHint": "Els passos s'executen de dalt a baix. Els passos de text funcionen sobre les línies seleccionades; les ordres que obren una finestra continuen quan es tanca.", "activeIn": "Actiu a", "activeInEverywhere": "Tot arreu", "generalMergeSelectedLinesAndUnbreak": "Fusiona les línies seleccionades i desfés el salt", "generalMergeSelectedLinesAndUnbreakCjk": "Fusiona les línies seleccionades i desfés el salt sense espai (CJK)", "generalUnbreakNoSpaceCjk": "Desfés el salt sense espai (CJK)", + "generalToggleBreak": "Commuta el salt de línia (afegeix/desfés)", "generalMergeSelectedLinesBilingual": "Fusiona les línies seleccionades de manera bilingüe", "generalToggleTranslationMode": "Commuta el mode de traducció", "generalToggleTranslationAndOriginalInPreviews": "Commuta la traducció i l'original a la vista prèvia de vídeo/àudio", @@ -3189,10 +3746,12 @@ "generalToggleItalic": "Commuta la cursiva", "generalToggleBold": "Commuta la negreta", "generalToggleUnderline": "Commuta el subratllat", + "generalToggleBox": "Commuta la caixa (EBU STL)", "fileOpen": "Obre", "fileOpenKeepVideo": "Obre (mantén el vídeo)", "fileSave": "Desa", "fileSaveAs": "Anomena i desa", + "fileSaveOriginal": "Desa l'original", "fileCompare": "Compara", "fileStatistics": "Estadístiques", "fileImportPlainText": "Importa text pla", @@ -3204,6 +3763,7 @@ "fillSelectedLinesWithClipboard": "Omple les línies seleccionades amb el text del porta-retalls", "listInverseSelection": "Inverteix la selecció", "listDeleteSelection": "Suprimeix la selecció", + "deleteSelectionEverywhere": "Suprimeix la selecció (a tot arreu)", "rippleDeleteSelection": "Suprimeix la selecció amb desplaçament", "assigned": "Assignada", "unassigned": "Sense assignar", @@ -3212,6 +3772,7 @@ "detectKey": "Detecta la tecla", "control": "Control", "alt": "Alt", + "altGr": "AltGr", "win": "Win", "shift": "Maj", "controlMac": "⌃", @@ -3222,6 +3783,11 @@ "resetShortcutsDetail": "Voleu restablir totes les dreceres als valors per defecte?", "togglePlayPause": "Commuta reprodueix/pausa", "toggleLockTimeCodes": "Commuta el bloqueig dels codis de temps", + "toggleTimeCodeMode": "Commuta el mode de codi de temps (temps/fotogrames/números de fotograma)", + "toggleScreenPrivacy": "Commuta la privadesa de pantalla (amaga els noms de fitxer i després també els textos)", + "screenPrivacyFileNamesHidden": "Privadesa de pantalla: noms de fitxer amagats", + "screenPrivacyFileNamesAndTextsHidden": "Privadesa de pantalla: noms de fitxer i textos amagats", + "screenPrivacyOff": "Privadesa de pantalla: desactivada", "duplicateSelectedLines": "Duplica les línies seleccionades", "sourceView": "Vista de codi", "addOrEditBookmark": "Afegeix o edita un marcador", @@ -3240,6 +3806,7 @@ "moveFirstWordFromNextLineUpCurrentSubtitle": "Mou la primera paraula de la línia següent amunt (subtítol actual)", "moveTextFromCursorToNextAndGoToNext": "Mou el text posterior al cursor al subtítol següent i ves-hi", "moveTextFromCursorToNextAndGoToNextAndPlay": "Mou el text posterior al cursor al subtítol següent, ves-hi i reprodueix", + "moveTextFromCursorToNextAndGoToNextAndPlayAndPause": "Mou el text després del cursor al subtítol següent, ves al següent, reprodueix-lo i fes pausa al final", "breakAtFirstSpaceFromCursor": "Salta al primer espai des de la posició del cursor", "breakAtFirstSpaceFromCursorAndGoToNext": "Salta al primer espai des de la posició del cursor i ves al següent", "toggleFocusGridAndWaveform": "Commuta el focus entre la graella de subtítols i la forma d'ona/l'espectrograma", @@ -3269,6 +3836,7 @@ "listViewColumnTextUp": "Columna, text amunt", "listViewColumnTextDown": "Columna, text avall", "autoTranslateSelectedLines": "Tradueix automàticament les línies seleccionades...", + "autoTranslateSelectedLinesNoPrompt": "Tradueix automàticament les línies seleccionades (sense preguntar, utilitza l'últim motor/llengües)", "setAssaResolution": "Estableix la resolució ASSA (PlayResX/PlayResY)", "setShortcutForX": "Estableix la drecera per a \"{0}\"", "commandFileNewKeepVideo": "Nou (mantén el vídeo)", @@ -3323,6 +3891,7 @@ "textBoxItalic": "Cursiva al quadre de text", "textBoxBold": "Negreta al quadre de text", "textBoxUnderline": "Subratllat al quadre de text", + "textBoxBox": "Quadre de text: caixa (EBU STL)", "resetWaveformZoomAndSpeed": "Restableix el zoom de la forma d'ona i la velocitat de reproducció", "togglePlaybackSpeed": "Commuta la velocitat de reproducció", "playbackSpeedSlower": "Velocitat de reproducció més lenta", @@ -3334,6 +3903,7 @@ "seekSilenceForward": "Cerca silenci endavant", "waveformGuessStart": "Endevina el temps d'inici a partir de la forma d'ona", "waveformGuessEnd": "Endevina el temps final a partir de la forma d'ona", + "waveformGuessStartAndEnd": "Endevina el temps d'inici i final a partir de la forma d'ona", "setVideoPositionCurrentSubtitleStart": "Estableix la posició del vídeo a l'inici de la línia actual", "goToSubtitlePositionAndPause": "Ves a la posició del subtítol i pausa", "setVideoPositionCurrentSubtitleEnd": "Estableix la posició del vídeo al final de la línia actual", @@ -3347,6 +3917,14 @@ "surroundWith": "Envolta amb...", "surroundWithXY": "Envolta amb {0}/{1}", "surroundWithNumberX": "Envolta amb #{0}", + "surroundWithBehavior": "Comportament", + "surroundWithBehaviorToggle": "Commuta (afegeix, o elimina si hi és)", + "surroundWithBehaviorAdd": "Afegeix (cada vegada)", + "surroundWithBehaviorRemove": "Elimina (tots)", + "surroundWithBehaviorRemoveOnce": "Elimina (un cada vegada)", + "surroundWithWorksOn": "Funciona sobre", + "surroundWithScopeSelectionOrText": "La selecció, si no tot el text", + "surroundWithScopeEachLine": "Cada línia", "searchVia": "Cerca amb", "searchViaX": "Cerca amb {0}", "searchViaNumberX": "Cerca amb #{0}", @@ -3374,6 +3952,11 @@ "copySubtitlePathToClipboard": "Copia el camí del subtítol al porta-retalls", "copySubtitleOriginalPathToClipboard": "Copia el camí del subtítol original al porta-retalls", "focusTextBox": "Enfoca el quadre de text", + "focusSubtitleListView": "Enfoca la llista de subtítols", + "focusWaveform": "Enfoca la forma d'ona", + "focusOriginalTextBox": "Enfoca el quadre de text original", + "textBoxGoToStart": "Quadre de text, ves a l'inici", + "textBoxGoToEnd": "Quadre de text, ves al final", "sortByNumber": "Ordena per número", "sortByStartTime": "Ordena pel temps de «Mostra»", "sortByEndTime": "Ordena pel temps d'«Amaga»", @@ -3411,9 +3994,9 @@ "selectionToSentenceCase": "Quadre de text: selecció a Majúscula inicial", "selectionToggleCasing": "Quadre de text: commuta les majúscules i minúscules (MAJÚSCULES -> minúscules -> Majúscula Inicial)", "googleIt": "Cerca-ho a Google (text seleccionat)", - "setActorXY": "Estableix l'actor {0}: {1}", + "setActorX": "Estableix l'actor: {0}", "setNewActor": "Estableix un actor nou...", - "setActorSlotXTitle": "Ranura d'actor {0}", + "setStyleX": "Estableix l'estil: {0}", "toggleDialogDashes": "Commuta els guions de diàleg", "mergeWithLineBeforeAsDialog": "Fusiona amb la línia anterior com a diàleg", "mergeWithLineAfterAsDialog": "Fusiona amb la línia següent com a diàleg", @@ -3424,7 +4007,23 @@ "moveStartOneFrameBackKeepGapPrev": "Mou l'inici un fotograma enrere (mantén l'interval amb l'anterior si és a prop)", "moveStartOneFrameForwardKeepGapPrev": "Mou l'inici un fotograma endavant (mantén l'interval amb l'anterior si és a prop)", "moveEndOneFrameBackKeepGapNext": "Mou el final un fotograma enrere (mantén l'interval amb el següent si és a prop)", - "moveEndOneFrameForwardKeepGapNext": "Mou el final un fotograma endavant (mantén l'interval amb el següent si és a prop)" + "moveEndOneFrameForwardKeepGapNext": "Mou el final un fotograma endavant (mantén l'interval amb el següent si és a prop)", + "moveStartXMsBack": "Mou l'inici X ms enrere (X es defineix a la configuració, també a la sincronització visual)", + "moveStartXMsForward": "Mou l'inici X ms endavant (X es defineix a la configuració, també a la sincronització visual)", + "moveEndXMsBack": "Mou el final X ms enrere (X es defineix a la configuració, també a la sincronització visual)", + "moveEndXMsForward": "Mou el final X ms endavant (X es defineix a la configuració, també a la sincronització visual)", + "moveSelectedLinesXMsBack": "Mou les línies seleccionades X ms enrere (X es defineix a la configuració)", + "moveSelectedLinesXMsForward": "Mou les línies seleccionades X ms endavant (X es defineix a la configuració)", + "moveSelectedLinesAndForwardXMsBack": "Mou les línies seleccionades i totes les següents X ms enrere (X es defineix a la configuració)", + "moveSelectedLinesAndForwardXMsForward": "Mou les línies seleccionades i totes les següents X ms endavant (X es defineix a la configuració)", + "moveAllLinesXMsBack": "Mou totes les línies X ms enrere (X es defineix a la configuració)", + "moveAllLinesXMsForward": "Mou totes les línies X ms endavant (X es defineix a la configuració)", + "moveSelectedLinesCustomMsBack": "Mou les línies seleccionades, mil·lisegons personalitzats ({0:#,###,##0}) enrere, {1}", + "moveSelectedLinesCustomMsForward": "Mou les línies seleccionades, mil·lisegons personalitzats ({0:#,###,##0}) endavant, {1}", + "moveSelectedLinesAndForwardCustomMsBack": "Mou les línies seleccionades i totes les següents, mil·lisegons personalitzats ({0:#,###,##0}) enrere, {1}", + "moveSelectedLinesAndForwardCustomMsForward": "Mou les línies seleccionades i totes les següents, mil·lisegons personalitzats ({0:#,###,##0}) endavant, {1}", + "moveAllLinesCustomMsBack": "Mou totes les línies, mil·lisegons personalitzats ({0:#,###,##0}) enrere, {1}", + "moveAllLinesCustomMsForward": "Mou totes les línies, mil·lisegons personalitzats ({0:#,###,##0}) endavant, {1}" }, "wordLists": { "title": "Llistes de paraules", @@ -3470,6 +4069,9 @@ "applyToAllLinesX": "Totes les línies ({0})", "updateAllXAvailable": "Actualitza-ho tot ({0})", "updatingXOfY": "S'està actualitzant {0}/{1}...", + "doNotAskAgain": "No ho tornis a preguntar (es pot canviar a Gestiona els connectors)", + "applyPluginsTo": "Aplica els connectors a", + "askEachTime": "Pregunta cada vegada", "allLines": "Totes les línies" }, "help": { @@ -3514,6 +4116,8 @@ "nOcrDatabase": "Base de dades nOCR", "drawMode": "Mode de dibuix:", "addNewCharcter": "Afegeix un caràcter nou", + "addNOcrCharacterTitle": "Afegeix un caràcter nOCR per a la línia {0}, caràcter {1} de {2}, amb la base de dades «{3}»", + "addBinaryOcrCharacterTitle": "Afegeix un caràcter d'OCR binari per a la línia {0}, caràcter {1} de {2}, amb la base de dades «{3}»", "lineIndexX": "Línia {0}", "inspectNOcrAdditions": "Inspecciona les addicions noves d'nOCR", "ocrSelectedLines": "OCR de les línies seleccionades", @@ -3562,6 +4166,8 @@ "binaryImageCompareInspectImageMatches": "«Comparació d'imatges binàries» - Inspecciona les coincidències d'imatge", "saveBlankTextTitle": "Voleu desar el text en blanc?", "saveBlankTextPrompt": "Voleu desar el text en blanc per a la imatge?", + "drawnLinesDoNotMatchTitle": "Les línies no coincideixen amb la imatge", + "drawnLinesDoNotMatchPrompt": "Algunes de les línies dibuixades no coincideixen amb la imatge (les línies verdes han d'estar sobre el caràcter i les vermelles fora), de manera que aquest caràcter no es reconeixerà.\n\nVoleu desar igualment?", "yesAndNeverAskAgain": "Sí i no ho tornis a preguntar", "importTextFromSubtitleDotDotDot": "Importa text d'un subtítol...", "importTextFromSubtitleOverwritePrompt": "Algunes línies ja tenen text d'OCR. Voleu sobreescriure el text existent?", @@ -3620,9 +4226,26 @@ "crispEmbedSettingsTitle": "Configuració de CrispEmbed", "crispEmbedDescription": "Motor d'OCR local amb diversos motors de model - baixeu aquí el motor i els models.", "trainNOcrDatabase": "Entrena la base de dades nOCR...", + "optimizeNOcrDatabase": "Optimitza la base de dades nOCR...", + "optimizeNOcrDatabaseQuestionXY": "Voleu eliminar les línies que no distingeixen els caràcters d'altres de similars a la base de dades nOCR «{0}»?\n\nAixò fa la base de dades més petita i l'OCR més ràpid, normalment amb menys errors. Triga fins a un minut.\n\nEs desa una còpia de seguretat com a «{1}».", + "optimizingNOcrDatabaseXY": "S'està optimitzant la base de dades nOCR... {0}/{1}", + "nOcrDatabaseOptimizedXYZ": "S'ha optimitzat la base de dades nOCR «{0}»: {1:#,##0} -> {2:#,##0} línies.", "startTraining": "Inicia l'entrenament", "abortTraining": "Interromp l'entrenament", "trainingOptions": "Opcions d'entrenament", + "trainNOcrSubtitle": "Renderitza caràcters amb les lletres instal·lades i n'ensenya les formes a nOCR. Trieu les lletres que fan servir els vostres subtítols.", + "xFontsSelected": "{0:#,##0} seleccionades", + "subtitleFonts": "Lletres dels subtítols", + "emptyDatabase": "Base de dades buida", + "startFromDatabase": "Comença des de", + "startFromDatabaseHint": "Afegeix els caràcters entrenats a una còpia d'una base de dades existent (recomanat) o comença des d'una de buida", + "fontSizes": "Mides de lletra", + "fontSizesHint": "Mides de lletra en píxels per renderitzar, separades per comes, p. ex. «30, 40»", + "numberOfLineSegmentsHint": "Línies comprovades per caràcter: més és més estricte però més lent", + "enterDatabaseName": "Introduïu un nom per a la base de dades", + "trainingFontXSizeYCharacterZ": "S'està entrenant «{0}» a {1} px: {2}", + "xLearned": "{0:#,##0} apresos", + "xSkipped": "{0:#,##0} omesos", "charactersToTrain": "Caràcters a entrenar", "letterCombinationsToTrain": "Combinacions de lletres que es podrien dividir en una sola imatge", "importCharactersFromSubtitleFile": "Importa caràcters d'un fitxer de subtítols...", @@ -3649,6 +4272,8 @@ "drawZoomOut": "Redueix (Ctrl+-)", "drawResetView": "Restableix la vista (Ctrl+0)", "drawToggleGrid": "Commuta la graella (Ctrl+G)", + "drawTogglePreview": "Commuta la vista prèvia (F9)", + "drawPreviewFailed": "Ha fallat la vista prèvia: cal ffmpeg amb libass (filtre subtitles)", "drawCopyToClipboard": "Copia al porta-retalls (Ctrl+C)", "drawShapes": "Formes", "drawSelectedPoint": "Punt seleccionat", @@ -3656,6 +4281,81 @@ "drawSelectedLayer": "Capa seleccionada", "drawToolX": "Eina: {0}", "drawHelpText": "Feu clic per afegir punts • Retorn/F8 per tancar la forma • Maj+Arrossega per desplaçar • Ctrl+Roda per fer zoom", + "drawMoveToLayer": "Mou a la capa", + "drawFlipHorizontal": "Inverteix horitzontalment", + "drawFlipVertical": "Inverteix verticalment", + "drawHideShape": "Amaga la forma", + "drawShowShape": "Mostra la forma", + "drawHideLayer": "Amaga la capa", + "drawShowLayer": "Mostra la capa", + "drawDeleteLayer": "Suprimeix la capa", + "drawDeletePoint": "Suprimeix el punt", + "drawCancelDrawing": "Cancel·la el dibuix", + "drawLayerX": "Capa {0}", + "drawImportSvg": "Importa una imatge SVG...", + "drawRotateClockwise": "Gira 90° en sentit horari", + "drawConvertToCurve": "Converteix el segment en corba", + "drawBackground": "Fons", + "drawPosition": "Posició", + "drawZoom": "Zoom", + "drawShapeTool": "Eina de formes", + "drawColorPickerTool": "Agafa el color d'una forma o del fons", + "drawShapeLibrary": "Biblioteca de formes", + "drawShapeLibraryHint": "Arrossegueu sobre el llenç per col·locar una forma, o feu clic per a la mida per defecte. Shift en manté les proporcions.", + "drawAddToLibrary": "Afegeix a la biblioteca de formes...", + "drawRemoveFromLibrary": "Elimina de la biblioteca de formes", + "drawShapeName": "Nom de la forma", + "drawCategorySpeechBubbles": "Bafarades", + "drawCategoryArrows": "Fletxes", + "drawCategoryBasicShapes": "Formes bàsiques", + "drawCategorySymbols": "Símbols", + "drawCategoryMyShapes": "Les meves formes", + "drawShapeSpeechBubble": "Bafarada", + "drawShapeSpeechBubbleRight": "Bafarada, cua a la dreta", + "drawShapeSpeechBubbleUp": "Bafarada, cua amunt", + "drawShapeOvalBubble": "Bafarada ovalada", + "drawShapeBoxBubble": "Bafarada rectangular", + "drawShapeThoughtBubble": "Bafarada de pensament", + "drawShapeShoutBubble": "Bafarada de crit", + "drawShapeCaptionBox": "Requadre de text", + "drawShapeArrow": "Fletxa", + "drawShapeDoubleArrow": "Fletxa doble", + "drawShapeChevron": "Galó", + "drawShapeCurvedArrow": "Fletxa corba", + "drawShapeRoundedRectangle": "Rectangle arrodonit", + "drawShapeTriangle": "Triangle", + "drawShapeDiamond": "Rombe", + "drawShapePentagon": "Pentàgon", + "drawShapeHexagon": "Hexàgon", + "drawShapeOctagon": "Octàgon", + "drawShapeStar": "Estrella", + "drawShapeRing": "Anell", + "drawShapePlus": "Més", + "drawShapeBanner": "Bàner", + "drawShapeHeart": "Cor", + "drawShapeCheck": "Marca de verificació", + "drawShapeMusicNote": "Nota musical", + "drawShapeMusicNotes": "Notes musicals", + "drawShapeLightning": "Llamp", + "drawShapeCloud": "Núvol", + "drawShapeMoon": "Lluna", + "drawShapeBadge": "Insígnia", + "drawSize": "Mida", + "drawLayer": "Capa", + "drawBackgroundVideoFrame": "Fotograma del vídeo (posició actual)", + "drawBackgroundVideoFrameAt": "Fotograma del vídeo a...", + "drawBackgroundImage": "Fitxer d'imatge...", + "drawBackgroundNone": "Sense fons", + "drawBackgroundStretch": "Estira la imatge al llenç", + "drawBackgroundOpacity": "Opacitat del fons", + "drawBackgroundVideoFrameFailed": "No s'ha pogut obtenir un fotograma del vídeo a {0}.", + "drawImages": "Imatges", + "drawConvertToLine": "Converteix el segment en línia", + "drawConvertShapeToCurves": "Converteix totes les línies en corbes", + "drawConvertShapeToLines": "Converteix totes les corbes en línies", + "drawRotateCounterClockwise": "Gira 90° en sentit antihorari", + "drawSvgImages": "Imatges SVG", + "drawSvgNoShapes": "No s'ha trobat cap forma plena ni traç al fitxer SVG.", "progressBarTitle": "Barra de progrés ASSA", "progressBarSettings": "Barra de progrés", "mouseOverColor": "Color en passar-hi el ratolí", @@ -3752,13 +4452,26 @@ "graphics": "Gràfics", "copyToStorageStyles": "Copia als estils emmagatzemats", "copyToFileStyles": "Copia als estils del fitxer", + "styleXAlreadyExistsInStorage": "Ja existeix un estil anomenat «{0}» als estils emmagatzemats.\n\nVoleu sobreescriure'l o conservar tots dos (la còpia rep un nom nou)?", + "styleXAlreadyExistsInFile": "Ja existeix un estil anomenat «{0}» al fitxer.\n\nVoleu sobreescriure'l o conservar tots dos (la còpia rep un nom nou)?", + "styleNameCannotBeEmpty": "El nom d'un estil no pot estar buit.", + "styleNameXIsUsedMoreThanOnce": "El nom d'estil «{0}» l'utilitza més d'un estil al fitxer.", + "overwrite": "Sobreescriu", + "keepBoth": "Conserva tots dos", + "doThisForAllConflictingStyles": "Fes això per a tots els estils en conflicte", "setStyleAsDefault": "Estableix l'estil com a predeterminat", "takeUsagesFromDotDotDot": "Pren els usos de...", "replaceStyleWithDotDotDot": "Substitueix l'estil per...", "noAttachmentsFound": "No s'ha trobat cap adjunt al fitxer ASSA seleccionat.", "deleteStyleQuestion": "Voleu suprimir l'estil?", "deleteStylesQuestion": "Voleu suprimir els estils?", + "deleteXStylesQuestion": "Voleu suprimir {0} estils?", + "deleteXStylesFromStorageQuestion": "Voleu suprimir {0} estils dels estils emmagatzemats?", + "deleteStyleXFromStorageQuestion": "Voleu suprimir l'estil «{0}» dels estils emmagatzemats?", + "deleteStyleXFromFileQuestion": "Voleu suprimir l'estil «{0}» del fitxer actual?", "openStyleImportFile": "Obre un fitxer de subtítols del qual importar estils", + "styleImportFiles": "Fitxers d'estil (.ass, .sty, categories .template del Subtitle Edit 4)", + "ssaStyleImportFiles": "Fitxers d'estil (.ssa, .ass, .sty)", "primary": "Primari", "secondary": "Secundari", "applyOverrideTags": "Aplica etiquetes de substitució", diff --git a/src/ui/Assets/Languages/Central Kurdish.json b/src/ui/Assets/Languages/Central Kurdish.json index af5dc226ef3..d7726590353 100644 --- a/src/ui/Assets/Languages/Central Kurdish.json +++ b/src/ui/Assets/Languages/Central Kurdish.json @@ -1,12 +1,30 @@ { "title": "Subtitle Edit", - "version": "v5.2.0-rc7", + "version": "v5.3.0", "translatedBy": "", "cultureName": "ckb-IQ", "general": { "abort": "وازهێنان", "actor": "ئەکتەر", "actors": "ئەکتەرەکان", + "setActorDotDotDot": "دانانی ئەکتەر...", + "actorPickerTitle": "دانانی ئەکتەر", + "actorPickerFilterHint": "بنووسە بۆ پاڵاوتن، یان ناوی ئەکتەرێکی نوێ بنووسە", + "actorPickerKeysHint": "ژمارەیەک یان Enter دابگرە بۆ دانانی ئەکتەر. Alt+Up/Down ڕیزبەندی دەگۆڕێت، Delete ئەکتەرەکە لادەبات.", + "actorPickerNewActorX": "ئەکتەری نوێ \"{0}\"", + "actorPickerLinesSelectedX": "دێڕە هەڵبژێردراوەکان: {0}", + "setStyleDotDotDot": "دانانی شێواز...", + "stylePickerTitle": "دانانی شێواز", + "stylePickerFilterHint": "بنووسە بۆ پاڵاوتن، یان ناوی شێوازێکی نوێ بنووسە", + "stylePickerKeysHint": "ژمارەیەک (1-9، 0) دابگرە بۆ دانانی شێوازەکە لەسەر هەموو دێڕە هەڵبژێردراوەکان.", + "stylePickerNewStyleX": "شێوازی نوێ \"{0}\"", + "stylePickerNewStyleInfo": "Enter دابگرە بۆ زیادکردنی ئەم شێوازە بە ڕێکخستنە بنەڕەتییەکان و دانانی لەسەر دێڕە هەڵبژێردراوەکان. دواتر \"بەڕێوەبردنی شێوازەکان...\" بەکاربهێنە بۆ گۆڕینی فۆنت، ڕەنگەکان و شوێنەکەی.", + "stylePickerCurrentStyleX": "شێوازی ئێستا: {0}", + "stylePickerLinesUsingStyleX": "ئەو دێڕانەی ئەم شێوازە بەکاردەهێنن: {0}", + "stylePickerMarginsXYZ": "چەپ {0}، ڕاست {1}، ستوونی {2}", + "stylePickerSetStyle": "دانانی شێواز", + "stylePickerManageStyles": "بەڕێوەبردنی شێوازەکان...", + "stylePickerManageStylesHint": "ئەم پەنجەرەیە دابخە و بەڕێوەبەری شێوازەکان بکەرەوە بۆ دەستکاریکردنی فۆنت، ڕەنگەکان، شوێن و زیاتر", "add": "زیادکردن", "addDotDotDot": "زیادکردن...", "addToNamesListCaseSensitive": "زیادکردن بۆ لیستی ناوەکان (هەستیار بۆ پیتی گەورە و بچووک)", @@ -339,6 +357,7 @@ "mergeSelectedLinesDialog": "تێکەڵکردنی دێڕە هەڵبژێردراوەکان وەک دیالۆگ", "mergeWithLineAfterAndAutoBreak": "تێکەڵکردن لەگەڵ دێڕی پاشەوە و دابەشکردنی خۆکار", "mergeWithLineAfterKeepBreaks": "تێکەڵکردن لەگەڵ دێڕی پاشەوە (پاراستنی دابەشکراوەکان)", + "mergeWithLineAfterAndUnbreak": "تێکەڵکردن لەگەڵ دێڕی دواتر و لابردنی دابەشبوونی دێڕ", "mergeWithLineBeforeAndAutoBreak": "تێکەڵکردن لەگەڵ دێڕی پێشەوە و دابەشکردنی خۆکار", "mergeWithLineBeforeKeepBreaks": "تێکەڵکردن لەگەڵ دێڕی پێشەوە (پاراستنی دابەشکراوەکان)", "middleCenter": "ناوەڕاست-ناوەڕاست", @@ -353,6 +372,8 @@ "moveAllShotChangeOneFrameForward": "گواستنەوەی هەموو گۆڕانی دیمەنەکان یەک چوارچێوە بۆ پێشەوە", "multiMode": "دۆخی فرەیی", "moveDown": "بردنە خوارەوە", + "increase": "زیادکردن", + "decrease": "کەمکردنەوە", "moveUp": "بردنە سەرەوە", "moveToTop": "بردن بۆ سەرەتا", "moveToBottom": "بردن بۆ کۆتایی", @@ -422,6 +443,7 @@ "percent": "ڕێژە", "pickLayer": "دانانی چین", "pickOllamaModel": "هەڵبژاردنی مۆدێلی Ollama", + "pickModel": "هەڵبژاردنی مۆدێل", "llamaCppUseRemoteServer": "بەکارهێنانی سێرڤەری دەرەکی (URL)", "llamaCppEngineSettings": "ڕێکخستنەکانی بزوێنەری llama.cpp", "llamaCppEngineSettingsSubtitle": "بنەمای سێرڤەری ناوخۆیی llama.cpp", @@ -498,6 +520,7 @@ "reset": "گەڕاندنەوە", "resizePanels": "گۆڕینی قەبارەی پانێڵەکان", "resolution": "ڕێزۆلیوشن", + "retry": "دووبارە هەوڵدانەوە", "reverseRightToLeftStartEnd": "پێچەوانەکردنی دەستپێک/کۆتایی RTL", "right": "ڕاست", "rightToLeft": "ڕاست بۆ چەپ", @@ -559,6 +582,8 @@ "shadowColor": "ڕەنگی سێبەر", "shadowWidth": "پانی سێبەر", "shiftLineBy": "گواستنی دێڕ بە", + "shotIn": "سەرەتای دیمەن", + "shotOut": "کۆتایی دیمەن", "shortcut": "کورتەڕێگە", "shortcuts": "کورتەڕێگەکان", "show": "نیشاندان", @@ -570,9 +595,13 @@ "showStartColumn": "پیشاندانی ستوونی «دەستپێک»", "showHideColumn": "پیشاندانی ستوونی «شاردنەوە»", "showHistory": "پیشاندانی مێژوو", + "clearHistory": "پاککردنەوەی مێژوو", + "removeFromHistory": "لابردن لە مێژوو", "showLayerColumn": "پیشاندانی ستوونی «چین»", "showPreview": "پیشاندانی پێشبینین", "showShotChangesList": "پیشاندانی پێرستی گۆڕانی دیمەنەکان", + "showShotInColumn": "پیشاندانی ستوونی \"سەرەتای دیمەن\"", + "showShotOutColumn": "پیشاندانی ستوونی \"کۆتایی دیمەن\"", "showStyleColumn": "پیشاندانی ستوونی «شێواز»", "showTeletext": "پیشاندانی ستوونی تێلەتێکست لە بینینی لیستدا", "showTimeCodes": "پیشاندانی کۆدەکانی کات", @@ -629,12 +658,14 @@ "styleExaggeration": "زۆرکردنی شێواز", "styles": "شێوازەکان", "subtitleFile": "فایلی ژێرنووس", + "subtitleFileChangedOutsideX": "فایلی \"{0}\" لە دەرەوەی Subtitle Edit گۆڕدرا.\n\nدووبارە باری بکەیتەوە و گۆڕانکارییەکانت لەدەست بدەیت؟", "subtitleFileName": "ناوی فایلی ژێرنووس", "subtitleFileSaved": "فایلی ژێرنووس پاشەکەوتکرا", "subtitleFileSavedToX": "فایلی ژێرنووس پاشەکەوتکرا لە {0}", "subtitleFiles": "فایلەکانی ژێرنووس", "subtitleFormats": "فۆرماتەکانی ژێرنووس", "subtitleLoadedX": "ژێرنووس بارکرا: {0}", + "subtitleReloadedChangedOutsideX": "ژێرنووس دووبارە بارکرایەوە (لە دەرەوەی Subtitle Edit گۆڕدرابوو): {0}", "suffix": "پاشگر", "suggestions": "پێشنیارەکان", "sync": "هاوکاتکردن", @@ -680,6 +711,7 @@ "type": "جۆر", "unbreak": "یەکخستنی دەق", "unbreakLines": "یەکخستنی دێڕەکان", + "undelete": "گەڕاندنەوەی سڕاوە", "underline": "ژێرهێڵ", "undo": "پاشگەزبوونەوە", "unknown": "نەزانراو", @@ -717,10 +749,23 @@ "videoCustom3ForwardX": "ڤیدیۆ، میلی چرکەی دڵخواز ({0:#,###,##0}) بۆ پێشەوە، 3", "videoCustom4BackX": "ڤیدیۆ، میلی چرکەی دڵخواز ({0:#,###,##0}) بۆ دواوە، 4", "videoCustom4ForwardX": "ڤیدیۆ، میلی چرکەی دڵخواز ({0:#,###,##0}) بۆ پێشەوە، 4", + "videoCustom1BackAndPauseX": "ڤیدیۆ، میلی چرکەی تایبەت ({0:#,###,##0}) بۆ دواوە و وەستان، 1", + "videoCustom1ForwardAndPauseX": "ڤیدیۆ، میلی چرکەی تایبەت ({0:#,###,##0}) بۆ پێشەوە و وەستان، 1", + "videoCustom2BackAndPauseX": "ڤیدیۆ، میلی چرکەی تایبەت ({0:#,###,##0}) بۆ دواوە و وەستان، 2", + "videoCustom2ForwardAndPauseX": "ڤیدیۆ، میلی چرکەی تایبەت ({0:#,###,##0}) بۆ پێشەوە و وەستان، 2", + "videoCustom3BackAndPauseX": "ڤیدیۆ، میلی چرکەی تایبەت ({0:#,###,##0}) بۆ دواوە و وەستان، 3", + "videoCustom3ForwardAndPauseX": "ڤیدیۆ، میلی چرکەی تایبەت ({0:#,###,##0}) بۆ پێشەوە و وەستان، 3", + "videoCustom4BackAndPauseX": "ڤیدیۆ، میلی چرکەی تایبەت ({0:#,###,##0}) بۆ دواوە و وەستان، 4", + "videoCustom4ForwardAndPauseX": "ڤیدیۆ، میلی چرکەی تایبەت ({0:#,###,##0}) بۆ پێشەوە و وەستان، 4", "videoExtension": "پاشگری فایلی ڤیدیۆ", "videoFile": "فایلی ڤیدیۆ", "videoFileGenerated": "فایلی ڤیدیۆ دروستکرا", "videoFileGeneratedX": "فایلی ڤیدیۆ دروستکرا: \"{0}\"", + "videoFilesGenerated": "فایلە ڤیدیۆییەکان دروستکران", + "xVideosGenerated": "{0} ڤیدیۆ دروستکران", + "xOfYVideosGenerated": "{0} لە {1} ڤیدیۆ دروستکران", + "savedInX": "پاشەکەوتکرا لە {0}", + "savedInXFolders": "پاشەکەوتکرا لە {0} فۆڵدەر", "videoFiles": "فایلە ڤیدیۆییەکان", "videoAndAudioFiles": "فایلە ڤیدیۆ و دەنگییەکان", "videoOffset": "لادانی ڤیدیۆ", @@ -778,7 +823,6 @@ "imageFiles": "فایلە وێنەکان", "openAiCompatibleSttEndpoint": "بەستەری STT (URL)", "openAiCompatibleSttApiKey": "کلیلی API", - "openAiCompatibleSttModel": "مۆدێل", "openAiCompatibleSttExtraHeaders": "سەرپەڕە زیادەکان", "openAiCompatibleSttTimeout": "کاتی چاوەڕوانی (چرکە)", "openAiCompatibleSttLanguage": "ئاماژەی زمان", @@ -788,6 +832,9 @@ "dashScopeSttRegion": "ناوچە", "dashScopeSttEnableWords": "کاتبەندی ئاستی وشە", "dashScopeSttRegionKeyHint": "تێبینی: کلیلی API ی Alibaba Cloud Model Studio پەیوەستە بە ناوچە - دڵنیابە ناوچەی هەڵبژێردراو لەگەڵ ئەو ناوچەیە بگونجێت کە کلیلی تێدا دروستکراوە (چین یان نێودەوڵەتی).", + "googleCloudSttProjectId": "ناسنامەی پرۆژە (تەنها بۆ Application Default Credentials)", + "googleCloudSttBucketName": "Storage bucket (بەتاڵ = خۆکار)", + "googleCloudSttDynamicBatching": "کۆمەڵکردنی داینامیکی (نزیکەی 5 جار هەرزانتر، بێ گەرەنتی دواکەوتن)", "onlineSttApiKeyMissing": "پێویستە کلیلی API دابنرێت. تکایە کلیلی API ـەکەت بنووسە و دووبارە هەوڵبدە.", "openAiCompatibleSttAutoTranscribeOnAudioSelection": "دەقکردنی خۆکاری هەڵبژاردە نوێکانی شەپۆل بە قسە بۆ دەق", "openAiCompatibleSttStream": "وەڵامی ستریم", @@ -845,6 +892,7 @@ "multipleReplace": "جێگرتنەوەی فرەیی...", "fillSelectedLinesWithClipboard": "پڕکردنەوەی دێڕە هەڵبژێردراوەکان بە دەقی کلیپبۆرد", "goToLineNumber": "چوون بۆ دێڕی ژمارە...", + "commandPalette": "پالێتی فەرمانەکان...", "rightToLeftMode": "دۆخی ڕاست بۆ چەپ", "modifySelectionDotDotDot": "دەستکاری هەڵبژاردە...", "tools": "ئامرازەکان", @@ -852,6 +900,7 @@ "applyDurationLimits": "جێبەجێکردنی سنوورەکانی ماوە...", "batchConvert": "گۆڕینی بەکۆمەڵ...", "beautifyTimeCodes": "جوانکردنی کۆدەکانی کات...", + "improveTimeCodes": "باشترکردنی کۆدەکانی کات (forced alignment)...", "bridgeGaps": "پڕکردنەوەی بۆشاییەکان...", "applyMinGap": "جێبەجێکردنی کەمترین بۆشایی لە نێوان ژێرنووسەکاندا...", "changeCasing": "گۆڕینی جۆری پیت...", @@ -990,6 +1039,9 @@ "selectCurrentLineWhilePlayingHint": "هەڵبژاردنی ژێرنووسی ئێستا لە کاتی لێداندا {0}", "videoPosition": "شوێنی ڤیدیۆ {0}", "videoPositionTextBox": "سنووقی دەقی شوێنی ڤیدیۆ {0}", + "toolbarLineBreakX": "دابەشبوونی دێڕ {0} (باقی تووڵامراز لە ڕیزێکی نوێدا بەردەوام دەبێت)", + "initialText": "دەقی سەرەتایی دێڕی هەڵبژێردراو", + "initialTextHint": "دەقی دێڕی هەڵبژێردراو وەک ئەوەی لە کاتی هەڵبژاردنی دێڕەکەدا بوو", "hideWaveformToolbar": "شاردنەوەی تووڵامراز {0}", "resetZoomAndSpeed": "گەڕاندنەوەی زووم و خێرایی لێدان {0}", "removeBlankLines": "لابردنی دێڕە بۆشەکان {0}", @@ -1003,6 +1055,15 @@ "textPlayHint": "لێدانی ژێرنووسی ئێستا و وەستان لە کۆتاییدا {0}", "textPauseHint": "وەستاندنی لێدان {0}", "textNextHint": "لێدانی ژێرنووسی دواتر و وەستان لە کۆتاییدا {0}", + "moveSelectedLines": "گواستنەوەی دێڕە هەڵبژێردراوەکان (دوگمەکانی دواوە/پێشەوە)", + "moveSelectedLinesAndFollowing": "گواستنەوەی دێڕە هەڵبژێردراوەکان و هەموو دواترەکان (دوگمەکانی دواوە/پێشەوە)", + "moveAllLines": "گواستنەوەی هەموو دێڕەکان (دوگمەکانی دواوە/پێشەوە)", + "moveSelectedLinesBackHint": "گواستنەوەی دێڕە هەڵبژێردراوەکان {0} ms بۆ دواوە {1}", + "moveSelectedLinesForwardHint": "گواستنەوەی دێڕە هەڵبژێردراوەکان {0} ms بۆ پێشەوە {1}", + "moveSelectedLinesAndFollowingBackHint": "گواستنەوەی دێڕە هەڵبژێردراوەکان و هەموو دواترەکان {0} ms بۆ دواوە {1}", + "moveSelectedLinesAndFollowingForwardHint": "گواستنەوەی دێڕە هەڵبژێردراوەکان و هەموو دواترەکان {0} ms بۆ پێشەوە {1}", + "moveAllLinesBackHint": "گواستنەوەی هەموو دێڕەکان {0} ms بۆ دواوە {1}", + "moveAllLinesForwardHint": "گواستنەوەی هەموو دێڕەکان {0} ms بۆ پێشەوە {1}", "guessStartNoLineSelected": "مەزەندەی دەستپێک: هیچ دێڕێک هەڵنەبژێردراوە", "guessStartTimeCodesLocked": "مەزەندەی دەستپێک: کۆدەکانی کات چەسپێنراون", "guessStartNoWaveform": "مەزەندەی دەستپێک: هیچ شەپۆلێک نییە", @@ -1024,15 +1085,18 @@ "charactersPerSecond": "پیت/چرکە: {0}", "chooseColumn": "هەڵبژاردنی ستوون", "columnPaste": "لکاندنی ستوونی", + "commandPaletteTitle": "پالێتی فەرمانەکان", + "commandPaletteSearchPlaceholder": "بنووسە بۆ گەڕان لە فەرمانەکان...", + "commandPaletteNoCommandsFound": "هیچ فەرمانێکی گونجاو نییە", "createdEmptyTranslation": "وەرگێڕانێکی بەتاڵ لە ژێرنووسی ئێستا دروستکرا", "deleteText": "سڕینەوەی دەق", "deleteTextAndShiftCellsUp": "سڕینەوەی دەق و گواستنەوەی خانەکان بۆ سەرەوە", "endTimeMustBeAfterStartTime": "کۆتایی دەبێت دوای دەستپێک بێت.", "errorLoad7Zip": "ئەم فایلە وا دیارە فایلێکی پەستاوکراوی 7-Zip بێت.\n\nSubtitle Edit ناتوانێت فایلی پەستاوکراو بکاتەوە.", - "errorLoadBinaryZeroes": "بەداخەوە ئەم فایلە تەنها سفرە باینەرییەکان لەخۆ دەگرێت!\n\nئەگەر ئەم فایلەت بە Subtitle Edit دەستکاری کردبێت، لە ڕێگەی بابەتی مینیو فایل -> گەڕاندنەوەی پاشەکەوتی خۆکار... ڕەنگە پاشەکەوتێک بدۆزیتەوە.", + "errorLoadBinaryZeroes": "بەداخەوە ئەم فایلە تەنها سفرە باینەرییەکان لەخۆ دەگرێت!\n\nئەگەر ئەم فایلەت بە Subtitle Edit دەستکاری کردبێت، لە ڕێگەی بابەتی مینیو فایل -> گەڕاندنەوەی پاشەکەوتی خۆکار... ڕەنگە پاشەکەوتێک بدۆزیتەوە.", "errorLoadGZip": "ئەم فایلە وا دیارە فایلێکی پەستاوکراوی GZip بێت.\n\nSubtitle Edit ناتوانێت فایلی پەستاوکراو بکاتەوە.", "errorLoadLargeFile": "ئەم فایلە زۆر گەورەیە بۆ ئەوەی وەک فایلی ژێرنووس بکرێتەوە.", - "errorLoadVideoFilePrompt": "ئەم فایلە وا دیارە فایلێکی ڤیدیۆ بێت:\n{0}\n\nدەتوانیت ڤیدیۆیەک لە ڕێی «ڤیدیۆ -> کردنەوەی ڤیدیۆ» یان بە ڕاکێشان و دانانی ڤیدیۆ لەسەر لێدەری ڤیدیۆ بکەیتەوە.\n\nدەتەوێت وەک فایلی ڤیدیۆ بکرێتەوە؟", + "errorLoadVideoFilePrompt": "ئەم فایلە وا دیارە فایلێکی ڤیدیۆ بێت:\n{0}\n\nدەتوانیت ڤیدیۆیەک لە ڕێی «ڤیدیۆ -> کردنەوەی ڤیدیۆ» یان بە ڕاکێشان و دانانی ڤیدیۆ لەسەر لێدەری ڤیدیۆ بکەیتەوە.\n\nدەتەوێت وەک فایلی ڤیدیۆ بکرێتەوە؟", "errorLoadJpg": "ئەم فایلە وا دیارە فایلێکی وێنەی JPG بێت.\n\nSubtitle Edit ناتوانێت فایلی وێنە بکاتەوە.", "errorLoadPng": "ئەم فایلە وا دیارە فایلێکی وێنەی PNG بێت.\n\nSubtitle Edit ناتوانێت فایلی وێنە بکاتەوە.", "errorLoadRar": "ئەم فایلە وا دیارە فایلێکی پەستاوکراوی 7-Zip بێت.\n\nSubtitle Edit ناتوانێت فایلی پەستاوکراو بکاتەوە.", @@ -1062,11 +1126,14 @@ "numberOfLinesEvenlyDistributedX": "{0} دێڕ بە یەکسانی دابەشکران", "oneLineCopiedFromOriginal": "یەک دێڕ لە ژێرنووسی ڕەسەنەوە لەبەرگیرا", "oneLineCopiedToOriginal": "یەک دێڕ بۆ ژێرنووسی ڕەسەن لەبەرگیرا", + "onlineOnlyVideo": "ڤیدیۆی تەنها-ئۆنلاین", + "onlineOnlyVideoXSizeYDownloadAndOpen": "فایلی ڤیدیۆی \"{0}\" ({1}) تەنها بە شێوەی ئۆنلاین هەڵگیراوە، بۆ نموونە لە Dropbox یان iCloud Drive، و پێش کردنەوەی دەبێت دابگیرێت.\n\nئێستا دایبگریت و بیکەیتەوە؟", "oneLineMerged": "یەک دێڕ تێکەڵکرا", "oneLineSwitched": "یەک دێڕ ئاڵوگۆڕکرا", "overwriteExistingCells": "لەسەرنووسی خانەکانی ئێستا", "overwriteOrShiftCellsDown": "لەسەرنووسین/گواستنەوەی خانەکان بۆ خوارەوە", "parsingMatroskaFile": "شیکردنەوەی فایلی Matroska...", + "readingDvdSubtitles": "خوێندنەوەی ژێرنووسەکانی DVD...", "pasteFromClipboardDotDotDot": "لکاندن لە کلیپبۆردەوە...", "redoPerformed": "دووبارەکردنەوە جێبەجێکرا", "redoPerformedXActionLeft": "دووبارەکردنەوە جێبەجێکرا (کردار ماوە: {0})", @@ -1106,12 +1173,14 @@ "trimmedXLines": "{0} دێڕی ژێرنووس کورتکرانەوە", "pastedXLinesOverSelectedLines": "{0} دێڕ لەسەر دێڕە هەڵبژێردراوەکان لکێشران", "pastedXOfYLinesOverSelectedLines": "{0} لە {1} دێڕ لەسەر دێڕە هەڵبژێردراوەکان لکێشران - ئەوانی تر جێیان نەبوو لە هەڵبژاردەکەدا", + "insertedXLinesFromClipboard": "{0} دێڕ لە کلیپبۆردەوە تێخرا", "openOriginalDifferentNumberOfSubtitlesXY": "فایلی ژێرنووسی ڕەسەن هەمان ژمارەی ژێرنووسی فایلی ئێستا نییە.\n\n• ژێرنووسە ڕەسەنەکان: {0}\n• ژێرنووسە ئێستاکان: {1}", "originalTextReadOnly": "دەقی ڕەسەن (تەنها خوێندنەوە)", "originalTextEditMode": "دەقی ڕەسەن (دۆخی دەستکاری)", "originalIsReadOnlyNotSaved": "ژێرنووسی ڕەسەن ئاماژەیەکی تەنها-خوێندنەوەیە و پاشەکەوت نەکرا", "originalIsEmptyNotSaved": "ژێرنووسی ڕەسەن هیچ دەقێکی تێدا نییە و پاشەکەوت نەکرا", "originalIsReadOnlyReference": "ژێرنووسی ڕەسەن وەک ئاماژەیەکی تەنها-خوێندنەوە کراوە", + "closeTranslationQuestionXY": "وەرگێڕانی \"{0}\" دابخرێت؟\n\nژێرنووسی ڕەسەنی \"{1}\" دەبێتە ئەو ژێرنووسەی دەستکاری دەکەیت. خودی فایلی وەرگێڕانەکە ناگۆڕدرێت.", "allowEditOfOriginalSubtitle": "ڕێگەدان بە دەستکاریکردنی ژێرنووسی ڕەسەن", "showAllOriginalLinesX": "پیشاندانی هەموو دێڕە ڕەسەنەکان ({0} لێرە هاوتایان نییە)", "showAllOriginalLinesHint": "دێڕەکانی بێ لێکچوون وەک ڕیزی زیادە پیشان دەدرێن، بۆ ئەوەی ببینیت وەرگێڕان چ دێڕێک کەمە.", @@ -1146,6 +1215,7 @@ "fileExportedInFormatXToY": "فایلەکە بە فۆرماتی {0} بۆ {1} دەرهێنرا", "fileExportedInFormatXToFileY": "فایلەکە بە فۆرماتی \"{0}\" بۆ فایلی \"{1}\" دەرهێنرا", "fixedXLines": "{0} دێڕ چاککران", + "toolXChangedYLines": "{0}: {1} دێڕ گۆڕدران", "transcriptionCompletedWithXLines": "دەقکردنەوە تەواوبوو بە {0} دێڕ", "openSubtitleFileX": "کردنەوەی فایلی ژێرنووسی \"{0}\"؟", "replacedXOccurrences": "{0} جار جێگیرکرایەوە", @@ -1236,6 +1306,21 @@ "formattingDotDotDot": "فۆرماتکردن...", "imageBasedSubtitleForEditDotDotDot": "ژێرنووسی وێنەیی بۆ دەستکاری...", "imageBasedSubtitleForOcrDotDotDot": "ژێرنووسی وێنەیی بۆ OCR...", + "dvdSubtitlesDotDotDot": "ژێرنووس لە DVD (IFO/VOB)...", + "titleImportDvd": "هێنانەناوی ژێرنووس لە DVD", + "dvdIfoFile": "فایلی IFO", + "dvdVobFiles": "فایلەکانی VOB", + "dvdVideoStandard": "ستانداردی ڤیدیۆ", + "dvdStartRipping": "دەستپێکردنی دەرهێنان", + "dvdRippingAborted": "دەرهێنان هەڵوەشێنرایەوە", + "dvdOpenIfoFile": "کردنەوەی فایلی DVD IFO", + "dvdAddVobFiles": "زیادکردنی فایلەکانی VOB", + "dvdIfoFiles": "فایلەکانی DVD IFO", + "dvdVobFileType": "فایلەکانی DVD VOB", + "dvdDropHint": "فایلێکی IFO بکەرەوە (VIDEO_TS\\VTS_01_0.IFO) یان فایلەکانی VOB زیاد بکە - یان ئێرە دایانبنێ", + "dvdNotAnIfoFileX": "{0} فایلی DVD IFO نییە", + "dvdMenuIfoNoTitleSetsX": "هیچ title set ێک (VTS_xx_0.IFO) لە تەنیشت {0} نەدۆزرایەوە", + "dvdEncryptedXOfY": "{0} لە {1} پاکەتی ژێرنووس هێشتا بە CSS شفرەکراون - ئەم فایلانەی VOB بەبێ شفرەلابردن کۆپی کراون، بۆیە هەندێک وێنەی ژێرنووس تێکدەچن.\n\nسەرەتا DVD ەکە بە ئامرازێکی دەرهێنانی DVD شفرە لابدە بۆ ئەنجامێکی پاک.\n\nهەر بەردەوام دەبیت؟", "splitTextAt": "دابەشکردنی دەق لە", "blankLines": "دێڕە بۆشەکان", "oneLineIsOneSubtitle": "یەک دێڕ یەک ژێرنووسە", @@ -1255,8 +1340,13 @@ "forcedAlignerProgress": "هاوشانکردن... پەنجەرەی {0} لە {1}، {2} لە {3} دێڕ", "forcedAlignerSetupTitle": "هاوشانکردنی کۆدەکانی کات لە ڕێی forced aligner", "forcedAlignerSetupIntro": "Forced aligner ـێک دەقی ژێرنووسی هەتەوەریت لەگەڵ دەنگدا دەگونجێنێت، بەبێ ئەوەی سەرەتا بیخوێنێتەوە. ڤیدیۆ درێژەکان بە پارچە هاوشان دەکرێن، بۆیە هەر درێژییەک کار دەکات.", + "forcedAlignerEndsFromSpeech": "دانانی کاتەکانی کۆتایی لە قسەی جیاکراوە (هێواش)", + "forcedAlignerEndsFromSpeechHint": "forced aligner دەدۆزێتەوە دێڕێک لە کوێ دەستپێدەکات، نەک لە کوێ کۆتایی دێت، بۆیە کاتەکانی کۆتایی بە ئاسایی لە کاتی خوێندنەوەوە دێن. کاتێک ئەمە چالاکە، سەرەتا مۆسیقا و کاریگەرییە دەنگییەکان لە دەنگەکە لادەبرێن و هەر دێڕێک لەو شوێنەدا کۆتایی دێت کە قسەکەی بێدەنگ دەبێت. نزیکەی هێندەی خودی ڤیدیۆکە کات دەبات لەگەڵ GPU - و چەندین جار زیاتر بەبێ GPU.", + "forcedAlignerIsolatingSpeech": "جیاکردنەوەی قسە... {0}", + "forcedAlignerIsolatingSpeechFailed": "نەتوانرا قسەکە جیا بکرێتەوە - کاتەکانی کۆتایی لەبری ئەوە لە کاتی خوێندنەوەوە دانران.", "forcedAlignerModel": "مۆدێلی aligner", - "forcedAlignerDownloadEngine": "داگرتن/نوێکردنەوەی بزوێنەر..." + "forcedAlignerDownloadEngine": "داگرتن/نوێکردنەوەی بزوێنەر...", + "forcedAlignerEstimatedXOfY": "{0} لە {1} دێڕ نەتوانران هاوشان بکرێن و بەپێی کاتی خوێندنەوە دانران - کۆدەکانی کاتیان تەنها خەمڵێنراون. ڕەنگە دەقەکە درێژتر بێت لەوەی لە ڤیدیۆکەدا دەوترێت." }, "export": { "exportImagesProfiles": "پرۆفایلەکانی دەرهێنانی وێنە", @@ -1305,6 +1395,18 @@ "imageBasedSubtitleSaved": "ژێرنووسی وێنەیی پاشەکەوتکرا", "fullFrameImage": "وێنەی چوارچێوەی تەواو", "fullFrameImageHint": "هەر وێنەیەک بە قەبارەی چوارچێوەی ڤیدیۆ بکە، لەگەڵ ژێرنووس لە شوێنی خۆیدا", + "stereo3D": "3D", + "stereo3DHint": "بۆ ڤیدیۆی 3D: ژێرنووسەکە یەک جار لە نیوەی هەر چاوێکی چوارچێوەکەدا بکێشە", + "halfSideBySide": "نیوە لە تەنیشت یەک", + "halfTopBottom": "نیوە سەرەوە/خوارەوە", + "depth3D": "قووڵی", + "depth3DHint": "ژێرنووسەکە چەند لە شاشەکە دێتە دەرەوە - پۆزەتیڤ نزیکتری دەکاتەوە، نێگەتیڤ دوورتری دەخاتەوە", + "plane3D": "3D-Plane", + "plane3DHint": "بە هەر ژێرنووسێک ئەو قووڵییە بدە کە Blu-ray ی 3D بۆی هەڵدەگرێت، لە فایلێکی 3D-Plane (.ofs) کە بە BD3D2MK3D یان OFSExtractor دروستکراوە. قووڵی لەو شوێنانە بەکاردێت کە 3D-Plane هیچی تێدا نییە", + "openPlane3DTitle": "کردنەوەی فایلی 3D-Plane", + "unableToLoadPlane3DX": "نەتوانرا 3D-Plane باربکرێت: {0}", + "plane3DXDepthYToZ": "{0} (قووڵی {1} بۆ {2})", + "plane3DXNoDepth": "{0} (هیچ بەهایەکی قووڵی نییە)", "textEffect": "کاریگەری دەق", "textEffectSettingsTitle": "ڕێکخستنەکانی کاریگەری دەق", "textEffectPreset": "پێش-ڕێکخستن", @@ -1409,7 +1511,22 @@ "restoreAutoBackupFile": "گەڕاندنەوەی فایلی پاشەکەوتی خۆکار", "restoreXFromY": "دەتەوێت \"{0}\" لە {1}ەوە بگەڕێنیتەوە؟", "deleteAllSubtitleBackups": "دەتەوێت هەموو فایلەکانی پاشەکەوتی ژێرنووس بسڕیتەوە؟", - "deleteAll": "سڕینەوەی هەموو" + "deleteAll": "سڕینەوەی هەموو", + "subtitles": "ژێرنووسەکان", + "settingsBackupInfo": "Settings.json پاشەکەوت دەکرێت کاتێک Subtitle Edit دەستپێدەکات، زۆرترین جارێک لە هەر {0} ڕۆژدا و تەنها ئەگەر ڕێکخستنەکان گۆڕابن. نوێترین {1} پاشەکەوت دەهێڵدرێنەوە.", + "settingsBackupInfoEveryStart": "Settings.json هەر جارێک Subtitle Edit دەستپێدەکات پاشەکەوت دەکرێت، ئەگەر ڕێکخستنەکان گۆڕابن. نوێترین {0} پاشەکەوت دەهێڵدرێنەوە.", + "settingsBackupOff": "پاشەکەوتی خۆکاری ڕێکخستنەکان ناچالاکە (هەڵبژاردەکان → ڕێکخستنەکان → فایل).", + "lastSettingsBackupX": "دوایین پاشەکەوت: {0}", + "noSettingsBackupsYet": "هێشتا هیچ پاشەکەوتێکی ڕێکخستنەکان نییە", + "openSettingsBackupFolder": "کردنەوەی فۆڵدەری پاشەکەوتی ڕێکخستنەکان", + "backUpSettingsNow": "ئێستا پاشەکەوت بکە", + "settingsBackedUp": "ڕێکخستنەکانی ئێستا پاشەکەوت کران.", + "restoreSettings": "گەڕاندنەوەی ڕێکخستنەکان", + "restoreSettingsFromX": "دەتەوێت ڕێکخستنەکانی ئێستات بە پاشەکەوتەکەی {0} جێگیر بکەیتەوە؟\n\nسەرەتا ڕێکخستنەکانی ئێستات پاشەکەوت دەکرێن. هەندێک گۆڕانکاری تەنها دوای دووبارە دەستپێکردنەوەی Subtitle Edit کاریگەر دەبن.", + "settingsRestored": "ڕێکخستنەکان گەڕێندرانەوە. تکایە Subtitle Edit دووبارە دەستپێبکەرەوە بۆ ئەوەی هەموو گۆڕانکارییەکان کاریگەر بن.", + "settingsRestoreFailed": "نەتوانرا ڕێکخستنەکان بگەڕێندرێنەوە: {0}", + "settingsBackupNotValid": "پاشەکەوتەکە فایلێکی دروستی ڕێکخستنەکان نییە، بۆیە ڕێکخستنەکانی ئێستات هێڵدرانەوە", + "deleteAllSettingsBackups": "دەتەوێت هەموو فایلەکانی پاشەکەوتی ڕێکخستنەکان بسڕیتەوە؟" }, "propertiesDCinema": { "title": "تایبەتمەندییەکانی D-Cinema SMPTE", @@ -1423,22 +1540,16 @@ "editRate": "ڕێژەی دەستکاری", "timeCodeRate": "ڕێژەی کۆدی کات", "startTime": "کاتی دەستپێک", - "font": "فۆنت", "fontId": "ID ی فۆنت", "fontUri": "URI ی فۆنت", - "generate": "دروستکردن", - "fontColor": "ڕەنگی فۆنت", "chooseColor": "هەڵبژاردنی ڕەنگ", "fontEffect": "کاریگەری فۆنت", "effectColor": "ڕەنگی کاریگەری", - "fontSize": "قەبارەی فۆنت", "topBottomMargin": "پەراوێزی سەرەوە/خوارەوە", "fadeUpTime": "کاتی فەیدی ناوچوون", "fadeDownTime": "کاتی فەیدی دەرچوون", - "frames": "چوارچێوەکان", "zPosition": "شوێنی Z", - "zPositionHelp": "ژمارە ئەرێنییەکان دەق دوور دەخەنەوە، ژمارە نەرێنییەکان دەق نزیک دەکەنەوە؛ ئەگەر شوێنی Z سفر بێت ئەوا دوو ڕەهەندییە (2D)", - "export": "دەرهێنان..." + "zPositionHelp": "ژمارە ئەرێنییەکان دەق دوور دەخەنەوە، ژمارە نەرێنییەکان دەق نزیک دەکەنەوە؛ ئەگەر شوێنی Z سفر بێت ئەوا دوو ڕەهەندییە (2D)" }, "webVtt": { "setStylesForSelectedLinesTitle": "دانانی شێوازەکانی WebVTT بۆ دێڕە هەڵبژێردراوەکان", @@ -1476,11 +1587,61 @@ "ignoreFormatting": "پشتگوێخستنی فۆرمات", "ignoreFormattingHint": "دێڕەکان کە تەنها لە تەگەکانی فۆرماتدا جیاوازن، وەک یان {\\an8}، بە جیاواز هەژمار ناکرێن", "showOnlyDifferencesInText": "تەنها جیاوازییەکان لە دەقدا", + "ignoreNumbering": "پشتگوێخستنی ژمارەکردن", + "ignoreNumberingHint": "ئەو دێڕانەی تەنها لە ژمارەکەیاندا جیاوازن بە جیاواز ناژمێردرێن", + "ignoreTimeDifferenceMs": "لێبووردەیی کات (ms)", + "ignoreTimeDifferenceMsHint": "ئەو کاتانەی دەستپێک و کۆتایی کە ئەوەندە میلی چرکە یان کەمتر جیاوازن بە جیاواز ناژمێردرێن", + "compareDifferences": "جیاوازییەکان", + "compareTextDifferences": "جیاوازییەکانی دەق", + "compareCurrent": "ئێستا", + "compareReference": "سەرچاوە", + "compareEditable": "دەستکاریکراو", + "compareEditableHint": "ئەو گۆڕانکارییانەی لێرە دەکرێن دەگەڕێنەوە بۆ ژێرنووسەکە کاتێک کرتە لە جێبەجێکردن دەکەیت", + "compareReadOnly": "تەنها خوێندنەوە", + "compareTakeFromReference": "وەرگرتنی دەق و کاتبەندی لە سەرچاوەکە", + "compareInsertFromReference": "تێخستنی ئەم دێڕە بۆ ناو ژێرنووسی ئێستا", + "compareDeleteFromCurrent": "سڕینەوەی ئەم دێڕە لە ژێرنووسی ئێستا", + "compareTakeText": "وەرگرتنی دەق", + "compareTakeTiming": "وەرگرتنی کاتبەندی", + "compareEditLine": "دەستکاری دێڕ", + "compareEditHint": "Ctrl+Enter بۆ پاشەکەوتکردن، Esc بۆ هەڵوەشاندنەوە", + "compareEdited": "دەستکاریکراو", + "compareOnePendingChange": "1 گۆڕانکاری چاوەڕوان", + "compareXPendingChanges": "{0} گۆڕانکاری چاوەڕوان", + "compareDiscardXChanges": "وازهێنان لەو {0} گۆڕانکارییەی لە بەراوردکردندا کراون؟", + "compareXChangesApplied": "بەراوردکردن: {0} گۆڕانکاری جێبەجێکران", + "compareChangeTextX": "#{0} دەق لە سەرچاوەوە", + "compareChangeTimingX": "#{0} کاتبەندی لە سەرچاوەوە", + "compareChangeTextAndTimingX": "#{0} دەق و کاتبەندی لە سەرچاوەوە", + "compareChangeEditedX": "#{0} دەستکاریکرا", + "compareChangeInsertedX": "#{0} تێخرا", + "compareChangeDeletedX": "#{0} سڕایەوە", + "compareSyncPoint": "خاڵی هاوکات", + "compareSyncPointHint": "خاڵی هاوکات - ئەم دوو دێڕە هەمیشە وەک جووتێک پیشان دەدرێن، و دێڕەکانی سەرەوە و خوارەوە بە جیا ڕیز دەکرێن", + "compareSyncPickCurrent": "خاڵی هاوکات: ئەم دێڕەی ئێستا بەکاربهێنە", + "compareSyncPickReference": "خاڵی هاوکات: ئەم دێڕەی سەرچاوە بەکاربهێنە", + "compareSyncRemove": "لابردنی خاڵی هاوکات", + "compareClearXSyncPoints": "پاککردنەوەی خاڵەکانی هاوکات ({0})", + "compareSyncCurrentPickedX": "ئێستای #{0} وەک خاڵی هاوکات هەڵبژێردرا - دێڕی سەرچاوەی گونجاو هەڵبژێرە و کرتە لە هاوکاتکردن بکە", + "compareSyncReferencePickedX": "سەرچاوەی #{0} وەک خاڵی هاوکات هەڵبژێردرا - دێڕی ئێستای گونجاو هەڵبژێرە و کرتە لە هاوکاتکردن بکە", + "compareSyncWithCurrentX": "هاوکاتکردن لەگەڵ ئێستای #{0}", + "compareSyncWithReferenceX": "هاوکاتکردن لەگەڵ سەرچاوەی #{0}", + "compareSyncApply": "هاوکاتکردن", + "compareSyncPointSetXY": "خاڵی هاوکات دانرا: ئێستای #{0} و سەرچاوەی #{1} ئێستا وەک جووتێک پیشان دەدرێن", + "compareSyncAlreadyPairedXY": "ئێستای #{0} و سەرچاوەی #{1} پێشتر وەک جووتێک بە هەمان دەستپێک پیشان دەدرێن - هیچ شتێک بۆ هاوکاتکردن نییە", + "compareSyncPointShiftedXYZW": "خاڵی هاوکات دانرا: ئێستای #{0}-#{1} {2} جوڵێندرا بۆ ئەوەی لەگەڵ سەرچاوەی #{3} دەستپێبکات", + "compareChangeSyncShiftXYZ": "#{0}-#{1} {2} جوڵێندرا (خاڵی هاوکات)", + "compareTimingDeltaHint": "سەرچاوەکە چەند درەنگتر (+) یان زووتر (−) لە دێڕی ئێستا دەستپێدەکات و کۆتایی دێت", "loadXFromFile": "بارکردنی \"{0}\" لە فایلەوە", + "compareLoadSavedFile": "بارکردنی فایلی پاشەکەوتکراو", "saveCompareHtmlTitle": "پاشەکەوتکردنی فایلی HTML ی بەراورد", "pickMatroskaTrackX": "هەڵبژاردنی تراکی Matroska - {0}", "pickTransportStreamTrackX": "هەڵبژاردنی تراکی transport stream - {0}", + "chapters": "بەشەکان", + "dvdVobFilesMissingX": "فایلەکانی VOB ونن ({0}% دۆزرایەوە)", "pickMp4TrackX": "هەڵبژاردنی تراکی MP4 - {0}", + "pickMxfTrackX": "هەڵبژاردنی ڕێڕەوی MXF - {0}", + "pickMpegTrackX": "هەڵبژاردنی ڕێڕەوی MPEG - {0}", "rosettaProperties": "تایبەتمەندییەکانی Timed Text Rosetta IMSC", "rosettaFontSize": "قەبارەی فۆنت (بەرزی ڕیز)", "propertyTimeBase": "بنەمای کات", @@ -1490,7 +1651,6 @@ "propertyDefaultRegion": "ناوچەی بنەڕەتی", "propertyStyleAttributeName": "ناوی تایبەتمەندی شێواز", "propertyTimeCodeFormat": "فۆرماتی کۆدی کات", - "propertyFileExtension": "پاشگری فایل", "propertyTopOrigin": "سەرچاوەی سەرەوە", "propertyTopExtent": "درێژی سەرەوە", "propertyBottomOrigin": "سەرچاوەی خوارەوە", @@ -1556,7 +1716,8 @@ "findWhat": "دۆزینەوەی چی", "descriptionOptional": "وەسف (ئارەزوومەندانە)", "invalidRegularExpressionX": "دەربڕینی ئاسایی هەڵەیە: {0}", - "regularExpressionTooSlowX": "دەربڕینی ئاسایی دوای {0} چرکە وازی هێنا - یاساکە بازدەخرێت، نموونەیەکی سادەتر تاقی بکەوە" + "regularExpressionTooSlowX": "دەربڕینی ئاسایی دوای {0} چرکە وازی هێنا - یاساکە بازدەخرێت، نموونەیەکی سادەتر تاقی بکەوە", + "moveToCategory": "گواستنەوە بۆ هاوپۆل" }, "find": { "searchTextWatermark": "گەڕان بۆ دەق...", @@ -1569,7 +1730,9 @@ "replaceIn": "جێگرتنەوە/گەڕان لە", "replaceInTextAndOriginal": "دەق و دەقی ڕەسەن", "replaceInTextOnly": "تەنها دەق", - "replaceInOriginalOnly": "تەنها دەقی ڕەسەن" + "replaceInOriginalOnly": "تەنها دەقی ڕەسەن", + "replacedOneOccurrence": "یەک دووبارەبوونەوە جێگیرکرایەوە", + "replacedXOccurrences": "{0} دووبارەبوونەوە جێگیرکرانەوە" }, "regularExpressionContextMenu": { "wordBoundary": "سنووری وشە (\\b)", @@ -1597,6 +1760,7 @@ "xLinesYSubtitles": "{0} دێڕ، {1} ژێرنووس", "xLinesYSubtitlesZErrors": "{0} دێڕ، {1} ژێرنووس، {2} هەڵە", "couldNotParseAsX": "نەتوانرا وەک {0} شیکردنەوە بکرێت", + "readAsXUseAsY": "سەرچاوەکە نەتوانرا وەک {1} بخوێندرێتەوە، بەڵام دەتوانرا وەک {0} بخوێندرێتەوە.\nدێڕەکان بەکاربهێنرێن و هەر وەک {1} پاشەکەوت بکرێت؟", "selectedXCharactersYLines": "هەڵبژێردراو: {0} پیت لە {1} دێڕدا", "replacedXOccurrences": "{0} جار جێگیرکرایەوە", "invalidRegularExpression": "دەربڕینی ئاسایی هەڵەیە", @@ -1632,7 +1796,23 @@ "largeChangeWarning": "گۆڕانکاری گەورە - وا دیارە دووبارە نووسینەوە بێت، بە وریاییەوە پێداچوونەوەی بکە", "mismatchWarning": "لە ڕەسەنەکە ناچێتەوە - ڕەنگە سەر بە دێڕێکی تر بێت", "engineError": "پەیوەندی بە بزوێنەری AI نەکرا: {0}", - "playCurrentLineHint": "لێدانی دێڕی هەڵبژێردراو لە لێدەری ڤیدیۆدا و وەستان لە کۆتاییدا (هەروەها دوو کرتە لەسەر دێڕێک)" + "playCurrentLineHint": "لێدانی دێڕی هەڵبژێردراو لە لێدەری ڤیدیۆدا و وەستان لە کۆتاییدا (هەروەها دوو کرتە لەسەر دێڕێک)", + "editAfterHint": "کرتە لە دەقی ڕیزی هەڵبژێردراو بکە بۆ دەستکاریکردنی چاککردنەکە پێش جێبەجێکردنی (Enter پاشەکەوت دەکات، Escape هەڵدەوەشێنێتەوە)", + "template": "قاڵب", + "templatePlaceholder": "قاڵبێک هەڵبژێرە بۆ جێگرتنەوەی پرۆمپتەکە", + "templateStandard": "پێداچوونەوەی ستاندارد", + "templateSpeechToText": "پاککردنەوەی قسە-بۆ-دەق", + "templateMachineTranslation": "چاککردنی وەرگێڕانی ئامێر", + "templateSdh": "SDH / کەمبیستەکان", + "referenceContext": "زانیاری سەرچاوە", + "referenceContextInfo": "ئارەزوومەندانە. ناوەکان، کارەکتەرەکان، فەرهەنگۆک، کورتەی چیرۆک یان تێبینییەکانی شێواز - مۆدێلەکە ئەمە وەک سەرچاوەی بڕیاردەر بۆ ناو و زاراوەکان دادەنێت. لەگەڵ هەموو کۆمەڵێک دەنێردرێت.", + "referenceContextPlaceholder": "ناوەکان: Kaito، Aunt Mei، Dr. Hollis\nزاراوەکان: Shinkansen، warp core\nکورتەی چیرۆک: Kaito گەشت دەکات بۆ تۆکیۆ بۆ دۆزینەوەی باوکە ونبووەکەی.", + "generateContext": "دروستکردن بە AI", + "generateContextHint": "با بزوێنەری AI ی هەڵبژێردراو ژێرنووسەکە بخوێنێتەوە و ڕەشنووسی ناوەکان، زاراوەکان و کورتەی چیرۆک ئامادە بکات - پێش بەکارهێنان ئەنجامەکە بپشکنە", + "generatingContextPartXOfY": "خوێندنەوەی بەشی {0} لە {1}...", + "generatingContextSynopsis": "نووسینی کورتەی چیرۆک...", + "replaceContextQuestion": "زانیاری سەرچاوەی ئێستا جێگیر بکرێتەوە؟", + "noContextGenerated": "بزوێنەری AI هیچ زانیارییەکی سەرچاوەی بەسوودی نەگەڕاندەوە." }, "aiAssistant": { "title": "یاریدەدەری AI", @@ -1678,6 +1858,7 @@ "removeLineBreaksPixelWidth": "یەکخستنی ژێرنووسەکان کە جێی یەک دێڕ دەبن (پانی پیکسڵ)", "fixUppercaseIInsideLowercaseWords": "چاککردنی 'i' ی گەورە لەناو وشە بچووکەکاندا (هەڵەی OCR)", "fixDoubleApostrophes": "چاککردنی پیتەکانی دوو کۆمە ('') بۆ یەک وێنەچە (\")", + "fixMisreadQuotes": "چاککردنی ئەپۆسترۆفی هەڵە خوێندراو وەک دوو جووت-کۆتێشن (OCR، ئینگلیزی)", "addPeriods": "زیادکردنی خاڵ دوای دێڕەکان کە دێڕی دواتر بە پیتی گەورە دەستپێدەکات", "startWithUppercaseLetterAfterParagraph": "دەستپێکردن بە پیتی گەورە دوای پەرەگراف", "startWithUppercaseLetterAfterPeriodInsideParagraph": "دەستپێکردن بە پیتی گەورە دوای خاڵ لەناو پەرەگرافدا", @@ -1700,6 +1881,7 @@ "fixCommasExample": ",, -> ,", "removeLineBreaksExample": "Foo
bar! -> Foo bar!", "fixDoubleApostrophesExample": "''Has double single quotes'' -> \"Has single double quote\"", + "fixMisreadQuotesExample": "\"Hello' -> \"Hello\"", "addPeriodsExample": "Hello world -> Hello world.", "startWithUppercaseLetterAfterParagraphExample": "p1: Foobar! || p2: foobar! -> p1: Foobar! || p2: Foobar!", "startWithUppercaseLetterAfterPeriodInsideParagraphExample": "Hello there! how are you? -> Hello there! How are you?", @@ -1789,6 +1971,8 @@ "fixMinDurationMs": "چاککردنی کەمترین ماوە (میلی چرکە)", "doNotGoPastShotChange": "لە گۆڕانی دیمەن تێنەپەڕە", "fixMaxDurationMs": "چاککردنی زۆرترین ماوە (میلی چرکە)", + "fixMinDurationFrames": "چاککردنی کەمترین ماوە (چوارچێوە)", + "fixMaxDurationFrames": "چاککردنی زۆرترین ماوە (چوارچێوە)", "minimumDurationMilliseconds": "کەمترین ماوە (میلی چرکە):", "maximumDurationMilliseconds": "زۆرترین ماوە (میلی چرکە):", "maxDurationShouldBeHigherThanMinDuration": "زۆرترین ماوە دەبێت لە کەمترین ماوە گەورەتر بێت", @@ -1838,13 +2022,80 @@ "timeCodesExtractFailed": "نەتوانرا کۆدەکانی کات لە ڤیدیۆکە بخوێنرێنەوە", "timeCodesHint": "لەبری گریمانەکردنی ڕێژەیەکی چوارچێوەی جێگیر، cue ـەکان بە کاتە ڕاستەقینەکانی چوارچێوەی ڤیدیۆ چەسپێنە. پێویستی بە خوێندنەوەی تەواوی ڤیدیۆکە هەیە؛ ئەنجامەکە کاش دەکرێت." }, + "improveTimeCodes": { + "title": "باشترکردنی کۆدەکانی کات (forced alignment)", + "aligner": "Aligner", + "alignerHint": "ئەو مۆدێلەی گوێ لە هەر دێڕێک دەگرێت لە دەنگەکەدا. باشترین هەڵبژاردەکان بۆ زمانی ژێرنووسەکە یەکەم پیشان دەدرێن.", + "maxShift": "زۆرترین جوڵاندن (چرکە)", + "maxShiftHint": "ژێرنووسەکە چەند دەکرێت لە هاوکاتی دەرچووبێت. دێڕێک کە دەستپێکەکەی زیاتر لەمە بجوڵێت وەک خۆی دەهێڵدرێتەوە و نیشانە دەکرێت. هەر بەهایەک بێت، دێڕێکی تاک کە بیەوێت زیاتر لە نیو چرکە زیاتر لە دێڕەکانی دەوروبەری بجوڵێت بە کوێرانە ناجوڵێندرێت: ئەگەر هەموو دراوسێکانی جوڵابن لەگەڵیان دەڕوات، ئەگەرنا بەبێ نیشانەی هەڵبژاردن پێشکەش دەکرێت بۆ ئەوەی بیپشکنیت.", + "adjustStartTimes": "ڕێکخستنی کاتەکانی دەستپێک", + "adjustEndTimes": "ڕێکخستنی کاتەکانی کۆتایی", + "isolateSpeech": "سەرەتا جیاکردنەوەی قسە (هێواش)", + "isolateSpeechHint": "پێش هاوشانکردن مۆسیقا و کاریگەرییە دەنگییەکان لادەبات، بۆ ئەوەی aligner تەنها گفتوگۆکە ببیستێت. یارمەتی دەدات بۆ ئەو دێڕانەی بەسەر مۆسیقای بەرز یان ئاکشندا دەوترێن، بەڵام نزیکەی هێندەی خودی دەنگەکە کات دەبات لەگەڵ GPU - و چەندین جار زیاتر بەبێ GPU.", + "isolatingSpeech": "جیاکردنەوەی قسە... (ئەمە کەمێک کات دەبات)", + "isolateSpeechFailed": "نەتوانرا قسەکە جیا بکرێتەوە - لەبری ئەوە بەرامبەر دەنگی ڕەسەن هاوشان کرا.", + "checkWithSpeechToText": "پشکنین بە قسە-بۆ-دەق", + "checkWithSpeechToTextHint": "دەنگەکەش دەقدەکاتەوە، و هەموو جوڵاندنێک بەرامبەر ئەو شوێنە دەپشکنێت کە وشەکان بەڕاستی تێیدا بیستراون. ئەو جوڵاندنە گەورانەی قسەکە پشتڕاستیان دەکاتەوە بۆت نیشانە دەکرێن، و ئەو جوڵاندنانەی لەو شوێنە دوور دەکەونەوە کە دێڕێک تێیدا دەبیسترێت بەبێ نیشانە دەهێڵدرێنەوە بۆ ئەوەی بیانپشکنیت. هەروەها پیشانی دەدات چەند لە هەر دێڕێک بیستراوە - ڕێژەیەکی کەم واتە دەقەکە جیاوازە لەوەی دەوترێت. ژێرنووسێک کە زیاتر لە زۆرترین جوڵاندن لە هاوکاتی دەرچووبێت - لادانێکی نەگۆڕ، خلیسکانێک بەهۆی جیاوازی ڕێژەی چوارچێوە، یان بازدان لەو شوێنانەی دیمەنەکان بڕاون - سەرەتا بەپێی وشە بیستراوەکان هاوکات دەکرێت. Crisp ASR Parakeet بەکاردەهێنێت، کە بۆ فیلمێکی درێژ لەگەڵ GPU چەند خولەکێک دەخایەنێت.", + "checkWithSpeechToTextNotAvailableX": "پشکنینی قسە-بۆ-دەق بۆ {0} بەردەست نییە", + "transcribingToCheck": "دەقکردنەوە بۆ پشکنینی هاوشانکردنەکە...", + "speechToTextFailed": "قسە-بۆ-دەق سەرکەوتوو نەبوو - کۆدەکانی کات بەرامبەری نەپشکنران.", + "speechToTextX": "قسە-بۆ-دەق: {0}", + "speechToTextConfirmedXDisputedY": "قسە: {0} پشتڕاستکرایەوە، {1} ناکۆک", + "hardlyHeardX": "قسە-بۆ-دەق تەنها {0}% ی وشەکانی ژێرنووسەکەی بیست. ئایا ژێرنووسەکە بەو زمانەیە کە قسەی پێدەکرێت؟ aligner ناتوانێت وەرگێڕانێک دابنێت.", + "heard": "بیستراو", + "heardHint": "قسە-بۆ-دەق چەند لە دێڕەکەی بیست. ڕێژەیەکی کەم واتە دەقەکە جیاوازە لەوەی دەوترێت - ئەو شوێنەی کە ئەگەری هەڵەی aligner زۆرترە.", + "statusConfirmedBySpeech": "بە قسە پشتڕاستکرایەوە", + "statusDisputedBySpeech": "لە شوێنی پێشووی خۆی بیسترا", + "statusMovedWithSync": "لەگەڵ هاوکاتکردنەکە جوڵێندرا", + "syncedFirstXY": "سەرەتا هاوکاتکرا: {0} چرکە لە دەستپێک، {1} چرکە لە کۆتایی", + "showSpeechOnly": "تەنها قسە پیشان بدە", + "showSpeechOnlyHint": "شەپۆلی دەنگی هاوشانکراو لە دەنگێک بکێشە کە مۆسیقا و کاریگەرییە دەنگییەکانی لێ لابراوە؛ شەپۆلی دەنگی ڕەسەن وەک خۆی دەمێنێتەوە بۆ بەراوردکردن. دوای جیاکردنەوەی قسە بەردەست دەبێت - لێرە، یان بە \"تەنها قسە پیشان بدە\" لە شەپۆلی دەنگی پەنجەرەی سەرەکیدا.", + "align": "هاوشانکردن", + "original": "ڕەسەن", + "aligned": "هاوشانکراو", + "intro": "aligner ێک هەڵبژێرە و \"هاوشانکردن\" دابگرە بۆ گوێگرتن لە هەموو دێڕێک لە دەنگەکەدا و ورددکردنی کۆدەکانی کاتی.", + "extractingAudio": "دەرهێنانی دەنگ...", + "aligningBatchXOfY": "هاوشانکردن... کۆمەڵی {0} لە {1}", + "extractAudioFailed": "نەتوانرا دەنگەکە بە ffmpeg دەربهێنرێت.", + "modelWillBeDownloaded": "{0} دادەگیرێت", + "summaryXRetimedYKeptZSkipped": "کاتی نوێ دانرا: {0} · هێڵدرایەوە: {1} · بێ قسە: {2}", + "meanShiftX": "تێکڕای جوڵاندن: {0} ms", + "toCheckX": "بۆ پشکنین: {0}", + "largeMovesToCheckX": "{0} دێڕ بە تەنها خۆیان زۆر دوور دەجوڵێن و بەبێ نیشانە هێڵدراونەتەوە - گوێیان لێ بگرە (F5 دێڕی هاوشانکراو لێدەدات، Shift+F5 ڕەسەنەکە) و ئەوانەی دروستن نیشانە بکە.", + "linesToCheckX": "{0} دێڕ بەبێ نیشانە هێڵدراونەتەوە بۆ پشکنین - گوێیان لێ بگرە (F5 دێڕی هاوشانکراو لێدەدات، Shift+F5 ڕەسەنەکە) و ئەوانەی دروستن نیشانە بکە.", + "changeXOfY": "گۆڕانکاری {0} لە {1}", + "previousChange": "گۆڕانکاری پێشوو", + "nextChange": "گۆڕانکاری دواتر", + "startShift": "دەستپێک", + "endShift": "کۆتایی", + "statusRetimed": "کاتی نوێ دانرا", + "statusMovedWithNeighbours": "لەگەڵ دراوسێکانی جوڵێندرا", + "statusLargeMoveUnconfirmed": "جوڵاندنی گەورە - گوێ بگرە، نیشانە بکە بۆ جێبەجێکردن", + "statusUnchanged": "پێشتر لە شوێنی خۆیدایە", + "statusNoSpeech": "بێ قسە", + "statusShiftTooLarge": "هێڵدرایەوە - جوڵاندن زۆر گەورەیە", + "statusFailed": "هێڵدرایەوە - aligner سەرکەوتوو نەبوو", + "statusAdjustedByHand": "بە دەست ڕێکخرا", + "alignedWaveformHint": "دێڕێک ڕابکێشە بۆ جوڵاندنی، یان لێوارەکانی ڕابکێشە بۆ گۆڕینی دەستپێک یان کۆتاییەکەی.", + "playLine": "لێدانی دێڕ", + "undoAdjustment": "پاشگەزبوونەوە لە ڕێکخستن", + "playPause": "لێدان / وەستان", + "playAligned": "هاوشانکراو", + "playAlignedHint": "دێڕی هەڵبژێردراو بە کۆدەکانی کاتی هاوشانکراوی لێبدە", + "apply": "جێبەجێکردن", + "applyHint": "نیشانەی دێڕێک لابدە بۆ پاراستنی کۆدەکانی کاتی ڕەسەنی. دوو کرتە لە دێڕێک بکە بۆ لێدانی. دێڕەکان هەروەها دەتوانرێن لە شەپۆلی دەنگی هاوشانکراودا بجوڵێندرێن یان قەبارەیان بگۆڕدرێت." + }, "beautifyTimeCodesProfile": { "title": "دەستکاری پرۆفیلی جوانکردنی کۆدەکانی کات", "loadPreset": "بارکردنی پێش-ڕێکخستن", "presetDefault": "بنەڕەتی", "presetNetflix": "Netflix", "presetSdi": "SDI", - "general": "گشتی", + "saveAsProfile": "پاشەکەوتکردن وەک پرۆفایل...", + "deleteProfile": "سڕینەوەی پرۆفایل", + "profileName": "ناوی پرۆفایل", + "overwriteProfileX": "پرۆفایلێک بە ناوی \"{0}\" پێشتر هەیە. جێگیری بکرێتەوە؟", + "deleteProfileX": "پرۆفایلی \"{0}\" بسڕدرێتەوە؟", "gap": "بۆشایی", "gapSuffix": "چوارچێوە (هەروەها خانەکانی بۆشایی نا-سفری خوارەوە نوێ دەکاتەوە)", "milliseconds": "میلی چرکە", @@ -1875,6 +2126,10 @@ "bridgeGaps": { "bridgeGapsSmallerThan": "پڕکردنەوەی بۆشاییە بچووکتر لە (میلی چرکە)", "minGap": "کەمترین بۆشایی (میلی چرکە)", + "bridgeGapsSmallerThanFrames": "پڕکردنەوەی بۆشاییەکانی بچووکتر لە (چوارچێوە)", + "minGapFrames": "کەمترین بۆشایی (چوارچێوە)", + "calculateBridgeGapsSmallerThanDotDotDot": "هەژمارکردنی سنووری پڕکردنەوەی بۆشایی لە ڕێژەی چوارچێوەوە...", + "useXMsAsBridgeGapsSmallerThan": "پڕکردنەوەی بۆشاییەکانی بچووکتر لە \"{0}\" میلی چرکە؟", "numberOfSmallGapsBridgedX": "ژمارەی بۆشاییە بچووکە پڕکراوەکان: {0}", "percentFoPrevious": "بۆشایی بۆ پێشوو (%)", "gapChange": "گۆڕینی بۆشایی" @@ -1908,6 +2163,7 @@ "addColor": "زیادکردنی ڕەنگ", "deleteLinesWithSpecificActorsOrStyles": "سڕینەوەی دێڕەکان بە ئەکتەر یان شێوازی دیاریکراو (چەند دانەیەک بە کۆما جیا بکە)", "useSourceStylesIfPossible": "بەکارهێنانی شێوازەکانی سەرچاوە ئەگەر بکرێت", + "keepSourceEmbeddedFonts": "پاراستنی فۆنتە تێخراوەکانی سەرچاوە (footer)", "editStyles": "دەستکاری شێوازەکان", "editProperties": "دەستکاری تایبەتمەندییەکان", "editAttachments": "دەستکاری هاوپێچەکان", @@ -1939,10 +2195,50 @@ "convertColorsToDialogReBreakLines": "دابەشکردنەوەی دێڕەکان", "snapTimeCodesToFramesInfo": "هەموو کاتی دەستپێک و کۆتایی بۆ نزیکترین چوارچێوە دەخڕێنرێتەوە. ڕێژەی چوارچێوە لە فایلێکی ڤیدیۆی هاوناو لەگەڵ فایلی ژێرنووسدا دەخوێنرێتەوە، مەگەر ڕێژەیەکی جێگیر هەڵبژێردرابێت.", "addFolderDotDotDot": "زیادکردنی فۆڵدەر...", + "addFolderRecursiveDotDotDot": "زیادکردنی فۆڵدەر بە ژێرفۆڵدەرەکانەوە...", "selectFolderToConvert": "فۆڵدەرێکی فایلەکان هەڵبژێرە بۆ گۆڕین", "includeSubfolders": "لەخۆگرتنی ژێرفۆڵدەرەکان کە فۆڵدەر زیاد دەکەیت", + "videoFilesWhenAddingFolder": "فایلە ڤیدیۆییەکان (mkv، mp4، ts...) لە کاتی زیادکردنی فۆڵدەردا", + "videoFilesAsk": "پرسیارکردن", + "videoFilesInclude": "زیادکردنی ڕێڕەوەکانی ژێرنووسیان", + "videoFilesSkip": "تێپەڕاندن", + "folderContainsXVideoFiles": "فۆڵدەرەکە {0} فایلی ڤیدیۆیی (mkv، mp4، ts...) تێدایە.\n\nڕێڕەوە تێخراوەکانی ژێرنووسیشیان زیاد بکرێن، یان تەنها فایلەکانی ژێرنووس؟", + "addVideoFiles": "زیادکردنی فایلە ڤیدیۆییەکان", + "skipVideoFiles": "تەنها فایلەکانی ژێرنووس", + "doNotAskAgainVideoFiles": "دووبارە مەپرسە (لە ڕێکخستنەکانی گۆڕینی بەکۆمەڵدا بیگۆڕە)", + "settingsSectionOutput": "دەرچوون", + "settingsSectionAddingFiles": "زیادکردنی فایلەکان", + "settingsSectionImageBasedSubtitles": "ژێرنووسی وێنەیی (OCR)", + "settingsSectionOther": "ئەوانی تر", "keepSourceFileTimestamp": "پاراستنی بەروار/کاتی فایلی سەرچاوە لەسەر فایلە دەرهێنراوەکان", - "scanningFolderX": "پشکنینی {0}..." + "preventSleepWhileConverting": "ڕێگریکردن لە خەوتنی کۆمپیوتەر لە کاتی گۆڕیندا", + "scanningFolderX": "پشکنینی {0}...", + "transportStreamSettings": "ڕێکخستنەکانی Transport Stream", + "transportStreamSettingsDotDotDot": "ڕێکخستنەکانی Transport Stream...", + "transportStreamSettingsInfo": "بۆ ئەو ڕێڕەوانەی ژێرنووس جێبەجێ دەبێت کە لە فایلەکانی Transport Stream (.ts/.m2ts) دەرهێنراون. شوێن و قەبارەی ڤیدیۆ تەنها کاریگەرییان لەسەر ڕێڕەوە وێنەییەکانی DVB هەیە کە بۆ فۆرماتێکی وێنەیی دەرهێنراون.", + "transportStreamOverrideXPosition": "جێگرتنەوەی شوێنی X ی ڕەسەن", + "transportStreamOverrideYPosition": "جێگرتنەوەی شوێنی Y ی ڕەسەن", + "transportStreamOverrideVideoSize": "جێگرتنەوەی قەبارەی ڤیدیۆی ڕەسەن", + "transportStreamBottomMargin": "پەراوێزی خوارەوە", + "transportStreamFileNameEnding": "کۆتایی ناوی فایل", + "transportStreamFileNameEndingInfo": "پێش پاشگری فایل بۆ هەر ڕێڕەوێکی دەرهێنراو زیاد دەکرێت. بەتاڵی بهێڵەرەوە بۆ بەکارهێنانی پاشگری ئاسایی زمان.", + "transportStreamOnlyTeletext": "تەنها teletext", + "transportStreamGetSizeFromVideo": "وەرگرتنی قەبارە لە ڤیدیۆوە...", + "twoLetterLanguageCodeUppercase": "کۆدی زمانی دوو پیتی (پیتی گەورە)", + "threeLetterLanguageCodeUppercase": "کۆدی زمانی سێ پیتی (پیتی گەورە)", + "formatContainsDotDotDot": "فۆرمات لەخۆدەگرێت...", + "extensionIsDotDotDot": "پاشگری فایل بریتییە لە...", + "statusIsError": "دۆخ هەڵەیە", + "forcedTracksOnly": "تەنها ڕێڕەوە ناچارییەکان (forced)", + "xConvertedYFailedInZ": "{0:#,###,##0} گۆڕدران، {1:#,###,##0} سەرکەوتوو نەبوون لە {2}", + "xNotProcessed": "{0:#,###,##0} پرۆسێس نەکران", + "removeUnicodeControlCharactersTitle": "لابردنی پیتە کۆنترۆڵییەکانی Unicode", + "removeUnicodeControlCharactersInfo": "پیتە نادیارەکانی کۆنترۆڵی ئاراستەی Unicode (LRM، RLM، LRE، RLE، PDF، LRO و RLO) لە دەقەکە لادەبات، و بۆشاییە نەشکاوەکان بە بۆشایی ئاسایی جێگیر دەکاتەوە.", + "preset": "پێش-ڕێکخستن", + "savePresetDotDotDot": "پاشەکەوتکردنی پێش-ڕێکخستن...", + "deletePreset": "سڕینەوەی پێش-ڕێکخستن", + "presetName": "ناوی پێش-ڕێکخستن", + "deletePresetX": "پێش-ڕێکخستنی \"{0}\" بسڕدرێتەوە؟" }, "changeCasing": { "fixNames": "چاککردنی ناوەکان", @@ -2027,10 +2323,13 @@ }, "mergeLinesWithSameText": { "maxMsBetweenLines": "زۆرترین میلی چرکە لە نێوان دێڕەکاندا", - "includeIncrementingLines": "لەخۆگرتنی دێڕەکانی دەقی ژمارە بەرزبوو" + "maxFramesBetweenLines": "زۆرترین چوارچێوە لە نێوان دێڕەکاندا", + "includeIncrementingLines": "لەخۆگرتنی دێڕەکانی دەقی ژمارە بەرزبوو", + "includeRollUpCaptions": "لەخۆگرتنی ژێرنووسی roll-up (خلیسکاو)" }, "mergeLinesWithSameTimeCodes": { "maxMsDifference": "زۆرترین جیاوازی (میلی چرکە)", + "maxFramesDifference": "زۆرترین جیاوازی (چوارچێوە)", "makeDialog": "دروستکردنی دیالۆگەکان" }, "netflixCheckAndFix": { @@ -2130,12 +2429,29 @@ "letterboxCustom": "بەرزی دڵخوازی بارەکە", "barHeightPx": "بەرزی بارەکە (پیکسڵ)", "titleSafePercent": "ناوچەی سەلامەتی ناونیشان (%)", + "moveCaptions": "گواستنەوەی ژێرنووسەکان", + "moveCaptionsDotDotDot": "گواستنەوەی ژێرنووسەکان...", + "moveCaptionsTo": "گواستنەوەی ژێرنووسەکان", + "moveCaptionsIntoBars": "بۆ ناو شریتە ڕەشەکانی letterbox (دەرەوەی وێنەکە)", + "moveCaptionsIntoPicture": "ناو وێنەکە (دەرەوەی شریتەکان)", + "moveCaptionsOffset": "دووری لە لێوار (px)", + "letterboxBarHeightXPxYOfZCaptionsWillMove": "بەرزی شریت: {0} px - {1} لە {2} ژێرنووس دەجوڵێن", + "removeFadeInOut": "لابردنی fade in/out", + "removeFadeInOutXLinesRemoved": "{0} دێڕی fade تێکەڵ کران لەگەڵ ئەو دێڕانەی سەر بەوانن", + "removeFadeInOutNothingFound": "هیچ دێڕێکی fade in/out نەدۆزرایەوە", "positionSummary": "{0}×{1} - {2} ژێرنووس - بەرزی بارەکە: {3} پیکسڵ", "xInActivePicture": "{0} لە وێنەکەدا", "xInTopBar": "{0} لە بارە سەرەوەکەدا", "xInBottomBar": "{0} لە بارە خوارەوەکەدا", "noImageSubtitlesLoaded": "هیچ ژێرنووسێکی وێنەیی بارنەکراوە", - "imageBasedFormatNotSupported": "فۆرماتی ژێرنووسی وێنەیی نەدۆزرایەوە/پشتگیری نەکراوە." + "imageBasedFormatNotSupported": "فۆرماتی ژێرنووسی وێنەیی نەدۆزرایەوە/پشتگیری نەکراوە.", + "frameRateDeclaredInFile": "ڕێژەی چوارچێوەی ڕاگەیەندراو لە فایلی Blu-ray sup", + "frameRateDetectedFromTimeCodesX": "ڕێژەی چوارچێوە لە کۆدەکانی کاتی Blu-ray sup دۆزرایەوە (فایلەکە {0} ڕادەگەیەنێت)", + "frameRateFromVideo": "ڕێژەی چوارچێوەی ڤیدیۆ", + "frameRateCurrent": "ڕێژەی چوارچێوەی ئێستا", + "frameRateSetManually": "ڕێژەی چوارچێوە بە دەست دانراوە", + "frameRateUsageInfo": "دەرهێنانەکانی Blu-ray sup، BDN XML، DOST و FCP بەم ڕێژەی چوارچێوەیە دەنووسرێن. هیچ کاتێک ناگۆڕێت - بۆ ئەوە \"گۆڕینی ڕێژەی چوارچێوە\" بەکاربهێنە.", + "useVideoFrameRateXInsteadOfY": "ڕێژەی چوارچێوەی ڤیدیۆکە {0} ە، بەڵام ژێرنووسەکە لەسەر {1} دانراوە.\n\nڕێژەی چوارچێوەی ڤیدیۆکە ({0}) بەکاربهێنرێت؟" }, "removeTextForHearingImpaired": { "interjections": "هاوار و دەنگەکان", @@ -2165,6 +2481,7 @@ "pickFontNameInstalledFonts": "فۆنتە دامەزراوەکان", "pickFontNameCollectedFonts": "فۆنتە کۆکراوەکان", "colorPickerTitle": "هەڵبژاردنی ڕەنگ", + "colorPickerBrightness": "ڕووناکی", "filterLayersTitle": "پاڵاوتنی چینەکان بۆ پیشاندان", "filterLayersHideFromWaveform": "شاردنەوە لە شەپۆل/سپێکترۆگرام", "filterLayersHideFromSubtitleGrid": "شاردنەوە لە خشتەی ژێرنووس", @@ -2215,7 +2532,21 @@ "correctWordsX": "وشە دروستەکان: {0}", "namesX": "ناوەکان: {0}", "addedToDictionaryX": "زیادکراو بۆ فەرهەنگ: {0}", - "doNotShowThisAgain": "ئەم نامەیە دووبارە پیشان مەدە" + "doNotShowThisAgain": "ئەم نامەیە دووبارە پیشان مەدە", + "editUseAlwaysList": "دەستکاری لیستی \"هەمیشە بەکاربهێنە\"...", + "useAlwaysListTitle": "پشکنینی ڕێنووس - لیستی \"هەمیشە بەکاربهێنە\"", + "useAlwaysListHeader": "لیستی \"هەمیشە بەکاربهێنە\"", + "useAlwaysListDescription": "ئەو وشانەی پشکنینی ڕێنووس بە خۆکاری جێگیریان دەکاتەوە، کە بە \"گۆڕینی هەموو\" و \"هەمیشە بەکاربهێنە\" پاشەکەوت کراون. جووتێک تەنها کاتێک بەکاردێت کە وشەکە بە هەڵە نووسرابێت.", + "useAlwaysWord": "وشەی هەڵە نووسراو", + "xPairs": "{0:#,##0} جووت", + "xUnusedPairs": "{0:#,##0} هەرگیز بەکارنەهاتوون", + "useAlwaysUnusedHint": "هەرگیز بەکارنەهاتووە - ئەم وشەیە دروست نووسراوە (یان ناوە یان وشەی بەکارهێنەرە)، بۆیە پشکنینی ڕێنووس هەرگیز نیشانەی ناکات", + "removeUnused": "لابردنی بەکارنەهاتووەکان", + "useAlwaysEmpty": "هێشتا هیچ جووتێکی \"هەمیشە بەکاربهێنە\" بۆ ئەم زمانە نییە", + "useAlwaysEmptyHint": "جووتەکان زیاد دەکرێن کاتێک لە پشکنینی ڕێنووسدا کرتە لە \"گۆڕینی هەموو\" یان \"هەمیشە بەکاربهێنە\" دەکەیت.", + "useAlwaysNoMatch": "هیچ جووتێک لەگەڵ گەڕانەکە ناگونجێت", + "useAlwaysSameWord": "جێگرەوەکە دەبێت جیاواز بێت لە وشەکە.", + "useAlwaysRememberOff": "\"لەبیرکردنی لیستی 'هەمیشە بەکاربهێنە'\" لە ڕێکخستنەکاندا ناچالاکە، بۆیە پشکنینی ڕێنووس ئەم لیستە بەکارناهێنێت و پاشەکەوتی ناکات." }, "video": { "burnIn": { @@ -2228,6 +2559,7 @@ "sampleRate": "ڕێژەی نموونە", "audio": "دەنگ", "preset": "پێش-ڕێکخستن", + "tune": "Tune", "pixelFormat": "فۆرماتی پیکسڵ", "crf": "CRF", "useSource": "بەکارهێنانی سەرچاوە", @@ -2239,7 +2571,9 @@ "outputProperties": "تایبەتمەندییەکانی دەرچوون...", "videoFileSize": "قەبارەی فایلی ڤیدیۆ", "oneBox": "یەک چوارگۆشە", - "logoInfo": "وێنەیەکی PNG هەڵبژێرە و ڕایبکێشە بۆ دانانی لەسەر ڤیدیۆ." + "logoInfo": "وێنەیەکی PNG هەڵبژێرە و ڕایبکێشە بۆ دانانی لەسەر ڤیدیۆ.", + "noSubtitlesGenerateAnyway": "هیچ دێڕێکی ژێرنووس نییە بۆ سووتاندن.\n\nڤیدیۆکە بەبێ ژێرنووس دروست بکرێت؟", + "noSubtitlesInXItemsGenerateAnyway": "{0} بڕگەی بەکۆمەڵ هیچ دێڕێکی ژێرنووسیان نییە بۆ سووتاندن.\n\nئەو ڤیدیۆیانە بەبێ ژێرنووس دروست بکرێن؟" }, "videoTransparent": { "title": "دروستکردنی ڤیدیۆی شەفاف لەگەڵ ژێرنووس" @@ -2250,6 +2584,21 @@ "translateToEnglish": "وەرگێڕان بۆ ئینگلیزی", "transcribing": "دەقکردنەوە...", "transcribingXOfY": "دەقکردنەوەی {0} لە {1}...", + "linesTranscribedXOfYFailedZ": "{0} لە {1} دێڕ دەقکرانەوە - {2} سەرکەوتوو نەبوون یان قسەیان تێدا نەبوو.\n\nدێڕە سەرکەوتوو نەبووەکان دەقی ئێستایان دەپارێزن. دێڕە دەقکراوەکان جێبەجێ بکرێن، یان سەرکەوتوو نەبووەکان دووبارە هەوڵ بدرێنەوە (دەتوانیت سەرەتا بزوێنەر یان ڕێکخستنەکان بگۆڕیت)؟", + "linesTranscribedXOfYCancelled": "دەقکردنەوە دوای {0} لە {1} دێڕ هەڵوەشێنرایەوە.\n\nدێڕە دەقکراوەکان جێبەجێ بکرێن؟ دێڕەکانی تر دەقی ئێستایان دەپارێزن.", + "noLinesTranscribed": "هیچ کام لە {0} دێڕەکە نەتوانران دەقبکرێنەوە.\nتکایە تۆماری ئامرازەکان بپشکنە بۆ وردەکاری.", + "applyTranscribedLines": "جێبەجێکردنی دەقکراوەکان", + "retryFailedLines": "دووبارە هەوڵدانەوەی سەرکەوتوو نەبووەکان", + "isolateSpeech": "جیاکردنەوەی قسە (هێواش)", + "isolateSpeechHint": "پێش دەقکردنەوە مۆسیقا و کاریگەرییە دەنگییەکان لادەبات. دێڕی لەدەستچووی کەمتر و کاتبەندی باشتر دەدات لە دەنگێکدا کە مۆسیقای بەرزی تێدایە، بەڵام نزیکەی هێندەی خودی دەنگەکە کات دەبات لەگەڵ GPU - و چەندین جار زیاتر بەبێ GPU.", + "isolatingSpeech": "جیاکردنەوەی قسە...", + "isolateSpeechFailed": "نەتوانرا قسەکە جیا بکرێتەوە - لەبری ئەوە دەنگی ڕەسەن دەقدەکرێتەوە.", + "detectSpeakers": "ناسینەوەی قسەکەران", + "detectSpeakersHint": "هەر دێڕێک بە قسەکەرەکە دەستپێدەکات، وەک \"(speaker 0)\"، بە بەکارهێنانی diarization ی Sortformer ی NVIDIA (تا 8 قسەکەر). باشترین کار دەکات لەگەڵ ئەو backend انەی دێڕەکان لەو شوێنەدا دابەش دەکەن کە قسەکەر دەگۆڕێت، وەک Parakeet و Cohere.", + "detectSpeakersNeedsNewerCrispAsr": "\"ناسینەوەی قسەکەران\" پێویستی بە Crisp ASR v{0} یان نوێترە، بەڵام v{1} دامەزراوە.\n\nCrisp ASR نوێ بکەرەوە (دوگمەی چەرخەکە لە تەنیشت بزوێنەرەکە) یان \"ناسینەوەی قسەکەران\" ناچالاک بکە.", + "xNeedsNewerCrispAsr": "{0} پێویستی بە Crisp ASR v{1} یان نوێترە، بەڵام v{2} دامەزراوە.\n\nئێستا نوێکردنەوەکە دابگیرێت و دابمەزرێت؟", + "vad": "VAD", + "vadHint": "ناسینەوەی چالاکی دەنگ: تەنها قسەکە دەقدەکرێتەوە، لەو شوێنانەدا دەبڕدرێت کە قسەکەر دەوەستێت.\nخۆکار: Silero، و تەنها بۆ ئەو بزوێنەرانەی پێویستیان پێیەتی (Cohere، Mega، Index-Echo).\nSilero، FireRedVAD، Whisper-VAD، WebRTC: هەمیشە چالاک، بۆ هەموو بزوێنەرێکی Crisp ASR. FireRedVAD وردترینە، Whisper-VAD کەمترین و پاکترین بڕین دەدات بەڵام هێواشترینە.\nIndex-Echo هەمیشە Silero بەکاردەهێنێت. --vad، --vad-model یان --chunk-seconds لە ڕێکخستنە پێشکەوتووەکاندا لە پێشترن.", "inputLanguage": "زمانی داخڵ", "advancedWhisperSettings": "پارامێترە پێشکەوتووەکانی قسە بۆ دەق", "downloadingSpeechToTextEngine": "داگرتنی بزوێنەری قسە بۆ دەق", @@ -2333,6 +2682,19 @@ "videoEncodingSettings": "TTS - ڕێکخستنەکانی کۆدکردنی ڤیدیۆ", "elevenLabsSettings": "TTS - ڕێکخستنەکانی ElevenLabs", "elevenLabsSettingsResetHint": "گەڕاندنەوەی ڕێکخستنەکانی ElevenLabs بۆ بەها بنەڕەتییەکان", + "provider": "دابینکەر", + "openAiCompatibleSettings": "TTS - ڕێکخستنەکانی گونجاو لەگەڵ OpenAI", + "customServerUrl": "URL ی ڕاژەکاری تایبەت", + "customServerUrlHint": "endpoint ی تەواوی قسەی ڕاژەکارێکی گونجاو لەگەڵ OpenAI، بەکاردێت کاتێک دابینکەر \"Custom\" ە، بۆ نموونە http://localhost:8880/v1/audio/speech", + "customModels": "مۆدێلە تایبەتەکان", + "customModelsHint": "ناوی مۆدێلەکان بە کۆما جیاکراونەتەوە، پێشکەش دەکرێن کاتێک دابینکەر \"Custom\" ە", + "customVoices": "دەنگە تایبەتەکان", + "customVoicesHint": "ناوی دەنگەکان بە کۆما جیاکراونەتەوە، لەلایەن دابینکەری \"Custom\" و ئەو مۆدێلانەی OpenRouter بەکاردێن کە هیچ لیستێکی دەنگ بڵاو ناکەنەوە", + "instructions": "ڕێنماییەکان", + "instructionsHint": "شێوازی قسەکردنی ئارەزوومەندانە، بۆ نموونە \"بە دەنگێکی هێمن و گەرم قسە بکە\". تەنها بۆ ئەو مۆدێلانە دەنێردرێت کە پشتگیری دەکەن (نەک tts-1/tts-1-hd)", + "openAiSpeedHint": "خێرایی قسە لە 0.25 تا 4.0 (1.0 ئاساییە). هەموو مۆدێلێک پشتگیری ناکات", + "outputFormat": "فۆرماتی دەرچوون", + "outputFormatHint": "فۆرماتی دەنگ کە لە ڕاژەکار داوا دەکرێت. \"Auto\" داوای MP3 دەکات و بۆ ئەو مۆدێلانەی تەنها PCM دەگەڕێننەوە دەگۆڕێت بۆ PCM (بۆ نموونە Gemini TTS)", "regenerateAudio": "دروستکردنەوەی دەنگ", "autoContinuePlaying": "بەردەوامبوونی خۆکاری لێدان", "playLine": "لێدانی دێڕ", @@ -2341,6 +2703,13 @@ "addingAudioToVideoFileDotDotDot": "زیادکردنی دەنگ بۆ فایلی ڤیدیۆ...", "preparingMergeDotDotDot": "ئامادەکاری تێکەڵکردن...", "importVoiceDotDotDot": "هێنانەناوی دەنگ...", + "renameVoiceDotDotDot": "ناوگۆڕینی دەنگ...", + "renameVoiceTitle": "ناوگۆڕینی دەنگ", + "voiceXCouldNotBeRenamedX": "نەتوانرا ناوی دەنگی '{0}' بگۆڕدرێت: {1}", + "deleteVoiceDotDotDot": "سڕینەوەی دەنگ...", + "deleteVoiceTitle": "سڕینەوەی دەنگ", + "deleteVoiceXQuestion": "دەنگی '{0}' بسڕدرێتەوە؟\n\nتۆمارە سەرچاوەکە و فایلەکانی لە دیسک لادەبرێن.", + "voiceXCouldNotBeDeletedX": "نەتوانرا دەنگی '{0}' بسڕدرێتەوە: {1}", "voiceImportSuccessTitle": "دەنگ هێنرا", "voiceXImported": "دەنگی '{0}' بە سەرکەوتوویی هێنرا", "voiceXCouldNotBeImported": "نەتوانرا دەنگی '{0}' بهێنرێت - بۆ وردەکارییەکان تۆمار ببینە", @@ -2350,6 +2719,49 @@ "dropAudioFileHereToImportVoice": "فایلی دەنگ لێرە دادەبنێ بۆ هێنانەناوی دەنگ", "dropAudioFileHereHint": ".wav یان .mp3", "voiceCloneTranscriptTitle": "دەقی دەنگەکە بنووسە (بۆ کلۆنکردنی دەنگ پێویستە)", + "voiceManagerTitle": "TTS - بەڕێوەبەری دەنگ", + "voiceManagerDotDotDot": "بەڕێوەبەری دەنگ...", + "voiceManagerMenuItem": "بەڕێوەبەری دەنگی TTS...", + "speakFromCurrentLineTitle": "خوێندنەوە لە دێڕی ئێستاوە", + "speakFromCurrentLineDotDotDot": "خوێندنەوە لە دێڕی ئێستاوە...", + "speakFromCurrentLineHint": "دێڕەکان یەک لە دوای یەک بە دەنگی بەرز دەخوێنێتەوە، لە دێڕی ئێستاوە دەستپێدەکات. Escape دابگرە یان کرتە لە دێڕێکی تر بکە بۆ وەستاندن.", + "stopSpeaking": "وەستاندنی خوێندنەوە", + "speakingLineX": "خوێندنەوەی دێڕی {0}... (Escape بۆ وەستاندن)", + "playWithSpeechTitle": "لێدان لەگەڵ قسە", + "playWithSpeechDotDotDot": "لێدان لەگەڵ قسە...", + "playWithSpeechHint": "ڤیدیۆکە لێدەدات و هەر دێڕێک دەخوێنێتەوە کاتێک ڤیدیۆکە دەگاتە ئەو. وەک ئاسایی بوەستێنە و بگەڕێ؛ Escape دابگرە بۆ وەستاندن.", + "lowerVideoVolumeWhileSpeaking": "کەمکردنەوەی دەنگی ڤیدیۆ لە کاتی خوێندنەوەدا", + "pauseVideoWhenSpeechRunsLate": "وەستاندنی ڤیدیۆ کاتێک قسە دەچێتە ناو دێڕی دواتر", + "waitingForSpeechLineX": "دروستکردنی قسە بۆ دێڕی {0}... (Escape بۆ وەستاندن)", + "voiceKindClone": "دەنگی کلۆنکراو", + "voiceKindPreset": "ناوخۆیی", + "voiceKindModel": "مۆدێلی دەنگ", + "voiceKindOnline": "ئۆنلاین", + "transcript": "دەقی نووسراو", + "transcriptRequiredHint": "پێویستە - بزوێنەرەکە لە کاتی کلۆنکردندا ئەم دەقە نووسراوەی تۆمارەکە (ref-text) دەخوێنێتەوە", + "transcriptOptionalHint": "ئارەزوومەندانە - دەقێکی نووسراوی وردی تۆمارەکە کلۆنکردن باشتر دەکات", + "saveTranscript": "پاشەکەوتکردنی دەقی نووسراو", + "searchVoices": "گەڕان لە دەنگەکان", + "loadingVoicesDotDotDot": "بارکردنی دەنگەکان...", + "noVoicesForEngine": "هیچ دەنگێک نییە - تۆمارێک هێنانەناو بکە یان پاکێجێکی دەنگ دابگرە", + "noVoicesMatchFilter": "هیچ دەنگێک لەگەڵ گەڕانەکە ناگونجێت", + "copyVoiceTo": "کۆپیکردنی دەنگ بۆ", + "copyVoiceToDotDotDot": "کۆپیکردنی دەنگ بۆ بزوێنەر...", + "copyingVoiceXToYDotDotDot": "کۆپیکردنی دەنگی '{0}' بۆ {1}...", + "voiceXCopiedToY": "دەنگی '{0}' کۆپی کرا بۆ {1}", + "voiceXCouldNotBeCopiedToY": "نەتوانرا دەنگی '{0}' کۆپی بکرێت بۆ {1} - بۆ وردەکاری سەیری تۆمارەکە بکە", + "voiceXAlreadyExistsInYContinue": "دەنگێک بە ناوی '{0}' پێشتر لە {1} هەیە.\n\nهەر کۆپی بکرێت؟", + "openVoicesFolder": "کردنەوەی فۆڵدەری دەنگەکان", + "downloadVoicePacksDotDotDot": "داگرتنی پاکێجەکانی دەنگ...", + "downloadVoicePacksTitle": "TTS - داگرتنی پاکێجەکانی دەنگ", + "voicePackIntro": "پاکێجەکانی دەنگ کۆمەڵێک تۆماری کورت و پاکن لەگەڵ دەقی نووسراو، ئامادەن بۆ کلۆنکردن. هەر پاکێجێک بۆ ناو بزوێنەری هەڵبژێردراو هێنانەناو دەکرێت (ئەو دەنگانەی پێشتر هەن تێدەپەڕێندرێن) و ATTRIBUTION.txt ێکی تێدایە لەگەڵ مۆڵەت و سەرچاوەکانی.", + "voicePackDetailsXVoicesYSizeZLicense": "{0} دەنگ · {1} · {2}", + "installToEngine": "دامەزراندن بۆ بزوێنەر", + "downloadAndInstall": "داگرتن و دامەزراندن", + "selectAtLeastOneVoicePack": "لانیکەم یەک پاکێجی دەنگ هەڵبژێرە", + "downloadingXDotDotDot": "داگرتنی {0}...", + "installingXDotDotDot": "دامەزراندنی {0}...", + "xVoicesInstalledYSkipped": "{0} دەنگ دامەزران ({1} پێشتر هەبوون)", "useSpeechToTextDotDotDot": "بەکارهێنانی قسە بۆ دەق...", "autoCastMenuItem": "دۆزینەوەی دەنگەکان لە ڤیدیۆ و کلۆنکردن...", "autoCastSpeakersTitle": "دەنگە دۆزراوەکان لە ڤیدیۆدا", @@ -2368,7 +2780,7 @@ "cloneVoicePerLinePreparing": "وەرگرتنی دەنگی هەر دێڕێک لە ڤیدیۆکەوە...", "cloneVoicePerLineNeedsVideo": "کلۆنکردنی دەنگی هەر دێڕێک پێویستی بە ئەو ڤیدیۆیە هەیە کە ژێرنووسەکە سەری دەگرێت. ڤیدیۆکە بکەوە و دووبارە تاقی بکەوە.", "cloneVoicePerLineNoClips": "هیچ دەنگێک نەتوانرا لە ڤیدیۆکەوە وەربگیرێت، بۆیە هیچ شتێک نییە بۆ کلۆنکردن. پشکنین بکە ڤیدیۆکە تراکێکی دەنگی هەیە.", - "cloneVoicePerLineNeedsOriginalSubtitleX": "{0} تەنها کاتێک دەتوانێت دێڕێک کۆپی بکات کە بزانێت لەو دێڕەدا لە ڤیدیۆکەدا چی دەوترێت، کە لە ژێرنووسی زمانی ڕەسەنەوە دێت. هیچ ژێرنووسێکی ڕەسەن بارنەکراوە، بۆیە هەموو دێڕەکان بە یەکەم دەنگی هاوردەکراو دەوترێن لە جیاتی ئەو دەنگەی لە ڤیدیۆکەدایە.\n\nژێرنووسی ڕەسەن لەتەنیشت وەرگێڕانەکە بکەرەوە (فایل > کردنەوەی ڕەسەن...)، پاشان دووبارە دروستی بکە.", + "cloneVoicePerLineTranscribeClipsXYZ": "{0} تەنها کاتێک دەتوانێت دێڕێک کلۆن بکات کە بزانێت لەو دێڕەدا چی لە ڤیدیۆکەدا دەوترێت. ئەوە بۆ {1} لە {2} دێڕ نەزانراوە - بە ئاسایی لە ژێرنووسی زمانی ڕەسەنەوە دێت (فایل > کردنەوەی ڕەسەن...).\n\nئێستا ئەو دێڕانە لە ڤیدیۆکەوە بە قسە-بۆ-دەق دەقبکرێنەوە؟\n\nبەڵێ: بزوێنەر و ئەو زمانەی لە ڤیدیۆکەدا قسەی پێدەکرێت هەڵبژێرە، و کلیپە سەرچاوەکان پێش دروستکردنی قسەکە دەقدەکرێنەوە.\nنەخێر: هەر دروستی بکە - ئەو دێڕانە بە دەنگێکی ئاسایی دەخوێندرێنەوە لەبری دەنگەکەی ناو ڤیدیۆکە.", "voiceCloneConsentTitle": "کلۆنکردنی دەنگ - پێش دەستپێکردن", "voiceCloneConsentHeader": "دەچیت دەنگێک کلۆن بکەیت", "voiceCloneConsentIntro": "کلۆنکردن دەنگی کەسێکی ڕاستەقینە لەبەردەگرێتەوە. ئەمە لە زۆربەی شوێنەکاندا یاسای خۆی هەیە، و لە یەکێتیی ئەورووپادا بەرهەستێکی یاسایی دێتە پاڵ تۆ، نەک Subtitle Edit. تکایە ئەمە جارێک بخوێنەوە.", @@ -2385,10 +2797,29 @@ "advancedTtsSettings": "ڕێکخستنە پێشکەوتووەکانی TTS", "advancedTtsAudioProcessing": "پرۆسێسکردنی دەنگ", "advancedTtsOutput": "دەرچوون", + "advancedTtsVoiceCloning": "کلۆنکردنی دەنگ", + "cleanCloneReferences": "پاککردنەوەی سەرچاوەکانی کلۆنی دەنگ (Sidon)", + "cleanCloneReferencesDescription": "مۆسیقا، ژاوەژاو و دەنگدانەوە لەو دەنگە لادەبات کە دەنگێکی لێ کلۆن دەکرێت: کلیپە وەرگیراوەکان لە ڤیدیۆکە و ئەو تۆمارانەی لە بەڕێوەبەری دەنگدا هێنانەناو کراون. مۆدێلی گەڕاندنەوەی قسەی Sidon لەسەر audio.cpp بەکاردەهێنێت (940 MB داگرتن). دەنگی کلۆنکراو چیتر پاشبنەما کۆپی ناکات، بەڵام قسەی زۆر نزم یان یەکتربڕ لەوانەیە خنکاو دەربچێت.", + "cleaningCloneReferencesDotDotDot": "پاککردنەوەی سەرچاوەکانی کلۆنی دەنگ...", + "downloadSidonTitle": "Sidon دابگیرێت؟", + "downloadSidonQuestionX": "پاککردنەوەی سەرچاوەکانی کلۆنی دەنگ پێویستی بە مۆدێلی گەڕاندنەوەی قسەی Sidon ({0}) هەیە.\n\nئێستا دابگیرێت؟ بەبێ ئەو، سەرچاوەکان وەک خۆیان بەکاردێن.", "proAudioPostProcessing": "پرۆسێسی دوایی دەنگی پڕۆفیشناڵ", "proAudioPostProcessingDescription": "EQ، دەروازەی دەنگ، کۆمپرێشن، ئاساییکردنەوەی توندی (-16 LUFS) و فەیدێکی کورت لە ناوچوون/دەرچوون بۆ هەر پارچەیەک زیاد دەکات.", + "generatingSpeechSegmentXOfY": "دروستکردنی قسە: پارچەی {0} لە {1}", + "adjustingSpeedSegmentXOfY": "ڕێکخستنی خێرایی: پارچەی {0} لە {1}", + "mergingAudioSegmentXOfY": "تێکەڵکردنی دەنگ: پارچەی {0} لە {1}", + "postProcessingSegmentXOfY": "پرۆسێسی دوایی: پارچەی {0} لە {1}", "audioDucking": "نزمکردنی دەنگ", "audioDuckingDescription": "دەنگی ڤیدیۆ ڕەسەن کەم دەکاتەوە و قسەکە تێکەڵ دەکات لەسەری، بۆ ئەوەی تراکە ڕەسەنەکە بە کەمی بمێنێتەوە. تەنها کاتێک جێبەجێ دەبێت کە قسەکە زیاد دەکرێت بۆ فایلی ڤیدیۆ.", + "removeOriginalSpeech": "لابردنی قسەی ڕەسەن (هێواش)", + "removeOriginalSpeechDescription": "مۆسیقا و کاریگەرییە دەنگییەکانی ڤیدیۆی ڕەسەن دەپارێزێت بەڵام قسەکەی لادەبات، و قسە نوێیەکە بەسەریدا تێکەڵ دەکات. نزیکەی هێندەی خودی ڤیدیۆکە کات دەبات لەگەڵ GPU، و چەندین جار زیاتر بەبێ GPU. کاتێک audio ducking چالاکە، ئاستی دەنگەکەی لەسەر مۆسیقا و کاریگەرییەکان جێبەجێ دەبێت. تەنها کاتێک جێبەجێ دەبێت کە قسەکە بۆ فایلی ڤیدیۆکە زیاد دەکرێت.", + "removingOriginalSpeech": "لابردنی قسەی ڕەسەن...", + "removeOriginalSpeechFailed": "نەتوانرا قسەی ڕەسەن لابدرێت - قسەکە بەبێ ئەوە بۆ ڤیدیۆکە زیادکرا.", + "speechRemovalModel": "مۆدێل", + "speechRemovalModelDescription": "باشترین کوالیتی زۆرترینی مۆسیقا و کاریگەرییە دەنگییەکان دەپارێزێت. خێرا نزیکەی سێ جار خێراترە، لەگەڵ لەدەستدانی کەمێک زیاتری مۆسیقا.", + "speechRemovalBestQuality": "باشترین کوالیتی (Mel-Band RoFormer)", + "speechRemovalFast": "خێرا (HTDemucs)", + "videoTruncatedWarning": "وێنەی ناو فایلە ڤیدیۆ نوێیەکە دوای {0} دەوەستێت، بەڵام ڤیدیۆی ڕەسەن {1} درێژە - وێنەکە دەچەقێت لە کاتێکدا دەنگەکە بەردەوام دەبێت.\n\nفایلەکە هێڵدرایەوە. سەیری error-log.txt بکە لە فۆڵدەری داتای Subtitle Edit بۆ دەرچوونی ffmpeg.", "originalVolumePercent": "ئاستی دەنگی ڕەسەن %", "vadSilenceCompression": "کۆمپرێکردنی بێدەنگی VAD", "vadSilenceCompressionDescription": "وەستانەکان لە نێوان وشەکاندا کورت دەکاتەوە لەبری خێراکردنی قسەکە، بۆ ئەوەی پارچەکە بگونجێت بەبێ هیچ زیانی کوالیتی.", @@ -2471,7 +2902,9 @@ "timeCodeFormatColon": "فۆرماتی کۆدی کات:", "shotChangesList": "پێرستی گۆڕانی دیمەنەکان", "shotChangesClearQuestion": "دڵنیایت لە پاککردنەوەی هەموو گۆڕانی دیمەنەکان؟", - "deleteSelectedShotChangeQuestion": "دڵنیایت لە سڕینەوەی گۆڕانی دیمەنی هەڵبژێردراو؟" + "deleteSelectedShotChangeQuestion": "دڵنیایت لە سڕینەوەی گۆڕانی دیمەنی هەڵبژێردراو؟", + "shotChanges": "گۆڕانی دیمەنەکان", + "exportShotChanges": "دەرهێنانی گۆڕانی دیمەنەکان" }, "chapters": { "title": "بەشەکان", @@ -2501,7 +2934,6 @@ "scaleTimesDescription": "کاتی هەموو بەشێک لە ڕێژەیەکی چوارچێوەوە بۆ ئەوی تر گۆڕە.", "fromFrameRate": "لە", "toFrameRate": "بۆ", - "apply": "جێبەجێکردن", "writeToVideo": "نووسین بۆ ڤیدیۆ...", "writeToVideoTitle": "نووسینی بەشەکان بۆ ڤیدیۆ", "writeToVideoDescription": "بەشەکان دەنووسرێن بەناو کۆپییەک لە فایلی ڤیدیۆدا. هیچ شتێک دووبارە کۆد ناکرێت، بۆیە خێرا و بێ زیانە.", @@ -2511,7 +2943,6 @@ "writtenToX": "بەشەکان نووسران بۆ {0}", "unableToWriteChapters": "نەتوانرا بەشەکان بنووسرێن بۆ فایلی ڤیدیۆ.", "chaptersSavedToX": "بەشەکان پاشەکەوتکران لە {0}", - "noVideoLoaded": "هیچ ڤیدیۆیەک بارنەکراوە", "emptyListCallToAction": "هێشتا هیچ بەشێک نییە - یەکێک زیاد بکە لە شوێنی ڤیدیۆدا، یان لە ڤیدیۆ یان فایلێکەوە بهێنە." }, "videoOcr": { @@ -2536,10 +2967,10 @@ "runningOcrXY": "جێبەجێکردنی OCR... {0}/{1}", "noLinesFoundTitle": "هیچ ژێرنووسێک نەدۆزرایەوە", "noLinesFoundMessage": "هیچ دەقێک لە ناوچەی سکاندا نەدۆزرایەوە - هەوڵبدە ناوچەی سکان، بزوێنەر یان کەمترین ڕووناکی ڕێک بخەیت.", + "ocrStoppedEarlyMessage": "بزوێنەری OCR پێش خوێندنەوەی هەموو چوارچێوەکان وەستا، بۆیە ئەو دێڕانەی تا ئێستا دۆزراونەتەوە دەهێڵدرێنەوە و باقییەکەی ونن.{0}{0}{1}", "linesFoundX": "{0} دێڕ دۆزرانەوە", "refiningTimingXY": "وردکردنەوەی کاتبەندی... {0}/{1}", "fixOcrErrors": "چاککردنی هەڵەکانی OCR", - "dictionary": "فەرهەنگ", "editLineX": "دەستکاری دێڕی {0}", "downloadDictionaryHint": "داگرتنی فەرهەنگی پشکنینی ڕێنووس...", "framesPerSecondHint": "چەند چوارچێوە لە چرکەیەکدا پشکنراون. 5 بۆ ژێرنووسی ئاسایی بەسە؛ بەرزتر دێڕە زۆر کورتەکان دەگرێتەوە و گرووپکردن توندتر دەکات، بەڵام کاتی OCR زیاتر دەبات.", @@ -2556,7 +2987,58 @@ "testOcr": "تاقیکردنەوەی چوارچێوەی ئێستا", "testOcrRunning": "تاقیکردنەوەی OCR لەسەر چوارچێوەی ئێستا...", "testOcrResultX": "ئەنجامی تاقیکردنەوە: {0}", - "testOcrNoTextFound": "تاقیکردنەوە: هیچ دەقێک لە ناوچەی سکاندا نەدۆزرایەوە" + "testOcrNoTextFound": "تاقیکردنەوە: هیچ دەقێک لە ناوچەی سکاندا نەدۆزرایەوە", + "engineSettings": "ڕێکخستنەکانی بزوێنەری OCR", + "engineBuiltIn": "ناوخۆیی لە سیستەمی کارپێکردندا", + "engineExternalServer": "ڕاژەکاری دەرەکی (لە دەرەوەی Subtitle Edit دامەزراوە و بەڕێوە دەبرێت)", + "engineCloudApi": "Cloud API (پێویستی بە کلیلی API هەیە)", + "engineNothingToInstall": "هیچ شتێک بۆ دامەزراندن نییە", + "engineModelsMissing": "بزوێنەر دامەزراوە، مۆدێلەکان ونن", + "website": "ماڵپەڕ" + }, + "backgroundMusic": { + "title": "دروستکردنی مۆسیقای پاشبنەما", + "generateBackgroundMusicDotDotDot": "دروستکردنی مۆسیقای پاشبنەما...", + "preset": "پێش-ڕێکخستن", + "prompt": "پرۆمپت", + "tempoBpm": "تێمپۆ (BPM)", + "generateSeconds": "دروستکردن (چرکە)", + "seed": "تۆو (Seed)", + "randomSeed": "هەڕەمەکی", + "outputLength": "درێژی دەرچوون", + "outputLengthVideoX": "{0} (درێژی ڤیدیۆ - مۆسیقاکە بێ پچڕان دووبارە دەبێتەوە)", + "musicVolumePercent": "ئاستی دەنگی مۆسیقا (%)", + "removeExistingAudioTracks": "لابردنی ڕێڕەوە دەنگییە هەبووەکان", + "originalAudioVolumePercent": "ئاستی دەنگی ڕەسەن (%)", + "saveAudioDotDotDot": "پاشەکەوتکردنی دەنگ...", + "addToVideoDotDotDot": "زیادکردن بۆ ڤیدیۆ...", + "engineInfo": "ACE-Step 1.5 Turbo لە ڕێی audio.cpp (مۆڵەتی MIT) - بە ناوخۆیی کاردەکات", + "presetCooking": "چێشتلێنان / نانکردن", + "presetTech": "تەکنەلۆژیا / هەڵسەنگاندنی ئامێر", + "presetForest": "دارستان / سروشت", + "presetRelaxingPiano": "پیانۆی ئارامبەخش", + "presetLofiVlog": "ڤلۆگی Lo-fi", + "presetCorporate": "کۆمپانیا / ڕوونکردنەوە", + "loadingModel": "بارکردنی مۆدێل...", + "composing": "دانانی مۆسیقا...", + "generatingAudio": "دروستکردنی دەنگ...", + "decodingAudio": "کۆدکردنەوەی دەنگ...", + "creatingLoop": "دروستکردنی دووبارەبوونەوەی بێ پچڕان...", + "resultLoopXYZ": "دووبارەبوونەوە: {0} لە {1} BPM، گونجانی جومگە {2}%", + "resultNoLoopX": "مۆسیقا ئامادەیە: {0}", + "addingMusicToVideo": "زیادکردنی مۆسیقا بۆ ڤیدیۆ...", + "downloadModelTitle": "مۆدێلی مۆسیقا دابگیرێت؟", + "downloadModelQuestionX": "دروستکردنی مۆسیقا پێویستی بە مۆدێلی ACE-Step 1.5 ({0}) هەیە.\n\nمۆدێلەکە دابگیرێت؟", + "downloadingModel": "داگرتنی مۆدێلی ACE-Step 1.5...", + "saveAudioTitle": "پاشەکەوتکردنی مۆسیقای پاشبنەما", + "audioFileSavedX": "فایلی دەنگ پاشەکەوتکرا: {0}", + "unableToGenerateMusic": "نەتوانرا مۆسیقا دروست بکرێت", + "unableToAddMusicToVideo": "نەتوانرا مۆسیقا بۆ ڤیدیۆ زیاد بکرێت", + "outputLengthTextToSpeech": "شوێن قسە دروستکراوەکە دەکەوێت - مۆسیقاکە بێ پچڕان دووبارە دەبێتەوە و کاتێک کەسێک قسە دەکات نزم دەبێتەوە", + "addBackgroundMusic": "زیادکردنی مۆسیقای پاشبنەما", + "addBackgroundMusicHint": "مۆسیقا بە ناوخۆیی بە ACE-Step 1.5 دروست بکە و لە ژێر قسەکەدا تێکەڵی بکە - کاتێک کەسێک قسە دەکات نزم دەبێتەوە", + "generatingBackgroundMusicDotDotDot": "دروستکردنی مۆسیقای پاشبنەما...", + "mixingBackgroundMusicDotDotDot": "تێکەڵکردنی مۆسیقای پاشبنەما..." }, "goToVideoPosition": "چوون بۆ شوێنی ڤیدیۆ", "goToVideoPositionDotDotDot": "چوون بۆ شوێنی ڤیدیۆ...", @@ -2567,10 +3049,47 @@ "openSecondarySubtitleOnVideoPlayerDotDotDot": "کردنەوەی فایلی ژێرنووسی دووەم...", "openSecondarySubtitleOnVideoPlayer": "فایلی ژێرنووسی دووەم (لەسەر لێدەری ڤیدیۆ)", "removeSecondarySubtitleOnVideoPlayer": "لابردنی فایلی ژێرنووسی دووەم", + "editSecondarySubtitleSettings": "دەستکاری ڕێکخستنەکانی ژێرنووسی دووەم", + "editSecondarySubtitleSettingsDotDotDot": "دەستکاری ڕێکخستنەکانی ژێرنووسی دووەم...", + "secondarySubtitleRememberSettings": "ئەم ڕێکخستنانە لەبیر بکە", + "secondarySubtitleDoNotShowAgain": "ئەم دیالۆگە دووبارە پیشان مەدە", + "secondarySubtitleJustifyLines": "ڕێکخستنی دێڕەکان (justify)", "openRecentVideo": "کردنەوەی ڤیدیۆی دوایی", "clearRecentVideos": "پاککردنەوەی ڤیدیۆ دوایینەکان", "cutVideoTitle": "بڕینی ڤیدیۆ", "cutVideoDotDotDot": "بڕینی ڤیدیۆ...", + "remuxVideoTitle": "Remux ی ڤیدیۆ", + "remuxVideoDotDotDot": "Remux ی ڤیدیۆ...", + "remuxVideoInputVideo": "ڤیدیۆی هاتوو:", + "remuxVideoMultipleTracksRequiresMkv": "چەند ڕێڕەوێکی دەنگ یان ژێرنووس پێویستیان بە کۆنتەینەری MKV هەیە. فۆرماتی دەرچوون بە خۆکاری گۆڕدرا بۆ .mkv.", + "remuxVideoOutputFormat": "فۆرماتی دەرچوون:", + "remuxVideoOutputFile": "فایلی دەرچوون:", + "remuxVideoRemuxing": "Remux کردنی ڤیدیۆ و دەنگ...", + "remuxVideoFinalizing": "تەواوکردنی فایلی دەرچوون (گواستنەوەی ئیندێکس بۆ سەرەتا)...", + "remuxVideoCompleted": "ڤیدیۆ بە سەرکەوتوویی remux کرا.", + "remuxVideoFailed": "Remux کردنی ڤیدیۆ سەرکەوتوو نەبوو.", + "remuxVideoPleaseSelectBoth": "تکایە هەردوو فایلی ڤیدیۆ و دەنگ هەڵبژێرە.", + "remuxVideoAssRequiresMkv": "ژێرنووسەکانی ASS/SSA پێویستیان بە کۆنتەینەری MKV هەیە بۆ پاراستنی هەموو فۆرمات و شێوازەکان.", + "remuxVideoSccRequiresMov": "ژێرنووسەکانی Scenarist (.scc) وەک closed captions ی CEA-608 تێدەخرێن، کە پێویستیان بە کۆنتەینەری MOV یان MPG هەیە. فۆرماتی دەرچوون بە خۆکاری گۆڕدرا بۆ .mov.", + "remuxVideoSccNotWithWebM": "closed captions ی Scenarist (.scc) پێویستیان بە کۆنتەینەری MOV هەیە، کە ناتوانێت ڤیدیۆی VP9/AV1 ی فایلێکی .webm هەڵبگرێت. فایلی .scc لابدە یان بیگۆڕە بۆ فۆرماتێکی تری ژێرنووس.", + "remuxVideoMpgMaxTwoCaptionTracks": "فایلێکی .mpg زۆرترین دوو ڕێڕەوی closed caption (CC1 و CC3) هەڵدەگرێت - فایلەکانی ژێرنووس لابدە تا یەک یان دوو بمێننەوە.", + "remuxVideoMccRequiresMpg": "ژێرنووسەکانی MacCaption (.mcc) وەک closed captions ی CEA-608/708 لە ڤیدیۆی MPEG-2 دا تێدەخرێن، کە پێویستی بە کۆنتەینەری MPG هەیە. فۆرماتی دەرچوون بە خۆکاری گۆڕدرا بۆ .mpg.", + "remuxVideoMpgReencodeVideoX": "closed captions لە فایلێکی .mpg دا پێویستیان بە ڤیدیۆی MPEG-2 هەیە، و ڤیدیۆکە {0} ە. ڤیدیۆکە دووبارە بۆ MPEG-2 کۆد بکرێتەوە؟ ئەمە زیاتر کات دەبات و کەمێک کوالیتی دادەبەزێنێت.", + "remuxVideoFileHasNoAudioX": "\"{0}\" هیچ ڕێڕەوێکی دەنگی نییە، بۆیە ناتوانرێت وەک سەرچاوەی دەنگ بەکاربهێنرێت.", + "remuxVideoAddingClosedCaptions": "زیادکردنی closed captions...", + "remuxVideoSelectAudioTrack": "هەڵبژاردنی ڕێڕەوی دەنگ", + "remuxVideoSelectAudioTrackFor": "هەڵبژاردنی ڕێڕەوی دەنگ بۆ {0}", + "remuxVideoSelectAudioTrackPrompt": "فایلی '{0}' {1} ڕێڕەوی دەنگی تێدایە. تکایە ئەو ڕێڕەوە دەنگییە هەڵبژێرە کە remux دەکرێت:", + "remuxVideoVideoFile": "ڤیدیۆ", + "remuxVideoAudioFile": "دەنگ", + "remuxVideoAudioFiles": "فایلە دەنگییەکان:", + "remuxVideoSubtitleFiles": "فایلەکانی ژێرنووس (ئارەزوومەندانە، ژێرنووسی نەرم):", + "remuxVideoSelectAudioTrackDotDotDot": "هەڵبژاردنی ڕێڕەوی دەنگ...", + "remuxVideoFilesX": "{0} فایل", + "remuxVideoMixAudio": "تێکەڵکردنی دەنگ بۆ یەک ڕێڕەو", + "remuxVideoVolumePercent": "ئاستی دەنگ %:", + "remuxVideoVolumeX": "ئاستی دەنگ {0}%", + "remuxVideoFastStart": "باشکردن بۆ پەخشی وێب (گواستنەوەی ئیندێکس بۆ پێشەوە - هێواشتر)", "embedSubtitlesDotDotDot": "زیادکردن/لابردنی ژێرنووسە نوقمکراوەکان...", "generateTimeCodes": "دروستکردنی کۆدەکانی کات", "checkeredImage": "وێنەی خشتەیی", @@ -2580,6 +3099,34 @@ "cutVideoSplitSegments": "پاشەکەوتکردنی پارچەکان بە جیا", "cutVideoAlsoCutSubtitle": "بڕینی ژێرنووسیش", "cutVideoSubtitleFileGeneratedX": "فایلی ژێرنووس دروستکرا: {0}", + "cutVideoTransition": "گواستنەوە لە نێوان پارچەکاندا", + "cutVideoTransitionDurationSeconds": "ماوە (چرکە)", + "cutVideoFadeInSeconds": "دەرکەوتن لە ڕەشەوە لە سەرەتادا (چرکە)", + "cutVideoFadeOutSeconds": "ونبوون بۆ ڕەش لە کۆتاییدا (چرکە)", + "cutVideoPreviewTransition": "پێشبینینی گواستنەوە", + "cutVideoPreviewTransitionTitle": "پێشبینینی گواستنەوە", + "cutVideoPreviewNeedsJoin": "هیچ لکاندنێک نییە بۆ پێشبینین - لانیکەم دوو پارچە زیاد بکە بۆ تێکەڵکردن، یان پارچەیەک بۆ بڕینی لە ناوەڕاستدا.", + "cutVideoTransitionInfoX": "هەر گواستنەوەیەک بەسەر ئەو بەشانەدا دەکەوێت کە دەیانلکێنێت، بۆیە دەرچوون بۆ هەر لکاندنێک {0} چرکە کورتتر دەبێت. ژێرنووسێکی بڕاو کاتەکانی بە گونجاوی دووبارە ڕێکدەخرێن.", + "cutVideoTransitionFade": "کاڵبوونەوە", + "cutVideoTransitionFadeBlack": "کاڵبوونەوە بە ڕەشدا", + "cutVideoTransitionFadeWhite": "کاڵبوونەوە بە سپیدا", + "cutVideoTransitionFadeGrays": "کاڵبوونەوە بە خۆڵەمێشیدا", + "cutVideoTransitionDissolve": "توانەوە", + "cutVideoTransitionWipeLeft": "سڕینەوە بۆ چەپ", + "cutVideoTransitionWipeRight": "سڕینەوە بۆ ڕاست", + "cutVideoTransitionWipeUp": "سڕینەوە بۆ سەرەوە", + "cutVideoTransitionWipeDown": "سڕینەوە بۆ خوارەوە", + "cutVideoTransitionSlideLeft": "خلیسکان بۆ چەپ", + "cutVideoTransitionSlideRight": "خلیسکان بۆ ڕاست", + "cutVideoTransitionSlideUp": "خلیسکان بۆ سەرەوە", + "cutVideoTransitionSlideDown": "خلیسکان بۆ خوارەوە", + "cutVideoTransitionSmoothLeft": "نەرم بۆ چەپ", + "cutVideoTransitionSmoothRight": "نەرم بۆ ڕاست", + "cutVideoTransitionCircleOpen": "کردنەوەی بازنەیی", + "cutVideoTransitionCircleClose": "داخستنی بازنەیی", + "cutVideoTransitionRadial": "تیشکی", + "cutVideoTransitionPixelize": "پیکسڵکردن", + "cutVideoTransitionBlur": "لێڵکردن", "importCurrentSubtitle": "هێنانەناوی ژێرنووسی ئێستا", "addRemoveEmbeddedSubtitlesTitle": "زیادکردن/لابردنی ژێرنووسە نوقمکراوەکان", "addRemoveEmbeddedSubtitlesMp4Title": "زیادکردن/لابردنی ژێرنووسە نوقمکراوەکان (MP4)", @@ -2595,6 +3142,7 @@ "embeddedTrackGeneratingVideoXY": "دروستکردنی ڤیدیۆ... {0}% {1}", "embeddedTrackGeneratingVideo": "دروستکردنی ڤیدیۆ...", "mp4FilesFilter": "فایلەکانی MP4", + "embeddedTracksPickVideoHint": "فایلێکی ڤیدیۆ هەڵبژێرە، یان یەکێک ئێرە دابنێ، بۆ بینینی ڕێڕەوەکانی ژێرنووسی.", "viewMatroskaTrackX": "بینینی تراکی Matroska - {0}", "resolutionSeparator": "x", "openFromUrlTitle": "کردنەوەی فایلی ڤیدیۆ لە URL ەوە", @@ -2641,12 +3189,15 @@ "reEncodeGeneratingDone": "دروستکردن تەواوبوو", "reEncodeGeneratedFilesX": "فایلە دروستکراوەکان ({0}):", "reEncodeFfmpegParameters": "پارامێترەکانی ffmpeg", + "analyzingVideoX": "شیکردنەوەی ڤیدیۆ... {0}% {1}", + "analyzingVideoXofY": "شیکردنەوەی ڤیدیۆ {0}/{1}... {2}% {3}", + "backgroundImageFileNotSelected": "فایلی وێنەی پاشبنەما هەڵنەبژێردراوە", + "backgroundImageFileDoesNotExistX": "فایلی وێنەی پاشبنەما بوونی نییە: {0}", "voices": "دەنگەکان", "presets": "پێش-ڕێکخستنەکان", "talkerX": "قسەکەری {0}", "baseModel": "مۆدێلی بنکە", "turboModel": "مۆدێلی توربۆ", - "ttsUpdateX": "نوێکردنەوەی {0}", "ttsEngineNotInstalled": "{0} دامەزراو نییە", "ttsEngineUpdateAvailable": "{0} - نوێکردنەوە بەردەستە", "ttsEngineTooOldUpdateRequired": "{0} زۆر کۆنە - نوێکردنەوە پێویستە", @@ -2669,9 +3220,17 @@ "cloneVoiceTo": "کلۆنکردنی دەنگ بۆ", "cloneVoiceNameTitle": "ناوی دەنگی نوێ", "cloneVoiceExtractFailed": "نەتوانرا دەنگ بۆ دێڕی هەڵبژێردراو دەربهێنرێت.", + "lineXHasNoDuration": "دێڕی {0} هیچ ماوەیەکی نییە (کاتی کۆتایی دوای کاتی دەستپێک نییە)، بۆیە هیچ دەنگێک نییە بۆ دەرهێنان.", + "audioClipsSkippedLinesX": "هیچ دەنگێک بۆ ئەم دێڕانە دەرنەهێنرا، بۆیە تێپەڕێندران: {0}", "toggleShotChange": "گۆڕینی گۆڕانی دیمەن", "resetWaveformZoomAndSpeed": "گەڕاندنەوەی زووم و خێرایی شەپۆل", "showOriginalSubtitle": "پیشاندانی ژێرنووسی ڕەسەن", + "timelineGroupTracksBy": "گرووپکردنی ڕێڕەوەکانی ژێرنووس بەپێی", + "timelineShowThumbnails": "پیشاندانی وێنە بچووکەکانی ڤیدیۆ", + "showSpeechOnly": "تەنها قسە پیشان بدە (دروستکردنی هێواشە)", + "isolatingSpeechForWaveformX": "جیاکردنەوەی قسە بۆ شەپۆلی دەنگ... {0}", + "isolatingSpeechForWaveformFailed": "نەتوانرا قسە بۆ شەپۆلی دەنگ جیا بکرێتەوە - سەیری تۆماری ئامرازەکان بکە", + "speechOnlyWaveformReady": "شەپۆلی دەنگ ئێستا تەنها قسە پیشان دەدات", "showOnlyWaveform": "پیشاندانی تەنها شەپۆل", "showOnlySpectrogram": "پیشاندانی تەنها سپێکترۆگرام", "showWaveformAndSpectrogram": "پیشاندانی شەپۆل و سپێکترۆگرام", @@ -2704,6 +3263,7 @@ "adjustSelectedLinesAndForward": "ڕێکخستنی دێڕە هەڵبژێردراوەکان و پێشەوە", "toFrameRate": "بۆ ڕێژەی چوارچێوە", "fromFrameRate": "لە ڕێژەی چوارچێوە", + "videoXFrameRateY": "{0} - {1} fps", "adjustAllTimes": "ڕێکخستنی هەموو کاتەکان (پیشاندانی زووتر/دواتر)", "showEarlier": "پیشاندانی زووتر", "showLater": "پیشاندانی دواتر", @@ -2713,7 +3273,8 @@ "syncPoints": "خاڵەکانی هاوکات", "pointSync": "هاوکاتکردنی خاڵ", "pointSyncViaOther": "هاوکاتکردنی خاڵ بە ژێرنووسێکی ترەوە", - "syncPointCandidateInfo": "دێڕەکان کە دوای {0}+ چرکە بێدەنگی دەستپێدەکەن ڕووناک دەکرێنەوە - زۆر جار خاڵی هاوکاتکردنی جێگیرن", + "syncPointCandidateAfterInfo": "ئەو دێڕانەی {0}+ چرکە بێدەنگییان بەدوادا دێت ڕووناک کراونەتەوە - زۆرجار خاڵی هاوکاتی متمانەپێکراو دروست دەکەن", + "gapAfter": "بۆشایی دوای", "adjustAllShortcuts": "کورتەڕێگەکانی کیبۆرد:\n\n• Shift + Left/Right: جوڵاندن بە 10 میلی چرکە\n• Ctrl + Left/Right: جوڵاندن بە 100 میلی چرکە\n• Alt + Left/Right: جوڵاندن بە 500 میلی چرکە", "adjustAllShortcutsFrames": "کورتەڕێگەکانی کیبۆرد:\n\n• Shift + Left/Right: جوڵاندن بە 1 چوارچێوە\n• Ctrl + Left/Right: جوڵاندن بە 10 چوارچێوە\n• Alt + Left/Right: جوڵاندن بە 1 چرکە", "offsetInSeconds": "لادان بە چرکە", @@ -2733,6 +3294,7 @@ "lineMerge": "تێکەڵکردنی دێڕ", "delayInSecondsBetweenRequests": "دواکەوتن بە چرکە لە نێوان داواکارییەکاندا", "maxBytesPerRequest": "زۆرترین بایت بۆ هەر داواکارییەک", + "keepMusicLinesUntranslated": "دێڕەکانی ناو هێماکانی مۆسیقا (♪ ... ♪) وەرمەگێڕە", "promptText": "دەقی پرۆمپت", "resetPromptToDefault": "گەڕاندنەوەی پرۆمپت بۆ بنەڕەتی", "translateEachLineSeparately": "وەرگێڕانی هەر دێڕێک بە جیا", @@ -2746,11 +3308,10 @@ "readyToTranslate": "ئامادە بۆ وەرگێڕان", "translating": "وەرگێڕان...", "translationComplete": "وەرگێڕان تەواوبوو", + "translateInPlaceNoOriginal": "وەرگێڕان لە شوێنی خۆیدا (ڕەسەن دروست مەکە)", "translationCancelled": "وەرگێڕان هەڵوەشێنرایەوە", "swapLanguages": "ئاڵوگۆڕی زمانی سەرچاوە و ئامانج", "xIsAlreadyDownloadedReDownload": "{0} پێشتر داگیراوە. دووبارە دابگریت؟", - "advancedDotDotDot": "پێشکەوتوو...", - "advancedSettings": "ڕێکخستنە پێشکەوتووەکان", "advancedSettingsSubtitle": "هەڵبژاردەکانی بەکۆمەڵ، چوارچێوە و نموونەگرتن بۆ بزوێنەری پێشکەوتووی llama.cpp", "context": "چوارچێوە", "synopsis": "کورتەی چیرۆک", @@ -2774,6 +3335,7 @@ "useOnlyExtraServerParametersHint": "llama-server بە پارامێترەکانی سەرەوە دەستپێبکە لەبری ڕێکخستنەکانی خودی Subtitle Edit - تەنها مۆدێل، هۆست و پۆرت دانران", "serverRunningAtX": "سێرڤەر کار دەکات لە {0}", "customPromptHint": "ڕێنماییە دڵخوازەکان ({0} = زمانی سەرچاوە، {1} = زمانی ئامانج)؛ بەتاڵ = پرۆمپتی دروستکراو", + "editTranslationHint": "کرتە لە وەرگێڕانی ڕیزی هەڵبژێردراو بکە بۆ دەستکاریکردنی (Enter پاشەکەوت دەکات، Escape هەڵدەوەشێنێتەوە)", "llamaCppDownloadEngineAndModelPrompt": "llama.cpp پێویستی بە داگرتنی بزوێنەری llama-server و مۆدێلێکی وەرگێڕان هەیە. ئێستا دابگریت؟", "llamaCppDownloadEnginePrompt": "llama.cpp پێویستی بە داگرتنی بزوێنەری llama-server هەیە. ئێستا دابگریت؟", "llamaCppDownloadModelPrompt": "llama.cpp پێویستی بە داگرتنی مۆدێلی وەرگێڕانی هەڵبژێردراو هەیە. ئێستا دابگریت؟" @@ -2844,6 +3406,7 @@ "showGridLines": "پیشاندانی هێڵەکانی خشتە", "showHorizontalLineAboveToolbar": "پیشاندانی هێڵی ئاسۆیی لە سەرووی تووڵامرازدا", "bookmarkColor": "ڕەنگی نیشانە", + "spellCheckHighlightColor": "ڕەنگی نیشانکردنی پشکنینی ڕێنووس", "singleLineMaxLength": "درێژی زۆرترین یەک دێڕ", "optimalCharsPerSec": "باشترین پیت/چرکە", "maxCharsPerSec": "زۆرترین پیت/چرکە", @@ -2868,13 +3431,21 @@ "useDoNotBreakAfterList": "بەکارهێنانی لیستی دوای-ئەمانە-مەبڕە", "newEmptyDefaultMs": "ماوەی بنەڕەتی ژێرنووسی نوێ (میلی چرکە)", "timeCodeUpDownStepMs": "هەنگاوی سەرەوە/خوارەوەی کات (میلی چرکە)", + "moveSelectedLinesStepMs": "هەنگاوی کورتەڕێگەی گواستنەوەی دێڕە هەڵبژێردراوەکان (ms)", + "moveStartEndStepMs": "هەنگاوی کورتەڕێگەی گواستنەوەی دەستپێک/کۆتایی (ms)", + "moveLinesShortenNeighbor": "گواستنەوەی دێڕەکان: کورتکردنەوەی دێڕی پێشوو/دواتر لەبری یەکتربڕین لەگەڵی", "promptBeforeDelete": "پرسیارکردن پێش سڕینەوە", "rememberPositionAndSize": "بیرهاتنەوەی شوێن و قەبارەی پەنجەرە", + "titleBarFullFileName": "پیشاندانی ڕێڕەوی تەواوی فایل لە شریتی ناونیشاندا", + "showRecentFiles": "پیشاندانی فایلە دوایینەکان (بۆ کردنەوەی دووبارە)", "openLastFileOnStart": "کردنەوەی دوایین فایل لە دەستپێکدا", "autoSave": "پاشەکەوتی خۆکار (پاشەکەوتکردنی فایلە کراوەکە لە کاتی دەستکاریکردندا)", "autoBackupOn": "پاشەکەوتی خۆکار", "autoBackupIntervalMinutes": "ماوەی پاشەکەوتی خۆکار (خولەک)", "autoBackupDeleteAfterDays": "پاراستنی پاشەکەوتی خۆکار (ڕۆژ)", + "settingsBackupOn": "پاشەکەوتی خۆکاری ڕێکخستنەکان", + "settingsBackupIntervalDays": "ماوەی نێوان پاشەکەوتی ڕێکخستنەکان (ڕۆژ، 0 = هەموو دەستپێکردنێک)", + "settingsBackupMaxCount": "ژمارەی پاشەکەوتی ڕێکخستنەکان بۆ هێشتنەوە", "autoConvertToUtf8": "گۆڕینی خۆکاری کۆدکردن بۆ UTF-8 لە کاتی کردنەوەدا", "autoTrimWhiteSpace": "کورتکردنەوەی خۆکاری بۆشاییەکان", "removeBlankLinesWhenOpening": "لابردنی دێڕە بۆشەکان لە کاتی کردنەوەی ژێرنووسێکدا", @@ -2891,7 +3462,10 @@ "colorGapTooShort": "ڕەنگکردن ئەگەر بۆشایی زۆر کورت بوو", "errorBackgroundColor": "ڕەنگی پاشبنەمای هەڵە", "waveformDrawGridLines": "کێشانی هێڵەکانی خشتە", + "waveformUseSkiaRenderer": "بەکارهێنانی ڕێندەرەری خێرای تاقیکاری", "waveformCenterVideoPositionAlsoWhenPaused": "ناوەڕاستکردنی شوێنی ڤیدیۆ لە کاتی وەستانیشدا", + "waveformSelectCurrentSubtitleWhilePaused": "هەڵبژاردنی ژێرنووسی ئێستا لە کاتی وەستانیشدا", + "waveformRememberZoom": "لەبیرکردنی زووم (ئاسۆیی و ستوونی)", "waveformShowToolbar": "پیشاندانی تووڵامراز", "waveformShowToolbarEdit": "دەستکاری تووڵامراز...", "waveformShowToolbarEditLabel": "بابەتەکانی تووڵامراز", @@ -2939,18 +3513,25 @@ "webVttUseXTimestampMap": "WebVTT: بەکارهێنانی X-TIMESTAMP-MAP (لادانی کۆدەکانی کات لە کاتی بارکردندا)", "assaAutoSetResolution": "ASSA: دانانی ڕێزۆلیوشن (PlayResX/PlayResY) لە ڤیدیۆوە کاتێک ڤیدیۆ دەکرێتەوە", "assaAutoSetResolutionPrompt": "ASSA: پرسیار بکە پێش گۆڕینی ڕێزۆلیوشن بۆ گونجان لەگەڵ ڤیدیۆکە (وەللا بە خۆکار ڕیسەمپڵ بکە)", - "showStopButton": "پیشاندانی دوگمەی ڕاوستان", - "showFullscreenButton": "پیشاندانی دوگمەی پڕەپەردە", + "videoControls": "کۆنترۆڵەکانی ڤیدیۆ", + "videoControlsEdit": "دەستکاری کۆنترۆڵەکانی ڤیدیۆ...", + "videoControlsPositionText": "دەقی شوێن / ماوە", + "videoControlsVideoFileName": "ناوی فایلی ڤیدیۆ", + "videoControlsPlayerName": "ناوی لێدەری ڤیدیۆ", "fullscreenHideControls": "شاردنەوەی کۆنترۆڵەکانی ڤیدیۆ لە پڕەپەردەدا", + "showSecondarySubtitleDialog": "پیشاندانی دیالۆگی شێواز لە کاتی کردنەوەی فایلی ژێرنووسی دووەمدا", + "rememberSecondarySubtitleFile": "لەبیرکردنی فایلی ژێرنووسی دووەم", "autoOpenVideoFile": "کردنەوەی خۆکاری فایلی ڤیدیۆ کە ژێرنووس دەکرێتەوە", "downloadMpv": "داگرتنی mpv", "downloadVlc": "داگرتنی VLC", "allowSingleLetterShortcutsInTextbox": "ڕێگەدان بە کورتەڕێگەی یەک-پیت لە سنووقی دەقدا", + "allowTextNavigationShortcutsInTextbox": "ڕێگەدان بە کورتەڕێگە لەسەر کلیلەکانی گەشتی دەق (Ctrl+Left/Right، Home/End) لە سنووقی دەقدا", "spellCheckEnglishTreatInApostropheAsIng": "پشکنینی ڕێنووس: وشەکانی بە 'in'' کۆتاییان دێت وەک 'ing' بزانە (تەنها ئینگلیزی)", "goToLineNumberSetsVideoPosition": "چوون بۆ دێڕی ژمارە شوێنی ڤیدیۆش دادەنێت", "adjustAllTimesRememberLineSelectionChoice": "ڕێکخستنی هەموو کاتەکان، بیرهاتنەوەی هەڵبژاردەی دێڕ", "mergeKeepEndTime": "تێکەڵکردنی دێڕەکان: پاراستنی کاتی کۆتایی (ڕێگەدان بە تێکەڵبوون لەگەڵ ژێرنووسی دواتردا)", "mergeKeepEndTimeOnlyAssa": "تێکەڵکردنی دێڕەکان: پاراستنی کاتی کۆتایی تەنها بۆ \"Advanced Sub Station Alpha\"", + "mergeAutoBreak": "تێکەڵکردنی دێڕەکان: دابەشکردنی خۆکاری دەقی تێکەڵکراو (ناچالاک دابەشبوونی دێڕی خودی دێڕەکان دەپارێزێت)", "filesAndLogs": "فایلەکان و تۆمارەکان", "showErrorLogFile": "پیشاندانی فایلی تۆماری هەڵەکان", "showToolsLogFile": "پیشاندانی فایلی تۆماری ئامرازەکان", @@ -2959,6 +3540,7 @@ "showAssaLayer": "پیشاندانی سنووقی چینی ASSA", "waveformCursorColor": "ڕەنگی نیشاندەری شەپۆل", "waveformShotChangeColor": "ڕەنگی گۆڕانی دیمەن لە شەپۆلدا", + "waveformGridColor": "ڕەنگی تۆڕی شەپۆلی دەنگ", "waveformParagraphLeftColor": "ڕەنگی لێواری چەپی شەپۆل", "waveformParagraphRightColor": "ڕەنگی لێواری ڕاستی شەپۆل", "waveformFancyHighColor": "ڕەنگی لووتکەی فانسی شەپۆل", @@ -2986,10 +3568,15 @@ "resetSyntaxColoring": "گەڕاندنەوەی ڕەنگکردنی سینتاکس", "resetWaveform": "گەڕاندنەوەی شەپۆل", "resetRules": "گەڕاندنەوەی یاساکان", - "useFrameMode": "بەکارهێنانی دۆخی چوارچێوە (hh.mm.ss.ff)", + "timeCodeMode": "دۆخی کۆدی کات", + "timeCodeModeTime": "کات (hh:mm:ss,zzz)", + "timeCodeModeFrames": "چوارچێوەکان (hh:mm:ss:ff)", + "timeCodeModeFrameNumbers": "ژمارەی چوارچێوەکان", + "frameModeSnapToVideoFrames": "هەڵگرتنی کاتەکان لەسەر چوارچێوەکانی ڤیدیۆ (نەک چوارچێوەکانی ناو هەر چرکەیەک)", "textBoxLimitNewLines": "سنووردانانی ژمارەی دێڕەکان لە سنووقی دەقی ژێرنووسدا", "mpvOpenGl": "libmpv - OpenGL", "mpvSoftwareRendering": "libmpv - ڕەندەرکردنی سۆفتوێر (هێواش)", + "mpvMetalRendering": "libmpv - Metal (تاقیکاری، پێویستی بە دووبارە دەستپێکردنەوە هەیە)", "mpvWidRendering": "libmpv - ڕەندەرکردنی Window ID ی سروشتی", "waveFormsAndSpectrogramFoldersContainsX": "فۆڵدەرەکانی \"Waveforms\" و \"spectrogram\" لەخۆ دەگرن {0}", "deleteWaveformAndSpectrogramFoldersQuestion": "سڕینەوەی فایلەکانی \"Waveforms\" و \"Spectrogram\"؟", @@ -3002,7 +3589,10 @@ "waveformExtractAudioSampleRate": "ڕێژەی نموونەی دەرهێنانی دەنگ", "waveformExtractAudioSampleRateOriginal": "ڕەسەن (پاراستنی سەرچاوە)", "waveformExtractAudioBitRate": "ڕێژەی بیتی دەرهێنانی دەنگ (MP3/M4A)", + "ffmpegUseCenterChannelOnly": "تەنها کەناڵی ناوەڕاستی پێشەوە بەکاربهێنە بۆ دەنگی 5.1 (FFmpeg)", "vlcWidRendering": "libVLC - ڕەندەرکردنی Window ID ی سروشتی", + "ffmpegSoftwareRendering": "FFmpeg", + "downloadFfmpegLibs": "داگرتنی کتێبخانەکانی FFmpeg (بۆ لێدەری ڤیدیۆی FFmpeg)", "subtitleGridEnterKeyAction": "کرداری کلیلی Enter لە خشتەی ژێرنووسدا", "subtitleSingleClickAction": "کرداری کرتەی تاکی خشتەی ژێرنووس", "subtitleDoubleClickAction": "کرداری دوو کرتەی خشتەی ژێرنووس", @@ -3030,6 +3620,7 @@ "subtitleGridFormattingShowFormatting": "پیشاندانی فۆرمات", "subtitleGridFormattingShowTags": "پیشاندانی تەگەکان", "subtitleGridFormattingHideTags": "شاردنەوەی تەگەکان", + "subtitleGridFormattingShowFormattingKeepTags": "پیشاندانی فۆرمات، پاراستنی تەگە نابینراوەکان", "waveformParagraphBackgroundColor": "ڕەنگی پاشبنەمای ژێرنووس لە شەپۆلدا", "waveformParagraphSelectedBackgroundColor": "ڕەنگی پاشبنەمای ژێرنووسی هەڵبژێردراو لە شەپۆلدا", "waveformAllowOverlap": "ڕێگەدان بە تێکەڵبوون (لە کاتی جوڵاندن/گۆڕینی قەبارەدا)", @@ -3045,9 +3636,12 @@ "splitOddLinesAction": "کرداری دابەشکردنی دێڕە تاکەکان", "ocrUseWordSplitList": "OCR: بەکارهێنانی لیستی دابەشکردنی وشە", "ocrGuessUnknownWords": "OCR: هەوڵدان بۆ مەزەندەکردنی وشە نەزانراوەکان", + "spellCheckRememberUseAlwaysList": "پشکنینی ڕێنووس: لەبیرکردنی لیستی \"هەمیشە بەکاربهێنە\"", + "fixShortDisplayTimesAllowMoveStartTime": "چاککردنی هەڵە باوەکان: چاککردنی کاتی پیشاندانی کورت دەکرێت کاتی دەستپێک بجوڵێنێت", "speechToTextSelectedLinesPromptFirstTimeOnly": "قسە بۆ دەق: دێڕە هەڵبژێردراوەکان، پرسیارکردن بۆ زمان/بزوێنەر تەنها یەکەم جار", "multipleReplaceShowDotDotDotButtons": "جێگرتنەوەی فرەیی: پیشاندانی دوگمەکانی مینیوی چوارچێوە", "gridFocusTextboxAfterInsertNew": "خشتە: فۆکەسکردن لەسەر سنووقی دەق دوای تێخستنی ژێرنووسی نوێ", + "undoRedoGoToChangedLine": "پاشگەزبوونەوە/دووبارەکردنەوە: چوون بۆ دێڕی گۆڕاو (و شوێنی ڤیدیۆ)", "musicSymbol": "هێمای مۆسیقا", "musicSymbolsToReplace": "هێماکانی مۆسیقا بۆ جێگرتنەوە (بە کۆما جیاکراوە)", "textToSpeechPromptMergeContinuationLines": "دەق بۆ قسە: پرسیارکردن بۆ تێکەڵکردنی دێڕە بەردەوامبووەکان", @@ -3056,6 +3650,7 @@ "useFocusedButtonBackgroundColor": "بەکارهێنانی ڕەنگی پاشبنەمای دوگمەی فۆکەسکراو", "focusedButtonBackgroundColor": "ڕەنگی پاشبنەمای دوگمەی فۆکەسکراو", "forceCrLfOnSave": "پەراندنی CR+LF لە کاتی پاشەکەوتکردندا (فایلەکانی ژێرنووسی دەقی)", + "linuxClipboardUseExternalTool": "کۆپیکردنی دەق لە ڕێی xclip/wl-copy (پیتە نیشانەدارەکان دەپارێزێت)", "showFormatLimitWarning": "ئاگادارکردنەوە لە کاتی پاشەکەوتکردندا کە دێڕەکان سنوورەکانی فۆرمات تێدەپەڕێن (بۆ نموونە SCC ی 32 پیت/دێڕ)", "textBoxButtonShowAutoBreak": "سنووقی دەق: پیشاندانی دوگمەی دابەشکردنی خۆکار", "textBoxButtonShowUnbreak": "سنووقی دەق: پیشاندانی دوگمەی یەکخستن", @@ -3067,12 +3662,18 @@ "waveformDoubleClickAction": "کرداری دوو کرتەی شەپۆل (دوای کرداری کرتەی تاک)", "allSettings": "هەموو ڕێکخستنەکان", "uiScale": "پێوانەی ڕووکار (%)", + "fontScale": "پێوەری فۆنت (%)", + "textSelectionOpacity": "ناڕوونی هەڵبژاردنی دەق (%)", "waveformToolbarItems": "بابەتەکانی تووڵامرازی شەپۆل", "matchIconColorToDarkTheme": "گونجاندنی ڕەنگی ئایکۆنەکان لەگەڵ ڕەنگی پێشەوەی ڕووکاری تاریک", "subtitlePreviewProperties": "تایبەتمەندییەکانی پێشبینینی ژێرنووس", "usePositionFromSubtitleFile": "بەکارهێنانی شوێن لە فایلی ژێرنووسەوە (TTML/PAC/EBU STL)", "marginIsPartOfSubtitleArea": "پەراوێز بەشێکە لە ناوچەی ژێرنووس", "textJustify": "ڕێکخستنی دەق (justify)", + "deinterlace": "Deinterlace ی ڤیدیۆ", + "deinterlaceOff": "ناچالاک", + "deinterlaceAuto": "خۆکار (تەنها چوارچێوە interlaced ەکان)", + "deinterlaceAlways": "هەمیشە", "pixelWidthInfo": "هێڵە سەوزەکان = سنووری زۆرترین پانی | ناوچەی سوور = دەق لە سنوور تێدەپەڕێت", "spellCheckEngineHunSpelll": "Hunspell", "spellCheckEngineMsWord": "MS Word", @@ -3081,7 +3682,16 @@ "waveformColorTheme": "ڕووکاری ڕەنگ:", "waveformSaveAsCustomTheme": "پاشەکەوتکردن وەک ڕووکاری دڵخواز...", "waveformLoadThemeDotDotDot": "بارکردنی ڕووکار...", - "waveformExportThemeDotDotDot": "دەرهێنانی ڕووکار..." + "waveformExportThemeDotDotDot": "دەرهێنانی ڕووکار...", + "waveformSaveCustomThemeTitle": "پاشەکەوتکردنی ڕووکاری تایبەت", + "waveformThemeFileType": "ڕووکاری شەپۆلی دەنگی Subtitle Edit", + "waveformThemeHighContrast": "جیاوازی بەرز", + "waveformThemeOceanBlue": "شینی ئۆقیانووس", + "waveformThemeWarmSunset": "ئاوابوونی گەرم", + "waveformThemeForest": "دارستان", + "waveformThemeMidnightPurple": "مۆری نیوەشەو", + "waveformThemeRetroAmber": "کەهرەبای کۆن", + "waveformThemeIce": "سەهۆڵ" }, "shortcuts": { "searchShortcuts": "گەڕان بۆ کورتەڕێگەکان...", @@ -3092,11 +3702,34 @@ "categoryVideo": "ڤیدیۆ", "categorySync": "هاوکاتکردن", "categoryAi": "AI", + "categoryCustom": "تایبەت", + "editCustomShortcut": "دەستکاری کورتەڕێگەی تایبەت", + "customShortcutTextBoxKeyHint": "لێرە کلیلێک لەگەڵ Ctrl/Alt بەکاربهێنە - کلیلێکی سادە چیتر لە سنووقی دەقدا نانووسێت.", + "customShortcutNumberX": "کورتەڕێگەی تایبەت #{0}", + "customShortcutNumberXY": "کورتەڕێگەی تایبەت #{0}: {1}", + "customShortcutStep": "هەنگاو", + "customShortcutSteps": "هەنگاوەکان", + "customShortcutStepRunCommand": "جێبەجێکردنی فەرمان", + "customShortcutStepInsertText": "تێخستنی دەق", + "customShortcutStepReplace": "دۆزینەوە و جێگرتنەوە", + "customShortcutInsertAtCursor": "لە شوێنی نیشانەدەر", + "customShortcutInsertAtStart": "لە سەرەتای دەق", + "customShortcutInsertAtEnd": "لە کۆتایی دەق", + "customShortcutSummaryRunCommandX": "جێبەجێکردن: {0}", + "customShortcutSummaryInsertXY": "تێخستنی \"{0}\" ({1})", + "customShortcutSummaryReplaceXY": "جێگرتنەوەی \"{0}\" بە \"{1}\"", + "customShortcutSummaryReplaceRegexXY": "جێگرتنەوەی regex ی \"{0}\" بە \"{1}\"", + "customShortcutPickCommand": "تکایە فەرمانێک بۆ جێبەجێکردن هەڵبژێرە.", + "customShortcutEnterText": "تکایە ئەو دەقە بنووسە کە تێدەخرێت.", + "customShortcutEnterFind": "تکایە ئەو دەقە بنووسە کە دەدۆزرێتەوە.", + "customShortcutInvalidRegexX": "دەربڕینی ئاسایی (regex) نادروستە: {0}", + "customShortcutStepsHint": "هەنگاوەکان لە سەرەوە بۆ خوارەوە جێبەجێ دەکرێن. هەنگاوەکانی دەق لەسەر دێڕە هەڵبژێردراوەکان کاردەکەن؛ ئەو فەرمانانەی پەنجەرەیەک دەکەنەوە دوای داخستنی بەردەوام دەبن.", "activeIn": "چالاک لە", "activeInEverywhere": "هەموو شوێنێک", "generalMergeSelectedLinesAndUnbreak": "تێکەڵکردنی دێڕە هەڵبژێردراوەکان و یەکخستن", "generalMergeSelectedLinesAndUnbreakCjk": "تێکەڵکردنی دێڕە هەڵبژێردراوەکان و یەکخستن بێ بۆشایی (CJK)", "generalUnbreakNoSpaceCjk": "یەکخستن بێ بۆشایی (CJK)", + "generalToggleBreak": "گۆڕینی دابەشکردن/لابردنی دابەشبوونی دێڕ", "generalMergeSelectedLinesBilingual": "تێکەڵکردنی دێڕە هەڵبژێردراوەکان بە دوو زمان", "generalToggleTranslationMode": "گۆڕینی دۆخی وەرگێڕان", "generalToggleTranslationAndOriginalInPreviews": "گۆڕینی وەرگێڕان و ڕەسەن لە پێشبینینی ڤیدیۆ/دەنگدا", @@ -3113,10 +3746,12 @@ "generalToggleItalic": "گۆڕینی لار", "generalToggleBold": "گۆڕینی قەڵەو", "generalToggleUnderline": "گۆڕینی ژێرهێڵ", + "generalToggleBox": "گۆڕینی چوارگۆشە (EBU STL)", "fileOpen": "کردنەوە", "fileOpenKeepVideo": "کردنەوە (پاراستنی ڤیدیۆ)", "fileSave": "پاشەکەوتکردن", "fileSaveAs": "پاشەکەوتکردن وەک", + "fileSaveOriginal": "پاشەکەوتکردنی ڕەسەن", "fileCompare": "بەراوردکردن", "fileStatistics": "ئامارەکان", "fileImportPlainText": "هێنانەناوی دەقی سادە", @@ -3128,6 +3763,7 @@ "fillSelectedLinesWithClipboard": "پڕکردنەوەی دێڕە هەڵبژێردراوەکان بە دەقی کلیپبۆرد", "listInverseSelection": "پێچەوانەکردنی هەڵبژاردە", "listDeleteSelection": "سڕینەوەی هەڵبژاردە", + "deleteSelectionEverywhere": "سڕینەوەی هەڵبژاردە (لە هەموو شوێنێک)", "rippleDeleteSelection": "سڕینەوەی زنجیرەیی هەڵبژاردە", "assigned": "دانراو", "unassigned": "دانەنراو", @@ -3136,6 +3772,7 @@ "detectKey": "دۆزینەوەی کلیل", "control": "Control", "alt": "Alt", + "altGr": "AltGr", "win": "Win", "shift": "Shift", "controlMac": "⌃", @@ -3146,6 +3783,11 @@ "resetShortcutsDetail": "دەتەوێت هەموو کورتەڕێگەکان بگەڕێنیتەوە بۆ بەها بنەڕەتییەکان؟", "togglePlayPause": "گۆڕینی لێدان/وەستان", "toggleLockTimeCodes": "گۆڕینی چەسپاندنی کۆدەکانی کات", + "toggleTimeCodeMode": "گۆڕینی دۆخی کۆدی کات (کات/چوارچێوە/ژمارەی چوارچێوە)", + "toggleScreenPrivacy": "گۆڕینی تایبەتمەندی شاشە (شاردنەوەی ناوی فایلەکان، پاشان دەقەکانیش)", + "screenPrivacyFileNamesHidden": "تایبەتمەندی شاشە: ناوی فایلەکان شاراوەن", + "screenPrivacyFileNamesAndTextsHidden": "تایبەتمەندی شاشە: ناوی فایلەکان و دەقەکان شاراوەن", + "screenPrivacyOff": "تایبەتمەندی شاشە: ناچالاک", "duplicateSelectedLines": "دووپاتکردنی دێڕە هەڵبژێردراوەکان", "sourceView": "بینینی سەرچاوە", "addOrEditBookmark": "زیادکردن یان دەستکاری نیشانە", @@ -3164,6 +3806,7 @@ "moveFirstWordFromNextLineUpCurrentSubtitle": "گواستنی یەکەم وشە لە دێڕی دواترەوە بۆ سەرەوە (ژێرنووسی ئێستا)", "moveTextFromCursorToNextAndGoToNext": "گواستنی دەقی دوای شوێنی نیشاندەر بۆ ژێرنووسی دواتر و چوون بۆ دواتر", "moveTextFromCursorToNextAndGoToNextAndPlay": "گواستنی دەقی دوای شوێنی نیشاندەر بۆ ژێرنووسی دواتر، چوون بۆ دواتر و لێدان", + "moveTextFromCursorToNextAndGoToNextAndPlayAndPause": "گواستنەوەی دەقی دوای شوێنی نیشانەدەر بۆ ژێرنووسی دواتر، چوون بۆ دواتر، لێدانی و وەستان لە کۆتاییەکەیدا", "breakAtFirstSpaceFromCursor": "دابەشکردن لە یەکەم بۆشایی لە شوێنی نیشاندەرەوە", "breakAtFirstSpaceFromCursorAndGoToNext": "دابەشکردن لە یەکەم بۆشایی لە شوێنی نیشاندەرەوە و چوون بۆ دواتر", "toggleFocusGridAndWaveform": "گۆڕینی فۆکەس لە نێوان خشتەی ژێرنووس و شەپۆل/سپێکترۆگرامدا", @@ -3193,6 +3836,7 @@ "listViewColumnTextUp": "ستوون، دەق بۆ سەرەوە", "listViewColumnTextDown": "ستوون، دەق بۆ خوارەوە", "autoTranslateSelectedLines": "وەرگێڕانی خۆکاری دێڕە هەڵبژێردراوەکان...", + "autoTranslateSelectedLinesNoPrompt": "وەرگێڕانی خۆکاری دێڕە هەڵبژێردراوەکان (بێ پرسیار، دوایین بزوێنەر/زمانەکان بەکاربهێنە)", "setAssaResolution": "دانانی ڕێزۆلیوشنی ASSA (PlayResX/PlayResY)", "setShortcutForX": "دانانی کورتەڕێگە بۆ \"{0}\"", "commandFileNewKeepVideo": "نوێ (پاراستنی ڤیدیۆ)", @@ -3247,6 +3891,7 @@ "textBoxItalic": "لاری سنووقی دەق", "textBoxBold": "قەڵەوی سنووقی دەق", "textBoxUnderline": "ژێرهێڵی سنووقی دەق", + "textBoxBox": "چوارگۆشەی سنووقی دەق (EBU STL)", "resetWaveformZoomAndSpeed": "گەڕاندنەوەی زووم و خێرایی لێدانی شەپۆل (ڕێژەی لێدان)", "togglePlaybackSpeed": "گۆڕینی خێرایی لێدان (ڕێژەی لێدان)", "playbackSpeedSlower": "خێرایی لێدانی هێواشتر (ڕێژەی لێدان)", @@ -3258,6 +3903,7 @@ "seekSilenceForward": "گەڕان بۆ بێدەنگی بۆ پێشەوە", "waveformGuessStart": "مەزەندەکردنی کاتی دەستپێک لە شەپۆلەوە", "waveformGuessEnd": "مەزەندەی کۆتایی لە شەپۆلەوە", + "waveformGuessStartAndEnd": "خەمڵاندنی کاتی دەستپێک و کۆتایی لە شەپۆلی دەنگەوە", "setVideoPositionCurrentSubtitleStart": "دانانی شوێنی ڤیدیۆ بۆ دەستپێکی دێڕی ئێستا", "goToSubtitlePositionAndPause": "چوون بۆ شوێنی ژێرنووس و وەستان", "setVideoPositionCurrentSubtitleEnd": "دانانی شوێنی ڤیدیۆ بۆ کۆتایی دێڕی ئێستا", @@ -3271,6 +3917,14 @@ "surroundWith": "داپۆشین بە...", "surroundWithXY": "داپۆشین بە {0}/{1}", "surroundWithNumberX": "دەورەدان بە #{0}", + "surroundWithBehavior": "ڕەفتار", + "surroundWithBehaviorToggle": "گۆڕین (زیادکردن، یان لابردن ئەگەر هەبێت)", + "surroundWithBehaviorAdd": "زیادکردن (هەموو جارێک)", + "surroundWithBehaviorRemove": "لابردن (هەموو)", + "surroundWithBehaviorRemoveOnce": "لابردن (یەک لە هەر جارێکدا)", + "surroundWithWorksOn": "کاردەکات لەسەر", + "surroundWithScopeSelectionOrText": "هەڵبژاردە، ئەگەرنا هەموو دەقەکە", + "surroundWithScopeEachLine": "هەر دێڕێک", "searchVia": "گەڕان لە ڕێگەی", "searchViaX": "گەڕان لە ڕێگەی {0}", "searchViaNumberX": "گەڕان لە ڕێگەی #{0}", @@ -3298,6 +3952,11 @@ "copySubtitlePathToClipboard": "لەبەرگرتنەوەی ڕێچکەی ژێرنووس بۆ کلیپبۆرد", "copySubtitleOriginalPathToClipboard": "لەبەرگرتنەوەی ڕێچکەی ژێرنووسی ڕەسەن بۆ کلیپبۆرد", "focusTextBox": "فۆکەسکردن لەسەر سنووقی دەق", + "focusSubtitleListView": "فۆکەس لەسەر لیستی ژێرنووسەکان", + "focusWaveform": "فۆکەس لەسەر شەپۆلی دەنگ", + "focusOriginalTextBox": "فۆکەس لەسەر سنووقی دەقی ڕەسەن", + "textBoxGoToStart": "سنووقی دەق، چوون بۆ سەرەتا", + "textBoxGoToEnd": "سنووقی دەق، چوون بۆ کۆتایی", "sortByNumber": "ڕیزکردن بەپێی ژمارە", "sortByStartTime": "ڕیزکردن بەپێی کاتی «نیشاندان»", "sortByEndTime": "ڕیزکردن بەپێی کاتی «شاردنەوە»", @@ -3335,9 +3994,9 @@ "selectionToSentenceCase": "سنووقی دەق: هەڵبژاردە بکە بە پیتی سەرەتای ڕستە", "selectionToggleCasing": "سنووقی دەق: گۆڕینی جۆری پیت (گەورە -> بچووک -> سەرەتای ڕستە)", "googleIt": "لە گووگڵ بیدۆزەوە (دەقی هەڵبژێردراو)", - "setActorXY": "دانانی ئەکتەری {0}: {1}", + "setActorX": "دانانی ئەکتەر: {0}", "setNewActor": "دانانی ئەکتەری نوێ...", - "setActorSlotXTitle": "خاڵی ئەکتەری {0}", + "setStyleX": "دانانی شێواز: {0}", "toggleDialogDashes": "گۆڕینی تێخەکانی دیالۆگ", "mergeWithLineBeforeAsDialog": "تێکەڵکردن لەگەڵ دێڕی پێشەوە وەک دیالۆگ", "mergeWithLineAfterAsDialog": "تێکەڵکردن لەگەڵ دێڕی پاشەوە وەک دیالۆگ", @@ -3348,7 +4007,23 @@ "moveStartOneFrameBackKeepGapPrev": "جوڵاندنی دەستپێک یەک چوارچێوە بۆ دواوە (پاراستنی بۆشایی بۆ پێشوو ئەگەر نزیک بێت)", "moveStartOneFrameForwardKeepGapPrev": "جوڵاندنی دەستپێک یەک چوارچێوە بۆ پێشەوە (پاراستنی بۆشایی بۆ پێشوو ئەگەر نزیک بێت)", "moveEndOneFrameBackKeepGapNext": "جوڵاندنی کۆتایی یەک چوارچێوە بۆ دواوە (پاراستنی بۆشایی بۆ دواتر ئەگەر نزیک بێت)", - "moveEndOneFrameForwardKeepGapNext": "جوڵاندنی کۆتایی یەک چوارچێوە بۆ پێشەوە (پاراستنی بۆشایی بۆ دواتر ئەگەر نزیک بێت)" + "moveEndOneFrameForwardKeepGapNext": "جوڵاندنی کۆتایی یەک چوارچێوە بۆ پێشەوە (پاراستنی بۆشایی بۆ دواتر ئەگەر نزیک بێت)", + "moveStartXMsBack": "گواستنەوەی دەستپێک X ms بۆ دواوە (X لە ڕێکخستنەکاندا دادەنرێت، هەروەها لە هاوکاتکردنی بینراودا)", + "moveStartXMsForward": "گواستنەوەی دەستپێک X ms بۆ پێشەوە (X لە ڕێکخستنەکاندا دادەنرێت، هەروەها لە هاوکاتکردنی بینراودا)", + "moveEndXMsBack": "گواستنەوەی کۆتایی X ms بۆ دواوە (X لە ڕێکخستنەکاندا دادەنرێت، هەروەها لە هاوکاتکردنی بینراودا)", + "moveEndXMsForward": "گواستنەوەی کۆتایی X ms بۆ پێشەوە (X لە ڕێکخستنەکاندا دادەنرێت، هەروەها لە هاوکاتکردنی بینراودا)", + "moveSelectedLinesXMsBack": "گواستنەوەی دێڕە هەڵبژێردراوەکان X ms بۆ دواوە (X لە ڕێکخستنەکاندا دادەنرێت)", + "moveSelectedLinesXMsForward": "گواستنەوەی دێڕە هەڵبژێردراوەکان X ms بۆ پێشەوە (X لە ڕێکخستنەکاندا دادەنرێت)", + "moveSelectedLinesAndForwardXMsBack": "گواستنەوەی دێڕە هەڵبژێردراوەکان و هەموو دواترەکان X ms بۆ دواوە (X لە ڕێکخستنەکاندا دادەنرێت)", + "moveSelectedLinesAndForwardXMsForward": "گواستنەوەی دێڕە هەڵبژێردراوەکان و هەموو دواترەکان X ms بۆ پێشەوە (X لە ڕێکخستنەکاندا دادەنرێت)", + "moveAllLinesXMsBack": "گواستنەوەی هەموو دێڕەکان X ms بۆ دواوە (X لە ڕێکخستنەکاندا دادەنرێت)", + "moveAllLinesXMsForward": "گواستنەوەی هەموو دێڕەکان X ms بۆ پێشەوە (X لە ڕێکخستنەکاندا دادەنرێت)", + "moveSelectedLinesCustomMsBack": "گواستنەوەی دێڕە هەڵبژێردراوەکان، میلی چرکەی تایبەت ({0:#,###,##0}) بۆ دواوە، {1}", + "moveSelectedLinesCustomMsForward": "گواستنەوەی دێڕە هەڵبژێردراوەکان، میلی چرکەی تایبەت ({0:#,###,##0}) بۆ پێشەوە، {1}", + "moveSelectedLinesAndForwardCustomMsBack": "گواستنەوەی دێڕە هەڵبژێردراوەکان و هەموو دواترەکان، میلی چرکەی تایبەت ({0:#,###,##0}) بۆ دواوە، {1}", + "moveSelectedLinesAndForwardCustomMsForward": "گواستنەوەی دێڕە هەڵبژێردراوەکان و هەموو دواترەکان، میلی چرکەی تایبەت ({0:#,###,##0}) بۆ پێشەوە، {1}", + "moveAllLinesCustomMsBack": "گواستنەوەی هەموو دێڕەکان، میلی چرکەی تایبەت ({0:#,###,##0}) بۆ دواوە، {1}", + "moveAllLinesCustomMsForward": "گواستنەوەی هەموو دێڕەکان، میلی چرکەی تایبەت ({0:#,###,##0}) بۆ پێشەوە، {1}" }, "wordLists": { "title": "لیستەکانی وشە", @@ -3393,7 +4068,11 @@ "applyToSelectedLinesX": "دێڕە هەڵبژێردراوەکان ({0})", "applyToAllLinesX": "هەموو دێڕەکان ({0})", "updateAllXAvailable": "نوێکردنەوەی هەموو ({0})", - "updatingXOfY": "نوێکردنەوە {0}/{1}..." + "updatingXOfY": "نوێکردنەوە {0}/{1}...", + "doNotAskAgain": "دووبارە مەپرسە (لە بەڕێوەبردنی Plugins ـەکاندا بیگۆڕە)", + "applyPluginsTo": "جێبەجێکردنی Plugins ـەکان لەسەر", + "askEachTime": "هەموو جارێک بپرسە", + "allLines": "هەموو دێڕەکان" }, "help": { "aboutSubtitleEdit": "دەربارەی Subtitle Edit", @@ -3437,6 +4116,8 @@ "nOcrDatabase": "بنکەدراوی nOCR", "drawMode": "دۆخی کێشان:", "addNewCharcter": "زیادکردنی پیتی نوێ", + "addNOcrCharacterTitle": "زیادکردنی پیتی nOCR بۆ دێڕی {0}، پیتی {1} لە {2} بە بەکارهێنانی بنکەدراوەی \"{3}\"", + "addBinaryOcrCharacterTitle": "زیادکردنی پیتی binary OCR بۆ دێڕی {0}، پیتی {1} لە {2} بە بەکارهێنانی بنکەدراوەی \"{3}\"", "lineIndexX": "دێڕی {0}", "inspectNOcrAdditions": "پشکنینی زیادکراوە نوێکانی nOCR", "ocrSelectedLines": "OCR کردنی دێڕە هەڵبژێردراوەکان", @@ -3485,6 +4166,8 @@ "binaryImageCompareInspectImageMatches": "\"بەراوردی وێنەی باینەری\" - پشکنینی لێکچوونەکانی وێنە", "saveBlankTextTitle": "پاشەکەوتکردنی دەقی بەتاڵ؟", "saveBlankTextPrompt": "دەقی بەتاڵ بۆ وێنەکە پاشەکەوت بکرێت؟", + "drawnLinesDoNotMatchTitle": "هێڵەکان لەگەڵ وێنەکە ناگونجێن", + "drawnLinesDoNotMatchPrompt": "هەندێک لە هێڵە کێشراوەکان لەگەڵ وێنەکە ناگونجێن (هێڵە سەوزەکان دەبێت لەسەر پیتەکە بن، هێڵە سوورەکان دەبێت لە دەرەوەی بن)، بۆیە ئەم پیتە ناناسرێتەوە.\n\nهەر پاشەکەوت بکرێت؟", "yesAndNeverAskAgain": "بەڵێ و دووبارە پرسیار مەکەوە", "importTextFromSubtitleDotDotDot": "هێنانەناوی دەق لە ژێرنووسەوە...", "importTextFromSubtitleOverwritePrompt": "هەندێک دێڕ پێشتر دەقی OCR یان هەیە. لەسەرنووسی دەقە ئێستاکان بکرێت؟", @@ -3543,9 +4226,26 @@ "crispEmbedSettingsTitle": "ڕێکخستنەکانی CrispEmbed", "crispEmbedDescription": "بزوێنەری ناوخۆیی OCR بە چەند بنکەی مۆدێل - بزوێنەر و مۆدێلەکان لێرە دابگرە.", "trainNOcrDatabase": "ڕاهێناندنی بنکەدراوی nOCR...", + "optimizeNOcrDatabase": "باشکردنی بنکەدراوەی nOCR...", + "optimizeNOcrDatabaseQuestionXY": "ئەو هێڵانە لابدرێن کە پیتەکان لە پیتە هاوشێوەکان جیا ناکەنەوە لە بنکەدراوەی nOCR ی \"{0}\"؟\n\nئەمە بنکەدراوەکە بچووکتر و OCR خێراتر دەکات، بە ئاسایی بە هەڵەی کەمتر. تا خولەکێک دەخایەنێت.\n\nپاشەکەوتێک وەک \"{1}\" هەڵدەگیرێت.", + "optimizingNOcrDatabaseXY": "باشکردنی بنکەدراوەی nOCR... {0}/{1}", + "nOcrDatabaseOptimizedXYZ": "بنکەدراوەی nOCR ی \"{0}\" باشکرا: {1:#,##0} -> {2:#,##0} هێڵ.", "startTraining": "دەستپێکردنی ڕاهێنان", "abortTraining": "وازهێنان لە ڕاهێنان", "trainingOptions": "هەڵبژاردەکانی ڕاهێنان", + "trainNOcrSubtitle": "پیتەکان بە فۆنتە دامەزراوەکان دەکێشێت و شێوەکانیان فێری nOCR دەکات. ئەو فۆنتانە هەڵبژێرە کە ژێرنووسەکانت بەکاریان دەهێنن.", + "xFontsSelected": "{0:#,##0} هەڵبژێردراو", + "subtitleFonts": "فۆنتەکانی ژێرنووس", + "emptyDatabase": "بنکەدراوەی بەتاڵ", + "startFromDatabase": "دەستپێکردن لە", + "startFromDatabaseHint": "پیتە فێرکراوەکان بۆ کۆپییەکی بنکەدراوەیەکی هەبوو زیاد بکە (پێشنیارکراو) یان لە بنکەدراوەیەکی بەتاڵەوە دەستپێبکە", + "fontSizes": "قەبارەکانی فۆنت", + "fontSizesHint": "قەبارەکانی فۆنت بە پیکسڵ بۆ کێشان، بە کۆما جیاکراونەتەوە، بۆ نموونە \"30, 40\"", + "numberOfLineSegmentsHint": "ژمارەی هێڵە پشکنراوەکان بۆ هەر پیتێک - زیاتر توندترە بەڵام هێواشترە", + "enterDatabaseName": "ناوێک بۆ بنکەدراوەکە بنووسە", + "trainingFontXSizeYCharacterZ": "فێرکردنی \"{0}\" لە {1} px: {2}", + "xLearned": "{0:#,##0} فێرکران", + "xSkipped": "{0:#,##0} تێپەڕێندران", "charactersToTrain": "پیتەکانی ڕاهێنان", "letterCombinationsToTrain": "تێکەڵەکانی پیت کە ڕەنگە دابەش بکرێن بۆ یەک وێنە", "importCharactersFromSubtitleFile": "هێنانەناوی پیتەکان لە فایلی ژێرنووسەوە...", @@ -3572,6 +4272,8 @@ "drawZoomOut": "زوومی دوورکردنەوە (Ctrl+-)", "drawResetView": "گەڕاندنەوەی بینین (Ctrl+0)", "drawToggleGrid": "گۆڕینی خشتە (Ctrl+G)", + "drawTogglePreview": "گۆڕینی پێشبینین (F9)", + "drawPreviewFailed": "پێشبینین سەرکەوتوو نەبوو - ffmpeg لەگەڵ libass (subtitles filter) پێویستە", "drawCopyToClipboard": "لەبەرگرتنەوە بۆ کلیپبۆرد (Ctrl+C)", "drawShapes": "شێوەکان", "drawSelectedPoint": "خاڵی هەڵبژێردراو", @@ -3579,6 +4281,81 @@ "drawSelectedLayer": "چینی هەڵبژێردراو", "drawToolX": "ئامراز: {0}", "drawHelpText": "کرتە بکە بۆ زیادکردنی خاڵ • Enter/F8 بۆ داخستنی شێوە • Shift+ڕاکێشان بۆ جوڵاندن • Ctrl+سکڕۆڵ بۆ زووم", + "drawMoveToLayer": "گواستنەوە بۆ چین", + "drawFlipHorizontal": "هەڵگێڕانەوەی ئاسۆیی", + "drawFlipVertical": "هەڵگێڕانەوەی ستوونی", + "drawHideShape": "شاردنەوەی شێوە", + "drawShowShape": "پیشاندانی شێوە", + "drawHideLayer": "شاردنەوەی چین", + "drawShowLayer": "پیشاندانی چین", + "drawDeleteLayer": "سڕینەوەی چین", + "drawDeletePoint": "سڕینەوەی خاڵ", + "drawCancelDrawing": "هەڵوەشاندنەوەی وێنەکێشان", + "drawLayerX": "چینی {0}", + "drawImportSvg": "هێنانەناوی وێنەی SVG...", + "drawRotateClockwise": "سوڕاندنی 90° بە ئاراستەی کاتژمێر", + "drawConvertToCurve": "گۆڕینی پارچە بۆ چەماوە", + "drawBackground": "پاشبنەما", + "drawPosition": "شوێن", + "drawZoom": "زووم", + "drawShapeTool": "ئامرازی شێوە", + "drawColorPickerTool": "هەڵگرتنی ڕەنگ لە شێوەیەک یان پاشبنەما", + "drawShapeLibrary": "کتێبخانەی شێوەکان", + "drawShapeLibraryHint": "لەسەر تابلۆکە ڕابکێشە بۆ دانانی شێوەیەک، یان کرتە بکە بۆ قەبارەی بنەڕەتی. Shift ڕێژەکانی دەپارێزێت.", + "drawAddToLibrary": "زیادکردن بۆ کتێبخانەی شێوەکان...", + "drawRemoveFromLibrary": "لابردن لە کتێبخانەی شێوەکان", + "drawShapeName": "ناوی شێوە", + "drawCategorySpeechBubbles": "بڵقی قسە", + "drawCategoryArrows": "تیرەکان", + "drawCategoryBasicShapes": "شێوە سەرەتاییەکان", + "drawCategorySymbols": "هێماکان", + "drawCategoryMyShapes": "شێوەکانی من", + "drawShapeSpeechBubble": "بڵقی قسە", + "drawShapeSpeechBubbleRight": "بڵقی قسە، کلک بۆ ڕاست", + "drawShapeSpeechBubbleUp": "بڵقی قسە، کلک بۆ سەرەوە", + "drawShapeOvalBubble": "بڵقی قسەی هێلکەیی", + "drawShapeBoxBubble": "بڵقی قسەی چوارگۆشە", + "drawShapeThoughtBubble": "بڵقی بیرکردنەوە", + "drawShapeShoutBubble": "بڵقی هاوار", + "drawShapeCaptionBox": "سندوقی ژێرنووس", + "drawShapeArrow": "تیر", + "drawShapeDoubleArrow": "تیری دووسەر", + "drawShapeChevron": "شێڤرۆن", + "drawShapeCurvedArrow": "تیری چەماوە", + "drawShapeRoundedRectangle": "لاکێشەی گۆشە خڕ", + "drawShapeTriangle": "سێگۆشە", + "drawShapeDiamond": "ئەڵماس", + "drawShapePentagon": "پێنجگۆشە", + "drawShapeHexagon": "شەشگۆشە", + "drawShapeOctagon": "هەشتگۆشە", + "drawShapeStar": "ئەستێرە", + "drawShapeRing": "ئەڵقە", + "drawShapePlus": "کۆ", + "drawShapeBanner": "بانەر", + "drawShapeHeart": "دڵ", + "drawShapeCheck": "نیشانەی ڕاست", + "drawShapeMusicNote": "نۆتەی مۆسیقا", + "drawShapeMusicNotes": "نۆتەکانی مۆسیقا", + "drawShapeLightning": "هەورەبروسکە", + "drawShapeCloud": "هەور", + "drawShapeMoon": "مانگ", + "drawShapeBadge": "باج", + "drawSize": "قەبارە", + "drawLayer": "چین", + "drawBackgroundVideoFrame": "چوارچێوەی ڤیدیۆ (شوێنی ئێستا)", + "drawBackgroundVideoFrameAt": "چوارچێوەی ڤیدیۆ لە...", + "drawBackgroundImage": "فایلی وێنە...", + "drawBackgroundNone": "بێ پاشبنەما", + "drawBackgroundStretch": "کشاندنی وێنە بۆ قەبارەی تابلۆ", + "drawBackgroundOpacity": "ناڕوونی پاشبنەما", + "drawBackgroundVideoFrameFailed": "نەتوانرا چوارچێوەیەکی ڤیدیۆ لە {0} وەربگیرێت.", + "drawImages": "وێنەکان", + "drawConvertToLine": "گۆڕینی پارچە بۆ هێڵ", + "drawConvertShapeToCurves": "گۆڕینی هەموو هێڵەکان بۆ چەماوە", + "drawConvertShapeToLines": "گۆڕینی هەموو چەماوەکان بۆ هێڵ", + "drawRotateCounterClockwise": "سوڕاندنی 90° بە پێچەوانەی ئاراستەی کاتژمێر", + "drawSvgImages": "وێنەکانی SVG", + "drawSvgNoShapes": "هیچ شێوەیەکی پڕکراو یان هێڵێک لە فایلی SVG دا نەدۆزرایەوە.", "progressBarTitle": "هێڵی پێشکەوتنی ASSA", "progressBarSettings": "هێڵی پێشکەوتن", "mouseOverColor": "ڕەنگی بردنی مشتەر", @@ -3675,13 +4452,26 @@ "graphics": "گرافیکەکان", "copyToStorageStyles": "لەبەرگرتنەوە بۆ شێوازەکانی کۆگا", "copyToFileStyles": "لەبەرگرتنەوە بۆ شێوازەکانی فایل", + "styleXAlreadyExistsInStorage": "شێوازێک بە ناوی \"{0}\" پێشتر لە کۆگادا هەیە.\n\nجێگیری بکرێتەوە، یان هەردووکیان بهێڵدرێنەوە (کۆپییەکە ناوێکی نوێ وەردەگرێت)؟", + "styleXAlreadyExistsInFile": "شێوازێک بە ناوی \"{0}\" پێشتر لە فایلەکەدا هەیە.\n\nجێگیری بکرێتەوە، یان هەردووکیان بهێڵدرێنەوە (کۆپییەکە ناوێکی نوێ وەردەگرێت)؟", + "styleNameCannotBeEmpty": "ناوی شێواز ناتوانێت بەتاڵ بێت.", + "styleNameXIsUsedMoreThanOnce": "ناوی شێوازی \"{0}\" لەلایەن زیاتر لە یەک شێواز لە فایلەکەدا بەکارهاتووە.", + "overwrite": "جێگیرکردنەوە", + "keepBoth": "هێشتنەوەی هەردووکیان", + "doThisForAllConflictingStyles": "ئەمە بۆ هەموو شێوازە ناکۆکەکان بکە", "setStyleAsDefault": "دانانی شێواز وەک بنەڕەتی", "takeUsagesFromDotDotDot": "وەرگرتنی بەکارهێنانەکان لە...", "replaceStyleWithDotDotDot": "جێگرتنەوەی شێواز بە...", "noAttachmentsFound": "هیچ هاوپێچێک لە فایلی ASSA ی هەڵبژێردراودا نەدۆزرایەوە.", "deleteStyleQuestion": "سڕینەوەی شێواز؟", "deleteStylesQuestion": "سڕینەوەی شێوازەکان؟", + "deleteXStylesQuestion": "دەتەوێت {0} شێواز بسڕیتەوە؟", + "deleteXStylesFromStorageQuestion": "دەتەوێت {0} شێواز لە کۆگا بسڕیتەوە؟", + "deleteStyleXFromStorageQuestion": "دەتەوێت شێوازی \"{0}\" لە کۆگا بسڕیتەوە؟", + "deleteStyleXFromFileQuestion": "دەتەوێت شێوازی \"{0}\" لە فایلی ئێستا بسڕیتەوە؟", "openStyleImportFile": "کردنەوەی فایلی ژێرنووس بۆ هێنانەناوی شێوازەکان", + "styleImportFiles": "فایلەکانی شێواز (.ass، .sty، هاوپۆلەکانی Subtitle Edit 4 .template)", + "ssaStyleImportFiles": "فایلەکانی شێواز (.ssa، .ass، .sty)", "primary": "سەرەکی", "secondary": "لاوەکی", "applyOverrideTags": "جێبەجێکردنی تەگی override", @@ -3828,7 +4618,7 @@ "detailGapToPrevious": "بۆ پێشوو: {0} < {1} میلی چرکە", "detailGapToNext": "بۆ دواتر: {0} < {1} میلی چرکە", "detail": "وردەکاری", - "tip": "دوو کرتە لەسەر دێڕێک بکە (یان Enter دابگرە) بۆ چوون بۆی. ئەو پشکنینانەی جێبەجێ دەکرێن و سنوورەکانیان لە ڕێکخستنەکان > گشتیدا دانراون.", + "tip": "دوو کرتە لەسەر دێڕێک بکە (یان Enter دابگرە) بۆ چوون بۆی. ئەو پشکنینانەی جێبەجێ دەکرێن و سنوورەکانیان لە ڕێکخستنەکان > گشتیدا دانراون.", "exportAsText": "فایلی دەق (.txt)...", "exportAsExcel": "فایلی Excel (.xlsx)...", "exportAsHtml": "پەڕەی وێب (.html)...", diff --git a/src/ui/Assets/Languages/ChineseSimplified.json b/src/ui/Assets/Languages/ChineseSimplified.json index 1767f4d1868..6aee7a70ace 100644 --- a/src/ui/Assets/Languages/ChineseSimplified.json +++ b/src/ui/Assets/Languages/ChineseSimplified.json @@ -1,6 +1,6 @@ { "title": "Subtitle Edit", - "version": "v5.3.0-beta22", + "version": "v5.3.0", "translatedBy": "wuwufei | FeiXJ | Leon Cheung | larsenlouis | VK | nkh0472 | 蒙太奇字幕组(MontageSubs) | emsb8888-prog", "cultureName": "zh-Hans", "general": { @@ -443,6 +443,7 @@ "percent": "百分比", "pickLayer": "设置图层", "pickOllamaModel": "选择 Ollama 模型", + "pickModel": "选择模型", "llamaCppUseRemoteServer": "使用外部服务器 (URL)", "llamaCppEngineSettings": "llama.cpp 设置", "llamaCppEngineSettingsSubtitle": "本地 llama.cpp 服务器版本", @@ -594,6 +595,8 @@ "showStartColumn": "显示“开始”列", "showHideColumn": "显示“隐藏”列", "showHistory": "显示历史记录(用于撤消)", + "clearHistory": "清除历史", + "removeFromHistory": "从历史中移除", "showLayerColumn": "显示“图层”列", "showPreview": "显示预览", "showShotChangesList": "显示镜头切换列表", @@ -655,12 +658,14 @@ "styleExaggeration": "样式夸张化", "styles": "样式", "subtitleFile": "字幕文件", + "subtitleFileChangedOutsideX": "文件“{0}”已在 Subtitle Edit 外部被修改。\n\n是否重新加载并放弃您的更改?", "subtitleFileName": "字幕文件名", "subtitleFileSaved": "字幕文件已保存", "subtitleFileSavedToX": "字幕文件已保存至 {0}", "subtitleFiles": "字幕文件", "subtitleFormats": "字幕格式", "subtitleLoadedX": "已加载字幕 “{0}”", + "subtitleReloadedChangedOutsideX": "已重新加载字幕(在 Subtitle Edit 外部被修改): {0}", "suffix": "后缀", "suggestions": "建议", "sync": "同步", @@ -744,6 +749,14 @@ "videoCustom3ForwardX": "视频,自定义毫秒 ({0:#,###,##0}) 前进,3", "videoCustom4BackX": "视频,自定义毫秒 ({0:#,###,##0}) 后退,4", "videoCustom4ForwardX": "视频,自定义毫秒 ({0:#,###,##0}) 前进,4", + "videoCustom1BackAndPauseX": "视频,自定义毫秒数 ({0:#,###,##0}) 后退并暂停,1", + "videoCustom1ForwardAndPauseX": "视频,自定义毫秒数 ({0:#,###,##0}) 前进并暂停,1", + "videoCustom2BackAndPauseX": "视频,自定义毫秒数 ({0:#,###,##0}) 后退并暂停,2", + "videoCustom2ForwardAndPauseX": "视频,自定义毫秒数 ({0:#,###,##0}) 前进并暂停,2", + "videoCustom3BackAndPauseX": "视频,自定义毫秒数 ({0:#,###,##0}) 后退并暂停,3", + "videoCustom3ForwardAndPauseX": "视频,自定义毫秒数 ({0:#,###,##0}) 前进并暂停,3", + "videoCustom4BackAndPauseX": "视频,自定义毫秒数 ({0:#,###,##0}) 后退并暂停,4", + "videoCustom4ForwardAndPauseX": "视频,自定义毫秒数 ({0:#,###,##0}) 前进并暂停,4", "videoExtension": "视频文件扩展名", "videoFile": "视频文件", "videoFileGenerated": "视频文件已生成", @@ -840,8 +853,7 @@ "transcriptionProgressServerFormat": "服务器:{0}", "transcriptionProgressModelFormat": "模型:{0}", "transcriptionProgressReceivedSegmentsFormat": "已接收片段 ({0})", - "transcriptionProgressModelAuto": "(自动)", - "openAiCompatibleSttModel": "模型" + "transcriptionProgressModelAuto": "(自动)" }, "main": { "menu": { @@ -880,6 +892,7 @@ "multipleReplace": "多重替换...", "fillSelectedLinesWithClipboard": "使用剪贴板文本填充选中字幕行", "goToLineNumber": "转到字幕编号...", + "commandPalette": "命令面板(_P)...", "rightToLeftMode": "从右到左模式...", "modifySelectionDotDotDot": "修改选区...", "tools": "工具", @@ -1072,6 +1085,9 @@ "charactersPerSecond": "字符/秒: {0}", "chooseColumn": "选择列", "columnPaste": "列粘贴", + "commandPaletteTitle": "命令面板", + "commandPaletteSearchPlaceholder": "输入以搜索命令...", + "commandPaletteNoCommandsFound": "没有匹配的命令", "createdEmptyTranslation": "从当前字幕创建了空的翻译", "deleteText": "删除文本", "deleteTextAndShiftCellsUp": "删除文本并上移单元格", @@ -1164,6 +1180,7 @@ "originalIsReadOnlyNotSaved": "原始字幕为只读参考,未被保存", "originalIsEmptyNotSaved": "原始字幕没有任何文本,因此未保存", "originalIsReadOnlyReference": "原始字幕以只读参考模式打开", + "closeTranslationQuestionXY": "关闭翻译“{0}”?\n\n原始字幕“{1}”将成为您编辑的字幕。翻译文件本身不会被更改。", "allowEditOfOriginalSubtitle": "允许编辑原始字幕", "showAllOriginalLinesX": "显示所有原始字幕行(其中 {0} 行在当前无匹配项)", "showAllOriginalLinesHint": "无匹配项的行将作为额外行显示,以便您查看翻译中缺失的内容。", @@ -1328,7 +1345,8 @@ "forcedAlignerIsolatingSpeech": "正在隔离语音... {0}", "forcedAlignerIsolatingSpeechFailed": "无法隔离语音——结束时间改为根据阅读时间设置。", "forcedAlignerModel": "对齐模型", - "forcedAlignerDownloadEngine": "下载 / 更新引擎..." + "forcedAlignerDownloadEngine": "下载 / 更新引擎...", + "forcedAlignerEstimatedXOfY": "{1} 行中有 {0} 行无法对齐,已按阅读时间放置——其时间码仅为估计值。脚本可能比视频中实际说出的内容更长。" }, "export": { "exportImagesProfiles": "导出图像配置文件", @@ -1531,13 +1549,7 @@ "fadeUpTime": "淡入时间", "fadeDownTime": "淡出时间", "zPosition": "Z 轴位置", - "zPositionHelp": "正数使文本向远处移动,负数使文本向近处移动;若 Z 轴位置为零,则为 2D 效果。", - "font": "字体", - "generate": "生成", - "fontColor": "字体颜色", - "fontSize": "字体大小", - "frames": "帧数", - "export": "导出..." + "zPositionHelp": "正数使文本向远处移动,负数使文本向近处移动;若 Z 轴位置为零,则为 2D 效果。" }, "webVtt": { "setStylesForSelectedLinesTitle": "为所选字幕行设置 WebVTT 样式", @@ -1643,8 +1655,7 @@ "propertyTopExtent": "顶部范围", "propertyBottomOrigin": "底部原点", "propertyBottomExtent": "底部范围", - "xProperties": "{0} 属性", - "propertyFileExtension": "文件扩展名" + "xProperties": "{0} 属性" }, "edit": { "modifySelection": { @@ -2072,9 +2083,7 @@ "playAligned": "已对齐", "playAlignedHint": "使用对齐后的时间码播放所选行", "apply": "应用", - "applyHint": "取消勾选某行以保留其原始时间码。双击某行可播放。", - "playOriginal": "原始", - "playOriginalHint": "使用原始时间码播放所选行" + "applyHint": "取消勾选某行以保留其原始时间码。双击某行可播放。" }, "beautifyTimeCodesProfile": { "title": "编辑时间码美化预设", @@ -2112,8 +2121,7 @@ "hintGap": "字幕之间的最小间隔,单位为帧;下方所有间隔参数均以此数值为基准。", "hintZones": "基于最近镜头切换点设置吸附阈值(红色)与预警阈值(绿色),单位帧;从左至右四组数值:左侧绿色预警、左侧强制吸附、右侧强制吸附、右侧绿色预警。", "hintConnected": "若两段字幕判定为关联(间隔低于阈值),将保留距离镜头切换点最近一侧的时间间隔。", - "hintChaining": "控制两段临近字幕的处理逻辑:可同步拉长,或相互拉开间距,取决于两者之间是否存在镜头切换。", - "general": "通用设置" + "hintChaining": "控制两段临近字幕的处理逻辑:可同步拉长,或相互拉开间距,取决于两者之间是否存在镜头切换。" }, "bridgeGaps": { "bridgeGapsSmallerThan": "桥接小于此值的间隔(毫秒)", @@ -2190,6 +2198,18 @@ "addFolderRecursiveDotDotDot": "递归添加文件夹...", "selectFolderToConvert": "选择包含待转换文件的文件夹", "includeSubfolders": "添加文件夹时包含子文件夹", + "videoFilesWhenAddingFolder": "添加文件夹时的视频文件(mkv、mp4、ts...)", + "videoFilesAsk": "询问", + "videoFilesInclude": "添加其字幕轨道", + "videoFilesSkip": "跳过", + "folderContainsXVideoFiles": "该文件夹包含 {0} 个视频文件(mkv、mp4、ts...)。\n\n是否同时添加其内嵌字幕轨道,还是仅添加字幕文件?", + "addVideoFiles": "添加视频文件", + "skipVideoFiles": "仅字幕文件", + "doNotAskAgainVideoFiles": "不再询问(可在批量转换设置中更改)", + "settingsSectionOutput": "输出", + "settingsSectionAddingFiles": "添加文件", + "settingsSectionImageBasedSubtitles": "图像字幕(OCR)", + "settingsSectionOther": "其他", "keepSourceFileTimestamp": "输出文件保留源文件的日期/时间", "preventSleepWhileConverting": "转换时防止计算机休眠", "scanningFolderX": "正在扫描 {0}...", @@ -2205,7 +2225,20 @@ "transportStreamOnlyTeletext": "仅图文电视", "transportStreamGetSizeFromVideo": "从视频获取大小...", "twoLetterLanguageCodeUppercase": "双字母语言代码(大写)", - "threeLetterLanguageCodeUppercase": "三字母语言代码(大写)" + "threeLetterLanguageCodeUppercase": "三字母语言代码(大写)", + "formatContainsDotDotDot": "格式包含...", + "extensionIsDotDotDot": "扩展名为...", + "statusIsError": "状态为错误", + "forcedTracksOnly": "仅强制轨道", + "xConvertedYFailedInZ": "已转换 {0:#,###,##0} 个,失败 {1:#,###,##0} 个,用时 {2}", + "xNotProcessed": "{0:#,###,##0} 个未处理", + "removeUnicodeControlCharactersTitle": "移除 Unicode 控制字符", + "removeUnicodeControlCharactersInfo": "从文本中移除不可见的 Unicode 方向控制字符(LRM、RLM、LRE、RLE、PDF、LRO 和 RLO),并将不换行空格替换为普通空格。", + "preset": "预设", + "savePresetDotDotDot": "保存预设...", + "deletePreset": "删除预设", + "presetName": "预设名称", + "deletePresetX": "删除预设“{0}”?" }, "changeCasing": { "fixNames": "修复名称", @@ -2499,7 +2532,21 @@ "correctWordsX": "无误单词: {0}", "namesX": "专有名词: {0}", "addedToDictionaryX": "添加至词典: {0}", - "doNotShowThisAgain": "不再显示此提示" + "doNotShowThisAgain": "不再显示此提示", + "editUseAlwaysList": "编辑“总是使用”列表...", + "useAlwaysListTitle": "拼写检查 - “总是使用”列表", + "useAlwaysListHeader": "“总是使用”列表", + "useAlwaysListDescription": "拼写检查自动替换的单词,由“全部更改”和“总是使用”保存。仅当单词拼写错误时才会使用该词对。", + "useAlwaysWord": "拼错的单词", + "xPairs": "{0:#,##0} 个词对", + "xUnusedPairs": "{0:#,##0} 个从未使用", + "useAlwaysUnusedHint": "从未使用——该单词拼写正确(或为名称或用户单词),因此拼写检查从不标记它", + "removeUnused": "移除未使用的", + "useAlwaysEmpty": "此语言尚无“总是使用”词对", + "useAlwaysEmptyHint": "在拼写检查中点击“全部更改”或“总是使用”时会添加词对。", + "useAlwaysNoMatch": "没有与搜索匹配的词对", + "useAlwaysSameWord": "替换词必须与原单词不同。", + "useAlwaysRememberOff": "设置中已关闭“记住‘总是使用’列表”,因此拼写检查不会使用或保存此列表。" }, "video": { "burnIn": { @@ -2680,6 +2727,12 @@ "speakFromCurrentLineHint": "从当前行开始逐行朗读。按 Escape 或点击另一行可停止。", "stopSpeaking": "停止朗读", "speakingLineX": "正在朗读第 {0} 行...(按 Escape 停止)", + "playWithSpeechTitle": "带语音播放", + "playWithSpeechDotDotDot": "带语音播放...", + "playWithSpeechHint": "播放视频,并在视频播放到每一行时朗读该行。可照常暂停和跳转;按 Escape 停止。", + "lowerVideoVolumeWhileSpeaking": "朗读时降低视频音量", + "pauseVideoWhenSpeechRunsLate": "语音延续到下一行时暂停视频", + "waitingForSpeechLineX": "正在为第 {0} 行生成语音...(按 Escape 停止)", "voiceKindClone": "克隆语音", "voiceKindPreset": "内置", "voiceKindModel": "语音模型", @@ -2744,6 +2797,12 @@ "advancedTtsSettings": "高级 TTS 语音合成设置", "advancedTtsAudioProcessing": "音频处理", "advancedTtsOutput": "输出", + "advancedTtsVoiceCloning": "声音克隆", + "cleanCloneReferences": "清理声音克隆参考音频(Sidon)", + "cleanCloneReferencesDescription": "从用于克隆声音的音频中移除音乐、噪音和混响:包括从视频中截取的片段以及在声音管理器中导入的录音。使用 audio.cpp 上的 Sidon 语音修复模型(需下载 940 MB)。克隆的声音不再复制背景,但非常安静或重叠的语音可能会变得发闷。", + "cleaningCloneReferencesDotDotDot": "正在清理声音克隆参考音频...", + "downloadSidonTitle": "下载 Sidon?", + "downloadSidonQuestionX": "清理声音克隆参考音频需要 Sidon 语音修复模型({0})。\n\n现在下载吗?如果不下载,参考音频将按原样使用。", "proAudioPostProcessing": "专业音频后处理", "proAudioPostProcessingDescription": "对每段音频应用均衡器暖化、噪声门、压缩、响度归一化(-16 LUFS)以及淡入淡出效果。", "generatingSpeechSegmentXOfY": "正在生成语音:第 {0} 段,共 {1} 段", @@ -2756,6 +2815,10 @@ "removeOriginalSpeechDescription": "保留原始视频的音乐和音效但移除其语音,并将新语音混合在其上。有 GPU 时约耗时与视频本身相当,无 GPU 时则长很多倍。开启音频闪避时,其音量适用于音乐和音效。仅在将语音添加到视频文件时适用。", "removingOriginalSpeech": "正在移除原始语音...", "removeOriginalSpeechFailed": "无法移除原始语音——语音已在不移除的情况下添加到视频。", + "speechRemovalModel": "模型", + "speechRemovalModelDescription": "最佳质量能最大程度保留音乐和音效。快速模式约快三倍,但音乐损失稍多。", + "speechRemovalBestQuality": "最佳质量(Mel-Band RoFormer)", + "speechRemovalFast": "快速(HTDemucs)", "videoTruncatedWarning": "新视频文件中的画面在 {0} 后停止,但原始视频长度为 {1}——画面将冻结而声音继续。\n\n文件已保留。请查看 Subtitle Edit 数据文件夹中的 error-log.txt 以获取 ffmpeg 输出。", "originalVolumePercent": "原始音量百分比", "vadSilenceCompression": "VAD 静音压缩", @@ -2827,8 +2890,7 @@ "subtitleUpdatedFromReviewSingular": "已根据语音审阅更新1条字幕", "subtitleUpdatedFromReviewPlural": "已根据语音审阅更新 {0} 条字幕", "subtitleMergedLinesAppliedSingular": "已应用1条文本转语音字幕合并操作", - "subtitleMergedLinesAppliedPlural": "已应用 {0} 条文本转语音字幕合并操作", - "cloneVoicePerLineNeedsOriginalSubtitleX": "{0} 只有在知道视频中该行说了什么时才能克隆该行的语音,而这需要原始语言的字幕。当前未加载原始字幕,因此每一行都会使用第一个导入的语音来朗读,而不是视频中的语音。\n\n请在译文字幕旁打开原始字幕(文件 > 打开原始文件...),然后重新生成。" + "subtitleMergedLinesAppliedPlural": "已应用 {0} 条文本转语音字幕合并操作" }, "shotChanges": { "generateShotChanges": "生成场景切换", @@ -2881,9 +2943,7 @@ "writtenToX": "章节已写入 {0}", "unableToWriteChapters": "无法将章节写入视频文件。", "chaptersSavedToX": "章节已保存至 {0}", - "emptyListCallToAction": "暂无章节 - 请在当前视频位置添加,或从视频/文件中导入。", - "apply": "应用", - "noVideoLoaded": "未加载视频" + "emptyListCallToAction": "暂无章节 - 请在当前视频位置添加,或从视频/文件中导入。" }, "videoOcr": { "title": "内嵌字幕 OCR 识别", @@ -2934,8 +2994,7 @@ "engineCloudApi": "云 API(需要 API 密钥)", "engineNothingToInstall": "无需安装", "engineModelsMissing": "引擎已安装,模型缺失", - "website": "网站", - "dictionary": "词典" + "website": "网站" }, "backgroundMusic": { "title": "生成背景音乐", @@ -3142,17 +3201,7 @@ "ttsEngineNotInstalled": "{0} 未安装", "ttsEngineUpdateAvailable": "{0} - 有可用更新", "ttsEngineTooOldUpdateRequired": "{0} 版本过旧 - 需要更新", - "ttsEngineChatterboxCapable": "{0} (支持 Chatterbox)", - "ttsUpdateX": "更新 {0}", - "beautifyTimeCodes": { - "useExactTimeCodes": "使用视频的精确时间码", - "extractTimeCodes": "提取时间码", - "extractingTimeCodes": "正在提取时间码...", - "noTimeCodesLoaded": "未加载时间码", - "xTimeCodesLoaded": "已加载 {0} 个时间码", - "timeCodesExtractFailed": "无法从视频读取时间码", - "timeCodesHint": "将字幕对齐到视频的真实帧时间,而非假定恒定帧率。需完整读取视频一次,结果将被缓存。" - } + "ttsEngineChatterboxCapable": "{0} (支持 Chatterbox)" }, "waveform": { "guessTimeCodes": "猜测时间码", @@ -3224,7 +3273,8 @@ "syncPoints": "同步点", "pointSync": "取点同步", "pointSyncViaOther": "通过其他字幕取点同步", - "syncPointCandidateInfo": "在 {0}+ 秒静音后开始的字幕行会高亮显示——它们通常是可靠的同步点", + "syncPointCandidateAfterInfo": "其后有 {0}+ 秒静音的行会被高亮显示——它们通常是可靠的同步点", + "gapAfter": "之后间隔", "adjustAllShortcuts": "键盘快捷键:\r\n\r\n• Shift + 左/右:移动 10 毫秒\r\n• Ctrl + 左/右:移动 100 毫秒\r\n• Alt + 左/右:移动 500 毫秒", "adjustAllShortcutsFrames": "键盘快捷键:\r\n\r\n• Shift + 左/右:移动 1 帧\r\n• Ctrl + 左/右:移动 10 帧\r\n• Alt + 左/右:移动 1 秒", "offsetInSeconds": "偏移秒数", @@ -3288,9 +3338,7 @@ "editTranslationHint": "点击所选行的译文进行编辑(回车保存,Esc 取消)", "llamaCppDownloadEngineAndModelPrompt": "llama.cpp 需要下载 llama-server 引擎和翻译模型。是否立即下载?", "llamaCppDownloadEnginePrompt": "llama.cpp 需要下载 llama-server 引擎。是否立即下载?", - "llamaCppDownloadModelPrompt": "llama.cpp 需要下载所选的翻译模型。是否立即下载?", - "advancedDotDotDot": "高级...", - "advancedSettings": "高级设置" + "llamaCppDownloadModelPrompt": "llama.cpp 需要下载所选的翻译模型。是否立即下载?" }, "options": { "settings": { @@ -3362,7 +3410,7 @@ "singleLineMaxLength": "单行最大长度", "optimalCharsPerSec": "最佳每秒字节数", "maxCharsPerSec": "最大每秒字频", - "maxWordsPerMin": "最大每分词频 ", + "maxWordsPerMin": "最大每分词频", "minDurationMs": "最小时长,毫秒", "maxDurationMs": "最大时长,毫秒", "minGapMs": "字幕间最小间隔,单位毫秒", @@ -3389,6 +3437,7 @@ "promptBeforeDelete": "删除前提示确认", "rememberPositionAndSize": "记住主窗口的位置和大小", "titleBarFullFileName": "在标题栏中显示完整文件路径", + "showRecentFiles": "显示最近的文件(用于重新打开)", "openLastFileOnStart": "启动时打开最近文件", "autoSave": "自动保存(编辑时自动保存当前打开文件)", "autoBackupOn": "自动备份", @@ -3416,6 +3465,7 @@ "waveformUseSkiaRenderer": "使用实验性快速渲染器", "waveformCenterVideoPositionAlsoWhenPaused": "暂停时也使视频位置居中", "waveformSelectCurrentSubtitleWhilePaused": "暂停时也选中当前字幕", + "waveformRememberZoom": "记住缩放(水平和垂直)", "waveformShowToolbar": "显示工具栏", "waveformShowToolbarEdit": "编辑工具栏...", "waveformShowToolbarEditLabel": "工具栏项目", @@ -3481,6 +3531,7 @@ "adjustAllTimesRememberLineSelectionChoice": "调整所有时间,记住行选择选项", "mergeKeepEndTime": "合并字幕行:保留结束时间(允许与下一条字幕重叠)", "mergeKeepEndTimeOnlyAssa": "合并字幕行:仅对 \"Advanced Sub Station Alpha\" 格式保留结束时间", + "mergeAutoBreak": "合并行:自动换行合并后的文本(关闭则保留各行原有的换行)", "filesAndLogs": "文件和日志", "showErrorLogFile": "显示错误日志文件", "showToolsLogFile": "显示工具日志文件", @@ -3521,9 +3572,11 @@ "timeCodeModeTime": "时间(hh:mm:ss,zzz)", "timeCodeModeFrames": "帧(hh:mm:ss:ff)", "timeCodeModeFrameNumbers": "帧编号", + "frameModeSnapToVideoFrames": "将时间存储在视频帧上(而非每秒内的帧)", "textBoxLimitNewLines": "限制字幕文本框中的行数", "mpvOpenGl": "libmpv - OpenGL 渲染", "mpvSoftwareRendering": "libmpv - 软件渲染(慢)", + "mpvMetalRendering": "libmpv - Metal(实验性,需要重启)", "mpvWidRendering": "libmpv - 原生窗口 ID 渲染", "waveFormsAndSpectrogramFoldersContainsX": "“Waveforms” 和 “Spectrogram” 文件夹包含 {0}", "deleteWaveformAndSpectrogramFoldersQuestion": "清空 “频谱图” 和 “波形” 文件夹", @@ -3617,6 +3670,10 @@ "usePositionFromSubtitleFile": "使用字幕文件中的位置信息 (TTML/PAC/EBU STL)", "marginIsPartOfSubtitleArea": "边距属于字幕区域的一部分", "textJustify": "两端对齐行", + "deinterlace": "视频去隔行", + "deinterlaceOff": "关闭", + "deinterlaceAuto": "自动(仅隔行帧)", + "deinterlaceAlways": "总是", "pixelWidthInfo": "绿色线条 = 最大宽度限制 | 红色区域 = 文字超出限制", "spellCheckEngineHunSpelll": "Hunspell", "spellCheckEngineMsWord": "MS Word", @@ -3634,10 +3691,7 @@ "waveformThemeForest": "森林", "waveformThemeMidnightPurple": "午夜紫", "waveformThemeRetroAmber": "复古琥珀", - "waveformThemeIce": "冰", - "showStopButton": "显示“停止”按钮", - "showFullscreenButton": "显示“全屏幕”按钮", - "useFrameMode": "使用帧模式(时.分.秒.帧)" + "waveformThemeIce": "冰" }, "shortcuts": { "searchShortcuts": "搜索快捷键...", @@ -3969,9 +4023,7 @@ "moveSelectedLinesAndForwardCustomMsBack": "将所选行及所有后续行向后移动自定义毫秒数({0:#,###,##0}),{1}", "moveSelectedLinesAndForwardCustomMsForward": "将所选行及所有后续行向前移动自定义毫秒数({0:#,###,##0}),{1}", "moveAllLinesCustomMsBack": "将所有行向后移动自定义毫秒数({0:#,###,##0}),{1}", - "moveAllLinesCustomMsForward": "将所有行向前移动自定义毫秒数({0:#,###,##0}),{1}", - "setActorXY": "设置角色 {0}: {1}", - "setActorSlotXTitle": "角色栏 {0}" + "moveAllLinesCustomMsForward": "将所有行向前移动自定义毫秒数({0:#,###,##0}),{1}" }, "wordLists": { "title": "词汇表", @@ -4174,9 +4226,26 @@ "crispEmbedSettingsTitle": "CrispEmbed 设置", "crispEmbedDescription": "支持多种模型后端的本地 OCR 引擎 - 在此处下载引擎和模型。", "trainNOcrDatabase": "训练 nOCR 数据库...", + "optimizeNOcrDatabase": "优化 nOCR 数据库...", + "optimizeNOcrDatabaseQuestionXY": "移除 nOCR 数据库“{0}”中无法将字符与相似字符区分开的线条?\n\n这会使数据库更小、OCR 更快,通常错误也更少。最多需要一分钟。\n\n备份将保存为“{1}”。", + "optimizingNOcrDatabaseXY": "正在优化 nOCR 数据库... {0}/{1}", + "nOcrDatabaseOptimizedXYZ": "nOCR 数据库“{0}”已优化:{1:#,##0} -> {2:#,##0} 条线条。", "startTraining": "开始训练", "abortTraining": "中止训练", "trainingOptions": "训练选项", + "trainNOcrSubtitle": "使用已安装的字体渲染字符,并让 nOCR 学习其形状。请选择您的字幕所使用的字体。", + "xFontsSelected": "已选择 {0:#,##0} 个", + "subtitleFonts": "字幕字体", + "emptyDatabase": "空数据库", + "startFromDatabase": "起始于", + "startFromDatabaseHint": "将训练的字符添加到现有数据库的副本中(推荐),或从空数据库开始", + "fontSizes": "字体大小", + "fontSizesHint": "要渲染的字体大小(像素),以逗号分隔,例如“30, 40”", + "numberOfLineSegmentsHint": "每个字符检查的线条数——越多越严格但越慢", + "enterDatabaseName": "输入数据库名称", + "trainingFontXSizeYCharacterZ": "正在训练“{0}”,{1} px:{2}", + "xLearned": "已学习 {0:#,##0} 个", + "xSkipped": "已跳过 {0:#,##0} 个", "charactersToTrain": "待训练的字符", "letterCombinationsToTrain": "可能被合并为单张图像的字母组合", "importCharactersFromSubtitleFile": "从字幕文件导入字符...", @@ -4203,6 +4272,8 @@ "drawZoomOut": "缩小(Ctrl+-)", "drawResetView": "重置视图(Ctrl+0)", "drawToggleGrid": "切换网格(Ctrl+G)", + "drawTogglePreview": "切换预览(F9)", + "drawPreviewFailed": "预览失败——需要带有 libass(subtitles 滤镜)的 ffmpeg", "drawCopyToClipboard": "复制到剪贴板(Ctrl+C)", "drawShapes": "形状", "drawSelectedPoint": "已选点", @@ -4210,6 +4281,81 @@ "drawSelectedLayer": "已选图层", "drawToolX": "工具: {0}", "drawHelpText": "点击添加点 • Enter/F8 闭合形状 • Shift+拖动平移 • Ctrl+滚轮缩放", + "drawMoveToLayer": "移动到图层", + "drawFlipHorizontal": "水平翻转", + "drawFlipVertical": "垂直翻转", + "drawHideShape": "隐藏形状", + "drawShowShape": "显示形状", + "drawHideLayer": "隐藏图层", + "drawShowLayer": "显示图层", + "drawDeleteLayer": "删除图层", + "drawDeletePoint": "删除点", + "drawCancelDrawing": "取消绘制", + "drawLayerX": "图层 {0}", + "drawImportSvg": "导入 SVG 图像...", + "drawRotateClockwise": "顺时针旋转 90°", + "drawConvertToCurve": "将线段转换为曲线", + "drawBackground": "背景", + "drawPosition": "位置", + "drawZoom": "缩放", + "drawShapeTool": "形状工具", + "drawColorPickerTool": "从形状或背景中拾取颜色", + "drawShapeLibrary": "形状库", + "drawShapeLibraryHint": "在画布上拖动以放置形状,或点击以默认大小放置。按住 Shift 保持比例。", + "drawAddToLibrary": "添加到形状库...", + "drawRemoveFromLibrary": "从形状库中移除", + "drawShapeName": "形状名称", + "drawCategorySpeechBubbles": "对话气泡", + "drawCategoryArrows": "箭头", + "drawCategoryBasicShapes": "基本形状", + "drawCategorySymbols": "符号", + "drawCategoryMyShapes": "我的形状", + "drawShapeSpeechBubble": "对话气泡", + "drawShapeSpeechBubbleRight": "对话气泡,尾部向右", + "drawShapeSpeechBubbleUp": "对话气泡,尾部向上", + "drawShapeOvalBubble": "椭圆对话气泡", + "drawShapeBoxBubble": "方框对话气泡", + "drawShapeThoughtBubble": "思考气泡", + "drawShapeShoutBubble": "喊叫气泡", + "drawShapeCaptionBox": "说明框", + "drawShapeArrow": "箭头", + "drawShapeDoubleArrow": "双向箭头", + "drawShapeChevron": "V 形箭头", + "drawShapeCurvedArrow": "弯曲箭头", + "drawShapeRoundedRectangle": "圆角矩形", + "drawShapeTriangle": "三角形", + "drawShapeDiamond": "菱形", + "drawShapePentagon": "五边形", + "drawShapeHexagon": "六边形", + "drawShapeOctagon": "八边形", + "drawShapeStar": "星形", + "drawShapeRing": "圆环", + "drawShapePlus": "加号", + "drawShapeBanner": "横幅", + "drawShapeHeart": "心形", + "drawShapeCheck": "对勾", + "drawShapeMusicNote": "音符", + "drawShapeMusicNotes": "多个音符", + "drawShapeLightning": "闪电", + "drawShapeCloud": "云朵", + "drawShapeMoon": "月亮", + "drawShapeBadge": "徽章", + "drawSize": "大小", + "drawLayer": "图层", + "drawBackgroundVideoFrame": "视频帧(当前位置)", + "drawBackgroundVideoFrameAt": "指定时间的视频帧...", + "drawBackgroundImage": "图像文件...", + "drawBackgroundNone": "无背景", + "drawBackgroundStretch": "将图像拉伸至画布", + "drawBackgroundOpacity": "背景不透明度", + "drawBackgroundVideoFrameFailed": "无法获取 {0} 处的视频帧。", + "drawImages": "图像", + "drawConvertToLine": "将线段转换为直线", + "drawConvertShapeToCurves": "将所有直线转换为曲线", + "drawConvertShapeToLines": "将所有曲线转换为直线", + "drawRotateCounterClockwise": "逆时针旋转 90°", + "drawSvgImages": "SVG 图像", + "drawSvgNoShapes": "在 SVG 文件中未找到填充形状或描边。", "progressBarTitle": "ASSA 进度条", "progressBarSettings": "进度条", "mouseOverColor": "鼠标悬停颜色", diff --git a/src/ui/Assets/Languages/ChineseTraditional.json b/src/ui/Assets/Languages/ChineseTraditional.json index 2f685a80fcf..ac43ac02fa9 100644 --- a/src/ui/Assets/Languages/ChineseTraditional.json +++ b/src/ui/Assets/Languages/ChineseTraditional.json @@ -1,6 +1,6 @@ { "title": "Subtitle Edit", - "version": "v5.3.0-rc4", + "version": "v5.3.0", "translatedBy": "昇(以下舊譯者:BAWAN、風逸蘭、Jackie Lam、SiderealArt)", "cultureName": "zh-Hant", "general": { @@ -1096,7 +1096,7 @@ "errorLoadBinaryZeroes": "抱歉,此檔案包含僅二進位的零!\n\n如果您曾使用 Subtitle Edit 編輯過此檔案,或許可以經由「檔案 → 還原自動備份……」功能找回備份...", "errorLoadGZip": "此檔案似乎是壓縮的 GZip 檔案。\n\nSubtitle Edit 無法開啟壓縮檔案。", "errorLoadLargeFile": "此檔案太大,無法作為字幕檔開啟。", - "errorLoadVideoFilePrompt": "此檔案似乎是視訊檔案:\n{0}\n\n您可以經由「視訊 -> 開啟視訊」或將視訊拖曳至視訊播放器來開啟視訊。\n\n確定要將其作為視訊檔案開啟嗎?", + "errorLoadVideoFilePrompt": "此檔案似乎是視訊檔案:\n{0}\n\n您可以經由「視訊 -> 開啟視訊」或將視訊拖曳至視訊播放器來開啟視訊。\n\n確定要將其作為視訊檔案開啟嗎?", "errorLoadJpg": "此檔案似乎是 JPG圖像檔案。\n\nSubtitle Edit 無法開啟圖像檔案。", "errorLoadPng": "此檔案似乎是 PNG圖像檔案。\n\nSubtitle Edit 無法開啟圖像檔案。", "errorLoadRar": "此檔案似乎是 RAR壓縮檔案。\n\nSubtitle Edit 無法開啟壓縮檔案。", @@ -1346,7 +1346,7 @@ "forcedAlignerIsolatingSpeechFailed": "無法隔離語音 - 結尾時間已改為從閱覽時間設置。", "forcedAlignerModel": "對齊器模型", "forcedAlignerDownloadEngine": "下載/更新引擎...", - "forcedAlignerEstimatedXOfY": "有 {0} / {1} 行未能對齊,已依閱覽時間配置 - 其時間碼僅為估算值。劇本可能比視訊中實際朗讀內容更長。" + "forcedAlignerEstimatedXOfY": "有 {0} / {1} 行未能對齊,已依閱覽時間配置 - 其時間碼僅為估算值。劇本可能比視訊中實際朗讀內容更長。" }, "export": { "exportImagesProfiles": "匯出圖像設定檔", @@ -3099,34 +3099,34 @@ "cutVideoSplitSegments": "分別儲存各區段", "cutVideoAlsoCutSubtitle": "同時剪輯字幕", "cutVideoSubtitleFileGeneratedX": "已產生的字幕檔:{0}", - "cutVideoTransition": "區段之間的轉場", - "cutVideoTransitionDurationSeconds": "持續時間(秒)", - "cutVideoFadeInSeconds": "起頭從黑畫面淡入(秒)", - "cutVideoFadeOutSeconds": "結尾淡出至黑畫面(秒)", - "cutVideoPreviewTransition": "預覽轉場", - "cutVideoPreviewTransitionTitle": "轉場預覽", - "cutVideoPreviewNeedsJoin": "無可預覽的拼接處 - 請至少新增兩個要合併的區段,或從區段中間剪除。", - "cutVideoTransitionInfoX": "每個轉場都會重疊部分拼接處,因此每次拼接會使輸出時間縮短 {0} 秒。剪輯字幕會重新對齊時間軸以相符。", - "cutVideoTransitionFade": "淡化", - "cutVideoTransitionFadeBlack": "淡化入黑而出", - "cutVideoTransitionFadeWhite": "淡化入白而出", - "cutVideoTransitionFadeGrays": "淡化入灰而出", - "cutVideoTransitionDissolve": "交叉溶解", - "cutVideoTransitionWipeLeft": "向左劃過", - "cutVideoTransitionWipeRight": "向右劃過", - "cutVideoTransitionWipeUp": "向上劃過", - "cutVideoTransitionWipeDown": "向下劃過", - "cutVideoTransitionSlideLeft": "向左滑", - "cutVideoTransitionSlideRight": "向右滑", - "cutVideoTransitionSlideUp": "向上滑", - "cutVideoTransitionSlideDown": "向下滑", - "cutVideoTransitionSmoothLeft": "向左滑動", - "cutVideoTransitionSmoothRight": "向右滑動", - "cutVideoTransitionCircleOpen": "圓形展開", - "cutVideoTransitionCircleClose": "圓形收合", - "cutVideoTransitionRadial": "輻射化", - "cutVideoTransitionPixelize": "像素化", - "cutVideoTransitionBlur": "模糊化", + "cutVideoTransition": "區段之間的轉場", + "cutVideoTransitionDurationSeconds": "持續時間(秒)", + "cutVideoFadeInSeconds": "起頭從黑畫面淡入(秒)", + "cutVideoFadeOutSeconds": "結尾淡出至黑畫面(秒)", + "cutVideoPreviewTransition": "預覽轉場", + "cutVideoPreviewTransitionTitle": "轉場預覽", + "cutVideoPreviewNeedsJoin": "無可預覽的拼接處 - 請至少新增兩個要合併的區段,或從區段中間剪除。", + "cutVideoTransitionInfoX": "每個轉場都會重疊部分拼接處,因此每次拼接會使輸出時間縮短 {0} 秒。剪輯字幕會重新對齊時間軸以相符。", + "cutVideoTransitionFade": "淡化", + "cutVideoTransitionFadeBlack": "淡化入黑而出", + "cutVideoTransitionFadeWhite": "淡化入白而出", + "cutVideoTransitionFadeGrays": "淡化入灰而出", + "cutVideoTransitionDissolve": "交叉溶解", + "cutVideoTransitionWipeLeft": "向左劃過", + "cutVideoTransitionWipeRight": "向右劃過", + "cutVideoTransitionWipeUp": "向上劃過", + "cutVideoTransitionWipeDown": "向下劃過", + "cutVideoTransitionSlideLeft": "向左滑", + "cutVideoTransitionSlideRight": "向右滑", + "cutVideoTransitionSlideUp": "向上滑", + "cutVideoTransitionSlideDown": "向下滑", + "cutVideoTransitionSmoothLeft": "向左滑動", + "cutVideoTransitionSmoothRight": "向右滑動", + "cutVideoTransitionCircleOpen": "圓形展開", + "cutVideoTransitionCircleClose": "圓形收合", + "cutVideoTransitionRadial": "輻射化", + "cutVideoTransitionPixelize": "像素化", + "cutVideoTransitionBlur": "模糊化", "importCurrentSubtitle": "匯入目前字幕", "addRemoveEmbeddedSubtitlesTitle": "新增/移除內嵌字幕", "addRemoveEmbeddedSubtitlesMp4Title": "新增/移除內嵌字幕(MP4)", @@ -3572,6 +3572,7 @@ "timeCodeModeTime": "時間 (hh:mm:ss,zzz)", "timeCodeModeFrames": "影格 (hh:mm:ss:ff)", "timeCodeModeFrameNumbers": "影格數", + "frameModeSnapToVideoFrames": "將時間儲存於視訊影格上(而非每秒內的影格)", "textBoxLimitNewLines": "限制字幕打字框中的行數", "mpvOpenGl": "libmpv - OpenGL", "mpvSoftwareRendering": "libmpv - 軟體彩現(慢)", diff --git a/src/ui/Assets/Languages/Czech.json b/src/ui/Assets/Languages/Czech.json index 04404a909d0..6470a6f6234 100644 --- a/src/ui/Assets/Languages/Czech.json +++ b/src/ui/Assets/Languages/Czech.json @@ -1,6 +1,6 @@ { "title": "Subtitle Edit", - "version": "v5.3.0-rc2", + "version": "v5.3.0", "translatedBy": "Matěj Fouma", "cultureName": "cs-CZ", "general": { @@ -1093,7 +1093,7 @@ "deleteTextAndShiftCellsUp": "Smazat text a posunout buňky nahoru", "endTimeMustBeAfterStartTime": "Čas konce musí být před časem začátku.", "errorLoad7Zip": "Zdá se, že tento soubor je komprimovaný soubor .7-Zip.\n\nSubtitle Edit neumí otevřít komprimované soubory.", - "errorLoadBinaryZeroes": "Promiňte, tento soubor obsahuje pouze binární nuly!\n\nPokud jste tento soubor upravili v Subtitle Editu, možná najdete zálohu pomocí položky nabídky Soubor -> Obnovit automatickou zálohu…", + "errorLoadBinaryZeroes": "Promiňte, tento soubor obsahuje pouze binární nuly!\n\nPokud jste tento soubor upravili v Subtitle Editu, možná najdete zálohu pomocí položky nabídky Soubor -> Obnovit automatickou zálohu…", "errorLoadGZip": "Zdá se, že tento soubor je komprimovaný soubor GZip.\n\nSubtitle Edit neumí otevřít komprimované soubory.", "errorLoadLargeFile": "Tento soubor je pro otevření jako soubor titulků příliš veliký.", "errorLoadVideoFilePrompt": "Zdá se, že tento soubor je soubor videa:\n{0}\n\nMůžete otevřít video pomocí \"Video → Otevřít video\" nebo přetažením do videopřehrávače.\n\nChcete jej otevřít jako soubor videa?", @@ -1345,7 +1345,8 @@ "forcedAlignerIsolatingSpeech": "Izolování řeči… {0}", "forcedAlignerIsolatingSpeechFailed": "Nepodařilo se izolovat řeč – časy konce byly místo toho nastaveny na základě doby čtení.", "forcedAlignerModel": "Model pro zarovnání", - "forcedAlignerDownloadEngine": "Stáhnout / aktualizovat modul…" + "forcedAlignerDownloadEngine": "Stáhnout / aktualizovat modul…", + "forcedAlignerEstimatedXOfY": "{0} z {1} řádků nebylo možné zarovnat, a proto byly umístěny podle doby čtení - jejich časové kódy jsou pouze odhadnuté. Skript je pravděpodobně delší než to, co zazní ve videu." }, "export": { "exportImagesProfiles": "Exportovat profily obrázků", @@ -2814,6 +2815,10 @@ "removeOriginalSpeechDescription": "Zachová hudbu a zvukové efekty originálního videa, ale odebere z něj řeč a namísto ní namixuje novou řeč. S GPU to trvá přibližně stejně dlouho jako samotné video – a bez něj mnohonásobně déle. Je-li zapnuto potlačení hlasitosti pozadí, jeho hlasitost se vztáhne na hudbu a efekty. Použije se pouze v případě, že je řeč přidána do souboru videa.", "removingOriginalSpeech": "Odebírání originální řeči…", "removeOriginalSpeechFailed": "Originální řeč se nepodařilo odebrat – řeč byla přidána do videa bez ní.", + "speechRemovalModel": "Model", + "speechRemovalModelDescription": "Nejlepší kvalita zachová nejvíce hudby a zvukových efektů. Rychlý je zhruba třikrát rychlejší, ale přijde se o trochu více hudby.", + "speechRemovalBestQuality": "Nejlepší kvalita (Mel-Band RoFormer)", + "speechRemovalFast": "Rychlý (HTDemucs)", "videoTruncatedWarning": "Obraz v novém souboru videa se zastaví po {0}, ale původní video má délku {1} – obraz zamrzne, zatímco zvuk bude pokračovat.\n\nSoubor byl zachován. Výstup ffmpegu najdete v souboru error-log.txt ve složce s daty Subtitle Editu.", "originalVolumePercent": "Původní hlasitost v %", "vadSilenceCompression": "Komprese ticha pomocí VAD", @@ -3567,6 +3572,7 @@ "timeCodeModeTime": "Čas (hh:mm:ss,zzz)", "timeCodeModeFrames": "Snímky (hh:mm:ss:ff)", "timeCodeModeFrameNumbers": "Čísla snímků", + "frameModeSnapToVideoFrames": "Ukládat časy na snímky videa (ne na snímky v rámci každé sekundy)", "textBoxLimitNewLines": "Omezit počet řádků v textovém poli titulků", "mpvOpenGl": "libmpv – OpenGL", "mpvSoftwareRendering": "libmpv – softwarové vykreslování (pomalé)", @@ -4220,6 +4226,10 @@ "crispEmbedSettingsTitle": "Nastavení CrispEmbed", "crispEmbedDescription": "Lokální OCR modul s více modelovými backendy – stáhněte si modul a modely zde.", "trainNOcrDatabase": "Trénovat databázi nOCR…", + "optimizeNOcrDatabase": "Optimalizovat databázi nOCR…", + "optimizeNOcrDatabaseQuestionXY": "Odebrat z databáze nOCR \"{0}\" řádky, které nepomáhají rozlišit znaky od podobných znaků?\n\nDatabáze tak bude menší a OCR rychlejší, obvykle s menším počtem chyb. Trvá to až minutu.\n\nZáloha bude uložena jako \"{1}\".", + "optimizingNOcrDatabaseXY": "Optimalizace databáze nOCR… {0}/{1}", + "nOcrDatabaseOptimizedXYZ": "Databáze nOCR \"{0}\" optimalizována: {1:#,##0} -> {2:#,##0} řádků.", "startTraining": "Spustit trénování", "abortTraining": "Přerušit trénování", "trainingOptions": "Možnosti trénování", diff --git a/src/ui/Assets/Languages/Danish.json b/src/ui/Assets/Languages/Danish.json index 0591cf9c07a..b9586712dc4 100644 --- a/src/ui/Assets/Languages/Danish.json +++ b/src/ui/Assets/Languages/Danish.json @@ -1,12 +1,30 @@ { "title": "Subtitle Edit", - "version": "v5.2.0-rc7", + "version": "v5.3.0", "translatedBy": "", "cultureName": "da-DK", "general": { "abort": "Afbryd", "actor": "Skuespiller", "actors": "Skuespillere", + "setActorDotDotDot": "Angiv skuespiller...", + "actorPickerTitle": "Angiv skuespiller", + "actorPickerFilterHint": "Skriv for at filtrere, eller skriv et nyt skuespillernavn", + "actorPickerKeysHint": "Tryk på et tal eller Enter for at angive skuespilleren. Alt+Op/Ned ændrer rækkefølgen, Delete fjerner skuespilleren.", + "actorPickerNewActorX": "Ny skuespiller \"{0}\"", + "actorPickerLinesSelectedX": "Valgte linjer: {0}", + "setStyleDotDotDot": "Angiv stilart...", + "stylePickerTitle": "Angiv stilart", + "stylePickerFilterHint": "Skriv for at filtrere, eller skriv et nyt stilartsnavn", + "stylePickerKeysHint": "Tryk på et tal (1-9, 0) for at angive stilarten på alle valgte linjer.", + "stylePickerNewStyleX": "Ny stilart \"{0}\"", + "stylePickerNewStyleInfo": "Tryk på Enter for at tilføje denne stilart med standardindstillinger og angive den på de valgte linjer. Brug \"Administrer stilarter...\" til senere at ændre skrifttype, farver og position.", + "stylePickerCurrentStyleX": "Aktuel stilart: {0}", + "stylePickerLinesUsingStyleX": "Linjer, der bruger denne stilart: {0}", + "stylePickerMarginsXYZ": "Venstre {0}, højre {1}, lodret {2}", + "stylePickerSetStyle": "_Angiv stilart", + "stylePickerManageStyles": "Administrer stilarter...", + "stylePickerManageStylesHint": "Luk dette vindue og åbn stilartsadministrationen for at redigere skrifttyper, farver, position og mere", "add": "Tilføj", "addDotDotDot": "Tilføj...", "addToNamesListCaseSensitive": "Føj til navnelisten (der skelnes mellem store og små bogstaver)", @@ -15,7 +33,6 @@ "adjustAlpha": "Juster alfa (gennemsigtighed)", "adjustDisplayDuration": "Juster visningsvarighed", "adjustment": "Justering", - "advanced": "Avanceret", "advancedSettings": "Avancerede indstillinger", "after": "Efter", "alignment": "Justering", @@ -59,7 +76,6 @@ "bdnXml": "BDN/xml", "bdnXml8Bit": "BDN/xml 8-bit", "before": "Før", - "beforeX": "Før '{0}'", "beginning": "Begyndelse", "bluRaySup": "Blu-ray (sup)", "bold": "Fed", @@ -71,7 +87,6 @@ "bookmarkEdit": "Rediger bogmærke", "bookmarkEditForSelectedLinesX": "Rediger bogmærke for {0} valgte linjer", "bookmarksList": "Vis bogmærker", - "borderColor": "Kantfarve", "borderStyle": "Kantstil", "bottomCenter": "Bund-Center", "bottomLeft": "Nederst til venstre", @@ -98,7 +113,6 @@ "changeCasing": "Skift mellem store/små bogstaver", "changeFormatting": "Skift formatering", "changeFrameRate": "Skift billedhastighed", - "changeLanguageFilter": "Skift sprogfilter...", "changeOnce": "Skift én gang", "changeSpeed": "Skift hastighed", "character": "Tegn", @@ -140,7 +154,6 @@ "conversionCancelledByUser": "Konvertering annulleret af bruger", "convert": "Konvertér", "converted": "Konverteret", - "convertingDotDotDot": "Konverterer...", "convertingXofYDotDoDot": "Konverterer {0:#,###,##0}/{1:#,###,##0}...", "copy": "Kopiér", "copyImageToClipboard": "Kopier billede til udklipsholder", @@ -151,7 +164,6 @@ "couldNotOpenFileXErrorY": "Kunne ikke åbne filen \"{0}\". Fejl: {1}", "count": "Antal", "cps": "Tegn/sek", - "currentSubtitle": "Nuværende undertekst", "cudaRuntime": "CUDA-runtime", "currentVideoPosition": "Aktuel videoposition", "custom": "Brugerdefineret", @@ -211,7 +223,6 @@ "expand": "Udvid", "export": "Eksportér", "exportDotDotDot": "Eksportér...", - "exportToX": "Eksporter til {0}", "extendAfter": "Forlæng/forkort til linje efter", "extendBefore": "Forlæng/forkort til linje før", "extendSelectedLinesToNextShotChangeOrNextSubtitle": "Udvid valgte linjer til næste sceneskift (eller næste undertekst)", @@ -228,7 +239,6 @@ "fileExtension": "Filtypenavn", "fileName": "Filnavn", "fileNameX": "Filnavn: {0}", - "fileNameXAndSize": "Filnavn: {0} ({1})", "fileSaved": "Filen er gemt", "fileSavedToX": "Fil gemt i {0}", "fileXAlreadyExists": "Filen '{0}' findes allerede.", @@ -238,8 +248,6 @@ "find": "Find", "findNext": "Find næste", "findTextX": "Find tekst - {0}", - "fiveHundredMilliseconds": "500 millisekunder", - "fiveSeconds": "Fem sekunder", "fix": "Ret", "fixCommonErrors": "Ret almindelige fejl", "fixRightToLeft": "Ret fra højre mod venstre", @@ -263,14 +271,12 @@ "foundXInLineYZ": "Fandt \"{0}\" i linje {1}, position {2}", "foundXMatches": "Fandt {0} match", "frameRate": "Billedhastighed", - "frameRateX": "Billedhastighed: {0:0.0###}", "frames": "Billeder", "from": "Fra", "fromCurrentVideoPosition": "fra den aktuelle videoposition", "fullScreen": "Fuld skærm", "gap": "Mellemrum", "general": "Generel", - "generalText": "Generel", "generate": "Generér", "generateImportShotChanges": "Generer/importér sceneskift", "generating": "Genererer...", @@ -288,12 +294,8 @@ "help": "Hjælp", "hex": "Hex", "hide": "Skjul", - "hidePreview": "Skjul forhåndsvisning", "history": "Historik", "horizontal": "Vandret", - "hourMinutesSecondsDecimalSeparatorMilliseconds": "Time: min: sek. {0} ms", - "hourMinutesSecondsFrames": "Time:Min:Sek:Billeder", - "ip": "Ip", "id": "ID", "image": "Billede", "imageSaved": "Billedet er gemt", @@ -354,6 +356,7 @@ "mergeSelectedLinesDialog": "Flet valgte linjer som dialog", "mergeWithLineAfterAndAutoBreak": "Flet med linje efter og auto-break", "mergeWithLineAfterKeepBreaks": "Flet med linje efter (behold pauser)", + "mergeWithLineAfterAndUnbreak": "Flet med næste linje og fjern linjeskift", "mergeWithLineBeforeAndAutoBreak": "Flet med linje før og auto-break", "mergeWithLineBeforeKeepBreaks": "Flet med linje før (behold pauser)", "middleCenter": "Mellem-Center", @@ -368,13 +371,13 @@ "moveAllShotChangeOneFrameForward": "Flyt alle sceneskift ét billede frem", "multiMode": "Multitilstand", "moveDown": "Flyt ned", + "increase": "Forøg", + "decrease": "Formindsk", "moveUp": "Flyt op", "moveToTop": "Flyt til toppen", "moveToBottom": "Flyt til bunden", "multipleReplace": "Multi-erstat", - "mute": "Stum", "name": "Navn", - "negative": "Negativ", "new": "Ny", "newDotDotDot": "Ny...", "newProfile": "Ny profil", @@ -404,20 +407,15 @@ "offsetY": "Forskydning Y", "ok": "_Ok", "oneFile": "En fil", - "oneHundredMilliseconds": "100 millisekunder", - "oneSecond": "Et sekund", "oneSecondBack": "Et sekund tilbage", "oneSecondForward": "Et sekund frem", - "onlineVideoFeatureNotAvailable": "Funktion ikke tilgængelig til online video", "openContainingFolder": "Åbn indeholdende mappe", "openDictionaryFolder": "Åbn ordbogsmappe", "openFile": "Åbn fil", "openImageBasedSubtitle": "Åbn billedbaseret undertekst", "openImageFile": "Åbn billedfil", - "openOriginalSubtitleFile": "Åbn original undertekstfil...", "openOriginalSubtitleFileTitle": "Åbn den originale undertekstfil", "openOutputFolder": "Åbn outputmappe", - "openSubtitle": "Åbn undertekst...", "openSubtitleFileTitle": "Åbn undertekstfil", "openSubtitles": "Åbn undertekster...", "openVideoFile": "Åbn videofil...", @@ -432,11 +430,6 @@ "outlineWidth": "Konturbredde", "outputFolder": "Outputmappe", "outputProperties": "Outputegenskaber", - "overlap": "Overlap", - "overlapNextX": "Overlap næste ({0:#;##0.###})", - "overlapPreviousLineX": "Overlap forrige linje ({0:#;##0.###})", - "overlapStartAndEnd": "Overlap start og slut", - "overlapX": "Overlap ({0:#;##0.###})", "overwriteExistingFiles": "Overskriv eksisterende filer", "overwriteFilesInFolderX": "Overskriv filer i mappen: \"{0}\"", "overwriteQuestion": "Overskrive?", @@ -449,6 +442,7 @@ "percent": "Procent", "pickLayer": "Indstil lag", "pickOllamaModel": "Vælg Ollama-model", + "pickModel": "Vælg model", "llamaCppUseRemoteServer": "Brug ekstern server (URL)", "llamaCppEngineSettings": "Indstillinger for llama.cpp-motor", "llamaCppEngineSettingsSubtitle": "Lokal llama.cpp-serverbuild", @@ -496,7 +490,6 @@ "refresh": "Opdater", "region": "Område", "regularExpression": "Regulært udtryk", - "regularExpressionIsNotValid": "Regulært udtryk er ikke gyldigt!", "relativeToCurrentVideoPosition": "I forhold til den aktuelle videoposition", "remove": "Fjern", "removeAlignment": "Fjern justering", @@ -521,7 +514,7 @@ "requiresRestart": "Kræver genstart", "reset": "Nulstil", "resolution": "Opløsning", - "resolutionX": "Opløsning: {0}", + "retry": "Prøv igen", "reverseRightToLeftStartEnd": "Omvendt RTL start/slut", "right": "Højre", "rightToLeft": "Højre til venstre", @@ -561,8 +554,6 @@ "selectedlinesX": "Valgte linjer: {0}", "sensitivity": "Følsomhed", "separator": "Separator", - "sessionKey": "Sessionsnøgle", - "sessionKeyGenerate": "Generer ny nøgle", "setEnd": "Sæt slut", "setEndAndGoToNext": "Sæt slut og gå til næste", "setEndAndOffsetTheRest": "Sæt slut og forskyd resten", @@ -581,8 +572,9 @@ "shadowColor": "Skyggefarve", "shadowWidth": "Skyggebredde", "shiftLineBy": "Forskyd linje med", + "shotIn": "Scene ind", + "shotOut": "Scene ud", "shortcut": "Genvej", - "shortcutX": "Genvej: {0}", "shortcuts": "Genveje", "show": "Vis", "showActorColumn": "Vis kolonnen 'Skuespiller'", @@ -593,17 +585,19 @@ "showStartColumn": "Vis \"Start\" kolonne", "showHideColumn": "Vis 'Skjul' kolonne", "showHistory": "Vis historik", + "clearHistory": "Ryd historik", + "removeFromHistory": "Fjern fra historik", "showLayerColumn": "Vis kolonnen 'Lag'", "showPreview": "Vis forhåndsvisning", "showShotChangesList": "Vis liste over sceneskift", + "showShotInColumn": "Vis kolonnen \"Scene ind\"", + "showShotOutColumn": "Vis kolonnen \"Scene ud\"", "showStyleColumn": "Vis kolonnen \"Stil\"", "showTeletext": "Vis tekst-tv-kolonne i listevisning", "showTimeCodes": "Vis tidskoder", "showWpmColumn": "Vis kolonnen 'Ord/min'", "showPixelWidthColumn": "Vis kolonnen \"Pixelbredde\".", "shrink": "Formindsk", - "singleBox": "Enkelt boks", - "singleLineLengths": "Enkelt linjelængde:", "singleMode": "Enkelt tilstand", "textTotalLength": "Tekst - samlet længde", "textSingleLineMaxLength": "Tekst - maks. længde for enkeltlinje", @@ -652,16 +646,16 @@ "strikeout": "Gennemstreget", "style": "Stil", "styleExaggeration": "Stiloverdrivelse", - "styleLanguage": "Stil / sprog", "styles": "Stilarter", "subtitleFile": "Undertekstfil", + "subtitleFileChangedOutsideX": "Filen \"{0}\" er blevet ændret uden for Subtitle Edit.\n\nGenindlæs den og mist dine ændringer?", "subtitleFileName": "Navn på undertekstfil", "subtitleFileSaved": "Undertekstfil gemt", "subtitleFileSavedToX": "Undertekstfil gemt i {0}", "subtitleFiles": "Undertekstfiler", "subtitleFormats": "Undertekstformater", "subtitleLoadedX": "Undertekster indlæst: {0}", - "subtitleXOfY": "Undertekst {0} af {1}", + "subtitleReloadedChangedOutsideX": "Undertekst genindlæst (ændret uden for Subtitle Edit): {0}", "suffix": "Suffiks", "suggestions": "Forslag", "sync": "Synkroniser", @@ -673,7 +667,6 @@ "teletextColor": "Tekst-tv-farve", "teletextLine": "Tekst-tv-linje", "tenHours": "10 timer", - "tenMilliseconds": "10 millisekunder", "text": "Tekst", "textColor": "Tekstfarve", "textFiles": "Tekstfiler", @@ -700,9 +693,6 @@ "topRight": "Øverst til højre", "top": "Øverst", "totalAdjustmentX": "Samlet justering: {0}", - "totalFramesX": "Antal billeder: {0:#;##0.##}", - "totalLengthX": "Samlet længde: {0}", - "totalLengthXSplitLine": "Samlet længde: {0} (del linje!)", "translate": "Oversæt", "translatePercentX": "Oversætter: {0}%", "translateRow": "Oversæt række", @@ -711,6 +701,7 @@ "type": "Type", "unbreak": "Saml linjer", "unbreakLines": "Saml linjer", + "undelete": "Fortryd sletning", "underline": "Understregning", "undo": "Fortryd", "unknown": "Ukendt", @@ -730,13 +721,11 @@ "usages": "Anvendelser", "use": "Benyt", "useAlways": "Brug altid", - "useLargerFontForThisWindow": "Brug større skrifttype til dette vindue", "useOnce": "Brug én gang", "useOutputFolder": "Brug outputmappe", "useSourceFolder": "Brug kildemappe", "useSourceResolution": "Brug kildeopløsning", "userName": "Brugernavn", - "userNameAlreadyInUse": "Brugernavn allerede i brug", "vertical": "Lodret", "video100MsBack": "Video, 100 millisekunder tilbage", "video100MsForward": "Video, 100 millisekunder frem", @@ -746,13 +735,16 @@ "videoCustom1ForwardX": "Video, tilpassede millisekunder ({0:#,###,##0}) frem, 1", "videoCustom2BackX": "Video, tilpassede millisekunder ({0:#,###,##0}) tilbage, 2", "videoCustom2ForwardX": "Video, tilpassede millisekunder ({0:#,###,##0}) frem, 2", - "videoEncodingX": "Videokodning: {0}", "videoExtension": "Videofiludvidelse", "videoFile": "Videofil", "videoFileGenerated": "Videofil genereret", "videoFileGeneratedX": "Videofil genereret: \"{0}\"", + "videoFilesGenerated": "Videofiler genereret", + "xVideosGenerated": "{0} videoer genereret", + "xOfYVideosGenerated": "{0} af {1} videoer genereret", + "savedInX": "Gemt i {0}", + "savedInXFolders": "Gemt i {0} mapper", "videoFiles": "Videofiler", - "videoInformation": "Videoinfo", "videoOffset": "Videooffset", "videoOneFrameBack": "Video, et billede tilbage", "videoOneFrameForward": "Video, et billede frem", @@ -763,7 +755,6 @@ "videoPlayer": "Videoafspiller", "videoPosition": "Videoposition", "videoResolution": "Videoopløsning", - "viewX": "Vis {0}", "visible": "Synlig", "voice": "Stemme", "volume": "Lydstyrke", @@ -771,9 +762,7 @@ "waveformCenterOnVideoPosition": "Bølgeform midt på videoposition", "waveformPasteFromClipboard": "Indsæt fra udklipsholder", "waveformSpectrogram": "Bølgeform/spektrogram", - "webServiceUrl": "WebService URL", "width": "Bredde", - "windowPositionAndSize": "Vinduets placering og størrelse", "wordsPerMin": "Ord/min", "pixelWidth": "Pixelbredde", "wpm": "Ord/min", @@ -787,8 +776,6 @@ "noBookmarksFound": "Ingen flere bogmærker fundet", "xRequiresAnApiKey": "{0} kræver en API-nøgle", "xRequiresAValidUrl": "{0} kræver en gyldig URL (f.eks. \"http://...\" eller \"https://...\")", - "xSeconds": "{0:0.0##} sekunder", - "xSubtitles": "{0:#,###,##0} undertekster", "yes": "Ja", "zeroBytes": "0 bytes", "copyToClipboard": "Kopiér til udklipsholder", @@ -810,10 +797,8 @@ "playbackSpeed": "Afspilningshastighed", "hoursMinutesSecondsMilliseconds": "TT:MM:SS.FFF", "imageFiles": "Billedfiler", - "openAiCompatibleStt": "OpenAI-kompatibel STT", "openAiCompatibleSttEndpoint": "STT-endpoint-URL", "openAiCompatibleSttApiKey": "API nøgle", - "openAiCompatibleSttModel": "Model", "openAiCompatibleSttExtraHeaders": "Ekstra HTTP-headere", "openAiCompatibleSttTimeout": "Timeout (sekunder)", "openAiCompatibleSttLanguage": "Sprogtip", @@ -828,11 +813,9 @@ "configurationRequired": "Konfiguration påkrævet", "transcriptionError": "Transskriptionsfejl", "transcriptionComplete": "Transskription færdig", - "transcribingWithOpenAi": "Transskriberer med OpenAI Service...", "processingResponse": "Behandler svar...", "unauthorizedApiKey": "Uautoriseret: Ugyldig API-nøgle eller utilstrækkelige tilladelser.", "requestTimeout": "Timeout for anmodning: Serveren tog for lang tid at svare.", - "noSegmentsReturned": "Ingen segmenter blev returneret fra transskriptionstjenesten.", "openAiCompatibleSttUrlMissing": "OpenAI-kompatibel STT-slutpunkts-URL er påkrævet.\r\nIndtast venligst en URL og prøv igen.", "openAiCompatibleSttUrlNotResponding": "Det OpenAI-kompatible STT-slutpunkt svarede ikke:\r\n\r\n{0}\r\n\r\n\r\nBekræft venligst URL'en, og at serveren kører.", "transcriptionFailed": "Transskription mislykkedes", @@ -854,11 +837,22 @@ "videoCustom3ForwardX": "Video, tilpasset millisekunder ({0:#,###,##0}) frem, 3", "videoCustom4BackX": "Video, tilpasset millisekunder ({0:#,###,##0}) tilbage, 4", "videoCustom4ForwardX": "Video, tilpasset millisekunder ({0:#,###,##0}) frem, 4", + "videoCustom1BackAndPauseX": "Video, tilpassede millisekunder ({0:#,###,##0}) tilbage og pause, 1", + "videoCustom1ForwardAndPauseX": "Video, tilpassede millisekunder ({0:#,###,##0}) frem og pause, 1", + "videoCustom2BackAndPauseX": "Video, tilpassede millisekunder ({0:#,###,##0}) tilbage og pause, 2", + "videoCustom2ForwardAndPauseX": "Video, tilpassede millisekunder ({0:#,###,##0}) frem og pause, 2", + "videoCustom3BackAndPauseX": "Video, tilpassede millisekunder ({0:#,###,##0}) tilbage og pause, 3", + "videoCustom3ForwardAndPauseX": "Video, tilpassede millisekunder ({0:#,###,##0}) frem og pause, 3", + "videoCustom4BackAndPauseX": "Video, tilpassede millisekunder ({0:#,###,##0}) tilbage og pause, 4", + "videoCustom4ForwardAndPauseX": "Video, tilpassede millisekunder ({0:#,###,##0}) frem og pause, 4", "videoAndAudioFiles": "Video- og lydfiler", "waveform": "Bølgeform", "dashScopeSttRegion": "Region", "dashScopeSttEnableWords": "Tidskoder på ordniveau", "dashScopeSttRegionKeyHint": "Bemærk: API-nøgler til Alibaba Cloud Model Studio er regionsspecifikke - sørg for, at den valgte region matcher den region, hvor API-nøglen blev oprettet (Kina vs. International).", + "googleCloudSttProjectId": "Projekt-id (kun til Application Default Credentials)", + "googleCloudSttBucketName": "Storage bucket (tom = automatisk)", + "googleCloudSttDynamicBatching": "Dynamisk batching (ca. 5x billigere, ingen garanti for ventetid)", "onlineSttApiKeyMissing": "Der kræves en API-nøgle. Indtast din API-nøgle, og prøv igen." }, "main": { @@ -868,12 +862,15 @@ "charactersPerSecond": "Tegn/sekund: {0}", "chooseColumn": "Vælg kolonne", "columnPaste": "Kolonneindsæt", + "commandPaletteTitle": "Kommandopalet", + "commandPaletteSearchPlaceholder": "Skriv for at søge efter kommandoer...", + "commandPaletteNoCommandsFound": "Ingen matchende kommandoer", "createdEmptyTranslation": "Oprettet tom oversættelse fra den aktuelle undertekst", "deleteText": "Slet tekst", "deleteTextAndShiftCellsUp": "Slet tekst og flyt celler op", "endTimeMustBeAfterStartTime": "Sluttidspunktet skal være efter starttidspunktet.", "errorLoad7Zip": "Denne fil ser ud til at være en komprimeret 7-Zip-fil.\r\n\r\n\r\nSubtitle Edit kan ikke åbne komprimerede filer.", - "errorLoadBinaryZeroes": "Beklager, denne fil indeholder kun binære nuller!\r\n\r\n\r\nHvis du har redigeret denne fil med Subtitle Edit, kan du muligvis finde en sikkerhedskopi via menupunktet Filer -> Gendan auto-backup...", + "errorLoadBinaryZeroes": "Beklager, denne fil indeholder kun binære nuller!\n\nHvis du har redigeret denne fil med Subtitle Edit, kan du muligvis finde en sikkerhedskopi via menupunktet Filer -> Gendan auto-backup...", "errorLoadGZip": "Denne fil ser ud til at være en komprimeret GZip-fil.\r\n\r\n\r\nSubtitle Edit kan ikke åbne komprimerede filer.", "errorLoadLargeFile": "Denne fil er for stor til at åbne som en undertekstfil.", "errorLoadVideoFilePrompt": "Denne fil ser ud til at være en videofil:\r\n\r\n{0}\r\n\r\n\r\nDu kan åbne en video via \"Video -> Åbn video\" eller ved at trække og slippe en video til videoafspilleren.\r\n\r\n\r\nVil du åbne den som en videofil?", @@ -905,18 +902,20 @@ "numberOfLinesEvenlyDistributedX": "Fordelte {0} linjer jævnt", "oneLineCopiedFromOriginal": "En linje kopieret fra original undertekst", "oneLineCopiedToOriginal": "Én linje kopieret til den originale undertekst", + "onlineOnlyVideo": "Video kun online", + "onlineOnlyVideoXSizeYDownloadAndOpen": "Videofilen \"{0}\" ({1}) er kun gemt online, f.eks. i Dropbox eller iCloud Drive, og skal downloades, før den kan åbnes.\n\nDownload og åbn den nu?", "oneLineMerged": "En linje flettet", "oneLineSwitched": "En linje skiftet", "overwriteExistingCells": "Overskriv eksisterende celler", "overwriteOrShiftCellsDown": "Overskriv/skift celler ned", "parsingMatroskaFile": "Parser Matroska-fil...", + "readingDvdSubtitles": "Læser DVD-undertekster...", "pasteFromClipboardDotDotDot": "Indsæt fra udklipsholder...", "redoPerformed": "Gendan udført", "redoPerformedXActionLeft": "Gentag udført (handlinger tilbage: {0})", "removedUnicodeControlCharactersX": "Fjernede Unicode-kontroltegn fra {0} linjer", "removedXBlankLines": "Fjernede {0} tomme linjer", "replacedXWithYCountZ": "Erstattet \"{0}\" med \"{1}\" ({2} forekomster)", - "replacedXWithYInLineZ": "Erstattet \"{0}\" med \"{1}\" i linje {2}", "reversedStartAndEndingsForRightToLeftX": "Omvendt start og afslutning for højre mod venstre i {0} linjer", "ruleProfileIsX": "Regelprofilen er nu \"{0}", "saveLanguageFile": "Gem sprogfil", @@ -924,7 +923,6 @@ "shiftTextCellsDown": "Skift tekstceller ned", "singleLineLength": "Linjelængde: ", "speedIsNowX": "Hastigheden er nu \"{0}", - "spellCheckResult": "Stavekontrol gennemført.\r\n\r\n• Antal ord rettet: {0}\r\n• Antal ord sprunget over: {1}", "subtitleGridFormattingX": "Gitterformatering: {0}", "subtitleImportedFromMatroskaFile": "Undertekst importeret fra Matroska-fil", "textDown": "Tekst ned", @@ -951,13 +949,14 @@ "trimmedXLines": "Trimmet {0} undertekstlinjer", "pastedXLinesOverSelectedLines": "Indsatte {0} linje(r) over de valgte linjer", "pastedXOfYLinesOverSelectedLines": "Indsatte {0} af {1} linje(r) over de valgte linjer - resten passede ikke i markeringen", + "insertedXLinesFromClipboard": "Indsatte {0} linje(r) fra udklipsholderen", "openOriginalDifferentNumberOfSubtitlesXY": "Den originale undertekstfil har ikke det samme antal undertekster som den aktuelle undertekstfil.\r\n\r\n\r\n• Originale undertekster: {0}\r\n\r\n• Aktuelle undertekster: {1}", - "importXMatchingOriginalLines": "Vil du importere {0} matchende originale undertekster?", "originalTextReadOnly": "Original tekst (skrivebeskyttet)", "originalTextEditMode": "Original tekst (redigeringstilstand)", "originalIsReadOnlyNotSaved": "Den originale undertekst er en skrivebeskyttet reference og blev ikke gemt", "originalIsEmptyNotSaved": "Den originale undertekst indeholder slet ingen tekst og blev ikke gemt", "originalIsReadOnlyReference": "Den originale undertekst er åbnet som en skrivebeskyttet reference", + "closeTranslationQuestionXY": "Luk oversættelsen \"{0}\"?\n\nDen originale undertekst \"{1}\" bliver den undertekst, du redigerer. Selve oversættelsesfilen ændres ikke.", "allowEditOfOriginalSubtitle": "Tillad redigering af den originale undertekst", "showAllOriginalLinesX": "Vis alle originale linjer ({0} har ingen match her)", "showAllOriginalLinesHint": "Linjerne uden match vises som ekstra rækker, så du kan se, hvad oversættelsen mangler.", @@ -992,6 +991,7 @@ "fileExportedInFormatXToY": "Fil eksporteret i formatet {0} til {1}", "fileExportedInFormatXToFileY": "Fil eksporteret i formatet \"{0}\" til filen \"{1}", "fixedXLines": "Rettede {0} linjer", + "toolXChangedYLines": "{0}: {1} linjer ændret", "transcriptionCompletedWithXLines": "Transskription afsluttet med {0} linjer", "openSubtitleFileX": "Vil du åbne undertekstfilen \"{0}\"?", "replacedXOccurrences": "Erstattede {0} forekomster", @@ -1053,14 +1053,15 @@ "multipleReplace": "_Multipel erstatning...", "fillSelectedLinesWithClipboard": "Udfyld valgte linjer med udklipsholdertekst", "goToLineNumber": "_Gå til linje nummer...", + "commandPalette": "Kommando_palet...", "rightToLeftMode": "_Højre-til-venstre-tilstand", "modifySelectionDotDotDot": "Rediger _valg...", "tools": "_Værktøjer", - "toolsSelectedLines": "_Værktøjer (valgte linjer)", "adjustDurations": "_Juster varigheder...", "applyDurationLimits": "Anvend varighed _grænser...", "batchConvert": "Batch _konverter...", "beautifyTimeCodes": "Forskøn tidskoder...", + "improveTimeCodes": "Forbedr tidskoder (forced alignment)...", "bridgeGaps": "_Byg bro over huller...", "applyMinGap": "Anvend min. afstand mellem undertekster...", "changeCasing": "_Skift store/små bogstaver...", @@ -1201,6 +1202,9 @@ "selectCurrentLineWhilePlayingHint": "Vælg den aktuelle undertekst, mens du spiller {0}", "videoPosition": "Videoposition {0}", "videoPositionTextBox": "Tekstboks til videoposition {0}", + "toolbarLineBreakX": "Linjeskift {0} (resten af værktøjslinjen fortsætter på en ny række)", + "initialText": "Oprindelig tekst for valgt linje", + "initialTextHint": "Teksten i den valgte linje, som den var, da linjen blev valgt", "hideWaveformToolbar": "Skjul værktøjslinje {0}", "resetZoomAndSpeed": "Nulstil zoom og afspilningshastighed {0}", "removeBlankLines": "Fjern tomme linjer {0}", @@ -1214,6 +1218,15 @@ "textPlayHint": "Afspil aktuel undertekst og stop ved slutningen {0}", "textPauseHint": "Pause afspilning {0}", "textNextHint": "Afspil næste undertekst og stop ved slutningen {0}", + "moveSelectedLines": "Flyt valgte linjer (tilbage/frem-knapper)", + "moveSelectedLinesAndFollowing": "Flyt valgte linjer og alle efterfølgende (tilbage/frem-knapper)", + "moveAllLines": "Flyt alle linjer (tilbage/frem-knapper)", + "moveSelectedLinesBackHint": "Flyt valgte linjer {0} ms tilbage {1}", + "moveSelectedLinesForwardHint": "Flyt valgte linjer {0} ms frem {1}", + "moveSelectedLinesAndFollowingBackHint": "Flyt valgte linjer og alle efterfølgende {0} ms tilbage {1}", + "moveSelectedLinesAndFollowingForwardHint": "Flyt valgte linjer og alle efterfølgende {0} ms frem {1}", + "moveAllLinesBackHint": "Flyt alle linjer {0} ms tilbage {1}", + "moveAllLinesForwardHint": "Flyt alle linjer {0} ms frem {1}", "guessStartNoLineSelected": "Gæt start: ingen linje valgt", "guessStartTimeCodesLocked": "Gæt start: tidskoder er låst", "guessStartNoWaveform": "Gæt start: ingen bølgeform", @@ -1260,20 +1273,66 @@ "xNumberOfDifferenceAndPercentChanged": "Antal forskelle: {0} ({1:0.##} % af ordene ændret)", "xNumberOfDifferenceAndPercentLettersChanged": "Antal forskelle: {0} ({1:0.##} % af bogstaverne ændret)", "showOnlyDifferences": "Kun forskelle", - "ignoreLineBreaks": "Ignorer linjeskift", "ignoreWhitespace": "Ignorer mellemrum", "ignoreWhitespaceHint": "Linjer, der kun adskiller sig i mellemrum, tabulatorer eller linjeskift, tæller ikke som forskellige", "ignoreFormatting": "Ignorer formatering", - "onlyLookForDifferencesInText": "Se kun efter forskelle i tekst", - "cannotCompareWithImageBasedSubtitles": "Kan ikke sammenlignes med billedbaserede undertekster", - "statisticsTitle": "Statistik", "ignoreFormattingHint": "Linjer, der kun adskiller sig i formateringstags som eller {\\an8}, tæller ikke som forskellige", "showOnlyDifferencesInText": "Kun forskelle i tekst", + "ignoreNumbering": "Ignorer nummerering", + "ignoreNumberingHint": "Linjer, der kun adskiller sig i deres nummer, tæller ikke som forskellige", + "ignoreTimeDifferenceMs": "Tidstolerance (ms)", + "ignoreTimeDifferenceMsHint": "Start- og sluttider, der afviger med dette antal millisekunder eller mindre, tæller ikke som forskellige", + "compareDifferences": "Forskelle", + "compareTextDifferences": "Tekstforskelle", + "compareCurrent": "Aktuel", + "compareReference": "Reference", + "compareEditable": "Redigerbar", + "compareEditableHint": "Ændringer foretaget her overføres til underteksten, når du klikker på Anvend", + "compareReadOnly": "Skrivebeskyttet", + "compareTakeFromReference": "Overtag tekst og timing fra referencen", + "compareInsertFromReference": "Indsæt denne linje i den aktuelle undertekst", + "compareDeleteFromCurrent": "Slet denne linje fra den aktuelle undertekst", + "compareTakeText": "Overtag tekst", + "compareTakeTiming": "Overtag timing", + "compareEditLine": "Rediger linje", + "compareEditHint": "Ctrl+Enter for at gemme, Esc for at annullere", + "compareEdited": "Redigeret", + "compareOnePendingChange": "1 ventende ændring", + "compareXPendingChanges": "{0} ventende ændringer", + "compareDiscardXChanges": "Kassér de {0} ændring(er) foretaget i Sammenlign?", + "compareXChangesApplied": "Sammenlign: {0} ændring(er) anvendt", + "compareChangeTextX": "#{0} tekst fra reference", + "compareChangeTimingX": "#{0} timing fra reference", + "compareChangeTextAndTimingX": "#{0} tekst og timing fra reference", + "compareChangeEditedX": "#{0} redigeret", + "compareChangeInsertedX": "#{0} indsat", + "compareChangeDeletedX": "#{0} slettet", + "compareSyncPoint": "Synkroniseringspunkt", + "compareSyncPointHint": "Synkroniseringspunkt - disse to linjer vises altid som et par, og linjerne over og under afstemmes hver for sig", + "compareSyncPickCurrent": "Synkroniseringspunkt: brug denne aktuelle linje", + "compareSyncPickReference": "Synkroniseringspunkt: brug denne referencelinje", + "compareSyncRemove": "Fjern synkroniseringspunkt", + "compareClearXSyncPoints": "Ryd synkroniseringspunkter ({0})", + "compareSyncCurrentPickedX": "Aktuel #{0} valgt som synkroniseringspunkt - vælg den tilsvarende referencelinje, og klik på Synkroniser", + "compareSyncReferencePickedX": "Reference #{0} valgt som synkroniseringspunkt - vælg den tilsvarende aktuelle linje, og klik på Synkroniser", + "compareSyncWithCurrentX": "Synkroniser med aktuel #{0}", + "compareSyncWithReferenceX": "Synkroniser med reference #{0}", + "compareSyncApply": "Synkroniser", + "compareSyncPointSetXY": "Synkroniseringspunkt angivet: aktuel #{0} og reference #{1} vises nu som et par", + "compareSyncAlreadyPairedXY": "Aktuel #{0} og reference #{1} vises allerede som et par med samme start - intet at synkronisere", + "compareSyncPointShiftedXYZW": "Synkroniseringspunkt angivet: aktuel #{0}-#{1} forskudt {2} til at starte med reference #{3}", + "compareChangeSyncShiftXYZ": "#{0}-#{1} forskudt {2} (synkroniseringspunkt)", + "compareTimingDeltaHint": "Hvor meget senere (+) eller tidligere (−) referencen starter og slutter end den aktuelle linje", "loadXFromFile": "Indlæs \"{0}\" fra filen", + "compareLoadSavedFile": "Indlæs gemt fil", "saveCompareHtmlTitle": "Gem sammenligne HTML-fil", "pickMatroskaTrackX": "Vælg Matroska-spor - {0}", "pickTransportStreamTrackX": "Vælg transportstrømspor - {0}", + "chapters": "Kapitler", + "dvdVobFilesMissingX": "VOB-filer mangler ({0}% fundet)", "pickMp4TrackX": "Vælg MP4-spor - {0}", + "pickMxfTrackX": "Vælg MXF-spor - {0}", + "pickMpegTrackX": "Vælg MPEG-spor - {0}", "rosettaProperties": "Timed Text Rosetta IMSC egenskaber", "rosettaFontSize": "Skriftstørrelse (rækkehøjde)", "propertyTimeBase": "Tidsbasis", @@ -1283,7 +1342,6 @@ "propertyDefaultRegion": "Standardregion", "propertyStyleAttributeName": "Navn på stilattribut", "propertyTimeCodeFormat": "Tidskodeformat", - "propertyFileExtension": "Filendelse", "propertyTopOrigin": "Øverste origo", "propertyTopExtent": "Øverste udstrækning", "propertyBottomOrigin": "Nederste origo", @@ -1291,13 +1349,9 @@ "xProperties": "{0} egenskaber", "ebuSaveOptions": { "title": "EBU-gemmeindstillinger", - "generalSubtitleInformation": "Generel undertekstinformation", "codePageNumber": "Kodesidenummer", "diskFormatCode": "Diskformatkode", "displayStandardCode": "Visningsstandardkode", - "colorRequiresTeletext": "Farver kræver tekst-tv!", - "alignmentRequiresTeletext": "Justering kræver tekst-tv!", - "teletextCharsShouldBe38": "'Maks. antal tegn pr. linje' for tekst-tv skal være 38!", "characterCodeTable": "Tegntabel", "languageCode": "Sprogkode", "originalProgramTitle": "Original programtitel", @@ -1314,7 +1368,6 @@ "maxNumberOfDisplayableRows": "Maks. antal linjer", "diskSequenceNumber": "Disksekvensnummer", "totalNumberOfDisks": "Samlet antal diske", - "import": "Importér...", "textAndTimingInformation": "Tekst- og timingoplysninger", "justificationCode": "Justeringskode", "verticalPosition": "Lodret position", @@ -1331,11 +1384,6 @@ "errors": "Fejl", "errorsX": "Fejl: {0}", "maxLengthError": "Linje {0} overskrider maks. længde ({1}) med {2}: {3}", - "textUnchangedPresentation": "Uændret præsentation", - "textLeftJustifiedText": "Venstrejusteret tekst", - "textCenteredText": "Centreret tekst", - "textRightJustifiedText": "Højrejusteret tekst", - "useBoxForOneNewLine": "Markér 'Brug boks omkring tekst' for kun ét linjeskift", "maxLengthErrorTeletextColored": "Linje {0}-{1}: 36 (ikke {2}) bør være maksimalt antal tegn for farvet tekst i dobbelt højde", "maxLengthErrorTeletextWhite": "Linje {0}-{1}: 37 (ikke {2}) bør være maksimalt antal tegn for hvid tekst i dobbelt højde", "maxLengthErrorTeletextSingleHeight": "Linje {0}: 38 (ikke {1}) bør være maksimalt antal tegn for hvid tekst i enkelt højde", @@ -1360,22 +1408,29 @@ "formattingDotDotDot": "Formaterer...", "imageBasedSubtitleForEditDotDotDot": "Billedbaseret undertekst til redigering...", "imageBasedSubtitleForOcrDotDotDot": "Billedbaseret undertekst til OCR...", + "dvdSubtitlesDotDotDot": "Undertekster fra DVD (IFO/VOB)...", + "titleImportDvd": "Importér undertekster fra DVD", + "dvdIfoFile": "IFO-fil", + "dvdVobFiles": "VOB-filer", + "dvdVideoStandard": "Videostandard", + "dvdStartRipping": "Start ripping", + "dvdRippingAborted": "Ripping afbrudt", + "dvdOpenIfoFile": "Åbn DVD IFO-fil", + "dvdAddVobFiles": "Tilføj VOB-filer", + "dvdIfoFiles": "DVD IFO-filer", + "dvdVobFileType": "DVD VOB-filer", + "dvdDropHint": "Åbn en IFO-fil (VIDEO_TS\\VTS_01_0.IFO) eller tilføj VOB-filer - eller slip dem her", + "dvdNotAnIfoFileX": "{0} er ikke en DVD IFO-fil", + "dvdMenuIfoNoTitleSetsX": "Ingen title sets (VTS_xx_0.IFO) fundet ved siden af {0}", + "dvdEncryptedXOfY": "{0} af de {1} undertekstpakker er stadig CSS-krypterede - disse VOB-filer blev kopieret uden dekryptering, så nogle undertekstbilleder vil være forvanskede.\n\nDekryptér DVD'en med et DVD-rippeværktøj først for at få et rent resultat.\n\nFortsæt alligevel?", "splitTextAt": "Del tekst ved", "blankLines": "Tomme linjer", "oneLineIsOneSubtitle": "Én linje er én undertekst", "twoLinesAreOneSubtitle": "To linjer er én undertekst", "importFilesDotDotDot": "Importer filer...", "multipleFiles": "Importer fra flere tekstfiler (én fil er én undertekst)", - "importOptions": "Importmuligheder", - "autoSplitText": "Automatisk opdeling af tekst", - "lineMode": "Linjetilstand", "maxLineLength": "Max linjelængde", - "maxLinesPerSubtitle": "Max linjer pr. undertekst", - "minGapBetweenSubtitles": "Minimum mellemrum mellem undertekster", "mergeShortLines": "Flet korte linjer", - "removeLinesWithoutLetters": "Fjern linjer uden bogstaver", - "splitAtEndCharsSetting": "Opdel ved sluttegn", - "takeTimeFromCurrentFile": "Tag tid fra den aktuelle fil", "fixed": "Fast", "numberOfSubtitlesX": "Antal undertekster: {0}", "numberOfSubtitlesXForcedY": "Antal undertekster: {0} ({1} tvungne)", @@ -1387,14 +1442,18 @@ "forcedAlignerProgress": "Justerer... vindue {0} af {1}, {2} af {3} linjer", "forcedAlignerSetupTitle": "Justér tidskoder via forced aligner", "forcedAlignerSetupIntro": "En forced aligner matcher den undertekst, du allerede har, mod lyden uden først at transskribere den. Lange videoer justeres i bidder, så enhver længde fungerer.", + "forcedAlignerEndsFromSpeech": "Angiv sluttider ud fra isoleret tale (langsom)", + "forcedAlignerEndsFromSpeechHint": "En forced aligner finder, hvor en linje starter, ikke hvor den slutter, så sluttider kommer normalt fra læsetiden. Når dette er slået til, fjernes musik og lydeffekter først fra lyden, og hver linje slutter, hvor dens tale bliver stille. Tager omtrent lige så lang tid som selve videoen med en GPU - og mange gange længere uden.", + "forcedAlignerIsolatingSpeech": "Isolerer tale... {0}", + "forcedAlignerIsolatingSpeechFailed": "Kunne ikke isolere talen - sluttider blev i stedet angivet ud fra læsetid.", "forcedAlignerModel": "Aligner-model", - "forcedAlignerDownloadEngine": "Download / opdater motor..." + "forcedAlignerDownloadEngine": "Download / opdater motor...", + "forcedAlignerEstimatedXOfY": "{0} af {1} linjer kunne ikke justeres og blev placeret efter læsetid - deres tidskoder er kun anslåede. Manuskriptet er sandsynligvis længere end det, der bliver sagt i videoen." }, "export": { "exportImagesProfiles": "Eksporter billedprofiler", "leftRightMargin": "Venstre/højre margin", "topBottomMargin": "Top/bund margin", - "titleExportBluRaySup": "Eksporter Blu-ray (sup)", "lineSpacingPercent": "Linjeafstand %", "paddingLeftRight": "Udfyldning venstre/højre", "paddingTopBottom": "Udfyldning top/bund", @@ -1435,10 +1494,21 @@ "titleExportDCinemaInteropPng": "D-Cinema interop/png", "titleExportDCinemaSmpte2014Png": "D-Cinema SMPTE 2014/png", "imageBasedSubtitleSaved": "Billedbaseret undertekst er gemt", - "titleExportBdnXml": "Eksporter BDN/xml", "titleExportWebVttThumbnails": "Eksporter WebVTT (miniaturebilleder)", "fullFrameImage": "Fuldt billedfelt", "fullFrameImageHint": "Gør hvert billede lige så stort som videobilledet med underteksten på sin plads", + "stereo3D": "3D", + "stereo3DHint": "Til 3D-video: tegn underteksten én gang i hvert øjes halvdel af billedet", + "halfSideBySide": "Halv side-om-side", + "halfTopBottom": "Halv top/bund", + "depth3D": "Dybde", + "depth3DHint": "Hvor langt underteksten træder ud af skærmen - positiv bringer den tættere på, negativ skubber den tilbage", + "plane3D": "3D-Plane", + "plane3DHint": "Giv hver undertekst den dybde, som en 3D Blu-ray gemmer for den, fra en 3D-Plane-fil (.ofs) lavet med BD3D2MK3D eller OFSExtractor. Dybde bruges, hvor 3D-Plane ikke har nogen", + "openPlane3DTitle": "Åbn 3D-Plane-fil", + "unableToLoadPlane3DX": "Kan ikke indlæse 3D-Plane: {0}", + "plane3DXDepthYToZ": "{0} (dybde {1} til {2})", + "plane3DXNoDepth": "{0} (ingen dybdeværdier)", "textEffect": "Teksteffekt", "textEffectSettingsTitle": "Indstillinger for teksteffekt", "textEffectPreset": "Forudindstilling", @@ -1470,13 +1540,10 @@ "statistics": { "title": "Statistik", "titleWithFileName": "Statistik - {0}", - "generalStatistics": "Generel statistik", - "mostUsed": "Mest brugt...", "mostUsedLines": "Mest brugte linjer", "mostUsedWords": "Mest brugte ord", "nothingFound": "Intet fundet", "numberOfLinesX": "Antal undertekstlinjer: {0:#,##0}", - "numberOfFilesX": "Antal undertekstfiler: {0:#,##0}", "lengthInFormatXinCharactersY": "Antal tegn som {0}: {1:#,###,##0}", "numberOfCharactersInTextOnly": "Antal tegn kun i tekst: {0:#,###,##0}", "totalDuration": "Samlet varighed af alle undertekster: {0:#,##0}", @@ -1517,7 +1584,6 @@ "gapMaximum": "Mellemrum - maksimum: {0:#,##0} ms", "gapAverage": "Mellemrum - gennemsnit: {0:#,##0.##} ms", "gapExceedingMinimum": "Mellemrum - under minimum ({0:#,##0} ms): {1} ({2:0.00}%)", - "export": "Eksportere...", "subtitles": "Undertekster", "words": "Ord", "totalDurationShort": "Samlet varighed", @@ -1547,6 +1613,21 @@ "restoreXFromY": "Vil du gendanne \"{0}\" fra {1}?", "deleteAllSubtitleBackups": "Vil du slette alle undertekst backup-filer?", "deleteAll": "Slet alle", + "subtitles": "Undertekster", + "settingsBackupInfo": "Settings.json sikkerhedskopieres, når Subtitle Edit starter, højst én gang hver {0} dag(e) og kun hvis indstillingerne er ændret. De nyeste {1} sikkerhedskopier beholdes.", + "settingsBackupInfoEveryStart": "Settings.json sikkerhedskopieres hver gang Subtitle Edit starter, hvis indstillingerne er ændret. De nyeste {0} sikkerhedskopier beholdes.", + "settingsBackupOff": "Automatisk sikkerhedskopiering af indstillinger er slået fra (Muligheder → Indstillinger → Fil).", + "lastSettingsBackupX": "Seneste sikkerhedskopi: {0}", + "noSettingsBackupsYet": "Ingen sikkerhedskopier af indstillinger endnu", + "openSettingsBackupFolder": "Åbn mappe med sikkerhedskopier af indstillinger", + "backUpSettingsNow": "Sikkerhedskopiér nu", + "settingsBackedUp": "De aktuelle indstillinger blev sikkerhedskopieret.", + "restoreSettings": "Gendan indstillinger", + "restoreSettingsFromX": "Vil du erstatte dine aktuelle indstillinger med sikkerhedskopien fra {0}?\n\nDine aktuelle indstillinger sikkerhedskopieres først. Nogle ændringer træder først i kraft, når Subtitle Edit genstartes.", + "settingsRestored": "Indstillingerne blev gendannet. Genstart Subtitle Edit, for at alle ændringer træder i kraft.", + "settingsRestoreFailed": "Kunne ikke gendanne indstillinger: {0}", + "settingsBackupNotValid": "sikkerhedskopien er ikke en gyldig indstillingsfil, så dine aktuelle indstillinger blev beholdt", + "deleteAllSettingsBackups": "Vil du slette alle sikkerhedskopifiler af indstillinger?", "xBackups": "{0} sikkerhedskopier" }, "propertiesDCinema": { @@ -1561,22 +1642,16 @@ "editRate": "Editrate", "timeCodeRate": "Tidskodehastighed", "startTime": "Starttidspunkt", - "font": "Skrifttype", "fontId": "Skrifttype-id", "fontUri": "Skrifttype URI", - "generate": "Generer", - "fontColor": "Skriftfarve", "chooseColor": "Vælg farve", "fontEffect": "Skrifttypeeffekt", "effectColor": "Effektfarve", - "fontSize": "Skriftstørrelse", "topBottomMargin": "Top/bund margin", "fadeUpTime": "Optoningstid", "fadeDownTime": "Udtoningstid", - "frames": "Billeder", "zPosition": "Z-position", - "zPositionHelp": "Positive tal flytter teksten længere væk, negative tal flytter teksten tættere på; hvis z-positionen er nul, er det 2D", - "export": "Eksportere..." + "zPositionHelp": "Positive tal flytter teksten længere væk, negative tal flytter teksten tættere på; hvis z-positionen er nul, er det 2D" }, "compareOnlyInOneFile": "Kun i én fil", "compareTextOrTimeDifference": "Tekst-/tidsforskel", @@ -1616,8 +1691,8 @@ "lengthLessThan": "Længde <", "lengthGreaterThan": "Længde >", "pixelLengthGreaterThan": "Pixellængde >", - "gapLessThan": "Mellemrum i ms <", - "gapGreaterThan": "Mellemrum i ms >", + "gapLessThan": "Mellemrum i ms <", + "gapGreaterThan": "Mellemrum i ms >", "exactlyOneLine": "Præcis én linje", "exactlyTwoLines": "Præcis to linjer", "moreThanTwoLines": "Mere end to linjer", @@ -1642,7 +1717,6 @@ "selectionIntersect": "Kryds med markering" }, "multipleReplace": { - "title": "Flere udskiftninger", "editRule": "Rediger regel", "newRule": "Ny regel", "editCategory": "Rediger kategori", @@ -1659,24 +1733,23 @@ "findWhat": "Find hvad", "descriptionOptional": "Beskrivelse (valgfrit)", "invalidRegularExpressionX": "Ugyldigt regulært udtryk: {0}", - "regularExpressionTooSlowX": "Det regulære udtryk gav op efter {0} sekunder - reglen springes over, prøv et enklere mønster" + "regularExpressionTooSlowX": "Det regulære udtryk gav op efter {0} sekunder - reglen springes over, prøv et enklere mønster", + "moveToCategory": "Flyt til kategori" }, "find": { "searchTextWatermark": "Søg tekst...", "wholeWord": "Hele ord", - "caseSensitive": "Der skelnes mellem store og små bogstaver", - "caseInsensitive": "Ufølsom mellem store og små bogstaver", "findPrevious": "F_ind forrige", "findNext": "_Find næste", "replaceAndFindNext": "_Erstat og find næste", "replaceAll": "Erstat _alle", - "replaceTitle": "Erstat", - "replaceWith": "Udskift med", "replaceTextWatermark": "Udskift med...", "replaceIn": "Erstat/søg i", "replaceInTextAndOriginal": "Tekst og original tekst", "replaceInTextOnly": "Kun tekst", - "replaceInOriginalOnly": "Kun original tekst" + "replaceInOriginalOnly": "Kun original tekst", + "replacedOneOccurrence": "Erstattede én forekomst", + "replacedXOccurrences": "Erstattede {0} forekomster" }, "noSubtitleSelected": "Ingen undertekst valgt", "pleaseSelectExactlyOneSubtitle": "Vælg venligst præcis én undertekst." @@ -1687,6 +1760,7 @@ "xLinesYSubtitles": "{0} linjer, {1} undertekster", "xLinesYSubtitlesZErrors": "{0} linjer, {1} undertekster, {2} fejl", "couldNotParseAsX": "Kunne ikke fortolkes som {0}", + "readAsXUseAsY": "Kilden kunne ikke læses som {1}, men den kunne læses som {0}.\nBrug linjerne og fortsæt med at gemme som {1}?", "selectedXCharactersYLines": "Valgt: {0} tegn i {1} linje(r)", "replacedXOccurrences": "Erstattede {0} forekomst(er)", "invalidRegularExpression": "Ugyldigt regulært udtryk", @@ -1698,7 +1772,6 @@ "closeSearch": "Luk søgning" }, "tools": { - "pickAlignmentTitle": "Vælg justering", "removeUnicodeCharacters": { "title": "Fjern/erstat Unicode-tegn", "charactersFoundX": "Unicode-tegn fundet: {0}", @@ -1708,30 +1781,22 @@ "pickFontNameInstalledFonts": "Installerede skrifttyper", "pickFontNameCollectedFonts": "Indsamlede skrifttyper", "colorPickerTitle": "Vælg farve", + "colorPickerBrightness": "Lysstyrke", "filterLayersTitle": "Filterlag til visning", "filterLayersHideFromWaveform": "Skjul fra bølgeform/spektrogram", "filterLayersHideFromSubtitleGrid": "Skjul fra undertekstgitter", "filterLayersHideFromVideoPreview": "Skjul fra videoeksempel", "pickSubtitleFormat": "Vælg undertekstformat", - "pickLayerTitle": "Vælg lag", "recentColors": "Nylige farver", "fixCommonErrors": { - "title": "Ret almindelige fejl", - "step1": "Trin 1/2 - Vælg hvilke fejl der skal rettes", - "whatToFix": "Hvad skal rettes", - "example": "Eksempel", - "selectAll": "Vælg alle", - "inverseSelection": "Invertér markering", "back": "< &Tilbage", "next": "&Næste >", - "step2": "Trin 2/2 - Bekræft rettelser", "fixes": "Rettelser", "ruleDetailsDotDotDot": "Regeldetaljer...", "showOnlyThisRule": "Vis kun rettelser fra denne regel", "appliedRulesForLineX": "Anvendt(e) regel/regler for linje {0}", "ruleX": "Regel: {0}", "log": "Log", - "function": "Funktion", "removedEmptyLine": "Fjern tom linje", "removedEmptyLineAtTop": "Fjern tom linje øverst", "removedEmptyLineAtBottom": "Fjern tom linje nederst", @@ -1752,13 +1817,13 @@ "removeLineBreaksPixelWidth": "Saml undertekster, der kan være på én linje (pixelbredde)", "fixUppercaseIInsideLowercaseWords": "Ret stort 'i' inde i småbogstavsord (OCR-fejl)", "fixDoubleApostrophes": "Ret dobbelte apostroffer ('') til ét anførselstegn (\")", + "fixMisreadQuotes": "Ret apostroffer fejllæst som dobbelte anførselstegn (OCR, engelsk)", "addPeriods": "Tilføj punktum efter linjer, hvor næste linje starter med stort bogstav", "startWithUppercaseLetterAfterParagraph": "Start med stort bogstav efter afsnit", "startWithUppercaseLetterAfterPeriodInsideParagraph": "Start med stort bogstav efter punktum inde i afsnit", "startWithUppercaseLetterAfterColon": "Start med stort bogstav efter kolon/semikolon", "fixLowercaseIToUppercaseI": "Ret enkeltstående lille 'i' til 'I' (engelsk)", "fixCommonOcrErrors": "Ret almindelige OCR-fejl (ved brug af OCR-erstatningsliste)", - "commonOcrErrorsFixed": "Almindelige OCR-fejl rettet (OcrReplaceList-fil brugt): {0}", "removeSpaceBetweenNumber": "Fjern mellemrum mellem tal", "breakDialogsOnOneLine": "Del dialoger på én linje", "removeDialogFirstInNonDialogs": "Fjern indledende bindestreg i første linje for ikke-dialoger", @@ -1775,18 +1840,17 @@ "fixCommasExample": ",, -> ,", "removeLineBreaksExample": "Foo
bar!\r\n-> Foo bar!", "fixDoubleApostrophesExample": "''Har dobbelte enkelt anførselstegn'' -> \"Har enkelt dobbelte anførselstegn", + "fixMisreadQuotesExample": "\"Hello' -> \"Hello\"", "addPeriodsExample": "Hello world -> Hello world.", "startWithUppercaseLetterAfterParagraphExample": "p1: Foobar! || p2: foobar! -> p1: Foobar! || p2: Foobar!", "startWithUppercaseLetterAfterPeriodInsideParagraphExample": "Hello there! how are you? -> Hello there! How are you?", "startWithUppercaseLetterAfterColonExample": "Speaker: hello world! -> Speaker: Hello world!", "removeHyphensSingleLineExample": "- Foobar. -> Foobar.", "fix3PlusLinesExample": "Foo
bar
baz! -> Foo bar baz!", - "xMissingQuotesAdded": "Manglende anførselstegn tilføjet: {0}", "fix3PlusLine": "Ret undertekst med mere end to linjer", "fix3PlusLines": "Ret undertekster med mere end to linjer", "analysing": "Analyserer...", "nothingToFix": "Intet at rette :)", - "fixesFoundX": "Fundne rettelser: {0}", "xFixesApplied": "Anvendte rettelser: {0}", "nothingFixableBut": "Intet kunne rettes automatisk. Underteksten indeholder fejl - se loggen for detaljer", "xFixedBut": "{0} problem(er) rettet, men underteksten indeholder stadig fejl - se loggen for detaljer", @@ -1794,7 +1858,6 @@ "fixFirstLetterToUppercaseAfterParagraph": "Ret første bogstav til stort efter afsnit", "mergeShortLine": "Flet kort linje (enkelt sætning)", "mergeShortLineAll": "Flet kort linje (alle undtagen dialoger)", - "unbreakShortLinePixelWidth": "Saml kort linje (pixelbredde)", "breakLongLine": "Ombryd lang linje", "fixLongDisplayTime": "Ret lang visningstid", "fixInvalidItalicTag": "Ret ugyldigt kursiv-tag", @@ -1817,7 +1880,6 @@ "fixUppercaseIInsideLowercaseWord": "Ret stort 'i' inde i småbogstavsord", "fixMissingPeriodAtEndOfLine": "Tilføj manglende punktum i slutningen af linjen", "refreshFixes": "Opdater tilgængelige rettelser", - "applyFixes": "&Anvend valgte rettelser og luk", "autoBreak": "Auto-&ombryd", "unbreak": "&Saml", "fixDoubleDash": "Ret '--' -> '...'", @@ -1836,8 +1898,6 @@ "fixSpaceBetweenNumbersExample": "1 100 -> 1100", "fixDialogsOneLineExample": "Hi John! - Hi Ida! -> Hi John!
- Hi Ida!", "removeDialogFirstInNonDialogsExample": "- How are you? -> How are you?", - "selectDefault": "Vælg standard", - "setDefault": "Angiv aktuelle rettelser som standard", "fixContinuationStyleX": "Ret fortsættelsesstil: {0}", "fixUnnecessaryLeadingDots": "Fjern unødvendige indledende punktummer", "fixCommonErrorsProfiles": "Profiler til retning af almindelige fejl", @@ -1845,7 +1905,6 @@ "searchRulesDotDotDot": "Søgeregler...", "goToApplyFixes": "Gå til at anvende rettelser", "backToFixList": "Tilbage til rettelseslisten", - "applyFixesAndClose": "Anvend rettelser og luk", "fixCommonOcrErrorsStep2": "Ret almindelige fejl, trin 2 (anvend rettelser)", "fixCommonOcrErrorsStep2FixesFoundX": "Ret almindelige fejl, trin 2 - Rettelser fundet: {0}", "action": "Handling", @@ -1859,18 +1918,11 @@ "adjustDurations": { "title": "Juster varigheder", "adjustVia": "Juster via", - "seconds": "Sekunder", - "percent": "Procent", "recalculate": "Genberegn", "addSeconds": "Tilføj sekunder", - "setAsPercent": "Angiv som procent af varighed", "note": "Bemærk: Visningstiden vil ikke overlappe starttiden for næste tekst", "fixed": "Fast", - "milliseconds": "Millisekunder", "extendOnly": "Forlæng kun", - "enforceDurationLimits": "Håndhæv minimum- og maksimumvarighed", - "checkShotChanges": "Udvid ikke forbi sceneskift", - "batchCheckShotChanges": "Respektér sceneskift (hvis tilgængeligt)", "recalculateRequiresOcrNote": "Genberegning er utilgængelig — en eller flere undertekster har ingen OCR-tekst. Kør OCR først." }, "applyDurationLimits": { @@ -1878,6 +1930,8 @@ "fixMinDurationMs": "Fast minimumsvarighed (ms)", "doNotGoPastShotChange": "Gå ikke forbi sceneskifte", "fixMaxDurationMs": "Ret maksimal varighed (ms)", + "fixMinDurationFrames": "Ret minimumsvarighed (billeder)", + "fixMaxDurationFrames": "Ret maksimumsvarighed (billeder)", "minimumDurationMilliseconds": "Minimal varighed (millisekunder):", "maximumDurationMilliseconds": "Maksimal varighed (millisekunder):", "maxDurationShouldBeHigherThanMinDuration": "Maksimal varighed bør være højere end minimumsvarighed", @@ -1897,12 +1951,8 @@ }, "beautifyTimeCodes": { "title": "Forskøn tidskoder", - "beautifySettings": "Forskøn indstillinger", "snapToFrames": "Fastgør til billeder", - "frameGap": "Billedmellemrum", "minDuration": "Min varighed (ms)", - "shotChangeThreshold": "Sceneskift-tærskel (ms)", - "shotChangeOffset": "Sceneskift-forskydning (billeder)", "original": "Original", "beautified": "Forskønnet", "subtitlesCount": "Undertekster", @@ -1931,13 +1981,80 @@ "timeCodesExtractFailed": "Kunne ikke læse tidskoder fra videoen", "timeCodesHint": "Fastgør start-/slutpunkter til videoens reelle billedtider i stedet for at antage en konstant billedhastighed. Kræver, at hele videoen læses igennem én gang; resultatet gemmes i cachen." }, + "improveTimeCodes": { + "title": "Forbedr tidskoder (forced alignment)", + "aligner": "Aligner", + "alignerHint": "Modellen, der lytter efter hver linje i lyden. De bedste valg for undertekstens sprog vises først.", + "maxShift": "Maks. forskydning (sekunder)", + "maxShiftHint": "Hvor langt underteksten må være ude af synkronisering. En linje, hvis start ville flytte sig længere, efterlades som den er og markeres. Uanset værdien flyttes en enkelt linje, der vil flytte sig mere end et halvt sekund længere end linjerne omkring den, ikke blindt: den følger sine naboer, når de alle er flyttet, og tilbydes ellers uden flueben, så du kan tjekke den.", + "adjustStartTimes": "Juster starttider", + "adjustEndTimes": "Juster sluttider", + "isolateSpeech": "Isoler tale først (langsom)", + "isolateSpeechHint": "Fjerner musik og lydeffekter før justering, så aligneren kun hører dialogen. Hjælper på linjer, der tales over høj musik eller action, men tager omtrent lige så lang tid som selve lyden med en GPU - og mange gange længere uden.", + "isolatingSpeech": "Isolerer tale... (dette tager et stykke tid)", + "isolateSpeechFailed": "Kunne ikke isolere talen - justeret mod den originale lyd i stedet.", + "checkWithSpeechToText": "Tjek med tale til tekst", + "checkWithSpeechToTextHint": "Transskriberer også lyden og tjekker hver flytning mod, hvor ordene faktisk blev hørt. Store flytninger, som talen bekræfter, får flueben for dig, og flytninger væk fra, hvor en linje høres, efterlades uden flueben, så du kan tjekke dem. Viser også, hvor meget af hver linje der blev hørt - en lav andel betyder, at teksten afviger fra det, der bliver sagt. En undertekst, der er længere ude af synkronisering end den maks. forskydning - en konstant forskydning, en drift fra forkert billedhastighed eller spring, hvor scener blev klippet - synkroniseres først ud fra de hørte ord. Bruger Crisp ASR Parakeet, som tager nogle få minutter for en spillefilm med en GPU.", + "checkWithSpeechToTextNotAvailableX": "Tjek med tale til tekst er ikke tilgængeligt for {0}", + "transcribingToCheck": "Transskriberer for at tjekke justeringen...", + "speechToTextFailed": "Tale til tekst mislykkedes - tidskoderne blev ikke tjekket mod den.", + "speechToTextX": "Tale til tekst: {0}", + "speechToTextConfirmedXDisputedY": "Tale: {0} bekræftet, {1} omstridt", + "hardlyHeardX": "Tale til tekst hørte kun {0}% af undertekstens ord. Er underteksten på det sprog, der tales? Aligneren kan ikke placere en oversættelse.", + "heard": "Hørt", + "heardHint": "Hvor meget af linjen tale til tekst hørte. En lav andel betyder, at teksten afviger fra det, der bliver sagt - hvor aligneren mest sandsynligt tager fejl.", + "statusConfirmedBySpeech": "Bekræftet af tale", + "statusDisputedBySpeech": "Hørt hvor den var", + "statusMovedWithSync": "Flyttet med synkroniseringen", + "syncedFirstXY": "Synkroniseret først: {0} s i starten, {1} s i slutningen", + "showSpeechOnly": "Vis kun tale", + "showSpeechOnlyHint": "Tegn den justerede bølgeform ud fra lyden med musik og lydeffekter fjernet; den originale bølgeform forbliver uændret til sammenligning. Tilgængelig, når talen er blevet isoleret - her eller med \"Vis kun tale\" i hovedvinduets bølgeform.", + "align": "Justér", + "original": "Original", + "aligned": "Justeret", + "intro": "Vælg en aligner, og tryk på \"Justér\" for at lytte efter hver linje i lyden og stramme dens tidskoder.", + "extractingAudio": "Udtrækker lyd...", + "aligningBatchXOfY": "Justerer... batch {0} af {1}", + "extractAudioFailed": "Kunne ikke udtrække lyden med ffmpeg.", + "modelWillBeDownloaded": "{0} vil blive downloadet", + "summaryXRetimedYKeptZSkipped": "Omtimet: {0} · Beholdt: {1} · Ingen tale: {2}", + "meanShiftX": "Gennemsnitlig forskydning: {0} ms", + "toCheckX": "Til tjek: {0}", + "largeMovesToCheckX": "{0} linje(r) ville flytte sig langt på egen hånd og er efterladt uden flueben - lyt til dem (F5 afspiller den justerede linje, Shift+F5 den originale), og sæt flueben ved dem, der er rigtige.", + "linesToCheckX": "{0} linje(r) er efterladt uden flueben til tjek - lyt til dem (F5 afspiller den justerede linje, Shift+F5 den originale), og sæt flueben ved dem, der er rigtige.", + "changeXOfY": "Ændring {0} af {1}", + "previousChange": "Forrige ændring", + "nextChange": "Næste ændring", + "startShift": "Start", + "endShift": "Slut", + "statusRetimed": "Omtimet", + "statusMovedWithNeighbours": "Flyttet med sine naboer", + "statusLargeMoveUnconfirmed": "Stor flytning - lyt, sæt flueben for at anvende", + "statusUnchanged": "Allerede på plads", + "statusNoSpeech": "Ingen tale", + "statusShiftTooLarge": "Beholdt - forskydning for stor", + "statusFailed": "Beholdt - aligner mislykkedes", + "statusAdjustedByHand": "Justeret manuelt", + "alignedWaveformHint": "Træk en linje for at flytte den, eller træk i dens kanter for at ændre dens start eller slut.", + "playLine": "Afspil linje", + "undoAdjustment": "Fortryd justering", + "playPause": "Afspil / pause", + "playAligned": "Justeret", + "playAlignedHint": "Afspil den valgte linje med dens justerede tidskoder", + "apply": "Anvend", + "applyHint": "Fjern fluebenet ved en linje for at beholde dens originale tidskoder. Dobbeltklik på en linje for at afspille den. Linjer kan også flyttes eller ændres i størrelse i den justerede bølgeform." + }, "beautifyTimeCodesProfile": { "title": "Rediger profil for forskønnelse af tidskoder", "loadPreset": "Indlæs forudindstilling", "presetDefault": "Standard", "presetNetflix": "Netflix", "presetSdi": "SDI", - "general": "Generelt", + "saveAsProfile": "Gem som profil...", + "deleteProfile": "Slet profil", + "profileName": "Profilnavn", + "overwriteProfileX": "Der findes allerede en profil med navnet \"{0}\". Overskriv den?", + "deleteProfileX": "Slet profilen \"{0}\"?", "gap": "Mellemrum", "gapSuffix": "billeder (opdaterer også felter for mellemrum forskellige fra nul nedenfor)", "milliseconds": "ms", @@ -1966,9 +2083,12 @@ "hintChaining": "Hvordan undertekster, der næsten rører hinanden, håndteres — udvid dem sammen, eller skub dem fra hinanden, afhængigt af om der ligger et sceneskift imellem." }, "bridgeGaps": { - "title": "Overbryg mellemrum", "bridgeGapsSmallerThan": "Overbryg mellemrum mindre end (ms)", "minGap": "Minimum mellemrum (ms)", + "bridgeGapsSmallerThanFrames": "Overbryg mellemrum mindre end (billeder)", + "minGapFrames": "Minimum mellemrum (billeder)", + "calculateBridgeGapsSmallerThanDotDotDot": "Beregn grænse for overbrygning ud fra en billedhastighed...", + "useXMsAsBridgeGapsSmallerThan": "Overbryg mellemrum mindre end \"{0}\" millisekunder?", "numberOfSmallGapsBridgedX": "Antal små mellemrum, der er slået bro over: {0}", "percentFoPrevious": "Mellemrum for forrige (%)", "gapChange": "Mellemrumsændring" @@ -1980,8 +2100,6 @@ "sortBy": { "title": "Sorter undertekster", "sortOrder": "Sorteringsrækkefølge", - "sortByNumber": "Nummer", - "sortByStartTime": "Starttidspunkt", "sortByEndTime": "Sluttidspunkt" }, "batchConvert": { @@ -2004,6 +2122,7 @@ "addColor": "Tilføj farve", "deleteLinesWithSpecificActorsOrStyles": "Slet linjer med skuespillere eller stilarter (adskil flere med komma)", "useSourceStylesIfPossible": "Brug kildestilarter, hvis muligt", + "keepSourceEmbeddedFonts": "Behold kildens indlejrede skrifttyper (footer)", "editStyles": "Rediger stilarter", "editProperties": "Rediger egenskaber", "editAttachments": "Rediger vedhæftede filer", @@ -2024,7 +2143,6 @@ "assaChangeStyleFromStyle": "Skift stil fra", "assaChangeStyleToStyle": "til", "assaChangeStyleImportStyle": "Importér stil...", - "assaChangeStyleImportedX": "Importeret: {0}", "assaChangeStyleTrimUnusedStyles": "Fjern ubrugte stilarter", "assaChangeStylePropertiesTitle": "Skift stilegenskaber", "assaChangeStylePropertiesInfo": "Ændrer de valgte felter i alle stilarter i filen, uden at røre selve stilarterne.", @@ -2036,13 +2154,52 @@ "convertColorsToDialogReBreakLines": "Ombryd linjer igen", "snapTimeCodesToFramesInfo": "Runder hver start- og sluttid til nærmeste billede. Billedhastigheden læses fra en videofil med samme navn som undertekstfilen, medmindre der vælges en fast billedhastighed.", "addFolderDotDotDot": "Tilføj mappe...", + "addFolderRecursiveDotDotDot": "Tilføj mappe rekursivt...", "selectFolderToConvert": "Vælg mappe med filer, der skal konverteres", "includeSubfolders": "Medtag undermapper, når en mappe tilføjes", + "videoFilesWhenAddingFolder": "Videofiler (mkv, mp4, ts...) ved tilføjelse af en mappe", + "videoFilesAsk": "Spørg", + "videoFilesInclude": "Tilføj deres undertekstspor", + "videoFilesSkip": "Spring over", + "folderContainsXVideoFiles": "Mappen indeholder {0} videofil(er) (mkv, mp4, ts...).\n\nTilføj også deres indlejrede undertekstspor, eller kun undertekstfilerne?", + "addVideoFiles": "Tilføj videofiler", + "skipVideoFiles": "Kun undertekstfiler", + "doNotAskAgainVideoFiles": "Spørg ikke igen (ændres i indstillingerne for batchkonvertering)", + "settingsSectionOutput": "Output", + "settingsSectionAddingFiles": "Tilføjelse af filer", + "settingsSectionImageBasedSubtitles": "Billedbaserede undertekster (OCR)", + "settingsSectionOther": "Andet", "keepSourceFileTimestamp": "Behold kildefilens dato/tid på outputfilerne", - "scanningFolderX": "Scanner {0}..." + "preventSleepWhileConverting": "Forhindr computeren i at gå i dvale under konvertering", + "scanningFolderX": "Scanner {0}...", + "transportStreamSettings": "Transport Stream-indstillinger", + "transportStreamSettingsDotDotDot": "Transport Stream-indstillinger...", + "transportStreamSettingsInfo": "Gælder for undertekstspor udtrukket fra Transport Stream-filer (.ts/.m2ts). Position og videostørrelse påvirker kun DVB-billedspor, der eksporteres til et billedbaseret format.", + "transportStreamOverrideXPosition": "Tilsidesæt original X-position", + "transportStreamOverrideYPosition": "Tilsidesæt original Y-position", + "transportStreamOverrideVideoSize": "Tilsidesæt original videostørrelse", + "transportStreamBottomMargin": "Bundmargen", + "transportStreamFileNameEnding": "Filnavnsendelse", + "transportStreamFileNameEndingInfo": "Tilføjes før filtypenavnet for hvert udtrukket spor. Lad være tom for at bruge det almindelige sprogsuffiks.", + "transportStreamOnlyTeletext": "Kun tekst-tv", + "transportStreamGetSizeFromVideo": "Hent størrelse fra video...", + "twoLetterLanguageCodeUppercase": "Sprogkode på to bogstaver (store bogstaver)", + "threeLetterLanguageCodeUppercase": "Sprogkode på tre bogstaver (store bogstaver)", + "formatContainsDotDotDot": "Format indeholder...", + "extensionIsDotDotDot": "Filtypenavn er...", + "statusIsError": "Status er fejl", + "forcedTracksOnly": "Kun tvungne spor", + "xConvertedYFailedInZ": "{0:#,###,##0} konverteret, {1:#,###,##0} mislykkedes på {2}", + "xNotProcessed": "{0:#,###,##0} ikke behandlet", + "removeUnicodeControlCharactersTitle": "Fjern Unicode-kontroltegn", + "removeUnicodeControlCharactersInfo": "Fjerner de usynlige Unicode-retningskontroltegn (LRM, RLM, LRE, RLE, PDF, LRO og RLO) fra teksten og erstatter hårde mellemrum med normale mellemrum.", + "preset": "Forudindstilling", + "savePresetDotDotDot": "Gem forudindstilling...", + "deletePreset": "Slet forudindstilling", + "presetName": "Navn på forudindstilling", + "deletePresetX": "Slet forudindstillingen \"{0}\"?" }, "changeCasing": { - "title": "Skift store/små bogstaver", "fixNames": "Ret navne", "namesX": "Navne: {0:#,##0}", "hitsX": "Træffere: {0:#,##0}", @@ -2053,9 +2210,7 @@ "allUppercase": "Alle store bogstaver", "allLowercase": "Alle små bogstaver" }, - "changeFormatting": { - "title": "Skift formatering" - }, + "changeFormatting": {}, "convertActors": { "title": "Konverter skuespillere", "convertActorFrom": "Konverter skuespiller fra", @@ -2084,18 +2239,12 @@ "outputFormatAssa": "Advanced Sub Station Alpha (.ass)", "style1": "Stil 1", "style2": "Stil 2", - "outlineWidth": "Omridsbredde", - "shadowWidth": "Skyggebredde", - "alignTop": "Top", - "alignBottom": "Bund", - "merge": "_Flet sammen", - "pleaseLoadTwoSubtitles": "Indlæs venligst to undertekster" + "merge": "_Flet sammen" }, "splitSubtitle": { "title": "Opdel undertekst", "numberOfEqualParts": "Antal lige store dele", "saveSplitParts": "_Gem delte dele", - "subtitleSplitIntoXParts": "Undertekster opdelt i {0} dele.", "xPartsSavedInFormatYToFolder": "{0} dele gemt i formatet {1} i mappen:" }, "splitBreakLongLines": { @@ -2129,18 +2278,17 @@ "columnMerge": "Flet", "columnFirst": "Første linje", "columnSecond": "Anden linje", - "columnMerged": "Flettet", - "selectAll": "Vælg alle", - "inverseSelection": "Omvendt valg" + "columnMerged": "Flettet" }, "mergeLinesWithSameText": { - "title": "Flet linjer med samme tekst", "maxMsBetweenLines": "Maks. millisekunder mellem linjer", - "includeIncrementingLines": "Inkluder linjer med stigende tekst" + "maxFramesBetweenLines": "Maks. billeder mellem linjer", + "includeIncrementingLines": "Inkluder linjer med stigende tekst", + "includeRollUpCaptions": "Medtag roll-up-undertekster (rullende)" }, "mergeLinesWithSameTimeCodes": { - "title": "Flet linjer med samme tidskoder", "maxMsDifference": "Max forskel (millisekunder)", + "maxFramesDifference": "Maks. forskel (billeder)", "makeDialog": "Lav dialoger" }, "netflixCheckAndFix": { @@ -2163,7 +2311,6 @@ "shotChanges": "Sceneskift", "startNumberSpellOut": "Startnummer staves ud", "textforHiUseBrackets": "Tekst til HI, brug parenteser", - "frameRate": "Billedhastighed", "twoFrameGrap": "To billeders mellemrum", "whiteSpace": "Blanktegn", "childrensProgram": "Børneprogram", @@ -2201,7 +2348,6 @@ "italicsFixed": "Kursiv rettet", "italicsNotAllowed": "Kursiv ikke tilladt", "bridgeGapsXToYFrames": "3-{0} billeders mellemrum => 2 billeders mellemrum", - "startNumberSpellOutComment": "Når et tal begynder en sætning, skal det altid staves", "maximumXCharactersPerSecond": "Maksimalt {0} tegn pr. sekund" }, "imageBasedEdit": { @@ -2210,10 +2356,8 @@ "resizeImagesDotDotDot": "Ændr størrelse på billeder...", "adjustBrightnessDotDotDot": "Juster lysstyrken...", "adjustAlphaDotDotDot": "Juster alfa...", - "centerHorizontally": "Centrer vandret", "cropImages": "Beskær billeder", "importTimeCodes": "Importer tidskoder...", - "setTextForSubtitle": "Indstil tekst til undertekst", "setText": "Indstil tekst", "screenWidth": "Skærmbredde", "screenHeight": "Skærmhøjde", @@ -2244,15 +2388,31 @@ "letterboxCustom": "Brugerdefineret bjælkehøjde", "barHeightPx": "Bjælkehøjde (px)", "titleSafePercent": "Titelsikkert område (%)", + "moveCaptions": "Flyt undertekster", + "moveCaptionsDotDotDot": "Flyt undertekster...", + "moveCaptionsTo": "Flyt undertekster", + "moveCaptionsIntoBars": "Ind i letterbox-bjælkerne (uden for billedet)", + "moveCaptionsIntoPicture": "Inde i billedet (ud af bjælkerne)", + "moveCaptionsOffset": "Afstand fra kant (px)", + "letterboxBarHeightXPxYOfZCaptionsWillMove": "Bjælkehøjde: {0} px - {1} af {2} undertekster vil blive flyttet", + "removeFadeInOut": "Fjern fade ind/ud", + "removeFadeInOutXLinesRemoved": "{0} fade-linjer flettet ind i de linjer, de hører til", + "removeFadeInOutNothingFound": "Ingen fade ind/ud-linjer fundet", "positionSummary": "{0}×{1} - {2} undertekster - bjælkehøjde: {3} px", "xInActivePicture": "{0} i billedet", "xInTopBar": "{0} i øverste bjælke", "xInBottomBar": "{0} i nederste bjælke", "noImageSubtitlesLoaded": "Ingen billedbaserede undertekster indlæst", - "imageBasedFormatNotSupported": "Billedbaseret undertekstformat blev ikke fundet/understøttes ikke." + "imageBasedFormatNotSupported": "Billedbaseret undertekstformat blev ikke fundet/understøttes ikke.", + "frameRateDeclaredInFile": "Billedhastighed angivet i Blu-ray sup-filen", + "frameRateDetectedFromTimeCodesX": "Billedhastighed registreret ud fra Blu-ray sup-tidskoderne (filen angiver {0})", + "frameRateFromVideo": "Videoens billedhastighed", + "frameRateCurrent": "Aktuel billedhastighed", + "frameRateSetManually": "Billedhastighed angivet manuelt", + "frameRateUsageInfo": "Blu-ray sup-, BDN XML-, DOST- og FCP-eksporter skrives med denne billedhastighed. Den ændrer ingen tider - brug \"Skift billedhastighed\" til det.", + "useVideoFrameRateXInsteadOfY": "Videoens billedhastighed er {0}, men underteksten er sat til {1}.\n\nBrug videoens billedhastighed ({0})?" }, "removeTextForHearingImpaired": { - "title": "Fjern tekst for hørehæmmede", "interjections": "Interjektioner", "skipIfStartWith": "Spring over hvis begynder med", "removeTextBetween": "Fjern tekst mellem", @@ -2274,7 +2434,6 @@ "aiReview": { "title": "AI-gennemgang", "review": "Gennemgå", - "stop": "Stop", "editPrompt": "Rediger prompt...", "editPromptTitle": "Rediger gennemgangsprompt", "promptInfo": "Instruktioner sendt til modellen - {language} erstattes med undertekstens sprog.", @@ -2285,7 +2444,6 @@ "reviewingLineXOfY": "Gennemgår linje {0} af {1}...", "reviewDone": "Gennemgang udført - {0} forslag i {1} linjer", "noIssuesFound": "Ingen problemer fundet", - "categoryAll": "Alle", "categorySpelling": "Stavning", "categoryGrammar": "Grammatik", "categoryPunctuation": "Tegnsætning", @@ -2297,8 +2455,23 @@ "largeChangeWarning": "Stor ændring - ligner en omskrivning, gennemgå omhyggeligt", "mismatchWarning": "Ligner ikke originalen - hører måske til en anden linje", "engineError": "AI-motoren kunne ikke nås: {0}", - "setupHint": "Vælg først en motor og en model i Værktøjer -> AI-gennemgang.", - "playCurrentLineHint": "Afspil den valgte linje i videoafspilleren, og sæt på pause ved dens slutning (du kan også dobbeltklikke på en linje)" + "playCurrentLineHint": "Afspil den valgte linje i videoafspilleren, og sæt på pause ved dens slutning (du kan også dobbeltklikke på en linje)", + "editAfterHint": "Klik på teksten i den valgte række for at redigere rettelsen, før den anvendes (Enter gemmer, Escape annullerer)", + "template": "Skabelon", + "templatePlaceholder": "Vælg en skabelon til at erstatte prompten", + "templateStandard": "Standard korrekturlæsning", + "templateSpeechToText": "Oprydning efter tale til tekst", + "templateMachineTranslation": "Rettelse af maskinoversættelse", + "templateSdh": "SDH / hørehæmmede", + "referenceContext": "Referencekontekst", + "referenceContextInfo": "Valgfrit. Navne, personer, ordliste, synopsis eller stilnoter - modellen behandler dette som autoriteten for navne og termer. Sendes med hver batch.", + "referenceContextPlaceholder": "Navne: Kaito, tante Mei, Dr. Hollis\nTermer: Shinkansen, warpkerne\nSynopsis: Kaito rejser til Tokyo for at finde sin forsvundne far.", + "generateContext": "Generer med AI", + "generateContextHint": "Lad den valgte AI-motor læse underteksten og lave udkast til navne, termer og en synopsis - kontrollér resultatet før brug", + "generatingContextPartXOfY": "Læser del {0} af {1}...", + "generatingContextSynopsis": "Skriver synopsis...", + "replaceContextQuestion": "Erstat den aktuelle referencekontekst?", + "noContextGenerated": "AI-motoren returnerede ingen brugbare referenceoplysninger." }, "aiAssistant": { "title": "AI-assistent", @@ -2359,7 +2532,21 @@ "correctWordsX": "Korrekte ord: {0}", "namesX": "Navne: {0}", "addedToDictionaryX": "Føjet til ordbog: {0}", - "doNotShowThisAgain": "Vis ikke denne meddelelse igen" + "doNotShowThisAgain": "Vis ikke denne meddelelse igen", + "editUseAlwaysList": "Rediger listen \"Brug altid\"...", + "useAlwaysListTitle": "Stavekontrol - listen \"Brug altid\"", + "useAlwaysListHeader": "Listen \"Brug altid\"", + "useAlwaysListDescription": "Ord, som stavekontrollen erstatter automatisk, gemt via \"Skift alle\" og \"Brug altid\". Et par bruges kun, når ordet er stavet forkert.", + "useAlwaysWord": "Forkert stavet ord", + "xPairs": "{0:#,##0} par", + "xUnusedPairs": "{0:#,##0} aldrig brugt", + "useAlwaysUnusedHint": "Aldrig brugt - dette ord er stavet korrekt (eller er et navn eller brugerord), så stavekontrollen markerer det aldrig", + "removeUnused": "Fjern ubrugte", + "useAlwaysEmpty": "Ingen \"Brug altid\"-par for dette sprog endnu", + "useAlwaysEmptyHint": "Par tilføjes, når du klikker på \"Skift alle\" eller \"Brug altid\" i stavekontrollen.", + "useAlwaysNoMatch": "Ingen par matcher søgningen", + "useAlwaysSameWord": "Erstatningen skal være forskellig fra ordet.", + "useAlwaysRememberOff": "\"Husk listen 'Brug altid'\" er slået fra i Indstillinger, så stavekontrollen hverken bruger eller gemmer denne liste." }, "video": { "chapters": { @@ -2390,7 +2577,6 @@ "scaleTimesDescription": "Skalér hvert kapitels tid fra én billedhastighed til en anden.", "fromFrameRate": "Fra", "toFrameRate": "Til", - "apply": "Anvend", "writeToVideo": "Skriv til video...", "writeToVideoTitle": "Skriv kapitler til video", "writeToVideoDescription": "Kapitlerne skrives ind i en kopi af videofilen. Intet genkodes, så det går hurtigt og uden kvalitetstab.", @@ -2400,7 +2586,6 @@ "writtenToX": "Kapitler skrevet til {0}", "unableToWriteChapters": "Kunne ikke skrive kapitler til videofilen.", "chaptersSavedToX": "Kapitler gemt i {0}", - "noVideoLoaded": "Ingen video indlæst", "emptyListCallToAction": "Ingen kapitler endnu - tilføj et ved videopositionen, eller importér dem fra en video eller fil." }, "goToVideoPosition": "Gå til videoposition", @@ -2412,30 +2597,88 @@ "openSecondarySubtitleOnVideoPlayerDotDotDot": "Sekundær undertekst på videoafspiller, åbn...", "openSecondarySubtitleOnVideoPlayer": "Sekundær undertekst (på videoafspiller)", "removeSecondarySubtitleOnVideoPlayer": "Sekundær undertekst på videoafspiller, fjern", + "editSecondarySubtitleSettings": "Rediger indstillinger for sekundær undertekst", + "editSecondarySubtitleSettingsDotDotDot": "Rediger indstillinger for sekundær undertekst...", + "secondarySubtitleRememberSettings": "Husk disse indstillinger", + "secondarySubtitleDoNotShowAgain": "Vis ikke denne dialog igen", + "secondarySubtitleJustifyLines": "Justér linjer", "openRecentVideo": "Åbn seneste video", "clearRecentVideos": "Ryd seneste videoer", "cutVideoTitle": "Klip video", "cutVideoDotDotDot": "Klip video...", + "remuxVideoTitle": "Remux video", + "remuxVideoDotDotDot": "Remux video...", + "remuxVideoInputVideo": "Inputvideo:", + "remuxVideoMultipleTracksRequiresMkv": "Flere lyd- eller undertekstspor kræver MKV-containeren. Outputformatet er automatisk skiftet til .mkv.", + "remuxVideoOutputFormat": "Outputformat:", + "remuxVideoOutputFile": "Outputfil:", + "remuxVideoRemuxing": "Remuxer video og lyd...", + "remuxVideoFinalizing": "Færdiggør outputfil (flytter indeks til starten)...", + "remuxVideoCompleted": "Videoen blev remuxet.", + "remuxVideoFailed": "Kunne ikke remuxe video.", + "remuxVideoPleaseSelectBoth": "Vælg både video- og lydfiler.", + "remuxVideoAssRequiresMkv": "ASS/SSA-undertekster kræver MKV-containeren for at bevare al formatering og alle stilarter.", + "remuxVideoSccRequiresMov": "Scenarist-undertekster (.scc) indlejres som CEA-608 closed captions, som kræver MOV- eller MPG-containeren. Outputformatet er automatisk skiftet til .mov.", + "remuxVideoSccNotWithWebM": "Scenarist closed captions (.scc) kræver MOV-containeren, som ikke kan indeholde VP9/AV1-videoen fra en .webm-fil. Fjern .scc-filen, eller konvertér den til et andet undertekstformat.", + "remuxVideoMpgMaxTwoCaptionTracks": "En .mpg-fil kan højst indeholde to closed caption-spor (CC1 og CC3) - fjern undertekstfiler, så der er én eller to tilbage.", + "remuxVideoMccRequiresMpg": "MacCaption-undertekster (.mcc) indlejres som CEA-608/708 closed captions i MPEG-2-videoen, hvilket kræver MPG-containeren. Outputformatet er automatisk skiftet til .mpg.", + "remuxVideoMpgReencodeVideoX": "Closed captions i en .mpg-fil kræver MPEG-2-video, og videoen er {0}. Genkod videoen til MPEG-2? Dette tager længere tid og forringer kvaliteten en smule.", + "remuxVideoFileHasNoAudioX": "\"{0}\" har intet lydspor, så den kan ikke bruges som lydkilde.", + "remuxVideoAddingClosedCaptions": "Tilføjer closed captions...", + "remuxVideoSelectAudioTrack": "Vælg lydspor", + "remuxVideoSelectAudioTrackFor": "Vælg lydspor for {0}", + "remuxVideoSelectAudioTrackPrompt": "Filen '{0}' indeholder {1} lydspor. Vælg det lydspor, der skal remuxes:", + "remuxVideoVideoFile": "video", + "remuxVideoAudioFile": "lyd", + "remuxVideoAudioFiles": "Lydfiler:", + "remuxVideoSubtitleFiles": "Undertekstfiler (valgfrit, soft subtitles):", + "remuxVideoSelectAudioTrackDotDotDot": "Vælg lydspor...", + "remuxVideoFilesX": "{0} filer", + "remuxVideoMixAudio": "Mix lyd til ét spor", + "remuxVideoVolumePercent": "Lydstyrke %:", + "remuxVideoVolumeX": "Lydstyrke {0}%", + "remuxVideoFastStart": "Optimer til webstreaming (flyt indeks til starten - langsommere)", "embedSubtitlesDotDotDot": "Tilføj/fjern indlejrede undertekster...", "generateTimeCodes": "Generer tidskoder", "checkeredImage": "Ternet billede", - "saveVideoAsTitle": "Gem video som", "promptForFfmpegParamsAndGenerate": "Spørg efter ffmpeg-parametre og generer", "cutVideoCutSegments": "Klip segmenter", "cutVideoMergeSegments": "Flet segmenter", "cutVideoSplitSegments": "Gem segmenter individuelt", - "cutVideoType": "Klip type", - "mpvRenderAuto": "Auto", - "mpvRenderNative": "Indbygget", - "mpvRenderOpenGl": "OpenGL", - "mpvRenderSoftware": "Software (langsom)", "cutVideoAlsoCutSubtitle": "Klip også underteksten", "cutVideoSubtitleFileGeneratedX": "Undertekstfil genereret: {0}", + "cutVideoTransition": "Overgang mellem segmenter", + "cutVideoTransitionDurationSeconds": "Varighed (sekunder)", + "cutVideoFadeInSeconds": "Fade fra sort ved start (sekunder)", + "cutVideoFadeOutSeconds": "Fade til sort ved slutning (sekunder)", + "cutVideoPreviewTransition": "Forhåndsvis overgang", + "cutVideoPreviewTransitionTitle": "Forhåndsvisning af overgang", + "cutVideoPreviewNeedsJoin": "Der er ingen sammenføjning at forhåndsvise - tilføj mindst to segmenter, der skal flettes, eller et segment, der skal klippes ud af midten.", + "cutVideoTransitionInfoX": "Hver overgang overlapper de dele, den sammenføjer, så outputtet bliver {0} sekunder kortere pr. sammenføjning. En klippet undertekst får tilpasset sin timing tilsvarende.", + "cutVideoTransitionFade": "Fade", + "cutVideoTransitionFadeBlack": "Fade gennem sort", + "cutVideoTransitionFadeWhite": "Fade gennem hvid", + "cutVideoTransitionFadeGrays": "Fade gennem grå", + "cutVideoTransitionDissolve": "Opløs", + "cutVideoTransitionWipeLeft": "Visk mod venstre", + "cutVideoTransitionWipeRight": "Visk mod højre", + "cutVideoTransitionWipeUp": "Visk op", + "cutVideoTransitionWipeDown": "Visk ned", + "cutVideoTransitionSlideLeft": "Glid mod venstre", + "cutVideoTransitionSlideRight": "Glid mod højre", + "cutVideoTransitionSlideUp": "Glid op", + "cutVideoTransitionSlideDown": "Glid ned", + "cutVideoTransitionSmoothLeft": "Blød mod venstre", + "cutVideoTransitionSmoothRight": "Blød mod højre", + "cutVideoTransitionCircleOpen": "Cirkel åbn", + "cutVideoTransitionCircleClose": "Cirkel luk", + "cutVideoTransitionRadial": "Radial", + "cutVideoTransitionPixelize": "Pixeler", + "cutVideoTransitionBlur": "Slør", "importCurrentSubtitle": "Importer aktuel undertekst", "addRemoveEmbeddedSubtitlesTitle": "Tilføj/fjern indlejrede undertekster", "addRemoveEmbeddedSubtitlesMp4Title": "Tilføj/fjern indlejrede undertekster (MP4)", "addCurrentSubtitle": "Tilføj aktuel undertekst", - "embeddedTrackColumnNew": "Ny", "embeddedTrackNoSubtitlesFoundTitle": "Ingen undertekster fundet", "embeddedTrackNoSubtitlesFoundMessage": "Den valgte undertekstfil indeholder ingen undertekster.", "embeddedTrackNoTracksTitle": "Ingen spor tilføjet", @@ -2447,7 +2690,7 @@ "embeddedTrackGeneratingVideoXY": "Genererer video... {0}% {1}", "embeddedTrackGeneratingVideo": "Genererer video...", "mp4FilesFilter": "MP4-filer", - "titleOrLanguage": "Titel/sprog", + "embeddedTracksPickVideoHint": "Vælg en videofil, eller slip en her, for at se dens undertekstspor.", "viewMatroskaTrackX": "Se Matroska-spor - {0}", "resolutionSeparator": "x", "openFromUrlTitle": "Åbn videofil fra URL", @@ -2457,9 +2700,7 @@ "openFromUrlDownloadAndOpen": "Download og åbn", "openFromUrlDownloadAndOpenDescription": "Gem videoen lokalt, og åbn den derefter.", "openFromUrlDownloadAndOpenNote": "Påkrævet for tale-til-tekst og bølgeformsspidser.", - "openFromUrlMissing": "Indtast venligst en video-URL først.", "openFromUrlDownloadingTitle": "Downloader video", - "openFromUrlSaveAs": "Gem video som", "openFromUrlDownloadSubtitles": "Download også undertekster", "openFromUrlDownloadVideo": "Download video", "openFromUrlIncludeAutoGeneratedSubtitles": "Medtag automatisk genererede undertekster (originalsprog)", @@ -2496,56 +2737,38 @@ "reEncodeGeneratingDone": "Generering udført", "reEncodeGeneratedFilesX": "Genererede filer ({0}):", "reEncodeFfmpegParameters": "ffmpeg parametre", + "analyzingVideoX": "Analyserer video... {0}% {1}", + "analyzingVideoXofY": "Analyserer video {0}/{1}... {2}% {3}", + "backgroundImageFileNotSelected": "Baggrundsbilledfil ikke valgt", + "backgroundImageFileDoesNotExistX": "Baggrundsbilledfilen findes ikke: {0}", "burnIn": { "title": "Generer video med indbrændte undertekster", - "infoAssaOff": "Bemærk: Advanced SubStation Alpha-styling understøttes.", - "infoAssaOn": "Bemærk: Advanced SubStation Alpha-styling vil blive brugt :)", - "xGeneratedWithBurnedInSubsInX": "\"{0}\" genereret med indbrændt undertekst i {1}.", - "timeRemainingMinutes": "Resterende tid: {0} minutter", - "timeRemainingOneMinute": "Resterende tid: Et minut", - "timeRemainingSeconds": "Resterende tid: {0} sekunder", - "timeRemainingAFewSeconds": "Resterende tid: Et par sekunder", - "timeRemainingMinutesAndSeconds": "Resterende tid: {0} minutter og {1} sekunder", - "timeRemainingOneMinuteAndSeconds": "Resterende tid: Et minut og {0} sekunder", - "targetFileName": "Målfilnavn: {0}", "targetFileSize": "Målfilstørrelse (kræver 2 pass-kodning)", "fileSizeMb": "Filstørrelse i MB", - "passX": "Gennemløb {0}", - "encoding": "Kodning", "bitRate": "Bithastighed", "totalBitRateX": "Samlet bithastighed: {0}", "sampleRate": "Samplingsfrekvens", "audio": "Lyd", - "stereo": "Stereo", "preset": "Forudindstilling", + "tune": "Tune", "pixelFormat": "Pixel-format", "crf": "CRF", - "tuneFor": "Tilpas til", - "alignRight": "Juster til højre", - "getStartPosition": "Hent startposition", - "getEndPosition": "Hent slutposition", "useSource": "Brug kilde", - "useSourceFolder": "Brug kildemappe", - "useSourceResolution": "Brug kildeopløsning", - "outputSettings": "Outputfil/-mappe...", "fontSizeFactor": "Skriftstørrelsesfaktor", "boxType": "Bokstype", - "fixRightToLeft": "Ret fra højre mod venstre", - "cut": "Klip", "fromTime": "Fra tiden", "toTime": "Til tid", "audioEncoding": "Lydkodning", "outputProperties": "Outputegenskaber...", "videoFileSize": "Videofilstørrelse", "oneBox": "Én kasse", - "boxPerLine": "Boks pr. linje", "logoInfo": "Vælg et PNG-billede, og træk det for at placere det på videoen.", + "noSubtitlesGenerateAnyway": "Der er ingen undertekstlinjer at brænde ind.\n\nGenerer videoen uden undertekster?", + "noSubtitlesInXItemsGenerateAnyway": "{0} batch-element(er) har ingen undertekstlinjer at brænde ind.\n\nGenerer disse videoer uden undertekster?", "matchSourceVideoSize": "Match kildevideoens størrelse" }, "videoTransparent": { - "title": "Generer gennemsigtig video med undertekster", - "infoAssaOff": "Bemærk: Advanced SubStation Alpha-styling understøttes.", - "infoAssaOn": "Bemærk: Advanced SubStation Alpha-styling vil blive brugt :)" + "title": "Generer gennemsigtig video med undertekster" }, "audioToText": { "title": "Tale til tekst", @@ -2553,6 +2776,21 @@ "translateToEnglish": "Oversæt til engelsk", "transcribing": "Transskriberer...", "transcribingXOfY": "Transskriberer {0} af {1}...", + "linesTranscribedXOfYFailedZ": "{0} af {1} linjer blev transskriberet - {2} mislykkedes eller havde ingen tale.\n\nMislykkede linjer beholder deres aktuelle tekst. Anvend de transskriberede linjer, eller prøv de mislykkede igen (du kan først ændre motor eller indstillinger)?", + "linesTranscribedXOfYCancelled": "Transskriptionen blev annulleret efter {0} af {1} linjer.\n\nAnvend de transskriberede linjer? De øvrige linjer beholder deres aktuelle tekst.", + "noLinesTranscribed": "Ingen af de {0} linjer kunne transskriberes.\nTjek værktøjsloggen for detaljer.", + "applyTranscribedLines": "Anvend transskriberede", + "retryFailedLines": "Prøv mislykkede igen", + "isolateSpeech": "Isoler tale (langsom)", + "isolateSpeechHint": "Fjerner musik og lydeffekter før transskription. Giver færre oversete linjer og bedre timing på lyd med høj musik, men tager omtrent lige så lang tid som selve lyden med en GPU - og mange gange længere uden.", + "isolatingSpeech": "Isolerer tale...", + "isolateSpeechFailed": "Kunne ikke isolere talen - transskriberer den originale lyd i stedet.", + "detectSpeakers": "Registrer talere", + "detectSpeakersHint": "Starter hver linje med taleren, f.eks. \"(speaker 0)\", ved hjælp af NVIDIAs Sortformer-diarisering (op til 8 talere). Fungerer bedst med backends, der deler linjer, hvor taleren skifter, som Parakeet og Cohere.", + "detectSpeakersNeedsNewerCrispAsr": "\"Registrer talere\" kræver Crisp ASR v{0} eller nyere, men v{1} er installeret.\n\nOpdater Crisp ASR (tandhjulsknappen ved siden af motoren), eller slå \"Registrer talere\" fra.", + "xNeedsNewerCrispAsr": "{0} kræver Crisp ASR v{1} eller nyere, men v{2} er installeret.\n\nDownload og installer opdateringen nu?", + "vad": "VAD", + "vadHint": "Stemmeaktivitetsregistrering: kun talen transskriberes, klippet hvor taleren holder pause.\nAuto: Silero, og kun for de motorer, der har brug for det (Cohere, Mega, Index-Echo).\nSilero, FireRedVAD, Whisper-VAD, WebRTC: altid slået til, for alle Crisp ASR-motorer. FireRedVAD er den mest præcise, Whisper-VAD giver de færreste og reneste klip, men er den langsomste.\nIndex-Echo bruger altid Silero. --vad, --vad-model eller --chunk-seconds i de avancerede indstillinger har forrang.", "inputLanguage": "Indtastningssprog", "advancedWhisperSettings": "Avancerede Whisper-indstillinger", "downloadingSpeechToTextEngine": "Downloader tale-til-tekst-motor", @@ -2565,8 +2803,6 @@ "whisperXxlHighlightWord": "Fremhæv ord", "selectModel": "Vælg model", "viewToolsLogFile": "Se Whisper-logfil", - "reDownloadX": "Gendownload {0}", - "downloadX": "Download {0}", "updateXTitle": "Vil du opdatere {0}?", "updateXMessage": "En nyere version af {0} er tilgængelig.{1}{1}Download og installer opdateringen nu?", "downloadingSpeechToTextModel": "Downloader tale-til-tekst-model", @@ -2610,27 +2846,16 @@ "backendAndUpdateStatus": "Backend og opdateringsstatus", "addCustomModelDotDotDot": "Tilføj tilpasset model...", "customModelHelp": "Du kan bruge din egen model: vælg modelfilen (whisper.cpp ggml '.bin') eller modelmappen (faster-whisper-mappe med en 'model.bin' indeni). Den kopieres til modelmappen og føjes til listen ovenfor.", - "cueBuilding": "Opbygning af cues (MLX Whisper)", - "cueRebuild": "Genopbyg cues fra ordtidskoder", - "cueMaxChars": "Maks. tegn pr. cue", - "cueMaxSeconds": "Maks. sekunder pr. cue", - "cueMaxCps": "Maks. tegn pr. sekund", - "vocabularyPrompt": "Ordforrådsprompt (navne, steder, termer)", - "beamSize": "Beam-størrelse", - "beamSizeNote": "0 afkoder hurtigst. 5 giver den bedste nøjagtighed (som Faster Whisper) med cirka 20-30 % mere tid. Værdier over 5 hjælper sjældent.", "addLanguageCodeToFileName": "Tilføj sprogkode til filnavnet", "addLanguageCodeToFileNameHint": "Navngiv genererede undertekster som \"video.da.srt\" i stedet for \"video.srt\"" }, "textToSpeech": { "title": "Tekst til tale", - "textToSpeechEngine": "Tekst til tale-motor", "reviewAudioSegments": "TTS - Gennemgå lydsegmenter", "reviewAudioSegmentsHistory": "TTS - Gennemgå lydhistorik", "stability": "Stabilitet", "similarity": "Lighed", "speakerBoost": "Talerboost", - "styleExaggeration": "Stiloverdrivelse", - "regenerateAudioSelectedLine": "Regenerer lyd for den valgte linje", "generateSpeechFromText": "Generer tale fra tekst", "testVoice": "Test stemme", "addAudioToVideoFile": "Tilføj lyd til videofil", @@ -2640,6 +2865,19 @@ "videoEncodingSettings": "TTS - Indstillinger for videokodning", "elevenLabsSettings": "TTS - ElevenLabs indstillinger", "elevenLabsSettingsResetHint": "Nulstil ElevenLabs-indstillingerne til standardværdier", + "provider": "Udbyder", + "openAiCompatibleSettings": "TTS - OpenAI-kompatible indstillinger", + "customServerUrl": "Brugerdefineret server-URL", + "customServerUrlHint": "Fuldt tale-endpoint for en OpenAI-kompatibel server, bruges når udbyderen er \"Custom\", f.eks. http://localhost:8880/v1/audio/speech", + "customModels": "Brugerdefinerede modeller", + "customModelsHint": "Kommaseparerede modelnavne, der tilbydes, når udbyderen er \"Custom\"", + "customVoices": "Brugerdefinerede stemmer", + "customVoicesHint": "Kommaseparerede stemmenavne, bruges af udbyderen \"Custom\" og af OpenRouter-modeller, der ikke offentliggør en stemmeliste", + "instructions": "Instruktioner", + "instructionsHint": "Valgfri talestil, f.eks. \"Tal med en rolig, varm stemme\". Sendes kun til modeller, der understøtter det (ikke tts-1/tts-1-hd)", + "openAiSpeedHint": "Talehastighed fra 0,25 til 4,0 (1,0 er normal). Ikke alle modeller understøtter det", + "outputFormat": "Outputformat", + "outputFormatHint": "Lydformat, der anmodes om fra serveren. \"Auto\" beder om MP3 og skifter til PCM for modeller, der kun returnerer PCM (f.eks. Gemini TTS)", "regenerateAudio": "Regenerer lyd", "autoContinuePlaying": "Fortsæt automatisk med at afspille", "playLine": "Afspil linje", @@ -2648,6 +2886,13 @@ "addingAudioToVideoFileDotDotDot": "Tilføjer lyd til videofil...", "preparingMergeDotDotDot": "Forbereder fletning...", "importVoiceDotDotDot": "Importer stemme...", + "renameVoiceDotDotDot": "Omdøb stemme...", + "renameVoiceTitle": "Omdøb stemme", + "voiceXCouldNotBeRenamedX": "Stemmen '{0}' kunne ikke omdøbes: {1}", + "deleteVoiceDotDotDot": "Slet stemme...", + "deleteVoiceTitle": "Slet stemme", + "deleteVoiceXQuestion": "Slet stemmen '{0}'?\n\nReferenceoptagelsen og dens filer fjernes fra disken.", + "voiceXCouldNotBeDeletedX": "Stemmen '{0}' kunne ikke slettes: {1}", "voiceImportSuccessTitle": "Stemme importeret", "voiceXImported": "Stemmen '{0}' blev importeret", "voiceXCouldNotBeImported": "Stemmen '{0}' kunne ikke importeres - se loggen for detaljer", @@ -2657,6 +2902,49 @@ "dropAudioFileHereToImportVoice": "Slip lydfil her for at importere stemme", "dropAudioFileHereHint": ".wav eller .mp3", "voiceCloneTranscriptTitle": "Indtast transskription af lyden (påkrævet for stemmekloning)", + "voiceManagerTitle": "TTS - Stemmeadministration", + "voiceManagerDotDotDot": "Stemmeadministration...", + "voiceManagerMenuItem": "TTS-stemmeadministration...", + "speakFromCurrentLineTitle": "Læs op fra aktuel linje", + "speakFromCurrentLineDotDotDot": "Læs op fra aktuel linje...", + "speakFromCurrentLineHint": "Læser linjerne højt én efter én, startende ved den aktuelle linje. Tryk på Escape eller klik på en anden linje for at stoppe.", + "stopSpeaking": "Stop oplæsning", + "speakingLineX": "Læser linje {0} op... (Escape for at stoppe)", + "playWithSpeechTitle": "Afspil med tale", + "playWithSpeechDotDotDot": "Afspil med tale...", + "playWithSpeechHint": "Afspiller videoen og læser hver linje op, når videoen når den. Sæt på pause og spol som normalt; tryk på Escape for at stoppe.", + "lowerVideoVolumeWhileSpeaking": "Sænk videoens lydstyrke under oplæsning", + "pauseVideoWhenSpeechRunsLate": "Sæt videoen på pause, når talen løber ind i næste linje", + "waitingForSpeechLineX": "Genererer tale for linje {0}... (Escape for at stoppe)", + "voiceKindClone": "Klonet stemme", + "voiceKindPreset": "Indbygget", + "voiceKindModel": "Stemmemodel", + "voiceKindOnline": "Online", + "transcript": "Transskription", + "transcriptRequiredHint": "Påkrævet - motoren læser denne transskription af optagelsen (ref-text) ved kloning", + "transcriptOptionalHint": "Valgfri - en præcis transskription af optagelsen forbedrer kloningen", + "saveTranscript": "Gem transskription", + "searchVoices": "Søg i stemmer", + "loadingVoicesDotDotDot": "Indlæser stemmer...", + "noVoicesForEngine": "Ingen stemmer - importér en optagelse eller download en stemmepakke", + "noVoicesMatchFilter": "Ingen stemmer matcher søgningen", + "copyVoiceTo": "Kopiér stemme til", + "copyVoiceToDotDotDot": "Kopiér stemme til motor...", + "copyingVoiceXToYDotDotDot": "Kopierer stemmen '{0}' til {1}...", + "voiceXCopiedToY": "Stemmen '{0}' kopieret til {1}", + "voiceXCouldNotBeCopiedToY": "Stemmen '{0}' kunne ikke kopieres til {1} - se loggen for detaljer", + "voiceXAlreadyExistsInYContinue": "Der findes allerede en stemme med navnet '{0}' i {1}.\n\nKopiér alligevel?", + "openVoicesFolder": "Åbn mappen med stemmer", + "downloadVoicePacksDotDotDot": "Download stemmepakker...", + "downloadVoicePacksTitle": "TTS - Download stemmepakker", + "voicePackIntro": "Stemmepakker er sæt af korte, rene optagelser med transskriptioner, klar til at klone fra. Hver pakke importeres i den valgte motor (stemmer, der allerede findes, springes over) og indeholder en ATTRIBUTION.txt med licens og kilder.", + "voicePackDetailsXVoicesYSizeZLicense": "{0} stemmer · {1} · {2}", + "installToEngine": "Installer i motor", + "downloadAndInstall": "Download og installer", + "selectAtLeastOneVoicePack": "Vælg mindst én stemmepakke", + "downloadingXDotDotDot": "Downloader {0}...", + "installingXDotDotDot": "Installerer {0}...", + "xVoicesInstalledYSkipped": "{0} stemmer installeret ({1} fandtes allerede)", "useSpeechToTextDotDotDot": "Brug tale-til-tekst...", "autoCastMenuItem": "Find stemmer i videoen og klon...", "autoCastSpeakersTitle": "Stemmer fundet i videoen", @@ -2675,7 +2963,7 @@ "cloneVoicePerLinePreparing": "Henter hver linjes stemme fra videoen...", "cloneVoicePerLineNeedsVideo": "For at klone hver linjes stemme kræves den video, underteksten hører til. Åbn videoen, og prøv igen.", "cloneVoicePerLineNoClips": "Der kunne ikke hentes lyd fra videoen, så der er intet at klone fra. Kontrollér, at videoen har et lydspor.", - "cloneVoicePerLineNeedsOriginalSubtitleX": "{0} kan kun klone en linje, når den ved, hvad der siges i videoen ved den linje, hvilket kommer fra underteksten på originalsproget. Der er ingen original undertekst indlæst, så hver linje ville blive talt af den første importerede stemme i stedet for stemmen i videoen.\n\nÅbn den originale undertekst (Fil > Åbn original...) ved siden af oversættelsen, og generér igen.", + "cloneVoicePerLineTranscribeClipsXYZ": "{0} kan kun klone en linje, når den ved, hvad der siges i videoen ved den linje. Det vides ikke for {1} af {2} linjer - det kommer normalt fra underteksten på originalsproget (Fil > Åbn original...).\n\nTransskriber disse linjer fra videoen med tale til tekst nu?\n\nJa: vælg motoren og det sprog, der tales i videoen, og referenceklippene transskriberes, før talen genereres.\nNej: generer alligevel - disse linjer læses op af en almindelig stemme i stedet for stemmen i videoen.", "voiceCloneConsentTitle": "Stemmekloning - før du går i gang", "voiceCloneConsentHeader": "Du er ved at klone en stemme", "voiceCloneConsentIntro": "Kloning kopierer en rigtig persons stemme. Det er de fleste steder omfattet af regler, og i EU af juridiske pligter, der påhviler dig og ikke Subtitle Edit. Læs venligst dette én gang.", @@ -2692,10 +2980,29 @@ "advancedTtsSettings": "Avancerede TTS-indstillinger", "advancedTtsAudioProcessing": "Lydbehandling", "advancedTtsOutput": "Output", + "advancedTtsVoiceCloning": "Stemmekloning", + "cleanCloneReferences": "Rens stemmeklonreferencer (Sidon)", + "cleanCloneReferencesDescription": "Fjerner musik, støj og rumklang fra den lyd, en stemme klones fra: klip taget fra videoen og optagelser importeret i stemmeadministrationen. Bruger Sidon-talegendannelsesmodellen på audio.cpp (940 MB download). Den klonede stemme kopierer ikke længere baggrunden, men meget svag eller overlappende tale kan blive dæmpet.", + "cleaningCloneReferencesDotDotDot": "Renser stemmeklonreferencer...", + "downloadSidonTitle": "Download Sidon?", + "downloadSidonQuestionX": "Rensning af stemmeklonreferencer kræver Sidon-talegendannelsesmodellen ({0}).\n\nDownload den nu? Uden den bruges referencerne, som de er.", "proAudioPostProcessing": "Pro lydefterbehandling", "proAudioPostProcessingDescription": "Anvender EQ-varme, noise gate, kompression, loudness-normalisering (-16 LUFS) og fade ind/ud til hvert segment.", + "generatingSpeechSegmentXOfY": "Genererer tale: segment {0} af {1}", + "adjustingSpeedSegmentXOfY": "Justerer hastighed: segment {0} af {1}", + "mergingAudioSegmentXOfY": "Fletter lyd: segment {0} af {1}", + "postProcessingSegmentXOfY": "Efterbehandling: segment {0} af {1}", "audioDucking": "Lyd-ducking", "audioDuckingDescription": "Reducerer den originale videolydstyrke og blander den med TTS-lyden, så det originale lydspor stadig er svagt hørbart.", + "removeOriginalSpeech": "Fjern original tale (langsom)", + "removeOriginalSpeechDescription": "Beholder musikken og lydeffekterne fra den originale video, men fjerner dens tale og mikser den nye tale ovenpå. Tager omtrent lige så lang tid som selve videoen med en GPU og mange gange længere uden. Med lyd-ducking slået til gælder dens lydstyrke for musikken og effekterne. Gælder kun, når talen tilføjes til videofilen.", + "removingOriginalSpeech": "Fjerner original tale...", + "removeOriginalSpeechFailed": "Kunne ikke fjerne den originale tale - talen blev tilføjet til videoen uden det.", + "speechRemovalModel": "Model", + "speechRemovalModelDescription": "Bedste kvalitet bevarer mest af musikken og lydeffekterne. Hurtig er omkring tre gange hurtigere, men mister lidt mere af musikken.", + "speechRemovalBestQuality": "Bedste kvalitet (Mel-Band RoFormer)", + "speechRemovalFast": "Hurtig (HTDemucs)", + "videoTruncatedWarning": "Billedet i den nye videofil stopper efter {0}, men den originale video er {1} lang - billedet fryser, mens lyden fortsætter.\n\nFilen blev beholdt. Se error-log.txt i Subtitle Edits datamappe for ffmpeg-outputtet.", "originalVolumePercent": "Original volumen %", "vadSilenceCompression": "VAD-stilhedskomprimering", "vadSilenceCompressionDescription": "Forkorter pauser mellem ord, før du skifter tempo.\r\nBruger Voice Activity Detection til kun at komprimere tavshedshuller, mens talen holdes urørt.\r\nDette er det foretrukne første trin - det reducerer varigheden uden kvalitetstab.", @@ -2778,7 +3085,6 @@ "subtitleMergedLinesAppliedPlural": "Anvendte {0} linjesammenlægninger fra tekst til tale" }, "shotChanges": { - "titleGenerateOrImport": "Generer/importér sceneskift", "generateShotChanges": "Generer sceneskift", "importShotChanges": "Importer sceneskift", "generateShotChangesWithFfmpeg": "Generer sceneskift med ffmpeg", @@ -2788,7 +3094,9 @@ "timeCodeFormatColon": "Tidskodeformat:", "shotChangesList": "Liste over sceneskift", "shotChangesClearQuestion": "Er du sikker på, at du vil rydde alle sceneskift?", - "deleteSelectedShotChangeQuestion": "Er du sikker på, at du vil slette det valgte sceneskift?" + "deleteSelectedShotChangeQuestion": "Er du sikker på, at du vil slette det valgte sceneskift?", + "shotChanges": "Sceneskift", + "exportShotChanges": "Eksportér sceneskift" }, "videoOcr": { "title": "OCR af indbrændt undertekst", @@ -2798,10 +3106,6 @@ "bottomHalf": "Nederste halvdel", "fullFrame": "Hele billedet", "engine": "OCR-motor", - "language": "Sprog", - "url": "URL", - "model": "Model", - "apiKey": "API-nøgle", "scan": "Scan", "framesPerSecond": "Billeder pr. sekund", "textBrightnessMinimum": "Minimum tekstlysstyrke (0=fra)", @@ -2816,10 +3120,10 @@ "runningOcrXY": "Kører OCR... {0}/{1}", "noLinesFoundTitle": "Ingen undertekster fundet", "noLinesFoundMessage": "Der blev ikke fundet nogen tekst i scanområdet - prøv at justere scanområdet, motoren eller minimumslysstyrken.", + "ocrStoppedEarlyMessage": "OCR-motoren stoppede, før alle billeder var læst, så de linjer, der er fundet indtil nu, beholdes, og resten mangler.{0}{0}{1}", "linesFoundX": "{0} linjer fundet", "refiningTimingXY": "Finjusterer timing... {0}/{1}", "fixOcrErrors": "Ret OCR-fejl", - "dictionary": "Ordbog", "editLineX": "Rediger linje {0}", "downloadDictionaryHint": "Download en stavekontrolordbog...", "framesPerSecondHint": "Hvor mange billeder pr. sekund der scannes. 5 dækker normale undertekster; højere fanger meget korte linjer og strammer grupperingen, men koster mere OCR-tid.", @@ -2836,14 +3140,64 @@ "testOcr": "Test aktuelt billede", "testOcrRunning": "Tester OCR på aktuelt billede...", "testOcrResultX": "Testresultat: {0}", - "testOcrNoTextFound": "Test: ingen tekst fundet i scanområdet" + "testOcrNoTextFound": "Test: ingen tekst fundet i scanområdet", + "engineSettings": "Indstillinger for OCR-motor", + "engineBuiltIn": "Indbygget i operativsystemet", + "engineExternalServer": "Ekstern server (installeret og administreret uden for Subtitle Edit)", + "engineCloudApi": "Cloud-API (kræver en API-nøgle)", + "engineNothingToInstall": "Intet at installere", + "engineModelsMissing": "Motor installeret, modeller mangler", + "website": "Websted" + }, + "backgroundMusic": { + "title": "Generer baggrundsmusik", + "generateBackgroundMusicDotDotDot": "Generer baggrundsmusik...", + "preset": "Forudindstilling", + "prompt": "Prompt", + "tempoBpm": "Tempo (BPM)", + "generateSeconds": "Generer (sekunder)", + "seed": "Seed", + "randomSeed": "Tilfældig", + "outputLength": "Outputlængde", + "outputLengthVideoX": "{0} (videoens længde - musikken looper sømløst)", + "musicVolumePercent": "Musiklydstyrke (%)", + "removeExistingAudioTracks": "Fjern eksisterende lydspor", + "originalAudioVolumePercent": "Original lydstyrke (%)", + "saveAudioDotDotDot": "Gem lyd...", + "addToVideoDotDotDot": "Tilføj til video...", + "engineInfo": "ACE-Step 1.5 Turbo via audio.cpp (MIT-licens) - kører lokalt", + "presetCooking": "Madlavning / bagning", + "presetTech": "Teknologi / gadget-anmeldelse", + "presetForest": "Skov / natur", + "presetRelaxingPiano": "Afslappende klaver", + "presetLofiVlog": "Lo-fi vlog", + "presetCorporate": "Erhverv / forklaringsvideo", + "loadingModel": "Indlæser model...", + "composing": "Komponerer...", + "generatingAudio": "Genererer lyd...", + "decodingAudio": "Afkoder lyd...", + "creatingLoop": "Opretter sømløst loop...", + "resultLoopXYZ": "Loop: {0} ved {1} BPM, overgangsmatch {2}%", + "resultNoLoopX": "Musik klar: {0}", + "addingMusicToVideo": "Tilføjer musik til video...", + "downloadModelTitle": "Download musikmodel?", + "downloadModelQuestionX": "Generering af musik kræver ACE-Step 1.5-modellen ({0}).\n\nDownload model?", + "downloadingModel": "Downloader ACE-Step 1.5-model...", + "saveAudioTitle": "Gem baggrundsmusik", + "audioFileSavedX": "Lydfil gemt: {0}", + "unableToGenerateMusic": "Kan ikke generere musik", + "unableToAddMusicToVideo": "Kan ikke tilføje musik til video", + "outputLengthTextToSpeech": "Følger den genererede tale - musikken looper sømløst og dæmpes, mens nogen taler", + "addBackgroundMusic": "Tilføj baggrundsmusik", + "addBackgroundMusicHint": "Generer musik lokalt med ACE-Step 1.5, og mix den under talen - den dæmpes, mens nogen taler", + "generatingBackgroundMusicDotDotDot": "Genererer baggrundsmusik...", + "mixingBackgroundMusicDotDotDot": "Mixer baggrundsmusik..." }, "voices": "Stemmer", "presets": "Forudindstillinger", "talkerX": "Taler {0}", "baseModel": "Basismodel", "turboModel": "Turbomodel", - "ttsUpdateX": "Opdater {0}", "ttsEngineNotInstalled": "{0} er ikke installeret", "ttsEngineUpdateAvailable": "{0} - opdatering tilgængelig", "ttsEngineTooOldUpdateRequired": "{0} er for gammel - opdatering påkrævet", @@ -2866,19 +3220,25 @@ "cloneVoiceTo": "Klon stemme til", "cloneVoiceNameTitle": "Navn på ny stemme", "cloneVoiceExtractFailed": "Kunne ikke udtrække lyd for den valgte linje.", + "lineXHasNoDuration": "Linje {0} har ingen varighed (sluttiden ligger ikke efter starttiden), så der er ingen lyd at udtrække.", + "audioClipsSkippedLinesX": "Der kunne ikke udtrækkes lyd for disse linjer, så de blev sprunget over: {0}", "toggleShotChange": "Vis/skjul sceneskift", "resetWaveformZoomAndSpeed": "Nulstil bølgeformszoom og hastighed", "showOriginalSubtitle": "Vis original undertekst", + "timelineGroupTracksBy": "Gruppér undertekstspor efter", + "timelineShowThumbnails": "Vis videominiaturer", + "showSpeechOnly": "Vis kun tale (langsom at generere)", + "isolatingSpeechForWaveformX": "Isolerer tale til bølgeformen... {0}", + "isolatingSpeechForWaveformFailed": "Kunne ikke isolere talen til bølgeformen - se værktøjsloggen", + "speechOnlyWaveformReady": "Bølgeformen viser nu kun tale", "showOnlyWaveform": "Vis kun bølgeform", "showOnlySpectrogram": "Vis kun spektrogram", "showWaveformAndSpectrogram": "Vis bølgeform og spektrogram", - "spectrogramClassic": "Klassisk", "spectrogramClassicViridis": "Viridis", "spectrogramClassicPlasma": "Plasma", "spectrogramClassicInferno": "Inferno", "spectrogramClassicTurbo": "Turbo", "spectrogramNeon": "Neon", - "waveformDrawStyleClassic": "Klassisk", "waveformDrawStyleFancy": "Flot", "setVideoPositionAndPauseAndSelectSubtitle": "Indstil videoposition, pause, og vælg undertekst", "setVideopositionAndPauseAndSelectSubtitleAndCenter": "Indstil videoposition, pause, vælg undertekster og centrer", @@ -2888,7 +3248,6 @@ }, "sync": { "visualSync": "Visuel synkronisering", - "sync": "Synkroniser", "startScene": "Startscene", "endScene": "Slutscene", "playTwoSecondsAndBack": "Spil 2 sekunder og tilbage", @@ -2904,19 +3263,18 @@ "adjustSelectedLinesAndForward": "Juster valgte linjer og frem", "toFrameRate": "Til billedhastighed", "fromFrameRate": "Fra billedhastighed", + "videoXFrameRateY": "{0} - {1} fps", "adjustAllTimes": "Juster alle tidspunkter (vis tidligere/senere)", "showEarlier": "Vis tidligere", "showLater": "Vis senere", - "changeFrameRate": "Skift billedhastighed", "setSyncPoint": "Indstil synkroniseringspunkt", "setSyncPointViaVideo": "Sæt synkroniseringspunkt via video...", "syncPointTimeCode": "Tidskode for synkroniseringspunkt", "syncPoints": "Synkroniseringspunkter", "pointSync": "Punktsynkronisering", "pointSyncViaOther": "Punktsynkronisering via anden undertekst", - "adjustmentX": "Justering: {0}", - "totalAdjustmentX": "Samlet justering: {0}", - "syncPointCandidateInfo": "Linjer, der starter efter {0}+ sekunders stilhed, fremhæves - de giver ofte pålidelige synkroniseringspunkter", + "syncPointCandidateAfterInfo": "Linjer efterfulgt af {0}+ sekunders stilhed er fremhævet - de giver ofte pålidelige synkroniseringspunkter", + "gapAfter": "Mellemrum efter", "adjustAllShortcuts": "Tastaturgenveje:\r\n\r\n• Skift + Venstre/Højre: Flyt 10 ms\r\n• Ctrl + Venstre/Højre: Flyt 100 ms\r\n• Alt + Venstre/Højre: Flyt 500 ms", "offsetInSeconds": "Offset i sekunder", "speedFactor": "Hastighedsfaktor", @@ -2936,6 +3294,7 @@ "lineMerge": "Linjefletning", "delayInSecondsBetweenRequests": "Forsinkelse i sekunder mellem anmodninger", "maxBytesPerRequest": "Maks. bytes pr. anmodning", + "keepMusicLinesUntranslated": "Oversæt ikke linjer i musiksymboler (♪ ... ♪)", "promptText": "Prompttekst", "resetPromptToDefault": "Nulstil prompt til standard", "translateEachLineSeparately": "Oversæt hver linje separat", @@ -2949,11 +3308,10 @@ "readyToTranslate": "Klar til at oversætte", "translating": "Oversætter...", "translationComplete": "Oversættelse færdig", + "translateInPlaceNoOriginal": "Oversæt på stedet (opret ikke original)", "translationCancelled": "Oversættelse annulleret", "swapLanguages": "Skift kilde- og målsprog", "xIsAlreadyDownloadedReDownload": "{0} er allerede downloadet.\r\nGendownload?", - "advancedDotDotDot": "Avanceret...", - "advancedSettings": "Avancerede indstillinger", "advancedSettingsSubtitle": "Batch-, kontekst- og sampling-indstillinger for den avancerede llama.cpp-motor", "context": "Kontekst", "synopsis": "Synopsis", @@ -2977,6 +3335,7 @@ "useOnlyExtraServerParametersHint": "Start llama-server med parametrene ovenfor i stedet for Subtitle Edits egen tuning - kun model, host og port sættes stadig", "serverRunningAtX": "Server kører på {0}", "customPromptHint": "Brugerdefinerede instruktioner ({0} = kildesprog, {1} = målsprog); tom = indbygget prompt", + "editTranslationHint": "Klik på oversættelsen i den valgte række for at redigere den (Enter gemmer, Escape annullerer)", "llamaCppDownloadEngineAndModelPrompt": "llama.cpp kræver, at llama-server-motoren og en oversættelsesmodel downloades. Vil du downloade nu?", "llamaCppDownloadEnginePrompt": "llama.cpp kræver, at llama-server-motoren downloades. Vil du downloade nu?", "llamaCppDownloadModelPrompt": "llama.cpp kræver, at den valgte oversættelsesmodel downloades. Vil du downloade nu?" @@ -2989,7 +3348,6 @@ "dialogStyleDashBothLinesWithSpace": "Bindestreg på begge linjer med mellemrum", "dialogStyleDashBothLinesWithoutSpace": "Bindestreg på begge linjer uden mellemrum", "continuationStyle": "Fortsættelsesstil", - "continuationStyleNone": "Ingen", "continuationStyleNoneTrailingDots": "Ingen, punktummer for pauser (kun afsluttende)", "continuationStyleNoneLeadingTrailingDots": "Ingen, punktummer for pauser", "continuationStyleNoneTrailingEllipsis": "Ingen, ellipse for pauser (kun afsluttende)", @@ -3000,7 +3358,6 @@ "continuationStyleLeadingTrailingEllipsis": "Ellipse", "continuationStyleLeadingTrailingDash": "Bindestreg", "continuationStyleLeadingTrailingDashDots": "Bindestreg, men punktummer for pauser", - "continuationStyleCustom": "Tilpasset", "cpsLineLengthStyle": "Cps/linjelængde", "cpsLineLengthStyleCalcAll": "Tæl alle tegn", "cpsLineLengthStyleCalcNoSpaceCpsOnly": "Tæl alle undtagen mellemrum, kun cps", @@ -3013,14 +3370,6 @@ "cpsLineLengthStyleCalcNoSpaceOrPunctuationCpsOnly": "Intet mellemrum eller tegnsætning, kun CPS", "cpsLineLengthStyleCalcIgnoreArabicDiacritics": "Ignorer arabiske diakritiske tegn", "cpsLineLengthStyleCalcIgnoreArabicDiacriticsNoSpace": "Ignorer arabiske diakritiske tegn, ingen mellemrum", - "timeCodeModeHhMmSsMs": "HH:MM:SS:MS", - "timeCodeModeHhMmSsFf": "HH:MM:SS:FF", - "splitBehaviorPrevious": "Tilføj mellemrum til venstre for delepunktet (fokus til højre)", - "splitBehaviorHalf": "Tilføj mellemrum i midten af delepunktet (fokus til venstre)", - "splitBehaviorNext": "Tilføj mellemrum til højre for delepunktet (fokus til venstre)", - "subtitleListActionNothing": "Intet", - "subtitleListActionVideoGoToPositionAndPause": "Gå til videoposition og sæt på pause", - "subtitleListActionVideoGoToPositionAndPlay": "Gå til videoposition og afspil", "subtitleGridCenterText": "Centrér tekst i undertekstgitteret", "minGapCalculateDotDotDot": "Beregn minimalt mellemrum ud fra en billedhastighed...", "minGapCalculateTitle": "Min. mellemrum mellem undertekster i ms", @@ -3052,8 +3401,14 @@ "checkForUpdatesChannelStableAndBeta": "Stabile og beta-versioner", "assaAutoSetResolution": "ASSA: sæt opløsning (PlayResX/PlayResY) ud fra videoen, når en video åbnes", "assaAutoSetResolutionPrompt": "ASSA: spørg, før opløsningen ændres, så den passer til videoen (ellers gensamples automatisk)", + "videoControls": "Videokontroller", + "videoControlsEdit": "Rediger videokontroller...", + "videoControlsPositionText": "Tekst for position / varighed", + "videoControlsVideoFileName": "Videofilnavn", + "videoControlsPlayerName": "Navn på videoafspiller", "mergeKeepEndTime": "Flet linjer: behold sluttid (tillad overlap med næste undertekst)", "mergeKeepEndTimeOnlyAssa": "Flet linjer: behold kun sluttid for \"Advanced Sub Station Alpha\"", + "mergeAutoBreak": "Flet linjer: automatisk linjeskift i flettet tekst (fra beholder linjernes egne linjeskift)", "defaultSaveLocation": "Standardplacering ved gemning", "defaultSaveLocationSourceFileFolder": "Mappe med undertekst-/videofil", "defaultSaveLocationLastUsedFolder": "Senest brugte mappe", @@ -3061,18 +3416,10 @@ "defaultSaveLocationSubtitleFileFolder": "Undertekstfilens mappe", "defaultSaveLocationCustomFolder": "Brugerdefineret mappe", "subtitleListActionVideoGoToPositionAndPlayCurrentAndPause": "Gå til videoposition, afspil aktuel og sæt på pause", - "subtitleListActionEditText": "Gå til tekstredigeringsfeltet", "subtitleListActionVideoGoToPositionMinus1SecAndPause": "Gå til videoposition - 1 s og sæt på pause", "subtitleListActionVideoGoToPositionMinusHalfSecAndPause": "Gå til videoposition - 0,5 s og sæt på pause", "subtitleListActionVideoGoToPositionMinus1SecAndPlay": "Gå til videoposition - 1 s og afspil", - "subtitleListActionEditTextAndPause": "Gå til tekstredigeringsfeltet, og sæt på pause ved videoposition", - "autoBackupEveryMinute": "Hvert minut", - "autoBackupEveryXthMinute": "Hvert {0}. minut", - "profiles": "Profiler", - "autoBackupDeleteAfterXMonths": "Slet auto-backups efter {0} måneder", "searchSettingsDotDotDot": "Søg efter indstillinger...", - "syntaxColoring": "Syntaksfarvning", - "waveformSpectrogram": "Bølgeform/spektrogram", "network": "Netværk", "fileTypeAssociations": "Filtypetilknytninger", "textBoxColorTags": "Farvemærker (HTML/ASSA) i underteksttekstboks", @@ -3100,10 +3447,9 @@ "darkThemeForegroundColor": "Forgrundsfarve (mørkt tema)", "darkThemeBackgroundColor": "Baggrundsfarve (mørkt tema)", "showGridLines": "Vis gitterlinjer", - "resetSettings": "Vil du nulstille indstillinger?", - "resetSettingsDetail": "Dette vil nulstille alle indstillinger til deres standardværdier.\r\n\r\n\r\nFortsætte?", "showHorizontalLineAboveToolbar": "Vis vandret linje over værktøjslinjen", "bookmarkColor": "Bogmærkefarve", + "spellCheckHighlightColor": "Fremhævningsfarve for stavekontrol", "singleLineMaxLength": "Enkeltlinje maks. længde", "optimalCharsPerSec": "Optimale tegn/sek", "maxCharsPerSec": "Max tegn/sek", @@ -3116,13 +3462,21 @@ "unbreakSubtitlesShortThan": "Saml undertekster kortere end", "newEmptyDefaultMs": "Standard ny undertekstvarighed (ms)", "timeCodeUpDownStepMs": "Tid op/ned-trin (ms)", + "moveSelectedLinesStepMs": "Trin for genvej til flytning af valgte linjer (ms)", + "moveStartEndStepMs": "Trin for genvej til flytning af start/slut (ms)", + "moveLinesShortenNeighbor": "Flyt linjer: forkort forrige/næste linje i stedet for at overlappe den", "promptBeforeDelete": "Spørg før sletning", "rememberPositionAndSize": "Husk vinduers placering og størrelse", + "titleBarFullFileName": "Vis fuld filsti i titellinjen", + "showRecentFiles": "Vis seneste filer (til genåbning)", "openLastFileOnStart": "Åbn sidste seneste fil ved start", "autoSave": "Gem automatisk (gem den åbne fil under redigering)", "autoBackupOn": "Auto-backup", "autoBackupIntervalMinutes": "Interval for automatisk backup (minutter)", "autoBackupDeleteAfterDays": "Opbevaring af automatisk backup (dage)", + "settingsBackupOn": "Automatisk sikkerhedskopiering af indstillinger", + "settingsBackupIntervalDays": "Interval for sikkerhedskopiering af indstillinger (dage, 0 = ved hver start)", + "settingsBackupMaxCount": "Antal sikkerhedskopier af indstillinger, der beholdes", "autoConvertToUtf8": "Konvertér automatisk kodning til UTF-8 ved åbning", "autoTrimWhiteSpace": "Automatisk trim mellemrum", "defaultEncoding": "Standardkodning", @@ -3137,22 +3491,13 @@ "colorGapTooShort": "Farv hvis mellemrummet er for kort", "errorBackgroundColor": "Fejlbaggrundsfarve", "waveformDrawGridLines": "Tegn gitterlinjer", - "waveformCenterVideoPosition": "Centrer videoposition", + "waveformUseSkiaRenderer": "Brug eksperimentel hurtig renderer", "waveformCenterVideoPositionAlsoWhenPaused": "Centrer videoposition også ved pause", + "waveformSelectCurrentSubtitleWhilePaused": "Vælg aktuel undertekst også under pause", + "waveformRememberZoom": "Husk zoom (vandret og lodret)", "waveformShowToolbar": "Vis værktøjslinje", "waveformShowToolbarEdit": "Rediger værktøjslinje...", "waveformSpectrogramCombinedWaveformHeight": "Bølgeform/spektrogram kombineret, bølgeformhøjde %", - "showWaveformToolbarPlay": "Værktøjslinje: vis afspilningsknappen", - "showWaveformToolbarRepeat": "Værktøjslinje: Vis gentag-knap", - "showWaveformToolbarRemoveBlankLines": "Værktøjslinje: vis knappen Fjern tomme linjer", - "showWaveformToolbarNew": "Værktøjslinje: vis ny undertekstknap", - "showWaveformToolbarSetStart": "Værktøjslinje: vis sæt startknap", - "showWaveformToolbarSetEnd": "Værktøjslinje: vis sæt afslutningsknap", - "showWaveformToolbarStartAndOffsetTheRest": "Værktøjslinje: vis sæt start og forskyd resten", - "showWaveformToolbarHorizontalZoom": "Værktøjslinje: vis vandret zoom-skyder", - "showWaveformToolbarVerticalZoom": "Værktøjslinje: vis lodret zoomskyder", - "showWaveformToolbarVideoPositionSlider": "Værktøjslinje: vis videopositionsskyderen", - "showWaveformToolbarPlaybackSpeed": "Værktøjslinje: vis afspilningshastighed", "waveformFocusTextboxAfterInsertNew": "Fokusér tekstboks efter indsættelse", "waveformInvertMouseWheel": "Vend musehjulet", "waveformSnapToShotChanges": "Fastgør til sceneskift", @@ -3168,18 +3513,18 @@ "fixCommonErrorsSkipStep1": "Ret almindelige fejl: spring trin 1 over (vælg rettelser)", "showPluginsMenu": "Vis menuen Plugins", "proxyAddress": "Proxyadresse", - "username": "Brugernavn", "password": "Adgangskode", "defaultFormat": "Standardformat", "defaultSaveAsFormat": "Standard 'Gem som'-format", "favoriteSubtitleFormats": "Foretrukne undertekstformater", - "showStopButton": "Vis stopknap", - "showFullscreenButton": "Vis knap på fuld skærm", "fullscreenHideControls": "Skjul videokontroller i fuld skærm", + "showSecondarySubtitleDialog": "Vis stilartsdialog ved åbning af en sekundær undertekstfil", + "rememberSecondarySubtitleFile": "Husk sekundær undertekstfil", "autoOpenVideoFile": "Åbn automatisk videofil, når du åbner undertekster", "downloadMpv": "Hent mpv", "downloadVlc": "Hent VLC", "allowSingleLetterShortcutsInTextbox": "Tillad enkeltbogstavsgenveje i tekstboksen", + "allowTextNavigationShortcutsInTextbox": "Tillad genveje på tekstnavigationstaster (Ctrl+Venstre/Højre, Home/End) i tekstboks", "spellCheckEnglishTreatInApostropheAsIng": "Stavekontrol: Behandl ord, der ender på 'in'' som 'ing' (kun på engelsk)", "goToLineNumberSetsVideoPosition": "Gå-til-linje-nummer indstiller også videoposition", "adjustAllTimesRememberLineSelectionChoice": "Juster alle tider, husk valg af linjer", @@ -3191,6 +3536,7 @@ "showAssaLayer": "Vis ASSA-lagboks", "waveformCursorColor": "Bølgeform markør-/hovedfarve", "waveformShotChangeColor": "Bølgeform sceneskift-farve", + "waveformGridColor": "Gitterfarve for bølgeform", "waveformParagraphLeftColor": "Bølgeform venstre kantfarve", "waveformParagraphRightColor": "Bølgeform højre kantfarve", "waveformFancyHighColor": "Bølgeform fancy høj farve", @@ -3218,26 +3564,30 @@ "resetSyntaxColoring": "Nulstil syntaksfarve", "resetWaveform": "Nulstil bølgeform", "resetRules": "Nulstil regler", - "useFrameMode": "Brug billedtilstand (tt.mm.ss.ff)", + "timeCodeMode": "Tidskodetilstand", + "timeCodeModeTime": "Tid (hh:mm:ss,zzz)", + "timeCodeModeFrames": "Billeder (hh:mm:ss:ff)", + "timeCodeModeFrameNumbers": "Billednumre", + "frameModeSnapToVideoFrames": "Gem tider på videobilleder (ikke billeder inden for hvert sekund)", "textBoxLimitNewLines": "Begræns antallet af linjer i undertekstboksen", - "mpvVideoOutput": "Videoudgang (mpv)", "mpvOpenGl": "libmpv - OpenGL", "mpvSoftwareRendering": "libmpv - Softwaregengivelse (langsom)", + "mpvMetalRendering": "libmpv - Metal (eksperimentel, kræver genstart)", "mpvWidRendering": "libmpv - Native Window ID-gengivelse", "waveFormsAndSpectrogramFoldersContainsX": "'Bølgeformer' og 'spektrogram'-mapper indeholder {0}", "deleteWaveformAndSpectrogramFoldersQuestion": "Slet 'Waveforms' og 'Spectrogram' filer?", "waveformGenerateSpectrogram": "Generer spektrogram", "waveformSpectrogramMode": "Spektrogramtilstand", - "waveformCenterOnSingleClick": "Centrer på enkelt klik", - "waveformSingleClickSelectsSubtitle": "Vælg undertekst ved et enkelt klik", "waveformRightClickSelectsSubtitle": "Vælg undertekst ved højreklik", - "waveformPauseOnSingleClick": "Pause ved et enkelt klik", "waveformDrawStyle": "Bølgeformtegnestil", "waveformExtractAudioFormat": "Udtræk lydformat", "waveformExtractAudioSampleRate": "Samplefrekvens for udtrukket lyd", "waveformExtractAudioSampleRateOriginal": "Original (behold kilde)", "waveformExtractAudioBitRate": "Bithastighed for udtrukket lyd (MP3/M4A)", + "ffmpegUseCenterChannelOnly": "Brug kun frontcenterkanalen til 5.1-lyd (FFmpeg)", "vlcWidRendering": "libVLC - Native Window ID-gengivelse", + "ffmpegSoftwareRendering": "FFmpeg", + "downloadFfmpegLibs": "Download FFmpeg-biblioteker (til FFmpeg-videoafspilleren)", "subtitleGridEnterKeyAction": "Undertekstgitter Enter-tasthandling", "subtitleSingleClickAction": "Undertekstgitter handling med et enkelt klik", "subtitleDoubleClickAction": "Undertekstgitter dobbeltklik på handling", @@ -3254,6 +3604,7 @@ "subtitleGridFormattingShowFormatting": "Vis formatering", "subtitleGridFormattingShowTags": "Vis tags", "subtitleGridFormattingHideTags": "Skjul tags", + "subtitleGridFormattingShowFormattingKeepTags": "Vis formatering, behold ikke-visuelle tags", "waveformParagraphBackgroundColor": "Bølgeform undertekst baggrundsfarve", "waveformParagraphSelectedBackgroundColor": "Bølgeform valgt undertekst baggrundsfarve", "waveformAllowOverlap": "Tillad overlapning (ved flytning/ændring af størrelse)", @@ -3263,8 +3614,6 @@ "saveAsBehaviorUseVideoFileName": "Brug videofilnavn", "saveAsBehaviorUseSubtitleFileName": "Brug undertekst filnavn", "saveAsAppendLanguageCodeTwoLetter": "To bogstaver", - "saveAsAppendLanguageCodeThreeLetter": "Tre bogstaver", - "saveAsAppendLanguageCodeThreeLetterBibliographic": "Tre bogstaver (ISO 639-2/B)", "saveAsAppendLanguageCodeLanguageName": "Sprognavn", "splitOddLineActionWeightTop": "Vægt øverst", "splitOddLineActionWeightBottom": "Vægt nederst", @@ -3273,6 +3622,7 @@ "speechToTextSelectedLinesPromptFirstTimeOnly": "Tale til tekst: valgte linjer, spørg kun om sprog/motor første gang", "multipleReplaceShowDotDotDotButtons": "Flere udskiftninger: Vis kontekstmenuknapper", "gridFocusTextboxAfterInsertNew": "Gitter: fokus tekstboks efter indsættelse af ny undertekst", + "undoRedoGoToChangedLine": "Fortryd/gendan: gå til ændret linje (og videoposition)", "musicSymbol": "Musiksymbol", "musicSymbolsToReplace": "Musiksymboler der skal erstattes (adskilt af komma)", "textToSpeechPromptMergeContinuationLines": "Tekst til tale: spørg om at flette fortsættelseslinjer", @@ -3281,8 +3631,7 @@ "useFocusedButtonBackgroundColor": "Brug baggrundsfarve til fokuseret knap", "focusedButtonBackgroundColor": "Fokuseret knap baggrundsfarve", "forceCrLfOnSave": "Tving CR+LF når der gemmes (tekstbaserede undertekstfiler)", - "showWaveformToolbarPlayNext": "Værktøjslinje: vis afspil næste knap", - "showWaveformToolbarPlaySelection": "Værktøjslinje: Vis afspilningsvalgknap", + "linuxClipboardUseExternalTool": "Kopiér tekst via xclip/wl-copy (bevarer tegn med accenter)", "showFormatLimitWarning": "Advar ved gemning, når linjer overskrider formatets grænser (f.eks. SCC 32 tegn/linje)", "textBoxButtonShowAutoBreak": "Tekstboks: vis autobryd-knap", "textBoxButtonShowUnbreak": "Tekstboks: vis saml-knap", @@ -3294,12 +3643,18 @@ "waveformDoubleClickAction": "Bølgeform-dobbeltklik-handling (efter enkelt-klik-handling)", "allSettings": "Alle indstillinger", "uiScale": "UI-skala (%)", + "fontScale": "Skriftskalering (%)", + "textSelectionOpacity": "Uigennemsigtighed for tekstmarkering (%)", "waveformToolbarItems": "Bølgeform-værktøjslinjeelementer", "matchIconColorToDarkTheme": "Match ikonfarve til mørk temaforgrundsfarve", "subtitlePreviewProperties": "Egenskaber til forhåndsvisning af undertekster", "usePositionFromSubtitleFile": "Brug position fra undertekstfilen (TTML/PAC/EBU STL)", "marginIsPartOfSubtitleArea": "Margen er en del af undertekstområdet", "textJustify": "Justér linjer", + "deinterlace": "Deinterlace video", + "deinterlaceOff": "Fra", + "deinterlaceAuto": "Auto (kun interlaced billeder)", + "deinterlaceAlways": "Altid", "pixelWidthInfo": "Grønne linjer = maks. breddegrænse | \r\nRødt område = tekst overskrider grænsen", "spellCheckEngineHunSpelll": "Hunspell", "spellCheckEngineMsWord": "MS Word", @@ -3309,6 +3664,15 @@ "waveformSaveAsCustomTheme": "Gem som brugerdefineret tema...", "waveformLoadThemeDotDotDot": "Indlæs tema...", "waveformExportThemeDotDotDot": "Eksporter tema...", + "waveformSaveCustomThemeTitle": "Gem brugerdefineret tema", + "waveformThemeFileType": "Subtitle Edit-bølgeformtema", + "waveformThemeHighContrast": "Høj kontrast", + "waveformThemeOceanBlue": "Havblå", + "waveformThemeWarmSunset": "Varm solnedgang", + "waveformThemeForest": "Skov", + "waveformThemeMidnightPurple": "Midnatslilla", + "waveformThemeRetroAmber": "Retro-rav", + "waveformThemeIce": "Is", "subtitleGridTextSingleLineSeparator": "Enkeltlinjeadskiller (f.eks.
)", "subtitleGridTextDisplay": "Undertekst tilpasset i gitter", "subtitleGridTextDisplayClip": "Beskær (enkelt linje)", @@ -3325,107 +3689,53 @@ "webVttUseXTimestampMap": "WebVTT: brug X-TIMESTAMP-MAP (forskyd tidskoder ved indlæsning)", "waveformAutoGenerate": "Generér automatisk bølgeform, når en video åbnes", "gridGoToSubtitleAndPlayAndFocusTextBox": "Gå til undertekst og afspil og fokusér på tekstboks", - "ocrGuessUnknownWords": "OCR: forsøg at gætte ukendte ord" + "ocrGuessUnknownWords": "OCR: forsøg at gætte ukendte ord", + "spellCheckRememberUseAlwaysList": "Stavekontrol: husk listen \"Brug altid\"", + "fixShortDisplayTimesAllowMoveStartTime": "Ret almindelige fejl: ret korte visningstider må flytte starttid" }, "shortcuts": { - "title": "Genveje", "searchShortcuts": "Søg i genveje...", - "filter": "Filter", - "categoryGeneral": "Generelt", "categorySubtitleGridAndTextBox": "Undertekstliste og tekstfelt", "categorySubtitleGrid": "Undertekstliste", - "categoryWaveform": "Bølgeform", - "generalSetupLikeSe4": "Konfigurer som Subtitle Edit 4 (tema, genveje, erstat regler)", - "generalMergeSelectedLines": "Flet markerede linjer", - "generalMergeWithPrevious": "Flet med forrige", - "generalMergeWithNext": "Flet med næste", - "generalMergeWithPreviousAndUnbreak": "Flet med forrige og saml", - "generalMergeWithNextAndUnbreak": "Flet med næste og saml", - "generalMergeWithPreviousAndAutoBreak": "Flet med forrige og auto-del", - "generalMergeWithNextAndAutoBreak": "Flet med næste og auto-del", - "generalMergeSelectedLinesAndAutoBreak": "Flet markerede linjer og auto-del", "generalMergeSelectedLinesAndUnbreak": "Flet markerede linjer og saml", "generalMergeSelectedLinesAndUnbreakCjk": "Flet markerede linjer og saml uden mellemrum (CJK)", "generalUnbreakNoSpaceCjk": "Fjern linjeskift uden mellemrum (CJK)", - "generalMergeSelectedLinesOnlyFirstText": "Flet markerede linjer, kun første tekst", + "generalToggleBreak": "Skift linjeskift/fjern linjeskift", "generalMergeSelectedLinesBilingual": "Flet markerede linjer tosproget", - "generalMergeWithPreviousBilingual": "Flet med forrige tosproget", - "generalMergeWithNextBilingual": "Flet med næste tosproget", - "generalMergeOriginalAndTranslation": "Flet original og oversættelse", "generalToggleTranslationMode": "Slå oversættelsestilstand til/fra", - "generalSwitchOriginalAndTranslation": "Skift original og oversættelse", - "generalSwitchOriginalAndTranslationTextBoxes": "Skift original og oversættelse-tekstfelter", "generalToggleTranslationAndOriginalInPreviews": "Skift mellem oversættelse og original i video-/lydforhåndsvisning", "generalChooseLayout": "Vælg layout", - "generalLayoutChooseX": "Layout {0}", - "generalPlayFirstSelected": "Afspil først markerede", - "generalGoToFirstSelectedLine": "Gå til første markerede linje", - "generalGoToNextEmptyLine": "Gå til næste tomme linje", "generalGoToNextSubtitle": "Gå til næste undertekst", "generalGoToNextSubtitlePlayTranslate": "Gå til næste undertekst (afspil oversæt)", "generalGoToNextSubtitleCursorAtEnd": "Gå til næste undertekst (markør ved slutning)", "generalGoToPrevSubtitle": "Gå til forrige undertekst", "generalGoToPrevSubtitlePlayTranslate": "Gå til forrige undertekst (afspil oversæt)", - "generalGoToStartOfCurrentSubtitle": "Gå til start af aktuel undertekst", - "generalGoToEndOfCurrentSubtitle": "Gå til slutning af aktuel undertekst", - "generalGoToPreviousSubtitleAndFocusVideo": "Gå til forrige undertekst og fokuser på video", - "generalGoToNextSubtitleAndFocusVideo": "Gå til næste undertekst og fokuser på video", - "generalGoToPrevSubtitleAndPlay": "Gå til forrige undertekst og afspil", - "generalGoToNextSubtitleAndPlay": "Gå til næste undertekst og afspil", - "generalGoToPreviousSubtitleAndFocusWaveform": "Gå til forrige undertekst og fokuser på bølgeform", - "generalGoToNextSubtitleAndFocusWaveform": "Gå til næste undertekst og fokuser på bølgeform", - "generalGoToLineNumber": "Gå til linjenummer", "generalGoToFirstLine": "Gå til første linje", "generalGoToLastLine": "Gå til sidste linje", "alsoSetVideoPosition": "Sæt også videoposition", "generalGoToVideoPosition": "Gå til videoposition", "generalToggleItalic": "Slå kursiv til/fra", "generalToggleBold": "Slå fed til/fra", - "generalToggleBookmarks": "Slå bogmærker til/fra", - "generalFocusTextBox": "Fokuser på tekstfelt", - "generalToggleBookmarksWithText": "Slå bogmærker med tekst til/fra", - "generalEditBookmarks": "Rediger bogmærker", - "fileNew": "Ny", "generalToggleUnderline": "Slå understregning til/fra", + "generalToggleBox": "Skift boks (EBU STL)", "fileOpen": "Åbn", "fileOpenKeepVideo": "Åbn (behold video)", "fileSave": "Gem", "fileSaveAs": "Gem som", - "fileSaveAll": "Gem alle", "fileSaveOriginal": "Gem original", - "fileSaveOriginalAs": "Gem original som", - "fileOpenOriginalSubtitle": "Åbn original undertekst", - "fileCloseOriginalSubtitle": "Luk original undertekst", - "fileTranslatedSubtitle": "Oversat undertekst", "fileCompare": "Sammenlign", "fileStatistics": "Statistik", "fileImportPlainText": "Importer almindelig tekst", - "fileImportBluRaySupForOcr": "Importer Blu-ray SUP til OCR", - "fileImportBluRaySupForEdit": "Importer Blu-ray SUP til redigering", - "fileImportTimeCodes": "Importer tidskoder", "fileExportEbuStl": "Eksporter EBU STL", "fileExportPac": "Eksporter PAC", - "fileExportEdlClipName": "Eksporter EDL-klipnavn", - "fileExportPlainText": "Eksporter almindelig tekst", - "fileExportCustomTextFormat1": "Eksporter tilpasset tekstformat 1", - "fileExportCustomTextFormat2": "Eksporter tilpasset tekstformat 2", - "fileExportCustomTextFormat3": "Eksporter tilpasset tekstformat 3", "fileExit": "Afslut", "openSeDataFolder": "Åbn Subtitle Edit-mappe", - "editFind": "Find", - "editFindNext": "Find næste", "editFindPrevious": "Find forrige", - "editReplace": "Erstat", - "editMultipleReplace": "Flere erstatninger", "fillSelectedLinesWithClipboard": "Udfyld valgte linjer med udklipsholdertekst", - "editModifySelection": "Rediger markering", - "listSelectAll": "Vælg alle", - "listSelectFirst": "Vælg første", - "listSelectLast": "Vælg sidste", "listInverseSelection": "Inverter markering", "listDeleteSelection": "Slet markering", + "deleteSelectionEverywhere": "Slet markering (overalt)", "rippleDeleteSelection": "Ripple-slet markering", - "settings": "Indstillinger", "assigned": "Tildelt", "unassigned": "Ikke tildelt", "pressedKeyX": "Trykt tast: {0}", @@ -3433,6 +3743,7 @@ "detectKey": "Registrer tast", "control": "Control", "alt": "Alt", + "altGr": "AltGr", "win": "Win", "shift": "Shift", "controlMac": "⌃", @@ -3443,11 +3754,14 @@ "resetShortcutsDetail": "Vil du nulstille alle genveje til standardværdier?", "togglePlayPause": "Skift afspilning/pause", "toggleLockTimeCodes": "Slå låsetidskoder til/fra", + "toggleTimeCodeMode": "Skift tidskodetilstand (tid/billeder/billednumre)", + "toggleScreenPrivacy": "Skift skærmbeskyttelse (skjul filnavne, derefter også tekster)", + "screenPrivacyFileNamesHidden": "Skærmbeskyttelse: filnavne skjult", + "screenPrivacyFileNamesAndTextsHidden": "Skærmbeskyttelse: filnavne og tekster skjult", + "screenPrivacyOff": "Skærmbeskyttelse: fra", "duplicateSelectedLines": "Dupliker valgte linjer", "sourceView": "Kildevisning", - "showAlignmentPicker": "Justering", "addOrEditBookmark": "Tilføj eller rediger bogmærke", - "listBookmarks": "Liste bogmærker", "toggleBookmark": "Skift bogmærke (valgte linjer, ingen tekst)", "toggleForcedSelectedLines": "Slå tvungen til/fra (valgte linjer)", "goToNextBookmark": "Gå til næste bogmærke", @@ -3458,7 +3772,6 @@ "waveformSetStartAndSetEndOfPreviousMinusGap": "Indstil start og sæt slutning af forrige minus mellemrum", "waveformSetEndAndStartOfNextAfterGap": "Indstil slutningen og starten af næste plus mellemrum", "waveformSetEndAndStartOfNextAfterGapAndGoToNext": "Indstil slutningen og starten af næste plus mellemrum og gå til næste", - "fetchFirstWordFromNextSubtitle": "Hent det første ord fra næste undertekst", "moveLastWordToNextSubtitle": "Flyt sidste ord til næste undertekst", "moveLastWordFromFirstLineDownCurrentSubtitle": "Flyt sidste ord fra første linje ned (aktuel undertekst)", "moveFirstWordFromNextLineUpCurrentSubtitle": "Flyt første ord fra næste linje op (aktuel undertekst)", @@ -3471,7 +3784,6 @@ "goToPreviousLineFromVideoPosition": "Gå til forrige undertekst (fra den aktuelle videoposition)", "goToNextLineFromVideoPosition": "Gå til næste undertekst (fra den aktuelle videoposition)", "goToNextLineAndSetVideoPosition": "Gå til næste undertekst og indstil videoposition", - "textBoxDeleteSelectionNoClipboard": "Tekstboks: Slet markering (ingen udklipsholder)", "textBoxDeleteForward": "Tekstboks: Slet markering eller næste tegn (slet fremad)", "textBoxCut": "Tekstboks: Klip", "textBoxCut2": "Tekstboks: Klip (alternativt)", @@ -3489,29 +3801,22 @@ "fileCloseOriginal": "Luk originalen", "fileCloseTranslation": "Luk oversættelse", "restoreAutoBackup": "Gendan automatisk sikkerhedskopiering", - "openContainingFolder": "Åbn indeholdende mappe", "importTimeCodes": "Importer tidskoder", "importSubtitleWithManuallyChosenEncoding": "Importer undertekster med manuelt valgt kodning", "exportBluRaySup": "Eksporter Blu-ray SUP", "exportCustomTextFormat": "Eksportér brugerdefineret tekstformat", "exportPlainText": "Eksporter almindelig tekst", - "showHistory": "Vis historik", "toggleRightToLeft": "Skift fra højre mod venstre", "modifySelection": "Rediger valg", "adjustDurations": "Juster varigheder", "applyDurationLimits": "Anvend varighedsgrænser", "batchConvert": "Batchkonvertering", - "bridgeGaps": "Byg bro over mellemrum", "applyMinGap": "Påfør min. mellemrum", - "fixCommonErrors": "Ret almindelige fejl", "makeEmptyTranslationFromCurrentSubtitle": "Lav en tom oversættelse fra den aktuelle undertekst", - "mergeLinesWithSameText": "Flet linjer med samme tekst", - "mergeLinesWithSameTimeCodes": "Flet linjer med samme tidskoder", "splitBreakLongLines": "Opdel/genbalancer lange linjer", "mergeShortLines": "Flet korte linjer", "mergeContinuationLines": "Flet fortsættelseslinjer", "snapAllTimesToFrames": "Fastgør alle tider til billeder", - "removeTextForHearingImpaired": "Fjern tekst for hørehæmmede", "joinSubtitles": "Sammenføj undertekster", "splitSubtitle": "Opdel undertekst", "spellCheck": "Stavekontrol", @@ -3532,7 +3837,6 @@ "adjustAllTimes": "Juster alle tider", "visualSync": "Visuel synkronisering", "translateViaCopyPaste": "Oversæt via copy-paste", - "shortcuts": "Genveje", "wordLists": "Ordlister", "chooseUiLanguage": "Vælg UI-sprog", "chooseRuleProfile": "Vælg regelprofil", @@ -3543,6 +3847,7 @@ "textBoxItalic": "Tekstboks kursiv", "textBoxBold": "Tekstboks fed", "textBoxUnderline": "Tekstboks understregning", + "textBoxBox": "Tekstboks boks (EBU STL)", "resetWaveformZoomAndSpeed": "Nulstil bølgeformszoom og afspilningshastighed", "togglePlaybackSpeed": "Skift afspilningshastighed", "playbackSpeedSlower": "Afspilningshastighed langsommere (afspilningsrate)", @@ -3554,22 +3859,28 @@ "seekSilenceForward": "Søg stilhed fremad", "waveformGuessStart": "Gæt starttid ud fra bølgeform", "waveformGuessEnd": "Gæt sluttid ud fra bølgeform", + "waveformGuessStartAndEnd": "Gæt start- og sluttid ud fra bølgeform", "setVideoPositionCurrentSubtitleStart": "Indstil videopositionen til den aktuelle linjestart", "goToSubtitlePositionAndPause": "Gå til undertekstposition og sæt på pause", "setVideoPositionCurrentSubtitleEnd": "Indstil videopositionen til den aktuelle linjeslutning", "toggleAudioTracks": "Skift lydspor", - "listErrors": "List fejl", "goToNextError": "Gå til næste fejl", "goToPreviousError": "Gå til forrige fejl", "addNameToNameList": "Tilføj navn til navnelisten", "findDoubleWords": "Find dobbeltord", "findDoubleLines": "Find dobbelte linjer", "colorX": "Farve {0}", - "removeColor": "Fjern farve", "surroundWith": "Omgiv med...", "surroundWithXY": "Omring med {0}/{1}", - "repeatLine": "Gentag linje", "surroundWithNumberX": "Omgiv med #{0}", + "surroundWithBehavior": "Adfærd", + "surroundWithBehaviorToggle": "Skift (tilføj, eller fjern hvis til stede)", + "surroundWithBehaviorAdd": "Tilføj (hver gang)", + "surroundWithBehaviorRemove": "Fjern (alle)", + "surroundWithBehaviorRemoveOnce": "Fjern (én ad gangen)", + "surroundWithWorksOn": "Virker på", + "surroundWithScopeSelectionOrText": "Markering, ellers hele teksten", + "surroundWithScopeEachLine": "Hver linje", "searchVia": "Søg via", "searchViaX": "Søg via {0}", "searchViaNumberX": "Søg via #{0}", @@ -3596,6 +3907,11 @@ "copySubtitlePathToClipboard": "Kopier undertekststien til udklipsholderen", "copySubtitleOriginalPathToClipboard": "Kopier undertekststien til originalen til udklipsholderen", "focusTextBox": "Fokus tekstboks", + "focusSubtitleListView": "Fokusér undertekstliste", + "focusWaveform": "Fokusér bølgeform", + "focusOriginalTextBox": "Fokusér original tekstboks", + "textBoxGoToStart": "Tekstboks, gå til start", + "textBoxGoToEnd": "Tekstboks, gå til slut", "sortByNumber": "Sortér efter nummer", "sortByStartTime": "Sorter efter \"Vis\" tid", "sortByEndTime": "Sorter efter \"Skjul\" tid", @@ -3620,7 +3936,6 @@ "assaStyles": "ASSA stilarter", "assaProperties": "ASSA egenskaber", "assaAttachments": "ASSA vedhæftede filer", - "assaVideoColorPicker": "ASSA Video farvevælger", "recalculateDurationSelectedLines": "Genberegn varighed (valgte linjer)", "setDurationMaxCpsSelectedLines": "Sæt varighed til maks. CPS (valgte linjer)", "toggleWaveformAndSpectrogramHeight": "Skift bølgeform/spektrogram opdelt højde", @@ -3634,9 +3949,9 @@ "selectionToSentenceCase": "Tekstboks: Markering til Stort begyndelsesbogstav", "selectionToggleCasing": "Tekstboks: Skift store/små bogstaver (STORE -> små -> Store Forbogstaver)", "googleIt": "Google det (valgt tekst)", - "setActorXY": "Sæt skuespiller {0}: {1}", + "setActorX": "Angiv skuespiller: {0}", "setNewActor": "Sæt ny skuespiller...", - "setActorSlotXTitle": "Skuespillerplads {0}", + "setStyleX": "Angiv stilart: {0}", "toggleDialogDashes": "Slå dialogtankestreger til/fra", "mergeWithLineBeforeAsDialog": "Flet med linjen før som dialog", "mergeWithLineAfterAsDialog": "Flet med linjen efter som dialog", @@ -3648,18 +3963,54 @@ "moveStartOneFrameForwardKeepGapPrev": "Flyt start ét billede frem (behold mellemrum til forrige, hvis tæt på)", "moveEndOneFrameBackKeepGapNext": "Flyt slut ét billede tilbage (behold mellemrum til næste, hvis tæt på)", "moveEndOneFrameForwardKeepGapNext": "Flyt slut ét billede frem (behold mellemrum til næste, hvis tæt på)", + "moveStartXMsBack": "Flyt start X ms tilbage (X angives i Indstillinger, også i Visuel synkronisering)", + "moveStartXMsForward": "Flyt start X ms frem (X angives i Indstillinger, også i Visuel synkronisering)", + "moveEndXMsBack": "Flyt slut X ms tilbage (X angives i Indstillinger, også i Visuel synkronisering)", + "moveEndXMsForward": "Flyt slut X ms frem (X angives i Indstillinger, også i Visuel synkronisering)", + "moveSelectedLinesXMsBack": "Flyt valgte linjer X ms tilbage (X angives i Indstillinger)", + "moveSelectedLinesXMsForward": "Flyt valgte linjer X ms frem (X angives i Indstillinger)", + "moveSelectedLinesAndForwardXMsBack": "Flyt valgte linjer og alle efterfølgende X ms tilbage (X angives i Indstillinger)", + "moveSelectedLinesAndForwardXMsForward": "Flyt valgte linjer og alle efterfølgende X ms frem (X angives i Indstillinger)", + "moveAllLinesXMsBack": "Flyt alle linjer X ms tilbage (X angives i Indstillinger)", + "moveAllLinesXMsForward": "Flyt alle linjer X ms frem (X angives i Indstillinger)", + "moveSelectedLinesCustomMsBack": "Flyt valgte linjer, tilpassede millisekunder ({0:#,###,##0}) tilbage, {1}", + "moveSelectedLinesCustomMsForward": "Flyt valgte linjer, tilpassede millisekunder ({0:#,###,##0}) frem, {1}", + "moveSelectedLinesAndForwardCustomMsBack": "Flyt valgte linjer og alle efterfølgende, tilpassede millisekunder ({0:#,###,##0}) tilbage, {1}", + "moveSelectedLinesAndForwardCustomMsForward": "Flyt valgte linjer og alle efterfølgende, tilpassede millisekunder ({0:#,###,##0}) frem, {1}", + "moveAllLinesCustomMsBack": "Flyt alle linjer, tilpassede millisekunder ({0:#,###,##0}) tilbage, {1}", + "moveAllLinesCustomMsForward": "Flyt alle linjer, tilpassede millisekunder ({0:#,###,##0}) frem, {1}", "categoryTextBox": "Tekstboks", "categoryFile": "Fil", "categoryVideo": "Video", "categorySync": "Synkronisering", - "categoryTranslate": "Oversæt", - "categorySearch": "Søg", - "categoryTools": "Værktøjer", "categoryAi": "AI", + "categoryCustom": "Tilpasset", + "editCustomShortcut": "Rediger brugerdefineret genvej", + "customShortcutTextBoxKeyHint": "Brug en tast med Ctrl/Alt her - en almindelig tast ville ikke længere skrive i tekstboksen.", + "customShortcutNumberX": "Brugerdefineret genvej #{0}", + "customShortcutNumberXY": "Brugerdefineret genvej #{0}: {1}", + "customShortcutStep": "Trin", + "customShortcutSteps": "Trin", + "customShortcutStepRunCommand": "Kør kommando", + "customShortcutStepInsertText": "Indsæt tekst", + "customShortcutStepReplace": "Søg og erstat", + "customShortcutInsertAtCursor": "Ved markøren", + "customShortcutInsertAtStart": "I starten af teksten", + "customShortcutInsertAtEnd": "I slutningen af teksten", + "customShortcutSummaryRunCommandX": "Kør: {0}", + "customShortcutSummaryInsertXY": "Indsæt \"{0}\" ({1})", + "customShortcutSummaryReplaceXY": "Erstat \"{0}\" med \"{1}\"", + "customShortcutSummaryReplaceRegexXY": "Erstat regulært udtryk \"{0}\" med \"{1}\"", + "customShortcutPickCommand": "Vælg en kommando, der skal køres.", + "customShortcutEnterText": "Indtast den tekst, der skal indsættes.", + "customShortcutEnterFind": "Indtast den tekst, der skal findes.", + "customShortcutInvalidRegexX": "Ugyldigt regulært udtryk: {0}", + "customShortcutStepsHint": "Trin køres fra top til bund. Teksttrin virker på de valgte linjer; kommandoer, der åbner et vindue, fortsætter, når det lukkes.", "activeIn": "Aktiv i", "activeInEverywhere": "Overalt", "moveTextFromCursorToNextAndGoToNext": "Flyt tekst efter markørposition til næste undertekst og gå til næste", "moveTextFromCursorToNextAndGoToNextAndPlay": "Flyt tekst efter markørposition til næste undertekst, gå til næste og afspil", + "moveTextFromCursorToNextAndGoToNextAndPlayAndPause": "Flyt tekst efter markørposition til næste undertekst, gå til næste, afspil den og sæt på pause ved slutningen", "videoGoToPreviousTimeCode": "Gå til forrige tidskode", "videoGoToNextTimeCode": "Gå til næste tidskode", "listViewColumnDeleteText": "Kolonne, slet tekst", @@ -3669,6 +4020,7 @@ "listViewColumnTextUp": "Kolonne, tekst op", "listViewColumnTextDown": "Kolonne, tekst ned", "autoTranslateSelectedLines": "Oversæt automatisk valgte linjer...", + "autoTranslateSelectedLinesNoPrompt": "Auto-oversæt valgte linjer (ingen prompt, brug seneste motor/sprog)", "setAssaResolution": "Sæt ASSA-opløsning (PlayResX/PlayResY)", "videoOcr": "OCR af indbrændt undertekst", "waveformInsertNewSelection": "Bølgeform, indsæt ny markering" @@ -3676,8 +4028,6 @@ "wordLists": { "title": "Ordlister", "addName": "Tilføj navn", - "addWord": "Tilføj ord", - "addPair": "Tilføj par", "nameAndIgnoreList": "Navne/ignorer liste", "userWords": "Brugerordliste", "ocrFixList": "OCR rettelsesliste", @@ -3694,7 +4044,6 @@ "noPluginsInstalled": "Ingen plugins installeret", "getPluginsOnline": "Hent plugins online...", "getPluginsTitle": "Hent plugins", - "enabled": "Aktiveret", "disabled": "Deaktiveret", "notSupportedOnThisOs": "Ikke understøttet på dette operativsystem", "removePluginXConfirm": "Vil du fjerne pluginnet '{0}'?\r\nDette sletter plugin-mappen:{1}", @@ -3711,17 +4060,19 @@ "downloadingXPercent": "Downloader {0} %...", "pluginXInstalled": "'{0}' installeret.", "couldNotInstallPluginX": "Kunne ikke installere '{0}': {1}", - "notInstalled": "Ikke installeret", "install": "Installer", "reinstall": "Geninstaller", - "update": "Opdater", "installedX": "Installeret ({0})", "updateAvailableXToY": "Opdatering tilgængelig ({0} → {1})", "applyPluginToWhichLinesX": "Anvend '{0}' til:", "applyToSelectedLinesX": "Valgte linjer ({0})", "applyToAllLinesX": "Alle linjer ({0})", "updateAllXAvailable": "Opdater alle ({0})", - "updatingXOfY": "Opdaterer {0}/{1}..." + "updatingXOfY": "Opdaterer {0}/{1}...", + "doNotAskAgain": "Spørg ikke igen (ændres i Administrer plugins)", + "applyPluginsTo": "Anvend plugins på", + "askEachTime": "Spørg hver gang", + "allLines": "Alle linjer" }, "help": { "aboutSubtitleEdit": "Om Subtitle Edit", @@ -3760,6 +4111,8 @@ "nOcrDatabase": "nOCR-database", "drawMode": "Tegnetilstand:", "addNewCharcter": "Tilføj nyt tegn", + "addNOcrCharacterTitle": "Tilføj nOCR-tegn for linje {0}, tegn {1} af {2} med databasen \"{3}\"", + "addBinaryOcrCharacterTitle": "Tilføj binært OCR-tegn for linje {0}, tegn {1} af {2} med databasen \"{3}\"", "lineIndexX": "Linje {0}", "inspectNOcrAdditions": "Undersøg nye nOCR-tilføjelser", "ocrSelectedLines": "OCR valgte linjer", @@ -3808,6 +4161,8 @@ "binaryImageCompareInspectImageMatches": "\"Sammenlign binært billede\" - Inspicer billedmatches", "saveBlankTextTitle": "Vil du gemme tom tekst?", "saveBlankTextPrompt": "Vil du gemme tom tekst til billede?", + "drawnLinesDoNotMatchTitle": "Linjerne matcher ikke billedet", + "drawnLinesDoNotMatchPrompt": "Nogle af de tegnede linjer matcher ikke billedet (grønne linjer skal være på tegnet, røde linjer skal være uden for det), så dette tegn vil ikke blive genkendt.\n\nGem alligevel?", "yesAndNeverAskAgain": "Ja og spørg aldrig igen", "importTextFromSubtitleDotDotDot": "Importer tekst fra undertekst...", "importTextFromSubtitleOverwritePrompt": "Nogle linjer har allerede OCR-tekst.\r\nVil du overskrive eksisterende tekst?", @@ -3833,7 +4188,6 @@ "vobSubColorsTitle": "VobSub/DVD farver", "vobSubColorsHeader": "Tilpas de fire VobSub-farver", "vobSubColorsDescription": "VobSub/DVD-undertekster bruger fire indekserede farver: baggrund, mønster, betoning 1 og betoning 2. Vælg hver farve nedenfor for at tilsidesætte den indlejrede palet - nyttigt, når de originale farver giver dårlig OCR-kontrast.", - "vobSubColorBackground": "Baggrund", "vobSubColorPattern": "Mønster", "vobSubColorEmphasis1": "Vægt 1", "vobSubColorEmphasis2": "Vægt 2", @@ -3872,9 +4226,26 @@ "crispEmbedSettingsTitle": "CrispEmbed-indstillinger", "crispEmbedDescription": "Lokal OCR-motor med flere modelbackends - download motoren og modellerne her.", "trainNOcrDatabase": "Træn nOCR-database...", + "optimizeNOcrDatabase": "Optimer nOCR-database...", + "optimizeNOcrDatabaseQuestionXY": "Fjern de linjer, der ikke skelner tegn fra lignende tegn, i nOCR-databasen \"{0}\"?\n\nDette gør databasen mindre og OCR hurtigere, som regel med færre fejl. Tager op til et minut.\n\nEn sikkerhedskopi gemmes som \"{1}\".", + "optimizingNOcrDatabaseXY": "Optimerer nOCR-database... {0}/{1}", + "nOcrDatabaseOptimizedXYZ": "nOCR-databasen \"{0}\" optimeret: {1:#,##0} -> {2:#,##0} linjer.", "startTraining": "Start træning", "abortTraining": "Afbryd træning", "trainingOptions": "Træningsindstillinger", + "trainNOcrSubtitle": "Gengiver tegn i installerede skrifttyper og lærer nOCR deres former. Vælg de skrifttyper, dine undertekster bruger.", + "xFontsSelected": "{0:#,##0} valgt", + "subtitleFonts": "Undertekstskrifttyper", + "emptyDatabase": "Tom database", + "startFromDatabase": "Start fra", + "startFromDatabaseHint": "Tilføj de trænede tegn til en kopi af en eksisterende database (anbefalet), eller start fra en tom", + "fontSizes": "Skriftstørrelser", + "fontSizesHint": "Skriftstørrelser i pixels, der skal gengives, adskilt af kommaer, f.eks. \"30, 40\"", + "numberOfLineSegmentsHint": "Linjer, der tjekkes pr. tegn - flere er strengere, men langsommere", + "enterDatabaseName": "Indtast et navn til databasen", + "trainingFontXSizeYCharacterZ": "Træner \"{0}\" ved {1} px: {2}", + "xLearned": "{0:#,##0} lært", + "xSkipped": "{0:#,##0} sprunget over", "charactersToTrain": "Tegn, der skal trænes", "letterCombinationsToTrain": "Bogstavkombinationer, der kan blive delt op i ét billede", "importCharactersFromSubtitleFile": "Importér tegn fra undertekstfil...", @@ -3901,6 +4272,8 @@ "drawZoomOut": "Zoom ud (Ctrl+-)", "drawResetView": "Nulstil visning (Ctrl+0)", "drawToggleGrid": "Skift gitter (Ctrl+G)", + "drawTogglePreview": "Slå forhåndsvisning til/fra (F9)", + "drawPreviewFailed": "Forhåndsvisning mislykkedes - ffmpeg med libass (subtitles-filter) er påkrævet", "drawCopyToClipboard": "Kopiér til udklipsholder (Ctrl+C)", "drawShapes": "Former", "drawSelectedPoint": "Valgt punkt", @@ -3908,16 +4281,86 @@ "drawSelectedLayer": "Valgt lag", "drawToolX": "Værktøj: {0}", "drawHelpText": "Klik for at tilføje punkter • Enter/F8 for at lukke form • Shift+Træk for at panorere • Ctrl+Scroll for at zoome", + "drawMoveToLayer": "Flyt til lag", + "drawFlipHorizontal": "Spejlvend vandret", + "drawFlipVertical": "Spejlvend lodret", + "drawHideShape": "Skjul figur", + "drawShowShape": "Vis figur", + "drawHideLayer": "Skjul lag", + "drawShowLayer": "Vis lag", + "drawDeleteLayer": "Slet lag", + "drawDeletePoint": "Slet punkt", + "drawCancelDrawing": "Annuller tegning", + "drawLayerX": "Lag {0}", + "drawImportSvg": "Importér SVG-billede...", + "drawRotateClockwise": "Rotér 90° med uret", + "drawConvertToCurve": "Konvertér segment til kurve", + "drawBackground": "Baggrund", + "drawPosition": "Position", + "drawZoom": "Zoom", + "drawShapeTool": "Figurværktøj", + "drawColorPickerTool": "Vælg farve fra en figur eller baggrunden", + "drawShapeLibrary": "Figurbibliotek", + "drawShapeLibraryHint": "Træk på lærredet for at placere en figur, eller klik for standardstørrelsen. Shift bevarer dens proportioner.", + "drawAddToLibrary": "Føj til figurbibliotek...", + "drawRemoveFromLibrary": "Fjern fra figurbibliotek", + "drawShapeName": "Figurnavn", + "drawCategorySpeechBubbles": "Talebobler", + "drawCategoryArrows": "Pile", + "drawCategoryBasicShapes": "Grundfigurer", + "drawCategorySymbols": "Symboler", + "drawCategoryMyShapes": "Mine figurer", + "drawShapeSpeechBubble": "Taleboble", + "drawShapeSpeechBubbleRight": "Taleboble, hale til højre", + "drawShapeSpeechBubbleUp": "Taleboble, hale op", + "drawShapeOvalBubble": "Oval taleboble", + "drawShapeBoxBubble": "Kasseformet taleboble", + "drawShapeThoughtBubble": "Tankeboble", + "drawShapeShoutBubble": "Råbeboble", + "drawShapeCaptionBox": "Tekstboks", + "drawShapeArrow": "Pil", + "drawShapeDoubleArrow": "Dobbeltpil", + "drawShapeChevron": "Chevron", + "drawShapeCurvedArrow": "Buet pil", + "drawShapeRoundedRectangle": "Afrundet rektangel", + "drawShapeTriangle": "Trekant", + "drawShapeDiamond": "Rombe", + "drawShapePentagon": "Femkant", + "drawShapeHexagon": "Sekskant", + "drawShapeOctagon": "Ottekant", + "drawShapeStar": "Stjerne", + "drawShapeRing": "Ring", + "drawShapePlus": "Plus", + "drawShapeBanner": "Banner", + "drawShapeHeart": "Hjerte", + "drawShapeCheck": "Flueben", + "drawShapeMusicNote": "Node", + "drawShapeMusicNotes": "Noder", + "drawShapeLightning": "Lyn", + "drawShapeCloud": "Sky", + "drawShapeMoon": "Måne", + "drawShapeBadge": "Badge", + "drawSize": "Størrelse", + "drawLayer": "Lag", + "drawBackgroundVideoFrame": "Videobillede (aktuel position)", + "drawBackgroundVideoFrameAt": "Videobillede ved...", + "drawBackgroundImage": "Billedfil...", + "drawBackgroundNone": "Ingen baggrund", + "drawBackgroundStretch": "Stræk billede til lærred", + "drawBackgroundOpacity": "Baggrundens uigennemsigtighed", + "drawBackgroundVideoFrameFailed": "Kunne ikke hente et videobillede ved {0}.", + "drawImages": "Billeder", + "drawConvertToLine": "Konvertér segment til linje", + "drawConvertShapeToCurves": "Konvertér alle linjer til kurver", + "drawConvertShapeToLines": "Konvertér alle kurver til linjer", + "drawRotateCounterClockwise": "Rotér 90° mod uret", + "drawSvgImages": "SVG-billeder", + "drawSvgNoShapes": "Ingen udfyldte figurer eller streger fundet i SVG-filen.", "progressBarTitle": "ASSA statuslinje", "progressBarSettings": "Statuslinje", - "progressBarPosition": "Position", - "progressBarBottom": "Bund", - "progressBarTop": "Top", "mouseOverColor": "Farve ved musemarkør over", "clickedColor": "Farve ved klik", "progressBarForeColor": "Forgrundsfarve", - "progressBarBackColor": "Baggrundsfarve", - "progressBarStyle": "Stil", "progressBarSquareCorners": "Firkantede hjørner", "progressBarRoundedCorners": "Afrundede hjørner", "progressBarChapters": "kapitler", @@ -3926,8 +4369,6 @@ "progressBarXAdjustment": "X justering", "progressBarYAdjustment": "Y justering", "progressBarTextAlignment": "Tekstjustering", - "progressBarTakePosFromVideo": "Tag position fra video", - "progressBarPreview": "Forhåndsvisning", "fontCollectorTitle": "Skrifttypesamler", "fontCollectorUsedIn": "Brugt i", "fontCollectorFontFiles": "Skrifttypefil(er)", @@ -3974,9 +4415,7 @@ "resolutionResamplerVideoDiffers": "Undertekstens opløsning ({0}x{1}) afviger fra videoens opløsning ({2}x{3}).\nVil du ændre underteksten, så den passer til videoen?", "resolutionResamplerAskOnVideoOpen": "Spørg, når en video med en anden opløsning åbnes", "backgroundBoxGenerator": "Generer baggrundsboks", - "backgroundBoxPadding": "Polstring", "backgroundBoxFillWidth": "Fyldbredde", - "backgroundBoxBoxColor": "Boksfarve", "backgroundBoxRadius": "Radius", "backgroundBoxCircle": "Cirkel", "backgroundBoxSpikes": "Pigge", @@ -3988,7 +4427,6 @@ "backgroundBoxTornPaperDouble": "Revet papir (top og bund)", "backgroundBoxStarburst": "Stjerneskud", "backgroundBoxScroll": "Pergament", - "stylesTitle": "Avancerede Sub Station Alpha stilarter", "stylesInFile": "Stilarter i fil", "stylesSaved": "Stilarter gemt", "stylesTitleX": "Stilarter - {0}", @@ -4008,12 +4446,25 @@ "graphics": "Grafik", "copyToStorageStyles": "Kopiér til lagerstile", "copyToFileStyles": "Kopiér til fil-stilarter", + "styleXAlreadyExistsInStorage": "Der findes allerede en stilart med navnet \"{0}\" i lageret.\n\nOverskriv den, eller behold begge (kopien får et nyt navn)?", + "styleXAlreadyExistsInFile": "Der findes allerede en stilart med navnet \"{0}\" i filen.\n\nOverskriv den, eller behold begge (kopien får et nyt navn)?", + "styleNameCannotBeEmpty": "Et stilartsnavn kan ikke være tomt.", + "styleNameXIsUsedMoreThanOnce": "Stilartsnavnet \"{0}\" bruges af mere end én stilart i filen.", + "overwrite": "Overskriv", + "keepBoth": "Behold begge", + "doThisForAllConflictingStyles": "Gør dette for alle stilarter i konflikt", "setStyleAsDefault": "Indstil stil som standard", "takeUsagesFromDotDotDot": "Overtag anvendelser fra...", "noAttachmentsFound": "Ingen vedhæftede filer fundet i den valgte ASSA-fil.", "deleteStyleQuestion": "Vil du slette stil?", "deleteStylesQuestion": "Vil du slette typografier?", + "deleteXStylesQuestion": "Vil du slette {0} stilarter?", + "deleteXStylesFromStorageQuestion": "Vil du slette {0} stilarter fra lageret?", + "deleteStyleXFromStorageQuestion": "Vil du slette stilarten \"{0}\" fra lageret?", + "deleteStyleXFromFileQuestion": "Vil du slette stilarten \"{0}\" fra den aktuelle fil?", "openStyleImportFile": "Åbn undertekstfil for at importere stilarter fra", + "styleImportFiles": "Stilartsfiler (.ass, .sty, Subtitle Edit 4-kategorier .template)", + "ssaStyleImportFiles": "Stilartsfiler (.ssa, .ass, .sty)", "primary": "Primær", "secondary": "Sekundær", "applyOverrideTags": "Anvend tilsidesættelsestags", @@ -4028,7 +4479,6 @@ "spaceIncrease": "Øget bogstavafstand", "popIn": "Pop ind", "spinIn": "Spin ind", - "playCurrent": "Afspil aktuel", "setPosition": "Indstil position", "scaleX": "Skalering X", "scaleY": "Skalering Y", @@ -4071,10 +4521,8 @@ "advancedEffectOldMovieDescription": "Simulerer en vintage 35 mm filmprojektor med støj, vignet og ridser", "advancedEffectNeonBurst": "Neoneksplosion", "advancedEffectNeonBurstDescription": "Højenergiske \"Shorts\"-stil undertekster med neonblomstrende glød og indskaleret \"pop\"-animation", - "advancedEffectSettings": "Indstillinger", "advancedEffectSnowFlakeCount": "Antal flager", "advancedEffectStarfieldStarCount": "Antal stjerner", - "advancedEffectStarfieldSpeed": "Hastighed", "advancedEffectGlitch": "Systemfejl", "advancedEffectGlitchDescription": "Digital fejl med kromatisk aberration, hvide blink og forvrængning overlejringer", "advancedEffectBounceIn": "Hoppe ind", @@ -4083,8 +4531,6 @@ "advancedEffectFirefliesDescription": "Varme glødende gul-rav prikker driver organisk hen over skærmen med pulserende glød", "advancedEffectMatrix": "Matrix", "advancedEffectMatrixDescription": "Faldende grønne tegnsøjler med underteksttekst bygger bogstav for bogstav fra regnen", - "advancedEffectAudioBars": "Lydbjælker (bølgeform)", - "advancedEffectAudioBarsDescription": "Animerede equalizerbjælker drevet af lydbølgeformen - søjler ruller til venstre som en bølgeform og pulserer med lyden", "advancedEffectAudioPulse": "Lyd tekst puls", "advancedEffectAudioPulseDescription": "Underteksttekst lyser og pulserer med lydstyrken - stille sektioner viser normal tekst, høje peaks tilføjer en farvet glorie og en lille opskalering", "advancedEffectConfetti": "Konfettieksplosion", @@ -4097,7 +4543,6 @@ "advancedEffectFancyKaraokeGlow": "Aktiv ord glød", "advancedEffectFancyKaraokeActiveColor": "Aktiv farve", "advancedEffectFancyKaraokeInactiveColor": "Inaktiv farve", - "advancedEffectFancyKaraokeInactiveOpacity": "Inaktiv opacitet (0-255)", "advancedEffectWordSpacingPixels": "Mellemrum (pixels)", "advancedEffectSlowZoomOut": "Langsom zoom ud", "advancedEffectSlowZoomOutDescription": "Tekst starter lidt større og krymper forsigtigt til normal størrelse i løbet af underteksternes varighed", @@ -4113,7 +4558,6 @@ "advancedEffectSlideInLeftDescription": "Tekst glider ind fra venstre uden for skærmen, holder og afsluttes derefter tilbage til venstre", "advancedEffectSlideInRight": "Skub ind fra højre", "advancedEffectSlideInRightDescription": "Tekst glider ind fra højre uden for skærmen, holder og afsluttes derefter tilbage til højre", - "advancedEffectFadeInOut": "Fade ind/ud", "advancedEffectFadeInOutDescription": "Tekst toner ind i starten og fader ud i slutningen af ​​hver undertekst", "advancedEffectWordFlip3D": "Ordvending i 3D", "advancedEffectWordFlip3DDescription": "Hvert ord folder sig ind med en 3D-vending og et elastisk pop, mens det siges — moderne 'Shorts'-undertekststil", @@ -4123,9 +4567,7 @@ "advancedEffectLowerThirdDescription": "Navne- og rollebanner nederst til venstre med en accentbjælke, der glider ind — interview-/dokumentarstil (første linje = navn, følgende linjer = rolle)", "advancedEffectLowerThirdAccentColor": "Accentfarve", "overrideTagsHistory": "ASSA tags historik", - "category": "Kategori", "allCategories": "[Alle]", - "defaultCategory": "Standard", "newCategory": "Ny kategori...", "renameCategory": "Omdøb kategori...", "deleteCategory": "Slet kategori", diff --git a/src/ui/Assets/Languages/Dutch.json b/src/ui/Assets/Languages/Dutch.json index 53f630c76fb..6e7bed3d842 100644 --- a/src/ui/Assets/Languages/Dutch.json +++ b/src/ui/Assets/Languages/Dutch.json @@ -2,7 +2,7 @@ "Title": "Subtitle Edit", "TranslatedBy": "", "CultureName": "nl-NL", - "version": "v5.3.0-beta12", + "version": "v5.3.0", "General": { "AssistedMove": "Begeleid verplaatsen", "AssistedMoveChooseMove": "Klik op een optie of druk op het nummer ervan om woorden te verplaatsen", @@ -77,6 +77,18 @@ "ActorPickerKeysHint": "Druk op een cijfer of Enter om de acteur in te stellen. Alt+Omhoog/Omlaag wijzigt de volgorde, Delete verwijdert de acteur.", "ActorPickerNewActorX": "Nieuwe acteur \"{0}\"", "ActorPickerLinesSelectedX": "Geselecteerde rijen: {0}", + "SetStyleDotDotDot": "Stijl instellen...", + "StylePickerTitle": "Stijl instellen", + "StylePickerFilterHint": "Typ om te filteren, of typ een nieuwe stijlnaam", + "StylePickerKeysHint": "Druk op een cijfer (1-9, 0) om de stijl op alle geselecteerde rijen in te stellen.", + "StylePickerNewStyleX": "Nieuwe stijl \"{0}\"", + "StylePickerNewStyleInfo": "Druk op Enter om deze stijl met standaardinstellingen toe te voegen en op de geselecteerde rijen in te stellen. Gebruik \"Stijlen beheren...\" om later het lettertype, de kleuren en de positie te wijzigen.", + "StylePickerCurrentStyleX": "Huidige stijl: {0}", + "StylePickerLinesUsingStyleX": "Rijen met deze stijl: {0}", + "StylePickerMarginsXYZ": "Links {0}, rechts {1}, verticaal {2}", + "StylePickerSetStyle": "_Stijl instellen", + "StylePickerManageStyles": "Stijlen beheren...", + "StylePickerManageStylesHint": "Dit venster sluiten en de stijlbeheerder openen om lettertypen, kleuren, positie en meer te bewerken", "Add": "Toevoegen", "AddDotDotDot": "Toevoegen...", "AddToNamesListCaseSensitive": "Toevoegen aan namenlijst (hoofdlettergevoelig)", @@ -85,7 +97,6 @@ "AdjustAlpha": "Alpha aanpassen (transparantie)", "AdjustDisplayDuration": "Weergaveduur aanpassen", "Adjustment": "Aanpassing", - "Advanced": "Geavanceerd", "AdvancedSettings": "Geavanceerde instellingen", "After": "Na", "Alignment": "Uitlijning", @@ -121,7 +132,6 @@ "BatchMode": "Batchmodus", "BdnXml": "BDN/xml", "Before": "Voor", - "BeforeX": "Voor \"{0}\"", "Beginning": "Begin", "BluRaySup": "Blu-ray (sup)", "Bold": "Vet", @@ -133,7 +143,6 @@ "BookmarkEdit": "Bladwijzer bewerken", "BookmarkEditForSelectedLinesX": "Bladwijzer bewerken voor {0} geselecteerde rijen", "BookmarksList": "Bladwijzers tonen", - "BorderColor": "Randkleur", "BorderStyle": "Randstijl", "BottomCenter": "Onder-Midden", "BottomLeft": "Onder-Links", @@ -158,7 +167,6 @@ "ChangeCasing": "Hoofdlettergebruik wijzigen", "ChangeFormatting": "Opmaak wijzigen", "ChangeFrameRate": "Framerate wijzigen", - "ChangeLanguageFilter": "Taalfilter wijzigen...", "ChangeOnce": "Eén keer wijzigen", "ChangeSpeed": "Snelheid wijzigen", "Character": "Teken", @@ -183,7 +191,6 @@ "ConversionCancelledByUser": "Conversie geannuleerd door gebruiker", "Convert": "Converteren", "Converted": "Geconverteerd", - "ConvertingDotDotDot": "Converteren...", "ConvertingXofYDotDoDot": "Converteren van {0:#,###,##0}/{1:#,###,##0}...", "Copy": "Kopiëren", "CopyImageToClipboard": "Afbeelding naar klembord kopiëren", @@ -191,7 +198,6 @@ "CouldNotSaveFileXErrorY": "Kan bestand \"{0}\" niet opslaan. Fout: {1}", "Count": "Aantal", "Cps": "Kar/s", - "CurrentSubtitle": "Huidige ondertitel", "CurrentVideoPosition": "Huidige videopositie", "Custom": "Aangepast", "Cut": "Knippen", @@ -237,7 +243,6 @@ "Expand": "Uitvouwen", "Export": "Exporteren", "ExportDotDotDot": "Exporteren...", - "ExportToX": "Exporteren naar {0}", "ExtendAfter": "Verlengen/inkorten naar volgende rij", "ExtendBefore": "Verlengen/inkorten naar vorige rij", "ExtendSelectedLinesToNextShotChangeOrNextSubtitle": "Geselecteerde rijen verlengen naar volgende shot-wijziging (of ondertitel)", @@ -254,7 +259,6 @@ "FileExtension": "Bestandsextensie", "FileName": "Bestandsnaam", "FileNameX": "Bestandsnaam: {0}", - "FileNameXAndSize": "Bestandsnaam: {0} ({1})", "FileSaved": "Bestand opgeslagen", "FileSavedToX": "Bestand opgeslagen in {0}", "FileXAlreadyExists": "Bestand \"{0}\" bestaat al.", @@ -264,8 +268,6 @@ "Find": "Zoeken", "FindNext": "Volgende zoeken", "FindTextX": "Tekst zoeken - {0}", - "FiveHundredMilliseconds": "500 milliseconden", - "FiveSeconds": "Vijf seconden", "Fix": "Herstellen", "FixCommonErrors": "Veelvoorkomende fouten herstellen", "FixRightToLeft": "Rechts-naar-links herstellen", @@ -289,14 +291,12 @@ "FoundXInLineYZ": "\"{0}\" gevonden in rij {1}, positie {2}", "FoundXMatches": "{0} overeenkomsten gevonden", "FrameRate": "Framerate", - "FrameRateX": "Framerate: {0:0.0###}", "Frames": "Frames", "From": "Van", "FromCurrentVideoPosition": "vanaf huidige videopositie", "FullScreen": "Volledig scherm", "Gap": "Gat", "General": "Algemeen", - "GeneralText": "Algemeen", "Generate": "Genereren", "GenerateImportShotChanges": "Shot-wijzigingen genereren/importeren", "Generating": "Bezig met genereren...", @@ -314,12 +314,8 @@ "Help": "Help", "Hex": "Hex", "Hide": "Verberg", - "HidePreview": "Voorbeeld verbergen", "History": "Geschiedenis", "Horizontal": "Horizontaal", - "HourMinutesSecondsDecimalSeparatorMilliseconds": "Uur:min:sec{0}ms", - "HourMinutesSecondsFrames": "Uur:min:sec:frames", - "Ip": "IP", "Id": "ID", "Image": "Afbeelding", "ImageSaved": "Afbeelding opgeslagen", @@ -397,9 +393,7 @@ "Decrease": "Verlagen", "MoveUp": "Omhoog verplaatsen", "MultipleReplace": "Meervoudig vervangen", - "Mute": "Dempen", "Name": "Naam", - "Negative": "Negatief", "New": "Nieuw", "NewDotDotDot": "Nieuw...", "NewProfile": "Nieuw profiel", @@ -427,20 +421,15 @@ "OffsetY": "Y-verschuiving", "Ok": "_OK", "OneFile": "Eén bestand", - "OneHundredMilliseconds": "100 milliseconden", - "OneSecond": "Eén seconde", "OneSecondBack": "Eén seconde terug", "OneSecondForward": "Eén seconde vooruit", - "OnlineVideoFeatureNotAvailable": "Functie niet beschikbaar voor online video", "OpenContainingFolder": "Map openen", "OpenDictionaryFolder": "Woordenboekmap openen", "OpenFile": "Bestand openen", "OpenImageBasedSubtitle": "Op afbeeldingen gebaseerde ondertitel openen", "OpenImageFile": "Afbeeldingsbestand openen", - "OpenOriginalSubtitleFile": "Origineel ondertitelbestand openen...", "OpenOriginalSubtitleFileTitle": "Origineel ondertitelbestand openen", "OpenOutputFolder": "Uitvoermap openen", - "OpenSubtitle": "Ondertitel openen...", "OpenSubtitleFileTitle": "Ondertitelbestand openen", "OpenSubtitles": "Ondertitels openen...", "OpenVideoFile": "Videobestand openen...", @@ -453,11 +442,6 @@ "OutlineWidth": "Contourbreedte", "OutputFolder": "Uitvoermap", "OutputProperties": "Uitvoereigenschappen", - "Overlap": "Overlapping", - "OverlapNextX": "Overlap volgende ({0:#;##0.###})", - "OverlapPreviousLineX": "Overlap vorige rij ({0:#;##0.###})", - "OverlapStartAndEnd": "Overlap begin en eind", - "OverlapX": "Overlapping ({0:#;##0.###})", "OverwriteExistingFiles": "Bestaande bestanden overschrijven", "OverwriteFilesInFolderX": "Bestanden in map overschrijven: \"{0}\"", "OverwriteQuestion": "Overschrijven?", @@ -470,6 +454,7 @@ "Percent": "Procent", "PickLayer": "Laag instellen", "PickOllamaModel": "Kies Ollama-model", + "PickModel": "Model kiezen", "LlamaCppUseRemoteServer": "Externe server gebruiken (URL)", "PickOutputFolder": "Kies uitvoermap", "PickResolutionFromCurrentVideo": "Resolutie van huidige video overnemen", @@ -513,7 +498,6 @@ "Refresh": "Vernieuwen", "Region": "Regio", "RegularExpression": "Reguliere expressie", - "RegularExpressionIsNotValid": "Reguliere expressie is niet geldig!", "RelativeToCurrentVideoPosition": "Relatief aan huidige videopositie", "Remove": "Verwijderen", "RemoveAlignment": "Uitlijning verwijderen", @@ -538,7 +522,7 @@ "Reset": "Resetten", "ResizePanels": "Panelen vergroten/verkleinen", "Resolution": "Resolutie", - "ResolutionX": "Resolutie: {0}", + "Retry": "Opnieuw proberen", "ReverseRightToLeftStartEnd": "RTL begin/eind omkeren", "Right": "Rechts", "RightToLeft": "Rechts naar links", @@ -576,8 +560,6 @@ "SelectedlinesX": "Geselecteerde rijen: {0}", "Sensitivity": "Gevoeligheid", "Separator": "Scheidingsteken", - "SessionKey": "Sessiesleutel", - "SessionKeyGenerate": "Nieuwe sleutel genereren", "SetEnd": "Eindtijd instellen", "SetEndAndGoToNext": "Eind instellen en naar volgende", "SetEndAddNewAndGoToNew": "Eindtijd instellen, nieuwe toevoegen en naar nieuwe gaan", @@ -600,8 +582,9 @@ "ShadowColor": "Schaduwkleur", "ShadowWidth": "Schaduwbreedte", "ShiftLineBy": "Rij verschuiven met", + "ShotIn": "Shot in", + "ShotOut": "Shot uit", "Shortcut": "Sneltoets", - "ShortcutX": "Sneltoets: {0}", "Shortcuts": "Sneltoetsen", "Show": "Toon", "ShowActorColumn": "Kolom \"Acteur\" tonen", @@ -612,17 +595,19 @@ "ShowStartColumn": "Kolom \"Begin\" tonen", "ShowHideColumn": "Kolom \"Verberg\" tonen", "ShowHistory": "Geschiedenis tonen", + "ClearHistory": "Geschiedenis wissen", + "RemoveFromHistory": "Uit geschiedenis verwijderen", "ShowLayerColumn": "Kolom \"Laag\" tonen", "ShowPreview": "Voorbeeld tonen", "ShowShotChangesList": "Shot-wijzigingenlijst tonen", + "ShowShotInColumn": "Kolom \"Shot in\" tonen", + "ShowShotOutColumn": "Kolom \"Shot uit\" tonen", "ShowStyleColumn": "Kolom \"Stijl\" tonen", "ShowTeletext": "Teletekstkolom tonen in lijstweergave", "ShowTimeCodes": "Tijdcodes tonen", "ShowWpmColumn": "Kolom \"W/min\" tonen", "ShowPixelWidthColumn": "Kolom \"Pixelbreedte\" tonen", "Shrink": "Verkleinen", - "SingleBox": "Enkel kader", - "SingleLineLengths": "Lengte enkele regel:", "SingleMode": "Enkele modus", "TextTotalLength": "Tekst - totale lengte", "TextSingleLineMaxLength": "Tekst - max. lengte enkele regel", @@ -671,16 +656,16 @@ "Strikeout": "Doorhalen", "Style": "Stijl", "StyleExaggeration": "Stijloverdrijving", - "StyleLanguage": "Stijl / Taal", "Styles": "Stijlen", "SubtitleFile": "Ondertitelbestand", + "SubtitleFileChangedOutsideX": "Het bestand \"{0}\" is buiten Subtitle Edit gewijzigd.\n\nOpnieuw laden en uw wijzigingen verliezen?", "SubtitleFileName": "Bestandsnaam ondertitel", "SubtitleFileSaved": "Ondertitelbestand opgeslagen", "SubtitleFileSavedToX": "Ondertitelbestand opgeslagen in {0}", "SubtitleFiles": "Ondertitelbestanden", "SubtitleFormats": "Ondertitelformaten", "SubtitleLoadedX": "Ondertitel geladen: {0}", - "SubtitleXOfY": "Ondertitel {0} van {1}", + "SubtitleReloadedChangedOutsideX": "Ondertitel opnieuw geladen (buiten Subtitle Edit gewijzigd): {0}", "Suffix": "Achtervoegsel", "Suggestions": "Suggesties", "Sync": "Synchroniseren", @@ -692,7 +677,6 @@ "TeletextColor": "Teletekstkleur", "TeletextLine": "Teletekstregel", "TenHours": "10 uur", - "TenMilliseconds": "10 milliseconden", "Text": "Tekst", "TextColor": "Tekstkleur", "TextFiles": "Tekstbestanden", @@ -719,9 +703,6 @@ "TopRight": "Boven-Rechts", "Top": "Boven", "TotalAdjustmentX": "Totale aanpassing: {0}", - "TotalFramesX": "Totaal aantal frames: {0:#;##0.##}", - "TotalLengthX": "Totale lengte: {0}", - "TotalLengthXSplitLine": "Totale lengte: {0} (rij splitsen!)", "Translate": "Vertalen", "TranslatePercentX": "Vertalen: {0}%", "TranslateRow": "Rij vertalen", @@ -750,13 +731,11 @@ "Usages": "Gebruik", "Use": "Gebruiken", "UseAlways": "Altijd gebruiken", - "UseLargerFontForThisWindow": "Groter lettertype gebruiken voor dit venster", "UseOnce": "Eenmalig gebruiken", "UseOutputFolder": "Uitvoermap gebruiken", "UseSourceFolder": "Bronmap gebruiken", "UseSourceResolution": "Bronresolutie gebruiken", "UserName": "Gebruikersnaam", - "UserNameAlreadyInUse": "Gebruikersnaam is al in gebruik", "Vertical": "Verticaal", "Video100MsBack": "Video, 100 milliseconden terug", "Video100MsForward": "Video, 100 milliseconden vooruit", @@ -770,14 +749,25 @@ "VideoCustom3ForwardX": "Video, aangepaste milliseconden ({0:#,###,##0}) vooruit, 3", "VideoCustom4BackX": "Video, aangepaste milliseconden ({0:#,###,##0}) achteruit, 4", "VideoCustom4ForwardX": "Video, aangepaste milliseconden ({0:#,###,##0}) vooruit, 4", - "VideoEncodingX": "Videocodering: {0}", + "VideoCustom1BackAndPauseX": "Video, aangepaste milliseconden ({0:#,###,##0}) terug en pauzeren, 1", + "VideoCustom1ForwardAndPauseX": "Video, aangepaste milliseconden ({0:#,###,##0}) vooruit en pauzeren, 1", + "VideoCustom2BackAndPauseX": "Video, aangepaste milliseconden ({0:#,###,##0}) terug en pauzeren, 2", + "VideoCustom2ForwardAndPauseX": "Video, aangepaste milliseconden ({0:#,###,##0}) vooruit en pauzeren, 2", + "VideoCustom3BackAndPauseX": "Video, aangepaste milliseconden ({0:#,###,##0}) terug en pauzeren, 3", + "VideoCustom3ForwardAndPauseX": "Video, aangepaste milliseconden ({0:#,###,##0}) vooruit en pauzeren, 3", + "VideoCustom4BackAndPauseX": "Video, aangepaste milliseconden ({0:#,###,##0}) terug en pauzeren, 4", + "VideoCustom4ForwardAndPauseX": "Video, aangepaste milliseconden ({0:#,###,##0}) vooruit en pauzeren, 4", "VideoExtension": "Videobestandsextensie", "VideoFile": "Videobestand", "VideoFileGenerated": "Videobestand gegenereerd", "VideoFileGeneratedX": "Videobestand gegenereerd: \"{0}\"", + "VideoFilesGenerated": "Videobestanden gegenereerd", + "XVideosGenerated": "{0} video's gegenereerd", + "XOfYVideosGenerated": "{0} van {1} video's gegenereerd", + "SavedInX": "Opgeslagen in {0}", + "SavedInXFolders": "Opgeslagen in {0} mappen", "VideoFiles": "Videobestanden", "VideoAndAudioFiles": "Video- en audiobestanden", - "VideoInformation": "Video-info", "VideoOffset": "Video-offset", "VideoOneFrameBack": "Video, één frame terug", "VideoOneFrameForward": "Video, één frame vooruit", @@ -788,7 +778,6 @@ "VideoPlayer": "Videospeler", "VideoPosition": "Videopositie", "VideoResolution": "Videoresolutie", - "ViewX": "{0} bekijken", "Visible": "Zichtbaar", "Voice": "Stem", "Volume": "Volume", @@ -797,9 +786,7 @@ "WaveformCenterOnVideoPosition": "Golfvorm centreren op videopositie", "WaveformPasteFromClipboard": "Plakken van klembord", "WaveformSpectrogram": "Golfvorm/spectrogram", - "WebServiceUrl": "Webservice URL", "Width": "Breedte", - "WindowPositionAndSize": "Vensterpositie en -grootte", "WordsPerMin": "W/min", "PixelWidth": "Pixelbreedte", "Wpm": "W/min", @@ -813,8 +800,6 @@ "NoBookmarksFound": "Geen bladwijzers meer gevonden", "XRequiresAnApiKey": "{0} vereist een API-sleutel", "XRequiresAValidUrl": "{0} vereist een geldige URL (bijv. \"http://...\" of \"https://...\")", - "XSeconds": "{0:0.0##} seconden", - "XSubtitles": "{0:#,###,##0} ondertitels", "Yes": "Ja", "ZeroBytes": "0 bytes", "CopyToClipboard": "Kopiëren naar klembord", @@ -836,10 +821,8 @@ "PlaybackSpeed": "Afspeelsnelheid", "HoursMinutesSecondsMilliseconds": "HH:MM:SS.FFF", "ImageFiles": "Afbeeldingsbestanden", - "OpenAiCompatibleStt": "OpenAI-compatibele STT", "OpenAiCompatibleSttEndpoint": "STT-eindpunt-URL", "OpenAiCompatibleSttApiKey": "API-sleutel", - "OpenAiCompatibleSttModel": "Model", "OpenAiCompatibleSttExtraHeaders": "Extra headers", "OpenAiCompatibleSttTimeout": "Time-out (seconden)", "OpenAiCompatibleSttLanguage": "Taalhint", @@ -861,11 +844,9 @@ "ConfigurationRequired": "Configuratie vereist", "TranscriptionError": "Transcriptiefout", "TranscriptionComplete": "Transcriptie voltooid", - "TranscribingWithOpenAi": "Transcriberen met OpenAI-service...", "ProcessingResponse": "Antwoord verwerken...", "UnauthorizedApiKey": "Niet geautoriseerd: ongeldige API-sleutel of onvoldoende rechten.", "RequestTimeout": "Time-out van aanvraag: de server deed er te lang over om te antwoorden.", - "NoSegmentsReturned": "Geen segmenten ontvangen van de transcriptieservice.", "OpenAiCompatibleSttUrlMissing": "De URL van het OpenAI-compatibele STT-eindpunt is vereist. Voer een URL in en probeer het opnieuw.", "OpenAiCompatibleSttUrlNotResponding": "Het OpenAI-compatibele STT-eindpunt reageerde niet:\n{0}\n\nControleer de URL en of de server actief is.", "TranscriptionFailed": "Transcriptie mislukt", @@ -881,12 +862,15 @@ "CharactersPerSecond": "Tekens/seconde: {0}", "ChooseColumn": "Kies kolom", "ColumnPaste": "Kolom plakken", + "CommandPaletteTitle": "Opdrachtpalet", + "CommandPaletteSearchPlaceholder": "Typ om opdrachten te zoeken...", + "CommandPaletteNoCommandsFound": "Geen overeenkomende opdrachten", "CreatedEmptyTranslation": "Lege vertaling gemaakt van de huidige ondertitel", "DeleteText": "Tekst verwijderen", "DeleteTextAndShiftCellsUp": "Verwijder tekst en schuif cellen omhoog", "EndTimeMustBeAfterStartTime": "De eindtijd moet na de starttijd liggen.", "ErrorLoad7Zip": "Dit bestand lijkt een gecomprimeerd 7-Zip-bestand te zijn.\r\n\r\n\r\nOndertitelbewerking kan geen gecomprimeerde bestanden openen.", - "ErrorLoadBinaryZeroes": "Sorry, dit bestand bevat alleen binaire nullen!\r\n\r\n\r\nAls u dit bestand heeft bewerkt met Subtitle Edit, kunt u mogelijk een back-up vinden via het menu-item Bestand -> Automatische back-up herstellen...", + "ErrorLoadBinaryZeroes": "Sorry, dit bestand bevat alleen binaire nullen!\n\nAls u dit bestand heeft bewerkt met Subtitle Edit, kunt u mogelijk een back-up vinden via het menu-item Bestand -> Automatische back-up herstellen...", "ErrorLoadGZip": "Dit bestand lijkt een gecomprimeerd GZip-bestand te zijn.\r\n\r\n\r\nOndertitelbewerking kan geen gecomprimeerde bestanden openen.", "ErrorLoadLargeFile": "Dit bestand is te groot om als ondertitelbestand te openen.", "ErrorLoadVideoFilePrompt": "Dit bestand lijkt een videobestand te zijn:\n{0}\n\nU kunt een video openen via \"Video -> Video openen\" of door een video naar de videospeler te slepen.\n\nWilt u het als videobestand openen?", @@ -926,13 +910,13 @@ "OverwriteExistingCells": "Bestaande cellen overschrijven", "OverwriteOrShiftCellsDown": "Cellen overschrijven/naar beneden verschuiven", "ParsingMatroskaFile": "Matroska-bestand parseren...", + "ReadingDvdSubtitles": "DVD-ondertitels lezen...", "PasteFromClipboardDotDotDot": "Plakken vanaf klembord...", "RedoPerformed": "Opnieuw uitgevoerd", "RedoPerformedXActionLeft": "Opnieuw uitgevoerd (resterende acties: {0})", "RemovedUnicodeControlCharactersX": "Unicode-besturingstekens verwijderd uit {0} rijen", "RemovedXBlankLines": "{0} lege rijen verwijderd", "ReplacedXWithYCountZ": "Vervangen \"{0}\" door \"{1}\" ({2} exemplaren)", - "ReplacedXWithYInLineZ": "'{0}' vervangen door '{1}' in rij {2}", "ReversedStartAndEndingsForRightToLeftX": "Omgekeerde start en einde voor rechts naar links in {0} rijen", "RuleProfileIsX": "Regelprofiel is nu '{0}'", "SaveLanguageFile": "Taalbestand opslaan", @@ -940,7 +924,6 @@ "ShiftTextCellsDown": "Verplaats tekstcellen naar beneden", "SingleLineLength": "Regellengte:", "SpeedIsNowX": "Snelheid is nu \"{0}", - "SpellCheckResult": "Spellingcontrole voltooid.\r\n\r\n\r\n• Gewijzigde woorden: {0}\r\n\r\n• Overgeslagen woorden: {1}", "SubtitleGridFormattingX": "Roosteropmaak: {0}", "SubtitleImportedFromMatroskaFile": "Ondertitel geïmporteerd uit Matroska-bestand", "TextDown": "Tekst naar beneden", @@ -967,13 +950,14 @@ "TrimmedXLines": "{0} rijen bijgesneden", "PastedXLinesOverSelectedLines": "{0} rij(en) over de geselecteerde rijen geplakt", "PastedXOfYLinesOverSelectedLines": "{0} van {1} rij(en) over de geselecteerde rijen geplakt - de rest paste niet in de selectie", + "InsertedXLinesFromClipboard": "{0} rij(en) vanaf het klembord ingevoegd", "OpenOriginalDifferentNumberOfSubtitlesXY": "Het originele ondertitelbestand heeft niet hetzelfde aantal ondertitels als het huidige ondertitelbestand.\r\n\r\n\r\n• Originele ondertitels: {0}\r\n\r\n• Huidige ondertitels: {1}", - "ImportXMatchingOriginalLines": "{0} overeenkomende originele ondertitels importeren?", "OriginalTextReadOnly": "Originele tekst (alleen-lezen)", "OriginalTextEditMode": "Originele tekst (bewerkmodus)", "OriginalIsReadOnlyNotSaved": "De originele ondertitel is een alleen-lezen referentie en is niet opgeslagen", "OriginalIsEmptyNotSaved": "De originele ondertitel bevat helemaal geen tekst en is niet opgeslagen", "OriginalIsReadOnlyReference": "De originele ondertitel is geopend als alleen-lezen referentie", + "CloseTranslationQuestionXY": "De vertaling \"{0}\" sluiten?\n\nDe originele ondertitel \"{1}\" wordt de ondertitel die u bewerkt. Het vertaalbestand zelf wordt niet gewijzigd.", "AllowEditOfOriginalSubtitle": "Bewerken van originele ondertitel toestaan", "ShowAllOriginalLinesX": "Alle originele rijen tonen ({0} hebben hier geen overeenkomst)", "ShowAllOriginalLinesHint": "De rijen zonder overeenkomst worden als extra rijen getoond, zodat u ziet wat er in de vertaling ontbreekt.", @@ -1008,6 +992,7 @@ "FileExportedInFormatXToY": "Bestand geëxporteerd in formaat {0} naar {1}", "FileExportedInFormatXToFileY": "Bestand geëxporteerd in formaat \"{0}\" naar bestand \"{1}", "FixedXLines": "{0} rijen hersteld", + "ToolXChangedYLines": "{0}: {1} rijen gewijzigd", "TranscriptionCompletedWithXLines": "Transcriptie voltooid met {0} rijen", "OpenSubtitleFileX": "Ondertitelbestand \"{0}\" openen?", "ReplacedXOccurrences": "{0} exemplaren vervangen", @@ -1071,10 +1056,10 @@ "MultipleReplace": "_Meervoudig vervangen...", "FillSelectedLinesWithClipboard": "Geselecteerde rijen vullen met klembordtekst", "GoToLineNumber": "Ga naar _rijnummer...", + "CommandPalette": "Opdracht_palet...", "RightToLeftMode": "_Rechts-naar-links modus (RTL)", "ModifySelectionDotDotDot": "_Selectie aanpassen...", "Tools": "_Gereedschappen", - "ToolsSelectedLines": "Gereedschappen (_geselecteerde rijen)", "AdjustDurations": "_Duur aanpassen...", "ApplyDurationLimits": "Duurgrenzen _toepassen...", "BatchConvert": "_Batch-conversie...", @@ -1291,20 +1276,66 @@ "CompareOnlyInOneFile": "Alleen in één bestand", "CompareTextOrTimeDifference": "Tekst-/tijdverschil", "CompareNumberDifference": "Nummerverschil", - "IgnoreLineBreaks": "Regeleinden negeren", "IgnoreWhitespace": "Witruimte negeren", "IgnoreWhitespaceHint": "Rijen die alleen verschillen in spaties, tabs of regeleinden tellen niet als verschillend", "IgnoreFormatting": "Opmaak negeren", - "OnlyLookForDifferencesInText": "Alleen naar verschillen in tekst zoeken", - "CannotCompareWithImageBasedSubtitles": "Kan niet vergelijken met afbeeldingsgebaseerde ondertitels", - "StatisticsTitle": "Statistieken", "IgnoreFormattingHint": "Rijen die alleen verschillen in opmaaktags, zoals of {\\an8}, tellen niet als verschillend", "ShowOnlyDifferencesInText": "Alleen verschillen in tekst", + "IgnoreNumbering": "Nummering negeren", + "IgnoreNumberingHint": "Rijen die alleen in hun nummer verschillen, tellen niet als verschillend", + "IgnoreTimeDifferenceMs": "Tijdtolerantie (ms)", + "IgnoreTimeDifferenceMsHint": "Begin- en eindtijden die dit aantal milliseconden of minder verschillen, tellen niet als verschillend", + "CompareDifferences": "Verschillen", + "CompareTextDifferences": "Tekstverschillen", + "CompareCurrent": "Huidig", + "CompareReference": "Referentie", + "CompareEditable": "Bewerkbaar", + "CompareEditableHint": "Wijzigingen die hier worden gemaakt, gaan terug naar de ondertitel wanneer u op Toepassen klikt", + "CompareReadOnly": "Alleen-lezen", + "CompareTakeFromReference": "Tekst en timing overnemen van de referentie", + "CompareInsertFromReference": "Deze rij in de huidige ondertitel invoegen", + "CompareDeleteFromCurrent": "Deze rij uit de huidige ondertitel verwijderen", + "CompareTakeText": "Tekst overnemen", + "CompareTakeTiming": "Timing overnemen", + "CompareEditLine": "Rij bewerken", + "CompareEditHint": "Ctrl+Enter om op te slaan, Esc om te annuleren", + "CompareEdited": "Bewerkt", + "CompareOnePendingChange": "1 openstaande wijziging", + "CompareXPendingChanges": "{0} openstaande wijzigingen", + "CompareDiscardXChanges": "De {0} wijziging(en) in Vergelijken verwerpen?", + "CompareXChangesApplied": "Vergelijken: {0} wijziging(en) toegepast", + "CompareChangeTextX": "#{0} tekst van referentie", + "CompareChangeTimingX": "#{0} timing van referentie", + "CompareChangeTextAndTimingX": "#{0} tekst en timing van referentie", + "CompareChangeEditedX": "#{0} bewerkt", + "CompareChangeInsertedX": "#{0} ingevoegd", + "CompareChangeDeletedX": "#{0} verwijderd", + "CompareSyncPoint": "Synchronisatiepunt", + "CompareSyncPointHint": "Synchronisatiepunt - deze twee rijen worden altijd als paar getoond, en de rijen erboven en eronder worden afzonderlijk uitgelijnd", + "CompareSyncPickCurrent": "Synchronisatiepunt: deze huidige rij gebruiken", + "CompareSyncPickReference": "Synchronisatiepunt: deze referentierij gebruiken", + "CompareSyncRemove": "Synchronisatiepunt verwijderen", + "CompareClearXSyncPoints": "Synchronisatiepunten wissen ({0})", + "CompareSyncCurrentPickedX": "Huidige #{0} gekozen als synchronisatiepunt - selecteer de overeenkomende referentierij en klik op Synchroniseren", + "CompareSyncReferencePickedX": "Referentie #{0} gekozen als synchronisatiepunt - selecteer de overeenkomende huidige rij en klik op Synchroniseren", + "CompareSyncWithCurrentX": "Synchroniseren met huidige #{0}", + "CompareSyncWithReferenceX": "Synchroniseren met referentie #{0}", + "CompareSyncApply": "Synchroniseren", + "CompareSyncPointSetXY": "Synchronisatiepunt ingesteld: huidige #{0} en referentie #{1} worden nu als paar getoond", + "CompareSyncAlreadyPairedXY": "Huidige #{0} en referentie #{1} worden al als paar getoond met hetzelfde begin - niets te synchroniseren", + "CompareSyncPointShiftedXYZW": "Synchronisatiepunt ingesteld: huidige #{0}-#{1} {2} verschoven om te beginnen met referentie #{3}", + "CompareChangeSyncShiftXYZ": "#{0}-#{1} {2} verschoven (synchronisatiepunt)", + "CompareTimingDeltaHint": "Hoeveel later (+) of eerder (−) de referentie begint en eindigt dan de huidige rij", "LoadXFromFile": "\"{0}\" laden uit bestand", + "CompareLoadSavedFile": "Opgeslagen bestand laden", "SaveCompareHtmlTitle": "HTML-vergelijkingsbestand opslaan", "PickMatroskaTrackX": "Matroska-spoor kiezen - {0}", "PickTransportStreamTrackX": "Transportstream-track kiezen - {0}", + "Chapters": "Hoofdstukken", + "DvdVobFilesMissingX": "VOB-bestanden ontbreken ({0}% gevonden)", "PickMp4TrackX": "MP4-track kiezen - {0}", + "PickMxfTrackX": "MXF-spoor kiezen - {0}", + "PickMpegTrackX": "MPEG-spoor kiezen - {0}", "RosettaProperties": "Timed Text Rosetta IMSC-eigenschappen", "RosettaFontSize": "Tekengrootte (rijhoogte)", "PropertyTimeBase": "Tijdbasis", @@ -1314,7 +1345,6 @@ "PropertyDefaultRegion": "Standaardregio", "PropertyStyleAttributeName": "Naam van stijlattribuut", "PropertyTimeCodeFormat": "Tijdcodeformaat", - "PropertyFileExtension": "Bestandsextensie", "PropertyTopOrigin": "Bovenoorsprong", "PropertyTopExtent": "Bovenomvang", "PropertyBottomOrigin": "Onderoorsprong", @@ -1322,13 +1352,9 @@ "XProperties": "Eigenschappen van {0}", "EbuSaveOptions": { "Title": "EBU-opslagopties", - "GeneralSubtitleInformation": "Algemene ondertitelinformatie", "CodePageNumber": "Codepaginanummer", "DiskFormatCode": "Schijfformaatcode", "DisplayStandardCode": "Weergavestandaardcode", - "ColorRequiresTeletext": "Kleuren vereisen teletekst!", - "AlignmentRequiresTeletext": "Uitlijning vereist teletekst!", - "TeletextCharsShouldBe38": "'Max. aantal tekens per rij' moet 38 zijn voor teletekst!", "CharacterCodeTable": "Tekentabel", "LanguageCode": "Taalcode", "OriginalProgramTitle": "Oorspronkelijke programmatitel", @@ -1345,7 +1371,6 @@ "MaxNumberOfDisplayableRows": "Max. aantal rijen", "DiskSequenceNumber": "Volgnummer schijf", "TotalNumberOfDisks": "Totaal aantal schijven", - "Import": "Importeren...", "TextAndTimingInformation": "Tekst- en timinginformatie", "JustificationCode": "Uitvullingscode", "VerticalPosition": "Verticale positie", @@ -1362,11 +1387,6 @@ "Errors": "Fouten", "ErrorsX": "Fouten: {0}", "MaxLengthError": "Regel {0} overschrijdt de maximale lengte ({1}) met {2}: {3}", - "TextUnchangedPresentation": "Ongewijzigde presentatie", - "TextLeftJustifiedText": "Links uitgelijnde tekst", - "TextCenteredText": "Gecentreerde tekst", - "TextRightJustifiedText": "Rechts uitgelijnde tekst", - "UseBoxForOneNewLine": "Vink 'Kader rond tekst gebruiken' aan voor slechts één nieuwe regel", "MaxLengthErrorTeletextColored": "Regel {0}-{1}: 36 (niet {2}) moet het maximum aantal tekens zijn voor gekleurde tekst met dubbele hoogte", "MaxLengthErrorTeletextWhite": "Regel {0}-{1}: 37 (niet {2}) moet het maximum aantal tekens zijn voor witte tekst met dubbele hoogte", "MaxLengthErrorTeletextSingleHeight": "Regel {0}: 38 (niet {1}) moet het maximum aantal tekens zijn voor witte tekst met enkele hoogte", @@ -1391,22 +1411,29 @@ "FormattingDotDotDot": "Opmaak...", "ImageBasedSubtitleForEditDotDotDot": "Afbeeldingsgebaseerde ondertitel om te bewerken...", "ImageBasedSubtitleForOcrDotDotDot": "Afbeeldingsgebaseerde ondertitel voor OCR...", + "DvdSubtitlesDotDotDot": "Ondertitels van DVD (IFO/VOB)...", + "TitleImportDvd": "Ondertitels van DVD importeren", + "DvdIfoFile": "IFO-bestand", + "DvdVobFiles": "VOB-bestanden", + "DvdVideoStandard": "Videostandaard", + "DvdStartRipping": "Rippen starten", + "DvdRippingAborted": "Rippen afgebroken", + "DvdOpenIfoFile": "DVD IFO-bestand openen", + "DvdAddVobFiles": "VOB-bestanden toevoegen", + "DvdIfoFiles": "DVD IFO-bestanden", + "DvdVobFileType": "DVD VOB-bestanden", + "DvdDropHint": "Open een IFO-bestand (VIDEO_TS\\VTS_01_0.IFO) of voeg VOB-bestanden toe - of sleep ze hierheen", + "DvdNotAnIfoFileX": "{0} is geen DVD IFO-bestand", + "DvdMenuIfoNoTitleSetsX": "Geen titelsets (VTS_xx_0.IFO) gevonden naast {0}", + "DvdEncryptedXOfY": "{0} van de {1} ondertitelpakketten zijn nog CSS-versleuteld - deze VOB-bestanden zijn gekopieerd zonder ontsleuteling, dus sommige ondertitelafbeeldingen zullen vervormd zijn.\n\nOntsleutel de DVD eerst met een DVD-ripprogramma voor een schoon resultaat.\n\nToch doorgaan?", "SplitTextAt": "Tekst splitsen bij", "BlankLines": "Lege regels", "OneLineIsOneSubtitle": "Eén regel is één ondertitel", "TwoLinesAreOneSubtitle": "Twee regels zijn één ondertitel", "ImportFilesDotDotDot": "Bestanden importeren...", "MultipleFiles": "Importeren uit meerdere tekstbestanden (één bestand is één ondertitel)", - "ImportOptions": "Importopties", - "AutoSplitText": "Tekst automatisch splitsen", - "LineMode": "Regelmodus", "MaxLineLength": "Max. regellengte", - "MaxLinesPerSubtitle": "Max. regels per ondertitel", - "MinGapBetweenSubtitles": "Min. tussenruimte tussen ondertitels", "MergeShortLines": "Korte regels samenvoegen", - "RemoveLinesWithoutLetters": "Regels zonder letters verwijderen", - "SplitAtEndCharsSetting": "Splitsen bij eindtekens", - "TakeTimeFromCurrentFile": "Tijd uit huidig bestand overnemen", "Fixed": "Vast", "NumberOfSubtitlesX": "Aantal ondertitels: {0}", "NumberOfSubtitlesXForcedY": "Aantal ondertitels: {0} ({1} geforceerd)", @@ -1423,13 +1450,13 @@ "ForcedAlignerIsolatingSpeech": "Spraak isoleren... {0}", "ForcedAlignerIsolatingSpeechFailed": "Kan de spraak niet isoleren - eindtijden zijn in plaats daarvan ingesteld op basis van leestijd.", "ForcedAlignerModel": "Aligner-model", - "ForcedAlignerDownloadEngine": "Engine downloaden/bijwerken..." + "ForcedAlignerDownloadEngine": "Engine downloaden/bijwerken...", + "ForcedAlignerEstimatedXOfY": "{0} van {1} rijen konden niet worden uitgelijnd en zijn geplaatst op basis van leestijd - hun tijdcodes zijn slechts geschat. Het script is waarschijnlijk langer dan wat er in de video wordt gesproken." }, "Export": { "ExportImagesProfiles": "Profielen voor afbeeldingsexport", "LeftRightMargin": "Marge links/rechts", "TopBottomMargin": "Marge boven/onder", - "TitleExportBluRaySup": "Blu-ray exporteren (sup)", "LineSpacingPercent": "Regelafstand %", "PaddingLeftRight": "Opvulling links/rechts", "PaddingTopBottom": "Opvulling boven/onder", @@ -1470,7 +1497,6 @@ "TitleExportDCinemaInteropPng": "D-Cinema interop/png", "TitleExportDCinemaSmpte2014Png": "D-Cinema SMPTE 2014/png", "ImageBasedSubtitleSaved": "Afbeeldingsgebaseerde ondertitel opgeslagen", - "TitleExportBdnXml": "BDN/xml exporteren", "TitleExportWebVttThumbnails": "WebVTT exporteren (miniaturen)", "FullFrameImage": "Afbeelding op volledig frame", "FullFrameImageHint": "Maak elke afbeelding even groot als het videoframe, met de ondertitel op zijn plaats", @@ -1517,13 +1543,10 @@ "Statistics": { "Title": "Statistieken", "TitleWithFileName": "Statistieken - {0}", - "GeneralStatistics": "Algemene statistieken", - "MostUsed": "Meest gebruikt...", "MostUsedLines": "Meest gebruikte regels", "MostUsedWords": "Meest gebruikte woorden", "NothingFound": "Niets gevonden", "NumberOfLinesX": "Aantal ondertitels: {0:#,##0}", - "NumberOfFilesX": "Aantal ondertitelbestanden: {0:#,##0}", "LengthInFormatXinCharactersY": "Aantal tekens als {0}: {1:#,###,##0}", "NumberOfCharactersInTextOnly": "Aantal tekens in alleen tekst: {0:#,###,##0}", "TotalDuration": "Totale duur van alle ondertitels: {0:#,##0}", @@ -1564,7 +1587,6 @@ "GapMaximum": "Tussenruimte - maximum: {0:#,##0} ms", "GapAverage": "Tussenruimte - gemiddeld: {0:#,##0.##} ms", "GapExceedingMinimum": "Tussenruimte - onder minimum ({0:#,##0} ms): {1} ({2:0.00}%)", - "Export": "Exporteren...", "Subtitles": "Ondertitels", "Words": "Woorden", "TotalDurationShort": "Totale duur", @@ -1623,22 +1645,16 @@ "EditRate": "Bewerkingssnelheid", "TimeCodeRate": "Tijdcodesnelheid", "StartTime": "Begintijd", - "Font": "Lettertype", "FontId": "Lettertype-ID", "FontUri": "Lettertype-URI", - "Generate": "Genereren", - "FontColor": "Tekstkleur", "ChooseColor": "Kleur kiezen", "FontEffect": "Lettertype-effect", "EffectColor": "Effectkleur", - "FontSize": "Lettergrootte", "TopBottomMargin": "Marge boven/onder", "FadeUpTime": "Tijd voor infaden", "FadeDownTime": "Tijd voor uitfaden", - "Frames": "Frames", "ZPosition": "Z-positie", - "ZPositionHelp": "Positieve getallen verplaatsen de tekst naar achteren, negatieve getallen naar voren; als de z-positie nul is, is het 2D", - "Export": "Exporteren..." + "ZPositionHelp": "Positieve getallen verplaatsen de tekst naar achteren, negatieve getallen naar voren; als de z-positie nul is, is het 2D" } }, "Edit": { @@ -1701,7 +1717,6 @@ "SelectionIntersect": "Snijden met selectie" }, "MultipleReplace": { - "Title": "Meervoudig vervangen", "EditRule": "Regel bewerken", "NewRule": "Nieuwe regel", "EditCategory": "Categorie bewerken", @@ -1718,19 +1733,16 @@ "FindWhat": "Zoeken naar", "DescriptionOptional": "Omschrijving (optioneel)", "InvalidRegularExpressionX": "Ongeldige reguliere expressie: {0}", - "RegularExpressionTooSlowX": "Reguliere expressie is na {0} seconden gestopt - de regel wordt overgeslagen, probeer een eenvoudiger patroon" + "RegularExpressionTooSlowX": "Reguliere expressie is na {0} seconden gestopt - de regel wordt overgeslagen, probeer een eenvoudiger patroon", + "MoveToCategory": "Naar categorie verplaatsen" }, "Find": { "SearchTextWatermark": "Tekst zoeken...", "WholeWord": "Heel woord", - "CaseSensitive": "Hoofdlettergevoelig", - "CaseInsensitive": "Hoofdletterongevoelig", "FindPrevious": "Zoek _vorige", "FindNext": "_Zoek volgende", "ReplaceAndFindNext": "_Vervangen en volgende vinden", "ReplaceAll": "Vervang _alles", - "ReplaceTitle": "Vervangen", - "ReplaceWith": "Vervangen door", "ReplaceTextWatermark": "Vervangen door...", "ReplaceIn": "Vervangen/zoeken in", "ReplaceInTextAndOriginal": "Tekst en originele tekst", @@ -1763,7 +1775,6 @@ "AiReview": { "Title": "AI-review", "Review": "Review", - "Stop": "Stop", "EditPrompt": "Prompt bewerken...", "EditPromptTitle": "Reviewprompt bewerken", "PromptInfo": "Instructies die naar het model worden gestuurd - {language} wordt vervangen door de ondertiteltaal.", @@ -1774,7 +1785,6 @@ "ReviewingLineXOfY": "Bezig met reviewen van rij {0} van {1}...", "ReviewDone": "Review voltooid - {0} suggesties in {1} rijen", "NoIssuesFound": "Geen problemen gevonden", - "CategoryAll": "Alle", "CategorySpelling": "Spelling", "CategoryGrammar": "Grammatica", "CategoryPunctuation": "Interpunctie", @@ -1786,9 +1796,23 @@ "LargeChangeWarning": "Grote wijziging - lijkt op een herschrijving, controleer zorgvuldig", "MismatchWarning": "Lijkt niet op het origineel - hoort mogelijk bij een andere rij", "EngineError": "De AI-engine kon niet worden bereikt: {0}", - "SetupHint": "Kies eerst een engine en model via Gereedschappen -> AI-review.", "PlayCurrentLineHint": "Speel de geselecteerde rij af in de videospeler en pauzeer aan het einde ervan (of dubbelklik op een rij)", - "EditAfterHint": "Klik op de tekst van de geselecteerde rij om de correctie te bewerken voordat u die toepast (Enter slaat op, Escape annuleert)" + "EditAfterHint": "Klik op de tekst van de geselecteerde rij om de correctie te bewerken voordat u die toepast (Enter slaat op, Escape annuleert)", + "Template": "Sjabloon", + "TemplatePlaceholder": "Kies een sjabloon om de prompt te vervangen", + "TemplateStandard": "Standaard proeflezen", + "TemplateSpeechToText": "Opschonen na spraak naar tekst", + "TemplateMachineTranslation": "Machinevertaling corrigeren", + "TemplateSdh": "SDH / slechthorenden", + "ReferenceContext": "Referentiecontext", + "ReferenceContextInfo": "Optioneel. Namen, personages, woordenlijst, synopsis of stijlnotities - het model beschouwt dit als de autoriteit voor namen en termen. Wordt met elke batch meegestuurd.", + "ReferenceContextPlaceholder": "Namen: Kaito, tante Mei, Dr. Hollis\nTermen: Shinkansen, warpkern\nSynopsis: Kaito reist naar Tokio om zijn vermiste vader te vinden.", + "GenerateContext": "Genereren met AI", + "GenerateContextHint": "Laat de geselecteerde AI-engine de ondertitel lezen en namen, termen en een synopsis opstellen - controleer het resultaat voor gebruik", + "GeneratingContextPartXOfY": "Deel {0} van {1} lezen...", + "GeneratingContextSynopsis": "Synopsis schrijven...", + "ReplaceContextQuestion": "De huidige referentiecontext vervangen?", + "NoContextGenerated": "De AI-engine gaf geen bruikbare referentie-informatie terug." }, "AiAssistant": { "Title": "AI-assistent", @@ -1805,7 +1829,6 @@ "Hint": "AI-assistent voor deze rij", "ShowReasoning": "Redenering van het model tonen" }, - "PickAlignmentTitle": "Kies uitlijning", "RemoveUnicodeCharacters": { "Title": "Unicode-tekens verwijderen/vervangen", "CharactersFoundX": "Gevonden Unicode-tekens: {0}", @@ -1815,31 +1838,23 @@ "PickFontNameInstalledFonts": "Geïnstalleerde lettertypen", "PickFontNameCollectedFonts": "Verzamelde lettertypen", "ColorPickerTitle": "Kleur kiezen", + "ColorPickerBrightness": "Helderheid", "FilterLayersTitle": "Lagen filteren voor weergave", "FilterLayersHideFromWaveform": "Verbergen in golfvorm/spectrogram", "FilterLayersHideFromSubtitleGrid": "Verbergen in ondertitelraster", "FilterLayersHideFromVideoPreview": "Verbergen in videovoorbeeld", "PickSubtitleFormat": "Ondertitelformaat kiezen", "PickSubtitleFormatImageBasedNoPreview": "Op afbeeldingen gebaseerd ondertitelformaat.\nBitmaps worden tijdens de conversie gegenereerd - geen tekstvoorbeeld.", - "PickLayerTitle": "Laag instellen", "RecentColors": "Recente kleuren", "FixCommonErrors": { - "Title": "Veelvoorkomende fouten herstellen", - "Step1": "Stap 1/2 - Kies welke fouten hersteld moeten worden", - "WhatToFix": "Wat herstellen", - "Example": "Voorbeeld", - "SelectAll": "Alles selecteren", - "InverseSelection": "Selectie omkeren", "Back": "< &Terug", "Next": "&Volgende >", - "Step2": "Stap 2/2 - Correcties controleren", "Fixes": "Correcties", "RuleDetailsDotDotDot": "Regeldetails...", "ShowOnlyThisRule": "Alleen correcties van deze regel tonen", "AppliedRulesForLineX": "Toegepaste regel(s) voor rij {0}", "RuleX": "Regel: {0}", "Log": "Logboek", - "Function": "Functie", "RemovedEmptyLine": "Lege regel verwijderen", "RemovedEmptyLineAtTop": "Lege regel bovenaan verwijderen", "RemovedEmptyLineAtBottom": "Lege regel onderaan verwijderen", @@ -1860,13 +1875,13 @@ "RemoveLineBreaksPixelWidth": "Ondertitels die op één regel passen samenvoegen (pixelbreedte)", "FixUppercaseIInsideLowercaseWords": "Hoofdletter 'i' in kleine-letterwoorden herstellen (OCR-fout)", "FixDoubleApostrophes": "Dubbele apostroffen ('') vervangen door een dubbel aanhalingsteken (\")", + "FixMisreadQuotes": "Apostrofs die als dubbele aanhalingstekens zijn gelezen herstellen (OCR, Engels)", "AddPeriods": "Punt toevoegen na rijen waarvan de volgende rij met een hoofdletter begint", "StartWithUppercaseLetterAfterParagraph": "Met hoofdletter beginnen na alinea", "StartWithUppercaseLetterAfterPeriodInsideParagraph": "Met hoofdletter beginnen na punt binnen alinea", "StartWithUppercaseLetterAfterColon": "Met hoofdletter beginnen na dubbele punt/puntkomma", "FixLowercaseIToUppercaseI": "Losstaande kleine letter 'i' naar 'I' herstellen (Engels)", "FixCommonOcrErrors": "Veelvoorkomende OCR-fouten herstellen (met OCR-vervanglijst)", - "CommonOcrErrorsFixed": "Veelvoorkomende OCR-fouten hersteld (OcrReplaceList-bestand gebruikt): {0}", "RemoveSpaceBetweenNumber": "Spatie tussen getallen verwijderen", "BreakDialogsOnOneLine": "Dialogen op één regel splitsen", "RemoveDialogFirstInNonDialogs": "Beginstreepje in de eerste regel verwijderen bij niet-dialogen", @@ -1883,18 +1898,17 @@ "FixCommasExample": ",, -> ,", "RemoveLineBreaksExample": "Foo
bar! -> Foo bar!", "FixDoubleApostrophesExample": "''Has double single quotes'' -> \"Has single double quote\"", + "FixMisreadQuotesExample": "\"Hello' -> \"Hello\"", "AddPeriodsExample": "Hello world -> Hello world.", "StartWithUppercaseLetterAfterParagraphExample": "p1: Foobar! || p2: foobar! -> p1: Foobar! || p2: Foobar!", "StartWithUppercaseLetterAfterPeriodInsideParagraphExample": "Hello there! how are you? -> Hello there! How are you?", "StartWithUppercaseLetterAfterColonExample": "Speaker: hello world! -> Speaker: Hello world!", "RemoveHyphensSingleLineExample": "- Foobar. -> Foobar.", "Fix3PlusLinesExample": "Foo
bar
baz! -> Foo bar baz!", - "XMissingQuotesAdded": "Ontbrekende aanhalingstekens toegevoegd: {0}", "Fix3PlusLine": "Ondertitel met meer dan twee regels herstellen", "Fix3PlusLines": "Ondertitels met meer dan twee regels herstellen", "Analysing": "Analyseren...", "NothingToFix": "Niets te herstellen :)", - "FixesFoundX": "Gevonden verbeteringen: {0}", "XFixesApplied": "Toegepaste verbeteringen: {0}", "XFixesYSelected": "{0} correcties - {1} geselecteerd", "NothingFixableBut": "Er kon niets automatisch worden hersteld. De ondertitel bevat fouten - zie het logboek voor details", @@ -1903,7 +1917,6 @@ "FixFirstLetterToUppercaseAfterParagraph": "Eerste letter na alinea naar hoofdletter herstellen", "MergeShortLine": "Korte regel samenvoegen (enkele zin)", "MergeShortLineAll": "Korte regel samenvoegen (alles behalve dialogen)", - "UnbreakShortLinePixelWidth": "Korte regel samenvoegen (pixelbreedte)", "BreakLongLine": "Lange regel afbreken", "FixLongDisplayTime": "Lange weergavetijd herstellen", "FixInvalidItalicTag": "Ongeldige cursief-tag herstellen", @@ -1926,7 +1939,6 @@ "FixUppercaseIInsideLowercaseWord": "Hoofdletter 'i' in kleine-letterwoord herstellen", "FixMissingPeriodAtEndOfLine": "Ontbrekende punt aan het einde van de regel toevoegen", "RefreshFixes": "Beschikbare correcties vernieuwen", - "ApplyFixes": "Geselecteerde correcties toepassen en sluiten", "AutoBreak": "Auto &afbr.", "Unbreak": "&Samenvoegen", "FixDoubleDash": "'--' -> '...' herstellen", @@ -1945,8 +1957,6 @@ "FixSpaceBetweenNumbersExample": "1 100 -> 1100", "FixDialogsOneLineExample": "Hoi Jan! - Hoi Ida! -> Hoi Jan!
- Hoi Ida!", "RemoveDialogFirstInNonDialogsExample": "- Hoe gaat het? -> Hoe gaat het?", - "SelectDefault": "Standaard selecteren", - "SetDefault": "Huidige correcties als standaard instellen", "FixContinuationStyleX": "Voortzettingsstijl herstellen: {0}", "FixUnnecessaryLeadingDots": "Overbodige punten aan het begin verwijderen", "FixCommonErrorsProfiles": "Profielen voor veelvoorkomende fouten herstellen", @@ -1954,7 +1964,6 @@ "SearchRulesDotDotDot": "Regels zoeken...", "GoToApplyFixes": "Naar correcties toepassen", "BackToFixList": "Terug naar de lijst met correcties", - "ApplyFixesAndClose": "Correcties toepassen en sluiten", "FixCommonOcrErrorsStep2": "Veelvoorkomende fouten herstellen, stap 2 (correcties toepassen)", "FixCommonOcrErrorsStep2FixesFoundX": "Veelvoorkomende fouten herstellen, stap 2 - gevonden verbeteringen: {0}", "Action": "Actie", @@ -1967,18 +1976,11 @@ "AdjustDurations": { "Title": "Duur aanpassen", "AdjustVia": "Aanpassen via", - "Seconds": "Seconden", - "Percent": "Procent", "Recalculate": "Opnieuw berekenen", "AddSeconds": "Seconden toevoegen", - "SetAsPercent": "Instellen als percentage van de duur", "Note": "Let op: de weergavetijd zal de begintijd van de volgende tekst niet overlappen", "Fixed": "Vast", - "Milliseconds": "Milliseconden", "ExtendOnly": "Alleen verlengen", - "EnforceDurationLimits": "Minimum- en maximumduur afdwingen", - "CheckShotChanges": "Niet verlengen tot voorbij shot-wijzigingen", - "BatchCheckShotChanges": "Shot-wijzigingen respecteren (indien beschikbaar)", "RecalculateRequiresOcrNote": "Herberekenen is niet beschikbaar — een of meer ondertitels hebben geen OCR-tekst. Voer eerst OCR uit." }, "ApplyDurationLimits": { @@ -1986,6 +1988,8 @@ "FixMinDurationMs": "Minimumduur herstellen (ms)", "DoNotGoPastShotChange": "Niet voorbij shot-wijziging gaan", "FixMaxDurationMs": "Maximumduur herstellen (ms)", + "FixMinDurationFrames": "Minimale duur herstellen (frames)", + "FixMaxDurationFrames": "Maximale duur herstellen (frames)", "MinimumDurationMilliseconds": "Minimale duur (milliseconden):", "MaximumDurationMilliseconds": "Maximale duur (milliseconden):", "MaxDurationShouldBeHigherThanMinDuration": "De maximumduur moet hoger zijn dan de minimumduur", @@ -2005,12 +2009,8 @@ }, "BeautifyTimeCodes": { "Title": "Tijdcodes verfraaien", - "BeautifySettings": "Instellingen voor verfraaien", "SnapToFrames": "Uitlijnen op frames", - "FrameGap": "Tussenruimte in frames", "MinDuration": "Min. duur (ms)", - "ShotChangeThreshold": "Drempel voor shot-wijziging (ms)", - "ShotChangeOffset": "Verschuiving shot-wijziging (frames)", "Original": "Origineel", "Beautified": "Verfraaid", "SubtitlesCount": "Ondertitels", @@ -2051,6 +2051,20 @@ "IsolateSpeechHint": "Verwijdert muziek en geluidseffecten vóór het uitlijnen, zodat de aligner alleen de dialoog hoort. Helpt bij rijen die over luide muziek of actie worden uitgesproken, maar duurt met een GPU ongeveer zo lang als de audio zelf - en zonder GPU vele malen langer.", "IsolatingSpeech": "Spraak isoleren... (dit duurt even)", "IsolateSpeechFailed": "Kan de spraak niet isoleren - in plaats daarvan uitgelijnd op de originele audio.", + "CheckWithSpeechToText": "Controleren met spraak naar tekst", + "CheckWithSpeechToTextHint": "Transcribeert ook de audio en controleert elke verplaatsing aan de hand van waar de woorden daadwerkelijk werden gehoord. Grote verplaatsingen die de spraak bevestigt, worden voor u aangevinkt, en verplaatsingen weg van waar een rij wordt gehoord, blijven uitgevinkt zodat u ze kunt controleren. Toont ook hoeveel van elke rij werd gehoord - een laag aandeel betekent dat de tekst afwijkt van wat er wordt gezegd. Een ondertitel die verder uit sync is dan de max. verschuiving - een constante offset, een drift door een verschil in framerate, of sprongen waar scènes zijn geknipt - wordt eerst gesynchroniseerd op basis van de gehoorde woorden. Gebruikt Crisp ASR Parakeet, wat met een GPU enkele minuten duurt voor een speelfilm.", + "CheckWithSpeechToTextNotAvailableX": "Controle met spraak naar tekst is niet beschikbaar voor {0}", + "TranscribingToCheck": "Transcriberen om de uitlijning te controleren...", + "SpeechToTextFailed": "Spraak naar tekst mislukt - de tijdcodes zijn er niet mee gecontroleerd.", + "SpeechToTextX": "Spraak naar tekst: {0}", + "SpeechToTextConfirmedXDisputedY": "Spraak: {0} bevestigd, {1} betwist", + "HardlyHeardX": "Spraak naar tekst heeft slechts {0}% van de woorden van de ondertitel gehoord. Is de ondertitel in de gesproken taal? De aligner kan geen vertaling plaatsen.", + "Heard": "Gehoord", + "HeardHint": "Hoeveel van de rij spraak naar tekst heeft gehoord. Een laag aandeel betekent dat de tekst afwijkt van wat er wordt gezegd - waar de aligner zich het meest waarschijnlijk vergist.", + "StatusConfirmedBySpeech": "Bevestigd door spraak", + "StatusDisputedBySpeech": "Gehoord waar het was", + "StatusMovedWithSync": "Verplaatst met de synchronisatie", + "SyncedFirstXY": "Eerst gesynchroniseerd: {0} s aan het begin, {1} s aan het einde", "ShowSpeechOnly": "Alleen spraak tonen", "ShowSpeechOnlyHint": "Teken de uitgelijnde golfvorm van de audio zonder muziek en geluidseffecten; de originele golfvorm blijft ter vergelijking ongewijzigd. Beschikbaar zodra de spraak is geïsoleerd - hier, of met \"Alleen spraak tonen\" in de golfvorm van het hoofdvenster.", "Align": "Uitlijnen", @@ -2065,6 +2079,7 @@ "MeanShiftX": "Gemiddelde verschuiving: {0} ms", "ToCheckX": "Te controleren: {0}", "LargeMovesToCheckX": "{0} rij(en) zouden op zichzelf ver verschuiven en zijn niet aangevinkt - speel Origineel / Uitgelijnd af en vink de juiste aan.", + "LinesToCheckX": "{0} rij(en) zijn uitgevinkt gelaten om te controleren - beluister ze (F5 speelt de uitgelijnde rij af, Shift+F5 het origineel) en vink de juiste aan.", "ChangeXOfY": "Wijziging {0} van {1}", "PreviousChange": "Vorige wijziging", "NextChange": "Volgende wijziging", @@ -2077,6 +2092,10 @@ "StatusNoSpeech": "Geen spraak", "StatusShiftTooLarge": "Behouden - verschuiving te groot", "StatusFailed": "Behouden - aligner mislukt", + "StatusAdjustedByHand": "Handmatig aangepast", + "AlignedWaveformHint": "Sleep een rij om deze te verplaatsen, of sleep de randen om het begin of einde te wijzigen.", + "PlayLine": "Rij afspelen", + "UndoAdjustment": "Aanpassing ongedaan maken", "PlayPause": "Afspelen / pauzeren", "PlayAligned": "Uitgelijnd", "PlayAlignedHint": "De geselecteerde rij afspelen met de uitgelijnde tijdcodes", @@ -2094,7 +2113,6 @@ "ProfileName": "Profielnaam", "OverwriteProfileX": "Er bestaat al een profiel met de naam \"{0}\". Overschrijven?", "DeleteProfileX": "Profiel \"{0}\" verwijderen?", - "General": "Algemeen", "Gap": "Tussenruimte", "GapSuffix": "frames (werkt ook de tussenruimtevelden hieronder bij die niet nul zijn)", "Milliseconds": "ms", @@ -2123,7 +2141,6 @@ "HintChaining": "Hoe ondertitels die elkaar bijna raken worden behandeld — samen verlengen of uit elkaar duwen, afhankelijk van of er een shot-wijziging tussen zit." }, "BridgeGaps": { - "Title": "Gaten overbruggen", "BridgeGapsSmallerThan": "Tussenruimten overbruggen kleiner dan (ms)", "MinGap": "Minimale tussenruimte (ms)", "BridgeGapsSmallerThanFrames": "Gaten overbruggen kleiner dan (frames)", @@ -2141,8 +2158,6 @@ "SortBy": { "Title": "Ondertitels sorteren", "SortOrder": "Sorteervolgorde", - "SortByNumber": "Nummer", - "SortByStartTime": "Starttijd", "SortByEndTime": "Eindtijd" }, "BatchConvert": { @@ -2165,6 +2180,7 @@ "AddColor": "Kleur toevoegen", "DeleteLinesWithSpecificActorsOrStyles": "Rijen met acteurs of stijlen verwijderen (scheid meerdere met een komma)", "UseSourceStylesIfPossible": "Bronstijlen gebruiken indien mogelijk", + "KeepSourceEmbeddedFonts": "Ingesloten lettertypen van bron behouden (footer)", "EditStyles": "Stijlen bewerken", "EditProperties": "Eigenschappen bewerken", "EditAttachments": "Bijlagen bewerken", @@ -2185,7 +2201,6 @@ "AssaChangeStyleFromStyle": "Stijl wijzigen van", "AssaChangeStyleToStyle": "naar", "AssaChangeStyleImportStyle": "Stijl importeren...", - "AssaChangeStyleImportedX": "Geïmporteerd: {0}", "AssaChangeStyleTrimUnusedStyles": "Ongebruikte stijlen verwijderen", "AssaChangeStylePropertiesTitle": "Stijleigenschappen wijzigen", "AssaChangeStylePropertiesInfo": "Wijzigt de gekozen velden in elke stijl in het bestand, zonder de stijlen zelf aan te passen.", @@ -2200,6 +2215,18 @@ "AddFolderRecursiveDotDotDot": "Map toevoegen (inclusief submappen)...", "SelectFolderToConvert": "Selecteer map met te converteren bestanden", "IncludeSubfolders": "Submappen meenemen bij het toevoegen van een map", + "VideoFilesWhenAddingFolder": "Videobestanden (mkv, mp4, ts...) bij het toevoegen van een map", + "VideoFilesAsk": "Vragen", + "VideoFilesInclude": "Hun ondertitelsporen toevoegen", + "VideoFilesSkip": "Overslaan", + "FolderContainsXVideoFiles": "De map bevat {0} videobestand(en) (mkv, mp4, ts...).\n\nOok hun ingesloten ondertitelsporen toevoegen, of alleen de ondertitelbestanden?", + "AddVideoFiles": "Videobestanden toevoegen", + "SkipVideoFiles": "Alleen ondertitelbestanden", + "DoNotAskAgainVideoFiles": "Niet opnieuw vragen (wijzigen in instellingen van Batchconversie)", + "SettingsSectionOutput": "Uitvoer", + "SettingsSectionAddingFiles": "Bestanden toevoegen", + "SettingsSectionImageBasedSubtitles": "Op afbeeldingen gebaseerde ondertitels (OCR)", + "SettingsSectionOther": "Overig", "KeepSourceFileTimestamp": "Datum/tijd van bronbestand behouden op uitvoerbestanden", "PreventSleepWhileConverting": "Voorkomen dat de computer in slaapstand gaat tijdens het converteren", "ScanningFolderX": "{0} scannen...", @@ -2215,10 +2242,22 @@ "TransportStreamOnlyTeletext": "Alleen teletekst", "TransportStreamGetSizeFromVideo": "Formaat uit video halen...", "TwoLetterLanguageCodeUppercase": "Taalcode van twee letters (hoofdletters)", - "ThreeLetterLanguageCodeUppercase": "Taalcode van drie letters (hoofdletters)" + "ThreeLetterLanguageCodeUppercase": "Taalcode van drie letters (hoofdletters)", + "FormatContainsDotDotDot": "Formaat bevat...", + "ExtensionIsDotDotDot": "Extensie is...", + "StatusIsError": "Status is fout", + "ForcedTracksOnly": "Alleen geforceerde sporen", + "XConvertedYFailedInZ": "{0:#,###,##0} geconverteerd, {1:#,###,##0} mislukt in {2}", + "XNotProcessed": "{0:#,###,##0} niet verwerkt", + "RemoveUnicodeControlCharactersTitle": "Unicode-besturingstekens verwijderen", + "RemoveUnicodeControlCharactersInfo": "Verwijdert de onzichtbare Unicode-richtingsbesturingstekens (LRM, RLM, LRE, RLE, PDF, LRO en RLO) uit de tekst en vervangt vaste spaties door normale spaties.", + "Preset": "Voorinstelling", + "SavePresetDotDotDot": "Voorinstelling opslaan...", + "DeletePreset": "Voorinstelling verwijderen", + "PresetName": "Naam voorinstelling", + "DeletePresetX": "Voorinstelling \"{0}\" verwijderen?" }, "ChangeCasing": { - "Title": "Hoofdlettergebruik wijzigen", "FixNames": "Namen herstellen", "NamesX": "Namen: {0:#,##0}", "HitsX": "Treffers: {0:#,##0}", @@ -2229,9 +2268,7 @@ "AllUppercase": "Alles in hoofdletters", "AllLowercase": "Alles in kleine letters" }, - "ChangeFormatting": { - "Title": "Opmaak wijzigen" - }, + "ChangeFormatting": {}, "ConvertActors": { "Title": "Acteurs converteren", "ConvertActorFrom": "Acteur converteren van", @@ -2260,18 +2297,12 @@ "OutputFormatAssa": "Advanced Sub Station Alpha (.ass)", "Style1": "Stijl 1", "Style2": "Stijl 2", - "OutlineWidth": "Contourbreedte", - "ShadowWidth": "Schaduwbreedte", - "AlignTop": "Boven", - "AlignBottom": "Onder", - "Merge": "_Samenvoegen", - "PleaseLoadTwoSubtitles": "Laad twee ondertitels" + "Merge": "_Samenvoegen" }, "SplitSubtitle": { "Title": "Ondertitel splitsen", "NumberOfEqualParts": "Aantal gelijke delen", "SaveSplitParts": "Gesplitste delen op_slaan", - "SubtitleSplitIntoXParts": "Ondertitel opgedeeld in {0} delen.", "XPartsSavedInFormatYToFolder": "{0} delen opgeslagen in formaat {1} in map:" }, "SplitBreakLongLines": { @@ -2305,19 +2336,17 @@ "ColumnMerge": "Samenvoegen", "ColumnFirst": "Eerste rij", "ColumnSecond": "Tweede rij", - "ColumnMerged": "Samengevoegd", - "SelectAll": "Alles selecteren", - "InverseSelection": "Selectie omkeren" + "ColumnMerged": "Samengevoegd" }, "MergeLinesWithSameText": { - "Title": "Rijen met dezelfde tekst samenvoegen", "MaxMsBetweenLines": "Max. milliseconden tussen rijen", + "MaxFramesBetweenLines": "Max. frames tussen rijen", "IncludeIncrementingLines": "Rijen met oplopende tekst meenemen", "IncludeRollUpCaptions": "Roll-up-ondertitels (scrollend) meenemen" }, "MergeLinesWithSameTimeCodes": { - "Title": "Rijen met dezelfde tijdcodes samenvoegen", "MaxMsDifference": "Max. verschil (milliseconden)", + "MaxFramesDifference": "Max. verschil (frames)", "MakeDialog": "Dialogen maken" }, "NetflixCheckAndFix": { @@ -2340,7 +2369,6 @@ "ShotChanges": "Shot-wijzigingen", "StartNumberSpellOut": "Getal aan het begin voluit schrijven", "TextforHiUseBrackets": "Tekst voor slechthorenden, rechte haken gebruiken", - "FrameRate": "Framerate", "TwoFrameGrap": "Tussenruimte van twee frames", "WhiteSpace": "Witruimte", "ChildrensProgram": "Kinderprogramma", @@ -2378,7 +2406,6 @@ "ItalicsFixed": "Cursief gecorrigeerd", "ItalicsNotAllowed": "Cursief niet toegestaan", "BridgeGapsXToYFrames": "Tussenruimte van 3-{0} frames => tussenruimte van 2 frames", - "StartNumberSpellOutComment": "Wanneer een getal een zin begint, moet het altijd voluit worden geschreven", "MaximumXCharactersPerSecond": "Maximaal {0} tekens per seconde" }, "ImageBasedEdit": { @@ -2388,10 +2415,8 @@ "ResizeImagesDotDotDot": "Formaat van afbeeldingen wijzigen...", "AdjustBrightnessDotDotDot": "Helderheid aanpassen...", "AdjustAlphaDotDotDot": "Alfa aanpassen...", - "CenterHorizontally": "Horizontaal centreren", "CropImages": "Afbeeldingen bijsnijden", "ImportTimeCodes": "Tijdcodes importeren...", - "SetTextForSubtitle": "Tekst voor ondertitel instellen", "ScreenWidth": "Schermbreedte", "ScreenHeight": "Schermhoogte", "AlphaThresholdInfo": "Pixels met een alfa onder de drempel worden volledig transparant", @@ -2436,10 +2461,16 @@ "XInTopBar": "{0} in bovenbalk", "XInBottomBar": "{0} in onderbalk", "NoImageSubtitlesLoaded": "Geen op afbeeldingen gebaseerde ondertitels geladen", - "ImageBasedFormatNotSupported": "Op afbeeldingen gebaseerd ondertitelformaat niet gevonden/ondersteund." + "ImageBasedFormatNotSupported": "Op afbeeldingen gebaseerd ondertitelformaat niet gevonden/ondersteund.", + "FrameRateDeclaredInFile": "Framerate opgegeven in het Blu-ray sup-bestand", + "FrameRateDetectedFromTimeCodesX": "Framerate gedetecteerd uit de Blu-ray sup-tijdcodes (het bestand geeft {0} op)", + "FrameRateFromVideo": "Framerate van de video", + "FrameRateCurrent": "Huidige framerate", + "FrameRateSetManually": "Framerate handmatig ingesteld", + "FrameRateUsageInfo": "Blu-ray sup-, BDN XML-, DOST- en FCP-exports worden met deze framerate geschreven. Het wijzigt geen tijden - gebruik daarvoor \"Framerate wijzigen\".", + "UseVideoFrameRateXInsteadOfY": "De framerate van de video is {0}, maar de ondertitel is ingesteld op {1}.\n\nDe framerate van de video ({0}) gebruiken?" }, "RemoveTextForHearingImpaired": { - "Title": "Tekst voor slechthorenden verwijderen", "Interjections": "Tussenwerpsels", "SkipIfStartWith": "Overslaan indien beginnend met", "RemoveTextBetween": "Tekst verwijderen tussen", @@ -2501,7 +2532,21 @@ "CorrectWordsX": "Correcte woorden: {0}", "NamesX": "Namen: {0}", "AddedToDictionaryX": "Toegevoegd aan woordenboek: {0}", - "DoNotShowThisAgain": "Dit bericht niet meer tonen" + "DoNotShowThisAgain": "Dit bericht niet meer tonen", + "EditUseAlwaysList": "Lijst \"Altijd gebruiken\" bewerken...", + "UseAlwaysListTitle": "Spellingcontrole - lijst \"Altijd gebruiken\"", + "UseAlwaysListHeader": "Lijst \"Altijd gebruiken\"", + "UseAlwaysListDescription": "Woorden die de spellingcontrole automatisch vervangt, opgeslagen via \"Alles wijzigen\" en \"Altijd gebruiken\". Een paar wordt alleen gebruikt als het woord verkeerd gespeld is.", + "UseAlwaysWord": "Verkeerd gespeld woord", + "XPairs": "{0:#,##0} paren", + "XUnusedPairs": "{0:#,##0} nooit gebruikt", + "UseAlwaysUnusedHint": "Nooit gebruikt - dit woord is correct gespeld (of is een naam of gebruikerswoord), dus de spellingcontrole markeert het nooit", + "RemoveUnused": "Ongebruikte verwijderen", + "UseAlwaysEmpty": "Nog geen \"Altijd gebruiken\"-paren voor deze taal", + "UseAlwaysEmptyHint": "Paren worden toegevoegd wanneer u in de spellingcontrole op \"Alles wijzigen\" of \"Altijd gebruiken\" klikt.", + "UseAlwaysNoMatch": "Geen paren komen overeen met de zoekopdracht", + "UseAlwaysSameWord": "De vervanging moet verschillen van het woord.", + "UseAlwaysRememberOff": "\"Lijst 'Altijd gebruiken' onthouden\" is uitgeschakeld in Instellingen, dus de spellingcontrole gebruikt deze lijst niet en slaat hem niet op." }, "Video": { "Chapters": { @@ -2532,7 +2577,6 @@ "ScaleTimesDescription": "Schaal elke hoofdstuktijd van de ene framerate naar de andere.", "FromFrameRate": "Van", "ToFrameRate": "Naar", - "Apply": "Toepassen", "WriteToVideo": "Naar video schrijven...", "WriteToVideoTitle": "Hoofdstukken naar video schrijven", "WriteToVideoDescription": "De hoofdstukken worden in een kopie van het videobestand geschreven. Er wordt niets opnieuw gecodeerd, dus dit gaat snel en zonder kwaliteitsverlies.", @@ -2542,7 +2586,6 @@ "WrittenToX": "Hoofdstukken geschreven naar {0}", "UnableToWriteChapters": "Kan de hoofdstukken niet naar het videobestand schrijven.", "ChaptersSavedToX": "Hoofdstukken opgeslagen in {0}", - "NoVideoLoaded": "Geen video geladen", "EmptyListCallToAction": "Nog geen hoofdstukken - voeg er een toe op de videopositie, of importeer ze uit een video of bestand." }, "GoToVideoPosition": "Ga naar videopositie", @@ -2575,6 +2618,13 @@ "RemuxVideoFailed": "Video remuxen mislukt.", "RemuxVideoPleaseSelectBoth": "Selecteer zowel een video- als een audiobestand.", "RemuxVideoAssRequiresMkv": "ASS/SSA-ondertitels vereisen de MKV-container om alle opmaak en stijlen te behouden.", + "RemuxVideoSccRequiresMov": "Scenarist (.scc)-ondertitels worden ingesloten als CEA-608 closed captions, die de MOV- of MPG-container vereisen. Het uitvoerformaat is automatisch gewijzigd in .mov.", + "RemuxVideoSccNotWithWebM": "Scenarist (.scc) closed captions vereisen de MOV-container, die de VP9/AV1-video van een .webm-bestand niet kan bevatten. Verwijder het .scc-bestand of converteer het naar een ander ondertitelformaat.", + "RemuxVideoMpgMaxTwoCaptionTracks": "Een .mpg-bestand bevat maximaal twee closed-caption-sporen (CC1 en CC3) - verwijder ondertitelbestanden zodat er een of twee overblijven.", + "RemuxVideoMccRequiresMpg": "MacCaption (.mcc)-ondertitels worden ingesloten als CEA-608/708 closed captions in de MPEG-2-video, wat de MPG-container vereist. Het uitvoerformaat is automatisch gewijzigd in .mpg.", + "RemuxVideoMpgReencodeVideoX": "Closed captions in een .mpg-bestand vereisen MPEG-2-video, en de video is {0}. De video opnieuw coderen naar MPEG-2? Dit duurt langer en verlaagt de kwaliteit een beetje.", + "RemuxVideoFileHasNoAudioX": "\"{0}\" heeft geen audiospoor en kan dus niet als audiobron worden gebruikt.", + "RemuxVideoAddingClosedCaptions": "Closed captions toevoegen...", "RemuxVideoSelectAudioTrack": "Audiospoor selecteren", "RemuxVideoSelectAudioTrackFor": "Audiospoor selecteren voor {0}", "RemuxVideoSelectAudioTrackPrompt": "Het bestand '{0}' bevat {1} audiosporen. Selecteer het audiospoor om te remuxen:", @@ -2587,19 +2637,14 @@ "RemuxVideoMixAudio": "Audio mixen tot één spoor", "RemuxVideoVolumePercent": "Volume %:", "RemuxVideoVolumeX": "Volume {0}%", + "RemuxVideoFastStart": "Optimaliseren voor webstreaming (index naar voren verplaatsen - langzamer)", "EmbedSubtitlesDotDotDot": "Ingesloten ondertitels toevoegen/verwijderen...", "GenerateTimeCodes": "Tijdcodes genereren", "CheckeredImage": "Geblokte afbeelding", - "SaveVideoAsTitle": "Video opslaan als", "PromptForFfmpegParamsAndGenerate": "Om ffmpeg-parameters vragen en genereren", "CutVideoCutSegments": "Segmenten knippen", "CutVideoMergeSegments": "Segmenten samenvoegen", "CutVideoSplitSegments": "Segmenten afzonderlijk opslaan", - "CutVideoType": "Kniptype", - "MpvRenderAuto": "Auto", - "MpvRenderNative": "Systeemeigen", - "MpvRenderOpenGl": "OpenGL", - "MpvRenderSoftware": "Software (traag)", "CutVideoAlsoCutSubtitle": "Ook ondertitel knippen", "CutVideoSubtitleFileGeneratedX": "Ondertitelbestand gegenereerd: {0}", "CutVideoTransition": "Overgang tussen segmenten", @@ -2634,7 +2679,6 @@ "AddRemoveEmbeddedSubtitlesTitle": "Ingesloten ondertitels toevoegen/verwijderen", "AddRemoveEmbeddedSubtitlesMp4Title": "Ingesloten ondertitels toevoegen/verwijderen (MP4)", "AddCurrentSubtitle": "Huidige ondertitel toevoegen", - "EmbeddedTrackColumnNew": "Nieuw", "EmbeddedTrackNoSubtitlesFoundTitle": "Geen ondertitels gevonden", "EmbeddedTrackNoSubtitlesFoundMessage": "Het geselecteerde ondertitelbestand bevat geen ondertitels.", "EmbeddedTrackNoTracksTitle": "Geen tracks toegevoegd", @@ -2647,7 +2691,6 @@ "EmbeddedTrackGeneratingVideo": "Video genereren...", "Mp4FilesFilter": "MP4-bestanden", "EmbeddedTracksPickVideoHint": "Kies een videobestand, of sleep er een hierheen, om de ondertitelsporen te zien.", - "TitleOrLanguage": "Titel/taal", "ViewMatroskaTrackX": "Matroska-spoor tonen - {0}", "ResolutionSeparator": "x", "OpenFromUrlTitle": "Videobestand openen vanaf URL", @@ -2657,9 +2700,7 @@ "OpenFromUrlDownloadAndOpen": "Downloaden en openen", "OpenFromUrlDownloadAndOpenDescription": "De video lokaal opslaan en daarna openen.", "OpenFromUrlDownloadAndOpenNote": "Vereist voor spraak-naar-tekst en golfvormpieken.", - "OpenFromUrlMissing": "Voer eerst een video-URL in.", "OpenFromUrlDownloadingTitle": "Video downloaden", - "OpenFromUrlSaveAs": "Video opslaan als", "OpenFromUrlDownloadSubtitles": "Ook ondertitels downloaden", "OpenFromUrlDownloadVideo": "Video downloaden", "OpenFromUrlIncludeAutoGeneratedSubtitles": "Automatisch gegenereerde ondertitels meenemen (originele taal)", @@ -2696,59 +2737,38 @@ "ReEncodeGeneratingDone": "Genereren voltooid", "ReEncodeGeneratedFilesX": "Gegenereerde bestanden ({0}):", "ReEncodeFfmpegParameters": "ffmpeg-parameters", + "AnalyzingVideoX": "Video analyseren... {0}% {1}", + "AnalyzingVideoXofY": "Video {0}/{1} analyseren... {2}% {3}", + "BackgroundImageFileNotSelected": "Geen achtergrondafbeelding geselecteerd", + "BackgroundImageFileDoesNotExistX": "Achtergrondafbeelding bestaat niet: {0}", "BurnIn": { "Title": "Video genereren met ingebrande ondertitels", - "InfoAssaOff": "Let op: opmaak met Advanced SubStation Alpha wordt ondersteund.", - "InfoAssaOn": "Let op: opmaak met Advanced SubStation Alpha wordt gebruikt :)", - "XGeneratedWithBurnedInSubsInX": "\"{0}\" gegenereerd met ingebrande ondertitel in {1}.", - "TimeRemainingMinutes": "Resterende tijd: {0} minuten", - "TimeRemainingOneMinute": "Resterende tijd: één minuut", - "TimeRemainingSeconds": "Resterende tijd: {0} seconden", - "TimeRemainingAFewSeconds": "Resterende tijd: enkele seconden", - "TimeRemainingMinutesAndSeconds": "Resterende tijd: {0} minuten en {1} seconden", - "TimeRemainingOneMinuteAndSeconds": "Resterende tijd: één minuut en {0} seconden", - "TargetFileName": "Doelbestandsnaam: {0}", "TargetFileSize": "Doelbestandsgrootte (vereist codering in 2 passes)", "FileSizeMb": "Bestandsgrootte in MB", "MatchSourceVideoSize": "Grootte van bronvideo overnemen", - "PassX": "Pass {0}", - "Encoding": "Codering", "BitRate": "Bitsnelheid", "TotalBitRateX": "Totale bitrate: {0}", "SampleRate": "Samplefrequentie", "Audio": "Audio", - "Stereo": "Stereo", "Preset": "Voorinstelling", "Tune": "Afstemming", "PixelFormat": "Pixelformaat", "Crf": "CRF", - "TuneFor": "Afstemmen op", - "AlignRight": "Rechts uitlijnen", - "GetStartPosition": "Beginpositie ophalen", - "GetEndPosition": "Eindpositie ophalen", "UseSource": "Bron gebruiken", - "UseSourceFolder": "Bronmap gebruiken", - "UseSourceResolution": "Bronresolutie gebruiken", - "OutputSettings": "Uitvoerbestand/-map...", "FontSizeFactor": "Factor tekengrootte", "BoxType": "Kadertype", - "FixRightToLeft": "Rechts-naar-links herstellen", - "Cut": "Knippen", "FromTime": "Van tijd", "ToTime": "Tot tijd", "AudioEncoding": "Audiocodering", "OutputProperties": "Uitvoereigenschappen...", "VideoFileSize": "Grootte videobestand", "OneBox": "Eén kader", - "BoxPerLine": "Kader per regel", "LogoInfo": "Kies een PNG-afbeelding en sleep deze om hem op de video te positioneren.", "NoSubtitlesGenerateAnyway": "Er zijn geen ondertitels om in te branden.\n\nDe video zonder ondertitels genereren?", "NoSubtitlesInXItemsGenerateAnyway": "{0} batch-item(s) hebben geen ondertitels om in te branden.\n\nDie video's zonder ondertitels genereren?" }, "VideoTransparent": { - "Title": "Transparante video met ondertitels genereren", - "InfoAssaOff": "Let op: opmaak met Advanced SubStation Alpha wordt ondersteund.", - "InfoAssaOn": "Let op: opmaak met Advanced SubStation Alpha wordt gebruikt :)" + "Title": "Transparante video met ondertitels genereren" }, "AudioToText": { "Title": "Spraak naar tekst", @@ -2756,10 +2776,21 @@ "TranslateToEnglish": "Naar het Engels vertalen", "Transcribing": "Transcriberen...", "TranscribingXOfY": "Transcriberen {0} van {1}...", + "LinesTranscribedXOfYFailedZ": "{0} van {1} rijen zijn getranscribeerd - {2} zijn mislukt of bevatten geen spraak.\n\nMislukte rijen behouden hun huidige tekst. De getranscribeerde rijen toepassen, of de mislukte opnieuw proberen (u kunt eerst de engine of instellingen wijzigen)?", + "LinesTranscribedXOfYCancelled": "Transcriptie is geannuleerd na {0} van {1} rijen.\n\nDe getranscribeerde rijen toepassen? De andere rijen behouden hun huidige tekst.", + "NoLinesTranscribed": "Geen van de {0} rijen kon worden getranscribeerd.\nControleer het hulpprogrammalogboek voor details.", + "ApplyTranscribedLines": "Getranscribeerde toepassen", + "RetryFailedLines": "Mislukte opnieuw proberen", "IsolateSpeech": "Spraak isoleren (traag)", "IsolateSpeechHint": "Verwijdert muziek en geluidseffecten vóór het transcriberen. Geeft minder gemiste rijen en betere timing bij audio met luide muziek, maar duurt met een GPU ongeveer zo lang als de audio zelf - en zonder GPU vele malen langer.", "IsolatingSpeech": "Spraak isoleren...", "IsolateSpeechFailed": "Kan de spraak niet isoleren - in plaats daarvan wordt de originele audio getranscribeerd.", + "DetectSpeakers": "Sprekers detecteren", + "DetectSpeakersHint": "Begint elke rij met de spreker, zoals \"(speaker 0)\", met behulp van NVIDIA's Sortformer-diarisatie (tot 8 sprekers). Werkt het best met backends die rijen splitsen waar de spreker wisselt, zoals Parakeet en Cohere.", + "DetectSpeakersNeedsNewerCrispAsr": "\"Sprekers detecteren\" vereist Crisp ASR v{0} of nieuwer, maar v{1} is geïnstalleerd.\n\nWerk Crisp ASR bij (de tandwielknop naast de engine) of schakel \"Sprekers detecteren\" uit.", + "XNeedsNewerCrispAsr": "{0} vereist Crisp ASR v{1} of nieuwer, maar v{2} is geïnstalleerd.\n\nDe update nu downloaden en installeren?", + "Vad": "VAD", + "VadHint": "Spraakactiviteitsdetectie: alleen de spraak wordt getranscribeerd, geknipt waar de spreker pauzeert.\nAuto: Silero, en alleen voor de engines die het nodig hebben (Cohere, Mega, Index-Echo).\nSilero, FireRedVAD, Whisper-VAD, WebRTC: altijd aan, voor elke Crisp ASR-engine. FireRedVAD is het nauwkeurigst, Whisper-VAD geeft de minste, schoonste knippen maar is het traagst.\nIndex-Echo gebruikt altijd Silero. --vad, --vad-model of --chunk-seconds in de geavanceerde instellingen hebben voorrang.", "InputLanguage": "Invoertaal", "AdvancedWhisperSettings": "Geavanceerde parameters voor spraak naar tekst", "DownloadingSpeechToTextEngine": "Spraak-naar-tekst-engine downloaden", @@ -2774,8 +2805,6 @@ "AddCustomModelDotDotDot": "Aangepast model toevoegen...", "CustomModelHelp": "U kunt uw eigen model gebruiken: kies het modelbestand (whisper.cpp ggml '.bin') of de modelmap (faster-whisper-map met een 'model.bin' erin). Het wordt naar de modellenmap gekopieerd en aan de lijst hierboven toegevoegd.", "ViewToolsLogFile": "Logbestand van gereedschappen bekijken", - "ReDownloadX": "{0} opnieuw downloaden", - "DownloadX": "{0} downloaden", "UpdateXTitle": "{0} bijwerken?", "UpdateXMessage": "Er is een nieuwere versie van {0} beschikbaar.{1}{1}De update nu downloaden en installeren?", "DownloadingSpeechToTextModel": "Model voor spraak naar tekst downloaden", @@ -2846,7 +2875,6 @@ "CloneVoicePerLineNeedsVideo": "Om de stem van elke rij te klonen is de video nodig waar de ondertitel bij hoort. Open de video en probeer het opnieuw.", "CloneVoicePerLineNoClips": "Er kon geen audio uit de video worden gehaald, dus er is niets om van te klonen. Controleer of de video een audiospoor heeft.", "CloneVoicePerLineTranscribeClipsXYZ": "{0} kan een rij alleen klonen als bekend is wat er in de video op die rij wordt gezegd. Dat is niet bekend voor {1} van {2} rijen - normaal komt het uit de ondertitel in de originele taal (Bestand > Origineel openen...).\n\nDie rijen nu uit de video transcriberen met spraak-naar-tekst?\n\nJa: kies de engine en de taal die in de video wordt gesproken; de referentieclips worden getranscribeerd voordat de spraak wordt gegenereerd.\nNee: toch genereren - die rijen worden uitgesproken door een gewone stem in plaats van de stem in de video.", - "CloneVoicePerLineNeedsOriginalSubtitleX": "{0} kan een rij alleen klonen als bekend is wat er in de video op die rij wordt gezegd, en dat komt uit de ondertitel in de originele taal. Er is geen originele ondertitel geladen, dus elke rij zou worden uitgesproken door de eerste geïmporteerde stem in plaats van door de stem in de video.\n\nOpen de originele ondertitel (Bestand > Origineel openen...) naast de vertaling en genereer opnieuw.", "VoiceCloneConsentTitle": "Stem klonen - voordat u begint", "VoiceCloneConsentHeader": "U staat op het punt een stem te klonen", "VoiceCloneConsentIntro": "Bij klonen wordt de stem van een echt persoon gekopieerd. Daar gelden vrijwel overal regels voor, en in de EU wettelijke verplichtingen die op u rusten, niet op Subtitle Edit. Lees dit één keer door.", @@ -2862,6 +2890,12 @@ "VoiceCloneConsentDeclined": "Stemklonen is niet beschikbaar totdat deze voorwaarden zijn geaccepteerd.", "AdvancedTtsAudioProcessing": "Audioverwerking", "AdvancedTtsOutput": "Uitvoer", + "AdvancedTtsVoiceCloning": "Stemklonen", + "CleanCloneReferences": "Stemkloonreferenties opschonen (Sidon)", + "CleanCloneReferencesDescription": "Verwijdert muziek, ruis en galm uit de audio waarvan een stem wordt gekloond: fragmenten uit de video en opnamen die in de stembeheerder zijn geïmporteerd. Gebruikt het Sidon-spraakherstelmodel op audio.cpp (download van 940 MB). De gekloonde stem kopieert de achtergrond niet meer, maar zeer zachte of overlappende spraak kan gedempt klinken.", + "CleaningCloneReferencesDotDotDot": "Stemkloonreferenties opschonen...", + "DownloadSidonTitle": "Sidon downloaden?", + "DownloadSidonQuestionX": "Voor het opschonen van stemkloonreferenties is het Sidon-spraakherstelmodel ({0}) nodig.\n\nNu downloaden? Zonder dit model worden de referenties gebruikt zoals ze zijn.", "RubberbandInstalled": "(geïnstalleerd)", "RubberbandNotFound": "(niet gevonden in FFmpeg)", "GenerationFolder": "Generatiemap", @@ -2870,14 +2904,11 @@ "DeleteTempFilesDescription": "Verwijdert de generatiemap wanneer het venster Tekst naar spraak wordt gesloten. Opgeslagen audio- en videobestanden blijven behouden.", "PiperSettings": "Piper-instellingen", "Title": "Tekst naar spraak", - "TextToSpeechEngine": "Engine voor tekst naar spraak", "ReviewAudioSegments": "TTS - audiosegmenten beoordelen", "ReviewAudioSegmentsHistory": "TTS - audiogeschiedenis beoordelen", "Stability": "Stabiliteit", "Similarity": "Gelijkenis", "SpeakerBoost": "Sprekerversterking", - "StyleExaggeration": "Stijloverdrijving", - "RegenerateAudioSelectedLine": "Audio voor geselecteerde rij opnieuw genereren", "GenerateSpeechFromText": "Spraak uit tekst genereren", "TestVoice": "Stem testen", "AddAudioToVideoFile": "Audio aan videobestand toevoegen", @@ -2907,6 +2938,8 @@ "Instructions": "Instructies", "InstructionsHint": "Optionele spreekstijl, bijv. \"Spreek met een rustige, warme stem\". Alleen verzonden naar modellen die dit ondersteunen (niet tts-1/tts-1-hd)", "OpenAiSpeedHint": "Spreeksnelheid van 0,25 tot 4,0 (1,0 is normaal). Niet elk model ondersteunt dit", + "OutputFormat": "Uitvoerformaat", + "OutputFormatHint": "Audioformaat dat bij de server wordt aangevraagd. \"Auto\" vraagt om MP3 en schakelt over naar PCM voor modellen die alleen PCM teruggeven (bijv. Gemini TTS)", "RegenerateAudio": "Audio opnieuw genereren", "AutoContinuePlaying": "Automatisch doorgaan met afspelen", "AddingAudioToVideoFileDotDotDot": "Audio aan videobestand toevoegen...", @@ -2927,6 +2960,17 @@ "VoiceManagerTitle": "TTS - Stembeheer", "VoiceManagerDotDotDot": "Stembeheer...", "VoiceManagerMenuItem": "TTS-stembeheer...", + "SpeakFromCurrentLineTitle": "Voorlezen vanaf huidige rij", + "SpeakFromCurrentLineDotDotDot": "Voorlezen vanaf huidige rij...", + "SpeakFromCurrentLineHint": "Leest de rijen een voor een voor, beginnend bij de huidige rij. Druk op Escape of klik op een andere rij om te stoppen.", + "StopSpeaking": "Voorlezen stoppen", + "SpeakingLineX": "Rij {0} voorlezen... (Escape om te stoppen)", + "PlayWithSpeechTitle": "Afspelen met spraak", + "PlayWithSpeechDotDotDot": "Afspelen met spraak...", + "PlayWithSpeechHint": "Speelt de video af en spreekt elke rij uit wanneer de video die bereikt. Pauzeer en zoek zoals gewoonlijk; druk op Escape om te stoppen.", + "LowerVideoVolumeWhileSpeaking": "Videovolume verlagen tijdens spreken", + "PauseVideoWhenSpeechRunsLate": "Video pauzeren als spraak doorloopt in de volgende rij", + "WaitingForSpeechLineX": "Spraak genereren voor rij {0}... (Escape om te stoppen)", "VoiceKindClone": "Gekloonde stem", "VoiceKindPreset": "Ingebouwd", "VoiceKindModel": "Stemmodel", @@ -2960,12 +3004,21 @@ "AdvancedTtsSettings": "Geavanceerde TTS-instellingen", "ProAudioPostProcessing": "Professionele audionabewerking", "ProAudioPostProcessingDescription": "Past EQ-warmte, ruispoort, compressie, luidheidsnormalisatie (-16 LUFS) en in-/uitfaden toe op elk segment.", + "GeneratingSpeechSegmentXOfY": "Spraak genereren: segment {0} van {1}", + "AdjustingSpeedSegmentXOfY": "Snelheid aanpassen: segment {0} van {1}", + "MergingAudioSegmentXOfY": "Audio samenvoegen: segment {0} van {1}", + "PostProcessingSegmentXOfY": "Nabewerking: segment {0} van {1}", "AudioDucking": "Audio dimmen (ducking)", "AudioDuckingDescription": "Verlaagt het volume van de oorspronkelijke video-audio en mengt deze met de TTS-audio, zodat de originele geluidsband nog zwak hoorbaar blijft.", "RemoveOriginalSpeech": "Originele spraak verwijderen (traag)", "RemoveOriginalSpeechDescription": "Behoudt de muziek en geluidseffecten van de originele video maar verwijdert de spraak, en mixt de nieuwe spraak daaroverheen. Duurt met een GPU ongeveer zo lang als de video zelf, en zonder GPU vele malen langer. Met audio-ducking aan geldt het volume voor de muziek en effecten. Alleen van toepassing als de spraak aan het videobestand wordt toegevoegd.", "RemovingOriginalSpeech": "Originele spraak verwijderen...", "RemoveOriginalSpeechFailed": "Kan de originele spraak niet verwijderen - de spraak is zonder deze stap aan de video toegevoegd.", + "SpeechRemovalModel": "Model", + "SpeechRemovalModelDescription": "Beste kwaliteit behoudt het meeste van de muziek en geluidseffecten. Snel is ongeveer drie keer zo snel, waarbij iets meer van de muziek verloren gaat.", + "SpeechRemovalBestQuality": "Beste kwaliteit (Mel-Band RoFormer)", + "SpeechRemovalFast": "Snel (HTDemucs)", + "VideoTruncatedWarning": "Het beeld in het nieuwe videobestand stopt na {0}, maar de originele video duurt {1} - het beeld bevriest terwijl het geluid doorgaat.\n\nHet bestand is bewaard. Zie error-log.txt in de gegevensmap van Subtitle Edit voor de uitvoer van ffmpeg.", "OriginalVolumePercent": "Oorspronkelijk volume %", "VadSilenceCompression": "VAD-stiltecompressie", "VadSilenceCompressionDescription": "Verkort pauzes tussen woorden voordat het tempo wordt gewijzigd. Gebruikt Voice Activity Detection om alleen stiltes samen te persen en laat spraak ongemoeid. Dit is de aanbevolen eerste stap — de duur wordt korter zonder kwaliteitsverlies.", @@ -3032,7 +3085,6 @@ "SubtitleMergedLinesAppliedPlural": "{0} samenvoegingen van rijen toegepast vanuit tekst naar spraak" }, "ShotChanges": { - "TitleGenerateOrImport": "Shot-wijzigingen genereren/importeren", "GenerateShotChanges": "Shot-wijzigingen genereren", "ImportShotChanges": "Shot-wijzigingen importeren", "GenerateShotChangesWithFfmpeg": "Shot-wijzigingen genereren met ffmpeg", @@ -3042,7 +3094,9 @@ "TimeCodeFormatColon": "Tijdcodeformaat:", "ShotChangesList": "Lijst met shot-wijzigingen", "ShotChangesClearQuestion": "Weet u zeker dat u alle shot-wijzigingen wilt wissen?", - "DeleteSelectedShotChangeQuestion": "Weet u zeker dat u de geselecteerde shot-wijziging wilt verwijderen?" + "DeleteSelectedShotChangeQuestion": "Weet u zeker dat u de geselecteerde shot-wijziging wilt verwijderen?", + "ShotChanges": "Shot-wijzigingen", + "ExportShotChanges": "Shot-wijzigingen exporteren" }, "VideoOcr": { "Title": "OCR op ingebrande ondertitel", @@ -3052,10 +3106,6 @@ "BottomHalf": "Onderste helft", "FullFrame": "Volledig beeld", "Engine": "OCR-engine", - "Language": "Taal", - "Url": "URL", - "Model": "Model", - "ApiKey": "API-sleutel", "Scan": "Scannen", "FramesPerSecond": "Frames per seconde", "TextBrightnessMinimum": "Minimale teksthelderheid (0=uit)", @@ -3070,10 +3120,10 @@ "RunningOcrXY": "OCR uitvoeren... {0}/{1}", "NoLinesFoundTitle": "Geen ondertitels gevonden", "NoLinesFoundMessage": "Er is geen tekst gevonden in het scangebied - probeer het scangebied, de engine of de minimale helderheid aan te passen.", + "OcrStoppedEarlyMessage": "De OCR-engine is gestopt voordat alle frames waren gelezen, dus de tot nu toe gevonden rijen blijven behouden en de rest ontbreekt.{0}{0}{1}", "LinesFoundX": "{0} rijen gevonden", "RefiningTimingXY": "Timing verfijnen... {0}/{1}", "FixOcrErrors": "OCR-fouten herstellen", - "Dictionary": "Woordenboek", "EditLineX": "Rij {0} bewerken", "DownloadDictionaryHint": "Een woordenboek voor spellingcontrole downloaden...", "FramesPerSecondHint": "Hoeveel frames per seconde worden gescand. 5 volstaat voor normale ondertitels; hoger vangt zeer korte rijen op en verscherpt de groepering, maar kost meer OCR-tijd.", @@ -3148,7 +3198,6 @@ "TalkerX": "Spreker {0}", "BaseModel": "Basismodel", "TurboModel": "Turbomodel", - "TtsUpdateX": "{0} bijwerken", "TtsEngineNotInstalled": "{0} niet geïnstalleerd", "TtsEngineUpdateAvailable": "{0} - update beschikbaar", "TtsEngineTooOldUpdateRequired": "{0} te oud - update vereist", @@ -3185,13 +3234,11 @@ "ShowOnlyWaveform": "Alleen golfvorm tonen", "ShowOnlySpectrogram": "Alleen spectrogram tonen", "ShowWaveformAndSpectrogram": "Golfvorm en spectrogram tonen", - "SpectrogramClassic": "Klassiek", "SpectrogramClassicViridis": "Viridis", "SpectrogramClassicPlasma": "Plasma", "SpectrogramClassicInferno": "Inferno", "SpectrogramClassicTurbo": "Turbo", "SpectrogramNeon": "Neon", - "WaveformDrawStyleClassic": "Klassiek", "WaveformDrawStyleFancy": "Fancy", "SetVideoPositionAndPauseAndSelectSubtitle": "Videopositie instellen, pauzeren en ondertitel selecteren", "SetVideopositionAndPauseAndSelectSubtitleAndCenter": "Videopositie instellen, pauzeren, ondertitel selecteren en centreren", @@ -3201,7 +3248,6 @@ }, "Sync": { "VisualSync": "Visueel synchroniseren", - "Sync": "Synchroniseren", "StartScene": "Beginscène", "EndScene": "Eindscène", "PlayTwoSecondsAndBack": "2 sec. afspelen en terug", @@ -3221,16 +3267,14 @@ "AdjustAllTimes": "Alle tijden aanpassen (eerder/later tonen)", "ShowEarlier": "Eerder tonen", "ShowLater": "Later tonen", - "ChangeFrameRate": "Framerate wijzigen", "SetSyncPoint": "Synchronisatiepunt instellen", "SetSyncPointViaVideo": "Synchronisatiepunt instellen via video...", "SyncPointTimeCode": "Tijdcode van synchronisatiepunt", "SyncPoints": "Synchronisatiepunten", "PointSync": "Puntsynchronisatie", "PointSyncViaOther": "Puntsynchronisatie via andere ondertitel", - "AdjustmentX": "Aanpassing: {0}", - "TotalAdjustmentX": "Totale aanpassing: {0}", - "SyncPointCandidateInfo": "Rijen die na {0}+ seconden stilte beginnen, worden gemarkeerd - die vormen vaak betrouwbare synchronisatiepunten", + "SyncPointCandidateAfterInfo": "Rijen gevolgd door {0}+ seconden stilte worden gemarkeerd - ze zijn vaak betrouwbare synchronisatiepunten", + "GapAfter": "Tussenruimte erna", "AdjustAllShortcuts": "Sneltoetsen:\r\n\r\n• Shift + links/rechts: 10 ms verplaatsen\r\n• Ctrl + links/rechts: 100 ms verplaatsen\r\n• Alt + links/rechts: 500 ms verplaatsen", "AdjustAllShortcutsFrames": "Sneltoetsen:\r\n\r\n• Shift + links/rechts: 1 frame verplaatsen\r\n• Ctrl + links/rechts: 10 frames verplaatsen\r\n• Alt + links/rechts: 1 seconde verplaatsen", "OffsetInSeconds": "Verschuiving in seconden", @@ -3268,8 +3312,6 @@ "TranslationCancelled": "Vertaling geannuleerd", "SwapLanguages": "Bron- en doeltaal verwisselen", "XIsAlreadyDownloadedReDownload": "{0} is al gedownload. Opnieuw downloaden?", - "AdvancedDotDotDot": "Geavanceerd...", - "AdvancedSettings": "Geavanceerde instellingen", "AdvancedSettingsSubtitle": "Batch-, context- en sampling-opties voor de geavanceerde llama.cpp-engine", "Context": "Context", "Synopsis": "Synopsis", @@ -3320,14 +3362,6 @@ "WaveformSnapToShotChangeSameShotEndMaxSeconds": "Vastklikken op dichtstbijzijnde shot-wijziging: max. eindafstand wanneer begin en einde dezelfde cut delen (seconden)", "WaveformGuessStartOffsetMs": "Begintijd raden uit golfvorm: begin eerder plaatsen met (ms)", "WaveformGuessEndOffsetMs": "Eindtijd raden uit golfvorm: einde later plaatsen met (ms)", - "ShowToolbarMultipleReplace": "Pictogram meervoudig vervangen tonen", - "ShowToolbarPointSync": "Pictogram puntsynchronisatie tonen", - "ShowToolbarAutoTranslate": "Pictogram automatisch vertalen tonen", - "ShowToolbarSpeechToText": "Pictogram spraak naar tekst tonen", - "ShowToolbarStyleManager": "Pictogram stijlbeheer tonen (ASSA/SSA/WebVTT)", - "ShowToolbarProperties": "Pictogram eigenschappen tonen (ASSA/SSA)", - "ShowToolbarAttachments": "Pictogram bijlagen tonen (ASSA/SSA)", - "ShowToolbarAssaDraw": "Pictogram ASSA-tekenen tonen (ASSA)", "ProxyDomain": "Domein", "ProxyUseSystemCredentials": "Systeemreferenties gebruiken", "ProxyBypassList": "Proxy omzeilen voor", @@ -3339,8 +3373,14 @@ "CheckForUpdatesChannelStableAndBeta": "Stabiele en bètaversies", "AssaAutoSetResolution": "ASSA: resolutie (PlayResX/PlayResY) instellen op basis van de video wanneer een video wordt geopend", "AssaAutoSetResolutionPrompt": "ASSA: vragen voordat de resolutie wordt aangepast aan de video (anders automatisch herschalen)", + "VideoControls": "Videobediening", + "VideoControlsEdit": "Videobediening bewerken...", + "VideoControlsPositionText": "Tekst positie / duur", + "VideoControlsVideoFileName": "Bestandsnaam video", + "VideoControlsPlayerName": "Naam videospeler", "MergeKeepEndTime": "Rijen samenvoegen: eindtijd behouden (overlap met volgende ondertitel toestaan)", "MergeKeepEndTimeOnlyAssa": "Rijen samenvoegen: eindtijd alleen behouden voor \"Advanced Sub Station Alpha\"", + "MergeAutoBreak": "Rijen samenvoegen: samengevoegde tekst automatisch afbreken (uit behoudt de eigen regeleinden van de rijen)", "DefaultSaveLocation": "Standaard opslaglocatie", "DefaultSaveLocationSourceFileFolder": "Map van ondertitel-/videobestand", "DefaultSaveLocationLastUsedFolder": "Laatst gebruikte map", @@ -3359,7 +3399,6 @@ "DialogStyleDashBothLinesWithSpace": "Streepje op beide regels met spatie", "DialogStyleDashBothLinesWithoutSpace": "Streepje op beide regels zonder spatie", "ContinuationStyle": "Voortzettingsstijl", - "ContinuationStyleNone": "Geen", "ContinuationStyleNoneTrailingDots": "Geen, punten bij pauzes (alleen aan het einde)", "ContinuationStyleNoneLeadingTrailingDots": "Geen, punten bij pauzes", "ContinuationStyleNoneTrailingEllipsis": "Geen, beletselteken bij pauzes (alleen aan het einde)", @@ -3370,7 +3409,6 @@ "ContinuationStyleLeadingTrailingEllipsis": "Beletselteken", "ContinuationStyleLeadingTrailingDash": "Streepje", "ContinuationStyleLeadingTrailingDashDots": "Streepje, maar punten bij pauzes", - "ContinuationStyleCustom": "Aangepast", "CpsLineLengthStyle": "Kar/s / regellengte", "CpsLineLengthStyleCalcAll": "Alle tekens tellen", "CpsLineLengthStyleCalcNoSpaceCpsOnly": "Alles behalve spaties tellen, alleen Kar/s", @@ -3383,27 +3421,11 @@ "CpsLineLengthStyleCalcNoSpaceOrPunctuationCpsOnly": "Geen spaties of leestekens, alleen Kar/s", "CpsLineLengthStyleCalcIgnoreArabicDiacritics": "Arabische diakritische tekens negeren", "CpsLineLengthStyleCalcIgnoreArabicDiacriticsNoSpace": "Arabische diakritische tekens negeren, geen spaties", - "TimeCodeModeHhMmSsMs": "HH:MM:SS:MS", - "TimeCodeModeHhMmSsFf": "HH:MM:SS:FF", - "SplitBehaviorPrevious": "Tussenruimte links van het splitspunt toevoegen (focus rechts)", - "SplitBehaviorHalf": "Tussenruimte in het midden van het splitspunt toevoegen (focus links)", - "SplitBehaviorNext": "Tussenruimte rechts van het splitspunt toevoegen (focus links)", - "SubtitleListActionNothing": "Niets", - "SubtitleListActionVideoGoToPositionAndPause": "Naar videopositie gaan en pauzeren", - "SubtitleListActionVideoGoToPositionAndPlay": "Naar videopositie gaan en afspelen", "SubtitleListActionVideoGoToPositionAndPlayCurrentAndPause": "Naar videopositie gaan, huidige afspelen en pauzeren", - "SubtitleListActionEditText": "Naar het tekstvak gaan", "SubtitleListActionVideoGoToPositionMinus1SecAndPause": "Naar videopositie - 1 s gaan en pauzeren", "SubtitleListActionVideoGoToPositionMinusHalfSecAndPause": "Naar videopositie - 0,5 s gaan en pauzeren", "SubtitleListActionVideoGoToPositionMinus1SecAndPlay": "Naar videopositie - 1 s gaan en afspelen", - "SubtitleListActionEditTextAndPause": "Naar het tekstvak gaan en pauzeren op de videopositie", - "AutoBackupEveryMinute": "Elke minuut", - "AutoBackupEveryXthMinute": "Om de {0} minuten", - "Profiles": "Profielen", - "AutoBackupDeleteAfterXMonths": "Automatische reservekopieën verwijderen na {0} maanden", "SearchSettingsDotDotDot": "Instellingen zoeken...", - "SyntaxColoring": "Syntax-kleuring", - "WaveformSpectrogram": "Golfvorm/spectrogram", "Network": "Netwerk", "FileTypeAssociations": "Bestandstypekoppelingen", "TextBoxColorTags": "Kleurtags (HTML/ASSA) in het ondertiteltekstvak", @@ -3436,8 +3458,6 @@ "DarkThemeForegroundColor": "Voorgrondkleur donker thema", "DarkThemeBackgroundColor": "Achtergrondkleur donker thema", "ShowGridLines": "Rasterlijnen tonen", - "ResetSettings": "Instellingen herstellen?", - "ResetSettingsDetail": "Hiermee worden alle instellingen op hun standaardwaarden teruggezet.\n\nDoorgaan?", "ShowHorizontalLineAboveToolbar": "Horizontale lijn boven de werkbalk tonen", "BookmarkColor": "Kleur van bladwijzer", "SpellCheckHighlightColor": "Markeringskleur spellingcontrole", @@ -3454,9 +3474,12 @@ "NewEmptyDefaultMs": "Standaardduur van nieuwe ondertitel (ms)", "TimeCodeUpDownStepMs": "Stapgrootte tijd omhoog/omlaag (ms)", "MoveSelectedLinesStepMs": "Stap voor sneltoets geselecteerde rijen verplaatsen (ms)", + "MoveStartEndStepMs": "Stapgrootte sneltoets begin/einde verplaatsen (ms)", "MoveLinesShortenNeighbor": "Rijen verplaatsen: vorige/volgende rij inkorten in plaats van te overlappen", "PromptBeforeDelete": "Bevestiging vragen vóór verwijderen", "RememberPositionAndSize": "Vensterpositie en -grootte onthouden", + "TitleBarFullFileName": "Volledig bestandspad in titelbalk tonen", + "ShowRecentFiles": "Recente bestanden tonen (om opnieuw te openen)", "OpenLastFileOnStart": "Laatst gebruikte bestand openen bij opstarten", "AutoSave": "Automatisch opslaan (het geopende bestand tijdens het bewerken opslaan)", "AutoBackupOn": "Automatische back-up", @@ -3479,23 +3502,13 @@ "ErrorBackgroundColor": "Achtergrondkleur bij fout", "WaveformDrawGridLines": "Rasterlijnen tekenen", "WaveformUseSkiaRenderer": "Experimentele snelle renderer gebruiken", - "WaveformCenterVideoPosition": "Videopositie centreren", "WaveformCenterVideoPositionAlsoWhenPaused": "Videopositie ook centreren tijdens pauze", + "WaveformSelectCurrentSubtitleWhilePaused": "Huidige ondertitel ook selecteren tijdens pauze", + "WaveformRememberZoom": "Zoom onthouden (horizontaal en verticaal)", "WaveformShowToolbar": "Werkbalk tonen", "WaveformShowToolbarEdit": "Werkbalk bewerken...", "WaveformShowToolbarEditLabel": "Werkbalkitems", "WaveformSpectrogramCombinedWaveformHeight": "Golfvorm/spectrogram gecombineerd, hoogte golfvorm %", - "ShowWaveformToolbarPlay": "Werkbalk: afspeelknop tonen", - "ShowWaveformToolbarRepeat": "Werkbalk: herhaalknop tonen", - "ShowWaveformToolbarRemoveBlankLines": "Werkbalk: knop voor lege rijen verwijderen tonen", - "ShowWaveformToolbarNew": "Werkbalk: knop voor nieuwe ondertitel tonen", - "ShowWaveformToolbarSetStart": "Werkbalk: knop voor begin instellen tonen", - "ShowWaveformToolbarSetEnd": "Werkbalk: knop voor einde instellen tonen", - "ShowWaveformToolbarStartAndOffsetTheRest": "Werkbalk: knop voor begin instellen en rest verschuiven tonen", - "ShowWaveformToolbarHorizontalZoom": "Werkbalk: horizontale zoomschuifregelaar tonen", - "ShowWaveformToolbarVerticalZoom": "Werkbalk: verticale zoomschuifregelaar tonen", - "ShowWaveformToolbarVideoPositionSlider": "Werkbalk: schuifregelaar voor videopositie tonen", - "ShowWaveformToolbarPlaybackSpeed": "Werkbalk: afspeelsnelheid tonen", "WaveformFocusTextboxAfterInsertNew": "Tekstvak focussen na invoegen", "WaveformInvertMouseWheel": "Muiswiel omkeren", "WaveformMouseWheelSetsVideoPosition": "Muiswiel stelt videopositie in", @@ -3514,27 +3527,8 @@ "WaveformSelectedColor": "Selectiekleur van golfvorm", "DownloadFfmpeg": "ffmpeg downloaden", "FixCommonErrorsSkipStep1": "Veelvoorkomende fouten corrigeren: stap 1 overslaan (correcties kiezen)", - "ShowToolbarNew": "Pictogram Nieuw tonen", - "ShowToolbarOpen": "Pictogram Openen tonen", - "ShowToolbarVideoFileOpen": "Pictogram Videobestand openen tonen", - "ShowToolbarSave": "Pictogram Opslaan tonen", - "ShowToolbarSaveAs": "Pictogram Opslaan als tonen", - "ShowToolbarFind": "Pictogram Zoeken tonen", - "ShowToolbarReplace": "Pictogram Vervangen tonen", - "ShowToolbarSpellCheck": "Pictogram Spellingcontrole tonen", - "ShowToolbarFixCommonErrors": "Pictogram Veelvoorkomende fouten herstellen tonen", - "ShowToolbarRemoveTextForHi": "Pictogram voor tekst voor slechthorenden verwijderen tonen", - "ShowToolbarVisualSync": "Pictogram voor visuele synchronisatie tonen", - "ShowToolbarBeautifyTimeCodes": "Pictogram voor tijdcodes verfraaien tonen", - "ShowToolbarBurnIn": "Pictogram voor inbranden tonen", - "ShowToolbarSettings": "Pictogram Instellingen tonen", - "ShowToolbarLayout": "Pictogram Indeling tonen", - "ShowToolbarHelp": "Pictogram Help tonen", - "ShowToolbarEncoding": "Codering tonen", - "ShowToolbarFrameRate": "Beeldsnelheid tonen", "ShowPluginsMenu": "Menu Plugins tonen", "ProxyAddress": "Proxyadres", - "Username": "Gebruikersnaam", "Password": "Wachtwoord", "DefaultFormat": "Standaardformaat", "DefaultSaveAsFormat": "Standaardformaat voor \"Opslaan als\"", @@ -3560,6 +3554,7 @@ "ShowAssaLayer": "ASSA-laagvak tonen", "WaveformCursorColor": "Kleur van golfvormcursor/-kop", "WaveformShotChangeColor": "Kleur shot-wijziging in golfvorm", + "WaveformGridColor": "Rasterkleur golfvorm", "WaveformParagraphLeftColor": "Kleur van linkerrand in golfvorm", "WaveformParagraphRightColor": "Kleur van rechterrand in golfvorm", "WaveformFancyHighColor": "Hoge kleur van golfvorm (Fancy)", @@ -3587,21 +3582,22 @@ "ResetSyntaxColoring": "Syntaxiskleuring herstellen", "ResetWaveform": "Golfvorm herstellen", "ResetRules": "Regels herstellen", - "UseFrameMode": "Framemodus gebruiken (hh.mm.ss.ff)", + "TimeCodeMode": "Tijdcodemodus", + "TimeCodeModeTime": "Tijd (hh:mm:ss,zzz)", + "TimeCodeModeFrames": "Frames (hh:mm:ss:ff)", + "TimeCodeModeFrameNumbers": "Framenummers", + "FrameModeSnapToVideoFrames": "Tijden op videoframes opslaan (niet frames binnen elke seconde)", "TextBoxLimitNewLines": "Aantal regels in het ondertiteltekstvak beperken", - "MpvVideoOutput": "Video-uitvoer (mpv)", "MpvOpenGl": "libmpv - OpenGL", "MpvSoftwareRendering": "libmpv - softwarerendering (traag)", + "MpvMetalRendering": "libmpv - Metal (experimenteel, herstart vereist)", "MpvWidRendering": "libmpv - rendering via systeemeigen venster-ID", "WaveFormsAndSpectrogramFoldersContainsX": "De mappen \"Waveforms\" en \"Spectrograms\" bevatten {0}", "DeleteWaveformAndSpectrogramFoldersQuestion": "Bestanden in \"Waveforms\" en \"Spectrogram\" verwijderen?", "WaveformGenerateSpectrogram": "Spectrogram genereren", "WaveformAutoGenerate": "Golfvorm automatisch genereren bij het openen van een video", "WaveformSpectrogramMode": "Spectrogrammodus", - "WaveformCenterOnSingleClick": "Centreren bij enkele klik", - "WaveformSingleClickSelectsSubtitle": "Ondertitel selecteren bij enkele klik", "WaveformRightClickSelectsSubtitle": "Ondertitel selecteren bij rechtsklikken", - "WaveformPauseOnSingleClick": "Pauzeren bij enkele klik", "WaveformDrawStyle": "Tekenstijl van golfvorm", "WaveformExtractAudioFormat": "Formaat geëxtraheerde audio", "WaveformExtractAudioSampleRate": "Samplefrequentie geëxtraheerde audio", @@ -3636,8 +3632,6 @@ "SaveAsBehaviorUseVideoFileName": "Naam van videobestand gebruiken", "SaveAsBehaviorUseSubtitleFileName": "Naam van ondertitelbestand gebruiken", "SaveAsAppendLanguageCodeTwoLetter": "Twee letters", - "SaveAsAppendLanguageCodeThreeLetter": "Drieletterige taalcode", - "SaveAsAppendLanguageCodeThreeLetterBibliographic": "Drie letters (ISO 639-2/B)", "SaveAsAppendLanguageCodeLanguageName": "Taalnaam", "SplitOddLineActionWeightTop": "Nadruk op bovenste regel", "SplitOddLineActionWeightBottom": "Nadruk op onderste regel", @@ -3656,8 +3650,7 @@ "UseFocusedButtonBackgroundColor": "Achtergrondkleur voor gefocuste knop gebruiken", "FocusedButtonBackgroundColor": "Achtergrondkleur van gefocuste knop", "ForceCrLfOnSave": "CR+LF afdwingen bij opslaan (tekstondertitelbestanden)", - "ShowWaveformToolbarPlayNext": "Werkbalk: knop Volgende afspelen tonen", - "ShowWaveformToolbarPlaySelection": "Werkbalk: knop Selectie afspelen tonen", + "LinuxClipboardUseExternalTool": "Tekst kopiëren via xclip/wl-copy (behoudt tekens met accenten)", "ShowFormatLimitWarning": "Waarschuwen bij opslaan wanneer regels de limieten van het formaat overschrijden (bijv. SCC 32 tekens/regel)", "TextBoxButtonShowAutoBreak": "Tekstvak: knop voor automatisch afbreken tonen", "TextBoxButtonShowUnbreak": "Tekstvak: knop voor samenvoegen tonen", @@ -3670,12 +3663,17 @@ "AllSettings": "Alle instellingen", "UiScale": "Interfaceschaal (%)", "FontScale": "Lettertypeschaal (%)", + "TextSelectionOpacity": "Dekking tekstselectie (%)", "WaveformToolbarItems": "Items op de golfvormwerkbalk", "MatchIconColorToDarkTheme": "Pictogramkleur afstemmen op voorgrondkleur van donker thema", "SubtitlePreviewProperties": "Eigenschappen van ondertitelvoorbeeld", "UsePositionFromSubtitleFile": "Positie uit ondertitelbestand gebruiken (TTML/PAC/EBU STL)", "MarginIsPartOfSubtitleArea": "Marge maakt deel uit van het ondertitelgebied", "TextJustify": "Regels uitvullen", + "Deinterlace": "Video deïnterlacen", + "DeinterlaceOff": "Uit", + "DeinterlaceAuto": "Auto (alleen geïnterlacete frames)", + "DeinterlaceAlways": "Altijd", "PixelWidthInfo": "Groene lijnen = maximale breedte | Rood gebied = tekst overschrijdt de limiet", "SpellCheckEngineHunSpelll": "Hunspell", "SpellCheckEngineMsWord": "MS Word", @@ -3697,115 +3695,76 @@ }, "Shortcuts": { "GeneralUnbreakNoSpaceCjk": "Samenvoegen zonder spatie (CJK)", - "Title": "Sneltoetsen", + "GeneralToggleBreak": "Afbreken/samenvoegen wisselen", "SearchShortcuts": "Sneltoetsen zoeken...", - "Filter": "Filter", - "CategoryGeneral": "Algemeen", "CategorySubtitleGridAndTextBox": "Ondertitellijst en tekstvak", "CategorySubtitleGrid": "Ondertitellijst", - "CategoryWaveform": "Golfvorm", "CategoryTextBox": "Tekstvak", "CategoryFile": "Bestand", "CategoryVideo": "Video", "CategorySync": "Synchronisatie", - "CategoryTranslate": "Vertalen", - "CategorySearch": "Zoeken", - "CategoryTools": "Gereedschappen", "CategoryAi": "AI", + "CategoryCustom": "Aangepast", + "EditCustomShortcut": "Aangepaste sneltoets bewerken", + "CustomShortcutTextBoxKeyHint": "Gebruik hier een toets met Ctrl/Alt - met een gewone toets kan niet meer in het tekstvak worden getypt.", + "CustomShortcutNumberX": "Aangepaste sneltoets #{0}", + "CustomShortcutNumberXY": "Aangepaste sneltoets #{0}: {1}", + "CustomShortcutStep": "Stap", + "CustomShortcutSteps": "Stappen", + "CustomShortcutStepRunCommand": "Opdracht uitvoeren", + "CustomShortcutStepInsertText": "Tekst invoegen", + "CustomShortcutStepReplace": "Zoeken en vervangen", + "CustomShortcutInsertAtCursor": "Bij cursor", + "CustomShortcutInsertAtStart": "Aan begin van tekst", + "CustomShortcutInsertAtEnd": "Aan einde van tekst", + "CustomShortcutSummaryRunCommandX": "Uitvoeren: {0}", + "CustomShortcutSummaryInsertXY": "\"{0}\" invoegen ({1})", + "CustomShortcutSummaryReplaceXY": "\"{0}\" vervangen door \"{1}\"", + "CustomShortcutSummaryReplaceRegexXY": "Regex \"{0}\" vervangen door \"{1}\"", + "CustomShortcutPickCommand": "Selecteer een opdracht om uit te voeren.", + "CustomShortcutEnterText": "Voer de in te voegen tekst in.", + "CustomShortcutEnterFind": "Voer de te zoeken tekst in.", + "CustomShortcutInvalidRegexX": "Ongeldige reguliere expressie: {0}", + "CustomShortcutStepsHint": "Stappen worden van boven naar beneden uitgevoerd. Tekststappen werken op de geselecteerde rijen; opdrachten die een venster openen, gaan verder nadat het is gesloten.", "ActiveIn": "Actief in", "ActiveInEverywhere": "Overal", - "GeneralSetupLikeSe4": "Instellen zoals Subtitle Edit 4 (thema, sneltoetsen, vervangingsregels)", - "GeneralMergeSelectedLines": "Geselecteerde rijen samenvoegen", - "GeneralMergeWithPrevious": "Samenvoegen met vorige", - "GeneralMergeWithNext": "Samenvoegen met volgende", - "GeneralMergeWithPreviousAndUnbreak": "Samenvoegen met vorige en tot één regel maken", - "GeneralMergeWithNextAndUnbreak": "Samenvoegen met volgende en tot één regel maken", - "GeneralMergeWithPreviousAndAutoBreak": "Samenvoegen met vorige en automatisch afbreken", - "GeneralMergeWithNextAndAutoBreak": "Samenvoegen met volgende en automatisch afbreken", - "GeneralMergeSelectedLinesAndAutoBreak": "Geselecteerde rijen samenvoegen en automatisch afbreken", "GeneralMergeSelectedLinesAndUnbreak": "Geselecteerde rijen samenvoegen en tot één regel maken", "GeneralMergeSelectedLinesAndUnbreakCjk": "Geselecteerde rijen samenvoegen en tot één regel maken zonder spatie (CJK)", - "GeneralMergeSelectedLinesOnlyFirstText": "Geselecteerde rijen samenvoegen, alleen de eerste tekst", "GeneralMergeSelectedLinesBilingual": "Geselecteerde rijen tweetalig samenvoegen", - "GeneralMergeWithPreviousBilingual": "Tweetalig samenvoegen met vorige", - "GeneralMergeWithNextBilingual": "Tweetalig samenvoegen met volgende", - "GeneralMergeOriginalAndTranslation": "Origineel en vertaling samenvoegen", "GeneralToggleTranslationMode": "Vertaalmodus in-/uitschakelen", - "GeneralSwitchOriginalAndTranslation": "Origineel en vertaling verwisselen", - "GeneralSwitchOriginalAndTranslationTextBoxes": "Tekstvakken van origineel en vertaling verwisselen", "GeneralToggleTranslationAndOriginalInPreviews": "Wisselen tussen vertaling en origineel in video-/audiovoorbeeld", "GeneralChooseLayout": "Indeling kiezen", - "GeneralLayoutChooseX": "Indeling {0}", - "GeneralPlayFirstSelected": "Eerste geselecteerde afspelen", - "GeneralGoToFirstSelectedLine": "Naar de eerste geselecteerde rij gaan", - "GeneralGoToNextEmptyLine": "Naar de volgende lege rij gaan", "GeneralGoToNextSubtitle": "Naar de volgende ondertitel gaan", "GeneralGoToNextSubtitlePlayTranslate": "Naar de volgende ondertitel gaan (afspelen en vertalen)", "GeneralGoToNextSubtitleCursorAtEnd": "Naar de volgende ondertitel gaan (cursor aan het einde)", "GeneralGoToPrevSubtitle": "Naar de vorige ondertitel gaan", "GeneralGoToPrevSubtitlePlayTranslate": "Naar de vorige ondertitel gaan (afspelen en vertalen)", - "GeneralGoToStartOfCurrentSubtitle": "Naar het begin van de huidige ondertitel gaan", - "GeneralGoToEndOfCurrentSubtitle": "Naar het einde van de huidige ondertitel gaan", - "GeneralGoToPreviousSubtitleAndFocusVideo": "Naar de vorige ondertitel gaan en de video focussen", - "GeneralGoToNextSubtitleAndFocusVideo": "Naar de volgende ondertitel gaan en de video focussen", - "GeneralGoToPrevSubtitleAndPlay": "Naar de vorige ondertitel gaan en afspelen", - "GeneralGoToNextSubtitleAndPlay": "Naar de volgende ondertitel gaan en afspelen", - "GeneralGoToPreviousSubtitleAndFocusWaveform": "Naar de vorige ondertitel gaan en de golfvorm focussen", - "GeneralGoToNextSubtitleAndFocusWaveform": "Naar de volgende ondertitel gaan en de golfvorm focussen", - "GeneralGoToLineNumber": "Ga naar rijnummer", "GeneralGoToFirstLine": "Ga naar eerste rij", "GeneralGoToLastLine": "Ga naar laatste rij", "AlsoSetVideoPosition": "Ook videopositie instellen", "GeneralGoToVideoPosition": "Naar videopositie gaan", "GeneralToggleItalic": "Cursief in-/uitschakelen", "GeneralToggleBold": "Vet in-/uitschakelen", - "GeneralToggleBookmarks": "Bladwijzers in-/uitschakelen", - "GeneralFocusTextBox": "Tekstvak focussen", - "GeneralToggleBookmarksWithText": "Bladwijzers met tekst in-/uitschakelen", - "GeneralEditBookmarks": "Bladwijzers bewerken", - "FileNew": "Nieuw", "GeneralToggleUnderline": "Onderstrepen in-/uitschakelen", "GeneralToggleBox": "Kader in-/uitschakelen (EBU STL)", "FileOpen": "Openen", "FileOpenKeepVideo": "Openen (video behouden)", "FileSave": "Opslaan", "FileSaveAs": "Opslaan als", - "FileSaveAll": "Alles opslaan", "FileSaveOriginal": "Origineel opslaan", - "FileSaveOriginalAs": "Origineel opslaan als", - "FileOpenOriginalSubtitle": "Oorspronkelijke ondertitel openen", - "FileCloseOriginalSubtitle": "Oorspronkelijke ondertitel sluiten", - "FileTranslatedSubtitle": "Vertaalde ondertitel", "FileCompare": "Vergelijken", "FileStatistics": "Statistieken", "FileImportPlainText": "Platte tekst importeren", - "FileImportBluRaySupForOcr": "Blu-ray SUP importeren voor OCR", - "FileImportBluRaySupForEdit": "Blu-ray SUP importeren om te bewerken", - "FileImportTimeCodes": "Tijdcodes importeren", "FileExportEbuStl": "EBU STL exporteren", "FileExportPac": "PAC exporteren", - "FileExportEdlClipName": "EDL-clipnaam exporteren", - "FileExportPlainText": "Platte tekst exporteren", - "FileExportCustomTextFormat1": "Aangepast tekstformaat 1 exporteren", - "FileExportCustomTextFormat2": "Aangepast tekstformaat 2 exporteren", - "FileExportCustomTextFormat3": "Aangepast tekstformaat 3 exporteren", "FileExit": "Afsluiten", "OpenSeDataFolder": "Map van Subtitle Edit openen", - "EditFind": "Zoeken", - "EditFindNext": "Volgende zoeken", "EditFindPrevious": "Vorige zoeken", - "EditReplace": "Vervangen", - "EditMultipleReplace": "Meervoudig vervangen", "FillSelectedLinesWithClipboard": "Geselecteerde rijen vullen met klembordtekst", - "EditModifySelection": "Selectie wijzigen", - "ListSelectAll": "Alles selecteren", - "ListSelectFirst": "Eerste selecteren", - "ListSelectLast": "Laatste selecteren", "ListInverseSelection": "Selectie omkeren", "ListDeleteSelection": "Selectie verwijderen", "DeleteSelectionEverywhere": "Selectie verwijderen (overal)", "RippleDeleteSelection": "Selectie verwijderen en doorschuiven", - "Settings": "Instellingen", "Assigned": "Toegewezen", "Unassigned": "Niet toegewezen", "PressedKeyX": "Ingedrukte toets: {0}", @@ -3813,6 +3772,7 @@ "DetectKey": "Toets detecteren", "Control": "Control", "Alt": "Alt", + "AltGr": "AltGr", "Win": "Win", "Shift": "Shift", "ControlMac": "⌃", @@ -3823,11 +3783,14 @@ "ResetShortcutsDetail": "Wilt u alle sneltoetsen op de standaardwaarden terugzetten?", "TogglePlayPause": "Afspelen/pauzeren", "ToggleLockTimeCodes": "Tijdcodes vergrendelen in-/uitschakelen", + "ToggleTimeCodeMode": "Tijdcodemodus wisselen (tijd/frames/framenummers)", + "ToggleScreenPrivacy": "Schermprivacy wisselen (bestandsnamen verbergen, daarna ook teksten)", + "ScreenPrivacyFileNamesHidden": "Schermprivacy: bestandsnamen verborgen", + "ScreenPrivacyFileNamesAndTextsHidden": "Schermprivacy: bestandsnamen en teksten verborgen", + "ScreenPrivacyOff": "Schermprivacy: uit", "DuplicateSelectedLines": "Geselecteerde rijen dupliceren", "SourceView": "Bronweergave", - "ShowAlignmentPicker": "Uitlijning", "AddOrEditBookmark": "Bladwijzer toevoegen of bewerken", - "ListBookmarks": "Bladwijzers tonen", "ToggleBookmark": "Bladwijzer in-/uitschakelen (geselecteerde rijen, zonder tekst)", "ToggleForcedSelectedLines": "Geforceerd in-/uitschakelen (geselecteerde rijen)", "GoToNextBookmark": "Naar de volgende bladwijzer gaan", @@ -3838,7 +3801,6 @@ "WaveformSetStartAndSetEndOfPreviousMinusGap": "Begin instellen en einde van vorige min tussenruimte instellen", "WaveformSetEndAndStartOfNextAfterGap": "Einde instellen en begin van volgende plus tussenruimte instellen", "WaveformSetEndAndStartOfNextAfterGapAndGoToNext": "Einde instellen, begin van volgende plus tussenruimte instellen en naar de volgende gaan", - "FetchFirstWordFromNextSubtitle": "Eerste woord van volgende ondertitel ophalen", "MoveLastWordToNextSubtitle": "Laatste woord naar de volgende ondertitel verplaatsen", "MoveLastWordFromFirstLineDownCurrentSubtitle": "Laatste woord van de eerste regel omlaag verplaatsen (huidige ondertitel)", "MoveFirstWordFromNextLineUpCurrentSubtitle": "Eerste woord van de volgende regel omhoog verplaatsen (huidige ondertitel)", @@ -3856,7 +3818,6 @@ "VideoGoToPreviousTimeCode": "Ga naar vorige tijdcode", "VideoGoToNextTimeCode": "Ga naar volgende tijdcode", "GoToNextLineAndSetVideoPosition": "Naar de volgende ondertitel gaan en de videopositie instellen", - "TextBoxDeleteSelectionNoClipboard": "Tekstvak: selectie verwijderen (niet naar klembord)", "TextBoxDeleteForward": "Tekstvak: Selectie of volgend teken verwijderen (voorwaarts verwijderen)", "TextBoxCut": "Tekstvak: knippen", "TextBoxCut2": "Tekstvak: knippen (alternatief)", @@ -3875,6 +3836,7 @@ "ListViewColumnTextUp": "Kolom, tekst omhoog", "ListViewColumnTextDown": "Kolom, tekst omlaag", "AutoTranslateSelectedLines": "Geselecteerde rijen automatisch vertalen...", + "AutoTranslateSelectedLinesNoPrompt": "Geselecteerde rijen automatisch vertalen (zonder vragen, laatste engine/talen gebruiken)", "SetAssaResolution": "ASSA-resolutie instellen (PlayResX/PlayResY)", "SetShortcutForX": "Sneltoets instellen voor \"{0}\"", "CommandFileNewKeepVideo": "Nieuw (video behouden)", @@ -3882,29 +3844,22 @@ "FileCloseOriginal": "Origineel sluiten", "FileCloseTranslation": "Vertaling sluiten", "RestoreAutoBackup": "Automatische back-up herstellen", - "OpenContainingFolder": "Map openen", "ImportTimeCodes": "Tijdcodes importeren", "ImportSubtitleWithManuallyChosenEncoding": "Ondertitel importeren met handmatig gekozen codering", "ExportBluRaySup": "Blu-ray SUP exporteren", "ExportCustomTextFormat": "Aangepast tekstformaat exporteren", "ExportPlainText": "Platte tekst exporteren", - "ShowHistory": "Geschiedenis tonen", "ToggleRightToLeft": "Rechts-naar-links in-/uitschakelen", "ModifySelection": "Selectie wijzigen", "AdjustDurations": "Duur aanpassen", "ApplyDurationLimits": "Duurlimieten toepassen", "BatchConvert": "Batchconversie", - "BridgeGaps": "Gaten overbruggen", "ApplyMinGap": "Min. tussenruimte toepassen", - "FixCommonErrors": "Veelvoorkomende fouten herstellen", "MakeEmptyTranslationFromCurrentSubtitle": "Lege vertaling maken van de huidige ondertitel", - "MergeLinesWithSameText": "Rijen met dezelfde tekst samenvoegen", - "MergeLinesWithSameTimeCodes": "Rijen met dezelfde tijdcodes samenvoegen", "SplitBreakLongLines": "Lange regels splitsen/herverdelen", "MergeShortLines": "Korte rijen samenvoegen", "MergeContinuationLines": "Vervolgregels samenvoegen", "SnapAllTimesToFrames": "Alle tijden uitlijnen op frames", - "RemoveTextForHearingImpaired": "Tekst voor slechthorenden verwijderen", "JoinSubtitles": "Ondertitels samenvoegen", "SplitSubtitle": "Ondertitel splitsen", "SpellCheck": "Spellingcontrole", @@ -3926,7 +3881,6 @@ "AdjustAllTimes": "Alle tijden aanpassen", "VisualSync": "Visueel synchroniseren", "TranslateViaCopyPaste": "Vertalen via kopiëren/plakken", - "Shortcuts": "Sneltoetsen", "WordLists": "Woordenlijsten", "ChooseUiLanguage": "Interfacetaal kiezen", "ChooseRuleProfile": "Regelprofiel kiezen", @@ -3954,18 +3908,23 @@ "GoToSubtitlePositionAndPause": "Ga naar ondertitelpositie en pauzeer", "SetVideoPositionCurrentSubtitleEnd": "Videopositie op het einde van de huidige rij instellen", "ToggleAudioTracks": "Audiosporen wisselen", - "ListErrors": "Fouten tonen", "GoToNextError": "Naar de volgende fout gaan", "GoToPreviousError": "Naar de vorige fout gaan", "AddNameToNameList": "Naam aan namenlijst toevoegen", "FindDoubleWords": "Dubbele woorden zoeken", "FindDoubleLines": "Dubbele rijen zoeken", "ColorX": "Kleur {0}", - "RemoveColor": "Kleur verwijderen", "SurroundWith": "Omgeven met...", "SurroundWithXY": "Omringen met {0}/{1}", - "RepeatLine": "Rij herhalen", "SurroundWithNumberX": "Omsluiten met #{0}", + "SurroundWithBehavior": "Gedrag", + "SurroundWithBehaviorToggle": "Wisselen (toevoegen, of verwijderen indien aanwezig)", + "SurroundWithBehaviorAdd": "Toevoegen (elke keer)", + "SurroundWithBehaviorRemove": "Verwijderen (alle)", + "SurroundWithBehaviorRemoveOnce": "Verwijderen (één per keer)", + "SurroundWithWorksOn": "Werkt op", + "SurroundWithScopeSelectionOrText": "Selectie, anders hele tekst", + "SurroundWithScopeEachLine": "Elke regel", "SearchVia": "Zoeken via", "SearchViaX": "Zoeken via {0}", "SearchViaNumberX": "Zoeken via #{0}", @@ -3993,6 +3952,11 @@ "CopySubtitlePathToClipboard": "Pad van ondertitel naar klembord kopiëren", "CopySubtitleOriginalPathToClipboard": "Pad van oorspronkelijke ondertitel naar klembord kopiëren", "FocusTextBox": "Tekstvak focussen", + "FocusSubtitleListView": "Focus op ondertitellijst", + "FocusWaveform": "Focus op golfvorm", + "FocusOriginalTextBox": "Focus op origineel tekstvak", + "TextBoxGoToStart": "Tekstvak, naar begin gaan", + "TextBoxGoToEnd": "Tekstvak, naar einde gaan", "SortByNumber": "Sorteren op nr.", "SortByStartTime": "Sorteren op tijd van \"Toon\"", "SortByEndTime": "Sorteren op tijd van \"Verberg\"", @@ -4017,7 +3981,6 @@ "AssaStyles": "ASSA-stijlen", "AssaProperties": "ASSA-eigenschappen", "AssaAttachments": "ASSA-bijlagen", - "AssaVideoColorPicker": "ASSA-kleurenkiezer voor video", "RecalculateDurationSelectedLines": "Duur opnieuw berekenen (geselecteerde rijen)", "SetDurationMaxCpsSelectedLines": "Duur instellen op max. Kar/s (geselecteerde rijen)", "ToggleWaveformAndSpectrogramHeight": "Verdeelde hoogte van golfvorm/spectrogram wisselen", @@ -4033,7 +3996,7 @@ "GoogleIt": "Google het (geselecteerde tekst)", "SetActorX": "Acteur instellen: {0}", "SetNewActor": "Nieuwe acteur instellen...", - "SetActorSlotXTitle": "Acteursplek {0}", + "SetStyleX": "Stijl instellen: {0}", "ToggleDialogDashes": "Dialoogstreepjes in-/uitschakelen", "MergeWithLineBeforeAsDialog": "Samenvoegen met rij ervoor als dialoog", "MergeWithLineAfterAsDialog": "Samenvoegen met rij erna als dialoog", @@ -4045,6 +4008,10 @@ "MoveStartOneFrameForwardKeepGapPrev": "Begin één frame vooruit (tussenruimte met vorige behouden indien dichtbij)", "MoveEndOneFrameBackKeepGapNext": "Einde één frame terug (tussenruimte met volgende behouden indien dichtbij)", "MoveEndOneFrameForwardKeepGapNext": "Einde één frame vooruit (tussenruimte met volgende behouden indien dichtbij)", + "MoveStartXMsBack": "Begin X ms terug verplaatsen (X ingesteld in Instellingen, ook in Visueel synchroniseren)", + "MoveStartXMsForward": "Begin X ms vooruit verplaatsen (X ingesteld in Instellingen, ook in Visueel synchroniseren)", + "MoveEndXMsBack": "Einde X ms terug verplaatsen (X ingesteld in Instellingen, ook in Visueel synchroniseren)", + "MoveEndXMsForward": "Einde X ms vooruit verplaatsen (X ingesteld in Instellingen, ook in Visueel synchroniseren)", "MoveSelectedLinesXMsBack": "Geselecteerde rijen X ms terug verplaatsen (X in te stellen in Instellingen)", "MoveSelectedLinesXMsForward": "Geselecteerde rijen X ms vooruit verplaatsen (X in te stellen in Instellingen)", "MoveSelectedLinesAndForwardXMsBack": "Geselecteerde rijen en alle volgende X ms terug verplaatsen (X in te stellen in Instellingen)", @@ -4061,8 +4028,6 @@ "WordLists": { "Title": "Woordenlijsten", "AddName": "Naam toevoegen", - "AddWord": "Woord toevoegen", - "AddPair": "Paar toevoegen", "NameAndIgnoreList": "Namen-/negeerlijst", "UserWords": "Gebruikerswoordenlijst", "OcrFixList": "OCR-correctielijst", @@ -4079,7 +4044,6 @@ "NoPluginsInstalled": "Geen plugins geïnstalleerd", "GetPluginsOnline": "Plugins online ophalen...", "GetPluginsTitle": "Plugins ophalen", - "Enabled": "Ingeschakeld", "Disabled": "Uitgeschakeld", "NotSupportedOnThisOs": "Niet ondersteund op dit besturingssysteem", "RemovePluginXConfirm": "Plugin '{0}' verwijderen? Hiermee wordt de pluginmap verwijderd:{1}", @@ -4096,10 +4060,8 @@ "DownloadingXPercent": "Downloaden {0}%...", "PluginXInstalled": "'{0}' geïnstalleerd.", "CouldNotInstallPluginX": "Kon '{0}' niet installeren: {1}", - "NotInstalled": "Niet geïnstalleerd", "Install": "Installeren", "Reinstall": "Opnieuw installeren", - "Update": "Bijwerken", "InstalledX": "Geïnstalleerd ({0})", "UpdateAvailableXToY": "Update beschikbaar ({0} → {1})", "ApplyPluginToWhichLinesX": "'{0}' toepassen op:", @@ -4204,6 +4166,8 @@ "BinaryImageCompareInspectImageMatches": "\"Binaire afbeeldingsvergelijking\" - afbeeldingsovereenkomsten inspecteren", "SaveBlankTextTitle": "Lege tekst opslaan?", "SaveBlankTextPrompt": "Lege tekst opslaan voor afbeelding?", + "DrawnLinesDoNotMatchTitle": "Lijnen komen niet overeen met afbeelding", + "DrawnLinesDoNotMatchPrompt": "Sommige getekende lijnen komen niet overeen met de afbeelding (groene lijnen moeten op het teken liggen, rode lijnen ernaast), dus dit teken wordt niet herkend.\n\nToch opslaan?", "YesAndNeverAskAgain": "Ja en nooit meer vragen", "ImportTextFromSubtitleDotDotDot": "Tekst importeren uit ondertitel...", "ImportTextFromSubtitleOverwritePrompt": "Sommige rijen hebben al OCR-tekst. Bestaande tekst overschrijven?", @@ -4229,7 +4193,6 @@ "VobSubColorsTitle": "VobSub/DVD-kleuren", "VobSubColorsHeader": "De vier VobSub-kleuren aanpassen", "VobSubColorsDescription": "VobSub/DVD-ondertitels gebruiken vier geïndexeerde kleuren: achtergrond, patroon, nadruk 1 en nadruk 2. Kies hieronder elke kleur om het ingebouwde palet te overschrijven - handig wanneer de originele kleuren een slecht OCR-contrast geven.", - "VobSubColorBackground": "Achtergrond", "VobSubColorPattern": "Patroon", "VobSubColorEmphasis1": "Nadruk 1", "VobSubColorEmphasis2": "Nadruk 2", @@ -4263,9 +4226,26 @@ "CrispEmbedSettingsTitle": "CrispEmbed-instellingen", "CrispEmbedDescription": "Lokale OCR-engine met meerdere modelbackends - download hier de engine en de modellen.", "TrainNOcrDatabase": "nOCR-database trainen...", + "OptimizeNOcrDatabase": "nOCR-database optimaliseren...", + "OptimizeNOcrDatabaseQuestionXY": "De lijnen in nOCR-database \"{0}\" verwijderen die tekens niet van vergelijkbare tekens onderscheiden?\n\nDit maakt de database kleiner en OCR sneller, meestal met minder fouten. Duurt tot een minuut.\n\nEr wordt een back-up opgeslagen als \"{1}\".", + "OptimizingNOcrDatabaseXY": "nOCR-database optimaliseren... {0}/{1}", + "NOcrDatabaseOptimizedXYZ": "nOCR-database \"{0}\" geoptimaliseerd: {1:#,##0} -> {2:#,##0} lijnen.", "StartTraining": "Training starten", "AbortTraining": "Training afbreken", "TrainingOptions": "Trainingsopties", + "TrainNOcrSubtitle": "Rendert tekens in geïnstalleerde lettertypen en leert nOCR hun vormen. Kies de lettertypen die uw ondertitels gebruiken.", + "XFontsSelected": "{0:#,##0} geselecteerd", + "SubtitleFonts": "Ondertitellettertypen", + "EmptyDatabase": "Lege database", + "StartFromDatabase": "Beginnen met", + "StartFromDatabaseHint": "De getrainde tekens toevoegen aan een kopie van een bestaande database (aanbevolen) of met een lege beginnen", + "FontSizes": "Lettergrootten", + "FontSizesHint": "Lettergrootten in pixels om mee te renderen, gescheiden door komma's, bijv. \"30, 40\"", + "NumberOfLineSegmentsHint": "Lijnen gecontroleerd per teken - meer is strenger maar langzamer", + "EnterDatabaseName": "Voer een naam in voor de database", + "TrainingFontXSizeYCharacterZ": "\"{0}\" trainen op {1} px: {2}", + "XLearned": "{0:#,##0} geleerd", + "XSkipped": "{0:#,##0} overgeslagen", "CharactersToTrain": "Te trainen tekens", "LetterCombinationsToTrain": "Lettercombinaties die tot één afbeelding gesplitst kunnen worden", "ImportCharactersFromSubtitleFile": "Tekens importeren uit ondertitelbestand...", @@ -4292,6 +4272,8 @@ "DrawZoomOut": "Uitzoomen (Ctrl+-)", "DrawResetView": "Weergave herstellen (Ctrl+0)", "DrawToggleGrid": "Raster in-/uitschakelen (Ctrl+G)", + "DrawTogglePreview": "Voorbeeld aan/uit (F9)", + "DrawPreviewFailed": "Voorbeeld mislukt - ffmpeg met libass (subtitles-filter) is vereist", "DrawCopyToClipboard": "Naar klembord kopiëren (Ctrl+C)", "DrawShapes": "Vormen", "DrawSelectedPoint": "Geselecteerd punt", @@ -4299,16 +4281,86 @@ "DrawSelectedLayer": "Geselecteerde laag", "DrawToolX": "Gereedschap: {0}", "DrawHelpText": "Klik om punten toe te voegen • Enter/F8 om de vorm te sluiten • Shift+slepen om te pannen • Ctrl+scrollen om te zoomen", + "DrawMoveToLayer": "Naar laag verplaatsen", + "DrawFlipHorizontal": "Horizontaal spiegelen", + "DrawFlipVertical": "Verticaal spiegelen", + "DrawHideShape": "Vorm verbergen", + "DrawShowShape": "Vorm tonen", + "DrawHideLayer": "Laag verbergen", + "DrawShowLayer": "Laag tonen", + "DrawDeleteLayer": "Laag verwijderen", + "DrawDeletePoint": "Punt verwijderen", + "DrawCancelDrawing": "Tekenen annuleren", + "DrawLayerX": "Laag {0}", + "DrawImportSvg": "SVG-afbeelding importeren...", + "DrawRotateClockwise": "90° rechtsom draaien", + "DrawConvertToCurve": "Segment omzetten naar curve", + "DrawBackground": "Achtergrond", + "DrawPosition": "Positie", + "DrawZoom": "Zoom", + "DrawShapeTool": "Vormgereedschap", + "DrawColorPickerTool": "Kleur kiezen uit een vorm of de achtergrond", + "DrawShapeLibrary": "Vormenbibliotheek", + "DrawShapeLibraryHint": "Sleep op het canvas om een vorm te plaatsen, of klik voor de standaardgrootte. Shift behoudt de verhoudingen.", + "DrawAddToLibrary": "Aan vormenbibliotheek toevoegen...", + "DrawRemoveFromLibrary": "Uit vormenbibliotheek verwijderen", + "DrawShapeName": "Vormnaam", + "DrawCategorySpeechBubbles": "Tekstballonnen", + "DrawCategoryArrows": "Pijlen", + "DrawCategoryBasicShapes": "Basisvormen", + "DrawCategorySymbols": "Symbolen", + "DrawCategoryMyShapes": "Mijn vormen", + "DrawShapeSpeechBubble": "Tekstballon", + "DrawShapeSpeechBubbleRight": "Tekstballon, staart rechts", + "DrawShapeSpeechBubbleUp": "Tekstballon, staart omhoog", + "DrawShapeOvalBubble": "Ovale tekstballon", + "DrawShapeBoxBubble": "Rechthoekige tekstballon", + "DrawShapeThoughtBubble": "Gedachtewolk", + "DrawShapeShoutBubble": "Schreeuwballon", + "DrawShapeCaptionBox": "Bijschriftkader", + "DrawShapeArrow": "Pijl", + "DrawShapeDoubleArrow": "Dubbele pijl", + "DrawShapeChevron": "Chevron", + "DrawShapeCurvedArrow": "Gebogen pijl", + "DrawShapeRoundedRectangle": "Afgeronde rechthoek", + "DrawShapeTriangle": "Driehoek", + "DrawShapeDiamond": "Ruit", + "DrawShapePentagon": "Vijfhoek", + "DrawShapeHexagon": "Zeshoek", + "DrawShapeOctagon": "Achthoek", + "DrawShapeStar": "Ster", + "DrawShapeRing": "Ring", + "DrawShapePlus": "Plus", + "DrawShapeBanner": "Banier", + "DrawShapeHeart": "Hart", + "DrawShapeCheck": "Vinkje", + "DrawShapeMusicNote": "Muzieknoot", + "DrawShapeMusicNotes": "Muzieknoten", + "DrawShapeLightning": "Bliksem", + "DrawShapeCloud": "Wolk", + "DrawShapeMoon": "Maan", + "DrawShapeBadge": "Badge", + "DrawSize": "Grootte", + "DrawLayer": "Laag", + "DrawBackgroundVideoFrame": "Videoframe (huidige positie)", + "DrawBackgroundVideoFrameAt": "Videoframe op...", + "DrawBackgroundImage": "Afbeeldingsbestand...", + "DrawBackgroundNone": "Geen achtergrond", + "DrawBackgroundStretch": "Afbeelding uitrekken tot canvas", + "DrawBackgroundOpacity": "Dekking achtergrond", + "DrawBackgroundVideoFrameFailed": "Kon geen videoframe ophalen op {0}.", + "DrawImages": "Afbeeldingen", + "DrawConvertToLine": "Segment omzetten naar lijn", + "DrawConvertShapeToCurves": "Alle lijnen omzetten naar curven", + "DrawConvertShapeToLines": "Alle curven omzetten naar lijnen", + "DrawRotateCounterClockwise": "90° linksom draaien", + "DrawSvgImages": "SVG-afbeeldingen", + "DrawSvgNoShapes": "Geen gevulde vormen of lijnen gevonden in het SVG-bestand.", "ProgressBarTitle": "ASSA-voortgangsbalk", "ProgressBarSettings": "Voortgangsbalk", - "ProgressBarPosition": "Positie", - "ProgressBarBottom": "Onder", - "ProgressBarTop": "Boven", "MouseOverColor": "Kleur bij muisaanwijzer", "ClickedColor": "Kleur bij klikken", "ProgressBarForeColor": "Voorgrondkleur", - "ProgressBarBackColor": "Achtergrondkleur", - "ProgressBarStyle": "Stijl", "ProgressBarSquareCorners": "Rechte hoeken", "ProgressBarRoundedCorners": "Afgeronde hoeken", "ProgressBarChapters": "Hoofdstukken", @@ -4317,8 +4369,6 @@ "ProgressBarXAdjustment": "X-aanpassing", "ProgressBarYAdjustment": "Y-aanpassing", "ProgressBarTextAlignment": "Tekstuitlijning", - "ProgressBarTakePosFromVideo": "Positie uit video overnemen", - "ProgressBarPreview": "Voorbeeld", "FontCollectorTitle": "Lettertypeverzamelaar", "FontCollectorUsedIn": "Gebruikt in", "FontCollectorFontFiles": "Lettertypebestand(en)", @@ -4365,9 +4415,7 @@ "ResolutionResamplerVideoDiffers": "De ondertitelresolutie ({0}x{1}) wijkt af van de videoresolutie ({2}x{3}).\nDe ondertitel aanpassen aan de video?", "ResolutionResamplerAskOnVideoOpen": "Vragen wanneer een video met een andere resolutie wordt geopend", "BackgroundBoxGenerator": "Achtergrondkader genereren", - "BackgroundBoxPadding": "Opvulling", "BackgroundBoxFillWidth": "Vulbreedte", - "BackgroundBoxBoxColor": "Kaderkleur", "BackgroundBoxRadius": "Straal", "BackgroundBoxCircle": "Cirkel", "BackgroundBoxSpikes": "Stekels", @@ -4379,12 +4427,9 @@ "BackgroundBoxTornPaperDouble": "Gescheurd papier (boven en onder)", "BackgroundBoxStarburst": "Stervorm", "BackgroundBoxScroll": "Rol / perkament", - "StylesTitle": "Geavanceerde Sub Station Alpha-stijlen", "StylesInFile": "Stijlen in bestand", "StylesSaved": "Stijlen opgeslagen", - "Category": "Categorie", "AllCategories": "[Alle]", - "DefaultCategory": "Standaard", "NewCategory": "Nieuwe categorie...", "RenameCategory": "Categorie hernoemen...", "DeleteCategory": "Categorie verwijderen", @@ -4407,13 +4452,26 @@ "Graphics": "Afbeeldingen", "CopyToStorageStyles": "Naar opslagstijlen kopiëren", "CopyToFileStyles": "Naar bestandsstijlen kopiëren", + "StyleXAlreadyExistsInStorage": "Er bestaat al een stijl met de naam \"{0}\" in de opslag.\n\nOverschrijven, of beide behouden (de kopie krijgt een nieuwe naam)?", + "StyleXAlreadyExistsInFile": "Er bestaat al een stijl met de naam \"{0}\" in het bestand.\n\nOverschrijven, of beide behouden (de kopie krijgt een nieuwe naam)?", + "StyleNameCannotBeEmpty": "Een stijlnaam mag niet leeg zijn.", + "StyleNameXIsUsedMoreThanOnce": "De stijlnaam \"{0}\" wordt door meer dan één stijl in het bestand gebruikt.", + "Overwrite": "Overschrijven", + "KeepBoth": "Beide behouden", + "DoThisForAllConflictingStyles": "Dit doen voor alle conflicterende stijlen", "SetStyleAsDefault": "Stijl als standaard instellen", "TakeUsagesFromDotDotDot": "Gebruik overnemen van...", "ReplaceStyleWithDotDotDot": "Stijl vervangen door...", "NoAttachmentsFound": "Geen bijlagen gevonden in het geselecteerde ASSA-bestand.", "DeleteStyleQuestion": "Stijl verwijderen?", "DeleteStylesQuestion": "Stijlen verwijderen?", + "DeleteXStylesQuestion": "Wilt u {0} stijlen verwijderen?", + "DeleteXStylesFromStorageQuestion": "Wilt u {0} stijlen uit de opslag verwijderen?", + "DeleteStyleXFromStorageQuestion": "Wilt u stijl \"{0}\" uit de opslag verwijderen?", + "DeleteStyleXFromFileQuestion": "Wilt u stijl \"{0}\" uit het huidige bestand verwijderen?", "OpenStyleImportFile": "Ondertitelbestand openen om stijlen uit te importeren", + "StyleImportFiles": "Stijlbestanden (.ass, .sty, Subtitle Edit 4-categorieën .template)", + "SsaStyleImportFiles": "Stijlbestanden (.ssa, .ass, .sty)", "Primary": "Primair", "Secondary": "Secundair", "ApplyOverrideTags": "Override-tags toepassen", @@ -4428,7 +4486,6 @@ "SpaceIncrease": "Spatiëring vergroten", "PopIn": "Inploppen", "SpinIn": "Draaiend binnenkomen", - "PlayCurrent": "Huidige afspelen", "SetPosition": "Positie instellen", "Rotation": "Rotatie", "ScaleX": "Schaal X", @@ -4472,10 +4529,8 @@ "AdvancedEffectOldMovieDescription": "Simuleert een klassieke 35mm-filmprojector met ruis, vignettering en krassen", "AdvancedEffectNeonBurst": "Neonuitbarsting", "AdvancedEffectNeonBurstDescription": "Energieke ondertitels in 'Shorts'-stijl met neongloed en een inschalende 'pop'-animatie", - "AdvancedEffectSettings": "Instellingen", "AdvancedEffectSnowFlakeCount": "Aantal vlokken", "AdvancedEffectStarfieldStarCount": "Aantal sterren", - "AdvancedEffectStarfieldSpeed": "Snelheid", "AdvancedEffectGlitch": "Glitch", "AdvancedEffectGlitchDescription": "Digitale glitch met chromatische aberratie, witte flitsen en vervormingslagen", "AdvancedEffectBounceIn": "Inveren", @@ -4484,8 +4539,6 @@ "AdvancedEffectFirefliesDescription": "Warm gloeiende geel-amberkleurige stippen die natuurlijk over het scherm zweven met een pulserende gloed", "AdvancedEffectMatrix": "Matrix", "AdvancedEffectMatrixDescription": "Vallende groene tekenkolommen waarbij de ondertiteltekst letter voor letter uit de regen wordt opgebouwd", - "AdvancedEffectAudioBars": "Audiobalken (golfvorm)", - "AdvancedEffectAudioBarsDescription": "Geanimeerde equalizerbalken aangestuurd door de audiogolfvorm — de balken scrollen naar links als een golfvorm en pulseren met de audio", "AdvancedEffectAudioPulse": "Audiopuls op tekst", "AdvancedEffectAudioPulseDescription": "De ondertiteltekst gloeit en pulseert met het audiovolume — stille passages tonen normale tekst, luide pieken voegen een gekleurde halo en een lichte vergroting toe", "AdvancedEffectConfetti": "Confetti-uitbarsting", @@ -4499,7 +4552,6 @@ "AdvancedEffectFancyKaraokeGlow": "Gloed van actief woord", "AdvancedEffectFancyKaraokeActiveColor": "Actieve kleur", "AdvancedEffectFancyKaraokeInactiveColor": "Inactieve kleur", - "AdvancedEffectFancyKaraokeInactiveOpacity": "Dekking inactief (0-255)", "AdvancedEffectWordSpacingPixels": "Spatiëring (pixels)", "AdvancedEffectSlowZoomOut": "Langzaam uitzoomen", "AdvancedEffectSlowZoomOutDescription": "De tekst begint iets groter en krimpt geleidelijk naar de normale grootte gedurende de duur van de ondertitel", @@ -4515,7 +4567,6 @@ "AdvancedEffectSlideInLeftDescription": "De tekst schuift van buiten beeld links naar binnen, blijft staan en verdwijnt weer naar links", "AdvancedEffectSlideInRight": "Van rechts inschuiven", "AdvancedEffectSlideInRightDescription": "De tekst schuift van buiten beeld rechts naar binnen, blijft staan en verdwijnt weer naar rechts", - "AdvancedEffectFadeInOut": "In-/uitfaden", "AdvancedEffectFadeInOutDescription": "De tekst faadt in aan het begin en uit aan het einde van elke ondertitel", "AdvancedEffectWordFlip3D": "3D-woordflip", "AdvancedEffectWordFlip3DDescription": "Elk woord klapt met een 3D-flip en elastische pop naar binnen terwijl het wordt uitgesproken — moderne 'Shorts'-ondertitelstijl", diff --git a/src/ui/Assets/Languages/English.json b/src/ui/Assets/Languages/English.json index b59e930fa55..e85086e3f05 100644 --- a/src/ui/Assets/Languages/English.json +++ b/src/ui/Assets/Languages/English.json @@ -1,6 +1,6 @@ { "title": "Subtitle Edit", - "version": "v5.3.0-rc4", + "version": "v5.3.0", "translatedBy": "", "cultureName": "en-US", "general": { @@ -1093,7 +1093,7 @@ "deleteTextAndShiftCellsUp": "Delete text and shift cells up", "endTimeMustBeAfterStartTime": "End time must be after start time.", "errorLoad7Zip": "This file seems to be a compressed 7-Zip file.\n\nSubtitle Edit cannot open compressed files.", - "errorLoadBinaryZeroes": "Sorry, this file contains only binary zeroes!\n\nIf you have edited this file with Subtitle Edit you might be able to find a backup via the menu item File -> Restore auto-backup...", + "errorLoadBinaryZeroes": "Sorry, this file contains only binary zeroes!\n\nIf you have edited this file with Subtitle Edit you might be able to find a backup via the menu item File -> Restore auto-backup...", "errorLoadGZip": "This file seems to be a compressed GZip file.\n\nSubtitle Edit cannot open compressed files.", "errorLoadLargeFile": "This file is too large to open as a subtitle file.", "errorLoadVideoFilePrompt": "This file seems to be a video file:\n{0}\n\nYou can open a video via \"Video -> Open video\" or by dragging and dropping a video onto the video player.\n\nDo you want to open it as a video file?", diff --git a/src/ui/Assets/Languages/Finnish.json b/src/ui/Assets/Languages/Finnish.json index f4f95a75483..5c4e248e1ff 100644 --- a/src/ui/Assets/Languages/Finnish.json +++ b/src/ui/Assets/Languages/Finnish.json @@ -1,6 +1,6 @@ { "title": "Subtitle Edit", - "version": "v5.2.0-rc7", + "version": "v5.3.0", "translatedBy": "LibreTranslate", "cultureName": "fi-FI", "general": { @@ -71,6 +71,24 @@ "abort": "Keskeytä", "actor": "Näyttelijä", "actors": "Näyttelijät", + "setActorDotDotDot": "Aseta näyttelijä...", + "actorPickerTitle": "Aseta näyttelijä", + "actorPickerFilterHint": "Kirjoita suodattaaksesi tai kirjoita uuden näyttelijän nimi", + "actorPickerKeysHint": "Aseta näyttelijä painamalla numeroa tai Enter. Alt+Ylös/Alas muuttaa järjestystä, Delete poistaa näyttelijän.", + "actorPickerNewActorX": "Uusi näyttelijä \"{0}\"", + "actorPickerLinesSelectedX": "Valittuja rivejä: {0}", + "setStyleDotDotDot": "Aseta tyyli...", + "stylePickerTitle": "Aseta tyyli", + "stylePickerFilterHint": "Kirjoita suodattaaksesi tai kirjoita uuden tyylin nimi", + "stylePickerKeysHint": "Aseta tyyli kaikille valituille riveille painamalla numeroa (1-9, 0).", + "stylePickerNewStyleX": "Uusi tyyli \"{0}\"", + "stylePickerNewStyleInfo": "Paina Enter lisätäksesi tämän tyylin oletusasetuksilla ja asettaaksesi sen valituille riveille. Voit muuttaa sen fonttia, värejä ja sijaintia myöhemmin kohdassa \"Hallitse tyylejä...\".", + "stylePickerCurrentStyleX": "Nykyinen tyyli: {0}", + "stylePickerLinesUsingStyleX": "Tätä tyyliä käyttäviä rivejä: {0}", + "stylePickerMarginsXYZ": "Vasen {0}, oikea {1}, pystysuora {2}", + "stylePickerSetStyle": "_Aseta tyyli", + "stylePickerManageStyles": "Hallitse tyylejä...", + "stylePickerManageStylesHint": "Sulje tämä ikkuna ja avaa tyylien hallinta muokataksesi fontteja, värejä, sijaintia ja muuta", "add": "Lisää", "addDotDotDot": "Lisää...", "addToNamesListCaseSensitive": "Lisää nimiluetteloon (kirjainkoko merkitsevä)", @@ -79,7 +97,6 @@ "adjustAlpha": "Säädä alfa (läpinäkyvyys)", "adjustDisplayDuration": "Säädä näyttöaika", "adjustment": "Säätö", - "advanced": "Lisäasetukset", "advancedSettings": "Lisäasetukset", "after": "Jälkeen", "alignment": "Kohdistus", @@ -115,7 +132,6 @@ "batchMode": "Eräajotila", "bdnXml": "BDN/xml", "before": "Ennen", - "beforeX": "Ennen \"{0}\"", "beginning": "Alku", "bluRaySup": "Blu-ray (sup)", "bold": "Lihavoitu", @@ -127,7 +143,6 @@ "bookmarkEdit": "Muokkaa kirjanmerkkiä", "bookmarkEditForSelectedLinesX": "Muokkaa kirjanmerkkiä {0} valitulle riville", "bookmarksList": "Luettelo kirjanmerkeistä", - "borderColor": "Reunan väri", "borderStyle": "Reunan tyyli", "bottomCenter": "Alhaalla keskellä", "bottomLeft": "Alhaalla vasemmalla", @@ -150,7 +165,6 @@ "changeCasing": "Vaihda kirjainkoko", "changeFormatting": "Vaihda muotoilua", "changeFrameRate": "Vaihda kuvataajuutta", - "changeLanguageFilter": "Vaihda kielisuodatinta...", "changeOnce": "Muuta kerran", "changeSpeed": "Muuta nopeutta", "character": "Merkki", @@ -172,7 +186,6 @@ "conversionCancelledByUser": "Käyttäjä peruutti muunnoksen", "convert": "Muunna", "converted": "Muunnettu", - "convertingDotDotDot": "Muunnetaan...", "convertingXofYDotDoDot": "Muunnetaan {0:#,###,##0}/{1:#,###,##0}...", "copy": "Kopioi", "copyImageToClipboard": "Kopioi kuva leikepöydälle", @@ -180,7 +193,6 @@ "couldNotSaveFileXErrorY": "Tiedostoa \"{0}\" ei voitu tallentaa. Virhe: {1}", "count": "Laske", "cps": "Merkkiä sekunnissa", - "currentSubtitle": "Nykyinen tekstitys", "currentVideoPosition": "Nykyinen videon sijainti", "cut": "Leikkaa", "dark": "Tumma", @@ -225,7 +237,6 @@ "expand": "Laajenna", "export": "Vie", "exportDotDotDot": "Vie...", - "exportToX": "Vie kohteeseen {0}", "extendAfter": "Laajenna/lyhennä seuraavaan riviin", "extendBefore": "Laajenna/lyhennä edelliseen riviin", "extendSelectedLinesToNextShotChangeOrNextSubtitle": "Laajenna valitut rivit seuraavaan kuvanvaihtoon (tai seuraavaan tekstitykseen)", @@ -241,7 +252,6 @@ "fileExtension": "Tiedostotunniste", "fileName": "Tiedostonimi", "fileNameX": "Tiedoston nimi: {0}", - "fileNameXAndSize": "Tiedoston nimi: {0} ({1})", "fileSaved": "Tiedosto tallennettu", "fileSavedToX": "Tiedosto tallennettu kohteeseen {0}", "fileXAlreadyExists": "Tiedosto \"{0}\" on jo olemassa.", @@ -251,8 +261,6 @@ "find": "Etsi", "findNext": "Etsi seuraava", "findTextX": "Etsi tekstiä - {0}", - "fiveHundredMilliseconds": "500 millisekuntia", - "fiveSeconds": "Viisi sekuntia", "fix": "Korjaa", "fixCommonErrors": "Korjaa yleiset virheet", "fixRightToLeft": "Korjaa oikealta vasemmalle", @@ -276,14 +284,12 @@ "foundXInLineYZ": "Löytyi \"{0}\" riviltä {1}, kohdasta {2}", "foundXMatches": "Löytyi {0} osumaa", "frameRate": "Kuvataajuus", - "frameRateX": "Kuvataajuus: {0:0.0###}", "frames": "Kuvat", "from": "Alkaen", "fromCurrentVideoPosition": "nykyisestä videon kohdasta", "fullScreen": "Koko näyttö", "gap": "Aukko", "general": "Yleistä", - "generalText": "Yleistä", "generate": "Luo", "generateImportShotChanges": "Luo/tuo kuvanvaihtoja", "generating": "Luodaan...", @@ -300,12 +306,8 @@ "help": "Ohje", "hex": "Hex", "hide": "Piilota", - "hidePreview": "Piilota esikatselu", "history": "Historia", "horizontal": "Vaakasuora", - "hourMinutesSecondsDecimalSeparatorMilliseconds": "Tunti:min:sek{0}ms", - "hourMinutesSecondsFrames": "Tunti:min:sek:kehykset", - "ip": "IP", "id": "ID", "image": "Kuva", "imageSaved": "Kuva tallennettu", @@ -363,6 +365,7 @@ "mergeSelectedLinesDialog": "Yhdistä valitut rivit dialogina", "mergeWithLineAfterAndAutoBreak": "Yhdistä seuraavan rivin kanssa ja automaattinen rivinvaihto", "mergeWithLineAfterKeepBreaks": "Yhdistä seuraavan rivin kanssa (säilytä rivinvaihdot)", + "mergeWithLineAfterAndUnbreak": "Yhdistä seuraavaan riviin ja poista rivinvaihto", "mergeWithLineBeforeAndAutoBreak": "Yhdistä edelliseen riviin ja automaattinen rivinvaihto", "mergeWithLineBeforeKeepBreaks": "Yhdistä edelliseen riviin (säilytä rivinvaihdot)", "middleCenter": "Keskelle", @@ -377,11 +380,11 @@ "moveAllShotChangeOneFrameForward": "Siirrä kaikki kuvanvaihdot yhden kehyksen eteenpäin", "multiMode": "Monitila", "moveDown": "Siirrä alas", + "increase": "Suurenna", + "decrease": "Pienennä", "moveUp": "Siirrä ylös", "multipleReplace": "Useita korvauksia", - "mute": "Mykistä", "name": "Nimi", - "negative": "Negatiivinen", "new": "Uusi", "newDotDotDot": "Uusi...", "newProfile": "Uusi profiili", @@ -409,20 +412,15 @@ "offsetY": "Siirtymä Y", "ok": "_OK", "oneFile": "Yksi tiedosto", - "oneHundredMilliseconds": "100 millisekuntia", - "oneSecond": "Yksi sekunti", "oneSecondBack": "Yksi sekunti taaksepäin", "oneSecondForward": "Yksi sekunti eteenpäin", - "onlineVideoFeatureNotAvailable": "Ominaisuus ei käytettävissä online-videolle", "openContainingFolder": "Avaa sisältävä kansio", "openDictionaryFolder": "Avaa sanakirjakansio", "openFile": "Avaa tiedosto", "openImageBasedSubtitle": "Avaa kuvapohjainen tekstitys", "openImageFile": "Avaa kuvatiedosto", - "openOriginalSubtitleFile": "Avaa alkuperäinen tekstitystiedosto...", "openOriginalSubtitleFileTitle": "Avaa alkuperäinen tekstitystiedosto", "openOutputFolder": "Avaa tulostuskansio", - "openSubtitle": "Avaa tekstitys...", "openSubtitleFileTitle": "Avaa tekstitystiedosto", "openSubtitles": "Avaa tekstitykset...", "openVideoFile": "Avaa videotiedosto...", @@ -435,11 +433,6 @@ "outlineWidth": "Ääriviivan leveys", "outputFolder": "Tulostuskansio", "outputProperties": "Tulostusominaisuudet", - "overlap": "Päällekkäisyys", - "overlapNextX": "Päällekkäisyys seuraavaan ({0:#;##0.###})", - "overlapPreviousLineX": "Päällekkäisyys edellisen rivin kanssa ({0:#;##0.###})", - "overlapStartAndEnd": "Päällekkäisyys alussa ja lopussa", - "overlapX": "Päällekkäisyys ({0:#;##0.###})", "overwriteExistingFiles": "Korvaa olemassa olevat tiedostot", "overwriteFilesInFolderX": "Korvaa kansion tiedostot: \"{0}\"", "overwriteQuestion": "Korvata?", @@ -452,6 +445,7 @@ "percent": "Prosentti", "pickLayer": "Aseta taso", "pickOllamaModel": "Valitse Ollama-malli", + "pickModel": "Valitse malli", "llamaCppUseRemoteServer": "Käytä ulkoista palvelinta (URL)", "pickOutputFolder": "Valitse tulostuskansio", "pickResolutionFromCurrentVideo": "Valitse resoluutio nykyisestä videosta", @@ -491,7 +485,6 @@ "refresh": "Päivitä", "region": "Alue", "regularExpression": "Säännöllinen lauseke", - "regularExpressionIsNotValid": "Säännöllinen lauseke ei ole kelvollinen!", "relativeToCurrentVideoPosition": "Suhteessa nykyiseen videon sijaintiin", "remove": "Poista", "removeAlignment": "Poista kohdistus", @@ -516,7 +509,7 @@ "reset": "Nollaa", "resizePanels": "Muuta paneelien kokoa", "resolution": "Tarkkuus", - "resolutionX": "Tarkkuus: {0}", + "retry": "Yritä uudelleen", "reverseRightToLeftStartEnd": "Käännä RTL:n alku/loppu", "right": "Oikea", "rightToLeft": "Oikealta vasemmalle", @@ -554,8 +547,6 @@ "selectedlinesX": "Valitut rivit: {0}", "sensitivity": "Herkkyys", "separator": "Erotin", - "sessionKey": "Istuntoavain", - "sessionKeyGenerate": "Luo uusi avain", "setEnd": "Aseta loppu", "setEndAndGoToNext": "Aseta loppu ja siirry seuraavaan", "setEndAddNewAndGoToNew": "Aseta loppu, lisää uusi ja siirry uuteen", @@ -574,8 +565,9 @@ "shadowColor": "Varjon väri", "shadowWidth": "Varjon leveys", "shiftLineBy": "Siirrä riviä", + "shotIn": "Kuva alkaa", + "shotOut": "Kuva päättyy", "shortcut": "Pikanäppäin", - "shortcutX": "Pikanäppäin: {0}", "shortcuts": "Pikanäppäimet", "show": "Näytä", "showActorColumn": "Näytä \"Actor\"-sarake", @@ -586,17 +578,19 @@ "showStartColumn": "Näytä \"Alku\"-sarake", "showHideColumn": "Näytä \"Piilota\"-sarake", "showHistory": "Näytä historia", + "clearHistory": "Tyhjennä historia", + "removeFromHistory": "Poista historiasta", "showLayerColumn": "Näytä \"Kerros\"-sarake", "showPreview": "Näytä esikatselu", "showShotChangesList": "Näytä kuvausmuutosten luettelo", + "showShotInColumn": "Näytä \"Kuva alkaa\"-sarake", + "showShotOutColumn": "Näytä \"Kuva päättyy\"-sarake", "showStyleColumn": "Näytä \"Tyyli\"-sarake", "showTeletext": "Näytä teksti-TV-sarake luettelonäkymässä", "showTimeCodes": "Näytä aikakoodit", "showWpmColumn": "Näytä \"Sanoja/min\"-sarake", "showPixelWidthColumn": "Näytä \"Pikselileveys\"-sarake", "shrink": "Pienennä", - "singleBox": "Yksi ruutu", - "singleLineLengths": "Yhden rivin pituus:", "singleMode": "Yksittäinen tila", "textTotalLength": "Teksti - kokonaispituus", "textSingleLineMaxLength": "Teksti - yhden rivin enimmäispituus", @@ -643,16 +637,16 @@ "strikeout": "Yliviivaus", "style": "Tyyli", "styleExaggeration": "Tyylin liioittelu", - "styleLanguage": "Tyyli / Kieli", "styles": "Tyylit", "subtitleFile": "Tekstitystiedosto", + "subtitleFileChangedOutsideX": "Tiedostoa \"{0}\" on muutettu Subtitle Editin ulkopuolella.\n\nLadataanko se uudelleen? Tekemäsi muutokset menetetään.", "subtitleFileName": "Tekstitystiedoston nimi", "subtitleFileSaved": "Tekstitystiedosto tallennettu", "subtitleFileSavedToX": "Tekstitystiedosto tallennettu kohteeseen {0}", "subtitleFiles": "Tekstitystiedostot", "subtitleFormats": "Tekstitysformaatit", "subtitleLoadedX": "Tekstitys ladattu: {0}", - "subtitleXOfY": "Tekstitys {0} / {1}", + "subtitleReloadedChangedOutsideX": "Tekstitys ladattu uudelleen (muutettu Subtitle Editin ulkopuolella): {0}", "suffix": "Jälkiliite", "suggestions": "Ehdotukset", "sync": "Synkronointi", @@ -664,7 +658,6 @@ "teletextColor": "Teksti-TV:n väri", "teletextLine": "Teksti-TV:n rivi", "tenHours": "10 tuntia", - "tenMilliseconds": "10 millisekuntia", "text": "Teksti", "textColor": "Tekstin väri", "textFiles": "Tekstitiedostot", @@ -689,9 +682,6 @@ "topLeft": "Ylävasemmalla", "topRight": "Yläoikealla", "totalAdjustmentX": "Kokonaiskorjaus: {0}", - "totalFramesX": "Kokonaiskuvamäärä: {0:#;##0.##}", - "totalLengthX": "Kokonaispituus: {0}", - "totalLengthXSplitLine": "Kokonaispituus: {0} (jakolinja!)", "translate": "Käännä", "translatePercentX": "Käännetään: {0}%", "translateRow": "Käännä rivi", @@ -700,6 +690,7 @@ "type": "Kirjoita", "unbreak": "Poista tekstin rivinvaihto", "unbreakLines": "Poista rivinvaihto", + "undelete": "Peru poisto", "underline": "Alaviiva", "undo": "Kumoa", "unknown": "Tuntematon", @@ -716,13 +707,11 @@ "usages": "Käyttötarkoitukset", "use": "Käytä", "useAlways": "Käytä aina", - "useLargerFontForThisWindow": "Käytä suurempaa fonttia tässä ikkunassa", "useOnce": "Käytä kerran", "useOutputFolder": "Käytä tulostuskansiota", "useSourceFolder": "Käytä lähdekansiota", "useSourceResolution": "Käytä lähdetarkkuutta", "userName": "Käyttäjänimi", - "userNameAlreadyInUse": "Käyttäjänimi jo käytössä", "vertical": "Pystysuora", "video100MsBack": "Video, 100 millisekuntia taaksepäin", "video100MsForward": "Video, 100 millisekuntia eteenpäin", @@ -736,14 +725,25 @@ "videoCustom3ForwardX": "Video, mukautetut millisekunnit ({0:#,###,##0}) eteenpäin, 3", "videoCustom4BackX": "Video, mukautetut millisekunnit ({0:#,###,##0}) taaksepäin, 4", "videoCustom4ForwardX": "Video, mukautetut millisekunnit ({0:#,###,##0}) eteenpäin, 4", - "videoEncodingX": "Videon koodaus: {0}", + "videoCustom1BackAndPauseX": "Video, mukautetut millisekunnit ({0:#,###,##0}) taaksepäin ja tauko, 1", + "videoCustom1ForwardAndPauseX": "Video, mukautetut millisekunnit ({0:#,###,##0}) eteenpäin ja tauko, 1", + "videoCustom2BackAndPauseX": "Video, mukautetut millisekunnit ({0:#,###,##0}) taaksepäin ja tauko, 2", + "videoCustom2ForwardAndPauseX": "Video, mukautetut millisekunnit ({0:#,###,##0}) eteenpäin ja tauko, 2", + "videoCustom3BackAndPauseX": "Video, mukautetut millisekunnit ({0:#,###,##0}) taaksepäin ja tauko, 3", + "videoCustom3ForwardAndPauseX": "Video, mukautetut millisekunnit ({0:#,###,##0}) eteenpäin ja tauko, 3", + "videoCustom4BackAndPauseX": "Video, mukautetut millisekunnit ({0:#,###,##0}) taaksepäin ja tauko, 4", + "videoCustom4ForwardAndPauseX": "Video, mukautetut millisekunnit ({0:#,###,##0}) eteenpäin ja tauko, 4", "videoExtension": "Videotiedoston laajennus", "videoFile": "Videotiedosto", "videoFileGenerated": "Videotiedosto luotu", "videoFileGeneratedX": "Luotu videotiedosto: \"{0}\"", + "videoFilesGenerated": "Videotiedostot luotu", + "xVideosGenerated": "{0} videota luotu", + "xOfYVideosGenerated": "{0}/{1} videota luotu", + "savedInX": "Tallennettu kohteeseen {0}", + "savedInXFolders": "Tallennettu {0} kansioon", "videoFiles": "Videotiedostot", "videoAndAudioFiles": "Video- ja äänitiedostot", - "videoInformation": "Videotiedot", "videoOffset": "Videon siirtymä", "videoOneFrameBack": "Video, yksi ruutu taaksepäin", "videoOneFrameForward": "Video, yksi ruutu eteenpäin", @@ -754,7 +754,6 @@ "videoPlayer": "Videosoitin", "videoPosition": "Videon sijainti", "videoResolution": "Videon resoluutio", - "viewX": "Näkymä {0}", "visible": "Näkyvissä", "voice": "Ääni", "volume": "Äänenvoimakkuus", @@ -763,9 +762,7 @@ "waveformCenterOnVideoPosition": "Aaltomuodon keskipiste videon sijainnissa", "waveformPasteFromClipboard": "Liitä leikepöydältä", "waveformSpectrogram": "Aaltomuoto/spektrogrammi", - "webServiceUrl": "Verkkopalvelun URL", "width": "Leveys", - "windowPositionAndSize": "Ikkunan sijainti ja koko", "wordsPerMin": "Sanoja/min", "pixelWidth": "Pikselileveys", "wpm": "Sanoja/min", @@ -775,8 +772,6 @@ "xNotFound": "\"{0}\" ei löytynyt", "xRequiresAnApiKey": "{0} vaatii API-avaimen", "xRequiresAValidUrl": "{0} vaatii kelvollisen URL-osoitteen (esim. \"http://...\" tai \"https://...\")", - "xSeconds": "{0:0.0##} sekuntia", - "xSubtitles": "{0:#,###,##0} tekstitystä", "yes": "Kyllä", "zeroBytes": "0 tavua", "copyToClipboard": "Kopioi leikepöydälle", @@ -826,10 +821,8 @@ "xOfYLinesUpdated": "{0}: {1}/{2} riviä päivitetty", "xOfYLinesUpdatedAndZNeighborsAdjusted": "{0}: {1}/{2} riviä päivitetty, {3} naapuria säädetty", "noBookmarksFound": "Ei enää kirjanmerkkejä", - "openAiCompatibleStt": "OpenAI-yhteensopiva STT", "openAiCompatibleSttEndpoint": "STT-päätepisteen URL", "openAiCompatibleSttApiKey": "API-avain", - "openAiCompatibleSttModel": "Malli", "openAiCompatibleSttExtraHeaders": "Lisäotsikot", "openAiCompatibleSttTimeout": "Aikakatkaisu (sekuntia)", "openAiCompatibleSttLanguage": "Kielivihje", @@ -839,6 +832,9 @@ "dashScopeSttRegion": "Alue", "dashScopeSttEnableWords": "Sanatason aikaleimat", "dashScopeSttRegionKeyHint": "Huomaa: Alibaba Cloud Model Studion API-avaimet ovat aluekohtaisia - varmista, että valittu alue vastaa aluetta, jossa API-avain luotiin (Kiina vs. kansainvälinen).", + "googleCloudSttProjectId": "Projektin tunnus (vain Application Default Credentials -tunnistetiedoille)", + "googleCloudSttBucketName": "Tallennussäilö (tyhjä = automaattinen)", + "googleCloudSttDynamicBatching": "Dynaaminen eräkäsittely (noin 5x halvempi, ei viivetakuuta)", "onlineSttApiKeyMissing": "API-avain vaaditaan. Anna API-avain ja yritä uudelleen.", "openAiCompatibleSttAutoTranscribeOnAudioSelection": "Litteroi uusi aaltomuotovalinta automaattisesti puheentunnistuksella", "openAiCompatibleSttStream": "Suoratoistovastaus", @@ -848,11 +844,9 @@ "configurationRequired": "Määritys vaaditaan", "transcriptionError": "Litterointivirhe", "transcriptionComplete": "Litterointi valmis", - "transcribingWithOpenAi": "Litteroidaan OpenAI-palvelulla...", "processingResponse": "Käsitellään vastausta...", "unauthorizedApiKey": "Ei valtuutusta: virheellinen API-avain tai riittämättömät käyttöoikeudet.", "requestTimeout": "Pyynnön aikakatkaisu: palvelimen vastaus kesti liian kauan.", - "noSegmentsReturned": "Litterointipalvelu ei palauttanut segmenttejä.", "openAiCompatibleSttUrlMissing": "OpenAI-yhteensopiva STT-päätepisteen URL vaaditaan. Anna URL ja yritä uudelleen.", "openAiCompatibleSttUrlNotResponding": "OpenAI-yhteensopiva STT-päätepiste ei vastannut:\n{0}\n\nTarkista URL ja että palvelin on käynnissä.", "transcriptionFailed": "Litterointi epäonnistui", @@ -897,10 +891,10 @@ "replace": "_Korvaa...", "multipleReplace": "_Korvaa useita...", "goToLineNumber": "_Siirry rivinumeroon...", + "commandPalette": "Komento_paletti...", "rightToLeftMode": "Oikealta vasemmalle -tila", "modifySelectionDotDotDot": "Muokkaa _valintaa...", "tools": "_Työkalut", - "toolsSelectedLines": "_Työkalut (valitut rivit)", "adjustDurations": "_Säädä kestoja...", "applyDurationLimits": "Käytä kestojen _rajoituksia...", "batchConvert": "_Muunna erissä...", @@ -990,6 +984,7 @@ "toggleSelectSubtitleWhilePlayingCurrentlyOff": "Vaihda tekstityksen valinta toiston aikana (nykyinen tila: pois päältä)", "fillSelectedLinesWithClipboard": "Täytä valitut rivit leikepöydän tekstillä", "beautifyTimeCodes": "Kaunista aikakoodit...", + "improveTimeCodes": "Paranna aikakoodeja (pakotettu kohdistus)...", "evenlyDistributeLines": "Jaa rivit tasaisesti (CPS:n mukaan)", "mergeContinuationLines": "Yhdistä jatkorivit...", "snapAllTimesToFrames": "Kiinnitä kaikki ajat kehyksiin", @@ -1044,6 +1039,9 @@ "selectCurrentLineWhilePlayingHint": "Valitse nykyinen tekstitys toiston aikana {0}", "videoPosition": "Videon sijainti {0}", "videoPositionTextBox": "Videon sijainnin tekstikenttä {0}", + "toolbarLineBreakX": "Rivinvaihto {0} (työkalurivin loppuosa jatkuu uudella rivillä)", + "initialText": "Valitun rivin alkuperäinen teksti", + "initialTextHint": "Valitun rivin teksti sellaisena kuin se oli, kun rivi valittiin", "hideWaveformToolbar": "Piilota työkalurivi {0}", "resetZoomAndSpeed": "Nollaa zoomaus ja toistonopeus {0}", "removeBlankLines": "Poista tyhjät rivit {0}", @@ -1057,6 +1055,15 @@ "textPlayHint": "Toista nykyinen tekstitys ja pysäytä lopussa {0}", "textPauseHint": "Keskeytä toisto {0}", "textNextHint": "Toista seuraava tekstitys ja pysäytä lopussa {0}", + "moveSelectedLines": "Siirrä valittuja rivejä (taakse/eteen-painikkeet)", + "moveSelectedLinesAndFollowing": "Siirrä valittuja ja kaikkia seuraavia rivejä (taakse/eteen-painikkeet)", + "moveAllLines": "Siirrä kaikkia rivejä (taakse/eteen-painikkeet)", + "moveSelectedLinesBackHint": "Siirrä valittuja rivejä {0} ms taaksepäin {1}", + "moveSelectedLinesForwardHint": "Siirrä valittuja rivejä {0} ms eteenpäin {1}", + "moveSelectedLinesAndFollowingBackHint": "Siirrä valittuja ja kaikkia seuraavia rivejä {0} ms taaksepäin {1}", + "moveSelectedLinesAndFollowingForwardHint": "Siirrä valittuja ja kaikkia seuraavia rivejä {0} ms eteenpäin {1}", + "moveAllLinesBackHint": "Siirrä kaikkia rivejä {0} ms taaksepäin {1}", + "moveAllLinesForwardHint": "Siirrä kaikkia rivejä {0} ms eteenpäin {1}", "guessStartNoLineSelected": "Arvaa alku: yhtään riviä ei ole valittu", "guessStartTimeCodesLocked": "Arvaa alku: aikakoodit on lukittu", "guessStartNoWaveform": "Arvaa alku: ei aaltomuotoa", @@ -1078,6 +1085,9 @@ "charactersPerSecond": "Merkkejä sekunnissa: {0}", "chooseColumn": "Valitse sarake", "columnPaste": "Sarake liitetään", + "commandPaletteTitle": "Komentopaletti", + "commandPaletteSearchPlaceholder": "Kirjoita hakeaksesi komentoja...", + "commandPaletteNoCommandsFound": "Ei vastaavia komentoja", "createdEmptyTranslation": "Luotu tyhjä käännös nykyisestä tekstityksestä", "deleteText": "Poista teksti", "deleteTextAndShiftCellsUp": "Poista teksti ja siirrä soluja ylöspäin", @@ -1115,18 +1125,20 @@ "noTextInClipboard": "Leikepöydällä ei ole tekstiä", "oneLineCopiedFromOriginal": "Yksi rivi kopioitu alkuperäisestä tekstityksestä", "oneLineCopiedToOriginal": "Yksi rivi kopioitu alkuperäiseen tekstitykseen", + "onlineOnlyVideo": "Vain verkossa oleva video", + "onlineOnlyVideoXSizeYDownloadAndOpen": "Videotiedosto \"{0}\" ({1}) on tallennettu vain verkkoon, esimerkiksi Dropboxiin tai iCloud Driveen, ja se on ladattava ennen kuin sen voi avata.\n\nLadataanko ja avataanko se nyt?", "oneLineMerged": "Yksi rivi yhdistetty", "oneLineSwitched": "Yksi rivi vaihdettu", "overwriteExistingCells": "Korvaa olemassa olevat solut", "overwriteOrShiftCellsDown": "Korvaa/siirrä soluja alaspäin", "parsingMatroskaFile": "Jäsennetään Matroska-tiedostoa...", + "readingDvdSubtitles": "Luetaan DVD-tekstityksiä...", "pasteFromClipboardDotDotDot": "Liitä leikepöydältä...", "redoPerformed": "Toiminto suoritettu uudelleen", "redoPerformedXActionLeft": "Toiminto toistettu (jäljellä olevat toimet: {0})", "removedUnicodeControlCharactersX": "Poistettu Unicode-ohjausmerkit {0} riviltä", "removedXBlankLines": "Poistettu {0} tyhjää riviä", "replacedXWithYCountZ": "Korvattu \"{0}\" merkillä \"{1}\" ({2} esiintymää)", - "replacedXWithYInLineZ": "Korvattu \"{0}\" merkillä \"{1}\" rivillä {2}", "reversedStartAndEndingsForRightToLeftX": "Käännetään alku- ja loppumerkit oikealta vasemmalle {0} rivillä", "ruleProfileIsX": "Sääntöprofiili on nyt \"{0}\"", "saveLanguageFile": "Tallenna kielitiedosto", @@ -1134,7 +1146,6 @@ "shiftTextCellsDown": "Siirrä tekstisoluja alaspäin", "singleLineLength": "Rivin pituus: ", "speedIsNowX": "Nopeus on nyt \"{0}\"", - "spellCheckResult": "Oikeinkirjoituksen tarkistus valmis. \n\n• Muutetut sanat: {0}\n• Ohitetut sanat: {1}", "subtitleGridFormattingX": "Ruudukon muotoilu: {0}", "subtitleImportedFromMatroskaFile": "Tekstitys tuotu Matroska-tiedostosta", "textDown": "Teksti alas", @@ -1160,13 +1171,14 @@ "trimmedXLines": "Leikattu {0} tekstitysriviä", "pastedXLinesOverSelectedLines": "Liitetty {0} rivi(ä) valittujen rivien päälle", "pastedXOfYLinesOverSelectedLines": "Liitetty {0}/{1} rivi(ä) valittujen rivien päälle - loput eivät mahtuneet valintaan", + "insertedXLinesFromClipboard": "Lisätty {0} riviä leikepöydältä", "openOriginalDifferentNumberOfSubtitlesXY": "Alkuperäisessä tekstitystiedostossa ei ole sama määrä tekstityksiä kuin nykyisessä tekstitystiedostossa.\n\n• Alkuperäiset tekstitykset: {0}\n• Nykyiset tekstitykset: {1}", - "importXMatchingOriginalLines": "Tuodaanko {0} vastaavaa alkuperäistä tekstitystä?", "originalTextReadOnly": "Alkuperäinen teksti (vain luku)", "originalTextEditMode": "Alkuperäinen teksti (muokkaustila)", "originalIsReadOnlyNotSaved": "Alkuperäinen tekstitys on vain luettava viite, eikä sitä tallennettu", "originalIsEmptyNotSaved": "Alkuperäisessä tekstityksessä ei ole lainkaan tekstiä, eikä sitä tallennettu", "originalIsReadOnlyReference": "Alkuperäinen tekstitys on avattu vain luettavana viitteenä", + "closeTranslationQuestionXY": "Suljetaanko käännös \"{0}\"?\n\nAlkuperäisestä tekstityksestä \"{1}\" tulee muokattava tekstitys. Itse käännöstiedostoa ei muuteta.", "allowEditOfOriginalSubtitle": "Salli alkuperäisen tekstityksen muokkaus", "showAllOriginalLinesX": "Näytä kaikki alkuperäiset rivit ({0} ilman vastinetta täällä)", "showAllOriginalLinesHint": "Rivit, joilla ei ole vastinetta, näytetään lisäriveinä, joten näet mitä käännöksestä puuttuu.", @@ -1200,6 +1212,7 @@ "fileExportedInFormatXToY": "Tiedosto viety muodossa {0} kohteeseen {1}", "fileExportedInFormatXToFileY": "Tiedosto viety muodossa \"{0}\" tiedostoon \"{1}\"", "fixedXLines": "Korjattu {0} riviä", + "toolXChangedYLines": "{0}: {1} riviä muutettu", "transcriptionCompletedWithXLines": "Transkriptio valmis, {0} riviä", "openSubtitleFileX": "Avaako tekstitystiedoston \"{0}\"?", "replacedXOccurrences": "Korvattu {0} esiintymää", @@ -1234,13 +1247,9 @@ "file": { "ebuSaveOptions": { "title": "EBU-tallennusasetukset", - "generalSubtitleInformation": "Yleiset tekstitystiedot", "codePageNumber": "Koodisivunumero", "diskFormatCode": "Levyn muotoilukoodi", "displayStandardCode": "Näytön standardikoodi", - "colorRequiresTeletext": "Värit vaativat teksti-TV:n!", - "alignmentRequiresTeletext": "Kohdistus vaatii teksti-TV:n!", - "teletextCharsShouldBe38": "Teletekstin 'Merkkien enimmäismäärä rivillä' tulisi olla 38!", "characterCodeTable": "Merkkitaulukko", "languageCode": "Kielikoodi", "originalProgramTitle": "Alkuperäinen ohjelman nimi", @@ -1257,7 +1266,6 @@ "maxNumberOfDisplayableRows": "Rivien enimmäismäärä", "diskSequenceNumber": "Levyn järjestysnumero", "totalNumberOfDisks": "Levyjen kokonaismäärä", - "import": "Tuo...", "textAndTimingInformation": "Teksti- ja ajoitustiedot", "justificationCode": "Tasoituskoodi", "verticalPosition": "Pystysuora sijainti", @@ -1274,11 +1282,6 @@ "errors": "Virheet", "errorsX": "Virheet: {0}", "maxLengthError": "Rivi {0} ylittää enimmäispituuden ({1}) {2}: {3}", - "textUnchangedPresentation": "Muuttumaton esitys", - "textLeftJustifiedText": "Vasemmalle tasattu teksti", - "textCenteredText": "Keskitetty teksti", - "textRightJustifiedText": "Oikealle tasattu teksti", - "useBoxForOneNewLine": "Valitse 'Käytä tekstin ympärillä olevaa kehystä' vain yhdelle rivinvaihdolle", "maxLengthErrorTeletextColored": "Rivi {0}-{1}: merkkien enimmäismäärän tulisi olla 36 (ei {2}) kaksinkertaisen korkeuden väritekstille", "maxLengthErrorTeletextWhite": "Rivi {0}-{1}: merkkien enimmäismäärän tulisi olla 37 (ei {2}) kaksinkertaisen korkeuden valkoiselle tekstille", "maxLengthErrorTeletextSingleHeight": "Rivi {0}: merkkien enimmäismäärän tulisi olla 38 (ei {1}) yksinkertaisen korkeuden valkoiselle tekstille", @@ -1303,22 +1306,29 @@ "formattingDotDotDot": "Muotoilu...", "imageBasedSubtitleForEditDotDotDot": "Kuvapohjainen tekstitys muokkausta varten...", "imageBasedSubtitleForOcrDotDotDot": "Kuvapohjainen tekstitys OCR:ää varten...", + "dvdSubtitlesDotDotDot": "Tekstitykset DVD:ltä (IFO/VOB)...", + "titleImportDvd": "Tuo tekstitykset DVD:ltä", + "dvdIfoFile": "IFO-tiedosto", + "dvdVobFiles": "VOB-tiedostot", + "dvdVideoStandard": "Videostandardi", + "dvdStartRipping": "Aloita kopiointi", + "dvdRippingAborted": "Kopiointi keskeytetty", + "dvdOpenIfoFile": "Avaa DVD:n IFO-tiedosto", + "dvdAddVobFiles": "Lisää VOB-tiedostoja", + "dvdIfoFiles": "DVD IFO -tiedostot", + "dvdVobFileType": "DVD VOB -tiedostot", + "dvdDropHint": "Avaa IFO-tiedosto (VIDEO_TS\\VTS_01_0.IFO) tai lisää VOB-tiedostoja - tai pudota ne tähän", + "dvdNotAnIfoFileX": "{0} ei ole DVD:n IFO-tiedosto", + "dvdMenuIfoNoTitleSetsX": "Nimikesarjoja (VTS_xx_0.IFO) ei löytynyt kohteen {0} vierestä", + "dvdEncryptedXOfY": "{0}/{1} tekstityspaketista on yhä CSS-salattuja - nämä VOB-tiedostot on kopioitu purkamatta salausta, joten osa tekstityskuvista on sotkeutuneita.\n\nPura DVD:n salaus ensin DVD-kopiointityökalulla, jotta tulos on puhdas.\n\nJatketaanko silti?", "splitTextAt": "Jaa teksti kohdassa", "blankLines": "Tyhjät rivit", "oneLineIsOneSubtitle": "Yksi rivi on yksi tekstitys", "twoLinesAreOneSubtitle": "Kaksi riviä on yksi tekstitys", "importFilesDotDotDot": "Tuo tiedostot...", "multipleFiles": "Tuo useista tekstitiedostoista (yksi tiedosto on yksi tekstitys)", - "importOptions": "Tuontiasetukset", - "autoSplitText": "Jaa teksti automaattisesti", - "lineMode": "Rivimoodi", "maxLineLength": "Rivien enimmäispituus", - "maxLinesPerSubtitle": "Enimmäisrivimäärä tekstitystä kohti", - "minGapBetweenSubtitles": "Vähimmäisväli tekstitysten välillä", "mergeShortLines": "Yhdistä lyhyet rivit", - "removeLinesWithoutLetters": "Poista rivit, joissa ei ole kirjaimia", - "splitAtEndCharsSetting": "Jaa loppumerkkien kohdalta", - "takeTimeFromCurrentFile": "Ota aika nykyisestä tiedostosta", "fixed": "Kiinteä", "numberOfSubtitlesX": "Tekstitysten lukumäärä: {0}", "numberOfSubtitlesXForcedY": "Tekstitysten määrä: {0} ({1} pakotettua)", @@ -1330,14 +1340,18 @@ "forcedAlignerProgress": "Kohdistetaan... ikkuna {0}/{1}, {2}/{3} riviä", "forcedAlignerSetupTitle": "Kohdista aikakoodit pakotetulla kohdistimella", "forcedAlignerSetupIntro": "Pakotettu kohdistin sovittaa jo olemassa olevan tekstitystekstin ääneen ilman, että sitä litteroidaan ensin. Pitkät videot kohdistetaan paloissa, joten mikä tahansa pituus käy.", + "forcedAlignerEndsFromSpeech": "Aseta loppuajat eristetystä puheesta (hidas)", + "forcedAlignerEndsFromSpeechHint": "Pakotettu kohdistin löytää, mistä rivi alkaa, mutta ei sitä, mihin se päättyy, joten loppuajat tulevat yleensä lukuajasta. Kun tämä on päällä, musiikki ja äänitehosteet poistetaan ensin äänestä ja kukin rivi päättyy, kun sen puhe hiljenee. Kestää GPU:lla suunnilleen yhtä kauan kuin itse video - ja ilman sitä monta kertaa kauemmin.", + "forcedAlignerIsolatingSpeech": "Eristetään puhetta... {0}", + "forcedAlignerIsolatingSpeechFailed": "Puhetta ei voitu eristää - loppuajat asetettiin sen sijaan lukuajan mukaan.", "forcedAlignerModel": "Kohdistimen malli", - "forcedAlignerDownloadEngine": "Lataa / päivitä moottori..." + "forcedAlignerDownloadEngine": "Lataa / päivitä moottori...", + "forcedAlignerEstimatedXOfY": "{0}/{1} riviä ei voitu kohdistaa, ja ne sijoitettiin lukuajan mukaan - niiden aikakoodit ovat vain arvioita. Käsikirjoitus on luultavasti pidempi kuin videossa puhuttu." }, "export": { "exportImagesProfiles": "Vie kuvaprofiilit", "leftRightMargin": "Vasen/oikea marginaali", "topBottomMargin": "Ylä-/alamarginaali", - "titleExportBluRaySup": "Vie Blu-ray (sup)", "lineSpacingPercent": "Riviväli %", "paddingLeftRight": "Vasemman/oikean reunan täyttö", "paddingTopBottom": "Ylä-/alarajan täyttö", @@ -1378,10 +1392,21 @@ "titleExportDCinemaInteropPng": "D-Cinema interop/png", "titleExportDCinemaSmpte2014Png": "D-Cinema SMPTE 2014/png", "imageBasedSubtitleSaved": "Kuvapohjainen tekstitys tallennettu", - "titleExportBdnXml": "Vie BDN/xml", "titleExportWebVttThumbnails": "Vie WebVTT (pikkukuvat)", "fullFrameImage": "Koko kuvaruudun kokoinen kuva", "fullFrameImageHint": "Tee jokaisesta kuvasta videokehyksen kokoinen, tekstitys omalla paikallaan", + "stereo3D": "3D", + "stereo3DHint": "3D-videolle: piirrä tekstitys kerran kummankin silmän kuvan puolikkaaseen", + "halfSideBySide": "Puolikas rinnakkain", + "halfTopBottom": "Puolikas ylä/ala", + "depth3D": "Syvyys", + "depth3DHint": "Kuinka paljon tekstitys erottuu ruudusta - positiivinen tuo sen lähemmäs, negatiivinen työntää sen taaemmas", + "plane3D": "3D-Plane", + "plane3DHint": "Anna kullekin tekstitykselle syvyys, jonka 3D Blu-ray tallentaa sille, BD3D2MK3D:llä tai OFSExtractorilla tehdystä 3D-Plane-tiedostosta (.ofs). Syvyys-arvoa käytetään, kun 3D-Planessa sitä ei ole", + "openPlane3DTitle": "Avaa 3D-Plane-tiedosto", + "unableToLoadPlane3DX": "3D-Planen lataus epäonnistui: {0}", + "plane3DXDepthYToZ": "{0} (syvyys {1}–{2})", + "plane3DXNoDepth": "{0} (ei syvyysarvoja)", "textEffect": "Tekstitehoste", "textEffectSettingsTitle": "Tekstitehosteen asetukset", "textEffectPreset": "Esiasetus", @@ -1413,13 +1438,10 @@ "statistics": { "title": "Tilastot", "titleWithFileName": "Tilastot - {0}", - "generalStatistics": "Yleiset tilastot", - "mostUsed": "Eniten käytetyt...", "mostUsedLines": "Eniten käytetyt rivit", "mostUsedWords": "Eniten käytetyt sanat", "nothingFound": "Ei löytynyt mitään", "numberOfLinesX": "Tekstitysrivien määrä: {0:#,##0}", - "numberOfFilesX": "Tekstitystiedostojen määrä: {0:#,##0}", "lengthInFormatXinCharactersY": "Merkkien määrä muodossa {0}: {1:#,###,##0}", "numberOfCharactersInTextOnly": "Pelkän tekstin merkkien määrä: {0:#,###,##0}", "totalDuration": "Kaikkien tekstitysten kokonaiskesto: {0:#,##0}", @@ -1453,7 +1475,6 @@ "gapMaximum": "Väli - enimmäisarvo: {0:#,##0} ms", "gapAverage": "Väli - keskiarvo: {0:#,##0.##} ms", "gapExceedingMinimum": "Väli - alle minimi ({0:#,##0} ms): {1} ({2:0.00}%)", - "export": "Vie...", "durationMinimum": "Kesto - vähintään: {0:0.000} sekuntia", "durationMaximum": "Kesto - enintään: {0:0.000} sekuntia", "durationAverage": "Kesto - keskimäärin: {0:0.000} sekuntia", @@ -1490,7 +1511,22 @@ "restoreAutoBackupFile": "Palauta automaattisen varmuuskopion tiedosto", "restoreXFromY": "Haluatko palauttaa \"{0}\" kohteesta {1}?", "deleteAllSubtitleBackups": "Haluatko poistaa kaikki tekstityksen varmuuskopiotiedostot?", - "deleteAll": "Poista kaikki" + "deleteAll": "Poista kaikki", + "subtitles": "Tekstitykset", + "settingsBackupInfo": "Settings.json varmuuskopioidaan Subtitle Editin käynnistyessä, enintään kerran {0} päivässä ja vain, jos asetukset ovat muuttuneet. Uusimmat {1} varmuuskopiota säilytetään.", + "settingsBackupInfoEveryStart": "Settings.json varmuuskopioidaan aina Subtitle Editin käynnistyessä, jos asetukset ovat muuttuneet. Uusimmat {0} varmuuskopiota säilytetään.", + "settingsBackupOff": "Asetusten automaattinen varmuuskopiointi on pois päältä (Asetukset → Asetukset → Tiedosto).", + "lastSettingsBackupX": "Viimeisin varmuuskopio: {0}", + "noSettingsBackupsYet": "Asetuksista ei ole vielä varmuuskopioita", + "openSettingsBackupFolder": "Avaa asetusten varmuuskopiokansio", + "backUpSettingsNow": "Varmuuskopioi nyt", + "settingsBackedUp": "Nykyiset asetukset varmuuskopioitiin.", + "restoreSettings": "Palauta asetukset", + "restoreSettingsFromX": "Haluatko korvata nykyiset asetuksesi varmuuskopiolla ajalta {0}?\n\nNykyiset asetuksesi varmuuskopioidaan ensin. Jotkin muutokset tulevat voimaan vasta, kun Subtitle Edit käynnistetään uudelleen.", + "settingsRestored": "Asetukset palautettiin. Käynnistä Subtitle Edit uudelleen, jotta kaikki muutokset tulevat voimaan.", + "settingsRestoreFailed": "Asetuksia ei voitu palauttaa: {0}", + "settingsBackupNotValid": "varmuuskopio ei ole kelvollinen asetustiedosto, joten nykyiset asetuksesi säilytettiin", + "deleteAllSettingsBackups": "Haluatko poistaa kaikki asetusten varmuuskopiotiedostot?" }, "propertiesDCinema": { "title": "D-Cinema SMPTE -ominaisuudet", @@ -1504,22 +1540,16 @@ "editRate": "Muokkausnopeus", "timeCodeRate": "Aikakoodinopeus", "startTime": "Aloitusaika", - "font": "Fontti", "fontId": "Fontin tunnus", "fontUri": "Fontin URI", - "generate": "Luo", - "fontColor": "Fontin väri", "chooseColor": "Valitse väri", "fontEffect": "Fontin tehoste", "effectColor": "Tehosteen väri", - "fontSize": "Fontin koko", "topBottomMargin": "Ylä-/alamarginaali", "fadeUpTime": "Haalistumisaika", "fadeDownTime": "Haalistumisaika", - "frames": "Kuvat", "zPosition": "Z-sijainti", - "zPositionHelp": "Positiiviset luvut siirtävät tekstiä kauemmas, negatiiviset lähemmäs; jos z-sijainti on nolla, teksti on 2D", - "export": "Vie..." + "zPositionHelp": "Positiiviset luvut siirtävät tekstiä kauemmas, negatiiviset lähemmäs; jos z-sijainti on nolla, teksti on 2D" }, "webVtt": { "setStylesForSelectedLinesTitle": "Aseta WebVTT-tyylit valituille riveille", @@ -1552,20 +1582,66 @@ "compareOnlyInOneFile": "Vain yhdessä tiedostossa", "compareTextOrTimeDifference": "Teksti-/aikaero", "compareNumberDifference": "Numeroero", - "ignoreLineBreaks": "Ohita rivinvaihdot", "ignoreWhitespace": "Ohita välilyönnit", "ignoreWhitespaceHint": "Rivejä, jotka eroavat vain välilyöntien, sarkainten tai rivinvaihtojen osalta, ei lasketa erilaisiksi", "ignoreFormatting": "Ohita muotoilu", - "onlyLookForDifferencesInText": "Etsi eroja vain tekstistä", - "cannotCompareWithImageBasedSubtitles": "Ei voi verrata kuvapohjaisiin tekstityksiin", - "statisticsTitle": "Tilastot", "ignoreFormattingHint": "Rivejä, jotka eroavat vain muotoilutunnisteiden osalta, kuten tai {\\an8}, ei lasketa erilaisiksi", "showOnlyDifferencesInText": "Vain tekstin erot", + "ignoreNumbering": "Ohita numerointi", + "ignoreNumberingHint": "Rivejä, jotka eroavat vain numeroltaan, ei lasketa erilaisiksi", + "ignoreTimeDifferenceMs": "Aikatoleranssi (ms)", + "ignoreTimeDifferenceMsHint": "Alku- ja loppuaikoja, jotka eroavat enintään näin monta millisekuntia, ei lasketa erilaisiksi", + "compareDifferences": "Erot", + "compareTextDifferences": "Tekstierot", + "compareCurrent": "Nykyinen", + "compareReference": "Vertailu", + "compareEditable": "Muokattava", + "compareEditableHint": "Tässä tehdyt muutokset siirtyvät tekstitykseen, kun napsautat Käytä", + "compareReadOnly": "Vain luku", + "compareTakeFromReference": "Ota teksti ja ajoitus vertailusta", + "compareInsertFromReference": "Lisää tämä rivi nykyiseen tekstitykseen", + "compareDeleteFromCurrent": "Poista tämä rivi nykyisestä tekstityksestä", + "compareTakeText": "Ota teksti", + "compareTakeTiming": "Ota ajoitus", + "compareEditLine": "Muokkaa riviä", + "compareEditHint": "Ctrl+Enter tallentaa, Esc peruuttaa", + "compareEdited": "Muokattu", + "compareOnePendingChange": "1 odottava muutos", + "compareXPendingChanges": "{0} odottavaa muutosta", + "compareDiscardXChanges": "Hylätäänkö vertailussa tehdyt {0} muutosta?", + "compareXChangesApplied": "Vertailu: {0} muutosta otettu käyttöön", + "compareChangeTextX": "#{0} teksti vertailusta", + "compareChangeTimingX": "#{0} ajoitus vertailusta", + "compareChangeTextAndTimingX": "#{0} teksti ja ajoitus vertailusta", + "compareChangeEditedX": "#{0} muokattu", + "compareChangeInsertedX": "#{0} lisätty", + "compareChangeDeletedX": "#{0} poistettu", + "compareSyncPoint": "Synkronointipiste", + "compareSyncPointHint": "Synkronointipiste - nämä kaksi riviä näytetään aina parina, ja ylä- ja alapuolen rivit kohdistetaan erikseen", + "compareSyncPickCurrent": "Synkronointipiste: käytä tätä nykyistä riviä", + "compareSyncPickReference": "Synkronointipiste: käytä tätä vertailuriviä", + "compareSyncRemove": "Poista synkronointipiste", + "compareClearXSyncPoints": "Tyhjennä synkronointipisteet ({0})", + "compareSyncCurrentPickedX": "Nykyinen #{0} valittu synkronointipisteeksi - valitse vastaava vertailurivi ja napsauta Synkronoi", + "compareSyncReferencePickedX": "Vertailu #{0} valittu synkronointipisteeksi - valitse vastaava nykyinen rivi ja napsauta Synkronoi", + "compareSyncWithCurrentX": "Synkronoi nykyisen #{0} kanssa", + "compareSyncWithReferenceX": "Synkronoi vertailun #{0} kanssa", + "compareSyncApply": "Synkronointi", + "compareSyncPointSetXY": "Synkronointipiste asetettu: nykyinen #{0} ja vertailu #{1} näytetään nyt parina", + "compareSyncAlreadyPairedXY": "Nykyinen #{0} ja vertailu #{1} näytetään jo parina samalla alulla - ei synkronoitavaa", + "compareSyncPointShiftedXYZW": "Synkronointipiste asetettu: nykyiset #{0}-#{1} siirretty {2} alkamaan vertailun #{3} kanssa", + "compareChangeSyncShiftXYZ": "#{0}-#{1} siirretty {2} (synkronointipiste)", + "compareTimingDeltaHint": "Kuinka paljon myöhemmin (+) tai aiemmin (−) vertailu alkaa ja päättyy kuin nykyinen rivi", "loadXFromFile": "Lataa \"{0}\" tiedostosta", + "compareLoadSavedFile": "Lataa tallennettu tiedosto", "saveCompareHtmlTitle": "Tallenna vertailu HTML-tiedostona", "pickMatroskaTrackX": "Valitse Matroska-raita - {0}", "pickTransportStreamTrackX": "Valitse transport stream -raita - {0}", + "chapters": "Luvut", + "dvdVobFilesMissingX": "VOB-tiedostoja puuttuu ({0} % löytyi)", "pickMp4TrackX": "Valitse MP4-raita - {0}", + "pickMxfTrackX": "Valitse MXF-raita - {0}", + "pickMpegTrackX": "Valitse MPEG-raita - {0}", "rosettaProperties": "Timed Text Rosetta IMSC -ominaisuudet", "rosettaFontSize": "Fonttikoko (rivin korkeus)", "propertyTimeBase": "Aikaperusta", @@ -1575,7 +1651,6 @@ "propertyDefaultRegion": "Oletusalue", "propertyStyleAttributeName": "Tyyliattribuutin nimi", "propertyTimeCodeFormat": "Aikakoodin muoto", - "propertyFileExtension": "Tiedostopääte", "propertyTopOrigin": "Yläreunan origo", "propertyTopExtent": "Yläreunan laajuus", "propertyBottomOrigin": "Alareunan origo", @@ -1625,7 +1700,6 @@ "selectionIntersect": "Leikkaa valinnan kanssa" }, "multipleReplace": { - "title": "Useita korvauksia", "editRule": "Muokkaa sääntöä", "newRule": "Uusi sääntö", "editCategory": "Muokkaa luokkaa", @@ -1642,24 +1716,23 @@ "findWhat": "Etsi", "descriptionOptional": "Kuvaus (valinnainen)", "invalidRegularExpressionX": "Virheellinen säännöllinen lauseke: {0}", - "regularExpressionTooSlowX": "Säännöllinen lauseke luovutti {0} sekunnin jälkeen - sääntö ohitetaan, kokeile yksinkertaisempaa lauseketta" + "regularExpressionTooSlowX": "Säännöllinen lauseke luovutti {0} sekunnin jälkeen - sääntö ohitetaan, kokeile yksinkertaisempaa lauseketta", + "moveToCategory": "Siirrä luokkaan" }, "find": { "searchTextWatermark": "Etsi tekstiä...", "wholeWord": "Koko sana", - "caseSensitive": "Kirjainkoko merkitsevä", - "caseInsensitive": "Kirjainkoko ei merkitse", "findPrevious": "Etsi edellinen", "findNext": "Etsi seuraava", "replaceAndFindNext": "Korvaa ja etsi seuraava", "replaceAll": "Korvaa _kaikki", - "replaceTitle": "Korvaa", - "replaceWith": "Korvaa seuraavalla", "replaceTextWatermark": "Korvaa seuraavalla...", "replaceIn": "Korvaa/etsi kohteesta", "replaceInTextAndOriginal": "Teksti ja alkuperäinen teksti", "replaceInTextOnly": "Vain teksti", - "replaceInOriginalOnly": "Vain alkuperäinen teksti" + "replaceInOriginalOnly": "Vain alkuperäinen teksti", + "replacedOneOccurrence": "Korvattiin yksi esiintymä", + "replacedXOccurrences": "Korvattiin {0} esiintymää" }, "regularExpressionContextMenu": { "wordBoundary": "Sanaraja (\\b)", @@ -1687,6 +1760,7 @@ "xLinesYSubtitles": "{0} riviä, {1} tekstitystä", "xLinesYSubtitlesZErrors": "{0} riviä, {1} tekstitystä, {2} virhe(ttä)", "couldNotParseAsX": "Jäsentäminen muotoon {0} ei onnistunut", + "readAsXUseAsY": "Lähdettä ei voitu lukea muodossa {1}, mutta se voitiin lukea muodossa {0}.\nKäytetäänkö rivejä ja tallennetaanko edelleen muodossa {1}?", "selectedXCharactersYLines": "Valittu: {0} merkkiä {1} rivillä", "replacedXOccurrences": "Korvattu {0} esiintymä(ä)", "invalidRegularExpression": "Virheellinen säännöllinen lauseke", @@ -1701,7 +1775,6 @@ "aiReview": { "title": "Tekoälytarkistus", "review": "Tarkista", - "stop": "Lopeta", "editPrompt": "Muokkaa kehotetta...", "editPromptTitle": "Muokkaa tarkistuskehotetta", "promptInfo": "Mallille lähetettävät ohjeet - {language} korvataan tekstityksen kielellä.", @@ -1712,7 +1785,6 @@ "reviewingLineXOfY": "Tarkistetaan riviä {0}/{1}...", "reviewDone": "Tarkistus valmis - {0} ehdotusta {1} rivillä", "noIssuesFound": "Ongelmia ei löytynyt", - "categoryAll": "Kaikki", "categorySpelling": "Oikeinkirjoitus", "categoryGrammar": "Kielioppi", "categoryPunctuation": "Välimerkit", @@ -1724,8 +1796,23 @@ "largeChangeWarning": "Suuri muutos - näyttää uudelleenkirjoitukselta, tarkista huolellisesti", "mismatchWarning": "Ei muistuta alkuperäistä - saattaa kuulua toiselle riville", "engineError": "Tekoälymoottoriin ei saatu yhteyttä: {0}", - "setupHint": "Valitse ensin moottori ja malli kohdasta Työkalut -> Tekoälytarkistus.", - "playCurrentLineHint": "Toista valittu rivi videosoittimessa ja pysäytä sen loppuun (myös kaksoisnapsauttamalla riviä)" + "playCurrentLineHint": "Toista valittu rivi videosoittimessa ja pysäytä sen loppuun (myös kaksoisnapsauttamalla riviä)", + "editAfterHint": "Napsauta valitun rivin tekstiä muokataksesi korjausta ennen sen käyttöä (Enter tallentaa, Escape peruuttaa)", + "template": "Malli", + "templatePlaceholder": "Valitse malli, joka korvaa kehotteen", + "templateStandard": "Tavallinen oikoluku", + "templateSpeechToText": "Puhe tekstiksi -tuloksen siistiminen", + "templateMachineTranslation": "Konekäännöksen korjaus", + "templateSdh": "SDH / kuulovammaisille", + "referenceContext": "Taustatiedot", + "referenceContextInfo": "Valinnainen. Nimet, hahmot, sanasto, juoniselostus tai tyyliohjeet - malli pitää näitä nimien ja termien auktoriteettina. Lähetetään jokaisen erän mukana.", + "referenceContextPlaceholder": "Nimet: Kaito, Mei-täti, tri Hollis\nTermit: Shinkansen, loimiydin\nJuoni: Kaito matkustaa Tokioon etsimään kadonnutta isäänsä.", + "generateContext": "Luo tekoälyllä", + "generateContextHint": "Anna valitun tekoälymoottorin lukea tekstitys ja laatia luonnos nimistä, termeistä ja juonesta - tarkista tulos ennen käyttöä", + "generatingContextPartXOfY": "Luetaan osaa {0}/{1}...", + "generatingContextSynopsis": "Kirjoitetaan juoniselostusta...", + "replaceContextQuestion": "Korvataanko nykyiset taustatiedot?", + "noContextGenerated": "Tekoälymoottori ei palauttanut käyttökelpoisia taustatietoja." }, "aiAssistant": { "title": "Tekoälyavustaja", @@ -1743,22 +1830,14 @@ "showReasoning": "Näytä mallin päättely" }, "fixCommonErrors": { - "title": "Korjaa yleiset virheet", - "step1": "Vaihe 1/2 - Valitse korjattavat virheet", - "whatToFix": "Mitä korjata", - "example": "Esimerkki", - "selectAll": "Valitse kaikki", - "inverseSelection": "Käännä valinta", "back": "< &Takaisin", "next": "&Seuraava >", - "step2": "Vaihe 2/2 - Tarkista korjaukset", "fixes": "Korjaukset", "ruleDetailsDotDotDot": "Säännön tiedot...", "showOnlyThisRule": "Näytä vain tämän säännön korjaukset", "appliedRulesForLineX": "Rivillä {0} käytetyt säännöt", "ruleX": "Sääntö: {0}", "log": "Loki", - "function": "Funktio", "removedEmptyLine": "Poista tyhjä rivi", "removedEmptyLineAtTop": "Poista tyhjä rivi yläreunasta", "removedEmptyLineAtBottom": "Poista tyhjä rivi alareunasta", @@ -1779,13 +1858,13 @@ "removeLineBreaksPixelWidth": "Poista rivinvaihdot tekstityksistä, jotka mahtuvat yhdelle riville (pikselileveys)", "fixUppercaseIInsideLowercaseWords": "Korjaa ison 'i'-kirjaimen pienissä sanoissa (OCR-virhe)", "fixDoubleApostrophes": "Korjaa kaksoisapostrofit ('') yksittäisiksi lainausmerkeiksi (\")", + "fixMisreadQuotes": "Korjaa lainausmerkeiksi väärin luetut heittomerkit (OCR, englanti)", "addPeriods": "Lisää piste rivien jälkeen, joissa seuraava rivi alkaa isolla kirjaimella", "startWithUppercaseLetterAfterParagraph": "Aloita isolla kirjaimella kappaleen jälkeen", "startWithUppercaseLetterAfterPeriodInsideParagraph": "Aloita isolla kirjaimella pisteen jälkeen kappaleen sisällä", "startWithUppercaseLetterAfterColon": "Aloita isolla kirjaimella kaksoispisteen/puolipisteen jälkeen", "fixLowercaseIToUppercaseI": "Korjaa yksinään oleva pieni 'i' isoksi 'I':ksi (englanti)", "fixCommonOcrErrors": "Korjaa yleiset OCR-virheet (käyttämällä OCR-korvausluetteloa)", - "commonOcrErrorsFixed": "Yleiset OCR-virheet korjattu (käytetty OcrReplaceList-tiedosto): {0}", "removeSpaceBetweenNumber": "Poista välilyönnit numeroiden välistä", "breakDialogsOnOneLine": "Jaa vuoropuhelut yhdelle riville", "removeDialogFirstInNonDialogs": "Poista alkuviiva ensimmäiseltä riviltä muissa kuin vuoropuheluissa", @@ -1802,18 +1881,17 @@ "fixCommasExample": ",, -> ,", "removeLineBreaksExample": "Foo
bar! -> Foo bar!", "fixDoubleApostrophesExample": "''Sisältää kaksinkertaisia yksinkertaisia lainausmerkkejä'' -> \"Sisältää yksinkertaisen kaksinkertaisen lainausmerkin\"", + "fixMisreadQuotesExample": "\"Hello' -> \"Hello\"", "addPeriodsExample": "Hello world -> Hello world.", "startWithUppercaseLetterAfterParagraphExample": "p1: Foobar! || p2: foobar! -> p1: Foobar! || p2: foobar!", "startWithUppercaseLetterAfterPeriodInsideParagraphExample": "Hello there! how are you? -> Hello there! How are you?", "startWithUppercaseLetterAfterColonExample": "Speaker: hello world! -> Puhuja: Hei maailma!", "removeHyphensSingleLineExample": "- Foobar. -> Foobar.", "fix3PlusLinesExample": "Foo
bar
baz! -> Foo bar baz!", - "xMissingQuotesAdded": "Puuttuvat lainausmerkit lisätty: {0}", "fix3PlusLine": "Korjaa yli kahden rivin tekstitys", "fix3PlusLines": "Korjaa yli kahden rivin tekstitykset", "analysing": "Analysoidaan...", "nothingToFix": "Ei korjattavaa :)", - "fixesFoundX": "Löydettyjä korjauksia: {0}", "xFixesApplied": "Sovellettuja korjauksia: {0}", "xFixesYSelected": "{0} korjausta - {1} valittu", "nothingFixableBut": "Mitään ei voitu korjata automaattisesti. Tekstitys sisältää virheitä – katso lokista lisätietoja", @@ -1822,7 +1900,6 @@ "fixFirstLetterToUppercaseAfterParagraph": "Korjaa kappaleen ensimmäinen kirjain isoksi", "mergeShortLine": "Yhdistä lyhyt rivi (yksi lause)", "mergeShortLineAll": "Yhdistä lyhyt rivi (kaikki paitsi vuoropuhelut)", - "unbreakShortLinePixelWidth": "Poista lyhyen rivin katkos (pikselileveys)", "breakLongLine": "Katkaise pitkä rivi", "fixLongDisplayTime": "Korjaa pitkä näyttöaika", "fixInvalidItalicTag": "Korjaa virheellinen kursiivitagi", @@ -1845,7 +1922,6 @@ "fixUppercaseIInsideLowercaseWord": "Korjaa ison 'i'-kirjaimen pienessä sanassa", "fixMissingPeriodAtEndOfLine": "Lisää puuttuva piste rivin loppuun", "refreshFixes": "Päivitä käytettävissä olevat korjaukset", - "applyFixes": "Käytä valittuja korjauksia ja sulje", "autoBreak": "Automaattinen &br", "unbreak": "&Poista rivinvaihto", "fixDoubleDash": "Korjaa '--' -> '...'", @@ -1864,8 +1940,6 @@ "fixSpaceBetweenNumbersExample": "1 100 -> 1100", "fixDialogsOneLineExample": "Hei John! - Hei Ida! -> Hei John!
- Hei Ida!", "removeDialogFirstInNonDialogsExample": "- Kuinka voit? -> Kuinka voit?", - "selectDefault": "Valitse oletus", - "setDefault": "Aseta nykyiset korjaukset oletukseksi", "fixContinuationStyleX": "Korjaa jatkotyyli: {0}", "fixUnnecessaryLeadingDots": "Poista tarpeettomat alkupisteet", "fixCommonErrorsProfiles": "Korjaa yleisten virheiden profiilit", @@ -1873,7 +1947,6 @@ "searchRulesDotDotDot": "Etsi sääntöjä...", "goToApplyFixes": "Siirry korjausten soveltamiseen", "backToFixList": "Takaisin korjausluetteloon", - "applyFixesAndClose": "Sovella korjaukset ja sulje", "fixCommonOcrErrorsStep2": "Korjaa yleiset virheet, vaihe 2 (sovella korjauksia)", "fixCommonOcrErrorsStep2FixesFoundX": "Korjaa yleiset virheet, vaihe 2 – Löydetyt korjaukset: {0}", "action": "Toiminto", @@ -1886,18 +1959,11 @@ "adjustDurations": { "title": "Säädä kestoja", "adjustVia": "Säädä kautta", - "seconds": "Sekuntia", - "percent": "Prosenttia", "recalculate": "Laske uudelleen", "addSeconds": "Lisää sekunteja", - "setAsPercent": "Aseta prosenttiosuutena kestosta", "note": "Huomautus: Näyttöaika ei saa olla päällekkäin seuraavan tekstin alkamisaikojen kanssa", "fixed": "Kiinteä", - "milliseconds": "Millisekuntia", "extendOnly": "Vain pidennä", - "enforceDurationLimits": "Pakota vähimmäis- ja enimmäiskesto", - "checkShotChanges": "Älä pidennä ohi kuvausvaihtojen", - "batchCheckShotChanges": "Ota huomioon kuvausvaihdot (jos saatavilla)", "recalculateRequiresOcrNote": "Uudelleenlaskenta ei ole käytettävissä — yhdellä tai useammalla tekstityksellä ei ole OCR-tekstiä. Suorita ensin OCR." }, "applyDurationLimits": { @@ -1905,6 +1971,8 @@ "fixMinDurationMs": "Kiinteä vähimmäiskesto (ms)", "doNotGoPastShotChange": "Älä ylitä kuvanvaihtoa", "fixMaxDurationMs": "Kiinteä enimmäiskesto (ms)", + "fixMinDurationFrames": "Korjaa vähimmäiskesto (kehykset)", + "fixMaxDurationFrames": "Korjaa enimmäiskesto (kehykset)", "minimumDurationMilliseconds": "Vähimmäiskesto (millisekuntia):", "maximumDurationMilliseconds": "Enimmäiskesto (millisekuntia):", "maxDurationShouldBeHigherThanMinDuration": "Enimmäiskeston tulisi olla pidempi kuin vähimmäiskesto", @@ -1924,12 +1992,8 @@ }, "beautifyTimeCodes": { "title": "Siisti aikakoodit", - "beautifySettings": "Siistiasetukset", "snapToFrames": "Kiinnitä kehyksiin", - "frameGap": "Kehysväli", "minDuration": "Minimikesto (ms)", - "shotChangeThreshold": "Kuvausvaihdon kynnysarvo (ms)", - "shotChangeOffset": "Kuvausvaihdon siirtymä (kehykset)", "original": "Alkuperäinen", "beautified": "Kaunistettu", "subtitlesCount": "Tekstitykset", @@ -1958,10 +2022,76 @@ "timeCodesExtractFailed": "Aikakoodeja ei voitu lukea videosta", "timeCodesHint": "Kiinnitä cuet videon todellisiin kehysaikoihin sen sijaan, että oletettaisiin vakiokuvataajuus. Vaatii koko videon lukemisen kerran; tulos tallennetaan välimuistiin." }, + "improveTimeCodes": { + "title": "Paranna aikakoodeja (pakotettu kohdistus)", + "aligner": "Kohdistin", + "alignerHint": "Malli, joka kuuntelee kunkin rivin äänestä. Parhaat vaihtoehdot tekstityksen kielelle on lueteltu ensin.", + "maxShift": "Enimmäissiirto (sekuntia)", + "maxShiftHint": "Kuinka paljon tekstitys voi olla epätahdissa. Rivi, jonka alku siirtyisi tätä enemmän, jätetään ennalleen ja merkitään. Arvosta riippumatta yksittäistä riviä, joka haluaisi siirtyä yli puoli sekuntia enemmän kuin ympäröivät rivit, ei siirretä sokeasti: se seuraa naapureitaan, jos ne kaikki siirtyivät, ja muuten se tarjotaan valitsematta tarkistettavaksesi.", + "adjustStartTimes": "Säädä alkuaikoja", + "adjustEndTimes": "Säädä loppuaikoja", + "isolateSpeech": "Eristä puhe ensin (hidas)", + "isolateSpeechHint": "Poistaa musiikin ja äänitehosteet ennen kohdistusta, jolloin kohdistin kuulee vain dialogin. Auttaa riveissä, jotka puhutaan kovan musiikin tai toiminnan päälle, mutta kestää GPU:lla suunnilleen yhtä kauan kuin itse ääni - ja ilman sitä monta kertaa kauemmin.", + "isolatingSpeech": "Eristetään puhetta... (tämä kestää hetken)", + "isolateSpeechFailed": "Puhetta ei voitu eristää - kohdistettiin sen sijaan alkuperäiseen ääneen.", + "checkWithSpeechToText": "Tarkista puhe tekstiksi -toiminnolla", + "checkWithSpeechToTextHint": "Litteroi myös äänen ja tarkistaa jokaisen siirron sitä vasten, missä sanat todella kuultiin. Puheen vahvistamat suuret siirrot valitaan puolestasi, ja siirrot pois kohdasta, jossa rivi kuullaan, jätetään valitsematta tarkistettavaksesi. Näyttää myös, kuinka suuri osa kustakin rivistä kuultiin - pieni osuus tarkoittaa, että teksti poikkeaa sanotusta. Tekstitys, joka on enimmäissiirtoa enemmän epätahdissa - vakiosiirtymä, kuvataajuuden eroavuudesta johtuva ajelehtiminen tai hypyt leikattujen kohtausten kohdalla - synkronoidaan ensin kuultujen sanojen mukaan. Käyttää Crisp ASR Parakeetia, joka vie GPU:lla muutaman minuutin elokuvan mittaiselle videolle.", + "checkWithSpeechToTextNotAvailableX": "Puhe tekstiksi -tarkistus ei ole käytettävissä kielelle {0}", + "transcribingToCheck": "Litteroidaan kohdistuksen tarkistamiseksi...", + "speechToTextFailed": "Puhe tekstiksi epäonnistui - aikakoodeja ei tarkistettu sitä vasten.", + "speechToTextX": "Puhe tekstiksi: {0}", + "speechToTextConfirmedXDisputedY": "Puhe: {0} vahvistettu, {1} kiistetty", + "hardlyHeardX": "Puhe tekstiksi kuuli vain {0} % tekstityksen sanoista. Onko tekstitys puhutulla kielellä? Kohdistin ei pysty sijoittamaan käännöstä.", + "heard": "Kuultu", + "heardHint": "Kuinka suuren osan rivistä puhe tekstiksi kuuli. Pieni osuus tarkoittaa, että teksti poikkeaa sanotusta - juuri siellä kohdistin todennäköisimmin erehtyy.", + "statusConfirmedBySpeech": "Puheen vahvistama", + "statusDisputedBySpeech": "Kuultu entisellä paikallaan", + "statusMovedWithSync": "Siirretty synkronoinnin mukana", + "syncedFirstXY": "Synkronoitu ensin: {0} s alussa, {1} s lopussa", + "showSpeechOnly": "Näytä vain puhe", + "showSpeechOnlyHint": "Piirrä kohdistettu aaltomuoto äänestä, josta musiikki ja äänitehosteet on poistettu; alkuperäinen aaltomuoto pysyy ennallaan vertailua varten. Käytettävissä, kun puhe on eristetty - täällä tai pääikkunan aaltomuodon \"Näytä vain puhe\" -toiminnolla.", + "align": "Kohdista", + "original": "Alkuperäinen", + "aligned": "Kohdistettu", + "intro": "Valitse kohdistin ja paina \"Kohdista\" kuunnellaksesi jokaisen rivin äänestä ja tarkentaaksesi sen aikakoodeja.", + "extractingAudio": "Puretaan ääntä...", + "aligningBatchXOfY": "Kohdistetaan... erä {0}/{1}", + "extractAudioFailed": "Ääntä ei voitu purkaa ffmpegillä.", + "modelWillBeDownloaded": "{0} ladataan", + "summaryXRetimedYKeptZSkipped": "Uudelleenajoitettu: {0} · Säilytetty: {1} · Ei puhetta: {2}", + "meanShiftX": "Keskimääräinen siirto: {0} ms", + "toCheckX": "Tarkistettavia: {0}", + "largeMovesToCheckX": "{0} riviä siirtyisi yksinään pitkän matkan, ja ne jätetään valitsematta - kuuntele ne (F5 toistaa kohdistetun rivin, Shift+F5 alkuperäisen) ja valitse oikeat.", + "linesToCheckX": "{0} riviä jätetään valitsematta tarkistettaviksi - kuuntele ne (F5 toistaa kohdistetun rivin, Shift+F5 alkuperäisen) ja valitse oikeat.", + "changeXOfY": "Muutos {0}/{1}", + "previousChange": "Edellinen muutos", + "nextChange": "Seuraava muutos", + "startShift": "Alku", + "endShift": "Loppu", + "statusRetimed": "Uudelleenajoitettu", + "statusMovedWithNeighbours": "Siirretty naapuriensa mukana", + "statusLargeMoveUnconfirmed": "Suuri siirto - kuuntele, valitse käyttääksesi", + "statusUnchanged": "Jo paikallaan", + "statusNoSpeech": "Ei puhetta", + "statusShiftTooLarge": "Säilytetty - siirto liian suuri", + "statusFailed": "Säilytetty - kohdistin epäonnistui", + "statusAdjustedByHand": "Säädetty käsin", + "alignedWaveformHint": "Vedä riviä siirtääksesi sitä tai vedä sen reunoja muuttaaksesi sen alkua tai loppua.", + "playLine": "Toista rivi", + "undoAdjustment": "Kumoa säätö", + "playPause": "Toista / tauko", + "playAligned": "Kohdistettu", + "playAlignedHint": "Toista valittu rivi sen kohdistetuilla aikakoodeilla", + "apply": "Käytä", + "applyHint": "Poista rivin valinta säilyttääksesi sen alkuperäiset aikakoodit. Kaksoisnapsauta riviä toistaaksesi sen. Rivejä voi myös siirtää tai muuttaa niiden kokoa kohdistetussa aaltomuodossa." + }, "bridgeGaps": { - "title": "Yhdistä aukot", "bridgeGapsSmallerThan": "Yhdistä aukot, jotka ovat pienempiä kuin (ms)", "minGap": "Pienin aukko (ms)", + "bridgeGapsSmallerThanFrames": "Yhdistä aukot, jotka ovat pienempiä kuin (kehykset)", + "minGapFrames": "Vähimmäisaukko (kehykset)", + "calculateBridgeGapsSmallerThanDotDotDot": "Laske aukkojen yhdistämisraja kuvataajuudesta...", + "useXMsAsBridgeGapsSmallerThan": "Yhdistetäänkö aukot, jotka ovat pienempiä kuin \"{0}\" millisekuntia?", "numberOfSmallGapsBridgedX": "Täytettyjen pienten aukkojen määrä: {0}", "percentFoPrevious": "Edellisen aukon suuruus (%)", "gapChange": "Aukon muutos" @@ -1973,8 +2103,6 @@ "sortBy": { "title": "Lajittele tekstitykset", "sortOrder": "Lajittelujärjestys", - "sortByNumber": "Numero", - "sortByStartTime": "Aloitusaika", "sortByEndTime": "Lopetusaika" }, "batchConvert": { @@ -1997,6 +2125,7 @@ "addColor": "Lisää väri", "deleteLinesWithSpecificActorsOrStyles": "Poista rivit, joissa on tiettyjä näyttelijöitä tai tyylejä (erota useat pilkulla)", "useSourceStylesIfPossible": "Käytä lähdetyylejä, jos mahdollista", + "keepSourceEmbeddedFonts": "Säilytä lähteen upotetut fontit (alatunniste)", "editStyles": "Muokkaa tyylejä", "editProperties": "Muokkaa ominaisuuksia", "editAttachments": "Muokkaa liitteitä", @@ -2017,7 +2146,6 @@ "assaChangeStyleFromStyle": "Vaihda tyyli", "assaChangeStyleToStyle": "tyyliin", "assaChangeStyleImportStyle": "Tuo tyyli...", - "assaChangeStyleImportedX": "Tuotu: {0}", "assaChangeStyleTrimUnusedStyles": "Poista käyttämättömät tyylit", "assaChangeStylePropertiesTitle": "Muuta tyylin ominaisuuksia", "assaChangeStylePropertiesInfo": "Muuttaa valitut kentät tiedoston jokaisessa tyylissä koskematta itse tyyleihin.", @@ -2029,13 +2157,52 @@ "convertColorsToDialogReBreakLines": "Rivitä rivit uudelleen", "snapTimeCodesToFramesInfo": "Pyöristää jokaisen alku- ja loppuajan lähimpään kehykseen. Kuvataajuus luetaan videotiedostosta, jolla on sama nimi kuin tekstitystiedostolla, ellei kiinteää kuvataajuutta ole valittu.", "addFolderDotDotDot": "Lisää kansio...", + "addFolderRecursiveDotDotDot": "Lisää kansio alikansioineen...", "selectFolderToConvert": "Valitse kansio, jonka tiedostot muunnetaan", "includeSubfolders": "Sisällytä alikansiot kansiota lisättäessä", + "videoFilesWhenAddingFolder": "Videotiedostot (mkv, mp4, ts...) kansiota lisättäessä", + "videoFilesAsk": "Kysy", + "videoFilesInclude": "Lisää niiden tekstitysraidat", + "videoFilesSkip": "Ohita", + "folderContainsXVideoFiles": "Kansiossa on {0} videotiedostoa (mkv, mp4, ts...).\n\nLisätäänkö myös niiden upotetut tekstitysraidat vai vain tekstitystiedostot?", + "addVideoFiles": "Lisää videotiedostot", + "skipVideoFiles": "Vain tekstitystiedostot", + "doNotAskAgainVideoFiles": "Älä kysy uudelleen (muuta joukkomuunnoksen asetuksissa)", + "settingsSectionOutput": "Tuloste", + "settingsSectionAddingFiles": "Tiedostojen lisääminen", + "settingsSectionImageBasedSubtitles": "Kuvapohjaiset tekstitykset (OCR)", + "settingsSectionOther": "Muut", "keepSourceFileTimestamp": "Säilytä lähdetiedoston päiväys/aika tulostiedostoissa", - "scanningFolderX": "Selataan {0}..." + "preventSleepWhileConverting": "Estä tietokonetta siirtymästä lepotilaan muunnoksen aikana", + "scanningFolderX": "Selataan {0}...", + "transportStreamSettings": "Transport Stream -asetukset", + "transportStreamSettingsDotDotDot": "Transport Stream -asetukset...", + "transportStreamSettingsInfo": "Koskee Transport Stream -tiedostoista (.ts/.m2ts) purettuja tekstitysraitoja. Sijainti ja videon koko vaikuttavat vain DVB-kuvaraitoihin, jotka viedään kuvapohjaiseen muotoon.", + "transportStreamOverrideXPosition": "Ohita alkuperäinen X-sijainti", + "transportStreamOverrideYPosition": "Ohita alkuperäinen Y-sijainti", + "transportStreamOverrideVideoSize": "Ohita alkuperäinen videon koko", + "transportStreamBottomMargin": "Alamarginaali", + "transportStreamFileNameEnding": "Tiedostonimen loppu", + "transportStreamFileNameEndingInfo": "Lisätään tiedostopäätteen eteen jokaiselle puretulle raidalle. Jätä tyhjäksi käyttääksesi tavallista kielen jälkiliitettä.", + "transportStreamOnlyTeletext": "Vain teksti-TV", + "transportStreamGetSizeFromVideo": "Hae koko videosta...", + "twoLetterLanguageCodeUppercase": "Kaksikirjaiminen kielikoodi (isot kirjaimet)", + "threeLetterLanguageCodeUppercase": "Kolmikirjaiminen kielikoodi (isot kirjaimet)", + "formatContainsDotDotDot": "Muoto sisältää...", + "extensionIsDotDotDot": "Tiedostopääte on...", + "statusIsError": "Tila on virhe", + "forcedTracksOnly": "Vain pakotetut raidat", + "xConvertedYFailedInZ": "{0:#,###,##0} muunnettu, {1:#,###,##0} epäonnistui, aika {2}", + "xNotProcessed": "{0:#,###,##0} käsittelemättä", + "removeUnicodeControlCharactersTitle": "Poista Unicode-ohjausmerkit", + "removeUnicodeControlCharactersInfo": "Poistaa tekstistä näkymättömät Unicode-suunnanohjausmerkit (LRM, RLM, LRE, RLE, PDF, LRO ja RLO) ja korvaa sitovat välilyönnit tavallisilla välilyönneillä.", + "preset": "Esiasetus", + "savePresetDotDotDot": "Tallenna esiasetus...", + "deletePreset": "Poista esiasetus", + "presetName": "Esiasetuksen nimi", + "deletePresetX": "Poistetaanko esiasetus \"{0}\"?" }, "changeCasing": { - "title": "Muuta kirjainkokoa", "fixNames": "Korjaa nimet", "namesX": "Nimiä: {0:#,##0}", "hitsX": "Osumia: {0:#,##0}", @@ -2046,9 +2213,7 @@ "allUppercase": "Kaikki isot kirjaimet", "allLowercase": "Kaikki pienet kirjaimet" }, - "changeFormatting": { - "title": "Muuta muotoilua" - }, + "changeFormatting": {}, "convertActors": { "title": "Muunna näyttelijät", "convertActorFrom": "Muunna näyttelijä", @@ -2077,18 +2242,12 @@ "outputFormatAssa": "Advanced Sub Station Alpha (.ass)", "style1": "Tyyli 1", "style2": "Tyyli 2", - "outlineWidth": "Ääriviivan leveys", - "shadowWidth": "Varjon leveys", - "alignTop": "Yläreuna", - "alignBottom": "Alareuna", - "merge": "_Yhdistä", - "pleaseLoadTwoSubtitles": "Lataa kaksi tekstitystä" + "merge": "_Yhdistä" }, "splitSubtitle": { "title": "Jaa tekstitys", "numberOfEqualParts": "Yhtä suurten osien lukumäärä", "saveSplitParts": "_Tallenna jaetut osat", - "subtitleSplitIntoXParts": "Tekstitys jaettu {0} osaan.", "xPartsSavedInFormatYToFolder": "{0} osaa tallennettu muodossa {1} kansioon:" }, "splitBreakLongLines": { @@ -2114,13 +2273,14 @@ "onlyContinuationLines": "Vain jatkorivit" }, "mergeLinesWithSameText": { - "title": "Yhdistä rivit, joilla on sama teksti", "maxMsBetweenLines": "Rivien välinen enimmäisviive (millisekunteina)", - "includeIncrementingLines": "Sisällytä rivit, joilla on kasvava teksti" + "maxFramesBetweenLines": "Enimmäiskehykset rivien välillä", + "includeIncrementingLines": "Sisällytä rivit, joilla on kasvava teksti", + "includeRollUpCaptions": "Sisällytä roll-up-tekstitykset (vierivät)" }, "mergeLinesWithSameTimeCodes": { - "title": "Yhdistä rivit, joilla on sama aikakoodi", "maxMsDifference": "Suurin ero (millisekunteina)", + "maxFramesDifference": "Enimmäisero (kehykset)", "makeDialog": "Luo valintaikkunat" }, "netflixCheckAndFix": { @@ -2143,7 +2303,6 @@ "shotChanges": "Kuvien vaihdot", "startNumberSpellOut": "Alkukuvio kirjoitetaan auki", "textforHiUseBrackets": "Teksti HI:lle, käytä sulkeita", - "frameRate": "Kuvataajuus", "twoFrameGrap": "Kahden kehyksen väli", "whiteSpace": "Välilyönti", "childrensProgram": "Lastenohjelma", @@ -2181,7 +2340,6 @@ "italicsFixed": "Kiinteä kursivointi", "italicsNotAllowed": "Kursivointia ei sallita", "bridgeGapsXToYFrames": "3–{0} kehyksen väli => 2 kehyksen väli", - "startNumberSpellOutComment": "Kun numero aloittaa lauseen, se tulee aina kirjoittaa auki", "maximumXCharactersPerSecond": "Enintään {0} merkkiä sekunnissa" }, "imageBasedEdit": { @@ -2191,10 +2349,8 @@ "resizeImagesDotDotDot": "Muuta kuvien kokoa...", "adjustBrightnessDotDotDot": "Säädä kirkkautta...", "adjustAlphaDotDotDot": "Säädä alfa-arvoa...", - "centerHorizontally": "Keskitä vaakasuoraan", "cropImages": "Rajaa kuvat", "importTimeCodes": "Tuo aikakoodit...", - "setTextForSubtitle": "Aseta teksti tekstitykseen", "screenWidth": "Näytön leveys", "screenHeight": "Näytön korkeus", "alphaThresholdInfo": "Pikselit, joiden alfa-arvo on alle kynnysarvon, muuttuvat täysin läpinäkyviksi", @@ -2224,15 +2380,31 @@ "letterboxCustom": "Mukautettu palkin korkeus", "barHeightPx": "Palkin korkeus (px)", "titleSafePercent": "Turva-alue (%)", + "moveCaptions": "Siirrä tekstityksiä", + "moveCaptionsDotDotDot": "Siirrä tekstityksiä...", + "moveCaptionsTo": "Siirrä tekstitykset", + "moveCaptionsIntoBars": "Letterbox-palkkeihin (kuvan ulkopuolelle)", + "moveCaptionsIntoPicture": "Kuvan sisään (pois palkeista)", + "moveCaptionsOffset": "Etäisyys reunasta (px)", + "letterboxBarHeightXPxYOfZCaptionsWillMove": "Palkin korkeus: {0} px - {1}/{2} tekstitystä siirtyy", + "removeFadeInOut": "Poista häivytys sisään/ulos", + "removeFadeInOutXLinesRemoved": "{0} häivytysriviä yhdistetty riveihin, joihin ne kuuluvat", + "removeFadeInOutNothingFound": "Häivytysrivejä ei löytynyt", "positionSummary": "{0}×{1} - {2} tekstitystä - palkin korkeus: {3} px", "xInActivePicture": "{0} kuvassa", "xInTopBar": "{0} yläpalkissa", "xInBottomBar": "{0} alapalkissa", "noImageSubtitlesLoaded": "Kuvatekstityksiä ei ole ladattu", - "imageBasedFormatNotSupported": "Kuvapohjaista tekstitysmuotoa ei löytynyt/tueta." + "imageBasedFormatNotSupported": "Kuvapohjaista tekstitysmuotoa ei löytynyt/tueta.", + "frameRateDeclaredInFile": "Blu-ray sup -tiedostossa ilmoitettu kuvataajuus", + "frameRateDetectedFromTimeCodesX": "Blu-ray sup -aikakoodeista tunnistettu kuvataajuus (tiedosto ilmoittaa {0})", + "frameRateFromVideo": "Videon kuvataajuus", + "frameRateCurrent": "Nykyinen kuvataajuus", + "frameRateSetManually": "Käsin asetettu kuvataajuus", + "frameRateUsageInfo": "Blu-ray sup-, BDN XML-, DOST- ja FCP-viennit kirjoitetaan tällä kuvataajuudella. Se ei muuta mitään aikoja - käytä siihen \"Vaihda kuvataajuutta\".", + "useVideoFrameRateXInsteadOfY": "Videon kuvataajuus on {0}, mutta tekstitykselle on asetettu {1}.\n\nKäytetäänkö videon kuvataajuutta ({0})?" }, "removeTextForHearingImpaired": { - "title": "Poista teksti kuulovammaisille", "interjections": "Huudahdukset", "skipIfStartWith": "Ohita, jos alkaa", "removeTextBetween": "Poista teksti välistä", @@ -2251,7 +2423,6 @@ "and": "ja", "onlySeparateLines": "Vain erilliset rivit" }, - "pickAlignmentTitle": "Valitse tasaus", "removeUnicodeCharacters": { "title": "Poista/korvaa Unicode-merkit", "charactersFoundX": "Unicode-merkkejä löytyi: {0}", @@ -2261,13 +2432,13 @@ "pickFontNameInstalledFonts": "Asennetut fontit", "pickFontNameCollectedFonts": "Kerätyt fontit", "colorPickerTitle": "Valitse väri", + "colorPickerBrightness": "Kirkkaus", "filterLayersTitle": "Suodata näytettävät kerrokset", "filterLayersHideFromWaveform": "Piilota aaltomuodosta/spektrogrammista", "filterLayersHideFromSubtitleGrid": "Piilota tekstitysruudukosta", "filterLayersHideFromVideoPreview": "Piilota videon esikatselusta", "pickSubtitleFormat": "Valitse tekstitysmuoto", "pickSubtitleFormatImageBasedNoPreview": "Kuvapohjainen tekstitysmuoto.\nBittikartat luodaan muunnoksen aikana - ei tekstiesikatselua.", - "pickLayerTitle": "Aseta taso", "recentColors": "Viimeisimmät värit", "beautifyTimeCodesProfile": { "title": "Muokkaa aikakoodien kaunistusprofiilia", @@ -2275,7 +2446,11 @@ "presetDefault": "Oletus", "presetNetflix": "Netflix", "presetSdi": "SDI", - "general": "Yleistä", + "saveAsProfile": "Tallenna profiiliksi...", + "deleteProfile": "Poista profiili", + "profileName": "Profiilin nimi", + "overwriteProfileX": "Profiili nimeltä \"{0}\" on jo olemassa. Korvataanko se?", + "deleteProfileX": "Poistetaanko profiili \"{0}\"?", "gap": "Aukko", "gapSuffix": "kehystä (päivittää myös alla olevat nollasta poikkeavat aukkokentät)", "milliseconds": "ms", @@ -2312,9 +2487,7 @@ "columnMerge": "Yhdistä", "columnFirst": "Ensimmäinen rivi", "columnSecond": "Toinen rivi", - "columnMerged": "Yhdistetty", - "selectAll": "Valitse kaikki", - "inverseSelection": "Käänteinen valinta" + "columnMerged": "Yhdistetty" }, "fixCommonErrorsSettingsTitle": "Yleisten virheiden korjauksen asetukset" }, @@ -2359,59 +2532,51 @@ "correctWordsX": "Oikeat sanat: {0}", "namesX": "Nimet: {0}", "addedToDictionaryX": "Lisätty sanakirjaan: {0}", - "doNotShowThisAgain": "Älä näytä tätä viestiä uudelleen" + "doNotShowThisAgain": "Älä näytä tätä viestiä uudelleen", + "editUseAlwaysList": "Muokkaa \"Käytä aina\" -luetteloa...", + "useAlwaysListTitle": "Oikeinkirjoituksen tarkistus - \"Käytä aina\" -luettelo", + "useAlwaysListHeader": "\"Käytä aina\" -luettelo", + "useAlwaysListDescription": "Sanat, jotka oikeinkirjoituksen tarkistus korvaa automaattisesti, tallennettu toiminnoilla \"Vaihda kaikki\" ja \"Käytä aina\". Paria käytetään vain, kun sana on kirjoitettu väärin.", + "useAlwaysWord": "Väärin kirjoitettu sana", + "xPairs": "{0:#,##0} paria", + "xUnusedPairs": "{0:#,##0} ei koskaan käytetty", + "useAlwaysUnusedHint": "Ei koskaan käytetty - tämä sana on kirjoitettu oikein (tai se on nimi tai käyttäjän sana), joten oikeinkirjoituksen tarkistus ei koskaan merkitse sitä", + "removeUnused": "Poista käyttämättömät", + "useAlwaysEmpty": "Tälle kielelle ei ole vielä \"Käytä aina\" -pareja", + "useAlwaysEmptyHint": "Pareja lisätään, kun napsautat oikeinkirjoituksen tarkistuksessa \"Vaihda kaikki\" tai \"Käytä aina\".", + "useAlwaysNoMatch": "Mikään pari ei vastaa hakua", + "useAlwaysSameWord": "Korvaavan sanan on oltava eri kuin sana.", + "useAlwaysRememberOff": "\"Muista 'Käytä aina' -luettelo\" on pois päältä asetuksissa, joten oikeinkirjoituksen tarkistus ei käytä eikä tallenna tätä luetteloa." }, "video": { "burnIn": { "title": "Luo video, johon on poltettu tekstitys", - "infoAssaOff": "Huomautus: Advanced SubStation Alpha -tyylituki tuettu.", - "infoAssaOn": "Huomautus: Advanced SubStation Alpha -tyylit otetaan käyttöön :)", - "xGeneratedWithBurnedInSubsInX": "\"{0}\" luotu kiinteillä tekstityksillä {1}.", - "timeRemainingMinutes": "Jäljellä oleva aika: {0} minuuttia", - "timeRemainingOneMinute": "Jäljellä oleva aika: Yksi minuutti", - "timeRemainingSeconds": "Jäljellä oleva aika: {0} sekuntia", - "timeRemainingAFewSeconds": "Jäljellä oleva aika: Muutama sekunti", - "timeRemainingMinutesAndSeconds": "Jäljellä oleva aika: {0} minuuttia ja {1} sekuntia", - "timeRemainingOneMinuteAndSeconds": "Jäljellä oleva aika: Yksi minuutti ja {0} sekuntia", - "targetFileName": "Kohdetiedoston nimi: {0}", "targetFileSize": "Kohdetiedoston koko (vaatii 2-vaiheisen koodauksen)", "fileSizeMb": "Tiedoston koko megatavuina", "matchSourceVideoSize": "Käytä lähdevideon kokoa", - "passX": "Vaihe {0}", - "encoding": "Koodaus", "bitRate": "Bittinopeus", "totalBitRateX": "Kokonaisbittinopeus: {0}", "sampleRate": "Näytetaajuus", "audio": "Ääni", - "stereo": "Stereo", "preset": "Esiasetus", + "tune": "Viritys", "pixelFormat": "Pikselimuoto", "crf": "CRF", - "tuneFor": "Säädä", - "alignRight": "Kohdista oikealle", - "getStartPosition": "Hae aloituskohta", - "getEndPosition": "Hae loppukohta", "useSource": "Käytä lähdettä", - "useSourceFolder": "Käytä lähdekansiota", - "useSourceResolution": "Käytä lähteen tarkkuutta", - "outputSettings": "Tulostustiedosto/kansio...", "fontSizeFactor": "Fonttikoko", "boxType": "Laatikon tyyppi", - "fixRightToLeft": "Korjaa oikealta vasemmalle", - "cut": "Leikkaa", "fromTime": "Alkaen ajasta", "toTime": "Loppuaika", "audioEncoding": "Äänen koodaus", "outputProperties": "Tulostusominaisuudet...", "videoFileSize": "Videotiedoston koko", "oneBox": "Yksi ruutu", - "boxPerLine": "Ruutu riviä kohti", - "logoInfo": "Valitse PNG-kuva ja vedä se paikalleen videolle." + "logoInfo": "Valitse PNG-kuva ja vedä se paikalleen videolle.", + "noSubtitlesGenerateAnyway": "Poltettavia tekstitysrivejä ei ole.\n\nLuodaanko video ilman tekstityksiä?", + "noSubtitlesInXItemsGenerateAnyway": "{0} erän kohteessa ei ole poltettavia tekstitysrivejä.\n\nLuodaanko nämä videot ilman tekstityksiä?" }, "videoTransparent": { - "title": "Luo läpinäkyvä video tekstityksillä", - "infoAssaOff": "Huomautus: Tukee SubStation Alpha -tyylien edistyneitä ominaisuuksia.", - "infoAssaOn": "Huomautus: SubStation Alpha -tyylien edistyneitä ominaisuuksia käytetään :)" + "title": "Luo läpinäkyvä video tekstityksillä" }, "audioToText": { "title": "Puhe tekstiksi", @@ -2419,6 +2584,21 @@ "translateToEnglish": "Käännä englanniksi", "transcribing": "Transkriboi...", "transcribingXOfY": "Transkriboin {0} / {1}...", + "linesTranscribedXOfYFailedZ": "{0}/{1} riviä litteroitiin - {2} epäonnistui tai niissä ei ollut puhetta.\n\nEpäonnistuneet rivit säilyttävät nykyisen tekstinsä. Käytetäänkö litteroidut rivit vai yritetäänkö epäonnistuneita uudelleen (voit vaihtaa moottoria tai asetuksia ensin)?", + "linesTranscribedXOfYCancelled": "Litterointi peruutettiin {0}/{1} rivin jälkeen.\n\nKäytetäänkö litteroidut rivit? Muut rivit säilyttävät nykyisen tekstinsä.", + "noLinesTranscribed": "Yhtäkään {0} rivistä ei voitu litteroida.\nKatso lisätietoja työkalujen lokista.", + "applyTranscribedLines": "Käytä litteroidut", + "retryFailedLines": "Yritä epäonnistuneita uudelleen", + "isolateSpeech": "Eristä puhe (hidas)", + "isolateSpeechHint": "Poistaa musiikin ja äänitehosteet ennen litterointia. Vähemmän puuttuvia rivejä ja parempi ajoitus äänessä, jossa on kovaa musiikkia, mutta kestää GPU:lla suunnilleen yhtä kauan kuin itse ääni - ja ilman sitä monta kertaa kauemmin.", + "isolatingSpeech": "Eristetään puhetta...", + "isolateSpeechFailed": "Puhetta ei voitu eristää - litteroidaan sen sijaan alkuperäinen ääni.", + "detectSpeakers": "Tunnista puhujat", + "detectSpeakersHint": "Aloittaa jokaisen rivin puhujalla, esim. \"(speaker 0)\", käyttäen NVIDIAn Sortformer-diarisointia (enintään 8 puhujaa). Toimii parhaiten taustajärjestelmillä, jotka jakavat rivit puhujan vaihtuessa, kuten Parakeet ja Cohere.", + "detectSpeakersNeedsNewerCrispAsr": "\"Tunnista puhujat\" vaatii Crisp ASR v{0}:n tai uudemman, mutta asennettuna on v{1}.\n\nPäivitä Crisp ASR (rataspainike moottorin vieressä) tai poista \"Tunnista puhujat\" käytöstä.", + "xNeedsNewerCrispAsr": "{0} vaatii Crisp ASR v{1}:n tai uudemman, mutta asennettuna on v{2}.\n\nLadataanko ja asennetaanko päivitys nyt?", + "vad": "VAD", + "vadHint": "Puheaktiivisuuden tunnistus: vain puhe litteroidaan, ja se katkaistaan puhujan taukojen kohdalta.\nAuto: Silero, ja vain sitä tarvitseville moottoreille (Cohere, Mega, Index-Echo).\nSilero, FireRedVAD, Whisper-VAD, WebRTC: aina päällä, kaikille Crisp ASR -moottoreille. FireRedVAD on tarkin, Whisper-VAD antaa vähiten ja siisteimmät katkaisut mutta on hitain.\nIndex-Echo käyttää aina Sileroa. Lisäasetusten --vad, --vad-model tai --chunk-seconds ovat etusijalla.", "inputLanguage": "Syöttökieli", "advancedWhisperSettings": "Whisperin lisäasetukset", "downloadingSpeechToTextEngine": "Lataa puheesta tekstiksi -moottoria", @@ -2432,7 +2612,6 @@ "selectModel": "Valitse malli", "addCustomModelDotDotDot": "Lisää mukautettu malli...", "customModelHelp": "Voit käyttää omaa mallia: valitse mallitiedosto (whisper.cpp ggml '.bin') tai mallikansio (faster-whisper-kansio, jossa on 'model.bin'). Se kopioidaan mallikansioon ja lisätään yllä olevaan luetteloon.", - "reDownloadX": "Lataa {0} uudelleen", "updateXTitle": "Päivitä {0}?", "updateXMessage": "{0}:n uudempi versio on saatavilla.{1}{1}Lataatko ja asennatko päivityksen nyt?", "downloadingSpeechToTextModel": "Lataan puheesta tekstiksi -mallia", @@ -2444,16 +2623,7 @@ "fixCasing": "Korjaa kirjainkoko", "addPeriods": "Lisää pisteet", "changeUnderlineToColor": "Vaihda alleviivaus väriksi", - "cueBuilding": "Tekstitysten muodostus (MLX Whisper)", - "cueRebuild": "Muodosta tekstitykset uudelleen sanojen aikaleimoista", - "cueMaxChars": "Merkkien enimmäismäärä tekstitystä kohden", - "cueMaxSeconds": "Sekuntien enimmäismäärä tekstitystä kohden", - "cueMaxCps": "Merkkien enimmäismäärä sekunnissa", - "vocabularyPrompt": "Sanastokehote (nimet, paikat, termit)", - "beamSize": "Beam-koko", - "beamSizeNote": "0 on nopein. 5 antaa parhaan tarkkuuden (kuten Faster Whisper) noin 20-30 % pidemmällä ajalla. Yli 5:n arvoista on harvoin hyötyä.", "viewToolsLogFile": "Näytä työkalujen lokitiedosto", - "downloadX": "Lataa {0}", "removeNonSpeechLines": "Poista ei-puherivit", "removeNonSpeechLinesHint": "Poista rivit, jotka kuvaavat vain ääntä, kuten \"[Musiikkia]\" tai \"(aallot lyövät)\"", "removeRepeatedLines": "Poista toistuvat rivit", @@ -2512,7 +2682,7 @@ "cloneVoicePerLinePreparing": "Otetaan kunkin rivin ääni videosta...", "cloneVoicePerLineNeedsVideo": "Kunkin rivin äänen kloonaaminen vaatii videon, johon tekstitys kuuluu. Avaa video ja yritä uudelleen.", "cloneVoicePerLineNoClips": "Videosta ei saatu ääntä, joten kloonattavaa ei ole. Tarkista, että videossa on ääniraita.", - "cloneVoicePerLineNeedsOriginalSubtitleX": "{0} voi kloonata rivin vain, kun se tietää, mitä videossa kyseisen rivin kohdalla sanotaan, mikä tulee alkuperäiskielisestä tekstityksestä. Alkuperäistä tekstitystä ei ole ladattu, joten jokaisen rivin puhuisi ensimmäinen tuotu ääni videon äänen sijaan.\n\nAvaa alkuperäinen tekstitys (Tiedosto > Avaa alkuperäinen...) käännöksen rinnalle ja luo uudelleen.", + "cloneVoicePerLineTranscribeClipsXYZ": "{0} voi kloonata rivin vain, kun se tietää, mitä videossa sanotaan kyseisen rivin kohdalla. Sitä ei tiedetä {1}/{2} rivin osalta - tieto tulee yleensä alkuperäiskielisestä tekstityksestä (Tiedosto > Avaa alkuperäinen...).\n\nLitteroidaanko nämä rivit videosta nyt puhe tekstiksi -toiminnolla?\n\nKyllä: valitse moottori ja videossa puhuttu kieli, niin referenssileikkeet litteroidaan ennen puheen luontia.\nEi: luo silti - nämä rivit puhutaan tavallisella äänellä videon äänen sijaan.", "voiceCloneConsentTitle": "Äänen kloonaus - ennen kuin aloitat", "voiceCloneConsentHeader": "Olet kloonaamassa ääntä", "voiceCloneConsentIntro": "Kloonaus kopioi todellisen ihmisen äänen. Siihen liittyy sääntöjä useimmissa maissa, ja EU:ssa lakisääteisiä velvoitteita, jotka koskevat sinua, eivät Subtitle Editiä. Lue tämä kerran.", @@ -2528,6 +2698,12 @@ "voiceCloneConsentDeclined": "Äänen kloonaus ei ole käytettävissä ennen kuin nämä ehdot hyväksytään.", "advancedTtsAudioProcessing": "Äänenkäsittely", "advancedTtsOutput": "Tuloste", + "advancedTtsVoiceCloning": "Äänen kloonaus", + "cleanCloneReferences": "Puhdista äänikloonauksen referenssit (Sidon)", + "cleanCloneReferencesDescription": "Poistaa musiikin, kohinan ja kaiun äänestä, josta ääni kloonataan: videosta otetuista leikkeistä ja äänten hallinnassa tuoduista tallenteista. Käyttää Sidon-puheenpalautusmallia audio.cpp:llä (940 Mt:n lataus). Kloonattu ääni ei enää kopioi taustaa, mutta hyvin hiljainen tai päällekkäinen puhe voi kuulostaa vaimealta.", + "cleaningCloneReferencesDotDotDot": "Puhdistetaan äänikloonauksen referenssejä...", + "downloadSidonTitle": "Ladataanko Sidon?", + "downloadSidonQuestionX": "Äänikloonauksen referenssien puhdistus vaatii Sidon-puheenpalautusmallin ({0}).\n\nLadataanko se nyt? Ilman sitä referenssejä käytetään sellaisenaan.", "rubberbandInstalled": "(asennettu)", "rubberbandNotFound": "(ei löydy FFmpegistä)", "generationFolder": "Luontikansio", @@ -2536,14 +2712,11 @@ "deleteTempFilesDescription": "Poistaa luontikansion, kun Teksti puheeksi -ikkuna suljetaan. Tallennetut ääni- ja videotiedostot säilytetään.", "piperSettings": "Piper-asetukset", "title": "Teksti puheeksi", - "textToSpeechEngine": "Teksti puheeksi -moottori", "reviewAudioSegments": "TTS - Tarkista äänisegmentit", "reviewAudioSegmentsHistory": "TTS - Tarkista äänihistoria", "stability": "Vakaus", "similarity": "Samankaltaisuus", "speakerBoost": "Puhujan äänen vahvistus", - "styleExaggeration": "Tyylin liioittelu", - "regenerateAudioSelectedLine": "Valitun rivin äänen uudelleenluonti", "generateSpeechFromText": "Luo puhe tekstistä", "testVoice": "Testaa ääni", "addAudioToVideoFile": "Lisää ääni videotiedostoon", @@ -2562,20 +2735,96 @@ "videoEncodingSettings": "TTS - Videon koodausasetukset", "elevenLabsSettings": "TTS - ElevenLabs-asetukset", "elevenLabsSettingsResetHint": "Palauta ElevenLabs-asetukset oletusarvoihin", + "provider": "Palveluntarjoaja", + "openAiCompatibleSettings": "TTS - OpenAI-yhteensopivat asetukset", + "customServerUrl": "Mukautetun palvelimen URL", + "customServerUrlHint": "OpenAI-yhteensopivan palvelimen täydellinen puhe-endpoint, jota käytetään, kun palveluntarjoaja on \"Custom\", esim. http://localhost:8880/v1/audio/speech", + "customModels": "Mukautetut mallit", + "customModelsHint": "Pilkuilla erotetut mallien nimet, joita tarjotaan, kun palveluntarjoaja on \"Custom\"", + "customVoices": "Mukautetut äänet", + "customVoicesHint": "Pilkuilla erotetut äänten nimet, joita käyttävät \"Custom\"-palveluntarjoaja ja OpenRouter-mallit, jotka eivät julkaise äänilistaa", + "instructions": "Ohjeet", + "instructionsHint": "Valinnainen puhetyyli, esim. \"Puhu rauhallisella, lämpimällä äänellä\". Lähetetään vain sitä tukeville malleille (ei tts-1/tts-1-hd)", + "openAiSpeedHint": "Puheen nopeus 0,25–4,0 (1,0 on normaali). Kaikki mallit eivät tue sitä", + "outputFormat": "Tulostemuoto", + "outputFormatHint": "Palvelimelta pyydetty äänimuoto. \"Auto\" pyytää MP3:a ja vaihtaa PCM:ään malleille, jotka palauttavat vain PCM:ää (esim. Gemini TTS)", "regenerateAudio": "Luo ääni uudelleen", "autoContinuePlaying": "Jatka toistoa automaattisesti", "addingAudioToVideoFileDotDotDot": "Lisätään ääntä videotiedostoon...", "preparingMergeDotDotDot": "Valmistellaan yhdistämistä...", "importVoiceDotDotDot": "Tuodaan ääni...", + "renameVoiceDotDotDot": "Nimeä ääni uudelleen...", + "renameVoiceTitle": "Nimeä ääni uudelleen", + "voiceXCouldNotBeRenamedX": "Ääntä '{0}' ei voitu nimetä uudelleen: {1}", + "deleteVoiceDotDotDot": "Poista ääni...", + "deleteVoiceTitle": "Poista ääni", + "deleteVoiceXQuestion": "Poistetaanko ääni '{0}'?\n\nReferenssitallenne ja sen tiedostot poistetaan levyltä.", + "voiceXCouldNotBeDeletedX": "Ääntä '{0}' ei voitu poistaa: {1}", "voiceImportSuccessTitle": "Ääni tuotu", "voiceXImported": "Ääni '{0}' tuotu onnistuneesti", "voiceCloneTranscriptTitle": "Syötä äänen transkriptio (vaaditaan äänen kloonaamiseen)", + "voiceManagerTitle": "TTS - Äänten hallinta", + "voiceManagerDotDotDot": "Äänten hallinta...", + "voiceManagerMenuItem": "TTS-äänten hallinta...", + "speakFromCurrentLineTitle": "Puhu nykyisestä rivistä alkaen", + "speakFromCurrentLineDotDotDot": "Puhu nykyisestä rivistä alkaen...", + "speakFromCurrentLineHint": "Lukee rivit ääneen peräkkäin nykyisestä rivistä alkaen. Lopeta painamalla Escape tai napsauttamalla toista riviä.", + "stopSpeaking": "Lopeta puhuminen", + "speakingLineX": "Puhutaan riviä {0}... (Escape lopettaa)", + "playWithSpeechTitle": "Toista puheen kanssa", + "playWithSpeechDotDotDot": "Toista puheen kanssa...", + "playWithSpeechHint": "Toistaa videon ja puhuu kunkin rivin, kun video saavuttaa sen. Keskeytä ja siirry tavalliseen tapaan; lopeta painamalla Escape.", + "lowerVideoVolumeWhileSpeaking": "Pienennä videon äänenvoimakkuutta puheen aikana", + "pauseVideoWhenSpeechRunsLate": "Keskeytä video, kun puhe ulottuu seuraavaan riviin", + "waitingForSpeechLineX": "Luodaan puhetta riville {0}... (Escape lopettaa)", + "voiceKindClone": "Kloonattu ääni", + "voiceKindPreset": "Sisäänrakennettu", + "voiceKindModel": "Äänimalli", + "voiceKindOnline": "Verkossa", + "transcript": "Litterointi", + "transcriptRequiredHint": "Pakollinen - moottori lukee tämän tallenteen litteroinnin (ref-text) kloonatessaan", + "transcriptOptionalHint": "Valinnainen - tarkka litterointi tallenteesta parantaa kloonausta", + "saveTranscript": "Tallenna litterointi", + "searchVoices": "Hae ääniä", + "loadingVoicesDotDotDot": "Ladataan ääniä...", + "noVoicesForEngine": "Ei ääniä - tuo tallenne tai lataa äänipaketti", + "noVoicesMatchFilter": "Mikään ääni ei vastaa hakua", + "copyVoiceTo": "Kopioi ääni kohteeseen", + "copyVoiceToDotDotDot": "Kopioi ääni moottoriin...", + "copyingVoiceXToYDotDotDot": "Kopioidaan ääntä '{0}' kohteeseen {1}...", + "voiceXCopiedToY": "Ääni '{0}' kopioitu kohteeseen {1}", + "voiceXCouldNotBeCopiedToY": "Ääntä '{0}' ei voitu kopioida kohteeseen {1} - katso lisätiedot lokista", + "voiceXAlreadyExistsInYContinue": "Ääni nimeltä '{0}' on jo olemassa kohteessa {1}.\n\nKopioidaanko silti?", + "openVoicesFolder": "Avaa äänikansio", + "downloadVoicePacksDotDotDot": "Lataa äänipaketteja...", + "downloadVoicePacksTitle": "TTS - Lataa äänipaketteja", + "voicePackIntro": "Äänipaketit ovat lyhyiden, puhtaiden tallenteiden ja niiden litterointien kokoelmia, valmiita kloonattaviksi. Jokainen paketti tuodaan valittuun moottoriin (jo olemassa olevat äänet ohitetaan) ja sisältää ATTRIBUTION.txt-tiedoston lisenssistä ja lähteistä.", + "voicePackDetailsXVoicesYSizeZLicense": "{0} ääntä · {1} · {2}", + "installToEngine": "Asenna moottoriin", + "downloadAndInstall": "Lataa ja asenna", + "selectAtLeastOneVoicePack": "Valitse vähintään yksi äänipaketti", + "downloadingXDotDotDot": "Ladataan {0}...", + "installingXDotDotDot": "Asennetaan {0}...", + "xVoicesInstalledYSkipped": "{0} ääntä asennettu ({1} oli jo olemassa)", "useSpeechToTextDotDotDot": "Käytä puheesta tekstiksi...", "advancedTtsSettings": "TTS:n lisäasetukset", "proAudioPostProcessing": "Pro-äänen jälkikäsittely", "proAudioPostProcessingDescription": "Soveltaa EQ-lämpöä, kohinaporttia, kompressiota, äänenvoimakkuuden normalisointia (-16 LUFS) ja häivytystä/häivytystä jokaiseen segmenttiin.", + "generatingSpeechSegmentXOfY": "Luodaan puhetta: segmentti {0}/{1}", + "adjustingSpeedSegmentXOfY": "Säädetään nopeutta: segmentti {0}/{1}", + "mergingAudioSegmentXOfY": "Yhdistetään ääntä: segmentti {0}/{1}", + "postProcessingSegmentXOfY": "Jälkikäsittely: segmentti {0}/{1}", "audioDucking": "Äänen vaimennus", "audioDuckingDescription": "Vähentää alkuperäisen videon äänenvoimakkuutta ja sekoittaa sen TTS-ääneen, jolloin alkuperäinen ääniraita on edelleen heikosti kuultavissa.", + "removeOriginalSpeech": "Poista alkuperäinen puhe (hidas)", + "removeOriginalSpeechDescription": "Säilyttää alkuperäisen videon musiikin ja äänitehosteet mutta poistaa sen puheen ja miksaa uuden puheen niiden päälle. Kestää GPU:lla suunnilleen yhtä kauan kuin itse video ja ilman sitä monta kertaa kauemmin. Kun äänen vaimennus on päällä, sen äänenvoimakkuus koskee musiikkia ja tehosteita. Koskee vain tilannetta, jossa puhe lisätään videotiedostoon.", + "removingOriginalSpeech": "Poistetaan alkuperäistä puhetta...", + "removeOriginalSpeechFailed": "Alkuperäistä puhetta ei voitu poistaa - puhe lisättiin videoon ilman poistoa.", + "speechRemovalModel": "Malli", + "speechRemovalModelDescription": "Paras laatu säilyttää eniten musiikista ja äänitehosteista. Nopea on noin kolme kertaa nopeampi, ja musiikista menetetään hieman enemmän.", + "speechRemovalBestQuality": "Paras laatu (Mel-Band RoFormer)", + "speechRemovalFast": "Nopea (HTDemucs)", + "videoTruncatedWarning": "Uuden videotiedoston kuva loppuu kohdassa {0}, mutta alkuperäinen video on {1} pitkä - kuva jähmettyy äänen jatkuessa.\n\nTiedosto säilytettiin. Katso ffmpegin tuloste tiedostosta error-log.txt Subtitle Editin datakansiossa.", "originalVolumePercent": "Alkuperäinen äänenvoimakkuus %", "vadSilenceCompression": "VAD-hiljaisuuden kompressio", "vadSilenceCompressionDescription": "Lyhentää sanojen välisiä taukoja ennen tempon muuttamista. Käyttää äänen aktiivisuuden tunnistusta (Voice Activity Detection) kompressoimaan vain hiljaisuuden aukkoja puhetta muuttamatta. Tämä on suositeltava ensimmäinen vaihe — se lyhentää kestoa ilman laadun heikkenemistä.", @@ -2644,7 +2893,6 @@ "subtitleMergedLinesAppliedPlural": "{0} rivien yhdistämistä otettu käyttöön teksti puheeksi -toiminnosta" }, "shotChanges": { - "titleGenerateOrImport": "Luo/tuo kuvanvaihtoja", "generateShotChanges": "Luo kuvanvaihtoja", "importShotChanges": "Tuo kuvanvaihtoja", "generateShotChangesWithFfmpeg": "Luo kuvausvaihtoja ffmpeg:llä", @@ -2654,7 +2902,9 @@ "timeCodeFormatColon": "Aikakoodimuoto:", "shotChangesList": "Kuvausvaihtojen luettelo", "shotChangesClearQuestion": "Haluatko varmasti poistaa kaikki kuvausvaihdot?", - "deleteSelectedShotChangeQuestion": "Haluatko varmasti poistaa valitun kuvausvaihdon?" + "deleteSelectedShotChangeQuestion": "Haluatko varmasti poistaa valitun kuvausvaihdon?", + "shotChanges": "Kuvanvaihdot", + "exportShotChanges": "Vie kuvanvaihdot" }, "chapters": { "title": "Luvut", @@ -2684,7 +2934,6 @@ "scaleTimesDescription": "Skaalaa jokaisen luvun aika kuvataajuudesta toiseen.", "fromFrameRate": "Mistä", "toFrameRate": "Mihin", - "apply": "Käytä", "writeToVideo": "Kirjoita videoon...", "writeToVideoTitle": "Kirjoita luvut videoon", "writeToVideoDescription": "Luvut kirjoitetaan videotiedoston kopioon. Mitään ei koodata uudelleen, joten tämä on nopeaa ja häviötöntä.", @@ -2694,7 +2943,6 @@ "writtenToX": "Luvut kirjoitettu kohteeseen {0}", "unableToWriteChapters": "Lukuja ei voitu kirjoittaa videotiedostoon.", "chaptersSavedToX": "Luvut tallennettu kohteeseen {0}", - "noVideoLoaded": "Videota ei ole ladattu", "emptyListCallToAction": "Ei vielä lukuja - lisää luku videon kohtaan tai tuo luvut videosta tai tiedostosta." }, "videoOcr": { @@ -2705,10 +2953,6 @@ "bottomHalf": "Alapuolisko", "fullFrame": "Koko kuva", "engine": "OCR-moottori", - "language": "Kieli", - "url": "URL", - "model": "Malli", - "apiKey": "API-avain", "scan": "Skannaa", "framesPerSecond": "Kuvia sekunnissa", "textBrightnessMinimum": "Tekstin vähimmäiskirkkaus (0=pois)", @@ -2723,10 +2967,10 @@ "runningOcrXY": "Suoritetaan OCR... {0}/{1}", "noLinesFoundTitle": "Tekstityksiä ei löytynyt", "noLinesFoundMessage": "Skannausalueelta ei löytynyt tekstiä - kokeile säätää skannausaluetta, moottoria tai vähimmäiskirkkautta.", + "ocrStoppedEarlyMessage": "OCR-moottori pysähtyi ennen kuin kaikki kehykset oli luettu, joten tähän mennessä löydetyt rivit säilytetään ja loput puuttuvat.{0}{0}{1}", "linesFoundX": "{0} riviä löytyi", "refiningTimingXY": "Hienosäädetään ajoitusta... {0}/{1}", "fixOcrErrors": "Korjaa OCR-virheet", - "dictionary": "Sanakirja", "editLineX": "Muokkaa riviä {0}", "downloadDictionaryHint": "Lataa oikolukusanakirja...", "framesPerSecondHint": "Kuinka monta kehystä sekunnissa skannataan. 5 riittää tavallisille tekstityksille; suurempi arvo nappaa hyvin lyhyet rivit ja tiivistää ryhmittelyä, mutta kuluttaa enemmän OCR-aikaa.", @@ -2743,7 +2987,58 @@ "testOcr": "Testaa nykyinen kuva", "testOcrRunning": "Testataan OCR:ää nykyisellä kuvalla...", "testOcrResultX": "Testitulos: {0}", - "testOcrNoTextFound": "Testi: skannausalueelta ei löytynyt tekstiä" + "testOcrNoTextFound": "Testi: skannausalueelta ei löytynyt tekstiä", + "engineSettings": "OCR-moottorin asetukset", + "engineBuiltIn": "Sisäänrakennettu käyttöjärjestelmään", + "engineExternalServer": "Ulkoinen palvelin (asennetaan ja hallitaan Subtitle Editin ulkopuolella)", + "engineCloudApi": "Pilvi-API (vaatii API-avaimen)", + "engineNothingToInstall": "Ei asennettavaa", + "engineModelsMissing": "Moottori asennettu, mallit puuttuvat", + "website": "Verkkosivusto" + }, + "backgroundMusic": { + "title": "Luo taustamusiikkia", + "generateBackgroundMusicDotDotDot": "Luo taustamusiikkia...", + "preset": "Esiasetus", + "prompt": "Kehote", + "tempoBpm": "Tempo (BPM)", + "generateSeconds": "Luo (sekuntia)", + "seed": "Siemen", + "randomSeed": "Satunnainen", + "outputLength": "Tulosteen pituus", + "outputLengthVideoX": "{0} (videon pituus - musiikki toistuu saumattomasti)", + "musicVolumePercent": "Musiikin äänenvoimakkuus (%)", + "removeExistingAudioTracks": "Poista olemassa olevat ääniraidat", + "originalAudioVolumePercent": "Alkuperäisen äänen voimakkuus (%)", + "saveAudioDotDotDot": "Tallenna ääni...", + "addToVideoDotDotDot": "Lisää videoon...", + "engineInfo": "ACE-Step 1.5 Turbo audio.cpp:n kautta (MIT-lisenssi) - toimii paikallisesti", + "presetCooking": "Ruoanlaitto / leivonta", + "presetTech": "Teknologia / laitearvostelu", + "presetForest": "Metsä / luonto", + "presetRelaxingPiano": "Rentouttava piano", + "presetLofiVlog": "Lo-fi-vlogi", + "presetCorporate": "Yritys / esittelyvideo", + "loadingModel": "Ladataan mallia...", + "composing": "Sävelletään...", + "generatingAudio": "Luodaan ääntä...", + "decodingAudio": "Puretaan ääntä...", + "creatingLoop": "Luodaan saumatonta silmukkaa...", + "resultLoopXYZ": "Silmukka: {0}, {1} BPM, sauman vastaavuus {2} %", + "resultNoLoopX": "Musiikki valmis: {0}", + "addingMusicToVideo": "Lisätään musiikkia videoon...", + "downloadModelTitle": "Ladataanko musiikkimalli?", + "downloadModelQuestionX": "Musiikin luonti vaatii ACE-Step 1.5 -mallin ({0}).\n\nLadataanko malli?", + "downloadingModel": "Ladataan ACE-Step 1.5 -mallia...", + "saveAudioTitle": "Tallenna taustamusiikki", + "audioFileSavedX": "Äänitiedosto tallennettu: {0}", + "unableToGenerateMusic": "Musiikin luonti epäonnistui", + "unableToAddMusicToVideo": "Musiikin lisääminen videoon epäonnistui", + "outputLengthTextToSpeech": "Seuraa luotua puhetta - musiikki toistuu saumattomasti ja hiljenee, kun joku puhuu", + "addBackgroundMusic": "Lisää taustamusiikki", + "addBackgroundMusicHint": "Luo musiikkia paikallisesti ACE-Step 1.5:llä ja miksaa se puheen alle - se hiljenee, kun joku puhuu", + "generatingBackgroundMusicDotDotDot": "Luodaan taustamusiikkia...", + "mixingBackgroundMusicDotDotDot": "Miksataan taustamusiikkia..." }, "goToVideoPosition": "Siirry videon kohtaan", "goToVideoPositionDotDotDot": "Siirry videon kohtaan...", @@ -2754,30 +3049,88 @@ "openSecondarySubtitleOnVideoPlayerDotDotDot": "Toissijainen tekstitys videosoittimessa, avaa...", "openSecondarySubtitleOnVideoPlayer": "Toissijaiset tekstitykset (videosoittimessa)", "removeSecondarySubtitleOnVideoPlayer": "Toissijaiset tekstitykset videosoittimessa, poista", + "editSecondarySubtitleSettings": "Muokkaa toissijaisen tekstityksen asetuksia", + "editSecondarySubtitleSettingsDotDotDot": "Muokkaa toissijaisen tekstityksen asetuksia...", + "secondarySubtitleRememberSettings": "Muista nämä asetukset", + "secondarySubtitleDoNotShowAgain": "Älä näytä tätä ikkunaa uudelleen", + "secondarySubtitleJustifyLines": "Tasaa rivit", "openRecentVideo": "Avaa viimeaikainen video", "clearRecentVideos": "Tyhjennä viimeaikaiset videot", "cutVideoTitle": "Leikkaa video", "cutVideoDotDotDot": "Leikkaa video...", + "remuxVideoTitle": "Remuxaa video", + "remuxVideoDotDotDot": "Remuxaa video...", + "remuxVideoInputVideo": "Lähdevideo:", + "remuxVideoMultipleTracksRequiresMkv": "Useat ääni- tai tekstitysraidat vaativat MKV-säiliön. Tulostemuodoksi on vaihdettu automaattisesti .mkv.", + "remuxVideoOutputFormat": "Tulostemuoto:", + "remuxVideoOutputFile": "Tulostetiedosto:", + "remuxVideoRemuxing": "Remuxataan videota ja ääntä...", + "remuxVideoFinalizing": "Viimeistellään tulostetiedostoa (siirretään indeksi alkuun)...", + "remuxVideoCompleted": "Video remuxattiin onnistuneesti.", + "remuxVideoFailed": "Videon remuxaus epäonnistui.", + "remuxVideoPleaseSelectBoth": "Valitse sekä video- että äänitiedostot.", + "remuxVideoAssRequiresMkv": "ASS/SSA-tekstitykset vaativat MKV-säiliön, jotta kaikki muotoilut ja tyylit säilyvät.", + "remuxVideoSccRequiresMov": "Scenarist (.scc) -tekstitykset upotetaan CEA-608-tekstityksinä (closed captions), jotka vaativat MOV- tai MPG-säiliön. Tulostemuodoksi on vaihdettu automaattisesti .mov.", + "remuxVideoSccNotWithWebM": "Scenarist (.scc) -tekstitykset vaativat MOV-säiliön, johon .webm-tiedoston VP9/AV1-video ei sovi. Poista .scc-tiedosto tai muunna se toiseen tekstitysmuotoon.", + "remuxVideoMpgMaxTwoCaptionTracks": ".mpg-tiedostossa voi olla enintään kaksi closed caption -raitaa (CC1 ja CC3) - poista tekstitystiedostoja niin, että jäljelle jää yksi tai kaksi.", + "remuxVideoMccRequiresMpg": "MacCaption (.mcc) -tekstitykset upotetaan CEA-608/708-tekstityksinä MPEG-2-videoon, mikä vaatii MPG-säiliön. Tulostemuodoksi on vaihdettu automaattisesti .mpg.", + "remuxVideoMpgReencodeVideoX": ".mpg-tiedoston closed caption -tekstitykset vaativat MPEG-2-videon, ja video on {0}. Koodataanko video uudelleen MPEG-2-muotoon? Tämä kestää kauemmin ja heikentää laatua hieman.", + "remuxVideoFileHasNoAudioX": "Tiedostossa \"{0}\" ei ole ääniraitaa, joten sitä ei voi käyttää äänilähteenä.", + "remuxVideoAddingClosedCaptions": "Lisätään closed caption -tekstityksiä...", + "remuxVideoSelectAudioTrack": "Valitse ääniraita", + "remuxVideoSelectAudioTrackFor": "Valitse ääniraita kohteelle {0}", + "remuxVideoSelectAudioTrackPrompt": "Tiedosto '{0}' sisältää {1} ääniraitaa. Valitse remuxattava ääniraita:", + "remuxVideoVideoFile": "video", + "remuxVideoAudioFile": "ääni", + "remuxVideoAudioFiles": "Äänitiedostot:", + "remuxVideoSubtitleFiles": "Tekstitystiedostot (valinnainen, valittavat tekstitykset):", + "remuxVideoSelectAudioTrackDotDotDot": "Valitse ääniraita...", + "remuxVideoFilesX": "{0} tiedostoa", + "remuxVideoMixAudio": "Miksaa ääni yhdeksi raidaksi", + "remuxVideoVolumePercent": "Äänenvoimakkuus %:", + "remuxVideoVolumeX": "Äänenvoimakkuus {0} %", + "remuxVideoFastStart": "Optimoi verkkosuoratoistoa varten (siirrä indeksi alkuun - hitaampi)", "embedSubtitlesDotDotDot": "Lisää/poista upotetut tekstitykset...", "generateTimeCodes": "Luo aikakoodit", "checkeredImage": "Ruudullinen kuva", - "saveVideoAsTitle": "Tallenna video nimellä", "promptForFfmpegParamsAndGenerate": "Kysy ffmpeg-parametreja ja luo", "cutVideoCutSegments": "Leikkaa segmentit", "cutVideoMergeSegments": "Yhdistä segmentit", "cutVideoSplitSegments": "Tallenna segmentit erikseen", - "cutVideoType": "Leikkaustyyppi", - "mpvRenderAuto": "Automaattinen", - "mpvRenderNative": "Natiivi", - "mpvRenderOpenGl": "OpenGL", - "mpvRenderSoftware": "Ohjelmisto (hidas)", "cutVideoAlsoCutSubtitle": "Leikkaa myös tekstitys", "cutVideoSubtitleFileGeneratedX": "Tekstitystiedosto luotu: {0}", + "cutVideoTransition": "Siirtymä segmenttien välillä", + "cutVideoTransitionDurationSeconds": "Kesto (sekuntia)", + "cutVideoFadeInSeconds": "Häivytys mustasta alussa (sekuntia)", + "cutVideoFadeOutSeconds": "Häivytys mustaan lopussa (sekuntia)", + "cutVideoPreviewTransition": "Esikatsele siirtymää", + "cutVideoPreviewTransitionTitle": "Siirtymän esikatselu", + "cutVideoPreviewNeedsJoin": "Esikatseltavaa liitosta ei ole - lisää vähintään kaksi yhdistettävää segmenttiä tai segmentti, joka leikataan pois keskeltä.", + "cutVideoTransitionInfoX": "Jokainen siirtymä on päällekkäin yhdistämiensä osien kanssa, joten tuloste lyhenee {0} sekuntia liitosta kohden. Leikattu tekstitys ajoitetaan uudelleen vastaavasti.", + "cutVideoTransitionFade": "Häivytys", + "cutVideoTransitionFadeBlack": "Häivytys mustan kautta", + "cutVideoTransitionFadeWhite": "Häivytys valkoisen kautta", + "cutVideoTransitionFadeGrays": "Häivytys harmaan kautta", + "cutVideoTransitionDissolve": "Ristihäivytys", + "cutVideoTransitionWipeLeft": "Pyyhkäisy vasemmalle", + "cutVideoTransitionWipeRight": "Pyyhkäisy oikealle", + "cutVideoTransitionWipeUp": "Pyyhkäisy ylös", + "cutVideoTransitionWipeDown": "Pyyhkäisy alas", + "cutVideoTransitionSlideLeft": "Liuku vasemmalle", + "cutVideoTransitionSlideRight": "Liuku oikealle", + "cutVideoTransitionSlideUp": "Liuku ylös", + "cutVideoTransitionSlideDown": "Liuku alas", + "cutVideoTransitionSmoothLeft": "Pehmeä vasemmalle", + "cutVideoTransitionSmoothRight": "Pehmeä oikealle", + "cutVideoTransitionCircleOpen": "Ympyrä aukeaa", + "cutVideoTransitionCircleClose": "Ympyrä sulkeutuu", + "cutVideoTransitionRadial": "Säteittäinen", + "cutVideoTransitionPixelize": "Pikselöinti", + "cutVideoTransitionBlur": "Sumennus", "importCurrentSubtitle": "Tuo nykyiset tekstitykset", "addRemoveEmbeddedSubtitlesTitle": "Lisää/poista upotetut tekstitykset", "addRemoveEmbeddedSubtitlesMp4Title": "Lisää/poista upotetut tekstitykset (MP4)", "addCurrentSubtitle": "Lisää nykyiset tekstitykset", - "embeddedTrackColumnNew": "Uusi", "embeddedTrackNoSubtitlesFoundTitle": "Tekstityksiä ei löytynyt", "embeddedTrackNoSubtitlesFoundMessage": "Valittu tekstitystiedosto ei sisällä tekstityksiä.", "embeddedTrackNoTracksTitle": "Ei lisättyjä raitoja", @@ -2789,7 +3142,7 @@ "embeddedTrackGeneratingVideoXY": "Luodaan videota... {0}% {1}", "embeddedTrackGeneratingVideo": "Luodaan videota...", "mp4FilesFilter": "MP4-tiedostot", - "titleOrLanguage": "Otsikko/kieli", + "embeddedTracksPickVideoHint": "Valitse videotiedosto tai pudota se tähän nähdäksesi sen tekstitysraidat.", "viewMatroskaTrackX": "Näytä Matroska-raita - {0}", "resolutionSeparator": "x", "openFromUrlTitle": "Avaa videotiedosto URL-osoitteesta", @@ -2825,15 +3178,17 @@ "reEncodeGeneratingDone": "Luominen valmis", "reEncodeGeneratedFilesX": "Luodut tiedostot ({0}):", "reEncodeFfmpegParameters": "ffmpeg-parametrit", + "analyzingVideoX": "Analysoidaan videota... {0} % {1}", + "analyzingVideoXofY": "Analysoidaan videota {0}/{1}... {2} % {3}", + "backgroundImageFileNotSelected": "Taustakuvatiedostoa ei ole valittu", + "backgroundImageFileDoesNotExistX": "Taustakuvatiedostoa ei ole olemassa: {0}", "openFromUrlOpenOnline": "Avaa verkossa", "openFromUrlOpenOnlineDescription": "Suoratoista video suoraan. Nopein aloitus.", "openFromUrlOpenOnlineNote": "Puhe tekstiksi ja aaltomuotopiikit eivät ole käytettävissä tässä tilassa.", "openFromUrlDownloadAndOpen": "Lataa ja avaa", "openFromUrlDownloadAndOpenDescription": "Tallenna video paikallisesti ja avaa se sitten.", "openFromUrlDownloadAndOpenNote": "Vaaditaan puhe tekstiksi -toimintoon ja aaltomuotopiikkeihin.", - "openFromUrlMissing": "Anna ensin videon URL.", "openFromUrlDownloadingTitle": "Ladataan videota", - "openFromUrlSaveAs": "Tallenna video nimellä", "openFromUrlDownloadSubtitles": "Lataa myös tekstitykset", "pickOnlineSubtitleTitle": "Valitse ladattava tekstitys", "pickOnlineSubtitleFetching": "Ladataan tekstityksiä...", @@ -2843,7 +3198,6 @@ "talkerX": "Puhuja {0}", "baseModel": "Perusmalli", "turboModel": "Turbo-malli", - "ttsUpdateX": "Päivitä {0}", "ttsEngineNotInstalled": "{0} ei ole asennettu", "ttsEngineUpdateAvailable": "{0} - päivitys saatavilla", "ttsEngineTooOldUpdateRequired": "{0} on liian vanha - päivitys vaaditaan", @@ -2866,19 +3220,25 @@ "cloneVoiceTo": "Kloonaa ääni kohteeseen", "cloneVoiceNameTitle": "Uuden äänen nimi", "cloneVoiceExtractFailed": "Valitun rivin ääntä ei voitu purkaa.", + "lineXHasNoDuration": "Rivillä {0} ei ole kestoa (loppuaika ei ole alkuajan jälkeen), joten purettavaa ääntä ei ole.", + "audioClipsSkippedLinesX": "Näille riveille ei voitu purkaa ääntä, joten ne ohitettiin: {0}", "toggleShotChange": "Vaihda kuvanvaihtoa", "resetWaveformZoomAndSpeed": "Nollaa aaltomuodon zoomaus ja nopeus", "showOriginalSubtitle": "Näytä alkuperäinen tekstitys", + "timelineGroupTracksBy": "Ryhmittele tekstitysraidat", + "timelineShowThumbnails": "Näytä videon pikkukuvat", + "showSpeechOnly": "Näytä vain puhe (hidas luoda)", + "isolatingSpeechForWaveformX": "Eristetään puhetta aaltomuotoa varten... {0}", + "isolatingSpeechForWaveformFailed": "Puhetta ei voitu eristää aaltomuotoa varten - katso työkalujen loki", + "speechOnlyWaveformReady": "Aaltomuoto näyttää nyt vain puheen", "showOnlyWaveform": "Näytä vain aaltomuoto", "showOnlySpectrogram": "Näytä vain spektrogrammi", "showWaveformAndSpectrogram": "Näytä aaltomuoto ja spektrogrammi", - "spectrogramClassic": "Klassinen", "spectrogramClassicViridis": "Viridis", "spectrogramClassicPlasma": "Plasma", "spectrogramClassicInferno": "Inferno", "spectrogramClassicTurbo": "Turbo", "spectrogramNeon": "Neon", - "waveformDrawStyleClassic": "Klassinen", "waveformDrawStyleFancy": "Fancy", "setVideoPositionAndPauseAndSelectSubtitle": "Aseta videon sijainti, keskeytä ja valitse tekstitys", "setVideopositionAndPauseAndSelectSubtitleAndCenter": "Aseta videon sijainti, keskeytä, valitse tekstitys ja keskitä", @@ -2888,7 +3248,6 @@ }, "sync": { "visualSync": "Visuaalinen synkronointi", - "sync": "Synkronointi", "startScene": "Aloita kohtaus", "endScene": "Lopeta kohtaus", "playTwoSecondsAndBack": "Toista 2 sekuntia ja palaa takaisin", @@ -2904,19 +3263,18 @@ "adjustSelectedLinesAndForward": "Säädä valittuja rivejä ja eteenpäin", "toFrameRate": "Kuvataajuuteen asti", "fromFrameRate": "Kuvataajuudesta", + "videoXFrameRateY": "{0} - {1} fps", "adjustAllTimes": "Säädä kaikki ajat (näytä aikaisemmin/myöhemmin)", "showEarlier": "Näytä aikaisemmin", "showLater": "Näytä myöhemmin", - "changeFrameRate": "Muuta kuvataajuutta", "setSyncPoint": "Aseta synkronointipiste", "setSyncPointViaVideo": "Aseta synkronointipiste videon avulla...", "syncPointTimeCode": "Synkronointipisteen aikakoodi", "syncPoints": "Synkronointipisteet", "pointSync": "Pisteen synkronointi", "pointSyncViaOther": "Pisteen synkronointi toisen tekstityksen kautta", - "adjustmentX": "Säätö: {0}", - "totalAdjustmentX": "Kokonaiskorjaus: {0}", - "syncPointCandidateInfo": "Rivit, jotka alkavat yli {0} sekunnin hiljaisuuden jälkeen, on korostettu - ne ovat usein luotettavia synkronointipisteitä", + "syncPointCandidateAfterInfo": "Rivit, joita seuraa vähintään {0} sekuntia hiljaisuutta, on korostettu - ne ovat usein luotettavia synkronointipisteitä", + "gapAfter": "Väli jälkeen", "adjustAllShortcuts": "Pikanäppäimet:\r\n\r\n• Shift + Vasen/Oikea: Siirrä 10 ms\r\n• Ctrl + Vasen/Oikea: Siirrä 100 ms\r\n• Alt + Vasen/Oikea: Siirrä 500 ms", "adjustAllShortcutsFrames": "Pikanäppäimet:\r\n\r\n• Shift + Vasen/Oikea: Siirrä 1 kuva\r\n• Ctrl + Vasen/Oikea: Siirrä 10 kuvaa\r\n• Alt + Vasen/Oikea: Siirrä 1 sekunti", "offsetInSeconds": "Siirtymä sekunteina", @@ -2936,6 +3294,7 @@ "lineMerge": "Rivien yhdistäminen", "delayInSecondsBetweenRequests": "Viive sekunteina pyyntöjen välillä", "maxBytesPerRequest": "Enimmäistietomäärä pyyntöä kohti", + "keepMusicLinesUntranslated": "Älä käännä musiikkisymbolien sisällä olevia rivejä (♪ ... ♪)", "promptText": "Kehoteteksti", "resetPromptToDefault": "Palauta kehote oletusarvoon", "translateEachLineSeparately": "Käännä jokainen rivi erikseen", @@ -2949,11 +3308,10 @@ "readyToTranslate": "Valmis kääntämään", "translating": "Käännetään...", "translationComplete": "Käännös valmis", + "translateInPlaceNoOriginal": "Käännä paikallaan (älä luo alkuperäistä)", "translationCancelled": "Käännös peruutettu", "swapLanguages": "Vaihda lähde- ja kohdekieli keskenään", "xIsAlreadyDownloadedReDownload": "{0} on jo ladattu. Ladataanko uudelleen?", - "advancedDotDotDot": "Lisäasetukset...", - "advancedSettings": "Lisäasetukset", "advancedSettingsSubtitle": "Erä-, konteksti- ja näytteenottoasetukset llama.cpp-lisämoottorille", "context": "Konteksti", "synopsis": "Synopsis", @@ -2977,6 +3335,7 @@ "useOnlyExtraServerParametersHint": "Käynnistä llama-server yllä olevilla parametreilla Subtitle Editin oman viritysten sijaan - vain malli, isäntä ja portti asetetaan edelleen", "serverRunningAtX": "Palvelin käynnissä osoitteessa {0}", "customPromptHint": "Mukautetut ohjeet ({0} = lähdekieli, {1} = kohdekieli); tyhjä = sisäänrakennettu kehote", + "editTranslationHint": "Napsauta valitun rivin käännöstä muokataksesi sitä (Enter tallentaa, Escape peruuttaa)", "llamaCppDownloadEngineAndModelPrompt": "llama.cpp vaatii llama-server-moottorin ja käännösmallin lataamisen. Ladataanko nyt?", "llamaCppDownloadEnginePrompt": "llama.cpp vaatii llama-server-moottorin lataamisen. Ladataanko nyt?", "llamaCppDownloadModelPrompt": "llama.cpp vaatii valitun käännösmallin lataamisen. Ladataanko nyt?" @@ -3014,8 +3373,14 @@ "checkForUpdatesChannelStableAndBeta": "Vakaat ja beta-versiot", "assaAutoSetResolution": "ASSA: aseta resoluutio (PlayResX/PlayResY) videosta, kun video avataan", "assaAutoSetResolutionPrompt": "ASSA: kysy ennen resoluution muuttamista videon mukaiseksi (muuten skaalaa automaattisesti)", + "videoControls": "Videon ohjaimet", + "videoControlsEdit": "Muokkaa videon ohjaimia...", + "videoControlsPositionText": "Sijainti-/kestoteksti", + "videoControlsVideoFileName": "Videotiedoston nimi", + "videoControlsPlayerName": "Videosoittimen nimi", "mergeKeepEndTime": "Yhdistä rivit: säilytä loppuaika (salli päällekkäisyys seuraavan tekstityksen kanssa)", "mergeKeepEndTimeOnlyAssa": "Yhdistä rivit: säilytä loppuaika vain muodolle \"Advanced Sub Station Alpha\"", + "mergeAutoBreak": "Rivien yhdistäminen: automaattinen rivinvaihto yhdistettyyn tekstiin (pois päältä säilyttää rivien omat rivinvaihdot)", "defaultSaveLocation": "Oletustallennuspaikka", "defaultSaveLocationSourceFileFolder": "Tekstitys-/videotiedoston kansio", "defaultSaveLocationLastUsedFolder": "Viimeksi käytetty kansio", @@ -3023,6 +3388,7 @@ "defaultSaveLocationSubtitleFileFolder": "Tekstitystiedoston kansio", "defaultSaveLocationCustomFolder": "Mukautettu kansio", "subtitleGridFormattingHideTags": "Piilota tunnisteet", + "subtitleGridFormattingShowFormattingKeepTags": "Näytä muotoilu, säilytä ei-visuaaliset tunnisteet", "musicSymbolsToReplace": "Korvattavat musiikkisymbolit (pilkuilla erotettuina)", "musicSymbol": "Musiikkisymboli", "colorWordsPerMinute": "Väritä sanaa/min, jos liian suuri", @@ -3033,7 +3399,6 @@ "dialogStyleDashBothLinesWithSpace": "Viiva molemmilla riveillä välilyönnillä", "dialogStyleDashBothLinesWithoutSpace": "Viiva molemmilla riveillä ilman välilyöntiä", "continuationStyle": "Jatkomuoto", - "continuationStyleNone": "Ei mitään", "continuationStyleNoneTrailingDots": "Ei mitään, pisteet taukoille (vain lopussa)", "continuationStyleNoneLeadingTrailingDots": "Ei mitään, pisteet taukoille", "continuationStyleNoneTrailingEllipsis": "Ei mitään, ellipsis taukoja varten (vain lopussa)", @@ -3044,7 +3409,6 @@ "continuationStyleLeadingTrailingEllipsis": "Ellipsis", "continuationStyleLeadingTrailingDash": "Dash", "continuationStyleLeadingTrailingDashDots": "Dash, but dots for pauses", - "continuationStyleCustom": "Mukautettu", "cpsLineLengthStyle": "Cps/rivin pituus", "cpsLineLengthStyleCalcAll": "Laske kaikki merkit", "cpsLineLengthStyleCalcNoSpaceCpsOnly": "Laske kaikki paitsi välilyönnit, vain cps", @@ -3057,27 +3421,11 @@ "cpsLineLengthStyleCalcNoSpaceOrPunctuationCpsOnly": "Ei välilyöntejä tai välimerkkejä, vain CPS", "cpsLineLengthStyleCalcIgnoreArabicDiacritics": "Ohita arabialaiset diakriittiset merkit", "cpsLineLengthStyleCalcIgnoreArabicDiacriticsNoSpace": "Ohita arabialaiset diakriittiset merkit, ei välilyöntejä", - "timeCodeModeHhMmSsMs": "HH:MM:SS:MS", - "timeCodeModeHhMmSsFf": "HH:MM:SS:FF", - "splitBehaviorPrevious": "Lisää väli jakopisteen vasemmalle puolelle (fokus oikealla)", - "splitBehaviorHalf": "Lisää väli jakopisteen keskelle (fokus vasemmalla)", - "splitBehaviorNext": "Lisää väli jakopisteen oikealle puolelle (fokus vasemmalla)", - "subtitleListActionNothing": "Ei mitään", - "subtitleListActionVideoGoToPositionAndPause": "Siirry videon kohtaan ja keskeytä", - "subtitleListActionVideoGoToPositionAndPlay": "Siirry videon kohtaan ja toista", "subtitleListActionVideoGoToPositionAndPlayCurrentAndPause": "Siirry videon kohtaan, toista nykyinen ja keskeytä", - "subtitleListActionEditText": "Siirry tekstin muokkausruutuun", "subtitleListActionVideoGoToPositionMinus1SecAndPause": "Siirry videon kohtaan - 1 s ja keskeytä", "subtitleListActionVideoGoToPositionMinusHalfSecAndPause": "Siirry videon kohtaan - 0,5 s ja keskeytä", "subtitleListActionVideoGoToPositionMinus1SecAndPlay": "Siirry videon kohtaan - 1 s ja toista", - "subtitleListActionEditTextAndPause": "Siirry tekstin muokkauskenttään ja keskeytä videon kohdalla", - "autoBackupEveryMinute": "Joka minuutti", - "autoBackupEveryXthMinute": "Joka {0} minuutti", - "profiles": "Profiilit", - "autoBackupDeleteAfterXMonths": "Poista automaattiset varmuuskopiot {0} kuukauden kuluttua", "searchSettingsDotDotDot": "Etsi asetuksia...", - "syntaxColoring": "Syntaksin väritys", - "waveformSpectrogram": "Aaltomuoto/spektrogrammi", "network": "Verkko", "fileTypeAssociations": "Tiedostotyyppien yhdistelmät", "textBoxColorTags": "Väritunnisteet (HTML/ASSA) tekstikentässä", @@ -3110,10 +3458,9 @@ "darkThemeForegroundColor": "Tumman teeman etualaväri", "darkThemeBackgroundColor": "Tumman teeman taustaväri", "showGridLines": "Näytä ruudukon viivat", - "resetSettings": "Palauta asetukset?", - "resetSettingsDetail": "Tämä palauttaa kaikki asetukset oletusarvoihinsa.\n\nJatka?", "showHorizontalLineAboveToolbar": "Näytä vaakaviiva työkalurivin yläpuolella", "bookmarkColor": "Kirjanmerkin väri", + "spellCheckHighlightColor": "Oikeinkirjoituksen tarkistuksen korostusväri", "singleLineMaxLength": "Yhden rivin enimmäispituus", "optimalCharsPerSec": "Optimaalinen merkkimäärä sekunnissa", "maxCharsPerSec": "Merkkien enimmäismäärä sekunnissa", @@ -3125,13 +3472,21 @@ "unbreakSubtitlesShortThan": "Yhdistä alle", "newEmptyDefaultMs": "Uuden tekstityksen oletuskesto (ms)", "timeCodeUpDownStepMs": "Ajan ylös/alas-askel (ms)", + "moveSelectedLinesStepMs": "Valittujen rivien siirron pikanäppäimen askel (ms)", + "moveStartEndStepMs": "Alun/lopun siirron pikanäppäimen askel (ms)", + "moveLinesShortenNeighbor": "Rivien siirto: lyhennä edellistä/seuraavaa riviä päällekkäisyyden sijaan", "promptBeforeDelete": "Kysy ennen poistamista", "rememberPositionAndSize": "Muista ikkunan sijainti ja koko", + "titleBarFullFileName": "Näytä koko tiedostopolku otsikkorivillä", + "showRecentFiles": "Näytä viimeisimmät tiedostot (uudelleenavaamista varten)", "openLastFileOnStart": "Avaa viimeisin tiedosto käynnistyksen yhteydessä", "autoSave": "Automaattinen tallennus (tallenna avoin tiedosto muokattaessa)", "autoBackupOn": "Automaattinen varmuuskopiointi", "autoBackupIntervalMinutes": "Automaattisen varmuuskopioinnin väli (minuutteina)", "autoBackupDeleteAfterDays": "Automaattisen varmuuskopion säilytysaika (päivinä)", + "settingsBackupOn": "Varmuuskopioi asetukset automaattisesti", + "settingsBackupIntervalDays": "Asetusten varmuuskopiointiväli (päivää, 0 = joka käynnistys)", + "settingsBackupMaxCount": "Säilytettävät asetusten varmuuskopiot", "autoConvertToUtf8": "Muunna koodaus automaattisesti UTF-8:ksi avattaessa", "autoTrimWhiteSpace": "Poista välilyönnit automaattisesti", "defaultEncoding": "Oletuskoodaus", @@ -3145,23 +3500,14 @@ "colorGapTooShort": "Väri, jos väli on liian lyhyt", "errorBackgroundColor": "Virheen taustaväri", "waveformDrawGridLines": "Piirrä ruudukon viivat", - "waveformCenterVideoPosition": "Keskitä videon sijainti", + "waveformUseSkiaRenderer": "Käytä kokeellista nopeaa piirtäjää", "waveformCenterVideoPositionAlsoWhenPaused": "Keskitä videon sijainti myös tauon aikana", + "waveformSelectCurrentSubtitleWhilePaused": "Valitse nykyinen tekstitys myös tauon aikana", + "waveformRememberZoom": "Muista zoomaus (vaaka- ja pystysuunta)", "waveformShowToolbar": "Näytä työkalurivi", "waveformShowToolbarEdit": "Muokkaa työkaluriviä...", "waveformShowToolbarEditLabel": "Työkalurivin kohteet", "waveformSpectrogramCombinedWaveformHeight": "Aaltomuoto/spektrogrammi yhdistettynä, aaltomuodon korkeus %", - "showWaveformToolbarPlay": "Työkalurivi: näytä toistopainike", - "showWaveformToolbarRepeat": "Työkalurivi: näytä toistopainike", - "showWaveformToolbarRemoveBlankLines": "Työkalurivi: näytä poista tyhjät rivit -painike", - "showWaveformToolbarNew": "Työkalurivi: näytä uusi tekstitys -painike", - "showWaveformToolbarSetStart": "Työkalurivi: näytä aseta alku -painike", - "showWaveformToolbarSetEnd": "Työkalurivi: näytä lopun asettamispainike", - "showWaveformToolbarStartAndOffsetTheRest": "Työkalurivi: näytä alun asettaminen ja lopun siirtäminen -painike", - "showWaveformToolbarHorizontalZoom": "Työkalurivi: näytä vaakasuuntaisen zoomauksen liukusäädin", - "showWaveformToolbarVerticalZoom": "Työkalurivi: näytä pystysuuntaisen zoomauksen liukusäädin", - "showWaveformToolbarVideoPositionSlider": "Työkalurivi: näytä videon sijainnin liukusäädin", - "showWaveformToolbarPlaybackSpeed": "Työkalurivi: näytä toistonopeus", "waveformFocusTextboxAfterInsertNew": "Siirrä kohdistus tekstikenttään lisäyksen jälkeen", "waveformInvertMouseWheel": "Käännä hiiren rullatoiminto", "waveformMouseWheelSetsVideoPosition": "Hiiren rulla asettaa videon sijainnin", @@ -3179,19 +3525,17 @@ "waveformSelectedColor": "Aaltomuodon valitun osan väri", "downloadFfmpeg": "Lataa ffmpeg", "proxyAddress": "Proxy-osoite", - "username": "Käyttäjätunnus", "password": "Salasana", "defaultFormat": "Oletusmuoto", "defaultSaveAsFormat": "Oletusmuoto \"Tallenna nimellä\"", "favoriteSubtitleFormats": "Suosikkitekstitysmuodot", "favoriteLanguages": "Suosikkikielet", "webVttUseXTimestampMap": "WebVTT: käytä X-TIMESTAMP-MAP-arvoa (siirrä aikakoodeja ladattaessa)", - "showStopButton": "Näytä pysäytyspainike", - "showFullscreenButton": "Näytä koko näytön painike", "autoOpenVideoFile": "Avaa videotiedosto automaattisesti, kun avaat tekstityksen", "downloadMpv": "Lataa mpv", "downloadVlc": "Lataa VLC", "allowSingleLetterShortcutsInTextbox": "Salli yksikirjaimiset pikanäppäimet tekstikentässä", + "allowTextNavigationShortcutsInTextbox": "Salli pikanäppäimet tekstissä liikkumisen näppäimillä (Ctrl+Vasen/Oikea, Home/End) tekstikentässä", "spellCheckEnglishTreatInApostropheAsIng": "Oikeinkirjoituksen tarkistus: käsittele 'in''-päätteiset sanat 'ing'-muotoisina (vain englanti)", "goToLineNumberSetsVideoPosition": "Siirry rivinumeroon asettaa myös videon sijainnin", "adjustAllTimesRememberLineSelectionChoice": "Säädä kaikki ajat, muista rivivalinta", @@ -3201,6 +3545,7 @@ "showAssaLayer": "Näytä ASSA-kerrosruutu", "waveformCursorColor": "Aaltomuodon osoittimen/pään väri", "waveformShotChangeColor": "Aaltomuodon kuvanvaihtoväri", + "waveformGridColor": "Aaltomuodon ruudukon väri", "waveformParagraphLeftColor": "Aaltomuodon vasemman reunan väri", "waveformParagraphRightColor": "Aaltomuodon oikean reunan väri", "waveformFancyHighColor": "Aaltomuodon korostusväri", @@ -3228,23 +3573,26 @@ "resetSyntaxColoring": "Nollaa syntaksin väritys", "resetWaveform": "Nollaa aaltomuoto", "resetRules": "Nollaa säännöt", - "useFrameMode": "Käytä kehystilaa (hh.mm.ss.ff)", + "timeCodeMode": "Aikakooditila", + "timeCodeModeTime": "Aika (hh:mm:ss,zzz)", + "timeCodeModeFrames": "Kehykset (hh:mm:ss:ff)", + "timeCodeModeFrameNumbers": "Kehysnumerot", + "frameModeSnapToVideoFrames": "Tallenna ajat videokehyksiin (ei kehyksiin sekunnin sisällä)", "textBoxLimitNewLines": "Rajoita rivien määrää tekstityskentässä", - "mpvVideoOutput": "Videolähtö (mpv)", "mpvOpenGl": "libmpv - OpenGL", "mpvSoftwareRendering": "libmpv - Ohjelmistorenderöinti (hidas)", + "mpvMetalRendering": "libmpv - Metal (kokeellinen, vaatii uudelleenkäynnistyksen)", "mpvWidRendering": "libmpv - Natiivi Window ID -renderöinti (nopeampi Windowsissa/Linuxissa)", "waveFormsAndSpectrogramFoldersContainsX": "Kansiot \"Waveforms\" ja \"spectrogram\" sisältävät {0}", "deleteWaveformAndSpectrogramFoldersQuestion": "Poistetaanko kansiot \"Waveforms\" ja \"Spectrogram\"?", "waveformGenerateSpectrogram": "Luo spektrogrammi", "waveformAutoGenerate": "Luo aaltomuoto automaattisesti videota avattaessa", "waveformSpectrogramMode": "Spektrogrammitila", - "waveformCenterOnSingleClick": "Keskitä yhdellä napsautuksella", - "waveformSingleClickSelectsSubtitle": "Valitse tekstitys yhdellä napsautuksella", "waveformRightClickSelectsSubtitle": "Valitse tekstitys oikealla napsautuksella", - "waveformPauseOnSingleClick": "Keskeytä yhdellä napsautuksella", "waveformDrawStyle": "Aaltomuodon piirtotyyli", "vlcWidRendering": "libVLC - Natiivin ikkunan tunnuksen renderointi", + "ffmpegSoftwareRendering": "FFmpeg", + "downloadFfmpegLibs": "Lataa FFmpeg-kirjastot (FFmpeg-videosoitinta varten)", "subtitleGridEnterKeyAction": "Tekstitysruudukon Enter-näppäimen toiminto", "subtitleSingleClickAction": "Tekstitysruudukon yksittäisen napsautuksen toiminto", "subtitleDoubleClickAction": "Tekstitysruudukon kaksoisnapsautustoiminto", @@ -3268,23 +3616,24 @@ "saveAsBehaviorUseVideoFileName": "Käytä videotiedoston nimeä", "saveAsBehaviorUseSubtitleFileName": "Käytä tekstitystiedoston nimeä", "saveAsAppendLanguageCodeTwoLetter": "Kaksikirjaiminen", - "saveAsAppendLanguageCodeThreeLetter": "Kolmikirjaiminen", "saveAsAppendLanguageCodeLanguageName": "Kielinimi", "splitOddLineActionWeightTop": "Painota yläosaa", "splitOddLineActionWeightBottom": "Painota alaosaa", "splitOddLinesAction": "Parittomien rivien jakotoiminto", "ocrUseWordSplitList": "OCR: käytä sananjakolistaa", "ocrGuessUnknownWords": "OCR: yritä arvata tuntemattomat sanat", + "spellCheckRememberUseAlwaysList": "Oikeinkirjoituksen tarkistus: muista \"Käytä aina\" -luettelo", + "fixShortDisplayTimesAllowMoveStartTime": "Korjaa yleiset virheet: lyhyiden näyttöaikojen korjaus voi siirtää alkuaikaa", "speechToTextSelectedLinesPromptFirstTimeOnly": "Puhe tekstiksi: valitut rivit, kysy kieltä/moottoria vain ensimmäisellä kerralla", "multipleReplaceShowDotDotDotButtons": "Useita korvauksia: näytä kontekstivalikon painikkeet", "gridFocusTextboxAfterInsertNew": "Ruudukko: siirrä kohdistus tekstikenttään uuden tekstityksen lisäämisen jälkeen", + "undoRedoGoToChangedLine": "Kumoa/tee uudelleen: siirry muutettuun riviin (ja videon kohtaan)", "textToSpeechPromptSkipNoiseLines": "Teksti puheeksi: kysy ääni-/musiikkirivien ohittamisesta", "textToSpeechPromptDetectSpeakers": "Teksti puheeksi: kysy puhujien nimien tunnistamisesta tekstistä", "useFocusedButtonBackgroundColor": "Käytä kohdistetun painikkeen taustaväriä", "focusedButtonBackgroundColor": "Valitun painikkeen taustaväri", "forceCrLfOnSave": "Pakota CR+LF tallennettaessa (tekstitystiedostot)", - "showWaveformToolbarPlayNext": "Työkalurivi: näytä seuraava-painike", - "showWaveformToolbarPlaySelection": "Työkalurivi: näytä toista valinta -painike", + "linuxClipboardUseExternalTool": "Kopioi teksti xclip/wl-copy-työkalulla (säilyttää aksenttimerkit)", "showFormatLimitWarning": "Varoita tallennettaessa, kun rivit ylittävät muodon rajat (esim. SCC 32 merkkiä/rivi)", "textBoxButtonShowAutoBreak": "Tekstiruutu: näytä automaattinen rivinvaihto -painike", "textBoxButtonShowUnbreak": "Tekstiruutu: näytä poista rivinvaihto -painike", @@ -3296,12 +3645,18 @@ "waveformDoubleClickAction": "Aaltomuodon kaksoisnapsautustoiminto (yksittäisen napsautuksen jälkeen)", "allSettings": "Kaikki asetukset", "uiScale": "Käyttöliittymän mittakaava (%)", + "fontScale": "Fontin skaalaus (%)", + "textSelectionOpacity": "Tekstivalinnan peittävyys (%)", "waveformToolbarItems": "Aaltomuodon työkalurivin kohteet", "matchIconColorToDarkTheme": "Sovita kuvakkeen väri tumman teeman etualaväriin", "subtitlePreviewProperties": "Tekstityksen esikatseluominaisuudet", "usePositionFromSubtitleFile": "Käytä sijaintia tekstitystiedostosta (TTML/PAC/EBU STL)", "marginIsPartOfSubtitleArea": "Marginaali on osa tekstitysaluetta", "textJustify": "Tasaa rivit", + "deinterlace": "Lomituksen poisto videosta", + "deinterlaceOff": "Pois", + "deinterlaceAuto": "Automaattinen (vain lomitetut kehykset)", + "deinterlaceAlways": "Aina", "pixelWidthInfo": "Vihreät viivat = enimmäisleveysraja | Punainen alue = teksti ylittää rajan", "spellCheckEngineHunSpelll": "Hunspell", "spellCheckEngineMsWord": "MS Word", @@ -3311,130 +3666,104 @@ "waveformSaveAsCustomTheme": "Tallenna mukautettuna teemana...", "waveformLoadThemeDotDotDot": "Lataa teema...", "waveformExportThemeDotDotDot": "Vie teema...", + "waveformSaveCustomThemeTitle": "Tallenna mukautettu teema", + "waveformThemeFileType": "Subtitle Editin aaltomuototeema", + "waveformThemeHighContrast": "Suuri kontrasti", + "waveformThemeOceanBlue": "Merensininen", + "waveformThemeWarmSunset": "Lämmin auringonlasku", + "waveformThemeForest": "Metsä", + "waveformThemeMidnightPurple": "Keskiyön violetti", + "waveformThemeRetroAmber": "Retro-meripihka", + "waveformThemeIce": "Jää", "minGapFrames": "Vähimmäisaukko (kehystä)", "waveformSnapToFrames": "Kiinnitä kehyksiin", "fixCommonErrorsSkipStep1": "Korjaa yleiset virheet: ohita vaihe 1 (valitse korjaukset)", "showPluginsMenu": "Näytä Lisäosat-valikko", "fullscreenHideControls": "Piilota videon ohjaimet koko näytön tilassa", + "showSecondarySubtitleDialog": "Näytä tyyli-ikkuna toissijaista tekstitystiedostoa avattaessa", + "rememberSecondarySubtitleFile": "Muista toissijainen tekstitystiedosto", "showToolsLogFile": "Näytä työkalujen lokitiedosto", "writeToolsLog": "Kirjoita työkalujen loki", "waveformExtractAudioFormat": "Poimi äänimuoto", "waveformExtractAudioSampleRate": "Poimi äänen näytteenottotaajuus", "waveformExtractAudioSampleRateOriginal": "Alkuperäinen (säilytä lähde)", "waveformExtractAudioBitRate": "Poimi äänen bittinopeus (MP3/M4A)", + "ffmpegUseCenterChannelOnly": "Käytä vain etukeskikanavaa 5.1-äänelle (FFmpeg)", "subtitleGridCenterSelectedRow": "Tekstitysruudukko, keskitä edellistä/seuraavaa riviä valittaessa", "waveformSetVideoPositionOnMoveStartEnd": "Aseta videon sijainti siirrettäessä alkua/loppua", - "saveAsAppendLanguageCodeThreeLetterBibliographic": "Kolmikirjaiminen (ISO 639-2/B)", "textToSpeechPromptMergeContinuationLines": "Puhe tekstiksi: kysy jatkorivien yhdistämistä" }, "shortcuts": { "generalUnbreakNoSpaceCjk": "Poista rivinvaihto ilman välilyöntiä (CJK)", - "title": "Pikakuvakkeet", + "generalToggleBreak": "Vaihda rivinvaihto päälle/pois", "searchShortcuts": "Hae pikakuvakkeita...", - "filter": "Suodata", - "categoryGeneral": "Yleistä", "categorySubtitleGridAndTextBox": "Alatekstiluettelonäkymä ja tekstikenttä", "categorySubtitleGrid": "Alatekstiluettelonäkymä", - "categoryWaveform": "Aaltomuoto", "categoryTextBox": "Tekstikenttä", "categoryFile": "Tiedosto", "categoryVideo": "Video", "categorySync": "Synkronointi", - "categoryTranslate": "Käännä", - "categorySearch": "Haku", - "categoryTools": "Työkalut", "categoryAi": "Tekoäly", + "categoryCustom": "Mukautettu", + "editCustomShortcut": "Muokkaa mukautettua pikanäppäintä", + "customShortcutTextBoxKeyHint": "Käytä tässä näppäintä yhdessä Ctrl/Alt-näppäimen kanssa - pelkällä näppäimellä ei enää voisi kirjoittaa tekstikenttään.", + "customShortcutNumberX": "Mukautettu pikanäppäin #{0}", + "customShortcutNumberXY": "Mukautettu pikanäppäin #{0}: {1}", + "customShortcutStep": "Vaihe", + "customShortcutSteps": "Vaiheet", + "customShortcutStepRunCommand": "Suorita komento", + "customShortcutStepInsertText": "Lisää teksti", + "customShortcutStepReplace": "Etsi ja korvaa", + "customShortcutInsertAtCursor": "Kohdistimen kohdalle", + "customShortcutInsertAtStart": "Tekstin alkuun", + "customShortcutInsertAtEnd": "Tekstin loppuun", + "customShortcutSummaryRunCommandX": "Suorita: {0}", + "customShortcutSummaryInsertXY": "Lisää \"{0}\" ({1})", + "customShortcutSummaryReplaceXY": "Korvaa \"{0}\" tekstillä \"{1}\"", + "customShortcutSummaryReplaceRegexXY": "Korvaa säännöllinen lauseke \"{0}\" tekstillä \"{1}\"", + "customShortcutPickCommand": "Valitse suoritettava komento.", + "customShortcutEnterText": "Syötä lisättävä teksti.", + "customShortcutEnterFind": "Syötä etsittävä teksti.", + "customShortcutInvalidRegexX": "Virheellinen säännöllinen lauseke: {0}", + "customShortcutStepsHint": "Vaiheet suoritetaan ylhäältä alas. Tekstivaiheet koskevat valittuja rivejä; ikkunan avaavat komennot jatkuvat, kun ikkuna suljetaan.", "activeIn": "Aktiivinen kohteessa", "activeInEverywhere": "Kaikkialla", - "generalSetupLikeSe4": "Määritä kuten Subtitle Edit 4 (teema, pikanäppäimet, korvaussäännöt)", - "generalMergeSelectedLines": "Yhdistä valitut rivit", - "generalMergeWithPrevious": "Yhdistä edelliseen", - "generalMergeWithNext": "Yhdistä seuraavaan", - "generalMergeWithPreviousAndUnbreak": "Yhdistä edelliseen ja poista rivinvaihto", - "generalMergeWithNextAndUnbreak": "Yhdistä seuraavaan ja poista rivinvaihto", - "generalMergeWithPreviousAndAutoBreak": "Yhdistä edelliseen ja tee automaattinen rivinvaihto", - "generalMergeWithNextAndAutoBreak": "Yhdistä seuraavaan ja tee automaattinen rivinvaihto", - "generalMergeSelectedLinesAndAutoBreak": "Yhdistä valitut rivit ja katkaise automaattisesti", "generalMergeSelectedLinesAndUnbreak": "Yhdistä valitut rivit ja poista katkaisut", "generalMergeSelectedLinesAndUnbreakCjk": "Yhdistä valitut rivit ja poista katkaisut CJK", - "generalMergeSelectedLinesOnlyFirstText": "Yhdistä valitut rivit vain ensimmäinen teksti", "generalMergeSelectedLinesBilingual": "Yhdistä valitut rivit kaksikielisenä", - "generalMergeWithPreviousBilingual": "Yhdistä edelliseen kaksikieliseen", - "generalMergeWithNextBilingual": "Yhdistä seuraavaan kaksikieliseen", - "generalMergeOriginalAndTranslation": "Yhdistä alkuperäinen ja käännös", "generalToggleTranslationMode": "Vaihda käännöstilaa", - "generalSwitchOriginalAndTranslation": "Vaihda alkuperäinen ja käännös", - "generalSwitchOriginalAndTranslationTextBoxes": "Vaihda alkuperäisen ja käännöksen tekstikentät", "generalToggleTranslationAndOriginalInPreviews": "Vaihda käännöksen ja alkuperäisen välillä video-/ääniesikatselussa", "generalChooseLayout": "Valitse asettelu", - "generalLayoutChooseX": "Asettelu {0}", - "generalPlayFirstSelected": "Toista ensimmäinen valittu", - "generalGoToFirstSelectedLine": "Siirry ensimmäiseen valittuun riviin", - "generalGoToNextEmptyLine": "Siirry seuraavaan tyhjään riviin", "generalGoToNextSubtitle": "Siirry seuraavaan tekstitykseen", "generalGoToNextSubtitlePlayTranslate": "Siirry seuraavaan tekstitykseen (toista käännös)", "generalGoToNextSubtitleCursorAtEnd": "Siirry seuraavaan tekstitykseen (kohdistin lopussa)", "generalGoToPrevSubtitle": "Siirry edelliseen tekstitykseen", "generalGoToPrevSubtitlePlayTranslate": "Siirry edelliseen tekstitykseen (toista käännös)", - "generalGoToStartOfCurrentSubtitle": "Siirry nykyisen tekstityksen alkuun", - "generalGoToEndOfCurrentSubtitle": "Siirry nykyisen tekstityksen loppuun", - "generalGoToPreviousSubtitleAndFocusVideo": "Siirry edelliseen tekstitykseen ja keskitä video", - "generalGoToNextSubtitleAndFocusVideo": "Siirry seuraavaan tekstitykseen ja keskitä video", - "generalGoToPrevSubtitleAndPlay": "Siirry edelliseen tekstitykseen ja toista", - "generalGoToNextSubtitleAndPlay": "Siirry seuraavaan tekstitykseen ja toista", - "generalGoToPreviousSubtitleAndFocusWaveform": "Siirry edelliseen tekstitykseen ja kohdista aaltomuoto", - "generalGoToNextSubtitleAndFocusWaveform": "Siirry seuraavaan tekstitykseen ja kohdista aaltomuoto", - "generalGoToLineNumber": "Siirry rivinumeroon", "generalGoToFirstLine": "Siirry ensimmäiselle riville", "generalGoToLastLine": "Siirry viimeiselle riville", "alsoSetVideoPosition": "Aseta myös videon sijainti", "generalGoToVideoPosition": "Siirry videon kohtaan", "generalToggleItalic": "Vaihda kursivointi", "generalToggleBold": "Vaihda lihavointi", - "generalToggleBookmarks": "Vaihda kirjanmerkit", - "generalFocusTextBox": "Siirrä kohdistus tekstikenttään", - "generalToggleBookmarksWithText": "Vaihda kirjanmerkkejä tekstillä", - "generalEditBookmarks": "Muokkaa kirjanmerkkejä", - "fileNew": "Uusi", "generalToggleUnderline": "Alleviivaus päälle/pois", + "generalToggleBox": "Vaihda laatikko päälle/pois (EBU STL)", "fileOpen": "Avaa", "fileOpenKeepVideo": "Avaa (säilytä video)", "fileSave": "Tallenna", "fileSaveAs": "Tallenna nimellä", - "fileSaveAll": "Tallenna kaikki", "fileSaveOriginal": "Tallenna alkuperäinen", - "fileSaveOriginalAs": "Tallenna alkuperäinen nimellä", - "fileOpenOriginalSubtitle": "Avaa alkuperäinen tekstitys", - "fileCloseOriginalSubtitle": "Sulje alkuperäinen tekstitys", - "fileTranslatedSubtitle": "Käännetty tekstitys", "fileCompare": "Vertaa", "fileStatistics": "Tilastot", "fileImportPlainText": "Tuo pelkkää tekstiä", - "fileImportBluRaySupForOcr": "Tuo Blu-ray SUP OCR:ää varten", - "fileImportBluRaySupForEdit": "Tuo Blu-ray SUP muokkausta varten", - "fileImportTimeCodes": "Tuo aikakoodit", "fileExportEbuStl": "Vie EBU STL", "fileExportPac": "Vie PAC", - "fileExportEdlClipName": "Vie EDL-leikkeen nimi", - "fileExportPlainText": "Vie pelkkä teksti", - "fileExportCustomTextFormat1": "Vie mukautettu tekstimuoto 1", - "fileExportCustomTextFormat2": "Vie mukautettu tekstimuoto 2", - "fileExportCustomTextFormat3": "Vie mukautettu tekstimuoto 3", "fileExit": "Poistu", "openSeDataFolder": "Avaa Subtitle Edit -kansio", - "editFind": "Etsi", - "editFindNext": "Etsi seuraava", "editFindPrevious": "Etsi edellinen", - "editReplace": "Korvaa", - "editMultipleReplace": "Korvaa useita", - "editModifySelection": "Muokkaa valintaa", - "listSelectAll": "Valitse kaikki", - "listSelectFirst": "Valitse ensimmäinen", - "listSelectLast": "Valitse viimeinen", "listInverseSelection": "Käännä valinta", "listDeleteSelection": "Poista valinta", + "deleteSelectionEverywhere": "Poista valinta (kaikkialla)", "rippleDeleteSelection": "Poista valinta aaltoilevasti", - "settings": "Asetukset", "assigned": "Määritetty", "unassigned": "Määrittämätön", "pressedKeyX": "Painettu näppäin: {0}", @@ -3442,6 +3771,7 @@ "detectKey": "Tunnista näppäin", "control": "Control", "alt": "Alt", + "altGr": "AltGr", "win": "Win", "shift": "Shift", "controlMac": "⌃", @@ -3452,11 +3782,14 @@ "resetShortcutsDetail": "Haluatko nollata kaikki pikanäppäimet oletusarvoihin?", "togglePlayPause": "Vaihda toisto/tauko", "toggleLockTimeCodes": "Vaihda aikakoodien lukitus", + "toggleTimeCodeMode": "Vaihda aikakooditilaa (aika/kehykset/kehysnumerot)", + "toggleScreenPrivacy": "Vaihda näytön yksityisyystilaa (piilota tiedostonimet, sitten myös tekstit)", + "screenPrivacyFileNamesHidden": "Näytön yksityisyys: tiedostonimet piilotettu", + "screenPrivacyFileNamesAndTextsHidden": "Näytön yksityisyys: tiedostonimet ja tekstit piilotettu", + "screenPrivacyOff": "Näytön yksityisyys: pois", "duplicateSelectedLines": "Kopioi valitut rivit", "sourceView": "Lähdekoodinäkymä", - "showAlignmentPicker": "Kohdistus", "addOrEditBookmark": "Lisää tai muokkaa kirjanmerkkiä", - "listBookmarks": "Luettelo kirjanmerkeistä", "toggleBookmark": "Vaihda kirjanmerkki (valitut rivit, ei tekstiä)", "toggleForcedSelectedLines": "Pakotettu päälle/pois (valitut rivit)", "goToNextBookmark": "Siirry seuraavaan kirjanmerkkiin", @@ -3467,7 +3800,6 @@ "waveformSetStartAndSetEndOfPreviousMinusGap": "Aseta edellisen välin alku ja loppu miinusväli", "waveformSetEndAndStartOfNextAfterGap": "Aseta seuraavan välin loppu ja alku plusväli", "waveformSetEndAndStartOfNextAfterGapAndGoToNext": "Aseta seuraavan plus-aukon loppu ja alku ja siirry seuraavaan", - "fetchFirstWordFromNextSubtitle": "Hae seuraavan tekstityksen ensimmäinen sana", "moveLastWordToNextSubtitle": "Siirrä viimeinen sana seuraavaan tekstitykseen", "breakAtFirstSpaceFromCursor": "Katkaise ensimmäisestä välilyönnistä kohdistimen kohdalta", "breakAtFirstSpaceFromCursorAndGoToNext": "Katkaise ensimmäisestä välilyönnistä kohdistimen kohdalta ja siirry seuraavaan", @@ -3479,7 +3811,6 @@ "videoGoToPreviousTimeCode": "Siirry edelliseen aikakoodiin", "videoGoToNextTimeCode": "Siirry seuraavaan aikakoodiin", "goToNextLineAndSetVideoPosition": "Siirry seuraavaan tekstitykseen ja aseta videon sijainti", - "textBoxDeleteSelectionNoClipboard": "Tekstiruutu: Poista valinta (ei leikepöytää)", "textBoxDeleteForward": "Tekstikenttä: Poista valinta tai seuraava merkki (eteenpäin poisto)", "textBoxCut": "Tekstiruutu: Leikkaa", "textBoxCut2": "Tekstiruutu: Leikkaa (vaihtoehto)", @@ -3498,6 +3829,7 @@ "listViewColumnTextUp": "Sarake, teksti ylös", "listViewColumnTextDown": "Sarake, teksti alas", "autoTranslateSelectedLines": "Käännä valitut rivit automaattisesti...", + "autoTranslateSelectedLinesNoPrompt": "Käännä valitut rivit automaattisesti (ei kyselyä, käytä viimeisintä moottoria/kieliä)", "setAssaResolution": "Aseta ASSA-tarkkuus (PlayResX/PlayResY)", "setShortcutForX": "Aseta pikanäppäin kohteelle \"{0}\"", "commandFileNewKeepVideo": "Uusi (säilytä video)", @@ -3505,27 +3837,20 @@ "fileCloseOriginal": "Sulje alkuperäinen", "fileCloseTranslation": "Sulje käännös", "restoreAutoBackup": "Palauta automaattinen varmuuskopio", - "openContainingFolder": "Avaa sisältävä kansio", "importTimeCodes": "Tuo aikakoodit", "importSubtitleWithManuallyChosenEncoding": "Tuo tekstitys manuaalisesti valitulla koodauksella", "exportBluRaySup": "Vie Blu-ray SUP", "exportCustomTextFormat": "Vie mukautettu tekstimuoto", "exportPlainText": "Vie pelkkä teksti", - "showHistory": "Näytä historia", "toggleRightToLeft": "Vaihda oikealta vasemmalle", "modifySelection": "Muokkaa valintaa", "adjustDurations": "Säädä kestoja", "applyDurationLimits": "Käytä kestoja koskevia rajoituksia", "batchConvert": "Joukkokonvertoi", - "bridgeGaps": "Yhdistä aukot", "applyMinGap": "Käytä vähimmäisaukkoa", - "fixCommonErrors": "Korjaa yleiset virheet", "makeEmptyTranslationFromCurrentSubtitle": "Luo tyhjä käännös nykyisestä tekstityksestä", - "mergeLinesWithSameText": "Yhdistä rivit, joissa on sama teksti", - "mergeLinesWithSameTimeCodes": "Yhdistä rivit, joissa on samat aikakoodit", "splitBreakLongLines": "Jaa/tasapainota pitkät rivit", "mergeShortLines": "Yhdistä lyhyet rivit", - "removeTextForHearingImpaired": "Poista teksti kuulovammaisille", "joinSubtitles": "Yhdistä tekstitykset", "splitSubtitle": "Jaa tekstitys", "spellCheck": "Oikeinkirjoituksen tarkistus", @@ -3547,7 +3872,6 @@ "adjustAllTimes": "Säädä kaikkia aikoja", "visualSync": "Visuaalinen synkronointi", "translateViaCopyPaste": "Käännä kopioimalla ja liittämällä", - "shortcuts": "Pikanäppäimet", "wordLists": "Sanaluettelot", "chooseUiLanguage": "Valitse käyttöliittymän kieli", "chooseRuleProfile": "Valitse sääntöprofiili", @@ -3558,6 +3882,7 @@ "textBoxItalic": "Tekstiruutu kursivoitu", "textBoxBold": "Tekstikenttä: lihavointi", "textBoxUnderline": "Tekstikenttä: alleviivaus", + "textBoxBox": "Tekstikenttä: laatikko (EBU STL)", "resetWaveformZoomAndSpeed": "Nollaa aaltomuodon zoomaus ja toistonopeus (toistonopeus)", "togglePlaybackSpeed": "Vaihda toistonopeutta (toistonopeus)", "playbackSpeedSlower": "Toistonopeus hitaampi (toistonopeus)", @@ -3567,22 +3892,28 @@ "seekSilence": "Etsi hiljaisuus", "waveformGuessStart": "Arvaa alkuaika aaltomuodosta", "waveformGuessEnd": "Arvaa loppuaika aaltomuodosta", + "waveformGuessStartAndEnd": "Arvaa alku- ja loppuaika aaltomuodosta", "setVideoPositionCurrentSubtitleStart": "Aseta videon sijainti nykyisen rivin alkuun", "goToSubtitlePositionAndPause": "Siirry tekstityksen kohtaan ja tauota", "setVideoPositionCurrentSubtitleEnd": "Aseta videon sijainti nykyisen rivin loppuun", "toggleAudioTracks": "Vaihda ääniraitoja", - "listErrors": "Luettele virheet", "goToNextError": "Siirry seuraavaan virheeseen", "goToPreviousError": "Siirry edelliseen virheeseen", "addNameToNameList": "Lisää nimi nimiluetteloon", "findDoubleWords": "Etsi kaksoissanat", "findDoubleLines": "Etsi kaksoisrivit", "colorX": "Väri {0}", - "removeColor": "Poista väri", "surroundWith": "Ympäröi...", "surroundWithXY": "Ympäröi {0}/{1}:llä", - "repeatLine": "Toista rivi", "surroundWithNumberX": "Ympäröi merkinnällä #{0}", + "surroundWithBehavior": "Toiminta", + "surroundWithBehaviorToggle": "Vaihda (lisää tai poista, jos olemassa)", + "surroundWithBehaviorAdd": "Lisää (joka kerta)", + "surroundWithBehaviorRemove": "Poista (kaikki)", + "surroundWithBehaviorRemoveOnce": "Poista (yksi kerrallaan)", + "surroundWithWorksOn": "Kohdistuu", + "surroundWithScopeSelectionOrText": "Valintaan, muuten koko tekstiin", + "surroundWithScopeEachLine": "Jokaiseen riviin", "searchVia": "Hae palvelulla", "searchViaX": "Hae palvelulla {0}", "searchViaNumberX": "Hae palvelulla #{0}", @@ -3607,6 +3938,11 @@ "copySubtitlePathToClipboard": "Kopioi tekstityksen polku leikepöydälle", "copySubtitleOriginalPathToClipboard": "Kopioi alkuperäisen tekstityksen polku leikepöydälle", "focusTextBox": "Kohdista tekstikenttä", + "focusSubtitleListView": "Kohdista tekstitysluettelonäkymään", + "focusWaveform": "Kohdista aaltomuotoon", + "focusOriginalTextBox": "Kohdista alkuperäisen tekstikenttään", + "textBoxGoToStart": "Tekstikenttä, siirry alkuun", + "textBoxGoToEnd": "Tekstikenttä, siirry loppuun", "sortByNumber": "Lajittele numeron mukaan", "sortByStartTime": "Lajittele \"Näytä\"-ajan mukaan", "sortByEndTime": "Lajittele \"Piilota\"-ajan mukaan", @@ -3631,7 +3967,6 @@ "assaStyles": "ASSA Tyylit", "assaProperties": "ASSA Ominaisuudet", "assaAttachments": "ASSA Liitteet", - "assaVideoColorPicker": "ASSA-videon värivalitsin", "recalculateDurationSelectedLines": "Laske kesto uudelleen (valitut rivit)", "toggleWaveformAndSpectrogramHeight": "Vaihda aaltomuodon/spektrogrammin jaetun korkeuden näkymää", "toggleSpectrogramStyle": "Vaihda spektrogrammin tyyliä", @@ -3643,6 +3978,7 @@ "moveFirstWordFromNextLineUpCurrentSubtitle": "Siirrä seuraavan rivin ensimmäinen sana ylös (nykyinen tekstitys)", "moveTextFromCursorToNextAndGoToNext": "Siirrä kohdistimen jälkeinen teksti seuraavaan tekstitykseen ja siirry seuraavaan", "moveTextFromCursorToNextAndGoToNextAndPlay": "Siirrä kohdistimen jälkeinen teksti seuraavaan tekstitykseen, siirry seuraavaan ja toista", + "moveTextFromCursorToNextAndGoToNextAndPlayAndPause": "Siirrä kohdistimen jälkeinen teksti seuraavaan tekstitykseen, siirry seuraavaan, toista se ja pysäytä sen lopussa", "toggleFocusGridAndWaveform": "Vaihda kohdistus tekstitysruudukon ja aaltomuodon/spektrogrammin välillä", "mergeContinuationLines": "Yhdistä jatkorivit", "snapAllTimesToFrames": "Kiinnitä kaikki ajat kehyksiin", @@ -3658,9 +3994,9 @@ "selectionToSentenceCase": "Tekstikenttä: Valinta lausekirjoitusasuun", "selectionToggleCasing": "Tekstikenttä: Vaihda kirjainkokoa (ISOT -> pienet -> Otsikkotyyli)", "googleIt": "Hae Googlesta (valittu teksti)", - "setActorXY": "Aseta näyttelijä {0}: {1}", + "setActorX": "Aseta näyttelijä: {0}", "setNewActor": "Aseta uusi näyttelijä...", - "setActorSlotXTitle": "Näyttelijäpaikka {0}", + "setStyleX": "Aseta tyyli: {0}", "toggleDialogDashes": "Vaihda dialogiviivat", "mergeWithLineBeforeAsDialog": "Yhdistä edelliseen riviin dialogina", "mergeWithLineAfterAsDialog": "Yhdistä seuraavaan riviin dialogina", @@ -3671,13 +4007,27 @@ "moveStartOneFrameBackKeepGapPrev": "Siirrä alkua yksi kehys taaksepäin (säilytä aukko edelliseen, jos lähellä)", "moveStartOneFrameForwardKeepGapPrev": "Siirrä alkua yksi kehys eteenpäin (säilytä aukko edelliseen, jos lähellä)", "moveEndOneFrameBackKeepGapNext": "Siirrä loppua yksi kehys taaksepäin (säilytä aukko seuraavaan, jos lähellä)", - "moveEndOneFrameForwardKeepGapNext": "Siirrä loppua yksi kehys eteenpäin (säilytä aukko seuraavaan, jos lähellä)" + "moveEndOneFrameForwardKeepGapNext": "Siirrä loppua yksi kehys eteenpäin (säilytä aukko seuraavaan, jos lähellä)", + "moveStartXMsBack": "Siirrä alkua X ms taaksepäin (X asetetaan asetuksissa, myös visuaalisessa synkronoinnissa)", + "moveStartXMsForward": "Siirrä alkua X ms eteenpäin (X asetetaan asetuksissa, myös visuaalisessa synkronoinnissa)", + "moveEndXMsBack": "Siirrä loppua X ms taaksepäin (X asetetaan asetuksissa, myös visuaalisessa synkronoinnissa)", + "moveEndXMsForward": "Siirrä loppua X ms eteenpäin (X asetetaan asetuksissa, myös visuaalisessa synkronoinnissa)", + "moveSelectedLinesXMsBack": "Siirrä valittuja rivejä X ms taaksepäin (X asetetaan asetuksissa)", + "moveSelectedLinesXMsForward": "Siirrä valittuja rivejä X ms eteenpäin (X asetetaan asetuksissa)", + "moveSelectedLinesAndForwardXMsBack": "Siirrä valittuja ja kaikkia seuraavia rivejä X ms taaksepäin (X asetetaan asetuksissa)", + "moveSelectedLinesAndForwardXMsForward": "Siirrä valittuja ja kaikkia seuraavia rivejä X ms eteenpäin (X asetetaan asetuksissa)", + "moveAllLinesXMsBack": "Siirrä kaikkia rivejä X ms taaksepäin (X asetetaan asetuksissa)", + "moveAllLinesXMsForward": "Siirrä kaikkia rivejä X ms eteenpäin (X asetetaan asetuksissa)", + "moveSelectedLinesCustomMsBack": "Siirrä valittuja rivejä, mukautetut millisekunnit ({0:#,###,##0}) taaksepäin, {1}", + "moveSelectedLinesCustomMsForward": "Siirrä valittuja rivejä, mukautetut millisekunnit ({0:#,###,##0}) eteenpäin, {1}", + "moveSelectedLinesAndForwardCustomMsBack": "Siirrä valittuja ja kaikkia seuraavia rivejä, mukautetut millisekunnit ({0:#,###,##0}) taaksepäin, {1}", + "moveSelectedLinesAndForwardCustomMsForward": "Siirrä valittuja ja kaikkia seuraavia rivejä, mukautetut millisekunnit ({0:#,###,##0}) eteenpäin, {1}", + "moveAllLinesCustomMsBack": "Siirrä kaikkia rivejä, mukautetut millisekunnit ({0:#,###,##0}) taaksepäin, {1}", + "moveAllLinesCustomMsForward": "Siirrä kaikkia rivejä, mukautetut millisekunnit ({0:#,###,##0}) eteenpäin, {1}" }, "wordLists": { "title": "Sanaluettelot", "addName": "Lisää nimi", - "addWord": "Lisää sana", - "addPair": "Lisää pari", "nameAndIgnoreList": "Nimi-/ohituslista", "userWords": "Käyttäjän sanaluettelo", "ocrFixList": "OCR-korjauslista", @@ -3730,6 +4080,8 @@ "nOcrDatabase": "nOCR-tietokanta", "drawMode": "Piirtotila:", "addNewCharcter": "Lisää uusi merkki", + "addNOcrCharacterTitle": "Lisää nOCR-merkki riville {0}, merkki {1}/{2}, tietokanta \"{3}\"", + "addBinaryOcrCharacterTitle": "Lisää binääri-OCR-merkki riville {0}, merkki {1}/{2}, tietokanta \"{3}\"", "lineIndexX": "Rivi {0}", "inspectNOcrAdditions": "Tarkista uudet nOCR-lisäykset", "ocrSelectedLines": "OCR-valitut rivit", @@ -3777,6 +4129,8 @@ "binaryImageCompareInspectImageMatches": "\"Binäärikuvien vertailu\" - Tarkista kuvan vastaavuudet", "saveBlankTextTitle": "Tallenna tyhjä teksti?", "saveBlankTextPrompt": "Tallenna tyhjä teksti kuvalle?", + "drawnLinesDoNotMatchTitle": "Viivat eivät vastaa kuvaa", + "drawnLinesDoNotMatchPrompt": "Osa piirretyistä viivoista ei vastaa kuvaa (vihreiden viivojen on oltava merkin päällä, punaisten sen ulkopuolella), joten tätä merkkiä ei tunnisteta.\n\nTallennetaanko silti?", "yesAndNeverAskAgain": "Kyllä, älä kysy enää", "importTextFromSubtitleDotDotDot": "Tuo teksti tekstityksestä...", "importTextFromSubtitleOverwritePrompt": "Joissakin riveissä on jo OCR-tekstiä. Korvataanko olemassa oleva teksti?", @@ -3803,7 +4157,6 @@ "vobSubColorsTitle": "VobSub/DVD-värit", "vobSubColorsHeader": "Mukauta neljää VobSub-väriä", "vobSubColorsDescription": "VobSub/DVD-tekstitykset käyttävät neljää indeksoitua väriä: tausta, kuvio, korostus 1 ja korostus 2. Valitse kukin väri alta ohittaaksesi upotetun paletin - hyödyllistä, kun alkuperäiset värit antavat huonon OCR-kontrastin.", - "vobSubColorBackground": "Tausta", "vobSubColorPattern": "Kuvio", "vobSubColorEmphasis1": "Korostus 1", "vobSubColorEmphasis2": "Korostus 2", @@ -3837,9 +4190,26 @@ "crispEmbedSettingsTitle": "CrispEmbed-asetukset", "crispEmbedDescription": "Paikallinen OCR-moottori usealla mallitaustaohjelmalla - lataa moottori ja mallit täältä.", "trainNOcrDatabase": "Kouluta nOCR-tietokanta...", + "optimizeNOcrDatabase": "Optimoi nOCR-tietokanta...", + "optimizeNOcrDatabaseQuestionXY": "Poistetaanko nOCR-tietokannasta \"{0}\" viivat, jotka eivät erota merkkejä samankaltaisista merkeistä?\n\nTämä pienentää tietokantaa ja nopeuttaa OCR:ää, yleensä vähemmillä virheillä. Kestää enintään minuutin.\n\nVarmuuskopio tallennetaan nimellä \"{1}\".", + "optimizingNOcrDatabaseXY": "Optimoidaan nOCR-tietokantaa... {0}/{1}", + "nOcrDatabaseOptimizedXYZ": "nOCR-tietokanta \"{0}\" optimoitu: {1:#,##0} -> {2:#,##0} viivaa.", "startTraining": "Aloita koulutus", "abortTraining": "Keskeytä koulutus", "trainingOptions": "Koulutusasetukset", + "trainNOcrSubtitle": "Piirtää merkit asennetuilla fonteilla ja opettaa nOCR:lle niiden muodot. Valitse tekstityksissäsi käytetyt fontit.", + "xFontsSelected": "{0:#,##0} valittu", + "subtitleFonts": "Tekstityksen fontit", + "emptyDatabase": "Tyhjä tietokanta", + "startFromDatabase": "Aloita kohteesta", + "startFromDatabaseHint": "Lisää opetetut merkit olemassa olevan tietokannan kopioon (suositeltu) tai aloita tyhjästä", + "fontSizes": "Fonttikoot", + "fontSizesHint": "Piirrettävät fonttikoot pikseleinä pilkuilla erotettuina, esim. \"30, 40\"", + "numberOfLineSegmentsHint": "Tarkistettavat viivat merkkiä kohden - useampi on tiukempi mutta hitaampi", + "enterDatabaseName": "Anna tietokannalle nimi", + "trainingFontXSizeYCharacterZ": "Opetetaan \"{0}\" koossa {1} px: {2}", + "xLearned": "{0:#,##0} opittu", + "xSkipped": "{0:#,##0} ohitettu", "charactersToTrain": "Koulutettavat merkit", "letterCombinationsToTrain": "Kirjainyhdistelmät, jotka saattavat jakautua yhdeksi kuvaksi", "importCharactersFromSubtitleFile": "Tuo merkit tekstitystiedostosta...", @@ -3866,6 +4236,8 @@ "drawZoomOut": "Loitonna (Ctrl+-)", "drawResetView": "Nollaa näkymä (Ctrl+0)", "drawToggleGrid": "Vaihda ruudukko (Ctrl+G)", + "drawTogglePreview": "Esikatselu päälle/pois (F9)", + "drawPreviewFailed": "Esikatselu epäonnistui - tarvitaan ffmpeg libass-tuella (subtitles-suodatin)", "drawCopyToClipboard": "Kopioi leikepöydälle (Ctrl+C)", "drawShapes": "Muodot", "drawSelectedPoint": "Valittu piste", @@ -3873,16 +4245,86 @@ "drawSelectedLayer": "Valittu taso", "drawToolX": "Työkalu: {0}", "drawHelpText": "Napsauta lisätäksesi pisteitä • Enter/F8 sulkeaaksesi muodon • Siirrä Shift+vedä • Zoomaa Ctrl+vieritys", + "drawMoveToLayer": "Siirrä kerrokseen", + "drawFlipHorizontal": "Peilaa vaakasuunnassa", + "drawFlipVertical": "Peilaa pystysuunnassa", + "drawHideShape": "Piilota muoto", + "drawShowShape": "Näytä muoto", + "drawHideLayer": "Piilota kerros", + "drawShowLayer": "Näytä kerros", + "drawDeleteLayer": "Poista kerros", + "drawDeletePoint": "Poista piste", + "drawCancelDrawing": "Peruuta piirtäminen", + "drawLayerX": "Kerros {0}", + "drawImportSvg": "Tuo SVG-kuva...", + "drawRotateClockwise": "Kierrä 90° myötäpäivään", + "drawConvertToCurve": "Muunna segmentti käyräksi", + "drawBackground": "Tausta", + "drawPosition": "Sijainti", + "drawZoom": "Zoomaus", + "drawShapeTool": "Muototyökalu", + "drawColorPickerTool": "Poimi väri muodosta tai taustasta", + "drawShapeLibrary": "Muotokirjasto", + "drawShapeLibraryHint": "Vedä piirtoalueella sijoittaaksesi muodon tai napsauta oletuskokoa varten. Shift säilyttää mittasuhteet.", + "drawAddToLibrary": "Lisää muotokirjastoon...", + "drawRemoveFromLibrary": "Poista muotokirjastosta", + "drawShapeName": "Muodon nimi", + "drawCategorySpeechBubbles": "Puhekuplat", + "drawCategoryArrows": "Nuolet", + "drawCategoryBasicShapes": "Perusmuodot", + "drawCategorySymbols": "Symbolit", + "drawCategoryMyShapes": "Omat muodot", + "drawShapeSpeechBubble": "Puhekupla", + "drawShapeSpeechBubbleRight": "Puhekupla, häntä oikealla", + "drawShapeSpeechBubbleUp": "Puhekupla, häntä ylhäällä", + "drawShapeOvalBubble": "Soikea puhekupla", + "drawShapeBoxBubble": "Laatikkopuhekupla", + "drawShapeThoughtBubble": "Ajatuskupla", + "drawShapeShoutBubble": "Huutokupla", + "drawShapeCaptionBox": "Tekstilaatikko", + "drawShapeArrow": "Nuoli", + "drawShapeDoubleArrow": "Kaksoisnuoli", + "drawShapeChevron": "Kulmanuoli", + "drawShapeCurvedArrow": "Kaareva nuoli", + "drawShapeRoundedRectangle": "Pyöristetty suorakulmio", + "drawShapeTriangle": "Kolmio", + "drawShapeDiamond": "Vinoneliö", + "drawShapePentagon": "Viisikulmio", + "drawShapeHexagon": "Kuusikulmio", + "drawShapeOctagon": "Kahdeksankulmio", + "drawShapeStar": "Tähti", + "drawShapeRing": "Rengas", + "drawShapePlus": "Plus", + "drawShapeBanner": "Banneri", + "drawShapeHeart": "Sydän", + "drawShapeCheck": "Valintamerkki", + "drawShapeMusicNote": "Nuotti", + "drawShapeMusicNotes": "Nuotit", + "drawShapeLightning": "Salama", + "drawShapeCloud": "Pilvi", + "drawShapeMoon": "Kuu", + "drawShapeBadge": "Merkki", + "drawSize": "Koko", + "drawLayer": "Kerros", + "drawBackgroundVideoFrame": "Videokehys (nykyinen kohta)", + "drawBackgroundVideoFrameAt": "Videokehys kohdassa...", + "drawBackgroundImage": "Kuvatiedosto...", + "drawBackgroundNone": "Ei taustaa", + "drawBackgroundStretch": "Venytä kuva piirtoalueen kokoiseksi", + "drawBackgroundOpacity": "Taustan peittävyys", + "drawBackgroundVideoFrameFailed": "Videokehystä ei saatu kohdasta {0}.", + "drawImages": "Kuvat", + "drawConvertToLine": "Muunna segmentti suoraksi", + "drawConvertShapeToCurves": "Muunna kaikki suorat käyriksi", + "drawConvertShapeToLines": "Muunna kaikki käyrät suoriksi", + "drawRotateCounterClockwise": "Kierrä 90° vastapäivään", + "drawSvgImages": "SVG-kuvat", + "drawSvgNoShapes": "SVG-tiedostosta ei löytynyt täytettyjä muotoja tai viivoja.", "progressBarTitle": "ASSA-etenemispalkki", "progressBarSettings": "Etenemispalkki", - "progressBarPosition": "Sijainti", - "progressBarBottom": "Alareuna", - "progressBarTop": "Yläreuna", "mouseOverColor": "Väri hiiren osoittaessa", "clickedColor": "Väri napsautettaessa", "progressBarForeColor": "Etualaväri", - "progressBarBackColor": "Taustaväri", - "progressBarStyle": "Tyyli", "progressBarSquareCorners": "Suorakulmaiset kulmat", "progressBarRoundedCorners": "Pyöristetyt kulmat", "progressBarChapters": "Luvut", @@ -3891,8 +4333,6 @@ "progressBarXAdjustment": "X-säätö", "progressBarYAdjustment": "Y-säätö", "progressBarTextAlignment": "Tekstin kohdistus", - "progressBarTakePosFromVideo": "Ota sijainti videosta", - "progressBarPreview": "Esikatselu", "fontCollectorTitle": "Fonttien kerääjä", "fontCollectorUsedIn": "Käytetty kohteessa", "fontCollectorFontFiles": "Fonttitiedosto(t)", @@ -3939,9 +4379,7 @@ "resolutionResamplerVideoDiffers": "Tekstityksen resoluutio ({0}x{1}) eroaa videon resoluutiosta ({2}x{3}).\nMuutetaanko tekstitys videon mukaiseksi?", "resolutionResamplerAskOnVideoOpen": "Kysy, kun avataan video, jolla on eri resoluutio", "backgroundBoxGenerator": "Luo taustaruutu", - "backgroundBoxPadding": "Täyttö", "backgroundBoxFillWidth": "Täyttöleveys", - "backgroundBoxBoxColor": "Ruudun väri", "backgroundBoxRadius": "Säde", "backgroundBoxCircle": "Ympyrä", "backgroundBoxSpikes": "Piikit", @@ -3953,12 +4391,9 @@ "backgroundBoxTornPaperDouble": "Repeytynyt paperi (ylä- ja alaosa)", "backgroundBoxStarburst": "Tähtisuihku", "backgroundBoxScroll": "Rulla / Pergamentti", - "stylesTitle": "Advanced Sub Station Alpha -tyylit", "stylesInFile": "Tiedostossa olevat tyylit", "stylesSaved": "Tallennetut tyylit", - "category": "Luokka", "allCategories": "[Kaikki]", - "defaultCategory": "Oletus", "newCategory": "Uusi luokka...", "renameCategory": "Nimeä luokka uudelleen...", "deleteCategory": "Poista luokka", @@ -3978,13 +4413,26 @@ "graphics": "Grafiikka", "copyToStorageStyles": "Kopioi tallennustyyliin", "copyToFileStyles": "Kopioi tiedostotyyliin", + "styleXAlreadyExistsInStorage": "Tyyli nimeltä \"{0}\" on jo olemassa tallennetuissa tyyleissä.\n\nKorvataanko se vai säilytetäänkö molemmat (kopio saa uuden nimen)?", + "styleXAlreadyExistsInFile": "Tyyli nimeltä \"{0}\" on jo olemassa tiedostossa.\n\nKorvataanko se vai säilytetäänkö molemmat (kopio saa uuden nimen)?", + "styleNameCannotBeEmpty": "Tyylin nimi ei voi olla tyhjä.", + "styleNameXIsUsedMoreThanOnce": "Tyylin nimeä \"{0}\" käyttää useampi kuin yksi tyyli tiedostossa.", + "overwrite": "Korvaa", + "keepBoth": "Säilytä molemmat", + "doThisForAllConflictingStyles": "Tee näin kaikille ristiriitaisille tyyleille", "setStyleAsDefault": "Aseta tyyli oletukseksi", "takeUsagesFromDotDotDot": "Ota käyttöpaikat kohdasta...", "replaceStyleWithDotDotDot": "Korvaa tyyli tyylillä...", "noAttachmentsFound": "Valitusta ASSA-tiedostosta ei löytynyt liitteitä.", "deleteStyleQuestion": "Poista tyyli?", "deleteStylesQuestion": "Poista tyylit?", + "deleteXStylesQuestion": "Haluatko poistaa {0} tyyliä?", + "deleteXStylesFromStorageQuestion": "Haluatko poistaa {0} tyyliä tallennetuista tyyleistä?", + "deleteStyleXFromStorageQuestion": "Haluatko poistaa tyylin \"{0}\" tallennetuista tyyleistä?", + "deleteStyleXFromFileQuestion": "Haluatko poistaa tyylin \"{0}\" nykyisestä tiedostosta?", "openStyleImportFile": "Avaa tekstitystiedosto, josta tuodaan tyylit", + "styleImportFiles": "Tyylitiedostot (.ass, .sty, Subtitle Edit 4 -kategoriat .template)", + "ssaStyleImportFiles": "Tyylitiedostot (.ssa, .ass, .sty)", "primary": "Ensisijainen", "secondary": "Toissijainen", "applyOverrideTags": "Käytä ohitustunnisteita", @@ -3999,7 +4447,6 @@ "spaceIncrease": "Lisää välilyöntiä", "popIn": "Ponnahda sisään", "spinIn": "Pyöräytä sisään", - "playCurrent": "Toista nykyinen", "setPosition": "Aseta sijainti", "rotation": "Kierto", "scaleX": "Skaalaus X", @@ -4043,10 +4490,8 @@ "advancedEffectOldMovieDescription": "Simuloi vanhaa 35 mm:n filmiprojektoria, jossa on kohinaa, vinjetointia ja naarmuja", "advancedEffectNeonBurst": "Neonpurske", "advancedEffectNeonBurstDescription": "Energiset 'Shorts'-tyyliset tekstitykset, joissa on neonhehkua ja sisään zoomaava 'pop'-animaatio", - "advancedEffectSettings": "Asetukset", "advancedEffectSnowFlakeCount": "Lumihiutaleiden määrä", "advancedEffectStarfieldStarCount": "Tähtien määrä", - "advancedEffectStarfieldSpeed": "Nopeus", "advancedEffectGlitch": "Häiriö", "advancedEffectGlitchDescription": "Digitaalinen häiriö, jossa on kromaattista aberraatiota, valkoisia välähdyksiä ja vääristymiä", "advancedEffectBounceIn": "Pomppiminen sisään", @@ -4055,8 +4500,6 @@ "advancedEffectFirefliesDescription": "Lämpimät, hohtavat kelta-meripihkaiset pisteet, jotka leijuvat orgaanisesti näytön poikki sykkivällä hohtolla", "advancedEffectMatrix": "Matrix", "advancedEffectMatrixDescription": "Putoavat vihreät hahmopylväät, joiden tekstitys muodostuu kirjain kirjaimelta sateesta", - "advancedEffectAudioBars": "Äänipalkit (aaltomuoto)", - "advancedEffectAudioBarsDescription": "Animoidut taajuuskorjainpalkit, joita ohjaa äänen aaltomuoto — palkit vierivät vasemmalle aaltomuodon mukaisesti ja sykkivät äänen tahdissa", "advancedEffectAudioPulse": "Äänitekstin syke", "advancedEffectAudioPulseDescription": "Tekstitysteksti hehkuu ja sykkii äänenvoimakkuuden mukaan — hiljaisissa kohdissa teksti näkyy normaalisti, äänekkäissä kohdissa tekstiin lisätään värillinen hohde ja se suurenee hieman", "advancedEffectConfetti": "Konfettisuihku", @@ -4070,7 +4513,6 @@ "advancedEffectFancyKaraokeGlow": "Aktiivisen sanan hehku", "advancedEffectFancyKaraokeActiveColor": "Aktiivinen väri", "advancedEffectFancyKaraokeInactiveColor": "Ei-aktiivinen väri", - "advancedEffectFancyKaraokeInactiveOpacity": "Ei-aktiivinen läpikuultavuus (0-255)", "advancedEffectWordSpacingPixels": "Välistys (pikseliä)", "advancedEffectSlowZoomOut": "Hidas loitontuminen", "advancedEffectSlowZoomOutDescription": "Teksti alkaa hieman suurempana ja kutistuu tasaisesti normaaliin kokoon tekstityksen keston aikana", @@ -4086,7 +4528,6 @@ "advancedEffectSlideInLeftDescription": "Teksti liukuu sisään näytön vasemmalta reunalta, pysähtyy ja poistuu sitten takaisin vasemmalle", "advancedEffectSlideInRight": "Liuku sisään oikealta", "advancedEffectSlideInRightDescription": "Teksti liukuu näytön oikealta reunan ulkopuolelta, pysähtyy ja poistuu sitten takaisin oikealle", - "advancedEffectFadeInOut": "Häivyttyy sisään/ulos", "advancedEffectFadeInOutDescription": "Teksti häivyttyy näkyviin kunkin tekstityksen alussa ja häivyttyy pois näkyvistä kunkin tekstityksen lopussa", "advancedEffectWordFlip3D": "Sanan 3D-käännös", "advancedEffectWordFlip3DDescription": "Jokainen sana taittuu esiin 3D-käännöksellä ja joustavalla ponnahduksella, kun se puhutaan — moderni Shorts-tekstitystyyli", @@ -4118,7 +4559,6 @@ "noPluginsInstalled": "Ei asennettuja lisäosia", "getPluginsOnline": "Hae lisäosia verkosta...", "getPluginsTitle": "Hae lisäosia", - "enabled": "Käytössä", "disabled": "Ei käytössä", "notSupportedOnThisOs": "Ei tuettu tässä käyttöjärjestelmässä", "removePluginXConfirm": "Poistetaanko lisäosa '{0}'? Tämä poistaa lisäosan kansion:{1}", @@ -4135,17 +4575,19 @@ "downloadingXPercent": "Ladataan {0} %...", "pluginXInstalled": "'{0}' asennettu.", "couldNotInstallPluginX": "Kohdetta '{0}' ei voitu asentaa: {1}", - "notInstalled": "Ei asennettu", "install": "Asenna", "reinstall": "Asenna uudelleen", - "update": "Päivitä", "installedX": "Asennettu ({0})", "updateAvailableXToY": "Päivitys saatavilla ({0} → {1})", "applyPluginToWhichLinesX": "Käytä '{0}' kohteeseen:", "applyToSelectedLinesX": "Valitut rivit ({0})", "applyToAllLinesX": "Kaikki rivit ({0})", "updateAllXAvailable": "Päivitä kaikki ({0})", - "updatingXOfY": "Päivitetään {0}/{1}..." + "updatingXOfY": "Päivitetään {0}/{1}...", + "doNotAskAgain": "Älä kysy uudelleen (muuta kohdassa Hallitse lisäosia)", + "applyPluginsTo": "Käytä lisäosia kohteeseen", + "askEachTime": "Kysy joka kerta", + "allLines": "Kaikki rivit" }, "errorList": { "title": "Luettele virheet", diff --git a/src/ui/Assets/Languages/French.json b/src/ui/Assets/Languages/French.json index 0d8b9e85188..b72ec8cff45 100644 --- a/src/ui/Assets/Languages/French.json +++ b/src/ui/Assets/Languages/French.json @@ -1,6 +1,6 @@ { "title": "Subtitle Edit", - "version": "v5.3.0-rc3", + "version": "v5.3.0", "translatedBy": "Need74 . (11.oct.2026)", "cultureName": "fr-FR", "general": { @@ -254,7 +254,7 @@ "fixRightToLeft": "Corriger droite-vers-gauche « RTL »", "fixRightToLeftViaUnicodeTags": "Corriger « RTL » via les balises unicode", "fixedValue": "Valeur fixe", - "focusSelectedLine": "Focaliser sur le ligne désignée (défiler dans la vue de la grille) ", + "focusSelectedLine": "Focaliser sur le ligne désignée (défiler dans la vue de la grille)", "font": "Police", "fontColor": "Couleur de police", "fontHeight": "Hauteur de police", @@ -537,7 +537,7 @@ "saveTranslationAsTitle": "Enregistrer la traduction sous", "saveVideoAsVideoTitle": "Enregistrer la vidéo sous", "savedChangesToX": "\"{0}\" enregistrée", - "savedChangesToXAndY": "\"{0}\" et \"{1}\" enregistrées ", + "savedChangesToXAndY": "\"{0}\" et \"{1}\" enregistrées", "savingDotDotDot": "Sauvegarde…", "script": "Script", "search": "Recherche", @@ -2461,7 +2461,7 @@ "curlyBrackets": "Accolades", "parentheses": "Parenthèses", "and": "et", - "onlySeparateLines": "Les lignes scindées uniquement ", + "onlySeparateLines": "Les lignes scindées uniquement", "removeTextBeforeColon": "Supprimer le texte avant les deux-points", "onlyIfTextIsUppercase": "Uniquement si le texte est en majuscules", "onlyOnSeparateLine": "Sur une ligne séparée uniquement", @@ -3572,6 +3572,7 @@ "timeCodeModeTime": "Heure (hh:mm:ss,zzz)", "timeCodeModeFrames": "Cadres (hh:mm:ss:ff)", "timeCodeModeFrameNumbers": "Numéros de cadre", + "frameModeSnapToVideoFrames": "Enregistrer les temps sur les images de la vidéo (et non sur les images de chaque seconde)", "textBoxLimitNewLines": "Limiter le nombre de lignes dans la zone de texte du sous-titre", "mpvOpenGl": "libmpv - OpenGL", "mpvSoftwareRendering": "libmpv - Rendu logiciel (lent)", @@ -4234,17 +4235,17 @@ "trainingOptions": "Options d'entraînement", "trainNOcrSubtitle": "Restitue les caractères dans les polices installées et apprend à nOCR leurs formes.\r\nChoisissez les polices utilisées par vos sous-titres.", "xFontsSelected": "{0:#,##0} sélectionné", - "subtitleFonts": "", - "emptyDatabase": "", - "startFromDatabase": "", - "startFromDatabaseHint": "", - "fontSizes": "", - "fontSizesHint": "", - "numberOfLineSegmentsHint": "", - "enterDatabaseName": "", - "trainingFontXSizeYCharacterZ": "", - "xLearned": "", - "xSkipped": "", + "subtitleFonts": "Polices des sous-titres", + "emptyDatabase": "Base de données vide", + "startFromDatabase": "Partir de", + "startFromDatabaseHint": "Ajouter les caractères entraînés à une copie d'une base de données existante (recommandé) ou partir d'une base vide", + "fontSizes": "Tailles de police", + "fontSizesHint": "Tailles de police en pixels pour le rendu, séparées par des virgules, par ex. \"30, 40\"", + "numberOfLineSegmentsHint": "Lignes vérifiées par caractère - plus il y en a, plus c'est strict mais lent", + "enterDatabaseName": "Saisissez un nom pour la base de données", + "trainingFontXSizeYCharacterZ": "Entraînement de \"{0}\" à {1} px : {2}", + "xLearned": "{0:#,##0} appris", + "xSkipped": "{0:#,##0} ignorés", "charactersToTrain": "Caractères à entraîner", "letterCombinationsToTrain": "Combinaisons de lettres pouvant être scindées en une seule image", "importCharactersFromSubtitleFile": "Importer des caractères depuis un fichier de sous-titres…", @@ -4271,8 +4272,8 @@ "drawZoomOut": "Zoom arrière (Ctrl+-)", "drawResetView": "Réinitialiser la vue (Ctrl+0)", "drawToggleGrid": "Afficher/Masquer la grille (Ctrl+G)", - "drawTogglePreview": "", - "drawPreviewFailed": "", + "drawTogglePreview": "Afficher/Masquer l'aperçu (F9)", + "drawPreviewFailed": "Échec de l'aperçu - ffmpeg avec libass (filtre subtitles) est requis", "drawCopyToClipboard": "Copier dans le presse-papiers (Ctrl+C)", "drawShapes": "Formes", "drawSelectedPoint": "Point sélectionné", @@ -4280,81 +4281,81 @@ "drawSelectedLayer": "Calque sélectionné", "drawToolX": "Outil : {0}", "drawHelpText": "Cliquer pour ajouter des points • F8/Entrée pour fermer la forme • Maj+Glisser pour se déplacer • Ctrl+Molette pour zoomer", - "drawMoveToLayer": "", - "drawFlipHorizontal": "", - "drawFlipVertical": "", - "drawHideShape": "", - "drawShowShape": "", - "drawHideLayer": "", - "drawShowLayer": "", - "drawDeleteLayer": "", - "drawDeletePoint": "", - "drawCancelDrawing": "", - "drawLayerX": "", - "drawImportSvg": "", - "drawRotateClockwise": "", - "drawConvertToCurve": "", - "drawBackground": "", - "drawPosition": "", - "drawZoom": "", - "drawShapeTool": "", - "drawColorPickerTool": "", - "drawShapeLibrary": "", - "drawShapeLibraryHint": "", - "drawAddToLibrary": "", - "drawRemoveFromLibrary": "", - "drawShapeName": "", - "drawCategorySpeechBubbles": "", - "drawCategoryArrows": "", - "drawCategoryBasicShapes": "", - "drawCategorySymbols": "", - "drawCategoryMyShapes": "", - "drawShapeSpeechBubble": "", - "drawShapeSpeechBubbleRight": "", - "drawShapeSpeechBubbleUp": "", - "drawShapeOvalBubble": "", - "drawShapeBoxBubble": "", - "drawShapeThoughtBubble": "", - "drawShapeShoutBubble": "", - "drawShapeCaptionBox": "", - "drawShapeArrow": "", - "drawShapeDoubleArrow": "", - "drawShapeChevron": "", - "drawShapeCurvedArrow": "", - "drawShapeRoundedRectangle": "", - "drawShapeTriangle": "", - "drawShapeDiamond": "", - "drawShapePentagon": "", - "drawShapeHexagon": "", - "drawShapeOctagon": "", - "drawShapeStar": "", - "drawShapeRing": "", - "drawShapePlus": "", - "drawShapeBanner": "", - "drawShapeHeart": "", - "drawShapeCheck": "", - "drawShapeMusicNote": "", - "drawShapeMusicNotes": "", - "drawShapeLightning": "", - "drawShapeCloud": "", - "drawShapeMoon": "", - "drawShapeBadge": "", - "drawSize": "", - "drawLayer": "", - "drawBackgroundVideoFrame": "", - "drawBackgroundVideoFrameAt": "", - "drawBackgroundImage": "", - "drawBackgroundNone": "", - "drawBackgroundStretch": "", - "drawBackgroundOpacity": "", - "drawBackgroundVideoFrameFailed": "", - "drawImages": "", - "drawConvertToLine": "", - "drawConvertShapeToCurves": "", - "drawConvertShapeToLines": "", - "drawRotateCounterClockwise": "", - "drawSvgImages": "", - "drawSvgNoShapes": "", + "drawMoveToLayer": "Déplacer vers le calque", + "drawFlipHorizontal": "Retourner horizontalement", + "drawFlipVertical": "Retourner verticalement", + "drawHideShape": "Masquer la forme", + "drawShowShape": "Afficher la forme", + "drawHideLayer": "Masquer le calque", + "drawShowLayer": "Afficher le calque", + "drawDeleteLayer": "Supprimer le calque", + "drawDeletePoint": "Supprimer le point", + "drawCancelDrawing": "Annuler le dessin", + "drawLayerX": "Calque {0}", + "drawImportSvg": "Importer une image SVG…", + "drawRotateClockwise": "Rotation de 90° dans le sens horaire", + "drawConvertToCurve": "Convertir le segment en courbe", + "drawBackground": "Arrière-plan", + "drawPosition": "Position", + "drawZoom": "Zoom", + "drawShapeTool": "Outil forme", + "drawColorPickerTool": "Prélever une couleur sur une forme ou l'arrière-plan", + "drawShapeLibrary": "Bibliothèque de formes", + "drawShapeLibraryHint": "Faites glisser sur le canevas pour placer une forme, ou cliquez pour la taille par défaut. Maj conserve ses proportions.", + "drawAddToLibrary": "Ajouter à la bibliothèque de formes…", + "drawRemoveFromLibrary": "Retirer de la bibliothèque de formes", + "drawShapeName": "Nom de la forme", + "drawCategorySpeechBubbles": "Bulles de dialogue", + "drawCategoryArrows": "Flèches", + "drawCategoryBasicShapes": "Formes de base", + "drawCategorySymbols": "Symboles", + "drawCategoryMyShapes": "Mes formes", + "drawShapeSpeechBubble": "Bulle de dialogue", + "drawShapeSpeechBubbleRight": "Bulle de dialogue, pointe à droite", + "drawShapeSpeechBubbleUp": "Bulle de dialogue, pointe vers le haut", + "drawShapeOvalBubble": "Bulle de dialogue ovale", + "drawShapeBoxBubble": "Bulle de dialogue rectangulaire", + "drawShapeThoughtBubble": "Bulle de pensée", + "drawShapeShoutBubble": "Bulle de cri", + "drawShapeCaptionBox": "Cartouche", + "drawShapeArrow": "Flèche", + "drawShapeDoubleArrow": "Double flèche", + "drawShapeChevron": "Chevron", + "drawShapeCurvedArrow": "Flèche courbe", + "drawShapeRoundedRectangle": "Rectangle arrondi", + "drawShapeTriangle": "Triangle", + "drawShapeDiamond": "Losange", + "drawShapePentagon": "Pentagone", + "drawShapeHexagon": "Hexagone", + "drawShapeOctagon": "Octogone", + "drawShapeStar": "Étoile", + "drawShapeRing": "Anneau", + "drawShapePlus": "Plus", + "drawShapeBanner": "Bannière", + "drawShapeHeart": "Cœur", + "drawShapeCheck": "Coche", + "drawShapeMusicNote": "Note de musique", + "drawShapeMusicNotes": "Notes de musique", + "drawShapeLightning": "Éclair", + "drawShapeCloud": "Nuage", + "drawShapeMoon": "Lune", + "drawShapeBadge": "Badge", + "drawSize": "Taille", + "drawLayer": "Calque", + "drawBackgroundVideoFrame": "Image vidéo (position actuelle)", + "drawBackgroundVideoFrameAt": "Image vidéo à…", + "drawBackgroundImage": "Fichier image…", + "drawBackgroundNone": "Aucun arrière-plan", + "drawBackgroundStretch": "Étirer l'image au canevas", + "drawBackgroundOpacity": "Opacité de l'arrière-plan", + "drawBackgroundVideoFrameFailed": "Impossible d'obtenir une image vidéo à {0}.", + "drawImages": "Images", + "drawConvertToLine": "Convertir le segment en ligne", + "drawConvertShapeToCurves": "Convertir toutes les lignes en courbes", + "drawConvertShapeToLines": "Convertir toutes les courbes en lignes", + "drawRotateCounterClockwise": "Rotation de 90° dans le sens antihoraire", + "drawSvgImages": "Images SVG", + "drawSvgNoShapes": "Aucune forme remplie ni aucun contour trouvé dans le fichier SVG.", "progressBarTitle": "Barre de progression ASSA", "progressBarSettings": "Barre de progression", "mouseOverColor": "Couleur au survol", diff --git a/src/ui/Assets/Languages/German.json b/src/ui/Assets/Languages/German.json index cb224d73fdf..7288709411a 100644 --- a/src/ui/Assets/Languages/German.json +++ b/src/ui/Assets/Languages/German.json @@ -1,6 +1,6 @@ { "title": "Subtitle Edit", - "version": "v5.3.0-rc1", + "version": "v5.3.0", "translatedBy": "Übersetzt von Christoph Kitsche. Überarbeitet von Netspark, Stephan Lenhart und FunkyKnilch.", "cultureName": "de-DE", "general": { @@ -357,6 +357,7 @@ "mergeSelectedLinesDialog": "Ausgewählte Texte als Dialog verbinden", "mergeWithLineAfterAndAutoBreak": "Mit nächster Zeile zusammenführen und automatisch umbrechen", "mergeWithLineAfterKeepBreaks": "Mit Zeile danach zusammenführen (Umbrüche beibehalten)", + "mergeWithLineAfterAndUnbreak": "Mit nächster Zeile verbinden und Zeilenumbruch entfernen", "mergeWithLineBeforeAndAutoBreak": "Mit vorherigem zusammenführen und automatisch umbrechen", "mergeWithLineBeforeKeepBreaks": "Mit Zeile davor zusammenführen (Umbrüche beibehalten)", "middleCenter": "Mitte-Zentriert", @@ -371,6 +372,8 @@ "moveAllShotChangeOneFrameForward": "Alle Szenenwechsel ein Frame vorwärts verschieben", "multiMode": "Multi-Modus", "moveDown": "Nach unten", + "increase": "Erhöhen", + "decrease": "Verringern", "moveUp": "Nach oben", "moveToTop": "Nach ganz oben verschieben", "moveToBottom": "Nach ganz unten verschieben", @@ -440,6 +443,7 @@ "percent": "Prozent", "pickLayer": "Ebene festlegen", "pickOllamaModel": "Ollama-Modell auswählen", + "pickModel": "Modell auswählen", "llamaCppUseRemoteServer": "Externen Server verwenden (URL)", "llamaCppEngineSettings": "llama.cpp-Engine-Einstellungen", "llamaCppEngineSettingsSubtitle": "Lokaler llama.cpp-Server-Build", @@ -516,6 +520,7 @@ "reset": "Zurücksetzen", "resizePanels": "Panelgröße ändern", "resolution": "Auflösung", + "retry": "Wiederholen", "reverseRightToLeftStartEnd": "RTL-Start/-Ende umkehren", "right": "Rechts", "rightToLeft": "Rechts nach links", @@ -590,6 +595,8 @@ "showStartColumn": "Spalte \"Start\" anzeigen", "showHideColumn": "Spalte \"Ausblenden\" anzeigen", "showHistory": "Verlauf (für Rückgängig) anzeigen", + "clearHistory": "Verlauf löschen", + "removeFromHistory": "Aus Verlauf entfernen", "showLayerColumn": "Spalte \"Ebene\" anzeigen", "showPreview": "Vorschau anzeigen", "showShotChangesList": "Liste der Shotänderungen anzeigen", @@ -651,12 +658,14 @@ "styleExaggeration": "Stilübertreibung", "styles": "Stile", "subtitleFile": "Untertiteldatei", + "subtitleFileChangedOutsideX": "Die Datei \"{0}\" wurde außerhalb von Subtitle Edit geändert.\n\nNeu laden und Ihre Änderungen verwerfen?", "subtitleFileName": "Name der Untertiteldatei", "subtitleFileSaved": "Untertiteldatei gespeichert", "subtitleFileSavedToX": "Untertiteldatei in {0} gespeichert", "subtitleFiles": "Untertiteldateien", "subtitleFormats": "Untertitelformate", "subtitleLoadedX": "Untertitel {0} geladen", + "subtitleReloadedChangedOutsideX": "Untertitel neu geladen (außerhalb von Subtitle Edit geändert): {0}", "suffix": "Suffix", "suggestions": "Vorschläge", "sync": "Anpassen", @@ -702,6 +711,7 @@ "type": "Typ", "unbreak": "Umbruch entfernen", "unbreakLines": "Zeilenumbrüche entfernen", + "undelete": "Löschen rückgängig machen", "underline": "Unterstrichen", "undo": "Rückgängig", "unknown": "Unbekannt", @@ -739,10 +749,23 @@ "videoCustom3ForwardX": "Video, benutzerdefinierte Millisekunden ({0:#,###,##0}) vorwärts, 3", "videoCustom4BackX": "Video, benutzerdefinierte Millisekunden ({0:#,###,##0}) zurück, 4", "videoCustom4ForwardX": "Video, benutzerdefinierte Millisekunden ({0:#,###,##0}) vorwärts, 4", + "videoCustom1BackAndPauseX": "Video, benutzerdefinierte Millisekunden ({0:#,###,##0}) zurück und pausieren, 1", + "videoCustom1ForwardAndPauseX": "Video, benutzerdefinierte Millisekunden ({0:#,###,##0}) vorwärts und pausieren, 1", + "videoCustom2BackAndPauseX": "Video, benutzerdefinierte Millisekunden ({0:#,###,##0}) zurück und pausieren, 2", + "videoCustom2ForwardAndPauseX": "Video, benutzerdefinierte Millisekunden ({0:#,###,##0}) vorwärts und pausieren, 2", + "videoCustom3BackAndPauseX": "Video, benutzerdefinierte Millisekunden ({0:#,###,##0}) zurück und pausieren, 3", + "videoCustom3ForwardAndPauseX": "Video, benutzerdefinierte Millisekunden ({0:#,###,##0}) vorwärts und pausieren, 3", + "videoCustom4BackAndPauseX": "Video, benutzerdefinierte Millisekunden ({0:#,###,##0}) zurück und pausieren, 4", + "videoCustom4ForwardAndPauseX": "Video, benutzerdefinierte Millisekunden ({0:#,###,##0}) vorwärts und pausieren, 4", "videoExtension": "Videodateierweiterung", "videoFile": "Videodatei", "videoFileGenerated": "Videodatei erstellt", "videoFileGeneratedX": "Videodatei erzeugt: \"{0}\"", + "videoFilesGenerated": "Videodateien erzeugt", + "xVideosGenerated": "{0} Videos erzeugt", + "xOfYVideosGenerated": "{0} von {1} Videos erzeugt", + "savedInX": "Gespeichert in {0}", + "savedInXFolders": "In {0} Ordnern gespeichert", "videoFiles": "Videodateien", "videoAndAudioFiles": "Video- und Audiodateien", "videoOffset": "Video-Offset", @@ -800,7 +823,6 @@ "imageFiles": "Bilddateien", "openAiCompatibleSttEndpoint": "STT-Endpunkt-URL", "openAiCompatibleSttApiKey": "API-Schlüssel", - "openAiCompatibleSttModel": "Modell", "openAiCompatibleSttExtraHeaders": "Zusätzliche Header", "openAiCompatibleSttTimeout": "Zeitüberschreitung (Sekunden)", "openAiCompatibleSttLanguage": "Sprachhinweis", @@ -810,6 +832,9 @@ "dashScopeSttRegion": "Region", "dashScopeSttEnableWords": "Zeitstempel auf Wortebene", "dashScopeSttRegionKeyHint": "Hinweis: Alibaba Cloud Model Studio API-Schlüssel sind regionsgebunden - stellen Sie sicher, dass die ausgewählte Region der Region entspricht, in der der API-Schlüssel erstellt wurde (China vs. International).", + "googleCloudSttProjectId": "Projekt-ID (nur für Application Default Credentials)", + "googleCloudSttBucketName": "Storage-Bucket (leer = automatisch)", + "googleCloudSttDynamicBatching": "Dynamisches Batching (etwa 5x günstiger, keine Latenzgarantie)", "onlineSttApiKeyMissing": "Ein API-Schlüssel ist erforderlich. Bitte geben Sie Ihren API-Schlüssel ein und versuchen Sie es erneut.", "openAiCompatibleSttAutoTranscribeOnAudioSelection": "Neue Wellenform-Auswahl automatisch per Sprache-zu-Text transkribieren", "openAiCompatibleSttStream": "Antwort streamen", @@ -867,6 +892,7 @@ "multipleReplace": "Mehrfach ersetzen...", "fillSelectedLinesWithClipboard": "Ausgewählte Zeilen mit Text aus Zwischenablage füllen", "goToLineNumber": "Gehe zu Untertitel Nummer...", + "commandPalette": "Befehls_palette...", "rightToLeftMode": "Rechts-nach-links-Modus", "modifySelectionDotDotDot": "Auswahl bearbeiten...", "tools": "Werkzeuge", @@ -1015,6 +1041,7 @@ "videoPositionTextBox": "Textfeld für Videoposition {0}", "toolbarLineBreakX": "Zeilenumbruch {0} (Rest der Symbolleiste wird in neuer Zeile fortgesetzt)", "initialText": "Ursprünglicher Text der ausgewählten Zeile", + "initialTextHint": "Der Text der ausgewählten Zeile, wie er beim Auswählen der Zeile war", "hideWaveformToolbar": "Symbolleiste ausblenden {0}", "resetZoomAndSpeed": "Zoom und Wiedergabegeschwindigkeit zurücksetzen {0}", "removeBlankLines": "Leere Zeilen entfernen {0}", @@ -1028,9 +1055,15 @@ "textPlayHint": "Aktuellen Untertitel abspielen und am Ende stoppen {0}", "textPauseHint": "Wiedergabe pausieren {0}", "textNextHint": "Nächsten Untertitel abspielen und am Ende stoppen {0}", - "moveSelectedLines": "Ausgewählte Zeilen verschieben (Zurück/Vorwärts Schaltflächen)", + "moveSelectedLines": "Ausgewählte Zeilen verschieben (Zurück/Vorwärts Schaltflächen)", "moveSelectedLinesAndFollowing": "Ausgewählte und nachfolgende Zeilen verschieben (Zurück/Vorwärts Schaltflächen)", "moveAllLines": "Alle Zeilen verschieben (Zurück/Vorwärts Schaltflächen)", + "moveSelectedLinesBackHint": "Ausgewählte Zeilen {0} ms zurück verschieben {1}", + "moveSelectedLinesForwardHint": "Ausgewählte Zeilen {0} ms vorwärts verschieben {1}", + "moveSelectedLinesAndFollowingBackHint": "Ausgewählte und alle nachfolgenden Zeilen {0} ms zurück verschieben {1}", + "moveSelectedLinesAndFollowingForwardHint": "Ausgewählte und alle nachfolgenden Zeilen {0} ms vorwärts verschieben {1}", + "moveAllLinesBackHint": "Alle Zeilen {0} ms zurück verschieben {1}", + "moveAllLinesForwardHint": "Alle Zeilen {0} ms vorwärts verschieben {1}", "guessStartNoLineSelected": "Start schätzen: keine Zeile ausgewählt", "guessStartTimeCodesLocked": "Start schätzen: Zeitstempel sind gesperrt", "guessStartNoWaveform": "Start schätzen: keine Wellenform", @@ -1060,7 +1093,7 @@ "deleteTextAndShiftCellsUp": "Text löschen und Zellen nach oben verschieben", "endTimeMustBeAfterStartTime": "Die Endzeit muss nach der Startzeit liegen.", "errorLoad7Zip": "Diese Datei scheint eine komprimierte 7-Zip-Datei zu sein.\nSubtitle Edit kann keine komprimierten Dateien öffnen.", - "errorLoadBinaryZeroes": "Diese Datei enthält nur Nullzeichen.\n\nFalls Sie diese Datei mit Subtitle Edit bearbeitet haben, dürfte eine Sicherung vorliegen unter Menüpunkt \"Datei -> Automatische Sicherung wiederherstellen...\"\t\t", + "errorLoadBinaryZeroes": "Diese Datei enthält nur Nullzeichen.\n\nFalls Sie diese Datei mit Subtitle Edit bearbeitet haben, dürfte eine Sicherung vorliegen unter Menüpunkt \"Datei -> Automatische Sicherung wiederherstellen...\"", "errorLoadGZip": "Diese Datei scheint eine komprimierte GZip-Datei zu sein.\nSubtitle Edit kann keine komprimierten Dateien öffnen.", "errorLoadLargeFile": "Diese Datei ist zu groß, um als Untertiteldatei geöffnet zu werden.", "errorLoadVideoFilePrompt": "Diese Datei scheint eine Videodatei zu sein:\n{0}\n\nSie können ein Video über \"Video -> Video öffnen\" oder durch Ziehen und Ablegen eines Videos auf den Videoplayer öffnen.\n\nMöchten Sie sie als Videodatei öffnen?", @@ -1093,11 +1126,14 @@ "numberOfLinesEvenlyDistributedX": "{0} Zeilen gleichmäßig verteilt", "oneLineCopiedFromOriginal": "Text vom Original einfügen", "oneLineCopiedToOriginal": "Eine Zeile in den Originaluntertitel kopiert", + "onlineOnlyVideo": "Nur online verfügbares Video", + "onlineOnlyVideoXSizeYDownloadAndOpen": "Die Videodatei \"{0}\" ({1}) ist nur online gespeichert, z. B. in Dropbox oder iCloud Drive, und muss heruntergeladen werden, bevor sie geöffnet werden kann.\n\nJetzt herunterladen und öffnen?", "oneLineMerged": "Eine Zeile zusammengeführt", "oneLineSwitched": "Eine Zeile gewechselt", "overwriteExistingCells": "Vorhandene Zellen überschreiben", "overwriteOrShiftCellsDown": "Zellen nach unten verschieben/überschreiben", "parsingMatroskaFile": "Untersuche Matroska Datei...", + "readingDvdSubtitles": "DVD-Untertitel werden gelesen...", "pasteFromClipboardDotDotDot": "Aus Zwischenablage einfügen...", "redoPerformed": "Wiederhergestellt", "redoPerformedXActionLeft": "Wiederhergestellt (verbleibende Aktionen: {0})", @@ -1137,12 +1173,14 @@ "trimmedXLines": "{0} Untertitelzeilen gekürzt", "pastedXLinesOverSelectedLines": "{0} Zeile(n) über die ausgewählten Zeilen eingefügt", "pastedXOfYLinesOverSelectedLines": "{0} von {1} Zeile(n) über die ausgewählten Zeilen eingefügt - der Rest passte nicht in die Auswahl", + "insertedXLinesFromClipboard": "{0} Zeile(n) aus der Zwischenablage eingefügt", "openOriginalDifferentNumberOfSubtitlesXY": "Die Original-Untertiteldatei hat nicht die gleiche Anzahl an Untertiteln wie die aktuelle Untertiteldatei.\n\n• Original-Untertitel: {0}\n• Aktuelle Untertitel: {1}", "originalTextReadOnly": "Originaltext (schreibgeschützt)", "originalTextEditMode": "Originaltext (Bearbeitungsmodus)", "originalIsReadOnlyNotSaved": "Der Originaluntertitel ist eine schreibgeschützte Referenz und wurde nicht gespeichert", "originalIsEmptyNotSaved": "Der Originaluntertitel enthält überhaupt keinen Text und wurde nicht gespeichert", "originalIsReadOnlyReference": "Der Originaluntertitel ist als schreibgeschützte Referenz geöffnet", + "closeTranslationQuestionXY": "Die Übersetzung \"{0}\" schließen?\n\nDer Originaluntertitel \"{1}\" wird zum Untertitel, den Sie bearbeiten. Die Übersetzungsdatei selbst wird nicht geändert.", "allowEditOfOriginalSubtitle": "Bearbeiten des Originaluntertitels erlauben", "showAllOriginalLinesX": "Alle Originalzeilen anzeigen ({0} ohne Entsprechung hier)", "showAllOriginalLinesHint": "Die Zeilen ohne Entsprechung werden als zusätzliche Zeilen angezeigt, damit Sie sehen, was der Übersetzung fehlt.", @@ -1177,6 +1215,7 @@ "fileExportedInFormatXToY": "Datei im Format {0} exportiert nach {1}", "fileExportedInFormatXToFileY": "Datei im Format \"{0}\" exportiert in Datei \"{1}\"", "fixedXLines": "{0} Zeilen korrigiert", + "toolXChangedYLines": "{0}: {1} Zeilen geändert", "transcriptionCompletedWithXLines": "Transkription abgeschlossen mit {0} Zeilen", "openSubtitleFileX": "Untertiteldatei \"{0}\" öffnen?", "replacedXOccurrences": "{0} Ersetzungen vorgenommen", @@ -1267,6 +1306,21 @@ "formattingDotDotDot": "Formatierung...", "imageBasedSubtitleForEditDotDotDot": "Bildbasierten Untertitel zum Bearbeiten...", "imageBasedSubtitleForOcrDotDotDot": "Bildbasierten Untertitel für OCR...", + "dvdSubtitlesDotDotDot": "Untertitel von DVD (IFO/VOB)...", + "titleImportDvd": "Untertitel von DVD importieren", + "dvdIfoFile": "IFO-Datei", + "dvdVobFiles": "VOB-Dateien", + "dvdVideoStandard": "Videostandard", + "dvdStartRipping": "Rippen starten", + "dvdRippingAborted": "Rippen abgebrochen", + "dvdOpenIfoFile": "DVD-IFO-Datei öffnen", + "dvdAddVobFiles": "VOB-Dateien hinzufügen", + "dvdIfoFiles": "DVD-IFO-Dateien", + "dvdVobFileType": "DVD-VOB-Dateien", + "dvdDropHint": "Öffnen Sie eine IFO-Datei (VIDEO_TS\\VTS_01_0.IFO) oder fügen Sie VOB-Dateien hinzu - oder ziehen Sie sie hierher", + "dvdNotAnIfoFileX": "{0} ist keine DVD-IFO-Datei", + "dvdMenuIfoNoTitleSetsX": "Keine Titelsets (VTS_xx_0.IFO) neben {0} gefunden", + "dvdEncryptedXOfY": "{0} der {1} Untertitelpakete sind noch CSS-verschlüsselt - diese VOB-Dateien wurden ohne Entschlüsselung kopiert, daher werden einige Untertitelbilder fehlerhaft sein.\n\nEntschlüsseln Sie die DVD zuerst mit einem DVD-Ripping-Tool, um ein sauberes Ergebnis zu erhalten.\n\nTrotzdem fortfahren?", "splitTextAt": "Text teilen bei", "blankLines": "Leerzeilen", "oneLineIsOneSubtitle": "Eine Zeile ist ein Untertitel", @@ -1286,8 +1340,13 @@ "forcedAlignerProgress": "Ausrichten... Fenster {0} von {1}, {2} von {3} Zeilen", "forcedAlignerSetupTitle": "Zeitstempel per Forced Aligner ausrichten", "forcedAlignerSetupIntro": "Ein Forced Aligner ordnet den bereits vorhandenen Untertiteltext dem Ton zu, ohne ihn zuvor zu transkribieren. Lange Videos werden in Abschnitten ausgerichtet, sodass jede Länge funktioniert.", + "forcedAlignerEndsFromSpeech": "Endzeiten aus isolierter Sprache setzen (langsam)", + "forcedAlignerEndsFromSpeechHint": "Ein Forced Aligner findet, wo eine Zeile beginnt, nicht wo sie endet, daher ergeben sich Endzeiten normalerweise aus der Lesezeit. Ist dies aktiviert, werden zuerst Musik und Soundeffekte aus dem Ton entfernt, und jede Zeile endet dort, wo ihre Sprache verstummt. Dauert mit einer GPU etwa so lange wie das Video selbst - und ohne GPU um ein Vielfaches länger.", + "forcedAlignerIsolatingSpeech": "Sprache wird isoliert... {0}", + "forcedAlignerIsolatingSpeechFailed": "Die Sprache konnte nicht isoliert werden - die Endzeiten wurden stattdessen aus der Lesezeit gesetzt.", "forcedAlignerModel": "Aligner-Modell", - "forcedAlignerDownloadEngine": "Engine herunterladen / aktualisieren..." + "forcedAlignerDownloadEngine": "Engine herunterladen / aktualisieren...", + "forcedAlignerEstimatedXOfY": "{0} von {1} Zeilen konnten nicht ausgerichtet werden und wurden nach Lesezeit platziert - ihre Zeitcodes sind nur geschätzt. Das Skript ist wahrscheinlich länger als das, was im Video gesprochen wird." }, "export": { "exportImagesProfiles": "Bildprofile exportieren", @@ -1336,6 +1395,18 @@ "imageBasedSubtitleSaved": "Bildbasierter Untertitel gespeichert", "fullFrameImage": "Vollbild-Bild", "fullFrameImageHint": "Jedes Bild in der Größe des Videobildes erzeugen, mit dem Untertitel an seiner Position", + "stereo3D": "3D", + "stereo3DHint": "Für 3D-Video: Untertitel einmal in jeder Augenhälfte des Bildes zeichnen", + "halfSideBySide": "Half Side-by-Side", + "halfTopBottom": "Half Top/Bottom", + "depth3D": "Tiefe", + "depth3DHint": "Wie weit der Untertitel aus dem Bildschirm heraustritt - positiv holt ihn näher, negativ schiebt ihn zurück", + "plane3D": "3D-Plane", + "plane3DHint": "Jedem Untertitel die Tiefe geben, die eine 3D-Blu-ray für ihn speichert, aus einer 3D-Plane-Datei (.ofs), erstellt mit BD3D2MK3D oder OFSExtractor. Tiefe wird verwendet, wo die 3D-Plane keine hat", + "openPlane3DTitle": "3D-Plane-Datei öffnen", + "unableToLoadPlane3DX": "3D-Plane konnte nicht geladen werden: {0}", + "plane3DXDepthYToZ": "{0} (Tiefe {1} bis {2})", + "plane3DXNoDepth": "{0} (keine Tiefenwerte)", "textEffect": "Texteffekt", "textEffectSettingsTitle": "Texteffekt-Einstellungen", "textEffectPreset": "Vorgabe", @@ -1440,7 +1511,22 @@ "restoreAutoBackupFile": "Auto-Sicherungsdatei wiederherstellen", "restoreXFromY": "Möchten Sie \"{0}\" von {1} wiederherstellen?", "deleteAllSubtitleBackups": "Möchten Sie alle Untertitel-Sicherungsdateien löschen?", - "deleteAll": "Alle löschen" + "deleteAll": "Alle löschen", + "subtitles": "Untertitel", + "settingsBackupInfo": "Settings.json wird beim Start von Subtitle Edit gesichert, höchstens einmal alle {0} Tag(e) und nur, wenn sich die Einstellungen geändert haben. Die neuesten {1} Sicherungen werden aufbewahrt.", + "settingsBackupInfoEveryStart": "Settings.json wird bei jedem Start von Subtitle Edit gesichert, wenn sich die Einstellungen geändert haben. Die neuesten {0} Sicherungen werden aufbewahrt.", + "settingsBackupOff": "Die automatische Sicherung der Einstellungen ist ausgeschaltet (Optionen → Einstellungen → Datei).", + "lastSettingsBackupX": "Letzte Sicherung: {0}", + "noSettingsBackupsYet": "Noch keine Sicherungen der Einstellungen", + "openSettingsBackupFolder": "Sicherungsordner der Einstellungen öffnen", + "backUpSettingsNow": "Jetzt sichern", + "settingsBackedUp": "Die aktuellen Einstellungen wurden gesichert.", + "restoreSettings": "Einstellungen wiederherstellen", + "restoreSettingsFromX": "Möchten Sie Ihre aktuellen Einstellungen durch die Sicherung vom {0} ersetzen?\n\nIhre aktuellen Einstellungen werden zuerst gesichert. Einige Änderungen werden erst nach einem Neustart von Subtitle Edit wirksam.", + "settingsRestored": "Die Einstellungen wurden wiederhergestellt. Bitte starten Sie Subtitle Edit neu, damit alle Änderungen wirksam werden.", + "settingsRestoreFailed": "Einstellungen konnten nicht wiederhergestellt werden: {0}", + "settingsBackupNotValid": "die Sicherung ist keine gültige Einstellungsdatei, daher wurden Ihre aktuellen Einstellungen beibehalten", + "deleteAllSettingsBackups": "Möchten Sie alle Sicherungsdateien der Einstellungen löschen?" }, "propertiesDCinema": { "title": "D-Cinema SMPTE-Eigenschaften", @@ -1454,22 +1540,16 @@ "editRate": "Bearbeitungsrate", "timeCodeRate": "Zeitstempelrate", "startTime": "Startzeit", - "font": "Schrift", "fontId": "Schrift-ID", "fontUri": "Schrift-URI", - "generate": "Erzeugen", - "fontColor": "Schriftfarbe", "chooseColor": "Farbe wählen", "fontEffect": "Schrifteffekt", "effectColor": "Effektfarbe", - "fontSize": "Schriftgröße", "topBottomMargin": "Oberer/unterer Rand", "fadeUpTime": "Einblendzeit", "fadeDownTime": "Ausblendzeit", - "frames": "Frames", "zPosition": "Z-Position", - "zPositionHelp": "Positive Zahlen rücken den Text weiter weg, negative Zahlen näher heran; bei Z-Position null ist es 2D", - "export": "Exportieren..." + "zPositionHelp": "Positive Zahlen rücken den Text weiter weg, negative Zahlen näher heran; bei Z-Position null ist es 2D" }, "webVtt": { "setStylesForSelectedLinesTitle": "WebVTT-Stile für ausgewählte Zeilen festlegen", @@ -1553,10 +1633,15 @@ "compareChangeSyncShiftXYZ": "#{0}–#{1} um {2} verschoben (Synchronisationspunkt)", "compareTimingDeltaHint": "Wie viel später (+) oder früher (−) die Referenz im Vergleich zur aktuellen Zeile beginnt und endet", "loadXFromFile": "Lade \"{0}\" aus der Datei", + "compareLoadSavedFile": "Gespeicherte Datei laden", "saveCompareHtmlTitle": "Vergleichs-HTML-Datei speichern", "pickMatroskaTrackX": "Matroska-Track auswählen - {0}", "pickTransportStreamTrackX": "Transportstream-Spur wählen - {0}", + "chapters": "Kapitel", + "dvdVobFilesMissingX": "VOB-Dateien fehlen ({0}% gefunden)", "pickMp4TrackX": "MP4-Spur wählen - {0}", + "pickMxfTrackX": "MXF-Spur auswählen - {0}", + "pickMpegTrackX": "MPEG-Spur auswählen - {0}", "rosettaProperties": "Timed Text Rosetta IMSC-Eigenschaften", "rosettaFontSize": "Schriftgröße (Zeilenhöhe)", "propertyTimeBase": "Zeitbasis", @@ -1566,7 +1651,6 @@ "propertyDefaultRegion": "Standardregion", "propertyStyleAttributeName": "Name des Stilattributs", "propertyTimeCodeFormat": "Zeitstempelformat", - "propertyFileExtension": "Dateierweiterung", "propertyTopOrigin": "Oberer Ursprung", "propertyTopExtent": "Obere Ausdehnung", "propertyBottomOrigin": "Unterer Ursprung", @@ -1676,6 +1760,7 @@ "xLinesYSubtitles": "{0} Zeilen, {1} Untertitel", "xLinesYSubtitlesZErrors": "{0} Zeilen, {1} Untertitel, {2} Fehler", "couldNotParseAsX": "Konnte nicht als {0} gelesen werden", + "readAsXUseAsY": "Die Quelle konnte nicht als {1} gelesen werden, aber als {0}.\nDie Zeilen verwenden und weiterhin als {1} speichern?", "selectedXCharactersYLines": "Ausgewählt: {0} Zeichen in {1} Zeile(n)", "replacedXOccurrences": "{0} Ersetzungen vorgenommen", "invalidRegularExpression": "Ungültiger regulärer Ausdruck", @@ -1773,6 +1858,7 @@ "removeLineBreaksPixelWidth": "Zeilenumbrüche aus Untertiteln entfernen, die in eine Zeile passen (Pixelbreite)", "fixUppercaseIInsideLowercaseWords": "Großes 'i' in klein geschriebenen Wörtern (OCR Fehler) korrigieren", "fixDoubleApostrophes": "Doppelte Apostrophe ('' ) zu Anführungszeichen (\"\") korrigieren", + "fixMisreadQuotes": "Als doppelte Anführungszeichen falsch erkannte Apostrophe korrigieren (OCR, Englisch)", "addPeriods": "Punkt am Ende einfügen, wenn nächste Zeile mit Großbuchstaben beginnt.", "startWithUppercaseLetterAfterParagraph": "Mit Großbuchstaben nach einem Punkt beginnen", "startWithUppercaseLetterAfterPeriodInsideParagraph": "Mit Großbuchstaben nach Punkten beginnen", @@ -1795,6 +1881,7 @@ "fixCommasExample": ",, -> ,", "removeLineBreaksExample": "Foo
bar! -> Foo bar!", "fixDoubleApostrophesExample": "''Hat doppelte einfache Anführungszeichen'' -> \"Hat einfaches doppeltes Anführungszeichen\"", + "fixMisreadQuotesExample": "\"Hello' -> \"Hello\"", "addPeriodsExample": "Hallo Welt -> Hallo Welt.", "startWithUppercaseLetterAfterParagraphExample": "p1: Foobar! || p2: foobar! -> p1: Foobar! || p2: Foobar!", "startWithUppercaseLetterAfterPeriodInsideParagraphExample": "Hallo da! wie geht es dir? -> Hallo da! Wie geht es dir?", @@ -1821,7 +1908,7 @@ "fixInvalidItalicTag": "Ungültigen Kursiv Tag korrigieren", "fixShortDisplayTime": "Kurze Anzeigezeit korrigieren", "fixOverlappingDisplayTime": "Überlappende Anzeigezeit korrigieren", - "fixInvalidItalicTagsExample": "Mir egal. -> Mir egal.", + "fixInvalidItalicTagsExample": "Mir egal. -> Mir egal.", "removeUnneededSpacesExample": "Hey du , da drüben. -> Hey du, da drüben.", "removeUnneededPeriodsExample": "Hey du!. -> Hey du!", "fixMissingSpacesExample": "Hey.Du. -> Hey. Du.", @@ -1884,6 +1971,8 @@ "fixMinDurationMs": "Mindestdauer (ms) festlegen", "doNotGoPastShotChange": "Nicht über die Szenenwechsel verlängern", "fixMaxDurationMs": "Maximale Dauer (ms) festlegen", + "fixMinDurationFrames": "Minimale Dauer korrigieren (Frames)", + "fixMaxDurationFrames": "Maximale Dauer korrigieren (Frames)", "minimumDurationMilliseconds": "Mindestdauer (Millisekunden):", "maximumDurationMilliseconds": "Höchstdauer (Millisekunden):", "maxDurationShouldBeHigherThanMinDuration": "Die maximale Dauer sollte höher sein als die minimale Dauer", @@ -1933,7 +2022,7 @@ "timeCodesExtractFailed": "Zeitstempel konnten nicht aus dem Video gelesen werden", "timeCodesHint": "Richtet Untertitel an den tatsächlichen Frame-Zeiten des Videos aus, anstatt von einer konstanten Bildrate auszugehen. Erfordert ein einmaliges Einlesen des gesamten Videos; das Ergebnis wird zwischengespeichert." }, - "improveTimeCodes": { + "improveTimeCodes": { "title": "Zeitstempel optimieren (Forced Alignment)", "aligner": "Aligner", "alignerHint": "Das Modell, das jede Zeile in der Audiodatei analysiert. Die am besten passenden Optionen für die Sprache des Untertitels stehen an erster Stelle.", @@ -2007,7 +2096,6 @@ "profileName": "Profilname", "overwriteProfileX": "Ein Profil mit dem Namen \"{0}\" existiert bereits. Überschreiben?", "deleteProfileX": "Profil \"{0}\" löschen?", - "general": "Allgemein", "gap": "Abstand", "gapSuffix": "Frames (aktualisiert auch die unten stehenden Abstandsfelder ungleich null)", "milliseconds": "ms", @@ -2038,6 +2126,10 @@ "bridgeGaps": { "bridgeGapsSmallerThan": "Abstände überbrücken kleiner als (ms)", "minGap": "Mindestabstand (ms)", + "bridgeGapsSmallerThanFrames": "Lücken überbrücken, die kleiner sind als (Frames)", + "minGapFrames": "Minimaler Abstand (Frames)", + "calculateBridgeGapsSmallerThanDotDotDot": "Grenze für Lückenüberbrückung aus einer Bildrate berechnen...", + "useXMsAsBridgeGapsSmallerThan": "Lücken überbrücken, die kleiner als \"{0}\" Millisekunden sind?", "numberOfSmallGapsBridgedX": "Zeitabstände überbrückt: {0}", "percentFoPrevious": "Abstand zum vorherigen (%)", "gapChange": "Abstandswechsel" @@ -2237,6 +2329,7 @@ }, "mergeLinesWithSameTimeCodes": { "maxMsDifference": "Max. Millisekunden Abstand", + "maxFramesDifference": "Max. Unterschied (Frames)", "makeDialog": "Dialog erstellen" }, "netflixCheckAndFix": { @@ -2336,12 +2429,29 @@ "letterboxCustom": "Benutzerdefinierte Balkenhöhe", "barHeightPx": "Balkenhöhe (px)", "titleSafePercent": "Title-safe (%)", + "moveCaptions": "Untertitel verschieben", + "moveCaptionsDotDotDot": "Untertitel verschieben...", + "moveCaptionsTo": "Untertitel verschieben", + "moveCaptionsIntoBars": "In die Letterbox-Balken (außerhalb des Bildes)", + "moveCaptionsIntoPicture": "In das Bild (aus den Balken heraus)", + "moveCaptionsOffset": "Abstand vom Rand (px)", + "letterboxBarHeightXPxYOfZCaptionsWillMove": "Balkenhöhe: {0} px - {1} von {2} Untertiteln werden verschoben", + "removeFadeInOut": "Ein-/Ausblenden entfernen", + "removeFadeInOutXLinesRemoved": "{0} Überblendungszeilen mit den zugehörigen Zeilen zusammengeführt", + "removeFadeInOutNothingFound": "Keine Ein-/Ausblendzeilen gefunden", "positionSummary": "{0}×{1} - {2} Untertitel - Balkenhöhe: {3} px", "xInActivePicture": "{0} im Bild", "xInTopBar": "{0} im oberen Balken", "xInBottomBar": "{0} im unteren Balken", "noImageSubtitlesLoaded": "Keine Bilduntertitel geladen", - "imageBasedFormatNotSupported": "Bildbasiertes Untertitelformat nicht gefunden/unterstützt." + "imageBasedFormatNotSupported": "Bildbasiertes Untertitelformat nicht gefunden/unterstützt.", + "frameRateDeclaredInFile": "In der Blu-ray-sup-Datei angegebene Bildrate", + "frameRateDetectedFromTimeCodesX": "Aus den Blu-ray-sup-Zeitcodes erkannte Bildrate (die Datei gibt {0} an)", + "frameRateFromVideo": "Bildrate des Videos", + "frameRateCurrent": "Aktuelle Bildrate", + "frameRateSetManually": "Bildrate manuell festgelegt", + "frameRateUsageInfo": "Blu-ray-sup-, BDN-XML-, DOST- und FCP-Exporte werden mit dieser Bildrate geschrieben. Zeiten werden dadurch nicht geändert - verwenden Sie dafür \"Bildrate ändern\".", + "useVideoFrameRateXInsteadOfY": "Die Bildrate des Videos ist {0}, der Untertitel ist aber auf {1} eingestellt.\n\nDie Bildrate des Videos ({0}) verwenden?" }, "removeTextForHearingImpaired": { "interjections": "Interjektionen", @@ -2371,6 +2481,7 @@ "pickFontNameInstalledFonts": "Installierte Schriftarten", "pickFontNameCollectedFonts": "Gesammelte Schriftarten", "colorPickerTitle": "Farbe wählen", + "colorPickerBrightness": "Helligkeit", "filterLayersTitle": "Ebenen für Anzeige filtern", "filterLayersHideFromWaveform": "Von Wellenform/Spektrogramm ausblenden", "filterLayersHideFromSubtitleGrid": "Von Untertiteltabelle ausblenden", @@ -2378,7 +2489,7 @@ "pickSubtitleFormat": "Untertitelformat wählen", "pickSubtitleFormatImageBasedNoPreview": "Bildbasiertes Untertitelformat.\nBitmaps werden bei der Konvertierung erzeugt - keine Textvorschau.", "recentColors": "Zuletzt verwendete Farben", - "fixCommonErrorsSettingsTitle": "" + "fixCommonErrorsSettingsTitle": "Einstellungen für \"Häufige Fehler beheben\"" }, "spellCheck": { "spellCheck": "Rechtschreibprüfung", @@ -2448,6 +2559,7 @@ "sampleRate": "Abtastrate", "audio": "Audio", "preset": "Voreinstellung", + "tune": "Tune", "pixelFormat": "Pixelformat", "crf": "CRF", "useSource": "Quelle verwenden", @@ -2459,7 +2571,9 @@ "outputProperties": "Ausgabeeigenschaften...", "videoFileSize": "Größe der Videodatei", "oneBox": "Eine Box", - "logoInfo": "Wählen Sie ein PNG-Bild und ziehen Sie es, um es auf dem Video zu positionieren." + "logoInfo": "Wählen Sie ein PNG-Bild und ziehen Sie es, um es auf dem Video zu positionieren.", + "noSubtitlesGenerateAnyway": "Es gibt keine Untertitelzeilen zum Einbrennen.\n\nDas Video ohne Untertitel erzeugen?", + "noSubtitlesInXItemsGenerateAnyway": "{0} Stapelelement(e) haben keine Untertitelzeilen zum Einbrennen.\n\nDiese Videos ohne Untertitel erzeugen?" }, "videoTransparent": { "title": "Transparentes Video mit Untertiteln erzeugen ..." @@ -2470,6 +2584,21 @@ "translateToEnglish": "Ins Englische übersetzen", "transcribing": "Transkribieren...", "transcribingXOfY": "Transkribiere Audio zu Text - Datei {0} von {1}...", + "linesTranscribedXOfYFailedZ": "{0} von {1} Zeilen wurden transkribiert - {2} sind fehlgeschlagen oder enthielten keine Sprache.\n\nFehlgeschlagene Zeilen behalten ihren aktuellen Text. Die transkribierten Zeilen übernehmen oder die fehlgeschlagenen erneut versuchen (Sie können vorher Engine oder Einstellungen ändern)?", + "linesTranscribedXOfYCancelled": "Die Transkription wurde nach {0} von {1} Zeilen abgebrochen.\n\nDie transkribierten Zeilen übernehmen? Die übrigen Zeilen behalten ihren aktuellen Text.", + "noLinesTranscribed": "Keine der {0} Zeilen konnte transkribiert werden.\nBitte prüfen Sie das Werkzeugprotokoll für Details.", + "applyTranscribedLines": "Transkribierte übernehmen", + "retryFailedLines": "Fehlgeschlagene wiederholen", + "isolateSpeech": "Sprache isolieren (langsam)", + "isolateSpeechHint": "Entfernt Musik und Soundeffekte vor dem Transkribieren. Ergibt weniger verpasste Zeilen und besseres Timing bei Ton mit lauter Musik, dauert aber mit einer GPU etwa so lange wie der Ton selbst - und ohne GPU um ein Vielfaches länger.", + "isolatingSpeech": "Sprache wird isoliert...", + "isolateSpeechFailed": "Die Sprache konnte nicht isoliert werden - stattdessen wird der Originalton transkribiert.", + "detectSpeakers": "Sprecher erkennen", + "detectSpeakersHint": "Beginnt jede Zeile mit dem Sprecher, z. B. \"(speaker 0)\", mithilfe der Sortformer-Diarisierung von NVIDIA (bis zu 8 Sprecher). Funktioniert am besten mit Backends, die Zeilen beim Sprecherwechsel trennen, wie Parakeet und Cohere.", + "detectSpeakersNeedsNewerCrispAsr": "\"Sprecher erkennen\" benötigt Crisp ASR v{0} oder neuer, installiert ist aber v{1}.\n\nAktualisieren Sie Crisp ASR (Zahnrad-Schaltfläche neben der Engine) oder deaktivieren Sie \"Sprecher erkennen\".", + "xNeedsNewerCrispAsr": "{0} benötigt Crisp ASR v{1} oder neuer, installiert ist aber v{2}.\n\nDas Update jetzt herunterladen und installieren?", + "vad": "VAD", + "vadHint": "Sprachaktivitätserkennung: Nur die Sprache wird transkribiert, geschnitten dort, wo der Sprecher pausiert.\nAuto: Silero, und nur für die Engines, die es benötigen (Cohere, Mega, Index-Echo).\nSilero, FireRedVAD, Whisper-VAD, WebRTC: immer aktiv, für jede Crisp-ASR-Engine. FireRedVAD ist am genauesten, Whisper-VAD liefert die wenigsten und saubersten Schnitte, ist aber am langsamsten.\nIndex-Echo verwendet immer Silero. --vad, --vad-model oder --chunk-seconds in den erweiterten Einstellungen haben Vorrang.", "inputLanguage": "Eingabesprache", "advancedWhisperSettings": "Erweiterte Whisper-Einstellungen", "downloadingSpeechToTextEngine": "Sprache-zu-Text-Engine wird heruntergeladen", @@ -2553,6 +2682,19 @@ "videoEncodingSettings": "TTS – Videokodierungseinstellungen", "elevenLabsSettings": "TTS – ElevenLabs-Einstellungen", "elevenLabsSettingsResetHint": "ElevenLabs-Einstellungen auf Standardwerte zurücksetzen", + "provider": "Anbieter", + "openAiCompatibleSettings": "TTS - OpenAI-kompatible Einstellungen", + "customServerUrl": "Benutzerdefinierte Server-URL", + "customServerUrlHint": "Vollständiger Sprach-Endpunkt eines OpenAI-kompatiblen Servers, verwendet wenn der Anbieter \"Custom\" ist, z. B. http://localhost:8880/v1/audio/speech", + "customModels": "Benutzerdefinierte Modelle", + "customModelsHint": "Kommagetrennte Modellnamen, die angeboten werden, wenn der Anbieter \"Custom\" ist", + "customVoices": "Benutzerdefinierte Stimmen", + "customVoicesHint": "Kommagetrennte Stimmennamen, verwendet vom Anbieter \"Custom\" und von OpenRouter-Modellen, die keine Stimmenliste veröffentlichen", + "instructions": "Anweisungen", + "instructionsHint": "Optionaler Sprechstil, z. B. \"Sprich mit ruhiger, warmer Stimme\". Wird nur an Modelle gesendet, die dies unterstützen (nicht tts-1/tts-1-hd)", + "openAiSpeedHint": "Sprechgeschwindigkeit von 0.25 bis 4.0 (1.0 ist normal). Nicht jedes Modell unterstützt dies", + "outputFormat": "Ausgabeformat", + "outputFormatHint": "Vom Server angefordertes Audioformat. \"Auto\" fordert MP3 an und wechselt zu PCM bei Modellen, die nur PCM liefern (z. B. Gemini TTS)", "regenerateAudio": "Audio neu generieren", "autoContinuePlaying": "Automatisches Weiterspielen", "playLine": "Zeile abspielen", @@ -2561,6 +2703,13 @@ "addingAudioToVideoFileDotDotDot": "Audio wird zur Videodatei hinzugefügt...", "preparingMergeDotDotDot": "Zusammenführung wird vorbereitet...", "importVoiceDotDotDot": "Stimme importieren...", + "renameVoiceDotDotDot": "Stimme umbenennen...", + "renameVoiceTitle": "Stimme umbenennen", + "voiceXCouldNotBeRenamedX": "Stimme '{0}' konnte nicht umbenannt werden: {1}", + "deleteVoiceDotDotDot": "Stimme löschen...", + "deleteVoiceTitle": "Stimme löschen", + "deleteVoiceXQuestion": "Stimme '{0}' löschen?\n\nDie Referenzaufnahme und ihre Dateien werden von der Festplatte entfernt.", + "voiceXCouldNotBeDeletedX": "Stimme '{0}' konnte nicht gelöscht werden: {1}", "voiceImportSuccessTitle": "Stimme importiert", "voiceXImported": "Stimme \"{0}\" erfolgreich importiert", "voiceXCouldNotBeImported": "Stimme '{0}' konnte nicht importiert werden - siehe Protokoll für Details", @@ -2570,6 +2719,49 @@ "dropAudioFileHereToImportVoice": "Audiodatei hier ablegen, um Stimme zu importieren", "dropAudioFileHereHint": ".wav oder .mp3", "voiceCloneTranscriptTitle": "Transkript des Audios eingeben (für Stimmklonen erforderlich)", + "voiceManagerTitle": "TTS - Stimmenverwaltung", + "voiceManagerDotDotDot": "Stimmenverwaltung...", + "voiceManagerMenuItem": "TTS-Stimmenverwaltung...", + "speakFromCurrentLineTitle": "Ab aktueller Zeile vorlesen", + "speakFromCurrentLineDotDotDot": "Ab aktueller Zeile vorlesen...", + "speakFromCurrentLineHint": "Liest die Zeilen nacheinander vor, beginnend bei der aktuellen Zeile. Drücken Sie Esc oder klicken Sie auf eine andere Zeile, um anzuhalten.", + "stopSpeaking": "Vorlesen beenden", + "speakingLineX": "Zeile {0} wird vorgelesen... (Esc zum Beenden)", + "playWithSpeechTitle": "Mit Sprachausgabe abspielen", + "playWithSpeechDotDotDot": "Mit Sprachausgabe abspielen...", + "playWithSpeechHint": "Spielt das Video ab und spricht jede Zeile, wenn das Video sie erreicht. Pausieren und springen Sie wie gewohnt; drücken Sie Esc zum Beenden.", + "lowerVideoVolumeWhileSpeaking": "Videolautstärke während der Sprachausgabe absenken", + "pauseVideoWhenSpeechRunsLate": "Video pausieren, wenn die Sprache in die nächste Zeile hineinläuft", + "waitingForSpeechLineX": "Sprache für Zeile {0} wird erzeugt... (Esc zum Beenden)", + "voiceKindClone": "Geklonte Stimme", + "voiceKindPreset": "Integriert", + "voiceKindModel": "Stimmenmodell", + "voiceKindOnline": "Online", + "transcript": "Transkript", + "transcriptRequiredHint": "Erforderlich - die Engine liest dieses Transkript der Aufnahme (ref-text) beim Klonen", + "transcriptOptionalHint": "Optional - ein genaues Transkript der Aufnahme verbessert das Klonen", + "saveTranscript": "Transkript speichern", + "searchVoices": "Stimmen suchen", + "loadingVoicesDotDotDot": "Stimmen werden geladen...", + "noVoicesForEngine": "Keine Stimmen - importieren Sie eine Aufnahme oder laden Sie ein Stimmenpaket herunter", + "noVoicesMatchFilter": "Keine Stimmen entsprechen der Suche", + "copyVoiceTo": "Stimme kopieren nach", + "copyVoiceToDotDotDot": "Stimme in Engine kopieren...", + "copyingVoiceXToYDotDotDot": "Stimme '{0}' wird nach {1} kopiert...", + "voiceXCopiedToY": "Stimme '{0}' nach {1} kopiert", + "voiceXCouldNotBeCopiedToY": "Stimme '{0}' konnte nicht nach {1} kopiert werden - siehe Protokoll für Details", + "voiceXAlreadyExistsInYContinue": "Eine Stimme namens '{0}' existiert bereits in {1}.\n\nTrotzdem kopieren?", + "openVoicesFolder": "Stimmenordner öffnen", + "downloadVoicePacksDotDotDot": "Stimmenpakete herunterladen...", + "downloadVoicePacksTitle": "TTS - Stimmenpakete herunterladen", + "voicePackIntro": "Stimmenpakete sind Sammlungen kurzer, sauberer Aufnahmen mit Transkripten, bereit zum Klonen. Jedes Paket wird in die gewählte Engine importiert (bereits vorhandene Stimmen werden übersprungen) und enthält eine ATTRIBUTION.txt mit Lizenz und Quellen.", + "voicePackDetailsXVoicesYSizeZLicense": "{0} Stimmen · {1} · {2}", + "installToEngine": "In Engine installieren", + "downloadAndInstall": "Herunterladen und installieren", + "selectAtLeastOneVoicePack": "Wählen Sie mindestens ein Stimmenpaket aus", + "downloadingXDotDotDot": "{0} wird heruntergeladen...", + "installingXDotDotDot": "{0} wird installiert...", + "xVoicesInstalledYSkipped": "{0} Stimmen installiert ({1} bereits vorhanden)", "useSpeechToTextDotDotDot": "Sprache-zu-Text verwenden...", "autoCastMenuItem": "Stimmen im Video finden und klonen...", "autoCastSpeakersTitle": "Im Video gefundene Stimmen", @@ -2588,7 +2780,7 @@ "cloneVoicePerLinePreparing": "Die Stimme jeder Zeile wird aus dem Video übernommen...", "cloneVoicePerLineNeedsVideo": "Zum Klonen der Stimme jeder Zeile wird das Video benötigt, zu dem der Untertitel gehört. Öffnen Sie das Video und versuchen Sie es erneut.", "cloneVoicePerLineNoClips": "Es konnte kein Audio aus dem Video übernommen werden, es gibt also nichts zum Klonen. Prüfen Sie, ob das Video eine Tonspur hat.", - "cloneVoicePerLineNeedsOriginalSubtitleX": "{0} kann eine Zeile nur klonen, wenn bekannt ist, was im Video an dieser Zeile gesagt wird - das kommt aus dem Untertitel in der Originalsprache. Es ist kein Originaluntertitel geladen, daher würde jede Zeile von der ersten importierten Stimme statt von der Stimme im Video gesprochen.\n\nÖffnen Sie den Originaluntertitel (Datei > Original öffnen...) neben der Übersetzung und erzeugen Sie ihn dann erneut.", + "cloneVoicePerLineTranscribeClipsXYZ": "{0} kann eine Zeile nur klonen, wenn bekannt ist, was im Video an dieser Zeile gesagt wird. Das ist für {1} von {2} Zeilen nicht bekannt - normalerweise stammt es aus dem Untertitel in der Originalsprache (Datei > Original öffnen...).\n\nDiese Zeilen jetzt mit Sprache-zu-Text aus dem Video transkribieren?\n\nJa: Wählen Sie die Engine und die im Video gesprochene Sprache, und die Referenzclips werden transkribiert, bevor die Sprache erzeugt wird.\nNein: Trotzdem erzeugen - diese Zeilen werden von einer gewöhnlichen Stimme statt der Stimme im Video gesprochen.", "voiceCloneConsentTitle": "Stimmklonen - bevor Sie beginnen", "voiceCloneConsentHeader": "Sie sind dabei, eine Stimme zu klonen", "voiceCloneConsentIntro": "Beim Klonen wird die Stimme einer realen Person kopiert. Das ist vielerorts mit Regeln verbunden und in der EU mit rechtlichen Pflichten, die Sie treffen, nicht Subtitle Edit. Bitte lesen Sie dies einmal durch.", @@ -2605,10 +2797,29 @@ "advancedTtsSettings": "Erweiterte TTS-Einstellungen", "advancedTtsAudioProcessing": "Audiobearbeitung", "advancedTtsOutput": "Ausgabe", + "advancedTtsVoiceCloning": "Stimmenklonen", + "cleanCloneReferences": "Referenzen für Stimmenklonen bereinigen (Sidon)", + "cleanCloneReferencesDescription": "Entfernt Musik, Rauschen und Hall aus dem Ton, von dem eine Stimme geklont wird: aus dem Video entnommene Clips und in der Stimmenverwaltung importierte Aufnahmen. Verwendet das Sidon-Sprachrestaurierungsmodell über audio.cpp (940 MB Download). Die geklonte Stimme übernimmt den Hintergrund nicht mehr, aber sehr leise oder überlappende Sprache kann dumpf klingen.", + "cleaningCloneReferencesDotDotDot": "Referenzen für Stimmenklonen werden bereinigt...", + "downloadSidonTitle": "Sidon herunterladen?", + "downloadSidonQuestionX": "Zum Bereinigen der Referenzen für Stimmenklonen wird das Sidon-Sprachrestaurierungsmodell ({0}) benötigt.\n\nJetzt herunterladen? Ohne es werden die Referenzen unverändert verwendet.", "proAudioPostProcessing": "Pro-Audio-Nachbearbeitung", "proAudioPostProcessingDescription": "Wendet EQ-Wärme, Noise Gate, Kompression, Lautheitsnormalisierung (-16 LUFS) und Ein-/Ausblenden auf jedes Segment an.", + "generatingSpeechSegmentXOfY": "Sprache wird erzeugt: Segment {0} von {1}", + "adjustingSpeedSegmentXOfY": "Geschwindigkeit wird angepasst: Segment {0} von {1}", + "mergingAudioSegmentXOfY": "Audio wird zusammengeführt: Segment {0} von {1}", + "postProcessingSegmentXOfY": "Nachbearbeitung: Segment {0} von {1}", "audioDucking": "Audio-Ducking", "audioDuckingDescription": "Reduziert die Lautstärke des Original-Videoaudios und mischt es mit dem TTS-Audio, sodass die Original-Tonspur noch leise hörbar ist.", + "removeOriginalSpeech": "Originalsprache entfernen (langsam)", + "removeOriginalSpeechDescription": "Behält Musik und Soundeffekte des Originalvideos, entfernt aber die Sprache und mischt die neue Sprache darüber. Dauert mit einer GPU etwa so lange wie das Video selbst und ohne GPU um ein Vielfaches länger. Bei aktiviertem Audio-Ducking gilt dessen Lautstärke für Musik und Effekte. Gilt nur, wenn die Sprache zur Videodatei hinzugefügt wird.", + "removingOriginalSpeech": "Originalsprache wird entfernt...", + "removeOriginalSpeechFailed": "Die Originalsprache konnte nicht entfernt werden - die Sprache wurde ohne diese Entfernung zum Video hinzugefügt.", + "speechRemovalModel": "Modell", + "speechRemovalModelDescription": "Beste Qualität erhält am meisten von Musik und Soundeffekten. Schnell ist etwa dreimal so schnell, wobei etwas mehr Musik verloren geht.", + "speechRemovalBestQuality": "Beste Qualität (Mel-Band RoFormer)", + "speechRemovalFast": "Schnell (HTDemucs)", + "videoTruncatedWarning": "Das Bild in der neuen Videodatei endet nach {0}, das Originalvideo ist aber {1} lang - das Bild friert ein, während der Ton weiterläuft.\n\nDie Datei wurde behalten. Siehe error-log.txt im Subtitle Edit-Datenordner für die ffmpeg-Ausgabe.", "originalVolumePercent": "Originallautstärke %", "vadSilenceCompression": "VAD-Stille-Kompression", "vadSilenceCompressionDescription": "Verkürzt Pausen zwischen Wörtern vor der Tempoänderung. Verwendet Voice Activity Detection, um nur Stillelücken zu komprimieren, während die Sprache unberührt bleibt. Dies ist der bevorzugte erste Schritt – er reduziert die Dauer ohne jeglichen Qualitätsverlust.", @@ -2691,7 +2902,9 @@ "timeCodeFormatColon": "Timecode-Format:", "shotChangesList": "Liste der Szenenwechsel", "shotChangesClearQuestion": "Sind Sie sicher, dass Sie alle Szenenwechsel löschen möchten?", - "deleteSelectedShotChangeQuestion": "Sind Sie sicher, dass Sie den ausgewählten Szenenwechsel löschen möchten?" + "deleteSelectedShotChangeQuestion": "Sind Sie sicher, dass Sie den ausgewählten Szenenwechsel löschen möchten?", + "shotChanges": "Szenenwechsel", + "exportShotChanges": "Szenenwechsel exportieren" }, "chapters": { "title": "Kapitel", @@ -2721,7 +2934,6 @@ "scaleTimesDescription": "Jede Kapitelzeit von einer Bildrate auf eine andere skalieren.", "fromFrameRate": "Von", "toFrameRate": "Bis", - "apply": "Anwenden", "writeToVideo": "In Video schreiben...", "writeToVideoTitle": "Kapitel in Video schreiben", "writeToVideoDescription": "Die Kapitel werden in eine Kopie der Videodatei geschrieben. Es wird nichts neu kodiert, daher geht dies schnell und verlustfrei.", @@ -2731,7 +2943,6 @@ "writtenToX": "Kapitel geschrieben nach {0}", "unableToWriteChapters": "Kapitel konnten nicht in die Videodatei geschrieben werden.", "chaptersSavedToX": "Kapitel gespeichert unter {0}", - "noVideoLoaded": "Kein Video geladen", "emptyListCallToAction": "Noch keine Kapitel - fügen Sie eines an der Videoposition hinzu oder importieren Sie sie aus einem Video oder einer Datei." }, "videoOcr": { @@ -2756,10 +2967,10 @@ "runningOcrXY": "OCR läuft... {0}/{1}", "noLinesFoundTitle": "Keine Untertitel gefunden", "noLinesFoundMessage": "Im Scanbereich wurde kein Text gefunden - passen Sie Scanbereich, Engine oder minimale Helligkeit an.", + "ocrStoppedEarlyMessage": "Die OCR-Engine hat angehalten, bevor alle Frames gelesen wurden, daher werden die bisher gefundenen Zeilen behalten und der Rest fehlt.{0}{0}{1}", "linesFoundX": "{0} Zeilen gefunden", "refiningTimingXY": "Timing wird verfeinert... {0}/{1}", "fixOcrErrors": "OCR-Fehler korrigieren", - "dictionary": "Wörterbuch", "editLineX": "Zeile {0} bearbeiten", "downloadDictionaryHint": "Ein Rechtschreibwörterbuch herunterladen...", "framesPerSecondHint": "Wie viele Frames pro Sekunde durchsucht werden. 5 deckt normale Untertitel ab; höhere Werte erfassen sehr kurze Zeilen und verbessern die Gruppierung, kosten aber mehr OCR-Zeit.", @@ -2776,7 +2987,14 @@ "testOcr": "Aktuellen Frame testen", "testOcrRunning": "Teste OCR auf aktuellem Frame...", "testOcrResultX": "Testergebnis: {0}", - "testOcrNoTextFound": "Test: kein Text im Scanbereich gefunden" + "testOcrNoTextFound": "Test: kein Text im Scanbereich gefunden", + "engineSettings": "OCR-Engine-Einstellungen", + "engineBuiltIn": "Im Betriebssystem integriert", + "engineExternalServer": "Externer Server (außerhalb von Subtitle Edit installiert und verwaltet)", + "engineCloudApi": "Cloud-API (erfordert einen API-Schlüssel)", + "engineNothingToInstall": "Nichts zu installieren", + "engineModelsMissing": "Engine installiert, Modelle fehlen", + "website": "Webseite" }, "backgroundMusic": { "title": "Hintergrundmusik generieren", @@ -2794,6 +3012,7 @@ "originalAudioVolumePercent": "Original-Lautstärke (%)", "saveAudioDotDotDot": "Audio speichern...", "addToVideoDotDotDot": "Zum Video hinzufügen...", + "engineInfo": "ACE-Step 1.5 Turbo über audio.cpp (MIT-Lizenz) - läuft lokal", "presetCooking": "Kochen / Backen", "presetTech": "Technik / Gadget-Review", "presetForest": "Wald / Natur", @@ -2830,10 +3049,47 @@ "openSecondarySubtitleOnVideoPlayerDotDotDot": "Sekundären Untertitel auf Videoplayer, öffnen...", "openSecondarySubtitleOnVideoPlayer": "Sekundärer Untertitel (auf Videoplayer)", "removeSecondarySubtitleOnVideoPlayer": "Sekundären Untertitel auf Videoplayer entfernen", + "editSecondarySubtitleSettings": "Einstellungen für zweiten Untertitel bearbeiten", + "editSecondarySubtitleSettingsDotDotDot": "Einstellungen für zweiten Untertitel bearbeiten...", + "secondarySubtitleRememberSettings": "Diese Einstellungen merken", + "secondarySubtitleDoNotShowAgain": "Diesen Dialog nicht mehr anzeigen", + "secondarySubtitleJustifyLines": "Zeilen ausrichten", "openRecentVideo": "Zuletzt verwendetes Video öffnen", "clearRecentVideos": "Zuletzt verwendete Videos leeren", "cutVideoTitle": "Video schneiden", "cutVideoDotDotDot": "Video schneiden...", + "remuxVideoTitle": "Video remuxen", + "remuxVideoDotDotDot": "Video remuxen...", + "remuxVideoInputVideo": "Eingabevideo:", + "remuxVideoMultipleTracksRequiresMkv": "Mehrere Audio- oder Untertitelspuren erfordern den MKV-Container. Das Ausgabeformat wurde automatisch auf .mkv umgestellt.", + "remuxVideoOutputFormat": "Ausgabeformat:", + "remuxVideoOutputFile": "Ausgabedatei:", + "remuxVideoRemuxing": "Video und Audio werden remuxt...", + "remuxVideoFinalizing": "Ausgabedatei wird abgeschlossen (Index wird an den Anfang verschoben)...", + "remuxVideoCompleted": "Video erfolgreich remuxt.", + "remuxVideoFailed": "Video konnte nicht remuxt werden.", + "remuxVideoPleaseSelectBoth": "Bitte wählen Sie sowohl Video- als auch Audiodateien aus.", + "remuxVideoAssRequiresMkv": "ASS/SSA-Untertitel erfordern den MKV-Container, um alle Formatierungen und Stile zu erhalten.", + "remuxVideoSccRequiresMov": "Scenarist-Untertitel (.scc) werden als CEA-608-Closed-Captions eingebettet, die den MOV- oder MPG-Container erfordern. Das Ausgabeformat wurde automatisch auf .mov umgestellt.", + "remuxVideoSccNotWithWebM": "Scenarist-Closed-Captions (.scc) benötigen den MOV-Container, der das VP9/AV1-Video einer .webm-Datei nicht aufnehmen kann. Entfernen Sie die .scc-Datei oder konvertieren Sie sie in ein anderes Untertitelformat.", + "remuxVideoMpgMaxTwoCaptionTracks": "Eine .mpg-Datei enthält höchstens zwei Closed-Caption-Spuren (CC1 und CC3) - entfernen Sie Untertiteldateien, sodass eine oder zwei übrig bleiben.", + "remuxVideoMccRequiresMpg": "MacCaption-Untertitel (.mcc) werden als CEA-608/708-Closed-Captions im MPEG-2-Video eingebettet, was den MPG-Container erfordert. Das Ausgabeformat wurde automatisch auf .mpg umgestellt.", + "remuxVideoMpgReencodeVideoX": "Closed Captions in einer .mpg-Datei benötigen MPEG-2-Video, und das Video ist {0}. Das Video in MPEG-2 neu kodieren? Dies dauert länger und verringert die Qualität ein wenig.", + "remuxVideoFileHasNoAudioX": "\"{0}\" hat keine Audiospur und kann daher nicht als Audioquelle verwendet werden.", + "remuxVideoAddingClosedCaptions": "Closed Captions werden hinzugefügt...", + "remuxVideoSelectAudioTrack": "Audiospur auswählen", + "remuxVideoSelectAudioTrackFor": "Audiospur für {0} auswählen", + "remuxVideoSelectAudioTrackPrompt": "Die Datei '{0}' enthält {1} Audiospuren. Bitte wählen Sie die zu remuxende Audiospur aus:", + "remuxVideoVideoFile": "Video", + "remuxVideoAudioFile": "Audio", + "remuxVideoAudioFiles": "Audiodateien:", + "remuxVideoSubtitleFiles": "Untertiteldateien (optional, Soft-Untertitel):", + "remuxVideoSelectAudioTrackDotDotDot": "Audiospur auswählen...", + "remuxVideoFilesX": "{0} Dateien", + "remuxVideoMixAudio": "Audio in eine Spur mischen", + "remuxVideoVolumePercent": "Lautstärke %:", + "remuxVideoVolumeX": "Lautstärke {0}%", + "remuxVideoFastStart": "Für Web-Streaming optimieren (Index nach vorne verschieben - langsamer)", "embedSubtitlesDotDotDot": "Eingebettete Untertitel hinzufügen/entfernen...", "generateTimeCodes": "Zeitstempel erzeugen", "checkeredImage": "Schachbrett-Bild", @@ -2843,6 +3099,34 @@ "cutVideoSplitSegments": "Segmente einzeln speichern", "cutVideoAlsoCutSubtitle": "Untertitel ebenfalls schneiden", "cutVideoSubtitleFileGeneratedX": "Untertiteldatei erzeugt: {0}", + "cutVideoTransition": "Übergang zwischen Segmenten", + "cutVideoTransitionDurationSeconds": "Dauer (Sekunden)", + "cutVideoFadeInSeconds": "Aus Schwarz einblenden am Anfang (Sekunden)", + "cutVideoFadeOutSeconds": "Zu Schwarz ausblenden am Ende (Sekunden)", + "cutVideoPreviewTransition": "Übergang in der Vorschau", + "cutVideoPreviewTransitionTitle": "Übergangsvorschau", + "cutVideoPreviewNeedsJoin": "Es gibt keine Verbindungsstelle für die Vorschau - fügen Sie mindestens zwei Segmente zum Zusammenführen hinzu oder ein Segment, das aus der Mitte herausgeschnitten wird.", + "cutVideoTransitionInfoX": "Jeder Übergang überlappt die Teile, die er verbindet, daher wird die Ausgabe pro Verbindungsstelle {0} Sekunden kürzer. Ein geschnittener Untertitel wird entsprechend neu getimt.", + "cutVideoTransitionFade": "Überblenden", + "cutVideoTransitionFadeBlack": "Über Schwarz blenden", + "cutVideoTransitionFadeWhite": "Über Weiß blenden", + "cutVideoTransitionFadeGrays": "Über Grau blenden", + "cutVideoTransitionDissolve": "Auflösen", + "cutVideoTransitionWipeLeft": "Wischen nach links", + "cutVideoTransitionWipeRight": "Wischen nach rechts", + "cutVideoTransitionWipeUp": "Wischen nach oben", + "cutVideoTransitionWipeDown": "Wischen nach unten", + "cutVideoTransitionSlideLeft": "Schieben nach links", + "cutVideoTransitionSlideRight": "Schieben nach rechts", + "cutVideoTransitionSlideUp": "Schieben nach oben", + "cutVideoTransitionSlideDown": "Schieben nach unten", + "cutVideoTransitionSmoothLeft": "Weich nach links", + "cutVideoTransitionSmoothRight": "Weich nach rechts", + "cutVideoTransitionCircleOpen": "Kreis öffnen", + "cutVideoTransitionCircleClose": "Kreis schließen", + "cutVideoTransitionRadial": "Radial", + "cutVideoTransitionPixelize": "Verpixeln", + "cutVideoTransitionBlur": "Unschärfe", "importCurrentSubtitle": "Aktuellen Untertitel importieren", "addRemoveEmbeddedSubtitlesTitle": "Eingebettete Untertitel hinzufügen/entfernen", "addRemoveEmbeddedSubtitlesMp4Title": "Eingebettete Untertitel hinzufügen/entfernen (MP4)", @@ -2858,6 +3142,7 @@ "embeddedTrackGeneratingVideoXY": "Video wird erzeugt... {0}% {1}", "embeddedTrackGeneratingVideo": "Video wird erzeugt...", "mp4FilesFilter": "MP4-Dateien", + "embeddedTracksPickVideoHint": "Wählen Sie eine Videodatei oder ziehen Sie eine hierher, um ihre Untertitelspuren anzuzeigen.", "viewMatroskaTrackX": "Matroska-Spur anzeigen - {0}", "resolutionSeparator": "x", "openFromUrlTitle": "Videodatei von URL öffnen", @@ -2904,12 +3189,15 @@ "reEncodeGeneratingDone": "Erzeugung abgeschlossen", "reEncodeGeneratedFilesX": "Erzeugte Dateien ({0}):", "reEncodeFfmpegParameters": "ffmpeg-Parameter", + "analyzingVideoX": "Video wird analysiert... {0}% {1}", + "analyzingVideoXofY": "Video {0}/{1} wird analysiert... {2}% {3}", + "backgroundImageFileNotSelected": "Keine Hintergrundbilddatei ausgewählt", + "backgroundImageFileDoesNotExistX": "Hintergrundbilddatei existiert nicht: {0}", "voices": "Stimmen", "presets": "Vorgaben", "talkerX": "Sprecher {0}", "baseModel": "Basismodell", "turboModel": "Turbo-Modell", - "ttsUpdateX": "{0} aktualisieren", "ttsEngineNotInstalled": "{0} nicht installiert", "ttsEngineUpdateAvailable": "{0} - Update verfügbar", "ttsEngineTooOldUpdateRequired": "{0} zu alt - Update erforderlich", @@ -2932,10 +3220,17 @@ "cloneVoiceTo": "Stimme klonen nach", "cloneVoiceNameTitle": "Name der neuen Stimme", "cloneVoiceExtractFailed": "Für die ausgewählte Zeile konnte kein Audio extrahiert werden.", + "lineXHasNoDuration": "Zeile {0} hat keine Dauer (die Endzeit liegt nicht nach der Startzeit), daher gibt es keinen Ton zum Extrahieren.", + "audioClipsSkippedLinesX": "Für diese Zeilen konnte kein Ton extrahiert werden, daher wurden sie übersprungen: {0}", "toggleShotChange": "Szenenwechsel umschalten", "resetWaveformZoomAndSpeed": "Wellenform-Zoom und -Geschwindigkeit zurücksetzen", "showOriginalSubtitle": "Original-Untertitel anzeigen", "timelineGroupTracksBy": "Untertitelspuren gruppieren nach", + "timelineShowThumbnails": "Video-Miniaturansichten anzeigen", + "showSpeechOnly": "Nur Sprache anzeigen (langsam zu erzeugen)", + "isolatingSpeechForWaveformX": "Sprache für die Wellenform wird isoliert... {0}", + "isolatingSpeechForWaveformFailed": "Die Sprache für die Wellenform konnte nicht isoliert werden - siehe Werkzeugprotokoll", + "speechOnlyWaveformReady": "Die Wellenform zeigt jetzt nur Sprache", "showOnlyWaveform": "Nur Wellenform anzeigen", "showOnlySpectrogram": "Nur Spektrogramm anzeigen", "showWaveformAndSpectrogram": "Wellenform und Spektrogramm anzeigen", @@ -2968,6 +3263,7 @@ "adjustSelectedLinesAndForward": "Ausgewählte Zeile(n) und weiter", "toFrameRate": "zu Bildrate", "fromFrameRate": "von Bildrate", + "videoXFrameRateY": "{0} - {1} fps", "adjustAllTimes": "Alle Zeiten anpassen (früher/später anzeigen)", "showEarlier": "Früher anzeigen", "showLater": "Später anzeigen", @@ -2979,7 +3275,6 @@ "syncPointCandidateAfterInfo": "Zeilen mit anschließender Stille von {0}+ Sekunden sind hervorgehoben – sie dienen oft als verlässliche Synchronisationspunkte.", "pointSyncViaOther": "Punkt-Synchronisation über anderen Untertitel", "gapAfter": "Pause danach", - "syncPointCandidateInfo": "Zeilen, auf die mindestens {0}+ Sekunden Stille folgt, werden hervorgehoben – diese eignen sich oft als zuverlässige Synchronisationspunkte.", "adjustAllShortcuts": "Tastenkombination:\n\n• Umschalt + Links/Rechts: 10 ms verschieben\n• Strg + Links/Rechts: 100 ms verschieben\n• Alt + Links/Rechts: 500 ms verschieben", "adjustAllShortcutsFrames": "Tastenkombinationen:\n\n• Umschalt + Links/Rechts: 1 Frame verschieben\n• Strg + Links/Rechts: 10 Frames verschieben\n• Alt + Links/Rechts: 1 Sekunde verschieben", "offsetInSeconds": "Versatz in Sekunden", @@ -3013,11 +3308,10 @@ "readyToTranslate": "Bereit zum Übersetzen", "translating": "Übersetzen...", "translationComplete": "Übersetzung abgeschlossen", + "translateInPlaceNoOriginal": "Direkt übersetzen (kein Original erstellen)", "translationCancelled": "Übersetzung abgebrochen", "swapLanguages": "Quell- und Zielsprache tauschen", "xIsAlreadyDownloadedReDownload": "{0} ist bereits heruntergeladen. Erneut herunterladen?", - "advancedDotDotDot": "Erweitert...", - "advancedSettings": "Erweiterte Einstellungen", "advancedSettingsSubtitle": "Batch-, Kontext- und Sampling-Optionen für die erweiterte llama.cpp-Engine", "context": "Kontext", "synopsis": "Inhaltsangabe", @@ -3041,6 +3335,7 @@ "useOnlyExtraServerParametersHint": "llama-server mit den obigen Parametern statt mit der eigenen Abstimmung von Subtitle Edit starten - nur Modell, Host und Port werden weiterhin gesetzt", "serverRunningAtX": "Server läuft auf {0}", "customPromptHint": "Eigene Anweisungen ({0} = Quellsprache, {1} = Zielsprache); leer = integrierter Prompt", + "editTranslationHint": "Klicken Sie auf die Übersetzung der ausgewählten Zeile, um sie zu bearbeiten (Enter speichert, Esc bricht ab)", "llamaCppDownloadEngineAndModelPrompt": "llama.cpp erfordert das Herunterladen der llama-server-Engine und eines Übersetzungsmodells. Jetzt herunterladen?", "llamaCppDownloadEnginePrompt": "llama.cpp erfordert das Herunterladen der llama-server-Engine. Jetzt herunterladen?", "llamaCppDownloadModelPrompt": "llama.cpp erfordert das Herunterladen des ausgewählten Übersetzungsmodells. Jetzt herunterladen?" @@ -3223,8 +3518,6 @@ "videoControlsPositionText": "Anzeige Position/Dauer", "videoControlsVideoFileName": "Video-Dateiname", "videoControlsPlayerName": "Videoplayer-Name", - "showStopButton": "Stopp Knopf anzeigen", - "showFullscreenButton": "Vollbild Knopf anzeigen", "fullscreenHideControls": "Bedienelemente bei Vollbild ausblenden", "showSecondarySubtitleDialog": "Stil-Dialog beim Öffnen einer zweiten Untertiteldatei anzeigen", "rememberSecondarySubtitleFile": "Zweite Untertiteldatei merken", @@ -3279,10 +3572,11 @@ "timeCodeModeTime": "Zeit (hh:mm:ss,zzz)", "timeCodeModeFrames": "Frames (hh:mm:ss:ff)", "timeCodeModeFrameNumbers": "Frame-Nummern", - "useFrameMode": "Bild-Modus verwenden (hh.mm.ss.ff)", + "frameModeSnapToVideoFrames": "Zeiten auf Videoframes speichern (nicht Frames innerhalb jeder Sekunde)", "textBoxLimitNewLines": "Anzahl der Zeilen im Textfeld begrenzen", "mpvOpenGl": "libmpv - OpenGL", "mpvSoftwareRendering": "libmpv – Software-Rendering (langsam)", + "mpvMetalRendering": "libmpv - Metal (experimentell, Neustart erforderlich)", "mpvWidRendering": "libmpv – Natives Fenster-ID-Rendering", "waveFormsAndSpectrogramFoldersContainsX": "Die Wellenform und Spektrogramm-Ordner enthalten {0}", "deleteWaveformAndSpectrogramFoldersQuestion": "\"Wellenform\"- und \"Spektrogramm\"-Dateien löschen?", @@ -3297,7 +3591,7 @@ "waveformExtractAudioBitRate": "Audio-Bitrate bei Extraktion (MP3/M4A)", "ffmpegUseCenterChannelOnly": "Nur Center-Kanal für 5.1-Audio verwenden (FFmpeg)", "vlcWidRendering": "libVLC - Rendering über native Fenster-ID", - "ffmpegSoftwareRendering": "FFmpeg", + "ffmpegSoftwareRendering": "FFmpeg", "downloadFfmpegLibs": "FFmpeg-Bibliotheken herunterladen (für den FFmpeg-Videoplayer)", "subtitleGridEnterKeyAction": "Aktion bei Enter im Untertitelraster", "subtitleSingleClickAction": "Einfachklick-Aktion im Untertitelraster", @@ -3356,6 +3650,7 @@ "useFocusedButtonBackgroundColor": "Hintergrundfarbe für fokussierte Schaltflächen verwenden", "focusedButtonBackgroundColor": "Hintergrundfarbe für fokussierte Schaltflächen", "forceCrLfOnSave": "CR+LF beim Speichern erzwingen (Textuntertitel)", + "linuxClipboardUseExternalTool": "Text über xclip/wl-copy kopieren (behält Akzentzeichen)", "showFormatLimitWarning": "Beim Speichern vor Überschreitung der Formatgrenzen warnen (z. B. SCC 32 Zeichen/Zeile)", "textBoxButtonShowAutoBreak": "Textfeld: Schaltfläche für automatischen Zeilenumbruch anzeigen", "textBoxButtonShowUnbreak": "Textfeld: Schaltfläche für Zeilenumbruch entfernen anzeigen", @@ -3389,7 +3684,14 @@ "waveformLoadThemeDotDotDot": "Thema laden...", "waveformExportThemeDotDotDot": "Thema exportieren...", "waveformSaveCustomThemeTitle": "Benutzerdefiniertes Thema speichern", - "waveformThemeHighContrast": "Hoher Kontrast" + "waveformThemeFileType": "Subtitle Edit-Wellenform-Design", + "waveformThemeHighContrast": "Hoher Kontrast", + "waveformThemeOceanBlue": "Ozeanblau", + "waveformThemeWarmSunset": "Warmer Sonnenuntergang", + "waveformThemeForest": "Wald", + "waveformThemeMidnightPurple": "Mitternachtslila", + "waveformThemeRetroAmber": "Retro-Bernstein", + "waveformThemeIce": "Eis" }, "shortcuts": { "searchShortcuts": "Tastenkombinationen durchsuchen...", @@ -3400,6 +3702,28 @@ "categoryVideo": "Video", "categorySync": "Synchronisation", "categoryAi": "KI", + "categoryCustom": "Benutzerdefiniert", + "editCustomShortcut": "Benutzerdefinierte Tastenkombination bearbeiten", + "customShortcutTextBoxKeyHint": "Verwenden Sie hier eine Taste mit Strg/Alt - eine einfache Taste würde im Textfeld nicht mehr tippen.", + "customShortcutNumberX": "Benutzerdefinierte Tastenkombination #{0}", + "customShortcutNumberXY": "Benutzerdefinierte Tastenkombination #{0}: {1}", + "customShortcutStep": "Schritt", + "customShortcutSteps": "Schritte", + "customShortcutStepRunCommand": "Befehl ausführen", + "customShortcutStepInsertText": "Text einfügen", + "customShortcutStepReplace": "Suchen und ersetzen", + "customShortcutInsertAtCursor": "An Cursorposition", + "customShortcutInsertAtStart": "Am Textanfang", + "customShortcutInsertAtEnd": "Am Textende", + "customShortcutSummaryRunCommandX": "Ausführen: {0}", + "customShortcutSummaryInsertXY": "\"{0}\" einfügen ({1})", + "customShortcutSummaryReplaceXY": "\"{0}\" durch \"{1}\" ersetzen", + "customShortcutSummaryReplaceRegexXY": "Regex \"{0}\" durch \"{1}\" ersetzen", + "customShortcutPickCommand": "Bitte wählen Sie einen auszuführenden Befehl aus.", + "customShortcutEnterText": "Bitte geben Sie den einzufügenden Text ein.", + "customShortcutEnterFind": "Bitte geben Sie den zu suchenden Text ein.", + "customShortcutInvalidRegexX": "Ungültiger regulärer Ausdruck: {0}", + "customShortcutStepsHint": "Schritte werden von oben nach unten ausgeführt. Textschritte wirken auf die ausgewählten Zeilen; Befehle, die ein Fenster öffnen, werden nach dessen Schließen fortgesetzt.", "activeIn": "Aktiv in", "activeInEverywhere": "Überall", "generalMergeSelectedLinesAndUnbreak": "Ausgewählte Zeilen zusammenführen und aufheben", @@ -3427,6 +3751,7 @@ "fileOpenKeepVideo": "Öffnen (Video behalten)", "fileSave": "Speichern", "fileSaveAs": "Speichern unter", + "fileSaveOriginal": "Original speichern", "fileCompare": "Vergleichen", "fileStatistics": "Statistik", "fileImportPlainText": "Nur-Text importieren", @@ -3447,6 +3772,7 @@ "detectKey": "Taste erkennen", "control": "Steuerung", "alt": "Alt", + "altGr": "AltGr", "win": "Win", "shift": "Shift", "controlMac": "⌃", @@ -3459,6 +3785,9 @@ "toggleLockTimeCodes": "Sperrzeitcodes umschalten", "toggleTimeCodeMode": "Zeitcode-Modus umschalten (Zeit/Frames/Frame-Nummern)", "toggleScreenPrivacy": "Bildschirm-Privatsphäre umschalten (Dateinamen ausblenden, dann auch Texte)", + "screenPrivacyFileNamesHidden": "Bildschirm-Privatsphäre: Dateinamen ausgeblendet", + "screenPrivacyFileNamesAndTextsHidden": "Bildschirm-Privatsphäre: Dateinamen und Texte ausgeblendet", + "screenPrivacyOff": "Bildschirm-Privatsphäre: aus", "duplicateSelectedLines": "Zeile duplizieren", "sourceView": "Quelltext anzeigen", "addOrEditBookmark": "Lesezeichen hinzufügen oder bearbeiten", @@ -3477,6 +3806,7 @@ "moveFirstWordFromNextLineUpCurrentSubtitle": "Erstes Wort von der nächsten Zeile nach oben verschieben (aktueller Untertitel)", "moveTextFromCursorToNextAndGoToNext": "Text nach Cursorposition in nächsten Untertitel verschieben und zum nächsten gehen", "moveTextFromCursorToNextAndGoToNextAndPlay": "Text nach Cursorposition in nächsten Untertitel verschieben, zum nächsten gehen und abspielen", + "moveTextFromCursorToNextAndGoToNextAndPlayAndPause": "Text nach Cursorposition in nächsten Untertitel verschieben, zum nächsten gehen, abspielen und an seinem Ende pausieren", "breakAtFirstSpaceFromCursor": "Am ersten Leerzeichen ab Cursorposition umbrechen", "breakAtFirstSpaceFromCursorAndGoToNext": "Am ersten Leerzeichen ab Cursorposition umbrechen und zur nächsten gehen", "toggleFocusGridAndWaveform": "Fokus zwischen Raster und Wellenform/Spektrogramm umschalten", @@ -3506,6 +3836,7 @@ "listViewColumnTextUp": "Spalte, Text nach oben", "listViewColumnTextDown": "Spalte, Text nach unten", "autoTranslateSelectedLines": "Ausgewählte Zeilen automatisch übersetzen...", + "autoTranslateSelectedLinesNoPrompt": "Ausgewählte Zeilen automatisch übersetzen (ohne Nachfrage, letzte Engine/Sprachen verwenden)", "setAssaResolution": "ASSA-Auflösung setzen (PlayResX/PlayResY)", "setShortcutForX": "Tastenkombination für \"{0}\" festlegen", "commandFileNewKeepVideo": "Öffnen (Video behalten)", @@ -3560,6 +3891,7 @@ "textBoxItalic": "Textfeld kursiv", "textBoxBold": "Textfeld fett", "textBoxUnderline": "Textfeld unterstrichen", + "textBoxBox": "Textfeld-Box (EBU STL)", "resetWaveformZoomAndSpeed": "Wiedergabegeschwindigkeit und Wellenform-Zoom zurücksetzen", "togglePlaybackSpeed": "Wiedergabegeschwindigkeit umschalten", "playbackSpeedSlower": "Wiedergabegeschwindigkeit langsamer (Abspielrate)", @@ -3571,6 +3903,7 @@ "seekSilenceForward": "Stille vorwärts suchen", "waveformGuessStart": "Startzeit aus Wellenform schätzen", "waveformGuessEnd": "Endzeit aus Wellenform schätzen", + "waveformGuessStartAndEnd": "Start- und Endzeit aus Wellenform schätzen", "setVideoPositionCurrentSubtitleStart": "Videoposition auf den Beginn des aktuellen Untertitels setzen", "goToSubtitlePositionAndPause": "Zur Untertitelposition gehen und pausieren", "setVideoPositionCurrentSubtitleEnd": "Videoposition zwischen Start/Ende des aktuellen Untertitels umschalten", @@ -3584,6 +3917,14 @@ "surroundWith": "Umgeben mit...", "surroundWithXY": "Umschließen mit {0}/{1}", "surroundWithNumberX": "Umschließen mit #{0}", + "surroundWithBehavior": "Verhalten", + "surroundWithBehaviorToggle": "Umschalten (hinzufügen oder entfernen, falls vorhanden)", + "surroundWithBehaviorAdd": "Hinzufügen (jedes Mal)", + "surroundWithBehaviorRemove": "Entfernen (alle)", + "surroundWithBehaviorRemoveOnce": "Entfernen (jeweils eins)", + "surroundWithWorksOn": "Wirkt auf", + "surroundWithScopeSelectionOrText": "Auswahl, sonst ganzer Text", + "surroundWithScopeEachLine": "Jede Zeile", "searchVia": "Suchen über", "searchViaX": "Suchen über {0}", "searchViaNumberX": "Suchen über #{0}", @@ -3611,6 +3952,11 @@ "copySubtitlePathToClipboard": "Untertitelpfad in Zwischenablage kopieren", "copySubtitleOriginalPathToClipboard": "Untertitelpfad des Originals in Zwischenablage kopieren", "focusTextBox": "Textfeld fokussieren", + "focusSubtitleListView": "Untertitelliste fokussieren", + "focusWaveform": "Wellenform fokussieren", + "focusOriginalTextBox": "Original-Textfeld fokussieren", + "textBoxGoToStart": "Textfeld, zum Anfang gehen", + "textBoxGoToEnd": "Textfeld, zum Ende gehen", "sortByNumber": "Nach Nummer sortieren", "sortByStartTime": "Nach \"Einblenden\"-Zeit sortieren", "sortByEndTime": "Nach \"Ausblenden\"-Zeit sortieren", @@ -3648,9 +3994,9 @@ "selectionToSentenceCase": "Textfeld: Auswahl in Satzschreibweise", "selectionToggleCasing": "Textfeld: Schreibweise umschalten (GROSS -> klein -> Titelschreibweise)", "googleIt": "Google es (ausgewählter Text)", - "setActorXY": "Darsteller {0} setzen: {1}", + "setActorX": "Darsteller festlegen: {0}", "setNewActor": "Neuen Darsteller setzen...", - "setActorSlotXTitle": "Darsteller-Slot {0}", + "setStyleX": "Stil festlegen: {0}", "toggleDialogDashes": "Dialog-Bindestriche umschalten", "mergeWithLineBeforeAsDialog": "Mit Zeile davor als Dialog zusammenführen", "mergeWithLineAfterAsDialog": "Mit Zeile danach als Dialog zusammenführen", @@ -3661,7 +4007,23 @@ "moveStartOneFrameBackKeepGapPrev": "Start ein Frame zurück verschieben (Abstand zum vorherigen beibehalten, falls nah)", "moveStartOneFrameForwardKeepGapPrev": "Start ein Frame vorwärts verschieben (Abstand zum vorherigen beibehalten, falls nah)", "moveEndOneFrameBackKeepGapNext": "Ende ein Frame zurück verschieben (Abstand zum nächsten beibehalten, falls nah)", - "moveEndOneFrameForwardKeepGapNext": "Ende ein Frame vorwärts verschieben (Abstand zum nächsten beibehalten, falls nah)" + "moveEndOneFrameForwardKeepGapNext": "Ende ein Frame vorwärts verschieben (Abstand zum nächsten beibehalten, falls nah)", + "moveStartXMsBack": "Start X ms zurück verschieben (X in Einstellungen festgelegt, auch in Visueller Anpassung)", + "moveStartXMsForward": "Start X ms vorwärts verschieben (X in Einstellungen festgelegt, auch in Visueller Anpassung)", + "moveEndXMsBack": "Ende X ms zurück verschieben (X in Einstellungen festgelegt, auch in Visueller Anpassung)", + "moveEndXMsForward": "Ende X ms vorwärts verschieben (X in Einstellungen festgelegt, auch in Visueller Anpassung)", + "moveSelectedLinesXMsBack": "Ausgewählte Zeilen X ms zurück verschieben (X in Einstellungen festgelegt)", + "moveSelectedLinesXMsForward": "Ausgewählte Zeilen X ms vorwärts verschieben (X in Einstellungen festgelegt)", + "moveSelectedLinesAndForwardXMsBack": "Ausgewählte und alle nachfolgenden Zeilen X ms zurück verschieben (X in Einstellungen festgelegt)", + "moveSelectedLinesAndForwardXMsForward": "Ausgewählte und alle nachfolgenden Zeilen X ms vorwärts verschieben (X in Einstellungen festgelegt)", + "moveAllLinesXMsBack": "Alle Zeilen X ms zurück verschieben (X in Einstellungen festgelegt)", + "moveAllLinesXMsForward": "Alle Zeilen X ms vorwärts verschieben (X in Einstellungen festgelegt)", + "moveSelectedLinesCustomMsBack": "Ausgewählte Zeilen verschieben, benutzerdefinierte Millisekunden ({0:#,###,##0}) zurück, {1}", + "moveSelectedLinesCustomMsForward": "Ausgewählte Zeilen verschieben, benutzerdefinierte Millisekunden ({0:#,###,##0}) vorwärts, {1}", + "moveSelectedLinesAndForwardCustomMsBack": "Ausgewählte und alle nachfolgenden Zeilen verschieben, benutzerdefinierte Millisekunden ({0:#,###,##0}) zurück, {1}", + "moveSelectedLinesAndForwardCustomMsForward": "Ausgewählte und alle nachfolgenden Zeilen verschieben, benutzerdefinierte Millisekunden ({0:#,###,##0}) vorwärts, {1}", + "moveAllLinesCustomMsBack": "Alle Zeilen verschieben, benutzerdefinierte Millisekunden ({0:#,###,##0}) zurück, {1}", + "moveAllLinesCustomMsForward": "Alle Zeilen verschieben, benutzerdefinierte Millisekunden ({0:#,###,##0}) vorwärts, {1}" }, "wordLists": { "title": "Wortlisten", @@ -3754,6 +4116,8 @@ "nOcrDatabase": "nOCR-Datenbank", "drawMode": "Zeichenmodus:", "addNewCharcter": "Neues Zeichen hinzufügen", + "addNOcrCharacterTitle": "nOCR-Zeichen für Zeile {0} hinzufügen, Zeichen {1} von {2}, mit Datenbank \"{3}\"", + "addBinaryOcrCharacterTitle": "Binary-OCR-Zeichen für Zeile {0} hinzufügen, Zeichen {1} von {2}, mit Datenbank \"{3}\"", "lineIndexX": "Zeile {0}", "inspectNOcrAdditions": "nOCR-Ergänzungen prüfen", "ocrSelectedLines": "OCR auf ausgewählte Zeilen", @@ -3862,9 +4226,26 @@ "crispEmbedSettingsTitle": "CrispEmbed-Einstellungen", "crispEmbedDescription": "Lokale OCR-Engine mit mehreren Modell-Backends - laden Sie die Engine und die Modelle hier herunter.", "trainNOcrDatabase": "nOCR-Datenbank trainieren...", + "optimizeNOcrDatabase": "nOCR-Datenbank optimieren...", + "optimizeNOcrDatabaseQuestionXY": "Die Linien entfernen, die Zeichen nicht von ähnlichen Zeichen in der nOCR-Datenbank \"{0}\" unterscheiden?\n\nDadurch wird die Datenbank kleiner und die OCR schneller, meist mit weniger Fehlern. Dauert bis zu einer Minute.\n\nEine Sicherung wird als \"{1}\" gespeichert.", + "optimizingNOcrDatabaseXY": "nOCR-Datenbank wird optimiert... {0}/{1}", + "nOcrDatabaseOptimizedXYZ": "nOCR-Datenbank \"{0}\" optimiert: {1:#,##0} -> {2:#,##0} Linien.", "startTraining": "Training starten", "abortTraining": "Training abbrechen", "trainingOptions": "Trainingsoptionen", + "trainNOcrSubtitle": "Rendert Zeichen in installierten Schriftarten und bringt nOCR ihre Formen bei. Wählen Sie die Schriftarten, die Ihre Untertitel verwenden.", + "xFontsSelected": "{0:#,##0} ausgewählt", + "subtitleFonts": "Untertitel-Schriftarten", + "emptyDatabase": "Leere Datenbank", + "startFromDatabase": "Ausgehend von", + "startFromDatabaseHint": "Die trainierten Zeichen zu einer Kopie einer vorhandenen Datenbank hinzufügen (empfohlen) oder mit einer leeren beginnen", + "fontSizes": "Schriftgrößen", + "fontSizesHint": "Schriftgrößen in Pixeln zum Rendern, durch Kommas getrennt, z. B. \"30, 40\"", + "numberOfLineSegmentsHint": "Geprüfte Linien pro Zeichen - mehr ist strenger, aber langsamer", + "enterDatabaseName": "Geben Sie einen Namen für die Datenbank ein", + "trainingFontXSizeYCharacterZ": "Training \"{0}\" mit {1} px: {2}", + "xLearned": "{0:#,##0} gelernt", + "xSkipped": "{0:#,##0} übersprungen", "charactersToTrain": "Zu trainierende Zeichen", "letterCombinationsToTrain": "Buchstabenkombinationen, die zu einem Bild zusammengefasst sein könnten", "importCharactersFromSubtitleFile": "Zeichen aus Untertiteldatei importieren...", @@ -3891,6 +4272,8 @@ "drawZoomOut": "Herauszoomen (Strg+-)", "drawResetView": "Ansicht zurücksetzen (Strg+0)", "drawToggleGrid": "Raster umschalten (Strg+G)", + "drawTogglePreview": "Vorschau umschalten (F9)", + "drawPreviewFailed": "Vorschau fehlgeschlagen - ffmpeg mit libass (subtitles-Filter) ist erforderlich", "drawCopyToClipboard": "In Zwischenablage kopieren (Strg+C)", "drawShapes": "Formen", "drawSelectedPoint": "Ausgewählter Punkt", @@ -3898,6 +4281,81 @@ "drawSelectedLayer": "Ausgewählte Ebene", "drawToolX": "Werkzeug: {0}", "drawHelpText": "Klicken, um Punkte hinzuzufügen • Eingabe/F8 zum Schließen der Form • Umschalt+Ziehen zum Schwenken • Strg+Scrollen zum Zoomen", + "drawMoveToLayer": "In Ebene verschieben", + "drawFlipHorizontal": "Horizontal spiegeln", + "drawFlipVertical": "Vertikal spiegeln", + "drawHideShape": "Form ausblenden", + "drawShowShape": "Form einblenden", + "drawHideLayer": "Ebene ausblenden", + "drawShowLayer": "Ebene einblenden", + "drawDeleteLayer": "Ebene löschen", + "drawDeletePoint": "Punkt löschen", + "drawCancelDrawing": "Zeichnen abbrechen", + "drawLayerX": "Ebene {0}", + "drawImportSvg": "SVG-Bild importieren...", + "drawRotateClockwise": "Um 90° im Uhrzeigersinn drehen", + "drawConvertToCurve": "Segment in Kurve umwandeln", + "drawBackground": "Hintergrund", + "drawPosition": "Position", + "drawZoom": "Zoom", + "drawShapeTool": "Formwerkzeug", + "drawColorPickerTool": "Farbe von einer Form oder dem Hintergrund aufnehmen", + "drawShapeLibrary": "Formenbibliothek", + "drawShapeLibraryHint": "Ziehen Sie auf der Zeichenfläche, um eine Form zu platzieren, oder klicken Sie für die Standardgröße. Umschalt behält die Proportionen bei.", + "drawAddToLibrary": "Zur Formenbibliothek hinzufügen...", + "drawRemoveFromLibrary": "Aus Formenbibliothek entfernen", + "drawShapeName": "Formname", + "drawCategorySpeechBubbles": "Sprechblasen", + "drawCategoryArrows": "Pfeile", + "drawCategoryBasicShapes": "Grundformen", + "drawCategorySymbols": "Symbole", + "drawCategoryMyShapes": "Meine Formen", + "drawShapeSpeechBubble": "Sprechblase", + "drawShapeSpeechBubbleRight": "Sprechblase, Spitze rechts", + "drawShapeSpeechBubbleUp": "Sprechblase, Spitze oben", + "drawShapeOvalBubble": "Ovale Sprechblase", + "drawShapeBoxBubble": "Rechteckige Sprechblase", + "drawShapeThoughtBubble": "Gedankenblase", + "drawShapeShoutBubble": "Schreiblase", + "drawShapeCaptionBox": "Textkasten", + "drawShapeArrow": "Pfeil", + "drawShapeDoubleArrow": "Doppelpfeil", + "drawShapeChevron": "Winkelpfeil", + "drawShapeCurvedArrow": "Gebogener Pfeil", + "drawShapeRoundedRectangle": "Abgerundetes Rechteck", + "drawShapeTriangle": "Dreieck", + "drawShapeDiamond": "Raute", + "drawShapePentagon": "Fünfeck", + "drawShapeHexagon": "Sechseck", + "drawShapeOctagon": "Achteck", + "drawShapeStar": "Stern", + "drawShapeRing": "Ring", + "drawShapePlus": "Plus", + "drawShapeBanner": "Banner", + "drawShapeHeart": "Herz", + "drawShapeCheck": "Häkchen", + "drawShapeMusicNote": "Musiknote", + "drawShapeMusicNotes": "Musiknoten", + "drawShapeLightning": "Blitz", + "drawShapeCloud": "Wolke", + "drawShapeMoon": "Mond", + "drawShapeBadge": "Abzeichen", + "drawSize": "Größe", + "drawLayer": "Ebene", + "drawBackgroundVideoFrame": "Videoframe (aktuelle Position)", + "drawBackgroundVideoFrameAt": "Videoframe bei...", + "drawBackgroundImage": "Bilddatei...", + "drawBackgroundNone": "Kein Hintergrund", + "drawBackgroundStretch": "Bild auf Zeichenfläche strecken", + "drawBackgroundOpacity": "Hintergrund-Deckkraft", + "drawBackgroundVideoFrameFailed": "Konnte keinen Videoframe bei {0} abrufen.", + "drawImages": "Bilder", + "drawConvertToLine": "Segment in Linie umwandeln", + "drawConvertShapeToCurves": "Alle Linien in Kurven umwandeln", + "drawConvertShapeToLines": "Alle Kurven in Linien umwandeln", + "drawRotateCounterClockwise": "Um 90° gegen den Uhrzeigersinn drehen", + "drawSvgImages": "SVG-Bilder", + "drawSvgNoShapes": "Keine gefüllten Formen oder Konturen in der SVG-Datei gefunden.", "progressBarTitle": "ASSA-Fortschrittsbalken", "progressBarSettings": "Fortschrittsbalken", "mouseOverColor": "Farbe bei Mauszeiger darüber", @@ -3982,7 +4440,7 @@ "attachmentsTitleX": "Anhänge – {0}", "smartWrappingTopWide": "0: Smart Wrapping (obere Zeile breiter)", "endOfLineWrapping": "1: Zeilenende-Wortumbruch, nur \\N bricht", - "noWrapping": "2: Kein Zeilenumbruch, sowohl \\n als auch \\N bricht\t\t", + "noWrapping": "2: Kein Zeilenumbruch, sowohl \\n als auch \\N bricht", "smartWrappingBottomWide": "3: Smart Wrapping (untere Zeile breiter)", "fontsAndGraphics": "Schriftarten und Bilder", "wrapStyle": "Umbruchstil", @@ -3994,13 +4452,26 @@ "graphics": "Grafiken", "copyToStorageStyles": "In Speicherstile kopieren", "copyToFileStyles": "In Dateistile kopieren", + "styleXAlreadyExistsInStorage": "Ein Stil namens \"{0}\" existiert bereits im Speicher.\n\nÜberschreiben oder beide behalten (die Kopie erhält einen neuen Namen)?", + "styleXAlreadyExistsInFile": "Ein Stil namens \"{0}\" existiert bereits in der Datei.\n\nÜberschreiben oder beide behalten (die Kopie erhält einen neuen Namen)?", + "styleNameCannotBeEmpty": "Ein Stilname darf nicht leer sein.", + "styleNameXIsUsedMoreThanOnce": "Der Stilname \"{0}\" wird in der Datei von mehr als einem Stil verwendet.", + "overwrite": "Überschreiben", + "keepBoth": "Beide behalten", + "doThisForAllConflictingStyles": "Für alle Stile mit Konflikt so verfahren", "setStyleAsDefault": "Als Standard einstellen", "takeUsagesFromDotDotDot": "Übernehme Verwendungen von...", "replaceStyleWithDotDotDot": "Stil ersetzen durch...", "noAttachmentsFound": "Keine Anhänge in ausgewählter ASSA-Datei gefunden.", "deleteStyleQuestion": "Stil löschen?", "deleteStylesQuestion": "Stile löschen?", + "deleteXStylesQuestion": "Möchten Sie {0} Stile löschen?", + "deleteXStylesFromStorageQuestion": "Möchten Sie {0} Stile aus dem Speicher löschen?", + "deleteStyleXFromStorageQuestion": "Möchten Sie den Stil \"{0}\" aus dem Speicher löschen?", + "deleteStyleXFromFileQuestion": "Möchten Sie den Stil \"{0}\" aus der aktuellen Datei löschen?", "openStyleImportFile": "Untertiteldatei, aus der Stile importiert werden sollen öffnen", + "styleImportFiles": "Stildateien (.ass, .sty, Subtitle Edit 4-Kategorien .template)", + "ssaStyleImportFiles": "Stildateien (.ssa, .ass, .sty)", "primary": "Primär", "secondary": "Sekundär", "applyOverrideTags": "Override-Tags anwenden", diff --git a/src/ui/Assets/Languages/Greek.json b/src/ui/Assets/Languages/Greek.json index e1ada4e4093..da25f9325ba 100644 --- a/src/ui/Assets/Languages/Greek.json +++ b/src/ui/Assets/Languages/Greek.json @@ -1,12 +1,30 @@ { "title": "Subtitle Edit", - "version": "v5.2.0-rc7", + "version": "v5.3.0", "translatedBy": "Claude (Anthropic)", "cultureName": "el-GR", "general": { "abort": "Ματαίωση", "actor": "Ηθοποιός", "actors": "Ηθοποιοί", + "setActorDotDotDot": "Ορισμός ηθοποιού...", + "actorPickerTitle": "Ορισμός ηθοποιού", + "actorPickerFilterHint": "Πληκτρολογήστε για φιλτράρισμα ή πληκτρολογήστε νέο όνομα ηθοποιού", + "actorPickerKeysHint": "Πατήστε έναν αριθμό ή Enter για να ορίσετε τον ηθοποιό. Το Alt+Πάνω/Κάτω αλλάζει τη σειρά, το Delete αφαιρεί τον ηθοποιό.", + "actorPickerNewActorX": "Νέος ηθοποιός \"{0}\"", + "actorPickerLinesSelectedX": "Επιλεγμένες γραμμές: {0}", + "setStyleDotDotDot": "Ορισμός στυλ...", + "stylePickerTitle": "Ορισμός στυλ", + "stylePickerFilterHint": "Πληκτρολογήστε για φιλτράρισμα ή πληκτρολογήστε νέο όνομα στυλ", + "stylePickerKeysHint": "Πατήστε έναν αριθμό (1-9, 0) για να ορίσετε το στυλ σε όλες τις επιλεγμένες γραμμές.", + "stylePickerNewStyleX": "Νέο στυλ \"{0}\"", + "stylePickerNewStyleInfo": "Πατήστε Enter για να προσθέσετε αυτό το στυλ με προεπιλεγμένες ρυθμίσεις και να το ορίσετε στις επιλεγμένες γραμμές. Χρησιμοποιήστε τη \"Διαχείριση στυλ...\" για να αλλάξετε αργότερα τη γραμματοσειρά, τα χρώματα και τη θέση του.", + "stylePickerCurrentStyleX": "Τρέχον στυλ: {0}", + "stylePickerLinesUsingStyleX": "Γραμμές που χρησιμοποιούν αυτό το στυλ: {0}", + "stylePickerMarginsXYZ": "Αριστερά {0}, δεξιά {1}, κατακόρυφα {2}", + "stylePickerSetStyle": "Ορισμός στυλ", + "stylePickerManageStyles": "Διαχείριση στυλ...", + "stylePickerManageStylesHint": "Κλείσιμο αυτού του παραθύρου και άνοιγμα της διαχείρισης στυλ για επεξεργασία γραμματοσειρών, χρωμάτων, θέσης και άλλων", "add": "Προσθήκη", "addDotDotDot": "Προσθήκη...", "addToNamesListCaseSensitive": "Προσθήκη στη λίστα ονομάτων (διάκριση πεζών-κεφαλαίων)", @@ -15,7 +33,6 @@ "adjustAlpha": "Προσαρμογή άλφα (διαφάνεια)", "adjustDisplayDuration": "Προσαρμογή διάρκειας εμφάνισης", "adjustment": "Προσαρμογή", - "advanced": "Για προχωρημένους", "advancedSettings": "Ρυθμίσεις για προχωρημένους", "after": "Μετά", "alignment": "Στοίχιση", @@ -60,7 +77,6 @@ "bdnXml": "BDN/xml", "bdnXml8Bit": "BDN/xml 8-bit", "before": "Πριν", - "beforeX": "Πριν από \"{0}\"", "beginning": "Αρχή", "bluRaySup": "Blu-ray (sup)", "bold": "Έντονα", @@ -72,7 +88,6 @@ "bookmarkEdit": "Επεξεργασία σελιδοδείκτη", "bookmarkEditForSelectedLinesX": "Επεξεργασία σελιδοδείκτη για {0} επιλεγμένες γραμμές", "bookmarksList": "Λίστα σελιδοδεικτών", - "borderColor": "Χρώμα περιγράμματος", "borderStyle": "Στυλ περιγράμματος", "bottomCenter": "Κάτω-Κέντρο", "bottomLeft": "Κάτω-Αριστερά", @@ -99,7 +114,6 @@ "changeCasing": "Αλλαγή πεζών-κεφαλαίων", "changeFormatting": "Αλλαγή μορφοποίησης", "changeFrameRate": "Αλλαγή ρυθμού καρέ", - "changeLanguageFilter": "Αλλαγή φίλτρου γλώσσας...", "changeOnce": "Αλλαγή μία φορά", "changeSpeed": "Αλλαγή ταχύτητας", "character": "Χαρακτήρας", @@ -141,7 +155,6 @@ "conversionCancelledByUser": "Η μετατροπή ακυρώθηκε από τον χρήστη", "convert": "Μετατροπή", "converted": "Μετατράπηκε", - "convertingDotDotDot": "Μετατροπή...", "convertingXofYDotDoDot": "Μετατροπή {0:#,###,##0}/{1:#,###,##0}...", "copy": "Αντιγραφή", "copyImageToClipboard": "Αντιγραφή εικόνας στο πρόχειρο", @@ -152,7 +165,6 @@ "couldNotOpenFileXErrorY": "Αδυναμία ανοίγματος του αρχείου \"{0}\". Σφάλμα: {1}", "count": "Πλήθος", "cps": "Χαρ./δευτ.", - "currentSubtitle": "Τρέχων υπότιτλος", "cudaRuntime": "Χρόνος εκτέλεσης CUDA", "currentVideoPosition": "Τρέχουσα θέση βίντεο", "custom": "Προσαρμοσμένο", @@ -212,7 +224,6 @@ "expand": "Ανάπτυξη", "export": "Εξαγωγή", "exportDotDotDot": "Εξαγωγή...", - "exportToX": "Εξαγωγή σε {0}", "extendAfter": "Επέκταση/σμίκρυνση έως την επόμενη γραμμή", "extendBefore": "Επέκταση/σμίκρυνση έως την προηγούμενη γραμμή", "extendSelectedLinesToNextShotChangeOrNextSubtitle": "Επέκταση επιλεγμένων γραμμών έως την επόμενη αλλαγή πλάνου (ή τον επόμενο υπότιτλο)", @@ -229,7 +240,6 @@ "fileExtension": "Επέκταση αρχείου", "fileName": "Όνομα αρχείου", "fileNameX": "Όνομα αρχείου: {0}", - "fileNameXAndSize": "Όνομα αρχείου: {0} ({1})", "fileSaved": "Το αρχείο αποθηκεύτηκε", "fileSavedToX": "Το αρχείο αποθηκεύτηκε στο {0}", "fileXAlreadyExists": "Το αρχείο \"{0}\" υπάρχει ήδη.", @@ -239,8 +249,6 @@ "find": "Εύρεση", "findNext": "Εύρεση επόμενου", "findTextX": "Εύρεση κειμένου - {0}", - "fiveHundredMilliseconds": "500 χιλιοστά του δευτερολέπτου", - "fiveSeconds": "Πέντε δευτερόλεπτα", "fix": "Διόρθωση", "fixCommonErrors": "Διόρθωση συνηθισμένων σφαλμάτων", "fixRightToLeft": "Διόρθωση δεξιά-προς-αριστερά", @@ -264,14 +272,12 @@ "foundXInLineYZ": "Βρέθηκε \"{0}\" στη γραμμή {1}, θέση {2}", "foundXMatches": "Βρέθηκαν {0} αποτελέσματα", "frameRate": "Ρυθμός καρέ", - "frameRateX": "Ρυθμός καρέ: {0:0.0###}", "frames": "Καρέ", "from": "Από", "fromCurrentVideoPosition": "από την τρέχουσα θέση βίντεο", "fullScreen": "Πλήρης οθόνη", "gap": "Κενό", "general": "Γενικά", - "generalText": "Γενικά", "generate": "Δημιουργία", "generateImportShotChanges": "Δημιουργία/εισαγωγή αλλαγών πλάνου", "generating": "Δημιουργία...", @@ -289,12 +295,8 @@ "help": "Βοήθεια", "hex": "Hex", "hide": "Απόκρυψη", - "hidePreview": "Απόκρυψη προεπισκόπησης", "history": "Ιστορικό", "horizontal": "Οριζόντια", - "hourMinutesSecondsDecimalSeparatorMilliseconds": "Ώρα:λεπ:δευτ{0}ms", - "hourMinutesSecondsFrames": "Ώρα:λεπ:δευτ:καρέ", - "ip": "IP", "id": "ID", "image": "Εικόνα", "imageSaved": "Η εικόνα αποθηκεύτηκε", @@ -355,6 +357,7 @@ "mergeSelectedLinesDialog": "Συγχώνευση επιλεγμένων γραμμών ως διάλογο", "mergeWithLineAfterAndAutoBreak": "Συγχώνευση με την επόμενη γραμμή και αυτόματη αλλαγή γραμμής", "mergeWithLineAfterKeepBreaks": "Συγχώνευση με την επόμενη γραμμή (διατήρηση αλλαγών γραμμής)", + "mergeWithLineAfterAndUnbreak": "Συγχώνευση με την επόμενη γραμμή και αφαίρεση αλλαγής γραμμής", "mergeWithLineBeforeAndAutoBreak": "Συγχώνευση με την προηγούμενη γραμμή και αυτόματη αλλαγή γραμμής", "mergeWithLineBeforeKeepBreaks": "Συγχώνευση με την προηγούμενη γραμμή (διατήρηση αλλαγών γραμμής)", "middleCenter": "Μέση-Κέντρο", @@ -369,13 +372,13 @@ "moveAllShotChangeOneFrameForward": "Μετακίνηση όλων των αλλαγών πλάνου ένα καρέ εμπρός", "multiMode": "Πολλαπλή λειτουργία", "moveDown": "Μετακίνηση κάτω", + "increase": "Αύξηση", + "decrease": "Μείωση", "moveUp": "Μετακίνηση πάνω", "moveToTop": "Μετακίνηση στην κορυφή", "moveToBottom": "Μετακίνηση στο τέλος", "multipleReplace": "Πολλαπλή αντικατάσταση", - "mute": "Σίγαση", "name": "Όνομα", - "negative": "Αρνητικό", "new": "Νέο", "newDotDotDot": "Νέο...", "newProfile": "Νέο προφίλ", @@ -405,20 +408,15 @@ "offsetY": "Μετατόπιση Y", "ok": "OK", "oneFile": "Ένα αρχείο", - "oneHundredMilliseconds": "100 χιλιοστά του δευτερολέπτου", - "oneSecond": "Ένα δευτερόλεπτο", "oneSecondBack": "Ένα δευτερόλεπτο πίσω", "oneSecondForward": "Ένα δευτερόλεπτο εμπρός", - "onlineVideoFeatureNotAvailable": "Η λειτουργία δεν είναι διαθέσιμη για διαδικτυακό βίντεο", "openContainingFolder": "Άνοιγμα φακέλου περιεχομένου", "openDictionaryFolder": "Άνοιγμα φακέλου λεξικών", "openFile": "Άνοιγμα αρχείου", "openImageBasedSubtitle": "Άνοιγμα υπότιτλου βασισμένου σε εικόνα", "openImageFile": "Άνοιγμα αρχείου εικόνας", - "openOriginalSubtitleFile": "Άνοιγμα αρχικού αρχείου υπότιτλων...", "openOriginalSubtitleFileTitle": "Άνοιγμα αρχικού αρχείου υπότιτλων", "openOutputFolder": "Άνοιγμα φακέλου εξόδου", - "openSubtitle": "Άνοιγμα υπότιτλου...", "openSubtitleFileTitle": "Άνοιγμα αρχείου υπότιτλων", "openSubtitles": "Άνοιγμα υπότιτλων...", "openVideoFile": "Άνοιγμα αρχείου βίντεο...", @@ -433,11 +431,6 @@ "outlineWidth": "Πλάτος περιγράμματος", "outputFolder": "Φάκελος εξόδου", "outputProperties": "Ιδιότητες εξόδου", - "overlap": "Επικάλυψη", - "overlapNextX": "Επικάλυψη επόμενου ({0:#;##0.###})", - "overlapPreviousLineX": "Επικάλυψη προηγούμενης γραμμής ({0:#;##0.###})", - "overlapStartAndEnd": "Επικάλυψη αρχής και τέλους", - "overlapX": "Επικάλυψη ({0:#;##0.###})", "overwriteExistingFiles": "Αντικατάσταση υπαρχόντων αρχείων", "overwriteFilesInFolderX": "Αντικατάσταση αρχείων στον φάκελο: \"{0}\"", "overwriteQuestion": "Αντικατάσταση;", @@ -450,6 +443,7 @@ "percent": "Ποσοστό", "pickLayer": "Ορισμός επιπέδου", "pickOllamaModel": "Επιλογή μοντέλου Ollama", + "pickModel": "Επιλογή μοντέλου", "llamaCppUseRemoteServer": "Χρήση εξωτερικού διακομιστή (URL)", "llamaCppEngineSettings": "Ρυθμίσεις μηχανής llama.cpp", "llamaCppEngineSettingsSubtitle": "Τοπική έκδοση διακομιστή llama.cpp", @@ -501,7 +495,6 @@ "refresh": "Ανανέωση", "region": "Περιοχή", "regularExpression": "Κανονική έκφραση", - "regularExpressionIsNotValid": "Η κανονική έκφραση δεν είναι έγκυρη!", "relativeToCurrentVideoPosition": "Σχετικά με την τρέχουσα θέση βίντεο", "remove": "Αφαίρεση", "removeAlignment": "Αφαίρεση στοίχισης", @@ -527,7 +520,7 @@ "reset": "Επαναφορά", "resizePanels": "Αλλαγή μεγέθους πινάκων", "resolution": "Ανάλυση", - "resolutionX": "Ανάλυση: {0}", + "retry": "Επανάληψη", "reverseRightToLeftStartEnd": "Αντιστροφή αρχής/τέλους RTL", "right": "Δεξιά", "rightToLeft": "Δεξιά προς αριστερά", @@ -567,8 +560,6 @@ "selectedlinesX": "Επιλεγμένες γραμμές: {0}", "sensitivity": "Ευαισθησία", "separator": "Διαχωριστικό", - "sessionKey": "Κλειδί συνεδρίας", - "sessionKeyGenerate": "Δημιουργία νέου κλειδιού", "setEnd": "Ορισμός τέλους", "setEndAndGoToNext": "Ορισμός τέλους και μετάβαση στο επόμενο", "setEndAddNewAndGoToNew": "Ορισμός τέλους, προσθήκη νέου και μετάβαση στο νέο", @@ -591,8 +582,9 @@ "shadowColor": "Χρώμα σκιάς", "shadowWidth": "Πλάτος σκιάς", "shiftLineBy": "Μετατόπιση γραμμής κατά", + "shotIn": "Είσοδος πλάνου", + "shotOut": "Έξοδος πλάνου", "shortcut": "Συντόμευση", - "shortcutX": "Συντόμευση: {0}", "shortcuts": "Συντομεύσεις", "show": "Εμφάνιση", "showActorColumn": "Εμφάνιση στήλης \"Ηθοποιός\"", @@ -603,17 +595,19 @@ "showStartColumn": "Εμφάνιση στήλης \"Έναρξη\"", "showHideColumn": "Εμφάνιση στήλης \"Απόκρυψη\"", "showHistory": "Εμφάνιση ιστορικού", + "clearHistory": "Εκκαθάριση ιστορικού", + "removeFromHistory": "Αφαίρεση από το ιστορικό", "showLayerColumn": "Εμφάνιση στήλης \"Επίπεδο\"", "showPreview": "Εμφάνιση προεπισκόπησης", "showShotChangesList": "Εμφάνιση λίστας αλλαγών πλάνου", + "showShotInColumn": "Εμφάνιση στήλης \"Είσοδος πλάνου\"", + "showShotOutColumn": "Εμφάνιση στήλης \"Έξοδος πλάνου\"", "showStyleColumn": "Εμφάνιση στήλης \"Στυλ\"", "showTeletext": "Εμφάνιση στήλης teletext στην προβολή λίστας", "showTimeCodes": "Εμφάνιση χρονικών κωδίκων", "showWpmColumn": "Εμφάνιση στήλης \"Λέξεις/λεπτό\"", "showPixelWidthColumn": "Εμφάνιση στήλης \"Πλάτος σε pixel\"", "shrink": "Σμίκρυνση", - "singleBox": "Ενιαίο πλαίσιο", - "singleLineLengths": "Μήκος μίας γραμμής:", "singleMode": "Απλή λειτουργία", "textTotalLength": "Κείμενο - συνολικό μήκος", "textSingleLineMaxLength": "Κείμενο - μέγιστο μήκος μίας γραμμής", @@ -662,16 +656,16 @@ "strikeout": "Διακριτή διαγραφή", "style": "Στυλ", "styleExaggeration": "Υπερβολή στυλ", - "styleLanguage": "Στυλ / Γλώσσα", "styles": "Στυλ", "subtitleFile": "Αρχείο υπότιτλων", + "subtitleFileChangedOutsideX": "Το αρχείο \"{0}\" άλλαξε εκτός του Subtitle Edit.\n\nΝα επαναφορτωθεί με απώλεια των αλλαγών σας;", "subtitleFileName": "Όνομα αρχείου υπότιτλων", "subtitleFileSaved": "Το αρχείο υπότιτλων αποθηκεύτηκε", "subtitleFileSavedToX": "Το αρχείο υπότιτλων αποθηκεύτηκε στο {0}", "subtitleFiles": "Αρχεία υπότιτλων", "subtitleFormats": "Μορφές υπότιτλων", "subtitleLoadedX": "Υπότιτλος φορτώθηκε: {0}", - "subtitleXOfY": "Υπότιτλος {0} από {1}", + "subtitleReloadedChangedOutsideX": "Ο υπότιτλος επαναφορτώθηκε (άλλαξε εκτός του Subtitle Edit): {0}", "suffix": "Επίθημα", "suggestions": "Προτάσεις", "sync": "Συγχρονισμός", @@ -683,7 +677,6 @@ "teletextColor": "Χρώμα teletext", "teletextLine": "Γραμμή teletext", "tenHours": "10 ώρες", - "tenMilliseconds": "10 χιλιοστά του δευτερολέπτου", "text": "Κείμενο", "textColor": "Χρώμα κειμένου", "textFiles": "Αρχεία κειμένου", @@ -710,9 +703,6 @@ "topRight": "Πάνω-Δεξιά", "top": "Πάνω", "totalAdjustmentX": "Συνολική προσαρμογή: {0}", - "totalFramesX": "Συνολικά καρέ: {0:#;##0.##}", - "totalLengthX": "Συνολικό μήκος: {0}", - "totalLengthXSplitLine": "Συνολικό μήκος: {0} (διαιρέστε τη γραμμή!)", "translate": "Μετάφραση", "translatePercentX": "Μετάφραση: {0}%", "translateRow": "Μετάφραση σειράς", @@ -721,6 +711,7 @@ "type": "Τύπος", "unbreak": "Αναίρεση αλλαγής γραμμής", "unbreakLines": "Αναίρεση αλλαγών γραμμής", + "undelete": "Αναίρεση διαγραφής", "underline": "Υπογράμμιση", "undo": "Αναίρεση", "unknown": "Άγνωστο", @@ -740,13 +731,11 @@ "usages": "Χρήσεις", "use": "Χρήση", "useAlways": "Χρήση πάντα", - "useLargerFontForThisWindow": "Χρήση μεγαλύτερης γραμματοσειράς για αυτό το παράθυρο", "useOnce": "Χρήση μία φορά", "useOutputFolder": "Χρήση φακέλου εξόδου", "useSourceFolder": "Χρήση φακέλου πηγής", "useSourceResolution": "Χρήση ανάλυσης πηγής", "userName": "Όνομα χρήστη", - "userNameAlreadyInUse": "Το όνομα χρήστη χρησιμοποιείται ήδη", "vertical": "Κατακόρυφα", "video100MsBack": "Βίντεο, 100 χιλιοστά του δευτερολέπτου πίσω", "video100MsForward": "Βίντεο, 100 χιλιοστά του δευτερολέπτου εμπρός", @@ -760,14 +749,25 @@ "videoCustom3ForwardX": "Βίντεο, προσαρμοσμένα χιλιοστά ({0:#,###,##0}) εμπρός, 3", "videoCustom4BackX": "Βίντεο, προσαρμοσμένα χιλιοστά ({0:#,###,##0}) πίσω, 4", "videoCustom4ForwardX": "Βίντεο, προσαρμοσμένα χιλιοστά ({0:#,###,##0}) εμπρός, 4", - "videoEncodingX": "Κωδικοποίηση βίντεο: {0}", + "videoCustom1BackAndPauseX": "Βίντεο, προσαρμοσμένα χιλιοστά δευτερολέπτου ({0:#,###,##0}) πίσω και παύση, 1", + "videoCustom1ForwardAndPauseX": "Βίντεο, προσαρμοσμένα χιλιοστά δευτερολέπτου ({0:#,###,##0}) εμπρός και παύση, 1", + "videoCustom2BackAndPauseX": "Βίντεο, προσαρμοσμένα χιλιοστά δευτερολέπτου ({0:#,###,##0}) πίσω και παύση, 2", + "videoCustom2ForwardAndPauseX": "Βίντεο, προσαρμοσμένα χιλιοστά δευτερολέπτου ({0:#,###,##0}) εμπρός και παύση, 2", + "videoCustom3BackAndPauseX": "Βίντεο, προσαρμοσμένα χιλιοστά δευτερολέπτου ({0:#,###,##0}) πίσω και παύση, 3", + "videoCustom3ForwardAndPauseX": "Βίντεο, προσαρμοσμένα χιλιοστά δευτερολέπτου ({0:#,###,##0}) εμπρός και παύση, 3", + "videoCustom4BackAndPauseX": "Βίντεο, προσαρμοσμένα χιλιοστά δευτερολέπτου ({0:#,###,##0}) πίσω και παύση, 4", + "videoCustom4ForwardAndPauseX": "Βίντεο, προσαρμοσμένα χιλιοστά δευτερολέπτου ({0:#,###,##0}) εμπρός και παύση, 4", "videoExtension": "Επέκταση αρχείου βίντεο", "videoFile": "Αρχείο βίντεο", "videoFileGenerated": "Το αρχείο βίντεο δημιουργήθηκε", "videoFileGeneratedX": "Δημιουργήθηκε αρχείο βίντεο: \"{0}\"", + "videoFilesGenerated": "Τα αρχεία βίντεο δημιουργήθηκαν", + "xVideosGenerated": "Δημιουργήθηκαν {0} βίντεο", + "xOfYVideosGenerated": "Δημιουργήθηκαν {0} από {1} βίντεο", + "savedInX": "Αποθηκεύτηκε στο {0}", + "savedInXFolders": "Αποθηκεύτηκε σε {0} φακέλους", "videoFiles": "Αρχεία βίντεο", "videoAndAudioFiles": "Αρχεία βίντεο και ήχου", - "videoInformation": "Πληροφορίες βίντεο", "videoOffset": "Μετατόπιση βίντεο", "videoOneFrameBack": "Βίντεο, ένα καρέ πίσω", "videoOneFrameForward": "Βίντεο, ένα καρέ εμπρός", @@ -778,7 +778,6 @@ "videoPlayer": "Πρόγραμμα αναπαραγωγής βίντεο", "videoPosition": "Θέση βίντεο", "videoResolution": "Ανάλυση βίντεο", - "viewX": "Προβολή {0}", "visible": "Ορατό", "voice": "Φωνή", "volume": "Ένταση", @@ -787,9 +786,7 @@ "waveformCenterOnVideoPosition": "Κεντράρισμα κυματομορφής στη θέση βίντεο", "waveformPasteFromClipboard": "Επικόλληση από το πρόχειρο", "waveformSpectrogram": "Κυματομορφή/φασματογράφημα", - "webServiceUrl": "URL υπηρεσίας web", "width": "Πλάτος", - "windowPositionAndSize": "Θέση και μέγεθος παραθύρου", "wordsPerMin": "Λέξεις/λεπτό", "pixelWidth": "Πλάτος σε pixel", "wpm": "Λέξεις/λεπτό", @@ -803,8 +800,6 @@ "noBookmarksFound": "Δεν βρέθηκαν άλλοι σελιδοδείκτες", "xRequiresAnApiKey": "Το {0} απαιτεί κλειδί API", "xRequiresAValidUrl": "Το {0} απαιτεί έγκυρο URL (π.χ. \"http://...\" ή \"https://...\")", - "xSeconds": "{0:0.0##} δευτερόλεπτα", - "xSubtitles": "{0:#,###,##0} υπότιτλοι", "yes": "Ναι", "zeroBytes": "0 byte", "copyToClipboard": "Αντιγραφή στο πρόχειρο", @@ -826,10 +821,8 @@ "playbackSpeed": "Ταχύτητα αναπαραγωγής", "hoursMinutesSecondsMilliseconds": "ΩΩ:ΛΛ:ΔΔ.FFF", "imageFiles": "Αρχεία εικόνας", - "openAiCompatibleStt": "STT συμβατό με OpenAI", "openAiCompatibleSttEndpoint": "URL τελικού σημείου STT", "openAiCompatibleSttApiKey": "Κλειδί API", - "openAiCompatibleSttModel": "Μοντέλο", "openAiCompatibleSttExtraHeaders": "Πρόσθετες κεφαλίδες", "openAiCompatibleSttTimeout": "Χρονικό όριο (δευτερόλεπτα)", "openAiCompatibleSttLanguage": "Υπόδειξη γλώσσας", @@ -839,6 +832,9 @@ "dashScopeSttRegion": "Περιοχή", "dashScopeSttEnableWords": "Χρονικές σημάνσεις σε επίπεδο λέξης", "dashScopeSttRegionKeyHint": "Σημείωση: Τα κλειδιά API του Alibaba Cloud Model Studio είναι συγκεκριμένα ανά περιοχή - βεβαιωθείτε ότι η επιλεγμένη περιοχή ταιριάζει με την περιοχή όπου δημιουργήθηκε το κλειδί API (Κίνα έναντι Διεθνούς).", + "googleCloudSttProjectId": "Αναγνωριστικό έργου (μόνο για Application Default Credentials)", + "googleCloudSttBucketName": "Κάδος αποθήκευσης (κενό = αυτόματα)", + "googleCloudSttDynamicBatching": "Δυναμική ομαδοποίηση (περίπου 5x φθηνότερη, χωρίς εγγύηση καθυστέρησης)", "onlineSttApiKeyMissing": "Απαιτείται κλειδί API. Παρακαλώ εισαγάγετε το κλειδί API σας και δοκιμάστε ξανά.", "openAiCompatibleSttAutoTranscribeOnAudioSelection": "Αυτόματη μεταγραφή νέας επιλογής κυματομορφής μέσω ομιλίας-σε-κείμενο", "openAiCompatibleSttStream": "Ροή απόκρισης", @@ -848,11 +844,9 @@ "configurationRequired": "Απαιτείται διαμόρφωση", "transcriptionError": "Σφάλμα μεταγραφής", "transcriptionComplete": "Η μεταγραφή ολοκληρώθηκε", - "transcribingWithOpenAi": "Μεταγραφή με υπηρεσία OpenAI...", "processingResponse": "Επεξεργασία απόκρισης...", "unauthorizedApiKey": "Μη εξουσιοδοτημένο: Μη έγκυρο κλειδί API ή ανεπαρκή δικαιώματα.", "requestTimeout": "Λήξη χρονικού ορίου αιτήματος: Ο διακομιστής άργησε πολύ να αποκριθεί.", - "noSegmentsReturned": "Δεν επιστράφηκαν τμήματα από την υπηρεσία μεταγραφής.", "openAiCompatibleSttUrlMissing": "Απαιτείται URL τελικού σημείου STT συμβατού με OpenAI. Παρακαλώ εισαγάγετε ένα URL και δοκιμάστε ξανά.", "openAiCompatibleSttUrlNotResponding": "Το τελικό σημείο STT συμβατό με OpenAI δεν αποκρίθηκε:\n{0}\n\nΠαρακαλώ επαληθεύστε το URL και ότι ο διακομιστής εκτελείται.", "transcriptionFailed": "Η μεταγραφή απέτυχε", @@ -898,14 +892,15 @@ "multipleReplace": "Πολλαπλή αντικατάσταση...", "fillSelectedLinesWithClipboard": "Συμπλήρωση επιλεγμένων γραμμών με κείμενο προχείρου", "goToLineNumber": "Μετάβαση σε αριθμό γραμμής...", + "commandPalette": "Παλέτα εντολών...", "rightToLeftMode": "Λειτουργία δεξιά-προς-αριστερά", "modifySelectionDotDotDot": "Τροποποίηση επιλογής...", "tools": "Εργαλεία", - "toolsSelectedLines": "Εργαλεία (επιλεγμένες γραμμές)", "adjustDurations": "Προσαρμογή διαρκειών...", "applyDurationLimits": "Εφαρμογή ορίων διάρκειας...", "batchConvert": "Μαζική μετατροπή...", "beautifyTimeCodes": "Καλλωπισμός χρονικών κωδίκων...", + "improveTimeCodes": "Βελτίωση χρονικών κωδίκων (εξαναγκασμένη ευθυγράμμιση)...", "bridgeGaps": "Γεφύρωση κενών...", "applyMinGap": "Εφαρμογή ελάχιστου κενού μεταξύ υπότιτλων...", "changeCasing": "Αλλαγή πεζών-κεφαλαίων...", @@ -1044,6 +1039,9 @@ "selectCurrentLineWhilePlayingHint": "Επιλογή τρέχοντος υπότιτλου κατά την αναπαραγωγή {0}", "videoPosition": "Θέση βίντεο {0}", "videoPositionTextBox": "Πλαίσιο κειμένου θέσης βίντεο {0}", + "toolbarLineBreakX": "Αλλαγή γραμμής {0} (η υπόλοιπη γραμμή εργαλείων συνεχίζει σε νέα σειρά)", + "initialText": "Αρχικό κείμενο επιλεγμένης γραμμής", + "initialTextHint": "Το κείμενο της επιλεγμένης γραμμής όπως ήταν όταν επιλέχθηκε η γραμμή", "hideWaveformToolbar": "Απόκρυψη γραμμής εργαλείων {0}", "resetZoomAndSpeed": "Επαναφορά εστίασης και ταχύτητας αναπαραγωγής {0}", "removeBlankLines": "Αφαίρεση κενών γραμμών {0}", @@ -1057,6 +1055,15 @@ "textPlayHint": "Αναπαραγωγή τρέχοντος υπότιτλου και διακοπή στο τέλος {0}", "textPauseHint": "Παύση αναπαραγωγής {0}", "textNextHint": "Αναπαραγωγή επόμενου υπότιτλου και διακοπή στο τέλος {0}", + "moveSelectedLines": "Μετακίνηση επιλεγμένων γραμμών (κουμπιά πίσω/εμπρός)", + "moveSelectedLinesAndFollowing": "Μετακίνηση επιλεγμένων γραμμών και όλων των επόμενων (κουμπιά πίσω/εμπρός)", + "moveAllLines": "Μετακίνηση όλων των γραμμών (κουμπιά πίσω/εμπρός)", + "moveSelectedLinesBackHint": "Μετακίνηση επιλεγμένων γραμμών {0} ms πίσω {1}", + "moveSelectedLinesForwardHint": "Μετακίνηση επιλεγμένων γραμμών {0} ms εμπρός {1}", + "moveSelectedLinesAndFollowingBackHint": "Μετακίνηση επιλεγμένων γραμμών και όλων των επόμενων {0} ms πίσω {1}", + "moveSelectedLinesAndFollowingForwardHint": "Μετακίνηση επιλεγμένων γραμμών και όλων των επόμενων {0} ms εμπρός {1}", + "moveAllLinesBackHint": "Μετακίνηση όλων των γραμμών {0} ms πίσω {1}", + "moveAllLinesForwardHint": "Μετακίνηση όλων των γραμμών {0} ms εμπρός {1}", "guessStartNoLineSelected": "Εκτίμηση αρχής: δεν έχει επιλεγεί γραμμή", "guessStartTimeCodesLocked": "Εκτίμηση αρχής: οι χρονικοί κώδικες είναι κλειδωμένοι", "guessStartNoWaveform": "Εκτίμηση αρχής: δεν υπάρχει κυματομορφή", @@ -1078,12 +1085,15 @@ "charactersPerSecond": "Χαρ./δευτερόλεπτο: {0}", "chooseColumn": "Επιλογή στήλης", "columnPaste": "Επικόλληση στήλης", + "commandPaletteTitle": "Παλέτα εντολών", + "commandPaletteSearchPlaceholder": "Πληκτρολογήστε για αναζήτηση εντολών...", + "commandPaletteNoCommandsFound": "Δεν βρέθηκαν αντίστοιχες εντολές", "createdEmptyTranslation": "Δημιουργήθηκε κενή μετάφραση από τον τρέχοντα υπότιτλο", "deleteText": "Διαγραφή κειμένου", "deleteTextAndShiftCellsUp": "Διαγραφή κειμένου και μετατόπιση κελιών πάνω", "endTimeMustBeAfterStartTime": "Ο χρόνος τέλους πρέπει να είναι μετά τον χρόνο έναρξης.", "errorLoad7Zip": "Αυτό το αρχείο φαίνεται να είναι συμπιεσμένο αρχείο 7-Zip.\n\nΤο Subtitle Edit δεν μπορεί να ανοίξει συμπιεσμένα αρχεία.", - "errorLoadBinaryZeroes": "Λυπούμαστε, αυτό το αρχείο περιέχει μόνο δυαδικά μηδενικά!\n\nΑν έχετε επεξεργαστεί αυτό το αρχείο με το Subtitle Edit, μπορεί να βρείτε αντίγραφο ασφαλείας μέσω του μενού Αρχείο -> Επαναφορά αυτόματου αντιγράφου ασφαλείας...", + "errorLoadBinaryZeroes": "Λυπούμαστε, αυτό το αρχείο περιέχει μόνο δυαδικά μηδενικά!\n\nΑν έχετε επεξεργαστεί αυτό το αρχείο με το Subtitle Edit, μπορεί να βρείτε αντίγραφο ασφαλείας μέσω του μενού Αρχείο -> Επαναφορά αυτόματου αντιγράφου ασφαλείας...", "errorLoadGZip": "Αυτό το αρχείο φαίνεται να είναι συμπιεσμένο αρχείο GZip.\n\nΤο Subtitle Edit δεν μπορεί να ανοίξει συμπιεσμένα αρχεία.", "errorLoadLargeFile": "Αυτό το αρχείο είναι πολύ μεγάλο για να ανοίξει ως αρχείο υπότιτλων.", "errorLoadVideoFilePrompt": "Αυτό το αρχείο φαίνεται να είναι αρχείο βίντεο:\n{0}\n\nΜπορείτε να ανοίξετε ένα βίντεο μέσω του \"Βίντεο -> Άνοιγμα βίντεο\" ή σύροντας και αφήνοντας ένα βίντεο στο πρόγραμμα αναπαραγωγής βίντεο.\n\nΘέλετε να το ανοίξετε ως αρχείο βίντεο;", @@ -1116,18 +1126,20 @@ "numberOfLinesEvenlyDistributedX": "Κατανεμήθηκαν ομοιόμορφα {0} γραμμές", "oneLineCopiedFromOriginal": "Μία γραμμή αντιγράφηκε από τον αρχικό υπότιτλο", "oneLineCopiedToOriginal": "Μία γραμμή αντιγράφηκε στον αρχικό υπότιτλο", + "onlineOnlyVideo": "Βίντεο μόνο στο διαδίκτυο", + "onlineOnlyVideoXSizeYDownloadAndOpen": "Το αρχείο βίντεο \"{0}\" ({1}) είναι αποθηκευμένο μόνο στο διαδίκτυο, για παράδειγμα στο Dropbox ή στο iCloud Drive, και πρέπει να ληφθεί πριν ανοιχτεί.\n\nΝα γίνει λήψη και άνοιγμα τώρα;", "oneLineMerged": "Μία γραμμή συγχωνεύτηκε", "oneLineSwitched": "Μία γραμμή εναλλάχθηκε", "overwriteExistingCells": "Αντικατάσταση υπαρχόντων κελιών", "overwriteOrShiftCellsDown": "Αντικατάσταση/μετατόπιση κελιών κάτω", "parsingMatroskaFile": "Ανάλυση αρχείου Matroska...", + "readingDvdSubtitles": "Ανάγνωση υπότιτλων DVD...", "pasteFromClipboardDotDotDot": "Επικόλληση από το πρόχειρο...", "redoPerformed": "Έγινε ακύρωση αναίρεσης", "redoPerformedXActionLeft": "Έγινε ακύρωση αναίρεσης (ενέργειες που απομένουν: {0})", "removedUnicodeControlCharactersX": "Αφαιρέθηκαν χαρακτήρες ελέγχου Unicode από {0} γραμμές", "removedXBlankLines": "Αφαιρέθηκαν {0} κενές γραμμές", "replacedXWithYCountZ": "Αντικαταστάθηκε το \"{0}\" με \"{1}\" ({2} εμφανίσεις)", - "replacedXWithYInLineZ": "Αντικαταστάθηκε το \"{0}\" με \"{1}\" στη γραμμή {2}", "reversedStartAndEndingsForRightToLeftX": "Αντιστράφηκαν αρχές και τέλη για δεξιά-προς-αριστερά σε {0} γραμμές", "ruleProfileIsX": "Το προφίλ κανόνων είναι τώρα \"{0}\"", "saveLanguageFile": "Αποθήκευση αρχείου γλώσσας", @@ -1135,7 +1147,6 @@ "shiftTextCellsDown": "Μετατόπιση κελιών κειμένου κάτω", "singleLineLength": "Μήκος γραμμής: ", "speedIsNowX": "Η ταχύτητα είναι τώρα \"{0}\"", - "spellCheckResult": "Ο ορθογραφικός έλεγχος ολοκληρώθηκε. \n\n• Λέξεις που άλλαξαν: {0}\n• Λέξεις που παραλείφθηκαν: {1}", "subtitleGridFormattingX": "Μορφοποίηση πλέγματος: {0}", "subtitleImportedFromMatroskaFile": "Υπότιτλος εισήχθη από αρχείο Matroska", "textDown": "Κείμενο κάτω", @@ -1162,13 +1173,14 @@ "trimmedXLines": "Περικόπηκαν {0} γραμμές υπότιτλων", "pastedXLinesOverSelectedLines": "Επικολλήθηκαν {0} γραμμή(ές) πάνω στις επιλεγμένες γραμμές", "pastedXOfYLinesOverSelectedLines": "Επικολλήθηκαν {0} από {1} γραμμή(ές) πάνω στις επιλεγμένες γραμμές - οι υπόλοιπες δεν χώρεσαν στην επιλογή", + "insertedXLinesFromClipboard": "Εισήχθησαν {0} γραμμή(ές) από το πρόχειρο", "openOriginalDifferentNumberOfSubtitlesXY": "Το αρχικό αρχείο υπότιτλων δεν έχει τον ίδιο αριθμό υπότιτλων με το τρέχον αρχείο υπότιτλων.\n\n• Αρχικοί υπότιτλοι: {0}\n• Τρέχοντες υπότιτλοι: {1}", - "importXMatchingOriginalLines": "Εισαγωγή {0} αρχικών υπότιτλων που ταιριάζουν;", "originalTextReadOnly": "Αρχικό κείμενο (μόνο για ανάγνωση)", "originalTextEditMode": "Αρχικό κείμενο (λειτουργία επεξεργασίας)", "originalIsReadOnlyNotSaved": "Ο αρχικός υπότιτλος είναι αναφορά μόνο για ανάγνωση και δεν αποθηκεύτηκε", "originalIsEmptyNotSaved": "Ο αρχικός υπότιτλος δεν έχει καθόλου κείμενο και δεν αποθηκεύτηκε", "originalIsReadOnlyReference": "Ο αρχικός υπότιτλος είναι ανοιχτός ως αναφορά μόνο για ανάγνωση", + "closeTranslationQuestionXY": "Κλείσιμο της μετάφρασης \"{0}\";\n\nΟ αρχικός υπότιτλος \"{1}\" γίνεται ο υπότιτλος που επεξεργάζεστε. Το ίδιο το αρχείο μετάφρασης δεν αλλάζει.", "allowEditOfOriginalSubtitle": "Να επιτρέπεται η επεξεργασία του αρχικού υπότιτλου", "showAllOriginalLinesX": "Εμφάνιση όλων των αρχικών γραμμών ({0} δεν έχουν αντιστοιχία εδώ)", "showAllOriginalLinesHint": "Οι γραμμές χωρίς αντιστοιχία εμφανίζονται ως επιπλέον σειρές, ώστε να βλέπετε τι λείπει από τη μετάφραση.", @@ -1203,6 +1215,7 @@ "fileExportedInFormatXToY": "Το αρχείο εξήχθη σε μορφή {0} στο {1}", "fileExportedInFormatXToFileY": "Το αρχείο εξήχθη σε μορφή \"{0}\" στο αρχείο \"{1}\"", "fixedXLines": "Διορθώθηκαν {0} γραμμές", + "toolXChangedYLines": "{0}: άλλαξαν {1} γραμμές", "transcriptionCompletedWithXLines": "Η μεταγραφή ολοκληρώθηκε με {0} γραμμές", "openSubtitleFileX": "Άνοιγμα αρχείου υπότιτλων \"{0}\";", "replacedXOccurrences": "Αντικαταστάθηκαν {0} εμφανίσεις", @@ -1234,13 +1247,9 @@ "file": { "ebuSaveOptions": { "title": "Επιλογές αποθήκευσης EBU", - "generalSubtitleInformation": "Γενικές πληροφορίες υπότιτλων", "codePageNumber": "Αριθμός κωδικοσελίδας", "diskFormatCode": "Κωδικός μορφής δίσκου", "displayStandardCode": "Κωδικός προτύπου εμφάνισης", - "colorRequiresTeletext": "Τα χρώματα απαιτούν teletext!", - "alignmentRequiresTeletext": "Η στοίχιση απαιτεί teletext!", - "teletextCharsShouldBe38": "Το 'Μέγ.# χαρακτήρων ανά σειρά' για teletext πρέπει να είναι 38!", "characterCodeTable": "Πίνακας χαρακτήρων", "languageCode": "Κωδικός γλώσσας", "originalProgramTitle": "Αρχικός τίτλος προγράμματος", @@ -1257,7 +1266,6 @@ "maxNumberOfDisplayableRows": "Μέγ.# σειρών", "diskSequenceNumber": "Αριθμός σειράς δίσκου", "totalNumberOfDisks": "Συνολικός αριθμός δίσκων", - "import": "Εισαγωγή...", "textAndTimingInformation": "Πληροφορίες κειμένου και χρονισμού", "justificationCode": "Κωδικός στοίχισης", "verticalPosition": "Κατακόρυφη θέση", @@ -1274,11 +1282,6 @@ "errors": "Σφάλματα", "errorsX": "Σφάλματα: {0}", "maxLengthError": "Η γραμμή {0} υπερβαίνει το μέγιστο μήκος ({1}) κατά {2}: {3}", - "textUnchangedPresentation": "Αμετάβλητη παρουσίαση", - "textLeftJustifiedText": "Αριστερά στοιχισμένο κείμενο", - "textCenteredText": "Κεντραρισμένο κείμενο", - "textRightJustifiedText": "Δεξιά στοιχισμένο κείμενο", - "useBoxForOneNewLine": "Επιλέξτε 'Χρήση πλαισίου γύρω από το κείμενο' για μόνο μία νέα γραμμή", "maxLengthErrorTeletextColored": "Γραμμή {0}-{1}: 36 (όχι {2}) πρέπει να είναι οι μέγιστοι χαρακτήρες για έγχρωμο κείμενο διπλού ύψους", "maxLengthErrorTeletextWhite": "Γραμμή {0}-{1}: 37 (όχι {2}) πρέπει να είναι οι μέγιστοι χαρακτήρες για λευκό κείμενο διπλού ύψους", "maxLengthErrorTeletextSingleHeight": "Γραμμή {0}: 38 (όχι {1}) πρέπει να είναι οι μέγιστοι χαρακτήρες για λευκό κείμενο μονού ύψους", @@ -1303,22 +1306,29 @@ "formattingDotDotDot": "Μορφοποίηση...", "imageBasedSubtitleForEditDotDotDot": "Υπότιτλος βασισμένος σε εικόνα για επεξεργασία...", "imageBasedSubtitleForOcrDotDotDot": "Υπότιτλος βασισμένος σε εικόνα για OCR...", + "dvdSubtitlesDotDotDot": "Υπότιτλοι από DVD (IFO/VOB)...", + "titleImportDvd": "Εισαγωγή υπότιτλων από DVD", + "dvdIfoFile": "Αρχείο IFO", + "dvdVobFiles": "Αρχεία VOB", + "dvdVideoStandard": "Πρότυπο βίντεο", + "dvdStartRipping": "Έναρξη εξαγωγής", + "dvdRippingAborted": "Η εξαγωγή ματαιώθηκε", + "dvdOpenIfoFile": "Άνοιγμα αρχείου DVD IFO", + "dvdAddVobFiles": "Προσθήκη αρχείων VOB", + "dvdIfoFiles": "Αρχεία DVD IFO", + "dvdVobFileType": "Αρχεία DVD VOB", + "dvdDropHint": "Ανοίξτε ένα αρχείο IFO (VIDEO_TS\\VTS_01_0.IFO) ή προσθέστε αρχεία VOB - ή αποθέστε τα εδώ", + "dvdNotAnIfoFileX": "Το {0} δεν είναι αρχείο DVD IFO", + "dvdMenuIfoNoTitleSetsX": "Δεν βρέθηκαν σύνολα τίτλων (VTS_xx_0.IFO) δίπλα στο {0}", + "dvdEncryptedXOfY": "{0} από τα {1} πακέτα υπότιτλων είναι ακόμη κρυπτογραφημένα με CSS - αυτά τα αρχεία VOB αντιγράφηκαν χωρίς αποκρυπτογράφηση, οπότε ορισμένες εικόνες υπότιτλων θα είναι αλλοιωμένες.\n\nΑποκρυπτογραφήστε πρώτα το DVD με ένα εργαλείο εξαγωγής DVD για καθαρό αποτέλεσμα.\n\nΣυνέχεια παρ' όλα αυτά;", "splitTextAt": "Διαίρεση κειμένου σε", "blankLines": "Κενές γραμμές", "oneLineIsOneSubtitle": "Μία γραμμή είναι ένας υπότιτλος", "twoLinesAreOneSubtitle": "Δύο γραμμές είναι ένας υπότιτλος", "importFilesDotDotDot": "Εισαγωγή αρχείων...", "multipleFiles": "Εισαγωγή από πολλαπλά αρχεία κειμένου (ένα αρχείο είναι ένας υπότιτλος)", - "importOptions": "Επιλογές εισαγωγής", - "autoSplitText": "Αυτόματη διαίρεση κειμένου", - "lineMode": "Λειτουργία γραμμών", "maxLineLength": "Μέγιστο μήκος γραμμής", - "maxLinesPerSubtitle": "Μέγιστες γραμμές ανά υπότιτλο", - "minGapBetweenSubtitles": "Ελάχιστο κενό μεταξύ υπότιτλων", "mergeShortLines": "Συγχώνευση σύντομων γραμμών", - "removeLinesWithoutLetters": "Αφαίρεση γραμμών χωρίς γράμματα", - "splitAtEndCharsSetting": "Διαίρεση σε χαρακτήρες τέλους", - "takeTimeFromCurrentFile": "Λήψη χρόνου από το τρέχον αρχείο", "fixed": "Σταθερή", "numberOfSubtitlesX": "Αριθμός υπότιτλων: {0}", "numberOfSubtitlesXForcedY": "Αριθμός υπότιτλων: {0} ({1} εξαναγκασμένοι)", @@ -1330,14 +1340,18 @@ "forcedAlignerProgress": "Ευθυγράμμιση... παράθυρο {0} από {1}, {2} από {3} γραμμές", "forcedAlignerSetupTitle": "Ευθυγράμμιση χρονικών κωδίκων μέσω forced aligner", "forcedAlignerSetupIntro": "Ένας forced aligner αντιστοιχίζει το κείμενο υπότιτλων που ήδη έχετε με τον ήχο, χωρίς να το μεταγράψει πρώτα. Τα μεγάλα βίντεο ευθυγραμμίζονται σε τμήματα, οπότε οποιαδήποτε διάρκεια λειτουργεί.", + "forcedAlignerEndsFromSpeech": "Ορισμός χρόνων λήξης από απομονωμένη ομιλία (αργό)", + "forcedAlignerEndsFromSpeechHint": "Ένας εξαναγκασμένος ευθυγραμμιστής βρίσκει πού αρχίζει μια γραμμή, όχι πού τελειώνει, οπότε οι χρόνοι λήξης προέρχονται κανονικά από τον χρόνο ανάγνωσης. Με αυτή την επιλογή ενεργή, η μουσική και τα ηχητικά εφέ αφαιρούνται πρώτα από τον ήχο και κάθε γραμμή τελειώνει εκεί όπου σιωπά η ομιλία της. Διαρκεί περίπου όσο το ίδιο το βίντεο με GPU - και πολλές φορές περισσότερο χωρίς.", + "forcedAlignerIsolatingSpeech": "Απομόνωση ομιλίας... {0}", + "forcedAlignerIsolatingSpeechFailed": "Δεν ήταν δυνατή η απομόνωση της ομιλίας - οι χρόνοι λήξης ορίστηκαν από τον χρόνο ανάγνωσης.", "forcedAlignerModel": "Μοντέλο ευθυγράμμισης", - "forcedAlignerDownloadEngine": "Λήψη / ενημέρωση μηχανής..." + "forcedAlignerDownloadEngine": "Λήψη / ενημέρωση μηχανής...", + "forcedAlignerEstimatedXOfY": "{0} από {1} γραμμές δεν ήταν δυνατό να ευθυγραμμιστούν και τοποθετήθηκαν βάσει χρόνου ανάγνωσης - οι χρονικοί κώδικές τους είναι μόνο εκτιμώμενοι. Το σενάριο είναι πιθανώς μεγαλύτερο από όσα λέγονται στο βίντεο." }, "export": { "exportImagesProfiles": "Προφίλ εξαγωγής εικόνων", "leftRightMargin": "Αριστερό/δεξί περιθώριο", "topBottomMargin": "Πάνω/κάτω περιθώριο", - "titleExportBluRaySup": "Εξαγωγή Blu-ray (sup)", "lineSpacingPercent": "Διάστιχο %", "paddingLeftRight": "Εσωτερικό περιθώριο αριστερά/δεξιά", "paddingTopBottom": "Εσωτερικό περιθώριο πάνω/κάτω", @@ -1378,10 +1392,21 @@ "titleExportDCinemaInteropPng": "D-Cinema interop/png", "titleExportDCinemaSmpte2014Png": "D-Cinema SMPTE 2014/png", "imageBasedSubtitleSaved": "Ο υπότιτλος βασισμένος σε εικόνα αποθηκεύτηκε", - "titleExportBdnXml": "Εξαγωγή BDN/xml", "titleExportWebVttThumbnails": "Εξαγωγή WebVTT (μικρογραφίες)", "fullFrameImage": "Εικόνα πλήρους καρέ", "fullFrameImageHint": "Κάθε εικόνα να έχει το μέγεθος του καρέ βίντεο, με τον υπότιτλο στη θέση του", + "stereo3D": "3D", + "stereo3DHint": "Για βίντεο 3D: σχεδίαση του υπότιτλου μία φορά σε κάθε μισό του καρέ (ένα για κάθε μάτι)", + "halfSideBySide": "Μισό δίπλα-δίπλα", + "halfTopBottom": "Μισό πάνω/κάτω", + "depth3D": "Βάθος", + "depth3DHint": "Πόσο εξέχει ο υπότιτλος από την οθόνη - θετική τιμή τον φέρνει πιο κοντά, αρνητική τον σπρώχνει πίσω", + "plane3D": "3D-Plane", + "plane3DHint": "Δίνει σε κάθε υπότιτλο το βάθος που αποθηκεύει γι' αυτόν ένα 3D Blu-ray, από αρχείο 3D-Plane (.ofs) που δημιουργήθηκε με BD3D2MK3D ή OFSExtractor. Το Βάθος χρησιμοποιείται όπου το 3D-Plane δεν έχει τιμή", + "openPlane3DTitle": "Άνοιγμα αρχείου 3D-Plane", + "unableToLoadPlane3DX": "Αδυναμία φόρτωσης 3D-Plane: {0}", + "plane3DXDepthYToZ": "{0} (βάθος {1} έως {2})", + "plane3DXNoDepth": "{0} (χωρίς τιμές βάθους)", "textEffect": "Εφέ κειμένου", "textEffectSettingsTitle": "Ρυθμίσεις εφέ κειμένου", "textEffectPreset": "Προεπιλογή", @@ -1413,13 +1438,10 @@ "statistics": { "title": "Στατιστικά", "titleWithFileName": "Στατιστικά - {0}", - "generalStatistics": "Γενικά στατιστικά", - "mostUsed": "Πιο συχνά χρησιμοποιούμενα...", "mostUsedLines": "Πιο συχνά χρησιμοποιούμενες γραμμές", "mostUsedWords": "Πιο συχνά χρησιμοποιούμενες λέξεις", "nothingFound": "Δεν βρέθηκε τίποτα", "numberOfLinesX": "Αριθμός γραμμών υπότιτλων: {0:#,##0}", - "numberOfFilesX": "Αριθμός αρχείων υπότιτλων: {0:#,##0}", "lengthInFormatXinCharactersY": "Αριθμός χαρακτήρων ως {0}: {1:#,###,##0}", "numberOfCharactersInTextOnly": "Αριθμός χαρακτήρων μόνο σε κείμενο: {0:#,###,##0}", "totalDuration": "Συνολική διάρκεια όλων των υπότιτλων: {0:#,##0}", @@ -1460,7 +1482,6 @@ "gapMaximum": "Κενό - μέγιστο: {0:#,##0} ms", "gapAverage": "Κενό - μέσος όρος: {0:#,##0.##} ms", "gapExceedingMinimum": "Κενό - κάτω από το ελάχιστο ({0:#,##0} ms): {1} ({2:0.00}%)", - "export": "Εξαγωγή...", "subtitles": "Υπότιτλοι", "words": "Λέξεις", "totalDurationShort": "Συνολική διάρκεια", @@ -1490,7 +1511,22 @@ "restoreAutoBackupFile": "Επαναφορά αρχείου αυτόματου αντιγράφου ασφαλείας", "restoreXFromY": "Θέλετε να επαναφέρετε το \"{0}\" από {1};", "deleteAllSubtitleBackups": "Θέλετε να διαγράψετε όλα τα αρχεία αντιγράφων ασφαλείας υπότιτλων;", - "deleteAll": "Διαγραφή όλων" + "deleteAll": "Διαγραφή όλων", + "subtitles": "Υπότιτλοι", + "settingsBackupInfo": "Δημιουργείται αντίγραφο ασφαλείας του Settings.json κατά την εκκίνηση του Subtitle Edit, το πολύ μία φορά κάθε {0} ημέρα(ες) και μόνο αν οι ρυθμίσεις έχουν αλλάξει. Διατηρούνται τα {1} νεότερα αντίγραφα.", + "settingsBackupInfoEveryStart": "Δημιουργείται αντίγραφο ασφαλείας του Settings.json σε κάθε εκκίνηση του Subtitle Edit, αν οι ρυθμίσεις έχουν αλλάξει. Διατηρούνται τα {0} νεότερα αντίγραφα.", + "settingsBackupOff": "Το αυτόματο αντίγραφο ασφαλείας ρυθμίσεων είναι απενεργοποιημένο (Επιλογές → Ρυθμίσεις → Αρχείο).", + "lastSettingsBackupX": "Τελευταίο αντίγραφο ασφαλείας: {0}", + "noSettingsBackupsYet": "Δεν υπάρχουν ακόμη αντίγραφα ασφαλείας ρυθμίσεων", + "openSettingsBackupFolder": "Άνοιγμα φακέλου αντιγράφων ασφαλείας ρυθμίσεων", + "backUpSettingsNow": "Δημιουργία αντιγράφου τώρα", + "settingsBackedUp": "Δημιουργήθηκε αντίγραφο ασφαλείας των τρεχουσών ρυθμίσεων.", + "restoreSettings": "Επαναφορά ρυθμίσεων", + "restoreSettingsFromX": "Θέλετε να αντικαταστήσετε τις τρέχουσες ρυθμίσεις σας με το αντίγραφο ασφαλείας από {0};\n\nΘα δημιουργηθεί πρώτα αντίγραφο ασφαλείας των τρεχουσών ρυθμίσεων. Ορισμένες αλλαγές ισχύουν μόνο μετά από επανεκκίνηση του Subtitle Edit.", + "settingsRestored": "Οι ρυθμίσεις επαναφέρθηκαν. Επανεκκινήστε το Subtitle Edit για να εφαρμοστούν όλες οι αλλαγές.", + "settingsRestoreFailed": "Αδυναμία επαναφοράς ρυθμίσεων: {0}", + "settingsBackupNotValid": "το αντίγραφο ασφαλείας δεν είναι έγκυρο αρχείο ρυθμίσεων, οπότε διατηρήθηκαν οι τρέχουσες ρυθμίσεις σας", + "deleteAllSettingsBackups": "Θέλετε να διαγράψετε όλα τα αρχεία αντιγράφων ασφαλείας ρυθμίσεων;" }, "propertiesDCinema": { "title": "Ιδιότητες D-Cinema SMPTE", @@ -1504,22 +1540,16 @@ "editRate": "Ρυθμός επεξεργασίας", "timeCodeRate": "Ρυθμός χρονικού κώδικα", "startTime": "Χρόνος έναρξης", - "font": "Γραμματοσειρά", "fontId": "ID γραμματοσειράς", "fontUri": "URI γραμματοσειράς", - "generate": "Δημιουργία", - "fontColor": "Χρώμα γραμματοσειράς", "chooseColor": "Επιλογή χρώματος", "fontEffect": "Εφέ γραμματοσειράς", "effectColor": "Χρώμα εφέ", - "fontSize": "Μέγεθος γραμματοσειράς", "topBottomMargin": "Πάνω/κάτω περιθώριο", "fadeUpTime": "Χρόνος εμφάνισης (fade up)", "fadeDownTime": "Χρόνος απόσβεσης (fade down)", - "frames": "Καρέ", "zPosition": "Θέση Z", - "zPositionHelp": "Οι θετικοί αριθμοί απομακρύνουν το κείμενο, οι αρνητικοί το πλησιάζουν· αν η θέση Z είναι μηδέν, τότε είναι 2D", - "export": "Εξαγωγή..." + "zPositionHelp": "Οι θετικοί αριθμοί απομακρύνουν το κείμενο, οι αρνητικοί το πλησιάζουν· αν η θέση Z είναι μηδέν, τότε είναι 2D" }, "webVtt": { "setStylesForSelectedLinesTitle": "Ορισμός στυλ WebVTT για τις επιλεγμένες γραμμές", @@ -1552,20 +1582,66 @@ "compareOnlyInOneFile": "Μόνο σε ένα αρχείο", "compareTextOrTimeDifference": "Διαφορά κειμένου/χρόνου", "compareNumberDifference": "Διαφορά αριθμού", - "ignoreLineBreaks": "Παράβλεψη αλλαγών γραμμής", "ignoreWhitespace": "Παράβλεψη κενών διαστημάτων", "ignoreWhitespaceHint": "Οι γραμμές που διαφέρουν μόνο σε κενά, στηλοθέτες ή αλλαγές γραμμής δεν θεωρούνται διαφορετικές", "ignoreFormatting": "Παράβλεψη μορφοποίησης", - "onlyLookForDifferencesInText": "Αναζήτηση διαφορών μόνο στο κείμενο", - "cannotCompareWithImageBasedSubtitles": "Δεν είναι δυνατή η σύγκριση με υπότιτλους βασισμένους σε εικόνα", - "statisticsTitle": "Στατιστικά", "ignoreFormattingHint": "Οι γραμμές που διαφέρουν μόνο σε ετικέτες μορφοποίησης, όπως ή {\\an8}, δεν θεωρούνται διαφορετικές", "showOnlyDifferencesInText": "Μόνο διαφορές στο κείμενο", + "ignoreNumbering": "Παράβλεψη αρίθμησης", + "ignoreNumberingHint": "Οι γραμμές που διαφέρουν μόνο στον αριθμό τους δεν θεωρούνται διαφορετικές", + "ignoreTimeDifferenceMs": "Χρονική ανοχή (ms)", + "ignoreTimeDifferenceMsHint": "Χρόνοι έναρξης και λήξης που διαφέρουν κατά τόσα χιλιοστά δευτερολέπτου ή λιγότερο δεν θεωρούνται διαφορετικοί", + "compareDifferences": "Διαφορές", + "compareTextDifferences": "Διαφορές κειμένου", + "compareCurrent": "Τρέχον", + "compareReference": "Αναφορά", + "compareEditable": "Επεξεργάσιμο", + "compareEditableHint": "Οι αλλαγές που γίνονται εδώ επιστρέφουν στον υπότιτλο όταν πατήσετε Εφαρμογή", + "compareReadOnly": "Μόνο για ανάγνωση", + "compareTakeFromReference": "Λήψη κειμένου και χρονισμού από την αναφορά", + "compareInsertFromReference": "Εισαγωγή αυτής της γραμμής στον τρέχοντα υπότιτλο", + "compareDeleteFromCurrent": "Διαγραφή αυτής της γραμμής από τον τρέχοντα υπότιτλο", + "compareTakeText": "Λήψη κειμένου", + "compareTakeTiming": "Λήψη χρονισμού", + "compareEditLine": "Επεξεργασία γραμμής", + "compareEditHint": "Ctrl+Enter για αποθήκευση, Esc για ακύρωση", + "compareEdited": "Επεξεργασμένο", + "compareOnePendingChange": "1 εκκρεμής αλλαγή", + "compareXPendingChanges": "{0} εκκρεμείς αλλαγές", + "compareDiscardXChanges": "Απόρριψη των {0} αλλαγών που έγιναν στη Σύγκριση;", + "compareXChangesApplied": "Σύγκριση: εφαρμόστηκαν {0} αλλαγή(ές)", + "compareChangeTextX": "#{0} κείμενο από την αναφορά", + "compareChangeTimingX": "#{0} χρονισμός από την αναφορά", + "compareChangeTextAndTimingX": "#{0} κείμενο και χρονισμός από την αναφορά", + "compareChangeEditedX": "#{0} επεξεργάστηκε", + "compareChangeInsertedX": "#{0} εισήχθη", + "compareChangeDeletedX": "#{0} διαγράφηκε", + "compareSyncPoint": "Σημείο συγχρονισμού", + "compareSyncPointHint": "Σημείο συγχρονισμού - αυτές οι δύο γραμμές εμφανίζονται πάντα ως ζεύγος, και οι γραμμές από πάνω και από κάτω ευθυγραμμίζονται ξεχωριστά", + "compareSyncPickCurrent": "Σημείο συγχρονισμού: χρήση αυτής της τρέχουσας γραμμής", + "compareSyncPickReference": "Σημείο συγχρονισμού: χρήση αυτής της γραμμής αναφοράς", + "compareSyncRemove": "Αφαίρεση σημείου συγχρονισμού", + "compareClearXSyncPoints": "Εκκαθάριση σημείων συγχρονισμού ({0})", + "compareSyncCurrentPickedX": "Η τρέχουσα #{0} επιλέχθηκε ως σημείο συγχρονισμού - επιλέξτε την αντίστοιχη γραμμή αναφοράς και πατήστε Συγχρονισμός", + "compareSyncReferencePickedX": "Η αναφορά #{0} επιλέχθηκε ως σημείο συγχρονισμού - επιλέξτε την αντίστοιχη τρέχουσα γραμμή και πατήστε Συγχρονισμός", + "compareSyncWithCurrentX": "Συγχρονισμός με την τρέχουσα #{0}", + "compareSyncWithReferenceX": "Συγχρονισμός με την αναφορά #{0}", + "compareSyncApply": "Συγχρονισμός", + "compareSyncPointSetXY": "Ορίστηκε σημείο συγχρονισμού: η τρέχουσα #{0} και η αναφορά #{1} εμφανίζονται τώρα ως ζεύγος", + "compareSyncAlreadyPairedXY": "Η τρέχουσα #{0} και η αναφορά #{1} εμφανίζονται ήδη ως ζεύγος με την ίδια έναρξη - τίποτα για συγχρονισμό", + "compareSyncPointShiftedXYZW": "Ορίστηκε σημείο συγχρονισμού: οι τρέχουσες #{0}-#{1} μετατοπίστηκαν {2} ώστε να αρχίζουν με την αναφορά #{3}", + "compareChangeSyncShiftXYZ": "#{0}-#{1} μετατοπίστηκαν {2} (σημείο συγχρονισμού)", + "compareTimingDeltaHint": "Πόσο αργότερα (+) ή νωρίτερα (−) αρχίζει και τελειώνει η αναφορά σε σχέση με την τρέχουσα γραμμή", "loadXFromFile": "Φόρτωση \"{0}\" από αρχείο", + "compareLoadSavedFile": "Φόρτωση αποθηκευμένου αρχείου", "saveCompareHtmlTitle": "Αποθήκευση αρχείου HTML σύγκρισης", "pickMatroskaTrackX": "Επιλογή κομματιού Matroska - {0}", "pickTransportStreamTrackX": "Επιλογή κομματιού transport stream - {0}", + "chapters": "Κεφάλαια", + "dvdVobFilesMissingX": "Λείπουν αρχεία VOB (βρέθηκε το {0}%)", "pickMp4TrackX": "Επιλογή κομματιού MP4 - {0}", + "pickMxfTrackX": "Επιλογή κομματιού MXF - {0}", + "pickMpegTrackX": "Επιλογή κομματιού MPEG - {0}", "rosettaProperties": "Ιδιότητες Timed Text Rosetta IMSC", "rosettaFontSize": "Μέγεθος γραμματοσειράς (ύψος σειράς)", "propertyTimeBase": "Χρονική βάση", @@ -1575,7 +1651,6 @@ "propertyDefaultRegion": "Προεπιλεγμένη περιοχή", "propertyStyleAttributeName": "Όνομα χαρακτηριστικού στυλ", "propertyTimeCodeFormat": "Μορφή χρονικού κώδικα", - "propertyFileExtension": "Επέκταση αρχείου", "propertyTopOrigin": "Πάνω αφετηρία", "propertyTopExtent": "Πάνω έκταση", "propertyBottomOrigin": "Κάτω αφετηρία", @@ -1625,7 +1700,6 @@ "selectionIntersect": "Τομή με την επιλογή" }, "multipleReplace": { - "title": "Πολλαπλή αντικατάσταση", "editRule": "Επεξεργασία κανόνα", "newRule": "Νέος κανόνας", "editCategory": "Επεξεργασία κατηγορίας", @@ -1642,24 +1716,23 @@ "findWhat": "Εύρεση του", "descriptionOptional": "Περιγραφή (προαιρετικά)", "invalidRegularExpressionX": "Μη έγκυρη κανονική έκφραση: {0}", - "regularExpressionTooSlowX": "Η κανονική έκφραση εγκατέλειψε μετά από {0} δευτερόλεπτα - ο κανόνας παραλείπεται, δοκιμάστε απλούστερο μοτίβο" + "regularExpressionTooSlowX": "Η κανονική έκφραση εγκατέλειψε μετά από {0} δευτερόλεπτα - ο κανόνας παραλείπεται, δοκιμάστε απλούστερο μοτίβο", + "moveToCategory": "Μετακίνηση σε κατηγορία" }, "find": { "searchTextWatermark": "Αναζήτηση κειμένου...", "wholeWord": "Ολόκληρη λέξη", - "caseSensitive": "Διάκριση πεζών-κεφαλαίων", - "caseInsensitive": "Χωρίς διάκριση πεζών-κεφαλαίων", "findPrevious": "Εύρεση προηγούμενου", "findNext": "Εύρεση επόμενου", "replaceAndFindNext": "Αντικατάσταση και εύρεση επόμενου", "replaceAll": "Αντικατάσταση όλων", - "replaceTitle": "Αντικατάσταση", - "replaceWith": "Αντικατάσταση με", "replaceTextWatermark": "Αντικατάσταση με...", "replaceIn": "Αντικατάσταση/αναζήτηση σε", "replaceInTextAndOriginal": "Κείμενο και αρχικό κείμενο", "replaceInTextOnly": "Μόνο κείμενο", - "replaceInOriginalOnly": "Μόνο αρχικό κείμενο" + "replaceInOriginalOnly": "Μόνο αρχικό κείμενο", + "replacedOneOccurrence": "Αντικαταστάθηκε μία εμφάνιση", + "replacedXOccurrences": "Αντικαταστάθηκαν {0} εμφανίσεις" }, "regularExpressionContextMenu": { "wordBoundary": "Όριο λέξης (\\b)", @@ -1687,6 +1760,7 @@ "xLinesYSubtitles": "{0} γραμμές, {1} υπότιτλοι", "xLinesYSubtitlesZErrors": "{0} γραμμές, {1} υπότιτλοι, {2} σφάλμα(τα)", "couldNotParseAsX": "Αδυναμία ανάλυσης ως {0}", + "readAsXUseAsY": "Η πηγή δεν ήταν δυνατό να διαβαστεί ως {1}, αλλά μπορούσε να διαβαστεί ως {0}.\nΧρήση των γραμμών και συνέχιση αποθήκευσης ως {1};", "selectedXCharactersYLines": "Επιλέχθηκαν: {0} χαρακτήρες σε {1} γραμμή(ές)", "replacedXOccurrences": "Αντικαταστάθηκαν {0} εμφανίσεις", "invalidRegularExpression": "Μη έγκυρη κανονική έκφραση", @@ -1701,7 +1775,6 @@ "aiReview": { "title": "Επισκόπηση με AI", "review": "Επισκόπηση", - "stop": "Διακοπή", "editPrompt": "Επεξεργασία προτροπής...", "editPromptTitle": "Επεξεργασία προτροπής επισκόπησης", "promptInfo": "Οδηγίες που αποστέλλονται στο μοντέλο - το {language} αντικαθίσταται με τη γλώσσα του υπότιτλου.", @@ -1712,7 +1785,6 @@ "reviewingLineXOfY": "Επισκόπηση γραμμής {0} από {1}...", "reviewDone": "Η επισκόπηση ολοκληρώθηκε - {0} προτάσεις σε {1} γραμμές", "noIssuesFound": "Δεν βρέθηκαν προβλήματα", - "categoryAll": "Όλα", "categorySpelling": "Ορθογραφία", "categoryGrammar": "Γραμματική", "categoryPunctuation": "Στίξη", @@ -1724,8 +1796,23 @@ "largeChangeWarning": "Μεγάλη αλλαγή - μοιάζει με ξαναγράψιμο, ελέγξτε προσεκτικά", "mismatchWarning": "Δεν μοιάζει με το πρωτότυπο - μπορεί να ανήκει σε άλλη γραμμή", "engineError": "Δεν ήταν δυνατή η επικοινωνία με τη μηχανή AI: {0}", - "setupHint": "Παρακαλώ επιλέξτε πρώτα μηχανή και μοντέλο στο Εργαλεία -> Επισκόπηση με AI.", - "playCurrentLineHint": "Αναπαραγωγή της επιλεγμένης γραμμής στο πρόγραμμα αναπαραγωγής και παύση στο τέλος της (ή διπλό κλικ σε μια γραμμή)" + "playCurrentLineHint": "Αναπαραγωγή της επιλεγμένης γραμμής στο πρόγραμμα αναπαραγωγής και παύση στο τέλος της (ή διπλό κλικ σε μια γραμμή)", + "editAfterHint": "Κάντε κλικ στο κείμενο της επιλεγμένης σειράς για να επεξεργαστείτε τη διόρθωση πριν την εφαρμόσετε (Enter για αποθήκευση, Escape για ακύρωση)", + "template": "Πρότυπο", + "templatePlaceholder": "Επιλέξτε ένα πρότυπο για αντικατάσταση της προτροπής", + "templateStandard": "Τυπική διόρθωση κειμένου", + "templateSpeechToText": "Καθαρισμός ομιλίας σε κείμενο", + "templateMachineTranslation": "Διόρθωση αυτόματης μετάφρασης", + "templateSdh": "SDH / άτομα με προβλήματα ακοής", + "referenceContext": "Πλαίσιο αναφοράς", + "referenceContextInfo": "Προαιρετικό. Ονόματα, χαρακτήρες, γλωσσάρι, σύνοψη ή σημειώσεις ύφους - το μοντέλο το θεωρεί αυθεντία για ονόματα και όρους. Αποστέλλεται με κάθε παρτίδα.", + "referenceContextPlaceholder": "Ονόματα: Kaito, θεία Mei, Dr. Hollis\nΌροι: Shinkansen, πυρήνας warp\nΣύνοψη: Ο Kaito ταξιδεύει στο Τόκιο για να βρει τον αγνοούμενο πατέρα του.", + "generateContext": "Δημιουργία με AI", + "generateContextHint": "Αφήστε την επιλεγμένη μηχανή AI να διαβάσει τον υπότιτλο και να συντάξει ονόματα, όρους και σύνοψη - ελέγξτε το αποτέλεσμα πριν τη χρήση", + "generatingContextPartXOfY": "Ανάγνωση μέρους {0} από {1}...", + "generatingContextSynopsis": "Σύνταξη σύνοψης...", + "replaceContextQuestion": "Αντικατάσταση του τρέχοντος πλαισίου αναφοράς;", + "noContextGenerated": "Η μηχανή AI δεν επέστρεψε αξιοποιήσιμες πληροφορίες αναφοράς." }, "aiAssistant": { "title": "Βοηθός AI", @@ -1743,22 +1830,14 @@ "showReasoning": "Εμφάνιση συλλογισμού του μοντέλου" }, "fixCommonErrors": { - "title": "Διόρθωση συνηθισμένων σφαλμάτων", - "step1": "Βήμα 1/2 - Επιλέξτε ποια σφάλματα να διορθωθούν", - "whatToFix": "Τι να διορθωθεί", - "example": "Παράδειγμα", - "selectAll": "Επιλογή όλων", - "inverseSelection": "Αντιστροφή επιλογής", "back": "< &Πίσω", "next": "&Επόμενο >", - "step2": "Βήμα 2/2 - Επαλήθευση διορθώσεων", "fixes": "Διορθώσεις", "ruleDetailsDotDotDot": "Λεπτομέρειες κανόνα...", "showOnlyThisRule": "Εμφάνιση μόνο των διορθώσεων αυτού του κανόνα", "appliedRulesForLineX": "Εφαρμοσμένοι κανόνες για τη γραμμή {0}", "ruleX": "Κανόνας: {0}", "log": "Καταγραφή", - "function": "Λειτουργία", "removedEmptyLine": "Αφαίρεση κενής γραμμής", "removedEmptyLineAtTop": "Αφαίρεση κενής γραμμής στην αρχή", "removedEmptyLineAtBottom": "Αφαίρεση κενής γραμμής στο τέλος", @@ -1779,13 +1858,13 @@ "removeLineBreaksPixelWidth": "Αναίρεση αλλαγής γραμμής σε υπότιτλους που χωρούν σε μία γραμμή (πλάτος σε pixel)", "fixUppercaseIInsideLowercaseWords": "Διόρθωση κεφαλαίου 'i' μέσα σε πεζές λέξεις (σφάλμα OCR)", "fixDoubleApostrophes": "Διόρθωση διπλών αποστρόφων ('') σε ένα εισαγωγικό (\")", + "fixMisreadQuotes": "Διόρθωση αποστρόφων που διαβάστηκαν λάθος ως διπλά εισαγωγικά (OCR, Αγγλικά)", "addPeriods": "Προσθήκη τελείας μετά από γραμμές όπου η επόμενη γραμμή ξεκινά με κεφαλαίο γράμμα", "startWithUppercaseLetterAfterParagraph": "Έναρξη με κεφαλαίο γράμμα μετά από παράγραφο", "startWithUppercaseLetterAfterPeriodInsideParagraph": "Έναρξη με κεφαλαίο γράμμα μετά από τελεία μέσα σε παράγραφο", "startWithUppercaseLetterAfterColon": "Έναρξη με κεφαλαίο γράμμα μετά από άνω-κάτω τελεία/άνω τελεία", "fixLowercaseIToUppercaseI": "Διόρθωση μόνου πεζού 'i' σε 'I' (Αγγλικά)", "fixCommonOcrErrors": "Διόρθωση συνηθισμένων σφαλμάτων OCR (με χρήση λίστας αντικατάστασης OCR)", - "commonOcrErrorsFixed": "Διορθώθηκαν συνηθισμένα σφάλματα OCR (χρησιμοποιήθηκε αρχείο OcrReplaceList): {0}", "removeSpaceBetweenNumber": "Αφαίρεση κενού μεταξύ αριθμών", "breakDialogsOnOneLine": "Διαίρεση διαλόγων σε μία γραμμή", "removeDialogFirstInNonDialogs": "Αφαίρεση αρχικής παύλας στην πρώτη γραμμή για μη διαλόγους", @@ -1802,18 +1881,17 @@ "fixCommasExample": ",, -> ,", "removeLineBreaksExample": "Foo
bar! -> Foo bar!", "fixDoubleApostrophesExample": "''Has double single quotes'' -> \"Has single double quote\"", + "fixMisreadQuotesExample": "\"Hello' -> \"Hello\"", "addPeriodsExample": "Hello world -> Hello world.", "startWithUppercaseLetterAfterParagraphExample": "p1: Foobar! || p2: foobar! -> p1: Foobar! || p2: Foobar!", "startWithUppercaseLetterAfterPeriodInsideParagraphExample": "Hello there! how are you? -> Hello there! How are you?", "startWithUppercaseLetterAfterColonExample": "Speaker: hello world! -> Speaker: Hello world!", "removeHyphensSingleLineExample": "- Foobar. -> Foobar.", "fix3PlusLinesExample": "Foo
bar
baz! -> Foo bar baz!", - "xMissingQuotesAdded": "Εισαγωγικά που προστέθηκαν: {0}", "fix3PlusLine": "Διόρθωση υπότιτλου με περισσότερες από δύο γραμμές", "fix3PlusLines": "Διόρθωση υπότιτλων με περισσότερες από δύο γραμμές", "analysing": "Ανάλυση...", "nothingToFix": "Τίποτα για διόρθωση :)", - "fixesFoundX": "Διορθώσεις που βρέθηκαν: {0}", "xFixesApplied": "Διορθώσεις που εφαρμόστηκαν: {0}", "xFixesYSelected": "{0} διορθώσεις - {1} επιλεγμένες", "nothingFixableBut": "Τίποτα δεν μπόρεσε να διορθωθεί αυτόματα. Ο υπότιτλος περιέχει σφάλματα - δείτε την καταγραφή για λεπτομέρειες", @@ -1822,7 +1900,6 @@ "fixFirstLetterToUppercaseAfterParagraph": "Διόρθωση πρώτου γράμματος σε κεφαλαίο μετά από παράγραφο", "mergeShortLine": "Συγχώνευση σύντομης γραμμής (μία πρόταση)", "mergeShortLineAll": "Συγχώνευση σύντομης γραμμής (όλα εκτός διαλόγων)", - "unbreakShortLinePixelWidth": "Αναίρεση αλλαγής γραμμής σύντομης γραμμής (πλάτος σε pixel)", "breakLongLine": "Αλλαγή γραμμής μεγάλης γραμμής", "fixLongDisplayTime": "Διόρθωση μεγάλου χρόνου εμφάνισης", "fixInvalidItalicTag": "Διόρθωση μη έγκυρης ετικέτας πλάγιας γραφής", @@ -1845,7 +1922,6 @@ "fixUppercaseIInsideLowercaseWord": "Διόρθωση κεφαλαίου 'i' μέσα σε πεζή λέξη", "fixMissingPeriodAtEndOfLine": "Προσθήκη τελείας που λείπει στο τέλος της γραμμής", "refreshFixes": "Ανανέωση διαθέσιμων διορθώσεων", - "applyFixes": "Εφαρμογή επιλεγμένων διορθώσεων και κλείσιμο", "autoBreak": "Αυτόματη &αλλαγή", "unbreak": "&Αναίρεση αλλαγής", "fixDoubleDash": "Διόρθωση '--' -> '...'", @@ -1864,8 +1940,6 @@ "fixSpaceBetweenNumbersExample": "1 100 -> 1100", "fixDialogsOneLineExample": "Hi John! - Hi Ida! -> Hi John!
- Hi Ida!", "removeDialogFirstInNonDialogsExample": "- How are you? -> How are you?", - "selectDefault": "Επιλογή προεπιλογής", - "setDefault": "Ορισμός τρεχουσών διορθώσεων ως προεπιλογή", "fixContinuationStyleX": "Διόρθωση στυλ συνέχειας: {0}", "fixUnnecessaryLeadingDots": "Αφαίρεση περιττών αρχικών τελειών", "fixCommonErrorsProfiles": "Προφίλ διόρθωσης συνηθισμένων σφαλμάτων", @@ -1873,7 +1947,6 @@ "searchRulesDotDotDot": "Αναζήτηση κανόνων...", "goToApplyFixes": "Μετάβαση στην εφαρμογή διορθώσεων", "backToFixList": "Πίσω στη λίστα διορθώσεων", - "applyFixesAndClose": "Εφαρμογή διορθώσεων και κλείσιμο", "fixCommonOcrErrorsStep2": "Διόρθωση συνηθισμένων σφαλμάτων, βήμα 2 (εφαρμογή διορθώσεων)", "fixCommonOcrErrorsStep2FixesFoundX": "Διόρθωση συνηθισμένων σφαλμάτων, βήμα 2 - Διορθώσεις που βρέθηκαν: {0}", "action": "Ενέργεια", @@ -1886,18 +1959,11 @@ "adjustDurations": { "title": "Προσαρμογή διαρκειών", "adjustVia": "Προσαρμογή μέσω", - "seconds": "Δευτερόλεπτα", - "percent": "Ποσοστό", "recalculate": "Επανυπολογισμός", "addSeconds": "Προσθήκη δευτερολέπτων", - "setAsPercent": "Ορισμός ως ποσοστό της διάρκειας", "note": "Σημείωση: Ο χρόνος εμφάνισης δεν θα επικαλύπτει τον χρόνο έναρξης του επόμενου κειμένου", "fixed": "Σταθερή", - "milliseconds": "Χιλιοστά του δευτερολέπτου", "extendOnly": "Μόνο επέκταση", - "enforceDurationLimits": "Επιβολή ελάχιστης και μέγιστης διάρκειας", - "checkShotChanges": "Χωρίς επέκταση πέρα από αλλαγές πλάνου", - "batchCheckShotChanges": "Σεβασμός αλλαγών πλάνου (αν είναι διαθέσιμες)", "recalculateRequiresOcrNote": "Ο επανυπολογισμός δεν είναι διαθέσιμος — ένας ή περισσότεροι υπότιτλοι δεν έχουν κείμενο OCR. Εκτελέστε πρώτα OCR." }, "applyDurationLimits": { @@ -1905,6 +1971,8 @@ "fixMinDurationMs": "Διόρθωση ελάχιστης διάρκειας (ms)", "doNotGoPastShotChange": "Χωρίς υπέρβαση αλλαγής πλάνου", "fixMaxDurationMs": "Διόρθωση μέγιστης διάρκειας (ms)", + "fixMinDurationFrames": "Διόρθωση ελάχιστης διάρκειας (καρέ)", + "fixMaxDurationFrames": "Διόρθωση μέγιστης διάρκειας (καρέ)", "minimumDurationMilliseconds": "Ελάχιστη διάρκεια (χιλιοστά δευτερολέπτου):", "maximumDurationMilliseconds": "Μέγιστη διάρκεια (χιλιοστά δευτερολέπτου):", "maxDurationShouldBeHigherThanMinDuration": "Η μέγιστη διάρκεια πρέπει να είναι μεγαλύτερη από την ελάχιστη διάρκεια", @@ -1924,12 +1992,8 @@ }, "beautifyTimeCodes": { "title": "Καλλωπισμός χρονικών κωδίκων", - "beautifySettings": "Ρυθμίσεις καλλωπισμού", "snapToFrames": "Προσκόλληση σε καρέ", - "frameGap": "Κενό καρέ", "minDuration": "Ελάχιστη διάρκεια (ms)", - "shotChangeThreshold": "Κατώφλι αλλαγής πλάνου (ms)", - "shotChangeOffset": "Μετατόπιση αλλαγής πλάνου (καρέ)", "original": "Αρχικό", "beautified": "Καλλωπισμένο", "subtitlesCount": "Υπότιτλοι", @@ -1958,13 +2022,80 @@ "timeCodesExtractFailed": "Αδυναμία ανάγνωσης χρονικών κωδίκων από το βίντεο", "timeCodesHint": "Προσκόλληση των σημείων στους πραγματικούς χρόνους καρέ του βίντεο αντί για υπόθεση σταθερού ρυθμού καρέ. Απαιτεί ανάγνωση ολόκληρου του βίντεο μία φορά· το αποτέλεσμα αποθηκεύεται στη μνήμη cache." }, + "improveTimeCodes": { + "title": "Βελτίωση χρονικών κωδίκων (εξαναγκασμένη ευθυγράμμιση)", + "aligner": "Ευθυγραμμιστής", + "alignerHint": "Το μοντέλο που αναζητά κάθε γραμμή στον ήχο. Οι καλύτερες επιλογές για τη γλώσσα του υπότιτλου εμφανίζονται πρώτες.", + "maxShift": "Μέγιστη μετατόπιση (δευτερόλεπτα)", + "maxShiftHint": "Πόσο εκτός συγχρονισμού μπορεί να είναι ο υπότιτλος. Μια γραμμή της οποίας η έναρξη θα μετακινούνταν περισσότερο παραμένει ως έχει και επισημαίνεται. Όποια κι αν είναι η τιμή, μια μεμονωμένη γραμμή που θέλει να μετακινηθεί πάνω από μισό δευτερόλεπτο περισσότερο από τις γύρω γραμμές δεν μετακινείται στα τυφλά: ακολουθεί τις γειτονικές της όταν έχουν μετακινηθεί όλες, διαφορετικά προτείνεται χωρίς σημάδι ελέγχου για να την ελέγξετε.", + "adjustStartTimes": "Προσαρμογή χρόνων έναρξης", + "adjustEndTimes": "Προσαρμογή χρόνων λήξης", + "isolateSpeech": "Απομόνωση ομιλίας πρώτα (αργό)", + "isolateSpeechHint": "Αφαιρεί τη μουσική και τα ηχητικά εφέ πριν από την ευθυγράμμιση, ώστε ο ευθυγραμμιστής να ακούει μόνο τον διάλογο. Βοηθά σε γραμμές που λέγονται πάνω από δυνατή μουσική ή δράση, αλλά διαρκεί περίπου όσο ο ίδιος ο ήχος με GPU - και πολλές φορές περισσότερο χωρίς.", + "isolatingSpeech": "Απομόνωση ομιλίας... (αυτό διαρκεί λίγο)", + "isolateSpeechFailed": "Δεν ήταν δυνατή η απομόνωση της ομιλίας - η ευθυγράμμιση έγινε με τον αρχικό ήχο.", + "checkWithSpeechToText": "Έλεγχος με ομιλία σε κείμενο", + "checkWithSpeechToTextHint": "Μεταγράφει επίσης τον ήχο και ελέγχει κάθε μετακίνηση σε σχέση με το πού ακούστηκαν πραγματικά οι λέξεις. Οι μεγάλες μετακινήσεις που επιβεβαιώνει η ομιλία επιλέγονται αυτόματα, ενώ οι μετακινήσεις μακριά από το σημείο όπου ακούγεται μια γραμμή μένουν αποεπιλεγμένες για να τις ελέγξετε. Δείχνει επίσης πόσο από κάθε γραμμή ακούστηκε - ένα χαμηλό ποσοστό σημαίνει ότι το κείμενο διαφέρει από αυτό που λέγεται. Ένας υπότιτλος που είναι πιο εκτός συγχρονισμού από τη μέγιστη μετατόπιση - σταθερή μετατόπιση, ολίσθηση από αναντιστοιχία ρυθμού καρέ ή άλματα όπου κόπηκαν σκηνές - συγχρονίζεται πρώτα με βάση τις λέξεις που ακούστηκαν. Χρησιμοποιεί το Crisp ASR Parakeet, που χρειάζεται λίγα λεπτά για μια ταινία μεγάλου μήκους με GPU.", + "checkWithSpeechToTextNotAvailableX": "Ο έλεγχος με ομιλία σε κείμενο δεν είναι διαθέσιμος για {0}", + "transcribingToCheck": "Μεταγραφή για έλεγχο της ευθυγράμμισης...", + "speechToTextFailed": "Η ομιλία σε κείμενο απέτυχε - οι χρονικοί κώδικες δεν ελέγχθηκαν.", + "speechToTextX": "Ομιλία σε κείμενο: {0}", + "speechToTextConfirmedXDisputedY": "Ομιλία: {0} επιβεβαιωμένες, {1} αμφισβητούμενες", + "hardlyHeardX": "Η ομιλία σε κείμενο άκουσε μόνο το {0}% των λέξεων του υπότιτλου. Είναι ο υπότιτλος στη γλώσσα που ομιλείται; Ο ευθυγραμμιστής δεν μπορεί να τοποθετήσει μια μετάφραση.", + "heard": "Ακούστηκε", + "heardHint": "Πόσο από τη γραμμή άκουσε η ομιλία σε κείμενο. Ένα χαμηλό ποσοστό σημαίνει ότι το κείμενο διαφέρει από αυτό που λέγεται - εκεί όπου ο ευθυγραμμιστής είναι πιο πιθανό να κάνει λάθος.", + "statusConfirmedBySpeech": "Επιβεβαιώθηκε από την ομιλία", + "statusDisputedBySpeech": "Ακούστηκε εκεί που ήταν", + "statusMovedWithSync": "Μετακινήθηκε με τον συγχρονισμό", + "syncedFirstXY": "Πρώτα συγχρονισμός: {0} s στην αρχή, {1} s στο τέλος", + "showSpeechOnly": "Εμφάνιση μόνο ομιλίας", + "showSpeechOnlyHint": "Σχεδίαση της ευθυγραμμισμένης κυματομορφής από τον ήχο χωρίς μουσική και ηχητικά εφέ· η αρχική κυματομορφή παραμένει ως έχει, για σύγκριση. Διαθέσιμο αφού απομονωθεί η ομιλία - εδώ ή με το \"Εμφάνιση μόνο ομιλίας\" στην κυματομορφή του κύριου παραθύρου.", + "align": "Ευθυγράμμιση", + "original": "Αρχικό", + "aligned": "Ευθυγραμμισμένο", + "intro": "Επιλέξτε έναν ευθυγραμμιστή και πατήστε \"Ευθυγράμμιση\" για να αναζητηθεί κάθε γραμμή στον ήχο και να βελτιωθούν οι χρονικοί κώδικές της.", + "extractingAudio": "Εξαγωγή ήχου...", + "aligningBatchXOfY": "Ευθυγράμμιση... παρτίδα {0} από {1}", + "extractAudioFailed": "Δεν ήταν δυνατή η εξαγωγή του ήχου με το ffmpeg.", + "modelWillBeDownloaded": "Θα γίνει λήψη του {0}", + "summaryXRetimedYKeptZSkipped": "Επαναχρονίστηκαν: {0} · Διατηρήθηκαν: {1} · Χωρίς ομιλία: {2}", + "meanShiftX": "Μέση μετατόπιση: {0} ms", + "toCheckX": "Για έλεγχο: {0}", + "largeMovesToCheckX": "{0} γραμμή(ές) θα μετακινούνταν πολύ μόνες τους και έμειναν αποεπιλεγμένες - ακούστε τες (το F5 αναπαράγει την ευθυγραμμισμένη γραμμή, το Shift+F5 την αρχική) και επιλέξτε όσες είναι σωστές.", + "linesToCheckX": "{0} γραμμή(ές) έμειναν αποεπιλεγμένες για έλεγχο - ακούστε τες (το F5 αναπαράγει την ευθυγραμμισμένη γραμμή, το Shift+F5 την αρχική) και επιλέξτε όσες είναι σωστές.", + "changeXOfY": "Αλλαγή {0} από {1}", + "previousChange": "Προηγούμενη αλλαγή", + "nextChange": "Επόμενη αλλαγή", + "startShift": "Έναρξη", + "endShift": "Λήξη", + "statusRetimed": "Επαναχρονίστηκε", + "statusMovedWithNeighbours": "Μετακινήθηκε μαζί με τις γειτονικές", + "statusLargeMoveUnconfirmed": "Μεγάλη μετακίνηση - ακούστε, επιλέξτε για εφαρμογή", + "statusUnchanged": "Ήδη στη θέση του", + "statusNoSpeech": "Χωρίς ομιλία", + "statusShiftTooLarge": "Διατηρήθηκε - πολύ μεγάλη μετατόπιση", + "statusFailed": "Διατηρήθηκε - ο ευθυγραμμιστής απέτυχε", + "statusAdjustedByHand": "Προσαρμόστηκε χειροκίνητα", + "alignedWaveformHint": "Σύρετε μια γραμμή για να τη μετακινήσετε ή σύρετε τα άκρα της για να αλλάξετε την έναρξη ή τη λήξη της.", + "playLine": "Αναπαραγωγή γραμμής", + "undoAdjustment": "Αναίρεση προσαρμογής", + "playPause": "Αναπαραγωγή / παύση", + "playAligned": "Ευθυγραμμισμένο", + "playAlignedHint": "Αναπαραγωγή της επιλεγμένης γραμμής με τους ευθυγραμμισμένους χρονικούς κώδικές της", + "apply": "Εφαρμογή", + "applyHint": "Αποεπιλέξτε μια γραμμή για να διατηρήσει τους αρχικούς χρονικούς κώδικές της. Κάντε διπλό κλικ σε μια γραμμή για αναπαραγωγή. Οι γραμμές μπορούν επίσης να μετακινηθούν ή να αλλάξουν μέγεθος στην ευθυγραμμισμένη κυματομορφή." + }, "beautifyTimeCodesProfile": { "title": "Επεξεργασία προφίλ καλλωπισμού χρονικών κωδίκων", "loadPreset": "Φόρτωση προεπιλογής", "presetDefault": "Προεπιλογή", "presetNetflix": "Netflix", "presetSdi": "SDI", - "general": "Γενικά", + "saveAsProfile": "Αποθήκευση ως προφίλ...", + "deleteProfile": "Διαγραφή προφίλ", + "profileName": "Όνομα προφίλ", + "overwriteProfileX": "Υπάρχει ήδη προφίλ με όνομα \"{0}\". Αντικατάσταση;", + "deleteProfileX": "Διαγραφή προφίλ \"{0}\";", "gap": "Κενό", "gapSuffix": "καρέ (ενημερώνει επίσης τα μη μηδενικά πεδία κενού παρακάτω)", "milliseconds": "ms", @@ -1993,9 +2124,12 @@ "hintChaining": "Πώς να αντιμετωπίζονται υπότιτλοι που σχεδόν αγγίζονται — επέκταση μαζί ή απώθηση, ανάλογα με το αν υπάρχει αλλαγή πλάνου ενδιάμεσα." }, "bridgeGaps": { - "title": "Γεφύρωση κενών", "bridgeGapsSmallerThan": "Γεφύρωση κενών μικρότερων από (ms)", "minGap": "Ελάχιστο κενό (ms)", + "bridgeGapsSmallerThanFrames": "Γεφύρωση κενών μικρότερων από (καρέ)", + "minGapFrames": "Ελάχιστο κενό (καρέ)", + "calculateBridgeGapsSmallerThanDotDotDot": "Υπολογισμός ορίου γεφύρωσης κενών από ρυθμό καρέ...", + "useXMsAsBridgeGapsSmallerThan": "Γεφύρωση κενών μικρότερων από \"{0}\" χιλιοστά δευτερολέπτου;", "numberOfSmallGapsBridgedX": "Αριθμός μικρών κενών που γεφυρώθηκαν: {0}", "percentFoPrevious": "Κενό για το προηγούμενο (%)", "gapChange": "Αλλαγή κενού" @@ -2007,8 +2141,6 @@ "sortBy": { "title": "Ταξινόμηση υπότιτλων", "sortOrder": "Σειρά ταξινόμησης", - "sortByNumber": "Αριθμός", - "sortByStartTime": "Χρόνος έναρξης", "sortByEndTime": "Χρόνος τέλους" }, "batchConvert": { @@ -2031,6 +2163,7 @@ "addColor": "Προσθήκη χρώματος", "deleteLinesWithSpecificActorsOrStyles": "Διαγραφή γραμμών με ηθοποιούς ή στυλ (διαχωρίστε πολλαπλά με κόμμα)", "useSourceStylesIfPossible": "Χρήση στυλ πηγής αν είναι δυνατόν", + "keepSourceEmbeddedFonts": "Διατήρηση ενσωματωμένων γραμματοσειρών της πηγής (υποσέλιδο)", "editStyles": "Επεξεργασία στυλ", "editProperties": "Επεξεργασία ιδιοτήτων", "editAttachments": "Επεξεργασία συνημμένων", @@ -2051,7 +2184,6 @@ "assaChangeStyleFromStyle": "Αλλαγή στυλ από", "assaChangeStyleToStyle": "σε", "assaChangeStyleImportStyle": "Εισαγωγή στυλ...", - "assaChangeStyleImportedX": "Εισήχθη: {0}", "assaChangeStyleTrimUnusedStyles": "Περικοπή αχρησιμοποίητων στυλ", "assaChangeStylePropertiesTitle": "Αλλαγή ιδιοτήτων στυλ", "assaChangeStylePropertiesInfo": "Αλλάζει τα επιλεγμένα πεδία σε κάθε στυλ του αρχείου, χωρίς να επηρεάζει τα ίδια τα στυλ.", @@ -2063,13 +2195,52 @@ "convertColorsToDialogReBreakLines": "Επαναδιαχωρισμός γραμμών", "snapTimeCodesToFramesInfo": "Στρογγυλοποιεί κάθε χρόνο έναρξης και λήξης στο πλησιέστερο καρέ. Ο ρυθμός καρέ διαβάζεται από αρχείο βίντεο με το ίδιο όνομα με το αρχείο υπότιτλων, εκτός αν επιλεγεί σταθερός ρυθμός καρέ.", "addFolderDotDotDot": "Προσθήκη φακέλου...", + "addFolderRecursiveDotDotDot": "Προσθήκη φακέλου αναδρομικά...", "selectFolderToConvert": "Επιλογή φακέλου με αρχεία προς μετατροπή", "includeSubfolders": "Συμπερίληψη υποφακέλων κατά την προσθήκη φακέλου", + "videoFilesWhenAddingFolder": "Αρχεία βίντεο (mkv, mp4, ts...) κατά την προσθήκη φακέλου", + "videoFilesAsk": "Ερώτηση", + "videoFilesInclude": "Προσθήκη των κομματιών υπότιτλων τους", + "videoFilesSkip": "Παράλειψη", + "folderContainsXVideoFiles": "Ο φάκελος περιέχει {0} αρχείο(α) βίντεο (mkv, mp4, ts...).\n\nΝα προστεθούν και τα ενσωματωμένα κομμάτια υπότιτλών τους ή μόνο τα αρχεία υπότιτλων;", + "addVideoFiles": "Προσθήκη αρχείων βίντεο", + "skipVideoFiles": "Μόνο αρχεία υπότιτλων", + "doNotAskAgainVideoFiles": "Να μην ερωτηθώ ξανά (αλλαγή στις Ρυθμίσεις μαζικής μετατροπής)", + "settingsSectionOutput": "Έξοδος", + "settingsSectionAddingFiles": "Προσθήκη αρχείων", + "settingsSectionImageBasedSubtitles": "Υπότιτλοι βασισμένοι σε εικόνα (OCR)", + "settingsSectionOther": "Άλλα", "keepSourceFileTimestamp": "Διατήρηση ημερομηνίας/ώρας αρχείου πηγής στα αρχεία εξόδου", - "scanningFolderX": "Σάρωση {0}..." + "preventSleepWhileConverting": "Αποτροπή αναστολής λειτουργίας του υπολογιστή κατά τη μετατροπή", + "scanningFolderX": "Σάρωση {0}...", + "transportStreamSettings": "Ρυθμίσεις Transport Stream", + "transportStreamSettingsDotDotDot": "Ρυθμίσεις Transport Stream...", + "transportStreamSettingsInfo": "Ισχύει για κομμάτια υπότιτλων που εξάγονται από αρχεία Transport Stream (.ts/.m2ts). Η θέση και το μέγεθος βίντεο επηρεάζουν μόνο κομμάτια εικόνας DVB που εξάγονται σε μορφή βασισμένη σε εικόνα.", + "transportStreamOverrideXPosition": "Παράκαμψη αρχικής θέσης X", + "transportStreamOverrideYPosition": "Παράκαμψη αρχικής θέσης Y", + "transportStreamOverrideVideoSize": "Παράκαμψη αρχικού μεγέθους βίντεο", + "transportStreamBottomMargin": "Κάτω περιθώριο", + "transportStreamFileNameEnding": "Κατάληξη ονόματος αρχείου", + "transportStreamFileNameEndingInfo": "Προστίθεται πριν από την επέκταση για κάθε κομμάτι που εξάγεται. Αφήστε κενό για χρήση του κανονικού επιθήματος γλώσσας.", + "transportStreamOnlyTeletext": "Μόνο teletext", + "transportStreamGetSizeFromVideo": "Λήψη μεγέθους από βίντεο...", + "twoLetterLanguageCodeUppercase": "Διγράμματος κωδικός γλώσσας (κεφαλαία)", + "threeLetterLanguageCodeUppercase": "Τριγράμματος κωδικός γλώσσας (κεφαλαία)", + "formatContainsDotDotDot": "Η μορφή περιέχει...", + "extensionIsDotDotDot": "Η επέκταση είναι...", + "statusIsError": "Η κατάσταση είναι σφάλμα", + "forcedTracksOnly": "Μόνο εξαναγκασμένα κομμάτια", + "xConvertedYFailedInZ": "{0:#,###,##0} μετατράπηκαν, {1:#,###,##0} απέτυχαν σε {2}", + "xNotProcessed": "{0:#,###,##0} δεν επεξεργάστηκαν", + "removeUnicodeControlCharactersTitle": "Αφαίρεση χαρακτήρων ελέγχου Unicode", + "removeUnicodeControlCharactersInfo": "Αφαιρεί από το κείμενο τους αόρατους χαρακτήρες ελέγχου κατεύθυνσης Unicode (LRM, RLM, LRE, RLE, PDF, LRO και RLO) και αντικαθιστά τα αδιάσπαστα κενά με κανονικά κενά.", + "preset": "Προεπιλογή", + "savePresetDotDotDot": "Αποθήκευση προεπιλογής...", + "deletePreset": "Διαγραφή προεπιλογής", + "presetName": "Όνομα προεπιλογής", + "deletePresetX": "Διαγραφή προεπιλογής \"{0}\";" }, "changeCasing": { - "title": "Αλλαγή πεζών-κεφαλαίων", "fixNames": "Διόρθωση ονομάτων", "namesX": "Ονόματα: {0:#,##0}", "hitsX": "Ευρέσεις: {0:#,##0}", @@ -2080,9 +2251,7 @@ "allUppercase": "Όλα κεφαλαία", "allLowercase": "Όλα πεζά" }, - "changeFormatting": { - "title": "Αλλαγή μορφοποίησης" - }, + "changeFormatting": {}, "convertActors": { "title": "Μετατροπή ηθοποιών", "convertActorFrom": "Μετατροπή ηθοποιού από", @@ -2111,18 +2280,12 @@ "outputFormatAssa": "Advanced Sub Station Alpha (.ass)", "style1": "Στυλ 1", "style2": "Στυλ 2", - "outlineWidth": "Πλάτος περιγράμματος", - "shadowWidth": "Πλάτος σκιάς", - "alignTop": "Πάνω", - "alignBottom": "Κάτω", - "merge": "Συγχώνευση", - "pleaseLoadTwoSubtitles": "Παρακαλώ φορτώστε δύο υπότιτλους" + "merge": "Συγχώνευση" }, "splitSubtitle": { "title": "Διαίρεση υπότιτλου", "numberOfEqualParts": "Αριθμός ίσων τμημάτων", "saveSplitParts": "Αποθήκευση διαιρεμένων τμημάτων", - "subtitleSplitIntoXParts": "Ο υπότιτλος διαιρέθηκε σε {0} τμήματα.", "xPartsSavedInFormatYToFolder": "{0} τμήματα αποθηκεύτηκαν σε μορφή {1} στον φάκελο:" }, "splitBreakLongLines": { @@ -2156,18 +2319,17 @@ "columnMerge": "Συγχώνευση", "columnFirst": "Πρώτη γραμμή", "columnSecond": "Δεύτερη γραμμή", - "columnMerged": "Συγχωνευμένο", - "selectAll": "Επιλογή όλων", - "inverseSelection": "Αντιστροφή επιλογής" + "columnMerged": "Συγχωνευμένο" }, "mergeLinesWithSameText": { - "title": "Συγχώνευση γραμμών με ίδιο κείμενο", "maxMsBetweenLines": "Μέγιστα χιλιοστά μεταξύ γραμμών", - "includeIncrementingLines": "Συμπερίληψη γραμμών με αυξανόμενο κείμενο" + "maxFramesBetweenLines": "Μέγιστα καρέ μεταξύ γραμμών", + "includeIncrementingLines": "Συμπερίληψη γραμμών με αυξανόμενο κείμενο", + "includeRollUpCaptions": "Συμπερίληψη κυλιόμενων λεζάντων (roll-up)" }, "mergeLinesWithSameTimeCodes": { - "title": "Συγχώνευση γραμμών με ίδιους χρονικούς κώδικες", "maxMsDifference": "Μέγιστη διαφορά (χιλιοστά)", + "maxFramesDifference": "Μέγιστη διαφορά (καρέ)", "makeDialog": "Δημιουργία διαλόγων" }, "netflixCheckAndFix": { @@ -2190,7 +2352,6 @@ "shotChanges": "Αλλαγές πλάνου", "startNumberSpellOut": "Ολογράφως αριθμός στην αρχή", "textforHiUseBrackets": "Κείμενο για HI, χρήση αγκυλών", - "frameRate": "Ρυθμός καρέ", "twoFrameGrap": "Κενό δύο καρέ", "whiteSpace": "Κενό διάστημα", "childrensProgram": "Παιδικό πρόγραμμα", @@ -2228,7 +2389,6 @@ "italicsFixed": "Διορθώθηκαν πλάγια", "italicsNotAllowed": "Τα πλάγια δεν επιτρέπονται", "bridgeGapsXToYFrames": "Κενό 3-{0} καρέ => κενό 2 καρέ", - "startNumberSpellOutComment": "Όταν ένας αριθμός ξεκινά μια πρόταση, πρέπει πάντα να γράφεται ολογράφως", "maximumXCharactersPerSecond": "Μέγιστο {0} χαρακτήρες ανά δευτερόλεπτο" }, "imageBasedEdit": { @@ -2237,10 +2397,8 @@ "resizeImagesDotDotDot": "Αλλαγή μεγέθους εικόνων...", "adjustBrightnessDotDotDot": "Προσαρμογή φωτεινότητας...", "adjustAlphaDotDotDot": "Προσαρμογή άλφα...", - "centerHorizontally": "Οριζόντιο κεντράρισμα", "cropImages": "Περικοπή εικόνων", "importTimeCodes": "Εισαγωγή χρονικών κωδίκων...", - "setTextForSubtitle": "Ορισμός κειμένου για υπότιτλο", "setText": "Ορισμός κειμένου", "screenWidth": "Πλάτος οθόνης", "screenHeight": "Ύψος οθόνης", @@ -2271,15 +2429,31 @@ "letterboxCustom": "Προσαρμοσμένο ύψος μπάρας", "barHeightPx": "Ύψος μπάρας (px)", "titleSafePercent": "Ασφαλής περιοχή τίτλου (%)", + "moveCaptions": "Μετακίνηση λεζάντων", + "moveCaptionsDotDotDot": "Μετακίνηση λεζάντων...", + "moveCaptionsTo": "Μετακίνηση λεζάντων", + "moveCaptionsIntoBars": "Μέσα στις μαύρες λωρίδες letterbox (εκτός εικόνας)", + "moveCaptionsIntoPicture": "Μέσα στην εικόνα (έξω από τις λωρίδες)", + "moveCaptionsOffset": "Απόσταση από την άκρη (px)", + "letterboxBarHeightXPxYOfZCaptionsWillMove": "Ύψος λωρίδας: {0} px - θα μετακινηθούν {1} από {2} λεζάντες", + "removeFadeInOut": "Αφαίρεση ξεθωριάσματος εισόδου/εξόδου", + "removeFadeInOutXLinesRemoved": "{0} γραμμές ξεθωριάσματος συγχωνεύτηκαν με τις γραμμές στις οποίες ανήκουν", + "removeFadeInOutNothingFound": "Δεν βρέθηκαν γραμμές ξεθωριάσματος εισόδου/εξόδου", "positionSummary": "{0}×{1} - {2} υπότιτλοι - ύψος μπάρας: {3} px", "xInActivePicture": "{0} στην εικόνα", "xInTopBar": "{0} στην πάνω μπάρα", "xInBottomBar": "{0} στην κάτω μπάρα", "noImageSubtitlesLoaded": "Δεν έχουν φορτωθεί υπότιτλοι εικόνας", - "imageBasedFormatNotSupported": "Η μορφή υπότιτλων βασισμένων σε εικόνα δεν βρέθηκε/δεν υποστηρίζεται." + "imageBasedFormatNotSupported": "Η μορφή υπότιτλων βασισμένων σε εικόνα δεν βρέθηκε/δεν υποστηρίζεται.", + "frameRateDeclaredInFile": "Ρυθμός καρέ που δηλώνεται στο αρχείο Blu-ray sup", + "frameRateDetectedFromTimeCodesX": "Ρυθμός καρέ που εντοπίστηκε από τους χρονικούς κώδικες του Blu-ray sup (το αρχείο δηλώνει {0})", + "frameRateFromVideo": "Ρυθμός καρέ του βίντεο", + "frameRateCurrent": "Τρέχων ρυθμός καρέ", + "frameRateSetManually": "Ρυθμός καρέ ορισμένος χειροκίνητα", + "frameRateUsageInfo": "Οι εξαγωγές Blu-ray sup, BDN XML, DOST και FCP γράφονται με αυτόν τον ρυθμό καρέ. Δεν αλλάζει κανέναν χρόνο - χρησιμοποιήστε την \"Αλλαγή ρυθμού καρέ\" γι' αυτό.", + "useVideoFrameRateXInsteadOfY": "Ο ρυθμός καρέ του βίντεο είναι {0}, αλλά ο υπότιτλος είναι ορισμένος σε {1}.\n\nΧρήση του ρυθμού καρέ του βίντεο ({0});" }, "removeTextForHearingImpaired": { - "title": "Αφαίρεση κειμένου για άτομα με προβλήματα ακοής", "interjections": "Επιφωνήματα", "skipIfStartWith": "Παράλειψη αν ξεκινά με", "removeTextBetween": "Αφαίρεση κειμένου μεταξύ", @@ -2298,7 +2472,6 @@ "removeInterjections": "Αφαίρεση επιφωνημάτων", "linesFoundX": "Γραμμές που βρέθηκαν: {0}" }, - "pickAlignmentTitle": "Επιλογή στοίχισης", "removeUnicodeCharacters": { "title": "Αφαίρεση/αντικατάσταση χαρακτήρων Unicode", "charactersFoundX": "Χαρακτήρες Unicode που βρέθηκαν: {0}", @@ -2308,13 +2481,13 @@ "pickFontNameInstalledFonts": "Εγκατεστημένες γραμματοσειρές", "pickFontNameCollectedFonts": "Συλλεγμένες γραμματοσειρές", "colorPickerTitle": "Επιλογή χρώματος", + "colorPickerBrightness": "Φωτεινότητα", "filterLayersTitle": "Φιλτράρισμα επιπέδων για εμφάνιση", "filterLayersHideFromWaveform": "Απόκρυψη από κυματομορφή/φασματογράφημα", "filterLayersHideFromSubtitleGrid": "Απόκρυψη από το πλέγμα υπότιτλων", "filterLayersHideFromVideoPreview": "Απόκρυψη από την προεπισκόπηση βίντεο", "pickSubtitleFormat": "Επιλογή μορφής υπότιτλων", "pickSubtitleFormatImageBasedNoPreview": "Μορφή υπότιτλων βασισμένη σε εικόνα.\nΤα bitmap δημιουργούνται κατά τη μετατροπή - χωρίς προεπισκόπηση κειμένου.", - "pickLayerTitle": "Ορισμός επιπέδου", "recentColors": "Πρόσφατα χρώματα", "fixCommonErrorsSettingsTitle": "Ρυθμίσεις διόρθωσης κοινών σφαλμάτων" }, @@ -2359,59 +2532,51 @@ "correctWordsX": "Σωστές λέξεις: {0}", "namesX": "Ονόματα: {0}", "addedToDictionaryX": "Προστέθηκαν στο λεξικό: {0}", - "doNotShowThisAgain": "Να μην εμφανιστεί ξανά αυτό το μήνυμα" + "doNotShowThisAgain": "Να μην εμφανιστεί ξανά αυτό το μήνυμα", + "editUseAlwaysList": "Επεξεργασία λίστας \"Χρήση πάντα\"...", + "useAlwaysListTitle": "Ορθογραφικός έλεγχος - λίστα \"Χρήση πάντα\"", + "useAlwaysListHeader": "Λίστα \"Χρήση πάντα\"", + "useAlwaysListDescription": "Λέξεις που ο ορθογραφικός έλεγχος αντικαθιστά αυτόματα, αποθηκευμένες μέσω \"Αλλαγή όλων\" και \"Χρήση πάντα\". Ένα ζεύγος χρησιμοποιείται μόνο όταν η λέξη είναι ανορθόγραφη.", + "useAlwaysWord": "Ανορθόγραφη λέξη", + "xPairs": "{0:#,##0} ζεύγη", + "xUnusedPairs": "{0:#,##0} δεν χρησιμοποιήθηκαν ποτέ", + "useAlwaysUnusedHint": "Δεν χρησιμοποιήθηκε ποτέ - αυτή η λέξη είναι σωστά γραμμένη (ή είναι όνομα ή λέξη χρήστη), οπότε ο ορθογραφικός έλεγχος δεν την επισημαίνει ποτέ", + "removeUnused": "Αφαίρεση αχρησιμοποίητων", + "useAlwaysEmpty": "Δεν υπάρχουν ακόμη ζεύγη \"Χρήση πάντα\" για αυτή τη γλώσσα", + "useAlwaysEmptyHint": "Τα ζεύγη προστίθενται όταν πατάτε \"Αλλαγή όλων\" ή \"Χρήση πάντα\" στον ορθογραφικό έλεγχο.", + "useAlwaysNoMatch": "Κανένα ζεύγος δεν ταιριάζει με την αναζήτηση", + "useAlwaysSameWord": "Η αντικατάσταση πρέπει να διαφέρει από τη λέξη.", + "useAlwaysRememberOff": "Η επιλογή \"Απομνημόνευση λίστας 'Χρήση πάντα'\" είναι απενεργοποιημένη στις Ρυθμίσεις, οπότε ο ορθογραφικός έλεγχος δεν χρησιμοποιεί ούτε αποθηκεύει αυτή τη λίστα." }, "video": { "burnIn": { "title": "Δημιουργία βίντεο με ενσωματωμένους υπότιτλους", - "infoAssaOff": "Σημείωση: Υποστηρίζεται μορφοποίηση Advanced SubStation Alpha.", - "infoAssaOn": "Σημείωση: Θα χρησιμοποιηθεί μορφοποίηση Advanced SubStation Alpha :)", - "xGeneratedWithBurnedInSubsInX": "Το \"{0}\" δημιουργήθηκε με ενσωματωμένους υπότιτλους σε {1}.", - "timeRemainingMinutes": "Χρόνος που απομένει: {0} λεπτά", - "timeRemainingOneMinute": "Χρόνος που απομένει: Ένα λεπτό", - "timeRemainingSeconds": "Χρόνος που απομένει: {0} δευτερόλεπτα", - "timeRemainingAFewSeconds": "Χρόνος που απομένει: Λίγα δευτερόλεπτα", - "timeRemainingMinutesAndSeconds": "Χρόνος που απομένει: {0} λεπτά και {1} δευτερόλεπτα", - "timeRemainingOneMinuteAndSeconds": "Χρόνος που απομένει: Ένα λεπτό και {0} δευτερόλεπτα", - "targetFileName": "Όνομα αρχείου προορισμού: {0}", "targetFileSize": "Μέγεθος αρχείου προορισμού (απαιτεί κωδικοποίηση 2 περασμάτων)", "fileSizeMb": "Μέγεθος αρχείου σε MB", "matchSourceVideoSize": "Αντιστοίχιση μεγέθους βίντεο πηγής", - "passX": "Πέρασμα {0}", - "encoding": "Κωδικοποίηση", "bitRate": "Ρυθμός bit", "totalBitRateX": "Συνολικός ρυθμός bit: {0}", "sampleRate": "Ρυθμός δειγματοληψίας", "audio": "Ήχος", - "stereo": "Στέρεο", "preset": "Προεπιλογή", + "tune": "Βελτιστοποίηση (tune)", "pixelFormat": "Μορφή pixel", "crf": "CRF", - "tuneFor": "Συντονισμός για", - "alignRight": "Στοίχιση δεξιά", - "getStartPosition": "Λήψη θέσης έναρξης", - "getEndPosition": "Λήψη θέσης τέλους", "useSource": "Χρήση πηγής", - "useSourceFolder": "Χρήση φακέλου πηγής", - "useSourceResolution": "Χρήση ανάλυσης πηγής", - "outputSettings": "Αρχείο/φάκελος εξόδου...", "fontSizeFactor": "Συντελεστής μεγέθους γραμματοσειράς", "boxType": "Τύπος πλαισίου", - "fixRightToLeft": "Διόρθωση δεξιά-προς-αριστερά", - "cut": "Αποκοπή", "fromTime": "Από χρόνο", "toTime": "Έως χρόνο", "audioEncoding": "Κωδικοποίηση ήχου", "outputProperties": "Ιδιότητες εξόδου...", "videoFileSize": "Μέγεθος αρχείου βίντεο", "oneBox": "Ένα πλαίσιο", - "boxPerLine": "Πλαίσιο ανά γραμμή", - "logoInfo": "Επιλέξτε μια εικόνα PNG και σύρετέ την για να την τοποθετήσετε στο βίντεο." + "logoInfo": "Επιλέξτε μια εικόνα PNG και σύρετέ την για να την τοποθετήσετε στο βίντεο.", + "noSubtitlesGenerateAnyway": "Δεν υπάρχουν γραμμές υπότιτλων για ενσωμάτωση.\n\nΔημιουργία του βίντεο χωρίς υπότιτλους;", + "noSubtitlesInXItemsGenerateAnyway": "{0} στοιχείο(α) της παρτίδας δεν έχουν γραμμές υπότιτλων για ενσωμάτωση.\n\nΔημιουργία αυτών των βίντεο χωρίς υπότιτλους;" }, "videoTransparent": { - "title": "Δημιουργία διαφανούς βίντεο με υπότιτλους", - "infoAssaOff": "Σημείωση: Υποστηρίζεται μορφοποίηση Advanced SubStation Alpha.", - "infoAssaOn": "Σημείωση: Θα χρησιμοποιηθεί μορφοποίηση Advanced SubStation Alpha :)" + "title": "Δημιουργία διαφανούς βίντεο με υπότιτλους" }, "audioToText": { "title": "Ομιλία σε κείμενο", @@ -2419,6 +2584,21 @@ "translateToEnglish": "Μετάφραση στα Αγγλικά", "transcribing": "Μεταγραφή...", "transcribingXOfY": "Μεταγραφή {0} από {1}...", + "linesTranscribedXOfYFailedZ": "Μεταγράφηκαν {0} από {1} γραμμές - {2} απέτυχαν ή δεν είχαν ομιλία.\n\nΟι γραμμές που απέτυχαν διατηρούν το τρέχον κείμενό τους. Εφαρμογή των μεταγραμμένων γραμμών ή νέα προσπάθεια για όσες απέτυχαν (μπορείτε πρώτα να αλλάξετε μηχανή ή ρυθμίσεις);", + "linesTranscribedXOfYCancelled": "Η μεταγραφή ακυρώθηκε μετά από {0} από {1} γραμμές.\n\nΕφαρμογή των μεταγραμμένων γραμμών; Οι υπόλοιπες γραμμές διατηρούν το τρέχον κείμενό τους.", + "noLinesTranscribed": "Καμία από τις {0} γραμμές δεν ήταν δυνατό να μεταγραφεί.\nΕλέγξτε το αρχείο καταγραφής εργαλείων για λεπτομέρειες.", + "applyTranscribedLines": "Εφαρμογή μεταγραμμένων", + "retryFailedLines": "Επανάληψη αποτυχημένων", + "isolateSpeech": "Απομόνωση ομιλίας (αργό)", + "isolateSpeechHint": "Αφαιρεί τη μουσική και τα ηχητικά εφέ πριν από τη μεταγραφή. Δίνει λιγότερες χαμένες γραμμές και καλύτερο χρονισμό σε ήχο με δυνατή μουσική, αλλά διαρκεί περίπου όσο ο ίδιος ο ήχος με GPU - και πολλές φορές περισσότερο χωρίς.", + "isolatingSpeech": "Απομόνωση ομιλίας...", + "isolateSpeechFailed": "Δεν ήταν δυνατή η απομόνωση της ομιλίας - γίνεται μεταγραφή του αρχικού ήχου.", + "detectSpeakers": "Εντοπισμός ομιλητών", + "detectSpeakersHint": "Ξεκινά κάθε γραμμή με τον ομιλητή, π.χ. \"(speaker 0)\", χρησιμοποιώντας τη διάκριση ομιλητών Sortformer της NVIDIA (έως 8 ομιλητές). Λειτουργεί καλύτερα με μηχανές που διαιρούν τις γραμμές όπου αλλάζει ο ομιλητής, όπως Parakeet και Cohere.", + "detectSpeakersNeedsNewerCrispAsr": "Ο \"Εντοπισμός ομιλητών\" απαιτεί Crisp ASR v{0} ή νεότερο, αλλά είναι εγκατεστημένο το v{1}.\n\nΕνημερώστε το Crisp ASR (το κουμπί με το γρανάζι δίπλα στη μηχανή) ή απενεργοποιήστε τον \"Εντοπισμό ομιλητών\".", + "xNeedsNewerCrispAsr": "Το {0} απαιτεί Crisp ASR v{1} ή νεότερο, αλλά είναι εγκατεστημένο το v{2}.\n\nΛήψη και εγκατάσταση της ενημέρωσης τώρα;", + "vad": "VAD", + "vadHint": "Ανίχνευση φωνητικής δραστηριότητας: μεταγράφεται μόνο η ομιλία, κομμένη εκεί όπου κάνει παύση ο ομιλητής.\nAuto: Silero, και μόνο για τις μηχανές που το χρειάζονται (Cohere, Mega, Index-Echo).\nSilero, FireRedVAD, Whisper-VAD, WebRTC: πάντα ενεργό, για κάθε μηχανή Crisp ASR. Το FireRedVAD είναι το πιο ακριβές, το Whisper-VAD δίνει τα λιγότερα και καθαρότερα κοψίματα αλλά είναι το πιο αργό.\nΤο Index-Echo χρησιμοποιεί πάντα Silero. Τα --vad, --vad-model ή --chunk-seconds στις ρυθμίσεις για προχωρημένους έχουν προτεραιότητα.", "inputLanguage": "Γλώσσα εισόδου", "advancedWhisperSettings": "Προηγμένες παράμετροι ομιλίας-σε-κείμενο", "downloadingSpeechToTextEngine": "Λήψη μηχανής ομιλίας-σε-κείμενο", @@ -2433,8 +2613,6 @@ "addCustomModelDotDotDot": "Προσθήκη προσαρμοσμένου μοντέλου...", "customModelHelp": "Μπορείτε να χρησιμοποιήσετε το δικό σας μοντέλο: επιλέξτε το αρχείο μοντέλου (whisper.cpp ggml '.bin') ή τον φάκελο μοντέλου (φάκελος faster-whisper με 'model.bin' μέσα). Αντιγράφεται στον φάκελο μοντέλων και προστίθεται στην παραπάνω λίστα.", "viewToolsLogFile": "Προβολή αρχείου καταγραφής εργαλείων", - "reDownloadX": "Νέα λήψη {0}", - "downloadX": "Λήψη {0}", "updateXTitle": "Ενημέρωση {0};", "updateXMessage": "Μια νεότερη έκδοση του {0} είναι διαθέσιμη.{1}{1}Λήψη και εγκατάσταση της ενημέρωσης τώρα;", "downloadingSpeechToTextModel": "Λήψη μοντέλου ομιλίας-σε-κείμενο", @@ -2446,14 +2624,6 @@ "fixCasing": "Διόρθωση πεζών-κεφαλαίων", "addPeriods": "Προσθήκη τελειών", "changeUnderlineToColor": "Αλλαγή υπογράμμισης σε χρώμα", - "cueBuilding": "Δημιουργία υπότιτλων (MLX Whisper)", - "cueRebuild": "Αναδημιουργία υπότιτλων από χρονικές σημάνσεις λέξεων", - "cueMaxChars": "Μέγιστοι χαρακτήρες ανά υπότιτλο", - "cueMaxSeconds": "Μέγιστα δευτερόλεπτα ανά υπότιτλο", - "cueMaxCps": "Μέγιστοι χαρακτήρες ανά δευτερόλεπτο", - "vocabularyPrompt": "Προτροπή λεξιλογίου (ονόματα, τοποθεσίες, όροι)", - "beamSize": "Μέγεθος beam", - "beamSizeNote": "Το 0 αποκωδικοποιεί ταχύτερα. Το 5 δίνει την καλύτερη ακρίβεια (όπως το Faster Whisper) με περίπου 20-30% περισσότερο χρόνο. Τιμές πάνω από 5 σπάνια βοηθούν.", "removeNonSpeechLines": "Αφαίρεση γραμμών χωρίς ομιλία", "removeNonSpeechLinesHint": "Απόρριψη γραμμών που περιγράφουν μόνο ήχο, όπως \"[Μουσική]\" ή \"(κύματα που σκάνε)\"", "removeRepeatedLines": "Αφαίρεση επαναλαμβανόμενων γραμμών", @@ -2489,14 +2659,11 @@ }, "textToSpeech": { "title": "Κείμενο σε ομιλία", - "textToSpeechEngine": "Μηχανή κειμένου σε ομιλία", "reviewAudioSegments": "TTS - Επισκόπηση τμημάτων ήχου", "reviewAudioSegmentsHistory": "TTS - Επισκόπηση ιστορικού ήχου", "stability": "Σταθερότητα", "similarity": "Ομοιότητα", "speakerBoost": "Ενίσχυση ομιλητή", - "styleExaggeration": "Υπερβολή στυλ", - "regenerateAudioSelectedLine": "Αναδημιουργία ήχου για την επιλεγμένη γραμμή", "generateSpeechFromText": "Δημιουργία ομιλίας από κείμενο", "testVoice": "Δοκιμή φωνής", "addAudioToVideoFile": "Προσθήκη ήχου σε αρχείο βίντεο", @@ -2515,6 +2682,19 @@ "videoEncodingSettings": "TTS - Ρυθμίσεις κωδικοποίησης βίντεο", "elevenLabsSettings": "TTS - Ρυθμίσεις ElevenLabs", "elevenLabsSettingsResetHint": "Επαναφορά ρυθμίσεων ElevenLabs στις προεπιλεγμένες τιμές", + "provider": "Πάροχος", + "openAiCompatibleSettings": "TTS - Ρυθμίσεις συμβατές με OpenAI", + "customServerUrl": "Προσαρμοσμένο URL διακομιστή", + "customServerUrlHint": "Πλήρες endpoint ομιλίας ενός διακομιστή συμβατού με OpenAI, χρησιμοποιείται όταν ο πάροχος είναι \"Custom\", π.χ. http://localhost:8880/v1/audio/speech", + "customModels": "Προσαρμοσμένα μοντέλα", + "customModelsHint": "Ονόματα μοντέλων χωρισμένα με κόμμα που προσφέρονται όταν ο πάροχος είναι \"Custom\"", + "customVoices": "Προσαρμοσμένες φωνές", + "customVoicesHint": "Ονόματα φωνών χωρισμένα με κόμμα, χρησιμοποιούνται από τον πάροχο \"Custom\" και από μοντέλα OpenRouter που δεν δημοσιεύουν λίστα φωνών", + "instructions": "Οδηγίες", + "instructionsHint": "Προαιρετικό ύφος ομιλίας, π.χ. \"Μίλα με ήρεμη, ζεστή φωνή\". Αποστέλλεται μόνο σε μοντέλα που το υποστηρίζουν (όχι tts-1/tts-1-hd)", + "openAiSpeedHint": "Ταχύτητα ομιλίας από 0.25 έως 4.0 (1.0 είναι η κανονική). Δεν την υποστηρίζουν όλα τα μοντέλα", + "outputFormat": "Μορφή εξόδου", + "outputFormatHint": "Μορφή ήχου που ζητείται από τον διακομιστή. Το \"Auto\" ζητά MP3 και αλλάζει σε PCM για μοντέλα που επιστρέφουν μόνο PCM (π.χ. Gemini TTS)", "regenerateAudio": "Αναδημιουργία ήχου", "autoContinuePlaying": "Αυτόματη συνέχεια αναπαραγωγής", "playLine": "Αναπαραγωγή γραμμής", @@ -2523,6 +2703,13 @@ "addingAudioToVideoFileDotDotDot": "Προσθήκη ήχου σε αρχείο βίντεο...", "preparingMergeDotDotDot": "Προετοιμασία συγχώνευσης...", "importVoiceDotDotDot": "Εισαγωγή φωνής...", + "renameVoiceDotDotDot": "Μετονομασία φωνής...", + "renameVoiceTitle": "Μετονομασία φωνής", + "voiceXCouldNotBeRenamedX": "Η φωνή '{0}' δεν ήταν δυνατό να μετονομαστεί: {1}", + "deleteVoiceDotDotDot": "Διαγραφή φωνής...", + "deleteVoiceTitle": "Διαγραφή φωνής", + "deleteVoiceXQuestion": "Διαγραφή της φωνής '{0}';\n\nΗ ηχογράφηση αναφοράς και τα αρχεία της αφαιρούνται από τον δίσκο.", + "voiceXCouldNotBeDeletedX": "Η φωνή '{0}' δεν ήταν δυνατό να διαγραφεί: {1}", "voiceImportSuccessTitle": "Η φωνή εισήχθη", "voiceXImported": "Η φωνή '{0}' εισήχθη επιτυχώς", "voiceXCouldNotBeImported": "Η φωνή '{0}' δεν ήταν δυνατό να εισαχθεί - δείτε το αρχείο καταγραφής για λεπτομέρειες", @@ -2532,6 +2719,49 @@ "dropAudioFileHereToImportVoice": "Αφήστε αρχείο ήχου εδώ για εισαγωγή φωνής", "dropAudioFileHereHint": ".wav ή .mp3", "voiceCloneTranscriptTitle": "Εισαγάγετε τη μεταγραφή του ήχου (απαιτείται για κλωνοποίηση φωνής)", + "voiceManagerTitle": "TTS - Διαχείριση φωνών", + "voiceManagerDotDotDot": "Διαχείριση φωνών...", + "voiceManagerMenuItem": "Διαχείριση φωνών TTS...", + "speakFromCurrentLineTitle": "Εκφώνηση από την τρέχουσα γραμμή", + "speakFromCurrentLineDotDotDot": "Εκφώνηση από την τρέχουσα γραμμή...", + "speakFromCurrentLineHint": "Διαβάζει δυνατά τις γραμμές τη μία μετά την άλλη, ξεκινώντας από την τρέχουσα γραμμή. Πατήστε Escape ή κάντε κλικ σε άλλη γραμμή για διακοπή.", + "stopSpeaking": "Διακοπή εκφώνησης", + "speakingLineX": "Εκφώνηση γραμμής {0}... (Escape για διακοπή)", + "playWithSpeechTitle": "Αναπαραγωγή με ομιλία", + "playWithSpeechDotDotDot": "Αναπαραγωγή με ομιλία...", + "playWithSpeechHint": "Αναπαράγει το βίντεο και εκφωνεί κάθε γραμμή όταν το βίντεο φτάνει σε αυτήν. Παύση και αναζήτηση ως συνήθως· πατήστε Escape για διακοπή.", + "lowerVideoVolumeWhileSpeaking": "Μείωση έντασης βίντεο κατά την εκφώνηση", + "pauseVideoWhenSpeechRunsLate": "Παύση βίντεο όταν η ομιλία επεκτείνεται στην επόμενη γραμμή", + "waitingForSpeechLineX": "Δημιουργία ομιλίας για τη γραμμή {0}... (Escape για διακοπή)", + "voiceKindClone": "Κλωνοποιημένη φωνή", + "voiceKindPreset": "Ενσωματωμένη", + "voiceKindModel": "Μοντέλο φωνής", + "voiceKindOnline": "Διαδικτυακή", + "transcript": "Απομαγνητοφώνηση", + "transcriptRequiredHint": "Απαιτείται - η μηχανή διαβάζει αυτή την απομαγνητοφώνηση της ηχογράφησης (ref-text) κατά την κλωνοποίηση", + "transcriptOptionalHint": "Προαιρετικό - μια ακριβής απομαγνητοφώνηση της ηχογράφησης βελτιώνει την κλωνοποίηση", + "saveTranscript": "Αποθήκευση απομαγνητοφώνησης", + "searchVoices": "Αναζήτηση φωνών", + "loadingVoicesDotDotDot": "Φόρτωση φωνών...", + "noVoicesForEngine": "Δεν υπάρχουν φωνές - εισαγάγετε μια ηχογράφηση ή κατεβάστε ένα πακέτο φωνών", + "noVoicesMatchFilter": "Καμία φωνή δεν ταιριάζει με την αναζήτηση", + "copyVoiceTo": "Αντιγραφή φωνής σε", + "copyVoiceToDotDotDot": "Αντιγραφή φωνής σε μηχανή...", + "copyingVoiceXToYDotDotDot": "Αντιγραφή φωνής '{0}' σε {1}...", + "voiceXCopiedToY": "Η φωνή '{0}' αντιγράφηκε σε {1}", + "voiceXCouldNotBeCopiedToY": "Η φωνή '{0}' δεν ήταν δυνατό να αντιγραφεί σε {1} - δείτε το αρχείο καταγραφής για λεπτομέρειες", + "voiceXAlreadyExistsInYContinue": "Υπάρχει ήδη φωνή με όνομα '{0}' στο {1}.\n\nΑντιγραφή παρ' όλα αυτά;", + "openVoicesFolder": "Άνοιγμα φακέλου φωνών", + "downloadVoicePacksDotDotDot": "Λήψη πακέτων φωνών...", + "downloadVoicePacksTitle": "TTS - Λήψη πακέτων φωνών", + "voicePackIntro": "Τα πακέτα φωνών είναι σύνολα σύντομων, καθαρών ηχογραφήσεων με απομαγνητοφωνήσεις, έτοιμα για κλωνοποίηση. Κάθε πακέτο εισάγεται στην επιλεγμένη μηχανή (οι φωνές που υπάρχουν ήδη παραλείπονται) και περιλαμβάνει ένα ATTRIBUTION.txt με την άδεια χρήσης και τις πηγές του.", + "voicePackDetailsXVoicesYSizeZLicense": "{0} φωνές · {1} · {2}", + "installToEngine": "Εγκατάσταση στη μηχανή", + "downloadAndInstall": "Λήψη και εγκατάσταση", + "selectAtLeastOneVoicePack": "Επιλέξτε τουλάχιστον ένα πακέτο φωνών", + "downloadingXDotDotDot": "Λήψη {0}...", + "installingXDotDotDot": "Εγκατάσταση {0}...", + "xVoicesInstalledYSkipped": "Εγκαταστάθηκαν {0} φωνές ({1} υπήρχαν ήδη)", "useSpeechToTextDotDotDot": "Χρήση ομιλίας-σε-κείμενο...", "autoCastMenuItem": "Εύρεση φωνών στο βίντεο και κλωνοποίηση...", "autoCastSpeakersTitle": "Φωνές που βρέθηκαν στο βίντεο", @@ -2550,7 +2780,7 @@ "cloneVoicePerLinePreparing": "Λήψη της φωνής κάθε γραμμής από το βίντεο...", "cloneVoicePerLineNeedsVideo": "Η κλωνοποίηση της φωνής κάθε γραμμής απαιτεί το βίντεο στο οποίο ανήκει ο υπότιτλος. Ανοίξτε το βίντεο και δοκιμάστε ξανά.", "cloneVoicePerLineNoClips": "Δεν ήταν δυνατή η λήψη ήχου από το βίντεο, οπότε δεν υπάρχει τίποτα για κλωνοποίηση. Ελέγξτε ότι το βίντεο έχει κομμάτι ήχου.", - "cloneVoicePerLineNeedsOriginalSubtitleX": "Το {0} μπορεί να κλωνοποιήσει μια γραμμή μόνο όταν γνωρίζει τι λέγεται στο βίντεο σε εκείνη τη γραμμή, κάτι που προέρχεται από τον υπότιτλο της αρχικής γλώσσας. Δεν έχει φορτωθεί αρχικός υπότιτλος, οπότε κάθε γραμμή θα εκφωνούνταν από την πρώτη εισαγόμενη φωνή αντί για τη φωνή του βίντεο.\n\nΑνοίξτε τον αρχικό υπότιτλο (Αρχείο > Άνοιγμα αρχικού...) δίπλα στη μετάφραση και δημιουργήστε ξανά.", + "cloneVoicePerLineTranscribeClipsXYZ": "Το {0} μπορεί να κλωνοποιήσει μια γραμμή μόνο όταν γνωρίζει τι λέγεται στο βίντεο σε εκείνη τη γραμμή. Αυτό δεν είναι γνωστό για {1} από {2} γραμμές - κανονικά προέρχεται από τον υπότιτλο της αρχικής γλώσσας (Αρχείο > Άνοιγμα αρχικού...).\n\nΜεταγραφή αυτών των γραμμών από το βίντεο με ομιλία σε κείμενο τώρα;\n\nΝαι: επιλέξτε τη μηχανή και τη γλώσσα που ομιλείται στο βίντεο, και τα αποσπάσματα αναφοράς μεταγράφονται πριν δημιουργηθεί η ομιλία.\nΌχι: δημιουργία παρ' όλα αυτά - αυτές οι γραμμές εκφωνούνται από μια συνηθισμένη φωνή αντί για τη φωνή του βίντεο.", "voiceCloneConsentTitle": "Κλωνοποίηση φωνής - πριν ξεκινήσετε", "voiceCloneConsentHeader": "Πρόκειται να κλωνοποιήσετε μια φωνή", "voiceCloneConsentIntro": "Η κλωνοποίηση αντιγράφει τη φωνή ενός πραγματικού ανθρώπου. Αυτό συνοδεύεται από κανόνες στις περισσότερες χώρες, και στην ΕΕ από νομικές υποχρεώσεις που βαρύνουν εσάς και όχι το Subtitle Edit. Διαβάστε το αυτό μία φορά.", @@ -2567,10 +2797,29 @@ "advancedTtsSettings": "Προηγμένες ρυθμίσεις TTS", "advancedTtsAudioProcessing": "Επεξεργασία ήχου", "advancedTtsOutput": "Έξοδος", + "advancedTtsVoiceCloning": "Κλωνοποίηση φωνής", + "cleanCloneReferences": "Καθαρισμός αναφορών κλωνοποίησης φωνής (Sidon)", + "cleanCloneReferencesDescription": "Αφαιρεί μουσική, θόρυβο και αντήχηση από τον ήχο από τον οποίο κλωνοποιείται μια φωνή: αποσπάσματα από το βίντεο και ηχογραφήσεις που εισήχθησαν στη διαχείριση φωνών. Χρησιμοποιεί το μοντέλο αποκατάστασης ομιλίας Sidon στο audio.cpp (λήψη 940 MB). Η κλωνοποιημένη φωνή δεν αντιγράφει πλέον το φόντο, αλλά πολύ σιγανή ή επικαλυπτόμενη ομιλία μπορεί να ακούγεται πνιχτή.", + "cleaningCloneReferencesDotDotDot": "Καθαρισμός αναφορών κλωνοποίησης φωνής...", + "downloadSidonTitle": "Λήψη Sidon;", + "downloadSidonQuestionX": "Ο καθαρισμός αναφορών κλωνοποίησης φωνής απαιτεί το μοντέλο αποκατάστασης ομιλίας Sidon ({0}).\n\nΛήψη τώρα; Χωρίς αυτό οι αναφορές χρησιμοποιούνται ως έχουν.", "proAudioPostProcessing": "Επαγγελματική μετεπεξεργασία ήχου", "proAudioPostProcessingDescription": "Εφαρμόζει ζεστασιά EQ, πύλη θορύβου, συμπίεση, κανονικοποίηση έντασης (-16 LUFS) και ξεθώριασμα εισόδου/εξόδου σε κάθε τμήμα.", + "generatingSpeechSegmentXOfY": "Δημιουργία ομιλίας: τμήμα {0} από {1}", + "adjustingSpeedSegmentXOfY": "Προσαρμογή ταχύτητας: τμήμα {0} από {1}", + "mergingAudioSegmentXOfY": "Συγχώνευση ήχου: τμήμα {0} από {1}", + "postProcessingSegmentXOfY": "Μεταεπεξεργασία: τμήμα {0} από {1}", "audioDucking": "Audio ducking", "audioDuckingDescription": "Μειώνει την ένταση του αρχικού ήχου του βίντεο και τον αναμειγνύει με τον ήχο TTS, ώστε το αρχικό ηχητικό υπόβαθρο να ακούγεται ακόμα αχνά.", + "removeOriginalSpeech": "Αφαίρεση αρχικής ομιλίας (αργό)", + "removeOriginalSpeechDescription": "Διατηρεί τη μουσική και τα ηχητικά εφέ του αρχικού βίντεο αλλά αφαιρεί την ομιλία του, και αναμειγνύει τη νέα ομιλία από πάνω. Διαρκεί περίπου όσο το ίδιο το βίντεο με GPU και πολλές φορές περισσότερο χωρίς. Με ενεργή τη μείωση έντασης ήχου (ducking), η ένταση εφαρμόζεται στη μουσική και τα εφέ. Ισχύει μόνο όταν η ομιλία προστίθεται στο αρχείο βίντεο.", + "removingOriginalSpeech": "Αφαίρεση αρχικής ομιλίας...", + "removeOriginalSpeechFailed": "Δεν ήταν δυνατή η αφαίρεση της αρχικής ομιλίας - η ομιλία προστέθηκε στο βίντεο χωρίς αυτή.", + "speechRemovalModel": "Μοντέλο", + "speechRemovalModelDescription": "Η Καλύτερη ποιότητα διατηρεί το μεγαλύτερο μέρος της μουσικής και των ηχητικών εφέ. Η Γρήγορη είναι περίπου τρεις φορές ταχύτερη, με λίγη περισσότερη απώλεια μουσικής.", + "speechRemovalBestQuality": "Καλύτερη ποιότητα (Mel-Band RoFormer)", + "speechRemovalFast": "Γρήγορη (HTDemucs)", + "videoTruncatedWarning": "Η εικόνα στο νέο αρχείο βίντεο σταματά μετά από {0}, αλλά το αρχικό βίντεο έχει διάρκεια {1} - η εικόνα θα παγώσει ενώ ο ήχος συνεχίζει.\n\nΤο αρχείο διατηρήθηκε. Δείτε το error-log.txt στον φάκελο δεδομένων του Subtitle Edit για την έξοδο του ffmpeg.", "originalVolumePercent": "Αρχική ένταση %", "vadSilenceCompression": "Συμπίεση σιωπής VAD", "vadSilenceCompressionDescription": "Συντομεύει τις παύσεις μεταξύ λέξεων πριν από την αλλαγή ρυθμού. Χρησιμοποιεί ανίχνευση φωνητικής δραστηριότητας για να συμπιέσει μόνο τα κενά σιωπής, αφήνοντας την ομιλία ανέπαφη. Αυτό είναι το προτιμώμενο πρώτο βήμα — μειώνει τη διάρκεια χωρίς καμία απώλεια ποιότητας.", @@ -2644,7 +2893,6 @@ "subtitleMergedLinesAppliedPlural": "Εφαρμόστηκαν {0} συγχωνεύσεις γραμμών από κείμενο σε ομιλία" }, "shotChanges": { - "titleGenerateOrImport": "Δημιουργία/εισαγωγή αλλαγών πλάνου", "generateShotChanges": "Δημιουργία αλλαγών πλάνου", "importShotChanges": "Εισαγωγή αλλαγών πλάνου", "generateShotChangesWithFfmpeg": "Δημιουργία αλλαγών πλάνου με ffmpeg", @@ -2654,7 +2902,9 @@ "timeCodeFormatColon": "Μορφή χρονικού κώδικα:", "shotChangesList": "Λίστα αλλαγών πλάνου", "shotChangesClearQuestion": "Είστε σίγουροι ότι θέλετε να εκκαθαρίσετε όλες τις αλλαγές πλάνου;", - "deleteSelectedShotChangeQuestion": "Είστε σίγουροι ότι θέλετε να διαγράψετε την επιλεγμένη αλλαγή πλάνου;" + "deleteSelectedShotChangeQuestion": "Είστε σίγουροι ότι θέλετε να διαγράψετε την επιλεγμένη αλλαγή πλάνου;", + "shotChanges": "Αλλαγές πλάνου", + "exportShotChanges": "Εξαγωγή αλλαγών πλάνου" }, "chapters": { "title": "Κεφάλαια", @@ -2684,7 +2934,6 @@ "scaleTimesDescription": "Κλιμάκωση κάθε χρόνου κεφαλαίου από έναν ρυθμό καρέ σε άλλον.", "fromFrameRate": "Από", "toFrameRate": "Σε", - "apply": "Εφαρμογή", "writeToVideo": "Εγγραφή στο βίντεο...", "writeToVideoTitle": "Εγγραφή κεφαλαίων στο βίντεο", "writeToVideoDescription": "Τα κεφάλαια γράφονται σε αντίγραφο του αρχείου βίντεο. Τίποτα δεν επανακωδικοποιείται, οπότε αυτό είναι γρήγορο και χωρίς απώλειες.", @@ -2694,7 +2943,6 @@ "writtenToX": "Τα κεφάλαια γράφτηκαν στο {0}", "unableToWriteChapters": "Αδυναμία εγγραφής κεφαλαίων στο αρχείο βίντεο.", "chaptersSavedToX": "Τα κεφάλαια αποθηκεύτηκαν στο {0}", - "noVideoLoaded": "Δεν έχει φορτωθεί βίντεο", "emptyListCallToAction": "Δεν υπάρχουν ακόμα κεφάλαια - προσθέστε ένα στη θέση βίντεο ή εισαγάγετέ τα από βίντεο ή αρχείο." }, "videoOcr": { @@ -2705,10 +2953,6 @@ "bottomHalf": "Κάτω μισό", "fullFrame": "Πλήρες καρέ", "engine": "Μηχανή OCR", - "language": "Γλώσσα", - "url": "URL", - "model": "Μοντέλο", - "apiKey": "Κλειδί API", "scan": "Σάρωση", "framesPerSecond": "Καρέ ανά δευτερόλεπτο", "textBrightnessMinimum": "Ελάχιστη φωτεινότητα κειμένου (0=ανενεργό)", @@ -2723,10 +2967,10 @@ "runningOcrXY": "Εκτέλεση OCR... {0}/{1}", "noLinesFoundTitle": "Δεν βρέθηκαν υπότιτλοι", "noLinesFoundMessage": "Δεν βρέθηκε κείμενο στην περιοχή σάρωσης - δοκιμάστε να προσαρμόσετε την περιοχή σάρωσης, τη μηχανή ή την ελάχιστη φωτεινότητα.", + "ocrStoppedEarlyMessage": "Η μηχανή OCR σταμάτησε πριν διαβαστούν όλα τα καρέ, οπότε διατηρούνται οι γραμμές που βρέθηκαν μέχρι τώρα και οι υπόλοιπες λείπουν.{0}{0}{1}", "linesFoundX": "Βρέθηκαν {0} γραμμές", "refiningTimingXY": "Βελτίωση χρονισμού... {0}/{1}", "fixOcrErrors": "Διόρθωση σφαλμάτων OCR", - "dictionary": "Λεξικό", "editLineX": "Επεξεργασία γραμμής {0}", "downloadDictionaryHint": "Λήψη λεξικού ορθογραφικού ελέγχου...", "framesPerSecondHint": "Πόσα καρέ ανά δευτερόλεπτο σαρώνονται. Το 5 καλύπτει τους κανονικούς υπότιτλους· υψηλότερη τιμή πιάνει πολύ σύντομες γραμμές και βελτιώνει την ομαδοποίηση, αλλά κοστίζει περισσότερο χρόνο OCR.", @@ -2743,7 +2987,58 @@ "testOcr": "Δοκιμή τρέχοντος καρέ", "testOcrRunning": "Δοκιμή OCR στο τρέχον καρέ...", "testOcrResultX": "Αποτέλεσμα δοκιμής: {0}", - "testOcrNoTextFound": "Δοκιμή: δεν βρέθηκε κείμενο στην περιοχή σάρωσης" + "testOcrNoTextFound": "Δοκιμή: δεν βρέθηκε κείμενο στην περιοχή σάρωσης", + "engineSettings": "Ρυθμίσεις μηχανής OCR", + "engineBuiltIn": "Ενσωματωμένη στο λειτουργικό σύστημα", + "engineExternalServer": "Εξωτερικός διακομιστής (εγκαθίσταται και διαχειρίζεται εκτός του Subtitle Edit)", + "engineCloudApi": "Cloud API (απαιτεί κλειδί API)", + "engineNothingToInstall": "Τίποτα για εγκατάσταση", + "engineModelsMissing": "Η μηχανή είναι εγκατεστημένη, λείπουν μοντέλα", + "website": "Ιστότοπος" + }, + "backgroundMusic": { + "title": "Δημιουργία μουσικής υπόκρουσης", + "generateBackgroundMusicDotDotDot": "Δημιουργία μουσικής υπόκρουσης...", + "preset": "Προεπιλογή", + "prompt": "Προτροπή", + "tempoBpm": "Τέμπο (BPM)", + "generateSeconds": "Δημιουργία (δευτερόλεπτα)", + "seed": "Seed", + "randomSeed": "Τυχαίο", + "outputLength": "Διάρκεια εξόδου", + "outputLengthVideoX": "{0} (διάρκεια βίντεο - η μουσική επαναλαμβάνεται χωρίς διακοπή)", + "musicVolumePercent": "Ένταση μουσικής (%)", + "removeExistingAudioTracks": "Αφαίρεση υπαρχόντων κομματιών ήχου", + "originalAudioVolumePercent": "Ένταση αρχικού ήχου (%)", + "saveAudioDotDotDot": "Αποθήκευση ήχου...", + "addToVideoDotDotDot": "Προσθήκη στο βίντεο...", + "engineInfo": "ACE-Step 1.5 Turbo μέσω audio.cpp (άδεια MIT) - εκτελείται τοπικά", + "presetCooking": "Μαγειρική / ζαχαροπλαστική", + "presetTech": "Τεχνολογία / κριτική συσκευών", + "presetForest": "Δάσος / φύση", + "presetRelaxingPiano": "Χαλαρωτικό πιάνο", + "presetLofiVlog": "Lo-fi vlog", + "presetCorporate": "Εταιρικό / επεξηγηματικό", + "loadingModel": "Φόρτωση μοντέλου...", + "composing": "Σύνθεση...", + "generatingAudio": "Δημιουργία ήχου...", + "decodingAudio": "Αποκωδικοποίηση ήχου...", + "creatingLoop": "Δημιουργία απρόσκοπτου βρόχου...", + "resultLoopXYZ": "Βρόχος: {0} στα {1} BPM, ταίριασμα ραφής {2}%", + "resultNoLoopX": "Η μουσική είναι έτοιμη: {0}", + "addingMusicToVideo": "Προσθήκη μουσικής στο βίντεο...", + "downloadModelTitle": "Λήψη μοντέλου μουσικής;", + "downloadModelQuestionX": "Η δημιουργία μουσικής απαιτεί το μοντέλο ACE-Step 1.5 ({0}).\n\nΛήψη μοντέλου;", + "downloadingModel": "Λήψη μοντέλου ACE-Step 1.5...", + "saveAudioTitle": "Αποθήκευση μουσικής υπόκρουσης", + "audioFileSavedX": "Το αρχείο ήχου αποθηκεύτηκε: {0}", + "unableToGenerateMusic": "Αδυναμία δημιουργίας μουσικής", + "unableToAddMusicToVideo": "Αδυναμία προσθήκης μουσικής στο βίντεο", + "outputLengthTextToSpeech": "Ακολουθεί την ομιλία που δημιουργήθηκε - η μουσική επαναλαμβάνεται χωρίς διακοπή και χαμηλώνει όταν μιλά κάποιος", + "addBackgroundMusic": "Προσθήκη μουσικής υπόκρουσης", + "addBackgroundMusicHint": "Τοπική δημιουργία μουσικής με το ACE-Step 1.5 και ανάμειξή της κάτω από την ομιλία - χαμηλώνει όταν μιλά κάποιος", + "generatingBackgroundMusicDotDotDot": "Δημιουργία μουσικής υπόκρουσης...", + "mixingBackgroundMusicDotDotDot": "Ανάμειξη μουσικής υπόκρουσης..." }, "goToVideoPosition": "Μετάβαση σε θέση βίντεο", "goToVideoPositionDotDotDot": "Μετάβαση σε θέση βίντεο...", @@ -2754,30 +3049,88 @@ "openSecondarySubtitleOnVideoPlayerDotDotDot": "Δευτερεύων υπότιτλος στο πρόγραμμα αναπαραγωγής, άνοιγμα...", "openSecondarySubtitleOnVideoPlayer": "Δευτερεύων υπότιτλος (στο πρόγραμμα αναπαραγωγής)", "removeSecondarySubtitleOnVideoPlayer": "Δευτερεύων υπότιτλος στο πρόγραμμα αναπαραγωγής, αφαίρεση", + "editSecondarySubtitleSettings": "Επεξεργασία ρυθμίσεων δευτερεύοντος υπότιτλου", + "editSecondarySubtitleSettingsDotDotDot": "Επεξεργασία ρυθμίσεων δευτερεύοντος υπότιτλου...", + "secondarySubtitleRememberSettings": "Απομνημόνευση αυτών των ρυθμίσεων", + "secondarySubtitleDoNotShowAgain": "Να μην εμφανιστεί ξανά αυτό το παράθυρο διαλόγου", + "secondarySubtitleJustifyLines": "Πλήρης στοίχιση γραμμών", "openRecentVideo": "Άνοιγμα πρόσφατου βίντεο", "clearRecentVideos": "Εκκαθάριση πρόσφατων βίντεο", "cutVideoTitle": "Αποκοπή βίντεο", "cutVideoDotDotDot": "Αποκοπή βίντεο...", + "remuxVideoTitle": "Επαναπολυπλεξία (remux) βίντεο", + "remuxVideoDotDotDot": "Επαναπολυπλεξία (remux) βίντεο...", + "remuxVideoInputVideo": "Βίντεο εισόδου:", + "remuxVideoMultipleTracksRequiresMkv": "Πολλαπλά κομμάτια ήχου ή υπότιτλων απαιτούν το container MKV. Η μορφή εξόδου άλλαξε αυτόματα σε .mkv.", + "remuxVideoOutputFormat": "Μορφή εξόδου:", + "remuxVideoOutputFile": "Αρχείο εξόδου:", + "remuxVideoRemuxing": "Επαναπολυπλεξία βίντεο και ήχου...", + "remuxVideoFinalizing": "Ολοκλήρωση αρχείου εξόδου (μετακίνηση ευρετηρίου στην αρχή)...", + "remuxVideoCompleted": "Η επαναπολυπλεξία του βίντεο ολοκληρώθηκε με επιτυχία.", + "remuxVideoFailed": "Η επαναπολυπλεξία του βίντεο απέτυχε.", + "remuxVideoPleaseSelectBoth": "Επιλέξτε αρχεία βίντεο και ήχου.", + "remuxVideoAssRequiresMkv": "Οι υπότιτλοι ASS/SSA απαιτούν το container MKV για να διατηρηθούν όλη η μορφοποίηση και τα στυλ.", + "remuxVideoSccRequiresMov": "Οι υπότιτλοι Scenarist (.scc) ενσωματώνονται ως κλειστοί υπότιτλοι CEA-608, που απαιτούν το container MOV ή MPG. Η μορφή εξόδου άλλαξε αυτόματα σε .mov.", + "remuxVideoSccNotWithWebM": "Οι κλειστοί υπότιτλοι Scenarist (.scc) χρειάζονται το container MOV, που δεν μπορεί να περιέχει το βίντεο VP9/AV1 ενός αρχείου .webm. Αφαιρέστε το αρχείο .scc ή μετατρέψτε το σε άλλη μορφή υπότιτλων.", + "remuxVideoMpgMaxTwoCaptionTracks": "Ένα αρχείο .mpg περιέχει το πολύ δύο κομμάτια κλειστών υπότιτλων (CC1 και CC3) - αφαιρέστε αρχεία υπότιτλων ώστε να μείνουν ένα ή δύο.", + "remuxVideoMccRequiresMpg": "Οι υπότιτλοι MacCaption (.mcc) ενσωματώνονται ως κλειστοί υπότιτλοι CEA-608/708 στο βίντεο MPEG-2, που απαιτεί το container MPG. Η μορφή εξόδου άλλαξε αυτόματα σε .mpg.", + "remuxVideoMpgReencodeVideoX": "Οι κλειστοί υπότιτλοι σε αρχείο .mpg χρειάζονται βίντεο MPEG-2, και το βίντεο είναι {0}. Επανακωδικοποίηση του βίντεο σε MPEG-2; Αυτό διαρκεί περισσότερο και μειώνει λίγο την ποιότητα.", + "remuxVideoFileHasNoAudioX": "Το \"{0}\" δεν έχει κομμάτι ήχου, οπότε δεν μπορεί να χρησιμοποιηθεί ως πηγή ήχου.", + "remuxVideoAddingClosedCaptions": "Προσθήκη κλειστών υπότιτλων...", + "remuxVideoSelectAudioTrack": "Επιλογή κομματιού ήχου", + "remuxVideoSelectAudioTrackFor": "Επιλογή κομματιού ήχου για {0}", + "remuxVideoSelectAudioTrackPrompt": "Το αρχείο '{0}' περιέχει {1} κομμάτια ήχου. Επιλέξτε το κομμάτι ήχου για επαναπολυπλεξία:", + "remuxVideoVideoFile": "βίντεο", + "remuxVideoAudioFile": "ήχος", + "remuxVideoAudioFiles": "Αρχεία ήχου:", + "remuxVideoSubtitleFiles": "Αρχεία υπότιτλων (προαιρετικά, soft υπότιτλοι):", + "remuxVideoSelectAudioTrackDotDotDot": "Επιλογή κομματιού ήχου...", + "remuxVideoFilesX": "{0} αρχεία", + "remuxVideoMixAudio": "Ανάμειξη ήχου σε ένα κομμάτι", + "remuxVideoVolumePercent": "Ένταση %:", + "remuxVideoVolumeX": "Ένταση {0}%", + "remuxVideoFastStart": "Βελτιστοποίηση για ροή μέσω διαδικτύου (μετακίνηση ευρετηρίου στην αρχή - πιο αργό)", "embedSubtitlesDotDotDot": "Προσθήκη/αφαίρεση ενσωματωμένων υπότιτλων...", "generateTimeCodes": "Δημιουργία χρονικών κωδίκων", "checkeredImage": "Καρό εικόνα", - "saveVideoAsTitle": "Αποθήκευση βίντεο ως", "promptForFfmpegParamsAndGenerate": "Ερώτηση για παραμέτρους ffmpeg και δημιουργία", "cutVideoCutSegments": "Αποκοπή τμημάτων", "cutVideoMergeSegments": "Συγχώνευση τμημάτων", "cutVideoSplitSegments": "Αποθήκευση τμημάτων ξεχωριστά", - "cutVideoType": "Τύπος αποκοπής", - "mpvRenderAuto": "Αυτόματο", - "mpvRenderNative": "Εγγενές", - "mpvRenderOpenGl": "OpenGL", - "mpvRenderSoftware": "Λογισμικό (αργό)", "cutVideoAlsoCutSubtitle": "Αποκοπή και του υπότιτλου", "cutVideoSubtitleFileGeneratedX": "Δημιουργήθηκε αρχείο υπότιτλων: {0}", + "cutVideoTransition": "Μετάβαση μεταξύ τμημάτων", + "cutVideoTransitionDurationSeconds": "Διάρκεια (δευτερόλεπτα)", + "cutVideoFadeInSeconds": "Ξεθώριασμα από μαύρο στην αρχή (δευτερόλεπτα)", + "cutVideoFadeOutSeconds": "Ξεθώριασμα σε μαύρο στο τέλος (δευτερόλεπτα)", + "cutVideoPreviewTransition": "Προεπισκόπηση μετάβασης", + "cutVideoPreviewTransitionTitle": "Προεπισκόπηση μετάβασης", + "cutVideoPreviewNeedsJoin": "Δεν υπάρχει ένωση για προεπισκόπηση - προσθέστε τουλάχιστον δύο τμήματα για συγχώνευση ή ένα τμήμα για αποκοπή από τη μέση.", + "cutVideoTransitionInfoX": "Κάθε μετάβαση επικαλύπτει τα μέρη που ενώνει, οπότε η έξοδος γίνεται {0} δευτερόλεπτα μικρότερη ανά ένωση. Ένας υπότιτλος που αποκόπτεται επαναχρονίζεται ανάλογα.", + "cutVideoTransitionFade": "Ξεθώριασμα", + "cutVideoTransitionFadeBlack": "Ξεθώριασμα μέσω μαύρου", + "cutVideoTransitionFadeWhite": "Ξεθώριασμα μέσω λευκού", + "cutVideoTransitionFadeGrays": "Ξεθώριασμα μέσω γκρι", + "cutVideoTransitionDissolve": "Διάλυση", + "cutVideoTransitionWipeLeft": "Σάρωση αριστερά", + "cutVideoTransitionWipeRight": "Σάρωση δεξιά", + "cutVideoTransitionWipeUp": "Σάρωση πάνω", + "cutVideoTransitionWipeDown": "Σάρωση κάτω", + "cutVideoTransitionSlideLeft": "Ολίσθηση αριστερά", + "cutVideoTransitionSlideRight": "Ολίσθηση δεξιά", + "cutVideoTransitionSlideUp": "Ολίσθηση πάνω", + "cutVideoTransitionSlideDown": "Ολίσθηση κάτω", + "cutVideoTransitionSmoothLeft": "Ομαλά αριστερά", + "cutVideoTransitionSmoothRight": "Ομαλά δεξιά", + "cutVideoTransitionCircleOpen": "Άνοιγμα κύκλου", + "cutVideoTransitionCircleClose": "Κλείσιμο κύκλου", + "cutVideoTransitionRadial": "Ακτινική", + "cutVideoTransitionPixelize": "Εικονοστοιχειοποίηση", + "cutVideoTransitionBlur": "Θόλωμα", "importCurrentSubtitle": "Εισαγωγή τρέχοντος υπότιτλου", "addRemoveEmbeddedSubtitlesTitle": "Προσθήκη/αφαίρεση ενσωματωμένων υπότιτλων", "addRemoveEmbeddedSubtitlesMp4Title": "Προσθήκη/αφαίρεση ενσωματωμένων υπότιτλων (MP4)", "addCurrentSubtitle": "Προσθήκη τρέχοντος υπότιτλου", - "embeddedTrackColumnNew": "Νέο", "embeddedTrackNoSubtitlesFoundTitle": "Δεν βρέθηκαν υπότιτλοι", "embeddedTrackNoSubtitlesFoundMessage": "Το επιλεγμένο αρχείο υπότιτλων δεν περιέχει υπότιτλους.", "embeddedTrackNoTracksTitle": "Δεν προστέθηκαν κομμάτια", @@ -2789,7 +3142,7 @@ "embeddedTrackGeneratingVideoXY": "Δημιουργία βίντεο... {0}% {1}", "embeddedTrackGeneratingVideo": "Δημιουργία βίντεο...", "mp4FilesFilter": "Αρχεία MP4", - "titleOrLanguage": "Τίτλος/γλώσσα", + "embeddedTracksPickVideoHint": "Επιλέξτε ένα αρχείο βίντεο ή αποθέστε το εδώ για να δείτε τα κομμάτια υπότιτλών του.", "viewMatroskaTrackX": "Προβολή κομματιού Matroska - {0}", "resolutionSeparator": "x", "openFromUrlTitle": "Άνοιγμα αρχείου βίντεο από URL", @@ -2799,9 +3152,7 @@ "openFromUrlDownloadAndOpen": "Λήψη και άνοιγμα", "openFromUrlDownloadAndOpenDescription": "Αποθήκευση του βίντεο τοπικά και άνοιγμά του.", "openFromUrlDownloadAndOpenNote": "Απαιτείται για ομιλία-σε-κείμενο και κορυφές κυματομορφής.", - "openFromUrlMissing": "Παρακαλώ εισαγάγετε πρώτα ένα URL βίντεο.", "openFromUrlDownloadingTitle": "Λήψη βίντεο", - "openFromUrlSaveAs": "Αποθήκευση βίντεο ως", "openFromUrlDownloadSubtitles": "Λήψη και υπότιτλων", "openFromUrlDownloadVideo": "Λήψη βίντεο", "openFromUrlIncludeAutoGeneratedSubtitles": "Συμπερίληψη αυτόματα δημιουργημένων υπότιτλων (αρχική γλώσσα)", @@ -2838,12 +3189,15 @@ "reEncodeGeneratingDone": "Η δημιουργία ολοκληρώθηκε", "reEncodeGeneratedFilesX": "Δημιουργημένα αρχεία ({0}):", "reEncodeFfmpegParameters": "Παράμετροι ffmpeg", + "analyzingVideoX": "Ανάλυση βίντεο... {0}% {1}", + "analyzingVideoXofY": "Ανάλυση βίντεο {0}/{1}... {2}% {3}", + "backgroundImageFileNotSelected": "Δεν επιλέχθηκε αρχείο εικόνας φόντου", + "backgroundImageFileDoesNotExistX": "Το αρχείο εικόνας φόντου δεν υπάρχει: {0}", "voices": "Φωνές", "presets": "Προεπιλογές", "talkerX": "Ομιλητής {0}", "baseModel": "Βασικό μοντέλο", "turboModel": "Μοντέλο turbo", - "ttsUpdateX": "Ενημέρωση {0}", "ttsEngineNotInstalled": "Το {0} δεν είναι εγκατεστημένο", "ttsEngineUpdateAvailable": "{0} - διαθέσιμη ενημέρωση", "ttsEngineTooOldUpdateRequired": "{0} πολύ παλιό - απαιτείται ενημέρωση", @@ -2866,19 +3220,25 @@ "cloneVoiceTo": "Κλωνοποίηση φωνής σε", "cloneVoiceNameTitle": "Όνομα νέας φωνής", "cloneVoiceExtractFailed": "Αδυναμία εξαγωγής ήχου για την επιλεγμένη γραμμή.", + "lineXHasNoDuration": "Η γραμμή {0} δεν έχει διάρκεια (ο χρόνος λήξης δεν είναι μετά τον χρόνο έναρξης), οπότε δεν υπάρχει ήχος για εξαγωγή.", + "audioClipsSkippedLinesX": "Δεν ήταν δυνατή η εξαγωγή ήχου για αυτές τις γραμμές, οπότε παραλείφθηκαν: {0}", "toggleShotChange": "Εναλλαγή αλλαγής πλάνου", "resetWaveformZoomAndSpeed": "Επαναφορά εστίασης και ταχύτητας κυματομορφής", "showOriginalSubtitle": "Εμφάνιση αρχικού υπότιτλου", + "timelineGroupTracksBy": "Ομαδοποίηση κομματιών υπότιτλων κατά", + "timelineShowThumbnails": "Εμφάνιση μικρογραφιών βίντεο", + "showSpeechOnly": "Εμφάνιση μόνο ομιλίας (αργή δημιουργία)", + "isolatingSpeechForWaveformX": "Απομόνωση ομιλίας για την κυματομορφή... {0}", + "isolatingSpeechForWaveformFailed": "Δεν ήταν δυνατή η απομόνωση της ομιλίας για την κυματομορφή - δείτε το αρχείο καταγραφής εργαλείων", + "speechOnlyWaveformReady": "Η κυματομορφή εμφανίζει τώρα μόνο ομιλία", "showOnlyWaveform": "Εμφάνιση μόνο κυματομορφής", "showOnlySpectrogram": "Εμφάνιση μόνο φασματογραφήματος", "showWaveformAndSpectrogram": "Εμφάνιση κυματομορφής και φασματογραφήματος", - "spectrogramClassic": "Κλασικό", "spectrogramClassicViridis": "Viridis", "spectrogramClassicPlasma": "Plasma", "spectrogramClassicInferno": "Inferno", "spectrogramClassicTurbo": "Turbo", "spectrogramNeon": "Νέον", - "waveformDrawStyleClassic": "Κλασικό", "waveformDrawStyleFancy": "Φαντεζί", "setVideoPositionAndPauseAndSelectSubtitle": "Ορισμός θέσης βίντεο, παύση και επιλογή υπότιτλου", "setVideopositionAndPauseAndSelectSubtitleAndCenter": "Ορισμός θέσης βίντεο, παύση, επιλογή υπότιτλου και κεντράρισμα", @@ -2888,7 +3248,6 @@ }, "sync": { "visualSync": "Οπτικός συγχρονισμός", - "sync": "Συγχρονισμός", "startScene": "Σκηνή έναρξης", "endScene": "Σκηνή τέλους", "playTwoSecondsAndBack": "Αναπαραγωγή 2 δευτ. και πίσω", @@ -2904,19 +3263,18 @@ "adjustSelectedLinesAndForward": "Προσαρμογή επιλεγμένων γραμμών και εξής", "toFrameRate": "Σε ρυθμό καρέ", "fromFrameRate": "Από ρυθμό καρέ", + "videoXFrameRateY": "{0} - {1} fps", "adjustAllTimes": "Προσαρμογή όλων των χρόνων (εμφάνιση νωρίτερα/αργότερα)", "showEarlier": "Εμφάνιση νωρίτερα", "showLater": "Εμφάνιση αργότερα", - "changeFrameRate": "Αλλαγή ρυθμού καρέ", "setSyncPoint": "Ορισμός σημείου συγχρονισμού", "setSyncPointViaVideo": "Ορισμός σημείου συγχρονισμού μέσω βίντεο...", "syncPointTimeCode": "Χρονικός κώδικας σημείου συγχρονισμού", "syncPoints": "Σημεία συγχρονισμού", "pointSync": "Συγχρονισμός σημείων", "pointSyncViaOther": "Συγχρονισμός σημείων μέσω άλλου υπότιτλου", - "adjustmentX": "Προσαρμογή: {0}", - "totalAdjustmentX": "Συνολική προσαρμογή: {0}", - "syncPointCandidateInfo": "Οι γραμμές που ξεκινούν μετά από {0}+ δευτερόλεπτα σιωπής επισημαίνονται - συχνά αποτελούν αξιόπιστα σημεία συγχρονισμού", + "syncPointCandidateAfterInfo": "Οι γραμμές που ακολουθούνται από σιωπή {0}+ δευτερολέπτων επισημαίνονται - συχνά αποτελούν αξιόπιστα σημεία συγχρονισμού", + "gapAfter": "Κενό μετά", "adjustAllShortcuts": "Συντομεύσεις πληκτρολογίου:\r\n\r\n• Shift + Αριστερά/Δεξιά: Μετακίνηση 10 ms\r\n• Ctrl + Αριστερά/Δεξιά: Μετακίνηση 100 ms\r\n• Alt + Αριστερά/Δεξιά: Μετακίνηση 500 ms", "adjustAllShortcutsFrames": "Συντομεύσεις πληκτρολογίου:\r\n\r\n• Shift + Αριστερά/Δεξιά: Μετακίνηση 1 καρέ\r\n• Ctrl + Αριστερά/Δεξιά: Μετακίνηση 10 καρέ\r\n• Alt + Αριστερά/Δεξιά: Μετακίνηση 1 δευτερόλεπτο", "offsetInSeconds": "Μετατόπιση σε δευτερόλεπτα", @@ -2936,6 +3294,7 @@ "lineMerge": "Συγχώνευση γραμμών", "delayInSecondsBetweenRequests": "Καθυστέρηση σε δευτερόλεπτα μεταξύ αιτημάτων", "maxBytesPerRequest": "Μέγιστα byte ανά αίτημα", + "keepMusicLinesUntranslated": "Να μη μεταφράζονται γραμμές μέσα σε σύμβολα μουσικής (♪ ... ♪)", "promptText": "Κείμενο προτροπής", "resetPromptToDefault": "Επαναφορά προτροπής στην προεπιλογή", "translateEachLineSeparately": "Μετάφραση κάθε γραμμής ξεχωριστά", @@ -2949,11 +3308,10 @@ "readyToTranslate": "Έτοιμο για μετάφραση", "translating": "Μετάφραση...", "translationComplete": "Η μετάφραση ολοκληρώθηκε", + "translateInPlaceNoOriginal": "Μετάφραση επί τόπου (χωρίς δημιουργία αρχικού)", "translationCancelled": "Η μετάφραση ακυρώθηκε", "swapLanguages": "Εναλλαγή γλωσσών πηγής και προορισμού", "xIsAlreadyDownloadedReDownload": "Το {0} έχει ήδη ληφθεί. Νέα λήψη;", - "advancedDotDotDot": "Για προχωρημένους...", - "advancedSettings": "Ρυθμίσεις για προχωρημένους", "advancedSettingsSubtitle": "Επιλογές παρτίδας, συμφραζομένων και δειγματοληψίας για την προηγμένη μηχανή llama.cpp", "context": "Συμφραζόμενα", "synopsis": "Σύνοψη", @@ -2977,6 +3335,7 @@ "useOnlyExtraServerParametersHint": "Εκκίνηση του llama-server με τις παραπάνω παραμέτρους αντί για τη ρύθμιση του ίδιου του Subtitle Edit - ορίζονται μόνο το μοντέλο, ο κεντρικός υπολογιστής και η θύρα", "serverRunningAtX": "Ο διακομιστής εκτελείται στο {0}", "customPromptHint": "Προσαρμοσμένες οδηγίες ({0} = γλώσσα πηγής, {1} = γλώσσα προορισμού)· κενό = ενσωματωμένη προτροπή", + "editTranslationHint": "Κάντε κλικ στη μετάφραση της επιλεγμένης σειράς για να την επεξεργαστείτε (Enter για αποθήκευση, Escape για ακύρωση)", "llamaCppDownloadEngineAndModelPrompt": "Το llama.cpp απαιτεί τη λήψη της μηχανής llama-server και ενός μοντέλου μετάφρασης. Λήψη τώρα;", "llamaCppDownloadEnginePrompt": "Το llama.cpp απαιτεί τη λήψη της μηχανής llama-server. Λήψη τώρα;", "llamaCppDownloadModelPrompt": "Το llama.cpp απαιτεί τη λήψη του επιλεγμένου μοντέλου μετάφρασης. Λήψη τώρα;" @@ -2989,7 +3348,6 @@ "dialogStyleDashBothLinesWithSpace": "Παύλα και στις δύο γραμμές με κενό", "dialogStyleDashBothLinesWithoutSpace": "Παύλα και στις δύο γραμμές χωρίς κενό", "continuationStyle": "Στυλ συνέχειας", - "continuationStyleNone": "Κανένα", "continuationStyleNoneTrailingDots": "Κανένα, τελείες για παύσεις (μόνο στο τέλος)", "continuationStyleNoneLeadingTrailingDots": "Κανένα, τελείες για παύσεις", "continuationStyleNoneTrailingEllipsis": "Κανένα, αποσιωπητικά για παύσεις (μόνο στο τέλος)", @@ -3000,7 +3358,6 @@ "continuationStyleLeadingTrailingEllipsis": "Αποσιωπητικά", "continuationStyleLeadingTrailingDash": "Παύλα", "continuationStyleLeadingTrailingDashDots": "Παύλα, αλλά τελείες για παύσεις", - "continuationStyleCustom": "Προσαρμοσμένο", "cpsLineLengthStyle": "Cps/μήκος γραμμής", "cpsLineLengthStyleCalcAll": "Καταμέτρηση όλων των χαρακτήρων", "cpsLineLengthStyleCalcNoSpaceCpsOnly": "Καταμέτρηση όλων εκτός κενών, μόνο cps", @@ -3013,14 +3370,6 @@ "cpsLineLengthStyleCalcNoSpaceOrPunctuationCpsOnly": "Χωρίς κενά ή σημεία στίξης, μόνο CPS", "cpsLineLengthStyleCalcIgnoreArabicDiacritics": "Παράβλεψη αραβικών διακριτικών", "cpsLineLengthStyleCalcIgnoreArabicDiacriticsNoSpace": "Παράβλεψη αραβικών διακριτικών, χωρίς κενά", - "timeCodeModeHhMmSsMs": "HH:MM:SS:MS", - "timeCodeModeHhMmSsFf": "HH:MM:SS:FF", - "splitBehaviorPrevious": "Προσθήκη κενού αριστερά του σημείου διαίρεσης (εστίαση δεξιά)", - "splitBehaviorHalf": "Προσθήκη κενού στο κέντρο του σημείου διαίρεσης (εστίαση αριστερά)", - "splitBehaviorNext": "Προσθήκη κενού δεξιά του σημείου διαίρεσης (εστίαση αριστερά)", - "subtitleListActionNothing": "Τίποτα", - "subtitleListActionVideoGoToPositionAndPause": "Μετάβαση στη θέση βίντεο και παύση", - "subtitleListActionVideoGoToPositionAndPlay": "Μετάβαση στη θέση βίντεο και αναπαραγωγή", "subtitleGridCenterText": "Κεντράρισμα κειμένου στο πλέγμα υπότιτλων", "minGapCalculateDotDotDot": "Υπολογισμός ελάχιστου κενού από ρυθμό καρέ...", "minGapCalculateTitle": "Ελάχ. κενό μεταξύ υπότιτλων σε ms", @@ -3052,8 +3401,14 @@ "checkForUpdatesChannelStableAndBeta": "Σταθερές και beta εκδόσεις", "assaAutoSetResolution": "ASSA: ορισμός ανάλυσης (PlayResX/PlayResY) από το βίντεο όταν ανοίγει βίντεο", "assaAutoSetResolutionPrompt": "ASSA: ερώτηση πριν από την αλλαγή της ανάλυσης ώστε να ταιριάζει με το βίντεο (αλλιώς αυτόματη αναδειγματοληψία)", + "videoControls": "Στοιχεία ελέγχου βίντεο", + "videoControlsEdit": "Επεξεργασία στοιχείων ελέγχου βίντεο...", + "videoControlsPositionText": "Κείμενο θέσης / διάρκειας", + "videoControlsVideoFileName": "Όνομα αρχείου βίντεο", + "videoControlsPlayerName": "Όνομα προγράμματος αναπαραγωγής βίντεο", "mergeKeepEndTime": "Συγχώνευση γραμμών: διατήρηση χρόνου τέλους (να επιτρέπεται επικάλυψη με τον επόμενο υπότιτλο)", "mergeKeepEndTimeOnlyAssa": "Συγχώνευση γραμμών: διατήρηση χρόνου τέλους μόνο για \"Advanced Sub Station Alpha\"", + "mergeAutoBreak": "Συγχώνευση γραμμών: αυτόματη αλλαγή γραμμής στο συγχωνευμένο κείμενο (απενεργοποιημένο διατηρεί τις αλλαγές γραμμής των γραμμών)", "defaultSaveLocation": "Προεπιλεγμένη θέση αποθήκευσης", "defaultSaveLocationSourceFileFolder": "Φάκελος αρχείου υπότιτλων/βίντεο", "defaultSaveLocationLastUsedFolder": "Τελευταίος φάκελος που χρησιμοποιήθηκε", @@ -3061,18 +3416,10 @@ "defaultSaveLocationSubtitleFileFolder": "Φάκελος αρχείου υπότιτλων", "defaultSaveLocationCustomFolder": "Προσαρμοσμένος φάκελος", "subtitleListActionVideoGoToPositionAndPlayCurrentAndPause": "Μετάβαση στη θέση βίντεο, αναπαραγωγή τρέχοντος και παύση", - "subtitleListActionEditText": "Μετάβαση στο πλαίσιο επεξεργασίας κειμένου", "subtitleListActionVideoGoToPositionMinus1SecAndPause": "Μετάβαση στη θέση βίντεο - 1 δευτ. και παύση", "subtitleListActionVideoGoToPositionMinusHalfSecAndPause": "Μετάβαση στη θέση βίντεο - 0,5 δευτ. και παύση", "subtitleListActionVideoGoToPositionMinus1SecAndPlay": "Μετάβαση στη θέση βίντεο - 1 δευτ. και αναπαραγωγή", - "subtitleListActionEditTextAndPause": "Μετάβαση στο πλαίσιο επεξεργασίας κειμένου και παύση στη θέση βίντεο", - "autoBackupEveryMinute": "Κάθε λεπτό", - "autoBackupEveryXthMinute": "Κάθε {0} λεπτά", - "profiles": "Προφίλ", - "autoBackupDeleteAfterXMonths": "Διαγραφή αυτόματων αντιγράφων ασφαλείας μετά από {0} μήνες", "searchSettingsDotDotDot": "Αναζήτηση ρυθμίσεων...", - "syntaxColoring": "Χρωματισμός σύνταξης", - "waveformSpectrogram": "Κυματομορφή/φασματογράφημα", "network": "Δίκτυο", "fileTypeAssociations": "Συσχετίσεις τύπων αρχείων", "textBoxColorTags": "Χρωματισμός ετικετών (HTML/ASSA) στο πλαίσιο κειμένου υπότιτλου", @@ -3105,10 +3452,9 @@ "darkThemeForegroundColor": "Χρώμα προσκηνίου σκούρου θέματος", "darkThemeBackgroundColor": "Χρώμα φόντου σκούρου θέματος", "showGridLines": "Εμφάνιση γραμμών πλέγματος", - "resetSettings": "Επαναφορά ρυθμίσεων;", - "resetSettingsDetail": "Αυτό θα επαναφέρει όλες τις ρυθμίσεις στις προεπιλεγμένες τιμές τους.\n\nΣυνέχεια;", "showHorizontalLineAboveToolbar": "Εμφάνιση οριζόντιας γραμμής πάνω από τη γραμμή εργαλείων", "bookmarkColor": "Χρώμα σελιδοδείκτη", + "spellCheckHighlightColor": "Χρώμα επισήμανσης ορθογραφικού ελέγχου", "singleLineMaxLength": "Μέγιστο μήκος μίας γραμμής", "optimalCharsPerSec": "Βέλτιστοι χαρ./δευτ.", "maxCharsPerSec": "Μέγ. χαρ./δευτ.", @@ -3121,13 +3467,21 @@ "unbreakSubtitlesShortThan": "Αναίρεση αλλαγής γραμμής σε υπότιτλους μικρότερους από", "newEmptyDefaultMs": "Προεπιλεγμένη διάρκεια νέου υπότιτλου (ms)", "timeCodeUpDownStepMs": "Βήμα αύξησης/μείωσης χρόνου (ms)", + "moveSelectedLinesStepMs": "Βήμα συντόμευσης μετακίνησης επιλεγμένων γραμμών (ms)", + "moveStartEndStepMs": "Βήμα συντόμευσης μετακίνησης έναρξης/λήξης (ms)", + "moveLinesShortenNeighbor": "Μετακίνηση γραμμών: συντόμευση της προηγούμενης/επόμενης γραμμής αντί για επικάλυψη", "promptBeforeDelete": "Ερώτηση πριν από τη διαγραφή", "rememberPositionAndSize": "Απομνημόνευση θέσης και μεγέθους παραθύρου", + "titleBarFullFileName": "Εμφάνιση πλήρους διαδρομής αρχείου στη γραμμή τίτλου", + "showRecentFiles": "Εμφάνιση πρόσφατων αρχείων (για νέο άνοιγμα)", "openLastFileOnStart": "Άνοιγμα τελευταίου πρόσφατου αρχείου κατά την εκκίνηση", "autoSave": "Αυτόματη αποθήκευση (αποθήκευση του ανοιχτού αρχείου κατά την επεξεργασία)", "autoBackupOn": "Αυτόματο αντίγραφο ασφαλείας", "autoBackupIntervalMinutes": "Διάστημα αυτόματων αντιγράφων ασφαλείας (λεπτά)", "autoBackupDeleteAfterDays": "Διατήρηση αυτόματων αντιγράφων ασφαλείας (ημέρες)", + "settingsBackupOn": "Αυτόματο αντίγραφο ασφαλείας ρυθμίσεων", + "settingsBackupIntervalDays": "Διάστημα αντιγράφων ασφαλείας ρυθμίσεων (ημέρες, 0 = σε κάθε εκκίνηση)", + "settingsBackupMaxCount": "Αντίγραφα ασφαλείας ρυθμίσεων προς διατήρηση", "autoConvertToUtf8": "Αυτόματη μετατροπή κωδικοποίησης σε UTF-8 κατά το άνοιγμα", "autoTrimWhiteSpace": "Αυτόματη περικοπή κενών διαστημάτων", "defaultEncoding": "Προεπιλεγμένη κωδικοποίηση", @@ -3143,23 +3497,14 @@ "colorGapTooShort": "Χρωματισμός αν το κενό είναι πολύ σύντομο", "errorBackgroundColor": "Χρώμα φόντου σφάλματος", "waveformDrawGridLines": "Σχεδίαση γραμμών πλέγματος", - "waveformCenterVideoPosition": "Κεντράρισμα θέσης βίντεο", + "waveformUseSkiaRenderer": "Χρήση πειραματικής γρήγορης απόδοσης", "waveformCenterVideoPositionAlsoWhenPaused": "Κεντράρισμα θέσης βίντεο και σε παύση", + "waveformSelectCurrentSubtitleWhilePaused": "Επιλογή τρέχοντος υπότιτλου και κατά την παύση", + "waveformRememberZoom": "Απομνημόνευση ζουμ (οριζόντιου και κατακόρυφου)", "waveformShowToolbar": "Εμφάνιση γραμμής εργαλείων", "waveformShowToolbarEdit": "Επεξεργασία γραμμής εργαλείων...", "waveformShowToolbarEditLabel": "Στοιχεία γραμμής εργαλείων", "waveformSpectrogramCombinedWaveformHeight": "Συνδυασμός κυματομορφής/φασματογραφήματος, ύψος κυματομορφής %", - "showWaveformToolbarPlay": "Γραμμή εργαλείων: εμφάνιση κουμπιού αναπαραγωγής", - "showWaveformToolbarRepeat": "Γραμμή εργαλείων: εμφάνιση κουμπιού επανάληψης", - "showWaveformToolbarRemoveBlankLines": "Γραμμή εργαλείων: εμφάνιση κουμπιού αφαίρεσης κενών γραμμών", - "showWaveformToolbarNew": "Γραμμή εργαλείων: εμφάνιση κουμπιού νέου υπότιτλου", - "showWaveformToolbarSetStart": "Γραμμή εργαλείων: εμφάνιση κουμπιού ορισμού αρχής", - "showWaveformToolbarSetEnd": "Γραμμή εργαλείων: εμφάνιση κουμπιού ορισμού τέλους", - "showWaveformToolbarStartAndOffsetTheRest": "Γραμμή εργαλείων: εμφάνιση κουμπιού ορισμού αρχής και μετατόπισης των υπολοίπων", - "showWaveformToolbarHorizontalZoom": "Γραμμή εργαλείων: εμφάνιση ρυθμιστικού οριζόντιας εστίασης", - "showWaveformToolbarVerticalZoom": "Γραμμή εργαλείων: εμφάνιση ρυθμιστικού κατακόρυφης εστίασης", - "showWaveformToolbarVideoPositionSlider": "Γραμμή εργαλείων: εμφάνιση ρυθμιστικού θέσης βίντεο", - "showWaveformToolbarPlaybackSpeed": "Γραμμή εργαλείων: εμφάνιση ταχύτητας αναπαραγωγής", "waveformFocusTextboxAfterInsertNew": "Εστίαση στο πλαίσιο κειμένου μετά την εισαγωγή", "waveformInvertMouseWheel": "Αντιστροφή τροχού ποντικιού", "waveformMouseWheelSetsVideoPosition": "Ο τροχός ποντικιού ορίζει τη θέση βίντεο", @@ -3180,20 +3525,20 @@ "fixCommonErrorsSkipStep1": "Διόρθωση συνηθισμένων σφαλμάτων: παράλειψη βήματος 1 (επιλογή διορθώσεων)", "showPluginsMenu": "Εμφάνιση μενού προσθέτων", "proxyAddress": "Διεύθυνση διαμεσολαβητή", - "username": "Όνομα χρήστη", "password": "Κωδικός πρόσβασης", "defaultFormat": "Προεπιλεγμένη μορφή", "defaultSaveAsFormat": "Προεπιλεγμένη μορφή \"Αποθήκευση ως\"", "favoriteSubtitleFormats": "Αγαπημένες μορφές υπότιτλων", "favoriteLanguages": "Αγαπημένες γλώσσες", "webVttUseXTimestampMap": "WebVTT: χρήση X-TIMESTAMP-MAP (μετατόπιση χρονικών κωδίκων κατά τη φόρτωση)", - "showStopButton": "Εμφάνιση κουμπιού διακοπής", - "showFullscreenButton": "Εμφάνιση κουμπιού πλήρους οθόνης", "fullscreenHideControls": "Απόκρυψη στοιχείων ελέγχου βίντεο σε πλήρη οθόνη", + "showSecondarySubtitleDialog": "Εμφάνιση παραθύρου στυλ κατά το άνοιγμα δευτερεύοντος αρχείου υπότιτλων", + "rememberSecondarySubtitleFile": "Απομνημόνευση δευτερεύοντος αρχείου υπότιτλων", "autoOpenVideoFile": "Αυτόματο άνοιγμα αρχείου βίντεο κατά το άνοιγμα υπότιτλου", "downloadMpv": "Λήψη mpv", "downloadVlc": "Λήψη VLC", "allowSingleLetterShortcutsInTextbox": "Να επιτρέπονται συντομεύσεις ενός γράμματος στο πλαίσιο κειμένου", + "allowTextNavigationShortcutsInTextbox": "Να επιτρέπονται συντομεύσεις σε πλήκτρα πλοήγησης κειμένου (Ctrl+Αριστερά/Δεξιά, Home/End) στο πλαίσιο κειμένου", "spellCheckEnglishTreatInApostropheAsIng": "Ορθογραφικός έλεγχος: Αντιμετώπιση λέξεων που τελειώνουν σε 'in'' ως 'ing' (μόνο Αγγλικά)", "goToLineNumberSetsVideoPosition": "Η μετάβαση σε αριθμό γραμμής ορίζει και τη θέση βίντεο", "adjustAllTimesRememberLineSelectionChoice": "Προσαρμογή όλων των χρόνων, απομνημόνευση επιλογής γραμμών", @@ -3205,6 +3550,7 @@ "showAssaLayer": "Εμφάνιση πλαισίου επιπέδου ASSA", "waveformCursorColor": "Χρώμα δρομέα/κεφαλής κυματομορφής", "waveformShotChangeColor": "Χρώμα αλλαγής πλάνου κυματομορφής", + "waveformGridColor": "Χρώμα πλέγματος κυματομορφής", "waveformParagraphLeftColor": "Χρώμα αριστερού περιγράμματος κυματομορφής", "waveformParagraphRightColor": "Χρώμα δεξιού περιγράμματος κυματομορφής", "waveformFancyHighColor": "Χρώμα υψηλών φαντεζί κυματομορφής", @@ -3232,27 +3578,31 @@ "resetSyntaxColoring": "Επαναφορά χρωματισμού σύνταξης", "resetWaveform": "Επαναφορά κυματομορφής", "resetRules": "Επαναφορά κανόνων", - "useFrameMode": "Χρήση λειτουργίας καρέ (hh.mm.ss.ff)", + "timeCodeMode": "Λειτουργία χρονικού κώδικα", + "timeCodeModeTime": "Χρόνος (hh:mm:ss,zzz)", + "timeCodeModeFrames": "Καρέ (hh:mm:ss:ff)", + "timeCodeModeFrameNumbers": "Αριθμοί καρέ", + "frameModeSnapToVideoFrames": "Αποθήκευση χρόνων στα καρέ του βίντεο (όχι σε καρέ μέσα σε κάθε δευτερόλεπτο)", "textBoxLimitNewLines": "Περιορισμός αριθμού γραμμών στο πλαίσιο κειμένου υπότιτλου", - "mpvVideoOutput": "Έξοδος βίντεο (mpv)", "mpvOpenGl": "libmpv - OpenGL", "mpvSoftwareRendering": "libmpv - Απόδοση λογισμικού (αργή)", + "mpvMetalRendering": "libmpv - Metal (πειραματικό, απαιτεί επανεκκίνηση)", "mpvWidRendering": "libmpv - Εγγενής απόδοση Window ID", "waveFormsAndSpectrogramFoldersContainsX": "Οι φάκελοι \"Waveforms\" και \"spectrogram\" περιέχουν {0}", "deleteWaveformAndSpectrogramFoldersQuestion": "Διαγραφή αρχείων \"Waveforms\" και \"Spectrogram\";", "waveformGenerateSpectrogram": "Δημιουργία φασματογραφήματος", "waveformAutoGenerate": "Αυτόματη δημιουργία κυματομορφής κατά το άνοιγμα βίντεο", "waveformSpectrogramMode": "Λειτουργία φασματογραφήματος", - "waveformCenterOnSingleClick": "Κεντράρισμα με μονό κλικ", - "waveformSingleClickSelectsSubtitle": "Επιλογή υπότιτλου με μονό κλικ", "waveformRightClickSelectsSubtitle": "Επιλογή υπότιτλου με δεξί κλικ", - "waveformPauseOnSingleClick": "Παύση με μονό κλικ", "waveformDrawStyle": "Στυλ σχεδίασης κυματομορφής", "waveformExtractAudioFormat": "Μορφή εξαγωγής ήχου", "waveformExtractAudioSampleRate": "Ρυθμός δειγματοληψίας εξαγωγής ήχου", "waveformExtractAudioSampleRateOriginal": "Αρχικός (διατήρηση πηγής)", "waveformExtractAudioBitRate": "Ρυθμός bit εξαγωγής ήχου (MP3/M4A)", + "ffmpegUseCenterChannelOnly": "Χρήση μόνο του μπροστινού κεντρικού καναλιού για ήχο 5.1 (FFmpeg)", "vlcWidRendering": "libVLC - Εγγενής απόδοση Window ID", + "ffmpegSoftwareRendering": "FFmpeg", + "downloadFfmpegLibs": "Λήψη βιβλιοθηκών FFmpeg (για το πρόγραμμα αναπαραγωγής βίντεο FFmpeg)", "subtitleGridEnterKeyAction": "Ενέργεια πλήκτρου Enter στο πλέγμα υπότιτλων", "subtitleSingleClickAction": "Ενέργεια μονού κλικ στο πλέγμα υπότιτλων", "subtitleDoubleClickAction": "Ενέργεια διπλού κλικ στο πλέγμα υπότιτλων", @@ -3270,6 +3620,7 @@ "subtitleGridFormattingShowFormatting": "Εμφάνιση μορφοποίησης", "subtitleGridFormattingShowTags": "Εμφάνιση ετικετών", "subtitleGridFormattingHideTags": "Απόκρυψη ετικετών", + "subtitleGridFormattingShowFormattingKeepTags": "Εμφάνιση μορφοποίησης, διατήρηση μη οπτικών ετικετών", "waveformParagraphBackgroundColor": "Χρώμα φόντου υπότιτλου κυματομορφής", "waveformParagraphSelectedBackgroundColor": "Χρώμα φόντου επιλεγμένου υπότιτλου κυματομορφής", "waveformAllowOverlap": "Να επιτρέπεται η επικάλυψη (κατά τη μετακίνηση/αλλαγή μεγέθους)", @@ -3279,17 +3630,18 @@ "saveAsBehaviorUseVideoFileName": "Χρήση ονόματος αρχείου βίντεο", "saveAsBehaviorUseSubtitleFileName": "Χρήση ονόματος αρχείου υπότιτλου", "saveAsAppendLanguageCodeTwoLetter": "Δύο γραμμάτων", - "saveAsAppendLanguageCodeThreeLetter": "Τριών γραμμάτων (ISO 639-2/T)", - "saveAsAppendLanguageCodeThreeLetterBibliographic": "Τριών γραμμάτων (ISO 639-2/B)", "saveAsAppendLanguageCodeLanguageName": "Όνομα γλώσσας", "splitOddLineActionWeightTop": "Βάρος πάνω", "splitOddLineActionWeightBottom": "Βάρος κάτω", "splitOddLinesAction": "Ενέργεια διαίρεσης μονών γραμμών", "ocrUseWordSplitList": "OCR: χρήση λίστας διαίρεσης λέξεων", "ocrGuessUnknownWords": "OCR: προσπάθεια εκτίμησης άγνωστων λέξεων", + "spellCheckRememberUseAlwaysList": "Ορθογραφικός έλεγχος: απομνημόνευση λίστας \"Χρήση πάντα\"", + "fixShortDisplayTimesAllowMoveStartTime": "Διόρθωση συνηθισμένων σφαλμάτων: η διόρθωση σύντομων χρόνων εμφάνισης μπορεί να μετακινήσει τον χρόνο έναρξης", "speechToTextSelectedLinesPromptFirstTimeOnly": "Ομιλία σε κείμενο: επιλεγμένες γραμμές, ερώτηση για γλώσσα/μηχανή μόνο την πρώτη φορά", "multipleReplaceShowDotDotDotButtons": "Πολλαπλή αντικατάσταση: εμφάνιση κουμπιών μενού περιβάλλοντος", "gridFocusTextboxAfterInsertNew": "Πλέγμα: εστίαση στο πλαίσιο κειμένου μετά την εισαγωγή νέου υπότιτλου", + "undoRedoGoToChangedLine": "Αναίρεση/ακύρωση αναίρεσης: μετάβαση στη γραμμή που άλλαξε (και στη θέση βίντεο)", "musicSymbol": "Μουσικό σύμβολο", "musicSymbolsToReplace": "Μουσικά σύμβολα προς αντικατάσταση (χωρισμένα με κόμμα)", "textToSpeechPromptMergeContinuationLines": "Κείμενο σε ομιλία: ερώτηση για συγχώνευση γραμμών συνέχειας", @@ -3298,8 +3650,7 @@ "useFocusedButtonBackgroundColor": "Χρήση χρώματος φόντου εστιασμένου κουμπιού", "focusedButtonBackgroundColor": "Χρώμα φόντου εστιασμένου κουμπιού", "forceCrLfOnSave": "Επιβολή CR+LF κατά την αποθήκευση (αρχεία υπότιτλων κειμένου)", - "showWaveformToolbarPlayNext": "Γραμμή εργαλείων: εμφάνιση κουμπιού αναπαραγωγής επόμενου", - "showWaveformToolbarPlaySelection": "Γραμμή εργαλείων: εμφάνιση κουμπιού αναπαραγωγής επιλογής", + "linuxClipboardUseExternalTool": "Αντιγραφή κειμένου μέσω xclip/wl-copy (διατηρεί τους τονισμένους χαρακτήρες)", "showFormatLimitWarning": "Προειδοποίηση κατά την αποθήκευση όταν οι γραμμές υπερβαίνουν τα όρια της μορφής (π.χ. SCC 32 χαρ./γραμμή)", "textBoxButtonShowAutoBreak": "Πλαίσιο κειμένου: εμφάνιση κουμπιού αυτόματης αλλαγής γραμμής", "textBoxButtonShowUnbreak": "Πλαίσιο κειμένου: εμφάνιση κουμπιού αναίρεσης αλλαγής γραμμής", @@ -3311,12 +3662,18 @@ "waveformDoubleClickAction": "Ενέργεια διπλού κλικ στην κυματομορφή (μετά την ενέργεια μονού κλικ)", "allSettings": "Όλες οι ρυθμίσεις", "uiScale": "Κλίμακα διεπαφής (%)", + "fontScale": "Κλίμακα γραμματοσειράς (%)", + "textSelectionOpacity": "Αδιαφάνεια επιλογής κειμένου (%)", "waveformToolbarItems": "Στοιχεία γραμμής εργαλείων κυματομορφής", "matchIconColorToDarkTheme": "Αντιστοίχιση χρώματος εικονιδίων με το χρώμα προσκηνίου σκούρου θέματος", "subtitlePreviewProperties": "Ιδιότητες προεπισκόπησης υπότιτλων", "usePositionFromSubtitleFile": "Χρήση θέσης από το αρχείο υπότιτλων (TTML/PAC/EBU STL)", "marginIsPartOfSubtitleArea": "Το περιθώριο αποτελεί μέρος της περιοχής υπότιτλων", "textJustify": "Πλήρης στοίχιση γραμμών", + "deinterlace": "Αποπλεγματοποίηση βίντεο (deinterlace)", + "deinterlaceOff": "Ανενεργό", + "deinterlaceAuto": "Αυτόματο (μόνο πεπλεγμένα καρέ)", + "deinterlaceAlways": "Πάντα", "pixelWidthInfo": "Πράσινες γραμμές = όριο μέγιστου πλάτους | Κόκκινη περιοχή = το κείμενο υπερβαίνει το όριο", "spellCheckEngineHunSpelll": "Hunspell", "spellCheckEngineMsWord": "MS Word", @@ -3325,117 +3682,89 @@ "waveformColorTheme": "Χρωματικό θέμα:", "waveformSaveAsCustomTheme": "Αποθήκευση ως προσαρμοσμένο θέμα...", "waveformLoadThemeDotDotDot": "Φόρτωση θέματος...", - "waveformExportThemeDotDotDot": "Εξαγωγή θέματος..." + "waveformExportThemeDotDotDot": "Εξαγωγή θέματος...", + "waveformSaveCustomThemeTitle": "Αποθήκευση προσαρμοσμένου θέματος", + "waveformThemeFileType": "Θέμα κυματομορφής Subtitle Edit", + "waveformThemeHighContrast": "Υψηλή αντίθεση", + "waveformThemeOceanBlue": "Μπλε ωκεανού", + "waveformThemeWarmSunset": "Ζεστό ηλιοβασίλεμα", + "waveformThemeForest": "Δάσος", + "waveformThemeMidnightPurple": "Μεσονύχτιο μωβ", + "waveformThemeRetroAmber": "Ρετρό κεχριμπαρί", + "waveformThemeIce": "Πάγος" }, "shortcuts": { - "title": "Συντομεύσεις", "searchShortcuts": "Αναζήτηση συντομεύσεων...", - "filter": "Φίλτρο", - "categoryGeneral": "Γενικά", "categorySubtitleGridAndTextBox": "Προβολή λίστας υπότιτλων και πλαίσιο κειμένου", "categorySubtitleGrid": "Προβολή λίστας υπότιτλων", - "categoryWaveform": "Κυματομορφή", "categoryTextBox": "Πλαίσιο κειμένου", "categoryFile": "Αρχείο", "categoryVideo": "Βίντεο", "categorySync": "Συγχρονισμός", - "categoryTranslate": "Μετάφραση", - "categorySearch": "Αναζήτηση", - "categoryTools": "Εργαλεία", "categoryAi": "AI", + "categoryCustom": "Προσαρμοσμένο", + "editCustomShortcut": "Επεξεργασία προσαρμοσμένης συντόμευσης", + "customShortcutTextBoxKeyHint": "Χρησιμοποιήστε εδώ ένα πλήκτρο με Ctrl/Alt - ένα απλό πλήκτρο δεν θα πληκτρολογούσε πλέον στο πλαίσιο κειμένου.", + "customShortcutNumberX": "Προσαρμοσμένη συντόμευση #{0}", + "customShortcutNumberXY": "Προσαρμοσμένη συντόμευση #{0}: {1}", + "customShortcutStep": "Βήμα", + "customShortcutSteps": "Βήματα", + "customShortcutStepRunCommand": "Εκτέλεση εντολής", + "customShortcutStepInsertText": "Εισαγωγή κειμένου", + "customShortcutStepReplace": "Εύρεση και αντικατάσταση", + "customShortcutInsertAtCursor": "Στη θέση του δρομέα", + "customShortcutInsertAtStart": "Στην αρχή του κειμένου", + "customShortcutInsertAtEnd": "Στο τέλος του κειμένου", + "customShortcutSummaryRunCommandX": "Εκτέλεση: {0}", + "customShortcutSummaryInsertXY": "Εισαγωγή \"{0}\" ({1})", + "customShortcutSummaryReplaceXY": "Αντικατάσταση \"{0}\" με \"{1}\"", + "customShortcutSummaryReplaceRegexXY": "Αντικατάσταση κανονικής έκφρασης \"{0}\" με \"{1}\"", + "customShortcutPickCommand": "Επιλέξτε μια εντολή για εκτέλεση.", + "customShortcutEnterText": "Εισαγάγετε το κείμενο προς εισαγωγή.", + "customShortcutEnterFind": "Εισαγάγετε το κείμενο προς εύρεση.", + "customShortcutInvalidRegexX": "Μη έγκυρη κανονική έκφραση: {0}", + "customShortcutStepsHint": "Τα βήματα εκτελούνται από πάνω προς τα κάτω. Τα βήματα κειμένου εφαρμόζονται στις επιλεγμένες γραμμές· οι εντολές που ανοίγουν παράθυρο συνεχίζουν αφού αυτό κλείσει.", "activeIn": "Ενεργό σε", "activeInEverywhere": "Παντού", - "generalSetupLikeSe4": "Ρύθμιση όπως το Subtitle Edit 4 (θέμα, συντομεύσεις, κανόνες αντικατάστασης)", - "generalMergeSelectedLines": "Συγχώνευση επιλεγμένων γραμμών", - "generalMergeWithPrevious": "Συγχώνευση με το προηγούμενο", - "generalMergeWithNext": "Συγχώνευση με το επόμενο", - "generalMergeWithPreviousAndUnbreak": "Συγχώνευση με το προηγούμενο και αναίρεση αλλαγής γραμμής", - "generalMergeWithNextAndUnbreak": "Συγχώνευση με το επόμενο και αναίρεση αλλαγής γραμμής", - "generalMergeWithPreviousAndAutoBreak": "Συγχώνευση με το προηγούμενο και αυτόματη αλλαγή γραμμής", - "generalMergeWithNextAndAutoBreak": "Συγχώνευση με το επόμενο και αυτόματη αλλαγή γραμμής", - "generalMergeSelectedLinesAndAutoBreak": "Συγχώνευση επιλεγμένων γραμμών και αυτόματη αλλαγή γραμμής", "generalMergeSelectedLinesAndUnbreak": "Συγχώνευση επιλεγμένων γραμμών και αναίρεση αλλαγής γραμμής", "generalMergeSelectedLinesAndUnbreakCjk": "Συγχώνευση επιλεγμένων γραμμών και αναίρεση αλλαγής χωρίς κενό (CJK)", "generalUnbreakNoSpaceCjk": "Αναίρεση αλλαγής γραμμής χωρίς κενό (CJK)", - "generalMergeSelectedLinesOnlyFirstText": "Συγχώνευση επιλεγμένων γραμμών, μόνο το πρώτο κείμενο", + "generalToggleBreak": "Εναλλαγή αλλαγής γραμμής/αφαίρεσης αλλαγής γραμμής", "generalMergeSelectedLinesBilingual": "Συγχώνευση επιλεγμένων γραμμών δίγλωσσα", - "generalMergeWithPreviousBilingual": "Συγχώνευση με το προηγούμενο δίγλωσσα", - "generalMergeWithNextBilingual": "Συγχώνευση με το επόμενο δίγλωσσα", - "generalMergeOriginalAndTranslation": "Συγχώνευση αρχικού και μετάφρασης", "generalToggleTranslationMode": "Εναλλαγή λειτουργίας μετάφρασης", - "generalSwitchOriginalAndTranslation": "Εναλλαγή αρχικού και μετάφρασης", - "generalSwitchOriginalAndTranslationTextBoxes": "Εναλλαγή πλαισίων κειμένου αρχικού και μετάφρασης", "generalToggleTranslationAndOriginalInPreviews": "Εναλλαγή μετάφρασης και πρωτοτύπου στην προεπισκόπηση βίντεο/ήχου", "generalChooseLayout": "Επιλογή διάταξης", - "generalLayoutChooseX": "Διάταξη {0}", - "generalPlayFirstSelected": "Αναπαραγωγή πρώτου επιλεγμένου", - "generalGoToFirstSelectedLine": "Μετάβαση στην πρώτη επιλεγμένη γραμμή", - "generalGoToNextEmptyLine": "Μετάβαση στην επόμενη κενή γραμμή", "generalGoToNextSubtitle": "Μετάβαση στον επόμενο υπότιτλο", "generalGoToNextSubtitlePlayTranslate": "Μετάβαση στον επόμενο υπότιτλο (αναπαραγωγή μετάφρασης)", "generalGoToNextSubtitleCursorAtEnd": "Μετάβαση στον επόμενο υπότιτλο (δρομέας στο τέλος)", "generalGoToPrevSubtitle": "Μετάβαση στον προηγούμενο υπότιτλο", "generalGoToPrevSubtitlePlayTranslate": "Μετάβαση στον προηγούμενο υπότιτλο (αναπαραγωγή μετάφρασης)", - "generalGoToStartOfCurrentSubtitle": "Μετάβαση στην αρχή του τρέχοντος υπότιτλου", - "generalGoToEndOfCurrentSubtitle": "Μετάβαση στο τέλος του τρέχοντος υπότιτλου", - "generalGoToPreviousSubtitleAndFocusVideo": "Μετάβαση στον προηγούμενο υπότιτλο και εστίαση στο βίντεο", - "generalGoToNextSubtitleAndFocusVideo": "Μετάβαση στον επόμενο υπότιτλο και εστίαση στο βίντεο", - "generalGoToPrevSubtitleAndPlay": "Μετάβαση στον προηγούμενο υπότιτλο και αναπαραγωγή", - "generalGoToNextSubtitleAndPlay": "Μετάβαση στον επόμενο υπότιτλο και αναπαραγωγή", - "generalGoToPreviousSubtitleAndFocusWaveform": "Μετάβαση στον προηγούμενο υπότιτλο και εστίαση στην κυματομορφή", - "generalGoToNextSubtitleAndFocusWaveform": "Μετάβαση στον επόμενο υπότιτλο και εστίαση στην κυματομορφή", - "generalGoToLineNumber": "Μετάβαση σε αριθμό γραμμής", "generalGoToFirstLine": "Μετάβαση στην πρώτη γραμμή", "generalGoToLastLine": "Μετάβαση στην τελευταία γραμμή", "alsoSetVideoPosition": "Ορισμός και της θέσης βίντεο", "generalGoToVideoPosition": "Μετάβαση σε θέση βίντεο", "generalToggleItalic": "Εναλλαγή πλάγιων", "generalToggleBold": "Εναλλαγή έντονων", - "generalToggleBookmarks": "Εναλλαγή σελιδοδεικτών", - "generalFocusTextBox": "Εστίαση στο πλαίσιο κειμένου", - "generalToggleBookmarksWithText": "Εναλλαγή σελιδοδεικτών με κείμενο", - "generalEditBookmarks": "Επεξεργασία σελιδοδεικτών", - "fileNew": "Νέο", "generalToggleUnderline": "Εναλλαγή υπογράμμισης", + "generalToggleBox": "Εναλλαγή πλαισίου (EBU STL)", "fileOpen": "Άνοιγμα", "fileOpenKeepVideo": "Άνοιγμα (διατήρηση βίντεο)", "fileSave": "Αποθήκευση", "fileSaveAs": "Αποθήκευση ως", - "fileSaveAll": "Αποθήκευση όλων", "fileSaveOriginal": "Αποθήκευση αρχικού", - "fileSaveOriginalAs": "Αποθήκευση αρχικού ως", - "fileOpenOriginalSubtitle": "Άνοιγμα αρχικού υπότιτλου", - "fileCloseOriginalSubtitle": "Κλείσιμο αρχικού υπότιτλου", - "fileTranslatedSubtitle": "Μεταφρασμένος υπότιτλος", "fileCompare": "Σύγκριση", "fileStatistics": "Στατιστικά", "fileImportPlainText": "Εισαγωγή απλού κειμένου", - "fileImportBluRaySupForOcr": "Εισαγωγή Blu-ray SUP για OCR", - "fileImportBluRaySupForEdit": "Εισαγωγή Blu-ray SUP για επεξεργασία", - "fileImportTimeCodes": "Εισαγωγή χρονικών κωδίκων", "fileExportEbuStl": "Εξαγωγή EBU STL", "fileExportPac": "Εξαγωγή PAC", - "fileExportEdlClipName": "Εξαγωγή ονόματος κλιπ EDL", - "fileExportPlainText": "Εξαγωγή απλού κειμένου", - "fileExportCustomTextFormat1": "Εξαγωγή προσαρμοσμένης μορφής κειμένου 1", - "fileExportCustomTextFormat2": "Εξαγωγή προσαρμοσμένης μορφής κειμένου 2", - "fileExportCustomTextFormat3": "Εξαγωγή προσαρμοσμένης μορφής κειμένου 3", "fileExit": "Έξοδος", "openSeDataFolder": "Άνοιγμα φακέλου Subtitle Edit", - "editFind": "Εύρεση", - "editFindNext": "Εύρεση επόμενου", "editFindPrevious": "Εύρεση προηγούμενου", - "editReplace": "Αντικατάσταση", - "editMultipleReplace": "Πολλαπλή αντικατάσταση", "fillSelectedLinesWithClipboard": "Συμπλήρωση επιλεγμένων γραμμών με κείμενο προχείρου", - "editModifySelection": "Τροποποίηση επιλογής", - "listSelectAll": "Επιλογή όλων", - "listSelectFirst": "Επιλογή πρώτου", - "listSelectLast": "Επιλογή τελευταίου", "listInverseSelection": "Αντιστροφή επιλογής", "listDeleteSelection": "Διαγραφή επιλογής", + "deleteSelectionEverywhere": "Διαγραφή επιλογής (παντού)", "rippleDeleteSelection": "Διαγραφή επιλογής με μετατόπιση (ripple)", - "settings": "Ρυθμίσεις", "assigned": "Ανατεθειμένες", "unassigned": "Μη ανατεθειμένες", "pressedKeyX": "Πατημένο πλήκτρο: {0}", @@ -3443,6 +3772,7 @@ "detectKey": "Ανίχνευση πλήκτρου", "control": "Control", "alt": "Alt", + "altGr": "AltGr", "win": "Win", "shift": "Shift", "controlMac": "⌃", @@ -3453,11 +3783,14 @@ "resetShortcutsDetail": "Θέλετε να επαναφέρετε όλες τις συντομεύσεις στις προεπιλεγμένες τιμές;", "togglePlayPause": "Εναλλαγή αναπαραγωγής/παύσης", "toggleLockTimeCodes": "Εναλλαγή κλειδώματος χρονικών κωδίκων", + "toggleTimeCodeMode": "Εναλλαγή λειτουργίας χρονικού κώδικα (χρόνος/καρέ/αριθμοί καρέ)", + "toggleScreenPrivacy": "Εναλλαγή ιδιωτικότητας οθόνης (απόκρυψη ονομάτων αρχείων, έπειτα και κειμένων)", + "screenPrivacyFileNamesHidden": "Ιδιωτικότητα οθόνης: κρυμμένα ονόματα αρχείων", + "screenPrivacyFileNamesAndTextsHidden": "Ιδιωτικότητα οθόνης: κρυμμένα ονόματα αρχείων και κείμενα", + "screenPrivacyOff": "Ιδιωτικότητα οθόνης: ανενεργή", "duplicateSelectedLines": "Δημιουργία διπλότυπων επιλεγμένων γραμμών", "sourceView": "Προβολή πηγής", - "showAlignmentPicker": "Στοίχιση", "addOrEditBookmark": "Προσθήκη ή επεξεργασία σελιδοδείκτη", - "listBookmarks": "Λίστα σελιδοδεικτών", "toggleBookmark": "Εναλλαγή σελιδοδείκτη (επιλεγμένες γραμμές, χωρίς κείμενο)", "toggleForcedSelectedLines": "Εναλλαγή εξαναγκασμού (επιλεγμένες γραμμές)", "goToNextBookmark": "Μετάβαση στον επόμενο σελιδοδείκτη", @@ -3468,12 +3801,12 @@ "waveformSetStartAndSetEndOfPreviousMinusGap": "Ορισμός αρχής και ορισμός τέλους προηγούμενου μείον κενό", "waveformSetEndAndStartOfNextAfterGap": "Ορισμός τέλους και αρχής επόμενου συν κενό", "waveformSetEndAndStartOfNextAfterGapAndGoToNext": "Ορισμός τέλους και αρχής επόμενου συν κενό και μετάβαση στο επόμενο", - "fetchFirstWordFromNextSubtitle": "Λήψη πρώτης λέξης από τον επόμενο υπότιτλο", "moveLastWordToNextSubtitle": "Μετακίνηση τελευταίας λέξης στον επόμενο υπότιτλο", "moveLastWordFromFirstLineDownCurrentSubtitle": "Μετακίνηση τελευταίας λέξης πρώτης γραμμής κάτω (τρέχων υπότιτλος)", "moveFirstWordFromNextLineUpCurrentSubtitle": "Μετακίνηση πρώτης λέξης επόμενης γραμμής πάνω (τρέχων υπότιτλος)", "moveTextFromCursorToNextAndGoToNext": "Μετακίνηση κειμένου μετά τον δρομέα στον επόμενο υπότιτλο και μετάβαση στο επόμενο", "moveTextFromCursorToNextAndGoToNextAndPlay": "Μετακίνηση κειμένου μετά τον δρομέα στον επόμενο υπότιτλο, μετάβαση και αναπαραγωγή", + "moveTextFromCursorToNextAndGoToNextAndPlayAndPause": "Μετακίνηση κειμένου μετά τη θέση του δρομέα στον επόμενο υπότιτλο, μετάβαση στον επόμενο, αναπαραγωγή του και παύση στο τέλος του", "breakAtFirstSpaceFromCursor": "Αλλαγή γραμμής στο πρώτο κενό από τη θέση του δρομέα", "breakAtFirstSpaceFromCursorAndGoToNext": "Αλλαγή γραμμής στο πρώτο κενό από τη θέση του δρομέα και μετάβαση στο επόμενο", "toggleFocusGridAndWaveform": "Εναλλαγή εστίασης μεταξύ πλέγματος υπότιτλων και κυματομορφής/φασματογραφήματος", @@ -3485,7 +3818,6 @@ "videoGoToPreviousTimeCode": "Μετάβαση στον προηγούμενο χρονικό κώδικα", "videoGoToNextTimeCode": "Μετάβαση στον επόμενο χρονικό κώδικα", "goToNextLineAndSetVideoPosition": "Μετάβαση στον επόμενο υπότιτλο και ορισμός θέσης βίντεο", - "textBoxDeleteSelectionNoClipboard": "Πλαίσιο κειμένου: Διαγραφή επιλογής (χωρίς πρόχειρο)", "textBoxDeleteForward": "Πλαίσιο κειμένου: Διαγραφή επιλογής ή επόμενου χαρακτήρα (διαγραφή προς τα εμπρός)", "textBoxCut": "Πλαίσιο κειμένου: Αποκοπή", "textBoxCut2": "Πλαίσιο κειμένου: Αποκοπή (εναλλακτικά)", @@ -3504,6 +3836,7 @@ "listViewColumnTextUp": "Στήλη, κείμενο πάνω", "listViewColumnTextDown": "Στήλη, κείμενο κάτω", "autoTranslateSelectedLines": "Αυτόματη μετάφραση επιλεγμένων γραμμών...", + "autoTranslateSelectedLinesNoPrompt": "Αυτόματη μετάφραση επιλεγμένων γραμμών (χωρίς ερώτηση, χρήση τελευταίας μηχανής/γλωσσών)", "setAssaResolution": "Ορισμός ανάλυσης ASSA (PlayResX/PlayResY)", "setShortcutForX": "Ορισμός συντόμευσης για \"{0}\"", "commandFileNewKeepVideo": "Νέο (διατήρηση βίντεο)", @@ -3511,29 +3844,22 @@ "fileCloseOriginal": "Κλείσιμο αρχικού", "fileCloseTranslation": "Κλείσιμο μετάφρασης", "restoreAutoBackup": "Επαναφορά αυτόματου αντιγράφου ασφαλείας", - "openContainingFolder": "Άνοιγμα φακέλου περιεχομένου", "importTimeCodes": "Εισαγωγή χρονικών κωδίκων", "importSubtitleWithManuallyChosenEncoding": "Εισαγωγή υπότιτλου με χειροκίνητα επιλεγμένη κωδικοποίηση", "exportBluRaySup": "Εξαγωγή Blu-ray SUP", "exportCustomTextFormat": "Εξαγωγή προσαρμοσμένης μορφής κειμένου", "exportPlainText": "Εξαγωγή απλού κειμένου", - "showHistory": "Εμφάνιση ιστορικού", "toggleRightToLeft": "Εναλλαγή δεξιά-προς-αριστερά", "modifySelection": "Τροποποίηση επιλογής", "adjustDurations": "Προσαρμογή διαρκειών", "applyDurationLimits": "Εφαρμογή ορίων διάρκειας", "batchConvert": "Μαζική μετατροπή", - "bridgeGaps": "Γεφύρωση κενών", "applyMinGap": "Εφαρμογή ελάχιστου κενού", - "fixCommonErrors": "Διόρθωση συνηθισμένων σφαλμάτων", "makeEmptyTranslationFromCurrentSubtitle": "Δημιουργία κενής μετάφρασης από τον τρέχοντα υπότιτλο", - "mergeLinesWithSameText": "Συγχώνευση γραμμών με ίδιο κείμενο", - "mergeLinesWithSameTimeCodes": "Συγχώνευση γραμμών με ίδιους χρονικούς κώδικες", "splitBreakLongLines": "Διαίρεση/εξισορρόπηση μεγάλων γραμμών", "mergeShortLines": "Συγχώνευση σύντομων γραμμών", "mergeContinuationLines": "Συγχώνευση γραμμών συνέχειας", "snapAllTimesToFrames": "Προσκόλληση όλων των χρόνων σε καρέ", - "removeTextForHearingImpaired": "Αφαίρεση κειμένου για άτομα με προβλήματα ακοής", "joinSubtitles": "Ένωση υπότιτλων", "splitSubtitle": "Διαίρεση υπότιτλου", "spellCheck": "Ορθογραφικός έλεγχος", @@ -3555,7 +3881,6 @@ "adjustAllTimes": "Προσαρμογή όλων των χρόνων", "visualSync": "Οπτικός συγχρονισμός", "translateViaCopyPaste": "Μετάφραση μέσω αντιγραφής-επικόλλησης", - "shortcuts": "Συντομεύσεις", "wordLists": "Λίστες λέξεων", "chooseUiLanguage": "Επιλογή γλώσσας διεπαφής", "chooseRuleProfile": "Επιλογή προφίλ κανόνων", @@ -3566,6 +3891,7 @@ "textBoxItalic": "Πλαίσιο κειμένου πλάγια", "textBoxBold": "Έντονα στο πλαίσιο κειμένου", "textBoxUnderline": "Υπογράμμιση στο πλαίσιο κειμένου", + "textBoxBox": "Πλαίσιο κειμένου, πλαίσιο (EBU STL)", "resetWaveformZoomAndSpeed": "Επαναφορά εστίασης κυματομορφής και ταχύτητας αναπαραγωγής", "togglePlaybackSpeed": "Εναλλαγή ταχύτητας αναπαραγωγής", "playbackSpeedSlower": "Πιο αργή ταχύτητα αναπαραγωγής", @@ -3577,22 +3903,28 @@ "seekSilenceForward": "Αναζήτηση σιωπής εμπρός", "waveformGuessStart": "Εκτίμηση χρόνου αρχής από την κυματομορφή", "waveformGuessEnd": "Εκτίμηση χρόνου τέλους από την κυματομορφή", + "waveformGuessStartAndEnd": "Εκτίμηση χρόνου έναρξης και λήξης από την κυματομορφή", "setVideoPositionCurrentSubtitleStart": "Ορισμός θέσης βίντεο στην αρχή της τρέχουσας γραμμής", "goToSubtitlePositionAndPause": "Μετάβαση στη θέση υπότιτλου και παύση", "setVideoPositionCurrentSubtitleEnd": "Ορισμός θέσης βίντεο στο τέλος της τρέχουσας γραμμής", "toggleAudioTracks": "Εναλλαγή κομματιών ήχου", - "listErrors": "Λίστα σφαλμάτων", "goToNextError": "Μετάβαση στο επόμενο σφάλμα", "goToPreviousError": "Μετάβαση στο προηγούμενο σφάλμα", "addNameToNameList": "Προσθήκη ονόματος στη λίστα ονομάτων", "findDoubleWords": "Εύρεση διπλών λέξεων", "findDoubleLines": "Εύρεση διπλών γραμμών", "colorX": "Χρώμα {0}", - "removeColor": "Αφαίρεση χρώματος", "surroundWith": "Περικύκλωση με...", "surroundWithXY": "Περικύκλωση με {0}/{1}", - "repeatLine": "Επανάληψη γραμμής", "surroundWithNumberX": "Περιτύλιξη με #{0}", + "surroundWithBehavior": "Συμπεριφορά", + "surroundWithBehaviorToggle": "Εναλλαγή (προσθήκη ή αφαίρεση αν υπάρχει)", + "surroundWithBehaviorAdd": "Προσθήκη (κάθε φορά)", + "surroundWithBehaviorRemove": "Αφαίρεση (όλων)", + "surroundWithBehaviorRemoveOnce": "Αφαίρεση (ενός κάθε φορά)", + "surroundWithWorksOn": "Εφαρμόζεται σε", + "surroundWithScopeSelectionOrText": "Επιλογή, αλλιώς ολόκληρο το κείμενο", + "surroundWithScopeEachLine": "Κάθε γραμμή", "searchVia": "Αναζήτηση μέσω", "searchViaX": "Αναζήτηση μέσω {0}", "searchViaNumberX": "Αναζήτηση μέσω #{0}", @@ -3620,6 +3952,11 @@ "copySubtitlePathToClipboard": "Αντιγραφή διαδρομής υπότιτλου στο πρόχειρο", "copySubtitleOriginalPathToClipboard": "Αντιγραφή διαδρομής αρχικού υπότιτλου στο πρόχειρο", "focusTextBox": "Εστίαση στο πλαίσιο κειμένου", + "focusSubtitleListView": "Εστίαση στην προβολή λίστας υπότιτλων", + "focusWaveform": "Εστίαση στην κυματομορφή", + "focusOriginalTextBox": "Εστίαση στο πλαίσιο αρχικού κειμένου", + "textBoxGoToStart": "Πλαίσιο κειμένου, μετάβαση στην αρχή", + "textBoxGoToEnd": "Πλαίσιο κειμένου, μετάβαση στο τέλος", "sortByNumber": "Ταξινόμηση κατά αριθμό", "sortByStartTime": "Ταξινόμηση κατά χρόνο \"Εμφάνισης\"", "sortByEndTime": "Ταξινόμηση κατά χρόνο \"Απόκρυψης\"", @@ -3644,7 +3981,6 @@ "assaStyles": "Στυλ ASSA", "assaProperties": "Ιδιότητες ASSA", "assaAttachments": "Συνημμένα ASSA", - "assaVideoColorPicker": "ASSA Επιλογέας χρώματος βίντεο", "recalculateDurationSelectedLines": "Επανυπολογισμός διάρκειας (επιλεγμένες γραμμές)", "setDurationMaxCpsSelectedLines": "Ορισμός διάρκειας στο μέγιστο CPS (επιλεγμένες γραμμές)", "toggleWaveformAndSpectrogramHeight": "Εναλλαγή διαιρεμένου ύψους κυματομορφής/φασματογραφήματος", @@ -3658,9 +3994,9 @@ "selectionToSentenceCase": "Πλαίσιο κειμένου: Επιλογή σε Μορφή πρότασης", "selectionToggleCasing": "Πλαίσιο κειμένου: Εναλλαγή πεζών/κεφαλαίων (ΚΕΦΑΛΑΙΑ -> πεζά -> Τίτλος)", "googleIt": "Αναζήτηση στο Google (επιλεγμένο κείμενο)", - "setActorXY": "Ορισμός ηθοποιού {0}: {1}", + "setActorX": "Ορισμός ηθοποιού: {0}", "setNewActor": "Ορισμός νέου ηθοποιού...", - "setActorSlotXTitle": "Θέση ηθοποιού {0}", + "setStyleX": "Ορισμός στυλ: {0}", "toggleDialogDashes": "Εναλλαγή παυλών διαλόγου", "mergeWithLineBeforeAsDialog": "Συγχώνευση με την προηγούμενη γραμμή ως διάλογο", "mergeWithLineAfterAsDialog": "Συγχώνευση με την επόμενη γραμμή ως διάλογο", @@ -3671,13 +4007,27 @@ "moveStartOneFrameBackKeepGapPrev": "Μετακίνηση αρχής ένα καρέ πίσω (διατήρηση κενού με το προηγούμενο αν είναι κοντά)", "moveStartOneFrameForwardKeepGapPrev": "Μετακίνηση αρχής ένα καρέ εμπρός (διατήρηση κενού με το προηγούμενο αν είναι κοντά)", "moveEndOneFrameBackKeepGapNext": "Μετακίνηση τέλους ένα καρέ πίσω (διατήρηση κενού με το επόμενο αν είναι κοντά)", - "moveEndOneFrameForwardKeepGapNext": "Μετακίνηση τέλους ένα καρέ εμπρός (διατήρηση κενού με το επόμενο αν είναι κοντά)" + "moveEndOneFrameForwardKeepGapNext": "Μετακίνηση τέλους ένα καρέ εμπρός (διατήρηση κενού με το επόμενο αν είναι κοντά)", + "moveStartXMsBack": "Μετακίνηση έναρξης X ms πίσω (το X ορίζεται στις Ρυθμίσεις, επίσης στον Οπτικό συγχρονισμό)", + "moveStartXMsForward": "Μετακίνηση έναρξης X ms εμπρός (το X ορίζεται στις Ρυθμίσεις, επίσης στον Οπτικό συγχρονισμό)", + "moveEndXMsBack": "Μετακίνηση λήξης X ms πίσω (το X ορίζεται στις Ρυθμίσεις, επίσης στον Οπτικό συγχρονισμό)", + "moveEndXMsForward": "Μετακίνηση λήξης X ms εμπρός (το X ορίζεται στις Ρυθμίσεις, επίσης στον Οπτικό συγχρονισμό)", + "moveSelectedLinesXMsBack": "Μετακίνηση επιλεγμένων γραμμών X ms πίσω (το X ορίζεται στις Ρυθμίσεις)", + "moveSelectedLinesXMsForward": "Μετακίνηση επιλεγμένων γραμμών X ms εμπρός (το X ορίζεται στις Ρυθμίσεις)", + "moveSelectedLinesAndForwardXMsBack": "Μετακίνηση επιλεγμένων γραμμών και όλων των επόμενων X ms πίσω (το X ορίζεται στις Ρυθμίσεις)", + "moveSelectedLinesAndForwardXMsForward": "Μετακίνηση επιλεγμένων γραμμών και όλων των επόμενων X ms εμπρός (το X ορίζεται στις Ρυθμίσεις)", + "moveAllLinesXMsBack": "Μετακίνηση όλων των γραμμών X ms πίσω (το X ορίζεται στις Ρυθμίσεις)", + "moveAllLinesXMsForward": "Μετακίνηση όλων των γραμμών X ms εμπρός (το X ορίζεται στις Ρυθμίσεις)", + "moveSelectedLinesCustomMsBack": "Μετακίνηση επιλεγμένων γραμμών, προσαρμοσμένα χιλιοστά δευτερολέπτου ({0:#,###,##0}) πίσω, {1}", + "moveSelectedLinesCustomMsForward": "Μετακίνηση επιλεγμένων γραμμών, προσαρμοσμένα χιλιοστά δευτερολέπτου ({0:#,###,##0}) εμπρός, {1}", + "moveSelectedLinesAndForwardCustomMsBack": "Μετακίνηση επιλεγμένων γραμμών και όλων των επόμενων, προσαρμοσμένα χιλιοστά δευτερολέπτου ({0:#,###,##0}) πίσω, {1}", + "moveSelectedLinesAndForwardCustomMsForward": "Μετακίνηση επιλεγμένων γραμμών και όλων των επόμενων, προσαρμοσμένα χιλιοστά δευτερολέπτου ({0:#,###,##0}) εμπρός, {1}", + "moveAllLinesCustomMsBack": "Μετακίνηση όλων των γραμμών, προσαρμοσμένα χιλιοστά δευτερολέπτου ({0:#,###,##0}) πίσω, {1}", + "moveAllLinesCustomMsForward": "Μετακίνηση όλων των γραμμών, προσαρμοσμένα χιλιοστά δευτερολέπτου ({0:#,###,##0}) εμπρός, {1}" }, "wordLists": { "title": "Λίστες λέξεων", "addName": "Προσθήκη ονόματος", - "addWord": "Προσθήκη λέξης", - "addPair": "Προσθήκη ζεύγους", "nameAndIgnoreList": "Λίστα ονομάτων/παράβλεψης", "userWords": "Λίστα λέξεων χρήστη", "ocrFixList": "Λίστα διορθώσεων OCR", @@ -3694,7 +4044,6 @@ "noPluginsInstalled": "Δεν υπάρχουν εγκατεστημένα πρόσθετα", "getPluginsOnline": "Λήψη προσθέτων online...", "getPluginsTitle": "Λήψη προσθέτων", - "enabled": "Ενεργό", "disabled": "Ανενεργό", "notSupportedOnThisOs": "Δεν υποστηρίζεται σε αυτό το λειτουργικό σύστημα", "removePluginXConfirm": "Αφαίρεση προσθέτου '{0}'; Αυτό διαγράφει τον φάκελο του προσθέτου:{1}", @@ -3711,17 +4060,19 @@ "downloadingXPercent": "Λήψη {0}%...", "pluginXInstalled": "Το '{0}' εγκαταστάθηκε.", "couldNotInstallPluginX": "Αδυναμία εγκατάστασης του '{0}': {1}", - "notInstalled": "Μη εγκατεστημένο", "install": "Εγκατάσταση", "reinstall": "Επανεγκατάσταση", - "update": "Ενημέρωση", "installedX": "Εγκατεστημένο ({0})", "updateAvailableXToY": "Διαθέσιμη ενημέρωση ({0} → {1})", "applyPluginToWhichLinesX": "Εφαρμογή του '{0}' σε:", "applyToSelectedLinesX": "Επιλεγμένες γραμμές ({0})", "applyToAllLinesX": "Όλες οι γραμμές ({0})", "updateAllXAvailable": "Ενημέρωση όλων ({0})", - "updatingXOfY": "Ενημέρωση {0}/{1}..." + "updatingXOfY": "Ενημέρωση {0}/{1}...", + "doNotAskAgain": "Να μην ερωτηθώ ξανά (αλλαγή στη Διαχείριση προσθέτων)", + "applyPluginsTo": "Εφαρμογή προσθέτων σε", + "askEachTime": "Ερώτηση κάθε φορά", + "allLines": "Όλες οι γραμμές" }, "help": { "aboutSubtitleEdit": "Σχετικά με το Subtitle Edit", @@ -3765,6 +4116,8 @@ "nOcrDatabase": "Βάση δεδομένων nOCR", "drawMode": "Λειτουργία σχεδίασης:", "addNewCharcter": "Προσθήκη νέου χαρακτήρα", + "addNOcrCharacterTitle": "Προσθήκη χαρακτήρα nOCR για τη γραμμή {0}, χαρακτήρας {1} από {2} με χρήση της βάσης δεδομένων \"{3}\"", + "addBinaryOcrCharacterTitle": "Προσθήκη χαρακτήρα binary OCR για τη γραμμή {0}, χαρακτήρας {1} από {2} με χρήση της βάσης δεδομένων \"{3}\"", "lineIndexX": "Γραμμή {0}", "inspectNOcrAdditions": "Επιθεώρηση νέων προσθηκών nOCR", "ocrSelectedLines": "OCR επιλεγμένων γραμμών", @@ -3813,6 +4166,8 @@ "binaryImageCompareInspectImageMatches": "\"Δυαδική σύγκριση εικόνας\" - Επιθεώρηση αντιστοιχίσεων εικόνας", "saveBlankTextTitle": "Αποθήκευση κενού κειμένου;", "saveBlankTextPrompt": "Αποθήκευση κενού κειμένου για την εικόνα;", + "drawnLinesDoNotMatchTitle": "Οι γραμμές δεν ταιριάζουν με την εικόνα", + "drawnLinesDoNotMatchPrompt": "Ορισμένες από τις σχεδιασμένες γραμμές δεν ταιριάζουν με την εικόνα (οι πράσινες γραμμές πρέπει να βρίσκονται πάνω στον χαρακτήρα, οι κόκκινες εκτός του), οπότε αυτός ο χαρακτήρας δεν θα αναγνωρίζεται.\n\nΑποθήκευση παρ' όλα αυτά;", "yesAndNeverAskAgain": "Ναι και να μην ερωτηθώ ξανά", "importTextFromSubtitleDotDotDot": "Εισαγωγή κειμένου από υπότιτλο...", "importTextFromSubtitleOverwritePrompt": "Ορισμένες γραμμές έχουν ήδη κείμενο OCR. Αντικατάσταση υπάρχοντος κειμένου;", @@ -3838,7 +4193,6 @@ "vobSubColorsTitle": "Χρώματα VobSub/DVD", "vobSubColorsHeader": "Προσαρμογή των τεσσάρων χρωμάτων VobSub", "vobSubColorsDescription": "Οι υπότιτλοι VobSub/DVD χρησιμοποιούν τέσσερα χρώματα ευρετηρίου: φόντο, μοτίβο, έμφαση 1 και έμφαση 2. Επιλέξτε κάθε χρώμα παρακάτω για να παρακάμψετε την ενσωματωμένη παλέτα - χρήσιμο όταν τα αρχικά χρώματα δίνουν κακή αντίθεση OCR.", - "vobSubColorBackground": "Φόντο", "vobSubColorPattern": "Μοτίβο", "vobSubColorEmphasis1": "Έμφαση 1", "vobSubColorEmphasis2": "Έμφαση 2", @@ -3872,9 +4226,26 @@ "crispEmbedSettingsTitle": "Ρυθμίσεις CrispEmbed", "crispEmbedDescription": "Τοπική μηχανή OCR με πολλαπλά υποστηρικτικά μοντέλα - κάντε λήψη της μηχανής και των μοντέλων εδώ.", "trainNOcrDatabase": "Εκπαίδευση βάσης δεδομένων nOCR...", + "optimizeNOcrDatabase": "Βελτιστοποίηση βάσης δεδομένων nOCR...", + "optimizeNOcrDatabaseQuestionXY": "Αφαίρεση των γραμμών που δεν ξεχωρίζουν χαρακτήρες από παρόμοιους χαρακτήρες στη βάση δεδομένων nOCR \"{0}\";\n\nΑυτό κάνει τη βάση δεδομένων μικρότερη και το OCR ταχύτερο, συνήθως με λιγότερα σφάλματα. Διαρκεί έως ένα λεπτό.\n\nΈνα αντίγραφο ασφαλείας αποθηκεύεται ως \"{1}\".", + "optimizingNOcrDatabaseXY": "Βελτιστοποίηση βάσης δεδομένων nOCR... {0}/{1}", + "nOcrDatabaseOptimizedXYZ": "Η βάση δεδομένων nOCR \"{0}\" βελτιστοποιήθηκε: {1:#,##0} -> {2:#,##0} γραμμές.", "startTraining": "Έναρξη εκπαίδευσης", "abortTraining": "Ματαίωση εκπαίδευσης", "trainingOptions": "Επιλογές εκπαίδευσης", + "trainNOcrSubtitle": "Αποδίδει χαρακτήρες με εγκατεστημένες γραμματοσειρές και διδάσκει στο nOCR τα σχήματά τους. Επιλέξτε τις γραμματοσειρές που χρησιμοποιούν οι υπότιτλοί σας.", + "xFontsSelected": "{0:#,##0} επιλεγμένες", + "subtitleFonts": "Γραμματοσειρές υπότιτλων", + "emptyDatabase": "Κενή βάση δεδομένων", + "startFromDatabase": "Έναρξη από", + "startFromDatabaseHint": "Προσθήκη των εκπαιδευμένων χαρακτήρων σε αντίγραφο μιας υπάρχουσας βάσης δεδομένων (συνιστάται) ή έναρξη από κενή", + "fontSizes": "Μεγέθη γραμματοσειράς", + "fontSizesHint": "Μεγέθη γραμματοσειράς σε pixel για απόδοση, χωρισμένα με κόμμα, π.χ. \"30, 40\"", + "numberOfLineSegmentsHint": "Γραμμές που ελέγχονται ανά χαρακτήρα - περισσότερες είναι αυστηρότερες αλλά πιο αργές", + "enterDatabaseName": "Εισαγάγετε όνομα για τη βάση δεδομένων", + "trainingFontXSizeYCharacterZ": "Εκπαίδευση \"{0}\" στα {1} px: {2}", + "xLearned": "{0:#,##0} μαθεύτηκαν", + "xSkipped": "{0:#,##0} παραλείφθηκαν", "charactersToTrain": "Χαρακτήρες προς εκπαίδευση", "letterCombinationsToTrain": "Συνδυασμοί γραμμάτων που μπορεί να χωριστούν σε μία εικόνα", "importCharactersFromSubtitleFile": "Εισαγωγή χαρακτήρων από αρχείο υπότιτλων...", @@ -3901,6 +4272,8 @@ "drawZoomOut": "Σμίκρυνση (Ctrl+-)", "drawResetView": "Επαναφορά προβολής (Ctrl+0)", "drawToggleGrid": "Εναλλαγή πλέγματος (Ctrl+G)", + "drawTogglePreview": "Εναλλαγή προεπισκόπησης (F9)", + "drawPreviewFailed": "Η προεπισκόπηση απέτυχε - απαιτείται ffmpeg με libass (φίλτρο subtitles)", "drawCopyToClipboard": "Αντιγραφή στο πρόχειρο (Ctrl+C)", "drawShapes": "Σχήματα", "drawSelectedPoint": "Επιλεγμένο σημείο", @@ -3908,16 +4281,86 @@ "drawSelectedLayer": "Επιλεγμένο επίπεδο", "drawToolX": "Εργαλείο: {0}", "drawHelpText": "Κλικ για προσθήκη σημείων • Enter/F8 για κλείσιμο σχήματος • Shift+Σύρσιμο για μετατόπιση • Ctrl+Τροχός για εστίαση", + "drawMoveToLayer": "Μετακίνηση σε επίπεδο", + "drawFlipHorizontal": "Οριζόντια αναστροφή", + "drawFlipVertical": "Κατακόρυφη αναστροφή", + "drawHideShape": "Απόκρυψη σχήματος", + "drawShowShape": "Εμφάνιση σχήματος", + "drawHideLayer": "Απόκρυψη επιπέδου", + "drawShowLayer": "Εμφάνιση επιπέδου", + "drawDeleteLayer": "Διαγραφή επιπέδου", + "drawDeletePoint": "Διαγραφή σημείου", + "drawCancelDrawing": "Ακύρωση σχεδίασης", + "drawLayerX": "Επίπεδο {0}", + "drawImportSvg": "Εισαγωγή εικόνας SVG...", + "drawRotateClockwise": "Περιστροφή 90° δεξιόστροφα", + "drawConvertToCurve": "Μετατροπή τμήματος σε καμπύλη", + "drawBackground": "Φόντο", + "drawPosition": "Θέση", + "drawZoom": "Ζουμ", + "drawShapeTool": "Εργαλείο σχήματος", + "drawColorPickerTool": "Επιλογή χρώματος από σχήμα ή από το φόντο", + "drawShapeLibrary": "Βιβλιοθήκη σχημάτων", + "drawShapeLibraryHint": "Σύρετε στον καμβά για να τοποθετήσετε ένα σχήμα ή κάντε κλικ για το προεπιλεγμένο μέγεθος. Το Shift διατηρεί τις αναλογίες του.", + "drawAddToLibrary": "Προσθήκη στη βιβλιοθήκη σχημάτων...", + "drawRemoveFromLibrary": "Αφαίρεση από τη βιβλιοθήκη σχημάτων", + "drawShapeName": "Όνομα σχήματος", + "drawCategorySpeechBubbles": "Συννεφάκια ομιλίας", + "drawCategoryArrows": "Βέλη", + "drawCategoryBasicShapes": "Βασικά σχήματα", + "drawCategorySymbols": "Σύμβολα", + "drawCategoryMyShapes": "Τα σχήματά μου", + "drawShapeSpeechBubble": "Συννεφάκι ομιλίας", + "drawShapeSpeechBubbleRight": "Συννεφάκι ομιλίας, ουρά δεξιά", + "drawShapeSpeechBubbleUp": "Συννεφάκι ομιλίας, ουρά πάνω", + "drawShapeOvalBubble": "Οβάλ συννεφάκι ομιλίας", + "drawShapeBoxBubble": "Ορθογώνιο συννεφάκι ομιλίας", + "drawShapeThoughtBubble": "Συννεφάκι σκέψης", + "drawShapeShoutBubble": "Συννεφάκι φωνής", + "drawShapeCaptionBox": "Πλαίσιο λεζάντας", + "drawShapeArrow": "Βέλος", + "drawShapeDoubleArrow": "Διπλό βέλος", + "drawShapeChevron": "Σιρίτι (chevron)", + "drawShapeCurvedArrow": "Καμπύλο βέλος", + "drawShapeRoundedRectangle": "Στρογγυλεμένο ορθογώνιο", + "drawShapeTriangle": "Τρίγωνο", + "drawShapeDiamond": "Ρόμβος", + "drawShapePentagon": "Πεντάγωνο", + "drawShapeHexagon": "Εξάγωνο", + "drawShapeOctagon": "Οκτάγωνο", + "drawShapeStar": "Αστέρι", + "drawShapeRing": "Δακτύλιος", + "drawShapePlus": "Συν", + "drawShapeBanner": "Λάβαρο", + "drawShapeHeart": "Καρδιά", + "drawShapeCheck": "Σημάδι ελέγχου", + "drawShapeMusicNote": "Μουσική νότα", + "drawShapeMusicNotes": "Μουσικές νότες", + "drawShapeLightning": "Κεραυνός", + "drawShapeCloud": "Σύννεφο", + "drawShapeMoon": "Φεγγάρι", + "drawShapeBadge": "Σήμα", + "drawSize": "Μέγεθος", + "drawLayer": "Επίπεδο", + "drawBackgroundVideoFrame": "Καρέ βίντεο (τρέχουσα θέση)", + "drawBackgroundVideoFrameAt": "Καρέ βίντεο στο...", + "drawBackgroundImage": "Αρχείο εικόνας...", + "drawBackgroundNone": "Χωρίς φόντο", + "drawBackgroundStretch": "Τέντωμα εικόνας στον καμβά", + "drawBackgroundOpacity": "Αδιαφάνεια φόντου", + "drawBackgroundVideoFrameFailed": "Δεν ήταν δυνατή η λήψη καρέ βίντεο στο {0}.", + "drawImages": "Εικόνες", + "drawConvertToLine": "Μετατροπή τμήματος σε ευθεία", + "drawConvertShapeToCurves": "Μετατροπή όλων των ευθειών σε καμπύλες", + "drawConvertShapeToLines": "Μετατροπή όλων των καμπυλών σε ευθείες", + "drawRotateCounterClockwise": "Περιστροφή 90° αριστερόστροφα", + "drawSvgImages": "Εικόνες SVG", + "drawSvgNoShapes": "Δεν βρέθηκαν γεμισμένα σχήματα ή πινελιές στο αρχείο SVG.", "progressBarTitle": "Γραμμή προόδου ASSA", "progressBarSettings": "Γραμμή προόδου", - "progressBarPosition": "Θέση", - "progressBarBottom": "Κάτω", - "progressBarTop": "Πάνω", "mouseOverColor": "Χρώμα κατά την αιώρηση ποντικιού", "clickedColor": "Χρώμα μετά από κλικ", "progressBarForeColor": "Χρώμα προσκηνίου", - "progressBarBackColor": "Χρώμα φόντου", - "progressBarStyle": "Στυλ", "progressBarSquareCorners": "Τετράγωνες γωνίες", "progressBarRoundedCorners": "Στρογγυλεμένες γωνίες", "progressBarChapters": "Κεφάλαια", @@ -3926,8 +4369,6 @@ "progressBarXAdjustment": "Προσαρμογή X", "progressBarYAdjustment": "Προσαρμογή Y", "progressBarTextAlignment": "Στοίχιση κειμένου", - "progressBarTakePosFromVideo": "Λήψη θέσης από το βίντεο", - "progressBarPreview": "Προεπισκόπηση", "fontCollectorTitle": "Συλλέκτης γραμματοσειρών", "fontCollectorUsedIn": "Χρησιμοποιείται σε", "fontCollectorFontFiles": "Αρχείο(α) γραμματοσειράς", @@ -3974,9 +4415,7 @@ "resolutionResamplerVideoDiffers": "Η ανάλυση του υπότιτλου ({0}x{1}) διαφέρει από την ανάλυση του βίντεο ({2}x{3}).\nΝα αλλάξει ο υπότιτλος ώστε να ταιριάζει με το βίντεο;", "resolutionResamplerAskOnVideoOpen": "Ερώτηση όταν ανοίγει βίντεο με διαφορετική ανάλυση", "backgroundBoxGenerator": "Δημιουργία πλαισίου φόντου", - "backgroundBoxPadding": "Εσωτερικό περιθώριο", "backgroundBoxFillWidth": "Πλήρωση πλάτους", - "backgroundBoxBoxColor": "Χρώμα πλαισίου", "backgroundBoxRadius": "Ακτίνα", "backgroundBoxCircle": "Κύκλος", "backgroundBoxSpikes": "Ακίδες", @@ -3988,12 +4427,9 @@ "backgroundBoxTornPaperDouble": "Σχισμένο χαρτί (πάνω και κάτω)", "backgroundBoxStarburst": "Έκρηξη αστεριού", "backgroundBoxScroll": "Πάπυρος / Περγαμηνή", - "stylesTitle": "Στυλ Advanced Sub Station Alpha", "stylesInFile": "Στυλ στο αρχείο", "stylesSaved": "Αποθηκευμένα στυλ", - "category": "Κατηγορία", "allCategories": "[Όλες]", - "defaultCategory": "Προεπιλογή", "newCategory": "Νέα κατηγορία...", "renameCategory": "Μετονομασία κατηγορίας...", "deleteCategory": "Διαγραφή κατηγορίας", @@ -4016,13 +4452,26 @@ "graphics": "Γραφικά", "copyToStorageStyles": "Αντιγραφή στα αποθηκευμένα στυλ", "copyToFileStyles": "Αντιγραφή στα στυλ αρχείου", + "styleXAlreadyExistsInStorage": "Υπάρχει ήδη στυλ με όνομα \"{0}\" στην αποθήκευση.\n\nΑντικατάσταση ή διατήρηση και των δύο (το αντίγραφο παίρνει νέο όνομα);", + "styleXAlreadyExistsInFile": "Υπάρχει ήδη στυλ με όνομα \"{0}\" στο αρχείο.\n\nΑντικατάσταση ή διατήρηση και των δύο (το αντίγραφο παίρνει νέο όνομα);", + "styleNameCannotBeEmpty": "Το όνομα στυλ δεν μπορεί να είναι κενό.", + "styleNameXIsUsedMoreThanOnce": "Το όνομα στυλ \"{0}\" χρησιμοποιείται από περισσότερα από ένα στυλ στο αρχείο.", + "overwrite": "Αντικατάσταση", + "keepBoth": "Διατήρηση και των δύο", + "doThisForAllConflictingStyles": "Να γίνει αυτό για όλα τα αντικρουόμενα στυλ", "setStyleAsDefault": "Ορισμός στυλ ως προεπιλογή", "takeUsagesFromDotDotDot": "Λήψη χρήσεων από...", "replaceStyleWithDotDotDot": "Αντικατάσταση στυλ με...", "noAttachmentsFound": "Δεν βρέθηκαν συνημμένα στο επιλεγμένο αρχείο ASSA.", "deleteStyleQuestion": "Διαγραφή στυλ;", "deleteStylesQuestion": "Διαγραφή στυλ;", + "deleteXStylesQuestion": "Θέλετε να διαγράψετε {0} στυλ;", + "deleteXStylesFromStorageQuestion": "Θέλετε να διαγράψετε {0} στυλ από την αποθήκευση;", + "deleteStyleXFromStorageQuestion": "Θέλετε να διαγράψετε το στυλ \"{0}\" από την αποθήκευση;", + "deleteStyleXFromFileQuestion": "Θέλετε να διαγράψετε το στυλ \"{0}\" από το τρέχον αρχείο;", "openStyleImportFile": "Άνοιγμα αρχείου υπότιτλων για εισαγωγή στυλ", + "styleImportFiles": "Αρχεία στυλ (.ass, .sty, κατηγορίες Subtitle Edit 4 .template)", + "ssaStyleImportFiles": "Αρχεία στυλ (.ssa, .ass, .sty)", "primary": "Πρωτεύον", "secondary": "Δευτερεύον", "applyOverrideTags": "Εφαρμογή ετικετών παράκαμψης", @@ -4037,7 +4486,6 @@ "spaceIncrease": "Αύξηση διαστήματος", "popIn": "Αναδυόμενη είσοδος", "spinIn": "Περιστρεφόμενη είσοδος", - "playCurrent": "Αναπαραγωγή τρέχοντος", "setPosition": "Ορισμός θέσης", "rotation": "Περιστροφή", "scaleX": "Κλίμακα X", @@ -4081,10 +4529,8 @@ "advancedEffectOldMovieDescription": "Προσομοιώνει έναν παλιό προβολέα φιλμ 35mm με θόρυβο, βινιέτα και γρατζουνιές", "advancedEffectNeonBurst": "Έκρηξη νέον", "advancedEffectNeonBurstDescription": "Υψηλής ενέργειας λεζάντες στυλ 'Shorts' με λάμψη νέον και κινούμενη είσοδο με κλιμάκωση", - "advancedEffectSettings": "Ρυθμίσεις", "advancedEffectSnowFlakeCount": "Πλήθος νιφάδων", "advancedEffectStarfieldStarCount": "Πλήθος αστεριών", - "advancedEffectStarfieldSpeed": "Ταχύτητα", "advancedEffectGlitch": "Glitch", "advancedEffectGlitchDescription": "Ψηφιακό glitch με χρωματική εκτροπή, λευκές αναλαμπές και επικαλύψεις παραμόρφωσης", "advancedEffectBounceIn": "Αναπήδηση εισόδου", @@ -4093,8 +4539,6 @@ "advancedEffectFirefliesDescription": "Ζεστές φωτεινές κιτρινο-κεχριμπαρένιες κουκκίδες που κινούνται οργανικά στην οθόνη με παλλόμενη λάμψη", "advancedEffectMatrix": "Matrix", "advancedEffectMatrixDescription": "Πέφτουσες πράσινες στήλες χαρακτήρων με το κείμενο του υπότιτλου να χτίζεται γράμμα-γράμμα από τη βροχή", - "advancedEffectAudioBars": "Μπάρες ήχου (κυματομορφή)", - "advancedEffectAudioBarsDescription": "Κινούμενες μπάρες ισοσταθμιστή που οδηγούνται από την κυματομορφή ήχου — οι μπάρες κυλούν αριστερά ως κυματομορφή και πάλλονται με τον ήχο", "advancedEffectAudioPulse": "Παλμός κειμένου με ήχο", "advancedEffectAudioPulseDescription": "Το κείμενο του υπότιτλου λάμπει και πάλλεται με την ένταση του ήχου — τα ήσυχα τμήματα εμφανίζουν κανονικό κείμενο, οι δυνατές κορυφές προσθέτουν χρωματιστό φωτοστέφανο και ελαφριά μεγέθυνση", "advancedEffectConfetti": "Έκρηξη κομφετί", @@ -4108,7 +4552,6 @@ "advancedEffectFancyKaraokeGlow": "Λάμψη ενεργής λέξης", "advancedEffectFancyKaraokeActiveColor": "Ενεργό χρώμα", "advancedEffectFancyKaraokeInactiveColor": "Ανενεργό χρώμα", - "advancedEffectFancyKaraokeInactiveOpacity": "Αδιαφάνεια ανενεργών (0-255)", "advancedEffectWordSpacingPixels": "Διάστημα (pixel)", "advancedEffectSlowZoomOut": "Αργή σμίκρυνση", "advancedEffectSlowZoomOutDescription": "Το κείμενο ξεκινά ελαφρώς μεγαλύτερο και συρρικνώνεται απαλά στο κανονικό μέγεθος κατά τη διάρκεια του υπότιτλου", @@ -4124,7 +4567,6 @@ "advancedEffectSlideInLeftDescription": "Το κείμενο ολισθαίνει από εκτός οθόνης αριστερά, παραμένει και μετά εξέρχεται πίσω προς τα αριστερά", "advancedEffectSlideInRight": "Ολίσθηση από δεξιά", "advancedEffectSlideInRightDescription": "Το κείμενο ολισθαίνει από εκτός οθόνης δεξιά, παραμένει και μετά εξέρχεται πίσω προς τα δεξιά", - "advancedEffectFadeInOut": "Ξεθώριασμα εισόδου/εξόδου", "advancedEffectFadeInOutDescription": "Το κείμενο εμφανίζεται βαθμιαία στην αρχή και εξαφανίζεται βαθμιαία στο τέλος κάθε υπότιτλου", "advancedEffectWordFlip3D": "Αναστροφή λέξης 3D", "advancedEffectWordFlip3DDescription": "Κάθε λέξη διπλώνει με αναστροφή 3D και ελαστικό τίναγμα καθώς εκφωνείται — σύγχρονο στυλ λεζάντας 'Shorts'", diff --git a/src/ui/Assets/Languages/Hebrew.json b/src/ui/Assets/Languages/Hebrew.json index 34c267138a3..3a4390536ed 100644 --- a/src/ui/Assets/Languages/Hebrew.json +++ b/src/ui/Assets/Languages/Hebrew.json @@ -1,6 +1,6 @@ { "title": "עברית", - "version": "v5.2.0-rc7", + "version": "v5.3.0", "translatedBy": "Antigravity & cdtauman", "cultureName": "he-IL", "general": { @@ -71,6 +71,24 @@ "abort": "הפסק", "actor": "שחקן", "actors": "שחקנים", + "setActorDotDotDot": "קבע שחקן...", + "actorPickerTitle": "קבע שחקן", + "actorPickerFilterHint": "הקלד כדי לסנן, או הקלד שם של שחקן חדש", + "actorPickerKeysHint": "הקש על מספר או על Enter כדי לקבוע את השחקן. Alt+למעלה/למטה משנה את הסדר, Delete מסיר את השחקן.", + "actorPickerNewActorX": "שחקן חדש \"{0}\"", + "actorPickerLinesSelectedX": "שורות שנבחרו: {0}", + "setStyleDotDotDot": "קבע סגנון...", + "stylePickerTitle": "קבע סגנון", + "stylePickerFilterHint": "הקלד כדי לסנן, או הקלד שם של סגנון חדש", + "stylePickerKeysHint": "הקש על מספר (1-9, 0) כדי לקבוע את הסגנון לכל השורות שנבחרו.", + "stylePickerNewStyleX": "סגנון חדש \"{0}\"", + "stylePickerNewStyleInfo": "הקש Enter כדי להוסיף סגנון זה עם הגדרות ברירת מחדל ולקבוע אותו לשורות שנבחרו. השתמש ב-\"נהל סגנונות...\" כדי לשנות מאוחר יותר את הגופן, הצבעים והמיקום שלו.", + "stylePickerCurrentStyleX": "סגנון נוכחי: {0}", + "stylePickerLinesUsingStyleX": "שורות המשתמשות בסגנון זה: {0}", + "stylePickerMarginsXYZ": "שמאל {0}, ימין {1}, אנכי {2}", + "stylePickerSetStyle": "_קבע סגנון", + "stylePickerManageStyles": "נהל סגנונות...", + "stylePickerManageStylesHint": "סגור חלון זה ופתח את מנהל הסגנונות כדי לערוך גופנים, צבעים, מיקום ועוד", "add": "הוסף", "addDotDotDot": "הוסף...", "addToNamesListCaseSensitive": "הוסף לרשימת השמות (רגיש לאותיות רישיות)", @@ -79,7 +97,6 @@ "adjustAlpha": "התאמת אלפא (שקיפות)", "adjustDisplayDuration": "התאמת משך תצוגה", "adjustment": "התאמה", - "advanced": "מתקדם", "advancedSettings": "הגדרות מתקדמות", "after": "אחרי", "alignment": "יישור", @@ -115,7 +132,6 @@ "batchMode": "מצב אצווה", "bdnXml": "BDN/xml", "before": "לפני", - "beforeX": "לפני \"{0}\"", "beginning": "התחלה", "bluRaySup": "Blu-ray (sup)", "bold": "מודגש", @@ -127,7 +143,6 @@ "bookmarkEdit": "ערוך סימנייה", "bookmarkEditForSelectedLinesX": "ערוך סימנייה עבור {0} שורות שנבחרו", "bookmarksList": "רשימת סימניות", - "borderColor": "צבע גבול", "borderStyle": "סגנון גבול", "bottomCenter": "למטה-מרכז", "bottomLeft": "למטה-שמאל", @@ -150,7 +165,6 @@ "changeCasing": "שנה סוג אותיות", "changeFormatting": "שנה עיצוב", "changeFrameRate": "שנה קצב פריימים", - "changeLanguageFilter": "שנה מסנן שפה...", "changeOnce": "שנה פעם אחת", "changeSpeed": "שנה מהירות", "character": "תו", @@ -172,7 +186,6 @@ "conversionCancelledByUser": "ההמרה בוטלה על ידי המשתמש", "convert": "המר", "converted": "הומר", - "convertingDotDotDot": "ממיר...", "convertingXofYDotDoDot": "ממיר {0:#,###,##0}/{1:#,###,##0}...", "copy": "העתק", "copyImageToClipboard": "העתק תמונה ללוח גזירים", @@ -180,7 +193,6 @@ "couldNotSaveFileXErrorY": "לא ניתן לשמור את הקובץ \"{0}\". שגיאה: {1}", "count": "ספירה", "cps": "תווים לשנייה", - "currentSubtitle": "כתובית נוכחית", "currentVideoPosition": "מיקום וידאו נוכחי", "cut": "גזור", "dark": "כהה", @@ -225,7 +237,6 @@ "expand": "הרחב", "export": "ייצוא", "exportDotDotDot": "ייצוא...", - "exportToX": "ייצוא אל {0}", "extendAfter": "הארך/קצר לשורה שאחרי", "extendBefore": "הארך/קצר לשורה שלפני", "extendSelectedLinesToNextShotChangeOrNextSubtitle": "הארך שורות שנבחרו לשינוי השוט הבא (או לכתובית הבאה)", @@ -241,7 +252,6 @@ "fileExtension": "סיומת קובץ", "fileName": "שם קובץ", "fileNameX": "שם קובץ: {0}", - "fileNameXAndSize": "שם קובץ: {0} ({1})", "fileSaved": "הקובץ נשמר", "fileSavedToX": "הקובץ נשמר ב-{0}", "fileXAlreadyExists": "הקובץ \"{0}\" כבר קיים.", @@ -251,8 +261,6 @@ "find": "חפש", "findNext": "חפש את הבא", "findTextX": "חפש טקסט - {0}", - "fiveHundredMilliseconds": "500 מילישניות", - "fiveSeconds": "חמש שניות", "fix": "תיקון", "fixCommonErrors": "תיקון שגיאות נפוצות", "fixRightToLeft": "תיקון מימין לשמאל", @@ -276,14 +284,12 @@ "foundXInLineYZ": "נמצא \"{0}\" בשורה {1}, מיקום {2}", "foundXMatches": "נמצאו {0} התאמות", "frameRate": "קצב פריימים", - "frameRateX": "קצב פריימים: {0:0.0###}", "frames": "פריימים", "from": "מ-", "fromCurrentVideoPosition": "ממיקום הוידאו הנוכחי", "fullScreen": "מסך מלא", "gap": "מרווח", "general": "כללי", - "generalText": "כללי", "generate": "צור", "generateImportShotChanges": "ייצור/ייבוא מעברי שוט", "generating": "יוצר...", @@ -300,12 +306,8 @@ "help": "עזרה", "hex": "Hex", "hide": "הסתר", - "hidePreview": "הסתר תצוגה מקדימה", "history": "היסטוריה", "horizontal": "אופקי", - "hourMinutesSecondsDecimalSeparatorMilliseconds": "שעה:דקה:שנייה{0}מילישנייה", - "hourMinutesSecondsFrames": "שעה:דקה:שנייה:פריימים", - "ip": "IP", "id": "מזהה", "image": "תמונה", "imageSaved": "התמונה נשמרה", @@ -364,6 +366,7 @@ "mergeSelectedLinesDialog": "מזג שורות שנבחרו כדו-שיח", "mergeWithLineAfterAndAutoBreak": "מזג עם השורה שאחרי ופצל אוטומטית", "mergeWithLineAfterKeepBreaks": "מזג עם השורה שאחרי (שמור על מעברי שורה)", + "mergeWithLineAfterAndUnbreak": "מזג עם השורה הבאה ובטל מעברי שורה", "mergeWithLineBeforeAndAutoBreak": "מזג עם השורה שלפני ופצל אוטומטית", "mergeWithLineBeforeKeepBreaks": "מזג עם השורה שלפני (שמור על מעברי שורה)", "middleCenter": "מרכז-מרכז", @@ -378,11 +381,11 @@ "moveAllShotChangeOneFrameForward": "הזז את כל שינויי השוט פריים אחד קדימה", "multiMode": "מצב מרובה", "moveDown": "הזז למטה", + "increase": "הגדל", + "decrease": "הקטן", "moveUp": "הזז למעלה", "multipleReplace": "החלפה מרובה", - "mute": "השתק", "name": "שם", - "negative": "שלילי", "new": "חדש", "newDotDotDot": "חדש...", "newProfile": "פרופיל חדש", @@ -410,20 +413,15 @@ "offsetY": "היסט Y", "ok": "אי_שור", "oneFile": "קובץ אחד", - "oneHundredMilliseconds": "100 מילישניות", - "oneSecond": "שנייה אחת", "oneSecondBack": "שנייה אחת אחורה", "oneSecondForward": "שנייה אחת קדימה", - "onlineVideoFeatureNotAvailable": "התכונה אינה זמינה עבור וידאו מקוון", "openContainingFolder": "פתח תיקייה מכילה", "openDictionaryFolder": "פתח תיקיית מילונים", "openFile": "פתח קובץ", "openImageBasedSubtitle": "פתח כתוביות מבוססות תמונה", "openImageFile": "פתח קובץ תמונה", - "openOriginalSubtitleFile": "פתח קובץ כתוביות מקורי...", "openOriginalSubtitleFileTitle": "פתח קובץ כתוביות מקורי", "openOutputFolder": "פתח תיקיית פלט", - "openSubtitle": "פתח כתוביות...", "openSubtitleFileTitle": "פתח קובץ כתוביות", "openSubtitles": "פתח כתוביות...", "openVideoFile": "פתח קובץ וידאו...", @@ -436,11 +434,6 @@ "outlineWidth": "רוחב קו מתאר", "outputFolder": "תיקיית פלט", "outputProperties": "מאפייני פלט", - "overlap": "חפיפה", - "overlapNextX": "חפיפה עם הבא ({0:#;##0.###})", - "overlapPreviousLineX": "חפיפה עם שורה קודמת ({0:#;##0.###})", - "overlapStartAndEnd": "חפיפת התחלה וסיום", - "overlapX": "חפיפה ({0:#;##0.###})", "overwriteExistingFiles": "דרוס קבצים קיימים", "overwriteFilesInFolderX": "דרוס קבצים בתיקייה: \"{0}\"", "overwriteQuestion": "לדרוס?", @@ -453,6 +446,7 @@ "percent": "אחוז", "pickLayer": "בחר שכבה", "pickOllamaModel": "בחר מודל Ollama", + "pickModel": "בחר מודל", "llamaCppUseRemoteServer": "השתמש בשרת חיצוני (URL)", "pickOutputFolder": "בחר תיקיית פלט", "pickResolutionFromCurrentVideo": "בחר רזולוציה מהוידאו הנוכחי", @@ -496,7 +490,6 @@ "refresh": "רענן", "region": "אזור", "regularExpression": "ביטוי רגולרי", - "regularExpressionIsNotValid": "הביטוי הרגולרי אינו חוקי!", "relativeToCurrentVideoPosition": "יחסית למיקום הוידאו הנוכחי", "remove": "הסר", "removeAlignment": "הסר יישור", @@ -521,7 +514,7 @@ "reset": "איפוס", "resizePanels": "שנה גודל חלוניות", "resolution": "רזולוציה", - "resolutionX": "רזולוציה: {0}", + "retry": "נסה שוב", "reverseRightToLeftStartEnd": "הפוך התחלה/סיום מימין לשמאל", "right": "ימין", "rightToLeft": "מימין לשמאל", @@ -559,8 +552,6 @@ "selectedlinesX": "שורות שנבחרו: {0}", "sensitivity": "רגישות", "separator": "מפריד", - "sessionKey": "מפתח הפעלה", - "sessionKeyGenerate": "צור מפתח חדש", "setEnd": "קבע סיום", "setEndAndGoToNext": "קבע סיום ועבור לבא", "setEndAddNewAndGoToNew": "קבע סיום, הוסף כתובית חדשה ועבור אליה", @@ -579,8 +570,9 @@ "shadowColor": "צבע צל", "shadowWidth": "רוחב צל", "shiftLineBy": "הסט שורה ב-", + "shotIn": "כניסת שוט", + "shotOut": "יציאת שוט", "shortcut": "קיצור דרך", - "shortcutX": "קיצור דרך: {0}", "shortcuts": "קיצורי דרך", "show": "הצג", "showActorColumn": "הצג עמודת \"שחקן\"", @@ -591,17 +583,19 @@ "showStartColumn": "הצג עמודת \"התחלה\"", "showHideColumn": "הצג עמודת \"הסתר\"", "showHistory": "הצג היסטוריה", + "clearHistory": "נקה היסטוריה", + "removeFromHistory": "הסר מההיסטוריה", "showLayerColumn": "הצג עמודת \"שכבה\"", "showPreview": "הצג תצוגה מקדימה", "showShotChangesList": "הצג רשימת מעברי שוט", + "showShotInColumn": "הצג עמודת \"כניסת שוט\"", + "showShotOutColumn": "הצג עמודת \"יציאת שוט\"", "showStyleColumn": "הצג עמודת \"סגנון\"", "showTeletext": "הצג עמודת טלטקסט בתצוגת רשימה", "showTimeCodes": "הצג קודי זמן", "showWpmColumn": "הצג עמודת \"מילים לדקה\"", "showPixelWidthColumn": "הצג עמודת \"רוחב פיקסלים\"", "shrink": "כווץ", - "singleBox": "תיבה בודדת", - "singleLineLengths": "אורך שורה בודדת:", "singleMode": "מצב יחיד", "textTotalLength": "טקסט - אורך כולל", "textSingleLineMaxLength": "טקסט - אורך מקסימלי לשורה בודדת", @@ -648,16 +642,16 @@ "strikeout": "קו חוצה", "style": "סגנון", "styleExaggeration": "הגזמת סגנון", - "styleLanguage": "סגנון / שפה", "styles": "סגנונות", "subtitleFile": "קובץ כתוביות", + "subtitleFileChangedOutsideX": "הקובץ \"{0}\" שונה מחוץ ל-Subtitle Edit.\n\nלטעון אותו מחדש ולאבד את השינויים שלך?", "subtitleFileName": "שם קובץ כתוביות", "subtitleFileSaved": "קובץ הכתוביות נשמר", "subtitleFileSavedToX": "קובץ הכתוביות נשמר ב-{0}", "subtitleFiles": "קובצי כתוביות", "subtitleFormats": "תבניות כתוביות", "subtitleLoadedX": "נטענה כתובית: {0}", - "subtitleXOfY": "כתובית {0} מתוך {1}", + "subtitleReloadedChangedOutsideX": "הכתובית נטענה מחדש (שונתה מחוץ ל-Subtitle Edit): {0}", "suffix": "סיומת", "suggestions": "הצעות", "sync": "סנכרון", @@ -669,7 +663,6 @@ "teletextColor": "צבע טלטקסט", "teletextLine": "שורת טלטקסט", "tenHours": "10 שעות", - "tenMilliseconds": "10 מילישניות", "text": "טקסט", "textColor": "צבע טקסט", "textFiles": "קובצי טקסט", @@ -695,9 +688,6 @@ "topLeft": "למעלה-שמאל", "topRight": "למעלה-ימין", "totalAdjustmentX": "התאמה כוללת: {0}", - "totalFramesX": "סה\"כ פריימים: {0:#;##0.##}", - "totalLengthX": "אורך כולל: {0}", - "totalLengthXSplitLine": "אורך כולל: {0} (פצל שורה!)", "translate": "תרגם", "translatePercentX": "מתרגם: {0}%", "translateRow": "תרגם שורה", @@ -706,6 +696,7 @@ "type": "סוג", "unbreak": "בטל מעברי שורה", "unbreakLines": "בטל מעברי שורות", + "undelete": "בטל מחיקה", "underline": "קו תחתון", "undo": "בטל", "unknown": "לא ידוע", @@ -725,13 +716,11 @@ "usages": "שימושים", "use": "השתמש", "useAlways": "השתמש תמיד", - "useLargerFontForThisWindow": "השתמש בגופן גדול יותר עבור חלון זה", "useOnce": "השתמש פעם אחת", "useOutputFolder": "השתמש בתיקיית פלט", "useSourceFolder": "השתמש בתיקיית מקור", "useSourceResolution": "השתמש ברזולוציית מקור", "userName": "שם משתמש", - "userNameAlreadyInUse": "שם המשתמש כבר נמצא בשימוש", "vertical": "אנכי", "video100MsBack": "וידאו, 100 מילישניות אחורה", "video100MsForward": "וידאו, 100 מילישניות קדימה", @@ -745,14 +734,25 @@ "videoCustom3ForwardX": "וידאו, מילישניות מותאמות אישית ({0:#,###,##0}) קדימה, 3", "videoCustom4BackX": "וידאו, מילישניות מותאמות אישית ({0:#,###,##0}) אחורה, 4", "videoCustom4ForwardX": "וידאו, מילישניות מותאמות אישית ({0:#,###,##0}) קדימה, 4", - "videoEncodingX": "קידוד וידאו: {0}", + "videoCustom1BackAndPauseX": "וידאו, אלפיות שנייה מותאמות אישית ({0:#,###,##0}) אחורה והשהה, 1", + "videoCustom1ForwardAndPauseX": "וידאו, אלפיות שנייה מותאמות אישית ({0:#,###,##0}) קדימה והשהה, 1", + "videoCustom2BackAndPauseX": "וידאו, אלפיות שנייה מותאמות אישית ({0:#,###,##0}) אחורה והשהה, 2", + "videoCustom2ForwardAndPauseX": "וידאו, אלפיות שנייה מותאמות אישית ({0:#,###,##0}) קדימה והשהה, 2", + "videoCustom3BackAndPauseX": "וידאו, אלפיות שנייה מותאמות אישית ({0:#,###,##0}) אחורה והשהה, 3", + "videoCustom3ForwardAndPauseX": "וידאו, אלפיות שנייה מותאמות אישית ({0:#,###,##0}) קדימה והשהה, 3", + "videoCustom4BackAndPauseX": "וידאו, אלפיות שנייה מותאמות אישית ({0:#,###,##0}) אחורה והשהה, 4", + "videoCustom4ForwardAndPauseX": "וידאו, אלפיות שנייה מותאמות אישית ({0:#,###,##0}) קדימה והשהה, 4", "videoExtension": "סיומת קובץ וידאו", "videoFile": "קובץ וידאו", "videoFileGenerated": "קובץ הוידאו נוצר", "videoFileGeneratedX": "קובץ וידאו נוצר: \"{0}\"", + "videoFilesGenerated": "קובצי וידאו נוצרו", + "xVideosGenerated": "{0} סרטוני וידאו נוצרו", + "xOfYVideosGenerated": "{0} מתוך {1} סרטוני וידאו נוצרו", + "savedInX": "נשמר ב-{0}", + "savedInXFolders": "נשמר ב-{0} תיקיות", "videoFiles": "קובצי וידאו", "videoAndAudioFiles": "קובצי וידאו ושמע", - "videoInformation": "מידע על הוידאו", "videoOffset": "היסט וידאו", "videoOneFrameBack": "וידאו, פריים אחד אחורה", "videoOneFrameForward": "וידאו, פריים אחד קדימה", @@ -763,7 +763,6 @@ "videoPlayer": "נגן וידאו", "videoPosition": "מיקום וידאו", "videoResolution": "רזולוציית וידאו", - "viewX": "הצג את {0}", "visible": "גלוי", "voice": "קול", "volume": "עוצמת שמע", @@ -772,9 +771,7 @@ "waveformCenterOnVideoPosition": "מרכז את גל השמע במיקום הוידאו", "waveformPasteFromClipboard": "הדבק מלוח הגזירים", "waveformSpectrogram": "גל שמע/ספקטרוגרמה", - "webServiceUrl": "כתובת URL של שירות רשת", "width": "רוחב", - "windowPositionAndSize": "מיקום וגודל החלון", "wordsPerMin": "מילים לדקה", "pixelWidth": "רוחב פיקסלים", "wpm": "מילים לדקה", @@ -784,8 +781,6 @@ "xNotFound": "\"{0}\" לא נמצא", "xRequiresAnApiKey": "{0} דורש מפתח API", "xRequiresAValidUrl": "{0} דורש כתובת URL חוקית (למשל \"http://...\" או \"https://...\")", - "xSeconds": "{0:0.0##} שניות", - "xSubtitles": "{0:#,###,##0} כתוביות", "yes": "כן", "zeroBytes": "0 בתים", "copyToClipboard": "העתק ללוח הגזירים", @@ -807,10 +802,8 @@ "playbackSpeed": "מהירות ניגון", "hoursMinutesSecondsMilliseconds": "HH:MM:SS.FFF", "imageFiles": "קובצי תמונה", - "openAiCompatibleStt": "STT תואם OpenAI", "openAiCompatibleSttEndpoint": "כתובת קצה של שירות STT", "openAiCompatibleSttApiKey": "מפתח API", - "openAiCompatibleSttModel": "מודל", "openAiCompatibleSttExtraHeaders": "כותרות נוספות", "openAiCompatibleSttTimeout": "זמן קצוב בשניות (Timeout)", "openAiCompatibleSttLanguage": "רמז שפה", @@ -820,16 +813,17 @@ "dashScopeSttRegion": "אזור", "dashScopeSttEnableWords": "חותמות זמן ברמת המילה", "dashScopeSttRegionKeyHint": "שים לב: מפתחות API של Alibaba Cloud Model Studio הם תלויי אזור - ודא שהאזור שנבחר תואם לאזור שבו נוצר מפתח ה-API (סין לעומת בינלאומי).", + "googleCloudSttProjectId": "מזהה פרויקט (רק עבור Application Default Credentials)", + "googleCloudSttBucketName": "דלי אחסון (ריק = אוטומטי)", + "googleCloudSttDynamicBatching": "אצווה דינמית (זול פי 5 בערך, ללא הבטחת זמן תגובה)", "onlineSttApiKeyMissing": "נדרש מפתח API. נא להזין את מפתח ה-API ולנסות שוב.", "openAiCompatibleSttAutoTranscribeOnAudioSelection": "תמלל אוטומטית בחירת גל שמע חדשה באמצעות דיבור לטקסט", "configurationRequired": "נדרשת תצורה", "transcriptionError": "שגיאת תמלול", "transcriptionComplete": "התמלול הושלם", - "transcribingWithOpenAi": "מתמלל באמצעות שירות OpenAI...", "processingResponse": "מעבד תגובה...", "unauthorizedApiKey": "לא מורשה: מפתח API לא חוקי או הרשאות לא מספיקות.", "requestTimeout": "פג זמן הבקשה: השרת לקח זמן רב מדי כדי להגיב.", - "noSegmentsReturned": "לא הוחזרו מקטעים משירות התמלול.", "openAiCompatibleSttUrlMissing": "כתובת קצה של שירות STT תואם OpenAI נדרשת. נא להזין כתובת URL ולנסות שוב.", "openAiCompatibleSttUrlNotResponding": "כתובת הקצה של שירות ה-STT התואם ל-OpenAI לא הגיבה:\n{0}\n\nנא לוודא שכתובת ה-URL נכונה ושהשרת פעיל.", "transcriptionFailed": "התמלול נכשל", @@ -898,10 +892,10 @@ "multipleReplace": "החלפה _מרובה...", "fillSelectedLinesWithClipboard": "מלא שורות שנבחרו בטקסט מלוח הגזירים", "goToLineNumber": "_עבור לשורה מספר...", + "commandPalette": "_לוח פקודות...", "rightToLeftMode": "מצב מ_ימין לשמאל", "modifySelectionDotDotDot": "שנה ב_חירה...", "tools": "_כלים", - "toolsSelectedLines": "כ_לים (שורות שנבחרו)", "adjustDurations": "_התאם משך...", "applyDurationLimits": "החל מגבלות מ_שך...", "batchConvert": "המרת א_צווה...", @@ -990,6 +984,7 @@ "toggleSelectSubtitleWhilePlayingCurrentlyOn": "הפעלת/השבתת בחירת כתובית בזמן ניגון (כרגע: פעיל)", "toggleSelectSubtitleWhilePlayingCurrentlyOff": "הפעלת/השבתת בחירת כתובית בזמן ניגון (כרגע: כבוי)", "beautifyTimeCodes": "ייפוי קודי זמן...", + "improveTimeCodes": "שפר קודי זמן (יישור כפוי)...", "evenlyDistributeLines": "פזר שורות באופן שווה (לפי תווים לשנייה)", "mergeContinuationLines": "מזג שורות המשך...", "snapAllTimesToFrames": "הצמד את כל הזמנים לפריימים", @@ -1044,6 +1039,9 @@ "selectCurrentLineWhilePlayingHint": "בחר כתובית נוכחית בזמן ניגון {0}", "videoPosition": "מיקום וידאו {0}", "videoPositionTextBox": "תיבת טקסט של מיקום הוידאו {0}", + "toolbarLineBreakX": "מעבר שורה {0} (שאר סרגל הכלים ממשיך בשורה חדשה)", + "initialText": "הטקסט ההתחלתי של השורה שנבחרה", + "initialTextHint": "הטקסט של השורה שנבחרה כפי שהיה בעת בחירת השורה", "hideWaveformToolbar": "הסתר סרגל כלים {0}", "resetZoomAndSpeed": "אפס זום ומהירות ניגון {0}", "removeBlankLines": "הסר שורות ריקות {0}", @@ -1057,6 +1055,15 @@ "textPlayHint": "נגן את הכתובית הנוכחית ועצור בסופה {0}", "textPauseHint": "השהה ניגון {0}", "textNextHint": "נגן את הכתובית הבאה ועצור בסופה {0}", + "moveSelectedLines": "הזז שורות שנבחרו (לחצני אחורה/קדימה)", + "moveSelectedLinesAndFollowing": "הזז שורות שנבחרו וכל השורות שאחריהן (לחצני אחורה/קדימה)", + "moveAllLines": "הזז את כל השורות (לחצני אחורה/קדימה)", + "moveSelectedLinesBackHint": "הזז שורות שנבחרו {0} ms אחורה {1}", + "moveSelectedLinesForwardHint": "הזז שורות שנבחרו {0} ms קדימה {1}", + "moveSelectedLinesAndFollowingBackHint": "הזז שורות שנבחרו וכל השורות שאחריהן {0} ms אחורה {1}", + "moveSelectedLinesAndFollowingForwardHint": "הזז שורות שנבחרו וכל השורות שאחריהן {0} ms קדימה {1}", + "moveAllLinesBackHint": "הזז את כל השורות {0} ms אחורה {1}", + "moveAllLinesForwardHint": "הזז את כל השורות {0} ms קדימה {1}", "guessStartNoLineSelected": "ניחוש התחלה: לא נבחרה שורה", "guessStartTimeCodesLocked": "ניחוש התחלה: קודי הזמן נעולים", "guessStartNoWaveform": "ניחוש התחלה: אין גל שמע", @@ -1078,6 +1085,9 @@ "charactersPerSecond": "תווים לשנייה: {0}", "chooseColumn": "בחר עמודה", "columnPaste": "הדבקת עמודה", + "commandPaletteTitle": "לוח פקודות", + "commandPaletteSearchPlaceholder": "הקלד כדי לחפש פקודות...", + "commandPaletteNoCommandsFound": "אין פקודות תואמות", "createdEmptyTranslation": "נוצר תרגום ריק מהכתובית הנוכחית", "deleteText": "מחק טקסט", "deleteTextAndShiftCellsUp": "מחק טקסט והסט תאים למעלה", @@ -1115,18 +1125,20 @@ "noTextInClipboard": "אין טקסט בלוח הגזירים", "oneLineCopiedFromOriginal": "שורה אחת הועתקה מהכתוביות המקוריות", "oneLineCopiedToOriginal": "שורה אחת הועתקה לכתובית המקורית", + "onlineOnlyVideo": "וידאו מקוון בלבד", + "onlineOnlyVideoXSizeYDownloadAndOpen": "קובץ הווידאו \"{0}\" ({1}) מאוחסן באופן מקוון בלבד, למשל ב-Dropbox או ב-iCloud Drive, ויש להוריד אותו לפני שניתן לפתוח אותו.\n\nלהוריד ולפתוח אותו כעת?", "oneLineMerged": "שורה אחת מוזגה", "oneLineSwitched": "שורה אחת הוחלפה", "overwriteExistingCells": "דרוס תאים קיימים", "overwriteOrShiftCellsDown": "דרוס/הסט תאים למטה", "parsingMatroskaFile": "מנתח קובץ Matroska...", + "readingDvdSubtitles": "קורא כתוביות DVD...", "pasteFromClipboardDotDotDot": "הדבק מלוח הגזירים...", "redoPerformed": "בוצעה פעולה חוזרת (Redo)", "redoPerformedXActionLeft": "בוצעה פעולה חוזרת (פעולות שנותרו: {0})", "removedUnicodeControlCharactersX": "הוסרו תווי בקרה של יוניקוד מ-{0} שורות", "removedXBlankLines": "הוסרו {0} שורות ריקות", "replacedXWithYCountZ": "הוחלף \"{0}\" ב-\"{1}\" ({2} מופעים)", - "replacedXWithYInLineZ": "הוחלף \"{0}\" ב-\"{1}\" בשורה {2}", "reversedStartAndEndingsForRightToLeftX": "הופכת התחלה וסיום עבור מימין לשמאל ב-{0} שורות", "ruleProfileIsX": "פרופיל הכללים כעת הוא \"{0}\"", "saveLanguageFile": "שמור קובץ שפה", @@ -1134,7 +1146,6 @@ "shiftTextCellsDown": "הסט תאי טקסט למטה", "singleLineLength": "אורך שורה: ", "speedIsNowX": "המהירות כעת היא \"{0}\"", - "spellCheckResult": "בדיקת האיות הושלמה. \n\n• מילים ששונו: {0}\n• מילים שדולגו: {1}", "subtitleGridFormattingX": "עיצוב טבלה: {0}", "subtitleImportedFromMatroskaFile": "כתובית יובאה מקובץ Matroska", "textDown": "טקסט למטה", @@ -1161,13 +1172,14 @@ "trimmedXLines": "נחתכו {0} שורות כתוביות", "pastedXLinesOverSelectedLines": "{0} שורות הודבקו על השורות שנבחרו", "pastedXOfYLinesOverSelectedLines": "{0} מתוך {1} שורות הודבקו על השורות שנבחרו - השאר לא נכנסו לבחירה", + "insertedXLinesFromClipboard": "הוכנסו {0} שורות מלוח הגזירים", "openOriginalDifferentNumberOfSubtitlesXY": "קובץ הכתוביות המקורי אינו מכיל את אותו מספר כתוביות כמו קובץ הכתוביות הנוכחי.\n\n• כתוביות מקוריות: {0}\n• כתוביות נוכחיות: {1}", - "importXMatchingOriginalLines": "האם לייבא {0} כתוביות מקוריות תואמות?", "originalTextReadOnly": "טקסט מקורי (קריאה בלבד)", "originalTextEditMode": "טקסט מקורי (מצב עריכה)", "originalIsReadOnlyNotSaved": "הכתובית המקורית היא מקור לקריאה בלבד ולא נשמרה", "originalIsEmptyNotSaved": "לכתובית המקורית אין טקסט כלל והיא לא נשמרה", "originalIsReadOnlyReference": "הכתובית המקורית פתוחה כמקור לקריאה בלבד", + "closeTranslationQuestionXY": "לסגור את התרגום \"{0}\"?\n\nהכתובית המקורית \"{1}\" תהפוך לכתובית שאתה עורך. קובץ התרגום עצמו אינו משתנה.", "allowEditOfOriginalSubtitle": "אפשר עריכה של הכתובית המקורית", "showAllOriginalLinesX": "הצג את כל השורות המקוריות (ל-{0} אין התאמה כאן)", "showAllOriginalLinesHint": "השורות ללא התאמה מוצגות כשורות נוספות, כך שניתן לראות מה חסר בתרגום.", @@ -1189,6 +1201,7 @@ "fileExportedInFormatXToY": "הקובץ יוצא בפורמט {0} אל {1}", "fileExportedInFormatXToFileY": "הקובץ יוצא בפורמט \"{0}\" לקובץ \"{1}\"", "fixedXLines": "תוקנו {0} שורות", + "toolXChangedYLines": "{0}: {1} שורות שונו", "transcriptionCompletedWithXLines": "התמלול הושלם עם {0} שורות", "openSubtitleFileX": "לפתוח את קובץ הכתוביות \"{0}\"?", "replacedXOccurrences": "הוחלפו {0} מופעים", @@ -1234,13 +1247,9 @@ "file": { "ebuSaveOptions": { "title": "אפשרויות שמירה של EBU", - "generalSubtitleInformation": "מידע כתוביות כללי", "codePageNumber": "מספר דף קוד", "diskFormatCode": "קוד פורמט דיסק", "displayStandardCode": "קוד תקן תצוגה", - "colorRequiresTeletext": "צבעים דורשים טלטקסט!", - "alignmentRequiresTeletext": "יישור דורש טלטקסט!", - "teletextCharsShouldBe38": "'מספר תווים מקסימלי לשורה' עבור טלטקסט צריך להיות 38!", "characterCodeTable": "טבלת תווים", "languageCode": "קוד שפה", "originalProgramTitle": "שם תוכנית מקורי", @@ -1257,7 +1266,6 @@ "maxNumberOfDisplayableRows": "מקסימום שורות", "diskSequenceNumber": "מספר סידורי של הדיסק", "totalNumberOfDisks": "סה\"כ מספר דיסקים", - "import": "ייבוא...", "textAndTimingInformation": "מידע טקסט ותזמון", "justificationCode": "קוד יישור", "verticalPosition": "מיקום אנכי", @@ -1274,11 +1282,6 @@ "errors": "שגיאות", "errorsX": "שגיאות: {0}", "maxLengthError": "שורה {0} חורגת מהאורך המקסימלי ({1}) ב-{2}: {3}", - "textUnchangedPresentation": "תצוגה ללא שינוי", - "textLeftJustifiedText": "טקסט מיושר לשמאל", - "textCenteredText": "טקסט ממורכז", - "textRightJustifiedText": "טקסט מיושר לימין", - "useBoxForOneNewLine": "סמן 'השתמש בתיבה סביב הטקסט' עבור שורה חדשה אחת בלבד", "maxLengthErrorTeletextColored": "שורה {0}-{1}: 36 (ולא {2}) הוא מספר התווים המקסימלי לטקסט צבעוני בגובה כפול", "maxLengthErrorTeletextWhite": "שורה {0}-{1}: 37 (ולא {2}) הוא מספר התווים המקסימלי לטקסט לבן בגובה כפול", "maxLengthErrorTeletextSingleHeight": "שורה {0}: 38 (ולא {1}) הוא מספר התווים המקסימלי לטקסט לבן בגובה יחיד", @@ -1303,22 +1306,29 @@ "formattingDotDotDot": "עיצוב...", "imageBasedSubtitleForEditDotDotDot": "כתוביות מבוססות תמונה לעריכה...", "imageBasedSubtitleForOcrDotDotDot": "כתוביות מבוססות תמונה ל-OCR...", + "dvdSubtitlesDotDotDot": "כתוביות מ-DVD (IFO/VOB)...", + "titleImportDvd": "ייבוא כתוביות מ-DVD", + "dvdIfoFile": "קובץ IFO", + "dvdVobFiles": "קובצי VOB", + "dvdVideoStandard": "תקן וידאו", + "dvdStartRipping": "התחל חילוץ", + "dvdRippingAborted": "החילוץ בוטל", + "dvdOpenIfoFile": "פתח קובץ DVD IFO", + "dvdAddVobFiles": "הוסף קובצי VOB", + "dvdIfoFiles": "קובצי DVD IFO", + "dvdVobFileType": "קובצי DVD VOB", + "dvdDropHint": "פתח קובץ IFO (VIDEO_TS\\VTS_01_0.IFO) או הוסף קובצי VOB - או גרור אותם לכאן", + "dvdNotAnIfoFileX": "{0} אינו קובץ DVD IFO", + "dvdMenuIfoNoTitleSetsX": "לא נמצאו ערכות כותרים (VTS_xx_0.IFO) לצד {0}", + "dvdEncryptedXOfY": "{0} מתוך {1} מנות הכתוביות עדיין מוצפנות ב-CSS - קובצי VOB אלה הועתקו ללא פענוח, ולכן חלק מתמונות הכתוביות יהיו משובשות.\n\nפענח תחילה את ה-DVD בעזרת כלי לחילוץ DVD לקבלת תוצאה נקייה.\n\nלהמשיך בכל זאת?", "splitTextAt": "פצל טקסט ב-", "blankLines": "שורות ריקות", "oneLineIsOneSubtitle": "שורה אחת היא כתובית אחת", "twoLinesAreOneSubtitle": "שתי שורות הן כתובית אחת", "importFilesDotDotDot": "ייבוא קבצים...", "multipleFiles": "ייבוא מקובצי טקסט מרובים (קובץ אחד הוא כתובית אחת)", - "importOptions": "אפשרויות ייבוא", - "autoSplitText": "פיצול טקסט אוטומטי", - "lineMode": "מצב שורות", "maxLineLength": "אורך שורה מקסימלי", - "maxLinesPerSubtitle": "מקסימום שורות לכתובית", - "minGapBetweenSubtitles": "מרווח מינימלי בין כתוביות", "mergeShortLines": "מזג שורות קצרות", - "removeLinesWithoutLetters": "הסר שורות ללא אותיות", - "splitAtEndCharsSetting": "פצל בתווי סיום", - "takeTimeFromCurrentFile": "קח זמן מהקובץ הנוכחי", "fixed": "קבוע", "numberOfSubtitlesX": "מספר כתוביות: {0}", "numberOfSubtitlesXForcedY": "מספר כתוביות: {0} ({1} כפויות)", @@ -1330,14 +1340,18 @@ "forcedAlignerProgress": "מיישר... חלון {0} מתוך {1}, {2} מתוך {3} שורות", "forcedAlignerSetupTitle": "סנכרן קודי זמן באמצעות יישור כפוי", "forcedAlignerSetupIntro": "יישור כפוי מתאים את טקסט הכתוביות שכבר יש לך אל השמע, ללא תמלול מוקדם. סרטונים ארוכים מיושרים במקטעים, כך שכל אורך מתאפשר.", + "forcedAlignerEndsFromSpeech": "קבע זמני סיום מדיבור מבודד (איטי)", + "forcedAlignerEndsFromSpeechHint": "יישור כפוי מוצא היכן שורה מתחילה, לא היכן היא מסתיימת, ולכן זמני הסיום נקבעים בדרך כלל לפי זמן הקריאה. כאשר אפשרות זו מופעלת, המוזיקה ואפקטי הקול מוסרים תחילה מהשמע וכל שורה מסתיימת היכן שהדיבור שלה משתתק. לוקח בערך כאורך הווידאו עצמו עם GPU - ופי כמה יותר בלעדיו.", + "forcedAlignerIsolatingSpeech": "מבודד דיבור... {0}", + "forcedAlignerIsolatingSpeechFailed": "לא ניתן היה לבודד את הדיבור - זמני הסיום נקבעו במקום זאת לפי זמן הקריאה.", "forcedAlignerModel": "מודל היישור", - "forcedAlignerDownloadEngine": "הורדה / עדכון מנוע..." + "forcedAlignerDownloadEngine": "הורדה / עדכון מנוע...", + "forcedAlignerEstimatedXOfY": "{0} מתוך {1} שורות לא ניתן היה ליישר והן מוקמו לפי זמן הקריאה - קודי הזמן שלהן משוערים בלבד. התסריט כנראה ארוך יותר ממה שנאמר בווידאו." }, "export": { "exportImagesProfiles": "פרופילי ייצוא תמונות", "leftRightMargin": "שוליים שמאל/ימין", "topBottomMargin": "שוליים עליונים/תחתונים", - "titleExportBluRaySup": "ייצוא Blu-ray (sup)", "lineSpacingPercent": "מרווח שורות %", "paddingLeftRight": "מרווח פנימי שמאל/ימין", "paddingTopBottom": "מרווח פנימי למעלה/למטה", @@ -1378,10 +1392,21 @@ "titleExportDCinemaInteropPng": "D-Cinema interop/png", "titleExportDCinemaSmpte2014Png": "D-Cinema SMPTE 2014/png", "imageBasedSubtitleSaved": "כתוביות מבוססות תמונה נשמרו", - "titleExportBdnXml": "ייצוא BDN/xml", "titleExportWebVttThumbnails": "ייצוא WebVTT (תמונות ממוזערות)", "fullFrameImage": "תמונה בגודל פריים מלא", "fullFrameImageHint": "הפוך כל תמונה לגודל פריים הוידאו, עם הכתובית במקומה", + "stereo3D": "3D", + "stereo3DHint": "עבור וידאו תלת-ממדי: צייר את הכתובית פעם אחת בכל חצי של הפריים, אחד לכל עין", + "halfSideBySide": "חצי זה לצד זה", + "halfTopBottom": "חצי למעלה/למטה", + "depth3D": "עומק", + "depth3DHint": "כמה הכתובית בולטת מחוץ למסך - ערך חיובי מקרב אותה, ערך שלילי מרחיק אותה לאחור", + "plane3D": "3D-Plane", + "plane3DHint": "תן לכל כתובית את העומק ש-Blu-ray תלת-ממדי שומר עבורה, מקובץ 3D-Plane (.ofs) שנוצר על ידי BD3D2MK3D או OFSExtractor. העומק משמש היכן שאין ל-3D-Plane ערך", + "openPlane3DTitle": "פתח קובץ 3D-Plane", + "unableToLoadPlane3DX": "לא ניתן לטעון 3D-Plane: {0}", + "plane3DXDepthYToZ": "{0} (עומק {1} עד {2})", + "plane3DXNoDepth": "{0} (ללא ערכי עומק)", "textEffect": "אפקט טקסט", "textEffectSettingsTitle": "הגדרות אפקט טקסט", "textEffectPreset": "ערכה מוגדרת מראש", @@ -1413,13 +1438,10 @@ "statistics": { "title": "סטטיסטיקה", "titleWithFileName": "סטטיסטיקה - {0}", - "generalStatistics": "סטטיסטיקה כללית", - "mostUsed": "הנפוץ ביותר...", "mostUsedLines": "השורות הנפוצות ביותר", "mostUsedWords": "המילים הנפוצות ביותר", "nothingFound": "לא נמצא דבר", "numberOfLinesX": "מספר שורות כתוביות: {0:#,##0}", - "numberOfFilesX": "מספר קובצי כתוביות: {0:#,##0}", "lengthInFormatXinCharactersY": "מספר תווים בפורמט {0}: {1:#,###,##0}", "numberOfCharactersInTextOnly": "מספר תווים בטקסט בלבד: {0:#,###,##0}", "totalDuration": "משך כולל של כל הכתוביות: {0:#,##0}", @@ -1460,7 +1482,6 @@ "gapMaximum": "מרווח - מקסימום: {0:#,##0} מילישניות", "gapAverage": "מרווח - ממוצע: {0:#,##0.##} מילישניות", "gapExceedingMinimum": "מרווח - מתחת למינימום ({0:#,##0} מילישניות): {1} ({2:0.00}%)", - "export": "ייצוא...", "subtitles": "כתוביות", "words": "מילים", "totalDurationShort": "משך כולל", @@ -1490,7 +1511,22 @@ "restoreAutoBackupFile": "שחזר קובץ גיבוי אוטומטי", "restoreXFromY": "האם ברצונך לשחזר את \"{0}\" מ-{1}?", "deleteAllSubtitleBackups": "האם ברצונך למחוק את כל קובצי הגיבוי של הכתוביות?", - "deleteAll": "מחק הכל" + "deleteAll": "מחק הכל", + "subtitles": "כתוביות", + "settingsBackupInfo": "Settings.json מגובה כאשר Subtitle Edit מופעל, לכל היותר פעם אחת בכל {0} ימים ורק אם ההגדרות השתנו. {1} הגיבויים החדשים ביותר נשמרים.", + "settingsBackupInfoEveryStart": "Settings.json מגובה בכל פעם ש-Subtitle Edit מופעל, אם ההגדרות השתנו. {0} הגיבויים החדשים ביותר נשמרים.", + "settingsBackupOff": "הגיבוי האוטומטי של ההגדרות כבוי (אפשרויות → הגדרות → קובץ).", + "lastSettingsBackupX": "גיבוי אחרון: {0}", + "noSettingsBackupsYet": "אין עדיין גיבויי הגדרות", + "openSettingsBackupFolder": "פתח תיקיית גיבוי הגדרות", + "backUpSettingsNow": "גבה כעת", + "settingsBackedUp": "ההגדרות הנוכחיות גובו.", + "restoreSettings": "שחזר הגדרות", + "restoreSettingsFromX": "האם ברצונך להחליף את ההגדרות הנוכחיות שלך בגיבוי מ-{0}?\n\nההגדרות הנוכחיות שלך יגובו תחילה. חלק מהשינויים ייכנסו לתוקף רק לאחר הפעלה מחדש של Subtitle Edit.", + "settingsRestored": "ההגדרות שוחזרו. הפעל מחדש את Subtitle Edit כדי שכל השינויים ייכנסו לתוקף.", + "settingsRestoreFailed": "לא ניתן לשחזר את ההגדרות: {0}", + "settingsBackupNotValid": "הגיבוי אינו קובץ הגדרות תקין, ולכן ההגדרות הנוכחיות שלך נשמרו", + "deleteAllSettingsBackups": "האם ברצונך למחוק את כל קובצי גיבוי ההגדרות?" }, "propertiesDCinema": { "title": "מאפייני D-Cinema SMPTE", @@ -1504,22 +1540,16 @@ "editRate": "קצב עריכה", "timeCodeRate": "קצב קוד זמן", "startTime": "זמן התחלה", - "font": "גופן", "fontId": "מזהה גופן", "fontUri": "נתיב גופן (URI)", - "generate": "צור", - "fontColor": "צבע גופן", "chooseColor": "בחר צבע", "fontEffect": "אפקט גופן", "effectColor": "צבע אפקט", - "fontSize": "גודל גופן", "topBottomMargin": "שוליים עליונים/תחתונים", "fadeUpTime": "זמן הופעה (Fade up)", "fadeDownTime": "זמן היעלמות (Fade down)", - "frames": "פריימים", "zPosition": "מיקום Z", - "zPositionHelp": "מספרים חיוביים מרחיקים את הטקסט, מספרים שליליים מקרבים אותו, ואם מיקום ה-Z הוא אפס אז זה דו-ממד", - "export": "ייצוא..." + "zPositionHelp": "מספרים חיוביים מרחיקים את הטקסט, מספרים שליליים מקרבים אותו, ואם מיקום ה-Z הוא אפס אז זה דו-ממד" }, "webVtt": { "setStylesForSelectedLinesTitle": "קבע סגנונות WebVTT לשורות שנבחרו", @@ -1552,20 +1582,66 @@ "compareOnlyInOneFile": "רק בקובץ אחד", "compareTextOrTimeDifference": "הבדל טקסט/זמן", "compareNumberDifference": "הבדל מספר", - "ignoreLineBreaks": "התעלם ממעברי שורה", "ignoreWhitespace": "התעלם מרווחים", "ignoreWhitespaceHint": "שורות הנבדלות רק ברווחים, טאבים או מעברי שורה אינן נחשבות שונות", "ignoreFormatting": "התעלם מעיצוב", - "onlyLookForDifferencesInText": "חפש הבדלים בטקסט בלבד", - "cannotCompareWithImageBasedSubtitles": "לא ניתן להשוות עם כתוביות מבוססות תמונה", - "statisticsTitle": "סטטיסטיקה", "ignoreFormattingHint": "שורות הנבדלות רק בתגי עיצוב, כמו או {\\an8}, אינן נחשבות שונות", "showOnlyDifferencesInText": "הבדלים בטקסט בלבד", + "ignoreNumbering": "התעלם ממספור", + "ignoreNumberingHint": "שורות השונות רק במספר שלהן אינן נחשבות שונות", + "ignoreTimeDifferenceMs": "סבילות זמן (ms)", + "ignoreTimeDifferenceMsHint": "זמני התחלה וסיום השונים במספר זה של אלפיות שנייה או פחות אינם נחשבים שונים", + "compareDifferences": "הבדלים", + "compareTextDifferences": "הבדלי טקסט", + "compareCurrent": "נוכחי", + "compareReference": "הפניה", + "compareEditable": "ניתן לעריכה", + "compareEditableHint": "שינויים שנעשים כאן חוזרים לכתובית כאשר לוחצים על החל", + "compareReadOnly": "לקריאה בלבד", + "compareTakeFromReference": "קח טקסט ותזמון מההפניה", + "compareInsertFromReference": "הכנס שורה זו לכתובית הנוכחית", + "compareDeleteFromCurrent": "מחק שורה זו מהכתובית הנוכחית", + "compareTakeText": "קח טקסט", + "compareTakeTiming": "קח תזמון", + "compareEditLine": "ערוך שורה", + "compareEditHint": "Ctrl+Enter לשמירה, Esc לביטול", + "compareEdited": "נערך", + "compareOnePendingChange": "שינוי ממתין אחד", + "compareXPendingChanges": "{0} שינויים ממתינים", + "compareDiscardXChanges": "לבטל את {0} השינויים שנעשו בהשוואה?", + "compareXChangesApplied": "השוואה: {0} שינויים הוחלו", + "compareChangeTextX": "#{0} טקסט מההפניה", + "compareChangeTimingX": "#{0} תזמון מההפניה", + "compareChangeTextAndTimingX": "#{0} טקסט ותזמון מההפניה", + "compareChangeEditedX": "#{0} נערך", + "compareChangeInsertedX": "#{0} הוכנס", + "compareChangeDeletedX": "#{0} נמחק", + "compareSyncPoint": "נקודת סנכרון", + "compareSyncPointHint": "נקודת סנכרון - שתי שורות אלה מוצגות תמיד כזוג, והשורות שמעליהן ומתחתיהן מיושרות בנפרד", + "compareSyncPickCurrent": "נקודת סנכרון: השתמש בשורה נוכחית זו", + "compareSyncPickReference": "נקודת סנכרון: השתמש בשורת הפניה זו", + "compareSyncRemove": "הסר נקודת סנכרון", + "compareClearXSyncPoints": "נקה נקודות סנכרון ({0})", + "compareSyncCurrentPickedX": "השורה הנוכחית #{0} נבחרה כנקודת סנכרון - בחר את שורת ההפניה התואמת ולחץ על סנכרון", + "compareSyncReferencePickedX": "שורת ההפניה #{0} נבחרה כנקודת סנכרון - בחר את השורה הנוכחית התואמת ולחץ על סנכרון", + "compareSyncWithCurrentX": "סנכרן עם השורה הנוכחית #{0}", + "compareSyncWithReferenceX": "סנכרן עם שורת ההפניה #{0}", + "compareSyncApply": "סנכרון", + "compareSyncPointSetXY": "נקודת סנכרון נקבעה: השורה הנוכחית #{0} ושורת ההפניה #{1} מוצגות כעת כזוג", + "compareSyncAlreadyPairedXY": "השורה הנוכחית #{0} ושורת ההפניה #{1} כבר מוצגות כזוג עם אותה התחלה - אין מה לסנכרן", + "compareSyncPointShiftedXYZW": "נקודת סנכרון נקבעה: השורות הנוכחיות #{0}-#{1} הוזזו {2} כדי להתחיל עם שורת ההפניה #{3}", + "compareChangeSyncShiftXYZ": "#{0}-#{1} הוזזו {2} (נקודת סנכרון)", + "compareTimingDeltaHint": "בכמה מאוחר יותר (+) או מוקדם יותר (−) שורת ההפניה מתחילה ומסתיימת לעומת השורה הנוכחית", "loadXFromFile": "טען \"{0}\" מקובץ", + "compareLoadSavedFile": "טען קובץ שמור", "saveCompareHtmlTitle": "שמור קובץ השוואה בפורמט HTML", "pickMatroskaTrackX": "בחר ערוץ Matroska - {0}", "pickTransportStreamTrackX": "בחר רצועת transport stream - {0}", + "chapters": "פרקים", + "dvdVobFilesMissingX": "חסרים קובצי VOB (נמצאו {0}%)", "pickMp4TrackX": "בחר רצועת MP4 - {0}", + "pickMxfTrackX": "בחר רצועת MXF - {0}", + "pickMpegTrackX": "בחר רצועת MPEG - {0}", "rosettaProperties": "מאפייני Timed Text Rosetta IMSC", "rosettaFontSize": "גודל גופן (גובה שורה)", "propertyTimeBase": "בסיס זמן", @@ -1575,7 +1651,6 @@ "propertyDefaultRegion": "אזור ברירת מחדל", "propertyStyleAttributeName": "שם מאפיין הסגנון", "propertyTimeCodeFormat": "פורמט קוד זמן", - "propertyFileExtension": "סיומת קובץ", "propertyTopOrigin": "מקור עליון", "propertyTopExtent": "היקף עליון", "propertyBottomOrigin": "מקור תחתון", @@ -1625,7 +1700,6 @@ "selectionIntersect": "הצלב עם הבחירה" }, "multipleReplace": { - "title": "החלפה מרובה", "editRule": "ערוך כלל", "newRule": "כלל חדש", "editCategory": "ערוך קטגוריה", @@ -1642,24 +1716,23 @@ "findWhat": "חפש את", "descriptionOptional": "תיאור (אופציונלי)", "invalidRegularExpressionX": "ביטוי רגולרי לא חוקי: {0}", - "regularExpressionTooSlowX": "הביטוי הרגולרי ויתר לאחר {0} שניות - הכלל דולג, נסה תבנית פשוטה יותר" + "regularExpressionTooSlowX": "הביטוי הרגולרי ויתר לאחר {0} שניות - הכלל דולג, נסה תבנית פשוטה יותר", + "moveToCategory": "העבר לקטגוריה" }, "find": { "searchTextWatermark": "טקסט לחיפוש...", "wholeWord": "מילה שלמה", - "caseSensitive": "רגיש לאותיות רישיות", - "caseInsensitive": "לא רגיש לאותיות רישיות", "findPrevious": "חפש את ה_קודם", "findNext": "חפש את ה_בא", "replaceAndFindNext": "ה_חלף וחפש את הבא", "replaceAll": "החלף ה_כל", - "replaceTitle": "החלפה", - "replaceWith": "החלף ב-", "replaceTextWatermark": "החלף ב-...", "replaceIn": "החלף/חפש ב-", "replaceInTextAndOriginal": "טקסט וטקסט מקורי", "replaceInTextOnly": "טקסט בלבד", - "replaceInOriginalOnly": "טקסט מקורי בלבד" + "replaceInOriginalOnly": "טקסט מקורי בלבד", + "replacedOneOccurrence": "הוחלף מופע אחד", + "replacedXOccurrences": "הוחלפו {0} מופעים" }, "regularExpressionContextMenu": { "wordBoundary": "גבול מילה (\\b)", @@ -1687,6 +1760,7 @@ "xLinesYSubtitles": "{0} שורות, {1} כתוביות", "xLinesYSubtitlesZErrors": "{0} שורות, {1} כתוביות, {2} שגיאות", "couldNotParseAsX": "לא ניתן לפענח כ-{0}", + "readAsXUseAsY": "לא ניתן היה לקרוא את המקור כ-{1}, אך ניתן היה לקרוא אותו כ-{0}.\nלהשתמש בשורות ולהמשיך לשמור כ-{1}?", "selectedXCharactersYLines": "נבחרו: {0} תווים ב-{1} שורות", "replacedXOccurrences": "הוחלפו {0} מופעים", "invalidRegularExpression": "ביטוי רגולרי לא חוקי", @@ -1701,7 +1775,6 @@ "aiReview": { "title": "סקירת AI", "review": "סקור", - "stop": "עצור", "editPrompt": "ערוך הנחיה...", "editPromptTitle": "ערוך הנחיית סקירה", "promptInfo": "הנחיות שנשלחות למודל - {language} מוחלף בשפת הכתוביות.", @@ -1712,7 +1785,6 @@ "reviewingLineXOfY": "סוקר שורה {0} מתוך {1}...", "reviewDone": "הסקירה הושלמה - {0} הצעות ב-{1} שורות", "noIssuesFound": "לא נמצאו בעיות", - "categoryAll": "הכל", "categorySpelling": "איות", "categoryGrammar": "דקדוק", "categoryPunctuation": "פיסוק", @@ -1724,8 +1796,23 @@ "largeChangeWarning": "שינוי גדול - נראה כמו שכתוב מחדש, בדוק בקפידה", "mismatchWarning": "אינו דומה למקור - ייתכן ששייך לשורה אחרת", "engineError": "לא ניתן היה להתחבר למנוע ה-AI: {0}", - "setupHint": "נא לבחור תחילה מנוע ומודל בכלים -> סקירת AI.", - "playCurrentLineHint": "נגן את השורה שנבחרה בנגן הוידאו ועצור בסופה (אפשר גם ללחוץ לחיצה כפולה על שורה)" + "playCurrentLineHint": "נגן את השורה שנבחרה בנגן הוידאו ועצור בסופה (אפשר גם ללחוץ לחיצה כפולה על שורה)", + "editAfterHint": "לחץ על הטקסט של השורה שנבחרה כדי לערוך את התיקון לפני החלתו (Enter שומר, Escape מבטל)", + "template": "תבנית", + "templatePlaceholder": "בחר תבנית כדי להחליף את ההנחיה", + "templateStandard": "הגהה רגילה", + "templateSpeechToText": "ניקוי דיבור לטקסט", + "templateMachineTranslation": "תיקון תרגום מכונה", + "templateSdh": "SDH / כבדי שמיעה", + "referenceContext": "הקשר מקור", + "referenceContextInfo": "אופציונלי. שמות, דמויות, מילון מונחים, תקציר או הערות סגנון - המודל מתייחס לכך כמקור הסמכות לשמות ולמונחים. נשלח עם כל אצווה.", + "referenceContextPlaceholder": "שמות: קאיטו, דודה מיי, ד\"ר הוליס\nמונחים: שינקנסן, ליבת עיוות\nתקציר: קאיטו נוסע לטוקיו כדי למצוא את אביו הנעדר.", + "generateContext": "צור באמצעות AI", + "generateContextHint": "תן למנוע ה-AI שנבחר לקרוא את הכתובית ולנסח שמות, מונחים ותקציר - בדוק את התוצאה לפני השימוש", + "generatingContextPartXOfY": "קורא חלק {0} מתוך {1}...", + "generatingContextSynopsis": "כותב תקציר...", + "replaceContextQuestion": "להחליף את הקשר המקור הנוכחי?", + "noContextGenerated": "מנוע ה-AI לא החזיר מידע מקור שמיש." }, "aiAssistant": { "title": "עוזר AI", @@ -1743,22 +1830,14 @@ "showReasoning": "הצג את הנמקת המודל" }, "fixCommonErrors": { - "title": "תיקון שגיאות נפוצות", - "step1": "שלב 1 מתוך 2 - בחירת שגיאות לתיקון", - "whatToFix": "מה לתקן", - "example": "דוגמה", - "selectAll": "בחר הכל", - "inverseSelection": "הפוך בחירה", "back": "< ה_קודם", "next": "ה_בא >", - "step2": "שלב 2 מתוך 2 - אימות תיקונים", "fixes": "תיקונים", "ruleDetailsDotDotDot": "פרטי הכלל...", "showOnlyThisRule": "הצג רק תיקונים מכלל זה", "appliedRulesForLineX": "כללים שהוחלו על שורה {0}", "ruleX": "כלל: {0}", "log": "יומן", - "function": "פונקציה", "removedEmptyLine": "הסר שורה ריקה", "removedEmptyLineAtTop": "הסר שורה ריקה למעלה", "removedEmptyLineAtBottom": "הסר שורה ריקה למטה", @@ -1779,13 +1858,13 @@ "removeLineBreaksPixelWidth": "בטל פיצול כתוביות שיכולות להיכנס בשורה אחת (רוחב פיקסלים)", "fixUppercaseIInsideLowercaseWords": "תקן אות 'i' רישית בתוך מילים באותיות קטנות (שגיאת OCR)", "fixDoubleApostrophes": "תקן תווי גרש כפולים ('') למירכאות בודדות (\")", + "fixMisreadQuotes": "תקן גרשיים שנקראו בטעות כמירכאות כפולות (OCR, אנגלית)", "addPeriods": "הוסף נקודה בסוף שורות אם השורה הבאה מתחילה באות רישית", "startWithUppercaseLetterAfterParagraph": "התחל באות רישית לאחר פסקה", "startWithUppercaseLetterAfterPeriodInsideParagraph": "התחל באות רישית לאחר נקודה בתוך פסקה", "startWithUppercaseLetterAfterColon": "התחל באות רישית לאחר נקודתיים/נקודה ופסיק", "fixLowercaseIToUppercaseI": "תקן אות 'i' קטנה מבודדת ל-'I' רישית (אנגלית)", "fixCommonOcrErrors": "תקן שגיאות OCR נפוצות (באמצעות רשימת החלפת OCR)", - "commonOcrErrorsFixed": "שגיאות OCR נפוצות שתוקנו (שימוש ברשימת החלפת OCR): {0}", "removeSpaceBetweenNumber": "הסר רווחים בין מספרים", "breakDialogsOnOneLine": "פצל דו-שיח בשורה אחת", "removeDialogFirstInNonDialogs": "הסר מקף התחלה בשורה ראשונה של טקסטים שאינם דו-שיח", @@ -1802,18 +1881,17 @@ "fixCommasExample": ",, -> ,", "removeLineBreaksExample": "Foo
bar! -> Foo bar!", "fixDoubleApostrophesExample": "''Has double single quotes'' -> \"Has single double quote\"", + "fixMisreadQuotesExample": "\"Hello' -> \"Hello\"", "addPeriodsExample": "Hello world -> Hello world.", "startWithUppercaseLetterAfterParagraphExample": "p1: Foobar! || p2: foobar! -> p1: Foobar! || p2: Foobar!", "startWithUppercaseLetterAfterPeriodInsideParagraphExample": "Hello there! how are you? -> Hello there! How are you?", "startWithUppercaseLetterAfterColonExample": "Speaker: hello world! -> Speaker: Hello world!", "removeHyphensSingleLineExample": "- Foobar. -> Foobar.", "fix3PlusLinesExample": "Foo
bar
baz! -> Foo bar baz!", - "xMissingQuotesAdded": "מירכאות חסרות שהתווספו: {0}", "fix3PlusLine": "תקן כתובית עם יותר משתי שורות", "fix3PlusLines": "תקן כתוביות עם יותר משתי שורות", "analysing": "מנתח...", "nothingToFix": "אין מה לתקן :)", - "fixesFoundX": "תיקונים שנמצאו: {0}", "xFixesApplied": "תיקונים שהוחלו: {0}", "xFixesYSelected": "{0} תיקונים - {1} נבחרו", "nothingFixableBut": "לא ניתן היה לתקן דבר באופן אוטומטי. הכתובית מכילה שגיאות - ראה יומן לפרטים", @@ -1822,7 +1900,6 @@ "fixFirstLetterToUppercaseAfterParagraph": "הפוך אות ראשונה לרישית לאחר פסקה", "mergeShortLine": "מזג שורה קצרות (משפט בודד)", "mergeShortLineAll": "מזג שורות קצרות (הכל מלבד דו-שיח)", - "unbreakShortLinePixelWidth": "בטל פיצול שורה קצרה (רוחב פיקסלים)", "breakLongLine": "פצל שורה ארוכה", "fixLongDisplayTime": "תקן זמן הצגה ארוך מדי", "fixInvalidItalicTag": "תקן תג נטייה (italic) לא תקין", @@ -1845,7 +1922,6 @@ "fixUppercaseIInsideLowercaseWord": "תקן אות 'i' רישית בתוך מילה באותיות קטנות", "fixMissingPeriodAtEndOfLine": "הוסף נקודה חסרה בסוף שורה", "refreshFixes": "רענן תיקונים זמינים", - "applyFixes": "החל תיקונים שנבחרו וסגור", "autoBreak": "פיצול &אוטומטי", "unbreak": "&בטל פיצול", "fixDoubleDash": "תקן '--' ל-'...'", @@ -1864,8 +1940,6 @@ "fixSpaceBetweenNumbersExample": "1 100 -> 1100", "fixDialogsOneLineExample": "Hi John! - Hi Ida! -> Hi John!
- Hi Ida!", "removeDialogFirstInNonDialogsExample": "- How are you? -> How are you?", - "selectDefault": "בחר ברירת מחדל", - "setDefault": "קבע את התיקונים הנוכחיים כברירת מחדל", "fixContinuationStyleX": "תקן סגנון המשכיות: {0}", "fixUnnecessaryLeadingDots": "הסר נקודות מובילות מיותרות", "fixCommonErrorsProfiles": "פרופילי תיקון שגיאות נפוצות", @@ -1873,7 +1947,6 @@ "searchRulesDotDotDot": "חפש כללים...", "goToApplyFixes": "עבור להחלת תיקונים", "backToFixList": "חזרה לרשימת התיקונים", - "applyFixesAndClose": "החל תיקונים וסגור", "fixCommonOcrErrorsStep2": "תיקון שגיאות נפוצות, שלב 2 (החלת תיקונים)", "fixCommonOcrErrorsStep2FixesFoundX": "תיקון שגיאות נפוצות, שלב 2 - תיקונים שנמצאו: {0}", "action": "פעולה", @@ -1886,18 +1959,11 @@ "adjustDurations": { "title": "התאם משכים", "adjustVia": "התאם באמצעות", - "seconds": "שניות", - "percent": "אחוז", "recalculate": "חשב מחדש", "addSeconds": "הוסף שניות", - "setAsPercent": "קבע כאחוז מהמשך", "note": "הערה: זמן התצוגה לא יחפוף את זמן ההתחלה של הטקסט הבא", "fixed": "קבוע", - "milliseconds": "מילישניות", "extendOnly": "הארך בלבד", - "enforceDurationLimits": "אכוף משך מינימלי ומקסימלי", - "checkShotChanges": "אל תאריך מעבר למעברי שוט", - "batchCheckShotChanges": "כבד מעברי שוט (אם זמינים)", "recalculateRequiresOcrNote": "חישוב מחדש אינו זמין — לאחת או יותר מהכתוביות אין טקסט OCR. הרץ OCR תחילה." }, "applyDurationLimits": { @@ -1905,6 +1971,8 @@ "fixMinDurationMs": "תקן משך מינימלי (מילישניות)", "doNotGoPastShotChange": "אל תעבור מעבר שוט", "fixMaxDurationMs": "תקן משך מקסימלי (מילישניות)", + "fixMinDurationFrames": "תקן משך מינימלי (פריימים)", + "fixMaxDurationFrames": "תקן משך מקסימלי (פריימים)", "minimumDurationMilliseconds": "משך מינימלי (מילישניות):", "maximumDurationMilliseconds": "משך מקסימלי (מילישניות):", "maxDurationShouldBeHigherThanMinDuration": "המשך המקסימלי חייב להיות גבוה מהמשך המינימלי", @@ -1924,12 +1992,8 @@ }, "beautifyTimeCodes": { "title": "ייפוי קודי זמן", - "beautifySettings": "הגדרות ייפוי", "snapToFrames": "הצמד לפריימים", - "frameGap": "מרווח פריימים", "minDuration": "משך מינימלי (מילישניות)", - "shotChangeThreshold": "סף מעבר שוט (מילישניות)", - "shotChangeOffset": "היסט מעבר שוט (פריימים)", "original": "מקור", "beautified": "מיופה", "subtitlesCount": "כתוביות", @@ -1958,10 +2022,76 @@ "timeCodesExtractFailed": "לא ניתן היה לקרוא קודי זמן מהוידאו", "timeCodesHint": "הצמד קודי זמן לזמני הפריימים האמיתיים של הוידאו במקום להניח קצב פריימים קבוע. נדרשת קריאה של הוידאו כולו פעם אחת; התוצאה נשמרת במטמון." }, + "improveTimeCodes": { + "title": "שפר קודי זמן (יישור כפוי)", + "aligner": "מיישר", + "alignerHint": "המודל שמאזין לכל שורה בשמע. האפשרויות הטובות ביותר לשפת הכתובית מופיעות ראשונות.", + "maxShift": "הזזה מקסימלית (שניות)", + "maxShiftHint": "עד כמה הכתובית עשויה להיות לא מסונכרנת. שורה שההתחלה שלה הייתה זזה יותר מכך נשארת כפי שהיא ומסומנת. בכל ערך שהוא, שורה בודדת שרוצה לזוז יותר מחצי שנייה מעבר לשורות שסביבה אינה מוזזת באופן עיוור: היא עוקבת אחר שכנותיה כאשר כולן זזו, ואחרת מוצעת ללא סימון כדי שתבדוק אותה.", + "adjustStartTimes": "התאם זמני התחלה", + "adjustEndTimes": "התאם זמני סיום", + "isolateSpeech": "בודד דיבור תחילה (איטי)", + "isolateSpeechHint": "מסיר מוזיקה ואפקטי קול לפני היישור, כך שהמיישר שומע רק את הדיאלוג. עוזר בשורות הנאמרות על רקע מוזיקה רועשת או אקשן, אך לוקח בערך כאורך השמע עצמו עם GPU - ופי כמה יותר בלעדיו.", + "isolatingSpeech": "מבודד דיבור... (זה לוקח זמן)", + "isolateSpeechFailed": "לא ניתן היה לבודד את הדיבור - היישור בוצע מול השמע המקורי במקום זאת.", + "checkWithSpeechToText": "בדוק באמצעות דיבור לטקסט", + "checkWithSpeechToTextHint": "מתמלל גם את השמע, ובודק כל הזזה מול המקום שבו המילים נשמעו בפועל. הזזות גדולות שהדיבור מאשר מסומנות עבורך, והזזות הרחק מהמקום שבו שורה נשמעת נשארות ללא סימון כדי שתבדוק אותן. מציג גם כמה מכל שורה נשמע - שיעור נמוך פירושו שהטקסט שונה ממה שנאמר. כתובית שאינה מסונכרנת יותר מההזזה המקסימלית - היסט קבוע, סחיפה עקב אי-התאמה בקצב פריימים, או קפיצות במקומות שבהם סצנות נחתכו - מסונכרנת תחילה לפי המילים שנשמעו. משתמש ב-Crisp ASR Parakeet, שלוקח כמה דקות לסרט באורך מלא עם GPU.", + "checkWithSpeechToTextNotAvailableX": "בדיקת דיבור לטקסט אינה זמינה עבור {0}", + "transcribingToCheck": "מתמלל כדי לבדוק את היישור...", + "speechToTextFailed": "דיבור לטקסט נכשל - קודי הזמן לא נבדקו מולו.", + "speechToTextX": "דיבור לטקסט: {0}", + "speechToTextConfirmedXDisputedY": "דיבור: {0} אושרו, {1} שנויים במחלוקת", + "hardlyHeardX": "דיבור לטקסט שמע רק {0}% ממילות הכתובית. האם הכתובית בשפה המדוברת? המיישר אינו יכול למקם תרגום.", + "heard": "נשמע", + "heardHint": "כמה מהשורה שמע דיבור לטקסט. שיעור נמוך פירושו שהטקסט שונה ממה שנאמר - שם המיישר צפוי לטעות יותר מכל.", + "statusConfirmedBySpeech": "אושר על ידי הדיבור", + "statusDisputedBySpeech": "נשמע במקום שבו היה", + "statusMovedWithSync": "הוזז עם הסנכרון", + "syncedFirstXY": "סונכרן תחילה: {0} שנ' בהתחלה, {1} שנ' בסוף", + "showSpeechOnly": "הצג דיבור בלבד", + "showSpeechOnlyHint": "צייר את גל השמע המיושר מהשמע ללא מוזיקה ואפקטי קול; גל השמע המקורי נשאר כפי שהוא, לצורך השוואה. זמין לאחר שהדיבור בודד - כאן, או באמצעות \"הצג דיבור בלבד\" בגל השמע של החלון הראשי.", + "align": "יישר", + "original": "מקור", + "aligned": "מיושר", + "intro": "בחר מיישר ולחץ על \"יישר\" כדי להאזין לכל שורה בשמע ולהדק את קודי הזמן שלה.", + "extractingAudio": "מחלץ שמע...", + "aligningBatchXOfY": "מיישר... אצווה {0} מתוך {1}", + "extractAudioFailed": "לא ניתן היה לחלץ את השמע באמצעות ffmpeg.", + "modelWillBeDownloaded": "תתבצע הורדה של {0}", + "summaryXRetimedYKeptZSkipped": "תוזמנו מחדש: {0} · נשמרו: {1} · ללא דיבור: {2}", + "meanShiftX": "הזזה ממוצעת: {0} ms", + "toCheckX": "לבדיקה: {0}", + "largeMovesToCheckX": "{0} שורות היו זזות מרחק רב בעצמן ונשארו ללא סימון - האזן להן (F5 מנגן את השורה המיושרת, Shift+F5 את המקורית) וסמן את אלה שנכונות.", + "linesToCheckX": "{0} שורות נשארו ללא סימון לבדיקה - האזן להן (F5 מנגן את השורה המיושרת, Shift+F5 את המקורית) וסמן את אלה שנכונות.", + "changeXOfY": "שינוי {0} מתוך {1}", + "previousChange": "השינוי הקודם", + "nextChange": "השינוי הבא", + "startShift": "התחלה", + "endShift": "סיום", + "statusRetimed": "תוזמן מחדש", + "statusMovedWithNeighbours": "הוזז עם שכנותיה", + "statusLargeMoveUnconfirmed": "הזזה גדולה - האזן, סמן כדי להחיל", + "statusUnchanged": "כבר במקום", + "statusNoSpeech": "ללא דיבור", + "statusShiftTooLarge": "נשמר - הזזה גדולה מדי", + "statusFailed": "נשמר - המיישר נכשל", + "statusAdjustedByHand": "הותאם ידנית", + "alignedWaveformHint": "גרור שורה כדי להזיז אותה, או גרור את קצוותיה כדי לשנות את ההתחלה או הסיום שלה.", + "playLine": "נגן שורה", + "undoAdjustment": "בטל התאמה", + "playPause": "נגן / השהה", + "playAligned": "מיושר", + "playAlignedHint": "נגן את השורה שנבחרה עם קודי הזמן המיושרים שלה", + "apply": "החל", + "applyHint": "בטל את סימון השורה כדי לשמור את קודי הזמן המקוריים שלה. לחץ לחיצה כפולה על שורה כדי לנגן אותה. ניתן גם להזיז שורות או לשנות את גודלן בגל השמע המיושר." + }, "bridgeGaps": { - "title": "גישור מרווחים", "bridgeGapsSmallerThan": "גשר על מרווחים קטנים מ- (מילישניות)", "minGap": "מרווח מינימלי (מילישניות)", + "bridgeGapsSmallerThanFrames": "גשר על מרווחים קטנים מ- (פריימים)", + "minGapFrames": "מרווח מינימלי (פריימים)", + "calculateBridgeGapsSmallerThanDotDotDot": "חשב את מגבלת גישור המרווחים מקצב פריימים...", + "useXMsAsBridgeGapsSmallerThan": "לגשר על מרווחים קטנים מ-\"{0}\" אלפיות שנייה?", "numberOfSmallGapsBridgedX": "מספר מרווחים קטנים שגושרו: {0}", "percentFoPrevious": "מרווח לקודם (%)", "gapChange": "שינוי מרווח" @@ -1973,8 +2103,6 @@ "sortBy": { "title": "מיין כתוביות", "sortOrder": "סדר מיון", - "sortByNumber": "מספר", - "sortByStartTime": "זמן התחלה", "sortByEndTime": "זמן סיום" }, "batchConvert": { @@ -1997,6 +2125,7 @@ "addColor": "הוסף צבע", "deleteLinesWithSpecificActorsOrStyles": "מחק שורות עם שחקנים או סגנונות מסוימים (הפרד בתו פסיק)", "useSourceStylesIfPossible": "השתמש בסגנונות מקור במידת האפשר", + "keepSourceEmbeddedFonts": "שמור גופנים מוטמעים מהמקור (כותרת תחתונה)", "editStyles": "ערוך סגנונות", "editProperties": "ערוך מאפיינים", "editAttachments": "ערוך קבצים מצורפים", @@ -2017,7 +2146,6 @@ "assaChangeStyleFromStyle": "שנה סגנון מ-", "assaChangeStyleToStyle": "אל", "assaChangeStyleImportStyle": "ייבוא סגנון...", - "assaChangeStyleImportedX": "יובא: {0}", "assaChangeStyleTrimUnusedStyles": "נקה סגנונות שאינם בשימוש", "assaChangeStylePropertiesTitle": "שנה מאפייני סגנון", "assaChangeStylePropertiesInfo": "משנה את השדות שנבחרו בכל סגנון בקובץ, מבלי לגעת בסגנונות עצמם.", @@ -2029,13 +2157,52 @@ "convertColorsToDialogReBreakLines": "פצל שורות מחדש", "snapTimeCodesToFramesInfo": "מעגל כל זמן התחלה וסיום לפריים הקרוב ביותר. קצב הפריימים נקרא מקובץ וידאו בעל שם זהה לקובץ הכתוביות, אלא אם נבחר קצב פריימים קבוע.", "addFolderDotDotDot": "הוסף תיקייה...", + "addFolderRecursiveDotDotDot": "הוסף תיקייה באופן רקורסיבי...", "selectFolderToConvert": "בחר תיקייה עם הקבצים להמרה", "includeSubfolders": "כלול תיקיות משנה בעת הוספת תיקייה", + "videoFilesWhenAddingFolder": "קובצי וידאו (mkv, mp4, ts...) בעת הוספת תיקייה", + "videoFilesAsk": "שאל", + "videoFilesInclude": "הוסף את רצועות הכתוביות שלהם", + "videoFilesSkip": "דלג", + "folderContainsXVideoFiles": "התיקייה מכילה {0} קובצי וידאו (mkv, mp4, ts...).\n\nלהוסיף גם את רצועות הכתוביות המוטמעות שלהם, או רק את קובצי הכתוביות?", + "addVideoFiles": "הוסף קובצי וידאו", + "skipVideoFiles": "קובצי כתוביות בלבד", + "doNotAskAgainVideoFiles": "אל תשאל שוב (ניתן לשנות בהגדרות המרת אצווה)", + "settingsSectionOutput": "פלט", + "settingsSectionAddingFiles": "הוספת קבצים", + "settingsSectionImageBasedSubtitles": "כתוביות מבוססות תמונה (OCR)", + "settingsSectionOther": "אחר", "keepSourceFileTimestamp": "שמור את תאריך/שעת קובץ המקור בקובצי הפלט", - "scanningFolderX": "סורק את {0}..." + "preventSleepWhileConverting": "מנע מהמחשב להיכנס למצב שינה בזמן ההמרה", + "scanningFolderX": "סורק את {0}...", + "transportStreamSettings": "הגדרות Transport Stream", + "transportStreamSettingsDotDotDot": "הגדרות Transport Stream...", + "transportStreamSettingsInfo": "חל על רצועות כתוביות שחולצו מקובצי Transport Stream (.ts/.m2ts). מיקום וגודל וידאו משפיעים רק על רצועות תמונה של DVB המיוצאות לפורמט מבוסס תמונה.", + "transportStreamOverrideXPosition": "עקוף את מיקום X המקורי", + "transportStreamOverrideYPosition": "עקוף את מיקום Y המקורי", + "transportStreamOverrideVideoSize": "עקוף את גודל הווידאו המקורי", + "transportStreamBottomMargin": "שוליים תחתונים", + "transportStreamFileNameEnding": "סיומת שם הקובץ", + "transportStreamFileNameEndingInfo": "מתווסף לפני הסיומת עבור כל רצועה שחולצה. השאר ריק כדי להשתמש בסיומת השפה הרגילה.", + "transportStreamOnlyTeletext": "טלטקסט בלבד", + "transportStreamGetSizeFromVideo": "קבל גודל מהווידאו...", + "twoLetterLanguageCodeUppercase": "קוד שפה בן שתי אותיות (אותיות רישיות)", + "threeLetterLanguageCodeUppercase": "קוד שפה בן שלוש אותיות (אותיות רישיות)", + "formatContainsDotDotDot": "הפורמט מכיל...", + "extensionIsDotDotDot": "הסיומת היא...", + "statusIsError": "המצב הוא שגיאה", + "forcedTracksOnly": "רצועות כפויות בלבד", + "xConvertedYFailedInZ": "{0:#,###,##0} הומרו, {1:#,###,##0} נכשלו תוך {2}", + "xNotProcessed": "{0:#,###,##0} לא עובדו", + "removeUnicodeControlCharactersTitle": "הסר תווי בקרה של Unicode", + "removeUnicodeControlCharactersInfo": "מסיר מהטקסט את תווי בקרת הכיוון הבלתי נראים של Unicode (LRM, RLM, LRE, RLE, PDF, LRO ו-RLO), ומחליף רווחים בלתי שבירים ברווחים רגילים.", + "preset": "ערכה מוגדרת מראש", + "savePresetDotDotDot": "שמור ערכה...", + "deletePreset": "מחק ערכה", + "presetName": "שם הערכה", + "deletePresetX": "למחוק את הערכה \"{0}\"?" }, "changeCasing": { - "title": "שנה סוג אותיות", "fixNames": "תקן שמות", "namesX": "שמות: {0:#,##0}", "hitsX": "התאמות: {0:#,##0}", @@ -2046,9 +2213,7 @@ "allUppercase": "הכל אותיות רישיות", "allLowercase": "הכל אותיות קטנות" }, - "changeFormatting": { - "title": "שנה עיצוב" - }, + "changeFormatting": {}, "convertActors": { "title": "המר שחקנים", "convertActorFrom": "המר שחקן מ-", @@ -2077,18 +2242,12 @@ "outputFormatAssa": "Advanced Sub Station Alpha (.ass)", "style1": "סגנון 1", "style2": "סגנון 2", - "outlineWidth": "רוחב קו מתאר", - "shadowWidth": "רוחב צל", - "alignTop": "למעלה", - "alignBottom": "למעלה", - "merge": "_מזג", - "pleaseLoadTwoSubtitles": "נא לטעון שתי כתוביות" + "merge": "_מזג" }, "splitSubtitle": { "title": "פצל כתובית", "numberOfEqualParts": "מספר חלקים שווים", "saveSplitParts": "_שמור חלקים מפוצלים", - "subtitleSplitIntoXParts": "הכתובית פוצלה ל-{0} חלקים.", "xPartsSavedInFormatYToFolder": "{0} חלקים נשמרו בפורמט {1} לתיקייה:" }, "splitBreakLongLines": { @@ -2114,13 +2273,14 @@ "onlyContinuationLines": "רק שורות המשך" }, "mergeLinesWithSameText": { - "title": "מזג שורות עם טקסט זהה", "maxMsBetweenLines": "מקסימום מילישניות בין שורות", - "includeIncrementingLines": "כלול שורות עם טקסט עולה" + "maxFramesBetweenLines": "מקסימום פריימים בין שורות", + "includeIncrementingLines": "כלול שורות עם טקסט עולה", + "includeRollUpCaptions": "כלול כתוביות גלילה (roll-up)" }, "mergeLinesWithSameTimeCodes": { - "title": "מזג שורות עם קודי זמן זהים", "maxMsDifference": "הפרש מקסימלי (מילישניות)", + "maxFramesDifference": "הפרש מקסימלי (פריימים)", "makeDialog": "צור דו-שיח" }, "netflixCheckAndFix": { @@ -2143,7 +2303,6 @@ "shotChanges": "מעברי שוט", "startNumberSpellOut": "כתוב מילים למספר שמתחיל משפט", "textforHiUseBrackets": "טקסט לכבדי שמיעה, השתמש בסוגריים", - "frameRate": "קצב פריימים", "twoFrameGrap": "מרווח של שני פריימים", "whiteSpace": "רווחים לבנים", "childrensProgram": "תוכנית ילדים", @@ -2181,7 +2340,6 @@ "italicsFixed": "כתב נטוי תוקן", "italicsNotAllowed": "כתב נטוי אינו מותר", "bridgeGapsXToYFrames": "מרווח של 3 עד {0} פריימים => מרווח של 2 פריימים", - "startNumberSpellOutComment": "כאשר מספר מתחיל משפט, יש לכתוב אותו תמיד כמילה", "maximumXCharactersPerSecond": "מקסימום {0} תווים לשנייה" }, "imageBasedEdit": { @@ -2191,10 +2349,8 @@ "resizeImagesDotDotDot": "שנה גודל תמונות...", "adjustBrightnessDotDotDot": "התאם בהירות...", "adjustAlphaDotDotDot": "התאם אלפא...", - "centerHorizontally": "מרכז אופקית", "cropImages": "חתוך תמונות", "importTimeCodes": "ייבוא קודי זמן...", - "setTextForSubtitle": "קבע טקסט עבור כתובית", "screenWidth": "רוחב מסך", "screenHeight": "גובה מסך", "alphaThresholdInfo": "פיקסלים עם אלפא מתחת לסף יהפכו לשקופים לחלוטין", @@ -2224,15 +2380,31 @@ "letterboxCustom": "גובה פס מותאם אישית", "barHeightPx": "גובה פס (px)", "titleSafePercent": "אזור בטוח לכותרות (%)", + "moveCaptions": "הזז כתוביות", + "moveCaptionsDotDotDot": "הזז כתוביות...", + "moveCaptionsTo": "הזז כתוביות", + "moveCaptionsIntoBars": "לתוך פסי ה-letterbox (מחוץ לתמונה)", + "moveCaptionsIntoPicture": "בתוך התמונה (מחוץ לפסים)", + "moveCaptionsOffset": "היסט מהקצה (px)", + "letterboxBarHeightXPxYOfZCaptionsWillMove": "גובה הפס: {0} px - {1} מתוך {2} כתוביות יוזזו", + "removeFadeInOut": "הסר עמעום כניסה/יציאה", + "removeFadeInOutXLinesRemoved": "{0} שורות עמעום מוזגו לתוך השורות שאליהן הן שייכות", + "removeFadeInOutNothingFound": "לא נמצאו שורות עמעום כניסה/יציאה", "positionSummary": "{0}×{1} - {2} כתוביות - גובה פס: {3} px", "xInActivePicture": "{0} בתמונה", "xInTopBar": "{0} בפס העליון", "xInBottomBar": "{0} בפס התחתון", "noImageSubtitlesLoaded": "לא נטענו כתוביות מבוססות תמונה", - "imageBasedFormatNotSupported": "פורמט כתוביות מבוסס תמונה לא נמצא/אינו נתמך." + "imageBasedFormatNotSupported": "פורמט כתוביות מבוסס תמונה לא נמצא/אינו נתמך.", + "frameRateDeclaredInFile": "קצב הפריימים המוצהר בקובץ ה-Blu-ray sup", + "frameRateDetectedFromTimeCodesX": "קצב פריימים שזוהה מקודי הזמן של ה-Blu-ray sup (הקובץ מצהיר על {0})", + "frameRateFromVideo": "קצב הפריימים של הווידאו", + "frameRateCurrent": "קצב פריימים נוכחי", + "frameRateSetManually": "קצב פריימים שנקבע ידנית", + "frameRateUsageInfo": "ייצוא Blu-ray sup, BDN XML, DOST ו-FCP נכתב בקצב פריימים זה. הוא אינו משנה זמנים כלשהם - השתמש ב-\"שנה קצב פריימים\" לשם כך.", + "useVideoFrameRateXInsteadOfY": "קצב הפריימים של הווידאו הוא {0}, אך הכתובית מוגדרת ל-{1}.\n\nלהשתמש בקצב הפריימים של הווידאו ({0})?" }, "removeTextForHearingImpaired": { - "title": "הסר טקסט לכבדי שמיעה", "interjections": "מילות קריאה", "skipIfStartWith": "דלג אם מתחיל ב-", "removeTextBetween": "הסר טקסט שבין", @@ -2251,7 +2423,6 @@ "removeInterjections": "הסר מילות קריאה", "linesFoundX": "שורות שנמצאו: {0}" }, - "pickAlignmentTitle": "בחר יישור", "removeUnicodeCharacters": { "title": "הסר/החלף תווי יוניקוד", "charactersFoundX": "תווי יוניקוד שנמצאו: {0}", @@ -2261,13 +2432,13 @@ "pickFontNameInstalledFonts": "גופנים מותקנים", "pickFontNameCollectedFonts": "גופנים שנאספו", "colorPickerTitle": "בחר צבע", + "colorPickerBrightness": "בהירות", "filterLayersTitle": "סנן שכבות לתצוגה", "filterLayersHideFromWaveform": "הסתר מגל שמע/ספקטרוגרמה", "filterLayersHideFromSubtitleGrid": "הסתר מטבלת הכתוביות", "filterLayersHideFromVideoPreview": "הסתר מתצוגה מקדימה של הוידאו", "pickSubtitleFormat": "בחר פורמט כתוביות", "pickSubtitleFormatImageBasedNoPreview": "פורמט כתוביות מבוסס תמונה.\nהתמונות נוצרות במהלך ההמרה - אין תצוגה מקדימה של טקסט.", - "pickLayerTitle": "קבע שכבה", "recentColors": "צבעים אחרונים", "beautifyTimeCodesProfile": { "title": "ערוך פרופיל ייפוי קודי זמן", @@ -2275,7 +2446,11 @@ "presetDefault": "ברירת מחדל", "presetNetflix": "Netflix", "presetSdi": "SDI", - "general": "כללי", + "saveAsProfile": "שמור כפרופיל...", + "deleteProfile": "מחק פרופיל", + "profileName": "שם פרופיל", + "overwriteProfileX": "פרופיל בשם \"{0}\" כבר קיים. לדרוס אותו?", + "deleteProfileX": "למחוק את הפרופיל \"{0}\"?", "gap": "מרווח", "gapSuffix": "פריימים (מעדכן גם את שדות המרווח שאינם אפס שלהלן)", "milliseconds": "מילישניות", @@ -2312,9 +2487,7 @@ "columnMerge": "מזג", "columnFirst": "שורה ראשונה", "columnSecond": "שורה שנייה", - "columnMerged": "ממוזג", - "selectAll": "בחר הכל", - "inverseSelection": "הפוך בחירה" + "columnMerged": "ממוזג" }, "fixCommonErrorsSettingsTitle": "הגדרות תיקון שגיאות נפוצות" }, @@ -2359,59 +2532,51 @@ "correctWordsX": "מילים נכונות: {0}", "namesX": "שמות: {0}", "addedToDictionaryX": "נוספו למילון: {0}", - "doNotShowThisAgain": "אל תציג הודעה זו שוב" + "doNotShowThisAgain": "אל תציג הודעה זו שוב", + "editUseAlwaysList": "ערוך רשימת \"השתמש תמיד\"...", + "useAlwaysListTitle": "בדיקת איות - רשימת \"השתמש תמיד\"", + "useAlwaysListHeader": "רשימת \"השתמש תמיד\"", + "useAlwaysListDescription": "מילים שבדיקת האיות מחליפה אוטומטית, שנשמרו באמצעות \"שנה הכל\" ו-\"השתמש תמיד\". צמד משמש רק כאשר המילה מאויתת שגוי.", + "useAlwaysWord": "מילה שגויה", + "xPairs": "{0:#,##0} צמדים", + "xUnusedPairs": "{0:#,##0} מעולם לא שומשו", + "useAlwaysUnusedHint": "מעולם לא שומש - מילה זו מאויתת נכון (או שהיא שם או מילת משתמש), ולכן בדיקת האיות לעולם אינה מסמנת אותה", + "removeUnused": "הסר שאינם בשימוש", + "useAlwaysEmpty": "אין עדיין צמדי \"השתמש תמיד\" לשפה זו", + "useAlwaysEmptyHint": "צמדים נוספים כאשר לוחצים על \"שנה הכל\" או \"השתמש תמיד\" בבדיקת האיות.", + "useAlwaysNoMatch": "אין צמדים התואמים לחיפוש", + "useAlwaysSameWord": "ההחלפה חייבת להיות שונה מהמילה.", + "useAlwaysRememberOff": "\"זכור רשימת 'השתמש תמיד'\" כבוי בהגדרות, ולכן בדיקת האיות אינה משתמשת ברשימה זו ואינה שומרת אותה." }, "video": { "burnIn": { "title": "צור וידאו עם כתוביות מובנות (Burn-in)", - "infoAssaOff": "הערה: ישנה תמיכה בעיצוב Advanced SubStation Alpha.", - "infoAssaOn": "הערה: ייעשה שימוש בעיצוב Advanced SubStation Alpha :)", - "xGeneratedWithBurnedInSubsInX": "\"{0}\" נוצר עם כתוביות מובנות תוך {1}.", - "timeRemainingMinutes": "הזמן הנותר: {0} דקות", - "timeRemainingOneMinute": "הזמן הנותר: דקה אחת", - "timeRemainingSeconds": "הזמן הנותר: {0} שניות", - "timeRemainingAFewSeconds": "הזמן הנותר: שניות בודדות", - "timeRemainingMinutesAndSeconds": "הזמן הנותר: {0} דקות ו-{1} שניות", - "timeRemainingOneMinuteAndSeconds": "הזמן הנותר: דקה אחת ו-{0} שניות", - "targetFileName": "שם קובץ היעד: {0}", "targetFileSize": "גודל קובץ יעד (דורש קידוד בשני מעברים)", "fileSizeMb": "גודל קובץ ב-MB", "matchSourceVideoSize": "התאם לגודל וידאו המקור", - "passX": "מעבר {0}", - "encoding": "קידוד", "bitRate": "קצב נתונים", "totalBitRateX": "קצב נתונים כולל: {0}", "sampleRate": "קצב דגימה", "audio": "שמע", - "stereo": "סטריאו", "preset": "פרופיל הגדרות", + "tune": "כוונון (Tune)", "pixelFormat": "פורמט פיקסלים", "crf": "CRF", - "tuneFor": "כוונן עבור", - "alignRight": "יישר לימין", - "getStartPosition": "קבל מיקום התחלה", - "getEndPosition": "קבל מיקום סיום", "useSource": "השתמש במקור", - "useSourceFolder": "השתמש בתיקיית מקור", - "useSourceResolution": "השתמש ברזולוציית מקור", - "outputSettings": "קובץ/תיקיית פלט...", "fontSizeFactor": "מקדם גודל גופן", "boxType": "סוג תיבה", - "fixRightToLeft": "תקן מימין לשמאל", - "cut": "חתוך", "fromTime": "מזמן", "toTime": "עד זמן", "audioEncoding": "קידוד שמע", "outputProperties": "מאפייני פלט...", "videoFileSize": "גודל קובץ וידאו", "oneBox": "תיבה אחת", - "boxPerLine": "תיבה לכל שורה", - "logoInfo": "בחר תמונת PNG וגרור אותה כדי למקם אותה על גבי הוידאו." + "logoInfo": "בחר תמונת PNG וגרור אותה כדי למקם אותה על גבי הוידאו.", + "noSubtitlesGenerateAnyway": "אין שורות כתוביות לצריבה.\n\nליצור את הווידאו ללא כתוביות?", + "noSubtitlesInXItemsGenerateAnyway": "ל-{0} פריטי אצווה אין שורות כתוביות לצריבה.\n\nליצור את סרטוני הווידאו האלה ללא כתוביות?" }, "videoTransparent": { - "title": "צור וידאו שקוף עם כתוביות", - "infoAssaOff": "הערה: ישנה תמיכה בעיצוב Advanced SubStation Alpha.", - "infoAssaOn": "הערה: ייעשה שימוש בעיצוב Advanced SubStation Alpha :)" + "title": "צור וידאו שקוף עם כתוביות" }, "audioToText": { "title": "דיבור לטקסט", @@ -2419,6 +2584,21 @@ "translateToEnglish": "תרגם לאנגלית", "transcribing": "מתמלל...", "transcribingXOfY": "מתמלל {0} מתוך {1}...", + "linesTranscribedXOfYFailedZ": "{0} מתוך {1} שורות תומללו - {2} נכשלו או שלא היה בהן דיבור.\n\nשורות שנכשלו שומרות על הטקסט הנוכחי שלהן. להחיל את השורות שתומללו, או לנסות שוב את אלה שנכשלו (ניתן לשנות קודם את המנוע או ההגדרות)?", + "linesTranscribedXOfYCancelled": "התמלול בוטל לאחר {0} מתוך {1} שורות.\n\nלהחיל את השורות שתומללו? שאר השורות שומרות על הטקסט הנוכחי שלהן.", + "noLinesTranscribed": "לא ניתן היה לתמלל אף אחת מ-{0} השורות.\nבדוק את יומן הכלים לפרטים.", + "applyTranscribedLines": "החל את שתומללו", + "retryFailedLines": "נסה שוב את שנכשלו", + "isolateSpeech": "בודד דיבור (איטי)", + "isolateSpeechHint": "מסיר מוזיקה ואפקטי קול לפני התמלול. מביא לפחות שורות שהוחמצו ולתזמון טוב יותר בשמע עם מוזיקה רועשת, אך לוקח בערך כאורך השמע עצמו עם GPU - ופי כמה יותר בלעדיו.", + "isolatingSpeech": "מבודד דיבור...", + "isolateSpeechFailed": "לא ניתן היה לבודד את הדיבור - מתמלל את השמע המקורי במקום זאת.", + "detectSpeakers": "זהה דוברים", + "detectSpeakersHint": "מתחיל כל שורה עם הדובר, כמו \"(speaker 0)\", באמצעות דיאריזציית Sortformer של NVIDIA (עד 8 דוברים). עובד הכי טוב עם מנועים שמפצלים שורות היכן שהדובר מתחלף, כמו Parakeet ו-Cohere.", + "detectSpeakersNeedsNewerCrispAsr": "\"זהה דוברים\" דורש Crisp ASR v{0} ומעלה, אך מותקנת גרסה v{1}.\n\nעדכן את Crisp ASR (לחצן גלגל השיניים ליד המנוע) או כבה את \"זהה דוברים\".", + "xNeedsNewerCrispAsr": "{0} דורש Crisp ASR v{1} ומעלה, אך מותקנת גרסה v{2}.\n\nלהוריד ולהתקין את העדכון כעת?", + "vad": "VAD", + "vadHint": "זיהוי פעילות קולית: רק הדיבור מתומלל, וחותכים היכן שהדובר עוצר.\nאוטומטי: Silero, ורק עבור המנועים שזקוקים לכך (Cohere, Mega, Index-Echo).\nSilero, FireRedVAD, Whisper-VAD, WebRTC: תמיד פעיל, לכל מנוע Crisp ASR. FireRedVAD הוא המדויק ביותר, Whisper-VAD נותן את החיתוכים המעטים והנקיים ביותר אך הוא האיטי ביותר.\nIndex-Echo תמיד משתמש ב-Silero. ‏--vad, --vad-model או --chunk-seconds בהגדרות המתקדמות גוברים.", "inputLanguage": "שפת קלט", "advancedWhisperSettings": "הגדרות Whisper מתקדמות", "downloadingSpeechToTextEngine": "מוריד מנוע דיבור לטקסט", @@ -2433,8 +2613,6 @@ "addCustomModelDotDotDot": "הוסף מודל מותאם אישית...", "customModelHelp": "ניתן להשתמש במודל משלך: בחר את קובץ המודל (whisper.cpp ggml '.bin') או את תיקיית המודל (תיקיית faster-whisper עם 'model.bin' בתוכה). הוא יועתק לתיקיית המודלים ויתווסף לרשימה למעלה.", "viewToolsLogFile": "הצג קובץ יומן כלים", - "reDownloadX": "הורד מחדש את {0}", - "downloadX": "הורד את {0}", "updateXTitle": "לעדכן את {0}?", "updateXMessage": "גרסה חדשה יותר של {0} זמינה.{1}{1}להוריד ולהתקין את העדכון כעת?", "downloadingSpeechToTextModel": "מוריד מודל דיבור לטקסט", @@ -2446,14 +2624,6 @@ "fixCasing": "תקן אותיות רישיות/קטנות", "addPeriods": "הוסף נקודות", "changeUnderlineToColor": "שנה קו תחתון לצבע", - "cueBuilding": "בניית כתוביות (MLX Whisper)", - "cueRebuild": "בנה מחדש כתוביות מחותמות זמן של מילים", - "cueMaxChars": "מקסימום תווים לכתובית", - "cueMaxSeconds": "מקסימום שניות לכתובית", - "cueMaxCps": "מקסימום תווים לשנייה", - "vocabularyPrompt": "הנחיית אוצר מילים (שמות, מקומות, מונחים)", - "beamSize": "גודל Beam", - "beamSizeNote": "0 מפענח הכי מהר. 5 נותן את הדיוק הטוב ביותר (כמו Faster Whisper) בתוספת זמן של כ-20-30%. ערכים מעל 5 כמעט ולא עוזרים.", "removeNonSpeechLines": "הסר שורות שאינן דיבור", "removeNonSpeechLinesHint": "השמט שורות שרק מתארות צליל, כמו \"[מוזיקה]\" או \"(גלים מתנפצים)\"", "removeRepeatedLines": "הסר שורות חוזרות", @@ -2512,7 +2682,7 @@ "cloneVoicePerLinePreparing": "לוקח את הקול של כל שורה מהוידאו...", "cloneVoicePerLineNeedsVideo": "שכפול הקול של כל שורה דורש את הוידאו שאליו שייכת הכתובית. פתח את הוידאו ונסה שוב.", "cloneVoicePerLineNoClips": "לא ניתן היה לקחת שמע מהוידאו, ולכן אין ממה לשכפל. ודא שלוידאו יש רצועת שמע.", - "cloneVoicePerLineNeedsOriginalSubtitleX": "{0} יכול לשכפל שורה רק כאשר הוא יודע מה נאמר בוידאו באותה שורה, מידע שמגיע מהכתובית בשפת המקור. לא נטענה כתובית מקורית, ולכן כל שורה תיאמר בקול המיובא הראשון במקום בקול שבוידאו.\n\nפתח את הכתובית המקורית (קובץ > פתח מקור...) לצד התרגום, ואז צור שוב.", + "cloneVoicePerLineTranscribeClipsXYZ": "{0} יכול לשכפל שורה רק כאשר ידוע מה נאמר בווידאו באותה שורה. הדבר אינו ידוע עבור {1} מתוך {2} שורות - בדרך כלל המידע מגיע מכתוביות בשפת המקור (קובץ > פתח מקור...).\n\nלתמלל שורות אלה מהווידאו באמצעות דיבור לטקסט כעת?\n\nכן: בחר את המנוע ואת השפה המדוברת בווידאו, וקטעי ההפניה יתומללו לפני יצירת הדיבור.\nלא: צור בכל זאת - שורות אלה יוקראו בקול רגיל במקום הקול שבווידאו.", "voiceCloneConsentTitle": "שכפול קול - לפני שמתחילים", "voiceCloneConsentHeader": "אתה עומד לשכפל קול", "voiceCloneConsentIntro": "שכפול מעתיק את קולו של אדם אמיתי. הדבר כרוך בכללים ברוב המקומות, ובאיחוד האירופי גם בחובות משפטיות החלות עליך, ולא על Subtitle Edit. נא לקרוא זאת פעם אחת.", @@ -2528,6 +2698,12 @@ "voiceCloneConsentDeclined": "שכפול קול אינו זמין עד לאישור תנאים אלה.", "advancedTtsAudioProcessing": "עיבוד שמע", "advancedTtsOutput": "פלט", + "advancedTtsVoiceCloning": "שכפול קול", + "cleanCloneReferences": "נקה הפניות לשכפול קול (Sidon)", + "cleanCloneReferencesDescription": "מסיר מוזיקה, רעש והדהוד מהשמע שממנו משוכפל קול: קטעים שנלקחו מהווידאו והקלטות שיובאו במנהל הקולות. משתמש במודל שחזור הדיבור Sidon ב-audio.cpp (הורדה של 940 MB). הקול המשוכפל כבר לא מעתיק את הרקע, אך דיבור שקט מאוד או חופף עלול לצאת עמום.", + "cleaningCloneReferencesDotDotDot": "מנקה הפניות לשכפול קול...", + "downloadSidonTitle": "להוריד את Sidon?", + "downloadSidonQuestionX": "ניקוי הפניות לשכפול קול דורש את מודל שחזור הדיבור Sidon ({0}).\n\nלהוריד אותו כעת? בלעדיו ההפניות ישמשו כפי שהן.", "rubberbandInstalled": "(מותקן)", "rubberbandNotFound": "(לא נמצא ב-FFmpeg)", "generationFolder": "תיקיית יצירה", @@ -2536,14 +2712,11 @@ "deleteTempFilesDescription": "מוחק את תיקיית היצירה כאשר חלון טקסט לדיבור נסגר. קובצי שמע ווידאו שנשמרו נשארים.", "piperSettings": "הגדרות Piper", "title": "טקסט לדיבור", - "textToSpeechEngine": "מנוע טקסט לדיבור", "reviewAudioSegments": "TTS - סקור מקטעי שמע", "reviewAudioSegmentsHistory": "TTS - סקור היסטוריית שמע", "stability": "יציבות", "similarity": "דמיון", "speakerBoost": "הגברת דובר", - "styleExaggeration": "הגזמת סגנון", - "regenerateAudioSelectedLine": "ייצר מחדש שמע עבור השורה שנבחרה", "generateSpeechFromText": "ייצר דיבור מטקסט", "testVoice": "בדוק קול", "addAudioToVideoFile": "הוסף שמע לקובץ וידאו", @@ -2562,22 +2735,98 @@ "videoEncodingSettings": "TTS - הגדרות קידוד וידאו", "elevenLabsSettings": "TTS - הגדרות ElevenLabs", "elevenLabsSettingsResetHint": "אפס את הגדרות ElevenLabs לערכי ברירת המחדל", + "provider": "ספק", + "openAiCompatibleSettings": "TTS - הגדרות תואמות OpenAI", + "customServerUrl": "כתובת URL של שרת מותאם אישית", + "customServerUrlHint": "נקודת הקצה המלאה לדיבור של שרת תואם OpenAI, בשימוש כאשר הספק הוא \"מותאם אישית\", לדוגמה http://localhost:8880/v1/audio/speech", + "customModels": "מודלים מותאמים אישית", + "customModelsHint": "שמות מודלים מופרדים בפסיקים המוצעים כאשר הספק הוא \"מותאם אישית\"", + "customVoices": "קולות מותאמים אישית", + "customVoicesHint": "שמות קולות מופרדים בפסיקים, בשימוש הספק \"מותאם אישית\" ומודלי OpenRouter שאינם מפרסמים רשימת קולות", + "instructions": "הוראות", + "instructionsHint": "סגנון דיבור אופציונלי, לדוגמה \"דבר בקול רגוע וחם\". נשלח רק למודלים שתומכים בכך (לא tts-1/tts-1-hd)", + "openAiSpeedHint": "מהירות דיבור מ-0.25 עד 4.0 (1.0 רגיל). לא כל מודל תומך בכך", + "outputFormat": "פורמט פלט", + "outputFormatHint": "פורמט השמע המבוקש מהשרת. \"אוטומטי\" מבקש MP3 ועובר ל-PCM עבור מודלים שמחזירים רק PCM (לדוגמה Gemini TTS)", "regenerateAudio": "ייצר שמע מחדש", "autoContinuePlaying": "המשך ניגון אוטומטית", "addingAudioToVideoFileDotDotDot": "מוסיף שמע לקובץ הוידאו...", "preparingMergeDotDotDot": "מכין מיזוג...", "importVoiceDotDotDot": "ייבוא קול...", + "renameVoiceDotDotDot": "שנה שם קול...", + "renameVoiceTitle": "שנה שם קול", + "voiceXCouldNotBeRenamedX": "לא ניתן היה לשנות את שם הקול '{0}': {1}", + "deleteVoiceDotDotDot": "מחק קול...", + "deleteVoiceTitle": "מחק קול", + "deleteVoiceXQuestion": "למחוק את הקול '{0}'?\n\nהקלטת ההפניה והקבצים שלה יוסרו מהדיסק.", + "voiceXCouldNotBeDeletedX": "לא ניתן היה למחוק את הקול '{0}': {1}", "voiceImportSuccessTitle": "הקול יובא בהצלחה", "voiceXImported": "הקול '{0}' יובא בהצלחה", "dropAudioFileHereToImportVoice": "גרור קובץ שמע לכאן כדי לייבא קול", "dropAudioFileHereHint": ".wav או .mp3", "voiceCloneTranscriptTitle": "הזן את תמלול השמע (נדרש לשכפול קול)", + "voiceManagerTitle": "TTS - מנהל קולות", + "voiceManagerDotDotDot": "מנהל קולות...", + "voiceManagerMenuItem": "מנהל קולות TTS...", + "speakFromCurrentLineTitle": "הקרא מהשורה הנוכחית", + "speakFromCurrentLineDotDotDot": "הקרא מהשורה הנוכחית...", + "speakFromCurrentLineHint": "מקריא את השורות בקול אחת אחרי השנייה, החל מהשורה הנוכחית. הקש Escape או לחץ על שורה אחרת כדי לעצור.", + "stopSpeaking": "הפסק להקריא", + "speakingLineX": "מקריא שורה {0}... (Escape לעצירה)", + "playWithSpeechTitle": "נגן עם דיבור", + "playWithSpeechDotDotDot": "נגן עם דיבור...", + "playWithSpeechHint": "מנגן את הווידאו ומקריא כל שורה כאשר הווידאו מגיע אליה. השהה ודלג כרגיל; הקש Escape כדי לעצור.", + "lowerVideoVolumeWhileSpeaking": "הנמך את עוצמת הווידאו בזמן ההקראה", + "pauseVideoWhenSpeechRunsLate": "השהה את הווידאו כאשר הדיבור גולש לשורה הבאה", + "waitingForSpeechLineX": "יוצר דיבור עבור שורה {0}... (Escape לעצירה)", + "voiceKindClone": "קול משוכפל", + "voiceKindPreset": "מובנה", + "voiceKindModel": "מודל קול", + "voiceKindOnline": "מקוון", + "transcript": "תמליל", + "transcriptRequiredHint": "נדרש - המנוע קורא תמליל זה של ההקלטה (ref-text) בעת השכפול", + "transcriptOptionalHint": "אופציונלי - תמליל מדויק של ההקלטה משפר את השכפול", + "saveTranscript": "שמור תמליל", + "searchVoices": "חפש קולות", + "loadingVoicesDotDotDot": "טוען קולות...", + "noVoicesForEngine": "אין קולות - ייבא הקלטה או הורד חבילת קולות", + "noVoicesMatchFilter": "אין קולות התואמים לחיפוש", + "copyVoiceTo": "העתק קול אל", + "copyVoiceToDotDotDot": "העתק קול למנוע...", + "copyingVoiceXToYDotDotDot": "מעתיק את הקול '{0}' אל {1}...", + "voiceXCopiedToY": "הקול '{0}' הועתק אל {1}", + "voiceXCouldNotBeCopiedToY": "לא ניתן היה להעתיק את הקול '{0}' אל {1} - ראה את היומן לפרטים", + "voiceXAlreadyExistsInYContinue": "קול בשם '{0}' כבר קיים ב-{1}.\n\nלהעתיק בכל זאת?", + "openVoicesFolder": "פתח תיקיית קולות", + "downloadVoicePacksDotDotDot": "הורד חבילות קולות...", + "downloadVoicePacksTitle": "TTS - הורדת חבילות קולות", + "voicePackIntro": "חבילות קולות הן ערכות של הקלטות קצרות ונקיות עם תמלילים, מוכנות לשכפול. כל חבילה מיובאת למנוע שנבחר (קולות שכבר קיימים מדולגים) וכוללת קובץ ATTRIBUTION.txt עם הרישיון והמקורות שלה.", + "voicePackDetailsXVoicesYSizeZLicense": "{0} קולות · {1} · {2}", + "installToEngine": "התקן במנוע", + "downloadAndInstall": "הורד והתקן", + "selectAtLeastOneVoicePack": "בחר לפחות חבילת קולות אחת", + "downloadingXDotDotDot": "מוריד את {0}...", + "installingXDotDotDot": "מתקין את {0}...", + "xVoicesInstalledYSkipped": "{0} קולות הותקנו ({1} כבר קיימים)", "useSpeechToTextDotDotDot": "השתמש בדיבור לטקסט...", "advancedTtsSettings": "הגדרות TTS מתקדמות", "proAudioPostProcessing": "עיבוד שמע מקצועי משלים", "proAudioPostProcessingDescription": "מחיל חמימות EQ, שער רעשים (noise gate), דחיסה, נרמול עוצמה (-16 LUFS), ועמעום כניסה/יציאה על כל מקטע.", + "generatingSpeechSegmentXOfY": "יוצר דיבור: מקטע {0} מתוך {1}", + "adjustingSpeedSegmentXOfY": "מתאים מהירות: מקטע {0} מתוך {1}", + "mergingAudioSegmentXOfY": "ממזג שמע: מקטע {0} מתוך {1}", + "postProcessingSegmentXOfY": "עיבוד לאחר: מקטע {0} מתוך {1}", "audioDucking": "עמעום שמע ברקע", "audioDuckingDescription": "מפחית את עוצמת השמע של הוידאו המקורי ומערבב אותו עם שמע ה-TTS, כך שעדיין ניתן לשמוע את פס הקול המקורי ברקע בעמימות.", + "removeOriginalSpeech": "הסר את הדיבור המקורי (איטי)", + "removeOriginalSpeechDescription": "שומר את המוזיקה ואפקטי הקול של הווידאו המקורי אך מסיר את הדיבור שלו, ומערבב את הדיבור החדש מעליהם. לוקח בערך כאורך הווידאו עצמו עם GPU, ופי כמה יותר בלעדיו. כאשר עמעום שמע ברקע פעיל, העוצמה שלו חלה על המוזיקה והאפקטים. חל רק כאשר הדיבור מתווסף לקובץ הווידאו.", + "removingOriginalSpeech": "מסיר את הדיבור המקורי...", + "removeOriginalSpeechFailed": "לא ניתן היה להסיר את הדיבור המקורי - הדיבור נוסף לווידאו בלי להסירו.", + "speechRemovalModel": "מודל", + "speechRemovalModelDescription": "האיכות הטובה ביותר שומרת את מירב המוזיקה ואפקטי הקול. מהיר הוא בערך פי שלושה מהיר יותר, עם מעט יותר אובדן של המוזיקה.", + "speechRemovalBestQuality": "האיכות הטובה ביותר (Mel-Band RoFormer)", + "speechRemovalFast": "מהיר (HTDemucs)", + "videoTruncatedWarning": "התמונה בקובץ הווידאו החדש נעצרת לאחר {0}, אך אורך הווידאו המקורי הוא {1} - התמונה תקפא בזמן שהקול ממשיך.\n\nהקובץ נשמר. ראה את error-log.txt בתיקיית הנתונים של Subtitle Edit לפלט של ffmpeg.", "originalVolumePercent": "עוצמת שמע מקורית %", "vadSilenceCompression": "דחיסת שתיקות VAD", "vadSilenceCompressionDescription": "מקצר הפסקות בין מילים לפני שינוי קצב. משתמש בזיהוי פעילות קולית (VAD) כדי לדחוס רק רגעי שתיקה תוך שמירה על הדיבור ללא שינוי. זהו השלב המועדף - הוא מקצר את המשך ללא אובדן איכות.", @@ -2644,7 +2893,6 @@ "subtitleMergedLinesAppliedPlural": "{0} מיזוגי שורות הוחלו מטקסט לדיבור" }, "shotChanges": { - "titleGenerateOrImport": "ייצור/ייבוא מעברי שוט", "generateShotChanges": "צור מעברי שוט", "importShotChanges": "ייבא מעברי שוט", "generateShotChangesWithFfmpeg": "צור מעברי שוט עם FFmpeg", @@ -2654,7 +2902,9 @@ "timeCodeFormatColon": "פורמט קוד זמן:", "shotChangesList": "רשימת מעברי שוט", "shotChangesClearQuestion": "האם אתה בטוח שברצונך לנקות את כל שינויי השוט?", - "deleteSelectedShotChangeQuestion": "האם אתה בטוח שברצונך למחוק את שינוי השוט שנבחר?" + "deleteSelectedShotChangeQuestion": "האם אתה בטוח שברצונך למחוק את שינוי השוט שנבחר?", + "shotChanges": "מעברי שוט", + "exportShotChanges": "ייצוא מעברי שוט" }, "chapters": { "title": "פרקים", @@ -2684,7 +2934,6 @@ "scaleTimesDescription": "המר את זמני כל הפרקים מקצב פריימים אחד לאחר.", "fromFrameRate": "מ-", "toFrameRate": "אל", - "apply": "החל", "writeToVideo": "כתוב לוידאו...", "writeToVideoTitle": "כתוב פרקים לוידאו", "writeToVideoDescription": "הפרקים נכתבים לתוך עותק של קובץ הוידאו. דבר אינו מקודד מחדש, ולכן הפעולה מהירה וללא אובדן איכות.", @@ -2694,7 +2943,6 @@ "writtenToX": "הפרקים נכתבו אל {0}", "unableToWriteChapters": "לא ניתן לכתוב פרקים לקובץ הוידאו.", "chaptersSavedToX": "הפרקים נשמרו אל {0}", - "noVideoLoaded": "לא נטען וידאו", "emptyListCallToAction": "אין עדיין פרקים - הוסף אחד במיקום הוידאו, או ייבא אותם מוידאו או מקובץ." }, "videoOcr": { @@ -2705,10 +2953,6 @@ "bottomHalf": "חצי תחתון", "fullFrame": "פריים מלא", "engine": "מנוע OCR", - "language": "שפה", - "url": "URL", - "model": "מודל", - "apiKey": "מפתח API", "scan": "סרוק", "framesPerSecond": "פריימים לשנייה", "textBrightnessMinimum": "בהירות טקסט מינימלית (0=כבוי)", @@ -2723,10 +2967,10 @@ "runningOcrXY": "מריץ OCR... {0}/{1}", "noLinesFoundTitle": "לא נמצאו כתוביות", "noLinesFoundMessage": "לא נמצא טקסט באזור הסריקה - נסה להתאים את אזור הסריקה, המנוע או סף הבהירות המינימלי.", + "ocrStoppedEarlyMessage": "מנוע ה-OCR נעצר לפני שכל הפריימים נקראו, ולכן השורות שנמצאו עד כה נשמרו והשאר חסרות.{0}{0}{1}", "linesFoundX": "נמצאו {0} שורות", "refiningTimingXY": "מדייק תזמון... {0}/{1}", "fixOcrErrors": "תקן שגיאות OCR", - "dictionary": "מילון", "editLineX": "ערוך שורה {0}", "downloadDictionaryHint": "הורד מילון לבדיקת איות...", "framesPerSecondHint": "כמה פריימים בשנייה נסרקים. 5 מספיק לכתוביות רגילות; ערך גבוה יותר קולט שורות קצרות מאוד ומהדק את הקיבוץ, אך עולה בזמן OCR רב יותר.", @@ -2743,7 +2987,58 @@ "testOcr": "בדוק פריים נוכחי", "testOcrRunning": "בודק OCR על הפריים הנוכחי...", "testOcrResultX": "תוצאת בדיקה: {0}", - "testOcrNoTextFound": "בדיקה: לא נמצא טקסט באזור הסריקה" + "testOcrNoTextFound": "בדיקה: לא נמצא טקסט באזור הסריקה", + "engineSettings": "הגדרות מנוע OCR", + "engineBuiltIn": "מובנה במערכת ההפעלה", + "engineExternalServer": "שרת חיצוני (מותקן ומנוהל מחוץ ל-Subtitle Edit)", + "engineCloudApi": "API בענן (דורש מפתח API)", + "engineNothingToInstall": "אין מה להתקין", + "engineModelsMissing": "המנוע מותקן, חסרים מודלים", + "website": "אתר אינטרנט" + }, + "backgroundMusic": { + "title": "צור מוזיקת רקע", + "generateBackgroundMusicDotDotDot": "צור מוזיקת רקע...", + "preset": "ערכה מוגדרת מראש", + "prompt": "הנחיה", + "tempoBpm": "טמפו (BPM)", + "generateSeconds": "צור (שניות)", + "seed": "זרע (Seed)", + "randomSeed": "אקראי", + "outputLength": "אורך הפלט", + "outputLengthVideoX": "{0} (אורך הווידאו - המוזיקה חוזרת בלולאה חלקה)", + "musicVolumePercent": "עוצמת המוזיקה (%)", + "removeExistingAudioTracks": "הסר רצועות שמע קיימות", + "originalAudioVolumePercent": "עוצמת השמע המקורי (%)", + "saveAudioDotDotDot": "שמור שמע...", + "addToVideoDotDotDot": "הוסף לווידאו...", + "engineInfo": "ACE-Step 1.5 Turbo באמצעות audio.cpp (רישיון MIT) - פועל באופן מקומי", + "presetCooking": "בישול / אפייה", + "presetTech": "טכנולוגיה / סקירת גאדג'טים", + "presetForest": "יער / טבע", + "presetRelaxingPiano": "פסנתר מרגיע", + "presetLofiVlog": "ולוג Lo-fi", + "presetCorporate": "תאגידי / סרטון הסבר", + "loadingModel": "טוען מודל...", + "composing": "מלחין...", + "generatingAudio": "יוצר שמע...", + "decodingAudio": "מפענח שמע...", + "creatingLoop": "יוצר לולאה חלקה...", + "resultLoopXYZ": "לולאה: {0} ב-{1} BPM, התאמת חיבור {2}%", + "resultNoLoopX": "המוזיקה מוכנה: {0}", + "addingMusicToVideo": "מוסיף מוזיקה לווידאו...", + "downloadModelTitle": "להוריד מודל מוזיקה?", + "downloadModelQuestionX": "יצירת מוזיקה דורשת את המודל ACE-Step 1.5 ({0}).\n\nלהוריד את המודל?", + "downloadingModel": "מוריד את המודל ACE-Step 1.5...", + "saveAudioTitle": "שמור מוזיקת רקע", + "audioFileSavedX": "קובץ השמע נשמר: {0}", + "unableToGenerateMusic": "לא ניתן ליצור מוזיקה", + "unableToAddMusicToVideo": "לא ניתן להוסיף מוזיקה לווידאו", + "outputLengthTextToSpeech": "עוקב אחר הדיבור שנוצר - המוזיקה חוזרת בלולאה חלקה ונחלשת בזמן שמישהו מדבר", + "addBackgroundMusic": "הוסף מוזיקת רקע", + "addBackgroundMusicHint": "צור מוזיקה באופן מקומי עם ACE-Step 1.5 ומקם אותה מתחת לדיבור - היא נחלשת בזמן שמישהו מדבר", + "generatingBackgroundMusicDotDotDot": "יוצר מוזיקת רקע...", + "mixingBackgroundMusicDotDotDot": "מערבב מוזיקת רקע..." }, "goToVideoPosition": "עבור למיקום וידאו", "goToVideoPositionDotDotDot": "עבור למיקום בוידאו...", @@ -2754,29 +3049,87 @@ "openSecondarySubtitleOnVideoPlayerDotDotDot": "כתוביות משניות בנגן הוידאו, פתח...", "openSecondarySubtitleOnVideoPlayer": "כתוביות משניות (בנגן הוידאו)", "removeSecondarySubtitleOnVideoPlayer": "כתוביות משניות בנגן הוידאו, הסר", + "editSecondarySubtitleSettings": "ערוך הגדרות כתוביות משניות", + "editSecondarySubtitleSettingsDotDotDot": "ערוך הגדרות כתוביות משניות...", + "secondarySubtitleRememberSettings": "זכור הגדרות אלה", + "secondarySubtitleDoNotShowAgain": "אל תציג חלון זה שוב", + "secondarySubtitleJustifyLines": "יישר שורות לשני הצדדים", "openRecentVideo": "פתח וידאו אחרון", "clearRecentVideos": "נקה וידאו אחרונים", "cutVideoTitle": "חיתוך וידאו", "cutVideoDotDotDot": "חיתוך וידאו...", + "remuxVideoTitle": "Remux וידאו", + "remuxVideoDotDotDot": "Remux וידאו...", + "remuxVideoInputVideo": "וידאו קלט:", + "remuxVideoMultipleTracksRequiresMkv": "מספר רצועות שמע או כתוביות דורשות את מכל MKV. פורמט הפלט הוחלף אוטומטית ל-.mkv.", + "remuxVideoOutputFormat": "פורמט פלט:", + "remuxVideoOutputFile": "קובץ פלט:", + "remuxVideoRemuxing": "מבצע Remux לווידאו ולשמע...", + "remuxVideoFinalizing": "מסיים את קובץ הפלט (מעביר את האינדקס להתחלה)...", + "remuxVideoCompleted": "ה-Remux של הווידאו הסתיים בהצלחה.", + "remuxVideoFailed": "ה-Remux של הווידאו נכשל.", + "remuxVideoPleaseSelectBoth": "נא לבחור גם קובץ וידאו וגם קובץ שמע.", + "remuxVideoAssRequiresMkv": "כתוביות ASS/SSA דורשות את מכל MKV כדי לשמר את כל העיצוב והסגנונות.", + "remuxVideoSccRequiresMov": "כתוביות Scenarist (.scc) מוטמעות ככתוביות סגורות CEA-608, הדורשות את מכל MOV או MPG. פורמט הפלט הוחלף אוטומטית ל-.mov.", + "remuxVideoSccNotWithWebM": "כתוביות סגורות Scenarist (.scc) דורשות את מכל MOV, שאינו יכול להכיל את וידאו ה-VP9/AV1 של קובץ .webm. הסר את קובץ ה-.scc או המר אותו לפורמט כתוביות אחר.", + "remuxVideoMpgMaxTwoCaptionTracks": "קובץ .mpg מכיל לכל היותר שתי רצועות כתוביות סגורות (CC1 ו-CC3) - הסר קובצי כתוביות כך שיישארו אחד או שניים.", + "remuxVideoMccRequiresMpg": "כתוביות MacCaption (.mcc) מוטמעות ככתוביות סגורות CEA-608/708 בווידאו MPEG-2, הדורש את מכל MPG. פורמט הפלט הוחלף אוטומטית ל-.mpg.", + "remuxVideoMpgReencodeVideoX": "כתוביות סגורות בקובץ .mpg דורשות וידאו MPEG-2, והווידאו הוא {0}. לקודד מחדש את הווידאו ל-MPEG-2? הפעולה לוקחת זמן רב יותר ומורידה מעט את האיכות.", + "remuxVideoFileHasNoAudioX": "ל-\"{0}\" אין רצועת שמע, ולכן לא ניתן להשתמש בו כמקור שמע.", + "remuxVideoAddingClosedCaptions": "מוסיף כתוביות סגורות...", + "remuxVideoSelectAudioTrack": "בחר רצועת שמע", + "remuxVideoSelectAudioTrackFor": "בחר רצועת שמע עבור {0}", + "remuxVideoSelectAudioTrackPrompt": "הקובץ '{0}' מכיל {1} רצועות שמע. נא לבחור את רצועת השמע ל-Remux:", + "remuxVideoVideoFile": "וידאו", + "remuxVideoAudioFile": "שמע", + "remuxVideoAudioFiles": "קובצי שמע:", + "remuxVideoSubtitleFiles": "קובצי כתוביות (אופציונלי, כתוביות רכות):", + "remuxVideoSelectAudioTrackDotDotDot": "בחר רצועת שמע...", + "remuxVideoFilesX": "{0} קבצים", + "remuxVideoMixAudio": "ערבב את השמע לרצועה אחת", + "remuxVideoVolumePercent": "עוצמה %:", + "remuxVideoVolumeX": "עוצמה {0}%", + "remuxVideoFastStart": "בצע אופטימיזציה להזרמה ברשת (העבר אינדקס להתחלה - איטי יותר)", "embedSubtitlesDotDotDot": "הוסף/הסר כתוביות מובנות...", "generateTimeCodes": "צור קודי זמן", "checkeredImage": "תמונה משובצת", - "saveVideoAsTitle": "שמור וידאו בשם", "promptForFfmpegParamsAndGenerate": "בקש פרמטרי FFmpeg וייצר", "cutVideoCutSegments": "חתוך מקטעים", "cutVideoMergeSegments": "מזג מקטעים", "cutVideoSplitSegments": "שמור כל מקטע בנפרד", - "cutVideoType": "סוג חיתוך", - "mpvRenderAuto": "אוטומטי", - "mpvRenderNative": "מקומי (Native)", - "mpvRenderOpenGl": "OpenGL", - "mpvRenderSoftware": "תוכנה (איטי)", "cutVideoAlsoCutSubtitle": "חתוך גם את הכתובית", "cutVideoSubtitleFileGeneratedX": "נוצר קובץ כתוביות: {0}", + "cutVideoTransition": "מעבר בין מקטעים", + "cutVideoTransitionDurationSeconds": "משך (שניות)", + "cutVideoFadeInSeconds": "עמעום משחור בהתחלה (שניות)", + "cutVideoFadeOutSeconds": "עמעום לשחור בסוף (שניות)", + "cutVideoPreviewTransition": "תצוגה מקדימה של המעבר", + "cutVideoPreviewTransitionTitle": "תצוגה מקדימה של מעבר", + "cutVideoPreviewNeedsJoin": "אין חיבור לתצוגה מקדימה - הוסף לפחות שני מקטעים למיזוג, או מקטע לחיתוך מהאמצע.", + "cutVideoTransitionInfoX": "כל מעבר חופף לחלקים שהוא מחבר, ולכן הפלט מתקצר ב-{0} שניות לכל חיבור. כתובית חתוכה מתוזמנת מחדש בהתאם.", + "cutVideoTransitionFade": "עמעום", + "cutVideoTransitionFadeBlack": "עמעום דרך שחור", + "cutVideoTransitionFadeWhite": "עמעום דרך לבן", + "cutVideoTransitionFadeGrays": "עמעום דרך אפור", + "cutVideoTransitionDissolve": "התמוססות", + "cutVideoTransitionWipeLeft": "מחיקה שמאלה", + "cutVideoTransitionWipeRight": "מחיקה ימינה", + "cutVideoTransitionWipeUp": "מחיקה למעלה", + "cutVideoTransitionWipeDown": "מחיקה למטה", + "cutVideoTransitionSlideLeft": "החלקה שמאלה", + "cutVideoTransitionSlideRight": "החלקה ימינה", + "cutVideoTransitionSlideUp": "החלקה למעלה", + "cutVideoTransitionSlideDown": "החלקה למטה", + "cutVideoTransitionSmoothLeft": "מעבר רך שמאלה", + "cutVideoTransitionSmoothRight": "מעבר רך ימינה", + "cutVideoTransitionCircleOpen": "פתיחת עיגול", + "cutVideoTransitionCircleClose": "סגירת עיגול", + "cutVideoTransitionRadial": "רדיאלי", + "cutVideoTransitionPixelize": "פיקסול", + "cutVideoTransitionBlur": "טשטוש", "importCurrentSubtitle": "ייבא כתובית נוכחית", "addRemoveEmbeddedSubtitlesTitle": "הוסף/הסר כתוביות מובנות", "addCurrentSubtitle": "הוסף כתובית נוכחית", - "titleOrLanguage": "כותרת/שפה", "viewMatroskaTrackX": "הצג ערוץ Matroska - {0}", "resolutionSeparator": "x", "openFromUrlTitle": "פתח קובץ וידאו מכתובת URL", @@ -2786,9 +3139,7 @@ "openFromUrlDownloadAndOpen": "הורד ופתח", "openFromUrlDownloadAndOpenDescription": "שמור את הוידאו באופן מקומי, ואז פתח אותו.", "openFromUrlDownloadAndOpenNote": "נדרש עבור דיבור לטקסט ונקודות גל שמע.", - "openFromUrlMissing": "Please enter a video URL first.", "openFromUrlDownloadingTitle": "מוריד וידאו", - "openFromUrlSaveAs": "שמור וידאו בשם", "openFromUrlDownloadSubtitles": "הורד גם כתוביות", "openFromUrlDownloadVideo": "הורד וידאו", "openFromUrlIncludeAutoGeneratedSubtitles": "כלול כתוביות שנוצרו אוטומטית (שפת המקור)", @@ -2825,8 +3176,11 @@ "reEncodeGeneratingDone": "הייצור הושלם", "reEncodeGeneratedFilesX": "קבצים שנוצרו ({0}):", "reEncodeFfmpegParameters": "פרמטרי FFmpeg", + "analyzingVideoX": "מנתח וידאו... {0}% {1}", + "analyzingVideoXofY": "מנתח וידאו {0}/{1}... {2}% {3}", + "backgroundImageFileNotSelected": "לא נבחר קובץ תמונת רקע", + "backgroundImageFileDoesNotExistX": "קובץ תמונת הרקע אינו קיים: {0}", "addRemoveEmbeddedSubtitlesMp4Title": "הוסף/הסר כתוביות מובנות (MP4)", - "embeddedTrackColumnNew": "חדש", "embeddedTrackNoSubtitlesFoundTitle": "לא נמצאו כתוביות", "embeddedTrackNoSubtitlesFoundMessage": "קובץ הכתוביות שנבחר אינו מכיל כתוביות.", "embeddedTrackNoTracksTitle": "לא נוספו רצועות", @@ -2838,12 +3192,12 @@ "embeddedTrackGeneratingVideoXY": "יוצר וידאו... {0}% {1}", "embeddedTrackGeneratingVideo": "יוצר וידאו...", "mp4FilesFilter": "קובצי MP4", + "embeddedTracksPickVideoHint": "בחר קובץ וידאו, או גרור אחד לכאן, כדי לראות את רצועות הכתוביות שלו.", "voices": "קולות", "presets": "ערכות מוגדרות מראש", "talkerX": "דובר {0}", "baseModel": "מודל בסיס", "turboModel": "מודל טורבו", - "ttsUpdateX": "עדכן את {0}", "ttsEngineNotInstalled": "{0} אינו מותקן", "ttsEngineUpdateAvailable": "{0} - קיים עדכון", "ttsEngineTooOldUpdateRequired": "{0} ישן מדי - נדרש עדכון", @@ -2866,19 +3220,25 @@ "cloneVoiceTo": "שכפל קול אל", "cloneVoiceNameTitle": "שם הקול החדש", "cloneVoiceExtractFailed": "לא ניתן היה לחלץ שמע עבור השורה שנבחרה.", + "lineXHasNoDuration": "לשורה {0} אין משך (זמן הסיום אינו אחרי זמן ההתחלה), ולכן אין שמע לחלץ.", + "audioClipsSkippedLinesX": "לא ניתן היה לחלץ שמע עבור שורות אלה, ולכן הן דולגו: {0}", "toggleShotChange": "החלף מעבר שוט", "resetWaveformZoomAndSpeed": "אפס זום ומהירות של גל השמע", "showOriginalSubtitle": "הצג כתובית מקורית", + "timelineGroupTracksBy": "קבץ רצועות כתוביות לפי", + "timelineShowThumbnails": "הצג תמונות ממוזערות של הווידאו", + "showSpeechOnly": "הצג דיבור בלבד (יצירה איטית)", + "isolatingSpeechForWaveformX": "מבודד דיבור עבור גל השמע... {0}", + "isolatingSpeechForWaveformFailed": "לא ניתן היה לבודד את הדיבור עבור גל השמע - ראה את יומן הכלים", + "speechOnlyWaveformReady": "גל השמע מציג כעת דיבור בלבד", "showOnlyWaveform": "הצג רק גל שמע", "showOnlySpectrogram": "הצג רק ספקטרוגרמה", "showWaveformAndSpectrogram": "הצג גל שמע וספקטרוגרמה", - "spectrogramClassic": "קלאסי", "spectrogramClassicViridis": "Viridis", "spectrogramClassicPlasma": "Plasma", "spectrogramClassicInferno": "Inferno", "spectrogramClassicTurbo": "Turbo", "spectrogramNeon": "ניאון", - "waveformDrawStyleClassic": "קלאסי", "waveformDrawStyleFancy": "Fancy", "setVideoPositionAndPauseAndSelectSubtitle": "קבע מיקום וידאו, השהה ובחר כתובית", "setVideopositionAndPauseAndSelectSubtitleAndCenter": "קבע מיקום וידאו, השהה, בחר כתובית ומרכז", @@ -2888,7 +3248,6 @@ }, "sync": { "visualSync": "סנכרון חזותי", - "sync": "סנכרון", "startScene": "סצנת התחלה", "endScene": "סצנת סיום", "playTwoSecondsAndBack": "נגן 2 שניות וחזור", @@ -2904,19 +3263,18 @@ "adjustSelectedLinesAndForward": "התאם שורות שנבחרו והלאה", "toFrameRate": "לקצב פריימים", "fromFrameRate": "מקצב פריימים", + "videoXFrameRateY": "{0} - {1} fps", "adjustAllTimes": "התאם את כל הזמנים (הצג מוקדם/מאוחר יותר)", "showEarlier": "הצג מוקדם יותר", "showLater": "הצג מאוחר יותר", - "changeFrameRate": "שנה קצב פריימים", "setSyncPoint": "קבע נקודת סנכרון", "setSyncPointViaVideo": "קבע נקודת סנכרון באמצעות הוידאו...", "syncPointTimeCode": "קוד זמן של נקודת הסנכרון", "syncPoints": "נקודות סנכרון", "pointSync": "סנכרון נקודתי", "pointSyncViaOther": "סנכרון נקודתי באמצעות כתובית אחרת", - "adjustmentX": "התאמה: {0}", - "totalAdjustmentX": "התאמה כוללת: {0}", - "syncPointCandidateInfo": "שורות המתחילות לאחר {0}+ שניות של שתיקה מודגשות - לרוב הן נקודות סנכרון אמינות", + "syncPointCandidateAfterInfo": "שורות שאחריהן {0}+ שניות של שקט מודגשות - לרוב הן נקודות סנכרון אמינות", + "gapAfter": "מרווח אחרי", "adjustAllShortcuts": "קיצורי מקשים:\r\n\r\n• Shift + חץ שמאלה/ימינה: הזזה ב-10 מילישניות\r\n• Ctrl + חץ שמאלה/ימינה: הזזה ב-100 מילישניות\r\n• Alt + חץ שמאלה/ימינה: הזזה ב-500 מילישניות", "adjustAllShortcutsFrames": "קיצורי מקלדת:\r\n\r\n• Shift + שמאלה/ימינה: הזז פריים אחד\r\n• Ctrl + שמאלה/ימינה: הזז 10 פריימים\r\n• Alt + שמאלה/ימינה: הזז שנייה אחת", "offsetInSeconds": "היסט בשניות", @@ -2936,6 +3294,7 @@ "lineMerge": "מיזוג שורות", "delayInSecondsBetweenRequests": "השהיה בשניות בין בקשות", "maxBytesPerRequest": "מקסימום בייטים לבקשה", + "keepMusicLinesUntranslated": "אל תתרגם שורות בתוך סמלי מוזיקה (♪ ... ♪)", "promptText": "הנחיית טקסט", "resetPromptToDefault": "אפס את ההנחיה לברירת המחדל", "translateEachLineSeparately": "תרגם כל שורה בנפרד", @@ -2949,11 +3308,10 @@ "readyToTranslate": "מוכן לתרגום", "translating": "מתרגם...", "translationComplete": "התרגום הושלם", + "translateInPlaceNoOriginal": "תרגם במקום (אל תיצור מקור)", "translationCancelled": "התרגום בוטל", "swapLanguages": "החלף שפות מקור ויעד", "xIsAlreadyDownloadedReDownload": "{0} כבר הורד. האם להוריד מחדש?", - "advancedDotDotDot": "מתקדם...", - "advancedSettings": "הגדרות מתקדמות", "advancedSettingsSubtitle": "אפשרויות אצווה, הקשר ודגימה עבור מנוע llama.cpp המתקדם", "context": "הקשר", "synopsis": "תקציר", @@ -2977,6 +3335,7 @@ "useOnlyExtraServerParametersHint": "הפעל את llama-server עם הפרמטרים שלמעלה במקום הכוונון של Subtitle Edit - רק המודל, המארח והפורט עדיין נקבעים", "serverRunningAtX": "השרת פועל בכתובת {0}", "customPromptHint": "הוראות מותאמות אישית ({0} = שפת המקור, {1} = שפת היעד); ריק = הנחיה מובנית", + "editTranslationHint": "לחץ על התרגום של השורה שנבחרה כדי לערוך אותו (Enter שומר, Escape מבטל)", "llamaCppDownloadEngineAndModelPrompt": "llama.cpp דורש הורדה של מנוע llama-server ושל מודל תרגום. להוריד כעת?", "llamaCppDownloadEnginePrompt": "llama.cpp דורש הורדה של מנוע llama-server. להוריד כעת?", "llamaCppDownloadModelPrompt": "llama.cpp דורש הורדה של מודל התרגום שנבחר. להוריד כעת?" @@ -3014,8 +3373,14 @@ "checkForUpdatesChannelStableAndBeta": "גרסאות יציבות וגרסאות בטא", "assaAutoSetResolution": "ASSA: קבע רזולוציה (PlayResX/PlayResY) מהוידאו כאשר נפתח וידאו", "assaAutoSetResolutionPrompt": "ASSA: שאל לפני שינוי הרזולוציה להתאמה לוידאו (אחרת המר אוטומטית)", + "videoControls": "פקדי וידאו", + "videoControlsEdit": "ערוך פקדי וידאו...", + "videoControlsPositionText": "טקסט מיקום / משך", + "videoControlsVideoFileName": "שם קובץ הווידאו", + "videoControlsPlayerName": "שם נגן הווידאו", "mergeKeepEndTime": "מיזוג שורות: שמור על זמן הסיום (אפשר חפיפה עם הכתובית הבאה)", "mergeKeepEndTimeOnlyAssa": "מיזוג שורות: שמור על זמן הסיום רק עבור \"Advanced Sub Station Alpha\"", + "mergeAutoBreak": "מיזוג שורות: פצל אוטומטית את הטקסט הממוזג (כבוי שומר את מעברי השורה המקוריים)", "defaultSaveLocation": "מיקום שמירה ברירת מחדל", "defaultSaveLocationSourceFileFolder": "תיקיית קובץ הכתוביות/הוידאו", "defaultSaveLocationLastUsedFolder": "התיקייה האחרונה שהייתה בשימוש", @@ -3023,6 +3388,7 @@ "defaultSaveLocationSubtitleFileFolder": "תיקיית קובץ הכתוביות", "defaultSaveLocationCustomFolder": "תיקייה מותאמת אישית", "subtitleGridFormattingHideTags": "הסתר תגים", + "subtitleGridFormattingShowFormattingKeepTags": "הצג עיצוב, שמור תגיות שאינן חזותיות", "musicSymbolsToReplace": "סמלי מוזיקה להחלפה (מופרדים בפסיק)", "musicSymbol": "סמל מוזיקלי", "colorWordsPerMinute": "צבע מילים/דקה אם גבוה מדי", @@ -3033,7 +3399,6 @@ "dialogStyleDashBothLinesWithSpace": "מקף בשתי השורות עם רווח", "dialogStyleDashBothLinesWithoutSpace": "מקף בשתי השורות ללא רווח", "continuationStyle": "סגנון המשך שורה", - "continuationStyleNone": "ללא", "continuationStyleNoneTrailingDots": "ללא, נקודות להפסקות (בסוף בלבד)", "continuationStyleNoneLeadingTrailingDots": "ללא, נקודות להפסקות", "continuationStyleNoneTrailingEllipsis": "ללא, שלוש נקודות (...) להפסקות (בסוף בלבד)", @@ -3044,7 +3409,6 @@ "continuationStyleLeadingTrailingEllipsis": "שלוש נקודות (...)", "continuationStyleLeadingTrailingDash": "מקף", "continuationStyleLeadingTrailingDashDots": "מקף, אך נקודות להפסקות", - "continuationStyleCustom": "מותאם אישית", "cpsLineLengthStyle": "קצב תווים לשנייה/אורך שורה", "cpsLineLengthStyleCalcAll": "ספור את כל התווים", "cpsLineLengthStyleCalcNoSpaceCpsOnly": "ספור הכל למעט רווחים, תווים לשנייה בלבד", @@ -3057,27 +3421,11 @@ "cpsLineLengthStyleCalcNoSpaceOrPunctuationCpsOnly": "ללא רווחים או סימני פיסוק, CPS בלבד", "cpsLineLengthStyleCalcIgnoreArabicDiacritics": "התעלם מסימני ניקוד ערביים", "cpsLineLengthStyleCalcIgnoreArabicDiacriticsNoSpace": "התעלם מסימני ניקוד ערביים, ללא רווח", - "timeCodeModeHhMmSsMs": "שעות:דקות:שניות:מילישניות", - "timeCodeModeHhMmSsFf": "שעות:דקות:שניות:פריימים", - "splitBehaviorPrevious": "הוסף מרווח משמאל לנקודת הפיצול (מיקוד ימין)", - "splitBehaviorHalf": "הוסף מרווח במרכז נקודת הפיצול (מיקוד שמאל)", - "splitBehaviorNext": "הוסף מרווח מימין לנקודת הפיצול (מיקוד שמאל)", - "subtitleListActionNothing": "אל תעשה כלום", - "subtitleListActionVideoGoToPositionAndPause": "עבור למיקום הוידאו והשהה", - "subtitleListActionVideoGoToPositionAndPlay": "עבור למיקום הוידאו ונגן", "subtitleListActionVideoGoToPositionAndPlayCurrentAndPause": "עבור למיקום הוידאו, נגן את הכתובית הנוכחית והשהה", - "subtitleListActionEditText": "עבור לתיבת עריכת הטקסט", "subtitleListActionVideoGoToPositionMinus1SecAndPause": "עבור למיקום הוידאו פחות שנייה אחת והשהה", "subtitleListActionVideoGoToPositionMinusHalfSecAndPause": "עבור למיקום הוידאו פחות חצי שנייה והשהה", "subtitleListActionVideoGoToPositionMinus1SecAndPlay": "עבור למיקום הוידאו פחות שנייה אחת ונגן", - "subtitleListActionEditTextAndPause": "עבור לתיבת עריכת הטקסט, והשהה במיקום הוידאו", - "autoBackupEveryMinute": "כל דקה", - "autoBackupEveryXthMinute": "כל {0} דקות", - "profiles": "פרופילים", - "autoBackupDeleteAfterXMonths": "מחק גיבויים אוטומטיים לאחר {0} חודשים", "searchSettingsDotDotDot": "חפש בהגדרות...", - "syntaxColoring": "צביעת תחביר", - "waveformSpectrogram": "גל שמע/ספקטרוגרמה", "network": "רשת", "fileTypeAssociations": "שיוך סוגי קבצים", "textBoxColorTags": "תגיות צבע (HTML/ASSA) בתיבת הטקסט של הכתובית", @@ -3110,10 +3458,9 @@ "darkThemeForegroundColor": "צבע גופן בערכת נושא כהה", "darkThemeBackgroundColor": "צבע רקע בערכת נושא כהה", "showGridLines": "הצג קווי רשת בטבלה", - "resetSettings": "לאפס את ההגדרות?", - "resetSettingsDetail": "פעולה זו תאפס את כל ההגדרות לערכי ברירת המחדל שלהן.\n\nהאם להמשיך?", "showHorizontalLineAboveToolbar": "הצג קו אופקי מעל סרגל הכלים", "bookmarkColor": "צבע סימנייה", + "spellCheckHighlightColor": "צבע הדגשה של בדיקת איות", "singleLineMaxLength": "אורך מקסימלי לשורה בודדת", "optimalCharsPerSec": "קצב תווים אופטימלי לשנייה", "maxCharsPerSec": "קצב תווים מקסימלי לשנייה", @@ -3125,13 +3472,21 @@ "unbreakSubtitlesShortThan": "בטל שבירת שורות בכתוביות קצרות מ-", "newEmptyDefaultMs": "משך ברירת מחדל לכתובית חדשה (מילישניות)", "timeCodeUpDownStepMs": "צעד הגדלה/הקטנה של זמן (ms)", + "moveSelectedLinesStepMs": "צעד קיצור הדרך להזזת שורות שנבחרו (ms)", + "moveStartEndStepMs": "צעד קיצור הדרך להזזת התחלה/סיום (ms)", + "moveLinesShortenNeighbor": "הזזת שורות: קצר את השורה הקודמת/הבאה במקום לחפוף לה", "promptBeforeDelete": "שאל לפני מחיקה", "rememberPositionAndSize": "זכור את מיקום וגודל החלון", + "titleBarFullFileName": "הצג נתיב קובץ מלא בשורת הכותרת", + "showRecentFiles": "הצג קבצים אחרונים (לפתיחה מחדש)", "openLastFileOnStart": "פתח את הקובץ האחרון שהיה בשימוש בעת ההפעלה", "autoSave": "שמירה אוטומטית (שמור את הקובץ הפתוח בזמן עריכה)", "autoBackupOn": "גיבוי אוטומטי", "autoBackupIntervalMinutes": "תדירות גיבוי אוטומטי (בדקות)", "autoBackupDeleteAfterDays": "משך שמירת גיבוי אוטומטי (בימים)", + "settingsBackupOn": "גיבוי אוטומטי של הגדרות", + "settingsBackupIntervalDays": "מרווח גיבוי הגדרות (ימים, 0 = בכל הפעלה)", + "settingsBackupMaxCount": "גיבויי הגדרות לשמירה", "autoConvertToUtf8": "המר אוטומטית לקידוד UTF-8 בעת פתיחה", "autoTrimWhiteSpace": "הסר אוטומטית רווחים מיותרים", "defaultEncoding": "קידוד ברירת מחדל", @@ -3145,23 +3500,14 @@ "colorGapTooShort": "צבע אם המרווח קצר מדי", "errorBackgroundColor": "צבע רקע של שגיאה", "waveformDrawGridLines": "צייר קווי רשת בגל השמע", - "waveformCenterVideoPosition": "מרכז את מיקום הוידאו", + "waveformUseSkiaRenderer": "השתמש במעבד תצוגה מהיר ניסיוני", "waveformCenterVideoPositionAlsoWhenPaused": "מרכז את מיקום הוידאו גם בזמן השהיה", + "waveformSelectCurrentSubtitleWhilePaused": "בחר את הכתובית הנוכחית גם בזמן השהיה", + "waveformRememberZoom": "זכור זום (אופקי ואנכי)", "waveformShowToolbar": "הצג סרגל כלים", "waveformShowToolbarEdit": "ערוך סרגל כלים...", "waveformShowToolbarEditLabel": "פריטי סרגל כלים", "waveformSpectrogramCombinedWaveformHeight": "גובה גל השמע בשילוב גל שמע/ספקטרוגרמה %", - "showWaveformToolbarPlay": "סרגל כלים: הצג כפתור ניגון", - "showWaveformToolbarRepeat": "סרגל כלים: הצג כפתור חזרה", - "showWaveformToolbarRemoveBlankLines": "סרגל כלים: הצג כפתור הסרת שורות ריקות", - "showWaveformToolbarNew": "סרגל כלים: הצג כפתור כתובית חדשה", - "showWaveformToolbarSetStart": "סרגל כלים: הצג כפתור קביעת התחלה", - "showWaveformToolbarSetEnd": "סרגל כלים: הצג כפתור קביעת סיום", - "showWaveformToolbarStartAndOffsetTheRest": "סרגל כלים: הצג כפתור קביעת התחלה והזזת השאר", - "showWaveformToolbarHorizontalZoom": "סרגל כלים: הצג זום אופקי", - "showWaveformToolbarVerticalZoom": "סרגל כלים: הצג זום אנכי", - "showWaveformToolbarVideoPositionSlider": "סרגל כלים: הצג מחוון מיקום וידאו", - "showWaveformToolbarPlaybackSpeed": "סרגל כלים: הצג מהירות ניגון", "waveformFocusTextboxAfterInsertNew": "התמקד בתיבת הטקסט לאחר הוספת כתובית", "waveformInvertMouseWheel": "הפוך את כיוון גלגלת העכבר", "waveformMouseWheelSetsVideoPosition": "גלגלת העכבר קובעת מיקום וידאו", @@ -3179,20 +3525,20 @@ "waveformSelectedColor": "צבע של כתובית נבחרת בגל שמע", "downloadFfmpeg": "הורד את FFmpeg", "proxyAddress": "כתובת שרת מתווך (Proxy)", - "username": "שם משתמש", "password": "סיסמה", "defaultFormat": "פורמט ברירת מחדל", "defaultSaveAsFormat": "פורמט ברירת מחדל ל-\"שמירה בשם\"", "favoriteSubtitleFormats": "פורמטי כתוביות מועדפים", "favoriteLanguages": "שפות מועדפות", "webVttUseXTimestampMap": "WebVTT: השתמש ב-X-TIMESTAMP-MAP (הסט קודי זמן בטעינה)", - "showStopButton": "הצג כפתור עצירה", - "showFullscreenButton": "הצג כפתור מסך מלא", "fullscreenHideControls": "הסתר פקדי וידאו במסך מלא", + "showSecondarySubtitleDialog": "הצג חלון סגנון בעת פתיחת קובץ כתוביות משני", + "rememberSecondarySubtitleFile": "זכור קובץ כתוביות משני", "autoOpenVideoFile": "פתח אוטומטית קובץ וידאו בעת פתיחת כתובית", "downloadMpv": "הורד את mpv", "downloadVlc": "הורד את VLC", "allowSingleLetterShortcutsInTextbox": "אפשר קיצורי מקשים של אות בודדת בתיבת הטקסט", + "allowTextNavigationShortcutsInTextbox": "אפשר קיצורי דרך על מקשי ניווט בטקסט (Ctrl+שמאלה/ימינה, Home/End) בתיבת הטקסט", "spellCheckEnglishTreatInApostropheAsIng": "בדיקת איות: התייחס למילים המסתיימות ב-'in'' כאל 'ing' (אנגלית בלבד)", "goToLineNumberSetsVideoPosition": "מעבר למספר שורה קובע גם את מיקום הוידאו", "adjustAllTimesRememberLineSelectionChoice": "התאמת כל הזמנים, זכור את הבחירה לגבי שורות", @@ -3204,6 +3550,7 @@ "showAssaLayer": "הצג תיבת שכבות ASSA", "waveformCursorColor": "צבע סמן/ראש גל שמע", "waveformShotChangeColor": "צבע מעברי שוט בגל שמע", + "waveformGridColor": "צבע רשת גל השמע", "waveformParagraphLeftColor": "צבע גבול שמאלי בגל שמע", "waveformParagraphRightColor": "צבע גבול ימני בגל שמע", "waveformFancyHighColor": "צבע הדגשה מעוצב בגל שמע", @@ -3231,23 +3578,26 @@ "resetSyntaxColoring": "אפס צביעת תחביר", "resetWaveform": "אפס גל שמע", "resetRules": "אפס כללים", - "useFrameMode": "השתמש במצב פריימים (hh.mm.ss.ff)", + "timeCodeMode": "מצב קוד זמן", + "timeCodeModeTime": "זמן (hh:mm:ss,zzz)", + "timeCodeModeFrames": "פריימים (hh:mm:ss:ff)", + "timeCodeModeFrameNumbers": "מספרי פריימים", + "frameModeSnapToVideoFrames": "שמור זמנים על פריימי וידאו (לא פריימים בתוך כל שנייה)", "textBoxLimitNewLines": "הגבל את מספר השורות בתיבת הטקסט של הכתובית", - "mpvVideoOutput": "פלט וידאו (mpv)", "mpvOpenGl": "libmpv - OpenGL", "mpvSoftwareRendering": "libmpv - עיבוד תוכנתי (איטי)", + "mpvMetalRendering": "libmpv - Metal (ניסיוני, דורש הפעלה מחדש)", "mpvWidRendering": "libmpv - עיבוד חלון מקומי (מהיר יותר ב-Windows/Linux)", "waveFormsAndSpectrogramFoldersContainsX": "תיקיות \"גל שמע\" ו-\"ספקטרוגרמה\" מכילות {0}", "deleteWaveformAndSpectrogramFoldersQuestion": "האם למחוק את קובצי \"גל שמע\" ו-\"ספקטרוגרמה\"?", "waveformGenerateSpectrogram": "צור ספקטרוגרמה", "waveformAutoGenerate": "צור גל שמע אוטומטית בעת פתיחת וידאו", "waveformSpectrogramMode": "מצב ספקטרוגרמה", - "waveformCenterOnSingleClick": "מרכז בלחיצה בודדת", - "waveformSingleClickSelectsSubtitle": "בחר כתובית בלחיצה בודדת", "waveformRightClickSelectsSubtitle": "בחר כתובית בלחיצה ימנית", - "waveformPauseOnSingleClick": "השהה בלחיצה בודדת", "waveformDrawStyle": "סגנון ציור גל שמע", "vlcWidRendering": "libVLC - עיבוד חלון מקומי", + "ffmpegSoftwareRendering": "FFmpeg", + "downloadFfmpegLibs": "הורד ספריות FFmpeg (עבור נגן הווידאו FFmpeg)", "subtitleGridEnterKeyAction": "פעולת מקש Enter בטבלת הכתוביות", "subtitleSingleClickAction": "פעולת לחיצה בודדת בטבלת הכתוביות", "subtitleDoubleClickAction": "פעולת לחיצה כפולה בטבלת הכתוביות", @@ -3272,24 +3622,24 @@ "saveAsBehaviorUseVideoFileName": "השתמש בשם קובץ וידאו", "saveAsBehaviorUseSubtitleFileName": "השתמש בשם קובץ כתובית", "saveAsAppendLanguageCodeTwoLetter": "שתי אותיות", - "saveAsAppendLanguageCodeThreeLetter": "שלוש אותיות (ISO 639-2/T)", - "saveAsAppendLanguageCodeThreeLetterBibliographic": "שלוש אותיות (ISO 639-2/B)", "saveAsAppendLanguageCodeLanguageName": "שם השפה", "splitOddLineActionWeightTop": "העדף את השורה העליונה", "splitOddLineActionWeightBottom": "העדף את השורה התחתונה", "splitOddLinesAction": "פעולה לפיצול שורות לא זוגיות", "ocrUseWordSplitList": "OCR: השתמש ברשימת פיצול מילים", "ocrGuessUnknownWords": "OCR: נסה לנחש מילים לא מוכרות", + "spellCheckRememberUseAlwaysList": "בדיקת איות: זכור רשימת \"השתמש תמיד\"", + "fixShortDisplayTimesAllowMoveStartTime": "תיקון שגיאות נפוצות: תיקון זמני תצוגה קצרים רשאי להזיז את זמן ההתחלה", "speechToTextSelectedLinesPromptFirstTimeOnly": "דיבור לטקסט: שורות שנבחרו, שאל לגבי שפה/מנוע בפעם הראשונה בלבד", "multipleReplaceShowDotDotDotButtons": "החלפה מרובה: הצג כפתורים תלויי הקשר", "gridFocusTextboxAfterInsertNew": "טבלה: התמקד בתיבת הטקסט לאחר הוספת כתובית חדשה", + "undoRedoGoToChangedLine": "בטל/בצע שוב: עבור לשורה שהשתנתה (ולמיקום הווידאו)", "textToSpeechPromptSkipNoiseLines": "טקסט לדיבור: הצע לדלג על שורות צליל/מוזיקה", "textToSpeechPromptDetectSpeakers": "טקסט לדיבור: הצע לזהות שמות דוברים בטקסט", "useFocusedButtonBackgroundColor": "השתמש בצבע רקע לכפתור ממוקד", "focusedButtonBackgroundColor": "צבע רקע לכפתור ממוקד", "forceCrLfOnSave": "אלץ סיומת שורה CR+LF בעת שמירה (קובצי כתוביות טקסט)", - "showWaveformToolbarPlayNext": "סרגל כלים: הצג כפתור נגן את הבא", - "showWaveformToolbarPlaySelection": "סרגל כלים: הצג כפתור נגן את הבחירה", + "linuxClipboardUseExternalTool": "העתק טקסט באמצעות xclip/wl-copy (שומר תווים עם סימני הטעמה)", "showFormatLimitWarning": "הזהר בעת שמירה כאשר שורות חורגות ממגבלות הפורמט (למשל SCC 32 תווים לשורה)", "textBoxButtonShowAutoBreak": "תיבת טקסט: הצג כפתור פיצול שורות אוטומטי", "textBoxButtonShowUnbreak": "תיבת טקסט: הצג כפתור ביטול פיצול שורות", @@ -3301,12 +3651,18 @@ "waveformDoubleClickAction": "פעולת לחיצה כפולה בגל שמע (לאחר לחיצה בודדת)", "allSettings": "כל ההגדרות", "uiScale": "קנה מידה של ממשק המשתמש (%)", + "fontScale": "קנה מידה של גופן (%)", + "textSelectionOpacity": "אטימות בחירת טקסט (%)", "waveformToolbarItems": "פריטי סרגל הכלים של גל שמע", "matchIconColorToDarkTheme": "התאם צבע סמל לצבע הגופן של ערכת נושא כהה", "subtitlePreviewProperties": "מאפייני תצוגה מקדימה של כתובית", "usePositionFromSubtitleFile": "השתמש במיקום מקובץ הכתוביות (TTML/PAC/EBU STL)", "marginIsPartOfSubtitleArea": "השוליים הם חלק מאזור הכתוביות", "textJustify": "יישר שורות לשני הצדדים", + "deinterlace": "בטל שזירה (Deinterlace) בווידאו", + "deinterlaceOff": "כבוי", + "deinterlaceAuto": "אוטומטי (פריימים שזורים בלבד)", + "deinterlaceAlways": "תמיד", "pixelWidthInfo": "קווים ירוקים = גבול רוחב מקסימלי | אזור אדום = הטקסט חורג מהגבול", "spellCheckEngineHunSpelll": "Hunspell", "spellCheckEngineMsWord": "MS Word", @@ -3316,6 +3672,15 @@ "waveformSaveAsCustomTheme": "שמור כערכת נושא מותאמת אישית...", "waveformLoadThemeDotDotDot": "טען ערכת נושא...", "waveformExportThemeDotDotDot": "ייצא ערכת נושא...", + "waveformSaveCustomThemeTitle": "שמור ערכת נושא מותאמת אישית", + "waveformThemeFileType": "ערכת נושא לגל שמע של Subtitle Edit", + "waveformThemeHighContrast": "ניגודיות גבוהה", + "waveformThemeOceanBlue": "כחול אוקיינוס", + "waveformThemeWarmSunset": "שקיעה חמימה", + "waveformThemeForest": "יער", + "waveformThemeMidnightPurple": "סגול חצות", + "waveformThemeRetroAmber": "ענבר רטרו", + "waveformThemeIce": "קרח", "minGapFrames": "מרווח מינימלי (פריימים)", "waveformSnapToFrames": "הצמד לפריימים", "fixCommonErrorsSkipStep1": "תיקון שגיאות נפוצות: דלג על שלב 1 (בחירת תיקונים)", @@ -3324,118 +3689,82 @@ "waveformExtractAudioSampleRate": "קצב דגימה לחילוץ שמע", "waveformExtractAudioSampleRateOriginal": "מקורי (שמור על המקור)", "waveformExtractAudioBitRate": "קצב סיביות לחילוץ שמע (MP3/M4A)", + "ffmpegUseCenterChannelOnly": "השתמש רק בערוץ המרכזי הקדמי עבור שמע 5.1 (FFmpeg)", "waveformSetVideoPositionOnMoveStartEnd": "קבע מיקום וידאו בעת הזזת התחלה/סיום", "textToSpeechPromptMergeContinuationLines": "טקסט לדיבור: בקש למזג שורות המשך" }, "shortcuts": { "generalUnbreakNoSpaceCjk": "בטל מעבר שורה ללא רווח (CJK)", - "title": "קיצורי דרך", + "generalToggleBreak": "החלף פיצול/ביטול פיצול", "searchShortcuts": "חפש קיצורי דרך...", - "filter": "מסנן", - "categoryGeneral": "כללי", "categorySubtitleGridAndTextBox": "טבלת כתוביות ותיבת טקסט", "categorySubtitleGrid": "טבלת כתוביות", - "categoryWaveform": "גל שמע", "categoryTextBox": "תיבת טקסט", "categoryFile": "קובץ", "categoryVideo": "וידאו", "categorySync": "סנכרון", - "categoryTranslate": "תרגום", - "categorySearch": "חיפוש", - "categoryTools": "כלים", "categoryAi": "AI", + "categoryCustom": "מותאם אישית", + "editCustomShortcut": "ערוך קיצור דרך מותאם אישית", + "customShortcutTextBoxKeyHint": "השתמש כאן במקש עם Ctrl/Alt - מקש רגיל כבר לא יקליד בתיבת הטקסט.", + "customShortcutNumberX": "קיצור דרך מותאם אישית #{0}", + "customShortcutNumberXY": "קיצור דרך מותאם אישית #{0}: {1}", + "customShortcutStep": "שלב", + "customShortcutSteps": "שלבים", + "customShortcutStepRunCommand": "הפעל פקודה", + "customShortcutStepInsertText": "הכנס טקסט", + "customShortcutStepReplace": "חפש והחלף", + "customShortcutInsertAtCursor": "במיקום הסמן", + "customShortcutInsertAtStart": "בתחילת הטקסט", + "customShortcutInsertAtEnd": "בסוף הטקסט", + "customShortcutSummaryRunCommandX": "הפעל: {0}", + "customShortcutSummaryInsertXY": "הכנס \"{0}\" ({1})", + "customShortcutSummaryReplaceXY": "החלף את \"{0}\" ב-\"{1}\"", + "customShortcutSummaryReplaceRegexXY": "החלף ביטוי רגולרי \"{0}\" ב-\"{1}\"", + "customShortcutPickCommand": "נא לבחור פקודה להפעלה.", + "customShortcutEnterText": "נא להזין את הטקסט להכנסה.", + "customShortcutEnterFind": "נא להזין את הטקסט לחיפוש.", + "customShortcutInvalidRegexX": "ביטוי רגולרי לא חוקי: {0}", + "customShortcutStepsHint": "השלבים מופעלים מלמעלה למטה. שלבי טקסט פועלים על השורות שנבחרו; פקודות שפותחות חלון ממשיכות לאחר שהוא נסגר.", "activeIn": "פעיל ב-", "activeInEverywhere": "בכל מקום", - "generalSetupLikeSe4": "הגדר כמו Subtitle Edit 4 (ערכת נושא, קיצורי דרך, כללי החלפה)", - "generalMergeSelectedLines": "מזג שורות שנבחרו", - "generalMergeWithPrevious": "מזג עם הקודם", - "generalMergeWithNext": "מזג עם הבא", - "generalMergeWithPreviousAndUnbreak": "מזג עם הקודם ובטל פיצול", - "generalMergeWithNextAndUnbreak": "מזג עם הבא ובטל פיצול", - "generalMergeWithPreviousAndAutoBreak": "מזג עם הקודם ופצל אוטומטית", - "generalMergeWithNextAndAutoBreak": "מזג עם הבא ופצל אוטומטית", - "generalMergeSelectedLinesAndAutoBreak": "מזג שורות שנבחרו ופצל אוטומטית", "generalMergeSelectedLinesAndUnbreak": "מזג שורות שנבחרו ובטל פיצול", "generalMergeSelectedLinesAndUnbreakCjk": "מזג שורות שנבחרו ובטל פיצול (לשפות אסייתיות)", - "generalMergeSelectedLinesOnlyFirstText": "מזג שורות שנבחרו (טקסט ראשון בלבד)", "generalMergeSelectedLinesBilingual": "מזג שורות שנבחרו כדו-לשוני", - "generalMergeWithPreviousBilingual": "מזג עם הקודם כדו-לשוני", - "generalMergeWithNextBilingual": "מזג עם הבא כדו-לשוני", - "generalMergeOriginalAndTranslation": "מזג מקור ותרגום", "generalToggleTranslationMode": "החלף מצב תרגום", - "generalSwitchOriginalAndTranslation": "החלף בין מקור לתרגום", - "generalSwitchOriginalAndTranslationTextBoxes": "החלף בין תיבות טקסט של מקור ותרגום", "generalToggleTranslationAndOriginalInPreviews": "החלף בין תרגום למקור בתצוגה המקדימה של וידאו/שמע", "generalChooseLayout": "בחר פריסת ממשק", - "generalLayoutChooseX": "פריסה {0}", - "generalPlayFirstSelected": "נגן את הכתובית הראשונה שנבחרה", - "generalGoToFirstSelectedLine": "עבור לשורה הראשונה שנבחרה", - "generalGoToNextEmptyLine": "עבור לשורה הריקה הבאה", "generalGoToNextSubtitle": "עבור לכתובית הבאה", "generalGoToNextSubtitlePlayTranslate": "עבור לכתובית הבאה (נגן תרגום)", "generalGoToNextSubtitleCursorAtEnd": "עבור לכתובית הבאה (סמן בסוף)", "generalGoToPrevSubtitle": "עבור לכתובית הקודמת", "generalGoToPrevSubtitlePlayTranslate": "עבור לכתובית הקודמת (נגן תרגום)", - "generalGoToStartOfCurrentSubtitle": "עבור לתחילת הכתובית הנוכחית", - "generalGoToEndOfCurrentSubtitle": "עבור לסוף הכתובית הנוכחית", - "generalGoToPreviousSubtitleAndFocusVideo": "עבור לכתובית הקודמת והתמקד בוידאו", - "generalGoToNextSubtitleAndFocusVideo": "עבור לכתובית הבאה והתמקד בוידאו", - "generalGoToPrevSubtitleAndPlay": "עבור לכתובית הקודמת ונגן", - "generalGoToNextSubtitleAndPlay": "עבור לכתובית הבאה ונגן", - "generalGoToPreviousSubtitleAndFocusWaveform": "עבור לכתובית הקודמת והתמקד בגל שמע", - "generalGoToNextSubtitleAndFocusWaveform": "עבור לכתובית הבאה והתמקד בגל שמע", - "generalGoToLineNumber": "עבור למספר שורה", "generalGoToFirstLine": "עבור לשורה הראשונה", "generalGoToLastLine": "עבור לשורה האחרונה", "alsoSetVideoPosition": "קבע גם את מיקום הוידאו", "generalGoToVideoPosition": "עבור למיקום וידאו", "generalToggleItalic": "החלף כתב נטוי", "generalToggleBold": "החלף כתב מודגש", - "generalToggleBookmarks": "החלף סימניות", - "generalFocusTextBox": "התמקד בתיבת הטקסט", - "generalToggleBookmarksWithText": "החלף סימניות עם טקסט", - "generalEditBookmarks": "ערוך סימניות", - "fileNew": "חדש", "generalToggleUnderline": "החלף מצב קו תחתון", + "generalToggleBox": "החלף תיבה (EBU STL)", "fileOpen": "פתח", "fileOpenKeepVideo": "פתח (שמור וידאו)", "fileSave": "שמור", "fileSaveAs": "שמור בשם", - "fileSaveAll": "שמור הכל", "fileSaveOriginal": "שמור מקור", - "fileSaveOriginalAs": "שמור מקור בשם", - "fileOpenOriginalSubtitle": "פתח כתובית מקורית", - "fileCloseOriginalSubtitle": "סגור כתובית מקורית", - "fileTranslatedSubtitle": "כתובית מתורגמת", "fileCompare": "השוואה", "fileStatistics": "סטטיסטיקה", "fileImportPlainText": "ייבא טקסט פשוט", - "fileImportBluRaySupForOcr": "ייבא Blu-ray SUP עבור OCR", - "fileImportBluRaySupForEdit": "ייבא Blu-ray SUP עבור עריכה", - "fileImportTimeCodes": "ייבא קודי זמן", "fileExportEbuStl": "ייצא EBU STL", "fileExportPac": "ייצא PAC", - "fileExportEdlClipName": "ייצא שם קליפ EDL", - "fileExportPlainText": "ייצא טקסט פשוט", - "fileExportCustomTextFormat1": "ייצא פורמט טקסט מותאם אישית 1", - "fileExportCustomTextFormat2": "ייצא פורמט טקסט מותאם אישית 2", - "fileExportCustomTextFormat3": "ייצא פורמט טקסט מותאם אישית 3", "fileExit": "יציאה", "openSeDataFolder": "פתח את תיקיית Subtitle Edit", - "editFind": "חפש", - "editFindNext": "חפש את הבא", "editFindPrevious": "חפש את הקודם", - "editReplace": "החלף", - "editMultipleReplace": "החלפה מרובה", "fillSelectedLinesWithClipboard": "מלא שורות שנבחרו בטקסט מלוח הגזירים", - "editModifySelection": "שנה בחירה", - "listSelectAll": "בחר הכל", - "listSelectFirst": "בחר ראשון", - "listSelectLast": "בחר אחרון", "listInverseSelection": "הפוך בחירה", "listDeleteSelection": "מחק בחירה", + "deleteSelectionEverywhere": "מחק בחירה (בכל מקום)", "rippleDeleteSelection": "מחיקת Ripple", - "settings": "הגדרות", "assigned": "מוקצה", "unassigned": "לא מוקצה", "pressedKeyX": "מקש שנלחץ: {0}", @@ -3443,6 +3772,7 @@ "detectKey": "זהה מקש", "control": "Ctrl", "alt": "Alt", + "altGr": "AltGr", "win": "Win", "shift": "Shift", "controlMac": "⌃", @@ -3453,11 +3783,14 @@ "resetShortcutsDetail": "האם ברצונך לאפס את כל קיצורי הדרך לערכי ברירת המחדל?", "togglePlayPause": "נגן/השהה", "toggleLockTimeCodes": "נעילת קודי זמן", + "toggleTimeCodeMode": "החלף מצב קוד זמן (זמן/פריימים/מספרי פריימים)", + "toggleScreenPrivacy": "החלף פרטיות מסך (הסתר שמות קבצים, ולאחר מכן גם טקסטים)", + "screenPrivacyFileNamesHidden": "פרטיות מסך: שמות קבצים מוסתרים", + "screenPrivacyFileNamesAndTextsHidden": "פרטיות מסך: שמות קבצים וטקסטים מוסתרים", + "screenPrivacyOff": "פרטיות מסך: כבויה", "duplicateSelectedLines": "שכפל שורות שנבחרו", "sourceView": "תצוגת קוד מקור", - "showAlignmentPicker": "יישור", "addOrEditBookmark": "הוסף או ערוך סימנייה", - "listBookmarks": "רשימת סימניות", "toggleBookmark": "החלף סימנייה (שורות נבחרות, ללא טקסט)", "toggleForcedSelectedLines": "החלף מצב כפוי (שורות שנבחרו)", "goToNextBookmark": "עבור לסימנייה הבאה", @@ -3468,12 +3801,12 @@ "waveformSetStartAndSetEndOfPreviousMinusGap": "קבע התחלה וקבע סוף של הקודם פחות מרווח", "waveformSetEndAndStartOfNextAfterGap": "קבע סוף והתחלה של הבא פלוס מרווח", "waveformSetEndAndStartOfNextAfterGapAndGoToNext": "קבע סוף והתחלה של הבא פלוס מרווח ועבור לבא", - "fetchFirstWordFromNextSubtitle": "משוך מילה ראשונה מהכתובית הבאה", "moveLastWordToNextSubtitle": "העבר מילה אחרונה לכתובית הבאה", "moveLastWordFromFirstLineDownCurrentSubtitle": "הורד מילה אחרונה מהשורה הראשונה (כתובית נוכחית)", "moveFirstWordFromNextLineUpCurrentSubtitle": "העלה מילה ראשונה מהשורה הבאה (כתובית נוכחית)", "moveTextFromCursorToNextAndGoToNext": "העבר את הטקסט שאחרי מיקום הסמן לכתובית הבאה ועבור אליה", "moveTextFromCursorToNextAndGoToNextAndPlay": "העבר את הטקסט שאחרי מיקום הסמן לכתובית הבאה, עבור אליה ונגן", + "moveTextFromCursorToNextAndGoToNextAndPlayAndPause": "העבר את הטקסט שאחרי מיקום הסמן לכתובית הבאה, עבור לבאה, נגן אותה והשהה בסופה", "breakAtFirstSpaceFromCursor": "פצל ברווח הראשון ממיקום הסמן", "breakAtFirstSpaceFromCursorAndGoToNext": "פצל ברווח הראשון ממיקום הסמן ועבור לבא", "toggleFocusGridAndWaveform": "העבר מיקוד בין טבלת כתוביות לגל שמע/ספקטרוגרמה", @@ -3485,7 +3818,6 @@ "videoGoToPreviousTimeCode": "עבור לקוד הזמן הקודם", "videoGoToNextTimeCode": "עבור לקוד הזמן הבא", "goToNextLineAndSetVideoPosition": "עבור לכתובית הבאה וקבע מיקום וידאו", - "textBoxDeleteSelectionNoClipboard": "תיבת טקסט: מחק בחירה (ללא לוח)", "textBoxDeleteForward": "תיבת טקסט: מחק בחירה או את התו הבא (מחיקה קדימה)", "textBoxCut": "תיבת טקסט: גזור", "textBoxCut2": "תיבת טקסט: גזור (חלופי)", @@ -3504,6 +3836,7 @@ "listViewColumnTextUp": "עמודה, טקסט למעלה", "listViewColumnTextDown": "עמודה, טקסט למטה", "autoTranslateSelectedLines": "תרגום אוטומטי של שורות שנבחרו...", + "autoTranslateSelectedLinesNoPrompt": "תרגם אוטומטית שורות שנבחרו (ללא שאלה, השתמש במנוע/בשפות האחרונים)", "setAssaResolution": "קבע רזולוציית ASSA (PlayResX/PlayResY)", "setShortcutForX": "קבע קיצור דרך עבור \"{0}\"", "commandFileNewKeepVideo": "חדש (שמור וידאו)", @@ -3511,27 +3844,20 @@ "fileCloseOriginal": "סגור מקור", "fileCloseTranslation": "סגור תרגום", "restoreAutoBackup": "שחזר מגיבוי אוטומטי", - "openContainingFolder": "פתח תיקייה מכילה", "importTimeCodes": "ייבא קודי זמן", "importSubtitleWithManuallyChosenEncoding": "ייבא כתובית עם קידוד לבחירה ידנית", "exportBluRaySup": "ייצא Blu-ray SUP", "exportCustomTextFormat": "ייצא פורמט טקסט מותאם אישית", "exportPlainText": "ייצא טקסט פשוט", - "showHistory": "הצג היסטוריה", "toggleRightToLeft": "החלף כיווניות מימין לשמאל", "modifySelection": "שנה בחירה", "adjustDurations": "התאם משכי זמן", "applyDurationLimits": "החל מגבלות משך זמן", "batchConvert": "המרת אצווה", - "bridgeGaps": "גשר על מרווחים", "applyMinGap": "החל מרווח מינימלי", - "fixCommonErrors": "תיקון שגיאות נפוצות", "makeEmptyTranslationFromCurrentSubtitle": "צור תרגום ריק מכתובית נוכחית", - "mergeLinesWithSameText": "מזג שורות עם טקסט זהה", - "mergeLinesWithSameTimeCodes": "מזג שורות עם קודי זמן זהים", "splitBreakLongLines": "פצל/אזן שורות ארוכות", "mergeShortLines": "מזג שורות קצרות", - "removeTextForHearingImpaired": "הסר טקסט לכבדי שמיעה", "joinSubtitles": "חבר כתוביות", "splitSubtitle": "פצל כתובית", "spellCheck": "בדיקת איות", @@ -3553,7 +3879,6 @@ "adjustAllTimes": "התאם את כל הזמנים", "visualSync": "סנכרון חזותי", "translateViaCopyPaste": "תרגם באמצעות העתק-הדבק", - "shortcuts": "קיצורי דרך", "wordLists": "רשימות מילים", "chooseUiLanguage": "בחר שפת ממשק", "chooseRuleProfile": "בחר פרופיל כללים", @@ -3564,6 +3889,7 @@ "textBoxItalic": "תיבת טקסט, כתב נטוי", "textBoxBold": "תיבת טקסט הדגשה", "textBoxUnderline": "תיבת טקסט קו תחתון", + "textBoxBox": "תיבת טקסט: תיבה (EBU STL)", "resetWaveformZoomAndSpeed": "אפס זום גל שמע ומהירות ניגון", "togglePlaybackSpeed": "החלף מהירות ניגון", "playbackSpeedSlower": "מהירות ניגון איטית יותר", @@ -3573,22 +3899,28 @@ "seekSilence": "חפש שקט", "waveformGuessStart": "נחש את זמן ההתחלה מגל השמע", "waveformGuessEnd": "נחש את זמן הסיום מגל השמע", + "waveformGuessStartAndEnd": "נחש זמן התחלה וסיום מגל השמע", "setVideoPositionCurrentSubtitleStart": "קבע מיקום וידאו לתחילת השורה הנוכחית", "goToSubtitlePositionAndPause": "עבור למיקום הכתובית והשהה", "setVideoPositionCurrentSubtitleEnd": "קבע מיקום וידאו לסוף השורה הנוכחית", "toggleAudioTracks": "החלף רצועות שמע", - "listErrors": "הצג שגיאות", "goToNextError": "עבור לשגיאה הבאה", "goToPreviousError": "עבור לשגיאה הקודמת", "addNameToNameList": "הוסף שם לרשימת השמות", "findDoubleWords": "מצא מילים כפולות", "findDoubleLines": "מצא שורות כפולות", "colorX": "צבע {0}", - "removeColor": "הסר צבע", "surroundWith": "הקף ב...", "surroundWithXY": "הקף ב- {0}/{1}", - "repeatLine": "חזור על השורה", "surroundWithNumberX": "הקף ב-#{0}", + "surroundWithBehavior": "התנהגות", + "surroundWithBehaviorToggle": "החלף (הוסף, או הסר אם קיים)", + "surroundWithBehaviorAdd": "הוסף (בכל פעם)", + "surroundWithBehaviorRemove": "הסר (הכל)", + "surroundWithBehaviorRemoveOnce": "הסר (אחד בכל פעם)", + "surroundWithWorksOn": "פועל על", + "surroundWithScopeSelectionOrText": "הבחירה, אחרת כל הטקסט", + "surroundWithScopeEachLine": "כל שורה", "searchVia": "חפש באמצעות", "searchViaX": "חפש באמצעות {0}", "searchViaNumberX": "חפש באמצעות #{0}", @@ -3613,6 +3945,11 @@ "copySubtitlePathToClipboard": "העתק נתיב כתובית ללוח הגזירים", "copySubtitleOriginalPathToClipboard": "העתק נתיב כתובית מקורית ללוח הגזירים", "focusTextBox": "התמקד בתיבת הטקסט", + "focusSubtitleListView": "התמקד ברשימת הכתוביות", + "focusWaveform": "התמקד בגל השמע", + "focusOriginalTextBox": "התמקד בתיבת הטקסט המקורי", + "textBoxGoToStart": "תיבת טקסט, עבור להתחלה", + "textBoxGoToEnd": "תיבת טקסט, עבור לסוף", "sortByStartTime": "מיין לפי זמן התחלה", "sortByEndTime": "מיין לפי זמן סיום", "sortByDuration": "מיין לפי משך", @@ -3635,7 +3972,6 @@ "assaStyles": "סגנונות ASSA", "assaProperties": "מאפייני ASSA", "assaAttachments": "קבצים מצורפים ASSA", - "assaVideoColorPicker": "דוגם צבע וידאו ASSA", "recalculateDurationSelectedLines": "חשב מחדש משך זמן (שורות נבחרות)", "toggleWaveformAndSpectrogramHeight": "שנה יחס גובה בין גל שמע לספקטרוגרמה", "toggleSpectrogramStyle": "החלף סגנון ספקטרוגרמה", @@ -3658,9 +3994,9 @@ "selectionToSentenceCase": "תיבת טקסט: הבחירה לאות רישית בתחילת משפט", "selectionToggleCasing": "תיבת טקסט: החלף סוג אותיות (רישיות -> קטנות -> אות רישית בכל מילה)", "googleIt": "חפש בגוגל (טקסט שנבחר)", - "setActorXY": "קבע שחקן {0}: {1}", + "setActorX": "קבע שחקן: {0}", "setNewActor": "קבע שחקן חדש...", - "setActorSlotXTitle": "משבצת שחקן {0}", + "setStyleX": "קבע סגנון: {0}", "toggleDialogDashes": "החלף מקפי דו-שיח", "mergeWithLineBeforeAsDialog": "מזג עם השורה שלפני כדו-שיח", "mergeWithLineAfterAsDialog": "מזג עם השורה שאחרי כדו-שיח", @@ -3671,13 +4007,27 @@ "moveStartOneFrameBackKeepGapPrev": "הזז את ההתחלה פריים אחד אחורה (שמור על המרווח לקודמת אם קרובה)", "moveStartOneFrameForwardKeepGapPrev": "הזז את ההתחלה פריים אחד קדימה (שמור על המרווח לקודמת אם קרובה)", "moveEndOneFrameBackKeepGapNext": "הזז את הסיום פריים אחד אחורה (שמור על המרווח לבאה אם קרובה)", - "moveEndOneFrameForwardKeepGapNext": "הזז את הסיום פריים אחד קדימה (שמור על המרווח לבאה אם קרובה)" + "moveEndOneFrameForwardKeepGapNext": "הזז את הסיום פריים אחד קדימה (שמור על המרווח לבאה אם קרובה)", + "moveStartXMsBack": "הזז התחלה X ms אחורה (X נקבע בהגדרות, גם בסנכרון חזותי)", + "moveStartXMsForward": "הזז התחלה X ms קדימה (X נקבע בהגדרות, גם בסנכרון חזותי)", + "moveEndXMsBack": "הזז סיום X ms אחורה (X נקבע בהגדרות, גם בסנכרון חזותי)", + "moveEndXMsForward": "הזז סיום X ms קדימה (X נקבע בהגדרות, גם בסנכרון חזותי)", + "moveSelectedLinesXMsBack": "הזז שורות שנבחרו X ms אחורה (X נקבע בהגדרות)", + "moveSelectedLinesXMsForward": "הזז שורות שנבחרו X ms קדימה (X נקבע בהגדרות)", + "moveSelectedLinesAndForwardXMsBack": "הזז שורות שנבחרו וכל השורות שאחריהן X ms אחורה (X נקבע בהגדרות)", + "moveSelectedLinesAndForwardXMsForward": "הזז שורות שנבחרו וכל השורות שאחריהן X ms קדימה (X נקבע בהגדרות)", + "moveAllLinesXMsBack": "הזז את כל השורות X ms אחורה (X נקבע בהגדרות)", + "moveAllLinesXMsForward": "הזז את כל השורות X ms קדימה (X נקבע בהגדרות)", + "moveSelectedLinesCustomMsBack": "הזז שורות שנבחרו, אלפיות שנייה מותאמות אישית ({0:#,###,##0}) אחורה, {1}", + "moveSelectedLinesCustomMsForward": "הזז שורות שנבחרו, אלפיות שנייה מותאמות אישית ({0:#,###,##0}) קדימה, {1}", + "moveSelectedLinesAndForwardCustomMsBack": "הזז שורות שנבחרו וכל השורות שאחריהן, אלפיות שנייה מותאמות אישית ({0:#,###,##0}) אחורה, {1}", + "moveSelectedLinesAndForwardCustomMsForward": "הזז שורות שנבחרו וכל השורות שאחריהן, אלפיות שנייה מותאמות אישית ({0:#,###,##0}) קדימה, {1}", + "moveAllLinesCustomMsBack": "הזז את כל השורות, אלפיות שנייה מותאמות אישית ({0:#,###,##0}) אחורה, {1}", + "moveAllLinesCustomMsForward": "הזז את כל השורות, אלפיות שנייה מותאמות אישית ({0:#,###,##0}) קדימה, {1}" }, "wordLists": { "title": "רשימות מילים", "addName": "הוסף שם", - "addWord": "הוסף מילה", - "addPair": "הוסף זוג", "nameAndIgnoreList": "רשימת שמות/התעלמות", "userWords": "רשימת מילים של המשתמש", "ocrFixList": "רשימת תיקוני OCR", @@ -3694,7 +4044,6 @@ "noPluginsInstalled": "לא מותקנים תוספים", "getPluginsOnline": "הורד תוספים מהאינטרנט...", "getPluginsTitle": "הורדת תוספים", - "enabled": "מופעל", "disabled": "מושבת", "notSupportedOnThisOs": "לא נתמך במערכת הפעלה זו", "removePluginXConfirm": "האם להסיר את התוסף '{0}'? פעולה זו תמחוק את תיקיית התוסף:{1}", @@ -3711,17 +4060,19 @@ "downloadingXPercent": "מוריד {0}%...", "pluginXInstalled": "'{0}' הותקן בהצלחה.", "couldNotInstallPluginX": "לא ניתן להתקין את '{0}': {1}", - "notInstalled": "לא מותקן", "install": "התקן", "reinstall": "התקן מחדש", - "update": "עדכן", "installedX": "מותקן ({0})", "updateAvailableXToY": "עדכון זמין ({0} ← {1})", "applyPluginToWhichLinesX": "החל את '{0}' על:", "applyToSelectedLinesX": "שורות שנבחרו ({0})", "applyToAllLinesX": "כל השורות ({0})", "updateAllXAvailable": "עדכן הכל ({0})", - "updatingXOfY": "מעדכן {0}/{1}..." + "updatingXOfY": "מעדכן {0}/{1}...", + "doNotAskAgain": "אל תשאל שוב (ניתן לשנות בניהול תוספים)", + "applyPluginsTo": "החל תוספים על", + "askEachTime": "שאל בכל פעם", + "allLines": "כל השורות" }, "help": { "aboutSubtitleEdit": "אודות Subtitle Edit", @@ -3765,6 +4116,8 @@ "nOcrDatabase": "מסד נתונים nOCR", "drawMode": "מצב ציור:", "addNewCharcter": "הוסף תו חדש", + "addNOcrCharacterTitle": "הוסף תו nOCR עבור שורה {0}, תו {1} מתוך {2} באמצעות מסד הנתונים \"{3}\"", + "addBinaryOcrCharacterTitle": "הוסף תו OCR בינארי עבור שורה {0}, תו {1} מתוך {2} באמצעות מסד הנתונים \"{3}\"", "lineIndexX": "שורה {0}", "inspectNOcrAdditions": "בחן תוספות nOCR חדשות", "ocrSelectedLines": "בצע OCR לשורות שנבחרו", @@ -3813,6 +4166,8 @@ "binaryImageCompareInspectImageMatches": "\"השוואת תמונה בינארית\" - בחן התאמות תמונה", "saveBlankTextTitle": "לשמור טקסט ריק?", "saveBlankTextPrompt": "האם לשמור טקסט ריק עבור התמונה?", + "drawnLinesDoNotMatchTitle": "הקווים אינם תואמים לתמונה", + "drawnLinesDoNotMatchPrompt": "חלק מהקווים שצוירו אינם תואמים לתמונה (קווים ירוקים חייבים להיות על התו, קווים אדומים חייבים להיות מחוצה לו), ולכן תו זה לא יזוהה.\n\nלשמור בכל זאת?", "yesAndNeverAskAgain": "כן ואל תשאל שוב לעולם", "importTextFromSubtitleDotDotDot": "ייבא טקסט מכתובית...", "importTextFromSubtitleOverwritePrompt": "לחלק מהשורות כבר יש טקסט OCR. האם לדרוס טקסט קיים?", @@ -3838,7 +4193,6 @@ "vobSubColorsTitle": "צבעי VobSub/DVD", "vobSubColorsHeader": "התאם אישית את ארבעת צבעי ה-VobSub", "vobSubColorsDescription": "כתוביות VobSub/DVD משתמשות בארבעה צבעים מיועדים: רקע, דפוס (pattern), הדגשה 1 והדגשה 2. בחר כל צבע למטה כדי לדרוס את פלטת הצבעים המובנית - שימושי כאשר הצבעים המקוריים נותנים ניגודיות נמוכה ב-OCR.", - "vobSubColorBackground": "רקע", "vobSubColorPattern": "דפוס", "vobSubColorEmphasis1": "הדגשה 1", "vobSubColorEmphasis2": "הדגשה 2", @@ -3872,9 +4226,26 @@ "crispEmbedSettingsTitle": "הגדרות CrispEmbed", "crispEmbedDescription": "מנוע OCR מקומי עם מספר מודלים - הורד כאן את המנוע ואת המודלים.", "trainNOcrDatabase": "אמן מסד נתונים nOCR...", + "optimizeNOcrDatabase": "בצע אופטימיזציה למסד נתונים nOCR...", + "optimizeNOcrDatabaseQuestionXY": "להסיר את הקווים שאינם מבדילים בין תווים לתווים דומים במסד הנתונים nOCR \"{0}\"?\n\nפעולה זו מקטינה את מסד הנתונים ומאיצה את ה-OCR, בדרך כלל עם פחות שגיאות. לוקח עד דקה.\n\nגיבוי נשמר בשם \"{1}\".", + "optimizingNOcrDatabaseXY": "מבצע אופטימיזציה למסד נתונים nOCR... {0}/{1}", + "nOcrDatabaseOptimizedXYZ": "בוצעה אופטימיזציה למסד הנתונים nOCR \"{0}\": {1:#,##0} -> {2:#,##0} קווים.", "startTraining": "התחל אימון", "abortTraining": "בטל אימון", "trainingOptions": "אפשרויות אימון", + "trainNOcrSubtitle": "מעבד תווים בגופנים המותקנים ומלמד את nOCR את הצורות שלהם. בחר את הגופנים שבהם משתמשות הכתוביות שלך.", + "xFontsSelected": "{0:#,##0} נבחרו", + "subtitleFonts": "גופני כתוביות", + "emptyDatabase": "מסד נתונים ריק", + "startFromDatabase": "התחל מ", + "startFromDatabaseHint": "הוסף את התווים שאומנו לעותק של מסד נתונים קיים (מומלץ) או התחל ממסד נתונים ריק", + "fontSizes": "גדלי גופן", + "fontSizesHint": "גדלי גופן בפיקסלים לעיבוד, מופרדים בפסיקים, לדוגמה \"30, 40\"", + "numberOfLineSegmentsHint": "קווים הנבדקים לכל תו - יותר פירושו מחמיר יותר אך איטי יותר", + "enterDatabaseName": "הזן שם למסד הנתונים", + "trainingFontXSizeYCharacterZ": "מאמן את \"{0}\" בגודל {1} px: {2}", + "xLearned": "{0:#,##0} נלמדו", + "xSkipped": "{0:#,##0} דולגו", "charactersToTrain": "תווים לאימון", "letterCombinationsToTrain": "צירופי אותיות שעשויים להתפצל לתמונה אחת", "importCharactersFromSubtitleFile": "ייבא תווים מקובץ כתוביות...", @@ -3901,6 +4272,8 @@ "drawZoomOut": "הקטן זום (Ctrl+-)", "drawResetView": "אפס תצוגה (Ctrl+0)", "drawToggleGrid": "הצג/הסתר רשת (Ctrl+G)", + "drawTogglePreview": "החלף תצוגה מקדימה (F9)", + "drawPreviewFailed": "התצוגה המקדימה נכשלה - נדרש ffmpeg עם libass (מסנן subtitles)", "drawCopyToClipboard": "העתק ללוח הגזירים (Ctrl+C)", "drawShapes": "צורות", "drawSelectedPoint": "נקודה נבחרת", @@ -3908,16 +4281,86 @@ "drawSelectedLayer": "שכבה נבחרת", "drawToolX": "כלי: {0}", "drawHelpText": "לחץ להוספת נקודות • Enter/F8 לסגירת צורה • Shift+גרור להזזת תצוגה • Ctrl+גלגלת לזום", + "drawMoveToLayer": "העבר לשכבה", + "drawFlipHorizontal": "הפוך אופקית", + "drawFlipVertical": "הפוך אנכית", + "drawHideShape": "הסתר צורה", + "drawShowShape": "הצג צורה", + "drawHideLayer": "הסתר שכבה", + "drawShowLayer": "הצג שכבה", + "drawDeleteLayer": "מחק שכבה", + "drawDeletePoint": "מחק נקודה", + "drawCancelDrawing": "בטל ציור", + "drawLayerX": "שכבה {0}", + "drawImportSvg": "ייבוא תמונת SVG...", + "drawRotateClockwise": "סובב 90° בכיוון השעון", + "drawConvertToCurve": "המר מקטע לעקומה", + "drawBackground": "רקע", + "drawPosition": "מיקום", + "drawZoom": "זום", + "drawShapeTool": "כלי צורות", + "drawColorPickerTool": "בחר צבע מצורה או מהרקע", + "drawShapeLibrary": "ספריית צורות", + "drawShapeLibraryHint": "גרור על משטח הציור כדי למקם צורה, או לחץ לגודל ברירת המחדל. Shift שומר על הפרופורציות שלה.", + "drawAddToLibrary": "הוסף לספריית הצורות...", + "drawRemoveFromLibrary": "הסר מספריית הצורות", + "drawShapeName": "שם הצורה", + "drawCategorySpeechBubbles": "בועות דיבור", + "drawCategoryArrows": "חצים", + "drawCategoryBasicShapes": "צורות בסיסיות", + "drawCategorySymbols": "סמלים", + "drawCategoryMyShapes": "הצורות שלי", + "drawShapeSpeechBubble": "בועת דיבור", + "drawShapeSpeechBubbleRight": "בועת דיבור, זנב ימינה", + "drawShapeSpeechBubbleUp": "בועת דיבור, זנב למעלה", + "drawShapeOvalBubble": "בועת דיבור אליפטית", + "drawShapeBoxBubble": "בועת דיבור מלבנית", + "drawShapeThoughtBubble": "בועת מחשבה", + "drawShapeShoutBubble": "בועת צעקה", + "drawShapeCaptionBox": "תיבת כיתוב", + "drawShapeArrow": "חץ", + "drawShapeDoubleArrow": "חץ כפול", + "drawShapeChevron": "שברון", + "drawShapeCurvedArrow": "חץ מעוקל", + "drawShapeRoundedRectangle": "מלבן מעוגל", + "drawShapeTriangle": "משולש", + "drawShapeDiamond": "מעוין", + "drawShapePentagon": "מחומש", + "drawShapeHexagon": "משושה", + "drawShapeOctagon": "מתומן", + "drawShapeStar": "כוכב", + "drawShapeRing": "טבעת", + "drawShapePlus": "פלוס", + "drawShapeBanner": "כרזה", + "drawShapeHeart": "לב", + "drawShapeCheck": "סימן וי", + "drawShapeMusicNote": "תו מוזיקלי", + "drawShapeMusicNotes": "תווים מוזיקליים", + "drawShapeLightning": "ברק", + "drawShapeCloud": "ענן", + "drawShapeMoon": "ירח", + "drawShapeBadge": "תג", + "drawSize": "גודל", + "drawLayer": "שכבה", + "drawBackgroundVideoFrame": "פריים וידאו (מיקום נוכחי)", + "drawBackgroundVideoFrameAt": "פריים וידאו במיקום...", + "drawBackgroundImage": "קובץ תמונה...", + "drawBackgroundNone": "ללא רקע", + "drawBackgroundStretch": "מתח את התמונה למשטח הציור", + "drawBackgroundOpacity": "אטימות הרקע", + "drawBackgroundVideoFrameFailed": "לא ניתן היה לקבל פריים וידאו ב-{0}.", + "drawImages": "תמונות", + "drawConvertToLine": "המר מקטע לקו", + "drawConvertShapeToCurves": "המר את כל הקווים לעקומות", + "drawConvertShapeToLines": "המר את כל העקומות לקווים", + "drawRotateCounterClockwise": "סובב 90° נגד כיוון השעון", + "drawSvgImages": "תמונות SVG", + "drawSvgNoShapes": "לא נמצאו צורות מלאות או קווי מתאר בקובץ ה-SVG.", "progressBarTitle": "מחוון התקדמות ASSA", "progressBarSettings": "מחוון התקדמות", - "progressBarPosition": "מיקום", - "progressBarBottom": "למטה", - "progressBarTop": "למעלה", "mouseOverColor": "צבע במעבר עכבר", "clickedColor": "צבע בלחיצה", "progressBarForeColor": "צבע גופן", - "progressBarBackColor": "צבע רקע", - "progressBarStyle": "סגנון", "progressBarSquareCorners": "פינות ישרות", "progressBarRoundedCorners": "פינות מעוגלות", "progressBarChapters": "פרקים", @@ -3926,8 +4369,6 @@ "progressBarXAdjustment": "התאמת ציר X", "progressBarYAdjustment": "התאמת ציר Y", "progressBarTextAlignment": "יישור טקסט", - "progressBarTakePosFromVideo": "קבל מיקום מהוידאו", - "progressBarPreview": "תצוגה מקדימה", "fontCollectorTitle": "אוסף גופנים", "fontCollectorUsedIn": "בשימוש ב-", "fontCollectorFontFiles": "קובצי גופן", @@ -3974,9 +4415,7 @@ "resolutionResamplerVideoDiffers": "רזולוציית הכתוביות ({0}x{1}) שונה מרזולוציית הוידאו ({2}x{3}).\nלשנות את הכתובית כך שתתאים לוידאו?", "resolutionResamplerAskOnVideoOpen": "שאל כאשר נפתח וידאו ברזולוציה שונה", "backgroundBoxGenerator": "מחולל תיבת רקע", - "backgroundBoxPadding": "ריפוד (Padding)", "backgroundBoxFillWidth": "מלא רוחב", - "backgroundBoxBoxColor": "צבע תיבה", "backgroundBoxRadius": "רדיוס", "backgroundBoxCircle": "עיגול", "backgroundBoxSpikes": "קוצים", @@ -3988,12 +4427,9 @@ "backgroundBoxTornPaperDouble": "נייר קרוע (למעלה ולמטה)", "backgroundBoxStarburst": "התפרצות כוכב", "backgroundBoxScroll": "מגילה / קלף", - "stylesTitle": "סגנונות Advanced Sub Station Alpha", "stylesInFile": "סגנונות בקובץ", "stylesSaved": "הסגנונות נשמרו", - "category": "קטגוריה", "allCategories": "[הכל]", - "defaultCategory": "ברירת מחדל", "newCategory": "קטגוריה חדשה...", "renameCategory": "שנה שם קטגוריה...", "deleteCategory": "מחק קטגוריה", @@ -4013,13 +4449,26 @@ "graphics": "גרפיקה", "copyToStorageStyles": "העתק לסגנונות השמורים במערכת", "copyToFileStyles": "העתק לסגנונות הקובץ", + "styleXAlreadyExistsInStorage": "סגנון בשם \"{0}\" כבר קיים בסגנונות השמורים.\n\nלדרוס אותו, או לשמור את שניהם (העותק יקבל שם חדש)?", + "styleXAlreadyExistsInFile": "סגנון בשם \"{0}\" כבר קיים בקובץ.\n\nלדרוס אותו, או לשמור את שניהם (העותק יקבל שם חדש)?", + "styleNameCannotBeEmpty": "שם סגנון אינו יכול להיות ריק.", + "styleNameXIsUsedMoreThanOnce": "שם הסגנון \"{0}\" משמש יותר מסגנון אחד בקובץ.", + "overwrite": "דרוס", + "keepBoth": "שמור את שניהם", + "doThisForAllConflictingStyles": "בצע זאת עבור כל הסגנונות המתנגשים", "setStyleAsDefault": "קבע סגנון כברירת מחדל", "takeUsagesFromDotDotDot": "קח שימושים מ...", "replaceStyleWithDotDotDot": "החלף סגנון ב...", "noAttachmentsFound": "לא נמצאו קבצים מצורפים בקובץ ה-ASSA הנבחר.", "deleteStyleQuestion": "למחוק סגנון?", "deleteStylesQuestion": "למחוק סגנונות?", + "deleteXStylesQuestion": "האם ברצונך למחוק {0} סגנונות?", + "deleteXStylesFromStorageQuestion": "האם ברצונך למחוק {0} סגנונות מהסגנונות השמורים?", + "deleteStyleXFromStorageQuestion": "האם ברצונך למחוק את הסגנון \"{0}\" מהסגנונות השמורים?", + "deleteStyleXFromFileQuestion": "האם ברצונך למחוק את הסגנון \"{0}\" מהקובץ הנוכחי?", "openStyleImportFile": "פתח קובץ כתוביות לייבוא סגנונות ממנו", + "styleImportFiles": "קובצי סגנון (.ass, .sty, קטגוריות .template של Subtitle Edit 4)", + "ssaStyleImportFiles": "קובצי סגנון (.ssa, .ass, .sty)", "primary": "ראשי", "secondary": "משני", "applyOverrideTags": "החל תגיות דריסה", @@ -4034,7 +4483,6 @@ "spaceIncrease": "הגדל מרווח", "popIn": "כניסה בקפיצה (Pop in)", "spinIn": "כניסה בסיבוב", - "playCurrent": "נגן נוכחי", "setPosition": "קבע מיקום", "rotation": "סיבוב", "scaleX": "קנה מידה X", @@ -4078,10 +4526,8 @@ "advancedEffectOldMovieDescription": "מדמה מקרן סרטים ישן של 35 מ\"מ עם רעש, אפקט פינות כהות ושריטות", "advancedEffectNeonBurst": "מתפרץ ניאון", "advancedEffectNeonBurstDescription": "כתוביות בסגנון 'Shorts' עתיר אנרגיה עם זוהר ניאון ואנימציית קפיצה בכניסה", - "advancedEffectSettings": "הגדרות", "advancedEffectSnowFlakeCount": "כמות פתיתי שלג", "advancedEffectStarfieldStarCount": "כמות כוכבים", - "advancedEffectStarfieldSpeed": "מהירות", "advancedEffectGlitch": "תקלה (Glitch)", "advancedEffectGlitchDescription": "תקלה דיגיטלית עם עיוות צבעוני, הבזקים לבנים ושכבות של עיוות", "advancedEffectBounceIn": "כניסה בקפיצה", @@ -4090,8 +4536,6 @@ "advancedEffectFirefliesDescription": "נקודות זהובות-ענבריות זוהרות וחמימות הנסחפות באופן אורגני על פני המסך עם זוהר פועם", "advancedEffectMatrix": "מטריקס", "advancedEffectMatrixDescription": "עמודות תווים ירוקים נופלות כאשר טקסט הכתובית נבנה אות אחר אות מתוך הגשם", - "advancedEffectAudioBars": "עמודות שמע (גל שמע)", - "advancedEffectAudioBarsDescription": "עמודות אקולייזר מונפשות המונעות על ידי גל השמע — העמודות נגללות שמאלה כגל שמע ופועמות עם השמע", "advancedEffectAudioPulse": "פעימת טקסט לפי שמע", "advancedEffectAudioPulseDescription": "טקסט הכתובית זוהר ופועם בהתאם לעוצמת השמע — קטעים שקטים מציגים טקסט רגיל, ושיאי עוצמה מוסיפים הילה צבעונית והגדלה קלה", "advancedEffectConfetti": "התפרצות קונפטי", @@ -4105,7 +4549,6 @@ "advancedEffectFancyKaraokeGlow": "זוהר מילה פעילה", "advancedEffectFancyKaraokeActiveColor": "צבע פעיל", "advancedEffectFancyKaraokeInactiveColor": "צבע לא פעיל", - "advancedEffectFancyKaraokeInactiveOpacity": "אטימות לא פעילה (0-255)", "advancedEffectWordSpacingPixels": "מרווח (פיקסלים)", "advancedEffectSlowZoomOut": "זום-אאוט איטי", "advancedEffectSlowZoomOutDescription": "הטקסט מתחיל מעט גדול יותר ומתכווץ בעדינות לגודלו הרגיל לאורך משך הכתובית", @@ -4121,7 +4564,6 @@ "advancedEffectSlideInLeftDescription": "הטקסט מחליק פנימה מחוץ למסך משמאל, נעצר, ואז יוצא חזרה שמאלה", "advancedEffectSlideInRight": "החלקה מימין", "advancedEffectSlideInRightDescription": "הטקסט מחליק פנימה מחוץ למסך מימין, נעצר, ואז יוצא חזרה ימינה", - "advancedEffectFadeInOut": "עמעום כניסה/יציאה", "advancedEffectFadeInOutDescription": "הטקסט מתעמעם בכניסה בתחילה ומתעמעם ביציאה בסוף כל כתובית", "advancedEffectWordFlip3D": "היפוך מילים תלת-ממדי", "advancedEffectWordFlip3DDescription": "כל מילה מתקפלת פנימה בהיפוך תלת-ממדי וקפיצה אלסטית בזמן אמירתה — סגנון כתוביות 'Shorts' מודרני", diff --git a/src/ui/Assets/Languages/Hungarian.json b/src/ui/Assets/Languages/Hungarian.json index a6ffac75667..2539017b57f 100644 --- a/src/ui/Assets/Languages/Hungarian.json +++ b/src/ui/Assets/Languages/Hungarian.json @@ -1,6 +1,6 @@ { "title": "Subtitle Edit", - "version": "v5.3.0-beta11", + "version": "v5.3.0", "translatedBy": "Zityi's Translator Te@m", "cultureName": "hu-HU", "general": { @@ -13,6 +13,18 @@ "actorPickerKeysHint": "Nyomjon meg egy számot vagy az Enter-t a beszélő beállításához. Az Alt+Fel/Le módosítja a sorrendet, a Delete eltávolítja a beszélőt.", "actorPickerNewActorX": "Új beszélő \"{0}\"", "actorPickerLinesSelectedX": "Kijelölt sorok: {0}", + "setStyleDotDotDot": "Stílus beállítása...", + "stylePickerTitle": "Stílus beállítása", + "stylePickerFilterHint": "Gépeljen a szűréshez, vagy írjon be egy új stílusnevet", + "stylePickerKeysHint": "Nyomjon meg egy számot (1-9, 0) a stílus beállításához az összes kijelölt sorra.", + "stylePickerNewStyleX": "Új stílus: \"{0}\"", + "stylePickerNewStyleInfo": "Nyomja meg az Enter billentyűt a stílus alapértelmezett beállításokkal történő hozzáadásához és a kijelölt sorokra való beállításához. A betűtípust, a színeket és a pozíciót később a \"Stílusok kezelése...\" menüpontban módosíthatja.", + "stylePickerCurrentStyleX": "Jelenlegi stílus: {0}", + "stylePickerLinesUsingStyleX": "Ezt a stílust használó sorok: {0}", + "stylePickerMarginsXYZ": "Bal {0}, jobb {1}, függőleges {2}", + "stylePickerSetStyle": "Stílus _beállítása", + "stylePickerManageStyles": "Stílusok kezelése...", + "stylePickerManageStylesHint": "Az ablak bezárása és a stíluskezelő megnyitása a betűtípusok, színek, pozíció és egyebek szerkesztéséhez", "add": "Hozzáadás", "addDotDotDot": "Hozzáadás...", "addToNamesListCaseSensitive": "Hozzáadás a nevek listájához (kis- és nagybetűérzékeny)", @@ -345,6 +357,7 @@ "mergeSelectedLinesDialog": "Kijelölt sorok egyesítése párbeszédként", "mergeWithLineAfterAndAutoBreak": "Egyesítés a következő sorral és automatikus tördelés", "mergeWithLineAfterKeepBreaks": "Egyesítés a következő sorral (törések megtartásával)", + "mergeWithLineAfterAndUnbreak": "Egyesítés a következő sorral és összekapcsolás", "mergeWithLineBeforeAndAutoBreak": "Egyesítés az előző sorral és automatikus tördelés", "mergeWithLineBeforeKeepBreaks": "Egyesítés az előző sorral (törések megtartásával)", "middleCenter": "Középre igazítva", @@ -430,6 +443,7 @@ "percent": "Százalék", "pickLayer": "Réteg beállítása", "pickOllamaModel": "Ollama modell kiválasztása", + "pickModel": "Modell kiválasztása", "llamaCppUseRemoteServer": "Külső szerver használata (URL)", "llamaCppEngineSettings": "llama.cpp motor beállításai", "llamaCppEngineSettingsSubtitle": "Helyi llama.cpp szerver build", @@ -506,6 +520,7 @@ "reset": "Visszaállítás", "resizePanels": "Panelek átméretezése", "resolution": "Felbontás", + "retry": "Újra", "reverseRightToLeftStartEnd": "RTL kezdet/vég megfordítása", "right": "Jobbra", "rightToLeft": "Jobbról balra", @@ -567,6 +582,8 @@ "shadowColor": "Árnyék színe", "shadowWidth": "Árnyék szélessége", "shiftLineBy": "Sor eltolása ennyivel", + "shotIn": "Vágás be", + "shotOut": "Vágás ki", "shortcut": "Gyorsbillentyű", "shortcuts": "Gyorsbillentyűk", "show": "Megjelenítés", @@ -578,9 +595,13 @@ "showStartColumn": "\"Kezdet\" oszlop megjelenítése", "showHideColumn": "\"Elrejtés\" oszlop megjelenítése", "showHistory": "Előzmények megjelenítése", + "clearHistory": "Előzmények törlése", + "removeFromHistory": "Eltávolítás az előzményekből", "showLayerColumn": "\"Réteg\" oszlop megjelenítése", "showPreview": "Előnézet megjelenítése", "showShotChangesList": "Vágásváltások listájának megjelenítése", + "showShotInColumn": "\"Vágás be\" oszlop megjelenítése", + "showShotOutColumn": "\"Vágás ki\" oszlop megjelenítése", "showStyleColumn": "\"Stílus\" oszlop megjelenítése", "showTeletext": "Teletext oszlop megjelenítése a listanézetben", "showTimeCodes": "Időkódok megjelenítése", @@ -637,12 +658,14 @@ "styleExaggeration": "Stílus eltúlzása", "styles": "Stílusok", "subtitleFile": "Feliratfájl", + "subtitleFileChangedOutsideX": "A(z) \"{0}\" fájl a Subtitle Editen kívül módosult.\n\nÚjratölti, és elveti a módosításait?", "subtitleFileName": "Feliratfájl neve", "subtitleFileSaved": "Feliratfájl elmentve", "subtitleFileSavedToX": "Feliratfájl elmentve ide: {0}", "subtitleFiles": "Feliratfájlok", "subtitleFormats": "Feliratformátumok", "subtitleLoadedX": "Felirat betöltve: {0}", + "subtitleReloadedChangedOutsideX": "Felirat újratöltve (a Subtitle Editen kívül módosult): {0}", "suffix": "Utótag", "suggestions": "Javaslatok", "sync": "Szinkron", @@ -726,10 +749,23 @@ "videoCustom3ForwardX": "Videó, egyéni ezredmásodperccel ({0:#,###,##0}) előre, 3", "videoCustom4BackX": "Videó, egyéni ezredmásodperccel ({0:#,###,##0}) vissza, 4", "videoCustom4ForwardX": "Videó, egyéni ezredmásodperccel ({0:#,###,##0}) előre, 4", + "videoCustom1BackAndPauseX": "Videó, egyéni ezredmásodperc ({0:#,###,##0}) vissza és szünet, 1", + "videoCustom1ForwardAndPauseX": "Videó, egyéni ezredmásodperc ({0:#,###,##0}) előre és szünet, 1", + "videoCustom2BackAndPauseX": "Videó, egyéni ezredmásodperc ({0:#,###,##0}) vissza és szünet, 2", + "videoCustom2ForwardAndPauseX": "Videó, egyéni ezredmásodperc ({0:#,###,##0}) előre és szünet, 2", + "videoCustom3BackAndPauseX": "Videó, egyéni ezredmásodperc ({0:#,###,##0}) vissza és szünet, 3", + "videoCustom3ForwardAndPauseX": "Videó, egyéni ezredmásodperc ({0:#,###,##0}) előre és szünet, 3", + "videoCustom4BackAndPauseX": "Videó, egyéni ezredmásodperc ({0:#,###,##0}) vissza és szünet, 4", + "videoCustom4ForwardAndPauseX": "Videó, egyéni ezredmásodperc ({0:#,###,##0}) előre és szünet, 4", "videoExtension": "Videófájl kiterjesztése", "videoFile": "Videófájl", "videoFileGenerated": "Videófájl létrehozva", "videoFileGeneratedX": "Videófájl generálva: \"{0}\"", + "videoFilesGenerated": "Videófájlok elkészültek", + "xVideosGenerated": "{0} videó elkészült", + "xOfYVideosGenerated": "{0} / {1} videó elkészült", + "savedInX": "Mentve ide: {0}", + "savedInXFolders": "Mentve {0} mappába", "videoFiles": "Videófájlok", "videoAndAudioFiles": "Videó- és hangfájlok", "videoOffset": "Videóeltolás", @@ -856,6 +892,7 @@ "multipleReplace": "_Több elem cseréje...", "fillSelectedLinesWithClipboard": "Kijelölt sorok kitöltése a vágólap szövegével", "goToLineNumber": "_Ugrás sorszámra...", + "commandPalette": "_Parancspaletta...", "rightToLeftMode": "_Jobbról balra mód", "modifySelectionDotDotDot": "Kijelölés _módosítása...", "tools": "_Eszközök", @@ -1048,12 +1085,15 @@ "charactersPerSecond": "Karakter/másodperc: {0}", "chooseColumn": "Oszlop kiválasztása", "columnPaste": "Oszlop beillesztése", + "commandPaletteTitle": "Parancspaletta", + "commandPaletteSearchPlaceholder": "Gépeljen a parancsok kereséséhez...", + "commandPaletteNoCommandsFound": "Nincs egyező parancs", "createdEmptyTranslation": "Üres fordítás létrehozva az aktuális feliratból", "deleteText": "Szöveg törlése", "deleteTextAndShiftCellsUp": "Szöveg törlése és a cellák eltolása felfelé", "endTimeMustBeAfterStartTime": "A befejezési időnek a kezdési idő után kell lennie.", "errorLoad7Zip": "Ez a fájl tömörített 7-Zip fájlnak tűnik.\n\nA Subtitle Edit nem tud megnyitni tömörített fájlokat.", - "errorLoadBinaryZeroes": "Sajnos ez a fájl csak bináris nullákat tartalmaz!\n\nHa a fájlt a Subtitle Edit programmal szerkesztette, előfordulhat, hogy talál biztonsági mentést a Fájl -> Automatikus mentés visszaállítása... menüpontban.", + "errorLoadBinaryZeroes": "Sajnos ez a fájl csak bináris nullákat tartalmaz!\n\nHa a fájlt a Subtitle Edit programmal szerkesztette, előfordulhat, hogy talál biztonsági mentést a Fájl -> Automatikus mentés visszaállítása... menüpontban.", "errorLoadGZip": "Ez a fájl tömörített GZip fájlnak tűnik.\n\nA Subtitle Edit nem tud megnyitni tömörített fájlokat.", "errorLoadLargeFile": "Ez a fájl túl nagy ahhoz, hogy feliratfájlként megnyitható legyen.", "errorLoadVideoFilePrompt": "Ez a fájl videófájlnak tűnik:\n{0}\n\nVideót a \"Videó -> Videó megnyitása\" menüponton keresztül vagy a videónak a videólejátszóra történő ráhúzásával nyithat meg.\n\nSzeretné megnyitni videófájlként?", @@ -1093,6 +1133,7 @@ "overwriteExistingCells": "Meglévő cellák felülírása", "overwriteOrShiftCellsDown": "Felülírás/cellák eltolása lefelé", "parsingMatroskaFile": "Matroska fájl feldolgozása...", + "readingDvdSubtitles": "DVD-feliratok olvasása...", "pasteFromClipboardDotDotDot": "Beillesztés a vágólapról...", "redoPerformed": "Művelet újból végrehajtva", "redoPerformedXActionLeft": "Művelet újból végrehajtva (hátralévő műveletek: {0})", @@ -1132,12 +1173,14 @@ "trimmedXLines": "{0} feliratsor megvágva", "pastedXLinesOverSelectedLines": "Beillesztve {0} sor a kijelölt sorokra", "pastedXOfYLinesOverSelectedLines": "{0}/{1} sor beillesztve a kiválasztott sorok fölé - a többi nem fért el a kijelölésben", + "insertedXLinesFromClipboard": "{0} sor beillesztve a vágólapról", "openOriginalDifferentNumberOfSubtitlesXY": "Az eredeti feliratfájl nem tartalmaz ugyanannyi feliratot, mint az aktuális feliratfájl.\n\n• Eredeti feliratok száma: {0}\n• Aktuális feliratok száma: {1}", "originalTextReadOnly": "Eredeti szöveg (csak olvasható)", "originalTextEditMode": "Eredeti szöveg (szerkesztési mód)", "originalIsReadOnlyNotSaved": "Az eredeti felirat csak olvasható hivatkozás, és nem lett elmentve", "originalIsEmptyNotSaved": "Az eredeti felirat egyáltalán nem tartalmaz szöveget, és nem lett elmentve", "originalIsReadOnlyReference": "Az eredeti felirat csak olvasható hivatkozásként van nyitva", + "closeTranslationQuestionXY": "Bezárja a(z) \"{0}\" fordítást?\n\nAz eredeti \"{1}\" felirat lesz a szerkesztett felirat. Magát a fordítási fájlt ez nem módosítja.", "allowEditOfOriginalSubtitle": "Eredeti felirat szerkesztésének engedélyezése", "showAllOriginalLinesX": "Minden eredeti sor megjelenítése (itt {0} nem egyezik)", "showAllOriginalLinesHint": "Az egyezés nélküli sorok extra sorokként jelennek meg, így láthatja, mi hiányzik a fordításból.", @@ -1172,6 +1215,7 @@ "fileExportedInFormatXToY": "Fájl exportálva {0} formátumban ide: {1}", "fileExportedInFormatXToFileY": "Fájl exportálva \"{0}\" formátumban a(z) \"{1}\" fájlba", "fixedXLines": "{0} sor javítva", + "toolXChangedYLines": "{0}: {1} sor módosítva", "transcriptionCompletedWithXLines": "Frissítés befejeződött {0} sorral", "openSubtitleFileX": "Megnyitja a(z) \"{0}\" feliratfájlt?", "replacedXOccurrences": "{0} előfordulás lecserélve", @@ -1262,6 +1306,21 @@ "formattingDotDotDot": "Formázás...", "imageBasedSubtitleForEditDotDotDot": "Képalapú felirat szerkesztéshez...", "imageBasedSubtitleForOcrDotDotDot": "Képalapú felirat OCR-hez...", + "dvdSubtitlesDotDotDot": "Feliratok DVD-ről (IFO/VOB)...", + "titleImportDvd": "Feliratok importálása DVD-ről", + "dvdIfoFile": "IFO-fájl", + "dvdVobFiles": "VOB-fájlok", + "dvdVideoStandard": "Videószabvány", + "dvdStartRipping": "Kinyerés indítása", + "dvdRippingAborted": "Kinyerés megszakítva", + "dvdOpenIfoFile": "DVD IFO-fájl megnyitása", + "dvdAddVobFiles": "VOB-fájlok hozzáadása", + "dvdIfoFiles": "DVD IFO-fájlok", + "dvdVobFileType": "DVD VOB-fájlok", + "dvdDropHint": "Nyisson meg egy IFO-fájlt (VIDEO_TS\\VTS_01_0.IFO), vagy adjon hozzá VOB-fájlokat - vagy húzza ide őket", + "dvdNotAnIfoFileX": "A(z) {0} nem DVD IFO-fájl", + "dvdMenuIfoNoTitleSetsX": "Nem található címkészlet (VTS_xx_0.IFO) a(z) {0} mellett", + "dvdEncryptedXOfY": "A(z) {1} feliratcsomagból {0} még mindig CSS-titkosított - ezeket a VOB-fájlokat visszafejtés nélkül másolták, így egyes feliratképek torzak lesznek.\n\nA tiszta eredményért először fejtse vissza a DVD-t egy DVD-kinyerő eszközzel.\n\nMégis folytatja?", "splitTextAt": "Szöveg felosztása itt:", "blankLines": "Üres sorok", "oneLineIsOneSubtitle": "Egy sor egy felirat", @@ -1286,7 +1345,8 @@ "forcedAlignerIsolatingSpeech": "Beszéd elkülönítése... {0}", "forcedAlignerIsolatingSpeechFailed": "Nem sikerült elkülöníteni a beszédet - a záró időpontok az olvasási idő alapján lettek beállítva.", "forcedAlignerModel": "Igazító modell", - "forcedAlignerDownloadEngine": "Motor letöltése / frissítése..." + "forcedAlignerDownloadEngine": "Motor letöltése / frissítése...", + "forcedAlignerEstimatedXOfY": "{1} sorból {0} nem igazítható, ezért olvasási idő alapján lettek elhelyezve - az időkódjaik csak becsültek. A szöveg valószínűleg hosszabb annál, ami a videóban elhangzik." }, "export": { "exportImagesProfiles": "Képprofilok exportálása", @@ -1488,7 +1548,7 @@ "topBottomMargin": "Felső/alsó margó", "fadeUpTime": "Áttűnés ideje felfelé", "fadeDownTime": "Áttűnés ideje lefelé", - "Pozíció": "Z-pozíció", + "zPosition": "Z-pozíció", "zPositionHelp": "A pozitív számok távolítják, a negatív számok közelebb hozzák a szöveget; ha a z-pozíció nulla, akkor 2D-s" }, "webVtt": { @@ -1527,11 +1587,61 @@ "ignoreFormatting": "Formázás figyelmen kívül hagyása", "ignoreFormattingHint": "A csak formázási címkékben, mint az vagy az {\\an8}, eltérő sorok nem számítanak eltérőnek", "showOnlyDifferencesInText": "Csak szövegbeli eltérések", + "ignoreNumbering": "Számozás figyelmen kívül hagyása", + "ignoreNumberingHint": "A csak a sorszámukban eltérő sorok nem számítanak eltérőnek", + "ignoreTimeDifferenceMs": "Időtűrés (ezredmp)", + "ignoreTimeDifferenceMsHint": "Az ennyi ezredmásodperccel vagy kevesebbel eltérő kezdő- és záróidők nem számítanak eltérőnek", + "compareDifferences": "Eltérések", + "compareTextDifferences": "Szöveges eltérések", + "compareCurrent": "Jelenlegi", + "compareReference": "Referencia", + "compareEditable": "Szerkeszthető", + "compareEditableHint": "Az itt végzett módosítások az Alkalmazás gombra kattintva kerülnek vissza a feliratba", + "compareReadOnly": "Csak olvasható", + "compareTakeFromReference": "Szöveg és időzítés átvétele a referenciából", + "compareInsertFromReference": "Sor beszúrása a jelenlegi feliratba", + "compareDeleteFromCurrent": "Sor törlése a jelenlegi feliratból", + "compareTakeText": "Szöveg átvétele", + "compareTakeTiming": "Időzítés átvétele", + "compareEditLine": "Sor szerkesztése", + "compareEditHint": "Ctrl+Enter a mentéshez, Esc a megszakításhoz", + "compareEdited": "Szerkesztve", + "compareOnePendingChange": "1 függőben lévő módosítás", + "compareXPendingChanges": "{0} függőben lévő módosítás", + "compareDiscardXChanges": "Elveti az Összehasonlításban végzett {0} módosítást?", + "compareXChangesApplied": "Összehasonlítás: {0} módosítás alkalmazva", + "compareChangeTextX": "#{0} szöveg a referenciából", + "compareChangeTimingX": "#{0} időzítés a referenciából", + "compareChangeTextAndTimingX": "#{0} szöveg és időzítés a referenciából", + "compareChangeEditedX": "#{0} szerkesztve", + "compareChangeInsertedX": "#{0} beszúrva", + "compareChangeDeletedX": "#{0} törölve", + "compareSyncPoint": "Szinkronizálási pont", + "compareSyncPointHint": "Szinkronizálási pont - ez a két sor mindig párként jelenik meg, a felette és alatta lévő sorok pedig külön kerülnek egymás mellé", + "compareSyncPickCurrent": "Szinkronizálási pont: ennek a jelenlegi sornak a használata", + "compareSyncPickReference": "Szinkronizálási pont: ennek a referenciasornak a használata", + "compareSyncRemove": "Szinkronizálási pont eltávolítása", + "compareClearXSyncPoints": "Szinkronizálási pontok törlése ({0})", + "compareSyncCurrentPickedX": "A jelenlegi #{0} kiválasztva szinkronizálási pontként - jelölje ki a megfelelő referenciasort, és kattintson a Szinkron gombra", + "compareSyncReferencePickedX": "A referencia #{0} kiválasztva szinkronizálási pontként - jelölje ki a megfelelő jelenlegi sort, és kattintson a Szinkron gombra", + "compareSyncWithCurrentX": "Szinkron a jelenlegi #{0} sorral", + "compareSyncWithReferenceX": "Szinkron a referencia #{0} sorral", + "compareSyncApply": "Szinkron", + "compareSyncPointSetXY": "Szinkronizálási pont beállítva: a jelenlegi #{0} és a referencia #{1} mostantól párként jelenik meg", + "compareSyncAlreadyPairedXY": "A jelenlegi #{0} és a referencia #{1} már párként jelenik meg azonos kezdettel - nincs mit szinkronizálni", + "compareSyncPointShiftedXYZW": "Szinkronizálási pont beállítva: a jelenlegi #{0}-#{1} eltolva {2} értékkel, hogy a referencia #{3} sorral kezdődjön", + "compareChangeSyncShiftXYZ": "#{0}-#{1} eltolva {2} értékkel (szinkronizálási pont)", + "compareTimingDeltaHint": "Mennyivel később (+) vagy korábban (−) kezdődik és végződik a referencia a jelenlegi sorhoz képest", "loadXFromFile": "\"{0}\" betöltése fájlból", + "compareLoadSavedFile": "Mentett fájl betöltése", "saveCompareHtmlTitle": "Összehasonlítás HTML-fájl mentése", "pickMatroskaTrackX": "Matroska sáv kiválasztása - {0}", "pickTransportStreamTrackX": "Átviteli adatfolyam sávjának kiválasztása - {0}", + "chapters": "Fejezetek", + "dvdVobFilesMissingX": "Hiányzó VOB-fájlok ({0}% található)", "pickMp4TrackX": "MP4 sáv kiválasztása - {0}", + "pickMxfTrackX": "MXF-sáv kiválasztása - {0}", + "pickMpegTrackX": "MPEG-sáv kiválasztása - {0}", "rosettaProperties": "Timed Text Rosetta IMSC tulajdonságok", "rosettaFontSize": "Betűméret (sormagasság)", "propertyTimeBase": "Időalap", @@ -1606,7 +1716,8 @@ "findWhat": "Keresendő", "descriptionOptional": "Leírás (opcionális)", "invalidRegularExpressionX": "Érvénytelen reguláris kifejezés: {0}", - "regularExpressionTooSlowX": "A reguláris kifejezés leállt {0} másodperc után - a szabály átugrásra került, próbáljon meg egy egyszerűbb mintát" + "regularExpressionTooSlowX": "A reguláris kifejezés leállt {0} másodperc után - a szabály átugrásra került, próbáljon meg egy egyszerűbb mintát", + "moveToCategory": "Áthelyezés kategóriába" }, "find": { "searchTextWatermark": "Szöveg keresése...", @@ -1619,7 +1730,9 @@ "replaceIn": "Csere/keresés ebben:", "replaceInTextAndOriginal": "Szöveg és eredeti szöveg", "replaceInTextOnly": "Csak szöveg", - "replaceInOriginalOnly": "Csak eredeti szöveg" + "replaceInOriginalOnly": "Csak eredeti szöveg", + "replacedOneOccurrence": "Egy előfordulás lecserélve", + "replacedXOccurrences": "{0} előfordulás lecserélve" }, "regularExpressionContextMenu": { "wordBoundary": "Szóhatár (\\b)", @@ -1647,6 +1760,7 @@ "xLinesYSubtitles": "{0} sor, {1} felirat", "xLinesYSubtitlesZErrors": "{0} sor, {1} felirat, {2} hiba", "couldNotParseAsX": "Nem sikerült feldolgozni {0}-ként", + "readAsXUseAsY": "A forrás nem olvasható be {1} formátumként, de {0} formátumként igen.\nHasználja a sorokat, és továbbra is {1} formátumban ment?", "selectedXCharactersYLines": "Kijelölve: {0} karakter {1} sorban", "replacedXOccurrences": "Kicserélve {0} előfordulás", "invalidRegularExpression": "Érvénytelen reguláris kifejezés", @@ -1683,7 +1797,22 @@ "mismatchWarning": "Nem hasonlít az eredetire - lehet, hogy egy másik sorhoz tartozik", "engineError": "A MI-motor nem érhető el: {0}", "playCurrentLineHint": "A kijelölt sor lejátszása a videólejátszóban és szüneteltetés a végén (a sorra való dupla kattintás is)", - "editAfterHint": "Kattintson a kijelölt sor szövegére a javítás szerkesztéséhez az alkalmazás előtt (Enter: mentés, Escape: mégse)" + "editAfterHint": "Kattintson a kijelölt sor szövegére a javítás szerkesztéséhez az alkalmazás előtt (Enter: mentés, Escape: mégse)", + "template": "Sablon", + "templatePlaceholder": "Válasszon sablont a prompt lecseréléséhez", + "templateStandard": "Általános lektorálás", + "templateSpeechToText": "Beszédfelismerés utáni tisztítás", + "templateMachineTranslation": "Gépi fordítás javítása", + "templateSdh": "SDH / nagyothallók", + "referenceContext": "Referencia-környezet", + "referenceContextInfo": "Opcionális. Nevek, szereplők, szójegyzék, szinopszis vagy stílusjegyzetek - a modell ezt tekinti mérvadónak a nevek és kifejezések tekintetében. Minden köteggel elküldésre kerül.", + "referenceContextPlaceholder": "Nevek: Kaito, Mei néni, Dr. Hollis\nKifejezések: Shinkansen, warpmag\nSzinopszis: Kaito Tokióba utazik, hogy megtalálja eltűnt apját.", + "generateContext": "Generálás MI-vel", + "generateContextHint": "A kiválasztott MI-motor elolvassa a feliratot, és vázlatot készít a nevekről, kifejezésekről és egy szinopszisról - használat előtt ellenőrizze az eredményt", + "generatingContextPartXOfY": "{0}. rész olvasása ({1} közül)...", + "generatingContextSynopsis": "Szinopszis írása...", + "replaceContextQuestion": "Lecseréli a jelenlegi referencia-környezetet?", + "noContextGenerated": "Az MI-motor nem adott vissza használható referencia-információt." }, "aiAssistant": { "title": "MI asszisztens", @@ -1729,6 +1858,7 @@ "removeLineBreaksPixelWidth": "Egy sorba beleférő feliratok összekapcsolása (képpontszélesség)", "fixUppercaseIInsideLowercaseWords": "Kisbetűs szavakban lévő nagy 'I' betűk javítása (OCR hiba)", "fixDoubleApostrophes": "Dupla aposztróf karakterek ('') javítása egyetlen idézőjellé (\")", + "fixMisreadQuotes": "Idézőjelként félreolvasott aposztrófok javítása (OCR, angol)", "addPeriods": "Pont hozzáadása a sorok után, ha a következő sor nagybetűvel kezdődik", "startWithUppercaseLetterAfterParagraph": "Nagybetűvel kezdődjön a bekezdés után", "startWithUppercaseLetterAfterPeriodInsideParagraph": "Nagybetűvel kezdődjön a bekezdésen belüli pont után", @@ -1751,6 +1881,7 @@ "fixCommasExample": ",, -> ,", "removeLineBreaksExample": "Foo
bar! -> Foo bar!", "fixDoubleApostrophesExample": "''Két egymás utáni szimpla idézőjelet tartalmaz'' -> \"Egy darab dupla idézőjelet tartalmaz\"", + "fixMisreadQuotesExample": "\"Hello' -> \"Hello\"", "addPeriodsExample": "Hello world -> Hello world.", "startWithUppercaseLetterAfterParagraphExample": "p1: Foobar! || p2: foobar! -> p1: Foobar! || p2: Foobar!", "startWithUppercaseLetterAfterPeriodInsideParagraphExample": "Helló! hogy vagy?  -> Helló! Hogy vagy?", @@ -1840,6 +1971,8 @@ "fixMinDurationMs": "Minimális időtartam javítása (ezredmp)", "doNotGoPastShotChange": "Ne menjen túl a vágásváltáson", "fixMaxDurationMs": "Maximális időtartam javítása (ezredmp)", + "fixMinDurationFrames": "Minimális időtartam javítása (képkocka)", + "fixMaxDurationFrames": "Maximális időtartam javítása (képkocka)", "minimumDurationMilliseconds": "Minimális időtartam (milliszekundum):", "maximumDurationMilliseconds": "Maximális időtartam (milliszekundum):", "maxDurationShouldBeHigherThanMinDuration": "A maximális időtartamnak nagyobbnak kell lennie a minimális időtartamnál", @@ -1901,6 +2034,20 @@ "isolateSpeechHint": "Eltávolítja a zenét és a hangeffekteket az igazítás előtt, így az igazító csak a párbeszédet hallja. Segít a hangos zene vagy akció alatt elhangzó soroknál, de GPU-val körülbelül annyi ideig tart, mint maga a hanganyag – GPU nélkül pedig sokszorosáig.", "isolatingSpeech": "Beszéd elkülönítése... (ez eltart egy ideig)", "isolateSpeechFailed": "Nem sikerült elkülöníteni a beszédet - ehelyett az eredeti hanganyaghoz lett igazítva.", + "checkWithSpeechToText": "Ellenőrzés beszédfelismeréssel", + "checkWithSpeechToTextHint": "A hangot is átírja, és minden áthelyezést összevet azzal, ahol a szavak ténylegesen elhangzottak. A beszéd által megerősített nagy áthelyezések automatikusan be lesznek jelölve, a sor elhangzási helyétől eltávolodó áthelyezések pedig jelöletlenek maradnak ellenőrzésre. Azt is megmutatja, hogy az egyes sorok mekkora része hallható - az alacsony arány azt jelenti, hogy a szöveg eltér attól, ami elhangzik. Az a felirat, amely a maximális eltolásnál jobban elcsúszott - állandó eltolás, képkockasebesség-eltérésből adódó csúszás vagy jelenetvágások miatti ugrások -, először a hallott szavak alapján lesz szinkronizálva. A Crisp ASR Parakeetet használja, amely GPU-val egy nagyjátékfilmnél néhány percet vesz igénybe.", + "checkWithSpeechToTextNotAvailableX": "A beszédfelismeréses ellenőrzés nem érhető el ehhez: {0}", + "transcribingToCheck": "Átírás az igazítás ellenőrzéséhez...", + "speechToTextFailed": "A beszédfelismerés sikertelen - az időkódok nem lettek ellenőrizve.", + "speechToTextX": "Beszédfelismerés: {0}", + "speechToTextConfirmedXDisputedY": "Beszéd: {0} megerősítve, {1} vitatott", + "hardlyHeardX": "A beszédfelismerés a felirat szavainak csak {0}%-át hallotta. A felirat azon a nyelven van, amelyen beszélnek? Az igazító nem tud fordítást elhelyezni.", + "heard": "Hallott", + "heardHint": "A sor mekkora részét hallotta a beszédfelismerés. Az alacsony arány azt jelenti, hogy a szöveg eltér attól, ami elhangzik - itt tévedhet a legnagyobb valószínűséggel az igazító.", + "statusConfirmedBySpeech": "Beszéd által megerősítve", + "statusDisputedBySpeech": "Az eredeti helyén hallható", + "statusMovedWithSync": "A szinkronnal együtt áthelyezve", + "syncedFirstXY": "Először szinkronizálva: {0} mp az elején, {1} mp a végén", "showSpeechOnly": "Csak beszéd megjelenítése", "showSpeechOnlyHint": "Az igazított hullámforma kirajzolása a zene és hangeffektek nélkül megtisztított hanganyagból; az eredeti hullámforma változatlan marad az összehasonlításhoz. Akkor érhető el, ha a beszéd már el lett különítve – itt, vagy a főablak hullámformáján a \"Csak beszéd megjelenítése\" opcióval.", "align": "Igazítás", @@ -1915,6 +2062,7 @@ "meanShiftX": "Átlagos eltolás: {0} ms", "toCheckX": "Ellenőrizendő: {0}", "largeMovesToCheckX": "{0} sor magában nagy utat tenne meg, ezért nincsenek bejelölve – játssza le az Eredeti / Igazított opciót, és jelölje be a megfelelőket.", + "linesToCheckX": "{0} sor jelöletlen maradt ellenőrzésre - hallgassa meg őket (F5 lejátssza az igazított sort, Shift+F5 az eredetit), és jelölje be a helyeseket.", "changeXOfY": "Módosítás: {0} / {1}", "previousChange": "Előző módosítás", "nextChange": "Következő módosítás", @@ -1927,10 +2075,12 @@ "statusNoSpeech": "Nincs beszéd", "statusShiftTooLarge": "Megtartva - az eltolás túl nagy", "statusFailed": "Megtartva - az igazító meghiúsult", + "statusAdjustedByHand": "Kézzel módosítva", + "alignedWaveformHint": "Húzzon egy sort az áthelyezéséhez, vagy húzza a széleit a kezdete vagy vége módosításához.", + "playLine": "Sor lejátszása", + "undoAdjustment": "Módosítás visszavonása", "playPause": "Lejátszás / szünet", - "playOriginal": "Eredeti", "playAligned": "Igazítva", - "playOriginalHint": "A kijelölt sor lejátszása az eredeti időkódokkal", "playAlignedHint": "A kijelölt sor lejátszása az igazított időkódokkal", "apply": "Alkalmaz", "applyHint": "Törölje a pipát a sor mellől az eredeti időkódok megtartásához. Dupla kattintással lejátszhatja a sort." @@ -1941,6 +2091,11 @@ "presetDefault": "Alapértelmezett", "presetNetflix": "Netflix", "presetSdi": "SDI", + "saveAsProfile": "Mentés profilként...", + "deleteProfile": "Profil törlése", + "profileName": "Profil neve", + "overwriteProfileX": "Már létezik \"{0}\" nevű profil. Felülírja?", + "deleteProfileX": "Törli a(z) \"{0}\" profilt?", "gap": "Köz", "gapSuffix": "frames (also updates non-zero gap fields below)", "milliseconds": "ms", @@ -2008,6 +2163,7 @@ "addColor": "Szín hozzáadása", "deleteLinesWithSpecificActorsOrStyles": "Sorok törlése szereplők vagy stílusok szerint (többet vesszővel válasszon el)", "useSourceStylesIfPossible": "Forrásstílusok használata, ha lehetséges", + "keepSourceEmbeddedFonts": "Forrás beágyazott betűtípusainak megtartása (lábléc)", "editStyles": "Stílusok szerkesztése", "editProperties": "Tulajdonságok szerkesztése", "editAttachments": "Mellékletek szerkesztése", @@ -2042,7 +2198,20 @@ "addFolderRecursiveDotDotDot": "Mappa hozzáadása rekurzívan...", "selectFolderToConvert": "Mappa kiválasztása a konvertálandó fájlokkal", "includeSubfolders": "Tartalmazza az almappákat a mappa hozzáadásakor", + "videoFilesWhenAddingFolder": "Videófájlok (mkv, mp4, ts...) mappa hozzáadásakor", + "videoFilesAsk": "Rákérdezés", + "videoFilesInclude": "Feliratsávjaik hozzáadása", + "videoFilesSkip": "Kihagyás", + "folderContainsXVideoFiles": "A mappa {0} videófájlt tartalmaz (mkv, mp4, ts...).\n\nA beágyazott feliratsávjaikat is hozzáadja, vagy csak a feliratfájlokat?", + "addVideoFiles": "Videófájlok hozzáadása", + "skipVideoFiles": "Csak feliratfájlok", + "doNotAskAgainVideoFiles": "Ne kérdezze újra (módosítható a Csoportos átalakítás beállításaiban)", + "settingsSectionOutput": "Kimenet", + "settingsSectionAddingFiles": "Fájlok hozzáadása", + "settingsSectionImageBasedSubtitles": "Képalapú feliratok (OCR)", + "settingsSectionOther": "Egyéb", "keepSourceFileTimestamp": "Forrásfájl dátumának/idejének megőrzése a kimeneti fájlokon", + "preventSleepWhileConverting": "A számítógép alvó állapotának megakadályozása átalakítás közben", "scanningFolderX": "{0} beolvasása...", "transportStreamSettings": "Transport Stream beállítások", "transportStreamSettingsDotDotDot": "Transport Stream beállítások...", @@ -2056,7 +2225,20 @@ "transportStreamOnlyTeletext": "Csak teletext", "transportStreamGetSizeFromVideo": "Méret átvétele a videóból...", "twoLetterLanguageCodeUppercase": "Kétbetűs nyelvkód (NAGYBETŰS)", - "threeLetterLanguageCodeUppercase": "Hárombetűs nyelvkód (NAGYBETŰS)" + "threeLetterLanguageCodeUppercase": "Hárombetűs nyelvkód (NAGYBETŰS)", + "formatContainsDotDotDot": "A formátum tartalmazza...", + "extensionIsDotDotDot": "A kiterjesztés...", + "statusIsError": "Az állapot hiba", + "forcedTracksOnly": "Csak kényszerített sávok", + "xConvertedYFailedInZ": "{0:#,###,##0} átalakítva, {1:#,###,##0} sikertelen, időtartam: {2}", + "xNotProcessed": "{0:#,###,##0} nincs feldolgozva", + "removeUnicodeControlCharactersTitle": "Unicode vezérlőkarakterek eltávolítása", + "removeUnicodeControlCharactersInfo": "Eltávolítja a láthatatlan Unicode irányvezérlő karaktereket (LRM, RLM, LRE, RLE, PDF, LRO és RLO) a szövegből, és a nem törhető szóközöket normál szóközökre cseréli.", + "preset": "Előbeállítás", + "savePresetDotDotDot": "Előbeállítás mentése...", + "deletePreset": "Előbeállítás törlése", + "presetName": "Előbeállítás neve", + "deletePresetX": "Törli a(z) \"{0}\" előbeállítást?" }, "changeCasing": { "fixNames": "Nevek javítása", @@ -2141,11 +2323,13 @@ }, "mergeLinesWithSameText": { "maxMsBetweenLines": "Max. ezredmásodperc a sorok között", + "maxFramesBetweenLines": "Max. képkocka a sorok között", "includeIncrementingLines": "Növekvő szöveget tartalmazó sorok bevétele", "includeRollUpCaptions": "Gördülő feliratok bevétele" }, "mergeLinesWithSameTimeCodes": { "maxMsDifference": "Max. eltérés (ezredmásodperc)", + "maxFramesDifference": "Max. eltérés (képkocka)", "makeDialog": "Párbeszédek készítése" }, "netflixCheckAndFix": { @@ -2204,7 +2388,7 @@ "maximumDuration": "Maximális időtartam: felirateseményenként 7 másodperc", "italicsFixed": "Javított dőlt betűs formázás", "italicsNotAllowed": "A dőlt betűs formázás nem megengedett", - "bridgeGapsXToYFrames": "3-{0} képkockaköz => 2 képkockaköz ", + "bridgeGapsXToYFrames": "3-{0} képkockaköz => 2 képkockaköz", "maximumXCharactersPerSecond": "Maximum {0} karakter másodpercenként" }, "imageBasedEdit": { @@ -2260,7 +2444,14 @@ "xInTopBar": "{0} a felső sávban", "xInBottomBar": "{0} az alsó sávban", "noImageSubtitlesLoaded": "Nincsenek képfeliratok betöltve", - "imageBasedFormatNotSupported": "Kép alapú feliratformátum nem található/nem támogatott." + "imageBasedFormatNotSupported": "Kép alapú feliratformátum nem található/nem támogatott.", + "frameRateDeclaredInFile": "A Blu-ray sup fájlban megadott képkockasebesség", + "frameRateDetectedFromTimeCodesX": "A Blu-ray sup időkódjaiból észlelt képkockasebesség (a fájlban megadott: {0})", + "frameRateFromVideo": "A videó képkockasebessége", + "frameRateCurrent": "Jelenlegi képkockasebesség", + "frameRateSetManually": "Kézzel beállított képkockasebesség", + "frameRateUsageInfo": "A Blu-ray sup, BDN XML, DOST és FCP exportok ezzel a képkockasebességgel készülnek. Az időket nem módosítja - ehhez használja a \"Képkockasebesség módosítása\" funkciót.", + "useVideoFrameRateXInsteadOfY": "A videó képkockasebessége {0}, de a felirat {1} értékre van állítva.\n\nHasználja a videó képkockasebességét ({0})?" }, "removeTextForHearingImpaired": { "interjections": "Indulattszavak", @@ -2290,6 +2481,7 @@ "pickFontNameInstalledFonts": "Telepített betűkészletek", "pickFontNameCollectedFonts": "Összegyűjtött betűkészletek", "colorPickerTitle": "Szín kiválasztása", + "colorPickerBrightness": "Fényerő", "filterLayersTitle": "Rétegek szűrése a megjelenítéshez", "filterLayersHideFromWaveform": "Elrejtés a hullámformáról/spektrogramról", "filterLayersHideFromSubtitleGrid": "Elrejtés a feliratrácsból", @@ -2340,7 +2532,21 @@ "correctWordsX": "Helyes szavak: {0}", "namesX": "Nevek: {0}", "addedToDictionaryX": "Hozzáadva a szótárhoz: {0}", - "doNotShowThisAgain": "Ne mutassa ezt az üzenetet többé" + "doNotShowThisAgain": "Ne mutassa ezt az üzenetet többé", + "editUseAlwaysList": "\"Mindig használja\" lista szerkesztése...", + "useAlwaysListTitle": "Helyesírás-ellenőrzés - \"Mindig használja\" lista", + "useAlwaysListHeader": "\"Mindig használja\" lista", + "useAlwaysListDescription": "A helyesírás-ellenőrző által automatikusan cserélt szavak, amelyeket az \"Összes módosítása\" és a \"Mindig ezt használja\" ment el. Egy pár csak akkor kerül felhasználásra, ha a szó hibásan van írva.", + "useAlwaysWord": "Hibásan írt szó", + "xPairs": "{0:#,##0} pár", + "xUnusedPairs": "{0:#,##0} soha nem használt", + "useAlwaysUnusedHint": "Soha nem használt - ez a szó helyesen van írva (vagy név, illetve felhasználói szó), így a helyesírás-ellenőrző sosem jelöli meg", + "removeUnused": "Nem használtak eltávolítása", + "useAlwaysEmpty": "Ehhez a nyelvhez még nincsenek \"Mindig használja\" párok", + "useAlwaysEmptyHint": "A párok akkor kerülnek hozzáadásra, amikor a helyesírás-ellenőrzésben az \"Összes módosítása\" vagy a \"Mindig ezt használja\" gombra kattint.", + "useAlwaysNoMatch": "Nincs a keresésnek megfelelő pár", + "useAlwaysSameWord": "A cserének különböznie kell a szótól.", + "useAlwaysRememberOff": "A \"'Mindig használja' lista megjegyzése\" ki van kapcsolva a Beállításokban, így a helyesírás-ellenőrző nem használja és nem menti ezt a listát." }, "video": { "burnIn": { @@ -2378,10 +2584,21 @@ "translateToEnglish": "Fordítás angolra", "transcribing": "Átírás...", "transcribingXOfY": "{0} / {1} átírása...", + "linesTranscribedXOfYFailedZ": "{1} sorból {0} átírva - {2} sikertelen volt, vagy nem tartalmazott beszédet.\n\nA sikertelen sorok megtartják jelenlegi szövegüket. Alkalmazza az átírt sorokat, vagy próbálja újra a sikerteleneket (előtte módosíthatja a motort vagy a beállításokat)?", + "linesTranscribedXOfYCancelled": "Az átírás megszakítva {1} sorból {0} után.\n\nAlkalmazza az átírt sorokat? A többi sor megtartja jelenlegi szövegét.", + "noLinesTranscribed": "A(z) {0} sor egyike sem volt átírható.\nA részletekért nézze meg az eszközök naplóját.", + "applyTranscribedLines": "Átírtak alkalmazása", + "retryFailedLines": "Sikertelenek újra", "isolateSpeech": "Beszéd elkülönítése (lassú)", "isolateSpeechHint": "Eltávolítja a zenét és a hangeffekteket az átírás előtt. Kevesebb kihagyott sort és jobb időzítést eredményez a hangos zenét tartalmazó hanganyagoknál, de GPU-val körülbelül annyi ideig tart, mint maga a hanganyag - GPU nélkül pedig sokszorosáig.", "isolatingSpeech": "Beszéd elkülönítése...", "isolateSpeechFailed": "Nem sikerült elkülöníteni a beszédet - ehelyett az eredeti hanganyag kerül átírásra.", + "detectSpeakers": "Beszélők felismerése", + "detectSpeakersHint": "Minden sort a beszélővel kezd, például \"(speaker 0)\", az NVIDIA Sortformer beszélő-szétválasztásával (legfeljebb 8 beszélő). Azokkal a háttérrendszerekkel működik a legjobban, amelyek beszélőváltásnál bontják a sorokat, például Parakeet és Cohere.", + "detectSpeakersNeedsNewerCrispAsr": "A \"Beszélők felismerése\" funkcióhoz Crisp ASR v{0} vagy újabb szükséges, de v{1} van telepítve.\n\nFrissítse a Crisp ASR-t (a motor melletti fogaskerék gomb), vagy kapcsolja ki a \"Beszélők felismerése\" funkciót.", + "xNeedsNewerCrispAsr": "A(z) {0} motorhoz Crisp ASR v{1} vagy újabb szükséges, de v{2} van telepítve.\n\nLetölti és telepíti most a frissítést?", + "vad": "VAD", + "vadHint": "Hangaktivitás-érzékelés: csak a beszéd kerül átírásra, a beszélő szüneteinél vágva.\nAuto: Silero, és csak az ezt igénylő motorokhoz (Cohere, Mega, Index-Echo).\nSilero, FireRedVAD, Whisper-VAD, WebRTC: mindig bekapcsolva, minden Crisp ASR motorhoz. A FireRedVAD a legpontosabb, a Whisper-VAD adja a legkevesebb, legtisztább vágást, de a leglassabb.\nAz Index-Echo mindig a Silerót használja. A speciális beállításokban megadott --vad, --vad-model vagy --chunk-seconds elsőbbséget élvez.", "inputLanguage": "Bemeneti nyelv", "advancedWhisperSettings": "Speciális beszédfelismerési paraméterek", "downloadingSpeechToTextEngine": "Beszédfelismerő motor letöltése", @@ -2465,6 +2682,19 @@ "videoEncodingSettings": "TTS - Videokódolási beállítások", "elevenLabsSettings": "TTS - ElevenLabs beállítások", "elevenLabsSettingsResetHint": "ElevenLabs beállítások visszaállítása az alapértelmezett értékekre", + "provider": "Szolgáltató", + "openAiCompatibleSettings": "TTS - OpenAI-kompatibilis beállítások", + "customServerUrl": "Egyéni szerver URL", + "customServerUrlHint": "Egy OpenAI-kompatibilis szerver teljes beszéd-végpontja, amelyet akkor használ, ha a szolgáltató \"Custom\", pl. http://localhost:8880/v1/audio/speech", + "customModels": "Egyéni modellek", + "customModelsHint": "Vesszővel elválasztott modellnevek, amelyek akkor jelennek meg, ha a szolgáltató \"Custom\"", + "customVoices": "Egyéni hangok", + "customVoicesHint": "Vesszővel elválasztott hangnevek, amelyeket a \"Custom\" szolgáltató és a hanglistát nem közzétevő OpenRouter modellek használnak", + "instructions": "Utasítások", + "instructionsHint": "Opcionális beszédstílus, pl. \"Beszélj nyugodt, meleg hangon\". Csak az ezt támogató modelleknek kerül elküldésre (a tts-1/tts-1-hd nem)", + "openAiSpeedHint": "Beszédsebesség 0,25 és 4,0 között (1,0 a normál). Nem minden modell támogatja", + "outputFormat": "Kimeneti formátum", + "outputFormatHint": "A szervertől kért hangformátum. Az \"Auto\" MP3-at kér, és PCM-re vált azoknál a modelleknél, amelyek csak PCM-et adnak vissza (pl. Gemini TTS)", "regenerateAudio": "Hang újragenerálása", "autoContinuePlaying": "Automatikus lejátszás folytatása", "playLine": "Sor lejátszása", @@ -2492,6 +2722,17 @@ "voiceManagerTitle": "TTS - Hangkezelő", "voiceManagerDotDotDot": "Hangkezelő...", "voiceManagerMenuItem": "TTS hangkezelő...", + "speakFromCurrentLineTitle": "Felolvasás az aktuális sortól", + "speakFromCurrentLineDotDotDot": "Felolvasás az aktuális sortól...", + "speakFromCurrentLineHint": "Egymás után felolvassa a sorokat, az aktuális sortól kezdve. A leállításhoz nyomja meg az Escape billentyűt, vagy kattintson egy másik sorra.", + "stopSpeaking": "Felolvasás leállítása", + "speakingLineX": "{0}. sor felolvasása... (Escape a leállításhoz)", + "playWithSpeechTitle": "Lejátszás felolvasással", + "playWithSpeechDotDotDot": "Lejátszás felolvasással...", + "playWithSpeechHint": "Lejátssza a videót, és felolvas minden sort, amikor a videó odaér. A szüneteltetés és a tekerés a szokásos módon működik; a leállításhoz nyomja meg az Escape billentyűt.", + "lowerVideoVolumeWhileSpeaking": "Videó hangerejének csökkentése felolvasás közben", + "pauseVideoWhenSpeechRunsLate": "Videó szüneteltetése, ha a felolvasás átnyúlik a következő sorba", + "waitingForSpeechLineX": "Beszéd generálása a(z) {0}. sorhoz... (Escape a leállításhoz)", "voiceKindClone": "Klónozott hang", "voiceKindPreset": "Beépített", "voiceKindModel": "Hangmodell", @@ -2539,7 +2780,7 @@ "cloneVoicePerLinePreparing": "Minden sor hangjának átvétele a videóból...", "cloneVoicePerLineNeedsVideo": "Minden sor hangjának klónozásához szükség van a videóra, amelyhez a felirat tartozik. Nyissa meg a videót, és próbálja újra.", "cloneVoicePerLineNoClips": "Nem sikerült hangot kinyerni a videóból, így nincs miből klónozni. Ellenőrizze, hogy a videó rendelkezik-e hangsávval.", - "cloneVoicePerLineNeedsOriginalSubtitleX": "A(z) {0} csak akkor tud egy sort klónozni, ha tudja, mi hangzik el a videóban az adott sornál, ez pedig az eredeti nyelvű feliratból származik. Mivel nincs betöltve eredeti felirat, a rendszer minden sort az első importált hangon szólaltatna meg a videóban hallható hang helyett.\n\nNyissa meg az eredeti feliratot (Fájl > Eredeti megnyitása...) a fordítás mellett, majd próbálja meg újra a generálást.", + "cloneVoicePerLineTranscribeClipsXYZ": "A(z) {0} csak akkor tud egy sort klónozni, ha tudja, mi hangzik el a videóban annál a sornál. Ez {2} sorból {1} esetében nem ismert - általában az eredeti nyelvű feliratból származik (Fájl > Eredeti megnyitása...).\n\nÁtírja most ezeket a sorokat a videóból beszédfelismeréssel?\n\nIgen: válassza ki a motort és a videóban beszélt nyelvet, és a referencia-klipek átírása a beszéd generálása előtt megtörténik.\nNem: generálás mindenképp - ezeket a sorokat egy általános hang mondja el a videóban hallható hang helyett.", "voiceCloneConsentTitle": "Hangklónozás - mielőtt elkezdené", "voiceCloneConsentHeader": "Ön egy hang klónozására készül", "voiceCloneConsentIntro": "A klónozás egy valós személy hangját másolja. Ez legtöbb helyen szabályokkal jár, az EU-ban pedig olyan jogi kötelezettségekkel, amelyek Önre hárulnak, nem a Subtitle Edit-re. Kérjük, olvassa el ezt egyszer.", @@ -2556,14 +2797,29 @@ "advancedTtsSettings": "Speciális TTS beállítások", "advancedTtsAudioProcessing": "Hangfeldolgozás", "advancedTtsOutput": "Kimenet", + "advancedTtsVoiceCloning": "Hangklónozás", + "cleanCloneReferences": "Hangklón-referenciák tisztítása (Sidon)", + "cleanCloneReferencesDescription": "Eltávolítja a zenét, zajt és visszhangot abból a hangból, amelyből a hangot klónozza: a videóból vett klipekből és a hangkezelőben importált felvételekből. A Sidon beszéd-helyreállító modellt használja az audio.cpp-n (940 MB letöltés). A klónozott hang többé nem másolja a hátteret, de a nagyon halk vagy egymásra beszélő hangok tompán szólhatnak.", + "cleaningCloneReferencesDotDotDot": "Hangklón-referenciák tisztítása...", + "downloadSidonTitle": "Letölti a Sidont?", + "downloadSidonQuestionX": "A hangklón-referenciák tisztításához a Sidon beszéd-helyreállító modell szükséges ({0}).\n\nLetölti most? Nélküle a referenciák változatlanul kerülnek felhasználásra.", "proAudioPostProcessing": "Profi hang-utófeldolgozás", "proAudioPostProcessingDescription": "EQ-t, zajkaput, kompressziót, hangerő-normalizálást (-16 LUFS) és egy rövid be-/kifakulást ad minden kliphez.", + "generatingSpeechSegmentXOfY": "Beszéd generálása: {0}. szegmens ({1} közül)", + "adjustingSpeedSegmentXOfY": "Sebesség módosítása: {0}. szegmens ({1} közül)", + "mergingAudioSegmentXOfY": "Hang egyesítése: {0}. szegmens ({1} közül)", + "postProcessingSegmentXOfY": "Utófeldolgozás: {0}. szegmens ({1} közül)", "audioDucking": "Hang háttérhalkítás", "audioDuckingDescription": "Lehalkítja az eredeti videóhangot, és rámixeli a beszédet, így az eredeti sáv halkan hallható marad. Csak akkor érvényes, ha a beszéd a videófájlhoz van hozzáadva.", "removeOriginalSpeech": "Eredeti beszéd eltávolítása (lassú)", "removeOriginalSpeechDescription": "Megtartja az eredeti videó zenéjét és hangeffektjeit, de eltávolítja annak beszédhangját, és arra keveri rá az új beszédet. GPU-val körülbelül annyi ideig tart, mint maga a videó, GPU nélkül pedig sokszorosáig. Bekapcsolt audio ducking esetén annak hangerőbeállítása a zenére és effektekre vonatkozik. Csak akkor érvényesül, ha a beszéd hozzáadásra kerül a videófájlhoz.", "removingOriginalSpeech": "Eredeti beszéd eltávolítása...", "removeOriginalSpeechFailed": "Nem sikerült eltávolítani az eredeti beszédet - a beszéd az eredeti beszéd eltávolítása nélkül került a videóhoz.", + "speechRemovalModel": "Modell", + "speechRemovalModelDescription": "A legjobb minőség őrzi meg leginkább a zenét és a hangeffekteket. A gyors körülbelül háromszor gyorsabb, de a zenéből kicsit több vész el.", + "speechRemovalBestQuality": "Legjobb minőség (Mel-Band RoFormer)", + "speechRemovalFast": "Gyors (HTDemucs)", + "videoTruncatedWarning": "Az új videófájlban a kép {0} után megáll, de az eredeti videó {1} hosszú - a kép kimerevedik, miközben a hang folytatódik.\n\nA fájl megmaradt. Az ffmpeg kimenetét a Subtitle Edit adatmappájában lévő error-log.txt fájlban találja.", "originalVolumePercent": "Eredeti hangerő %", "vadSilenceCompression": "VAD csend-kompresszió", "vadSilenceCompressionDescription": "Leredukálja a szavak közötti szüneteket a beszéd gyorsítása helyett, így a klip minőségromlás nélkül illeszkedik.", @@ -2646,7 +2902,9 @@ "timeCodeFormatColon": "Időkód formátuma:", "shotChangesList": "Vágásváltások listája", "shotChangesClearQuestion": "Biztosan törölni szeretné az összes vágásváltást?", - "deleteSelectedShotChangeQuestion": "Biztosan törölni szeretné a kijelölt vágásváltást?" + "deleteSelectedShotChangeQuestion": "Biztosan törölni szeretné a kijelölt vágásváltást?", + "shotChanges": "Vágásváltások", + "exportShotChanges": "Vágásváltások exportálása" }, "chapters": { "title": "Fejezetek", @@ -2709,6 +2967,7 @@ "runningOcrXY": "OCR futtatása... {0}/{1}", "noLinesFoundTitle": "Nem találhatók feliratok", "noLinesFoundMessage": "Nem található szöveg a beolvasási területen - próbálja meg módosítani a beolvasási területet, a motort vagy a minimális fényerőt.", + "ocrStoppedEarlyMessage": "Az OCR-motor leállt, mielőtt minden képkockát beolvasott volna, így az eddig talált sorok megmaradnak, a többi hiányzik.{0}{0}{1}", "linesFoundX": "{0} sor található", "refiningTimingXY": "Időzítés finomítása... {0}/{1}", "fixOcrErrors": "OCR hibák javítása", @@ -2790,8 +3049,11 @@ "openSecondarySubtitleOnVideoPlayerDotDotDot": "Második feliratfájl megnyitása...", "openSecondarySubtitleOnVideoPlayer": "Második feliratfájl (a videólejátszón)", "removeSecondarySubtitleOnVideoPlayer": "Második feliratfájl eltávolítása", + "editSecondarySubtitleSettings": "Második felirat beállításainak szerkesztése", + "editSecondarySubtitleSettingsDotDotDot": "Második felirat beállításainak szerkesztése...", "secondarySubtitleRememberSettings": "Megjegyzendők ezek a beállítások", "secondarySubtitleDoNotShowAgain": "Ne jelenítse meg újra ezt a párbeszédablakot", + "secondarySubtitleJustifyLines": "Sorok sorkizárása", "openRecentVideo": "Legutóbbi videó megnyitása", "clearRecentVideos": "Legutóbbi videók törlése", "cutVideoTitle": "Videó vágása", @@ -2803,10 +3065,18 @@ "remuxVideoOutputFormat": "Kimeneti formátum:", "remuxVideoOutputFile": "Kimeneti fájl:", "remuxVideoRemuxing": "Videó és hang újracsomagolása...", + "remuxVideoFinalizing": "Kimeneti fájl véglegesítése (index áthelyezése az elejére)...", "remuxVideoCompleted": "A videó újracsomagolása sikeresen megtörtént.", "remuxVideoFailed": "Nem sikerült a videó újracsomagolása.", "remuxVideoPleaseSelectBoth": "Kérjük, válassza ki a videó- és a hangfájlt is.", "remuxVideoAssRequiresMkv": "Az ASS/SSA feliratokhoz MKV konténer szükséges az összes formázás és stílus megőrzéséhez.", + "remuxVideoSccRequiresMov": "A Scenarist (.scc) feliratok CEA-608 zárt feliratként kerülnek beágyazásra, amihez MOV vagy MPG konténer szükséges. A kimeneti formátum automatikusan .mov-ra váltott.", + "remuxVideoSccNotWithWebM": "A Scenarist (.scc) zárt feliratokhoz MOV konténer szükséges, amely nem tudja tárolni egy .webm fájl VP9/AV1 videóját. Távolítsa el az .scc fájlt, vagy alakítsa át más feliratformátumra.", + "remuxVideoMpgMaxTwoCaptionTracks": "Egy .mpg fájl legfeljebb két zárt feliratsávot tartalmazhat (CC1 és CC3) - távolítson el feliratfájlokat, hogy egy vagy kettő maradjon.", + "remuxVideoMccRequiresMpg": "A MacCaption (.mcc) feliratok CEA-608/708 zárt feliratként kerülnek beágyazásra az MPEG-2 videóba, amihez MPG konténer szükséges. A kimeneti formátum automatikusan .mpg-re váltott.", + "remuxVideoMpgReencodeVideoX": "Az .mpg fájlban lévő zárt feliratokhoz MPEG-2 videó szükséges, a videó pedig {0}. Újrakódolja a videót MPEG-2 formátumra? Ez tovább tart, és kissé rontja a minőséget.", + "remuxVideoFileHasNoAudioX": "A(z) \"{0}\" nem tartalmaz hangsávot, így nem használható hangforrásként.", + "remuxVideoAddingClosedCaptions": "Zárt feliratok hozzáadása...", "remuxVideoSelectAudioTrack": "Hangsáv kiválasztása", "remuxVideoSelectAudioTrackFor": "Hangsáv kiválasztása ehhez: {0}", "remuxVideoSelectAudioTrackPrompt": "A(z) '{0}' fájl {1} hangsávot tartalmaz. Kérjük, válassza ki az újracsomagolandó hangsávot:", @@ -2819,6 +3089,7 @@ "remuxVideoMixAudio": "Hangsávok keverése egyetlen sávvá", "remuxVideoVolumePercent": "Hangerő %:", "remuxVideoVolumeX": "Hangerő {0}%", + "remuxVideoFastStart": "Optimalizálás webes streameléshez (index áthelyezése az elejére - lassabb)", "embedSubtitlesDotDotDot": "Beágyazott feliratok hozzáadása/eltávolítása...", "generateTimeCodes": "Időkódok generálása", "checkeredImage": "Sakktábla minta", @@ -2828,6 +3099,34 @@ "cutVideoSplitSegments": "Szegmensek mentése külön-külön", "cutVideoAlsoCutSubtitle": "Felirat vágása is", "cutVideoSubtitleFileGeneratedX": "Generált feliratfájl: {0}", + "cutVideoTransition": "Átmenet a szegmensek között", + "cutVideoTransitionDurationSeconds": "Időtartam (másodperc)", + "cutVideoFadeInSeconds": "Áttűnés feketéből az elején (másodperc)", + "cutVideoFadeOutSeconds": "Áttűnés feketébe a végén (másodperc)", + "cutVideoPreviewTransition": "Átmenet előnézete", + "cutVideoPreviewTransitionTitle": "Átmenet előnézete", + "cutVideoPreviewNeedsJoin": "Nincs megtekinthető illesztés - adjon hozzá legalább két egyesítendő szegmenst, vagy egy, a közepéből kivágandó szegmenst.", + "cutVideoTransitionInfoX": "Minden átmenet átfedi az általa összekapcsolt részeket, így a kimenet illesztésenként {0} másodperccel rövidebb lesz. A vágott felirat időzítése ennek megfelelően módosul.", + "cutVideoTransitionFade": "Áttűnés", + "cutVideoTransitionFadeBlack": "Áttűnés feketén keresztül", + "cutVideoTransitionFadeWhite": "Áttűnés fehéren keresztül", + "cutVideoTransitionFadeGrays": "Áttűnés szürkén keresztül", + "cutVideoTransitionDissolve": "Átoldás", + "cutVideoTransitionWipeLeft": "Söprés balra", + "cutVideoTransitionWipeRight": "Söprés jobbra", + "cutVideoTransitionWipeUp": "Söprés felfelé", + "cutVideoTransitionWipeDown": "Söprés lefelé", + "cutVideoTransitionSlideLeft": "Csúsztatás balra", + "cutVideoTransitionSlideRight": "Csúsztatás jobbra", + "cutVideoTransitionSlideUp": "Csúsztatás felfelé", + "cutVideoTransitionSlideDown": "Csúsztatás lefelé", + "cutVideoTransitionSmoothLeft": "Lágy balra", + "cutVideoTransitionSmoothRight": "Lágy jobbra", + "cutVideoTransitionCircleOpen": "Kör nyitás", + "cutVideoTransitionCircleClose": "Kör zárás", + "cutVideoTransitionRadial": "Sugaras", + "cutVideoTransitionPixelize": "Pixelesítés", + "cutVideoTransitionBlur": "Elmosás", "importCurrentSubtitle": "Aktuális felirat importálása", "addRemoveEmbeddedSubtitlesTitle": "Beágyazott feliratok hozzáadása/eltávolítása", "addRemoveEmbeddedSubtitlesMp4Title": "Beágyazott feliratok hozzáadása/eltávolítása (MP4)", @@ -2890,6 +3189,10 @@ "reEncodeGeneratingDone": "Generálás kész", "reEncodeGeneratedFilesX": "Generált fájlok ({0}):", "reEncodeFfmpegParameters": "ffmpeg parameters", + "analyzingVideoX": "Videó elemzése... {0}% {1}", + "analyzingVideoXofY": "Videó elemzése {0}/{1}... {2}% {3}", + "backgroundImageFileNotSelected": "Nincs kiválasztva háttérképfájl", + "backgroundImageFileDoesNotExistX": "A háttérképfájl nem létezik: {0}", "voices": "Hangok", "presets": "Előbeállítások", "talkerX": "Beszélő {0}", @@ -2917,6 +3220,8 @@ "cloneVoiceTo": "Hang klónozása ide:", "cloneVoiceNameTitle": "Új hang neve", "cloneVoiceExtractFailed": "Nem sikerült a hang kinyerése a kiválasztott sorhoz.", + "lineXHasNoDuration": "A(z) {0}. sornak nincs időtartama (a záróidő nem a kezdőidő után van), így nincs kinyerhető hang.", + "audioClipsSkippedLinesX": "Ezekhez a sorokhoz nem sikerült hangot kinyerni, ezért ki lettek hagyva: {0}", "toggleShotChange": "Vágásváltás ki-/bekapcsolása", "resetWaveformZoomAndSpeed": "Hullámforma nagyítás és sebesség visszaállítása", "showOriginalSubtitle": "Eredeti felirat megjelenítése", @@ -2958,6 +3263,7 @@ "adjustSelectedLinesAndForward": "Kijelölt sorok és a következők igazítása", "toFrameRate": "Képkockasebességre", "fromFrameRate": "Képkockasebességből", + "videoXFrameRateY": "{0} - {1} fps", "adjustAllTimes": "Minden időpont beállítása (korábbi/későbbi megjelenítés)", "showEarlier": "Korábbi megjelenítés", "showLater": "Későbbi megjelenítés", @@ -2967,7 +3273,8 @@ "syncPoints": "Szinkronizálási pontok", "pointSync": "Pontszinkron", "pointSyncViaOther": "Pontszinkron másik felirat alapján", - "syncPointCandidateInfo": "A {0}+ másodperces csend után kezdődő sorok ki vannak emelve - gyakran megbízható szinkronizálási pontokat alkotnak", + "syncPointCandidateAfterInfo": "Ki vannak emelve azok a sorok, amelyeket {0}+ másodperc csend követ - ezek gyakran megbízható szinkronizálási pontok", + "gapAfter": "Köz utána", "adjustAllShortcuts": "Billentyűparancsok:\n\n• Shift + Balra/Jobbra: Mozgatás 10 ezredmásodperccel\n• Ctrl + Balra/Jobbra: Mozgatás 100 ezredmásodperccel\n• Alt + Balra/Jobbra: Mozgatás 500 ezredmásodperccel", "adjustAllShortcutsFrames": "Billentyűparancsok:\n\n• Shift + Balra/Jobbra: Lépés 1 képkockát\n• Ctrl + Balra/Jobbra: Lépés 10 képkockát\n• Alt + Balra/Jobbra: Lépés 1 másodpercet", "offsetInSeconds": "Eltolás másodpercben", @@ -2987,6 +3294,7 @@ "lineMerge": "Soregyesítés", "delayInSecondsBetweenRequests": "Kérések közötti késleltetés másodpercben", "maxBytesPerRequest": "Max. bájt kérésenként", + "keepMusicLinesUntranslated": "Zenei szimbólumok közötti sorok (♪ ... ♪) fordításának kihagyása", "promptText": "Parancsszöveg", "resetPromptToDefault": "Prompt visszaállítása az alapértelmezettre", "translateEachLineSeparately": "Minden sor fordítása külön-külön", @@ -3124,9 +3432,12 @@ "newEmptyDefaultMs": "Új felirat alapértelmezett időtartama (ezredmp)", "timeCodeUpDownStepMs": "Idő növelésének/csökkentésének léptéke (ms)", "moveSelectedLinesStepMs": "Kijelölt sorok mozgatása billentyűparancs lépésköz (ezredmp)", + "moveStartEndStepMs": "Kezdet/vég mozgatása gyorsbillentyű lépésköze (ezredmp)", "moveLinesShortenNeighbor": "Sorok mozgatása: az előző/következő sor rövidítése az átfedés helyett", "promptBeforeDelete": "Rákérdezés törlés előtt", "rememberPositionAndSize": "Ablakpozíció és -méret megjegyzése", + "titleBarFullFileName": "Teljes fájlútvonal megjelenítése a címsorban", + "showRecentFiles": "Legutóbbi fájlok megjelenítése (újbóli megnyitáshoz)", "openLastFileOnStart": "A legutóbbi fájl megnyitása indításkor", "autoSave": "Automatikus mentés (a megnyitott fájl mentése szerkesztés közben)", "autoBackupOn": "Automatikus mentés", @@ -3153,6 +3464,8 @@ "waveformDrawGridLines": "Rácsvonalak rajzolása", "waveformUseSkiaRenderer": "Kísérleti gyors renderelő használata", "waveformCenterVideoPositionAlsoWhenPaused": "Videó pozíciójának középre igazítása szüneteltetés közben is", + "waveformSelectCurrentSubtitleWhilePaused": "Aktuális felirat kijelölése szüneteltetés közben is", + "waveformRememberZoom": "Nagyítás megjegyzése (vízszintes és függőleges)", "waveformShowToolbar": "Eszköztár megjelenítése", "waveformShowToolbarEdit": "Eszköztár szerkesztése...", "waveformShowToolbarEditLabel": "Eszköztár elemei", @@ -3200,8 +3513,11 @@ "webVttUseXTimestampMap": "WebVTT: X-TIMESTAMP-MAP használata (időkódok eltolása betöltéskor)", "assaAutoSetResolution": "ASSA: felbontás beállítása (PlayResX/PlayResY) a videóból, amikor egy videó megnyitásra kerül", "assaAutoSetResolutionPrompt": "ASSA: rákérdezés a felbontás megváltoztatása előtt a videónak megfelelően (ellenkező esetben automatikus újramintavételezés)", - "showStopButton": "Leállítás gomb megjelenítése", - "showFullscreenButton": "Teljes képernyő gomb megjelenítése", + "videoControls": "Videóvezérlők", + "videoControlsEdit": "Videóvezérlők szerkesztése...", + "videoControlsPositionText": "Pozíció / időtartam szöveg", + "videoControlsVideoFileName": "Videófájl neve", + "videoControlsPlayerName": "Videólejátszó neve", "fullscreenHideControls": "Videóvezérlők elrejtése teljes képernyőn", "showSecondarySubtitleDialog": "Stílus párbeszédablak megjelenítése második feliratfájl megnyitásakor", "rememberSecondarySubtitleFile": "Második feliratfájl megjegyzése", @@ -3215,6 +3531,7 @@ "adjustAllTimesRememberLineSelectionChoice": "Minden idő igazítása, sorválasztási döntés megjegyzése", "mergeKeepEndTime": "Sorok egyesítése: befejezési idő megtartása (átfedés engedélyezése a következő felirattal)", "mergeKeepEndTimeOnlyAssa": "Sorok egyesítése: befejezési idő megtartása csak az \"Advanced Sub Station Alpha\" esetében", + "mergeAutoBreak": "Sorok egyesítése: az egyesített szöveg automatikus tördelése (kikapcsolva a sorok saját sortörései megmaradnak)", "filesAndLogs": "Fájlok és naplók", "showErrorLogFile": "Hibanapló-fájl megjelenítése", "showToolsLogFile": "Eszközök naplófájljának megtekintése", @@ -3223,6 +3540,7 @@ "showAssaLayer": "ASSA rétegdoboz megjelenítése", "waveformCursorColor": "Hullámforma kurzor/fej színe", "waveformShotChangeColor": "Hullámforma vágásváltás színe", + "waveformGridColor": "Hullámforma rácsának színe", "waveformParagraphLeftColor": "Hullámforma bal oldali szegély színe", "waveformParagraphRightColor": "Hullámforma jobb oldali szegély színe", "waveformFancyHighColor": "Hullámforma különleges élénk szín", @@ -3250,10 +3568,15 @@ "resetSyntaxColoring": "Szintaxis-kiemelés alaphelyzetbe állítása", "resetWaveform": "Hullámforma alaphelyzetbe állítása", "resetRules": "Szabályok alaphelyzetbe állítása", - "useFrameMode": "Képkocka mód használata (óó.pp.mp.kk)", + "timeCodeMode": "Időkód mód", + "timeCodeModeTime": "Idő (hh:mm:ss,zzz)", + "timeCodeModeFrames": "Képkockák (hh:mm:ss:ff)", + "timeCodeModeFrameNumbers": "Képkockaszámok", + "frameModeSnapToVideoFrames": "Idők tárolása videó-képkockákon (nem másodpercen belüli képkockákon)", "textBoxLimitNewLines": "Sorok számának korlátozása a felirat szövegdobozában", "mpvOpenGl": "libmpv - OpenGL", "mpvSoftwareRendering": "libmpv - Software rendering (slow)", + "mpvMetalRendering": "libmpv - Metal (kísérleti, újraindítást igényel)", "mpvWidRendering": "libmpv - Native Window ID rendering", "waveFormsAndSpectrogramFoldersContainsX": "A \"Waveforms\" és \"Spectrogram\" mappák tartalma: {0}", "deleteWaveformAndSpectrogramFoldersQuestion": "Törli a \"Waveforms\" és \"Spectrogram\" fájlokat?", @@ -3327,6 +3650,7 @@ "useFocusedButtonBackgroundColor": "Aktív gomb háttérszínének használata", "focusedButtonBackgroundColor": "Aktív gomb háttérszíne", "forceCrLfOnSave": "CR+LF kényszerítése mentéskor (szöveges feliratfájlok)", + "linuxClipboardUseExternalTool": "Szöveg másolása xclip/wl-copy segítségével (megtartja az ékezetes karaktereket)", "showFormatLimitWarning": "Figyelmeztetés mentéskor, ha a sorok túllépik a formátum korlátait (pl. SCC 32 karakter/sor)", "textBoxButtonShowAutoBreak": "Szövegdoboz: automatikus tördelés gomb megjelenítése", "textBoxButtonShowUnbreak": "Szövegdoboz: összekapcsolás gomb megjelenítése", @@ -3339,12 +3663,17 @@ "allSettings": "Összes beállítás", "uiScale": "Felület méretezése (%)", "fontScale": "Betűkészlet skálázása (%)", + "textSelectionOpacity": "Szövegkijelölés átlátszatlansága (%)", "waveformToolbarItems": "Hullámforma eszköztár elemei", "matchIconColorToDarkTheme": "Ikon színének igazítása a sötét téma előtérszínéhez", "subtitlePreviewProperties": "Felirat-előnézet tulajdonságai", "usePositionFromSubtitleFile": "Pozíció használata a feliratfájlból (TTML/PAC/EBU STL)", "marginIsPartOfSubtitleArea": "A margó a feliratterület része", "textJustify": "Sorok sorkizárása", + "deinterlace": "Videó váltottsorosság-mentesítése", + "deinterlaceOff": "Ki", + "deinterlaceAuto": "Auto (csak váltottsoros képkockák)", + "deinterlaceAlways": "Mindig", "pixelWidthInfo": "Zöld vonalak = max. szélesség korlát | Piros terület = a szöveg túllépi a korlátot", "spellCheckEngineHunSpelll": "Hunspell", "spellCheckEngineMsWord": "MS Word", @@ -3373,11 +3702,34 @@ "categoryVideo": "Videó", "categorySync": "Szinkronizálás", "categoryAi": "MI", + "categoryCustom": "Egyéni", + "editCustomShortcut": "Egyéni gyorsbillentyű szerkesztése", + "customShortcutTextBoxKeyHint": "Itt Ctrl/Alt billentyűvel kombinált billentyűt használjon - egy sima billentyű többé nem gépelne a szövegmezőbe.", + "customShortcutNumberX": "#{0} egyéni gyorsbillentyű", + "customShortcutNumberXY": "#{0} egyéni gyorsbillentyű: {1}", + "customShortcutStep": "Lépés", + "customShortcutSteps": "Lépések", + "customShortcutStepRunCommand": "Parancs futtatása", + "customShortcutStepInsertText": "Szöveg beszúrása", + "customShortcutStepReplace": "Keresés és csere", + "customShortcutInsertAtCursor": "A kurzornál", + "customShortcutInsertAtStart": "A szöveg elején", + "customShortcutInsertAtEnd": "A szöveg végén", + "customShortcutSummaryRunCommandX": "Futtatás: {0}", + "customShortcutSummaryInsertXY": "\"{0}\" beszúrása ({1})", + "customShortcutSummaryReplaceXY": "\"{0}\" cseréje erre: \"{1}\"", + "customShortcutSummaryReplaceRegexXY": "\"{0}\" reguláris kifejezés cseréje erre: \"{1}\"", + "customShortcutPickCommand": "Válassza ki a futtatandó parancsot.", + "customShortcutEnterText": "Adja meg a beszúrandó szöveget.", + "customShortcutEnterFind": "Adja meg a keresendő szöveget.", + "customShortcutInvalidRegexX": "Érvénytelen reguláris kifejezés: {0}", + "customShortcutStepsHint": "A lépések fentről lefelé futnak. A szöveges lépések a kijelölt sorokon működnek; az ablakot megnyitó parancsok után a futás az ablak bezárásakor folytatódik.", "activeIn": "Aktív itt:", "activeInEverywhere": "Mindenhol", "generalMergeSelectedLinesAndUnbreak": "Kijelölt sorok egyesítése és összekapcsolás", "generalMergeSelectedLinesAndUnbreakCjk": "A kijelölt sorok összevonása és a tördelés feloldása szóköz nélkül (CJK)", "generalUnbreakNoSpaceCjk": "Megszakítás megszüntetése szóköz nélkül (CJK)", + "generalToggleBreak": "Tördelés/összekapcsolás váltása", "generalMergeSelectedLinesBilingual": "Kijelölt sorok egyesítése kétnyelvűként", "generalToggleTranslationMode": "Fordítási mód ki-/bekapcsolása", "generalToggleTranslationAndOriginalInPreviews": "Fordítás és eredeti átváltása a videó/hang előnézetben", @@ -3399,6 +3751,7 @@ "fileOpenKeepVideo": "Megnyitás (videó megtartása)", "fileSave": "Mentés", "fileSaveAs": "Mentés másként", + "fileSaveOriginal": "Eredeti mentése", "fileCompare": "Összehasonlítás", "fileStatistics": "Statisztika", "fileImportPlainText": "Formázatlan szöveg importálása", @@ -3419,6 +3772,7 @@ "detectKey": "Billentyű észlelése", "control": "Control", "alt": "Alt", + "altGr": "AltGr", "win": "Win", "shift": "Shift", "controlMac": "⌃", @@ -3429,6 +3783,11 @@ "resetShortcutsDetail": "Biztosan vissza szeretné állítani az összes billentyűparancsot az alapértelmezett értékekre?", "togglePlayPause": "Lejátszás/szünet ki-/bekapcsolása", "toggleLockTimeCodes": "Időkódok zárolásának ki-/bekapcsolása", + "toggleTimeCodeMode": "Időkód mód váltása (idő/képkockák/képkockaszámok)", + "toggleScreenPrivacy": "Képernyő-adatvédelem váltása (fájlnevek elrejtése, majd a szövegeké is)", + "screenPrivacyFileNamesHidden": "Képernyő-adatvédelem: fájlnevek elrejtve", + "screenPrivacyFileNamesAndTextsHidden": "Képernyő-adatvédelem: fájlnevek és szövegek elrejtve", + "screenPrivacyOff": "Képernyő-adatvédelem: ki", "duplicateSelectedLines": "Kijelölt sorok megkettőzése", "sourceView": "Forrásnézet", "addOrEditBookmark": "Könyvjelző hozzáadása vagy szerkesztése", @@ -3477,6 +3836,7 @@ "listViewColumnTextUp": "Oszlop, szöveg fel", "listViewColumnTextDown": "Oszlop, szöveg le", "autoTranslateSelectedLines": "Kijelölt sorok automatikus fordítása...", + "autoTranslateSelectedLinesNoPrompt": "Kijelölt sorok automatikus fordítása (rákérdezés nélkül, utolsó motor/nyelvek használatával)", "setAssaResolution": "ASSA felbontás beállítása (PlayResX/PlayResY)", "setShortcutForX": "Billentyűparancs beállítása ehhez: \"{0}\"", "commandFileNewKeepVideo": "Új (videó megtartása)", @@ -3557,6 +3917,14 @@ "surroundWith": "Körülvétel ezzel...", "surroundWithXY": "Körülvétel ezzel: {0}/{1}", "surroundWithNumberX": "Körülvétel ezzel: #{0}", + "surroundWithBehavior": "Viselkedés", + "surroundWithBehaviorToggle": "Váltás (hozzáadás, vagy eltávolítás, ha már van)", + "surroundWithBehaviorAdd": "Hozzáadás (minden alkalommal)", + "surroundWithBehaviorRemove": "Eltávolítás (összes)", + "surroundWithBehaviorRemoveOnce": "Eltávolítás (alkalmanként egy)", + "surroundWithWorksOn": "Hatókör", + "surroundWithScopeSelectionOrText": "Kijelölés, egyébként a teljes szöveg", + "surroundWithScopeEachLine": "Minden sor", "searchVia": "Keresés ezen keresztül:", "searchViaX": "Keresés ezen keresztül: {0}", "searchViaNumberX": "Keresés ezen keresztül: #{0}", @@ -3584,6 +3952,11 @@ "copySubtitlePathToClipboard": "Felirat útvonalának másolása a vágólapra", "copySubtitleOriginalPathToClipboard": "Eredeti felirat útvonalának másolása a vágólapra", "focusTextBox": "Fókusz a szövegdobozra", + "focusSubtitleListView": "Fókusz a feliratlista nézetre", + "focusWaveform": "Fókusz a hullámformára", + "focusOriginalTextBox": "Fókusz az eredeti szövegmezőre", + "textBoxGoToStart": "Szövegmező, ugrás az elejére", + "textBoxGoToEnd": "Szövegmező, ugrás a végére", "sortByNumber": "Rendezés sorszám szerint", "sortByStartTime": "Rendezés \"Megjelenítés\" ideje szerint", "sortByEndTime": "Rendezés \"Elrejtés\" ideje szerint", @@ -3623,6 +3996,7 @@ "googleIt": "Keresés a Google-on (kijelölt szöveg)", "setActorX": "Beszélő beállítása: {0}", "setNewActor": "Új szereplő beállítása...", + "setStyleX": "Stílus beállítása: {0}", "toggleDialogDashes": "Párbeszéd-gondolatjelek ki-/bekapcsolása", "mergeWithLineBeforeAsDialog": "Egyesítés az előző sorral párbeszédként", "mergeWithLineAfterAsDialog": "Egyesítés a következő sorral párbeszédként", @@ -3634,6 +4008,10 @@ "moveStartOneFrameForwardKeepGapPrev": "Kezdőpont eltolása egy képkockával előrefelé (távolság megtartása az előzőtől, ha közel van)", "moveEndOneFrameBackKeepGapNext": "Végpont eltolása egy képkockával visszafelé (távolság megtartása a következőtől, ha közel van)", "moveEndOneFrameForwardKeepGapNext": "Végpont eltolása egy képkockával előrefelé (távolság megtartása a következőtől, ha közel van)", + "moveStartXMsBack": "Kezdőpont mozgatása X ezredmásodperccel vissza (X a Beállításokban adható meg, a Vizuális szinkronizálásban is)", + "moveStartXMsForward": "Kezdőpont mozgatása X ezredmásodperccel előre (X a Beállításokban adható meg, a Vizuális szinkronizálásban is)", + "moveEndXMsBack": "Végpont mozgatása X ezredmásodperccel vissza (X a Beállításokban adható meg, a Vizuális szinkronizálásban is)", + "moveEndXMsForward": "Végpont mozgatása X ezredmásodperccel előre (X a Beállításokban adható meg, a Vizuális szinkronizálásban is)", "moveSelectedLinesXMsBack": "Kijelölt sorok mozgatása X ezredmásodperccel vissza (X beállítva a Beállításokban)", "moveSelectedLinesXMsForward": "Kijelölt sorok mozgatása X ezredmásodperccel előre (X beállítva a Beállításokban)", "moveSelectedLinesAndForwardXMsBack": "Kijelölt sorok és az összes azt követő mozgatása X ezredmásodperccel vissza (X beállítva a Beállításokban)", @@ -3788,6 +4166,8 @@ "binaryImageCompareInspectImageMatches": "\"Bináris képösszehasonlító\" - Képegyezések vizsgálata", "saveBlankTextTitle": "Menti az üres szöveget?", "saveBlankTextPrompt": "Menti az üres szöveget a képhez?", + "drawnLinesDoNotMatchTitle": "A vonalak nem egyeznek a képpel", + "drawnLinesDoNotMatchPrompt": "Néhány rajzolt vonal nem egyezik a képpel (a zöld vonalaknak a karakteren, a pirosaknak azon kívül kell lenniük), így ez a karakter nem lesz felismerve.\n\nMégis menti?", "yesAndNeverAskAgain": "Igen, és ne kérdezze meg többé", "importTextFromSubtitleDotDotDot": "Szöveg importálása feliratból...", "importTextFromSubtitleOverwritePrompt": "Néhány sor már tartalmaz OCR szöveget. Felülírja a meglévő szöveget?", @@ -3846,9 +4226,26 @@ "crispEmbedSettingsTitle": "CrispEmbed beállításai", "crispEmbedDescription": "Helyi OCR motor több modell háttérrel - töltse le a motort és a modelleket itt.", "trainNOcrDatabase": "nOCR adatbázis betanítása...", + "optimizeNOcrDatabase": "nOCR adatbázis optimalizálása...", + "optimizeNOcrDatabaseQuestionXY": "Eltávolítja azokat a vonalakat a(z) \"{0}\" nOCR adatbázisból, amelyek nem különböztetik meg a karaktereket a hasonló karakterektől?\n\nEz kisebbé teszi az adatbázist és gyorsabbá az OCR-t, általában kevesebb hibával. Legfeljebb egy percig tart.\n\nA biztonsági mentés neve: \"{1}\".", + "optimizingNOcrDatabaseXY": "nOCR adatbázis optimalizálása... {0}/{1}", + "nOcrDatabaseOptimizedXYZ": "A(z) \"{0}\" nOCR adatbázis optimalizálva: {1:#,##0} -> {2:#,##0} vonal.", "startTraining": "Betanítás indítása", "abortTraining": "Betanítás megszakítása", "trainingOptions": "Betanítási lehetőségek", + "trainNOcrSubtitle": "Karaktereket jelenít meg a telepített betűtípusokkal, és megtanítja az nOCR-nek az alakjukat. Válassza ki a feliratai által használt betűtípusokat.", + "xFontsSelected": "{0:#,##0} kijelölve", + "subtitleFonts": "Felirat betűtípusai", + "emptyDatabase": "Üres adatbázis", + "startFromDatabase": "Kiindulás", + "startFromDatabaseHint": "A betanított karakterek hozzáadása egy meglévő adatbázis másolatához (ajánlott), vagy kezdés egy üres adatbázisból", + "fontSizes": "Betűméretek", + "fontSizesHint": "Megjelenítési betűméretek képpontban, vesszővel elválasztva, pl. \"30, 40\"", + "numberOfLineSegmentsHint": "Karakterenként ellenőrzött vonalak - több szigorúbb, de lassabb", + "enterDatabaseName": "Adja meg az adatbázis nevét", + "trainingFontXSizeYCharacterZ": "\"{0}\" betanítása {1} px méretben: {2}", + "xLearned": "{0:#,##0} betanítva", + "xSkipped": "{0:#,##0} kihagyva", "charactersToTrain": "Betanítandó karakterek", "letterCombinationsToTrain": "Betűkombinációk, amelyek egy képpé válhatnak", "importCharactersFromSubtitleFile": "Karakterek importálása feliratfájlból...", @@ -3875,6 +4272,8 @@ "drawZoomOut": "Kicsinyítés (Ctrl+-)", "drawResetView": "Nézet visszaállítása (Ctrl+0)", "drawToggleGrid": "Rács ki-/bekapcsolása (Ctrl+G)", + "drawTogglePreview": "Előnézet be/ki (F9)", + "drawPreviewFailed": "Az előnézet sikertelen - libass-t tartalmazó ffmpeg szükséges (subtitles szűrő)", "drawCopyToClipboard": "Másolás a vágólapra (Ctrl+C)", "drawShapes": "Alakzatok", "drawSelectedPoint": "Kijelölt pont", @@ -3882,6 +4281,81 @@ "drawSelectedLayer": "Kijelölt réteg", "drawToolX": "Eszköz: {0}", "drawHelpText": "Kattintson pontok hozzáadásához • Enter/F8 az alakzat lezárásához • Shift+Húzás a mozgatáshoz • Ctrl+Görgetés a nagyításhoz", + "drawMoveToLayer": "Áthelyezés rétegre", + "drawFlipHorizontal": "Vízszintes tükrözés", + "drawFlipVertical": "Függőleges tükrözés", + "drawHideShape": "Alakzat elrejtése", + "drawShowShape": "Alakzat megjelenítése", + "drawHideLayer": "Réteg elrejtése", + "drawShowLayer": "Réteg megjelenítése", + "drawDeleteLayer": "Réteg törlése", + "drawDeletePoint": "Pont törlése", + "drawCancelDrawing": "Rajzolás megszakítása", + "drawLayerX": "{0}. réteg", + "drawImportSvg": "SVG-kép importálása...", + "drawRotateClockwise": "Forgatás 90°-kal az óramutató járásával megegyezően", + "drawConvertToCurve": "Szakasz átalakítása görbévé", + "drawBackground": "Háttér", + "drawPosition": "Pozíció", + "drawZoom": "Nagyítás", + "drawShapeTool": "Alakzat eszköz", + "drawColorPickerTool": "Szín kiválasztása egy alakzatról vagy a háttérről", + "drawShapeLibrary": "Alakzattár", + "drawShapeLibraryHint": "Húzzon a vásznon egy alakzat elhelyezéséhez, vagy kattintson az alapértelmezett mérethez. A Shift megtartja az arányokat.", + "drawAddToLibrary": "Hozzáadás az alakzattárhoz...", + "drawRemoveFromLibrary": "Eltávolítás az alakzattárból", + "drawShapeName": "Alakzat neve", + "drawCategorySpeechBubbles": "Szövegbuborékok", + "drawCategoryArrows": "Nyilak", + "drawCategoryBasicShapes": "Alapalakzatok", + "drawCategorySymbols": "Szimbólumok", + "drawCategoryMyShapes": "Saját alakzatok", + "drawShapeSpeechBubble": "Szövegbuborék", + "drawShapeSpeechBubbleRight": "Szövegbuborék, nyúlvány jobbra", + "drawShapeSpeechBubbleUp": "Szövegbuborék, nyúlvány felfelé", + "drawShapeOvalBubble": "Ovális szövegbuborék", + "drawShapeBoxBubble": "Szögletes szövegbuborék", + "drawShapeThoughtBubble": "Gondolatbuborék", + "drawShapeShoutBubble": "Kiáltásbuborék", + "drawShapeCaptionBox": "Képaláírás-doboz", + "drawShapeArrow": "Nyíl", + "drawShapeDoubleArrow": "Kettős nyíl", + "drawShapeChevron": "Csúcsos nyíl", + "drawShapeCurvedArrow": "Ívelt nyíl", + "drawShapeRoundedRectangle": "Lekerekített téglalap", + "drawShapeTriangle": "Háromszög", + "drawShapeDiamond": "Rombusz", + "drawShapePentagon": "Ötszög", + "drawShapeHexagon": "Hatszög", + "drawShapeOctagon": "Nyolcszög", + "drawShapeStar": "Csillag", + "drawShapeRing": "Gyűrű", + "drawShapePlus": "Plusz", + "drawShapeBanner": "Szalag", + "drawShapeHeart": "Szív", + "drawShapeCheck": "Pipa", + "drawShapeMusicNote": "Hangjegy", + "drawShapeMusicNotes": "Hangjegyek", + "drawShapeLightning": "Villám", + "drawShapeCloud": "Felhő", + "drawShapeMoon": "Hold", + "drawShapeBadge": "Jelvény", + "drawSize": "Méret", + "drawLayer": "Réteg", + "drawBackgroundVideoFrame": "Videó-képkocka (aktuális pozíció)", + "drawBackgroundVideoFrameAt": "Videó-képkocka itt...", + "drawBackgroundImage": "Képfájl...", + "drawBackgroundNone": "Nincs háttér", + "drawBackgroundStretch": "Kép nyújtása a vászonra", + "drawBackgroundOpacity": "Háttér átlátszatlansága", + "drawBackgroundVideoFrameFailed": "Nem sikerült videó-képkockát lekérni itt: {0}.", + "drawImages": "Képek", + "drawConvertToLine": "Szakasz átalakítása egyenessé", + "drawConvertShapeToCurves": "Minden egyenes átalakítása görbévé", + "drawConvertShapeToLines": "Minden görbe átalakítása egyenessé", + "drawRotateCounterClockwise": "Forgatás 90°-kal az óramutató járásával ellentétesen", + "drawSvgImages": "SVG-képek", + "drawSvgNoShapes": "Az SVG-fájlban nem található kitöltött alakzat vagy körvonal.", "progressBarTitle": "ASSA folyamatjelző sáv", "progressBarSettings": "Folyamatjelző sáv", "mouseOverColor": "Egér-rámutatás színe", @@ -3978,13 +4452,26 @@ "graphics": "Grafikák", "copyToStorageStyles": "Másolás a tároló stílusaiba", "copyToFileStyles": "Másolás a fájl stílusaiba", + "styleXAlreadyExistsInStorage": "Már létezik \"{0}\" nevű stílus a tárolóban.\n\nFelülírja, vagy megtartja mindkettőt (a másolat új nevet kap)?", + "styleXAlreadyExistsInFile": "Már létezik \"{0}\" nevű stílus a fájlban.\n\nFelülírja, vagy megtartja mindkettőt (a másolat új nevet kap)?", + "styleNameCannotBeEmpty": "A stílusnév nem lehet üres.", + "styleNameXIsUsedMoreThanOnce": "A(z) \"{0}\" stílusnevet több stílus is használja a fájlban.", + "overwrite": "Felülírás", + "keepBoth": "Mindkettő megtartása", + "doThisForAllConflictingStyles": "Ugyanez minden ütköző stílusnál", "setStyleAsDefault": "Stílus beállítása alapértelmezettként", "takeUsagesFromDotDotDot": "Használat átvétele innen...", "replaceStyleWithDotDotDot": "Stílus cseréje a következőre...", "noAttachmentsFound": "Nem találhatók mellékletek a kiválasztott ASSA fájlban.", "deleteStyleQuestion": "Törli a stílust?", "deleteStylesQuestion": "Törli a stílusokat?", + "deleteXStylesQuestion": "Biztosan törölni szeretne {0} stílust?", + "deleteXStylesFromStorageQuestion": "Biztosan törölni szeretne {0} stílust a tárolóból?", + "deleteStyleXFromStorageQuestion": "Biztosan törölni szeretné a(z) \"{0}\" stílust a tárolóból?", + "deleteStyleXFromFileQuestion": "Biztosan törölni szeretné a(z) \"{0}\" stílust az aktuális fájlból?", "openStyleImportFile": "Nyissa meg a feliratfájlt a stílusok importálásához", + "styleImportFiles": "Stílusfájlok (.ass, .sty, Subtitle Edit 4 kategóriák .template)", + "ssaStyleImportFiles": "Stílusfájlok (.ssa, .ass, .sty)", "primary": "Elsődleges", "secondary": "Másodlagos", "applyOverrideTags": "Felülbíráló címkék alkalmazása", diff --git a/src/ui/Assets/Languages/Indonesian.json b/src/ui/Assets/Languages/Indonesian.json index 2d6102cdacd..5d5fa4ba90d 100644 --- a/src/ui/Assets/Languages/Indonesian.json +++ b/src/ui/Assets/Languages/Indonesian.json @@ -1,12 +1,30 @@ { "title": "Subtitle Edit", - "version": "v5.2.0-rc7", + "version": "v5.3.0", "translatedBy": "Claude (Anthropic)", "cultureName": "id-ID", "general": { "abort": "Batalkan", "actor": "Aktor", "actors": "Aktor", + "setActorDotDotDot": "Atur aktor...", + "actorPickerTitle": "Atur aktor", + "actorPickerFilterHint": "Ketik untuk memfilter, atau ketik nama aktor baru", + "actorPickerKeysHint": "Tekan angka atau Enter untuk mengatur aktor. Alt+Atas/Bawah mengubah urutan, Delete menghapus aktor.", + "actorPickerNewActorX": "Aktor baru \"{0}\"", + "actorPickerLinesSelectedX": "Baris terpilih: {0}", + "setStyleDotDotDot": "Atur gaya...", + "stylePickerTitle": "Atur gaya", + "stylePickerFilterHint": "Ketik untuk memfilter, atau ketik nama gaya baru", + "stylePickerKeysHint": "Tekan angka (1-9, 0) untuk mengatur gaya pada semua baris terpilih.", + "stylePickerNewStyleX": "Gaya baru \"{0}\"", + "stylePickerNewStyleInfo": "Tekan Enter untuk menambahkan gaya ini dengan pengaturan baku dan menerapkannya pada baris terpilih. Gunakan \"Kelola gaya...\" untuk mengubah fon, warna, dan posisinya nanti.", + "stylePickerCurrentStyleX": "Gaya saat ini: {0}", + "stylePickerLinesUsingStyleX": "Baris yang memakai gaya ini: {0}", + "stylePickerMarginsXYZ": "Kiri {0}, kanan {1}, vertikal {2}", + "stylePickerSetStyle": "_Atur gaya", + "stylePickerManageStyles": "Kelola gaya...", + "stylePickerManageStylesHint": "Tutup jendela ini dan buka pengelola gaya untuk mengedit fon, warna, posisi, dan lainnya", "add": "Tambah", "addDotDotDot": "Tambah...", "addToNamesListCaseSensitive": "Tambahkan ke daftar nama (peka huruf besar/kecil)", @@ -15,7 +33,6 @@ "adjustAlpha": "Sesuaikan alfa (transparansi)", "adjustDisplayDuration": "Sesuaikan durasi tampil", "adjustment": "Penyesuaian", - "advanced": "Lanjutan", "advancedSettings": "Pengaturan lanjutan", "after": "Sesudah", "alignment": "Perataan", @@ -60,7 +77,6 @@ "bdnXml": "BDN/xml", "bdnXml8Bit": "BDN/xml 8-bit", "before": "Sebelum", - "beforeX": "Sebelum \"{0}\"", "beginning": "Awal", "bluRaySup": "Blu-ray (sup)", "bold": "Tebal", @@ -72,7 +88,6 @@ "bookmarkEdit": "Edit markah", "bookmarkEditForSelectedLinesX": "Edit markah untuk {0} baris terpilih", "bookmarksList": "Daftar markah", - "borderColor": "Warna garis tepi", "borderStyle": "Gaya garis tepi", "bottomCenter": "Bawah-Tengah", "bottomLeft": "Bawah-Kiri", @@ -99,7 +114,6 @@ "changeCasing": "Ubah kapitalisasi", "changeFormatting": "Ubah pemformatan", "changeFrameRate": "Ubah laju bingkai", - "changeLanguageFilter": "Ubah filter bahasa...", "changeOnce": "Ubah sekali", "changeSpeed": "Ubah kecepatan", "character": "Karakter", @@ -141,7 +155,6 @@ "conversionCancelledByUser": "Konversi dibatalkan oleh pengguna", "convert": "Konversi", "converted": "Terkonversi", - "convertingDotDotDot": "Mengonversi...", "convertingXofYDotDoDot": "Mengonversi {0:#,###,##0}/{1:#,###,##0}...", "copy": "Salin", "copyImageToClipboard": "Salin gambar ke papan klip", @@ -152,7 +165,6 @@ "couldNotOpenFileXErrorY": "Tidak dapat membuka file \"{0}\". Kesalahan: {1}", "count": "Jumlah", "cps": "Karakter/dtk", - "currentSubtitle": "Subtitel saat ini", "cudaRuntime": "Runtime CUDA", "currentVideoPosition": "Posisi video saat ini", "custom": "Kustom", @@ -212,7 +224,6 @@ "expand": "Bentangkan", "export": "Ekspor", "exportDotDotDot": "Ekspor...", - "exportToX": "Ekspor ke {0}", "extendAfter": "Perpanjang/perpendek ke baris sesudahnya", "extendBefore": "Perpanjang/perpendek ke baris sebelumnya", "extendSelectedLinesToNextShotChangeOrNextSubtitle": "Perpanjang baris terpilih ke perubahan shot berikutnya (atau subtitel berikutnya)", @@ -229,7 +240,6 @@ "fileExtension": "Ekstensi file", "fileName": "Nama file", "fileNameX": "Nama file: {0}", - "fileNameXAndSize": "Nama file: {0} ({1})", "fileSaved": "File disimpan", "fileSavedToX": "File disimpan ke {0}", "fileXAlreadyExists": "File \"{0}\" sudah ada.", @@ -239,8 +249,6 @@ "find": "Cari", "findNext": "Cari berikutnya", "findTextX": "Cari teks - {0}", - "fiveHundredMilliseconds": "500 milidetik", - "fiveSeconds": "Lima detik", "fix": "Perbaiki", "fixCommonErrors": "Perbaiki kesalahan umum", "fixRightToLeft": "Perbaiki kanan-ke-kiri", @@ -264,14 +272,12 @@ "foundXInLineYZ": "Ditemukan \"{0}\" di baris {1}, posisi {2}", "foundXMatches": "Ditemukan {0} kecocokan", "frameRate": "Laju bingkai", - "frameRateX": "Laju bingkai: {0:0.0###}", "frames": "Bingkai", "from": "Dari", "fromCurrentVideoPosition": "dari posisi video saat ini", "fullScreen": "Layar penuh", "gap": "Jeda", "general": "Umum", - "generalText": "Umum", "generate": "Buat", "generateImportShotChanges": "Buat/impor perubahan shot", "generating": "Membuat...", @@ -289,12 +295,8 @@ "help": "Bantuan", "hex": "Heks", "hide": "Sembunyikan", - "hidePreview": "Sembunyikan pratinjau", "history": "Riwayat", "horizontal": "Horizontal", - "hourMinutesSecondsDecimalSeparatorMilliseconds": "Jam:mnt:dtk{0}md", - "hourMinutesSecondsFrames": "Jam:mnt:dtk:bingkai", - "ip": "IP", "id": "ID", "image": "Gambar", "imageSaved": "Gambar disimpan", @@ -355,6 +357,7 @@ "mergeSelectedLinesDialog": "Gabungkan baris terpilih sebagai dialog", "mergeWithLineAfterAndAutoBreak": "Gabungkan dengan baris sesudahnya dan pisah baris otomatis", "mergeWithLineAfterKeepBreaks": "Gabungkan dengan baris sesudahnya (pertahankan pemisah baris)", + "mergeWithLineAfterAndUnbreak": "Gabungkan dengan baris sesudahnya dan satukan baris", "mergeWithLineBeforeAndAutoBreak": "Gabungkan dengan baris sebelumnya dan pisah baris otomatis", "mergeWithLineBeforeKeepBreaks": "Gabungkan dengan baris sebelumnya (pertahankan pemisah baris)", "middleCenter": "Tengah-Tengah", @@ -369,13 +372,13 @@ "moveAllShotChangeOneFrameForward": "Geser semua perubahan shot satu bingkai maju", "multiMode": "Mode multi", "moveDown": "Pindah ke bawah", + "increase": "Tambah", + "decrease": "Kurangi", "moveUp": "Pindah ke atas", "moveToTop": "Pindahkan ke atas", "moveToBottom": "Pindahkan ke bawah", "multipleReplace": "Penggantian ganda", - "mute": "Bisukan", "name": "Nama", - "negative": "Negatif", "new": "Baru", "newDotDotDot": "Baru...", "newProfile": "Profil baru", @@ -405,20 +408,15 @@ "offsetY": "Offset Y", "ok": "_OK", "oneFile": "Satu file", - "oneHundredMilliseconds": "100 milidetik", - "oneSecond": "Satu detik", "oneSecondBack": "Satu detik mundur", "oneSecondForward": "Satu detik maju", - "onlineVideoFeatureNotAvailable": "Fitur tidak tersedia untuk video daring", "openContainingFolder": "Buka folder berisi file", "openDictionaryFolder": "Buka folder kamus", "openFile": "Buka file", "openImageBasedSubtitle": "Buka subtitel berbasis gambar", "openImageFile": "Buka file gambar", - "openOriginalSubtitleFile": "Buka file subtitel asli...", "openOriginalSubtitleFileTitle": "Buka file subtitel asli", "openOutputFolder": "Buka folder keluaran", - "openSubtitle": "Buka subtitel...", "openSubtitleFileTitle": "Buka file subtitel", "openSubtitles": "Buka subtitel...", "openVideoFile": "Buka file video...", @@ -433,11 +431,6 @@ "outlineWidth": "Lebar garis luar", "outputFolder": "Folder keluaran", "outputProperties": "Properti keluaran", - "overlap": "Tumpang tindih", - "overlapNextX": "Tumpang tindih berikutnya ({0:#;##0.###})", - "overlapPreviousLineX": "Tumpang tindih baris sebelumnya ({0:#;##0.###})", - "overlapStartAndEnd": "Tumpang tindih awal dan akhir", - "overlapX": "Tumpang tindih ({0:#;##0.###})", "overwriteExistingFiles": "Timpa file yang ada", "overwriteFilesInFolderX": "Timpa file di folder: \"{0}\"", "overwriteQuestion": "Timpa?", @@ -450,6 +443,7 @@ "percent": "Persen", "pickLayer": "Atur lapisan", "pickOllamaModel": "Pilih model Ollama", + "pickModel": "Pilih model", "llamaCppUseRemoteServer": "Gunakan server eksternal (URL)", "llamaCppEngineSettings": "Pengaturan mesin llama.cpp", "llamaCppEngineSettingsSubtitle": "Build server llama.cpp lokal", @@ -501,7 +495,6 @@ "refresh": "Segarkan", "region": "Wilayah", "regularExpression": "Ekspresi reguler", - "regularExpressionIsNotValid": "Ekspresi reguler tidak valid!", "relativeToCurrentVideoPosition": "Relatif terhadap posisi video saat ini", "remove": "Hapus", "removeAlignment": "Hapus perataan", @@ -527,7 +520,7 @@ "reset": "Atur ulang", "resizePanels": "Ubah ukuran panel", "resolution": "Resolusi", - "resolutionX": "Resolusi: {0}", + "retry": "Coba lagi", "reverseRightToLeftStartEnd": "Balikkan awal/akhir RTL", "right": "Kanan", "rightToLeft": "Kanan ke kiri", @@ -567,8 +560,6 @@ "selectedlinesX": "Baris terpilih: {0}", "sensitivity": "Sensitivitas", "separator": "Pemisah", - "sessionKey": "Kunci sesi", - "sessionKeyGenerate": "Buat kunci baru", "setEnd": "Atur akhir", "setEndAndGoToNext": "Atur akhir lalu ke berikutnya", "setEndAddNewAndGoToNew": "Atur akhir, tambah baru, dan pindah ke yang baru", @@ -591,8 +582,9 @@ "shadowColor": "Warna bayangan", "shadowWidth": "Lebar bayangan", "shiftLineBy": "Geser baris sebesar", + "shotIn": "Shot masuk", + "shotOut": "Shot keluar", "shortcut": "Pintasan", - "shortcutX": "Pintasan: {0}", "shortcuts": "Pintasan", "show": "Tampilkan", "showActorColumn": "Tampilkan kolom \"Aktor\"", @@ -603,17 +595,19 @@ "showStartColumn": "Tampilkan kolom \"Mulai\"", "showHideColumn": "Tampilkan kolom \"Selesai\"", "showHistory": "Tampilkan riwayat", + "clearHistory": "Bersihkan riwayat", + "removeFromHistory": "Hapus dari riwayat", "showLayerColumn": "Tampilkan kolom \"Lapisan\"", "showPreview": "Tampilkan pratinjau", "showShotChangesList": "Tampilkan daftar perubahan shot", + "showShotInColumn": "Tampilkan kolom \"Shot masuk\"", + "showShotOutColumn": "Tampilkan kolom \"Shot keluar\"", "showStyleColumn": "Tampilkan kolom \"Gaya\"", "showTeletext": "Tampilkan kolom teletext di tampilan daftar", "showTimeCodes": "Tampilkan kode waktu", "showWpmColumn": "Tampilkan kolom \"Kata/mnt\"", "showPixelWidthColumn": "Tampilkan kolom \"Lebar piksel\"", "shrink": "Kecilkan", - "singleBox": "Kotak tunggal", - "singleLineLengths": "Panjang satu baris:", "singleMode": "Mode tunggal", "textTotalLength": "Teks - panjang total", "textSingleLineMaxLength": "Teks - panjang maks satu baris", @@ -662,16 +656,16 @@ "strikeout": "Coret", "style": "Gaya", "styleExaggeration": "Penekanan gaya", - "styleLanguage": "Gaya / Bahasa", "styles": "Gaya", "subtitleFile": "File subtitel", + "subtitleFileChangedOutsideX": "File \"{0}\" telah diubah di luar Subtitle Edit.\n\nMuat ulang dan buang perubahan Anda?", "subtitleFileName": "Nama file subtitel", "subtitleFileSaved": "File subtitel disimpan", "subtitleFileSavedToX": "File subtitel disimpan ke {0}", "subtitleFiles": "File subtitel", "subtitleFormats": "Format subtitel", "subtitleLoadedX": "Subtitel dimuat: {0}", - "subtitleXOfY": "Subtitel {0} dari {1}", + "subtitleReloadedChangedOutsideX": "Subtitel dimuat ulang (diubah di luar Subtitle Edit): {0}", "suffix": "Akhiran", "suggestions": "Saran", "sync": "Sinkron", @@ -683,7 +677,6 @@ "teletextColor": "Warna teletext", "teletextLine": "Baris teletext", "tenHours": "10 jam", - "tenMilliseconds": "10 milidetik", "text": "Teks", "textColor": "Warna teks", "textFiles": "File teks", @@ -710,9 +703,6 @@ "topRight": "Atas-Kanan", "top": "Atas", "totalAdjustmentX": "Total penyesuaian: {0}", - "totalFramesX": "Total bingkai: {0:#;##0.##}", - "totalLengthX": "Panjang total: {0}", - "totalLengthXSplitLine": "Panjang total: {0} (pisah baris!)", "translate": "Terjemahkan", "translatePercentX": "Menerjemahkan: {0}%", "translateRow": "Terjemahkan baris", @@ -721,6 +711,7 @@ "type": "Jenis", "unbreak": "Satukan baris teks", "unbreakLines": "Satukan baris", + "undelete": "Batalkan hapus", "underline": "Garis bawah", "undo": "Urungkan", "unknown": "Tidak diketahui", @@ -740,13 +731,11 @@ "usages": "Penggunaan", "use": "Gunakan", "useAlways": "Selalu gunakan", - "useLargerFontForThisWindow": "Gunakan fon lebih besar untuk jendela ini", "useOnce": "Gunakan sekali", "useOutputFolder": "Gunakan folder keluaran", "useSourceFolder": "Gunakan folder sumber", "useSourceResolution": "Gunakan resolusi sumber", "userName": "Nama pengguna", - "userNameAlreadyInUse": "Nama pengguna sudah dipakai", "vertical": "Vertikal", "video100MsBack": "Video, 100 milidetik mundur", "video100MsForward": "Video, 100 milidetik maju", @@ -760,14 +749,25 @@ "videoCustom3ForwardX": "Video, milidetik kustom ({0:#,###,##0}) maju, 3", "videoCustom4BackX": "Video, milidetik kustom ({0:#,###,##0}) mundur, 4", "videoCustom4ForwardX": "Video, milidetik kustom ({0:#,###,##0}) maju, 4", - "videoEncodingX": "Pengodean video: {0}", + "videoCustom1BackAndPauseX": "Video, milidetik kustom ({0:#,###,##0}) mundur dan jeda, 1", + "videoCustom1ForwardAndPauseX": "Video, milidetik kustom ({0:#,###,##0}) maju dan jeda, 1", + "videoCustom2BackAndPauseX": "Video, milidetik kustom ({0:#,###,##0}) mundur dan jeda, 2", + "videoCustom2ForwardAndPauseX": "Video, milidetik kustom ({0:#,###,##0}) maju dan jeda, 2", + "videoCustom3BackAndPauseX": "Video, milidetik kustom ({0:#,###,##0}) mundur dan jeda, 3", + "videoCustom3ForwardAndPauseX": "Video, milidetik kustom ({0:#,###,##0}) maju dan jeda, 3", + "videoCustom4BackAndPauseX": "Video, milidetik kustom ({0:#,###,##0}) mundur dan jeda, 4", + "videoCustom4ForwardAndPauseX": "Video, milidetik kustom ({0:#,###,##0}) maju dan jeda, 4", "videoExtension": "Ekstensi file video", "videoFile": "File video", "videoFileGenerated": "File video dibuat", "videoFileGeneratedX": "File video dibuat: \"{0}\"", + "videoFilesGenerated": "File video dibuat", + "xVideosGenerated": "{0} video dibuat", + "xOfYVideosGenerated": "{0} dari {1} video dibuat", + "savedInX": "Disimpan di {0}", + "savedInXFolders": "Disimpan di {0} folder", "videoFiles": "File video", "videoAndAudioFiles": "File video dan audio", - "videoInformation": "Info video", "videoOffset": "Offset video", "videoOneFrameBack": "Video, satu bingkai mundur", "videoOneFrameForward": "Video, satu bingkai maju", @@ -778,7 +778,6 @@ "videoPlayer": "Pemutar video", "videoPosition": "Posisi video", "videoResolution": "Resolusi video", - "viewX": "Tampilan {0}", "visible": "Terlihat", "voice": "Suara", "volume": "Volume", @@ -787,9 +786,7 @@ "waveformCenterOnVideoPosition": "Pusatkan bentuk gelombang pada posisi video", "waveformPasteFromClipboard": "Tempel dari papan klip", "waveformSpectrogram": "Bentuk gelombang/spektogram", - "webServiceUrl": "URL layanan web", "width": "Lebar", - "windowPositionAndSize": "Posisi dan ukuran jendela", "wordsPerMin": "Kata/mnt", "pixelWidth": "Lebar piksel", "wpm": "Kata/mnt", @@ -803,8 +800,6 @@ "noBookmarksFound": "Tidak ada markah lagi yang ditemukan", "xRequiresAnApiKey": "{0} memerlukan kunci API", "xRequiresAValidUrl": "{0} memerlukan URL yang valid (mis. \"http://...\" atau \"https://...\")", - "xSeconds": "{0:0.0##} detik", - "xSubtitles": "{0:#,###,##0} subtitel", "yes": "Ya", "zeroBytes": "0 bita", "copyToClipboard": "Salin ke papan klip", @@ -826,10 +821,8 @@ "playbackSpeed": "Kecepatan pemutaran", "hoursMinutesSecondsMilliseconds": "HH:MM:SS.FFF", "imageFiles": "File gambar", - "openAiCompatibleStt": "STT Kompatibel OpenAI", "openAiCompatibleSttEndpoint": "URL Endpoint STT", "openAiCompatibleSttApiKey": "Kunci API", - "openAiCompatibleSttModel": "Model", "openAiCompatibleSttExtraHeaders": "Header Tambahan", "openAiCompatibleSttTimeout": "Batas waktu (detik)", "openAiCompatibleSttLanguage": "Petunjuk Bahasa", @@ -839,6 +832,9 @@ "dashScopeSttRegion": "Wilayah", "dashScopeSttEnableWords": "Stempel waktu tingkat kata", "dashScopeSttRegionKeyHint": "Catatan: Kunci API Alibaba Cloud Model Studio bersifat spesifik wilayah - pastikan wilayah yang dipilih cocok dengan wilayah tempat kunci API dibuat (Tiongkok vs. Internasional).", + "googleCloudSttProjectId": "ID proyek (hanya untuk Application Default Credentials)", + "googleCloudSttBucketName": "Bucket penyimpanan (kosong = otomatis)", + "googleCloudSttDynamicBatching": "Batching dinamis (sekitar 5x lebih murah, tanpa jaminan latensi)", "onlineSttApiKeyMissing": "Kunci API diperlukan. Silakan masukkan kunci API Anda dan coba lagi.", "openAiCompatibleSttAutoTranscribeOnAudioSelection": "Transkripsi otomatis seleksi bentuk gelombang baru melalui ucapan-ke-teks", "openAiCompatibleSttStream": "Streaming respons", @@ -848,11 +844,9 @@ "configurationRequired": "Konfigurasi Diperlukan", "transcriptionError": "Kesalahan Transkripsi", "transcriptionComplete": "Transkripsi selesai", - "transcribingWithOpenAi": "Mentranskripsi dengan Layanan OpenAI...", "processingResponse": "Memproses respons...", "unauthorizedApiKey": "Tidak sah: Kunci API tidak valid atau izin tidak mencukupi.", "requestTimeout": "Batas waktu permintaan: Server terlalu lama merespons.", - "noSegmentsReturned": "Tidak ada segmen yang dikembalikan dari layanan transkripsi.", "openAiCompatibleSttUrlMissing": "URL endpoint STT Kompatibel OpenAI diperlukan. Silakan masukkan URL dan coba lagi.", "openAiCompatibleSttUrlNotResponding": "Endpoint STT Kompatibel OpenAI tidak merespons:\n{0}\n\nSilakan periksa URL dan pastikan server berjalan.", "transcriptionFailed": "Transkripsi gagal", @@ -898,14 +892,15 @@ "multipleReplace": "Penggantian gan_da...", "fillSelectedLinesWithClipboard": "Isi baris terpilih dengan teks papan klip", "goToLineNumber": "_Pergi ke nomor baris...", + "commandPalette": "_Palet perintah...", "rightToLeftMode": "Mode kanan-ke-k_iri", "modifySelectionDotDotDot": "Ubah _seleksi...", "tools": "Ala_t", - "toolsSelectedLines": "Ala_t (baris terpilih)", "adjustDurations": "Sesu_aikan durasi...", "applyDurationLimits": "Terapkan batas _durasi...", "batchConvert": "Konversi _batch...", "beautifyTimeCodes": "Percantik kode waktu...", + "improveTimeCodes": "Perbaiki kode waktu (forced alignment)...", "bridgeGaps": "Jembatani _jeda...", "applyMinGap": "Terapkan jeda min. antar subtitel...", "changeCasing": "Ubah _kapitalisasi...", @@ -1044,6 +1039,9 @@ "selectCurrentLineWhilePlayingHint": "Pilih subtitel saat ini selama pemutaran {0}", "videoPosition": "Posisi video {0}", "videoPositionTextBox": "Kotak teks posisi video {0}", + "toolbarLineBreakX": "Pemisah baris {0} (sisa bilah alat berlanjut di baris baru)", + "initialText": "Teks awal baris terpilih", + "initialTextHint": "Teks baris terpilih seperti saat baris tersebut dipilih", "hideWaveformToolbar": "Sembunyikan bilah alat {0}", "resetZoomAndSpeed": "Atur ulang zoom & kecepatan pemutaran {0}", "removeBlankLines": "Hapus baris kosong {0}", @@ -1057,6 +1055,15 @@ "textPlayHint": "Putar subtitel saat ini dan berhenti di akhir {0}", "textPauseHint": "Jeda pemutaran {0}", "textNextHint": "Putar subtitel berikutnya dan berhenti di akhir {0}", + "moveSelectedLines": "Geser baris terpilih (tombol mundur/maju)", + "moveSelectedLinesAndFollowing": "Geser baris terpilih dan semua berikutnya (tombol mundur/maju)", + "moveAllLines": "Geser semua baris (tombol mundur/maju)", + "moveSelectedLinesBackHint": "Geser baris terpilih {0} ms mundur {1}", + "moveSelectedLinesForwardHint": "Geser baris terpilih {0} ms maju {1}", + "moveSelectedLinesAndFollowingBackHint": "Geser baris terpilih dan semua berikutnya {0} ms mundur {1}", + "moveSelectedLinesAndFollowingForwardHint": "Geser baris terpilih dan semua berikutnya {0} ms maju {1}", + "moveAllLinesBackHint": "Geser semua baris {0} ms mundur {1}", + "moveAllLinesForwardHint": "Geser semua baris {0} ms maju {1}", "guessStartNoLineSelected": "Tebak awal: tidak ada baris terpilih", "guessStartTimeCodesLocked": "Tebak awal: kode waktu terkunci", "guessStartNoWaveform": "Tebak awal: tidak ada bentuk gelombang", @@ -1078,12 +1085,15 @@ "charactersPerSecond": "Karakter/detik: {0}", "chooseColumn": "Pilih kolom", "columnPaste": "Tempel kolom", + "commandPaletteTitle": "Palet perintah", + "commandPaletteSearchPlaceholder": "Ketik untuk mencari perintah...", + "commandPaletteNoCommandsFound": "Tidak ada perintah yang cocok", "createdEmptyTranslation": "Terjemahan kosong dibuat dari subtitel saat ini", "deleteText": "Hapus teks", "deleteTextAndShiftCellsUp": "Hapus teks dan geser sel ke atas", "endTimeMustBeAfterStartTime": "Waktu selesai harus setelah waktu mulai.", "errorLoad7Zip": "File ini tampaknya file terkompresi 7-Zip.\n\nSubtitle Edit tidak dapat membuka file terkompresi.", - "errorLoadBinaryZeroes": "Maaf, file ini hanya berisi nol biner!\n\nJika Anda pernah mengedit file ini dengan Subtitle Edit, Anda mungkin dapat menemukan cadangan melalui menu File -> Pulihkan cadangan otomatis...", + "errorLoadBinaryZeroes": "Maaf, file ini hanya berisi nol biner!\n\nJika Anda pernah mengedit file ini dengan Subtitle Edit, Anda mungkin dapat menemukan cadangan melalui menu File -> Pulihkan cadangan otomatis...", "errorLoadGZip": "File ini tampaknya file terkompresi GZip.\n\nSubtitle Edit tidak dapat membuka file terkompresi.", "errorLoadLargeFile": "File ini terlalu besar untuk dibuka sebagai file subtitel.", "errorLoadVideoFilePrompt": "File ini tampaknya file video:\n{0}\n\nAnda dapat membuka video melalui \"Video -> Buka video\" atau dengan menyeret dan menjatuhkan video ke pemutar video.\n\nApakah Anda ingin membukanya sebagai file video?", @@ -1116,18 +1126,20 @@ "numberOfLinesEvenlyDistributedX": "{0} baris didistribusikan secara merata", "oneLineCopiedFromOriginal": "Satu baris disalin dari subtitel asli", "oneLineCopiedToOriginal": "Satu baris disalin ke subtitel asli", + "onlineOnlyVideo": "Video khusus daring", + "onlineOnlyVideoXSizeYDownloadAndOpen": "File video \"{0}\" ({1}) hanya tersimpan secara daring, misalnya di Dropbox atau iCloud Drive, dan harus diunduh sebelum dapat dibuka.\n\nUnduh dan buka sekarang?", "oneLineMerged": "Satu baris digabungkan", "oneLineSwitched": "Satu baris ditukar", "overwriteExistingCells": "Timpa sel yang ada", "overwriteOrShiftCellsDown": "Timpa/geser sel ke bawah", "parsingMatroskaFile": "Mengurai file Matroska...", + "readingDvdSubtitles": "Membaca subtitel DVD...", "pasteFromClipboardDotDotDot": "Tempel dari papan klip...", "redoPerformed": "Ulangi dilakukan", "redoPerformedXActionLeft": "Ulangi dilakukan (aksi tersisa: {0})", "removedUnicodeControlCharactersX": "Karakter kontrol Unicode dihapus dari {0} baris", "removedXBlankLines": "{0} baris kosong dihapus", "replacedXWithYCountZ": "\"{0}\" diganti dengan \"{1}\" ({2} kemunculan)", - "replacedXWithYInLineZ": "\"{0}\" diganti dengan \"{1}\" di baris {2}", "reversedStartAndEndingsForRightToLeftX": "Awal dan akhir dibalik untuk kanan-ke-kiri di {0} baris", "ruleProfileIsX": "Profil aturan sekarang \"{0}\"", "saveLanguageFile": "Simpan file bahasa", @@ -1135,7 +1147,6 @@ "shiftTextCellsDown": "Geser sel teks ke bawah", "singleLineLength": "Panjang baris: ", "speedIsNowX": "Kecepatan sekarang \"{0}\"", - "spellCheckResult": "Pemeriksaan ejaan selesai. \n\n• Kata diubah: {0}\n• Kata dilewati: {1}", "subtitleGridFormattingX": "Pemformatan kisi: {0}", "subtitleImportedFromMatroskaFile": "Subtitel diimpor dari file Matroska", "textDown": "Teks ke bawah", @@ -1162,13 +1173,14 @@ "trimmedXLines": "{0} baris subtitel dipangkas", "pastedXLinesOverSelectedLines": "{0} baris ditempel menimpa baris terpilih", "pastedXOfYLinesOverSelectedLines": "{0} dari {1} baris ditempel menimpa baris terpilih - sisanya tidak muat pada seleksi", + "insertedXLinesFromClipboard": "{0} baris disisipkan dari papan klip", "openOriginalDifferentNumberOfSubtitlesXY": "File subtitel asli tidak memiliki jumlah subtitel yang sama dengan file subtitel saat ini.\n\n• Subtitel asli: {0}\n• Subtitel saat ini: {1}", - "importXMatchingOriginalLines": "Impor {0} subtitel asli yang cocok?", "originalTextReadOnly": "Teks asli (hanya-baca)", "originalTextEditMode": "Teks asli (mode edit)", "originalIsReadOnlyNotSaved": "Subtitel asli adalah referensi hanya-baca dan tidak disimpan", "originalIsEmptyNotSaved": "Subtitel asli tidak memiliki teks sama sekali dan tidak disimpan", "originalIsReadOnlyReference": "Subtitel asli dibuka sebagai referensi hanya-baca", + "closeTranslationQuestionXY": "Tutup terjemahan \"{0}\"?\n\nSubtitel asli \"{1}\" menjadi subtitel yang Anda edit. File terjemahan itu sendiri tidak diubah.", "allowEditOfOriginalSubtitle": "Izinkan edit subtitel asli", "showAllOriginalLinesX": "Tampilkan semua baris asli ({0} tidak punya pasangan di sini)", "showAllOriginalLinesHint": "Baris tanpa pasangan ditampilkan sebagai baris tambahan, sehingga Anda dapat melihat apa yang kurang pada terjemahan.", @@ -1203,6 +1215,7 @@ "fileExportedInFormatXToY": "File diekspor dalam format {0} ke {1}", "fileExportedInFormatXToFileY": "File diekspor dalam format \"{0}\" ke file \"{1}\"", "fixedXLines": "{0} baris diperbaiki", + "toolXChangedYLines": "{0}: {1} baris diubah", "transcriptionCompletedWithXLines": "Transkripsi selesai dengan {0} baris", "openSubtitleFileX": "Buka file subtitel \"{0}\"?", "replacedXOccurrences": "{0} kemunculan diganti", @@ -1234,13 +1247,9 @@ "file": { "ebuSaveOptions": { "title": "Opsi penyimpanan EBU", - "generalSubtitleInformation": "Informasi umum subtitel", "codePageNumber": "Nomor halaman kode", "diskFormatCode": "Kode format disk", "displayStandardCode": "Kode standar tampilan", - "colorRequiresTeletext": "Warna memerlukan teleteks!", - "alignmentRequiresTeletext": "Perataan memerlukan teleteks!", - "teletextCharsShouldBe38": "'Maks# karakter per baris' untuk teleteks harus 38!", "characterCodeTable": "Tabel karakter", "languageCode": "Kode bahasa", "originalProgramTitle": "Judul program asli", @@ -1257,7 +1266,6 @@ "maxNumberOfDisplayableRows": "Maks# baris", "diskSequenceNumber": "Nomor urut disk", "totalNumberOfDisks": "Jumlah total disk", - "import": "Impor...", "textAndTimingInformation": "Informasi teks dan pewaktuan", "justificationCode": "Kode perataan", "verticalPosition": "Posisi vertikal", @@ -1274,11 +1282,6 @@ "errors": "Kesalahan", "errorsX": "Kesalahan: {0}", "maxLengthError": "Baris {0} melebihi panjang maks ({1}) sebanyak {2}: {3}", - "textUnchangedPresentation": "Presentasi tidak berubah", - "textLeftJustifiedText": "Teks rata kiri", - "textCenteredText": "Teks rata tengah", - "textRightJustifiedText": "Teks rata kanan", - "useBoxForOneNewLine": "Centang 'Gunakan kotak di sekitar teks' untuk hanya satu baris baru", "maxLengthErrorTeletextColored": "Baris {0}-{1}: 36 (bukan {2}) seharusnya jumlah karakter maksimum untuk teks berwarna tinggi ganda", "maxLengthErrorTeletextWhite": "Baris {0}-{1}: 37 (bukan {2}) seharusnya jumlah karakter maksimum untuk teks putih tinggi ganda", "maxLengthErrorTeletextSingleHeight": "Baris {0}: 38 (bukan {1}) seharusnya jumlah karakter maksimum untuk teks putih tinggi tunggal", @@ -1303,22 +1306,29 @@ "formattingDotDotDot": "Pemformatan...", "imageBasedSubtitleForEditDotDotDot": "Subtitel berbasis gambar untuk diedit...", "imageBasedSubtitleForOcrDotDotDot": "Subtitel berbasis gambar untuk OCR...", + "dvdSubtitlesDotDotDot": "Subtitel dari DVD (IFO/VOB)...", + "titleImportDvd": "Impor subtitel dari DVD", + "dvdIfoFile": "File IFO", + "dvdVobFiles": "File VOB", + "dvdVideoStandard": "Standar video", + "dvdStartRipping": "Mulai ripping", + "dvdRippingAborted": "Ripping dibatalkan", + "dvdOpenIfoFile": "Buka file IFO DVD", + "dvdAddVobFiles": "Tambah file VOB", + "dvdIfoFiles": "File IFO DVD", + "dvdVobFileType": "File VOB DVD", + "dvdDropHint": "Buka file IFO (VIDEO_TS\\VTS_01_0.IFO) atau tambahkan file VOB - atau letakkan di sini", + "dvdNotAnIfoFileX": "{0} bukan file IFO DVD", + "dvdMenuIfoNoTitleSetsX": "Tidak ada title set (VTS_xx_0.IFO) yang ditemukan di samping {0}", + "dvdEncryptedXOfY": "{0} dari {1} paket subtitel masih terenkripsi CSS - file VOB ini disalin tanpa dekripsi, sehingga sebagian gambar subtitel akan rusak.\n\nDekripsi DVD dengan alat ripping DVD terlebih dahulu untuk hasil yang bersih.\n\nTetap lanjutkan?", "splitTextAt": "Pisahkan teks pada", "blankLines": "Baris kosong", "oneLineIsOneSubtitle": "Satu baris adalah satu subtitel", "twoLinesAreOneSubtitle": "Dua baris adalah satu subtitel", "importFilesDotDotDot": "Impor file...", "multipleFiles": "Impor dari beberapa file teks (satu file adalah satu subtitel)", - "importOptions": "Opsi impor", - "autoSplitText": "Pisah teks otomatis", - "lineMode": "Mode baris", "maxLineLength": "Panjang baris maks", - "maxLinesPerSubtitle": "Maks baris per subtitel", - "minGapBetweenSubtitles": "Jeda min antar subtitel", "mergeShortLines": "Gabungkan baris pendek", - "removeLinesWithoutLetters": "Hapus baris tanpa huruf", - "splitAtEndCharsSetting": "Pisah pada karakter akhir", - "takeTimeFromCurrentFile": "Ambil waktu dari file saat ini", "fixed": "Tetap", "numberOfSubtitlesX": "Jumlah subtitel: {0}", "numberOfSubtitlesXForcedY": "Jumlah subtitel: {0} ({1} dipaksa)", @@ -1330,14 +1340,18 @@ "forcedAlignerProgress": "Menjajarkan... jendela {0} dari {1}, {2} dari {3} baris", "forcedAlignerSetupTitle": "Jajarkan kode waktu via forced aligner", "forcedAlignerSetupIntro": "Forced aligner mencocokkan teks subtitel yang sudah Anda miliki dengan audio, tanpa mentranskripsinya terlebih dahulu. Video panjang dijajarkan per potongan, jadi panjang berapa pun dapat diproses.", + "forcedAlignerEndsFromSpeech": "Atur waktu akhir dari ucapan terisolasi (lambat)", + "forcedAlignerEndsFromSpeechHint": "Forced aligner menemukan di mana baris dimulai, bukan di mana ia berakhir, sehingga waktu akhir biasanya berasal dari waktu baca. Jika opsi ini aktif, musik dan efek suara dihapus dari audio terlebih dahulu dan setiap baris berakhir saat ucapannya hening. Memakan waktu kira-kira sepanjang video itu sendiri dengan GPU - dan berkali-kali lebih lama tanpa GPU.", + "forcedAlignerIsolatingSpeech": "Mengisolasi ucapan... {0}", + "forcedAlignerIsolatingSpeechFailed": "Tidak dapat mengisolasi ucapan - waktu akhir diatur dari waktu baca sebagai gantinya.", "forcedAlignerModel": "Model aligner", - "forcedAlignerDownloadEngine": "Unduh / perbarui mesin..." + "forcedAlignerDownloadEngine": "Unduh / perbarui mesin...", + "forcedAlignerEstimatedXOfY": "{0} dari {1} baris tidak dapat diselaraskan dan ditempatkan berdasarkan waktu baca - kode waktunya hanya perkiraan. Naskah kemungkinan lebih panjang dari yang diucapkan dalam video." }, "export": { "exportImagesProfiles": "Profil ekspor gambar", "leftRightMargin": "Margin kiri/kanan", "topBottomMargin": "Margin atas/bawah", - "titleExportBluRaySup": "Ekspor Blu-ray (sup)", "lineSpacingPercent": "Spasi baris %", "paddingLeftRight": "Padding kiri/kanan", "paddingTopBottom": "Padding atas/bawah", @@ -1378,10 +1392,21 @@ "titleExportDCinemaInteropPng": "D-Cinema interop/png", "titleExportDCinemaSmpte2014Png": "D-Cinema SMPTE 2014/png", "imageBasedSubtitleSaved": "Subtitel berbasis gambar disimpan", - "titleExportBdnXml": "Ekspor BDN/xml", "titleExportWebVttThumbnails": "Ekspor WebVTT (gambar mini)", "fullFrameImage": "Gambar bingkai penuh", "fullFrameImageHint": "Buat setiap gambar seukuran bingkai video, dengan subtitel pada posisinya", + "stereo3D": "3D", + "stereo3DHint": "Untuk video 3D: gambar subtitel sekali di setiap separuh bingkai untuk masing-masing mata", + "halfSideBySide": "Half side-by-side", + "halfTopBottom": "Half top/bottom", + "depth3D": "Kedalaman", + "depth3DHint": "Seberapa jauh subtitel menonjol keluar layar - positif mendekatkannya, negatif menjauhkannya", + "plane3D": "3D-Plane", + "plane3DHint": "Beri setiap subtitel kedalaman yang disimpan oleh Blu-ray 3D untuknya, dari file 3D-Plane (.ofs) yang dibuat oleh BD3D2MK3D atau OFSExtractor. Kedalaman digunakan bila 3D-Plane tidak memilikinya", + "openPlane3DTitle": "Buka file 3D-Plane", + "unableToLoadPlane3DX": "Tidak dapat memuat 3D-Plane: {0}", + "plane3DXDepthYToZ": "{0} (kedalaman {1} hingga {2})", + "plane3DXNoDepth": "{0} (tanpa nilai kedalaman)", "textEffect": "Efek teks", "textEffectSettingsTitle": "Pengaturan efek teks", "textEffectPreset": "Prasetel", @@ -1413,13 +1438,10 @@ "statistics": { "title": "Statistik", "titleWithFileName": "Statistik - {0}", - "generalStatistics": "Statistik umum", - "mostUsed": "Paling sering digunakan...", "mostUsedLines": "Baris paling sering digunakan", "mostUsedWords": "Kata paling sering digunakan", "nothingFound": "Tidak ada yang ditemukan", "numberOfLinesX": "Jumlah baris subtitel: {0:#,##0}", - "numberOfFilesX": "Jumlah file subtitel: {0:#,##0}", "lengthInFormatXinCharactersY": "Jumlah karakter sebagai {0}: {1:#,###,##0}", "numberOfCharactersInTextOnly": "Jumlah karakter hanya dalam teks: {0:#,###,##0}", "totalDuration": "Durasi total semua subtitel: {0:#,##0}", @@ -1460,7 +1482,6 @@ "gapMaximum": "Jeda - maksimum: {0:#,##0} ms", "gapAverage": "Jeda - rata-rata: {0:#,##0.##} ms", "gapExceedingMinimum": "Jeda - di bawah minimum ({0:#,##0} ms): {1} ({2:0.00}%)", - "export": "Ekspor...", "subtitles": "Subtitel", "words": "Kata", "totalDurationShort": "Durasi total", @@ -1490,7 +1511,22 @@ "restoreAutoBackupFile": "Pulihkan file cadangan otomatis", "restoreXFromY": "Apakah Anda ingin memulihkan \"{0}\" dari {1}?", "deleteAllSubtitleBackups": "Apakah Anda ingin menghapus semua file cadangan subtitel?", - "deleteAll": "Hapus semua" + "deleteAll": "Hapus semua", + "subtitles": "Subtitel", + "settingsBackupInfo": "Settings.json dicadangkan saat Subtitle Edit dimulai, paling banyak sekali setiap {0} hari dan hanya jika pengaturan telah berubah. {1} cadangan terbaru disimpan.", + "settingsBackupInfoEveryStart": "Settings.json dicadangkan setiap kali Subtitle Edit dimulai, jika pengaturan telah berubah. {0} cadangan terbaru disimpan.", + "settingsBackupOff": "Pencadangan pengaturan otomatis dinonaktifkan (Opsi → Pengaturan → File).", + "lastSettingsBackupX": "Cadangan terakhir: {0}", + "noSettingsBackupsYet": "Belum ada cadangan pengaturan", + "openSettingsBackupFolder": "Buka folder cadangan pengaturan", + "backUpSettingsNow": "Cadangkan sekarang", + "settingsBackedUp": "Pengaturan saat ini telah dicadangkan.", + "restoreSettings": "Pulihkan pengaturan", + "restoreSettingsFromX": "Apakah Anda ingin mengganti pengaturan saat ini dengan cadangan dari {0}?\n\nPengaturan Anda saat ini akan dicadangkan terlebih dahulu. Beberapa perubahan baru berlaku setelah Subtitle Edit dimulai ulang.", + "settingsRestored": "Pengaturan telah dipulihkan. Silakan mulai ulang Subtitle Edit agar semua perubahan berlaku.", + "settingsRestoreFailed": "Tidak dapat memulihkan pengaturan: {0}", + "settingsBackupNotValid": "cadangan bukan file pengaturan yang valid, sehingga pengaturan Anda saat ini dipertahankan", + "deleteAllSettingsBackups": "Apakah Anda ingin menghapus semua file cadangan pengaturan?" }, "propertiesDCinema": { "title": "Properti D-Cinema SMPTE", @@ -1504,22 +1540,16 @@ "editRate": "Laju edit", "timeCodeRate": "Laju kode waktu", "startTime": "Waktu mulai", - "font": "Fon", "fontId": "ID fon", "fontUri": "URI fon", - "generate": "Buat", - "fontColor": "Warna fon", "chooseColor": "Pilih warna", "fontEffect": "Efek fon", "effectColor": "Warna efek", - "fontSize": "Ukuran fon", "topBottomMargin": "Margin atas/bawah", "fadeUpTime": "Waktu pudar masuk", "fadeDownTime": "Waktu pudar keluar", - "frames": "Bingkai", "zPosition": "Posisi Z", - "zPositionHelp": "Angka positif menjauhkan teks, angka negatif mendekatkan teks; jika posisi Z nol maka 2D", - "export": "Ekspor..." + "zPositionHelp": "Angka positif menjauhkan teks, angka negatif mendekatkan teks; jika posisi Z nol maka 2D" }, "webVtt": { "setStylesForSelectedLinesTitle": "Atur gaya WebVTT untuk baris terpilih", @@ -1552,20 +1582,66 @@ "compareOnlyInOneFile": "Hanya di satu file", "compareTextOrTimeDifference": "Perbedaan teks/waktu", "compareNumberDifference": "Perbedaan nomor", - "ignoreLineBreaks": "Abaikan pemisah baris", "ignoreWhitespace": "Abaikan spasi kosong", "ignoreWhitespaceHint": "Baris yang hanya berbeda pada spasi, tab, atau pemisah baris tidak dihitung berbeda", "ignoreFormatting": "Abaikan pemformatan", - "onlyLookForDifferencesInText": "Hanya cari perbedaan dalam teks", - "cannotCompareWithImageBasedSubtitles": "Tidak dapat membandingkan dengan subtitel berbasis gambar", - "statisticsTitle": "Statistik", "ignoreFormattingHint": "Baris yang hanya berbeda pada tag pemformatan, seperti atau {\\an8}, tidak dihitung berbeda", "showOnlyDifferencesInText": "Hanya perbedaan dalam teks", + "ignoreNumbering": "Abaikan penomoran", + "ignoreNumberingHint": "Baris yang hanya berbeda nomornya tidak dihitung sebagai berbeda", + "ignoreTimeDifferenceMs": "Toleransi waktu (ms)", + "ignoreTimeDifferenceMsHint": "Waktu mulai dan akhir yang berbeda sebanyak milidetik ini atau kurang tidak dihitung sebagai berbeda", + "compareDifferences": "Perbedaan", + "compareTextDifferences": "Perbedaan teks", + "compareCurrent": "Saat ini", + "compareReference": "Referensi", + "compareEditable": "Dapat diedit", + "compareEditableHint": "Perubahan yang dibuat di sini dikembalikan ke subtitel saat Anda mengklik Terapkan", + "compareReadOnly": "Hanya-baca", + "compareTakeFromReference": "Ambil teks dan waktu dari referensi", + "compareInsertFromReference": "Sisipkan baris ini ke subtitel saat ini", + "compareDeleteFromCurrent": "Hapus baris ini dari subtitel saat ini", + "compareTakeText": "Ambil teks", + "compareTakeTiming": "Ambil waktu", + "compareEditLine": "Edit baris", + "compareEditHint": "Ctrl+Enter untuk menyimpan, Esc untuk membatalkan", + "compareEdited": "Diedit", + "compareOnePendingChange": "1 perubahan tertunda", + "compareXPendingChanges": "{0} perubahan tertunda", + "compareDiscardXChanges": "Buang {0} perubahan yang dibuat di Bandingkan?", + "compareXChangesApplied": "Bandingkan: {0} perubahan diterapkan", + "compareChangeTextX": "#{0} teks dari referensi", + "compareChangeTimingX": "#{0} waktu dari referensi", + "compareChangeTextAndTimingX": "#{0} teks dan waktu dari referensi", + "compareChangeEditedX": "#{0} diedit", + "compareChangeInsertedX": "#{0} disisipkan", + "compareChangeDeletedX": "#{0} dihapus", + "compareSyncPoint": "Titik sinkron", + "compareSyncPointHint": "Titik sinkron - kedua baris ini selalu ditampilkan sebagai pasangan, dan baris di atas dan di bawahnya diselaraskan secara terpisah", + "compareSyncPickCurrent": "Titik sinkron: gunakan baris saat ini", + "compareSyncPickReference": "Titik sinkron: gunakan baris referensi ini", + "compareSyncRemove": "Hapus titik sinkron", + "compareClearXSyncPoints": "Bersihkan titik sinkron ({0})", + "compareSyncCurrentPickedX": "Baris saat ini #{0} dipilih sebagai titik sinkron - pilih baris referensi yang cocok lalu klik Sinkron", + "compareSyncReferencePickedX": "Referensi #{0} dipilih sebagai titik sinkron - pilih baris saat ini yang cocok lalu klik Sinkron", + "compareSyncWithCurrentX": "Sinkronkan dengan baris saat ini #{0}", + "compareSyncWithReferenceX": "Sinkronkan dengan referensi #{0}", + "compareSyncApply": "Sinkron", + "compareSyncPointSetXY": "Titik sinkron diatur: baris saat ini #{0} dan referensi #{1} kini ditampilkan sebagai pasangan", + "compareSyncAlreadyPairedXY": "Baris saat ini #{0} dan referensi #{1} sudah ditampilkan sebagai pasangan dengan awal yang sama - tidak ada yang perlu disinkronkan", + "compareSyncPointShiftedXYZW": "Titik sinkron diatur: baris saat ini #{0}-#{1} digeser {2} agar dimulai bersama referensi #{3}", + "compareChangeSyncShiftXYZ": "#{0}-#{1} digeser {2} (titik sinkron)", + "compareTimingDeltaHint": "Seberapa lebih lambat (+) atau lebih awal (−) referensi dimulai dan berakhir dibandingkan baris saat ini", "loadXFromFile": "Muat \"{0}\" dari file", + "compareLoadSavedFile": "Muat file yang disimpan", "saveCompareHtmlTitle": "Simpan file HTML perbandingan", "pickMatroskaTrackX": "Pilih trek Matroska - {0}", "pickTransportStreamTrackX": "Pilih trek transport stream - {0}", + "chapters": "Bab", + "dvdVobFilesMissingX": "File VOB hilang ({0}% ditemukan)", "pickMp4TrackX": "Pilih trek MP4 - {0}", + "pickMxfTrackX": "Pilih trek MXF - {0}", + "pickMpegTrackX": "Pilih trek MPEG - {0}", "rosettaProperties": "Properti Timed Text Rosetta IMSC", "rosettaFontSize": "Ukuran fon (tinggi baris)", "propertyTimeBase": "Basis waktu", @@ -1575,7 +1651,6 @@ "propertyDefaultRegion": "Region baku", "propertyStyleAttributeName": "Nama atribut gaya", "propertyTimeCodeFormat": "Format kode waktu", - "propertyFileExtension": "Ekstensi file", "propertyTopOrigin": "Origin atas", "propertyTopExtent": "Extent atas", "propertyBottomOrigin": "Origin bawah", @@ -1625,7 +1700,6 @@ "selectionIntersect": "Iriskan dengan seleksi" }, "multipleReplace": { - "title": "Penggantian ganda", "editRule": "Edit aturan", "newRule": "Aturan baru", "editCategory": "Edit kategori", @@ -1642,24 +1716,23 @@ "findWhat": "Cari apa", "descriptionOptional": "Deskripsi (opsional)", "invalidRegularExpressionX": "Ekspresi reguler tidak valid: {0}", - "regularExpressionTooSlowX": "Ekspresi reguler menyerah setelah {0} detik - aturan dilewati, coba pola yang lebih sederhana" + "regularExpressionTooSlowX": "Ekspresi reguler menyerah setelah {0} detik - aturan dilewati, coba pola yang lebih sederhana", + "moveToCategory": "Pindahkan ke kategori" }, "find": { "searchTextWatermark": "Cari teks...", "wholeWord": "Kata utuh", - "caseSensitive": "Peka huruf besar/kecil", - "caseInsensitive": "Tidak peka huruf besar/kecil", "findPrevious": "Cari se_belumnya", "findNext": "_Cari berikutnya", "replaceAndFindNext": "_Ganti & cari berikutnya", "replaceAll": "Ganti semu_a", - "replaceTitle": "Ganti", - "replaceWith": "Ganti dengan", "replaceTextWatermark": "Ganti dengan...", "replaceIn": "Ganti/cari di", "replaceInTextAndOriginal": "Teks dan teks asli", "replaceInTextOnly": "Teks saja", - "replaceInOriginalOnly": "Teks asli saja" + "replaceInOriginalOnly": "Teks asli saja", + "replacedOneOccurrence": "Satu kemunculan diganti", + "replacedXOccurrences": "{0} kemunculan diganti" }, "regularExpressionContextMenu": { "wordBoundary": "Batas kata (\\b)", @@ -1687,6 +1760,7 @@ "xLinesYSubtitles": "{0} baris, {1} subtitel", "xLinesYSubtitlesZErrors": "{0} baris, {1} subtitel, {2} kesalahan", "couldNotParseAsX": "Tidak dapat diurai sebagai {0}", + "readAsXUseAsY": "Sumber tidak dapat dibaca sebagai {1}, tetapi dapat dibaca sebagai {0}.\nGunakan baris-barisnya dan tetap simpan sebagai {1}?", "selectedXCharactersYLines": "Terpilih: {0} karakter dalam {1} baris", "replacedXOccurrences": "{0} kemunculan diganti", "invalidRegularExpression": "Ekspresi reguler tidak valid", @@ -1701,7 +1775,6 @@ "aiReview": { "title": "Tinjauan AI", "review": "Tinjau", - "stop": "Berhenti", "editPrompt": "Edit prompt...", "editPromptTitle": "Edit prompt tinjauan", "promptInfo": "Instruksi yang dikirim ke model - {language} diganti dengan bahasa subtitel.", @@ -1712,7 +1785,6 @@ "reviewingLineXOfY": "Meninjau baris {0} dari {1}...", "reviewDone": "Tinjauan selesai - {0} saran dalam {1} baris", "noIssuesFound": "Tidak ada masalah ditemukan", - "categoryAll": "Semua", "categorySpelling": "Ejaan", "categoryGrammar": "Tata bahasa", "categoryPunctuation": "Tanda baca", @@ -1724,8 +1796,23 @@ "largeChangeWarning": "Perubahan besar - tampak seperti penulisan ulang, tinjau dengan cermat", "mismatchWarning": "Tidak menyerupai aslinya - mungkin milik baris lain", "engineError": "Mesin AI tidak dapat dijangkau: {0}", - "setupHint": "Silakan pilih mesin dan model di Alat -> Tinjauan AI terlebih dahulu.", - "playCurrentLineHint": "Putar baris terpilih di pemutar video dan jeda di akhirnya (juga klik ganda sebuah baris)" + "playCurrentLineHint": "Putar baris terpilih di pemutar video dan jeda di akhirnya (juga klik ganda sebuah baris)", + "editAfterHint": "Klik teks baris terpilih untuk mengedit perbaikan sebelum menerapkannya (Enter menyimpan, Escape membatalkan)", + "template": "Templat", + "templatePlaceholder": "Pilih templat untuk mengganti prompt", + "templateStandard": "Proofreading standar", + "templateSpeechToText": "Pembersihan hasil ucapan ke teks", + "templateMachineTranslation": "Perbaikan terjemahan mesin", + "templateSdh": "SDH / tunarungu", + "referenceContext": "Konteks referensi", + "referenceContextInfo": "Opsional. Nama, karakter, glosarium, sinopsis, atau catatan gaya - model menganggap ini sebagai acuan utama untuk nama dan istilah. Dikirim bersama setiap batch.", + "referenceContextPlaceholder": "Nama: Kaito, Bibi Mei, Dr. Hollis\nIstilah: Shinkansen, inti warp\nSinopsis: Kaito pergi ke Tokyo untuk mencari ayahnya yang hilang.", + "generateContext": "Buat dengan AI", + "generateContextHint": "Biarkan mesin AI terpilih membaca subtitel dan menyusun draf nama, istilah, dan sinopsis - periksa hasilnya sebelum digunakan", + "generatingContextPartXOfY": "Membaca bagian {0} dari {1}...", + "generatingContextSynopsis": "Menulis sinopsis...", + "replaceContextQuestion": "Ganti konteks referensi saat ini?", + "noContextGenerated": "Mesin AI tidak mengembalikan informasi referensi yang dapat digunakan." }, "aiAssistant": { "title": "Asisten AI", @@ -1743,22 +1830,14 @@ "showReasoning": "Tampilkan penalaran model" }, "fixCommonErrors": { - "title": "Perbaiki kesalahan umum", - "step1": "Langkah 1/2 - Pilih kesalahan yang akan diperbaiki", - "whatToFix": "Apa yang diperbaiki", - "example": "Contoh", - "selectAll": "Pilih semua", - "inverseSelection": "Balikkan seleksi", "back": "< &Kembali", "next": "&Berikutnya >", - "step2": "Langkah 2/2 - Verifikasi perbaikan", "fixes": "Perbaikan", "ruleDetailsDotDotDot": "Detail aturan...", "showOnlyThisRule": "Tampilkan hanya perbaikan dari aturan ini", "appliedRulesForLineX": "Aturan yang diterapkan untuk baris {0}", "ruleX": "Aturan: {0}", "log": "Log", - "function": "Fungsi", "removedEmptyLine": "Hapus baris kosong", "removedEmptyLineAtTop": "Hapus baris kosong di atas", "removedEmptyLineAtBottom": "Hapus baris kosong di bawah", @@ -1779,13 +1858,13 @@ "removeLineBreaksPixelWidth": "Satukan subtitel yang muat dalam satu baris (lebar piksel)", "fixUppercaseIInsideLowercaseWords": "Perbaiki huruf 'i' besar di dalam kata huruf kecil (kesalahan OCR)", "fixDoubleApostrophes": "Perbaiki karakter apostrof ganda ('') menjadi tanda kutip tunggal (\")", + "fixMisreadQuotes": "Perbaiki apostrof yang salah terbaca sebagai tanda kutip ganda (OCR, Inggris)", "addPeriods": "Tambahkan titik setelah baris ketika baris berikutnya diawali huruf besar", "startWithUppercaseLetterAfterParagraph": "Awali dengan huruf besar setelah paragraf", "startWithUppercaseLetterAfterPeriodInsideParagraph": "Awali dengan huruf besar setelah titik di dalam paragraf", "startWithUppercaseLetterAfterColon": "Awali dengan huruf besar setelah titik dua/titik koma", "fixLowercaseIToUppercaseI": "Perbaiki 'i' kecil tunggal menjadi 'I' (Inggris)", "fixCommonOcrErrors": "Perbaiki kesalahan OCR umum (menggunakan daftar penggantian OCR)", - "commonOcrErrorsFixed": "Kesalahan OCR umum diperbaiki (file OcrReplaceList digunakan): {0}", "removeSpaceBetweenNumber": "Hapus spasi di antara angka", "breakDialogsOnOneLine": "Pisahkan dialog dalam satu baris", "removeDialogFirstInNonDialogs": "Hapus tanda hubung awal di baris pertama untuk non-dialog", @@ -1802,18 +1881,17 @@ "fixCommasExample": ",, -> ,", "removeLineBreaksExample": "Foo
bar! -> Foo bar!", "fixDoubleApostrophesExample": "''Punya kutip tunggal ganda'' -> \"Punya kutip ganda tunggal\"", + "fixMisreadQuotesExample": "\"Hello' -> \"Hello\"", "addPeriodsExample": "Halo dunia -> Halo dunia.", "startWithUppercaseLetterAfterParagraphExample": "p1: Foobar! || p2: foobar! -> p1: Foobar! || p2: Foobar!", "startWithUppercaseLetterAfterPeriodInsideParagraphExample": "Halo! apa kabar? -> Halo! Apa kabar?", "startWithUppercaseLetterAfterColonExample": "Pembicara: halo dunia! -> Pembicara: Halo dunia!", "removeHyphensSingleLineExample": "- Foobar. -> Foobar.", "fix3PlusLinesExample": "Foo
bar
baz! -> Foo bar baz!", - "xMissingQuotesAdded": "Tanda kutip yang hilang ditambahkan: {0}", "fix3PlusLine": "Perbaiki subtitel dengan lebih dari dua baris", "fix3PlusLines": "Perbaiki subtitel dengan lebih dari dua baris", "analysing": "Menganalisis...", "nothingToFix": "Tidak ada yang perlu diperbaiki :)", - "fixesFoundX": "Perbaikan ditemukan: {0}", "xFixesApplied": "Perbaikan diterapkan: {0}", "xFixesYSelected": "{0} perbaikan - {1} terpilih", "nothingFixableBut": "Tidak ada yang dapat diperbaiki secara otomatis. Subtitel mengandung kesalahan - lihat log untuk detail", @@ -1822,7 +1900,6 @@ "fixFirstLetterToUppercaseAfterParagraph": "Perbaiki huruf pertama menjadi huruf besar setelah paragraf", "mergeShortLine": "Gabungkan baris pendek (satu kalimat)", "mergeShortLineAll": "Gabungkan baris pendek (semua kecuali dialog)", - "unbreakShortLinePixelWidth": "Satukan baris pendek (lebar piksel)", "breakLongLine": "Pisahkan baris panjang", "fixLongDisplayTime": "Perbaiki waktu tampil yang panjang", "fixInvalidItalicTag": "Perbaiki tag miring tidak valid", @@ -1845,7 +1922,6 @@ "fixUppercaseIInsideLowercaseWord": "Perbaiki huruf 'i' besar di dalam kata huruf kecil", "fixMissingPeriodAtEndOfLine": "Tambahkan titik yang hilang di akhir baris", "refreshFixes": "Segarkan perbaikan yang tersedia", - "applyFixes": "Terapkan perbaikan terpilih & tutup", "autoBreak": "&Pisah otomatis", "unbreak": "Sat&ukan", "fixDoubleDash": "Perbaiki '--' -> '...'", @@ -1864,8 +1940,6 @@ "fixSpaceBetweenNumbersExample": "1 100 -> 1100", "fixDialogsOneLineExample": "Hai John! - Hai Ida! -> Hai John!
- Hai Ida!", "removeDialogFirstInNonDialogsExample": "- Apa kabar? -> Apa kabar?", - "selectDefault": "Pilih default", - "setDefault": "Jadikan perbaikan saat ini sebagai default", "fixContinuationStyleX": "Perbaiki gaya lanjutan: {0}", "fixUnnecessaryLeadingDots": "Hapus titik awal yang tidak perlu", "fixCommonErrorsProfiles": "Profil perbaikan kesalahan umum", @@ -1873,7 +1947,6 @@ "searchRulesDotDotDot": "Cari aturan...", "goToApplyFixes": "Lanjut ke penerapan perbaikan", "backToFixList": "Kembali ke daftar perbaikan", - "applyFixesAndClose": "Terapkan perbaikan & tutup", "fixCommonOcrErrorsStep2": "Perbaiki kesalahan umum, langkah 2 (terapkan perbaikan)", "fixCommonOcrErrorsStep2FixesFoundX": "Perbaiki kesalahan umum, langkah 2 - Perbaikan ditemukan: {0}", "action": "Aksi", @@ -1886,18 +1959,11 @@ "adjustDurations": { "title": "Sesuaikan durasi", "adjustVia": "Sesuaikan via", - "seconds": "Detik", - "percent": "Persen", "recalculate": "Hitung ulang", "addSeconds": "Tambahkan detik", - "setAsPercent": "Atur sebagai persen dari durasi", "note": "Catatan: Waktu tampil tidak akan tumpang tindih dengan waktu mulai teks berikutnya", "fixed": "Tetap", - "milliseconds": "Milidetik", "extendOnly": "Hanya perpanjang", - "enforceDurationLimits": "Terapkan durasi minimum dan maksimum", - "checkShotChanges": "Jangan perpanjang melewati perubahan shot", - "batchCheckShotChanges": "Hormati perubahan shot (jika tersedia)", "recalculateRequiresOcrNote": "Hitung ulang tidak tersedia — satu atau lebih subtitel tidak memiliki teks OCR. Jalankan OCR terlebih dahulu." }, "applyDurationLimits": { @@ -1905,6 +1971,8 @@ "fixMinDurationMs": "Perbaiki durasi minimum (ms)", "doNotGoPastShotChange": "Jangan lewati perubahan shot", "fixMaxDurationMs": "Perbaiki durasi maksimum (ms)", + "fixMinDurationFrames": "Perbaiki durasi minimum (bingkai)", + "fixMaxDurationFrames": "Perbaiki durasi maksimum (bingkai)", "minimumDurationMilliseconds": "Durasi minimum (milidetik):", "maximumDurationMilliseconds": "Durasi maksimum (milidetik):", "maxDurationShouldBeHigherThanMinDuration": "Durasi maksimum harus lebih tinggi daripada durasi minimum", @@ -1924,12 +1992,8 @@ }, "beautifyTimeCodes": { "title": "Percantik kode waktu", - "beautifySettings": "Pengaturan Percantik", "snapToFrames": "Jajarkan ke Bingkai", - "frameGap": "Jeda Bingkai", "minDuration": "Durasi Min (ms)", - "shotChangeThreshold": "Ambang Perubahan Shot (ms)", - "shotChangeOffset": "Offset Perubahan Shot (bingkai)", "original": "Asli", "beautified": "Dipercantik", "subtitlesCount": "Subtitel", @@ -1958,13 +2022,80 @@ "timeCodesExtractFailed": "Tidak dapat membaca kode waktu dari video", "timeCodesHint": "Jajarkan cue ke waktu bingkai sebenarnya dari video alih-alih mengasumsikan laju bingkai konstan. Memerlukan pembacaan seluruh video sekali; hasilnya disimpan di singgahan." }, + "improveTimeCodes": { + "title": "Perbaiki kode waktu (forced alignment)", + "aligner": "Aligner", + "alignerHint": "Model yang mendengarkan setiap baris dalam audio. Pilihan terbaik untuk bahasa subtitel dicantumkan lebih dulu.", + "maxShift": "Pergeseran maks. (detik)", + "maxShiftHint": "Seberapa jauh subtitel mungkin tidak sinkron. Baris yang awalnya akan bergeser lebih jauh dibiarkan apa adanya dan ditandai. Berapa pun nilainya, satu baris yang ingin bergeser lebih dari setengah detik lebih jauh daripada baris di sekitarnya tidak digeser begitu saja: baris itu mengikuti tetangganya jika semuanya bergeser, dan jika tidak, ditawarkan tanpa centang agar Anda periksa.", + "adjustStartTimes": "Sesuaikan waktu mulai", + "adjustEndTimes": "Sesuaikan waktu akhir", + "isolateSpeech": "Isolasi ucapan terlebih dahulu (lambat)", + "isolateSpeechHint": "Menghapus musik dan efek suara sebelum penyelarasan, sehingga aligner hanya mendengar dialog. Membantu pada baris yang diucapkan di atas musik keras atau adegan aksi, tetapi memakan waktu kira-kira sepanjang audio itu sendiri dengan GPU - dan berkali-kali lebih lama tanpa GPU.", + "isolatingSpeech": "Mengisolasi ucapan... (ini memakan waktu)", + "isolateSpeechFailed": "Tidak dapat mengisolasi ucapan - diselaraskan dengan audio asli sebagai gantinya.", + "checkWithSpeechToText": "Periksa dengan ucapan ke teks", + "checkWithSpeechToTextHint": "Juga mentranskripsikan audio, dan memeriksa setiap pergeseran terhadap posisi kata-kata benar-benar terdengar. Pergeseran besar yang dikonfirmasi ucapan dicentang untuk Anda, dan pergeseran yang menjauh dari posisi baris terdengar dibiarkan tanpa centang agar Anda periksa. Juga menampilkan seberapa banyak dari setiap baris yang terdengar - porsi rendah berarti teks berbeda dari yang diucapkan. Subtitel yang tidak sinkron lebih jauh dari pergeseran maks. - offset konstan, pergeseran bertahap akibat laju bingkai yang tidak cocok, atau lompatan di tempat adegan dipotong - disinkronkan terlebih dahulu berdasarkan kata-kata yang terdengar. Menggunakan Crisp ASR Parakeet, yang memakan beberapa menit untuk film panjang dengan GPU.", + "checkWithSpeechToTextNotAvailableX": "Pemeriksaan ucapan ke teks tidak tersedia untuk {0}", + "transcribingToCheck": "Mentranskripsikan untuk memeriksa penyelarasan...", + "speechToTextFailed": "Ucapan ke teks gagal - kode waktu tidak diperiksa terhadapnya.", + "speechToTextX": "Ucapan ke teks: {0}", + "speechToTextConfirmedXDisputedY": "Ucapan: {0} dikonfirmasi, {1} dipersoalkan", + "hardlyHeardX": "Ucapan ke teks hanya mendengar {0}% kata dalam subtitel. Apakah subtitel dalam bahasa yang diucapkan? Aligner tidak dapat menempatkan terjemahan.", + "heard": "Terdengar", + "heardHint": "Seberapa banyak dari baris yang terdengar oleh ucapan ke teks. Porsi rendah berarti teks berbeda dari yang diucapkan - di situlah aligner paling mungkin salah.", + "statusConfirmedBySpeech": "Dikonfirmasi oleh ucapan", + "statusDisputedBySpeech": "Terdengar di posisi semula", + "statusMovedWithSync": "Digeser bersama sinkronisasi", + "syncedFirstXY": "Disinkronkan terlebih dahulu: {0} d di awal, {1} d di akhir", + "showSpeechOnly": "Tampilkan ucapan saja", + "showSpeechOnlyHint": "Gambar bentuk gelombang hasil penyelarasan dari audio dengan musik dan efek suara dihapus; bentuk gelombang asli tetap apa adanya, sebagai pembanding. Tersedia setelah ucapan diisolasi - di sini, atau dengan \"Tampilkan ucapan saja\" pada bentuk gelombang di jendela utama.", + "align": "Selaraskan", + "original": "Asli", + "aligned": "Diselaraskan", + "intro": "Pilih aligner dan tekan \"Selaraskan\" untuk mendengarkan setiap baris dalam audio dan merapatkan kode waktunya.", + "extractingAudio": "Mengekstrak audio...", + "aligningBatchXOfY": "Menyelaraskan... batch {0} dari {1}", + "extractAudioFailed": "Tidak dapat mengekstrak audio dengan ffmpeg.", + "modelWillBeDownloaded": "{0} akan diunduh", + "summaryXRetimedYKeptZSkipped": "Diatur ulang: {0} · Dipertahankan: {1} · Tanpa ucapan: {2}", + "meanShiftX": "Pergeseran rata-rata: {0} ms", + "toCheckX": "Perlu diperiksa: {0}", + "largeMovesToCheckX": "{0} baris akan bergeser jauh dengan sendirinya dan dibiarkan tanpa centang - dengarkan baris tersebut (F5 memutar baris hasil penyelarasan, Shift+F5 yang asli) dan centang yang benar.", + "linesToCheckX": "{0} baris dibiarkan tanpa centang untuk diperiksa - dengarkan baris tersebut (F5 memutar baris hasil penyelarasan, Shift+F5 yang asli) dan centang yang benar.", + "changeXOfY": "Perubahan {0} dari {1}", + "previousChange": "Perubahan sebelumnya", + "nextChange": "Perubahan berikutnya", + "startShift": "Mulai", + "endShift": "Akhir", + "statusRetimed": "Diatur ulang", + "statusMovedWithNeighbours": "Digeser bersama tetangganya", + "statusLargeMoveUnconfirmed": "Pergeseran besar - dengarkan, centang untuk menerapkan", + "statusUnchanged": "Sudah di tempatnya", + "statusNoSpeech": "Tanpa ucapan", + "statusShiftTooLarge": "Dipertahankan - pergeseran terlalu besar", + "statusFailed": "Dipertahankan - aligner gagal", + "statusAdjustedByHand": "Disesuaikan manual", + "alignedWaveformHint": "Seret baris untuk menggesernya, atau seret tepinya untuk mengubah awal atau akhirnya.", + "playLine": "Putar baris", + "undoAdjustment": "Urungkan penyesuaian", + "playPause": "Putar / jeda", + "playAligned": "Diselaraskan", + "playAlignedHint": "Putar baris terpilih dengan kode waktu hasil penyelarasan", + "apply": "Terapkan", + "applyHint": "Hapus centang baris untuk mempertahankan kode waktu aslinya. Klik ganda baris untuk memutarnya. Baris juga dapat digeser atau diubah ukurannya di bentuk gelombang hasil penyelarasan." + }, "beautifyTimeCodesProfile": { "title": "Edit profil percantik kode waktu", "loadPreset": "Muat prasetel", "presetDefault": "Default", "presetNetflix": "Netflix", "presetSdi": "SDI", - "general": "Umum", + "saveAsProfile": "Simpan sebagai profil...", + "deleteProfile": "Hapus profil", + "profileName": "Nama profil", + "overwriteProfileX": "Profil bernama \"{0}\" sudah ada. Timpa?", + "deleteProfileX": "Hapus profil \"{0}\"?", "gap": "Jeda", "gapSuffix": "bingkai (juga memperbarui kolom jeda non-nol di bawah)", "milliseconds": "ms", @@ -1993,9 +2124,12 @@ "hintChaining": "Cara menangani subtitel yang hampir bersentuhan — perpanjang bersama, atau dorong terpisah, tergantung apakah ada perubahan shot di antaranya." }, "bridgeGaps": { - "title": "Jembatani jeda", "bridgeGapsSmallerThan": "Jembatani jeda lebih kecil dari (ms)", "minGap": "Jeda minimum (ms)", + "bridgeGapsSmallerThanFrames": "Jembatani jeda lebih kecil dari (bingkai)", + "minGapFrames": "Jeda minimum (bingkai)", + "calculateBridgeGapsSmallerThanDotDotDot": "Hitung batas jembatan jeda dari laju bingkai...", + "useXMsAsBridgeGapsSmallerThan": "Jembatani jeda lebih kecil dari \"{0}\" milidetik?", "numberOfSmallGapsBridgedX": "Jumlah jeda kecil yang dijembatani: {0}", "percentFoPrevious": "Jeda untuk sebelumnya (%)", "gapChange": "Perubahan jeda" @@ -2007,8 +2141,6 @@ "sortBy": { "title": "Urutkan subtitel", "sortOrder": "Urutan", - "sortByNumber": "Nomor", - "sortByStartTime": "Waktu mulai", "sortByEndTime": "Waktu selesai" }, "batchConvert": { @@ -2031,6 +2163,7 @@ "addColor": "Tambahkan warna", "deleteLinesWithSpecificActorsOrStyles": "Hapus baris dengan aktor atau gaya (pisahkan dengan koma)", "useSourceStylesIfPossible": "Gunakan gaya sumber jika memungkinkan", + "keepSourceEmbeddedFonts": "Pertahankan fon tertanam sumber (footer)", "editStyles": "Edit gaya", "editProperties": "Edit properti", "editAttachments": "Edit lampiran", @@ -2051,7 +2184,6 @@ "assaChangeStyleFromStyle": "Ubah gaya dari", "assaChangeStyleToStyle": "menjadi", "assaChangeStyleImportStyle": "Impor gaya...", - "assaChangeStyleImportedX": "Diimpor: {0}", "assaChangeStyleTrimUnusedStyles": "Pangkas gaya yang tidak terpakai", "assaChangeStylePropertiesTitle": "Ubah properti gaya", "assaChangeStylePropertiesInfo": "Mengubah bidang yang dipilih pada setiap gaya di file, tanpa mengubah gaya itu sendiri.", @@ -2063,13 +2195,52 @@ "convertColorsToDialogReBreakLines": "Pisah ulang baris", "snapTimeCodesToFramesInfo": "Membulatkan setiap waktu mulai dan selesai ke bingkai terdekat. Laju bingkai dibaca dari file video dengan nama sama seperti file subtitel, kecuali laju bingkai tetap dipilih.", "addFolderDotDotDot": "Tambah folder...", + "addFolderRecursiveDotDotDot": "Tambah folder secara rekursif...", "selectFolderToConvert": "Pilih folder berisi file untuk dikonversi", "includeSubfolders": "Sertakan subfolder saat menambahkan folder", + "videoFilesWhenAddingFolder": "File video (mkv, mp4, ts...) saat menambahkan folder", + "videoFilesAsk": "Tanyakan", + "videoFilesInclude": "Tambahkan trek subtitelnya", + "videoFilesSkip": "Lewati", + "folderContainsXVideoFiles": "Folder berisi {0} file video (mkv, mp4, ts...).\n\nTambahkan juga trek subtitel tertanamnya, atau hanya file subtitel?", + "addVideoFiles": "Tambah file video", + "skipVideoFiles": "Hanya file subtitel", + "doNotAskAgainVideoFiles": "Jangan tanya lagi (ubah di pengaturan Konversi batch)", + "settingsSectionOutput": "Keluaran", + "settingsSectionAddingFiles": "Menambahkan file", + "settingsSectionImageBasedSubtitles": "Subtitel berbasis gambar (OCR)", + "settingsSectionOther": "Lainnya", "keepSourceFileTimestamp": "Pertahankan tanggal/waktu file sumber pada file keluaran", - "scanningFolderX": "Memindai {0}..." + "preventSleepWhileConverting": "Cegah komputer tidur saat mengonversi", + "scanningFolderX": "Memindai {0}...", + "transportStreamSettings": "Pengaturan Transport Stream", + "transportStreamSettingsDotDotDot": "Pengaturan Transport Stream...", + "transportStreamSettingsInfo": "Berlaku untuk trek subtitel yang diekstrak dari file Transport Stream (.ts/.m2ts). Posisi dan ukuran video hanya memengaruhi trek gambar DVB yang diekspor ke format berbasis gambar.", + "transportStreamOverrideXPosition": "Timpa posisi X asli", + "transportStreamOverrideYPosition": "Timpa posisi Y asli", + "transportStreamOverrideVideoSize": "Timpa ukuran video asli", + "transportStreamBottomMargin": "Margin bawah", + "transportStreamFileNameEnding": "Akhiran nama file", + "transportStreamFileNameEndingInfo": "Ditambahkan sebelum ekstensi untuk setiap trek yang diekstrak. Biarkan kosong untuk menggunakan akhiran bahasa biasa.", + "transportStreamOnlyTeletext": "Hanya teleteks", + "transportStreamGetSizeFromVideo": "Ambil ukuran dari video...", + "twoLetterLanguageCodeUppercase": "Kode bahasa dua huruf (huruf besar)", + "threeLetterLanguageCodeUppercase": "Kode bahasa tiga huruf (huruf besar)", + "formatContainsDotDotDot": "Format mengandung...", + "extensionIsDotDotDot": "Ekstensi adalah...", + "statusIsError": "Status adalah kesalahan", + "forcedTracksOnly": "Hanya trek paksa", + "xConvertedYFailedInZ": "{0:#,###,##0} dikonversi, {1:#,###,##0} gagal dalam {2}", + "xNotProcessed": "{0:#,###,##0} tidak diproses", + "removeUnicodeControlCharactersTitle": "Hapus karakter kontrol Unicode", + "removeUnicodeControlCharactersInfo": "Menghapus karakter kontrol arah Unicode yang tidak terlihat (LRM, RLM, LRE, RLE, PDF, LRO, dan RLO) dari teks, dan mengganti spasi tak terputus dengan spasi biasa.", + "preset": "Prasetel", + "savePresetDotDotDot": "Simpan prasetel...", + "deletePreset": "Hapus prasetel", + "presetName": "Nama prasetel", + "deletePresetX": "Hapus prasetel \"{0}\"?" }, "changeCasing": { - "title": "Ubah kapitalisasi", "fixNames": "Perbaiki nama", "namesX": "Nama: {0:#,##0}", "hitsX": "Kecocokan: {0:#,##0}", @@ -2080,9 +2251,7 @@ "allUppercase": "Semua huruf besar", "allLowercase": "Semua huruf kecil" }, - "changeFormatting": { - "title": "Ubah pemformatan" - }, + "changeFormatting": {}, "convertActors": { "title": "Konversi aktor", "convertActorFrom": "Konversi aktor dari", @@ -2111,18 +2280,12 @@ "outputFormatAssa": "Advanced Sub Station Alpha (.ass)", "style1": "Gaya 1", "style2": "Gaya 2", - "outlineWidth": "Lebar garis luar", - "shadowWidth": "Lebar bayangan", - "alignTop": "Atas", - "alignBottom": "Bawah", - "merge": "_Gabungkan", - "pleaseLoadTwoSubtitles": "Silakan muat dua subtitel" + "merge": "_Gabungkan" }, "splitSubtitle": { "title": "Pisah subtitel", "numberOfEqualParts": "Jumlah bagian yang sama", "saveSplitParts": "_Simpan bagian yang dipisah", - "subtitleSplitIntoXParts": "Subtitel dipisah menjadi {0} bagian.", "xPartsSavedInFormatYToFolder": "{0} bagian disimpan dalam format {1} ke folder:" }, "splitBreakLongLines": { @@ -2156,18 +2319,17 @@ "columnMerge": "Gabungkan", "columnFirst": "Baris pertama", "columnSecond": "Baris kedua", - "columnMerged": "Digabungkan", - "selectAll": "Pilih semua", - "inverseSelection": "Balikkan seleksi" + "columnMerged": "Digabungkan" }, "mergeLinesWithSameText": { - "title": "Gabungkan baris dengan teks yang sama", "maxMsBetweenLines": "Milidetik maks antar baris", - "includeIncrementingLines": "Sertakan baris dengan teks bertambah" + "maxFramesBetweenLines": "Maks. bingkai antar baris", + "includeIncrementingLines": "Sertakan baris dengan teks bertambah", + "includeRollUpCaptions": "Sertakan teks roll-up (bergulir)" }, "mergeLinesWithSameTimeCodes": { - "title": "Gabungkan baris dengan kode waktu yang sama", "maxMsDifference": "Perbedaan maks (milidetik)", + "maxFramesDifference": "Selisih maks. (bingkai)", "makeDialog": "Jadikan dialog" }, "netflixCheckAndFix": { @@ -2190,7 +2352,6 @@ "shotChanges": "Perubahan shot", "startNumberSpellOut": "Tulis lengkap angka di awal", "textforHiUseBrackets": "Teks untuk HI, gunakan tanda kurung siku", - "frameRate": "Laju bingkai", "twoFrameGrap": "Jeda dua bingkai", "whiteSpace": "Spasi kosong", "childrensProgram": "Program anak-anak", @@ -2228,7 +2389,6 @@ "italicsFixed": "Miring diperbaiki", "italicsNotAllowed": "Miring tidak diizinkan", "bridgeGapsXToYFrames": "Jeda 3-{0} bingkai => jeda 2 bingkai", - "startNumberSpellOutComment": "Ketika angka mengawali kalimat, angka harus selalu ditulis lengkap", "maximumXCharactersPerSecond": "Maksimum {0} karakter per detik" }, "imageBasedEdit": { @@ -2237,10 +2397,8 @@ "resizeImagesDotDotDot": "Ubah ukuran gambar...", "adjustBrightnessDotDotDot": "Sesuaikan kecerahan...", "adjustAlphaDotDotDot": "Sesuaikan alfa...", - "centerHorizontally": "Ratakan tengah horizontal", "cropImages": "Pangkas gambar", "importTimeCodes": "Impor kode waktu...", - "setTextForSubtitle": "Atur teks untuk subtitel", "setText": "Atur teks", "screenWidth": "Lebar layar", "screenHeight": "Tinggi layar", @@ -2271,15 +2429,31 @@ "letterboxCustom": "Tinggi bilah kustom", "barHeightPx": "Tinggi bilah (px)", "titleSafePercent": "Area aman judul (%)", + "moveCaptions": "Geser teks", + "moveCaptionsDotDotDot": "Geser teks...", + "moveCaptionsTo": "Geser teks", + "moveCaptionsIntoBars": "Ke dalam bilah letterbox (di luar gambar)", + "moveCaptionsIntoPicture": "Di dalam gambar (keluar dari bilah)", + "moveCaptionsOffset": "Jarak dari tepi (px)", + "letterboxBarHeightXPxYOfZCaptionsWillMove": "Tinggi bilah: {0} px - {1} dari {2} teks akan digeser", + "removeFadeInOut": "Hapus pudar masuk/keluar", + "removeFadeInOutXLinesRemoved": "{0} baris pudar digabungkan ke baris induknya", + "removeFadeInOutNothingFound": "Tidak ditemukan baris pudar masuk/keluar", "positionSummary": "{0}×{1} - {2} subtitel - tinggi bilah: {3} px", "xInActivePicture": "{0} di gambar", "xInTopBar": "{0} di bilah atas", "xInBottomBar": "{0} di bilah bawah", "noImageSubtitlesLoaded": "Tidak ada subtitel gambar yang dimuat", - "imageBasedFormatNotSupported": "Format subtitel berbasis gambar tidak ditemukan/didukung." + "imageBasedFormatNotSupported": "Format subtitel berbasis gambar tidak ditemukan/didukung.", + "frameRateDeclaredInFile": "Laju bingkai yang dinyatakan dalam file sup Blu-ray", + "frameRateDetectedFromTimeCodesX": "Laju bingkai terdeteksi dari kode waktu sup Blu-ray (file menyatakan {0})", + "frameRateFromVideo": "Laju bingkai video", + "frameRateCurrent": "Laju bingkai saat ini", + "frameRateSetManually": "Laju bingkai diatur manual", + "frameRateUsageInfo": "Ekspor Blu-ray sup, BDN XML, DOST, dan FCP ditulis dengan laju bingkai ini. Ini tidak mengubah waktu apa pun - gunakan \"Ubah laju bingkai\" untuk itu.", + "useVideoFrameRateXInsteadOfY": "Laju bingkai video adalah {0}, tetapi subtitel diatur ke {1}.\n\nGunakan laju bingkai video ({0})?" }, "removeTextForHearingImpaired": { - "title": "Hapus teks untuk tunarungu", "interjections": "Interjeksi", "skipIfStartWith": "Lewati jika diawali dengan", "removeTextBetween": "Hapus teks di antara", @@ -2298,7 +2472,6 @@ "removeInterjections": "Hapus interjeksi", "linesFoundX": "Baris ditemukan: {0}" }, - "pickAlignmentTitle": "Pilih perataan", "removeUnicodeCharacters": { "title": "Hapus/ganti karakter Unicode", "charactersFoundX": "Karakter Unicode ditemukan: {0}", @@ -2308,13 +2481,13 @@ "pickFontNameInstalledFonts": "Fon terpasang", "pickFontNameCollectedFonts": "Fon terkumpul", "colorPickerTitle": "Pilih warna", + "colorPickerBrightness": "Kecerahan", "filterLayersTitle": "Filter lapisan untuk ditampilkan", "filterLayersHideFromWaveform": "Sembunyikan dari bentuk gelombang/spektogram", "filterLayersHideFromSubtitleGrid": "Sembunyikan dari kisi subtitel", "filterLayersHideFromVideoPreview": "Sembunyikan dari pratinjau video", "pickSubtitleFormat": "Pilih format subtitel", "pickSubtitleFormatImageBasedNoPreview": "Format subtitel berbasis gambar.\nBitmap dibuat selama konversi - tidak ada pratinjau teks.", - "pickLayerTitle": "Atur lapisan", "recentColors": "Warna terbaru", "fixCommonErrorsSettingsTitle": "Pengaturan perbaikan kesalahan umum" }, @@ -2359,59 +2532,51 @@ "correctWordsX": "Kata benar: {0}", "namesX": "Nama: {0}", "addedToDictionaryX": "Ditambahkan ke kamus: {0}", - "doNotShowThisAgain": "Jangan tampilkan pesan ini lagi" + "doNotShowThisAgain": "Jangan tampilkan pesan ini lagi", + "editUseAlwaysList": "Edit daftar \"Selalu gunakan\"...", + "useAlwaysListTitle": "Periksa ejaan - daftar \"Selalu gunakan\"", + "useAlwaysListHeader": "Daftar \"Selalu gunakan\"", + "useAlwaysListDescription": "Kata-kata yang diganti otomatis oleh pemeriksa ejaan, disimpan oleh \"Ubah semua\" dan \"Selalu gunakan\". Pasangan hanya digunakan bila kata tersebut salah eja.", + "useAlwaysWord": "Kata salah eja", + "xPairs": "{0:#,##0} pasangan", + "xUnusedPairs": "{0:#,##0} tidak pernah digunakan", + "useAlwaysUnusedHint": "Tidak pernah digunakan - kata ini dieja dengan benar (atau merupakan nama atau kata pengguna), sehingga pemeriksa ejaan tidak pernah menandainya", + "removeUnused": "Hapus yang tidak digunakan", + "useAlwaysEmpty": "Belum ada pasangan \"Selalu gunakan\" untuk bahasa ini", + "useAlwaysEmptyHint": "Pasangan ditambahkan saat Anda mengklik \"Ubah semua\" atau \"Selalu gunakan\" di pemeriksa ejaan.", + "useAlwaysNoMatch": "Tidak ada pasangan yang cocok dengan pencarian", + "useAlwaysSameWord": "Pengganti harus berbeda dari kata tersebut.", + "useAlwaysRememberOff": "\"Ingat daftar 'Selalu gunakan'\" dinonaktifkan di Pengaturan, sehingga pemeriksa ejaan tidak menggunakan atau menyimpan daftar ini." }, "video": { "burnIn": { "title": "Buat video dengan subtitel terbakar", - "infoAssaOff": "Catatan: Gaya Advanced SubStation Alpha didukung.", - "infoAssaOn": "Catatan: Gaya Advanced SubStation Alpha akan digunakan :)", - "xGeneratedWithBurnedInSubsInX": "\"{0}\" dibuat dengan subtitel terbakar dalam {1}.", - "timeRemainingMinutes": "Waktu tersisa: {0} menit", - "timeRemainingOneMinute": "Waktu tersisa: Satu menit", - "timeRemainingSeconds": "Waktu tersisa: {0} detik", - "timeRemainingAFewSeconds": "Waktu tersisa: Beberapa detik", - "timeRemainingMinutesAndSeconds": "Waktu tersisa: {0} menit dan {1} detik", - "timeRemainingOneMinuteAndSeconds": "Waktu tersisa: Satu menit dan {0} detik", - "targetFileName": "Nama file tujuan: {0}", "targetFileSize": "Ukuran file tujuan (memerlukan pengodean 2 lintasan)", "fileSizeMb": "Ukuran file dalam MB", "matchSourceVideoSize": "Samakan dengan ukuran video sumber", - "passX": "Lintasan {0}", - "encoding": "Pengodean", "bitRate": "Laju bit", "totalBitRateX": "Total laju bit: {0}", "sampleRate": "Laju sampel", "audio": "Audio", - "stereo": "Stereo", "preset": "Prasetel", + "tune": "Tune", "pixelFormat": "Format piksel", "crf": "CRF", - "tuneFor": "Setel untuk", - "alignRight": "Rata kanan", - "getStartPosition": "Ambil posisi awal", - "getEndPosition": "Ambil posisi akhir", "useSource": "Gunakan sumber", - "useSourceFolder": "Gunakan folder sumber", - "useSourceResolution": "Gunakan resolusi sumber", - "outputSettings": "File/folder keluaran...", "fontSizeFactor": "Faktor ukuran fon", "boxType": "Jenis kotak", - "fixRightToLeft": "Perbaiki kanan-ke-kiri", - "cut": "Potong", "fromTime": "Dari waktu", "toTime": "Sampai waktu", "audioEncoding": "Pengodean audio", "outputProperties": "Properti keluaran...", "videoFileSize": "Ukuran file video", "oneBox": "Satu kotak", - "boxPerLine": "Kotak per baris", - "logoInfo": "Pilih gambar PNG dan seret untuk memosisikannya di video." + "logoInfo": "Pilih gambar PNG dan seret untuk memosisikannya di video.", + "noSubtitlesGenerateAnyway": "Tidak ada baris subtitel untuk dibakar.\n\nBuat video tanpa subtitel?", + "noSubtitlesInXItemsGenerateAnyway": "{0} item batch tidak memiliki baris subtitel untuk dibakar.\n\nBuat video tersebut tanpa subtitel?" }, "videoTransparent": { - "title": "Buat video transparan dengan subtitel", - "infoAssaOff": "Catatan: Gaya Advanced SubStation Alpha didukung.", - "infoAssaOn": "Catatan: Gaya Advanced SubStation Alpha akan digunakan :)" + "title": "Buat video transparan dengan subtitel" }, "audioToText": { "title": "Ucapan ke teks", @@ -2419,6 +2584,21 @@ "translateToEnglish": "Terjemahkan ke bahasa Inggris", "transcribing": "Mentranskripsi...", "transcribingXOfY": "Mentranskripsi {0} dari {1}...", + "linesTranscribedXOfYFailedZ": "{0} dari {1} baris telah ditranskripsikan - {2} gagal atau tidak berisi ucapan.\n\nBaris yang gagal mempertahankan teksnya saat ini. Terapkan baris yang ditranskripsikan, atau coba lagi yang gagal (Anda dapat mengubah mesin atau pengaturan terlebih dahulu)?", + "linesTranscribedXOfYCancelled": "Transkripsi dibatalkan setelah {0} dari {1} baris.\n\nTerapkan baris yang ditranskripsikan? Baris lainnya mempertahankan teksnya saat ini.", + "noLinesTranscribed": "Tidak satu pun dari {0} baris dapat ditranskripsikan.\nSilakan periksa log alat untuk detailnya.", + "applyTranscribedLines": "Terapkan yang ditranskripsikan", + "retryFailedLines": "Coba lagi yang gagal", + "isolateSpeech": "Isolasi ucapan (lambat)", + "isolateSpeechHint": "Menghapus musik dan efek suara sebelum transkripsi. Menghasilkan lebih sedikit baris yang terlewat dan pewaktuan yang lebih baik pada audio dengan musik keras, tetapi memakan waktu kira-kira sepanjang audio itu sendiri dengan GPU - dan berkali-kali lebih lama tanpa GPU.", + "isolatingSpeech": "Mengisolasi ucapan...", + "isolateSpeechFailed": "Tidak dapat mengisolasi ucapan - mentranskripsikan audio asli sebagai gantinya.", + "detectSpeakers": "Deteksi pembicara", + "detectSpeakersHint": "Memulai setiap baris dengan pembicara, seperti \"(speaker 0)\", menggunakan diarisasi Sortformer dari NVIDIA (hingga 8 pembicara). Paling baik dengan backend yang memisah baris saat pembicara berganti, seperti Parakeet dan Cohere.", + "detectSpeakersNeedsNewerCrispAsr": "\"Deteksi pembicara\" memerlukan Crisp ASR v{0} atau yang lebih baru, tetapi yang terpasang v{1}.\n\nPerbarui Crisp ASR (tombol roda gigi di samping mesin) atau nonaktifkan \"Deteksi pembicara\".", + "xNeedsNewerCrispAsr": "{0} memerlukan Crisp ASR v{1} atau yang lebih baru, tetapi yang terpasang v{2}.\n\nUnduh dan pasang pembaruan sekarang?", + "vad": "VAD", + "vadHint": "Deteksi aktivitas suara: hanya ucapan yang ditranskripsikan, dipotong saat pembicara berhenti sejenak.\nOtomatis: Silero, dan hanya untuk mesin yang memerlukannya (Cohere, Mega, Index-Echo).\nSilero, FireRedVAD, Whisper-VAD, WebRTC: selalu aktif, untuk setiap mesin Crisp ASR. FireRedVAD paling akurat, Whisper-VAD memberi potongan paling sedikit dan paling bersih tetapi paling lambat.\nIndex-Echo selalu menggunakan Silero. --vad, --vad-model, atau --chunk-seconds di pengaturan lanjutan lebih diutamakan.", "inputLanguage": "Bahasa masukan", "advancedWhisperSettings": "Parameter ucapan-ke-teks lanjutan", "downloadingSpeechToTextEngine": "Mengunduh mesin ucapan-ke-teks", @@ -2433,8 +2613,6 @@ "addCustomModelDotDotDot": "Tambahkan model kustom...", "customModelHelp": "Anda dapat menggunakan model sendiri: pilih file model (whisper.cpp ggml '.bin') atau folder model (folder faster-whisper dengan 'model.bin' di dalamnya). Model disalin ke folder model dan ditambahkan ke daftar di atas.", "viewToolsLogFile": "Lihat file log alat", - "reDownloadX": "Unduh ulang {0}", - "downloadX": "Unduh {0}", "updateXTitle": "Perbarui {0}?", "updateXMessage": "Versi lebih baru dari {0} tersedia.{1}{1}Unduh dan pasang pembaruan sekarang?", "downloadingSpeechToTextModel": "Mengunduh model ucapan-ke-teks", @@ -2446,14 +2624,6 @@ "fixCasing": "Perbaiki kapitalisasi", "addPeriods": "Tambahkan titik", "changeUnderlineToColor": "Ubah garis bawah menjadi warna", - "cueBuilding": "Pembentukan cue (MLX Whisper)", - "cueRebuild": "Bentuk ulang cue dari stempel waktu kata", - "cueMaxChars": "Karakter maks per cue", - "cueMaxSeconds": "Detik maks per cue", - "cueMaxCps": "Karakter maks per detik", - "vocabularyPrompt": "Prompt kosakata (nama, tempat, istilah)", - "beamSize": "Ukuran beam", - "beamSizeNote": "0 memecahkan kode tercepat. 5 memberikan akurasi terbaik (seperti Faster Whisper) dengan waktu sekitar 20-30% lebih lama. Nilai di atas 5 jarang membantu.", "removeNonSpeechLines": "Hapus baris non-ucapan", "removeNonSpeechLinesHint": "Buang baris yang hanya menggambarkan suara, seperti \"[Musik]\" atau \"(deburan ombak)\"", "removeRepeatedLines": "Hapus baris berulang", @@ -2489,14 +2659,11 @@ }, "textToSpeech": { "title": "Teks ke ucapan", - "textToSpeechEngine": "Mesin teks ke ucapan", "reviewAudioSegments": "TTS - Tinjau segmen audio", "reviewAudioSegmentsHistory": "TTS - Tinjau riwayat audio", "stability": "Stabilitas", "similarity": "Kemiripan", "speakerBoost": "Penguatan pembicara", - "styleExaggeration": "Penekanan gaya", - "regenerateAudioSelectedLine": "Buat ulang audio untuk baris terpilih", "generateSpeechFromText": "Buat ucapan dari teks", "testVoice": "Uji suara", "addAudioToVideoFile": "Tambahkan audio ke file video", @@ -2515,6 +2682,19 @@ "videoEncodingSettings": "TTS - Pengaturan pengodean video", "elevenLabsSettings": "TTS - Pengaturan ElevenLabs", "elevenLabsSettingsResetHint": "Atur ulang pengaturan ElevenLabs ke nilai default", + "provider": "Penyedia", + "openAiCompatibleSettings": "TTS - Pengaturan kompatibel OpenAI", + "customServerUrl": "URL server kustom", + "customServerUrlHint": "Endpoint ucapan lengkap dari server kompatibel OpenAI, digunakan saat penyedia adalah \"Custom\", mis. http://localhost:8880/v1/audio/speech", + "customModels": "Model kustom", + "customModelsHint": "Nama model dipisahkan koma yang ditawarkan saat penyedia adalah \"Custom\"", + "customVoices": "Suara kustom", + "customVoicesHint": "Nama suara dipisahkan koma, digunakan oleh penyedia \"Custom\" dan oleh model OpenRouter yang tidak menerbitkan daftar suara", + "instructions": "Instruksi", + "instructionsHint": "Gaya bicara opsional, mis. \"Berbicaralah dengan suara tenang dan hangat\". Hanya dikirim ke model yang mendukungnya (bukan tts-1/tts-1-hd)", + "openAiSpeedHint": "Kecepatan ucapan dari 0.25 hingga 4.0 (1.0 normal). Tidak semua model mendukungnya", + "outputFormat": "Format keluaran", + "outputFormatHint": "Format audio yang diminta dari server. \"Auto\" meminta MP3 dan beralih ke PCM untuk model yang hanya mengembalikan PCM (mis. Gemini TTS)", "regenerateAudio": "Buat ulang audio", "autoContinuePlaying": "Lanjut putar otomatis", "playLine": "Putar baris", @@ -2523,6 +2703,13 @@ "addingAudioToVideoFileDotDotDot": "Menambahkan audio ke file video...", "preparingMergeDotDotDot": "Menyiapkan penggabungan...", "importVoiceDotDotDot": "Impor suara...", + "renameVoiceDotDotDot": "Ganti nama suara...", + "renameVoiceTitle": "Ganti nama suara", + "voiceXCouldNotBeRenamedX": "Suara '{0}' tidak dapat diganti namanya: {1}", + "deleteVoiceDotDotDot": "Hapus suara...", + "deleteVoiceTitle": "Hapus suara", + "deleteVoiceXQuestion": "Hapus suara '{0}'?\n\nRekaman referensi dan file-filenya dihapus dari disk.", + "voiceXCouldNotBeDeletedX": "Suara '{0}' tidak dapat dihapus: {1}", "voiceImportSuccessTitle": "Suara diimpor", "voiceXImported": "Suara '{0}' berhasil diimpor", "voiceXCouldNotBeImported": "Suara '{0}' tidak dapat diimpor - lihat log untuk detailnya", @@ -2532,6 +2719,49 @@ "dropAudioFileHereToImportVoice": "Jatuhkan file audio di sini untuk mengimpor suara", "dropAudioFileHereHint": ".wav atau .mp3", "voiceCloneTranscriptTitle": "Masukkan transkrip audio (diperlukan untuk kloning suara)", + "voiceManagerTitle": "TTS - Pengelola suara", + "voiceManagerDotDotDot": "Pengelola suara...", + "voiceManagerMenuItem": "Pengelola suara TTS...", + "speakFromCurrentLineTitle": "Ucapkan dari baris saat ini", + "speakFromCurrentLineDotDotDot": "Ucapkan dari baris saat ini...", + "speakFromCurrentLineHint": "Membacakan baris satu per satu dengan keras, mulai dari baris saat ini. Tekan Escape atau klik baris lain untuk berhenti.", + "stopSpeaking": "Berhenti berbicara", + "speakingLineX": "Mengucapkan baris {0}... (Escape untuk berhenti)", + "playWithSpeechTitle": "Putar dengan ucapan", + "playWithSpeechDotDotDot": "Putar dengan ucapan...", + "playWithSpeechHint": "Memutar video dan mengucapkan setiap baris saat video mencapainya. Jeda dan cari posisi seperti biasa; tekan Escape untuk berhenti.", + "lowerVideoVolumeWhileSpeaking": "Turunkan volume video saat berbicara", + "pauseVideoWhenSpeechRunsLate": "Jeda video saat ucapan melewati baris berikutnya", + "waitingForSpeechLineX": "Membuat ucapan untuk baris {0}... (Escape untuk berhenti)", + "voiceKindClone": "Suara kloning", + "voiceKindPreset": "Bawaan", + "voiceKindModel": "Model suara", + "voiceKindOnline": "Daring", + "transcript": "Transkrip", + "transcriptRequiredHint": "Wajib - mesin membaca transkrip rekaman ini (ref-text) saat mengkloning", + "transcriptOptionalHint": "Opsional - transkrip rekaman yang akurat meningkatkan hasil kloning", + "saveTranscript": "Simpan transkrip", + "searchVoices": "Cari suara", + "loadingVoicesDotDotDot": "Memuat suara...", + "noVoicesForEngine": "Tidak ada suara - impor rekaman atau unduh paket suara", + "noVoicesMatchFilter": "Tidak ada suara yang cocok dengan pencarian", + "copyVoiceTo": "Salin suara ke", + "copyVoiceToDotDotDot": "Salin suara ke mesin...", + "copyingVoiceXToYDotDotDot": "Menyalin suara '{0}' ke {1}...", + "voiceXCopiedToY": "Suara '{0}' disalin ke {1}", + "voiceXCouldNotBeCopiedToY": "Suara '{0}' tidak dapat disalin ke {1} - lihat log untuk detailnya", + "voiceXAlreadyExistsInYContinue": "Suara bernama '{0}' sudah ada di {1}.\n\nTetap salin?", + "openVoicesFolder": "Buka folder suara", + "downloadVoicePacksDotDotDot": "Unduh paket suara...", + "downloadVoicePacksTitle": "TTS - Unduh paket suara", + "voicePackIntro": "Paket suara adalah kumpulan rekaman pendek yang bersih beserta transkripnya, siap untuk dikloning. Setiap paket diimpor ke mesin yang dipilih (suara yang sudah ada dilewati) dan menyertakan ATTRIBUTION.txt dengan lisensi dan sumbernya.", + "voicePackDetailsXVoicesYSizeZLicense": "{0} suara · {1} · {2}", + "installToEngine": "Pasang ke mesin", + "downloadAndInstall": "Unduh dan pasang", + "selectAtLeastOneVoicePack": "Pilih setidaknya satu paket suara", + "downloadingXDotDotDot": "Mengunduh {0}...", + "installingXDotDotDot": "Memasang {0}...", + "xVoicesInstalledYSkipped": "{0} suara dipasang ({1} sudah ada)", "useSpeechToTextDotDotDot": "Gunakan ucapan-ke-teks...", "autoCastMenuItem": "Temukan suara dalam video dan kloning...", "autoCastSpeakersTitle": "Suara yang ditemukan dalam video", @@ -2550,7 +2780,7 @@ "cloneVoicePerLinePreparing": "Mengambil suara setiap baris dari video...", "cloneVoicePerLineNeedsVideo": "Mengkloning suara setiap baris memerlukan video milik subtitel ini. Buka videonya lalu coba lagi.", "cloneVoicePerLineNoClips": "Tidak ada audio yang dapat diambil dari video, jadi tidak ada yang bisa dikloning. Periksa apakah video memiliki trek audio.", - "cloneVoicePerLineNeedsOriginalSubtitleX": "{0} hanya dapat mengkloning sebuah baris bila mengetahui apa yang diucapkan dalam video pada baris itu, yang berasal dari subtitel bahasa asli. Tidak ada subtitel asli yang dimuat, jadi setiap baris akan diucapkan oleh suara pertama yang diimpor alih-alih suara dalam video.\n\nBuka subtitel asli (File > Buka asli...) bersama terjemahannya, lalu buat lagi.", + "cloneVoicePerLineTranscribeClipsXYZ": "{0} hanya dapat mengkloning baris bila diketahui apa yang diucapkan dalam video pada baris tersebut. Hal itu tidak diketahui untuk {1} dari {2} baris - biasanya berasal dari subtitel bahasa asli (File > Buka asli...).\n\nTranskripsikan baris tersebut dari video dengan ucapan ke teks sekarang?\n\nYa: pilih mesin dan bahasa yang diucapkan dalam video, dan klip referensi ditranskripsikan sebelum ucapan dibuat.\nTidak: tetap buat - baris tersebut diucapkan oleh suara biasa, bukan suara dalam video.", "voiceCloneConsentTitle": "Kloning suara - sebelum Anda mulai", "voiceCloneConsentHeader": "Anda akan mengkloning sebuah suara", "voiceCloneConsentIntro": "Kloning menyalin suara orang sungguhan. Hal itu tunduk pada aturan di sebagian besar wilayah, dan di UE disertai kewajiban hukum yang menjadi tanggung jawab Anda, bukan Subtitle Edit. Silakan baca ini sekali.", @@ -2567,10 +2797,29 @@ "advancedTtsSettings": "Pengaturan TTS lanjutan", "advancedTtsAudioProcessing": "Pemrosesan audio", "advancedTtsOutput": "Keluaran", + "advancedTtsVoiceCloning": "Kloning suara", + "cleanCloneReferences": "Bersihkan referensi kloning suara (Sidon)", + "cleanCloneReferencesDescription": "Menghapus musik, derau, dan gema dari audio sumber kloning suara: klip yang diambil dari video dan rekaman yang diimpor di pengelola suara. Menggunakan model pemulihan ucapan Sidon pada audio.cpp (unduhan 940 MB). Suara kloning tidak lagi meniru latar belakang, tetapi ucapan yang sangat pelan atau bertumpang tindih bisa terdengar teredam.", + "cleaningCloneReferencesDotDotDot": "Membersihkan referensi kloning suara...", + "downloadSidonTitle": "Unduh Sidon?", + "downloadSidonQuestionX": "Membersihkan referensi kloning suara memerlukan model pemulihan ucapan Sidon ({0}).\n\nUnduh sekarang? Tanpa model ini, referensi digunakan apa adanya.", "proAudioPostProcessing": "Pascapemrosesan audio pro", "proAudioPostProcessingDescription": "Menerapkan kehangatan EQ, gerbang derau, kompresi, normalisasi kenyaringan (-16 LUFS), dan pudar masuk/keluar pada setiap segmen.", + "generatingSpeechSegmentXOfY": "Membuat ucapan: segmen {0} dari {1}", + "adjustingSpeedSegmentXOfY": "Menyesuaikan kecepatan: segmen {0} dari {1}", + "mergingAudioSegmentXOfY": "Menggabungkan audio: segmen {0} dari {1}", + "postProcessingSegmentXOfY": "Pascaproses: segmen {0} dari {1}", "audioDucking": "Ducking audio", "audioDuckingDescription": "Mengurangi volume audio video asli dan mencampurnya dengan audio TTS, sehingga jalur suara asli masih terdengar samar.", + "removeOriginalSpeech": "Hapus ucapan asli (lambat)", + "removeOriginalSpeechDescription": "Mempertahankan musik dan efek suara dari video asli tetapi menghapus ucapannya, lalu mencampur ucapan baru di atasnya. Memakan waktu kira-kira sepanjang video itu sendiri dengan GPU, dan berkali-kali lebih lama tanpa GPU. Jika ducking audio aktif, volumenya berlaku untuk musik dan efek. Hanya berlaku saat ucapan ditambahkan ke file video.", + "removingOriginalSpeech": "Menghapus ucapan asli...", + "removeOriginalSpeechFailed": "Tidak dapat menghapus ucapan asli - ucapan ditambahkan ke video tanpa penghapusan.", + "speechRemovalModel": "Model", + "speechRemovalModelDescription": "Kualitas terbaik mempertahankan musik dan efek suara paling banyak. Cepat kira-kira tiga kali lebih cepat, dengan sedikit lebih banyak musik yang hilang.", + "speechRemovalBestQuality": "Kualitas terbaik (Mel-Band RoFormer)", + "speechRemovalFast": "Cepat (HTDemucs)", + "videoTruncatedWarning": "Gambar dalam file video baru berhenti setelah {0}, tetapi video asli berdurasi {1} - gambar akan membeku sementara suara terus berlanjut.\n\nFile tetap disimpan. Lihat error-log.txt di folder data Subtitle Edit untuk keluaran ffmpeg.", "originalVolumePercent": "Volume asli %", "vadSilenceCompression": "Kompresi keheningan VAD", "vadSilenceCompressionDescription": "Mempersingkat jeda antar kata sebelum mengubah tempo. Menggunakan Voice Activity Detection untuk memampatkan hanya jeda keheningan sambil menjaga ucapan tetap utuh. Ini langkah pertama yang disarankan — mengurangi durasi tanpa kehilangan kualitas.", @@ -2644,7 +2893,6 @@ "subtitleMergedLinesAppliedPlural": "{0} penggabungan baris diterapkan dari teks ke ucapan" }, "shotChanges": { - "titleGenerateOrImport": "Buat/impor perubahan shot", "generateShotChanges": "Buat perubahan shot", "importShotChanges": "Impor perubahan shot", "generateShotChangesWithFfmpeg": "Buat perubahan shot dengan ffmpeg", @@ -2654,7 +2902,9 @@ "timeCodeFormatColon": "Format kode waktu:", "shotChangesList": "Daftar perubahan shot", "shotChangesClearQuestion": "Apakah Anda yakin ingin membersihkan semua perubahan shot?", - "deleteSelectedShotChangeQuestion": "Apakah Anda yakin ingin menghapus perubahan shot terpilih?" + "deleteSelectedShotChangeQuestion": "Apakah Anda yakin ingin menghapus perubahan shot terpilih?", + "shotChanges": "Perubahan shot", + "exportShotChanges": "Ekspor perubahan shot" }, "chapters": { "title": "Bab", @@ -2684,7 +2934,6 @@ "scaleTimesDescription": "Skalakan setiap waktu bab dari satu laju bingkai ke laju bingkai lain.", "fromFrameRate": "Dari", "toFrameRate": "Ke", - "apply": "Terapkan", "writeToVideo": "Tulis ke video...", "writeToVideoTitle": "Tulis bab ke video", "writeToVideoDescription": "Bab ditulis ke dalam salinan file video. Tidak ada yang dienkode ulang, jadi ini cepat dan tanpa kehilangan kualitas.", @@ -2694,7 +2943,6 @@ "writtenToX": "Bab ditulis ke {0}", "unableToWriteChapters": "Tidak dapat menulis bab ke file video.", "chaptersSavedToX": "Bab disimpan ke {0}", - "noVideoLoaded": "Tidak ada video dimuat", "emptyListCallToAction": "Belum ada bab - tambahkan satu pada posisi video, atau impor dari video atau file." }, "videoOcr": { @@ -2705,10 +2953,6 @@ "bottomHalf": "Setengah bawah", "fullFrame": "Bingkai penuh", "engine": "Mesin OCR", - "language": "Bahasa", - "url": "URL", - "model": "Model", - "apiKey": "Kunci API", "scan": "Pindai", "framesPerSecond": "Bingkai per detik", "textBrightnessMinimum": "Kecerahan teks minimum (0=mati)", @@ -2723,10 +2967,10 @@ "runningOcrXY": "Menjalankan OCR... {0}/{1}", "noLinesFoundTitle": "Tidak ada subtitel ditemukan", "noLinesFoundMessage": "Tidak ada teks ditemukan di area pindai - coba sesuaikan area pindai, mesin, atau kecerahan minimum.", + "ocrStoppedEarlyMessage": "Mesin OCR berhenti sebelum semua bingkai dibaca, sehingga baris yang ditemukan sejauh ini dipertahankan dan sisanya hilang.{0}{0}{1}", "linesFoundX": "{0} baris ditemukan", "refiningTimingXY": "Menyempurnakan pewaktuan... {0}/{1}", "fixOcrErrors": "Perbaiki kesalahan OCR", - "dictionary": "Kamus", "editLineX": "Edit baris {0}", "downloadDictionaryHint": "Unduh kamus periksa ejaan...", "framesPerSecondHint": "Berapa bingkai per detik yang dipindai. 5 mencukupi untuk subtitel biasa; nilai lebih tinggi menangkap baris yang sangat singkat dan mempererat pengelompokan, tetapi memakan lebih banyak waktu OCR.", @@ -2743,7 +2987,58 @@ "testOcr": "Uji bingkai saat ini", "testOcrRunning": "Menguji OCR pada bingkai saat ini...", "testOcrResultX": "Hasil uji: {0}", - "testOcrNoTextFound": "Uji: tidak ada teks ditemukan di area pindai" + "testOcrNoTextFound": "Uji: tidak ada teks ditemukan di area pindai", + "engineSettings": "Pengaturan mesin OCR", + "engineBuiltIn": "Bawaan sistem operasi", + "engineExternalServer": "Server eksternal (dipasang dan dikelola di luar Subtitle Edit)", + "engineCloudApi": "API cloud (memerlukan kunci API)", + "engineNothingToInstall": "Tidak ada yang perlu dipasang", + "engineModelsMissing": "Mesin terpasang, model belum ada", + "website": "Situs web" + }, + "backgroundMusic": { + "title": "Buat musik latar", + "generateBackgroundMusicDotDotDot": "Buat musik latar...", + "preset": "Prasetel", + "prompt": "Prompt", + "tempoBpm": "Tempo (BPM)", + "generateSeconds": "Buat (detik)", + "seed": "Seed", + "randomSeed": "Acak", + "outputLength": "Panjang keluaran", + "outputLengthVideoX": "{0} (panjang video - musik berulang tanpa jeda)", + "musicVolumePercent": "Volume musik (%)", + "removeExistingAudioTracks": "Hapus trek audio yang ada", + "originalAudioVolumePercent": "Volume audio asli (%)", + "saveAudioDotDotDot": "Simpan audio...", + "addToVideoDotDotDot": "Tambahkan ke video...", + "engineInfo": "ACE-Step 1.5 Turbo via audio.cpp (lisensi MIT) - berjalan secara lokal", + "presetCooking": "Memasak / membuat kue", + "presetTech": "Ulasan teknologi / gawai", + "presetForest": "Hutan / alam", + "presetRelaxingPiano": "Piano santai", + "presetLofiVlog": "Vlog lo-fi", + "presetCorporate": "Korporat / video penjelasan", + "loadingModel": "Memuat model...", + "composing": "Mengomposisi...", + "generatingAudio": "Membuat audio...", + "decodingAudio": "Mendekode audio...", + "creatingLoop": "Membuat pengulangan tanpa jeda...", + "resultLoopXYZ": "Pengulangan: {0} pada {1} BPM, kecocokan sambungan {2}%", + "resultNoLoopX": "Musik siap: {0}", + "addingMusicToVideo": "Menambahkan musik ke video...", + "downloadModelTitle": "Unduh model musik?", + "downloadModelQuestionX": "Membuat musik memerlukan model ACE-Step 1.5 ({0}).\n\nUnduh model?", + "downloadingModel": "Mengunduh model ACE-Step 1.5...", + "saveAudioTitle": "Simpan musik latar", + "audioFileSavedX": "File audio disimpan: {0}", + "unableToGenerateMusic": "Tidak dapat membuat musik", + "unableToAddMusicToVideo": "Tidak dapat menambahkan musik ke video", + "outputLengthTextToSpeech": "Mengikuti ucapan yang dibuat - musik berulang tanpa jeda dan meredup saat seseorang berbicara", + "addBackgroundMusic": "Tambah musik latar", + "addBackgroundMusicHint": "Buat musik secara lokal dengan ACE-Step 1.5 dan campur di bawah ucapan - musik meredup saat seseorang berbicara", + "generatingBackgroundMusicDotDotDot": "Membuat musik latar...", + "mixingBackgroundMusicDotDotDot": "Mencampur musik latar..." }, "goToVideoPosition": "Pergi ke posisi video", "goToVideoPositionDotDotDot": "Pergi ke posisi video...", @@ -2754,30 +3049,88 @@ "openSecondarySubtitleOnVideoPlayerDotDotDot": "Subtitel sekunder di pemutar video, buka...", "openSecondarySubtitleOnVideoPlayer": "Subtitel sekunder (di pemutar video)", "removeSecondarySubtitleOnVideoPlayer": "Subtitel sekunder di pemutar video, hapus", + "editSecondarySubtitleSettings": "Edit pengaturan subtitel kedua", + "editSecondarySubtitleSettingsDotDotDot": "Edit pengaturan subtitel kedua...", + "secondarySubtitleRememberSettings": "Ingat pengaturan ini", + "secondarySubtitleDoNotShowAgain": "Jangan tampilkan dialog ini lagi", + "secondarySubtitleJustifyLines": "Ratakan baris", "openRecentVideo": "Buka video terbaru", "clearRecentVideos": "Bersihkan video terbaru", "cutVideoTitle": "Potong video", "cutVideoDotDotDot": "Potong video...", + "remuxVideoTitle": "Remux video", + "remuxVideoDotDotDot": "Remux video...", + "remuxVideoInputVideo": "Video masukan:", + "remuxVideoMultipleTracksRequiresMkv": "Beberapa trek audio atau subtitel memerlukan kontainer MKV. Format keluaran telah otomatis diubah ke .mkv.", + "remuxVideoOutputFormat": "Format keluaran:", + "remuxVideoOutputFile": "File keluaran:", + "remuxVideoRemuxing": "Me-remux video dan audio...", + "remuxVideoFinalizing": "Menyelesaikan file keluaran (memindahkan indeks ke awal)...", + "remuxVideoCompleted": "Video berhasil di-remux.", + "remuxVideoFailed": "Gagal me-remux video.", + "remuxVideoPleaseSelectBoth": "Silakan pilih file video dan audio.", + "remuxVideoAssRequiresMkv": "Subtitel ASS/SSA memerlukan kontainer MKV untuk mempertahankan semua pemformatan dan gaya.", + "remuxVideoSccRequiresMov": "Subtitel Scenarist (.scc) disematkan sebagai closed caption CEA-608, yang memerlukan kontainer MOV atau MPG. Format keluaran telah otomatis diubah ke .mov.", + "remuxVideoSccNotWithWebM": "Closed caption Scenarist (.scc) memerlukan kontainer MOV, yang tidak dapat menampung video VP9/AV1 dari file .webm. Hapus file .scc atau konversikan ke format subtitel lain.", + "remuxVideoMpgMaxTwoCaptionTracks": "File .mpg menampung paling banyak dua trek closed caption (CC1 dan CC3) - hapus file subtitel sehingga tersisa satu atau dua.", + "remuxVideoMccRequiresMpg": "Subtitel MacCaption (.mcc) disematkan sebagai closed caption CEA-608/708 dalam video MPEG-2, yang memerlukan kontainer MPG. Format keluaran telah otomatis diubah ke .mpg.", + "remuxVideoMpgReencodeVideoX": "Closed caption dalam file .mpg memerlukan video MPEG-2, sedangkan videonya {0}. Enkode ulang video ke MPEG-2? Ini memakan waktu lebih lama dan sedikit menurunkan kualitas.", + "remuxVideoFileHasNoAudioX": "\"{0}\" tidak memiliki trek audio, sehingga tidak dapat digunakan sebagai sumber audio.", + "remuxVideoAddingClosedCaptions": "Menambahkan closed caption...", + "remuxVideoSelectAudioTrack": "Pilih trek audio", + "remuxVideoSelectAudioTrackFor": "Pilih trek audio untuk {0}", + "remuxVideoSelectAudioTrackPrompt": "File '{0}' berisi {1} trek audio. Silakan pilih trek audio untuk di-remux:", + "remuxVideoVideoFile": "video", + "remuxVideoAudioFile": "audio", + "remuxVideoAudioFiles": "File audio:", + "remuxVideoSubtitleFiles": "File subtitel (opsional, soft subtitle):", + "remuxVideoSelectAudioTrackDotDotDot": "Pilih trek audio...", + "remuxVideoFilesX": "{0} file", + "remuxVideoMixAudio": "Campur audio ke dalam satu trek", + "remuxVideoVolumePercent": "Volume %:", + "remuxVideoVolumeX": "Volume {0}%", + "remuxVideoFastStart": "Optimalkan untuk streaming web (pindahkan indeks ke depan - lebih lambat)", "embedSubtitlesDotDotDot": "Tambah/hapus subtitel tersemat...", "generateTimeCodes": "Buat kode waktu", "checkeredImage": "Gambar kotak-kotak", - "saveVideoAsTitle": "Simpan video sebagai", "promptForFfmpegParamsAndGenerate": "Tanyakan parameter ffmpeg lalu buat", "cutVideoCutSegments": "Potong segmen", "cutVideoMergeSegments": "Gabungkan segmen", "cutVideoSplitSegments": "Simpan segmen satu per satu", - "cutVideoType": "Jenis potong", - "mpvRenderAuto": "Otomatis", - "mpvRenderNative": "Asli", - "mpvRenderOpenGl": "OpenGL", - "mpvRenderSoftware": "Perangkat lunak (lambat)", "cutVideoAlsoCutSubtitle": "Potong juga subtitel", "cutVideoSubtitleFileGeneratedX": "File subtitel dibuat: {0}", + "cutVideoTransition": "Transisi antar segmen", + "cutVideoTransitionDurationSeconds": "Durasi (detik)", + "cutVideoFadeInSeconds": "Pudar dari hitam di awal (detik)", + "cutVideoFadeOutSeconds": "Pudar ke hitam di akhir (detik)", + "cutVideoPreviewTransition": "Pratinjau transisi", + "cutVideoPreviewTransitionTitle": "Pratinjau transisi", + "cutVideoPreviewNeedsJoin": "Tidak ada sambungan untuk dipratinjau - tambahkan setidaknya dua segmen untuk digabung, atau satu segmen untuk dipotong dari bagian tengah.", + "cutVideoTransitionInfoX": "Setiap transisi menumpuk bagian yang disambungkannya, sehingga keluaran menjadi {0} detik lebih pendek per sambungan. Subtitel yang dipotong diatur ulang waktunya agar sesuai.", + "cutVideoTransitionFade": "Pudar", + "cutVideoTransitionFadeBlack": "Pudar melalui hitam", + "cutVideoTransitionFadeWhite": "Pudar melalui putih", + "cutVideoTransitionFadeGrays": "Pudar melalui abu-abu", + "cutVideoTransitionDissolve": "Larut", + "cutVideoTransitionWipeLeft": "Sapu ke kiri", + "cutVideoTransitionWipeRight": "Sapu ke kanan", + "cutVideoTransitionWipeUp": "Sapu ke atas", + "cutVideoTransitionWipeDown": "Sapu ke bawah", + "cutVideoTransitionSlideLeft": "Geser ke kiri", + "cutVideoTransitionSlideRight": "Geser ke kanan", + "cutVideoTransitionSlideUp": "Geser ke atas", + "cutVideoTransitionSlideDown": "Geser ke bawah", + "cutVideoTransitionSmoothLeft": "Halus ke kiri", + "cutVideoTransitionSmoothRight": "Halus ke kanan", + "cutVideoTransitionCircleOpen": "Lingkaran membuka", + "cutVideoTransitionCircleClose": "Lingkaran menutup", + "cutVideoTransitionRadial": "Radial", + "cutVideoTransitionPixelize": "Pikselisasi", + "cutVideoTransitionBlur": "Buram", "importCurrentSubtitle": "Impor subtitel saat ini", "addRemoveEmbeddedSubtitlesTitle": "Tambah/hapus subtitel tersemat", "addRemoveEmbeddedSubtitlesMp4Title": "Tambah/hapus subtitel tersemat (MP4)", "addCurrentSubtitle": "Tambahkan subtitel saat ini", - "embeddedTrackColumnNew": "Baru", "embeddedTrackNoSubtitlesFoundTitle": "Tidak ada subtitel ditemukan", "embeddedTrackNoSubtitlesFoundMessage": "File subtitel terpilih tidak berisi subtitel apa pun.", "embeddedTrackNoTracksTitle": "Tidak ada trek ditambahkan", @@ -2789,7 +3142,7 @@ "embeddedTrackGeneratingVideoXY": "Membuat video... {0}% {1}", "embeddedTrackGeneratingVideo": "Membuat video...", "mp4FilesFilter": "File MP4", - "titleOrLanguage": "Judul/bahasa", + "embeddedTracksPickVideoHint": "Pilih file video, atau letakkan di sini, untuk melihat trek subtitelnya.", "viewMatroskaTrackX": "Lihat trek Matroska - {0}", "resolutionSeparator": "x", "openFromUrlTitle": "Buka file video dari URL", @@ -2799,9 +3152,7 @@ "openFromUrlDownloadAndOpen": "Unduh dan buka", "openFromUrlDownloadAndOpenDescription": "Simpan video secara lokal, lalu buka.", "openFromUrlDownloadAndOpenNote": "Diperlukan untuk ucapan-ke-teks dan puncak bentuk gelombang.", - "openFromUrlMissing": "Silakan masukkan URL video terlebih dahulu.", "openFromUrlDownloadingTitle": "Mengunduh video", - "openFromUrlSaveAs": "Simpan video sebagai", "openFromUrlDownloadSubtitles": "Unduh juga subtitel", "openFromUrlDownloadVideo": "Unduh video", "openFromUrlIncludeAutoGeneratedSubtitles": "Sertakan subtitel yang dibuat otomatis (bahasa asli)", @@ -2838,12 +3189,15 @@ "reEncodeGeneratingDone": "Pembuatan selesai", "reEncodeGeneratedFilesX": "File yang dibuat ({0}):", "reEncodeFfmpegParameters": "Parameter ffmpeg", + "analyzingVideoX": "Menganalisis video... {0}% {1}", + "analyzingVideoXofY": "Menganalisis video {0}/{1}... {2}% {3}", + "backgroundImageFileNotSelected": "File gambar latar belakang belum dipilih", + "backgroundImageFileDoesNotExistX": "File gambar latar belakang tidak ada: {0}", "voices": "Suara", "presets": "Prasetel", "talkerX": "Pembicara {0}", "baseModel": "Model dasar", "turboModel": "Model turbo", - "ttsUpdateX": "Perbarui {0}", "ttsEngineNotInstalled": "{0} tidak terpasang", "ttsEngineUpdateAvailable": "{0} - pembaruan tersedia", "ttsEngineTooOldUpdateRequired": "{0} terlalu lama - perlu pembaruan", @@ -2866,19 +3220,25 @@ "cloneVoiceTo": "Kloning suara ke", "cloneVoiceNameTitle": "Nama suara baru", "cloneVoiceExtractFailed": "Tidak dapat mengekstrak audio untuk baris terpilih.", + "lineXHasNoDuration": "Baris {0} tidak memiliki durasi (waktu akhir tidak setelah waktu mulai), sehingga tidak ada audio untuk diekstrak.", + "audioClipsSkippedLinesX": "Tidak ada audio yang dapat diekstrak untuk baris berikut, sehingga dilewati: {0}", "toggleShotChange": "Alihkan perubahan shot", "resetWaveformZoomAndSpeed": "Atur ulang zoom & kecepatan bentuk gelombang", "showOriginalSubtitle": "Tampilkan subtitel asli", + "timelineGroupTracksBy": "Kelompokkan trek subtitel berdasarkan", + "timelineShowThumbnails": "Tampilkan gambar mini video", + "showSpeechOnly": "Tampilkan ucapan saja (lambat dibuat)", + "isolatingSpeechForWaveformX": "Mengisolasi ucapan untuk bentuk gelombang... {0}", + "isolatingSpeechForWaveformFailed": "Tidak dapat mengisolasi ucapan untuk bentuk gelombang - lihat log alat", + "speechOnlyWaveformReady": "Bentuk gelombang sekarang hanya menampilkan ucapan", "showOnlyWaveform": "Tampilkan hanya bentuk gelombang", "showOnlySpectrogram": "Tampilkan hanya spektogram", "showWaveformAndSpectrogram": "Tampilkan bentuk gelombang dan spektogram", - "spectrogramClassic": "Klasik", "spectrogramClassicViridis": "Viridis", "spectrogramClassicPlasma": "Plasma", "spectrogramClassicInferno": "Inferno", "spectrogramClassicTurbo": "Turbo", "spectrogramNeon": "Neon", - "waveformDrawStyleClassic": "Klasik", "waveformDrawStyleFancy": "Mewah", "setVideoPositionAndPauseAndSelectSubtitle": "Atur posisi video, jeda, dan pilih subtitel", "setVideopositionAndPauseAndSelectSubtitleAndCenter": "Atur posisi video, jeda, pilih subtitel, dan pusatkan", @@ -2888,7 +3248,6 @@ }, "sync": { "visualSync": "Sinkronisasi Visual", - "sync": "Sinkron", "startScene": "Adegan awal", "endScene": "Adegan akhir", "playTwoSecondsAndBack": "Putar 2 dtk & kembali", @@ -2904,19 +3263,18 @@ "adjustSelectedLinesAndForward": "Sesuaikan baris terpilih dan seterusnya", "toFrameRate": "Ke laju bingkai", "fromFrameRate": "Dari laju bingkai", + "videoXFrameRateY": "{0} - {1} fps", "adjustAllTimes": "Sesuaikan semua waktu (tampilkan lebih awal/akhir)", "showEarlier": "Tampilkan lebih awal", "showLater": "Tampilkan lebih akhir", - "changeFrameRate": "Ubah laju bingkai", "setSyncPoint": "Atur titik sinkron", "setSyncPointViaVideo": "Atur titik sinkronisasi via video...", "syncPointTimeCode": "Kode waktu titik sinkronisasi", "syncPoints": "Titik sinkron", "pointSync": "Sinkronisasi titik", "pointSyncViaOther": "Sinkronisasi titik via subtitel lain", - "adjustmentX": "Penyesuaian: {0}", - "totalAdjustmentX": "Total penyesuaian: {0}", - "syncPointCandidateInfo": "Baris yang dimulai setelah keheningan {0}+ detik disorot - baris seperti itu sering menjadi titik sinkronisasi yang andal", + "syncPointCandidateAfterInfo": "Baris yang diikuti oleh {0}+ detik keheningan disorot - baris tersebut sering menjadi titik sinkron yang andal", + "gapAfter": "Jeda sesudah", "adjustAllShortcuts": "Pintasan keyboard:\r\n\r\n• Shift + Kiri/Kanan: Geser 10 ms\r\n• Ctrl + Kiri/Kanan: Geser 100 ms\r\n• Alt + Kiri/Kanan: Geser 500 ms", "adjustAllShortcutsFrames": "Pintasan keyboard:\r\n\r\n• Shift + Kiri/Kanan: Geser 1 bingkai\r\n• Ctrl + Kiri/Kanan: Geser 10 bingkai\r\n• Alt + Kiri/Kanan: Geser 1 detik", "offsetInSeconds": "Offset dalam detik", @@ -2936,6 +3294,7 @@ "lineMerge": "Penggabungan baris", "delayInSecondsBetweenRequests": "Jeda dalam detik antar permintaan", "maxBytesPerRequest": "Byte maks per permintaan", + "keepMusicLinesUntranslated": "Jangan terjemahkan baris dalam simbol musik (♪ ... ♪)", "promptText": "Teks prompt", "resetPromptToDefault": "Atur ulang prompt ke baku", "translateEachLineSeparately": "Terjemahkan setiap baris secara terpisah", @@ -2949,11 +3308,10 @@ "readyToTranslate": "Siap menerjemahkan", "translating": "Menerjemahkan...", "translationComplete": "Terjemahan selesai", + "translateInPlaceNoOriginal": "Terjemahkan langsung (jangan buat asli)", "translationCancelled": "Terjemahan dibatalkan", "swapLanguages": "Tukar bahasa sumber dan tujuan", "xIsAlreadyDownloadedReDownload": "{0} sudah diunduh. Unduh ulang?", - "advancedDotDotDot": "Lanjutan...", - "advancedSettings": "Pengaturan lanjutan", "advancedSettingsSubtitle": "Opsi batch, konteks, dan sampling untuk mesin llama.cpp lanjutan", "context": "Konteks", "synopsis": "Sinopsis", @@ -2977,6 +3335,7 @@ "useOnlyExtraServerParametersHint": "Mulai llama-server dengan parameter di atas alih-alih penyetelan bawaan Subtitle Edit - hanya model, host, dan port yang tetap diatur", "serverRunningAtX": "Server berjalan di {0}", "customPromptHint": "Instruksi kustom ({0} = bahasa sumber, {1} = bahasa tujuan); kosong = prompt bawaan", + "editTranslationHint": "Klik terjemahan baris terpilih untuk mengeditnya (Enter menyimpan, Escape membatalkan)", "llamaCppDownloadEngineAndModelPrompt": "llama.cpp memerlukan mesin llama-server dan model terjemahan untuk diunduh. Unduh sekarang?", "llamaCppDownloadEnginePrompt": "llama.cpp memerlukan mesin llama-server untuk diunduh. Unduh sekarang?", "llamaCppDownloadModelPrompt": "llama.cpp memerlukan model terjemahan terpilih untuk diunduh. Unduh sekarang?" @@ -2989,7 +3348,6 @@ "dialogStyleDashBothLinesWithSpace": "Tanda hubung kedua baris dengan spasi", "dialogStyleDashBothLinesWithoutSpace": "Tanda hubung kedua baris tanpa spasi", "continuationStyle": "Gaya lanjutan", - "continuationStyleNone": "Tidak ada", "continuationStyleNoneTrailingDots": "Tidak ada, titik untuk jeda (hanya di akhir)", "continuationStyleNoneLeadingTrailingDots": "Tidak ada, titik untuk jeda", "continuationStyleNoneTrailingEllipsis": "Tidak ada, elipsis untuk jeda (hanya di akhir)", @@ -3000,7 +3358,6 @@ "continuationStyleLeadingTrailingEllipsis": "Elipsis", "continuationStyleLeadingTrailingDash": "Tanda hubung", "continuationStyleLeadingTrailingDashDots": "Tanda hubung, tetapi titik untuk jeda", - "continuationStyleCustom": "Kustom", "cpsLineLengthStyle": "Cps/panjang baris", "cpsLineLengthStyleCalcAll": "Hitung semua karakter", "cpsLineLengthStyleCalcNoSpaceCpsOnly": "Hitung semua kecuali spasi, hanya cps", @@ -3013,14 +3370,6 @@ "cpsLineLengthStyleCalcNoSpaceOrPunctuationCpsOnly": "Tanpa spasi atau tanda baca, hanya CPS", "cpsLineLengthStyleCalcIgnoreArabicDiacritics": "Abaikan diakritik Arab", "cpsLineLengthStyleCalcIgnoreArabicDiacriticsNoSpace": "Abaikan diakritik Arab, tanpa spasi", - "timeCodeModeHhMmSsMs": "HH:MM:SS:MS", - "timeCodeModeHhMmSsFf": "HH:MM:SS:FF", - "splitBehaviorPrevious": "Tambahkan jeda di kiri titik pisah (fokus kanan)", - "splitBehaviorHalf": "Tambahkan jeda di tengah titik pisah (fokus kiri)", - "splitBehaviorNext": "Tambahkan jeda di kanan titik pisah (fokus kiri)", - "subtitleListActionNothing": "Tidak ada", - "subtitleListActionVideoGoToPositionAndPause": "Pergi ke posisi video dan jeda", - "subtitleListActionVideoGoToPositionAndPlay": "Pergi ke posisi video dan putar", "subtitleGridCenterText": "Tengahkan teks di kisi subtitel", "minGapCalculateDotDotDot": "Hitung jeda minimum dari laju bingkai...", "minGapCalculateTitle": "Jeda min. antar subtitel dalam md", @@ -3052,8 +3401,14 @@ "checkForUpdatesChannelStableAndBeta": "Versi stabil dan beta", "assaAutoSetResolution": "ASSA: atur resolusi (PlayResX/PlayResY) dari video saat video dibuka", "assaAutoSetResolutionPrompt": "ASSA: tanyakan sebelum mengubah resolusi agar cocok dengan video (jika tidak, ubah skala otomatis)", + "videoControls": "Kontrol video", + "videoControlsEdit": "Edit kontrol video...", + "videoControlsPositionText": "Teks posisi / durasi", + "videoControlsVideoFileName": "Nama file video", + "videoControlsPlayerName": "Nama pemutar video", "mergeKeepEndTime": "Gabungkan baris: pertahankan waktu selesai (izinkan tumpang tindih dengan subtitel berikutnya)", "mergeKeepEndTimeOnlyAssa": "Gabungkan baris: pertahankan waktu selesai hanya untuk \"Advanced Sub Station Alpha\"", + "mergeAutoBreak": "Gabungkan baris: pisah otomatis teks gabungan (nonaktif mempertahankan pemisah baris asli)", "defaultSaveLocation": "Lokasi simpan baku", "defaultSaveLocationSourceFileFolder": "Folder file subtitel/video", "defaultSaveLocationLastUsedFolder": "Folder yang terakhir digunakan", @@ -3061,18 +3416,10 @@ "defaultSaveLocationSubtitleFileFolder": "Folder file subtitel", "defaultSaveLocationCustomFolder": "Folder kustom", "subtitleListActionVideoGoToPositionAndPlayCurrentAndPause": "Pergi ke posisi video, putar saat ini, dan jeda", - "subtitleListActionEditText": "Pergi ke kotak edit teks", "subtitleListActionVideoGoToPositionMinus1SecAndPause": "Pergi ke posisi video - 1 dtk dan jeda", "subtitleListActionVideoGoToPositionMinusHalfSecAndPause": "Pergi ke posisi video - 0,5 dtk dan jeda", "subtitleListActionVideoGoToPositionMinus1SecAndPlay": "Pergi ke posisi video - 1 dtk dan putar", - "subtitleListActionEditTextAndPause": "Pergi ke kotak edit teks, dan jeda pada posisi video", - "autoBackupEveryMinute": "Setiap menit", - "autoBackupEveryXthMinute": "Setiap {0} menit", - "profiles": "Profil", - "autoBackupDeleteAfterXMonths": "Hapus cadangan otomatis setelah {0} bulan", "searchSettingsDotDotDot": "Cari pengaturan...", - "syntaxColoring": "Pewarnaan sintaks", - "waveformSpectrogram": "Bentuk gelombang/spektogram", "network": "Jaringan", "fileTypeAssociations": "Asosiasi jenis file", "textBoxColorTags": "Warnai tag (HTML/ASSA) di kotak teks subtitel", @@ -3105,10 +3452,9 @@ "darkThemeForegroundColor": "Warna latar depan tema gelap", "darkThemeBackgroundColor": "Warna latar belakang tema gelap", "showGridLines": "Tampilkan garis kisi", - "resetSettings": "Atur ulang pengaturan?", - "resetSettingsDetail": "Ini akan mengatur ulang semua pengaturan ke nilai default.\n\nLanjutkan?", "showHorizontalLineAboveToolbar": "Tampilkan garis horizontal di atas bilah alat", "bookmarkColor": "Warna markah", + "spellCheckHighlightColor": "Warna sorotan periksa ejaan", "singleLineMaxLength": "Panjang maks satu baris", "optimalCharsPerSec": "Karakter/dtk optimal", "maxCharsPerSec": "Maks karakter/dtk", @@ -3121,13 +3467,21 @@ "unbreakSubtitlesShortThan": "Satukan subtitel lebih pendek dari", "newEmptyDefaultMs": "Durasi default subtitel baru (ms)", "timeCodeUpDownStepMs": "Kenaikan waktu naik/turun (ms)", + "moveSelectedLinesStepMs": "Langkah pintasan geser baris terpilih (ms)", + "moveStartEndStepMs": "Langkah pintasan geser awal/akhir (ms)", + "moveLinesShortenNeighbor": "Geser baris: perpendek baris sebelumnya/berikutnya alih-alih menumpanginya", "promptBeforeDelete": "Konfirmasi sebelum menghapus", "rememberPositionAndSize": "Ingat posisi dan ukuran jendela", + "titleBarFullFileName": "Tampilkan jalur file lengkap di bilah judul", + "showRecentFiles": "Tampilkan file terbaru (untuk dibuka ulang)", "openLastFileOnStart": "Buka file terbaru terakhir saat mulai", "autoSave": "Simpan otomatis (simpan file terbuka saat mengedit)", "autoBackupOn": "Cadangan otomatis", "autoBackupIntervalMinutes": "Interval cadangan otomatis (menit)", "autoBackupDeleteAfterDays": "Retensi cadangan otomatis (hari)", + "settingsBackupOn": "Cadangkan pengaturan otomatis", + "settingsBackupIntervalDays": "Interval pencadangan pengaturan (hari, 0 = setiap mulai)", + "settingsBackupMaxCount": "Jumlah cadangan pengaturan yang disimpan", "autoConvertToUtf8": "Konversi otomatis pengodean ke UTF-8 saat membuka", "autoTrimWhiteSpace": "Pangkas otomatis spasi kosong", "defaultEncoding": "Pengodean default", @@ -3143,23 +3497,14 @@ "colorGapTooShort": "Warnai jika jeda terlalu pendek", "errorBackgroundColor": "Warna latar belakang kesalahan", "waveformDrawGridLines": "Gambar garis kisi", - "waveformCenterVideoPosition": "Pusatkan posisi video", + "waveformUseSkiaRenderer": "Gunakan perender cepat eksperimental", "waveformCenterVideoPositionAlsoWhenPaused": "Pusatkan posisi video juga saat dijeda", + "waveformSelectCurrentSubtitleWhilePaused": "Pilih subtitel saat ini juga saat dijeda", + "waveformRememberZoom": "Ingat zoom (horizontal dan vertikal)", "waveformShowToolbar": "Tampilkan bilah alat", "waveformShowToolbarEdit": "Edit bilah alat...", "waveformShowToolbarEditLabel": "Item bilah alat", "waveformSpectrogramCombinedWaveformHeight": "Bentuk gelombang/spektogram gabungan, tinggi bentuk gelombang %", - "showWaveformToolbarPlay": "Bilah alat: tampilkan tombol putar", - "showWaveformToolbarRepeat": "Bilah alat: tampilkan tombol ulang", - "showWaveformToolbarRemoveBlankLines": "Bilah alat: tampilkan tombol hapus baris kosong", - "showWaveformToolbarNew": "Bilah alat: tampilkan tombol subtitel baru", - "showWaveformToolbarSetStart": "Bilah alat: tampilkan tombol atur awal", - "showWaveformToolbarSetEnd": "Bilah alat: tampilkan tombol atur akhir", - "showWaveformToolbarStartAndOffsetTheRest": "Bilah alat: tampilkan tombol atur awal dan geser sisanya", - "showWaveformToolbarHorizontalZoom": "Bilah alat: tampilkan penggeser zoom horizontal", - "showWaveformToolbarVerticalZoom": "Bilah alat: tampilkan penggeser zoom vertikal", - "showWaveformToolbarVideoPositionSlider": "Bilah alat: tampilkan penggeser posisi video", - "showWaveformToolbarPlaybackSpeed": "Bilah alat: tampilkan kecepatan pemutaran", "waveformFocusTextboxAfterInsertNew": "Fokuskan kotak teks setelah menyisipkan", "waveformInvertMouseWheel": "Balikkan roda tetikus", "waveformMouseWheelSetsVideoPosition": "Roda tetikus mengatur posisi video", @@ -3180,20 +3525,20 @@ "fixCommonErrorsSkipStep1": "Perbaiki kesalahan umum: lewati langkah 1 (pilih perbaikan)", "showPluginsMenu": "Tampilkan menu Plugin", "proxyAddress": "Alamat proksi", - "username": "Nama pengguna", "password": "Kata sandi", "defaultFormat": "Format default", "defaultSaveAsFormat": "Format default \"Simpan sebagai\"", "favoriteSubtitleFormats": "Format subtitel favorit", "favoriteLanguages": "Bahasa favorit", "webVttUseXTimestampMap": "WebVTT: gunakan X-TIMESTAMP-MAP (offset kode waktu saat memuat)", - "showStopButton": "Tampilkan tombol berhenti", - "showFullscreenButton": "Tampilkan tombol layar penuh", "fullscreenHideControls": "Sembunyikan kontrol video di layar penuh", + "showSecondarySubtitleDialog": "Tampilkan dialog gaya saat membuka file subtitel kedua", + "rememberSecondarySubtitleFile": "Ingat file subtitel kedua", "autoOpenVideoFile": "Buka file video otomatis saat membuka subtitel", "downloadMpv": "Unduh mpv", "downloadVlc": "Unduh VLC", "allowSingleLetterShortcutsInTextbox": "Izinkan pintasan satu huruf di kotak teks", + "allowTextNavigationShortcutsInTextbox": "Izinkan pintasan pada tombol navigasi teks (Ctrl+Kiri/Kanan, Home/End) di kotak teks", "spellCheckEnglishTreatInApostropheAsIng": "Periksa ejaan: Perlakukan kata berakhiran 'in'' sebagai 'ing' (hanya Inggris)", "goToLineNumberSetsVideoPosition": "Pergi-ke-nomor-baris juga mengatur posisi video", "adjustAllTimesRememberLineSelectionChoice": "Sesuaikan semua waktu, ingat pilihan seleksi baris", @@ -3205,6 +3550,7 @@ "showAssaLayer": "Tampilkan kotak lapisan ASSA", "waveformCursorColor": "Warna kursor/kepala bentuk gelombang", "waveformShotChangeColor": "Warna perubahan shot bentuk gelombang", + "waveformGridColor": "Warna kisi bentuk gelombang", "waveformParagraphLeftColor": "Warna batas kiri bentuk gelombang", "waveformParagraphRightColor": "Warna batas kanan bentuk gelombang", "waveformFancyHighColor": "Warna tinggi mewah bentuk gelombang", @@ -3232,27 +3578,31 @@ "resetSyntaxColoring": "Atur ulang pewarnaan sintaks", "resetWaveform": "Atur ulang bentuk gelombang", "resetRules": "Atur ulang aturan", - "useFrameMode": "Gunakan mode bingkai (hh.mm.ss.ff)", + "timeCodeMode": "Mode kode waktu", + "timeCodeModeTime": "Waktu (hh:mm:ss,zzz)", + "timeCodeModeFrames": "Bingkai (hh:mm:ss:ff)", + "timeCodeModeFrameNumbers": "Nomor bingkai", + "frameModeSnapToVideoFrames": "Simpan waktu pada bingkai video (bukan bingkai dalam setiap detik)", "textBoxLimitNewLines": "Batasi jumlah baris di kotak teks subtitel", - "mpvVideoOutput": "Keluaran video (mpv)", "mpvOpenGl": "libmpv - OpenGL", "mpvSoftwareRendering": "libmpv - Rendering perangkat lunak (lambat)", + "mpvMetalRendering": "libmpv - Metal (eksperimental, perlu mulai ulang)", "mpvWidRendering": "libmpv - Rendering ID Jendela asli", "waveFormsAndSpectrogramFoldersContainsX": "Folder \"Waveforms\" dan \"spectrogram\" berisi {0}", "deleteWaveformAndSpectrogramFoldersQuestion": "Hapus file \"Waveforms\" dan \"Spectrogram\"?", "waveformGenerateSpectrogram": "Buat spektogram", "waveformAutoGenerate": "Buat bentuk gelombang otomatis saat membuka video", "waveformSpectrogramMode": "Mode spektogram", - "waveformCenterOnSingleClick": "Pusatkan saat klik tunggal", - "waveformSingleClickSelectsSubtitle": "Pilih subtitel saat klik tunggal", "waveformRightClickSelectsSubtitle": "Pilih subtitel saat klik kanan", - "waveformPauseOnSingleClick": "Jeda saat klik tunggal", "waveformDrawStyle": "Gaya gambar bentuk gelombang", "waveformExtractAudioFormat": "Format ekstrak audio", "waveformExtractAudioSampleRate": "Laju sampel ekstrak audio", "waveformExtractAudioSampleRateOriginal": "Asli (pertahankan sumber)", "waveformExtractAudioBitRate": "Laju bit ekstrak audio (MP3/M4A)", + "ffmpegUseCenterChannelOnly": "Gunakan hanya kanal tengah depan untuk audio 5.1 (FFmpeg)", "vlcWidRendering": "libVLC - Rendering ID Jendela asli", + "ffmpegSoftwareRendering": "FFmpeg", + "downloadFfmpegLibs": "Unduh pustaka FFmpeg (untuk pemutar video FFmpeg)", "subtitleGridEnterKeyAction": "Aksi tombol Enter di kisi subtitel", "subtitleSingleClickAction": "Aksi klik tunggal di kisi subtitel", "subtitleDoubleClickAction": "Aksi klik ganda di kisi subtitel", @@ -3270,6 +3620,7 @@ "subtitleGridFormattingShowFormatting": "Tampilkan pemformatan", "subtitleGridFormattingShowTags": "Tampilkan tag", "subtitleGridFormattingHideTags": "Sembunyikan tag", + "subtitleGridFormattingShowFormattingKeepTags": "Tampilkan pemformatan, pertahankan tag non-visual", "waveformParagraphBackgroundColor": "Warna latar belakang subtitel bentuk gelombang", "waveformParagraphSelectedBackgroundColor": "Warna latar belakang subtitel terpilih bentuk gelombang", "waveformAllowOverlap": "Izinkan tumpang tindih (saat memindah/mengubah ukuran)", @@ -3279,17 +3630,18 @@ "saveAsBehaviorUseVideoFileName": "Gunakan nama file video", "saveAsBehaviorUseSubtitleFileName": "Gunakan nama file subtitel", "saveAsAppendLanguageCodeTwoLetter": "Dua huruf", - "saveAsAppendLanguageCodeThreeLetter": "Tiga huruf (ISO 639-2/T)", - "saveAsAppendLanguageCodeThreeLetterBibliographic": "Tiga huruf (ISO 639-2/B)", "saveAsAppendLanguageCodeLanguageName": "Nama bahasa", "splitOddLineActionWeightTop": "Beratkan atas", "splitOddLineActionWeightBottom": "Beratkan bawah", "splitOddLinesAction": "Aksi pisah baris ganjil", "ocrUseWordSplitList": "OCR: gunakan daftar pemisahan kata", "ocrGuessUnknownWords": "OCR: coba tebak kata yang tidak dikenal", + "spellCheckRememberUseAlwaysList": "Periksa ejaan: ingat daftar \"Selalu gunakan\"", + "fixShortDisplayTimesAllowMoveStartTime": "Perbaiki kesalahan umum: perbaikan waktu tampil pendek boleh menggeser waktu mulai", "speechToTextSelectedLinesPromptFirstTimeOnly": "Ucapan ke teks: baris terpilih, tanyakan bahasa/mesin hanya pertama kali", "multipleReplaceShowDotDotDotButtons": "Penggantian ganda: tampilkan tombol menu konteks", "gridFocusTextboxAfterInsertNew": "Kisi: fokuskan kotak teks setelah menyisipkan subtitel baru", + "undoRedoGoToChangedLine": "Urungkan/ulangi: pergi ke baris yang diubah (dan posisi video)", "musicSymbol": "Simbol musik", "musicSymbolsToReplace": "Simbol musik untuk diganti (dipisahkan koma)", "textToSpeechPromptMergeContinuationLines": "Teks ke ucapan: tanyakan untuk menggabungkan baris lanjutan", @@ -3298,8 +3650,7 @@ "useFocusedButtonBackgroundColor": "Gunakan warna latar belakang tombol terfokus", "focusedButtonBackgroundColor": "Warna latar belakang tombol terfokus", "forceCrLfOnSave": "Paksa CR+LF saat menyimpan (file subtitel teks)", - "showWaveformToolbarPlayNext": "Bilah alat: tampilkan tombol putar berikutnya", - "showWaveformToolbarPlaySelection": "Bilah alat: tampilkan tombol putar seleksi", + "linuxClipboardUseExternalTool": "Salin teks via xclip/wl-copy (mempertahankan karakter beraksen)", "showFormatLimitWarning": "Peringatkan saat menyimpan bila baris melebihi batas format (mis. SCC 32 karakter/baris)", "textBoxButtonShowAutoBreak": "Kotak teks: tampilkan tombol pisah otomatis", "textBoxButtonShowUnbreak": "Kotak teks: tampilkan tombol satukan", @@ -3311,12 +3662,18 @@ "waveformDoubleClickAction": "Aksi klik ganda bentuk gelombang (setelah aksi klik tunggal)", "allSettings": "Semua pengaturan", "uiScale": "Skala UI (%)", + "fontScale": "Skala fon (%)", + "textSelectionOpacity": "Opasitas seleksi teks (%)", "waveformToolbarItems": "Item bilah alat bentuk gelombang", "matchIconColorToDarkTheme": "Samakan warna ikon dengan warna latar depan tema gelap", "subtitlePreviewProperties": "Properti pratinjau subtitel", "usePositionFromSubtitleFile": "Gunakan posisi dari file subtitel (TTML/PAC/EBU STL)", "marginIsPartOfSubtitleArea": "Margin adalah bagian dari area subtitel", "textJustify": "Ratakan baris", + "deinterlace": "Deinterlace video", + "deinterlaceOff": "Nonaktif", + "deinterlaceAuto": "Otomatis (hanya bingkai interlaced)", + "deinterlaceAlways": "Selalu", "pixelWidthInfo": "Garis hijau = batas lebar maks | Area merah = teks melebihi batas", "spellCheckEngineHunSpelll": "Hunspell", "spellCheckEngineMsWord": "MS Word", @@ -3325,117 +3682,89 @@ "waveformColorTheme": "Tema warna:", "waveformSaveAsCustomTheme": "Simpan sebagai tema kustom...", "waveformLoadThemeDotDotDot": "Muat tema...", - "waveformExportThemeDotDotDot": "Ekspor tema..." + "waveformExportThemeDotDotDot": "Ekspor tema...", + "waveformSaveCustomThemeTitle": "Simpan tema kustom", + "waveformThemeFileType": "Tema bentuk gelombang Subtitle Edit", + "waveformThemeHighContrast": "Kontras Tinggi", + "waveformThemeOceanBlue": "Biru Samudra", + "waveformThemeWarmSunset": "Senja Hangat", + "waveformThemeForest": "Hutan", + "waveformThemeMidnightPurple": "Ungu Tengah Malam", + "waveformThemeRetroAmber": "Amber Retro", + "waveformThemeIce": "Es" }, "shortcuts": { - "title": "Pintasan", "searchShortcuts": "Cari pintasan...", - "filter": "Filter", - "categoryGeneral": "Umum", "categorySubtitleGridAndTextBox": "Tampilan daftar subtitel & kotak teks", "categorySubtitleGrid": "Tampilan daftar subtitel", - "categoryWaveform": "Bentuk gelombang", "categoryTextBox": "Kotak teks", "categoryFile": "File", "categoryVideo": "Video", "categorySync": "Sinkronisasi", - "categoryTranslate": "Terjemahkan", - "categorySearch": "Pencarian", - "categoryTools": "Alat", "categoryAi": "AI", + "categoryCustom": "Kustom", + "editCustomShortcut": "Edit pintasan kustom", + "customShortcutTextBoxKeyHint": "Gunakan tombol dengan Ctrl/Alt di sini - tombol biasa tidak akan lagi mengetik di kotak teks.", + "customShortcutNumberX": "Pintasan kustom #{0}", + "customShortcutNumberXY": "Pintasan kustom #{0}: {1}", + "customShortcutStep": "Langkah", + "customShortcutSteps": "Langkah-langkah", + "customShortcutStepRunCommand": "Jalankan perintah", + "customShortcutStepInsertText": "Sisipkan teks", + "customShortcutStepReplace": "Cari dan ganti", + "customShortcutInsertAtCursor": "Di kursor", + "customShortcutInsertAtStart": "Di awal teks", + "customShortcutInsertAtEnd": "Di akhir teks", + "customShortcutSummaryRunCommandX": "Jalankan: {0}", + "customShortcutSummaryInsertXY": "Sisipkan \"{0}\" ({1})", + "customShortcutSummaryReplaceXY": "Ganti \"{0}\" dengan \"{1}\"", + "customShortcutSummaryReplaceRegexXY": "Ganti regex \"{0}\" dengan \"{1}\"", + "customShortcutPickCommand": "Silakan pilih perintah untuk dijalankan.", + "customShortcutEnterText": "Silakan masukkan teks untuk disisipkan.", + "customShortcutEnterFind": "Silakan masukkan teks yang dicari.", + "customShortcutInvalidRegexX": "Ekspresi reguler tidak valid: {0}", + "customShortcutStepsHint": "Langkah dijalankan dari atas ke bawah. Langkah teks bekerja pada baris terpilih; perintah yang membuka jendela berlanjut setelah jendela ditutup.", "activeIn": "Aktif di", "activeInEverywhere": "Di mana saja", - "generalSetupLikeSe4": "Atur seperti Subtitle Edit 4 (tema, pintasan, aturan penggantian)", - "generalMergeSelectedLines": "Gabungkan baris terpilih", - "generalMergeWithPrevious": "Gabungkan dengan sebelumnya", - "generalMergeWithNext": "Gabungkan dengan berikutnya", - "generalMergeWithPreviousAndUnbreak": "Gabungkan dengan sebelumnya dan satukan", - "generalMergeWithNextAndUnbreak": "Gabungkan dengan berikutnya dan satukan", - "generalMergeWithPreviousAndAutoBreak": "Gabungkan dengan sebelumnya dan pisah otomatis", - "generalMergeWithNextAndAutoBreak": "Gabungkan dengan berikutnya dan pisah otomatis", - "generalMergeSelectedLinesAndAutoBreak": "Gabungkan baris terpilih dan pisah otomatis", "generalMergeSelectedLinesAndUnbreak": "Gabungkan baris terpilih dan satukan", "generalMergeSelectedLinesAndUnbreakCjk": "Gabungkan baris terpilih dan satukan tanpa spasi (CJK)", "generalUnbreakNoSpaceCjk": "Satukan tanpa spasi (CJK)", - "generalMergeSelectedLinesOnlyFirstText": "Gabungkan baris terpilih hanya teks pertama", + "generalToggleBreak": "Alihkan pisah/satukan baris", "generalMergeSelectedLinesBilingual": "Gabungkan baris terpilih dwibahasa", - "generalMergeWithPreviousBilingual": "Gabungkan dengan sebelumnya dwibahasa", - "generalMergeWithNextBilingual": "Gabungkan dengan berikutnya dwibahasa", - "generalMergeOriginalAndTranslation": "Gabungkan asli dan terjemahan", "generalToggleTranslationMode": "Alihkan mode terjemahan", - "generalSwitchOriginalAndTranslation": "Tukar asli dan terjemahan", - "generalSwitchOriginalAndTranslationTextBoxes": "Tukar kotak teks asli dan terjemahan", "generalToggleTranslationAndOriginalInPreviews": "Alihkan terjemahan dan asli pada pratinjau video/audio", "generalChooseLayout": "Pilih tata letak", - "generalLayoutChooseX": "Tata letak {0}", - "generalPlayFirstSelected": "Putar yang pertama terpilih", - "generalGoToFirstSelectedLine": "Pergi ke baris terpilih pertama", - "generalGoToNextEmptyLine": "Pergi ke baris kosong berikutnya", "generalGoToNextSubtitle": "Pergi ke subtitel berikutnya", "generalGoToNextSubtitlePlayTranslate": "Pergi ke subtitel berikutnya (putar terjemahan)", "generalGoToNextSubtitleCursorAtEnd": "Pergi ke subtitel berikutnya (kursor di akhir)", "generalGoToPrevSubtitle": "Pergi ke subtitel sebelumnya", "generalGoToPrevSubtitlePlayTranslate": "Pergi ke subtitel sebelumnya (putar terjemahan)", - "generalGoToStartOfCurrentSubtitle": "Pergi ke awal subtitel saat ini", - "generalGoToEndOfCurrentSubtitle": "Pergi ke akhir subtitel saat ini", - "generalGoToPreviousSubtitleAndFocusVideo": "Pergi ke subtitel sebelumnya dan fokuskan video", - "generalGoToNextSubtitleAndFocusVideo": "Pergi ke subtitel berikutnya dan fokuskan video", - "generalGoToPrevSubtitleAndPlay": "Pergi ke subtitel sebelumnya dan putar", - "generalGoToNextSubtitleAndPlay": "Pergi ke subtitel berikutnya dan putar", - "generalGoToPreviousSubtitleAndFocusWaveform": "Pergi ke subtitel sebelumnya dan fokuskan bentuk gelombang", - "generalGoToNextSubtitleAndFocusWaveform": "Pergi ke subtitel berikutnya dan fokuskan bentuk gelombang", - "generalGoToLineNumber": "Pergi ke nomor baris", "generalGoToFirstLine": "Pergi ke baris pertama", "generalGoToLastLine": "Pergi ke baris terakhir", "alsoSetVideoPosition": "Atur juga posisi video", "generalGoToVideoPosition": "Pergi ke posisi video", "generalToggleItalic": "Alihkan miring", "generalToggleBold": "Alihkan tebal", - "generalToggleBookmarks": "Alihkan markah", - "generalFocusTextBox": "Fokuskan kotak teks", - "generalToggleBookmarksWithText": "Alihkan markah dengan teks", - "generalEditBookmarks": "Edit markah", - "fileNew": "Baru", "generalToggleUnderline": "Alihkan garis bawah", + "generalToggleBox": "Alihkan kotak (EBU STL)", "fileOpen": "Buka", "fileOpenKeepVideo": "Buka (pertahankan video)", "fileSave": "Simpan", "fileSaveAs": "Simpan sebagai", - "fileSaveAll": "Simpan semua", "fileSaveOriginal": "Simpan asli", - "fileSaveOriginalAs": "Simpan asli sebagai", - "fileOpenOriginalSubtitle": "Buka subtitel asli", - "fileCloseOriginalSubtitle": "Tutup subtitel asli", - "fileTranslatedSubtitle": "Subtitel terjemahan", "fileCompare": "Bandingkan", "fileStatistics": "Statistik", "fileImportPlainText": "Impor teks polos", - "fileImportBluRaySupForOcr": "Impor Blu-ray SUP untuk OCR", - "fileImportBluRaySupForEdit": "Impor Blu-ray SUP untuk diedit", - "fileImportTimeCodes": "Impor kode waktu", "fileExportEbuStl": "Ekspor EBU STL", "fileExportPac": "Ekspor PAC", - "fileExportEdlClipName": "Ekspor nama klip EDL", - "fileExportPlainText": "Ekspor teks polos", - "fileExportCustomTextFormat1": "Ekspor format teks kustom 1", - "fileExportCustomTextFormat2": "Ekspor format teks kustom 2", - "fileExportCustomTextFormat3": "Ekspor format teks kustom 3", "fileExit": "Keluar", "openSeDataFolder": "Buka folder Subtitle Edit", - "editFind": "Cari", - "editFindNext": "Cari berikutnya", "editFindPrevious": "Cari sebelumnya", - "editReplace": "Ganti", - "editMultipleReplace": "Penggantian ganda", "fillSelectedLinesWithClipboard": "Isi baris terpilih dengan teks papan klip", - "editModifySelection": "Ubah seleksi", - "listSelectAll": "Pilih semua", - "listSelectFirst": "Pilih pertama", - "listSelectLast": "Pilih terakhir", "listInverseSelection": "Balikkan seleksi", "listDeleteSelection": "Hapus seleksi", + "deleteSelectionEverywhere": "Hapus seleksi (di mana saja)", "rippleDeleteSelection": "Hapus seleksi secara ripple", - "settings": "Pengaturan", "assigned": "Ditetapkan", "unassigned": "Belum ditetapkan", "pressedKeyX": "Tombol ditekan: {0}", @@ -3443,6 +3772,7 @@ "detectKey": "Deteksi tombol", "control": "Control", "alt": "Alt", + "altGr": "AltGr", "win": "Win", "shift": "Shift", "controlMac": "⌃", @@ -3453,11 +3783,14 @@ "resetShortcutsDetail": "Apakah Anda ingin mengatur ulang semua pintasan ke nilai default?", "togglePlayPause": "Alihkan putar/jeda", "toggleLockTimeCodes": "Alihkan kunci kode waktu", + "toggleTimeCodeMode": "Alihkan mode kode waktu (waktu/bingkai/nomor bingkai)", + "toggleScreenPrivacy": "Alihkan privasi layar (sembunyikan nama file, lalu juga teks)", + "screenPrivacyFileNamesHidden": "Privasi layar: nama file disembunyikan", + "screenPrivacyFileNamesAndTextsHidden": "Privasi layar: nama file dan teks disembunyikan", + "screenPrivacyOff": "Privasi layar: nonaktif", "duplicateSelectedLines": "Duplikat baris terpilih", "sourceView": "Tampilan sumber", - "showAlignmentPicker": "Perataan", "addOrEditBookmark": "Tambah atau edit markah", - "listBookmarks": "Daftar markah", "toggleBookmark": "Alihkan markah (baris terpilih, tanpa teks)", "toggleForcedSelectedLines": "Alihkan paksa (baris terpilih)", "goToNextBookmark": "Pergi ke markah berikutnya", @@ -3468,12 +3801,12 @@ "waveformSetStartAndSetEndOfPreviousMinusGap": "Atur awal dan atur akhir sebelumnya dikurangi jeda", "waveformSetEndAndStartOfNextAfterGap": "Atur akhir dan awal berikutnya ditambah jeda", "waveformSetEndAndStartOfNextAfterGapAndGoToNext": "Atur akhir dan awal berikutnya ditambah jeda, lalu ke berikutnya", - "fetchFirstWordFromNextSubtitle": "Ambil kata pertama dari subtitel berikutnya", "moveLastWordToNextSubtitle": "Pindahkan kata terakhir ke subtitel berikutnya", "moveLastWordFromFirstLineDownCurrentSubtitle": "Pindahkan kata terakhir baris pertama ke bawah (subtitel saat ini)", "moveFirstWordFromNextLineUpCurrentSubtitle": "Pindahkan kata pertama baris berikutnya ke atas (subtitel saat ini)", "moveTextFromCursorToNextAndGoToNext": "Pindahkan teks setelah posisi kursor ke subtitel berikutnya lalu ke berikutnya", "moveTextFromCursorToNextAndGoToNextAndPlay": "Pindahkan teks setelah posisi kursor ke subtitel berikutnya, ke berikutnya dan putar", + "moveTextFromCursorToNextAndGoToNextAndPlayAndPause": "Pindahkan teks setelah posisi kursor ke subtitel berikutnya, pergi ke berikutnya, putar dan jeda di akhirnya", "breakAtFirstSpaceFromCursor": "Pisah pada spasi pertama dari posisi kursor", "breakAtFirstSpaceFromCursorAndGoToNext": "Pisah pada spasi pertama dari posisi kursor dan pergi ke berikutnya", "toggleFocusGridAndWaveform": "Alihkan fokus antara kisi subtitel dan bentuk gelombang/spektogram", @@ -3485,7 +3818,6 @@ "videoGoToPreviousTimeCode": "Pergi ke kode waktu sebelumnya", "videoGoToNextTimeCode": "Pergi ke kode waktu berikutnya", "goToNextLineAndSetVideoPosition": "Pergi ke subtitel berikutnya dan atur posisi video", - "textBoxDeleteSelectionNoClipboard": "Kotak teks: Hapus seleksi (tanpa papan klip)", "textBoxDeleteForward": "Kotak teks: Hapus seleksi atau karakter berikutnya (hapus maju)", "textBoxCut": "Kotak teks: Potong", "textBoxCut2": "Kotak teks: Potong (alternatif)", @@ -3504,6 +3836,7 @@ "listViewColumnTextUp": "Kolom, teks ke atas", "listViewColumnTextDown": "Kolom, teks ke bawah", "autoTranslateSelectedLines": "Terjemahkan otomatis baris terpilih...", + "autoTranslateSelectedLinesNoPrompt": "Terjemahan otomatis baris terpilih (tanpa konfirmasi, gunakan mesin/bahasa terakhir)", "setAssaResolution": "Atur resolusi ASSA (PlayResX/PlayResY)", "setShortcutForX": "Atur pintasan untuk \"{0}\"", "commandFileNewKeepVideo": "Baru (pertahankan video)", @@ -3511,29 +3844,22 @@ "fileCloseOriginal": "Tutup asli", "fileCloseTranslation": "Tutup terjemahan", "restoreAutoBackup": "Pulihkan cadangan otomatis", - "openContainingFolder": "Buka folder berisi file", "importTimeCodes": "Impor kode waktu", "importSubtitleWithManuallyChosenEncoding": "Impor subtitel dengan pengodean yang dipilih manual", "exportBluRaySup": "Ekspor Blu-ray SUP", "exportCustomTextFormat": "Ekspor format teks kustom", "exportPlainText": "Ekspor teks polos", - "showHistory": "Tampilkan riwayat", "toggleRightToLeft": "Alihkan kanan-ke-kiri", "modifySelection": "Ubah seleksi", "adjustDurations": "Sesuaikan durasi", "applyDurationLimits": "Terapkan batas durasi", "batchConvert": "Konversi batch", - "bridgeGaps": "Jembatani jeda", "applyMinGap": "Terapkan jeda min", - "fixCommonErrors": "Perbaiki kesalahan umum", "makeEmptyTranslationFromCurrentSubtitle": "Buat terjemahan kosong dari subtitel saat ini", - "mergeLinesWithSameText": "Gabungkan baris dengan teks yang sama", - "mergeLinesWithSameTimeCodes": "Gabungkan baris dengan kode waktu yang sama", "splitBreakLongLines": "Pisah/seimbangkan baris panjang", "mergeShortLines": "Gabungkan baris pendek", "mergeContinuationLines": "Gabungkan baris lanjutan", "snapAllTimesToFrames": "Jajarkan semua waktu ke bingkai", - "removeTextForHearingImpaired": "Hapus teks untuk tunarungu", "joinSubtitles": "Satukan subtitel", "splitSubtitle": "Pisah subtitel", "spellCheck": "Periksa ejaan", @@ -3555,7 +3881,6 @@ "adjustAllTimes": "Sesuaikan semua waktu", "visualSync": "Sinkronisasi visual", "translateViaCopyPaste": "Terjemahkan via salin-tempel", - "shortcuts": "Pintasan", "wordLists": "Daftar kata", "chooseUiLanguage": "Pilih bahasa UI", "chooseRuleProfile": "Pilih profil aturan", @@ -3566,6 +3891,7 @@ "textBoxItalic": "Kotak teks miring", "textBoxBold": "Kotak teks tebal", "textBoxUnderline": "Kotak teks garis bawah", + "textBoxBox": "Kotak teks kotak (EBU STL)", "resetWaveformZoomAndSpeed": "Atur ulang zoom bentuk gelombang dan kecepatan pemutaran (laju putar)", "togglePlaybackSpeed": "Alihkan kecepatan pemutaran (laju putar)", "playbackSpeedSlower": "Kecepatan pemutaran lebih lambat (laju putar)", @@ -3577,22 +3903,28 @@ "seekSilenceForward": "Cari keheningan maju", "waveformGuessStart": "Tebak waktu mulai dari bentuk gelombang", "waveformGuessEnd": "Tebak waktu selesai dari bentuk gelombang", + "waveformGuessStartAndEnd": "Tebak waktu mulai dan akhir dari bentuk gelombang", "setVideoPositionCurrentSubtitleStart": "Atur posisi video ke awal baris saat ini", "goToSubtitlePositionAndPause": "Pergi ke posisi subtitel dan jeda", "setVideoPositionCurrentSubtitleEnd": "Atur posisi video ke akhir baris saat ini", "toggleAudioTracks": "Alihkan trek audio", - "listErrors": "Daftar kesalahan", "goToNextError": "Pergi ke kesalahan berikutnya", "goToPreviousError": "Pergi ke kesalahan sebelumnya", "addNameToNameList": "Tambahkan nama ke daftar nama", "findDoubleWords": "Cari kata ganda", "findDoubleLines": "Cari baris ganda", "colorX": "Warna {0}", - "removeColor": "Hapus warna", "surroundWith": "Kelilingi dengan...", "surroundWithXY": "Kelilingi dengan {0}/{1}", - "repeatLine": "Ulangi baris", "surroundWithNumberX": "Kelilingi dengan #{0}", + "surroundWithBehavior": "Perilaku", + "surroundWithBehaviorToggle": "Alihkan (tambah, atau hapus jika ada)", + "surroundWithBehaviorAdd": "Tambah (setiap kali)", + "surroundWithBehaviorRemove": "Hapus (semua)", + "surroundWithBehaviorRemoveOnce": "Hapus (satu setiap kali)", + "surroundWithWorksOn": "Bekerja pada", + "surroundWithScopeSelectionOrText": "Seleksi, jika tidak seluruh teks", + "surroundWithScopeEachLine": "Setiap baris", "searchVia": "Cari via", "searchViaX": "Cari via {0}", "searchViaNumberX": "Cari via #{0}", @@ -3620,6 +3952,11 @@ "copySubtitlePathToClipboard": "Salin jalur subtitel ke papan klip", "copySubtitleOriginalPathToClipboard": "Salin jalur subtitel asli ke papan klip", "focusTextBox": "Fokuskan kotak teks", + "focusSubtitleListView": "Fokuskan tampilan daftar subtitel", + "focusWaveform": "Fokuskan bentuk gelombang", + "focusOriginalTextBox": "Fokuskan kotak teks asli", + "textBoxGoToStart": "Kotak teks, pergi ke awal", + "textBoxGoToEnd": "Kotak teks, pergi ke akhir", "sortByNumber": "Urutkan berdasarkan nomor", "sortByStartTime": "Urutkan berdasarkan waktu \"Tampil\"", "sortByEndTime": "Urutkan berdasarkan waktu \"Selesai\"", @@ -3644,7 +3981,6 @@ "assaStyles": "ASSA Gaya", "assaProperties": "ASSA Properti", "assaAttachments": "ASSA Lampiran", - "assaVideoColorPicker": "ASSA Pemilih warna video", "recalculateDurationSelectedLines": "Hitung ulang durasi (baris terpilih)", "setDurationMaxCpsSelectedLines": "Atur durasi ke CPS maks (baris terpilih)", "toggleWaveformAndSpectrogramHeight": "Alihkan tinggi terbagi bentuk gelombang/spektogram", @@ -3658,9 +3994,9 @@ "selectionToSentenceCase": "Kotak teks: Seleksi ke Huruf kalimat", "selectionToggleCasing": "Kotak teks: Alihkan kapitalisasi (BESAR -> kecil -> Huruf Judul)", "googleIt": "Cari di Google (teks terpilih)", - "setActorXY": "Atur aktor {0}: {1}", + "setActorX": "Atur aktor: {0}", "setNewActor": "Atur aktor baru...", - "setActorSlotXTitle": "Slot aktor {0}", + "setStyleX": "Atur gaya: {0}", "toggleDialogDashes": "Alihkan tanda hubung dialog", "mergeWithLineBeforeAsDialog": "Gabungkan dengan baris sebelumnya sebagai dialog", "mergeWithLineAfterAsDialog": "Gabungkan dengan baris sesudahnya sebagai dialog", @@ -3671,13 +4007,27 @@ "moveStartOneFrameBackKeepGapPrev": "Geser awal satu bingkai mundur (pertahankan jeda ke sebelumnya jika dekat)", "moveStartOneFrameForwardKeepGapPrev": "Geser awal satu bingkai maju (pertahankan jeda ke sebelumnya jika dekat)", "moveEndOneFrameBackKeepGapNext": "Geser akhir satu bingkai mundur (pertahankan jeda ke berikutnya jika dekat)", - "moveEndOneFrameForwardKeepGapNext": "Geser akhir satu bingkai maju (pertahankan jeda ke berikutnya jika dekat)" + "moveEndOneFrameForwardKeepGapNext": "Geser akhir satu bingkai maju (pertahankan jeda ke berikutnya jika dekat)", + "moveStartXMsBack": "Geser awal X ms mundur (X diatur di Pengaturan, juga di Sinkronisasi Visual)", + "moveStartXMsForward": "Geser awal X ms maju (X diatur di Pengaturan, juga di Sinkronisasi Visual)", + "moveEndXMsBack": "Geser akhir X ms mundur (X diatur di Pengaturan, juga di Sinkronisasi Visual)", + "moveEndXMsForward": "Geser akhir X ms maju (X diatur di Pengaturan, juga di Sinkronisasi Visual)", + "moveSelectedLinesXMsBack": "Geser baris terpilih X ms mundur (X diatur di Pengaturan)", + "moveSelectedLinesXMsForward": "Geser baris terpilih X ms maju (X diatur di Pengaturan)", + "moveSelectedLinesAndForwardXMsBack": "Geser baris terpilih dan semua berikutnya X ms mundur (X diatur di Pengaturan)", + "moveSelectedLinesAndForwardXMsForward": "Geser baris terpilih dan semua berikutnya X ms maju (X diatur di Pengaturan)", + "moveAllLinesXMsBack": "Geser semua baris X ms mundur (X diatur di Pengaturan)", + "moveAllLinesXMsForward": "Geser semua baris X ms maju (X diatur di Pengaturan)", + "moveSelectedLinesCustomMsBack": "Geser baris terpilih, milidetik kustom ({0:#,###,##0}) mundur, {1}", + "moveSelectedLinesCustomMsForward": "Geser baris terpilih, milidetik kustom ({0:#,###,##0}) maju, {1}", + "moveSelectedLinesAndForwardCustomMsBack": "Geser baris terpilih dan semua berikutnya, milidetik kustom ({0:#,###,##0}) mundur, {1}", + "moveSelectedLinesAndForwardCustomMsForward": "Geser baris terpilih dan semua berikutnya, milidetik kustom ({0:#,###,##0}) maju, {1}", + "moveAllLinesCustomMsBack": "Geser semua baris, milidetik kustom ({0:#,###,##0}) mundur, {1}", + "moveAllLinesCustomMsForward": "Geser semua baris, milidetik kustom ({0:#,###,##0}) maju, {1}" }, "wordLists": { "title": "Daftar kata", "addName": "Tambah nama", - "addWord": "Tambah kata", - "addPair": "Tambah pasangan", "nameAndIgnoreList": "Daftar nama/abaikan", "userWords": "Daftar kata pengguna", "ocrFixList": "Daftar perbaikan OCR", @@ -3694,7 +4044,6 @@ "noPluginsInstalled": "Tidak ada plugin terpasang", "getPluginsOnline": "Dapatkan plugin daring...", "getPluginsTitle": "Dapatkan plugin", - "enabled": "Diaktifkan", "disabled": "Dinonaktifkan", "notSupportedOnThisOs": "Tidak didukung di sistem operasi ini", "removePluginXConfirm": "Hapus plugin '{0}'? Ini menghapus folder plugin:{1}", @@ -3711,17 +4060,19 @@ "downloadingXPercent": "Mengunduh {0}%...", "pluginXInstalled": "'{0}' terpasang.", "couldNotInstallPluginX": "Tidak dapat memasang '{0}': {1}", - "notInstalled": "Tidak terpasang", "install": "Pasang", "reinstall": "Pasang ulang", - "update": "Perbarui", "installedX": "Terpasang ({0})", "updateAvailableXToY": "Pembaruan tersedia ({0} → {1})", "applyPluginToWhichLinesX": "Terapkan '{0}' ke:", "applyToSelectedLinesX": "Baris terpilih ({0})", "applyToAllLinesX": "Semua baris ({0})", "updateAllXAvailable": "Perbarui semua ({0})", - "updatingXOfY": "Memperbarui {0}/{1}..." + "updatingXOfY": "Memperbarui {0}/{1}...", + "doNotAskAgain": "Jangan tanya lagi (ubah di Kelola plugin)", + "applyPluginsTo": "Terapkan plugin ke", + "askEachTime": "Tanyakan setiap kali", + "allLines": "Semua baris" }, "help": { "aboutSubtitleEdit": "Tentang Subtitle Edit", @@ -3765,6 +4116,8 @@ "nOcrDatabase": "Basis data nOCR", "drawMode": "Mode gambar:", "addNewCharcter": "Tambah karakter baru", + "addNOcrCharacterTitle": "Tambah karakter nOCR untuk baris {0}, karakter {1} dari {2} menggunakan basis data \"{3}\"", + "addBinaryOcrCharacterTitle": "Tambah karakter binary OCR untuk baris {0}, karakter {1} dari {2} menggunakan basis data \"{3}\"", "lineIndexX": "Baris {0}", "inspectNOcrAdditions": "Periksa penambahan nOCR baru", "ocrSelectedLines": "OCR baris terpilih", @@ -3813,6 +4166,8 @@ "binaryImageCompareInspectImageMatches": "\"Perbandingan gambar biner\" - Periksa kecocokan gambar", "saveBlankTextTitle": "Simpan teks kosong?", "saveBlankTextPrompt": "Simpan teks kosong untuk gambar?", + "drawnLinesDoNotMatchTitle": "Garis tidak cocok dengan gambar", + "drawnLinesDoNotMatchPrompt": "Beberapa garis yang digambar tidak cocok dengan gambar (garis hijau harus berada di karakter, garis merah harus di luarnya), sehingga karakter ini tidak akan dikenali.\n\nTetap simpan?", "yesAndNeverAskAgain": "Ya dan jangan tanya lagi", "importTextFromSubtitleDotDotDot": "Impor teks dari subtitel...", "importTextFromSubtitleOverwritePrompt": "Beberapa baris sudah memiliki teks OCR. Timpa teks yang ada?", @@ -3838,7 +4193,6 @@ "vobSubColorsTitle": "Warna VobSub/DVD", "vobSubColorsHeader": "Sesuaikan empat warna VobSub", "vobSubColorsDescription": "Subtitel VobSub/DVD menggunakan empat warna berindeks: latar belakang, pola, penekanan 1, dan penekanan 2. Pilih setiap warna di bawah untuk menimpa palet tersemat - berguna ketika warna asli memberikan kontras OCR yang buruk.", - "vobSubColorBackground": "Latar belakang", "vobSubColorPattern": "Pola", "vobSubColorEmphasis1": "Penekanan 1", "vobSubColorEmphasis2": "Penekanan 2", @@ -3872,9 +4226,26 @@ "crispEmbedSettingsTitle": "Pengaturan CrispEmbed", "crispEmbedDescription": "Mesin OCR lokal dengan beberapa backend model - unduh mesin dan modelnya di sini.", "trainNOcrDatabase": "Latih basis data nOCR...", + "optimizeNOcrDatabase": "Optimalkan basis data nOCR...", + "optimizeNOcrDatabaseQuestionXY": "Hapus garis yang tidak membedakan karakter dari karakter serupa di basis data nOCR \"{0}\"?\n\nIni membuat basis data lebih kecil dan OCR lebih cepat, biasanya dengan lebih sedikit kesalahan. Memakan waktu hingga satu menit.\n\nCadangan disimpan sebagai \"{1}\".", + "optimizingNOcrDatabaseXY": "Mengoptimalkan basis data nOCR... {0}/{1}", + "nOcrDatabaseOptimizedXYZ": "Basis data nOCR \"{0}\" dioptimalkan: {1:#,##0} -> {2:#,##0} garis.", "startTraining": "Mulai pelatihan", "abortTraining": "Batalkan pelatihan", "trainingOptions": "Opsi pelatihan", + "trainNOcrSubtitle": "Merender karakter dalam fon yang terpasang dan mengajari nOCR bentuknya. Pilih fon yang digunakan subtitel Anda.", + "xFontsSelected": "{0:#,##0} terpilih", + "subtitleFonts": "Fon subtitel", + "emptyDatabase": "Basis data kosong", + "startFromDatabase": "Mulai dari", + "startFromDatabaseHint": "Tambahkan karakter yang dilatih ke salinan basis data yang ada (disarankan) atau mulai dari basis data kosong", + "fontSizes": "Ukuran fon", + "fontSizesHint": "Ukuran fon dalam piksel untuk dirender, dipisahkan koma, mis. \"30, 40\"", + "numberOfLineSegmentsHint": "Garis yang diperiksa per karakter - lebih banyak lebih ketat tetapi lebih lambat", + "enterDatabaseName": "Masukkan nama untuk basis data", + "trainingFontXSizeYCharacterZ": "Melatih \"{0}\" pada {1} px: {2}", + "xLearned": "{0:#,##0} dipelajari", + "xSkipped": "{0:#,##0} dilewati", "charactersToTrain": "Karakter yang dilatih", "letterCombinationsToTrain": "Kombinasi huruf yang mungkin terpisah menjadi satu gambar", "importCharactersFromSubtitleFile": "Impor karakter dari file subtitel...", @@ -3901,6 +4272,8 @@ "drawZoomOut": "Zoom Keluar (Ctrl+-)", "drawResetView": "Atur Ulang Tampilan (Ctrl+0)", "drawToggleGrid": "Alihkan Kisi (Ctrl+G)", + "drawTogglePreview": "Alihkan pratinjau (F9)", + "drawPreviewFailed": "Pratinjau gagal - diperlukan ffmpeg dengan libass (filter subtitles)", "drawCopyToClipboard": "Salin ke Papan Klip (Ctrl+C)", "drawShapes": "Bentuk", "drawSelectedPoint": "Titik terpilih", @@ -3908,16 +4281,86 @@ "drawSelectedLayer": "Lapisan terpilih", "drawToolX": "Alat: {0}", "drawHelpText": "Klik untuk menambah titik • Enter/F8 untuk menutup bentuk • Shift+Seret untuk menggeser • Ctrl+Gulir untuk zoom", + "drawMoveToLayer": "Pindahkan ke lapisan", + "drawFlipHorizontal": "Balik horizontal", + "drawFlipVertical": "Balik vertikal", + "drawHideShape": "Sembunyikan bentuk", + "drawShowShape": "Tampilkan bentuk", + "drawHideLayer": "Sembunyikan lapisan", + "drawShowLayer": "Tampilkan lapisan", + "drawDeleteLayer": "Hapus lapisan", + "drawDeletePoint": "Hapus titik", + "drawCancelDrawing": "Batalkan menggambar", + "drawLayerX": "Lapisan {0}", + "drawImportSvg": "Impor gambar SVG...", + "drawRotateClockwise": "Putar 90° searah jarum jam", + "drawConvertToCurve": "Konversi segmen menjadi kurva", + "drawBackground": "Latar belakang", + "drawPosition": "Posisi", + "drawZoom": "Zoom", + "drawShapeTool": "Alat bentuk", + "drawColorPickerTool": "Ambil warna dari bentuk atau latar belakang", + "drawShapeLibrary": "Pustaka bentuk", + "drawShapeLibraryHint": "Seret di kanvas untuk menempatkan bentuk, atau klik untuk ukuran baku. Shift mempertahankan proporsinya.", + "drawAddToLibrary": "Tambahkan ke pustaka bentuk...", + "drawRemoveFromLibrary": "Hapus dari pustaka bentuk", + "drawShapeName": "Nama bentuk", + "drawCategorySpeechBubbles": "Balon ucapan", + "drawCategoryArrows": "Panah", + "drawCategoryBasicShapes": "Bentuk dasar", + "drawCategorySymbols": "Simbol", + "drawCategoryMyShapes": "Bentuk saya", + "drawShapeSpeechBubble": "Balon ucapan", + "drawShapeSpeechBubbleRight": "Balon ucapan, ekor ke kanan", + "drawShapeSpeechBubbleUp": "Balon ucapan, ekor ke atas", + "drawShapeOvalBubble": "Balon ucapan oval", + "drawShapeBoxBubble": "Balon ucapan kotak", + "drawShapeThoughtBubble": "Balon pikiran", + "drawShapeShoutBubble": "Balon teriakan", + "drawShapeCaptionBox": "Kotak keterangan", + "drawShapeArrow": "Panah", + "drawShapeDoubleArrow": "Panah ganda", + "drawShapeChevron": "Chevron", + "drawShapeCurvedArrow": "Panah melengkung", + "drawShapeRoundedRectangle": "Persegi panjang membulat", + "drawShapeTriangle": "Segitiga", + "drawShapeDiamond": "Belah ketupat", + "drawShapePentagon": "Segi lima", + "drawShapeHexagon": "Segi enam", + "drawShapeOctagon": "Segi delapan", + "drawShapeStar": "Bintang", + "drawShapeRing": "Cincin", + "drawShapePlus": "Plus", + "drawShapeBanner": "Spanduk", + "drawShapeHeart": "Hati", + "drawShapeCheck": "Tanda centang", + "drawShapeMusicNote": "Not musik", + "drawShapeMusicNotes": "Not-not musik", + "drawShapeLightning": "Petir", + "drawShapeCloud": "Awan", + "drawShapeMoon": "Bulan", + "drawShapeBadge": "Lencana", + "drawSize": "Ukuran", + "drawLayer": "Lapisan", + "drawBackgroundVideoFrame": "Bingkai video (posisi saat ini)", + "drawBackgroundVideoFrameAt": "Bingkai video pada...", + "drawBackgroundImage": "File gambar...", + "drawBackgroundNone": "Tanpa latar belakang", + "drawBackgroundStretch": "Rentangkan gambar ke kanvas", + "drawBackgroundOpacity": "Opasitas latar belakang", + "drawBackgroundVideoFrameFailed": "Tidak dapat mengambil bingkai video pada {0}.", + "drawImages": "Gambar", + "drawConvertToLine": "Konversi segmen menjadi garis", + "drawConvertShapeToCurves": "Konversi semua garis menjadi kurva", + "drawConvertShapeToLines": "Konversi semua kurva menjadi garis", + "drawRotateCounterClockwise": "Putar 90° berlawanan arah jarum jam", + "drawSvgImages": "Gambar SVG", + "drawSvgNoShapes": "Tidak ditemukan bentuk terisi atau goresan dalam file SVG.", "progressBarTitle": "Bilah progres ASSA", "progressBarSettings": "Bilah progres", - "progressBarPosition": "Posisi", - "progressBarBottom": "Bawah", - "progressBarTop": "Atas", "mouseOverColor": "Warna saat kursor di atas", "clickedColor": "Warna saat diklik", "progressBarForeColor": "Warna latar depan", - "progressBarBackColor": "Warna latar belakang", - "progressBarStyle": "Gaya", "progressBarSquareCorners": "Sudut persegi", "progressBarRoundedCorners": "Sudut membulat", "progressBarChapters": "Bab", @@ -3926,8 +4369,6 @@ "progressBarXAdjustment": "Penyesuaian X", "progressBarYAdjustment": "Penyesuaian Y", "progressBarTextAlignment": "Perataan teks", - "progressBarTakePosFromVideo": "Ambil posisi dari video", - "progressBarPreview": "Pratinjau", "fontCollectorTitle": "Pengumpul fon", "fontCollectorUsedIn": "Digunakan di", "fontCollectorFontFiles": "File fon", @@ -3974,9 +4415,7 @@ "resolutionResamplerVideoDiffers": "Resolusi subtitel ({0}x{1}) berbeda dari resolusi video ({2}x{3}).\nUbah subtitel agar cocok dengan video?", "resolutionResamplerAskOnVideoOpen": "Tanyakan saat video dengan resolusi berbeda dibuka", "backgroundBoxGenerator": "Buat kotak latar belakang", - "backgroundBoxPadding": "Padding", "backgroundBoxFillWidth": "Isi lebar", - "backgroundBoxBoxColor": "Warna kotak", "backgroundBoxRadius": "Radius", "backgroundBoxCircle": "Lingkaran", "backgroundBoxSpikes": "Duri", @@ -3988,12 +4427,9 @@ "backgroundBoxTornPaperDouble": "Kertas sobek (atas & bawah)", "backgroundBoxStarburst": "Ledakan bintang", "backgroundBoxScroll": "Gulungan / Perkamen", - "stylesTitle": "Gaya Advanced Sub Station Alpha", "stylesInFile": "Gaya dalam file", "stylesSaved": "Gaya disimpan", - "category": "Kategori", "allCategories": "[Semua]", - "defaultCategory": "Default", "newCategory": "Kategori baru...", "renameCategory": "Ganti nama kategori...", "deleteCategory": "Hapus kategori", @@ -4016,13 +4452,26 @@ "graphics": "Grafis", "copyToStorageStyles": "Salin ke gaya penyimpanan", "copyToFileStyles": "Salin ke gaya file", + "styleXAlreadyExistsInStorage": "Gaya bernama \"{0}\" sudah ada di penyimpanan.\n\nTimpa, atau pertahankan keduanya (salinan mendapat nama baru)?", + "styleXAlreadyExistsInFile": "Gaya bernama \"{0}\" sudah ada di file.\n\nTimpa, atau pertahankan keduanya (salinan mendapat nama baru)?", + "styleNameCannotBeEmpty": "Nama gaya tidak boleh kosong.", + "styleNameXIsUsedMoreThanOnce": "Nama gaya \"{0}\" digunakan oleh lebih dari satu gaya di file.", + "overwrite": "Timpa", + "keepBoth": "Pertahankan keduanya", + "doThisForAllConflictingStyles": "Lakukan ini untuk semua gaya yang bentrok", "setStyleAsDefault": "Jadikan gaya sebagai default", "takeUsagesFromDotDotDot": "Ambil penggunaan dari...", "replaceStyleWithDotDotDot": "Ganti gaya dengan...", "noAttachmentsFound": "Tidak ada lampiran ditemukan di file ASSA terpilih.", "deleteStyleQuestion": "Hapus gaya?", "deleteStylesQuestion": "Hapus gaya-gaya?", + "deleteXStylesQuestion": "Apakah Anda ingin menghapus {0} gaya?", + "deleteXStylesFromStorageQuestion": "Apakah Anda ingin menghapus {0} gaya dari penyimpanan?", + "deleteStyleXFromStorageQuestion": "Apakah Anda ingin menghapus gaya \"{0}\" dari penyimpanan?", + "deleteStyleXFromFileQuestion": "Apakah Anda ingin menghapus gaya \"{0}\" dari file saat ini?", "openStyleImportFile": "Buka file subtitel untuk mengimpor gaya", + "styleImportFiles": "File gaya (.ass, .sty, kategori Subtitle Edit 4 .template)", + "ssaStyleImportFiles": "File gaya (.ssa, .ass, .sty)", "primary": "Primer", "secondary": "Sekunder", "applyOverrideTags": "Terapkan tag override", @@ -4037,7 +4486,6 @@ "spaceIncrease": "Penambahan spasi", "popIn": "Muncul masuk", "spinIn": "Berputar masuk", - "playCurrent": "Putar saat ini", "setPosition": "Atur posisi", "rotation": "Rotasi", "scaleX": "Skala X", @@ -4081,10 +4529,8 @@ "advancedEffectOldMovieDescription": "Mensimulasikan proyektor film 35mm antik dengan derau, vinyet, dan goresan", "advancedEffectNeonBurst": "Ledakan neon", "advancedEffectNeonBurstDescription": "Takarir gaya 'Shorts' berenergi tinggi dengan kilau bloom neon dan animasi 'pop' skala masuk", - "advancedEffectSettings": "Pengaturan", "advancedEffectSnowFlakeCount": "Jumlah kepingan", "advancedEffectStarfieldStarCount": "Jumlah bintang", - "advancedEffectStarfieldSpeed": "Kecepatan", "advancedEffectGlitch": "Glitch", "advancedEffectGlitchDescription": "Glitch digital dengan aberasi kromatik, kilatan putih, dan lapisan distorsi", "advancedEffectBounceIn": "Pantulan masuk", @@ -4093,8 +4539,6 @@ "advancedEffectFirefliesDescription": "Titik kuning-amber bercahaya hangat melayang secara organik di layar dengan kilau berdenyut", "advancedEffectMatrix": "Matrix", "advancedEffectMatrixDescription": "Kolom karakter hijau berjatuhan dengan teks subtitel terbentuk huruf demi huruf dari hujan tersebut", - "advancedEffectAudioBars": "Bilah audio (bentuk gelombang)", - "advancedEffectAudioBarsDescription": "Bilah ekualiser beranimasi yang digerakkan bentuk gelombang audio — bilah bergulir ke kiri sebagai bentuk gelombang dan berdenyut mengikuti audio", "advancedEffectAudioPulse": "Denyut teks audio", "advancedEffectAudioPulseDescription": "Teks subtitel bercahaya dan berdenyut mengikuti volume audio — bagian hening menampilkan teks normal, puncak keras menambahkan halo berwarna dan sedikit pembesaran", "advancedEffectConfetti": "Ledakan konfeti", @@ -4108,7 +4552,6 @@ "advancedEffectFancyKaraokeGlow": "Kilau kata aktif", "advancedEffectFancyKaraokeActiveColor": "Warna aktif", "advancedEffectFancyKaraokeInactiveColor": "Warna tidak aktif", - "advancedEffectFancyKaraokeInactiveOpacity": "Keburaman tidak aktif (0-255)", "advancedEffectWordSpacingPixels": "Spasi (piksel)", "advancedEffectSlowZoomOut": "Zoom keluar lambat", "advancedEffectSlowZoomOutDescription": "Teks dimulai sedikit lebih besar dan perlahan mengecil ke ukuran normal selama durasi subtitel", @@ -4124,7 +4567,6 @@ "advancedEffectSlideInLeftDescription": "Teks masuk dari luar layar kiri, bertahan, lalu keluar kembali ke kiri", "advancedEffectSlideInRight": "Geser masuk dari kanan", "advancedEffectSlideInRightDescription": "Teks masuk dari luar layar kanan, bertahan, lalu keluar kembali ke kanan", - "advancedEffectFadeInOut": "Pudar masuk/keluar", "advancedEffectFadeInOutDescription": "Teks memudar masuk di awal dan memudar keluar di akhir setiap subtitel", "advancedEffectWordFlip3D": "Balik kata 3D", "advancedEffectWordFlip3DDescription": "Setiap kata muncul dengan balikan 3D dan pantulan elastis saat diucapkan — gaya teks 'Shorts' modern", diff --git a/src/ui/Assets/Languages/Italian.json b/src/ui/Assets/Languages/Italian.json index 45723a54092..68152823a72 100644 --- a/src/ui/Assets/Languages/Italian.json +++ b/src/ui/Assets/Languages/Italian.json @@ -1,6 +1,6 @@ { "title": "SubtitleEdit", - "version": "v. 5.3.0-rc3", + "version": "v5.3.0", "translatedBy": "v. 12.10.2026 di bovirus e GitGianluc", "cultureName": "it-IT", "general": { @@ -1274,7 +1274,7 @@ "newLineRows": "Numero righe aggiunte da una nuova riga", "teletext": "Televideo", "useBox": "Usa casella intorno al testo", - "doubleHeight": "Per il testo usa doppia altezza ", + "doubleHeight": "Per il testo usa doppia altezza", "videoPreview": "Anteprima video", "previewFont": "Font personalizzata", "previewFontDefault": "(usa la font dell'anteprima video)", @@ -1387,7 +1387,7 @@ "lineNumbers": "Numeri di riga", "showLineNumbers": "Visualizza numeri di riga", "addNewLineAfterLineNumber": "Dopo il numero di riga aggiungi una nuova riga", - "addNewLineAfterTimeCode": "Dopo il codice temporale aggiungi una nuova riga ", + "addNewLineAfterTimeCode": "Dopo il codice temporale aggiungi una nuova riga", "addNewLineAfterText": "Aggiungi una nuova riga dopo il testo", "addLineBetweenSubtitles": "Aggiungi una linea tra i sottotitoli", "titleExportDCinemaInteropPng": "Interoperabilità/png D-Cinema", @@ -1596,7 +1596,7 @@ "compareCurrent": "Attuale", "compareReference": "Riferimento", "compareEditable": "Modificabile", - "compareEditableHint": "Quando selezioni 'Applica' le modifiche apportate qui tornano al sottotitolo ", + "compareEditableHint": "Quando selezioni 'Applica' le modifiche apportate qui tornano al sottotitolo", "compareReadOnly": "Sola lettura", "compareTakeFromReference": "Prendi testo e tempi dal riferimento", "compareInsertFromReference": "Inserisci questa riga nel sottotitolo attuale", @@ -1860,14 +1860,14 @@ "fixDoubleApostrophes": "Sostituisci i doppi apici ('') con le doppie virgolette (\")", "fixMisreadQuotes": "Correggi gli apostrofi interpretati erroneamente come doppie virgolette (OCR, Inglese)", "addPeriods": "Aggiungi un punto dopo le righe dove la riga successiva inizia con la lettera maiuscola", - "startWithUppercaseLetterAfterParagraph": "Dopo un paragrafo inizia la riga con una lettera maiuscola ", + "startWithUppercaseLetterAfterParagraph": "Dopo un paragrafo inizia la riga con una lettera maiuscola", "startWithUppercaseLetterAfterPeriodInsideParagraph": "Dopo un punto nel paragrafo inizia la riga con una lettera maiuscola", - "startWithUppercaseLetterAfterColon": "Dopo i due punti/punto e virgola inizia la riga con una lettera maiuscola ", + "startWithUppercaseLetterAfterColon": "Dopo i due punti/punto e virgola inizia la riga con una lettera maiuscola", "fixLowercaseIToUppercaseI": "Sostituisci la lettera minuscola 'i' da sola con 'I' (Inglese)", "fixCommonOcrErrors": "Correggi gli errori comuni OCR (usando elenco sostituzioni OCR)", "removeSpaceBetweenNumber": "Rimuovi spazio tra i numeri", "breakDialogsOnOneLine": "Dividi i dialoghi in una sola riga", - "removeDialogFirstInNonDialogs": "Per i non dialoghi rimuovi trattino iniziale nella prima riga ", + "removeDialogFirstInNonDialogs": "Per i non dialoghi rimuovi trattino iniziale nella prima riga", "normalizeStrings": "Normalizza stringhe", "fixTurkishAnsi": "Correggi lettere ANSI turco (Islandese) a Unicode", "fixDanishLetterI": "Correggi lettera danese 'i'", @@ -2716,7 +2716,7 @@ "importPiperVoiceTitle": "Apri modello vocale Piper (.onnx)", "piperVoiceConfigMissingTitle": "File configurazione mancante", "piperVoiceConfigMissingMessage": "Una voce Piper ha bisogno del suo file di configurazione accanto al file del modello: {0}", - "dropAudioFileHereToImportVoice": "Per importare la voce rilascia qui il file audio ", + "dropAudioFileHereToImportVoice": "Per importare la voce rilascia qui il file audio", "dropAudioFileHereHint": ".wav o .mp3", "voiceCloneTranscriptTitle": "Inserisci la trascrizione dell'audio (richiesto per la clonazione della voce)", "voiceManagerTitle": "TTS: gestione vocale", @@ -3572,6 +3572,7 @@ "timeCodeModeTime": "Tempo (oo:mm:ss,zzz)", "timeCodeModeFrames": "Fotogrammi (hh:mm:ss:ff)", "timeCodeModeFrameNumbers": "Numeri fotogrammi", + "frameModeSnapToVideoFrames": "Memorizza i tempi sui fotogrammi del video (non sui fotogrammi di ogni secondo)", "textBoxLimitNewLines": "Limita il numero di righe nella casella testo sottotitoli", "mpvOpenGl": "libmpv - OpenGL", "mpvSoftwareRendering": "libmpv - rendering software (lento)", @@ -3592,8 +3593,8 @@ "vlcWidRendering": "libVLC - rendering ID finestra nativa", "ffmpegSoftwareRendering": "FFmpeg - Rendering software", "downloadFfmpegLibs": "Scarica le librerie FFmpeg (per il lettore video FFmpeg)", - "subtitleGridEnterKeyAction": "Griglia sottotitoli - azione tasto Invio ", - "subtitleSingleClickAction": "Griglia sottotitoli - azione singolo clic ", + "subtitleGridEnterKeyAction": "Griglia sottotitoli - azione tasto Invio", + "subtitleSingleClickAction": "Griglia sottotitoli - azione singolo clic", "subtitleDoubleClickAction": "Griglia sottotitoli - azione doppio clic", "subtitleGridCenterSelectedRow": "Griglia sottotitoli, centra quando si seleziona la riga precedente/successiva", "saveAsBehavior": "Comportamento 'Salva con nome'", @@ -3944,8 +3945,8 @@ "trimWhitespaceSelectedLines": "Taglia spazi vuoti (righe selezionate)", "waveformInsertNewSelection": "La forma d'onda inserisce una nuova selezione", "waveformNewSelectionPasteFromClipboard": "Incolla testo appunti forma d'onda nella nuova selezione", - "waveformHorizontalZoomInCommand": "Forma d'onda - zoom+ orizzontale ", - "waveformHorizontalZoomOutCommand": "Forma d'onda - zoom- orizzontale ", + "waveformHorizontalZoomInCommand": "Forma d'onda - zoom+ orizzontale", + "waveformHorizontalZoomOutCommand": "Forma d'onda - zoom- orizzontale", "waveformVerticalZoomInCommand": "Forma d'onda - zoom+ verticale", "waveformVerticalZoomOutCommand": "Forma d'onda - zoom- verticale", "copySubtitlePathToClipboard": "Copia percorso file sottotitoli negli appunti", diff --git a/src/ui/Assets/Languages/Japanese.json b/src/ui/Assets/Languages/Japanese.json index 44cddc24aee..cd2c274be89 100644 --- a/src/ui/Assets/Languages/Japanese.json +++ b/src/ui/Assets/Languages/Japanese.json @@ -1,6 +1,6 @@ { "title": "Subtitle Edit", - "version": "v5.3.0-rc4", + "version": "v5.3.0", "translatedBy": "hisui3393", "cultureName": "jp-JP", "general": { @@ -527,7 +527,7 @@ "rules": "ルール", "saveChangesMessage": "現在の字幕ファイルへの変更を保存しますか?", "saveChangesTitle": "変更を保存しますか?", - "saveChangesToX": " \"{0}\"への変更を保存しますか?", + "saveChangesToX": "\"{0}\"への変更を保存しますか?", "saveChangesToXOriginal": "別の字幕ファイル \"{0}\"への変更を保存しますか?", "saveDotDotDot": "保存...", "saveFileAsTitle": "ファイルに 名前を付けて保存", @@ -665,7 +665,7 @@ "subtitleFiles": "字幕ファイル", "subtitleFormats": "字幕フォーマット", "subtitleLoadedX": "字幕が読み込まれました: {0}", - "subtitleReloadedChangedOutsideX": "外部で変更された字幕を再読み込みしました", + "subtitleReloadedChangedOutsideX": "外部で変更された字幕を再読み込みしました: {0}", "suffix": "接尾語", "suggestions": "候補", "sync": "同期", @@ -895,7 +895,7 @@ "commandPalette": "コマンドパレット...", "rightToLeftMode": "右から左モード", "modifySelectionDotDotDot": "選択範囲を変更", - "tools": " ツール", + "tools": "ツール", "adjustDurations": "_表示時間の調整", "applyDurationLimits": "表示時間の適用", "batchConvert": "_一括変換", @@ -2239,7 +2239,7 @@ "deletePreset": "プリセットを削除", "presetName": "プリセット名", "deletePresetX": "プリセット「{0}」を削除しますか?" - }, + }, "changeCasing": { "fixNames": "名前を修正", "namesX": "名前数: {0:#,##0}", @@ -2905,7 +2905,7 @@ "deleteSelectedShotChangeQuestion": "選択したショット変更を削除しますか?", "shotChanges": "ショットチェンジ", "exportShotChanges": "ショットチェンジをエクスポート" - }, + }, "chapters": { "title": "チャプター", "chaptersDotDotDot": "チャプター...", @@ -3572,6 +3572,7 @@ "timeCodeModeTime": "時間(hh:mm:ss,zzz)", "timeCodeModeFrames": "フレーム(hh:mm:ss:ff)", "timeCodeModeFrameNumbers": "フレーム番号", + "frameModeSnapToVideoFrames": "時間を動画のフレームに合わせて保存(各秒内のフレームではなく)", "textBoxLimitNewLines": "テキストボックスの行数を制限します", "mpvOpenGl": "GPU (OpenGL):高速,滑らか", "mpvSoftwareRendering": "CPUのみ:互換性重視(低速)", diff --git a/src/ui/Assets/Languages/Korean.json b/src/ui/Assets/Languages/Korean.json index 268b1bce262..26a215d0046 100644 --- a/src/ui/Assets/Languages/Korean.json +++ b/src/ui/Assets/Languages/Korean.json @@ -1,6 +1,6 @@ { "title": "Subtitle Edit", - "version": "v5.3.0-rc3", + "version": "v5.3.0", "translatedBy": "Hack茶ん, 12시27분(@12si27)", "cultureName": "ko-KR", "general": { @@ -3572,6 +3572,7 @@ "timeCodeModeTime": "시간 (hh:mm:ss,zzz)", "timeCodeModeFrames": "프레임 (hh:mm:ss:ff)", "timeCodeModeFrameNumbers": "프레임 번호", + "frameModeSnapToVideoFrames": "시간을 동영상 프레임에 맞춰 저장 (초 단위 내 프레임이 아님)", "textBoxLimitNewLines": "자막 입력칸 줄 수 제한", "mpvOpenGl": "libmpv - OpenGL", "mpvSoftwareRendering": "libmpv - 소프트웨어 렌더링(느림)", diff --git a/src/ui/Assets/Languages/Macedonian.json b/src/ui/Assets/Languages/Macedonian.json index 00cdeb20e8f..74f51841195 100644 --- a/src/ui/Assets/Languages/Macedonian.json +++ b/src/ui/Assets/Languages/Macedonian.json @@ -1,6 +1,6 @@ { "title": "Уредување на титлови", - "version": "v5.2.0-rc7", + "version": "v5.3.0", "translatedBy": "Кирил Жежоски", "cultureName": "mk-MK", "general": { @@ -71,6 +71,24 @@ "abort": "Прекини", "actor": "Актер", "actors": "Актери", + "setActorDotDotDot": "Постави актер...", + "actorPickerTitle": "Постави актер", + "actorPickerFilterHint": "Пишувај за да филтрираш или внеси име на нов актер", + "actorPickerKeysHint": "Притисни број или Enter за да го поставиш актерот. Alt+Up/Down го менува редоследот, Delete го отстранува актерот.", + "actorPickerNewActorX": "Нов актер „{0}“", + "actorPickerLinesSelectedX": "Избрани линии: {0}", + "setStyleDotDotDot": "Постави стил...", + "stylePickerTitle": "Постави стил", + "stylePickerFilterHint": "Пишувај за да филтрираш или внеси име на нов стил", + "stylePickerKeysHint": "Притисни број (1-9, 0) за да го поставиш стилот на сите избрани линии.", + "stylePickerNewStyleX": "Нов стил „{0}“", + "stylePickerNewStyleInfo": "Притисни Enter за да го додадеш овој стил со стандардни поставки и да го поставиш на избраните линии. Користи „Управувај со стилови...“ за подоцна да ги смениш фонтот, боите и позицијата.", + "stylePickerCurrentStyleX": "Тековен стил: {0}", + "stylePickerLinesUsingStyleX": "Линии што го користат овој стил: {0}", + "stylePickerMarginsXYZ": "Лево {0}, десно {1}, вертикално {2}", + "stylePickerSetStyle": "_Постави стил", + "stylePickerManageStyles": "Управувај со стилови...", + "stylePickerManageStylesHint": "Затвори го овој прозорец и отвори го управувачот со стилови за да ги уредиш фонтовите, боите, позицијата и друго", "add": "Додај", "addDotDotDot": "Додај...", "addToNamesListCaseSensitive": "Додај во листата со имена (чувствително на големина)", @@ -79,7 +97,6 @@ "adjustAlpha": "Прилагоди алфа (прозирност)", "adjustDisplayDuration": "Прилагоди време за прикажување", "adjustment": "Прилагодување", - "advanced": "Напредно", "advancedSettings": "Напредни поставки", "after": "По", "alignment": "Порамнување", @@ -115,7 +132,6 @@ "batchMode": "Пакетен режим", "bdnXml": "BDN/xml", "before": "Пред", - "beforeX": "Пред „{0}“", "beginning": "Почеток", "bluRaySup": "Blu-ray (sup)", "bold": "Задебелено", @@ -127,7 +143,6 @@ "bookmarkEdit": "Уреди обележувач", "bookmarkEditForSelectedLinesX": "Уреди обележувач за {0} избрани линии", "bookmarksList": "Листа со обележувачи", - "borderColor": "Боја на граница", "borderStyle": "Стил на граница", "bottomCenter": "Дно‑центар", "bottomLeft": "Дно‑лево", @@ -150,7 +165,6 @@ "changeCasing": "Смени големина на букви", "changeFormatting": "Смени форматирање", "changeFrameRate": "Смени брзина на кадри", - "changeLanguageFilter": "Смени филтер за јазик...", "changeOnce": "Смени еднаш", "changeSpeed": "Смени брзина", "character": "Знак", @@ -172,7 +186,6 @@ "conversionCancelledByUser": "Конверзијата е прекината од корисникот", "convert": "Конвертирај", "converted": "Конвертирано", - "convertingDotDotDot": "Конвертирање...", "convertingXofYDotDoDot": "Конвертирање {0:#,###,##0}/{1:#,###,##0}...", "copy": "Копирај", "copyImageToClipboard": "Копирај слика во таблата со исечоци", @@ -180,7 +193,6 @@ "couldNotSaveFileXErrorY": "Не можеше да се зачува датотеката „{0}“. Грешка: {1}", "count": "Број", "cps": "Знаци/сек", - "currentSubtitle": "Тековен титл", "currentVideoPosition": "Тековна позиција на видео", "cut": "Исечи", "dark": "Темно", @@ -225,7 +237,6 @@ "expand": "Прошири", "export": "Извези", "exportDotDotDot": "Извези...", - "exportToX": "Извези во {0}", "extendAfter": "Продолжи/скъси до линија после", "extendBefore": "Продолжи/скъси до линија пред", "extendSelectedLinesToNextShotChangeOrNextSubtitle": "Продолжи избрани линии до следна промена на сцена (или следен титл)", @@ -241,7 +252,6 @@ "fileExtension": "Наставка на датотека", "fileName": "Име на датотека", "fileNameX": "Име на датотека: {0}", - "fileNameXAndSize": "Име на датотека: {0} ({1})", "fileSaved": "Датотеката е зачувана", "fileSavedToX": "Датотеката е зачувана во {0}", "fileXAlreadyExists": "Датотеката „{0}“ веќе постои.", @@ -251,8 +261,6 @@ "find": "Најди", "findNext": "Најди следна", "findTextX": "Најди текст - {0}", - "fiveHundredMilliseconds": "500 милисекунди", - "fiveSeconds": "Пет секунди", "fix": "Поправи", "fixCommonErrors": "Поправи чести грешки", "fixRightToLeft": "Поправи оддесно‑на‑лево", @@ -276,14 +284,12 @@ "foundXInLineYZ": "Пронајдено „{0}“ во линија {1}, позиција {2}", "foundXMatches": "Пронајдено {0} резултати", "frameRate": "Брзина на кадри", - "frameRateX": "Брзина на кадри: {0:0.0###}", "frames": "Кадри", "from": "Од", "fromCurrentVideoPosition": "од тековната позиција во видеото", "fullScreen": "Цел екран", "gap": "Празнина", "general": "Општо", - "generalText": "Општо", "generate": "Генерирај", "generateImportShotChanges": "Генерирај/импортирај промени на сцени", "generating": "Генерирање...", @@ -300,12 +306,8 @@ "help": "Помош", "hex": "Хекс", "hide": "Скриј", - "hidePreview": "Скриј преглед", "history": "Историја", "horizontal": "Хоризонтално", - "hourMinutesSecondsDecimalSeparatorMilliseconds": "Час:мин:сек{0}мс", - "hourMinutesSecondsFrames": "Час:мин:сек:кадри", - "ip": "IP", "id": "ID", "image": "Слика", "imageSaved": "Сликата е зачувана", @@ -363,6 +365,7 @@ "mergeSelectedLinesDialog": "Спој избрани линии како дијалог", "mergeWithLineAfterAndAutoBreak": "Спој со линија после и автоматски раздели", "mergeWithLineAfterKeepBreaks": "Спој со линија после (задржи разделувања)", + "mergeWithLineAfterAndUnbreak": "Спој со линија после и обратно дели", "mergeWithLineBeforeAndAutoBreak": "Спој со линија пред и автоматски раздели", "mergeWithLineBeforeKeepBreaks": "Спој со линија пред (задржи разделувања)", "middleCenter": "Средина‑центар", @@ -377,11 +380,11 @@ "moveAllShotChangeOneFrameForward": "Помести сите промени на сцена за еден кадар напред", "multiMode": "Мулти режим", "moveDown": "Помести надолу", + "increase": "Зголеми", + "decrease": "Намали", "moveUp": "Помести нагоре", "multipleReplace": "Множествена замена", - "mute": "Заглуши", "name": "Име", - "negative": "Негативно", "new": "Ново", "newDotDotDot": "Ново...", "newProfile": "Нов профил", @@ -408,20 +411,15 @@ "offsetY": "Отстап Y", "ok": "_ОК", "oneFile": "Една датотека", - "oneHundredMilliseconds": "100 милисекунди", - "oneSecond": "Една секунда", "oneSecondBack": "Една секунда назад", "oneSecondForward": "Една секунда напред", - "onlineVideoFeatureNotAvailable": "Функцијата не е достапна за онлајн видео", "openContainingFolder": "Отвори содржна папка", "openDictionaryFolder": "Отвори папка со речници", "openFile": "Отвори датотека", "openImageBasedSubtitle": "Отвори титл базиран на слика", "openImageFile": "Отвори датотека со слика", - "openOriginalSubtitleFile": "Отвори оригинална датотека со титл...", "openOriginalSubtitleFileTitle": "Отвори оригинална датотека со титл", "openOutputFolder": "Отвори папка за излезни датотеки", - "openSubtitle": "Отвори титл...", "openSubtitleFileTitle": "Отвори датотека со титл", "openSubtitles": "Отвори титлови...", "openVideoFile": "Отвори видеодатотека...", @@ -434,11 +432,6 @@ "outlineWidth": "Ширина на контура", "outputFolder": "Излезна папка", "outputProperties": "Својства на излезот", - "overlap": "Преклопување", - "overlapNextX": "Преклопи следно ({0:#;##0.###})", - "overlapPreviousLineX": "Преклопи претходна линија ({0:#;##0.###})", - "overlapStartAndEnd": "Преклопи почеток и крај", - "overlapX": "Преклопи ({0:#;##0.###})", "overwriteExistingFiles": "Презапиши постоечки датотеки", "overwriteFilesInFolderX": "Презапиши датотеки во папка: „{0}“", "overwriteQuestion": "Презапиши?", @@ -451,6 +444,7 @@ "percent": "Процент", "pickLayer": "Постави слој", "pickOllamaModel": "Избери модел за Ollama", + "pickModel": "Избери модел", "llamaCppUseRemoteServer": "Користи надворешен сервер (URL)", "pickOutputFolder": "Избери излезна папка", "pickResolutionFromCurrentVideo": "Избери резолуција од тековното видео", @@ -490,7 +484,6 @@ "refresh": "Освежи", "region": "Регион", "regularExpression": "Регуларен израз", - "regularExpressionIsNotValid": "Регуларниот израз не е валиден!", "relativeToCurrentVideoPosition": "Во однос на тековната позиција на видеото", "remove": "Отстрани", "removeAlignment": "Отстрани порамнување", @@ -515,7 +508,7 @@ "reset": "Ресетирај", "resizePanels": "Промени големина на панелите", "resolution": "Резолуција", - "resolutionX": "Резолуција: {0}", + "retry": "Обиди се повторно", "reverseRightToLeftStartEnd": "Преврти RTL почеток/крај", "right": "Десно", "rightToLeft": "Оддесно‑на‑лево", @@ -553,8 +546,6 @@ "selectedlinesX": "Избрани линии: {0}", "sensitivity": "Сензитивност", "separator": "Сепаратор", - "sessionKey": "Клуч за сесија", - "sessionKeyGenerate": "Генерирај нов клуч", "setEnd": "Постави крај", "setEndAndGoToNext": "Постави крај и оди следно", "setEndAddNewAndGoToNew": "Постави крај, додај нова и оди на новата", @@ -573,8 +564,9 @@ "shadowColor": "Боја на сенка", "shadowWidth": "Ширина на сенка", "shiftLineBy": "Помести линија за", + "shotIn": "Почеток на кадар", + "shotOut": "Крај на кадар", "shortcut": "Кратенка", - "shortcutX": "Кратенка: {0}", "shortcuts": "Кратенки", "show": "Прикажи", "showActorColumn": "Прикажи колона „Актер“", @@ -585,17 +577,19 @@ "showStartColumn": "Прикажи колона „Старт“", "showHideColumn": "Прикажи колона „Скриј“", "showHistory": "Прикажи историја", + "clearHistory": "Исчисти историја", + "removeFromHistory": "Отстрани од историјата", "showLayerColumn": "Прикажи колона „Слој“", "showPreview": "Прикажи преглед", "showShotChangesList": "Прикажи листа со промени на сцени", + "showShotInColumn": "Прикажи колона „Почеток на кадар“", + "showShotOutColumn": "Прикажи колона „Крај на кадар“", "showStyleColumn": "Прикажи колона „Стил“", "showTeletext": "Прикажи колона за телетекст во листата", "showTimeCodes": "Прикажи временски кодови", "showWpmColumn": "Прикажи колона „Збори/мин“", "showPixelWidthColumn": "Прикажи колона „Ширина во пиксели“", "shrink": "Смали", - "singleBox": "Една кутија", - "singleLineLengths": "Должина на една линија:", "singleMode": "Единечен режим", "textTotalLength": "Текст - вкупна должина", "textSingleLineMaxLength": "Текст - максимална должина на една линија", @@ -642,16 +636,16 @@ "strikeout": "Прешкртано", "style": "Стил", "styleExaggeration": "Експериментален стил", - "styleLanguage": "Стил / Јазик", "styles": "Стилови", "subtitleFile": "Датотека со титлови", + "subtitleFileChangedOutsideX": "Датотеката „{0}“ е променета надвор од Subtitle Edit.\n\nДа се вчита повторно и да се изгубат твоите промени?", "subtitleFileName": "Име на датотека со титлови", "subtitleFileSaved": "Датотеката со титлови е зачувана", "subtitleFileSavedToX": "Датотеката со титлови е зачувана во {0}", "subtitleFiles": "Датотеки со титлови", "subtitleFormats": "Формати за титлови", "subtitleLoadedX": "Титлот е вчитан: {0}", - "subtitleXOfY": "Титл {0} од {1}", + "subtitleReloadedChangedOutsideX": "Титлот е повторно вчитан (променет надвор од Subtitle Edit): {0}", "suffix": "Суфикс", "suggestions": "Сугестија", "sync": "Синхронизација", @@ -663,7 +657,6 @@ "teletextColor": "Боја на телетекст", "teletextLine": "Линија на телетекст", "tenHours": "10 часа", - "tenMilliseconds": "10 милисекунди", "text": "Текст", "textColor": "Боја на текст", "textFiles": "Текстуални датотеки", @@ -688,9 +681,6 @@ "topLeft": "Горен‑лево", "topRight": "Горен‑десно", "totalAdjustmentX": "Вкупно прилагодување: {0}", - "totalFramesX": "Вкупно кадри: {0:#;##0.##}", - "totalLengthX": "Вкупна должина: {0}", - "totalLengthXSplitLine": "Вкупна должина: {0} (раздели линија!)", "translate": "Преведи", "translatePercentX": "Преведување: {0}%", "translateRow": "Преведи ред", @@ -699,6 +689,7 @@ "type": "Тип", "unbreak": "Обратно дели текст", "unbreakLines": "Обратно дели линии", + "undelete": "Врати избришано", "underline": "Подвлечено", "undo": "Врати", "unknown": "Непознато", @@ -715,13 +706,11 @@ "usages": "Употреба", "use": "Користи", "useAlways": "Користи секогаш", - "useLargerFontForThisWindow": "Користи поголем фонт за ова прозорец", "useOnce": "Користи еднаш", "useOutputFolder": "Користи излезна папка", "useSourceFolder": "Користи изворна папка", "useSourceResolution": "Користи изворна резолуција", "userName": "Корисничко име", - "userNameAlreadyInUse": "Корисничкото име веќе е во употреба", "vertical": "Вертикално", "video100MsBack": "Видео, 100 милисекунди назад", "video100MsForward": "Видео, 100 милисекунди напред", @@ -735,14 +724,25 @@ "videoCustom3ForwardX": "Видео, прилагодени милисекунди ({0:#,###,##0}) напред, 3", "videoCustom4BackX": "Видео, прилагодени милисекунди ({0:#,###,##0}) назад, 4", "videoCustom4ForwardX": "Видео, прилагодени милисекунди ({0:#,###,##0}) напред, 4", - "videoEncodingX": "Видео кодирање: {0}", + "videoCustom1BackAndPauseX": "Видео, прилагодени милисекунди ({0:#,###,##0}) назад и пауза, 1", + "videoCustom1ForwardAndPauseX": "Видео, прилагодени милисекунди ({0:#,###,##0}) напред и пауза, 1", + "videoCustom2BackAndPauseX": "Видео, прилагодени милисекунди ({0:#,###,##0}) назад и пауза, 2", + "videoCustom2ForwardAndPauseX": "Видео, прилагодени милисекунди ({0:#,###,##0}) напред и пауза, 2", + "videoCustom3BackAndPauseX": "Видео, прилагодени милисекунди ({0:#,###,##0}) назад и пауза, 3", + "videoCustom3ForwardAndPauseX": "Видео, прилагодени милисекунди ({0:#,###,##0}) напред и пауза, 3", + "videoCustom4BackAndPauseX": "Видео, прилагодени милисекунди ({0:#,###,##0}) назад и пауза, 4", + "videoCustom4ForwardAndPauseX": "Видео, прилагодени милисекунди ({0:#,###,##0}) напред и пауза, 4", "videoExtension": "Наставка за видео датотека", "videoFile": "Видеодатотека", "videoFileGenerated": "Видео датотеката е генерирана", "videoFileGeneratedX": "Генерирана видеодатотека: „{0}“", + "videoFilesGenerated": "Видео датотеките се генерирани", + "xVideosGenerated": "Генерирани видеа: {0}", + "xOfYVideosGenerated": "Генерирани {0} од {1} видеа", + "savedInX": "Зачувано во {0}", + "savedInXFolders": "Зачувано во {0} папки", "videoFiles": "Видеодатотеки", "videoAndAudioFiles": "Видео и аудио датотеки", - "videoInformation": "Видео информации", "videoOffset": "Одбег на видео", "videoOneFrameBack": "Видео, еден кадар назад", "videoOneFrameForward": "Видео, еден кадар напред", @@ -753,7 +753,6 @@ "videoPlayer": "Видеоплеер", "videoPosition": "Позиција на видео", "videoResolution": "Резолуција на видео", - "viewX": "Преглед {0}", "visible": "Видливо", "voice": "Глас", "volume": "Гласност", @@ -762,15 +761,12 @@ "waveformCenterOnVideoPosition": "Волуменска форма центрирана на позицијата во видеото", "waveformPasteFromClipboard": "Залепи од таблата со исечоци", "waveformSpectrogram": "Волуменска форма/спектрограма", - "webServiceUrl": "URL на веб сервис", "width": "Ширина", - "openAiCompatibleStt": "OpenAI компатибилен STT", "xOfYFiles": "{0:#,###,##0} од {1:#,###,##0} датотеки", "zeroBytes": "0 бајти", "copiedToClipboard": "Копирано во таблата со исечоци", "openAiCompatibleSttEndpoint": "STT крајна точка URL", "openAiCompatibleSttApiKey": "API клуч", - "openAiCompatibleSttModel": "Модел", "openAiCompatibleSttExtraHeaders": "Дополнителни заглавија", "openAiCompatibleSttTimeout": "Времетраење (секунди)", "openAiCompatibleSttLanguage": "Јазичен подсетник", @@ -780,18 +776,18 @@ "dashScopeSttRegion": "Регион", "dashScopeSttEnableWords": "Временски ознаки на ниво на збор", "dashScopeSttRegionKeyHint": "Забелешка: API клучевите на Alibaba Cloud Model Studio се специфични за регион - осигурајте се дека избраниот регион одговара на регионот каде што е создаден API клучот (Кина наспроти Меѓународен).", + "googleCloudSttProjectId": "ID на проект (само за Application Default Credentials)", + "googleCloudSttBucketName": "Storage bucket (празно = автоматски)", + "googleCloudSttDynamicBatching": "Динамичко групирање (околу 5x поевтино, без гаранција за доцнење)", "onlineSttApiKeyMissing": "Потребен е API клуч. Внесете го вашиот API клуч и обидете се повторно.", "configurationRequired": "Потребна е конфигурација", "transcriptionError": "Грешка при транскрипција", "transcriptionComplete": "Транскрипцијата е завршена", - "transcribingWithOpenAi": "Транскрибирање со OpenAI сервис...", "unauthorizedApiKey": "Неавторизирано: Неважечки API клуч или недоволни дозволи.", "requestTimeout": "Времетраење на барање: Серверот преволно време одговори.", - "noSegmentsReturned": "Не се вратени сегменти од сервисот за транскрипција.", "openAiCompatibleSttAutoTranscribeOnAudioSelection": "Автоматски транскрибирај при избирање на аудио", "processingResponse": "Обработка на одговор...", "transcriptionFailed": "Транскрипцијата не успеа", - "windowPositionAndSize": "Позиција и големина на прозорецот", "wordsPerMin": "Збори/мин", "pixelWidth": "Ширина во пиксели", "wpm": "Збори/мин", @@ -800,8 +796,6 @@ "xNotFound": "„{0}“ не е пронајдено", "xRequiresAnApiKey": "{0} бара API клуч", "xRequiresAValidUrl": "{0} бара валиден URL (на пр. \"http://...\" или \"https://...\")", - "xSeconds": "{0:0.0##} секунди", - "xSubtitles": "{0:#,###,##0} титлови", "yes": "Да", "copyToClipboard": "Копирај во таблата со исечоци", "playCurrent": "Пушти тековно", @@ -897,10 +891,10 @@ "replace": "_Замени...", "multipleReplace": "_Множествена замена...", "goToLineNumber": "_Оди на број на линија...", + "commandPalette": "Палета со _команди...", "rightToLeftMode": "Р_ежим оддесно‑на‑лево", "modifySelectionDotDotDot": "Измени _избор...", "tools": "_Алатки", - "toolsSelectedLines": "_Алатки (избрани линии)", "adjustDurations": "_Прилагоди времетраења...", "applyDurationLimits": "Примени ограничувања за _времетраење...", "batchConvert": "_Пакетно конвертирање...", @@ -990,6 +984,7 @@ "toggleSelectSubtitleWhilePlayingCurrentlyOff": "Вклучи избор на титл за време на репродукција (тековно: исклучено)", "fillSelectedLinesWithClipboard": "Пополни избрани линии со текст од таблата со исечоци", "beautifyTimeCodes": "Улепши временски кодови...", + "improveTimeCodes": "Подобри временски кодови (присилно порамнување)...", "evenlyDistributeLines": "Рамномерно распредели линии (по знаци/сек)", "mergeContinuationLines": "Спој линии за продолжување...", "snapAllTimesToFrames": "Приклучи ги сите времиња на кадри", @@ -1044,6 +1039,9 @@ "selectCurrentLineWhilePlayingHint": "Избери тековната титра при репродукција {0}", "videoPosition": "Видео позиција {0}", "videoPositionTextBox": "Поле за текст со позиција во видеото {0}", + "toolbarLineBreakX": "Прекин на ред {0} (остатокот од лентата со алатки продолжува во нов ред)", + "initialText": "Почетен текст на избраната линија", + "initialTextHint": "Текстот на избраната линија каков што беше кога линијата беше избрана", "hideWaveformToolbar": "Скриј лентата со алатки {0}", "resetZoomAndSpeed": "Ресетирај зум и брзина на репродукција {0}", "removeBlankLines": "Отстрани празни линии {0}", @@ -1057,6 +1055,15 @@ "textPlayHint": "Пушти тековен титл и запри на крајот {0}", "textPauseHint": "Паузирај репродукција {0}", "textNextHint": "Пушти следен титл и запри на крајот {0}", + "moveSelectedLines": "Помести избрани линии (копчиња назад/напред)", + "moveSelectedLinesAndFollowing": "Помести избрани линии и сите следни (копчиња назад/напред)", + "moveAllLines": "Помести ги сите линии (копчиња назад/напред)", + "moveSelectedLinesBackHint": "Помести избрани линии {0} ms назад {1}", + "moveSelectedLinesForwardHint": "Помести избрани линии {0} ms напред {1}", + "moveSelectedLinesAndFollowingBackHint": "Помести избрани линии и сите следни {0} ms назад {1}", + "moveSelectedLinesAndFollowingForwardHint": "Помести избрани линии и сите следни {0} ms напред {1}", + "moveAllLinesBackHint": "Помести ги сите линии {0} ms назад {1}", + "moveAllLinesForwardHint": "Помести ги сите линии {0} ms напред {1}", "guessStartNoLineSelected": "Погоди почеток: нема избрана линија", "guessStartTimeCodesLocked": "Погоди почеток: временските кодови се заклучени", "guessStartNoWaveform": "Погоди почеток: нема форма на бран", @@ -1078,6 +1085,9 @@ "charactersPerSecond": "Знаци/секунда: {0}", "chooseColumn": "Избери колона", "columnPaste": "Колона вметнување", + "commandPaletteTitle": "Палета со команди", + "commandPaletteSearchPlaceholder": "Пишувај за да бараш команди...", + "commandPaletteNoCommandsFound": "Нема соодветни команди", "createdEmptyTranslation": "Создаден празен превод од тековната титра", "deleteText": "Избриши текст", "deleteTextAndShiftCellsUp": "Избриши текст и помести ќелии нагоре", @@ -1115,18 +1125,20 @@ "noTextInClipboard": "Нема текст во привремената меморија", "oneLineCopiedFromOriginal": "Една линија копирана од оригиналната титра", "oneLineCopiedToOriginal": "Една линија е копирана во оригиналниот титл", + "onlineOnlyVideo": "Видео достапно само онлајн", + "onlineOnlyVideoXSizeYDownloadAndOpen": "Видео датотеката „{0}“ ({1}) е зачувана само онлајн, на пример во Dropbox или iCloud Drive, и мора да се преземе пред да може да се отвори.\n\nДа се преземе и отвори сега?", "oneLineMerged": "Една линија споена", "oneLineSwitched": "Една линија заменета", "overwriteExistingCells": "Презапиши постоечки ќелии", "overwriteOrShiftCellsDown": "Презапиши/помести ќелии надолу", "parsingMatroskaFile": "Парсирам Matroska датотека...", + "readingDvdSubtitles": "Читање DVD титлови...", "pasteFromClipboardDotDotDot": "Залепи од привремена меморија...", "redoPerformed": "Повторно действие извршено", "redoPerformedXActionLeft": "Повторно действие извршено (остани {0} акции)", "removedUnicodeControlCharactersX": "Отстранети Unicode контролни знаци од {0} линии", "removedXBlankLines": "Отстранети {0} празни линии", "replacedXWithYCountZ": "Заменет \"{0}\" со \"{1}\" ({2} појави)", - "replacedXWithYInLineZ": "Заменет \"{0}\" со \"{1}\" во линија {2}", "reversedStartAndEndingsForRightToLeftX": "Реверсирани старт и завршувања за десно-налево во {0} линии", "ruleProfileIsX": "Правилниот профил е сега \"{0}\"", "saveLanguageFile": "Зачувај датотека за јазик", @@ -1134,7 +1146,6 @@ "shiftTextCellsDown": "Помести текстуални ќелии надолу", "singleLineLength": "Должина на линија: ", "speedIsNowX": "Брзината е сега \"{0}\"", - "spellCheckResult": "Проверката на правопис заврши. \n\n• Променети зборови: {0}\n• Прескокнати зборови: {1}", "subtitleGridFormattingX": "Форматирање на табелата: {0}", "subtitleImportedFromMatroskaFile": "Титрата е увезена од Matroska датотека", "textDown": "Текст надолу", @@ -1160,13 +1171,14 @@ "trimmedXLines": "Скратени {0} титра линии", "pastedXLinesOverSelectedLines": "Залепени се {0} линија(и) врз избраните линии", "pastedXOfYLinesOverSelectedLines": "Залепени се {0} од {1} линија(и) врз избраните линии - остатокот не се собра во изборот", + "insertedXLinesFromClipboard": "Вметнати {0} линија(и) од таблата со исечоци", "openOriginalDifferentNumberOfSubtitlesXY": "Оригиналната датотека за титри нема ист број на титри како тековната датотека.\n\n• Оригинални титри: {0}\n• Тековни титри: {1}", - "importXMatchingOriginalLines": "Увези {0} согласни оригинални титри?", "originalTextReadOnly": "Оригинален текст (само за читање)", "originalTextEditMode": "Оригинален текст (режим на уредување)", "originalIsReadOnlyNotSaved": "Оригиналниот титл е референца само за читање и не беше зачуван", "originalIsEmptyNotSaved": "Оригиналниот титл воопшто нема текст и не беше зачуван", "originalIsReadOnlyReference": "Оригиналниот титл е отворен како референца само за читање", + "closeTranslationQuestionXY": "Да се затвори преводот „{0}“?\n\nОригиналниот титл „{1}“ станува титлот што го уредуваш. Самата датотека со превод не се менува.", "allowEditOfOriginalSubtitle": "Дозволи уредување на оригиналниот титл", "showAllOriginalLinesX": "Прикажи ги сите оригинални линии ({0} немаат совпаѓање тука)", "showAllOriginalLinesHint": "Линиите без совпаѓање се прикажани како дополнителни редови, за да видиш што недостига во преводот.", @@ -1188,6 +1200,7 @@ "fileExportedInFormatXToY": "Датотеката е извезена во формат {0} во {1}", "fileExportedInFormatXToFileY": "Датотеката е извезена во формат \"{0}\" во датотека \"{1}\"", "fixedXLines": "Поправени {0} линии", + "toolXChangedYLines": "{0}: променети {1} линии", "transcriptionCompletedWithXLines": "Транскрипцијата заврши со {0} линии", "openSubtitleFileX": "Отвори датотека за титра \"{0}\"?", "replacedXOccurrences": "Заменети {0} појави", @@ -1234,13 +1247,9 @@ "file": { "ebuSaveOptions": { "title": "EBU опции за зачувување", - "generalSubtitleInformation": "Општи информации за титри", "codePageNumber": "Број на кодна страница", "diskFormatCode": "Код за формат на диск", "displayStandardCode": "Код за стандард за приказ", - "colorRequiresTeletext": "Боите бараат телетекст!", - "alignmentRequiresTeletext": "Подесувањето бара телетекст!", - "teletextCharsShouldBe38": "'Максимален број знаци по ред' за телетекст треба да биде 38!", "characterCodeTable": "Табела со знаци", "languageCode": "Код за јазик", "originalProgramTitle": "Оригинален наслов на програм", @@ -1257,7 +1266,6 @@ "maxNumberOfDisplayableRows": "Максимален број редови", "diskSequenceNumber": "Секвенциски број на диск", "totalNumberOfDisks": "Вкупен број на дискови", - "import": "Увези...", "textAndTimingInformation": "Текст и информации за време", "justificationCode": "Код за порамнување", "verticalPosition": "Вертикална позиција", @@ -1274,11 +1282,6 @@ "errors": "Грешки", "errorsX": "Грешки: {0}", "maxLengthError": "Линија {0} го надминува максимумот ({1}) за {2}: {3}", - "textUnchangedPresentation": "Непроменета презентација", - "textLeftJustifiedText": "Текст лево порамнуван", - "textCenteredText": "Текст центриран", - "textRightJustifiedText": "Текст десно порамнуван", - "useBoxForOneNewLine": "Обележете 'Користи рамка околу текстот' за само еден нов ред", "maxLengthErrorTeletextColored": "Линија {0}-{1}: 36 (не {2}) треба да биде максимален број знаци за обоен текст со двојна висина", "maxLengthErrorTeletextWhite": "Линија {0}-{1}: 37 (не {2}) треба да биде максимален број знаци за бел текст со двојна висина", "maxLengthErrorTeletextSingleHeight": "Линија {0}: 38 (не {1}) треба да биде максимален број знаци за бел текст со единечна висина", @@ -1303,22 +1306,29 @@ "formattingDotDotDot": "Форматирање...", "imageBasedSubtitleForEditDotDotDot": "Титра базирана на слика за уредување...", "imageBasedSubtitleForOcrDotDotDot": "Титра базирана на слика за OCR...", + "dvdSubtitlesDotDotDot": "Титлови од DVD (IFO/VOB)...", + "titleImportDvd": "Увези титлови од DVD", + "dvdIfoFile": "IFO датотека", + "dvdVobFiles": "VOB датотеки", + "dvdVideoStandard": "Видео стандард", + "dvdStartRipping": "Започни извлекување", + "dvdRippingAborted": "Извлекувањето е прекинато", + "dvdOpenIfoFile": "Отвори DVD IFO датотека", + "dvdAddVobFiles": "Додај VOB датотеки", + "dvdIfoFiles": "DVD IFO датотеки", + "dvdVobFileType": "DVD VOB датотеки", + "dvdDropHint": "Отвори IFO датотека (VIDEO_TS\\VTS_01_0.IFO) или додај VOB датотеки - или повлечи ги тука", + "dvdNotAnIfoFileX": "{0} не е DVD IFO датотека", + "dvdMenuIfoNoTitleSetsX": "Не се пронајдени сетови на наслови (VTS_xx_0.IFO) покрај {0}", + "dvdEncryptedXOfY": "{0} од {1} пакети со титлови се сè уште CSS шифрирани - овие VOB датотеки се копирани без дешифрирање, па некои слики од титловите ќе бидат искривени.\n\nПрво дешифрирај го DVD-то со алатка за извлекување DVD за чист резултат.\n\nДа се продолжи сепак?", "splitTextAt": "Подели текст на", "blankLines": "Празни линии", "oneLineIsOneSubtitle": "Една линија е една титра", "twoLinesAreOneSubtitle": "Две линии се една титра", "importFilesDotDotDot": "Увези датотеки...", "multipleFiles": "Увези од повеќе текстуални датотеки (една датотека е една титра)", - "importOptions": "Опции за увоз", - "autoSplitText": "Автоматски подели текст", - "lineMode": "Режим на линија", "maxLineLength": "Максимална должина на линија", - "maxLinesPerSubtitle": "Максимум линии по титра", - "minGapBetweenSubtitles": "Минимален празен простор помеѓу титрите", "mergeShortLines": "Спои кратки линии", - "removeLinesWithoutLetters": "Отстрани линии без букви", - "splitAtEndCharsSetting": "Подели при крајни знаци", - "takeTimeFromCurrentFile": "Преземи време од тековната датотека", "fixed": "Фиксирано", "numberOfSubtitlesX": "Број на титри: {0}", "numberOfSubtitlesXForcedY": "Број на титлови: {0} ({1} присилни)", @@ -1330,14 +1340,18 @@ "forcedAlignerProgress": "Порамнување... прозорец {0} од {1}, {2} од {3} линии", "forcedAlignerSetupTitle": "Порамни временски кодови преку присилен порамнувач", "forcedAlignerSetupIntro": "Присилниот порамнувач го совпаѓа текстот на титлот што веќе го имаш со аудиото, без прво да го транскрибира. Долгите видеа се порамнуваат во делови, па секоја должина е возможна.", + "forcedAlignerEndsFromSpeech": "Постави крајни времиња од изолиран говор (бавно)", + "forcedAlignerEndsFromSpeechHint": "Присилниот порамнувач наоѓа каде линијата започнува, а не каде завршува, па крајните времиња обично доаѓаат од времето за читање. Со ова вклучено, музиката и звучните ефекти прво се отстрануваат од аудиото и секоја линија завршува каде што нејзиниот говор утихнува. Трае приближно колку и самото видео со GPU - и многукратно подолго без него.", + "forcedAlignerIsolatingSpeech": "Изолирање говор... {0}", + "forcedAlignerIsolatingSpeechFailed": "Говорот не можеше да се изолира - крајните времиња се поставени од времето за читање.", "forcedAlignerModel": "Модел за порамнување", - "forcedAlignerDownloadEngine": "Преземи / ажурирај мотор..." + "forcedAlignerDownloadEngine": "Преземи / ажурирај мотор...", + "forcedAlignerEstimatedXOfY": "{0} од {1} линии не можеа да се порамнат и се поставени според времето за читање - нивните временски кодови се само проценети. Скриптата веројатно е подолга од она што се зборува во видеото." }, "export": { "exportImagesProfiles": "Извези профили за слики", "leftRightMargin": "Лева/десна маргина", "topBottomMargin": "Горна/долна маргина", - "titleExportBluRaySup": "Извези Blu-ray (sup)", "lineSpacingPercent": "Растојание помеѓу редови %", "paddingLeftRight": "Падинг лево/десно", "paddingTopBottom": "Падинг горе/долу", @@ -1378,10 +1392,21 @@ "titleExportDCinemaInteropPng": "D-Cinema интероп/png", "titleExportDCinemaSmpte2014Png": "D-Cinema SMPTE 2014/png", "imageBasedSubtitleSaved": "Снимката-базиран титл е зачуван", - "titleExportBdnXml": "Извези BDN/xml", "titleExportWebVttThumbnails": "Извези WebVTT (слики)", "fullFrameImage": "Слика со цел кадар", "fullFrameImageHint": "Направи ја секоја слика со големина на видео кадарот, со титлот на своето место", + "stereo3D": "3D", + "stereo3DHint": "За 3D видео: нацртај го титлот еднаш во половината од кадарот за секое око", + "halfSideBySide": "Половина една до друга", + "halfTopBottom": "Половина горе/долу", + "depth3D": "Длабочина", + "depth3DHint": "Колку титлот излегува од екранот - позитивна вредност го приближува, негативна го оддалечува", + "plane3D": "3D-Plane", + "plane3DHint": "Дај му на секој титл длабочина што 3D Blu-ray ја чува за него, од 3D-Plane (.ofs) датотека направена со BD3D2MK3D или OFSExtractor. Длабочината се користи таму каде што 3D-Plane нема", + "openPlane3DTitle": "Отвори 3D-Plane датотека", + "unableToLoadPlane3DX": "Не може да се вчита 3D-Plane: {0}", + "plane3DXDepthYToZ": "{0} (длабочина од {1} до {2})", + "plane3DXNoDepth": "{0} (без вредности за длабочина)", "textEffect": "Текстуален ефект", "textEffectSettingsTitle": "Подесувања за текстуален ефект", "textEffectPreset": "Претходно поставено", @@ -1413,13 +1438,10 @@ "statistics": { "title": "Статистики", "titleWithFileName": "Статистики - {0}", - "generalStatistics": "Општи статистики", - "mostUsed": "Најкористени...", "mostUsedLines": "Најчесто користени линии", "mostUsedWords": "Најчесто користени зборови", "nothingFound": "Ништо не е пронајдено", "numberOfLinesX": "Број на титл линии: {0:#,##0}", - "numberOfFilesX": "Број на титл датотеки: {0:#,##0}", "lengthInFormatXinCharactersY": "Број на знаци како {0}: {1:#,###,##0}", "numberOfCharactersInTextOnly": "Број на знаци само во текст: {0:#,###,##0}", "totalDuration": "Вкупно траење на сите титли: {0:#,##0}", @@ -1460,7 +1482,6 @@ "gapMaximum": "Гап - максимум: {0:#,##0} мс", "gapAverage": "Гап - просек: {0:#,##0.##} мс", "gapExceedingMinimum": "Гап - под минимум ({0:#,##0} мс): {1} ({2:0.00}%)", - "export": "Извези...", "subtitles": "Титлови", "words": "Зборови", "totalDurationShort": "Вкупно времетраење", @@ -1490,7 +1511,22 @@ "restoreAutoBackupFile": "Врати датотека од автоматска резервна копија", "restoreXFromY": "Дали сакате да го вратите \"{0}\" од {1}?", "deleteAllSubtitleBackups": "Дали сакате да избришете сите резервни датотеки за титли?", - "deleteAll": "Избриши сè" + "deleteAll": "Избриши сè", + "subtitles": "Титлови", + "settingsBackupInfo": "Се прави резервна копија на Settings.json кога Subtitle Edit ќе се стартува, најмногу еднаш на секои {0} ден(а) и само ако подесувањата се променети. Се чуваат најновите {1} резервни копии.", + "settingsBackupInfoEveryStart": "Се прави резервна копија на Settings.json секогаш кога Subtitle Edit ќе се стартува, ако подесувањата се променети. Се чуваат најновите {0} резервни копии.", + "settingsBackupOff": "Автоматската резервна копија на подесувањата е исклучена (Опции → Подесувања → Датотека).", + "lastSettingsBackupX": "Последна резервна копија: {0}", + "noSettingsBackupsYet": "Сè уште нема резервни копии на подесувањата", + "openSettingsBackupFolder": "Отвори папка со резервни копии на подесувањата", + "backUpSettingsNow": "Направи резервна копија сега", + "settingsBackedUp": "Направена е резервна копија на тековните подесувања.", + "restoreSettings": "Врати подесувања", + "restoreSettingsFromX": "Дали сакаш да ги замениш тековните подесувања со резервната копија од {0}?\n\nПрво ќе се направи резервна копија на тековните подесувања. Некои промени стапуваат на сила дури по рестартирање на Subtitle Edit.", + "settingsRestored": "Подесувањата се вратени. Рестартирај го Subtitle Edit за сите промени да стапат на сила.", + "settingsRestoreFailed": "Подесувањата не можеа да се вратат: {0}", + "settingsBackupNotValid": "резервната копија не е валидна датотека со подесувања, па тековните подесувања се задржани", + "deleteAllSettingsBackups": "Дали сакаш да ги избришеш сите датотеки со резервни копии на подесувањата?" }, "propertiesDCinema": { "title": "Својства за D-Cinema SMPTE", @@ -1504,22 +1540,16 @@ "editRate": "Скорост на уредување", "timeCodeRate": "Скорост на временски код", "startTime": "Време на старт", - "font": "Фонт", "fontId": "ID на фонт", "fontUri": "URI на фонт", - "generate": "Генерирај", - "fontColor": "Боја на фонт", "chooseColor": "Избери боја", "fontEffect": "Ефект на фонт", "effectColor": "Боја на ефект", - "fontSize": "Големина на фонт", "topBottomMargin": "Горна/долна маргина", "fadeUpTime": "Време за појавување", "fadeDownTime": "Време за исчезнување", - "frames": "Фрејмови", "zPosition": "Z-позиција", - "zPositionHelp": "Позитивните броеви го оддалечуваат текстот, негативните го приближуваат; ако z-позицијата е нула, тогаш е 2D", - "export": "Извези..." + "zPositionHelp": "Позитивните броеви го оддалечуваат текстот, негативните го приближуваат; ако z-позицијата е нула, тогаш е 2D" }, "webVtt": { "setStylesForSelectedLinesTitle": "Постави WebVTT стилови за избраните линии", @@ -1552,20 +1582,66 @@ "compareOnlyInOneFile": "Само во една датотека", "compareTextOrTimeDifference": "Разлика во текст/време", "compareNumberDifference": "Разлика во број", - "ignoreLineBreaks": "Игнорирај преломи на линии", "ignoreWhitespace": "Игнорирај празни знаци", "ignoreWhitespaceHint": "Линиите што се разликуваат само по празни места, табулатори или прекини на ред не се сметаат за различни", "ignoreFormatting": "Игнорирај форматирање", - "onlyLookForDifferencesInText": "Само проверувај разлики во текстот", - "cannotCompareWithImageBasedSubtitles": "Не може да се споредуваат со титли базирани на слика", - "statisticsTitle": "Статистики", "ignoreFormattingHint": "Линиите што се разликуваат само по ознаки за форматирање, како или {\\an8}, не се сметаат за различни", "showOnlyDifferencesInText": "Само разлики во текстот", + "ignoreNumbering": "Игнорирај нумерирање", + "ignoreNumberingHint": "Линиите што се разликуваат само по својот број не се сметаат за различни", + "ignoreTimeDifferenceMs": "Временска толеранција (ms)", + "ignoreTimeDifferenceMsHint": "Почетните и крајните времиња што се разликуваат за толку милисекунди или помалку не се сметаат за различни", + "compareDifferences": "Разлики", + "compareTextDifferences": "Разлики во текстот", + "compareCurrent": "Тековен", + "compareReference": "Референтен", + "compareEditable": "Може да се уредува", + "compareEditableHint": "Промените направени тука се враќаат во титлот кога ќе кликнеш Примени", + "compareReadOnly": "Само за читање", + "compareTakeFromReference": "Земи текст и тајминг од референтниот", + "compareInsertFromReference": "Вметни ја оваа линија во тековниот титл", + "compareDeleteFromCurrent": "Избриши ја оваа линија од тековниот титл", + "compareTakeText": "Земи текст", + "compareTakeTiming": "Земи тајминг", + "compareEditLine": "Уреди линија", + "compareEditHint": "Ctrl+Enter за зачувување, Esc за откажување", + "compareEdited": "Уредено", + "compareOnePendingChange": "1 промена на чекање", + "compareXPendingChanges": "{0} промени на чекање", + "compareDiscardXChanges": "Да се отфрлат {0} промена(и) направени во Сравни?", + "compareXChangesApplied": "Сравни: применети {0} промена(и)", + "compareChangeTextX": "#{0} текст од референтниот", + "compareChangeTimingX": "#{0} тајминг од референтниот", + "compareChangeTextAndTimingX": "#{0} текст и тајминг од референтниот", + "compareChangeEditedX": "#{0} уредено", + "compareChangeInsertedX": "#{0} вметнато", + "compareChangeDeletedX": "#{0} избришано", + "compareSyncPoint": "Точка на синхронизација", + "compareSyncPointHint": "Точка на синхронизација - овие две линии секогаш се прикажуваат како пар, а линиите над и под се порамнуваат посебно", + "compareSyncPickCurrent": "Точка на синхронизација: користи ја оваа тековна линија", + "compareSyncPickReference": "Точка на синхронизација: користи ја оваа референтна линија", + "compareSyncRemove": "Отстрани точка на синхронизација", + "compareClearXSyncPoints": "Исчисти точки на синхронизација ({0})", + "compareSyncCurrentPickedX": "Тековна #{0} е избрана како точка на синхронизација - избери ја соодветната референтна линија и кликни Синхронизирај", + "compareSyncReferencePickedX": "Референтна #{0} е избрана како точка на синхронизација - избери ја соодветната тековна линија и кликни Синхронизирај", + "compareSyncWithCurrentX": "Синхронизирај со тековна #{0}", + "compareSyncWithReferenceX": "Синхронизирај со референтна #{0}", + "compareSyncApply": "Синхронизација", + "compareSyncPointSetXY": "Точката на синхронизација е поставена: тековна #{0} и референтна #{1} сега се прикажуваат како пар", + "compareSyncAlreadyPairedXY": "Тековна #{0} и референтна #{1} веќе се прикажуваат како пар со ист почеток - нема што да се синхронизира", + "compareSyncPointShiftedXYZW": "Точката на синхронизација е поставена: тековни #{0}-#{1} поместени {2} за да започнуваат со референтна #{3}", + "compareChangeSyncShiftXYZ": "#{0}-#{1} поместени {2} (точка на синхронизација)", + "compareTimingDeltaHint": "Колку подоцна (+) или порано (−) референтната линија започнува и завршува во однос на тековната линија", "loadXFromFile": "Вчитај \"{0}\" од датотека", + "compareLoadSavedFile": "Вчитај зачувана датотека", "saveCompareHtmlTitle": "Зачувај HTML за споредба", "pickMatroskaTrackX": "Избери Matroska патека - {0}", "pickTransportStreamTrackX": "Избери трака од транспортен стрим - {0}", + "chapters": "Поглавја", + "dvdVobFilesMissingX": "Недостасуваат VOB датотеки (пронајдени {0}%)", "pickMp4TrackX": "Избери MP4 трака - {0}", + "pickMxfTrackX": "Избери MXF патека - {0}", + "pickMpegTrackX": "Избери MPEG патека - {0}", "rosettaProperties": "Својства за Timed Text Rosetta IMSC", "rosettaFontSize": "Големина на фонт (висина на ред)", "propertyTimeBase": "Временска основа", @@ -1575,7 +1651,6 @@ "propertyDefaultRegion": "Стандарден регион", "propertyStyleAttributeName": "Име на атрибутот за стил", "propertyTimeCodeFormat": "Формат на временски код", - "propertyFileExtension": "Наставка на датотека", "propertyTopOrigin": "Горно потекло", "propertyTopExtent": "Горен опсег", "propertyBottomOrigin": "Долно потекло", @@ -1625,7 +1700,6 @@ "selectionIntersect": "Пресек со селекција" }, "multipleReplace": { - "title": "Повеќе заменувања", "editRule": "Уреди правило", "newRule": "Ново правило", "editCategory": "Уреди категорија", @@ -1642,24 +1716,23 @@ "findWhat": "Што да најдам", "descriptionOptional": "Опис (опционално)", "invalidRegularExpressionX": "Невалиден регуларен израз: {0}", - "regularExpressionTooSlowX": "Регуларниот израз откажа по {0} секунди - правилото е прескокнато, пробај поедноставен образец" + "regularExpressionTooSlowX": "Регуларниот израз откажа по {0} секунди - правилото е прескокнато, пробај поедноставен образец", + "moveToCategory": "Помести во категорија" }, "find": { "searchTextWatermark": "Пребарај текст...", "wholeWord": "Цела реч", - "caseSensitive": "Разликувај големи/мали букви", - "caseInsensitive": "Не разликувај големи/мали букви", "findPrevious": "Најди _претходно", "findNext": "_Најди следно", "replaceAndFindNext": "_Замени & најди следно", "replaceAll": "Замени _сите", - "replaceTitle": "Замени", - "replaceWith": "Замени со", "replaceTextWatermark": "Замени со...", "replaceIn": "Замени/пребарај во", "replaceInTextAndOriginal": "Текст и оригинален текст", "replaceInTextOnly": "Само текст", - "replaceInOriginalOnly": "Само оригинален текст" + "replaceInOriginalOnly": "Само оригинален текст", + "replacedOneOccurrence": "Заменето е едно појавување", + "replacedXOccurrences": "Заменети {0} појавувања" }, "regularExpressionContextMenu": { "wordBoundary": "Граница на збор (\\b)", @@ -1687,6 +1760,7 @@ "xLinesYSubtitles": "{0} линии, {1} титлови", "xLinesYSubtitlesZErrors": "{0} линии, {1} титлови, {2} грешка(и)", "couldNotParseAsX": "Не може да се парсира како {0}", + "readAsXUseAsY": "Изворот не можеше да се прочита како {1}, но можеше да се прочита како {0}.\nДа се користат линиите и да продолжи зачувувањето како {1}?", "selectedXCharactersYLines": "Избрано: {0} знаци во {1} линија(и)", "replacedXOccurrences": "Заменети се {0} појавување(а)", "invalidRegularExpression": "Невалиден регуларен израз", @@ -1701,7 +1775,6 @@ "aiReview": { "title": "AI преглед", "review": "Прегледај", - "stop": "Стоп", "editPrompt": "Уреди промпт...", "editPromptTitle": "Уреди промпт за преглед", "promptInfo": "Инструкции испратени до моделот - {language} се заменува со јазикот на титлот.", @@ -1712,7 +1785,6 @@ "reviewingLineXOfY": "Преглед на линија {0} од {1}...", "reviewDone": "Прегледот е завршен - {0} сугестии во {1} линии", "noIssuesFound": "Не се пронајдени проблеми", - "categoryAll": "Сите", "categorySpelling": "Правопис", "categoryGrammar": "Граматика", "categoryPunctuation": "Интерпункција", @@ -1724,8 +1796,23 @@ "largeChangeWarning": "Голема промена - изгледа како препишување, прегледајте внимателно", "mismatchWarning": "Не наликува на оригиналот - можеби припаѓа на друга линија", "engineError": "AI моторот не е достапен: {0}", - "setupHint": "Прво изберете мотор и модел во Алатки -> AI преглед.", - "playCurrentLineHint": "Пушти ја избраната линија во видео плеерот и паузирај на нејзиниот крај (може и со двоен клик на линија)" + "playCurrentLineHint": "Пушти ја избраната линија во видео плеерот и паузирај на нејзиниот крај (може и со двоен клик на линија)", + "editAfterHint": "Кликни на текстот на избраниот ред за да ја уредиш поправката пред да ја примениш (Enter зачувува, Escape откажува)", + "template": "Шаблон", + "templatePlaceholder": "Избери шаблон што ќе го замени промптот", + "templateStandard": "Стандардна лектура", + "templateSpeechToText": "Чистење по говор во текст", + "templateMachineTranslation": "Поправка на машински превод", + "templateSdh": "SDH / за лица со оштетен слух", + "referenceContext": "Референтен контекст", + "referenceContextInfo": "Опционално. Имиња, ликови, речник на поими, синопсис или стилски белешки - моделот го смета ова за меродавно за имиња и поими. Се испраќа со секоја серија.", + "referenceContextPlaceholder": "Имиња: Каито, тетка Меи, д-р Холис\nПоими: Шинкансен, warp јадро\nСинопсис: Каито патува во Токио за да го најде својот исчезнат татко.", + "generateContext": "Генерирај со AI", + "generateContextHint": "Дозволи избраниот AI мотор да го прочита титлот и да подготви имиња, поими и синопсис - провери го резултатот пред употреба", + "generatingContextPartXOfY": "Читање дел {0} од {1}...", + "generatingContextSynopsis": "Пишување синопсис...", + "replaceContextQuestion": "Да се замени тековниот референтен контекст?", + "noContextGenerated": "AI моторот не врати употреблива референтна информација." }, "aiAssistant": { "title": "AI помошник", @@ -1743,22 +1830,14 @@ "showReasoning": "Прикажи го расудувањето на моделот" }, "fixCommonErrors": { - "title": "Поправи чести грешки", - "step1": "Чекор 1/2 - Изберете кои грешки да се поправат", - "whatToFix": "Што да се поправи", - "example": "Пример", - "selectAll": "Избери сè", - "inverseSelection": "Инвертирај селекција", "back": "< &Назад", "next": "&Следно >", - "step2": "Чекор 2/2 - Потврди поправки", "fixes": "Поправки", "ruleDetailsDotDotDot": "Детали за правилото...", "showOnlyThisRule": "Прикажи само поправки од ова правило", "appliedRulesForLineX": "Применети правила за линијата {0}", "ruleX": "Правило: {0}", "log": "Лог", - "function": "Функција", "removedEmptyLine": "Отстрани празна линија", "removedEmptyLineAtTop": "Отстрани празна линија на почеток", "removedEmptyLineAtBottom": "Отстрани празна линија на крај", @@ -1779,13 +1858,13 @@ "removeLineBreaksPixelWidth": "Обединете титли кои можат да се сместат во една линија (по пиксел ширина)", "fixUppercaseIInsideLowercaseWords": "Поправи голем 'i' во мали зборови (OCR грешка)", "fixDoubleApostrophes": "Поправи два апострофа ('') во единичен наводник (\")", + "fixMisreadQuotes": "Поправи апострофи погрешно прочитани како двојни наводници (OCR, англиски)", "addPeriods": "Додај точка после линии каде следната започнува со голема буква", "startWithUppercaseLetterAfterParagraph": "Започни со голема буква после пасус", "startWithUppercaseLetterAfterPeriodInsideParagraph": "Започни со голема буква после точка во пасус", "startWithUppercaseLetterAfterColon": "Започни со голема буква после двоеточие/точка-запирка", "fixLowercaseIToUppercaseI": "Поправи самостојно мало 'i' во 'I' (англиски)", "fixCommonOcrErrors": "Поправи честите OCR грешки (со листа за замена)", - "commonOcrErrorsFixed": "Чести OCR грешки поправени (употребена датотека OcrReplaceList): {0}", "removeSpaceBetweenNumber": "Отстрани празно место помеѓу броеви", "breakDialogsOnOneLine": "Раздели дијалози на една линија", "removeDialogFirstInNonDialogs": "Отстрани почетна цртичка во прва линија за не-дијалози", @@ -1802,18 +1881,17 @@ "fixCommasExample": ",, -> ,", "removeLineBreaksExample": "Foo
bar! -> Foo bar!", "fixDoubleApostrophesExample": "''Has double single quotes'' -> \"Has single double quote\"", + "fixMisreadQuotesExample": "\"Hello' -> \"Hello\"", "addPeriodsExample": "Hello world -> Hello world.", "startWithUppercaseLetterAfterParagraphExample": "p1: Foobar! || p2: foobar! -> p1: Foobar! || p2: Foobar!", "startWithUppercaseLetterAfterPeriodInsideParagraphExample": "Hello there! how are you? -> Hello there! How are you?", "startWithUppercaseLetterAfterColonExample": "Speaker: hello world! -> Speaker: Hello world!", "removeHyphensSingleLineExample": "- Foobar. -> Foobar.", "fix3PlusLinesExample": "Foo
bar
baz! -> Foo bar baz!", - "xMissingQuotesAdded": "Недостасувачки наводници додадени: {0}", "fix3PlusLine": "Поправи титл со повеќе од две линии", "fix3PlusLines": "Поправи титли со повеќе од две линии", "analysing": "Анализирање...", "nothingToFix": "Нема што се поправува :)", - "fixesFoundX": "Пронајдени поправки: {0}", "xFixesApplied": "Применети поправки: {0}", "xFixesYSelected": "{0} поправки - {1} избрани", "nothingFixableBut": "Нешто не може автоматски да се поправи. Титлот содржи грешки - погледнете лог за детали", @@ -1822,7 +1900,6 @@ "fixFirstLetterToUppercaseAfterParagraph": "Поправи првата буква во голема после пасус", "mergeShortLine": "Спои кратка линија (една реченица)", "mergeShortLineAll": "Спои кратка линија (сите освен дијалози)", - "unbreakShortLinePixelWidth": "Обезбеди кратка линија (по пиксел ширина)", "breakLongLine": "Раздели долга линија", "fixLongDisplayTime": "Поправи долг период на приказ", "fixInvalidItalicTag": "Поправи невалидна италичка ознака", @@ -1845,7 +1922,6 @@ "fixUppercaseIInsideLowercaseWord": "Поправи големо 'i' во мали збор", "fixMissingPeriodAtEndOfLine": "Додај недостасувачка точка на крајот на линијата", "refreshFixes": "Освежи достапни поправки", - "applyFixes": "Примени избрани поправки & затвори", "autoBreak": "Авто &бр", "unbreak": "&Разбери", "fixDoubleDash": "Поправи '--' -> '...'", @@ -1864,8 +1940,6 @@ "fixSpaceBetweenNumbersExample": "1 100 -> 1100", "fixDialogsOneLineExample": "Hi John! - Hi Ida! -> Hi John!
- Hi Ida!", "removeDialogFirstInNonDialogsExample": "- How are you? -> How are you?", - "selectDefault": "Избери стандардно", - "setDefault": "Постави тековни поправки како стандард", "fixContinuationStyleX": "Поправи стил за продолжување: {0}", "fixUnnecessaryLeadingDots": "Отстрани непотребни почетни точки", "fixCommonErrorsProfiles": "Профили за поправка на честите грешки", @@ -1873,7 +1947,6 @@ "searchRulesDotDotDot": "Пребарај правила...", "goToApplyFixes": "Оди на примена на поправки", "backToFixList": "Назад кон листата со поправки", - "applyFixesAndClose": "Примени поправки & затвори", "fixCommonOcrErrorsStep2": "Поправи честите грешки, чекор 2 (примени поправки)", "fixCommonOcrErrorsStep2FixesFoundX": "Поправи честите грешки, чекор 2 - пронајдени поправки: {0}", "action": "Акција", @@ -1886,18 +1959,11 @@ "adjustDurations": { "title": "Прилагоди траења", "adjustVia": "Прилагоди преку", - "seconds": "Секунди", - "percent": "Процент", "recalculate": "Пресметај повторно", "addSeconds": "Додај секунди", - "setAsPercent": "Постави како процент од траењето", "note": "Забелешка: Временскиот приказ нема да се преклопи со стартот на следниот текст", "fixed": "Поправено", - "milliseconds": "Милисекунди", "extendOnly": "Само прошири", - "enforceDurationLimits": "Наложи минимум и максимум траење", - "checkShotChanges": "Не ги проширувај над промените на сцените", - "batchCheckShotChanges": "Почитувај промените на сцените (ако постојат)", "recalculateRequiresOcrNote": "Повторното пресметување не е достапно — еден или повеќе титлови немаат OCR текст. Прво извршете OCR." }, "applyDurationLimits": { @@ -1905,6 +1971,8 @@ "fixMinDurationMs": "Поправи минимум траење (ms)", "doNotGoPastShotChange": "Не преминувај преку промена на сцена", "fixMaxDurationMs": "Поправи максимум траење (ms)", + "fixMinDurationFrames": "Поправи минимално времетраење (кадри)", + "fixMaxDurationFrames": "Поправи максимално времетраење (кадри)", "minimumDurationMilliseconds": "Минимално времетраење (милисекунди):", "maximumDurationMilliseconds": "Максимално времетраење (милисекунди):", "maxDurationShouldBeHigherThanMinDuration": "Максимум траење треба да биде повисоко од минимум траење", @@ -1924,12 +1992,8 @@ }, "beautifyTimeCodes": { "title": "Улепши временски кодови", - "beautifySettings": "Поставки за улепшување", "snapToFrames": "Прилагоди на фрејмови", - "frameGap": "Празнина помеѓу фрејмови", "minDuration": "Минимум траење (ms)", - "shotChangeThreshold": "Праг за промена на сцена (ms)", - "shotChangeOffset": "Поместување на промена на сцена (фрејмови)", "original": "Оригинал", "beautified": "Улепшен", "subtitlesCount": "Титлови", @@ -1958,10 +2022,76 @@ "timeCodesExtractFailed": "Не може да се прочитаат временските кодови од видеото", "timeCodesHint": "Прилепи ги временските точки до вистинските времиња на кадрите во видеото, наместо да се претпоставува постојана брзина на кадри. Бара едно читање на целото видео; резултатот се кешира." }, + "improveTimeCodes": { + "title": "Подобри временски кодови (присилно порамнување)", + "aligner": "Порамнувач", + "alignerHint": "Моделот што ја слуша секоја линија во аудиото. Најдобрите избори за јазикот на титлот се наведени први.", + "maxShift": "Макс. поместување (секунди)", + "maxShiftHint": "Колку титлот може да биде несинхронизиран. Линија чиј почеток би се поместил подалеку останува како што е и се означува. Без оглед на вредноста, поединечна линија што сака да се помести повеќе од половина секунда подалеку од линиите околу неа не се поместува слепо: ги следи соседите кога сите тие се поместиле, а инаку се нуди нештиклирана за да ја провериш.", + "adjustStartTimes": "Прилагоди почетни времиња", + "adjustEndTimes": "Прилагоди крајни времиња", + "isolateSpeech": "Прво изолирај го говорот (бавно)", + "isolateSpeechHint": "Ги отстранува музиката и звучните ефекти пред порамнувањето, за порамнувачот да го слуша само дијалогот. Помага кај линии изговорени преку гласна музика или акција, но трае приближно колку и самото аудио со GPU - и многукратно подолго без него.", + "isolatingSpeech": "Изолирање говор... (ова трае некое време)", + "isolateSpeechFailed": "Говорот не можеше да се изолира - порамнето е според оригиналното аудио.", + "checkWithSpeechToText": "Провери со говор во текст", + "checkWithSpeechToTextHint": "Го транскрибира и аудиото и го проверува секое поместување според тоа каде зборовите навистина се слушнати. Големите поместувања што говорот ги потврдува се штиклираат за тебе, а поместувањата подалеку од местото каде што линијата се слуша остануваат нештиклирани за да ги провериш. Исто така покажува колку од секоја линија е слушнато - мал удел значи дека текстот се разликува од она што се зборува. Титл што е повеќе несинхронизиран од макс. поместување - константно отстапување, лизгање поради неусогласена брзина на кадри или скокови каде што биле исечени сцени - прво се синхронизира според слушнатите зборови. Користи Crisp ASR Parakeet, што трае неколку минути за долгометражен филм со GPU.", + "checkWithSpeechToTextNotAvailableX": "Проверката со говор во текст не е достапна за {0}", + "transcribingToCheck": "Транскрибирање за проверка на порамнувањето...", + "speechToTextFailed": "Говор во текст не успеа - временските кодови не се проверени според него.", + "speechToTextX": "Говор во текст: {0}", + "speechToTextConfirmedXDisputedY": "Говор: {0} потврдени, {1} оспорени", + "hardlyHeardX": "Говор во текст слушна само {0}% од зборовите на титлот. Дали титлот е на јазикот што се зборува? Порамнувачот не може да постави превод.", + "heard": "Слушнато", + "heardHint": "Колку од линијата слушнал говор во текст. Мал удел значи дека текстот се разликува од она што се зборува - таму порамнувачот најверојатно греши.", + "statusConfirmedBySpeech": "Потврдено од говорот", + "statusDisputedBySpeech": "Слушнато каде што беше", + "statusMovedWithSync": "Поместено со синхронизацијата", + "syncedFirstXY": "Прво синхронизирано: {0} s на почетокот, {1} s на крајот", + "showSpeechOnly": "Прикажи само говор", + "showSpeechOnlyHint": "Нацртај ја порамнетата форма на бран од аудиото без музика и звучни ефекти; оригиналната форма на бран останува каква што е, за споредба. Достапно откако говорот ќе се изолира - тука или со „Прикажи само говор“ во формата на бран во главниот прозорец.", + "align": "Порамни", + "original": "Оригинал", + "aligned": "Порамнето", + "intro": "Избери порамнувач и притисни „Порамни“ за да се слушне секоја линија во аудиото и да се затегнат нејзините временски кодови.", + "extractingAudio": "Извлекување аудио...", + "aligningBatchXOfY": "Порамнување... серија {0} од {1}", + "extractAudioFailed": "Аудиото не можеше да се извлече со ffmpeg.", + "modelWillBeDownloaded": "{0} ќе биде преземен", + "summaryXRetimedYKeptZSkipped": "Повторно тајмирани: {0} · Задржани: {1} · Без говор: {2}", + "meanShiftX": "Просечно поместување: {0} ms", + "toCheckX": "За проверка: {0}", + "largeMovesToCheckX": "{0} линија(и) би се поместиле многу сами по себе и се оставени нештиклирани - слушни ги (F5 ја пушта порамнетата линија, Shift+F5 оригиналната) и штиклирај ги оние што се точни.", + "linesToCheckX": "{0} линија(и) се оставени нештиклирани за проверка - слушни ги (F5 ја пушта порамнетата линија, Shift+F5 оригиналната) и штиклирај ги оние што се точни.", + "changeXOfY": "Промена {0} од {1}", + "previousChange": "Претходна промена", + "nextChange": "Следна промена", + "startShift": "Почеток", + "endShift": "Крај", + "statusRetimed": "Повторно тајмирано", + "statusMovedWithNeighbours": "Поместено со соседите", + "statusLargeMoveUnconfirmed": "Големо поместување - слушни, штиклирај за примена", + "statusUnchanged": "Веќе на место", + "statusNoSpeech": "Без говор", + "statusShiftTooLarge": "Задржано - премногу големо поместување", + "statusFailed": "Задржано - порамнувачот не успеа", + "statusAdjustedByHand": "Рачно прилагодено", + "alignedWaveformHint": "Повлечи линија за да ја поместиш или повлечи ги нејзините рабови за да го смениш почетокот или крајот.", + "playLine": "Пушти линија", + "undoAdjustment": "Врати прилагодување", + "playPause": "Пушти / пауза", + "playAligned": "Порамнето", + "playAlignedHint": "Пушти ја избраната линија со нејзините порамнети временски кодови", + "apply": "Примени", + "applyHint": "Отштиклирај линија за да ги задржиш нејзините оригинални временски кодови. Двоен клик на линија за да ја пуштиш. Линиите може и да се поместуваат или да им се менува големината во порамнетата форма на бран." + }, "bridgeGaps": { - "title": "Мост за празнини", "bridgeGapsSmallerThan": "Мост за празнини помали од (ms)", "minGap": "Минимум празнина (ms)", + "bridgeGapsSmallerThanFrames": "Пополни празнини помали од (кадри)", + "minGapFrames": "Минимална празнина (кадри)", + "calculateBridgeGapsSmallerThanDotDotDot": "Пресметај граница за пополнување празнини од брзина на кадри...", + "useXMsAsBridgeGapsSmallerThan": "Да се пополнат празнини помали од „{0}“ милисекунди?", "numberOfSmallGapsBridgedX": "Број на мостови за малите празнини: {0}", "percentFoPrevious": "Празнина за претходната (%)", "gapChange": "Промена на празнина" @@ -1973,8 +2103,6 @@ "sortBy": { "title": "Подреди титлите", "sortOrder": "Редослед на подредување", - "sortByNumber": "Број", - "sortByStartTime": "Време на старт", "sortByEndTime": "Време на крај" }, "batchConvert": { @@ -1997,6 +2125,7 @@ "addColor": "Додај боја", "deleteLinesWithSpecificActorsOrStyles": "Избриши линии со одредени актери или стилови (оддели со запирка)", "useSourceStylesIfPossible": "Користи стилови од изворот ако е можно", + "keepSourceEmbeddedFonts": "Задржи вградени фонтови од изворот (подножје)", "editStyles": "Уреди стилови", "editProperties": "Уреди својства", "editAttachments": "Уреди прилози", @@ -2017,7 +2146,6 @@ "assaChangeStyleFromStyle": "Смени стил од", "assaChangeStyleToStyle": "во", "assaChangeStyleImportStyle": "Увези стил...", - "assaChangeStyleImportedX": "Увезено: {0}", "assaChangeStyleTrimUnusedStyles": "Отсечи неупотребени стилови", "assaChangeStylePropertiesTitle": "Промени својства на стил", "assaChangeStylePropertiesInfo": "Ги менува избраните полиња во секој стил во датотеката, без да ги менува самите стилови.", @@ -2029,13 +2157,52 @@ "convertColorsToDialogReBreakLines": "Прекини ги линиите повторно", "snapTimeCodesToFramesInfo": "Го заокружува секое почетно и крајно време до најблискиот кадар. Брзината на кадри се чита од видео датотека со исто име како датотеката со титл, освен ако не е избрана фиксна брзина на кадри.", "addFolderDotDotDot": "Додај папка...", + "addFolderRecursiveDotDotDot": "Додај папка рекурзивно...", "selectFolderToConvert": "Избери папка со датотеки за конвертирање", "includeSubfolders": "Вклучи потпапки при додавање папка", + "videoFilesWhenAddingFolder": "Видео датотеки (mkv, mp4, ts...) при додавање папка", + "videoFilesAsk": "Прашај", + "videoFilesInclude": "Додај ги нивните патеки со титлови", + "videoFilesSkip": "Прескокни", + "folderContainsXVideoFiles": "Папката содржи {0} видео датотека(и) (mkv, mp4, ts...).\n\nДа се додадат и нивните вградени патеки со титлови или само датотеките со титлови?", + "addVideoFiles": "Додај видео датотеки", + "skipVideoFiles": "Само датотеки со титлови", + "doNotAskAgainVideoFiles": "Не прашувај повторно (смени во поставките за групно конвертирање)", + "settingsSectionOutput": "Излез", + "settingsSectionAddingFiles": "Додавање датотеки", + "settingsSectionImageBasedSubtitles": "Титлови базирани на слики (OCR)", + "settingsSectionOther": "Друго", "keepSourceFileTimestamp": "Задржи го датумот/времето на изворната датотека врз излезните датотеки", - "scanningFolderX": "Скенирање на {0}..." + "preventSleepWhileConverting": "Спречи компјутерот да заспие додека конвертира", + "scanningFolderX": "Скенирање на {0}...", + "transportStreamSettings": "Поставки за Transport Stream", + "transportStreamSettingsDotDotDot": "Поставки за Transport Stream...", + "transportStreamSettingsInfo": "Се применува на патеки со титлови извлечени од Transport Stream датотеки (.ts/.m2ts). Позицијата и големината на видеото влијаат само на DVB патеки со слики извезени во формат базиран на слики.", + "transportStreamOverrideXPosition": "Пребриши ја оригиналната X позиција", + "transportStreamOverrideYPosition": "Пребриши ја оригиналната Y позиција", + "transportStreamOverrideVideoSize": "Пребриши ја оригиналната големина на видеото", + "transportStreamBottomMargin": "Долна маргина", + "transportStreamFileNameEnding": "Завршеток на име на датотека", + "transportStreamFileNameEndingInfo": "Се додава пред екстензијата за секоја извлечена патека. Остави празно за да се користи вообичаениот постфикс за јазик.", + "transportStreamOnlyTeletext": "Само телетекст", + "transportStreamGetSizeFromVideo": "Земи големина од видеото...", + "twoLetterLanguageCodeUppercase": "Двобуквен код на јазик (големи букви)", + "threeLetterLanguageCodeUppercase": "Трибуквен код на јазик (големи букви)", + "formatContainsDotDotDot": "Форматот содржи...", + "extensionIsDotDotDot": "Екстензијата е...", + "statusIsError": "Статусот е грешка", + "forcedTracksOnly": "Само присилни патеки", + "xConvertedYFailedInZ": "{0:#,###,##0} конвертирани, {1:#,###,##0} неуспешни за {2}", + "xNotProcessed": "{0:#,###,##0} необработени", + "removeUnicodeControlCharactersTitle": "Отстрани Unicode контролни знаци", + "removeUnicodeControlCharactersInfo": "Ги отстранува невидливите Unicode контролни знаци за насока (LRM, RLM, LRE, RLE, PDF, LRO и RLO) од текстот и ги заменува непрекинливите празни места со обични празни места.", + "preset": "Предлошка", + "savePresetDotDotDot": "Зачувај предлошка...", + "deletePreset": "Избриши предлошка", + "presetName": "Име на предлошка", + "deletePresetX": "Да се избрише предлошката „{0}“?" }, "changeCasing": { - "title": "Смени регистар", "fixNames": "Поправи имиња", "namesX": "Имиња: {0:#,##0}", "hitsX": "Погодоци: {0:#,##0}", @@ -2046,9 +2213,7 @@ "allUppercase": "Сите големи букви", "allLowercase": "Сите мали букви" }, - "changeFormatting": { - "title": "Смени форматирање" - }, + "changeFormatting": {}, "convertActors": { "title": "Конвертирај актери", "convertActorFrom": "Конвертирај актера од", @@ -2077,18 +2242,12 @@ "outputFormatAssa": "Advanced Sub Station Alpha (.ass)", "style1": "Стил 1", "style2": "Стил 2", - "outlineWidth": "Ширина на контура", - "shadowWidth": "Ширина на сенка", - "alignTop": "Горе", - "alignBottom": "Долу", - "merge": "_Спој", - "pleaseLoadTwoSubtitles": "Ве молиме вчитајте два титла" + "merge": "_Спој" }, "splitSubtitle": { "title": "Раздели титл", "numberOfEqualParts": "Број на еднакви делови", "saveSplitParts": "_Зачувај разделени делови", - "subtitleSplitIntoXParts": "Титлот е разделен на {0} делови.", "xPartsSavedInFormatYToFolder": "{0} делови зачувани во формат {1} во папка:" }, "splitBreakLongLines": { @@ -2114,13 +2273,14 @@ "onlyContinuationLines": "Само линии за продолжување" }, "mergeLinesWithSameText": { - "title": "Спој линии со истиот текст", "maxMsBetweenLines": "Максимум милисекунди помеѓу линии", - "includeIncrementingLines": "Вклучи линии со растечки текст" + "maxFramesBetweenLines": "Макс. кадри помеѓу линиите", + "includeIncrementingLines": "Вклучи линии со растечки текст", + "includeRollUpCaptions": "Вклучи титлови што се тркалаат нагоре (лизгачки)" }, "mergeLinesWithSameTimeCodes": { - "title": "Спој линии со исти временски кодови", "maxMsDifference": "Максимална разлика (милисекунди)", + "maxFramesDifference": "Макс. разлика (кадри)", "makeDialog": "Направи дијалози" }, "netflixCheckAndFix": { @@ -2143,7 +2303,6 @@ "shotChanges": "Промени на сцените", "startNumberSpellOut": "Почетен број спелуван", "textforHiUseBrackets": "Текст за HI, користи загради", - "frameRate": "Брзина на кадарот", "twoFrameGrap": "Два кадарски празнина", "whiteSpace": "Бела празнина", "childrensProgram": "Детска програма", @@ -2181,7 +2340,6 @@ "italicsFixed": "Италици поправени", "italicsNotAllowed": "Италици не се дозволени", "bridgeGapsXToYFrames": "3-{0} кадри празнина => 2 кадри празнина", - "startNumberSpellOutComment": "Кога бројот започнува реченица, секогаш треба да се спелува", "maximumXCharactersPerSecond": "Максимум {0} карактери во секунда" }, "imageBasedEdit": { @@ -2191,10 +2349,8 @@ "resizeImagesDotDotDot": "Промени големина на слики...", "adjustBrightnessDotDotDot": "Прилагоди осветленост...", "adjustAlphaDotDotDot": "Прилагоди алфа...", - "centerHorizontally": "Центрирај хоризонтално", "cropImages": "Одсечи слики", "importTimeCodes": "Увези временски кодови...", - "setTextForSubtitle": "Постави текст за титула", "screenWidth": "Ширина на екран", "screenHeight": "Висина на екран", "alphaThresholdInfo": "Пиксели со алфа под прагот стануваат целосно транспарентни", @@ -2224,15 +2380,31 @@ "letterboxCustom": "Прилагодена висина на лента", "barHeightPx": "Висина на лента (px)", "titleSafePercent": "Безбедна зона за наслов (%)", + "moveCaptions": "Помести титлови", + "moveCaptionsDotDotDot": "Помести титлови...", + "moveCaptionsTo": "Помести титлови", + "moveCaptionsIntoBars": "Во letterbox лентите (надвор од сликата)", + "moveCaptionsIntoPicture": "Во сликата (надвор од лентите)", + "moveCaptionsOffset": "Отстапување од работ (px)", + "letterboxBarHeightXPxYOfZCaptionsWillMove": "Висина на лентата: {0} px - {1} од {2} титлови ќе се поместат", + "removeFadeInOut": "Отстрани затемнување внатре/надвор", + "removeFadeInOutXLinesRemoved": "{0} линии со затемнување споени во линиите на кои припаѓаат", + "removeFadeInOutNothingFound": "Не се пронајдени линии со затемнување внатре/надвор", "positionSummary": "{0}×{1} - {2} титлови - висина на лента: {3} px", "xInActivePicture": "{0} во сликата", "xInTopBar": "{0} во горната лента", "xInBottomBar": "{0} во долната лента", "noImageSubtitlesLoaded": "Нема вчитани титлови базирани на слики", - "imageBasedFormatNotSupported": "Форматот на титл базиран на слика не е пронајден/поддржан." + "imageBasedFormatNotSupported": "Форматот на титл базиран на слика не е пронајден/поддржан.", + "frameRateDeclaredInFile": "Брзина на кадри наведена во Blu-ray sup датотеката", + "frameRateDetectedFromTimeCodesX": "Брзина на кадри откриена од временските кодови на Blu-ray sup (датотеката наведува {0})", + "frameRateFromVideo": "Брзина на кадри на видеото", + "frameRateCurrent": "Тековна брзина на кадри", + "frameRateSetManually": "Рачно поставена брзина на кадри", + "frameRateUsageInfo": "Извозите во Blu-ray sup, BDN XML, DOST и FCP се запишуваат со оваа брзина на кадри. Не менува никакви времиња - за тоа користи „Смени брзина на кадри“.", + "useVideoFrameRateXInsteadOfY": "Брзината на кадри на видеото е {0}, но титлот е поставен на {1}.\n\nДа се користи брзината на кадри на видеото ({0})?" }, "removeTextForHearingImpaired": { - "title": "Отстрани текст за лица со оштетен слух", "interjections": "Интерјекци", "skipIfStartWith": "Прескокни ако започнува со", "removeTextBetween": "Отстрани текст меѓу", @@ -2251,7 +2423,6 @@ "removeInterjections": "Отстрани интерјекци", "linesFoundX": "Пронајдени линии: {0}" }, - "pickAlignmentTitle": "Избери порамнување", "removeUnicodeCharacters": { "title": "Отстрани/замени Unicode знаци", "charactersFoundX": "Пронајдени Unicode знаци: {0}", @@ -2261,13 +2432,13 @@ "pickFontNameInstalledFonts": "Инсталирани фонтови", "pickFontNameCollectedFonts": "Собрани фонтови", "colorPickerTitle": "Избери боја", + "colorPickerBrightness": "Осветленост", "filterLayersTitle": "Филтрирај слоеви за приказ", "filterLayersHideFromWaveform": "Скриј од форма на бран/спектрограма", "filterLayersHideFromSubtitleGrid": "Скриј од мрежата за титули", "filterLayersHideFromVideoPreview": "Скриј од преглед на видеото", "pickSubtitleFormat": "Избери формат за титула", "pickSubtitleFormatImageBasedNoPreview": "Формат на титл базиран на слики.\nБитмапите се генерираат при конвертирање - нема преглед на текст.", - "pickLayerTitle": "Постави слој", "recentColors": "Последни бои", "beautifyTimeCodesProfile": { "title": "Уреди профил за улепшување на временски кодови", @@ -2275,7 +2446,11 @@ "presetDefault": "Стандардно", "presetNetflix": "Netflix", "presetSdi": "SDI", - "general": "Општо", + "saveAsProfile": "Зачувај како профил...", + "deleteProfile": "Избриши профил", + "profileName": "Име на профил", + "overwriteProfileX": "Профил со име „{0}“ веќе постои. Да се презапише?", + "deleteProfileX": "Да се избрише профилот „{0}“?", "gap": "Празнина", "gapSuffix": "кадри (исто така ги ажурира полињата за празнина различни од нула подолу)", "milliseconds": "ms", @@ -2312,9 +2487,7 @@ "columnMerge": "Спој", "columnFirst": "Прва линија", "columnSecond": "Втора линија", - "columnMerged": "Споено", - "selectAll": "Избери сѐ", - "inverseSelection": "Инвертирај избор" + "columnMerged": "Споено" }, "fixCommonErrorsSettingsTitle": "Подесувања за поправка на чести грешки" }, @@ -2359,59 +2532,51 @@ "correctWordsX": "Точни зборови: {0}", "namesX": "Имиња: {0}", "addedToDictionaryX": "Додадени во речникот: {0}", - "doNotShowThisAgain": "Не ја прикажувај повторно оваа порака" + "doNotShowThisAgain": "Не ја прикажувај повторно оваа порака", + "editUseAlwaysList": "Уреди листа „Користи секогаш“...", + "useAlwaysListTitle": "Проверка на правопис - листа „Користи секогаш“", + "useAlwaysListHeader": "Листа „Користи секогаш“", + "useAlwaysListDescription": "Зборови што проверката на правопис ги заменува автоматски, зачувани со „Смени сѐ“ и „Користи секогаш“. Парот се користи само кога зборот е погрешно напишан.", + "useAlwaysWord": "Погрешно напишан збор", + "xPairs": "{0:#,##0} парови", + "xUnusedPairs": "{0:#,##0} никогаш користени", + "useAlwaysUnusedHint": "Никогаш користен - овој збор е правилно напишан (или е име или кориснички збор), па проверката на правопис никогаш не го означува", + "removeUnused": "Отстрани некористени", + "useAlwaysEmpty": "Сè уште нема парови „Користи секогаш“ за овој јазик", + "useAlwaysEmptyHint": "Паровите се додаваат кога ќе кликнеш „Смени сѐ“ или „Користи секогаш“ во проверката на правопис.", + "useAlwaysNoMatch": "Ниту еден пар не одговара на пребарувањето", + "useAlwaysSameWord": "Замената мора да се разликува од зборот.", + "useAlwaysRememberOff": "„Запомни ја листата 'Користи секогаш'“ е исклучено во Подесувања, па проверката на правопис не ја користи ниту ја зачувува оваа листа." }, "video": { "burnIn": { "title": "Создади видео со вграден титул", - "infoAssaOff": "Забелешка: Поддржано е напредно стилизирање за Advanced SubStation Alpha.", - "infoAssaOn": "Забелешка: Ќе се користи напредно стилизирање за Advanced SubStation Alpha :)", - "xGeneratedWithBurnedInSubsInX": "\"{0}\" генериран со вграден титул во {1}.", - "timeRemainingMinutes": "Преостанато време: {0} минути", - "timeRemainingOneMinute": "Преостанато време: Еден минут", - "timeRemainingSeconds": "Преостанато време: {0} секунди", - "timeRemainingAFewSeconds": "Преостанато време: Неколку секунди", - "timeRemainingMinutesAndSeconds": "Преостанато време: {0} минути и {1} секунди", - "timeRemainingOneMinuteAndSeconds": "Преостанато време: Еден минут и {0} секунди", - "targetFileName": "Име на целен фајл: {0}", "targetFileSize": "Големина на целен фајл (потребно е 2-степенно енкодирање)", "fileSizeMb": "Големина на фајлот во MB", "matchSourceVideoSize": "Совпадни со големината на изворното видео", - "passX": "Пас {0}", - "encoding": "Кодирање", "bitRate": "Битска брзина", "totalBitRateX": "Вкупна битска брзина: {0}", "sampleRate": "Фреквенција на примерок", "audio": "Аудио", - "stereo": "Стерео", "preset": "Профил", + "tune": "Tune", "pixelFormat": "Формат на пиксели", "crf": "CRF", - "tuneFor": "Прилагоди за", - "alignRight": "Подреди надесно", - "getStartPosition": "Земи почетна позиција", - "getEndPosition": "Земи крајна позиција", "useSource": "Користи извор", - "useSourceFolder": "Користи изворна папка", - "useSourceResolution": "Користи резолуција на изворот", - "outputSettings": "Излезен фајл/папка...", "fontSizeFactor": "Фактор на големина на фонт", "boxType": "Тип на кутия", - "fixRightToLeft": "Поправи десно-налево", - "cut": "Отсечи", "fromTime": "Од време", "toTime": "До време", "audioEncoding": "Кодирање на аудио", "outputProperties": "Својства на излезот...", "videoFileSize": "Големина на видео фајлот", "oneBox": "Една кутия", - "boxPerLine": "Кутия по линија", - "logoInfo": "Избери PNG слика и повлечи ја за да ја поставиш на видеото." + "logoInfo": "Избери PNG слика и повлечи ја за да ја поставиш на видеото.", + "noSubtitlesGenerateAnyway": "Нема линии со титлови за вградување.\n\nДа се генерира видеото без титлови?", + "noSubtitlesInXItemsGenerateAnyway": "{0} пакетни ставки немаат линии со титлови за вградување.\n\nДа се генерираат тие видеа без титлови?" }, "videoTransparent": { - "title": "Создади транспарентно видео со титули", - "infoAssaOff": "Забелешка: Поддржано е напредно стилизирање за Advanced SubStation Alpha.", - "infoAssaOn": "Забелешка: Ќе се користи напредно стилизирање за Advanced SubStation Alpha :)" + "title": "Создади транспарентно видео со титули" }, "audioToText": { "title": "Говор во текст", @@ -2419,6 +2584,21 @@ "translateToEnglish": "Преведи на англиски", "transcribing": "Транскрибирање...", "transcribingXOfY": "Транскрибирање {0} од {1}...", + "linesTranscribedXOfYFailedZ": "Транскрибирани се {0} од {1} линии - {2} не успеаја или немаа говор.\n\nНеуспешните линии го задржуваат тековниот текст. Да се применат транскрибираните линии или повторно да се обидат неуспешните (прво можеш да го смениш моторот или поставките)?", + "linesTranscribedXOfYCancelled": "Транскрипцијата е откажана по {0} од {1} линии.\n\nДа се применат транскрибираните линии? Другите линии го задржуваат тековниот текст.", + "noLinesTranscribed": "Ниту една од {0} линии не можеше да се транскрибира.\nПровери го дневникот за алатки за детали.", + "applyTranscribedLines": "Примени транскрибирани", + "retryFailedLines": "Повтори неуспешни", + "isolateSpeech": "Изолирај говор (бавно)", + "isolateSpeechHint": "Ги отстранува музиката и звучните ефекти пред транскрибирањето. Дава помалку пропуштени линии и подобар тајминг кај аудио со гласна музика, но трае приближно колку и самото аудио со GPU - и многукратно подолго без него.", + "isolatingSpeech": "Изолирање говор...", + "isolateSpeechFailed": "Говорот не можеше да се изолира - се транскрибира оригиналното аудио.", + "detectSpeakers": "Откриј говорници", + "detectSpeakersHint": "Ја започнува секоја линија со говорникот, како „(speaker 0)“, користејќи ја Sortformer диаризацијата на NVIDIA (до 8 говорници). Работи најдобро со позадини што ги делат линиите кога се менува говорникот, како Parakeet и Cohere.", + "detectSpeakersNeedsNewerCrispAsr": "„Откриј говорници“ бара Crisp ASR v{0} или понов, но инсталиран е v{1}.\n\nАжурирај го Crisp ASR (копчето со запченик до моторот) или исклучи „Откриј говорници“.", + "xNeedsNewerCrispAsr": "{0} бара Crisp ASR v{1} или понов, но инсталиран е v{2}.\n\nДа се преземе и инсталира ажурирањето сега?", + "vad": "VAD", + "vadHint": "Откривање гласовна активност: се транскрибира само говорот, сечен таму каде што говорникот паузира.\nАвтоматски: Silero, и само за моторите на кои им треба (Cohere, Mega, Index-Echo).\nSilero, FireRedVAD, Whisper-VAD, WebRTC: секогаш вклучено, за секој Crisp ASR мотор. FireRedVAD е најпрецизен, Whisper-VAD дава најмалку и најчисти резови, но е најбавен.\nIndex-Echo секогаш користи Silero. --vad, --vad-model или --chunk-seconds во напредните поставки имаат предност.", "inputLanguage": "Влезен јазик", "advancedWhisperSettings": "Напредни поставки за Whisper", "downloadingSpeechToTextEngine": "Преземам енџин за говор-во-текст", @@ -2433,7 +2613,6 @@ "addCustomModelDotDotDot": "Додај сопствен модел...", "customModelHelp": "Можете да користите сопствен модел: изберете ја датотеката на моделот (whisper.cpp ggml '.bin') или папката на моделот (faster-whisper папка со 'model.bin' внатре). Се копира во папката со модели и се додава во листата погоре.", "viewToolsLogFile": "Погледни лог фајл на алатките", - "reDownloadX": "Повторно преземи {0}", "updateXTitle": "Ажурирање {0}?", "updateXMessage": "Нова верзија на {0} е достапна.{1}{1}Преземи и инсталирај го ажурирањето сега?", "downloadingSpeechToTextModel": "Преземам модел за говор-во-текст", @@ -2445,15 +2624,6 @@ "fixCasing": "Поправи големина на букви", "addPeriods": "Додај точки", "changeUnderlineToColor": "Смени подвлекување со боја", - "cueBuilding": "Градење титлови (MLX Whisper)", - "cueRebuild": "Повторно изгради титлови од временските ознаки на зборовите", - "cueMaxChars": "Максимум знаци по титл", - "cueMaxSeconds": "Максимум секунди по титл", - "cueMaxCps": "Максимум знаци во секунда", - "vocabularyPrompt": "Промпт со вокабулар (имиња, места, термини)", - "beamSize": "Големина на beam", - "beamSizeNote": "0 декодира најбрзо. 5 дава најдобра точност (како Faster Whisper) за околу 20-30% повеќе време. Вредности над 5 ретко помагаат.", - "downloadX": "Преземи {0}", "removeNonSpeechLines": "Отстрани линии без говор", "removeNonSpeechLinesHint": "Отфрли линии што опишуваат само звук, како „[Музика]“ или „(бранови удираат)“", "removeRepeatedLines": "Отстрани повторени линии", @@ -2512,7 +2682,7 @@ "cloneVoicePerLinePreparing": "Земање на гласот на секоја линија од видеото...", "cloneVoicePerLineNeedsVideo": "За клонирање на гласот на секоја линија потребно е видеото на кое припаѓа титлот. Отвори го видеото и пробај повторно.", "cloneVoicePerLineNoClips": "Не може да се земе аудио од видеото, па нема од што да се клонира. Провери дали видеото има аудио трака.", - "cloneVoicePerLineNeedsOriginalSubtitleX": "{0} може да клонира линија само кога знае што се кажува во видеото на таа линија, што доаѓа од титлот на оригиналниот јазик. Нема вчитан оригинален титл, па секоја линија би била изговорена од првиот увезен глас наместо од гласот во видеото.\n\nОтвори го оригиналниот титл (Датотека > Отвори оригинално...) покрај преводот, па генерирај повторно.", + "cloneVoicePerLineTranscribeClipsXYZ": "{0} може да клонира линија само кога знае што се зборува во видеото на таа линија. Тоа не е познато за {1} од {2} линии - обично доаѓа од титлот на оригиналниот јазик (Датотека > Отвори оригинал...).\n\nДа се транскрибираат тие линии од видеото со говор во текст сега?\n\nДа: избери го моторот и јазикот што се зборува во видеото, и референтните клипови се транскрибираат пред да се генерира говорот.\nНе: генерирај сепак - тие линии се изговараат со обичен глас наместо со гласот од видеото.", "voiceCloneConsentTitle": "Клонирање глас - пред да почнеш", "voiceCloneConsentHeader": "Ќе клонираш глас", "voiceCloneConsentIntro": "Клонирањето копира глас на вистинска личност. Тоа носи со себе правила во повеќето места, а во ЕУ и правни обврски што паѓаат на тебе, а не на Subtitle Edit. Прочитај го ова еднаш.", @@ -2528,6 +2698,12 @@ "voiceCloneConsentDeclined": "Клонирањето глас не е достапно додека не се прифатат овие услови.", "advancedTtsAudioProcessing": "Обработка на аудио", "advancedTtsOutput": "Излез", + "advancedTtsVoiceCloning": "Клонирање глас", + "cleanCloneReferences": "Исчисти референци за клонирање глас (Sidon)", + "cleanCloneReferencesDescription": "Ги отстранува музиката, шумот и одекот од аудиото од кое се клонира гласот: клипови земени од видеото и снимки увезени во управувачот со гласови. Го користи моделот за реставрација на говор Sidon на audio.cpp (преземање од 940 MB). Клонираниот глас повеќе не ја копира позадината, но многу тивок или преклопен говор може да звучи придушено.", + "cleaningCloneReferencesDotDotDot": "Чистење референци за клонирање глас...", + "downloadSidonTitle": "Да се преземе Sidon?", + "downloadSidonQuestionX": "За чистење на референците за клонирање глас е потребен моделот за реставрација на говор Sidon ({0}).\n\nДа се преземе сега? Без него референците се користат какви што се.", "rubberbandInstalled": "(инсталиран)", "rubberbandNotFound": "(не е пронајден во FFmpeg)", "generationFolder": "Папка за генерирање", @@ -2536,14 +2712,11 @@ "deleteTempFilesDescription": "Ја брише папката за генерирање кога се затвора прозорецот „Текст во говор“. Зачуваните аудио и видео датотеки се задржуваат.", "piperSettings": "Поставки за Piper", "title": "Текст во говор", - "textToSpeechEngine": "Енџин за текст-во-говор", "reviewAudioSegments": "ТТС - Преглед на аудио сегменти", "reviewAudioSegmentsHistory": "ТТС - Преглед на историја на аудио", "stability": "Стабилност", "similarity": "Сличност", "speakerBoost": "Зголемување на гласот", - "styleExaggeration": "Претерано стилизирање", - "regenerateAudioSelectedLine": "Регенерирај аудио за избраната линија", "generateSpeechFromText": "Создади говор од текст", "testVoice": "Тестирај глас", "addAudioToVideoFile": "Додај аудио во видео фајл", @@ -2562,20 +2735,96 @@ "videoEncodingSettings": "ТТС - Поставки за кодирање на видео", "elevenLabsSettings": "ТТС - Поставки за ElevenLabs", "elevenLabsSettingsResetHint": "Ресетирај поставки за ElevenLabs на стандардни вредности", + "provider": "Провајдер", + "openAiCompatibleSettings": "TTS - Поставки за OpenAI-компатибилен сервер", + "customServerUrl": "URL на прилагоден сервер", + "customServerUrlHint": "Целосна крајна точка за говор на OpenAI-компатибилен сервер, се користи кога провајдерот е „Custom“, на пр. http://localhost:8880/v1/audio/speech", + "customModels": "Прилагодени модели", + "customModelsHint": "Имиња на модели одделени со запирка, понудени кога провајдерот е „Custom“", + "customVoices": "Прилагодени гласови", + "customVoicesHint": "Имиња на гласови одделени со запирка, ги користи провајдерот „Custom“ и OpenRouter моделите што не објавуваат листа со гласови", + "instructions": "Инструкции", + "instructionsHint": "Опционален стил на зборување, на пр. „Зборувај со смирен, топол глас“. Се испраќа само до моделите што го поддржуваат (не tts-1/tts-1-hd)", + "openAiSpeedHint": "Брзина на говор од 0.25 до 4.0 (1.0 е нормално). Не ја поддржува секој модел", + "outputFormat": "Излезен формат", + "outputFormatHint": "Аудио формат побаран од серверот. „Автоматски“ бара MP3 и се префрла на PCM за моделите што враќаат само PCM (на пр. Gemini TTS)", "regenerateAudio": "Регенерирај аудио", "autoContinuePlaying": "Автоматски продолжи со репродукција", "addingAudioToVideoFileDotDotDot": "Додавање аудио во видео фајл...", "preparingMergeDotDotDot": "Подготовка за спојување...", "importVoiceDotDotDot": "Увези глас...", + "renameVoiceDotDotDot": "Преименувај глас...", + "renameVoiceTitle": "Преименувај глас", + "voiceXCouldNotBeRenamedX": "Гласот „{0}“ не можеше да се преименува: {1}", + "deleteVoiceDotDotDot": "Избриши глас...", + "deleteVoiceTitle": "Избриши глас", + "deleteVoiceXQuestion": "Да се избрише гласот „{0}“?\n\nРеферентната снимка и нејзините датотеки се отстрануваат од дискот.", + "voiceXCouldNotBeDeletedX": "Гласот „{0}“ не можеше да се избрише: {1}", "voiceImportSuccessTitle": "Гласот е увезен", "voiceXImported": "Гласот '{0}' е успешно увезен", "voiceCloneTranscriptTitle": "Внеси транскрипт на аудиото (потребно за клонирање на глас)", + "voiceManagerTitle": "TTS - Управувач со гласови", + "voiceManagerDotDotDot": "Управувач со гласови...", + "voiceManagerMenuItem": "TTS управувач со гласови...", + "speakFromCurrentLineTitle": "Зборувај од тековната линија", + "speakFromCurrentLineDotDotDot": "Зборувај од тековната линија...", + "speakFromCurrentLineHint": "Ги чита линиите на глас една по друга, почнувајќи од тековната линија. Притисни Escape или кликни на друга линија за да запре.", + "stopSpeaking": "Престани да зборуваш", + "speakingLineX": "Се зборува линија {0}... (Escape за запирање)", + "playWithSpeechTitle": "Пушти со говор", + "playWithSpeechDotDotDot": "Пушти со говор...", + "playWithSpeechHint": "Го пушта видеото и ја изговара секоја линија кога видеото ќе стигне до неа. Паузирај и барај како и обично; притисни Escape за запирање.", + "lowerVideoVolumeWhileSpeaking": "Намали ја јачината на видеото додека се зборува", + "pauseVideoWhenSpeechRunsLate": "Паузирај го видеото кога говорот ќе навлезе во следната линија", + "waitingForSpeechLineX": "Генерирање говор за линија {0}... (Escape за запирање)", + "voiceKindClone": "Клониран глас", + "voiceKindPreset": "Вграден", + "voiceKindModel": "Гласовен модел", + "voiceKindOnline": "Онлајн", + "transcript": "Транскрипт", + "transcriptRequiredHint": "Задолжително - моторот го чита овој транскрипт на снимката (ref-text) при клонирање", + "transcriptOptionalHint": "Опционално - точен транскрипт на снимката го подобрува клонирањето", + "saveTranscript": "Зачувај транскрипт", + "searchVoices": "Барај гласови", + "loadingVoicesDotDotDot": "Вчитување гласови...", + "noVoicesForEngine": "Нема гласови - увези снимка или преземи пакет со гласови", + "noVoicesMatchFilter": "Ниту еден глас не одговара на пребарувањето", + "copyVoiceTo": "Копирај глас во", + "copyVoiceToDotDotDot": "Копирај глас во мотор...", + "copyingVoiceXToYDotDotDot": "Копирање на гласот „{0}“ во {1}...", + "voiceXCopiedToY": "Гласот „{0}“ е копиран во {1}", + "voiceXCouldNotBeCopiedToY": "Гласот „{0}“ не можеше да се копира во {1} - види го дневникот за детали", + "voiceXAlreadyExistsInYContinue": "Глас со име „{0}“ веќе постои во {1}.\n\nДа се копира сепак?", + "openVoicesFolder": "Отвори папка со гласови", + "downloadVoicePacksDotDotDot": "Преземи пакети со гласови...", + "downloadVoicePacksTitle": "TTS - Преземи пакети со гласови", + "voicePackIntro": "Пакетите со гласови се збирки кратки, чисти снимки со транскрипти, подготвени за клонирање. Секој пакет се увезува во избраниот мотор (гласовите што веќе постојат се прескокнуваат) и содржи ATTRIBUTION.txt со неговата лиценца и извори.", + "voicePackDetailsXVoicesYSizeZLicense": "{0} гласови · {1} · {2}", + "installToEngine": "Инсталирај во мотор", + "downloadAndInstall": "Преземи и инсталирај", + "selectAtLeastOneVoicePack": "Избери барем еден пакет со гласови", + "downloadingXDotDotDot": "Преземање {0}...", + "installingXDotDotDot": "Инсталирање {0}...", + "xVoicesInstalledYSkipped": "Инсталирани {0} гласови ({1} веќе постоеја)", "useSpeechToTextDotDotDot": "Користи говор-во-текст...", "advancedTtsSettings": "Напредни поставки за TTS", "proAudioPostProcessing": "Професионално постпроцесирање на аудио", "proAudioPostProcessingDescription": "Примена на EQ топлина, шумска порта, компресија, нормализација на јачина (-16 LUFS) и fade in/out на секој сегмент.", + "generatingSpeechSegmentXOfY": "Генерирање говор: сегмент {0} од {1}", + "adjustingSpeedSegmentXOfY": "Прилагодување брзина: сегмент {0} од {1}", + "mergingAudioSegmentXOfY": "Спојување аудио: сегмент {0} од {1}", + "postProcessingSegmentXOfY": "Дополнителна обработка: сегмент {0} од {1}", "audioDucking": "Намалување на аудио", "audioDuckingDescription": "Намалува оригиналната звучна гласност на видеото и ја меша со TTS аудиото, така оригиналната звучна трака е сé уште слабовидна.", + "removeOriginalSpeech": "Отстрани оригинален говор (бавно)", + "removeOriginalSpeechDescription": "Ги задржува музиката и звучните ефекти од оригиналното видео, но го отстранува неговиот говор и го меша новиот говор преку тоа. Трае приближно колку и самото видео со GPU и многукратно подолго без него. Со вклучено намалување на аудио, неговата јачина се применува на музиката и ефектите. Се применува само кога говорот се додава во видео датотеката.", + "removingOriginalSpeech": "Отстранување на оригиналниот говор...", + "removeOriginalSpeechFailed": "Оригиналниот говор не можеше да се отстрани - говорот е додаден во видеото без тоа.", + "speechRemovalModel": "Модел", + "speechRemovalModelDescription": "Најдобар квалитет задржува најмногу од музиката и звучните ефекти. Брзо е околу три пати побрзо, со малку повеќе изгубена музика.", + "speechRemovalBestQuality": "Најдобар квалитет (Mel-Band RoFormer)", + "speechRemovalFast": "Брзо (HTDemucs)", + "videoTruncatedWarning": "Сликата во новата видео датотека престанува по {0}, но оригиналното видео трае {1} - сликата ќе замрзне додека звукот продолжува.\n\nДатотеката е задржана. Види error-log.txt во папката со податоци на Subtitle Edit за излезот од ffmpeg.", "originalVolumePercent": "Оригинална гласност %", "vadSilenceCompression": "VAD компресија на тишина", "vadSilenceCompressionDescription": "Скратува паузите меѓу зборовите пред менување на темпото. Користи Voice Activity Detection за компресирање само на тишината додека говора остава непроменет. Ова е првиот препорачуван чекор — ја намалува длителноста без губиток на квалитет.", @@ -2644,7 +2893,6 @@ "subtitleMergedLinesAppliedPlural": "Применети се {0} спојувања на линии од текст во говор" }, "shotChanges": { - "titleGenerateOrImport": "Создади/увези промени на кадри", "generateShotChanges": "Создади промени на кадри", "importShotChanges": "Увези промени на кадри", "generateShotChangesWithFfmpeg": "Создади промени на кадри со ffmpeg", @@ -2654,7 +2902,9 @@ "timeCodeFormatColon": "Формат на временски код:", "shotChangesList": "Листа со промени на кадри", "shotChangesClearQuestion": "Дали сте сигурни дека сакате да ги избришете сите промени на кадри?", - "deleteSelectedShotChangeQuestion": "Дали сте сигурни дека сакате да ја избришете избраната промена на кадар?" + "deleteSelectedShotChangeQuestion": "Дали сте сигурни дека сакате да ја избришете избраната промена на кадар?", + "shotChanges": "Промени на сцени", + "exportShotChanges": "Извези промени на сцени" }, "chapters": { "title": "Поглавја", @@ -2684,7 +2934,6 @@ "scaleTimesDescription": "Скалирај го времето на секое поглавје од една брзина на кадри во друга.", "fromFrameRate": "Од", "toFrameRate": "Во", - "apply": "Примени", "writeToVideo": "Запиши во видео...", "writeToVideoTitle": "Запиши поглавја во видео", "writeToVideoDescription": "Поглавјата се запишуваат во копија од видео датотеката. Ништо не се рекодира, па ова е брзо и без загуба на квалитет.", @@ -2694,7 +2943,6 @@ "writtenToX": "Поглавјата се запишани во {0}", "unableToWriteChapters": "Не може да се запишат поглавјата во видео датотеката.", "chaptersSavedToX": "Поглавјата се зачувани во {0}", - "noVideoLoaded": "Нема вчитано видео", "emptyListCallToAction": "Сè уште нема поглавја - додај едно на позицијата во видеото, или увези ги од видео или датотека." }, "videoOcr": { @@ -2705,10 +2953,6 @@ "bottomHalf": "Долна половина", "fullFrame": "Цел кадар", "engine": "OCR мотор", - "language": "Јазик", - "url": "URL", - "model": "Модел", - "apiKey": "API клуч", "scan": "Скенирај", "framesPerSecond": "Кадри во секунда", "textBrightnessMinimum": "Минимална светлина на текстот (0=исклучено)", @@ -2723,10 +2967,10 @@ "runningOcrXY": "Извршување OCR... {0}/{1}", "noLinesFoundTitle": "Не се пронајдени титлови", "noLinesFoundMessage": "Не е пронајден текст во областа за скенирање - обидете се да ја прилагодите областа за скенирање, моторот или минималната светлина.", + "ocrStoppedEarlyMessage": "OCR моторот запре пред да се прочитаат сите кадри, па досега пронајдените линии се задржани, а останатите недостасуваат.{0}{0}{1}", "linesFoundX": "Пронајдени се {0} линии", "refiningTimingXY": "Дотерување на времињата... {0}/{1}", "fixOcrErrors": "Поправи OCR грешки", - "dictionary": "Речник", "editLineX": "Уреди линија {0}", "downloadDictionaryHint": "Преземи речник за проверка на правопис...", "framesPerSecondHint": "Колку кадри во секунда се скенираат. 5 покрива нормални титлови; повисока вредност фаќа многу кратки линии и го подобрува групирањето, но чини повеќе OCR време.", @@ -2743,7 +2987,58 @@ "testOcr": "Тестирај тековен кадар", "testOcrRunning": "Тестирање OCR на тековниот кадар...", "testOcrResultX": "Резултат од тестот: {0}", - "testOcrNoTextFound": "Тест: не е пронајден текст во областа за скенирање" + "testOcrNoTextFound": "Тест: не е пронајден текст во областа за скенирање", + "engineSettings": "Поставки за OCR мотор", + "engineBuiltIn": "Вграден во оперативниот систем", + "engineExternalServer": "Надворешен сервер (инсталиран и управуван надвор од Subtitle Edit)", + "engineCloudApi": "Cloud API (бара API клуч)", + "engineNothingToInstall": "Нема ништо за инсталирање", + "engineModelsMissing": "Моторот е инсталиран, недостасуваат модели", + "website": "Веб-страница" + }, + "backgroundMusic": { + "title": "Генерирај музика во позадина", + "generateBackgroundMusicDotDotDot": "Генерирај музика во позадина...", + "preset": "Предлошка", + "prompt": "Промпт", + "tempoBpm": "Темпо (BPM)", + "generateSeconds": "Генерирај (секунди)", + "seed": "Seed", + "randomSeed": "Случајно", + "outputLength": "Должина на излезот", + "outputLengthVideoX": "{0} (должина на видеото - музиката се повторува непрекинато)", + "musicVolumePercent": "Јачина на музиката (%)", + "removeExistingAudioTracks": "Отстрани постоечки аудио патеки", + "originalAudioVolumePercent": "Јачина на оригиналното аудио (%)", + "saveAudioDotDotDot": "Зачувај аудио...", + "addToVideoDotDotDot": "Додај во видео...", + "engineInfo": "ACE-Step 1.5 Turbo преку audio.cpp (MIT лиценца) - работи локално", + "presetCooking": "Готвење / печење", + "presetTech": "Технологија / преглед на уреди", + "presetForest": "Шума / природа", + "presetRelaxingPiano": "Релаксирачко пијано", + "presetLofiVlog": "Lo-fi влог", + "presetCorporate": "Корпоративно / објаснувачко", + "loadingModel": "Вчитување модел...", + "composing": "Компонирање...", + "generatingAudio": "Генерирање аудио...", + "decodingAudio": "Декодирање аудио...", + "creatingLoop": "Креирање непрекинат циклус...", + "resultLoopXYZ": "Циклус: {0} на {1} BPM, совпаѓање на спојот {2}%", + "resultNoLoopX": "Музиката е подготвена: {0}", + "addingMusicToVideo": "Додавање музика во видеото...", + "downloadModelTitle": "Да се преземе музичкиот модел?", + "downloadModelQuestionX": "За генерирање музика е потребен моделот ACE-Step 1.5 ({0}).\n\nДа се преземе моделот?", + "downloadingModel": "Преземање на моделот ACE-Step 1.5...", + "saveAudioTitle": "Зачувај музика во позадина", + "audioFileSavedX": "Аудио датотеката е зачувана: {0}", + "unableToGenerateMusic": "Не може да се генерира музика", + "unableToAddMusicToVideo": "Не може да се додаде музика во видеото", + "outputLengthTextToSpeech": "Го следи генерираниот говор - музиката се повторува непрекинато и се стишува додека некој зборува", + "addBackgroundMusic": "Додај музика во позадина", + "addBackgroundMusicHint": "Генерирај музика локално со ACE-Step 1.5 и измешај ја под говорот - се стишува додека некој зборува", + "generatingBackgroundMusicDotDotDot": "Генерирање музика во позадина...", + "mixingBackgroundMusicDotDotDot": "Мешање музика во позадина..." }, "goToVideoPosition": "Оди до позиција во видеото", "goToVideoPositionDotDotDot": "Оди на позиција во видеото...", @@ -2754,29 +3049,87 @@ "openSecondarySubtitleOnVideoPlayerDotDotDot": "Вторична титула на видео плеерот, отвори...", "openSecondarySubtitleOnVideoPlayer": "Вторична титула (на видео плеерот)", "removeSecondarySubtitleOnVideoPlayer": "Вторична титула на видео плеерот, отстрани", + "editSecondarySubtitleSettings": "Уреди поставки за вториот титл", + "editSecondarySubtitleSettingsDotDotDot": "Уреди поставки за вториот титл...", + "secondarySubtitleRememberSettings": "Запомни ги овие поставки", + "secondarySubtitleDoNotShowAgain": "Не го прикажувај овој дијалог повторно", + "secondarySubtitleJustifyLines": "Порамни линии обострано", "openRecentVideo": "Отвори скорешно видео", "clearRecentVideos": "Исчисти скорешни видеа", "cutVideoTitle": "Исечи видео", "cutVideoDotDotDot": "Исечи видео...", + "remuxVideoTitle": "Ремукс на видео", + "remuxVideoDotDotDot": "Ремукс на видео...", + "remuxVideoInputVideo": "Влезно видео:", + "remuxVideoMultipleTracksRequiresMkv": "Повеќе аудио патеки или патеки со титлови бараат MKV контејнер. Излезниот формат е автоматски префрлен на .mkv.", + "remuxVideoOutputFormat": "Излезен формат:", + "remuxVideoOutputFile": "Излезна датотека:", + "remuxVideoRemuxing": "Ремукс на видео и аудио...", + "remuxVideoFinalizing": "Финализирање на излезната датотека (поместување на индексот на почетокот)...", + "remuxVideoCompleted": "Видеото е успешно ремуксирано.", + "remuxVideoFailed": "Ремуксот на видеото не успеа.", + "remuxVideoPleaseSelectBoth": "Избери и видео и аудио датотеки.", + "remuxVideoAssRequiresMkv": "ASS/SSA титловите бараат MKV контејнер за да се зачуваат сето форматирање и стилови.", + "remuxVideoSccRequiresMov": "Scenarist (.scc) титловите се вградуваат како CEA-608 скриени титлови, кои бараат MOV или MPG контејнер. Излезниот формат е автоматски префрлен на .mov.", + "remuxVideoSccNotWithWebM": "Scenarist (.scc) скриените титлови бараат MOV контејнер, кој не може да го содржи VP9/AV1 видеото од .webm датотека. Отстрани ја .scc датотеката или конвертирај ја во друг формат на титл.", + "remuxVideoMpgMaxTwoCaptionTracks": ".mpg датотека содржи најмногу две патеки со скриени титлови (CC1 и CC3) - отстрани датотеки со титлови за да останат една или две.", + "remuxVideoMccRequiresMpg": "MacCaption (.mcc) титловите се вградуваат како CEA-608/708 скриени титлови во MPEG-2 видеото, што бара MPG контејнер. Излезниот формат е автоматски префрлен на .mpg.", + "remuxVideoMpgReencodeVideoX": "Скриените титлови во .mpg датотека бараат MPEG-2 видео, а видеото е {0}. Да се прекодира видеото во MPEG-2? Ова трае подолго и малку го намалува квалитетот.", + "remuxVideoFileHasNoAudioX": "„{0}“ нема аудио патека, па не може да се користи како аудио извор.", + "remuxVideoAddingClosedCaptions": "Додавање скриени титлови...", + "remuxVideoSelectAudioTrack": "Избери аудио патека", + "remuxVideoSelectAudioTrackFor": "Избери аудио патека за {0}", + "remuxVideoSelectAudioTrackPrompt": "Датотеката „{0}“ содржи {1} аудио патеки. Избери ја аудио патеката за ремукс:", + "remuxVideoVideoFile": "видео", + "remuxVideoAudioFile": "аудио", + "remuxVideoAudioFiles": "Аудио датотеки:", + "remuxVideoSubtitleFiles": "Датотеки со титлови (опционално, меки титлови):", + "remuxVideoSelectAudioTrackDotDotDot": "Избери аудио патека...", + "remuxVideoFilesX": "{0} датотеки", + "remuxVideoMixAudio": "Измешај го аудиото во една патека", + "remuxVideoVolumePercent": "Јачина %:", + "remuxVideoVolumeX": "Јачина {0}%", + "remuxVideoFastStart": "Оптимизирај за веб стриминг (помести го индексот напред - побавно)", "embedSubtitlesDotDotDot": "Додај/отстрани вградени титули...", "generateTimeCodes": "Создади временски кодови", "checkeredImage": "Текст слика со шаховина", - "saveVideoAsTitle": "Сними видео како", "promptForFfmpegParamsAndGenerate": "Побарај ffmpeg параметри и генерирај", "cutVideoCutSegments": "Исечи сегменти", "cutVideoMergeSegments": "Спој сегменти", "cutVideoSplitSegments": "Сними сегменти поединечно", - "cutVideoType": "Тип на исекување", - "mpvRenderAuto": "Автоматски", - "mpvRenderNative": "Нативен", - "mpvRenderOpenGl": "OpenGL", - "mpvRenderSoftware": "Софтвер (бавно)", "cutVideoAlsoCutSubtitle": "Исечи го и титлот", "cutVideoSubtitleFileGeneratedX": "Генерирана датотека со титл: {0}", + "cutVideoTransition": "Транзиција помеѓу сегментите", + "cutVideoTransitionDurationSeconds": "Времетраење (секунди)", + "cutVideoFadeInSeconds": "Појавување од црно на почетокот (секунди)", + "cutVideoFadeOutSeconds": "Затемнување во црно на крајот (секунди)", + "cutVideoPreviewTransition": "Преглед на транзиција", + "cutVideoPreviewTransitionTitle": "Преглед на транзиција", + "cutVideoPreviewNeedsJoin": "Нема спој за преглед - додај барем два сегменти за спојување или сегмент за исекување од средината.", + "cutVideoTransitionInfoX": "Секоја транзиција ги преклопува деловите што ги спојува, па излезот станува пократок за {0} секунди по спој. Исечен титл повторно се тајмира за да одговара.", + "cutVideoTransitionFade": "Затемнување", + "cutVideoTransitionFadeBlack": "Затемнување преку црно", + "cutVideoTransitionFadeWhite": "Затемнување преку бело", + "cutVideoTransitionFadeGrays": "Затемнување преку сиво", + "cutVideoTransitionDissolve": "Претопување", + "cutVideoTransitionWipeLeft": "Бришење лево", + "cutVideoTransitionWipeRight": "Бришење десно", + "cutVideoTransitionWipeUp": "Бришење нагоре", + "cutVideoTransitionWipeDown": "Бришење надолу", + "cutVideoTransitionSlideLeft": "Лизгање лево", + "cutVideoTransitionSlideRight": "Лизгање десно", + "cutVideoTransitionSlideUp": "Лизгање нагоре", + "cutVideoTransitionSlideDown": "Лизгање надолу", + "cutVideoTransitionSmoothLeft": "Мазно лево", + "cutVideoTransitionSmoothRight": "Мазно десно", + "cutVideoTransitionCircleOpen": "Отворање круг", + "cutVideoTransitionCircleClose": "Затворање круг", + "cutVideoTransitionRadial": "Радијално", + "cutVideoTransitionPixelize": "Пикселизирање", + "cutVideoTransitionBlur": "Заматување", "importCurrentSubtitle": "Увези тековна титула", "addRemoveEmbeddedSubtitlesTitle": "Додај/отстрани вградени титули", "addCurrentSubtitle": "Додај тековна титула", - "titleOrLanguage": "Наслов/јазик", "viewMatroskaTrackX": "Преглед на Matroska трака - {0}", "resolutionSeparator": "x", "openFromUrlTitle": "Отвори видео фајл од URL", @@ -2812,8 +3165,11 @@ "reEncodeGeneratingDone": "Генерирањето заврши", "reEncodeGeneratedFilesX": "Генерирани датотеки ({0}):", "reEncodeFfmpegParameters": "ffmpeg параметри", + "analyzingVideoX": "Анализирање видео... {0}% {1}", + "analyzingVideoXofY": "Анализирање видео {0}/{1}... {2}% {3}", + "backgroundImageFileNotSelected": "Не е избрана датотека со слика за позадина", + "backgroundImageFileDoesNotExistX": "Датотеката со слика за позадина не постои: {0}", "addRemoveEmbeddedSubtitlesMp4Title": "Додај/отстрани вградени титлови (MP4)", - "embeddedTrackColumnNew": "Ново", "embeddedTrackNoSubtitlesFoundTitle": "Не се пронајдени титлови", "embeddedTrackNoSubtitlesFoundMessage": "Избраната датотека со титл не содржи никакви титлови.", "embeddedTrackNoTracksTitle": "Не се додадени траки", @@ -2825,15 +3181,14 @@ "embeddedTrackGeneratingVideoXY": "Генерирање видео... {0}% {1}", "embeddedTrackGeneratingVideo": "Генерирање видео...", "mp4FilesFilter": "MP4 датотеки", + "embeddedTracksPickVideoHint": "Избери видео датотека или повлечи ја тука за да ги видиш нејзините патеки со титлови.", "openFromUrlOpenOnline": "Отвори онлајн", "openFromUrlOpenOnlineDescription": "Стримувај го видеото директно. Најбрз почеток.", "openFromUrlOpenOnlineNote": "Говор во текст и шилци на формата на бран не се достапни во овој режим.", "openFromUrlDownloadAndOpen": "Преземи и отвори", "openFromUrlDownloadAndOpenDescription": "Зачувај го видеото локално, потоа отвори го.", "openFromUrlDownloadAndOpenNote": "Потребно за говор во текст и шилци на формата на бран.", - "openFromUrlMissing": "Прво внесете URL на видео.", "openFromUrlDownloadingTitle": "Преземање видео", - "openFromUrlSaveAs": "Зачувај видео како", "openFromUrlDownloadSubtitles": "Преземи и титлови", "pickOnlineSubtitleTitle": "Избери титл за преземање", "pickOnlineSubtitleFetching": "Преземање титлови...", @@ -2843,7 +3198,6 @@ "talkerX": "Говорник {0}", "baseModel": "Основен модел", "turboModel": "Turbo модел", - "ttsUpdateX": "Ажурирај {0}", "ttsEngineNotInstalled": "{0} не е инсталиран", "ttsEngineUpdateAvailable": "{0} - достапно е ажурирање", "ttsEngineTooOldUpdateRequired": "{0} е престар - потребно е ажурирање", @@ -2866,19 +3220,25 @@ "cloneVoiceTo": "Клонирај глас во", "cloneVoiceNameTitle": "Име на новиот глас", "cloneVoiceExtractFailed": "Не може да се извлече аудио за избраната линија.", + "lineXHasNoDuration": "Линијата {0} нема времетраење (времето за крај не е по почетното време), па нема аудио за извлекување.", + "audioClipsSkippedLinesX": "Не можеше да се извлече аудио за овие линии, па тие се прескокнати: {0}", "toggleShotChange": "Смени промена на кадар", "resetWaveformZoomAndSpeed": "Ресетирај зум и брзина на форма на бран", "showOriginalSubtitle": "Прикажи оригинален титл", + "timelineGroupTracksBy": "Групирај патеки со титлови по", + "timelineShowThumbnails": "Прикажи сликички од видеото", + "showSpeechOnly": "Прикажи само говор (бавно генерирање)", + "isolatingSpeechForWaveformX": "Изолирање говор за формата на бран... {0}", + "isolatingSpeechForWaveformFailed": "Говорот не можеше да се изолира за формата на бран - види го дневникот за алатки", + "speechOnlyWaveformReady": "Формата на бран сега прикажува само говор", "showOnlyWaveform": "Прикажи само форма на бран", "showOnlySpectrogram": "Прикажи само спектрограма", "showWaveformAndSpectrogram": "Прикажи форма на бран и спектрограма", - "spectrogramClassic": "Класичен", "spectrogramClassicViridis": "Viridis", "spectrogramClassicPlasma": "Plasma", "spectrogramClassicInferno": "Inferno", "spectrogramClassicTurbo": "Turbo", "spectrogramNeon": "Neon", - "waveformDrawStyleClassic": "Класично", "waveformDrawStyleFancy": "Фантазно", "setVideoPositionAndPauseAndSelectSubtitle": "Постави позиција на видеото, пауза, и избери титула", "setVideopositionAndPauseAndSelectSubtitleAndCenter": "Постави позиција на видеото, пауза, избери титула и центрирај", @@ -2888,7 +3248,6 @@ }, "sync": { "visualSync": "Визуелна синхронизација", - "sync": "Синхронизација", "startScene": "Започни сцена", "endScene": "Заврши сцена", "playTwoSecondsAndBack": "Пушти 2 секунди и назад", @@ -2904,19 +3263,18 @@ "adjustSelectedLinesAndForward": "Прилагоди избрани линии и напред", "toFrameRate": "Во фрејмрејт", "fromFrameRate": "Од фрејмрејт", + "videoXFrameRateY": "{0} - {1} fps", "adjustAllTimes": "Прилагоди сите времена (прикажи порано/подоцна)", "showEarlier": "Прикажи порано", "showLater": "Прикажи подоцна", - "changeFrameRate": "Промени фрејмрејт", "setSyncPoint": "Постави точка за синхронизација", "setSyncPointViaVideo": "Постави точка за синхронизација преку видео...", "syncPointTimeCode": "Временски код на точката за синхронизација", "syncPoints": "Точки за синхронизација", "pointSync": "Точка синхронизација", "pointSyncViaOther": "Точка синхронизација преку друга титула", - "adjustmentX": "Прилагодување: {0}", - "totalAdjustmentX": "Вкупно прилагодување: {0}", - "syncPointCandidateInfo": "Линиите што почнуваат по {0}+ секунди тишина се означени - тие често се сигурни точки за синхронизација", + "syncPointCandidateAfterInfo": "Линиите по кои следат {0}+ секунди тишина се истакнати - тие често се сигурни точки на синхронизација", + "gapAfter": "Празнина после", "adjustAllShortcuts": "Кратенки на тастатура:\r\n\r\n• Shift + Left/Right: Премести 10 ms\r\n• Ctrl + Left/Right: Премести 100 ms\r\n• Alt + Left/Right: Премести 500 ms", "adjustAllShortcutsFrames": "Кратенки на тастатурата:\r\n\r\n• Shift + Лево/Десно: Помести 1 кадар\r\n• Ctrl + Лево/Десно: Помести 10 кадри\r\n• Alt + Лево/Десно: Помести 1 секунда", "offsetInSeconds": "Поместување во секунди", @@ -2936,6 +3294,7 @@ "lineMerge": "Спојување на линии", "delayInSecondsBetweenRequests": "Задршка во секунди меѓу барањата", "maxBytesPerRequest": "Максимален број бајти по барање", + "keepMusicLinesUntranslated": "Не преведувај линии во музички симболи (♪ ... ♪)", "promptText": "Текст за упит", "resetPromptToDefault": "Ресетирај го промптот на стандардно", "translateEachLineSeparately": "Преведи секоја линија поодделно", @@ -2949,11 +3308,10 @@ "readyToTranslate": "Подготвено за превод", "translating": "Преведување...", "translationComplete": "Преводот е завршен", + "translateInPlaceNoOriginal": "Преведи на место (не создавај оригинал)", "translationCancelled": "Преводот е откажан", "swapLanguages": "Замени изворен и целен јазик", "xIsAlreadyDownloadedReDownload": "{0} веќе е преземено. Да се преземе повторно?", - "advancedDotDotDot": "Напредно...", - "advancedSettings": "Напредни подесувања", "advancedSettingsSubtitle": "Опции за пакети, контекст и семплирање за напредниот llama.cpp мотор", "context": "Контекст", "synopsis": "Синопсис", @@ -2977,6 +3335,7 @@ "useOnlyExtraServerParametersHint": "Стартувај го llama-server со горните параметри наместо со сопственото дотерување на Subtitle Edit - сепак се поставуваат само моделот, хостот и портата", "serverRunningAtX": "Серверот работи на {0}", "customPromptHint": "Прилагодени инструкции ({0} = изворен јазик, {1} = целен јазик); празно = вграден промпт", + "editTranslationHint": "Кликни на преводот на избраниот ред за да го уредиш (Enter зачувува, Escape откажува)", "llamaCppDownloadEngineAndModelPrompt": "llama.cpp бара да се преземат llama-server моторот и модел за преведување. Да преземам сега?", "llamaCppDownloadEnginePrompt": "llama.cpp бара да се преземе llama-server моторот. Да преземам сега?", "llamaCppDownloadModelPrompt": "llama.cpp бара да се преземе избраниот модел за преведување. Да преземам сега?" @@ -3014,8 +3373,14 @@ "checkForUpdatesChannelStableAndBeta": "Стабилни и бета верзии", "assaAutoSetResolution": "ASSA: постави резолуција (PlayResX/PlayResY) од видеото кога ќе се отвори видео", "assaAutoSetResolutionPrompt": "ASSA: прашај пред промена на резолуцијата за да одговара на видеото (инаку скалирај автоматски)", + "videoControls": "Видео контроли", + "videoControlsEdit": "Уреди видео контроли...", + "videoControlsPositionText": "Текст за позиција / времетраење", + "videoControlsVideoFileName": "Име на видео датотеката", + "videoControlsPlayerName": "Име на видеоплеерот", "mergeKeepEndTime": "Спој линии: задржи го крајното време (дозволи преклопување со следниот титл)", "mergeKeepEndTimeOnlyAssa": "Спој линии: задржи го крајното време само за „Advanced Sub Station Alpha“", + "mergeAutoBreak": "Спојување линии: автоматски дели го спојениот текст (исклучено ги задржува сопствените прекини на ред)", "defaultSaveLocation": "Стандардна локација за зачувување", "defaultSaveLocationSourceFileFolder": "Папка на датотеката со титл/видео", "defaultSaveLocationLastUsedFolder": "Последно користена папка", @@ -3023,6 +3388,7 @@ "defaultSaveLocationSubtitleFileFolder": "Папка на датотеката со титл", "defaultSaveLocationCustomFolder": "Прилагодена папка", "subtitleGridFormattingHideTags": "Скриј ознаки", + "subtitleGridFormattingShowFormattingKeepTags": "Прикажи форматирање, задржи невизуелни ознаки", "musicSymbolsToReplace": "Музички симболи за замена (одделени со запирка)", "musicSymbol": "Музички симбол", "colorWordsPerMinute": "Обои збори/мин ако е превисоко", @@ -3033,7 +3399,6 @@ "dialogStyleDashBothLinesWithSpace": "Трка двете линии со простор", "dialogStyleDashBothLinesWithoutSpace": "Трка двете линии без простор", "continuationStyle": "Стил на продолжување", - "continuationStyleNone": "Никако", "continuationStyleNoneTrailingDots": "Никако, точки за паузи (само задни)", "continuationStyleNoneLeadingTrailingDots": "Никако, точки за паузи", "continuationStyleNoneTrailingEllipsis": "Никако, троеточие за паузи (само задни)", @@ -3044,7 +3409,6 @@ "continuationStyleLeadingTrailingEllipsis": "Троеточие", "continuationStyleLeadingTrailingDash": "Трка", "continuationStyleLeadingTrailingDashDots": "Трка, но точки за паузи", - "continuationStyleCustom": "Прилагодено", "cpsLineLengthStyle": "Cps/должина на линија", "cpsLineLengthStyleCalcAll": "Броење на сите знаци", "cpsLineLengthStyleCalcNoSpaceCpsOnly": "Броење на сите освен простор, само cps", @@ -3057,27 +3421,11 @@ "cpsLineLengthStyleCalcNoSpaceOrPunctuationCpsOnly": "Без простор или пунктуација, само CPS", "cpsLineLengthStyleCalcIgnoreArabicDiacritics": "Игнорирај арапски диакритички знаци", "cpsLineLengthStyleCalcIgnoreArabicDiacriticsNoSpace": "Игнорирај арапски диакритички знаци, без простор", - "timeCodeModeHhMmSsMs": "HH:MM:SS:MS", - "timeCodeModeHhMmSsFf": "HH:MM:SS:FF", - "splitBehaviorPrevious": "Додај празнина лево од точката за поделба (фокус десно)", - "splitBehaviorHalf": "Додај празнина во центарот на точката за поделба (фокус лево)", - "splitBehaviorNext": "Додај празнина десно од точката за поделба (фокус лево)", - "subtitleListActionNothing": "Ништо", - "subtitleListActionVideoGoToPositionAndPause": "Оди на позицијата во видеото и паузирај", - "subtitleListActionVideoGoToPositionAndPlay": "Оди на позицијата во видеото и пушти", "subtitleListActionVideoGoToPositionAndPlayCurrentAndPause": "Оди на позицијата во видеото, пушти тековното и паузирај", - "subtitleListActionEditText": "Оди до полето за уредување на текстот", "subtitleListActionVideoGoToPositionMinus1SecAndPause": "Оди на позицијата во видеото - 1 с и паузирај", "subtitleListActionVideoGoToPositionMinusHalfSecAndPause": "Оди на позицијата во видеото - 0,5 с и паузирај", "subtitleListActionVideoGoToPositionMinus1SecAndPlay": "Оди на позицијата во видеото - 1 с и пушти", - "subtitleListActionEditTextAndPause": "Оди до полето за уредување, па паузирај на позицијата во видеото", - "autoBackupEveryMinute": "Секој минут", - "autoBackupEveryXthMinute": "Секој {0} минут", - "profiles": "Профили", - "autoBackupDeleteAfterXMonths": "Избриши автоматските резерви по {0} месеци", "searchSettingsDotDotDot": "Пребарај поставки...", - "syntaxColoring": "Боја на синтакса", - "waveformSpectrogram": "Волнова форма/спектрограм", "network": "Мрежа", "fileTypeAssociations": "Поврзувања со типови датотеки", "textBoxColorTags": "Означувања за боја (HTML/ASSA) во полето за субтитри", @@ -3110,10 +3458,9 @@ "darkThemeForegroundColor": "Боја на преден план во темна тема", "darkThemeBackgroundColor": "Боја на позадина во темна тема", "showGridLines": "Прикажи линии во мрежата", - "resetSettings": "Ресетирање на поставките?", - "resetSettingsDetail": "Ова ќе ги ресетира сите поставки на нивните стандардни вредности.\n\nПродолжи?", "showHorizontalLineAboveToolbar": "Прикажи хоризонтална линија над алатникот", "bookmarkColor": "Боја на ознака", + "spellCheckHighlightColor": "Боја на истакнување при проверка на правопис", "singleLineMaxLength": "Максимална должина на една линија", "optimalCharsPerSec": "Оптимални знаци/сек", "maxCharsPerSec": "Максимум знаци/сек", @@ -3125,13 +3472,21 @@ "unbreakSubtitlesShortThan": "Одвој субтитри подолги од", "newEmptyDefaultMs": "Стандардно ново траење на субтитри (мс)", "timeCodeUpDownStepMs": "Чекор за време горе/долу (ms)", + "moveSelectedLinesStepMs": "Чекор на кратенката за поместување избрани линии (ms)", + "moveStartEndStepMs": "Чекор на кратенката за поместување почеток/крај (ms)", + "moveLinesShortenNeighbor": "Поместување линии: скрати ја претходната/следната линија наместо да се преклопи", "promptBeforeDelete": "Потврда пред бришење", "rememberPositionAndSize": "Запомни позиција и големина на прозорецот", + "titleBarFullFileName": "Прикажи целосна патека на датотеката во насловната лента", + "showRecentFiles": "Прикажи скорешни датотеки (за повторно отворање)", "openLastFileOnStart": "Отвори последната отворена датотека при старт", "autoSave": "Автоматско зачувување (зачувај ја отворената датотека при уредување)", "autoBackupOn": "Автоматско бекапирање", "autoBackupIntervalMinutes": "Интервал за автоматско бекапирање (минути)", "autoBackupDeleteAfterDays": "Задржување на автоматски резерви (денови)", + "settingsBackupOn": "Автоматска резервна копија на подесувањата", + "settingsBackupIntervalDays": "Интервал за резервна копија на подесувањата (денови, 0 = при секое стартување)", + "settingsBackupMaxCount": "Број на резервни копии на подесувањата за чување", "autoConvertToUtf8": "Автоматски конвертирај кодирање во UTF-8 при отварање", "autoTrimWhiteSpace": "Автоматски отстранувај празни места", "defaultEncoding": "Стандардно кодирање", @@ -3145,23 +3500,14 @@ "colorGapTooShort": "Обојување ако празниот простор е премногу краток", "errorBackgroundColor": "Боја на позадина за грешка", "waveformDrawGridLines": "Цртај линии на мрежата", - "waveformCenterVideoPosition": "Центрирај позицијата на видеото", + "waveformUseSkiaRenderer": "Користи експериментален брз рендерер", "waveformCenterVideoPositionAlsoWhenPaused": "Центрирај ја позицијата на видеото и додека е паузирано", + "waveformSelectCurrentSubtitleWhilePaused": "Избери го тековниот титл и кога е паузирано", + "waveformRememberZoom": "Запомни зумирање (хоризонтално и вертикално)", "waveformShowToolbar": "Прикажи алатник", "waveformShowToolbarEdit": "Уреди алатник...", "waveformShowToolbarEditLabel": "Ставки на лентата со алатки", "waveformSpectrogramCombinedWaveformHeight": "Волнова форма/спектрограм комбинирано, висина на волнова форма %", - "showWaveformToolbarPlay": "Алатник: прикажи копче за репродукција", - "showWaveformToolbarRepeat": "Алатник: прикажи копче за повторување", - "showWaveformToolbarRemoveBlankLines": "Алатник: прикажи копче за отстранување празни линии", - "showWaveformToolbarNew": "Алатник: прикажи копче за нов субтитр", - "showWaveformToolbarSetStart": "Алатник: прикажи копче за поставување почеток", - "showWaveformToolbarSetEnd": "Алатник: прикажи копче за поставување крај", - "showWaveformToolbarStartAndOffsetTheRest": "Алатник: прикажи копче за поставување почеток и поместување на останатото", - "showWaveformToolbarHorizontalZoom": "Алатник: прикажи лизгач за хоризонтален зум", - "showWaveformToolbarVerticalZoom": "Алатник: прикажи лизгач за вертикален зум", - "showWaveformToolbarVideoPositionSlider": "Алатник: прикажи лизгач за позиција на видеото", - "showWaveformToolbarPlaybackSpeed": "Алатник: прикажи брзина на репродукција", "waveformFocusTextboxAfterInsertNew": "Фокусирај полето за текст по внесување нов субтитр", "waveformInvertMouseWheel": "Инвертирај мишињевото колеџо", "waveformMouseWheelSetsVideoPosition": "Тркалцето на глувчето ја поставува позицијата на видеото", @@ -3179,19 +3525,17 @@ "waveformSelectedColor": "Боја за избрани делови од волната форма", "downloadFfmpeg": "Преземи ffmpeg", "proxyAddress": "Адреса на прокси", - "username": "Корисничко име", "password": "Лозинка", "defaultFormat": "Стандарден формат", "defaultSaveAsFormat": "Стандарден формат за \"Зачувај како\"", "favoriteSubtitleFormats": "Омилени формати за субтитри", "favoriteLanguages": "Омилени јазици", "webVttUseXTimestampMap": "WebVTT: користи X-TIMESTAMP-MAP (помести временски кодови при вчитување)", - "showStopButton": "Прикажи копче за стоп", - "showFullscreenButton": "Прикажи копче за цел екран", "autoOpenVideoFile": "Автоматски отвори видео датотека при отворање на субтитри", "downloadMpv": "Преземи mpv", "downloadVlc": "Преземи VLC", "allowSingleLetterShortcutsInTextbox": "Дозволи кратки пречки од една буква во полето за текст", + "allowTextNavigationShortcutsInTextbox": "Дозволи кратенки на копчињата за навигација низ текст (Ctrl+Left/Right, Home/End) во полето за текст", "spellCheckEnglishTreatInApostropheAsIng": "Проверка на правопис: Третирај зборови што завршуваат на 'in'' како 'ing' (само англиски)", "goToLineNumberSetsVideoPosition": "Оди до бројот на линија и постави позиција на видеото", "adjustAllTimesRememberLineSelectionChoice": "Прилагодете ги сите времиња, запомни изборот за линии", @@ -3202,6 +3546,7 @@ "showAssaLayer": "Прикажи слој за ASSA", "waveformCursorColor": "Боја на курсор/глава на волната форма", "waveformShotChangeColor": "Боја на промена на сцена во волната форма", + "waveformGridColor": "Боја на мрежата на формата на бран", "waveformParagraphLeftColor": "Боја на левата граница на параграфот во волната форма", "waveformParagraphRightColor": "Боја на десната граница на параграфот во волната форма", "waveformFancyHighColor": "Боја на висок дел во волната форма", @@ -3229,23 +3574,26 @@ "resetSyntaxColoring": "Ресетирај бојање на синтакса", "resetWaveform": "Ресетирај волната форма", "resetRules": "Ресетирај правила", - "useFrameMode": "Користи режим со кадри (hh.mm.ss.ff)", + "timeCodeMode": "Режим на временски код", + "timeCodeModeTime": "Време (hh:mm:ss,zzz)", + "timeCodeModeFrames": "Кадри (hh:mm:ss:ff)", + "timeCodeModeFrameNumbers": "Броеви на кадри", + "frameModeSnapToVideoFrames": "Чувај ги времињата на видео кадри (не кадри во секоја секунда)", "textBoxLimitNewLines": "Ограничи бројот на линии во полето за субтитри", - "mpvVideoOutput": "Видео излез (mpv)", "mpvOpenGl": "libmpv - OpenGL", "mpvSoftwareRendering": "libmpv - Софтверско рендерирање (бавно)", + "mpvMetalRendering": "libmpv - Metal (експериментално, бара рестартирање)", "mpvWidRendering": "libmpv - Приказ со Native Window ID (побрзо на Windows/Linux)", "waveFormsAndSpectrogramFoldersContainsX": "\"Waveforms\" и \"spectrogram\" папките содржат {0}", "deleteWaveformAndSpectrogramFoldersQuestion": "Избриши датотеки од \"Waveforms\" и \"Spectrogram\"?", "waveformGenerateSpectrogram": "Генерирај спектрограма", "waveformAutoGenerate": "Автоматски генерирај форма на бран при отворање видео", "waveformSpectrogramMode": "Режим на спектрограма", - "waveformCenterOnSingleClick": "Центрирај со еднократен клик", - "waveformSingleClickSelectsSubtitle": "Избери субтитр со еднократен клик", "waveformRightClickSelectsSubtitle": "Избери субтитр со десен клик", - "waveformPauseOnSingleClick": "Паузирај со еднократен клик", "waveformDrawStyle": "Стил за цртање на волната форма", "vlcWidRendering": "libVLC - Приказ со Native Window ID", + "ffmpegSoftwareRendering": "FFmpeg", + "downloadFfmpegLibs": "Преземи FFmpeg библиотеки (за FFmpeg видеоплеерот)", "subtitleGridEnterKeyAction": "Акција за Enter копче во мрежата со субтитри", "subtitleSingleClickAction": "Акција за еднократен клик во мрежата со субтитри", "subtitleDoubleClickAction": "Акција за двоклик во мрежата со субтитри", @@ -3270,23 +3618,24 @@ "saveAsBehaviorUseVideoFileName": "Користи име на видео датотека", "saveAsBehaviorUseSubtitleFileName": "Користи име на датотека за субтитр", "saveAsAppendLanguageCodeTwoLetter": "Двојбуквено", - "saveAsAppendLanguageCodeThreeLetter": "Требуквено", "saveAsAppendLanguageCodeLanguageName": "Име на јазикот", "splitOddLineActionWeightTop": "Тежина горе", "splitOddLineActionWeightBottom": "Тежина долу", "splitOddLinesAction": "Акција за поделба на непарни линии", "ocrUseWordSplitList": "OCR: користи листа за поделба на зборови", "ocrGuessUnknownWords": "OCR: обиди се да погодиш непознати зборови", + "spellCheckRememberUseAlwaysList": "Проверка на правопис: запомни ја листата „Користи секогаш“", + "fixShortDisplayTimesAllowMoveStartTime": "Поправи чести грешки: поправката на кратки времиња за приказ може да го помести почетното време", "speechToTextSelectedLinesPromptFirstTimeOnly": "Говор-во-текст: избрани линии, прашај за јазик/мотор само прв пат", "multipleReplaceShowDotDotDotButtons": "Повеќекратна замена: прикажи копчиња за контекст мени", "gridFocusTextboxAfterInsertNew": "Мрежа: фокусирај поле за текст по внесување нов субтитр", + "undoRedoGoToChangedLine": "Врати/повтори: оди на променетата линија (и позицијата во видеото)", "textToSpeechPromptSkipNoiseLines": "Текст во говор: прашај за прескокнување линии со звук/музика", "textToSpeechPromptDetectSpeakers": "Текст во говор: прашај за откривање имиња на говорници во текстот", "useFocusedButtonBackgroundColor": "Користи боја на позадина за фокусирано копче", "focusedButtonBackgroundColor": "Боја на позадина за фокусирано копче", "forceCrLfOnSave": "Присилно CR+LF при зачувување (текстуални датотеки за субтитри)", - "showWaveformToolbarPlayNext": "Алатник: прикажи копче за репродукција на следното", - "showWaveformToolbarPlaySelection": "Алатник: прикажи копче за репродукција на изборот", + "linuxClipboardUseExternalTool": "Копирај текст преку xclip/wl-copy (ги задржува знаците со акценти)", "showFormatLimitWarning": "Предупреди при зачувување кога линиите ги надминуваат ограничувањата на форматот (на пр. SCC 32 знаци/линија)", "textBoxButtonShowAutoBreak": "Поле за текст: прикажи копче за автоматско преломување", "textBoxButtonShowUnbreak": "Поле за текст: прикажи копче за од-случување", @@ -3298,12 +3647,18 @@ "waveformDoubleClickAction": "Акција за двоклик во волната форма (по акција за еднократен клик)", "allSettings": "Сите поставки", "uiScale": "Големина на UI (%)", + "fontScale": "Скала на фонтот (%)", + "textSelectionOpacity": "Непрозирност на избраниот текст (%)", "waveformToolbarItems": "Предмети во алатникот за волната форма", "matchIconColorToDarkTheme": "Согласувај боја на икона со преден план на темната тема", "subtitlePreviewProperties": "Својства за преглед на субтитри", "usePositionFromSubtitleFile": "Користи позиција од датотеката со титл (TTML/PAC/EBU STL)", "marginIsPartOfSubtitleArea": "Маргината е дел од областа на титлот", "textJustify": "Порамни ги линиите обострано", + "deinterlace": "Деинтерлејсирај видео", + "deinterlaceOff": "Исклучено", + "deinterlaceAuto": "Автоматски (само интерлејсирани кадри)", + "deinterlaceAlways": "Секогаш", "pixelWidthInfo": "Зелени линии = максимум ширина | Црвена област = текстот надминува ограничувањето", "spellCheckEngineHunSpelll": "Hunspell", "spellCheckEngineMsWord": "MS Word", @@ -3313,128 +3668,102 @@ "waveformSaveAsCustomTheme": "Зачувај како прилагодена тема...", "waveformLoadThemeDotDotDot": "Вчитај тема...", "waveformExportThemeDotDotDot": "Извези тема...", + "waveformSaveCustomThemeTitle": "Зачувај прилагодена тема", + "waveformThemeFileType": "Тема за форма на бран на Subtitle Edit", + "waveformThemeHighContrast": "Висок контраст", + "waveformThemeOceanBlue": "Океанско сино", + "waveformThemeWarmSunset": "Топол залез", + "waveformThemeForest": "Шума", + "waveformThemeMidnightPurple": "Полноќно виолетово", + "waveformThemeRetroAmber": "Ретро килибар", + "waveformThemeIce": "Мраз", "minGapFrames": "Најмала празнина (кадри)", "waveformSnapToFrames": "Приклучи на кадри", "fixCommonErrorsSkipStep1": "Поправи чести грешки: прескокни чекор 1 (избери поправки)", "showPluginsMenu": "Прикажи мени Приклучоци", "fullscreenHideControls": "Скриј контроли за видео во цел екран", + "showSecondarySubtitleDialog": "Прикажи дијалог за стил при отворање втора датотека со титл", + "rememberSecondarySubtitleFile": "Запомни ја втората датотека со титл", "writeToolsLog": "Запиши дневник за алатки", "waveformExtractAudioFormat": "Формат за извлекување аудио", "waveformExtractAudioSampleRate": "Брзина на семплирање за извлекување аудио", "waveformExtractAudioSampleRateOriginal": "Оригинал (задржи извор)", "waveformExtractAudioBitRate": "Битрата за извлекување аудио (MP3/M4A)", + "ffmpegUseCenterChannelOnly": "Користи само предниот централен канал за 5.1 аудио (FFmpeg)", "waveformSetVideoPositionOnMoveStartEnd": "Постави позиција на видео при поместување старт/крај", - "saveAsAppendLanguageCodeThreeLetterBibliographic": "Тричлен (ISO 639-2/B)", "textToSpeechPromptMergeContinuationLines": "Текст во говор: побарај спојување на линии за продолжување" }, "shortcuts": { "generalUnbreakNoSpaceCjk": "Отстрани прелом без празно место (CJK)", - "title": "Прекратници", + "generalToggleBreak": "Смени дели/обратно дели", "searchShortcuts": "Пребарај прекратници...", - "filter": "Филтер", - "categoryGeneral": "Општо", "categorySubtitleGridAndTextBox": "Преглед на листа со субтитри & поле за текст", "categorySubtitleGrid": "Преглед на листа со субтитри", - "categoryWaveform": "Волнова форма", "categoryTextBox": "Поле за текст", "categoryFile": "Датотека", "categoryVideo": "Видео", "categorySync": "Синхронизација", - "categoryTranslate": "Превод", - "categorySearch": "Пребарување", - "categoryTools": "Алатки", "categoryAi": "AI", + "categoryCustom": "Прилагодено", + "editCustomShortcut": "Уреди прилагодена кратенка", + "customShortcutTextBoxKeyHint": "Тука користи копче со Ctrl/Alt - обично копче повеќе нема да пишува во полето за текст.", + "customShortcutNumberX": "Прилагодена кратенка #{0}", + "customShortcutNumberXY": "Прилагодена кратенка #{0}: {1}", + "customShortcutStep": "Чекор", + "customShortcutSteps": "Чекори", + "customShortcutStepRunCommand": "Изврши команда", + "customShortcutStepInsertText": "Вметни текст", + "customShortcutStepReplace": "Најди и замени", + "customShortcutInsertAtCursor": "На курсорот", + "customShortcutInsertAtStart": "На почетокот на текстот", + "customShortcutInsertAtEnd": "На крајот на текстот", + "customShortcutSummaryRunCommandX": "Изврши: {0}", + "customShortcutSummaryInsertXY": "Вметни „{0}“ ({1})", + "customShortcutSummaryReplaceXY": "Замени „{0}“ со „{1}“", + "customShortcutSummaryReplaceRegexXY": "Замени regex „{0}“ со „{1}“", + "customShortcutPickCommand": "Избери команда за извршување.", + "customShortcutEnterText": "Внеси текст за вметнување.", + "customShortcutEnterFind": "Внеси текст за пребарување.", + "customShortcutInvalidRegexX": "Невалиден регуларен израз: {0}", + "customShortcutStepsHint": "Чекорите се извршуваат од горе надолу. Текстуалните чекори работат на избраните линии; командите што отвораат прозорец продолжуваат откако тој ќе се затвори.", "activeIn": "Активна во", "activeInEverywhere": "Насекаде", - "generalSetupLikeSe4": "Постави како Subtitle Edit 4 (тема, кратенки, правила за замена)", - "generalMergeSelectedLines": "Спој избрани линии", - "generalMergeWithPrevious": "Спој со претходната", - "generalMergeWithNext": "Спој со следната", - "generalMergeWithPreviousAndUnbreak": "Спој со претходната и отстрани прелом", - "generalMergeWithNextAndUnbreak": "Спој со следната и отстрани прелом", - "generalMergeWithPreviousAndAutoBreak": "Спој со претходната и автоматски прелом", - "generalMergeWithNextAndAutoBreak": "Спој со следната и автоматски прелом", - "generalMergeSelectedLinesAndAutoBreak": "Спој избрани линии и автоматски прелом", "generalMergeSelectedLinesAndUnbreak": "Спој избрани линии и отстрани прелом", "generalMergeSelectedLinesAndUnbreakCjk": "Спој избрани линии и отстрани прелом CJK", - "generalMergeSelectedLinesOnlyFirstText": "Спој избрани линии, само првиот текст", "generalMergeSelectedLinesBilingual": "Спој избрани линии двјојно", - "generalMergeWithPreviousBilingual": "Спој со претходната двјојно", - "generalMergeWithNextBilingual": "Спој со следната двјојно", - "generalMergeOriginalAndTranslation": "Спој оригинал и превод", "generalToggleTranslationMode": "Промени режим на превод", - "generalSwitchOriginalAndTranslation": "Смени оригинал и превод", - "generalSwitchOriginalAndTranslationTextBoxes": "Смени полетата за оригинал и превод", "generalToggleTranslationAndOriginalInPreviews": "Смени превод и оригинал во прегледот на видео/аудио", "generalChooseLayout": "Избери распоред", - "generalLayoutChooseX": "Распоред {0}", - "generalPlayFirstSelected": "Пушти првото избрано", - "generalGoToFirstSelectedLine": "Оди до првата избрана линија", - "generalGoToNextEmptyLine": "Оди до следната празна линија", "generalGoToNextSubtitle": "Оди до следниот субтитр", "generalGoToNextSubtitlePlayTranslate": "Оди до следниот субтитр (пушти превод)", "generalGoToNextSubtitleCursorAtEnd": "Оди до следниот субтитр (курсорот е на крај)", "generalGoToPrevSubtitle": "Оди до претходниот субтитр", "generalGoToPrevSubtitlePlayTranslate": "Оди до претходниот субтитр (пушти превод)", - "generalGoToStartOfCurrentSubtitle": "Оди до почетокот на тековниот субтитр", - "generalGoToEndOfCurrentSubtitle": "Оди до крајот на тековниот субтитр", - "generalGoToPreviousSubtitleAndFocusVideo": "Оди до претходниот субтитр и фокусирај видеото", - "generalGoToNextSubtitleAndFocusVideo": "Оди до следниот субтитр и фокусирај видеото", - "generalGoToPrevSubtitleAndPlay": "Оди до претходниот субтитр и пушти", - "generalGoToNextSubtitleAndPlay": "Оди до следниот субтитр и пушти", - "generalGoToPreviousSubtitleAndFocusWaveform": "Оди до претходниот субтитр и фокусирај волната форма", - "generalGoToNextSubtitleAndFocusWaveform": "Оди до следниот субтитр и фокусирај волната форма", - "generalGoToLineNumber": "Оди до бројот на линија", "generalGoToFirstLine": "Оди на првата линија", "generalGoToLastLine": "Оди на последната линија", "alsoSetVideoPosition": "Постави ја и позицијата во видеото", "generalGoToVideoPosition": "Оди до позиција на видеото", "generalToggleItalic": "Превклучи курзив", "generalToggleBold": "Превклучи задебелување", - "generalToggleBookmarks": "Превклучи ознаки", - "generalFocusTextBox": "Фокусирај поле за текст", - "generalToggleBookmarksWithText": "Превклучи ознаки со текст", - "generalEditBookmarks": "Уреди ознаки", - "fileNew": "Ново", "generalToggleUnderline": "Смени подвлечено", + "generalToggleBox": "Смени кутија (EBU STL)", "fileOpen": "Отвори", "fileOpenKeepVideo": "Отвори (запази видео)", "fileSave": "Зачувај", "fileSaveAs": "Зачувај како", - "fileSaveAll": "Зачувај сите", "fileSaveOriginal": "Зачувај оригинал", - "fileSaveOriginalAs": "Зачувај оригинал како", - "fileOpenOriginalSubtitle": "Отвори оригинален субтитр", - "fileCloseOriginalSubtitle": "Затвори оригинален субтитр", - "fileTranslatedSubtitle": "Преведен субтитр", "fileCompare": "Сравни", "fileStatistics": "Статистика", "fileImportPlainText": "Увези обичен текст", - "fileImportBluRaySupForOcr": "Увези Blu-ray SUP за OCR", - "fileImportBluRaySupForEdit": "Увези Blu-ray SUP за уредување", - "fileImportTimeCodes": "Увези врем. коди", "fileExportEbuStl": "Извези EBU STL", "fileExportPac": "Извези PAC", - "fileExportEdlClipName": "Извези име на EDL клип", - "fileExportPlainText": "Извези обичен текст", - "fileExportCustomTextFormat1": "Извези прилагоден текстуален формат 1", - "fileExportCustomTextFormat2": "Извези прилагоден текстуален формат 2", - "fileExportCustomTextFormat3": "Извези прилагоден текстуален формат 3", "fileExit": "Излез", "openSeDataFolder": "Отвори папка со податоци за Subtitle Edit", - "editFind": "Најди", - "editFindNext": "Најди следно", "editFindPrevious": "Најди претходно", - "editReplace": "Замени", - "editMultipleReplace": "Повеќекратна замена", - "editModifySelection": "Модифицирај избор", - "listSelectAll": "Избери ги сите", - "listSelectFirst": "Избери првата", - "listSelectLast": "Избери последната", "listInverseSelection": "Инвертирај избор", "listDeleteSelection": "Избриши избор", + "deleteSelectionEverywhere": "Избриши избор (насекаде)", "rippleDeleteSelection": "Рипл ефект при бришење на избор", - "settings": "Поставки", "assigned": "Доделено", "unassigned": "Недоделено", "pressedKeyX": "Притиснат клуч: {0}", @@ -3442,6 +3771,7 @@ "detectKey": "Откриј клуч", "control": "Ctrl", "alt": "Alt", + "altGr": "AltGr", "win": "Win", "shift": "Shift", "controlMac": "⌃", @@ -3452,11 +3782,14 @@ "resetShortcutsDetail": "Дали сакате да ги ресетирате сите пречки на стандардните вредности?", "togglePlayPause": "Пушти/паузирај", "toggleLockTimeCodes": "Заклучи врем. коди", + "toggleTimeCodeMode": "Смени режим на временски код (време/кадри/броеви на кадри)", + "toggleScreenPrivacy": "Смени приватност на екранот (скриј имиња на датотеки, потоа и текстови)", + "screenPrivacyFileNamesHidden": "Приватност на екранот: имињата на датотеките се скриени", + "screenPrivacyFileNamesAndTextsHidden": "Приватност на екранот: имињата на датотеките и текстовите се скриени", + "screenPrivacyOff": "Приватност на екранот: исклучено", "duplicateSelectedLines": "Дуплирај избрани линии", "sourceView": "Преглед на извор", - "showAlignmentPicker": "Позиционирање", "addOrEditBookmark": "Додај или уреди ознака", - "listBookmarks": "Листа со ознаки", "toggleBookmark": "Превклучи ознака (избрани линии, без текст)", "toggleForcedSelectedLines": "Смени присилно (избрани линии)", "goToNextBookmark": "Оди до следната ознака", @@ -3467,12 +3800,12 @@ "waveformSetStartAndSetEndOfPreviousMinusGap": "Постави почеток и крај на претходната - празнина", "waveformSetEndAndStartOfNextAfterGap": "Постави крај и почеток на следната + празнина", "waveformSetEndAndStartOfNextAfterGapAndGoToNext": "Постави крај и почеток на следната + празнина и оди до следната", - "fetchFirstWordFromNextSubtitle": "Земи прва реч од следниот субтитр", "moveLastWordToNextSubtitle": "Помести последна реч до следниот субтитр", "moveLastWordFromFirstLineDownCurrentSubtitle": "Помести последна реч од првата линија надолу (тековен субтитр)", "moveFirstWordFromNextLineUpCurrentSubtitle": "Помести прва реч од следната линија нагоре (тековен субтитр)", "moveTextFromCursorToNextAndGoToNext": "Помести го текстот по курсорот во следниот титл и оди на следниот", "moveTextFromCursorToNextAndGoToNextAndPlay": "Помести го текстот по курсорот во следниот титл, оди на следниот и пушти", + "moveTextFromCursorToNextAndGoToNextAndPlayAndPause": "Помести го текстот по курсорот во следниот титл, оди на следниот, пушти го и паузирај на неговиот крај", "breakAtFirstSpaceFromCursor": "Прекини на првото празно место од позицијата на курсорот", "breakAtFirstSpaceFromCursorAndGoToNext": "Прекини на првото празно место од позицијата на курсорот и оди на следна", "toggleFocusGridAndWaveform": "Превклучи фокус помеѓу мрежата со субтитри и волната форма/спектрограм", @@ -3484,7 +3817,6 @@ "videoGoToPreviousTimeCode": "Оди на претходен временски код", "videoGoToNextTimeCode": "Оди на следен временски код", "goToNextLineAndSetVideoPosition": "Оди до следниот субтитр и постави позиција на видеото", - "textBoxDeleteSelectionNoClipboard": "Поле за текст: Избриши избор (без таблата со исечоци)", "textBoxDeleteForward": "Поле за текст: Избриши го изборот или следниот знак (бришење нанапред)", "textBoxCut": "Поле за текст: Сечење", "textBoxCut2": "Поле за текст: Сечење (алтернативно)", @@ -3503,6 +3835,7 @@ "listViewColumnTextUp": "Колона, текст нагоре", "listViewColumnTextDown": "Колона, текст надолу", "autoTranslateSelectedLines": "Автоматски преведи избрани линии...", + "autoTranslateSelectedLinesNoPrompt": "Автоматски преведи избрани линии (без прашање, користи последен мотор/јазици)", "setAssaResolution": "Постави ASSA резолуција (PlayResX/PlayResY)", "setShortcutForX": "Постави пречка за \"{0}\"", "commandFileNewKeepVideo": "Ново (запази видео)", @@ -3510,27 +3843,20 @@ "fileCloseOriginal": "Затвори оригинал", "fileCloseTranslation": "Затвори превод", "restoreAutoBackup": "Врати автоматско бекапирање", - "openContainingFolder": "Отвори папка со датотека", "importTimeCodes": "Увези врем. коди", "importSubtitleWithManuallyChosenEncoding": "Увези субтитр со рачно избрано кодирање", "exportBluRaySup": "Извези Blu-ray SUP", "exportCustomTextFormat": "Извези прилагоден текстуален формат", "exportPlainText": "Извези обичен текст", - "showHistory": "Прикажи историја", "toggleRightToLeft": "Превклучи десно-налево", "modifySelection": "Модифицирај избор", "adjustDurations": "Прилагоди траења", "applyDurationLimits": "Примени граници на траење", "batchConvert": "Групно конвертирање", - "bridgeGaps": "Пополнувај празнини", "applyMinGap": "Примени минимум празнина", - "fixCommonErrors": "Поправи чести грешки", "makeEmptyTranslationFromCurrentSubtitle": "Создади празен превод од тековниот субтитр", - "mergeLinesWithSameText": "Спој линии со истиот текст", - "mergeLinesWithSameTimeCodes": "Спој линии со исти врем. кодови", "splitBreakLongLines": "Подели/израмни долги линии", "mergeShortLines": "Спој кратки линии", - "removeTextForHearingImpaired": "Отстрани текст за глуви", "joinSubtitles": "Спои субтитри", "splitSubtitle": "Подели субтитр", "spellCheck": "Провери правопис", @@ -3552,7 +3878,6 @@ "adjustAllTimes": "Прилагоди сите времиња", "visualSync": "Визуелна синхронизација", "translateViaCopyPaste": "Преведи со копирање‑вставување", - "shortcuts": "Прекратници", "wordLists": "Листи со зборови", "chooseUiLanguage": "Изберете јазик за UI", "chooseRuleProfile": "Изберете профил за правила", @@ -3563,6 +3888,7 @@ "textBoxItalic": "Поле за текст: курзив", "textBoxBold": "Поле за текст задебелено", "textBoxUnderline": "Поле за текст подвлечено", + "textBoxBox": "Кутија во полето за текст (EBU STL)", "resetWaveformZoomAndSpeed": "Ресетирај зум и брзина на репродукција на волната форма", "togglePlaybackSpeed": "Промени брзина на репродукција", "playbackSpeedSlower": "Брзина полека", @@ -3572,22 +3898,28 @@ "seekSilence": "Тргни до тишина", "waveformGuessStart": "Погоди почетно време од формата на бран", "waveformGuessEnd": "Погоди крајно време од формата на бран", + "waveformGuessStartAndEnd": "Погоди почетно и крајно време од формата на бран", "setVideoPositionCurrentSubtitleStart": "Постави позиција на видеото на почетокот на тековниот субтитр", "goToSubtitlePositionAndPause": "Оди на позицијата на титлот и паузирај", "setVideoPositionCurrentSubtitleEnd": "Постави позиција на видеото на крајот на тековниот субтитр", "toggleAudioTracks": "Превклучи аудио траки", - "listErrors": "Листа на грешки", "goToNextError": "Оди до следната грешка", "goToPreviousError": "Оди до претходната грешка", "addNameToNameList": "Додај име во листата со имиња", "findDoubleWords": "Најди дупли зборови", "findDoubleLines": "Најди дупли линии", "colorX": "Боја {0}", - "removeColor": "Отстрани боја", "surroundWith": "Овие со...", "surroundWithXY": "Овие со {0}/{1}", - "repeatLine": "Повтори линија", "surroundWithNumberX": "Опкружи со #{0}", + "surroundWithBehavior": "Однесување", + "surroundWithBehaviorToggle": "Смени (додај или отстрани ако постои)", + "surroundWithBehaviorAdd": "Додај (секој пат)", + "surroundWithBehaviorRemove": "Отстрани (сите)", + "surroundWithBehaviorRemoveOnce": "Отстрани (по една секој пат)", + "surroundWithWorksOn": "Работи на", + "surroundWithScopeSelectionOrText": "Избор, инаку целиот текст", + "surroundWithScopeEachLine": "Секоја линија", "searchVia": "Пребарај преку", "searchViaX": "Пребарај преку {0}", "searchViaNumberX": "Пребарај преку #{0}", @@ -3612,6 +3944,11 @@ "copySubtitlePathToClipboard": "Копирај патека до субтитрот во таблата со исечоци", "copySubtitleOriginalPathToClipboard": "Копирај патека до оригиналниот субтитр во таблата со исечоци", "focusTextBox": "Фокусирај поле за текст", + "focusSubtitleListView": "Фокусирај ја листата со титлови", + "focusWaveform": "Фокусирај ја формата на бран", + "focusOriginalTextBox": "Фокусирај го полето за оригинален текст", + "textBoxGoToStart": "Поле за текст, оди на почеток", + "textBoxGoToEnd": "Поле за текст, оди на крај", "sortByStartTime": "Сортирај по време за \"Прикажи\"", "sortByEndTime": "Сортирај по време за \"Скриј\"", "sortByDuration": "Подреди по времетраење", @@ -3634,7 +3971,6 @@ "assaStyles": "ASSA стилови", "assaProperties": "ASSA својства", "assaAttachments": "ASSA прилози", - "assaVideoColorPicker": "ASSA избирач на видео боја", "recalculateDurationSelectedLines": "Пресметај повторно траење (избрани линии)", "toggleWaveformAndSpectrogramHeight": "Превклучи поделено височина за волна форма/спектрограма", "toggleSpectrogramStyle": "Превклучи стил на спектрограм", @@ -3658,9 +3994,9 @@ "selectionToSentenceCase": "Поле за текст: Избраното во Реченична форма", "selectionToggleCasing": "Поле за текст: Смени големина на букви (ГОЛЕМИ -> мали -> Со Голема Почетна)", "googleIt": "Пребарај на Google (избран текст)", - "setActorXY": "Постави актер {0}: {1}", + "setActorX": "Постави актер: {0}", "setNewActor": "Постави нов актер...", - "setActorSlotXTitle": "Слот за актер {0}", + "setStyleX": "Постави стил: {0}", "toggleDialogDashes": "Смени црти за дијалог", "mergeWithLineBeforeAsDialog": "Спој со линија пред како дијалог", "mergeWithLineAfterAsDialog": "Спој со линија после како дијалог", @@ -3671,13 +4007,27 @@ "moveStartOneFrameBackKeepGapPrev": "Помести старт за еден кадар назад (задржи празнина до претходно ако е блиску)", "moveStartOneFrameForwardKeepGapPrev": "Помести старт за еден кадар напред (задржи празнина до претходно ако е блиску)", "moveEndOneFrameBackKeepGapNext": "Помести крај за еден кадар назад (задржи празнина до следно ако е блиску)", - "moveEndOneFrameForwardKeepGapNext": "Помести крај за еден кадар напред (задржи празнина до следно ако е блиску)" + "moveEndOneFrameForwardKeepGapNext": "Помести крај за еден кадар напред (задржи празнина до следно ако е блиску)", + "moveStartXMsBack": "Помести почеток X ms назад (X се поставува во Подесувања, исто и во Визуелна синхронизација)", + "moveStartXMsForward": "Помести почеток X ms напред (X се поставува во Подесувања, исто и во Визуелна синхронизација)", + "moveEndXMsBack": "Помести крај X ms назад (X се поставува во Подесувања, исто и во Визуелна синхронизација)", + "moveEndXMsForward": "Помести крај X ms напред (X се поставува во Подесувања, исто и во Визуелна синхронизација)", + "moveSelectedLinesXMsBack": "Помести избрани линии X ms назад (X се поставува во Подесувања)", + "moveSelectedLinesXMsForward": "Помести избрани линии X ms напред (X се поставува во Подесувања)", + "moveSelectedLinesAndForwardXMsBack": "Помести избрани линии и сите следни X ms назад (X се поставува во Подесувања)", + "moveSelectedLinesAndForwardXMsForward": "Помести избрани линии и сите следни X ms напред (X се поставува во Подесувања)", + "moveAllLinesXMsBack": "Помести ги сите линии X ms назад (X се поставува во Подесувања)", + "moveAllLinesXMsForward": "Помести ги сите линии X ms напред (X се поставува во Подесувања)", + "moveSelectedLinesCustomMsBack": "Помести избрани линии, прилагодени милисекунди ({0:#,###,##0}) назад, {1}", + "moveSelectedLinesCustomMsForward": "Помести избрани линии, прилагодени милисекунди ({0:#,###,##0}) напред, {1}", + "moveSelectedLinesAndForwardCustomMsBack": "Помести избрани линии и сите следни, прилагодени милисекунди ({0:#,###,##0}) назад, {1}", + "moveSelectedLinesAndForwardCustomMsForward": "Помести избрани линии и сите следни, прилагодени милисекунди ({0:#,###,##0}) напред, {1}", + "moveAllLinesCustomMsBack": "Помести ги сите линии, прилагодени милисекунди ({0:#,###,##0}) назад, {1}", + "moveAllLinesCustomMsForward": "Помести ги сите линии, прилагодени милисекунди ({0:#,###,##0}) напред, {1}" }, "wordLists": { "title": "Списоци со зборови", "addName": "Додади име", - "addWord": "Додади збор", - "addPair": "Додади пар", "nameAndIgnoreList": "Листа со имиња/игнорирања", "userWords": "Корисничка листа со зборови", "ocrFixList": "Листа за поправки на OCR", @@ -3730,6 +4080,8 @@ "nOcrDatabase": "nOCR база", "drawMode": "Режим на цртање:", "addNewCharcter": "Додади нов знак", + "addNOcrCharacterTitle": "Додај nOCR знак за линија {0}, знак {1} од {2} со базата „{3}“", + "addBinaryOcrCharacterTitle": "Додај бинарен OCR знак за линија {0}, знак {1} од {2} со базата „{3}“", "lineIndexX": "Линија {0}", "inspectNOcrAdditions": "Прегледај нови nOCR дополнувања", "ocrSelectedLines": "OCR избрани линии", @@ -3777,6 +4129,8 @@ "binaryImageCompareInspectImageMatches": "„Binary image compare“ - Прегледај совпаѓања на слика", "saveBlankTextTitle": "Да се зачува празен текст?", "saveBlankTextPrompt": "Да се зачува празен текст за сликата?", + "drawnLinesDoNotMatchTitle": "Линиите не одговараат на сликата", + "drawnLinesDoNotMatchPrompt": "Некои од нацртаните линии не одговараат на сликата (зелените линии мора да бидат на знакот, црвените линии мора да бидат надвор од него), па овој знак нема да биде препознаен.\n\nДа се зачува сепак?", "yesAndNeverAskAgain": "Да, и повеќе не прашувај", "importTextFromSubtitleDotDotDot": "Увези текст од титл...", "importTextFromSubtitleOverwritePrompt": "Некои линии веќе имаат OCR текст. Да ги заменам постоечките?", @@ -3803,7 +4157,6 @@ "vobSubColorsTitle": "VobSub/DVD бои", "vobSubColorsHeader": "Прилагоди ги четирите VobSub бои", "vobSubColorsDescription": "VobSub/DVD титловите користат четири индексирани бои: позадина, шаблон, нагласување 1 и нагласување 2. Изберете ја секоја боја подолу за да ја заобиколите вградената палета - корисно кога оригиналните бои даваат слаб OCR контраст.", - "vobSubColorBackground": "Позадина", "vobSubColorPattern": "Шаблон", "vobSubColorEmphasis1": "Нагласување 1", "vobSubColorEmphasis2": "Нагласување 2", @@ -3837,9 +4190,26 @@ "crispEmbedSettingsTitle": "Подесувања за CrispEmbed", "crispEmbedDescription": "Локален OCR мотор со повеќе модели во позадина - преземи ги моторот и моделите тука.", "trainNOcrDatabase": "Тренирај nOCR база...", + "optimizeNOcrDatabase": "Оптимизирај nOCR база...", + "optimizeNOcrDatabaseQuestionXY": "Да се отстранат линиите што не ги разликуваат знаците од слични знаци во nOCR базата „{0}“?\n\nОва ја прави базата помала и OCR побрз, обично со помалку грешки. Трае до една минута.\n\nРезервна копија се зачувува како „{1}“.", + "optimizingNOcrDatabaseXY": "Оптимизирање на nOCR базата... {0}/{1}", + "nOcrDatabaseOptimizedXYZ": "nOCR базата „{0}“ е оптимизирана: {1:#,##0} -> {2:#,##0} линии.", "startTraining": "Започни тренирање", "abortTraining": "Прекини тренирање", "trainingOptions": "Опции за тренирање", + "trainNOcrSubtitle": "Исцртува знаци во инсталираните фонтови и го учи nOCR на нивните форми. Избери ги фонтовите што ги користат твоите титлови.", + "xFontsSelected": "{0:#,##0} избрани", + "subtitleFonts": "Фонтови на титловите", + "emptyDatabase": "Празна база", + "startFromDatabase": "Започни од", + "startFromDatabaseHint": "Додај ги обучените знаци во копија од постоечка база (препорачано) или започни од празна", + "fontSizes": "Големини на фонт", + "fontSizesHint": "Големини на фонт во пиксели за исцртување, одделени со запирки, на пр. „30, 40“", + "numberOfLineSegmentsHint": "Линии што се проверуваат по знак - повеќе е построго, но побавно", + "enterDatabaseName": "Внеси име за базата", + "trainingFontXSizeYCharacterZ": "Обука на „{0}“ на {1} px: {2}", + "xLearned": "{0:#,##0} научени", + "xSkipped": "{0:#,##0} прескокнати", "charactersToTrain": "Знаци за тренирање", "letterCombinationsToTrain": "Комбинации букви што може да завршат во една слика", "importCharactersFromSubtitleFile": "Увези знаци од датотека со титл...", @@ -3866,6 +4236,8 @@ "drawZoomOut": "Зумирај оддалечи (Ctrl+-)", "drawResetView": "Ресетирај поглед (Ctrl+0)", "drawToggleGrid": "Вклучи/исклучи мрежа (Ctrl+G)", + "drawTogglePreview": "Смени преглед (F9)", + "drawPreviewFailed": "Прегледот не успеа - потребен е ffmpeg со libass (филтер subtitles)", "drawCopyToClipboard": "Копирај во таблата за исечоци (Ctrl+C)", "drawShapes": "Форми", "drawSelectedPoint": "Избран пункт", @@ -3873,16 +4245,86 @@ "drawSelectedLayer": "Избран слој", "drawToolX": "Алат: {0}", "drawHelpText": "Кликни за додавање точки • Enter/F8 за затворање на форма • Shift+влечи за поместување • Ctrl+скрол за зум", + "drawMoveToLayer": "Помести во слој", + "drawFlipHorizontal": "Преврти хоризонтално", + "drawFlipVertical": "Преврти вертикално", + "drawHideShape": "Скриј форма", + "drawShowShape": "Прикажи форма", + "drawHideLayer": "Скриј слој", + "drawShowLayer": "Прикажи слој", + "drawDeleteLayer": "Избриши слој", + "drawDeletePoint": "Избриши точка", + "drawCancelDrawing": "Откажи цртање", + "drawLayerX": "Слој {0}", + "drawImportSvg": "Увези SVG слика...", + "drawRotateClockwise": "Ротирај 90° во насока на стрелките на часовникот", + "drawConvertToCurve": "Конвертирај сегмент во крива", + "drawBackground": "Позадина", + "drawPosition": "Позиција", + "drawZoom": "Зумирање", + "drawShapeTool": "Алатка за форми", + "drawColorPickerTool": "Земи боја од форма или од позадината", + "drawShapeLibrary": "Библиотека со форми", + "drawShapeLibraryHint": "Повлечи на платното за да поставиш форма или кликни за стандардна големина. Shift ги задржува пропорциите.", + "drawAddToLibrary": "Додај во библиотеката со форми...", + "drawRemoveFromLibrary": "Отстрани од библиотеката со форми", + "drawShapeName": "Име на форма", + "drawCategorySpeechBubbles": "Балони за говор", + "drawCategoryArrows": "Стрелки", + "drawCategoryBasicShapes": "Основни форми", + "drawCategorySymbols": "Симболи", + "drawCategoryMyShapes": "Мои форми", + "drawShapeSpeechBubble": "Балон за говор", + "drawShapeSpeechBubbleRight": "Балон за говор, опашка десно", + "drawShapeSpeechBubbleUp": "Балон за говор, опашка горе", + "drawShapeOvalBubble": "Овален балон за говор", + "drawShapeBoxBubble": "Правоаголен балон за говор", + "drawShapeThoughtBubble": "Балон за мисла", + "drawShapeShoutBubble": "Балон за викање", + "drawShapeCaptionBox": "Кутија за натпис", + "drawShapeArrow": "Стрелка", + "drawShapeDoubleArrow": "Двојна стрелка", + "drawShapeChevron": "Шеврон", + "drawShapeCurvedArrow": "Закривена стрелка", + "drawShapeRoundedRectangle": "Заоблен правоаголник", + "drawShapeTriangle": "Триаголник", + "drawShapeDiamond": "Ромб", + "drawShapePentagon": "Петаголник", + "drawShapeHexagon": "Шестаголник", + "drawShapeOctagon": "Осумаголник", + "drawShapeStar": "Ѕвезда", + "drawShapeRing": "Прстен", + "drawShapePlus": "Плус", + "drawShapeBanner": "Банер", + "drawShapeHeart": "Срце", + "drawShapeCheck": "Штиклирање", + "drawShapeMusicNote": "Музичка нота", + "drawShapeMusicNotes": "Музички ноти", + "drawShapeLightning": "Молња", + "drawShapeCloud": "Облак", + "drawShapeMoon": "Месечина", + "drawShapeBadge": "Значка", + "drawSize": "Големина", + "drawLayer": "Слој", + "drawBackgroundVideoFrame": "Видео кадар (тековна позиција)", + "drawBackgroundVideoFrameAt": "Видео кадар на...", + "drawBackgroundImage": "Датотека со слика...", + "drawBackgroundNone": "Без позадина", + "drawBackgroundStretch": "Растегни ја сликата на платното", + "drawBackgroundOpacity": "Непрозирност на позадината", + "drawBackgroundVideoFrameFailed": "Не можеше да се земе видео кадар на {0}.", + "drawImages": "Слики", + "drawConvertToLine": "Конвертирај сегмент во линија", + "drawConvertShapeToCurves": "Конвертирај ги сите линии во криви", + "drawConvertShapeToLines": "Конвертирај ги сите криви во линии", + "drawRotateCounterClockwise": "Ротирај 90° спротивно од стрелките на часовникот", + "drawSvgImages": "SVG слики", + "drawSvgNoShapes": "Не се пронајдени пополнети форми или потези во SVG датотеката.", "progressBarTitle": "ASSA лента за напредок", "progressBarSettings": "Лента за напредок", - "progressBarPosition": "Позиција", - "progressBarBottom": "Дно", - "progressBarTop": "Врв", "mouseOverColor": "Боја при поминување со глувче", "clickedColor": "Боја при клик", "progressBarForeColor": "Боја на преднина", - "progressBarBackColor": "Боја на позадина", - "progressBarStyle": "Стил", "progressBarSquareCorners": "Квадратни агли", "progressBarRoundedCorners": "Заоблени агли", "progressBarChapters": "Поглавја", @@ -3891,8 +4333,6 @@ "progressBarXAdjustment": "X поправка", "progressBarYAdjustment": "Y поправка", "progressBarTextAlignment": "Порамнување на текст", - "progressBarTakePosFromVideo": "Примени позиција од видеото", - "progressBarPreview": "Преглед", "fontCollectorTitle": "Собирач на фонтови", "fontCollectorUsedIn": "Користен во", "fontCollectorFontFiles": "Датотека(и) со фонт", @@ -3939,9 +4379,7 @@ "resolutionResamplerVideoDiffers": "Резолуцијата на титлот ({0}x{1}) се разликува од резолуцијата на видеото ({2}x{3}).\nДа се промени титлот за да одговара на видеото?", "resolutionResamplerAskOnVideoOpen": "Прашај кога ќе се отвори видео со различна резолуција", "backgroundBoxGenerator": "Генерирај позадинско поле", - "backgroundBoxPadding": "Подлога", "backgroundBoxFillWidth": "Пополни ширина", - "backgroundBoxBoxColor": "Боја на поле", "backgroundBoxRadius": "Радиус", "backgroundBoxCircle": "Круг", "backgroundBoxSpikes": "Шипки", @@ -3953,12 +4391,9 @@ "backgroundBoxTornPaperDouble": "Разрезан хартија (горе и долу)", "backgroundBoxStarburst": "Звезден експлозија", "backgroundBoxScroll": "Свиткање / Папир", - "stylesTitle": "Напредни стилови за Sub Station Alpha", "stylesInFile": "Стилови во датотеката", "stylesSaved": "Стиловите се зачувани", - "category": "Категорија", "allCategories": "[Сите]", - "defaultCategory": "Стандардна", "newCategory": "Нова категорија...", "renameCategory": "Преименувај категорија...", "deleteCategory": "Избриши категорија", @@ -3978,13 +4413,26 @@ "graphics": "Графика", "copyToStorageStyles": "Копирај во складишните стилови", "copyToFileStyles": "Копирај во датотечните стилови", + "styleXAlreadyExistsInStorage": "Стил со име „{0}“ веќе постои во складиштето.\n\nДа се презапише или да се задржат двата (копијата добива ново име)?", + "styleXAlreadyExistsInFile": "Стил со име „{0}“ веќе постои во датотеката.\n\nДа се презапише или да се задржат двата (копијата добива ново име)?", + "styleNameCannotBeEmpty": "Името на стилот не може да биде празно.", + "styleNameXIsUsedMoreThanOnce": "Името на стилот „{0}“ го користат повеќе од еден стил во датотеката.", + "overwrite": "Презапиши", + "keepBoth": "Задржи ги двата", + "doThisForAllConflictingStyles": "Направи го ова за сите конфликтни стилови", "setStyleAsDefault": "Постави стил како стандард", "takeUsagesFromDotDotDot": "Применувања од...", "replaceStyleWithDotDotDot": "Замени стил со...", "noAttachmentsFound": "Не се пронајдени прилози во избраната ASSA датотека.", "deleteStyleQuestion": "Да се избрише стилот?", "deleteStylesQuestion": "Да се избришат стиловите?", + "deleteXStylesQuestion": "Дали сакаш да избришеш {0} стилови?", + "deleteXStylesFromStorageQuestion": "Дали сакаш да избришеш {0} стилови од складиштето?", + "deleteStyleXFromStorageQuestion": "Дали сакаш да го избришеш стилот „{0}“ од складиштето?", + "deleteStyleXFromFileQuestion": "Дали сакаш да го избришеш стилот „{0}“ од тековната датотека?", "openStyleImportFile": "Отвори титл датотека за увезување стилови", + "styleImportFiles": "Датотеки со стилови (.ass, .sty, Subtitle Edit 4 категории .template)", + "ssaStyleImportFiles": "Датотеки со стилови (.ssa, .ass, .sty)", "primary": "Главен", "secondary": "Секундарен", "applyOverrideTags": "Примени надворешни тагови", @@ -3999,7 +4447,6 @@ "spaceIncrease": "Зголемување на растојание", "popIn": "Поп влез", "spinIn": "Вртете се", - "playCurrent": "Пушти тековно", "setPosition": "Постави позиција", "rotation": "Ротација", "scaleX": "Скала X", @@ -4043,10 +4490,8 @@ "advancedEffectOldMovieDescription": "Симулира стар 35mm проектор со шум, винета и гребнатини", "advancedEffectNeonBurst": "Неон експлозија", "advancedEffectNeonBurstDescription": "Високоенергетски титри во стилот 'Shorts' со неон сјај и анимација за 'pop' скалирање", - "advancedEffectSettings": "Поставки", "advancedEffectSnowFlakeCount": "Број на снежни кристали", "advancedEffectStarfieldStarCount": "Број на ѕвезди", - "advancedEffectStarfieldSpeed": "Брзина", "advancedEffectGlitch": "Глитч", "advancedEffectGlitchDescription": "Дигитален глитч со хроматска аберација, бели светкави и дисторзиони слојеви", "advancedEffectBounceIn": "Отскок", @@ -4055,8 +4500,6 @@ "advancedEffectFirefliesDescription": "Топли жутокафени точки со сјај, кои плават органски низ екранот со пулсирачки светлина", "advancedEffectMatrix": "Матрикс", "advancedEffectMatrixDescription": "Падачки зелени колони со карактери, текстот се гради буква по буква од дождот", - "advancedEffectAudioBars": "Аудио ленти (форма на бран)", - "advancedEffectAudioBarsDescription": "Анимирани ленти на еквалайзер управувани со аудио брановата форма — лентите се лизгаат налево како бран и пулсираат со аудиото", "advancedEffectAudioPulse": "Аудио текст пулс", "advancedEffectAudioPulseDescription": "Текстот светли и пулсира со гласноста на аудиото — тихите делови се нормален текст, гласните пикови додаваат обоен ореол и малку зголемување", "advancedEffectConfetti": "Конфети експлозија", @@ -4070,7 +4513,6 @@ "advancedEffectFancyKaraokeGlow": "Сјај на активен збор", "advancedEffectFancyKaraokeActiveColor": "Активна боја", "advancedEffectFancyKaraokeInactiveColor": "Неактивна боја", - "advancedEffectFancyKaraokeInactiveOpacity": "Неактивна провидност (0-255)", "advancedEffectWordSpacingPixels": "Растојание (пиксели)", "advancedEffectSlowZoomOut": "Бавно оддалечување", "advancedEffectSlowZoomOutDescription": "Текстот почнува малку поголем и нежно се намалува до нормална големина за време на титлот", @@ -4086,7 +4528,6 @@ "advancedEffectSlideInLeftDescription": "Текстот се вметнува од лево надвор од екранот, задржува се, потоа излегува лево", "advancedEffectSlideInRight": "Слајд од десно", "advancedEffectSlideInRightDescription": "Текстот се вметнува од десно надвор од екранот, задржува се, потоа излегува десно", - "advancedEffectFadeInOut": "Fade in/out", "advancedEffectFadeInOutDescription": "Текстот се појавува во почетокот и исчезува на крајот на секој титл", "advancedEffectWordFlip3D": "3D превртување на зборови", "advancedEffectWordFlip3DDescription": "Секој збор се превиткува со 3D превртување и еластичен потскок додека се изговара — модерен стил на титлови за „Shorts“", @@ -4118,7 +4559,6 @@ "noPluginsInstalled": "Нема инсталирани приклучоци", "getPluginsOnline": "Земи приклучоци онлајн...", "getPluginsTitle": "Земи приклучоци", - "enabled": "Овозможено", "disabled": "Оневозможено", "notSupportedOnThisOs": "Не е поддржано на овој оперативен систем", "removePluginXConfirm": "Да се отстрани приклучокот „{0}“? Ова ја брише папката на приклучокот:{1}", @@ -4135,17 +4575,19 @@ "downloadingXPercent": "Преземање {0}%...", "pluginXInstalled": "„{0}“ инсталиран.", "couldNotInstallPluginX": "Не можеше да се инсталира „{0}“: {1}", - "notInstalled": "Не е инсталирано", "install": "Инсталирај", "reinstall": "Реинсталирај", - "update": "Ажурирај", "installedX": "Инсталирано ({0})", "updateAvailableXToY": "Достапно е ажурирање ({0} → {1})", "applyPluginToWhichLinesX": "Примени „{0}“ на:", "applyToSelectedLinesX": "Избрани линии ({0})", "applyToAllLinesX": "Сите линии ({0})", "updateAllXAvailable": "Ажурирај ги сите ({0})", - "updatingXOfY": "Ажурирање {0}/{1}..." + "updatingXOfY": "Ажурирање {0}/{1}...", + "doNotAskAgain": "Не прашувај повторно (смени во Управувај со приклучоци)", + "applyPluginsTo": "Примени приклучоци на", + "askEachTime": "Прашај секој пат", + "allLines": "Сите линии" }, "errorList": { "title": "Листај грешки", diff --git a/src/ui/Assets/Languages/Norwegian.json b/src/ui/Assets/Languages/Norwegian.json index eae594c586a..044b2811c33 100644 --- a/src/ui/Assets/Languages/Norwegian.json +++ b/src/ui/Assets/Languages/Norwegian.json @@ -2,7 +2,7 @@ "Title": "Subtitle Edit", "TranslatedBy": "Gemini", "CultureName": "nb-NO", - "version": "v5.2.0-rc7", + "version": "v5.3.0", "General": { "AssistedMove": "Assistert flytting", "AssistedMoveChooseMove": "Klikk et alternativ eller trykk nummeret for å flytte ord", @@ -71,6 +71,24 @@ "Abort": "Avbryt", "Actor": "Skuespiller", "Actors": "Skuespillere", + "SetActorDotDotDot": "Angi skuespiller...", + "ActorPickerTitle": "Angi skuespiller", + "ActorPickerFilterHint": "Skriv for å filtrere, eller skriv navnet på en ny skuespiller", + "ActorPickerKeysHint": "Trykk et tall eller Enter for å angi skuespilleren. Alt+Opp/Ned endrer rekkefølgen, Delete fjerner skuespilleren.", + "ActorPickerNewActorX": "Ny skuespiller \"{0}\"", + "ActorPickerLinesSelectedX": "Valgte linjer: {0}", + "SetStyleDotDotDot": "Angi stil...", + "StylePickerTitle": "Angi stil", + "StylePickerFilterHint": "Skriv for å filtrere, eller skriv navnet på en ny stil", + "StylePickerKeysHint": "Trykk et tall (1-9, 0) for å angi stilen på alle valgte linjer.", + "StylePickerNewStyleX": "Ny stil \"{0}\"", + "StylePickerNewStyleInfo": "Trykk Enter for å legge til denne stilen med standardinnstillinger og angi den på de valgte linjene. Bruk \"Behandle stiler...\" for å endre skrift, farger og posisjon senere.", + "StylePickerCurrentStyleX": "Gjeldende stil: {0}", + "StylePickerLinesUsingStyleX": "Linjer som bruker denne stilen: {0}", + "StylePickerMarginsXYZ": "Venstre {0}, høyre {1}, vertikal {2}", + "StylePickerSetStyle": "_Angi stil", + "StylePickerManageStyles": "Behandle stiler...", + "StylePickerManageStylesHint": "Lukk dette vinduet og åpne stilbehandleren for å redigere skrifter, farger, posisjon og mer", "Add": "Legg til", "AddDotDotDot": "Legg til...", "AddToNamesListCaseSensitive": "Legg til i navneliste (skiller mellom store/små bokstaver)", @@ -79,7 +97,6 @@ "AdjustAlpha": "Juster alfa (gjennomsiktighet)", "AdjustDisplayDuration": "Juster visningsvarighet", "Adjustment": "Justering", - "Advanced": "Avansert", "AdvancedSettings": "Avanserte innstillinger", "After": "Etter", "Alignment": "Justering", @@ -115,7 +132,6 @@ "BatchMode": "Satsvis modus (batch)", "BdnXml": "BDN/xml", "Before": "Før", - "BeforeX": "Før \"{0}\"", "Beginning": "Begynnelse", "BluRaySup": "Blu-ray (sup)", "Bold": "Fet", @@ -127,7 +143,6 @@ "BookmarkEdit": "Rediger bokmerke", "BookmarkEditForSelectedLinesX": "Rediger bokmerke for {0} valgte linjer", "BookmarksList": "Vis bokmerker", - "BorderColor": "Kantfarge", "BorderStyle": "Kantstil", "BottomCenter": "Bunn-Senter", "BottomLeft": "Bunn-Venstre", @@ -152,7 +167,6 @@ "ChangeCasing": "Endre bokstavstørrelse", "ChangeFormatting": "Endre formatering", "ChangeFrameRate": "Endre bildefrekvens", - "ChangeLanguageFilter": "Endre språkfilter...", "ChangeOnce": "Endre én gang", "ChangeSpeed": "Endre hastighet", "Character": "Tegn", @@ -177,7 +191,6 @@ "ConversionCancelledByUser": "Konvertering avbrutt av bruker", "Convert": "Konverter", "Converted": "Konvertert", - "ConvertingDotDotDot": "Konverterer...", "ConvertingXofYDotDoDot": "Konverterer {0:#,###,##0}/{1:#,###,##0}...", "Copy": "Kopier", "CopyImageToClipboard": "Kopier bilde til utklippstavle", @@ -185,7 +198,6 @@ "CouldNotSaveFileXErrorY": "Kunne ikke lagre fil \"{0}\". Feil: {1}", "Count": "Antall", "Cps": "Tegn/sek", - "CurrentSubtitle": "Nåværende undertekst", "CurrentVideoPosition": "Nåværende videoposisjon", "Custom": "Egendefinert", "Cut": "Klipp ut", @@ -231,7 +243,6 @@ "Expand": "Utvid", "Export": "Eksporter", "ExportDotDotDot": "Eksporter...", - "ExportToX": "Eksporter til {0}", "ExtendAfter": "Utvid/forkort til linjen etter", "ExtendBefore": "Utvid/forkort til linjen før", "ExtendSelectedLinesToNextShotChangeOrNextSubtitle": "Utvid valgte linjer til neste sceneskifte (eller neste undertekst)", @@ -248,7 +259,6 @@ "FileExtension": "Filtype", "FileName": "Filnavn", "FileNameX": "Filnavn: {0}", - "FileNameXAndSize": "Filnavn: {0} ({1})", "FileSaved": "Fil lagret", "FileSavedToX": "Fil lagret i {0}", "FileXAlreadyExists": "Filen \"{0}\" finnes allerede.", @@ -258,8 +268,6 @@ "Find": "Finn", "FindNext": "Finn neste", "FindTextX": "Finn tekst - {0}", - "FiveHundredMilliseconds": "500 millisekunder", - "FiveSeconds": "Fem sekunder", "Fix": "Fiks", "FixCommonErrors": "Fiks vanlige feil", "FixRightToLeft": "Fiks høyre-til-venstre", @@ -283,14 +291,12 @@ "FoundXInLineYZ": "Fant \"{0}\" i linje {1}, posisjon {2}", "FoundXMatches": "Fant {0} treff", "FrameRate": "Bildefrekvens", - "FrameRateX": "Bildefrekvens: {0:0.0###}", "Frames": "Bilder (frames)", "From": "Fra", "FromCurrentVideoPosition": "fra nåværende videoposisjon", "FullScreen": "Fullskjerm", "Gap": "Mellomrom", "General": "Generelt", - "GeneralText": "Generell tekst", "Generate": "Generer", "GenerateImportShotChanges": "Generer/importer sceneskifter", "Generating": "Genererer...", @@ -308,12 +314,8 @@ "Help": "Hjelp", "Hex": "Hex", "Hide": "Skjul", - "HidePreview": "Skjul forhåndsvisning", "History": "Historikk", "Horizontal": "Horisontal", - "HourMinutesSecondsDecimalSeparatorMilliseconds": "Time:min:sek{0}ms", - "HourMinutesSecondsFrames": "Time:min:sek:bilder", - "Ip": "IP", "Id": "ID", "Image": "Bilde", "ImageSaved": "Bilde lagret", @@ -372,6 +374,7 @@ "MergeSelectedLinesDialog": "Slå sammen valgte linjer som dialog", "MergeWithLineAfterAndAutoBreak": "Slå sammen med linjen etter og automatisk linjedeling", "MergeWithLineAfterKeepBreaks": "Slå sammen med linjen etter (behold linjeskift)", + "MergeWithLineAfterAndUnbreak": "Slå sammen med neste linje og fjern linjedeling", "MergeWithLineBeforeAndAutoBreak": "Slå sammen med linjen før og automatisk linjedeling", "MergeWithLineBeforeKeepBreaks": "Slå sammen med linjen før (behold linjeskift)", "MiddleCenter": "Midt-Senter", @@ -386,11 +389,11 @@ "MoveAllShotChangeOneFrameForward": "Flytt alle sceneskifter ett bilde fremover", "MultiMode": "Multimodus", "MoveDown": "Flytt ned", + "Increase": "Øk", + "Decrease": "Reduser", "MoveUp": "Flytt opp", "MultipleReplace": "Søk og erstatt flere", - "Mute": "Stum", "Name": "Navn", - "Negative": "Negativ", "New": "Ny", "NewDotDotDot": "Ny...", "NewProfile": "Ny profil", @@ -418,20 +421,15 @@ "OffsetY": "Forskyvning Y", "Ok": "_OK", "OneFile": "Én fil", - "OneHundredMilliseconds": "100 millisekunder", - "OneSecond": "Ett sekund", "OneSecondBack": "Ett sekund bakover", "OneSecondForward": "Ett sekund fremover", - "OnlineVideoFeatureNotAvailable": "Funksjonen er ikke tilgjengelig for online video", "OpenContainingFolder": "Åpne inneholdende mappe", "OpenDictionaryFolder": "Åpne ordboksmappe", "OpenFile": "Åpne fil", "OpenImageBasedSubtitle": "Åpne bildebasert undertekst", "OpenImageFile": "Åpne bildefil", - "OpenOriginalSubtitleFile": "Åpne original undertekstfil...", "OpenOriginalSubtitleFileTitle": "Åpne original undertekstfil", "OpenOutputFolder": "Åpne utdatamappe", - "OpenSubtitle": "Åpne undertekst...", "OpenSubtitleFileTitle": "Åpne undertekstfil", "OpenSubtitles": "Åpne undertekster...", "OpenVideoFile": "Åpne videofil...", @@ -444,11 +442,6 @@ "OutlineWidth": "Omrissbredde", "OutputFolder": "Utdatamappe", "OutputProperties": "Utdataegenskaper", - "Overlap": "Overlapp", - "OverlapNextX": "Overlapp neste ({0:#;##0.###})", - "OverlapPreviousLineX": "Overlapp forrige linje ({0:#;##0.###})", - "OverlapStartAndEnd": "Overlapp start og slutt", - "OverlapX": "Overlapp ({0:#;##0.###})", "OverwriteExistingFiles": "Overskriv eksisterende filer", "OverwriteFilesInFolderX": "Overskriv filer i mappe: \"{0}\"", "OverwriteQuestion": "Overskrive?", @@ -461,6 +454,7 @@ "Percent": "Prosent", "PickLayer": "Velg lag", "PickOllamaModel": "Velg Ollama-modell", + "PickModel": "Velg modell", "LlamaCppUseRemoteServer": "Bruk ekstern server (URL)", "PickOutputFolder": "Velg utdatamappe", "PickResolutionFromCurrentVideo": "Hent oppløsning fra gjeldende video", @@ -504,7 +498,6 @@ "Refresh": "Oppdater", "Region": "Region", "RegularExpression": "Regulært uttrykk (Regex)", - "RegularExpressionIsNotValid": "Regulært uttrykk er ikke gyldig!", "RelativeToCurrentVideoPosition": "Relativt til gjeldende videoposisjon", "Remove": "Fjern", "RemoveAlignment": "Fjern justering", @@ -529,7 +522,7 @@ "Reset": "Tilbakestill", "ResizePanels": "Endre størrelse på paneler", "Resolution": "Oppløsning", - "ResolutionX": "Oppløsning: {0}", + "Retry": "Prøv igjen", "ReverseRightToLeftStartEnd": "Reverser høyre-til-venstre start/slutt", "Right": "Høyre", "RightToLeft": "Høyre til venstre", @@ -567,8 +560,6 @@ "SelectedlinesX": "Valgte linjer: {0}", "Sensitivity": "Følsomhet", "Separator": "Skilletegn", - "SessionKey": "Øktnøkkel", - "SessionKeyGenerate": "Generer ny nøkkel", "SetEnd": "Sett slutt", "SetEndAndGoToNext": "Sett slutt og gå til neste", "SetEndAddNewAndGoToNew": "Sett slutt, legg til ny og gå til ny", @@ -591,8 +582,9 @@ "ShadowColor": "Skyggefarge", "ShadowWidth": "Skyggebredde", "ShiftLineBy": "Forskyv linje med", + "ShotIn": "Skudd inn", + "ShotOut": "Skudd ut", "Shortcut": "Hurtigtast", - "ShortcutX": "Hurtigtast: {0}", "Shortcuts": "Hurtigtaster", "Show": "Vis", "ShowActorColumn": "Vis \"Skuespiller\"-kolonne", @@ -603,17 +595,19 @@ "ShowStartColumn": "Vis \"Start\"-kolonne", "ShowHideColumn": "Vis \"Skjul\"-kolonne", "ShowHistory": "Vis historikk", + "ClearHistory": "Tøm historikk", + "RemoveFromHistory": "Fjern fra historikk", "ShowLayerColumn": "Vis \"Lag\"-kolonne", "ShowPreview": "Vis forhåndsvisning", "ShowShotChangesList": "Vis liste over sceneskifter", + "ShowShotInColumn": "Vis kolonnen \"Skudd inn\"", + "ShowShotOutColumn": "Vis kolonnen \"Skudd ut\"", "ShowStyleColumn": "Vis \"Stil\"-kolonne", "ShowTeletext": "Vis tekst-TV-kolonne i listevisning", "ShowTimeCodes": "Vis tidskoder", "ShowWpmColumn": "Vis \"Ord/min\"-kolonne", "ShowPixelWidthColumn": "Vis \"Pikselbredde\"-kolonne", "Shrink": "Krymp", - "SingleBox": "Enkeltboks", - "SingleLineLengths": "Enkeltlinjelengde:", "SingleMode": "Enkeltmodus", "TextTotalLength": "Tekst - total lengde", "TextSingleLineMaxLength": "Tekst - maks enkeltlinjelengde", @@ -662,16 +656,16 @@ "Strikeout": "Gjennomstrek", "Style": "Stil", "StyleExaggeration": "Stiloverdrivelse", - "StyleLanguage": "Stil / Språk", "Styles": "Stiler", "SubtitleFile": "Undertekstfil", + "SubtitleFileChangedOutsideX": "Filen \"{0}\" ble endret utenfor Subtitle Edit.\n\nVil du laste den inn på nytt og miste endringene dine?", "SubtitleFileName": "Undertekstfilnavn", "SubtitleFileSaved": "Undertekstfil lagret", "SubtitleFileSavedToX": "Undertekstfil lagret i {0}", "SubtitleFiles": "Undertekstfiler", "SubtitleFormats": "Undertekstformater", "SubtitleLoadedX": "Undertekst lastet: {0}", - "SubtitleXOfY": "Undertekst {0} av {1}", + "SubtitleReloadedChangedOutsideX": "Undertekst lastet inn på nytt (endret utenfor Subtitle Edit): {0}", "Suffix": "Suffiks", "Suggestions": "Forslag", "Sync": "Synkronisering", @@ -683,7 +677,6 @@ "TeletextColor": "Tekst-TV-farge", "TeletextLine": "Tekst-TV-linje", "TenHours": "10 timer", - "TenMilliseconds": "10 millisekunder", "Text": "Tekst", "TextColor": "Tekstfarge", "TextFiles": "Tekstfiler", @@ -710,9 +703,6 @@ "TopRight": "Topp-Høyre", "Top": "Øverst", "TotalAdjustmentX": "Total justering: {0}", - "TotalFramesX": "Totalt antall bilder: {0:#;##0.##}", - "TotalLengthX": "Total lengde: {0}", - "TotalLengthXSplitLine": "Total lengde: {0} (delt linje!)", "Translate": "Oversett", "TranslatePercentX": "Oversetter: {0}%", "TranslateRow": "Oversett rad", @@ -721,6 +711,7 @@ "Type": "Type", "Unbreak": "Fjern linjedeling", "UnbreakLines": "Fjern linjedeling på linjer", + "Undelete": "Angre sletting", "Underline": "Understrek", "Undo": "Angre", "Unknown": "Ukjent", @@ -740,13 +731,11 @@ "Usages": "Bruk", "Use": "Bruk", "UseAlways": "Bruk alltid", - "UseLargerFontForThisWindow": "Bruk større skrift i dette vinduet", "UseOnce": "Bruk én gang", "UseOutputFolder": "Bruk utdatamappe", "UseSourceFolder": "Bruk kildemappe", "UseSourceResolution": "Bruk kildeoppløsning", "UserName": "Brukernavn", - "UserNameAlreadyInUse": "Brukernavn allerede i bruk", "Vertical": "Vertikal", "Video100MsBack": "Video, 100 millisekunder bakover", "Video100MsForward": "Video, 100 millisekunder fremover", @@ -760,14 +749,25 @@ "VideoCustom3ForwardX": "Video, egendefinerte millisekunder ({0:#,###,##0}) fremover, 3", "VideoCustom4BackX": "Video, egendefinerte millisekunder ({0:#,###,##0}) bakover, 4", "VideoCustom4ForwardX": "Video, egendefinerte millisekunder ({0:#,###,##0}) fremover, 4", - "VideoEncodingX": "Videokoding: {0}", + "VideoCustom1BackAndPauseX": "Video, egendefinerte millisekunder ({0:#,###,##0}) tilbake og pause, 1", + "VideoCustom1ForwardAndPauseX": "Video, egendefinerte millisekunder ({0:#,###,##0}) fremover og pause, 1", + "VideoCustom2BackAndPauseX": "Video, egendefinerte millisekunder ({0:#,###,##0}) tilbake og pause, 2", + "VideoCustom2ForwardAndPauseX": "Video, egendefinerte millisekunder ({0:#,###,##0}) fremover og pause, 2", + "VideoCustom3BackAndPauseX": "Video, egendefinerte millisekunder ({0:#,###,##0}) tilbake og pause, 3", + "VideoCustom3ForwardAndPauseX": "Video, egendefinerte millisekunder ({0:#,###,##0}) fremover og pause, 3", + "VideoCustom4BackAndPauseX": "Video, egendefinerte millisekunder ({0:#,###,##0}) tilbake og pause, 4", + "VideoCustom4ForwardAndPauseX": "Video, egendefinerte millisekunder ({0:#,###,##0}) fremover og pause, 4", "VideoExtension": "Videofiltype", "VideoFile": "Videofil", "VideoFileGenerated": "Videofil generert", "VideoFileGeneratedX": "Videofil generert: \"{0}\"", + "VideoFilesGenerated": "Videofiler generert", + "XVideosGenerated": "{0} videoer generert", + "XOfYVideosGenerated": "{0} av {1} videoer generert", + "SavedInX": "Lagret i {0}", + "SavedInXFolders": "Lagret i {0} mapper", "VideoFiles": "Videofiler", "VideoAndAudioFiles": "Video- og lydfiler", - "VideoInformation": "Videoinfo", "VideoOffset": "Videoforskyvning", "VideoOneFrameBack": "Video, ett bilde bakover", "VideoOneFrameForward": "Video, ett bilde fremover", @@ -778,7 +778,6 @@ "VideoPlayer": "Videospiller", "VideoPosition": "Videoposisjon", "VideoResolution": "Videooppløsning", - "ViewX": "Vis {0}", "Visible": "Synlig", "Voice": "Stemme", "Volume": "Volum", @@ -787,9 +786,7 @@ "WaveformCenterOnVideoPosition": "Sentrer bølgeform på videoposisjon", "WaveformPasteFromClipboard": "Lim inn fra utklippstavle", "WaveformSpectrogram": "Bølgeform/spektrogram", - "WebServiceUrl": "Nettadresse for webtjeneste", "Width": "Bredde", - "WindowPositionAndSize": "Vindusposisjon og -størrelse", "WordsPerMin": "Ord/min", "PixelWidth": "Pikselbredde", "Wpm": "Ord/min", @@ -803,8 +800,6 @@ "NoBookmarksFound": "Ingen flere bokmerker funnet", "XRequiresAnApiKey": "{0} krever en API-nøkkel", "XRequiresAValidUrl": "{0} krever en gyldig URL (f.eks. \"http://...\" eller \"https://...\")", - "XSeconds": "{0:0.0##} sekunder", - "XSubtitles": "{0:#,###,##0} undertekster", "Yes": "Ja", "ZeroBytes": "0 byte", "CopyToClipboard": "Kopier til utklippstavle", @@ -826,10 +821,8 @@ "PlaybackSpeed": "Avspillingshastighet", "HoursMinutesSecondsMilliseconds": "TT:MM:SS.FFF", "ImageFiles": "Bildefiler", - "OpenAiCompatibleStt": "OpenAI-kompatibel STT", "OpenAiCompatibleSttEndpoint": "STT-endepunkt-URL", "OpenAiCompatibleSttApiKey": "API-nøkkel", - "OpenAiCompatibleSttModel": "Modell", "OpenAiCompatibleSttExtraHeaders": "Ekstra hoder", "OpenAiCompatibleSttTimeout": "Tidsavbrudd (sekunder)", "OpenAiCompatibleSttLanguage": "Språkhint", @@ -839,6 +832,9 @@ "DashScopeSttRegion": "Region", "DashScopeSttEnableWords": "Tidsstempler på ordnivå", "DashScopeSttRegionKeyHint": "Merk: API-nøkler for Alibaba Cloud Model Studio er regionsspesifikke - sørg for at den valgte regionen samsvarer med regionen der API-nøkkelen ble opprettet (Kina vs. internasjonal).", + "GoogleCloudSttProjectId": "Prosjekt-ID (kun for Application Default Credentials)", + "GoogleCloudSttBucketName": "Lagringsbøtte (tom = automatisk)", + "GoogleCloudSttDynamicBatching": "Dynamisk satsvis behandling (ca. 5x billigere, ingen garanti for ventetid)", "OnlineSttApiKeyMissing": "En API-nøkkel er påkrevd. Skriv inn API-nøkkelen din og prøv igjen.", "OpenAiCompatibleSttAutoTranscribeOnAudioSelection": "Transkriber nytt bølgeformutvalg automatisk via tale-til-tekst", "OpenAiCompatibleSttStream": "Strøm svar", @@ -848,11 +844,9 @@ "ConfigurationRequired": "Konfigurasjon kreves", "TranscriptionError": "Transkripsjonsfeil", "TranscriptionComplete": "Transkripsjon fullført", - "TranscribingWithOpenAi": "Transkriberer med OpenAI-tjenesten...", "ProcessingResponse": "Behandler svar...", "UnauthorizedApiKey": "Uautorisert: Ugyldig API-nøkkel eller utilstrekkelige tillatelser.", "RequestTimeout": "Forespørsel tidsavbrutt: Serveren brukte for lang tid på å svare.", - "NoSegmentsReturned": "Ingen segmenter returnert fra transkripsjonstjenesten.", "OpenAiCompatibleSttUrlMissing": "URL for OpenAI-kompatibelt STT-endepunkt er påkrevd. Skriv inn en URL og prøv igjen.", "OpenAiCompatibleSttUrlNotResponding": "Det OpenAI-kompatible STT-endepunktet svarte ikke:\n{0}\n\nKontroller URL-en og at serveren kjører.", "TranscriptionFailed": "Transkripsjon mislyktes", @@ -868,6 +862,9 @@ "CharactersPerSecond": "Tegn/sekund: {0}", "ChooseColumn": "Velg kolonne", "ColumnPaste": "Lim inn i kolonne", + "CommandPaletteTitle": "Kommandopalett", + "CommandPaletteSearchPlaceholder": "Skriv for å søke etter kommandoer...", + "CommandPaletteNoCommandsFound": "Ingen samsvarende kommandoer", "CreatedEmptyTranslation": "Opprettet tom oversettelse fra gjeldende undertekst", "DeleteText": "Slett tekst", "DeleteTextAndShiftCellsUp": "Slett tekst og flytt celler opp", @@ -906,18 +903,20 @@ "NumberOfLinesEvenlyDistributedX": "Fordelte {0} linjer jevnt", "OneLineCopiedFromOriginal": "Én linje kopiert fra original undertekst", "OneLineCopiedToOriginal": "Én linje kopiert til original undertekst", + "OnlineOnlyVideo": "Video kun lagret på nett", + "OnlineOnlyVideoXSizeYDownloadAndOpen": "Videofilen \"{0}\" ({1}) er kun lagret på nett, for eksempel i Dropbox eller iCloud Drive, og må lastes ned før den kan åpnes.\n\nVil du laste den ned og åpne den nå?", "OneLineMerged": "Én linje slått sammen", "OneLineSwitched": "Én linje byttet", "OverwriteExistingCells": "Overskriv eksisterende celler", "OverwriteOrShiftCellsDown": "Overskriv/flytt celler ned", "ParsingMatroskaFile": "Analyserer Matroska-fil...", + "ReadingDvdSubtitles": "Leser DVD-undertekster...", "PasteFromClipboardDotDotDot": "Lim inn fra utklippstavle...", "RedoPerformed": "Gjør om utført", "RedoPerformedXActionLeft": "Gjør om utført (handlinger igjen: {0})", "RemovedUnicodeControlCharactersX": "Fjernet Unicode-kontrolltegn fra {0} linjer", "RemovedXBlankLines": "Fjernet {0} tomme linjer", "ReplacedXWithYCountZ": "Erstattet \"{0}\" med \"{1}\" ({2} forekomster)", - "ReplacedXWithYInLineZ": "Erstattet \"{0}\" med \"{1}\" i linje {2}", "ReversedStartAndEndingsForRightToLeftX": "Reverserte start og slutt for høyre-til-venstre i {0} linjer", "RuleProfileIsX": "Regelprofil er nå \"{0}\"", "SaveLanguageFile": "Lagre språkfil", @@ -925,7 +924,6 @@ "ShiftTextCellsDown": "Flytt tekstceller ned", "SingleLineLength": "Linjelengde: ", "SpeedIsNowX": "Hastighet er nå \"{0}\"", - "SpellCheckResult": "Stavekontroll fullført. \n\n• Endrede ord: {0}\n• Hoppet over ord: {1}", "SubtitleGridFormattingX": "Rutenettformatering: {0}", "SubtitleImportedFromMatroskaFile": "Undertekst importert fra Matroska-fil", "TextDown": "Tekst ned", @@ -952,13 +950,14 @@ "TrimmedXLines": "Trimmet {0} undertekstlinjer", "PastedXLinesOverSelectedLines": "Limte inn {0} linje(r) over de valgte linjene", "PastedXOfYLinesOverSelectedLines": "Limte inn {0} av {1} linje(r) over de valgte linjene - resten fikk ikke plass i utvalget", + "InsertedXLinesFromClipboard": "Satte inn {0} linje(r) fra utklippstavlen", "OpenOriginalDifferentNumberOfSubtitlesXY": "Den originale undertekstfilen har ikke samme antall undertekster som gjeldende undertekstfil.\n\n• Originale undertekster: {0}\n• Gjeldende undertekster: {1}", - "ImportXMatchingOriginalLines": "Importere {0} samsvarende originale undertekster?", "OriginalTextReadOnly": "Original tekst (skrivebeskyttet)", "OriginalTextEditMode": "Original tekst (redigeringsmodus)", "OriginalIsReadOnlyNotSaved": "Den originale underteksten er en skrivebeskyttet referanse og ble ikke lagret", "OriginalIsEmptyNotSaved": "Den originale underteksten har ingen tekst i det hele tatt og ble ikke lagret", "OriginalIsReadOnlyReference": "Den originale underteksten er åpnet som en skrivebeskyttet referanse", + "CloseTranslationQuestionXY": "Lukke oversettelsen \"{0}\"?\n\nDen originale underteksten \"{1}\" blir underteksten du redigerer. Selve oversettelsesfilen endres ikke.", "AllowEditOfOriginalSubtitle": "Tillat redigering av original undertekst", "ShowAllOriginalLinesX": "Vis alle originale linjer ({0} har ingen match her)", "ShowAllOriginalLinesHint": "Linjene uten match vises som ekstra rader, slik at du kan se hva oversettelsen mangler.", @@ -993,6 +992,7 @@ "FileExportedInFormatXToY": "Fil eksportert i formatet {0} til {1}", "FileExportedInFormatXToFileY": "Fil eksportert i formatet \"{0}\" til fil \"{1}\"", "FixedXLines": "Fikset {0} linjer", + "ToolXChangedYLines": "{0}: {1} linjer endret", "TranscriptionCompletedWithXLines": "Transkripsjon fullført med {0} linjer", "OpenSubtitleFileX": "Åpne undertekstfilen \"{0}\"?", "ReplacedXOccurrences": "Erstattet {0} forekomster", @@ -1056,14 +1056,15 @@ "MultipleReplace": "_Søk og erstatt flere...", "FillSelectedLinesWithClipboard": "Fyll valgte linjer med utklippstavletekst", "GoToLineNumber": "_Gå til linjenummer...", + "CommandPalette": "Kommando_palett...", "RightToLeftMode": "Høyre-til-venstre-modus", "ModifySelectionDotDotDot": "Endre _utvalg...", "Tools": "_Verktøy", - "ToolsSelectedLines": "_Verktøy (valgte linjer)", "AdjustDurations": "_Juster varigheter...", "ApplyDurationLimits": "Bruk varighets_grenser...", "BatchConvert": "_Satsvis konvertering...", "BeautifyTimeCodes": "Forskjønn tidskoder...", + "ImproveTimeCodes": "Forbedre tidskoder (tvungen justering)...", "BridgeGaps": "Tett _hull...", "ApplyMinGap": "Bruk min. mellomrom mellom undertekster...", "ChangeCasing": "_Endre bokstavstørrelse...", @@ -1202,6 +1203,9 @@ "SelectCurrentLineWhilePlayingHint": "Velg gjeldende undertekst under avspilling {0}", "VideoPosition": "Videoposisjon {0}", "VideoPositionTextBox": "Tekstboks for videoposisjon {0}", + "ToolbarLineBreakX": "Linjeskift {0} (resten av verktøylinjen fortsetter på en ny rad)", + "InitialText": "Opprinnelig tekst for valgt linje", + "InitialTextHint": "Teksten i den valgte linjen slik den var da linjen ble valgt", "HideWaveformToolbar": "Skjul verktøylinje {0}", "ResetZoomAndSpeed": "Tilbakestill zoom og avspillingshastighet {0}", "RemoveBlankLines": "Fjern tomme linjer {0}", @@ -1215,6 +1219,15 @@ "TextPlayHint": "Spill av gjeldende undertekst og stopp ved slutten {0}", "TextPauseHint": "Pause avspilling {0}", "TextNextHint": "Spill av neste undertekst og stopp ved slutten {0}", + "MoveSelectedLines": "Flytt valgte linjer (tilbake/fremover-knapper)", + "MoveSelectedLinesAndFollowing": "Flytt valgte linjer og alle etterfølgende (tilbake/fremover-knapper)", + "MoveAllLines": "Flytt alle linjer (tilbake/fremover-knapper)", + "MoveSelectedLinesBackHint": "Flytt valgte linjer {0} ms tilbake {1}", + "MoveSelectedLinesForwardHint": "Flytt valgte linjer {0} ms fremover {1}", + "MoveSelectedLinesAndFollowingBackHint": "Flytt valgte linjer og alle etterfølgende {0} ms tilbake {1}", + "MoveSelectedLinesAndFollowingForwardHint": "Flytt valgte linjer og alle etterfølgende {0} ms fremover {1}", + "MoveAllLinesBackHint": "Flytt alle linjer {0} ms tilbake {1}", + "MoveAllLinesForwardHint": "Flytt alle linjer {0} ms fremover {1}", "GuessStartNoLineSelected": "Gjett start: ingen linje valgt", "GuessStartTimeCodesLocked": "Gjett start: tidskodene er låst", "GuessStartNoWaveform": "Gjett start: ingen bølgeform", @@ -1263,20 +1276,66 @@ "CompareOnlyInOneFile": "Kun i én fil", "CompareTextOrTimeDifference": "Tekst-/tidsforskjell", "CompareNumberDifference": "Nummerforskjell", - "IgnoreLineBreaks": "Ignorer linjeskift", "IgnoreWhitespace": "Ignorer mellomrom", "IgnoreWhitespaceHint": "Linjer som bare skiller seg i mellomrom, tabulatorer eller linjeskift regnes ikke som forskjellige", "IgnoreFormatting": "Ignorer formatering", - "OnlyLookForDifferencesInText": "Se kun etter forskjeller i tekst", - "CannotCompareWithImageBasedSubtitles": "Kan ikke sammenligne med bildebaserte undertekster", - "StatisticsTitle": "Statistikk", "IgnoreFormattingHint": "Linjer som bare skiller seg i formateringskoder, som eller {\\an8}, regnes ikke som forskjellige", "ShowOnlyDifferencesInText": "Kun forskjeller i tekst", + "IgnoreNumbering": "Ignorer nummerering", + "IgnoreNumberingHint": "Linjer som bare har forskjellig nummer, regnes ikke som forskjellige", + "IgnoreTimeDifferenceMs": "Tidstoleranse (ms)", + "IgnoreTimeDifferenceMsHint": "Start- og sluttider som avviker med så mange millisekunder eller mindre, regnes ikke som forskjellige", + "CompareDifferences": "Forskjeller", + "CompareTextDifferences": "Tekstforskjeller", + "CompareCurrent": "Gjeldende", + "CompareReference": "Referanse", + "CompareEditable": "Redigerbar", + "CompareEditableHint": "Endringer gjort her overføres til underteksten når du klikker Bruk", + "CompareReadOnly": "Skrivebeskyttet", + "CompareTakeFromReference": "Hent tekst og timing fra referansen", + "CompareInsertFromReference": "Sett inn denne linjen i gjeldende undertekst", + "CompareDeleteFromCurrent": "Slett denne linjen fra gjeldende undertekst", + "CompareTakeText": "Hent tekst", + "CompareTakeTiming": "Hent timing", + "CompareEditLine": "Rediger linje", + "CompareEditHint": "Ctrl+Enter for å lagre, Esc for å avbryte", + "CompareEdited": "Redigert", + "CompareOnePendingChange": "1 ventende endring", + "CompareXPendingChanges": "{0} ventende endringer", + "CompareDiscardXChanges": "Forkaste de {0} endring(ene) gjort i Sammenlign?", + "CompareXChangesApplied": "Sammenlign: {0} endring(er) brukt", + "CompareChangeTextX": "#{0} tekst fra referanse", + "CompareChangeTimingX": "#{0} timing fra referanse", + "CompareChangeTextAndTimingX": "#{0} tekst og timing fra referanse", + "CompareChangeEditedX": "#{0} redigert", + "CompareChangeInsertedX": "#{0} satt inn", + "CompareChangeDeletedX": "#{0} slettet", + "CompareSyncPoint": "Synkroniseringspunkt", + "CompareSyncPointHint": "Synkroniseringspunkt - disse to linjene vises alltid som et par, og linjene over og under justeres hver for seg", + "CompareSyncPickCurrent": "Synkroniseringspunkt: bruk denne gjeldende linjen", + "CompareSyncPickReference": "Synkroniseringspunkt: bruk denne referanselinjen", + "CompareSyncRemove": "Fjern synkroniseringspunkt", + "CompareClearXSyncPoints": "Fjern synkroniseringspunkter ({0})", + "CompareSyncCurrentPickedX": "Gjeldende #{0} valgt som synkroniseringspunkt - velg den tilsvarende referanselinjen og klikk Synkroniser", + "CompareSyncReferencePickedX": "Referanse #{0} valgt som synkroniseringspunkt - velg den tilsvarende gjeldende linjen og klikk Synkroniser", + "CompareSyncWithCurrentX": "Synkroniser med gjeldende #{0}", + "CompareSyncWithReferenceX": "Synkroniser med referanse #{0}", + "CompareSyncApply": "Synkronisering", + "CompareSyncPointSetXY": "Synkroniseringspunkt angitt: gjeldende #{0} og referanse #{1} vises nå som et par", + "CompareSyncAlreadyPairedXY": "Gjeldende #{0} og referanse #{1} vises allerede som et par med samme start - ingenting å synkronisere", + "CompareSyncPointShiftedXYZW": "Synkroniseringspunkt angitt: gjeldende #{0}-#{1} forskjøvet {2} for å starte med referanse #{3}", + "CompareChangeSyncShiftXYZ": "#{0}-#{1} forskjøvet {2} (synkroniseringspunkt)", + "CompareTimingDeltaHint": "Hvor mye senere (+) eller tidligere (−) referansen starter og slutter enn gjeldende linje", "LoadXFromFile": "Last inn \"{0}\" fra fil", + "CompareLoadSavedFile": "Last inn lagret fil", "SaveCompareHtmlTitle": "Lagre HTML-fil for sammenligning", "PickMatroskaTrackX": "Velg Matroska-spor - {0}", "PickTransportStreamTrackX": "Velg transportstrømspor - {0}", + "Chapters": "Kapitler", + "DvdVobFilesMissingX": "VOB-filer mangler ({0}% funnet)", "PickMp4TrackX": "Velg MP4-spor - {0}", + "PickMxfTrackX": "Velg MXF-spor - {0}", + "PickMpegTrackX": "Velg MPEG-spor - {0}", "RosettaProperties": "Timed Text Rosetta IMSC-egenskaper", "RosettaFontSize": "Skriftstørrelse (radhøyde)", "PropertyTimeBase": "Tidsbase", @@ -1286,7 +1345,6 @@ "PropertyDefaultRegion": "Standardregion", "PropertyStyleAttributeName": "Navn på stilattributt", "PropertyTimeCodeFormat": "Tidskodeformat", - "PropertyFileExtension": "Filtype", "PropertyTopOrigin": "Toppunkt", "PropertyTopExtent": "Topputstrekning", "PropertyBottomOrigin": "Bunnpunkt", @@ -1294,13 +1352,9 @@ "XProperties": "{0} egenskaper", "EbuSaveOptions": { "Title": "EBU-lagringsalternativer", - "GeneralSubtitleInformation": "Generell undertekstinformasjon", "CodePageNumber": "Kodesidenummer", "DiskFormatCode": "Diskformatkode", "DisplayStandardCode": "Visningsstandardkode", - "ColorRequiresTeletext": "Farger krever tekst-TV!", - "AlignmentRequiresTeletext": "Justering krever tekst-TV!", - "TeletextCharsShouldBe38": "'Maks antall tegn per rad' for tekst-TV bør være 38!", "CharacterCodeTable": "Tegntabell", "LanguageCode": "Språkkode", "OriginalProgramTitle": "Original programtittel", @@ -1317,7 +1371,6 @@ "MaxNumberOfDisplayableRows": "Maks antall rader", "DiskSequenceNumber": "Disksekvensnummer", "TotalNumberOfDisks": "Totalt antall disker", - "Import": "Importer...", "TextAndTimingInformation": "Tekst- og tidsinformasjon", "JustificationCode": "Justeringskode", "VerticalPosition": "Vertikal posisjon", @@ -1334,11 +1387,6 @@ "Errors": "Feil", "ErrorsX": "Feil: {0}", "MaxLengthError": "Linje {0} overskrider maks lengde ({1}) med {2}: {3}", - "TextUnchangedPresentation": "Uendret presentasjon", - "TextLeftJustifiedText": "Venstrejustert tekst", - "TextCenteredText": "Sentrert tekst", - "TextRightJustifiedText": "Høyrejustert tekst", - "UseBoxForOneNewLine": "Sjekk 'Bruk boks rundt teksten' kun for én ny linje", "MaxLengthErrorTeletextColored": "Linje {0}-{1}: 36 (ikke {2}) skal være maksimalt antall tegn for farget tekst med dobbel høyde", "MaxLengthErrorTeletextWhite": "Linje {0}-{1}: 37 (ikke {2}) skal være maksimalt antall tegn for hvit tekst med dobbel høyde", "MaxLengthErrorTeletextSingleHeight": "Linje {0}: 38 (ikke {1}) skal være maksimalt antall tegn for hvit tekst med enkel høyde", @@ -1363,22 +1411,29 @@ "FormattingDotDotDot": "Formatering...", "ImageBasedSubtitleForEditDotDotDot": "Bildebasert undertekst for redigering...", "ImageBasedSubtitleForOcrDotDotDot": "Bildebasert undertekst for OCR...", + "DvdSubtitlesDotDotDot": "Undertekster fra DVD (IFO/VOB)...", + "TitleImportDvd": "Importer undertekster fra DVD", + "DvdIfoFile": "IFO-fil", + "DvdVobFiles": "VOB-filer", + "DvdVideoStandard": "Videostandard", + "DvdStartRipping": "Start ripping", + "DvdRippingAborted": "Ripping avbrutt", + "DvdOpenIfoFile": "Åpne DVD IFO-fil", + "DvdAddVobFiles": "Legg til VOB-filer", + "DvdIfoFiles": "DVD IFO-filer", + "DvdVobFileType": "DVD VOB-filer", + "DvdDropHint": "Åpne en IFO-fil (VIDEO_TS\\VTS_01_0.IFO) eller legg til VOB-filer - eller slipp dem her", + "DvdNotAnIfoFileX": "{0} er ikke en DVD IFO-fil", + "DvdMenuIfoNoTitleSetsX": "Ingen tittelsett (VTS_xx_0.IFO) funnet ved siden av {0}", + "DvdEncryptedXOfY": "{0} av de {1} undertekstpakkene er fortsatt CSS-kryptert - disse VOB-filene ble kopiert uten dekryptering, så noen undertekstbilder vil bli forvrengt.\n\nDekrypter DVD-en med et DVD-rippeverktøy først for et rent resultat.\n\nVil du fortsette likevel?", "SplitTextAt": "Del tekst ved", "BlankLines": "Tomme linjer", "OneLineIsOneSubtitle": "Én linje er én undertekst", "TwoLinesAreOneSubtitle": "To linjer er én undertekst", "ImportFilesDotDotDot": "Importer filer...", "MultipleFiles": "Importer fra flere tekstfiler (én fil er én undertekst)", - "ImportOptions": "Importeringsalternativer", - "AutoSplitText": "Del opp tekst automatisk", - "LineMode": "Linjemodus", "MaxLineLength": "Maks linjelengde", - "MaxLinesPerSubtitle": "Maks linjer per undertekst", - "MinGapBetweenSubtitles": "Min. mellomrom mellom undertekster", "MergeShortLines": "Slå sammen korte linjer", - "RemoveLinesWithoutLetters": "Fjern linjer uten bokstaver", - "SplitAtEndCharsSetting": "Del ved sluttegn", - "TakeTimeFromCurrentFile": "Hent tid fra gjeldende fil", "Fixed": "Fast", "NumberOfSubtitlesX": "Antall undertekster: {0}", "NumberOfSubtitlesXForcedY": "Antall undertekster: {0} ({1} tvungne)", @@ -1390,14 +1445,18 @@ "ForcedAlignerProgress": "Justerer... vindu {0} av {1}, {2} av {3} linjer", "ForcedAlignerSetupTitle": "Juster tidskoder via tvungen justering", "ForcedAlignerSetupIntro": "En tvungen justerer sammenligner undertekstteksten du allerede har med lyden, uten å transkribere den først. Lange videoer justeres i deler, så enhver lengde fungerer.", + "ForcedAlignerEndsFromSpeech": "Angi sluttider fra isolert tale (tregt)", + "ForcedAlignerEndsFromSpeechHint": "Tvungen justering finner hvor en linje starter, ikke hvor den slutter, så sluttider kommer vanligvis fra lesetid. Med dette på fjernes musikk og lydeffekter fra lyden først, og hver linje slutter der talen blir stille. Tar omtrent like lang tid som selve videoen med GPU - og mange ganger lenger uten.", + "ForcedAlignerIsolatingSpeech": "Isolerer tale... {0}", + "ForcedAlignerIsolatingSpeechFailed": "Kunne ikke isolere talen - sluttider ble i stedet angitt fra lesetid.", "ForcedAlignerModel": "Justeringsmodell", - "ForcedAlignerDownloadEngine": "Last ned / oppdater motor..." + "ForcedAlignerDownloadEngine": "Last ned / oppdater motor...", + "ForcedAlignerEstimatedXOfY": "{0} av {1} linjer kunne ikke justeres og ble plassert etter lesetid - tidskodene deres er bare anslått. Manuset er sannsynligvis lengre enn det som sies i videoen." }, "Export": { "ExportImagesProfiles": "Eksporter bildeprofiler", "LeftRightMargin": "Venstre/høyre marg", "TopBottomMargin": "Topp/bunn marg", - "TitleExportBluRaySup": "Eksporter Blu-ray (sup)", "LineSpacingPercent": "Linjeavstand %", "PaddingLeftRight": "Utfylling venstre/høyre", "PaddingTopBottom": "Utfylling topp/bunn", @@ -1438,10 +1497,21 @@ "TitleExportDCinemaInteropPng": "D-Cinema interop/png", "TitleExportDCinemaSmpte2014Png": "D-Cinema SMPTE 2014/png", "ImageBasedSubtitleSaved": "Bildebasert undertekst lagret", - "TitleExportBdnXml": "Eksporter BDN/xml", "TitleExportWebVttThumbnails": "Eksporter WebVTT (miniatyrbilder)", "FullFrameImage": "Bilde i full bildestørrelse", "FullFrameImageHint": "Gjør hvert bilde like stort som videobildet, med underteksten på sin plass", + "Stereo3D": "3D", + "Stereo3DHint": "For 3D-video: tegn underteksten én gang i hver øyes halvdel av bildet", + "HalfSideBySide": "Halv side-ved-side", + "HalfTopBottom": "Halv topp/bunn", + "Depth3D": "Dybde", + "Depth3DHint": "Hvor langt underteksten stikker ut av skjermen - positiv bringer den nærmere, negativ skyver den bakover", + "Plane3D": "3D-Plane", + "Plane3DHint": "Gi hver undertekst dybden en 3D Blu-ray lagrer for den, fra en 3D-Plane-fil (.ofs) laget av BD3D2MK3D eller OFSExtractor. Dybde brukes der 3D-Plane ikke har noen", + "OpenPlane3DTitle": "Åpne 3D-Plane-fil", + "UnableToLoadPlane3DX": "Kan ikke laste 3D-Plane: {0}", + "Plane3DXDepthYToZ": "{0} (dybde {1} til {2})", + "Plane3DXNoDepth": "{0} (ingen dybdeverdier)", "TextEffect": "Teksteffekt", "TextEffectSettingsTitle": "Innstillinger for teksteffekt", "TextEffectPreset": "Forhåndsinnstilling", @@ -1473,13 +1543,10 @@ "Statistics": { "Title": "Statistikk", "TitleWithFileName": "Statistikk - {0}", - "GeneralStatistics": "Generell statistikk", - "MostUsed": "Mest brukt...", "MostUsedLines": "Mest brukte linjer", "MostUsedWords": "Mest brukte ord", "NothingFound": "Ingenting funnet", "NumberOfLinesX": "Antall undertekstlinjer: {0:#,##0}", - "NumberOfFilesX": "Antall undertekstfiler: {0:#,##0}", "LengthInFormatXinCharactersY": "Antall tegn som {0}: {1:#,###,##0}", "NumberOfCharactersInTextOnly": "Antall tegn kun i tekst: {0:#,###,##0}", "TotalDuration": "Total varighet av alle undertekster: {0:#,##0}", @@ -1520,7 +1587,6 @@ "GapMaximum": "Mellomrom - maksimum: {0:#,##0} ms", "GapAverage": "Mellomrom - gjennomsnitt: {0:#,##0.##} ms", "GapExceedingMinimum": "Mellomrom - under minimum ({0:#,##0} ms): {1} ({2:0.00}%)", - "Export": "Eksporter...", "Subtitles": "Undertekster", "Words": "Ord", "TotalDurationShort": "Total varighet", @@ -1550,7 +1616,22 @@ "RestoreAutoBackupFile": "Gjenopprett auto-sikkerhetskopifil", "RestoreXFromY": "Vil du gjenopprette \"{0}\" fra {1}?", "DeleteAllSubtitleBackups": "Vil du slette alle sikkerhetskopifiler for undertekster?", - "DeleteAll": "Slett alle" + "DeleteAll": "Slett alle", + "Subtitles": "Undertekster", + "SettingsBackupInfo": "Settings.json sikkerhetskopieres når Subtitle Edit starter, høyst én gang hver {0}. dag og bare hvis innstillingene er endret. De {1} nyeste sikkerhetskopiene beholdes.", + "SettingsBackupInfoEveryStart": "Settings.json sikkerhetskopieres hver gang Subtitle Edit starter, hvis innstillingene er endret. De {0} nyeste sikkerhetskopiene beholdes.", + "SettingsBackupOff": "Automatisk sikkerhetskopi av innstillinger er slått av (Alternativer → Innstillinger → Fil).", + "LastSettingsBackupX": "Siste sikkerhetskopi: {0}", + "NoSettingsBackupsYet": "Ingen sikkerhetskopier av innstillinger ennå", + "OpenSettingsBackupFolder": "Åpne mappe for sikkerhetskopi av innstillinger", + "BackUpSettingsNow": "Sikkerhetskopier nå", + "SettingsBackedUp": "Gjeldende innstillinger ble sikkerhetskopiert.", + "RestoreSettings": "Gjenopprett innstillinger", + "RestoreSettingsFromX": "Vil du erstatte gjeldende innstillinger med sikkerhetskopien fra {0}?\n\nGjeldende innstillinger sikkerhetskopieres først. Noen endringer trer først i kraft etter at Subtitle Edit er startet på nytt.", + "SettingsRestored": "Innstillingene ble gjenopprettet. Start Subtitle Edit på nytt for at alle endringer skal tre i kraft.", + "SettingsRestoreFailed": "Kunne ikke gjenopprette innstillinger: {0}", + "SettingsBackupNotValid": "sikkerhetskopien er ikke en gyldig innstillingsfil, så gjeldende innstillinger ble beholdt", + "DeleteAllSettingsBackups": "Vil du slette alle sikkerhetskopifiler for innstillinger?" }, "PropertiesDCinema": { "Title": "D-Cinema SMPTE egenskaper", @@ -1564,22 +1645,16 @@ "EditRate": "Redigeringsrate", "TimeCodeRate": "Tidskoderate", "StartTime": "Starttid", - "Font": "Skrift", "FontId": "Skrift-ID", "FontUri": "Skrift-URI", - "Generate": "Generer", - "FontColor": "Skriftfarge", "ChooseColor": "Velg farge", "FontEffect": "Skrifteffekt", "EffectColor": "Effektfarge", - "FontSize": "Skriftstørrelse", "TopBottomMargin": "Topp/bunn marg", "FadeUpTime": "Inntoningstid", "FadeDownTime": "Uttoningstid", - "Frames": "Bilder", "ZPosition": "Z-posisjon", - "ZPositionHelp": "Positive tall flytter teksten bort, negative tall flytter teksten nærmere; hvis z-posisjonen er null, er den 2D", - "Export": "Eksporter..." + "ZPositionHelp": "Positive tall flytter teksten bort, negative tall flytter teksten nærmere; hvis z-posisjonen er null, er den 2D" } }, "Edit": { @@ -1642,7 +1717,6 @@ "SelectionIntersect": "Kryss med utvalg" }, "MultipleReplace": { - "Title": "Søk og erstatt flere", "EditRule": "Rediger regel", "NewRule": "Ny regel", "EditCategory": "Rediger kategori", @@ -1659,24 +1733,23 @@ "FindWhat": "Finn hva", "DescriptionOptional": "Beskrivelse (valgfritt)", "InvalidRegularExpressionX": "Ugyldig regulært uttrykk: {0}", - "RegularExpressionTooSlowX": "Det regulære uttrykket ga opp etter {0} sekunder - regelen hoppes over, prøv et enklere mønster" + "RegularExpressionTooSlowX": "Det regulære uttrykket ga opp etter {0} sekunder - regelen hoppes over, prøv et enklere mønster", + "MoveToCategory": "Flytt til kategori" }, "Find": { "SearchTextWatermark": "Søk tekst...", "WholeWord": "Hele ord", - "CaseSensitive": "Skiller mellom store/små bokstaver", - "CaseInsensitive": "Skiller ikke mellom store/små bokstaver", "FindPrevious": "Finn f_orrige", "FindNext": "_Finn neste", "ReplaceAndFindNext": "_Erstatt og finn neste", "ReplaceAll": "Erstatt _alle", - "ReplaceTitle": "Erstatt", - "ReplaceWith": "Erstatt med", "ReplaceTextWatermark": "Erstatt med...", "ReplaceIn": "Erstatt/søk i", "ReplaceInTextAndOriginal": "Tekst og originaltekst", "ReplaceInTextOnly": "Kun tekst", - "ReplaceInOriginalOnly": "Kun originaltekst" + "ReplaceInOriginalOnly": "Kun originaltekst", + "ReplacedOneOccurrence": "Erstattet én forekomst", + "ReplacedXOccurrences": "Erstattet {0} forekomster" }, "NoSubtitleSelected": "Ingen undertekst valgt", "PleaseSelectExactlyOneSubtitle": "Velg nøyaktig én undertekst." @@ -1687,6 +1760,7 @@ "XLinesYSubtitles": "{0} linjer, {1} undertekster", "XLinesYSubtitlesZErrors": "{0} linjer, {1} undertekster, {2} feil", "CouldNotParseAsX": "Kunne ikke tolke som {0}", + "ReadAsXUseAsY": "Kilden kunne ikke leses som {1}, men den kunne leses som {0}.\nVil du bruke linjene og fortsette å lagre som {1}?", "SelectedXCharactersYLines": "Valgt: {0} tegn i {1} linje(r)", "ReplacedXOccurrences": "Erstattet {0} forekomst(er)", "InvalidRegularExpression": "Ugyldig regulært uttrykk", @@ -1701,7 +1775,6 @@ "AiReview": { "Title": "AI-gjennomgang", "Review": "Gjennomgå", - "Stop": "Stopp", "EditPrompt": "Rediger ledetekst...", "EditPromptTitle": "Rediger ledetekst for gjennomgang", "PromptInfo": "Instruksjoner som sendes til modellen - {language} erstattes med undertekstens språk.", @@ -1712,7 +1785,6 @@ "ReviewingLineXOfY": "Gjennomgår linje {0} av {1}...", "ReviewDone": "Gjennomgang ferdig - {0} forslag i {1} linjer", "NoIssuesFound": "Ingen problemer funnet", - "CategoryAll": "Alle", "CategorySpelling": "Staving", "CategoryGrammar": "Grammatikk", "CategoryPunctuation": "Tegnsetting", @@ -1724,8 +1796,23 @@ "LargeChangeWarning": "Stor endring - ser ut som en omskriving, gå nøye gjennom", "MismatchWarning": "Ligner ikke på originalen - kan tilhøre en annen linje", "EngineError": "Kunne ikke nå AI-motoren: {0}", - "SetupHint": "Velg først en motor og en modell i Verktøy -> AI-gjennomgang.", - "PlayCurrentLineHint": "Spill av den valgte linjen i videospilleren og pause ved slutten (dobbeltklikk også på en linje)" + "PlayCurrentLineHint": "Spill av den valgte linjen i videospilleren og pause ved slutten (dobbeltklikk også på en linje)", + "EditAfterHint": "Klikk på teksten i den valgte raden for å redigere rettelsen før den brukes (Enter lagrer, Escape avbryter)", + "Template": "Mal", + "TemplatePlaceholder": "Velg en mal for å erstatte ledeteksten", + "TemplateStandard": "Standard korrekturlesing", + "TemplateSpeechToText": "Opprydding etter tale til tekst", + "TemplateMachineTranslation": "Retting av maskinoversettelse", + "TemplateSdh": "SDH / hørselshemmede", + "ReferenceContext": "Referansekontekst", + "ReferenceContextInfo": "Valgfritt. Navn, karakterer, ordliste, synopsis eller stilnotater - modellen behandler dette som fasit for navn og begreper. Sendes med hver bunke.", + "ReferenceContextPlaceholder": "Navn: Kaito, tante Mei, Dr. Hollis\nBegreper: Shinkansen, warpkjerne\nSynopsis: Kaito reiser til Tokyo for å finne sin savnede far.", + "GenerateContext": "Generer med AI", + "GenerateContextHint": "La den valgte AI-motoren lese underteksten og lage utkast til navn, begreper og en synopsis - kontroller resultatet før bruk", + "GeneratingContextPartXOfY": "Leser del {0} av {1}...", + "GeneratingContextSynopsis": "Skriver synopsis...", + "ReplaceContextQuestion": "Erstatte gjeldende referansekontekst?", + "NoContextGenerated": "AI-motoren returnerte ingen brukbar referanseinformasjon." }, "AiAssistant": { "Title": "AI-assistent", @@ -1742,7 +1829,6 @@ "Hint": "AI-assistent for denne linjen", "ShowReasoning": "Vis modellens resonnement" }, - "PickAlignmentTitle": "Velg justering", "RemoveUnicodeCharacters": { "Title": "Fjern/erstatt Unicode-tegn", "CharactersFoundX": "Unicode-tegn funnet: {0}", @@ -1752,31 +1838,23 @@ "PickFontNameInstalledFonts": "Installerte skrifter", "PickFontNameCollectedFonts": "Innsamlede skrifter", "ColorPickerTitle": "Velg farge", + "ColorPickerBrightness": "Lysstyrke", "FilterLayersTitle": "Filtrer lag for visning", "FilterLayersHideFromWaveform": "Skjul fra bølgeform/spektrogram", "FilterLayersHideFromSubtitleGrid": "Skjul fra undertekstrutenett", "FilterLayersHideFromVideoPreview": "Skjul fra videoforhåndsvisning", "PickSubtitleFormat": "Velg undertekstformat", "PickSubtitleFormatImageBasedNoPreview": "Bildebasert undertekstformat.\nBitmap-bilder genereres under konvertering - ingen tekstforhåndsvisning.", - "PickLayerTitle": "Velg lag", "RecentColors": "Nylige farger", "FixCommonErrors": { - "Title": "Fiks vanlige feil", - "Step1": "Trinn 1/2 - Velg hvilke feil som skal fikses", - "WhatToFix": "Hva som skal fikses", - "Example": "Eksempel", - "SelectAll": "Velg alle", - "InverseSelection": "Inverter utvalg", "Back": "< Til_bake", "Next": "_Neste >", - "Step2": "Trinn 2/2 - Bekreft rettelser", "Fixes": "Rettelser", "RuleDetailsDotDotDot": "Regeldetaljer...", "ShowOnlyThisRule": "Vis kun rettelser fra denne regelen", "AppliedRulesForLineX": "Anvendt(e) regel(er) for linje {0}", "RuleX": "Regel: {0}", "Log": "Logg", - "Function": "Funksjon", "RemovedEmptyLine": "Fjern tom linje", "RemovedEmptyLineAtTop": "Fjern tom linje øverst", "RemovedEmptyLineAtBottom": "Fjern tom linje nederst", @@ -1797,13 +1875,13 @@ "RemoveLineBreaksPixelWidth": "Fjern linjedeling for undertekster som får plass på én linje (pikselbredde)", "FixUppercaseIInsideLowercaseWords": "Fiks stor 'i' inni ord med små bokstaver (OCR-feil)", "FixDoubleApostrophes": "Fiks doble apostrof-tegn ('') til et enkelt anførselstegn (\")", + "FixMisreadQuotes": "Rett apostrofer feillest som doble anførselstegn (OCR, engelsk)", "AddPeriods": "Legg til punktum etter linjer der neste linje starter med stor bokstav", "StartWithUppercaseLetterAfterParagraph": "Start med stor bokstav etter avsnitt", "StartWithUppercaseLetterAfterPeriodInsideParagraph": "Start med stor bokstav etter punktum inni avsnitt", "StartWithUppercaseLetterAfterColon": "Start med stor bokstav etter kolon/semikolon", "FixLowercaseIToUppercaseI": "Fiks enslig liten 'i' til 'I' (Engelsk)", "FixCommonOcrErrors": "Fiks vanlige OCR-feil (bruker OCR-erstattningsliste)", - "CommonOcrErrorsFixed": "Vanlige OCR-feil fikset (OcrReplaceList-fil brukt): {0}", "RemoveSpaceBetweenNumber": "Fjern mellomrom mellom tall", "BreakDialogsOnOneLine": "Del dialoger på én linje", "RemoveDialogFirstInNonDialogs": "Fjern startstrek i første linje for ikke-dialoger", @@ -1820,18 +1898,17 @@ "FixCommasExample": ",, -> ,", "RemoveLineBreaksExample": "Foo
bar!\r\n-> Foo bar!", "FixDoubleApostrophesExample": "''Har doble enkle anførselstegn'' -> \"Har enkelt doble anførselstegn", + "FixMisreadQuotesExample": "\"Hello' -> \"Hello\"", "AddPeriodsExample": "Hei verden -> Hei verden.", "StartWithUppercaseLetterAfterParagraphExample": "p1: Foobar! ||p2: foobar!-> p1: Foobar!||p2: Foobar!", "StartWithUppercaseLetterAfterPeriodInsideParagraphExample": "Hei der!\r\nhvordan har du det? \r\n-> Hei der!\r\nHvordan har du det?", "StartWithUppercaseLetterAfterColonExample": "Foredragsholder: hei verden!\r\n-> Speaker: Hei verden!", "RemoveHyphensSingleLineExample": "- Foobar.\r\n-> Foobar.", "Fix3PlusLinesExample": "Foo
bar
baz!\r\n-> Foo bar baz!", - "XMissingQuotesAdded": "Manglende anførselstegn lagt til: {0}", "Fix3PlusLine": "Fiks undertekst med mer enn to linjer", "Fix3PlusLines": "Fiks undertekster med mer enn to linjer", "Analysing": "Analyserer...", "NothingToFix": "Ingenting å fikse :)", - "FixesFoundX": "Rettelser funnet: {0}", "XFixesApplied": "Rettelser anvendt: {0}", "XFixesYSelected": "{0} rettelser - {1} valgt", "NothingFixableBut": "Ingenting kunne fikses automatisk. Underteksten inneholder feil - se loggen for detaljer", @@ -1840,7 +1917,6 @@ "FixFirstLetterToUppercaseAfterParagraph": "Fiks første bokstav til stor bokstav etter avsnitt", "MergeShortLine": "Slå sammen kort linje (enkel setning)", "MergeShortLineAll": "Slå sammen kort linje (alle unntatt dialoger)", - "UnbreakShortLinePixelWidth": "Fjern linjedeling for kort linje (pikselbredde)", "BreakLongLine": "Del lang linje", "FixLongDisplayTime": "Fiks lang visningstid", "FixInvalidItalicTag": "Fiks ugyldig kursiv-kode", @@ -1863,7 +1939,6 @@ "FixUppercaseIInsideLowercaseWord": "Fiks stor 'i' inni ord med små bokstaver", "FixMissingPeriodAtEndOfLine": "Legg til manglende punktum på slutten av linjen", "RefreshFixes": "Oppdater tilgjengelige rettelser", - "ApplyFixes": "Bruk valgte rettelser og lukk", "AutoBreak": "Auto &bryt", "Unbreak": "&Fjern linjedeling", "FixDoubleDash": "Fiks '--' -> '...'", @@ -1882,8 +1957,6 @@ "FixSpaceBetweenNumbersExample": "1 100 -> 1100", "FixDialogsOneLineExample": "Hei John! - Hei Ida! -> Hei John!
- Hei Ida!", "RemoveDialogFirstInNonDialogsExample": "- Hvordan har du det? -> Hvordan har du det?", - "SelectDefault": "Velg standard", - "SetDefault": "Sett nåværende rettelser som standard", "FixContinuationStyleX": "Fiks fortsettelsesstil: {0}", "FixUnnecessaryLeadingDots": "Fjern unødvendige ledende punktum", "FixCommonErrorsProfiles": "Profiler for fiks vanlige feil", @@ -1891,7 +1964,6 @@ "SearchRulesDotDotDot": "Søk etter regler...", "GoToApplyFixes": "Gå til bruk rettelser", "BackToFixList": "Tilbake til rettelsesliste", - "ApplyFixesAndClose": "Bruk rettelser og lukk", "FixCommonOcrErrorsStep2": "Fiks vanlige feil, trinn 2 (bruk rettelser)", "FixCommonOcrErrorsStep2FixesFoundX": "Fiks vanlige feil, trinn 2 - Rettelser funnet: {0}", "Action": "Handling", @@ -1904,18 +1976,11 @@ "AdjustDurations": { "Title": "Juster varigheter", "AdjustVia": "Juster via", - "Seconds": "Sekunder", - "Percent": "Prosent", "Recalculate": "Beregn på nytt", "AddSeconds": "Legg til sekunder", - "SetAsPercent": "Sett som prosent av varighet", "Note": "Merk: Visningstid vil ikke overlappe starttid for neste tekst", "Fixed": "Fast", - "Milliseconds": "Millisekunder", "ExtendOnly": "Bare utvid", - "EnforceDurationLimits": "Håndhev minimum- og maksimumsvarighet", - "CheckShotChanges": "Ikke utvid forbi sceneskifter", - "BatchCheckShotChanges": "Respekter sceneskifter (hvis tilgjengelig)", "RecalculateRequiresOcrNote": "Beregn på nytt er utilgjengelig — én eller flere undertekster har ingen OCR-tekst. Kjør OCR først." }, "ApplyDurationLimits": { @@ -1923,6 +1988,8 @@ "FixMinDurationMs": "Fiks minimumsvarighet (ms)", "DoNotGoPastShotChange": "Ikke gå forbi sceneskifte", "FixMaxDurationMs": "Fiks maksimumsvarighet (ms)", + "FixMinDurationFrames": "Rett minimum varighet (bilder)", + "FixMaxDurationFrames": "Rett maksimum varighet (bilder)", "MinimumDurationMilliseconds": "Minimum varighet (millisekunder):", "MaximumDurationMilliseconds": "Maksimum varighet (millisekunder):", "MaxDurationShouldBeHigherThanMinDuration": "Maksimumsvarighet bør være høyere enn minimumsvarighet", @@ -1942,12 +2009,8 @@ }, "BeautifyTimeCodes": { "Title": "Forskjønn tidskoder", - "BeautifySettings": "Forskjønn-innstillinger", "SnapToFrames": "Fest til bilder", - "FrameGap": "Bildemellomrom", "MinDuration": "Min. varighet (ms)", - "ShotChangeThreshold": "Sceneskifteterskel (ms)", - "ShotChangeOffset": "Sceneskifteforskyvning (bilder)", "Original": "Original", "Beautified": "Forskjønnet", "SubtitlesCount": "Undertekster", @@ -1976,13 +2039,80 @@ "TimeCodesExtractFailed": "Kunne ikke lese tidskoder fra videoen", "TimeCodesHint": "Fest cue-punkter til videoens faktiske bildetider i stedet for å anta en konstant bildefrekvens. Krever at hele videoen leses én gang; resultatet mellomlagres." }, + "ImproveTimeCodes": { + "Title": "Forbedre tidskoder (tvungen justering)", + "Aligner": "Justerer", + "AlignerHint": "Modellen som lytter etter hver linje i lyden. De beste valgene for undertekstens språk står først.", + "MaxShift": "Maks forskyvning (sekunder)", + "MaxShiftHint": "Hvor langt underteksten kan være ute av synk. En linje hvis start ville flyttet seg lenger, blir stående og flagges. Uansett verdi blir en enkelt linje som vil flytte seg mer enn et halvt sekund lenger enn linjene rundt den, ikke flyttet blindt: den følger naboene når alle flyttet seg, og tilbys ellers uten avkrysning slik at du kan kontrollere den.", + "AdjustStartTimes": "Juster starttider", + "AdjustEndTimes": "Juster sluttider", + "IsolateSpeech": "Isoler tale først (tregt)", + "IsolateSpeechHint": "Fjerner musikk og lydeffekter før justering, slik at justereren bare hører dialogen. Hjelper på linjer som sies over høy musikk eller action, men tar omtrent like lang tid som selve lyden med GPU - og mange ganger lenger uten.", + "IsolatingSpeech": "Isolerer tale... (dette tar en stund)", + "IsolateSpeechFailed": "Kunne ikke isolere talen - justerte mot den originale lyden i stedet.", + "CheckWithSpeechToText": "Kontroller med tale til tekst", + "CheckWithSpeechToTextHint": "Transkriberer også lyden og kontrollerer hver flytting mot hvor ordene faktisk ble hørt. Store flyttinger som talen bekrefter, krysses av for deg, og flyttinger bort fra der en linje høres, står uten avkrysning slik at du kan kontrollere dem. Viser også hvor mye av hver linje som ble hørt - en lav andel betyr at teksten avviker fra det som sies. En undertekst som er mer ute av synk enn maks forskyvning - en konstant forskyvning, en glidning fra feil bildefrekvens, eller hopp der scener ble klippet - synkroniseres først etter de hørte ordene. Bruker Crisp ASR Parakeet, som tar noen minutter for en spillefilm med GPU.", + "CheckWithSpeechToTextNotAvailableX": "Kontroll med tale til tekst er ikke tilgjengelig for {0}", + "TranscribingToCheck": "Transkriberer for å kontrollere justeringen...", + "SpeechToTextFailed": "Tale til tekst mislyktes - tidskodene ble ikke kontrollert mot den.", + "SpeechToTextX": "Tale til tekst: {0}", + "SpeechToTextConfirmedXDisputedY": "Tale: {0} bekreftet, {1} omstridt", + "HardlyHeardX": "Tale til tekst hørte bare {0}% av undertekstens ord. Er underteksten på språket som snakkes? Justereren kan ikke plassere en oversettelse.", + "Heard": "Hørt", + "HeardHint": "Hvor mye av linjen tale til tekst hørte. En lav andel betyr at teksten avviker fra det som sies - der justereren mest sannsynlig tar feil.", + "StatusConfirmedBySpeech": "Bekreftet av tale", + "StatusDisputedBySpeech": "Hørt der den var", + "StatusMovedWithSync": "Flyttet med synkroniseringen", + "SyncedFirstXY": "Synkronisert først: {0} s ved starten, {1} s ved slutten", + "ShowSpeechOnly": "Vis kun tale", + "ShowSpeechOnlyHint": "Tegn den justerte bølgeformen fra lyden med musikk og lydeffekter fjernet; den originale bølgeformen forblir som den er, for sammenligning. Tilgjengelig når talen er isolert - her, eller med \"Vis kun tale\" i hovedvinduets bølgeform.", + "Align": "Juster", + "Original": "Original", + "Aligned": "Justert", + "Intro": "Velg en justerer og trykk \"Juster\" for å lytte etter hver linje i lyden og stramme inn tidskodene.", + "ExtractingAudio": "Trekker ut lyd...", + "AligningBatchXOfY": "Justerer... bunke {0} av {1}", + "ExtractAudioFailed": "Kunne ikke trekke ut lyden med ffmpeg.", + "ModelWillBeDownloaded": "{0} vil bli lastet ned", + "SummaryXRetimedYKeptZSkipped": "Ny timing: {0} · Beholdt: {1} · Ingen tale: {2}", + "MeanShiftX": "Gjennomsnittlig forskyvning: {0} ms", + "ToCheckX": "Til kontroll: {0}", + "LargeMovesToCheckX": "{0} linje(r) ville flyttet seg langt på egen hånd og står uten avkrysning - lytt til dem (F5 spiller den justerte linjen, Shift+F5 originalen) og kryss av de som er riktige.", + "LinesToCheckX": "{0} linje(r) står uten avkrysning for kontroll - lytt til dem (F5 spiller den justerte linjen, Shift+F5 originalen) og kryss av de som er riktige.", + "ChangeXOfY": "Endring {0} av {1}", + "PreviousChange": "Forrige endring", + "NextChange": "Neste endring", + "StartShift": "Start", + "EndShift": "Slutt", + "StatusRetimed": "Ny timing", + "StatusMovedWithNeighbours": "Flyttet med naboene", + "StatusLargeMoveUnconfirmed": "Stor flytting - lytt, kryss av for å bruke", + "StatusUnchanged": "Allerede på plass", + "StatusNoSpeech": "Ingen tale", + "StatusShiftTooLarge": "Beholdt - for stor forskyvning", + "StatusFailed": "Beholdt - justereren mislyktes", + "StatusAdjustedByHand": "Justert manuelt", + "AlignedWaveformHint": "Dra en linje for å flytte den, eller dra kantene for å endre start eller slutt.", + "PlayLine": "Spill av linje", + "UndoAdjustment": "Angre justering", + "PlayPause": "Spill av / pause", + "PlayAligned": "Justert", + "PlayAlignedHint": "Spill av den valgte linjen med de justerte tidskodene", + "Apply": "Bruk", + "ApplyHint": "Fjern avkrysningen for en linje for å beholde de originale tidskodene. Dobbeltklikk en linje for å spille den av. Linjer kan også flyttes eller endres i størrelse i den justerte bølgeformen." + }, "BeautifyTimeCodesProfile": { "Title": "Rediger profil for forskjønning av tidskoder", "LoadPreset": "Last inn forhåndsinnstilling", "PresetDefault": "Standard", "PresetNetflix": "Netflix", "PresetSdi": "SDI", - "General": "Generelt", + "SaveAsProfile": "Lagre som profil...", + "DeleteProfile": "Slett profil", + "ProfileName": "Profilnavn", + "OverwriteProfileX": "En profil med navnet \"{0}\" finnes allerede. Vil du overskrive den?", + "DeleteProfileX": "Slette profilen \"{0}\"?", "Gap": "Mellomrom", "GapSuffix": "bilder (oppdaterer også feltene for mellomrom forskjellig fra null nedenfor)", "Milliseconds": "ms", @@ -2011,9 +2141,12 @@ "HintChaining": "Hvordan undertekster som nesten berører hverandre håndteres — utvid dem sammen, eller skyv dem fra hverandre, avhengig av om et sceneskifte ligger imellom." }, "BridgeGaps": { - "Title": "Tett hull", "BridgeGapsSmallerThan": "Tett hull mindre enn (ms)", "MinGap": "Minimum mellomrom (ms)", + "BridgeGapsSmallerThanFrames": "Fyll mellomrom mindre enn (bilder)", + "MinGapFrames": "Minste mellomrom (bilder)", + "CalculateBridgeGapsSmallerThanDotDotDot": "Beregn grense for mellomrom fra en bildefrekvens...", + "UseXMsAsBridgeGapsSmallerThan": "Fylle mellomrom mindre enn \"{0}\" millisekunder?", "NumberOfSmallGapsBridgedX": "Antall små hull tettet: {0}", "PercentFoPrevious": "Mellomrom for forrige (%)", "GapChange": "Mellomromsendring" @@ -2025,8 +2158,6 @@ "SortBy": { "Title": "Sorter undertekster", "SortOrder": "Sorteringsrekkefølge", - "SortByNumber": "Tall", - "SortByStartTime": "Starttid", "SortByEndTime": "Sluttid" }, "BatchConvert": { @@ -2049,6 +2180,7 @@ "AddColor": "Legg til farge", "DeleteLinesWithSpecificActorsOrStyles": "Slett linjer med skuespillere eller stiler (separer flere med komma)", "UseSourceStylesIfPossible": "Bruk kildestiler hvis mulig", + "KeepSourceEmbeddedFonts": "Behold innebygde skrifter fra kilden (bunntekst)", "EditStyles": "Rediger stiler", "EditProperties": "Rediger egenskaper", "EditAttachments": "Rediger vedlegg", @@ -2069,7 +2201,6 @@ "AssaChangeStyleFromStyle": "Endre stil fra", "AssaChangeStyleToStyle": "til", "AssaChangeStyleImportStyle": "Importer stil...", - "AssaChangeStyleImportedX": "Importert: {0}", "AssaChangeStyleTrimUnusedStyles": "Trim ubrukte stiler", "AssaChangeStylePropertiesTitle": "Endre stilegenskaper", "AssaChangeStylePropertiesInfo": "Endrer de valgte feltene i hver stil i filen, uten å røre stilene selv.", @@ -2081,13 +2212,52 @@ "ConvertColorsToDialogReBreakLines": "Del linjer på nytt", "SnapTimeCodesToFramesInfo": "Runder hver start- og sluttid til nærmeste bilde. Bildefrekvensen leses fra en videofil med samme navn som undertekstfilen, med mindre en fast bildefrekvens er valgt.", "AddFolderDotDotDot": "Legg til mappe...", + "AddFolderRecursiveDotDotDot": "Legg til mappe rekursivt...", "SelectFolderToConvert": "Velg mappe med filer som skal konverteres", "IncludeSubfolders": "Ta med undermapper når en mappe legges til", + "VideoFilesWhenAddingFolder": "Videofiler (mkv, mp4, ts...) når en mappe legges til", + "VideoFilesAsk": "Spør", + "VideoFilesInclude": "Legg til undertekstsporene deres", + "VideoFilesSkip": "Hopp over", + "FolderContainsXVideoFiles": "Mappen inneholder {0} videofil(er) (mkv, mp4, ts...).\n\nVil du også legge til de innebygde undertekstsporene, eller bare undertekstfilene?", + "AddVideoFiles": "Legg til videofiler", + "SkipVideoFiles": "Kun undertekstfiler", + "DoNotAskAgainVideoFiles": "Ikke spør igjen (endre i innstillingene for Satsvis konvertering)", + "SettingsSectionOutput": "Utdata", + "SettingsSectionAddingFiles": "Legge til filer", + "SettingsSectionImageBasedSubtitles": "Bildebaserte undertekster (OCR)", + "SettingsSectionOther": "Annet", "KeepSourceFileTimestamp": "Behold kildefilens dato/tid på utdatafilene", - "ScanningFolderX": "Skanner {0}..." + "PreventSleepWhileConverting": "Hindre at datamaskinen går i dvale under konvertering", + "ScanningFolderX": "Skanner {0}...", + "TransportStreamSettings": "Transport Stream-innstillinger", + "TransportStreamSettingsDotDotDot": "Transport Stream-innstillinger...", + "TransportStreamSettingsInfo": "Gjelder undertekstspor trukket ut fra Transport Stream-filer (.ts/.m2ts). Posisjon og videostørrelse påvirker bare DVB-bildespor som eksporteres til et bildebasert format.", + "TransportStreamOverrideXPosition": "Overstyr original X-posisjon", + "TransportStreamOverrideYPosition": "Overstyr original Y-posisjon", + "TransportStreamOverrideVideoSize": "Overstyr original videostørrelse", + "TransportStreamBottomMargin": "Bunnmarg", + "TransportStreamFileNameEnding": "Filnavnendelse", + "TransportStreamFileNameEndingInfo": "Legges til foran filtypen for hvert uttrukket spor. La stå tomt for å bruke vanlig språksuffiks.", + "TransportStreamOnlyTeletext": "Kun tekst-TV", + "TransportStreamGetSizeFromVideo": "Hent størrelse fra video...", + "TwoLetterLanguageCodeUppercase": "Språkkode med to bokstaver (store bokstaver)", + "ThreeLetterLanguageCodeUppercase": "Språkkode med tre bokstaver (store bokstaver)", + "FormatContainsDotDotDot": "Format inneholder...", + "ExtensionIsDotDotDot": "Filtype er...", + "StatusIsError": "Status er feil", + "ForcedTracksOnly": "Kun tvungne spor", + "XConvertedYFailedInZ": "{0:#,###,##0} konvertert, {1:#,###,##0} mislyktes på {2}", + "XNotProcessed": "{0:#,###,##0} ikke behandlet", + "RemoveUnicodeControlCharactersTitle": "Fjern Unicode-kontrolltegn", + "RemoveUnicodeControlCharactersInfo": "Fjerner de usynlige Unicode-retningskontrolltegnene (LRM, RLM, LRE, RLE, PDF, LRO og RLO) fra teksten, og erstatter hardt mellomrom med vanlig mellomrom.", + "Preset": "Forhåndsinnstilling", + "SavePresetDotDotDot": "Lagre forhåndsinnstilling...", + "DeletePreset": "Slett forhåndsinnstilling", + "PresetName": "Navn på forhåndsinnstilling", + "DeletePresetX": "Slette forhåndsinnstillingen \"{0}\"?" }, "ChangeCasing": { - "Title": "Endre bokstavstørrelse", "FixNames": "Fiks navn", "NamesX": "Navn: {0:#,##0}", "HitsX": "Treff: {0:#,##0}", @@ -2098,9 +2268,7 @@ "AllUppercase": "Bare store bokstaver", "AllLowercase": "Bare små bokstaver" }, - "ChangeFormatting": { - "Title": "Endre formatering" - }, + "ChangeFormatting": {}, "ConvertActors": { "Title": "Konverter skuespillere", "ConvertActorFrom": "Konverter skuespiller fra", @@ -2129,18 +2297,12 @@ "OutputFormatAssa": "Advanced Sub Station Alpha (.ass)", "Style1": "Stil 1", "Style2": "Stil 2", - "OutlineWidth": "Omrissbredde", - "ShadowWidth": "Skyggebredde", - "AlignTop": "Øverst", - "AlignBottom": "Nederst", - "Merge": "_Slå sammen", - "PleaseLoadTwoSubtitles": "Last inn to undertekster" + "Merge": "_Slå sammen" }, "SplitSubtitle": { "Title": "Del opp undertekst", "NumberOfEqualParts": "Antall like deler", "SaveSplitParts": "_Lagre oppdelte deler", - "SubtitleSplitIntoXParts": "Undertekst delt i {0} deler.", "XPartsSavedInFormatYToFolder": "{0} deler lagret i formatet {1} i mappen:" }, "SplitBreakLongLines": { @@ -2174,18 +2336,17 @@ "ColumnMerge": "Slå sammen", "ColumnFirst": "Første linje", "ColumnSecond": "Andre linje", - "ColumnMerged": "Sammenslått", - "SelectAll": "Velg alle", - "InverseSelection": "Omvendt utvalg" + "ColumnMerged": "Sammenslått" }, "MergeLinesWithSameText": { - "Title": "Slå sammen linjer med samme tekst", "MaxMsBetweenLines": "Maks millisekunder mellom linjer", - "IncludeIncrementingLines": "Inkluder linjer med økende tekst" + "MaxFramesBetweenLines": "Maks bilder mellom linjer", + "IncludeIncrementingLines": "Inkluder linjer med økende tekst", + "IncludeRollUpCaptions": "Inkluder rulletekst (roll-up)" }, "MergeLinesWithSameTimeCodes": { - "Title": "Slå sammen linjer med samme tidskoder", "MaxMsDifference": "Maks forskjell (millisekunder)", + "MaxFramesDifference": "Maks forskjell (bilder)", "MakeDialog": "Lag dialoger" }, "NetflixCheckAndFix": { @@ -2208,7 +2369,6 @@ "ShotChanges": "Skudskifter", "StartNumberSpellOut": "Startnummer staves ut", "TextforHiUseBrackets": "Tekst for HI, bruk parenteser", - "FrameRate": "Bildefrekvens", "TwoFrameGrap": "Mellomrom med to frames", "WhiteSpace": "Hvit plass", "ChildrensProgram": "Barneprogram", @@ -2246,7 +2406,6 @@ "ItalicsFixed": "Fast kursiv", "ItalicsNotAllowed": "Kursiv ikke tillatt", "BridgeGapsXToYFrames": "3-{0} rammer mellomrom => 2 rammer gap", - "StartNumberSpellOutComment": "Når et tall begynner en setning, skal det alltid staves", "MaximumXCharactersPerSecond": "Maksimalt {0} tegn per sekund" }, "ImageBasedEdit": { @@ -2256,10 +2415,8 @@ "ResizeImagesDotDotDot": "Endre størrelse på bilder...", "AdjustBrightnessDotDotDot": "Juster lysstyrke...", "AdjustAlphaDotDotDot": "Juster alfa...", - "CenterHorizontally": "Midtstill horisontalt", "CropImages": "Beskjær bilder", "ImportTimeCodes": "Importer tidskoder...", - "SetTextForSubtitle": "Sett tekst for undertekst", "ScreenWidth": "Skjermbredde", "ScreenHeight": "Skjermhøyde", "AlphaThresholdInfo": "Piksler med alfa under terskelen blir helt gjennomsiktige", @@ -2289,15 +2446,31 @@ "LetterboxCustom": "Egendefinert bjelkehøyde", "BarHeightPx": "Bjelkehøyde (px)", "TitleSafePercent": "Tittelsikker sone (%)", + "MoveCaptions": "Flytt undertekster", + "MoveCaptionsDotDotDot": "Flytt undertekster...", + "MoveCaptionsTo": "Flytt undertekster", + "MoveCaptionsIntoBars": "Inn i letterbox-feltene (utenfor bildet)", + "MoveCaptionsIntoPicture": "Inn i bildet (ut av feltene)", + "MoveCaptionsOffset": "Avstand fra kant (px)", + "LetterboxBarHeightXPxYOfZCaptionsWillMove": "Felthøyde: {0} px - {1} av {2} undertekster flyttes", + "RemoveFadeInOut": "Fjern inn-/uttoning", + "RemoveFadeInOutXLinesRemoved": "{0} toningslinjer slått sammen med linjene de hører til", + "RemoveFadeInOutNothingFound": "Ingen inn-/uttoningslinjer funnet", "PositionSummary": "{0}×{1} - {2} undertekster - bjelkehøyde: {3} px", "XInActivePicture": "{0} i bildet", "XInTopBar": "{0} i øvre bjelke", "XInBottomBar": "{0} i nedre bjelke", "NoImageSubtitlesLoaded": "Ingen bildeundertekster lastet", - "ImageBasedFormatNotSupported": "Bildebasert undertekstformat ble ikke funnet/støttes ikke." + "ImageBasedFormatNotSupported": "Bildebasert undertekstformat ble ikke funnet/støttes ikke.", + "FrameRateDeclaredInFile": "Bildefrekvens angitt i Blu-ray sup-filen", + "FrameRateDetectedFromTimeCodesX": "Bildefrekvens oppdaget fra tidskodene i Blu-ray sup (filen angir {0})", + "FrameRateFromVideo": "Videoens bildefrekvens", + "FrameRateCurrent": "Gjeldende bildefrekvens", + "FrameRateSetManually": "Bildefrekvens angitt manuelt", + "FrameRateUsageInfo": "Eksport til Blu-ray sup, BDN XML, DOST og FCP skrives med denne bildefrekvensen. Den endrer ingen tider - bruk \"Endre bildefrekvens\" for det.", + "UseVideoFrameRateXInsteadOfY": "Videoens bildefrekvens er {0}, men underteksten er satt til {1}.\n\nVil du bruke videoens bildefrekvens ({0})?" }, "RemoveTextForHearingImpaired": { - "Title": "Fjern tekst for hørselshemmede", "Interjections": "Interjeksjoner", "SkipIfStartWith": "Hopp over hvis starter med", "RemoveTextBetween": "Fjern tekst mellom", @@ -2359,7 +2532,21 @@ "CorrectWordsX": "Korrekte ord: {0}", "NamesX": "Navn: {0}", "AddedToDictionaryX": "Lagt til i ordbok: {0}", - "DoNotShowThisAgain": "Ikke vis denne meldingen igjen" + "DoNotShowThisAgain": "Ikke vis denne meldingen igjen", + "EditUseAlwaysList": "Rediger \"Bruk alltid\"-listen...", + "UseAlwaysListTitle": "Stavekontroll - \"Bruk alltid\"-listen", + "UseAlwaysListHeader": "\"Bruk alltid\"-listen", + "UseAlwaysListDescription": "Ord som stavekontrollen erstatter automatisk, lagret med \"Endre alle\" og \"Bruk alltid\". Et par brukes bare når ordet er feilstavet.", + "UseAlwaysWord": "Feilstavet ord", + "XPairs": "{0:#,##0} par", + "XUnusedPairs": "{0:#,##0} aldri brukt", + "UseAlwaysUnusedHint": "Aldri brukt - dette ordet er stavet riktig (eller er et navn eller brukerord), så stavekontrollen markerer det aldri", + "RemoveUnused": "Fjern ubrukte", + "UseAlwaysEmpty": "Ingen \"Bruk alltid\"-par for dette språket ennå", + "UseAlwaysEmptyHint": "Par legges til når du klikker \"Endre alle\" eller \"Bruk alltid\" i stavekontrollen.", + "UseAlwaysNoMatch": "Ingen par samsvarer med søket", + "UseAlwaysSameWord": "Erstatningen må være forskjellig fra ordet.", + "UseAlwaysRememberOff": "\"Husk 'Bruk alltid'-listen\" er slått av i Innstillinger, så stavekontrollen verken bruker eller lagrer denne listen." }, "Video": { "Chapters": { @@ -2390,7 +2577,6 @@ "ScaleTimesDescription": "Skaler hver kapitteltid fra én bildefrekvens til en annen.", "FromFrameRate": "Fra", "ToFrameRate": "Til", - "Apply": "Bruk", "WriteToVideo": "Skriv til video...", "WriteToVideoTitle": "Skriv kapitler til video", "WriteToVideoDescription": "Kapitlene skrives inn i en kopi av videofilen. Ingenting kodes om, så dette går raskt og uten kvalitetstap.", @@ -2400,7 +2586,6 @@ "WrittenToX": "Kapitler skrevet til {0}", "UnableToWriteChapters": "Kunne ikke skrive kapitler til videofilen.", "ChaptersSavedToX": "Kapitler lagret i {0}", - "NoVideoLoaded": "Ingen video lastet", "EmptyListCallToAction": "Ingen kapitler ennå - legg til ett ved videoposisjonen, eller importer dem fra en video eller fil." }, "GoToVideoPosition": "Gå til videoposisjon", @@ -2412,30 +2597,88 @@ "OpenSecondarySubtitleOnVideoPlayerDotDotDot": "Sekundær undertekst på videospiller, åpne...", "OpenSecondarySubtitleOnVideoPlayer": "Sekundær undertekst (på videospiller)", "RemoveSecondarySubtitleOnVideoPlayer": "Sekundær undertekst på videospiller, fjern", + "EditSecondarySubtitleSettings": "Rediger innstillinger for andre undertekst", + "EditSecondarySubtitleSettingsDotDotDot": "Rediger innstillinger for andre undertekst...", + "SecondarySubtitleRememberSettings": "Husk disse innstillingene", + "SecondarySubtitleDoNotShowAgain": "Ikke vis denne dialogen igjen", + "SecondarySubtitleJustifyLines": "Juster linjer", "OpenRecentVideo": "Åpne nylig video", "ClearRecentVideos": "Tøm nylige videoer", "CutVideoTitle": "Klipp video", "CutVideoDotDotDot": "Klipp video...", + "RemuxVideoTitle": "Remux video", + "RemuxVideoDotDotDot": "Remux video...", + "RemuxVideoInputVideo": "Inndatavideo:", + "RemuxVideoMultipleTracksRequiresMkv": "Flere lyd- eller undertekstspor krever MKV-containeren. Utdataformatet er automatisk endret til .mkv.", + "RemuxVideoOutputFormat": "Utdataformat:", + "RemuxVideoOutputFile": "Utdatafil:", + "RemuxVideoRemuxing": "Remuxer video og lyd...", + "RemuxVideoFinalizing": "Fullfører utdatafilen (flytter indeksen til starten)...", + "RemuxVideoCompleted": "Videoen ble remuxet.", + "RemuxVideoFailed": "Kunne ikke remuxe videoen.", + "RemuxVideoPleaseSelectBoth": "Velg både video- og lydfiler.", + "RemuxVideoAssRequiresMkv": "ASS/SSA-undertekster krever MKV-containeren for å bevare all formatering og alle stiler.", + "RemuxVideoSccRequiresMov": "Scenarist-undertekster (.scc) bygges inn som CEA-608-teksting (closed captions), som krever MOV- eller MPG-containeren. Utdataformatet er automatisk endret til .mov.", + "RemuxVideoSccNotWithWebM": "Scenarist-teksting (.scc) krever MOV-containeren, som ikke kan inneholde VP9/AV1-videoen i en .webm-fil. Fjern .scc-filen eller konverter den til et annet undertekstformat.", + "RemuxVideoMpgMaxTwoCaptionTracks": "En .mpg-fil kan inneholde høyst to tekstingsspor (CC1 og CC3) - fjern undertekstfiler slik at én eller to gjenstår.", + "RemuxVideoMccRequiresMpg": "MacCaption-undertekster (.mcc) bygges inn som CEA-608/708-teksting i MPEG-2-videoen, som krever MPG-containeren. Utdataformatet er automatisk endret til .mpg.", + "RemuxVideoMpgReencodeVideoX": "Teksting i en .mpg-fil krever MPEG-2-video, og videoen er {0}. Vil du omkode videoen til MPEG-2? Dette tar lengre tid og senker kvaliteten litt.", + "RemuxVideoFileHasNoAudioX": "\"{0}\" har ikke noe lydspor, så den kan ikke brukes som lydkilde.", + "RemuxVideoAddingClosedCaptions": "Legger til teksting (closed captions)...", + "RemuxVideoSelectAudioTrack": "Velg lydspor", + "RemuxVideoSelectAudioTrackFor": "Velg lydspor for {0}", + "RemuxVideoSelectAudioTrackPrompt": "Filen '{0}' inneholder {1} lydspor. Velg lydsporet som skal remuxes:", + "RemuxVideoVideoFile": "video", + "RemuxVideoAudioFile": "lyd", + "RemuxVideoAudioFiles": "Lydfiler:", + "RemuxVideoSubtitleFiles": "Undertekstfiler (valgfritt, myke undertekster):", + "RemuxVideoSelectAudioTrackDotDotDot": "Velg lydspor...", + "RemuxVideoFilesX": "{0} filer", + "RemuxVideoMixAudio": "Miks lyd til ett spor", + "RemuxVideoVolumePercent": "Volum %:", + "RemuxVideoVolumeX": "Volum {0}%", + "RemuxVideoFastStart": "Optimaliser for strømming på nett (flytt indeksen til starten - tregere)", "EmbedSubtitlesDotDotDot": "Legg til/fjern innebygde undertekster...", "GenerateTimeCodes": "Generer tidskoder", "CheckeredImage": "Rutete bilde", - "SaveVideoAsTitle": "Lagre video som", "PromptForFfmpegParamsAndGenerate": "Spør om ffmpeg-parametere og generer", "CutVideoCutSegments": "Klipp segmenter", "CutVideoMergeSegments": "Slå sammen segmenter", "CutVideoSplitSegments": "Lagre segmenter individuelt", - "CutVideoType": "Klipptype", - "MpvRenderAuto": "Auto", - "MpvRenderNative": "Native", - "MpvRenderOpenGl": "OpenGL", - "MpvRenderSoftware": "Programvare (treg)", "CutVideoAlsoCutSubtitle": "Klipp også underteksten", "CutVideoSubtitleFileGeneratedX": "Undertekstfil generert: {0}", + "CutVideoTransition": "Overgang mellom segmenter", + "CutVideoTransitionDurationSeconds": "Varighet (sekunder)", + "CutVideoFadeInSeconds": "Ton inn fra svart ved start (sekunder)", + "CutVideoFadeOutSeconds": "Ton ut til svart ved slutt (sekunder)", + "CutVideoPreviewTransition": "Forhåndsvis overgang", + "CutVideoPreviewTransitionTitle": "Forhåndsvisning av overgang", + "CutVideoPreviewNeedsJoin": "Det finnes ingen skjøt å forhåndsvise - legg til minst to segmenter som skal slås sammen, eller et segment som skal klippes ut av midten.", + "CutVideoTransitionInfoX": "Hver overgang overlapper delene den skjøter, så utdataene blir {0} sekunder kortere per skjøt. En klippet undertekst får ny timing for å passe.", + "CutVideoTransitionFade": "Toning", + "CutVideoTransitionFadeBlack": "Toning via svart", + "CutVideoTransitionFadeWhite": "Toning via hvitt", + "CutVideoTransitionFadeGrays": "Toning via grått", + "CutVideoTransitionDissolve": "Overtoning", + "CutVideoTransitionWipeLeft": "Visk mot venstre", + "CutVideoTransitionWipeRight": "Visk mot høyre", + "CutVideoTransitionWipeUp": "Visk opp", + "CutVideoTransitionWipeDown": "Visk ned", + "CutVideoTransitionSlideLeft": "Skyv mot venstre", + "CutVideoTransitionSlideRight": "Skyv mot høyre", + "CutVideoTransitionSlideUp": "Skyv opp", + "CutVideoTransitionSlideDown": "Skyv ned", + "CutVideoTransitionSmoothLeft": "Myk mot venstre", + "CutVideoTransitionSmoothRight": "Myk mot høyre", + "CutVideoTransitionCircleOpen": "Sirkel åpne", + "CutVideoTransitionCircleClose": "Sirkel lukke", + "CutVideoTransitionRadial": "Radial", + "CutVideoTransitionPixelize": "Pikseler", + "CutVideoTransitionBlur": "Uskarphet", "ImportCurrentSubtitle": "Importer gjeldende undertekst", "AddRemoveEmbeddedSubtitlesTitle": "Legg til/fjern innebygde undertekster", "AddRemoveEmbeddedSubtitlesMp4Title": "Legg til/fjern innebygde undertekster (MP4)", "AddCurrentSubtitle": "Legg til gjeldende undertekst", - "EmbeddedTrackColumnNew": "Ny", "EmbeddedTrackNoSubtitlesFoundTitle": "Ingen undertekster funnet", "EmbeddedTrackNoSubtitlesFoundMessage": "Den valgte undertekstfilen inneholder ingen undertekster.", "EmbeddedTrackNoTracksTitle": "Ingen spor lagt til", @@ -2447,7 +2690,7 @@ "EmbeddedTrackGeneratingVideoXY": "Genererer video... {0}% {1}", "EmbeddedTrackGeneratingVideo": "Genererer video...", "Mp4FilesFilter": "MP4-filer", - "TitleOrLanguage": "Tittel/språk", + "EmbeddedTracksPickVideoHint": "Velg en videofil, eller slipp en her, for å se undertekstsporene.", "ViewMatroskaTrackX": "Vis Matroska-spor - {0}", "ResolutionSeparator": "x", "OpenFromUrlTitle": "Åpne videofil fra nettadresse", @@ -2457,9 +2700,7 @@ "OpenFromUrlDownloadAndOpen": "Last ned og åpne", "OpenFromUrlDownloadAndOpenDescription": "Lagre videoen lokalt, og åpne den deretter.", "OpenFromUrlDownloadAndOpenNote": "Kreves for tale-til-tekst og bølgeformtopper.", - "OpenFromUrlMissing": "Skriv inn en video-URL først.", "OpenFromUrlDownloadingTitle": "Laster ned video", - "OpenFromUrlSaveAs": "Lagre video som", "OpenFromUrlDownloadSubtitles": "Last også ned undertekster", "OpenFromUrlDownloadVideo": "Last ned video", "OpenFromUrlIncludeAutoGeneratedSubtitles": "Ta med automatisk genererte undertekster (originalspråk)", @@ -2496,56 +2737,38 @@ "ReEncodeGeneratingDone": "Generering ferdig", "ReEncodeGeneratedFilesX": "Genererte filer ({0}):", "ReEncodeFfmpegParameters": "ffmpeg parametere", + "AnalyzingVideoX": "Analyserer video... {0}% {1}", + "AnalyzingVideoXofY": "Analyserer video {0}/{1}... {2}% {3}", + "BackgroundImageFileNotSelected": "Bakgrunnsbildefil er ikke valgt", + "BackgroundImageFileDoesNotExistX": "Bakgrunnsbildefilen finnes ikke: {0}", "BurnIn": { "Title": "Generer video med innbrente undertekster", - "InfoAssaOff": "Merk: Advanced SubStation Alpha-stiler støttes.", - "InfoAssaOn": "Merk: Advanced SubStation Alpha-stiler vil bli brukt :)", - "XGeneratedWithBurnedInSubsInX": "\"{0}\" generert med innbrent undertekst på {1}.", - "TimeRemainingMinutes": "Gjenstående tid: {0} minutter", - "TimeRemainingOneMinute": "Gjenstående tid: Ett minutt", - "TimeRemainingSeconds": "Gjenstående tid: {0} sekunder", - "TimeRemainingAFewSeconds": "Gjenstående tid: Noen få sekunder", - "TimeRemainingMinutesAndSeconds": "Gjenstående tid: {0} minutter og {1} sekunder", - "TimeRemainingOneMinuteAndSeconds": "Gjenstående tid: Ett minutt og {0} sekunder", - "TargetFileName": "Målfilnavn: {0}", "TargetFileSize": "Målfilstørrelse (krever 2-pass-koding)", "FileSizeMb": "Filstørrelse i MB", "MatchSourceVideoSize": "Match størrelsen på kildevideoen", - "PassX": "Pass {0}", - "Encoding": "Koding", "BitRate": "Bitrate", "TotalBitRateX": "Total bitrate: {0}", "SampleRate": "Samplingsfrekvens", "Audio": "Lyd", - "Stereo": "Stereo", "Preset": "Forhåndsinnstilling", + "Tune": "Tune", "PixelFormat": "Pikselformat", "Crf": "CRF", - "TuneFor": "Optimaliser for", - "AlignRight": "Høyrejuster", - "GetStartPosition": "Hent startposisjon", - "GetEndPosition": "Hent sluttposisjon", "UseSource": "Bruk kilde", - "UseSourceFolder": "Bruk kildemappe", - "UseSourceResolution": "Bruk kildeoppløsning", - "OutputSettings": "Utdatafil/mappe...", "FontSizeFactor": "Skriftstørrelsesfaktor", "BoxType": "Bokstype", - "FixRightToLeft": "Fiks høyre-til-venstre", - "Cut": "Klipp", "FromTime": "Fra tid", "ToTime": "Til tid", "AudioEncoding": "Lydkoding", "OutputProperties": "Utdataegenskaper...", "VideoFileSize": "Videofilstørrelse", "OneBox": "Én boks", - "BoxPerLine": "Boks per linje", - "LogoInfo": "Velg et PNG-bilde og dra det for å plassere det på videoen." + "LogoInfo": "Velg et PNG-bilde og dra det for å plassere det på videoen.", + "NoSubtitlesGenerateAnyway": "Det finnes ingen undertekstlinjer å brenne inn.\n\nVil du generere videoen uten undertekster?", + "NoSubtitlesInXItemsGenerateAnyway": "{0} element(er) i satsen har ingen undertekstlinjer å brenne inn.\n\nVil du generere disse videoene uten undertekster?" }, "VideoTransparent": { - "Title": "Generer gjennomsiktig video med undertekster", - "InfoAssaOff": "Merk: Advanced SubStation Alpha-stiler støttes.", - "InfoAssaOn": "Merk: Advanced SubStation Alpha-stiler vil bli brukt :)" + "Title": "Generer gjennomsiktig video med undertekster" }, "AudioToText": { "Title": "Tale til tekst", @@ -2553,6 +2776,21 @@ "TranslateToEnglish": "Oversett til engelsk", "Transcribing": "Transkriberer...", "TranscribingXOfY": "Transkriberer {0} av {1}...", + "LinesTranscribedXOfYFailedZ": "{0} av {1} linjer ble transkribert - {2} mislyktes eller hadde ingen tale.\n\nMislykkede linjer beholder gjeldende tekst. Vil du bruke de transkriberte linjene, eller prøve de mislykkede på nytt (du kan endre motor eller innstillinger først)?", + "LinesTranscribedXOfYCancelled": "Transkripsjonen ble avbrutt etter {0} av {1} linjer.\n\nVil du bruke de transkriberte linjene? De andre linjene beholder gjeldende tekst.", + "NoLinesTranscribed": "Ingen av de {0} linjene kunne transkriberes.\nSe verktøyloggen for detaljer.", + "ApplyTranscribedLines": "Bruk transkriberte", + "RetryFailedLines": "Prøv mislykkede på nytt", + "IsolateSpeech": "Isoler tale (tregt)", + "IsolateSpeechHint": "Fjerner musikk og lydeffekter før transkribering. Gir færre tapte linjer og bedre timing på lyd med høy musikk, men tar omtrent like lang tid som selve lyden med GPU - og mange ganger lenger uten.", + "IsolatingSpeech": "Isolerer tale...", + "IsolateSpeechFailed": "Kunne ikke isolere talen - transkriberer den originale lyden i stedet.", + "DetectSpeakers": "Gjenkjenn talere", + "DetectSpeakersHint": "Starter hver linje med taleren, som \"(speaker 0)\", ved hjelp av NVIDIAs Sortformer-diarisering (opptil 8 talere). Fungerer best med motorer som deler linjer der taleren skifter, som Parakeet og Cohere.", + "DetectSpeakersNeedsNewerCrispAsr": "\"Gjenkjenn talere\" krever Crisp ASR v{0} eller nyere, men v{1} er installert.\n\nOppdater Crisp ASR (tannhjulknappen ved siden av motoren) eller slå av \"Gjenkjenn talere\".", + "XNeedsNewerCrispAsr": "{0} krever Crisp ASR v{1} eller nyere, men v{2} er installert.\n\nVil du laste ned og installere oppdateringen nå?", + "Vad": "VAD", + "VadHint": "Stemmeaktivitetsgjenkjenning: bare talen transkriberes, delt opp der taleren tar pause.\nAuto: Silero, og bare for motorene som trenger det (Cohere, Mega, Index-Echo).\nSilero, FireRedVAD, Whisper-VAD, WebRTC: alltid på, for alle Crisp ASR-motorer. FireRedVAD er mest nøyaktig, Whisper-VAD gir færrest og reneste kutt, men er tregest.\nIndex-Echo bruker alltid Silero. --vad, --vad-model eller --chunk-seconds i de avanserte innstillingene har forrang.", "InputLanguage": "Inndataspråk", "AdvancedWhisperSettings": "Avanserte Whisper-innstillinger", "DownloadingSpeechToTextEngine": "Laster ned tale-til-tekst-motor", @@ -2567,8 +2805,6 @@ "AddCustomModelDotDotDot": "Legg til egendefinert modell...", "CustomModelHelp": "Du kan bruke din egen modell: velg modellfilen (whisper.cpp ggml '.bin') eller modellmappen (faster-whisper-mappe med en 'model.bin' inni). Den kopieres til modellmappen og legges til i listen over.", "ViewToolsLogFile": "Vis Whisper-loggfil", - "ReDownloadX": "Last ned {0} på nytt", - "DownloadX": "Last ned {0}", "UpdateXTitle": "Oppdatere {0}?", "UpdateXMessage": "En nyere versjon av {0} er tilgjengelig.{1}{1}Vil du laste ned og installere oppdateringen nå?", "DownloadingSpeechToTextModel": "Laster ned tale-til-tekst-modell", @@ -2638,7 +2874,7 @@ "CloneVoicePerLinePreparing": "Henter stemmen i hver linje fra videoen...", "CloneVoicePerLineNeedsVideo": "Kloning av stemmen i hver linje krever videoen underteksten hører til. Åpne videoen og prøv igjen.", "CloneVoicePerLineNoClips": "Ingen lyd kunne hentes fra videoen, så det er ingenting å klone fra. Kontroller at videoen har et lydspor.", - "CloneVoicePerLineNeedsOriginalSubtitleX": "{0} kan bare klone en linje når den vet hva som sies i videoen ved den linjen, og det kommer fra underteksten på originalspråket. Ingen original undertekst er lastet, så hver linje ville blitt lest av den første importerte stemmen i stedet for stemmen i videoen.\n\nÅpne den originale underteksten (Fil > Åpne original...) ved siden av oversettelsen, og generer på nytt.", + "CloneVoicePerLineTranscribeClipsXYZ": "{0} kan bare klone en linje når den vet hva som sies i videoen ved den linjen. Det er ikke kjent for {1} av {2} linjer - det kommer vanligvis fra underteksten på originalspråket (Fil > Åpne original...).\n\nVil du transkribere disse linjene fra videoen med tale til tekst nå?\n\nJa: velg motoren og språket som snakkes i videoen, så transkriberes referanseklippene før talen genereres.\nNei: generer likevel - disse linjene leses av en vanlig stemme i stedet for stemmen i videoen.", "VoiceCloneConsentTitle": "Stemmekloning - før du starter", "VoiceCloneConsentHeader": "Du er i ferd med å klone en stemme", "VoiceCloneConsentIntro": "Kloning kopierer en virkelig persons stemme. Det følger regler med det de fleste steder, og i EU juridiske plikter som faller på deg, ikke på Subtitle Edit. Les dette én gang.", @@ -2654,6 +2890,12 @@ "VoiceCloneConsentDeclined": "Stemmekloning er ikke tilgjengelig før disse vilkårene er godtatt.", "AdvancedTtsAudioProcessing": "Lydbehandling", "AdvancedTtsOutput": "Utdata", + "AdvancedTtsVoiceCloning": "Stemmekloning", + "CleanCloneReferences": "Rens referanser for stemmekloning (Sidon)", + "CleanCloneReferencesDescription": "Fjerner musikk, støy og etterklang fra lyden en stemme klones fra: klipp hentet fra videoen og opptak importert i stemmebehandlingen. Bruker Sidon-modellen for talegjenoppretting på audio.cpp (940 MB nedlasting). Den klonede stemmen kopierer ikke lenger bakgrunnen, men svært stille eller overlappende tale kan bli dempet.", + "CleaningCloneReferencesDotDotDot": "Renser referanser for stemmekloning...", + "DownloadSidonTitle": "Laste ned Sidon?", + "DownloadSidonQuestionX": "Rensing av referanser for stemmekloning krever Sidon-modellen for talegjenoppretting ({0}).\n\nVil du laste den ned nå? Uten den brukes referansene som de er.", "RubberbandInstalled": "(installert)", "RubberbandNotFound": "(ikke funnet i FFmpeg)", "GenerationFolder": "Genereringsmappe", @@ -2662,14 +2904,11 @@ "DeleteTempFilesDescription": "Sletter genereringsmappen når Tekst til tale-vinduet lukkes. Lagrede lyd- og videofiler beholdes.", "PiperSettings": "Piper-innstillinger", "Title": "Tekst til tale", - "TextToSpeechEngine": "Tekst til tale-motor", "ReviewAudioSegments": "TTS - Gjennomgå lydsegmenter", "ReviewAudioSegmentsHistory": "TTS - Gjennomgå lydhistorikk", "Stability": "Stabilitet", "Similarity": "Likhet", "SpeakerBoost": "Talerforsterkning", - "StyleExaggeration": "Stiloverdrivelse", - "RegenerateAudioSelectedLine": "Regenerer lyd for valgt linje", "GenerateSpeechFromText": "Generer tale fra tekst", "TestVoice": "Test stemme", "AddAudioToVideoFile": "Legg lyd til videofil", @@ -2688,22 +2927,98 @@ "VideoEncodingSettings": "TTS - Videokodingsinnstillinger", "ElevenLabsSettings": "TTS - ElevenLabs-innstillinger", "ElevenLabsSettingsResetHint": "Tilbakestill ElevenLabs-innstillinger til standardverdier", + "Provider": "Leverandør", + "OpenAiCompatibleSettings": "TTS - OpenAI-kompatible innstillinger", + "CustomServerUrl": "Egendefinert server-URL", + "CustomServerUrlHint": "Fullstendig tale-endepunkt for en OpenAI-kompatibel server, brukt når leverandøren er \"Egendefinert\", f.eks. http://localhost:8880/v1/audio/speech", + "CustomModels": "Egendefinerte modeller", + "CustomModelsHint": "Kommaseparerte modellnavn som tilbys når leverandøren er \"Egendefinert\"", + "CustomVoices": "Egendefinerte stemmer", + "CustomVoicesHint": "Kommaseparerte stemmenavn, brukt av leverandøren \"Egendefinert\" og av OpenRouter-modeller som ikke publiserer en stemmeliste", + "Instructions": "Instruksjoner", + "InstructionsHint": "Valgfri talestil, f.eks. \"Snakk med en rolig, varm stemme\". Sendes bare til modeller som støtter det (ikke tts-1/tts-1-hd)", + "OpenAiSpeedHint": "Talehastighet fra 0,25 til 4,0 (1,0 er normal). Ikke alle modeller støtter det", + "OutputFormat": "Utdataformat", + "OutputFormatHint": "Lydformat som bes om fra serveren. \"Auto\" ber om MP3 og bytter til PCM for modeller som bare returnerer PCM (f.eks. Gemini TTS)", "RegenerateAudio": "Regenerer lyd", "AutoContinuePlaying": "Auto-fortsett avspilling", "AddingAudioToVideoFileDotDotDot": "Legger lyd til videofil...", "PreparingMergeDotDotDot": "Forbereder sammenslåing...", "ImportVoiceDotDotDot": "Importer stemme...", + "RenameVoiceDotDotDot": "Gi stemme nytt navn...", + "RenameVoiceTitle": "Gi stemme nytt navn", + "VoiceXCouldNotBeRenamedX": "Stemmen '{0}' kunne ikke få nytt navn: {1}", + "DeleteVoiceDotDotDot": "Slett stemme...", + "DeleteVoiceTitle": "Slett stemme", + "DeleteVoiceXQuestion": "Slette stemmen '{0}'?\n\nReferanseopptaket og filene tilhørende det fjernes fra disken.", + "VoiceXCouldNotBeDeletedX": "Stemmen '{0}' kunne ikke slettes: {1}", "VoiceImportSuccessTitle": "Stemme importert", "VoiceXImported": "Stemme '{0}' ble importert vellykket", "DropAudioFileHereToImportVoice": "Slipp lydfil her for å importere stemme", "DropAudioFileHereHint": ".wav eller .mp3", "VoiceCloneTranscriptTitle": "Skriv inn transkripsjon av lyden (kreves for stemmekloning)", + "VoiceManagerTitle": "TTS - Stemmebehandling", + "VoiceManagerDotDotDot": "Stemmebehandling...", + "VoiceManagerMenuItem": "TTS-stemmebehandling...", + "SpeakFromCurrentLineTitle": "Les opp fra gjeldende linje", + "SpeakFromCurrentLineDotDotDot": "Les opp fra gjeldende linje...", + "SpeakFromCurrentLineHint": "Leser linjene høyt én etter én, fra gjeldende linje. Trykk Escape eller klikk på en annen linje for å stoppe.", + "StopSpeaking": "Stopp opplesing", + "SpeakingLineX": "Leser opp linje {0}... (Escape for å stoppe)", + "PlayWithSpeechTitle": "Spill av med tale", + "PlayWithSpeechDotDotDot": "Spill av med tale...", + "PlayWithSpeechHint": "Spiller av videoen og leser opp hver linje når videoen når den. Pause og søk som vanlig; trykk Escape for å stoppe.", + "LowerVideoVolumeWhileSpeaking": "Senk videovolumet under opplesing", + "PauseVideoWhenSpeechRunsLate": "Sett videoen på pause når talen går over i neste linje", + "WaitingForSpeechLineX": "Genererer tale for linje {0}... (Escape for å stoppe)", + "VoiceKindClone": "Klonet stemme", + "VoiceKindPreset": "Innebygd", + "VoiceKindModel": "Stemmemodell", + "VoiceKindOnline": "På nett", + "Transcript": "Transkripsjon", + "TranscriptRequiredHint": "Påkrevd - motoren leser denne transkripsjonen av opptaket (ref-text) ved kloning", + "TranscriptOptionalHint": "Valgfritt - en nøyaktig transkripsjon av opptaket forbedrer kloningen", + "SaveTranscript": "Lagre transkripsjon", + "SearchVoices": "Søk etter stemmer", + "LoadingVoicesDotDotDot": "Laster stemmer...", + "NoVoicesForEngine": "Ingen stemmer - importer et opptak eller last ned en stemmepakke", + "NoVoicesMatchFilter": "Ingen stemmer samsvarer med søket", + "CopyVoiceTo": "Kopier stemme til", + "CopyVoiceToDotDotDot": "Kopier stemme til motor...", + "CopyingVoiceXToYDotDotDot": "Kopierer stemmen '{0}' til {1}...", + "VoiceXCopiedToY": "Stemmen '{0}' kopiert til {1}", + "VoiceXCouldNotBeCopiedToY": "Stemmen '{0}' kunne ikke kopieres til {1} - se loggen for detaljer", + "VoiceXAlreadyExistsInYContinue": "En stemme med navnet '{0}' finnes allerede i {1}.\n\nVil du kopiere likevel?", + "OpenVoicesFolder": "Åpne stemmemappe", + "DownloadVoicePacksDotDotDot": "Last ned stemmepakker...", + "DownloadVoicePacksTitle": "TTS - Last ned stemmepakker", + "VoicePackIntro": "Stemmepakker er sett med korte, rene opptak med transkripsjoner, klare til kloning. Hver pakke importeres til den valgte motoren (stemmer som allerede finnes, hoppes over) og inneholder en ATTRIBUTION.txt med lisens og kilder.", + "VoicePackDetailsXVoicesYSizeZLicense": "{0} stemmer · {1} · {2}", + "InstallToEngine": "Installer til motor", + "DownloadAndInstall": "Last ned og installer", + "SelectAtLeastOneVoicePack": "Velg minst én stemmepakke", + "DownloadingXDotDotDot": "Laster ned {0}...", + "InstallingXDotDotDot": "Installerer {0}...", + "XVoicesInstalledYSkipped": "{0} stemmer installert ({1} fantes allerede)", "UseSpeechToTextDotDotDot": "Bruk tale-til-tekst...", "AdvancedTtsSettings": "Avanserte TTS-innstillinger", "ProAudioPostProcessing": "Pro lydetterbehandling", "ProAudioPostProcessingDescription": "Anvender EQ-varme, støyport, kompresjon, loudness-normalisering (-16 LUFS) og inntoning/uttoning på hvert segment.", + "GeneratingSpeechSegmentXOfY": "Genererer tale: segment {0} av {1}", + "AdjustingSpeedSegmentXOfY": "Justerer hastighet: segment {0} av {1}", + "MergingAudioSegmentXOfY": "Slår sammen lyd: segment {0} av {1}", + "PostProcessingSegmentXOfY": "Etterbehandling: segment {0} av {1}", "AudioDucking": "Lyd-ducking", "AudioDuckingDescription": "Reduserer det originale videolydvolumet og mikser det med TTS-lyden, slik at det originale lydsporet fortsatt høres svakt.", + "RemoveOriginalSpeech": "Fjern original tale (tregt)", + "RemoveOriginalSpeechDescription": "Beholder musikken og lydeffektene i den originale videoen, men fjerner talen, og mikser den nye talen over dette. Tar omtrent like lang tid som selve videoen med GPU, og mange ganger lenger uten. Med lyddemping på gjelder volumet musikken og effektene. Gjelder bare når talen legges til videofilen.", + "RemovingOriginalSpeech": "Fjerner original tale...", + "RemoveOriginalSpeechFailed": "Kunne ikke fjerne den originale talen - talen ble lagt til videoen uten dette.", + "SpeechRemovalModel": "Modell", + "SpeechRemovalModelDescription": "Beste kvalitet beholder mest av musikken og lydeffektene. Rask er omtrent tre ganger raskere, med litt mer av musikken tapt.", + "SpeechRemovalBestQuality": "Beste kvalitet (Mel-Band RoFormer)", + "SpeechRemovalFast": "Rask (HTDemucs)", + "VideoTruncatedWarning": "Bildet i den nye videofilen stopper etter {0}, men den originale videoen er {1} lang - bildet fryser mens lyden fortsetter.\n\nFilen ble beholdt. Se error-log.txt i datamappen til Subtitle Edit for utdata fra ffmpeg.", "OriginalVolumePercent": "Originalt volum %", "VadSilenceCompression": "VAD stillhetskomprimering", "VadSilenceCompressionDescription": "Forkorter pauser mellom ord før tempoet endres. Bruker stemmeaktivitetsdeteksjon for å komprimere bare stillhetshull mens talen forblir uberørt. Dette er det foretrukne første trinnet — det reduserer varigheten uten tap av kvalitet.", @@ -2770,7 +3085,6 @@ "SubtitleMergedLinesAppliedPlural": "Brukte {0} linjesammenslåinger fra tekst til tale" }, "ShotChanges": { - "TitleGenerateOrImport": "Generer/importer sceneskifter", "GenerateShotChanges": "Generer sceneskifter", "ImportShotChanges": "Importer sceneskifter", "GenerateShotChangesWithFfmpeg": "Generer sceneskifter med ffmpeg", @@ -2780,7 +3094,9 @@ "TimeCodeFormatColon": "Tidskodeformat:", "ShotChangesList": "Sceneskifteliste", "ShotChangesClearQuestion": "Er du sikker på at du vil tømme alle sceneskifter?", - "DeleteSelectedShotChangeQuestion": "Er du sikker på at du vil slette det valgte sceneskiftet?" + "DeleteSelectedShotChangeQuestion": "Er du sikker på at du vil slette det valgte sceneskiftet?", + "ShotChanges": "Sceneskifter", + "ExportShotChanges": "Eksporter sceneskifter" }, "VideoOcr": { "Title": "OCR av innbrent undertekst", @@ -2790,10 +3106,6 @@ "BottomHalf": "Nederste halvdel", "FullFrame": "Hele bildet", "Engine": "OCR-motor", - "Language": "Språk", - "Url": "URL", - "Model": "Modell", - "ApiKey": "API-nøkkel", "Scan": "Skann", "FramesPerSecond": "Bilder per sekund", "TextBrightnessMinimum": "Minimum tekstlysstyrke (0=av)", @@ -2808,10 +3120,10 @@ "RunningOcrXY": "Kjører OCR... {0}/{1}", "NoLinesFoundTitle": "Ingen undertekster funnet", "NoLinesFoundMessage": "Ingen tekst ble funnet i skanneområdet - prøv å justere skanneområdet, motoren eller minimum lysstyrke.", + "OcrStoppedEarlyMessage": "OCR-motoren stoppet før alle bilder var lest, så linjene som er funnet så langt beholdes, og resten mangler.{0}{0}{1}", "LinesFoundX": "{0} linjer funnet", "RefiningTimingXY": "Finjusterer timing... {0}/{1}", "FixOcrErrors": "Fiks OCR-feil", - "Dictionary": "Ordbok", "EditLineX": "Rediger linje {0}", "DownloadDictionaryHint": "Last ned en stavekontrollordbok...", "FramesPerSecondHint": "Hvor mange bilder per sekund som skannes. 5 dekker vanlige undertekster; høyere fanger opp svært korte linjer og strammer grupperingen, men koster mer OCR-tid.", @@ -2828,14 +3140,64 @@ "TestOcr": "Test gjeldende bilde", "TestOcrRunning": "Tester OCR på gjeldende bilde...", "TestOcrResultX": "Testresultat: {0}", - "TestOcrNoTextFound": "Test: ingen tekst funnet i skanneområdet" + "TestOcrNoTextFound": "Test: ingen tekst funnet i skanneområdet", + "EngineSettings": "Innstillinger for OCR-motor", + "EngineBuiltIn": "Innebygd i operativsystemet", + "EngineExternalServer": "Ekstern server (installeres og administreres utenfor Subtitle Edit)", + "EngineCloudApi": "Sky-API (krever en API-nøkkel)", + "EngineNothingToInstall": "Ingenting å installere", + "EngineModelsMissing": "Motor installert, modeller mangler", + "Website": "Nettsted" + }, + "BackgroundMusic": { + "Title": "Generer bakgrunnsmusikk", + "GenerateBackgroundMusicDotDotDot": "Generer bakgrunnsmusikk...", + "Preset": "Forhåndsinnstilling", + "Prompt": "Ledetekst", + "TempoBpm": "Tempo (BPM)", + "GenerateSeconds": "Generer (sekunder)", + "Seed": "Frø", + "RandomSeed": "Tilfeldig", + "OutputLength": "Utdatalengde", + "OutputLengthVideoX": "{0} (videolengde - musikken går i sømløs sløyfe)", + "MusicVolumePercent": "Musikkvolum (%)", + "RemoveExistingAudioTracks": "Fjern eksisterende lydspor", + "OriginalAudioVolumePercent": "Originalt lydvolum (%)", + "SaveAudioDotDotDot": "Lagre lyd...", + "AddToVideoDotDotDot": "Legg til video...", + "EngineInfo": "ACE-Step 1.5 Turbo via audio.cpp (MIT-lisens) - kjører lokalt", + "PresetCooking": "Matlaging / baking", + "PresetTech": "Teknologi / dingsanmeldelse", + "PresetForest": "Skog / natur", + "PresetRelaxingPiano": "Avslappende piano", + "PresetLofiVlog": "Lo-fi-vlogg", + "PresetCorporate": "Bedrift / forklaringsvideo", + "LoadingModel": "Laster modell...", + "Composing": "Komponerer...", + "GeneratingAudio": "Genererer lyd...", + "DecodingAudio": "Dekoder lyd...", + "CreatingLoop": "Lager sømløs sløyfe...", + "ResultLoopXYZ": "Sløyfe: {0} på {1} BPM, sømsamsvar {2}%", + "ResultNoLoopX": "Musikk klar: {0}", + "AddingMusicToVideo": "Legger til musikk i video...", + "DownloadModelTitle": "Laste ned musikkmodell?", + "DownloadModelQuestionX": "Generering av musikk krever ACE-Step 1.5-modellen ({0}).\n\nVil du laste ned modellen?", + "DownloadingModel": "Laster ned ACE-Step 1.5-modellen...", + "SaveAudioTitle": "Lagre bakgrunnsmusikk", + "AudioFileSavedX": "Lydfil lagret: {0}", + "UnableToGenerateMusic": "Kan ikke generere musikk", + "UnableToAddMusicToVideo": "Kan ikke legge til musikk i video", + "OutputLengthTextToSpeech": "Følger den genererte talen - musikken går i sømløs sløyfe og dempes mens noen snakker", + "AddBackgroundMusic": "Legg til bakgrunnsmusikk", + "AddBackgroundMusicHint": "Generer musikk lokalt med ACE-Step 1.5 og miks den under talen - den dempes mens noen snakker", + "GeneratingBackgroundMusicDotDotDot": "Genererer bakgrunnsmusikk...", + "MixingBackgroundMusicDotDotDot": "Mikser bakgrunnsmusikk..." }, "Voices": "Stemmer", "Presets": "Forhåndsinnstillinger", "TalkerX": "Taler {0}", "BaseModel": "Basismodell", "TurboModel": "Turbomodell", - "TtsUpdateX": "Oppdater {0}", "TtsEngineNotInstalled": "{0} er ikke installert", "TtsEngineUpdateAvailable": "{0} - oppdatering tilgjengelig", "TtsEngineTooOldUpdateRequired": "{0} er for gammel - oppdatering kreves", @@ -2858,19 +3220,25 @@ "CloneVoiceTo": "Klon stemme til", "CloneVoiceNameTitle": "Navn på ny stemme", "CloneVoiceExtractFailed": "Kunne ikke trekke ut lyd for den valgte linjen.", + "LineXHasNoDuration": "Linje {0} har ingen varighet (sluttiden er ikke etter starttiden), så det finnes ingen lyd å trekke ut.", + "AudioClipsSkippedLinesX": "Ingen lyd kunne trekkes ut for disse linjene, så de ble hoppet over: {0}", "ToggleShotChange": "Veksle sceneskifte", "ResetWaveformZoomAndSpeed": "Tilbakestill bølgeform-zoom og hastighet", "ShowOriginalSubtitle": "Vis original undertekst", + "TimelineGroupTracksBy": "Grupper undertekstspor etter", + "TimelineShowThumbnails": "Vis miniatyrbilder fra video", + "ShowSpeechOnly": "Vis kun tale (tregt å generere)", + "IsolatingSpeechForWaveformX": "Isolerer tale for bølgeformen... {0}", + "IsolatingSpeechForWaveformFailed": "Kunne ikke isolere talen for bølgeformen - se verktøyloggen", + "SpeechOnlyWaveformReady": "Bølgeformen viser nå bare tale", "ShowOnlyWaveform": "Vis kun bølgeform", "ShowOnlySpectrogram": "Vis kun spektrogram", "ShowWaveformAndSpectrogram": "Vis bølgeform og spektrogram", - "SpectrogramClassic": "Klassisk", "SpectrogramClassicViridis": "Viridis", "SpectrogramClassicPlasma": "Plasma", "SpectrogramClassicInferno": "Inferno", "SpectrogramClassicTurbo": "Turbo", "SpectrogramNeon": "Neon", - "WaveformDrawStyleClassic": "Klassisk", "WaveformDrawStyleFancy": "Fancy", "SetVideoPositionAndPauseAndSelectSubtitle": "Sett videoposisjon, pause, og velg undertekst", "SetVideopositionAndPauseAndSelectSubtitleAndCenter": "Sett videoposisjon, pause, velg undertekst, og sentrer", @@ -2880,7 +3248,6 @@ }, "Sync": { "VisualSync": "Visuell synkronisering", - "Sync": "Synkronisering", "StartScene": "Startscene", "EndScene": "Sluttscene", "PlayTwoSecondsAndBack": "Spill 2 sek og tilbake", @@ -2896,19 +3263,18 @@ "AdjustSelectedLinesAndForward": "Juster valgte linjer og fremover", "ToFrameRate": "Til bildefrekvens", "FromFrameRate": "Fra bildefrekvens", + "VideoXFrameRateY": "{0} - {1} fps", "AdjustAllTimes": "Juster alle tider (vis tidligere/senere)", "ShowEarlier": "Vis tidligere", "ShowLater": "Vis senere", - "ChangeFrameRate": "Endre bildefrekvens", "SetSyncPoint": "Sett synkroniseringspunkt", "SetSyncPointViaVideo": "Sett synkroniseringspunkt via video...", "SyncPointTimeCode": "Tidskode for synkroniseringspunkt", "SyncPoints": "Synkroniseringspunkter", "PointSync": "Punktsynkronisering", "PointSyncViaOther": "Punktsynkronisering via annen undertekst", - "AdjustmentX": "Justering: {0}", - "TotalAdjustmentX": "Total justering: {0}", - "SyncPointCandidateInfo": "Linjer som starter etter {0}+ sekunder med stillhet er uthevet - de gir ofte pålitelige synkroniseringspunkter", + "SyncPointCandidateAfterInfo": "Linjer som etterfølges av {0}+ sekunder med stillhet er uthevet - de gir ofte pålitelige synkroniseringspunkter", + "GapAfter": "Mellomrom etter", "AdjustAllShortcuts": "Hurtigtaster:\r\n\r\n• Shift + Venstre/Høyre: Flytt 10 ms\r\n• Ctrl + Venstre/Høyre: Flytt 100 ms\r\n• Alt + Venstre/Høyre: Flytt 500 ms", "AdjustAllShortcutsFrames": "Hurtigtaster:\r\n\r\n• Shift + Venstre/Høyre: Flytt 1 bilde\r\n• Ctrl + Venstre/Høyre: Flytt 10 bilder\r\n• Alt + Venstre/Høyre: Flytt 1 sekund", "OffsetInSeconds": "Forskyvning i sekunder", @@ -2928,6 +3294,7 @@ "LineMerge": "Linjesammenslåing", "DelayInSecondsBetweenRequests": "Forsinkelse i sekunder mellom forespørsler", "MaxBytesPerRequest": "Maks bytes per forespørsel", + "KeepMusicLinesUntranslated": "Ikke oversett linjer i musikksymboler (♪ ... ♪)", "PromptText": "Ledetekst (prompt)", "ResetPromptToDefault": "Tilbakestill ledetekst til standard", "TranslateEachLineSeparately": "Oversett hver linje separat", @@ -2941,11 +3308,10 @@ "ReadyToTranslate": "Klar til å oversette", "Translating": "Oversetter...", "TranslationComplete": "Oversettelse fullført", + "TranslateInPlaceNoOriginal": "Oversett på stedet (ikke opprett original)", "TranslationCancelled": "Oversettelse avbrutt", "SwapLanguages": "Bytt kilde- og målspråk", "XIsAlreadyDownloadedReDownload": "{0} er allerede lastet ned. Laste ned på nytt?", - "AdvancedDotDotDot": "Avansert...", - "AdvancedSettings": "Avanserte innstillinger", "AdvancedSettingsSubtitle": "Alternativer for batch, kontekst og sampling for den avanserte llama.cpp-motoren", "Context": "Kontekst", "Synopsis": "Synopsis", @@ -2969,6 +3335,7 @@ "UseOnlyExtraServerParametersHint": "Start llama-server med parameterne over i stedet for Subtitle Edits egen tilpasning - kun modell, vert og port angis fortsatt", "ServerRunningAtX": "Server kjører på {0}", "CustomPromptHint": "Egendefinerte instruksjoner ({0} = kildespråk, {1} = målspråk); tom = innebygd ledetekst", + "EditTranslationHint": "Klikk på oversettelsen i den valgte raden for å redigere den (Enter lagrer, Escape avbryter)", "LlamaCppDownloadEngineAndModelPrompt": "llama.cpp krever at llama-server-motoren og en oversettelsesmodell lastes ned. Vil du laste ned nå?", "LlamaCppDownloadEnginePrompt": "llama.cpp krever at llama-server-motoren lastes ned. Vil du laste ned nå?", "LlamaCppDownloadModelPrompt": "llama.cpp krever at den valgte oversettelsesmodellen lastes ned. Vil du laste ned nå?" @@ -2995,14 +3362,6 @@ "WaveformSnapToShotChangeSameShotEndMaxSeconds": "Fest til nærmeste sceneskifte: maks sluttavstand når start og slutt deler samme klipp (sekunder)", "WaveformGuessStartOffsetMs": "Gjett starttid fra bølgeform: legg starten tidligere med (ms)", "WaveformGuessEndOffsetMs": "Gjett sluttid fra bølgeform: legg slutten senere med (ms)", - "ShowToolbarMultipleReplace": "Vis ikon for søk og erstatt flere", - "ShowToolbarPointSync": "Vis ikon for punktsynkronisering", - "ShowToolbarAutoTranslate": "Vis ikon for auto-oversett", - "ShowToolbarSpeechToText": "Vis ikon for tale til tekst", - "ShowToolbarStyleManager": "Vis ikon for stilbehandler (ASSA/SSA/WebVTT)", - "ShowToolbarProperties": "Vis ikon for egenskaper (ASSA/SSA)", - "ShowToolbarAttachments": "Vis ikon for vedlegg (ASSA/SSA)", - "ShowToolbarAssaDraw": "Vis ikon for ASSA-tegning (ASSA)", "ProxyDomain": "Domene", "ProxyUseSystemCredentials": "Bruk systemets legitimasjon", "ProxyBypassList": "Omgå proxy for", @@ -3014,8 +3373,14 @@ "CheckForUpdatesChannelStableAndBeta": "Stabile og beta-versjoner", "AssaAutoSetResolution": "ASSA: sett oppløsning (PlayResX/PlayResY) fra videoen når en video åpnes", "AssaAutoSetResolutionPrompt": "ASSA: spør før oppløsningen endres til å passe videoen (ellers resamples den automatisk)", + "VideoControls": "Videokontroller", + "VideoControlsEdit": "Rediger videokontroller...", + "VideoControlsPositionText": "Posisjon-/varighetstekst", + "VideoControlsVideoFileName": "Videofilnavn", + "VideoControlsPlayerName": "Navn på videospiller", "MergeKeepEndTime": "Slå sammen linjer: behold sluttid (tillat overlapp med neste undertekst)", "MergeKeepEndTimeOnlyAssa": "Slå sammen linjer: behold sluttid kun for \"Advanced Sub Station Alpha\"", + "MergeAutoBreak": "Slå sammen linjer: automatisk linjedeling av sammenslått tekst (av beholder linjenes egne linjeskift)", "DefaultSaveLocation": "Standard lagringssted", "DefaultSaveLocationSourceFileFolder": "Mappen til undertekst-/videofilen", "DefaultSaveLocationLastUsedFolder": "Sist brukte mappe", @@ -3023,6 +3388,7 @@ "DefaultSaveLocationSubtitleFileFolder": "Undertekstfilens mappe", "DefaultSaveLocationCustomFolder": "Egendefinert mappe", "SubtitleGridFormattingHideTags": "Skjul koder", + "SubtitleGridFormattingShowFormattingKeepTags": "Vis formatering, behold ikke-visuelle koder", "MusicSymbolsToReplace": "Musikksymboler som skal erstattes (atskilt med komma)", "MusicSymbol": "Musikksymbol", "ColorWordsPerMinute": "Fargelegg ord/min hvis for høyt", @@ -3033,7 +3399,6 @@ "DialogStyleDashBothLinesWithSpace": "Bindestrek begge linjer med mellomrom", "DialogStyleDashBothLinesWithoutSpace": "Bindestrek begge linjer uten mellomrom", "ContinuationStyle": "Fortsettelsesstil", - "ContinuationStyleNone": "Ingen", "ContinuationStyleNoneTrailingDots": "Ingen, punktum for pauser (bare etterfølgende)", "ContinuationStyleNoneLeadingTrailingDots": "Ingen, punktum for pauser", "ContinuationStyleNoneTrailingEllipsis": "Ingen, ellipse for pauser (bare etterfølgende)", @@ -3044,7 +3409,6 @@ "ContinuationStyleLeadingTrailingEllipsis": "Ellipse", "ContinuationStyleLeadingTrailingDash": "Bindestrek", "ContinuationStyleLeadingTrailingDashDots": "Bindestrek, men punktum for pauser", - "ContinuationStyleCustom": "Egendefinert", "CpsLineLengthStyle": "Tegn/sek / linjelengde", "CpsLineLengthStyleCalcAll": "Tell alle tegn", "CpsLineLengthStyleCalcNoSpaceCpsOnly": "Tell alle unntatt mellomrom, kun for tegn/sek", @@ -3057,27 +3421,11 @@ "CpsLineLengthStyleCalcNoSpaceOrPunctuationCpsOnly": "Ingen mellomrom eller tegnsetting, kun for tegn/sek", "CpsLineLengthStyleCalcIgnoreArabicDiacritics": "Ignorer arabiske diakritiske tegn", "CpsLineLengthStyleCalcIgnoreArabicDiacriticsNoSpace": "Ignorer arabiske diakritiske tegn, ingen mellomrom", - "TimeCodeModeHhMmSsMs": "TT:MM:SS:MS", - "TimeCodeModeHhMmSsFf": "TT:MM:SS:FF", - "SplitBehaviorPrevious": "Legg til mellomrom til venstre for delingspunktet (fokus høyre)", - "SplitBehaviorHalf": "Legg til mellomrom i midten av delingspunktet (fokus venstre)", - "SplitBehaviorNext": "Legg til mellomrom til høyre for delingspunktet (fokus venstre)", - "SubtitleListActionNothing": "Ingenting", - "SubtitleListActionVideoGoToPositionAndPause": "Gå til videoposisjon og sett på pause", - "SubtitleListActionVideoGoToPositionAndPlay": "Gå til videoposisjon og spill av", "SubtitleListActionVideoGoToPositionAndPlayCurrentAndPause": "Gå til videoposisjon, spill gjeldende og sett på pause", - "SubtitleListActionEditText": "Gå til rediger tekstboks", "SubtitleListActionVideoGoToPositionMinus1SecAndPause": "Gå til videoposisjon - 1 s og sett på pause", "SubtitleListActionVideoGoToPositionMinusHalfSecAndPause": "Gå til videoposisjon - 0,5 s og sett på pause", "SubtitleListActionVideoGoToPositionMinus1SecAndPlay": "Gå til videoposisjon - 1 s og spill av", - "SubtitleListActionEditTextAndPause": "Gå til rediger tekstboks, og sett på pause ved videoposisjon", - "AutoBackupEveryMinute": "Hvert minutt", - "AutoBackupEveryXthMinute": "Hvert {0}. minutt", - "Profiles": "Profiler", - "AutoBackupDeleteAfterXMonths": "Slett auto-sikkerhetskopier etter {0} måneder", "SearchSettingsDotDotDot": "Søk etter innstillinger...", - "SyntaxColoring": "Syntaksfarging", - "WaveformSpectrogram": "Bølgeform/spektrogram", "Network": "Nettverk", "FileTypeAssociations": "Filtypetilknytninger", "TextBoxColorTags": "Fargekoder (HTML/ASSA) i undertekst-tekstboks", @@ -3110,10 +3458,9 @@ "DarkThemeForegroundColor": "Mørkt tema forgrunnsfarge", "DarkThemeBackgroundColor": "Mørkt tema bakgrunnsfarge", "ShowGridLines": "Vis rutenettlinjer", - "ResetSettings": "Tilbakestill innstillinger?", - "ResetSettingsDetail": "Dette vil tilbakestille alle innstillinger til standardverdiene.\n\nFortsette?", "ShowHorizontalLineAboveToolbar": "Vis horisontal linje over verktøylinjen", "BookmarkColor": "Bokmerkefarge", + "SpellCheckHighlightColor": "Uthevingsfarge for stavekontroll", "SingleLineMaxLength": "Enkeltlinje maks lengde", "OptimalCharsPerSec": "Optimalt tegn/sek", "MaxCharsPerSec": "Maks tegn/sek", @@ -3126,13 +3473,21 @@ "UnbreakSubtitlesShortThan": "Fjern linjedeling på undertekster kortere enn", "NewEmptyDefaultMs": "Standard varighet for ny undertekst (ms)", "TimeCodeUpDownStepMs": "Tid opp/ned-steg (ms)", + "MoveSelectedLinesStepMs": "Steg for hurtigtast for flytting av valgte linjer (ms)", + "MoveStartEndStepMs": "Steg for hurtigtast for flytting av start/slutt (ms)", + "MoveLinesShortenNeighbor": "Flytt linjer: forkort forrige/neste linje i stedet for å overlappe den", "PromptBeforeDelete": "Spør før sletting", "RememberPositionAndSize": "Husk vindusposisjon og -størrelse", + "TitleBarFullFileName": "Vis full filbane i tittellinjen", + "ShowRecentFiles": "Vis nylige filer (for gjenåpning)", "OpenLastFileOnStart": "Åpne siste siste fil ved start", "AutoSave": "Autolagring (lagre den åpne filen mens du redigerer)", "AutoBackupOn": "Auto-sikkerhetskopi", "AutoBackupIntervalMinutes": "Intervall for auto-sikkerhetskopi (minutter)", "AutoBackupDeleteAfterDays": "Behold auto-sikkerhetskopi i (dager)", + "SettingsBackupOn": "Automatisk sikkerhetskopi av innstillinger", + "SettingsBackupIntervalDays": "Intervall for sikkerhetskopi av innstillinger (dager, 0 = hver oppstart)", + "SettingsBackupMaxCount": "Antall sikkerhetskopier av innstillinger som beholdes", "AutoConvertToUtf8": "Auto-konverter til UTF-8 ved åpning", "AutoTrimWhiteSpace": "Auto-trim mellomrom", "DefaultEncoding": "Standard koding", @@ -3146,23 +3501,14 @@ "ColorGapTooShort": "Farg hvis mellomrommet er for kort", "ErrorBackgroundColor": "Feil bakgrunnsfarge", "WaveformDrawGridLines": "Tegn rutenettlinjer", - "WaveformCenterVideoPosition": "Sentrer videoposisjon", + "WaveformUseSkiaRenderer": "Bruk eksperimentell rask gjengivelse", "WaveformCenterVideoPositionAlsoWhenPaused": "Sentrer videoposisjon også når pauset", + "WaveformSelectCurrentSubtitleWhilePaused": "Velg gjeldende undertekst også under pause", + "WaveformRememberZoom": "Husk zoom (horisontal og vertikal)", "WaveformShowToolbar": "Vis verktøylinje", "WaveformShowToolbarEdit": "Rediger verktøylinje...", "WaveformShowToolbarEditLabel": "Verktøylinjeelementer", "WaveformSpectrogramCombinedWaveformHeight": "Bølgeform/spektrogram kombinert, bølgeform høyde %", - "ShowWaveformToolbarPlay": "Verktøylinje: vis spill-knapp", - "ShowWaveformToolbarRepeat": "Verktøylinje: vis gjenta-knapp", - "ShowWaveformToolbarRemoveBlankLines": "Verktøylinje: vis knapp for fjern tomme linjer", - "ShowWaveformToolbarNew": "Verktøylinje: vis knapp for ny undertekst", - "ShowWaveformToolbarSetStart": "Verktøylinje: vis knapp for sett start", - "ShowWaveformToolbarSetEnd": "Verktøylinje: vis knapp for sett slutt", - "ShowWaveformToolbarStartAndOffsetTheRest": "Verktøylinje: vis knapp for sett start og forskyv resten", - "ShowWaveformToolbarHorizontalZoom": "Verktøylinje: vis horisontal zoom-glidebryter", - "ShowWaveformToolbarVerticalZoom": "Verktøylinje: vis vertikal zoom-glidebryter", - "ShowWaveformToolbarVideoPositionSlider": "Verktøylinje: vis videoposisjons-glidebryter", - "ShowWaveformToolbarPlaybackSpeed": "Verktøylinje: vis avspillingshastighet", "WaveformFocusTextboxAfterInsertNew": "Fokus tekstboks etter innsetting", "WaveformInvertMouseWheel": "Inverter musehjul", "WaveformMouseWheelSetsVideoPosition": "Musehjul setter videoposisjon", @@ -3181,27 +3527,8 @@ "WaveformSelectedColor": "Bølgeform valgt farge", "DownloadFfmpeg": "Last ned ffmpeg", "FixCommonErrorsSkipStep1": "Rett vanlige feil: hopp over trinn 1 (velg rettelser)", - "ShowToolbarNew": "Vis ny-ikon", - "ShowToolbarOpen": "Vis åpne-ikon", - "ShowToolbarVideoFileOpen": "Vis videofil åpne-ikon", - "ShowToolbarSave": "Vis lagre-ikon", - "ShowToolbarSaveAs": "Vis lagre som-ikon", - "ShowToolbarFind": "Vis finn-ikon", - "ShowToolbarReplace": "Vis erstatt-ikon", - "ShowToolbarSpellCheck": "Vis stavekontroll-ikon", - "ShowToolbarFixCommonErrors": "Vis fiks vanlige feil-ikon", - "ShowToolbarRemoveTextForHi": "Vis ikon for fjern tekst for hørselshemmede", - "ShowToolbarVisualSync": "Vis ikon for visuell synkronisering", - "ShowToolbarBeautifyTimeCodes": "Vis ikon for forskjønn tidskoder", - "ShowToolbarBurnIn": "Vis ikon for innbrenning", - "ShowToolbarSettings": "Vis innstillinger-ikon", - "ShowToolbarLayout": "Vis oppsett-ikon", - "ShowToolbarHelp": "Vis hjelp-ikon", - "ShowToolbarEncoding": "Vis koding", - "ShowToolbarFrameRate": "Vis bildefrekvens", "ShowPluginsMenu": "Vis Plugins-menyen", "ProxyAddress": "Proxy-adresse", - "Username": "Brukernavn", "Password": "Passord", "DefaultFormat": "Standard format", "DefaultSaveAsFormat": "Standard \"Lagre som\"-format", @@ -3209,10 +3536,13 @@ "FavoriteLanguages": "Favorittspråk", "WebVttUseXTimestampMap": "WebVTT: bruk X-TIMESTAMP-MAP (forskyv tidskoder ved innlasting)", "FullscreenHideControls": "Skjul videokontroller i fullskjerm", + "ShowSecondarySubtitleDialog": "Vis stildialog ved åpning av en andre undertekstfil", + "RememberSecondarySubtitleFile": "Husk andre undertekstfil", "AutoOpenVideoFile": "Åpne videofil automatisk når undertekst åpnes", "DownloadMpv": "Last ned mpv", "DownloadVlc": "Last ned VLC", "AllowSingleLetterShortcutsInTextbox": "Tillat snarveier med én bokstav i tekstboksen", + "AllowTextNavigationShortcutsInTextbox": "Tillat hurtigtaster på tekstnavigeringstaster (Ctrl+Venstre/Høyre, Home/End) i tekstboksen", "SpellCheckEnglishTreatInApostropheAsIng": "Stavekontroll: Behandle ord som slutter på 'in'' som 'ing' (kun engelsk)", "GoToLineNumberSetsVideoPosition": "Gå-til-linjenummer setter også videoposisjon", "AdjustAllTimesRememberLineSelectionChoice": "Juster alle tider, husk valg av linjeutvalg", @@ -3224,6 +3554,7 @@ "ShowAssaLayer": "Vis ASSA lag-boks", "WaveformCursorColor": "Bølgeform markør/hodefarge", "WaveformShotChangeColor": "Bølgebilde endrer farge", + "WaveformGridColor": "Rutenettfarge for bølgeform", "WaveformParagraphLeftColor": "Bølgeform venstre kantfarge", "WaveformParagraphRightColor": "Bølgeform høyre kantfarge", "WaveformFancyHighColor": "Bølgeform fancy høy farge", @@ -3251,27 +3582,31 @@ "ResetSyntaxColoring": "Tilbakestill syntaksfarging", "ResetWaveform": "Tilbakestill bølgeform", "ResetRules": "Tilbakestill regler", - "UseFrameMode": "Bruk bildemodus (tt.mm.ss.bb)", + "TimeCodeMode": "Tidskodemodus", + "TimeCodeModeTime": "Tid (hh:mm:ss,zzz)", + "TimeCodeModeFrames": "Bilder (hh:mm:ss:ff)", + "TimeCodeModeFrameNumbers": "Bildenumre", + "FrameModeSnapToVideoFrames": "Lagre tider på videobilder (ikke bilder innenfor hvert sekund)", "TextBoxLimitNewLines": "Begrens antall linjer i undertekst-tekstboks", - "MpvVideoOutput": "Videoutgang (mpv)", "MpvOpenGl": "libmpv - OpenGL", "MpvSoftwareRendering": "libmpv - Programvare-rendering (treg)", + "MpvMetalRendering": "libmpv - Metal (eksperimentell, krever omstart)", "MpvWidRendering": "libmpv - Native Window ID-rendering (raskere på Windows/Linux)", "WaveFormsAndSpectrogramFoldersContainsX": "\"Waveforms\" og \"spectrogram\"-mapper inneholder {0}", "DeleteWaveformAndSpectrogramFoldersQuestion": "Slett \"Waveforms\" og \"Spectrogram\"-filer?", "WaveformGenerateSpectrogram": "Generer spektrogram", "WaveformAutoGenerate": "Generer bølgeform automatisk når en video åpnes", "WaveformSpectrogramMode": "Spektrogrammodus", - "WaveformCenterOnSingleClick": "Sentrer ved enkeltklikk", - "WaveformSingleClickSelectsSubtitle": "Velg undertekst ved enkeltklikk", "WaveformRightClickSelectsSubtitle": "Velg undertekst ved høyreklikk", - "WaveformPauseOnSingleClick": "Pause ved enkeltklikk", "WaveformDrawStyle": "Bølgeform tegnestil", "WaveformExtractAudioFormat": "Format for uttrukket lyd", "WaveformExtractAudioSampleRate": "Samplingsfrekvens for uttrukket lyd", "WaveformExtractAudioSampleRateOriginal": "Original (behold kilde)", "WaveformExtractAudioBitRate": "Bitrate for uttrukket lyd (MP3/M4A)", + "FfmpegUseCenterChannelOnly": "Bruk kun senterkanal foran for 5.1-lyd (FFmpeg)", "VlcWidRendering": "libVLC - Native Window ID-rendering", + "FfmpegSoftwareRendering": "FFmpeg", + "DownloadFfmpegLibs": "Last ned FFmpeg-biblioteker (for FFmpeg-videospilleren)", "SubtitleGridEnterKeyAction": "Undertekstrutenett Enter-tast handling", "SubtitleSingleClickAction": "Undertekstrutenett enkeltklikk-handling", "SubtitleDoubleClickAction": "Undertekstrutenett dobbeltklikk-handling", @@ -3297,25 +3632,25 @@ "SaveAsBehaviorUseVideoFileName": "Bruk videofilnavn", "SaveAsBehaviorUseSubtitleFileName": "Bruk undertekstfilnavn", "SaveAsAppendLanguageCodeTwoLetter": "To bokstaver", - "SaveAsAppendLanguageCodeThreeLetter": "Tre bokstaver", - "SaveAsAppendLanguageCodeThreeLetterBibliographic": "Tre bokstaver (ISO 639-2/B)", "SaveAsAppendLanguageCodeLanguageName": "Språknavn", "SplitOddLineActionWeightTop": "Vekt topp", "SplitOddLineActionWeightBottom": "Vekt bunn", "SplitOddLinesAction": "Del opp oddetall-linjer handling", "OcrUseWordSplitList": "OCR: bruk ord-oppdelingsliste", "OcrGuessUnknownWords": "OCR: prøv å gjette ukjente ord", + "SpellCheckRememberUseAlwaysList": "Stavekontroll: husk \"Bruk alltid\"-listen", + "FixShortDisplayTimesAllowMoveStartTime": "Fiks vanlige feil: retting av korte visningstider kan flytte starttid", "SpeechToTextSelectedLinesPromptFirstTimeOnly": "Tale til tekst: valgte linjer, spør kun om språk/motor første gang", "MultipleReplaceShowDotDotDotButtons": "Søk og erstatt flere: vis kontekstmenyknapper", "GridFocusTextboxAfterInsertNew": "Rutenett: fokus tekstboks etter innsetting av ny undertekst", + "UndoRedoGoToChangedLine": "Angre/gjør om: gå til endret linje (og videoposisjon)", "TextToSpeechPromptMergeContinuationLines": "Tekst til tale: spør om å slå sammen fortsettelseslinjer", "TextToSpeechPromptSkipNoiseLines": "Tekst til tale: spør om å hoppe over lyd-/musikklinjer", "TextToSpeechPromptDetectSpeakers": "Tekst til tale: spør om å finne talernavn i teksten", "UseFocusedButtonBackgroundColor": "Bruk fokusert knapp bakgrunnsfarge", "FocusedButtonBackgroundColor": "Fokusert knapp bakgrunnsfarge", "ForceCrLfOnSave": "Tving CR+LF ved lagring (tekstbaserte undertekstfiler)", - "ShowWaveformToolbarPlayNext": "Verktøylinje: vis spill neste knapp", - "ShowWaveformToolbarPlaySelection": "Verktøylinje: vis spill av utvalg knapp", + "LinuxClipboardUseExternalTool": "Kopier tekst via xclip/wl-copy (beholder aksenttegn)", "ShowFormatLimitWarning": "Advar ved lagring når linjer overskrider formatets grenser (f.eks. SCC 32 tegn/linje)", "TextBoxButtonShowAutoBreak": "Tekstboks: vis knapp for automatisk linjedeling", "TextBoxButtonShowUnbreak": "Tekstboks: vis knapp for fjern linjedeling", @@ -3327,12 +3662,18 @@ "WaveformDoubleClickAction": "Bølgeform dobbeltklikk-handling (etter enkeltklikk-handling)", "AllSettings": "Alle innstillinger", "UiScale": "UI-skalering (%)", + "FontScale": "Skriftskalering (%)", + "TextSelectionOpacity": "Gjennomsiktighet for tekstmarkering (%)", "WaveformToolbarItems": "Bølgeform verktøylinjeelementer", "MatchIconColorToDarkTheme": "Samsvar ikonfarge med mørkt tema forgrunnsfarge", "SubtitlePreviewProperties": "Egenskaper for forhåndsvisning av undertekst", "UsePositionFromSubtitleFile": "Bruk posisjon fra undertekstfilen (TTML/PAC/EBU STL)", "MarginIsPartOfSubtitleArea": "Margen er en del av undertekstområdet", "TextJustify": "Blokkjuster linjer", + "Deinterlace": "Fjern linjefletting i video", + "DeinterlaceOff": "Av", + "DeinterlaceAuto": "Auto (kun linjeflettede bilder)", + "DeinterlaceAlways": "Alltid", "PixelWidthInfo": "Grønne linjer = maks-breddegrense | \r\nRødt område = tekst overskrider grensen", "SpellCheckEngineHunSpelll": "Hunspell", "SpellCheckEngineMsWord": "MS Word", @@ -3341,117 +3682,89 @@ "WaveformColorTheme": "Fargetema:", "WaveformSaveAsCustomTheme": "Lagre som tilpasset tema...", "WaveformLoadThemeDotDotDot": "Last inn tema...", - "WaveformExportThemeDotDotDot": "Eksporter tema..." + "WaveformExportThemeDotDotDot": "Eksporter tema...", + "WaveformSaveCustomThemeTitle": "Lagre egendefinert tema", + "WaveformThemeFileType": "Subtitle Edit bølgeformtema", + "WaveformThemeHighContrast": "Høy kontrast", + "WaveformThemeOceanBlue": "Havblå", + "WaveformThemeWarmSunset": "Varm solnedgang", + "WaveformThemeForest": "Skog", + "WaveformThemeMidnightPurple": "Midnattslilla", + "WaveformThemeRetroAmber": "Retro rav", + "WaveformThemeIce": "Is" }, "Shortcuts": { "GeneralUnbreakNoSpaceCjk": "Fjern linjedeling uten mellomrom (CJK)", - "Title": "Hurtigtaster", + "GeneralToggleBreak": "Veksle linjedeling/fjern linjedeling", "SearchShortcuts": "Søk etter hurtigtaster...", - "Filter": "Filter", - "CategoryGeneral": "Generelt", "CategorySubtitleGridAndTextBox": "Undertekstrutenett og tekstboks", "CategorySubtitleGrid": "Undertekstrutenett", - "CategoryWaveform": "Bølgeform", "CategoryTextBox": "Tekstboks", "CategoryFile": "Fil", "CategoryVideo": "Video", "CategorySync": "Synkronisering", - "CategoryTranslate": "Oversett", - "CategorySearch": "Søk", - "CategoryTools": "Verktøy", "CategoryAi": "AI", + "CategoryCustom": "Egendefinert", + "EditCustomShortcut": "Rediger egendefinert hurtigtast", + "CustomShortcutTextBoxKeyHint": "Bruk en tast med Ctrl/Alt her - en vanlig tast ville ikke lenger skrive i tekstboksen.", + "CustomShortcutNumberX": "Egendefinert hurtigtast #{0}", + "CustomShortcutNumberXY": "Egendefinert hurtigtast #{0}: {1}", + "CustomShortcutStep": "Trinn", + "CustomShortcutSteps": "Trinn", + "CustomShortcutStepRunCommand": "Kjør kommando", + "CustomShortcutStepInsertText": "Sett inn tekst", + "CustomShortcutStepReplace": "Finn og erstatt", + "CustomShortcutInsertAtCursor": "Ved markøren", + "CustomShortcutInsertAtStart": "Ved starten av teksten", + "CustomShortcutInsertAtEnd": "Ved slutten av teksten", + "CustomShortcutSummaryRunCommandX": "Kjør: {0}", + "CustomShortcutSummaryInsertXY": "Sett inn \"{0}\" ({1})", + "CustomShortcutSummaryReplaceXY": "Erstatt \"{0}\" med \"{1}\"", + "CustomShortcutSummaryReplaceRegexXY": "Erstatt regex \"{0}\" med \"{1}\"", + "CustomShortcutPickCommand": "Velg en kommando som skal kjøres.", + "CustomShortcutEnterText": "Skriv inn teksten som skal settes inn.", + "CustomShortcutEnterFind": "Skriv inn teksten som skal finnes.", + "CustomShortcutInvalidRegexX": "Ugyldig regulært uttrykk: {0}", + "CustomShortcutStepsHint": "Trinnene kjøres ovenfra og ned. Teksttrinn virker på de valgte linjene; kommandoer som åpner et vindu, fortsetter etter at det lukkes.", "ActiveIn": "Aktiv i", "ActiveInEverywhere": "Overalt", - "GeneralSetupLikeSe4": "Sett opp som Subtitle Edit 4 (tema, hurtigtaster, erstatningsregler)", - "GeneralMergeSelectedLines": "Slå sammen valgte linjer", - "GeneralMergeWithPrevious": "Slå sammen med forrige", - "GeneralMergeWithNext": "Slå sammen med neste", - "GeneralMergeWithPreviousAndUnbreak": "Slå sammen med forrige og fjern linjedeling", - "GeneralMergeWithNextAndUnbreak": "Slå sammen med neste og fjern linjedeling", - "GeneralMergeWithPreviousAndAutoBreak": "Slå sammen med forrige og automatisk linjedeling", - "GeneralMergeWithNextAndAutoBreak": "Slå sammen med neste og automatisk linjedeling", - "GeneralMergeSelectedLinesAndAutoBreak": "Slå sammen valgte linjer og automatisk linjedeling", "GeneralMergeSelectedLinesAndUnbreak": "Slå sammen valgte linjer og fjern linjedeling", "GeneralMergeSelectedLinesAndUnbreakCjk": "Slå sammen valgte linjer og fjern linjedeling CJK", - "GeneralMergeSelectedLinesOnlyFirstText": "Slå sammen valgte linjer kun første tekst", "GeneralMergeSelectedLinesBilingual": "Slå sammen valgte linjer tospråklig", - "GeneralMergeWithPreviousBilingual": "Slå sammen med forrige tospråklig", - "GeneralMergeWithNextBilingual": "Slå sammen med neste tospråklig", - "GeneralMergeOriginalAndTranslation": "Slå sammen original og oversettelse", "GeneralToggleTranslationMode": "Veksle oversettelsesmodus", - "GeneralSwitchOriginalAndTranslation": "Bytt original og oversettelse", - "GeneralSwitchOriginalAndTranslationTextBoxes": "Bytt tekstbokser for original og oversettelse", "GeneralToggleTranslationAndOriginalInPreviews": "Veksle mellom oversettelse og original i video-/lydforhåndsvisning", "GeneralChooseLayout": "Velg oppsett", - "GeneralLayoutChooseX": "Oppsett {0}", - "GeneralPlayFirstSelected": "Spill av første valgte", - "GeneralGoToFirstSelectedLine": "Gå til første valgte linje", - "GeneralGoToNextEmptyLine": "Gå til neste tomme linje", "GeneralGoToNextSubtitle": "Gå til neste undertekst", "GeneralGoToNextSubtitlePlayTranslate": "Gå til neste undertekst (spill av oversett)", "GeneralGoToNextSubtitleCursorAtEnd": "Gå til neste undertekst (markør til slutt)", "GeneralGoToPrevSubtitle": "Gå til forrige undertekst", "GeneralGoToPrevSubtitlePlayTranslate": "Gå til forrige undertekst (spill av oversett)", - "GeneralGoToStartOfCurrentSubtitle": "Gå til start av gjeldende undertekst", - "GeneralGoToEndOfCurrentSubtitle": "Gå til slutt av gjeldende undertekst", - "GeneralGoToPreviousSubtitleAndFocusVideo": "Gå til forrige undertekst og fokuser på video", - "GeneralGoToNextSubtitleAndFocusVideo": "Gå til neste undertekst og fokuser på video", - "GeneralGoToPrevSubtitleAndPlay": "Gå til forrige undertekst og spill av", - "GeneralGoToNextSubtitleAndPlay": "Gå til neste undertekst og spill av", - "GeneralGoToPreviousSubtitleAndFocusWaveform": "Gå til forrige undertekst og fokuser på bølgeform", - "GeneralGoToNextSubtitleAndFocusWaveform": "Gå til neste undertekst og fokuser på bølgeform", - "GeneralGoToLineNumber": "Gå til linjenummer", "GeneralGoToFirstLine": "Gå til første linje", "GeneralGoToLastLine": "Gå til siste linje", "AlsoSetVideoPosition": "Sett også videoposisjon", "GeneralGoToVideoPosition": "Gå til videoposisjon", "GeneralToggleItalic": "Veksle kursiv", "GeneralToggleBold": "Veksle fet skrift", - "GeneralToggleBookmarks": "Veksle bokmerker", - "GeneralFocusTextBox": "Fokus tekstboks", - "GeneralToggleBookmarksWithText": "Veksle bokmerker med tekst", - "GeneralEditBookmarks": "Rediger bokmerker", - "FileNew": "Ny", "GeneralToggleUnderline": "Veksle understreking", + "GeneralToggleBox": "Veksle boks (EBU STL)", "FileOpen": "Åpne", "FileOpenKeepVideo": "Åpne (behold video)", "FileSave": "Lagre", "FileSaveAs": "Lagre som", - "FileSaveAll": "Lagre alle", "FileSaveOriginal": "Lagre original", - "FileSaveOriginalAs": "Lagre original som", - "FileOpenOriginalSubtitle": "Åpne original undertekst", - "FileCloseOriginalSubtitle": "Lukk original undertekst", - "FileTranslatedSubtitle": "Oversatt undertekst", "FileCompare": "Sammenlign", "FileStatistics": "Statistikk", "FileImportPlainText": "Importer klartekst", - "FileImportBluRaySupForOcr": "Importer Blu-ray SUP for OCR", - "FileImportBluRaySupForEdit": "Importer Blu-ray SUP for redigering", - "FileImportTimeCodes": "Importer tidskoder", "FileExportEbuStl": "Eksporter EBU STL", "FileExportPac": "Eksporter PAC", - "FileExportEdlClipName": "Eksporter EDL klipnavn", - "FileExportPlainText": "Eksporter klartekst", - "FileExportCustomTextFormat1": "Eksporter egendefinert tekstformat 1", - "FileExportCustomTextFormat2": "Eksporter egendefinert tekstformat 2", - "FileExportCustomTextFormat3": "Eksporter egendefinert tekstformat 3", "FileExit": "Avslutt", "OpenSeDataFolder": "Åpne Subtitle Edit-mappe", - "EditFind": "Finn", - "EditFindNext": "Finn neste", "EditFindPrevious": "Finn forrige", - "EditReplace": "Erstatt", - "EditMultipleReplace": "Søk og erstatt flere", "FillSelectedLinesWithClipboard": "Fyll valgte linjer med utklippstavletekst", - "EditModifySelection": "Endre utvalg", - "ListSelectAll": "Velg alle", - "ListSelectFirst": "Velg første", - "ListSelectLast": "Velg siste", "ListInverseSelection": "Inverter utvalg", "ListDeleteSelection": "Slett utvalg", + "DeleteSelectionEverywhere": "Slett utvalg (overalt)", "RippleDeleteSelection": "Krusning-slett utvalg", - "Settings": "Innstillinger", "Assigned": "Tilordnet", "Unassigned": "Ikke tilordnet", "PressedKeyX": "Trykket tast: {0}", @@ -3459,6 +3772,7 @@ "DetectKey": "Gjenkjenn tast", "Control": "Control", "Alt": "Alt", + "AltGr": "AltGr", "Win": "Win", "Shift": "Shift", "ControlMac": "⌃", @@ -3469,11 +3783,14 @@ "ResetShortcutsDetail": "Vil du tilbakestille alle hurtigtaster til standardverdier?", "TogglePlayPause": "Veksle spill/pause", "ToggleLockTimeCodes": "Veksle lås tidskoder", + "ToggleTimeCodeMode": "Veksle tidskodemodus (tid/bilder/bildenumre)", + "ToggleScreenPrivacy": "Veksle skjermpersonvern (skjul filnavn, deretter også tekster)", + "ScreenPrivacyFileNamesHidden": "Skjermpersonvern: filnavn skjult", + "ScreenPrivacyFileNamesAndTextsHidden": "Skjermpersonvern: filnavn og tekster skjult", + "ScreenPrivacyOff": "Skjermpersonvern: av", "DuplicateSelectedLines": "Dupliser valgte linjer", "SourceView": "Kildevisning", - "ShowAlignmentPicker": "Justering", "AddOrEditBookmark": "Legg til eller rediger bokmerke", - "ListBookmarks": "Vis bokmerker", "ToggleBookmark": "Veksle bokmerke (valgte linjer, ingen tekst)", "ToggleForcedSelectedLines": "Veksle tvunget (valgte linjer)", "GoToNextBookmark": "Gå til neste bokmerke", @@ -3484,12 +3801,12 @@ "WaveformSetStartAndSetEndOfPreviousMinusGap": "Sett start og sett slutt for forrige minus mellomrom", "WaveformSetEndAndStartOfNextAfterGap": "Sett slutt og start for neste pluss mellomrom", "WaveformSetEndAndStartOfNextAfterGapAndGoToNext": "Sett slutt og start for neste pluss mellomrom og gå til neste", - "FetchFirstWordFromNextSubtitle": "Hent første ord fra neste undertekst", "MoveLastWordToNextSubtitle": "Flytt siste ord til neste undertekst", "MoveLastWordFromFirstLineDownCurrentSubtitle": "Flytt siste ord fra første linje ned (gjeldende undertekst)", "MoveFirstWordFromNextLineUpCurrentSubtitle": "Flytt første ord fra neste linje opp (gjeldende undertekst)", "MoveTextFromCursorToNextAndGoToNext": "Flytt tekst etter markørposisjonen til neste undertekst og gå til neste", "MoveTextFromCursorToNextAndGoToNextAndPlay": "Flytt tekst etter markørposisjonen til neste undertekst, gå til neste og spill av", + "MoveTextFromCursorToNextAndGoToNextAndPlayAndPause": "Flytt tekst etter markørposisjonen til neste undertekst, gå til neste, spill den av og sett på pause ved slutten", "BreakAtFirstSpaceFromCursor": "Del ved første mellomrom fra markørposisjonen", "BreakAtFirstSpaceFromCursorAndGoToNext": "Del ved første mellomrom fra markørposisjonen og gå til neste", "ToggleFocusGridAndWaveform": "Veksle fokus mellom undertekstrutenett og bølgeform/spektrogram", @@ -3501,7 +3818,6 @@ "VideoGoToPreviousTimeCode": "Gå til forrige tidskode", "VideoGoToNextTimeCode": "Gå til neste tidskode", "GoToNextLineAndSetVideoPosition": "Gå til neste undertekst og sett videoposisjon", - "TextBoxDeleteSelectionNoClipboard": "Tekstboks: Slett utvalg (ingen utklippstavle)", "TextBoxDeleteForward": "Tekstboks: Slett utvalg eller neste tegn (slett forover)", "TextBoxCut": "Tekstboks: Klipp ut", "TextBoxCut2": "Tekstboks: Klipp ut (alternativ)", @@ -3520,6 +3836,7 @@ "ListViewColumnTextUp": "Kolonne, tekst opp", "ListViewColumnTextDown": "Kolonne, tekst ned", "AutoTranslateSelectedLines": "Auto-oversett valgte linjer...", + "AutoTranslateSelectedLinesNoPrompt": "Auto-oversett valgte linjer (uten spørsmål, bruk siste motor/språk)", "SetAssaResolution": "Sett ASSA-oppløsning (PlayResX/PlayResY)", "SetShortcutForX": "Angi hurtigtast for \"{0}\"", "CommandFileNewKeepVideo": "Ny (behold video)", @@ -3527,29 +3844,22 @@ "FileCloseOriginal": "Lukk original", "FileCloseTranslation": "Lukk oversettelse", "RestoreAutoBackup": "Gjenopprett auto-sikkerhetskopi", - "OpenContainingFolder": "Åpne inneholdende mappe", "ImportTimeCodes": "Importer tidskoder", "ImportSubtitleWithManuallyChosenEncoding": "Importer undertekst med manuelt valgt koding", "ExportBluRaySup": "Eksporter Blu-ray SUP", "ExportCustomTextFormat": "Eksporter egendefinert tekstformat", "ExportPlainText": "Eksporter klartekst", - "ShowHistory": "Vis historikk", "ToggleRightToLeft": "Veksle høyre-til-venstre", "ModifySelection": "Endre utvalg", "AdjustDurations": "Juster varigheter", "ApplyDurationLimits": "Bruk varighetsgrenser", "BatchConvert": "Satsvis konvertering", - "BridgeGaps": "Tett hull", "ApplyMinGap": "Bruk min. mellomrom", - "FixCommonErrors": "Fiks vanlige feil", "MakeEmptyTranslationFromCurrentSubtitle": "Lag tom oversettelse fra gjeldende undertekst", - "MergeLinesWithSameText": "Slå sammen linjer med samme tekst", - "MergeLinesWithSameTimeCodes": "Slå sammen linjer med samme tidskoder", "SplitBreakLongLines": "Del opp / balanser lange linjer", "MergeShortLines": "Slå sammen korte linjer", "MergeContinuationLines": "Slå sammen fortsettelseslinjer", "SnapAllTimesToFrames": "Fest alle tider til bilder", - "RemoveTextForHearingImpaired": "Fjern tekst for hørselshemmede", "JoinSubtitles": "Slå sammen undertekster", "SplitSubtitle": "Del opp undertekst", "SpellCheck": "Stavekontroll", @@ -3571,7 +3881,6 @@ "AdjustAllTimes": "Juster alle tider", "VisualSync": "Visuell synkronisering", "TranslateViaCopyPaste": "Oversett via kopier-lim inn", - "Shortcuts": "Hurtigtaster", "WordLists": "Ordlister", "ChooseUiLanguage": "Velg språk for grensesnitt", "ChooseRuleProfile": "Velg regelprofil", @@ -3582,6 +3891,7 @@ "TextBoxItalic": "Tekstboks kursiv", "TextBoxBold": "Tekstboks fet", "TextBoxUnderline": "Tekstboks understreking", + "TextBoxBox": "Tekstboks boks (EBU STL)", "ResetWaveformZoomAndSpeed": "Tilbakestill bølgeform zoom og avspillingshastighet", "TogglePlaybackSpeed": "Veksle avspillingshastighet", "PlaybackSpeedSlower": "Avspillingshastighet saktere", @@ -3593,22 +3903,28 @@ "SeekSilenceForward": "Søk stillhet forover", "WaveformGuessStart": "Gjett starttid fra bølgeform", "WaveformGuessEnd": "Gjett sluttid fra bølgeform", + "WaveformGuessStartAndEnd": "Gjett start- og sluttid fra bølgeform", "SetVideoPositionCurrentSubtitleStart": "Sett videoposisjon til gjeldende linjestart", "GoToSubtitlePositionAndPause": "Gå til undertekstposisjon og pause", "SetVideoPositionCurrentSubtitleEnd": "Sett videoposisjon til gjeldende linjeslutt", "ToggleAudioTracks": "Veksle lydspor", - "ListErrors": "Vis feil", "GoToNextError": "Gå til neste feil", "GoToPreviousError": "Gå til forrige feil", "AddNameToNameList": "Legg til navn i navneliste", "FindDoubleWords": "Finn doble ord", "FindDoubleLines": "Finn doble linjer", "ColorX": "Farge {0}", - "RemoveColor": "Fjern farge", "SurroundWith": "Omgi med...", "SurroundWithXY": "Omgi med {0}/{1}", - "RepeatLine": "Gjenta linje", "SurroundWithNumberX": "Omslutt med #{0}", + "SurroundWithBehavior": "Virkemåte", + "SurroundWithBehaviorToggle": "Veksle (legg til, eller fjern hvis til stede)", + "SurroundWithBehaviorAdd": "Legg til (hver gang)", + "SurroundWithBehaviorRemove": "Fjern (alle)", + "SurroundWithBehaviorRemoveOnce": "Fjern (én hver gang)", + "SurroundWithWorksOn": "Virker på", + "SurroundWithScopeSelectionOrText": "Utvalg, ellers hele teksten", + "SurroundWithScopeEachLine": "Hver linje", "SearchVia": "Søk via", "SearchViaX": "Søk via {0}", "SearchViaNumberX": "Søk via #{0}", @@ -3636,6 +3952,11 @@ "CopySubtitlePathToClipboard": "Kopier undertekststi til utklippstavle", "CopySubtitleOriginalPathToClipboard": "Kopier undertekststi for original til utklippstavle", "FocusTextBox": "Fokus tekstboks", + "FocusSubtitleListView": "Fokus på undertekstlisten", + "FocusWaveform": "Fokus på bølgeform", + "FocusOriginalTextBox": "Fokus på original tekstboks", + "TextBoxGoToStart": "Tekstboks, gå til start", + "TextBoxGoToEnd": "Tekstboks, gå til slutt", "SortByNumber": "Sorter etter nummer", "SortByStartTime": "Sorter etter \"Vis\"-tid", "SortByEndTime": "Sorter etter \"Skjul\"-tid", @@ -3660,7 +3981,6 @@ "AssaStyles": "ASSA Stiler", "AssaProperties": "ASSA Egenskaper", "AssaAttachments": "ASSA Vedlegg", - "AssaVideoColorPicker": "ASSA Videofargevelger", "RecalculateDurationSelectedLines": "Beregn varighet på nytt (valgte linjer)", "SetDurationMaxCpsSelectedLines": "Sett varighet til maks. CPS (valgte linjer)", "ToggleWaveformAndSpectrogramHeight": "Veksle delt høyde for bølgeform/spektrogram", @@ -3676,7 +3996,7 @@ "GoogleIt": "Google det (valgt tekst)", "SetActorX": "Sett skuespiller: {0}", "SetNewActor": "Sett ny skuespiller...", - "SetActorSlotXTitle": "Skuespillerplass {0}", + "SetStyleX": "Angi stil: {0}", "ToggleDialogDashes": "Veksle dialogtankestreker", "MergeWithLineBeforeAsDialog": "Slå sammen med linjen før som dialog", "MergeWithLineAfterAsDialog": "Slå sammen med linjen etter som dialog", @@ -3687,13 +4007,27 @@ "MoveStartOneFrameBackKeepGapPrev": "Flytt start ett bilde bakover (behold mellomrom til forrige hvis nær)", "MoveStartOneFrameForwardKeepGapPrev": "Flytt start ett bilde forover (behold mellomrom til forrige hvis nær)", "MoveEndOneFrameBackKeepGapNext": "Flytt slutt ett bilde bakover (behold mellomrom til neste hvis nær)", - "MoveEndOneFrameForwardKeepGapNext": "Flytt slutt ett bilde forover (behold mellomrom til neste hvis nær)" + "MoveEndOneFrameForwardKeepGapNext": "Flytt slutt ett bilde forover (behold mellomrom til neste hvis nær)", + "MoveStartXMsBack": "Flytt start X ms tilbake (X angis i Innstillinger, også i Visuell synkronisering)", + "MoveStartXMsForward": "Flytt start X ms fremover (X angis i Innstillinger, også i Visuell synkronisering)", + "MoveEndXMsBack": "Flytt slutt X ms tilbake (X angis i Innstillinger, også i Visuell synkronisering)", + "MoveEndXMsForward": "Flytt slutt X ms fremover (X angis i Innstillinger, også i Visuell synkronisering)", + "MoveSelectedLinesXMsBack": "Flytt valgte linjer X ms tilbake (X angis i Innstillinger)", + "MoveSelectedLinesXMsForward": "Flytt valgte linjer X ms fremover (X angis i Innstillinger)", + "MoveSelectedLinesAndForwardXMsBack": "Flytt valgte linjer og alle etterfølgende X ms tilbake (X angis i Innstillinger)", + "MoveSelectedLinesAndForwardXMsForward": "Flytt valgte linjer og alle etterfølgende X ms fremover (X angis i Innstillinger)", + "MoveAllLinesXMsBack": "Flytt alle linjer X ms tilbake (X angis i Innstillinger)", + "MoveAllLinesXMsForward": "Flytt alle linjer X ms fremover (X angis i Innstillinger)", + "MoveSelectedLinesCustomMsBack": "Flytt valgte linjer, egendefinerte millisekunder ({0:#,###,##0}) tilbake, {1}", + "MoveSelectedLinesCustomMsForward": "Flytt valgte linjer, egendefinerte millisekunder ({0:#,###,##0}) fremover, {1}", + "MoveSelectedLinesAndForwardCustomMsBack": "Flytt valgte linjer og alle etterfølgende, egendefinerte millisekunder ({0:#,###,##0}) tilbake, {1}", + "MoveSelectedLinesAndForwardCustomMsForward": "Flytt valgte linjer og alle etterfølgende, egendefinerte millisekunder ({0:#,###,##0}) fremover, {1}", + "MoveAllLinesCustomMsBack": "Flytt alle linjer, egendefinerte millisekunder ({0:#,###,##0}) tilbake, {1}", + "MoveAllLinesCustomMsForward": "Flytt alle linjer, egendefinerte millisekunder ({0:#,###,##0}) fremover, {1}" }, "WordLists": { "Title": "Ordlister", "AddName": "Legg til navn", - "AddWord": "Legg til ord", - "AddPair": "Legg til par", "NameAndIgnoreList": "Navn/ignoreringsliste", "UserWords": "Brukerordliste", "OcrFixList": "OCR fikseliste", @@ -3710,7 +4044,6 @@ "NoPluginsInstalled": "Ingen plugins installert", "GetPluginsOnline": "Hent plugins på nett...", "GetPluginsTitle": "Hent plugins", - "Enabled": "Aktivert", "Disabled": "Deaktivert", "NotSupportedOnThisOs": "Støttes ikke på dette operativsystemet", "RemovePluginXConfirm": "Fjerne plugin '{0}'? Dette sletter plugin-mappen:{1}", @@ -3727,17 +4060,19 @@ "DownloadingXPercent": "Laster ned {0}%...", "PluginXInstalled": "'{0}' installert.", "CouldNotInstallPluginX": "Kunne ikke installere '{0}': {1}", - "NotInstalled": "Ikke installert", "Install": "Installer", "Reinstall": "Installer på nytt", - "Update": "Oppdater", "InstalledX": "Installert ({0})", "UpdateAvailableXToY": "Oppdatering tilgjengelig ({0} → {1})", "ApplyPluginToWhichLinesX": "Bruk '{0}' på:", "ApplyToSelectedLinesX": "Valgte linjer ({0})", "ApplyToAllLinesX": "Alle linjer ({0})", "UpdateAllXAvailable": "Oppdater alle ({0})", - "UpdatingXOfY": "Oppdaterer {0}/{1}..." + "UpdatingXOfY": "Oppdaterer {0}/{1}...", + "DoNotAskAgain": "Ikke spør igjen (endre i Administrer plugins)", + "ApplyPluginsTo": "Bruk plugins på", + "AskEachTime": "Spør hver gang", + "AllLines": "Alle linjer" }, "Help": { "AboutSubtitleEdit": "Om Subtitle Edit", @@ -3781,6 +4116,8 @@ "NOcrDatabase": "nOCR-database", "DrawMode": "Tegnemodus:", "AddNewCharcter": "Legg til nytt tegn", + "AddNOcrCharacterTitle": "Legg til nOCR-tegn for linje {0}, tegn {1} av {2} med databasen \"{3}\"", + "AddBinaryOcrCharacterTitle": "Legg til binært OCR-tegn for linje {0}, tegn {1} av {2} med databasen \"{3}\"", "LineIndexX": "Linje {0}", "InspectNOcrAdditions": "Inspiser nye nOCR-tillegg", "OcrSelectedLines": "OCR valgte linjer", @@ -3829,6 +4166,8 @@ "BinaryImageCompareInspectImageMatches": "\"Binær bildesammenligning\" - Inspiser bildetreff", "SaveBlankTextTitle": "Vil du lagre tom tekst?", "SaveBlankTextPrompt": "Lagre tom tekst for bildet?", + "DrawnLinesDoNotMatchTitle": "Linjene samsvarer ikke med bildet", + "DrawnLinesDoNotMatchPrompt": "Noen av de tegnede linjene samsvarer ikke med bildet (grønne linjer må ligge på tegnet, røde linjer må ligge utenfor), så dette tegnet vil ikke bli gjenkjent.\n\nVil du lagre likevel?", "YesAndNeverAskAgain": "Ja, og aldri spør igjen", "ImportTextFromSubtitleDotDotDot": "Importer tekst fra undertekst...", "ImportTextFromSubtitleOverwritePrompt": "Noen linjer har allerede OCR-tekst.\r\nVil du overskrive eksisterende tekst?", @@ -3854,7 +4193,6 @@ "VobSubColorsTitle": "VobSub/DVD-farger", "VobSubColorsHeader": "Tilpass de fire VobSub-fargene", "VobSubColorsDescription": "VobSub/DVD-undertekster bruker fire indekserte farger: bakgrunn, mønster, utheving 1 og utheving 2. Velg hver farge nedenfor for å overstyre den innebygde paletten – nyttig når originalfargene gir dårlig OCR-kontrast.", - "VobSubColorBackground": "Bakgrunn", "VobSubColorPattern": "Mønster", "VobSubColorEmphasis1": "Utheving 1", "VobSubColorEmphasis2": "Utheving 2", @@ -3888,9 +4226,26 @@ "CrispEmbedSettingsTitle": "CrispEmbed-innstillinger", "CrispEmbedDescription": "Lokal OCR-motor med flere modellbakstykker - last ned motoren og modellene her.", "TrainNOcrDatabase": "Tren nOCR-database...", + "OptimizeNOcrDatabase": "Optimaliser nOCR-database...", + "OptimizeNOcrDatabaseQuestionXY": "Fjerne linjene som ikke skiller tegn fra lignende tegn i nOCR-databasen \"{0}\"?\n\nDette gjør databasen mindre og OCR raskere, vanligvis med færre feil. Tar opptil ett minutt.\n\nEn sikkerhetskopi lagres som \"{1}\".", + "OptimizingNOcrDatabaseXY": "Optimaliserer nOCR-database... {0}/{1}", + "NOcrDatabaseOptimizedXYZ": "nOCR-databasen \"{0}\" er optimalisert: {1:#,##0} -> {2:#,##0} linjer.", "StartTraining": "Start trening", "AbortTraining": "Avbryt trening", "TrainingOptions": "Treningsalternativer", + "TrainNOcrSubtitle": "Tegner tegn i installerte skrifter og lærer nOCR formene deres. Velg skriftene undertekstene dine bruker.", + "XFontsSelected": "{0:#,##0} valgt", + "SubtitleFonts": "Undertekstskrifter", + "EmptyDatabase": "Tom database", + "StartFromDatabase": "Start fra", + "StartFromDatabaseHint": "Legg de trente tegnene til en kopi av en eksisterende database (anbefalt) eller start fra en tom", + "FontSizes": "Skriftstørrelser", + "FontSizesHint": "Skriftstørrelser i piksler å tegne med, adskilt med komma, f.eks. \"30, 40\"", + "NumberOfLineSegmentsHint": "Linjer som kontrolleres per tegn - flere er strengere, men tregere", + "EnterDatabaseName": "Skriv inn et navn på databasen", + "TrainingFontXSizeYCharacterZ": "Trener \"{0}\" med {1} px: {2}", + "XLearned": "{0:#,##0} lært", + "XSkipped": "{0:#,##0} hoppet over", "CharactersToTrain": "Tegn som skal trenes", "LetterCombinationsToTrain": "Bokstavkombinasjoner som kan bli delt til ett bilde", "ImportCharactersFromSubtitleFile": "Importer tegn fra undertekstfil...", @@ -3917,6 +4272,8 @@ "DrawZoomOut": "Zoom ut (Ctrl+-)", "DrawResetView": "Tilbakestill visning (Ctrl+0)", "DrawToggleGrid": "Veksle rutenett (Ctrl+G)", + "DrawTogglePreview": "Veksle forhåndsvisning (F9)", + "DrawPreviewFailed": "Forhåndsvisning mislyktes - ffmpeg med libass (subtitles-filter) kreves", "DrawCopyToClipboard": "Kopier til utklippstavle (Ctrl+C)", "DrawShapes": "Former", "DrawSelectedPoint": "Valgt punkt", @@ -3924,16 +4281,86 @@ "DrawSelectedLayer": "Valgt lag", "DrawToolX": "Verktøy: {0}", "DrawHelpText": "Klikk for å legge til punkter • Enter/F8 for å lukke form • Shift+Dra for å panorere • Ctrl+Rull for å zoome", + "DrawMoveToLayer": "Flytt til lag", + "DrawFlipHorizontal": "Vend horisontalt", + "DrawFlipVertical": "Vend vertikalt", + "DrawHideShape": "Skjul figur", + "DrawShowShape": "Vis figur", + "DrawHideLayer": "Skjul lag", + "DrawShowLayer": "Vis lag", + "DrawDeleteLayer": "Slett lag", + "DrawDeletePoint": "Slett punkt", + "DrawCancelDrawing": "Avbryt tegning", + "DrawLayerX": "Lag {0}", + "DrawImportSvg": "Importer SVG-bilde...", + "DrawRotateClockwise": "Roter 90° med klokken", + "DrawConvertToCurve": "Konverter segment til kurve", + "DrawBackground": "Bakgrunn", + "DrawPosition": "Posisjon", + "DrawZoom": "Zoom", + "DrawShapeTool": "Figurverktøy", + "DrawColorPickerTool": "Hent farge fra en figur eller bakgrunnen", + "DrawShapeLibrary": "Figurbibliotek", + "DrawShapeLibraryHint": "Dra på lerretet for å plassere en figur, eller klikk for standardstørrelse. Shift beholder proporsjonene.", + "DrawAddToLibrary": "Legg til i figurbiblioteket...", + "DrawRemoveFromLibrary": "Fjern fra figurbiblioteket", + "DrawShapeName": "Figurnavn", + "DrawCategorySpeechBubbles": "Snakkebobler", + "DrawCategoryArrows": "Piler", + "DrawCategoryBasicShapes": "Grunnleggende figurer", + "DrawCategorySymbols": "Symboler", + "DrawCategoryMyShapes": "Mine figurer", + "DrawShapeSpeechBubble": "Snakkeboble", + "DrawShapeSpeechBubbleRight": "Snakkeboble, hale mot høyre", + "DrawShapeSpeechBubbleUp": "Snakkeboble, hale opp", + "DrawShapeOvalBubble": "Oval snakkeboble", + "DrawShapeBoxBubble": "Firkantet snakkeboble", + "DrawShapeThoughtBubble": "Tankeboble", + "DrawShapeShoutBubble": "Ropeboble", + "DrawShapeCaptionBox": "Tekstboks", + "DrawShapeArrow": "Pil", + "DrawShapeDoubleArrow": "Dobbeltpil", + "DrawShapeChevron": "Vinkel", + "DrawShapeCurvedArrow": "Buet pil", + "DrawShapeRoundedRectangle": "Avrundet rektangel", + "DrawShapeTriangle": "Trekant", + "DrawShapeDiamond": "Rute", + "DrawShapePentagon": "Femkant", + "DrawShapeHexagon": "Sekskant", + "DrawShapeOctagon": "Åttekant", + "DrawShapeStar": "Stjerne", + "DrawShapeRing": "Ring", + "DrawShapePlus": "Pluss", + "DrawShapeBanner": "Banner", + "DrawShapeHeart": "Hjerte", + "DrawShapeCheck": "Hake", + "DrawShapeMusicNote": "Musikknote", + "DrawShapeMusicNotes": "Musikknoter", + "DrawShapeLightning": "Lyn", + "DrawShapeCloud": "Sky", + "DrawShapeMoon": "Måne", + "DrawShapeBadge": "Merke", + "DrawSize": "Størrelse", + "DrawLayer": "Lag", + "DrawBackgroundVideoFrame": "Videobilde (gjeldende posisjon)", + "DrawBackgroundVideoFrameAt": "Videobilde ved...", + "DrawBackgroundImage": "Bildefil...", + "DrawBackgroundNone": "Ingen bakgrunn", + "DrawBackgroundStretch": "Strekk bildet til lerretet", + "DrawBackgroundOpacity": "Bakgrunnens dekkevne", + "DrawBackgroundVideoFrameFailed": "Kunne ikke hente et videobilde ved {0}.", + "DrawImages": "Bilder", + "DrawConvertToLine": "Konverter segment til linje", + "DrawConvertShapeToCurves": "Konverter alle linjer til kurver", + "DrawConvertShapeToLines": "Konverter alle kurver til linjer", + "DrawRotateCounterClockwise": "Roter 90° mot klokken", + "DrawSvgImages": "SVG-bilder", + "DrawSvgNoShapes": "Fant ingen fylte figurer eller streker i SVG-filen.", "ProgressBarTitle": "ASSA fremdriftslinje", "ProgressBarSettings": "Fremdriftslinje", - "ProgressBarPosition": "Posisjon", - "ProgressBarBottom": "Bunn", - "ProgressBarTop": "Topp", "MouseOverColor": "Farge ved musepeker over", "ClickedColor": "Farge ved klikk", "ProgressBarForeColor": "Forgrunnsfarge", - "ProgressBarBackColor": "Bakgrunnsfarge", - "ProgressBarStyle": "Stil", "ProgressBarSquareCorners": "Firkantede hjørner", "ProgressBarRoundedCorners": "Avrundede hjørner", "ProgressBarChapters": "Kapitler", @@ -3942,8 +4369,6 @@ "ProgressBarXAdjustment": "X-justering", "ProgressBarYAdjustment": "Y-justering", "ProgressBarTextAlignment": "Tekstjustering", - "ProgressBarTakePosFromVideo": "Hent posisjon fra video", - "ProgressBarPreview": "Forhåndsvisning", "FontCollectorTitle": "Skriftsamler", "FontCollectorUsedIn": "Brukt i", "FontCollectorFontFiles": "Skriftfil(er)", @@ -3990,9 +4415,7 @@ "ResolutionResamplerVideoDiffers": "Undertekstens oppløsning ({0}x{1}) er forskjellig fra videoens oppløsning ({2}x{3}).\nVil du endre underteksten til å passe videoen?", "ResolutionResamplerAskOnVideoOpen": "Spør når en video med en annen oppløsning åpnes", "BackgroundBoxGenerator": "Generer bakgrunnsboks", - "BackgroundBoxPadding": "Utfylling", "BackgroundBoxFillWidth": "Fyll bredde", - "BackgroundBoxBoxColor": "Boksfarge", "BackgroundBoxRadius": "Radius", "BackgroundBoxCircle": "Sirkel", "BackgroundBoxSpikes": "Pigger", @@ -4004,12 +4427,9 @@ "BackgroundBoxTornPaperDouble": "Revet papir (topp & bunn)", "BackgroundBoxStarburst": "Stjerneburst", "BackgroundBoxScroll": "Skriftrull / Pergament", - "StylesTitle": "Advanced Sub Station Alpha-stiler", "StylesInFile": "Stiler i fil", "StylesSaved": "Stiler lagret", - "Category": "Kategori", "AllCategories": "[Alle]", - "DefaultCategory": "Standard", "NewCategory": "Ny kategori...", "RenameCategory": "Gi kategori nytt navn...", "DeleteCategory": "Slett kategori", @@ -4032,13 +4452,26 @@ "Graphics": "Grafikk", "CopyToStorageStyles": "Kopier til lagringsstiler", "CopyToFileStyles": "Kopier til filstiler", + "StyleXAlreadyExistsInStorage": "En stil med navnet \"{0}\" finnes allerede i lagringen.\n\nVil du overskrive den, eller beholde begge (kopien får et nytt navn)?", + "StyleXAlreadyExistsInFile": "En stil med navnet \"{0}\" finnes allerede i filen.\n\nVil du overskrive den, eller beholde begge (kopien får et nytt navn)?", + "StyleNameCannotBeEmpty": "Et stilnavn kan ikke være tomt.", + "StyleNameXIsUsedMoreThanOnce": "Stilnavnet \"{0}\" brukes av mer enn én stil i filen.", + "Overwrite": "Overskriv", + "KeepBoth": "Behold begge", + "DoThisForAllConflictingStyles": "Gjør dette for alle stiler i konflikt", "SetStyleAsDefault": "Sett stil som standard", "TakeUsagesFromDotDotDot": "Hent bruk fra...", "ReplaceStyleWithDotDotDot": "Erstatt stil med...", "NoAttachmentsFound": "Ingen vedlegg funnet i den valgte ASSA-filen.", "DeleteStyleQuestion": "Slett stil?", "DeleteStylesQuestion": "Slett stiler?", + "DeleteXStylesQuestion": "Vil du slette {0} stiler?", + "DeleteXStylesFromStorageQuestion": "Vil du slette {0} stiler fra lagringen?", + "DeleteStyleXFromStorageQuestion": "Vil du slette stilen \"{0}\" fra lagringen?", + "DeleteStyleXFromFileQuestion": "Vil du slette stilen \"{0}\" fra gjeldende fil?", "OpenStyleImportFile": "Åpne undertekstfil å importere stiler fra", + "StyleImportFiles": "Stilfiler (.ass, .sty, Subtitle Edit 4-kategorier .template)", + "SsaStyleImportFiles": "Stilfiler (.ssa, .ass, .sty)", "Primary": "Primær", "Secondary": "Sekundær", "ApplyOverrideTags": "Bruk overstyringskoder", @@ -4053,7 +4486,6 @@ "SpaceIncrease": "Mellomromsøkning", "PopIn": "Pop inn", "SpinIn": "Spinn inn", - "PlayCurrent": "Spill gjeldende", "SetPosition": "Angi posisjon", "Rotation": "Rotasjon", "ScaleX": "Skala X", @@ -4097,10 +4529,8 @@ "AdvancedEffectOldMovieDescription": "Simulerer en vintage 35mm filmprojektor med støy, vignett og riper", "AdvancedEffectNeonBurst": "Neon-utbrudd", "AdvancedEffectNeonBurstDescription": "Høyenergi 'Shorts'-stiltekster med neon-glød og skaler-inn 'pop'-animasjon", - "AdvancedEffectSettings": "Innstillinger", "AdvancedEffectSnowFlakeCount": "Antall fnugg", "AdvancedEffectStarfieldStarCount": "Antall stjerner", - "AdvancedEffectStarfieldSpeed": "Hastighet", "AdvancedEffectGlitch": "Glitch", "AdvancedEffectGlitchDescription": "Digital glitch med kromatisk avvik, hvite blink og forvrengningsoverlegg", "AdvancedEffectBounceIn": "Sprett inn", @@ -4109,8 +4539,6 @@ "AdvancedEffectFirefliesDescription": "Varme glødende gul-ravfargede prikker driver organisk over skjermen med pulserende glød", "AdvancedEffectMatrix": "Matrix", "AdvancedEffectMatrixDescription": "Fallende grønne tegnsøyler hvor underteksten bygges opp bokstav for bokstav fra regnet", - "AdvancedEffectAudioBars": "Lydstolper (bølgeform)", - "AdvancedEffectAudioBarsDescription": "Animerte equalizer-stolper drevet av lydbølgeformen — stolper ruller mot venstre som en bølgeform og pulserer med lyden", "AdvancedEffectAudioPulse": "Lydtekst-puls", "AdvancedEffectAudioPulseDescription": "Underteksten gløder og pulserer med lydvolumet — stille partier viser normal tekst, høye topper legger til en farget glorie og en liten oppskalering", "AdvancedEffectConfetti": "Konfetti-utbrudd", @@ -4124,7 +4552,6 @@ "AdvancedEffectFancyKaraokeGlow": "Aktivt ord-glød", "AdvancedEffectFancyKaraokeActiveColor": "Aktiv farge", "AdvancedEffectFancyKaraokeInactiveColor": "Inaktiv farge", - "AdvancedEffectFancyKaraokeInactiveOpacity": "Inaktiv ugjennomsiktighet (0-255)", "AdvancedEffectWordSpacingPixels": "Avstand (piksler)", "AdvancedEffectSlowZoomOut": "Sakte zoom-ut", "AdvancedEffectSlowZoomOutDescription": "Teksten starter litt større og krymper forsiktig til normal størrelse over undertekstens varighet", @@ -4140,7 +4567,6 @@ "AdvancedEffectSlideInLeftDescription": "Teksten glir inn fra utenfor skjermen til venstre, holdes, og går deretter tilbake til venstre", "AdvancedEffectSlideInRight": "Gli inn fra høyre", "AdvancedEffectSlideInRightDescription": "Teksten glir inn fra utenfor skjermen til høyre, holdes, og går deretter tilbake til høyre", - "AdvancedEffectFadeInOut": "Inntoning/uttoning", "AdvancedEffectFadeInOutDescription": "Teksten tones inn i begynnelsen og tones ut på slutten av hver undertekst", "AdvancedEffectWordFlip3D": "Ordvending 3D", "AdvancedEffectWordFlip3DDescription": "Hvert ord felles inn med en 3D-vending og et elastisk sprett når det sies — moderne 'Shorts'-undertekststil", diff --git a/src/ui/Assets/Languages/Persian.json b/src/ui/Assets/Languages/Persian.json index a25d3c52fb2..ec44ede7dd6 100644 --- a/src/ui/Assets/Languages/Persian.json +++ b/src/ui/Assets/Languages/Persian.json @@ -1,12 +1,30 @@ { "title": "Subtitle Edit", - "version": "v5.2.0-rc7", + "version": "v5.3.0", "translatedBy": "رامتین جوکار", "cultureName": "fa-IR", "general": { "abort": "لغو", "actor": "بازیگر/گوینده", "actors": "بازیگران/گویندگان", + "setActorDotDotDot": "تنظیم بازیگر/گوینده...", + "actorPickerTitle": "تنظیم بازیگر/گوینده", + "actorPickerFilterHint": "برای فیلتر کردن تایپ کنید، یا نام بازیگر جدیدی را وارد کنید", + "actorPickerKeysHint": "برای تنظیم بازیگر، یک عدد یا Enter را فشار دهید. Alt+بالا/پایین ترتیب را تغییر می‌دهد و Delete بازیگر را حذف می‌کند.", + "actorPickerNewActorX": "بازیگر جدید \"{0}\"", + "actorPickerLinesSelectedX": "خطوط انتخاب‌شده: {0}", + "setStyleDotDotDot": "تنظیم استایل...", + "stylePickerTitle": "تنظیم استایل", + "stylePickerFilterHint": "برای فیلتر کردن تایپ کنید، یا نام استایل جدیدی را وارد کنید", + "stylePickerKeysHint": "برای تنظیم استایل روی همه خطوط انتخاب‌شده، یک عدد (۱-۹، ۰) را فشار دهید.", + "stylePickerNewStyleX": "استایل جدید \"{0}\"", + "stylePickerNewStyleInfo": "Enter را فشار دهید تا این استایل با تنظیمات پیش‌فرض اضافه شده و روی خطوط انتخاب‌شده تنظیم شود. برای تغییر فونت، رنگ‌ها و موقعیت آن در آینده از \"مدیریت استایل‌ها...\" استفاده کنید.", + "stylePickerCurrentStyleX": "استایل فعلی: {0}", + "stylePickerLinesUsingStyleX": "خطوطی که از این استایل استفاده می‌کنند: {0}", + "stylePickerMarginsXYZ": "چپ {0}، راست {1}، عمودی {2}", + "stylePickerSetStyle": "تنظیم استایل", + "stylePickerManageStyles": "مدیریت استایل‌ها...", + "stylePickerManageStylesHint": "این پنجره را ببندید و مدیر استایل‌ها را برای ویرایش فونت‌ها، رنگ‌ها، موقعیت و موارد دیگر باز کنید", "add": "افزودن", "addDotDotDot": "افزودن...", "addToNamesListCaseSensitive": "افزودن به لیست نام‌ها (حساس به حروف کوچک و بزرگ)", @@ -339,6 +357,7 @@ "mergeSelectedLinesDialog": "ادغام خطوط انتخاب‌شده به عنوان مکالمه", "mergeWithLineAfterAndAutoBreak": "ادغام با خط بعد و شکستن خودکار خط", "mergeWithLineAfterKeepBreaks": "ادغام با خط بعد (حفظ شکستگی‌ها)", + "mergeWithLineAfterAndUnbreak": "ادغام با خط بعد و حذف شکستگی", "mergeWithLineBeforeAndAutoBreak": "ادغام با خط قبل و شکستن خودکار خط", "mergeWithLineBeforeKeepBreaks": "ادغام با خط قبل (حفظ شکستگی‌ها)", "middleCenter": "وسط-وسط", @@ -353,6 +372,8 @@ "moveAllShotChangeOneFrameForward": "انتقال تمام تغییرات پلان به اندازه یک فریم به جلو", "multiMode": "حالت چندگانه", "moveDown": "حرکت به پایین", + "increase": "افزایش", + "decrease": "کاهش", "moveUp": "حرکت به بالا", "moveToTop": "انتقال به بالا", "moveToBottom": "انتقال به پایین", @@ -422,6 +443,7 @@ "percent": "درصد", "pickLayer": "تنظیم لایه", "pickOllamaModel": "انتخاب مدل Ollama", + "pickModel": "انتخاب مدل", "llamaCppUseRemoteServer": "استفاده از سرور خارجی (URL)", "llamaCppEngineSettings": "تنظیمات موتور llama.cpp", "llamaCppEngineSettingsSubtitle": "ساخت سرور محلی llama.cpp", @@ -498,6 +520,7 @@ "reset": "بازنشانی", "resizePanels": "تغییر اندازه پنل‌ها", "resolution": "رزولوشن", + "retry": "تلاش مجدد", "reverseRightToLeftStartEnd": "معکوس کردن شروع/پایان راست‌به‌چپ", "right": "راست", "rightToLeft": "راست به چپ", @@ -559,6 +582,8 @@ "shadowColor": "رنگ سایه", "shadowWidth": "عرض سایه", "shiftLineBy": "جابجایی خط به میزان", + "shotIn": "ورود پلان", + "shotOut": "خروج پلان", "shortcut": "میانبر", "shortcuts": "کلیدهای میانبر", "show": "نمایش", @@ -570,9 +595,13 @@ "showStartColumn": "نمایش ستون \"زمان شروع\"", "showHideColumn": "نمایش ستون \"زمان پایان\"", "showHistory": "نمایش تاریخچه", + "clearHistory": "پاک کردن تاریخچه", + "removeFromHistory": "حذف از تاریخچه", "showLayerColumn": "نمایش ستون \"لایه\"", "showPreview": "نمایش پیش‌نمایش", "showShotChangesList": "نمایش لیست تغییرات پلان", + "showShotInColumn": "نمایش ستون \"ورود پلان\"", + "showShotOutColumn": "نمایش ستون \"خروج پلان\"", "showStyleColumn": "نمایش ستون \"استایل\"", "showTeletext": "نمایش تله‌تکست", "showTimeCodes": "نمایش کدهای زمانی", @@ -629,12 +658,14 @@ "styleExaggeration": "اغراق استایل", "styles": "استایل‌ها", "subtitleFile": "فایل زیرنویس", + "subtitleFileChangedOutsideX": "فایل \"{0}\" خارج از Subtitle Edit تغییر کرده است.\n\nآن را دوباره بارگذاری کنید و تغییرات خود را از دست بدهید؟", "subtitleFileName": "نام فایل زیرنویس", "subtitleFileSaved": "فایل زیرنویس ذخیره شد", "subtitleFileSavedToX": "فایل زیرنویس در {0} ذخیره شد", "subtitleFiles": "فایل‌های زیرنویس", "subtitleFormats": "فرمت‌های زیرنویس", "subtitleLoadedX": "زیرنویس بارگذاری شد: {0}", + "subtitleReloadedChangedOutsideX": "زیرنویس دوباره بارگذاری شد (خارج از Subtitle Edit تغییر کرده بود): {0}", "suffix": "پسوند", "suggestions": "پیشنهادات", "sync": "همگام‌سازی", @@ -680,6 +711,7 @@ "type": "نوع", "unbreak": "پیوستن مجدد متن (حذف شکستگی)", "unbreakLines": "پیوستن مجدد خطوط", + "undelete": "بازگردانی حذف", "underline": "خط زیر متن", "undo": "لغو عملکرد", "unknown": "ناشناخته", @@ -717,10 +749,23 @@ "videoCustom3ForwardX": "ویدئو، میلی‌ثانیه سفارشی ({0:#,###,##0}) به جلو، ۳", "videoCustom4BackX": "ویدئو، میلی‌ثانیه سفارشی ({0:#,###,##0}) به عقب، ۴", "videoCustom4ForwardX": "ویدئو، میلی‌ثانیه سفارشی ({0:#,###,##0}) به جلو، ۴", + "videoCustom1BackAndPauseX": "ویدئو، میلی‌ثانیه سفارشی ({0:#,###,##0}) به عقب و توقف موقت، ۱", + "videoCustom1ForwardAndPauseX": "ویدئو، میلی‌ثانیه سفارشی ({0:#,###,##0}) به جلو و توقف موقت، ۱", + "videoCustom2BackAndPauseX": "ویدئو، میلی‌ثانیه سفارشی ({0:#,###,##0}) به عقب و توقف موقت، ۲", + "videoCustom2ForwardAndPauseX": "ویدئو، میلی‌ثانیه سفارشی ({0:#,###,##0}) به جلو و توقف موقت، ۲", + "videoCustom3BackAndPauseX": "ویدئو، میلی‌ثانیه سفارشی ({0:#,###,##0}) به عقب و توقف موقت، ۳", + "videoCustom3ForwardAndPauseX": "ویدئو، میلی‌ثانیه سفارشی ({0:#,###,##0}) به جلو و توقف موقت، ۳", + "videoCustom4BackAndPauseX": "ویدئو، میلی‌ثانیه سفارشی ({0:#,###,##0}) به عقب و توقف موقت، ۴", + "videoCustom4ForwardAndPauseX": "ویدئو، میلی‌ثانیه سفارشی ({0:#,###,##0}) به جلو و توقف موقت، ۴", "videoExtension": "پسوند فایل ویدئویی", "videoFile": "فایل ویدئویی", "videoFileGenerated": "فایل ویدیویی ایجاد شد", "videoFileGeneratedX": "فایل ویدئویی تولید شد: \"{0}\"", + "videoFilesGenerated": "فایل‌های ویدئویی تولید شدند", + "xVideosGenerated": "{0} ویدئو تولید شد", + "xOfYVideosGenerated": "{0} از {1} ویدئو تولید شد", + "savedInX": "ذخیره شد در {0}", + "savedInXFolders": "ذخیره شد در {0} پوشه", "videoFiles": "فایل‌های ویدئویی", "videoAndAudioFiles": "فایل‌های ویدئویی و صوتی", "videoOffset": "انحراف ویدئو", @@ -778,7 +823,6 @@ "imageFiles": "فایل‌های تصویری", "openAiCompatibleSttEndpoint": "آدرس Endpoint برای STT", "openAiCompatibleSttApiKey": "کلید API", - "openAiCompatibleSttModel": "مدل", "openAiCompatibleSttExtraHeaders": "هدرهای اضافی", "openAiCompatibleSttTimeout": "زمان انتظار ابطال (ثانیه)", "openAiCompatibleSttLanguage": "راهنمای زبان", @@ -788,6 +832,9 @@ "dashScopeSttRegion": "منطقه", "dashScopeSttEnableWords": "برچسب زمانی در سطح کلمات", "dashScopeSttRegionKeyHint": "نکته: کلیدهای API بخش Alibaba Cloud Model Studio وابسته به منطقه هستند - مطمئن شوید منطقه انتخاب‌شده با منطقه‌ای که کلید در آن ساخته شده مطابقت دارد (چین در مقابل بین‌المللی).", + "googleCloudSttProjectId": "شناسه پروژه (فقط برای Application Default Credentials)", + "googleCloudSttBucketName": "باکت ذخیره‌سازی (خالی = خودکار)", + "googleCloudSttDynamicBatching": "دسته‌بندی پویا (حدود ۵ برابر ارزان‌تر، بدون تضمین تأخیر)", "onlineSttApiKeyMissing": "کلید API الزامی است. لطفاً کلید خود را وارد کرده و دوباره تلاش کنید.", "openAiCompatibleSttAutoTranscribeOnAudioSelection": "پیاده‌سازی خودکار متن برای بخش انتخاب‌شده در شکل موج از طریق گفتار به متن", "openAiCompatibleSttStream": "پاسخ به صورت استریم (جریانی)", @@ -800,7 +847,6 @@ "processingResponse": "در حال پردازش پاسخ...", "unauthorizedApiKey": "عدم دسترسی: کلید API نامعتبر است یا مجوزهای کافی وجود ندارد.", "requestTimeout": "پایان زمان درخواست: پاسخ‌دهی سرور خیلی طول کشید.", - "noSegmentsReturned": "هیچ بخشی از سرویس پیاده‌سازی متن دریافت نشد.", "openAiCompatibleSttUrlMissing": "آدرس Endpoint تبدیل گفتار به متن سازگار با OpenAI الزامی است. لطفاً یک آدرس وارد کرده و دوباره تلاش کنید.", "openAiCompatibleSttUrlNotResponding": "آدرس Endpoint تبدیل گفتار به متن پاسخگو نیست:\n{0}\n\nلطفاً صحت آدرس و روشن بودن سرور را بررسی کنید.", "transcriptionFailed": "پیاده‌سازی متن با شکست مواجه شد", @@ -846,6 +892,7 @@ "multipleReplace": "جایگزینی چندگانه...", "fillSelectedLinesWithClipboard": "پر کردن خطوط انتخاب‌شده با متن حافظه موقت", "goToLineNumber": "برو به خط شماره...", + "commandPalette": "پالت فرمان...", "rightToLeftMode": "حالت راست‌به‌چپ", "modifySelectionDotDotDot": "اصلاح خطوط انتخاب‌شده...", "tools": "ابزارها", @@ -853,6 +900,7 @@ "applyDurationLimits": "اعمال محدودیت‌های مدت زمان...", "batchConvert": "تبدیل دسته‌ای (گروهی)...", "beautifyTimeCodes": "زیباسازی کدهای زمانی...", + "improveTimeCodes": "بهبود کدهای زمانی (هم‌ترازی اجباری)...", "bridgeGaps": "پر کردن فاصله‌ها...", "applyMinGap": "اعمال حداقل فاصله بین زیرنویس‌ها...", "changeCasing": "تغییر حالت حروف...", @@ -991,6 +1039,9 @@ "selectCurrentLineWhilePlayingHint": "انتخاب زیرنویس فعلی در زمان پخش {0}", "videoPosition": "موقعیت ویدئو {0}", "videoPositionTextBox": "کادر متنی موقعیت ویدیو {0}", + "toolbarLineBreakX": "شکست خط {0} (بقیه نوار ابزار در ردیف جدید ادامه می‌یابد)", + "initialText": "متن اولیه خط انتخاب‌شده", + "initialTextHint": "متن خط انتخاب‌شده همان‌طور که هنگام انتخاب خط بود", "hideWaveformToolbar": "پنهان کردن نوار ابزار {0}", "resetZoomAndSpeed": "بازنشانی بزرگنمایی و سرعت پخش {0}", "removeBlankLines": "حذف خطوط خالی {0}", @@ -1004,6 +1055,15 @@ "textPlayHint": "پخش زیرنویس فعلی و توقف در پایان {0}", "textPauseHint": "توقف موقت پخش {0}", "textNextHint": "پخش زیرنویس بعدی و توقف در پایان {0}", + "moveSelectedLines": "جابجایی خطوط انتخاب‌شده (دکمه‌های عقب/جلو)", + "moveSelectedLinesAndFollowing": "جابجایی خطوط انتخاب‌شده و همه خطوط بعدی (دکمه‌های عقب/جلو)", + "moveAllLines": "جابجایی همه خطوط (دکمه‌های عقب/جلو)", + "moveSelectedLinesBackHint": "جابجایی خطوط انتخاب‌شده {0} میلی‌ثانیه به عقب {1}", + "moveSelectedLinesForwardHint": "جابجایی خطوط انتخاب‌شده {0} میلی‌ثانیه به جلو {1}", + "moveSelectedLinesAndFollowingBackHint": "جابجایی خطوط انتخاب‌شده و همه خطوط بعدی {0} میلی‌ثانیه به عقب {1}", + "moveSelectedLinesAndFollowingForwardHint": "جابجایی خطوط انتخاب‌شده و همه خطوط بعدی {0} میلی‌ثانیه به جلو {1}", + "moveAllLinesBackHint": "جابجایی همه خطوط {0} میلی‌ثانیه به عقب {1}", + "moveAllLinesForwardHint": "جابجایی همه خطوط {0} میلی‌ثانیه به جلو {1}", "guessStartNoLineSelected": "حدس شروع: هیچ خطی انتخاب نشده است", "guessStartTimeCodesLocked": "حدس شروع: کدهای زمانی قفل هستند", "guessStartNoWaveform": "حدس شروع: شکل موجی وجود ندارد", @@ -1025,6 +1085,9 @@ "charactersPerSecond": "کاراکتر/ثانیه: {0}", "chooseColumn": "انتخاب ستون", "columnPaste": "چسباندن ستونی", + "commandPaletteTitle": "پالت فرمان", + "commandPaletteSearchPlaceholder": "برای جستجوی فرمان‌ها تایپ کنید...", + "commandPaletteNoCommandsFound": "هیچ فرمان منطبقی یافت نشد", "createdEmptyTranslation": "ترجمه خالی از زیرنویس فعلی ایجاد شد", "deleteText": "حذف متن", "deleteTextAndShiftCellsUp": "حذف متن و انتقال سلول‌ها به بالا", @@ -1063,11 +1126,14 @@ "numberOfLinesEvenlyDistributedX": "تعداد {0} خط به صورت یکنواخت توزیع شدند", "oneLineCopiedFromOriginal": "یک خط از زیرنویس اصلی کپی شد", "oneLineCopiedToOriginal": "یک خط در زیرنویس اصلی کپی شد", + "onlineOnlyVideo": "ویدئوی فقط آنلاین", + "onlineOnlyVideoXSizeYDownloadAndOpen": "فایل ویدئویی \"{0}\" ({1}) فقط به‌صورت آنلاین ذخیره شده است، برای مثال در Dropbox یا iCloud Drive، و باید پیش از باز شدن دانلود شود.\n\nاکنون دانلود و باز شود؟", "oneLineMerged": "یک خط ادغام شد", "oneLineSwitched": "جای یک خط تعویض شد", "overwriteExistingCells": "جایگزینی سلول‌های موجود", "overwriteOrShiftCellsDown": "جایگزینی/انتقال سلول‌ها به پایین", "parsingMatroskaFile": "در حال تجزیه فایل ماتروشکا (MKV)...", + "readingDvdSubtitles": "در حال خواندن زیرنویس‌های DVD...", "pasteFromClipboardDotDotDot": "چسباندن از حافظه موقت...", "redoPerformed": "عملکرد مجدداً انجام شد (Redo)", "redoPerformedXActionLeft": "عملکرد مجدداً انجام شد (تعداد کارهای باقی‌مانده: {0})", @@ -1107,12 +1173,14 @@ "trimmedXLines": "تعداد {0} خط زیرنویس کوتاه شدند", "pastedXLinesOverSelectedLines": "{0} خط روی خطوط انتخاب‌شده جای‌گذاری شد", "pastedXOfYLinesOverSelectedLines": "{0} خط از {1} خط روی خطوط انتخاب‌شده جای‌گذاری شد - بقیه با محدوده انتخاب‌شده مطابقت نداشتند", + "insertedXLinesFromClipboard": "{0} خط از کلیپ‌بورد درج شد", "openOriginalDifferentNumberOfSubtitlesXY": "تعداد خطوط فایل زیرنویس اصلی با فایل زیرنویس فعلی یکسان نیست.\n\n• زیرنویس‌های اصلی: {0}\n• زیرنویس‌های فعلی: {1}", "originalTextReadOnly": "متن اصلی (فقط خواندنی)", "originalTextEditMode": "متن اصلی (حالت ویرایش)", "originalIsReadOnlyNotSaved": "زیرنویس اصلی فقط به‌صورت مرجع و خواندنی است و ذخیره نشد", "originalIsEmptyNotSaved": "زیرنویس اصلی هیچ متنی ندارد و ذخیره نشد", "originalIsReadOnlyReference": "زیرنویس اصلی به‌عنوان مرجع فقط‌خواندنی باز شده است", + "closeTranslationQuestionXY": "ترجمه \"{0}\" بسته شود؟\n\nزیرنویس اصلی \"{1}\" به زیرنویسی که ویرایش می‌کنید تبدیل می‌شود. خود فایل ترجمه تغییر نمی‌کند.", "allowEditOfOriginalSubtitle": "اجازه ویرایش زیرنویس اصلی", "showAllOriginalLinesX": "نمایش همه خطوط اصلی ({0} مورد در اینجا تطابق ندارند)", "showAllOriginalLinesHint": "خطوطی که تطابق ندارند به‌صورت ردیف‌های اضافی نمایش داده می‌شوند تا بتوانید مواردی را که در ترجمه وجود ندارند مشاهده کنید.", @@ -1147,6 +1215,7 @@ "fileExportedInFormatXToY": "فایل در فرمت {0} به مسیر {1} خروجی گرفته شد", "fileExportedInFormatXToFileY": "فایل در فرمت \"{0}\" در فایل \"{1}\" ذخیره شد", "fixedXLines": "تعداد {0} خط اصلاح شد", + "toolXChangedYLines": "{0}: {1} خط تغییر کرد", "transcriptionCompletedWithXLines": "پیاده‌سازی متن با {0} خط کامل شد", "openSubtitleFileX": "فایل زیرنویس \"{0}\" باز شود؟", "replacedXOccurrences": "تعداد {0} مورد جایگزین شد", @@ -1237,6 +1306,21 @@ "formattingDotDotDot": "فرمت‌دهی...", "imageBasedSubtitleForEditDotDotDot": "زیرنویس مبتنی بر تصویر برای ویرایش...", "imageBasedSubtitleForOcrDotDotDot": "زیرنویس مبتنی بر تصویر برای OCR...", + "dvdSubtitlesDotDotDot": "زیرنویس‌ها از DVD (IFO/VOB)...", + "titleImportDvd": "وارد کردن زیرنویس‌ها از DVD", + "dvdIfoFile": "فایل IFO", + "dvdVobFiles": "فایل‌های VOB", + "dvdVideoStandard": "استاندارد ویدئو", + "dvdStartRipping": "شروع استخراج", + "dvdRippingAborted": "استخراج لغو شد", + "dvdOpenIfoFile": "باز کردن فایل IFO دی‌وی‌دی", + "dvdAddVobFiles": "افزودن فایل‌های VOB", + "dvdIfoFiles": "فایل‌های IFO دی‌وی‌دی", + "dvdVobFileType": "فایل‌های VOB دی‌وی‌دی", + "dvdDropHint": "یک فایل IFO (VIDEO_TS\\VTS_01_0.IFO) باز کنید یا فایل‌های VOB را اضافه کنید - یا آن‌ها را اینجا رها کنید", + "dvdNotAnIfoFileX": "{0} یک فایل IFO دی‌وی‌دی نیست", + "dvdMenuIfoNoTitleSetsX": "هیچ مجموعه عنوانی (VTS_xx_0.IFO) در کنار {0} یافت نشد", + "dvdEncryptedXOfY": "{0} از {1} بسته زیرنویس هنوز با CSS رمزگذاری شده‌اند - این فایل‌های VOB بدون رمزگشایی کپی شده‌اند، بنابراین برخی از تصاویر زیرنویس مخدوش خواهند بود.\n\nبرای نتیجه‌ای تمیز، ابتدا DVD را با یک ابزار استخراج DVD رمزگشایی کنید.\n\nبه هر حال ادامه داده شود؟", "splitTextAt": "تقسیم متن در", "blankLines": "خطوط خالی", "oneLineIsOneSubtitle": "هر خط، یک زیرنویس است", @@ -1256,8 +1340,13 @@ "forcedAlignerProgress": "در حال همگام‌سازی... پنجره {0} از {1}، {2} از {3} خط", "forcedAlignerSetupTitle": "همگام‌سازی کدهای زمانی از طریق هم‌ترازکننده اجباری", "forcedAlignerSetupIntro": "هم‌ترازکننده اجباری، متن زیرنویسی را که از قبل دارید با صدای فایل مطابقت می‌دهد، بدون اینکه ابتدا آن را رونویسی کند. ویدیوهای طولانی به بخش‌های کوچک‌تر تقسیم و همگام‌سازی می‌شوند؛ بنابراین طول ویدیو محدودیتی ایجاد نمی‌کند.", + "forcedAlignerEndsFromSpeech": "تنظیم زمان‌های پایان از گفتار جداشده (کند)", + "forcedAlignerEndsFromSpeechHint": "هم‌ترازکننده اجباری محل شروع یک خط را پیدا می‌کند، نه محل پایان آن را؛ بنابراین زمان‌های پایان معمولاً از زمان خواندن به دست می‌آیند. با فعال بودن این گزینه، ابتدا موسیقی و جلوه‌های صوتی از صدا حذف می‌شوند و هر خط در جایی که گفتارش ساکت می‌شود پایان می‌یابد. با GPU تقریباً به اندازه خود ویدئو طول می‌کشد - و بدون آن چندین برابر بیشتر.", + "forcedAlignerIsolatingSpeech": "در حال جداسازی گفتار... {0}", + "forcedAlignerIsolatingSpeechFailed": "جداسازی گفتار ممکن نشد - زمان‌های پایان به‌جای آن از زمان خواندن تنظیم شدند.", "forcedAlignerModel": "مدل هم‌ترازکننده", - "forcedAlignerDownloadEngine": "دانلود / به‌روزرسانی موتور..." + "forcedAlignerDownloadEngine": "دانلود / به‌روزرسانی موتور...", + "forcedAlignerEstimatedXOfY": "{0} از {1} خط هم‌تراز نشدند و بر اساس زمان خواندن قرار گرفتند - کدهای زمانی آن‌ها فقط تخمینی است. احتمالاً متن از آنچه در ویدئو گفته می‌شود طولانی‌تر است." }, "export": { "exportImagesProfiles": "پروفایل‌های خروجی تصویر", @@ -1306,6 +1395,18 @@ "imageBasedSubtitleSaved": "زیرنویس مبتنی بر تصویر ذخیره شد", "fullFrameImage": "تصویر تمام‌فریم", "fullFrameImageHint": "هر تصویر را به اندازه فریم ویدیو ایجاد کن و زیرنویس را در محل خود قرار بده", + "stereo3D": "3D", + "stereo3DHint": "برای ویدئوی سه‌بعدی: زیرنویس یک بار در هر نیمه مربوط به هر چشم در فریم ترسیم شود", + "halfSideBySide": "نیم کنار‌به‌کنار", + "halfTopBottom": "نیم بالا/پایین", + "depth3D": "عمق", + "depth3DHint": "میزان بیرون‌زدگی زیرنویس از صفحه - مقدار مثبت آن را نزدیک‌تر می‌کند و منفی آن را به عقب می‌برد", + "plane3D": "3D-Plane", + "plane3DHint": "به هر زیرنویس عمقی را بدهید که Blu-ray سه‌بعدی برای آن ذخیره می‌کند، از یک فایل 3D-Plane (.ofs) ساخته‌شده با BD3D2MK3D یا OFSExtractor. در جایی که 3D-Plane مقداری ندارد، از عمق استفاده می‌شود", + "openPlane3DTitle": "باز کردن فایل 3D-Plane", + "unableToLoadPlane3DX": "بارگذاری 3D-Plane ممکن نیست: {0}", + "plane3DXDepthYToZ": "{0} (عمق {1} تا {2})", + "plane3DXNoDepth": "{0} (بدون مقادیر عمق)", "textEffect": "افکت متن", "textEffectSettingsTitle": "تنظیمات افکت متن", "textEffectPreset": "پیش‌تنظیم", @@ -1410,7 +1511,22 @@ "restoreAutoBackupFile": "بازیابی فایل پشتیبان خودکار", "restoreXFromY": "آیا می‌خواهید «{0}» را از {1} بازیابی کنید؟", "deleteAllSubtitleBackups": "آیا می‌خواهید تمام فایل‌های پشتیبان زیرنویس را حذف کنید؟", - "deleteAll": "حذف همه" + "deleteAll": "حذف همه", + "subtitles": "زیرنویس‌ها", + "settingsBackupInfo": "از Settings.json هنگام شروع Subtitle Edit نسخه پشتیبان گرفته می‌شود، حداکثر یک بار در هر {0} روز و فقط در صورتی که تنظیمات تغییر کرده باشند. {1} نسخه پشتیبان جدیدتر نگه داشته می‌شوند.", + "settingsBackupInfoEveryStart": "از Settings.json هر بار که Subtitle Edit شروع می‌شود نسخه پشتیبان گرفته می‌شود، در صورتی که تنظیمات تغییر کرده باشند. {0} نسخه پشتیبان جدیدتر نگه داشته می‌شوند.", + "settingsBackupOff": "پشتیبان‌گیری خودکار از تنظیمات خاموش است (گزینه‌ها ← تنظیمات ← فایل).", + "lastSettingsBackupX": "آخرین نسخه پشتیبان: {0}", + "noSettingsBackupsYet": "هنوز نسخه پشتیبانی از تنظیمات وجود ندارد", + "openSettingsBackupFolder": "باز کردن پوشه پشتیبان تنظیمات", + "backUpSettingsNow": "پشتیبان‌گیری اکنون", + "settingsBackedUp": "از تنظیمات فعلی نسخه پشتیبان گرفته شد.", + "restoreSettings": "بازیابی تنظیمات", + "restoreSettingsFromX": "آیا می‌خواهید تنظیمات فعلی خود را با نسخه پشتیبان مربوط به {0} جایگزین کنید؟\n\nابتدا از تنظیمات فعلی شما نسخه پشتیبان گرفته می‌شود. برخی تغییرات فقط پس از راه‌اندازی مجدد Subtitle Edit اعمال می‌شوند.", + "settingsRestored": "تنظیمات بازیابی شدند. لطفاً برای اعمال همه تغییرات، Subtitle Edit را مجدداً راه‌اندازی کنید.", + "settingsRestoreFailed": "بازیابی تنظیمات ممکن نشد: {0}", + "settingsBackupNotValid": "نسخه پشتیبان یک فایل تنظیمات معتبر نیست، بنابراین تنظیمات فعلی شما حفظ شد", + "deleteAllSettingsBackups": "آیا می‌خواهید همه فایل‌های پشتیبان تنظیمات را حذف کنید؟" }, "propertiesDCinema": { "title": "ویژگی‌های D-Cinema SMPTE", @@ -1424,22 +1540,16 @@ "editRate": "نرخ ویرایش", "timeCodeRate": "نرخ کد زمانی", "startTime": "زمان شروع", - "font": "فونت", "fontId": "شناسه فونت", "fontUri": "آدرس (URI) فونت", - "generate": "تولید", - "fontColor": "رنگ فونت", "chooseColor": "انتخاب رنگ", "fontEffect": "جلوه (افکت) فونت", "effectColor": "رنگ جلوه", - "fontSize": "اندازه فونت", "topBottomMargin": "حاشیه بالا/پایین", "fadeUpTime": "مدت زمان ظاهر شدن تدریجی", "fadeDownTime": "مدت زمان محو شدن تدریجی", - "frames": "فریم‌ها", "zPosition": "موقعیت Z", - "zPositionHelp": "اعداد مثبت متن را دورتر و اعداد منفی آن را نزدیک‌تر می‌برند؛ اگر موقعیت Z صفر باشد، حالت دوبعدی است", - "export": "خروجی گرفتن..." + "zPositionHelp": "اعداد مثبت متن را دورتر و اعداد منفی آن را نزدیک‌تر می‌برند؛ اگر موقعیت Z صفر باشد، حالت دوبعدی است" }, "webVtt": { "setStylesForSelectedLinesTitle": "تنظیم سبک‌های WebVTT برای خطوط انتخاب‌شده", @@ -1477,11 +1587,61 @@ "ignoreFormatting": "نادیده گرفتن قالب‌بندی", "ignoreFormattingHint": "خطوطی که فقط در برچسب‌های قالب‌بندی مانند یا {\\an8} تفاوت دارند، متفاوت به شمار نمی‌آیند", "showOnlyDifferencesInText": "فقط تفاوت‌های متن", + "ignoreNumbering": "نادیده گرفتن شماره‌گذاری", + "ignoreNumberingHint": "خطوطی که فقط در شماره‌شان متفاوت‌اند، متفاوت به حساب نمی‌آیند", + "ignoreTimeDifferenceMs": "تلورانس زمانی (میلی‌ثانیه)", + "ignoreTimeDifferenceMsHint": "زمان‌های شروع و پایانی که به این تعداد میلی‌ثانیه یا کمتر تفاوت دارند، متفاوت به حساب نمی‌آیند", + "compareDifferences": "تفاوت‌ها", + "compareTextDifferences": "تفاوت‌های متنی", + "compareCurrent": "فعلی", + "compareReference": "مرجع", + "compareEditable": "قابل ویرایش", + "compareEditableHint": "تغییراتی که اینجا انجام می‌شوند، با کلیک روی اعمال به زیرنویس بازمی‌گردند", + "compareReadOnly": "فقط‌خواندنی", + "compareTakeFromReference": "گرفتن متن و زمان‌بندی از مرجع", + "compareInsertFromReference": "درج این خط در زیرنویس فعلی", + "compareDeleteFromCurrent": "حذف این خط از زیرنویس فعلی", + "compareTakeText": "گرفتن متن", + "compareTakeTiming": "گرفتن زمان‌بندی", + "compareEditLine": "ویرایش خط", + "compareEditHint": "Ctrl+Enter برای ذخیره، Esc برای لغو", + "compareEdited": "ویرایش‌شده", + "compareOnePendingChange": "۱ تغییر در انتظار", + "compareXPendingChanges": "{0} تغییر در انتظار", + "compareDiscardXChanges": "{0} تغییر انجام‌شده در مقایسه نادیده گرفته شود؟", + "compareXChangesApplied": "مقایسه: {0} تغییر اعمال شد", + "compareChangeTextX": "#{0} متن از مرجع", + "compareChangeTimingX": "#{0} زمان‌بندی از مرجع", + "compareChangeTextAndTimingX": "#{0} متن و زمان‌بندی از مرجع", + "compareChangeEditedX": "#{0} ویرایش شد", + "compareChangeInsertedX": "#{0} درج شد", + "compareChangeDeletedX": "#{0} حذف شد", + "compareSyncPoint": "نقطه همگام‌سازی", + "compareSyncPointHint": "نقطه همگام‌سازی - این دو خط همیشه به‌صورت یک جفت نمایش داده می‌شوند و خطوط بالا و پایین به‌طور جداگانه هم‌تراز می‌شوند", + "compareSyncPickCurrent": "نقطه همگام‌سازی: استفاده از این خط فعلی", + "compareSyncPickReference": "نقطه همگام‌سازی: استفاده از این خط مرجع", + "compareSyncRemove": "حذف نقطه همگام‌سازی", + "compareClearXSyncPoints": "پاک کردن نقاط همگام‌سازی ({0})", + "compareSyncCurrentPickedX": "خط فعلی #{0} به‌عنوان نقطه همگام‌سازی انتخاب شد - خط مرجع منطبق را انتخاب کرده و روی همگام‌سازی کلیک کنید", + "compareSyncReferencePickedX": "خط مرجع #{0} به‌عنوان نقطه همگام‌سازی انتخاب شد - خط فعلی منطبق را انتخاب کرده و روی همگام‌سازی کلیک کنید", + "compareSyncWithCurrentX": "همگام‌سازی با خط فعلی #{0}", + "compareSyncWithReferenceX": "همگام‌سازی با خط مرجع #{0}", + "compareSyncApply": "همگام‌سازی", + "compareSyncPointSetXY": "نقطه همگام‌سازی تنظیم شد: خط فعلی #{0} و خط مرجع #{1} اکنون به‌صورت یک جفت نمایش داده می‌شوند", + "compareSyncAlreadyPairedXY": "خط فعلی #{0} و خط مرجع #{1} از قبل با شروع یکسان به‌صورت یک جفت نمایش داده می‌شوند - چیزی برای همگام‌سازی وجود ندارد", + "compareSyncPointShiftedXYZW": "نقطه همگام‌سازی تنظیم شد: خطوط فعلی #{0}-#{1} به اندازه {2} جابجا شدند تا با خط مرجع #{3} شروع شوند", + "compareChangeSyncShiftXYZ": "#{0}-#{1} به اندازه {2} جابجا شد (نقطه همگام‌سازی)", + "compareTimingDeltaHint": "اینکه شروع و پایان مرجع چقدر دیرتر (+) یا زودتر (−) از خط فعلی است", "loadXFromFile": "بارگذاری «{0}» از فایل", + "compareLoadSavedFile": "بارگذاری فایل ذخیره‌شده", "saveCompareHtmlTitle": "ذخیره فایل HTML مقایسه", "pickMatroskaTrackX": "انتخاب ترک Matroska - {0}", "pickTransportStreamTrackX": "انتخاب ترک جریان انتقال - {0}", + "chapters": "فصل‌ها", + "dvdVobFilesMissingX": "فایل‌های VOB ناقص هستند ({0}٪ یافت شد)", "pickMp4TrackX": "انتخاب ترک MP4 - {0}", + "pickMxfTrackX": "انتخاب ترک MXF - {0}", + "pickMpegTrackX": "انتخاب ترک MPEG - {0}", "rosettaProperties": "ویژگی‌های Timed Text Rosetta IMSC", "rosettaFontSize": "اندازه فونت (ارتفاع ردیف)", "propertyTimeBase": "مبنای زمانی", @@ -1491,7 +1651,6 @@ "propertyDefaultRegion": "ناحیه پیش‌فرض", "propertyStyleAttributeName": "نام ویژگی سبک", "propertyTimeCodeFormat": "فرمت کد زمانی", - "propertyFileExtension": "پسوند فایل", "propertyTopOrigin": "مبدأ بالا", "propertyTopExtent": "گستره بالا", "propertyBottomOrigin": "مبدأ پایین", @@ -1557,24 +1716,23 @@ "findWhat": "عبارت مورد جستجو", "descriptionOptional": "توضیحات (اختیاری)", "invalidRegularExpressionX": "عبارت باقاعده نامعتبر: {0}", - "regularExpressionTooSlowX": "عبارت باقاعده پس از {0} ثانیه متوقف شد؛ قانون نادیده گرفته می‌شود. الگوی ساده‌تری را امتحان کنید" + "regularExpressionTooSlowX": "عبارت باقاعده پس از {0} ثانیه متوقف شد؛ قانون نادیده گرفته می‌شود. الگوی ساده‌تری را امتحان کنید", + "moveToCategory": "انتقال به دسته‌بندی" }, "find": { "searchTextWatermark": "جستجوی متن...", "wholeWord": "کل کلمه", - "caseSensitive": "حساس به حروف بزرگ و کوچک", - "caseInsensitive": "غیرحساس به حروف بزرگ و کوچک", "findPrevious": "یافتن قبلی", "findNext": "یافتن بعدی", "replaceAndFindNext": "جایگزینی و یافتن بعدی", "replaceAll": "جایگزینی همه", - "replaceTitle": "جایگزینی", - "replaceWith": "جایگزینی با", "replaceTextWatermark": "جایگزینی با...", "replaceIn": "جایگزینی/جستجو در", "replaceInTextAndOriginal": "متن و متن اصلی", "replaceInTextOnly": "فقط متن", - "replaceInOriginalOnly": "فقط متن اصلی" + "replaceInOriginalOnly": "فقط متن اصلی", + "replacedOneOccurrence": "یک مورد جایگزین شد", + "replacedXOccurrences": "{0} مورد جایگزین شد" }, "regularExpressionContextMenu": { "wordBoundary": "مرز کلمه (\\b)", @@ -1602,6 +1760,7 @@ "xLinesYSubtitles": "{0} خط، {1} زیرنویس", "xLinesYSubtitlesZErrors": "{0} خط، {1} زیرنویس، {2} خطا", "couldNotParseAsX": "تجزیه به‌عنوان {0} ممکن نبود", + "readAsXUseAsY": "منبع را نمی‌توان به‌صورت {1} خواند، اما می‌توان آن را به‌صورت {0} خواند.\nاز این خطوط استفاده شود و ذخیره‌سازی به‌صورت {1} ادامه یابد؟", "selectedXCharactersYLines": "انتخاب‌شده: {0} کاراکتر در {1} خط", "replacedXOccurrences": "تعداد {0} مورد جایگزین شد", "invalidRegularExpression": "عبارت با قاعده نامعتبر", @@ -1637,7 +1796,23 @@ "largeChangeWarning": "تغییر بزرگ - به نظر می‌رسد متن بازنویسی شده است؛ با دقت بررسی کنید", "mismatchWarning": "شباهتی به متن اصلی ندارد - ممکن است متعلق به خط دیگری باشد", "engineError": "دسترسی به موتور هوش مصنوعی امکان‌پذیر نبود: {0}", - "playCurrentLineHint": "خط انتخاب‌شده را در پخش‌کننده ویدیو پخش کنید و در انتهای آن مکث کنید (همچنین می‌توانید روی یک خط دوبار کلیک کنید)" + "playCurrentLineHint": "خط انتخاب‌شده را در پخش‌کننده ویدیو پخش کنید و در انتهای آن مکث کنید (همچنین می‌توانید روی یک خط دوبار کلیک کنید)", + "editAfterHint": "برای ویرایش اصلاحیه پیش از اعمال، روی متن ردیف انتخاب‌شده کلیک کنید (Enter ذخیره می‌کند، Escape لغو می‌کند)", + "template": "الگو", + "templatePlaceholder": "یک الگو برای جایگزینی پرامپت انتخاب کنید", + "templateStandard": "ویرایش و بازخوانی استاندارد", + "templateSpeechToText": "پاک‌سازی تبدیل گفتار به متن", + "templateMachineTranslation": "اصلاح ترجمه ماشینی", + "templateSdh": "SDH / ناشنوایان", + "referenceContext": "زمینه مرجع", + "referenceContextInfo": "اختیاری. نام‌ها، شخصیت‌ها، واژه‌نامه، خلاصه یا نکات سبک نگارش - مدل این را به‌عنوان مرجع معتبر برای نام‌ها و اصطلاحات در نظر می‌گیرد. همراه با هر دسته ارسال می‌شود.", + "referenceContextPlaceholder": "نام‌ها: کایتو، خاله مِی، دکتر هالیس\nاصطلاحات: شینکانسن، هسته وارپ\nخلاصه: کایتو برای یافتن پدر گمشده‌اش به توکیو سفر می‌کند.", + "generateContext": "تولید با هوش مصنوعی", + "generateContextHint": "اجازه دهید موتور هوش مصنوعی انتخاب‌شده زیرنویس را بخواند و پیش‌نویسی از نام‌ها، اصطلاحات و خلاصه تهیه کند - پیش از استفاده نتیجه را بررسی کنید", + "generatingContextPartXOfY": "در حال خواندن بخش {0} از {1}...", + "generatingContextSynopsis": "در حال نوشتن خلاصه...", + "replaceContextQuestion": "زمینه مرجع فعلی جایگزین شود؟", + "noContextGenerated": "موتور هوش مصنوعی هیچ اطلاعات مرجع قابل استفاده‌ای برنگرداند." }, "aiAssistant": { "title": "دستیار هوش مصنوعی", @@ -1683,6 +1858,7 @@ "removeLineBreaksPixelWidth": "تبدیل زیرنویس‌های قابل نمایش در یک خط به یک خط (بر اساس عرض پیکسلی)", "fixUppercaseIInsideLowercaseWords": "اصلاح «i» بزرگ در میان کلمات با حروف کوچک (خطای OCR)", "fixDoubleApostrophes": "تبدیل دو نویسه نقل‌قول تکی ('') به یک نقل‌قول (\")", + "fixMisreadQuotes": "اصلاح آپاستروف‌هایی که به‌اشتباه نقل‌قول دوتایی خوانده شده‌اند (OCR، انگلیسی)", "addPeriods": "افزودن نقطه در انتهای خطوطی که خط بعدی با حرف بزرگ شروع می‌شود", "startWithUppercaseLetterAfterParagraph": "شروع با حرف بزرگ پس از پاراگراف", "startWithUppercaseLetterAfterPeriodInsideParagraph": "شروع با حرف بزرگ پس از نقطه درون پاراگراف", @@ -1705,6 +1881,7 @@ "fixCommasExample": ",, -> ,", "removeLineBreaksExample": "Foo
bar! -> Foo bar!", "fixDoubleApostrophesExample": "''Has double single quotes'' -> \"Has single double quote\"", + "fixMisreadQuotesExample": "\"Hello' -> \"Hello\"", "addPeriodsExample": "Hello world -> Hello world.", "startWithUppercaseLetterAfterParagraphExample": "p1: Foobar! || p2: foobar! -> p1: Foobar! || p2: Foobar!", "startWithUppercaseLetterAfterPeriodInsideParagraphExample": "Hello there! how are you? -> Hello there! How are you?", @@ -1784,7 +1961,6 @@ "adjustVia": "تنظیم از طریق", "recalculate": "محاسبه مجدد", "addSeconds": "افزودن ثانیه", - "setAsPercent": "تنظیم به عنوان درصدی از مدت زمان", "note": "نکته: زمان نمایش با زمان شروع متن بعدی تداخل نخواهد داشت", "fixed": "ثابت", "extendOnly": "فقط افزایش طول", @@ -1795,6 +1971,8 @@ "fixMinDurationMs": "اصلاح حداقل مدت‌زمان (میلی‌ثانیه)", "doNotGoPastShotChange": "از تغییر نما عبور نکن", "fixMaxDurationMs": "اصلاح حداکثر مدت‌زمان (میلی‌ثانیه)", + "fixMinDurationFrames": "اصلاح حداقل مدت زمان (فریم)", + "fixMaxDurationFrames": "اصلاح حداکثر مدت زمان (فریم)", "minimumDurationMilliseconds": "حداقل مدت‌زمان (میلی‌ثانیه):", "maximumDurationMilliseconds": "حداکثر مدت‌زمان (میلی‌ثانیه):", "maxDurationShouldBeHigherThanMinDuration": "حداکثر مدت‌زمان باید بیشتر از حداقل مدت‌زمان باشد", @@ -1844,13 +2022,80 @@ "timeCodesExtractFailed": "خواندن کدهای زمانی از ویدیو امکان‌پذیر نبود", "timeCodesHint": "زیرنویس‌ها را با زمان واقعی فریم‌های ویدیو هم‌تراز می‌کند، به‌جای اینکه نرخ فریم ثابت فرض شود. برای این کار باید کل ویدیو یک بار خوانده شود؛ نتیجه ذخیره و از آن استفاده می‌شود." }, + "improveTimeCodes": { + "title": "بهبود کدهای زمانی (هم‌ترازی اجباری)", + "aligner": "هم‌ترازکننده", + "alignerHint": "مدلی که به هر خط در صدا گوش می‌دهد. بهترین گزینه‌ها برای زبان زیرنویس در ابتدا فهرست شده‌اند.", + "maxShift": "حداکثر جابجایی (ثانیه)", + "maxShiftHint": "میزانی که زیرنویس ممکن است ناهمگام باشد. خطی که شروعش بیش از این جابجا شود، همان‌طور که هست باقی می‌ماند و علامت‌گذاری می‌شود. صرف‌نظر از این مقدار، خطی که بخواهد بیش از نیم ثانیه بیشتر از خطوط اطرافش جابجا شود، کورکورانه جابجا نمی‌شود: اگر همه خطوط مجاورش جابجا شده باشند از آن‌ها پیروی می‌کند، و در غیر این صورت بدون تیک برای بررسی شما ارائه می‌شود.", + "adjustStartTimes": "تنظیم زمان‌های شروع", + "adjustEndTimes": "تنظیم زمان‌های پایان", + "isolateSpeech": "ابتدا جداسازی گفتار (کند)", + "isolateSpeechHint": "پیش از هم‌ترازی، موسیقی و جلوه‌های صوتی را حذف می‌کند تا هم‌ترازکننده فقط دیالوگ را بشنود. برای خطوطی که روی موسیقی بلند یا صحنه‌های اکشن گفته می‌شوند مفید است، اما با GPU تقریباً به اندازه خود صدا طول می‌کشد - و بدون آن چندین برابر بیشتر.", + "isolatingSpeech": "در حال جداسازی گفتار... (این کار مدتی طول می‌کشد)", + "isolateSpeechFailed": "جداسازی گفتار ممکن نشد - به‌جای آن با صدای اصلی هم‌تراز شد.", + "checkWithSpeechToText": "بررسی با تبدیل گفتار به متن", + "checkWithSpeechToTextHint": "صدا را نیز رونویسی می‌کند و هر جابجایی را با محلی که کلمات واقعاً شنیده شده‌اند مقایسه می‌کند. جابجایی‌های بزرگی که گفتار تأیید می‌کند برای شما تیک می‌خورند، و جابجایی‌هایی که از محل شنیده شدن خط دور می‌شوند بدون تیک باقی می‌مانند تا آن‌ها را بررسی کنید. همچنین نشان می‌دهد چه مقدار از هر خط شنیده شده است - سهم کم یعنی متن با آنچه گفته می‌شود متفاوت است. زیرنویسی که بیش از حداکثر جابجایی ناهمگام باشد - یک اختلاف ثابت، رانش ناشی از عدم تطابق نرخ فریم، یا پرش در جاهایی که صحنه‌ها بریده شده‌اند - ابتدا بر اساس کلمات شنیده‌شده همگام‌سازی می‌شود. از Crisp ASR Parakeet استفاده می‌کند که برای یک فیلم بلند با GPU چند دقیقه طول می‌کشد.", + "checkWithSpeechToTextNotAvailableX": "بررسی با تبدیل گفتار به متن برای {0} در دسترس نیست", + "transcribingToCheck": "در حال رونویسی برای بررسی هم‌ترازی...", + "speechToTextFailed": "تبدیل گفتار به متن ناموفق بود - کدهای زمانی با آن بررسی نشدند.", + "speechToTextX": "تبدیل گفتار به متن: {0}", + "speechToTextConfirmedXDisputedY": "گفتار: {0} تأییدشده، {1} مورد اختلاف", + "hardlyHeardX": "تبدیل گفتار به متن فقط {0}٪ از کلمات زیرنویس را شنید. آیا زیرنویس به زبانی است که صحبت می‌شود؟ هم‌ترازکننده نمی‌تواند یک ترجمه را جای‌گذاری کند.", + "heard": "شنیده‌شده", + "heardHint": "اینکه تبدیل گفتار به متن چه مقدار از خط را شنیده است. سهم کم یعنی متن با آنچه گفته می‌شود متفاوت است - جایی که احتمال اشتباه هم‌ترازکننده بیشتر است.", + "statusConfirmedBySpeech": "تأییدشده توسط گفتار", + "statusDisputedBySpeech": "در محل قبلی شنیده شد", + "statusMovedWithSync": "همراه با همگام‌سازی جابجا شد", + "syncedFirstXY": "ابتدا همگام‌سازی شد: {0} ثانیه در ابتدا، {1} ثانیه در انتها", + "showSpeechOnly": "نمایش فقط گفتار", + "showSpeechOnlyHint": "شکل موج هم‌ترازشده را از صدایی که موسیقی و جلوه‌های صوتی آن حذف شده ترسیم کنید؛ شکل موج اصلی برای مقایسه همان‌طور باقی می‌ماند. پس از جداسازی گفتار در دسترس است - اینجا، یا با \"نمایش فقط گفتار\" در شکل موج پنجره اصلی.", + "align": "هم‌ترازی", + "original": "اصلی", + "aligned": "هم‌ترازشده", + "intro": "یک هم‌ترازکننده انتخاب کنید و \"هم‌ترازی\" را فشار دهید تا هر خط در صدا شنیده شده و کدهای زمانی آن دقیق‌تر شوند.", + "extractingAudio": "در حال استخراج صدا...", + "aligningBatchXOfY": "در حال هم‌ترازی... دسته {0} از {1}", + "extractAudioFailed": "استخراج صدا با ffmpeg ممکن نشد.", + "modelWillBeDownloaded": "{0} دانلود خواهد شد", + "summaryXRetimedYKeptZSkipped": "زمان‌بندی مجدد: {0} · حفظ‌شده: {1} · بدون گفتار: {2}", + "meanShiftX": "میانگین جابجایی: {0} میلی‌ثانیه", + "toCheckX": "برای بررسی: {0}", + "largeMovesToCheckX": "{0} خط به‌تنهایی جابجایی زیادی خواهند داشت و بدون تیک باقی مانده‌اند - به آن‌ها گوش دهید (F5 خط هم‌ترازشده و Shift+F5 خط اصلی را پخش می‌کند) و مواردی را که درست هستند تیک بزنید.", + "linesToCheckX": "{0} خط برای بررسی بدون تیک باقی مانده‌اند - به آن‌ها گوش دهید (F5 خط هم‌ترازشده و Shift+F5 خط اصلی را پخش می‌کند) و مواردی را که درست هستند تیک بزنید.", + "changeXOfY": "تغییر {0} از {1}", + "previousChange": "تغییر قبلی", + "nextChange": "تغییر بعدی", + "startShift": "شروع", + "endShift": "پایان", + "statusRetimed": "زمان‌بندی مجدد شد", + "statusMovedWithNeighbours": "همراه با خطوط مجاور جابجا شد", + "statusLargeMoveUnconfirmed": "جابجایی بزرگ - گوش دهید، برای اعمال تیک بزنید", + "statusUnchanged": "از قبل در جای خود", + "statusNoSpeech": "بدون گفتار", + "statusShiftTooLarge": "حفظ شد - جابجایی بیش از حد بزرگ", + "statusFailed": "حفظ شد - هم‌ترازکننده ناموفق بود", + "statusAdjustedByHand": "تنظیم دستی", + "alignedWaveformHint": "برای جابجایی یک خط آن را بکشید، یا لبه‌های آن را برای تغییر شروع یا پایان بکشید.", + "playLine": "پخش خط", + "undoAdjustment": "لغو تنظیم", + "playPause": "پخش / توقف موقت", + "playAligned": "هم‌ترازشده", + "playAlignedHint": "پخش خط انتخاب‌شده با کدهای زمانی هم‌ترازشده آن", + "apply": "اعمال", + "applyHint": "تیک یک خط را بردارید تا کدهای زمانی اصلی آن حفظ شود. برای پخش یک خط روی آن دوبار کلیک کنید. خطوط را می‌توان در شکل موج هم‌ترازشده نیز جابجا یا تغییر اندازه داد." + }, "beautifyTimeCodesProfile": { "title": "ویرایش پروفایل زیباسازی کدهای زمانی", "loadPreset": "بارگذاری پیش‌تنظیم", "presetDefault": "پیش‌فرض", "presetNetflix": "نتفلیکس", "presetSdi": "SDI", - "general": "عمومی", + "saveAsProfile": "ذخیره به‌عنوان پروفایل...", + "deleteProfile": "حذف پروفایل", + "profileName": "نام پروفایل", + "overwriteProfileX": "پروفایلی با نام \"{0}\" از قبل وجود دارد. بازنویسی شود؟", + "deleteProfileX": "پروفایل \"{0}\" حذف شود؟", "gap": "فاصله", "gapSuffix": "فریم (همچنین فیلدهای فاصله غیرصفر زیر را بروز می‌کند)", "milliseconds": "میلی‌ثانیه", @@ -1881,6 +2126,10 @@ "bridgeGaps": { "bridgeGapsSmallerThan": "پل زدن فاصله‌های کمتر از (میلی‌ثانیه)", "minGap": "حداقل فاصله (میلی‌ثانیه)", + "bridgeGapsSmallerThanFrames": "پل زدن فاصله‌های کمتر از (فریم)", + "minGapFrames": "حداقل فاصله (فریم)", + "calculateBridgeGapsSmallerThanDotDotDot": "محاسبه حد پل زدن فاصله از روی نرخ فریم...", + "useXMsAsBridgeGapsSmallerThan": "پل زدن فاصله‌های کمتر از \"{0}\" میلی‌ثانیه؟", "numberOfSmallGapsBridgedX": "تعداد فاصله‌های کوچک پل‌زده‌شده: {0}", "percentFoPrevious": "فاصله برای قبلی (%)", "gapChange": "تغییر فاصله" @@ -1914,6 +2163,7 @@ "addColor": "افزودن رنگ", "deleteLinesWithSpecificActorsOrStyles": "حذف خطوط دارای بازیگران یا سبک‌های مشخص (چند مورد را با ویرگول جدا کنید)", "useSourceStylesIfPossible": "در صورت امکان از سبک‌های منبع استفاده کن", + "keepSourceEmbeddedFonts": "حفظ فونت‌های جاسازی‌شده منبع (پانویس)", "editStyles": "ویرایش سبک‌ها", "editProperties": "ویرایش ویژگی‌ها", "editAttachments": "ویرایش پیوست‌ها", @@ -1945,13 +2195,52 @@ "convertColorsToDialogReBreakLines": "شکستن مجدد خطوط", "snapTimeCodesToFramesInfo": "زمان شروع و پایان هر زیرنویس را به نزدیک‌ترین فریم گرد می‌کند. نرخ فریم از فایل ویدیویی هم‌نام با فایل زیرنویس خوانده می‌شود، مگر اینکه نرخ فریم ثابت انتخاب شده باشد.", "addFolderDotDotDot": "افزودن پوشه...", + "addFolderRecursiveDotDotDot": "افزودن پوشه به‌صورت بازگشتی...", "selectFolderToConvert": "انتخاب پوشه حاوی فایل‌ها برای تبدیل", "includeSubfolders": "هنگام افزودن پوشه، زیرپوشه‌ها را نیز شامل کن", + "videoFilesWhenAddingFolder": "فایل‌های ویدئویی (mkv، mp4، ts...) هنگام افزودن پوشه", + "videoFilesAsk": "پرسیدن", + "videoFilesInclude": "افزودن ترک‌های زیرنویس آن‌ها", + "videoFilesSkip": "رد کردن", + "folderContainsXVideoFiles": "این پوشه شامل {0} فایل ویدئویی (mkv، mp4، ts...) است.\n\nترک‌های زیرنویس جاسازی‌شده آن‌ها نیز اضافه شوند، یا فقط فایل‌های زیرنویس؟", + "addVideoFiles": "افزودن فایل‌های ویدئویی", + "skipVideoFiles": "فقط فایل‌های زیرنویس", + "doNotAskAgainVideoFiles": "دوباره نپرس (قابل تغییر در تنظیمات تبدیل دسته‌ای)", + "settingsSectionOutput": "خروجی", + "settingsSectionAddingFiles": "افزودن فایل‌ها", + "settingsSectionImageBasedSubtitles": "زیرنویس‌های مبتنی بر تصویر (OCR)", + "settingsSectionOther": "سایر", "keepSourceFileTimestamp": "تاریخ/زمان فایل منبع را در فایل‌های خروجی حفظ کن", - "scanningFolderX": "در حال بررسی {0}..." + "preventSleepWhileConverting": "جلوگیری از به خواب رفتن رایانه هنگام تبدیل", + "scanningFolderX": "در حال بررسی {0}...", + "transportStreamSettings": "تنظیمات Transport Stream", + "transportStreamSettingsDotDotDot": "تنظیمات Transport Stream...", + "transportStreamSettingsInfo": "روی ترک‌های زیرنویس استخراج‌شده از فایل‌های Transport Stream (.ts/.m2ts) اعمال می‌شود. موقعیت و اندازه ویدئو فقط روی ترک‌های تصویری DVB که به یک قالب مبتنی بر تصویر صادر می‌شوند تأثیر دارند.", + "transportStreamOverrideXPosition": "جایگزینی موقعیت X اصلی", + "transportStreamOverrideYPosition": "جایگزینی موقعیت Y اصلی", + "transportStreamOverrideVideoSize": "جایگزینی اندازه ویدئوی اصلی", + "transportStreamBottomMargin": "حاشیه پایین", + "transportStreamFileNameEnding": "پایان نام فایل", + "transportStreamFileNameEndingInfo": "برای هر ترک استخراج‌شده پیش از پسوند اضافه می‌شود. برای استفاده از پسوند زبان معمول، خالی بگذارید.", + "transportStreamOnlyTeletext": "فقط تله‌تکست", + "transportStreamGetSizeFromVideo": "دریافت اندازه از ویدئو...", + "twoLetterLanguageCodeUppercase": "کد زبان دوحرفی (حروف بزرگ)", + "threeLetterLanguageCodeUppercase": "کد زبان سه‌حرفی (حروف بزرگ)", + "formatContainsDotDotDot": "قالب شامل...", + "extensionIsDotDotDot": "پسوند برابر با...", + "statusIsError": "وضعیت خطا است", + "forcedTracksOnly": "فقط ترک‌های اجباری", + "xConvertedYFailedInZ": "{0:#,###,##0} تبدیل شد، {1:#,###,##0} ناموفق در {2}", + "xNotProcessed": "{0:#,###,##0} پردازش نشد", + "removeUnicodeControlCharactersTitle": "حذف کاراکترهای کنترلی یونیکد", + "removeUnicodeControlCharactersInfo": "کاراکترهای نامرئی کنترل جهت یونیکد (LRM، RLM، LRE، RLE، PDF، LRO و RLO) را از متن حذف می‌کند و فاصله‌های بدون شکست را با فاصله‌های معمولی جایگزین می‌کند.", + "preset": "پیش‌تنظیم", + "savePresetDotDotDot": "ذخیره پیش‌تنظیم...", + "deletePreset": "حذف پیش‌تنظیم", + "presetName": "نام پیش‌تنظیم", + "deletePresetX": "پیش‌تنظیم \"{0}\" حذف شود؟" }, "changeCasing": { - "title": "تغییر وضعیت حروف (بزرگی/کوچکی)", "fixNames": "اصلاح نام‌ها", "namesX": "نام‌ها: {0:#,##0}", "hitsX": "موارد یافت‌شده: {0:#,##0}", @@ -2034,10 +2323,13 @@ }, "mergeLinesWithSameText": { "maxMsBetweenLines": "حداکثر میلی‌ثانیه بین خطوط", - "includeIncrementingLines": "شامل شدن خطوط با متن افزایشی" + "maxFramesBetweenLines": "حداکثر فریم بین خطوط", + "includeIncrementingLines": "شامل شدن خطوط با متن افزایشی", + "includeRollUpCaptions": "شامل زیرنویس‌های رول‌آپ (پیمایشی)" }, "mergeLinesWithSameTimeCodes": { "maxMsDifference": "حداکثر تفاوت (میلی‌ثانیه)", + "maxFramesDifference": "حداکثر تفاوت (فریم)", "makeDialog": "تبدیل به دیالوگ" }, "netflixCheckAndFix": { @@ -2060,7 +2352,6 @@ "shotChanges": "تغییرات نما", "startNumberSpellOut": "حروف‌نویسی عدد در شروع خط", "textforHiUseBrackets": "متن مخصوص کم‌شنوایان (HI)، استفاده از کروشه", - "frameRate": "نرخ فریم", "twoFrameGrap": "فاصله دو فریم", "whiteSpace": "فضای خالی", "childrensProgram": "برنامه کودک", @@ -2098,7 +2389,6 @@ "italicsFixed": "ایتالیک اصلاح شد", "italicsNotAllowed": "ایتالیک مجاز نیست", "bridgeGapsXToYFrames": "فاصله 3 تا {0} فریم => تبدیل به فاصله 2 فریم", - "startNumberSpellOutComment": "هنگامی که یک جمله با عدد شروع می‌شود، عدد باید همیشه به صورت حروفی نوشته شود", "maximumXCharactersPerSecond": "حداکثر {0} کاراکتر در ثانیه" }, "imageBasedEdit": { @@ -2107,10 +2397,8 @@ "resizeImagesDotDotDot": "تغییر اندازه تصاویر...", "adjustBrightnessDotDotDot": "تنظیم روشنایی...", "adjustAlphaDotDotDot": "تنظیم کانال آلفا (شفافیت)...", - "centerHorizontally": "وسط‌چین کردن افقی", "cropImages": "برش تصاویر (Crop)", "importTimeCodes": "وارد کردن کدهای زمانی...", - "setTextForSubtitle": "تنظیم متن برای زیرنویس", "setText": "تنظیم متن", "screenWidth": "عرض صفحه", "screenHeight": "ارتفاع صفحه", @@ -2141,12 +2429,29 @@ "letterboxCustom": "ارتفاع نوار سفارشی", "barHeightPx": "ارتفاع نوار (پیکسل)", "titleSafePercent": "محدوده امن عنوان (%)", + "moveCaptions": "جابجایی زیرنویس‌ها", + "moveCaptionsDotDotDot": "جابجایی زیرنویس‌ها...", + "moveCaptionsTo": "جابجایی زیرنویس‌ها", + "moveCaptionsIntoBars": "به داخل نوارهای لترباکس (خارج از تصویر)", + "moveCaptionsIntoPicture": "به داخل تصویر (خارج از نوارها)", + "moveCaptionsOffset": "فاصله از لبه (px)", + "letterboxBarHeightXPxYOfZCaptionsWillMove": "ارتفاع نوار: {0} px - {1} از {2} زیرنویس جابجا خواهند شد", + "removeFadeInOut": "حذف ظهور/محو شدن تدریجی", + "removeFadeInOutXLinesRemoved": "{0} خط محو شدن با خطوطی که به آن‌ها تعلق دارند ادغام شد", + "removeFadeInOutNothingFound": "هیچ خط ظهور/محو شدن تدریجی یافت نشد", "positionSummary": "{0}×{1} - {2} زیرنویس - ارتفاع نوار: {3} پیکسل", "xInActivePicture": "{0} در تصویر اصلی", "xInTopBar": "{0} در نوار بالا", "xInBottomBar": "{0} در نوار پایین", "noImageSubtitlesLoaded": "هیچ زیرنویس تصویری بارگذاری نشده است", - "imageBasedFormatNotSupported": "فرمت زیرنویس مبتنی بر تصویر یافت نشد/پشتیبانی نمی‌شود." + "imageBasedFormatNotSupported": "فرمت زیرنویس مبتنی بر تصویر یافت نشد/پشتیبانی نمی‌شود.", + "frameRateDeclaredInFile": "نرخ فریم اعلام‌شده در فایل sup بلوری", + "frameRateDetectedFromTimeCodesX": "نرخ فریم تشخیص داده‌شده از کدهای زمانی sup بلوری (فایل {0} را اعلام می‌کند)", + "frameRateFromVideo": "نرخ فریم ویدئو", + "frameRateCurrent": "نرخ فریم فعلی", + "frameRateSetManually": "نرخ فریم تنظیم‌شده به‌صورت دستی", + "frameRateUsageInfo": "خروجی‌های Blu-ray sup، BDN XML، DOST و FCP با این نرخ فریم نوشته می‌شوند. این گزینه هیچ زمانی را تغییر نمی‌دهد - برای آن از \"تغییر نرخ فریم\" استفاده کنید.", + "useVideoFrameRateXInsteadOfY": "نرخ فریم ویدئو {0} است، اما زیرنویس روی {1} تنظیم شده است.\n\nاز نرخ فریم ویدئو ({0}) استفاده شود؟" }, "removeTextForHearingImpaired": { "interjections": "اصوات نواخت‌ها و میان‌افکندها", @@ -2176,6 +2481,7 @@ "pickFontNameInstalledFonts": "فونت های نصب شده", "pickFontNameCollectedFonts": "فونت‌های جمع‌آوری‌شده", "colorPickerTitle": "انتخاب رنگ", + "colorPickerBrightness": "روشنایی", "filterLayersTitle": "فیلتر کردن لایه‌ها برای نمایش", "filterLayersHideFromWaveform": "پنهان کردن از شکل موج/اسپکتروگرام", "filterLayersHideFromSubtitleGrid": "پنهان کردن از جدول زیرنویس", @@ -2226,7 +2532,21 @@ "correctWordsX": "کلمات صحیح: {0}", "namesX": "نام‌ها: {0}", "addedToDictionaryX": "اضافه شده به دیکشنری: {0}", - "doNotShowThisAgain": "این پیام دوباره نشان داده نشود" + "doNotShowThisAgain": "این پیام دوباره نشان داده نشود", + "editUseAlwaysList": "ویرایش فهرست \"همیشه استفاده شود\"...", + "useAlwaysListTitle": "بررسی املایی - فهرست \"همیشه استفاده شود\"", + "useAlwaysListHeader": "فهرست \"همیشه استفاده شود\"", + "useAlwaysListDescription": "کلماتی که بررسی املایی به‌طور خودکار جایگزین می‌کند و با \"تغییر همه\" و \"همیشه استفاده شود\" ذخیره شده‌اند. یک جفت فقط زمانی استفاده می‌شود که کلمه غلط املایی داشته باشد.", + "useAlwaysWord": "کلمه غلط املایی", + "xPairs": "{0:#,##0} جفت", + "xUnusedPairs": "{0:#,##0} هرگز استفاده نشده", + "useAlwaysUnusedHint": "هرگز استفاده نشده - این کلمه درست نوشته شده است (یا یک نام یا کلمه کاربر است)، بنابراین بررسی املایی هرگز آن را علامت‌گذاری نمی‌کند", + "removeUnused": "حذف موارد استفاده‌نشده", + "useAlwaysEmpty": "هنوز هیچ جفت \"همیشه استفاده شود\" برای این زبان وجود ندارد", + "useAlwaysEmptyHint": "جفت‌ها زمانی اضافه می‌شوند که در بررسی املایی روی \"تغییر همه\" یا \"همیشه استفاده شود\" کلیک کنید.", + "useAlwaysNoMatch": "هیچ جفتی با جستجو مطابقت ندارد", + "useAlwaysSameWord": "جایگزین باید با کلمه متفاوت باشد.", + "useAlwaysRememberOff": "\"به‌خاطر سپردن فهرست 'همیشه استفاده شود'\" در تنظیمات خاموش است، بنابراین بررسی املایی از این فهرست استفاده نمی‌کند و آن را ذخیره نمی‌کند." }, "video": { "burnIn": { @@ -2239,6 +2559,7 @@ "sampleRate": "نرخ نمونه‌برداری", "audio": "صدا", "preset": "پیش‌تنظیم", + "tune": "تنظیم دقیق (Tune)", "pixelFormat": "فرمت پیکسل", "crf": "CRF", "useSource": "استفاده از منبع", @@ -2250,7 +2571,9 @@ "outputProperties": "ویژگی‌های خروجی...", "videoFileSize": "حجم فایل ویدیو", "oneBox": "یک کادر", - "logoInfo": "یک تصویر PNG انتخاب کنید و آن را برای تعیین موقعیت روی ویدیو بکشید (Drag)." + "logoInfo": "یک تصویر PNG انتخاب کنید و آن را برای تعیین موقعیت روی ویدیو بکشید (Drag).", + "noSubtitlesGenerateAnyway": "هیچ خط زیرنویسی برای چسباندن وجود ندارد.\n\nویدئو بدون زیرنویس تولید شود؟", + "noSubtitlesInXItemsGenerateAnyway": "{0} مورد دسته‌ای هیچ خط زیرنویسی برای چسباندن ندارند.\n\nآن ویدئوها بدون زیرنویس تولید شوند؟" }, "videoTransparent": { "title": "تولید ویدیوی شفاف همراه با زیرنویس" @@ -2261,6 +2584,21 @@ "translateToEnglish": "ترجمه به انگلیسی", "transcribing": "در حال پیاده‌سازی متن...", "transcribingXOfY": "در حال پیاده‌سازی {0} از {1}...", + "linesTranscribedXOfYFailedZ": "{0} از {1} خط رونویسی شدند - {2} مورد ناموفق بودند یا گفتاری نداشتند.\n\nخطوط ناموفق متن فعلی خود را حفظ می‌کنند. خطوط رونویسی‌شده اعمال شوند، یا خطوط ناموفق دوباره امتحان شوند (می‌توانید ابتدا موتور یا تنظیمات را تغییر دهید)؟", + "linesTranscribedXOfYCancelled": "رونویسی پس از {0} از {1} خط لغو شد.\n\nخطوط رونویسی‌شده اعمال شوند؟ سایر خطوط متن فعلی خود را حفظ می‌کنند.", + "noLinesTranscribed": "هیچ‌یک از {0} خط رونویسی نشد.\nلطفاً برای جزئیات، فایل گزارش ابزارها را بررسی کنید.", + "applyTranscribedLines": "اعمال رونویسی‌شده‌ها", + "retryFailedLines": "تلاش مجدد ناموفق‌ها", + "isolateSpeech": "جداسازی گفتار (کند)", + "isolateSpeechHint": "پیش از رونویسی، موسیقی و جلوه‌های صوتی را حذف می‌کند. خطوط از دست رفته کمتر و زمان‌بندی بهتری در صداهای دارای موسیقی بلند به دست می‌دهد، اما با GPU تقریباً به اندازه خود صدا طول می‌کشد - و بدون آن چندین برابر بیشتر.", + "isolatingSpeech": "در حال جداسازی گفتار...", + "isolateSpeechFailed": "جداسازی گفتار ممکن نشد - به‌جای آن صدای اصلی رونویسی می‌شود.", + "detectSpeakers": "تشخیص گویندگان", + "detectSpeakersHint": "هر خط را با نام گوینده شروع می‌کند، مانند \"(speaker 0)\"، با استفاده از تفکیک گوینده Sortformer انویدیا (تا ۸ گوینده). با موتورهایی که خطوط را در محل تغییر گوینده تقسیم می‌کنند، مانند Parakeet و Cohere، بهترین عملکرد را دارد.", + "detectSpeakersNeedsNewerCrispAsr": "\"تشخیص گویندگان\" به Crisp ASR نسخه v{0} یا جدیدتر نیاز دارد، اما v{1} نصب شده است.\n\nCrisp ASR را به‌روزرسانی کنید (دکمه چرخ‌دنده کنار موتور) یا \"تشخیص گویندگان\" را خاموش کنید.", + "xNeedsNewerCrispAsr": "{0} به Crisp ASR نسخه v{1} یا جدیدتر نیاز دارد، اما v{2} نصب شده است.\n\nبه‌روزرسانی اکنون دانلود و نصب شود؟", + "vad": "VAD", + "vadHint": "تشخیص فعالیت صوتی: فقط گفتار رونویسی می‌شود و در جاهایی که گوینده مکث می‌کند بریده می‌شود.\nخودکار: Silero، و فقط برای موتورهایی که به آن نیاز دارند (Cohere، Mega، Index-Echo).\nSilero، FireRedVAD، Whisper-VAD، WebRTC: همیشه فعال، برای همه موتورهای Crisp ASR. FireRedVAD دقیق‌ترین است، Whisper-VAD کمترین و تمیزترین برش‌ها را می‌دهد اما کندترین است.\nIndex-Echo همیشه از Silero استفاده می‌کند. --vad، --vad-model یا --chunk-seconds در تنظیمات پیشرفته اولویت دارند.", "inputLanguage": "زبان ورودی", "advancedWhisperSettings": "پارامترهای پیشرفته گفتار به متن", "downloadingSpeechToTextEngine": "در حال دانلود موتور گفتار به متن", @@ -2344,6 +2682,19 @@ "videoEncodingSettings": "TTS - تنظیمات رمزگذاری ویدیو", "elevenLabsSettings": "TTS - تنظیمات ElevenLabs", "elevenLabsSettingsResetHint": "بازنشانی تنظیمات ElevenLabs به مقادیر پیش‌فرض", + "provider": "ارائه‌دهنده", + "openAiCompatibleSettings": "TTS - تنظیمات سازگار با OpenAI", + "customServerUrl": "URL سرور سفارشی", + "customServerUrlHint": "نقطه پایانی کامل گفتار یک سرور سازگار با OpenAI، که وقتی ارائه‌دهنده \"سفارشی\" است استفاده می‌شود، مثلاً http://localhost:8880/v1/audio/speech", + "customModels": "مدل‌های سفارشی", + "customModelsHint": "نام مدل‌ها با جداکننده کاما که وقتی ارائه‌دهنده \"سفارشی\" است ارائه می‌شوند", + "customVoices": "صداهای سفارشی", + "customVoicesHint": "نام صداها با جداکننده کاما، که توسط ارائه‌دهنده \"سفارشی\" و مدل‌های OpenRouter که فهرست صدا منتشر نمی‌کنند استفاده می‌شوند", + "instructions": "دستورالعمل‌ها", + "instructionsHint": "سبک گفتار اختیاری، مثلاً \"با صدایی آرام و گرم صحبت کن\". فقط به مدل‌هایی ارسال می‌شود که از آن پشتیبانی می‌کنند (نه tts-1/tts-1-hd)", + "openAiSpeedHint": "سرعت گفتار از 0.25 تا 4.0 (1.0 معمولی است). همه مدل‌ها از آن پشتیبانی نمی‌کنند", + "outputFormat": "قالب خروجی", + "outputFormatHint": "قالب صوتی درخواستی از سرور. \"خودکار\" MP3 درخواست می‌کند و برای مدل‌هایی که فقط PCM برمی‌گردانند (مثلاً Gemini TTS) به PCM تغییر می‌کند", "regenerateAudio": "تولید مجدد صدا", "autoContinuePlaying": "ادامه پخش خودکار", "playLine": "پخش خط", @@ -2352,6 +2703,13 @@ "addingAudioToVideoFileDotDotDot": "در حال افزودن صدا به فایل ویدیو...", "preparingMergeDotDotDot": "در حال آماده‌سازی ادغام...", "importVoiceDotDotDot": "وارد کردن صدا...", + "renameVoiceDotDotDot": "تغییر نام صدا...", + "renameVoiceTitle": "تغییر نام صدا", + "voiceXCouldNotBeRenamedX": "تغییر نام صدای '{0}' ممکن نشد: {1}", + "deleteVoiceDotDotDot": "حذف صدا...", + "deleteVoiceTitle": "حذف صدا", + "deleteVoiceXQuestion": "صدای '{0}' حذف شود؟\n\nفایل ضبط مرجع و فایل‌های آن از دیسک حذف می‌شوند.", + "voiceXCouldNotBeDeletedX": "حذف صدای '{0}' ممکن نشد: {1}", "voiceImportSuccessTitle": "صدا وارد شد", "voiceXImported": "صدا «{0}» با موفقیت وارد شد", "voiceXCouldNotBeImported": "صدا «{0}» قابل وارد کردن نبود - برای جزئیات گزارش را ببینید", @@ -2361,6 +2719,49 @@ "dropAudioFileHereToImportVoice": "برای وارد کردن صدا، فایل صوتی را اینجا رها کنید", "dropAudioFileHereHint": ".wav یا .mp3", "voiceCloneTranscriptTitle": "متن رونویسی فایل صوتی را وارد کنید (برای شبیه‌سازی صدا الزامی است)", + "voiceManagerTitle": "TTS - مدیر صداها", + "voiceManagerDotDotDot": "مدیر صداها...", + "voiceManagerMenuItem": "مدیر صداهای TTS...", + "speakFromCurrentLineTitle": "خواندن از خط فعلی", + "speakFromCurrentLineDotDotDot": "خواندن از خط فعلی...", + "speakFromCurrentLineHint": "خطوط را یکی پس از دیگری با صدای بلند می‌خواند و از خط فعلی شروع می‌کند. برای توقف، Escape را فشار دهید یا روی خط دیگری کلیک کنید.", + "stopSpeaking": "توقف خواندن", + "speakingLineX": "در حال خواندن خط {0}... (Escape برای توقف)", + "playWithSpeechTitle": "پخش همراه با گفتار", + "playWithSpeechDotDotDot": "پخش همراه با گفتار...", + "playWithSpeechHint": "ویدئو را پخش می‌کند و هر خط را وقتی ویدئو به آن می‌رسد می‌خواند. مانند همیشه توقف موقت و جستجو کنید؛ برای توقف Escape را فشار دهید.", + "lowerVideoVolumeWhileSpeaking": "کاهش صدای ویدئو هنگام خواندن", + "pauseVideoWhenSpeechRunsLate": "توقف موقت ویدئو وقتی گفتار به خط بعدی می‌رسد", + "waitingForSpeechLineX": "در حال تولید گفتار برای خط {0}... (Escape برای توقف)", + "voiceKindClone": "صدای شبیه‌سازی‌شده", + "voiceKindPreset": "داخلی", + "voiceKindModel": "مدل صدا", + "voiceKindOnline": "آنلاین", + "transcript": "متن پیاده‌شده", + "transcriptRequiredHint": "الزامی - موتور هنگام شبیه‌سازی این متن پیاده‌شده از فایل ضبط (ref-text) را می‌خواند", + "transcriptOptionalHint": "اختیاری - متن پیاده‌شده دقیق از فایل ضبط، شبیه‌سازی را بهبود می‌دهد", + "saveTranscript": "ذخیره متن پیاده‌شده", + "searchVoices": "جستجوی صداها", + "loadingVoicesDotDotDot": "در حال بارگذاری صداها...", + "noVoicesForEngine": "هیچ صدایی وجود ندارد - یک فایل ضبط وارد کنید یا یک بسته صدا دانلود کنید", + "noVoicesMatchFilter": "هیچ صدایی با جستجو مطابقت ندارد", + "copyVoiceTo": "کپی صدا به", + "copyVoiceToDotDotDot": "کپی صدا به موتور...", + "copyingVoiceXToYDotDotDot": "در حال کپی صدای '{0}' به {1}...", + "voiceXCopiedToY": "صدای '{0}' به {1} کپی شد", + "voiceXCouldNotBeCopiedToY": "کپی صدای '{0}' به {1} ممکن نشد - برای جزئیات گزارش را ببینید", + "voiceXAlreadyExistsInYContinue": "صدایی با نام '{0}' از قبل در {1} وجود دارد.\n\nبه هر حال کپی شود؟", + "openVoicesFolder": "باز کردن پوشه صداها", + "downloadVoicePacksDotDotDot": "دانلود بسته‌های صدا...", + "downloadVoicePacksTitle": "TTS - دانلود بسته‌های صدا", + "voicePackIntro": "بسته‌های صدا مجموعه‌هایی از فایل‌های ضبط کوتاه و تمیز همراه با متن پیاده‌شده هستند که آماده شبیه‌سازی‌اند. هر بسته در موتور انتخاب‌شده وارد می‌شود (صداهایی که از قبل وجود دارند رد می‌شوند) و شامل یک فایل ATTRIBUTION.txt با مجوز و منابع آن است.", + "voicePackDetailsXVoicesYSizeZLicense": "{0} صدا · {1} · {2}", + "installToEngine": "نصب در موتور", + "downloadAndInstall": "دانلود و نصب", + "selectAtLeastOneVoicePack": "حداقل یک بسته صدا انتخاب کنید", + "downloadingXDotDotDot": "در حال دانلود {0}...", + "installingXDotDotDot": "در حال نصب {0}...", + "xVoicesInstalledYSkipped": "{0} صدا نصب شد ({1} مورد از قبل وجود داشت)", "useSpeechToTextDotDotDot": "استفاده از تبدیل گفتار به متن...", "autoCastMenuItem": "یافتن صداها در ویدیو و شبیه‌سازی...", "autoCastSpeakersTitle": "صداهای یافت‌شده در ویدیو", @@ -2379,7 +2780,7 @@ "cloneVoicePerLinePreparing": "در حال استخراج صدای هر خط از ویدیو...", "cloneVoicePerLineNeedsVideo": "برای شبیه‌سازی صدای هر خط، ویدیویی که زیرنویس به آن تعلق دارد لازم است. ویدیو را باز کنید و دوباره امتحان کنید.", "cloneVoicePerLineNoClips": "هیچ صدایی از ویدیو قابل استخراج نبود، بنابراین چیزی برای شبیه‌سازی وجود ندارد. بررسی کنید که ویدیو دارای ترک صوتی باشد.", - "cloneVoicePerLineNeedsOriginalSubtitleX": "{0} تنها زمانی می‌تواند یک خط را شبیه‌سازی کند که بداند در ویدئو در آن خط چه گفته می‌شود، و این اطلاعات از زیرنویس زبان اصلی به دست می‌آید. هیچ زیرنویس اصلی بارگذاری نشده است، بنابراین همه خطوط به‌جای صدای موجود در ویدئو با نخستین صدای وارد‌شده خوانده می‌شوند.\n\nزیرنویس اصلی را در کنار ترجمه باز کنید (فایل > باز کردن فایل اصلی...) و سپس دوباره تولید کنید.", + "cloneVoicePerLineTranscribeClipsXYZ": "{0} فقط زمانی می‌تواند یک خط را شبیه‌سازی کند که بداند در آن خط چه چیزی در ویدئو گفته می‌شود. این موضوع برای {1} از {2} خط مشخص نیست - معمولاً از زیرنویس زبان اصلی به دست می‌آید (فایل > باز کردن نسخه اصلی...).\n\nاکنون آن خطوط از ویدئو با تبدیل گفتار به متن رونویسی شوند؟\n\nبله: موتور و زبان گفته‌شده در ویدئو را انتخاب کنید تا کلیپ‌های مرجع پیش از تولید گفتار رونویسی شوند.\nخیر: به هر حال تولید شود - آن خطوط به‌جای صدای موجود در ویدئو با یک صدای معمولی خوانده می‌شوند.", "voiceCloneConsentTitle": "شبیه‌سازی صدا - پیش از شروع", "voiceCloneConsentHeader": "شما در آستانه شبیه‌سازی یک صدا هستید", "voiceCloneConsentIntro": "شبیه‌سازی صدا، صدای یک فرد واقعی را کپی می‌کند. این کار در بیشتر کشورها مشمول قوانین خاصی است و در اتحادیه اروپا تعهدات قانونی آن بر عهده شماست، نه Subtitle Edit. لطفاً این متن را یک بار مطالعه کنید.", @@ -2396,10 +2797,29 @@ "advancedTtsSettings": "تنظیمات پیشرفته TTS", "advancedTtsAudioProcessing": "پردازش صدا", "advancedTtsOutput": "خروجی", + "advancedTtsVoiceCloning": "شبیه‌سازی صدا", + "cleanCloneReferences": "پاک‌سازی مراجع شبیه‌سازی صدا (Sidon)", + "cleanCloneReferencesDescription": "موسیقی، نویز و پژواک را از صدایی که صدا از آن شبیه‌سازی می‌شود حذف می‌کند: کلیپ‌های گرفته‌شده از ویدئو و فایل‌های ضبط واردشده در مدیر صداها. از مدل بازسازی گفتار Sidon روی audio.cpp استفاده می‌کند (دانلود ۹۴۰ مگابایت). صدای شبیه‌سازی‌شده دیگر پس‌زمینه را کپی نمی‌کند، اما گفتار بسیار آرام یا هم‌پوشان ممکن است گرفته شنیده شود.", + "cleaningCloneReferencesDotDotDot": "در حال پاک‌سازی مراجع شبیه‌سازی صدا...", + "downloadSidonTitle": "Sidon دانلود شود؟", + "downloadSidonQuestionX": "پاک‌سازی مراجع شبیه‌سازی صدا به مدل بازسازی گفتار Sidon ({0}) نیاز دارد.\n\nاکنون دانلود شود؟ بدون آن، مراجع همان‌طور که هستند استفاده می‌شوند.", "proAudioPostProcessing": "پس‌پردازش حرفه‌ای صدا", "proAudioPostProcessingDescription": "گرمای اکولایزر، دروازه نویز، فشرده‌سازی، نرمال‌سازی بلندی صدا (۱۶- LUFS) و محو شدن تدریجی ابتدا/انتها را روی هر بخش اعمال می‌کند.", + "generatingSpeechSegmentXOfY": "در حال تولید گفتار: بخش {0} از {1}", + "adjustingSpeedSegmentXOfY": "در حال تنظیم سرعت: بخش {0} از {1}", + "mergingAudioSegmentXOfY": "در حال ادغام صدا: بخش {0} از {1}", + "postProcessingSegmentXOfY": "پس‌پردازش: بخش {0} از {1}", "audioDucking": "کاهش خودکار صدای پس‌زمینه", "audioDuckingDescription": "صدای ویدیوی اصلی را کاهش داده و آن را با صدای TTS میکس می‌کند تا موسیقی یا صدای اصلی همچنان به‌صورت ضعیف قابل شنیدن باشد.", + "removeOriginalSpeech": "حذف گفتار اصلی (کند)", + "removeOriginalSpeechDescription": "موسیقی و جلوه‌های صوتی ویدئوی اصلی را نگه می‌دارد اما گفتار آن را حذف می‌کند و گفتار جدید را روی آن میکس می‌کند. با GPU تقریباً به اندازه خود ویدئو طول می‌کشد و بدون آن چندین برابر بیشتر. با فعال بودن کاهش خودکار صدای پس‌زمینه، میزان صدای آن روی موسیقی و جلوه‌ها اعمال می‌شود. فقط زمانی اعمال می‌شود که گفتار به فایل ویدئو اضافه شود.", + "removingOriginalSpeech": "در حال حذف گفتار اصلی...", + "removeOriginalSpeechFailed": "حذف گفتار اصلی ممکن نشد - گفتار بدون آن به ویدئو اضافه شد.", + "speechRemovalModel": "مدل", + "speechRemovalModelDescription": "بهترین کیفیت بیشترین مقدار موسیقی و جلوه‌های صوتی را حفظ می‌کند. سریع حدود سه برابر سریع‌تر است، با کمی از دست رفتن بیشتر موسیقی.", + "speechRemovalBestQuality": "بهترین کیفیت (Mel-Band RoFormer)", + "speechRemovalFast": "سریع (HTDemucs)", + "videoTruncatedWarning": "تصویر در فایل ویدئویی جدید پس از {0} متوقف می‌شود، اما ویدئوی اصلی {1} طول دارد - تصویر ثابت می‌ماند در حالی که صدا ادامه دارد.\n\nفایل حفظ شد. برای خروجی ffmpeg، فایل error-log.txt را در پوشه داده‌های Subtitle Edit ببینید.", "originalVolumePercent": "درصد صدای اصلی", "vadSilenceCompression": "فشرده‌سازی سکوت با VAD", "vadSilenceCompressionDescription": "پیش از تغییر سرعت، مکث‌های بین کلمات را کوتاه می‌کند. با استفاده از تشخیص فعالیت صوتی، فقط فاصله‌های سکوت را فشرده می‌کند و گفتار را دست‌نخورده نگه می‌دارد. این روش اولین مرحله پیشنهادی است - مدت‌زمان را بدون افت کیفیت کاهش می‌دهد.", @@ -2482,7 +2902,9 @@ "timeCodeFormatColon": "فرمت کد زمانی:", "shotChangesList": "لیست تغییرات نما", "shotChangesClearQuestion": "آیا مطمئن هستید که می‌خواهید تمام تغییرات نما را پاک کنید؟", - "deleteSelectedShotChangeQuestion": "آیا مطمئن هستید که می‌خواهید تغییر نمای انتخاب‌شده را حذف کنید؟" + "deleteSelectedShotChangeQuestion": "آیا مطمئن هستید که می‌خواهید تغییر نمای انتخاب‌شده را حذف کنید؟", + "shotChanges": "تغییرات پلان", + "exportShotChanges": "صدور تغییرات پلان" }, "chapters": { "title": "فصل‌ها", @@ -2512,7 +2934,6 @@ "scaleTimesDescription": "زمان تمام فصل‌ها را از یک نرخ فریم به نرخ فریم دیگری تبدیل می‌کند.", "fromFrameRate": "از", "toFrameRate": "به", - "apply": "اعمال", "writeToVideo": "نوشتن در ویدیو...", "writeToVideoTitle": "نوشتن فصل‌ها در ویدیو", "writeToVideoDescription": "فصل‌ها در یک نسخه کپی از فایل ویدیو نوشته می‌شوند. هیچ رمزگذاری مجددی انجام نمی‌شود، بنابراین این فرایند سریع و بدون افت کیفیت است.", @@ -2522,7 +2943,6 @@ "writtenToX": "فصل‌ها در {0} نوشته شدند", "unableToWriteChapters": "امکان نوشتن فصل‌ها در فایل ویدیو وجود ندارد.", "chaptersSavedToX": "فصل‌ها در {0} ذخیره شدند", - "noVideoLoaded": "هیچ ویدیویی بارگذاری نشده است", "emptyListCallToAction": "هنوز فصلی وجود ندارد - یک فصل در موقعیت فعلی ویدیو اضافه کنید یا فصل‌ها را از یک ویدیو یا فایل وارد کنید." }, "videoOcr": { @@ -2547,10 +2967,10 @@ "runningOcrXY": "در حال اجرای OCR... {0}/{1}", "noLinesFoundTitle": "هیچ زیرنویسی یافت نشد", "noLinesFoundMessage": "هیچ متنی در ناحیه اسکن یافت نشد - تنظیم ناحیه اسکن، موتور یا حداقل روشنایی را تغییر دهید.", + "ocrStoppedEarlyMessage": "موتور OCR پیش از خواندن همه فریم‌ها متوقف شد، بنابراین خطوطی که تاکنون یافت شده‌اند حفظ می‌شوند و بقیه موجود نیستند.{0}{0}{1}", "linesFoundX": "{0} خط یافت شد", "refiningTimingXY": "در حال بهینه‌سازی زمان‌بندی... {0}/{1}", "fixOcrErrors": "اصلاح خطاهای OCR", - "dictionary": "فرهنگ لغت", "editLineX": "ویرایش خط {0}", "downloadDictionaryHint": "دانلود فرهنگ لغت بررسی املا...", "framesPerSecondHint": "تعداد فریم‌هایی که در هر ثانیه بررسی می‌شوند. مقدار ۵ برای زیرنویس‌های معمولی مناسب است؛ مقادیر بالاتر خطوط بسیار کوتاه را بهتر تشخیص می‌دهند و گروه‌بندی را دقیق‌تر می‌کنند، اما زمان OCR بیشتری می‌گیرند.", @@ -2567,7 +2987,58 @@ "testOcr": "آزمایش فریم فعلی", "testOcrRunning": "در حال آزمایش OCR روی فریم فعلی...", "testOcrResultX": "نتیجه آزمایش: {0}", - "testOcrNoTextFound": "آزمایش: هیچ متنی در ناحیه اسکن یافت نشد" + "testOcrNoTextFound": "آزمایش: هیچ متنی در ناحیه اسکن یافت نشد", + "engineSettings": "تنظیمات موتور OCR", + "engineBuiltIn": "داخلی سیستم‌عامل", + "engineExternalServer": "سرور خارجی (نصب و مدیریت‌شده خارج از Subtitle Edit)", + "engineCloudApi": "API ابری (نیازمند کلید API)", + "engineNothingToInstall": "چیزی برای نصب وجود ندارد", + "engineModelsMissing": "موتور نصب شده، مدل‌ها موجود نیستند", + "website": "وب‌سایت" + }, + "backgroundMusic": { + "title": "تولید موسیقی پس‌زمینه", + "generateBackgroundMusicDotDotDot": "تولید موسیقی پس‌زمینه...", + "preset": "پیش‌تنظیم", + "prompt": "پرامپت", + "tempoBpm": "تمپو (BPM)", + "generateSeconds": "تولید (ثانیه)", + "seed": "Seed", + "randomSeed": "تصادفی", + "outputLength": "طول خروجی", + "outputLengthVideoX": "{0} (طول ویدئو - موسیقی به‌صورت یکپارچه تکرار می‌شود)", + "musicVolumePercent": "میزان صدای موسیقی (%)", + "removeExistingAudioTracks": "حذف ترک‌های صوتی موجود", + "originalAudioVolumePercent": "میزان صدای اصلی (%)", + "saveAudioDotDotDot": "ذخیره صدا...", + "addToVideoDotDotDot": "افزودن به ویدئو...", + "engineInfo": "ACE-Step 1.5 Turbo از طریق audio.cpp (مجوز MIT) - به‌صورت محلی اجرا می‌شود", + "presetCooking": "آشپزی / شیرینی‌پزی", + "presetTech": "فناوری / بررسی گجت", + "presetForest": "جنگل / طبیعت", + "presetRelaxingPiano": "پیانوی آرامش‌بخش", + "presetLofiVlog": "ولاگ Lo-fi", + "presetCorporate": "سازمانی / توضیحی", + "loadingModel": "در حال بارگذاری مدل...", + "composing": "در حال آهنگ‌سازی...", + "generatingAudio": "در حال تولید صدا...", + "decodingAudio": "در حال رمزگشایی صدا...", + "creatingLoop": "در حال ساخت حلقه یکپارچه...", + "resultLoopXYZ": "حلقه: {0} با {1} BPM، تطابق محل اتصال {2}٪", + "resultNoLoopX": "موسیقی آماده است: {0}", + "addingMusicToVideo": "در حال افزودن موسیقی به ویدئو...", + "downloadModelTitle": "مدل موسیقی دانلود شود؟", + "downloadModelQuestionX": "تولید موسیقی به مدل ACE-Step 1.5 ({0}) نیاز دارد.\n\nمدل دانلود شود؟", + "downloadingModel": "در حال دانلود مدل ACE-Step 1.5...", + "saveAudioTitle": "ذخیره موسیقی پس‌زمینه", + "audioFileSavedX": "فایل صوتی ذخیره شد: {0}", + "unableToGenerateMusic": "تولید موسیقی ممکن نیست", + "unableToAddMusicToVideo": "افزودن موسیقی به ویدئو ممکن نیست", + "outputLengthTextToSpeech": "از گفتار تولیدشده پیروی می‌کند - موسیقی به‌صورت یکپارچه تکرار می‌شود و هنگام صحبت کسی صدایش کم می‌شود", + "addBackgroundMusic": "افزودن موسیقی پس‌زمینه", + "addBackgroundMusicHint": "موسیقی را به‌صورت محلی با ACE-Step 1.5 تولید کرده و زیر گفتار میکس کنید - هنگام صحبت کسی صدایش کم می‌شود", + "generatingBackgroundMusicDotDotDot": "در حال تولید موسیقی پس‌زمینه...", + "mixingBackgroundMusicDotDotDot": "در حال میکس موسیقی پس‌زمینه..." }, "goToVideoPosition": "رفتن به موقعیت ویدیو", "goToVideoPositionDotDotDot": "رفتن به موقعیت ویدیو...", @@ -2578,10 +3049,47 @@ "openSecondarySubtitleOnVideoPlayerDotDotDot": "باز کردن زیرنویس دوم در پخش‌کننده ویدیو...", "openSecondarySubtitleOnVideoPlayer": "زیرنویس دوم (در پخش‌کننده ویدیو)", "removeSecondarySubtitleOnVideoPlayer": "حذف زیرنویس دوم از پخش‌کننده ویدیو", + "editSecondarySubtitleSettings": "ویرایش تنظیمات زیرنویس دوم", + "editSecondarySubtitleSettingsDotDotDot": "ویرایش تنظیمات زیرنویس دوم...", + "secondarySubtitleRememberSettings": "به‌خاطر سپردن این تنظیمات", + "secondarySubtitleDoNotShowAgain": "این پنجره دیگر نمایش داده نشود", + "secondarySubtitleJustifyLines": "تراز دوطرفه خطوط", "openRecentVideo": "باز کردن ویدئوی اخیر", "clearRecentVideos": "پاک کردن ویدئوهای اخیر", "cutVideoTitle": "برش ویدیو", "cutVideoDotDotDot": "برش ویدیو...", + "remuxVideoTitle": "ریماکس ویدئو", + "remuxVideoDotDotDot": "ریماکس ویدئو...", + "remuxVideoInputVideo": "ویدئوی ورودی:", + "remuxVideoMultipleTracksRequiresMkv": "چند ترک صوتی یا زیرنویس به کانتینر MKV نیاز دارند. قالب خروجی به‌طور خودکار به .mkv تغییر یافت.", + "remuxVideoOutputFormat": "قالب خروجی:", + "remuxVideoOutputFile": "فایل خروجی:", + "remuxVideoRemuxing": "در حال ریماکس ویدئو و صدا...", + "remuxVideoFinalizing": "در حال نهایی‌سازی فایل خروجی (انتقال ایندکس به ابتدا)...", + "remuxVideoCompleted": "ویدئو با موفقیت ریماکس شد.", + "remuxVideoFailed": "ریماکس ویدئو ناموفق بود.", + "remuxVideoPleaseSelectBoth": "لطفاً هر دو فایل ویدئو و صدا را انتخاب کنید.", + "remuxVideoAssRequiresMkv": "زیرنویس‌های ASS/SSA برای حفظ همه قالب‌بندی‌ها و استایل‌ها به کانتینر MKV نیاز دارند.", + "remuxVideoSccRequiresMov": "زیرنویس‌های Scenarist (.scc) به‌صورت زیرنویس بسته CEA-608 جاسازی می‌شوند که به کانتینر MOV یا MPG نیاز دارند. قالب خروجی به‌طور خودکار به .mov تغییر یافت.", + "remuxVideoSccNotWithWebM": "زیرنویس‌های بسته Scenarist (.scc) به کانتینر MOV نیاز دارند که نمی‌تواند ویدئوی VP9/AV1 یک فایل .webm را نگه دارد. فایل .scc را حذف کنید یا آن را به قالب زیرنویس دیگری تبدیل کنید.", + "remuxVideoMpgMaxTwoCaptionTracks": "یک فایل .mpg حداکثر دو ترک زیرنویس بسته (CC1 و CC3) نگه می‌دارد - فایل‌های زیرنویس را حذف کنید تا یک یا دو مورد باقی بماند.", + "remuxVideoMccRequiresMpg": "زیرنویس‌های MacCaption (.mcc) به‌صورت زیرنویس بسته CEA-608/708 در ویدئوی MPEG-2 جاسازی می‌شوند که به کانتینر MPG نیاز دارد. قالب خروجی به‌طور خودکار به .mpg تغییر یافت.", + "remuxVideoMpgReencodeVideoX": "زیرنویس‌های بسته در یک فایل .mpg به ویدئوی MPEG-2 نیاز دارند، و ویدئو {0} است. ویدئو مجدداً به MPEG-2 رمزگذاری شود؟ این کار زمان بیشتری می‌برد و کیفیت را کمی کاهش می‌دهد.", + "remuxVideoFileHasNoAudioX": "\"{0}\" ترک صوتی ندارد، بنابراین نمی‌توان از آن به‌عنوان منبع صدا استفاده کرد.", + "remuxVideoAddingClosedCaptions": "در حال افزودن زیرنویس‌های بسته...", + "remuxVideoSelectAudioTrack": "انتخاب ترک صوتی", + "remuxVideoSelectAudioTrackFor": "انتخاب ترک صوتی برای {0}", + "remuxVideoSelectAudioTrackPrompt": "فایل '{0}' شامل {1} ترک صوتی است. لطفاً ترک صوتی را برای ریماکس انتخاب کنید:", + "remuxVideoVideoFile": "ویدئو", + "remuxVideoAudioFile": "صدا", + "remuxVideoAudioFiles": "فایل‌های صوتی:", + "remuxVideoSubtitleFiles": "فایل‌های زیرنویس (اختیاری، زیرنویس نرم):", + "remuxVideoSelectAudioTrackDotDotDot": "انتخاب ترک صوتی...", + "remuxVideoFilesX": "{0} فایل", + "remuxVideoMixAudio": "میکس صدا در یک ترک", + "remuxVideoVolumePercent": "میزان صدا %:", + "remuxVideoVolumeX": "میزان صدا {0}%", + "remuxVideoFastStart": "بهینه‌سازی برای پخش آنلاین وب (انتقال ایندکس به ابتدا - کندتر)", "embedSubtitlesDotDotDot": "افزودن/حذف زیرنویس‌های تعبیه‌شده...", "generateTimeCodes": "تولید کدهای زمانی", "checkeredImage": "تصویر شطرنجی", @@ -2591,6 +3099,34 @@ "cutVideoSplitSegments": "ذخیره جداگانه بخش‌ها", "cutVideoAlsoCutSubtitle": "برش زیرنویس نیز", "cutVideoSubtitleFileGeneratedX": "فایل زیرنویس ایجاد شد: {0}", + "cutVideoTransition": "انتقال بین بخش‌ها", + "cutVideoTransitionDurationSeconds": "مدت زمان (ثانیه)", + "cutVideoFadeInSeconds": "ظهور تدریجی از سیاه در ابتدا (ثانیه)", + "cutVideoFadeOutSeconds": "محو شدن به سیاه در انتها (ثانیه)", + "cutVideoPreviewTransition": "پیش‌نمایش انتقال", + "cutVideoPreviewTransitionTitle": "پیش‌نمایش انتقال", + "cutVideoPreviewNeedsJoin": "هیچ اتصالی برای پیش‌نمایش وجود ندارد - حداقل دو بخش برای ادغام، یا یک بخش برای برش از وسط اضافه کنید.", + "cutVideoTransitionInfoX": "هر انتقال با بخش‌هایی که به هم وصل می‌کند هم‌پوشانی دارد، بنابراین خروجی به ازای هر اتصال {0} ثانیه کوتاه‌تر می‌شود. زیرنویس برش‌خورده برای تطابق زمان‌بندی مجدد می‌شود.", + "cutVideoTransitionFade": "محو شدن", + "cutVideoTransitionFadeBlack": "محو شدن از طریق سیاه", + "cutVideoTransitionFadeWhite": "محو شدن از طریق سفید", + "cutVideoTransitionFadeGrays": "محو شدن از طریق خاکستری", + "cutVideoTransitionDissolve": "حل شدن", + "cutVideoTransitionWipeLeft": "پاک شدن به چپ", + "cutVideoTransitionWipeRight": "پاک شدن به راست", + "cutVideoTransitionWipeUp": "پاک شدن به بالا", + "cutVideoTransitionWipeDown": "پاک شدن به پایین", + "cutVideoTransitionSlideLeft": "لغزش به چپ", + "cutVideoTransitionSlideRight": "لغزش به راست", + "cutVideoTransitionSlideUp": "لغزش به بالا", + "cutVideoTransitionSlideDown": "لغزش به پایین", + "cutVideoTransitionSmoothLeft": "نرم به چپ", + "cutVideoTransitionSmoothRight": "نرم به راست", + "cutVideoTransitionCircleOpen": "باز شدن دایره‌ای", + "cutVideoTransitionCircleClose": "بسته شدن دایره‌ای", + "cutVideoTransitionRadial": "شعاعی", + "cutVideoTransitionPixelize": "پیکسلی", + "cutVideoTransitionBlur": "تاری", "importCurrentSubtitle": "وارد کردن زیرنویس فعلی", "addRemoveEmbeddedSubtitlesTitle": "افزودن/حذف زیرنویس‌های تعبیه‌شده", "addRemoveEmbeddedSubtitlesMp4Title": "افزودن/حذف زیرنویس‌های تعبیه‌شده (MP4)", @@ -2606,6 +3142,7 @@ "embeddedTrackGeneratingVideoXY": "در حال تولید ویدیو... {0}% {1}", "embeddedTrackGeneratingVideo": "در حال تولید ویدیو...", "mp4FilesFilter": "فایل‌های MP4", + "embeddedTracksPickVideoHint": "برای دیدن ترک‌های زیرنویس یک فایل ویدئویی، آن را انتخاب کنید یا اینجا رها کنید.", "viewMatroskaTrackX": "مشاهده ترک Matroska - {0}", "resolutionSeparator": "x", "openFromUrlTitle": "باز کردن فایل ویدیو از URL", @@ -2652,12 +3189,15 @@ "reEncodeGeneratingDone": "تولید به پایان رسید", "reEncodeGeneratedFilesX": "فایل‌های تولیدشده ({0}):", "reEncodeFfmpegParameters": "پارامترهای ffmpeg", + "analyzingVideoX": "در حال تحلیل ویدئو... {0}% {1}", + "analyzingVideoXofY": "در حال تحلیل ویدئو {0}/{1}... {2}% {3}", + "backgroundImageFileNotSelected": "فایل تصویر پس‌زمینه انتخاب نشده است", + "backgroundImageFileDoesNotExistX": "فایل تصویر پس‌زمینه وجود ندارد: {0}", "voices": "صداها", "presets": "پیش‌تنظیم‌ها", "talkerX": "گوینده {0}", "baseModel": "مدل پایه", "turboModel": "مدل توربو", - "ttsUpdateX": "به‌روزرسانی {0}", "ttsEngineNotInstalled": "{0} نصب نشده است", "ttsEngineUpdateAvailable": "{0} - به‌روزرسانی موجود است", "ttsEngineTooOldUpdateRequired": "{0} بیش‌ازحد قدیمی است - به‌روزرسانی لازم است", @@ -2680,9 +3220,17 @@ "cloneVoiceTo": "کلون کردن صدا به", "cloneVoiceNameTitle": "نام صدای جدید", "cloneVoiceExtractFailed": "امکان استخراج صدا برای خط انتخاب‌شده وجود ندارد.", + "lineXHasNoDuration": "خط {0} مدت زمان ندارد (زمان پایان بعد از زمان شروع نیست)، بنابراین صدایی برای استخراج وجود ندارد.", + "audioClipsSkippedLinesX": "هیچ صدایی برای این خطوط استخراج نشد، بنابراین رد شدند: {0}", "toggleShotChange": "فعال/غیرفعال کردن تغییر نما", "resetWaveformZoomAndSpeed": "بازنشانی بزرگ‌نمایی و سرعت موج صدا", "showOriginalSubtitle": "نمایش زیرنویس اصلی", + "timelineGroupTracksBy": "گروه‌بندی ترک‌های زیرنویس بر اساس", + "timelineShowThumbnails": "نمایش تصاویر بندانگشتی ویدئو", + "showSpeechOnly": "نمایش فقط گفتار (تولید کند)", + "isolatingSpeechForWaveformX": "در حال جداسازی گفتار برای شکل موج... {0}", + "isolatingSpeechForWaveformFailed": "جداسازی گفتار برای شکل موج ممکن نشد - فایل گزارش ابزارها را ببینید", + "speechOnlyWaveformReady": "شکل موج اکنون فقط گفتار را نشان می‌دهد", "showOnlyWaveform": "فقط نمایش موج صدا", "showOnlySpectrogram": "فقط نمایش طیف‌نگار", "showWaveformAndSpectrogram": "نمایش موج صدا و طیف‌نگار", @@ -2715,6 +3263,7 @@ "adjustSelectedLinesAndForward": "تنظیم خطوط انتخاب‌شده و به بعد", "toFrameRate": "به نرخ فریم", "fromFrameRate": "از نرخ فریم", + "videoXFrameRateY": "{0} - {1} fps", "adjustAllTimes": "تنظیم همه زمان‌ها (جلو/عقب بردن زیرنویس)", "showEarlier": "نمایش زودتر (عقب بردن زمان)", "showLater": "نمایش دیرتر (جلو بردن زمان)", @@ -2724,9 +3273,8 @@ "syncPoints": "نقاط همگام‌سازی", "pointSync": "همگام‌سازی نقطه‌ای", "pointSyncViaOther": "همگام‌سازی نقطه‌ای از طریق زیرنویس دیگر", - "adjustmentX": "تعدیل: {0}", - "totalAdjustmentX": "تعدیل کل: {0}", - "syncPointCandidateInfo": "خطوطی که پس از {0} ثانیه یا بیشتر سکوت آغاز می‌شوند برجسته شده‌اند - این خطوط اغلب نقاط همگام‌سازی قابل اعتمادی هستند", + "syncPointCandidateAfterInfo": "خطوطی که پس از آن‌ها {0}+ ثانیه سکوت وجود دارد هایلایت شده‌اند - این خطوط اغلب نقاط همگام‌سازی قابل اعتمادی هستند", + "gapAfter": "فاصله بعد", "adjustAllShortcuts": "میانبرهای کیبورد:\r\n\r\n• Shift + چپ/راست: جابجایی ۱۰ میلی‌ثانیه\r\n• Ctrl + چپ/راست: جابجایی ۱۰۰ میلی‌ثانیه\r\n• Alt + چپ/راست: جابجایی ۵۰۰ میلی‌ثانیه", "adjustAllShortcutsFrames": "میانبرهای کیبورد:\r\n\r\n• Shift + چپ/راست: جابجایی ۱ فریم\r\n• Ctrl + چپ/راست: جابجایی ۱۰ فریم\r\n• Alt + چپ/راست: جابجایی ۱ ثانیه", "offsetInSeconds": "آفست به ثانیه", @@ -2746,6 +3294,7 @@ "lineMerge": "ادغام خطوط", "delayInSecondsBetweenRequests": "تاخیر به ثانیه بین درخواست‌ها", "maxBytesPerRequest": "حداکثر بایت در هر درخواست", + "keepMusicLinesUntranslated": "خطوط داخل نمادهای موسیقی (♪ ... ♪) ترجمه نشوند", "promptText": "متن پرامپت", "resetPromptToDefault": "بازنشانی پرامپت به حالت پیش‌فرض", "translateEachLineSeparately": "ترجمه هر خط به‌صورت جداگانه", @@ -2759,11 +3308,10 @@ "readyToTranslate": "آماده ترجمه", "translating": "در حال ترجمه...", "translationComplete": "ترجمه کامل شد", + "translateInPlaceNoOriginal": "ترجمه در محل (بدون ایجاد نسخه اصلی)", "translationCancelled": "ترجمه لغو شد", "swapLanguages": "جابه‌جایی زبان مبدأ و مقصد", "xIsAlreadyDownloadedReDownload": "{0} قبلاً دانلود شده است. دوباره دانلود شود؟", - "advancedDotDotDot": "پیشرفته...", - "advancedSettings": "تنظیمات پیشرفته", "advancedSettingsSubtitle": "گزینه‌های دسته‌ای، زمینه و نمونه‌برداری برای موتور پیشرفته llama.cpp", "context": "زمینه", "synopsis": "خلاصه", @@ -2787,6 +3335,7 @@ "useOnlyExtraServerParametersHint": "llama-server را با پارامترهای بالا اجرا می‌کند و تنظیمات اختصاصی Subtitle Edit را نادیده می‌گیرد - فقط مدل، میزبان و پورت همچنان تنظیم می‌شوند", "serverRunningAtX": "سرور در {0} در حال اجرا است", "customPromptHint": "دستورالعمل‌های سفارشی ({0} = زبان مبدأ، {1} = زبان مقصد)؛ خالی = پرامپت داخلی", + "editTranslationHint": "برای ویرایش ترجمه ردیف انتخاب‌شده، روی آن کلیک کنید (Enter ذخیره می‌کند، Escape لغو می‌کند)", "llamaCppDownloadEngineAndModelPrompt": "llama.cpp به موتور llama-server و یک مدل ترجمه نیاز دارد که دانلود شوند. اکنون دانلود شوند؟", "llamaCppDownloadEnginePrompt": "llama.cpp به موتور llama-server نیاز دارد که دانلود شود. اکنون دانلود شود؟", "llamaCppDownloadModelPrompt": "llama.cpp به مدل ترجمه انتخاب‌شده نیاز دارد که دانلود شود. اکنون دانلود شود؟" @@ -2857,6 +3406,7 @@ "showGridLines": "نمایش خطوط جدول", "showHorizontalLineAboveToolbar": "نمایش خط افقی بالای نوار ابزار", "bookmarkColor": "رنگ نشانک", + "spellCheckHighlightColor": "رنگ هایلایت بررسی املایی", "singleLineMaxLength": "حداکثر طول یک خط", "optimalCharsPerSec": "تعداد بهینه نویسه در ثانیه", "maxCharsPerSec": "حداکثر نویسه در ثانیه", @@ -2881,13 +3431,21 @@ "useDoNotBreakAfterList": "استفاده از فهرست «پس از این موارد نشکن»", "newEmptyDefaultMs": "مدت پیش‌فرض زیرنویس جدید (میلی‌ثانیه)", "timeCodeUpDownStepMs": "مقدار تغییر زمان با بالا/پایین (میلی‌ثانیه)", + "moveSelectedLinesStepMs": "گام میانبر جابجایی خطوط انتخاب‌شده (میلی‌ثانیه)", + "moveStartEndStepMs": "گام میانبر جابجایی شروع/پایان (میلی‌ثانیه)", + "moveLinesShortenNeighbor": "جابجایی خطوط: کوتاه کردن خط قبلی/بعدی به‌جای هم‌پوشانی با آن", "promptBeforeDelete": "پرسش پیش از حذف", "rememberPositionAndSize": "به‌خاطر سپردن موقعیت و اندازه پنجره", + "titleBarFullFileName": "نمایش مسیر کامل فایل در نوار عنوان", + "showRecentFiles": "نمایش فایل‌های اخیر (برای باز کردن مجدد)", "openLastFileOnStart": "باز کردن آخرین فایل اخیر هنگام شروع", "autoSave": "ذخیره خودکار (ذخیره فایل باز هنگام ویرایش)", "autoBackupOn": "پشتیبان‌گیری خودکار", "autoBackupIntervalMinutes": "فاصله پشتیبان‌گیری خودکار (دقیقه)", "autoBackupDeleteAfterDays": "نگهداری پشتیبان خودکار (روز)", + "settingsBackupOn": "پشتیبان‌گیری خودکار از تنظیمات", + "settingsBackupIntervalDays": "فاصله پشتیبان‌گیری از تنظیمات (روز، ۰ = هر بار شروع)", + "settingsBackupMaxCount": "تعداد نسخه‌های پشتیبان تنظیمات برای نگهداری", "autoConvertToUtf8": "تبدیل خودکار رمزگذاری به UTF-8 هنگام باز کردن", "autoTrimWhiteSpace": "حذف خودکار فاصله‌های اضافی", "removeBlankLinesWhenOpening": "حذف خطوط خالی هنگام باز کردن زیرنویس", @@ -2904,7 +3462,10 @@ "colorGapTooShort": "رنگی کردن اگر فاصله خیلی کوتاه باشد", "errorBackgroundColor": "رنگ پس‌زمینه خطا", "waveformDrawGridLines": "رسم خطوط شبکه", + "waveformUseSkiaRenderer": "استفاده از رندرکننده سریع آزمایشی", "waveformCenterVideoPositionAlsoWhenPaused": "مرکز کردن موقعیت ویدیو حتی هنگام توقف", + "waveformSelectCurrentSubtitleWhilePaused": "انتخاب زیرنویس فعلی حتی هنگام توقف موقت", + "waveformRememberZoom": "به‌خاطر سپردن بزرگ‌نمایی (افقی و عمودی)", "waveformShowToolbar": "نمایش نوار ابزار", "waveformShowToolbarEdit": "ویرایش نوار ابزار...", "waveformShowToolbarEditLabel": "موارد نوار ابزار", @@ -2952,18 +3513,25 @@ "webVttUseXTimestampMap": "WebVTT: استفاده از X-TIMESTAMP-MAP (جابجایی کدهای زمانی هنگام بارگذاری)", "assaAutoSetResolution": "ASSA: تنظیم رزولوشن (PlayResX/PlayResY) بر اساس ویدئو هنگام باز شدن ویدئو", "assaAutoSetResolutionPrompt": "ASSA: پیش از تغییر رزولوشن برای تطبیق با ویدئو پرسیده شود (در غیر این صورت به‌صورت خودکار بازنمونه‌برداری می‌شود)", - "showStopButton": "نمایش دکمه توقف", - "showFullscreenButton": "نمایش دکمه تمام‌صفحه", + "videoControls": "کنترل‌های ویدئو", + "videoControlsEdit": "ویرایش کنترل‌های ویدئو...", + "videoControlsPositionText": "متن موقعیت / مدت زمان", + "videoControlsVideoFileName": "نام فایل ویدئو", + "videoControlsPlayerName": "نام پخش‌کننده ویدئو", "fullscreenHideControls": "پنهان کردن کنترل‌های ویدیو در حالت تمام‌صفحه", + "showSecondarySubtitleDialog": "نمایش پنجره استایل هنگام باز کردن فایل زیرنویس دوم", + "rememberSecondarySubtitleFile": "به‌خاطر سپردن فایل زیرنویس دوم", "autoOpenVideoFile": "باز کردن خودکار فایل ویدیو هنگام باز کردن زیرنویس", "downloadMpv": "دانلود mpv", "downloadVlc": "دانلود VLC", "allowSingleLetterShortcutsInTextbox": "اجازه میانبرهای تک‌حرفی در کادر متن", + "allowTextNavigationShortcutsInTextbox": "اجازه میانبر روی کلیدهای پیمایش متن (Ctrl+چپ/راست، Home/End) در جعبه متن", "spellCheckEnglishTreatInApostropheAsIng": "غلط‌یابی: در انگلیسی، کلمات پایان‌یافته به 'in'' را مانند 'ing' در نظر بگیر", "goToLineNumberSetsVideoPosition": "رفتن به شماره خط، موقعیت ویدیو را نیز تنظیم کند", "adjustAllTimesRememberLineSelectionChoice": "تنظیم همه زمان‌ها، به‌خاطر سپردن انتخاب خط", "mergeKeepEndTime": "ادغام خطوط: حفظ زمان پایان (اجازه هم‌پوشانی با زیرنویس بعدی)", "mergeKeepEndTimeOnlyAssa": "ادغام خطوط: حفظ زمان پایان فقط برای «Advanced Sub Station Alpha»", + "mergeAutoBreak": "ادغام خطوط: شکست خودکار متن ادغام‌شده (خاموش، شکست‌های خط خود خطوط را حفظ می‌کند)", "filesAndLogs": "فایل‌ها و گزارش‌ها", "showErrorLogFile": "نمایش فایل گزارش خطا", "showToolsLogFile": "نمایش فایل گزارش ابزارها", @@ -2972,6 +3540,7 @@ "showAssaLayer": "نمایش کادر لایه ASSA", "waveformCursorColor": "رنگ مکان‌نما/سر موج‌نما", "waveformShotChangeColor": "رنگ تغییر نما در موج‌نما", + "waveformGridColor": "رنگ شبکه شکل موج", "waveformParagraphLeftColor": "رنگ حاشیه چپ موج‌نما", "waveformParagraphRightColor": "رنگ حاشیه راست موج‌نما", "waveformFancyHighColor": "رنگ بخش بالای فانتزی موج‌نما", @@ -2999,10 +3568,15 @@ "resetSyntaxColoring": "بازنشانی رنگ‌بندی نحوی", "resetWaveform": "بازنشانی موج‌نما", "resetRules": "بازنشانی قوانین", - "useFrameMode": "استفاده از حالت فریم (hh.mm.ss.ff)", + "timeCodeMode": "حالت کد زمانی", + "timeCodeModeTime": "زمان (hh:mm:ss,zzz)", + "timeCodeModeFrames": "فریم‌ها (hh:mm:ss:ff)", + "timeCodeModeFrameNumbers": "شماره فریم‌ها", + "frameModeSnapToVideoFrames": "ذخیره زمان‌ها روی فریم‌های ویدئو (نه فریم‌های درون هر ثانیه)", "textBoxLimitNewLines": "محدود کردن تعداد خطوط در کادر متن زیرنویس", "mpvOpenGl": "libmpv - OpenGL", "mpvSoftwareRendering": "libmpv - رندر نرم‌افزاری (کند)", + "mpvMetalRendering": "libmpv - Metal (آزمایشی، نیازمند راه‌اندازی مجدد)", "mpvWidRendering": "libmpv - رندر با شناسه بومی پنجره", "waveFormsAndSpectrogramFoldersContainsX": "پوشه‌های «Waveforms» و «spectrogram» شامل {0} هستند", "deleteWaveformAndSpectrogramFoldersQuestion": "فایل‌های «Waveforms» و «Spectrogram» حذف شوند؟", @@ -3015,7 +3589,10 @@ "waveformExtractAudioSampleRate": "نرخ نمونه‌برداری صدای استخراج‌شده", "waveformExtractAudioSampleRateOriginal": "اصلی (حفظ منبع)", "waveformExtractAudioBitRate": "نرخ بیت صدای استخراج‌شده (MP3/M4A)", + "ffmpegUseCenterChannelOnly": "استفاده فقط از کانال مرکزی جلو برای صدای 5.1 (FFmpeg)", "vlcWidRendering": "libVLC - رندر با شناسه بومی پنجره", + "ffmpegSoftwareRendering": "FFmpeg", + "downloadFfmpegLibs": "دانلود کتابخانه‌های FFmpeg (برای پخش‌کننده ویدئوی FFmpeg)", "subtitleGridEnterKeyAction": "عملکرد کلید Enter در جدول زیرنویس", "subtitleSingleClickAction": "عملکرد تک‌کلیک در جدول زیرنویس", "subtitleDoubleClickAction": "عملکرد دوبارکلیک در جدول زیرنویس", @@ -3043,6 +3620,7 @@ "subtitleGridFormattingShowFormatting": "نمایش قالب‌بندی", "subtitleGridFormattingShowTags": "نمایش تگ‌ها", "subtitleGridFormattingHideTags": "پنهان کردن تگ‌ها", + "subtitleGridFormattingShowFormattingKeepTags": "نمایش قالب‌بندی، حفظ تگ‌های غیربصری", "waveformParagraphBackgroundColor": "رنگ پس‌زمینه زیرنویس در موج‌نما", "waveformParagraphSelectedBackgroundColor": "رنگ پس‌زمینه زیرنویس انتخاب‌شده در موج‌نما", "waveformAllowOverlap": "اجازه هم‌پوشانی (هنگام جابه‌جایی/تغییر اندازه)", @@ -3058,9 +3636,12 @@ "splitOddLinesAction": "عملکرد تقسیم خطوط فرد", "ocrUseWordSplitList": "OCR: استفاده از فهرست تقسیم کلمات", "ocrGuessUnknownWords": "OCR: تلاش برای حدس زدن کلمات ناشناخته", + "spellCheckRememberUseAlwaysList": "بررسی املایی: به‌خاطر سپردن فهرست \"همیشه استفاده شود\"", + "fixShortDisplayTimesAllowMoveStartTime": "اصلاح خطاهای رایج: اصلاح زمان‌های نمایش کوتاه ممکن است زمان شروع را جابجا کند", "speechToTextSelectedLinesPromptFirstTimeOnly": "گفتار به متن: خطوط انتخاب‌شده، پرسش زبان/موتور فقط بار اول", "multipleReplaceShowDotDotDotButtons": "جایگزینی چندگانه: نمایش دکمه‌های منوی زمینه", "gridFocusTextboxAfterInsertNew": "جدول: تمرکز روی کادر متن پس از درج زیرنویس جدید", + "undoRedoGoToChangedLine": "لغو/انجام مجدد: رفتن به خط تغییرکرده (و موقعیت ویدئو)", "musicSymbol": "نماد موسیقی", "musicSymbolsToReplace": "نمادهای موسیقی برای جایگزینی (جداشده با ویرگول)", "textToSpeechPromptMergeContinuationLines": "تبدیل متن به گفتار: پرسش برای ادغام خطوط ادامه", @@ -3069,6 +3650,7 @@ "useFocusedButtonBackgroundColor": "استفاده از رنگ پس‌زمینه دکمه دارای تمرکز", "focusedButtonBackgroundColor": "رنگ پس‌زمینه دکمه دارای تمرکز", "forceCrLfOnSave": "اجبار CR+LF هنگام ذخیره (فایل‌های متنی زیرنویس)", + "linuxClipboardUseExternalTool": "کپی متن از طریق xclip/wl-copy (حفظ کاراکترهای دارای اعراب)", "showFormatLimitWarning": "هشدار هنگام ذخیره اگر خطوط از محدودیت قالب عبور کنند (مثلاً ۳۲ نویسه/خط در SCC)", "textBoxButtonShowAutoBreak": "کادر متن: نمایش دکمه شکستن خودکار", "textBoxButtonShowUnbreak": "کادر متن: نمایش دکمه یک‌خطی کردن", @@ -3080,12 +3662,18 @@ "waveformDoubleClickAction": "عملکرد دوبارکلیک موج‌نما (پس از عملکرد تک‌کلیک)", "allSettings": "همه تنظیمات", "uiScale": "مقیاس رابط کاربری (%)", + "fontScale": "مقیاس فونت (%)", + "textSelectionOpacity": "شفافیت انتخاب متن (%)", "waveformToolbarItems": "موارد نوار ابزار موج‌نما", "matchIconColorToDarkTheme": "هماهنگ کردن رنگ آیکون با رنگ پیش‌زمینه تم تیره", "subtitlePreviewProperties": "ویژگی‌های پیش‌نمایش زیرنویس", "usePositionFromSubtitleFile": "استفاده از موقعیت موجود در فایل زیرنویس (TTML/PAC/EBU STL)", "marginIsPartOfSubtitleArea": "حاشیه بخشی از محدوده زیرنویس است", "textJustify": "تراز دوطرفه خطوط", + "deinterlace": "حذف درهم‌بافتگی ویدئو (Deinterlace)", + "deinterlaceOff": "خاموش", + "deinterlaceAuto": "خودکار (فقط فریم‌های درهم‌بافته)", + "deinterlaceAlways": "همیشه", "pixelWidthInfo": "خطوط سبز = حداکثر عرض مجاز | ناحیه قرمز = متن از حد مجاز بیشتر است", "spellCheckEngineHunSpelll": "Hunspell", "spellCheckEngineMsWord": "MS Word", @@ -3094,7 +3682,16 @@ "waveformColorTheme": "تم رنگی:", "waveformSaveAsCustomTheme": "ذخیره به‌عنوان تم سفارشی...", "waveformLoadThemeDotDotDot": "بارگذاری تم...", - "waveformExportThemeDotDotDot": "خروجی گرفتن از تم..." + "waveformExportThemeDotDotDot": "خروجی گرفتن از تم...", + "waveformSaveCustomThemeTitle": "ذخیره پوسته سفارشی", + "waveformThemeFileType": "پوسته شکل موج Subtitle Edit", + "waveformThemeHighContrast": "کنتراست بالا", + "waveformThemeOceanBlue": "آبی اقیانوسی", + "waveformThemeWarmSunset": "غروب گرم", + "waveformThemeForest": "جنگل", + "waveformThemeMidnightPurple": "بنفش نیمه‌شب", + "waveformThemeRetroAmber": "کهربایی رترو", + "waveformThemeIce": "یخ" }, "shortcuts": { "searchShortcuts": "جستجوی میانبرها...", @@ -3105,11 +3702,34 @@ "categoryVideo": "ویدئو", "categorySync": "همگام‌سازی", "categoryAi": "هوش مصنوعی", + "categoryCustom": "سفارشی", + "editCustomShortcut": "ویرایش میانبر سفارشی", + "customShortcutTextBoxKeyHint": "اینجا از کلیدی همراه با Ctrl/Alt استفاده کنید - یک کلید ساده دیگر در جعبه متن تایپ نمی‌کند.", + "customShortcutNumberX": "میانبر سفارشی #{0}", + "customShortcutNumberXY": "میانبر سفارشی #{0}: {1}", + "customShortcutStep": "مرحله", + "customShortcutSteps": "مراحل", + "customShortcutStepRunCommand": "اجرای فرمان", + "customShortcutStepInsertText": "درج متن", + "customShortcutStepReplace": "جستجو و جایگزینی", + "customShortcutInsertAtCursor": "در محل مکان‌نما", + "customShortcutInsertAtStart": "در ابتدای متن", + "customShortcutInsertAtEnd": "در انتهای متن", + "customShortcutSummaryRunCommandX": "اجرا: {0}", + "customShortcutSummaryInsertXY": "درج \"{0}\" ({1})", + "customShortcutSummaryReplaceXY": "جایگزینی \"{0}\" با \"{1}\"", + "customShortcutSummaryReplaceRegexXY": "جایگزینی عبارت باقاعده \"{0}\" با \"{1}\"", + "customShortcutPickCommand": "لطفاً یک فرمان برای اجرا انتخاب کنید.", + "customShortcutEnterText": "لطفاً متن مورد نظر برای درج را وارد کنید.", + "customShortcutEnterFind": "لطفاً متن مورد نظر برای جستجو را وارد کنید.", + "customShortcutInvalidRegexX": "عبارت باقاعده نامعتبر: {0}", + "customShortcutStepsHint": "مراحل از بالا به پایین اجرا می‌شوند. مراحل متنی روی خطوط انتخاب‌شده کار می‌کنند؛ فرمان‌هایی که پنجره‌ای باز می‌کنند پس از بسته شدن آن ادامه می‌یابند.", "activeIn": "فعال در", "activeInEverywhere": "همه‌جا", "generalMergeSelectedLinesAndUnbreak": "ادغام خطوط انتخاب‌شده و یک‌خطی کردن", "generalMergeSelectedLinesAndUnbreakCjk": "ادغام خطوط انتخاب‌شده و یک‌خطی کردن بدون فاصله (CJK)", "generalUnbreakNoSpaceCjk": "یک‌خطی کردن بدون فاصله (CJK)", + "generalToggleBreak": "تغییر وضعیت شکست/حذف شکستگی", "generalMergeSelectedLinesBilingual": "ادغام خطوط انتخاب‌شده به‌صورت دوزبانه", "generalToggleTranslationMode": "تغییر حالت ترجمه", "generalToggleTranslationAndOriginalInPreviews": "تغییر بین ترجمه و متن اصلی در پیش‌نمایش ویدئو/صدا", @@ -3126,10 +3746,12 @@ "generalToggleItalic": "تغییر حالت ایتالیک", "generalToggleBold": "تغییر حالت پررنگ", "generalToggleUnderline": "تغییر حالت زیرخط", + "generalToggleBox": "تغییر وضعیت کادر (EBU STL)", "fileOpen": "باز کردن", "fileOpenKeepVideo": "باز کردن (با حفظ ویدئو)", "fileSave": "ذخیره", "fileSaveAs": "ذخیره به‌عنوان", + "fileSaveOriginal": "ذخیره نسخه اصلی", "fileCompare": "مقایسه", "fileStatistics": "آمار", "fileImportPlainText": "وارد کردن متن ساده", @@ -3141,6 +3763,7 @@ "fillSelectedLinesWithClipboard": "پر کردن خطوط انتخاب‌شده با متن کلیپ‌بورد", "listInverseSelection": "معکوس کردن انتخاب", "listDeleteSelection": "حذف انتخاب", + "deleteSelectionEverywhere": "حذف انتخاب (همه‌جا)", "rippleDeleteSelection": "حذف انتخاب و جابه‌جایی موارد بعدی", "assigned": "اختصاص‌یافته", "unassigned": "اختصاص‌نیافته", @@ -3149,6 +3772,7 @@ "detectKey": "تشخیص کلید", "control": "Ctrl", "alt": "Alt", + "altGr": "AltGr", "win": "Win", "shift": "Shift", "controlMac": "⌃", @@ -3159,6 +3783,11 @@ "resetShortcutsDetail": "آیا می‌خواهید همه میانبرها به مقادیر پیش‌فرض بازنشانی شوند؟", "togglePlayPause": "تغییر بین پخش و مکث", "toggleLockTimeCodes": "قفل/باز کردن قفل کدهای زمانی", + "toggleTimeCodeMode": "تغییر حالت کد زمانی (زمان/فریم‌ها/شماره فریم‌ها)", + "toggleScreenPrivacy": "تغییر وضعیت حریم خصوصی صفحه (پنهان کردن نام فایل‌ها، سپس متن‌ها نیز)", + "screenPrivacyFileNamesHidden": "حریم خصوصی صفحه: نام فایل‌ها پنهان است", + "screenPrivacyFileNamesAndTextsHidden": "حریم خصوصی صفحه: نام فایل‌ها و متن‌ها پنهان است", + "screenPrivacyOff": "حریم خصوصی صفحه: خاموش", "duplicateSelectedLines": "تکثیر خطوط انتخاب‌شده", "sourceView": "نمایش منبع", "addOrEditBookmark": "افزودن یا ویرایش نشانک", @@ -3177,6 +3806,7 @@ "moveFirstWordFromNextLineUpCurrentSubtitle": "انتقال اولین کلمه از خط بعدی به خط بالا (زیرنویس فعلی)", "moveTextFromCursorToNextAndGoToNext": "انتقال متن بعد از مکان‌نما به زیرنویس بعدی و رفتن به آن", "moveTextFromCursorToNextAndGoToNextAndPlay": "انتقال متن بعد از مکان‌نما به زیرنویس بعدی، رفتن به آن و پخش", + "moveTextFromCursorToNextAndGoToNextAndPlayAndPause": "انتقال متن بعد از مکان‌نما به زیرنویس بعدی، رفتن به بعدی، پخش آن و توقف موقت در انتهای آن", "breakAtFirstSpaceFromCursor": "شکستن خط در نخستین فاصله از موقعیت مکان‌نما", "breakAtFirstSpaceFromCursorAndGoToNext": "شکستن خط در نخستین فاصله از موقعیت مکان‌نما و رفتن به بعدی", "toggleFocusGridAndWaveform": "تغییر فوکوس بین فهرست زیرنویس و شکل موج/طیف‌نگار", @@ -3206,6 +3836,7 @@ "listViewColumnTextUp": "ستون: انتقال متن به بالا", "listViewColumnTextDown": "ستون: انتقال متن به پایین", "autoTranslateSelectedLines": "ترجمه خودکار خطوط انتخاب‌شده...", + "autoTranslateSelectedLinesNoPrompt": "ترجمه خودکار خطوط انتخاب‌شده (بدون پرسش، استفاده از آخرین موتور/زبان‌ها)", "setAssaResolution": "تنظیم وضوح ASSA (PlayResX/PlayResY)", "setShortcutForX": "تنظیم میانبر برای «{0}»", "commandFileNewKeepVideo": "جدید (با حفظ ویدئو)", @@ -3260,6 +3891,7 @@ "textBoxItalic": "کادر متن: ایتالیک", "textBoxBold": "کادر متن: پررنگ", "textBoxUnderline": "کادر متن: زیرخط", + "textBoxBox": "کادر جعبه متن (EBU STL)", "resetWaveformZoomAndSpeed": "بازنشانی بزرگ‌نمایی شکل موج و سرعت پخش", "togglePlaybackSpeed": "تغییر سرعت پخش", "playbackSpeedSlower": "کاهش سرعت پخش", @@ -3271,6 +3903,7 @@ "seekSilenceForward": "یافتن سکوت به جلو", "waveformGuessStart": "حدس زمان شروع از روی شکل موج", "waveformGuessEnd": "حدس زمان پایان از روی شکل موج", + "waveformGuessStartAndEnd": "حدس زمان شروع و پایان از شکل موج", "setVideoPositionCurrentSubtitleStart": "تنظیم موقعیت ویدئو روی شروع خط فعلی", "goToSubtitlePositionAndPause": "رفتن به موقعیت زیرنویس و مکث", "setVideoPositionCurrentSubtitleEnd": "تنظیم موقعیت ویدئو روی پایان خط فعلی", @@ -3284,6 +3917,14 @@ "surroundWith": "احاطه کردن با...", "surroundWithXY": "احاطه کردن با {0}/{1}", "surroundWithNumberX": "احاطه کردن با #{0}", + "surroundWithBehavior": "رفتار", + "surroundWithBehaviorToggle": "تغییر وضعیت (افزودن، یا حذف در صورت وجود)", + "surroundWithBehaviorAdd": "افزودن (هر بار)", + "surroundWithBehaviorRemove": "حذف (همه)", + "surroundWithBehaviorRemoveOnce": "حذف (هر بار یکی)", + "surroundWithWorksOn": "اعمال روی", + "surroundWithScopeSelectionOrText": "انتخاب، در غیر این صورت کل متن", + "surroundWithScopeEachLine": "هر خط", "searchVia": "جستجو از طریق", "searchViaX": "جستجو از طریق {0}", "searchViaNumberX": "جستجو از طریق #{0}", @@ -3311,6 +3952,11 @@ "copySubtitlePathToClipboard": "کپی مسیر زیرنویس در کلیپ‌بورد", "copySubtitleOriginalPathToClipboard": "کپی مسیر نسخه اصلی زیرنویس در کلیپ‌بورد", "focusTextBox": "قرار دادن فوکوس روی کادر متن", + "focusSubtitleListView": "تمرکز روی نمای فهرست زیرنویس", + "focusWaveform": "تمرکز روی شکل موج", + "focusOriginalTextBox": "تمرکز روی جعبه متن اصلی", + "textBoxGoToStart": "جعبه متن، رفتن به ابتدا", + "textBoxGoToEnd": "جعبه متن، رفتن به انتها", "sortByNumber": "مرتب‌سازی بر اساس شماره", "sortByStartTime": "مرتب‌سازی بر اساس زمان «نمایش»", "sortByEndTime": "مرتب‌سازی بر اساس زمان «مخفی شدن»", @@ -3348,9 +3994,9 @@ "selectionToSentenceCase": "کادر متن: تبدیل انتخاب به حالت جمله", "selectionToggleCasing": "کادر متن: تغییر حالت حروف (بزرگ → کوچک → عنوان)", "googleIt": "جستجو در گوگل (متن انتخاب‌شده)", - "setActorXY": "تنظیم گوینده {0}: {1}", + "setActorX": "تنظیم بازیگر: {0}", "setNewActor": "تنظیم گوینده جدید...", - "setActorSlotXTitle": "جایگاه گوینده {0}", + "setStyleX": "تنظیم استایل: {0}", "toggleDialogDashes": "تغییر وضعیت خط تیره دیالوگ", "mergeWithLineBeforeAsDialog": "ادغام با خط قبل به‌عنوان دیالوگ", "mergeWithLineAfterAsDialog": "ادغام با خط بعد به‌عنوان دیالوگ", @@ -3361,7 +4007,23 @@ "moveStartOneFrameBackKeepGapPrev": "انتقال شروع یک فریم به عقب (حفظ فاصله با قبلی در صورت کم بودن)", "moveStartOneFrameForwardKeepGapPrev": "انتقال شروع یک فریم به جلو (حفظ فاصله با قبلی در صورت کم بودن)", "moveEndOneFrameBackKeepGapNext": "انتقال پایان یک فریم به عقب (حفظ فاصله با بعدی در صورت کم بودن)", - "moveEndOneFrameForwardKeepGapNext": "انتقال پایان یک فریم به جلو (حفظ فاصله با بعدی در صورت کم بودن)" + "moveEndOneFrameForwardKeepGapNext": "انتقال پایان یک فریم به جلو (حفظ فاصله با بعدی در صورت کم بودن)", + "moveStartXMsBack": "جابجایی شروع X میلی‌ثانیه به عقب (X در تنظیمات تعیین می‌شود، همچنین در همگام‌سازی بصری)", + "moveStartXMsForward": "جابجایی شروع X میلی‌ثانیه به جلو (X در تنظیمات تعیین می‌شود، همچنین در همگام‌سازی بصری)", + "moveEndXMsBack": "جابجایی پایان X میلی‌ثانیه به عقب (X در تنظیمات تعیین می‌شود، همچنین در همگام‌سازی بصری)", + "moveEndXMsForward": "جابجایی پایان X میلی‌ثانیه به جلو (X در تنظیمات تعیین می‌شود، همچنین در همگام‌سازی بصری)", + "moveSelectedLinesXMsBack": "جابجایی خطوط انتخاب‌شده X میلی‌ثانیه به عقب (X در تنظیمات تعیین می‌شود)", + "moveSelectedLinesXMsForward": "جابجایی خطوط انتخاب‌شده X میلی‌ثانیه به جلو (X در تنظیمات تعیین می‌شود)", + "moveSelectedLinesAndForwardXMsBack": "جابجایی خطوط انتخاب‌شده و همه خطوط بعدی X میلی‌ثانیه به عقب (X در تنظیمات تعیین می‌شود)", + "moveSelectedLinesAndForwardXMsForward": "جابجایی خطوط انتخاب‌شده و همه خطوط بعدی X میلی‌ثانیه به جلو (X در تنظیمات تعیین می‌شود)", + "moveAllLinesXMsBack": "جابجایی همه خطوط X میلی‌ثانیه به عقب (X در تنظیمات تعیین می‌شود)", + "moveAllLinesXMsForward": "جابجایی همه خطوط X میلی‌ثانیه به جلو (X در تنظیمات تعیین می‌شود)", + "moveSelectedLinesCustomMsBack": "جابجایی خطوط انتخاب‌شده، میلی‌ثانیه سفارشی ({0:#,###,##0}) به عقب، {1}", + "moveSelectedLinesCustomMsForward": "جابجایی خطوط انتخاب‌شده، میلی‌ثانیه سفارشی ({0:#,###,##0}) به جلو، {1}", + "moveSelectedLinesAndForwardCustomMsBack": "جابجایی خطوط انتخاب‌شده و همه خطوط بعدی، میلی‌ثانیه سفارشی ({0:#,###,##0}) به عقب، {1}", + "moveSelectedLinesAndForwardCustomMsForward": "جابجایی خطوط انتخاب‌شده و همه خطوط بعدی، میلی‌ثانیه سفارشی ({0:#,###,##0}) به جلو، {1}", + "moveAllLinesCustomMsBack": "جابجایی همه خطوط، میلی‌ثانیه سفارشی ({0:#,###,##0}) به عقب، {1}", + "moveAllLinesCustomMsForward": "جابجایی همه خطوط، میلی‌ثانیه سفارشی ({0:#,###,##0}) به جلو، {1}" }, "wordLists": { "title": "لیست کلمات", @@ -3406,7 +4068,11 @@ "applyToSelectedLinesX": "خطوط انتخاب‌شده ({0})", "applyToAllLinesX": "تمام خطوط ({0})", "updateAllXAvailable": "بروزرسانی همه موارد موجود ({0})", - "updatingXOfY": "در حال بروزرسانی {0} از {1}..." + "updatingXOfY": "در حال بروزرسانی {0} از {1}...", + "doNotAskAgain": "دوباره نپرس (قابل تغییر در مدیریت پلاگین‌ها)", + "applyPluginsTo": "اعمال پلاگین‌ها روی", + "askEachTime": "هر بار پرسیده شود", + "allLines": "همه خطوط" }, "help": { "aboutSubtitleEdit": "درباره Subtitle Edit", @@ -3450,6 +4116,8 @@ "nOcrDatabase": "پایگاه داده nOCR", "drawMode": "حالت رسم:", "addNewCharcter": "افزودن نویسهٔ جدید", + "addNOcrCharacterTitle": "افزودن کاراکتر nOCR برای خط {0}، کاراکتر {1} از {2} با استفاده از پایگاه داده \"{3}\"", + "addBinaryOcrCharacterTitle": "افزودن کاراکتر OCR باینری برای خط {0}، کاراکتر {1} از {2} با استفاده از پایگاه داده \"{3}\"", "lineIndexX": "خط {0}", "inspectNOcrAdditions": "بررسی موارد جدید nOCR", "ocrSelectedLines": "OCR روی خطوط انتخاب‌شده", @@ -3498,6 +4166,8 @@ "binaryImageCompareInspectImageMatches": "«مقایسهٔ تصاویر دودویی» - بررسی تطبیق‌های تصویر", "saveBlankTextTitle": "متن خالی ذخیره شود؟", "saveBlankTextPrompt": "متن خالی برای تصویر ذخیره شود؟", + "drawnLinesDoNotMatchTitle": "خطوط با تصویر مطابقت ندارند", + "drawnLinesDoNotMatchPrompt": "برخی از خطوط ترسیم‌شده با تصویر مطابقت ندارند (خطوط سبز باید روی کاراکتر و خطوط قرمز باید خارج از آن باشند)، بنابراین این کاراکتر تشخیص داده نخواهد شد.\n\nبه هر حال ذخیره شود؟", "yesAndNeverAskAgain": "بله و دیگر نپرس", "importTextFromSubtitleDotDotDot": "وارد کردن متن از زیرنویس...", "importTextFromSubtitleOverwritePrompt": "برخی خطوط از قبل متن OCR دارند. متن موجود بازنویسی شود؟", @@ -3556,9 +4226,26 @@ "crispEmbedSettingsTitle": "تنظیمات CrispEmbed", "crispEmbedDescription": "موتور OCR محلی با چندین مدل پشتیبان - موتور و مدل‌ها را از اینجا دانلود کنید.", "trainNOcrDatabase": "آموزش پایگاه داده nOCR...", + "optimizeNOcrDatabase": "بهینه‌سازی پایگاه داده nOCR...", + "optimizeNOcrDatabaseQuestionXY": "خطوطی که کاراکترها را از کاراکترهای مشابه در پایگاه داده nOCR \"{0}\" متمایز نمی‌کنند حذف شوند؟\n\nاین کار پایگاه داده را کوچک‌تر و OCR را سریع‌تر می‌کند، معمولاً با خطای کمتر. تا یک دقیقه طول می‌کشد.\n\nیک نسخه پشتیبان با نام \"{1}\" ذخیره می‌شود.", + "optimizingNOcrDatabaseXY": "در حال بهینه‌سازی پایگاه داده nOCR... {0}/{1}", + "nOcrDatabaseOptimizedXYZ": "پایگاه داده nOCR \"{0}\" بهینه شد: {1:#,##0} -> {2:#,##0} خط.", "startTraining": "شروع آموزش", "abortTraining": "لغو آموزش", "trainingOptions": "گزینه‌های آموزش", + "trainNOcrSubtitle": "کاراکترها را با فونت‌های نصب‌شده رندر می‌کند و شکل آن‌ها را به nOCR آموزش می‌دهد. فونت‌هایی را که زیرنویس‌های شما استفاده می‌کنند انتخاب کنید.", + "xFontsSelected": "{0:#,##0} انتخاب‌شده", + "subtitleFonts": "فونت‌های زیرنویس", + "emptyDatabase": "پایگاه داده خالی", + "startFromDatabase": "شروع از", + "startFromDatabaseHint": "کاراکترهای آموزش‌دیده را به یک کپی از پایگاه داده موجود اضافه کنید (توصیه می‌شود) یا از یک پایگاه داده خالی شروع کنید", + "fontSizes": "اندازه‌های فونت", + "fontSizesHint": "اندازه‌های فونت به پیکسل برای رندر، جداشده با کاما، مثلاً \"30, 40\"", + "numberOfLineSegmentsHint": "خطوط بررسی‌شده برای هر کاراکتر - تعداد بیشتر سخت‌گیرانه‌تر اما کندتر است", + "enterDatabaseName": "یک نام برای پایگاه داده وارد کنید", + "trainingFontXSizeYCharacterZ": "در حال آموزش \"{0}\" در {1} px: {2}", + "xLearned": "{0:#,##0} آموخته شد", + "xSkipped": "{0:#,##0} رد شد", "charactersToTrain": "نویسه‌های مورد آموزش", "letterCombinationsToTrain": "ترکیب‌های حروفی که ممکن است در یک تصویر به هم متصل شوند", "importCharactersFromSubtitleFile": "وارد کردن نویسه‌ها از فایل زیرنویس...", @@ -3585,6 +4272,8 @@ "drawZoomOut": "کوچک‌نمایی (Ctrl+-)", "drawResetView": "بازنشانی نما (Ctrl+0)", "drawToggleGrid": "نمایش/عدم نمایش شبکه (Ctrl+G)", + "drawTogglePreview": "تغییر وضعیت پیش‌نمایش (F9)", + "drawPreviewFailed": "پیش‌نمایش ناموفق بود - ffmpeg همراه با libass (فیلتر subtitles) لازم است", "drawCopyToClipboard": "کپی در کلیپ‌بورد (Ctrl+C)", "drawShapes": "اشکال", "drawSelectedPoint": "نقطه انتخاب‌شده", @@ -3592,6 +4281,81 @@ "drawSelectedLayer": "لایه انتخاب‌شده", "drawToolX": "ابزار: {0}", "drawHelpText": "برای افزودن نقاط کلیک کنید • Enter/F8 برای بستن شکل • Shift+کشیدن برای جابه‌جایی نما • Ctrl+اسکرول برای بزرگ‌نمایی", + "drawMoveToLayer": "انتقال به لایه", + "drawFlipHorizontal": "قرینه افقی", + "drawFlipVertical": "قرینه عمودی", + "drawHideShape": "پنهان کردن شکل", + "drawShowShape": "نمایش شکل", + "drawHideLayer": "پنهان کردن لایه", + "drawShowLayer": "نمایش لایه", + "drawDeleteLayer": "حذف لایه", + "drawDeletePoint": "حذف نقطه", + "drawCancelDrawing": "لغو ترسیم", + "drawLayerX": "لایه {0}", + "drawImportSvg": "وارد کردن تصویر SVG...", + "drawRotateClockwise": "چرخش ۹۰° در جهت عقربه‌های ساعت", + "drawConvertToCurve": "تبدیل بخش به منحنی", + "drawBackground": "پس‌زمینه", + "drawPosition": "موقعیت", + "drawZoom": "بزرگ‌نمایی", + "drawShapeTool": "ابزار شکل", + "drawColorPickerTool": "برداشتن رنگ از یک شکل یا پس‌زمینه", + "drawShapeLibrary": "کتابخانه شکل‌ها", + "drawShapeLibraryHint": "برای قرار دادن یک شکل روی بوم بکشید، یا برای اندازه پیش‌فرض کلیک کنید. Shift تناسب آن را حفظ می‌کند.", + "drawAddToLibrary": "افزودن به کتابخانه شکل‌ها...", + "drawRemoveFromLibrary": "حذف از کتابخانه شکل‌ها", + "drawShapeName": "نام شکل", + "drawCategorySpeechBubbles": "حباب‌های گفتگو", + "drawCategoryArrows": "پیکان‌ها", + "drawCategoryBasicShapes": "شکل‌های پایه", + "drawCategorySymbols": "نمادها", + "drawCategoryMyShapes": "شکل‌های من", + "drawShapeSpeechBubble": "حباب گفتگو", + "drawShapeSpeechBubbleRight": "حباب گفتگو، دنباله به راست", + "drawShapeSpeechBubbleUp": "حباب گفتگو، دنباله به بالا", + "drawShapeOvalBubble": "حباب گفتگوی بیضی", + "drawShapeBoxBubble": "حباب گفتگوی جعبه‌ای", + "drawShapeThoughtBubble": "حباب فکر", + "drawShapeShoutBubble": "حباب فریاد", + "drawShapeCaptionBox": "کادر عنوان", + "drawShapeArrow": "پیکان", + "drawShapeDoubleArrow": "پیکان دوطرفه", + "drawShapeChevron": "شورون", + "drawShapeCurvedArrow": "پیکان منحنی", + "drawShapeRoundedRectangle": "مستطیل گوشه‌گرد", + "drawShapeTriangle": "مثلث", + "drawShapeDiamond": "لوزی", + "drawShapePentagon": "پنج‌ضلعی", + "drawShapeHexagon": "شش‌ضلعی", + "drawShapeOctagon": "هشت‌ضلعی", + "drawShapeStar": "ستاره", + "drawShapeRing": "حلقه", + "drawShapePlus": "به‌علاوه", + "drawShapeBanner": "بنر", + "drawShapeHeart": "قلب", + "drawShapeCheck": "علامت تیک", + "drawShapeMusicNote": "نت موسیقی", + "drawShapeMusicNotes": "نت‌های موسیقی", + "drawShapeLightning": "صاعقه", + "drawShapeCloud": "ابر", + "drawShapeMoon": "ماه", + "drawShapeBadge": "نشان", + "drawSize": "اندازه", + "drawLayer": "لایه", + "drawBackgroundVideoFrame": "فریم ویدئو (موقعیت فعلی)", + "drawBackgroundVideoFrameAt": "فریم ویدئو در...", + "drawBackgroundImage": "فایل تصویر...", + "drawBackgroundNone": "بدون پس‌زمینه", + "drawBackgroundStretch": "کشیدن تصویر به اندازه بوم", + "drawBackgroundOpacity": "شفافیت پس‌زمینه", + "drawBackgroundVideoFrameFailed": "دریافت فریم ویدئو در {0} ممکن نشد.", + "drawImages": "تصاویر", + "drawConvertToLine": "تبدیل بخش به خط", + "drawConvertShapeToCurves": "تبدیل همه خطوط به منحنی", + "drawConvertShapeToLines": "تبدیل همه منحنی‌ها به خط", + "drawRotateCounterClockwise": "چرخش ۹۰° خلاف جهت عقربه‌های ساعت", + "drawSvgImages": "تصاویر SVG", + "drawSvgNoShapes": "هیچ شکل توپُر یا خط‌کشی در فایل SVG یافت نشد.", "progressBarTitle": "نوار پیشرفت ASSA", "progressBarSettings": "نوار پیشرفت", "mouseOverColor": "رنگ هنگام قرار گرفتن ماوس", @@ -3688,13 +4452,26 @@ "graphics": "گرافیک", "copyToStorageStyles": "کپی در سبک‌های ذخیره‌شده", "copyToFileStyles": "کپی در سبک‌های فایل", + "styleXAlreadyExistsInStorage": "استایلی با نام \"{0}\" از قبل در فضای ذخیره‌سازی وجود دارد.\n\nبازنویسی شود، یا هر دو حفظ شوند (کپی نام جدیدی می‌گیرد)؟", + "styleXAlreadyExistsInFile": "استایلی با نام \"{0}\" از قبل در فایل وجود دارد.\n\nبازنویسی شود، یا هر دو حفظ شوند (کپی نام جدیدی می‌گیرد)؟", + "styleNameCannotBeEmpty": "نام استایل نمی‌تواند خالی باشد.", + "styleNameXIsUsedMoreThanOnce": "نام استایل \"{0}\" توسط بیش از یک استایل در فایل استفاده شده است.", + "overwrite": "بازنویسی", + "keepBoth": "حفظ هر دو", + "doThisForAllConflictingStyles": "انجام این کار برای همه استایل‌های متعارض", "setStyleAsDefault": "تنظیم سبک به‌عنوان پیش‌فرض", "takeUsagesFromDotDotDot": "گرفتن موارد استفاده از...", "replaceStyleWithDotDotDot": "جایگزینی سبک با...", "noAttachmentsFound": "هیچ پیوستی در فایل ASSA انتخاب‌شده یافت نشد.", "deleteStyleQuestion": "سبک حذف شود؟", "deleteStylesQuestion": "سبک‌ها حذف شوند؟", + "deleteXStylesQuestion": "آیا می‌خواهید {0} استایل را حذف کنید؟", + "deleteXStylesFromStorageQuestion": "آیا می‌خواهید {0} استایل را از فضای ذخیره‌سازی حذف کنید؟", + "deleteStyleXFromStorageQuestion": "آیا می‌خواهید استایل \"{0}\" را از فضای ذخیره‌سازی حذف کنید؟", + "deleteStyleXFromFileQuestion": "آیا می‌خواهید استایل \"{0}\" را از فایل فعلی حذف کنید؟", "openStyleImportFile": "باز کردن فایل زیرنویس برای وارد کردن سبک‌ها", + "styleImportFiles": "فایل‌های استایل (.ass، .sty، دسته‌بندی‌های Subtitle Edit 4 .template)", + "ssaStyleImportFiles": "فایل‌های استایل (.ssa، .ass، .sty)", "primary": "اصلی", "secondary": "ثانویه", "applyOverrideTags": "اعمال تگ‌های بازنویسی", diff --git a/src/ui/Assets/Languages/Polish.json b/src/ui/Assets/Languages/Polish.json index 943fb06460b..573d7c67c52 100644 --- a/src/ui/Assets/Languages/Polish.json +++ b/src/ui/Assets/Languages/Polish.json @@ -1,6 +1,6 @@ { "title": "Subtitle Edit", - "version": "v5.3.0-rc2", + "version": "v5.3.0", "translatedBy": "Adam Malich (ostatnia aktualizacja: 11.10.2026)", "cultureName": "pl-PL", "general": { @@ -175,11 +175,11 @@ "delete": "Usuń", "deleteAtPosition": "Usuń napisy w pozycji wideo", "deleteCurrentLine": "Usuń bieżącą linię", - "deleteFirstLines": "Usuń pierwsze linie ", - "deleteLastLines": "Usuń ostatnie linie ", + "deleteFirstLines": "Usuń pierwsze linie", + "deleteLastLines": "Usuń ostatnie linie", "deleteLineXPrompt": "Usunąć linię numer {0}?", "deleteLines": "Usuń linie", - "deleteLinesContainingText": "Usuń linie zawierające tekst ", + "deleteLinesContainingText": "Usuń linie zawierające tekst", "deleteXLinesPrompt": "Usunąć {0} linii?", "description": "Opis", "dictionary": "Słownik", @@ -210,7 +210,7 @@ "editing": "Edytowanie", "effect": "Efekt", "enabled": "Aktywne", - "encoding": "Kodowanie ", + "encoding": "Kodowanie", "endTime": "Czas ukrycia", "engine": "Silnik", "enterProfileName": "Wstaw nazwę profilu", @@ -244,7 +244,7 @@ "fileSavedToX": "Plik zapisano w {0}", "fileXAlreadyExists": "Plik \"{0}\" już istnieje.", "fileXOfY": "Plik {0} z {1}", - "filter": "Filtruj ", + "filter": "Filtruj", "filterByLayer": "Filtruj według warstwy", "find": "Znajdź", "findNext": "Znajdź następny", @@ -265,13 +265,13 @@ "footer": "Stopka", "forced": "Wymuszone", "foreground": "Pierwszy plan", - "format": "Format ", + "format": "Format", "forward": "przewiń do przodu", "foundNoMatches": "Nie znaleziono żadnych dopasowań", "foundOneMatch": "Znaleziono jedno dopasowanie", "foundXInLineYZ": "Znaleziono \"{0}\" w linii {1}, pozycja {2}", "foundXMatches": "Znaleziono {0} dopasowań", - "frameRate": "Liczba klatek na sekundę ", + "frameRate": "Liczba klatek na sekundę", "frames": "Klatki", "from": "Z", "fromCurrentVideoPosition": "od bieżącej pozycji wideo", @@ -296,7 +296,7 @@ "hex": "Hex", "hide": "Ukryj", "history": "Historia", - "horizontal": "W poziomie ", + "horizontal": "W poziomie", "id": "ID", "image": "Obraz", "imageSaved": "Obraz zapisany", @@ -324,7 +324,7 @@ "italic": "Kursywa", "keepExistingTimeCodes": "Zachowaj istniejące kody czasowe (nie dodawaj przesunięcia wideo)", "keyFile": "Plik klucza", - "language": "Język ", + "language": "Język", "languagePostFix": "Postfiks językowy (mkv/mp4/automatyczne tłumaczenie)", "layer": "Warstwa", "layerFilterOn": "Filtr warstwowy włączony", @@ -402,7 +402,7 @@ "numberSymbol": "#", "ocrDotDotDot": "OCR...", "ocrPercentX": "OCR: {0}%", - "offset": "Przesunięcie ", + "offset": "Przesunięcie", "offsetTimeCodes": "Przesuń kody czasowe", "offsetX": "Przesunięcie X", "offsetY": "Przesunięcie Y", @@ -429,7 +429,7 @@ "outline": "Zarys", "outlineColor": "Kolor zarysu", "outlineWidth": "Szerokość zarysu", - "outputFolder": "Folder wyjściowy ", + "outputFolder": "Folder wyjściowy", "outputProperties": "Właściwości wyjścia", "overwriteExistingFiles": "Nadpisz istniejące pliki", "overwriteFilesInFolderX": "Nadpisz pliki w folderze: \"{0}\"", @@ -671,8 +671,8 @@ "sync": "Synchronizacja", "syntaxColoring": "Kolorowanie składni", "system": "Systemowy", - "targetEncoding": "Docelowe kodowanie ", - "targetFormat": "Docelowy format ", + "targetEncoding": "Docelowe kodowanie", + "targetFormat": "Docelowy format", "teletextAlignment": "Wyrównanie teletekstu", "teletextColor": "Kolor teletekstu", "teletextLine": "Linia teletekstu", @@ -736,7 +736,7 @@ "useSourceFolder": "Użyj folderu źródłowego", "useSourceResolution": "Użyj rozdzielczości źródłowej", "userName": "Nazwa użytkownika", - "vertical": "W pionie ", + "vertical": "W pionie", "video100MsBack": "Wideo, 100 milisekund do tyłu", "video100MsForward": "Wideo, 100 milisekund do przodu", "video500MsBack": "Wideo, 500 milisekund do tyłu", @@ -757,7 +757,7 @@ "videoCustom3ForwardAndPauseX": "Wideo, niestandardowe milisekundy ({0:#,###,##0}) przewiń do przodu i wstrzymaj, 3", "videoCustom4BackAndPauseX": "Wideo, niestandardowe milisekundy ({0:#,###,##0}) przewiń do tyłu i wstrzymaj, 4", "videoCustom4ForwardAndPauseX": "Wideo, niestandardowe milisekundy ({0:#,###,##0}) przewiń do przodu i wstrzymaj, 4", - "videoExtension": "Rozszerzenie pliku wideo ", + "videoExtension": "Rozszerzenie pliku wideo", "videoFile": "Plik wideo", "videoFileGenerated": "Wygenerowano plik wideo", "videoFileGeneratedX": "Wygenerowano plik wideo: \"{0}\"", @@ -786,7 +786,7 @@ "waveformCenterOnVideoPosition": "Środek przebiegu fali w pozycji wideo", "waveformPasteFromClipboard": "Wklej ze schowka", "waveformSpectrogram": "Przebieg fali/spektrogram", - "width": "Szerokość ", + "width": "Szerokość", "wordsPerMin": "Słów/min.", "pixelWidth": "Szerokość w pikselach", "wpm": "Słów/min.", @@ -1093,7 +1093,7 @@ "deleteTextAndShiftCellsUp": "Usuń tekst i przesuń komórki w górę", "endTimeMustBeAfterStartTime": "Czas zakończenia musi być po czasie rozpoczęcia.", "errorLoad7Zip": "Ten plik wydaje się być skompresowanym plikiem 7-Zip.\n\nSubtitle Edit nie może otwierać plików skompresowanych.", - "errorLoadBinaryZeroes": "Przepraszamy, ten plik zawiera tylko zera binarne!\n\nJeśli edytowałeś ten plik za pomocą Subtitle Edit, możesz znaleźć kopię zapasową za pomocą pozycji menu Plik -> Przywróć automatyczną kopię zapasową...", + "errorLoadBinaryZeroes": "Przepraszamy, ten plik zawiera tylko zera binarne!\n\nJeśli edytowałeś ten plik za pomocą Subtitle Edit, możesz znaleźć kopię zapasową za pomocą pozycji menu Plik -> Przywróć automatyczną kopię zapasową...", "errorLoadGZip": "Wygląda na to, że ten plik jest skompresowanym plikiem GZip.\n\nSubtitle Edit nie potrafi otwierać plików skompresowanych.", "errorLoadLargeFile": "Ten plik jest zbyt duży, aby można go było otworzyć jako plik z napisami.", "errorLoadVideoFilePrompt": "Ten plik wydaje się być plikiem wideo:\n{0}\n\nMożesz otworzyć wideo za pomocą \"Wideo -> Otwórz wideo\" lub przeciągając i upuszczając film na odtwarzacz wideo.\n\nCzy chcesz otworzyć ten plik jako plik wideo?", @@ -1345,7 +1345,8 @@ "forcedAlignerIsolatingSpeech": "Izolowanie mowy... {0}", "forcedAlignerIsolatingSpeechFailed": "Nie udało się wyodrębnić mowy – zamiast tego czasy zakończenia ustalono na podstawie czasu odczytu.", "forcedAlignerModel": "Model wyrównywacza", - "forcedAlignerDownloadEngine": "Pobierz / Zaktualizuj silnik..." + "forcedAlignerDownloadEngine": "Pobierz / Zaktualizuj silnik...", + "forcedAlignerEstimatedXOfY": "Nie udało się wyrównać {0} z {1} wierszy, więc zostały rozmieszczone według czasu czytania - ich kody czasowe są jedynie szacunkowe. Skrypt jest prawdopodobnie dłuższy niż to, co pada w filmie." }, "export": { "exportImagesProfiles": "Eksportuj profile obrazów", @@ -2459,7 +2460,7 @@ "brackets": "Nawiasy kwadratowe '[' i ']'", "curlyBrackets": "Nawiasy klamrowe '{' i '}'", "parentheses": "Nawiasy okrągłe '(' i ')'", - "and": " i ", + "and": "i", "onlySeparateLines": "Tylko oddzielne linie", "removeTextBeforeColon": "Usuń tekst przed dwukropkiem", "onlyIfTextIsUppercase": "Tylko, jeśli tekst pisany WIELKIMI LITERAMI", @@ -2814,6 +2815,10 @@ "removeOriginalSpeechDescription": "Zachowuje muzykę i efekty dźwiękowe z oryginalnego filmu, ale usuwa ścieżkę mówioną i nakłada na to nową. Przy użyciu karty graficznej proces ten trwa mniej więcej tyle samo, co samo wideo, natomiast bez niej – wielokrotnie dłużej. Gdy włączona jest funkcja wyciszania tła (audio ducking), jej poziom głośności wpływa na muzykę i efekty dźwiękowe. Dotyczy tylko sytuacji, gdy mowa jest dodawana do pliku wideo.", "removingOriginalSpeech": "Usuwanie oryginalnej mowy...", "removeOriginalSpeechFailed": "Nie udało się usunąć oryginalnej mowy – mowa została dodana do wideo bez niej.", + "speechRemovalModel": "Model", + "speechRemovalModelDescription": "Najlepsza jakość zachowuje najwięcej muzyki i efektów dźwiękowych. Tryb szybki jest około trzy razy szybszy, ale traci się nieco więcej muzyki.", + "speechRemovalBestQuality": "Najlepsza jakość (Mel-Band RoFormer)", + "speechRemovalFast": "Szybki (HTDemucs)", "videoTruncatedWarning": "Obraz w nowym pliku wideo zatrzymuje się po {0}, podczas gdy oryginalne nagranie trwa {1} – obraz się zawiesi, a dźwięk będzie nadal odtwarzany.\n\nPlik został zachowany. Informacje wyjściowe programu ffmpeg znajdują się w pliku error-log.txt w folderze danych aplikacji Subtitle Edit.", "originalVolumePercent": "Oryginalna głośność %", "vadSilenceCompression": "Kompresja ciszy VAD", @@ -3045,7 +3050,7 @@ "openSecondarySubtitleOnVideoPlayer": "Drugi plik z napisami (w odtwarzaczu wideo)", "removeSecondarySubtitleOnVideoPlayer": "Usuń drugi plik z napisami", "editSecondarySubtitleSettings": "Edytuj ustawienia drugich napisów", - "editSecondarySubtitleSettingsDotDotDot": "Edytuj ustawienia drugich napisów...", + "editSecondarySubtitleSettingsDotDotDot": "Edytuj ustawienia drugich napisów...", "secondarySubtitleRememberSettings": "Zapamiętaj te ustawienia", "secondarySubtitleDoNotShowAgain": "Nie pokazuj ponownie tego okna dialogowego", "secondarySubtitleJustifyLines": "Wyjustuj wiersze", @@ -3501,7 +3506,7 @@ "checkForUpdatesChannel": "Powiadamiaj o", "checkForUpdatesChannelStable": "Tylko wersjach stabilnych", "checkForUpdatesChannelStableAndBeta": "Wersjach stabilnych i beta", - "defaultFormat": "Domyślny format ", + "defaultFormat": "Domyślny format", "defaultSaveAsFormat": "Domyślny format \"Zapisz jako\"", "favoriteSubtitleFormats": "Ulubione formaty napisów", "favoriteLanguages": "Ulubione języki", @@ -3567,6 +3572,7 @@ "timeCodeModeTime": "Czas (hh:mm:ss,zzz)", "timeCodeModeFrames": "Klatki (hh:mm:ss:ff)", "timeCodeModeFrameNumbers": "Numery klatek", + "frameModeSnapToVideoFrames": "Zapisuj czasy na klatkach wideo (a nie na klatkach w obrębie każdej sekundy)", "textBoxLimitNewLines": "Ogranicz liczbę linii w polu tekstowym napisów", "mpvOpenGl": "libmpv - OpenGL", "mpvSoftwareRendering": "libmpv - renderowanie programowe (wolno)", @@ -4084,8 +4090,8 @@ "startOcr": "_Rozpocznij OCR", "pauseOcr": "_Wstrzymaj OCR", "inspectLine": "Sprawdź linię...", - "ocrEngine": "Silnik OCR ", - "tesseractEngineMode": "Tryb silnika ", + "ocrEngine": "Silnik OCR", + "tesseractEngineMode": "Tryb silnika", "tesseractEngineModeLegacy": "Tylko oryginalny Tesseract (potrafi wykrywać kursywę)", "tesseractEngineModeNeural": "Tylko sieci neuronowe LSTM", "tesseractEngineModeBoth": "Tesseract + LSTM", diff --git a/src/ui/Assets/Languages/Portuguese (Brazil).json b/src/ui/Assets/Languages/Portuguese (Brazil).json index c6ca873fd91..ad84ca24fab 100644 --- a/src/ui/Assets/Languages/Portuguese (Brazil).json +++ b/src/ui/Assets/Languages/Portuguese (Brazil).json @@ -1,6 +1,6 @@ { "title": "Subtitle Edit", - "version": "v5.3.0-rc4", + "version": "v5.3.0", "translatedBy": "igorruckert", "cultureName": "pt-BR", "general": { @@ -1082,7 +1082,7 @@ "audioTrackIsNowX": "A faixa de áudio agora é \"{0}", "audioTrackX": "Faixa de áudio {0}", "autoBreakHint": "Quebra automática de linhas selecionadas", - "charactersPerSecond": "Caracteres/seg: {0:0.00}", + "charactersPerSecond": "Caracteres/seg: {0}", "chooseColumn": "Escolha a coluna", "columnPaste": "Antes de colar coluna", "commandPaletteTitle": "Paleta de comandos", @@ -1161,7 +1161,7 @@ "videoAndWaveformPreviewTextX": "Prévia do vídeo/forma de onda: {0}", "xLinesCopiedFromOriginal": "{0} linhas copiadas da legenda original", "xLinesCopiedToOriginal": "{0} linhas copiadas para a legenda original", - "xLinesMerged": "Linhas combinadas", + "xLinesMerged": "{0} linhas mescladas", "xLinesSelectedOfY": "{0} linhas selecionadas de {1}", "xLinesSwitched": "{0} linhas trocadas", "xShotChangedLoaded": "{0} mudanças de cena carregadas", @@ -3420,7 +3420,7 @@ "minGapCalculateFrames": "Intervalo mínimo em quadros", "minGapCalculateXFramesAtYGivesZMs": "{0} quadros a uma taxa de quadros de {1} fornecem {2} milissegundos.", "minGapCalculateUseXAsNewGap": "Use \"{0}\" milissegundos como novo intervalo mínimo?", - "maxLines": "Colorir texto se tiver mais de {0} linhas", + "maxLines": "Número máximo de linhas", "unbreakSubtitlesShortThan": "Unir legendas menores que", "autoBreakLineEndingEarly": "Quebra automática antecipada para final de frase (.!?)", "autoBreakCommaBreakEarly": "Quebra automática antecipada para vírgula", @@ -3572,6 +3572,7 @@ "timeCodeModeTime": "Hora (hh:mm:ss,zzz)", "timeCodeModeFrames": "Quadros (hh:mm:ss:ff)", "timeCodeModeFrameNumbers": "Números de quadros", + "frameModeSnapToVideoFrames": "Armazenar tempos nos quadros do vídeo (não em quadros dentro de cada segundo)", "textBoxLimitNewLines": "Limitar o número de linhas na caixa de texto da legenda", "mpvOpenGl": "libmpv-OpenGL", "mpvSoftwareRendering": "libmpv - Renderização de software (lenta)", diff --git a/src/ui/Assets/Languages/Portuguese.json b/src/ui/Assets/Languages/Portuguese.json index 2e5482566af..dea39afb802 100644 --- a/src/ui/Assets/Languages/Portuguese.json +++ b/src/ui/Assets/Languages/Portuguese.json @@ -1,6 +1,6 @@ { "title": "Subtitle Edit", - "version": "v5.2.0-rc7", + "version": "v5.3.0", "translatedBy": "Hugo Carvalho (hugok79) e Filipe Mota (BlackSpirits)\nE-mail: hugokarvalho@hotmail.com / blackspirits@gmail.com\nAntigo tradutor: Marco Barbosa (moob)", "cultureName": "pt-PT", "general": { @@ -71,6 +71,24 @@ "abort": "Cancelar", "actor": "Ator", "actors": "Atores", + "setActorDotDotDot": "Definir ator...", + "actorPickerTitle": "Definir ator", + "actorPickerFilterHint": "Escreva para filtrar ou escreva o nome de um novo ator", + "actorPickerKeysHint": "Prima um número ou Enter para definir o ator. Alt+Cima/Baixo altera a ordem, Delete remove o ator.", + "actorPickerNewActorX": "Novo ator \"{0}\"", + "actorPickerLinesSelectedX": "Linhas selecionadas: {0}", + "setStyleDotDotDot": "Definir estilo...", + "stylePickerTitle": "Definir estilo", + "stylePickerFilterHint": "Escreva para filtrar ou escreva o nome de um novo estilo", + "stylePickerKeysHint": "Prima um número (1-9, 0) para definir o estilo em todas as linhas selecionadas.", + "stylePickerNewStyleX": "Novo estilo \"{0}\"", + "stylePickerNewStyleInfo": "Prima Enter para adicionar este estilo com as definições predefinidas e aplicá-lo às linhas selecionadas. Utilize \"Gerir estilos...\" para alterar mais tarde o tipo de letra, as cores e a posição.", + "stylePickerCurrentStyleX": "Estilo atual: {0}", + "stylePickerLinesUsingStyleX": "Linhas que utilizam este estilo: {0}", + "stylePickerMarginsXYZ": "Esquerda {0}, direita {1}, vertical {2}", + "stylePickerSetStyle": "_Definir estilo", + "stylePickerManageStyles": "Gerir estilos...", + "stylePickerManageStylesHint": "Fechar esta janela e abrir o gestor de estilos para editar tipos de letra, cores, posição e mais", "add": "Adicionar", "addDotDotDot": "Adicionar...", "addToNamesListCaseSensitive": "Adicionar à lista de nomes (diferencia maiúsculas de minúsculas)", @@ -79,7 +97,6 @@ "adjustAlpha": "Ajustar alfa (transparência)", "adjustDisplayDuration": "Ajustar a duração da visualização", "adjustment": "Ajuste", - "advanced": "Avançadas", "advancedSettings": "Definições avançadas", "after": "Depois", "alignment": "Alinhamento", @@ -115,7 +132,6 @@ "batchMode": "Modo múltiplos ficheiros", "bdnXml": "BDN/xml", "before": "Antes", - "beforeX": "Antes de \"{0}\"", "beginning": "Início", "bluRaySup": "Blu-ray (sup)", "bold": "Negrito", @@ -127,7 +143,6 @@ "bookmarkEdit": "Editar marcador...", "bookmarkEditForSelectedLinesX": "Editar marcador para {0} linhas selecionadas", "bookmarksList": "Listar marcadores", - "borderColor": "Cor do contorno", "borderStyle": "Estilo do contorno", "bottomCenter": "Inferior-centro", "bottomLeft": "Inferior-esquerdo", @@ -152,7 +167,6 @@ "changeCasing": "Inverter maiúsculas/minúsculas", "changeFormatting": "Alterar formatação", "changeFrameRate": "Alterar a taxa de fotogramas", - "changeLanguageFilter": "Alterar o filtro de idioma...", "changeOnce": "Alterar uma vez", "changeSpeed": "Alterar velocidade", "character": "Caráter", @@ -177,7 +191,6 @@ "conversionCancelledByUser": "Conversão cancelada pelo utilizador", "convert": "Converter", "converted": "Convertido", - "convertingDotDotDot": "A converter...", "convertingXofYDotDoDot": "A converter {0:#,###,##0}/{1:#,###,##0}...", "copy": "Copiar", "copyImageToClipboard": "Copiar imagem para a área de transferência", @@ -185,7 +198,6 @@ "couldNotSaveFileXErrorY": "Não foi possível guardar o ficheiro \"{0}\". Erro: {1}", "count": "Contagem", "cps": "Caracteres/seg", - "currentSubtitle": "Legenda atual", "currentVideoPosition": "Posição de vídeo atual", "custom": "Personalizado", "cut": "Cortar", @@ -231,7 +243,6 @@ "expand": "Expandir", "export": "Exportar", "exportDotDotDot": "Exportar...", - "exportToX": "Exportar para {0}", "extendAfter": "Estender/encurtar para a linha depois", "extendBefore": "Estender/encurtar a linha antes", "extendSelectedLinesToNextShotChangeOrNextSubtitle": "Prolongar linhas selecionadas até à mudança de cena seguinte (ou linha seguinte)", @@ -248,7 +259,6 @@ "fileExtension": "Extensão do ficheiro", "fileName": "Nome do ficheiro", "fileNameX": "Nome do ficheiro: {0}", - "fileNameXAndSize": "Nome do ficheiro: {0} ({1})", "fileSaved": "Ficheiro guardado", "fileSavedToX": "Ficheiro guardado em {0}", "fileXAlreadyExists": "O ficheiro \"{0}\" já existe.", @@ -258,8 +268,6 @@ "find": "Localizar", "findNext": "Localizar seguinte", "findTextX": "Localizar texto - {0}", - "fiveHundredMilliseconds": "500 milissegundos", - "fiveSeconds": "Cinco segundos", "fix": "Corrigir", "fixCommonErrors": "Corrigir erros comuns", "fixRightToLeft": "Corrigir da direita para a esquerda", @@ -283,14 +291,12 @@ "foundXInLineYZ": "Foi encontrado \"{0}\" na linha {1}, posição {2}", "foundXMatches": "Foram encontradas {0} correspondências", "frameRate": "Taxa de fotogramas", - "frameRateX": "Fotogramas por segundo: {0:0.0###}", "frames": "Fotogramas", "from": "De", "fromCurrentVideoPosition": "da posição atual do vídeo", "fullScreen": "Ecrã inteiro", "gap": "Intervalo", "general": "Geral", - "generalText": "Geral", "generate": "Gerar", "generateImportShotChanges": "Gerar/importar mudanças de cena", "generating": "A gerar...", @@ -308,12 +314,8 @@ "help": "Ajuda", "hex": "Hex", "hide": "Ocultar", - "hidePreview": "Ocultar pré-visualização", "history": "Histórico", "horizontal": "Horizontal", - "hourMinutesSecondsDecimalSeparatorMilliseconds": "Hora:min:seg{0}ms", - "hourMinutesSecondsFrames": "Hora:min:seg:fotogramas", - "ip": "IP", "id": "ID", "image": "Imagem", "imageSaved": "Imagem guardada", @@ -372,6 +374,7 @@ "mergeSelectedLinesDialog": "Combinar as linhas selecionadas como diálogo", "mergeWithLineAfterAndAutoBreak": "Combinar com a linha seguinte e quebrar automaticamente", "mergeWithLineAfterKeepBreaks": "Combinar com a linha seguinte (manter quebras)", + "mergeWithLineAfterAndUnbreak": "Combinar com a linha seguinte e eliminar quebras", "mergeWithLineBeforeAndAutoBreak": "Combinar com a linha anterior e quebrar automaticamente", "mergeWithLineBeforeKeepBreaks": "Combinar com a linha anterior (manter quebras)", "middleCenter": "Centro-meio", @@ -386,11 +389,11 @@ "moveAllShotChangeOneFrameForward": "Mover todas as mudanças de cena um fotograma para a frente", "multiMode": "Modo múltiplo", "moveDown": "Mover para baixo", + "increase": "Aumentar", + "decrease": "Diminuir", "moveUp": "Mover para cima", "multipleReplace": "Múltipla substituição", - "mute": "Mudo", "name": "Nome", - "negative": "Negativo", "new": "Novo", "newDotDotDot": "Novo...", "newProfile": "Novo perfil", @@ -418,20 +421,15 @@ "offsetY": "Desvio Y", "ok": "_OK", "oneFile": "Um ficheiro", - "oneHundredMilliseconds": "100 milissegundos", - "oneSecond": "Um segundo", "oneSecondBack": "Atrasar 1 segundo", "oneSecondForward": "Adiantar 1 segundo", - "onlineVideoFeatureNotAvailable": "Funcionalidade não disponível para vídeo online", "openContainingFolder": "Abrir pasta", "openDictionaryFolder": "Abrir pasta do dicionário", "openFile": "Abrir ficheiro", "openImageBasedSubtitle": "Abrir legenda baseada em imagem", "openImageFile": "Abrir ficheiro de imagem", - "openOriginalSubtitleFile": "Abrir legenda original...", "openOriginalSubtitleFileTitle": "Abrir ficheiro de legenda original", "openOutputFolder": "Abrir pasta de saída", - "openSubtitle": "Abrir legenda...", "openSubtitleFileTitle": "Abrir ficheiro de legenda", "openSubtitles": "Abrir legendas...", "openVideoFile": "Abrir ficheiro de vídeo...", @@ -444,11 +442,6 @@ "outlineWidth": "Largura do contorno", "outputFolder": "Pasta de saída", "outputProperties": "Propriedades de saída", - "overlap": "Sobrepor", - "overlapNextX": "Sobrepor a seguinte ({0:#;##0.###})", - "overlapPreviousLineX": "Sobrepor linha anterior ({0:#;##0.###})", - "overlapStartAndEnd": "Sobrepor início e fim", - "overlapX": "Sobrepor ({0:#;##0.###})", "overwriteExistingFiles": "Substituir ficheiros existentes", "overwriteFilesInFolderX": "Substituir ficheiros na pasta: \"{0}\"", "overwriteQuestion": "Substituir?", @@ -461,6 +454,7 @@ "percent": "Percentagem", "pickLayer": "Definir camada", "pickOllamaModel": "Escolher modelo Ollama", + "pickModel": "Escolher modelo", "llamaCppUseRemoteServer": "Utilizar servidor externo (URL)", "pickOutputFolder": "Escolher pasta de saída", "pickResolutionFromCurrentVideo": "Escolher resolução do vídeo atual", @@ -504,7 +498,6 @@ "refresh": "Atualizar", "region": "Região", "regularExpression": "Expressão regular", - "regularExpressionIsNotValid": "Expressão regular não válida!", "relativeToCurrentVideoPosition": "Em relação à posição do vídeo atual", "remove": "Remover", "removeAlignment": "Remover alinhamento", @@ -529,7 +522,7 @@ "reset": "Repor", "resizePanels": "Redimensionar painéis", "resolution": "Resolução", - "resolutionX": "Resolução: {0}", + "retry": "Tentar novamente", "reverseRightToLeftStartEnd": "Inverter RTL início/fim", "right": "Direita", "rightToLeft": "Direita para a esquerda", @@ -567,8 +560,6 @@ "selectedlinesX": "Linhas selecionadas: {0}", "sensitivity": "Sensibilidade", "separator": "Separador", - "sessionKey": "Nome de sessão", - "sessionKeyGenerate": "Gerar nova chave", "setEnd": "Definir fim", "setEndAndGoToNext": "Definir fim e ir para o seguinte", "setEndAddNewAndGoToNew": "Definir fim, adicionar nova e ir para a nova", @@ -591,8 +582,9 @@ "shadowColor": "Cor da sombra", "shadowWidth": "Largura da sombra", "shiftLineBy": "Deslocar linha em", + "shotIn": "Entrada de cena", + "shotOut": "Saída de cena", "shortcut": "Atalho", - "shortcutX": "Atalho: {0}", "shortcuts": "Atalhos", "show": "Mostrar", "showActorColumn": "Mostrar coluna \"Ator\"", @@ -603,17 +595,19 @@ "showStartColumn": "Mostrar coluna \"Início\"", "showHideColumn": "Mostrar coluna \"Ocultar\"", "showHistory": "Mostrar histórico", + "clearHistory": "Limpar histórico", + "removeFromHistory": "Remover do histórico", "showLayerColumn": "Mostrar coluna \"Camada\"", "showPreview": "Mostrar pré-visualização", "showShotChangesList": "Mostrar lista de mudanças de cena", + "showShotInColumn": "Mostrar coluna \"Entrada de cena\"", + "showShotOutColumn": "Mostrar coluna \"Saída de cena\"", "showStyleColumn": "Mostrar coluna \"Estilo\"", "showTeletext": "Mostrar coluna de teletexto na lista", "showTimeCodes": "Mostrar tempos", "showWpmColumn": "Mostrar coluna \"Palavras/min\"", "showPixelWidthColumn": "Mostrar coluna \"Largura do pixel\"", "shrink": "Encolher", - "singleBox": "Caixa única", - "singleLineLengths": "Comprimento da linha:", "singleMode": "Modo único", "textTotalLength": "Texto - comprimento total", "textSingleLineMaxLength": "Texto - comprimento máximo de uma linha", @@ -662,16 +656,16 @@ "strikeout": "Rasurado", "style": "Estilo", "styleExaggeration": "Exagero de estilo", - "styleLanguage": "Estilo / Idioma", "styles": "Estilos", "subtitleFile": "Ficheiro de legenda", + "subtitleFileChangedOutsideX": "O ficheiro \"{0}\" foi alterado fora do Subtitle Edit.\n\nRecarregá-lo e perder as suas alterações?", "subtitleFileName": "Nome do ficheiro de legenda", "subtitleFileSaved": "Ficheiro de legenda guardado", "subtitleFileSavedToX": "Ficheiro de legenda guardado em {0}", "subtitleFiles": "Ficheiros de legendas", "subtitleFormats": "Formatos de legendas", "subtitleLoadedX": "Legenda carregada: {0}", - "subtitleXOfY": "Legenda {0} de {1}", + "subtitleReloadedChangedOutsideX": "Legenda recarregada (alterada fora do Subtitle Edit): {0}", "suffix": "Sufixo", "suggestions": "Sugestões", "sync": "Sincronizar", @@ -683,7 +677,6 @@ "teletextColor": "Cor do teletexto", "teletextLine": "Linha de teletexto", "tenHours": "10 horas", - "tenMilliseconds": "10 milissegundos", "text": "Texto", "textColor": "Cor do texto", "textFiles": "Ficheiros de texto", @@ -710,9 +703,6 @@ "topRight": "Superior-direito", "top": "Superior", "totalAdjustmentX": "Total de ajustes: {0}", - "totalFramesX": "Total de fotogramas: {0:#;##0.##}", - "totalLengthX": "Comprimento total: {0}", - "totalLengthXSplitLine": "Comprimento total: {0} (dividir linha!)", "translate": "Traduzir", "translatePercentX": "A traduzir: {0}%", "translateRow": "Traduzir linha", @@ -721,6 +711,7 @@ "type": "Tipo", "unbreak": "Eliminar quebra de linha", "unbreakLines": "Eliminar quebras de linha", + "undelete": "Recuperar eliminadas", "underline": "Sublinhado", "undo": "Anular", "unknown": "Desconhecido", @@ -740,13 +731,11 @@ "usages": "Utilizações", "use": "Utilizar", "useAlways": "Utilizar sempre", - "useLargerFontForThisWindow": "Utilizar um tipo de letra maior para esta janela", "useOnce": "Utilizar uma vez", "useOutputFolder": "Utilizar pasta de saída", "useSourceFolder": "Utilizar pasta de origem", "useSourceResolution": "Utilizar resolução original", "userName": "Utilizador", - "userNameAlreadyInUse": "Utilizador em utilização", "vertical": "Vertical", "video100MsBack": "Vídeo, há 100 milissegundos", "video100MsForward": "Vídeo, 100 milissegundos à frente", @@ -760,14 +749,25 @@ "videoCustom3ForwardX": "Vídeo, milissegundos personalizados ({0:#,###,##0}) à frente, 3", "videoCustom4BackX": "Vídeo, milissegundos personalizados ({0:#,###,##0}) atrás, 4", "videoCustom4ForwardX": "Vídeo, milissegundos personalizados ({0:#,###,##0}) à frente, 4", - "videoEncodingX": "Codificação de vídeo: {0}", + "videoCustom1BackAndPauseX": "Vídeo, milissegundos personalizados ({0:#,###,##0}) atrás e pausar, 1", + "videoCustom1ForwardAndPauseX": "Vídeo, milissegundos personalizados ({0:#,###,##0}) à frente e pausar, 1", + "videoCustom2BackAndPauseX": "Vídeo, milissegundos personalizados ({0:#,###,##0}) atrás e pausar, 2", + "videoCustom2ForwardAndPauseX": "Vídeo, milissegundos personalizados ({0:#,###,##0}) à frente e pausar, 2", + "videoCustom3BackAndPauseX": "Vídeo, milissegundos personalizados ({0:#,###,##0}) atrás e pausar, 3", + "videoCustom3ForwardAndPauseX": "Vídeo, milissegundos personalizados ({0:#,###,##0}) à frente e pausar, 3", + "videoCustom4BackAndPauseX": "Vídeo, milissegundos personalizados ({0:#,###,##0}) atrás e pausar, 4", + "videoCustom4ForwardAndPauseX": "Vídeo, milissegundos personalizados ({0:#,###,##0}) à frente e pausar, 4", "videoExtension": "Extensão de ficheiro de vídeo", "videoFile": "Ficheiro de vídeo", "videoFileGenerated": "Ficheiro de vídeo gerado", "videoFileGeneratedX": "Ficheiro de vídeo gerado: \"{0}\"", + "videoFilesGenerated": "Ficheiros de vídeo gerados", + "xVideosGenerated": "{0} vídeos gerados", + "xOfYVideosGenerated": "{0} de {1} vídeos gerados", + "savedInX": "Guardado em {0}", + "savedInXFolders": "Guardado em {0} pastas", "videoFiles": "Ficheiros de vídeo", "videoAndAudioFiles": "Ficheiros de vídeo e áudio", - "videoInformation": "Informações do vídeo", "videoOffset": "Desvio do vídeo", "videoOneFrameBack": "Vídeo, um fotograma atrás", "videoOneFrameForward": "Vídeo, um fotograma à frente", @@ -778,7 +778,6 @@ "videoPlayer": "Reprodutor de vídeo", "videoPosition": "Posição do vídeo", "videoResolution": "Resolução de vídeo", - "viewX": "Ver {0}", "visible": "Visível", "voice": "Voz", "volume": "Volume", @@ -787,9 +786,7 @@ "waveformCenterOnVideoPosition": "Forma de onda ao centro na posição do vídeo", "waveformPasteFromClipboard": "Colar da área de transferência", "waveformSpectrogram": "Forma de onda/espectrograma", - "webServiceUrl": "URL do serviço web", "width": "Largura", - "windowPositionAndSize": "Posição e tamanho da janela", "wordsPerMin": "Palavras/min", "pixelWidth": "Largura do pixel", "wpm": "Palavras/min", @@ -803,8 +800,6 @@ "noBookmarksFound": "Não foram encontrados mais marcadores", "xRequiresAnApiKey": "{0} requer uma chave API.", "xRequiresAValidUrl": "{0} requer um URL válido (por ex. \"http://...\" ou \"https://...\")", - "xSeconds": "{0:0.0##} segundos", - "xSubtitles": "{0:#,###,##0} legendas", "yes": "Sim", "zeroBytes": "0 bytes", "copyToClipboard": "Copiar", @@ -826,10 +821,8 @@ "playbackSpeed": "Velocidade de reprodução", "hoursMinutesSecondsMilliseconds": "HH:MM:SS.FFF", "imageFiles": "Ficheiros de imagens", - "openAiCompatibleStt": "STT compatível com OpenAI", "openAiCompatibleSttEndpoint": "URL do endpoint STT", "openAiCompatibleSttApiKey": "Chave API", - "openAiCompatibleSttModel": "Modelo", "openAiCompatibleSttExtraHeaders": "Cabeçalhos adicionais", "openAiCompatibleSttTimeout": "Tempo limite (segundos)", "openAiCompatibleSttLanguage": "Sugestão de idioma", @@ -839,6 +832,9 @@ "dashScopeSttRegion": "Região", "dashScopeSttEnableWords": "Marcas de tempo ao nível da palavra", "dashScopeSttRegionKeyHint": "Nota: as chaves API do Alibaba Cloud Model Studio são específicas de cada região - certifique-se de que a região selecionada corresponde à região onde a chave API foi criada (China vs. Internacional).", + "googleCloudSttProjectId": "ID do projeto (apenas para Application Default Credentials)", + "googleCloudSttBucketName": "Bucket de armazenamento (vazio = automático)", + "googleCloudSttDynamicBatching": "Agrupamento dinâmico (cerca de 5x mais barato, sem garantia de latência)", "onlineSttApiKeyMissing": "É necessária uma chave API. Introduza a sua chave API e tente novamente.", "openAiCompatibleSttAutoTranscribeOnAudioSelection": "Transcrever automaticamente na seleção de áudio", "openAiCompatibleSttStream": "Fluxo de resposta", @@ -848,11 +844,9 @@ "configurationRequired": "Configuração necessária", "transcriptionError": "Erro de transcrição", "transcriptionComplete": "Transcrição concluída", - "transcribingWithOpenAi": "A transcrever com o serviço compatível com OpenAI...", "processingResponse": "A processar resposta...", "unauthorizedApiKey": "Não autorizado: chave API inválida ou permissões insuficientes.", "requestTimeout": "Tempo limite do pedido: o servidor demorou demasiado tempo a responder.", - "noSegmentsReturned": "Nenhum segmento devolvido pelo serviço de transcrição.", "openAiCompatibleSttUrlMissing": "O URL do endpoint STT compatível com OpenAI é obrigatório. Introduza um URL e tente novamente.", "openAiCompatibleSttUrlNotResponding": "O endpoint STT compatível com OpenAI não respondeu:\n{0}\n\nVerifique o URL e se o servidor está em execução.", "transcriptionFailed": "Falha na transcrição", @@ -898,14 +892,15 @@ "multipleReplace": "_Substituição múltipla", "fillSelectedLinesWithClipboard": "Preencher linhas selecionadas com o texto da área de transferência", "goToLineNumber": "_Ir para o número da linha...", + "commandPalette": "_Paleta de comandos...", "rightToLeftMode": "Modo da direita para a esquerda", "modifySelectionDotDotDot": "Modificar _seleção...", "tools": "_Ferramentas", - "toolsSelectedLines": "_Ferramentas (linhas selecionadas)", "adjustDurations": "_Ajustar durações...", "applyDurationLimits": "Aplicar limites de duração às linhas selecionadas...", "batchConvert": "_Conversão em lote...", "beautifyTimeCodes": "Otimizar tempos...", + "improveTimeCodes": "Melhorar tempos (alinhamento forçado)...", "bridgeGaps": "Unir _intervalos...", "applyMinGap": "Aplicar intervalo mínimo entre as legendas...", "changeCasing": "_Alterar maiúsculas/minúsculas...", @@ -1044,6 +1039,9 @@ "selectCurrentLineWhilePlayingHint": "Selecionar a legenda atual durante a reprodução {0}", "videoPosition": "Posição do vídeo {0}", "videoPositionTextBox": "Caixa de texto da posição do vídeo {0}", + "toolbarLineBreakX": "Quebra de linha {0} (o resto da barra de ferramentas continua numa nova linha)", + "initialText": "Texto inicial da linha selecionada", + "initialTextHint": "O texto da linha selecionada tal como estava quando a linha foi selecionada", "hideWaveformToolbar": "Ocultar barra de ferramentas {0}", "resetZoomAndSpeed": "Repor zoom e velocidade de reprodução {0}", "removeBlankLines": "Remover linhas em branco {0}", @@ -1057,6 +1055,15 @@ "textPlayHint": "Reproduzir a legenda atual e parar no fim {0}", "textPauseHint": "Pausar a reprodução {0}", "textNextHint": "Reproduzir a legenda seguinte e parar no fim {0}", + "moveSelectedLines": "Mover linhas selecionadas (botões atrás/à frente)", + "moveSelectedLinesAndFollowing": "Mover linhas selecionadas e todas as seguintes (botões atrás/à frente)", + "moveAllLines": "Mover todas as linhas (botões atrás/à frente)", + "moveSelectedLinesBackHint": "Mover linhas selecionadas {0} ms para trás {1}", + "moveSelectedLinesForwardHint": "Mover linhas selecionadas {0} ms para a frente {1}", + "moveSelectedLinesAndFollowingBackHint": "Mover linhas selecionadas e todas as seguintes {0} ms para trás {1}", + "moveSelectedLinesAndFollowingForwardHint": "Mover linhas selecionadas e todas as seguintes {0} ms para a frente {1}", + "moveAllLinesBackHint": "Mover todas as linhas {0} ms para trás {1}", + "moveAllLinesForwardHint": "Mover todas as linhas {0} ms para a frente {1}", "guessStartNoLineSelected": "Adivinhar início: nenhuma linha selecionada", "guessStartTimeCodesLocked": "Adivinhar início: os tempos estão bloqueados", "guessStartNoWaveform": "Adivinhar início: sem forma de onda", @@ -1078,6 +1085,9 @@ "charactersPerSecond": "Caracteres/segundo: {0}", "chooseColumn": "Escolher coluna", "columnPaste": "Colar coluna", + "commandPaletteTitle": "Paleta de comandos", + "commandPaletteSearchPlaceholder": "Escreva para pesquisar comandos...", + "commandPaletteNoCommandsFound": "Nenhum comando correspondente", "createdEmptyTranslation": "Criada uma tradução vazia da legenda atual", "deleteText": "Eliminar texto", "deleteTextAndShiftCellsUp": "Eliminar texto e deslocar células para cima", @@ -1115,18 +1125,20 @@ "noTextInClipboard": "Nenhum texto na área de transferência", "oneLineCopiedFromOriginal": "Uma linha copiada da legenda original", "oneLineCopiedToOriginal": "Uma linha copiada para a legenda original", + "onlineOnlyVideo": "Vídeo apenas online", + "onlineOnlyVideoXSizeYDownloadAndOpen": "O ficheiro de vídeo \"{0}\" ({1}) está armazenado apenas online, por exemplo no Dropbox ou no iCloud Drive, e tem de ser transferido antes de poder ser aberto.\n\nTransferir e abrir agora?", "oneLineMerged": "Uma linha combinada", "oneLineSwitched": "Uma linha trocada", "overwriteExistingCells": "Substituir células existentes", "overwriteOrShiftCellsDown": "Substituir/deslocar células para baixo", "parsingMatroskaFile": "A analisar o ficheiro Matroska (.mkv), aguarde...", + "readingDvdSubtitles": "A ler legendas do DVD...", "pasteFromClipboardDotDotDot": "Colar da área de transferência", "redoPerformed": "Refazer aplicado", "redoPerformedXActionLeft": "Refazer realizado (ações restantes: {0})", "removedUnicodeControlCharactersX": "Caracteres de controlo Unicode removidos de {0} linhas", "removedXBlankLines": "Removidas {0} linhas em branco", "replacedXWithYCountZ": "\"{0}\" substituído por \"{1}\" ({2} ocorrências)", - "replacedXWithYInLineZ": "\"{0}\" substituído por \"{1}\" na linha {2}", "reversedStartAndEndingsForRightToLeftX": "Inícios e fins invertidos da direita para a esquerda em {0} linhas", "ruleProfileIsX": "O perfil da regra agora é \"{0}\"", "saveLanguageFile": "Guardar ficheiro de idioma", @@ -1134,7 +1146,6 @@ "shiftTextCellsDown": "Deslocar células de texto para baixo", "singleLineLength": "Comprimento da linha:", "speedIsNowX": "A velocidade agora é \"{0}\"", - "spellCheckResult": "Verificação ortográfica concluída. \n\n• Palavras alteradas: {0}\n• Palavras ignoradas: {1}", "subtitleGridFormattingX": "Formatação da grelha: {0}", "subtitleImportedFromMatroskaFile": "Legenda importada do ficheiro Matroska (.mkv)", "textDown": "Texto para baixo", @@ -1161,13 +1172,14 @@ "trimmedXLines": "{0} linhas de legenda cortadas", "pastedXLinesOverSelectedLines": "{0} linha(s) colada(s) sobre as linhas selecionadas", "pastedXOfYLinesOverSelectedLines": "{0} de {1} linha(s) colada(s) sobre as linhas selecionadas - as restantes não couberam na seleção", + "insertedXLinesFromClipboard": "Inserida(s) {0} linha(s) da área de transferência", "openOriginalDifferentNumberOfSubtitlesXY": "O ficheiro de legenda original não tem o mesmo número de legendas que o ficheiro de legenda atual.\n\n• Legendas originais: {0}\n• Legendas atuais: {1}", - "importXMatchingOriginalLines": "Importar {0} legendas originais correspondentes?", "originalTextReadOnly": "Texto original (apenas leitura)", "originalTextEditMode": "Texto original (modo de edição)", "originalIsReadOnlyNotSaved": "A legenda original é uma referência apenas de leitura e não foi guardada", "originalIsEmptyNotSaved": "A legenda original não tem qualquer texto e não foi guardada", "originalIsReadOnlyReference": "A legenda original está aberta como referência apenas de leitura", + "closeTranslationQuestionXY": "Fechar a tradução \"{0}\"?\n\nA legenda original \"{1}\" passa a ser a legenda que edita. O ficheiro de tradução em si não é alterado.", "allowEditOfOriginalSubtitle": "Permitir a edição da legenda original", "showAllOriginalLinesX": "Mostrar todas as linhas originais ({0} não têm correspondência aqui)", "showAllOriginalLinesHint": "As linhas sem correspondência são mostradas como linhas extra, para que possa ver o que falta na tradução.", @@ -1202,6 +1214,7 @@ "fileExportedInFormatXToY": "Ficheiro exportado no formato {0} em {1}", "fileExportedInFormatXToFileY": "Ficheiro exportado no formato \"{0}\" para o ficheiro \"{1}\"", "fixedXLines": "{0} linhas corrigidas", + "toolXChangedYLines": "{0}: {1} linhas alteradas", "transcriptionCompletedWithXLines": "Transcrição concluída com {0} linhas", "openSubtitleFileX": "Abrir ficheiro de legenda \"{0}\"?", "replacedXOccurrences": "Substituídas {0} ocorrências", @@ -1234,13 +1247,9 @@ "file": { "ebuSaveOptions": { "title": "Opções para guardar EBU", - "generalSubtitleInformation": "Informação geral da legenda", "codePageNumber": "Número de código da página", "diskFormatCode": "Código do formato do disco", "displayStandardCode": "Mostrar código da norma", - "colorRequiresTeletext": "Cores necessárias para teletexto!", - "alignmentRequiresTeletext": "Alinhamento requer teletexto!", - "teletextCharsShouldBe38": "Máximo de caracteres por linha para teletexto deve ser 38!", "characterCodeTable": "Tabela de caracteres", "languageCode": "Código do idioma", "originalProgramTitle": "Título original do programa", @@ -1257,7 +1266,6 @@ "maxNumberOfDisplayableRows": "Máx. de linhas", "diskSequenceNumber": "Número de sequência do disco", "totalNumberOfDisks": "Número total de discos", - "import": "Importar...", "textAndTimingInformation": "Informação de tempo e texto", "justificationCode": "Código de justificação", "verticalPosition": "Posição vertical", @@ -1274,11 +1282,6 @@ "errors": "Erros", "errorsX": "Erros: {0}", "maxLengthError": "Linha {0} excede ao comprimento máximo ({1}) em {2}: {3}", - "textUnchangedPresentation": "Apresentação original", - "textLeftJustifiedText": "Alinhado à esquerda", - "textCenteredText": "Centrado", - "textRightJustifiedText": "Alinhado à direita", - "useBoxForOneNewLine": "Marcar \"Utilizar caixa à volta do texto\" apenas numa linha nova", "maxLengthErrorTeletextColored": "Linha {0}-{1}: 36 (e não {2}) deve ser o número máximo de caracteres para texto colorido de altura dupla", "maxLengthErrorTeletextWhite": "Linha {0}-{1}: 37 (e não {2}) deve ser o número máximo de caracteres para texto branco de altura dupla", "maxLengthErrorTeletextSingleHeight": "Linha {0}: 38 (e não {1}) deve ser o número máximo de caracteres para texto branco de altura simples", @@ -1303,22 +1306,29 @@ "formattingDotDotDot": "Formatação...", "imageBasedSubtitleForEditDotDotDot": "Legenda baseada em imagem para edição...", "imageBasedSubtitleForOcrDotDotDot": "Legenda baseada em imagem para OCR...", + "dvdSubtitlesDotDotDot": "Legendas de DVD (IFO/VOB)...", + "titleImportDvd": "Importar legendas de DVD", + "dvdIfoFile": "Ficheiro IFO", + "dvdVobFiles": "Ficheiros VOB", + "dvdVideoStandard": "Norma de vídeo", + "dvdStartRipping": "Iniciar extração", + "dvdRippingAborted": "Extração cancelada", + "dvdOpenIfoFile": "Abrir ficheiro IFO de DVD", + "dvdAddVobFiles": "Adicionar ficheiros VOB", + "dvdIfoFiles": "Ficheiros IFO de DVD", + "dvdVobFileType": "Ficheiros VOB de DVD", + "dvdDropHint": "Abra um ficheiro IFO (VIDEO_TS\\VTS_01_0.IFO) ou adicione ficheiros VOB - ou largue-os aqui", + "dvdNotAnIfoFileX": "{0} não é um ficheiro IFO de DVD", + "dvdMenuIfoNoTitleSetsX": "Nenhum conjunto de títulos (VTS_xx_0.IFO) encontrado junto de {0}", + "dvdEncryptedXOfY": "{0} dos {1} pacotes de legendas ainda estão encriptados com CSS - estes ficheiros VOB foram copiados sem desencriptação, pelo que algumas imagens de legendas ficarão corrompidas.\n\nDesencripte primeiro o DVD com uma ferramenta de extração de DVD para obter um resultado limpo.\n\nContinuar mesmo assim?", "splitTextAt": "Dividir texto em", "blankLines": "Linhas em branco", "oneLineIsOneSubtitle": "Uma linha é uma legenda", "twoLinesAreOneSubtitle": "Duas linhas numa", "importFilesDotDotDot": "Importar ficheiros...", "multipleFiles": "Importe de vários ficheiros de texto (um ficheiro é uma legenda)", - "importOptions": "Opções de importação", - "autoSplitText": "Auto-dividir texto", - "lineMode": "Modo de linha", "maxLineLength": "Comprimento máximo da linha", - "maxLinesPerSubtitle": "Máximo de linhas por legenda", - "minGapBetweenSubtitles": "Espaço entre linhas (milissegundos)", "mergeShortLines": "Combinar as linhas curtas", - "removeLinesWithoutLetters": "Remover linhas sem letras", - "splitAtEndCharsSetting": "Dividir nos últimos caracteres", - "takeTimeFromCurrentFile": "Tirar tempo do ficheiro atual", "fixed": "Corrigido", "numberOfSubtitlesX": "Número de legendas: {0}", "numberOfSubtitlesXForcedY": "Número de legendas: {0} ({1} forçadas)", @@ -1330,14 +1340,18 @@ "forcedAlignerProgress": "A alinhar... janela {0} de {1}, {2} de {3} linhas", "forcedAlignerSetupTitle": "Alinhar tempos com alinhador forçado", "forcedAlignerSetupIntro": "Um alinhador forçado faz corresponder o texto da legenda que já tem ao áudio, sem o transcrever primeiro. Os vídeos longos são alinhados por blocos, pelo que qualquer duração funciona.", + "forcedAlignerEndsFromSpeech": "Definir tempos finais a partir da fala isolada (lento)", + "forcedAlignerEndsFromSpeechHint": "Um alinhador forçado encontra onde uma linha começa, não onde termina, por isso os tempos finais vêm normalmente do tempo de leitura. Com esta opção ativa, a música e os efeitos sonoros são primeiro removidos do áudio e cada linha termina onde a fala fica em silêncio. Demora cerca do mesmo tempo que o próprio vídeo com uma GPU - e muitas vezes mais sem uma.", + "forcedAlignerIsolatingSpeech": "A isolar a fala... {0}", + "forcedAlignerIsolatingSpeechFailed": "Não foi possível isolar a fala - os tempos finais foram definidos a partir do tempo de leitura.", "forcedAlignerModel": "Modelo do alinhador", - "forcedAlignerDownloadEngine": "Transferir / atualizar motor..." + "forcedAlignerDownloadEngine": "Transferir / atualizar motor...", + "forcedAlignerEstimatedXOfY": "{0} de {1} linhas não puderam ser alinhadas e foram colocadas pelo tempo de leitura - os seus tempos são apenas estimados. O script é provavelmente mais longo do que o que é falado no vídeo." }, "export": { "exportImagesProfiles": "Exportar perfis de imagens", "leftRightMargin": "Margem esq./dir.", "topBottomMargin": "Margem superior/inferior", - "titleExportBluRaySup": "Exportar Blu-ray (sup)", "lineSpacingPercent": "Espaçamento entre linhas %", "paddingLeftRight": "Preenchimento esquerdo/direito", "paddingTopBottom": "Preenchimento superior/inferior", @@ -1378,10 +1392,21 @@ "titleExportDCinemaInteropPng": "Interoperabilidade D-Cinema/png", "titleExportDCinemaSmpte2014Png": "D-Cinema SMPTE 2014/png", "imageBasedSubtitleSaved": "Legenda baseada em imagem guardada", - "titleExportBdnXml": "Exportar BDN/xml", "titleExportWebVttThumbnails": "Exportar WebVTT (miniaturas)", "fullFrameImage": "Imagem de fotograma completo", "fullFrameImageHint": "Criar cada imagem com o tamanho do fotograma do vídeo, com a legenda no seu lugar", + "stereo3D": "3D", + "stereo3DHint": "Para vídeo 3D: desenhar a legenda uma vez em cada metade da imagem (uma por olho)", + "halfSideBySide": "Meio lado a lado", + "halfTopBottom": "Meio cima/baixo", + "depth3D": "Profundidade", + "depth3DHint": "Quanto a legenda sobressai do ecrã - positivo aproxima-a, negativo afasta-a", + "plane3D": "Plano 3D", + "plane3DHint": "Dar a cada legenda a profundidade que um Blu-ray 3D guarda para ela, a partir de um ficheiro de plano 3D (.ofs) criado pelo BD3D2MK3D ou OFSExtractor. A profundidade é utilizada onde o plano 3D não tem nenhuma", + "openPlane3DTitle": "Abrir ficheiro de plano 3D", + "unableToLoadPlane3DX": "Não foi possível carregar o plano 3D: {0}", + "plane3DXDepthYToZ": "{0} (profundidade {1} a {2})", + "plane3DXNoDepth": "{0} (sem valores de profundidade)", "textEffect": "Efeito de texto", "textEffectSettingsTitle": "Definições do efeito de texto", "textEffectPreset": "Predefinição", @@ -1413,13 +1438,10 @@ "statistics": { "title": "Estatísticas", "titleWithFileName": "Estatísticas - {0}", - "generalStatistics": "Estatísticas gerais", - "mostUsed": "Mais utilizado...", "mostUsedLines": "Linhas mais usadas", "mostUsedWords": "Palavras mais usadas", "nothingFound": "Nenhuma encontrada", "numberOfLinesX": "Número de linhas da legenda: {0:#,##0}", - "numberOfFilesX": "Número de ficheiros de legenda: {0:#,##0}", "lengthInFormatXinCharactersY": "Número de caracteres como {0}: {1:#,###,##0}", "numberOfCharactersInTextOnly": "Número de caracteres apenas no texto: {0:#,###,##0}", "totalDuration": "Duração total de todas as legendas: {0:#,##0}", @@ -1460,7 +1482,6 @@ "gapMaximum": "Intervalo - máximo: {0:#,##0} ms", "gapAverage": "Intervalo - médio: {0:#,##0.##} ms", "gapExceedingMinimum": "Intervalo - abaixo do mínimo ({0:#,##0} ms): {1} ({2:0.00}%)", - "export": "Exportar...", "subtitles": "Legendas", "words": "Palavras", "totalDurationShort": "Duração total", @@ -1490,7 +1511,22 @@ "restoreAutoBackupFile": "Restaurar ficheiro de cópia de segurança automática", "restoreXFromY": "Pretende restaurar \"{0}\" de {1}?", "deleteAllSubtitleBackups": "Pretende eliminar todos os ficheiros de cópias de segurança de legendas?", - "deleteAll": "Eliminar tudo" + "deleteAll": "Eliminar tudo", + "subtitles": "Legendas", + "settingsBackupInfo": "É feita uma cópia de segurança do Settings.json quando o Subtitle Edit inicia, no máximo uma vez a cada {0} dia(s) e apenas se as definições tiverem sido alteradas. São mantidas as {1} cópias de segurança mais recentes.", + "settingsBackupInfoEveryStart": "É feita uma cópia de segurança do Settings.json sempre que o Subtitle Edit inicia, se as definições tiverem sido alteradas. São mantidas as {0} cópias de segurança mais recentes.", + "settingsBackupOff": "A cópia de segurança automática das definições está desativada (Opções → Definições → Ficheiro).", + "lastSettingsBackupX": "Última cópia de segurança: {0}", + "noSettingsBackupsYet": "Ainda não há cópias de segurança das definições", + "openSettingsBackupFolder": "Abrir pasta de cópias de segurança das definições", + "backUpSettingsNow": "Fazer cópia de segurança agora", + "settingsBackedUp": "Foi feita uma cópia de segurança das definições atuais.", + "restoreSettings": "Restaurar definições", + "restoreSettingsFromX": "Pretende substituir as definições atuais pela cópia de segurança de {0}?\n\nSerá feita primeiro uma cópia de segurança das definições atuais. Algumas alterações só têm efeito após reiniciar o Subtitle Edit.", + "settingsRestored": "As definições foram restauradas. Reinicie o Subtitle Edit para que todas as alterações tenham efeito.", + "settingsRestoreFailed": "Não foi possível restaurar as definições: {0}", + "settingsBackupNotValid": "a cópia de segurança não é um ficheiro de definições válido, pelo que as definições atuais foram mantidas", + "deleteAllSettingsBackups": "Pretende eliminar todos os ficheiros de cópia de segurança das definições?" }, "propertiesDCinema": { "title": "Propriedades D-Cinema SMPTE", @@ -1504,22 +1540,16 @@ "editRate": "Editar taxa", "timeCodeRate": "Taxa de velocidade dos tempos", "startTime": "Tempo inicial", - "font": "Tipo de letra", "fontId": "ID", "fontUri": "URI", - "generate": "Gerar", - "fontColor": "Cor do tipo de letra", "chooseColor": "Escolher cor", "fontEffect": "Efeito", "effectColor": "Cor do efeito", - "fontSize": "Tamanho", "topBottomMargin": "Margem superior/inferior", "fadeUpTime": "Desvanecer o tempo ascendente", "fadeDownTime": "Desvanecer o tempo descendente", - "frames": "Fotogramas", "zPosition": "Posição Z", - "zPositionHelp": "Números positivos afastam o texto, números negativos aproximam-no; se a posição Z for zero, é 2D", - "export": "Exportar..." + "zPositionHelp": "Números positivos afastam o texto, números negativos aproximam-no; se a posição Z for zero, é 2D" }, "webVtt": { "setStylesForSelectedLinesTitle": "Definir estilos WebVTT para as linhas selecionadas", @@ -1552,20 +1582,66 @@ "compareOnlyInOneFile": "Apenas num ficheiro", "compareTextOrTimeDifference": "Diferença de texto/tempo", "compareNumberDifference": "Diferença de número", - "ignoreLineBreaks": "Ignorar quebra de linha", "ignoreWhitespace": "Ignorar espaço em branco", "ignoreWhitespaceHint": "As linhas que diferem apenas em espaços, tabulações ou quebras de linha não contam como diferentes", "ignoreFormatting": "Ignorar formatação", - "onlyLookForDifferencesInText": "Procurar apenas as diferenças no texto", - "cannotCompareWithImageBasedSubtitles": "Não é possível comparar legendas baseadas em imagens", - "statisticsTitle": "Estatísticas", "ignoreFormattingHint": "As linhas que diferem apenas em etiquetas de formatação, como ou {\\an8}, não contam como diferentes", "showOnlyDifferencesInText": "Apenas diferenças no texto", + "ignoreNumbering": "Ignorar numeração", + "ignoreNumberingHint": "Linhas que diferem apenas no número não contam como diferentes", + "ignoreTimeDifferenceMs": "Tolerância de tempo (ms)", + "ignoreTimeDifferenceMsHint": "Tempos iniciais e finais que diferem este número de milissegundos ou menos não contam como diferentes", + "compareDifferences": "Diferenças", + "compareTextDifferences": "Diferenças de texto", + "compareCurrent": "Atual", + "compareReference": "Referência", + "compareEditable": "Editável", + "compareEditableHint": "As alterações feitas aqui são aplicadas à legenda quando clicar em Aplicar", + "compareReadOnly": "Só de leitura", + "compareTakeFromReference": "Usar texto e tempos da referência", + "compareInsertFromReference": "Inserir esta linha na legenda atual", + "compareDeleteFromCurrent": "Eliminar esta linha da legenda atual", + "compareTakeText": "Usar texto", + "compareTakeTiming": "Usar tempos", + "compareEditLine": "Editar linha", + "compareEditHint": "Ctrl+Enter para guardar, Esc para cancelar", + "compareEdited": "Editada", + "compareOnePendingChange": "1 alteração pendente", + "compareXPendingChanges": "{0} alterações pendentes", + "compareDiscardXChanges": "Descartar a(s) {0} alteração(ões) feita(s) em Comparar?", + "compareXChangesApplied": "Comparar: {0} alteração(ões) aplicada(s)", + "compareChangeTextX": "#{0} texto da referência", + "compareChangeTimingX": "#{0} tempos da referência", + "compareChangeTextAndTimingX": "#{0} texto e tempos da referência", + "compareChangeEditedX": "#{0} editada", + "compareChangeInsertedX": "#{0} inserida", + "compareChangeDeletedX": "#{0} eliminada", + "compareSyncPoint": "Ponto de sincronização", + "compareSyncPointHint": "Ponto de sincronização - estas duas linhas são sempre mostradas como um par e as linhas acima e abaixo são alinhadas separadamente", + "compareSyncPickCurrent": "Ponto de sincronização: usar esta linha atual", + "compareSyncPickReference": "Ponto de sincronização: usar esta linha de referência", + "compareSyncRemove": "Remover ponto de sincronização", + "compareClearXSyncPoints": "Limpar pontos de sincronização ({0})", + "compareSyncCurrentPickedX": "Atual #{0} escolhida como ponto de sincronização - selecione a linha de referência correspondente e clique em Sincronizar", + "compareSyncReferencePickedX": "Referência #{0} escolhida como ponto de sincronização - selecione a linha atual correspondente e clique em Sincronizar", + "compareSyncWithCurrentX": "Sincronizar com a atual #{0}", + "compareSyncWithReferenceX": "Sincronizar com a referência #{0}", + "compareSyncApply": "Sincronizar", + "compareSyncPointSetXY": "Ponto de sincronização definido: atual #{0} e referência #{1} são agora mostradas como um par", + "compareSyncAlreadyPairedXY": "Atual #{0} e referência #{1} já são mostradas como um par com o mesmo início - nada a sincronizar", + "compareSyncPointShiftedXYZW": "Ponto de sincronização definido: atual #{0}-#{1} deslocadas {2} para começar com a referência #{3}", + "compareChangeSyncShiftXYZ": "#{0}-#{1} deslocadas {2} (ponto de sincronização)", + "compareTimingDeltaHint": "Quanto mais tarde (+) ou mais cedo (−) a referência começa e termina em relação à linha atual", "loadXFromFile": "Carregar \"{0}\" do ficheiro", + "compareLoadSavedFile": "Carregar ficheiro guardado", "saveCompareHtmlTitle": "Guardar ficheiro HTML de comparação", "pickMatroskaTrackX": "Escolher a faixa Matroska - {0}", "pickTransportStreamTrackX": "Escolher faixa do fluxo de transporte - {0}", + "chapters": "Capítulos", + "dvdVobFilesMissingX": "Ficheiros VOB em falta ({0}% encontrados)", "pickMp4TrackX": "Escolher faixa MP4 - {0}", + "pickMxfTrackX": "Escolher faixa MXF - {0}", + "pickMpegTrackX": "Escolher faixa MPEG - {0}", "rosettaProperties": "Propriedades do Rosetta IMSC de texto cronometrado", "rosettaFontSize": "Tamanho do tipo de letra (altura da linha)", "propertyTimeBase": "Base de tempo", @@ -1575,7 +1651,6 @@ "propertyDefaultRegion": "Região predefinida", "propertyStyleAttributeName": "Nome do atributo de estilo", "propertyTimeCodeFormat": "Formato do tempo", - "propertyFileExtension": "Extensão do ficheiro", "propertyTopOrigin": "Origem superior", "propertyTopExtent": "Extensão superior", "propertyBottomOrigin": "Origem inferior", @@ -1625,7 +1700,6 @@ "selectionIntersect": "Cruzar com seleção" }, "multipleReplace": { - "title": "Múltipla substituição", "editRule": "Editar regra", "newRule": "Nova regra", "editCategory": "Editar categoria", @@ -1642,24 +1716,23 @@ "findWhat": "Localizar:", "descriptionOptional": "Descrição (opcional)", "invalidRegularExpressionX": "Expressão regular inválida: {0}", - "regularExpressionTooSlowX": "A expressão regular desistiu ao fim de {0} segundos - a regra é ignorada, experimente um padrão mais simples" + "regularExpressionTooSlowX": "A expressão regular desistiu ao fim de {0} segundos - a regra é ignorada, experimente um padrão mais simples", + "moveToCategory": "Mover para categoria" }, "find": { "searchTextWatermark": "Pesquisar texto...", "wholeWord": "Palavra completa", - "caseSensitive": "_Diferenciar maiúsculas/minúsculas", - "caseInsensitive": "Não diferenciar maiúsculas de minúsculas", "findPrevious": "Localizar _anterior", "findNext": "Localizar _seguinte", "replaceAndFindNext": "_Substituir e localizar próximo", "replaceAll": "Substituir _tudo", - "replaceTitle": "Substituir", - "replaceWith": "Substituir por:", "replaceTextWatermark": "Substituir por...", "replaceIn": "Substituir/procurar em", "replaceInTextAndOriginal": "Texto e texto original", "replaceInTextOnly": "Apenas texto", - "replaceInOriginalOnly": "Apenas texto original" + "replaceInOriginalOnly": "Apenas texto original", + "replacedOneOccurrence": "Substituída uma ocorrência", + "replacedXOccurrences": "Substituídas {0} ocorrências" }, "regularExpressionContextMenu": { "wordBoundary": "Limite de palavra (\\b)", @@ -1687,6 +1760,7 @@ "xLinesYSubtitles": "{0} linhas, {1} legendas", "xLinesYSubtitlesZErrors": "{0} linhas, {1} legendas, {2} erro(s)", "couldNotParseAsX": "Não foi possível processar como {0}", + "readAsXUseAsY": "Não foi possível ler a origem como {1}, mas foi possível lê-la como {0}.\nUsar as linhas e continuar a guardar como {1}?", "selectedXCharactersYLines": "Selecionado: {0} caracteres em {1} linha(s)", "replacedXOccurrences": "{0} ocorrência(s) substituída(s)", "invalidRegularExpression": "Expressão regular inválida", @@ -1701,7 +1775,6 @@ "aiReview": { "title": "Revisão por IA", "review": "Rever", - "stop": "Parar", "editPrompt": "Editar prompt...", "editPromptTitle": "Editar prompt de revisão", "promptInfo": "Instruções enviadas ao modelo - {language} é substituído pelo idioma da legenda.", @@ -1712,7 +1785,6 @@ "reviewingLineXOfY": "A rever a linha {0} de {1}...", "reviewDone": "Revisão concluída - {0} sugestões em {1} linhas", "noIssuesFound": "Nenhum problema encontrado", - "categoryAll": "Todas", "categorySpelling": "Ortografia", "categoryGrammar": "Gramática", "categoryPunctuation": "Pontuação", @@ -1724,8 +1796,23 @@ "largeChangeWarning": "Alteração grande - parece uma reescrita, reveja com atenção", "mismatchWarning": "Não se assemelha ao original - pode pertencer a outra linha", "engineError": "Não foi possível contactar o motor de IA: {0}", - "setupHint": "Escolha primeiro um motor e um modelo em Ferramentas -> Revisão por IA.", - "playCurrentLineHint": "Reproduzir a linha selecionada no leitor de vídeo e pausar no seu fim (também pode fazer duplo clique numa linha)" + "playCurrentLineHint": "Reproduzir a linha selecionada no leitor de vídeo e pausar no seu fim (também pode fazer duplo clique numa linha)", + "editAfterHint": "Clique no texto da linha selecionada para editar a correção antes de a aplicar (Enter guarda, Escape cancela)", + "template": "Modelo", + "templatePlaceholder": "Escolha um modelo para substituir o prompt", + "templateStandard": "Revisão padrão", + "templateSpeechToText": "Limpeza de fala para texto", + "templateMachineTranslation": "Correção de tradução automática", + "templateSdh": "SDH / deficientes auditivos", + "referenceContext": "Contexto de referência", + "referenceContextInfo": "Opcional. Nomes, personagens, glossário, sinopse ou notas de estilo - o modelo trata isto como a autoridade para nomes e termos. Enviado com cada lote.", + "referenceContextPlaceholder": "Nomes: Kaito, Tia Mei, Dr. Hollis\nTermos: Shinkansen, núcleo de dobra\nSinopse: Kaito viaja até Tóquio para encontrar o pai desaparecido.", + "generateContext": "Gerar com IA", + "generateContextHint": "Deixar o motor de IA selecionado ler a legenda e esboçar nomes, termos e uma sinopse - verifique o resultado antes de usar", + "generatingContextPartXOfY": "A ler a parte {0} de {1}...", + "generatingContextSynopsis": "A escrever a sinopse...", + "replaceContextQuestion": "Substituir o contexto de referência atual?", + "noContextGenerated": "O motor de IA não devolveu informação de referência utilizável." }, "aiAssistant": { "title": "Assistente de IA", @@ -1743,22 +1830,14 @@ "showReasoning": "Mostrar o raciocínio do modelo" }, "fixCommonErrors": { - "title": "Corrigir erros frequentes", - "step1": "Passo 1/2 - selecionar os erros a corrigir", - "whatToFix": "O que corrigir", - "example": "Exemplo", - "selectAll": "Selec. tudo", - "inverseSelection": "Inverter seleção", "back": "< &Anterior", "next": "&Próximo >", - "step2": "Fase 2/2 - Verificar correções", "fixes": "Correções", "ruleDetailsDotDotDot": "Detalhes da regra...", "showOnlyThisRule": "Mostrar apenas as correções desta regra", "appliedRulesForLineX": "Regra(s) aplicada(s) à linha {0}", "ruleX": "Regra: {0}", "log": "Registo", - "function": "Função", "removedEmptyLine": "Remover linha em branco", "removedEmptyLineAtTop": "Remover linha inicial em branco", "removedEmptyLineAtBottom": "Remover linha final em branco", @@ -1779,13 +1858,13 @@ "removeLineBreaksPixelWidth": "Combinar legendas que cabem numa linha (largura de pixel)", "fixUppercaseIInsideLowercaseWords": "Corrigir 'i' maiúscula em palavras em minúsculo (erro de OCR)", "fixDoubleApostrophes": "Substituir apóstrofe dupla ('') por aspas (\")", + "fixMisreadQuotes": "Corrigir apóstrofos mal lidos como aspas duplas (OCR, inglês)", "addPeriods": "Adicionar ponto final quando a linha seguinte começar com maiúscula", "startWithUppercaseLetterAfterParagraph": "Iniciar parágrafo com maiúscula", "startWithUppercaseLetterAfterPeriodInsideParagraph": "Iniciar com maiúscula depois de ponto final dentro do parágrafo", "startWithUppercaseLetterAfterColon": "Iniciar com maiúscula depois de \":\" ou \";\"", "fixLowercaseIToUppercaseI": "Corrigir 'i' minúscula sozinha por 'I' maiúscula (Inglês)", "fixCommonOcrErrors": "Corrigir erros frequentes de OCR (usando lista de substituição OCR)", - "commonOcrErrorsFixed": "Erros frequentes de OCR corrigidos (ficheiro OcrReplaceList utilizado): {0}", "removeSpaceBetweenNumber": "Remover espaços entre números", "breakDialogsOnOneLine": "Dividir diálogos numa linha", "removeDialogFirstInNonDialogs": "Remover o travessão inicial na primeira linha para não diálogos", @@ -1802,18 +1881,17 @@ "fixCommasExample": ",, -> ,", "removeLineBreaksExample": "Foo
bar! -> Foo bar!", "fixDoubleApostrophesExample": "''Has double single quotes'' -> \"Has single double quote\"", + "fixMisreadQuotesExample": "\"Olá' -> \"Olá\"", "addPeriodsExample": "Hello world -> Hello world.", "startWithUppercaseLetterAfterParagraphExample": "p1: Foobar! || p2: foobar! -> p1: Foobar! || p2: Foobar!", "startWithUppercaseLetterAfterPeriodInsideParagraphExample": "Hello there! how are you? -> Hello there! How are you?", "startWithUppercaseLetterAfterColonExample": "Speaker: hello world! -> Speaker: Hello world!", "removeHyphensSingleLineExample": "- Foobar. -> Foobar.", "fix3PlusLinesExample": "Foo
bar
baz! -> Foo bar baz!", - "xMissingQuotesAdded": "Aspas em falta adicionadas: {0}", "fix3PlusLine": "Corrigir Linhas com mais de duas linhas", "fix3PlusLines": "Corrigir Linhas com mais de duas linhas", "analysing": "A analisar...", "nothingToFix": "Nada para corrigir :)", - "fixesFoundX": "Correções encontradas: {0}", "xFixesApplied": "Correções aplicadas: {0}", "xFixesYSelected": "{0} correções - {1} selecionadas", "nothingFixableBut": "Não pôde ser corrigido automaticamente. A legenda contém erros - veja o registo para mais detalhes", @@ -1822,7 +1900,6 @@ "fixFirstLetterToUppercaseAfterParagraph": "Corrigir primeira letra do parágrafo para maiúscula", "mergeShortLine": "Eliminar quebra de linha em linha curta (uma frase)", "mergeShortLineAll": "Eliminar quebra de linha em linha curta (exceto diálogos)", - "unbreakShortLinePixelWidth": "Combinar linha curta (largura de pixel)", "breakLongLine": "Dividir linha longa", "fixLongDisplayTime": "Corrigir duração longa", "fixInvalidItalicTag": "Corrigir etiqueta itálica inválida", @@ -1845,7 +1922,6 @@ "fixUppercaseIInsideLowercaseWord": "Corrigir letra maiúscula 'i' numa palavra minúscula", "fixMissingPeriodAtEndOfLine": "Adicionar ponto final em falta no final da linha", "refreshFixes": "Atualizar correções disponíveis", - "applyFixes": "Aplicar correções selecionadas", "autoBreak": "Ajuste &auto", "unbreak": "&Combinar", "fixDoubleDash": "Corrigir '--' -> '...'", @@ -1864,8 +1940,6 @@ "fixSpaceBetweenNumbersExample": "1 100 -> 1100", "fixDialogsOneLineExample": "Olá João! - Olá Olga! -> Olá João!
- Olá Olga!", "removeDialogFirstInNonDialogsExample": "- Como estás? -> Como estás?", - "selectDefault": "Predefinição", - "setDefault": "Definir as correções atuais como predefinição", "fixContinuationStyleX": "Corrigir estilo de continuação: {0}", "fixUnnecessaryLeadingDots": "Remover reticências iniciais desnecessárias", "fixCommonErrorsProfiles": "Corrigir perfis de erros comuns", @@ -1873,7 +1947,6 @@ "searchRulesDotDotDot": "Pesquisar regras...", "goToApplyFixes": "Avançar para aplicar correções", "backToFixList": "Voltar à lista de correções", - "applyFixesAndClose": "Aplicar correções e fechar", "fixCommonOcrErrorsStep2": "Corrigir erros comuns, passo 2 (aplicar correções)", "fixCommonOcrErrorsStep2FixesFoundX": "Corrigir erros comuns, passo 2 – correções encontradas: {0}", "action": "Ação", @@ -1886,18 +1959,11 @@ "adjustDurations": { "title": "Ajustar duração", "adjustVia": "Ajustar por", - "seconds": "Segundos", - "percent": "Percentagem", "recalculate": "Recalcular", "addSeconds": "Adicionar segundos", - "setAsPercent": "Definir a percentagem de duração", "note": "Nota: a duração não pode sobrepor o tempo de início do texto seguinte", "fixed": "Corrigido", - "milliseconds": "Milissegundos", "extendOnly": "Apenas prolongar", - "enforceDurationLimits": "Aplicar duração mínima e máxima", - "checkShotChanges": "Não prolongar para além das mudanças de cena", - "batchCheckShotChanges": "Respeitar as mudanças de cena (se disponível)", "recalculateRequiresOcrNote": "Recalcular não está disponível — uma ou mais legendas não têm texto OCR. Execute primeiro o OCR." }, "applyDurationLimits": { @@ -1905,6 +1971,8 @@ "fixMinDurationMs": "Corrigir duração mínima (ms)", "doNotGoPastShotChange": "Não ultrapassar mudança de cena", "fixMaxDurationMs": "Corrigir duração máxima (ms)", + "fixMinDurationFrames": "Corrigir duração mínima (fotogramas)", + "fixMaxDurationFrames": "Corrigir duração máxima (fotogramas)", "minimumDurationMilliseconds": "Duração mínima (milissegundos):", "maximumDurationMilliseconds": "Duração máxima (milissegundos):", "maxDurationShouldBeHigherThanMinDuration": "A duração máxima deve ser superior à duração mínima", @@ -1924,12 +1992,8 @@ }, "beautifyTimeCodes": { "title": "Otimizar tempos", - "beautifySettings": "Definições de otimização", "snapToFrames": "fotogramas", - "frameGap": "Espaço entre fotogramas", "minDuration": "Duração mínima (ms)", - "shotChangeThreshold": "Limite de mudança da cena (ms)", - "shotChangeOffset": "Deslocamento de mudança de cena (fotogramas)", "original": "Copiar texto do original para o atual", "beautified": "Otimizado", "subtitlesCount": "Legendas", @@ -1958,13 +2022,80 @@ "timeCodesExtractFailed": "Não foi possível ler os tempos do vídeo", "timeCodesHint": "Encaixa as deixas nos tempos reais dos fotogramas do vídeo em vez de assumir uma taxa de fotogramas constante. Requer a leitura de todo o vídeo uma vez; o resultado fica em cache." }, + "improveTimeCodes": { + "title": "Melhorar tempos (alinhamento forçado)", + "aligner": "Alinhador", + "alignerHint": "O modelo que procura cada linha no áudio. As melhores opções para o idioma da legenda são listadas primeiro.", + "maxShift": "Deslocamento máximo (segundos)", + "maxShiftHint": "Quão dessincronizada a legenda pode estar. Uma linha cujo início se moveria mais do que isso fica como está e é assinalada. Seja qual for o valor, uma linha isolada que queira mover-se mais de meio segundo além das linhas à sua volta não é movida às cegas: segue as vizinhas quando todas se moveram e, caso contrário, é apresentada desmarcada para que a verifique.", + "adjustStartTimes": "Ajustar tempos iniciais", + "adjustEndTimes": "Ajustar tempos finais", + "isolateSpeech": "Isolar primeiro a fala (lento)", + "isolateSpeechHint": "Remove a música e os efeitos sonoros antes de alinhar, para que o alinhador ouça apenas o diálogo. Ajuda em linhas faladas sobre música alta ou ação, mas demora cerca do mesmo tempo que o próprio áudio com uma GPU - e muitas vezes mais sem uma.", + "isolatingSpeech": "A isolar a fala... (isto demora algum tempo)", + "isolateSpeechFailed": "Não foi possível isolar a fala - foi alinhado com o áudio original.", + "checkWithSpeechToText": "Verificar com fala para texto", + "checkWithSpeechToTextHint": "Transcreve também o áudio e verifica cada deslocação face ao local onde as palavras foram realmente ouvidas. Deslocações grandes confirmadas pela fala são marcadas automaticamente, e deslocações para longe do local onde uma linha é ouvida ficam desmarcadas para que as verifique. Mostra também quanto de cada linha foi ouvido - uma percentagem baixa significa que o texto difere do que é dito. Uma legenda mais dessincronizada do que o deslocamento máximo - um desvio constante, uma deriva por diferença de taxa de fotogramas, ou saltos onde foram cortadas cenas - é primeiro sincronizada pelas palavras ouvidas. Utiliza o Crisp ASR Parakeet, que demora alguns minutos para uma longa-metragem com uma GPU.", + "checkWithSpeechToTextNotAvailableX": "A verificação com fala para texto não está disponível para {0}", + "transcribingToCheck": "A transcrever para verificar o alinhamento...", + "speechToTextFailed": "A fala para texto falhou - os tempos não foram verificados.", + "speechToTextX": "Fala para texto: {0}", + "speechToTextConfirmedXDisputedY": "Fala: {0} confirmadas, {1} contestadas", + "hardlyHeardX": "A fala para texto ouviu apenas {0}% das palavras da legenda. A legenda está no idioma falado? O alinhador não consegue posicionar uma tradução.", + "heard": "Ouvido", + "heardHint": "Quanto da linha a fala para texto ouviu. Uma percentagem baixa significa que o texto difere do que é dito - onde é mais provável que o alinhador erre.", + "statusConfirmedBySpeech": "Confirmada pela fala", + "statusDisputedBySpeech": "Ouvida onde estava", + "statusMovedWithSync": "Movida com a sincronização", + "syncedFirstXY": "Sincronizado primeiro: {0} s no início, {1} s no fim", + "showSpeechOnly": "Mostrar apenas a fala", + "showSpeechOnlyHint": "Desenhar a forma de onda alinhada a partir do áudio sem música e efeitos sonoros; a forma de onda original fica como está, para comparação. Disponível depois de a fala ter sido isolada - aqui, ou com \"Mostrar apenas a fala\" na forma de onda da janela principal.", + "align": "Alinhar", + "original": "Original", + "aligned": "Alinhado", + "intro": "Escolha um alinhador e prima \"Alinhar\" para procurar cada linha no áudio e ajustar os seus tempos.", + "extractingAudio": "A extrair o áudio...", + "aligningBatchXOfY": "A alinhar... lote {0} de {1}", + "extractAudioFailed": "Não foi possível extrair o áudio com o ffmpeg.", + "modelWillBeDownloaded": "{0} será transferido", + "summaryXRetimedYKeptZSkipped": "Reajustadas: {0} · Mantidas: {1} · Sem fala: {2}", + "meanShiftX": "Deslocamento médio: {0} ms", + "toCheckX": "A verificar: {0}", + "largeMovesToCheckX": "{0} linha(s) mover-se-iam muito sozinhas e ficam desmarcadas - ouça-as (F5 reproduz a linha alinhada, Shift+F5 a original) e marque as que estão certas.", + "linesToCheckX": "{0} linha(s) ficam desmarcadas para verificação - ouça-as (F5 reproduz a linha alinhada, Shift+F5 a original) e marque as que estão certas.", + "changeXOfY": "Alteração {0} de {1}", + "previousChange": "Alteração anterior", + "nextChange": "Alteração seguinte", + "startShift": "Início", + "endShift": "Fim", + "statusRetimed": "Reajustada", + "statusMovedWithNeighbours": "Movida com as vizinhas", + "statusLargeMoveUnconfirmed": "Deslocação grande - ouça e marque para aplicar", + "statusUnchanged": "Já está no lugar", + "statusNoSpeech": "Sem fala", + "statusShiftTooLarge": "Mantida - deslocamento demasiado grande", + "statusFailed": "Mantida - o alinhador falhou", + "statusAdjustedByHand": "Ajustada manualmente", + "alignedWaveformHint": "Arraste uma linha para a mover, ou arraste as suas extremidades para alterar o início ou o fim.", + "playLine": "Reproduzir linha", + "undoAdjustment": "Anular ajuste", + "playPause": "Reproduzir / pausar", + "playAligned": "Alinhada", + "playAlignedHint": "Reproduzir a linha selecionada com os seus tempos alinhados", + "apply": "Aplicar", + "applyHint": "Desmarque uma linha para manter os seus tempos originais. Faça duplo clique numa linha para a reproduzir. As linhas também podem ser movidas ou redimensionadas na forma de onda alinhada." + }, "beautifyTimeCodesProfile": { "title": "Editar perfil de otimização de códigos de tempo", "loadPreset": "Carregar predefinição", "presetDefault": "Predefinido", "presetNetflix": "Netflix", "presetSdi": "SDI", - "general": "Geral", + "saveAsProfile": "Guardar como perfil...", + "deleteProfile": "Eliminar perfil", + "profileName": "Nome do perfil", + "overwriteProfileX": "Já existe um perfil com o nome \"{0}\". Substituí-lo?", + "deleteProfileX": "Eliminar o perfil \"{0}\"?", "gap": "Intervalo", "gapSuffix": "fotogramas (também atualiza campos de pausa diferente de zero abaixo)", "milliseconds": "ms", @@ -1993,9 +2124,12 @@ "hintChaining": "Como lidar com legendas que quase se tocam – estenda-as juntas ou afaste-as, dependendo se uma mudança de cena ocorre no meio." }, "bridgeGaps": { - "title": "Unir intervalos", "bridgeGapsSmallerThan": "fotogramas", "minGap": "Pausa mínima (ms)", + "bridgeGapsSmallerThanFrames": "Unir intervalos menores que (fotogramas)", + "minGapFrames": "Intervalo mínimo (fotogramas)", + "calculateBridgeGapsSmallerThanDotDotDot": "Calcular o limite de união de intervalos a partir de uma taxa de fotogramas...", + "useXMsAsBridgeGapsSmallerThan": "Unir intervalos menores que \"{0}\" milissegundos?", "numberOfSmallGapsBridgedX": "Número de pequenos intervalos unidos: {0}", "percentFoPrevious": "Pausa para anterior (%)", "gapChange": "Mudança de pausa" @@ -2007,8 +2141,6 @@ "sortBy": { "title": "Classificar legendas", "sortOrder": "Ordem de classificação", - "sortByNumber": "Número", - "sortByStartTime": "Hora de início", "sortByEndTime": "Tempo final" }, "batchConvert": { @@ -2031,6 +2163,7 @@ "addColor": "Adicionar cor", "deleteLinesWithSpecificActorsOrStyles": "Eliminar linhas", "useSourceStylesIfPossible": "Utilizar estilos de origem, se possível", + "keepSourceEmbeddedFonts": "Manter tipos de letra incorporados da origem (rodapé)", "editStyles": "Editar estilos", "editProperties": "Editar propriedades", "editAttachments": "Editar anexos", @@ -2051,7 +2184,6 @@ "assaChangeStyleFromStyle": "Alterar estilo de", "assaChangeStyleToStyle": "para", "assaChangeStyleImportStyle": "Importar estilo...", - "assaChangeStyleImportedX": "Importado: {0}", "assaChangeStyleTrimUnusedStyles": "Cortar estilos não utilizados", "assaChangeStylePropertiesTitle": "Alterar propriedades do estilo", "assaChangeStylePropertiesInfo": "Altera os campos escolhidos em todos os estilos do ficheiro, sem mexer nos próprios estilos.", @@ -2063,13 +2195,52 @@ "convertColorsToDialogReBreakLines": "Requebrar as linhas", "snapTimeCodesToFramesInfo": "Arredonda todos os tempos de início e fim para o fotograma mais próximo. A taxa de fotogramas é lida a partir de um ficheiro de vídeo com o mesmo nome do ficheiro de legenda, a menos que seja escolhida uma taxa de fotogramas fixa.", "addFolderDotDotDot": "Adicionar pasta...", + "addFolderRecursiveDotDotDot": "Adicionar pasta com subpastas...", "selectFolderToConvert": "Selecionar a pasta com os ficheiros a converter", "includeSubfolders": "Incluir subpastas ao adicionar uma pasta", + "videoFilesWhenAddingFolder": "Ficheiros de vídeo (mkv, mp4, ts...) ao adicionar uma pasta", + "videoFilesAsk": "Perguntar", + "videoFilesInclude": "Adicionar as suas faixas de legendas", + "videoFilesSkip": "Ignorar", + "folderContainsXVideoFiles": "A pasta contém {0} ficheiro(s) de vídeo (mkv, mp4, ts...).\n\nAdicionar também as faixas de legendas incorporadas, ou apenas os ficheiros de legendas?", + "addVideoFiles": "Adicionar ficheiros de vídeo", + "skipVideoFiles": "Apenas ficheiros de legendas", + "doNotAskAgainVideoFiles": "Não perguntar novamente (alterar nas definições de Converter múltiplos ficheiros)", + "settingsSectionOutput": "Saída", + "settingsSectionAddingFiles": "Adicionar ficheiros", + "settingsSectionImageBasedSubtitles": "Legendas baseadas em imagens (OCR)", + "settingsSectionOther": "Outros", "keepSourceFileTimestamp": "Manter a data/hora do ficheiro de origem nos ficheiros de saída", - "scanningFolderX": "A analisar {0}..." + "preventSleepWhileConverting": "Impedir que o computador entre em suspensão durante a conversão", + "scanningFolderX": "A analisar {0}...", + "transportStreamSettings": "Definições de Transport Stream", + "transportStreamSettingsDotDotDot": "Definições de Transport Stream...", + "transportStreamSettingsInfo": "Aplica-se às faixas de legendas extraídas de ficheiros Transport Stream (.ts/.m2ts). A posição e o tamanho do vídeo só afetam faixas de imagem DVB exportadas para um formato baseado em imagens.", + "transportStreamOverrideXPosition": "Substituir a posição X original", + "transportStreamOverrideYPosition": "Substituir a posição Y original", + "transportStreamOverrideVideoSize": "Substituir o tamanho de vídeo original", + "transportStreamBottomMargin": "Margem inferior", + "transportStreamFileNameEnding": "Final do nome do ficheiro", + "transportStreamFileNameEndingInfo": "Adicionado antes da extensão para cada faixa extraída. Deixe vazio para usar o sufixo de idioma normal.", + "transportStreamOnlyTeletext": "Apenas teletexto", + "transportStreamGetSizeFromVideo": "Obter tamanho do vídeo...", + "twoLetterLanguageCodeUppercase": "Código de idioma de duas letras (maiúsculas)", + "threeLetterLanguageCodeUppercase": "Código de idioma de três letras (maiúsculas)", + "formatContainsDotDotDot": "O formato contém...", + "extensionIsDotDotDot": "A extensão é...", + "statusIsError": "O estado é erro", + "forcedTracksOnly": "Apenas faixas forçadas", + "xConvertedYFailedInZ": "{0:#,###,##0} convertidos, {1:#,###,##0} falharam em {2}", + "xNotProcessed": "{0:#,###,##0} não processados", + "removeUnicodeControlCharactersTitle": "Remover caracteres de controlo Unicode", + "removeUnicodeControlCharactersInfo": "Remove do texto os caracteres invisíveis de controlo de direção Unicode (LRM, RLM, LRE, RLE, PDF, LRO e RLO) e substitui os espaços não separáveis por espaços normais.", + "preset": "Predefinição", + "savePresetDotDotDot": "Guardar predefinição...", + "deletePreset": "Eliminar predefinição", + "presetName": "Nome da predefinição", + "deletePresetX": "Eliminar a predefinição \"{0}\"?" }, "changeCasing": { - "title": "Inverter maiúsculas/minúsculas", "fixNames": "Nome", "namesX": "Nomes: {0:#,##0}", "hitsX": "Ocorrências: {0:#,##0}", @@ -2080,9 +2251,7 @@ "allUppercase": "TODAS MAIÚSCULAS", "allLowercase": "todas minúsculas" }, - "changeFormatting": { - "title": "Alterar formatação" - }, + "changeFormatting": {}, "convertActors": { "title": "Converter atores", "convertActorFrom": "Converter ator de", @@ -2111,18 +2280,12 @@ "outputFormatAssa": "Advanced Sub Station Alpha (.ass)", "style1": "Estilo 1", "style2": "Estilo 2", - "outlineWidth": "Largura do contorno", - "shadowWidth": "Largura da sombra", - "alignTop": "Superior", - "alignBottom": "Inferior", - "merge": "_Combinar", - "pleaseLoadTwoSubtitles": "carregue duas legendas" + "merge": "_Combinar" }, "splitSubtitle": { "title": "Dividir legenda", "numberOfEqualParts": "Número de partes iguais", "saveSplitParts": "_Guardar partes divididas", - "subtitleSplitIntoXParts": "Legenda dividida em {0} partes.", "xPartsSavedInFormatYToFolder": "{0} partes guardadas no formato {1} na pasta:" }, "splitBreakLongLines": { @@ -2156,18 +2319,17 @@ "columnMerge": "Combinar", "columnFirst": "Primeira linha", "columnSecond": "Segunda linha", - "columnMerged": "Combinado", - "selectAll": "Selecionar tudo", - "inverseSelection": "Inverter seleção" + "columnMerged": "Combinado" }, "mergeLinesWithSameText": { - "title": "Combinar as linhas com o mesmo texto", "maxMsBetweenLines": "Máximo de milissegundos entre linhas", - "includeIncrementingLines": "Incluir linhas com texto incremental" + "maxFramesBetweenLines": "Máximo de fotogramas entre linhas", + "includeIncrementingLines": "Incluir linhas com texto incremental", + "includeRollUpCaptions": "Incluir legendas roll-up (em deslocamento)" }, "mergeLinesWithSameTimeCodes": { - "title": "Combinar linhas com os mesmos tempos", "maxMsDifference": "Diferença máxima (milissegundos)", + "maxFramesDifference": "Diferença máxima (fotogramas)", "makeDialog": "Fazer diálogos" }, "netflixCheckAndFix": { @@ -2190,7 +2352,6 @@ "shotChanges": "Mudanças de cena", "startNumberSpellOut": "Número inicial falado", "textforHiUseBrackets": "Texto para deficientes auditivos, utilizar parênteses retos", - "frameRate": "Taxa de fotogramas", "twoFrameGrap": "Pausa de dois fotogramas", "whiteSpace": "Espaço em branco", "childrensProgram": "Programa infantil", @@ -2228,7 +2389,6 @@ "italicsFixed": "Itálicos corrigidos", "italicsNotAllowed": "Itálico não permitido", "bridgeGapsXToYFrames": "3-{0} pausa de fotogramas => pausa de 2 fotogramas", - "startNumberSpellOutComment": "Quando um número inicia uma frase, deve ser sempre escrito por extenso", "maximumXCharactersPerSecond": "Máximo de {0} caracteres por segundo" }, "imageBasedEdit": { @@ -2238,10 +2398,8 @@ "resizeImagesDotDotDot": "Redimensionar imagens...", "adjustBrightnessDotDotDot": "Alterar brilho...", "adjustAlphaDotDotDot": "Alterar alpha...", - "centerHorizontally": "Centrar horizontalmente", "cropImages": "Cortar imagens", "importTimeCodes": "Importar códigos de tempo...", - "setTextForSubtitle": "Definir texto para legenda", "screenWidth": "Largura do ecrã", "screenHeight": "Altura do ecrã", "alphaThresholdInfo": "Pixels com alfa abaixo do limite tornam-se totalmente transparentes", @@ -2271,15 +2429,31 @@ "letterboxCustom": "Altura de barra personalizada", "barHeightPx": "Altura da barra (px)", "titleSafePercent": "Área segura de título (%)", + "moveCaptions": "Mover legendas", + "moveCaptionsDotDotDot": "Mover legendas...", + "moveCaptionsTo": "Mover legendas", + "moveCaptionsIntoBars": "Para as barras de letterbox (fora da imagem)", + "moveCaptionsIntoPicture": "Para dentro da imagem (fora das barras)", + "moveCaptionsOffset": "Distância da margem (px)", + "letterboxBarHeightXPxYOfZCaptionsWillMove": "Altura da barra: {0} px - {1} de {2} legendas serão movidas", + "removeFadeInOut": "Remover fade in/out", + "removeFadeInOutXLinesRemoved": "{0} linhas de fade combinadas com as linhas a que pertencem", + "removeFadeInOutNothingFound": "Nenhuma linha de fade in/out encontrada", "positionSummary": "{0}×{1} - {2} legendas - altura da barra: {3} px", "xInActivePicture": "{0} na imagem", "xInTopBar": "{0} na barra superior", "xInBottomBar": "{0} na barra inferior", "noImageSubtitlesLoaded": "Nenhuma legenda de imagem carregada", - "imageBasedFormatNotSupported": "Formato de legenda baseado em imagens não encontrado/não suportado." + "imageBasedFormatNotSupported": "Formato de legenda baseado em imagens não encontrado/não suportado.", + "frameRateDeclaredInFile": "Taxa de fotogramas declarada no ficheiro Blu-ray sup", + "frameRateDetectedFromTimeCodesX": "Taxa de fotogramas detetada a partir dos tempos do Blu-ray sup (o ficheiro declara {0})", + "frameRateFromVideo": "Taxa de fotogramas do vídeo", + "frameRateCurrent": "Taxa de fotogramas atual", + "frameRateSetManually": "Taxa de fotogramas definida manualmente", + "frameRateUsageInfo": "As exportações Blu-ray sup, BDN XML, DOST e FCP são escritas com esta taxa de fotogramas. Não altera nenhum tempo - utilize \"Alterar a taxa de fotogramas\" para isso.", + "useVideoFrameRateXInsteadOfY": "A taxa de fotogramas do vídeo é {0}, mas a legenda está definida para {1}.\n\nUsar a taxa de fotogramas do vídeo ({0})?" }, "removeTextForHearingImpaired": { - "title": "Remover texto para deficientes auditivos", "interjections": "Interjeições", "skipIfStartWith": "Ignorar se começar por", "removeTextBetween": "Remover texto entre", @@ -2298,7 +2472,6 @@ "removeInterjections": "Remover interjeições", "linesFoundX": "Linhas encontradas: {0}" }, - "pickAlignmentTitle": "Ferramentas", "removeUnicodeCharacters": { "title": "Remover/substituir caracteres Unicode", "charactersFoundX": "Caracteres Unicode encontrados: {0}", @@ -2308,13 +2481,13 @@ "pickFontNameInstalledFonts": "Tipos de letra instalados", "pickFontNameCollectedFonts": "Tipos de letra recolhidos", "colorPickerTitle": "Escolher cor", + "colorPickerBrightness": "Brilho", "filterLayersTitle": "Filtrar camadas para visualização", "filterLayersHideFromWaveform": "Ocultar da forma de onda/espectrograma", "filterLayersHideFromSubtitleGrid": "Ocultar da grelha de legendas", "filterLayersHideFromVideoPreview": "Ocultar da visualização do vídeo", "pickSubtitleFormat": "Escolher formato da legenda", "pickSubtitleFormatImageBasedNoPreview": "Formato de legenda baseado em imagem.\nOs bitmaps são gerados durante a conversão - sem pré-visualização de texto.", - "pickLayerTitle": "Definir camada", "recentColors": "Cores recentes", "fixCommonErrorsSettingsTitle": "Definições de correção de erros comuns" }, @@ -2359,59 +2532,51 @@ "correctWordsX": "Palavras corretas: {0}", "namesX": "Nomes: {0}", "addedToDictionaryX": "Adicionadas ao dicionário: {0}", - "doNotShowThisAgain": "Não mostrar esta mensagem novamente" + "doNotShowThisAgain": "Não mostrar esta mensagem novamente", + "editUseAlwaysList": "Editar lista \"Utilizar sempre\"...", + "useAlwaysListTitle": "Verificação ortográfica - lista \"Utilizar sempre\"", + "useAlwaysListHeader": "Lista \"Utilizar sempre\"", + "useAlwaysListDescription": "Palavras que a verificação ortográfica substitui automaticamente, guardadas por \"Alterar tudo\" e \"Utilizar sempre\". Um par só é usado quando a palavra está mal escrita.", + "useAlwaysWord": "Palavra mal escrita", + "xPairs": "{0:#,##0} pares", + "xUnusedPairs": "{0:#,##0} nunca usados", + "useAlwaysUnusedHint": "Nunca usado - esta palavra está escrita corretamente (ou é um nome ou palavra do utilizador), por isso a verificação ortográfica nunca a assinala", + "removeUnused": "Remover não usados", + "useAlwaysEmpty": "Ainda não há pares \"Utilizar sempre\" para este idioma", + "useAlwaysEmptyHint": "Os pares são adicionados quando clica em \"Alterar tudo\" ou \"Utilizar sempre\" na verificação ortográfica.", + "useAlwaysNoMatch": "Nenhum par corresponde à pesquisa", + "useAlwaysSameWord": "A substituição tem de ser diferente da palavra.", + "useAlwaysRememberOff": "\"Lembrar lista 'Utilizar sempre'\" está desativado nas Definições, por isso a verificação ortográfica não usa nem guarda esta lista." }, "video": { "burnIn": { "title": "Gerar vídeo com legendas fixas", - "infoAssaOff": "Nota: suporta o estilo de Advanced Sub Station Alpha.", - "infoAssaOn": "Nota: será utilizado o estilo de Advanced Sub Station Alpha :)", - "xGeneratedWithBurnedInSubsInX": "\"{0}\" gerado com legenda embutida em {1}.", - "timeRemainingMinutes": "Tempo restante: {0} minutos", - "timeRemainingOneMinute": "Tempo restante: Um minuto", - "timeRemainingSeconds": "Tempo restante: {0} segundos", - "timeRemainingAFewSeconds": "Tempo restante: Alguns segundos", - "timeRemainingMinutesAndSeconds": "Tempo restante: {0} minutos e {1} segundos", - "timeRemainingOneMinuteAndSeconds": "Tempo restante: Um minuto e {0} segundos", - "targetFileName": "Nome do ficheiro de destino: {0}", "targetFileSize": "Tamanho do ficheiro de destino (requer codificação de 2 passagens)", "fileSizeMb": "Tamanho do ficheiro em MB", "matchSourceVideoSize": "Corresponder ao tamanho do vídeo original", - "passX": "Passagem {0}", - "encoding": "Codificação", "bitRate": "Taxa de bits", "totalBitRateX": "Taxa de bits total: {0}", "sampleRate": "Taxa de amostra", "audio": "Áudio", - "stereo": "Estéreo", "preset": "Predefinição", + "tune": "Tune", "pixelFormat": "Formato de pixéis", "crf": "CRF", - "tuneFor": "Sintonizar para", - "alignRight": "Alinhar à direita", - "getStartPosition": "Posição inicial", - "getEndPosition": "Posição final", "useSource": "Utilizar original", - "useSourceFolder": "Utilizar pasta de origem", - "useSourceResolution": "Utilizar resolução original", - "outputSettings": "Ficheiro/pasta de saída...", "fontSizeFactor": "Fator de tamanho do tipo de letra", "boxType": "Tipo de caixa", - "fixRightToLeft": "Corrigir da direita para a esquerda", - "cut": "Cortar", "fromTime": "Do tempo", "toTime": "Até tempo", "audioEncoding": "Codificação do áudio", "outputProperties": "Propriedades de saída...", "videoFileSize": "Tamanho do ficheiro de vídeo", "oneBox": "Uma caixa", - "boxPerLine": "Caixa por linha", - "logoInfo": "Escolher uma imagem PNG e arraste-a para a posicionar no vídeo." + "logoInfo": "Escolher uma imagem PNG e arraste-a para a posicionar no vídeo.", + "noSubtitlesGenerateAnyway": "Não há linhas de legenda para incorporar.\n\nGerar o vídeo sem legendas?", + "noSubtitlesInXItemsGenerateAnyway": "{0} item(ns) do lote não têm linhas de legenda para incorporar.\n\nGerar esses vídeos sem legendas?" }, "videoTransparent": { - "title": "Abrir segunda legenda...", - "infoAssaOff": "Nota: suporta o estilo de Advanced Sub Station Alpha.", - "infoAssaOn": "Nota: será utilizado o estilo de Advanced Sub Station Alpha :)" + "title": "Abrir segunda legenda..." }, "audioToText": { "title": "Fala para texto", @@ -2419,6 +2584,21 @@ "translateToEnglish": "Traduzir para Inglês", "transcribing": "A transcrever áudio para texto...", "transcribingXOfY": "A transcrever {0} de {1}...", + "linesTranscribedXOfYFailedZ": "{0} de {1} linhas foram transcritas - {2} falharam ou não tinham fala.\n\nAs linhas que falharam mantêm o texto atual. Aplicar as linhas transcritas ou tentar novamente as que falharam (pode alterar primeiro o motor ou as definições)?", + "linesTranscribedXOfYCancelled": "A transcrição foi cancelada após {0} de {1} linhas.\n\nAplicar as linhas transcritas? As outras linhas mantêm o texto atual.", + "noLinesTranscribed": "Nenhuma das {0} linhas pôde ser transcrita.\nConsulte o registo das ferramentas para mais detalhes.", + "applyTranscribedLines": "Aplicar transcritas", + "retryFailedLines": "Repetir falhadas", + "isolateSpeech": "Isolar a fala (lento)", + "isolateSpeechHint": "Remove a música e os efeitos sonoros antes de transcrever. Resulta em menos linhas perdidas e melhores tempos em áudio com música alta, mas demora cerca do mesmo tempo que o próprio áudio com uma GPU - e muitas vezes mais sem uma.", + "isolatingSpeech": "A isolar a fala...", + "isolateSpeechFailed": "Não foi possível isolar a fala - a transcrever o áudio original.", + "detectSpeakers": "Detetar falantes", + "detectSpeakersHint": "Inicia cada linha com o falante, como \"(speaker 0)\", usando a diarização Sortformer da NVIDIA (até 8 falantes). Funciona melhor com motores que dividem as linhas quando o falante muda, como Parakeet e Cohere.", + "detectSpeakersNeedsNewerCrispAsr": "\"Detetar falantes\" requer o Crisp ASR v{0} ou mais recente, mas está instalada a v{1}.\n\nAtualize o Crisp ASR (o botão de engrenagem junto ao motor) ou desative \"Detetar falantes\".", + "xNeedsNewerCrispAsr": "{0} requer o Crisp ASR v{1} ou mais recente, mas está instalada a v{2}.\n\nTransferir e instalar a atualização agora?", + "vad": "VAD", + "vadHint": "Deteção de atividade de voz: apenas a fala é transcrita, cortada onde o falante faz pausas.\nAutomático: Silero, e apenas para os motores que precisam (Cohere, Mega, Index-Echo).\nSilero, FireRedVAD, Whisper-VAD, WebRTC: sempre ativo, para todos os motores Crisp ASR. O FireRedVAD é o mais preciso, o Whisper-VAD dá menos cortes e mais limpos, mas é o mais lento.\nO Index-Echo usa sempre o Silero. --vad, --vad-model ou --chunk-seconds nas definições avançadas têm prioridade.", "inputLanguage": "Idioma de entrada", "advancedWhisperSettings": "Definições avançadas do Whisper", "downloadingSpeechToTextEngine": "A descarregar motor de fala para texto", @@ -2433,8 +2613,6 @@ "addCustomModelDotDotDot": "Adicionar modelo personalizado...", "customModelHelp": "Pode utilizar o seu próprio modelo: escolha o ficheiro do modelo ('.bin' ggml do whisper.cpp) ou a pasta do modelo (pasta faster-whisper com um 'model.bin' no interior). É copiado para a pasta de modelos e adicionado à lista acima.", "viewToolsLogFile": "Ver ficheiro de registo do Whisper", - "reDownloadX": "Descarregar novamente {0}", - "downloadX": "Transferir o {0}", "updateXTitle": "Atualizar {0}?", "updateXMessage": "Está disponível uma versão mais recente de {0}.{1}{1}Transferir e instalar a atualização agora?", "downloadingSpeechToTextModel": "Descarregar modelo de fala para texto", @@ -2446,14 +2624,6 @@ "fixCasing": "Corrigir maiúsculas/minúsculas", "addPeriods": "Adicionar pontos", "changeUnderlineToColor": "Alterar sublinhado para cor", - "cueBuilding": "Construção de cues (MLX Whisper)", - "cueRebuild": "Reconstruir cues a partir das marcas de tempo das palavras", - "cueMaxChars": "Máximo de caracteres por cue", - "cueMaxSeconds": "Máximo de segundos por cue", - "cueMaxCps": "Máximo de caracteres por segundo", - "vocabularyPrompt": "Prompt de vocabulário (nomes, lugares, termos)", - "beamSize": "Beam size", - "beamSizeNote": "0 descodifica mais rápido. 5 dá a melhor precisão (como o Faster Whisper) com cerca de 20-30% mais tempo. Valores acima de 5 raramente ajudam.", "removeNonSpeechLines": "Remover linhas sem fala", "removeNonSpeechLinesHint": "Eliminar as linhas que apenas descrevem sons, como \"[Música]\" ou \"(ondas a rebentar)\"", "removeRepeatedLines": "Remover linhas repetidas", @@ -2512,7 +2682,7 @@ "cloneVoicePerLinePreparing": "A obter a voz de cada linha a partir do vídeo...", "cloneVoicePerLineNeedsVideo": "Para clonar a voz de cada linha é necessário o vídeo a que a legenda pertence. Abra o vídeo e tente novamente.", "cloneVoicePerLineNoClips": "Não foi possível obter áudio do vídeo, pelo que não há nada a partir do qual clonar. Verifique se o vídeo tem uma faixa de áudio.", - "cloneVoicePerLineNeedsOriginalSubtitleX": "O {0} só consegue clonar uma linha quando sabe o que é dito no vídeo nessa linha, o que vem da legenda no idioma original. Não está carregada nenhuma legenda original, pelo que todas as linhas seriam ditas pela primeira voz importada em vez da voz do vídeo.\n\nAbra a legenda original (Ficheiro > Abrir original...) ao lado da tradução e gere novamente.", + "cloneVoicePerLineTranscribeClipsXYZ": "{0} só consegue clonar uma linha quando sabe o que é dito no vídeo nessa linha. Isso não é conhecido para {1} de {2} linhas - normalmente vem da legenda no idioma original (Ficheiro > Abrir original...).\n\nTranscrever agora essas linhas a partir do vídeo com fala para texto?\n\nSim: escolha o motor e o idioma falado no vídeo, e os excertos de referência são transcritos antes de a fala ser gerada.\nNão: gerar mesmo assim - essas linhas são faladas por uma voz comum em vez da voz do vídeo.", "voiceCloneConsentTitle": "Clonagem de voz - antes de começar", "voiceCloneConsentHeader": "Está prestes a clonar uma voz", "voiceCloneConsentIntro": "A clonagem copia a voz de uma pessoa real. Isso implica regras na maioria dos locais e, na UE, deveres legais que recaem sobre si e não sobre o Subtitle Edit. Leia isto uma vez.", @@ -2528,6 +2698,12 @@ "voiceCloneConsentDeclined": "A clonagem de voz não está disponível enquanto estes termos não forem aceites.", "advancedTtsAudioProcessing": "Processamento de áudio", "advancedTtsOutput": "Saída", + "advancedTtsVoiceCloning": "Clonagem de voz", + "cleanCloneReferences": "Limpar referências de clonagem de voz (Sidon)", + "cleanCloneReferencesDescription": "Remove música, ruído e reverberação do áudio a partir do qual uma voz é clonada: excertos retirados do vídeo e gravações importadas no gestor de vozes. Utiliza o modelo de restauro de fala Sidon no audio.cpp (transferência de 940 MB). A voz clonada deixa de copiar o fundo, mas a fala muito baixa ou sobreposta pode ficar abafada.", + "cleaningCloneReferencesDotDotDot": "A limpar as referências de clonagem de voz...", + "downloadSidonTitle": "Transferir Sidon?", + "downloadSidonQuestionX": "A limpeza das referências de clonagem de voz requer o modelo de restauro de fala Sidon ({0}).\n\nTransferi-lo agora? Sem ele, as referências são usadas tal como estão.", "rubberbandInstalled": "(instalado)", "rubberbandNotFound": "(não encontrado no FFmpeg)", "generationFolder": "Pasta de geração", @@ -2536,14 +2712,11 @@ "deleteTempFilesDescription": "Elimina a pasta de geração quando a janela Texto para fala é fechada. Os ficheiros de áudio e de vídeo guardados são mantidos.", "piperSettings": "Definições do Piper", "title": "Texto para voz", - "textToSpeechEngine": "Motor de conversão de texto em fala", "reviewAudioSegments": "TTS - Analisar segmentos de áudio", "reviewAudioSegmentsHistory": "TTS - Rever histórico de áudio", "stability": "Estabilidade", "similarity": "Semelhança", "speakerBoost": "Aumento do alto-falante", - "styleExaggeration": "Exagero de estilo", - "regenerateAudioSelectedLine": "Regenerar", "generateSpeechFromText": "Gerar voz a partir de texto", "testVoice": "Teste de voz", "addAudioToVideoFile": "Adicionar áudio ao ficheiro de vídeo (novo ficheiro)", @@ -2562,22 +2735,98 @@ "videoEncodingSettings": "TTS - Definições de codificação de vídeo", "elevenLabsSettings": "TTS - Definições do ElevenLabs", "elevenLabsSettingsResetHint": "Repor as definições do ElevenLabs para os valores predefinidos", + "provider": "Fornecedor", + "openAiCompatibleSettings": "TTS - Definições compatíveis com OpenAI", + "customServerUrl": "URL de servidor personalizado", + "customServerUrlHint": "Endpoint de fala completo de um servidor compatível com OpenAI, usado quando o fornecedor é \"Custom\", por ex. http://localhost:8880/v1/audio/speech", + "customModels": "Modelos personalizados", + "customModelsHint": "Nomes de modelos separados por vírgulas, oferecidos quando o fornecedor é \"Custom\"", + "customVoices": "Vozes personalizadas", + "customVoicesHint": "Nomes de vozes separados por vírgulas, usados pelo fornecedor \"Custom\" e pelos modelos OpenRouter que não publicam lista de vozes", + "instructions": "Instruções", + "instructionsHint": "Estilo de fala opcional, por ex. \"Fala com uma voz calma e calorosa\". Enviado apenas para modelos que o suportam (não tts-1/tts-1-hd)", + "openAiSpeedHint": "Velocidade da fala de 0.25 a 4.0 (1.0 é normal). Nem todos os modelos a suportam", + "outputFormat": "Formato de saída", + "outputFormatHint": "Formato de áudio pedido ao servidor. \"Auto\" pede MP3 e muda para PCM em modelos que só devolvem PCM (por ex. Gemini TTS)", "regenerateAudio": "Regenerar áudio", "autoContinuePlaying": "Continuar automaticamente", "addingAudioToVideoFileDotDotDot": "A adicionar áudio ao ficheiro de vídeo...", "preparingMergeDotDotDot": "A preparar combinação...", "importVoiceDotDotDot": "Importar voz...", + "renameVoiceDotDotDot": "Mudar o nome da voz...", + "renameVoiceTitle": "Mudar o nome da voz", + "voiceXCouldNotBeRenamedX": "Não foi possível mudar o nome da voz '{0}': {1}", + "deleteVoiceDotDotDot": "Eliminar voz...", + "deleteVoiceTitle": "Eliminar voz", + "deleteVoiceXQuestion": "Eliminar a voz '{0}'?\n\nA gravação de referência e os respetivos ficheiros são removidos do disco.", + "voiceXCouldNotBeDeletedX": "Não foi possível eliminar a voz '{0}': {1}", "voiceImportSuccessTitle": "Voz importada", "voiceXImported": "Voz '{0}' importada com sucesso", "dropAudioFileHereToImportVoice": "Largue o ficheiro de áudio aqui para importar a voz", "dropAudioFileHereHint": ".wav ou .mp3", "voiceCloneTranscriptTitle": "Introduza a transcrição do áudio (obrigatório para clonagem de voz)", + "voiceManagerTitle": "TTS - Gestor de vozes", + "voiceManagerDotDotDot": "Gestor de vozes...", + "voiceManagerMenuItem": "Gestor de vozes TTS...", + "speakFromCurrentLineTitle": "Falar a partir da linha atual", + "speakFromCurrentLineDotDotDot": "Falar a partir da linha atual...", + "speakFromCurrentLineHint": "Lê as linhas em voz alta, uma após outra, começando na linha atual. Prima Escape ou clique noutra linha para parar.", + "stopSpeaking": "Parar de falar", + "speakingLineX": "A falar a linha {0}... (Escape para parar)", + "playWithSpeechTitle": "Reproduzir com fala", + "playWithSpeechDotDotDot": "Reproduzir com fala...", + "playWithSpeechHint": "Reproduz o vídeo e fala cada linha quando o vídeo a alcança. Pause e procure como habitualmente; prima Escape para parar.", + "lowerVideoVolumeWhileSpeaking": "Baixar o volume do vídeo durante a fala", + "pauseVideoWhenSpeechRunsLate": "Pausar o vídeo quando a fala invade a linha seguinte", + "waitingForSpeechLineX": "A gerar a fala da linha {0}... (Escape para parar)", + "voiceKindClone": "Voz clonada", + "voiceKindPreset": "Incorporada", + "voiceKindModel": "Modelo de voz", + "voiceKindOnline": "Online", + "transcript": "Transcrição", + "transcriptRequiredHint": "Obrigatório - o motor lê esta transcrição da gravação (ref-text) ao clonar", + "transcriptOptionalHint": "Opcional - uma transcrição precisa da gravação melhora a clonagem", + "saveTranscript": "Guardar transcrição", + "searchVoices": "Pesquisar vozes", + "loadingVoicesDotDotDot": "A carregar vozes...", + "noVoicesForEngine": "Sem vozes - importe uma gravação ou transfira um pacote de vozes", + "noVoicesMatchFilter": "Nenhuma voz corresponde à pesquisa", + "copyVoiceTo": "Copiar voz para", + "copyVoiceToDotDotDot": "Copiar voz para motor...", + "copyingVoiceXToYDotDotDot": "A copiar a voz '{0}' para {1}...", + "voiceXCopiedToY": "Voz '{0}' copiada para {1}", + "voiceXCouldNotBeCopiedToY": "Não foi possível copiar a voz '{0}' para {1} - consulte o registo para mais detalhes", + "voiceXAlreadyExistsInYContinue": "Já existe uma voz com o nome '{0}' em {1}.\n\nCopiar mesmo assim?", + "openVoicesFolder": "Abrir pasta de vozes", + "downloadVoicePacksDotDotDot": "Transferir pacotes de vozes...", + "downloadVoicePacksTitle": "TTS - Transferir pacotes de vozes", + "voicePackIntro": "Os pacotes de vozes são conjuntos de gravações curtas e limpas com transcrições, prontas para clonar. Cada pacote é importado para o motor escolhido (as vozes já existentes são ignoradas) e inclui um ATTRIBUTION.txt com a respetiva licença e fontes.", + "voicePackDetailsXVoicesYSizeZLicense": "{0} vozes · {1} · {2}", + "installToEngine": "Instalar no motor", + "downloadAndInstall": "Transferir e instalar", + "selectAtLeastOneVoicePack": "Selecione pelo menos um pacote de vozes", + "downloadingXDotDotDot": "A transferir {0}...", + "installingXDotDotDot": "A instalar {0}...", + "xVoicesInstalledYSkipped": "{0} vozes instaladas ({1} já existentes)", "useSpeechToTextDotDotDot": "Utilizar fala para texto...", "advancedTtsSettings": "Definições avançadas de TTS", "proAudioPostProcessing": "Pós-processamento de áudio profissional", "proAudioPostProcessingDescription": "Aplica calor de equalização, gate de ruído, compressão, normalização de volume (-16 LUFS) e fade in/out a cada segmento.", + "generatingSpeechSegmentXOfY": "A gerar a fala: segmento {0} de {1}", + "adjustingSpeedSegmentXOfY": "A ajustar a velocidade: segmento {0} de {1}", + "mergingAudioSegmentXOfY": "A combinar o áudio: segmento {0} de {1}", + "postProcessingSegmentXOfY": "Pós-processamento: segmento {0} de {1}", "audioDucking": "Ducking de áudio", "audioDuckingDescription": "Reduz o volume do áudio do vídeo original e mistura-o com o áudio TTS, para que a faixa de áudio original ainda seja ligeiramente audível.", + "removeOriginalSpeech": "Remover a fala original (lento)", + "removeOriginalSpeechDescription": "Mantém a música e os efeitos sonoros do vídeo original, mas remove a sua fala e mistura a nova fala por cima. Demora cerca do mesmo tempo que o próprio vídeo com uma GPU, e muitas vezes mais sem uma. Com o ducking de áudio ativo, o seu volume aplica-se à música e aos efeitos. Só se aplica quando a fala é adicionada ao ficheiro de vídeo.", + "removingOriginalSpeech": "A remover a fala original...", + "removeOriginalSpeechFailed": "Não foi possível remover a fala original - a fala foi adicionada ao vídeo sem essa remoção.", + "speechRemovalModel": "Modelo", + "speechRemovalModelDescription": "A melhor qualidade preserva o máximo da música e dos efeitos sonoros. O rápido é cerca de três vezes mais rápido, perdendo um pouco mais da música.", + "speechRemovalBestQuality": "Melhor qualidade (Mel-Band RoFormer)", + "speechRemovalFast": "Rápido (HTDemucs)", + "videoTruncatedWarning": "A imagem no novo ficheiro de vídeo para após {0}, mas o vídeo original tem {1} de duração - a imagem ficará congelada enquanto o som continua.\n\nO ficheiro foi mantido. Consulte error-log.txt na pasta de dados do Subtitle Edit para ver a saída do ffmpeg.", "originalVolumePercent": "% do volume original", "vadSilenceCompression": "Compressão de silêncio VAD", "vadSilenceCompressionDescription": "Encurta as pausas entre as palavras antes de alterar o andamento. Utiliza deteção de atividade de voz para comprimir apenas as pausas de silêncio, mantendo a fala intacta. Este é o primeiro passo preferido – reduz a duração sem qualquer perda de qualidade.", @@ -2644,7 +2893,6 @@ "subtitleMergedLinesAppliedPlural": "Aplicadas {0} junções de linhas da conversão de texto em fala" }, "shotChanges": { - "titleGenerateOrImport": "Gerar/importar mudanças de cena", "generateShotChanges": "Gerar mudanças de cena", "importShotChanges": "Importar mudanças de cena", "generateShotChangesWithFfmpeg": "Gerar mudanças de cena com o FFmpeg", @@ -2654,7 +2902,9 @@ "timeCodeFormatColon": "Formato do código de tempo:", "shotChangesList": "Lista de mudanças de cena", "shotChangesClearQuestion": "Tem a certeza de que pretende limpar todas as mudanças de cena?", - "deleteSelectedShotChangeQuestion": "Tem a certeza de que pretende eliminar a mudança de cena selecionada?" + "deleteSelectedShotChangeQuestion": "Tem a certeza de que pretende eliminar a mudança de cena selecionada?", + "shotChanges": "Mudanças de cena", + "exportShotChanges": "Exportar mudanças de cena" }, "chapters": { "title": "Capítulos", @@ -2684,7 +2934,6 @@ "scaleTimesDescription": "Ajustar a escala do tempo de cada capítulo de uma taxa de fotogramas para outra.", "fromFrameRate": "De", "toFrameRate": "Para", - "apply": "Aplicar", "writeToVideo": "Escrever no vídeo...", "writeToVideoTitle": "Escrever os capítulos no vídeo", "writeToVideoDescription": "Os capítulos são escritos numa cópia do ficheiro de vídeo. Nada é recodificado, pelo que é rápido e sem perdas.", @@ -2694,7 +2943,6 @@ "writtenToX": "Capítulos escritos em {0}", "unableToWriteChapters": "Não foi possível escrever os capítulos no ficheiro de vídeo.", "chaptersSavedToX": "Capítulos guardados em {0}", - "noVideoLoaded": "Nenhum vídeo carregado", "emptyListCallToAction": "Ainda não há capítulos - adicione um na posição do vídeo, ou importe-os de um vídeo ou ficheiro." }, "videoOcr": { @@ -2705,10 +2953,6 @@ "bottomHalf": "Metade inferior", "fullFrame": "Fotograma completo", "engine": "Motor de OCR", - "language": "Idioma", - "url": "URL", - "model": "Modelo", - "apiKey": "Chave API", "scan": "Analisar", "framesPerSecond": "Fotogramas por segundo", "textBrightnessMinimum": "Brilho mínimo do texto (0=desativado)", @@ -2723,10 +2967,10 @@ "runningOcrXY": "A executar OCR... {0}/{1}", "noLinesFoundTitle": "Nenhuma legenda encontrada", "noLinesFoundMessage": "Não foi encontrado texto na área de análise - experimente ajustar a área de análise, o motor ou o brilho mínimo.", + "ocrStoppedEarlyMessage": "O motor de OCR parou antes de todos os fotogramas serem lidos, por isso as linhas encontradas até agora são mantidas e as restantes estão em falta.{0}{0}{1}", "linesFoundX": "{0} linhas encontradas", "refiningTimingXY": "A afinar os tempos... {0}/{1}", "fixOcrErrors": "Corrigir erros de OCR", - "dictionary": "Dicionário", "editLineX": "Editar linha {0}", "downloadDictionaryHint": "Transferir um dicionário de verificação ortográfica...", "framesPerSecondHint": "Quantos fotogramas por segundo são analisados. 5 cobre legendas normais; valores mais altos apanham linhas muito curtas e melhoram o agrupamento, mas custam mais tempo de OCR.", @@ -2743,7 +2987,58 @@ "testOcr": "Testar fotograma atual", "testOcrRunning": "A testar OCR no fotograma atual...", "testOcrResultX": "Resultado do teste: {0}", - "testOcrNoTextFound": "Teste: nenhum texto encontrado na área de análise" + "testOcrNoTextFound": "Teste: nenhum texto encontrado na área de análise", + "engineSettings": "Definições do motor de OCR", + "engineBuiltIn": "Incorporado no sistema operativo", + "engineExternalServer": "Servidor externo (instalado e gerido fora do Subtitle Edit)", + "engineCloudApi": "API na nuvem (requer uma chave de API)", + "engineNothingToInstall": "Nada a instalar", + "engineModelsMissing": "Motor instalado, modelos em falta", + "website": "Website" + }, + "backgroundMusic": { + "title": "Gerar música de fundo", + "generateBackgroundMusicDotDotDot": "Gerar música de fundo...", + "preset": "Predefinição", + "prompt": "Prompt", + "tempoBpm": "Andamento (BPM)", + "generateSeconds": "Gerar (segundos)", + "seed": "Semente", + "randomSeed": "Aleatória", + "outputLength": "Duração da saída", + "outputLengthVideoX": "{0} (duração do vídeo - a música repete-se sem cortes)", + "musicVolumePercent": "Volume da música (%)", + "removeExistingAudioTracks": "Remover faixas de áudio existentes", + "originalAudioVolumePercent": "Volume do áudio original (%)", + "saveAudioDotDotDot": "Guardar áudio...", + "addToVideoDotDotDot": "Adicionar ao vídeo...", + "engineInfo": "ACE-Step 1.5 Turbo via audio.cpp (licença MIT) - executado localmente", + "presetCooking": "Culinária / pastelaria", + "presetTech": "Tecnologia / análise de gadgets", + "presetForest": "Floresta / natureza", + "presetRelaxingPiano": "Piano relaxante", + "presetLofiVlog": "Vlog lo-fi", + "presetCorporate": "Empresarial / explicativo", + "loadingModel": "A carregar o modelo...", + "composing": "A compor...", + "generatingAudio": "A gerar o áudio...", + "decodingAudio": "A descodificar o áudio...", + "creatingLoop": "A criar repetição sem cortes...", + "resultLoopXYZ": "Repetição: {0} a {1} BPM, correspondência da junção {2}%", + "resultNoLoopX": "Música pronta: {0}", + "addingMusicToVideo": "A adicionar música ao vídeo...", + "downloadModelTitle": "Transferir modelo de música?", + "downloadModelQuestionX": "A geração de música requer o modelo ACE-Step 1.5 ({0}).\n\nTransferir o modelo?", + "downloadingModel": "A transferir o modelo ACE-Step 1.5...", + "saveAudioTitle": "Guardar música de fundo", + "audioFileSavedX": "Ficheiro de áudio guardado: {0}", + "unableToGenerateMusic": "Não foi possível gerar a música", + "unableToAddMusicToVideo": "Não foi possível adicionar a música ao vídeo", + "outputLengthTextToSpeech": "Segue a fala gerada - a música repete-se sem cortes e baixa enquanto alguém fala", + "addBackgroundMusic": "Adicionar música de fundo", + "addBackgroundMusicHint": "Gerar música localmente com o ACE-Step 1.5 e misturá-la por baixo da fala - baixa enquanto alguém fala", + "generatingBackgroundMusicDotDotDot": "A gerar música de fundo...", + "mixingBackgroundMusicDotDotDot": "A misturar música de fundo..." }, "goToVideoPosition": "Posição vídeo:", "goToVideoPositionDotDotDot": "Ir para a posição do vídeo...", @@ -2754,30 +3049,88 @@ "openSecondarySubtitleOnVideoPlayerDotDotDot": "Abrir legenda secundária no leitor de vídeo...", "openSecondarySubtitleOnVideoPlayer": "Legenda secundária (no leitor de vídeo)", "removeSecondarySubtitleOnVideoPlayer": "Remover legenda secundária do leitor de vídeo", + "editSecondarySubtitleSettings": "Editar definições da legenda secundária", + "editSecondarySubtitleSettingsDotDotDot": "Editar definições da legenda secundária...", + "secondarySubtitleRememberSettings": "Lembrar estas definições", + "secondarySubtitleDoNotShowAgain": "Não mostrar esta caixa de diálogo novamente", + "secondarySubtitleJustifyLines": "Justificar as linhas", "openRecentVideo": "Abrir vídeo recente", "clearRecentVideos": "Limpar vídeos recentes", "cutVideoTitle": "Cortar vídeo", "cutVideoDotDotDot": "Cortar vídeo...", + "remuxVideoTitle": "Remultiplexar vídeo", + "remuxVideoDotDotDot": "Remultiplexar vídeo...", + "remuxVideoInputVideo": "Vídeo de entrada:", + "remuxVideoMultipleTracksRequiresMkv": "Várias faixas de áudio ou de legendas requerem o contentor MKV. O formato de saída foi automaticamente alterado para .mkv.", + "remuxVideoOutputFormat": "Formato de saída:", + "remuxVideoOutputFile": "Ficheiro de saída:", + "remuxVideoRemuxing": "A remultiplexar vídeo e áudio...", + "remuxVideoFinalizing": "A finalizar o ficheiro de saída (a mover o índice para o início)...", + "remuxVideoCompleted": "Vídeo remultiplexado com êxito.", + "remuxVideoFailed": "Falha ao remultiplexar o vídeo.", + "remuxVideoPleaseSelectBoth": "Selecione os ficheiros de vídeo e de áudio.", + "remuxVideoAssRequiresMkv": "As legendas ASS/SSA requerem o contentor MKV para preservar toda a formatação e estilos.", + "remuxVideoSccRequiresMov": "As legendas Scenarist (.scc) são incorporadas como closed captions CEA-608, que requerem o contentor MOV ou MPG. O formato de saída foi automaticamente alterado para .mov.", + "remuxVideoSccNotWithWebM": "As closed captions Scenarist (.scc) precisam do contentor MOV, que não pode conter o vídeo VP9/AV1 de um ficheiro .webm. Remova o ficheiro .scc ou converta-o para outro formato de legenda.", + "remuxVideoMpgMaxTwoCaptionTracks": "Um ficheiro .mpg contém no máximo duas faixas de closed captions (CC1 e CC3) - remova ficheiros de legendas para que restem uma ou duas.", + "remuxVideoMccRequiresMpg": "As legendas MacCaption (.mcc) são incorporadas como closed captions CEA-608/708 no vídeo MPEG-2, o que requer o contentor MPG. O formato de saída foi automaticamente alterado para .mpg.", + "remuxVideoMpgReencodeVideoX": "As closed captions num ficheiro .mpg precisam de vídeo MPEG-2, e o vídeo é {0}. Recodificar o vídeo para MPEG-2? Isto demora mais e reduz um pouco a qualidade.", + "remuxVideoFileHasNoAudioX": "\"{0}\" não tem faixa de áudio, por isso não pode ser usado como fonte de áudio.", + "remuxVideoAddingClosedCaptions": "A adicionar closed captions...", + "remuxVideoSelectAudioTrack": "Selecionar faixa de áudio", + "remuxVideoSelectAudioTrackFor": "Selecionar faixa de áudio para {0}", + "remuxVideoSelectAudioTrackPrompt": "O ficheiro '{0}' contém {1} faixas de áudio. Selecione a faixa de áudio a remultiplexar:", + "remuxVideoVideoFile": "vídeo", + "remuxVideoAudioFile": "áudio", + "remuxVideoAudioFiles": "Ficheiros de áudio:", + "remuxVideoSubtitleFiles": "Ficheiros de legendas (opcional, legendas não incorporadas na imagem):", + "remuxVideoSelectAudioTrackDotDotDot": "Selecionar faixa de áudio...", + "remuxVideoFilesX": "{0} ficheiros", + "remuxVideoMixAudio": "Misturar o áudio numa só faixa", + "remuxVideoVolumePercent": "Volume %:", + "remuxVideoVolumeX": "Volume {0}%", + "remuxVideoFastStart": "Otimizar para streaming na web (mover o índice para o início - mais lento)", "embedSubtitlesDotDotDot": "Adicionar/remover legendas incorporadas...", "generateTimeCodes": "Gerar tempos", "checkeredImage": "Imagem axadrezada", - "saveVideoAsTitle": "Guardar vídeo como", "promptForFfmpegParamsAndGenerate": "Pedir parâmetros do FFmpeg e gerar", "cutVideoCutSegments": "Cortar segmentos", "cutVideoMergeSegments": "Unir segmentos", "cutVideoSplitSegments": "Guardar segmentos individualmente", - "cutVideoType": "Tipo de corte", - "mpvRenderAuto": "Auto", - "mpvRenderNative": "Nativo", - "mpvRenderOpenGl": "OpenGL", - "mpvRenderSoftware": "Software (lento)", "cutVideoAlsoCutSubtitle": "Cortar também a legenda", "cutVideoSubtitleFileGeneratedX": "Ficheiro de legenda gerado: {0}", + "cutVideoTransition": "Transição entre segmentos", + "cutVideoTransitionDurationSeconds": "Duração (segundos)", + "cutVideoFadeInSeconds": "Aparecer a partir do preto no início (segundos)", + "cutVideoFadeOutSeconds": "Desvanecer para preto no fim (segundos)", + "cutVideoPreviewTransition": "Pré-visualizar transição", + "cutVideoPreviewTransitionTitle": "Pré-visualização da transição", + "cutVideoPreviewNeedsJoin": "Não há nenhuma junção para pré-visualizar - adicione pelo menos dois segmentos para combinar, ou um segmento para cortar do meio.", + "cutVideoTransitionInfoX": "Cada transição sobrepõe as partes que junta, por isso a saída fica {0} segundos mais curta por junção. Uma legenda cortada é reajustada em conformidade.", + "cutVideoTransitionFade": "Desvanecer", + "cutVideoTransitionFadeBlack": "Desvanecer através do preto", + "cutVideoTransitionFadeWhite": "Desvanecer através do branco", + "cutVideoTransitionFadeGrays": "Desvanecer através do cinzento", + "cutVideoTransitionDissolve": "Dissolver", + "cutVideoTransitionWipeLeft": "Varrer para a esquerda", + "cutVideoTransitionWipeRight": "Varrer para a direita", + "cutVideoTransitionWipeUp": "Varrer para cima", + "cutVideoTransitionWipeDown": "Varrer para baixo", + "cutVideoTransitionSlideLeft": "Deslizar para a esquerda", + "cutVideoTransitionSlideRight": "Deslizar para a direita", + "cutVideoTransitionSlideUp": "Deslizar para cima", + "cutVideoTransitionSlideDown": "Deslizar para baixo", + "cutVideoTransitionSmoothLeft": "Suave para a esquerda", + "cutVideoTransitionSmoothRight": "Suave para a direita", + "cutVideoTransitionCircleOpen": "Círculo a abrir", + "cutVideoTransitionCircleClose": "Círculo a fechar", + "cutVideoTransitionRadial": "Radial", + "cutVideoTransitionPixelize": "Pixelizar", + "cutVideoTransitionBlur": "Desfocar", "importCurrentSubtitle": "Importar legenda atual", "addRemoveEmbeddedSubtitlesTitle": "Adicionar/remover legendas incorporadas", "addRemoveEmbeddedSubtitlesMp4Title": "Adicionar/remover legendas incorporadas (MP4)", "addCurrentSubtitle": "Adicionar legenda atual", - "embeddedTrackColumnNew": "Novo", "embeddedTrackNoSubtitlesFoundTitle": "Nenhuma legenda encontrada", "embeddedTrackNoSubtitlesFoundMessage": "O ficheiro selecionado não contém legendas.", "embeddedTrackNoTracksTitle": "Nenhuma faixa adicionada", @@ -2789,7 +3142,7 @@ "embeddedTrackGeneratingVideoXY": "A gerar vídeo... {0}% {1}", "embeddedTrackGeneratingVideo": "A gerar vídeo...", "mp4FilesFilter": "Ficheiros MP4", - "titleOrLanguage": "Título/idioma", + "embeddedTracksPickVideoHint": "Escolha um ficheiro de vídeo, ou largue-o aqui, para ver as suas faixas de legendas.", "viewMatroskaTrackX": "Ver faixa Matroska - {0}", "resolutionSeparator": "x", "openFromUrlTitle": "Abrir ficheiro de vídeo da URL", @@ -2799,9 +3152,7 @@ "openFromUrlDownloadAndOpen": "Descarregar e abrir", "openFromUrlDownloadAndOpenDescription": "Guardar o vídeo localmente e abri-lo.", "openFromUrlDownloadAndOpenNote": "Necessário para picos de fala para texto e forma de onda.", - "openFromUrlMissing": "Introduza primeiro o URL do vídeo.", "openFromUrlDownloadingTitle": "A descarregar vídeo", - "openFromUrlSaveAs": "Guardar vídeo como", "openFromUrlDownloadSubtitles": "Descarregar também as legendas", "openFromUrlDownloadVideo": "Transferir vídeo", "openFromUrlIncludeAutoGeneratedSubtitles": "Incluir legendas geradas automaticamente (idioma original)", @@ -2838,12 +3189,15 @@ "reEncodeGeneratingDone": "Geração concluída", "reEncodeGeneratedFilesX": "Ficheiros gerados ({0}):", "reEncodeFfmpegParameters": "Parâmetros do FFmpeg", + "analyzingVideoX": "A analisar o vídeo... {0}% {1}", + "analyzingVideoXofY": "A analisar o vídeo {0}/{1}... {2}% {3}", + "backgroundImageFileNotSelected": "Ficheiro de imagem de fundo não selecionado", + "backgroundImageFileDoesNotExistX": "O ficheiro de imagem de fundo não existe: {0}", "voices": "Vozes", "presets": "Predefinições", "talkerX": "Locutor {0}", "baseModel": "Modelo base", "turboModel": "Modelo turbo", - "ttsUpdateX": "Atualizar {0}", "ttsEngineNotInstalled": "{0} não instalado", "ttsEngineUpdateAvailable": "{0} - atualização disponível", "ttsEngineTooOldUpdateRequired": "{0} demasiado antigo - é necessário atualizar", @@ -2866,19 +3220,25 @@ "cloneVoiceTo": "Clonar voz para", "cloneVoiceNameTitle": "Nome da nova voz", "cloneVoiceExtractFailed": "Não foi possível extrair o áudio da linha selecionada.", + "lineXHasNoDuration": "A linha {0} não tem duração (o tempo final não é posterior ao tempo inicial), por isso não há áudio para extrair.", + "audioClipsSkippedLinesX": "Não foi possível extrair áudio para estas linhas, por isso foram ignoradas: {0}", "toggleShotChange": "Alternar a mudança de cena", "resetWaveformZoomAndSpeed": "Repor zoom e velocidade da forma de onda", "showOriginalSubtitle": "Mostrar a legenda original", + "timelineGroupTracksBy": "Agrupar faixas de legendas por", + "timelineShowThumbnails": "Mostrar miniaturas do vídeo", + "showSpeechOnly": "Mostrar apenas a fala (lento a gerar)", + "isolatingSpeechForWaveformX": "A isolar a fala para a forma de onda... {0}", + "isolatingSpeechForWaveformFailed": "Não foi possível isolar a fala para a forma de onda - consulte o registo das ferramentas", + "speechOnlyWaveformReady": "A forma de onda mostra agora apenas a fala", "showOnlyWaveform": "Mostrar apenas forma de onda", "showOnlySpectrogram": "Mostrar apenas espectrograma", "showWaveformAndSpectrogram": "Mostrar forma de onda e espectrograma", - "spectrogramClassic": "Clássico", "spectrogramClassicViridis": "Viridis", "spectrogramClassicPlasma": "Plasma", "spectrogramClassicInferno": "Inferno", "spectrogramClassicTurbo": "Turbo", "spectrogramNeon": "Néon", - "waveformDrawStyleClassic": "Clássico", "waveformDrawStyleFancy": "Sofisticado", "setVideoPositionAndPauseAndSelectSubtitle": "Definir posição do vídeo, pausar e selecionar legenda", "setVideopositionAndPauseAndSelectSubtitleAndCenter": "Definir posição do vídeo, pausar, selecionar legenda e centrar", @@ -2888,7 +3248,6 @@ }, "sync": { "visualSync": "Sincronia visual", - "sync": "Sincronizar", "startScene": "Cena inicial", "endScene": "Cena final", "playTwoSecondsAndBack": "Reproduzir 2 segundos e voltar", @@ -2904,19 +3263,18 @@ "adjustSelectedLinesAndForward": "Ajustar linhas selecionadas e avançar", "toFrameRate": "Para a taxa de fotogramas", "fromFrameRate": "Da taxa de fotogramas", + "videoXFrameRateY": "{0} - {1} fps", "adjustAllTimes": "Ajustar todos os tempos (mostrar antes/depois)...", "showEarlier": "Mostrar _antes", "showLater": "Mostrar _depois", - "changeFrameRate": "Alterar a taxa de fotogramas", "setSyncPoint": "Definir ponto de sincronia", "setSyncPointViaVideo": "Definir ponto de sincronização através do vídeo...", "syncPointTimeCode": "Tempo do ponto de sincronização", "syncPoints": "Pontos de sincronização", "pointSync": "Ponto de sincronia...", "pointSyncViaOther": "Pontos de sincronização via outra legenda", - "adjustmentX": "Ajuste: {0}", - "totalAdjustmentX": "Total de ajustes: {0}", - "syncPointCandidateInfo": "As linhas que começam após {0}+ segundos de silêncio são realçadas - costumam dar pontos de sincronização fiáveis", + "syncPointCandidateAfterInfo": "As linhas seguidas de {0}+ segundos de silêncio estão realçadas - costumam ser pontos de sincronização fiáveis", + "gapAfter": "Intervalo depois", "adjustAllShortcuts": "Atalhos de teclado:\n\n• Shift + Esquerda/Direita: Mover 10 ms\n• Ctrl + Esquerda/Direita: Mover 100 ms\n• Alt + Esquerda/Direita: Mover 500 ms", "adjustAllShortcutsFrames": "Atalhos de teclado:\r\n\r\n• Shift + Esquerda/Direita: Mover 1 fotograma\r\n• Ctrl + Esquerda/Direita: Mover 10 fotogramas\r\n• Alt + Esquerda/Direita: Mover 1 segundo", "offsetInSeconds": "Deslocamento em segundos", @@ -2936,6 +3294,7 @@ "lineMerge": "Unir linha", "delayInSecondsBetweenRequests": "Atraso em segundos entre pedidos", "maxBytesPerRequest": "Máximo de bytes por pedido", + "keepMusicLinesUntranslated": "Não traduzir linhas entre símbolos de música (♪ ... ♪)", "promptText": "Texto do pedido", "resetPromptToDefault": "Repor a instrução predefinida", "translateEachLineSeparately": "Traduzir cada linha separadamente", @@ -2949,11 +3308,10 @@ "readyToTranslate": "Pronto para traduzir", "translating": "A traduzir...", "translationComplete": "Tradução concluída", + "translateInPlaceNoOriginal": "Traduzir no local (não criar original)", "translationCancelled": "Tradução cancelada", "swapLanguages": "Trocar idiomas de origem e de destino", "xIsAlreadyDownloadedReDownload": "{0} já foi descarregado. Descarregar novamente?", - "advancedDotDotDot": "Avançado...", - "advancedSettings": "Definições avançadas", "advancedSettingsSubtitle": "Opções de lote, contexto e amostragem para o motor avançado llama.cpp", "context": "Contexto", "synopsis": "Sinopse", @@ -2977,6 +3335,7 @@ "useOnlyExtraServerParametersHint": "Iniciar o llama-server com os parâmetros acima em vez do ajuste próprio do Subtitle Edit - apenas o modelo, o anfitrião e a porta continuam a ser definidos", "serverRunningAtX": "Servidor em execução em {0}", "customPromptHint": "Instruções personalizadas ({0} = idioma de origem, {1} = idioma de destino); vazio = instrução integrada", + "editTranslationHint": "Clique na tradução da linha selecionada para a editar (Enter guarda, Escape cancela)", "llamaCppDownloadEngineAndModelPrompt": "O llama.cpp requer a transferência do motor llama-server e de um modelo de tradução. Transferir agora?", "llamaCppDownloadEnginePrompt": "O llama.cpp requer a transferência do motor llama-server. Transferir agora?", "llamaCppDownloadModelPrompt": "O llama.cpp requer a transferência do modelo de tradução selecionado. Transferir agora?" @@ -3014,8 +3373,14 @@ "checkForUpdatesChannelStableAndBeta": "Versões estáveis e beta", "assaAutoSetResolution": "ASSA: definir a resolução (PlayResX/PlayResY) a partir do vídeo quando um vídeo é aberto", "assaAutoSetResolutionPrompt": "ASSA: perguntar antes de alterar a resolução para corresponder ao vídeo (caso contrário, reamostrar automaticamente)", + "videoControls": "Controlos de vídeo", + "videoControlsEdit": "Editar controlos de vídeo...", + "videoControlsPositionText": "Texto de posição / duração", + "videoControlsVideoFileName": "Nome do ficheiro de vídeo", + "videoControlsPlayerName": "Nome do leitor de vídeo", "mergeKeepEndTime": "Combinar linhas: manter o tempo de fim (permitir sobreposição com a legenda seguinte)", "mergeKeepEndTimeOnlyAssa": "Combinar linhas: manter o tempo de fim apenas para \"Advanced Sub Station Alpha\"", + "mergeAutoBreak": "Combinar linhas: quebrar automaticamente o texto combinado (desativado mantém as quebras de linha originais)", "defaultSaveLocation": "Localização de gravação predefinida", "defaultSaveLocationSourceFileFolder": "Pasta do ficheiro de legenda/vídeo", "defaultSaveLocationLastUsedFolder": "Última pasta utilizada", @@ -3023,6 +3388,7 @@ "defaultSaveLocationSubtitleFileFolder": "Pasta do ficheiro de legenda", "defaultSaveLocationCustomFolder": "Pasta personalizada", "subtitleGridFormattingHideTags": "Ocultar etiquetas", + "subtitleGridFormattingShowFormattingKeepTags": "Mostrar formatação, manter etiquetas não visuais", "musicSymbolsToReplace": "Símbolos musicais a substituir (separados por vírgulas)", "musicSymbol": "Símbolo musical", "colorWordsPerMinute": "Colorir palavras/min se muito alto", @@ -3033,7 +3399,6 @@ "dialogStyleDashBothLinesWithSpace": "Travessão em ambas as linhas com espaço", "dialogStyleDashBothLinesWithoutSpace": "Travessão em ambas as linhas sem espaço", "continuationStyle": "Estilo de continuação", - "continuationStyleNone": "Nenhum", "continuationStyleNoneTrailingDots": "Nenhum, pontos para pausas (apenas para continuação)", "continuationStyleNoneLeadingTrailingDots": "Nenhum, pontos para pausas", "continuationStyleNoneTrailingEllipsis": "Nenhum, reticências para pausas (apenas para continuação)", @@ -3044,7 +3409,6 @@ "continuationStyleLeadingTrailingEllipsis": "Reticências", "continuationStyleLeadingTrailingDash": "Travessão", "continuationStyleLeadingTrailingDashDots": "Travessão, mas pontos para pausas", - "continuationStyleCustom": "Personalizado", "cpsLineLengthStyle": "Cps/comprimento da linha", "cpsLineLengthStyleCalcAll": "Contar todos os caracteres", "cpsLineLengthStyleCalcNoSpaceCpsOnly": "Contar tudo exceto espaços, apenas CPS", @@ -3057,27 +3421,11 @@ "cpsLineLengthStyleCalcNoSpaceOrPunctuationCpsOnly": "Sem espaço ou pontuação, apenas CPS", "cpsLineLengthStyleCalcIgnoreArabicDiacritics": "Ignorar diacríticos árabes", "cpsLineLengthStyleCalcIgnoreArabicDiacriticsNoSpace": "Ignore diacríticos árabes, sem espaço", - "timeCodeModeHhMmSsMs": "HH:MM:SS.MS (00:00:01.500)", - "timeCodeModeHhMmSsFf": "HH:MM:SS:FF", - "splitBehaviorPrevious": "Adicionar intervalo à esquerda do ponto de divisão (enfoque direito)", - "splitBehaviorHalf": "Adicionar intervalo no centro do ponto de divisão (enfoque esquerdo)", - "splitBehaviorNext": "Adicionar intervalo à direita do ponto de divisão (enfoque esquerdo)", - "subtitleListActionNothing": "Não fazer nada", - "subtitleListActionVideoGoToPositionAndPause": "Ir para posição do vídeo e pausar", - "subtitleListActionVideoGoToPositionAndPlay": "Ir para posição do vídeo e reproduzir", "subtitleListActionVideoGoToPositionAndPlayCurrentAndPause": "Ir para posição do vídeo, reproduzir atual e pausar", - "subtitleListActionEditText": "Ir para a caixa de edição de texto", "subtitleListActionVideoGoToPositionMinus1SecAndPause": "Ir para posição do vídeo - 1 seg e pausar", "subtitleListActionVideoGoToPositionMinusHalfSecAndPause": "Ir para posição do vídeo - 0.5 seg e pausar", "subtitleListActionVideoGoToPositionMinus1SecAndPlay": "Ir para posição do vídeo - 1 seg e reproduzir", - "subtitleListActionEditTextAndPause": "Ir para a caixa de edição de texto e pausar na posição do vídeo", - "autoBackupEveryMinute": "A cada minuto", - "autoBackupEveryXthMinute": "A cada {0} minuto", - "profiles": "Perfis", - "autoBackupDeleteAfterXMonths": "{0} meses", "searchSettingsDotDotDot": "Pesquisar definições...", - "syntaxColoring": "Cor da sintaxe", - "waveformSpectrogram": "Forma de onda/espectrograma", "network": "Rede", "fileTypeAssociations": "Associações de tipos de ficheiro", "textBoxColorTags": "Fotograma de texto", @@ -3110,10 +3458,9 @@ "darkThemeForegroundColor": "Cor de primeiro plano do tema escuro", "darkThemeBackgroundColor": "Cor de fundo do tema escuro", "showGridLines": "Mostrar linhas na grelha", - "resetSettings": "Repor definições?", - "resetSettingsDetail": "Isto irá repor todas as definições para os seus valores predefinidos.\n\nContinuar?", "showHorizontalLineAboveToolbar": "Barra de ferramentas", "bookmarkColor": "Cor do marcador", + "spellCheckHighlightColor": "Cor de realce da verificação ortográfica", "singleLineMaxLength": "Comprimento máximo de linha única", "optimalCharsPerSec": "Caracteres/seg ideais", "maxCharsPerSec": "Máximo de caracteres/s", @@ -3126,13 +3473,21 @@ "unbreakSubtitlesShortThan": "Eliminar quebra em linha menor que...", "newEmptyDefaultMs": "Duração predefinida da nova legenda (ms)", "timeCodeUpDownStepMs": "Incremento de tempo para cima/baixo (ms)", + "moveSelectedLinesStepMs": "Passo do atalho para mover linhas selecionadas (ms)", + "moveStartEndStepMs": "Passo do atalho para mover início/fim (ms)", + "moveLinesShortenNeighbor": "Mover linhas: encurtar a linha anterior/seguinte em vez de a sobrepor", "promptBeforeDelete": "Avisar antes de eliminar", "rememberPositionAndSize": "Lembrar tamanho e posição da janela principal", + "titleBarFullFileName": "Mostrar o caminho completo do ficheiro na barra de título", + "showRecentFiles": "Mostrar ficheiros recentes (para reabrir)", "openLastFileOnStart": "Abrir último ficheiro recente ao iniciar", "autoSave": "Guardar automaticamente (guardar o ficheiro aberto durante a edição)", "autoBackupOn": "Cópia de segurança automática", "autoBackupIntervalMinutes": "Intervalo da cópia de segurança automática (minutos)", "autoBackupDeleteAfterDays": "Eliminar cópias de segurança automáticas após (dias)", + "settingsBackupOn": "Cópia de segurança automática das definições", + "settingsBackupIntervalDays": "Intervalo da cópia de segurança das definições (dias, 0 = cada início)", + "settingsBackupMaxCount": "Cópias de segurança das definições a manter", "autoConvertToUtf8": "Converter automaticamente para UTF-8 ao abrir", "autoTrimWhiteSpace": "Corte automático de espaços em branco", "defaultEncoding": "Codificação predefinida", @@ -3146,23 +3501,14 @@ "colorGapTooShort": "Cor se a pausa for muito curta", "errorBackgroundColor": "Cor de fundo de erro", "waveformDrawGridLines": "Desenhar linhas de grelha", - "waveformCenterVideoPosition": "Centrar posição do vídeo", + "waveformUseSkiaRenderer": "Usar renderizador rápido experimental", "waveformCenterVideoPositionAlsoWhenPaused": "Centrar a posição do vídeo também durante a pausa", + "waveformSelectCurrentSubtitleWhilePaused": "Selecionar a legenda atual também em pausa", + "waveformRememberZoom": "Lembrar zoom (horizontal e vertical)", "waveformShowToolbar": "Mostrar barra de ferramentas", "waveformShowToolbarEdit": "Editar barra de ferramentas...", "waveformShowToolbarEditLabel": "Itens da barra de ferramentas", "waveformSpectrogramCombinedWaveformHeight": "Forma de onda/espectrograma combinados, % de altura da forma de onda", - "showWaveformToolbarPlay": "Barra de ferramentas: mostrar botão de reprodução", - "showWaveformToolbarRepeat": "Barra de ferramentas: mostrar botão de repetição", - "showWaveformToolbarRemoveBlankLines": "Remover", - "showWaveformToolbarNew": "Barra de ferramentas: mostrar botão de nova legenda", - "showWaveformToolbarSetStart": "Barra de ferramentas: mostrar botão definir início", - "showWaveformToolbarSetEnd": "Barra de ferramentas: mostrar definição do fim", - "showWaveformToolbarStartAndOffsetTheRest": "Barra de ferramentas: mostrar botão definir início e botão de descanso", - "showWaveformToolbarHorizontalZoom": "Barra de ferramentas: mostrar controlo deslizante de zoom horizontal", - "showWaveformToolbarVerticalZoom": "Zoom vertical entrada", - "showWaveformToolbarVideoPositionSlider": "Barra de ferramentas: mostrar controlo deslizante da posição do vídeo", - "showWaveformToolbarPlaybackSpeed": "Barra de ferramentas: mostrar velocidade de reprodução", "waveformFocusTextboxAfterInsertNew": "Focar fotograma de texto", "waveformInvertMouseWheel": "Inverter a roda do rato", "waveformMouseWheelSetsVideoPosition": "A roda do rato define a posição do vídeo", @@ -3182,20 +3528,20 @@ "downloadFfmpeg": "Descarregar o FFmpeg", "showPluginsMenu": "Mostrar menu de plugins", "proxyAddress": "Endereço proxy", - "username": "Utilizador", "password": "Palavra-passe", "defaultFormat": "Formato predefinido", "defaultSaveAsFormat": "- Auto -", "favoriteSubtitleFormats": "Nota: os formatos favoritos serão mostrados primeiro ao selecionar um formato, o formato predefinido será sempre mostrado primeiro", "favoriteLanguages": "Idiomas favoritos", "webVttUseXTimestampMap": "WebVTT: utilizar X-TIMESTAMP-MAP (deslocar códigos de tempo ao carregar)", - "showStopButton": "Mostrar botão (Parar)", - "showFullscreenButton": "Ecrã inteiro", "fullscreenHideControls": "Ocultar controlos de vídeo em ecrã inteiro", + "showSecondarySubtitleDialog": "Mostrar caixa de diálogo de estilo ao abrir uma legenda secundária", + "rememberSecondarySubtitleFile": "Lembrar ficheiro de legenda secundária", "autoOpenVideoFile": "Abrir automaticamente o vídeo ao abrir a legenda", "downloadMpv": "O mpv lib foi transferido e está pronto a ser utilizado.", "downloadVlc": "Transferir VLC", "allowSingleLetterShortcutsInTextbox": "Permitir atalhos de uma letra na caixa de texto", + "allowTextNavigationShortcutsInTextbox": "Permitir atalhos em teclas de navegação de texto (Ctrl+Esquerda/Direita, Home/End) na caixa de texto", "spellCheckEnglishTreatInApostropheAsIng": "Verificação ortográfica: tratar palavras terminadas em 'in'' como 'ing' (apenas inglês)", "goToLineNumberSetsVideoPosition": "Ir para linha número também define a posição do vídeo", "adjustAllTimesRememberLineSelectionChoice": "Ajustar todos os tempos e lembrar a opção de seleção de linhas", @@ -3207,6 +3553,7 @@ "showAssaLayer": "Mostrar caixa de camada ASSA", "waveformCursorColor": "Cor do cursor", "waveformShotChangeColor": "Cor da forma de onda na mudança de cena", + "waveformGridColor": "Cor da grelha da forma de onda", "waveformParagraphLeftColor": "Cor da borda esquerda da forma de onda", "waveformParagraphRightColor": "Cor da borda direita da forma de onda", "waveformFancyHighColor": "Cor alta da forma de onda sofisticada", @@ -3234,27 +3581,31 @@ "resetSyntaxColoring": "Repor coloração de sintaxe", "resetWaveform": "Repor forma de onda", "resetRules": "Repor regras", - "useFrameMode": "Utilizar modo de fotograma (hh.mm.ss.ff)", + "timeCodeMode": "Modo de código de tempo", + "timeCodeModeTime": "Tempo (hh:mm:ss,zzz)", + "timeCodeModeFrames": "Fotogramas (hh:mm:ss:ff)", + "timeCodeModeFrameNumbers": "Números de fotograma", + "frameModeSnapToVideoFrames": "Guardar tempos nos fotogramas do vídeo (não fotogramas dentro de cada segundo)", "textBoxLimitNewLines": "Limitar o número de linhas na caixa de texto da legenda", - "mpvVideoOutput": "Saída de vídeo (mpv)", "mpvOpenGl": "libmpv - OpenGL", "mpvSoftwareRendering": "libmpv - Software (lento)", + "mpvMetalRendering": "libmpv - Metal (experimental, requer reinício)", "mpvWidRendering": "libmpv - ID de janela nativa", "waveFormsAndSpectrogramFoldersContainsX": "As pastas \"Waveforms\" e \"spectrogram\" contêm {0}", "deleteWaveformAndSpectrogramFoldersQuestion": "Forma de onda/espectrograma", "waveformGenerateSpectrogram": "Gerar espectrograma", "waveformAutoGenerate": "Gerar automaticamente a forma de onda ao abrir um vídeo", "waveformSpectrogramMode": "Modo espectrograma", - "waveformCenterOnSingleClick": "Centrar com um único clique", - "waveformSingleClickSelectsSubtitle": "Apenas um clique para selecionar linhas", "waveformRightClickSelectsSubtitle": "Selecionar legenda clicando com o botão direito", - "waveformPauseOnSingleClick": "Pausar com um único clique", "waveformDrawStyle": "Estilo de desenho de forma de onda", "waveformExtractAudioFormat": "Formato de áudio a extrair", "waveformExtractAudioSampleRate": "Taxa de amostragem do áudio a extrair", "waveformExtractAudioSampleRateOriginal": "Original (manter origem)", "waveformExtractAudioBitRate": "Débito de áudio a extrair (MP3/M4A)", + "ffmpegUseCenterChannelOnly": "Usar apenas o canal central frontal para áudio 5.1 (FFmpeg)", "vlcWidRendering": "libVLC - ID de janela nativa", + "ffmpegSoftwareRendering": "FFmpeg", + "downloadFfmpegLibs": "Transferir bibliotecas FFmpeg (para o leitor de vídeo FFmpeg)", "subtitleGridEnterKeyAction": "Ação da tecla Enter na grelha de legendas", "subtitleSingleClickAction": "Ação do clique simples na grelha da legenda", "subtitleDoubleClickAction": "Ação do clique duplo na grelha da legenda", @@ -3280,25 +3631,25 @@ "saveAsBehaviorUseVideoFileName": "Utilizar nome do ficheiro de vídeo", "saveAsBehaviorUseSubtitleFileName": "Utilizar nome do ficheiro de legenda", "saveAsAppendLanguageCodeTwoLetter": "Duas letras", - "saveAsAppendLanguageCodeThreeLetter": "Três letras (ISO 639-2/T)", - "saveAsAppendLanguageCodeThreeLetterBibliographic": "Três letras (ISO 639-2/B)", "saveAsAppendLanguageCodeLanguageName": "Nome do idioma", "splitOddLineActionWeightTop": "Peso superior", "splitOddLineActionWeightBottom": "Peso inferior", "splitOddLinesAction": "Ação de dividir linhas ímpares", "ocrUseWordSplitList": "Utilizar lista de divisão de palavras (OCR + FCE)", "ocrGuessUnknownWords": "OCR: tentar adivinhar palavras desconhecidas", + "spellCheckRememberUseAlwaysList": "Verificação ortográfica: lembrar lista \"Utilizar sempre\"", + "fixShortDisplayTimesAllowMoveStartTime": "Corrigir erros comuns: a correção de tempos de exibição curtos pode mover o tempo inicial", "speechToTextSelectedLinesPromptFirstTimeOnly": "Fala para texto: linhas selecionadas, pedir idioma/motor apenas na primeira vez", "multipleReplaceShowDotDotDotButtons": "Substituição múltipla: mostra botões do menu de contexto", "gridFocusTextboxAfterInsertNew": "Grelha: focar caixa de texto após inserir nova legenda", + "undoRedoGoToChangedLine": "Anular/refazer: ir para a linha alterada (e posição do vídeo)", "textToSpeechPromptMergeContinuationLines": "Texto para fala: prompt para combinar linhas de continuação", "textToSpeechPromptSkipNoiseLines": "Texto para fala: perguntar se ignora as linhas de som/música", "textToSpeechPromptDetectSpeakers": "Texto para fala: perguntar se deteta nomes de intervenientes no texto", "useFocusedButtonBackgroundColor": "Utilizado", "focusedButtonBackgroundColor": "Cor de fundo do botão em foco", "forceCrLfOnSave": "Forçar CR+LF ao guardar (ficheiros de legenda de texto)", - "showWaveformToolbarPlayNext": "Barra de ferramentas: mostrar o próximo botão de reprodução", - "showWaveformToolbarPlaySelection": "Barra de ferramentas: mostra o botão de seleção de reprodução", + "linuxClipboardUseExternalTool": "Copiar texto através de xclip/wl-copy (mantém os caracteres acentuados)", "showFormatLimitWarning": "Avisar ao guardar quando as linhas excedem os limites do formato (por exemplo, SCC 32 caracteres/linha)", "textBoxButtonShowAutoBreak": "Caixa de texto: mostrar botão de interrupção automática", "textBoxButtonShowUnbreak": "Caixa de texto: mostrar botão de desbloqueio", @@ -3310,12 +3661,18 @@ "waveformDoubleClickAction": "Ação de clique duplo em forma de onda (após ação de clique único)", "allSettings": "Todas as definições", "uiScale": "Escala da interface (%)", + "fontScale": "Escala do tipo de letra (%)", + "textSelectionOpacity": "Opacidade da seleção de texto (%)", "waveformToolbarItems": "Itens da barra de ferramentas da forma de onda", "matchIconColorToDarkTheme": "Combinar cor do ícone com a cor de primeiro plano do tema escuro", "subtitlePreviewProperties": "Propriedades de pré-visualização de legendas", "usePositionFromSubtitleFile": "Utilizar a posição do ficheiro de legenda (TTML/PAC/EBU STL)", "marginIsPartOfSubtitleArea": "A margem faz parte da área da legenda", "textJustify": "Justificar as linhas", + "deinterlace": "Desentrelaçar vídeo", + "deinterlaceOff": "Desativado", + "deinterlaceAuto": "Automático (apenas fotogramas entrelaçados)", + "deinterlaceAlways": "Sempre", "pixelWidthInfo": "Linhas verdes = limite de largura máxima | Área vermelha = texto excede o limite", "spellCheckEngineHunSpelll": "Hunspell", "spellCheckEngineMsWord": "Microsoft Word", @@ -3325,117 +3682,89 @@ "waveformSaveAsCustomTheme": "Guardar como tema personalizado...", "waveformLoadThemeDotDotDot": "Carregar tema...", "waveformExportThemeDotDotDot": "Exportar tema...", + "waveformSaveCustomThemeTitle": "Guardar tema personalizado", + "waveformThemeFileType": "Tema da forma de onda do Subtitle Edit", + "waveformThemeHighContrast": "Alto contraste", + "waveformThemeOceanBlue": "Azul oceano", + "waveformThemeWarmSunset": "Pôr do sol quente", + "waveformThemeForest": "Floresta", + "waveformThemeMidnightPurple": "Roxo meia-noite", + "waveformThemeRetroAmber": "Âmbar retro", + "waveformThemeIce": "Gelo", "fixCommonErrorsSkipStep1": "Corrigir erros comuns: saltar passo 1 (escolher correções)" }, "shortcuts": { "generalUnbreakNoSpaceCjk": "Desquebrar sem espaço (CJK)", - "title": "Atalhos", + "generalToggleBreak": "Alternar quebrar/eliminar quebra", "searchShortcuts": "Atalhos de pesquisa...", - "filter": "Filtro", - "categoryGeneral": "Geral", "categorySubtitleGridAndTextBox": "Visualização de lista de legendas e caixa de texto", "categorySubtitleGrid": "Visualização da lista de legendas", - "categoryWaveform": "Forma de onda", "categoryTextBox": "Caixa de texto", "categoryFile": "Ficheiro", "categoryVideo": "Vídeo", "categorySync": "Sincronização", - "categoryTranslate": "Traduzir", - "categorySearch": "Pesquisar", - "categoryTools": "Ferramentas", "categoryAi": "IA", + "categoryCustom": "Personalizado", + "editCustomShortcut": "Editar atalho personalizado", + "customShortcutTextBoxKeyHint": "Use aqui uma tecla com Ctrl/Alt - uma tecla simples deixaria de escrever na caixa de texto.", + "customShortcutNumberX": "Atalho personalizado #{0}", + "customShortcutNumberXY": "Atalho personalizado #{0}: {1}", + "customShortcutStep": "Passo", + "customShortcutSteps": "Passos", + "customShortcutStepRunCommand": "Executar comando", + "customShortcutStepInsertText": "Inserir texto", + "customShortcutStepReplace": "Localizar e substituir", + "customShortcutInsertAtCursor": "No cursor", + "customShortcutInsertAtStart": "No início do texto", + "customShortcutInsertAtEnd": "No fim do texto", + "customShortcutSummaryRunCommandX": "Executar: {0}", + "customShortcutSummaryInsertXY": "Inserir \"{0}\" ({1})", + "customShortcutSummaryReplaceXY": "Substituir \"{0}\" por \"{1}\"", + "customShortcutSummaryReplaceRegexXY": "Substituir regex \"{0}\" por \"{1}\"", + "customShortcutPickCommand": "Selecione um comando a executar.", + "customShortcutEnterText": "Introduza o texto a inserir.", + "customShortcutEnterFind": "Introduza o texto a localizar.", + "customShortcutInvalidRegexX": "Expressão regular inválida: {0}", + "customShortcutStepsHint": "Os passos são executados de cima para baixo. Os passos de texto atuam nas linhas selecionadas; os comandos que abrem uma janela continuam depois de esta fechar.", "activeIn": "Ativo em", "activeInEverywhere": "Em todo o lado", - "generalSetupLikeSe4": "Configurar como o Subtitle Edit 4 (tema, atalhos, regras de substituição)", - "generalMergeSelectedLines": "Combinar as linhas selecionadas", - "generalMergeWithPrevious": "Combinar com o anterior", - "generalMergeWithNext": "Combinar com o próximo", - "generalMergeWithPreviousAndUnbreak": "Combinar com a anterior e eliminar quebra de linha", - "generalMergeWithNextAndUnbreak": "Combinar com a próxima e eliminar quebra de linha", - "generalMergeWithPreviousAndAutoBreak": "Combinar com a anterior e quebrar linha automaticamente", - "generalMergeWithNextAndAutoBreak": "Combinar com a próxima e quebrar linha automaticamente", - "generalMergeSelectedLinesAndAutoBreak": "Combinar as linhas selecionadas e quebrar linha", "generalMergeSelectedLinesAndUnbreak": "Combinar as linhas selecionadas e remover quebra", "generalMergeSelectedLinesAndUnbreakCjk": "Combinar linhas selecionadas e desquebrar CJK", - "generalMergeSelectedLinesOnlyFirstText": "Combinar as linhas selecionadas apenas no primeiro texto", "generalMergeSelectedLinesBilingual": "Combinar as linhas selecionadas bilingue", - "generalMergeWithPreviousBilingual": "Combinar com bilingue anterior", - "generalMergeWithNextBilingual": "Combinar com bilingue seguinte", - "generalMergeOriginalAndTranslation": "Combinar o orginal com o traduzido", "generalToggleTranslationMode": "Alternar modo de tradução", - "generalSwitchOriginalAndTranslation": "Mudar entre o original e a tradução", - "generalSwitchOriginalAndTranslationTextBoxes": "Alternar caixas de texto originais e de tradução", "generalToggleTranslationAndOriginalInPreviews": "Alternar entre tradução e original na pré-visualização de vídeo/áudio", "generalChooseLayout": "Escolher esquema", - "generalLayoutChooseX": "Layout {0}", - "generalPlayFirstSelected": "Reproduzir primeiro selecionado", - "generalGoToFirstSelectedLine": "Ir para a primeira linha selecionada", - "generalGoToNextEmptyLine": "Ir para a próxima linha em branco", "generalGoToNextSubtitle": "Ir para linha seguinte", "generalGoToNextSubtitlePlayTranslate": "Ir para a legenda seguinte (reproduzir tradução)", "generalGoToNextSubtitleCursorAtEnd": "Ir para a legenda seguinte (cursor no final)", "generalGoToPrevSubtitle": "Ir para linha anterior", "generalGoToPrevSubtitlePlayTranslate": "Ir para a legenda anterior (reproduzir tradução)", - "generalGoToStartOfCurrentSubtitle": "Ir para o início da legenda atual", - "generalGoToEndOfCurrentSubtitle": "Ir para o final da legenda atual", - "generalGoToPreviousSubtitleAndFocusVideo": "Ir para a legenda anterior e focar o vídeo", - "generalGoToNextSubtitleAndFocusVideo": "Ir para a próxima legenda e focar o vídeo", - "generalGoToPrevSubtitleAndPlay": "Ir para a legenda anterior e reproduzir", - "generalGoToNextSubtitleAndPlay": "Ir para a legenda seguinte e reproduzir", - "generalGoToPreviousSubtitleAndFocusWaveform": "Ir para a legenda anterior e focar a forma de onda", - "generalGoToNextSubtitleAndFocusWaveform": "Ir para a legenda seguinte e focar a forma de onda", - "generalGoToLineNumber": "Ir para o número da linha", "generalGoToFirstLine": "Ir para a primeira linha", "generalGoToLastLine": "Ir para a última linha", "alsoSetVideoPosition": "Definir também a posição do vídeo", "generalGoToVideoPosition": "Ir para a posição do vídeo", "generalToggleItalic": "Alternar itálico", "generalToggleBold": "Alternar negrito", - "generalToggleBookmarks": "Alternar marcadores", - "generalFocusTextBox": "Caixa de texto em foco", - "generalToggleBookmarksWithText": "Alternar favoritos com texto", - "generalEditBookmarks": "Editar favoritos", - "fileNew": "Novo", "generalToggleUnderline": "Alternar sublinhado", + "generalToggleBox": "Alternar caixa (EBU STL)", "fileOpen": "Abrir", "fileOpenKeepVideo": "Abrir (manter vídeo)", "fileSave": "Guardar", "fileSaveAs": "Guardar como", - "fileSaveAll": "Guardar tudo", "fileSaveOriginal": "Guardar original", - "fileSaveOriginalAs": "Guardar original como", - "fileOpenOriginalSubtitle": "Abrir legenda original", - "fileCloseOriginalSubtitle": "Fechar legenda original", - "fileTranslatedSubtitle": "Legenda traduzida", "fileCompare": "Comparar", "fileStatistics": "Estatísticas", "fileImportPlainText": "Importar texto sem formatação", - "fileImportBluRaySupForOcr": "Importar Blu-ray SUP para OCR", - "fileImportBluRaySupForEdit": "Importar Blu-ray SUP para edição", - "fileImportTimeCodes": "Importar códigos de tempo", "fileExportEbuStl": "Exportar EBU STL", "fileExportPac": "Exportar PAC", - "fileExportEdlClipName": "Exportar nome do clipe EDL", - "fileExportPlainText": "Exportar texto simples", - "fileExportCustomTextFormat1": "Exportar formato de texto personalizado 1", - "fileExportCustomTextFormat2": "Exportar formato de texto personalizado 2", - "fileExportCustomTextFormat3": "Exportar formato de texto personalizado 3", "fileExit": "Sair", "openSeDataFolder": "Abrir pasta do Subtitle Edit", - "editFind": "Localizar", - "editFindNext": "Localizar próximo", "editFindPrevious": "Localizar anterior", - "editReplace": "Substituir", - "editMultipleReplace": "Múltipla substituição", "fillSelectedLinesWithClipboard": "Preencher linhas selecionadas com o texto da área de transferência", - "editModifySelection": "Modificar a seleção", - "listSelectAll": "Selecionar tudo", - "listSelectFirst": "Selecionar primeiro", - "listSelectLast": "Selecionar último", "listInverseSelection": "Inverter seleção", "listDeleteSelection": "Eliminar seleção", + "deleteSelectionEverywhere": "Eliminar seleção (em todo o lado)", "rippleDeleteSelection": "Eliminar seleção em cascata", - "settings": "Definições", "assigned": "Atribuído", "unassigned": "Não atribuído", "pressedKeyX": "Tecla pressionada: {0}", @@ -3443,6 +3772,7 @@ "detectKey": "Detetar tecla", "control": "Ctrl", "alt": "Alt", + "altGr": "AltGr", "win": "Win", "shift": "Shift", "controlMac": "⌃", @@ -3453,11 +3783,14 @@ "resetShortcutsDetail": "Pretende repor todos os atalhos para os valores predefinidos?", "togglePlayPause": "Alternar reproduzir/pausar", "toggleLockTimeCodes": "Alternar códigos de tempo de bloqueio", + "toggleTimeCodeMode": "Alternar modo de código de tempo (tempo/fotogramas/números de fotograma)", + "toggleScreenPrivacy": "Alternar privacidade do ecrã (ocultar nomes de ficheiros e depois também textos)", + "screenPrivacyFileNamesHidden": "Privacidade do ecrã: nomes de ficheiros ocultos", + "screenPrivacyFileNamesAndTextsHidden": "Privacidade do ecrã: nomes de ficheiros e textos ocultos", + "screenPrivacyOff": "Privacidade do ecrã: desativada", "duplicateSelectedLines": "Duplicar linhas selecionadas", "sourceView": "Visualização de origem", - "showAlignmentPicker": "Alinhamento", "addOrEditBookmark": "Adicionar ou editar marcador", - "listBookmarks": "Listar marcador", "toggleBookmark": "Alternar marcador (linhas selecionadas, sem texto)", "toggleForcedSelectedLines": "Alternar forçado (linhas selecionadas)", "goToNextBookmark": "Ir para o marcador seguinte", @@ -3468,12 +3801,12 @@ "waveformSetStartAndSetEndOfPreviousMinusGap": "Definir início e fim da pausa negativo anterior", "waveformSetEndAndStartOfNextAfterGap": "Definir o fim e o início da próxima pausa positiva", "waveformSetEndAndStartOfNextAfterGapAndGoToNext": "Definir o fim e o início da próxima, mais intervalo, e ir para a seguinte", - "fetchFirstWordFromNextSubtitle": "Obter a primeira palavra da próxima linha da legenda", "moveLastWordToNextSubtitle": "Mover a última palavra para a próxima linha da legenda", "moveLastWordFromFirstLineDownCurrentSubtitle": "Mover a primeira palavra da linha de baixo (linha atual)", "moveFirstWordFromNextLineUpCurrentSubtitle": "Mover a primeira palavra da linha de cima (linha atual)", "moveTextFromCursorToNextAndGoToNext": "Mover o texto após o cursor para a legenda seguinte e ir para a seguinte", "moveTextFromCursorToNextAndGoToNextAndPlay": "Mover o texto após o cursor para a legenda seguinte, ir para a seguinte e reproduzir", + "moveTextFromCursorToNextAndGoToNextAndPlayAndPause": "Mover o texto após a posição do cursor para a legenda seguinte, ir para a seguinte, reproduzi-la e pausar no fim", "breakAtFirstSpaceFromCursor": "Quebrar no primeiro espaço a partir da posição do cursor", "breakAtFirstSpaceFromCursorAndGoToNext": "Quebrar no primeiro espaço a partir da posição do cursor e ir para a seguinte", "toggleFocusGridAndWaveform": "Alternar foco entre grelha de legendas e forma de onda/espectrograma", @@ -3485,7 +3818,6 @@ "videoGoToPreviousTimeCode": "Ir para o código de tempo anterior", "videoGoToNextTimeCode": "Ir para o código de tempo seguinte", "goToNextLineAndSetVideoPosition": "Ir para a legenda seguinte e definir a posição do vídeo", - "textBoxDeleteSelectionNoClipboard": "Caixa de texto: Eliminar seleção (sem área de transferência)", "textBoxDeleteForward": "Caixa de texto: eliminar a seleção ou o caractere seguinte (eliminar para a frente)", "textBoxCut": "Caixa de texto: Recortar", "textBoxCut2": "Caixa de texto: Recortar (alternativa)", @@ -3504,6 +3836,7 @@ "listViewColumnTextUp": "Coluna, texto para cima", "listViewColumnTextDown": "Coluna, texto para baixo", "autoTranslateSelectedLines": "Traduzir automaticamente as linhas selecionadas...", + "autoTranslateSelectedLinesNoPrompt": "Traduzir automaticamente as linhas selecionadas (sem perguntar, usar último motor/idiomas)", "setAssaResolution": "Definir resolução ASSA (PlayResX/PlayResY)", "setShortcutForX": "Definir atalho para \"{0}\"", "commandFileNewKeepVideo": "Novo (manter vídeo)", @@ -3511,29 +3844,22 @@ "fileCloseOriginal": "Fechar original", "fileCloseTranslation": "Fechar tradução", "restoreAutoBackup": "Restaurar cópia de segurança automática", - "openContainingFolder": "Abrir pasta", "importTimeCodes": "Importar códigos de tempo", "importSubtitleWithManuallyChosenEncoding": "Importar legenda com codificação escolhida manualmente", "exportBluRaySup": "Blu-ray sup...", "exportCustomTextFormat": "Exportar formato de texto personalizado", "exportPlainText": "Texto sem formatação...", - "showHistory": "Mostrar histórico", "toggleRightToLeft": "Alternar da direita para a esquerda", "modifySelection": "Modificar a seleção", "adjustDurations": "Ajustar duração", "applyDurationLimits": "Aplicar limites de duração", "batchConvert": "Converter múltiplos ficheiros", - "bridgeGaps": "Unir intervalos", "applyMinGap": "Aplicar pausa mínima", - "fixCommonErrors": "Corrigir erros comuns", "makeEmptyTranslationFromCurrentSubtitle": "Criar uma tradução vazia da legenda atual", - "mergeLinesWithSameText": "Combinar as linhas com o mesmo texto", - "mergeLinesWithSameTimeCodes": "Combinar linhas com os mesmos tempos", "splitBreakLongLines": "Dividir/reequilibrar linhas longas", "mergeShortLines": "Combinar as linhas curtas", "mergeContinuationLines": "Combinar linhas de continuação", "snapAllTimesToFrames": "Ajustar todos os tempos aos fotogramas", - "removeTextForHearingImpaired": "Remover texto para deficientes auditivos", "joinSubtitles": "Juntar legendas", "splitSubtitle": "Dividir legenda", "spellCheck": "Verificação ortográfica", @@ -3555,7 +3881,6 @@ "adjustAllTimes": "Ajustar todos os tempos", "visualSync": "Sincronia visual", "translateViaCopyPaste": "Traduzir via copiar e colar", - "shortcuts": "Atalhos", "wordLists": "Listas de palavras", "chooseUiLanguage": "Escolher idioma da interface", "chooseRuleProfile": "Escolher perfil de regras", @@ -3566,6 +3891,7 @@ "textBoxItalic": "Caixa de texto em itálico", "textBoxBold": "Caixa de texto: negrito", "textBoxUnderline": "Caixa de texto: sublinhado", + "textBoxBox": "Caixa de texto, caixa (EBU STL)", "resetWaveformZoomAndSpeed": "Repor o zoom da forma de onda e a velocidade de reprodução (taxa de reprodução)", "togglePlaybackSpeed": "Alternar velocidade de reprodução (taxa de reprodução)", "playbackSpeedSlower": "Velocidade de reprodução mais lenta (taxa de reprodução)", @@ -3577,22 +3903,28 @@ "seekSilenceForward": "Localizar silêncio no áudio para a frente", "waveformGuessStart": "Adivinhar o tempo de início pela forma de onda", "waveformGuessEnd": "Adivinhar o tempo de fim pela forma de onda", + "waveformGuessStartAndEnd": "Adivinhar tempo inicial e final a partir da forma de onda", "setVideoPositionCurrentSubtitleStart": "Definir posição do vídeo para o início da linha atual", "goToSubtitlePositionAndPause": "Ir para a posição da legenda e pausar", "setVideoPositionCurrentSubtitleEnd": "Definir a posição do vídeo para o final da linha atual", "toggleAudioTracks": "Alternar faixas de áudio", - "listErrors": "Listar erros", "goToNextError": "Ir para o próximo erro", "goToPreviousError": "Ir para erro anterior", "addNameToNameList": "Adicionar nome à lista de nomes", "findDoubleWords": "Localizar palavras duplicadas", "findDoubleLines": "Localizar linhas repetidas", "colorX": "Cor {0}", - "removeColor": "Remover cor", "surroundWith": "Cercar com...", "surroundWithXY": "Cercar com {0}/{1}", - "repeatLine": "Repetir linha", "surroundWithNumberX": "Envolver com #{0}", + "surroundWithBehavior": "Comportamento", + "surroundWithBehaviorToggle": "Alternar (adicionar, ou remover se existir)", + "surroundWithBehaviorAdd": "Adicionar (sempre)", + "surroundWithBehaviorRemove": "Remover (todos)", + "surroundWithBehaviorRemoveOnce": "Remover (um de cada vez)", + "surroundWithWorksOn": "Atua em", + "surroundWithScopeSelectionOrText": "Seleção, senão todo o texto", + "surroundWithScopeEachLine": "Cada linha", "searchVia": "Pesquisar através de", "searchViaX": "Pesquisar através de {0}", "searchViaNumberX": "Pesquisar através de #{0}", @@ -3620,6 +3952,11 @@ "copySubtitlePathToClipboard": "Copiar caminho da legenda para a área de transferência", "copySubtitleOriginalPathToClipboard": "Copiar caminho da legenda original para a área de transferência", "focusTextBox": "Destacar o fotograma de texto", + "focusSubtitleListView": "Focar a lista de legendas", + "focusWaveform": "Focar a forma de onda", + "focusOriginalTextBox": "Focar a caixa de texto original", + "textBoxGoToStart": "Caixa de texto, ir para o início", + "textBoxGoToEnd": "Caixa de texto, ir para o fim", "sortByNumber": "Classificar por número", "sortByStartTime": "Classificar por tempo de \"Mostrar\"", "sortByEndTime": "Classificar por tempo de \"Ocultar\"", @@ -3644,7 +3981,6 @@ "assaStyles": "Estilos ASSA", "assaProperties": "Propriedades ASSA", "assaAttachments": "Anexos ASSA", - "assaVideoColorPicker": "Seletor de cores de vídeo ASSA", "recalculateDurationSelectedLines": "Recalcular a duração (linhas selecionadas)", "setDurationMaxCpsSelectedLines": "Definir duração para CPS máximo (linhas selecionadas)", "toggleWaveformAndSpectrogramHeight": "Alternar altura dividida entre forma de onda/espectrograma", @@ -3658,9 +3994,9 @@ "selectionToSentenceCase": "Caixa de texto: seleção para Letra de frase", "selectionToggleCasing": "Caixa de texto: alternar maiúsculas/minúsculas (MAIÚSCULAS -> minúsculas -> Iniciais Maiúsculas)", "googleIt": "Pesquisar no Google (texto selecionado)", - "setActorXY": "Definir ator {0}: {1}", + "setActorX": "Definir ator: {0}", "setNewActor": "Definir novo ator...", - "setActorSlotXTitle": "Slot do ator {0}", + "setStyleX": "Definir estilo: {0}", "toggleDialogDashes": "Alternar travessões", "mergeWithLineBeforeAsDialog": "Combinar com linha anterior como diálogo", "mergeWithLineAfterAsDialog": "Combinar com linha após a caixa de diálogo", @@ -3671,13 +4007,27 @@ "moveStartOneFrameBackKeepGapPrev": "Mover início um fotograma para trás (manter pausa em relação ao anterior se estiver próximo)", "moveStartOneFrameForwardKeepGapPrev": "Mover início um fotograma para frente (manter pausa em relação ao anterior se estiver próximo)", "moveEndOneFrameBackKeepGapNext": "Mover final um fotograma para trás (manter pausa para o próximo se estiver próximo)", - "moveEndOneFrameForwardKeepGapNext": "Mover final um fotograma para frente (manter pausa para o próximo se estiver próximo)" + "moveEndOneFrameForwardKeepGapNext": "Mover final um fotograma para frente (manter pausa para o próximo se estiver próximo)", + "moveStartXMsBack": "Mover início X ms para trás (X definido nas Definições, também na Sincronização visual)", + "moveStartXMsForward": "Mover início X ms para a frente (X definido nas Definições, também na Sincronização visual)", + "moveEndXMsBack": "Mover fim X ms para trás (X definido nas Definições, também na Sincronização visual)", + "moveEndXMsForward": "Mover fim X ms para a frente (X definido nas Definições, também na Sincronização visual)", + "moveSelectedLinesXMsBack": "Mover linhas selecionadas X ms para trás (X definido nas Definições)", + "moveSelectedLinesXMsForward": "Mover linhas selecionadas X ms para a frente (X definido nas Definições)", + "moveSelectedLinesAndForwardXMsBack": "Mover linhas selecionadas e todas as seguintes X ms para trás (X definido nas Definições)", + "moveSelectedLinesAndForwardXMsForward": "Mover linhas selecionadas e todas as seguintes X ms para a frente (X definido nas Definições)", + "moveAllLinesXMsBack": "Mover todas as linhas X ms para trás (X definido nas Definições)", + "moveAllLinesXMsForward": "Mover todas as linhas X ms para a frente (X definido nas Definições)", + "moveSelectedLinesCustomMsBack": "Mover linhas selecionadas, milissegundos personalizados ({0:#,###,##0}) atrás, {1}", + "moveSelectedLinesCustomMsForward": "Mover linhas selecionadas, milissegundos personalizados ({0:#,###,##0}) à frente, {1}", + "moveSelectedLinesAndForwardCustomMsBack": "Mover linhas selecionadas e todas as seguintes, milissegundos personalizados ({0:#,###,##0}) atrás, {1}", + "moveSelectedLinesAndForwardCustomMsForward": "Mover linhas selecionadas e todas as seguintes, milissegundos personalizados ({0:#,###,##0}) à frente, {1}", + "moveAllLinesCustomMsBack": "Mover todas as linhas, milissegundos personalizados ({0:#,###,##0}) atrás, {1}", + "moveAllLinesCustomMsForward": "Mover todas as linhas, milissegundos personalizados ({0:#,###,##0}) à frente, {1}" }, "wordLists": { "title": "Listas de palavras", "addName": "Adicionar nome", - "addWord": "Adicionar palavra", - "addPair": "Adicionar par", "nameAndIgnoreList": "Lista de nomes/ignorar", "userWords": "Lista de palavras do utilizador", "ocrFixList": "Lista de correções OCR", @@ -3694,7 +4044,6 @@ "noPluginsInstalled": "Nenhum plugin instalado", "getPluginsOnline": "Obter plugins online...", "getPluginsTitle": "Obter plugins", - "enabled": "Ativo", "disabled": "Desativado", "notSupportedOnThisOs": "Não suportado neste sistema operativo", "removePluginXConfirm": "Remover plugin '{0}'? Isto irá eliminar a pasta do plugin: {1}", @@ -3711,17 +4060,19 @@ "downloadingXPercent": "A descarregar {0}%...", "pluginXInstalled": "'{0}' instalado.", "couldNotInstallPluginX": "Não foi possível instalar '{0}': {1}", - "notInstalled": "Não instalado", "install": "Instalar", "reinstall": "Reinstalar", - "update": "Atualizar", "installedX": "({0}) instalado", "updateAvailableXToY": "Atualização disponível ({0} → {1})", "applyPluginToWhichLinesX": "Aplicar '{0}' a:", "applyToSelectedLinesX": "Linhas selecionadas ({0})", "applyToAllLinesX": "Todas as linhas ({0})", "updateAllXAvailable": "Atualizar todos ({0})", - "updatingXOfY": "A atualizar {0}/{1}..." + "updatingXOfY": "A atualizar {0}/{1}...", + "doNotAskAgain": "Não perguntar novamente (alterar em Gerir plugins)", + "applyPluginsTo": "Aplicar plugins a", + "askEachTime": "Perguntar sempre", + "allLines": "Todas as linhas" }, "help": { "aboutSubtitleEdit": "Acerca do Subtitle Edit", @@ -3765,6 +4116,8 @@ "nOcrDatabase": "Base de dados nOCR", "drawMode": "Modo de desenho:", "addNewCharcter": "Adicionar novo caráter", + "addNOcrCharacterTitle": "Adicionar caractere nOCR para a linha {0}, caractere {1} de {2}, usando a base de dados \"{3}\"", + "addBinaryOcrCharacterTitle": "Adicionar caractere OCR binário para a linha {0}, caractere {1} de {2}, usando a base de dados \"{3}\"", "lineIndexX": "Linha {0}", "inspectNOcrAdditions": "Inspecionar novas adições nOCR", "ocrSelectedLines": "Linhas selecionadas do OCR", @@ -3813,6 +4166,8 @@ "binaryImageCompareInspectImageMatches": "\"Comparação de imagens binárias\" - inspecionar correspondências de imagens", "saveBlankTextTitle": "Guardar texto em branco?", "saveBlankTextPrompt": "Guardar texto em branco para imagem?", + "drawnLinesDoNotMatchTitle": "As linhas não correspondem à imagem", + "drawnLinesDoNotMatchPrompt": "Algumas das linhas desenhadas não correspondem à imagem (as linhas verdes têm de estar sobre o caractere, as vermelhas fora dele), por isso este caractere não será reconhecido.\n\nGuardar mesmo assim?", "yesAndNeverAskAgain": "Sim e não perguntar novamente", "importTextFromSubtitleDotDotDot": "Importar texto da legenda...", "importTextFromSubtitleOverwritePrompt": "Algumas linhas já possuem texto OCR. Substituir o texto existente?", @@ -3838,7 +4193,6 @@ "vobSubColorsTitle": "Cores VobSub/DVD", "vobSubColorsHeader": "Personalizar as quatro cores do VobSub", "vobSubColorsDescription": "As legendas VobSub/DVD utilizam quatro cores indexadas: fundo, predefinição, ênfase 1 e ênfase 2. Escolha cada cor abaixo para substituir a paleta incorporada - útil quando as cores originais têm pouco contraste para OCR.", - "vobSubColorBackground": "Fundo", "vobSubColorPattern": "Predefinição", "vobSubColorEmphasis1": "Ênfase 1", "vobSubColorEmphasis2": "Ênfase 2", @@ -3872,9 +4226,26 @@ "crispEmbedSettingsTitle": "Definições do CrispEmbed", "crispEmbedDescription": "Motor de OCR local com vários motores de modelos - transfira aqui o motor e os modelos.", "trainNOcrDatabase": "Treinar a base de dados nOCR...", + "optimizeNOcrDatabase": "Otimizar base de dados nOCR...", + "optimizeNOcrDatabaseQuestionXY": "Remover as linhas que não distinguem caracteres de caracteres semelhantes na base de dados nOCR \"{0}\"?\n\nIsto torna a base de dados mais pequena e o OCR mais rápido, normalmente com menos erros. Demora até um minuto.\n\nÉ guardada uma cópia de segurança como \"{1}\".", + "optimizingNOcrDatabaseXY": "A otimizar a base de dados nOCR... {0}/{1}", + "nOcrDatabaseOptimizedXYZ": "Base de dados nOCR \"{0}\" otimizada: {1:#,##0} -> {2:#,##0} linhas.", "startTraining": "Iniciar treino", "abortTraining": "Interromper treino", "trainingOptions": "Opções de treino", + "trainNOcrSubtitle": "Desenha caracteres nos tipos de letra instalados e ensina ao nOCR as suas formas. Escolha os tipos de letra que as suas legendas utilizam.", + "xFontsSelected": "{0:#,##0} selecionados", + "subtitleFonts": "Tipos de letra das legendas", + "emptyDatabase": "Base de dados vazia", + "startFromDatabase": "Começar a partir de", + "startFromDatabaseHint": "Adicionar os caracteres treinados a uma cópia de uma base de dados existente (recomendado) ou começar a partir de uma vazia", + "fontSizes": "Tamanhos de letra", + "fontSizesHint": "Tamanhos de letra em píxeis a desenhar, separados por vírgulas, por ex. \"30, 40\"", + "numberOfLineSegmentsHint": "Linhas verificadas por caractere - mais é mais rigoroso, mas mais lento", + "enterDatabaseName": "Introduza um nome para a base de dados", + "trainingFontXSizeYCharacterZ": "A treinar \"{0}\" a {1} px: {2}", + "xLearned": "{0:#,##0} aprendidos", + "xSkipped": "{0:#,##0} ignorados", "charactersToTrain": "Caracteres a treinar", "letterCombinationsToTrain": "Combinações de letras que podem ser divididas numa imagem", "importCharactersFromSubtitleFile": "Importar caracteres de um ficheiro de legenda...", @@ -3901,6 +4272,8 @@ "drawZoomOut": "Diminuir zoom (Ctrl+-)", "drawResetView": "Repor vista (Ctrl+0)", "drawToggleGrid": "Alternar grelha (Ctrl+G)", + "drawTogglePreview": "Alternar pré-visualização (F9)", + "drawPreviewFailed": "A pré-visualização falhou - é necessário o ffmpeg com libass (filtro subtitles)", "drawCopyToClipboard": "Área de transferência", "drawShapes": "Formas", "drawSelectedPoint": "Ponto selecionado", @@ -3908,16 +4281,86 @@ "drawSelectedLayer": "Camada selecionada", "drawToolX": "Ferramenta: {0}", "drawHelpText": "Clique para adicionar pontos • Enter/F8 para fechar a forma • Shift+Arrastar para deslocar • Ctrl+Scroll para ampliar", + "drawMoveToLayer": "Mover para camada", + "drawFlipHorizontal": "Inverter horizontalmente", + "drawFlipVertical": "Inverter verticalmente", + "drawHideShape": "Ocultar forma", + "drawShowShape": "Mostrar forma", + "drawHideLayer": "Ocultar camada", + "drawShowLayer": "Mostrar camada", + "drawDeleteLayer": "Eliminar camada", + "drawDeletePoint": "Eliminar ponto", + "drawCancelDrawing": "Cancelar desenho", + "drawLayerX": "Camada {0}", + "drawImportSvg": "Importar imagem SVG...", + "drawRotateClockwise": "Rodar 90° no sentido horário", + "drawConvertToCurve": "Converter segmento em curva", + "drawBackground": "Fundo", + "drawPosition": "Posição", + "drawZoom": "Zoom", + "drawShapeTool": "Ferramenta de formas", + "drawColorPickerTool": "Escolher cor de uma forma ou do fundo", + "drawShapeLibrary": "Biblioteca de formas", + "drawShapeLibraryHint": "Arraste na tela para colocar uma forma, ou clique para o tamanho predefinido. Shift mantém as proporções.", + "drawAddToLibrary": "Adicionar à biblioteca de formas...", + "drawRemoveFromLibrary": "Remover da biblioteca de formas", + "drawShapeName": "Nome da forma", + "drawCategorySpeechBubbles": "Balões de fala", + "drawCategoryArrows": "Setas", + "drawCategoryBasicShapes": "Formas básicas", + "drawCategorySymbols": "Símbolos", + "drawCategoryMyShapes": "As minhas formas", + "drawShapeSpeechBubble": "Balão de fala", + "drawShapeSpeechBubbleRight": "Balão de fala, cauda à direita", + "drawShapeSpeechBubbleUp": "Balão de fala, cauda para cima", + "drawShapeOvalBubble": "Balão de fala oval", + "drawShapeBoxBubble": "Balão de fala retangular", + "drawShapeThoughtBubble": "Balão de pensamento", + "drawShapeShoutBubble": "Balão de grito", + "drawShapeCaptionBox": "Caixa de legenda", + "drawShapeArrow": "Seta", + "drawShapeDoubleArrow": "Seta dupla", + "drawShapeChevron": "Divisa", + "drawShapeCurvedArrow": "Seta curva", + "drawShapeRoundedRectangle": "Retângulo arredondado", + "drawShapeTriangle": "Triângulo", + "drawShapeDiamond": "Losango", + "drawShapePentagon": "Pentágono", + "drawShapeHexagon": "Hexágono", + "drawShapeOctagon": "Octógono", + "drawShapeStar": "Estrela", + "drawShapeRing": "Anel", + "drawShapePlus": "Mais", + "drawShapeBanner": "Faixa", + "drawShapeHeart": "Coração", + "drawShapeCheck": "Visto", + "drawShapeMusicNote": "Nota musical", + "drawShapeMusicNotes": "Notas musicais", + "drawShapeLightning": "Relâmpago", + "drawShapeCloud": "Nuvem", + "drawShapeMoon": "Lua", + "drawShapeBadge": "Distintivo", + "drawSize": "Tamanho", + "drawLayer": "Camada", + "drawBackgroundVideoFrame": "Fotograma do vídeo (posição atual)", + "drawBackgroundVideoFrameAt": "Fotograma do vídeo em...", + "drawBackgroundImage": "Ficheiro de imagem...", + "drawBackgroundNone": "Sem fundo", + "drawBackgroundStretch": "Esticar imagem para a tela", + "drawBackgroundOpacity": "Opacidade do fundo", + "drawBackgroundVideoFrameFailed": "Não foi possível obter um fotograma do vídeo em {0}.", + "drawImages": "Imagens", + "drawConvertToLine": "Converter segmento em linha", + "drawConvertShapeToCurves": "Converter todas as linhas em curvas", + "drawConvertShapeToLines": "Converter todas as curvas em linhas", + "drawRotateCounterClockwise": "Rodar 90° no sentido anti-horário", + "drawSvgImages": "Imagens SVG", + "drawSvgNoShapes": "Não foram encontradas formas preenchidas nem traços no ficheiro SVG.", "progressBarTitle": "Barra de progressão", "progressBarSettings": "Barra de progressão", - "progressBarPosition": "Posição", - "progressBarBottom": "Inferior", - "progressBarTop": "Superior", "mouseOverColor": "Cor ao passar o rato", "clickedColor": "Cor ao clicar", "progressBarForeColor": "Cor do primeiro plano", - "progressBarBackColor": "Cor de fundo", - "progressBarStyle": "Estilo", "progressBarSquareCorners": "Cantos quadrados", "progressBarRoundedCorners": "Cantos arredondados", "progressBarChapters": "Capítulos", @@ -3926,8 +4369,6 @@ "progressBarXAdjustment": "Ajuste X", "progressBarYAdjustment": "Ajuste Y", "progressBarTextAlignment": "Alinhamento do texto", - "progressBarTakePosFromVideo": "Assumir a posição do vídeo", - "progressBarPreview": "Pré-visualizar", "fontCollectorTitle": "Coletor de tipos de letra", "fontCollectorUsedIn": "Utilizado em", "fontCollectorFontFiles": "Ficheiro(s) de tipo de letra", @@ -3974,9 +4415,7 @@ "resolutionResamplerVideoDiffers": "A resolução da legenda ({0}x{1}) difere da resolução do vídeo ({2}x{3}).\nAlterar a legenda para corresponder ao vídeo?", "resolutionResamplerAskOnVideoOpen": "Perguntar quando for aberto um vídeo com uma resolução diferente", "backgroundBoxGenerator": "Gerar caixa de fundo", - "backgroundBoxPadding": "Preenchimento", "backgroundBoxFillWidth": "Largura de preenchimento", - "backgroundBoxBoxColor": "Cor da caixa", "backgroundBoxRadius": "Raio", "backgroundBoxCircle": "Círculo", "backgroundBoxSpikes": "Picos", @@ -3988,12 +4427,9 @@ "backgroundBoxTornPaperDouble": "Papel rasgado (superior e inferior)", "backgroundBoxStarburst": "Explosão estelar", "backgroundBoxScroll": "Rolagem / Pergaminho", - "stylesTitle": "Estilos de Advanced Sub Station Alpha", "stylesInFile": "Estilos em ficheiro", "stylesSaved": "Estilos guardados", - "category": "Categoria", "allCategories": "[Todas]", - "defaultCategory": "Predefinição", "newCategory": "Nova categoria...", "renameCategory": "Mudar o nome da categoria...", "deleteCategory": "Eliminar categoria", @@ -4013,13 +4449,26 @@ "graphics": "Gráficos", "copyToStorageStyles": "Copiar para estilos de armazenamento", "copyToFileStyles": "Copiar para estilos de ficheiro", + "styleXAlreadyExistsInStorage": "Já existe um estilo com o nome \"{0}\" no armazenamento.\n\nSubstituí-lo, ou manter ambos (a cópia recebe um novo nome)?", + "styleXAlreadyExistsInFile": "Já existe um estilo com o nome \"{0}\" no ficheiro.\n\nSubstituí-lo, ou manter ambos (a cópia recebe um novo nome)?", + "styleNameCannotBeEmpty": "O nome de um estilo não pode estar vazio.", + "styleNameXIsUsedMoreThanOnce": "O nome de estilo \"{0}\" é usado por mais do que um estilo no ficheiro.", + "overwrite": "Substituir", + "keepBoth": "Manter ambos", + "doThisForAllConflictingStyles": "Fazer isto para todos os estilos em conflito", "setStyleAsDefault": "Definir estilo como predefinição", "takeUsagesFromDotDotDot": "Faça utilização de ...", "replaceStyleWithDotDotDot": "Substituir estilo por...", "noAttachmentsFound": "Nenhum anexo encontrado no ficheiro ASSA selecionado.", "deleteStyleQuestion": "Eliminar estilo?", "deleteStylesQuestion": "Eliminar estilos?", + "deleteXStylesQuestion": "Pretende eliminar {0} estilos?", + "deleteXStylesFromStorageQuestion": "Pretende eliminar {0} estilos do armazenamento?", + "deleteStyleXFromStorageQuestion": "Pretende eliminar o estilo \"{0}\" do armazenamento?", + "deleteStyleXFromFileQuestion": "Pretende eliminar o estilo \"{0}\" do ficheiro atual?", "openStyleImportFile": "Abrir ficheiro de legendas do qual importar estilos", + "styleImportFiles": "Ficheiros de estilo (.ass, .sty, categorias do Subtitle Edit 4 .template)", + "ssaStyleImportFiles": "Ficheiros de estilo (.ssa, .ass, .sty)", "primary": "Primário", "secondary": "Segundário", "applyOverrideTags": "Aplicar etiquetas de substituição", @@ -4034,7 +4483,6 @@ "spaceIncrease": "Aumento de espaço", "popIn": "Entre", "spinIn": "Girar", - "playCurrent": "_Reproduzir atual", "setPosition": "Definir posição", "rotation": "Rotação", "scaleX": "Escala X", @@ -4078,10 +4526,8 @@ "advancedEffectOldMovieDescription": "Simula um projetor de filme vintage de 35 mm com ruído, vinheta e riscos", "advancedEffectNeonBurst": "Explosão de néon", "advancedEffectNeonBurstDescription": "Legendas estilo 'Shorts' de alta energia com brilho neon e animação 'pop' ampliada", - "advancedEffectSettings": "Definições", "advancedEffectSnowFlakeCount": "Contagem de flocos", "advancedEffectStarfieldStarCount": "Contagem de estrelas", - "advancedEffectStarfieldSpeed": "Velocidade", "advancedEffectGlitch": "Glitch", "advancedEffectGlitchDescription": "Glitch digital com aberração cromática, flashes brancos e sobreposições de distorção", "advancedEffectBounceIn": "Saltar para dentro", @@ -4090,8 +4536,6 @@ "advancedEffectFirefliesDescription": "Pontos amarelo-âmbar quentes e brilhantes a flutuar organicamente pelo ecrã com brilho pulsante", "advancedEffectMatrix": "Matriz", "advancedEffectMatrixDescription": "Colunas de caracteres verdes a cair, com o texto da legenda a formar-se letra a letra na chuva", - "advancedEffectAudioBars": "Barras de áudio (forma de onda)", - "advancedEffectAudioBarsDescription": "Barras animadas de equalizador movidas pela forma de onda do áudio — as barras deslocam-se para a esquerda como uma forma de onda e pulsam com o áudio", "advancedEffectAudioPulse": "Pulso de texto de áudio", "advancedEffectAudioPulseDescription": "O texto da legenda brilha e pulsa com o volume do áudio – secções silenciosas mostram texto normal, picos altos adicionam um halo colorido e um ligeiro aumento de escala", "advancedEffectConfetti": "Explosão de confete", @@ -4105,7 +4549,6 @@ "advancedEffectFancyKaraokeGlow": "Brilho da palavra ativa", "advancedEffectFancyKaraokeActiveColor": "Cor ativa", "advancedEffectFancyKaraokeInactiveColor": "Cor inativa", - "advancedEffectFancyKaraokeInactiveOpacity": "Opacidade inativa (0-255)", "advancedEffectWordSpacingPixels": "Espaçamento (pixels)", "advancedEffectSlowZoomOut": "Diminuição lenta do zoom", "advancedEffectSlowZoomOutDescription": "O texto começa um pouco maior e diminui suavemente para o tamanho normal ao longo da duração da legenda", @@ -4121,7 +4564,6 @@ "advancedEffectSlideInLeftDescription": "O texto desliza a partir de fora do ecrã pela esquerda, mantém-se e sai novamente para a esquerda", "advancedEffectSlideInRight": "Deslizar da direita", "advancedEffectSlideInRightDescription": "O texto desliza a partir de fora do ecrã pela direita, mantém-se e sai novamente para a direita", - "advancedEffectFadeInOut": "Fade in/out", "advancedEffectFadeInOutDescription": "O texto aparece gradualmente no início e desaparece no final de cada legenda", "advancedEffectWordFlip3D": "Rotação 3D das palavras", "advancedEffectWordFlip3DDescription": "Cada palavra entra com uma rotação 3D e um salto elástico à medida que é dita — estilo moderno de legendas \"Shorts\"", diff --git a/src/ui/Assets/Languages/Romanian.json b/src/ui/Assets/Languages/Romanian.json index daa21a1b66b..d9fd42239d1 100644 --- a/src/ui/Assets/Languages/Romanian.json +++ b/src/ui/Assets/Languages/Romanian.json @@ -1,6 +1,6 @@ { "title": "Subtitle Edit", - "version": "v5.2.0-rc7", + "version": "v5.3.0", "translatedBy": "zildan v1 05.04.26", "cultureName": "ro-RO", "general": { @@ -71,6 +71,24 @@ "abort": "Renunță", "actor": "Actor", "actors": "Actori", + "SetActorDotDotDot": "Setați actorul...", + "ActorPickerTitle": "Setați actorul", + "ActorPickerFilterHint": "Tastați pentru a filtra sau tastați numele unui actor nou", + "ActorPickerKeysHint": "Apăsați un număr sau Enter pentru a seta actorul. Alt+Sus/Jos schimbă ordinea, Delete elimină actorul.", + "ActorPickerNewActorX": "Actor nou „{0}”", + "ActorPickerLinesSelectedX": "Linii selectate: {0}", + "SetStyleDotDotDot": "Setați stilul...", + "StylePickerTitle": "Setați stilul", + "StylePickerFilterHint": "Tastați pentru a filtra sau tastați numele unui stil nou", + "StylePickerKeysHint": "Apăsați un număr (1-9, 0) pentru a seta stilul pe toate liniile selectate.", + "StylePickerNewStyleX": "Stil nou „{0}”", + "StylePickerNewStyleInfo": "Apăsați Enter pentru a adăuga acest stil cu setările implicite și a-l seta pe liniile selectate. Folosiți „Gestionați stilurile...” pentru a-i schimba ulterior fontul, culorile și poziția.", + "StylePickerCurrentStyleX": "Stilul curent: {0}", + "StylePickerLinesUsingStyleX": "Linii care folosesc acest stil: {0}", + "StylePickerMarginsXYZ": "Stânga {0}, dreapta {1}, vertical {2}", + "StylePickerSetStyle": "_Setați stilul", + "StylePickerManageStyles": "Gestionați stilurile...", + "StylePickerManageStylesHint": "Închideți această fereastră și deschideți managerul de stiluri pentru a edita fonturi, culori, poziție și altele", "add": "Adăuga", "addDotDotDot": "Adăuga...", "addToNamesListCaseSensitive": "Adăugați la lista de nume (se face distincția între majuscule și minuscule)", @@ -79,7 +97,6 @@ "adjustAlpha": "Ajustați alfa (transparență)", "adjustDisplayDuration": "Reglați durata de afișare", "adjustment": "Ajustare", - "advanced": "Avansat", "advancedSettings": "Setări avansate", "after": "După", "alignment": "Aliniere", @@ -112,7 +129,6 @@ "batchMode": "Modul lot", "bdnXml": "BDN/xml", "before": "Înainte", - "beforeX": "Înainte de „{0}”", "beginning": "Început", "bluRaySup": "Blu-ray (sup)", "bold": "Îndrăzneţ", @@ -124,7 +140,6 @@ "bookmarkEdit": "Editați marcajul", "bookmarkEditForSelectedLinesX": "Editați marcajul pentru {0} linii selectate", "bookmarksList": "Listează marcaje", - "borderColor": "Culoarea chenarului", "borderStyle": "Stil de chenar", "bottomCenter": "Centru de jos", "bottomLeft": "Jos-Stânga", @@ -147,7 +162,6 @@ "changeCasing": "Schimbați carcasa", "changeFormatting": "Schimbați formatarea", "changeFrameRate": "Schimbați rata de cadre", - "changeLanguageFilter": "Schimbați filtrul de limbă...", "changeOnce": "Schimbați o dată", "changeSpeed": "Schimbați viteza", "character": "Caracter", @@ -169,7 +183,6 @@ "conversionCancelledByUser": "Conversie anulată de utilizator", "convert": "Convertit", "converted": "Convertit", - "convertingDotDotDot": "Se convertește...", "convertingXofYDotDoDot": "Se convertește {0:#,###,##0}/{1:#,###,##0}...", "copy": "Copie", "copyImageToClipboard": "Copiați imaginea în clipboard", @@ -177,7 +190,6 @@ "couldNotSaveFileXErrorY": "Nu s-a putut salva fișierul „{0}”. Eroare: {1}", "count": "Conta", "cps": "Caractere/sec", - "currentSubtitle": "Subtitrare curentă", "currentVideoPosition": "Poziția video actuală", "cut": "Tăiați", "dark": "Întuneric", @@ -222,7 +234,6 @@ "expand": "Extinde", "export": "Export", "exportDotDotDot": "Export...", - "exportToX": "Exportați în {0}", "extendAfter": "Extinde/scurta la linie după", "extendBefore": "Extinde/scurta la linie înainte", "extendSelectedLinesToNextShotChangeOrNextSubtitle": "Extindeți liniile selectate până la următoarea modificare a imaginii (sau următoarea subtitrare)", @@ -237,7 +248,6 @@ "fileExtension": "Extensie de fișier", "fileName": "Nume de fișier", "fileNameX": "Nume fișier: {0}", - "fileNameXAndSize": "Nume fișier: {0} ({1})", "fileSaved": "Fișier salvat", "fileSavedToX": "Fișier salvat în {0}", "fileXAlreadyExists": "Fișierul „{0}” există deja.", @@ -247,8 +257,6 @@ "find": "Găsi", "findNext": "Găsiți următorul", "findTextX": "Găsiți text - {0}", - "fiveHundredMilliseconds": "500 de milisecunde", - "fiveSeconds": "Cinci secunde", "fix": "Fix", "fixCommonErrors": "Remediați erorile comune", "fixRightToLeft": "Remediați de la dreapta la stânga", @@ -272,13 +280,11 @@ "foundXInLineYZ": "S-a găsit „{0}” în rândul {1}, poziția {2}", "foundXMatches": "S-au găsit {0} potriviri", "frameRate": "Rata de cadre", - "frameRateX": "Frecvența cadrelor: {0:0.0###}", "frames": "Rame", "from": "Din", "fromCurrentVideoPosition": "din poziția video curentă", "gap": "Decalaj", "general": "General", - "generalText": "General", "generate": "Genera", "generateImportShotChanges": "Generați/importați modificări de fotografie", "generating": "Se generează...", @@ -295,12 +301,8 @@ "help": "Ajutor", "hex": "Hex", "hide": "Ascunde", - "hidePreview": "Ascunde previzualizarea", "history": "Istorie", "horizontal": "Orizontală", - "hourMinutesSecondsDecimalSeparatorMilliseconds": "Ora:min:sec{0}ms", - "hourMinutesSecondsFrames": "Ora:min:sec:cadre", - "ip": "IP", "id": "ID", "image": "Imagine", "imageSaved": "Imaginea salvată", @@ -357,6 +359,7 @@ "mergeSelectedLinesDialog": "Îmbina liniile selectate ca dialog", "mergeWithLineAfterAndAutoBreak": "Îmbinați cu linia după și auto-break", "mergeWithLineAfterKeepBreaks": "Îmbinați cu linia după (păstrați pauzele)", + "MergeWithLineAfterAndUnbreak": "Îmbinați cu linia de după și desfaceți", "mergeWithLineBeforeAndAutoBreak": "Îmbinați cu linia înainte și auto-break", "mergeWithLineBeforeKeepBreaks": "Îmbinați cu linia înainte (păstrați pauzele)", "middleCenter": "Centru-Mijloc", @@ -370,10 +373,11 @@ "moveAllShotChangeOneFrameBack": "Mutați toate modificările capturii cu un cadru înapoi", "moveAllShotChangeOneFrameForward": "Mutați toate modificările de fotografiere cu un cadru înainte", "moveDown": "Deplasați-vă în jos", + "Increase": "Creștere", + "Decrease": "Scădere", "moveUp": "Mișcă-te în sus", "multipleReplace": "Înlocuire multiplă", "name": "Nume", - "negative": "Negativ", "new": "Nou", "newDotDotDot": "Nou...", "newProfile": "Profil nou", @@ -401,20 +405,15 @@ "offsetY": "Offset Y", "ok": "_OK", "oneFile": "Un singur dosar", - "oneHundredMilliseconds": "100 de milisecunde", - "oneSecond": "O secundă", "oneSecondBack": "O secundă înapoi", "oneSecondForward": "O secundă", - "onlineVideoFeatureNotAvailable": "Funcția nu este disponibilă pentru videoclipurile online", "openContainingFolder": "Deschideți folderul care conține", "openDictionaryFolder": "Deschideți folderul dicționar", "openFile": "Deschideți fișierul", "openImageBasedSubtitle": "Deschideți subtitrarea bazată pe imagini", "openImageFile": "Deschideți fișierul imagine", - "openOriginalSubtitleFile": "Deschide fișierul de subtitrare original...", "openOriginalSubtitleFileTitle": "Deschideți fișierul de subtitrare original", "openOutputFolder": "Deschideți folderul de ieșire", - "openSubtitle": "Deschide subtitrarea...", "openSubtitleFileTitle": "Deschideți fișierul de subtitrare", "openSubtitles": "Deschideți subtitrări...", "openVideoFile": "Deschide fișierul video...", @@ -427,11 +426,6 @@ "outlineWidth": "Lățimea conturului", "outputFolder": "Dosarul de ieșire", "outputProperties": "Proprietăți de ieșire", - "overlap": "Suprapune", - "overlapNextX": "Se suprapune în continuare ({0:#;##0.###})", - "overlapPreviousLineX": "Se suprapune linia anterioară ({0:#;##0.###})", - "overlapStartAndEnd": "Se suprapun începutul și sfârșitul", - "overlapX": "Suprapunere ({0:#;##0.###})", "overwriteExistingFiles": "Suprascrie fișierele existente", "overwriteFilesInFolderX": "Suprascrierea fișierelor din dosarul: „{0}”", "overwriteQuestion": "Suprascrie?", @@ -444,6 +438,7 @@ "percent": "La sută", "pickLayer": "Setați stratul", "pickOllamaModel": "Alege modelul Ollama", + "PickModel": "Alegeți modelul", "llamaCppUseRemoteServer": "Utilizați server extern (URL)", "pickOutputFolder": "Alegeți folderul de ieșire", "pickResolutionFromCurrentVideo": "Alegeți rezoluția din videoclipul curent", @@ -483,7 +478,6 @@ "refresh": "Reîmprospăta", "region": "Regiune", "regularExpression": "Expresia regulată", - "regularExpressionIsNotValid": "Expresia regulată nu este validă!", "relativeToCurrentVideoPosition": "Raportat la poziția video curentă", "remove": "Elimina", "removeAlignment": "Eliminați alinierea", @@ -508,7 +502,7 @@ "reset": "Resetați", "resizePanels": "Redimensionați panourile", "resolution": "Rezoluţie", - "resolutionX": "Rezoluție: {0}", + "Retry": "Reîncercați", "reverseRightToLeftStartEnd": "Reverse RTL start/end", "right": "Corect", "rightToLeft": "De la dreapta la stânga", @@ -546,8 +540,6 @@ "selectedlinesX": "Liniile selectate: {0}", "sensitivity": "Sensibilitate", "separator": "Separator", - "sessionKey": "Cheia de sesiune", - "sessionKeyGenerate": "Generați o cheie nouă", "setEnd": "Setează sfârșitul", "setEndAndGoToNext": "Setați sfârșitul și treceți la următorul", "setEndAddNewAndGoToNew": "Setați sfârșitul, adăugați nou și treceți la nou", @@ -566,8 +558,9 @@ "shadowColor": "Culoarea umbrei", "shadowWidth": "Lățimea umbrei", "shiftLineBy": "Decalați linia cu", + "ShotIn": "Intrare plan", + "ShotOut": "Ieșire plan", "shortcut": "Comandă rapidă", - "shortcutX": "Comandă rapidă: {0}", "shortcuts": "Comenzi rapide", "show": "Spectacol", "showActorColumn": "Afișați coloana „Actor”.", @@ -578,17 +571,19 @@ "showStartColumn": "Afișați coloana „Start”.", "showHideColumn": "Afișați coloana „Ascunde”.", "showHistory": "Arată istoricul", + "ClearHistory": "Ștergeți istoricul", + "RemoveFromHistory": "Eliminați din istoric", "showLayerColumn": "Afișați coloana „Strat”.", "showPreview": "Afișează previzualizarea", "showShotChangesList": "Afișează lista cu modificări de fotografiere", + "ShowShotInColumn": "Afișați coloana „Intrare plan”", + "ShowShotOutColumn": "Afișați coloana „Ieșire plan”", "showStyleColumn": "Afișați coloana „Stil”.", "showTeletext": "Afișați coloana teletext în vizualizarea în listă", "showTimeCodes": "Afișați codurile de timp", "showWpmColumn": "Afișați coloana „Cuvinte/min”.", "showPixelWidthColumn": "Afișați coloana „Lățimea pixelilor”.", "shrink": "Se micsoreaza", - "singleBox": "Cutie unică", - "singleLineLengths": "Lungimea unei singure linii:", "singleMode": "Mod unic", "textTotalLength": "Text - lungime totală", "textSingleLineMaxLength": "Text - lungime maximă pe o singură linie", @@ -634,16 +629,16 @@ "strikeout": "Strikeout", "style": "Stil", "styleExaggeration": "Exagerarea stilului", - "styleLanguage": "Stil / Limbă", "styles": "Stiluri", "subtitleFile": "Fișier cu subtitrare", + "SubtitleFileChangedOutsideX": "Fișierul „{0}” a fost modificat în afara Subtitle Edit.\n\nÎl reîncărcați și pierdeți modificările?", "subtitleFileName": "Nume fișier de subtitrare", "subtitleFileSaved": "Fișierul de subtitrare a fost salvat", "subtitleFileSavedToX": "Fișierul cu subtitrare salvat în {0}", "subtitleFiles": "Fișiere de subtitrare", "subtitleFormats": "Formate de subtitrare", "subtitleLoadedX": "Subtitrare încărcată: {0}", - "subtitleXOfY": "Subtitrare {0} din {1}", + "SubtitleReloadedChangedOutsideX": "Subtitrare reîncărcată (modificată în afara Subtitle Edit): {0}", "suffix": "Sufix", "suggestions": "Sugestii", "sync": "Sincronizare", @@ -655,7 +650,6 @@ "teletextColor": "Culoare teletext", "teletextLine": "Linie teletext", "tenHours": "10 ore", - "tenMilliseconds": "10 milisecunde", "text": "Text", "textColor": "Culoarea textului", "textFiles": "Fișiere text", @@ -680,9 +674,6 @@ "topLeft": "Sus-Stânga", "topRight": "Sus-Dreapta", "totalAdjustmentX": "Ajustare totală: {0}", - "totalFramesX": "Total cadre: {0:#;##0.##}", - "totalLengthX": "Lungime totală: {0}", - "totalLengthXSplitLine": "Lungime totală: {0} (linie divizată!)", "translate": "Traduce", "translatePercentX": "Se traduce: {0}%", "translateRow": "Traduceți rândul", @@ -691,6 +682,7 @@ "type": "Tip", "unbreak": "Desfaceți textul", "unbreakLines": "Desfaceți liniile", + "Undelete": "Anulați ștergerea", "underline": "Subliniați", "undo": "Revenire", "unknown": "Necunoscut", @@ -707,13 +699,11 @@ "usages": "Utilizări", "use": "Utilizare", "useAlways": "Folosiți întotdeauna", - "useLargerFontForThisWindow": "Utilizați font mai mare pentru această fereastră", "useOnce": "Utilizați o dată", "useOutputFolder": "Folosiți folderul de ieșire", "useSourceFolder": "Utilizați folderul sursă", "useSourceResolution": "Utilizați rezoluția sursei", "userName": "Nume de utilizator", - "userNameAlreadyInUse": "Numele de utilizator este deja folosit", "vertical": "Vertical", "video100MsBack": "Video, cu 100 de milisecunde înapoi", "video100MsForward": "Video, 100 de milisecunde înainte", @@ -727,14 +717,25 @@ "videoCustom3ForwardX": "Video, milisecunde personalizate ({0:#,###,##0}) înainte, 3", "videoCustom4BackX": "Video, milisecunde personalizate ({0:#,###,##0}) înapoi, 4", "videoCustom4ForwardX": "Video, milisecunde personalizate ({0:#,###,##0}) înainte, 4", - "videoEncodingX": "Codificare video: {0}", + "VideoCustom1BackAndPauseX": "Video, milisecunde personalizate ({0:#,###,##0}) înapoi și pauză, 1", + "VideoCustom1ForwardAndPauseX": "Video, milisecunde personalizate ({0:#,###,##0}) înainte și pauză, 1", + "VideoCustom2BackAndPauseX": "Video, milisecunde personalizate ({0:#,###,##0}) înapoi și pauză, 2", + "VideoCustom2ForwardAndPauseX": "Video, milisecunde personalizate ({0:#,###,##0}) înainte și pauză, 2", + "VideoCustom3BackAndPauseX": "Video, milisecunde personalizate ({0:#,###,##0}) înapoi și pauză, 3", + "VideoCustom3ForwardAndPauseX": "Video, milisecunde personalizate ({0:#,###,##0}) înainte și pauză, 3", + "VideoCustom4BackAndPauseX": "Video, milisecunde personalizate ({0:#,###,##0}) înapoi și pauză, 4", + "VideoCustom4ForwardAndPauseX": "Video, milisecunde personalizate ({0:#,###,##0}) înainte și pauză, 4", "videoExtension": "Extensie fișier video", "videoFile": "Fișier video", "videoFileGenerated": "Fișierul video a fost generat", "videoFileGeneratedX": "Fișier video generat: „{0}”", + "VideoFilesGenerated": "Fișiere video generate", + "XVideosGenerated": "{0} videoclipuri generate", + "XOfYVideosGenerated": "{0} din {1} videoclipuri generate", + "SavedInX": "Salvat în {0}", + "SavedInXFolders": "Salvat în {0} foldere", "videoFiles": "Fișiere video", "videoAndAudioFiles": "Fișiere video și audio", - "videoInformation": "Informații video", "videoOffset": "Compensare video", "videoOneFrameBack": "Video, un cadru înapoi", "videoOneFrameForward": "Video, un cadru înainte", @@ -745,7 +746,6 @@ "videoPlayer": "Player video", "videoPosition": "Poziția video", "videoResolution": "Rezoluție video", - "viewX": "Vedeți {0}", "visible": "Vizibil", "voice": "Voce", "warning": "Avertizare", @@ -753,9 +753,7 @@ "waveformCenterOnVideoPosition": "Centrul formei de undă pe poziția video", "waveformPasteFromClipboard": "Lipiți din clipboard", "waveformSpectrogram": "Formă de undă/spectrogramă", - "webServiceUrl": "Adresa URL a serviciului web", "width": "Lăţime", - "windowPositionAndSize": "Poziția și dimensiunea ferestrei", "wordsPerMin": "Cuvinte/min", "pixelWidth": "Lățimea pixelilor", "wpm": "Cuvinte/min", @@ -765,8 +763,6 @@ "xNotFound": "„{0}” nu a fost găsit", "xRequiresAnApiKey": "{0} necesită o cheie API", "xRequiresAValidUrl": "{0} necesită un URL valid (de ex. \"http://...\" sau \"https://...\")", - "xSeconds": "{0:0.0##} secunde", - "xSubtitles": "{0:#,###,##0} subtitrări", "yes": "Da", "zeroBytes": "0 octeți", "copyToClipboard": "Copiați în clipboard", @@ -793,7 +789,6 @@ "installFolder": "Folderul de instalare", "lineHeight": "Înălțimea liniei", "multiMode": "Mod multiplu", - "mute": "Fără sunet", "playSelectedLinesAndFocusWaveform": "Redați liniile selectate și focalizați forma de undă", "playSelectedLinesWithLoopAndFocusWaveform": "Redați liniile selectate cu buclă și focalizați forma de undă", "redownload": "Descărcați din nou...", @@ -826,10 +821,8 @@ "playbackSpeed": "Viteza de redare", "hoursMinutesSecondsMilliseconds": "HH:MM:SS.FFF", "imageFiles": "Fișiere imagine", - "openAiCompatibleStt": "OpenAI Compatible STT", "openAiCompatibleSttEndpoint": "URL endpoint STT", "openAiCompatibleSttApiKey": "Cheie API", - "openAiCompatibleSttModel": "Model", "openAiCompatibleSttExtraHeaders": "Anteturi suplimentare", "openAiCompatibleSttTimeout": "Expirare (secunde)", "openAiCompatibleSttLanguage": "Sugestie de limbă", @@ -839,6 +832,9 @@ "dashScopeSttRegion": "Regiune", "dashScopeSttEnableWords": "Marcaje temporale la nivel de cuvânt", "dashScopeSttRegionKeyHint": "Notă: cheile API Alibaba Cloud Model Studio sunt specifice regiunii - asigurați-vă că regiunea selectată corespunde regiunii în care a fost creată cheia API (China vs. Internațional).", + "GoogleCloudSttProjectId": "ID proiect (doar pentru Application Default Credentials)", + "GoogleCloudSttBucketName": "Bucket de stocare (gol = automat)", + "GoogleCloudSttDynamicBatching": "Loturi dinamice (de circa 5x mai ieftin, fără garanție de latență)", "onlineSttApiKeyMissing": "Este necesară o cheie API. Introduceți cheia API și încercați din nou.", "openAiCompatibleSttAutoTranscribeOnAudioSelection": "Transcrieți automat noua selecție a formei de undă prin speech-to-text", "openAiCompatibleSttStream": "Răspuns în flux", @@ -848,11 +844,9 @@ "configurationRequired": "Configurare necesară", "transcriptionError": "Eroare de transcriere", "transcriptionComplete": "Transcriere finalizată", - "transcribingWithOpenAi": "Se transcrie cu serviciul OpenAI...", "processingResponse": "Se procesează răspunsul...", "unauthorizedApiKey": "Neautorizat: cheie API invalidă sau permisiuni insuficiente.", "requestTimeout": "Expirarea cererii: serverul a răspuns prea târziu.", - "noSegmentsReturned": "Niciun segment returnat de serviciul de transcriere.", "openAiCompatibleSttUrlMissing": "URL-ul endpoint-ului OpenAI Compatible STT este necesar. Vă rugăm să introduceți un URL și să încercați din nou.", "openAiCompatibleSttUrlNotResponding": "Endpoint-ul OpenAI Compatible STT nu a răspuns:\n{0}\n\nVă rugăm să verificați URL-ul și ca serverul să fie pornit.", "transcriptionFailed": "Transcrierea a eșuat", @@ -896,15 +890,15 @@ "replace": "_Înlocuire...", "multipleReplace": "_Înlocuiți mai multe...", "goToLineNumber": "_Mergeți la numărul rândului...", + "commandPalette": "_Paleta de comenzi...", "rightToLeftMode": "Mod de la dreapta la stânga", "modifySelectionDotDotDot": "Modificați _selectia...", "tools": "_Unelte", - "toolsSelectedLines": "_Instrumente (linii selectate)", "adjustDurations": "_Ajustați duratele...", "applyDurationLimits": "Aplicați _limite de durată...", "batchConvert": "_Conversie în lot...", "bridgeGaps": "Punturi _gaps...", - "applyMinGap": "Aplicați min. ", + "applyMinGap": "Aplicați min.", "changeCasing": "_Schimbați CASE...", "changeFormatting": "Schimbați formatarea...", "aiReview": "Revizuire AI...", @@ -987,6 +981,7 @@ "closeTranslation": "Închideți _traducerea", "fillSelectedLinesWithClipboard": "Completați liniile selectate cu textul din clipboard", "beautifyTimeCodes": "Înfrumusețați codurile de timp...", + "improveTimeCodes": "Îmbunătățiți codurile de timp (aliniere forțată)...", "evenlyDistributeLines": "Distribuiți uniform liniile (după CPS)", "mergeContinuationLines": "Îmbinați liniile de continuare...", "snapAllTimesToFrames": "Fixați toate timpii la cadre", @@ -1044,6 +1039,9 @@ "selectCurrentLineWhilePlayingHint": "Selectați subtitrarea curentă în timp ce redați {0}", "videoPosition": "Poziția video {0}", "videoPositionTextBox": "Casetă de text pentru poziția video {0}", + "toolbarLineBreakX": "Întrerupere de rând {0} (restul barei de instrumente continuă pe un rând nou)", + "initialText": "Textul inițial al liniei selectate", + "initialTextHint": "Textul liniei selectate așa cum era în momentul în care linia a fost selectată", "hideWaveformToolbar": "Ascundeți bara de instrumente {0}", "resetZoomAndSpeed": "Resetați zoom-ul și viteza de redare {0}", "removeBlankLines": "Eliminați liniile goale {0}", @@ -1057,6 +1055,15 @@ "textPlayHint": "Redați subtitrarea curentă și opriți la sfârșit {0}", "textPauseHint": "Întrerupeți redarea {0}", "textNextHint": "Redați subtitrarea următoare și opriți la sfârșit {0}", + "moveSelectedLines": "Mutați liniile selectate (butoanele înapoi/înainte)", + "moveSelectedLinesAndFollowing": "Mutați liniile selectate și toate cele următoare (butoanele înapoi/înainte)", + "moveAllLines": "Mutați toate liniile (butoanele înapoi/înainte)", + "moveSelectedLinesBackHint": "Mutați liniile selectate cu {0} ms înapoi {1}", + "moveSelectedLinesForwardHint": "Mutați liniile selectate cu {0} ms înainte {1}", + "moveSelectedLinesAndFollowingBackHint": "Mutați liniile selectate și toate cele următoare cu {0} ms înapoi {1}", + "moveSelectedLinesAndFollowingForwardHint": "Mutați liniile selectate și toate cele următoare cu {0} ms înainte {1}", + "moveAllLinesBackHint": "Mutați toate liniile cu {0} ms înapoi {1}", + "moveAllLinesForwardHint": "Mutați toate liniile cu {0} ms înainte {1}", "guessStartNoLineSelected": "Ghicire început: nicio linie selectată", "guessStartTimeCodesLocked": "Ghicire început: codurile de timp sunt blocate", "guessStartNoWaveform": "Ghicire început: nicio formă de undă", @@ -1078,20 +1085,23 @@ "charactersPerSecond": "Caractere/secundă: {0}", "chooseColumn": "Alege coloana", "columnPaste": "Pastă de coloană", + "commandPaletteTitle": "Paleta de comenzi", + "commandPaletteSearchPlaceholder": "Tastați pentru a căuta comenzi...", + "commandPaletteNoCommandsFound": "Nicio comandă potrivită", "createdEmptyTranslation": "S-a creat o traducere goală din subtitrarea curentă", "deleteText": "Ștergeți textul", "deleteTextAndShiftCellsUp": "Ștergeți textul și mutați celulele în sus", "endTimeMustBeAfterStartTime": "Ora de încheiere trebuie să fie după ora de începere.", - "errorLoad7Zip": "Acest fișier pare a fi un fișier 7-Zip comprimat.\n\n", - "errorLoadBinaryZeroes": "Ne pare rău, acest fișier conține doar zerouri binare!\n\n", - "errorLoadGZip": "Acest fișier pare a fi un fișier GZip comprimat.\n\n", - "errorLoadJpg": "Acest fișier pare a fi un fișier imagine JPG.\n\n", - "errorLoadPng": "Acest fișier pare a fi un fișier imagine PNG.\n\n", - "errorLoadRar": "Acest fișier pare a fi un fișier 7-Zip comprimat.\n\n", + "errorLoad7Zip": "Acest fișier pare a fi un fișier 7-Zip comprimat.\n\nSubtitle Edit nu poate deschide fișiere comprimate.", + "errorLoadBinaryZeroes": "Ne pare rău, acest fișier conține doar zerouri binare!\n\nDacă ați editat acest fișier cu Subtitle Edit, este posibil să găsiți o copie de rezervă prin elementul de meniu Fișier -> Restabiliți backupul automat...", + "errorLoadGZip": "Acest fișier pare a fi un fișier GZip comprimat.\n\nSubtitle Edit nu poate deschide fișiere comprimate.", + "errorLoadJpg": "Acest fișier pare a fi un fișier imagine JPG.\n\nSubtitle Edit nu poate deschide fișiere imagine.", + "errorLoadPng": "Acest fișier pare a fi un fișier imagine PNG.\n\nSubtitle Edit nu poate deschide fișiere imagine.", + "errorLoadRar": "Acest fișier pare a fi un fișier RAR comprimat.\n\nSubtitle Edit nu poate deschide fișiere comprimate.", "errorLoadRawPgsPrompt": "Acest fișier pare să fie un flux PGS brut, fără anteturile „PG” Blu-ray SUP, deci nu conține marcaje de timp.\n\nAcest lucru se întâmplă de obicei când o pistă PGS Matroska este extrasă în mod „brut”. Pentru sincronizări corecte, extrageți din nou pista în modul normal (de ex. „mkvextract tracks” fără --raw) sau deschideți fișierul Matroska original direct în Subtitle Edit.\n\nDoriți să importați totuși imaginile subtitrării, cu sincronizări generate automat, care trebuie corectate manual?", - "errorLoadSrr": "Acest fișier pare să fie un fișier ReScene SRR.\n\n", - "errorLoadTorrent": "Acest fișier pare să fie un fișier BitTorrent.\n\n", - "errorLoadZip": "Acest fișier pare a fi un fișier ZIP comprimat.\n\n", + "errorLoadSrr": "Acest fișier pare să fie un fișier ReScene SRR.\n\nSubtitle Edit nu poate deschide fișiere SRR.", + "errorLoadTorrent": "Acest fișier pare să fie un fișier BitTorrent.\n\nSubtitle Edit nu poate deschide fișiere torrent.", + "errorLoadZip": "Acest fișier pare a fi un fișier ZIP comprimat.\n\nSubtitle Edit nu poate deschide fișiere comprimate.", "extractingWaveInfo": "Se extrag informații despre Wave...", "extractingShotChanges": "Se extrag modificările de fotografiere...", "failedToExtractWaveInfo": "Nu s-au extras informațiile despre Wave", @@ -1113,18 +1123,20 @@ "noTextInClipboard": "Niciun text în clipboard", "oneLineCopiedFromOriginal": "Un rând copiat din subtitrarea originală", "oneLineCopiedToOriginal": "O linie a fost copiată în subtitrarea originală", + "onlineOnlyVideo": "Videoclip disponibil doar online", + "onlineOnlyVideoXSizeYDownloadAndOpen": "Fișierul video „{0}” ({1}) este stocat doar online, de exemplu în Dropbox sau iCloud Drive, și trebuie descărcat înainte de a putea fi deschis.\n\nÎl descărcați și deschideți acum?", "oneLineMerged": "O linie fuzionată", "oneLineSwitched": "O linie comutată", "overwriteExistingCells": "Suprascrie celulele existente", "overwriteOrShiftCellsDown": "Suprascrie/deplasează celulele în jos", "parsingMatroskaFile": "Se analizează fișierul Matroska...", + "readingDvdSubtitles": "Se citesc subtitrările DVD...", "pasteFromClipboardDotDotDot": "Lipiți din clipboard...", "redoPerformed": "Redo executat", "redoPerformedXActionLeft": "Repetare efectuată (acțiuni rămase: {0})", "removedUnicodeControlCharactersX": "S-au eliminat caracterele de control Unicode din {0} linii", "removedXBlankLines": "S-au eliminat {0} linii goale", "replacedXWithYCountZ": "S-a înlocuit „{0}” cu „{1}” ({2} apariții)", - "replacedXWithYInLineZ": "S-a înlocuit „{0}” cu „{1}” în rândul {2}", "reversedStartAndEndingsForRightToLeftX": "Început și sfârșit inversat de la dreapta la stânga în {0} linii", "ruleProfileIsX": "Profilul regulii este acum „{0}”", "saveLanguageFile": "Salvați fișierul de limbă", @@ -1132,7 +1144,6 @@ "shiftTextCellsDown": "Deplasați celulele text în jos", "singleLineLength": "Lungimea liniei: ", "speedIsNowX": "Viteza este acum „{0}”", - "spellCheckResult": "Verificare ortografică finalizată. \n\n• Cuvinte modificate: {0}\n• Cuvinte omise: {1}", "subtitleGridFormattingX": "Formatare grilă: {0}", "subtitleImportedFromMatroskaFile": "Subtitrare importată din fișierul Matroska", "textDown": "Text în jos", @@ -1152,19 +1163,20 @@ "xLinesSwitched": "{0} linii schimbate", "xShotChangedLoaded": "S-au încărcat {0} modificări de fotografie", "youtubeDlDownloadedSuccessfully": "„yt-dlp” a fost descărcat cu succes.", - "youtubeDlNotInstalledDownloadNow": "„yt-dlp” nu este instalat și este necesar pentru redarea videoclipurilor online.\n\n", + "youtubeDlNotInstalledDownloadNow": "„yt-dlp” nu este instalat și este necesar pentru redarea videoclipurilor online.\n\nDescărcați acum?", "promptInsertSubtitleOverlap": "Liniile inserate se vor suprapune peste liniile existente.\n\nInserați oricum?", "insertUnicodeSymbol": "Introduceți simbolul Unicode", "trimmedXLines": "Au fost tăiate {0} linii de subtitrare", "pastedXLinesOverSelectedLines": "S-au lipit {0} linie(i) peste liniile selectate", "pastedXOfYLinesOverSelectedLines": "S-au lipit {0} din {1} linie(i) peste liniile selectate - restul nu a încăput în selecție", + "insertedXLinesFromClipboard": "S-au inserat {0} linii din clipboard", "openOriginalDifferentNumberOfSubtitlesXY": "Fișierul de subtitrare original nu are același număr de subtitrări ca fișierul de subtitrare curent.\n\n• Subtitrări originale: {0}\n• Subtitrări curente: {1}", - "importXMatchingOriginalLines": "Importați {0} subtitrări originale care se potrivesc?", "originalTextReadOnly": "Text original (doar citire)", "originalTextEditMode": "Text original (mod editare)", "originalIsReadOnlyNotSaved": "Subtitrarea originală este o referință doar pentru citire și nu a fost salvată", "originalIsEmptyNotSaved": "Subtitrarea originală nu conține niciun text și nu a fost salvată", "originalIsReadOnlyReference": "Subtitrarea originală este deschisă ca referință doar pentru citire", + "closeTranslationQuestionXY": "Închideți traducerea „{0}”?\n\nSubtitrarea originală „{1}” devine subtitrarea pe care o editați. Fișierul de traducere în sine nu este modificat.", "allowEditOfOriginalSubtitle": "Permiteți editarea subtitrării originale", "showAllOriginalLinesX": "Afișați toate liniile originale ({0} nu au corespondent aici)", "showAllOriginalLinesHint": "Liniile fără corespondent sunt afișate ca rânduri suplimentare, ca să vedeți ce lipsește din traducere.", @@ -1175,7 +1187,7 @@ "allowEditHintReadOnly": "Originalul este afișat doar pentru citire și nu este salvat niciodată, deci fișierul său nu se poate modifica.", "allowEditHintAllLines": "Întregul original este pe ecran, deci salvarea lui păstrează fiecare linie.", "allowEditHintMatchingOnlyX": "Salvarea originalului ar scrie doar liniile afișate - celelalte {0} s-ar pierde.", - "videoOpenedChangeLayoutQuestion": "Un fișier video a fost deschis.\n\n", + "videoOpenedChangeLayoutQuestion": "Un fișier video a fost deschis.\n\nDoriți să schimbați aspectul pentru a afișa panoul video?", "sortedByStartTime": "Sortate după ora „Afișează”.", "sortedByEndTime": "Sortate după ora „Ascunde”.", "videoContrastSetTo": "Contrast: {0}", @@ -1186,6 +1198,7 @@ "fileExportedInFormatXToY": "Fișier exportat în format {0} în {1}", "fileExportedInFormatXToFileY": "Fișier exportat în formatul „{0}” în fișierul „{1}”", "fixedXLines": "S-au rezolvat {0} linii", + "toolXChangedYLines": "{0}: {1} linii modificate", "transcriptionCompletedWithXLines": "Transcrierea finalizată cu {0} rânduri", "replacedXOccurrences": "S-au înlocuit {0} apariții", "ffmpegDownloadedAndInstalledToX": "ffmpeg descărcat și instalat pe {0}", @@ -1201,7 +1214,7 @@ "nothingToSaveOriginal": "Nimic de salvat (original)", "liveSpellCheckLanguageXLoaded": "Limba de verificare ortografică live {0} a fost încărcată", "downloadFfmpegTitle": "Descărcați FFmpeg?", - "downloadFfmpegQuestion": "FFmpeg este necesar pentru redarea videoclipurilor online și pentru unele funcții de editare video.\n\n", + "downloadFfmpegQuestion": "FFmpeg este necesar pentru redarea videoclipurilor online și pentru unele funcții de editare video.\n\nDescărcați FFmpeg acum?", "errorLoadLargeFile": "Acest fișier este prea mare pentru a fi deschis ca fișier de subtitrare.", "errorLoadVideoFilePrompt": "Acest fișier pare a fi un fișier video:\n{0}\n\nPuteți deschide un videoclip prin „Video -> Deschide video” sau trăgând și plasând un videoclip pe player-ul video.\n\nDoriți să îl deschideți ca fișier video?", "numberOfLinesEvenlyDistributedX": "S-au distribuit uniform {0} linii", @@ -1234,13 +1247,9 @@ "file": { "ebuSaveOptions": { "title": "Opțiuni de salvare EBU", - "generalSubtitleInformation": "Informații generale subtitrare", "codePageNumber": "Numărul paginii de cod", "diskFormatCode": "Cod format de disc", "displayStandardCode": "Afișează codul standard", - "colorRequiresTeletext": "Culorile necesită teletext!", - "alignmentRequiresTeletext": "Alinierea necesită teletext!", - "teletextCharsShouldBe38": "„Numărul maxim de caractere pe rând” pentru teletext ar trebui să fie 38!", "characterCodeTable": "Tabelul de caractere", "languageCode": "Cod de limbă", "originalProgramTitle": "Titlul original al programului", @@ -1257,7 +1266,6 @@ "maxNumberOfDisplayableRows": "Numărul maxim de rânduri", "diskSequenceNumber": "Numărul secvenței discului", "totalNumberOfDisks": "Numărul total de discuri", - "import": "Import...", "textAndTimingInformation": "Text și informații despre sincronizare", "justificationCode": "Cod de justificare", "verticalPosition": "Poziție verticală", @@ -1274,11 +1282,6 @@ "errors": "Erori", "errorsX": "Erori: {0}", "maxLengthError": "Linia {0} depășește lungimea maximă ({1}) cu {2}: {3}", - "textUnchangedPresentation": "Prezentare neschimbată", - "textLeftJustifiedText": "Text justificat stânga", - "textCenteredText": "Text centrat", - "textRightJustifiedText": "Text justificat dreapta", - "useBoxForOneNewLine": "Bifați „Utilizați caseta în jurul textului” pentru o singură linie nouă", "maxLengthErrorTeletextColored": "Linia {0}-{1}: 36 (nu {2}) ar trebui să fie numărul maxim de caractere pentru text colorat cu înălțime dublă", "maxLengthErrorTeletextWhite": "Linia {0}-{1}: 37 (nu {2}) ar trebui să fie numărul maxim de caractere pentru text alb cu înălțime dublă", "maxLengthErrorTeletextSingleHeight": "Linia {0}: 38 (nu {1}) ar trebui să fie numărul maxim de caractere pentru text alb cu înălțime simplă", @@ -1293,26 +1296,33 @@ "subtitleWithManuallyChosenEncodingDotDotDot": "_Subtitrare cu codificare aleasă manual...", "titleImportImages": "Importați imagini", "importFileLabel": "Alegeți imaginile de importat (coduri de timp în numele fișierelor acceptate)", - "importFilesInfo": "Utilizați nume de fișiere codificate în timp:\r\n", + "importFilesInfo": "Utilizați nume de fișiere codificate în timp:\nstart_HH_MM_SS_MMM__end_HH_MM_SS_MMM[_index].ext\n\nExemple:\n0_00_01_042__0_00_03_919_0001.png\n0_00_01_042__0_00_03_919.png\n\nReguli:\n• HH_MM_SS_MMM pentru ora de început și de sfârșit\n• Dublul caracter de subliniere separă începutul de sfârșit\n• Index opțional după ora de sfârșit", "formattingDotDotDot": "Se formatează...", "imageBasedSubtitleForEditDotDotDot": "Subtitrare bazată pe imagini pentru editare...", "imageBasedSubtitleForOcrDotDotDot": "Subtitrare pe bază de imagini pentru OCR...", + "dvdSubtitlesDotDotDot": "Subtitrări de pe DVD (IFO/VOB)...", + "titleImportDvd": "Importați subtitrări de pe DVD", + "dvdIfoFile": "Fișier IFO", + "dvdVobFiles": "Fișiere VOB", + "dvdVideoStandard": "Standard video", + "dvdStartRipping": "Începeți extragerea", + "dvdRippingAborted": "Extragere întreruptă", + "dvdOpenIfoFile": "Deschideți fișierul DVD IFO", + "dvdAddVobFiles": "Adăugați fișiere VOB", + "dvdIfoFiles": "Fișiere DVD IFO", + "dvdVobFileType": "Fișiere DVD VOB", + "dvdDropHint": "Deschideți un fișier IFO (VIDEO_TS\\VTS_01_0.IFO) sau adăugați fișiere VOB - ori trageți-le aici", + "dvdNotAnIfoFileX": "{0} nu este un fișier DVD IFO", + "dvdMenuIfoNoTitleSetsX": "Nu s-au găsit seturi de titluri (VTS_xx_0.IFO) lângă {0}", + "dvdEncryptedXOfY": "{0} din cele {1} pachete de subtitrare sunt încă criptate CSS - aceste fișiere VOB au fost copiate fără decriptare, așa că unele imagini de subtitrare vor fi deformate.\n\nPentru un rezultat curat, decriptați mai întâi DVD-ul cu un instrument de extragere DVD.\n\nContinuați totuși?", "splitTextAt": "Împărțiți textul la", "blankLines": "Linii goale", "oneLineIsOneSubtitle": "Un rând este o subtitrare", "twoLinesAreOneSubtitle": "Două rânduri sunt un subtitrare", "importFilesDotDotDot": "Importați fișiere...", "multipleFiles": "Importați din mai multe fișiere text (un fișier este un subtitrare)", - "importOptions": "Opțiuni de import", - "autoSplitText": "Divizarea automată a textului", - "lineMode": "Modul linie", "maxLineLength": "Lungimea maximă a liniei", - "maxLinesPerSubtitle": "Max rânduri per subtitrare", - "minGapBetweenSubtitles": "Distanța minimă între subtitrări", "mergeShortLines": "Îmbinați linii scurte", - "removeLinesWithoutLetters": "Eliminați liniile fără litere", - "splitAtEndCharsSetting": "Împărțiți la sfârșitul caracterelor", - "takeTimeFromCurrentFile": "Luați timp de la fișierul curent", "fixed": "Fix", "numberOfSubtitlesX": "Număr de subtitrări: {0}", "numberOfSubtitlesXForcedY": "Număr de subtitrări: {0} ({1} forțate)", @@ -1330,14 +1340,18 @@ "forcedAlignerProgress": "Se aliniază... fereastra {0} din {1}, {2} din {3} linii", "forcedAlignerSetupTitle": "Aliniați codurile de timp cu alinierea forțată", "forcedAlignerSetupIntro": "Un aliniator forțat potrivește textul subtitrării pe care îl aveți deja cu audio, fără a-l transcrie mai întâi. Videoclipurile lungi sunt aliniate pe segmente, deci orice lungime funcționează.", + "forcedAlignerEndsFromSpeech": "Setați timpii de sfârșit din vorbirea izolată (lent)", + "forcedAlignerEndsFromSpeechHint": "Un aliniator forțat găsește unde începe o linie, nu unde se termină, așa că timpii de sfârșit provin în mod normal din timpul de citire. Cu această opțiune activată, muzica și efectele sonore sunt mai întâi eliminate din audio, iar fiecare linie se termină acolo unde vorbirea se oprește. Durează cam cât videoclipul însuși cu un GPU - și de multe ori mai mult fără.", + "forcedAlignerIsolatingSpeech": "Se izolează vorbirea... {0}", + "forcedAlignerIsolatingSpeechFailed": "Vorbirea nu a putut fi izolată - timpii de sfârșit au fost setați în schimb din timpul de citire.", "forcedAlignerModel": "Model de aliniere", - "forcedAlignerDownloadEngine": "Descărcați / actualizați motorul..." + "forcedAlignerDownloadEngine": "Descărcați / actualizați motorul...", + "forcedAlignerEstimatedXOfY": "{0} din {1} linii nu au putut fi aliniate și au fost plasate după timpul de citire - codurile lor de timp sunt doar estimate. Probabil scriptul este mai lung decât ceea ce se vorbește în videoclip." }, "export": { "exportImagesProfiles": "Exportați profiluri de imagini", "leftRightMargin": "Marginea din stânga/dreapta", "topBottomMargin": "Marja de sus/de jos", - "titleExportBluRaySup": "Exportați Blu-ray (sup)", "lineSpacingPercent": "Spațiere între rânduri %", "paddingLeftRight": "Umplutură stânga/dreapta", "paddingTopBottom": "Captuseala sus/jos", @@ -1378,10 +1392,21 @@ "titleExportDCinemaInteropPng": "D-Cinema interop/png", "titleExportDCinemaSmpte2014Png": "D-Cinema SMPTE 2014/png", "imageBasedSubtitleSaved": "Subtitrare bazată pe imagine salvată", - "titleExportBdnXml": "Exportați BDN/xml", "titleExportWebVttThumbnails": "Export WebVTT (miniaturi)", "fullFrameImage": "Imagine pe cadru întreg", "fullFrameImageHint": "Faceți fiecare imagine de dimensiunea cadrului video, cu subtitrarea la locul ei", + "stereo3D": "3D", + "stereo3DHint": "Pentru video 3D: desenați subtitrarea o dată în jumătatea de cadru a fiecărui ochi", + "halfSideBySide": "Jumătate alăturat (side-by-side)", + "halfTopBottom": "Jumătate sus/jos", + "depth3D": "Adâncime", + "depth3DHint": "Cât de mult iese subtitrarea din ecran - valorile pozitive o aduc mai aproape, cele negative o împing înapoi", + "plane3D": "3D-Plane", + "plane3DHint": "Dați fiecărei subtitrări adâncimea pe care un Blu-ray 3D o stochează pentru ea, dintr-un fișier 3D-Plane (.ofs) creat de BD3D2MK3D sau OFSExtractor. Adâncimea este folosită acolo unde 3D-Plane nu are niciuna", + "openPlane3DTitle": "Deschideți fișierul 3D-Plane", + "unableToLoadPlane3DX": "Nu se poate încărca 3D-Plane: {0}", + "plane3DXDepthYToZ": "{0} (adâncime de la {1} la {2})", + "plane3DXNoDepth": "{0} (fără valori de adâncime)", "textEffect": "Efect de text", "textEffectSettingsTitle": "Setări pentru efectul de text", "textEffectPreset": "Presetare", @@ -1413,13 +1438,10 @@ "statistics": { "title": "Statistici", "titleWithFileName": "Statistici - {0}", - "generalStatistics": "Statistici generale", - "mostUsed": "Cel mai folosit...", "mostUsedLines": "Cele mai folosite linii", "mostUsedWords": "Cele mai folosite cuvinte", "nothingFound": "Nu s-a găsit nimic", "numberOfLinesX": "Numărul de linii de subtitrare: {0:#,##0}", - "numberOfFilesX": "Număr de fișiere de subtitrare: {0:#,##0}", "lengthInFormatXinCharactersY": "Numărul de caractere ca {0}: {1:#,###,##0}", "numberOfCharactersInTextOnly": "Numărul de caractere numai în text: {0:#,###,##0}", "totalDuration": "Durata totală a tuturor subtitrarilor: {0:#,##0}", @@ -1460,7 +1482,6 @@ "gapMaximum": "Interval - maxim: {0:#,##0} ms", "gapAverage": "Gap - medie: {0:#,##0.##} ms", "gapExceedingMinimum": "Interval - sub minim ({0:#,##0} ms): {1} ({2:0.00}%)", - "export": "Export...", "subtitles": "Subtitrări", "words": "Cuvinte", "totalDurationShort": "Durata totală", @@ -1490,6 +1511,21 @@ "restoreAutoBackupFile": "Restaurați fișierul de backup automat", "deleteAllSubtitleBackups": "Doriți să ștergeți toate fișierele de rezervă pentru subtitrare?", "deleteAll": "Ștergeți toate", + "subtitles": "Subtitrări", + "settingsBackupInfo": "Se face o copie de rezervă a Settings.json la pornirea Subtitle Edit, cel mult o dată la {0} zi(le) și doar dacă setările s-au schimbat. Se păstrează cele mai noi {1} copii de rezervă.", + "settingsBackupInfoEveryStart": "Se face o copie de rezervă a Settings.json la fiecare pornire a Subtitle Edit, dacă setările s-au schimbat. Se păstrează cele mai noi {0} copii de rezervă.", + "settingsBackupOff": "Backupul automat al setărilor este dezactivat (Opțiuni → Setări → Fișier).", + "lastSettingsBackupX": "Ultimul backup: {0}", + "noSettingsBackupsYet": "Încă nu există copii de rezervă ale setărilor", + "openSettingsBackupFolder": "Deschideți folderul cu copiile de rezervă ale setărilor", + "backUpSettingsNow": "Faceți backup acum", + "settingsBackedUp": "S-a făcut o copie de rezervă a setărilor curente.", + "restoreSettings": "Restaurați setările", + "restoreSettingsFromX": "Doriți să înlocuiți setările curente cu copia de rezervă din {0}?\n\nMai întâi se va face o copie de rezervă a setărilor curente. Unele modificări intră în vigoare doar după repornirea Subtitle Edit.", + "settingsRestored": "Setările au fost restaurate. Reporniți Subtitle Edit pentru ca toate modificările să intre în vigoare.", + "settingsRestoreFailed": "Setările nu au putut fi restaurate: {0}", + "settingsBackupNotValid": "copia de rezervă nu este un fișier de setări valid, așa că setările curente au fost păstrate", + "deleteAllSettingsBackups": "Doriți să ștergeți toate fișierele de backup ale setărilor?", "restoreXFromY": "Doriți să restabiliți „{0}” din {1}?" }, "propertiesDCinema": { @@ -1504,22 +1540,16 @@ "editRate": "Editați rata", "timeCodeRate": "Rata codului de timp", "startTime": "Ora de începere", - "font": "Font", "fontId": "ID font", "fontUri": "URI font", - "generate": "Genera", - "fontColor": "Culoarea fontului", "chooseColor": "Alege culoarea", "fontEffect": "Efect de font", "effectColor": "Efect de culoare", - "fontSize": "Dimensiunea fontului", "topBottomMargin": "Marja de sus/de jos", "fadeUpTime": "Se estompează timpul", "fadeDownTime": "Fade down time", - "frames": "Rame", "zPosition": "Poziția Z", - "zPositionHelp": "Numerele pozitive depărtează textul, numerele negative apropie textul; dacă poziția Z este zero, atunci este 2D", - "export": "Export..." + "zPositionHelp": "Numerele pozitive depărtează textul, numerele negative apropie textul; dacă poziția Z este zero, atunci este 2D" }, "webVtt": { "setStylesForSelectedLinesTitle": "Setați stilurile WebVTT pentru liniile selectate", @@ -1552,20 +1582,66 @@ "compareOnlyInOneFile": "Doar într-un singur fișier", "compareTextOrTimeDifference": "Diferență de text/timp", "compareNumberDifference": "Diferență de număr", - "ignoreLineBreaks": "Ignorați întreruperile de rând", "ignoreWhitespace": "Ignorați spațiile albe", "ignoreWhitespaceHint": "Liniile care diferă doar prin spații, taburi sau întreruperi de linie nu contează ca fiind diferite", "ignoreFormatting": "Ignorați formatarea", - "onlyLookForDifferencesInText": "Căutați doar diferențele în text", - "cannotCompareWithImageBasedSubtitles": "Nu se poate compara cu subtitrarile bazate pe imagini", - "statisticsTitle": "Statistici", "ignoreFormattingHint": "Liniile care diferă doar prin etichete de formatare, precum sau {\\an8}, nu contează ca fiind diferite", "showOnlyDifferencesInText": "Doar diferențe de text", + "ignoreNumbering": "Ignorați numerotarea", + "ignoreNumberingHint": "Liniile care diferă doar prin număr nu sunt considerate diferite", + "ignoreTimeDifferenceMs": "Toleranță de timp (ms)", + "ignoreTimeDifferenceMsHint": "Timpii de început și de sfârșit care diferă cu cel mult atâtea milisecunde nu sunt considerați diferiți", + "compareDifferences": "Diferențe", + "compareTextDifferences": "Diferențe de text", + "compareCurrent": "Curent", + "compareReference": "Referință", + "compareEditable": "Editabil", + "compareEditableHint": "Modificările făcute aici sunt aplicate subtitrării când faceți clic pe Aplicați", + "compareReadOnly": "Doar citire", + "compareTakeFromReference": "Preluați textul și sincronizarea din referință", + "compareInsertFromReference": "Inserați această linie în subtitrarea curentă", + "compareDeleteFromCurrent": "Ștergeți această linie din subtitrarea curentă", + "compareTakeText": "Preluați textul", + "compareTakeTiming": "Preluați sincronizarea", + "compareEditLine": "Editați linia", + "compareEditHint": "Ctrl+Enter pentru salvare, Esc pentru anulare", + "compareEdited": "Editat", + "compareOnePendingChange": "1 modificare în așteptare", + "compareXPendingChanges": "{0} modificări în așteptare", + "compareDiscardXChanges": "Renunțați la cele {0} modificări făcute în Comparare?", + "compareXChangesApplied": "Comparare: {0} modificări aplicate", + "compareChangeTextX": "#{0} text din referință", + "compareChangeTimingX": "#{0} sincronizare din referință", + "compareChangeTextAndTimingX": "#{0} text și sincronizare din referință", + "compareChangeEditedX": "#{0} editat", + "compareChangeInsertedX": "#{0} inserat", + "compareChangeDeletedX": "#{0} șters", + "compareSyncPoint": "Punct de sincronizare", + "compareSyncPointHint": "Punct de sincronizare - aceste două linii sunt afișate mereu ca pereche, iar liniile de deasupra și de dedesubt sunt aliniate separat", + "compareSyncPickCurrent": "Punct de sincronizare: folosiți această linie curentă", + "compareSyncPickReference": "Punct de sincronizare: folosiți această linie de referință", + "compareSyncRemove": "Eliminați punctul de sincronizare", + "compareClearXSyncPoints": "Ștergeți punctele de sincronizare ({0})", + "compareSyncCurrentPickedX": "Linia curentă #{0} aleasă ca punct de sincronizare - selectați linia de referință corespunzătoare și faceți clic pe Sincronizare", + "compareSyncReferencePickedX": "Linia de referință #{0} aleasă ca punct de sincronizare - selectați linia curentă corespunzătoare și faceți clic pe Sincronizare", + "compareSyncWithCurrentX": "Sincronizați cu linia curentă #{0}", + "compareSyncWithReferenceX": "Sincronizați cu linia de referință #{0}", + "compareSyncApply": "Sincronizare", + "compareSyncPointSetXY": "Punct de sincronizare setat: linia curentă #{0} și linia de referință #{1} sunt acum afișate ca pereche", + "compareSyncAlreadyPairedXY": "Linia curentă #{0} și linia de referință #{1} sunt deja afișate ca pereche cu același început - nimic de sincronizat", + "compareSyncPointShiftedXYZW": "Punct de sincronizare setat: liniile curente #{0}-#{1} au fost decalate cu {2} pentru a începe cu linia de referință #{3}", + "compareChangeSyncShiftXYZ": "#{0}-#{1} decalate cu {2} (punct de sincronizare)", + "compareTimingDeltaHint": "Cu cât mai târziu (+) sau mai devreme (−) începe și se termină referința față de linia curentă", "loadXFromFile": "Încărcați „{0}” din fișier", + "compareLoadSavedFile": "Încărcați fișierul salvat", "saveCompareHtmlTitle": "Salvați fișierul HTML de comparare", "pickMatroskaTrackX": "Alegeți piesa Matroska - {0}", "pickTransportStreamTrackX": "Alegeți pista din fluxul de transport - {0}", + "chapters": "Capitole", + "dvdVobFilesMissingX": "Fișiere VOB lipsă ({0}% găsite)", "pickMp4TrackX": "Alegeți pista MP4 - {0}", + "pickMxfTrackX": "Alegeți pista MXF - {0}", + "pickMpegTrackX": "Alegeți pista MPEG - {0}", "rosettaProperties": "Timed Text Rosetta IMSC proprietăți", "rosettaFontSize": "Dimensiunea fontului (înălțimea rândului)", "propertyTimeBase": "Bază de timp", @@ -1575,7 +1651,6 @@ "propertyDefaultRegion": "Regiune implicită", "propertyStyleAttributeName": "Numele atributului de stil", "propertyTimeCodeFormat": "Formatul codului de timp", - "propertyFileExtension": "Extensia fișierului", "propertyTopOrigin": "Origine sus", "propertyTopExtent": "Întindere sus", "propertyBottomOrigin": "Origine jos", @@ -1625,7 +1700,6 @@ "selectionIntersect": "Se intersectează cu selecția" }, "multipleReplace": { - "title": "Înlocuire multiplă", "editRule": "Editați regula", "newRule": "Regulă nouă", "editCategory": "Editați categoria", @@ -1642,24 +1716,23 @@ "findWhat": "Găsiți ce", "descriptionOptional": "Descriere (opțional)", "invalidRegularExpressionX": "Expresie regulată nevalidă: {0}", - "regularExpressionTooSlowX": "Expresia regulată a renunțat după {0} secunde - regula este omisă, încercați un tipar mai simplu" + "regularExpressionTooSlowX": "Expresia regulată a renunțat după {0} secunde - regula este omisă, încercați un tipar mai simplu", + "moveToCategory": "Mutați în categorie" }, "find": { "searchTextWatermark": "Căutați text...", "wholeWord": "Cuvântul întreg", - "caseSensitive": "Caz sensibil", - "caseInsensitive": "Nu ține seama de majuscule", "findPrevious": "Găsiți _anterior", "findNext": "_Găsiți următorul", "replaceAndFindNext": "_Înlocuiți și găsiți următorul", "replaceAll": "Înlocuiți _toate", - "replaceTitle": "Înlocui", - "replaceWith": "Înlocuiește cu", "replaceTextWatermark": "Inlocuieste cu...", "replaceIn": "Înlocuiți/căutați în", "replaceInTextAndOriginal": "Text și text original", "replaceInTextOnly": "Numai text", - "replaceInOriginalOnly": "Numai textul original" + "replaceInOriginalOnly": "Numai textul original", + "replacedOneOccurrence": "S-a înlocuit o apariție", + "replacedXOccurrences": "S-au înlocuit {0} apariții" }, "regularExpressionContextMenu": { "wordBoundary": "Limită de cuvânt (\\b)", @@ -1687,6 +1760,7 @@ "xLinesYSubtitles": "{0} linii, {1} subtitrări", "xLinesYSubtitlesZErrors": "{0} linii, {1} subtitrări, {2} eroare(ori)", "couldNotParseAsX": "Nu s-a putut interpreta ca {0}", + "readAsXUseAsY": "Sursa nu a putut fi citită ca {1}, dar a putut fi citită ca {0}.\nFolosiți liniile și continuați să salvați ca {1}?", "selectedXCharactersYLines": "Selectat: {0} caractere în {1} linie(i)", "replacedXOccurrences": "S-au înlocuit {0} apariție(ii)", "invalidRegularExpression": "Expresie regulată nevalidă", @@ -1701,7 +1775,6 @@ "aiReview": { "title": "Revizuire AI", "review": "Revizuiți", - "stop": "Stop", "editPrompt": "Editați promptul...", "editPromptTitle": "Editați promptul de revizuire", "promptInfo": "Instrucțiuni trimise modelului - {language} este înlocuit cu limba subtitrării.", @@ -1712,7 +1785,6 @@ "reviewingLineXOfY": "Se revizuiește linia {0} din {1}...", "reviewDone": "Revizuire finalizată - {0} sugestii în {1} linii", "noIssuesFound": "Nu s-au găsit probleme", - "categoryAll": "Toate", "categorySpelling": "Ortografie", "categoryGrammar": "Gramatică", "categoryPunctuation": "Punctuație", @@ -1724,8 +1796,23 @@ "largeChangeWarning": "Modificare mare - pare o rescriere, verificați cu atenție", "mismatchWarning": "Nu seamănă cu originalul - ar putea aparține altei linii", "engineError": "Motorul AI nu a putut fi contactat: {0}", - "setupHint": "Alegeți mai întâi un motor și un model în Unelte -> Revizuire AI.", - "playCurrentLineHint": "Redați linia selectată în playerul video și opriți la sfârșitul ei (sau faceți dublu clic pe o linie)" + "playCurrentLineHint": "Redați linia selectată în playerul video și opriți la sfârșitul ei (sau faceți dublu clic pe o linie)", + "editAfterHint": "Faceți clic pe textul rândului selectat pentru a edita corectura înainte de a o aplica (Enter salvează, Escape anulează)", + "template": "Șablon", + "templatePlaceholder": "Alegeți un șablon pentru a înlocui promptul", + "templateStandard": "Corectură standard", + "templateSpeechToText": "Curățare după transcriere (vorbire în text)", + "templateMachineTranslation": "Corectarea traducerii automate", + "templateSdh": "SDH / persoane cu deficiențe de auz", + "referenceContext": "Context de referință", + "referenceContextInfo": "Opțional. Nume, personaje, glosar, rezumat sau note de stil - modelul tratează acestea ca autoritate pentru nume și termeni. Trimis cu fiecare lot.", + "referenceContextPlaceholder": "Nume: Kaito, mătușa Mei, Dr. Hollis\nTermeni: Shinkansen, miez warp\nRezumat: Kaito călătorește la Tokyo pentru a-și găsi tatăl dispărut.", + "generateContext": "Generați cu AI", + "generateContextHint": "Lăsați motorul AI selectat să citească subtitrarea și să schițeze nume, termeni și un rezumat - verificați rezultatul înainte de utilizare", + "generatingContextPartXOfY": "Se citește partea {0} din {1}...", + "generatingContextSynopsis": "Se scrie rezumatul...", + "replaceContextQuestion": "Înlocuiți contextul de referință curent?", + "noContextGenerated": "Motorul AI nu a returnat nicio informație de referință utilizabilă." }, "aiAssistant": { "title": "Asistent AI", @@ -1743,22 +1830,14 @@ "showReasoning": "Afișați raționamentul modelului" }, "fixCommonErrors": { - "title": "Remediați erorile comune", - "step1": "Pasul 1/2 - Alegeți ce erori să remediați", - "whatToFix": "Ce să repare", - "example": "Exemplu", - "selectAll": "Selectați toate", - "inverseSelection": "Inversați selecția", "back": "< &Înapoi", "next": "&Următorul >", - "step2": "Pasul 2/2 - Verificați remedierea", "fixes": "Remedieri", "ruleDetailsDotDotDot": "Detalii despre regulă...", "showOnlyThisRule": "Afișați numai remedierile din această regulă", "appliedRulesForLineX": "Regula(ile) aplicată(e) pentru linia {0}", "ruleX": "Regulă: {0}", "log": "Jurnal", - "function": "Funcţie", "removedEmptyLine": "Eliminați linia goală", "removedEmptyLineAtTop": "Îndepărtați linia goală din partea de sus", "removedEmptyLineAtBottom": "Îndepărtați linia goală din partea de jos", @@ -1779,13 +1858,13 @@ "removeLineBreaksPixelWidth": "Desfaceți subtitrări care se pot încadra pe o singură linie (lățimea pixelilor)", "fixUppercaseIInsideLowercaseWords": "Remediați „i” majuscul în interiorul cuvintelor cu minuscule (eroare OCR)", "fixDoubleApostrophes": "Fixați caracterele cu apostrof dublu ('') la un singur ghilimele (\")", + "fixMisreadQuotes": "Remediați apostrofurile citite greșit ca ghilimele duble (OCR, engleză)", "addPeriods": "Adăugați punct după rânduri în care rândul următor începe cu literă mare", "startWithUppercaseLetterAfterParagraph": "Începeți cu litera mare după paragraf", "startWithUppercaseLetterAfterPeriodInsideParagraph": "Începeți cu literă mare după punct în interiorul paragrafului", "startWithUppercaseLetterAfterColon": "Începeți cu majuscule după două puncte/punct virgulă", "fixLowercaseIToUppercaseI": "Remediați singur „i” minuscul la „I” (engleză)", "fixCommonOcrErrors": "Remediați erorile OCR obișnuite (folosind lista de înlocuire OCR)", - "commonOcrErrorsFixed": "Erori OCR obișnuite remediate (fișierul OcrReplaceList utilizat): {0}", "removeSpaceBetweenNumber": "Eliminați spațiul dintre numere", "breakDialogsOnOneLine": "Împărțiți dialogurile pe o singură linie", "removeDialogFirstInNonDialogs": "Eliminați liniuța de pornire din prima linie pentru non-dialoguri", @@ -1798,21 +1877,18 @@ "removeHyphensSingleLine": "Eliminați liniuțele de dialog în rânduri simple", "fixHyphensInDialogs": "Remediați liniuța în casetele de dialog prin stil: {0}", "addMissingQuotesExample": "\"Ce mai faci? -> \"Ce mai faci?\"", - "xMissingQuotesAdded": "Au adăugat ghilimele lipsă: {0}", "fix3PlusLine": "Remediați subtitrarea cu mai mult de două rânduri", "fix3PlusLines": "Remediați subtitrările cu mai mult de două rânduri", "analysing": "Se analizează...", "nothingToFix": "Nimic de reparat :)", - "fixesFoundX": "Remedieri găsite: {0}", "xFixesApplied": "Remedieri aplicate: {0}", "xFixesYSelected": "{0} remedieri - {1} selectate", - "nothingFixableBut": "Nimic nu a putut fi reparat automat. ", + "nothingFixableBut": "Nimic nu a putut fi reparat automat.", "xFixedBut": "S-au rezolvat {0} probleme, dar subtitrarea conține încă erori - consultați jurnalul pentru detalii", "xCouldBeFixedBut": "{0} probleme ar putea fi remediate, dar subtitrarea va conține în continuare erori - consultați jurnalul pentru detalii", "fixFirstLetterToUppercaseAfterParagraph": "Fixați prima literă la majuscule după paragraf", "mergeShortLine": "Îmbinați linia scurtă (o singură propoziție)", "mergeShortLineAll": "Îmbinați linia scurtă (toate cu excepția casetelor de dialog)", - "unbreakShortLinePixelWidth": "Desfaceți linia scurtă (lățimea pixelilor)", "breakLongLine": "Rupe linia lungă", "fixLongDisplayTime": "Remediați timpul de afișare lung", "fixInvalidItalicTag": "Remediați eticheta cursiv nevalidă", @@ -1820,11 +1896,11 @@ "fixOverlappingDisplayTime": "Remediați timpul de afișare suprapus", "fixShortGap": "Remediați decalajul scurt", "fixInvalidItalicTagsExample": "ce-mi pasă. -> ce-mi pasă.", - "removeUnneededSpacesExample": "Hei, tu ; ", - "removeUnneededPeriodsExample": "Hei, tu!. ", - "fixMissingSpacesExample": "Hei, tu. ", - "fixUppercaseIInsideLowercaseWordsExample": "Pământul este bine. ", - "fixLowercaseIToUppercaseIExample": "ce-mi pasă. ", + "removeUnneededSpacesExample": "Hei, tu ;", + "removeUnneededPeriodsExample": "Hei, tu!.", + "fixMissingSpacesExample": "Hei, tu.", + "fixUppercaseIInsideLowercaseWordsExample": "Pământul este bine.", + "fixLowercaseIToUppercaseIExample": "ce-mi pasă.", "startTimeLaterThanEndTime": "Numărul text {0}: ora de început este mai târziu decât ora de încheiere: {4}{1} -> {2} {3}", "unableToFixStartTimeLaterThanEndTime": "Nu se poate remedia numărul textului {0}: ora de începere este mai târziu decât ora de încheiere: {1}", "xFixedToYZ": "{0} fixat la: {1}{2}", @@ -1835,7 +1911,6 @@ "fixUppercaseIInsideLowercaseWord": "Remediați „i” majuscul în interiorul cuvântului cu minuscule", "fixMissingPeriodAtEndOfLine": "Adăugați un punct lipsă la sfârșitul rândului", "refreshFixes": "Actualizați corecțiile disponibile", - "applyFixes": "Aplicați corecțiile selectate și închideți", "autoBreak": "Auto &br", "unbreak": "&Unbreak", "fixDoubleDash": "Remediați „--” -> „...”", @@ -1843,19 +1918,17 @@ "fixEllipsesStart": "Eliminați primul „...”", "fixMissingOpenBracket": "Remediați lipsa [ sau ( în linie", "fixMusicNotation": "Înlocuiți simbolurile muzicale (de exemplu, âTª) cu simbolul preferat", - "fixDoubleDashExample": "'Woa-- um da!' ", - "fixDoubleGreaterThanExample": "'>> Robert: Sup dude!' ", + "fixDoubleDashExample": "'Woa-- um da!'", + "fixDoubleGreaterThanExample": "'>> Robert: Sup dude!'", "fixEllipsesStartExample": "'... și apoi noi' -> 'și apoi noi'", - "fixMissingOpenBracketExample": "Atentie! ", + "fixMissingOpenBracketExample": "Atentie!", "fixMusicNotationExample": "„âTª visele dulci sunt” -> „♫ visele dulci sunt”", "numberOfImportantLogMessages": "{0} mesaje de jurnal importante!", "fixedOkXY": "Remediat și OK - „{0}”: {1}", "fixOcrErrorExample": "Nu -> Nu", "fixSpaceBetweenNumbersExample": "1 100 -> 1100", "fixDialogsOneLineExample": "Salut John!
- Bună Ida!", - "removeDialogFirstInNonDialogsExample": "- Ce mai faci? ", - "selectDefault": "Selectați implicit", - "setDefault": "Setați corecțiile curente ca implicite", + "removeDialogFirstInNonDialogsExample": "- Ce mai faci?", "fixContinuationStyleX": "Remediați stilul de continuare: {0}", "fixUnnecessaryLeadingDots": "Eliminați punctele principale inutile", "fixCommonErrorsProfiles": "Remediați profilurile de erori comune", @@ -1863,7 +1936,6 @@ "searchRulesDotDotDot": "Reguli de căutare...", "goToApplyFixes": "Accesați aplicați remedieri", "backToFixList": "Înapoi la lista de remedieri", - "applyFixesAndClose": "Aplicați remedieri și închideți", "fixCommonOcrErrorsStep2": "Remediați erorile comune, pasul 2 (aplicați remedieri)", "fixCommonOcrErrorsStep2FixesFoundX": "Remediați erorile comune, pasul 2 - Remedieri găsite: {0}", "action": "Acţiune", @@ -1872,6 +1944,7 @@ "fixCommasExample": ",, -> ,", "removeLineBreaksExample": "Foo
bar! -> Foo bar!", "fixDoubleApostrophesExample": "''Has double single quotes'' -> \"Has single double quote\"", + "fixMisreadQuotesExample": "\"Hello' -> \"Hello\"", "addPeriodsExample": "Hello world -> Hello world.", "startWithUppercaseLetterAfterParagraphExample": "p1: Foobar! || p2: foobar! -> p1: Foobar! || p2: Foobar!", "startWithUppercaseLetterAfterPeriodInsideParagraphExample": "Hello there! how are you? -> Hello there! How are you?", @@ -1886,18 +1959,11 @@ "adjustDurations": { "title": "Ajustați duratele", "adjustVia": "Reglați prin", - "seconds": "secunde", - "percent": "La sută", "recalculate": "Recalculați", "addSeconds": "Adăugați secunde", - "setAsPercent": "Setați ca procent din durată", "note": "Notă: ora de afișare nu se va suprapune cu ora de începere a textului următor", "fixed": "Fix", - "milliseconds": "Milisecunde", "extendOnly": "Extindeți numai", - "enforceDurationLimits": "Aplicați durata minimă și maximă", - "checkShotChanges": "Nu extindeți modificările din trecut", - "batchCheckShotChanges": "Respectați modificările de lovituri (dacă sunt disponibile)", "recalculateRequiresOcrNote": "Recalcularea nu este disponibilă — una sau mai multe subtitrări nu au text OCR. Rulați mai întâi OCR." }, "applyDurationLimits": { @@ -1905,6 +1971,8 @@ "fixMinDurationMs": "Fixați durata minimă (ms)", "doNotGoPastShotChange": "Nu treceți de schimbarea loviturii", "fixMaxDurationMs": "Fixați durata maximă (ms)", + "fixMinDurationFrames": "Fixați durata minimă (cadre)", + "fixMaxDurationFrames": "Fixați durata maximă (cadre)", "minimumDurationMilliseconds": "Durată minimă (milisecunde):", "maximumDurationMilliseconds": "Durată maximă (milisecunde):", "maxDurationShouldBeHigherThanMinDuration": "Durata maximă ar trebui să fie mai mare decât durata minimă", @@ -1924,12 +1992,8 @@ }, "beautifyTimeCodes": { "title": "Înfrumusețați codurile de timp", - "beautifySettings": "Setări înfrumusețați", "snapToFrames": "Snap la cadre", - "frameGap": "Gap de cadru", "minDuration": "Durata min. (ms)", - "shotChangeThreshold": "Pragul de modificare a tragerii (ms)", - "shotChangeOffset": "Shot Change Offset (cadre)", "original": "Original", "beautified": "Înfrumusețată", "subtitlesCount": "Subtitrări", @@ -1958,10 +2022,76 @@ "timeCodesExtractFailed": "Nu s-au putut citi codurile de timp din videoclip", "timeCodesHint": "Fixați cue-urile la timpii reali ai cadrelor din videoclip, în loc să presupuneți o rată de cadre constantă. Necesită citirea întregului videoclip o dată; rezultatul este păstrat în cache." }, + "improveTimeCodes": { + "title": "Îmbunătățiți codurile de timp (aliniere forțată)", + "aligner": "Aliniator", + "alignerHint": "Modelul care ascultă fiecare linie în audio. Cele mai bune opțiuni pentru limba subtitrării sunt listate primele.", + "maxShift": "Decalaj maxim (secunde)", + "maxShiftHint": "Cât de mult poate fi desincronizată subtitrarea. O linie al cărei început s-ar muta mai departe este lăsată așa cum este și marcată. Indiferent de valoare, o singură linie care ar vrea să se mute cu peste o jumătate de secundă mai mult decât liniile din jurul ei nu este mutată orbește: urmează vecinii atunci când toți s-au mutat, iar altfel este oferită nebifată pentru a o verifica.", + "adjustStartTimes": "Ajustați timpii de început", + "adjustEndTimes": "Ajustați timpii de sfârșit", + "isolateSpeech": "Izolați mai întâi vorbirea (lent)", + "isolateSpeechHint": "Elimină muzica și efectele sonore înainte de aliniere, astfel încât aliniatorul aude doar dialogul. Ajută la liniile rostite peste muzică sau acțiune puternică, dar durează cam cât audio-ul însuși cu un GPU - și de multe ori mai mult fără.", + "isolatingSpeech": "Se izolează vorbirea... (durează ceva timp)", + "isolateSpeechFailed": "Vorbirea nu a putut fi izolată - alinierea s-a făcut în schimb pe audio-ul original.", + "checkWithSpeechToText": "Verificați cu vorbire în text", + "checkWithSpeechToTextHint": "Transcrie și audio-ul și verifică fiecare mutare față de locul în care cuvintele au fost efectiv auzite. Mutările mari confirmate de vorbire sunt bifate automat, iar mutările care se îndepărtează de locul în care se aude o linie sunt lăsate nebifate pentru verificare. Arată și cât din fiecare linie a fost auzit - o proporție mică înseamnă că textul diferă de ceea ce se spune. O subtitrare desincronizată mai mult decât decalajul maxim - un decalaj constant, o deviere din cauza unei rate de cadre nepotrivite sau salturi acolo unde s-au tăiat scene - este mai întâi sincronizată după cuvintele auzite. Folosește Crisp ASR Parakeet, care durează câteva minute pentru un lungmetraj cu un GPU.", + "checkWithSpeechToTextNotAvailableX": "Verificarea cu vorbire în text nu este disponibilă pentru {0}", + "transcribingToCheck": "Se transcrie pentru verificarea alinierii...", + "speechToTextFailed": "Vorbirea în text a eșuat - codurile de timp nu au fost verificate.", + "speechToTextX": "Vorbire în text: {0}", + "speechToTextConfirmedXDisputedY": "Vorbire: {0} confirmate, {1} contestate", + "hardlyHeardX": "Vorbirea în text a auzit doar {0}% din cuvintele subtitrării. Este subtitrarea în limba vorbită? Aliniatorul nu poate plasa o traducere.", + "heard": "Auzit", + "heardHint": "Cât din linie a auzit vorbirea în text. O proporție mică înseamnă că textul diferă de ceea ce se spune - acolo unde aliniatorul greșește cel mai probabil.", + "statusConfirmedBySpeech": "Confirmat de vorbire", + "statusDisputedBySpeech": "Auzit unde era", + "statusMovedWithSync": "Mutat odată cu sincronizarea", + "syncedFirstXY": "Sincronizat mai întâi: {0} s la început, {1} s la sfârșit", + "showSpeechOnly": "Afișați doar vorbirea", + "showSpeechOnlyHint": "Desenați forma de undă aliniată din audio-ul fără muzică și efecte sonore; forma de undă originală rămâne neschimbată, pentru comparație. Disponibil după ce vorbirea a fost izolată - aici sau cu „Afișați doar vorbirea” în forma de undă din fereastra principală.", + "align": "Aliniați", + "original": "Original", + "aligned": "Aliniat", + "intro": "Alegeți un aliniator și apăsați „Aliniați” pentru a asculta fiecare linie în audio și a-i strânge codurile de timp.", + "extractingAudio": "Se extrage audio...", + "aligningBatchXOfY": "Se aliniază... lotul {0} din {1}", + "extractAudioFailed": "Audio-ul nu a putut fi extras cu ffmpeg.", + "modelWillBeDownloaded": "{0} va fi descărcat", + "summaryXRetimedYKeptZSkipped": "Resincronizate: {0} · Păstrate: {1} · Fără vorbire: {2}", + "meanShiftX": "Decalaj mediu: {0} ms", + "toCheckX": "De verificat: {0}", + "largeMovesToCheckX": "{0} linii s-ar muta mult de unele singure și sunt lăsate nebifate - ascultați-le (F5 redă linia aliniată, Shift+F5 originalul) și bifați-le pe cele corecte.", + "linesToCheckX": "{0} linii sunt lăsate nebifate pentru verificare - ascultați-le (F5 redă linia aliniată, Shift+F5 originalul) și bifați-le pe cele corecte.", + "changeXOfY": "Modificarea {0} din {1}", + "previousChange": "Modificarea anterioară", + "nextChange": "Modificarea următoare", + "startShift": "Început", + "endShift": "Sfârșit", + "statusRetimed": "Resincronizat", + "statusMovedWithNeighbours": "Mutat odată cu vecinii", + "statusLargeMoveUnconfirmed": "Mutare mare - ascultați, bifați pentru a aplica", + "statusUnchanged": "Deja la locul lui", + "statusNoSpeech": "Fără vorbire", + "statusShiftTooLarge": "Păstrat - decalaj prea mare", + "statusFailed": "Păstrat - aliniatorul a eșuat", + "statusAdjustedByHand": "Ajustat manual", + "alignedWaveformHint": "Trageți o linie pentru a o muta sau trageți-i marginile pentru a-i schimba începutul sau sfârșitul.", + "playLine": "Redați linia", + "undoAdjustment": "Anulați ajustarea", + "playPause": "Redare / pauză", + "playAligned": "Aliniat", + "playAlignedHint": "Redați linia selectată cu codurile de timp aliniate", + "apply": "Aplicați", + "applyHint": "Debifați o linie pentru a-i păstra codurile de timp originale. Faceți dublu clic pe o linie pentru a o reda. Liniile pot fi și mutate sau redimensionate în forma de undă aliniată." + }, "bridgeGaps": { - "title": "Goluri de pod", "bridgeGapsSmallerThan": "Goluri de punte mai mici de (ms)", "minGap": "Interval minim (ms)", + "bridgeGapsSmallerThanFrames": "Închideți intervalele mai mici de (cadre)", + "minGapFrames": "Interval minim (cadre)", + "calculateBridgeGapsSmallerThanDotDotDot": "Calculați limita de închidere a intervalelor dintr-o rată de cadre...", + "useXMsAsBridgeGapsSmallerThan": "Închideți intervalele mai mici de „{0}” milisecunde?", "numberOfSmallGapsBridgedX": "Numărul de goluri mici acoperite: {0}", "percentFoPrevious": "Diferența anterioară (%)", "gapChange": "Schimbarea decalajului" @@ -1973,8 +2103,6 @@ "sortBy": { "title": "Sortați subtitrări", "sortOrder": "Ordinea de sortare", - "sortByNumber": "Număr", - "sortByStartTime": "Ora de începere", "sortByEndTime": "Ora de sfârșit" }, "batchConvert": { @@ -1997,6 +2125,7 @@ "addColor": "Adăugați culoare", "deleteLinesWithSpecificActorsOrStyles": "Ștergeți liniile cu actori sau stiluri (separați multiplu prin virgulă)", "useSourceStylesIfPossible": "Folosiți stiluri sursă dacă este posibil", + "keepSourceEmbeddedFonts": "Păstrați fonturile încorporate din sursă (subsol)", "editStyles": "Editați stiluri", "editProperties": "Editați proprietăți", "editAttachments": "Editați atașamentele", @@ -2017,7 +2146,6 @@ "assaChangeStyleFromStyle": "Schimbați stilul de la", "assaChangeStyleToStyle": "la", "assaChangeStyleImportStyle": "Importați stilul...", - "assaChangeStyleImportedX": "Importat: {0}", "assaChangeStyleTrimUnusedStyles": "Eliminați stilurile nefolosite", "assaChangeStylePropertiesTitle": "Modificați proprietățile stilului", "assaChangeStylePropertiesInfo": "Modifică câmpurile alese în fiecare stil din fișier, fără a atinge stilurile în sine.", @@ -2029,13 +2157,52 @@ "convertColorsToDialogReBreakLines": "Reîmpărțiți liniile", "snapTimeCodesToFramesInfo": "Rotunjește fiecare timp de început și de sfârșit la cel mai apropiat cadru. Rata de cadre este citită dintr-un fișier video cu același nume ca fișierul de subtitrare, dacă nu este aleasă o rată de cadre fixă.", "addFolderDotDotDot": "Adăugați folder...", + "addFolderRecursiveDotDotDot": "Adăugați folderul recursiv...", "selectFolderToConvert": "Selectați folderul cu fișierele de convertit", "includeSubfolders": "Includeți subfolderele când adăugați un folder", + "videoFilesWhenAddingFolder": "Fișiere video (mkv, mp4, ts...) la adăugarea unui folder", + "videoFilesAsk": "Întrebați", + "videoFilesInclude": "Adăugați pistele lor de subtitrare", + "videoFilesSkip": "Omiteți", + "folderContainsXVideoFiles": "Folderul conține {0} fișier(e) video (mkv, mp4, ts...).\n\nAdăugați și pistele de subtitrare încorporate sau doar fișierele de subtitrare?", + "addVideoFiles": "Adăugați fișierele video", + "skipVideoFiles": "Doar fișierele de subtitrare", + "doNotAskAgainVideoFiles": "Nu mai întrebați (modificați în setările conversiei în lot)", + "settingsSectionOutput": "Ieșire", + "settingsSectionAddingFiles": "Adăugarea fișierelor", + "settingsSectionImageBasedSubtitles": "Subtitrări bazate pe imagini (OCR)", + "settingsSectionOther": "Altele", "keepSourceFileTimestamp": "Păstrați data/ora fișierului sursă pe fișierele de ieșire", - "scanningFolderX": "Se scanează {0}..." + "preventSleepWhileConverting": "Împiedicați intrarea computerului în repaus în timpul conversiei", + "scanningFolderX": "Se scanează {0}...", + "transportStreamSettings": "Setări Transport Stream", + "transportStreamSettingsDotDotDot": "Setări Transport Stream...", + "transportStreamSettingsInfo": "Se aplică pistelor de subtitrare extrase din fișiere Transport Stream (.ts/.m2ts). Poziția și dimensiunea video afectează doar pistele de imagine DVB exportate într-un format bazat pe imagini.", + "transportStreamOverrideXPosition": "Înlocuiți poziția X originală", + "transportStreamOverrideYPosition": "Înlocuiți poziția Y originală", + "transportStreamOverrideVideoSize": "Înlocuiți dimensiunea video originală", + "transportStreamBottomMargin": "Margine de jos", + "transportStreamFileNameEnding": "Terminația numelui de fișier", + "transportStreamFileNameEndingInfo": "Adăugată înaintea extensiei pentru fiecare pistă extrasă. Lăsați gol pentru a folosi sufixul obișnuit de limbă.", + "transportStreamOnlyTeletext": "Doar teletext", + "transportStreamGetSizeFromVideo": "Preluați dimensiunea din video...", + "twoLetterLanguageCodeUppercase": "Cod de limbă din două litere (majuscule)", + "threeLetterLanguageCodeUppercase": "Cod de limbă din trei litere (majuscule)", + "formatContainsDotDotDot": "Formatul conține...", + "extensionIsDotDotDot": "Extensia este...", + "statusIsError": "Starea este eroare", + "forcedTracksOnly": "Doar pistele forțate", + "xConvertedYFailedInZ": "{0:#,###,##0} convertite, {1:#,###,##0} eșuate în {2}", + "xNotProcessed": "{0:#,###,##0} neprocesate", + "removeUnicodeControlCharactersTitle": "Eliminați caracterele de control Unicode", + "removeUnicodeControlCharactersInfo": "Elimină din text caracterele invizibile Unicode de control al direcției (LRM, RLM, LRE, RLE, PDF, LRO și RLO) și înlocuiește spațiile neîntreruptibile cu spații normale.", + "preset": "Presetare", + "savePresetDotDotDot": "Salvați presetarea...", + "deletePreset": "Ștergeți presetarea", + "presetName": "Numele presetării", + "deletePresetX": "Ștergeți presetarea „{0}”?" }, "changeCasing": { - "title": "Schimbați carcasa", "fixNames": "Remediați numele", "namesX": "Nume: {0:#,##0}", "hitsX": "Potriviri: {0:#,##0}", @@ -2046,9 +2213,7 @@ "allUppercase": "Toate majuscule", "allLowercase": "Toate litere mici" }, - "changeFormatting": { - "title": "Schimbați formatarea" - }, + "changeFormatting": {}, "convertActors": { "title": "Convertiți actorii", "convertActorFrom": "Convertiți actorul din", @@ -2069,7 +2234,6 @@ "title": "Subtitrare împărțită", "numberOfEqualParts": "Număr de părți egale", "saveSplitParts": "_Salvați părțile divizate", - "subtitleSplitIntoXParts": "Subtitrare împărțită în {0} părți.", "xPartsSavedInFormatYToFolder": "{0} părți salvate în format {1} în dosar:" }, "splitBreakLongLines": { @@ -2095,13 +2259,14 @@ "onlyContinuationLines": "Doar linii de continuare" }, "mergeLinesWithSameText": { - "title": "Îmbina liniile cu același text", "maxMsBetweenLines": "Max milisecunde între linii", - "includeIncrementingLines": "Includeți linii cu text în creștere" + "maxFramesBetweenLines": "Număr maxim de cadre între linii", + "includeIncrementingLines": "Includeți linii cu text în creștere", + "includeRollUpCaptions": "Includeți subtitrările roll-up (derulante)" }, "mergeLinesWithSameTimeCodes": { - "title": "Îmbina liniile cu aceleași coduri de timp", "maxMsDifference": "Diferența maximă (milisecunde)", + "maxFramesDifference": "Diferență maximă (cadre)", "makeDialog": "Faceți dialoguri" }, "netflixCheckAndFix": { @@ -2124,7 +2289,6 @@ "shotChanges": "Modificări de fotografiere", "startNumberSpellOut": "Scrieți în litere numărul de la început", "textforHiUseBrackets": "Text pentru deficiențe de auz, folosiți paranteze", - "frameRate": "Rata de cadre", "twoFrameGrap": "Decalaj de două cadre", "whiteSpace": "Spațiu alb", "childrensProgram": "Program pentru copii", @@ -2162,7 +2326,6 @@ "italicsFixed": "Cursiv remediat", "italicsNotAllowed": "Cursivul nu este permis", "bridgeGapsXToYFrames": "decalaj de 3-{0} cadre => decalaj de 2 cadre", - "startNumberSpellOutComment": "Când un număr începe o propoziție, ar trebui întotdeauna scris în litere", "maximumXCharactersPerSecond": "Maximum {0} caractere pe secundă" }, "imageBasedEdit": { @@ -2172,23 +2335,21 @@ "resizeImagesDotDotDot": "Redimensionați imaginile...", "adjustBrightnessDotDotDot": "Reglați luminozitatea...", "adjustAlphaDotDotDot": "Ajustați alfa...", - "centerHorizontally": "Centrați pe orizontală", "cropImages": "Decupați imaginile", "importTimeCodes": "Importă coduri de timp...", - "setTextForSubtitle": "Setați textul pentru subtitrare", "screenWidth": "Lățimea ecranului", "screenHeight": "Înălțimea ecranului", "alphaThresholdInfo": "Pixelii cu alfa sub prag devin complet transparenți", "resetToDefaults": "Resetați la valorile implicite", - "alphaAdjustmentInfo": "Ajustare alfa: Adăugați/scădeți din canalul alfa.\n", + "alphaAdjustmentInfo": "Ajustare alfa: Adăugați/scădeți din canalul alfa.\nPrevizualizarea se actualizează automat și se afișează pe un fundal în carouri pentru a vizualiza transparența.", "adjustBrightness": "Reglați luminozitatea", "brightness": "Luminozitate", "contrast": "Contrast", "gamma": "Gamma", - "brightnessAdjustmentInfo": "Reglați glisoarele pentru a modifica luminozitatea, contrastul și gama.\n", + "brightnessAdjustmentInfo": "Reglați glisoarele pentru a modifica luminozitatea, contrastul și gama.\nPrevizualizarea se actualizează automat și afișează prima subtitrare selectată.", "resizeImages": "Redimensionați imaginile", "percentage": "Procent", - "resizeImagesInfo": "Introduceți procentul de redimensionare a imaginilor.\n", + "resizeImagesInfo": "Introduceți procentul de redimensionare a imaginilor.\nPrevizualizarea se actualizează automat.", "originalSizeXNewSizeY": "Original: {0} × {1} px\nNou: {2} × {3} px", "adjustColorDotDotDot": "Ajustați culoarea...", "adjustColor": "Ajustați culoarea", @@ -2205,15 +2366,31 @@ "letterboxCustom": "Înălțime personalizată a barei", "barHeightPx": "Înălțimea barei (px)", "titleSafePercent": "Zonă sigură pentru titlu (%)", + "moveCaptions": "Mutați subtitrările", + "moveCaptionsDotDotDot": "Mutați subtitrările...", + "moveCaptionsTo": "Mutați subtitrările", + "moveCaptionsIntoBars": "În benzile letterbox (în afara imaginii)", + "moveCaptionsIntoPicture": "În interiorul imaginii (în afara benzilor)", + "moveCaptionsOffset": "Distanța de la margine (px)", + "letterboxBarHeightXPxYOfZCaptionsWillMove": "Înălțimea benzii: {0} px - {1} din {2} subtitrări vor fi mutate", + "removeFadeInOut": "Eliminați fade in/out", + "removeFadeInOutXLinesRemoved": "{0} linii de fade îmbinate în liniile cărora le aparțin", + "removeFadeInOutNothingFound": "Nu s-au găsit linii de fade in/out", "positionSummary": "{0}×{1} - {2} subtitrări - înălțimea barei: {3} px", "xInActivePicture": "{0} în imagine", "xInTopBar": "{0} în bara de sus", "xInBottomBar": "{0} în bara de jos", "noImageSubtitlesLoaded": "Nu sunt încărcate subtitrări imagine", - "imageBasedFormatNotSupported": "Format de subtitrare bazat pe imagini negăsit/neacceptat." + "imageBasedFormatNotSupported": "Format de subtitrare bazat pe imagini negăsit/neacceptat.", + "frameRateDeclaredInFile": "Rata de cadre declarată în fișierul Blu-ray sup", + "frameRateDetectedFromTimeCodesX": "Rata de cadre detectată din codurile de timp Blu-ray sup (fișierul declară {0})", + "frameRateFromVideo": "Rata de cadre a videoclipului", + "frameRateCurrent": "Rata de cadre curentă", + "frameRateSetManually": "Rata de cadre setată manual", + "frameRateUsageInfo": "Exporturile Blu-ray sup, BDN XML, DOST și FCP sunt scrise la această rată de cadre. Nu modifică niciun timp - folosiți „Modificați rata de cadre” pentru asta.", + "useVideoFrameRateXInsteadOfY": "Rata de cadre a videoclipului este {0}, dar subtitrarea este setată la {1}.\n\nFolosiți rata de cadre a videoclipului ({0})?" }, "removeTextForHearingImpaired": { - "title": "Eliminați textul pentru persoanele cu deficiențe de auz", "interjections": "Interjecții", "skipIfStartWith": "Sariți dacă începeți cu", "removeTextBetween": "Eliminați textul dintre", @@ -2232,7 +2409,6 @@ "removeInterjections": "Eliminați interjecțiile", "linesFoundX": "Linii găsite: {0}" }, - "pickAlignmentTitle": "Alegeți alinierea", "removeUnicodeCharacters": { "title": "Eliminați/înlocuiți caracterele Unicode", "charactersFoundX": "Caractere Unicode găsite: {0}", @@ -2242,13 +2418,13 @@ "pickFontNameInstalledFonts": "Fonturi instalate", "pickFontNameCollectedFonts": "Fonturi colectate", "colorPickerTitle": "Alege culoarea", + "colorPickerBrightness": "Luminozitate", "filterLayersTitle": "Straturi de filtrare pentru afișare", "filterLayersHideFromWaveform": "Ascundeți din forma de undă/spectrogramă", "filterLayersHideFromSubtitleGrid": "Ascundeți din grila de subtitrare", "filterLayersHideFromVideoPreview": "Ascundeți din previzualizarea videoclipului", "pickSubtitleFormat": "Alegeți formatul de subtitrare", "pickSubtitleFormatImageBasedNoPreview": "Format de subtitrare bazat pe imagini.\nBitmap-urile sunt generate în timpul conversiei - fără previzualizare de text.", - "pickLayerTitle": "Setați stratul", "recentColors": "Culori recente", "beautifyTimeCodesProfile": { "title": "Editați profilul de înfrumusețare a codurilor de timp", @@ -2256,7 +2432,11 @@ "presetDefault": "Implicit", "presetNetflix": "Netflix", "presetSdi": "SDI", - "general": "General", + "saveAsProfile": "Salvați ca profil...", + "deleteProfile": "Ștergeți profilul", + "profileName": "Numele profilului", + "overwriteProfileX": "Există deja un profil numit „{0}”. Îl suprascrieți?", + "deleteProfileX": "Ștergeți profilul „{0}”?", "gap": "Decalaj", "gapSuffix": "cadre (actualizează și câmpurile de decalaj diferite de zero de mai jos)", "milliseconds": "ms", @@ -2296,12 +2476,7 @@ "outputFormatAssa": "Advanced Sub Station Alpha (.ass)", "style1": "Stilul 1", "style2": "Stilul 2", - "outlineWidth": "Lățimea conturului", - "shadowWidth": "Lățimea umbrei", - "alignTop": "Sus", - "alignBottom": "Jos", - "merge": "_Îmbină", - "pleaseLoadTwoSubtitles": "Vă rugăm să încărcați două subtitrări" + "merge": "_Îmbină" }, "mergeContinuationLines": { "title": "Îmbinați liniile de continuare", @@ -2312,9 +2487,7 @@ "columnMerge": "Îmbină", "columnFirst": "Prima linie", "columnSecond": "A doua linie", - "columnMerged": "Îmbinat", - "selectAll": "Selectați toate", - "inverseSelection": "Inversați selecția" + "columnMerged": "Îmbinat" }, "fixCommonErrorsSettingsTitle": "Setări pentru remedierea erorilor comune" }, @@ -2359,59 +2532,51 @@ "correctWordsX": "Cuvinte corecte: {0}", "namesX": "Nume: {0}", "addedToDictionaryX": "Adăugate în dicționar: {0}", - "doNotShowThisAgain": "Nu mai afișa acest mesaj" + "doNotShowThisAgain": "Nu mai afișa acest mesaj", + "editUseAlwaysList": "Editați lista „Folosiți întotdeauna”...", + "useAlwaysListTitle": "Verificare ortografică - lista „Folosiți întotdeauna”", + "useAlwaysListHeader": "Lista „Folosiți întotdeauna”", + "useAlwaysListDescription": "Cuvinte pe care verificarea ortografică le înlocuiește automat, salvate prin „Schimbați tot” și „Folosiți întotdeauna”. O pereche este folosită doar când cuvântul este scris greșit.", + "useAlwaysWord": "Cuvânt scris greșit", + "xPairs": "{0:#,##0} perechi", + "xUnusedPairs": "{0:#,##0} niciodată folosite", + "useAlwaysUnusedHint": "Niciodată folosit - acest cuvânt este scris corect (sau este un nume ori un cuvânt al utilizatorului), așa că verificarea ortografică nu îl semnalează niciodată", + "removeUnused": "Eliminați nefolosite", + "useAlwaysEmpty": "Încă nu există perechi „Folosiți întotdeauna” pentru această limbă", + "useAlwaysEmptyHint": "Perechile sunt adăugate când faceți clic pe „Schimbați tot” sau „Folosiți întotdeauna” în verificarea ortografică.", + "useAlwaysNoMatch": "Nicio pereche nu corespunde căutării", + "useAlwaysSameWord": "Înlocuirea trebuie să fie diferită de cuvânt.", + "useAlwaysRememberOff": "„Memorați lista «Folosiți întotdeauna»” este dezactivat în Setări, așa că verificarea ortografică nu folosește și nu salvează această listă." }, "video": { "burnIn": { "title": "Generați videoclipuri cu subtitrări arse", - "infoAssaOff": "Notă: este acceptat stilul Advanced SubStation Alpha.", - "infoAssaOn": "Notă: va fi folosit stilul avansat al SubStation Alpha :)", - "xGeneratedWithBurnedInSubsInX": "„{0}” generat cu subtitrare inscripționată în {1}.", - "timeRemainingMinutes": "Timp rămas: {0} minute", - "timeRemainingOneMinute": "Timp rămas: un minut", - "timeRemainingSeconds": "Timp rămas: {0} secunde", - "timeRemainingAFewSeconds": "Timp rămas: câteva secunde", - "timeRemainingMinutesAndSeconds": "Timp rămas: {0} minute și {1} secunde", - "timeRemainingOneMinuteAndSeconds": "Timp rămas: un minut și {0} secunde", - "targetFileName": "Nume fișier țintă: {0}", "targetFileSize": "Dimensiunea fișierului țintă (necesită codare cu 2 treceri)", "fileSizeMb": "Dimensiunea fișierului în MB", "matchSourceVideoSize": "Potriviți dimensiunea videoclipului sursă", - "passX": "Treceți {0}", - "encoding": "Codificare", "bitRate": "Rata de biți", "totalBitRateX": "Rată totală de biți: {0}", "sampleRate": "Rata de eșantionare", "audio": "Audio", - "stereo": "Stereo", "preset": "Presetat", + "tune": "Tune", "pixelFormat": "Format pixel", "crf": "CRF", - "tuneFor": "Acordați pentru", - "alignRight": "Aliniați dreapta", - "getStartPosition": "Obțineți poziția de pornire", - "getEndPosition": "Obțineți poziția finală", "useSource": "Utilizați sursa", - "useSourceFolder": "Utilizați folderul sursă", - "useSourceResolution": "Utilizați rezoluția sursei", - "outputSettings": "Fișier/dosar de ieșire...", "fontSizeFactor": "Factor de dimensiune a fontului", "boxType": "Tip cutie", - "fixRightToLeft": "Remediați de la dreapta la stânga", - "cut": "Tăiați", "fromTime": "Din timp", "toTime": "La timp", "audioEncoding": "Codificare audio", "outputProperties": "Proprietăți de ieșire...", "videoFileSize": "Dimensiunea fișierului video", "oneBox": "O cutie", - "boxPerLine": "Cutie pe linie", - "logoInfo": "Alegeți o imagine PNG și trageți-o pentru a o poziționa pe videoclip." + "logoInfo": "Alegeți o imagine PNG și trageți-o pentru a o poziționa pe videoclip.", + "noSubtitlesGenerateAnyway": "Nu există linii de subtitrare de inscripționat.\n\nGenerați videoclipul fără subtitrări?", + "noSubtitlesInXItemsGenerateAnyway": "{0} elemente din lot nu au linii de subtitrare de inscripționat.\n\nGenerați acele videoclipuri fără subtitrări?" }, "videoTransparent": { - "title": "Generați videoclipuri transparente cu subtitrări", - "infoAssaOff": "Notă: este acceptat stilul Advanced SubStation Alpha.", - "infoAssaOn": "Notă: va fi folosit stilul avansat al SubStation Alpha :)" + "title": "Generați videoclipuri transparente cu subtitrări" }, "audioToText": { "title": "Vorbire către text", @@ -2419,6 +2584,21 @@ "translateToEnglish": "Traduceți în engleză", "transcribing": "Se transcrie...", "transcribingXOfY": "Se transcrie {0} din {1}...", + "linesTranscribedXOfYFailedZ": "{0} din {1} linii au fost transcrise - {2} au eșuat sau nu au avut vorbire.\n\nLiniile eșuate își păstrează textul curent. Aplicați liniile transcrise sau reîncercați-le pe cele eșuate (puteți schimba mai întâi motorul sau setările)?", + "linesTranscribedXOfYCancelled": "Transcrierea a fost anulată după {0} din {1} linii.\n\nAplicați liniile transcrise? Celelalte linii își păstrează textul curent.", + "noLinesTranscribed": "Niciuna dintre cele {0} linii nu a putut fi transcrisă.\nVerificați jurnalul instrumentelor pentru detalii.", + "applyTranscribedLines": "Aplicați cele transcrise", + "retryFailedLines": "Reîncercați cele eșuate", + "isolateSpeech": "Izolați vorbirea (lent)", + "isolateSpeechHint": "Elimină muzica și efectele sonore înainte de transcriere. Duce la mai puține linii ratate și o sincronizare mai bună pe audio cu muzică puternică, dar durează cam cât audio-ul însuși cu un GPU - și de multe ori mai mult fără.", + "isolatingSpeech": "Se izolează vorbirea...", + "isolateSpeechFailed": "Vorbirea nu a putut fi izolată - se transcrie în schimb audio-ul original.", + "detectSpeakers": "Detectați vorbitorii", + "detectSpeakersHint": "Începe fiecare linie cu vorbitorul, de exemplu „(speaker 0)”, folosind diarizarea Sortformer de la NVIDIA (până la 8 vorbitori). Funcționează cel mai bine cu backend-urile care împart liniile acolo unde se schimbă vorbitorul, precum Parakeet și Cohere.", + "detectSpeakersNeedsNewerCrispAsr": "„Detectați vorbitorii” necesită Crisp ASR v{0} sau mai nou, dar este instalat v{1}.\n\nActualizați Crisp ASR (butonul roată de lângă motor) sau dezactivați „Detectați vorbitorii”.", + "xNeedsNewerCrispAsr": "{0} necesită Crisp ASR v{1} sau mai nou, dar este instalat v{2}.\n\nDescărcați și instalați actualizarea acum?", + "vad": "VAD", + "vadHint": "Detectarea activității vocale: se transcrie doar vorbirea, tăiată acolo unde vorbitorul face pauză.\nAuto: Silero, și doar pentru motoarele care au nevoie (Cohere, Mega, Index-Echo).\nSilero, FireRedVAD, Whisper-VAD, WebRTC: mereu activ, pentru fiecare motor Crisp ASR. FireRedVAD este cel mai precis, Whisper-VAD dă cele mai puține și mai curate tăieturi, dar este cel mai lent.\nIndex-Echo folosește întotdeauna Silero. --vad, --vad-model sau --chunk-seconds din setările avansate au prioritate.", "inputLanguage": "Limba de intrare", "advancedWhisperSettings": "Setări avansate pentru șoaptă", "enableVad": "Activați VAD", @@ -2431,7 +2611,6 @@ "addCustomModelDotDotDot": "Adăugați model personalizat...", "customModelHelp": "Puteți folosi propriul model: alegeți fișierul modelului (whisper.cpp ggml '.bin') sau folderul modelului (folder faster-whisper cu un 'model.bin' înăuntru). Acesta este copiat în folderul de modele și adăugat în lista de mai sus.", "viewToolsLogFile": "Vizualizați fișierul jurnal Whisper", - "reDownloadX": "Descărcați din nou {0}", "downloadingSpeechToTextModel": "Se descarcă modelul de vorbire în text", "whisperPostProcessingTitle": "Post-procesare în șoaptă", "adjustTimings": "Ajustați timpul", @@ -2441,17 +2620,8 @@ "fixCasing": "Fixați carcasa", "addPeriods": "Adăugați puncte", "changeUnderlineToColor": "Schimbați sublinierea în culoare", - "cueBuilding": "Construire cue-uri (MLX Whisper)", - "cueRebuild": "Reconstruiți cue-urile din marcajele temporale ale cuvintelor", - "cueMaxChars": "Caractere maxime per cue", - "cueMaxSeconds": "Secunde maxime per cue", - "cueMaxCps": "Caractere maxime pe secundă", - "vocabularyPrompt": "Prompt de vocabular (nume, locuri, termeni)", - "beamSize": "Dimensiune beam", - "beamSizeNote": "0 decodează cel mai rapid. 5 oferă cea mai bună acuratețe (ca Faster Whisper) cu aproximativ 20-30% mai mult timp. Valorile peste 5 ajută rar.", "downloadingSpeechToTextEngine": "Se descarcă motorul de vorbire în text", "unpackingSpeechToTextEngine": "Se despachetează motorul de vorbire în text", - "downloadX": "Descărcați {0}", "updateXTitle": "Actualizați {0}?", "updateXMessage": "O versiune mai nouă a {0} este disponibilă.{1}{1}Descărcați și instalați actualizarea acum?", "removeNonSpeechLines": "Eliminați liniile fără vorbire", @@ -2512,7 +2682,7 @@ "cloneVoicePerLinePreparing": "Se preia vocea fiecărei linii din videoclip...", "cloneVoicePerLineNeedsVideo": "Clonarea vocii fiecărei linii necesită videoclipul căruia îi aparține subtitrarea. Deschideți videoclipul și încercați din nou.", "cloneVoicePerLineNoClips": "Nu s-a putut prelua audio din videoclip, deci nu există sursă de clonare. Verificați dacă videoclipul are o pistă audio.", - "cloneVoicePerLineNeedsOriginalSubtitleX": "{0} poate clona o linie doar când știe ce se spune în videoclip la acea linie, informație care provine din subtitrarea în limba originală. Nicio subtitrare originală nu este încărcată, deci fiecare linie ar fi rostită de prima voce importată, în locul vocii din videoclip.\n\nDeschideți subtitrarea originală (Fișier > Deschideți originalul...) alături de traducere, apoi generați din nou.", + "cloneVoicePerLineTranscribeClipsXYZ": "{0} poate clona o linie doar când știe ce se spune în videoclip la acea linie. Acest lucru nu se știe pentru {1} din {2} linii - în mod normal provine din subtitrarea în limba originală (Fișier > Deschideți originalul...).\n\nTranscrieți acum acele linii din videoclip cu vorbire în text?\n\nDa: alegeți motorul și limba vorbită în videoclip, iar clipurile de referință sunt transcrise înainte de generarea vorbirii.\nNu: generați oricum - acele linii sunt rostite de o voce obișnuită în loc de vocea din videoclip.", "voiceCloneConsentTitle": "Clonarea vocii - înainte de a începe", "voiceCloneConsentHeader": "Sunteți pe cale să clonați o voce", "voiceCloneConsentIntro": "Clonarea copiază vocea unei persoane reale. Acest lucru vine cu reguli în majoritatea locurilor, iar în UE cu obligații legale care vă revin dvs., nu programului Subtitle Edit. Citiți acest text o dată.", @@ -2528,6 +2698,12 @@ "voiceCloneConsentDeclined": "Clonarea vocii nu este disponibilă până când acești termeni nu sunt acceptați.", "advancedTtsAudioProcessing": "Procesare audio", "advancedTtsOutput": "Ieșire", + "advancedTtsVoiceCloning": "Clonarea vocii", + "cleanCloneReferences": "Curățați referințele pentru clonarea vocii (Sidon)", + "cleanCloneReferencesDescription": "Elimină muzica, zgomotul și reverberația din audio-ul din care se clonează o voce: clipuri preluate din videoclip și înregistrări importate în managerul de voci. Folosește modelul de restaurare a vorbirii Sidon pe audio.cpp (descărcare de 940 MB). Vocea clonată nu mai copiază fundalul, dar vorbirea foarte încetă sau suprapusă poate ieși înfundată.", + "cleaningCloneReferencesDotDotDot": "Se curăță referințele pentru clonarea vocii...", + "downloadSidonTitle": "Descărcați Sidon?", + "downloadSidonQuestionX": "Curățarea referințelor pentru clonarea vocii necesită modelul de restaurare a vorbirii Sidon ({0}).\n\nÎl descărcați acum? Fără el, referințele sunt folosite așa cum sunt.", "rubberbandInstalled": "(instalat)", "rubberbandNotFound": "(negăsit în FFmpeg)", "generationFolder": "Folder de generare", @@ -2536,14 +2712,11 @@ "deleteTempFilesDescription": "Șterge folderul de generare la închiderea ferestrei Text în vorbire. Fișierele audio și video salvate sunt păstrate.", "piperSettings": "Setări Piper", "title": "Text în vorbire", - "textToSpeechEngine": "Motor text în vorbire", "reviewAudioSegments": "TTS - Examinați segmentele audio", "reviewAudioSegmentsHistory": "TTS - Examinați istoricul audio", "stability": "Stabilitate", "similarity": "Similitudine", "speakerBoost": "Amplificare difuzor", - "styleExaggeration": "Exagerarea stilului", - "regenerateAudioSelectedLine": "Regenerează sunetul pentru linia selectată", "generateSpeechFromText": "Generați vorbire din text", "testVoice": "Testează vocea", "addAudioToVideoFile": "Adăugați audio la fișierul video", @@ -2562,37 +2735,113 @@ "videoEncodingSettings": "TTS - Setări de codare video", "elevenLabsSettings": "Setări TTS - ElevenLabs", "elevenLabsSettingsResetHint": "Resetați setările ElevenLabs la valorile implicite", + "provider": "Furnizor", + "openAiCompatibleSettings": "TTS - Setări compatibile OpenAI", + "customServerUrl": "URL server personalizat", + "customServerUrlHint": "Endpoint-ul complet de vorbire al unui server compatibil OpenAI, folosit când furnizorul este „Custom”, de ex. http://localhost:8880/v1/audio/speech", + "customModels": "Modele personalizate", + "customModelsHint": "Nume de modele separate prin virgulă, oferite când furnizorul este „Custom”", + "customVoices": "Voci personalizate", + "customVoicesHint": "Nume de voci separate prin virgulă, folosite de furnizorul „Custom” și de modelele OpenRouter care nu publică o listă de voci", + "instructions": "Instrucțiuni", + "instructionsHint": "Stil de vorbire opțional, de ex. „Vorbește cu o voce calmă și caldă”. Trimis doar modelelor care îl acceptă (nu tts-1/tts-1-hd)", + "openAiSpeedHint": "Viteza vorbirii de la 0.25 la 4.0 (1.0 este normal). Nu toate modelele o acceptă", + "outputFormat": "Format de ieșire", + "outputFormatHint": "Formatul audio cerut de la server. „Auto” cere MP3 și trece la PCM pentru modelele care returnează doar PCM (de ex. Gemini TTS)", "regenerateAudio": "Regenerează sunetul", "autoContinuePlaying": "Continuați automat jocul", "addingAudioToVideoFileDotDotDot": "Se adaugă sunet la fișierul video...", "preparingMergeDotDotDot": "Se pregătește îmbinarea...", "importVoiceDotDotDot": "Import voce...", + "renameVoiceDotDotDot": "Redenumiți vocea...", + "renameVoiceTitle": "Redenumiți vocea", + "voiceXCouldNotBeRenamedX": "Vocea „{0}” nu a putut fi redenumită: {1}", + "deleteVoiceDotDotDot": "Ștergeți vocea...", + "deleteVoiceTitle": "Ștergeți vocea", + "deleteVoiceXQuestion": "Ștergeți vocea „{0}”?\n\nÎnregistrarea de referință și fișierele ei sunt eliminate de pe disc.", + "voiceXCouldNotBeDeletedX": "Vocea „{0}” nu a putut fi ștearsă: {1}", "voiceImportSuccessTitle": "Voce importată", "voiceXImported": "Vocea „{0}” a fost importată cu succes", "advancedTtsSettings": "Setări avansate TTS", "proAudioPostProcessing": "Post-procesare audio profesională", "proAudioPostProcessingDescription": "Aplică căldura EQ, noise gate, compresie, normalizarea sonorității (-16 LUFS) și fade in/out pentru fiecare segment.", + "generatingSpeechSegmentXOfY": "Se generează vorbirea: segmentul {0} din {1}", + "adjustingSpeedSegmentXOfY": "Se ajustează viteza: segmentul {0} din {1}", + "mergingAudioSegmentXOfY": "Se îmbină audio: segmentul {0} din {1}", + "postProcessingSegmentXOfY": "Post-procesare: segmentul {0} din {1}", "audioDucking": "Ducking audio", "audioDuckingDescription": "Reduce volumul audio video original și îl amestecă cu sunetul TTS, astfel încât coloana sonoră originală este încă puțin audibilă.", + "removeOriginalSpeech": "Eliminați vorbirea originală (lent)", + "removeOriginalSpeechDescription": "Păstrează muzica și efectele sonore ale videoclipului original, dar îi elimină vorbirea și mixează noua vorbire peste acestea. Durează cam cât videoclipul însuși cu un GPU și de multe ori mai mult fără. Cu atenuarea audio (ducking) activată, volumul ei se aplică muzicii și efectelor. Se aplică doar când vorbirea este adăugată în fișierul video.", + "removingOriginalSpeech": "Se elimină vorbirea originală...", + "removeOriginalSpeechFailed": "Vorbirea originală nu a putut fi eliminată - vorbirea a fost adăugată în videoclip fără această eliminare.", + "speechRemovalModel": "Model", + "speechRemovalModelDescription": "Calitatea cea mai bună păstrează cel mai mult din muzică și efectele sonore. Rapid este de circa trei ori mai iute, cu ceva mai multă muzică pierdută.", + "speechRemovalBestQuality": "Calitate maximă (Mel-Band RoFormer)", + "speechRemovalFast": "Rapid (HTDemucs)", + "videoTruncatedWarning": "Imaginea din noul fișier video se oprește după {0}, dar videoclipul original durează {1} - imaginea va îngheța în timp ce sunetul continuă.\n\nFișierul a fost păstrat. Consultați error-log.txt din folderul de date Subtitle Edit pentru ieșirea ffmpeg.", "originalVolumePercent": "Volumul original %", "vadSilenceCompression": "Compresie silențioasă VAD", - "vadSilenceCompressionDescription": "Scurtează pauzele dintre cuvinte înainte de a schimba tempo-ul. ", + "vadSilenceCompressionDescription": "Scurtează pauzele dintre cuvinte înainte de a schimba tempo-ul.", "maxSilenceMs": "Tăcere maximă (ms)", "highQualityTimeStretch": "Extindere în timp de înaltă calitate (WSOLA/bandă de cauciuc)", - "highQualityTimeStretchDescription": "Folosește algoritmul de bandă de cauciuc (WSOLA) în loc de filtrul de atempo implicit pentru schimbările de viteză care păstrează înălțimea. ", + "highQualityTimeStretchDescription": "Folosește algoritmul de bandă de cauciuc (WSOLA) în loc de filtrul de atempo implicit pentru schimbările de viteză care păstrează înălțimea.", "silencePaddingMs": "Umplutură de tăcere (ms)", - "silencePaddingMsDescription": "Adaugă o scurtă tăcere la sfârșitul fiecărui segment. ", + "silencePaddingMsDescription": "Adaugă o scurtă tăcere la sfârșitul fiecărui segment.", "outputSampleRate": "Frecvența de eșantionare a ieșirii (0 = implicit)", - "outputSampleRateDescription": "Reeșantionează toate segmentele la rata de eșantionare specificată (de exemplu, 44100, 48000). ", + "outputSampleRateDescription": "Reeșantionează toate segmentele la rata de eșantionare specificată (de exemplu, 44100, 48000).", "edgeTtsRate": "Rata Edge-TTS", - "edgeTtsRateDescription": "Rata de vorbire pentru Edge-TTS, de ex. ", + "edgeTtsRateDescription": "Rata de vorbire pentru Edge-TTS, de ex.", "edgeTtsPitch": "Pasul Edge-TTS", - "edgeTtsPitchDescription": "Ajustarea pasului pentru Edge-TTS, de ex. ", + "edgeTtsPitchDescription": "Ajustarea pasului pentru Edge-TTS, de ex.", "edgeTtsVolume": "Volumul Edge-TTS", - "edgeTtsVolumeDescription": "Reglarea volumului pentru Edge-TTS, de ex. ", + "edgeTtsVolumeDescription": "Reglarea volumului pentru Edge-TTS, de ex.", "dropAudioFileHereToImportVoice": "Plasați fișierul audio aici pentru a importa vocea", "dropAudioFileHereHint": ".wav sau .mp3", "voiceCloneTranscriptTitle": "Introduceți transcrierea audio (necesar pentru clonarea vocii)", + "voiceManagerTitle": "TTS - Manager de voci", + "voiceManagerDotDotDot": "Manager de voci...", + "voiceManagerMenuItem": "Manager de voci TTS...", + "speakFromCurrentLineTitle": "Citiți de la linia curentă", + "speakFromCurrentLineDotDotDot": "Citiți de la linia curentă...", + "speakFromCurrentLineHint": "Citește liniile cu voce tare una după alta, începând cu linia curentă. Apăsați Escape sau faceți clic pe altă linie pentru a opri.", + "stopSpeaking": "Opriți citirea", + "speakingLineX": "Se citește linia {0}... (Escape pentru oprire)", + "playWithSpeechTitle": "Redare cu vorbire", + "playWithSpeechDotDotDot": "Redare cu vorbire...", + "playWithSpeechHint": "Redă videoclipul și rostește fiecare linie când videoclipul ajunge la ea. Puneți pauză și derulați ca de obicei; apăsați Escape pentru a opri.", + "lowerVideoVolumeWhileSpeaking": "Reduceți volumul videoclipului în timpul vorbirii", + "pauseVideoWhenSpeechRunsLate": "Puneți pauză videoclipului când vorbirea se suprapune cu linia următoare", + "waitingForSpeechLineX": "Se generează vorbirea pentru linia {0}... (Escape pentru oprire)", + "voiceKindClone": "Voce clonată", + "voiceKindPreset": "Integrată", + "voiceKindModel": "Model de voce", + "voiceKindOnline": "Online", + "transcript": "Transcriere", + "transcriptRequiredHint": "Obligatoriu - motorul citește această transcriere a înregistrării (ref-text) la clonare", + "transcriptOptionalHint": "Opțional - o transcriere exactă a înregistrării îmbunătățește clonarea", + "saveTranscript": "Salvați transcrierea", + "searchVoices": "Căutați voci", + "loadingVoicesDotDotDot": "Se încarcă vocile...", + "noVoicesForEngine": "Nicio voce - importați o înregistrare sau descărcați un pachet de voci", + "noVoicesMatchFilter": "Nicio voce nu corespunde căutării", + "copyVoiceTo": "Copiați vocea în", + "copyVoiceToDotDotDot": "Copiați vocea în motor...", + "copyingVoiceXToYDotDotDot": "Se copiază vocea „{0}” în {1}...", + "voiceXCopiedToY": "Vocea „{0}” a fost copiată în {1}", + "voiceXCouldNotBeCopiedToY": "Vocea „{0}” nu a putut fi copiată în {1} - consultați jurnalul pentru detalii", + "voiceXAlreadyExistsInYContinue": "O voce numită „{0}” există deja în {1}.\n\nO copiați totuși?", + "openVoicesFolder": "Deschideți folderul cu voci", + "downloadVoicePacksDotDotDot": "Descărcați pachete de voci...", + "downloadVoicePacksTitle": "TTS - Descărcați pachete de voci", + "voicePackIntro": "Pachetele de voci sunt seturi de înregistrări scurte și curate, cu transcrieri, gata pentru clonare. Fiecare pachet este importat în motorul ales (vocile deja existente sunt omise) și include un ATTRIBUTION.txt cu licența și sursele sale.", + "voicePackDetailsXVoicesYSizeZLicense": "{0} voci · {1} · {2}", + "installToEngine": "Instalați în motor", + "downloadAndInstall": "Descărcați și instalați", + "selectAtLeastOneVoicePack": "Selectați cel puțin un pachet de voci", + "downloadingXDotDotDot": "Se descarcă {0}...", + "installingXDotDotDot": "Se instalează {0}...", + "xVoicesInstalledYSkipped": "{0} voci instalate ({1} existau deja)", "useSpeechToTextDotDotDot": "Folosiți vorbire în text...", "downloadPiperPrompt": "„Text în vorbire” necesită Piper.\r\n\r\nDescărcați și folosiți Piper?", "omniVoiceTtsSettings": "Setări OmniVoice TTS", @@ -2644,7 +2893,6 @@ "subtitleMergedLinesAppliedPlural": "S-au aplicat {0} îmbinări de linii din text în vorbire" }, "shotChanges": { - "titleGenerateOrImport": "Generați/importați modificări de fotografie", "generateShotChanges": "Generați modificări de fotografiere", "importShotChanges": "Importă modificări de fotografiere", "generateShotChangesWithFfmpeg": "Generați modificări de fotografiere cu ffmpeg", @@ -2654,7 +2902,9 @@ "timeCodeFormatColon": "Format cod de timp:", "shotChangesList": "Lista cu modificări de fotografie", "shotChangesClearQuestion": "Sigur doriți să ștergeți toate modificările de fotografiere?", - "deleteSelectedShotChangeQuestion": "Sigur doriți să ștergeți modificarea fotografiei selectate?" + "deleteSelectedShotChangeQuestion": "Sigur doriți să ștergeți modificarea fotografiei selectate?", + "shotChanges": "Schimbări de plan", + "exportShotChanges": "Exportați schimbările de plan" }, "chapters": { "title": "Capitole", @@ -2684,7 +2934,6 @@ "scaleTimesDescription": "Scalați timpii fiecărui capitol de la o rată de cadre la alta.", "fromFrameRate": "De la", "toFrameRate": "La", - "apply": "Aplicați", "writeToVideo": "Scrieți în videoclip...", "writeToVideoTitle": "Scrieți capitolele în videoclip", "writeToVideoDescription": "Capitolele sunt scrise într-o copie a fișierului video. Nimic nu este recodificat, deci operația este rapidă și fără pierderi.", @@ -2694,7 +2943,6 @@ "writtenToX": "Capitole scrise în {0}", "unableToWriteChapters": "Capitolele nu au putut fi scrise în fișierul video.", "chaptersSavedToX": "Capitole salvate în {0}", - "noVideoLoaded": "Niciun videoclip încărcat", "emptyListCallToAction": "Încă niciun capitol - adăugați unul la poziția video sau importați-le dintr-un videoclip ori fișier." }, "videoOcr": { @@ -2705,10 +2953,6 @@ "bottomHalf": "Jumătatea de jos", "fullFrame": "Cadru complet", "engine": "Motor OCR", - "language": "Limbă", - "url": "URL", - "model": "Model", - "apiKey": "Cheie API", "scan": "Scanați", "framesPerSecond": "Cadre pe secundă", "textBrightnessMinimum": "Luminozitate minimă text (0=dezactivat)", @@ -2723,10 +2967,10 @@ "runningOcrXY": "Se rulează OCR... {0}/{1}", "noLinesFoundTitle": "Nu s-au găsit subtitrări", "noLinesFoundMessage": "Nu s-a găsit text în zona de scanare - încercați să ajustați zona de scanare, motorul sau luminozitatea minimă.", + "ocrStoppedEarlyMessage": "Motorul OCR s-a oprit înainte ca toate cadrele să fie citite, așa că liniile găsite până acum sunt păstrate, iar restul lipsesc.{0}{0}{1}", "linesFoundX": "{0} linii găsite", "refiningTimingXY": "Se rafinează sincronizarea... {0}/{1}", "fixOcrErrors": "Remediați erorile OCR", - "dictionary": "Dicționar", "editLineX": "Editați linia {0}", "downloadDictionaryHint": "Descărcați un dicționar de verificare ortografică...", "framesPerSecondHint": "Câte cadre pe secundă sunt scanate. 5 acoperă subtitrările normale; o valoare mai mare prinde liniile foarte scurte și strânge gruparea, dar costă mai mult timp de OCR.", @@ -2743,7 +2987,58 @@ "testOcr": "Testați cadrul curent", "testOcrRunning": "Se testează OCR pe cadrul curent...", "testOcrResultX": "Rezultat test: {0}", - "testOcrNoTextFound": "Test: nu s-a găsit text în zona de scanare" + "testOcrNoTextFound": "Test: nu s-a găsit text în zona de scanare", + "engineSettings": "Setări motor OCR", + "engineBuiltIn": "Integrat în sistemul de operare", + "engineExternalServer": "Server extern (instalat și gestionat în afara Subtitle Edit)", + "engineCloudApi": "API cloud (necesită o cheie API)", + "engineNothingToInstall": "Nimic de instalat", + "engineModelsMissing": "Motor instalat, modele lipsă", + "website": "Site web" + }, + "backgroundMusic": { + "title": "Generați muzică de fundal", + "generateBackgroundMusicDotDotDot": "Generați muzică de fundal...", + "preset": "Presetare", + "prompt": "Prompt", + "tempoBpm": "Tempo (BPM)", + "generateSeconds": "Generați (secunde)", + "seed": "Seed", + "randomSeed": "Aleatoriu", + "outputLength": "Lungimea ieșirii", + "outputLengthVideoX": "{0} (lungimea videoclipului - muzica se repetă fără întreruperi)", + "musicVolumePercent": "Volumul muzicii (%)", + "removeExistingAudioTracks": "Eliminați pistele audio existente", + "originalAudioVolumePercent": "Volumul audio original (%)", + "saveAudioDotDotDot": "Salvați audio...", + "addToVideoDotDotDot": "Adăugați în videoclip...", + "engineInfo": "ACE-Step 1.5 Turbo prin audio.cpp (licență MIT) - rulează local", + "presetCooking": "Gătit / copt", + "presetTech": "Tehnologie / recenzie gadgeturi", + "presetForest": "Pădure / natură", + "presetRelaxingPiano": "Pian relaxant", + "presetLofiVlog": "Vlog lo-fi", + "presetCorporate": "Corporate / explicativ", + "loadingModel": "Se încarcă modelul...", + "composing": "Se compune...", + "generatingAudio": "Se generează audio...", + "decodingAudio": "Se decodează audio...", + "creatingLoop": "Se creează bucla fără întreruperi...", + "resultLoopXYZ": "Buclă: {0} la {1} BPM, potrivire la îmbinare {2}%", + "resultNoLoopX": "Muzica este gata: {0}", + "addingMusicToVideo": "Se adaugă muzica în videoclip...", + "downloadModelTitle": "Descărcați modelul muzical?", + "downloadModelQuestionX": "Generarea muzicii necesită modelul ACE-Step 1.5 ({0}).\n\nDescărcați modelul?", + "downloadingModel": "Se descarcă modelul ACE-Step 1.5...", + "saveAudioTitle": "Salvați muzica de fundal", + "audioFileSavedX": "Fișier audio salvat: {0}", + "unableToGenerateMusic": "Nu se poate genera muzica", + "unableToAddMusicToVideo": "Nu se poate adăuga muzica în videoclip", + "outputLengthTextToSpeech": "Urmează vorbirea generată - muzica se repetă fără întreruperi și scade când vorbește cineva", + "addBackgroundMusic": "Adăugați muzică de fundal", + "addBackgroundMusicHint": "Generați muzică local cu ACE-Step 1.5 și mixați-o sub vorbire - scade când vorbește cineva", + "generatingBackgroundMusicDotDotDot": "Se generează muzica de fundal...", + "mixingBackgroundMusicDotDotDot": "Se mixează muzica de fundal..." }, "goToVideoPosition": "Mergeți la poziția video", "goToVideoPositionDotDotDot": "Mergeți la poziția video...", @@ -2754,34 +3049,91 @@ "openSecondarySubtitleOnVideoPlayerDotDotDot": "Subtitrare secundară pe playerul video, deschide...", "openSecondarySubtitleOnVideoPlayer": "Subtitrare secundară (pe player video)", "removeSecondarySubtitleOnVideoPlayer": "Subtitrare secundară pe playerul video, eliminați", + "editSecondarySubtitleSettings": "Editați setările celei de-a doua subtitrări", + "editSecondarySubtitleSettingsDotDotDot": "Editați setările celei de-a doua subtitrări...", + "secondarySubtitleRememberSettings": "Memorați aceste setări", + "secondarySubtitleDoNotShowAgain": "Nu mai afișați acest dialog", + "secondarySubtitleJustifyLines": "Aliniați liniile la ambele margini", "openRecentVideo": "Deschideți un videoclip recent", "clearRecentVideos": "Ștergeți videoclipurile recente", "cutVideoTitle": "Tăiați videoclipul", "cutVideoDotDotDot": "Tăiați videoclipul...", + "remuxVideoTitle": "Remux video", + "remuxVideoDotDotDot": "Remux video...", + "remuxVideoInputVideo": "Videoclip de intrare:", + "remuxVideoMultipleTracksRequiresMkv": "Mai multe piste audio sau de subtitrare necesită containerul MKV. Formatul de ieșire a fost schimbat automat în .mkv.", + "remuxVideoOutputFormat": "Format de ieșire:", + "remuxVideoOutputFile": "Fișier de ieșire:", + "remuxVideoRemuxing": "Se face remux pentru video și audio...", + "remuxVideoFinalizing": "Se finalizează fișierul de ieșire (se mută indexul la început)...", + "remuxVideoCompleted": "Remux video realizat cu succes.", + "remuxVideoFailed": "Remux video eșuat.", + "remuxVideoPleaseSelectBoth": "Selectați atât fișierul video, cât și fișierele audio.", + "remuxVideoAssRequiresMkv": "Subtitrările ASS/SSA necesită containerul MKV pentru a păstra toată formatarea și stilurile.", + "remuxVideoSccRequiresMov": "Subtitrările Scenarist (.scc) sunt încorporate ca subtitrări închise CEA-608, care necesită containerul MOV sau MPG. Formatul de ieșire a fost schimbat automat în .mov.", + "remuxVideoSccNotWithWebM": "Subtitrările închise Scenarist (.scc) necesită containerul MOV, care nu poate conține video VP9/AV1 dintr-un fișier .webm. Eliminați fișierul .scc sau convertiți-l în alt format de subtitrare.", + "remuxVideoMpgMaxTwoCaptionTracks": "Un fișier .mpg conține cel mult două piste de subtitrări închise (CC1 și CC3) - eliminați fișiere de subtitrare astfel încât să rămână una sau două.", + "remuxVideoMccRequiresMpg": "Subtitrările MacCaption (.mcc) sunt încorporate ca subtitrări închise CEA-608/708 în video MPEG-2, care necesită containerul MPG. Formatul de ieșire a fost schimbat automat în .mpg.", + "remuxVideoMpgReencodeVideoX": "Subtitrările închise dintr-un fișier .mpg necesită video MPEG-2, iar videoclipul este {0}. Re-encodați videoclipul în MPEG-2? Durează mai mult și scade puțin calitatea.", + "remuxVideoFileHasNoAudioX": "„{0}” nu are pistă audio, deci nu poate fi folosit ca sursă audio.", + "remuxVideoAddingClosedCaptions": "Se adaugă subtitrările închise...", + "remuxVideoSelectAudioTrack": "Selectați pista audio", + "remuxVideoSelectAudioTrackFor": "Selectați pista audio pentru {0}", + "remuxVideoSelectAudioTrackPrompt": "Fișierul „{0}” conține {1} piste audio. Selectați pista audio pentru remux:", + "remuxVideoVideoFile": "video", + "remuxVideoAudioFile": "audio", + "remuxVideoAudioFiles": "Fișiere audio:", + "remuxVideoSubtitleFiles": "Fișiere de subtitrare (opțional, subtitrări soft):", + "remuxVideoSelectAudioTrackDotDotDot": "Selectați pista audio...", + "remuxVideoFilesX": "{0} fișiere", + "remuxVideoMixAudio": "Mixați audio într-o singură pistă", + "remuxVideoVolumePercent": "Volum %:", + "remuxVideoVolumeX": "Volum {0}%", + "remuxVideoFastStart": "Optimizați pentru streaming web (mută indexul la început - mai lent)", "embedSubtitlesDotDotDot": "Adăugați/eliminați subtitrări încorporate...", "generateTimeCodes": "Generați coduri de timp", "checkeredImage": "Imagine în carouri", - "saveVideoAsTitle": "Salvați videoclipul ca", "promptForFfmpegParamsAndGenerate": "Solicitați parametrii ffmpeg și generați", "cutVideoCutSegments": "Tăiați segmente", "cutVideoMergeSegments": "Îmbinați segmente", "cutVideoSplitSegments": "Salvați segmentele individual", - "cutVideoType": "Tip de tăiere", - "mpvRenderAuto": "Auto", - "mpvRenderNative": "Nativ", - "mpvRenderOpenGl": "OpenGL", - "mpvRenderSoftware": "Software (lent)", "cutVideoAlsoCutSubtitle": "Tăiați și subtitrarea", "cutVideoSubtitleFileGeneratedX": "Fișier de subtitrare generat: {0}", + "cutVideoTransition": "Tranziție între segmente", + "cutVideoTransitionDurationSeconds": "Durată (secunde)", + "cutVideoFadeInSeconds": "Apariție din negru la început (secunde)", + "cutVideoFadeOutSeconds": "Dispariție în negru la sfârșit (secunde)", + "cutVideoPreviewTransition": "Previzualizați tranziția", + "cutVideoPreviewTransitionTitle": "Previzualizarea tranziției", + "cutVideoPreviewNeedsJoin": "Nu există nicio îmbinare de previzualizat - adăugați cel puțin două segmente de îmbinat sau un segment de tăiat din mijloc.", + "cutVideoTransitionInfoX": "Fiecare tranziție suprapune părțile pe care le îmbină, așa că ieșirea devine cu {0} secunde mai scurtă la fiecare îmbinare. O subtitrare tăiată este resincronizată corespunzător.", + "cutVideoTransitionFade": "Estompare", + "cutVideoTransitionFadeBlack": "Estompare prin negru", + "cutVideoTransitionFadeWhite": "Estompare prin alb", + "cutVideoTransitionFadeGrays": "Estompare prin gri", + "cutVideoTransitionDissolve": "Dizolvare", + "cutVideoTransitionWipeLeft": "Ștergere spre stânga", + "cutVideoTransitionWipeRight": "Ștergere spre dreapta", + "cutVideoTransitionWipeUp": "Ștergere în sus", + "cutVideoTransitionWipeDown": "Ștergere în jos", + "cutVideoTransitionSlideLeft": "Glisare spre stânga", + "cutVideoTransitionSlideRight": "Glisare spre dreapta", + "cutVideoTransitionSlideUp": "Glisare în sus", + "cutVideoTransitionSlideDown": "Glisare în jos", + "cutVideoTransitionSmoothLeft": "Lin spre stânga", + "cutVideoTransitionSmoothRight": "Lin spre dreapta", + "cutVideoTransitionCircleOpen": "Cerc care se deschide", + "cutVideoTransitionCircleClose": "Cerc care se închide", + "cutVideoTransitionRadial": "Radial", + "cutVideoTransitionPixelize": "Pixelare", + "cutVideoTransitionBlur": "Estompare (blur)", "importCurrentSubtitle": "Importă subtitrarea curentă", "addRemoveEmbeddedSubtitlesTitle": "Adăugați/eliminați subtitrări încorporate", "addCurrentSubtitle": "Adăugați subtitrarea curentă", - "titleOrLanguage": "Titlu/limba", "viewMatroskaTrackX": "Vezi traseul Matroska - {0}", "resolutionSeparator": "x", "openFromUrlTitle": "Deschideți fișierul video de la URL", "addRemoveEmbeddedSubtitlesMp4Title": "Adăugați/eliminați subtitrări încorporate (MP4)", - "embeddedTrackColumnNew": "Nou", "embeddedTrackNoSubtitlesFoundTitle": "Nu s-au găsit subtitrări", "embeddedTrackNoSubtitlesFoundMessage": "Fișierul de subtitrare selectat nu conține nicio subtitrare.", "embeddedTrackNoTracksTitle": "Nu s-au adăugat piste", @@ -2793,15 +3145,14 @@ "embeddedTrackGeneratingVideoXY": "Se generează videoclipul... {0}% {1}", "embeddedTrackGeneratingVideo": "Se generează videoclipul...", "mp4FilesFilter": "Fișiere MP4", + "embeddedTracksPickVideoHint": "Alegeți un fișier video sau trageți-l aici pentru a-i vedea pistele de subtitrare.", "openFromUrlOpenOnline": "Deschideți online", "openFromUrlOpenOnlineDescription": "Redați videoclipul direct în flux. Pornire cea mai rapidă.", "openFromUrlOpenOnlineNote": "Vorbirea în text și vârfurile formei de undă nu sunt disponibile în acest mod.", "openFromUrlDownloadAndOpen": "Descărcați și deschideți", "openFromUrlDownloadAndOpenDescription": "Salvați videoclipul local, apoi deschideți-l.", "openFromUrlDownloadAndOpenNote": "Necesar pentru vorbire în text și vârfurile formei de undă.", - "openFromUrlMissing": "Vă rugăm să introduceți mai întâi un URL video.", "openFromUrlDownloadingTitle": "Se descarcă videoclipul", - "openFromUrlSaveAs": "Salvați videoclipul ca", "openFromUrlDownloadSubtitles": "Descărcați și subtitrările", "openFromUrlDownloadVideo": "Descărcați videoclipul", "openFromUrlIncludeAutoGeneratedSubtitles": "Includeți subtitrările generate automat (limba originală)", @@ -2838,12 +3189,15 @@ "reEncodeGeneratingDone": "Generare finalizată", "reEncodeGeneratedFilesX": "Fișiere generate ({0}):", "reEncodeFfmpegParameters": "parametri ffmpeg", + "analyzingVideoX": "Se analizează videoclipul... {0}% {1}", + "analyzingVideoXofY": "Se analizează videoclipul {0}/{1}... {2}% {3}", + "backgroundImageFileNotSelected": "Fișierul imagine de fundal nu este selectat", + "backgroundImageFileDoesNotExistX": "Fișierul imagine de fundal nu există: {0}", "voices": "Voci", "presets": "Presetări", "talkerX": "Vorbitorul {0}", "baseModel": "Model de bază", "turboModel": "Model turbo", - "ttsUpdateX": "Actualizați {0}", "ttsEngineNotInstalled": "{0} nu este instalat", "ttsEngineUpdateAvailable": "{0} - actualizare disponibilă", "ttsEngineTooOldUpdateRequired": "{0} prea vechi - este necesară actualizarea", @@ -2857,26 +3211,32 @@ "guessTimeCodesSplitLongSubtitlesAt": "Împărțiți subtitrări lungi la (ms):", "speechToTextSelectedLinesDotDotDot": "Vorbire în text liniile selectate...", "seekSilence": "Caută tăcerea", - "minSilenceDurationSeconds": "Min. ", - "maxSilenceVolume": "Max. ", + "minSilenceDurationSeconds": "Min.", + "maxSilenceVolume": "Max.", "guessTimeCodesDotDotDot": "Ghici coduri de timp...", "seekSilenceDotDotDot": "Caută tăcerea...", "cloneVoiceTo": "Clonați vocea în", "cloneVoiceNameTitle": "Numele noii voci", "cloneVoiceExtractFailed": "Nu s-a putut extrage audio pentru linia selectată.", + "lineXHasNoDuration": "Linia {0} nu are durată (timpul de sfârșit nu este după timpul de început), deci nu există audio de extras.", + "audioClipsSkippedLinesX": "Nu s-a putut extrage audio pentru aceste linii, așa că au fost omise: {0}", "toggleShotChange": "Comutați schimbarea loviturii", "resetWaveformZoomAndSpeed": "Resetați zoomul și viteza formei de undă", "showOriginalSubtitle": "Afișați subtitrarea originală", + "timelineGroupTracksBy": "Grupați pistele de subtitrare după", + "timelineShowThumbnails": "Afișați miniaturi video", + "showSpeechOnly": "Afișați doar vorbirea (generare lentă)", + "isolatingSpeechForWaveformX": "Se izolează vorbirea pentru forma de undă... {0}", + "isolatingSpeechForWaveformFailed": "Vorbirea nu a putut fi izolată pentru forma de undă - consultați jurnalul instrumentelor", + "speechOnlyWaveformReady": "Forma de undă afișează acum doar vorbirea", "showOnlyWaveform": "Afișați numai forma de undă", "showOnlySpectrogram": "Afișați numai spectrograma", "showWaveformAndSpectrogram": "Afișați forma de undă și spectrograma", - "spectrogramClassic": "Clasic", "spectrogramClassicViridis": "Viridis", "spectrogramClassicPlasma": "Plasma", "spectrogramClassicInferno": "Infern", "spectrogramClassicTurbo": "Turbo", "spectrogramNeon": "Neon", - "waveformDrawStyleClassic": "Clasic", "waveformDrawStyleFancy": "Fantezie", "setVideoPositionAndPauseAndSelectSubtitle": "Setați poziția videoclipului, întrerupeți și selectați subtitrarea", "setVideopositionAndPauseAndSelectSubtitleAndCenter": "Setați poziția videoclipului, întrerupeți, selectați subtitrarea și centrați", @@ -2888,7 +3248,6 @@ }, "sync": { "visualSync": "Sincronizare vizuală", - "sync": "Sincronizare", "startScene": "Scena de început", "endScene": "Scena finală", "playTwoSecondsAndBack": "Redați 2 secunde și înapoi", @@ -2904,24 +3263,23 @@ "adjustSelectedLinesAndForward": "Ajustați liniile selectate și înainte", "toFrameRate": "Pentru rata de cadre", "fromFrameRate": "Din rata de cadre", + "videoXFrameRateY": "{0} - {1} fps", "adjustAllTimes": "Ajustați toate orele (afișați mai devreme/mai târziu)", "showEarlier": "Arată mai devreme", "showLater": "Arată mai târziu", - "changeFrameRate": "Schimbați rata de cadre", "setSyncPoint": "Setați punctul de sincronizare", "setSyncPointViaVideo": "Setați punctul de sincronizare prin video...", "syncPointTimeCode": "Codul de timp al punctului de sincronizare", "syncPoints": "Puncte de sincronizare", "pointSync": "Sincronizare punct", "pointSyncViaOther": "Sincronizare punct prin altă subtitrare", - "adjustmentX": "Ajustare: {0}", - "syncPointCandidateInfo": "Liniile care încep după {0}+ secunde de liniște sunt evidențiate - ele fac adesea puncte de sincronizare de încredere", - "adjustAllShortcuts": "Comenzi rapide de la tastatură:\r\n\r\n", + "syncPointCandidateAfterInfo": "Liniile urmate de {0}+ secunde de liniște sunt evidențiate - adesea sunt puncte de sincronizare de încredere", + "gapAfter": "Interval după", + "adjustAllShortcuts": "Comenzi rapide de la tastatură:\n\n• Shift + Stânga/Dreapta: Mută cu 10 ms\n• Ctrl + Stânga/Dreapta: Mută cu 100 ms\n• Alt + Stânga/Dreapta: Mută cu 500 ms", "adjustAllShortcutsFrames": "Comenzi rapide de la tastatură:\r\n\r\n• Shift + Stânga/Dreapta: Mutați 1 cadru\r\n• Ctrl + Stânga/Dreapta: Mutați 10 cadre\r\n• Alt + Stânga/Dreapta: Mutați 1 secundă", "offsetInSeconds": "Offset în secunde", "speedFactor": "Factorul de viteză", - "showEarlierLaterDotDotDot": "Arată mai devreme/mai târziu...", - "totalAdjustmentX": "Ajustare totală: {0}" + "showEarlierLaterDotDotDot": "Arată mai devreme/mai târziu..." }, "translate": { "translateViaCopyPaste": "Traduceți automat prin copiere/lipire", @@ -2936,6 +3294,7 @@ "lineMerge": "Îmbinare linie", "delayInSecondsBetweenRequests": "Întârziere în secunde între solicitări", "maxBytesPerRequest": "Max bytes per solicitare", + "keepMusicLinesUntranslated": "Nu traduceți liniile între simboluri muzicale (♪ ... ♪)", "promptText": "Text prompt", "resetPromptToDefault": "Resetați promptul la valoarea implicită", "translateEachLineSeparately": "Traduceți fiecare linie separat", @@ -2949,11 +3308,10 @@ "readyToTranslate": "Gata de traducere", "translating": "Se traduce...", "translationComplete": "Traducere finalizată", + "translateInPlaceNoOriginal": "Traduceți pe loc (fără a crea original)", "translationCancelled": "Traducere anulată", "swapLanguages": "Interschimbați limbile sursă și țintă", "xIsAlreadyDownloadedReDownload": "{0} este deja descărcat. Descărcați din nou?", - "advancedDotDotDot": "Avansate...", - "advancedSettings": "Setări avansate", "advancedSettingsSubtitle": "Opțiuni de lot, context și eșantionare pentru motorul avansat llama.cpp", "context": "Context", "synopsis": "Sinopsis", @@ -2977,6 +3335,7 @@ "useOnlyExtraServerParametersHint": "Porniți llama-server cu parametrii de mai sus în locul reglajelor proprii ale Subtitle Edit - se setează în continuare doar modelul, gazda și portul", "serverRunningAtX": "Serverul rulează la {0}", "customPromptHint": "Instrucțiuni personalizate ({0} = limba sursă, {1} = limba țintă); gol = prompt încorporat", + "editTranslationHint": "Faceți clic pe traducerea rândului selectat pentru a o edita (Enter salvează, Escape anulează)", "llamaCppDownloadEngineAndModelPrompt": "llama.cpp necesită descărcarea motorului llama-server și a unui model de traducere. Descărcați acum?", "llamaCppDownloadEnginePrompt": "llama.cpp necesită descărcarea motorului llama-server. Descărcați acum?", "llamaCppDownloadModelPrompt": "llama.cpp necesită descărcarea modelului de traducere selectat. Descărcați acum?" @@ -3014,8 +3373,14 @@ "checkForUpdatesChannelStableAndBeta": "Versiuni stabile și beta", "assaAutoSetResolution": "ASSA: setați rezoluția (PlayResX/PlayResY) din videoclip atunci când se deschide un videoclip", "assaAutoSetResolutionPrompt": "ASSA: întrebați înainte de a schimba rezoluția pentru a se potrivi cu videoclipul (altfel reeșantionați automat)", + "videoControls": "Comenzi video", + "videoControlsEdit": "Editați comenzile video...", + "videoControlsPositionText": "Text poziție / durată", + "videoControlsVideoFileName": "Numele fișierului video", + "videoControlsPlayerName": "Numele playerului video", "mergeKeepEndTime": "Îmbinarea liniilor: păstrați ora de sfârșit (permiteți suprapunerea cu subtitrarea următoare)", "mergeKeepEndTimeOnlyAssa": "Îmbinarea liniilor: păstrați ora de sfârșit numai pentru „Advanced Sub Station Alpha”", + "mergeAutoBreak": "Îmbinarea liniilor: auto-break pentru textul îmbinat (dezactivat păstrează întreruperile de rând proprii liniilor)", "defaultSaveLocation": "Locația implicită de salvare", "defaultSaveLocationSourceFileFolder": "Folderul fișierului de subtitrare/video", "defaultSaveLocationLastUsedFolder": "Ultimul folder utilizat", @@ -3023,6 +3388,7 @@ "defaultSaveLocationSubtitleFileFolder": "Folderul fișierului de subtitrare", "defaultSaveLocationCustomFolder": "Folder personalizat", "subtitleGridFormattingHideTags": "Ascundeți etichetele", + "subtitleGridFormattingShowFormattingKeepTags": "Afișați formatarea, păstrați etichetele non-vizuale", "musicSymbolsToReplace": "Simboluri muzicale de înlocuit (separate prin virgulă)", "musicSymbol": "Simbol muzical", "colorWordsPerMinute": "Colorați cuvinte/min dacă e prea mare", @@ -3033,7 +3399,6 @@ "dialogStyleDashBothLinesWithSpace": "Tranzați ambele linii cu spațiu", "dialogStyleDashBothLinesWithoutSpace": "Tranzați ambele linii fără spațiu", "continuationStyle": "Stilul de continuare", - "continuationStyleNone": "Nici unul", "continuationStyleNoneTrailingDots": "Niciuna, puncte pentru pauze (numai după)", "continuationStyleNoneLeadingTrailingDots": "Niciuna, puncte pentru pauze", "continuationStyleNoneTrailingEllipsis": "Niciuna, puncte de suspensie pentru pauze (numai după)", @@ -3044,7 +3409,6 @@ "continuationStyleLeadingTrailingEllipsis": "Elipsă", "continuationStyleLeadingTrailingDash": "Dash", "continuationStyleLeadingTrailingDashDots": "Linie, dar puncte pentru pauze", - "continuationStyleCustom": "Personalizat", "cpsLineLengthStyle": "Cps/lungimea liniei", "cpsLineLengthStyleCalcAll": "Numără toate caracterele", "cpsLineLengthStyleCalcNoSpaceCpsOnly": "Numărați toate, cu excepția spațiului, numai cps", @@ -3057,26 +3421,10 @@ "cpsLineLengthStyleCalcNoSpaceOrPunctuationCpsOnly": "Fără spațiu sau semne de punctuație, numai CPS", "cpsLineLengthStyleCalcIgnoreArabicDiacritics": "Ignorați semnele diacritice arabe", "cpsLineLengthStyleCalcIgnoreArabicDiacriticsNoSpace": "Ignorați semnele diacritice arabe, fără spațiu", - "timeCodeModeHhMmSsMs": "HH:MM:SS:MS", - "timeCodeModeHhMmSsFf": "HH:MM:SS:FF", - "splitBehaviorPrevious": "Adăugați un spațiu la stânga punctului de împărțire (focalizare la dreapta)", - "splitBehaviorHalf": "Adăugați un spațiu în centrul punctului de împărțire (focalizare la stânga)", - "splitBehaviorNext": "Adăugați un spațiu la dreapta punctului de împărțire (focalizare la stânga)", - "subtitleListActionNothing": "Nimic", - "subtitleListActionVideoGoToPositionAndPause": "Accesați poziția video și întrerupeți", - "subtitleListActionVideoGoToPositionAndPlay": "Mergeți la poziția video și redați", "subtitleListActionVideoGoToPositionAndPlayCurrentAndPause": "Accesați poziția video, redați curentul și întrerupeți", - "subtitleListActionEditText": "Accesați caseta de text de editare", "subtitleListActionVideoGoToPositionMinus1SecAndPause": "Mergeți la poziția video - 1 s și faceți o pauză", "subtitleListActionVideoGoToPositionMinusHalfSecAndPause": "Mergeți la poziția video - 0,5 s și faceți o pauză", "subtitleListActionVideoGoToPositionMinus1SecAndPlay": "Mergeți la poziția video - 1 s și redați", - "subtitleListActionEditTextAndPause": "Accesați caseta de text de editare și întrerupeți la poziția videoclipului", - "autoBackupEveryMinute": "În fiecare minut", - "autoBackupEveryXthMinute": "La fiecare {0}minut", - "profiles": "Profiluri", - "autoBackupDeleteAfterXMonths": "Ștergeți copiile de rezervă automate după {0} luni", - "syntaxColoring": "Colorarea sintaxelor", - "waveformSpectrogram": "Formă de undă/spectrogramă", "network": "Reţea", "fileTypeAssociations": "Asocieri de tipuri de fișiere", "textBoxColorTags": "Etichete de culoare (HTML/ASSA) în caseta de text pentru subtitrare", @@ -3109,10 +3457,9 @@ "darkThemeForegroundColor": "Culoare în prim-plan a temei întunecate", "darkThemeBackgroundColor": "Culoare de fundal întunecată", "showGridLines": "Afișați liniile de grilă", - "resetSettings": "Resetați setările?", - "resetSettingsDetail": "Aceasta va reseta toate setările la valorile implicite.\n\n", "showHorizontalLineAboveToolbar": "Afișați linia orizontală deasupra barei de instrumente", "bookmarkColor": "Culoarea semnului de carte", + "spellCheckHighlightColor": "Culoarea de evidențiere a verificării ortografice", "singleLineMaxLength": "Lungimea maximă a unei singure linii", "optimalCharsPerSec": "Caractere optime/sec", "maxCharsPerSec": "Max caractere/sec", @@ -3124,11 +3471,19 @@ "unbreakSubtitlesShortThan": "Unbreak subtitrări mai scurte decât", "newEmptyDefaultMs": "Durata implicită a subtitrarilor noi (ms)", "timeCodeUpDownStepMs": "Increment sus/jos pentru timp (ms)", + "moveSelectedLinesStepMs": "Pasul comenzii rapide de mutare a liniilor selectate (ms)", + "moveStartEndStepMs": "Pasul comenzii rapide de mutare început/sfârșit (ms)", + "moveLinesShortenNeighbor": "Mutarea liniilor: scurtați linia anterioară/următoare în loc să o suprapuneți", "promptBeforeDelete": "Solicitați înainte de ștergere", "rememberPositionAndSize": "Amintiți-vă poziția și dimensiunea ferestrei", + "titleBarFullFileName": "Afișați calea completă a fișierului în bara de titlu", + "showRecentFiles": "Afișați fișierele recente (pentru redeschidere)", "autoBackupOn": "Backup automat", "autoBackupIntervalMinutes": "Interval de backup automat (minute)", "autoBackupDeleteAfterDays": "Reținerea backupului automat (zile)", + "settingsBackupOn": "Backup automat al setărilor", + "settingsBackupIntervalDays": "Intervalul de backup al setărilor (zile, 0 = la fiecare pornire)", + "settingsBackupMaxCount": "Copii de rezervă ale setărilor de păstrat", "autoConvertToUtf8": "Conversie automată în UTF-8 la deschidere", "autoTrimWhiteSpace": "Decuparea automată a spațiului alb", "defaultEncoding": "Codare implicită", @@ -3142,23 +3497,14 @@ "colorGapTooShort": "Colorează dacă decalajul este prea scurt", "errorBackgroundColor": "Eroare culoarea de fundal", "waveformDrawGridLines": "Desenați linii de grilă", - "waveformCenterVideoPosition": "Poziția video centrală", + "waveformUseSkiaRenderer": "Folosiți randarea rapidă experimentală", "waveformCenterVideoPositionAlsoWhenPaused": "Centrați poziția video și în timpul pauzei", + "waveformSelectCurrentSubtitleWhilePaused": "Selectați subtitrarea curentă și în timpul pauzei", + "waveformRememberZoom": "Memorați zoomul (orizontal și vertical)", "waveformShowToolbar": "Afișați bara de instrumente", "waveformShowToolbarEdit": "Editați bara de instrumente...", "waveformShowToolbarEditLabel": "Elemente ale barei de instrumente", "waveformSpectrogramCombinedWaveformHeight": "Forma de undă/spectrogramă combinată, înălțimea formei de undă %", - "showWaveformToolbarPlay": "Bara de instrumente: afișați butonul de redare", - "showWaveformToolbarRepeat": "Bara de instrumente: afișați butonul de repetare", - "showWaveformToolbarRemoveBlankLines": "Bara de instrumente: afișați butonul de eliminare a liniilor goale", - "showWaveformToolbarNew": "Bara de instrumente: afișați butonul de subtitrare nou", - "showWaveformToolbarSetStart": "Bara de instrumente: afișați butonul de pornire setați", - "showWaveformToolbarSetEnd": "Bara de instrumente: afișează butonul de terminare a setării", - "showWaveformToolbarStartAndOffsetTheRest": "Bara de instrumente: afișează start set și offset butonul de odihnă", - "showWaveformToolbarHorizontalZoom": "Bara de instrumente: afișați glisorul de zoom orizontal", - "showWaveformToolbarVerticalZoom": "Bara de instrumente: afișați glisorul de zoom vertical", - "showWaveformToolbarVideoPositionSlider": "Bara de instrumente: afișați glisorul de poziție video", - "showWaveformToolbarPlaybackSpeed": "Bara de instrumente: arată viteza de redare", "waveformFocusTextboxAfterInsertNew": "Focalizează caseta de text după inserare", "waveformInvertMouseWheel": "Întoarceți roata mouse-ului", "waveformMouseWheelSetsVideoPosition": "Rotița mouse-ului setează poziția video", @@ -3176,15 +3522,12 @@ "waveformSelectedColor": "Culoarea selectată a formei de undă", "downloadFfmpeg": "Descărcați ffmpeg", "proxyAddress": "Adresa proxy", - "username": "Nume de utilizator", "password": "Parolă", "defaultFormat": "Format implicit", "defaultSaveAsFormat": "Format implicit „Salvare ca”.", "favoriteSubtitleFormats": "Formatele de subtitrare preferate", "favoriteLanguages": "Limbi favorite", "webVttUseXTimestampMap": "WebVTT: utilizați X-TIMESTAMP-MAP (decalați codurile de timp la încărcare)", - "showStopButton": "Afișați butonul de oprire", - "showFullscreenButton": "Afișează butonul pe ecran complet", "autoOpenVideoFile": "Deschideți automat fișierul video când deschideți subtitrarea", "downloadMpv": "Descărcați mpv", "downloadVlc": "Descărcați VLC", @@ -3223,23 +3566,26 @@ "resetSyntaxColoring": "Resetați colorarea sintaxei", "resetWaveform": "Resetați forma de undă", "resetRules": "Resetați regulile", - "useFrameMode": "Utilizați modul cadru (hh.mm.ss.ff)", + "timeCodeMode": "Mod cod de timp", + "timeCodeModeTime": "Timp (hh:mm:ss,zzz)", + "timeCodeModeFrames": "Cadre (hh:mm:ss:ff)", + "timeCodeModeFrameNumbers": "Numere de cadre", + "frameModeSnapToVideoFrames": "Stocați timpii pe cadrele video (nu pe cadre în cadrul fiecărei secunde)", "textBoxLimitNewLines": "Limitați numărul de rânduri în caseta de text subtitrare", - "mpvVideoOutput": "Ieșire video (mpv)", "mpvOpenGl": "libmpv - OpenGL", "mpvSoftwareRendering": "libmpv - Redare software (lent)", + "mpvMetalRendering": "libmpv - Metal (experimental, necesită repornire)", "mpvWidRendering": "libmpv - Redare nativă a ID-ului ferestrei (mai rapidă pe Windows/Linux)", "waveFormsAndSpectrogramFoldersContainsX": "Dosarele „Forme de undă” și „spectrogramă” conțin {0}", "deleteWaveformAndSpectrogramFoldersQuestion": "Ștergeți fișierele „Forme de undă” și „Spectrogramă”?", "waveformGenerateSpectrogram": "Generați spectrogramă", "waveformAutoGenerate": "Generați automat forma de undă la deschiderea unui videoclip", "waveformSpectrogramMode": "Modul spectrogramă", - "waveformCenterOnSingleClick": "Centrați pe un singur clic", - "waveformSingleClickSelectsSubtitle": "Selectați subtitrarea cu un singur clic", "waveformRightClickSelectsSubtitle": "Selectați subtitrarea cu clic dreapta", - "waveformPauseOnSingleClick": "Întrerupeți la un singur clic", "waveformDrawStyle": "Stilul de desen al formei de undă", "vlcWidRendering": "libVLC - Redare nativă a ID-ului ferestrei", + "ffmpegSoftwareRendering": "FFmpeg", + "downloadFfmpegLibs": "Descărcați bibliotecile FFmpeg (pentru playerul video FFmpeg)", "subtitleGridEnterKeyAction": "Grilă de subtitrare Acțiunea tastei Enter", "subtitleSingleClickAction": "Acțiune cu un singur clic pentru grila de subtitrare", "subtitleDoubleClickAction": "Acțiune de dublu clic pe grila de subtitrare", @@ -3263,22 +3609,23 @@ "saveAsBehaviorUseVideoFileName": "Utilizați numele fișierului video", "saveAsBehaviorUseSubtitleFileName": "Utilizați numele fișierului de subtitrare", "saveAsAppendLanguageCodeTwoLetter": "Două litere", - "saveAsAppendLanguageCodeThreeLetter": "Trei litere", "saveAsAppendLanguageCodeLanguageName": "Numele limbii", "splitOddLineActionWeightTop": "Greutate de sus", "splitOddLineActionWeightBottom": "Greutate de jos", "splitOddLinesAction": "Acțiune de împărțire a liniilor impare", "ocrUseWordSplitList": "OCR: utilizați lista împărțită de cuvinte", "ocrGuessUnknownWords": "OCR: încercați să ghiciți cuvintele necunoscute", + "spellCheckRememberUseAlwaysList": "Verificare ortografică: memorați lista „Folosiți întotdeauna”", + "fixShortDisplayTimesAllowMoveStartTime": "Remediați erorile comune: remedierea timpilor de afișare scurți poate muta timpul de început", "multipleReplaceShowDotDotDotButtons": "Înlocuire multiplă: afișați butoanele meniului contextual", "gridFocusTextboxAfterInsertNew": "Grilă: focus caseta de text după inserarea unui nou subtitrare", + "undoRedoGoToChangedLine": "Anulare/refacere: mergeți la linia modificată (și la poziția video)", "textToSpeechPromptSkipNoiseLines": "Text în vorbire: întrebați dacă se omit liniile cu sunete/muzică", "textToSpeechPromptDetectSpeakers": "Text în vorbire: întrebați dacă se detectează numele vorbitorilor din text", "useFocusedButtonBackgroundColor": "Utilizați culoarea de fundal a butonului focalizat", "focusedButtonBackgroundColor": "Culoarea de fundal a butonului focalizat", "forceCrLfOnSave": "Forțați CR+LF la salvare (fișiere de subtitrare text)", - "showWaveformToolbarPlayNext": "Bara de instrumente: afișați butonul de redare următor", - "showWaveformToolbarPlaySelection": "Bara de instrumente: afișați butonul de selecție a redării", + "linuxClipboardUseExternalTool": "Copiați textul prin xclip/wl-copy (păstrează caracterele accentuate)", "showFormatLimitWarning": "Avertizați la salvare când liniile depășesc limitele formatului (de ex. SCC 32 caractere/linie)", "textBoxButtonShowAutoBreak": "Caseta text: afișați butonul de întrerupere automată", "textBoxButtonShowUnbreak": "Caseta de text: afișați butonul de întrerupere", @@ -3290,6 +3637,8 @@ "waveformDoubleClickAction": "Acțiune de dublu clic pe formă de undă (după acțiunea de un singur clic)", "allSettings": "Toate setările", "uiScale": "Scala UI (%)", + "fontScale": "Scara fontului (%)", + "textSelectionOpacity": "Opacitatea selecției de text (%)", "waveformToolbarItems": "Elemente din bara de instrumente Waveform", "matchIconColorToDarkTheme": "Potriviți culoarea pictogramei cu culoarea întunecată a primului plan a temei", "subtitlePreviewProperties": "Proprietăți de previzualizare a subtitrarilor", @@ -3301,22 +3650,30 @@ "fixCommonErrorsSkipStep1": "Remediați erorile comune: omiteți pasul 1 (alegeți remedierile)", "showPluginsMenu": "Afișați meniul Plugins", "fullscreenHideControls": "Ascundeți controalele video în ecran complet", + "showSecondarySubtitleDialog": "Afișați dialogul de stil la deschiderea unui al doilea fișier de subtitrare", + "rememberSecondarySubtitleFile": "Memorați al doilea fișier de subtitrare", "allowSingleLetterShortcutsInTextbox": "Permiteți comenzi rapide dintr-o singură literă în caseta de text", + "allowTextNavigationShortcutsInTextbox": "Permiteți comenzi rapide pe tastele de navigare în text (Ctrl+Stânga/Dreapta, Home/End) în caseta de text", "spellCheckEnglishTreatInApostropheAsIng": "Verificare ortografică: tratați cuvintele terminate în 'in'' ca 'ing' (doar engleză)", "writeToolsLog": "Scrieți jurnalul instrumentelor", "waveformShotChangeColor": "Culoarea schimbării fotografiei în forma de undă", + "waveformGridColor": "Culoarea grilei formei de undă", "waveformExtractAudioFormat": "Format de extragere audio", "waveformExtractAudioSampleRate": "Rata de eșantionare la extragerea audio", "waveformExtractAudioSampleRateOriginal": "Originală (păstrați sursa)", "waveformExtractAudioBitRate": "Rata de biți la extragerea audio (MP3/M4A)", + "ffmpegUseCenterChannelOnly": "Folosiți doar canalul central frontal pentru audio 5.1 (FFmpeg)", "subtitleGridCenterSelectedRow": "Grila de subtitrare, centrați la selectarea rândului anterior/următor", "waveformSetVideoPositionOnMoveStartEnd": "Setați poziția video la mutarea începutului/sfârșitului", - "saveAsAppendLanguageCodeThreeLetterBibliographic": "Trei litere (ISO 639-2/B)", "speechToTextSelectedLinesPromptFirstTimeOnly": "Vorbire în text: linii selectate, solicitați limba/motorul doar prima dată", "textToSpeechPromptMergeContinuationLines": "Text în vorbire: solicitați îmbinarea liniilor de continuare", "usePositionFromSubtitleFile": "Folosiți poziția din fișierul de subtitrare (TTML/PAC/EBU STL)", "marginIsPartOfSubtitleArea": "Marginea face parte din zona subtitrării", "textJustify": "Aliniați liniile la ambele capete", + "deinterlace": "Deîntrețesere video", + "deinterlaceOff": "Dezactivat", + "deinterlaceAuto": "Auto (doar cadrele întrețesute)", + "deinterlaceAlways": "Întotdeauna", "pixelWidthInfo": "Liniile verzi = limita de lățime maximă | Zona roșie = textul depășește limita", "spellCheckEngineHunSpelll": "Hunspell", "spellCheckEngineMsWord": "MS Word", @@ -3325,116 +3682,88 @@ "waveformColorTheme": "Temă de culori:", "waveformSaveAsCustomTheme": "Salvați ca temă personalizată...", "waveformLoadThemeDotDotDot": "Încărcați tema...", - "waveformExportThemeDotDotDot": "Exportați tema..." + "waveformExportThemeDotDotDot": "Exportați tema...", + "waveformSaveCustomThemeTitle": "Salvați tema personalizată", + "waveformThemeFileType": "Temă de formă de undă Subtitle Edit", + "waveformThemeHighContrast": "Contrast ridicat", + "waveformThemeOceanBlue": "Albastru ocean", + "waveformThemeWarmSunset": "Apus cald", + "waveformThemeForest": "Pădure", + "waveformThemeMidnightPurple": "Mov de miezul nopții", + "waveformThemeRetroAmber": "Chihlimbar retro", + "waveformThemeIce": "Gheață" }, "shortcuts": { "generalUnbreakNoSpaceCjk": "Desfaceți fără spațiu (CJK)", - "title": "Comenzi rapide", + "generalToggleBreak": "Comutați întreruperea/desfacerea rândurilor", "searchShortcuts": "Căutați comenzi rapide...", - "filter": "Filtra", - "categoryGeneral": "General", "categorySubtitleGridAndTextBox": "Vizualizarea listei de subtitrări și caseta de text", "categorySubtitleGrid": "Vizualizare listă de subtitrări", - "categoryWaveform": "Formă de undă", "categoryTextBox": "Casetă de text", "categoryFile": "Fișier", "categoryVideo": "Video", "categorySync": "Sincronizare", - "categoryTranslate": "Traducere", - "categorySearch": "Căutare", - "categoryTools": "Unelte", "categoryAi": "AI", + "categoryCustom": "Personalizat", + "editCustomShortcut": "Editați comanda rapidă personalizată", + "customShortcutTextBoxKeyHint": "Folosiți aici o tastă cu Ctrl/Alt - o tastă simplă nu ar mai scrie în caseta de text.", + "customShortcutNumberX": "Comandă rapidă personalizată #{0}", + "customShortcutNumberXY": "Comandă rapidă personalizată #{0}: {1}", + "customShortcutStep": "Pas", + "customShortcutSteps": "Pași", + "customShortcutStepRunCommand": "Rulați comanda", + "customShortcutStepInsertText": "Inserați text", + "customShortcutStepReplace": "Căutați și înlocuiți", + "customShortcutInsertAtCursor": "La cursor", + "customShortcutInsertAtStart": "La începutul textului", + "customShortcutInsertAtEnd": "La sfârșitul textului", + "customShortcutSummaryRunCommandX": "Rulați: {0}", + "customShortcutSummaryInsertXY": "Inserați „{0}” ({1})", + "customShortcutSummaryReplaceXY": "Înlocuiți „{0}” cu „{1}”", + "customShortcutSummaryReplaceRegexXY": "Înlocuiți regex „{0}” cu „{1}”", + "customShortcutPickCommand": "Selectați o comandă de rulat.", + "customShortcutEnterText": "Introduceți textul de inserat.", + "customShortcutEnterFind": "Introduceți textul de căutat.", + "customShortcutInvalidRegexX": "Expresie regulată nevalidă: {0}", + "customShortcutStepsHint": "Pașii rulează de sus în jos. Pașii de text acționează asupra liniilor selectate; comenzile care deschid o fereastră continuă după ce aceasta se închide.", "activeIn": "Activ în", "activeInEverywhere": "Peste tot", - "generalSetupLikeSe4": "Configurați ca Subtitle Edit 4 (temă, comenzi rapide, reguli de înlocuire)", - "generalMergeSelectedLines": "Îmbina liniile selectate", - "generalMergeWithPrevious": "Îmbinați cu precedentul", - "generalMergeWithNext": "Îmbinați cu următorul", - "generalMergeWithPreviousAndUnbreak": "Îmbinați cu anterior și unbreak", - "generalMergeWithNextAndUnbreak": "Îmbină cu următorul și unbreak", - "generalMergeWithPreviousAndAutoBreak": "Îmbinați cu anterioară și auto-break", - "generalMergeWithNextAndAutoBreak": "Îmbinați cu următorul și auto-break", - "generalMergeSelectedLinesAndAutoBreak": "Îmbina liniile selectate și întrerupe automat", "generalMergeSelectedLinesAndUnbreak": "Îmbinați liniile selectate și dezactivați", "generalMergeSelectedLinesAndUnbreakCjk": "Îmbinați liniile selectate și dezactivați CJK", - "generalMergeSelectedLinesOnlyFirstText": "Îmbinați liniile selectate numai primul text", "generalMergeSelectedLinesBilingual": "Îmbinați liniile selectate bilingve", - "generalMergeWithPreviousBilingual": "Îmbinați cu bilingvul anterior", - "generalMergeWithNextBilingual": "Îmbinați cu următorul bilingv", - "generalMergeOriginalAndTranslation": "Îmbina originalul și traducerea", "generalToggleTranslationMode": "Comutați modul de traducere", - "generalSwitchOriginalAndTranslation": "Comutați originalul și traducerea", - "generalSwitchOriginalAndTranslationTextBoxes": "Comutați casetele de text original și de traducere", "generalToggleTranslationAndOriginalInPreviews": "Comutați traducerea și originalul în previzualizarea video/audio", "generalChooseLayout": "Alegeți aspectul", - "generalLayoutChooseX": "Aspect {0}", - "generalPlayFirstSelected": "Redați primul selectat", - "generalGoToFirstSelectedLine": "Accesați prima linie selectată", - "generalGoToNextEmptyLine": "Treceți la următoarea linie goală", "generalGoToNextSubtitle": "Treci la subtitrarea următoare", "generalGoToNextSubtitlePlayTranslate": "Accesați subtitrarea următoare (redați traducerea)", "generalGoToNextSubtitleCursorAtEnd": "Accesați subtitrarea următoare (cursor la sfârșit)", "generalGoToPrevSubtitle": "Accesați subtitrarea anterioară", "generalGoToPrevSubtitlePlayTranslate": "Accesați subtitrarea anterioară (redați traducerea)", - "generalGoToStartOfCurrentSubtitle": "Accesați începutul subtitrarii curente", - "generalGoToEndOfCurrentSubtitle": "Mergeți la sfârșitul subtitrarii curente", - "generalGoToPreviousSubtitleAndFocusVideo": "Accesați subtitrarea anterioară și focalizați videoclipul", - "generalGoToNextSubtitleAndFocusVideo": "Accesați subtitrarea următoare și focalizați videoclipul", - "generalGoToPrevSubtitleAndPlay": "Accesați subtitrarea anterioară și jucați", - "generalGoToNextSubtitleAndPlay": "Treci la următoarea subtitrare și joacă", - "generalGoToPreviousSubtitleAndFocusWaveform": "Accesați subtitrarea anterioară și focalizați forma de undă", - "generalGoToNextSubtitleAndFocusWaveform": "Accesați subtitrarea următoare și focalizați forma de undă", - "generalGoToLineNumber": "Accesați numărul rândului", "generalGoToFirstLine": "Mergeți la prima linie", "generalGoToLastLine": "Mergeți la ultima linie", "alsoSetVideoPosition": "Setați și poziția video", "generalGoToVideoPosition": "Mergeți la poziția video", "generalToggleItalic": "Comutați italic", "generalToggleBold": "Comută bold", - "generalToggleBookmarks": "Comutați marcajele", - "generalFocusTextBox": "Focus caseta de text", - "generalToggleBookmarksWithText": "Comutați marcajele cu text", - "generalEditBookmarks": "Editați marcajele", - "fileNew": "Nou", "generalToggleUnderline": "Comutați sublinierea", + "generalToggleBox": "Comutați caseta (EBU STL)", "fileOpen": "Deschide", "fileOpenKeepVideo": "Deschide (păstrează videoclipul)", "fileSave": "Salva", "fileSaveAs": "Salvați ca", - "fileSaveAll": "Salvează totul", "fileSaveOriginal": "Salvați originalul", - "fileSaveOriginalAs": "Salvați originalul ca", - "fileOpenOriginalSubtitle": "Deschideți subtitrarea originală", - "fileCloseOriginalSubtitle": "Închideți subtitrarea originală", - "fileTranslatedSubtitle": "Subtitrare tradusă", "fileCompare": "Comparaţie", "fileStatistics": "Statistici", "fileImportPlainText": "Importă text simplu", - "fileImportBluRaySupForOcr": "Importați Blu-ray SUP pentru OCR", - "fileImportBluRaySupForEdit": "Importați Blu-ray SUP pentru editare", - "fileImportTimeCodes": "Importă coduri de timp", "fileExportEbuStl": "Export EBU STL", "fileExportPac": "Export PAC", - "fileExportEdlClipName": "Exportați numele clipului EDL", - "fileExportPlainText": "Exportați text simplu", - "fileExportCustomTextFormat1": "Exportați formatul de text personalizat 1", - "fileExportCustomTextFormat2": "Exportați formatul de text personalizat 2", - "fileExportCustomTextFormat3": "Exportați formatul de text personalizat 3", "fileExit": "Ieșire", "openSeDataFolder": "Deschideți folderul Editare subtitrări", - "editFind": "Găsi", - "editFindNext": "Găsiți următorul", "editFindPrevious": "Găsiți anterior", - "editReplace": "Înlocui", - "editMultipleReplace": "Înlocuire multiplă", - "editModifySelection": "Modificați selecția", - "listSelectAll": "Selectați toate", - "listSelectFirst": "Selectați mai întâi", - "listSelectLast": "Selectați ultimul", "listInverseSelection": "Selecția inversă", "listDeleteSelection": "Ștergeți selecția", + "deleteSelectionEverywhere": "Ștergeți selecția (oriunde)", "rippleDeleteSelection": "Ripple șterge selecția", - "settings": "Setări", "assigned": "Alocat", "unassigned": "Nealocat", "pressedKeyX": "Tasta apăsată: {0}", @@ -3442,6 +3771,7 @@ "detectKey": "Detectează cheia", "control": "Controla", "alt": "Alt", + "altGr": "AltGr", "win": "Câştiga", "shift": "Schimbare", "controlMac": "⌃", @@ -3452,11 +3782,14 @@ "resetShortcutsDetail": "Doriți să resetați toate comenzile rapide la valorile implicite?", "togglePlayPause": "Comutați redarea/pauză", "toggleLockTimeCodes": "Comutați blocarea codurilor de timp", + "toggleTimeCodeMode": "Comutați modul cod de timp (timp/cadre/numere de cadre)", + "toggleScreenPrivacy": "Comutați confidențialitatea ecranului (ascundeți numele fișierelor, apoi și textele)", + "screenPrivacyFileNamesHidden": "Confidențialitatea ecranului: nume de fișiere ascunse", + "screenPrivacyFileNamesAndTextsHidden": "Confidențialitatea ecranului: nume de fișiere și texte ascunse", + "screenPrivacyOff": "Confidențialitatea ecranului: dezactivată", "duplicateSelectedLines": "Duplicați liniile selectate", "sourceView": "Vizualizare sursă", - "showAlignmentPicker": "Aliniere", "addOrEditBookmark": "Adăugați sau editați marcajul", - "listBookmarks": "Listează marcaje", "toggleBookmark": "Comutați marcajul (linii selectate, fără text)", "toggleForcedSelectedLines": "Comutați forțat (liniile selectate)", "goToNextBookmark": "Accesați următorul marcaj", @@ -3467,12 +3800,12 @@ "waveformSetStartAndSetEndOfPreviousMinusGap": "Setați începutul și sfârșitul intervalului minus anterior", "waveformSetEndAndStartOfNextAfterGap": "Setați sfârșitul și începutul următorului interval plus", "waveformSetEndAndStartOfNextAfterGapAndGoToNext": "Setați sfârșitul și începutul următorului interval plus și treceți la următorul", - "fetchFirstWordFromNextSubtitle": "Preluați primul cuvânt din subtitrarea următoare", "moveLastWordToNextSubtitle": "Mută ​​ultimul cuvânt la subtitrarea următoare", "moveLastWordFromFirstLineDownCurrentSubtitle": "Mută ​​ultimul cuvânt de pe primul rând în jos (subtitrare curentă)", "moveFirstWordFromNextLineUpCurrentSubtitle": "Mută ​​primul cuvânt din rândul următor (subtitrare curentă)", "moveTextFromCursorToNextAndGoToNext": "Mutați textul de după cursor la subtitrarea următoare și treceți la următoarea", "moveTextFromCursorToNextAndGoToNextAndPlay": "Mutați textul de după cursor la subtitrarea următoare, treceți la următoarea și redați", + "moveTextFromCursorToNextAndGoToNextAndPlayAndPause": "Mutați textul de după poziția cursorului în subtitrarea următoare, mergeți la următoarea, redați-o și puneți pauză la sfârșitul ei", "breakAtFirstSpaceFromCursor": "Întrerupeți la primul spațiu de la poziția cursorului", "breakAtFirstSpaceFromCursorAndGoToNext": "Întrerupeți la primul spațiu de la poziția cursorului și treceți la următoarea", "toggleFocusGridAndWaveform": "Comutați focalizarea între grila de subtitrare și forma de undă/spectrogramă", @@ -3484,7 +3817,6 @@ "videoGoToPreviousTimeCode": "Mergeți la codul de timp anterior", "videoGoToNextTimeCode": "Mergeți la codul de timp următor", "goToNextLineAndSetVideoPosition": "Treceți la următoarea subtitrare și setați poziția videoclipului", - "textBoxDeleteSelectionNoClipboard": "Casetă text: ștergeți selecția (fără clipboard)", "textBoxDeleteForward": "Casetă de text: Ștergeți selecția sau caracterul următor (ștergere înainte)", "textBoxCut": "Caseta de text: Tăiați", "textBoxCut2": "Caseta de text: Tăiați (alternativă)", @@ -3503,33 +3835,27 @@ "listViewColumnTextUp": "Coloană, text în sus", "listViewColumnTextDown": "Coloană, text în jos", "autoTranslateSelectedLines": "Traduceți automat liniile selectate...", + "autoTranslateSelectedLinesNoPrompt": "Traduceți automat liniile selectate (fără confirmare, folosind ultimul motor/ultimele limbi)", "setAssaResolution": "Setați rezoluția ASSA (PlayResX/PlayResY)", "setShortcutForX": "Setați comanda rapidă pentru „{0}”", "commandFileNewKeepVideo": "Nou (păstrați videoclipul)", "fileOpenOriginal": "Deschide originalul", "fileCloseOriginal": "Închide originalul", "restoreAutoBackup": "Restaurați backupul automat", - "openContainingFolder": "Deschideți folderul care conține", "importTimeCodes": "Importă coduri de timp", "importSubtitleWithManuallyChosenEncoding": "Importă subtitrare cu codificare aleasă manual", "exportBluRaySup": "Exportați Blu-ray SUP", "exportCustomTextFormat": "Exportați formatul de text personalizat", "exportPlainText": "Exportați text simplu", - "showHistory": "Arată istoricul", "toggleRightToLeft": "Comutați de la dreapta la stânga", "modifySelection": "Modificați selecția", "adjustDurations": "Ajustați duratele", "applyDurationLimits": "Aplicați limite de durată", "batchConvert": "Conversie în lot", - "bridgeGaps": "Goluri de pod", "applyMinGap": "Aplicați intervalul minim", - "fixCommonErrors": "Remediați erorile comune", "makeEmptyTranslationFromCurrentSubtitle": "Faceți o traducere goală din subtitrarea curentă", - "mergeLinesWithSameText": "Îmbina liniile cu același text", - "mergeLinesWithSameTimeCodes": "Îmbina liniile cu aceleași coduri de timp", "splitBreakLongLines": "Împărțiți/reechilibrați linii lungi", "mergeShortLines": "Îmbinați linii scurte", - "removeTextForHearingImpaired": "Eliminați textul pentru persoanele cu deficiențe de auz", "joinSubtitles": "Alăturați-vă subtitrarilor", "splitSubtitle": "Subtitrare împărțită", "spellCheck": "Verificare ortografică", @@ -3551,7 +3877,6 @@ "adjustAllTimes": "Reglați tot timpul", "visualSync": "Sincronizare vizuală", "translateViaCopyPaste": "Traduceți prin copy-paste", - "shortcuts": "Comenzi rapide", "wordLists": "Liste de cuvinte", "chooseUiLanguage": "Alegeți limba UI", "chooseRuleProfile": "Alegeți profilul de regulă", @@ -3562,6 +3887,7 @@ "textBoxItalic": "Caseta de text italic", "textBoxBold": "Casetă de text aldin", "textBoxUnderline": "Casetă de text subliniat", + "textBoxBox": "Caseta de text - casetă (EBU STL)", "resetWaveformZoomAndSpeed": "Resetați zoom-ul formei de undă și viteza de redare (rata de redare)", "togglePlaybackSpeed": "Comutați viteza de redare (rata de redare)", "playbackSpeedSlower": "Viteza de redare mai mică (rata de redare)", @@ -3571,22 +3897,28 @@ "seekSilence": "Caută tăcerea", "waveformGuessStart": "Ghiciți timpul de început din forma de undă", "waveformGuessEnd": "Ghiciți timpul de sfârșit din forma de undă", + "waveformGuessStartAndEnd": "Ghiciți timpii de început și de sfârșit din forma de undă", "setVideoPositionCurrentSubtitleStart": "Setați poziția video la începutul liniei curente", "goToSubtitlePositionAndPause": "Mergeți la poziția subtitrării și puneți pauză", "setVideoPositionCurrentSubtitleEnd": "Setați poziția video la capătul curent al liniei", "toggleAudioTracks": "Comutați melodiile audio", - "listErrors": "Enumeră erorile", "goToNextError": "GoTo următoarea eroare", "goToPreviousError": "Eroare anterioară GoTo", "addNameToNameList": "Adăugați nume la lista de nume", "findDoubleWords": "Găsiți cuvinte duble", "findDoubleLines": "Găsiți linii duble", "colorX": "Culoare {0}", - "removeColor": "Îndepărtați culoarea", "surroundWith": "Înconjurați cu...", "surroundWithXY": "Înconjurați cu {0}/{1}", - "repeatLine": "Repetați linia", "surroundWithNumberX": "Încadrați cu #{0}", + "surroundWithBehavior": "Comportament", + "surroundWithBehaviorToggle": "Comutare (adăugați sau eliminați dacă există)", + "surroundWithBehaviorAdd": "Adăugare (de fiecare dată)", + "surroundWithBehaviorRemove": "Eliminare (toate)", + "surroundWithBehaviorRemoveOnce": "Eliminare (câte una de fiecare dată)", + "surroundWithWorksOn": "Acționează asupra", + "surroundWithScopeSelectionOrText": "Selecției, altfel întregului text", + "surroundWithScopeEachLine": "Fiecărei linii", "searchVia": "Căutați prin", "searchViaX": "Căutați prin {0}", "searchViaNumberX": "Căutați prin #{0}", @@ -3611,6 +3943,11 @@ "copySubtitlePathToClipboard": "Copiați calea subtitrarilor în clipboard", "copySubtitleOriginalPathToClipboard": "Copiați calea de subtitrare a originalului în clipboard", "focusTextBox": "Focus caseta de text", + "focusSubtitleListView": "Focalizați lista de subtitrări", + "focusWaveform": "Focalizați forma de undă", + "focusOriginalTextBox": "Focalizați caseta de text originală", + "textBoxGoToStart": "Caseta de text, mergeți la început", + "textBoxGoToEnd": "Caseta de text, mergeți la sfârșit", "sortByStartTime": "Sortați după ora „Afișați”.", "sortByEndTime": "Sortați după ora „Ascunde”.", "sortByDuration": "Sortați după durată", @@ -3633,7 +3970,6 @@ "assaStyles": "Stiluri ASSA", "assaProperties": "Proprietăți ASSA", "assaAttachments": "Atasamente ASSA", - "assaVideoColorPicker": "Selector de culori ASSA Video", "recalculateDurationSelectedLines": "Recalculați durata (linii selectate)", "toggleWaveformAndSpectrogramHeight": "Comutați forma de undă/înălțimea împărțită a spectrogramei", "toggleSpectrogramStyle": "Comutați stilul spectrogramei", @@ -3658,9 +3994,9 @@ "selectionToSentenceCase": "Casetă de text: Selecția în Scriere de propoziție", "selectionToggleCasing": "Casetă de text: Comutați scrierea (MAJUSCULE -> minuscule -> Fiecare Cuvânt Cu Majusculă)", "googleIt": "Căutați pe Google (textul selectat)", - "setActorXY": "Setați actorul {0}: {1}", + "setActorX": "Setați actorul: {0}", "setNewActor": "Setați un nou actor...", - "setActorSlotXTitle": "Slot actor {0}", + "setStyleX": "Setați stilul: {0}", "toggleDialogDashes": "Comutați liniuțele de dialog", "mergeWithLineBeforeAsDialog": "Îmbinați cu linia dinainte ca dialog", "mergeWithLineAfterAsDialog": "Îmbinați cu linia de după ca dialog", @@ -3671,13 +4007,27 @@ "moveStartOneFrameBackKeepGapPrev": "Mutați începutul cu un cadru înapoi (păstrați decalajul față de precedentul dacă este aproape)", "moveStartOneFrameForwardKeepGapPrev": "Mutați începutul cu un cadru înainte (păstrați decalajul față de precedentul dacă este aproape)", "moveEndOneFrameBackKeepGapNext": "Mutați sfârșitul cu un cadru înapoi (păstrați decalajul față de următorul dacă este aproape)", - "moveEndOneFrameForwardKeepGapNext": "Mutați sfârșitul cu un cadru înainte (păstrați decalajul față de următorul dacă este aproape)" + "moveEndOneFrameForwardKeepGapNext": "Mutați sfârșitul cu un cadru înainte (păstrați decalajul față de următorul dacă este aproape)", + "moveStartXMsBack": "Mutați începutul cu X ms înapoi (X setat în Setări, și în Sincronizare vizuală)", + "moveStartXMsForward": "Mutați începutul cu X ms înainte (X setat în Setări, și în Sincronizare vizuală)", + "moveEndXMsBack": "Mutați sfârșitul cu X ms înapoi (X setat în Setări, și în Sincronizare vizuală)", + "moveEndXMsForward": "Mutați sfârșitul cu X ms înainte (X setat în Setări, și în Sincronizare vizuală)", + "moveSelectedLinesXMsBack": "Mutați liniile selectate cu X ms înapoi (X setat în Setări)", + "moveSelectedLinesXMsForward": "Mutați liniile selectate cu X ms înainte (X setat în Setări)", + "moveSelectedLinesAndForwardXMsBack": "Mutați liniile selectate și toate cele următoare cu X ms înapoi (X setat în Setări)", + "moveSelectedLinesAndForwardXMsForward": "Mutați liniile selectate și toate cele următoare cu X ms înainte (X setat în Setări)", + "moveAllLinesXMsBack": "Mutați toate liniile cu X ms înapoi (X setat în Setări)", + "moveAllLinesXMsForward": "Mutați toate liniile cu X ms înainte (X setat în Setări)", + "moveSelectedLinesCustomMsBack": "Mutați liniile selectate, milisecunde personalizate ({0:#,###,##0}) înapoi, {1}", + "moveSelectedLinesCustomMsForward": "Mutați liniile selectate, milisecunde personalizate ({0:#,###,##0}) înainte, {1}", + "moveSelectedLinesAndForwardCustomMsBack": "Mutați liniile selectate și toate cele următoare, milisecunde personalizate ({0:#,###,##0}) înapoi, {1}", + "moveSelectedLinesAndForwardCustomMsForward": "Mutați liniile selectate și toate cele următoare, milisecunde personalizate ({0:#,###,##0}) înainte, {1}", + "moveAllLinesCustomMsBack": "Mutați toate liniile, milisecunde personalizate ({0:#,###,##0}) înapoi, {1}", + "moveAllLinesCustomMsForward": "Mutați toate liniile, milisecunde personalizate ({0:#,###,##0}) înainte, {1}" }, "wordLists": { "title": "Liste de cuvinte", "addName": "Adăugați numele", - "addWord": "Adaugă cuvânt", - "addPair": "Adăugați pereche", "nameAndIgnoreList": "Listă de nume/ignore", "userWords": "Lista de cuvinte de utilizator", "ocrFixList": "Lista de corecții OCR", @@ -3730,6 +4080,8 @@ "nOcrDatabase": "baza de date nOCR", "drawMode": "Mod desen:", "addNewCharcter": "Adăugați un caracter nou", + "addNOcrCharacterTitle": "Adăugați caracter nOCR pentru linia {0}, caracterul {1} din {2} folosind baza de date „{3}”", + "addBinaryOcrCharacterTitle": "Adăugați caracter OCR binar pentru linia {0}, caracterul {1} din {2} folosind baza de date „{3}”", "lineIndexX": "Linia {0}", "inspectNOcrAdditions": "Inspectați noile adăugări nOCR", "ocrSelectedLines": "OCR linii selectate", @@ -3752,7 +4104,7 @@ "changeWordFromTo": "Schimbați cuvântul de la/la", "clearBackground": "Fundal clar", "clearForeground": "Prim-plan clar", - "nOcrDrawHelp": "Sfaturi pentru desen\r\n", + "nOcrDrawHelp": "Sfaturi pentru desen\n────────────────────\n• Țineți apăsat Ctrl pentru a continua linia\n• Ctrl+z=anulare, Ctrl+y=refacere", "editWholeText": "Editați textul întreg", "editWordOnly": "Editați numai cuvântul", "imagePreProcessing": "Preprocesarea imaginii", @@ -3778,6 +4130,8 @@ "fillSelectedLinesWithClipboard": "Completați liniile selectate cu textul din clipboard", "saveBlankTextTitle": "Salvați text gol?", "saveBlankTextPrompt": "Salvați text gol pentru imagine?", + "drawnLinesDoNotMatchTitle": "Liniile nu corespund imaginii", + "drawnLinesDoNotMatchPrompt": "Unele dintre liniile desenate nu corespund imaginii (liniile verzi trebuie să fie pe caracter, cele roșii în afara lui), așa că acest caracter nu va fi recunoscut.\n\nSalvați totuși?", "yesAndNeverAskAgain": "Da și nu mai întreba niciodată", "importTextFromSubtitleDotDotDot": "Importați text din subtitrare...", "importTextFromSubtitleOverwritePrompt": "Unele linii au deja text OCR. Suprascrieți textul existent?", @@ -3803,7 +4157,6 @@ "vobSubColorsTitle": "Culori VobSub/DVD", "vobSubColorsHeader": "Personalizați cele patru culori VobSub", "vobSubColorsDescription": "Subtitrările VobSub/DVD folosesc patru culori indexate: fundal, model, accent 1 și accent 2. Alegeți fiecare culoare de mai jos pentru a suprascrie paleta încorporată - util când culorile originale dau un contrast OCR slab.", - "vobSubColorBackground": "Fundal", "vobSubColorPattern": "Model", "vobSubColorEmphasis1": "Accent 1", "vobSubColorEmphasis2": "Accent 2", @@ -3837,9 +4190,26 @@ "crispEmbedSettingsTitle": "Setări CrispEmbed", "crispEmbedDescription": "Motor OCR local cu mai multe modele de bază - descărcați aici motorul și modelele.", "trainNOcrDatabase": "Antrenați baza de date nOCR...", + "optimizeNOcrDatabase": "Optimizați baza de date nOCR...", + "optimizeNOcrDatabaseQuestionXY": "Eliminați liniile care nu deosebesc caracterele de caracterele similare din baza de date nOCR „{0}”?\n\nAstfel baza de date devine mai mică și OCR-ul mai rapid, de obicei cu mai puține erori. Durează până la un minut.\n\nO copie de rezervă este salvată ca „{1}”.", + "optimizingNOcrDatabaseXY": "Se optimizează baza de date nOCR... {0}/{1}", + "nOcrDatabaseOptimizedXYZ": "Baza de date nOCR „{0}” optimizată: {1:#,##0} -> {2:#,##0} linii.", "startTraining": "Începeți antrenarea", "abortTraining": "Întrerupeți antrenarea", "trainingOptions": "Opțiuni de antrenare", + "trainNOcrSubtitle": "Randează caractere cu fonturile instalate și învață nOCR formele lor. Alegeți fonturile folosite de subtitrările dvs.", + "xFontsSelected": "{0:#,##0} selectate", + "subtitleFonts": "Fonturi de subtitrare", + "emptyDatabase": "Bază de date goală", + "startFromDatabase": "Porniți de la", + "startFromDatabaseHint": "Adăugați caracterele antrenate într-o copie a unei baze de date existente (recomandat) sau porniți de la una goală", + "fontSizes": "Dimensiuni de font", + "fontSizesHint": "Dimensiunile fontului în pixeli pentru randare, separate prin virgulă, de ex. „30, 40”", + "numberOfLineSegmentsHint": "Linii verificate per caracter - mai multe înseamnă mai strict, dar mai lent", + "enterDatabaseName": "Introduceți un nume pentru baza de date", + "trainingFontXSizeYCharacterZ": "Se antrenează „{0}” la {1} px: {2}", + "xLearned": "{0:#,##0} învățate", + "xSkipped": "{0:#,##0} omise", "charactersToTrain": "Caractere de antrenat", "letterCombinationsToTrain": "Combinații de litere care ar putea fi împărțite într-o singură imagine", "importCharactersFromSubtitleFile": "Importați caractere dintr-un fișier de subtitrare...", @@ -3866,6 +4236,8 @@ "drawZoomOut": "Micșorați (Ctrl+-)", "drawResetView": "Resetați vizualizarea (Ctrl+0)", "drawToggleGrid": "Comutați grila (Ctrl+G)", + "drawTogglePreview": "Comutați previzualizarea (F9)", + "drawPreviewFailed": "Previzualizarea a eșuat - este necesar ffmpeg cu libass (filtrul subtitles)", "drawCopyToClipboard": "Copiați în Clipboard (Ctrl+C)", "drawShapes": "Forme", "drawSelectedPoint": "Punctul selectat", @@ -3873,16 +4245,86 @@ "drawSelectedLayer": "Stratul selectat", "drawToolX": "Instrument: {0}", "drawHelpText": "Faceți clic pentru a adăuga puncte • Enter/F8 pentru a închide forma • Shift+Tras pentru a deplasa • Ctrl+Scroll pentru a mări", + "drawMoveToLayer": "Mutați pe stratul", + "drawFlipHorizontal": "Răsturnați orizontal", + "drawFlipVertical": "Răsturnați vertical", + "drawHideShape": "Ascundeți forma", + "drawShowShape": "Afișați forma", + "drawHideLayer": "Ascundeți stratul", + "drawShowLayer": "Afișați stratul", + "drawDeleteLayer": "Ștergeți stratul", + "drawDeletePoint": "Ștergeți punctul", + "drawCancelDrawing": "Anulați desenarea", + "drawLayerX": "Stratul {0}", + "drawImportSvg": "Importați imagine SVG...", + "drawRotateClockwise": "Rotiți 90° în sensul acelor de ceasornic", + "drawConvertToCurve": "Convertiți segmentul în curbă", + "drawBackground": "Fundal", + "drawPosition": "Poziţie", + "drawZoom": "Zoom", + "drawShapeTool": "Instrument formă", + "drawColorPickerTool": "Alegeți culoarea dintr-o formă sau din fundal", + "drawShapeLibrary": "Bibliotecă de forme", + "drawShapeLibraryHint": "Trageți pe pânză pentru a plasa o formă sau faceți clic pentru dimensiunea implicită. Shift păstrează proporțiile.", + "drawAddToLibrary": "Adăugați în biblioteca de forme...", + "drawRemoveFromLibrary": "Eliminați din biblioteca de forme", + "drawShapeName": "Numele formei", + "drawCategorySpeechBubbles": "Baloane de dialog", + "drawCategoryArrows": "Săgeți", + "drawCategoryBasicShapes": "Forme de bază", + "drawCategorySymbols": "Simboluri", + "drawCategoryMyShapes": "Formele mele", + "drawShapeSpeechBubble": "Balon de dialog", + "drawShapeSpeechBubbleRight": "Balon de dialog, coadă la dreapta", + "drawShapeSpeechBubbleUp": "Balon de dialog, coadă în sus", + "drawShapeOvalBubble": "Balon de dialog oval", + "drawShapeBoxBubble": "Balon de dialog dreptunghiular", + "drawShapeThoughtBubble": "Balon de gânduri", + "drawShapeShoutBubble": "Balon de strigăt", + "drawShapeCaptionBox": "Casetă de text", + "drawShapeArrow": "Săgeată", + "drawShapeDoubleArrow": "Săgeată dublă", + "drawShapeChevron": "Chevron", + "drawShapeCurvedArrow": "Săgeată curbată", + "drawShapeRoundedRectangle": "Dreptunghi rotunjit", + "drawShapeTriangle": "Triunghi", + "drawShapeDiamond": "Romb", + "drawShapePentagon": "Pentagon", + "drawShapeHexagon": "Hexagon", + "drawShapeOctagon": "Octogon", + "drawShapeStar": "Stea", + "drawShapeRing": "Inel", + "drawShapePlus": "Plus", + "drawShapeBanner": "Banner", + "drawShapeHeart": "Inimă", + "drawShapeCheck": "Bifă", + "drawShapeMusicNote": "Notă muzicală", + "drawShapeMusicNotes": "Note muzicale", + "drawShapeLightning": "Fulger", + "drawShapeCloud": "Nor", + "drawShapeMoon": "Lună", + "drawShapeBadge": "Insignă", + "drawSize": "Dimensiune", + "drawLayer": "Strat", + "drawBackgroundVideoFrame": "Cadru video (poziția curentă)", + "drawBackgroundVideoFrameAt": "Cadru video la...", + "drawBackgroundImage": "Fișier imagine...", + "drawBackgroundNone": "Fără fundal", + "drawBackgroundStretch": "Întindeți imaginea pe pânză", + "drawBackgroundOpacity": "Opacitatea fundalului", + "drawBackgroundVideoFrameFailed": "Nu s-a putut obține un cadru video la {0}.", + "drawImages": "Imagini", + "drawConvertToLine": "Convertiți segmentul în linie", + "drawConvertShapeToCurves": "Convertiți toate liniile în curbe", + "drawConvertShapeToLines": "Convertiți toate curbele în linii", + "drawRotateCounterClockwise": "Rotiți 90° în sens invers acelor de ceasornic", + "drawSvgImages": "Imagini SVG", + "drawSvgNoShapes": "Nu s-au găsit forme umplute sau contururi în fișierul SVG.", "progressBarTitle": "bara de progres ASSA", "progressBarSettings": "Bara de progres", - "progressBarPosition": "Poziţie", - "progressBarBottom": "Fund", - "progressBarTop": "Top", "mouseOverColor": "Culoare la trecerea mouse-ului", "clickedColor": "Culoare la clic", "progressBarForeColor": "Culoarea primului plan", - "progressBarBackColor": "Culoare de fundal", - "progressBarStyle": "Stil", "progressBarSquareCorners": "Colțuri pătrate", "progressBarRoundedCorners": "Colțuri rotunjite", "progressBarChapters": "Capitole", @@ -3891,8 +4333,6 @@ "progressBarXAdjustment": "Ajustare X", "progressBarYAdjustment": "Ajustare Y", "progressBarTextAlignment": "Alinierea textului", - "progressBarTakePosFromVideo": "Luați poziția din videoclip", - "progressBarPreview": "Previzualizare", "fontCollectorTitle": "Colector de fonturi", "fontCollectorUsedIn": "Folosit în", "fontCollectorFontFiles": "Fișier(e) de font", @@ -3939,9 +4379,7 @@ "resolutionResamplerVideoDiffers": "Rezoluția subtitrării ({0}x{1}) diferă de rezoluția videoclipului ({2}x{3}).\nModificați subtitrarea pentru a se potrivi cu videoclipul?", "resolutionResamplerAskOnVideoOpen": "Întrebați când se deschide un videoclip cu o rezoluție diferită", "backgroundBoxGenerator": "Generați caseta de fundal", - "backgroundBoxPadding": "Captuseala", "backgroundBoxFillWidth": "Lățimea umplerii", - "backgroundBoxBoxColor": "Culoarea casetei", "backgroundBoxRadius": "Rază", "backgroundBoxCircle": "Cerc", "backgroundBoxSpikes": "Spikes", @@ -3953,12 +4391,9 @@ "backgroundBoxTornPaperDouble": "Hârtie ruptă (sus și jos)", "backgroundBoxStarburst": "Starburst", "backgroundBoxScroll": "Pergament / Pergament", - "stylesTitle": "Stiluri avansate de substație Alpha", "stylesInFile": "Stiluri în fișier", "stylesSaved": "Stiluri salvate", - "category": "Categorie", "allCategories": "[Toate]", - "defaultCategory": "Implicit", "newCategory": "Categorie nouă...", "renameCategory": "Redenumiți categoria...", "deleteCategory": "Ștergeți categoria", @@ -3978,13 +4413,26 @@ "graphics": "Grafică", "copyToStorageStyles": "Copiați în stiluri de stocare", "copyToFileStyles": "Copiați în stiluri de fișiere", + "styleXAlreadyExistsInStorage": "Un stil numit „{0}” există deja în depozit.\n\nÎl suprascrieți sau le păstrați pe amândouă (copia primește un nume nou)?", + "styleXAlreadyExistsInFile": "Un stil numit „{0}” există deja în fișier.\n\nÎl suprascrieți sau le păstrați pe amândouă (copia primește un nume nou)?", + "styleNameCannotBeEmpty": "Numele stilului nu poate fi gol.", + "styleNameXIsUsedMoreThanOnce": "Numele de stil „{0}” este folosit de mai multe stiluri din fișier.", + "overwrite": "Suprascrieți", + "keepBoth": "Păstrați-le pe amândouă", + "doThisForAllConflictingStyles": "Faceți la fel pentru toate stilurile în conflict", "setStyleAsDefault": "Setați stilul ca implicit", "takeUsagesFromDotDotDot": "Luați utilizări de la...", "replaceStyleWithDotDotDot": "Înlocuiți stilul cu...", "noAttachmentsFound": "Nu s-au găsit atașamente în fișierul ASSA selectat.", "deleteStyleQuestion": "Ștergeți stilul?", "deleteStylesQuestion": "Ștergeți stilurile?", + "deleteXStylesQuestion": "Doriți să ștergeți {0} stiluri?", + "deleteXStylesFromStorageQuestion": "Doriți să ștergeți {0} stiluri din depozit?", + "deleteStyleXFromStorageQuestion": "Doriți să ștergeți stilul „{0}” din depozit?", + "deleteStyleXFromFileQuestion": "Doriți să ștergeți stilul „{0}” din fișierul curent?", "openStyleImportFile": "Deschideți fișierul de subtitrare din care să importați stiluri", + "styleImportFiles": "Fișiere de stil (.ass, .sty, categorii Subtitle Edit 4 .template)", + "ssaStyleImportFiles": "Fișiere de stil (.ssa, .ass, .sty)", "primary": "Primar", "secondary": "Secundar", "applyOverrideTags": "Aplicați etichete de înlocuire", @@ -3999,7 +4447,6 @@ "spaceIncrease": "Creșterea spațiului", "popIn": "Intră", "spinIn": "Învârtiți-vă", - "playCurrent": "Redați curent", "setPosition": "Setați poziția", "rotation": "Rotație", "scaleX": "Scalare X", @@ -4043,10 +4490,8 @@ "advancedEffectOldMovieDescription": "Simulează un proiector de film vintage de 35 mm cu zgomot, vignetă și zgârieturi", "advancedEffectNeonBurst": "Neon izbucni", "advancedEffectNeonBurstDescription": "Subtitrări în stil „Short” pline de energie, cu strălucire de neon și animație „pop” extinsă", - "advancedEffectSettings": "Setări", "advancedEffectSnowFlakeCount": "Număr de fulgi", "advancedEffectStarfieldStarCount": "Număr de stele", - "advancedEffectStarfieldSpeed": "Viteză", "advancedEffectGlitch": "Glitch", "advancedEffectGlitchDescription": "Defecțiune digitală cu aberații cromatice, fulgerări albe și suprapuneri de distorsiuni", "advancedEffectBounceIn": "Sări înăuntru", @@ -4055,8 +4500,6 @@ "advancedEffectFirefliesDescription": "Puncte calde strălucitoare galben-chihlimbar care plutesc organic pe ecran cu strălucire pulsatorie", "advancedEffectMatrix": "Matrice", "advancedEffectMatrixDescription": "Coloane de caractere verzi care se încadrează cu text subtitrat, construind literă cu literă din ploaie", - "advancedEffectAudioBars": "Bare audio (forma de undă)", - "advancedEffectAudioBarsDescription": "Bare de egalizare animate conduse de forma de undă audio — barele defilează spre stânga ca formă de undă și pulsează cu sunetul", "advancedEffectAudioPulse": "Puls de text audio", "advancedEffectAudioPulseDescription": "Textul subtitrării strălucește și bate cu volumul audio - secțiunile silențioase arată text normal, vârfurile puternice adaugă un halou colorat și o ușoară mărire", "advancedEffectConfetti": "Confetti au izbucnit", @@ -4070,7 +4513,6 @@ "advancedEffectFancyKaraokeGlow": "Cuvânt activ strălucire", "advancedEffectFancyKaraokeActiveColor": "Culoare activă", "advancedEffectFancyKaraokeInactiveColor": "Culoare inactivă", - "advancedEffectFancyKaraokeInactiveOpacity": "Opacitate inactivă (0-255)", "advancedEffectWordSpacingPixels": "Spațiere (pixeli)", "advancedEffectSlowZoomOut": "Mişcare lentă", "advancedEffectSlowZoomOutDescription": "Textul începe puțin mai mare și se micșorează ușor la dimensiunea normală pe durata subtitrarilor", @@ -4081,12 +4523,11 @@ "advancedEffectFadeOut": "Tranziție - fade-out", "advancedEffectFadeOutDescription": "Un efect de dispariție pe linie în care ecranul se termină negru.", "advancedEffectTvClose": "Tranziție - TV aproape", - "advancedEffectTvCloseDescription": "Barele negre cresc spre interior de sus și de jos, în timp ce mijlocul devine alb (sus negru / mijloc alb / jos negru). ", + "advancedEffectTvCloseDescription": "Barele negre cresc spre interior de sus și de jos, în timp ce mijlocul devine alb (sus negru / mijloc alb / jos negru).", "advancedEffectSlideInLeft": "Glisați de la stânga", "advancedEffectSlideInLeftDescription": "Textul glisează din stânga în afara ecranului, se menține, apoi iese înapoi la stânga", "advancedEffectSlideInRight": "Glisați din dreapta", "advancedEffectSlideInRightDescription": "Textul alunecă din afara ecranului la dreapta, ține, apoi iese înapoi la dreapta", - "advancedEffectFadeInOut": "Fade in/out", "advancedEffectFadeInOutDescription": "Textul apare la început și dispare la sfârșitul fiecărui subtitrare", "advancedEffectWordFlip3D": "Rotire 3D a cuvintelor", "advancedEffectWordFlip3DDescription": "Fiecare cuvânt apare cu o rotire 3D și un salt elastic pe măsură ce este rostit — stil modern de subtitrare „Shorts”", @@ -4118,7 +4559,6 @@ "noPluginsInstalled": "Niciun plugin instalat", "getPluginsOnline": "Obțineți pluginuri online...", "getPluginsTitle": "Obțineți pluginuri", - "enabled": "Activat", "disabled": "Dezactivat", "notSupportedOnThisOs": "Neacceptat pe acest sistem de operare", "removePluginXConfirm": "Eliminați pluginul „{0}”? Aceasta șterge folderul pluginului:{1}", @@ -4135,17 +4575,19 @@ "downloadingXPercent": "Se descarcă {0}%...", "pluginXInstalled": "„{0}” instalat.", "couldNotInstallPluginX": "Nu s-a putut instala „{0}”: {1}", - "notInstalled": "Neinstalat", "install": "Instalați", "reinstall": "Reinstalați", - "update": "Actualizați", "installedX": "Instalat ({0})", "updateAvailableXToY": "Actualizare disponibilă ({0} → {1})", "applyPluginToWhichLinesX": "Aplicați „{0}” la:", "applyToSelectedLinesX": "Linii selectate ({0})", "applyToAllLinesX": "Toate liniile ({0})", "updateAllXAvailable": "Actualizați toate ({0})", - "updatingXOfY": "Se actualizează {0}/{1}..." + "updatingXOfY": "Se actualizează {0}/{1}...", + "doNotAskAgain": "Nu mai întrebați (modificați în Gestionați pluginurile)", + "applyPluginsTo": "Aplicați pluginurile pe", + "askEachTime": "Întrebați de fiecare dată", + "allLines": "Toate liniile" }, "errorList": { "title": "Listați erorile", diff --git a/src/ui/Assets/Languages/Russian.json b/src/ui/Assets/Languages/Russian.json index 0e45c1e4288..6943c5c066d 100644 --- a/src/ui/Assets/Languages/Russian.json +++ b/src/ui/Assets/Languages/Russian.json @@ -1,6 +1,6 @@ { "title": "Subtitle Edit", - "version": "v5.2.0-rc7", + "version": "v5.3.0", "translatedBy": "", "cultureName": "ru-RU", "general": { @@ -595,6 +595,8 @@ "showStartColumn": "Показать столбец \"Начало\"", "showHideColumn": "Показать столбец \"Конец\"", "showHistory": "Показать историю", + "clearHistory": "Очистить историю", + "removeFromHistory": "Удалить из истории", "showLayerColumn": "Показать столбец \"Слой\"", "showPreview": "Показать предварительный просмотр", "showShotChangesList": "Показать список смен планов", @@ -821,7 +823,6 @@ "imageFiles": "Файлы изображений", "openAiCompatibleSttEndpoint": "URL конечной точки STT", "openAiCompatibleSttApiKey": "Ключ API", - "openAiCompatibleSttModel": "Модель", "openAiCompatibleSttExtraHeaders": "Дополнительные заголовки", "openAiCompatibleSttTimeout": "Тайм-аут (секунды)", "openAiCompatibleSttLanguage": "Подсказка по языку", @@ -891,6 +892,7 @@ "multipleReplace": "_Множественная замена...", "fillSelectedLinesWithClipboard": "Заполнить выделенные строки текстом из буфера обмена", "goToLineNumber": "_Перейти к строке...", + "commandPalette": "_Палитра команд...", "rightToLeftMode": "Р_ежим справа налево", "modifySelectionDotDotDot": "Изменить _выделение...", "tools": "_Инструменты", @@ -1083,6 +1085,9 @@ "charactersPerSecond": "Символов/секунду: {0}", "chooseColumn": "Выбрать столбец", "columnPaste": "Вставка в столбец", + "commandPaletteTitle": "Палитра команд", + "commandPaletteSearchPlaceholder": "Начните вводить для поиска команд...", + "commandPaletteNoCommandsFound": "Подходящих команд нет", "createdEmptyTranslation": "Создан пустой перевод на основе текущих субтитров", "deleteText": "Удалить текст", "deleteTextAndShiftCellsUp": "Удалить текст и сдвинуть ячейки вверх", @@ -1301,6 +1306,21 @@ "formattingDotDotDot": "Форматирование...", "imageBasedSubtitleForEditDotDotDot": "Субтитры-изображения для редактирования...", "imageBasedSubtitleForOcrDotDotDot": "Субтитры-изображения для OCR...", + "dvdSubtitlesDotDotDot": "Субтитры с DVD (IFO/VOB)...", + "titleImportDvd": "Импорт субтитров с DVD", + "dvdIfoFile": "Файл IFO", + "dvdVobFiles": "Файлы VOB", + "dvdVideoStandard": "Стандарт видео", + "dvdStartRipping": "Начать извлечение", + "dvdRippingAborted": "Извлечение прервано", + "dvdOpenIfoFile": "Открыть файл DVD IFO", + "dvdAddVobFiles": "Добавить файлы VOB", + "dvdIfoFiles": "Файлы DVD IFO", + "dvdVobFileType": "Файлы DVD VOB", + "dvdDropHint": "Откройте файл IFO (VIDEO_TS\\VTS_01_0.IFO) или добавьте файлы VOB - либо перетащите их сюда", + "dvdNotAnIfoFileX": "{0} не является файлом DVD IFO", + "dvdMenuIfoNoTitleSetsX": "Рядом с {0} не найдены наборы разделов (VTS_xx_0.IFO)", + "dvdEncryptedXOfY": "{0} из {1} пакетов субтитров все еще зашифрованы CSS - эти файлы VOB были скопированы без расшифровки, поэтому некоторые изображения субтитров будут искажены.\n\nДля чистого результата сначала расшифруйте DVD программой для копирования DVD.\n\nВсе равно продолжить?", "splitTextAt": "Разделять текст по", "blankLines": "Пустые строки", "oneLineIsOneSubtitle": "Одна строка — один субтитр", @@ -1320,8 +1340,13 @@ "forcedAlignerProgress": "Выравнивание... окно {0} из {1}, {2} из {3} строк", "forcedAlignerSetupTitle": "Принудительное выравнивание временных кодов", "forcedAlignerSetupIntro": "Функция принудительного выравнивания сопоставляет уже имеющийся текст субтитров с аудиодорожкой, не выполняя предварительную транскрипцию. Длинные видео выравниваются по фрагментам, поэтому подходит любая длина.", + "forcedAlignerEndsFromSpeech": "Задать время окончания по выделенной речи (медленно)", + "forcedAlignerEndsFromSpeechHint": "Принудительное выравнивание находит, где строка начинается, но не где она заканчивается, поэтому время окончания обычно рассчитывается по времени чтения. Если включено, из аудио сначала удаляются музыка и звуковые эффекты, и каждая строка заканчивается там, где затихает речь. С GPU занимает примерно столько же времени, сколько длится видео, - а без него во много раз дольше.", + "forcedAlignerIsolatingSpeech": "Выделение речи... {0}", + "forcedAlignerIsolatingSpeechFailed": "Не удалось выделить речь - время окончания задано по времени чтения.", "forcedAlignerModel": "Модель выравнивания", - "forcedAlignerDownloadEngine": "Скачать/обновить движок..." + "forcedAlignerDownloadEngine": "Скачать/обновить движок...", + "forcedAlignerEstimatedXOfY": "{0} из {1} строк не удалось выровнять, и они размещены по времени чтения - их тайм-коды лишь приблизительны. Вероятно, сценарий длиннее того, что произносится в видео." }, "export": { "exportImagesProfiles": "Экспорт профилей изображений", @@ -1370,6 +1395,18 @@ "imageBasedSubtitleSaved": "Сохранены субтитры на основе изображений", "fullFrameImage": "Полнокадровое изображение", "fullFrameImageHint": "Сделать каждое изображение размером с кадр видео, с субтитром на своем месте", + "stereo3D": "3D", + "stereo3DHint": "Для 3D-видео: рисовать субтитр по одному разу в половине кадра для каждого глаза", + "halfSideBySide": "Half side-by-side (горизонтальная стереопара)", + "halfTopBottom": "Half top/bottom (вертикальная стереопара)", + "depth3D": "Глубина", + "depth3DHint": "Насколько субтитр выступает из экрана - положительное значение приближает его, отрицательное отодвигает назад", + "plane3D": "3D-Plane", + "plane3DHint": "Задать каждому субтитру глубину, которую хранит для него 3D Blu-ray, из файла 3D-Plane (.ofs), созданного BD3D2MK3D или OFSExtractor. Глубина используется там, где в 3D-Plane ее нет", + "openPlane3DTitle": "Открыть файл 3D-Plane", + "unableToLoadPlane3DX": "Не удалось загрузить 3D-Plane: {0}", + "plane3DXDepthYToZ": "{0} (глубина от {1} до {2})", + "plane3DXNoDepth": "{0} (нет значений глубины)", "textEffect": "Текстовый эффект", "textEffectSettingsTitle": "Настройки текстовых эффектов", "textEffectPreset": "Предустановка", @@ -1474,7 +1511,22 @@ "restoreAutoBackupFile": "Восстановить файл автосохранения", "restoreXFromY": "Вы хотите восстановить \"{0}\" из {1}?", "deleteAllSubtitleBackups": "Вы хотите удалить все резервные файлы субтитров?", - "deleteAll": "Удалить все" + "deleteAll": "Удалить все", + "subtitles": "Субтитры", + "settingsBackupInfo": "Резервная копия Settings.json создается при запуске Subtitle Edit, не чаще одного раза в {0} дн. и только если настройки изменились. Хранятся последние {1} резервных копий.", + "settingsBackupInfoEveryStart": "Резервная копия Settings.json создается при каждом запуске Subtitle Edit, если настройки изменились. Хранятся последние {0} резервных копий.", + "settingsBackupOff": "Автоматическое резервное копирование настроек отключено (Параметры → Настройки → Файл).", + "lastSettingsBackupX": "Последняя резервная копия: {0}", + "noSettingsBackupsYet": "Резервных копий настроек пока нет", + "openSettingsBackupFolder": "Открыть папку резервных копий настроек", + "backUpSettingsNow": "Создать копию сейчас", + "settingsBackedUp": "Резервная копия текущих настроек создана.", + "restoreSettings": "Восстановить настройки", + "restoreSettingsFromX": "Вы хотите заменить текущие настройки резервной копией от {0}?\n\nСначала будет создана резервная копия текущих настроек. Некоторые изменения вступят в силу только после перезапуска Subtitle Edit.", + "settingsRestored": "Настройки восстановлены. Перезапустите Subtitle Edit, чтобы все изменения вступили в силу.", + "settingsRestoreFailed": "Не удалось восстановить настройки: {0}", + "settingsBackupNotValid": "резервная копия не является корректным файлом настроек, поэтому текущие настройки сохранены", + "deleteAllSettingsBackups": "Вы хотите удалить все файлы резервных копий настроек?" }, "propertiesDCinema": { "title": "Свойства D-Cinema SMPTE", @@ -1488,22 +1540,16 @@ "editRate": "Частота кадров (Edit rate)", "timeCodeRate": "Частота временного кода", "startTime": "Время начала", - "font": "Шрифт", "fontId": "Идентификатор шрифта", "fontUri": "URI шрифта", - "generate": "Сгенерировать", - "fontColor": "Цвет шрифта", "chooseColor": "Выбрать цвет", "fontEffect": "Эффект шрифта", "effectColor": "Цвет эффекта", - "fontSize": "Размер шрифта", "topBottomMargin": "Отступ сверху/снизу", "fadeUpTime": "Время появления", "fadeDownTime": "Время исчезания", - "frames": "Рамки", "zPosition": "Z-положение", - "zPositionHelp": "Положительные числа отдаляют текст, отрицательные — приближают; если z-позиция равна нулю, то это двумерный текст", - "export": "Экспорт..." + "zPositionHelp": "Положительные числа отдаляют текст, отрицательные — приближают; если z-позиция равна нулю, то это двумерный текст" }, "webVtt": { "setStylesForSelectedLinesTitle": "Задать стили WebVTT для выбранных строк", @@ -1541,11 +1587,61 @@ "ignoreFormatting": "Игнорировать форматирование", "ignoreFormattingHint": "Строки, отличающиеся только тегами форматирования, такими как или {\\an8}, не считаются отличающимися", "showOnlyDifferencesInText": "Различия только в тексте", + "ignoreNumbering": "Игнорировать нумерацию", + "ignoreNumberingHint": "Строки, отличающиеся только номером, не считаются разными", + "ignoreTimeDifferenceMs": "Допуск по времени (мс)", + "ignoreTimeDifferenceMsHint": "Время начала и окончания, отличающееся на это число миллисекунд или меньше, не считается разным", + "compareDifferences": "Различия", + "compareTextDifferences": "Различия текста", + "compareCurrent": "Текущий", + "compareReference": "Эталон", + "compareEditable": "Редактируемый", + "compareEditableHint": "Изменения, сделанные здесь, переносятся в субтитры при нажатии \"Применить\"", + "compareReadOnly": "Только чтение", + "compareTakeFromReference": "Взять текст и тайминг из эталона", + "compareInsertFromReference": "Вставить эту строку в текущие субтитры", + "compareDeleteFromCurrent": "Удалить эту строку из текущих субтитров", + "compareTakeText": "Взять текст", + "compareTakeTiming": "Взять тайминг", + "compareEditLine": "Редактировать строку", + "compareEditHint": "Ctrl+Enter - сохранить, Esc - отмена", + "compareEdited": "Изменено", + "compareOnePendingChange": "1 несохраненное изменение", + "compareXPendingChanges": "Несохраненных изменений: {0}", + "compareDiscardXChanges": "Отменить изменения ({0}), сделанные в сравнении?", + "compareXChangesApplied": "Сравнение: применено изменений: {0}", + "compareChangeTextX": "№{0} текст из эталона", + "compareChangeTimingX": "№{0} тайминг из эталона", + "compareChangeTextAndTimingX": "№{0} текст и тайминг из эталона", + "compareChangeEditedX": "№{0} изменена", + "compareChangeInsertedX": "№{0} вставлена", + "compareChangeDeletedX": "№{0} удалена", + "compareSyncPoint": "Точка синхронизации", + "compareSyncPointHint": "Точка синхронизации - эти две строки всегда показываются парой, а строки выше и ниже выравниваются отдельно", + "compareSyncPickCurrent": "Точка синхронизации: использовать эту текущую строку", + "compareSyncPickReference": "Точка синхронизации: использовать эту строку эталона", + "compareSyncRemove": "Удалить точку синхронизации", + "compareClearXSyncPoints": "Очистить точки синхронизации ({0})", + "compareSyncCurrentPickedX": "Текущая №{0} выбрана точкой синхронизации - выберите соответствующую строку эталона и нажмите \"Синхронизировать\"", + "compareSyncReferencePickedX": "Эталон №{0} выбран точкой синхронизации - выберите соответствующую текущую строку и нажмите \"Синхронизировать\"", + "compareSyncWithCurrentX": "Синхронизировать с текущей №{0}", + "compareSyncWithReferenceX": "Синхронизировать с эталоном №{0}", + "compareSyncApply": "Синхронизировать", + "compareSyncPointSetXY": "Точка синхронизации задана: текущая №{0} и эталон №{1} теперь показываются парой", + "compareSyncAlreadyPairedXY": "Текущая №{0} и эталон №{1} уже показываются парой с одинаковым началом - синхронизировать нечего", + "compareSyncPointShiftedXYZW": "Точка синхронизации задана: текущие №{0}-№{1} сдвинуты на {2}, чтобы начинаться с эталона №{3}", + "compareChangeSyncShiftXYZ": "№{0}-№{1} сдвинуты на {2} (точка синхронизации)", + "compareTimingDeltaHint": "Насколько позже (+) или раньше (−) эталон начинается и заканчивается по сравнению с текущей строкой", "loadXFromFile": "Загрузить \"{0}\" из файла", + "compareLoadSavedFile": "Загрузить сохраненный файл", "saveCompareHtmlTitle": "Сохранить сравнение в HTML", "pickMatroskaTrackX": "Выбрать дорожку Matroska - {0}", "pickTransportStreamTrackX": "Выбрать дорожку транспортного потока - {0}", + "chapters": "Главы", + "dvdVobFilesMissingX": "Отсутствуют файлы VOB (найдено {0}%)", "pickMp4TrackX": "Выбрать дорожку MP4 - {0}", + "pickMxfTrackX": "Выберите дорожку MXF - {0}", + "pickMpegTrackX": "Выберите дорожку MPEG - {0}", "rosettaProperties": "Свойства Timed Text Rosetta IMSC", "rosettaFontSize": "Размер шрифта (высота строки)", "propertyTimeBase": "Временная база", @@ -1555,7 +1651,6 @@ "propertyDefaultRegion": "Регион по умолчанию", "propertyStyleAttributeName": "Название атрибута стиля", "propertyTimeCodeFormat": "Формат временного кода", - "propertyFileExtension": "Расширение файла", "propertyTopOrigin": "Начало верхней области", "propertyTopExtent": "Размер верхней области", "propertyBottomOrigin": "Начало нижней области", @@ -1621,7 +1716,8 @@ "findWhat": "Что искать", "descriptionOptional": "Описание (необязательно)", "invalidRegularExpressionX": "Недопустимое регулярное выражение: {0}", - "regularExpressionTooSlowX": "Регулярное выражение завершило работу через {0} секунд — правило пропущено, попробуйте более простой шаблон" + "regularExpressionTooSlowX": "Регулярное выражение завершило работу через {0} секунд — правило пропущено, попробуйте более простой шаблон", + "moveToCategory": "Переместить в категорию" }, "find": { "searchTextWatermark": "Поиск текста...", @@ -1634,7 +1730,9 @@ "replaceIn": "Заменить/поиск в", "replaceInTextAndOriginal": "Текст и оригинальный текст", "replaceInTextOnly": "Только текст", - "replaceInOriginalOnly": "Только оригинальный текст" + "replaceInOriginalOnly": "Только оригинальный текст", + "replacedOneOccurrence": "Заменено одно вхождение", + "replacedXOccurrences": "Заменено вхождений: {0}" }, "regularExpressionContextMenu": { "wordBoundary": "Граница слова (\\b)", @@ -1662,6 +1760,7 @@ "xLinesYSubtitles": "Строк: {0}, субтитров: {1}", "xLinesYSubtitlesZErrors": "Строк: {0}, субтитров: {1}, ошибок: {2}", "couldNotParseAsX": "Не удалось разобрать как {0}", + "readAsXUseAsY": "Исходный текст не удалось прочитать как {1}, но удалось прочитать как {0}.\nИспользовать строки и продолжить сохранять как {1}?", "selectedXCharactersYLines": "Выбрано символов: {0}, строк: {1}", "replacedXOccurrences": "Заменено вхождений: {0}", "invalidRegularExpression": "Недопустимое регулярное выражение", @@ -1697,7 +1796,23 @@ "largeChangeWarning": "Значительные изменения — похоже на переписывание текста, внимательно проверьте", "mismatchWarning": "Не похоже на оригинал — возможно, относится к другой строке", "engineError": "Не удалось связаться с движком ИИ: {0}", - "playCurrentLineHint": "Воспроизвести выбранную строку в видеоплеере и поставить на паузу в конце (также можно дважды щелкнуть по строке)" + "playCurrentLineHint": "Воспроизвести выбранную строку в видеоплеере и поставить на паузу в конце (также можно дважды щелкнуть по строке)", + "editAfterHint": "Щелкните текст выбранной строки, чтобы изменить исправление перед применением (Enter - сохранить, Escape - отмена)", + "template": "Шаблон", + "templatePlaceholder": "Выберите шаблон для замены промпта", + "templateStandard": "Стандартная вычитка", + "templateSpeechToText": "Очистка после распознавания речи", + "templateMachineTranslation": "Исправление машинного перевода", + "templateSdh": "SDH / для слабослышащих", + "referenceContext": "Справочный контекст", + "referenceContextInfo": "Необязательно. Имена, персонажи, глоссарий, синопсис или заметки о стиле - модель считает это главным источником для имен и терминов. Отправляется с каждым пакетом.", + "referenceContextPlaceholder": "Имена: Кайто, тетя Мэй, доктор Холлис\nТермины: Синкансэн, варп-ядро\nСинопсис: Кайто едет в Токио, чтобы найти пропавшего отца.", + "generateContext": "Создать с помощью ИИ", + "generateContextHint": "Выбранный движок ИИ прочитает субтитры и составит имена, термины и синопсис - проверьте результат перед использованием", + "generatingContextPartXOfY": "Чтение части {0} из {1}...", + "generatingContextSynopsis": "Составление синопсиса...", + "replaceContextQuestion": "Заменить текущий справочный контекст?", + "noContextGenerated": "Движок ИИ не вернул пригодной справочной информации." }, "aiAssistant": { "title": "ИИ-помощник", @@ -1743,6 +1858,7 @@ "removeLineBreaksPixelWidth": "Убрать переносы в субтитрах, которые помещаются в одну строку (ширина в пикселях)", "fixUppercaseIInsideLowercaseWords": "Исправить заглавную 'I' внутри слов в нижнем регистре (ошибка OCR)", "fixDoubleApostrophes": "Заменить двойной апостроф ('') на двойную кавычку (\")", + "fixMisreadQuotes": "Исправить апострофы, ошибочно распознанные как двойные кавычки (OCR, английский)", "addPeriods": "Добавлять точку после строк, где следующая строка начинается с заглавной буквы", "startWithUppercaseLetterAfterParagraph": "Начинать с заглавной буквы после абзаца", "startWithUppercaseLetterAfterPeriodInsideParagraph": "Начинать с заглавной буквы после точки внутри абзаца", @@ -1765,6 +1881,7 @@ "fixCommasExample": ",, -> ,", "removeLineBreaksExample": "Фу
бар! -> Фу бар!", "fixDoubleApostrophesExample": "''Имеет двойные одинарные кавычки'' -> \"Имеет одинарные двойные кавычки\"", + "fixMisreadQuotesExample": "\"Hello' -> \"Hello\"", "addPeriodsExample": "Привет мир -> Привет мир.", "startWithUppercaseLetterAfterParagraphExample": "p1: Фубар! || p2: фубар! -> p1: Фубар! || p2: Фубар!", "startWithUppercaseLetterAfterPeriodInsideParagraphExample": "Привет! как дела? -> Привет! Как дела?", @@ -1854,6 +1971,8 @@ "fixMinDurationMs": "Исправлять минимальную длительность (мс)", "doNotGoPastShotChange": "Не заходить за смену плана", "fixMaxDurationMs": "Исправлять максимальную длительность (мс)", + "fixMinDurationFrames": "Исправить минимальную длительность (кадры)", + "fixMaxDurationFrames": "Исправить максимальную длительность (кадры)", "minimumDurationMilliseconds": "Минимальная длительность (миллисекунды):", "maximumDurationMilliseconds": "Максимальная длительность (миллисекунды):", "maxDurationShouldBeHigherThanMinDuration": "Максимальная длительность должна быть больше минимальной", @@ -1903,13 +2022,80 @@ "timeCodesExtractFailed": "Не удалось считать временные коды из видео", "timeCodesHint": "Привязывать реплики к реальному времени кадров видео, а не к постоянной частоте кадров. Требуется однократно прочитать все видео; результат кэшируется." }, + "improveTimeCodes": { + "title": "Улучшить тайм-коды (принудительное выравнивание)", + "aligner": "Выравниватель", + "alignerHint": "Модель, которая ищет каждую строку в аудио. Лучшие варианты для языка субтитров указаны первыми.", + "maxShift": "Макс. сдвиг (секунды)", + "maxShiftHint": "Насколько субтитры могут быть рассинхронизированы. Строка, начало которой сдвинулось бы дальше, остается как есть и помечается. При любом значении отдельная строка, которая хочет сдвинуться более чем на полсекунды дальше соседних строк, не сдвигается вслепую: она следует за соседями, если сдвинулись все они, а иначе предлагается неотмеченной для вашей проверки.", + "adjustStartTimes": "Корректировать время начала", + "adjustEndTimes": "Корректировать время окончания", + "isolateSpeech": "Сначала выделить речь (медленно)", + "isolateSpeechHint": "Удаляет музыку и звуковые эффекты перед выравниванием, чтобы выравниватель слышал только диалоги. Помогает для строк, произносимых на фоне громкой музыки или экшена, но с GPU занимает примерно столько же времени, сколько длится аудио, - а без него во много раз дольше.", + "isolatingSpeech": "Выделение речи... (это займет некоторое время)", + "isolateSpeechFailed": "Не удалось выделить речь - выравнивание выполнено по исходному аудио.", + "checkWithSpeechToText": "Проверить распознаванием речи", + "checkWithSpeechToTextHint": "Также распознает аудио и проверяет каждый сдвиг по тому, где слова действительно прозвучали. Большие сдвиги, подтвержденные речью, отмечаются автоматически, а сдвиги в сторону от места, где строка слышна, остаются неотмеченными для вашей проверки. Также показывает, какая часть каждой строки была услышана - низкая доля означает, что текст отличается от сказанного. Субтитры, рассинхронизированные сильнее максимального сдвига (постоянное смещение, дрейф из-за несовпадения частоты кадров или скачки там, где были вырезаны сцены), сначала синхронизируются по услышанным словам. Использует Crisp ASR Parakeet, что для полнометражного фильма с GPU занимает несколько минут.", + "checkWithSpeechToTextNotAvailableX": "Проверка распознаванием речи недоступна для {0}", + "transcribingToCheck": "Распознавание речи для проверки выравнивания...", + "speechToTextFailed": "Распознавание речи не удалось - тайм-коды не были проверены.", + "speechToTextX": "Распознавание речи: {0}", + "speechToTextConfirmedXDisputedY": "Речь: подтверждено {0}, спорных {1}", + "hardlyHeardX": "Распознавание речи услышало только {0}% слов субтитров. Субтитры на том же языке, на котором говорят? Выравниватель не может разместить перевод.", + "heard": "Услышано", + "heardHint": "Какая часть строки была услышана распознаванием речи. Низкая доля означает, что текст отличается от сказанного - именно там выравниватель чаще всего ошибается.", + "statusConfirmedBySpeech": "Подтверждено речью", + "statusDisputedBySpeech": "Услышано на прежнем месте", + "statusMovedWithSync": "Сдвинуто вместе с синхронизацией", + "syncedFirstXY": "Сначала синхронизировано: {0} с в начале, {1} с в конце", + "showSpeechOnly": "Показывать только речь", + "showSpeechOnlyHint": "Рисовать выровненную осциллограмму по аудио без музыки и звуковых эффектов; исходная осциллограмма остается как есть для сравнения. Доступно после выделения речи - здесь или через \"Показывать только речь\" на осциллограмме главного окна.", + "align": "Выровнять", + "original": "Оригинал", + "aligned": "Выровнено", + "intro": "Выберите выравниватель и нажмите \"Выровнять\", чтобы найти каждую строку в аудио и уточнить ее тайм-коды.", + "extractingAudio": "Извлечение аудио...", + "aligningBatchXOfY": "Выравнивание... пакет {0} из {1}", + "extractAudioFailed": "Не удалось извлечь аудио с помощью ffmpeg.", + "modelWillBeDownloaded": "{0} будет загружена", + "summaryXRetimedYKeptZSkipped": "Перенастроено: {0} · Оставлено: {1} · Нет речи: {2}", + "meanShiftX": "Средний сдвиг: {0} мс", + "toCheckX": "Для проверки: {0}", + "largeMovesToCheckX": "Строк, которые сами по себе сдвинулись бы далеко и оставлены неотмеченными: {0} - прослушайте их (F5 воспроизводит выровненную строку, Shift+F5 - оригинал) и отметьте правильные.", + "linesToCheckX": "Строк, оставленных неотмеченными для проверки: {0} - прослушайте их (F5 воспроизводит выровненную строку, Shift+F5 - оригинал) и отметьте правильные.", + "changeXOfY": "Изменение {0} из {1}", + "previousChange": "Предыдущее изменение", + "nextChange": "Следующее изменение", + "startShift": "Начало", + "endShift": "Конец", + "statusRetimed": "Перенастроено", + "statusMovedWithNeighbours": "Сдвинуто вместе с соседями", + "statusLargeMoveUnconfirmed": "Большой сдвиг - прослушайте, отметьте для применения", + "statusUnchanged": "Уже на месте", + "statusNoSpeech": "Нет речи", + "statusShiftTooLarge": "Оставлено - слишком большой сдвиг", + "statusFailed": "Оставлено - ошибка выравнивания", + "statusAdjustedByHand": "Изменено вручную", + "alignedWaveformHint": "Перетащите строку, чтобы сдвинуть ее, или перетащите ее края, чтобы изменить начало или конец.", + "playLine": "Воспроизвести строку", + "undoAdjustment": "Отменить корректировку", + "playPause": "Воспроизведение / пауза", + "playAligned": "Выровнено", + "playAlignedHint": "Воспроизвести выбранную строку с выровненными тайм-кодами", + "apply": "Применить", + "applyHint": "Снимите отметку со строки, чтобы сохранить ее исходные тайм-коды. Дважды щелкните строку, чтобы воспроизвести ее. Строки также можно сдвигать и изменять их размер на выровненной осциллограмме." + }, "beautifyTimeCodesProfile": { "title": "Редактировать профиль облагораживания тайм-кодов", "loadPreset": "Загрузить предустановку", "presetDefault": "По умолчанию", "presetNetflix": "Netflix", "presetSdi": "SDI", - "general": "Общий", + "saveAsProfile": "Сохранить как профиль...", + "deleteProfile": "Удалить профиль", + "profileName": "Имя профиля", + "overwriteProfileX": "Профиль с именем \"{0}\" уже существует. Перезаписать его?", + "deleteProfileX": "Удалить профиль \"{0}\"?", "gap": "Промежуток", "gapSuffix": "кадров (также обновляет ненулевые поля промежутков ниже)", "milliseconds": "мс", @@ -1940,6 +2126,10 @@ "bridgeGaps": { "bridgeGapsSmallerThan": "Заполнять промежутки меньше (мс)", "minGap": "Минимальный промежуток (мс)", + "bridgeGapsSmallerThanFrames": "Заполнять промежутки меньше (кадры)", + "minGapFrames": "Минимальный промежуток (кадры)", + "calculateBridgeGapsSmallerThanDotDotDot": "Рассчитать предел заполнения промежутков по частоте кадров...", + "useXMsAsBridgeGapsSmallerThan": "Заполнять промежутки меньше \"{0}\" миллисекунд?", "numberOfSmallGapsBridgedX": "Заполнено коротких промежутков: {0}", "percentFoPrevious": "Доля промежутка для предыдущего (%)", "gapChange": "Изменение промежутка" @@ -1973,6 +2163,7 @@ "addColor": "Добавить цвет", "deleteLinesWithSpecificActorsOrStyles": "Удалить строки с актерами или стилями (несколько — через запятую)", "useSourceStylesIfPossible": "По возможности использовать исходные стили (заголовок ниже тогда используется только для не-ASSA файлов)", + "keepSourceEmbeddedFonts": "Сохранять встроенные шрифты источника (footer)", "editStyles": "Редактировать стили", "editProperties": "Редактировать свойства", "editAttachments": "Редактировать вложения", @@ -2004,10 +2195,50 @@ "convertColorsToDialogReBreakLines": "Заново расставить переносы", "snapTimeCodesToFramesInfo": "Округляет время начала и окончания до ближайшего кадра. Частота кадров считывается из видеофайла с тем же именем, что и файл субтитров, если не выбрана фиксированная частота кадров.", "addFolderDotDotDot": "Добавить папку...", + "addFolderRecursiveDotDotDot": "Добавить папку рекурсивно...", "selectFolderToConvert": "Выбрать папку с файлами для преобразования", "includeSubfolders": "Включать подпапки при добавлении папки", + "videoFilesWhenAddingFolder": "Видеофайлы (mkv, mp4, ts...) при добавлении папки", + "videoFilesAsk": "Спрашивать", + "videoFilesInclude": "Добавлять их дорожки субтитров", + "videoFilesSkip": "Пропускать", + "folderContainsXVideoFiles": "Папка содержит видеофайлы (mkv, mp4, ts...): {0}.\n\nДобавить также их встроенные дорожки субтитров или только файлы субтитров?", + "addVideoFiles": "Добавить видеофайлы", + "skipVideoFiles": "Только файлы субтитров", + "doNotAskAgainVideoFiles": "Больше не спрашивать (можно изменить в настройках пакетного преобразования)", + "settingsSectionOutput": "Вывод", + "settingsSectionAddingFiles": "Добавление файлов", + "settingsSectionImageBasedSubtitles": "Графические субтитры (OCR)", + "settingsSectionOther": "Другое", "keepSourceFileTimestamp": "Сохранять дату/время исходного файла у выходных файлов", - "scanningFolderX": "Сканирование {0}..." + "preventSleepWhileConverting": "Не давать компьютеру уходить в спящий режим во время преобразования", + "scanningFolderX": "Сканирование {0}...", + "transportStreamSettings": "Настройки Transport Stream", + "transportStreamSettingsDotDotDot": "Настройки Transport Stream...", + "transportStreamSettingsInfo": "Применяется к дорожкам субтитров, извлеченным из файлов Transport Stream (.ts/.m2ts). Положение и размер видео влияют только на графические дорожки DVB, экспортируемые в графический формат.", + "transportStreamOverrideXPosition": "Переопределить исходное положение X", + "transportStreamOverrideYPosition": "Переопределить исходное положение Y", + "transportStreamOverrideVideoSize": "Переопределить исходный размер видео", + "transportStreamBottomMargin": "Нижнее поле", + "transportStreamFileNameEnding": "Окончание имени файла", + "transportStreamFileNameEndingInfo": "Добавляется перед расширением для каждой извлеченной дорожки. Оставьте пустым, чтобы использовать обычный языковой суффикс.", + "transportStreamOnlyTeletext": "Только телетекст", + "transportStreamGetSizeFromVideo": "Взять размер из видео...", + "twoLetterLanguageCodeUppercase": "Двухбуквенный код языка (прописные)", + "threeLetterLanguageCodeUppercase": "Трехбуквенный код языка (прописные)", + "formatContainsDotDotDot": "Формат содержит...", + "extensionIsDotDotDot": "Расширение равно...", + "statusIsError": "Статус - ошибка", + "forcedTracksOnly": "Только принудительные дорожки", + "xConvertedYFailedInZ": "Преобразовано: {0:#,###,##0}, с ошибкой: {1:#,###,##0}, за {2}", + "xNotProcessed": "Не обработано: {0:#,###,##0}", + "removeUnicodeControlCharactersTitle": "Удалить управляющие символы Unicode", + "removeUnicodeControlCharactersInfo": "Удаляет из текста невидимые управляющие символы направления Unicode (LRM, RLM, LRE, RLE, PDF, LRO и RLO) и заменяет неразрывные пробелы обычными.", + "preset": "Предустановка", + "savePresetDotDotDot": "Сохранить предустановку...", + "deletePreset": "Удалить предустановку", + "presetName": "Имя предустановки", + "deletePresetX": "Удалить предустановку \"{0}\"?" }, "changeCasing": { "fixNames": "Исправить имена", @@ -2092,10 +2323,13 @@ }, "mergeLinesWithSameText": { "maxMsBetweenLines": "Максимальное количество миллисекунд между строками", - "includeIncrementingLines": "Включать строки с нарастающим текстом" + "maxFramesBetweenLines": "Макс. кадров между строками", + "includeIncrementingLines": "Включать строки с нарастающим текстом", + "includeRollUpCaptions": "Включая прокручиваемые (roll-up) субтитры" }, "mergeLinesWithSameTimeCodes": { "maxMsDifference": "Максимальная разница (миллисекунды)", + "maxFramesDifference": "Макс. разница (кадры)", "makeDialog": "Создавать диалоги" }, "netflixCheckAndFix": { @@ -2195,12 +2429,29 @@ "letterboxCustom": "Своя высота полос", "barHeightPx": "Высота полосы (пикселей)", "titleSafePercent": "Безопасная зона титров (%)", + "moveCaptions": "Переместить субтитры", + "moveCaptionsDotDotDot": "Переместить субтитры...", + "moveCaptionsTo": "Переместить субтитры", + "moveCaptionsIntoBars": "На черные полосы letterbox (за пределы изображения)", + "moveCaptionsIntoPicture": "Внутрь изображения (с полос)", + "moveCaptionsOffset": "Отступ от края (px)", + "letterboxBarHeightXPxYOfZCaptionsWillMove": "Высота полосы: {0} px - будет перемещено субтитров: {1} из {2}", + "removeFadeInOut": "Удалить появление/исчезновение", + "removeFadeInOutXLinesRemoved": "Строк появления/исчезновения, объединенных со своими строками: {0}", + "removeFadeInOutNothingFound": "Строки появления/исчезновения не найдены", "positionSummary": "{0}×{1} - субтитров: {2} - высота полосы: {3} px", "xInActivePicture": "{0} в кадре", "xInTopBar": "{0} в верхней полосе", "xInBottomBar": "{0} в нижней полосе", "noImageSubtitlesLoaded": "Субтитры-изображения не загружены", - "imageBasedFormatNotSupported": "Формат субтитров-изображений не найден/не поддерживается." + "imageBasedFormatNotSupported": "Формат субтитров-изображений не найден/не поддерживается.", + "frameRateDeclaredInFile": "Частота кадров, указанная в файле Blu-ray sup", + "frameRateDetectedFromTimeCodesX": "Частота кадров, определенная по тайм-кодам Blu-ray sup (в файле указано {0})", + "frameRateFromVideo": "Частота кадров видео", + "frameRateCurrent": "Текущая частота кадров", + "frameRateSetManually": "Частота кадров задана вручную", + "frameRateUsageInfo": "Экспорт в Blu-ray sup, BDN XML, DOST и FCP выполняется с этой частотой кадров. Время при этом не меняется - для этого используйте \"Изменить частоту кадров\".", + "useVideoFrameRateXInsteadOfY": "Частота кадров видео - {0}, а у субтитров задана {1}.\n\nИспользовать частоту кадров видео ({0})?" }, "removeTextForHearingImpaired": { "interjections": "Междометия", @@ -2230,6 +2481,7 @@ "pickFontNameInstalledFonts": "Установленные шрифты", "pickFontNameCollectedFonts": "Собранные шрифты", "colorPickerTitle": "Выбрать цвет", + "colorPickerBrightness": "Яркость", "filterLayersTitle": "Фильтр отображаемых слоев", "filterLayersHideFromWaveform": "Скрыть на осциллограмме/спектрограмме", "filterLayersHideFromSubtitleGrid": "Скрыть в таблице субтитров", @@ -2280,7 +2532,21 @@ "correctWordsX": "Правильных слов: {0}", "namesX": "Имена: {0}", "addedToDictionaryX": "Добавлено в словарь: {0}", - "doNotShowThisAgain": "Больше не показывать это сообщение" + "doNotShowThisAgain": "Больше не показывать это сообщение", + "editUseAlwaysList": "Изменить список \"Всегда использовать\"...", + "useAlwaysListTitle": "Проверка орфографии - список \"Всегда использовать\"", + "useAlwaysListHeader": "Список \"Всегда использовать\"", + "useAlwaysListDescription": "Слова, которые проверка орфографии заменяет автоматически; сохраняются кнопками \"Изменить все\" и \"Всегда использовать\". Пара используется, только если слово написано с ошибкой.", + "useAlwaysWord": "Слово с ошибкой", + "xPairs": "Пар: {0:#,##0}", + "xUnusedPairs": "Ни разу не использовано: {0:#,##0}", + "useAlwaysUnusedHint": "Ни разу не использовано - это слово написано правильно (или является именем либо пользовательским словом), поэтому проверка орфографии никогда его не отмечает", + "removeUnused": "Удалить неиспользуемые", + "useAlwaysEmpty": "Для этого языка пока нет пар \"Всегда использовать\"", + "useAlwaysEmptyHint": "Пары добавляются при нажатии \"Изменить все\" или \"Всегда использовать\" в проверке орфографии.", + "useAlwaysNoMatch": "Нет пар, соответствующих поиску", + "useAlwaysSameWord": "Замена должна отличаться от слова.", + "useAlwaysRememberOff": "Параметр \"Запоминать список 'Всегда использовать'\" отключен в настройках, поэтому проверка орфографии не использует и не сохраняет этот список." }, "video": { "burnIn": { @@ -2293,6 +2559,7 @@ "sampleRate": "Частота дискретизации", "audio": "Аудио", "preset": "Предустановка", + "tune": "Tune", "pixelFormat": "Пиксельный формат", "crf": "CRF", "useSource": "Как в исходнике", @@ -2304,7 +2571,9 @@ "outputProperties": "Свойства вывода...", "videoFileSize": "Размер видеофайла", "oneBox": "Одна подложка", - "logoInfo": "Выберите изображение PNG и перетащите его, чтобы разместить на видео." + "logoInfo": "Выберите изображение PNG и перетащите его, чтобы разместить на видео.", + "noSubtitlesGenerateAnyway": "Нет строк субтитров для вшивания.\n\nСоздать видео без субтитров?", + "noSubtitlesInXItemsGenerateAnyway": "Элементов пакета без строк субтитров для вшивания: {0}.\n\nСоздать эти видео без субтитров?" }, "videoTransparent": { "title": "Создать прозрачное видео с субтитрами" @@ -2315,6 +2584,21 @@ "translateToEnglish": "Перевести на английский", "transcribing": "Распознавание...", "transcribingXOfY": "Распознавание {0} из {1}...", + "linesTranscribedXOfYFailedZ": "Распознано строк: {0} из {1} - {2} с ошибкой или без речи.\n\nСтроки с ошибкой сохраняют текущий текст. Применить распознанные строки или повторить неудачные (можно сначала изменить движок или настройки)?", + "linesTranscribedXOfYCancelled": "Распознавание отменено после {0} из {1} строк.\n\nПрименить распознанные строки? Остальные строки сохранят текущий текст.", + "noLinesTranscribed": "Не удалось распознать ни одну из {0} строк.\nПодробности см. в журнале инструментов.", + "applyTranscribedLines": "Применить распознанное", + "retryFailedLines": "Повторить неудачные", + "isolateSpeech": "Выделить речь (медленно)", + "isolateSpeechHint": "Удаляет музыку и звуковые эффекты перед распознаванием. Дает меньше пропущенных строк и лучший тайминг для аудио с громкой музыкой, но с GPU занимает примерно столько же времени, сколько длится аудио, - а без него во много раз дольше.", + "isolatingSpeech": "Выделение речи...", + "isolateSpeechFailed": "Не удалось выделить речь - распознается исходное аудио.", + "detectSpeakers": "Определять говорящих", + "detectSpeakersHint": "Начинает каждую строку с говорящего, например \"(speaker 0)\", с помощью диаризации NVIDIA Sortformer (до 8 говорящих). Лучше всего работает с движками, которые разделяют строки при смене говорящего, например Parakeet и Cohere.", + "detectSpeakersNeedsNewerCrispAsr": "Для \"Определять говорящих\" нужен Crisp ASR v{0} или новее, но установлен v{1}.\n\nОбновите Crisp ASR (кнопка с шестеренкой рядом с движком) или отключите \"Определять говорящих\".", + "xNeedsNewerCrispAsr": "Для {0} нужен Crisp ASR v{1} или новее, но установлен v{2}.\n\nСкачать и установить обновление сейчас?", + "vad": "VAD", + "vadHint": "Обнаружение голосовой активности: распознается только речь, разрезанная там, где говорящий делает паузу.\nАвто: Silero и только для движков, которым это нужно (Cohere, Mega, Index-Echo).\nSilero, FireRedVAD, Whisper-VAD, WebRTC: всегда включено для каждого движка Crisp ASR. FireRedVAD - самый точный, Whisper-VAD дает меньше всего разрезов и самые чистые, но работает медленнее всех.\nIndex-Echo всегда использует Silero. --vad, --vad-model или --chunk-seconds в расширенных настройках имеют приоритет.", "inputLanguage": "Язык ввода", "advancedWhisperSettings": "Дополнительные параметры распознавания речи", "downloadingSpeechToTextEngine": "Загрузка движка распознавания речи", @@ -2398,6 +2682,19 @@ "videoEncodingSettings": "TTS — Настройки кодирования видео", "elevenLabsSettings": "TTS - Настройки ElevenLabs", "elevenLabsSettingsResetHint": "Сбросить настройки ElevenLabs до значений по умолчанию", + "provider": "Провайдер", + "openAiCompatibleSettings": "TTS - Настройки OpenAI-совместимого сервера", + "customServerUrl": "URL собственного сервера", + "customServerUrlHint": "Полный адрес конечной точки речи OpenAI-совместимого сервера, используется, когда выбран провайдер \"Custom\", например http://localhost:8880/v1/audio/speech", + "customModels": "Свои модели", + "customModelsHint": "Имена моделей через запятую, предлагаемые, когда выбран провайдер \"Custom\"", + "customVoices": "Свои голоса", + "customVoicesHint": "Имена голосов через запятую, используемые провайдером \"Custom\" и моделями OpenRouter, не публикующими список голосов", + "instructions": "Инструкции", + "instructionsHint": "Необязательный стиль речи, например \"Говорите спокойным, теплым голосом\". Отправляется только моделям, которые это поддерживают (не tts-1/tts-1-hd)", + "openAiSpeedHint": "Скорость речи от 0.25 до 4.0 (1.0 - обычная). Поддерживается не всеми моделями", + "outputFormat": "Выходной формат", + "outputFormatHint": "Аудиоформат, запрашиваемый у сервера. \"Авто\" запрашивает MP3 и переключается на PCM для моделей, которые возвращают только PCM (например, Gemini TTS)", "regenerateAudio": "Сгенерировать аудио заново", "autoContinuePlaying": "Автоматическое продолжение воспроизведения", "playLine": "Воспроизвести строку", @@ -2406,6 +2703,13 @@ "addingAudioToVideoFileDotDotDot": "Добавление аудио к видеофайлу...", "preparingMergeDotDotDot": "Подготовка к объединению...", "importVoiceDotDotDot": "Импорт голоса...", + "renameVoiceDotDotDot": "Переименовать голос...", + "renameVoiceTitle": "Переименовать голос", + "voiceXCouldNotBeRenamedX": "Не удалось переименовать голос '{0}': {1}", + "deleteVoiceDotDotDot": "Удалить голос...", + "deleteVoiceTitle": "Удалить голос", + "deleteVoiceXQuestion": "Удалить голос '{0}'?\n\nЭталонная запись и ее файлы будут удалены с диска.", + "voiceXCouldNotBeDeletedX": "Не удалось удалить голос '{0}': {1}", "voiceImportSuccessTitle": "Голос импортирован", "voiceXImported": "Голос '{0}' успешно импортирован", "voiceXCouldNotBeImported": "Не удалось импортировать голос '{0}' — подробности см. в журнале", @@ -2415,6 +2719,49 @@ "dropAudioFileHereToImportVoice": "Перетащите сюда аудиофайл, чтобы импортировать голос", "dropAudioFileHereHint": ".wav или .mp3", "voiceCloneTranscriptTitle": "Введите расшифровку аудиозаписи (необходимо для клонирования голоса)", + "voiceManagerTitle": "TTS - Менеджер голосов", + "voiceManagerDotDotDot": "Менеджер голосов...", + "voiceManagerMenuItem": "Менеджер голосов TTS...", + "speakFromCurrentLineTitle": "Читать вслух с текущей строки", + "speakFromCurrentLineDotDotDot": "Читать вслух с текущей строки...", + "speakFromCurrentLineHint": "Читает строки вслух одну за другой, начиная с текущей. Нажмите Escape или щелкните другую строку, чтобы остановить.", + "stopSpeaking": "Остановить чтение", + "speakingLineX": "Чтение строки {0}... (Escape - остановить)", + "playWithSpeechTitle": "Воспроизвести с речью", + "playWithSpeechDotDotDot": "Воспроизвести с речью...", + "playWithSpeechHint": "Воспроизводит видео и произносит каждую строку, когда видео до нее доходит. Пауза и перемотка работают как обычно; нажмите Escape, чтобы остановить.", + "lowerVideoVolumeWhileSpeaking": "Уменьшать громкость видео во время речи", + "pauseVideoWhenSpeechRunsLate": "Ставить видео на паузу, если речь заходит на следующую строку", + "waitingForSpeechLineX": "Создание речи для строки {0}... (Escape - остановить)", + "voiceKindClone": "Клонированный голос", + "voiceKindPreset": "Встроенный", + "voiceKindModel": "Модель голоса", + "voiceKindOnline": "Онлайн", + "transcript": "Расшифровка", + "transcriptRequiredHint": "Обязательно - движок читает эту расшифровку записи (ref-text) при клонировании", + "transcriptOptionalHint": "Необязательно - точная расшифровка записи улучшает клонирование", + "saveTranscript": "Сохранить расшифровку", + "searchVoices": "Поиск голосов", + "loadingVoicesDotDotDot": "Загрузка голосов...", + "noVoicesForEngine": "Нет голосов - импортируйте запись или скачайте набор голосов", + "noVoicesMatchFilter": "Нет голосов, соответствующих поиску", + "copyVoiceTo": "Копировать голос в", + "copyVoiceToDotDotDot": "Копировать голос в движок...", + "copyingVoiceXToYDotDotDot": "Копирование голоса '{0}' в {1}...", + "voiceXCopiedToY": "Голос '{0}' скопирован в {1}", + "voiceXCouldNotBeCopiedToY": "Не удалось скопировать голос '{0}' в {1} - подробности см. в журнале", + "voiceXAlreadyExistsInYContinue": "Голос с именем '{0}' уже существует в {1}.\n\nВсе равно копировать?", + "openVoicesFolder": "Открыть папку голосов", + "downloadVoicePacksDotDotDot": "Скачать наборы голосов...", + "downloadVoicePacksTitle": "TTS - Скачать наборы голосов", + "voicePackIntro": "Наборы голосов - это коллекции коротких чистых записей с расшифровками, готовых для клонирования. Каждый набор импортируется в выбранный движок (уже имеющиеся голоса пропускаются) и содержит ATTRIBUTION.txt с лицензией и источниками.", + "voicePackDetailsXVoicesYSizeZLicense": "Голосов: {0} · {1} · {2}", + "installToEngine": "Установить в движок", + "downloadAndInstall": "Скачать и установить", + "selectAtLeastOneVoicePack": "Выберите хотя бы один набор голосов", + "downloadingXDotDotDot": "Скачивание {0}...", + "installingXDotDotDot": "Установка {0}...", + "xVoicesInstalledYSkipped": "Установлено голосов: {0} (уже было: {1})", "useSpeechToTextDotDotDot": "Распознать речь...", "autoCastMenuItem": "Найти голоса в видео и клонировать их...", "autoCastSpeakersTitle": "Голоса, звучащие в видео", @@ -2433,7 +2780,7 @@ "cloneVoicePerLinePreparing": "Извлечение голоса каждой реплики из видео...", "cloneVoicePerLineNeedsVideo": "Для клонирования голоса каждой реплики нужно видео, к которому относятся субтитры. Откройте видео и попробуйте снова.", "cloneVoicePerLineNoClips": "Из видео не удалось извлечь звук, поэтому клонировать его не с чего. Убедитесь, что в видео есть звуковая дорожка.", - "cloneVoicePerLineNeedsOriginalSubtitleX": "{0} может клонировать строку только в том случае, если знает, что говорится в видео на этой строке, а именно из субтитров на языке оригинала. Оригинальные субтитры не загружены, поэтому каждая строка будет произнесена первым импортированным голосом, а не голосом из видео.\n\nОткройте оригинальные субтитры (Файл > Открыть оригинал...) рядом с переводом, затем сгенерируйте их снова.", + "cloneVoicePerLineTranscribeClipsXYZ": "{0} может клонировать голос для строки, только если известно, что говорится в видео в этой строке. Это неизвестно для {1} из {2} строк - обычно это берется из субтитров на языке оригинала (Файл > Открыть оригинал...).\n\nРаспознать речь для этих строк из видео сейчас?\n\nДа: выберите движок и язык речи в видео, и эталонные фрагменты будут распознаны перед созданием речи.\nНет: все равно создать - эти строки будут произнесены обычным голосом вместо голоса из видео.", "voiceCloneConsentTitle": "Клонирование голоса — перед началом работы", "voiceCloneConsentHeader": "Вы собираетесь клонировать голос", "voiceCloneConsentIntro": "Клонирование копирует голос реального человека. В большинстве стран для этого есть правила, а в ЕС — юридические обязанности, которые лежат на вас, а не на Subtitle Edit. Пожалуйста, прочтите это один раз.", @@ -2450,10 +2797,29 @@ "advancedTtsSettings": "Расширенные настройки синтеза речи", "advancedTtsAudioProcessing": "Обработка звука", "advancedTtsOutput": "Вывод", + "advancedTtsVoiceCloning": "Клонирование голоса", + "cleanCloneReferences": "Очищать эталоны для клонирования голоса (Sidon)", + "cleanCloneReferencesDescription": "Удаляет музыку, шум и реверберацию из аудио, с которого клонируется голос: фрагментов из видео и записей, импортированных в менеджере голосов. Использует модель восстановления речи Sidon на audio.cpp (загрузка 940 МБ). Клонированный голос больше не копирует фон, но очень тихая или перекрывающаяся речь может звучать приглушенно.", + "cleaningCloneReferencesDotDotDot": "Очистка эталонов для клонирования голоса...", + "downloadSidonTitle": "Скачать Sidon?", + "downloadSidonQuestionX": "Для очистки эталонов для клонирования голоса нужна модель восстановления речи Sidon ({0}).\n\nСкачать ее сейчас? Без нее эталоны используются как есть.", "proAudioPostProcessing": "Профессиональная постобработка звука", "proAudioPostProcessingDescription": "Применяет к каждому сегменту эквалайзер, шумоподавление, компрессию, нормализацию громкости (-16 LUFS) и плавное нарастание/затухание.", + "generatingSpeechSegmentXOfY": "Создание речи: сегмент {0} из {1}", + "adjustingSpeedSegmentXOfY": "Настройка скорости: сегмент {0} из {1}", + "mergingAudioSegmentXOfY": "Объединение аудио: сегмент {0} из {1}", + "postProcessingSegmentXOfY": "Постобработка: сегмент {0} из {1}", "audioDucking": "Приглушение оригинала", "audioDuckingDescription": "Приглушает исходный звук видео и накладывает поверх него речь, так что оригинальная дорожка остается едва слышной. Применяется, только когда речь добавляется в видеофайл.", + "removeOriginalSpeech": "Удалить оригинальную речь (медленно)", + "removeOriginalSpeechDescription": "Сохраняет музыку и звуковые эффекты исходного видео, но удаляет из него речь и накладывает поверх новую речь. С GPU занимает примерно столько же времени, сколько длится видео, а без него во много раз дольше. При включенном приглушении оригинала его громкость применяется к музыке и эффектам. Применяется, только когда речь добавляется в видеофайл.", + "removingOriginalSpeech": "Удаление оригинальной речи...", + "removeOriginalSpeechFailed": "Не удалось удалить оригинальную речь - речь добавлена в видео без этого.", + "speechRemovalModel": "Модель", + "speechRemovalModelDescription": "Лучшее качество сохраняет больше всего музыки и звуковых эффектов. Быстрый режим примерно в три раза быстрее, но теряется чуть больше музыки.", + "speechRemovalBestQuality": "Лучшее качество (Mel-Band RoFormer)", + "speechRemovalFast": "Быстро (HTDemucs)", + "videoTruncatedWarning": "Изображение в новом видеофайле обрывается через {0}, но длительность исходного видео - {1}: картинка застынет, а звук продолжится.\n\nФайл сохранен. Вывод ffmpeg см. в error-log.txt в папке данных Subtitle Edit.", "originalVolumePercent": "Громкость оригинала, %", "vadSilenceCompression": "Сжатие тишины VAD", "vadSilenceCompressionDescription": "Сокращает паузы между словами вместо ускорения речи, чтобы фрагмент поместился без потери качества.", @@ -2536,7 +2902,9 @@ "timeCodeFormatColon": "Формат временного кода:", "shotChangesList": "Список смен планов", "shotChangesClearQuestion": "Удалить все смены планов?", - "deleteSelectedShotChangeQuestion": "Удалить выбранную смену плана?" + "deleteSelectedShotChangeQuestion": "Удалить выбранную смену плана?", + "shotChanges": "Смены планов", + "exportShotChanges": "Экспорт смен планов" }, "chapters": { "title": "Главы", @@ -2566,7 +2934,6 @@ "scaleTimesDescription": "Пересчитать время каждой главы из одной частоты кадров в другую.", "fromFrameRate": "От", "toFrameRate": "К", - "apply": "Применить", "writeToVideo": "Записать в видео...", "writeToVideoTitle": "Записать главы в видео", "writeToVideoDescription": "Главы записываются в копию видеофайла. Ничего не перекодируется, поэтому процесс быстрый и без потерь качества.", @@ -2576,7 +2943,6 @@ "writtenToX": "Главы записаны в {0}", "unableToWriteChapters": "Не удалось записать главы в видеофайл.", "chaptersSavedToX": "Главы сохранены в {0}", - "noVideoLoaded": "Видео не загружено", "emptyListCallToAction": "Глав пока нет — добавьте главу в позиции видео или импортируйте их из видео или файла." }, "videoOcr": { @@ -2601,10 +2967,10 @@ "runningOcrXY": "Выполняется распознавание текста... {0}/{1}", "noLinesFoundTitle": "Субтитры не найдены", "noLinesFoundMessage": "В области сканирования текст не найден — попробуйте отрегулировать область сканирования, движок или минимальную яркость.", + "ocrStoppedEarlyMessage": "Движок OCR остановился до того, как были прочитаны все кадры, поэтому найденные строки сохранены, а остальные отсутствуют.{0}{0}{1}", "linesFoundX": "найдено {0} строк", "refiningTimingXY": "Уточнение времени... {0}/{1}", "fixOcrErrors": "Исправить ошибки OCR", - "dictionary": "Словарь", "editLineX": "Редактировать строку {0}", "downloadDictionaryHint": "Скачать словарь для проверки орфографии...", "framesPerSecondHint": "Сколько кадров в секунду сканируется. 5 подходит для обычных субтитров; более высокое значение позволяет распознавать очень короткие строки и более точно группировать их, но требует больше времени на распознавание текста.", @@ -2621,7 +2987,58 @@ "testOcr": "Проверить текущий кадр", "testOcrRunning": "Тестирование распознавания текста на текущем кадре...", "testOcrResultX": "Результат теста: {0}", - "testOcrNoTextFound": "Проверка: в области сканирования текст не найден" + "testOcrNoTextFound": "Проверка: в области сканирования текст не найден", + "engineSettings": "Настройки движка OCR", + "engineBuiltIn": "Встроен в операционную систему", + "engineExternalServer": "Внешний сервер (устанавливается и управляется вне Subtitle Edit)", + "engineCloudApi": "Облачный API (требуется ключ API)", + "engineNothingToInstall": "Ничего устанавливать не нужно", + "engineModelsMissing": "Движок установлен, модели отсутствуют", + "website": "Веб-сайт" + }, + "backgroundMusic": { + "title": "Создать фоновую музыку", + "generateBackgroundMusicDotDotDot": "Создать фоновую музыку...", + "preset": "Предустановка", + "prompt": "Промпт", + "tempoBpm": "Темп (BPM)", + "generateSeconds": "Создать (секунды)", + "seed": "Seed", + "randomSeed": "Случайный", + "outputLength": "Длительность результата", + "outputLengthVideoX": "{0} (длительность видео - музыка бесшовно зацикливается)", + "musicVolumePercent": "Громкость музыки (%)", + "removeExistingAudioTracks": "Удалить существующие аудиодорожки", + "originalAudioVolumePercent": "Громкость исходного аудио (%)", + "saveAudioDotDotDot": "Сохранить аудио...", + "addToVideoDotDotDot": "Добавить в видео...", + "engineInfo": "ACE-Step 1.5 Turbo через audio.cpp (лицензия MIT) - работает локально", + "presetCooking": "Кулинария / выпечка", + "presetTech": "Обзор техники / гаджетов", + "presetForest": "Лес / природа", + "presetRelaxingPiano": "Расслабляющее фортепиано", + "presetLofiVlog": "Lo-fi влог", + "presetCorporate": "Корпоративное / объясняющее видео", + "loadingModel": "Загрузка модели...", + "composing": "Сочинение...", + "generatingAudio": "Создание аудио...", + "decodingAudio": "Декодирование аудио...", + "creatingLoop": "Создание бесшовной петли...", + "resultLoopXYZ": "Петля: {0} при {1} BPM, совпадение стыка {2}%", + "resultNoLoopX": "Музыка готова: {0}", + "addingMusicToVideo": "Добавление музыки в видео...", + "downloadModelTitle": "Скачать музыкальную модель?", + "downloadModelQuestionX": "Для создания музыки нужна модель ACE-Step 1.5 ({0}).\n\nСкачать модель?", + "downloadingModel": "Скачивание модели ACE-Step 1.5...", + "saveAudioTitle": "Сохранить фоновую музыку", + "audioFileSavedX": "Аудиофайл сохранен: {0}", + "unableToGenerateMusic": "Не удалось создать музыку", + "unableToAddMusicToVideo": "Не удалось добавить музыку в видео", + "outputLengthTextToSpeech": "По созданной речи - музыка бесшовно зацикливается и приглушается, когда кто-то говорит", + "addBackgroundMusic": "Добавить фоновую музыку", + "addBackgroundMusicHint": "Создать музыку локально с помощью ACE-Step 1.5 и подмешать ее под речь - она приглушается, когда кто-то говорит", + "generatingBackgroundMusicDotDotDot": "Создание фоновой музыки...", + "mixingBackgroundMusicDotDotDot": "Сведение фоновой музыки..." }, "goToVideoPosition": "Перейти к позиции видео", "goToVideoPositionDotDotDot": "Перейти к позиции видео...", @@ -2632,10 +3049,47 @@ "openSecondarySubtitleOnVideoPlayerDotDotDot": "Открыть второй файл субтитров...", "openSecondarySubtitleOnVideoPlayer": "Второй файл субтитров (в видеоплеере)", "removeSecondarySubtitleOnVideoPlayer": "Убрать второй файл субтитров", + "editSecondarySubtitleSettings": "Настройки вторых субтитров", + "editSecondarySubtitleSettingsDotDotDot": "Настройки вторых субтитров...", + "secondarySubtitleRememberSettings": "Запомнить эти настройки", + "secondarySubtitleDoNotShowAgain": "Больше не показывать это окно", + "secondarySubtitleJustifyLines": "Выравнивать строки по ширине", "openRecentVideo": "Открыть недавнее видео", "clearRecentVideos": "Очистить список недавних видео", "cutVideoTitle": "Обрезка видео", "cutVideoDotDotDot": "Обрезать видео...", + "remuxVideoTitle": "Перепаковать видео", + "remuxVideoDotDotDot": "Перепаковать видео...", + "remuxVideoInputVideo": "Входное видео:", + "remuxVideoMultipleTracksRequiresMkv": "Для нескольких аудиодорожек или дорожек субтитров нужен контейнер MKV. Выходной формат автоматически изменен на .mkv.", + "remuxVideoOutputFormat": "Выходной формат:", + "remuxVideoOutputFile": "Выходной файл:", + "remuxVideoRemuxing": "Перепаковка видео и аудио...", + "remuxVideoFinalizing": "Завершение выходного файла (перемещение индекса в начало)...", + "remuxVideoCompleted": "Видео успешно перепаковано.", + "remuxVideoFailed": "Не удалось перепаковать видео.", + "remuxVideoPleaseSelectBoth": "Выберите и видео-, и аудиофайлы.", + "remuxVideoAssRequiresMkv": "Для субтитров ASS/SSA нужен контейнер MKV, чтобы сохранить все форматирование и стили.", + "remuxVideoSccRequiresMov": "Субтитры Scenarist (.scc) встраиваются как скрытые субтитры CEA-608, для которых нужен контейнер MOV или MPG. Выходной формат автоматически изменен на .mov.", + "remuxVideoSccNotWithWebM": "Для скрытых субтитров Scenarist (.scc) нужен контейнер MOV, который не может содержать видео VP9/AV1 из файла .webm. Удалите файл .scc или преобразуйте его в другой формат субтитров.", + "remuxVideoMpgMaxTwoCaptionTracks": "Файл .mpg может содержать не более двух дорожек скрытых субтитров (CC1 и CC3) - удалите файлы субтитров, чтобы осталось один или два.", + "remuxVideoMccRequiresMpg": "Субтитры MacCaption (.mcc) встраиваются как скрытые субтитры CEA-608/708 в видео MPEG-2, для чего нужен контейнер MPG. Выходной формат автоматически изменен на .mpg.", + "remuxVideoMpgReencodeVideoX": "Для скрытых субтитров в файле .mpg нужно видео MPEG-2, а видео - {0}. Перекодировать видео в MPEG-2? Это займет больше времени и немного снизит качество.", + "remuxVideoFileHasNoAudioX": "В \"{0}\" нет аудиодорожки, поэтому его нельзя использовать как источник аудио.", + "remuxVideoAddingClosedCaptions": "Добавление скрытых субтитров...", + "remuxVideoSelectAudioTrack": "Выберите аудиодорожку", + "remuxVideoSelectAudioTrackFor": "Выберите аудиодорожку для {0}", + "remuxVideoSelectAudioTrackPrompt": "Файл '{0}' содержит аудиодорожек: {1}. Выберите аудиодорожку для перепаковки:", + "remuxVideoVideoFile": "видео", + "remuxVideoAudioFile": "аудио", + "remuxVideoAudioFiles": "Аудиофайлы:", + "remuxVideoSubtitleFiles": "Файлы субтитров (необязательно, отключаемые субтитры):", + "remuxVideoSelectAudioTrackDotDotDot": "Выбрать аудиодорожку...", + "remuxVideoFilesX": "Файлов: {0}", + "remuxVideoMixAudio": "Смешать аудио в одну дорожку", + "remuxVideoVolumePercent": "Громкость %:", + "remuxVideoVolumeX": "Громкость {0}%", + "remuxVideoFastStart": "Оптимизировать для потоковой передачи (перенести индекс в начало - медленнее)", "embedSubtitlesDotDotDot": "Добавить/удалить встроенные субтитры...", "generateTimeCodes": "Создать временные коды", "checkeredImage": "Клетчатое изображение", @@ -2645,6 +3099,34 @@ "cutVideoSplitSegments": "Сохранить сегменты по отдельности", "cutVideoAlsoCutSubtitle": "Также обрезать субтитры", "cutVideoSubtitleFileGeneratedX": "Сгенерирован файл субтитров: {0}", + "cutVideoTransition": "Переход между сегментами", + "cutVideoTransitionDurationSeconds": "Длительность (секунды)", + "cutVideoFadeInSeconds": "Появление из черного в начале (секунды)", + "cutVideoFadeOutSeconds": "Затемнение в черный в конце (секунды)", + "cutVideoPreviewTransition": "Просмотр перехода", + "cutVideoPreviewTransitionTitle": "Предпросмотр перехода", + "cutVideoPreviewNeedsJoin": "Нет стыка для просмотра - добавьте хотя бы два сегмента для объединения или сегмент, вырезаемый из середины.", + "cutVideoTransitionInfoX": "Каждый переход перекрывает соединяемые части, поэтому результат становится короче на {0} секунд на каждый стык. Тайминг обрезанных субтитров корректируется соответственно.", + "cutVideoTransitionFade": "Растворение", + "cutVideoTransitionFadeBlack": "Через черный", + "cutVideoTransitionFadeWhite": "Через белый", + "cutVideoTransitionFadeGrays": "Через серый", + "cutVideoTransitionDissolve": "Наплыв", + "cutVideoTransitionWipeLeft": "Шторка влево", + "cutVideoTransitionWipeRight": "Шторка вправо", + "cutVideoTransitionWipeUp": "Шторка вверх", + "cutVideoTransitionWipeDown": "Шторка вниз", + "cutVideoTransitionSlideLeft": "Сдвиг влево", + "cutVideoTransitionSlideRight": "Сдвиг вправо", + "cutVideoTransitionSlideUp": "Сдвиг вверх", + "cutVideoTransitionSlideDown": "Сдвиг вниз", + "cutVideoTransitionSmoothLeft": "Плавно влево", + "cutVideoTransitionSmoothRight": "Плавно вправо", + "cutVideoTransitionCircleOpen": "Раскрытие круга", + "cutVideoTransitionCircleClose": "Закрытие круга", + "cutVideoTransitionRadial": "Радиальный", + "cutVideoTransitionPixelize": "Пикселизация", + "cutVideoTransitionBlur": "Размытие", "importCurrentSubtitle": "Импортировать текущий субтитр", "addRemoveEmbeddedSubtitlesTitle": "Добавить/удалить встроенные субтитры", "addRemoveEmbeddedSubtitlesMp4Title": "Добавить/удалить встроенные субтитры (MP4)", @@ -2660,6 +3142,7 @@ "embeddedTrackGeneratingVideoXY": "Создание видео... {0}% {1}", "embeddedTrackGeneratingVideo": "Создание видео...", "mp4FilesFilter": "MP4-файлы", + "embeddedTracksPickVideoHint": "Выберите видеофайл или перетащите его сюда, чтобы увидеть его дорожки субтитров.", "viewMatroskaTrackX": "Просмотр дорожки Matroska - {0}", "resolutionSeparator": "x", "openFromUrlTitle": "Открыть видеофайл по URL-адресу", @@ -2706,12 +3189,15 @@ "reEncodeGeneratingDone": "Создание завершено", "reEncodeGeneratedFilesX": "Сгенерированные файлы ({0}):", "reEncodeFfmpegParameters": "Параметры ffmpeg", + "analyzingVideoX": "Анализ видео... {0}% {1}", + "analyzingVideoXofY": "Анализ видео {0}/{1}... {2}% {3}", + "backgroundImageFileNotSelected": "Файл фонового изображения не выбран", + "backgroundImageFileDoesNotExistX": "Файл фонового изображения не существует: {0}", "voices": "Голоса", "presets": "Предустановки", "talkerX": "Говорящий {0}", "baseModel": "Базовая модель", "turboModel": "Турбо-модель", - "ttsUpdateX": "Обновление {0}", "ttsEngineNotInstalled": "{0} не установлен", "ttsEngineUpdateAvailable": "{0} - доступно обновление", "ttsEngineTooOldUpdateRequired": "{0} слишком старый - требуется обновление", @@ -2734,9 +3220,17 @@ "cloneVoiceTo": "Клонировать голос в", "cloneVoiceNameTitle": "Имя нового голоса", "cloneVoiceExtractFailed": "Не удалось извлечь аудио для выбранной строки.", + "lineXHasNoDuration": "У строки {0} нет длительности (время окончания не позже времени начала), поэтому извлекать нечего.", + "audioClipsSkippedLinesX": "Для этих строк не удалось извлечь аудио, поэтому они пропущены: {0}", "toggleShotChange": "Добавить/удалить смену плана", "resetWaveformZoomAndSpeed": "Сбросить масштаб и скорость осциллограммы", "showOriginalSubtitle": "Показывать оригинальные субтитры", + "timelineGroupTracksBy": "Группировать дорожки субтитров по", + "timelineShowThumbnails": "Показывать миниатюры видео", + "showSpeechOnly": "Показывать только речь (медленное создание)", + "isolatingSpeechForWaveformX": "Выделение речи для осциллограммы... {0}", + "isolatingSpeechForWaveformFailed": "Не удалось выделить речь для осциллограммы - см. журнал инструментов", + "speechOnlyWaveformReady": "Осциллограмма теперь показывает только речь", "showOnlyWaveform": "Показать только осциллограмму", "showOnlySpectrogram": "Показать только спектрограмму", "showWaveformAndSpectrogram": "Отобразить осциллограмму и спектрограмму", @@ -2769,6 +3263,7 @@ "adjustSelectedLinesAndForward": "Настроить выбранные и последующие строки", "toFrameRate": "В частоту кадров", "fromFrameRate": "Из частоты кадров", + "videoXFrameRateY": "{0} - {1} кадр/с", "adjustAllTimes": "Сдвинуть все тайм-коды (показывать раньше/позже)", "showEarlier": "Показать раньше", "showLater": "Показать позже", @@ -2778,7 +3273,8 @@ "syncPoints": "Точки синхронизации", "pointSync": "Точечная синхронизация", "pointSyncViaOther": "Точечная синхронизация через другие субтитры", - "syncPointCandidateInfo": "Строки, начинающиеся после {0}+ секунд тишины, выделены — они часто являются надежными точками синхронизации", + "syncPointCandidateAfterInfo": "Выделены строки, за которыми следует {0}+ секунд тишины - они часто служат надежными точками синхронизации", + "gapAfter": "Промежуток после", "adjustAllShortcuts": "Сочетания клавиш:\n\n• Shift + Влево/Вправо: сдвиг на 10 мс\n• Ctrl + Влево/Вправо: сдвиг на 100 мс\n• Alt + Влево/Вправо: сдвиг на 500 мс", "adjustAllShortcutsFrames": "Сочетания клавиш:\n\n• Shift + Влево/Вправо: сдвиг на 1 кадр\n• Ctrl + Влево/Вправо: сдвиг на 10 кадров\n• Alt + Влево/Вправо: сдвиг на 1 секунду", "offsetInSeconds": "Смещение в секундах", @@ -2798,6 +3294,7 @@ "lineMerge": "Объединение строк", "delayInSecondsBetweenRequests": "Задержка в секундах между запросами", "maxBytesPerRequest": "Максимальное количество байтов на запрос", + "keepMusicLinesUntranslated": "Не переводить строки в музыкальных символах (♪ ... ♪)", "promptText": "Текст промпта", "resetPromptToDefault": "Сбросить промпт по умолчанию", "translateEachLineSeparately": "Переводить каждую строку отдельно", @@ -2811,11 +3308,10 @@ "readyToTranslate": "Готов к переводу", "translating": "Перевод...", "translationComplete": "Перевод завершен", + "translateInPlaceNoOriginal": "Перевести на месте (не создавать оригинал)", "translationCancelled": "Перевод отменен", "swapLanguages": "Поменять местами исходный и целевой языки", "xIsAlreadyDownloadedReDownload": "{0} уже скачан. Скачать заново?", - "advancedDotDotDot": "Подробнее...", - "advancedSettings": "Расширенные настройки", "advancedSettingsSubtitle": "Параметры пакетов, контекста и сэмплирования для расширенного движка llama.cpp", "context": "Контекст", "synopsis": "Краткое содержание", @@ -2839,6 +3335,7 @@ "useOnlyExtraServerParametersHint": "Запускать llama-server с параметрами выше вместо собственных настроек Subtitle Edit — задаются только модель, хост и порт", "serverRunningAtX": "Сервер работает по адресу {0}", "customPromptHint": "Пользовательские инструкции ({0} = исходный язык, {1} = целевой язык); пусто = встроенный промпт", + "editTranslationHint": "Щелкните перевод выбранной строки, чтобы изменить его (Enter - сохранить, Escape - отмена)", "llamaCppDownloadEngineAndModelPrompt": "Для llama.cpp нужно скачать движок llama-server и модель перевода. Скачать сейчас?", "llamaCppDownloadEnginePrompt": "Для llama.cpp нужно скачать движок llama-server. Скачать сейчас?", "llamaCppDownloadModelPrompt": "Для llama.cpp нужно скачать выбранную модель перевода. Скачать сейчас?" @@ -2909,6 +3406,7 @@ "showGridLines": "Показывать линии таблицы", "showHorizontalLineAboveToolbar": "Отобразить горизонтальную линию над панелью инструментов", "bookmarkColor": "Цвет закладки", + "spellCheckHighlightColor": "Цвет выделения при проверке орфографии", "singleLineMaxLength": "Максимальная длина одной строки", "optimalCharsPerSec": "Оптимальное количество символов в секунду", "maxCharsPerSec": "Максимальное количество символов в секунду", @@ -2933,13 +3431,21 @@ "useDoNotBreakAfterList": "Не переносить строку после слов из списка", "newEmptyDefaultMs": "Длительность новых субтитров по умолчанию (мс)", "timeCodeUpDownStepMs": "Шаг увеличения/уменьшения времени (мс)", + "moveSelectedLinesStepMs": "Шаг сочетания клавиш для сдвига выбранных строк (мс)", + "moveStartEndStepMs": "Шаг сочетания клавиш для сдвига начала/конца (мс)", + "moveLinesShortenNeighbor": "Сдвиг строк: укорачивать предыдущую/следующую строку вместо перекрытия", "promptBeforeDelete": "Спрашивать перед удалением", "rememberPositionAndSize": "Запомнить положение и размер окна", + "titleBarFullFileName": "Показывать полный путь к файлу в заголовке окна", + "showRecentFiles": "Показывать недавние файлы (для повторного открытия)", "openLastFileOnStart": "Открывать последний файл при запуске", "autoSave": "Автосохранение (сохранение открытого файла во время редактирования)", "autoBackupOn": "Автоматическое резервное копирование", "autoBackupIntervalMinutes": "Интервал автоматического резервного копирования (минуты)", "autoBackupDeleteAfterDays": "Срок хранения автоматических резервных копий (дней)", + "settingsBackupOn": "Автоматическое резервное копирование настроек", + "settingsBackupIntervalDays": "Интервал резервного копирования настроек (дни, 0 = при каждом запуске)", + "settingsBackupMaxCount": "Хранить резервных копий настроек", "autoConvertToUtf8": "Автоматически преобразовывать кодировку в UTF-8 при открытии", "autoTrimWhiteSpace": "Автоматически обрезать пробелы", "removeBlankLinesWhenOpening": "Удалять пустые строки при открытии субтитров", @@ -2956,7 +3462,10 @@ "colorGapTooShort": "Подсвечивать слишком короткий промежуток", "errorBackgroundColor": "Цвет фона ошибки", "waveformDrawGridLines": "Рисовать линии сетки", + "waveformUseSkiaRenderer": "Использовать экспериментальный быстрый рендерер", "waveformCenterVideoPositionAlsoWhenPaused": "Центрировать позицию видео и во время паузы", + "waveformSelectCurrentSubtitleWhilePaused": "Выбирать текущий субтитр также во время паузы", + "waveformRememberZoom": "Запоминать масштаб (по горизонтали и вертикали)", "waveformShowToolbar": "Показать панель инструментов", "waveformShowToolbarEdit": "Редактировать панель инструментов...", "waveformShowToolbarEditLabel": "Элементы панели инструментов", @@ -3004,18 +3513,25 @@ "webVttUseXTimestampMap": "WebVTT: использовать X-TIMESTAMP-MAP (смещать временные коды при загрузке)", "assaAutoSetResolution": "ASSA: устанавливать разрешение (PlayResX/PlayResY) по видео при его открытии", "assaAutoSetResolutionPrompt": "ASSA: спрашивать перед изменением разрешения под видео (иначе пересчитывать автоматически)", - "showStopButton": "Показать кнопку \"Стоп\"", - "showFullscreenButton": "Показать кнопку полноэкранного режима", + "videoControls": "Элементы управления видео", + "videoControlsEdit": "Изменить элементы управления видео...", + "videoControlsPositionText": "Текст позиции / длительности", + "videoControlsVideoFileName": "Имя видеофайла", + "videoControlsPlayerName": "Название видеоплеера", "fullscreenHideControls": "Скрыть элементы управления видео в полноэкранном режиме", + "showSecondarySubtitleDialog": "Показывать окно стиля при открытии второго файла субтитров", + "rememberSecondarySubtitleFile": "Запоминать второй файл субтитров", "autoOpenVideoFile": "Автоматически открывать видеофайл при открытии субтитров", "downloadMpv": "Скачать mpv", "downloadVlc": "Скачать VLC", "allowSingleLetterShortcutsInTextbox": "Разрешить однобуквенные сочетания клавиш в поле текста", + "allowTextNavigationShortcutsInTextbox": "Разрешить сочетания клавиш на клавишах навигации по тексту (Ctrl+Left/Right, Home/End) в текстовом поле", "spellCheckEnglishTreatInApostropheAsIng": "Проверка орфографии: считать слова на 'in'' как 'ing' (только английский)", "goToLineNumberSetsVideoPosition": "Переход к строке также устанавливает позицию видео", "adjustAllTimesRememberLineSelectionChoice": "Сдвиг всех тайм-кодов: запоминать выбор строк", "mergeKeepEndTime": "Объединение строк: сохранять время конца (допускать перекрытие со следующим субтитром)", "mergeKeepEndTimeOnlyAssa": "Объединение строк: сохранять время конца только для \"Advanced Sub Station Alpha\"", + "mergeAutoBreak": "Объединение строк: автоперенос объединенного текста (если выключено, сохраняются собственные переносы строк)", "filesAndLogs": "Файлы и журналы", "showErrorLogFile": "Показать файл журнала ошибок", "showToolsLogFile": "Показать файл журнала инструментов", @@ -3024,6 +3540,7 @@ "showAssaLayer": "Показывать поле слоя ASSA", "waveformCursorColor": "Цвет курсора на осциллограмме", "waveformShotChangeColor": "Цвет смены плана на осциллограмме", + "waveformGridColor": "Цвет сетки осциллограммы", "waveformParagraphLeftColor": "Цвет левой границы субтитра на осциллограмме", "waveformParagraphRightColor": "Цвет правой границы субтитра на осциллограмме", "waveformFancyHighColor": "Цвет пиков осциллограммы (стиль \"Нарядный\")", @@ -3051,10 +3568,15 @@ "resetSyntaxColoring": "Сбросить подсветку синтаксиса", "resetWaveform": "Сбросить осциллограмму", "resetRules": "Сбросить правила", - "useFrameMode": "Использовать режим кадра (hh.mm.ss.ff)", + "timeCodeMode": "Режим тайм-кодов", + "timeCodeModeTime": "Время (hh:mm:ss,zzz)", + "timeCodeModeFrames": "Кадры (hh:mm:ss:ff)", + "timeCodeModeFrameNumbers": "Номера кадров", + "frameModeSnapToVideoFrames": "Хранить время по кадрам видео (а не по кадрам внутри каждой секунды)", "textBoxLimitNewLines": "Ограничить количество строк в текстовом поле субтитров", "mpvOpenGl": "libmpv - OpenGL", "mpvSoftwareRendering": "libmpv — Программная отрисовка (медленная)", + "mpvMetalRendering": "libmpv - Metal (экспериментально, требуется перезапуск)", "mpvWidRendering": "libmpv - отрисовка в нативное окно (Window ID)", "waveFormsAndSpectrogramFoldersContainsX": "Папки \"Waveforms\" и \"spectrogram\" содержат {0}", "deleteWaveformAndSpectrogramFoldersQuestion": "Удалить файлы \"Waveforms\" и \"Spectrogram\"?", @@ -3067,7 +3589,10 @@ "waveformExtractAudioSampleRate": "Частота дискретизации извлекаемого аудио", "waveformExtractAudioSampleRateOriginal": "Оригинальная (как в источнике)", "waveformExtractAudioBitRate": "Битрейт извлекаемого аудио (MP3/M4A)", + "ffmpegUseCenterChannelOnly": "Использовать только передний центральный канал для аудио 5.1 (FFmpeg)", "vlcWidRendering": "libVLC - отрисовка в нативное окно (Window ID)", + "ffmpegSoftwareRendering": "FFmpeg", + "downloadFfmpegLibs": "Скачать библиотеки FFmpeg (для видеоплеера FFmpeg)", "subtitleGridEnterKeyAction": "Действие клавиши Enter в таблице субтитров", "subtitleSingleClickAction": "Действие одинарного щелчка в таблице субтитров", "subtitleDoubleClickAction": "Действие двойного щелчка в таблице субтитров", @@ -3095,6 +3620,7 @@ "subtitleGridFormattingShowFormatting": "Показать форматирование", "subtitleGridFormattingShowTags": "Показать теги", "subtitleGridFormattingHideTags": "Скрыть теги", + "subtitleGridFormattingShowFormattingKeepTags": "Показать форматирование, оставить невизуальные теги", "waveformParagraphBackgroundColor": "Цвет фона субтитра на осциллограмме", "waveformParagraphSelectedBackgroundColor": "Цвет фона выбранного субтитра на осциллограмме", "waveformAllowOverlap": "Разрешить перекрытие (при перемещении/изменении размера)", @@ -3110,9 +3636,12 @@ "splitOddLinesAction": "Разделение нечетного числа строк", "ocrUseWordSplitList": "OCR: использовать список разбиения слов", "ocrGuessUnknownWords": "OCR: пытаться угадывать незнакомые слова", + "spellCheckRememberUseAlwaysList": "Проверка орфографии: запоминать список \"Всегда использовать\"", + "fixShortDisplayTimesAllowMoveStartTime": "Исправление типичных ошибок: исправление короткого времени показа может сдвигать время начала", "speechToTextSelectedLinesPromptFirstTimeOnly": "Распознавание речи для выбранных строк: спрашивать язык/движок только в первый раз", "multipleReplaceShowDotDotDotButtons": "Множественная замена: показывать кнопки контекстного меню", "gridFocusTextboxAfterInsertNew": "Таблица: переходить к полю текста после вставки нового субтитра", + "undoRedoGoToChangedLine": "Отмена/повтор: переходить к измененной строке (и позиции видео)", "musicSymbol": "Музыкальный символ", "musicSymbolsToReplace": "Музыкальные символы для замены (разделенные запятой)", "textToSpeechPromptMergeContinuationLines": "Синтез речи: предлагать объединять строки-продолжения", @@ -3121,6 +3650,7 @@ "useFocusedButtonBackgroundColor": "Использовать цвет фона для кнопки в фокусе", "focusedButtonBackgroundColor": "Цвет фона кнопки с фокусом", "forceCrLfOnSave": "Всегда сохранять с CR+LF (текстовые файлы субтитров)", + "linuxClipboardUseExternalTool": "Копировать текст через xclip/wl-copy (сохраняет символы с диакритикой)", "showFormatLimitWarning": "Предупреждать при сохранении, если строки превышают ограничения формата (например, SCC — 32 символа в строке)", "textBoxButtonShowAutoBreak": "Поле текста: показывать кнопку автопереноса", "textBoxButtonShowUnbreak": "Поле текста: показывать кнопку \"Убрать переносы\"", @@ -3132,12 +3662,18 @@ "waveformDoubleClickAction": "Действие двойного щелчка на осциллограмме (после действия одинарного щелчка)", "allSettings": "Все настройки", "uiScale": "Масштаб интерфейса (%)", + "fontScale": "Масштаб шрифта (%)", + "textSelectionOpacity": "Непрозрачность выделения текста (%)", "waveformToolbarItems": "Элементы панели инструментов осциллограммы", "matchIconColorToDarkTheme": "Подбирать цвет значков под цвет текста темной темы", "subtitlePreviewProperties": "Свойства предварительного просмотра субтитров", "usePositionFromSubtitleFile": "Использовать позицию из файла субтитров (TTML/PAC/EBU STL)", "marginIsPartOfSubtitleArea": "Отступ входит в область субтитров", "textJustify": "Выравнивать строки", + "deinterlace": "Деинтерлейсинг видео", + "deinterlaceOff": "Выкл.", + "deinterlaceAuto": "Авто (только чересстрочные кадры)", + "deinterlaceAlways": "Всегда", "pixelWidthInfo": "Зелёные линии = предел максимальной ширины | Красная зона = текст превышает предел", "spellCheckEngineHunSpelll": "Hunspell", "spellCheckEngineMsWord": "MS Word", @@ -3146,7 +3682,16 @@ "waveformColorTheme": "Цветовая тема:", "waveformSaveAsCustomTheme": "Сохранить как пользовательскую тему...", "waveformLoadThemeDotDotDot": "Загрузить тему...", - "waveformExportThemeDotDotDot": "Экспорт темы..." + "waveformExportThemeDotDotDot": "Экспорт темы...", + "waveformSaveCustomThemeTitle": "Сохранить свою тему", + "waveformThemeFileType": "Тема осциллограммы Subtitle Edit", + "waveformThemeHighContrast": "Высокий контраст", + "waveformThemeOceanBlue": "Синий океан", + "waveformThemeWarmSunset": "Теплый закат", + "waveformThemeForest": "Лес", + "waveformThemeMidnightPurple": "Полуночный пурпур", + "waveformThemeRetroAmber": "Ретро-янтарь", + "waveformThemeIce": "Лед" }, "shortcuts": { "searchShortcuts": "Поиск сочетаний клавиш...", @@ -3157,11 +3702,34 @@ "categoryVideo": "Видео", "categorySync": "Синхронизация", "categoryAi": "ИИ", + "categoryCustom": "Пользовательские", + "editCustomShortcut": "Изменить пользовательское сочетание", + "customShortcutTextBoxKeyHint": "Используйте здесь клавишу с Ctrl/Alt - обычная клавиша перестанет вводить текст в текстовом поле.", + "customShortcutNumberX": "Пользовательское сочетание №{0}", + "customShortcutNumberXY": "Пользовательское сочетание №{0}: {1}", + "customShortcutStep": "Шаг", + "customShortcutSteps": "Шаги", + "customShortcutStepRunCommand": "Выполнить команду", + "customShortcutStepInsertText": "Вставить текст", + "customShortcutStepReplace": "Найти и заменить", + "customShortcutInsertAtCursor": "В позиции курсора", + "customShortcutInsertAtStart": "В начале текста", + "customShortcutInsertAtEnd": "В конце текста", + "customShortcutSummaryRunCommandX": "Выполнить: {0}", + "customShortcutSummaryInsertXY": "Вставить \"{0}\" ({1})", + "customShortcutSummaryReplaceXY": "Заменить \"{0}\" на \"{1}\"", + "customShortcutSummaryReplaceRegexXY": "Заменить регулярное выражение \"{0}\" на \"{1}\"", + "customShortcutPickCommand": "Выберите команду для выполнения.", + "customShortcutEnterText": "Введите текст для вставки.", + "customShortcutEnterFind": "Введите текст для поиска.", + "customShortcutInvalidRegexX": "Неверное регулярное выражение: {0}", + "customShortcutStepsHint": "Шаги выполняются сверху вниз. Текстовые шаги работают с выбранными строками; команды, открывающие окно, продолжаются после его закрытия.", "activeIn": "Действует в", "activeInEverywhere": "Везде", "generalMergeSelectedLinesAndUnbreak": "Объединить выбранные строки и убрать переносы", "generalMergeSelectedLinesAndUnbreakCjk": "Объединить выбранные строки и убрать переносы без пробела (CJK)", "generalUnbreakNoSpaceCjk": "Убрать переносы без пробела (CJK)", + "generalToggleBreak": "Переключить перенос/без переноса", "generalMergeSelectedLinesBilingual": "Объединить выбранные строки (двуязычно)", "generalToggleTranslationMode": "Переключить режим перевода", "generalToggleTranslationAndOriginalInPreviews": "Переключить перевод/оригинал в предпросмотре видео/аудио", @@ -3178,10 +3746,12 @@ "generalToggleItalic": "Вкл./выкл. курсив", "generalToggleBold": "Вкл./выкл. полужирный", "generalToggleUnderline": "Вкл./выкл. подчеркивание", + "generalToggleBox": "Переключить рамку (EBU STL)", "fileOpen": "Открыть", "fileOpenKeepVideo": "Открыть (сохранить видео)", "fileSave": "Сохранить", "fileSaveAs": "Сохранить как", + "fileSaveOriginal": "Сохранить оригинал", "fileCompare": "Сравнить", "fileStatistics": "Статистика", "fileImportPlainText": "Импорт простого текста", @@ -3193,6 +3763,7 @@ "fillSelectedLinesWithClipboard": "Заполнить выделенные строки текстом из буфера обмена", "listInverseSelection": "Инвертировать выделение", "listDeleteSelection": "Удалить выделение", + "deleteSelectionEverywhere": "Удалить выделение (везде)", "rippleDeleteSelection": "Удалить выделение со сдвигом (ripple delete)", "assigned": "Назначено", "unassigned": "Не назначено", @@ -3201,6 +3772,7 @@ "detectKey": "Определить клавишу", "control": "Ctrl", "alt": "Alt", + "altGr": "AltGr", "win": "Win", "shift": "Shift", "controlMac": "⌃", @@ -3211,6 +3783,11 @@ "resetShortcutsDetail": "Вы хотите сбросить все сочетания клавиш до значений по умолчанию?", "togglePlayPause": "Переключить воспроизведение/паузу", "toggleLockTimeCodes": "Вкл./выкл. блокировку временных кодов", + "toggleTimeCodeMode": "Переключить режим тайм-кодов (время/кадры/номера кадров)", + "toggleScreenPrivacy": "Переключить защиту экрана (скрыть имена файлов, затем и тексты)", + "screenPrivacyFileNamesHidden": "Защита экрана: имена файлов скрыты", + "screenPrivacyFileNamesAndTextsHidden": "Защита экрана: имена файлов и тексты скрыты", + "screenPrivacyOff": "Защита экрана: выкл.", "duplicateSelectedLines": "Дублировать выбранные строки", "sourceView": "Исходный текст", "addOrEditBookmark": "Добавить или отредактировать закладку", @@ -3229,6 +3806,7 @@ "moveFirstWordFromNextLineUpCurrentSubtitle": "Перенести первое слово следующей строки вверх (текущий субтитр)", "moveTextFromCursorToNextAndGoToNext": "Перенести текст после курсора в следующий субтитр и перейти к нему", "moveTextFromCursorToNextAndGoToNextAndPlay": "Перенести текст после курсора в следующий субтитр, перейти к нему и воспроизвести", + "moveTextFromCursorToNextAndGoToNextAndPlayAndPause": "Перенести текст после курсора в следующий субтитр, перейти к нему, воспроизвести и остановиться в конце", "breakAtFirstSpaceFromCursor": "Перенос строки по первому пробелу от курсора", "breakAtFirstSpaceFromCursorAndGoToNext": "Перенос строки по первому пробелу от курсора и переход к следующему", "toggleFocusGridAndWaveform": "Переключить фокус между таблицей субтитров и осциллограммой/спектрограммой", @@ -3258,6 +3836,7 @@ "listViewColumnTextUp": "Столбец, текст вверх", "listViewColumnTextDown": "Столбец, текст вниз", "autoTranslateSelectedLines": "Автоперевод выбранных строк...", + "autoTranslateSelectedLinesNoPrompt": "Автоперевод выбранных строк (без запроса, последний движок/языки)", "setAssaResolution": "Установить разрешение ASSA (PlayResX/PlayResY)", "setShortcutForX": "Установить сочетание клавиш для \"{0}\"", "commandFileNewKeepVideo": "Новый (сохранить видео)", @@ -3312,6 +3891,7 @@ "textBoxItalic": "Поле текста: курсив", "textBoxBold": "Поле текста: полужирный", "textBoxUnderline": "Поле текста: подчеркивание", + "textBoxBox": "Рамка в текстовом поле (EBU STL)", "resetWaveformZoomAndSpeed": "Сбросить масштаб осциллограммы и скорость воспроизведения", "togglePlaybackSpeed": "Переключить скорость воспроизведения", "playbackSpeedSlower": "Медленнее воспроизведение", @@ -3323,6 +3903,7 @@ "seekSilenceForward": "Искать тишину вперед", "waveformGuessStart": "Подобрать время начала по осциллограмме", "waveformGuessEnd": "Подобрать время конца по осциллограмме", + "waveformGuessStartAndEnd": "Определить время начала и окончания по осциллограмме", "setVideoPositionCurrentSubtitleStart": "Установить позицию видео на начало текущей строки", "goToSubtitlePositionAndPause": "Перейти к позиции субтитра и поставить на паузу", "setVideoPositionCurrentSubtitleEnd": "Установить позицию видео на конец текущей строки", @@ -3336,6 +3917,14 @@ "surroundWith": "Обрамить...", "surroundWithXY": "Обрамить {0}/{1}", "surroundWithNumberX": "Обрамить #{0}", + "surroundWithBehavior": "Поведение", + "surroundWithBehaviorToggle": "Переключать (добавить или удалить, если есть)", + "surroundWithBehaviorAdd": "Добавлять (каждый раз)", + "surroundWithBehaviorRemove": "Удалять (все)", + "surroundWithBehaviorRemoveOnce": "Удалять (по одному за раз)", + "surroundWithWorksOn": "Применяется к", + "surroundWithScopeSelectionOrText": "Выделение, иначе весь текст", + "surroundWithScopeEachLine": "Каждая строка", "searchVia": "Поиск по", "searchViaX": "Поиск по {0}", "searchViaNumberX": "Поиск по #{0}", @@ -3363,6 +3952,11 @@ "copySubtitlePathToClipboard": "Скопировать путь к субтитрам в буфер обмена", "copySubtitleOriginalPathToClipboard": "Скопировать путь к оригиналу субтитров в буфер обмена", "focusTextBox": "Перейти к полю текста", + "focusSubtitleListView": "Фокус на список субтитров", + "focusWaveform": "Фокус на осциллограмму", + "focusOriginalTextBox": "Фокус на текстовое поле оригинала", + "textBoxGoToStart": "Текстовое поле: перейти в начало", + "textBoxGoToEnd": "Текстовое поле: перейти в конец", "sortByNumber": "Сортировать по номеру", "sortByStartTime": "Сортировать по времени \"Начало\"", "sortByEndTime": "Сортировать по времени \"Конец\"", @@ -3400,9 +3994,9 @@ "selectionToSentenceCase": "Поле текста: выделенное — как в предложении", "selectionToggleCasing": "Поле текста: переключить регистр (ВЕРХНИЙ -> нижний -> Как В Заголовке)", "googleIt": "Погуглить (выделенный текст)", - "setActorXY": "Набор акторов {0}: {1}", + "setActorX": "Задать актера: {0}", "setNewActor": "Задать нового актера...", - "setActorSlotXTitle": "Слот актора {0}", + "setStyleX": "Задать стиль: {0}", "toggleDialogDashes": "Вкл./выкл. тире диалога", "mergeWithLineBeforeAsDialog": "Объединить с предыдущей строкой как диалог", "mergeWithLineAfterAsDialog": "Объединить со следующей строкой как диалог", @@ -3413,7 +4007,23 @@ "moveStartOneFrameBackKeepGapPrev": "Сдвинуть начало на один кадр назад (сохранять промежуток до предыдущего, если он близко)", "moveStartOneFrameForwardKeepGapPrev": "Сдвинуть начало на один кадр вперед (сохранять промежуток до предыдущего, если он близко)", "moveEndOneFrameBackKeepGapNext": "Сдвинуть конец на один кадр назад (сохранять промежуток до следующего, если он близко)", - "moveEndOneFrameForwardKeepGapNext": "Сдвинуть конец на один кадр вперед (сохранять промежуток до следующего, если он близко)" + "moveEndOneFrameForwardKeepGapNext": "Сдвинуть конец на один кадр вперед (сохранять промежуток до следующего, если он близко)", + "moveStartXMsBack": "Сдвинуть начало на X мс назад (X задается в настройках, также в визуальной синхронизации)", + "moveStartXMsForward": "Сдвинуть начало на X мс вперед (X задается в настройках, также в визуальной синхронизации)", + "moveEndXMsBack": "Сдвинуть конец на X мс назад (X задается в настройках, также в визуальной синхронизации)", + "moveEndXMsForward": "Сдвинуть конец на X мс вперед (X задается в настройках, также в визуальной синхронизации)", + "moveSelectedLinesXMsBack": "Сдвинуть выбранные строки на X мс назад (X задается в настройках)", + "moveSelectedLinesXMsForward": "Сдвинуть выбранные строки на X мс вперед (X задается в настройках)", + "moveSelectedLinesAndForwardXMsBack": "Сдвинуть выбранные и все последующие строки на X мс назад (X задается в настройках)", + "moveSelectedLinesAndForwardXMsForward": "Сдвинуть выбранные и все последующие строки на X мс вперед (X задается в настройках)", + "moveAllLinesXMsBack": "Сдвинуть все строки на X мс назад (X задается в настройках)", + "moveAllLinesXMsForward": "Сдвинуть все строки на X мс вперед (X задается в настройках)", + "moveSelectedLinesCustomMsBack": "Сдвинуть выбранные строки на заданное число миллисекунд ({0:#,###,##0}) назад, {1}", + "moveSelectedLinesCustomMsForward": "Сдвинуть выбранные строки на заданное число миллисекунд ({0:#,###,##0}) вперед, {1}", + "moveSelectedLinesAndForwardCustomMsBack": "Сдвинуть выбранные и все последующие строки на заданное число миллисекунд ({0:#,###,##0}) назад, {1}", + "moveSelectedLinesAndForwardCustomMsForward": "Сдвинуть выбранные и все последующие строки на заданное число миллисекунд ({0:#,###,##0}) вперед, {1}", + "moveAllLinesCustomMsBack": "Сдвинуть все строки на заданное число миллисекунд ({0:#,###,##0}) назад, {1}", + "moveAllLinesCustomMsForward": "Сдвинуть все строки на заданное число миллисекунд ({0:#,###,##0}) вперед, {1}" }, "wordLists": { "title": "Списки слов", @@ -3458,7 +4068,11 @@ "applyToSelectedLinesX": "Выбранные строки ({0})", "applyToAllLinesX": "Все строки ({0})", "updateAllXAvailable": "Обновить все ({0})", - "updatingXOfY": "Обновление {0}/{1}..." + "updatingXOfY": "Обновление {0}/{1}...", + "doNotAskAgain": "Больше не спрашивать (можно изменить в \"Управление плагинами\")", + "applyPluginsTo": "Применять плагины к", + "askEachTime": "Спрашивать каждый раз", + "allLines": "Все строки" }, "help": { "aboutSubtitleEdit": "О Subtitle Edit", @@ -3502,6 +4116,8 @@ "nOcrDatabase": "база данных nOCR", "drawMode": "Режим рисования:", "addNewCharcter": "Добавить новый символ", + "addNOcrCharacterTitle": "Добавить символ nOCR для строки {0}, символ {1} из {2}, база данных \"{3}\"", + "addBinaryOcrCharacterTitle": "Добавить символ Binary OCR для строки {0}, символ {1} из {2}, база данных \"{3}\"", "lineIndexX": "Строка {0}", "inspectNOcrAdditions": "Проверить новые добавления nOCR", "ocrSelectedLines": "OCR выбранных строк", @@ -3550,6 +4166,8 @@ "binaryImageCompareInspectImageMatches": "\"Сравнение бинарных изображений\" — проверка совпадений изображений", "saveBlankTextTitle": "Сохранить пустой текст?", "saveBlankTextPrompt": "Сохранить пустой текст для изображения?", + "drawnLinesDoNotMatchTitle": "Линии не соответствуют изображению", + "drawnLinesDoNotMatchPrompt": "Некоторые нарисованные линии не соответствуют изображению (зеленые линии должны лежать на символе, красные - вне его), поэтому этот символ не будет распознан.\n\nВсе равно сохранить?", "yesAndNeverAskAgain": "Да, и больше никогда не спрашивать", "importTextFromSubtitleDotDotDot": "Импорт текста из субтитров...", "importTextFromSubtitleOverwritePrompt": "В некоторых строках уже есть текст OCR. Перезаписать существующий текст?", @@ -3608,9 +4226,26 @@ "crispEmbedSettingsTitle": "Настройки CrispEmbed", "crispEmbedDescription": "Локальный движок OCR с несколькими моделями — скачайте движок и модели здесь.", "trainNOcrDatabase": "Обучить базу nOCR...", + "optimizeNOcrDatabase": "Оптимизировать базу данных nOCR...", + "optimizeNOcrDatabaseQuestionXY": "Удалить из базы данных nOCR \"{0}\" линии, которые не помогают отличать символы от похожих?\n\nЭто уменьшает базу данных и ускоряет OCR, обычно с меньшим числом ошибок. Занимает до минуты.\n\nРезервная копия сохраняется как \"{1}\".", + "optimizingNOcrDatabaseXY": "Оптимизация базы данных nOCR... {0}/{1}", + "nOcrDatabaseOptimizedXYZ": "База данных nOCR \"{0}\" оптимизирована: {1:#,##0} -> {2:#,##0} линий.", "startTraining": "Начать обучение", "abortTraining": "Прервать обучение", "trainingOptions": "Параметры обучения", + "trainNOcrSubtitle": "Отрисовывает символы установленными шрифтами и обучает nOCR их формам. Выберите шрифты, используемые в ваших субтитрах.", + "xFontsSelected": "Выбрано: {0:#,##0}", + "subtitleFonts": "Шрифты субтитров", + "emptyDatabase": "Пустая база данных", + "startFromDatabase": "Начать с", + "startFromDatabaseHint": "Добавить обученные символы в копию существующей базы данных (рекомендуется) или начать с пустой", + "fontSizes": "Размеры шрифта", + "fontSizesHint": "Размеры шрифта в пикселях для отрисовки, через запятую, например \"30, 40\"", + "numberOfLineSegmentsHint": "Число проверяемых линий на символ - больше значит строже, но медленнее", + "enterDatabaseName": "Введите имя базы данных", + "trainingFontXSizeYCharacterZ": "Обучение \"{0}\" при {1} px: {2}", + "xLearned": "Изучено: {0:#,##0}", + "xSkipped": "Пропущено: {0:#,##0}", "charactersToTrain": "Символы для обучения", "letterCombinationsToTrain": "Сочетания букв, которые могут слиться в одно изображение", "importCharactersFromSubtitleFile": "Импорт символов из файла субтитров...", @@ -3637,6 +4272,8 @@ "drawZoomOut": "Уменьшить масштаб (Ctrl+-)", "drawResetView": "Сбросить вид (Ctrl+0)", "drawToggleGrid": "Переключить сетку (Ctrl+G)", + "drawTogglePreview": "Переключить предпросмотр (F9)", + "drawPreviewFailed": "Ошибка предпросмотра - требуется ffmpeg с libass (фильтр subtitles)", "drawCopyToClipboard": "Скопировать в буфер обмена (Ctrl+C)", "drawShapes": "Фигуры", "drawSelectedPoint": "Выбранная точка", @@ -3644,6 +4281,81 @@ "drawSelectedLayer": "Выбранный слой", "drawToolX": "Инструмент: {0}", "drawHelpText": "Щелчок — добавить точку • Enter/F8 — замкнуть фигуру • Shift+перетаскивание — панорама • Ctrl+прокрутка — масштаб", + "drawMoveToLayer": "Переместить на слой", + "drawFlipHorizontal": "Отразить по горизонтали", + "drawFlipVertical": "Отразить по вертикали", + "drawHideShape": "Скрыть фигуру", + "drawShowShape": "Показать фигуру", + "drawHideLayer": "Скрыть слой", + "drawShowLayer": "Показать слой", + "drawDeleteLayer": "Удалить слой", + "drawDeletePoint": "Удалить точку", + "drawCancelDrawing": "Отменить рисование", + "drawLayerX": "Слой {0}", + "drawImportSvg": "Импортировать изображение SVG...", + "drawRotateClockwise": "Повернуть на 90° по часовой стрелке", + "drawConvertToCurve": "Преобразовать сегмент в кривую", + "drawBackground": "Фон", + "drawPosition": "Позиция", + "drawZoom": "Масштаб", + "drawShapeTool": "Инструмент \"Фигура\"", + "drawColorPickerTool": "Взять цвет с фигуры или фона", + "drawShapeLibrary": "Библиотека фигур", + "drawShapeLibraryHint": "Перетащите по холсту, чтобы разместить фигуру, или щелкните для размера по умолчанию. Shift сохраняет пропорции.", + "drawAddToLibrary": "Добавить в библиотеку фигур...", + "drawRemoveFromLibrary": "Удалить из библиотеки фигур", + "drawShapeName": "Имя фигуры", + "drawCategorySpeechBubbles": "Речевые облачка", + "drawCategoryArrows": "Стрелки", + "drawCategoryBasicShapes": "Основные фигуры", + "drawCategorySymbols": "Символы", + "drawCategoryMyShapes": "Мои фигуры", + "drawShapeSpeechBubble": "Речевое облачко", + "drawShapeSpeechBubbleRight": "Речевое облачко, хвост вправо", + "drawShapeSpeechBubbleUp": "Речевое облачко, хвост вверх", + "drawShapeOvalBubble": "Овальное речевое облачко", + "drawShapeBoxBubble": "Прямоугольное речевое облачко", + "drawShapeThoughtBubble": "Облачко мыслей", + "drawShapeShoutBubble": "Облачко крика", + "drawShapeCaptionBox": "Плашка с подписью", + "drawShapeArrow": "Стрелка", + "drawShapeDoubleArrow": "Двойная стрелка", + "drawShapeChevron": "Шеврон", + "drawShapeCurvedArrow": "Изогнутая стрелка", + "drawShapeRoundedRectangle": "Скругленный прямоугольник", + "drawShapeTriangle": "Треугольник", + "drawShapeDiamond": "Ромб", + "drawShapePentagon": "Пятиугольник", + "drawShapeHexagon": "Шестиугольник", + "drawShapeOctagon": "Восьмиугольник", + "drawShapeStar": "Звезда", + "drawShapeRing": "Кольцо", + "drawShapePlus": "Плюс", + "drawShapeBanner": "Баннер", + "drawShapeHeart": "Сердце", + "drawShapeCheck": "Галочка", + "drawShapeMusicNote": "Нота", + "drawShapeMusicNotes": "Ноты", + "drawShapeLightning": "Молния", + "drawShapeCloud": "Облако", + "drawShapeMoon": "Луна", + "drawShapeBadge": "Значок", + "drawSize": "Размер", + "drawLayer": "Слой", + "drawBackgroundVideoFrame": "Кадр видео (текущая позиция)", + "drawBackgroundVideoFrameAt": "Кадр видео в позиции...", + "drawBackgroundImage": "Файл изображения...", + "drawBackgroundNone": "Без фона", + "drawBackgroundStretch": "Растянуть изображение на холст", + "drawBackgroundOpacity": "Непрозрачность фона", + "drawBackgroundVideoFrameFailed": "Не удалось получить кадр видео в позиции {0}.", + "drawImages": "Изображения", + "drawConvertToLine": "Преобразовать сегмент в линию", + "drawConvertShapeToCurves": "Преобразовать все линии в кривые", + "drawConvertShapeToLines": "Преобразовать все кривые в линии", + "drawRotateCounterClockwise": "Повернуть на 90° против часовой стрелки", + "drawSvgImages": "Изображения SVG", + "drawSvgNoShapes": "В файле SVG не найдено залитых фигур или обводок.", "progressBarTitle": "Индикатор прогресса ASSA", "progressBarSettings": "Индикатор прогресса", "mouseOverColor": "Цвет при наведении мыши", @@ -3740,13 +4452,26 @@ "graphics": "Графика", "copyToStorageStyles": "Копировать в сохраненные стили", "copyToFileStyles": "Копировать в стили файла", + "styleXAlreadyExistsInStorage": "Стиль с именем \"{0}\" уже существует в сохраненных стилях.\n\nПерезаписать его или оставить оба (копия получит новое имя)?", + "styleXAlreadyExistsInFile": "Стиль с именем \"{0}\" уже существует в файле.\n\nПерезаписать его или оставить оба (копия получит новое имя)?", + "styleNameCannotBeEmpty": "Имя стиля не может быть пустым.", + "styleNameXIsUsedMoreThanOnce": "Имя стиля \"{0}\" используется в файле более чем одним стилем.", + "overwrite": "Перезаписать", + "keepBoth": "Оставить оба", + "doThisForAllConflictingStyles": "Сделать так для всех конфликтующих стилей", "setStyleAsDefault": "Установить стиль по умолчанию", "takeUsagesFromDotDotDot": "Перенести использование из...", "replaceStyleWithDotDotDot": "Заменить стиль на...", "noAttachmentsFound": "В выбранном файле ASSA вложения не найдены.", "deleteStyleQuestion": "Удалить стиль?", "deleteStylesQuestion": "Удалить стили?", + "deleteXStylesQuestion": "Вы хотите удалить стили ({0})?", + "deleteXStylesFromStorageQuestion": "Вы хотите удалить стили ({0}) из сохраненных стилей?", + "deleteStyleXFromStorageQuestion": "Вы хотите удалить стиль \"{0}\" из сохраненных стилей?", + "deleteStyleXFromFileQuestion": "Вы хотите удалить стиль \"{0}\" из текущего файла?", "openStyleImportFile": "Открыть файл субтитров для импорта стилей", + "styleImportFiles": "Файлы стилей (.ass, .sty, категории Subtitle Edit 4 .template)", + "ssaStyleImportFiles": "Файлы стилей (.ssa, .ass, .sty)", "primary": "Основной", "secondary": "Вторичный", "applyOverrideTags": "Применить теги переопределения", diff --git a/src/ui/Assets/Languages/Spanish.json b/src/ui/Assets/Languages/Spanish.json index c6d741b32e4..45ed785772d 100644 --- a/src/ui/Assets/Languages/Spanish.json +++ b/src/ui/Assets/Languages/Spanish.json @@ -2,7 +2,7 @@ "Title": "Subtitle Edit", "TranslatedBy": "Gemini", "CultureName": "es-ES", - "version": "v5.2.0-rc7", + "version": "v5.3.0", "General": { "AssistedMove": "Movimiento asistido", "AssistedMoveChooseMove": "Haga clic en una opción o pulse su número para mover palabras", @@ -71,6 +71,24 @@ "Abort": "Abortar", "Actor": "Actor", "Actors": "Actores", + "SetActorDotDotDot": "Establecer actor...", + "ActorPickerTitle": "Establecer actor", + "ActorPickerFilterHint": "Escriba para filtrar o escriba un nuevo nombre de actor", + "ActorPickerKeysHint": "Pulse un número o Enter para establecer el actor. Alt+Arriba/Abajo cambia el orden, Supr elimina el actor.", + "ActorPickerNewActorX": "Nuevo actor \"{0}\"", + "ActorPickerLinesSelectedX": "Líneas seleccionadas: {0}", + "SetStyleDotDotDot": "Establecer estilo...", + "StylePickerTitle": "Establecer estilo", + "StylePickerFilterHint": "Escriba para filtrar o escriba un nuevo nombre de estilo", + "StylePickerKeysHint": "Pulse un número (1-9, 0) para establecer el estilo en todas las líneas seleccionadas.", + "StylePickerNewStyleX": "Nuevo estilo \"{0}\"", + "StylePickerNewStyleInfo": "Pulse Enter para añadir este estilo con los ajustes predeterminados y aplicarlo a las líneas seleccionadas. Use \"Administrar estilos...\" para cambiar su fuente, colores y posición más adelante.", + "StylePickerCurrentStyleX": "Estilo actual: {0}", + "StylePickerLinesUsingStyleX": "Líneas que usan este estilo: {0}", + "StylePickerMarginsXYZ": "Izquierdo {0}, derecho {1}, vertical {2}", + "StylePickerSetStyle": "_Establecer estilo", + "StylePickerManageStyles": "Administrar estilos...", + "StylePickerManageStylesHint": "Cerrar esta ventana y abrir el administrador de estilos para editar fuentes, colores, posición y más", "Add": "Añadir", "AddDotDotDot": "Añadir...", "AddToNamesListCaseSensitive": "Añadir a la lista de nombres (sensible a mayúsculas)", @@ -79,7 +97,6 @@ "AdjustAlpha": "Ajustar alfa (transparencia)", "AdjustDisplayDuration": "Ajustar duración de visualización", "Adjustment": "Ajuste", - "Advanced": "Avanzado", "AdvancedSettings": "Ajustes avanzados", "After": "Después", "Alignment": "Alineación", @@ -115,7 +132,6 @@ "BatchMode": "Modo por lotes", "BdnXml": "BDN/xml", "Before": "Antes", - "BeforeX": "Antes de \"{0}\"", "Beginning": "Principio", "BluRaySup": "Blu-ray (sup)", "Bold": "Negrita", @@ -127,7 +143,6 @@ "BookmarkEdit": "Editar marcador", "BookmarkEditForSelectedLinesX": "Editar marcador para {0} líneas seleccionadas", "BookmarksList": "Listar marcadores", - "BorderColor": "Color del borde", "BorderStyle": "Estilo del borde", "BottomCenter": "Abajo-Centro", "BottomLeft": "Abajo-Izquierda", @@ -152,7 +167,6 @@ "ChangeCasing": "Cambiar mayúsculas/minúsculas", "ChangeFormatting": "Cambiar formato", "ChangeFrameRate": "Cambiar tasa de fotogramas", - "ChangeLanguageFilter": "Cambiar filtro de idioma...", "ChangeOnce": "Cambiar una vez", "ChangeSpeed": "Cambiar velocidad", "Character": "Carácter", @@ -177,7 +191,6 @@ "ConversionCancelledByUser": "Conversión cancelada por el usuario", "Convert": "Convertir", "Converted": "Convertido", - "ConvertingDotDotDot": "Convirtiendo...", "ConvertingXofYDotDoDot": "Convirtiendo {0:#,###,##0}/{1:#,###,##0}...", "Copy": "Copiar", "CopyImageToClipboard": "Copiar imagen al portapapeles", @@ -185,7 +198,6 @@ "CouldNotSaveFileXErrorY": "No se pudo guardar el archivo \"{0}\". Error: {1}", "Count": "Cuenta", "Cps": "Car./seg", - "CurrentSubtitle": "Subtítulo actual", "CurrentVideoPosition": "Posición actual del vídeo", "Custom": "Personalizado", "Cut": "Cortar", @@ -231,7 +243,6 @@ "Expand": "Expandir", "Export": "Exportar", "ExportDotDotDot": "Exportar...", - "ExportToX": "Exportar a {0}", "ExtendAfter": "Extender/acortar a la línea posterior", "ExtendBefore": "Extender/acortar a la línea anterior", "ExtendSelectedLinesToNextShotChangeOrNextSubtitle": "Extender líneas seleccionadas al siguiente cambio de plano (o siguiente subtítulo)", @@ -248,7 +259,6 @@ "FileExtension": "Extensión de archivo", "FileName": "Nombre de archivo", "FileNameX": "Nombre de archivo: {0}", - "FileNameXAndSize": "Nombre de archivo: {0} ({1})", "FileSaved": "Archivo guardado", "FileSavedToX": "Archivo guardado en {0}", "FileXAlreadyExists": "El archivo \"{0}\" ya existe.", @@ -258,8 +268,6 @@ "Find": "Buscar", "FindNext": "Buscar siguiente", "FindTextX": "Buscar texto - {0}", - "FiveHundredMilliseconds": "500 milisegundos", - "FiveSeconds": "Cinco segundos", "Fix": "Corregir", "FixCommonErrors": "Corregir errores comunes", "FixRightToLeft": "Corregir derecha a izquierda", @@ -283,14 +291,12 @@ "FoundXInLineYZ": "Encontrado \"{0}\" en la línea {1}, posición {2}", "FoundXMatches": "Encontrado {0} coincidencias", "FrameRate": "Tasa de fotogramas", - "FrameRateX": "Tasa de fotogramas: {0:0.0###}", "Frames": "Fotogramas", "From": "Desde", "FromCurrentVideoPosition": "desde la posición actual del vídeo", "FullScreen": "Pantalla completa", "Gap": "Espacio", "General": "General", - "GeneralText": "General", "Generate": "Generar", "GenerateImportShotChanges": "Generar/importar cambios de plano", "Generating": "Generando...", @@ -308,12 +314,8 @@ "Help": "Ayuda", "Hex": "Hex", "Hide": "Ocultar", - "HidePreview": "Ocultar vista previa", "History": "Historial", "Horizontal": "Horizontal", - "HourMinutesSecondsDecimalSeparatorMilliseconds": "Hora:min:seg{0}ms", - "HourMinutesSecondsFrames": "Hora:min:seg:fotogramas", - "Ip": "IP", "Id": "ID", "Image": "Imagen", "ImageSaved": "Imagen guardada", @@ -372,6 +374,7 @@ "MergeSelectedLinesDialog": "Combinar líneas seleccionadas como diálogo", "MergeWithLineAfterAndAutoBreak": "Combinar con línea posterior y división automática", "MergeWithLineAfterKeepBreaks": "Combinar con línea posterior (mantener saltos)", + "MergeWithLineAfterAndUnbreak": "Combinar con línea posterior y unir", "MergeWithLineBeforeAndAutoBreak": "Combinar con línea anterior y división automática", "MergeWithLineBeforeKeepBreaks": "Combinar con línea anterior (mantener saltos)", "MiddleCenter": "Medio-Centro", @@ -386,11 +389,11 @@ "MoveAllShotChangeOneFrameForward": "Mover todos los cambios de plano un fotograma adelante", "MultiMode": "Modo múltiple", "MoveDown": "Bajar", + "Increase": "Aumentar", + "Decrease": "Disminuir", "MoveUp": "Subir", "MultipleReplace": "Reemplazo múltiple", - "Mute": "Silenciar", "Name": "Nombre", - "Negative": "Negativo", "New": "Nuevo", "NewDotDotDot": "Nuevo...", "NewProfile": "Nuevo perfil", @@ -418,20 +421,15 @@ "OffsetY": "Desfase Y", "Ok": "_Aceptar", "OneFile": "Un archivo", - "OneHundredMilliseconds": "100 milisegundos", - "OneSecond": "Un segundo", "OneSecondBack": "Un segundo atrás", "OneSecondForward": "Un segundo adelante", - "OnlineVideoFeatureNotAvailable": "Función no disponible para vídeo en línea", "OpenContainingFolder": "Abrir carpeta contenedora", "OpenDictionaryFolder": "Abrir carpeta de diccionarios", "OpenFile": "Abrir archivo", "OpenImageBasedSubtitle": "Abrir subtítulo basado en imágenes", "OpenImageFile": "Abrir archivo de imagen", - "OpenOriginalSubtitleFile": "Abrir archivo de subtítulo original...", "OpenOriginalSubtitleFileTitle": "Abrir archivo de subtítulo original", "OpenOutputFolder": "Abrir carpeta de salida", - "OpenSubtitle": "Abrir subtítulo...", "OpenSubtitleFileTitle": "Abrir archivo de subtítulos", "OpenSubtitles": "Abrir subtítulos...", "OpenVideoFile": "Abrir archivo de vídeo...", @@ -444,11 +442,6 @@ "OutlineWidth": "Ancho del contorno", "OutputFolder": "Carpeta de salida", "OutputProperties": "Propiedades de salida", - "Overlap": "Superposición", - "OverlapNextX": "Superp. siguiente ({0:#;##0.###})", - "OverlapPreviousLineX": "Superp. línea ant. ({0:#;##0.###})", - "OverlapStartAndEnd": "Superponer inicio y fin", - "OverlapX": "Superposición ({0:#;##0.###})", "OverwriteExistingFiles": "Sobrescribir archivos existentes", "OverwriteFilesInFolderX": "Sobrescribir archivos en la carpeta: \"{0}\"", "OverwriteQuestion": "¿Sobrescribir?", @@ -461,6 +454,7 @@ "Percent": "Porcentaje", "PickLayer": "Establecer capa", "PickOllamaModel": "Elegir modelo Ollama", + "PickModel": "Elegir modelo", "LlamaCppUseRemoteServer": "Usar servidor externo (URL)", "PickOutputFolder": "Elegir carpeta de salida", "PickResolutionFromCurrentVideo": "Elegir resolución del vídeo actual", @@ -504,7 +498,6 @@ "Refresh": "Actualizar", "Region": "Región", "RegularExpression": "Expresión regular", - "RegularExpressionIsNotValid": "¡La expresión regular no es válida!", "RelativeToCurrentVideoPosition": "Relativo a la posición actual del vídeo", "Remove": "Eliminar", "RemoveAlignment": "Eliminar alineación", @@ -529,7 +522,7 @@ "Reset": "Restablecer", "ResizePanels": "Redimensionar paneles", "Resolution": "Resolución", - "ResolutionX": "Resolución: {0}", + "Retry": "Reintentar", "ReverseRightToLeftStartEnd": "Invertir inicio/fin RTL", "Right": "Derecha", "RightToLeft": "Derecha a izquierda", @@ -567,8 +560,6 @@ "SelectedlinesX": "Líneas seleccionadas: {0}", "Sensitivity": "Sensibilidad", "Separator": "Separador", - "SessionKey": "Clave de sesión", - "SessionKeyGenerate": "Generar nueva clave", "SetEnd": "Establecer fin", "SetEndAndGoToNext": "Establecer fin e ir al siguiente", "SetEndAddNewAndGoToNew": "Establecer fin, añadir nuevo e ir al nuevo", @@ -591,8 +582,9 @@ "ShadowColor": "Color de sombra", "ShadowWidth": "Ancho de sombra", "ShiftLineBy": "Desplazar línea en", + "ShotIn": "Entrada de plano", + "ShotOut": "Salida de plano", "Shortcut": "Atajo", - "ShortcutX": "Atajo: {0}", "Shortcuts": "Atajos", "Show": "Mostrar", "ShowActorColumn": "Mostrar columna \"Actor\"", @@ -603,17 +595,19 @@ "ShowStartColumn": "Mostrar columna \"Inicio\"", "ShowHideColumn": "Mostrar columna \"Ocultar\"", "ShowHistory": "Mostrar historial", + "ClearHistory": "Limpiar historial", + "RemoveFromHistory": "Eliminar del historial", "ShowLayerColumn": "Mostrar columna \"Capa\"", "ShowPreview": "Mostrar vista previa", "ShowShotChangesList": "Mostrar lista de cambios de plano", + "ShowShotInColumn": "Mostrar columna \"Entrada de plano\"", + "ShowShotOutColumn": "Mostrar columna \"Salida de plano\"", "ShowStyleColumn": "Mostrar columna \"Estilo\"", "ShowTeletext": "Mostrar columna de teletexto en la vista de lista", "ShowTimeCodes": "Mostrar códigos de tiempo", "ShowWpmColumn": "Mostrar columna \"Pal./min\"", "ShowPixelWidthColumn": "Mostrar columna \"Ancho píxeles\"", "Shrink": "Encoger", - "SingleBox": "Caja única", - "SingleLineLengths": "Longitud de línea única:", "SingleMode": "Modo único", "TextTotalLength": "Texto - longitud total", "TextSingleLineMaxLength": "Texto - longitud máx. de línea única", @@ -662,16 +656,16 @@ "Strikeout": "Tachado", "Style": "Estilo", "StyleExaggeration": "Exageración de estilo", - "StyleLanguage": "Estilo / Idioma", "Styles": "Estilos", "SubtitleFile": "Archivo de subtítulos", + "SubtitleFileChangedOutsideX": "El archivo \"{0}\" se ha modificado fuera de Subtitle Edit.\n\n¿Recargarlo y perder sus cambios?", "SubtitleFileName": "Nombre del archivo de subtítulos", "SubtitleFileSaved": "Archivo de subtítulos guardado", "SubtitleFileSavedToX": "Archivo de subtítulos guardado en {0}", "SubtitleFiles": "Archivos de subtítulos", "SubtitleFormats": "Formatos de subtítulos", "SubtitleLoadedX": "Subtítulo cargado: {0}", - "SubtitleXOfY": "Subtítulo {0} de {1}", + "SubtitleReloadedChangedOutsideX": "Subtítulo recargado (modificado fuera de Subtitle Edit): {0}", "Suffix": "Sufijo", "Suggestions": "Sugerencias", "Sync": "Sincronizar", @@ -683,7 +677,6 @@ "TeletextColor": "Color de teletexto", "TeletextLine": "Línea de teletexto", "TenHours": "10 horas", - "TenMilliseconds": "10 milisegundos", "Text": "Texto", "TextColor": "Color de texto", "TextFiles": "Archivos de texto", @@ -710,9 +703,6 @@ "TopRight": "Arriba-Derecha", "Top": "Arriba", "TotalAdjustmentX": "Ajuste total: {0}", - "TotalFramesX": "Fotogramas totales: {0:#;##0.##}", - "TotalLengthX": "Longitud total: {0}", - "TotalLengthXSplitLine": "Longitud total: {0} (¡dividir línea!)", "Translate": "Traducir", "TranslatePercentX": "Traduciendo: {0}%", "TranslateRow": "Traducir fila", @@ -721,6 +711,7 @@ "Type": "Tipo", "Unbreak": "Unir texto", "UnbreakLines": "Unir líneas", + "Undelete": "Recuperar", "Underline": "Subrayado", "Undo": "Deshacer", "Unknown": "Desconocido", @@ -740,13 +731,11 @@ "Usages": "Usos", "Use": "Usar", "UseAlways": "Usar siempre", - "UseLargerFontForThisWindow": "Usar fuente más grande para esta ventana", "UseOnce": "Usar una vez", "UseOutputFolder": "Usar carpeta de salida", "UseSourceFolder": "Usar carpeta de origen", "UseSourceResolution": "Usar resolución de origen", "UserName": "Nombre de usuario", - "UserNameAlreadyInUse": "Nombre de usuario ya en uso", "Vertical": "Vertical", "Video100MsBack": "Vídeo, 100 ms atrás", "Video100MsForward": "Vídeo, 100 ms adelante", @@ -760,14 +749,25 @@ "VideoCustom3ForwardX": "Vídeo, milisegundos personalizados ({0:#,###,##0}) adelante, 3", "VideoCustom4BackX": "Vídeo, milisegundos personalizados ({0:#,###,##0}) atrás, 4", "VideoCustom4ForwardX": "Vídeo, milisegundos personalizados ({0:#,###,##0}) adelante, 4", - "VideoEncodingX": "Codificación de vídeo: {0}", + "VideoCustom1BackAndPauseX": "Vídeo, milisegundos personalizados ({0:#,###,##0}) atrás y pausa, 1", + "VideoCustom1ForwardAndPauseX": "Vídeo, milisegundos personalizados ({0:#,###,##0}) adelante y pausa, 1", + "VideoCustom2BackAndPauseX": "Vídeo, milisegundos personalizados ({0:#,###,##0}) atrás y pausa, 2", + "VideoCustom2ForwardAndPauseX": "Vídeo, milisegundos personalizados ({0:#,###,##0}) adelante y pausa, 2", + "VideoCustom3BackAndPauseX": "Vídeo, milisegundos personalizados ({0:#,###,##0}) atrás y pausa, 3", + "VideoCustom3ForwardAndPauseX": "Vídeo, milisegundos personalizados ({0:#,###,##0}) adelante y pausa, 3", + "VideoCustom4BackAndPauseX": "Vídeo, milisegundos personalizados ({0:#,###,##0}) atrás y pausa, 4", + "VideoCustom4ForwardAndPauseX": "Vídeo, milisegundos personalizados ({0:#,###,##0}) adelante y pausa, 4", "VideoExtension": "Extensión de archivo de vídeo", "VideoFile": "Archivo de vídeo", "VideoFileGenerated": "Archivo de vídeo generado", "VideoFileGeneratedX": "Archivo de vídeo generado: \"{0}\"", + "VideoFilesGenerated": "Archivos de vídeo generados", + "XVideosGenerated": "{0} vídeos generados", + "XOfYVideosGenerated": "{0} de {1} vídeos generados", + "SavedInX": "Guardado en {0}", + "SavedInXFolders": "Guardado en {0} carpetas", "VideoFiles": "Archivos de vídeo", "VideoAndAudioFiles": "Archivos de vídeo y audio", - "VideoInformation": "Info. vídeo", "VideoOffset": "Desfase de vídeo", "VideoOneFrameBack": "Vídeo, un fotograma atrás", "VideoOneFrameForward": "Vídeo, un fotograma adelante", @@ -778,7 +778,6 @@ "VideoPlayer": "Reproductor de vídeo", "VideoPosition": "Posición del vídeo", "VideoResolution": "Resolución del vídeo", - "ViewX": "Ver {0}", "Visible": "Visible", "Voice": "Voz", "Volume": "Volumen", @@ -787,9 +786,7 @@ "WaveformCenterOnVideoPosition": "Centrar forma de onda en posición del vídeo", "WaveformPasteFromClipboard": "Pegar desde el portapapeles", "WaveformSpectrogram": "Forma de onda/espectrograma", - "WebServiceUrl": "URL del servicio web", "Width": "Ancho", - "WindowPositionAndSize": "Posición y tamaño de la ventana", "WordsPerMin": "Pal./min", "PixelWidth": "Ancho píxeles", "Wpm": "Pal./min", @@ -803,8 +800,6 @@ "NoBookmarksFound": "No se encontraron más marcadores", "XRequiresAnApiKey": "{0} requiere una clave API", "XRequiresAValidUrl": "{0} requiere una URL válida (p. ej. \"http://...\" o \"https://...\")", - "XSeconds": "{0:0.0##} segundos", - "XSubtitles": "{0:#,###,##0} subtítulos", "Yes": "Sí", "ZeroBytes": "0 bytes", "CopyToClipboard": "Copiar al portapapeles", @@ -826,10 +821,8 @@ "PlaybackSpeed": "Velocidad de reproducción", "HoursMinutesSecondsMilliseconds": "HH:MM:SS.FFF", "ImageFiles": "Archivos de imagen", - "OpenAiCompatibleStt": "STT compatible con OpenAI", "OpenAiCompatibleSttEndpoint": "URL del punto de conexión STT", "OpenAiCompatibleSttApiKey": "Clave de API", - "OpenAiCompatibleSttModel": "Modelo", "OpenAiCompatibleSttExtraHeaders": "Encabezados adicionales", "OpenAiCompatibleSttTimeout": "Tiempo de espera (segundos)", "OpenAiCompatibleSttLanguage": "Sugerencia de idioma", @@ -839,6 +832,9 @@ "DashScopeSttRegion": "Región", "DashScopeSttEnableWords": "Marcas de tiempo por palabra", "DashScopeSttRegionKeyHint": "Nota: las claves API de Alibaba Cloud Model Studio son específicas de cada región: asegúrese de que la región seleccionada coincide con la región donde se creó la clave API (China o Internacional).", + "GoogleCloudSttProjectId": "ID de proyecto (solo para Application Default Credentials)", + "GoogleCloudSttBucketName": "Bucket de almacenamiento (vacío = auto)", + "GoogleCloudSttDynamicBatching": "Procesamiento por lotes dinámico (unas 5 veces más barato, sin garantía de latencia)", "OnlineSttApiKeyMissing": "Se requiere una clave API. Introduzca su clave API e inténtelo de nuevo.", "OpenAiCompatibleSttAutoTranscribeOnAudioSelection": "Transcribir automáticamente la nueva selección de forma de onda mediante voz a texto", "OpenAiCompatibleSttStream": "Transmitir respuesta", @@ -848,11 +844,9 @@ "ConfigurationRequired": "Configuración requerida", "TranscriptionError": "Error de transcripción", "TranscriptionComplete": "Transcripción completada", - "TranscribingWithOpenAi": "Transcribiendo con el servicio de OpenAI...", "ProcessingResponse": "Procesando respuesta...", "UnauthorizedApiKey": "No autorizado: clave de API no válida o permisos insuficientes.", "RequestTimeout": "Tiempo de espera agotado: el servidor tardó demasiado en responder.", - "NoSegmentsReturned": "El servicio de transcripción no devolvió ningún segmento.", "OpenAiCompatibleSttUrlMissing": "Se requiere la URL del punto de conexión STT compatible con OpenAI. Introduzca una URL e inténtelo de nuevo.", "OpenAiCompatibleSttUrlNotResponding": "El punto de conexión STT compatible con OpenAI no respondió:\n{0}\n\nVerifique la URL y que el servidor esté en ejecución.", "TranscriptionFailed": "La transcripción falló", @@ -868,6 +862,9 @@ "CharactersPerSecond": "Car./segundo: {0}", "ChooseColumn": "Elegir columna", "ColumnPaste": "Pegado de columna", + "CommandPaletteTitle": "Paleta de comandos", + "CommandPaletteSearchPlaceholder": "Escriba para buscar comandos...", + "CommandPaletteNoCommandsFound": "No hay comandos coincidentes", "CreatedEmptyTranslation": "Creada traducción vacía desde subtítulo actual", "DeleteText": "Eliminar texto", "DeleteTextAndShiftCellsUp": "Eliminar texto y desplazar celdas arriba", @@ -906,18 +903,20 @@ "NumberOfLinesEvenlyDistributedX": "Se distribuyeron {0} líneas uniformemente", "OneLineCopiedFromOriginal": "Una línea copiada del subtítulo original", "OneLineCopiedToOriginal": "Una línea copiada al subtítulo original", + "OnlineOnlyVideo": "Vídeo solo en línea", + "OnlineOnlyVideoXSizeYDownloadAndOpen": "El archivo de vídeo \"{0}\" ({1}) solo está almacenado en línea, por ejemplo en Dropbox o iCloud Drive, y debe descargarse antes de poder abrirlo.\n\n¿Descargarlo y abrirlo ahora?", "OneLineMerged": "Una línea combinada", "OneLineSwitched": "Una línea intercambiada", "OverwriteExistingCells": "Sobrescribir celdas existentes", "OverwriteOrShiftCellsDown": "Sobrescribir/desplazar celdas abajo", "ParsingMatroskaFile": "Analizando archivo Matroska...", + "ReadingDvdSubtitles": "Leyendo subtítulos del DVD...", "PasteFromClipboardDotDotDot": "Pegar desde el portapapeles...", "RedoPerformed": "Rehacer realizado", "RedoPerformedXActionLeft": "Rehacer realizado (acciones restantes: {0})", "RemovedUnicodeControlCharactersX": "Eliminados caracteres de control Unicode en {0} líneas", "RemovedXBlankLines": "Eliminadas {0} líneas en blanco", "ReplacedXWithYCountZ": "Reemplazado \"{0}\" con \"{1}\" ({2} ocurrencias)", - "ReplacedXWithYInLineZ": "Reemplazado \"{0}\" con \"{1}\" en la línea {2}", "ReversedStartAndEndingsForRightToLeftX": "Invertido inicio y fin para derecha a izquierda en {0} líneas", "RuleProfileIsX": "El perfil de reglas es ahora \"{0}\"", "SaveLanguageFile": "Guardar archivo de idioma", @@ -925,7 +924,6 @@ "ShiftTextCellsDown": "Desplazar celdas de texto abajo", "SingleLineLength": "Longitud de línea: ", "SpeedIsNowX": "La velocidad es ahora \"{0}\"", - "SpellCheckResult": "Corrección ortográfica completada. \n\n• Palabras cambiadas: {0}\n• Palabras omitidas: {1}", "SubtitleGridFormattingX": "Formato de la lista: {0}", "SubtitleImportedFromMatroskaFile": "Subtítulo importado desde archivo Matroska", "TextDown": "Texto abajo", @@ -952,13 +950,14 @@ "TrimmedXLines": "Recortadas {0} líneas de subtítulos", "PastedXLinesOverSelectedLines": "Se pegaron {0} línea(s) sobre las líneas seleccionadas", "PastedXOfYLinesOverSelectedLines": "Se pegaron {0} de {1} línea(s) sobre las líneas seleccionadas; el resto no cupo en la selección", + "InsertedXLinesFromClipboard": "Se insertaron {0} línea(s) desde el portapapeles", "OpenOriginalDifferentNumberOfSubtitlesXY": "El archivo original no tiene el mismo número de subtítulos que el actual.\n\n• Subtítulos originales: {0}\n• Subtítulos actuales: {1}", - "ImportXMatchingOriginalLines": "¿Importar {0} subtítulos originales coincidentes?", "OriginalTextReadOnly": "Texto original (solo lectura)", "OriginalTextEditMode": "Texto original (modo de edición)", "OriginalIsReadOnlyNotSaved": "El subtítulo original es una referencia de solo lectura y no se guardó", "OriginalIsEmptyNotSaved": "El subtítulo original no tiene ningún texto y no se guardó", "OriginalIsReadOnlyReference": "El subtítulo original está abierto como referencia de solo lectura", + "CloseTranslationQuestionXY": "¿Cerrar la traducción \"{0}\"?\n\nEl subtítulo original \"{1}\" pasa a ser el subtítulo que edita. El archivo de traducción en sí no se modifica.", "AllowEditOfOriginalSubtitle": "Permitir la edición del subtítulo original", "ShowAllOriginalLinesX": "Mostrar todas las líneas originales ({0} no tienen correspondencia aquí)", "ShowAllOriginalLinesHint": "Las líneas sin correspondencia se muestran como filas adicionales, para que pueda ver lo que falta en la traducción.", @@ -993,6 +992,7 @@ "FileExportedInFormatXToY": "Archivo exportado en formato {0} a {1}", "FileExportedInFormatXToFileY": "Archivo exportado en formato \"{0}\" al archivo \"{1}\"", "FixedXLines": "Corregidas {0} líneas", + "ToolXChangedYLines": "{0}: {1} líneas cambiadas", "TranscriptionCompletedWithXLines": "Transcripción completada con {0} líneas", "OpenSubtitleFileX": "¿Abrir el archivo de subtítulos \"{0}\"?", "ReplacedXOccurrences": "Reemplazadas {0} ocurrencias", @@ -1056,14 +1056,15 @@ "MultipleReplace": "Reemplazo _múltiple...", "FillSelectedLinesWithClipboard": "Rellenar líneas seleccionadas con el texto del portapapeles", "GoToLineNumber": "_Ir al número de línea...", + "CommandPalette": "_Paleta de comandos...", "RightToLeftMode": "Modo d_erecha a izquierda", "ModifySelectionDotDotDot": "Modificar _selección...", "Tools": "_Herramientas", - "ToolsSelectedLines": "_Herramientas (líneas seleccionadas)", "AdjustDurations": "_Ajustar duraciones...", "ApplyDurationLimits": "Aplicar _límites de duración...", "BatchConvert": "Conversión por _lotes...", "BeautifyTimeCodes": "Embellecer códigos de tiempo...", + "ImproveTimeCodes": "Mejorar códigos de tiempo (alineación forzada)...", "BridgeGaps": "Unir _espacios...", "ApplyMinGap": "Aplicar espacio mín. entre subtítulos...", "ChangeCasing": "_Cambiar mayúsculas/minúsculas...", @@ -1202,6 +1203,9 @@ "SelectCurrentLineWhilePlayingHint": "Seleccionar subtítulo actual durante la reproducción {0}", "VideoPosition": "Posición del vídeo {0}", "VideoPositionTextBox": "Cuadro de texto de posición del vídeo {0}", + "ToolbarLineBreakX": "Salto de línea {0} (el resto de la barra de herramientas continúa en una nueva fila)", + "InitialText": "Texto inicial de la línea seleccionada", + "InitialTextHint": "El texto de la línea seleccionada tal como era cuando se seleccionó la línea", "HideWaveformToolbar": "Ocultar barra de herramientas {0}", "ResetZoomAndSpeed": "Restablecer zoom y velocidad de reproducción {0}", "RemoveBlankLines": "Eliminar líneas en blanco {0}", @@ -1215,6 +1219,15 @@ "TextPlayHint": "Reproducir el subtítulo actual y detenerse al final {0}", "TextPauseHint": "Pausar la reproducción {0}", "TextNextHint": "Reproducir el subtítulo siguiente y detenerse al final {0}", + "MoveSelectedLines": "Mover líneas seleccionadas (botones atrás/adelante)", + "MoveSelectedLinesAndFollowing": "Mover líneas seleccionadas y todas las siguientes (botones atrás/adelante)", + "MoveAllLines": "Mover todas las líneas (botones atrás/adelante)", + "MoveSelectedLinesBackHint": "Mover líneas seleccionadas {0} ms atrás {1}", + "MoveSelectedLinesForwardHint": "Mover líneas seleccionadas {0} ms adelante {1}", + "MoveSelectedLinesAndFollowingBackHint": "Mover líneas seleccionadas y todas las siguientes {0} ms atrás {1}", + "MoveSelectedLinesAndFollowingForwardHint": "Mover líneas seleccionadas y todas las siguientes {0} ms adelante {1}", + "MoveAllLinesBackHint": "Mover todas las líneas {0} ms atrás {1}", + "MoveAllLinesForwardHint": "Mover todas las líneas {0} ms adelante {1}", "GuessStartNoLineSelected": "Estimar inicio: ninguna línea seleccionada", "GuessStartTimeCodesLocked": "Estimar inicio: los códigos de tiempo están bloqueados", "GuessStartNoWaveform": "Estimar inicio: no hay forma de onda", @@ -1263,20 +1276,66 @@ "CompareOnlyInOneFile": "Solo en un archivo", "CompareTextOrTimeDifference": "Diferencia de texto/tiempo", "CompareNumberDifference": "Diferencia de número", - "IgnoreLineBreaks": "Ignorar saltos de línea", "IgnoreWhitespace": "Ignorar espacios en blanco", "IgnoreWhitespaceHint": "Las líneas que solo se diferencian en espacios, tabulaciones o saltos de línea no cuentan como distintas", "IgnoreFormatting": "Ignorar formato", - "OnlyLookForDifferencesInText": "Solo buscar diferencias en el texto", - "CannotCompareWithImageBasedSubtitles": "No se puede comparar con subtítulos basados en imágenes", - "StatisticsTitle": "Estadísticas", "IgnoreFormattingHint": "Las líneas que solo se diferencian en etiquetas de formato, como o {\\an8}, no cuentan como distintas", "ShowOnlyDifferencesInText": "Solo diferencias en texto", + "IgnoreNumbering": "Ignorar numeración", + "IgnoreNumberingHint": "Las líneas que solo difieren en su número no cuentan como diferentes", + "IgnoreTimeDifferenceMs": "Tolerancia de tiempo (ms)", + "IgnoreTimeDifferenceMsHint": "Los tiempos de inicio y fin que difieren en esta cantidad de milisegundos o menos no cuentan como diferentes", + "CompareDifferences": "Diferencias", + "CompareTextDifferences": "Diferencias de texto", + "CompareCurrent": "Actual", + "CompareReference": "Referencia", + "CompareEditable": "Editable", + "CompareEditableHint": "Los cambios hechos aquí vuelven al subtítulo al hacer clic en Aplicar", + "CompareReadOnly": "Solo lectura", + "CompareTakeFromReference": "Tomar texto y tiempos de la referencia", + "CompareInsertFromReference": "Insertar esta línea en el subtítulo actual", + "CompareDeleteFromCurrent": "Eliminar esta línea del subtítulo actual", + "CompareTakeText": "Tomar texto", + "CompareTakeTiming": "Tomar tiempos", + "CompareEditLine": "Editar línea", + "CompareEditHint": "Ctrl+Enter para guardar, Esc para cancelar", + "CompareEdited": "Editado", + "CompareOnePendingChange": "1 cambio pendiente", + "CompareXPendingChanges": "{0} cambios pendientes", + "CompareDiscardXChanges": "¿Descartar los {0} cambio(s) hechos en Comparar?", + "CompareXChangesApplied": "Comparar: {0} cambio(s) aplicado(s)", + "CompareChangeTextX": "#{0} texto de la referencia", + "CompareChangeTimingX": "#{0} tiempos de la referencia", + "CompareChangeTextAndTimingX": "#{0} texto y tiempos de la referencia", + "CompareChangeEditedX": "#{0} editado", + "CompareChangeInsertedX": "#{0} insertado", + "CompareChangeDeletedX": "#{0} eliminado", + "CompareSyncPoint": "Punto de sincronización", + "CompareSyncPointHint": "Punto de sincronización: estas dos líneas siempre se muestran como pareja, y las líneas de arriba y de abajo se alinean por separado", + "CompareSyncPickCurrent": "Punto de sincronización: usar esta línea actual", + "CompareSyncPickReference": "Punto de sincronización: usar esta línea de referencia", + "CompareSyncRemove": "Eliminar punto de sincronización", + "CompareClearXSyncPoints": "Borrar puntos de sincronización ({0})", + "CompareSyncCurrentPickedX": "Actual #{0} elegida como punto de sincronización: seleccione la línea de referencia correspondiente y haga clic en Sincronizar", + "CompareSyncReferencePickedX": "Referencia #{0} elegida como punto de sincronización: seleccione la línea actual correspondiente y haga clic en Sincronizar", + "CompareSyncWithCurrentX": "Sincronizar con actual #{0}", + "CompareSyncWithReferenceX": "Sincronizar con referencia #{0}", + "CompareSyncApply": "Sincronizar", + "CompareSyncPointSetXY": "Punto de sincronización establecido: actual #{0} y referencia #{1} se muestran ahora como pareja", + "CompareSyncAlreadyPairedXY": "Actual #{0} y referencia #{1} ya se muestran como pareja con el mismo inicio: nada que sincronizar", + "CompareSyncPointShiftedXYZW": "Punto de sincronización establecido: actual #{0}-#{1} desplazadas {2} para empezar con la referencia #{3}", + "CompareChangeSyncShiftXYZ": "#{0}-#{1} desplazadas {2} (punto de sincronización)", + "CompareTimingDeltaHint": "Cuánto más tarde (+) o antes (−) empieza y termina la referencia respecto a la línea actual", "LoadXFromFile": "Cargar \"{0}\" desde archivo", + "CompareLoadSavedFile": "Cargar archivo guardado", "SaveCompareHtmlTitle": "Guardar archivo HTML de comparación", "PickMatroskaTrackX": "Elegir pista Matroska - {0}", "PickTransportStreamTrackX": "Elegir pista de transport stream - {0}", + "Chapters": "Capítulos", + "DvdVobFilesMissingX": "Faltan archivos VOB ({0}% encontrados)", "PickMp4TrackX": "Elegir pista MP4 - {0}", + "PickMxfTrackX": "Elegir pista MXF - {0}", + "PickMpegTrackX": "Elegir pista MPEG - {0}", "RosettaProperties": "Propiedades Timed Text Rosetta IMSC", "RosettaFontSize": "Tamaño de fuente (altura de fila)", "PropertyTimeBase": "Base de tiempo", @@ -1286,7 +1345,6 @@ "PropertyDefaultRegion": "Región predeterminada", "PropertyStyleAttributeName": "Nombre del atributo de estilo", "PropertyTimeCodeFormat": "Formato del código de tiempo", - "PropertyFileExtension": "Extensión de archivo", "PropertyTopOrigin": "Origen superior", "PropertyTopExtent": "Extensión superior", "PropertyBottomOrigin": "Origen inferior", @@ -1294,13 +1352,9 @@ "XProperties": "Propiedades de {0}", "EbuSaveOptions": { "Title": "Opciones de guardado EBU", - "GeneralSubtitleInformation": "Información general de subtítulos", "CodePageNumber": "Número de página de códigos", "DiskFormatCode": "Código de formato de disco", "DisplayStandardCode": "Código estándar de visualización", - "ColorRequiresTeletext": "¡Los colores requieren teletexto!", - "AlignmentRequiresTeletext": "¡La alineación requiere teletexto!", - "TeletextCharsShouldBe38": "¡El máx. de caracteres por fila para teletexto debería ser 38!", "CharacterCodeTable": "Tabla de caracteres", "LanguageCode": "Código de idioma", "OriginalProgramTitle": "Título original del programa", @@ -1317,7 +1371,6 @@ "MaxNumberOfDisplayableRows": "Máx. número de filas", "DiskSequenceNumber": "Número de secuencia de disco", "TotalNumberOfDisks": "Número total de discos", - "Import": "Importar...", "TextAndTimingInformation": "Información de texto y tiempos", "JustificationCode": "Código de justificación", "VerticalPosition": "Posición vertical", @@ -1334,11 +1387,6 @@ "Errors": "Errores", "ErrorsX": "Errores: {0}", "MaxLengthError": "La línea {0} excede la longitud máx. ({1}) por {2}: {3}", - "TextUnchangedPresentation": "Presentación sin cambios", - "TextLeftJustifiedText": "Texto justificado a la izquierda", - "TextCenteredText": "Texto centrado", - "TextRightJustifiedText": "Texto justificado a la derecha", - "UseBoxForOneNewLine": "Usar caja para solo un salto de línea", "MaxLengthErrorTeletextColored": "Línea {0}-{1}: el máximo de caracteres para texto en color de doble altura debería ser 36 (no {2})", "MaxLengthErrorTeletextWhite": "Línea {0}-{1}: el máximo de caracteres para texto blanco de doble altura debería ser 37 (no {2})", "MaxLengthErrorTeletextSingleHeight": "Línea {0}: el máximo de caracteres para texto blanco de altura simple debería ser 38 (no {1})", @@ -1363,22 +1411,29 @@ "FormattingDotDotDot": "Formato...", "ImageBasedSubtitleForEditDotDotDot": "Subtítulo basado en imagen para editar...", "ImageBasedSubtitleForOcrDotDotDot": "Subtítulo basado en imagen para OCR...", + "DvdSubtitlesDotDotDot": "Subtítulos de DVD (IFO/VOB)...", + "TitleImportDvd": "Importar subtítulos de DVD", + "DvdIfoFile": "Archivo IFO", + "DvdVobFiles": "Archivos VOB", + "DvdVideoStandard": "Estándar de vídeo", + "DvdStartRipping": "Iniciar extracción", + "DvdRippingAborted": "Extracción cancelada", + "DvdOpenIfoFile": "Abrir archivo IFO de DVD", + "DvdAddVobFiles": "Añadir archivos VOB", + "DvdIfoFiles": "Archivos IFO de DVD", + "DvdVobFileType": "Archivos VOB de DVD", + "DvdDropHint": "Abra un archivo IFO (VIDEO_TS\\VTS_01_0.IFO) o añada archivos VOB, o suéltelos aquí", + "DvdNotAnIfoFileX": "{0} no es un archivo IFO de DVD", + "DvdMenuIfoNoTitleSetsX": "No se encontraron conjuntos de títulos (VTS_xx_0.IFO) junto a {0}", + "DvdEncryptedXOfY": "{0} de los {1} paquetes de subtítulos siguen cifrados con CSS: estos archivos VOB se copiaron sin descifrar, por lo que algunas imágenes de subtítulos aparecerán corruptas.\n\nDescifre primero el DVD con una herramienta de extracción de DVD para obtener un resultado limpio.\n\n¿Continuar de todos modos?", "SplitTextAt": "Dividir texto en", "BlankLines": "Líneas en blanco", "OneLineIsOneSubtitle": "Una línea es un subtítulo", "TwoLinesAreOneSubtitle": "Dos líneas son un subtítulo", "ImportFilesDotDotDot": "Importar archivos...", "MultipleFiles": "Importar de varios archivos de texto (un archivo = un subtítulo)", - "ImportOptions": "Opciones de importación", - "AutoSplitText": "Dividir texto automáticamente", - "LineMode": "Modo de línea", "MaxLineLength": "Longitud máx. de línea", - "MaxLinesPerSubtitle": "Máx. líneas por subtítulo", - "MinGapBetweenSubtitles": "Espacio mín. entre subtítulos", "MergeShortLines": "Combinar líneas cortas", - "RemoveLinesWithoutLetters": "Eliminar líneas sin letras", - "SplitAtEndCharsSetting": "Dividir en caracteres finales", - "TakeTimeFromCurrentFile": "Tomar tiempo del archivo actual", "Fixed": "Fijo", "NumberOfSubtitlesX": "Número de subtítulos: {0}", "NumberOfSubtitlesXForcedY": "Número de subtítulos: {0} ({1} forzados)", @@ -1390,14 +1445,18 @@ "ForcedAlignerProgress": "Alineando... ventana {0} de {1}, {2} de {3} líneas", "ForcedAlignerSetupTitle": "Alinear códigos de tiempo con alineador forzado", "ForcedAlignerSetupIntro": "Un alineador forzado hace coincidir con el audio el texto del subtítulo que ya tiene, sin transcribirlo antes. Los vídeos largos se alinean por fragmentos, así que sirve cualquier duración.", + "ForcedAlignerEndsFromSpeech": "Establecer tiempos finales a partir de la voz aislada (lento)", + "ForcedAlignerEndsFromSpeechHint": "Un alineador forzado encuentra dónde empieza una línea, no dónde termina, por lo que los tiempos finales normalmente se basan en el tiempo de lectura. Con esta opción activada, primero se eliminan la música y los efectos de sonido del audio y cada línea termina donde su voz se silencia. Tarda aproximadamente lo mismo que el propio vídeo con una GPU, y muchas veces más sin ella.", + "ForcedAlignerIsolatingSpeech": "Aislando la voz... {0}", + "ForcedAlignerIsolatingSpeechFailed": "No se pudo aislar la voz; los tiempos finales se establecieron a partir del tiempo de lectura.", "ForcedAlignerModel": "Modelo del alineador", - "ForcedAlignerDownloadEngine": "Descargar / actualizar motor..." + "ForcedAlignerDownloadEngine": "Descargar / actualizar motor...", + "ForcedAlignerEstimatedXOfY": "{0} de {1} líneas no se pudieron alinear y se colocaron según el tiempo de lectura; sus códigos de tiempo son solo estimados. Probablemente el guion es más largo que lo que se habla en el vídeo." }, "Export": { "ExportImagesProfiles": "Exportar perfiles de imágenes", "LeftRightMargin": "Margen izq./der.", "TopBottomMargin": "Margen sup./inf.", - "TitleExportBluRaySup": "Exportar Blu-ray (sup)", "LineSpacingPercent": "Espaciado de línea %", "PaddingLeftRight": "Relleno izq./der.", "PaddingTopBottom": "Relleno sup./inf.", @@ -1438,10 +1497,21 @@ "TitleExportDCinemaInteropPng": "D-Cinema interop/png", "TitleExportDCinemaSmpte2014Png": "D-Cinema SMPTE 2014/png", "ImageBasedSubtitleSaved": "Subtítulo basado en imágenes guardado", - "TitleExportBdnXml": "Exportar BDN/xml", "TitleExportWebVttThumbnails": "Exportar WebVTT (miniaturas)", "FullFrameImage": "Imagen de fotograma completo", "FullFrameImageHint": "Hacer que cada imagen tenga el tamaño del fotograma del vídeo, con el subtítulo en su sitio", + "Stereo3D": "3D", + "Stereo3DHint": "Para vídeo 3D: dibujar el subtítulo una vez en la mitad del fotograma de cada ojo", + "HalfSideBySide": "Medio lado a lado", + "HalfTopBottom": "Medio arriba/abajo", + "Depth3D": "Profundidad", + "Depth3DHint": "Cuánto sobresale el subtítulo de la pantalla: un valor positivo lo acerca, uno negativo lo aleja", + "Plane3D": "Plano 3D", + "Plane3DHint": "Dar a cada subtítulo la profundidad que un Blu-ray 3D almacena para él, a partir de un archivo de plano 3D (.ofs) creado por BD3D2MK3D u OFSExtractor. Se usa Profundidad donde el plano 3D no tiene ninguna", + "OpenPlane3DTitle": "Abrir archivo de plano 3D", + "UnableToLoadPlane3DX": "No se pudo cargar el plano 3D: {0}", + "Plane3DXDepthYToZ": "{0} (profundidad {1} a {2})", + "Plane3DXNoDepth": "{0} (sin valores de profundidad)", "TextEffect": "Efecto de texto", "TextEffectSettingsTitle": "Ajustes del efecto de texto", "TextEffectPreset": "Preajuste", @@ -1473,13 +1543,10 @@ "Statistics": { "Title": "Estadísticas", "TitleWithFileName": "Estadísticas - {0}", - "GeneralStatistics": "Estadísticas generales", - "MostUsed": "Más usado...", "MostUsedLines": "Líneas más usadas", "MostUsedWords": "Palabras más usadas", "NothingFound": "No se encontró nada", "NumberOfLinesX": "Número de líneas de subtítulos: {0:#,##0}", - "NumberOfFilesX": "Número de archivos de subtítulos: {0:#,##0}", "LengthInFormatXinCharactersY": "Número de caracteres como {0}: {1:#,###,##0}", "NumberOfCharactersInTextOnly": "Caracteres en solo texto: {0:#,###,##0}", "TotalDuration": "Duración total de todos los subtítulos: {0:#,##0}", @@ -1520,7 +1587,6 @@ "GapMaximum": "Espacio - máximo: {0:#,##0} ms", "GapAverage": "Espacio - medio: {0:#,##0.##} ms", "GapExceedingMinimum": "Espacio - bajo el mín. ({0:#,##0} ms): {1} ({2:0.00}%)", - "Export": "Exportar...", "Subtitles": "Subtítulos", "Words": "Palabras", "TotalDurationShort": "Duración total", @@ -1550,7 +1616,22 @@ "RestoreAutoBackupFile": "Restaurar archivo de copia de seguridad", "RestoreXFromY": "¿Desea restaurar \"{0}\" desde {1}?", "DeleteAllSubtitleBackups": "¿Desea eliminar todos los archivos de copia de seguridad?", - "DeleteAll": "Eliminar todo" + "DeleteAll": "Eliminar todo", + "Subtitles": "Subtítulos", + "SettingsBackupInfo": "Se hace una copia de seguridad de Settings.json cuando se inicia Subtitle Edit, como máximo una vez cada {0} día(s) y solo si los ajustes han cambiado. Se conservan las {1} copias más recientes.", + "SettingsBackupInfoEveryStart": "Se hace una copia de seguridad de Settings.json cada vez que se inicia Subtitle Edit, si los ajustes han cambiado. Se conservan las {0} copias más recientes.", + "SettingsBackupOff": "La copia de seguridad automática de los ajustes está desactivada (Opciones → Ajustes → Archivo).", + "LastSettingsBackupX": "Última copia de seguridad: {0}", + "NoSettingsBackupsYet": "Aún no hay copias de seguridad de los ajustes", + "OpenSettingsBackupFolder": "Abrir carpeta de copias de seguridad de ajustes", + "BackUpSettingsNow": "Hacer copia ahora", + "SettingsBackedUp": "Se hizo una copia de seguridad de los ajustes actuales.", + "RestoreSettings": "Restaurar ajustes", + "RestoreSettingsFromX": "¿Desea reemplazar sus ajustes actuales por la copia de seguridad del {0}?\n\nPrimero se hará una copia de seguridad de sus ajustes actuales. Algunos cambios solo surten efecto después de reiniciar Subtitle Edit.", + "SettingsRestored": "Se restauraron los ajustes. Reinicie Subtitle Edit para que todos los cambios surtan efecto.", + "SettingsRestoreFailed": "No se pudieron restaurar los ajustes: {0}", + "SettingsBackupNotValid": "la copia de seguridad no es un archivo de ajustes válido, por lo que se conservaron sus ajustes actuales", + "DeleteAllSettingsBackups": "¿Desea eliminar todos los archivos de copia de seguridad de ajustes?" }, "PropertiesDCinema": { "Title": "Propiedades D-Cinema SMPTE", @@ -1564,22 +1645,16 @@ "EditRate": "Tasa de edición", "TimeCodeRate": "Tasa de código de tiempo", "StartTime": "Tiempo de inicio", - "Font": "Fuente", "FontId": "ID de fuente", "FontUri": "URI de fuente", - "Generate": "Generar", - "FontColor": "Color de fuente", "ChooseColor": "Elegir color", "FontEffect": "Efecto de fuente", "EffectColor": "Color del efecto", - "FontSize": "Tamaño de fuente", "TopBottomMargin": "Margen sup./inf.", "FadeUpTime": "Tiempo de aparición", "FadeDownTime": "Tiempo de desaparición", - "Frames": "Fotogramas", "ZPosition": "Posición Z", - "ZPositionHelp": "Los números positivos alejan el texto, los negativos lo acercan; si la posición Z es cero, es 2D", - "Export": "Exportar..." + "ZPositionHelp": "Los números positivos alejan el texto, los negativos lo acercan; si la posición Z es cero, es 2D" } }, "Edit": { @@ -1642,7 +1717,6 @@ "SelectionIntersect": "Intersección con la selección" }, "MultipleReplace": { - "Title": "Reemplazo múltiple", "EditRule": "Editar regla", "NewRule": "Nueva regla", "EditCategory": "Editar categoría", @@ -1659,24 +1733,23 @@ "FindWhat": "Buscar", "DescriptionOptional": "Descripción (opcional)", "InvalidRegularExpressionX": "Expresión regular no válida: {0}", - "RegularExpressionTooSlowX": "La expresión regular se abandonó tras {0} segundos: se omite la regla, pruebe con un patrón más sencillo" + "RegularExpressionTooSlowX": "La expresión regular se abandonó tras {0} segundos: se omite la regla, pruebe con un patrón más sencillo", + "MoveToCategory": "Mover a categoría" }, "Find": { "SearchTextWatermark": "Buscar texto...", "WholeWord": "Palabra completa", - "CaseSensitive": "Sensible a mayúsculas", - "CaseInsensitive": "No sensible a mayúsculas", "FindPrevious": "Buscar _anterior", "FindNext": "_Buscar siguiente", "ReplaceAndFindNext": "_Reemplazar y buscar siguiente", "ReplaceAll": "Reemplazar _todo", - "ReplaceTitle": "Reemplazar", - "ReplaceWith": "Reemplazar con", "ReplaceTextWatermark": "Reemplazar con...", "ReplaceIn": "Reemplazar/buscar en", "ReplaceInTextAndOriginal": "Texto y texto original", "ReplaceInTextOnly": "Solo el texto", - "ReplaceInOriginalOnly": "Solo el texto original" + "ReplaceInOriginalOnly": "Solo el texto original", + "ReplacedOneOccurrence": "Se reemplazó una coincidencia", + "ReplacedXOccurrences": "Se reemplazaron {0} coincidencias" }, "NoSubtitleSelected": "No hay ningún subtítulo seleccionado", "PleaseSelectExactlyOneSubtitle": "Seleccione exactamente un subtítulo." @@ -1687,6 +1760,7 @@ "XLinesYSubtitles": "{0} líneas, {1} subtítulos", "XLinesYSubtitlesZErrors": "{0} líneas, {1} subtítulos, {2} error(es)", "CouldNotParseAsX": "No se pudo analizar como {0}", + "ReadAsXUseAsY": "El origen no se pudo leer como {1}, pero sí como {0}.\n¿Usar las líneas y seguir guardando como {1}?", "SelectedXCharactersYLines": "Seleccionado: {0} caracteres en {1} línea(s)", "ReplacedXOccurrences": "{0} coincidencia(s) reemplazada(s)", "InvalidRegularExpression": "Expresión regular no válida", @@ -1701,7 +1775,6 @@ "AiReview": { "Title": "Revisión con IA", "Review": "Revisar", - "Stop": "Parar", "EditPrompt": "Editar prompt...", "EditPromptTitle": "Editar prompt de revisión", "PromptInfo": "Instrucciones enviadas al modelo: {language} se sustituye por el idioma del subtítulo.", @@ -1712,7 +1785,6 @@ "ReviewingLineXOfY": "Revisando la línea {0} de {1}...", "ReviewDone": "Revisión finalizada - {0} sugerencias en {1} líneas", "NoIssuesFound": "No se encontraron problemas", - "CategoryAll": "Todas", "CategorySpelling": "Ortografía", "CategoryGrammar": "Gramática", "CategoryPunctuation": "Puntuación", @@ -1724,8 +1796,23 @@ "LargeChangeWarning": "Cambio grande - parece una reescritura, revise con atención", "MismatchWarning": "No se parece al original: puede pertenecer a otra línea", "EngineError": "No se pudo contactar con el motor de IA: {0}", - "SetupHint": "Elija primero un motor y un modelo en Herramientas -> Revisión con IA.", - "PlayCurrentLineHint": "Reproducir la línea seleccionada en el reproductor de vídeo y pausar al final (también con doble clic en una línea)" + "PlayCurrentLineHint": "Reproducir la línea seleccionada en el reproductor de vídeo y pausar al final (también con doble clic en una línea)", + "EditAfterHint": "Haga clic en el texto de la fila seleccionada para editar la corrección antes de aplicarla (Enter guarda, Escape cancela)", + "Template": "Plantilla", + "TemplatePlaceholder": "Elija una plantilla para reemplazar la indicación", + "TemplateStandard": "Revisión estándar", + "TemplateSpeechToText": "Limpieza de voz a texto", + "TemplateMachineTranslation": "Corrección de traducción automática", + "TemplateSdh": "SDH / deficientes auditivos", + "ReferenceContext": "Contexto de referencia", + "ReferenceContextInfo": "Opcional. Nombres, personajes, glosario, sinopsis o notas de estilo: el modelo lo trata como la autoridad para nombres y términos. Se envía con cada lote.", + "ReferenceContextPlaceholder": "Nombres: Kaito, tía Mei, Dr. Hollis\nTérminos: Shinkansen, núcleo de curvatura\nSinopsis: Kaito viaja a Tokio para encontrar a su padre desaparecido.", + "GenerateContext": "Generar con IA", + "GenerateContextHint": "Deje que el motor de IA seleccionado lea el subtítulo y redacte nombres, términos y una sinopsis; revise el resultado antes de usarlo", + "GeneratingContextPartXOfY": "Leyendo parte {0} de {1}...", + "GeneratingContextSynopsis": "Escribiendo sinopsis...", + "ReplaceContextQuestion": "¿Reemplazar el contexto de referencia actual?", + "NoContextGenerated": "El motor de IA no devolvió información de referencia utilizable." }, "AiAssistant": { "Title": "Asistente de IA", @@ -1742,7 +1829,6 @@ "Hint": "Asistente de IA para esta línea", "ShowReasoning": "Mostrar el razonamiento del modelo" }, - "PickAlignmentTitle": "Elegir alineación", "RemoveUnicodeCharacters": { "Title": "Eliminar/reemplazar caracteres Unicode", "CharactersFoundX": "Caracteres Unicode encontrados: {0}", @@ -1752,31 +1838,23 @@ "PickFontNameInstalledFonts": "Fuentes instaladas", "PickFontNameCollectedFonts": "Fuentes recopiladas", "ColorPickerTitle": "Elegir color", + "ColorPickerBrightness": "Brillo", "FilterLayersTitle": "Filtrar capas para visualización", "FilterLayersHideFromWaveform": "Ocultar de forma de onda/espectrograma", "FilterLayersHideFromSubtitleGrid": "Ocultar de la cuadrícula", "FilterLayersHideFromVideoPreview": "Ocultar de la vista previa de vídeo", "PickSubtitleFormat": "Elegir formato de subtítulo", "PickSubtitleFormatImageBasedNoPreview": "Formato de subtítulo basado en imagen.\nLos mapas de bits se generan durante la conversión: no hay vista previa del texto.", - "PickLayerTitle": "Establecer capa", "RecentColors": "Colores recientes", "FixCommonErrors": { - "Title": "Corregir errores comunes", - "Step1": "Paso 1/2 - Elegir qué errores corregir", - "WhatToFix": "Qué corregir", - "Example": "Ejemplo", - "SelectAll": "Seleccionar todo", - "InverseSelection": "Invertir selección", "Back": "< _Atrás", "Next": "_Siguiente >", - "Step2": "Paso 2/2 - Verificar correcciones", "Fixes": "Correcciones", "RuleDetailsDotDotDot": "Detalles de la regla...", "ShowOnlyThisRule": "Mostrar solo las correcciones de esta regla", "AppliedRulesForLineX": "Regla(s) aplicada(s) a la línea {0}", "RuleX": "Regla: {0}", "Log": "Registro", - "Function": "Función", "RemovedEmptyLine": "Eliminar línea vacía", "RemovedEmptyLineAtTop": "Eliminar línea vacía superior", "RemovedEmptyLineAtBottom": "Eliminar línea vacía inferior", @@ -1797,13 +1875,13 @@ "RemoveLineBreaksPixelWidth": "Unir subtítulos que caben en una línea (ancho píxeles)", "FixUppercaseIInsideLowercaseWords": "Corregir 'i' mayúscula dentro de palabras en minúscula (error OCR)", "FixDoubleApostrophes": "Corregir apóstrofes dobles ('') a comilla simple (\")", + "FixMisreadQuotes": "Corregir apóstrofos leídos como comillas dobles (OCR, inglés)", "AddPeriods": "Añadir punto tras líneas si la siguiente empieza en mayúscula", "StartWithUppercaseLetterAfterParagraph": "Empezar con mayúscula tras párrafo", "StartWithUppercaseLetterAfterPeriodInsideParagraph": "Empezar con mayúscula tras punto dentro del párrafo", "StartWithUppercaseLetterAfterColon": "Empezar con mayúscula tras dos puntos/punto y coma", "FixLowercaseIToUppercaseI": "Corregir 'i' minúscula sola a 'I' (Inglés)", "FixCommonOcrErrors": "Corregir errores de OCR comunes (usando lista de reemplazo)", - "CommonOcrErrorsFixed": "Errores OCR comunes corregidos: {0}", "RemoveSpaceBetweenNumber": "Eliminar espacio entre números", "BreakDialogsOnOneLine": "Dividir diálogos en una sola línea", "RemoveDialogFirstInNonDialogs": "Eliminar guion inicial en no-diálogos", @@ -1820,18 +1898,17 @@ "FixCommasExample": ",, -> ,", "RemoveLineBreaksExample": "Foo
bar! -> Foo bar!", "FixDoubleApostrophesExample": "''Has double single quotes'' -> \"Has single double quote\"", + "FixMisreadQuotesExample": "\"Hello' -> \"Hello\"", "AddPeriodsExample": "Hello world -> Hello world.", "StartWithUppercaseLetterAfterParagraphExample": "p1: Foobar! || p2: foobar! -> p1: Foobar! || p2: Foobar!", "StartWithUppercaseLetterAfterPeriodInsideParagraphExample": "Hello there! how are you? -> Hello there! How are you?", "StartWithUppercaseLetterAfterColonExample": "Speaker: hello world! -> Speaker: Hello world!", "RemoveHyphensSingleLineExample": "- Foobar. -> Foobar.", "Fix3PlusLinesExample": "Foo
bar
baz! -> Foo bar baz!", - "XMissingQuotesAdded": "Comillas faltantes añadidas: {0}", "Fix3PlusLine": "Corregir subtítulo con más de dos líneas", "Fix3PlusLines": "Corregir subtítulos con más de dos líneas", "Analysing": "Analizando...", "NothingToFix": "Nada que corregir :)", - "FixesFoundX": "Correcciones encontradas: {0}", "XFixesApplied": "Correcciones aplicadas: {0}", "XFixesYSelected": "{0} correcciones - {1} seleccionadas", "NothingFixableBut": "Nada pudo corregirse automáticamente. El subtítulo tiene errores; ver registro.", @@ -1840,7 +1917,6 @@ "FixFirstLetterToUppercaseAfterParagraph": "Corregir primera letra a mayúscula tras párrafo", "MergeShortLine": "Combinar línea corta (una frase)", "MergeShortLineAll": "Combinar línea corta (todo menos diálogos)", - "UnbreakShortLinePixelWidth": "Unir línea corta (ancho píxeles)", "BreakLongLine": "Dividir línea larga", "FixLongDisplayTime": "Corregir tiempo largo", "FixInvalidItalicTag": "Corregir etiqueta cursiva inválida", @@ -1863,7 +1939,6 @@ "FixUppercaseIInsideLowercaseWord": "Corregir 'i' mayúscula en palabra en minúscula", "FixMissingPeriodAtEndOfLine": "Añadir punto faltante al final de línea", "RefreshFixes": "Actualizar correcciones disponibles", - "ApplyFixes": "Aplicar correcciones y cerrar", "AutoBreak": "Auto &div", "Unbreak": "&Unir", "FixDoubleDash": "Corregir '--' -> '...'", @@ -1882,8 +1957,6 @@ "FixSpaceBetweenNumbersExample": "1 100 -> 1100", "FixDialogsOneLineExample": "¡Hola Juan! - ¡Hola Ana! -> ¡Hola Juan!
- ¡Hola Ana!", "RemoveDialogFirstInNonDialogsExample": "- ¿Cómo estás? -> ¿Cómo estás?", - "SelectDefault": "Seleccionar predet.", - "SetDefault": "Establecer correcciones actuales como predet.", "FixContinuationStyleX": "Corregir estilo de continuación: {0}", "FixUnnecessaryLeadingDots": "Eliminar puntos iniciales innecesarios", "FixCommonErrorsProfiles": "Perfiles de corrección de errores", @@ -1891,7 +1964,6 @@ "SearchRulesDotDotDot": "Buscar reglas...", "GoToApplyFixes": "Ir a aplicar correcciones", "BackToFixList": "Volver a la lista", - "ApplyFixesAndClose": "Aplicar correcciones y cerrar", "FixCommonOcrErrorsStep2": "Corregir errores, paso 2 (aplicar)", "FixCommonOcrErrorsStep2FixesFoundX": "Corregir errores, paso 2 - Encontrados: {0}", "Action": "Acción", @@ -1904,18 +1976,11 @@ "AdjustDurations": { "Title": "Ajustar duraciones", "AdjustVia": "Ajustar vía", - "Seconds": "Segundos", - "Percent": "Porcentaje", "Recalculate": "Recalcular", "AddSeconds": "Añadir segundos", - "SetAsPercent": "Establecer como porcentaje de duración", "Note": "Nota: El tiempo final no se superpondrá al inicio del siguiente texto", "Fixed": "Fijo", - "Milliseconds": "Milisegundos", "ExtendOnly": "Solo extender", - "EnforceDurationLimits": "Aplicar límites de duración mínima y máxima", - "CheckShotChanges": "No extender más allá de los cambios de plano", - "BatchCheckShotChanges": "Respetar cambios de plano (si existen)", "RecalculateRequiresOcrNote": "Recalcular no está disponible — uno o más subtítulos no tienen texto OCR. Ejecute primero el OCR." }, "ApplyDurationLimits": { @@ -1923,6 +1988,8 @@ "FixMinDurationMs": "Corregir duración mínima (ms)", "DoNotGoPastShotChange": "No pasar del cambio de plano", "FixMaxDurationMs": "Corregir duración máxima (ms)", + "FixMinDurationFrames": "Corregir duración mínima (fotogramas)", + "FixMaxDurationFrames": "Corregir duración máxima (fotogramas)", "MinimumDurationMilliseconds": "Duración mínima (milisegundos):", "MaximumDurationMilliseconds": "Duración máxima (milisegundos):", "MaxDurationShouldBeHigherThanMinDuration": "La duración máxima debe ser mayor que la mínima", @@ -1942,12 +2009,8 @@ }, "BeautifyTimeCodes": { "Title": "Embellecer códigos de tiempo", - "BeautifySettings": "Ajustes de embellecimiento", "SnapToFrames": "Ajustar a fotogramas", - "FrameGap": "Espacio de fotogramas", "MinDuration": "Duración mín. (ms)", - "ShotChangeThreshold": "Umbral de cambio de plano (ms)", - "ShotChangeOffset": "Desfase de cambio de plano (fotogramas)", "Original": "Original", "Beautified": "Embellecido", "SubtitlesCount": "Subtítulos", @@ -1976,13 +2039,80 @@ "TimeCodesExtractFailed": "No se pudieron leer los códigos de tiempo del vídeo", "TimeCodesHint": "Ajusta las entradas/salidas a los tiempos reales de los fotogramas del vídeo en lugar de suponer una tasa de fotogramas constante. Requiere leer todo el vídeo una vez; el resultado se guarda en caché." }, + "ImproveTimeCodes": { + "Title": "Mejorar códigos de tiempo (alineación forzada)", + "Aligner": "Alineador", + "AlignerHint": "El modelo que escucha cada línea en el audio. Las mejores opciones para el idioma del subtítulo aparecen primero.", + "MaxShift": "Desplazamiento máximo (segundos)", + "MaxShiftHint": "Cuánto puede estar desincronizado el subtítulo. Una línea cuyo inicio se movería más se deja como está y se marca. Sea cual sea el valor, una línea que quiera moverse más de medio segundo más que las líneas que la rodean no se mueve a ciegas: sigue a sus vecinas si todas se movieron y, en caso contrario, se ofrece desmarcada para que la revise.", + "AdjustStartTimes": "Ajustar tiempos de inicio", + "AdjustEndTimes": "Ajustar tiempos de fin", + "IsolateSpeech": "Aislar la voz primero (lento)", + "IsolateSpeechHint": "Elimina la música y los efectos de sonido antes de alinear, para que el alineador solo oiga el diálogo. Ayuda en líneas dichas sobre música alta o acción, pero tarda aproximadamente lo mismo que el propio audio con una GPU, y muchas veces más sin ella.", + "IsolatingSpeech": "Aislando la voz... (esto tarda un poco)", + "IsolateSpeechFailed": "No se pudo aislar la voz: se alineó con el audio original en su lugar.", + "CheckWithSpeechToText": "Comprobar con voz a texto", + "CheckWithSpeechToTextHint": "Transcribe también el audio y comprueba cada movimiento con el lugar donde realmente se oyeron las palabras. Los movimientos grandes que confirma la voz se marcan automáticamente, y los que se alejan de donde se oye una línea se dejan desmarcados para que los revise. También muestra cuánto de cada línea se oyó: un porcentaje bajo significa que el texto difiere de lo que se dice. Un subtítulo más desincronizado que el desplazamiento máximo (un desfase constante, una deriva por una tasa de fotogramas distinta o saltos donde se cortaron escenas) se sincroniza primero con las palabras oídas. Usa Crisp ASR Parakeet, que tarda unos minutos para un largometraje con una GPU.", + "CheckWithSpeechToTextNotAvailableX": "La comprobación con voz a texto no está disponible para {0}", + "TranscribingToCheck": "Transcribiendo para comprobar la alineación...", + "SpeechToTextFailed": "La voz a texto falló: los códigos de tiempo no se comprobaron con ella.", + "SpeechToTextX": "Voz a texto: {0}", + "SpeechToTextConfirmedXDisputedY": "Voz: {0} confirmadas, {1} discutidas", + "HardlyHeardX": "La voz a texto solo oyó el {0}% de las palabras del subtítulo. ¿Está el subtítulo en el idioma hablado? El alineador no puede ubicar una traducción.", + "Heard": "Oído", + "HeardHint": "Cuánto de la línea oyó la voz a texto. Un porcentaje bajo significa que el texto difiere de lo que se dice, que es donde es más probable que el alineador se equivoque.", + "StatusConfirmedBySpeech": "Confirmado por la voz", + "StatusDisputedBySpeech": "Oído donde estaba", + "StatusMovedWithSync": "Movido con la sincronización", + "SyncedFirstXY": "Sincronizado primero: {0} s al inicio, {1} s al final", + "ShowSpeechOnly": "Mostrar solo voz", + "ShowSpeechOnlyHint": "Dibuja la forma de onda alineada a partir del audio sin música ni efectos de sonido; la forma de onda original queda como está, para comparar. Disponible una vez aislada la voz, aquí o con \"Mostrar solo voz\" en la forma de onda de la ventana principal.", + "Align": "Alinear", + "Original": "Original", + "Aligned": "Alineado", + "Intro": "Elija un alineador y pulse \"Alinear\" para escuchar cada línea en el audio y ajustar sus códigos de tiempo.", + "ExtractingAudio": "Extrayendo audio...", + "AligningBatchXOfY": "Alineando... lote {0} de {1}", + "ExtractAudioFailed": "No se pudo extraer el audio con ffmpeg.", + "ModelWillBeDownloaded": "Se descargará {0}", + "SummaryXRetimedYKeptZSkipped": "Reajustadas: {0} · Conservadas: {1} · Sin voz: {2}", + "MeanShiftX": "Desplazamiento medio: {0} ms", + "ToCheckX": "Por revisar: {0}", + "LargeMovesToCheckX": "{0} línea(s) se moverían mucho por sí solas y se dejan desmarcadas: escúchelas (F5 reproduce la línea alineada, Shift+F5 la original) y marque las correctas.", + "LinesToCheckX": "{0} línea(s) se dejan desmarcadas para revisar: escúchelas (F5 reproduce la línea alineada, Shift+F5 la original) y marque las correctas.", + "ChangeXOfY": "Cambio {0} de {1}", + "PreviousChange": "Cambio anterior", + "NextChange": "Cambio siguiente", + "StartShift": "Inicio", + "EndShift": "Fin", + "StatusRetimed": "Reajustado", + "StatusMovedWithNeighbours": "Movido con sus vecinas", + "StatusLargeMoveUnconfirmed": "Movimiento grande: escuche y marque para aplicar", + "StatusUnchanged": "Ya en su sitio", + "StatusNoSpeech": "Sin voz", + "StatusShiftTooLarge": "Conservado: desplazamiento demasiado grande", + "StatusFailed": "Conservado: el alineador falló", + "StatusAdjustedByHand": "Ajustado a mano", + "AlignedWaveformHint": "Arrastre una línea para moverla, o arrastre sus bordes para cambiar su inicio o fin.", + "PlayLine": "Reproducir línea", + "UndoAdjustment": "Deshacer ajuste", + "PlayPause": "Reproducir / pausar", + "PlayAligned": "Alineado", + "PlayAlignedHint": "Reproducir la línea seleccionada con sus códigos de tiempo alineados", + "Apply": "Aplicar", + "ApplyHint": "Desmarque una línea para conservar sus códigos de tiempo originales. Haga doble clic en una línea para reproducirla. Las líneas también se pueden mover o redimensionar en la forma de onda alineada." + }, "BeautifyTimeCodesProfile": { "Title": "Editar perfil de embellecimiento de códigos de tiempo", "LoadPreset": "Cargar preajuste", "PresetDefault": "Predeterminado", "PresetNetflix": "Netflix", "PresetSdi": "SDI", - "General": "General", + "SaveAsProfile": "Guardar como perfil...", + "DeleteProfile": "Eliminar perfil", + "ProfileName": "Nombre del perfil", + "OverwriteProfileX": "Ya existe un perfil llamado \"{0}\". ¿Sobrescribirlo?", + "DeleteProfileX": "¿Eliminar el perfil \"{0}\"?", "Gap": "Espacio", "GapSuffix": "fotogramas (también actualiza los campos de espacio distintos de cero de abajo)", "Milliseconds": "ms", @@ -2011,9 +2141,12 @@ "HintChaining": "Cómo tratar los subtítulos que casi se tocan: extenderlos juntos o separarlos, según si hay un cambio de plano en medio." }, "BridgeGaps": { - "Title": "Unir espacios", "BridgeGapsSmallerThan": "Unir espacios menores de (ms)", "MinGap": "Espacio mínimo (ms)", + "BridgeGapsSmallerThanFrames": "Unir espacios menores que (fotogramas)", + "MinGapFrames": "Espacio mínimo (fotogramas)", + "CalculateBridgeGapsSmallerThanDotDotDot": "Calcular límite de unión de espacios a partir de una tasa de fotogramas...", + "UseXMsAsBridgeGapsSmallerThan": "¿Unir espacios menores que \"{0}\" milisegundos?", "NumberOfSmallGapsBridgedX": "Número de espacios pequeños unidos: {0}", "PercentFoPrevious": "Espacio para el anterior (%)", "GapChange": "Cambio de espacio" @@ -2025,8 +2158,6 @@ "SortBy": { "Title": "Ordenar subtítulos", "SortOrder": "Orden", - "SortByNumber": "Número", - "SortByStartTime": "Tiempo de inicio", "SortByEndTime": "Tiempo de fin" }, "BatchConvert": { @@ -2049,6 +2180,7 @@ "AddColor": "Añadir color", "DeleteLinesWithSpecificActorsOrStyles": "Eliminar líneas con actores o estilos (separados por coma)", "UseSourceStylesIfPossible": "Usar estilos de origen si es posible", + "KeepSourceEmbeddedFonts": "Conservar fuentes incrustadas del origen (pie)", "EditStyles": "Editar estilos", "EditProperties": "Editar propiedades", "EditAttachments": "Editar adjuntos", @@ -2069,7 +2201,6 @@ "AssaChangeStyleFromStyle": "Cambiar estilo de", "AssaChangeStyleToStyle": "a", "AssaChangeStyleImportStyle": "Importar estilo...", - "AssaChangeStyleImportedX": "Importado: {0}", "AssaChangeStyleTrimUnusedStyles": "Eliminar estilos no usados", "AssaChangeStylePropertiesTitle": "Cambiar propiedades de los estilos", "AssaChangeStylePropertiesInfo": "Cambia los campos elegidos en todos los estilos del archivo, sin tocar los estilos en sí.", @@ -2081,13 +2212,52 @@ "ConvertColorsToDialogReBreakLines": "Volver a dividir las líneas", "SnapTimeCodesToFramesInfo": "Redondea cada tiempo de inicio y fin al fotograma más cercano. La tasa de fotogramas se lee de un archivo de vídeo con el mismo nombre que el archivo de subtítulos, salvo que se elija una tasa de fotogramas fija.", "AddFolderDotDotDot": "Añadir carpeta...", + "AddFolderRecursiveDotDotDot": "Añadir carpeta recursivamente...", "SelectFolderToConvert": "Seleccione la carpeta con los archivos que convertir", "IncludeSubfolders": "Incluir subcarpetas al añadir una carpeta", + "VideoFilesWhenAddingFolder": "Archivos de vídeo (mkv, mp4, ts...) al añadir una carpeta", + "VideoFilesAsk": "Preguntar", + "VideoFilesInclude": "Añadir sus pistas de subtítulos", + "VideoFilesSkip": "Omitir", + "FolderContainsXVideoFiles": "La carpeta contiene {0} archivo(s) de vídeo (mkv, mp4, ts...).\n\n¿Añadir también sus pistas de subtítulos incrustadas, o solo los archivos de subtítulos?", + "AddVideoFiles": "Añadir archivos de vídeo", + "SkipVideoFiles": "Solo archivos de subtítulos", + "DoNotAskAgainVideoFiles": "No volver a preguntar (cambiar en los ajustes de conversión por lotes)", + "SettingsSectionOutput": "Salida", + "SettingsSectionAddingFiles": "Añadir archivos", + "SettingsSectionImageBasedSubtitles": "Subtítulos basados en imágenes (OCR)", + "SettingsSectionOther": "Otros", "KeepSourceFileTimestamp": "Mantener la fecha/hora del archivo de origen en los archivos de salida", - "ScanningFolderX": "Analizando {0}..." + "PreventSleepWhileConverting": "Impedir que el equipo entre en suspensión durante la conversión", + "ScanningFolderX": "Analizando {0}...", + "TransportStreamSettings": "Ajustes de Transport Stream", + "TransportStreamSettingsDotDotDot": "Ajustes de Transport Stream...", + "TransportStreamSettingsInfo": "Se aplica a las pistas de subtítulos extraídas de archivos Transport Stream (.ts/.m2ts). La posición y el tamaño del vídeo solo afectan a las pistas de imagen DVB exportadas a un formato basado en imágenes.", + "TransportStreamOverrideXPosition": "Reemplazar posición X original", + "TransportStreamOverrideYPosition": "Reemplazar posición Y original", + "TransportStreamOverrideVideoSize": "Reemplazar tamaño de vídeo original", + "TransportStreamBottomMargin": "Margen inferior", + "TransportStreamFileNameEnding": "Final del nombre de archivo", + "TransportStreamFileNameEndingInfo": "Se añade antes de la extensión de cada pista extraída. Déjelo vacío para usar el postfijo de idioma habitual.", + "TransportStreamOnlyTeletext": "Solo teletexto", + "TransportStreamGetSizeFromVideo": "Obtener tamaño del vídeo...", + "TwoLetterLanguageCodeUppercase": "Código de idioma de dos letras (mayúsculas)", + "ThreeLetterLanguageCodeUppercase": "Código de idioma de tres letras (mayúsculas)", + "FormatContainsDotDotDot": "El formato contiene...", + "ExtensionIsDotDotDot": "La extensión es...", + "StatusIsError": "El estado es error", + "ForcedTracksOnly": "Solo pistas forzadas", + "XConvertedYFailedInZ": "{0:#,###,##0} convertidos, {1:#,###,##0} fallidos en {2}", + "XNotProcessed": "{0:#,###,##0} sin procesar", + "RemoveUnicodeControlCharactersTitle": "Eliminar caracteres de control Unicode", + "RemoveUnicodeControlCharactersInfo": "Elimina del texto los caracteres invisibles de control de dirección Unicode (LRM, RLM, LRE, RLE, PDF, LRO y RLO) y reemplaza los espacios de no separación por espacios normales.", + "Preset": "Preajuste", + "SavePresetDotDotDot": "Guardar preajuste...", + "DeletePreset": "Eliminar preajuste", + "PresetName": "Nombre del preajuste", + "DeletePresetX": "¿Eliminar el preajuste \"{0}\"?" }, "ChangeCasing": { - "Title": "Cambiar mayúsculas/minúsculas", "FixNames": "Corregir nombres", "NamesX": "Nombres: {0:#,##0}", "HitsX": "Coincidencias: {0:#,##0}", @@ -2098,9 +2268,7 @@ "AllUppercase": "Todo en mayúsculas", "AllLowercase": "Todo en minúsculas" }, - "ChangeFormatting": { - "Title": "Cambiar formato" - }, + "ChangeFormatting": {}, "ConvertActors": { "Title": "Convertir actores", "ConvertActorFrom": "Convertir actor de", @@ -2129,18 +2297,12 @@ "OutputFormatAssa": "Advanced Sub Station Alpha (.ass)", "Style1": "Estilo 1", "Style2": "Estilo 2", - "OutlineWidth": "Ancho del contorno", - "ShadowWidth": "Ancho de la sombra", - "AlignTop": "Arriba", - "AlignBottom": "Abajo", - "Merge": "_Combinar", - "PleaseLoadTwoSubtitles": "Cargue dos subtítulos" + "Merge": "_Combinar" }, "SplitSubtitle": { "Title": "Dividir subtítulo", "NumberOfEqualParts": "Número de partes iguales", "SaveSplitParts": "_Guardar partes divididas", - "SubtitleSplitIntoXParts": "Subtítulo dividido en {0} partes.", "XPartsSavedInFormatYToFolder": "{0} partes guardadas en formato {1} en la carpeta:" }, "SplitBreakLongLines": { @@ -2174,18 +2336,17 @@ "ColumnMerge": "Combinar", "ColumnFirst": "Primera línea", "ColumnSecond": "Segunda línea", - "ColumnMerged": "Combinada", - "SelectAll": "Seleccionar todo", - "InverseSelection": "Invertir selección" + "ColumnMerged": "Combinada" }, "MergeLinesWithSameText": { - "Title": "Combinar líneas con el mismo texto", "MaxMsBetweenLines": "Máximo de milisegundos entre líneas", - "IncludeIncrementingLines": "Incluir líneas con texto incremental" + "MaxFramesBetweenLines": "Máx. fotogramas entre líneas", + "IncludeIncrementingLines": "Incluir líneas con texto incremental", + "IncludeRollUpCaptions": "Incluir subtítulos roll-up (desplazables)" }, "MergeLinesWithSameTimeCodes": { - "Title": "Combinar líneas con mismos códigos de tiempo", "MaxMsDifference": "Diferencia máx. (milisegundos)", + "MaxFramesDifference": "Diferencia máxima (fotogramas)", "MakeDialog": "Crear diálogos" }, "NetflixCheckAndFix": { @@ -2208,7 +2369,6 @@ "ShotChanges": "Cambios de tiro", "StartNumberSpellOut": "Deletrear el número de inicio", "TextforHiUseBrackets": "Texto para HI, use corchetes", - "FrameRate": "Velocidad de fotogramas", "TwoFrameGrap": "Espacio de dos marcos", "WhiteSpace": "espacio en blanco", "ChildrensProgram": "Programa infantil", @@ -2246,7 +2406,6 @@ "ItalicsFixed": "Cursiva corregida", "ItalicsNotAllowed": "No se permite cursiva", "BridgeGapsXToYFrames": "Espacio de 3-{0} fotogramas => espacio de 2 fotogramas", - "StartNumberSpellOutComment": "Cuando un número inicia una frase, siempre debe escribirse con palabras", "MaximumXCharactersPerSecond": "Máximo {0} caracteres por segundo" }, "ImageBasedEdit": { @@ -2256,10 +2415,8 @@ "ResizeImagesDotDotDot": "Redimensionar imágenes...", "AdjustBrightnessDotDotDot": "Ajustar brillo...", "AdjustAlphaDotDotDot": "Ajustar alfa...", - "CenterHorizontally": "Centrar horizontalmente", "CropImages": "Recortar imágenes", "ImportTimeCodes": "Importar códigos de tiempo...", - "SetTextForSubtitle": "Establecer texto del subtítulo", "ScreenWidth": "Ancho de pantalla", "ScreenHeight": "Alto de pantalla", "AlphaThresholdInfo": "Los píxeles con alfa bajo el umbral serán totalmente transparentes", @@ -2289,15 +2446,31 @@ "LetterboxCustom": "Altura de banda personalizada", "BarHeightPx": "Altura de banda (px)", "TitleSafePercent": "Zona segura de títulos (%)", + "MoveCaptions": "Mover subtítulos", + "MoveCaptionsDotDotDot": "Mover subtítulos...", + "MoveCaptionsTo": "Mover subtítulos", + "MoveCaptionsIntoBars": "A las barras negras (fuera de la imagen)", + "MoveCaptionsIntoPicture": "Dentro de la imagen (fuera de las barras)", + "MoveCaptionsOffset": "Distancia desde el borde (px)", + "LetterboxBarHeightXPxYOfZCaptionsWillMove": "Altura de barra: {0} px - se moverán {1} de {2} subtítulos", + "RemoveFadeInOut": "Eliminar fundido de entrada/salida", + "RemoveFadeInOutXLinesRemoved": "{0} líneas de fundido combinadas con las líneas a las que pertenecen", + "RemoveFadeInOutNothingFound": "No se encontraron líneas de fundido de entrada/salida", "PositionSummary": "{0}×{1} - {2} subtítulos - altura de banda: {3} px", "XInActivePicture": "{0} en la imagen", "XInTopBar": "{0} en la banda superior", "XInBottomBar": "{0} en la banda inferior", "NoImageSubtitlesLoaded": "No hay subtítulos de imagen cargados", - "ImageBasedFormatNotSupported": "Formato de subtítulo basado en imagen no encontrado o no compatible." + "ImageBasedFormatNotSupported": "Formato de subtítulo basado en imagen no encontrado o no compatible.", + "FrameRateDeclaredInFile": "Tasa de fotogramas declarada en el archivo sup de Blu-ray", + "FrameRateDetectedFromTimeCodesX": "Tasa de fotogramas detectada a partir de los códigos de tiempo del sup de Blu-ray (el archivo declara {0})", + "FrameRateFromVideo": "Tasa de fotogramas del vídeo", + "FrameRateCurrent": "Tasa de fotogramas actual", + "FrameRateSetManually": "Tasa de fotogramas establecida manualmente", + "FrameRateUsageInfo": "Las exportaciones Blu-ray sup, BDN XML, DOST y FCP se escriben con esta tasa de fotogramas. No cambia ningún tiempo; use \"Cambiar tasa de fotogramas\" para ello.", + "UseVideoFrameRateXInsteadOfY": "La tasa de fotogramas del vídeo es {0}, pero el subtítulo está configurado a {1}.\n\n¿Usar la tasa de fotogramas del vídeo ({0})?" }, "RemoveTextForHearingImpaired": { - "Title": "Eliminar texto para deficientes auditivos", "Interjections": "Interjecciones", "SkipIfStartWith": "Omitir si empieza por", "RemoveTextBetween": "Eliminar texto entre", @@ -2359,7 +2532,21 @@ "CorrectWordsX": "Palabras correctas: {0}", "NamesX": "Nombres: {0}", "AddedToDictionaryX": "Añadidas al diccionario: {0}", - "DoNotShowThisAgain": "No volver a mostrar este mensaje" + "DoNotShowThisAgain": "No volver a mostrar este mensaje", + "EditUseAlwaysList": "Editar lista \"Usar siempre\"...", + "UseAlwaysListTitle": "Ortografía - Lista \"Usar siempre\"", + "UseAlwaysListHeader": "Lista \"Usar siempre\"", + "UseAlwaysListDescription": "Palabras que la corrección ortográfica reemplaza automáticamente, guardadas con \"Cambiar todo\" y \"Usar siempre\". Un par solo se usa cuando la palabra está mal escrita.", + "UseAlwaysWord": "Palabra mal escrita", + "XPairs": "{0:#,##0} pares", + "XUnusedPairs": "{0:#,##0} nunca usados", + "UseAlwaysUnusedHint": "Nunca usado: esta palabra está bien escrita (o es un nombre o palabra del usuario), así que la corrección ortográfica nunca la marca", + "RemoveUnused": "Eliminar no usados", + "UseAlwaysEmpty": "Aún no hay pares \"Usar siempre\" para este idioma", + "UseAlwaysEmptyHint": "Los pares se añaden al hacer clic en \"Cambiar todo\" o \"Usar siempre\" en la corrección ortográfica.", + "UseAlwaysNoMatch": "Ningún par coincide con la búsqueda", + "UseAlwaysSameWord": "El reemplazo debe ser distinto de la palabra.", + "UseAlwaysRememberOff": "\"Recordar lista 'Usar siempre'\" está desactivado en Ajustes, por lo que la corrección ortográfica no usa ni guarda esta lista." }, "Video": { "Chapters": { @@ -2390,7 +2577,6 @@ "ScaleTimesDescription": "Escala el tiempo de cada capítulo de una tasa de fotogramas a otra.", "FromFrameRate": "De", "ToFrameRate": "A", - "Apply": "Aplicar", "WriteToVideo": "Escribir en el vídeo...", "WriteToVideoTitle": "Escribir los capítulos en el vídeo", "WriteToVideoDescription": "Los capítulos se escriben en una copia del archivo de vídeo. No se recodifica nada, por lo que es rápido y sin pérdidas.", @@ -2400,7 +2586,6 @@ "WrittenToX": "Capítulos escritos en {0}", "UnableToWriteChapters": "No se pudieron escribir los capítulos en el archivo de vídeo.", "ChaptersSavedToX": "Capítulos guardados en {0}", - "NoVideoLoaded": "No hay ningún vídeo cargado", "EmptyListCallToAction": "Todavía no hay capítulos: añada uno en la posición del vídeo o impórtelos de un vídeo o un archivo." }, "GoToVideoPosition": "Ir a la posición del vídeo", @@ -2412,30 +2597,88 @@ "OpenSecondarySubtitleOnVideoPlayerDotDotDot": "Subtítulo secundario en reproductor, abrir...", "OpenSecondarySubtitleOnVideoPlayer": "Subtítulo secundario (en reproductor)", "RemoveSecondarySubtitleOnVideoPlayer": "Subtítulo secundario en reproductor, quitar", + "EditSecondarySubtitleSettings": "Editar ajustes del subtítulo secundario", + "EditSecondarySubtitleSettingsDotDotDot": "Editar ajustes del subtítulo secundario...", + "SecondarySubtitleRememberSettings": "Recordar estos ajustes", + "SecondarySubtitleDoNotShowAgain": "No volver a mostrar este diálogo", + "SecondarySubtitleJustifyLines": "Justificar líneas", "OpenRecentVideo": "Abrir vídeo reciente", "ClearRecentVideos": "Borrar los vídeos recientes", "CutVideoTitle": "Cortar vídeo", "CutVideoDotDotDot": "Cortar vídeo...", + "RemuxVideoTitle": "Remultiplexar vídeo", + "RemuxVideoDotDotDot": "Remultiplexar vídeo...", + "RemuxVideoInputVideo": "Vídeo de entrada:", + "RemuxVideoMultipleTracksRequiresMkv": "Varias pistas de audio o subtítulos requieren el contenedor MKV. El formato de salida se ha cambiado automáticamente a .mkv.", + "RemuxVideoOutputFormat": "Formato de salida:", + "RemuxVideoOutputFile": "Archivo de salida:", + "RemuxVideoRemuxing": "Remultiplexando vídeo y audio...", + "RemuxVideoFinalizing": "Finalizando archivo de salida (moviendo el índice al principio)...", + "RemuxVideoCompleted": "Vídeo remultiplexado correctamente.", + "RemuxVideoFailed": "Error al remultiplexar el vídeo.", + "RemuxVideoPleaseSelectBoth": "Seleccione los archivos de vídeo y de audio.", + "RemuxVideoAssRequiresMkv": "Los subtítulos ASS/SSA requieren el contenedor MKV para conservar todo el formato y los estilos.", + "RemuxVideoSccRequiresMov": "Los subtítulos Scenarist (.scc) se incrustan como subtítulos cerrados CEA-608, que requieren el contenedor MOV o MPG. El formato de salida se ha cambiado automáticamente a .mov.", + "RemuxVideoSccNotWithWebM": "Los subtítulos cerrados Scenarist (.scc) necesitan el contenedor MOV, que no puede contener el vídeo VP9/AV1 de un archivo .webm. Quite el archivo .scc o conviértalo a otro formato de subtítulo.", + "RemuxVideoMpgMaxTwoCaptionTracks": "Un archivo .mpg contiene como máximo dos pistas de subtítulos cerrados (CC1 y CC3): quite archivos de subtítulos para que queden uno o dos.", + "RemuxVideoMccRequiresMpg": "Los subtítulos MacCaption (.mcc) se incrustan como subtítulos cerrados CEA-608/708 en el vídeo MPEG-2, lo que requiere el contenedor MPG. El formato de salida se ha cambiado automáticamente a .mpg.", + "RemuxVideoMpgReencodeVideoX": "Los subtítulos cerrados en un archivo .mpg necesitan vídeo MPEG-2, y el vídeo es {0}. ¿Recodificar el vídeo a MPEG-2? Tarda más y reduce un poco la calidad.", + "RemuxVideoFileHasNoAudioX": "\"{0}\" no tiene pista de audio, por lo que no se puede usar como fuente de audio.", + "RemuxVideoAddingClosedCaptions": "Añadiendo subtítulos cerrados...", + "RemuxVideoSelectAudioTrack": "Seleccionar pista de audio", + "RemuxVideoSelectAudioTrackFor": "Seleccionar pista de audio para {0}", + "RemuxVideoSelectAudioTrackPrompt": "El archivo '{0}' contiene {1} pistas de audio. Seleccione la pista de audio que se va a remultiplexar:", + "RemuxVideoVideoFile": "vídeo", + "RemuxVideoAudioFile": "audio", + "RemuxVideoAudioFiles": "Archivos de audio:", + "RemuxVideoSubtitleFiles": "Archivos de subtítulos (opcional, subtítulos no incrustados):", + "RemuxVideoSelectAudioTrackDotDotDot": "Seleccionar pista de audio...", + "RemuxVideoFilesX": "{0} archivos", + "RemuxVideoMixAudio": "Mezclar el audio en una sola pista", + "RemuxVideoVolumePercent": "Volumen %:", + "RemuxVideoVolumeX": "Volumen {0}%", + "RemuxVideoFastStart": "Optimizar para streaming web (mover el índice al principio; más lento)", "EmbedSubtitlesDotDotDot": "Añadir/quitar subtítulos incrustados...", "GenerateTimeCodes": "Generar códigos de tiempo", "CheckeredImage": "Imagen ajedrezada", - "SaveVideoAsTitle": "Guardar vídeo como", "PromptForFfmpegParamsAndGenerate": "Pedir parámetros ffmpeg y generar", "CutVideoCutSegments": "Cortar segmentos", "CutVideoMergeSegments": "Combinar segmentos", "CutVideoSplitSegments": "Guardar segmentos individualmente", - "CutVideoType": "Tipo de corte", - "MpvRenderAuto": "Auto", - "MpvRenderNative": "Nativo", - "MpvRenderOpenGl": "OpenGL", - "MpvRenderSoftware": "Software (lento)", "CutVideoAlsoCutSubtitle": "Cortar también el subtítulo", "CutVideoSubtitleFileGeneratedX": "Archivo de subtítulos generado: {0}", + "CutVideoTransition": "Transición entre segmentos", + "CutVideoTransitionDurationSeconds": "Duración (segundos)", + "CutVideoFadeInSeconds": "Fundido desde negro al inicio (segundos)", + "CutVideoFadeOutSeconds": "Fundido a negro al final (segundos)", + "CutVideoPreviewTransition": "Previsualizar transición", + "CutVideoPreviewTransitionTitle": "Vista previa de la transición", + "CutVideoPreviewNeedsJoin": "No hay ninguna unión que previsualizar: añada al menos dos segmentos para combinar, o un segmento para cortar del medio.", + "CutVideoTransitionInfoX": "Cada transición superpone las partes que une, así que la salida es {0} segundos más corta por cada unión. Un subtítulo cortado se reajusta en consecuencia.", + "CutVideoTransitionFade": "Fundido", + "CutVideoTransitionFadeBlack": "Fundido a negro", + "CutVideoTransitionFadeWhite": "Fundido a blanco", + "CutVideoTransitionFadeGrays": "Fundido a gris", + "CutVideoTransitionDissolve": "Disolución", + "CutVideoTransitionWipeLeft": "Barrido a la izquierda", + "CutVideoTransitionWipeRight": "Barrido a la derecha", + "CutVideoTransitionWipeUp": "Barrido hacia arriba", + "CutVideoTransitionWipeDown": "Barrido hacia abajo", + "CutVideoTransitionSlideLeft": "Deslizar a la izquierda", + "CutVideoTransitionSlideRight": "Deslizar a la derecha", + "CutVideoTransitionSlideUp": "Deslizar hacia arriba", + "CutVideoTransitionSlideDown": "Deslizar hacia abajo", + "CutVideoTransitionSmoothLeft": "Suave a la izquierda", + "CutVideoTransitionSmoothRight": "Suave a la derecha", + "CutVideoTransitionCircleOpen": "Círculo que se abre", + "CutVideoTransitionCircleClose": "Círculo que se cierra", + "CutVideoTransitionRadial": "Radial", + "CutVideoTransitionPixelize": "Pixelar", + "CutVideoTransitionBlur": "Desenfoque", "ImportCurrentSubtitle": "Importar subtítulo actual", "AddRemoveEmbeddedSubtitlesTitle": "Añadir/quitar subtítulos incrustados", "AddRemoveEmbeddedSubtitlesMp4Title": "Añadir/quitar subtítulos incrustados (MP4)", "AddCurrentSubtitle": "Añadir subtítulo actual", - "EmbeddedTrackColumnNew": "Nuevo", "EmbeddedTrackNoSubtitlesFoundTitle": "No se encontraron subtítulos", "EmbeddedTrackNoSubtitlesFoundMessage": "El archivo de subtítulos seleccionado no contiene ningún subtítulo.", "EmbeddedTrackNoTracksTitle": "No se añadieron pistas", @@ -2447,7 +2690,7 @@ "EmbeddedTrackGeneratingVideoXY": "Generando vídeo... {0}% {1}", "EmbeddedTrackGeneratingVideo": "Generando vídeo...", "Mp4FilesFilter": "Archivos MP4", - "TitleOrLanguage": "Título/idioma", + "EmbeddedTracksPickVideoHint": "Elija un archivo de vídeo, o suéltelo aquí, para ver sus pistas de subtítulos.", "ViewMatroskaTrackX": "Ver pista Matroska - {0}", "ResolutionSeparator": "x", "OpenFromUrlTitle": "Abrir archivo de vídeo desde URL", @@ -2457,9 +2700,7 @@ "OpenFromUrlDownloadAndOpen": "Descargar y abrir", "OpenFromUrlDownloadAndOpenDescription": "Guardar el vídeo localmente y luego abrirlo.", "OpenFromUrlDownloadAndOpenNote": "Necesario para la voz a texto y los picos de la forma de onda.", - "OpenFromUrlMissing": "Introduzca primero una URL de vídeo.", "OpenFromUrlDownloadingTitle": "Descargando vídeo", - "OpenFromUrlSaveAs": "Guardar vídeo como", "OpenFromUrlDownloadSubtitles": "Descargar también los subtítulos", "OpenFromUrlDownloadVideo": "Descargar vídeo", "OpenFromUrlIncludeAutoGeneratedSubtitles": "Incluir los subtítulos generados automáticamente (idioma original)", @@ -2496,56 +2737,38 @@ "ReEncodeGeneratingDone": "Generación completada", "ReEncodeGeneratedFilesX": "Archivos generados ({0}):", "ReEncodeFfmpegParameters": "Parámetros de ffmpeg", + "AnalyzingVideoX": "Analizando vídeo... {0}% {1}", + "AnalyzingVideoXofY": "Analizando vídeo {0}/{1}... {2}% {3}", + "BackgroundImageFileNotSelected": "No se ha seleccionado un archivo de imagen de fondo", + "BackgroundImageFileDoesNotExistX": "El archivo de imagen de fondo no existe: {0}", "BurnIn": { "Title": "Generar vídeo con subtítulos incrustados", - "InfoAssaOff": "Nota: Estilos Advanced SubStation Alpha compatibles.", - "InfoAssaOn": "Nota: ¡Se usarán estilos Advanced SubStation Alpha! :)", - "XGeneratedWithBurnedInSubsInX": "\"{0}\" generado con subtítulos incrustados en {1}.", - "TimeRemainingMinutes": "Tiempo restante: {0} minutos", - "TimeRemainingOneMinute": "Tiempo restante: Un minuto", - "TimeRemainingSeconds": "Tiempo restante: {0} segundos", - "TimeRemainingAFewSeconds": "Tiempo restante: Unos segundos", - "TimeRemainingMinutesAndSeconds": "Tiempo restante: {0} minutos y {1} segundos", - "TimeRemainingOneMinuteAndSeconds": "Tiempo restante: Un minuto y {0} segundos", - "TargetFileName": "Nombre de archivo de destino: {0}", "TargetFileSize": "Tamaño de destino (requiere codificación de 2 pasos)", "FileSizeMb": "Tamaño en MB", "MatchSourceVideoSize": "Igualar el tamaño del vídeo de origen", - "PassX": "Paso {0}", - "Encoding": "Codificando", "BitRate": "Tasa de bits", "TotalBitRateX": "Tasa de bits total: {0}", "SampleRate": "Tasa de muestreo", "Audio": "Audio", - "Stereo": "Estéreo", "Preset": "Ajuste", + "Tune": "Ajuste (tune)", "PixelFormat": "Formato de píxel", "Crf": "CRF", - "TuneFor": "Optimizar para", - "AlignRight": "Alinear a la derecha", - "GetStartPosition": "Obtener posición inicial", - "GetEndPosition": "Obtener posición final", "UseSource": "Usar origen", - "UseSourceFolder": "Usar carpeta de origen", - "UseSourceResolution": "Usar resolución de origen", - "OutputSettings": "Archivo/carpeta de salida...", "FontSizeFactor": "Factor de tamaño de fuente", "BoxType": "Tipo de caja", - "FixRightToLeft": "Corregir derecha a izquierda", - "Cut": "Cortar", "FromTime": "Desde el tiempo", "ToTime": "Hasta el tiempo", "AudioEncoding": "Codificación de audio", "OutputProperties": "Propiedades de salida...", "VideoFileSize": "Tamaño de archivo de vídeo", "OneBox": "Una caja", - "BoxPerLine": "Caja por línea", - "LogoInfo": "Elija una imagen PNG y arrástrela para colocarla en el video." + "LogoInfo": "Elija una imagen PNG y arrástrela para colocarla en el video.", + "NoSubtitlesGenerateAnyway": "No hay líneas de subtítulo para incrustar.\n\n¿Generar el vídeo sin subtítulos?", + "NoSubtitlesInXItemsGenerateAnyway": "{0} elemento(s) del lote no tienen líneas de subtítulo para incrustar.\n\n¿Generar esos vídeos sin subtítulos?" }, "VideoTransparent": { - "Title": "Generar vídeo transparente con subtítulos", - "InfoAssaOff": "Nota: Estilos Advanced SubStation Alpha compatibles.", - "InfoAssaOn": "Nota: ¡Se usarán estilos Advanced SubStation Alpha! :)" + "Title": "Generar vídeo transparente con subtítulos" }, "AudioToText": { "Title": "Voz a texto", @@ -2553,6 +2776,21 @@ "TranslateToEnglish": "Traducir al inglés", "Transcribing": "Transcribiendo...", "TranscribingXOfY": "Transcribiendo {0} de {1}...", + "LinesTranscribedXOfYFailedZ": "Se transcribieron {0} de {1} líneas; {2} fallaron o no tenían voz.\n\nLas líneas fallidas conservan su texto actual. ¿Aplicar las líneas transcritas o reintentar las fallidas (puede cambiar antes el motor o los ajustes)?", + "LinesTranscribedXOfYCancelled": "La transcripción se canceló tras {0} de {1} líneas.\n\n¿Aplicar las líneas transcritas? Las demás líneas conservan su texto actual.", + "NoLinesTranscribed": "No se pudo transcribir ninguna de las {0} líneas.\nConsulte el registro de herramientas para más detalles.", + "ApplyTranscribedLines": "Aplicar transcritas", + "RetryFailedLines": "Reintentar fallidas", + "IsolateSpeech": "Aislar la voz (lento)", + "IsolateSpeechHint": "Elimina la música y los efectos de sonido antes de transcribir. Se pierden menos líneas y la sincronización es mejor en audio con música alta, pero tarda aproximadamente lo mismo que el propio audio con una GPU, y muchas veces más sin ella.", + "IsolatingSpeech": "Aislando la voz...", + "IsolateSpeechFailed": "No se pudo aislar la voz: se transcribe el audio original en su lugar.", + "DetectSpeakers": "Detectar hablantes", + "DetectSpeakersHint": "Empieza cada línea con el hablante, como \"(speaker 0)\", usando la diarización Sortformer de NVIDIA (hasta 8 hablantes). Funciona mejor con motores que dividen las líneas donde cambia el hablante, como Parakeet y Cohere.", + "DetectSpeakersNeedsNewerCrispAsr": "\"Detectar hablantes\" necesita Crisp ASR v{0} o posterior, pero está instalada la v{1}.\n\nActualice Crisp ASR (el botón de engranaje junto al motor) o desactive \"Detectar hablantes\".", + "XNeedsNewerCrispAsr": "{0} necesita Crisp ASR v{1} o posterior, pero está instalada la v{2}.\n\n¿Descargar e instalar la actualización ahora?", + "Vad": "VAD", + "VadHint": "Detección de actividad de voz: solo se transcribe el habla, cortada donde el hablante hace pausas.\nAuto: Silero, y solo para los motores que lo necesitan (Cohere, Mega, Index-Echo).\nSilero, FireRedVAD, Whisper-VAD, WebRTC: siempre activos, para todos los motores Crisp ASR. FireRedVAD es el más preciso; Whisper-VAD da los cortes más escasos y limpios, pero es el más lento.\nIndex-Echo siempre usa Silero. --vad, --vad-model o --chunk-seconds en los ajustes avanzados tienen prioridad.", "InputLanguage": "Idioma de entrada", "AdvancedWhisperSettings": "Ajustes avanzados de Whisper", "DownloadingSpeechToTextEngine": "Descargando motor de voz a texto", @@ -2567,8 +2805,6 @@ "AddCustomModelDotDotDot": "Añadir modelo personalizado...", "CustomModelHelp": "Puede usar su propio modelo: elija el archivo del modelo (whisper.cpp ggml '.bin') o la carpeta del modelo (carpeta de faster-whisper con un 'model.bin' dentro). Se copia a la carpeta de modelos y se añade a la lista de arriba.", "ViewToolsLogFile": "Ver registro de Whisper", - "ReDownloadX": "Volver a descargar {0}", - "DownloadX": "Descargar {0}", "UpdateXTitle": "¿Actualizar {0}?", "UpdateXMessage": "Hay disponible una versión más reciente de {0}.{1}{1}¿Descargar e instalar la actualización ahora?", "DownloadingSpeechToTextModel": "Descargando modelo de voz a texto", @@ -2638,7 +2874,7 @@ "CloneVoicePerLinePreparing": "Tomando del vídeo la voz de cada línea...", "CloneVoicePerLineNeedsVideo": "Para clonar la voz de cada línea hace falta el vídeo al que pertenece el subtítulo. Abra el vídeo e inténtelo de nuevo.", "CloneVoicePerLineNoClips": "No se pudo tomar audio del vídeo, así que no hay nada de lo que clonar. Compruebe que el vídeo tiene pista de audio.", - "CloneVoicePerLineNeedsOriginalSubtitleX": "{0} solo puede clonar una línea cuando sabe qué se dice en el vídeo en esa línea, lo que proviene del subtítulo en el idioma original. No hay ningún subtítulo original cargado, así que todas las líneas las diría la primera voz importada en lugar de la voz del vídeo.\n\nAbra el subtítulo original (Archivo > Abrir original...) junto a la traducción y vuelva a generar.", + "CloneVoicePerLineTranscribeClipsXYZ": "{0} solo puede clonar una línea cuando sabe lo que se dice en el vídeo en esa línea. Eso no se sabe para {1} de {2} líneas; normalmente procede del subtítulo en el idioma original (Archivo > Abrir original...).\n\n¿Transcribir ahora esas líneas desde el vídeo con voz a texto?\n\nSí: elija el motor y el idioma hablado en el vídeo, y los clips de referencia se transcriben antes de generar la voz.\nNo: generar de todos modos; esas líneas las dice una voz normal en lugar de la voz del vídeo.", "VoiceCloneConsentTitle": "Clonación de voz: antes de empezar", "VoiceCloneConsentHeader": "Está a punto de clonar una voz", "VoiceCloneConsentIntro": "La clonación copia la voz de una persona real. Eso conlleva normas en la mayoría de los lugares y, en la UE, obligaciones legales que recaen en usted, no en Subtitle Edit. Léalo una vez, por favor.", @@ -2654,6 +2890,12 @@ "VoiceCloneConsentDeclined": "La clonación de voz no está disponible hasta que se acepten estos términos.", "AdvancedTtsAudioProcessing": "Procesamiento de audio", "AdvancedTtsOutput": "Salida", + "AdvancedTtsVoiceCloning": "Clonación de voz", + "CleanCloneReferences": "Limpiar referencias de clonación de voz (Sidon)", + "CleanCloneReferencesDescription": "Elimina música, ruido y reverberación del audio del que se clona una voz: clips tomados del vídeo y grabaciones importadas en el administrador de voces. Usa el modelo de restauración de voz Sidon en audio.cpp (descarga de 940 MB). La voz clonada ya no copia el fondo, pero el habla muy baja o superpuesta puede sonar apagada.", + "CleaningCloneReferencesDotDotDot": "Limpiando referencias de clonación de voz...", + "DownloadSidonTitle": "¿Descargar Sidon?", + "DownloadSidonQuestionX": "Limpiar las referencias de clonación de voz necesita el modelo de restauración de voz Sidon ({0}).\n\n¿Descargarlo ahora? Sin él, las referencias se usan tal cual.", "RubberbandInstalled": "(instalado)", "RubberbandNotFound": "(no se encontró en FFmpeg)", "GenerationFolder": "Carpeta de generación", @@ -2662,14 +2904,11 @@ "DeleteTempFilesDescription": "Elimina la carpeta de generación cuando se cierra la ventana Texto a voz. Los archivos de audio y vídeo guardados se conservan.", "PiperSettings": "Ajustes de Piper", "Title": "Texto a voz", - "TextToSpeechEngine": "Motor de texto a voz", "ReviewAudioSegments": "TTS - Revisar segmentos de audio", "ReviewAudioSegmentsHistory": "TTS - Revisar historial de audio", "Stability": "Estabilidad", "Similarity": "Similitud", "SpeakerBoost": "Refuerzo del orador", - "StyleExaggeration": "Exageración de estilo", - "RegenerateAudioSelectedLine": "Regenerar audio de línea seleccionada", "GenerateSpeechFromText": "Generar voz desde texto", "TestVoice": "Probar voz", "AddAudioToVideoFile": "Añadir audio al archivo de vídeo", @@ -2688,22 +2927,98 @@ "VideoEncodingSettings": "TTS - Ajustes de codificación de vídeo", "ElevenLabsSettings": "TTS - Ajustes de ElevenLabs", "ElevenLabsSettingsResetHint": "Restablecer ElevenLabs a valores predeterminados", + "Provider": "Proveedor", + "OpenAiCompatibleSettings": "TTS - Ajustes compatibles con OpenAI", + "CustomServerUrl": "URL de servidor personalizado", + "CustomServerUrlHint": "Endpoint de voz completo de un servidor compatible con OpenAI, usado cuando el proveedor es \"Personalizado\", p. ej. http://localhost:8880/v1/audio/speech", + "CustomModels": "Modelos personalizados", + "CustomModelsHint": "Nombres de modelos separados por comas que se ofrecen cuando el proveedor es \"Personalizado\"", + "CustomVoices": "Voces personalizadas", + "CustomVoicesHint": "Nombres de voces separados por comas, usados por el proveedor \"Personalizado\" y por los modelos de OpenRouter que no publican lista de voces", + "Instructions": "Instrucciones", + "InstructionsHint": "Estilo de habla opcional, p. ej. \"Habla con una voz tranquila y cálida\". Solo se envía a los modelos que lo admiten (no tts-1/tts-1-hd)", + "OpenAiSpeedHint": "Velocidad del habla de 0.25 a 4.0 (1.0 es normal). No todos los modelos la admiten", + "OutputFormat": "Formato de salida", + "OutputFormatHint": "Formato de audio solicitado al servidor. \"Auto\" pide MP3 y cambia a PCM para los modelos que solo devuelven PCM (p. ej. Gemini TTS)", "RegenerateAudio": "Regenerar audio", "AutoContinuePlaying": "Continuar reproducción automáticamente", "AddingAudioToVideoFileDotDotDot": "Añadiendo audio al archivo de vídeo...", "PreparingMergeDotDotDot": "Preparando combinación...", "ImportVoiceDotDotDot": "Importar voz...", + "RenameVoiceDotDotDot": "Renombrar voz...", + "RenameVoiceTitle": "Renombrar voz", + "VoiceXCouldNotBeRenamedX": "No se pudo renombrar la voz '{0}': {1}", + "DeleteVoiceDotDotDot": "Eliminar voz...", + "DeleteVoiceTitle": "Eliminar voz", + "DeleteVoiceXQuestion": "¿Eliminar la voz '{0}'?\n\nLa grabación de referencia y sus archivos se eliminan del disco.", + "VoiceXCouldNotBeDeletedX": "No se pudo eliminar la voz '{0}': {1}", "VoiceImportSuccessTitle": "Voz importada", "VoiceXImported": "Voz '{0}' importada con éxito", "DropAudioFileHereToImportVoice": "Suelte un archivo de audio aquí para importar una voz", "DropAudioFileHereHint": ".wav o .mp3", "VoiceCloneTranscriptTitle": "Introduzca la transcripción del audio (necesaria para la clonación de voz)", + "VoiceManagerTitle": "TTS - Administrador de voces", + "VoiceManagerDotDotDot": "Administrador de voces...", + "VoiceManagerMenuItem": "Administrador de voces TTS...", + "SpeakFromCurrentLineTitle": "Leer desde la línea actual", + "SpeakFromCurrentLineDotDotDot": "Leer desde la línea actual...", + "SpeakFromCurrentLineHint": "Lee las líneas en voz alta una tras otra, empezando por la línea actual. Pulse Escape o haga clic en otra línea para detener.", + "StopSpeaking": "Dejar de leer", + "SpeakingLineX": "Leyendo la línea {0}... (Escape para detener)", + "PlayWithSpeechTitle": "Reproducir con voz", + "PlayWithSpeechDotDotDot": "Reproducir con voz...", + "PlayWithSpeechHint": "Reproduce el vídeo y lee cada línea cuando el vídeo llega a ella. Pause y busque como siempre; pulse Escape para detener.", + "LowerVideoVolumeWhileSpeaking": "Bajar el volumen del vídeo mientras se lee", + "PauseVideoWhenSpeechRunsLate": "Pausar el vídeo cuando la voz invade la línea siguiente", + "WaitingForSpeechLineX": "Generando voz para la línea {0}... (Escape para detener)", + "VoiceKindClone": "Voz clonada", + "VoiceKindPreset": "Integrada", + "VoiceKindModel": "Modelo de voz", + "VoiceKindOnline": "En línea", + "Transcript": "Transcripción", + "TranscriptRequiredHint": "Obligatorio: el motor lee esta transcripción de la grabación (ref-text) al clonar", + "TranscriptOptionalHint": "Opcional: una transcripción precisa de la grabación mejora la clonación", + "SaveTranscript": "Guardar transcripción", + "SearchVoices": "Buscar voces", + "LoadingVoicesDotDotDot": "Cargando voces...", + "NoVoicesForEngine": "No hay voces: importe una grabación o descargue un paquete de voces", + "NoVoicesMatchFilter": "Ninguna voz coincide con la búsqueda", + "CopyVoiceTo": "Copiar voz a", + "CopyVoiceToDotDotDot": "Copiar voz a motor...", + "CopyingVoiceXToYDotDotDot": "Copiando la voz '{0}' a {1}...", + "VoiceXCopiedToY": "Voz '{0}' copiada a {1}", + "VoiceXCouldNotBeCopiedToY": "No se pudo copiar la voz '{0}' a {1}; consulte el registro para más detalles", + "VoiceXAlreadyExistsInYContinue": "Ya existe una voz llamada '{0}' en {1}.\n\n¿Copiar de todos modos?", + "OpenVoicesFolder": "Abrir carpeta de voces", + "DownloadVoicePacksDotDotDot": "Descargar paquetes de voces...", + "DownloadVoicePacksTitle": "TTS - Descargar paquetes de voces", + "VoicePackIntro": "Los paquetes de voces son conjuntos de grabaciones cortas y limpias con transcripciones, listas para clonar. Cada paquete se importa en el motor elegido (se omiten las voces ya presentes) e incluye un ATTRIBUTION.txt con su licencia y fuentes.", + "VoicePackDetailsXVoicesYSizeZLicense": "{0} voces · {1} · {2}", + "InstallToEngine": "Instalar en motor", + "DownloadAndInstall": "Descargar e instalar", + "SelectAtLeastOneVoicePack": "Seleccione al menos un paquete de voces", + "DownloadingXDotDotDot": "Descargando {0}...", + "InstallingXDotDotDot": "Instalando {0}...", + "XVoicesInstalledYSkipped": "{0} voces instaladas ({1} ya presentes)", "UseSpeechToTextDotDotDot": "Usar voz a texto...", "AdvancedTtsSettings": "Ajustes avanzados de TTS", "ProAudioPostProcessing": "Post-procesamiento de audio Pro", "ProAudioPostProcessingDescription": "Aplica EQ, puerta de ruido, compresión, normalización (-16 LUFS) y fundidos a cada segmento.", + "GeneratingSpeechSegmentXOfY": "Generando voz: segmento {0} de {1}", + "AdjustingSpeedSegmentXOfY": "Ajustando velocidad: segmento {0} de {1}", + "MergingAudioSegmentXOfY": "Combinando audio: segmento {0} de {1}", + "PostProcessingSegmentXOfY": "Posprocesando: segmento {0} de {1}", "AudioDucking": "Atenuación de audio (ducking)", "AudioDuckingDescription": "Reduce el volumen del vídeo original para mezclarlo con el TTS.", + "RemoveOriginalSpeech": "Eliminar la voz original (lento)", + "RemoveOriginalSpeechDescription": "Conserva la música y los efectos de sonido del vídeo original pero elimina su voz, y mezcla la nueva voz sobre ellos. Tarda aproximadamente lo mismo que el propio vídeo con una GPU, y muchas veces más sin ella. Con la atenuación de audio activada, su volumen se aplica a la música y los efectos. Solo se aplica cuando la voz se añade al archivo de vídeo.", + "RemovingOriginalSpeech": "Eliminando la voz original...", + "RemoveOriginalSpeechFailed": "No se pudo eliminar la voz original: la voz se añadió al vídeo sin hacerlo.", + "SpeechRemovalModel": "Modelo", + "SpeechRemovalModelDescription": "Mejor calidad conserva al máximo la música y los efectos de sonido. Rápido es unas tres veces más veloz, con algo más de pérdida de música.", + "SpeechRemovalBestQuality": "Mejor calidad (Mel-Band RoFormer)", + "SpeechRemovalFast": "Rápido (HTDemucs)", + "VideoTruncatedWarning": "La imagen del nuevo archivo de vídeo se detiene tras {0}, pero el vídeo original dura {1}: la imagen se congelará mientras el sonido continúa.\n\nSe conservó el archivo. Consulte error-log.txt en la carpeta de datos de Subtitle Edit para ver la salida de ffmpeg.", "OriginalVolumePercent": "Volumen original %", "VadSilenceCompression": "Compresión de silencios VAD", "VadSilenceCompressionDescription": "Acorta las pausas entre palabras mediante detección de voz. Reduce la duración sin pérdida de calidad.", @@ -2770,7 +3085,6 @@ "SubtitleMergedLinesAppliedPlural": "Se aplicaron {0} combinaciones de líneas desde texto a voz" }, "ShotChanges": { - "TitleGenerateOrImport": "Generar/importar cambios de plano", "GenerateShotChanges": "Generar cambios de plano", "ImportShotChanges": "Importar cambios de plano", "GenerateShotChangesWithFfmpeg": "Generar cambios de plano con ffmpeg", @@ -2780,7 +3094,9 @@ "TimeCodeFormatColon": "Formato de código de tiempo:", "ShotChangesList": "Lista de cambios de plano", "ShotChangesClearQuestion": "¿Seguro que desea borrar todos los cambios de plano?", - "DeleteSelectedShotChangeQuestion": "¿Seguro que desea eliminar el cambio de plano seleccionado?" + "DeleteSelectedShotChangeQuestion": "¿Seguro que desea eliminar el cambio de plano seleccionado?", + "ShotChanges": "Cambios de plano", + "ExportShotChanges": "Exportar cambios de plano" }, "VideoOcr": { "Title": "OCR de subtítulo incrustado", @@ -2790,10 +3106,6 @@ "BottomHalf": "Mitad inferior", "FullFrame": "Fotograma completo", "Engine": "Motor OCR", - "Language": "Idioma", - "Url": "URL", - "Model": "Modelo", - "ApiKey": "Clave API", "Scan": "Escanear", "FramesPerSecond": "Fotogramas por segundo", "TextBrightnessMinimum": "Brillo mínimo del texto (0=desactivado)", @@ -2808,10 +3120,10 @@ "RunningOcrXY": "Ejecutando OCR... {0}/{1}", "NoLinesFoundTitle": "No se encontraron subtítulos", "NoLinesFoundMessage": "No se encontró texto en el área de escaneo: pruebe a ajustar el área de escaneo, el motor o el brillo mínimo.", + "OcrStoppedEarlyMessage": "El motor OCR se detuvo antes de leer todos los fotogramas, por lo que se conservan las líneas encontradas hasta ahora y faltan las demás.{0}{0}{1}", "LinesFoundX": "{0} líneas encontradas", "RefiningTimingXY": "Afinando los tiempos... {0}/{1}", "FixOcrErrors": "Corregir errores de OCR", - "Dictionary": "Diccionario", "EditLineX": "Editar la línea {0}", "DownloadDictionaryHint": "Descargar un diccionario ortográfico...", "FramesPerSecondHint": "Cuántos fotogramas por segundo se analizan. 5 cubre los subtítulos normales; un valor mayor detecta líneas muy cortas y agrupa mejor, pero cuesta más tiempo de OCR.", @@ -2828,14 +3140,64 @@ "TestOcr": "Probar el fotograma actual", "TestOcrRunning": "Probando el OCR en el fotograma actual...", "TestOcrResultX": "Resultado de la prueba: {0}", - "TestOcrNoTextFound": "Prueba: no se encontró texto en el área de escaneo" + "TestOcrNoTextFound": "Prueba: no se encontró texto en el área de escaneo", + "EngineSettings": "Ajustes del motor OCR", + "EngineBuiltIn": "Integrado en el sistema operativo", + "EngineExternalServer": "Servidor externo (instalado y gestionado fuera de Subtitle Edit)", + "EngineCloudApi": "API en la nube (requiere una clave API)", + "EngineNothingToInstall": "Nada que instalar", + "EngineModelsMissing": "Motor instalado, faltan modelos", + "Website": "Sitio web" + }, + "BackgroundMusic": { + "Title": "Generar música de fondo", + "GenerateBackgroundMusicDotDotDot": "Generar música de fondo...", + "Preset": "Preajuste", + "Prompt": "Indicación", + "TempoBpm": "Tempo (BPM)", + "GenerateSeconds": "Generar (segundos)", + "Seed": "Semilla", + "RandomSeed": "Aleatoria", + "OutputLength": "Duración de salida", + "OutputLengthVideoX": "{0} (duración del vídeo; la música se repite sin cortes)", + "MusicVolumePercent": "Volumen de la música (%)", + "RemoveExistingAudioTracks": "Eliminar pistas de audio existentes", + "OriginalAudioVolumePercent": "Volumen del audio original (%)", + "SaveAudioDotDotDot": "Guardar audio...", + "AddToVideoDotDotDot": "Añadir al vídeo...", + "EngineInfo": "ACE-Step 1.5 Turbo mediante audio.cpp (licencia MIT): se ejecuta localmente", + "PresetCooking": "Cocina / repostería", + "PresetTech": "Tecnología / reseña de gadgets", + "PresetForest": "Bosque / naturaleza", + "PresetRelaxingPiano": "Piano relajante", + "PresetLofiVlog": "Vlog lo-fi", + "PresetCorporate": "Corporativo / explicativo", + "LoadingModel": "Cargando modelo...", + "Composing": "Componiendo...", + "GeneratingAudio": "Generando audio...", + "DecodingAudio": "Decodificando audio...", + "CreatingLoop": "Creando bucle sin cortes...", + "ResultLoopXYZ": "Bucle: {0} a {1} BPM, coincidencia de unión {2}%", + "ResultNoLoopX": "Música lista: {0}", + "AddingMusicToVideo": "Añadiendo música al vídeo...", + "DownloadModelTitle": "¿Descargar modelo de música?", + "DownloadModelQuestionX": "Generar música requiere el modelo ACE-Step 1.5 ({0}).\n\n¿Descargar el modelo?", + "DownloadingModel": "Descargando modelo ACE-Step 1.5...", + "SaveAudioTitle": "Guardar música de fondo", + "AudioFileSavedX": "Archivo de audio guardado: {0}", + "UnableToGenerateMusic": "No se pudo generar la música", + "UnableToAddMusicToVideo": "No se pudo añadir la música al vídeo", + "OutputLengthTextToSpeech": "Sigue la voz generada: la música se repite sin cortes y baja mientras alguien habla", + "AddBackgroundMusic": "Añadir música de fondo", + "AddBackgroundMusicHint": "Genera música localmente con ACE-Step 1.5 y la mezcla bajo la voz; baja mientras alguien habla", + "GeneratingBackgroundMusicDotDotDot": "Generando música de fondo...", + "MixingBackgroundMusicDotDotDot": "Mezclando música de fondo..." }, "Voices": "Voces", "Presets": "Preajustes", "TalkerX": "Hablante {0}", "BaseModel": "Modelo base", "TurboModel": "Modelo turbo", - "TtsUpdateX": "Actualizar {0}", "TtsEngineNotInstalled": "{0} no está instalado", "TtsEngineUpdateAvailable": "{0} - actualización disponible", "TtsEngineTooOldUpdateRequired": "{0} es demasiado antiguo: se requiere actualizar", @@ -2858,19 +3220,25 @@ "CloneVoiceTo": "Clonar voz en", "CloneVoiceNameTitle": "Nombre de la nueva voz", "CloneVoiceExtractFailed": "No se pudo extraer el audio de la línea seleccionada.", + "LineXHasNoDuration": "La línea {0} no tiene duración (el tiempo de fin no es posterior al de inicio), por lo que no hay audio que extraer.", + "AudioClipsSkippedLinesX": "No se pudo extraer audio para estas líneas, así que se omitieron: {0}", "ToggleShotChange": "Alternar cambio de plano", "ResetWaveformZoomAndSpeed": "Restablecer zoom y velocidad", "ShowOriginalSubtitle": "Mostrar el subtítulo original", + "TimelineGroupTracksBy": "Agrupar pistas de subtítulos por", + "TimelineShowThumbnails": "Mostrar miniaturas del vídeo", + "ShowSpeechOnly": "Mostrar solo voz (lento de generar)", + "IsolatingSpeechForWaveformX": "Aislando la voz para la forma de onda... {0}", + "IsolatingSpeechForWaveformFailed": "No se pudo aislar la voz para la forma de onda; consulte el registro de herramientas", + "SpeechOnlyWaveformReady": "La forma de onda ahora muestra solo la voz", "ShowOnlyWaveform": "Solo forma de onda", "ShowOnlySpectrogram": "Solo espectrograma", "ShowWaveformAndSpectrogram": "Forma de onda y espectrograma", - "SpectrogramClassic": "Clásico", "SpectrogramClassicViridis": "Viridis", "SpectrogramClassicPlasma": "Plasma", "SpectrogramClassicInferno": "Inferno", "SpectrogramClassicTurbo": "Turbo", "SpectrogramNeon": "Neón", - "WaveformDrawStyleClassic": "Clásico", "WaveformDrawStyleFancy": "Moderno", "SetVideoPositionAndPauseAndSelectSubtitle": "Pos. vídeo, pausar y seleccionar subtítulo", "SetVideopositionAndPauseAndSelectSubtitleAndCenter": "Pos. vídeo, pausar, seleccionar y centrar", @@ -2880,7 +3248,6 @@ }, "Sync": { "VisualSync": "Sincronización visual", - "Sync": "Sincronizar", "StartScene": "Escena inicial", "EndScene": "Escena final", "PlayTwoSecondsAndBack": "Reprod. 2s y atrás", @@ -2896,19 +3263,18 @@ "AdjustSelectedLinesAndForward": "Ajustar líneas sel. y adelante", "ToFrameRate": "A tasa fotogramas", "FromFrameRate": "Desde tasa fotogramas", + "VideoXFrameRateY": "{0} - {1} fps", "AdjustAllTimes": "Ajustar todos los tiempos (antes/después)", "ShowEarlier": "Mostrar antes", "ShowLater": "Mostrar después", - "ChangeFrameRate": "Cambiar tasa fotogramas", "SetSyncPoint": "Establecer punto sincro", "SetSyncPointViaVideo": "Establecer punto de sincronización con el vídeo...", "SyncPointTimeCode": "Código de tiempo del punto de sincronización", "SyncPoints": "Puntos sincro", "PointSync": "Sincronización puntos", "PointSyncViaOther": "Sincronización puntos vía otro subtítulo", - "AdjustmentX": "Ajuste: {0}", - "TotalAdjustmentX": "Ajuste total: {0}", - "SyncPointCandidateInfo": "Se resaltan las líneas que empiezan tras {0}+ segundos de silencio: suelen ser buenos puntos de sincronización", + "SyncPointCandidateAfterInfo": "Se resaltan las líneas seguidas de {0}+ segundos de silencio; suelen ser puntos de sincronización fiables", + "GapAfter": "Espacio después", "AdjustAllShortcuts": "Atajos de teclado:\r\n\r\n• Mayús + Izq/Der: Mover 10 ms\r\n• Ctrl + Izq/Der: Mover 100 ms\r\n• Alt + Izq/Der: Mover 500 ms", "AdjustAllShortcutsFrames": "Atajos de teclado:\r\n\r\n• Mayús + Izq/Der: Mover 1 fotograma\r\n• Ctrl + Izq/Der: Mover 10 fotogramas\r\n• Alt + Izq/Der: Mover 1 segundo", "OffsetInSeconds": "Desfase en segundos", @@ -2928,6 +3294,7 @@ "LineMerge": "Unir líneas", "DelayInSecondsBetweenRequests": "Retraso en segundos entre peticiones", "MaxBytesPerRequest": "Bytes máx. por petición", + "KeepMusicLinesUntranslated": "No traducir líneas entre símbolos musicales (♪ ... ♪)", "PromptText": "Texto del prompt", "ResetPromptToDefault": "Restablecer el prompt predeterminado", "TranslateEachLineSeparately": "Traduce cada línea por separado", @@ -2941,11 +3308,10 @@ "ReadyToTranslate": "Listo para traducir", "Translating": "Traduciendo...", "TranslationComplete": "Traducción completada", + "TranslateInPlaceNoOriginal": "Traducir en el sitio (no crear original)", "TranslationCancelled": "Traducción cancelada", "SwapLanguages": "Intercambiar idiomas de origen y destino", "XIsAlreadyDownloadedReDownload": "{0} ya está descargado. ¿Volver a descargar?", - "AdvancedDotDotDot": "Avanzado...", - "AdvancedSettings": "Ajustes avanzados", "AdvancedSettingsSubtitle": "Opciones de lote, contexto y muestreo para el motor avanzado llama.cpp", "Context": "Contexto", "Synopsis": "Sinopsis", @@ -2969,6 +3335,7 @@ "UseOnlyExtraServerParametersHint": "Inicia llama-server con los parámetros anteriores en lugar del ajuste propio de Subtitle Edit; solo se siguen estableciendo el modelo, el host y el puerto", "ServerRunningAtX": "Servidor en ejecución en {0}", "CustomPromptHint": "Instrucciones personalizadas ({0} = idioma de origen, {1} = idioma de destino); vacío = prompt integrado", + "EditTranslationHint": "Haga clic en la traducción de la fila seleccionada para editarla (Enter guarda, Escape cancela)", "LlamaCppDownloadEngineAndModelPrompt": "llama.cpp necesita que se descarguen el motor llama-server y un modelo de traducción. ¿Descargar ahora?", "LlamaCppDownloadEnginePrompt": "llama.cpp necesita que se descargue el motor llama-server. ¿Descargar ahora?", "LlamaCppDownloadModelPrompt": "llama.cpp necesita que se descargue el modelo de traducción seleccionado. ¿Descargar ahora?" @@ -2995,14 +3362,6 @@ "WaveformSnapToShotChangeSameShotEndMaxSeconds": "Ajustar al cambio de plano más cercano: distancia máx. del fin cuando el inicio y el fin comparten corte (segundos)", "WaveformGuessStartOffsetMs": "Estimar el tiempo de inicio desde la forma de onda: adelantar el inicio (ms)", "WaveformGuessEndOffsetMs": "Estimar el tiempo de fin desde la forma de onda: retrasar el fin (ms)", - "ShowToolbarMultipleReplace": "Mostrar el icono de reemplazo múltiple", - "ShowToolbarPointSync": "Mostrar el icono de sincronización por puntos", - "ShowToolbarAutoTranslate": "Mostrar el icono de traducción automática", - "ShowToolbarSpeechToText": "Mostrar el icono de voz a texto", - "ShowToolbarStyleManager": "Mostrar el icono del gestor de estilos (ASSA/SSA/WebVTT)", - "ShowToolbarProperties": "Mostrar el icono de propiedades (ASSA/SSA)", - "ShowToolbarAttachments": "Mostrar el icono de adjuntos (ASSA/SSA)", - "ShowToolbarAssaDraw": "Mostrar el icono de dibujo ASSA (ASSA)", "ProxyDomain": "Dominio", "ProxyUseSystemCredentials": "Usar las credenciales del sistema", "ProxyBypassList": "Omitir el proxy para", @@ -3014,8 +3373,14 @@ "CheckForUpdatesChannelStableAndBeta": "Versiones estables y beta", "AssaAutoSetResolution": "ASSA: establecer la resolución (PlayResX/PlayResY) desde el vídeo al abrir un vídeo", "AssaAutoSetResolutionPrompt": "ASSA: preguntar antes de cambiar la resolución para que coincida con el vídeo (si no, remuestrear automáticamente)", + "VideoControls": "Controles de vídeo", + "VideoControlsEdit": "Editar controles de vídeo...", + "VideoControlsPositionText": "Texto de posición / duración", + "VideoControlsVideoFileName": "Nombre del archivo de vídeo", + "VideoControlsPlayerName": "Nombre del reproductor de vídeo", "MergeKeepEndTime": "Fusionar líneas: mantener el tiempo de fin (permitir superposición con el subtítulo siguiente)", "MergeKeepEndTimeOnlyAssa": "Fusionar líneas: mantener el tiempo de fin solo para \"Advanced Sub Station Alpha\"", + "MergeAutoBreak": "Combinar líneas: división automática del texto combinado (desactivado conserva los saltos de línea propios)", "DefaultSaveLocation": "Ubicación de guardado predeterminada", "DefaultSaveLocationSourceFileFolder": "Carpeta del archivo de subtítulos/vídeo", "DefaultSaveLocationLastUsedFolder": "Última carpeta usada", @@ -3023,6 +3388,7 @@ "DefaultSaveLocationSubtitleFileFolder": "Carpeta del archivo de subtítulos", "DefaultSaveLocationCustomFolder": "Carpeta personalizada", "SubtitleGridFormattingHideTags": "Ocultar etiquetas", + "SubtitleGridFormattingShowFormattingKeepTags": "Mostrar formato, conservar etiquetas no visuales", "MusicSymbolsToReplace": "Símbolos musicales a reemplazar (separados por comas)", "MusicSymbol": "Símbolo musical", "ColorWordsPerMinute": "Colorear palabras/min si es muy alto", @@ -3033,7 +3399,6 @@ "DialogStyleDashBothLinesWithSpace": "Guiones ambas líneas con espacio", "DialogStyleDashBothLinesWithoutSpace": "Guiones ambas líneas sin espacio", "ContinuationStyle": "Estilo de continuación", - "ContinuationStyleNone": "Ninguno", "ContinuationStyleNoneTrailingDots": "Ninguno, puntos para pausas (solo al final)", "ContinuationStyleNoneLeadingTrailingDots": "Ninguno, puntos para pausas", "ContinuationStyleNoneTrailingEllipsis": "Ninguno, elipsis para pausas (solo al final)", @@ -3044,7 +3409,6 @@ "ContinuationStyleLeadingTrailingEllipsis": "Elipsis", "ContinuationStyleLeadingTrailingDash": "Guion", "ContinuationStyleLeadingTrailingDashDots": "Guion, pero puntos para pausas", - "ContinuationStyleCustom": "Personalizado", "CpsLineLengthStyle": "CPS/Longitud línea", "CpsLineLengthStyleCalcAll": "Contar todos los caracteres", "CpsLineLengthStyleCalcNoSpaceCpsOnly": "Contar todo menos espacios, solo CPS", @@ -3057,27 +3421,11 @@ "CpsLineLengthStyleCalcNoSpaceOrPunctuationCpsOnly": "Sin espacios ni puntuación, solo CPS", "CpsLineLengthStyleCalcIgnoreArabicDiacritics": "Ignorar diacríticos árabes", "CpsLineLengthStyleCalcIgnoreArabicDiacriticsNoSpace": "Ignorar diacríticos árabes, sin espacios", - "TimeCodeModeHhMmSsMs": "HH:MM:SS:MS", - "TimeCodeModeHhMmSsFf": "HH:MM:SS:FF", - "SplitBehaviorPrevious": "Hueco a la izq. de división (foco derecha)", - "SplitBehaviorHalf": "Hueco en centro de división (foco izquierda)", - "SplitBehaviorNext": "Hueco a la der. de división (foco izquierda)", - "SubtitleListActionNothing": "Nada", - "SubtitleListActionVideoGoToPositionAndPause": "Ir a pos. vídeo y pausar", - "SubtitleListActionVideoGoToPositionAndPlay": "Ir a pos. vídeo y reproducir", "SubtitleListActionVideoGoToPositionAndPlayCurrentAndPause": "Ir a pos. vídeo, reprod. actual y pausar", - "SubtitleListActionEditText": "Ir a cuadro de edición", "SubtitleListActionVideoGoToPositionMinus1SecAndPause": "Ir a pos. vídeo - 1 s y pausar", "SubtitleListActionVideoGoToPositionMinusHalfSecAndPause": "Ir a pos. vídeo - 0.5 s y pausar", "SubtitleListActionVideoGoToPositionMinus1SecAndPlay": "Ir a pos. vídeo - 1 s y reproducir", - "SubtitleListActionEditTextAndPause": "Ir a cuadro edición y pausar en pos. vídeo", - "AutoBackupEveryMinute": "Cada minuto", - "AutoBackupEveryXthMinute": "Cada {0} minutos", - "Profiles": "Perfiles", - "AutoBackupDeleteAfterXMonths": "Eliminar copias tras {0} meses", "SearchSettingsDotDotDot": "Buscar ajustes...", - "SyntaxColoring": "Coloreado de sintaxis", - "WaveformSpectrogram": "Forma de onda/espectrograma", "Network": "Red", "FileTypeAssociations": "Asociaciones de archivo", "TextBoxColorTags": "Etiquetas color (HTML/ASSA) en cuadro de edición", @@ -3110,10 +3458,9 @@ "DarkThemeForegroundColor": "Color de frente (tema oscuro)", "DarkThemeBackgroundColor": "Color de fondo (tema oscuro)", "ShowGridLines": "Mostrar líneas de cuadrícula", - "ResetSettings": "¿Restablecer ajustes?", - "ResetSettingsDetail": "Se restablecerán todos los ajustes a sus valores por defecto.\n\n¿Continuar?", "ShowHorizontalLineAboveToolbar": "Línea horizontal sobre barra herramientas", "BookmarkColor": "Color de marcadores", + "SpellCheckHighlightColor": "Color de resaltado de la ortografía", "SingleLineMaxLength": "Longitud máx. línea única", "OptimalCharsPerSec": "CPS óptimos", "MaxCharsPerSec": "CPS máximos", @@ -3126,13 +3473,21 @@ "UnbreakSubtitlesShortThan": "Unir subtítulos más cortos que", "NewEmptyDefaultMs": "Duración predet. nuevo subtítulo (ms)", "TimeCodeUpDownStepMs": "Incremento de tiempo arriba/abajo (ms)", + "MoveSelectedLinesStepMs": "Paso del atajo para mover líneas seleccionadas (ms)", + "MoveStartEndStepMs": "Paso del atajo para mover inicio/fin (ms)", + "MoveLinesShortenNeighbor": "Mover líneas: acortar la línea anterior/siguiente en lugar de superponerla", "PromptBeforeDelete": "Preguntar antes de borrar", "RememberPositionAndSize": "Recordar posición y tamaño de ventana", + "TitleBarFullFileName": "Mostrar la ruta completa del archivo en la barra de título", + "ShowRecentFiles": "Mostrar archivos recientes (para reabrir)", "OpenLastFileOnStart": "Abrir el último archivo reciente al iniciar", "AutoSave": "Guardado automático (guardar el archivo abierto mientras se edita)", "AutoBackupOn": "Copia seguridad automática", "AutoBackupIntervalMinutes": "Intervalo copia seguridad (min)", "AutoBackupDeleteAfterDays": "Días de retención de copias", + "SettingsBackupOn": "Copia de seguridad automática de los ajustes", + "SettingsBackupIntervalDays": "Intervalo de copia de los ajustes (días, 0 = en cada inicio)", + "SettingsBackupMaxCount": "Copias de los ajustes a conservar", "AutoConvertToUtf8": "Auto-convertir a UTF-8 al abrir", "AutoTrimWhiteSpace": "Auto-recortar espacios", "DefaultEncoding": "Codificación predeterminada", @@ -3146,23 +3501,14 @@ "ColorGapTooShort": "Color si espacio es muy corto", "ErrorBackgroundColor": "Color fondo error", "WaveformDrawGridLines": "Dibujar líneas cuadrícula", - "WaveformCenterVideoPosition": "Centrar posición vídeo", + "WaveformUseSkiaRenderer": "Usar renderizador rápido experimental", "WaveformCenterVideoPositionAlsoWhenPaused": "Centrar la posición del vídeo también en pausa", + "WaveformSelectCurrentSubtitleWhilePaused": "Seleccionar el subtítulo actual también en pausa", + "WaveformRememberZoom": "Recordar zoom (horizontal y vertical)", "WaveformShowToolbar": "Mostrar barra de herramientas", "WaveformShowToolbarEdit": "Editar barra herramientas...", "WaveformShowToolbarEditLabel": "Elementos de la barra de herramientas", "WaveformSpectrogramCombinedWaveformHeight": "Onda/espectro combinado, altura onda %", - "ShowWaveformToolbarPlay": "Barra: botón reproducir", - "ShowWaveformToolbarRepeat": "Barra: botón repetir", - "ShowWaveformToolbarRemoveBlankLines": "Barra: botón quitar líneas blanco", - "ShowWaveformToolbarNew": "Barra: botón nuevo subtítulo", - "ShowWaveformToolbarSetStart": "Barra: botón fijar inicio", - "ShowWaveformToolbarSetEnd": "Barra: botón fijar fin", - "ShowWaveformToolbarStartAndOffsetTheRest": "Barra: botón fijar inicio y desfasar resto", - "ShowWaveformToolbarHorizontalZoom": "Barra: deslizador zoom horizontal", - "ShowWaveformToolbarVerticalZoom": "Barra: deslizador zoom vertical", - "ShowWaveformToolbarVideoPositionSlider": "Barra: deslizador posición vídeo", - "ShowWaveformToolbarPlaybackSpeed": "Barra: velocidad reproducción", "WaveformFocusTextboxAfterInsertNew": "Foco en cuadro texto tras insertar", "WaveformInvertMouseWheel": "Invertir rueda ratón", "WaveformMouseWheelSetsVideoPosition": "La rueda del ratón fija la posición del vídeo", @@ -3181,27 +3527,8 @@ "WaveformSelectedColor": "Color de onda seleccionada", "DownloadFfmpeg": "Descargar ffmpeg", "FixCommonErrorsSkipStep1": "Corregir errores comunes: omitir el paso 1 (elegir correcciones)", - "ShowToolbarNew": "Icono nuevo", - "ShowToolbarOpen": "Icono abrir", - "ShowToolbarVideoFileOpen": "Icono abrir vídeo", - "ShowToolbarSave": "Icono guardar", - "ShowToolbarSaveAs": "Icono guardar como", - "ShowToolbarFind": "Icono buscar", - "ShowToolbarReplace": "Icono reemplazar", - "ShowToolbarSpellCheck": "Icono ortografía", - "ShowToolbarFixCommonErrors": "Icono errores comunes", - "ShowToolbarRemoveTextForHi": "Mostrar icono de eliminar texto para sordos", - "ShowToolbarVisualSync": "Mostrar icono de sincronización visual", - "ShowToolbarBeautifyTimeCodes": "Mostrar icono de embellecer códigos de tiempo", - "ShowToolbarBurnIn": "Mostrar icono de incrustación", - "ShowToolbarSettings": "Icono ajustes", - "ShowToolbarLayout": "Icono diseño", - "ShowToolbarHelp": "Icono ayuda", - "ShowToolbarEncoding": "Mostrar codificación", - "ShowToolbarFrameRate": "Mostrar tasa fotogramas", "ShowPluginsMenu": "Mostrar el menú Plugins", "ProxyAddress": "Dirección proxy", - "Username": "Usuario", "Password": "Password", "DefaultFormat": "Formato predeterminado", "DefaultSaveAsFormat": "Formato predeterminado \"Guardar como\"", @@ -3209,10 +3536,13 @@ "FavoriteLanguages": "Idiomas favoritos", "WebVttUseXTimestampMap": "WebVTT: usar X-TIMESTAMP-MAP (desfasar códigos de tiempo al cargar)", "FullscreenHideControls": "Ocultar los controles de vídeo en pantalla completa", + "ShowSecondarySubtitleDialog": "Mostrar diálogo de estilo al abrir un archivo de subtítulo secundario", + "RememberSecondarySubtitleFile": "Recordar archivo de subtítulo secundario", "AutoOpenVideoFile": "Abrir vídeo al abrir subtítulos", "DownloadMpv": "Descargar mpv", "DownloadVlc": "Descargar VLC", "AllowSingleLetterShortcutsInTextbox": "Permitir atajos de una sola letra en el cuadro de texto", + "AllowTextNavigationShortcutsInTextbox": "Permitir atajos en teclas de navegación de texto (Ctrl+Izquierda/Derecha, Inicio/Fin) en el cuadro de texto", "SpellCheckEnglishTreatInApostropheAsIng": "Ortografía: tratar las palabras terminadas en 'in'' como 'ing' (solo inglés)", "GoToLineNumberSetsVideoPosition": "Ir a línea también mueve vídeo", "AdjustAllTimesRememberLineSelectionChoice": "Recordar elección selección líneas al ajustar tiempos", @@ -3224,6 +3554,7 @@ "ShowAssaLayer": "Mostrar cuadro capa ASSA", "WaveformCursorColor": "Color cursor/cabezal onda", "WaveformShotChangeColor": "Color de cambio de plano en la forma de onda", + "WaveformGridColor": "Color de la cuadrícula de la forma de onda", "WaveformParagraphLeftColor": "Color borde izquierdo onda", "WaveformParagraphRightColor": "Color borde derecho onda", "WaveformFancyHighColor": "Color onda moderno (alto)", @@ -3251,27 +3582,31 @@ "ResetSyntaxColoring": "Restablecer color sintaxis", "ResetWaveform": "Restablecer forma de onda", "ResetRules": "Restablecer reglas", - "UseFrameMode": "Usar modo fotogramas (hh.mm.ss.ff)", + "TimeCodeMode": "Modo de código de tiempo", + "TimeCodeModeTime": "Tiempo (hh:mm:ss,zzz)", + "TimeCodeModeFrames": "Fotogramas (hh:mm:ss:ff)", + "TimeCodeModeFrameNumbers": "Números de fotograma", + "FrameModeSnapToVideoFrames": "Guardar tiempos en fotogramas del vídeo (no fotogramas dentro de cada segundo)", "TextBoxLimitNewLines": "Limitar saltos línea en cuadro texto", - "MpvVideoOutput": "Salida vídeo (mpv)", "MpvOpenGl": "libmpv - OpenGL", "MpvSoftwareRendering": "libmpv - Software (lento)", + "MpvMetalRendering": "libmpv - Metal (experimental, requiere reiniciar)", "MpvWidRendering": "libmpv - Nativo Window ID (rápido en Win/Linux)", "WaveFormsAndSpectrogramFoldersContainsX": "Carpetas de onda/espectro contienen {0}", "DeleteWaveformAndSpectrogramFoldersQuestion": "¿Borrar archivos de onda y espectro?", "WaveformGenerateSpectrogram": "Generar espectrograma", "WaveformAutoGenerate": "Generar la forma de onda automáticamente al abrir un vídeo", "WaveformSpectrogramMode": "Modo espectrograma", - "WaveformCenterOnSingleClick": "Centrar con un clic", - "WaveformSingleClickSelectsSubtitle": "Clic selecciona subtítulo", "WaveformRightClickSelectsSubtitle": "Clic derecho selecciona subtítulo", - "WaveformPauseOnSingleClick": "Clic pausa reproducción", "WaveformDrawStyle": "Estilo dibujo onda", "WaveformExtractAudioFormat": "Formato de audio extraído", "WaveformExtractAudioSampleRate": "Frecuencia de muestreo del audio extraído", "WaveformExtractAudioSampleRateOriginal": "Original (mantener la fuente)", "WaveformExtractAudioBitRate": "Tasa de bits del audio extraído (MP3/M4A)", + "FfmpegUseCenterChannelOnly": "Usar solo el canal central frontal para audio 5.1 (FFmpeg)", "VlcWidRendering": "libVLC - Nativo Window ID", + "FfmpegSoftwareRendering": "FFmpeg", + "DownloadFfmpegLibs": "Descargar bibliotecas FFmpeg (para el reproductor de vídeo FFmpeg)", "SubtitleGridEnterKeyAction": "Acción tecla Enter en lista", "SubtitleSingleClickAction": "Acción un clic en lista", "SubtitleDoubleClickAction": "Acción doble clic en lista", @@ -3297,25 +3632,25 @@ "SaveAsBehaviorUseVideoFileName": "Usar nombre de vídeo", "SaveAsBehaviorUseSubtitleFileName": "Usar nombre de subtítulo", "SaveAsAppendLanguageCodeTwoLetter": "Dos letras", - "SaveAsAppendLanguageCodeThreeLetter": "Tres letras", - "SaveAsAppendLanguageCodeThreeLetterBibliographic": "Tres letras (ISO 639-2/B)", "SaveAsAppendLanguageCodeLanguageName": "Nombre del idioma", "SplitOddLineActionWeightTop": "Priorizar arriba", "SplitOddLineActionWeightBottom": "Priorizar abajo", "SplitOddLinesAction": "Acción dividir líneas impares", "OcrUseWordSplitList": "OCR: usar lista división palabras", "OcrGuessUnknownWords": "OCR: intentar adivinar palabras desconocidas", + "SpellCheckRememberUseAlwaysList": "Revisión ortográfica: recordar lista \"Usar siempre\"", + "FixShortDisplayTimesAllowMoveStartTime": "Corregir errores comunes: corregir tiempos cortos puede mover el tiempo de inicio", "SpeechToTextSelectedLinesPromptFirstTimeOnly": "Voz a texto: líneas seleccionadas, preguntar por idioma/motor solo la primera vez", "MultipleReplaceShowDotDotDotButtons": "Reemplazo múltiple: botones menú contexto", "GridFocusTextboxAfterInsertNew": "Lista: foco en cuadro texto tras insertar", + "UndoRedoGoToChangedLine": "Deshacer/rehacer: ir a la línea modificada (y a la posición del vídeo)", "TextToSpeechPromptMergeContinuationLines": "Texto a voz: preguntar para combinar líneas de continuación", "TextToSpeechPromptSkipNoiseLines": "Texto a voz: preguntar para omitir las líneas de sonido/música", "TextToSpeechPromptDetectSpeakers": "Texto a voz: preguntar para detectar nombres de hablantes en el texto", "UseFocusedButtonBackgroundColor": "Usar color fondo en botón enfocado", "FocusedButtonBackgroundColor": "Color fondo botón enfocado", "ForceCrLfOnSave": "Forzar CR+LF al guardar", - "ShowWaveformToolbarPlayNext": "Barra: botón reprod. siguiente", - "ShowWaveformToolbarPlaySelection": "Barra: botón reprod. selección", + "LinuxClipboardUseExternalTool": "Copiar texto mediante xclip/wl-copy (conserva los caracteres acentuados)", "ShowFormatLimitWarning": "Avisar al guardar cuando las líneas superen los límites del formato (p. ej. SCC 32 car./línea)", "TextBoxButtonShowAutoBreak": "C. texto: botón auto-división", "TextBoxButtonShowUnbreak": "C. texto: botón unir", @@ -3327,12 +3662,18 @@ "WaveformDoubleClickAction": "Acción doble clic en onda", "AllSettings": "Todos los ajustes", "UiScale": "Escala de la interfaz (%)", + "FontScale": "Escala de fuente (%)", + "TextSelectionOpacity": "Opacidad de la selección de texto (%)", "WaveformToolbarItems": "Elementos barra onda", "MatchIconColorToDarkTheme": "Iconos acordes al tema oscuro", "SubtitlePreviewProperties": "Propiedades vista previa", "UsePositionFromSubtitleFile": "Usar la posición del archivo de subtítulos (TTML/PAC/EBU STL)", "MarginIsPartOfSubtitleArea": "El margen forma parte del área del subtítulo", "TextJustify": "Justificar las líneas", + "Deinterlace": "Desentrelazar vídeo", + "DeinterlaceOff": "Desactivado", + "DeinterlaceAuto": "Auto (solo fotogramas entrelazados)", + "DeinterlaceAlways": "Siempre", "PixelWidthInfo": "Líneas verdes = límite de ancho máximo | \r\nÁrea roja = el texto excede el límite", "SpellCheckEngineHunSpelll": "Hunspell", "SpellCheckEngineMsWord": "MS Word", @@ -3341,117 +3682,89 @@ "WaveformColorTheme": "Tema de color:", "WaveformSaveAsCustomTheme": "Guardar como tema personalizado...", "WaveformLoadThemeDotDotDot": "Cargar tema...", - "WaveformExportThemeDotDotDot": "Exportar tema..." + "WaveformExportThemeDotDotDot": "Exportar tema...", + "WaveformSaveCustomThemeTitle": "Guardar tema personalizado", + "WaveformThemeFileType": "Tema de forma de onda de Subtitle Edit", + "WaveformThemeHighContrast": "Alto contraste", + "WaveformThemeOceanBlue": "Azul océano", + "WaveformThemeWarmSunset": "Atardecer cálido", + "WaveformThemeForest": "Bosque", + "WaveformThemeMidnightPurple": "Púrpura medianoche", + "WaveformThemeRetroAmber": "Ámbar retro", + "WaveformThemeIce": "Hielo" }, "Shortcuts": { "GeneralUnbreakNoSpaceCjk": "Unir sin espacio (CJK)", - "Title": "Atajos de teclado", + "GeneralToggleBreak": "Alternar dividir/unir", "SearchShortcuts": "Buscar atajos...", - "Filter": "Filtro", - "CategoryGeneral": "General", "CategorySubtitleGridAndTextBox": "Lista y cuadro texto", "CategorySubtitleGrid": "Lista subtítulos", - "CategoryWaveform": "Forma de onda", "CategoryTextBox": "Cuadro de texto", "CategoryFile": "Archivo", "CategoryVideo": "Vídeo", "CategorySync": "Sincronización", - "CategoryTranslate": "Traducir", - "CategorySearch": "Buscar", - "CategoryTools": "Herramientas", "CategoryAi": "IA", + "CategoryCustom": "Personalizado", + "EditCustomShortcut": "Editar atajo personalizado", + "CustomShortcutTextBoxKeyHint": "Use aquí una tecla con Ctrl/Alt: una tecla sola ya no escribiría en el cuadro de texto.", + "CustomShortcutNumberX": "Atajo personalizado n.º {0}", + "CustomShortcutNumberXY": "Atajo personalizado n.º {0}: {1}", + "CustomShortcutStep": "Paso", + "CustomShortcutSteps": "Pasos", + "CustomShortcutStepRunCommand": "Ejecutar comando", + "CustomShortcutStepInsertText": "Insertar texto", + "CustomShortcutStepReplace": "Buscar y reemplazar", + "CustomShortcutInsertAtCursor": "En el cursor", + "CustomShortcutInsertAtStart": "Al inicio del texto", + "CustomShortcutInsertAtEnd": "Al final del texto", + "CustomShortcutSummaryRunCommandX": "Ejecutar: {0}", + "CustomShortcutSummaryInsertXY": "Insertar \"{0}\" ({1})", + "CustomShortcutSummaryReplaceXY": "Reemplazar \"{0}\" por \"{1}\"", + "CustomShortcutSummaryReplaceRegexXY": "Reemplazar expresión regular \"{0}\" por \"{1}\"", + "CustomShortcutPickCommand": "Seleccione un comando para ejecutar.", + "CustomShortcutEnterText": "Introduzca el texto que desea insertar.", + "CustomShortcutEnterFind": "Introduzca el texto que desea buscar.", + "CustomShortcutInvalidRegexX": "Expresión regular no válida: {0}", + "CustomShortcutStepsHint": "Los pasos se ejecutan de arriba abajo. Los pasos de texto actúan sobre las líneas seleccionadas; los comandos que abren una ventana continúan cuando esta se cierra.", "ActiveIn": "Activo en", "ActiveInEverywhere": "En todas partes", - "GeneralSetupLikeSe4": "Configurar como Subtitle Edit 4 (tema, atajos, reglas de reemplazo)", - "GeneralMergeSelectedLines": "Combinar líneas seleccionadas", - "GeneralMergeWithPrevious": "Combinar con el anterior", - "GeneralMergeWithNext": "Combinar con el siguiente", - "GeneralMergeWithPreviousAndUnbreak": "Combinar con anterior y unir", - "GeneralMergeWithNextAndUnbreak": "Combinar con siguiente y unir", - "GeneralMergeWithPreviousAndAutoBreak": "Combinar con anterior y auto-dividir", - "GeneralMergeWithNextAndAutoBreak": "Combinar con siguiente y auto-dividir", - "GeneralMergeSelectedLinesAndAutoBreak": "Combinar seleccionados y auto-dividir", "GeneralMergeSelectedLinesAndUnbreak": "Combinar seleccionados y unir", "GeneralMergeSelectedLinesAndUnbreakCjk": "Combinar seleccionados y unir CJK", - "GeneralMergeSelectedLinesOnlyFirstText": "Combinar seleccionados (solo primer texto)", "GeneralMergeSelectedLinesBilingual": "Combinar seleccionados bilingüe", - "GeneralMergeWithPreviousBilingual": "Combinar con anterior bilingüe", - "GeneralMergeWithNextBilingual": "Combinar con siguiente bilingüe", - "GeneralMergeOriginalAndTranslation": "Combinar original y traducción", "GeneralToggleTranslationMode": "Alternar modo traducción", - "GeneralSwitchOriginalAndTranslation": "Intercambiar original y traducción", - "GeneralSwitchOriginalAndTranslationTextBoxes": "Intercambiar cuadros de texto original/trad.", "GeneralToggleTranslationAndOriginalInPreviews": "Alternar traducción y original en la vista previa de vídeo/audio", "GeneralChooseLayout": "Elegir diseño", - "GeneralLayoutChooseX": "Diseño {0}", - "GeneralPlayFirstSelected": "Reproducir primero seleccionado", - "GeneralGoToFirstSelectedLine": "Ir a primera línea seleccionada", - "GeneralGoToNextEmptyLine": "Ir a siguiente línea vacía", "GeneralGoToNextSubtitle": "Ir al siguiente subtítulo", "GeneralGoToNextSubtitlePlayTranslate": "Ir a sig. subtítulo (reprod. trad.)", "GeneralGoToNextSubtitleCursorAtEnd": "Ir a sig. subtítulo (cursor al final)", "GeneralGoToPrevSubtitle": "Ir al subtítulo anterior", "GeneralGoToPrevSubtitlePlayTranslate": "Ir a ant. subtítulo (reprod. trad.)", - "GeneralGoToStartOfCurrentSubtitle": "Ir al inicio del subtítulo actual", - "GeneralGoToEndOfCurrentSubtitle": "Ir al fin del subtítulo actual", - "GeneralGoToPreviousSubtitleAndFocusVideo": "Ir a ant. subtítulo y foco vídeo", - "GeneralGoToNextSubtitleAndFocusVideo": "Ir a sig. subtítulo y foco vídeo", - "GeneralGoToPrevSubtitleAndPlay": "Ir a ant. subtítulo y reproducir", - "GeneralGoToNextSubtitleAndPlay": "Ir a sig. subtítulo y reproducir", - "GeneralGoToPreviousSubtitleAndFocusWaveform": "Ir a ant. subtítulo y foco onda", - "GeneralGoToNextSubtitleAndFocusWaveform": "Ir a sig. subtítulo y foco onda", - "GeneralGoToLineNumber": "Ir al número de línea", "GeneralGoToFirstLine": "Ir a la primera línea", "GeneralGoToLastLine": "Ir a la última línea", "AlsoSetVideoPosition": "Establecer también la posición del vídeo", "GeneralGoToVideoPosition": "Ir a la posición del vídeo", "GeneralToggleItalic": "Alternar cursiva", "GeneralToggleBold": "Alternar negrita", - "GeneralToggleBookmarks": "Alternar marcadores", - "GeneralFocusTextBox": "Enfocar cuadro de texto", - "GeneralToggleBookmarksWithText": "Alternar marcadores con texto", - "GeneralEditBookmarks": "Editar marcadores", - "FileNew": "Nuevo", "GeneralToggleUnderline": "Alternar subrayado", + "GeneralToggleBox": "Alternar caja (EBU STL)", "FileOpen": "Abrir", "FileOpenKeepVideo": "Abrir (mantener vídeo)", "FileSave": "Guardar", "FileSaveAs": "Guardar como", - "FileSaveAll": "Guardar todo", "FileSaveOriginal": "Guardar original", - "FileSaveOriginalAs": "Guardar original como", - "FileOpenOriginalSubtitle": "Abrir subtítulo original", - "FileCloseOriginalSubtitle": "Cerrar subtítulo original", - "FileTranslatedSubtitle": "Subtítulo traducido", "FileCompare": "Comparar", "FileStatistics": "Estadísticas", "FileImportPlainText": "Importar texto plano", - "FileImportBluRaySupForOcr": "Importar Blu-ray SUP para OCR", - "FileImportBluRaySupForEdit": "Importar Blu-ray SUP para editar", - "FileImportTimeCodes": "Importar códigos de tiempo", "FileExportEbuStl": "Exportar EBU STL", "FileExportPac": "Exportar PAC", - "FileExportEdlClipName": "Exportar nombre clip EDL", - "FileExportPlainText": "Exportar texto plano", - "FileExportCustomTextFormat1": "Exportar formato personal. 1", - "FileExportCustomTextFormat2": "Exportar formato personal. 2", - "FileExportCustomTextFormat3": "Exportar formato personal. 3", "FileExit": "Salir", "OpenSeDataFolder": "Abrir carpeta Subtitle Edit", - "EditFind": "Buscar", - "EditFindNext": "Buscar siguiente", "EditFindPrevious": "Buscar anterior", - "EditReplace": "Reemplazar", - "EditMultipleReplace": "Reemplazo múltiple", "FillSelectedLinesWithClipboard": "Rellenar líneas seleccionadas con el texto del portapapeles", - "EditModifySelection": "Modificar selección", - "ListSelectAll": "Seleccionar todo", - "ListSelectFirst": "Seleccionar primero", - "ListSelectLast": "Seleccionar último", "ListInverseSelection": "Invertir selección", "ListDeleteSelection": "Eliminar selección", + "DeleteSelectionEverywhere": "Eliminar selección (en todas partes)", "RippleDeleteSelection": "Eliminar selección (efecto rizo)", - "Settings": "Ajustes", "Assigned": "Asignado", "Unassigned": "Sin asignar", "PressedKeyX": "Tecla pulsada: {0}", @@ -3459,6 +3772,7 @@ "DetectKey": "Detectar tecla", "Control": "Control", "Alt": "Alt", + "AltGr": "AltGr", "Win": "Win", "Shift": "Mayús", "ControlMac": "⌃", @@ -3469,11 +3783,14 @@ "ResetShortcutsDetail": "¿Desea restablecer todos los atajos a sus valores por defecto?", "TogglePlayPause": "Alternar reprod./pausa", "ToggleLockTimeCodes": "Alternar bloqueo tiempos", + "ToggleTimeCodeMode": "Alternar modo de código de tiempo (tiempo/fotogramas/números de fotograma)", + "ToggleScreenPrivacy": "Alternar privacidad de pantalla (ocultar nombres de archivo y luego también textos)", + "ScreenPrivacyFileNamesHidden": "Privacidad de pantalla: nombres de archivo ocultos", + "ScreenPrivacyFileNamesAndTextsHidden": "Privacidad de pantalla: nombres de archivo y textos ocultos", + "ScreenPrivacyOff": "Privacidad de pantalla: desactivada", "DuplicateSelectedLines": "Duplicar líneas seleccionadas", "SourceView": "Vista de origen", - "ShowAlignmentPicker": "Alineación", "AddOrEditBookmark": "Añadir o editar marcador", - "ListBookmarks": "Listar marcadores", "ToggleBookmark": "Alternar marcador (sin texto)", "ToggleForcedSelectedLines": "Alternar forzado (líneas seleccionadas)", "GoToNextBookmark": "Ir al siguiente marcador", @@ -3484,12 +3801,12 @@ "WaveformSetStartAndSetEndOfPreviousMinusGap": "Fijar inicio y fin del anterior menos espacio", "WaveformSetEndAndStartOfNextAfterGap": "Fijar fin e inicio del siguiente más espacio", "WaveformSetEndAndStartOfNextAfterGapAndGoToNext": "Fijar fin, inicio del sig. e ir al siguiente", - "FetchFirstWordFromNextSubtitle": "Traer primera palabra del siguiente", "MoveLastWordToNextSubtitle": "Mover última palabra al siguiente", "MoveLastWordFromFirstLineDownCurrentSubtitle": "Bajar última palabra de la primera línea", "MoveFirstWordFromNextLineUpCurrentSubtitle": "Subir primera palabra de la segunda línea", "MoveTextFromCursorToNextAndGoToNext": "Mover el texto tras el cursor al siguiente subtítulo e ir al siguiente", "MoveTextFromCursorToNextAndGoToNextAndPlay": "Mover el texto tras el cursor al siguiente subtítulo, ir al siguiente y reproducir", + "MoveTextFromCursorToNextAndGoToNextAndPlayAndPause": "Mover el texto tras la posición del cursor al siguiente subtítulo, ir al siguiente, reproducirlo y pausar al final", "BreakAtFirstSpaceFromCursor": "Dividir en el primer espacio desde la posición del cursor", "BreakAtFirstSpaceFromCursorAndGoToNext": "Dividir en el primer espacio desde la posición del cursor e ir a la siguiente", "ToggleFocusGridAndWaveform": "Cambiar foco entre lista y onda", @@ -3501,7 +3818,6 @@ "VideoGoToPreviousTimeCode": "Ir al código de tiempo anterior", "VideoGoToNextTimeCode": "Ir al siguiente código de tiempo", "GoToNextLineAndSetVideoPosition": "Ir a línea siguiente y mover vídeo", - "TextBoxDeleteSelectionNoClipboard": "Cuadro texto: Borrar sin copiar", "TextBoxDeleteForward": "Cuadro de texto: eliminar la selección o el carácter siguiente (supresión hacia delante)", "TextBoxCut": "Cuadro texto: Cortar", "TextBoxCut2": "Cuadro texto: Cortar (alt.)", @@ -3520,6 +3836,7 @@ "ListViewColumnTextUp": "Columna, texto arriba", "ListViewColumnTextDown": "Columna, texto abajo", "AutoTranslateSelectedLines": "Auto-traducir líneas seleccionadas...", + "AutoTranslateSelectedLinesNoPrompt": "Traducir automáticamente las líneas seleccionadas (sin preguntar, usar último motor/idiomas)", "SetAssaResolution": "Establecer resolución ASSA (PlayResX/PlayResY)", "SetShortcutForX": "Fijar atajo para \"{0}\"", "CommandFileNewKeepVideo": "Nuevo (mantener vídeo)", @@ -3527,29 +3844,22 @@ "FileCloseOriginal": "Cerrar original", "FileCloseTranslation": "Cerrar traducción", "RestoreAutoBackup": "Restaurar copia seguridad", - "OpenContainingFolder": "Abrir carpeta contenedora", "ImportTimeCodes": "Importar códigos tiempo", "ImportSubtitleWithManuallyChosenEncoding": "Importar subtítulo con codificación manual", "ExportBluRaySup": "Exportar Blu-ray SUP", "ExportCustomTextFormat": "Exportar formato personal.", "ExportPlainText": "Exportar texto plano", - "ShowHistory": "Mostrar historial", "ToggleRightToLeft": "Alternar derecha a izquierda", "ModifySelection": "Modificar selección", "AdjustDurations": "Ajustar duraciones", "ApplyDurationLimits": "Aplicar límites duración", "BatchConvert": "Conversión por lotes", - "BridgeGaps": "Unir espacios", "ApplyMinGap": "Aplicar espacio mín.", - "FixCommonErrors": "Corregir errores comunes", "MakeEmptyTranslationFromCurrentSubtitle": "Vaciar traducción desde subtítulo actual", - "MergeLinesWithSameText": "Combinar líneas con mismo texto", - "MergeLinesWithSameTimeCodes": "Combinar con mismos códigos tiempo", "SplitBreakLongLines": "Dividir/reequilibrar líneas largas", "MergeShortLines": "Combinar líneas cortas", "MergeContinuationLines": "Combinar líneas de continuación", "SnapAllTimesToFrames": "Ajustar todos los tiempos a fotogramas", - "RemoveTextForHearingImpaired": "Quitar texto defic. auditivos", "JoinSubtitles": "Unir subtítulos", "SplitSubtitle": "Dividir subtítulo", "SpellCheck": "Corrector ortográfico", @@ -3571,7 +3881,6 @@ "AdjustAllTimes": "Ajustar todos los tiempos", "VisualSync": "Sincronización visual", "TranslateViaCopyPaste": "Traducir vía copiar-pegar", - "Shortcuts": "Atajos de teclado", "WordLists": "Listas de palabras", "ChooseUiLanguage": "Elegir idioma interfaz", "ChooseRuleProfile": "Elegir perfil de reglas", @@ -3582,6 +3891,7 @@ "TextBoxItalic": "Cuadro texto: Cursiva", "TextBoxBold": "Cuadro de texto: negrita", "TextBoxUnderline": "Cuadro de texto: subrayado", + "TextBoxBox": "Caja del cuadro de texto (EBU STL)", "ResetWaveformZoomAndSpeed": "Restablecer zoom y velocidad onda", "TogglePlaybackSpeed": "Alternar velocidad reproducción", "PlaybackSpeedSlower": "Reproducción más lenta", @@ -3593,22 +3903,28 @@ "SeekSilenceForward": "Buscar silencio hacia delante", "WaveformGuessStart": "Estimar el tiempo de inicio desde la forma de onda", "WaveformGuessEnd": "Estimar el tiempo de fin desde la forma de onda", + "WaveformGuessStartAndEnd": "Estimar tiempos de inicio y fin a partir de la forma de onda", "SetVideoPositionCurrentSubtitleStart": "Ir al inicio de línea actual", "GoToSubtitlePositionAndPause": "Ir a la posición del subtítulo y pausar", "SetVideoPositionCurrentSubtitleEnd": "Ir al fin de línea actual", "ToggleAudioTracks": "Alternar pistas audio", - "ListErrors": "Listar errores", "GoToNextError": "Ir al siguiente error", "GoToPreviousError": "Ir al anterior error", "AddNameToNameList": "Añadir nombre a la lista", "FindDoubleWords": "Buscar palabras dobles", "FindDoubleLines": "Buscar líneas dobles", "ColorX": "Color {0}", - "RemoveColor": "Quitar color", "SurroundWith": "Rodear con...", "SurroundWithXY": "Rodear con {0}/{1}", - "RepeatLine": "Repetir línea", "SurroundWithNumberX": "Rodear con n.º {0}", + "SurroundWithBehavior": "Comportamiento", + "SurroundWithBehaviorToggle": "Alternar (añadir, o quitar si existe)", + "SurroundWithBehaviorAdd": "Añadir (cada vez)", + "SurroundWithBehaviorRemove": "Quitar (todos)", + "SurroundWithBehaviorRemoveOnce": "Quitar (uno cada vez)", + "SurroundWithWorksOn": "Actúa sobre", + "SurroundWithScopeSelectionOrText": "Selección, si no todo el texto", + "SurroundWithScopeEachLine": "Cada línea", "SearchVia": "Buscar con", "SearchViaX": "Buscar con {0}", "SearchViaNumberX": "Buscar con n.º {0}", @@ -3636,6 +3952,11 @@ "CopySubtitlePathToClipboard": "Copiar ruta subtítulo", "CopySubtitleOriginalPathToClipboard": "Copiar ruta subtítulo original", "FocusTextBox": "Enfocar cuadro texto", + "FocusSubtitleListView": "Enfocar la lista de subtítulos", + "FocusWaveform": "Enfocar la forma de onda", + "FocusOriginalTextBox": "Enfocar el cuadro de texto original", + "TextBoxGoToStart": "Cuadro de texto, ir al inicio", + "TextBoxGoToEnd": "Cuadro de texto, ir al final", "SortByNumber": "Ordenar por número", "SortByStartTime": "Ordenar por inicio", "SortByEndTime": "Ordenar por fin", @@ -3660,7 +3981,6 @@ "AssaStyles": "Estilos ASSA", "AssaProperties": "Propiedades ASSA", "AssaAttachments": "Adjuntos ASSA", - "AssaVideoColorPicker": "Selector color vídeo ASSA", "RecalculateDurationSelectedLines": "Recalcular duración (líneas sel.)", "SetDurationMaxCpsSelectedLines": "Establecer duración a CPS máx. (líneas seleccionadas)", "ToggleWaveformAndSpectrogramHeight": "Alternar altura onda/espectro", @@ -3676,7 +3996,7 @@ "GoogleIt": "Buscar en Google (texto seleccionado)", "SetActorX": "Establecer actor: {0}", "SetNewActor": "Establecer nuevo actor...", - "SetActorSlotXTitle": "Espacio de actor {0}", + "SetStyleX": "Establecer estilo: {0}", "ToggleDialogDashes": "Alternar guiones de diálogo", "MergeWithLineBeforeAsDialog": "Combinar con la línea anterior como diálogo", "MergeWithLineAfterAsDialog": "Combinar con la línea siguiente como diálogo", @@ -3687,13 +4007,27 @@ "MoveStartOneFrameBackKeepGapPrev": "Mover el inicio un fotograma atrás (mantener el espacio con el anterior si está cerca)", "MoveStartOneFrameForwardKeepGapPrev": "Mover el inicio un fotograma adelante (mantener el espacio con el anterior si está cerca)", "MoveEndOneFrameBackKeepGapNext": "Mover el fin un fotograma atrás (mantener el espacio con el siguiente si está cerca)", - "MoveEndOneFrameForwardKeepGapNext": "Mover el fin un fotograma adelante (mantener el espacio con el siguiente si está cerca)" + "MoveEndOneFrameForwardKeepGapNext": "Mover el fin un fotograma adelante (mantener el espacio con el siguiente si está cerca)", + "MoveStartXMsBack": "Mover inicio X ms atrás (X se define en Ajustes, también en Sincronización visual)", + "MoveStartXMsForward": "Mover inicio X ms adelante (X se define en Ajustes, también en Sincronización visual)", + "MoveEndXMsBack": "Mover fin X ms atrás (X se define en Ajustes, también en Sincronización visual)", + "MoveEndXMsForward": "Mover fin X ms adelante (X se define en Ajustes, también en Sincronización visual)", + "MoveSelectedLinesXMsBack": "Mover líneas seleccionadas X ms atrás (X se define en Ajustes)", + "MoveSelectedLinesXMsForward": "Mover líneas seleccionadas X ms adelante (X se define en Ajustes)", + "MoveSelectedLinesAndForwardXMsBack": "Mover líneas seleccionadas y todas las siguientes X ms atrás (X se define en Ajustes)", + "MoveSelectedLinesAndForwardXMsForward": "Mover líneas seleccionadas y todas las siguientes X ms adelante (X se define en Ajustes)", + "MoveAllLinesXMsBack": "Mover todas las líneas X ms atrás (X se define en Ajustes)", + "MoveAllLinesXMsForward": "Mover todas las líneas X ms adelante (X se define en Ajustes)", + "MoveSelectedLinesCustomMsBack": "Mover líneas seleccionadas, milisegundos personalizados ({0:#,###,##0}) atrás, {1}", + "MoveSelectedLinesCustomMsForward": "Mover líneas seleccionadas, milisegundos personalizados ({0:#,###,##0}) adelante, {1}", + "MoveSelectedLinesAndForwardCustomMsBack": "Mover líneas seleccionadas y todas las siguientes, milisegundos personalizados ({0:#,###,##0}) atrás, {1}", + "MoveSelectedLinesAndForwardCustomMsForward": "Mover líneas seleccionadas y todas las siguientes, milisegundos personalizados ({0:#,###,##0}) adelante, {1}", + "MoveAllLinesCustomMsBack": "Mover todas las líneas, milisegundos personalizados ({0:#,###,##0}) atrás, {1}", + "MoveAllLinesCustomMsForward": "Mover todas las líneas, milisegundos personalizados ({0:#,###,##0}) adelante, {1}" }, "WordLists": { "Title": "Listas de palabras", "AddName": "Añadir nombre", - "AddWord": "Añadir palabra", - "AddPair": "Añadir par", "NameAndIgnoreList": "Lista nombres/ignorar", "UserWords": "Lista palabras usuario", "OcrFixList": "Lista corrección OCR", @@ -3710,7 +4044,6 @@ "NoPluginsInstalled": "No hay plugins instalados", "GetPluginsOnline": "Obtener plugins en línea...", "GetPluginsTitle": "Obtener plugins", - "Enabled": "Activado", "Disabled": "Desactivado", "NotSupportedOnThisOs": "No compatible con este sistema operativo", "RemovePluginXConfirm": "¿Eliminar el plugin '{0}'? Esto borra la carpeta del plugin:{1}", @@ -3727,17 +4060,19 @@ "DownloadingXPercent": "Descargando {0}%...", "PluginXInstalled": "'{0}' instalado.", "CouldNotInstallPluginX": "No se pudo instalar '{0}': {1}", - "NotInstalled": "No instalado", "Install": "Instalar", "Reinstall": "Reinstalar", - "Update": "Actualizar", "InstalledX": "Instalado ({0})", "UpdateAvailableXToY": "Actualización disponible ({0} → {1})", "ApplyPluginToWhichLinesX": "Aplicar '{0}' a:", "ApplyToSelectedLinesX": "Líneas seleccionadas ({0})", "ApplyToAllLinesX": "Todas las líneas ({0})", "UpdateAllXAvailable": "Actualizar todo ({0})", - "UpdatingXOfY": "Actualizando {0}/{1}..." + "UpdatingXOfY": "Actualizando {0}/{1}...", + "DoNotAskAgain": "No volver a preguntar (se cambia en Administrar plugins)", + "ApplyPluginsTo": "Aplicar plugins a", + "AskEachTime": "Preguntar cada vez", + "AllLines": "Todas las líneas" }, "Help": { "AboutSubtitleEdit": "Acerca de Subtitle Edit", @@ -3781,6 +4116,8 @@ "NOcrDatabase": "Base de datos nOCR", "DrawMode": "Modo dibujo:", "AddNewCharcter": "Añadir nuevo carácter", + "AddNOcrCharacterTitle": "Añadir carácter nOCR para la línea {0}, carácter {1} de {2} usando la base de datos \"{3}\"", + "AddBinaryOcrCharacterTitle": "Añadir carácter OCR binario para la línea {0}, carácter {1} de {2} usando la base de datos \"{3}\"", "LineIndexX": "Línea {0}", "InspectNOcrAdditions": "Inspeccionar adiciones nOCR", "OcrSelectedLines": "OCR líneas seleccionadas", @@ -3829,6 +4166,8 @@ "BinaryImageCompareInspectImageMatches": "\"Binary image compare\" - Inspeccionar", "SaveBlankTextTitle": "¿Guardar texto en blanco?", "SaveBlankTextPrompt": "¿Guardar texto en blanco para la imagen?", + "DrawnLinesDoNotMatchTitle": "Las líneas no coinciden con la imagen", + "DrawnLinesDoNotMatchPrompt": "Algunas de las líneas dibujadas no coinciden con la imagen (las líneas verdes deben estar sobre el carácter y las rojas fuera de él), por lo que este carácter no se reconocerá.\n\n¿Guardar de todos modos?", "YesAndNeverAskAgain": "Sí y no volver a preguntar", "ImportTextFromSubtitleDotDotDot": "Importar texto desde subtítulo...", "ImportTextFromSubtitleOverwritePrompt": "Algunas líneas ya tienen texto de OCR. ¿Sobrescribir el texto existente?", @@ -3854,7 +4193,6 @@ "VobSubColorsTitle": "Colores VobSub/DVD", "VobSubColorsHeader": "Personalizar los cuatro colores de VobSub", "VobSubColorsDescription": "Los subtítulos VobSub/DVD usan cuatro colores indexados: fondo, patrón, énfasis 1 y énfasis 2. Elija cada color a continuación para anular la paleta incrustada: útil cuando los colores originales dan poco contraste para el OCR.", - "VobSubColorBackground": "Fondo", "VobSubColorPattern": "Patrón", "VobSubColorEmphasis1": "Énfasis 1", "VobSubColorEmphasis2": "Énfasis 2", @@ -3888,9 +4226,26 @@ "CrispEmbedSettingsTitle": "Ajustes de CrispEmbed", "CrispEmbedDescription": "Motor de OCR local con varios backends de modelo: descargue aquí el motor y los modelos.", "TrainNOcrDatabase": "Entrenar base de datos nOCR...", + "OptimizeNOcrDatabase": "Optimizar base de datos nOCR...", + "OptimizeNOcrDatabaseQuestionXY": "¿Eliminar las líneas que no distinguen los caracteres de otros similares en la base de datos nOCR \"{0}\"?\n\nEsto hace la base de datos más pequeña y el OCR más rápido, normalmente con menos errores. Tarda hasta un minuto.\n\nSe guarda una copia de seguridad como \"{1}\".", + "OptimizingNOcrDatabaseXY": "Optimizando base de datos nOCR... {0}/{1}", + "NOcrDatabaseOptimizedXYZ": "Base de datos nOCR \"{0}\" optimizada: {1:#,##0} -> {2:#,##0} líneas.", "StartTraining": "Iniciar el entrenamiento", "AbortTraining": "Cancelar el entrenamiento", "TrainingOptions": "Opciones de entrenamiento", + "TrainNOcrSubtitle": "Representa caracteres en las fuentes instaladas y enseña a nOCR sus formas. Elija las fuentes que usan sus subtítulos.", + "XFontsSelected": "{0:#,##0} seleccionadas", + "SubtitleFonts": "Fuentes de los subtítulos", + "EmptyDatabase": "Base de datos vacía", + "StartFromDatabase": "Empezar desde", + "StartFromDatabaseHint": "Añadir los caracteres entrenados a una copia de una base de datos existente (recomendado) o empezar desde una vacía", + "FontSizes": "Tamaños de fuente", + "FontSizesHint": "Tamaños de fuente en píxeles para representar, separados por comas, p. ej. \"30, 40\"", + "NumberOfLineSegmentsHint": "Líneas comprobadas por carácter: más es más estricto pero más lento", + "EnterDatabaseName": "Introduzca un nombre para la base de datos", + "TrainingFontXSizeYCharacterZ": "Entrenando \"{0}\" a {1} px: {2}", + "XLearned": "{0:#,##0} aprendidos", + "XSkipped": "{0:#,##0} omitidos", "CharactersToTrain": "Caracteres que entrenar", "LetterCombinationsToTrain": "Combinaciones de letras que podrían dividirse en una imagen", "ImportCharactersFromSubtitleFile": "Importar caracteres de un archivo de subtítulos...", @@ -3917,6 +4272,8 @@ "DrawZoomOut": "Zoom - (Ctrl+-)", "DrawResetView": "Reset vista (Ctrl+0)", "DrawToggleGrid": "Cuadrícula (Ctrl+G)", + "DrawTogglePreview": "Alternar vista previa (F9)", + "DrawPreviewFailed": "La vista previa falló: se requiere ffmpeg con libass (filtro subtitles)", "DrawCopyToClipboard": "Copiar (Ctrl+C)", "DrawShapes": "Formas", "DrawSelectedPoint": "Punto seleccionado", @@ -3924,16 +4281,86 @@ "DrawSelectedLayer": "Capa seleccionada", "DrawToolX": "Herramienta: {0}", "DrawHelpText": "Clic para puntos • Enter/F8 cierra forma • Mayús+Arrastrar para desplazar • Ctrl+Rueda para zoom", + "DrawMoveToLayer": "Mover a capa", + "DrawFlipHorizontal": "Voltear horizontalmente", + "DrawFlipVertical": "Voltear verticalmente", + "DrawHideShape": "Ocultar forma", + "DrawShowShape": "Mostrar forma", + "DrawHideLayer": "Ocultar capa", + "DrawShowLayer": "Mostrar capa", + "DrawDeleteLayer": "Eliminar capa", + "DrawDeletePoint": "Eliminar punto", + "DrawCancelDrawing": "Cancelar dibujo", + "DrawLayerX": "Capa {0}", + "DrawImportSvg": "Importar imagen SVG...", + "DrawRotateClockwise": "Girar 90° en sentido horario", + "DrawConvertToCurve": "Convertir segmento en curva", + "DrawBackground": "Fondo", + "DrawPosition": "Posición", + "DrawZoom": "Zoom", + "DrawShapeTool": "Herramienta de forma", + "DrawColorPickerTool": "Tomar color de una forma o del fondo", + "DrawShapeLibrary": "Biblioteca de formas", + "DrawShapeLibraryHint": "Arrastre sobre el lienzo para colocar una forma, o haga clic para el tamaño predeterminado. Shift mantiene sus proporciones.", + "DrawAddToLibrary": "Añadir a la biblioteca de formas...", + "DrawRemoveFromLibrary": "Quitar de la biblioteca de formas", + "DrawShapeName": "Nombre de la forma", + "DrawCategorySpeechBubbles": "Bocadillos", + "DrawCategoryArrows": "Flechas", + "DrawCategoryBasicShapes": "Formas básicas", + "DrawCategorySymbols": "Símbolos", + "DrawCategoryMyShapes": "Mis formas", + "DrawShapeSpeechBubble": "Bocadillo", + "DrawShapeSpeechBubbleRight": "Bocadillo, cola a la derecha", + "DrawShapeSpeechBubbleUp": "Bocadillo, cola arriba", + "DrawShapeOvalBubble": "Bocadillo ovalado", + "DrawShapeBoxBubble": "Bocadillo rectangular", + "DrawShapeThoughtBubble": "Bocadillo de pensamiento", + "DrawShapeShoutBubble": "Bocadillo de grito", + "DrawShapeCaptionBox": "Cuadro de texto", + "DrawShapeArrow": "Flecha", + "DrawShapeDoubleArrow": "Flecha doble", + "DrawShapeChevron": "Chevrón", + "DrawShapeCurvedArrow": "Flecha curva", + "DrawShapeRoundedRectangle": "Rectángulo redondeado", + "DrawShapeTriangle": "Triángulo", + "DrawShapeDiamond": "Rombo", + "DrawShapePentagon": "Pentágono", + "DrawShapeHexagon": "Hexágono", + "DrawShapeOctagon": "Octágono", + "DrawShapeStar": "Estrella", + "DrawShapeRing": "Anillo", + "DrawShapePlus": "Más", + "DrawShapeBanner": "Pancarta", + "DrawShapeHeart": "Corazón", + "DrawShapeCheck": "Marca de verificación", + "DrawShapeMusicNote": "Nota musical", + "DrawShapeMusicNotes": "Notas musicales", + "DrawShapeLightning": "Rayo", + "DrawShapeCloud": "Nube", + "DrawShapeMoon": "Luna", + "DrawShapeBadge": "Insignia", + "DrawSize": "Tamaño", + "DrawLayer": "Capa", + "DrawBackgroundVideoFrame": "Fotograma del vídeo (posición actual)", + "DrawBackgroundVideoFrameAt": "Fotograma del vídeo en...", + "DrawBackgroundImage": "Archivo de imagen...", + "DrawBackgroundNone": "Sin fondo", + "DrawBackgroundStretch": "Estirar la imagen al lienzo", + "DrawBackgroundOpacity": "Opacidad del fondo", + "DrawBackgroundVideoFrameFailed": "No se pudo obtener un fotograma del vídeo en {0}.", + "DrawImages": "Imágenes", + "DrawConvertToLine": "Convertir segmento en línea", + "DrawConvertShapeToCurves": "Convertir todas las líneas en curvas", + "DrawConvertShapeToLines": "Convertir todas las curvas en líneas", + "DrawRotateCounterClockwise": "Girar 90° en sentido antihorario", + "DrawSvgImages": "Imágenes SVG", + "DrawSvgNoShapes": "No se encontraron formas rellenas ni trazos en el archivo SVG.", "ProgressBarTitle": "Barra de progreso ASSA", "ProgressBarSettings": "Barra de progreso", - "ProgressBarPosition": "Posición", - "ProgressBarBottom": "Abajo", - "ProgressBarTop": "Arriba", "MouseOverColor": "Color al pasar el ratón", "ClickedColor": "Color al hacer clic", "ProgressBarForeColor": "Color de frente", - "ProgressBarBackColor": "Color de fondo", - "ProgressBarStyle": "Estilo", "ProgressBarSquareCorners": "Esquinas cuadradas", "ProgressBarRoundedCorners": "Esquinas redondeadas", "ProgressBarChapters": "Capítulos", @@ -3942,8 +4369,6 @@ "ProgressBarXAdjustment": "Ajuste X", "ProgressBarYAdjustment": "Ajuste Y", "ProgressBarTextAlignment": "Alineación del texto", - "ProgressBarTakePosFromVideo": "Tomar posición del vídeo", - "ProgressBarPreview": "Vista previa", "FontCollectorTitle": "Recopilador de fuentes", "FontCollectorUsedIn": "Usada en", "FontCollectorFontFiles": "Archivo(s) de fuente", @@ -3990,9 +4415,7 @@ "ResolutionResamplerVideoDiffers": "La resolución del subtítulo ({0}x{1}) difiere de la resolución del vídeo ({2}x{3}).\n¿Cambiar el subtítulo para que coincida con el vídeo?", "ResolutionResamplerAskOnVideoOpen": "Preguntar cuando se abra un vídeo con una resolución distinta", "BackgroundBoxGenerator": "Generar caja de fondo", - "BackgroundBoxPadding": "Relleno", "BackgroundBoxFillWidth": "Llenar ancho", - "BackgroundBoxBoxColor": "Color caja", "BackgroundBoxRadius": "Radio", "BackgroundBoxCircle": "Círculo", "BackgroundBoxSpikes": "Puntas", @@ -4004,12 +4427,9 @@ "BackgroundBoxTornPaperDouble": "Papel rasgado (sup e inf)", "BackgroundBoxStarburst": "Explosión estelar", "BackgroundBoxScroll": "Pergamino", - "StylesTitle": "Estilos Advanced Sub Station Alpha", "StylesInFile": "Estilos en el archivo", "StylesSaved": "Estilos guardados", - "Category": "Categoría", "AllCategories": "[Todas]", - "DefaultCategory": "Predeterminada", "NewCategory": "Nueva categoría...", "RenameCategory": "Renombrar categoría...", "DeleteCategory": "Eliminar categoría", @@ -4032,13 +4452,26 @@ "Graphics": "Gráficos", "CopyToStorageStyles": "Copiar a estilos de almacén", "CopyToFileStyles": "Copiar a estilos de archivo", + "StyleXAlreadyExistsInStorage": "Ya existe un estilo llamado \"{0}\" en el almacén.\n\n¿Sobrescribirlo o conservar ambos (la copia recibe un nombre nuevo)?", + "StyleXAlreadyExistsInFile": "Ya existe un estilo llamado \"{0}\" en el archivo.\n\n¿Sobrescribirlo o conservar ambos (la copia recibe un nombre nuevo)?", + "StyleNameCannotBeEmpty": "El nombre de un estilo no puede estar vacío.", + "StyleNameXIsUsedMoreThanOnce": "El nombre de estilo \"{0}\" lo usa más de un estilo del archivo.", + "Overwrite": "Sobrescribir", + "KeepBoth": "Conservar ambos", + "DoThisForAllConflictingStyles": "Hacer esto para todos los estilos en conflicto", "SetStyleAsDefault": "Estilo por defecto", "TakeUsagesFromDotDotDot": "Tomar usos de...", "ReplaceStyleWithDotDotDot": "Reemplazar estilo por...", "NoAttachmentsFound": "No hay adjuntos en el archivo ASSA.", "DeleteStyleQuestion": "¿Borrar estilo?", "DeleteStylesQuestion": "¿Borrar estilos?", + "DeleteXStylesQuestion": "¿Desea eliminar {0} estilos?", + "DeleteXStylesFromStorageQuestion": "¿Desea eliminar {0} estilos del almacén?", + "DeleteStyleXFromStorageQuestion": "¿Desea eliminar el estilo \"{0}\" del almacén?", + "DeleteStyleXFromFileQuestion": "¿Desea eliminar el estilo \"{0}\" del archivo actual?", "OpenStyleImportFile": "Abrir archivo para importar estilos", + "StyleImportFiles": "Archivos de estilo (.ass, .sty, categorías .template de Subtitle Edit 4)", + "SsaStyleImportFiles": "Archivos de estilo (.ssa, .ass, .sty)", "Primary": "Primario", "Secondary": "Secundario", "ApplyOverrideTags": "Aplicar etiquetas anulación", @@ -4053,7 +4486,6 @@ "SpaceIncrease": "Aumento espacio", "PopIn": "Aparición (pop in)", "SpinIn": "Giro (spin in)", - "PlayCurrent": "Reprod. actual", "SetPosition": "Fijar posición", "Rotation": "Rotación", "ScaleX": "Escala X", @@ -4097,10 +4529,8 @@ "AdvancedEffectOldMovieDescription": "Simula proyector de 35mm con ruido y rayas", "AdvancedEffectNeonBurst": "Explosión neón", "AdvancedEffectNeonBurstDescription": "Subtítulos estilo 'Shorts' con brillo neón y escala", - "AdvancedEffectSettings": "Ajustes", "AdvancedEffectSnowFlakeCount": "Cantidad de copos", "AdvancedEffectStarfieldStarCount": "Cantidad de estrellas", - "AdvancedEffectStarfieldSpeed": "Velocidad", "AdvancedEffectGlitch": "Glitch", "AdvancedEffectGlitchDescription": "Fallo digital con aberración cromática y distorsión", "AdvancedEffectBounceIn": "Rebote", @@ -4109,8 +4539,6 @@ "AdvancedEffectFirefliesDescription": "Puntos ámbar brillantes que flotan orgánicamente", "AdvancedEffectMatrix": "Matrix", "AdvancedEffectMatrixDescription": "Columnas de caracteres verdes cayendo", - "AdvancedEffectAudioBars": "Barras de audio (onda)", - "AdvancedEffectAudioBarsDescription": "Ecualizador animado por el audio", "AdvancedEffectAudioPulse": "Pulso de texto por audio", "AdvancedEffectAudioPulseDescription": "El texto brilla y crece según el volumen del audio", "AdvancedEffectConfetti": "Explosión de confeti", @@ -4124,7 +4552,6 @@ "AdvancedEffectFancyKaraokeGlow": "Brillo palabra activa", "AdvancedEffectFancyKaraokeActiveColor": "Color activo", "AdvancedEffectFancyKaraokeInactiveColor": "Color inactivo", - "AdvancedEffectFancyKaraokeInactiveOpacity": "Opacidad inactiva (0-255)", "AdvancedEffectWordSpacingPixels": "Espaciado (píxeles)", "AdvancedEffectSlowZoomOut": "Zoom out lento", "AdvancedEffectSlowZoomOutDescription": "El texto encoge ligeramente durante el subtítulo", @@ -4140,7 +4567,6 @@ "AdvancedEffectSlideInLeftDescription": "Entra por la izquierda y sale por la izquierda", "AdvancedEffectSlideInRight": "Deslizar desde derecha", "AdvancedEffectSlideInRightDescription": "Entra por la derecha y sale por la derecha", - "AdvancedEffectFadeInOut": "Fundido entrada/salida", "AdvancedEffectFadeInOutDescription": "Texto aparece y desaparece suavemente", "AdvancedEffectWordFlip3D": "Giro de palabra 3D", "AdvancedEffectWordFlip3DDescription": "Cada palabra entra con un giro 3D y un rebote elástico al pronunciarse: estilo moderno de subtítulos de 'Shorts'", diff --git a/src/ui/Assets/Languages/Swedish.json b/src/ui/Assets/Languages/Swedish.json index 6cfcbfa5e38..58344968f6c 100644 --- a/src/ui/Assets/Languages/Swedish.json +++ b/src/ui/Assets/Languages/Swedish.json @@ -1,12 +1,30 @@ { "title": "Subtitle Edit", - "version": "v5.2.0-rc7", + "version": "v5.3.0", "translatedBy": "Claude (Opus 4.7)", "cultureName": "sv-SE", "general": { "abort": "Avbryt", "actor": "Skådespelare", "actors": "Skådespelare", + "setActorDotDotDot": "Välj skådespelare...", + "actorPickerTitle": "Välj skådespelare", + "actorPickerFilterHint": "Skriv för att filtrera, eller skriv ett nytt skådespelarnamn", + "actorPickerKeysHint": "Tryck på en siffra eller Enter för att välja skådespelaren. Alt+Upp/Ned ändrar ordningen, Delete tar bort skådespelaren.", + "actorPickerNewActorX": "Ny skådespelare \"{0}\"", + "actorPickerLinesSelectedX": "Markerade rader: {0}", + "setStyleDotDotDot": "Välj stil...", + "stylePickerTitle": "Välj stil", + "stylePickerFilterHint": "Skriv för att filtrera, eller skriv ett nytt stilnamn", + "stylePickerKeysHint": "Tryck på en siffra (1-9, 0) för att välja stilen för alla markerade rader.", + "stylePickerNewStyleX": "Ny stil \"{0}\"", + "stylePickerNewStyleInfo": "Tryck på Enter för att lägga till den här stilen med standardinställningar och använda den på de markerade raderna. Använd \"Hantera stilar...\" för att ändra dess typsnitt, färger och position senare.", + "stylePickerCurrentStyleX": "Aktuell stil: {0}", + "stylePickerLinesUsingStyleX": "Rader som använder den här stilen: {0}", + "stylePickerMarginsXYZ": "Vänster {0}, höger {1}, vertikal {2}", + "stylePickerSetStyle": "_Välj stil", + "stylePickerManageStyles": "Hantera stilar...", + "stylePickerManageStylesHint": "Stäng det här fönstret och öppna stilhanteraren för att redigera typsnitt, färger, position med mera", "add": "Lägg till", "addDotDotDot": "Lägg till...", "addToNamesListCaseSensitive": "Lägg till i namnlista (skiftlägeskänslig)", @@ -15,7 +33,6 @@ "adjustAlpha": "Justera alfa (transparens)", "adjustDisplayDuration": "Justera visningslängd", "adjustment": "Justering", - "advanced": "Avancerat", "advancedSettings": "Avancerade inställningar", "after": "Efter", "alignment": "Justering", @@ -59,7 +76,6 @@ "bdnXml": "BDN/xml", "bdnXml8Bit": "BDN/xml 8-bitars", "before": "Före", - "beforeX": "Före \"{0}\"", "beginning": "Början", "bluRaySup": "Blu-ray (sup)", "bold": "Fet", @@ -71,7 +87,6 @@ "bookmarkEdit": "Redigera bokmärke", "bookmarkEditForSelectedLinesX": "Redigera bokmärke för {0} markerade rader", "bookmarksList": "Lista bokmärken", - "borderColor": "Kantfärg", "borderStyle": "Kantstil", "bottomCenter": "Botten-Mitten", "bottomLeft": "Botten-Vänster", @@ -98,7 +113,6 @@ "changeCasing": "Ändra skiftläge", "changeFormatting": "Ändra formatering", "changeFrameRate": "Ändra bildfrekvens", - "changeLanguageFilter": "Ändra språkfilter...", "changeOnce": "Ändra en gång", "changeSpeed": "Ändra hastighet", "character": "Tecken", @@ -140,7 +154,6 @@ "conversionCancelledByUser": "Konvertering avbruten av användaren", "convert": "Konvertera", "converted": "Konverterad", - "convertingDotDotDot": "Konverterar...", "convertingXofYDotDoDot": "Konverterar {0:#,###,##0}/{1:#,###,##0}...", "copy": "Kopiera", "copyImageToClipboard": "Kopiera bild till urklipp", @@ -151,7 +164,6 @@ "couldNotOpenFileXErrorY": "Kunde inte öppna filen \"{0}\". Fel: {1}", "count": "Antal", "cps": "Tecken/sek", - "currentSubtitle": "Aktuell undertext", "cudaRuntime": "CUDA-körmiljö", "currentVideoPosition": "Aktuell videoposition", "custom": "Beställnings", @@ -211,7 +223,6 @@ "expand": "Expandera", "export": "Exportera", "exportDotDotDot": "Exportera...", - "exportToX": "Exportera till {0}", "extendAfter": "Förläng/förkorta till nästa rad", "extendBefore": "Förläng/förkorta till föregående rad", "extendSelectedLinesToNextShotChangeOrNextSubtitle": "Förläng markerade rader till nästa scenbyte (eller nästa undertext)", @@ -228,7 +239,6 @@ "fileExtension": "Filändelse", "fileName": "Filnamn", "fileNameX": "Filnamn: {0}", - "fileNameXAndSize": "Filnamn: {0} ({1})", "fileSaved": "Filen sparad", "fileSavedToX": "Filen sparad till {0}", "fileXAlreadyExists": "Filen \"{0}\" finns redan.", @@ -238,8 +248,6 @@ "find": "Sök", "findNext": "Sök nästa", "findTextX": "Sök text - {0}", - "fiveHundredMilliseconds": "500 millisekunder", - "fiveSeconds": "Fem sekunder", "fix": "Åtgärda", "fixCommonErrors": "Åtgärda vanliga fel", "fixRightToLeft": "Åtgärda höger-till-vänster", @@ -263,14 +271,12 @@ "foundXInLineYZ": "Hittade \"{0}\" på rad {1}, position {2}", "foundXMatches": "Hittade {0} träffar", "frameRate": "Bildfrekvens", - "frameRateX": "Bildfrekvens: {0:0.0###}", "frames": "Bilder", "from": "Från", "fromCurrentVideoPosition": "från aktuell videoposition", "fullScreen": "Helskärm", "gap": "Mellanrum", "general": "Allmänt", - "generalText": "Allmänt", "generate": "Generera", "generateImportShotChanges": "Generera/importera scenbyten", "generating": "Genererar...", @@ -288,12 +294,8 @@ "help": "Hjälp", "hex": "Hex", "hide": "Dölj", - "hidePreview": "Dölj förhandsvisning", "history": "Historik", "horizontal": "Horisontell", - "hourMinutesSecondsDecimalSeparatorMilliseconds": "Tim:min:sek{0}ms", - "hourMinutesSecondsFrames": "Tim:min:sek:bilder", - "ip": "IP", "id": "ID", "image": "Bild", "imageSaved": "Bild sparad", @@ -354,6 +356,7 @@ "mergeSelectedLinesDialog": "Slå ihop markerade rader som dialog", "mergeWithLineAfterAndAutoBreak": "Slå ihop med nästa rad och bryt automatiskt", "mergeWithLineAfterKeepBreaks": "Slå ihop med nästa rad (behåll radbrytningar)", + "mergeWithLineAfterAndUnbreak": "Slå ihop med nästa rad och ta bort radbrytningar", "mergeWithLineBeforeAndAutoBreak": "Slå ihop med föregående rad och bryt automatiskt", "mergeWithLineBeforeKeepBreaks": "Slå ihop med föregående rad (behåll radbrytningar)", "middleCenter": "Mitten-Mitten", @@ -368,13 +371,13 @@ "moveAllShotChangeOneFrameForward": "Flytta alla scenbyten en bildruta framåt", "multiMode": "Multiläge", "moveDown": "Flytta ner", + "increase": "Öka", + "decrease": "Minska", "moveUp": "Flytta upp", "moveToTop": "Flytta överst", "moveToBottom": "Flytta nederst", "multipleReplace": "Multipel ersättning", - "mute": "Tysta", "name": "Namn", - "negative": "Negativ", "new": "Ny", "newDotDotDot": "Ny...", "newProfile": "Ny profil", @@ -404,20 +407,15 @@ "offsetY": "Förskjutning Y", "ok": "_OK", "oneFile": "En fil", - "oneHundredMilliseconds": "100 millisekunder", - "oneSecond": "En sekund", "oneSecondBack": "En sekund bakåt", "oneSecondForward": "En sekund framåt", - "onlineVideoFeatureNotAvailable": "Funktionen är inte tillgänglig för onlinevideo", "openContainingFolder": "Öppna innehållande mapp", "openDictionaryFolder": "Öppna ordboksmapp", "openFile": "Öppna fil", "openImageBasedSubtitle": "Öppna bildbaserad undertext", "openImageFile": "Öppna bildfil", - "openOriginalSubtitleFile": "Öppna originalundertextfil...", "openOriginalSubtitleFileTitle": "Öppna originalundertextfil", "openOutputFolder": "Öppna utmatningsmapp", - "openSubtitle": "Öppna undertext...", "openSubtitleFileTitle": "Öppna undertextfil", "openSubtitles": "Öppna undertexter...", "openVideoFile": "Öppna videofil...", @@ -432,11 +430,6 @@ "outlineWidth": "Konturbredd", "outputFolder": "Utmatningsmapp", "outputProperties": "Utmatningsegenskaper", - "overlap": "Överlapp", - "overlapNextX": "Överlapp nästa ({0:#;##0.###})", - "overlapPreviousLineX": "Överlapp föreg. rad ({0:#;##0.###})", - "overlapStartAndEnd": "Överlapp start och slut", - "overlapX": "Överlapp ({0:#;##0.###})", "overwriteExistingFiles": "Skriv över befintliga filer", "overwriteFilesInFolderX": "Skriv över filer i mapp: \"{0}\"", "overwriteQuestion": "Skriv över?", @@ -449,6 +442,7 @@ "percent": "Procent", "pickLayer": "Välj lager", "pickOllamaModel": "Välj Ollama-modell", + "pickModel": "Välj modell", "llamaCppUseRemoteServer": "Använd extern server (URL)", "llamaCppEngineSettings": "Inställningar för llama.cpp-motorn", "llamaCppEngineSettingsSubtitle": "Lokalt llama.cpp-serverbygge", @@ -496,7 +490,6 @@ "refresh": "Uppdatera", "region": "Region", "regularExpression": "Reguljärt uttryck", - "regularExpressionIsNotValid": "Reguljärt uttryck är ogiltigt!", "relativeToCurrentVideoPosition": "Relativt aktuell videoposition", "remove": "Ta bort", "removeAlignment": "Ta bort justering", @@ -521,7 +514,7 @@ "requiresRestart": "Kräver omstart", "reset": "Återställ", "resolution": "Upplösning", - "resolutionX": "Upplösning: {0}", + "retry": "Försök igen", "reverseRightToLeftStartEnd": "Vänd RTL start/slut", "right": "Höger", "rightToLeft": "Höger till vänster", @@ -561,8 +554,6 @@ "selectedlinesX": "Markerade rader: {0}", "sensitivity": "Känslighet", "separator": "Avgränsare", - "sessionKey": "Sessionsnyckel", - "sessionKeyGenerate": "Generera ny nyckel", "setEnd": "Sätt slut", "setEndAndGoToNext": "Sätt slut och gå till nästa", "setEndAndOffsetTheRest": "Sätt slut och förskjut resten", @@ -581,8 +572,9 @@ "shadowColor": "Skuggfärg", "shadowWidth": "Skuggbredd", "shiftLineBy": "Förskjut rad med", + "shotIn": "Scen in", + "shotOut": "Scen ut", "shortcut": "Kortkommando", - "shortcutX": "Kortkommando: {0}", "shortcuts": "Kortkommandon", "show": "Visa", "showActorColumn": "Visa kolumnen \"Skådespelare\"", @@ -593,17 +585,19 @@ "showStartColumn": "Visa kolumnen \"Start\"", "showHideColumn": "Visa kolumnen \"Dölj\"", "showHistory": "Visa historik", + "clearHistory": "Rensa historik", + "removeFromHistory": "Ta bort från historik", "showLayerColumn": "Visa kolumnen \"Lager\"", "showPreview": "Visa förhandsvisning", "showShotChangesList": "Visa lista över scenbyten", + "showShotInColumn": "Visa kolumnen \"Scen in\"", + "showShotOutColumn": "Visa kolumnen \"Scen ut\"", "showStyleColumn": "Visa kolumnen \"Stil\"", "showTeletext": "Visa text-tv-kolumnen i listvyn", "showTimeCodes": "Visa tidskoder", "showWpmColumn": "Visa kolumnen \"Ord/min\"", "showPixelWidthColumn": "Visa kolumnen \"Pixelbredd\"", "shrink": "Krymp", - "singleBox": "Enkel ruta", - "singleLineLengths": "Enkelradslängd:", "singleMode": "Enskilt läge", "textTotalLength": "Text - total längd", "textSingleLineMaxLength": "Text - max enkelradslängd", @@ -652,16 +646,16 @@ "strikeout": "Genomstruken", "style": "Stil", "styleExaggeration": "Stilöverdrift", - "styleLanguage": "Stil / Språk", "styles": "Stilar", "subtitleFile": "Undertextfil", + "subtitleFileChangedOutsideX": "Filen \"{0}\" har ändrats utanför Subtitle Edit.\n\nLäsa in den igen och förlora dina ändringar?", "subtitleFileName": "Undertextfilnamn", "subtitleFileSaved": "Undertextfil sparad", "subtitleFileSavedToX": "Undertextfil sparad till {0}", "subtitleFiles": "Undertextfiler", "subtitleFormats": "Undertextformat", "subtitleLoadedX": "Undertext inläst: {0}", - "subtitleXOfY": "Undertext {0} av {1}", + "subtitleReloadedChangedOutsideX": "Undertext inläst igen (ändrad utanför Subtitle Edit): {0}", "suffix": "Suffix", "suggestions": "Förslag", "sync": "Synk", @@ -673,7 +667,6 @@ "teletextColor": "Text-tv-färg", "teletextLine": "Text-tv-rad", "tenHours": "10 timmar", - "tenMilliseconds": "10 millisekunder", "text": "Text", "textColor": "Textfärg", "textFiles": "Textfiler", @@ -700,9 +693,6 @@ "topRight": "Topp-Höger", "top": "Överkant", "totalAdjustmentX": "Total justering: {0}", - "totalFramesX": "Totalt antal bilder: {0:#;##0.##}", - "totalLengthX": "Total längd: {0}", - "totalLengthXSplitLine": "Total längd: {0} (delad rad!)", "translate": "Översätt", "translatePercentX": "Översätter: {0}%", "translateRow": "Översätt rad", @@ -711,6 +701,7 @@ "type": "Typ", "unbreak": "Slå ihop text", "unbreakLines": "Slå ihop rader", + "undelete": "Ångra borttagning", "underline": "Understrykning", "undo": "Ångra", "unknown": "Okänt", @@ -730,13 +721,11 @@ "usages": "Användningar", "use": "Använd", "useAlways": "Använd alltid", - "useLargerFontForThisWindow": "Använd större typsnitt för detta fönster", "useOnce": "Använd en gång", "useOutputFolder": "Använd utmatningsmapp", "useSourceFolder": "Använd källmapp", "useSourceResolution": "Använd källupplösning", "userName": "Användarnamn", - "userNameAlreadyInUse": "Användarnamnet används redan", "vertical": "Vertikal", "video100MsBack": "Video, 100 millisekunder bakåt", "video100MsForward": "Video, 100 millisekunder framåt", @@ -746,13 +735,16 @@ "videoCustom1ForwardX": "Video, anpassade millisekunder ({0:#,###,##0}) framåt, 1", "videoCustom2BackX": "Video, anpassade millisekunder ({0:#,###,##0}) bakåt, 2", "videoCustom2ForwardX": "Video, anpassade millisekunder ({0:#,###,##0}) framåt, 2", - "videoEncodingX": "Videokodning: {0}", "videoExtension": "Videofilsändelse", "videoFile": "Videofil", "videoFileGenerated": "Videofil genererad", "videoFileGeneratedX": "Videofil genererad: \"{0}\"", + "videoFilesGenerated": "Videofiler genererade", + "xVideosGenerated": "{0} videor genererade", + "xOfYVideosGenerated": "{0} av {1} videor genererade", + "savedInX": "Sparad i {0}", + "savedInXFolders": "Sparad i {0} mappar", "videoFiles": "Videofiler", - "videoInformation": "Videoinfo", "videoOffset": "Videoförskjutning", "videoOneFrameBack": "Video, en bildruta bakåt", "videoOneFrameForward": "Video, en bildruta framåt", @@ -763,7 +755,6 @@ "videoPlayer": "Videospelare", "videoPosition": "Videoposition", "videoResolution": "Videoupplösning", - "viewX": "Visa {0}", "visible": "Synlig", "voice": "Röst", "volume": "Volym", @@ -771,9 +762,7 @@ "waveformCenterOnVideoPosition": "Centrera vågform på videoposition", "waveformPasteFromClipboard": "Klistra in från urklipp", "waveformSpectrogram": "Vågform/spektrogram", - "webServiceUrl": "Webbtjänst-URL", "width": "Bredd", - "windowPositionAndSize": "Fönsterposition och storlek", "wordsPerMin": "Ord/min", "pixelWidth": "Pixelbredd", "wpm": "Ord/min", @@ -787,8 +776,6 @@ "noBookmarksFound": "Inga fler bokmärken hittades", "xRequiresAnApiKey": "{0} kräver en API-nyckel", "xRequiresAValidUrl": "{0} kräver en giltig URL (t.ex. \"http://...\" eller \"https://...\")", - "xSeconds": "{0:0.0##} sekunder", - "xSubtitles": "{0:#,###,##0} undertexter", "yes": "Ja", "zeroBytes": "0 byte", "copyToClipboard": "Kopiera till urklipp", @@ -810,10 +797,8 @@ "playbackSpeed": "Uppspelningshastighet", "hoursMinutesSecondsMilliseconds": "HH:MM:SS.FFF", "imageFiles": "Bildfiler", - "openAiCompatibleStt": "OpenAI-kompatibel STT", "openAiCompatibleSttEndpoint": "STT Endpoint URL", "openAiCompatibleSttApiKey": "API-nyckel", - "openAiCompatibleSttModel": "Modell", "openAiCompatibleSttExtraHeaders": "Extra rubriker", "openAiCompatibleSttTimeout": "Timeout (sekunder)", "openAiCompatibleSttLanguage": "Språktips", @@ -828,11 +813,9 @@ "configurationRequired": "Konfiguration krävs", "transcriptionError": "Transkriptionsfel", "transcriptionComplete": "Transkriptionen är klar", - "transcribingWithOpenAi": "Transkriberar med OpenAI Service...", "processingResponse": "Bearbetar svar...", "unauthorizedApiKey": "Obehörig: Ogiltig API-nyckel eller otillräckliga behörigheter.", "requestTimeout": "Timeout för begäran: Servern tog för lång tid att svara.", - "noSegmentsReturned": "Inga segment returnerades från transkriptionstjänsten.", "openAiCompatibleSttUrlMissing": "OpenAI-kompatibel STT-slutpunkts-URL krävs.\r\nAnge en URL och försök igen.", "openAiCompatibleSttUrlNotResponding": "Den OpenAI-kompatibla STT-ändpunkten svarade inte:\r\n\r\n{0}\r\n\r\n\r\nVänligen verifiera URL:en och att servern är igång.", "transcriptionFailed": "Transkriptionen misslyckades", @@ -854,11 +837,22 @@ "videoCustom3ForwardX": "Video, anpassade millisekunder ({0:#,###,##0}) framåt, 3", "videoCustom4BackX": "Video, anpassade millisekunder ({0:#,###,##0}) bakåt, 4", "videoCustom4ForwardX": "Video, anpassade millisekunder ({0:#,###,##0}) framåt, 4", + "videoCustom1BackAndPauseX": "Video, anpassade millisekunder ({0:#,###,##0}) bakåt och pausa, 1", + "videoCustom1ForwardAndPauseX": "Video, anpassade millisekunder ({0:#,###,##0}) framåt och pausa, 1", + "videoCustom2BackAndPauseX": "Video, anpassade millisekunder ({0:#,###,##0}) bakåt och pausa, 2", + "videoCustom2ForwardAndPauseX": "Video, anpassade millisekunder ({0:#,###,##0}) framåt och pausa, 2", + "videoCustom3BackAndPauseX": "Video, anpassade millisekunder ({0:#,###,##0}) bakåt och pausa, 3", + "videoCustom3ForwardAndPauseX": "Video, anpassade millisekunder ({0:#,###,##0}) framåt och pausa, 3", + "videoCustom4BackAndPauseX": "Video, anpassade millisekunder ({0:#,###,##0}) bakåt och pausa, 4", + "videoCustom4ForwardAndPauseX": "Video, anpassade millisekunder ({0:#,###,##0}) framåt och pausa, 4", "videoAndAudioFiles": "Video- och ljudfiler", "waveform": "Vågform", "dashScopeSttRegion": "Region", "dashScopeSttEnableWords": "Tidsstämplar på ordnivå", "dashScopeSttRegionKeyHint": "Obs: API-nycklar för Alibaba Cloud Model Studio är regionsspecifika - se till att den valda regionen matchar den region där API-nyckeln skapades (Kina vs. Internationell).", + "googleCloudSttProjectId": "Projekt-ID (endast för Application Default Credentials)", + "googleCloudSttBucketName": "Lagringsbucket (tom = automatiskt)", + "googleCloudSttDynamicBatching": "Dynamisk batchbearbetning (cirka 5x billigare, ingen garanterad latens)", "onlineSttApiKeyMissing": "En API-nyckel krävs. Ange din API-nyckel och försök igen." }, "main": { @@ -868,12 +862,15 @@ "charactersPerSecond": "Tecken/sekund: {0}", "chooseColumn": "Välj kolumn", "columnPaste": "Klistra in kolumn", + "commandPaletteTitle": "Kommandopalett", + "commandPaletteSearchPlaceholder": "Skriv för att söka kommandon...", + "commandPaletteNoCommandsFound": "Inga matchande kommandon", "createdEmptyTranslation": "Skapade tom översättning från aktuell undertext", "deleteText": "Ta bort text", "deleteTextAndShiftCellsUp": "Ta bort text och flytta upp celler", "endTimeMustBeAfterStartTime": "Sluttid måste vara efter starttid.", "errorLoad7Zip": "Den här filen verkar vara en komprimerad 7-Zip-fil.\n\nSubtitle Edit kan inte öppna komprimerade filer.", - "errorLoadBinaryZeroes": "Tyvärr, denna fil innehåller endast binära nollor!\n\nOm du har redigerat denna fil med Subtitle Edit kan du kanske hitta en säkerhetskopia via menyalternativet Arkiv -> Återställ auto-säkerhetskopia...", + "errorLoadBinaryZeroes": "Tyvärr, denna fil innehåller endast binära nollor!\n\nOm du har redigerat denna fil med Subtitle Edit kan du kanske hitta en säkerhetskopia via menyalternativet Arkiv -> Återställ auto-säkerhetskopia...", "errorLoadGZip": "Den här filen verkar vara en komprimerad GZip-fil.\n\nSubtitle Edit kan inte öppna komprimerade filer.", "errorLoadLargeFile": "Den här filen är för stor för att öppna som undertextfil.", "errorLoadVideoFilePrompt": "Den här filen verkar vara en videofil:\n{0}\n\nDu kan öppna en video via \"Video -> Öppna video\" eller genom att dra och släppa en video på videospelaren.\n\nVill du öppna den som en videofil?", @@ -905,18 +902,20 @@ "numberOfLinesEvenlyDistributedX": "Fördelade {0} rader jämnt", "oneLineCopiedFromOriginal": "En rad kopierad från originalundertext", "oneLineCopiedToOriginal": "En rad kopierad till originalundertexten", + "onlineOnlyVideo": "Video som bara finns online", + "onlineOnlyVideoXSizeYDownloadAndOpen": "Videofilen \"{0}\" ({1}) finns bara lagrad online, till exempel i Dropbox eller iCloud Drive, och måste laddas ner innan den kan öppnas.\n\nLadda ner och öppna den nu?", "oneLineMerged": "En rad sammanslagen", "oneLineSwitched": "En rad växlad", "overwriteExistingCells": "Skriv över befintliga celler", "overwriteOrShiftCellsDown": "Skriv över/flytta ner celler", "parsingMatroskaFile": "Tolkar Matroska-fil...", + "readingDvdSubtitles": "Läser DVD-undertexter...", "pasteFromClipboardDotDotDot": "Klistra in från urklipp...", "redoPerformed": "Gör om utfört", "redoPerformedXActionLeft": "Gör om utfört (åtgärder kvar: {0})", "removedUnicodeControlCharactersX": "Tog bort Unicode-kontrolltecken från {0} rader", "removedXBlankLines": "Tog bort {0} tomma rader", "replacedXWithYCountZ": "Ersatte \"{0}\" med \"{1}\" ({2} förekomster)", - "replacedXWithYInLineZ": "Ersatte \"{0}\" med \"{1}\" på rad {2}", "reversedStartAndEndingsForRightToLeftX": "Vände start och slut för höger-till-vänster på {0} rader", "ruleProfileIsX": "Regelprofil är nu \"{0}\"", "saveLanguageFile": "Spara språkfil", @@ -924,7 +923,6 @@ "shiftTextCellsDown": "Flytta ner textceller", "singleLineLength": "Radlängd: ", "speedIsNowX": "Hastighet är nu \"{0}\"", - "spellCheckResult": "Rättstavning slutförd. \n\n• Ändrade ord: {0}\n• Överhoppade ord: {1}", "subtitleGridFormattingX": "Listformatering: {0}", "subtitleImportedFromMatroskaFile": "Undertext importerad från Matroska-fil", "textDown": "Text ner", @@ -951,13 +949,14 @@ "trimmedXLines": "Trimmade {0} undertextrader", "pastedXLinesOverSelectedLines": "Klistrade in {0} rad(er) över de markerade raderna", "pastedXOfYLinesOverSelectedLines": "Klistrade in {0} av {1} rad(er) över de markerade raderna - resten fick inte plats i markeringen", + "insertedXLinesFromClipboard": "Infogade {0} rad(er) från urklipp", "openOriginalDifferentNumberOfSubtitlesXY": "Originalundertextfilen har inte samma antal undertexter som den aktuella undertextfilen.\n\n• Originalundertexter: {0}\n• Aktuella undertexter: {1}", - "importXMatchingOriginalLines": "Importera {0} matchande originalundertexter?", "originalTextReadOnly": "Originaltext (skrivskyddad)", "originalTextEditMode": "Originaltext (redigeringsläge)", "originalIsReadOnlyNotSaved": "Originalundertexten är en skrivskyddad referens och sparades inte", "originalIsEmptyNotSaved": "Originalundertexten innehåller ingen text alls och sparades inte", "originalIsReadOnlyReference": "Originalundertexten är öppnad som en skrivskyddad referens", + "closeTranslationQuestionXY": "Stäng översättningen \"{0}\"?\n\nOriginalundertexten \"{1}\" blir den undertext du redigerar. Själva översättningsfilen ändras inte.", "allowEditOfOriginalSubtitle": "Tillåt redigering av originalundertexten", "showAllOriginalLinesX": "Visa alla originalrader ({0} har ingen motsvarighet här)", "showAllOriginalLinesHint": "Raderna utan motsvarighet visas som extra rader, så att du kan se vad översättningen saknar.", @@ -992,6 +991,7 @@ "fileExportedInFormatXToY": "Fil exporterad i format {0} till {1}", "fileExportedInFormatXToFileY": "Fil exporterad i format \"{0}\" till fil \"{1}\"", "fixedXLines": "Åtgärdade {0} rader", + "toolXChangedYLines": "{0}: {1} rader ändrade", "transcriptionCompletedWithXLines": "Transkription slutförd med {0} rader", "openSubtitleFileX": "Öppna undertextfil \"{0}\"?", "replacedXOccurrences": "Ersatte {0} förekomster", @@ -1053,14 +1053,15 @@ "multipleReplace": "_Multipel ersättning...", "fillSelectedLinesWithClipboard": "Fyll markerade rader med urklippstext", "goToLineNumber": "_Gå till radnummer...", + "commandPalette": "Kommando_palett...", "rightToLeftMode": "Hö_ger-till-vänster-läge", "modifySelectionDotDotDot": "Ändra _markering...", "tools": "_Verktyg", - "toolsSelectedLines": "_Verktyg (markerade rader)", "adjustDurations": "_Justera varaktigheter...", "applyDurationLimits": "Tillämpa varaktighets_gränser...", "batchConvert": "_Batchkonvertera...", "beautifyTimeCodes": "Försköna tidskoder...", + "improveTimeCodes": "Förbättra tidskoder (forced alignment)...", "bridgeGaps": "Överbrygga _mellanrum...", "applyMinGap": "Tillämpa min. mellanrum mellan undertexter...", "changeCasing": "_Ändra skiftläge...", @@ -1201,6 +1202,9 @@ "selectCurrentLineWhilePlayingHint": "Välj aktuell undertext under uppspelning {0}", "videoPosition": "Videoposition {0}", "videoPositionTextBox": "Textruta för videoposition {0}", + "toolbarLineBreakX": "Radbrytning {0} (resten av verktygsfältet fortsätter på en ny rad)", + "initialText": "Ursprunglig text för markerad rad", + "initialTextHint": "Texten på den markerade raden som den var när raden markerades", "hideWaveformToolbar": "Dölj verktygsfält {0}", "resetZoomAndSpeed": "Återställ zoom & uppspelningshastighet {0}", "removeBlankLines": "Ta bort tomma rader {0}", @@ -1214,6 +1218,15 @@ "textPlayHint": "Spela upp aktuell undertext och stanna vid slutet {0}", "textPauseHint": "Pausa uppspelningen {0}", "textNextHint": "Spela upp nästa undertext och stanna vid slutet {0}", + "moveSelectedLines": "Flytta markerade rader (bakåt/framåt-knappar)", + "moveSelectedLinesAndFollowing": "Flytta markerade rader och alla efterföljande (bakåt/framåt-knappar)", + "moveAllLines": "Flytta alla rader (bakåt/framåt-knappar)", + "moveSelectedLinesBackHint": "Flytta markerade rader {0} ms bakåt {1}", + "moveSelectedLinesForwardHint": "Flytta markerade rader {0} ms framåt {1}", + "moveSelectedLinesAndFollowingBackHint": "Flytta markerade rader och alla efterföljande {0} ms bakåt {1}", + "moveSelectedLinesAndFollowingForwardHint": "Flytta markerade rader och alla efterföljande {0} ms framåt {1}", + "moveAllLinesBackHint": "Flytta alla rader {0} ms bakåt {1}", + "moveAllLinesForwardHint": "Flytta alla rader {0} ms framåt {1}", "guessStartNoLineSelected": "Gissa start: ingen rad markerad", "guessStartTimeCodesLocked": "Gissa start: tidskoderna är låsta", "guessStartNoWaveform": "Gissa start: ingen vågform", @@ -1260,20 +1273,66 @@ "xNumberOfDifferenceAndPercentChanged": "Antal skillnader: {0} ({1:0.##}% av ord ändrade)", "xNumberOfDifferenceAndPercentLettersChanged": "Antal skillnader: {0} ({1:0.##}% av bokstäver ändrade)", "showOnlyDifferences": "Endast skillnader", - "ignoreLineBreaks": "Ignorera radbrytningar", "ignoreWhitespace": "Ignorera blanksteg", "ignoreWhitespaceHint": "Rader som endast skiljer sig i mellanslag, tabbar eller radbrytningar räknas inte som olika", "ignoreFormatting": "Ignorera formatering", - "onlyLookForDifferencesInText": "Sök endast efter skillnader i text", - "cannotCompareWithImageBasedSubtitles": "Kan inte jämföra med bildbaserade undertexter", - "statisticsTitle": "Statistik", "ignoreFormattingHint": "Rader som endast skiljer sig i formateringstaggar, som eller {\\an8}, räknas inte som olika", "showOnlyDifferencesInText": "Endast skillnader i text", + "ignoreNumbering": "Ignorera numrering", + "ignoreNumberingHint": "Rader som endast skiljer sig i sitt nummer räknas inte som olika", + "ignoreTimeDifferenceMs": "Tidstolerans (ms)", + "ignoreTimeDifferenceMsHint": "Start- och sluttider som skiljer sig med så här många millisekunder eller mindre räknas inte som olika", + "compareDifferences": "Skillnader", + "compareTextDifferences": "Textskillnader", + "compareCurrent": "Aktuell", + "compareReference": "Referens", + "compareEditable": "Redigerbar", + "compareEditableHint": "Ändringar som görs här förs tillbaka till undertexten när du klickar på Tillämpa", + "compareReadOnly": "Skrivskyddad", + "compareTakeFromReference": "Hämta text och tider från referensen", + "compareInsertFromReference": "Infoga den här raden i den aktuella undertexten", + "compareDeleteFromCurrent": "Ta bort den här raden från den aktuella undertexten", + "compareTakeText": "Hämta text", + "compareTakeTiming": "Hämta tider", + "compareEditLine": "Redigera rad", + "compareEditHint": "Ctrl+Enter för att spara, Esc för att avbryta", + "compareEdited": "Redigerad", + "compareOnePendingChange": "1 väntande ändring", + "compareXPendingChanges": "{0} väntande ändringar", + "compareDiscardXChanges": "Förkasta de {0} ändring(ar) som gjorts i Jämför?", + "compareXChangesApplied": "Jämför: {0} ändring(ar) tillämpade", + "compareChangeTextX": "#{0} text från referensen", + "compareChangeTimingX": "#{0} tider från referensen", + "compareChangeTextAndTimingX": "#{0} text och tider från referensen", + "compareChangeEditedX": "#{0} redigerad", + "compareChangeInsertedX": "#{0} infogad", + "compareChangeDeletedX": "#{0} borttagen", + "compareSyncPoint": "Synkpunkt", + "compareSyncPointHint": "Synkpunkt - dessa två rader visas alltid som ett par, och raderna ovanför och nedanför justeras var för sig", + "compareSyncPickCurrent": "Synkpunkt: använd den här aktuella raden", + "compareSyncPickReference": "Synkpunkt: använd den här referensraden", + "compareSyncRemove": "Ta bort synkpunkt", + "compareClearXSyncPoints": "Rensa synkpunkter ({0})", + "compareSyncCurrentPickedX": "Aktuell #{0} vald som synkpunkt - markera den matchande referensraden och klicka på Synka", + "compareSyncReferencePickedX": "Referens #{0} vald som synkpunkt - markera den matchande aktuella raden och klicka på Synka", + "compareSyncWithCurrentX": "Synka med aktuell #{0}", + "compareSyncWithReferenceX": "Synka med referens #{0}", + "compareSyncApply": "Synk", + "compareSyncPointSetXY": "Synkpunkt satt: aktuell #{0} och referens #{1} visas nu som ett par", + "compareSyncAlreadyPairedXY": "Aktuell #{0} och referens #{1} visas redan som ett par med samma start - inget att synka", + "compareSyncPointShiftedXYZW": "Synkpunkt satt: aktuell #{0}-#{1} förskjuten {2} för att börja med referens #{3}", + "compareChangeSyncShiftXYZ": "#{0}-#{1} förskjuten {2} (synkpunkt)", + "compareTimingDeltaHint": "Hur mycket senare (+) eller tidigare (−) referensen börjar och slutar än den aktuella raden", "loadXFromFile": "Läs in \"{0}\" från fil", + "compareLoadSavedFile": "Läs in sparad fil", "saveCompareHtmlTitle": "Spara HTML-fil för jämförelse", "pickMatroskaTrackX": "Välj Matroska-spår - {0}", "pickTransportStreamTrackX": "Välj transportströmsspår - {0}", + "chapters": "Kapitel", + "dvdVobFilesMissingX": "VOB-filer saknas ({0}% hittades)", "pickMp4TrackX": "Välj MP4-spår - {0}", + "pickMxfTrackX": "Välj MXF-spår - {0}", + "pickMpegTrackX": "Välj MPEG-spår - {0}", "rosettaProperties": "Timed Text Rosetta IMSC-egenskaper", "rosettaFontSize": "Typsnittsstorlek (radhöjd)", "propertyTimeBase": "Tidsbas", @@ -1283,7 +1342,6 @@ "propertyDefaultRegion": "Standardregion", "propertyStyleAttributeName": "Namn på stilattribut", "propertyTimeCodeFormat": "Tidskodsformat", - "propertyFileExtension": "Filändelse", "propertyTopOrigin": "Övre origo", "propertyTopExtent": "Övre utsträckning", "propertyBottomOrigin": "Nedre origo", @@ -1291,13 +1349,9 @@ "xProperties": "{0} egenskaper", "ebuSaveOptions": { "title": "EBU-sparalternativ", - "generalSubtitleInformation": "Allmän undertextinformation", "codePageNumber": "Kodsidnummer", "diskFormatCode": "Diskformatkod", "displayStandardCode": "Visningsstandardkod", - "colorRequiresTeletext": "Färger kräver text-TV!", - "alignmentRequiresTeletext": "Justering kräver text-TV!", - "teletextCharsShouldBe38": "'Max# tecken per rad' för text-TV bör vara 38!", "characterCodeTable": "Teckentabell", "languageCode": "Språkkod", "originalProgramTitle": "Originalprogramtitel", @@ -1314,7 +1368,6 @@ "maxNumberOfDisplayableRows": "Max# rader", "diskSequenceNumber": "Disksekvensnummer", "totalNumberOfDisks": "Totalt antal diskar", - "import": "Importera...", "textAndTimingInformation": "Text- och tidsinformation", "justificationCode": "Justeringskod", "verticalPosition": "Vertikal position", @@ -1331,11 +1384,6 @@ "errors": "Fel", "errorsX": "Fel: {0}", "maxLengthError": "Rad {0} överstiger maxlängd ({1}) med {2}: {3}", - "textUnchangedPresentation": "Oförändrad presentation", - "textLeftJustifiedText": "Vänsterjusterad text", - "textCenteredText": "Centrerad text", - "textRightJustifiedText": "Högerjusterad text", - "useBoxForOneNewLine": "Markera 'Använd ruta runt text' för endast en ny rad", "maxLengthErrorTeletextColored": "Rad {0}-{1}: 36 (inte {2}) bör vara högsta antal tecken för färgad text med dubbel höjd", "maxLengthErrorTeletextWhite": "Rad {0}-{1}: 37 (inte {2}) bör vara högsta antal tecken för vit text med dubbel höjd", "maxLengthErrorTeletextSingleHeight": "Rad {0}: 38 (inte {1}) bör vara högsta antal tecken för vit text med enkel höjd", @@ -1360,22 +1408,29 @@ "formattingDotDotDot": "Formatering...", "imageBasedSubtitleForEditDotDotDot": "Bildbaserad undertext för redigering...", "imageBasedSubtitleForOcrDotDotDot": "Bildbaserad undertext för OCR...", + "dvdSubtitlesDotDotDot": "Undertexter från DVD (IFO/VOB)...", + "titleImportDvd": "Importera undertexter från DVD", + "dvdIfoFile": "IFO-fil", + "dvdVobFiles": "VOB-filer", + "dvdVideoStandard": "Videostandard", + "dvdStartRipping": "Starta rippning", + "dvdRippingAborted": "Rippning avbruten", + "dvdOpenIfoFile": "Öppna DVD IFO-fil", + "dvdAddVobFiles": "Lägg till VOB-filer", + "dvdIfoFiles": "DVD IFO-filer", + "dvdVobFileType": "DVD VOB-filer", + "dvdDropHint": "Öppna en IFO-fil (VIDEO_TS\\VTS_01_0.IFO) eller lägg till VOB-filer - eller släpp dem här", + "dvdNotAnIfoFileX": "{0} är inte en DVD IFO-fil", + "dvdMenuIfoNoTitleSetsX": "Inga titeluppsättningar (VTS_xx_0.IFO) hittades bredvid {0}", + "dvdEncryptedXOfY": "{0} av de {1} undertextpaketen är fortfarande CSS-krypterade - dessa VOB-filer kopierades utan dekryptering, så vissa undertextbilder kommer att bli förvrängda.\n\nDekryptera DVD:n med ett DVD-rippningsverktyg först för ett rent resultat.\n\nFortsätta ändå?", "splitTextAt": "Dela text vid", "blankLines": "Tomma rader", "oneLineIsOneSubtitle": "En rad är en undertext", "twoLinesAreOneSubtitle": "Två rader är en undertext", "importFilesDotDotDot": "Importera filer...", "multipleFiles": "Importera från flera textfiler (en fil är en undertext)", - "importOptions": "Importeringsalternativ", - "autoSplitText": "Dela text automatiskt", - "lineMode": "Radläge", "maxLineLength": "Max radlängd", - "maxLinesPerSubtitle": "Max antal rader per undertext", - "minGapBetweenSubtitles": "Min mellanrum mellan undertexter", "mergeShortLines": "Slå ihop korta rader", - "removeLinesWithoutLetters": "Ta bort rader utan bokstäver", - "splitAtEndCharsSetting": "Dela vid slutmarkeringar", - "takeTimeFromCurrentFile": "Ta tid från aktuell fil", "fixed": "Fast", "numberOfSubtitlesX": "Antal undertexter: {0}", "numberOfSubtitlesXForcedY": "Antal undertexter: {0} ({1} tvingade)", @@ -1387,14 +1442,18 @@ "forcedAlignerProgress": "Justerar... fönster {0} av {1}, {2} av {3} rader", "forcedAlignerSetupTitle": "Justera tidskoder via forced aligner", "forcedAlignerSetupIntro": "En forced aligner matchar undertexten du redan har mot ljudet, utan att först transkribera det. Långa videor justeras i delar, så alla längder fungerar.", + "forcedAlignerEndsFromSpeech": "Sätt sluttider från isolerat tal (långsamt)", + "forcedAlignerEndsFromSpeechHint": "En forced aligner hittar var en rad börjar, inte var den slutar, så sluttider kommer normalt från lästiden. Med detta påslaget tas musik och ljudeffekter först bort från ljudet och varje rad slutar där dess tal tystnar. Tar ungefär lika lång tid som själva videon med en GPU - och många gånger längre utan.", + "forcedAlignerIsolatingSpeech": "Isolerar tal... {0}", + "forcedAlignerIsolatingSpeechFailed": "Kunde inte isolera talet - sluttiderna sattes från lästiden i stället.", "forcedAlignerModel": "Justeringsmodell", - "forcedAlignerDownloadEngine": "Ladda ner/uppdatera motor..." + "forcedAlignerDownloadEngine": "Ladda ner/uppdatera motor...", + "forcedAlignerEstimatedXOfY": "{0} av {1} rader kunde inte justeras och placerades efter lästid - deras tidskoder är bara uppskattade. Manuset är troligen längre än det som sägs i videon." }, "export": { "exportImagesProfiles": "Exportera bildprofiler", "leftRightMargin": "Vänster/höger marginal", "topBottomMargin": "Topp/botten marginal", - "titleExportBluRaySup": "Exportera Blu-ray (sup)", "lineSpacingPercent": "Radavstånd %", "paddingLeftRight": "Utfyllnad vänster/höger", "paddingTopBottom": "Utfyllnad topp/botten", @@ -1435,10 +1494,21 @@ "titleExportDCinemaInteropPng": "D-Cinema interop/png", "titleExportDCinemaSmpte2014Png": "D-Cinema SMPTE 2014/png", "imageBasedSubtitleSaved": "Bildbaserad undertext sparad", - "titleExportBdnXml": "Exportera BDN/xml", "titleExportWebVttThumbnails": "Exportera WebVTT (miniatyrbilder)", "fullFrameImage": "Helbildsruta", "fullFrameImageHint": "Gör varje bild lika stor som videons bildruta, med undertexten på sin plats", + "stereo3D": "3D", + "stereo3DHint": "För 3D-video: rita undertexten en gång i varje ögas halva av bildrutan", + "halfSideBySide": "Halv sida vid sida", + "halfTopBottom": "Halv topp/botten", + "depth3D": "Djup", + "depth3DHint": "Hur långt undertexten sticker ut från skärmen - positivt för den närmare, negativt skjuter den bakåt", + "plane3D": "3D-Plane", + "plane3DHint": "Ge varje undertext det djup som en 3D Blu-ray lagrar för den, från en 3D-Plane-fil (.ofs) skapad av BD3D2MK3D eller OFSExtractor. Djup används där 3D-Plane saknar värde", + "openPlane3DTitle": "Öppna 3D-Plane-fil", + "unableToLoadPlane3DX": "Kunde inte läsa in 3D-Plane: {0}", + "plane3DXDepthYToZ": "{0} (djup {1} till {2})", + "plane3DXNoDepth": "{0} (inga djupvärden)", "textEffect": "Texteffekt", "textEffectSettingsTitle": "Inställningar för texteffekt", "textEffectPreset": "Förinställning", @@ -1470,13 +1540,10 @@ "statistics": { "title": "Statistik", "titleWithFileName": "Statistik - {0}", - "generalStatistics": "Allmän statistik", - "mostUsed": "Mest använda...", "mostUsedLines": "Mest använda rader", "mostUsedWords": "Mest använda ord", "nothingFound": "Inget hittades", "numberOfLinesX": "Antal undertextrader: {0:#,##0}", - "numberOfFilesX": "Antal undertextfiler: {0:#,##0}", "lengthInFormatXinCharactersY": "Antal tecken som {0}: {1:#,###,##0}", "numberOfCharactersInTextOnly": "Antal tecken endast i text: {0:#,###,##0}", "totalDuration": "Total varaktighet för alla undertexter: {0:#,##0}", @@ -1517,7 +1584,6 @@ "gapMaximum": "Mellanrum - maximum: {0:#,##0} ms", "gapAverage": "Mellanrum - genomsnitt: {0:#,##0.##} ms", "gapExceedingMinimum": "Mellanrum - under minimum ({0:#,##0} ms): {1} ({2:0.00}%)", - "export": "Exportera...", "subtitles": "Undertexter", "words": "Ord", "totalDurationShort": "Total varaktighet", @@ -1547,6 +1613,21 @@ "restoreXFromY": "Vill du återställa \"{0}\" från {1}?", "deleteAllSubtitleBackups": "Vill du ta bort alla säkerhetskopior av undertexter?", "deleteAll": "Ta bort alla", + "subtitles": "Undertexter", + "settingsBackupInfo": "Settings.json säkerhetskopieras när Subtitle Edit startar, högst en gång var {0}:e dag och endast om inställningarna har ändrats. De {1} senaste säkerhetskopiorna behålls.", + "settingsBackupInfoEveryStart": "Settings.json säkerhetskopieras varje gång Subtitle Edit startar, om inställningarna har ändrats. De {0} senaste säkerhetskopiorna behålls.", + "settingsBackupOff": "Automatisk säkerhetskopiering av inställningar är avstängd (Alternativ → Inställningar → Fil).", + "lastSettingsBackupX": "Senaste säkerhetskopia: {0}", + "noSettingsBackupsYet": "Inga säkerhetskopior av inställningar ännu", + "openSettingsBackupFolder": "Öppna mapp för säkerhetskopior av inställningar", + "backUpSettingsNow": "Säkerhetskopiera nu", + "settingsBackedUp": "Aktuella inställningar har säkerhetskopierats.", + "restoreSettings": "Återställ inställningar", + "restoreSettingsFromX": "Vill du ersätta dina aktuella inställningar med säkerhetskopian från {0}?\n\nDina aktuella inställningar säkerhetskopieras först. Vissa ändringar träder i kraft först efter omstart av Subtitle Edit.", + "settingsRestored": "Inställningarna har återställts. Starta om Subtitle Edit för att alla ändringar ska träda i kraft.", + "settingsRestoreFailed": "Kunde inte återställa inställningarna: {0}", + "settingsBackupNotValid": "säkerhetskopian är inte en giltig inställningsfil, så dina aktuella inställningar behölls", + "deleteAllSettingsBackups": "Vill du ta bort alla säkerhetskopior av inställningar?", "xBackups": "{0} säkerhetskopior" }, "propertiesDCinema": { @@ -1561,22 +1642,16 @@ "editRate": "Redigeringsfrekvens", "timeCodeRate": "Tidskodsfrekvens", "startTime": "Starttid", - "font": "Typsnitt", "fontId": "Typsnitts-ID", "fontUri": "Typsnitts-URI", - "generate": "Generera", - "fontColor": "Typsnittsfärg", "chooseColor": "Välj färg", "fontEffect": "Typsnittseffekt", "effectColor": "Effektfärg", - "fontSize": "Typsnittsstorlek", "topBottomMargin": "Topp/botten marginal", "fadeUpTime": "Tona upp-tid", "fadeDownTime": "Tona ner-tid", - "frames": "Bilder", "zPosition": "Z-position", - "zPositionHelp": "Positiva tal flyttar texten längre bort, negativa tal flyttar texten närmare; om z-positionen är noll är den 2D", - "export": "Exportera..." + "zPositionHelp": "Positiva tal flyttar texten längre bort, negativa tal flyttar texten närmare; om z-positionen är noll är den 2D" }, "compareOnlyInOneFile": "Endast i en fil", "compareTextOrTimeDifference": "Text-/tidsskillnad", @@ -1642,7 +1717,6 @@ "selectionIntersect": "Skär med markering" }, "multipleReplace": { - "title": "Multipel ersättning", "editRule": "Redigera regel", "newRule": "Ny regel", "editCategory": "Redigera kategori", @@ -1659,24 +1733,23 @@ "findWhat": "Sök efter", "descriptionOptional": "Beskrivning (valfritt)", "invalidRegularExpressionX": "Ogiltigt reguljärt uttryck: {0}", - "regularExpressionTooSlowX": "Det reguljära uttrycket gav upp efter {0} sekunder - regeln hoppas över, prova ett enklare mönster" + "regularExpressionTooSlowX": "Det reguljära uttrycket gav upp efter {0} sekunder - regeln hoppas över, prova ett enklare mönster", + "moveToCategory": "Flytta till kategori" }, "find": { "searchTextWatermark": "Söktext...", "wholeWord": "Helord", - "caseSensitive": "Skiftlägeskänslig", - "caseInsensitive": "Skiftlägesokänslig", "findPrevious": "Sök _föregående", "findNext": "Sök _nästa", "replaceAndFindNext": "_Ersätt & sök nästa", "replaceAll": "Ersätt _alla", - "replaceTitle": "Ersätt", - "replaceWith": "Ersätt med", "replaceTextWatermark": "Ersätt med...", "replaceIn": "Ersätt/sök i", "replaceInTextAndOriginal": "Text och originaltext", "replaceInTextOnly": "Endast text", - "replaceInOriginalOnly": "Endast originaltext" + "replaceInOriginalOnly": "Endast originaltext", + "replacedOneOccurrence": "Ersatte en förekomst", + "replacedXOccurrences": "Ersatte {0} förekomster" }, "noSubtitleSelected": "Ingen undertext markerad", "pleaseSelectExactlyOneSubtitle": "Markera exakt en undertext." @@ -1687,6 +1760,7 @@ "xLinesYSubtitles": "{0} rader, {1} undertexter", "xLinesYSubtitlesZErrors": "{0} rader, {1} undertexter, {2} fel", "couldNotParseAsX": "Kunde inte tolkas som {0}", + "readAsXUseAsY": "Källan kunde inte läsas som {1}, men den kunde läsas som {0}.\nAnvända raderna och fortsätta spara som {1}?", "selectedXCharactersYLines": "Markerat: {0} tecken på {1} rad(er)", "replacedXOccurrences": "Ersatte {0} förekomst(er)", "invalidRegularExpression": "Ogiltigt reguljärt uttryck", @@ -1698,7 +1772,6 @@ "closeSearch": "Stäng sökning" }, "tools": { - "pickAlignmentTitle": "Välj justering", "removeUnicodeCharacters": { "title": "Ta bort/ersätt Unicode-tecken", "charactersFoundX": "Unicode-tecken hittade: {0}", @@ -1708,30 +1781,22 @@ "pickFontNameInstalledFonts": "Installerade typsnitt", "pickFontNameCollectedFonts": "Insamlade typsnitt", "colorPickerTitle": "Välj färg", + "colorPickerBrightness": "Ljusstyrka", "filterLayersTitle": "Filtrera lager för visning", "filterLayersHideFromWaveform": "Dölj från vågform/spektrogram", "filterLayersHideFromSubtitleGrid": "Dölj från undertextlista", "filterLayersHideFromVideoPreview": "Dölj från videoförhandsvisning", "pickSubtitleFormat": "Välj undertextformat", - "pickLayerTitle": "Sätt lager", "recentColors": "Senaste färger", "fixCommonErrors": { - "title": "Åtgärda vanliga fel", - "step1": "Steg 1/2 - Välj vilka fel som ska åtgärdas", - "whatToFix": "Vad som ska åtgärdas", - "example": "Exempel", - "selectAll": "Markera alla", - "inverseSelection": "Invertera markering", "back": "< &Bakåt", "next": "&Nästa >", - "step2": "Steg 2/2 - Verifiera åtgärder", "fixes": "Åtgärder", "ruleDetailsDotDotDot": "Regeldetaljer...", "showOnlyThisRule": "Visa endast åtgärder från den här regeln", "appliedRulesForLineX": "Tillämpade regler för rad {0}", "ruleX": "Regel: {0}", "log": "Logg", - "function": "Funktion", "removedEmptyLine": "Ta bort tom rad", "removedEmptyLineAtTop": "Ta bort tom rad överst", "removedEmptyLineAtBottom": "Ta bort tom rad underst", @@ -1752,13 +1817,13 @@ "removeLineBreaksPixelWidth": "Slå ihop undertexter som får plats på en rad (pixelbredd)", "fixUppercaseIInsideLowercaseWords": "Åtgärda versalt 'i' inuti gemena ord (OCR-fel)", "fixDoubleApostrophes": "Åtgärda dubbla apostroftecken ('') till ett citationstecken (\")", + "fixMisreadQuotes": "Åtgärda apostrofer som feltolkats som dubbla citattecken (OCR, engelska)", "addPeriods": "Lägg till punkt efter rader där nästa rad börjar med versal bokstav", "startWithUppercaseLetterAfterParagraph": "Börja med versal bokstav efter stycke", "startWithUppercaseLetterAfterPeriodInsideParagraph": "Börja med versal bokstav efter punkt inuti stycke", "startWithUppercaseLetterAfterColon": "Börja med versal bokstav efter kolon/semikolon", "fixLowercaseIToUppercaseI": "Åtgärda ensamt gement 'i' till 'I' (engelska)", "fixCommonOcrErrors": "Åtgärda vanliga OCR-fel (med OCR-ersättningslista)", - "commonOcrErrorsFixed": "Vanliga OCR-fel åtgärdade (OcrReplaceList-fil använd): {0}", "removeSpaceBetweenNumber": "Ta bort mellanslag mellan siffror", "breakDialogsOnOneLine": "Dela dialoger på en rad", "removeDialogFirstInNonDialogs": "Ta bort startbindestreck på första raden för icke-dialoger", @@ -1775,18 +1840,17 @@ "fixCommasExample": ",, -> ,", "removeLineBreaksExample": "Foo
bar! -> Foo bar!", "fixDoubleApostrophesExample": "''Har dubbla enkla citationstecken'' -> \"Har enkelt dubbelt citationstecken\"", + "fixMisreadQuotesExample": "\"Hello' -> \"Hello\"", "addPeriodsExample": "Hej världen -> Hej världen.", "startWithUppercaseLetterAfterParagraphExample": "p1: Foobar! || p2: foobar! -> p1: Foobar! || p2: Foobar!", "startWithUppercaseLetterAfterPeriodInsideParagraphExample": "Hej där! hur mår du? -> Hej där! Hur mår du?", "startWithUppercaseLetterAfterColonExample": "Talare: hej världen! -> Talare: Hej världen!", "removeHyphensSingleLineExample": "- Foobar. -> Foobar.", "fix3PlusLinesExample": "Foo
bar
baz! -> Foo bar baz!", - "xMissingQuotesAdded": "Saknade citationstecken tillagda: {0}", "fix3PlusLine": "Åtgärda undertext med fler än två rader", "fix3PlusLines": "Åtgärda undertexter med fler än två rader", "analysing": "Analyserar...", "nothingToFix": "Inget att åtgärda :)", - "fixesFoundX": "Åtgärder hittade: {0}", "xFixesApplied": "Åtgärder tillämpade: {0}", "nothingFixableBut": "Inget kunde åtgärdas automatiskt. Undertexten innehåller fel - se logg för detaljer", "xFixedBut": "{0} problem åtgärdade men undertexten innehåller fortfarande fel - se logg för detaljer", @@ -1794,7 +1858,6 @@ "fixFirstLetterToUppercaseAfterParagraph": "Åtgärda första bokstaven till versal efter stycke", "mergeShortLine": "Slå ihop kort rad (enskild mening)", "mergeShortLineAll": "Slå ihop kort rad (alla utom dialoger)", - "unbreakShortLinePixelWidth": "Slå ihop kort rad (pixelbredd)", "breakLongLine": "Bryt lång rad", "fixLongDisplayTime": "Åtgärda lång visningstid", "fixInvalidItalicTag": "Åtgärda ogiltig kursivtagg", @@ -1817,7 +1880,6 @@ "fixUppercaseIInsideLowercaseWord": "Åtgärda versalt 'i' inuti gement ord", "fixMissingPeriodAtEndOfLine": "Lägg till saknad punkt i slutet av rad", "refreshFixes": "Uppdatera tillgängliga åtgärder", - "applyFixes": "Tillämpa valda åtgärder & stäng", "autoBreak": "Auto &br", "unbreak": "&Slå ihop", "fixDoubleDash": "Åtgärda '--' -> '...'", @@ -1836,8 +1898,6 @@ "fixSpaceBetweenNumbersExample": "1 100 -> 1100", "fixDialogsOneLineExample": "Hej John! - Hej Ida! -> Hej John!
- Hej Ida!", "removeDialogFirstInNonDialogsExample": "- Hur mår du? -> Hur mår du?", - "selectDefault": "Välj standard", - "setDefault": "Sätt aktuella åtgärder som standard", "fixContinuationStyleX": "Åtgärda fortsättningsstil: {0}", "fixUnnecessaryLeadingDots": "Ta bort onödiga inledande punkter", "fixCommonErrorsProfiles": "Profiler för åtgärda vanliga fel", @@ -1845,7 +1905,6 @@ "searchRulesDotDotDot": "Sök regler...", "goToApplyFixes": "Gå till tillämpa åtgärder", "backToFixList": "Tillbaka till åtgärdslista", - "applyFixesAndClose": "Tillämpa åtgärder & stäng", "fixCommonOcrErrorsStep2": "Åtgärda vanliga fel, steg 2 (tillämpa åtgärder)", "fixCommonOcrErrorsStep2FixesFoundX": "Åtgärda vanliga fel, steg 2 - Åtgärder hittade: {0}", "action": "Åtgärd", @@ -1859,18 +1918,11 @@ "adjustDurations": { "title": "Justera varaktigheter", "adjustVia": "Justera via", - "seconds": "Sekunder", - "percent": "Procent", "recalculate": "Räkna om", "addSeconds": "Lägg till sekunder", - "setAsPercent": "Sätt som procent av varaktighet", "note": "Obs: Visningstid kommer inte att överlappa nästa texts starttid", "fixed": "Fast", - "milliseconds": "Millisekunder", "extendOnly": "Endast förläng", - "enforceDurationLimits": "Tvinga minimi- och maximivaraktighet", - "checkShotChanges": "Förläng inte förbi scenbyten", - "batchCheckShotChanges": "Respektera scenbyten (om tillgängligt)", "recalculateRequiresOcrNote": "Omberäkning är inte tillgänglig — en eller flera undertexter saknar OCR-text. Kör OCR först." }, "applyDurationLimits": { @@ -1878,6 +1930,8 @@ "fixMinDurationMs": "Åtgärda minimivaraktighet (ms)", "doNotGoPastShotChange": "Gå inte förbi scenbyte", "fixMaxDurationMs": "Åtgärda maximivaraktighet (ms)", + "fixMinDurationFrames": "Åtgärda minsta varaktighet (bildrutor)", + "fixMaxDurationFrames": "Åtgärda största varaktighet (bildrutor)", "minimumDurationMilliseconds": "Minsta varaktighet (millisekunder):", "maximumDurationMilliseconds": "Största varaktighet (millisekunder):", "maxDurationShouldBeHigherThanMinDuration": "Maximivaraktighet bör vara högre än minimivaraktighet", @@ -1897,12 +1951,8 @@ }, "beautifyTimeCodes": { "title": "Försköna tidskoder", - "beautifySettings": "Förskönningsinställningar", "snapToFrames": "Fäst till bildrutor", - "frameGap": "Bildrutsmellanrum", "minDuration": "Min varaktighet (ms)", - "shotChangeThreshold": "Scenbyteströskel (ms)", - "shotChangeOffset": "Scenbytsförskjutning (bilder)", "original": "Original", "beautified": "Förskönad", "subtitlesCount": "Undertexter", @@ -1931,13 +1981,80 @@ "timeCodesExtractFailed": "Kunde inte läsa tidskoder från videon", "timeCodesHint": "Fäst start-/slutpunkter vid videons verkliga bildrutetider i stället för att anta en konstant bildfrekvens. Kräver att hela videon läses en gång; resultatet cachas." }, + "improveTimeCodes": { + "title": "Förbättra tidskoder (forced alignment)", + "aligner": "Aligner", + "alignerHint": "Modellen som lyssnar efter varje rad i ljudet. De bästa valen för undertextens språk listas först.", + "maxShift": "Max förskjutning (sekunder)", + "maxShiftHint": "Hur mycket undertexten får vara ur synk. En rad vars start skulle flyttas längre lämnas som den är och flaggas. Oavsett värde flyttas inte en enskild rad som vill flytta mer än en halv sekund längre än raderna runt den i blindo: den följer sina grannar om alla flyttades, och erbjuds annars avmarkerad så att du kan kontrollera den.", + "adjustStartTimes": "Justera starttider", + "adjustEndTimes": "Justera sluttider", + "isolateSpeech": "Isolera tal först (långsamt)", + "isolateSpeechHint": "Tar bort musik och ljudeffekter före justeringen, så att alignern bara hör dialogen. Hjälper för rader som sägs över hög musik eller action, men tar ungefär lika lång tid som själva ljudet med en GPU - och många gånger längre utan.", + "isolatingSpeech": "Isolerar tal... (detta tar en stund)", + "isolateSpeechFailed": "Kunde inte isolera talet - justerades mot originalljudet i stället.", + "checkWithSpeechToText": "Kontrollera med tal till text", + "checkWithSpeechToTextHint": "Transkriberar även ljudet och kontrollerar varje flytt mot var orden faktiskt hördes. Stora flyttar som talet bekräftar markeras åt dig, och flyttar bort från där en rad hörs lämnas avmarkerade så att du kan kontrollera dem. Visar också hur stor del av varje rad som hördes - en låg andel betyder att texten skiljer sig från det som sägs. En undertext som är mer ur synk än max förskjutning - en konstant förskjutning, en drift från felaktig bildfrekvens eller hopp där scener klippts bort - synkas först efter de hörda orden. Använder Crisp ASR Parakeet, vilket tar några minuter för en långfilm med en GPU.", + "checkWithSpeechToTextNotAvailableX": "Kontroll med tal till text är inte tillgänglig för {0}", + "transcribingToCheck": "Transkriberar för att kontrollera justeringen...", + "speechToTextFailed": "Tal till text misslyckades - tidskoderna kontrollerades inte mot det.", + "speechToTextX": "Tal till text: {0}", + "speechToTextConfirmedXDisputedY": "Tal: {0} bekräftade, {1} ifrågasatta", + "hardlyHeardX": "Tal till text hörde bara {0}% av undertextens ord. Är undertexten på det språk som talas? Alignern kan inte placera en översättning.", + "heard": "Hört", + "heardHint": "Hur stor del av raden som tal till text hörde. En låg andel betyder att texten skiljer sig från det som sägs - där alignern mest sannolikt har fel.", + "statusConfirmedBySpeech": "Bekräftad av tal", + "statusDisputedBySpeech": "Hördes där den var", + "statusMovedWithSync": "Flyttad med synkningen", + "syncedFirstXY": "Synkad först: {0} s i början, {1} s i slutet", + "showSpeechOnly": "Visa endast tal", + "showSpeechOnlyHint": "Rita den justerade vågformen från ljudet med musik och ljudeffekter borttagna; den ursprungliga vågformen förblir som den är, för jämförelse. Tillgängligt när talet har isolerats - här, eller med \"Visa endast tal\" i huvudfönstrets vågform.", + "align": "Justera", + "original": "Original", + "aligned": "Justerad", + "intro": "Välj en aligner och tryck på \"Justera\" för att lyssna efter varje rad i ljudet och skärpa dess tidskoder.", + "extractingAudio": "Extraherar ljud...", + "aligningBatchXOfY": "Justerar... batch {0} av {1}", + "extractAudioFailed": "Kunde inte extrahera ljudet med ffmpeg.", + "modelWillBeDownloaded": "{0} kommer att laddas ner", + "summaryXRetimedYKeptZSkipped": "Omtidsatta: {0} · Behållna: {1} · Inget tal: {2}", + "meanShiftX": "Genomsnittlig förskjutning: {0} ms", + "toCheckX": "Att kontrollera: {0}", + "largeMovesToCheckX": "{0} rad(er) skulle flyttas långt på egen hand och lämnas avmarkerade - lyssna på dem (F5 spelar den justerade raden, Shift+F5 originalet) och markera de som är rätt.", + "linesToCheckX": "{0} rad(er) lämnas avmarkerade för kontroll - lyssna på dem (F5 spelar den justerade raden, Shift+F5 originalet) och markera de som är rätt.", + "changeXOfY": "Ändring {0} av {1}", + "previousChange": "Föregående ändring", + "nextChange": "Nästa ändring", + "startShift": "Start", + "endShift": "Slut", + "statusRetimed": "Omtidsatt", + "statusMovedWithNeighbours": "Flyttad med sina grannar", + "statusLargeMoveUnconfirmed": "Stor flytt - lyssna, markera för att tillämpa", + "statusUnchanged": "Redan på plats", + "statusNoSpeech": "Inget tal", + "statusShiftTooLarge": "Behållen - för stor förskjutning", + "statusFailed": "Behållen - alignern misslyckades", + "statusAdjustedByHand": "Justerad manuellt", + "alignedWaveformHint": "Dra en rad för att flytta den, eller dra dess kanter för att ändra start eller slut.", + "playLine": "Spela rad", + "undoAdjustment": "Ångra justering", + "playPause": "Spela / pausa", + "playAligned": "Justerad", + "playAlignedHint": "Spela den markerade raden med dess justerade tidskoder", + "apply": "Tillämpa", + "applyHint": "Avmarkera en rad för att behålla dess ursprungliga tidskoder. Dubbelklicka på en rad för att spela den. Rader kan också flyttas eller ändras i storlek i den justerade vågformen." + }, "beautifyTimeCodesProfile": { "title": "Redigera profil för försköning av tidskoder", "loadPreset": "Läs in förinställning", "presetDefault": "Standard", "presetNetflix": "Netflix", "presetSdi": "SDI", - "general": "Allmänt", + "saveAsProfile": "Spara som profil...", + "deleteProfile": "Ta bort profil", + "profileName": "Profilnamn", + "overwriteProfileX": "En profil med namnet \"{0}\" finns redan. Skriva över den?", + "deleteProfileX": "Ta bort profilen \"{0}\"?", "gap": "Mellanrum", "gapSuffix": "bildrutor (uppdaterar även mellanrumsfälten skilda från noll nedan)", "milliseconds": "ms", @@ -1966,9 +2083,12 @@ "hintChaining": "Hur undertexter som nästan rör vid varandra hanteras — förläng dem tillsammans eller skjut isär dem, beroende på om ett scenbyte ligger emellan." }, "bridgeGaps": { - "title": "Överbrygga mellanrum", "bridgeGapsSmallerThan": "Överbrygga mellanrum mindre än (ms)", "minGap": "Minimimellanrum (ms)", + "bridgeGapsSmallerThanFrames": "Överbrygga mellanrum mindre än (bildrutor)", + "minGapFrames": "Minsta mellanrum (bildrutor)", + "calculateBridgeGapsSmallerThanDotDotDot": "Beräkna gräns för överbryggning av mellanrum från en bildfrekvens...", + "useXMsAsBridgeGapsSmallerThan": "Överbrygga mellanrum mindre än \"{0}\" millisekunder?", "numberOfSmallGapsBridgedX": "Antal små mellanrum överbryggade: {0}", "percentFoPrevious": "Mellanrum för föregående (%)", "gapChange": "Mellanrumsändring" @@ -1980,8 +2100,6 @@ "sortBy": { "title": "Sortera undertexter", "sortOrder": "Sorteringsordning", - "sortByNumber": "Nummer", - "sortByStartTime": "Starttid", "sortByEndTime": "Sluttid" }, "batchConvert": { @@ -2004,6 +2122,7 @@ "addColor": "Lägg till färg", "deleteLinesWithSpecificActorsOrStyles": "Ta bort rader med skådespelare eller stilar (separera flera med komma)", "useSourceStylesIfPossible": "Använd källstilar om möjligt", + "keepSourceEmbeddedFonts": "Behåll källans inbäddade typsnitt (sidfot)", "editStyles": "Redigera stilar", "editProperties": "Redigera egenskaper", "editAttachments": "Redigera bilagor", @@ -2024,7 +2143,6 @@ "assaChangeStyleFromStyle": "Ändra stil från", "assaChangeStyleToStyle": "till", "assaChangeStyleImportStyle": "Importera stil...", - "assaChangeStyleImportedX": "Importerad: {0}", "assaChangeStyleTrimUnusedStyles": "Trimma oanvända stilar", "assaChangeStylePropertiesTitle": "Ändra stilegenskaper", "assaChangeStylePropertiesInfo": "Ändrar de valda fälten i alla stilar i filen, utan att röra stilarna i övrigt.", @@ -2036,13 +2154,52 @@ "convertColorsToDialogReBreakLines": "Radbryt om", "snapTimeCodesToFramesInfo": "Avrundar varje start- och sluttid till närmaste bildruta. Bildfrekvensen läses från en videofil med samma namn som undertextfilen, om inte en fast bildfrekvens väljs.", "addFolderDotDotDot": "Lägg till mapp...", + "addFolderRecursiveDotDotDot": "Lägg till mapp rekursivt...", "selectFolderToConvert": "Välj mapp med filer att konvertera", "includeSubfolders": "Inkludera undermappar när en mapp läggs till", + "videoFilesWhenAddingFolder": "Videofiler (mkv, mp4, ts...) när en mapp läggs till", + "videoFilesAsk": "Fråga", + "videoFilesInclude": "Lägg till deras undertextspår", + "videoFilesSkip": "Hoppa över", + "folderContainsXVideoFiles": "Mappen innehåller {0} videofil(er) (mkv, mp4, ts...).\n\nLägga till deras inbäddade undertextspår också, eller bara undertextfilerna?", + "addVideoFiles": "Lägg till videofiler", + "skipVideoFiles": "Endast undertextfiler", + "doNotAskAgainVideoFiles": "Fråga inte igen (ändra i batchkonverteringsinställningarna)", + "settingsSectionOutput": "Utdata", + "settingsSectionAddingFiles": "Lägga till filer", + "settingsSectionImageBasedSubtitles": "Bildbaserade undertexter (OCR)", + "settingsSectionOther": "Övrigt", "keepSourceFileTimestamp": "Behåll källfilens datum/tid på utdatafilerna", - "scanningFolderX": "Söker igenom {0}..." + "preventSleepWhileConverting": "Förhindra att datorn går i viloläge under konvertering", + "scanningFolderX": "Söker igenom {0}...", + "transportStreamSettings": "Inställningar för Transport Stream", + "transportStreamSettingsDotDotDot": "Inställningar för Transport Stream...", + "transportStreamSettingsInfo": "Gäller undertextspår som extraheras från Transport Stream-filer (.ts/.m2ts). Position och videostorlek påverkar endast DVB-bildspår som exporteras till ett bildbaserat format.", + "transportStreamOverrideXPosition": "Åsidosätt ursprunglig X-position", + "transportStreamOverrideYPosition": "Åsidosätt ursprunglig Y-position", + "transportStreamOverrideVideoSize": "Åsidosätt ursprunglig videostorlek", + "transportStreamBottomMargin": "Nedre marginal", + "transportStreamFileNameEnding": "Filnamnsslut", + "transportStreamFileNameEndingInfo": "Läggs till före filändelsen för varje extraherat spår. Lämna tomt för att använda det vanliga språksuffixet.", + "transportStreamOnlyTeletext": "Endast text-tv", + "transportStreamGetSizeFromVideo": "Hämta storlek från video...", + "twoLetterLanguageCodeUppercase": "Språkkod med två bokstäver (versaler)", + "threeLetterLanguageCodeUppercase": "Språkkod med tre bokstäver (versaler)", + "formatContainsDotDotDot": "Format innehåller...", + "extensionIsDotDotDot": "Filändelse är...", + "statusIsError": "Status är fel", + "forcedTracksOnly": "Endast tvingade spår", + "xConvertedYFailedInZ": "{0:#,###,##0} konverterade, {1:#,###,##0} misslyckades på {2}", + "xNotProcessed": "{0:#,###,##0} ej bearbetade", + "removeUnicodeControlCharactersTitle": "Ta bort Unicode-styrtecken", + "removeUnicodeControlCharactersInfo": "Tar bort de osynliga Unicode-styrtecknen för textriktning (LRM, RLM, LRE, RLE, PDF, LRO och RLO) från texten, och ersätter hårda mellanslag med vanliga mellanslag.", + "preset": "Förinställning", + "savePresetDotDotDot": "Spara förinställning...", + "deletePreset": "Ta bort förinställning", + "presetName": "Namn på förinställning", + "deletePresetX": "Ta bort förinställningen \"{0}\"?" }, "changeCasing": { - "title": "Ändra skiftläge", "fixNames": "Åtgärda namn", "namesX": "Namn: {0:#,##0}", "hitsX": "Träffar: {0:#,##0}", @@ -2053,9 +2210,7 @@ "allUppercase": "Alla versaler", "allLowercase": "Alla gemener" }, - "changeFormatting": { - "title": "Ändra formatering" - }, + "changeFormatting": {}, "convertActors": { "title": "Konvertera skådespelare", "convertActorFrom": "Konvertera skådespelare från", @@ -2084,18 +2239,12 @@ "outputFormatAssa": "Advanced Sub Station Alpha (.ass)", "style1": "Stil 1", "style2": "Stil 2", - "outlineWidth": "Konturbredd", - "shadowWidth": "Skuggans bredd", - "alignTop": "Överst", - "alignBottom": "Botten", - "merge": "_Slå ihop", - "pleaseLoadTwoSubtitles": "Vänligen ladda två undertexter" + "merge": "_Slå ihop" }, "splitSubtitle": { "title": "Dela undertext", "numberOfEqualParts": "Antal lika delar", "saveSplitParts": "_Spara delade delar", - "subtitleSplitIntoXParts": "Undertext delad i {0} delar.", "xPartsSavedInFormatYToFolder": "{0} delar sparade i format {1} till mapp:" }, "splitBreakLongLines": { @@ -2129,18 +2278,17 @@ "columnMerge": "Slå samman", "columnFirst": "Första raden", "columnSecond": "Andra raden", - "columnMerged": "Sammanslagen", - "selectAll": "Markera alla", - "inverseSelection": "Omvänd markering" + "columnMerged": "Sammanslagen" }, "mergeLinesWithSameText": { - "title": "Slå ihop rader med samma text", "maxMsBetweenLines": "Max millisekunder mellan rader", - "includeIncrementingLines": "Inkludera rader med inkrementerande text" + "maxFramesBetweenLines": "Max bildrutor mellan rader", + "includeIncrementingLines": "Inkludera rader med inkrementerande text", + "includeRollUpCaptions": "Inkludera rullande (roll-up) undertexter" }, "mergeLinesWithSameTimeCodes": { - "title": "Slå ihop rader med samma tidskoder", "maxMsDifference": "Max skillnad (millisekunder)", + "maxFramesDifference": "Max skillnad (bildrutor)", "makeDialog": "Gör dialoger" }, "netflixCheckAndFix": { @@ -2163,7 +2311,6 @@ "shotChanges": "Scenbyten", "startNumberSpellOut": "Startnummer skriv ut", "textforHiUseBrackets": "Text för HI, använd hakparenteser", - "frameRate": "Bildfrekvens", "twoFrameGrap": "Tvåbildsmellanrum", "whiteSpace": "Blanksteg", "childrensProgram": "Barnprogram", @@ -2201,7 +2348,6 @@ "italicsFixed": "Åtgärdade kursiveringar", "italicsNotAllowed": "Kursiveringar inte tillåtna", "bridgeGapsXToYFrames": "3-{0} bilders mellanrum => 2 bilders mellanrum", - "startNumberSpellOutComment": "När en siffra inleder en mening ska den alltid skrivas ut", "maximumXCharactersPerSecond": "Maximalt {0} tecken per sekund" }, "imageBasedEdit": { @@ -2210,10 +2356,8 @@ "resizeImagesDotDotDot": "Ändra storlek på bilder...", "adjustBrightnessDotDotDot": "Justera ljusstyrka...", "adjustAlphaDotDotDot": "Justera alfa...", - "centerHorizontally": "Centrera horisontellt", "cropImages": "Beskär bilder", "importTimeCodes": "Importera tidskoder...", - "setTextForSubtitle": "Sätt text för undertext", "setText": "Sätt text", "screenWidth": "Skärmbredd", "screenHeight": "Skärmhöjd", @@ -2244,15 +2388,31 @@ "letterboxCustom": "Anpassad balkhöjd", "barHeightPx": "Balkhöjd (px)", "titleSafePercent": "Titelsäker yta (%)", + "moveCaptions": "Flytta undertexter", + "moveCaptionsDotDotDot": "Flytta undertexter...", + "moveCaptionsTo": "Flytta undertexter", + "moveCaptionsIntoBars": "In i letterbox-fälten (utanför bilden)", + "moveCaptionsIntoPicture": "Inuti bilden (ut ur fälten)", + "moveCaptionsOffset": "Avstånd från kanten (px)", + "letterboxBarHeightXPxYOfZCaptionsWillMove": "Fälthöjd: {0} px - {1} av {2} undertexter kommer att flyttas", + "removeFadeInOut": "Ta bort intoning/uttoning", + "removeFadeInOutXLinesRemoved": "{0} toningsrader sammanslagna med de rader de hör till", + "removeFadeInOutNothingFound": "Inga intonings-/uttoningsrader hittades", "positionSummary": "{0}×{1} - {2} undertexter - balkhöjd: {3} px", "xInActivePicture": "{0} i bilden", "xInTopBar": "{0} i övre balken", "xInBottomBar": "{0} i nedre balken", "noImageSubtitlesLoaded": "Inga bildbaserade undertexter inlästa", - "imageBasedFormatNotSupported": "Bildbaserat undertextformat hittades inte/stöds inte." + "imageBasedFormatNotSupported": "Bildbaserat undertextformat hittades inte/stöds inte.", + "frameRateDeclaredInFile": "Bildfrekvens angiven i Blu-ray sup-filen", + "frameRateDetectedFromTimeCodesX": "Bildfrekvens identifierad från Blu-ray sup-tidskoderna (filen anger {0})", + "frameRateFromVideo": "Videons bildfrekvens", + "frameRateCurrent": "Aktuell bildfrekvens", + "frameRateSetManually": "Bildfrekvens angiven manuellt", + "frameRateUsageInfo": "Export till Blu-ray sup, BDN XML, DOST och FCP skrivs med den här bildfrekvensen. Den ändrar inga tider - använd \"Ändra bildfrekvens\" för det.", + "useVideoFrameRateXInsteadOfY": "Videons bildfrekvens är {0}, men undertexten är inställd på {1}.\n\nAnvända videons bildfrekvens ({0})?" }, "removeTextForHearingImpaired": { - "title": "Ta bort text för hörselskadade", "interjections": "Interjektioner", "skipIfStartWith": "Hoppa över om börjar med", "removeTextBetween": "Ta bort text mellan", @@ -2274,7 +2434,6 @@ "aiReview": { "title": "AI-granskning", "review": "Granska", - "stop": "Stoppa", "editPrompt": "Redigera prompt...", "editPromptTitle": "Redigera granskningsprompt", "promptInfo": "Instruktioner som skickas till modellen - {language} ersätts med undertextens språk.", @@ -2285,7 +2444,6 @@ "reviewingLineXOfY": "Granskar rad {0} av {1}...", "reviewDone": "Granskning klar - {0} förslag i {1} rader", "noIssuesFound": "Inga problem hittades", - "categoryAll": "Alla", "categorySpelling": "Stavning", "categoryGrammar": "Grammatik", "categoryPunctuation": "Interpunktion", @@ -2297,8 +2455,23 @@ "largeChangeWarning": "Stor ändring - ser ut som en omskrivning, granska noga", "mismatchWarning": "Liknar inte originalet - kan höra till en annan rad", "engineError": "AI-motorn kunde inte nås: {0}", - "setupHint": "Välj först en motor och en modell i Verktyg -> AI-granskning.", - "playCurrentLineHint": "Spela upp den markerade raden i videospelaren och pausa vid dess slut (dubbelklicka också på en rad)" + "playCurrentLineHint": "Spela upp den markerade raden i videospelaren och pausa vid dess slut (dubbelklicka också på en rad)", + "editAfterHint": "Klicka på texten i den markerade raden för att redigera åtgärden innan den tillämpas (Enter sparar, Escape avbryter)", + "template": "Mall", + "templatePlaceholder": "Välj en mall för att ersätta prompten", + "templateStandard": "Standardkorrekturläsning", + "templateSpeechToText": "Uppstädning av tal till text", + "templateMachineTranslation": "Rättning av maskinöversättning", + "templateSdh": "SDH / hörselskadade", + "referenceContext": "Referenskontext", + "referenceContextInfo": "Valfritt. Namn, rollfigurer, ordlista, synopsis eller stilanteckningar - modellen behandlar detta som auktoritet för namn och termer. Skickas med varje batch.", + "referenceContextPlaceholder": "Namn: Kaito, moster Mei, dr Hollis\nTermer: Shinkansen, varpkärna\nSynopsis: Kaito reser till Tokyo för att hitta sin försvunna far.", + "generateContext": "Generera med AI", + "generateContextHint": "Låt den valda AI-motorn läsa undertexten och ta fram namn, termer och en synopsis - kontrollera resultatet före användning", + "generatingContextPartXOfY": "Läser del {0} av {1}...", + "generatingContextSynopsis": "Skriver synopsis...", + "replaceContextQuestion": "Ersätta den aktuella referenskontexten?", + "noContextGenerated": "AI-motorn returnerade ingen användbar referensinformation." }, "aiAssistant": { "title": "AI-assistent", @@ -2359,7 +2532,21 @@ "correctWordsX": "Korrekta ord: {0}", "namesX": "Namn: {0}", "addedToDictionaryX": "Tillagda i ordbok: {0}", - "doNotShowThisAgain": "Visa inte det här meddelandet igen" + "doNotShowThisAgain": "Visa inte det här meddelandet igen", + "editUseAlwaysList": "Redigera listan \"Använd alltid\"...", + "useAlwaysListTitle": "Rättstavning - listan \"Använd alltid\"", + "useAlwaysListHeader": "Listan \"Använd alltid\"", + "useAlwaysListDescription": "Ord som rättstavningen ersätter automatiskt, sparade av \"Ändra alla\" och \"Använd alltid\". Ett par används bara när ordet är felstavat.", + "useAlwaysWord": "Felstavat ord", + "xPairs": "{0:#,##0} par", + "xUnusedPairs": "{0:#,##0} aldrig använda", + "useAlwaysUnusedHint": "Aldrig använt - det här ordet är rättstavat (eller är ett namn eller ett användarord), så rättstavningen flaggar det aldrig", + "removeUnused": "Ta bort oanvända", + "useAlwaysEmpty": "Inga \"Använd alltid\"-par för det här språket ännu", + "useAlwaysEmptyHint": "Par läggs till när du klickar på \"Ändra alla\" eller \"Använd alltid\" i rättstavningen.", + "useAlwaysNoMatch": "Inga par matchar sökningen", + "useAlwaysSameWord": "Ersättningen måste skilja sig från ordet.", + "useAlwaysRememberOff": "\"Kom ihåg listan 'Använd alltid'\" är avstängt i Inställningar, så rättstavningen varken använder eller sparar den här listan." }, "video": { "chapters": { @@ -2390,7 +2577,6 @@ "scaleTimesDescription": "Skala om varje kapiteltid från en bildfrekvens till en annan.", "fromFrameRate": "Från", "toFrameRate": "Till", - "apply": "Tillämpa", "writeToVideo": "Skriv till video...", "writeToVideoTitle": "Skriv kapitel till video", "writeToVideoDescription": "Kapitlen skrivs in i en kopia av videofilen. Inget kodas om, så det går snabbt och utan kvalitetsförlust.", @@ -2400,7 +2586,6 @@ "writtenToX": "Kapitel skrivna till {0}", "unableToWriteChapters": "Det gick inte att skriva kapitel till videofilen.", "chaptersSavedToX": "Kapitel sparade till {0}", - "noVideoLoaded": "Ingen video inläst", "emptyListCallToAction": "Inga kapitel ännu - lägg till ett vid videopositionen, eller importera dem från en video eller fil." }, "goToVideoPosition": "Gå till videoposition", @@ -2412,30 +2597,88 @@ "openSecondarySubtitleOnVideoPlayerDotDotDot": "Sekundär undertext på videospelare, öppna...", "openSecondarySubtitleOnVideoPlayer": "Sekundär undertext (på videospelare)", "removeSecondarySubtitleOnVideoPlayer": "Sekundär undertext på videospelare, ta bort", + "editSecondarySubtitleSettings": "Redigera inställningar för sekundär undertext", + "editSecondarySubtitleSettingsDotDotDot": "Redigera inställningar för sekundär undertext...", + "secondarySubtitleRememberSettings": "Kom ihåg dessa inställningar", + "secondarySubtitleDoNotShowAgain": "Visa inte den här dialogrutan igen", + "secondarySubtitleJustifyLines": "Justera rader", "openRecentVideo": "Öppna senaste video", "clearRecentVideos": "Rensa senaste videor", "cutVideoTitle": "Klipp video", "cutVideoDotDotDot": "Klipp video...", + "remuxVideoTitle": "Remuxa video", + "remuxVideoDotDotDot": "Remuxa video...", + "remuxVideoInputVideo": "Indatavideo:", + "remuxVideoMultipleTracksRequiresMkv": "Flera ljud- eller undertextspår kräver MKV-containern. Utdataformatet har automatiskt ändrats till .mkv.", + "remuxVideoOutputFormat": "Utdataformat:", + "remuxVideoOutputFile": "Utdatafil:", + "remuxVideoRemuxing": "Remuxar video och ljud...", + "remuxVideoFinalizing": "Slutför utdatafilen (flyttar index till början)...", + "remuxVideoCompleted": "Videon har remuxats.", + "remuxVideoFailed": "Kunde inte remuxa videon.", + "remuxVideoPleaseSelectBoth": "Välj både video- och ljudfiler.", + "remuxVideoAssRequiresMkv": "ASS/SSA-undertexter kräver MKV-containern för att bevara all formatering och alla stilar.", + "remuxVideoSccRequiresMov": "Scenarist-undertexter (.scc) bäddas in som CEA-608 closed captions, vilket kräver MOV- eller MPG-containern. Utdataformatet har automatiskt ändrats till .mov.", + "remuxVideoSccNotWithWebM": "Scenarist closed captions (.scc) kräver MOV-containern, som inte kan innehålla VP9/AV1-videon i en .webm-fil. Ta bort .scc-filen eller konvertera den till ett annat undertextformat.", + "remuxVideoMpgMaxTwoCaptionTracks": "En .mpg-fil rymmer högst två closed caption-spår (CC1 och CC3) - ta bort undertextfiler så att en eller två återstår.", + "remuxVideoMccRequiresMpg": "MacCaption-undertexter (.mcc) bäddas in som CEA-608/708 closed captions i MPEG-2-videon, vilket kräver MPG-containern. Utdataformatet har automatiskt ändrats till .mpg.", + "remuxVideoMpgReencodeVideoX": "Closed captions i en .mpg-fil kräver MPEG-2-video, och videon är {0}. Koda om videon till MPEG-2? Det tar längre tid och sänker kvaliteten något.", + "remuxVideoFileHasNoAudioX": "\"{0}\" har inget ljudspår, så den kan inte användas som ljudkälla.", + "remuxVideoAddingClosedCaptions": "Lägger till closed captions...", + "remuxVideoSelectAudioTrack": "Välj ljudspår", + "remuxVideoSelectAudioTrackFor": "Välj ljudspår för {0}", + "remuxVideoSelectAudioTrackPrompt": "Filen '{0}' innehåller {1} ljudspår. Välj det ljudspår som ska remuxas:", + "remuxVideoVideoFile": "video", + "remuxVideoAudioFile": "ljud", + "remuxVideoAudioFiles": "Ljudfiler:", + "remuxVideoSubtitleFiles": "Undertextfiler (valfritt, mjuka undertexter):", + "remuxVideoSelectAudioTrackDotDotDot": "Välj ljudspår...", + "remuxVideoFilesX": "{0} filer", + "remuxVideoMixAudio": "Mixa ljud till ett spår", + "remuxVideoVolumePercent": "Volym %:", + "remuxVideoVolumeX": "Volym {0}%", + "remuxVideoFastStart": "Optimera för webbströmning (flytta index till början - långsammare)", "embedSubtitlesDotDotDot": "Lägg till/ta bort inbäddade undertexter...", "generateTimeCodes": "Generera tidskoder", "checkeredImage": "Rutig bild", - "saveVideoAsTitle": "Spara video som", "promptForFfmpegParamsAndGenerate": "Fråga efter ffmpeg-parametrar och generera", "cutVideoCutSegments": "Klipp segment", "cutVideoMergeSegments": "Slå ihop segment", "cutVideoSplitSegments": "Spara segment individuellt", - "cutVideoType": "Klipptyp", - "mpvRenderAuto": "Auto", - "mpvRenderNative": "Inbyggd", - "mpvRenderOpenGl": "OpenGL", - "mpvRenderSoftware": "Programvara (långsam)", "cutVideoAlsoCutSubtitle": "Klipp även undertexten", "cutVideoSubtitleFileGeneratedX": "Undertextfil genererad: {0}", + "cutVideoTransition": "Övergång mellan segment", + "cutVideoTransitionDurationSeconds": "Varaktighet (sekunder)", + "cutVideoFadeInSeconds": "Tona in från svart i början (sekunder)", + "cutVideoFadeOutSeconds": "Tona ut till svart i slutet (sekunder)", + "cutVideoPreviewTransition": "Förhandsvisa övergång", + "cutVideoPreviewTransitionTitle": "Förhandsvisning av övergång", + "cutVideoPreviewNeedsJoin": "Det finns ingen skarv att förhandsvisa - lägg till minst två segment att slå ihop, eller ett segment att klippa ut ur mitten.", + "cutVideoTransitionInfoX": "Varje övergång överlappar de delar den sammanfogar, så utdata blir {0} sekunder kortare per skarv. En klippt undertext tidsätts om för att matcha.", + "cutVideoTransitionFade": "Toning", + "cutVideoTransitionFadeBlack": "Toning via svart", + "cutVideoTransitionFadeWhite": "Toning via vitt", + "cutVideoTransitionFadeGrays": "Toning via grått", + "cutVideoTransitionDissolve": "Övertoning", + "cutVideoTransitionWipeLeft": "Torka vänster", + "cutVideoTransitionWipeRight": "Torka höger", + "cutVideoTransitionWipeUp": "Torka uppåt", + "cutVideoTransitionWipeDown": "Torka nedåt", + "cutVideoTransitionSlideLeft": "Glid vänster", + "cutVideoTransitionSlideRight": "Glid höger", + "cutVideoTransitionSlideUp": "Glid uppåt", + "cutVideoTransitionSlideDown": "Glid nedåt", + "cutVideoTransitionSmoothLeft": "Mjukt vänster", + "cutVideoTransitionSmoothRight": "Mjukt höger", + "cutVideoTransitionCircleOpen": "Cirkel öppna", + "cutVideoTransitionCircleClose": "Cirkel stäng", + "cutVideoTransitionRadial": "Radiell", + "cutVideoTransitionPixelize": "Pixla", + "cutVideoTransitionBlur": "Oskärpa", "importCurrentSubtitle": "Importera aktuell undertext", "addRemoveEmbeddedSubtitlesTitle": "Lägg till/ta bort inbäddade undertexter", "addRemoveEmbeddedSubtitlesMp4Title": "Lägg till/ta bort inbäddade undertexter (MP4)", "addCurrentSubtitle": "Lägg till aktuell undertext", - "embeddedTrackColumnNew": "Ny", "embeddedTrackNoSubtitlesFoundTitle": "Inga undertexter hittades", "embeddedTrackNoSubtitlesFoundMessage": "Den valda undertextfilen innehåller inga undertexter.", "embeddedTrackNoTracksTitle": "Inga spår tillagda", @@ -2447,7 +2690,7 @@ "embeddedTrackGeneratingVideoXY": "Genererar video... {0}% {1}", "embeddedTrackGeneratingVideo": "Genererar video...", "mp4FilesFilter": "MP4-filer", - "titleOrLanguage": "Titel/språk", + "embeddedTracksPickVideoHint": "Välj en videofil, eller släpp en här, för att se dess undertextspår.", "viewMatroskaTrackX": "Visa Matroska-spår - {0}", "resolutionSeparator": "x", "openFromUrlTitle": "Öppna videofil från URL", @@ -2457,9 +2700,7 @@ "openFromUrlDownloadAndOpen": "Ladda ner och öppna", "openFromUrlDownloadAndOpenDescription": "Spara videon lokalt och öppna den sedan.", "openFromUrlDownloadAndOpenNote": "Krävs för tal-till-text och vågformsspikar.", - "openFromUrlMissing": "Ange först en videowebbadress.", "openFromUrlDownloadingTitle": "Laddar ner video", - "openFromUrlSaveAs": "Spara video som", "openFromUrlDownloadSubtitles": "Ladda också ner undertexter", "openFromUrlDownloadVideo": "Ladda ner video", "openFromUrlIncludeAutoGeneratedSubtitles": "Inkludera automatgenererade undertexter (originalspråk)", @@ -2496,56 +2737,38 @@ "reEncodeGeneratingDone": "Generering klar", "reEncodeGeneratedFilesX": "Genererade filer ({0}):", "reEncodeFfmpegParameters": "ffmpeg-parametrar", + "analyzingVideoX": "Analyserar video... {0}% {1}", + "analyzingVideoXofY": "Analyserar video {0}/{1}... {2}% {3}", + "backgroundImageFileNotSelected": "Ingen bakgrundsbildfil vald", + "backgroundImageFileDoesNotExistX": "Bakgrundsbildfilen finns inte: {0}", "burnIn": { "title": "Generera video med inbrända undertexter", - "infoAssaOff": "Obs: Advanced SubStation Alpha-stilning stöds.", - "infoAssaOn": "Obs: Advanced SubStation Alpha-stilning kommer att användas :)", - "xGeneratedWithBurnedInSubsInX": "\"{0}\" genererad med inbrända undertexter på {1}.", - "timeRemainingMinutes": "Återstående tid: {0} minuter", - "timeRemainingOneMinute": "Återstående tid: En minut", - "timeRemainingSeconds": "Återstående tid: {0} sekunder", - "timeRemainingAFewSeconds": "Återstående tid: Några sekunder", - "timeRemainingMinutesAndSeconds": "Återstående tid: {0} minuter och {1} sekunder", - "timeRemainingOneMinuteAndSeconds": "Återstående tid: En minut och {0} sekunder", - "targetFileName": "Målfilnamn: {0}", "targetFileSize": "Målfilstorlek (kräver 2-pass-kodning)", "fileSizeMb": "Filstorlek i MB", - "passX": "Pass {0}", - "encoding": "Kodning", "bitRate": "Bithastighet", "totalBitRateX": "Total bithastighet: {0}", "sampleRate": "Samplingsfrekvens", "audio": "Ljud", - "stereo": "Stereo", "preset": "Förinställning", + "tune": "Finjustering (tune)", "pixelFormat": "Pixelformat", "crf": "CRF", - "tuneFor": "Justera för", - "alignRight": "Justera höger", - "getStartPosition": "Hämta startposition", - "getEndPosition": "Hämta slutposition", "useSource": "Använd källa", - "useSourceFolder": "Använd källmapp", - "useSourceResolution": "Använd källupplösning", - "outputSettings": "Utmatningsfil/-mapp...", "fontSizeFactor": "Typsnittsstorleksfaktor", "boxType": "Ruttyp", - "fixRightToLeft": "Åtgärda höger-till-vänster", - "cut": "Klipp", "fromTime": "Från tid", "toTime": "Till tid", "audioEncoding": "Ljudkodning", "outputProperties": "Utmatningsegenskaper...", "videoFileSize": "Videofilstorlek", "oneBox": "En ruta", - "boxPerLine": "Ruta per rad", "logoInfo": "Välj en PNG-bild och dra den för att placera den på videon.", + "noSubtitlesGenerateAnyway": "Det finns inga undertextrader att bränna in.\n\nGenerera videon utan undertexter?", + "noSubtitlesInXItemsGenerateAnyway": "{0} batchobjekt saknar undertextrader att bränna in.\n\nGenerera de videorna utan undertexter?", "matchSourceVideoSize": "Matcha källvideons storlek" }, "videoTransparent": { - "title": "Generera transparent video med undertexter", - "infoAssaOff": "Obs: Advanced SubStation Alpha-stilning stöds.", - "infoAssaOn": "Obs: Advanced SubStation Alpha-stilning kommer att användas :)" + "title": "Generera transparent video med undertexter" }, "audioToText": { "title": "Tal till text", @@ -2553,6 +2776,21 @@ "translateToEnglish": "Översätt till engelska", "transcribing": "Transkriberar...", "transcribingXOfY": "Transkriberar {0} av {1}...", + "linesTranscribedXOfYFailedZ": "{0} av {1} rader transkriberades - {2} misslyckades eller innehöll inget tal.\n\nMisslyckade rader behåller sin nuvarande text. Tillämpa de transkriberade raderna, eller försök igen med de misslyckade (du kan ändra motor eller inställningar först)?", + "linesTranscribedXOfYCancelled": "Transkriberingen avbröts efter {0} av {1} rader.\n\nTillämpa de transkriberade raderna? Övriga rader behåller sin nuvarande text.", + "noLinesTranscribed": "Ingen av de {0} raderna kunde transkriberas.\nKontrollera verktygsloggen för detaljer.", + "applyTranscribedLines": "Tillämpa transkriberade", + "retryFailedLines": "Försök igen med misslyckade", + "isolateSpeech": "Isolera tal (långsamt)", + "isolateSpeechHint": "Tar bort musik och ljudeffekter före transkriberingen. Ger färre missade rader och bättre tider för ljud med hög musik, men tar ungefär lika lång tid som själva ljudet med en GPU - och många gånger längre utan.", + "isolatingSpeech": "Isolerar tal...", + "isolateSpeechFailed": "Kunde inte isolera talet - transkriberar originalljudet i stället.", + "detectSpeakers": "Identifiera talare", + "detectSpeakersHint": "Inleder varje rad med talaren, som \"(speaker 0)\", med NVIDIAs Sortformer-diarisering (upp till 8 talare). Fungerar bäst med motorer som delar rader där talaren byts, som Parakeet och Cohere.", + "detectSpeakersNeedsNewerCrispAsr": "\"Identifiera talare\" kräver Crisp ASR v{0} eller nyare, men v{1} är installerad.\n\nUppdatera Crisp ASR (kugghjulsknappen bredvid motorn) eller stäng av \"Identifiera talare\".", + "xNeedsNewerCrispAsr": "{0} kräver Crisp ASR v{1} eller nyare, men v{2} är installerad.\n\nLadda ner och installera uppdateringen nu?", + "vad": "VAD", + "vadHint": "Röstaktivitetsdetektering: endast talet transkriberas, uppdelat där talaren pausar.\nAuto: Silero, och endast för de motorer som behöver det (Cohere, Mega, Index-Echo).\nSilero, FireRedVAD, Whisper-VAD, WebRTC: alltid på, för alla Crisp ASR-motorer. FireRedVAD är mest exakt, Whisper-VAD ger färst och renast uppdelningar men är långsammast.\nIndex-Echo använder alltid Silero. --vad, --vad-model eller --chunk-seconds i de avancerade inställningarna har företräde.", "inputLanguage": "Inmatningsspråk", "advancedWhisperSettings": "Avancerade Whisper-inställningar", "downloadingSpeechToTextEngine": "Laddar ner tal-till-text-motor", @@ -2565,8 +2803,6 @@ "whisperXxlHighlightWord": "Markera ord", "selectModel": "Välj modell", "viewToolsLogFile": "Visa Whisper-loggfil", - "reDownloadX": "Ladda ner {0} igen", - "downloadX": "Ladda ned {0}", "updateXTitle": "Uppdatera {0}?", "updateXMessage": "En nyare version av {0} är tillgänglig.{1}{1}Ladda ner och installera uppdateringen nu?", "downloadingSpeechToTextModel": "Laddar ner tal-till-text-modell", @@ -2610,27 +2846,16 @@ "backendAndUpdateStatus": "Backend och uppdateringsstatus", "addCustomModelDotDotDot": "Lägg till anpassad modell...", "customModelHelp": "Du kan använda din egen modell: välj modellfilen (whisper.cpp ggml '.bin') eller modellmappen (faster-whisper-mapp med en 'model.bin' inuti). Den kopieras till modellmappen och läggs till i listan ovan.", - "cueBuilding": "Cue-byggnad (MLX Whisper)", - "cueRebuild": "Bygg om cues från ordtidsstämplar", - "cueMaxChars": "Max tecken per cue", - "cueMaxSeconds": "Max sekunder per cue", - "cueMaxCps": "Max tecken per sekund", - "vocabularyPrompt": "Vokabulärprompt (namn, platser, termer)", - "beamSize": "Beam-storlek", - "beamSizeNote": "0 avkodar snabbast. 5 ger bäst noggrannhet (som Faster Whisper) med ungefär 20-30 % mer tid. Värden över 5 hjälper sällan.", "addLanguageCodeToFileName": "Lägg till språkkod i filnamnet", "addLanguageCodeToFileNameHint": "Namnge genererade undertexter som \"video.sv.srt\" i stället för \"video.srt\"" }, "textToSpeech": { "title": "Text till tal", - "textToSpeechEngine": "Text-till-tal-motor", "reviewAudioSegments": "TTS - Granska ljudsegment", "reviewAudioSegmentsHistory": "TTS - Granska ljudhistorik", "stability": "Stabilitet", "similarity": "Likhet", "speakerBoost": "Talarboost", - "styleExaggeration": "Stilöverdrift", - "regenerateAudioSelectedLine": "Återskapa ljud för markerad rad", "generateSpeechFromText": "Generera tal från text", "testVoice": "Testa röst", "addAudioToVideoFile": "Lägg till ljud i videofil", @@ -2640,6 +2865,19 @@ "videoEncodingSettings": "TTS - Videokodningsinställningar", "elevenLabsSettings": "TTS - ElevenLabs-inställningar", "elevenLabsSettingsResetHint": "Återställ ElevenLabs-inställningar till standardvärden", + "provider": "Leverantör", + "openAiCompatibleSettings": "TTS - OpenAI-kompatibla inställningar", + "customServerUrl": "Anpassad server-URL", + "customServerUrlHint": "Fullständig talslutpunkt för en OpenAI-kompatibel server, används när leverantören är \"Anpassad\", t.ex. http://localhost:8880/v1/audio/speech", + "customModels": "Anpassade modeller", + "customModelsHint": "Kommaseparerade modellnamn som erbjuds när leverantören är \"Anpassad\"", + "customVoices": "Anpassade röster", + "customVoicesHint": "Kommaseparerade röstnamn, används av leverantören \"Anpassad\" och av OpenRouter-modeller som inte publicerar någon röstlista", + "instructions": "Instruktioner", + "instructionsHint": "Valfri talstil, t.ex. \"Tala med lugn, varm röst\". Skickas endast till modeller som stöder det (inte tts-1/tts-1-hd)", + "openAiSpeedHint": "Talhastighet från 0.25 till 4.0 (1.0 är normal). Alla modeller stöder det inte", + "outputFormat": "Utdataformat", + "outputFormatHint": "Ljudformat som begärs från servern. \"Auto\" begär MP3 och byter till PCM för modeller som endast returnerar PCM (t.ex. Gemini TTS)", "regenerateAudio": "Återskapa ljud", "autoContinuePlaying": "Fortsätt automatiskt att spela", "playLine": "Spela upp rad", @@ -2648,6 +2886,13 @@ "addingAudioToVideoFileDotDotDot": "Lägger till ljud i videofil...", "preparingMergeDotDotDot": "Förbereder sammanslagning...", "importVoiceDotDotDot": "Importera röst...", + "renameVoiceDotDotDot": "Byt namn på röst...", + "renameVoiceTitle": "Byt namn på röst", + "voiceXCouldNotBeRenamedX": "Rösten '{0}' kunde inte byta namn: {1}", + "deleteVoiceDotDotDot": "Ta bort röst...", + "deleteVoiceTitle": "Ta bort röst", + "deleteVoiceXQuestion": "Ta bort rösten '{0}'?\n\nReferensinspelningen och dess filer tas bort från disken.", + "voiceXCouldNotBeDeletedX": "Rösten '{0}' kunde inte tas bort: {1}", "voiceImportSuccessTitle": "Röst importerad", "voiceXImported": "Rösten '{0}' importerades", "voiceXCouldNotBeImported": "Rösten '{0}' kunde inte importeras - se loggen för detaljer", @@ -2657,6 +2902,49 @@ "dropAudioFileHereToImportVoice": "Släpp ljudfil här för att importera röst", "dropAudioFileHereHint": ".wav eller .mp3", "voiceCloneTranscriptTitle": "Ange transkription av ljudet (krävs för röstkloning)", + "voiceManagerTitle": "TTS - Rösthanterare", + "voiceManagerDotDotDot": "Rösthanterare...", + "voiceManagerMenuItem": "TTS-rösthanterare...", + "speakFromCurrentLineTitle": "Läs upp från aktuell rad", + "speakFromCurrentLineDotDotDot": "Läs upp från aktuell rad...", + "speakFromCurrentLineHint": "Läser upp raderna en efter en, med början på den aktuella raden. Tryck på Escape eller klicka på en annan rad för att stoppa.", + "stopSpeaking": "Sluta läsa upp", + "speakingLineX": "Läser upp rad {0}... (Escape för att stoppa)", + "playWithSpeechTitle": "Spela med tal", + "playWithSpeechDotDotDot": "Spela med tal...", + "playWithSpeechHint": "Spelar videon och läser upp varje rad när videon når den. Pausa och sök som vanligt; tryck på Escape för att stoppa.", + "lowerVideoVolumeWhileSpeaking": "Sänk videovolymen under uppläsning", + "pauseVideoWhenSpeechRunsLate": "Pausa videon när talet når in i nästa rad", + "waitingForSpeechLineX": "Genererar tal för rad {0}... (Escape för att stoppa)", + "voiceKindClone": "Klonad röst", + "voiceKindPreset": "Inbyggd", + "voiceKindModel": "Röstmodell", + "voiceKindOnline": "Online", + "transcript": "Transkription", + "transcriptRequiredHint": "Krävs - motorn läser den här transkriptionen av inspelningen (ref-text) vid kloning", + "transcriptOptionalHint": "Valfritt - en korrekt transkription av inspelningen förbättrar kloningen", + "saveTranscript": "Spara transkription", + "searchVoices": "Sök röster", + "loadingVoicesDotDotDot": "Läser in röster...", + "noVoicesForEngine": "Inga röster - importera en inspelning eller ladda ner ett röstpaket", + "noVoicesMatchFilter": "Inga röster matchar sökningen", + "copyVoiceTo": "Kopiera röst till", + "copyVoiceToDotDotDot": "Kopiera röst till motor...", + "copyingVoiceXToYDotDotDot": "Kopierar rösten '{0}' till {1}...", + "voiceXCopiedToY": "Rösten '{0}' kopierad till {1}", + "voiceXCouldNotBeCopiedToY": "Rösten '{0}' kunde inte kopieras till {1} - se loggen för detaljer", + "voiceXAlreadyExistsInYContinue": "En röst med namnet '{0}' finns redan i {1}.\n\nKopiera ändå?", + "openVoicesFolder": "Öppna röstmapp", + "downloadVoicePacksDotDotDot": "Ladda ner röstpaket...", + "downloadVoicePacksTitle": "TTS - Ladda ner röstpaket", + "voicePackIntro": "Röstpaket är samlingar av korta, rena inspelningar med transkriptioner, redo att klona från. Varje paket importeras till den valda motorn (röster som redan finns hoppas över) och innehåller en ATTRIBUTION.txt med licens och källor.", + "voicePackDetailsXVoicesYSizeZLicense": "{0} röster · {1} · {2}", + "installToEngine": "Installera i motor", + "downloadAndInstall": "Ladda ner och installera", + "selectAtLeastOneVoicePack": "Välj minst ett röstpaket", + "downloadingXDotDotDot": "Laddar ner {0}...", + "installingXDotDotDot": "Installerar {0}...", + "xVoicesInstalledYSkipped": "{0} röster installerade ({1} fanns redan)", "useSpeechToTextDotDotDot": "Använd tal-till-text...", "autoCastMenuItem": "Hitta röster i videon och klona...", "autoCastSpeakersTitle": "Röster hittade i videon", @@ -2675,7 +2963,7 @@ "cloneVoicePerLinePreparing": "Hämtar varje rads röst från videon...", "cloneVoicePerLineNeedsVideo": "För att klona varje rads röst krävs videon som undertexten hör till. Öppna videon och försök igen.", "cloneVoicePerLineNoClips": "Inget ljud kunde hämtas från videon, så det finns inget att klona från. Kontrollera att videon har ett ljudspår.", - "cloneVoicePerLineNeedsOriginalSubtitleX": "{0} kan bara klona en rad när den vet vad som sägs i videon vid den raden, vilket kommer från undertexten på originalspråket. Ingen originalundertext är inläst, så varje rad skulle läsas av den första importerade rösten i stället för av rösten i videon.\n\nÖppna originalundertexten (Arkiv > Öppna original...) bredvid översättningen och generera sedan igen.", + "cloneVoicePerLineTranscribeClipsXYZ": "{0} kan bara klona en rad när den vet vad som sägs i videon vid den raden. Det är inte känt för {1} av {2} rader - det kommer normalt från undertexten på originalspråket (Arkiv > Öppna original...).\n\nTranskribera de raderna från videon med tal till text nu?\n\nJa: välj motorn och språket som talas i videon, så transkriberas referensklippen innan talet genereras.\nNej: generera ändå - de raderna läses upp av en vanlig röst i stället för rösten i videon.", "voiceCloneConsentTitle": "Röstkloning - innan du börjar", "voiceCloneConsentHeader": "Du är på väg att klona en röst", "voiceCloneConsentIntro": "Kloning kopierar en verklig persons röst. Det omfattas av regler på de flesta håll, och inom EU av rättsliga skyldigheter som vilar på dig, inte på Subtitle Edit. Läs igenom detta en gång.", @@ -2692,10 +2980,29 @@ "advancedTtsSettings": "Avancerade TTS-inställningar", "advancedTtsAudioProcessing": "Ljudbehandling", "advancedTtsOutput": "Utdata", + "advancedTtsVoiceCloning": "Röstkloning", + "cleanCloneReferences": "Rensa referenser för röstkloning (Sidon)", + "cleanCloneReferencesDescription": "Tar bort musik, brus och efterklang från ljudet som en röst klonas från: klipp tagna från videon och inspelningar importerade i rösthanteraren. Använder talrestaureringsmodellen Sidon på audio.cpp (940 MB nedladdning). Den klonade rösten kopierar inte längre bakgrunden, men mycket tyst eller överlappande tal kan bli dovt.", + "cleaningCloneReferencesDotDotDot": "Rensar referenser för röstkloning...", + "downloadSidonTitle": "Ladda ner Sidon?", + "downloadSidonQuestionX": "Rensning av referenser för röstkloning kräver talrestaureringsmodellen Sidon ({0}).\n\nLadda ner den nu? Utan den används referenserna som de är.", "proAudioPostProcessing": "Professionell ljudefterbearbetning", "proAudioPostProcessingDescription": "Tillämpar EQ-värme, brusgrind, kompression, ljudstyrkenormalisering (-16 LUFS) och in-/uttoning på varje segment.", + "generatingSpeechSegmentXOfY": "Genererar tal: segment {0} av {1}", + "adjustingSpeedSegmentXOfY": "Justerar hastighet: segment {0} av {1}", + "mergingAudioSegmentXOfY": "Slår ihop ljud: segment {0} av {1}", + "postProcessingSegmentXOfY": "Efterbearbetning: segment {0} av {1}", "audioDucking": "Ljuddämpning", "audioDuckingDescription": "Minskar volymen för det ursprungliga videoljudet och blandar det med TTS-ljudet, så att det ursprungliga ljudspåret fortfarande är svagt hörbart.", + "removeOriginalSpeech": "Ta bort originaltal (långsamt)", + "removeOriginalSpeechDescription": "Behåller musiken och ljudeffekterna i originalvideon men tar bort dess tal, och mixar det nya talet över det. Tar ungefär lika lång tid som själva videon med en GPU, och många gånger längre utan. Med ljuddämpning påslagen gäller dess volym musiken och effekterna. Gäller endast när talet läggs till i videofilen.", + "removingOriginalSpeech": "Tar bort originaltal...", + "removeOriginalSpeechFailed": "Kunde inte ta bort originaltalet - talet lades till i videon utan det.", + "speechRemovalModel": "Modell", + "speechRemovalModelDescription": "Bästa kvalitet behåller mest av musiken och ljudeffekterna. Snabb är ungefär tre gånger snabbare, med lite mer av musiken förlorad.", + "speechRemovalBestQuality": "Bästa kvalitet (Mel-Band RoFormer)", + "speechRemovalFast": "Snabb (HTDemucs)", + "videoTruncatedWarning": "Bilden i den nya videofilen slutar efter {0}, men originalvideon är {1} lång - bilden fryser medan ljudet fortsätter.\n\nFilen behölls. Se error-log.txt i Subtitle Edits datamapp för ffmpeg-utdata.", "originalVolumePercent": "Ursprunglig volym %", "vadSilenceCompression": "VAD-tystnadskompression", "vadSilenceCompressionDescription": "Förkortar pauser mellan ord innan tempo ändras. Använder Voice Activity Detection för att endast komprimera tystnadsmellanrum medan tal lämnas orört. Detta är det föredragna första steget — det minskar varaktighet utan kvalitetsförlust.", @@ -2778,7 +3085,6 @@ "subtitleMergedLinesAppliedPlural": "Tillämpade {0} radsammanslagningar från text till tal" }, "shotChanges": { - "titleGenerateOrImport": "Generera/importera scenbyten", "generateShotChanges": "Generera scenbyten", "importShotChanges": "Importera scenbyten", "generateShotChangesWithFfmpeg": "Generera scenbyten med ffmpeg", @@ -2788,7 +3094,9 @@ "timeCodeFormatColon": "Tidskodsformat:", "shotChangesList": "Lista över scenbyten", "shotChangesClearQuestion": "Är du säker på att du vill rensa alla scenbyten?", - "deleteSelectedShotChangeQuestion": "Är du säker på att du vill ta bort det markerade scenbytet?" + "deleteSelectedShotChangeQuestion": "Är du säker på att du vill ta bort det markerade scenbytet?", + "shotChanges": "Scenbyten", + "exportShotChanges": "Exportera scenbyten" }, "videoOcr": { "title": "OCR av inbränd undertext", @@ -2798,10 +3106,6 @@ "bottomHalf": "Nedre halva", "fullFrame": "Hela bilden", "engine": "OCR-motor", - "language": "Språk", - "url": "URL", - "model": "Modell", - "apiKey": "API-nyckel", "scan": "Skanna", "framesPerSecond": "Bildrutor per sekund", "textBrightnessMinimum": "Minsta textljusstyrka (0=av)", @@ -2816,10 +3120,10 @@ "runningOcrXY": "Kör OCR... {0}/{1}", "noLinesFoundTitle": "Inga undertexter hittades", "noLinesFoundMessage": "Ingen text hittades i skanningsområdet - försök justera skanningsområdet, motorn eller minsta ljusstyrka.", + "ocrStoppedEarlyMessage": "OCR-motorn stoppade innan alla bildrutor lästs, så de rader som hittats hittills behålls och resten saknas.{0}{0}{1}", "linesFoundX": "{0} rader hittades", "refiningTimingXY": "Finjusterar tider... {0}/{1}", "fixOcrErrors": "Åtgärda OCR-fel", - "dictionary": "Ordbok", "editLineX": "Redigera rad {0}", "downloadDictionaryHint": "Ladda ner en rättstavningsordbok...", "framesPerSecondHint": "Hur många bildrutor per sekund som avsöks. 5 räcker för normala undertexter; högre fångar mycket korta rader och ger tätare gruppering, men kostar mer OCR-tid.", @@ -2836,14 +3140,64 @@ "testOcr": "Testa nuvarande bildruta", "testOcrRunning": "Testar OCR på nuvarande bildruta...", "testOcrResultX": "Testresultat: {0}", - "testOcrNoTextFound": "Test: ingen text hittades i skanningsområdet" + "testOcrNoTextFound": "Test: ingen text hittades i skanningsområdet", + "engineSettings": "Inställningar för OCR-motor", + "engineBuiltIn": "Inbyggd i operativsystemet", + "engineExternalServer": "Extern server (installeras och hanteras utanför Subtitle Edit)", + "engineCloudApi": "Moln-API (kräver en API-nyckel)", + "engineNothingToInstall": "Inget att installera", + "engineModelsMissing": "Motor installerad, modeller saknas", + "website": "Webbplats" + }, + "backgroundMusic": { + "title": "Generera bakgrundsmusik", + "generateBackgroundMusicDotDotDot": "Generera bakgrundsmusik...", + "preset": "Förinställning", + "prompt": "Prompt", + "tempoBpm": "Tempo (BPM)", + "generateSeconds": "Generera (sekunder)", + "seed": "Seed", + "randomSeed": "Slumpmässig", + "outputLength": "Utdatalängd", + "outputLengthVideoX": "{0} (videolängd - musiken loopas sömlöst)", + "musicVolumePercent": "Musikvolym (%)", + "removeExistingAudioTracks": "Ta bort befintliga ljudspår", + "originalAudioVolumePercent": "Originalljudets volym (%)", + "saveAudioDotDotDot": "Spara ljud...", + "addToVideoDotDotDot": "Lägg till i video...", + "engineInfo": "ACE-Step 1.5 Turbo via audio.cpp (MIT-licens) - körs lokalt", + "presetCooking": "Matlagning / bakning", + "presetTech": "Teknik / prylrecension", + "presetForest": "Skog / natur", + "presetRelaxingPiano": "Avslappnande piano", + "presetLofiVlog": "Lo-fi-vlogg", + "presetCorporate": "Företag / förklaringsvideo", + "loadingModel": "Läser in modell...", + "composing": "Komponerar...", + "generatingAudio": "Genererar ljud...", + "decodingAudio": "Avkodar ljud...", + "creatingLoop": "Skapar sömlös loop...", + "resultLoopXYZ": "Loop: {0} vid {1} BPM, skarvmatchning {2}%", + "resultNoLoopX": "Musik klar: {0}", + "addingMusicToVideo": "Lägger till musik i video...", + "downloadModelTitle": "Ladda ner musikmodell?", + "downloadModelQuestionX": "Musikgenerering kräver modellen ACE-Step 1.5 ({0}).\n\nLadda ner modell?", + "downloadingModel": "Laddar ner modellen ACE-Step 1.5...", + "saveAudioTitle": "Spara bakgrundsmusik", + "audioFileSavedX": "Ljudfil sparad: {0}", + "unableToGenerateMusic": "Kunde inte generera musik", + "unableToAddMusicToVideo": "Kunde inte lägga till musik i video", + "outputLengthTextToSpeech": "Följer det genererade talet - musiken loopas sömlöst och sänks medan någon pratar", + "addBackgroundMusic": "Lägg till bakgrundsmusik", + "addBackgroundMusicHint": "Generera musik lokalt med ACE-Step 1.5 och mixa in den under talet - den sänks medan någon pratar", + "generatingBackgroundMusicDotDotDot": "Genererar bakgrundsmusik...", + "mixingBackgroundMusicDotDotDot": "Mixar bakgrundsmusik..." }, "voices": "Röster", "presets": "Förinställningar", "talkerX": "Talare {0}", "baseModel": "Basmodell", "turboModel": "Turbomodell", - "ttsUpdateX": "Uppdatera {0}", "ttsEngineNotInstalled": "{0} är inte installerad", "ttsEngineUpdateAvailable": "{0} - uppdatering tillgänglig", "ttsEngineTooOldUpdateRequired": "{0} för gammal - uppdatering krävs", @@ -2866,19 +3220,25 @@ "cloneVoiceTo": "Klona röst till", "cloneVoiceNameTitle": "Namn på ny röst", "cloneVoiceExtractFailed": "Kunde inte extrahera ljud för den markerade raden.", + "lineXHasNoDuration": "Rad {0} har ingen varaktighet (sluttiden är inte efter starttiden), så det finns inget ljud att extrahera.", + "audioClipsSkippedLinesX": "Inget ljud kunde extraheras för dessa rader, så de hoppades över: {0}", "toggleShotChange": "Växla scenbyte", "resetWaveformZoomAndSpeed": "Återställ vågformszoom & hastighet", "showOriginalSubtitle": "Visa originalundertext", + "timelineGroupTracksBy": "Gruppera undertextspår efter", + "timelineShowThumbnails": "Visa videominiatyrer", + "showSpeechOnly": "Visa endast tal (långsamt att generera)", + "isolatingSpeechForWaveformX": "Isolerar tal för vågformen... {0}", + "isolatingSpeechForWaveformFailed": "Kunde inte isolera talet för vågformen - se verktygsloggen", + "speechOnlyWaveformReady": "Vågformen visar nu endast tal", "showOnlyWaveform": "Visa endast vågform", "showOnlySpectrogram": "Visa endast spektrogram", "showWaveformAndSpectrogram": "Visa vågform och spektrogram", - "spectrogramClassic": "Klassisk", "spectrogramClassicViridis": "Viridis", "spectrogramClassicPlasma": "Plasma", "spectrogramClassicInferno": "Inferno", "spectrogramClassicTurbo": "Turbo", "spectrogramNeon": "Neon", - "waveformDrawStyleClassic": "Klassisk", "waveformDrawStyleFancy": "Snygg", "setVideoPositionAndPauseAndSelectSubtitle": "Sätt videoposition, pausa och välj undertext", "setVideopositionAndPauseAndSelectSubtitleAndCenter": "Sätt videoposition, pausa, välj undertext och centrera", @@ -2888,7 +3248,6 @@ }, "sync": { "visualSync": "Visuell synk", - "sync": "Synk", "startScene": "Startscen", "endScene": "Slutscen", "playTwoSecondsAndBack": "Spela 2 sek & tillbaka", @@ -2904,19 +3263,18 @@ "adjustSelectedLinesAndForward": "Justera markerade rader och framåt", "toFrameRate": "Till bildfrekvens", "fromFrameRate": "Från bildfrekvens", + "videoXFrameRateY": "{0} - {1} fps", "adjustAllTimes": "Justera alla tider (visa tidigare/senare)", "showEarlier": "Visa tidigare", "showLater": "Visa senare", - "changeFrameRate": "Ändra bildfrekvens", "setSyncPoint": "Sätt synkpunkt", "setSyncPointViaVideo": "Ange synkpunkt via video...", "syncPointTimeCode": "Synkpunktens tidskod", "syncPoints": "Synkpunkter", "pointSync": "Punktsynk", "pointSyncViaOther": "Punktsynk via annan undertext", - "adjustmentX": "Justering: {0}", - "totalAdjustmentX": "Total justering: {0}", - "syncPointCandidateInfo": "Rader som börjar efter {0}+ sekunders tystnad markeras - de blir ofta pålitliga synkpunkter", + "syncPointCandidateAfterInfo": "Rader som följs av {0}+ sekunders tystnad markeras - de är ofta pålitliga synkpunkter", + "gapAfter": "Mellanrum efter", "adjustAllShortcuts": "Tangentbordsgenvägar:\r\n\r\n• Skift + Vänster/Höger: Flytta 10 ms\r\n• Ctrl + Vänster/Höger: Flytta 100 ms\r\n• Alt + Vänster/Höger: Flytta 500 ms", "offsetInSeconds": "Förskjutning i sekunder", "speedFactor": "Hastighetsfaktor", @@ -2936,6 +3294,7 @@ "lineMerge": "Radsammanslagning", "delayInSecondsBetweenRequests": "Fördröjning i sekunder mellan förfrågningar", "maxBytesPerRequest": "Max byte per förfrågan", + "keepMusicLinesUntranslated": "Översätt inte rader inom musiksymboler (♪ ... ♪)", "promptText": "Prompttext", "resetPromptToDefault": "Återställ prompt till standard", "translateEachLineSeparately": "Översätt varje rad separat", @@ -2949,11 +3308,10 @@ "readyToTranslate": "Klar att översätta", "translating": "Översätter...", "translationComplete": "Översättning klar", + "translateInPlaceNoOriginal": "Översätt på plats (skapa inte original)", "translationCancelled": "Översättningen avbröts", "swapLanguages": "Byt käll- och målspråk", "xIsAlreadyDownloadedReDownload": "{0} har redan laddats ned.\r\nLadda ner igen?", - "advancedDotDotDot": "Avancerat...", - "advancedSettings": "Avancerade inställningar", "advancedSettingsSubtitle": "Batch-, kontext- och samplingsalternativ för den avancerade llama.cpp-motorn", "context": "Kontext", "synopsis": "Synopsis", @@ -2977,6 +3335,7 @@ "useOnlyExtraServerParametersHint": "Starta llama-server med parametrarna ovan i stället för Subtitle Edits egen inställning - endast modell, värd och port anges fortfarande", "serverRunningAtX": "Servern körs på {0}", "customPromptHint": "Egna instruktioner ({0} = källspråk, {1} = målspråk); tomt = inbyggd prompt", + "editTranslationHint": "Klicka på översättningen i den markerade raden för att redigera den (Enter sparar, Escape avbryter)", "llamaCppDownloadEngineAndModelPrompt": "llama.cpp kräver att llama-server-motorn och en översättningsmodell laddas ner. Ladda ner nu?", "llamaCppDownloadEnginePrompt": "llama.cpp kräver att llama-server-motorn laddas ner. Ladda ner nu?", "llamaCppDownloadModelPrompt": "llama.cpp kräver att den valda översättningsmodellen laddas ner. Ladda ner nu?" @@ -2989,7 +3348,6 @@ "dialogStyleDashBothLinesWithSpace": "Bindestreck båda raderna med mellanslag", "dialogStyleDashBothLinesWithoutSpace": "Bindestreck båda raderna utan mellanslag", "continuationStyle": "Fortsättningsstil", - "continuationStyleNone": "Ingen", "continuationStyleNoneTrailingDots": "Ingen, punkter för pauser (endast efterföljande)", "continuationStyleNoneLeadingTrailingDots": "Ingen, punkter för pauser", "continuationStyleNoneTrailingEllipsis": "Ingen, ellips för pauser (endast efterföljande)", @@ -3000,7 +3358,6 @@ "continuationStyleLeadingTrailingEllipsis": "Ellips", "continuationStyleLeadingTrailingDash": "Bindestreck", "continuationStyleLeadingTrailingDashDots": "Bindestreck, men punkter för pauser", - "continuationStyleCustom": "Anpassad", "cpsLineLengthStyle": "Cps/radlängd", "cpsLineLengthStyleCalcAll": "Räkna alla tecken", "cpsLineLengthStyleCalcNoSpaceCpsOnly": "Räkna alla utom mellanslag, endast cps", @@ -3013,14 +3370,6 @@ "cpsLineLengthStyleCalcNoSpaceOrPunctuationCpsOnly": "Inga mellanslag eller skiljetecken, endast CPS", "cpsLineLengthStyleCalcIgnoreArabicDiacritics": "Ignorera arabiska diakritiska tecken", "cpsLineLengthStyleCalcIgnoreArabicDiacriticsNoSpace": "Ignorera arabiska diakritiska tecken, inga mellanslag", - "timeCodeModeHhMmSsMs": "HH:MM:SS:MS", - "timeCodeModeHhMmSsFf": "HH:MM:SS:FF", - "splitBehaviorPrevious": "Lägg till mellanrum till vänster om delningspunkt (fokus höger)", - "splitBehaviorHalf": "Lägg till mellanrum i mitten av delningspunkt (fokus vänster)", - "splitBehaviorNext": "Lägg till mellanrum till höger om delningspunkt (fokus vänster)", - "subtitleListActionNothing": "Inget", - "subtitleListActionVideoGoToPositionAndPause": "Gå till videoposition och pausa", - "subtitleListActionVideoGoToPositionAndPlay": "Gå till videoposition och spela", "subtitleGridCenterText": "Centrera text i undertextlistan", "minGapCalculateDotDotDot": "Beräkna minsta mellanrum utifrån en bildfrekvens...", "minGapCalculateTitle": "Minsta mellanrum mellan undertexter i ms", @@ -3052,8 +3401,14 @@ "checkForUpdatesChannelStableAndBeta": "Stabila och beta-versioner", "assaAutoSetResolution": "ASSA: ange upplösning (PlayResX/PlayResY) från videon när en video öppnas", "assaAutoSetResolutionPrompt": "ASSA: fråga innan upplösningen ändras för att matcha videon (annars skalas den om automatiskt)", + "videoControls": "Videokontroller", + "videoControlsEdit": "Redigera videokontroller...", + "videoControlsPositionText": "Position-/varaktighetstext", + "videoControlsVideoFileName": "Videofilnamn", + "videoControlsPlayerName": "Videospelarens namn", "mergeKeepEndTime": "Slå ihop rader: behåll sluttid (tillåt överlapp med nästa undertext)", "mergeKeepEndTimeOnlyAssa": "Slå ihop rader: behåll sluttid endast för \"Advanced Sub Station Alpha\"", + "mergeAutoBreak": "Slå ihop rader: bryt sammanslagen text automatiskt (av behåller radernas egna radbrytningar)", "defaultSaveLocation": "Standardplats för sparande", "defaultSaveLocationSourceFileFolder": "Undertext-/videofilens mapp", "defaultSaveLocationLastUsedFolder": "Senast använda mappen", @@ -3061,18 +3416,10 @@ "defaultSaveLocationSubtitleFileFolder": "Undertextfilens mapp", "defaultSaveLocationCustomFolder": "Egen mapp", "subtitleListActionVideoGoToPositionAndPlayCurrentAndPause": "Gå till videoposition, spela aktuell och pausa", - "subtitleListActionEditText": "Gå till redigera textruta", "subtitleListActionVideoGoToPositionMinus1SecAndPause": "Gå till videoposition - 1 s och pausa", "subtitleListActionVideoGoToPositionMinusHalfSecAndPause": "Gå till videoposition - 0,5 s och pausa", "subtitleListActionVideoGoToPositionMinus1SecAndPlay": "Gå till videoposition - 1 s och spela", - "subtitleListActionEditTextAndPause": "Gå till redigera textruta och pausa vid videoposition", - "autoBackupEveryMinute": "Varje minut", - "autoBackupEveryXthMinute": "Varje {0}:e minut", - "profiles": "Profiler", - "autoBackupDeleteAfterXMonths": "Ta bort auto-säkerhetskopior efter {0} månader", "searchSettingsDotDotDot": "Sök efter inställningar...", - "syntaxColoring": "Syntaxfärgning", - "waveformSpectrogram": "Vågform/spektrogram", "network": "Nätverk", "fileTypeAssociations": "Filtypsassociationer", "textBoxColorTags": "Färgtaggar (HTML/ASSA) i undertextrutan", @@ -3100,10 +3447,9 @@ "darkThemeForegroundColor": "Mörkt temas förgrundsfärg", "darkThemeBackgroundColor": "Mörkt temas bakgrundsfärg", "showGridLines": "Visa rutnätslinjer", - "resetSettings": "Återställ inställningar?", - "resetSettingsDetail": "Detta återställer alla inställningar till deras standardvärden.\n\nFortsätt?", "showHorizontalLineAboveToolbar": "Visa horisontell linje ovanför verktygsfält", "bookmarkColor": "Bokmärkesfärg", + "spellCheckHighlightColor": "Markeringsfärg för rättstavning", "singleLineMaxLength": "Enkelradsmaxlängd", "optimalCharsPerSec": "Optimalt antal tecken/sek", "maxCharsPerSec": "Max tecken/sek", @@ -3116,13 +3462,21 @@ "unbreakSubtitlesShortThan": "Slå ihop undertexter kortare än", "newEmptyDefaultMs": "Standard varaktighet för ny undertext (ms)", "timeCodeUpDownStepMs": "Tid upp/ned-steg (ms)", + "moveSelectedLinesStepMs": "Steg för kortkommandot flytta markerade rader (ms)", + "moveStartEndStepMs": "Steg för kortkommandot flytta start/slut (ms)", + "moveLinesShortenNeighbor": "Flytta rader: förkorta föregående/nästa rad i stället för att överlappa den", "promptBeforeDelete": "Fråga före borttagning", "rememberPositionAndSize": "Kom ihåg fönsterposition och storlek", + "titleBarFullFileName": "Visa fullständig filsökväg i titelraden", + "showRecentFiles": "Visa senaste filer (för att öppna igen)", "openLastFileOnStart": "Öppna senast använda fil vid start", "autoSave": "Spara automatiskt (spara den öppna filen medan du redigerar)", "autoBackupOn": "Auto-säkerhetskopia", "autoBackupIntervalMinutes": "Auto-säkerhetskopieringsintervall (minuter)", "autoBackupDeleteAfterDays": "Auto-säkerhetskopia bevarandetid (dagar)", + "settingsBackupOn": "Säkerhetskopiera inställningar automatiskt", + "settingsBackupIntervalDays": "Intervall för säkerhetskopiering av inställningar (dagar, 0 = varje start)", + "settingsBackupMaxCount": "Antal säkerhetskopior av inställningar att behålla", "autoConvertToUtf8": "Auto-konvertera kodning till UTF-8 vid öppning", "autoTrimWhiteSpace": "Auto-trimma blanksteg", "defaultEncoding": "Standardkodning", @@ -3137,22 +3491,13 @@ "colorGapTooShort": "Färglägg om mellanrum är för kort", "errorBackgroundColor": "Felbakgrundsfärg", "waveformDrawGridLines": "Rita rutnätslinjer", - "waveformCenterVideoPosition": "Centrera videoposition", + "waveformUseSkiaRenderer": "Använd experimentell snabb renderare", "waveformCenterVideoPositionAlsoWhenPaused": "Centrera videoposition även vid paus", + "waveformSelectCurrentSubtitleWhilePaused": "Välj aktuell undertext även när pausad", + "waveformRememberZoom": "Kom ihåg zoom (horisontell och vertikal)", "waveformShowToolbar": "Visa verktygsfält", "waveformShowToolbarEdit": "Redigera verktygsfält...", "waveformSpectrogramCombinedWaveformHeight": "Vågform/spektrogram kombinerade, vågformshöjd %", - "showWaveformToolbarPlay": "Verktygsfält: visa spela-knapp", - "showWaveformToolbarRepeat": "Verktygsfält: visa upprepa-knapp", - "showWaveformToolbarRemoveBlankLines": "Verktygsfält: visa knapp för ta bort tomma rader", - "showWaveformToolbarNew": "Verktygsfält: visa knapp för ny undertext", - "showWaveformToolbarSetStart": "Verktygsfält: visa sätt start-knapp", - "showWaveformToolbarSetEnd": "Verktygsfält: visa sätt slut-knapp", - "showWaveformToolbarStartAndOffsetTheRest": "Verktygsfält: visa knapp för sätt start och förskjut resten", - "showWaveformToolbarHorizontalZoom": "Verktygsfält: visa horisontellt zoomreglage", - "showWaveformToolbarVerticalZoom": "Verktygsfält: visa vertikalt zoomreglage", - "showWaveformToolbarVideoPositionSlider": "Verktygsfält: visa videopositionsreglage", - "showWaveformToolbarPlaybackSpeed": "Verktygsfält: visa uppspelningshastighet", "waveformFocusTextboxAfterInsertNew": "Fokusera textruta efter infogning", "waveformInvertMouseWheel": "Invertera mushjul", "waveformSnapToShotChanges": "Fäst till scenbyten", @@ -3168,18 +3513,18 @@ "fixCommonErrorsSkipStep1": "Åtgärda vanliga fel: hoppa över steg 1 (välj åtgärder)", "showPluginsMenu": "Visa menyn Plugins", "proxyAddress": "Proxyadress", - "username": "Användarnamn", "password": "Lösenord", "defaultFormat": "Standardformat", "defaultSaveAsFormat": "Standard \"Spara som\"-format", "favoriteSubtitleFormats": "Favoritundertextformat", - "showStopButton": "Visa stoppknapp", - "showFullscreenButton": "Visa helskärmsknapp", "fullscreenHideControls": "Dölj videokontroller i helskärm", + "showSecondarySubtitleDialog": "Visa stildialog när en sekundär undertextfil öppnas", + "rememberSecondarySubtitleFile": "Kom ihåg sekundär undertextfil", "autoOpenVideoFile": "Öppna videofil automatiskt vid öppning av undertext", "downloadMpv": "Ladda ner mpv", "downloadVlc": "Ladda ner VLC", "allowSingleLetterShortcutsInTextbox": "Tillåt kortkommandon med enstaka bokstav i textruta", + "allowTextNavigationShortcutsInTextbox": "Tillåt kortkommandon på textnavigeringstangenter (Ctrl+Vänster/Höger, Home/End) i textrutan", "spellCheckEnglishTreatInApostropheAsIng": "Stavningskontroll: Behandla ord som slutar på 'in'' som 'ing' (endast på engelska)", "goToLineNumberSetsVideoPosition": "Gå-till-radnummer sätter också videoposition", "adjustAllTimesRememberLineSelectionChoice": "Justera alla tider, kom ihåg val av radmarkering", @@ -3191,6 +3536,7 @@ "showAssaLayer": "Visa ASSA-lagerruta", "waveformCursorColor": "Vågformskursorns/huvudets färg", "waveformShotChangeColor": "Vågformsscenbytets färg", + "waveformGridColor": "Rutnätsfärg för vågform", "waveformParagraphLeftColor": "Vågform vänsterkantsfärg", "waveformParagraphRightColor": "Vågform högerkantsfärg", "waveformFancyHighColor": "Vågform snygg hög färg", @@ -3218,26 +3564,30 @@ "resetSyntaxColoring": "Återställ syntaxfärgning", "resetWaveform": "Återställ vågform", "resetRules": "Återställ regler", - "useFrameMode": "Använd bildläge (hh.mm.ss.ff)", + "timeCodeMode": "Tidskodsläge", + "timeCodeModeTime": "Tid (hh:mm:ss,zzz)", + "timeCodeModeFrames": "Bildrutor (hh:mm:ss:ff)", + "timeCodeModeFrameNumbers": "Bildrutenummer", + "frameModeSnapToVideoFrames": "Lagra tider på videobildrutor (inte bildrutor inom varje sekund)", "textBoxLimitNewLines": "Begränsa antal rader i undertextrutan", - "mpvVideoOutput": "Videoutmatning (mpv)", "mpvOpenGl": "libmpv - OpenGL", "mpvSoftwareRendering": "libmpv - Programvarurendering (långsam)", + "mpvMetalRendering": "libmpv - Metal (experimentell, kräver omstart)", "mpvWidRendering": "libmpv - Inbyggd Window ID-rendering (snabbare på Windows/Linux)", "waveFormsAndSpectrogramFoldersContainsX": "Mapparna \"Vågformer\" och \"Spektrogram\" innehåller {0}", "deleteWaveformAndSpectrogramFoldersQuestion": "Ta bort filer i \"Vågformer\" och \"Spektrogram\"?", "waveformGenerateSpectrogram": "Generera spektrogram", "waveformSpectrogramMode": "Spektrogramläge", - "waveformCenterOnSingleClick": "Centrera vid enkelklick", - "waveformSingleClickSelectsSubtitle": "Välj undertext vid enkelklick", "waveformRightClickSelectsSubtitle": "Välj undertext vid högerklick", - "waveformPauseOnSingleClick": "Pausa vid enkelklick", "waveformDrawStyle": "Vågformens ritstil", "waveformExtractAudioFormat": "Format för extraherat ljud", "waveformExtractAudioSampleRate": "Samplingsfrekvens för extraherat ljud", "waveformExtractAudioSampleRateOriginal": "Original (behåll källan)", "waveformExtractAudioBitRate": "Bithastighet för extraherat ljud (MP3/M4A)", + "ffmpegUseCenterChannelOnly": "Använd endast främre mittkanalen för 5.1-ljud (FFmpeg)", "vlcWidRendering": "libVLC - Inbyggd Window ID-rendering", + "ffmpegSoftwareRendering": "FFmpeg", + "downloadFfmpegLibs": "Ladda ner FFmpeg-bibliotek (för FFmpeg-videospelaren)", "subtitleGridEnterKeyAction": "Åtgärd för Enter-tangent i undertextlista", "subtitleSingleClickAction": "Åtgärd för enkelklick i undertextlista", "subtitleDoubleClickAction": "Åtgärd för dubbelklick i undertextlista", @@ -3254,6 +3604,7 @@ "subtitleGridFormattingShowFormatting": "Visa formatering", "subtitleGridFormattingShowTags": "Visa taggar", "subtitleGridFormattingHideTags": "Dölj taggar", + "subtitleGridFormattingShowFormattingKeepTags": "Visa formatering, behåll icke-visuella taggar", "waveformParagraphBackgroundColor": "Vågform undertextbakgrundsfärg", "waveformParagraphSelectedBackgroundColor": "Vågform markerad undertextbakgrundsfärg", "waveformAllowOverlap": "Tillåt överlapp (vid flytt/storleksändring)", @@ -3263,8 +3614,6 @@ "saveAsBehaviorUseVideoFileName": "Använd videofilnamn", "saveAsBehaviorUseSubtitleFileName": "Använd undertextfilnamn", "saveAsAppendLanguageCodeTwoLetter": "Två bokstäver", - "saveAsAppendLanguageCodeThreeLetter": "Tre bokstäver", - "saveAsAppendLanguageCodeThreeLetterBibliographic": "Tre bokstäver (ISO 639-2/B)", "saveAsAppendLanguageCodeLanguageName": "Språknamn", "splitOddLineActionWeightTop": "Vikt övre", "splitOddLineActionWeightBottom": "Vikt nedre", @@ -3273,6 +3622,7 @@ "speechToTextSelectedLinesPromptFirstTimeOnly": "Tal till text: markerade rader, fråga efter språk/motor endast första gången", "multipleReplaceShowDotDotDotButtons": "Multipel ersättning: visa snabbmenysknappar", "gridFocusTextboxAfterInsertNew": "Lista: fokusera textruta efter infogning av ny undertext", + "undoRedoGoToChangedLine": "Ångra/gör om: gå till ändrad rad (och videoposition)", "musicSymbol": "Musiksymbol", "musicSymbolsToReplace": "Musiksymboler att ersätta (separerade med komma)", "textToSpeechPromptMergeContinuationLines": "Text till tal: fråga om att slå samman fortsättningsrader", @@ -3281,8 +3631,7 @@ "useFocusedButtonBackgroundColor": "Använd bakgrundsfärg för fokuserad knapp", "focusedButtonBackgroundColor": "Bakgrundsfärg för fokuserad knapp", "forceCrLfOnSave": "Tvinga CR+LF vid sparande (textundertextfiler)", - "showWaveformToolbarPlayNext": "Verktygsfält: visa spela nästa-knapp", - "showWaveformToolbarPlaySelection": "Verktygsfält: visa spela markering-knapp", + "linuxClipboardUseExternalTool": "Kopiera text via xclip/wl-copy (behåller tecken med accenter)", "showFormatLimitWarning": "Varna vid sparande när rader överskrider formatets gränser (t.ex. SCC 32 tecken/rad)", "textBoxButtonShowAutoBreak": "Textruta: visa auto-bryt-knapp", "textBoxButtonShowUnbreak": "Textruta: visa slå ihop-knapp", @@ -3294,12 +3643,18 @@ "waveformDoubleClickAction": "Åtgärd för dubbelklick på vågform (efter enkelklicksåtgärd)", "allSettings": "Alla inställningar", "uiScale": "UI-skala (%)", + "fontScale": "Typsnittsskala (%)", + "textSelectionOpacity": "Opacitet för textmarkering (%)", "waveformToolbarItems": "Vågformens verktygsfältsobjekt", "matchIconColorToDarkTheme": "Matcha ikonfärg till mörkt temas förgrundsfärg", "subtitlePreviewProperties": "Egenskaper för undertextsförhandsvisning", "usePositionFromSubtitleFile": "Använd position från undertextfilen (TTML/PAC/EBU STL)", "marginIsPartOfSubtitleArea": "Marginalen är en del av undertextområdet", "textJustify": "Justera rader", + "deinterlace": "Avflätning av video", + "deinterlaceOff": "Av", + "deinterlaceAuto": "Auto (endast flätade bildrutor)", + "deinterlaceAlways": "Alltid", "pixelWidthInfo": "Gröna linjer = max-bredd-gräns | Rött område = text överstiger gräns", "spellCheckEngineHunSpelll": "Hunspell", "spellCheckEngineMsWord": "MS Word", @@ -3309,6 +3664,15 @@ "waveformSaveAsCustomTheme": "Spara som anpassat tema...", "waveformLoadThemeDotDotDot": "Läs in tema...", "waveformExportThemeDotDotDot": "Exportera tema...", + "waveformSaveCustomThemeTitle": "Spara anpassat tema", + "waveformThemeFileType": "Subtitle Edit vågformstema", + "waveformThemeHighContrast": "Hög kontrast", + "waveformThemeOceanBlue": "Havsblå", + "waveformThemeWarmSunset": "Varm solnedgång", + "waveformThemeForest": "Skog", + "waveformThemeMidnightPurple": "Midnattslila", + "waveformThemeRetroAmber": "Retro bärnsten", + "waveformThemeIce": "Is", "subtitleGridTextSingleLineSeparator": "Enkelradsavgränsare (t.ex.
)", "subtitleGridTextDisplay": "Anpassning av undertext i rutnät", "subtitleGridTextDisplayClip": "Klipp (en rad)", @@ -3325,107 +3689,53 @@ "webVttUseXTimestampMap": "WebVTT: använd X-TIMESTAMP-MAP (förskjut tidskoder vid inläsning)", "waveformAutoGenerate": "Generera vågform automatiskt när en video öppnas", "gridGoToSubtitleAndPlayAndFocusTextBox": "Gå till undertext och spela och fokusera textruta", - "ocrGuessUnknownWords": "OCR: försök gissa okända ord" + "ocrGuessUnknownWords": "OCR: försök gissa okända ord", + "spellCheckRememberUseAlwaysList": "Rättstavning: kom ihåg listan \"Använd alltid\"", + "fixShortDisplayTimesAllowMoveStartTime": "Åtgärda vanliga fel: åtgärda korta visningstider får flytta starttid" }, "shortcuts": { - "title": "Kortkommandon", "searchShortcuts": "Sök kortkommandon...", - "filter": "Filter", - "categoryGeneral": "Allmänt", "categorySubtitleGridAndTextBox": "Undertextlistvy & textruta", "categorySubtitleGrid": "Undertextlistvy", - "categoryWaveform": "Vågform", - "generalSetupLikeSe4": "Ställ in som Subtitle Edit 4 (tema, genvägar, ersätt regler)", - "generalMergeSelectedLines": "Slå ihop markerade rader", - "generalMergeWithPrevious": "Slå ihop med föregående", - "generalMergeWithNext": "Slå ihop med nästa", - "generalMergeWithPreviousAndUnbreak": "Slå ihop med föregående och slå ihop", - "generalMergeWithNextAndUnbreak": "Slå ihop med nästa och slå ihop", - "generalMergeWithPreviousAndAutoBreak": "Slå ihop med föregående och bryt automatiskt", - "generalMergeWithNextAndAutoBreak": "Slå ihop med nästa och bryt automatiskt", - "generalMergeSelectedLinesAndAutoBreak": "Slå ihop markerade rader och bryt automatiskt", "generalMergeSelectedLinesAndUnbreak": "Slå ihop markerade rader och slå ihop", "generalMergeSelectedLinesAndUnbreakCjk": "Slå ihop markerade rader och slå ihop CJK", "generalUnbreakNoSpaceCjk": "Slå ihop utan mellanslag (CJK)", - "generalMergeSelectedLinesOnlyFirstText": "Slå ihop markerade rader endast första texten", + "generalToggleBreak": "Växla radbrytning/ta bort radbrytning", "generalMergeSelectedLinesBilingual": "Slå ihop markerade rader tvåspråkig", - "generalMergeWithPreviousBilingual": "Slå ihop med föregående tvåspråkig", - "generalMergeWithNextBilingual": "Slå ihop med nästa tvåspråkig", - "generalMergeOriginalAndTranslation": "Slå ihop original och översättning", "generalToggleTranslationMode": "Växla översättningsläge", - "generalSwitchOriginalAndTranslation": "Växla original och översättning", - "generalSwitchOriginalAndTranslationTextBoxes": "Växla original- och översättningstextrutor", "generalToggleTranslationAndOriginalInPreviews": "Växla mellan översättning och original i video-/ljudförhandsvisning", "generalChooseLayout": "Välj layout", - "generalLayoutChooseX": "Layout {0}", - "generalPlayFirstSelected": "Spela första markerad", - "generalGoToFirstSelectedLine": "Gå till första markerad rad", - "generalGoToNextEmptyLine": "Gå till nästa tomma rad", "generalGoToNextSubtitle": "Gå till nästa undertext", "generalGoToNextSubtitlePlayTranslate": "Gå till nästa undertext (spela översätt)", "generalGoToNextSubtitleCursorAtEnd": "Gå till nästa undertext (markör i slutet)", "generalGoToPrevSubtitle": "Gå till föregående undertext", "generalGoToPrevSubtitlePlayTranslate": "Gå till föregående undertext (spela översätt)", - "generalGoToStartOfCurrentSubtitle": "Gå till början av aktuell undertext", - "generalGoToEndOfCurrentSubtitle": "Gå till slutet av aktuell undertext", - "generalGoToPreviousSubtitleAndFocusVideo": "Gå till föregående undertext och fokusera video", - "generalGoToNextSubtitleAndFocusVideo": "Gå till nästa undertext och fokusera video", - "generalGoToPrevSubtitleAndPlay": "Gå till föregående undertext och spela", - "generalGoToNextSubtitleAndPlay": "Gå till nästa undertext och spela", - "generalGoToPreviousSubtitleAndFocusWaveform": "Gå till föregående undertext och fokusera vågform", - "generalGoToNextSubtitleAndFocusWaveform": "Gå till nästa undertext och fokusera vågform", - "generalGoToLineNumber": "Gå till radnummer", "generalGoToFirstLine": "Gå till första raden", "generalGoToLastLine": "Gå till sista raden", "alsoSetVideoPosition": "Ange även videoposition", "generalGoToVideoPosition": "Gå till videoposition", "generalToggleItalic": "Växla kursiv", "generalToggleBold": "Växla fet", - "generalToggleBookmarks": "Växla bokmärken", - "generalFocusTextBox": "Fokusera textruta", - "generalToggleBookmarksWithText": "Växla bokmärken med text", - "generalEditBookmarks": "Redigera bokmärken", - "fileNew": "Ny", "generalToggleUnderline": "Växla understrykning", + "generalToggleBox": "Växla ruta (EBU STL)", "fileOpen": "Öppna", "fileOpenKeepVideo": "Öppna (behåll video)", "fileSave": "Spara", "fileSaveAs": "Spara som", - "fileSaveAll": "Spara alla", "fileSaveOriginal": "Spara original", - "fileSaveOriginalAs": "Spara original som", - "fileOpenOriginalSubtitle": "Öppna originalundertext", - "fileCloseOriginalSubtitle": "Stäng originalundertext", - "fileTranslatedSubtitle": "Översatt undertext", "fileCompare": "Jämför", "fileStatistics": "Statistik", "fileImportPlainText": "Importera vanlig text", - "fileImportBluRaySupForOcr": "Importera Blu-ray SUP för OCR", - "fileImportBluRaySupForEdit": "Importera Blu-ray SUP för redigering", - "fileImportTimeCodes": "Importera tidskoder", "fileExportEbuStl": "Exportera EBU STL", "fileExportPac": "Exportera PAC", - "fileExportEdlClipName": "Exportera EDL-klippnamn", - "fileExportPlainText": "Exportera vanlig text", - "fileExportCustomTextFormat1": "Exportera anpassat textformat 1", - "fileExportCustomTextFormat2": "Exportera anpassat textformat 2", - "fileExportCustomTextFormat3": "Exportera anpassat textformat 3", "fileExit": "Avsluta", "openSeDataFolder": "Öppna Subtitle Edit-mapp", - "editFind": "Sök", - "editFindNext": "Sök nästa", "editFindPrevious": "Sök föregående", - "editReplace": "Ersätt", - "editMultipleReplace": "Multipel ersättning", "fillSelectedLinesWithClipboard": "Fyll markerade rader med urklippstext", - "editModifySelection": "Ändra markering", - "listSelectAll": "Markera alla", - "listSelectFirst": "Markera första", - "listSelectLast": "Markera sista", "listInverseSelection": "Invertera markering", "listDeleteSelection": "Ta bort markering", + "deleteSelectionEverywhere": "Ta bort markering (överallt)", "rippleDeleteSelection": "Ripple-ta bort markering", - "settings": "Inställningar", "assigned": "Tilldelad", "unassigned": "Otilldelad", "pressedKeyX": "Tryckt tangent: {0}", @@ -3433,6 +3743,7 @@ "detectKey": "Identifiera tangent", "control": "Ctrl", "alt": "Alt", + "altGr": "AltGr", "win": "Win", "shift": "Skift", "controlMac": "⌃", @@ -3443,11 +3754,14 @@ "resetShortcutsDetail": "Vill du återställa alla kortkommandon till standardvärden?", "togglePlayPause": "Växla spela/pausa", "toggleLockTimeCodes": "Växla lås tidskoder", + "toggleTimeCodeMode": "Växla tidskodsläge (tid/bildrutor/bildrutenummer)", + "toggleScreenPrivacy": "Växla skärmsekretess (dölj filnamn, sedan även texter)", + "screenPrivacyFileNamesHidden": "Skärmsekretess: filnamn dolda", + "screenPrivacyFileNamesAndTextsHidden": "Skärmsekretess: filnamn och texter dolda", + "screenPrivacyOff": "Skärmsekretess: av", "duplicateSelectedLines": "Duplicera markerade rader", "sourceView": "Källvy", - "showAlignmentPicker": "Justering", "addOrEditBookmark": "Lägg till eller redigera bokmärke", - "listBookmarks": "Lista bokmärken", "toggleBookmark": "Växla bokmärke (markerade rader, ingen text)", "toggleForcedSelectedLines": "Växla tvingad (markerade rader)", "goToNextBookmark": "Gå till nästa bokmärke", @@ -3458,7 +3772,6 @@ "waveformSetStartAndSetEndOfPreviousMinusGap": "Sätt start och sätt slut på föregående minus mellanrum", "waveformSetEndAndStartOfNextAfterGap": "Sätt slut och start på nästa plus mellanrum", "waveformSetEndAndStartOfNextAfterGapAndGoToNext": "Sätt slut och start på nästa plus mellanrum och gå till nästa", - "fetchFirstWordFromNextSubtitle": "Hämta första ordet från nästa undertext", "moveLastWordToNextSubtitle": "Flytta sista ordet till nästa undertext", "moveLastWordFromFirstLineDownCurrentSubtitle": "Flytta sista ordet från första raden ner (aktuell undertext)", "moveFirstWordFromNextLineUpCurrentSubtitle": "Flytta första ordet från nästa rad upp (aktuell undertext)", @@ -3471,7 +3784,6 @@ "goToPreviousLineFromVideoPosition": "Gå till föregående undertext (från aktuell videoposition)", "goToNextLineFromVideoPosition": "Gå till nästa undertext (från aktuell videoposition)", "goToNextLineAndSetVideoPosition": "Gå till nästa undertext och sätt videoposition", - "textBoxDeleteSelectionNoClipboard": "Textruta: Ta bort markering (inget urklipp)", "textBoxDeleteForward": "Textruta: Ta bort markering eller nästa tecken (framåtradering)", "textBoxCut": "Textruta: Klipp ut", "textBoxCut2": "Textruta: Klipp ut (alternativ)", @@ -3489,29 +3801,22 @@ "fileCloseOriginal": "Stäng original", "fileCloseTranslation": "Stäng översättning", "restoreAutoBackup": "Återställ auto-säkerhetskopia", - "openContainingFolder": "Öppna innehållande mapp", "importTimeCodes": "Importera tidskoder", "importSubtitleWithManuallyChosenEncoding": "Importera undertext med manuellt vald kodning", "exportBluRaySup": "Exportera Blu-ray SUP", "exportCustomTextFormat": "Exportera anpassat textformat", "exportPlainText": "Exportera vanlig text", - "showHistory": "Visa historik", "toggleRightToLeft": "Växla höger-till-vänster", "modifySelection": "Ändra markering", "adjustDurations": "Justera varaktigheter", "applyDurationLimits": "Tillämpa varaktighetsgränser", "batchConvert": "Batchkonvertera", - "bridgeGaps": "Överbrygga mellanrum", "applyMinGap": "Tillämpa min mellanrum", - "fixCommonErrors": "Åtgärda vanliga fel", "makeEmptyTranslationFromCurrentSubtitle": "Skapa tom översättning från aktuell undertext", - "mergeLinesWithSameText": "Slå ihop rader med samma text", - "mergeLinesWithSameTimeCodes": "Slå ihop rader med samma tidskoder", "splitBreakLongLines": "Dela/balansera om långa rader", "mergeShortLines": "Slå ihop korta rader", "mergeContinuationLines": "Slå samman fortsättningsrader", "snapAllTimesToFrames": "Fäst alla tider vid bildrutor", - "removeTextForHearingImpaired": "Ta bort text för hörselskadade", "joinSubtitles": "Sammanfoga undertexter", "splitSubtitle": "Dela undertext", "spellCheck": "Rättstavning", @@ -3532,7 +3837,6 @@ "adjustAllTimes": "Justera alla tider", "visualSync": "Visuell synk", "translateViaCopyPaste": "Översätt via kopiera-klistra in", - "shortcuts": "Kortkommandon", "wordLists": "Ordlistor", "chooseUiLanguage": "Välj UI-språk", "chooseRuleProfile": "Välj regelprofil", @@ -3543,6 +3847,7 @@ "textBoxItalic": "Textruta kursiv", "textBoxBold": "Textruta fet", "textBoxUnderline": "Textruta understruken", + "textBoxBox": "Textruta ruta (EBU STL)", "resetWaveformZoomAndSpeed": "Återställ vågformens zoom och uppspelningshastighet", "togglePlaybackSpeed": "Växla uppspelningshastighet", "playbackSpeedSlower": "Uppspelningshastighet långsammare", @@ -3554,22 +3859,28 @@ "seekSilenceForward": "Sök tystnad framåt", "waveformGuessStart": "Gissa starttid från vågform", "waveformGuessEnd": "Gissa sluttid från vågform", + "waveformGuessStartAndEnd": "Gissa start- och sluttid från vågform", "setVideoPositionCurrentSubtitleStart": "Sätt videoposition till aktuell rads start", "goToSubtitlePositionAndPause": "Gå till undertextens position och pausa", "setVideoPositionCurrentSubtitleEnd": "Sätt videoposition till aktuell rads slut", "toggleAudioTracks": "Växla ljudspår", - "listErrors": "Lista fel", "goToNextError": "Gå till nästa fel", "goToPreviousError": "Gå till föregående fel", "addNameToNameList": "Lägg till namn i namnlista", "findDoubleWords": "Hitta dubbla ord", "findDoubleLines": "Hitta dubbla rader", "colorX": "Färg {0}", - "removeColor": "Ta bort färg", "surroundWith": "Omge med...", "surroundWithXY": "Omge med {0}/{1}", - "repeatLine": "Upprepa rad", "surroundWithNumberX": "Omslut med #{0}", + "surroundWithBehavior": "Beteende", + "surroundWithBehaviorToggle": "Växla (lägg till, eller ta bort om det finns)", + "surroundWithBehaviorAdd": "Lägg till (varje gång)", + "surroundWithBehaviorRemove": "Ta bort (alla)", + "surroundWithBehaviorRemoveOnce": "Ta bort (en i taget)", + "surroundWithWorksOn": "Fungerar på", + "surroundWithScopeSelectionOrText": "Markering, annars hela texten", + "surroundWithScopeEachLine": "Varje rad", "searchVia": "Sök via", "searchViaX": "Sök via {0}", "searchViaNumberX": "Sök via #{0}", @@ -3596,6 +3907,11 @@ "copySubtitlePathToClipboard": "Kopiera undertextsökväg till urklipp", "copySubtitleOriginalPathToClipboard": "Kopiera undertextsökväg för original till urklipp", "focusTextBox": "Fokusera textruta", + "focusSubtitleListView": "Fokusera undertextlistvy", + "focusWaveform": "Fokusera vågform", + "focusOriginalTextBox": "Fokusera originaltextruta", + "textBoxGoToStart": "Textruta, gå till början", + "textBoxGoToEnd": "Textruta, gå till slutet", "sortByNumber": "Sortera efter nummer", "sortByStartTime": "Sortera efter \"Visa\"-tid", "sortByEndTime": "Sortera efter \"Dölj\"-tid", @@ -3620,7 +3936,6 @@ "assaStyles": "ASSA Stilar", "assaProperties": "ASSA Egenskaper", "assaAttachments": "ASSA Bilagor", - "assaVideoColorPicker": "ASSA Videofärgväljare", "recalculateDurationSelectedLines": "Räkna om varaktighet (markerade rader)", "setDurationMaxCpsSelectedLines": "Sätt varaktighet till max. CPS (markerade rader)", "toggleWaveformAndSpectrogramHeight": "Växla vågform/spektrogram delad höjd", @@ -3634,9 +3949,9 @@ "selectionToSentenceCase": "Textruta: Markering till Meningsskiftläge", "selectionToggleCasing": "Textruta: Växla skiftläge (VERSALER -> gemener -> Inledande Versaler)", "googleIt": "Googla det (markerad text)", - "setActorXY": "Sätt skådespelare {0}: {1}", + "setActorX": "Välj skådespelare: {0}", "setNewActor": "Sätt ny skådespelare...", - "setActorSlotXTitle": "Skådespelarplats {0}", + "setStyleX": "Välj stil: {0}", "toggleDialogDashes": "Växla dialogtankstreck", "mergeWithLineBeforeAsDialog": "Slå samman med raden före som dialog", "mergeWithLineAfterAsDialog": "Slå samman med raden efter som dialog", @@ -3648,18 +3963,54 @@ "moveStartOneFrameForwardKeepGapPrev": "Flytta start en bildruta framåt (behåll mellanrum till föregående om nära)", "moveEndOneFrameBackKeepGapNext": "Flytta slut en bildruta bakåt (behåll mellanrum till nästa om nära)", "moveEndOneFrameForwardKeepGapNext": "Flytta slut en bildruta framåt (behåll mellanrum till nästa om nära)", + "moveStartXMsBack": "Flytta start X ms bakåt (X anges i Inställningar, även i Visuell synk)", + "moveStartXMsForward": "Flytta start X ms framåt (X anges i Inställningar, även i Visuell synk)", + "moveEndXMsBack": "Flytta slut X ms bakåt (X anges i Inställningar, även i Visuell synk)", + "moveEndXMsForward": "Flytta slut X ms framåt (X anges i Inställningar, även i Visuell synk)", + "moveSelectedLinesXMsBack": "Flytta markerade rader X ms bakåt (X anges i Inställningar)", + "moveSelectedLinesXMsForward": "Flytta markerade rader X ms framåt (X anges i Inställningar)", + "moveSelectedLinesAndForwardXMsBack": "Flytta markerade rader och alla efterföljande X ms bakåt (X anges i Inställningar)", + "moveSelectedLinesAndForwardXMsForward": "Flytta markerade rader och alla efterföljande X ms framåt (X anges i Inställningar)", + "moveAllLinesXMsBack": "Flytta alla rader X ms bakåt (X anges i Inställningar)", + "moveAllLinesXMsForward": "Flytta alla rader X ms framåt (X anges i Inställningar)", + "moveSelectedLinesCustomMsBack": "Flytta markerade rader, anpassade millisekunder ({0:#,###,##0}) bakåt, {1}", + "moveSelectedLinesCustomMsForward": "Flytta markerade rader, anpassade millisekunder ({0:#,###,##0}) framåt, {1}", + "moveSelectedLinesAndForwardCustomMsBack": "Flytta markerade rader och alla efterföljande, anpassade millisekunder ({0:#,###,##0}) bakåt, {1}", + "moveSelectedLinesAndForwardCustomMsForward": "Flytta markerade rader och alla efterföljande, anpassade millisekunder ({0:#,###,##0}) framåt, {1}", + "moveAllLinesCustomMsBack": "Flytta alla rader, anpassade millisekunder ({0:#,###,##0}) bakåt, {1}", + "moveAllLinesCustomMsForward": "Flytta alla rader, anpassade millisekunder ({0:#,###,##0}) framåt, {1}", "categoryTextBox": "Textruta", "categoryFile": "Arkiv", "categoryVideo": "Video", "categorySync": "Synkronisering", - "categoryTranslate": "Översätt", - "categorySearch": "Sök", - "categoryTools": "Verktyg", "categoryAi": "AI", + "categoryCustom": "Anpassad", + "editCustomShortcut": "Redigera anpassat kortkommando", + "customShortcutTextBoxKeyHint": "Använd en tangent med Ctrl/Alt här - en vanlig tangent skulle inte längre skriva i textrutan.", + "customShortcutNumberX": "Anpassat kortkommando #{0}", + "customShortcutNumberXY": "Anpassat kortkommando #{0}: {1}", + "customShortcutStep": "Steg", + "customShortcutSteps": "Steg", + "customShortcutStepRunCommand": "Kör kommando", + "customShortcutStepInsertText": "Infoga text", + "customShortcutStepReplace": "Sök och ersätt", + "customShortcutInsertAtCursor": "Vid markören", + "customShortcutInsertAtStart": "I början av texten", + "customShortcutInsertAtEnd": "I slutet av texten", + "customShortcutSummaryRunCommandX": "Kör: {0}", + "customShortcutSummaryInsertXY": "Infoga \"{0}\" ({1})", + "customShortcutSummaryReplaceXY": "Ersätt \"{0}\" med \"{1}\"", + "customShortcutSummaryReplaceRegexXY": "Ersätt reguljärt uttryck \"{0}\" med \"{1}\"", + "customShortcutPickCommand": "Välj ett kommando att köra.", + "customShortcutEnterText": "Ange texten att infoga.", + "customShortcutEnterFind": "Ange texten att söka efter.", + "customShortcutInvalidRegexX": "Ogiltigt reguljärt uttryck: {0}", + "customShortcutStepsHint": "Stegen körs uppifrån och ned. Textsteg fungerar på de markerade raderna; kommandon som öppnar ett fönster fortsätter när det stängs.", "activeIn": "Aktiv i", "activeInEverywhere": "Överallt", "moveTextFromCursorToNextAndGoToNext": "Flytta text efter markörposition till nästa undertext och gå till nästa", "moveTextFromCursorToNextAndGoToNextAndPlay": "Flytta text efter markörposition till nästa undertext, gå till nästa och spela", + "moveTextFromCursorToNextAndGoToNextAndPlayAndPause": "Flytta text efter markörpositionen till nästa undertext, gå till nästa, spela den och pausa vid dess slut", "videoGoToPreviousTimeCode": "Gå till föregående tidskod", "videoGoToNextTimeCode": "Gå till nästa tidskod", "listViewColumnDeleteText": "Kolumn, ta bort text", @@ -3669,6 +4020,7 @@ "listViewColumnTextUp": "Kolumn, text upp", "listViewColumnTextDown": "Kolumn, text ner", "autoTranslateSelectedLines": "Översätt markerade rader automatiskt...", + "autoTranslateSelectedLinesNoPrompt": "Auto-översätt markerade rader (ingen fråga, använd senaste motor/språk)", "setAssaResolution": "Ställ in ASSA-upplösning (PlayResX/PlayResY)", "videoOcr": "OCR av inbränd undertext", "waveformInsertNewSelection": "Vågform infoga ny markering" @@ -3676,8 +4028,6 @@ "wordLists": { "title": "Ordlistor", "addName": "Lägg till namn", - "addWord": "Lägg till ord", - "addPair": "Lägg till par", "nameAndIgnoreList": "Namn-/ignorerlista", "userWords": "Användarordlista", "ocrFixList": "OCR-åtgärdslista", @@ -3694,7 +4044,6 @@ "noPluginsInstalled": "Inga plugins installerade", "getPluginsOnline": "Skaffa plugins online...", "getPluginsTitle": "Skaffa plugins", - "enabled": "Aktiverad", "disabled": "Inaktiverad", "notSupportedOnThisOs": "Stöds inte på detta operativsystem", "removePluginXConfirm": "Ta bort plugin '{0}'?\r\nDetta tar bort plugin-mappen:{1}", @@ -3711,17 +4060,19 @@ "downloadingXPercent": "Laddar ned {0} %...", "pluginXInstalled": "'{0}' installerad.", "couldNotInstallPluginX": "Kunde inte installera '{0}': {1}", - "notInstalled": "Ej installerad", "install": "Installera", "reinstall": "Installera om", - "update": "Uppdatera", "installedX": "Installerad ({0})", "updateAvailableXToY": "Uppdatering tillgänglig ({0} → {1})", "applyPluginToWhichLinesX": "Använd '{0}' på:", "applyToSelectedLinesX": "Markerade rader ({0})", "applyToAllLinesX": "Alla rader ({0})", "updateAllXAvailable": "Uppdatera alla ({0})", - "updatingXOfY": "Uppdaterar {0}/{1}..." + "updatingXOfY": "Uppdaterar {0}/{1}...", + "doNotAskAgain": "Fråga inte igen (ändra i Hantera plugins)", + "applyPluginsTo": "Tillämpa plugins på", + "askEachTime": "Fråga varje gång", + "allLines": "Alla rader" }, "help": { "aboutSubtitleEdit": "Om Subtitle Edit", @@ -3760,6 +4111,8 @@ "nOcrDatabase": "nOCR-databas", "drawMode": "Ritläge:", "addNewCharcter": "Lägg till nytt tecken", + "addNOcrCharacterTitle": "Lägg till nOCR-tecken för rad {0}, tecken {1} av {2} med databasen \"{3}\"", + "addBinaryOcrCharacterTitle": "Lägg till binärt OCR-tecken för rad {0}, tecken {1} av {2} med databasen \"{3}\"", "lineIndexX": "Rad {0}", "inspectNOcrAdditions": "Inspektera nya nOCR-tillägg", "ocrSelectedLines": "OCR markerade rader", @@ -3808,6 +4161,8 @@ "binaryImageCompareInspectImageMatches": "\"Binär bildjämförelse\" - Inspektera bildträffar", "saveBlankTextTitle": "Spara tom text?", "saveBlankTextPrompt": "Spara tom text för bild?", + "drawnLinesDoNotMatchTitle": "Linjerna matchar inte bilden", + "drawnLinesDoNotMatchPrompt": "Några av de ritade linjerna matchar inte bilden (gröna linjer måste ligga på tecknet, röda linjer måste ligga utanför det), så det här tecknet kommer inte att kännas igen.\n\nSpara ändå?", "yesAndNeverAskAgain": "Ja och fråga aldrig igen", "importTextFromSubtitleDotDotDot": "Importera text från undertext...", "importTextFromSubtitleOverwritePrompt": "Vissa rader har redan OCR-text. Skriva över befintlig text?", @@ -3833,7 +4188,6 @@ "vobSubColorsTitle": "VobSub/DVD-färger", "vobSubColorsHeader": "Anpassa de fyra VobSub-färgerna", "vobSubColorsDescription": "VobSub/DVD-undertexter använder fyra indexerade färger: bakgrund, mönster, betoning 1 och betoning 2. Välj varje färg nedan för att åsidosätta den inbäddade paletten - användbart när originalfärgerna ger dålig OCR-kontrast.", - "vobSubColorBackground": "Bakgrund", "vobSubColorPattern": "Mönster", "vobSubColorEmphasis1": "Betoning 1", "vobSubColorEmphasis2": "Betoning 2", @@ -3872,9 +4226,26 @@ "crispEmbedSettingsTitle": "CrispEmbed-inställningar", "crispEmbedDescription": "Lokal OCR-motor med flera modellbackender - ladda ner motorn och modellerna här.", "trainNOcrDatabase": "Träna nOCR-databas...", + "optimizeNOcrDatabase": "Optimera nOCR-databas...", + "optimizeNOcrDatabaseQuestionXY": "Ta bort linjerna som inte skiljer tecken från liknande tecken i nOCR-databasen \"{0}\"?\n\nDetta gör databasen mindre och OCR snabbare, oftast med färre fel. Tar upp till en minut.\n\nEn säkerhetskopia sparas som \"{1}\".", + "optimizingNOcrDatabaseXY": "Optimerar nOCR-databas... {0}/{1}", + "nOcrDatabaseOptimizedXYZ": "nOCR-databasen \"{0}\" optimerad: {1:#,##0} -> {2:#,##0} linjer.", "startTraining": "Starta träning", "abortTraining": "Avbryt träning", "trainingOptions": "Träningsalternativ", + "trainNOcrSubtitle": "Renderar tecken i installerade typsnitt och lär nOCR deras former. Välj de typsnitt som dina undertexter använder.", + "xFontsSelected": "{0:#,##0} valda", + "subtitleFonts": "Undertexttypsnitt", + "emptyDatabase": "Tom databas", + "startFromDatabase": "Börja från", + "startFromDatabaseHint": "Lägg till de tränade tecknen i en kopia av en befintlig databas (rekommenderas) eller börja från en tom", + "fontSizes": "Typsnittsstorlekar", + "fontSizesHint": "Typsnittsstorlekar i pixlar att rendera med, separerade med kommatecken, t.ex. \"30, 40\"", + "numberOfLineSegmentsHint": "Linjer som kontrolleras per tecken - fler är striktare men långsammare", + "enterDatabaseName": "Ange ett namn för databasen", + "trainingFontXSizeYCharacterZ": "Tränar \"{0}\" vid {1} px: {2}", + "xLearned": "{0:#,##0} inlärda", + "xSkipped": "{0:#,##0} överhoppade", "charactersToTrain": "Tecken att träna", "letterCombinationsToTrain": "Bokstavskombinationer som kan delas upp i en bild", "importCharactersFromSubtitleFile": "Importera tecken från undertextfil...", @@ -3901,6 +4272,8 @@ "drawZoomOut": "Zooma ut (Ctrl+-)", "drawResetView": "Återställ vy (Ctrl+0)", "drawToggleGrid": "Växla rutnät (Ctrl+G)", + "drawTogglePreview": "Växla förhandsvisning (F9)", + "drawPreviewFailed": "Förhandsvisningen misslyckades - ffmpeg med libass (subtitles-filter) krävs", "drawCopyToClipboard": "Kopiera till urklipp (Ctrl+C)", "drawShapes": "Former", "drawSelectedPoint": "Markerad punkt", @@ -3908,16 +4281,86 @@ "drawSelectedLayer": "Markerat lager", "drawToolX": "Verktyg: {0}", "drawHelpText": "Klicka för att lägga till punkter • Enter/F8 för att stänga form • Skift+Dra för att panorera • Ctrl+Skrolla för att zooma", + "drawMoveToLayer": "Flytta till lager", + "drawFlipHorizontal": "Vänd horisontellt", + "drawFlipVertical": "Vänd vertikalt", + "drawHideShape": "Dölj form", + "drawShowShape": "Visa form", + "drawHideLayer": "Dölj lager", + "drawShowLayer": "Visa lager", + "drawDeleteLayer": "Ta bort lager", + "drawDeletePoint": "Ta bort punkt", + "drawCancelDrawing": "Avbryt ritning", + "drawLayerX": "Lager {0}", + "drawImportSvg": "Importera SVG-bild...", + "drawRotateClockwise": "Rotera 90° medurs", + "drawConvertToCurve": "Konvertera segment till kurva", + "drawBackground": "Bakgrund", + "drawPosition": "Position", + "drawZoom": "Zooma", + "drawShapeTool": "Formverktyg", + "drawColorPickerTool": "Hämta färg från en form eller bakgrunden", + "drawShapeLibrary": "Formbibliotek", + "drawShapeLibraryHint": "Dra på ritytan för att placera en form, eller klicka för standardstorlek. Shift behåller proportionerna.", + "drawAddToLibrary": "Lägg till i formbiblioteket...", + "drawRemoveFromLibrary": "Ta bort från formbiblioteket", + "drawShapeName": "Formnamn", + "drawCategorySpeechBubbles": "Pratbubblor", + "drawCategoryArrows": "Pilar", + "drawCategoryBasicShapes": "Grundformer", + "drawCategorySymbols": "Symboler", + "drawCategoryMyShapes": "Mina former", + "drawShapeSpeechBubble": "Pratbubbla", + "drawShapeSpeechBubbleRight": "Pratbubbla, spets höger", + "drawShapeSpeechBubbleUp": "Pratbubbla, spets upp", + "drawShapeOvalBubble": "Oval pratbubbla", + "drawShapeBoxBubble": "Rektangulär pratbubbla", + "drawShapeThoughtBubble": "Tankebubbla", + "drawShapeShoutBubble": "Ropbubbla", + "drawShapeCaptionBox": "Textruta", + "drawShapeArrow": "Pil", + "drawShapeDoubleArrow": "Dubbelpil", + "drawShapeChevron": "Vinkelpil", + "drawShapeCurvedArrow": "Böjd pil", + "drawShapeRoundedRectangle": "Rundad rektangel", + "drawShapeTriangle": "Triangel", + "drawShapeDiamond": "Romb", + "drawShapePentagon": "Femhörning", + "drawShapeHexagon": "Sexhörning", + "drawShapeOctagon": "Åttahörning", + "drawShapeStar": "Stjärna", + "drawShapeRing": "Ring", + "drawShapePlus": "Plus", + "drawShapeBanner": "Banderoll", + "drawShapeHeart": "Hjärta", + "drawShapeCheck": "Bock", + "drawShapeMusicNote": "Musiknot", + "drawShapeMusicNotes": "Musiknoter", + "drawShapeLightning": "Blixt", + "drawShapeCloud": "Moln", + "drawShapeMoon": "Måne", + "drawShapeBadge": "Märke", + "drawSize": "Storlek", + "drawLayer": "Lager", + "drawBackgroundVideoFrame": "Videobildruta (aktuell position)", + "drawBackgroundVideoFrameAt": "Videobildruta vid...", + "drawBackgroundImage": "Bildfil...", + "drawBackgroundNone": "Ingen bakgrund", + "drawBackgroundStretch": "Sträck ut bilden till ritytan", + "drawBackgroundOpacity": "Bakgrundens opacitet", + "drawBackgroundVideoFrameFailed": "Kunde inte hämta en videobildruta vid {0}.", + "drawImages": "Bilder", + "drawConvertToLine": "Konvertera segment till linje", + "drawConvertShapeToCurves": "Konvertera alla linjer till kurvor", + "drawConvertShapeToLines": "Konvertera alla kurvor till linjer", + "drawRotateCounterClockwise": "Rotera 90° moturs", + "drawSvgImages": "SVG-bilder", + "drawSvgNoShapes": "Inga fyllda former eller streck hittades i SVG-filen.", "progressBarTitle": "ASSA-förloppsindikator", "progressBarSettings": "Förloppsindikator", - "progressBarPosition": "Position", - "progressBarBottom": "Botten", - "progressBarTop": "Topp", "mouseOverColor": "Färg vid muspekare över", "clickedColor": "Färg vid klick", "progressBarForeColor": "Förgrundsfärg", - "progressBarBackColor": "Bakgrundsfärg", - "progressBarStyle": "Stil", "progressBarSquareCorners": "Rätvinkliga hörn", "progressBarRoundedCorners": "Rundade hörn", "progressBarChapters": "Kapitel", @@ -3926,8 +4369,6 @@ "progressBarXAdjustment": "X-justering", "progressBarYAdjustment": "Y-justering", "progressBarTextAlignment": "Textjustering", - "progressBarTakePosFromVideo": "Ta position från video", - "progressBarPreview": "Förhandsvisning", "fontCollectorTitle": "Typsnittssamlare", "fontCollectorUsedIn": "Används i", "fontCollectorFontFiles": "Typsnittsfil(er)", @@ -3974,9 +4415,7 @@ "resolutionResamplerVideoDiffers": "Undertextens upplösning ({0}x{1}) skiljer sig från videons upplösning ({2}x{3}).\nVill du ändra undertexten så att den matchar videon?", "resolutionResamplerAskOnVideoOpen": "Fråga när en video med annan upplösning öppnas", "backgroundBoxGenerator": "Generera bakgrundsruta", - "backgroundBoxPadding": "Utfyllnad", "backgroundBoxFillWidth": "Fyll bredd", - "backgroundBoxBoxColor": "Rutfärg", "backgroundBoxRadius": "Radie", "backgroundBoxCircle": "Cirkel", "backgroundBoxSpikes": "Taggar", @@ -3988,7 +4427,6 @@ "backgroundBoxTornPaperDouble": "Sönderrivet papper (topp & botten)", "backgroundBoxStarburst": "Stjärnexplosion", "backgroundBoxScroll": "Bokrulle / Pergament", - "stylesTitle": "Advanced Sub Station Alpha-stilar", "stylesInFile": "Stilar i fil", "stylesSaved": "Stilar sparade", "stylesTitleX": "Stilar - {0}", @@ -4008,12 +4446,25 @@ "graphics": "Grafik", "copyToStorageStyles": "Kopiera till lagringsstilar", "copyToFileStyles": "Kopiera till filstilar", + "styleXAlreadyExistsInStorage": "En stil med namnet \"{0}\" finns redan i lagringen.\n\nSkriva över den, eller behålla båda (kopian får ett nytt namn)?", + "styleXAlreadyExistsInFile": "En stil med namnet \"{0}\" finns redan i filen.\n\nSkriva över den, eller behålla båda (kopian får ett nytt namn)?", + "styleNameCannotBeEmpty": "Ett stilnamn kan inte vara tomt.", + "styleNameXIsUsedMoreThanOnce": "Stilnamnet \"{0}\" används av mer än en stil i filen.", + "overwrite": "Skriv över", + "keepBoth": "Behåll båda", + "doThisForAllConflictingStyles": "Gör så för alla stilar i konflikt", "setStyleAsDefault": "Sätt stil som standard", "takeUsagesFromDotDotDot": "Ta användningar från...", "noAttachmentsFound": "Inga bilagor hittades i vald ASSA-fil.", "deleteStyleQuestion": "Ta bort stil?", "deleteStylesQuestion": "Ta bort stilar?", + "deleteXStylesQuestion": "Vill du ta bort {0} stilar?", + "deleteXStylesFromStorageQuestion": "Vill du ta bort {0} stilar från lagringen?", + "deleteStyleXFromStorageQuestion": "Vill du ta bort stilen \"{0}\" från lagringen?", + "deleteStyleXFromFileQuestion": "Vill du ta bort stilen \"{0}\" från den aktuella filen?", "openStyleImportFile": "Öppna undertextfil att importera stilar från", + "styleImportFiles": "Stilfiler (.ass, .sty, Subtitle Edit 4-kategorier .template)", + "ssaStyleImportFiles": "Stilfiler (.ssa, .ass, .sty)", "primary": "Primär", "secondary": "Sekundär", "applyOverrideTags": "Tillämpa överstyrningstaggar", @@ -4028,7 +4479,6 @@ "spaceIncrease": "Ökat mellanrum", "popIn": "Pop in", "spinIn": "Snurra in", - "playCurrent": "Spela aktuell", "setPosition": "Sätt position", "scaleX": "Skala X", "scaleY": "Skala Y", @@ -4071,10 +4521,8 @@ "advancedEffectOldMovieDescription": "Simulerar en vintage 35mm filmprojektor med brus, vinjett och repor", "advancedEffectNeonBurst": "Neonexplosion", "advancedEffectNeonBurstDescription": "Högenergetiska 'Shorts'-stiliserade undertexter med neonglödan och pop-animation med skala-in", - "advancedEffectSettings": "Inställningar", "advancedEffectSnowFlakeCount": "Antal flingor", "advancedEffectStarfieldStarCount": "Antal stjärnor", - "advancedEffectStarfieldSpeed": "Hastighet", "advancedEffectGlitch": "Glitch", "advancedEffectGlitchDescription": "Digital glitch med kromatisk aberration, vita blixtar och förvrängningsöverlägg", "advancedEffectBounceIn": "Studsa in", @@ -4083,8 +4531,6 @@ "advancedEffectFirefliesDescription": "Varma glödande gul-bärnstensfärgade prickar driver organiskt över skärmen med pulserande glöd", "advancedEffectMatrix": "Matrix", "advancedEffectMatrixDescription": "Fallande gröna teckenkolumner med undertexttext som byggs upp bokstav för bokstav från regnet", - "advancedEffectAudioBars": "Ljudstaplar (vågform)", - "advancedEffectAudioBarsDescription": "Animerade equalizer-staplar drivna av ljudvågformen — staplar rullar åt vänster som en vågform och pulserar med ljudet", "advancedEffectAudioPulse": "Ljudtextpuls", "advancedEffectAudioPulseDescription": "Undertexttext glöder och pulserar med ljudvolymen — tysta avsnitt visar normal text, höga toppar lägger till en färgad gloria och en lätt skala-upp", "advancedEffectConfetti": "Konfettiexplosion", @@ -4097,7 +4543,6 @@ "advancedEffectFancyKaraokeGlow": "Aktivt ord-glöd", "advancedEffectFancyKaraokeActiveColor": "Aktiv färg", "advancedEffectFancyKaraokeInactiveColor": "Inaktiv färg", - "advancedEffectFancyKaraokeInactiveOpacity": "Inaktiv opacitet (0-255)", "advancedEffectWordSpacingPixels": "Mellanrum (pixlar)", "advancedEffectSlowZoomOut": "Långsam zoom-ut", "advancedEffectSlowZoomOutDescription": "Texten börjar något större och krymper försiktigt till normal storlek under undertextens varaktighet", @@ -4113,7 +4558,6 @@ "advancedEffectSlideInLeftDescription": "Texten glider in från utanför skärmen åt vänster, hålls och avslutas tillbaka åt vänster", "advancedEffectSlideInRight": "Glid in från höger", "advancedEffectSlideInRightDescription": "Texten glider in från utanför skärmen åt höger, hålls och avslutas tillbaka åt höger", - "advancedEffectFadeInOut": "Tona in/ut", "advancedEffectFadeInOutDescription": "Texten tonar in vid starten och tonar ut vid slutet av varje undertext", "advancedEffectWordFlip3D": "3D-ordvändning", "advancedEffectWordFlip3DDescription": "Varje ord viks in med en 3D-vändning och elastisk pop när det uttalas — modern textningsstil för \"Shorts\"", @@ -4123,9 +4567,7 @@ "advancedEffectLowerThirdDescription": "Banderoll med namn och roll nere till vänster med en accentstapel som glider in — intervju-/dokumentärstil (första raden = namn, följande rader = roll)", "advancedEffectLowerThirdAccentColor": "Accentfärg", "overrideTagsHistory": "Historik för överstyrningstaggar", - "category": "Kategori", "allCategories": "[Alla]", - "defaultCategory": "Standard", "newCategory": "Ny kategori...", "renameCategory": "Byt namn på kategori...", "deleteCategory": "Ta bort kategori", diff --git a/src/ui/Assets/Languages/Thai.json b/src/ui/Assets/Languages/Thai.json index 0b568fda0e6..6aad6cda459 100644 --- a/src/ui/Assets/Languages/Thai.json +++ b/src/ui/Assets/Languages/Thai.json @@ -1,12 +1,30 @@ { "title": "Subtitle Edit", - "version": "v5.2.0-rc7", + "version": "v5.3.0", "translatedBy": "Claude (Anthropic)", "cultureName": "th-TH", "general": { "abort": "ยกเลิกกลางคัน", "actor": "นักแสดง", "actors": "นักแสดง", + "setActorDotDotDot": "กำหนดนักแสดง...", + "actorPickerTitle": "กำหนดนักแสดง", + "actorPickerFilterHint": "พิมพ์เพื่อกรอง หรือพิมพ์ชื่อนักแสดงใหม่", + "actorPickerKeysHint": "กดตัวเลขหรือ Enter เพื่อกำหนดนักแสดง Alt+Up/Down เปลี่ยนลำดับ Delete ลบนักแสดง", + "actorPickerNewActorX": "นักแสดงใหม่ \"{0}\"", + "actorPickerLinesSelectedX": "บรรทัดที่เลือก: {0}", + "setStyleDotDotDot": "กำหนดสไตล์...", + "stylePickerTitle": "กำหนดสไตล์", + "stylePickerFilterHint": "พิมพ์เพื่อกรอง หรือพิมพ์ชื่อสไตล์ใหม่", + "stylePickerKeysHint": "กดตัวเลข (1-9, 0) เพื่อกำหนดสไตล์ให้ทุกบรรทัดที่เลือก", + "stylePickerNewStyleX": "สไตล์ใหม่ \"{0}\"", + "stylePickerNewStyleInfo": "กด Enter เพื่อเพิ่มสไตล์นี้ด้วยการตั้งค่าเริ่มต้นและกำหนดให้บรรทัดที่เลือก ใช้ \"จัดการสไตล์...\" เพื่อเปลี่ยนฟอนต์ สี และตำแหน่งภายหลัง", + "stylePickerCurrentStyleX": "สไตล์ปัจจุบัน: {0}", + "stylePickerLinesUsingStyleX": "บรรทัดที่ใช้สไตล์นี้: {0}", + "stylePickerMarginsXYZ": "ซ้าย {0}, ขวา {1}, แนวตั้ง {2}", + "stylePickerSetStyle": "กำหนดสไตล์", + "stylePickerManageStyles": "จัดการสไตล์...", + "stylePickerManageStylesHint": "ปิดหน้าต่างนี้และเปิดตัวจัดการสไตล์เพื่อแก้ไขฟอนต์ สี ตำแหน่ง และอื่นๆ", "add": "เพิ่ม", "addDotDotDot": "เพิ่ม...", "addToNamesListCaseSensitive": "เพิ่มลงในรายชื่อ (ตรงตามตัวพิมพ์ใหญ่-เล็ก)", @@ -15,7 +33,6 @@ "adjustAlpha": "ปรับค่าอัลฟา (ความโปร่งใส)", "adjustDisplayDuration": "ปรับระยะเวลาแสดงผล", "adjustment": "การปรับ", - "advanced": "ขั้นสูง", "advancedSettings": "การตั้งค่าขั้นสูง", "after": "หลัง", "alignment": "การจัดตำแหน่ง", @@ -60,7 +77,6 @@ "bdnXml": "BDN/xml", "bdnXml8Bit": "BDN/xml 8 บิต", "before": "ก่อน", - "beforeX": "ก่อน \"{0}\"", "beginning": "จุดเริ่มต้น", "bluRaySup": "Blu-ray (sup)", "bold": "ตัวหนา", @@ -72,7 +88,6 @@ "bookmarkEdit": "แก้ไขบุ๊กมาร์ก", "bookmarkEditForSelectedLinesX": "แก้ไขบุ๊กมาร์กให้ {0} บรรทัดที่เลือก", "bookmarksList": "แสดงรายการบุ๊กมาร์ก", - "borderColor": "สีเส้นขอบ", "borderStyle": "รูปแบบเส้นขอบ", "bottomCenter": "ล่าง-กลาง", "bottomLeft": "ล่าง-ซ้าย", @@ -99,7 +114,6 @@ "changeCasing": "เปลี่ยนตัวพิมพ์ใหญ่-เล็ก", "changeFormatting": "เปลี่ยนการจัดรูปแบบ", "changeFrameRate": "เปลี่ยนอัตราเฟรม", - "changeLanguageFilter": "เปลี่ยนตัวกรองภาษา...", "changeOnce": "เปลี่ยนครั้งเดียว", "changeSpeed": "เปลี่ยนความเร็ว", "character": "อักขระ", @@ -141,7 +155,6 @@ "conversionCancelledByUser": "ผู้ใช้ยกเลิกการแปลง", "convert": "แปลง", "converted": "แปลงแล้ว", - "convertingDotDotDot": "กำลังแปลง...", "convertingXofYDotDoDot": "กำลังแปลง {0:#,###,##0}/{1:#,###,##0}...", "copy": "คัดลอก", "copyImageToClipboard": "คัดลอกภาพไปยังคลิปบอร์ด", @@ -152,7 +165,6 @@ "couldNotOpenFileXErrorY": "ไม่สามารถเปิดไฟล์ \"{0}\" ข้อผิดพลาด: {1}", "count": "จำนวน", "cps": "อักขระ/วินาที", - "currentSubtitle": "คำบรรยายปัจจุบัน", "cudaRuntime": "CUDA runtime", "currentVideoPosition": "ตำแหน่งวิดีโอปัจจุบัน", "custom": "กำหนดเอง", @@ -212,7 +224,6 @@ "expand": "ขยาย", "export": "ส่งออก", "exportDotDotDot": "ส่งออก...", - "exportToX": "ส่งออกเป็น {0}", "extendAfter": "ขยาย/หดถึงบรรทัดถัดไป", "extendBefore": "ขยาย/หดถึงบรรทัดก่อนหน้า", "extendSelectedLinesToNextShotChangeOrNextSubtitle": "ขยายบรรทัดที่เลือกถึงจุดเปลี่ยนฉากถัดไป (หรือคำบรรยายถัดไป)", @@ -229,7 +240,6 @@ "fileExtension": "นามสกุลไฟล์", "fileName": "ชื่อไฟล์", "fileNameX": "ชื่อไฟล์: {0}", - "fileNameXAndSize": "ชื่อไฟล์: {0} ({1})", "fileSaved": "บันทึกไฟล์แล้ว", "fileSavedToX": "บันทึกไฟล์ไปยัง {0} แล้ว", "fileXAlreadyExists": "มีไฟล์ \"{0}\" อยู่แล้ว", @@ -239,8 +249,6 @@ "find": "ค้นหา", "findNext": "ค้นหาถัดไป", "findTextX": "ค้นหาข้อความ - {0}", - "fiveHundredMilliseconds": "500 มิลลิวินาที", - "fiveSeconds": "ห้าวินาที", "fix": "แก้ไข", "fixCommonErrors": "แก้ไขข้อผิดพลาดทั่วไป", "fixRightToLeft": "แก้ไขข้อความขวาไปซ้าย", @@ -264,14 +272,12 @@ "foundXInLineYZ": "พบ \"{0}\" ในบรรทัด {1} ตำแหน่ง {2}", "foundXMatches": "พบ {0} รายการที่ตรงกัน", "frameRate": "อัตราเฟรม", - "frameRateX": "อัตราเฟรม: {0:0.0###}", "frames": "เฟรม", "from": "จาก", "fromCurrentVideoPosition": "จากตำแหน่งวิดีโอปัจจุบัน", "fullScreen": "เต็มหน้าจอ", "gap": "ช่องว่าง", "general": "ทั่วไป", - "generalText": "ทั่วไป", "generate": "สร้าง", "generateImportShotChanges": "สร้าง/นำเข้าจุดเปลี่ยนฉาก", "generating": "กำลังสร้าง...", @@ -289,12 +295,8 @@ "help": "ช่วยเหลือ", "hex": "เลขฐานสิบหก", "hide": "ซ่อน", - "hidePreview": "ซ่อนตัวอย่าง", "history": "ประวัติ", "horizontal": "แนวนอน", - "hourMinutesSecondsDecimalSeparatorMilliseconds": "ชม.:นาที:วินาที{0}มิลลิวินาที", - "hourMinutesSecondsFrames": "ชม.:นาที:วินาที:เฟรม", - "ip": "IP", "id": "ID", "image": "ภาพ", "imageSaved": "บันทึกภาพแล้ว", @@ -355,6 +357,7 @@ "mergeSelectedLinesDialog": "รวมบรรทัดที่เลือกเป็นบทสนทนา", "mergeWithLineAfterAndAutoBreak": "รวมกับบรรทัดถัดไปและตัดบรรทัดอัตโนมัติ", "mergeWithLineAfterKeepBreaks": "รวมกับบรรทัดถัดไป (คงการตัดบรรทัด)", + "mergeWithLineAfterAndUnbreak": "รวมกับบรรทัดถัดไปและรวมเป็นบรรทัดเดียว", "mergeWithLineBeforeAndAutoBreak": "รวมกับบรรทัดก่อนหน้าและตัดบรรทัดอัตโนมัติ", "mergeWithLineBeforeKeepBreaks": "รวมกับบรรทัดก่อนหน้า (คงการตัดบรรทัด)", "middleCenter": "กลาง-กลาง", @@ -369,13 +372,13 @@ "moveAllShotChangeOneFrameForward": "เลื่อนจุดเปลี่ยนฉากทั้งหมดไปข้างหน้าหนึ่งเฟรม", "multiMode": "โหมดหลายรายการ", "moveDown": "เลื่อนลง", + "increase": "เพิ่ม", + "decrease": "ลด", "moveUp": "เลื่อนขึ้น", "moveToTop": "ย้ายไปบนสุด", "moveToBottom": "ย้ายไปล่างสุด", "multipleReplace": "แทนที่หลายรายการ", - "mute": "ปิดเสียง", "name": "ชื่อ", - "negative": "ค่าลบ", "new": "ใหม่", "newDotDotDot": "ใหม่...", "newProfile": "โปรไฟล์ใหม่", @@ -405,20 +408,15 @@ "offsetY": "ออฟเซ็ต Y", "ok": "ตกลง", "oneFile": "ไฟล์เดียว", - "oneHundredMilliseconds": "100 มิลลิวินาที", - "oneSecond": "หนึ่งวินาที", "oneSecondBack": "ถอยหลังหนึ่งวินาที", "oneSecondForward": "เดินหน้าหนึ่งวินาที", - "onlineVideoFeatureNotAvailable": "ฟีเจอร์นี้ใช้ไม่ได้กับวิดีโอออนไลน์", "openContainingFolder": "เปิดโฟลเดอร์ที่เก็บไฟล์", "openDictionaryFolder": "เปิดโฟลเดอร์พจนานุกรม", "openFile": "เปิดไฟล์", "openImageBasedSubtitle": "เปิดคำบรรยายแบบภาพ", "openImageFile": "เปิดไฟล์ภาพ", - "openOriginalSubtitleFile": "เปิดไฟล์คำบรรยายต้นฉบับ...", "openOriginalSubtitleFileTitle": "เปิดไฟล์คำบรรยายต้นฉบับ", "openOutputFolder": "เปิดโฟลเดอร์ผลลัพธ์", - "openSubtitle": "เปิดคำบรรยาย...", "openSubtitleFileTitle": "เปิดไฟล์คำบรรยาย", "openSubtitles": "เปิดคำบรรยาย...", "openVideoFile": "เปิดไฟล์วิดีโอ...", @@ -433,11 +431,6 @@ "outlineWidth": "ความหนาเส้นขอบตัวอักษร", "outputFolder": "โฟลเดอร์ผลลัพธ์", "outputProperties": "คุณสมบัติผลลัพธ์", - "overlap": "การซ้อนทับ", - "overlapNextX": "ซ้อนทับบรรทัดถัดไป ({0:#;##0.###})", - "overlapPreviousLineX": "ซ้อนทับบรรทัดก่อนหน้า ({0:#;##0.###})", - "overlapStartAndEnd": "ซ้อนทับจุดเริ่มต้นและจุดสิ้นสุด", - "overlapX": "ซ้อนทับ ({0:#;##0.###})", "overwriteExistingFiles": "เขียนทับไฟล์ที่มีอยู่", "overwriteFilesInFolderX": "เขียนทับไฟล์ในโฟลเดอร์: \"{0}\"", "overwriteQuestion": "เขียนทับ?", @@ -450,6 +443,7 @@ "percent": "เปอร์เซ็นต์", "pickLayer": "กำหนดเลเยอร์", "pickOllamaModel": "เลือกโมเดล Ollama", + "pickModel": "เลือกโมเดล", "llamaCppUseRemoteServer": "ใช้เซิร์ฟเวอร์ภายนอก (URL)", "llamaCppEngineSettings": "การตั้งค่าเอนจิน llama.cpp", "llamaCppEngineSettingsSubtitle": "เซิร์ฟเวอร์ llama.cpp แบบโลคัล", @@ -501,7 +495,6 @@ "refresh": "รีเฟรช", "region": "ภูมิภาค", "regularExpression": "นิพจน์ปรกติ (regex)", - "regularExpressionIsNotValid": "นิพจน์ปรกติไม่ถูกต้อง!", "relativeToCurrentVideoPosition": "สัมพัทธ์กับตำแหน่งวิดีโอปัจจุบัน", "remove": "ลบออก", "removeAlignment": "ลบการจัดตำแหน่ง", @@ -527,7 +520,7 @@ "reset": "รีเซ็ต", "resizePanels": "ปรับขนาดแผง", "resolution": "ความละเอียด", - "resolutionX": "ความละเอียด: {0}", + "retry": "ลองอีกครั้ง", "reverseRightToLeftStartEnd": "สลับจุดเริ่มต้น/จุดสิ้นสุดแบบ RTL", "right": "ขวา", "rightToLeft": "ขวาไปซ้าย", @@ -567,8 +560,6 @@ "selectedlinesX": "บรรทัดที่เลือก: {0}", "sensitivity": "ความไว", "separator": "ตัวคั่น", - "sessionKey": "คีย์เซสชัน", - "sessionKeyGenerate": "สร้างคีย์ใหม่", "setEnd": "กำหนดจุดสิ้นสุด", "setEndAndGoToNext": "กำหนดจุดสิ้นสุดแล้วไปบรรทัดถัดไป", "setEndAddNewAndGoToNew": "กำหนดจุดสิ้นสุด เพิ่มบรรทัดใหม่ แล้วไปบรรทัดใหม่", @@ -591,8 +582,9 @@ "shadowColor": "สีเงา", "shadowWidth": "ความกว้างเงา", "shiftLineBy": "เลื่อนบรรทัดไป", + "shotIn": "ช็อตเข้า", + "shotOut": "ช็อตออก", "shortcut": "ปุ่มลัด", - "shortcutX": "ปุ่มลัด: {0}", "shortcuts": "ปุ่มลัด", "show": "แสดง", "showActorColumn": "แสดงคอลัมน์ \"นักแสดง\"", @@ -603,17 +595,19 @@ "showStartColumn": "แสดงคอลัมน์ \"เริ่ม\"", "showHideColumn": "แสดงคอลัมน์ \"สิ้นสุด\"", "showHistory": "แสดงประวัติ", + "clearHistory": "ล้างประวัติ", + "removeFromHistory": "ลบออกจากประวัติ", "showLayerColumn": "แสดงคอลัมน์ \"เลเยอร์\"", "showPreview": "แสดงตัวอย่าง", "showShotChangesList": "แสดงรายการจุดเปลี่ยนฉาก", + "showShotInColumn": "แสดงคอลัมน์ \"ช็อตเข้า\"", + "showShotOutColumn": "แสดงคอลัมน์ \"ช็อตออก\"", "showStyleColumn": "แสดงคอลัมน์ \"สไตล์\"", "showTeletext": "แสดงคอลัมน์เทเลเท็กซ์ในมุมมองรายการ", "showTimeCodes": "แสดงไทม์โค้ด", "showWpmColumn": "แสดงคอลัมน์ \"คำ/นาที\"", "showPixelWidthColumn": "แสดงคอลัมน์ \"ความกว้างพิกเซล\"", "shrink": "หด", - "singleBox": "กล่องเดียว", - "singleLineLengths": "ความยาวต่อบรรทัด:", "singleMode": "โหมดรายการเดียว", "textTotalLength": "ข้อความ - ความยาวรวม", "textSingleLineMaxLength": "ข้อความ - ความยาวสูงสุดต่อบรรทัด", @@ -662,16 +656,16 @@ "strikeout": "ขีดฆ่า", "style": "สไตล์", "styleExaggeration": "การเน้นสไตล์", - "styleLanguage": "สไตล์ / ภาษา", "styles": "สไตล์", "subtitleFile": "ไฟล์คำบรรยาย", + "subtitleFileChangedOutsideX": "ไฟล์ \"{0}\" ถูกเปลี่ยนแปลงภายนอก Subtitle Edit\n\nโหลดใหม่และละทิ้งการเปลี่ยนแปลงของคุณหรือไม่?", "subtitleFileName": "ชื่อไฟล์คำบรรยาย", "subtitleFileSaved": "บันทึกไฟล์คำบรรยายแล้ว", "subtitleFileSavedToX": "บันทึกไฟล์คำบรรยายไปที่ {0} แล้ว", "subtitleFiles": "ไฟล์คำบรรยาย", "subtitleFormats": "รูปแบบคำบรรยาย", "subtitleLoadedX": "โหลดคำบรรยายแล้ว: {0}", - "subtitleXOfY": "คำบรรยาย {0} จาก {1}", + "subtitleReloadedChangedOutsideX": "โหลดคำบรรยายใหม่แล้ว (ถูกเปลี่ยนแปลงภายนอก Subtitle Edit): {0}", "suffix": "คำต่อท้าย", "suggestions": "คำแนะนำ", "sync": "ซิงค์", @@ -683,7 +677,6 @@ "teletextColor": "สีเทเลเท็กซ์", "teletextLine": "บรรทัดเทเลเท็กซ์", "tenHours": "10 ชั่วโมง", - "tenMilliseconds": "10 มิลลิวินาที", "text": "ข้อความ", "textColor": "สีข้อความ", "textFiles": "ไฟล์ข้อความ", @@ -710,9 +703,6 @@ "topRight": "บน-ขวา", "top": "บน", "totalAdjustmentX": "การปรับทั้งหมด: {0}", - "totalFramesX": "จำนวนเฟรมทั้งหมด: {0:#;##0.##}", - "totalLengthX": "ความยาวรวม: {0}", - "totalLengthXSplitLine": "ความยาวรวม: {0} (แยกบรรทัด!)", "translate": "แปล", "translatePercentX": "กำลังแปล: {0}%", "translateRow": "แปลแถวนี้", @@ -721,6 +711,7 @@ "type": "ประเภท", "unbreak": "รวมข้อความเป็นบรรทัดเดียว", "unbreakLines": "รวมบรรทัด", + "undelete": "เลิกลบ", "underline": "ขีดเส้นใต้", "undo": "เลิกทำ", "unknown": "ไม่ทราบ", @@ -740,13 +731,11 @@ "usages": "การใช้งาน", "use": "ใช้", "useAlways": "ใช้เสมอ", - "useLargerFontForThisWindow": "ใช้ฟอนต์ขนาดใหญ่ขึ้นสำหรับหน้าต่างนี้", "useOnce": "ใช้ครั้งเดียว", "useOutputFolder": "ใช้โฟลเดอร์ผลลัพธ์", "useSourceFolder": "ใช้โฟลเดอร์ต้นทาง", "useSourceResolution": "ใช้ความละเอียดต้นฉบับ", "userName": "ชื่อผู้ใช้", - "userNameAlreadyInUse": "ชื่อผู้ใช้นี้ถูกใช้แล้ว", "vertical": "แนวตั้ง", "video100MsBack": "วิดีโอ ถอยหลัง 100 มิลลิวินาที", "video100MsForward": "วิดีโอ เดินหน้า 100 มิลลิวินาที", @@ -760,14 +749,25 @@ "videoCustom3ForwardX": "วิดีโอ เดินหน้าตามมิลลิวินาทีกำหนดเอง ({0:#,###,##0}), 3", "videoCustom4BackX": "วิดีโอ ถอยหลังตามมิลลิวินาทีกำหนดเอง ({0:#,###,##0}), 4", "videoCustom4ForwardX": "วิดีโอ เดินหน้าตามมิลลิวินาทีกำหนดเอง ({0:#,###,##0}), 4", - "videoEncodingX": "การเข้ารหัสวิดีโอ: {0}", + "videoCustom1BackAndPauseX": "วิดีโอ ถอยหลังตามมิลลิวินาทีที่กำหนดเอง ({0:#,###,##0}) และหยุดชั่วคราว 1", + "videoCustom1ForwardAndPauseX": "วิดีโอ เดินหน้าตามมิลลิวินาทีที่กำหนดเอง ({0:#,###,##0}) และหยุดชั่วคราว 1", + "videoCustom2BackAndPauseX": "วิดีโอ ถอยหลังตามมิลลิวินาทีที่กำหนดเอง ({0:#,###,##0}) และหยุดชั่วคราว 2", + "videoCustom2ForwardAndPauseX": "วิดีโอ เดินหน้าตามมิลลิวินาทีที่กำหนดเอง ({0:#,###,##0}) และหยุดชั่วคราว 2", + "videoCustom3BackAndPauseX": "วิดีโอ ถอยหลังตามมิลลิวินาทีที่กำหนดเอง ({0:#,###,##0}) และหยุดชั่วคราว 3", + "videoCustom3ForwardAndPauseX": "วิดีโอ เดินหน้าตามมิลลิวินาทีที่กำหนดเอง ({0:#,###,##0}) และหยุดชั่วคราว 3", + "videoCustom4BackAndPauseX": "วิดีโอ ถอยหลังตามมิลลิวินาทีที่กำหนดเอง ({0:#,###,##0}) และหยุดชั่วคราว 4", + "videoCustom4ForwardAndPauseX": "วิดีโอ เดินหน้าตามมิลลิวินาทีที่กำหนดเอง ({0:#,###,##0}) และหยุดชั่วคราว 4", "videoExtension": "นามสกุลไฟล์วิดีโอ", "videoFile": "ไฟล์วิดีโอ", "videoFileGenerated": "สร้างไฟล์วิดีโอแล้ว", "videoFileGeneratedX": "สร้างไฟล์วิดีโอแล้ว: \"{0}\"", + "videoFilesGenerated": "สร้างไฟล์วิดีโอแล้ว", + "xVideosGenerated": "สร้างวิดีโอแล้ว {0} ไฟล์", + "xOfYVideosGenerated": "สร้างวิดีโอแล้ว {0} จาก {1} ไฟล์", + "savedInX": "บันทึกไว้ใน {0}", + "savedInXFolders": "บันทึกไว้ใน {0} โฟลเดอร์", "videoFiles": "ไฟล์วิดีโอ", "videoAndAudioFiles": "ไฟล์วิดีโอและเสียง", - "videoInformation": "ข้อมูลวิดีโอ", "videoOffset": "ออฟเซ็ตวิดีโอ", "videoOneFrameBack": "วิดีโอ ถอยหลังหนึ่งเฟรม", "videoOneFrameForward": "วิดีโอ เดินหน้าหนึ่งเฟรม", @@ -778,7 +778,6 @@ "videoPlayer": "โปรแกรมเล่นวิดีโอ", "videoPosition": "ตำแหน่งวิดีโอ", "videoResolution": "ความละเอียดวิดีโอ", - "viewX": "มุมมอง {0}", "visible": "มองเห็นได้", "voice": "เสียงพูด", "volume": "ระดับเสียง", @@ -787,9 +786,7 @@ "waveformCenterOnVideoPosition": "จัดกึ่งกลางคลื่นเสียงตามตำแหน่งวิดีโอ", "waveformPasteFromClipboard": "วางจากคลิปบอร์ด", "waveformSpectrogram": "คลื่นเสียง/สเปกโตรแกรม", - "webServiceUrl": "URL ของเว็บเซอร์วิส", "width": "ความกว้าง", - "windowPositionAndSize": "ตำแหน่งและขนาดหน้าต่าง", "wordsPerMin": "คำ/นาที", "pixelWidth": "ความกว้างเป็นพิกเซล", "wpm": "คำ/นาที", @@ -803,8 +800,6 @@ "noBookmarksFound": "ไม่พบบุ๊กมาร์กเพิ่มเติม", "xRequiresAnApiKey": "{0} ต้องใช้คีย์ API", "xRequiresAValidUrl": "{0} ต้องใช้ URL ที่ถูกต้อง (เช่น \"http://...\" หรือ \"https://...\")", - "xSeconds": "{0:0.0##} วินาที", - "xSubtitles": "{0:#,###,##0} คำบรรยาย", "yes": "ใช่", "zeroBytes": "0 ไบต์", "copyToClipboard": "คัดลอกไปยังคลิปบอร์ด", @@ -826,10 +821,8 @@ "playbackSpeed": "ความเร็วการเล่น", "hoursMinutesSecondsMilliseconds": "HH:MM:SS.FFF", "imageFiles": "ไฟล์ภาพ", - "openAiCompatibleStt": "STT ที่เข้ากันได้กับ OpenAI", "openAiCompatibleSttEndpoint": "URL ปลายทาง STT", "openAiCompatibleSttApiKey": "คีย์ API", - "openAiCompatibleSttModel": "โมเดล", "openAiCompatibleSttExtraHeaders": "ส่วนหัวเพิ่มเติม", "openAiCompatibleSttTimeout": "หมดเวลา (วินาที)", "openAiCompatibleSttLanguage": "ภาษาที่คาดไว้", @@ -839,6 +832,9 @@ "dashScopeSttRegion": "ภูมิภาค", "dashScopeSttEnableWords": "ประทับเวลาระดับคำ", "dashScopeSttRegionKeyHint": "หมายเหตุ: คีย์ API ของ Alibaba Cloud Model Studio ผูกกับภูมิภาค - ตรวจสอบให้แน่ใจว่าภูมิภาคที่เลือกตรงกับภูมิภาคที่สร้างคีย์ API (จีน หรือ นานาชาติ)", + "googleCloudSttProjectId": "Project ID (เฉพาะ Application Default Credentials)", + "googleCloudSttBucketName": "Storage bucket (ว่าง = อัตโนมัติ)", + "googleCloudSttDynamicBatching": "Dynamic batching (ถูกกว่าประมาณ 5 เท่า ไม่รับประกันความหน่วง)", "onlineSttApiKeyMissing": "ต้องใช้คีย์ API กรุณาป้อนคีย์ API ของคุณแล้วลองอีกครั้ง", "openAiCompatibleSttAutoTranscribeOnAudioSelection": "ถอดเสียงส่วนที่เลือกใหม่บนคลื่นเสียงโดยอัตโนมัติผ่านการแปลงเสียงเป็นข้อความ", "openAiCompatibleSttStream": "สตรีมการตอบกลับ", @@ -848,11 +844,9 @@ "configurationRequired": "ต้องตั้งค่าก่อน", "transcriptionError": "ข้อผิดพลาดในการถอดเสียง", "transcriptionComplete": "ถอดเสียงเสร็จสมบูรณ์", - "transcribingWithOpenAi": "กำลังถอดเสียงด้วยบริการ OpenAI...", "processingResponse": "กำลังประมวลผลการตอบกลับ...", "unauthorizedApiKey": "ไม่ได้รับอนุญาต: คีย์ API ไม่ถูกต้องหรือสิทธิ์ไม่เพียงพอ", "requestTimeout": "คำขอหมดเวลา: เซิร์ฟเวอร์ใช้เวลาตอบกลับนานเกินไป", - "noSegmentsReturned": "บริการถอดเสียงไม่ส่งเซกเมนต์ใดกลับมา", "openAiCompatibleSttUrlMissing": "ต้องระบุ URL ปลายทางของ STT ที่เข้ากันได้กับ OpenAI กรุณาป้อน URL แล้วลองอีกครั้ง", "openAiCompatibleSttUrlNotResponding": "ปลายทาง STT ที่เข้ากันได้กับ OpenAI ไม่ตอบสนอง:\n{0}\n\nกรุณาตรวจสอบ URL และตรวจสอบว่าเซิร์ฟเวอร์กำลังทำงานอยู่", "transcriptionFailed": "การถอดเสียงล้มเหลว", @@ -898,14 +892,15 @@ "multipleReplace": "แทนที่หลายรายการ...", "fillSelectedLinesWithClipboard": "เติมบรรทัดที่เลือกด้วยข้อความจากคลิปบอร์ด", "goToLineNumber": "ไปยังบรรทัดที่...", + "commandPalette": "แผงคำสั่ง...", "rightToLeftMode": "โหมดขวาไปซ้าย", "modifySelectionDotDotDot": "ปรับเปลี่ยนการเลือก...", "tools": "เครื่องมือ", - "toolsSelectedLines": "เครื่องมือ (บรรทัดที่เลือก)", "adjustDurations": "ปรับระยะเวลา...", "applyDurationLimits": "ใช้ขีดจำกัดระยะเวลา...", "batchConvert": "แปลงเป็นชุด...", "beautifyTimeCodes": "จัดรหัสเวลาให้สวยงาม...", + "improveTimeCodes": "ปรับปรุงไทม์โค้ด (forced alignment)...", "bridgeGaps": "เชื่อมช่องว่าง...", "applyMinGap": "ใช้ช่องว่างขั้นต่ำระหว่างคำบรรยาย...", "changeCasing": "เปลี่ยนตัวพิมพ์ใหญ่-เล็ก...", @@ -1044,6 +1039,9 @@ "selectCurrentLineWhilePlayingHint": "เลือกคำบรรยายปัจจุบันขณะเล่น {0}", "videoPosition": "ตำแหน่งวิดีโอ {0}", "videoPositionTextBox": "กล่องข้อความตำแหน่งวิดีโอ {0}", + "toolbarLineBreakX": "ขึ้นบรรทัดใหม่ {0} (ส่วนที่เหลือของแถบเครื่องมือจะต่อในแถวใหม่)", + "initialText": "ข้อความเริ่มต้นของบรรทัดที่เลือก", + "initialTextHint": "ข้อความของบรรทัดที่เลือกตามที่เป็นอยู่ตอนที่เลือกบรรทัดนั้น", "hideWaveformToolbar": "ซ่อนแถบเครื่องมือ {0}", "resetZoomAndSpeed": "รีเซ็ตการซูมและความเร็วการเล่น {0}", "removeBlankLines": "ลบบรรทัดว่าง {0}", @@ -1057,6 +1055,15 @@ "textPlayHint": "เล่นคำบรรยายปัจจุบันและหยุดเมื่อจบ {0}", "textPauseHint": "หยุดการเล่นชั่วคราว {0}", "textNextHint": "เล่นคำบรรยายถัดไปและหยุดเมื่อจบ {0}", + "moveSelectedLines": "ย้ายบรรทัดที่เลือก (ปุ่มถอยหลัง/เดินหน้า)", + "moveSelectedLinesAndFollowing": "ย้ายบรรทัดที่เลือกและบรรทัดถัดไปทั้งหมด (ปุ่มถอยหลัง/เดินหน้า)", + "moveAllLines": "ย้ายทุกบรรทัด (ปุ่มถอยหลัง/เดินหน้า)", + "moveSelectedLinesBackHint": "ย้ายบรรทัดที่เลือกถอยหลัง {0} ms {1}", + "moveSelectedLinesForwardHint": "ย้ายบรรทัดที่เลือกเดินหน้า {0} ms {1}", + "moveSelectedLinesAndFollowingBackHint": "ย้ายบรรทัดที่เลือกและบรรทัดถัดไปทั้งหมดถอยหลัง {0} ms {1}", + "moveSelectedLinesAndFollowingForwardHint": "ย้ายบรรทัดที่เลือกและบรรทัดถัดไปทั้งหมดเดินหน้า {0} ms {1}", + "moveAllLinesBackHint": "ย้ายทุกบรรทัดถอยหลัง {0} ms {1}", + "moveAllLinesForwardHint": "ย้ายทุกบรรทัดเดินหน้า {0} ms {1}", "guessStartNoLineSelected": "เดาจุดเริ่มต้น: ไม่ได้เลือกบรรทัด", "guessStartTimeCodesLocked": "เดาจุดเริ่มต้น: ไทม์โค้ดถูกล็อกอยู่", "guessStartNoWaveform": "เดาจุดเริ่มต้น: ไม่มีคลื่นเสียง", @@ -1078,12 +1085,15 @@ "charactersPerSecond": "อักขระ/วินาที: {0}", "chooseColumn": "เลือกคอลัมน์", "columnPaste": "วางแบบคอลัมน์", + "commandPaletteTitle": "แผงคำสั่ง", + "commandPaletteSearchPlaceholder": "พิมพ์เพื่อค้นหาคำสั่ง...", + "commandPaletteNoCommandsFound": "ไม่พบคำสั่งที่ตรงกัน", "createdEmptyTranslation": "สร้างคำแปลว่างจากคำบรรยายปัจจุบันแล้ว", "deleteText": "ลบข้อความ", "deleteTextAndShiftCellsUp": "ลบข้อความและเลื่อนเซลล์ขึ้น", "endTimeMustBeAfterStartTime": "เวลาสิ้นสุดต้องอยู่หลังเวลาเริ่มต้น", "errorLoad7Zip": "ไฟล์นี้ดูเหมือนจะเป็นไฟล์บีบอัด 7-Zip\n\nSubtitle Edit ไม่สามารถเปิดไฟล์บีบอัดได้", - "errorLoadBinaryZeroes": "ขออภัย ไฟล์นี้มีแต่ศูนย์ไบนารีเท่านั้น!\n\nหากคุณเคยแก้ไขไฟล์นี้ด้วย Subtitle Edit คุณอาจหาข้อมูลสำรองได้จากเมนู ไฟล์ -> กู้คืนข้อมูลสำรองอัตโนมัติ...", + "errorLoadBinaryZeroes": "ขออภัย ไฟล์นี้มีแต่ศูนย์ไบนารีเท่านั้น!\n\nหากคุณเคยแก้ไขไฟล์นี้ด้วย Subtitle Edit คุณอาจหาข้อมูลสำรองได้จากเมนู ไฟล์ -> กู้คืนข้อมูลสำรองอัตโนมัติ...", "errorLoadGZip": "ไฟล์นี้ดูเหมือนจะเป็นไฟล์บีบอัด GZip\n\nSubtitle Edit ไม่สามารถเปิดไฟล์บีบอัดได้", "errorLoadLargeFile": "ไฟล์นี้ใหญ่เกินกว่าจะเปิดเป็นไฟล์คำบรรยายได้", "errorLoadVideoFilePrompt": "ไฟล์นี้ดูเหมือนจะเป็นไฟล์วิดีโอ:\n{0}\n\nคุณสามารถเปิดวิดีโอผ่าน \"วิดีโอ -> เปิดวิดีโอ\" หรือลากแล้ววางวิดีโอลงบนโปรแกรมเล่นวิดีโอ\n\nต้องการเปิดเป็นไฟล์วิดีโอหรือไม่?", @@ -1116,18 +1126,20 @@ "numberOfLinesEvenlyDistributedX": "กระจาย {0} บรรทัดอย่างสม่ำเสมอแล้ว", "oneLineCopiedFromOriginal": "คัดลอกหนึ่งบรรทัดจากคำบรรยายต้นฉบับแล้ว", "oneLineCopiedToOriginal": "คัดลอกหนึ่งบรรทัดไปยังคำบรรยายต้นฉบับแล้ว", + "onlineOnlyVideo": "วิดีโอที่อยู่บนออนไลน์เท่านั้น", + "onlineOnlyVideoXSizeYDownloadAndOpen": "ไฟล์วิดีโอ \"{0}\" ({1}) ถูกจัดเก็บไว้บนออนไลน์เท่านั้น เช่น ใน Dropbox หรือ iCloud Drive และต้องดาวน์โหลดก่อนจึงจะเปิดได้\n\nดาวน์โหลดและเปิดตอนนี้หรือไม่?", "oneLineMerged": "รวมหนึ่งบรรทัดแล้ว", "oneLineSwitched": "สลับหนึ่งบรรทัดแล้ว", "overwriteExistingCells": "เขียนทับเซลล์ที่มีอยู่", "overwriteOrShiftCellsDown": "เขียนทับ/เลื่อนเซลล์ลง", "parsingMatroskaFile": "กำลังแยกวิเคราะห์ไฟล์ Matroska...", + "readingDvdSubtitles": "กำลังอ่านคำบรรยาย DVD...", "pasteFromClipboardDotDotDot": "วางจากคลิปบอร์ด...", "redoPerformed": "ทำซ้ำแล้ว", "redoPerformedXActionLeft": "ทำซ้ำแล้ว (การกระทำที่เหลือ: {0})", "removedUnicodeControlCharactersX": "ลบอักขระควบคุม Unicode ออกจาก {0} บรรทัดแล้ว", "removedXBlankLines": "ลบบรรทัดว่าง {0} บรรทัดแล้ว", "replacedXWithYCountZ": "แทนที่ \"{0}\" ด้วย \"{1}\" แล้ว ({2} ตำแหน่ง)", - "replacedXWithYInLineZ": "แทนที่ \"{0}\" ด้วย \"{1}\" ในบรรทัด {2} แล้ว", "reversedStartAndEndingsForRightToLeftX": "กลับต้นและท้ายสำหรับข้อความขวาไปซ้ายใน {0} บรรทัดแล้ว", "ruleProfileIsX": "โปรไฟล์กฎตอนนี้คือ \"{0}\"", "saveLanguageFile": "บันทึกไฟล์ภาษา", @@ -1135,7 +1147,6 @@ "shiftTextCellsDown": "เลื่อนเซลล์ข้อความลง", "singleLineLength": "ความยาวบรรทัด: ", "speedIsNowX": "ความเร็วตอนนี้คือ \"{0}\"", - "spellCheckResult": "ตรวจสอบการสะกดเสร็จสมบูรณ์ \n\n• คำที่เปลี่ยน: {0}\n• คำที่ข้าม: {1}", "subtitleGridFormattingX": "การจัดรูปแบบตาราง: {0}", "subtitleImportedFromMatroskaFile": "นำเข้าคำบรรยายจากไฟล์ Matroska แล้ว", "textDown": "เลื่อนข้อความลง", @@ -1162,13 +1173,14 @@ "trimmedXLines": "ตัดช่องว่างในคำบรรยาย {0} บรรทัดแล้ว", "pastedXLinesOverSelectedLines": "วาง {0} บรรทัดทับบรรทัดที่เลือกแล้ว", "pastedXOfYLinesOverSelectedLines": "วาง {0} จาก {1} บรรทัดทับบรรทัดที่เลือกแล้ว - ส่วนที่เหลือไม่พอดีกับส่วนที่เลือก", + "insertedXLinesFromClipboard": "แทรก {0} บรรทัดจากคลิปบอร์ดแล้ว", "openOriginalDifferentNumberOfSubtitlesXY": "ไฟล์คำบรรยายต้นฉบับมีจำนวนคำบรรยายไม่เท่ากับไฟล์คำบรรยายปัจจุบัน\n\n• คำบรรยายต้นฉบับ: {0}\n• คำบรรยายปัจจุบัน: {1}", - "importXMatchingOriginalLines": "นำเข้าคำบรรยายต้นฉบับที่ตรงกัน {0} รายการหรือไม่?", "originalTextReadOnly": "ข้อความต้นฉบับ (อ่านอย่างเดียว)", "originalTextEditMode": "ข้อความต้นฉบับ (โหมดแก้ไข)", "originalIsReadOnlyNotSaved": "คำบรรยายต้นฉบับเป็นข้อมูลอ้างอิงแบบอ่านอย่างเดียวและไม่ได้ถูกบันทึก", "originalIsEmptyNotSaved": "คำบรรยายต้นฉบับไม่มีข้อความเลยและไม่ได้ถูกบันทึก", "originalIsReadOnlyReference": "คำบรรยายต้นฉบับเปิดอยู่เป็นข้อมูลอ้างอิงแบบอ่านอย่างเดียว", + "closeTranslationQuestionXY": "ปิดคำแปล \"{0}\" หรือไม่?\n\nคำบรรยายต้นฉบับ \"{1}\" จะกลายเป็นคำบรรยายที่คุณแก้ไข ตัวไฟล์คำแปลจะไม่ถูกเปลี่ยนแปลง", "allowEditOfOriginalSubtitle": "อนุญาตให้แก้ไขคำบรรยายต้นฉบับ", "showAllOriginalLinesX": "แสดงบรรทัดต้นฉบับทั้งหมด ({0} บรรทัดไม่มีคู่ที่ตรงกันที่นี่)", "showAllOriginalLinesHint": "บรรทัดที่ไม่มีคู่ที่ตรงกันจะแสดงเป็นแถวเพิ่มเติม เพื่อให้เห็นว่าคำแปลขาดอะไรไป", @@ -1203,6 +1215,7 @@ "fileExportedInFormatXToY": "ส่งออกไฟล์ในรูปแบบ {0} ไปที่ {1} แล้ว", "fileExportedInFormatXToFileY": "ส่งออกไฟล์ในรูปแบบ \"{0}\" ไปยังไฟล์ \"{1}\" แล้ว", "fixedXLines": "แก้ไข {0} บรรทัดแล้ว", + "toolXChangedYLines": "{0}: เปลี่ยนแปลง {1} บรรทัด", "transcriptionCompletedWithXLines": "ถอดเสียงเสร็จสมบูรณ์ ได้ {0} บรรทัด", "openSubtitleFileX": "เปิดไฟล์คำบรรยาย \"{0}\" หรือไม่?", "replacedXOccurrences": "แทนที่แล้ว {0} ตำแหน่ง", @@ -1234,13 +1247,9 @@ "file": { "ebuSaveOptions": { "title": "ตัวเลือกการบันทึก EBU", - "generalSubtitleInformation": "ข้อมูลคำบรรยายทั่วไป", "codePageNumber": "หมายเลขโค้ดเพจ", "diskFormatCode": "รหัสรูปแบบดิสก์", "displayStandardCode": "รหัสมาตรฐานการแสดงผล", - "colorRequiresTeletext": "สีต้องใช้เทเลเท็กซ์!", - "alignmentRequiresTeletext": "การจัดวางต้องใช้เทเลเท็กซ์!", - "teletextCharsShouldBe38": "'จำนวนอักขระสูงสุดต่อแถว' สำหรับเทเลเท็กซ์ควรเป็น 38!", "characterCodeTable": "ตารางอักขระ", "languageCode": "รหัสภาษา", "originalProgramTitle": "ชื่อรายการต้นฉบับ", @@ -1257,7 +1266,6 @@ "maxNumberOfDisplayableRows": "จำนวนแถวสูงสุด", "diskSequenceNumber": "หมายเลขลำดับดิสก์", "totalNumberOfDisks": "จำนวนดิสก์ทั้งหมด", - "import": "นำเข้า...", "textAndTimingInformation": "ข้อมูลข้อความและจังหวะเวลา", "justificationCode": "รหัสการจัดชิดขอบ", "verticalPosition": "ตำแหน่งแนวตั้ง", @@ -1274,11 +1282,6 @@ "errors": "ข้อผิดพลาด", "errorsX": "ข้อผิดพลาด: {0}", "maxLengthError": "บรรทัด {0} เกินความยาวสูงสุด ({1}) อยู่ {2}: {3}", - "textUnchangedPresentation": "การแสดงผลแบบไม่เปลี่ยนแปลง", - "textLeftJustifiedText": "ข้อความชิดซ้าย", - "textCenteredText": "ข้อความกึ่งกลาง", - "textRightJustifiedText": "ข้อความชิดขวา", - "useBoxForOneNewLine": "เลือก 'ใช้กล่องล้อมรอบข้อความ' สำหรับการขึ้นบรรทัดใหม่เพียงครั้งเดียว", "maxLengthErrorTeletextColored": "บรรทัด {0}-{1}: จำนวนอักขระสูงสุดสำหรับข้อความสีความสูงสองเท่าควรเป็น 36 (ไม่ใช่ {2})", "maxLengthErrorTeletextWhite": "บรรทัด {0}-{1}: จำนวนอักขระสูงสุดสำหรับข้อความสีขาวความสูงสองเท่าควรเป็น 37 (ไม่ใช่ {2})", "maxLengthErrorTeletextSingleHeight": "บรรทัด {0}: จำนวนอักขระสูงสุดสำหรับข้อความสีขาวความสูงปกติควรเป็น 38 (ไม่ใช่ {1})", @@ -1303,22 +1306,29 @@ "formattingDotDotDot": "การจัดรูปแบบ...", "imageBasedSubtitleForEditDotDotDot": "คำบรรยายแบบภาพสำหรับแก้ไข...", "imageBasedSubtitleForOcrDotDotDot": "คำบรรยายแบบภาพสำหรับ OCR...", + "dvdSubtitlesDotDotDot": "คำบรรยายจาก DVD (IFO/VOB)...", + "titleImportDvd": "นำเข้าคำบรรยายจาก DVD", + "dvdIfoFile": "ไฟล์ IFO", + "dvdVobFiles": "ไฟล์ VOB", + "dvdVideoStandard": "มาตรฐานวิดีโอ", + "dvdStartRipping": "เริ่มริป", + "dvdRippingAborted": "ยกเลิกการริปแล้ว", + "dvdOpenIfoFile": "เปิดไฟล์ DVD IFO", + "dvdAddVobFiles": "เพิ่มไฟล์ VOB", + "dvdIfoFiles": "ไฟล์ DVD IFO", + "dvdVobFileType": "ไฟล์ DVD VOB", + "dvdDropHint": "เปิดไฟล์ IFO (VIDEO_TS\\VTS_01_0.IFO) หรือเพิ่มไฟล์ VOB - หรือลากมาวางที่นี่", + "dvdNotAnIfoFileX": "{0} ไม่ใช่ไฟล์ DVD IFO", + "dvdMenuIfoNoTitleSetsX": "ไม่พบชุดไตเติล (VTS_xx_0.IFO) ข้าง {0}", + "dvdEncryptedXOfY": "แพ็กเก็ตคำบรรยาย {0} จาก {1} ยังคงเข้ารหัส CSS อยู่ - ไฟล์ VOB เหล่านี้ถูกคัดลอกมาโดยไม่ได้ถอดรหัส ภาพคำบรรยายบางภาพจึงจะผิดเพี้ยน\n\nถอดรหัส DVD ด้วยเครื่องมือริป DVD ก่อนเพื่อผลลัพธ์ที่สมบูรณ์\n\nดำเนินการต่อหรือไม่?", "splitTextAt": "แยกข้อความที่", "blankLines": "บรรทัดว่าง", "oneLineIsOneSubtitle": "หนึ่งบรรทัดคือหนึ่งซับไตเติล", "twoLinesAreOneSubtitle": "สองบรรทัดคือหนึ่งซับไตเติล", "importFilesDotDotDot": "นำเข้าไฟล์...", "multipleFiles": "นำเข้าจากไฟล์ข้อความหลายไฟล์ (หนึ่งไฟล์คือหนึ่งซับไตเติล)", - "importOptions": "ตัวเลือกการนำเข้า", - "autoSplitText": "แบ่งข้อความอัตโนมัติ", - "lineMode": "โหมดบรรทัด", "maxLineLength": "ความยาวบรรทัดสูงสุด", - "maxLinesPerSubtitle": "จำนวนบรรทัดสูงสุดต่อซับไตเติล", - "minGapBetweenSubtitles": "ช่องว่างต่ำสุดระหว่างซับไตเติล", "mergeShortLines": "รวมบรรทัดสั้น", - "removeLinesWithoutLetters": "ลบบรรทัดที่ไม่มีตัวอักษร", - "splitAtEndCharsSetting": "แบ่งที่อักขระท้ายประโยค", - "takeTimeFromCurrentFile": "ใช้เวลาจากไฟล์ปัจจุบัน", "fixed": "คงที่", "numberOfSubtitlesX": "จำนวนซับไตเติล: {0}", "numberOfSubtitlesXForcedY": "จำนวนคำบรรยาย: {0} (บังคับแสดง {1})", @@ -1330,14 +1340,18 @@ "forcedAlignerProgress": "กำลังจัดแนว... ช่วงที่ {0} จาก {1}, {2} จาก {3} บรรทัด", "forcedAlignerSetupTitle": "จัดตำแหน่งไทม์โค้ดผ่านตัวจัดแนวแบบบังคับ", "forcedAlignerSetupIntro": "ตัวจัดแนวแบบบังคับจะจับคู่ข้อความคำบรรยายที่คุณมีอยู่แล้วเข้ากับเสียง โดยไม่ต้องถอดเสียงก่อน วิดีโอยาวจะถูกจัดแนวเป็นช่วง ๆ จึงรองรับความยาวได้ทุกขนาด", + "forcedAlignerEndsFromSpeech": "กำหนดเวลาสิ้นสุดจากเสียงพูดที่แยกออกมา (ช้า)", + "forcedAlignerEndsFromSpeechHint": "Forced aligner จะหาตำแหน่งที่บรรทัดเริ่ม ไม่ใช่ตำแหน่งที่สิ้นสุด ดังนั้นเวลาสิ้นสุดปกติจะมาจากเวลาในการอ่าน เมื่อเปิดตัวเลือกนี้ ดนตรีและเสียงประกอบจะถูกลบออกจากเสียงก่อน และแต่ละบรรทัดจะสิ้นสุดตรงที่เสียงพูดเงียบลง ใช้เวลาประมาณเท่ากับความยาววิดีโอเมื่อมี GPU - และนานกว่านั้นหลายเท่าหากไม่มี", + "forcedAlignerIsolatingSpeech": "กำลังแยกเสียงพูด... {0}", + "forcedAlignerIsolatingSpeechFailed": "ไม่สามารถแยกเสียงพูดได้ - จึงกำหนดเวลาสิ้นสุดจากเวลาในการอ่านแทน", "forcedAlignerModel": "โมเดลตัวจัดแนว", - "forcedAlignerDownloadEngine": "ดาวน์โหลด / อัปเดตเอนจิน..." + "forcedAlignerDownloadEngine": "ดาวน์โหลด / อัปเดตเอนจิน...", + "forcedAlignerEstimatedXOfY": "{0} จาก {1} บรรทัดไม่สามารถจัดแนวได้และถูกวางตามเวลาในการอ่าน - ไทม์โค้ดของบรรทัดเหล่านี้เป็นเพียงค่าประมาณ สคริปต์อาจยาวกว่าสิ่งที่พูดในวิดีโอ" }, "export": { "exportImagesProfiles": "โปรไฟล์การส่งออกรูปภาพ", "leftRightMargin": "ระยะขอบซ้าย/ขวา", "topBottomMargin": "ระยะขอบบน/ล่าง", - "titleExportBluRaySup": "ส่งออก Blu-ray (sup)", "lineSpacingPercent": "ระยะห่างบรรทัด %", "paddingLeftRight": "ระยะเว้นซ้าย/ขวา", "paddingTopBottom": "ระยะเว้นบน/ล่าง", @@ -1378,10 +1392,21 @@ "titleExportDCinemaInteropPng": "D-Cinema interop/png", "titleExportDCinemaSmpte2014Png": "D-Cinema SMPTE 2014/png", "imageBasedSubtitleSaved": "บันทึกซับไตเติลแบบรูปภาพแล้ว", - "titleExportBdnXml": "ส่งออก BDN/xml", "titleExportWebVttThumbnails": "ส่งออก WebVTT (ภาพขนาดย่อ)", "fullFrameImage": "ภาพเต็มเฟรม", "fullFrameImageHint": "ทำให้แต่ละภาพมีขนาดเท่าเฟรมวิดีโอ โดยมีคำบรรยายอยู่ในตำแหน่งของมัน", + "stereo3D": "3D", + "stereo3DHint": "สำหรับวิดีโอ 3D: วาดคำบรรยายหนึ่งครั้งในแต่ละครึ่งของเฟรมสำหรับตาแต่ละข้าง", + "halfSideBySide": "Half side-by-side", + "halfTopBottom": "Half top/bottom", + "depth3D": "ความลึก", + "depth3DHint": "ระยะที่คำบรรยายลอยออกมาจากหน้าจอ - ค่าบวกทำให้ใกล้ขึ้น ค่าลบดันให้ถอยไป", + "plane3D": "3D-Plane", + "plane3DHint": "กำหนดความลึกที่ Blu-ray 3D เก็บไว้ให้กับคำบรรยายแต่ละรายการ จากไฟล์ 3D-Plane (.ofs) ที่สร้างโดย BD3D2MK3D หรือ OFSExtractor จะใช้ค่าความลึกในกรณีที่ 3D-Plane ไม่มีค่า", + "openPlane3DTitle": "เปิดไฟล์ 3D-Plane", + "unableToLoadPlane3DX": "ไม่สามารถโหลด 3D-Plane: {0}", + "plane3DXDepthYToZ": "{0} (ความลึก {1} ถึง {2})", + "plane3DXNoDepth": "{0} (ไม่มีค่าความลึก)", "textEffect": "เอฟเฟกต์ข้อความ", "textEffectSettingsTitle": "การตั้งค่าเอฟเฟกต์ข้อความ", "textEffectPreset": "พรีเซ็ต", @@ -1413,13 +1438,10 @@ "statistics": { "title": "สถิติ", "titleWithFileName": "สถิติ - {0}", - "generalStatistics": "สถิติทั่วไป", - "mostUsed": "ใช้บ่อยที่สุด...", "mostUsedLines": "บรรทัดที่ใช้บ่อยที่สุด", "mostUsedWords": "คำที่ใช้บ่อยที่สุด", "nothingFound": "ไม่พบสิ่งใด", "numberOfLinesX": "จำนวนบรรทัดซับไตเติล: {0:#,##0}", - "numberOfFilesX": "จำนวนไฟล์ซับไตเติล: {0:#,##0}", "lengthInFormatXinCharactersY": "จำนวนอักขระในรูปแบบ {0}: {1:#,###,##0}", "numberOfCharactersInTextOnly": "จำนวนอักขระเฉพาะในข้อความ: {0:#,###,##0}", "totalDuration": "ระยะเวลารวมของซับไตเติลทั้งหมด: {0:#,##0}", @@ -1460,7 +1482,6 @@ "gapMaximum": "ช่องว่าง - สูงสุด: {0:#,##0} มิลลิวินาที", "gapAverage": "ช่องว่าง - เฉลี่ย: {0:#,##0.##} มิลลิวินาที", "gapExceedingMinimum": "ช่องว่าง - ต่ำกว่าค่าต่ำสุด ({0:#,##0} มิลลิวินาที): {1} ({2:0.00}%)", - "export": "ส่งออก...", "subtitles": "คำบรรยาย", "words": "คำ", "totalDurationShort": "ระยะเวลารวม", @@ -1490,7 +1511,22 @@ "restoreAutoBackupFile": "กู้คืนไฟล์ข้อมูลสำรองอัตโนมัติ", "restoreXFromY": "คุณต้องการกู้คืน \"{0}\" จาก {1} หรือไม่?", "deleteAllSubtitleBackups": "คุณต้องการลบไฟล์สำรองซับไตเติลทั้งหมดหรือไม่?", - "deleteAll": "ลบทั้งหมด" + "deleteAll": "ลบทั้งหมด", + "subtitles": "คำบรรยาย", + "settingsBackupInfo": "Settings.json จะถูกสำรองข้อมูลเมื่อ Subtitle Edit เริ่มทำงาน อย่างมากทุกๆ {0} วัน และเฉพาะเมื่อการตั้งค่ามีการเปลี่ยนแปลง จะเก็บข้อมูลสำรองล่าสุดไว้ {1} รายการ", + "settingsBackupInfoEveryStart": "Settings.json จะถูกสำรองข้อมูลทุกครั้งที่ Subtitle Edit เริ่มทำงาน หากการตั้งค่ามีการเปลี่ยนแปลง จะเก็บข้อมูลสำรองล่าสุดไว้ {0} รายการ", + "settingsBackupOff": "การสำรองการตั้งค่าอัตโนมัติถูกปิดอยู่ (ตัวเลือก → การตั้งค่า → ไฟล์)", + "lastSettingsBackupX": "สำรองข้อมูลล่าสุด: {0}", + "noSettingsBackupsYet": "ยังไม่มีข้อมูลสำรองการตั้งค่า", + "openSettingsBackupFolder": "เปิดโฟลเดอร์สำรองการตั้งค่า", + "backUpSettingsNow": "สำรองข้อมูลตอนนี้", + "settingsBackedUp": "สำรองการตั้งค่าปัจจุบันแล้ว", + "restoreSettings": "กู้คืนการตั้งค่า", + "restoreSettingsFromX": "คุณต้องการแทนที่การตั้งค่าปัจจุบันด้วยข้อมูลสำรองจาก {0} หรือไม่?\n\nการตั้งค่าปัจจุบันของคุณจะถูกสำรองไว้ก่อน การเปลี่ยนแปลงบางอย่างจะมีผลหลังจากเริ่ม Subtitle Edit ใหม่เท่านั้น", + "settingsRestored": "กู้คืนการตั้งค่าแล้ว โปรดเริ่ม Subtitle Edit ใหม่เพื่อให้การเปลี่ยนแปลงทั้งหมดมีผล", + "settingsRestoreFailed": "ไม่สามารถกู้คืนการตั้งค่า: {0}", + "settingsBackupNotValid": "ข้อมูลสำรองไม่ใช่ไฟล์การตั้งค่าที่ถูกต้อง จึงคงการตั้งค่าปัจจุบันของคุณไว้", + "deleteAllSettingsBackups": "คุณต้องการลบไฟล์สำรองการตั้งค่าทั้งหมดหรือไม่?" }, "propertiesDCinema": { "title": "คุณสมบัติ D-Cinema SMPTE", @@ -1504,22 +1540,16 @@ "editRate": "อัตราการตัดต่อ", "timeCodeRate": "อัตราไทม์โค้ด", "startTime": "เวลาเริ่มต้น", - "font": "ฟอนต์", "fontId": "ID ฟอนต์", "fontUri": "URI ฟอนต์", - "generate": "สร้าง", - "fontColor": "สีฟอนต์", "chooseColor": "เลือกสี", "fontEffect": "เอฟเฟกต์ฟอนต์", "effectColor": "สีเอฟเฟกต์", - "fontSize": "ขนาดฟอนต์", "topBottomMargin": "ระยะขอบบน/ล่าง", "fadeUpTime": "เวลาเฟดเข้า", "fadeDownTime": "เวลาเฟดออก", - "frames": "เฟรม", "zPosition": "ตำแหน่งแกน Z", - "zPositionHelp": "ค่าบวกจะเลื่อนข้อความออกไปไกล ค่าลบจะเลื่อนข้อความเข้ามาใกล้ ถ้าตำแหน่งแกน Z เป็นศูนย์จะเป็นแบบ 2 มิติ", - "export": "ส่งออก..." + "zPositionHelp": "ค่าบวกจะเลื่อนข้อความออกไปไกล ค่าลบจะเลื่อนข้อความเข้ามาใกล้ ถ้าตำแหน่งแกน Z เป็นศูนย์จะเป็นแบบ 2 มิติ" }, "webVtt": { "setStylesForSelectedLinesTitle": "กำหนดสไตล์ WebVTT ให้บรรทัดที่เลือก", @@ -1552,20 +1582,66 @@ "compareOnlyInOneFile": "มีในไฟล์เดียวเท่านั้น", "compareTextOrTimeDifference": "ความแตกต่างของข้อความ/เวลา", "compareNumberDifference": "ความแตกต่างของหมายเลข", - "ignoreLineBreaks": "ไม่สนใจการขึ้นบรรทัดใหม่", "ignoreWhitespace": "ไม่สนใจช่องว่าง", "ignoreWhitespaceHint": "บรรทัดที่ต่างกันเพียงช่องว่าง แท็บ หรือการตัดบรรทัด จะไม่นับว่าต่างกัน", "ignoreFormatting": "ไม่สนใจการจัดรูปแบบ", - "onlyLookForDifferencesInText": "ค้นหาความแตกต่างเฉพาะในข้อความเท่านั้น", - "cannotCompareWithImageBasedSubtitles": "ไม่สามารถเปรียบเทียบกับซับไตเติลแบบรูปภาพได้", - "statisticsTitle": "สถิติ", "ignoreFormattingHint": "บรรทัดที่ต่างกันเพียงแท็กจัดรูปแบบ เช่น หรือ {\\an8} จะไม่นับว่าต่างกัน", "showOnlyDifferencesInText": "เฉพาะความแตกต่างในข้อความ", + "ignoreNumbering": "ละเว้นลำดับเลข", + "ignoreNumberingHint": "บรรทัดที่ต่างกันเฉพาะหมายเลขจะไม่นับว่าแตกต่าง", + "ignoreTimeDifferenceMs": "ค่าความคลาดเคลื่อนของเวลา (ms)", + "ignoreTimeDifferenceMsHint": "เวลาเริ่มต้นและสิ้นสุดที่ต่างกันไม่เกินจำนวนมิลลิวินาทีนี้จะไม่นับว่าแตกต่าง", + "compareDifferences": "ความแตกต่าง", + "compareTextDifferences": "ความแตกต่างของข้อความ", + "compareCurrent": "ปัจจุบัน", + "compareReference": "อ้างอิง", + "compareEditable": "แก้ไขได้", + "compareEditableHint": "การเปลี่ยนแปลงที่ทำที่นี่จะกลับไปยังคำบรรยายเมื่อคุณคลิก นำไปใช้", + "compareReadOnly": "อ่านอย่างเดียว", + "compareTakeFromReference": "ใช้ข้อความและเวลาจากไฟล์อ้างอิง", + "compareInsertFromReference": "แทรกบรรทัดนี้ลงในคำบรรยายปัจจุบัน", + "compareDeleteFromCurrent": "ลบบรรทัดนี้ออกจากคำบรรยายปัจจุบัน", + "compareTakeText": "ใช้ข้อความ", + "compareTakeTiming": "ใช้เวลา", + "compareEditLine": "แก้ไขบรรทัด", + "compareEditHint": "Ctrl+Enter เพื่อบันทึก, Esc เพื่อยกเลิก", + "compareEdited": "แก้ไขแล้ว", + "compareOnePendingChange": "การเปลี่ยนแปลงที่รอดำเนินการ 1 รายการ", + "compareXPendingChanges": "การเปลี่ยนแปลงที่รอดำเนินการ {0} รายการ", + "compareDiscardXChanges": "ละทิ้งการเปลี่ยนแปลง {0} รายการที่ทำในหน้าต่างเปรียบเทียบหรือไม่?", + "compareXChangesApplied": "เปรียบเทียบ: นำการเปลี่ยนแปลง {0} รายการไปใช้แล้ว", + "compareChangeTextX": "#{0} ข้อความจากไฟล์อ้างอิง", + "compareChangeTimingX": "#{0} เวลาจากไฟล์อ้างอิง", + "compareChangeTextAndTimingX": "#{0} ข้อความและเวลาจากไฟล์อ้างอิง", + "compareChangeEditedX": "#{0} แก้ไขแล้ว", + "compareChangeInsertedX": "#{0} แทรกแล้ว", + "compareChangeDeletedX": "#{0} ลบแล้ว", + "compareSyncPoint": "จุดซิงค์", + "compareSyncPointHint": "จุดซิงค์ - สองบรรทัดนี้จะแสดงเป็นคู่เสมอ และบรรทัดด้านบนและด้านล่างจะถูกจัดเรียงแยกกัน", + "compareSyncPickCurrent": "จุดซิงค์: ใช้บรรทัดปัจจุบันนี้", + "compareSyncPickReference": "จุดซิงค์: ใช้บรรทัดอ้างอิงนี้", + "compareSyncRemove": "ลบจุดซิงค์", + "compareClearXSyncPoints": "ล้างจุดซิงค์ ({0})", + "compareSyncCurrentPickedX": "เลือกบรรทัดปัจจุบัน #{0} เป็นจุดซิงค์แล้ว - เลือกบรรทัดอ้างอิงที่ตรงกันแล้วคลิก ซิงค์", + "compareSyncReferencePickedX": "เลือกบรรทัดอ้างอิง #{0} เป็นจุดซิงค์แล้ว - เลือกบรรทัดปัจจุบันที่ตรงกันแล้วคลิก ซิงค์", + "compareSyncWithCurrentX": "ซิงค์กับบรรทัดปัจจุบัน #{0}", + "compareSyncWithReferenceX": "ซิงค์กับบรรทัดอ้างอิง #{0}", + "compareSyncApply": "ซิงก์", + "compareSyncPointSetXY": "ตั้งจุดซิงค์แล้ว: บรรทัดปัจจุบัน #{0} และบรรทัดอ้างอิง #{1} แสดงเป็นคู่แล้ว", + "compareSyncAlreadyPairedXY": "บรรทัดปัจจุบัน #{0} และบรรทัดอ้างอิง #{1} แสดงเป็นคู่โดยมีเวลาเริ่มต้นเดียวกันอยู่แล้ว - ไม่มีอะไรต้องซิงค์", + "compareSyncPointShiftedXYZW": "ตั้งจุดซิงค์แล้ว: เลื่อนบรรทัดปัจจุบัน #{0}-#{1} ไป {2} ให้เริ่มพร้อมบรรทัดอ้างอิง #{3}", + "compareChangeSyncShiftXYZ": "#{0}-#{1} เลื่อน {2} (จุดซิงค์)", + "compareTimingDeltaHint": "ไฟล์อ้างอิงเริ่มและสิ้นสุดช้ากว่า (+) หรือเร็วกว่า (−) บรรทัดปัจจุบันเท่าใด", "loadXFromFile": "โหลด \"{0}\" จากไฟล์", + "compareLoadSavedFile": "โหลดไฟล์ที่บันทึกไว้", "saveCompareHtmlTitle": "บันทึกไฟล์ HTML เปรียบเทียบ", "pickMatroskaTrackX": "เลือกแทร็ก Matroska - {0}", "pickTransportStreamTrackX": "เลือกแทร็ก transport stream - {0}", + "chapters": "บท", + "dvdVobFilesMissingX": "ไฟล์ VOB ขาดหายไป (พบ {0}%)", "pickMp4TrackX": "เลือกแทร็ก MP4 - {0}", + "pickMxfTrackX": "เลือกแทร็ก MXF - {0}", + "pickMpegTrackX": "เลือกแทร็ก MPEG - {0}", "rosettaProperties": "คุณสมบัติ Timed Text Rosetta IMSC", "rosettaFontSize": "ขนาดฟอนต์ (ความสูงแถว)", "propertyTimeBase": "ฐานเวลา", @@ -1575,7 +1651,6 @@ "propertyDefaultRegion": "พื้นที่เริ่มต้น", "propertyStyleAttributeName": "ชื่อแอตทริบิวต์สไตล์", "propertyTimeCodeFormat": "รูปแบบไทม์โค้ด", - "propertyFileExtension": "นามสกุลไฟล์", "propertyTopOrigin": "จุดกำเนิดด้านบน", "propertyTopExtent": "ขอบเขตด้านบน", "propertyBottomOrigin": "จุดกำเนิดด้านล่าง", @@ -1625,7 +1700,6 @@ "selectionIntersect": "อินเตอร์เซกต์กับการเลือก" }, "multipleReplace": { - "title": "แทนที่หลายรายการ", "editRule": "แก้ไขกฎ", "newRule": "กฎใหม่", "editCategory": "แก้ไขหมวดหมู่", @@ -1642,24 +1716,23 @@ "findWhat": "ค้นหาอะไร", "descriptionOptional": "คำอธิบาย (ไม่บังคับ)", "invalidRegularExpressionX": "นิพจน์ทั่วไปไม่ถูกต้อง: {0}", - "regularExpressionTooSlowX": "นิพจน์ทั่วไปหยุดทำงานหลังจาก {0} วินาที - ข้ามกฎนี้ ลองใช้รูปแบบที่ง่ายกว่า" + "regularExpressionTooSlowX": "นิพจน์ทั่วไปหยุดทำงานหลังจาก {0} วินาที - ข้ามกฎนี้ ลองใช้รูปแบบที่ง่ายกว่า", + "moveToCategory": "ย้ายไปยังหมวดหมู่" }, "find": { "searchTextWatermark": "ค้นหาข้อความ...", "wholeWord": "ทั้งคำ", - "caseSensitive": "ตรงตามตัวพิมพ์ใหญ่-เล็ก", - "caseInsensitive": "ไม่สนใจตัวพิมพ์ใหญ่-เล็ก", "findPrevious": "ค้นหาก่อนหน้า", "findNext": "ค้นหาถัดไป", "replaceAndFindNext": "แทนที่และค้นหาถัดไป", "replaceAll": "แทนที่ทั้งหมด", - "replaceTitle": "แทนที่", - "replaceWith": "แทนที่ด้วย", "replaceTextWatermark": "แทนที่ด้วย...", "replaceIn": "แทนที่/ค้นหาใน", "replaceInTextAndOriginal": "ข้อความและข้อความต้นฉบับ", "replaceInTextOnly": "เฉพาะข้อความ", - "replaceInOriginalOnly": "เฉพาะข้อความต้นฉบับ" + "replaceInOriginalOnly": "เฉพาะข้อความต้นฉบับ", + "replacedOneOccurrence": "แทนที่แล้ว 1 ตำแหน่ง", + "replacedXOccurrences": "แทนที่แล้ว {0} ตำแหน่ง" }, "regularExpressionContextMenu": { "wordBoundary": "ขอบเขตคำ (\\b)", @@ -1687,6 +1760,7 @@ "xLinesYSubtitles": "{0} บรรทัด, {1} คำบรรยาย", "xLinesYSubtitlesZErrors": "{0} บรรทัด, {1} คำบรรยาย, ข้อผิดพลาด {2} รายการ", "couldNotParseAsX": "ไม่สามารถแปลงข้อมูลเป็น {0} ได้", + "readAsXUseAsY": "ไม่สามารถอ่านซอร์สเป็น {1} ได้ แต่สามารถอ่านเป็น {0} ได้\nใช้บรรทัดเหล่านี้และบันทึกเป็น {1} ต่อไปหรือไม่?", "selectedXCharactersYLines": "เลือกแล้ว: {0} อักขระใน {1} บรรทัด", "replacedXOccurrences": "แทนที่แล้ว {0} รายการ", "invalidRegularExpression": "นิพจน์ทั่วไปไม่ถูกต้อง", @@ -1701,7 +1775,6 @@ "aiReview": { "title": "รีวิวด้วย AI", "review": "รีวิว", - "stop": "หยุด", "editPrompt": "แก้ไขพรอมต์...", "editPromptTitle": "แก้ไขพรอมต์รีวิว", "promptInfo": "คำสั่งที่ส่งไปยังโมเดล - {language} จะถูกแทนที่ด้วยภาษาของซับไตเติล", @@ -1712,7 +1785,6 @@ "reviewingLineXOfY": "กำลังรีวิวบรรทัด {0} จาก {1}...", "reviewDone": "รีวิวเสร็จสิ้น - {0} ข้อเสนอแนะใน {1} บรรทัด", "noIssuesFound": "ไม่พบปัญหา", - "categoryAll": "ทั้งหมด", "categorySpelling": "การสะกดคำ", "categoryGrammar": "ไวยากรณ์", "categoryPunctuation": "เครื่องหมายวรรคตอน", @@ -1724,8 +1796,23 @@ "largeChangeWarning": "การเปลี่ยนแปลงขนาดใหญ่ - ดูเหมือนการเขียนใหม่ กรุณาตรวจสอบอย่างระมัดระวัง", "mismatchWarning": "ไม่คล้ายกับต้นฉบับ - อาจเป็นของบรรทัดอื่น", "engineError": "ไม่สามารถเชื่อมต่อกับเอนจิน AI ได้: {0}", - "setupHint": "กรุณาเลือกเอนจินและโมเดลใน เครื่องมือ -> รีวิวด้วย AI ก่อน", - "playCurrentLineHint": "เล่นบรรทัดที่เลือกในโปรแกรมเล่นวิดีโอและหยุดชั่วคราวเมื่อจบบรรทัด (ดับเบิลคลิกที่บรรทัดก็ได้)" + "playCurrentLineHint": "เล่นบรรทัดที่เลือกในโปรแกรมเล่นวิดีโอและหยุดชั่วคราวเมื่อจบบรรทัด (ดับเบิลคลิกที่บรรทัดก็ได้)", + "editAfterHint": "คลิกข้อความของแถวที่เลือกเพื่อแก้ไขการแก้ก่อนนำไปใช้ (Enter บันทึก, Escape ยกเลิก)", + "template": "เทมเพลต", + "templatePlaceholder": "เลือกเทมเพลตเพื่อแทนที่พรอมต์", + "templateStandard": "พิสูจน์อักษรมาตรฐาน", + "templateSpeechToText": "ทำความสะอาดผลแปลงเสียงเป็นข้อความ", + "templateMachineTranslation": "แก้ไขการแปลด้วยเครื่อง", + "templateSdh": "SDH / ผู้บกพร่องทางการได้ยิน", + "referenceContext": "บริบทอ้างอิง", + "referenceContextInfo": "ไม่บังคับ ชื่อ ตัวละคร อภิธานศัพท์ เรื่องย่อ หรือบันทึกสไตล์ - โมเดลจะถือว่าข้อมูลนี้เป็นหลักสำหรับชื่อและคำศัพท์ ส่งไปพร้อมทุกชุด", + "referenceContextPlaceholder": "ชื่อ: Kaito, Aunt Mei, Dr. Hollis\nคำศัพท์: Shinkansen, warp core\nเรื่องย่อ: Kaito เดินทางไปโตเกียวเพื่อตามหาพ่อที่หายตัวไป", + "generateContext": "สร้างด้วย AI", + "generateContextHint": "ให้เอนจิน AI ที่เลือกอ่านคำบรรยายและร่างชื่อ คำศัพท์ และเรื่องย่อ - ตรวจสอบผลลัพธ์ก่อนใช้งาน", + "generatingContextPartXOfY": "กำลังอ่านส่วนที่ {0} จาก {1}...", + "generatingContextSynopsis": "กำลังเขียนเรื่องย่อ...", + "replaceContextQuestion": "แทนที่บริบทอ้างอิงปัจจุบันหรือไม่?", + "noContextGenerated": "เอนจิน AI ไม่ได้ส่งคืนข้อมูลอ้างอิงที่ใช้ได้" }, "aiAssistant": { "title": "ผู้ช่วย AI", @@ -1743,22 +1830,14 @@ "showReasoning": "แสดงการให้เหตุผลของโมเดล" }, "fixCommonErrors": { - "title": "แก้ไขข้อผิดพลาดทั่วไป", - "step1": "ขั้นตอนที่ 1/2 - เลือกข้อผิดพลาดที่จะแก้ไข", - "whatToFix": "สิ่งที่จะแก้ไข", - "example": "ตัวอย่าง", - "selectAll": "เลือกทั้งหมด", - "inverseSelection": "สลับการเลือก", "back": "< ย้อนกลับ", "next": "ถัดไป >", - "step2": "ขั้นตอนที่ 2/2 - ตรวจสอบการแก้ไข", "fixes": "การแก้ไข", "ruleDetailsDotDotDot": "รายละเอียดกฎ...", "showOnlyThisRule": "แสดงเฉพาะการแก้ไขจากกฎนี้", "appliedRulesForLineX": "กฎที่ใช้กับบรรทัด {0}", "ruleX": "กฎ: {0}", "log": "บันทึกเหตุการณ์", - "function": "ฟังก์ชัน", "removedEmptyLine": "ลบบรรทัดว่าง", "removedEmptyLineAtTop": "ลบบรรทัดว่างด้านบน", "removedEmptyLineAtBottom": "ลบบรรทัดว่างด้านล่าง", @@ -1779,13 +1858,13 @@ "removeLineBreaksPixelWidth": "รวมซับไตเติลที่พอดีในบรรทัดเดียวได้ (ความกว้างพิกเซล)", "fixUppercaseIInsideLowercaseWords": "แก้ไข 'i' ตัวพิมพ์ใหญ่ในคำตัวพิมพ์เล็ก (ข้อผิดพลาด OCR)", "fixDoubleApostrophes": "แก้ไขอะพอสทรอฟีคู่ ('') เป็นอัญประกาศเดี่ยว (\")", + "fixMisreadQuotes": "แก้ไขอะพอสทรอฟีที่อ่านผิดเป็นอัญประกาศคู่ (OCR, ภาษาอังกฤษ)", "addPeriods": "เพิ่มจุดหลังบรรทัดเมื่อบรรทัดถัดไปขึ้นต้นด้วยตัวพิมพ์ใหญ่", "startWithUppercaseLetterAfterParagraph": "ขึ้นต้นด้วยตัวพิมพ์ใหญ่หลังย่อหน้า", "startWithUppercaseLetterAfterPeriodInsideParagraph": "ขึ้นต้นด้วยตัวพิมพ์ใหญ่หลังจุดภายในย่อหน้า", "startWithUppercaseLetterAfterColon": "ขึ้นต้นด้วยตัวพิมพ์ใหญ่หลังทวิภาค/อัฒภาค", "fixLowercaseIToUppercaseI": "แก้ไข 'i' ตัวพิมพ์เล็กที่อยู่โดดๆ เป็น 'I' (ภาษาอังกฤษ)", "fixCommonOcrErrors": "แก้ไขข้อผิดพลาด OCR ทั่วไป (ใช้รายการแทนที่ OCR)", - "commonOcrErrorsFixed": "แก้ไขข้อผิดพลาด OCR ทั่วไปแล้ว (ใช้ไฟล์ OcrReplaceList): {0}", "removeSpaceBetweenNumber": "ลบช่องว่างระหว่างตัวเลข", "breakDialogsOnOneLine": "แยกบทสนทนาที่อยู่ในบรรทัดเดียว", "removeDialogFirstInNonDialogs": "ลบขีดนำหน้าในบรรทัดแรกสำหรับข้อความที่ไม่ใช่บทสนทนา", @@ -1802,18 +1881,17 @@ "fixCommasExample": ",, -> ,", "removeLineBreaksExample": "Foo
bar! -> Foo bar!", "fixDoubleApostrophesExample": "''Has double single quotes'' -> \"Has single double quote\"", + "fixMisreadQuotesExample": "\"Hello' -> \"Hello\"", "addPeriodsExample": "Hello world -> Hello world.", "startWithUppercaseLetterAfterParagraphExample": "p1: Foobar! || p2: foobar! -> p1: Foobar! || p2: Foobar!", "startWithUppercaseLetterAfterPeriodInsideParagraphExample": "Hello there! how are you? -> Hello there! How are you?", "startWithUppercaseLetterAfterColonExample": "Speaker: hello world! -> Speaker: Hello world!", "removeHyphensSingleLineExample": "- Foobar. -> Foobar.", "fix3PlusLinesExample": "Foo
bar
baz! -> Foo bar baz!", - "xMissingQuotesAdded": "เพิ่มอัญประกาศที่ขาดหายไปแล้ว: {0}", "fix3PlusLine": "แก้ไขซับไตเติลที่มีมากกว่าสองบรรทัด", "fix3PlusLines": "แก้ไขซับไตเติลที่มีมากกว่าสองบรรทัด", "analysing": "กำลังวิเคราะห์...", "nothingToFix": "ไม่มีอะไรต้องแก้ไข :)", - "fixesFoundX": "พบการแก้ไข: {0}", "xFixesApplied": "นำการแก้ไขไปใช้แล้ว: {0}", "xFixesYSelected": "{0} การแก้ไข - เลือกแล้ว {1}", "nothingFixableBut": "ไม่สามารถแก้ไขอะไรได้โดยอัตโนมัติ ซับไตเติลมีข้อผิดพลาด - ดูรายละเอียดในบันทึกเหตุการณ์", @@ -1822,7 +1900,6 @@ "fixFirstLetterToUppercaseAfterParagraph": "แก้ไขตัวอักษรแรกเป็นตัวพิมพ์ใหญ่หลังย่อหน้า", "mergeShortLine": "รวมบรรทัดสั้น (ประโยคเดียว)", "mergeShortLineAll": "รวมบรรทัดสั้น (ทั้งหมดยกเว้นบทสนทนา)", - "unbreakShortLinePixelWidth": "รวมบรรทัดสั้น (ความกว้างพิกเซล)", "breakLongLine": "แบ่งบรรทัดยาว", "fixLongDisplayTime": "แก้ไขเวลาแสดงผลที่ยาวเกินไป", "fixInvalidItalicTag": "แก้ไขแท็กตัวเอียงที่ไม่ถูกต้อง", @@ -1845,7 +1922,6 @@ "fixUppercaseIInsideLowercaseWord": "แก้ไข 'i' ตัวพิมพ์ใหญ่ในคำตัวพิมพ์เล็ก", "fixMissingPeriodAtEndOfLine": "เพิ่มจุดที่ขาดหายไปที่ท้ายบรรทัด", "refreshFixes": "รีเฟรชการแก้ไขที่มีอยู่", - "applyFixes": "นำการแก้ไขที่เลือกไปใช้และปิด", "autoBreak": "แบ่งบรรทัดอัตโนมัติ", "unbreak": "รวมบรรทัด", "fixDoubleDash": "แก้ไข '--' -> '...'", @@ -1864,8 +1940,6 @@ "fixSpaceBetweenNumbersExample": "1 100 -> 1100", "fixDialogsOneLineExample": "Hi John! - Hi Ida! -> Hi John!
- Hi Ida!", "removeDialogFirstInNonDialogsExample": "- How are you? -> How are you?", - "selectDefault": "เลือกค่าเริ่มต้น", - "setDefault": "ตั้งการแก้ไขปัจจุบันเป็นค่าเริ่มต้น", "fixContinuationStyleX": "แก้ไขสไตล์ความต่อเนื่อง: {0}", "fixUnnecessaryLeadingDots": "ลบจุดนำหน้าที่ไม่จำเป็น", "fixCommonErrorsProfiles": "โปรไฟล์การแก้ไขข้อผิดพลาดทั่วไป", @@ -1873,7 +1947,6 @@ "searchRulesDotDotDot": "ค้นหากฎ...", "goToApplyFixes": "ไปที่การนำการแก้ไขไปใช้", "backToFixList": "กลับไปยังรายการแก้ไข", - "applyFixesAndClose": "นำการแก้ไขไปใช้และปิด", "fixCommonOcrErrorsStep2": "แก้ไขข้อผิดพลาดทั่วไป ขั้นตอนที่ 2 (นำการแก้ไขไปใช้)", "fixCommonOcrErrorsStep2FixesFoundX": "แก้ไขข้อผิดพลาดทั่วไป ขั้นตอนที่ 2 - พบการแก้ไข: {0}", "action": "การกระทำ", @@ -1886,18 +1959,11 @@ "adjustDurations": { "title": "ปรับระยะเวลา", "adjustVia": "ปรับผ่าน", - "seconds": "วินาที", - "percent": "เปอร์เซ็นต์", "recalculate": "คำนวณใหม่", "addSeconds": "เพิ่มวินาที", - "setAsPercent": "ตั้งเป็นเปอร์เซ็นต์ของระยะเวลา", "note": "หมายเหตุ: เวลาแสดงผลจะไม่ซ้อนทับเวลาเริ่มต้นของข้อความถัดไป", "fixed": "คงที่", - "milliseconds": "มิลลิวินาที", "extendOnly": "ขยายเท่านั้น", - "enforceDurationLimits": "บังคับใช้ระยะเวลาต่ำสุดและสูงสุด", - "checkShotChanges": "ไม่ขยายเกินจุดเปลี่ยนฉาก", - "batchCheckShotChanges": "เคารพจุดเปลี่ยนฉาก (ถ้ามี)", "recalculateRequiresOcrNote": "ไม่สามารถคำนวณใหม่ได้ — ซับไตเติลบางรายการไม่มีข้อความ OCR กรุณาทำ OCR ก่อน" }, "applyDurationLimits": { @@ -1905,6 +1971,8 @@ "fixMinDurationMs": "แก้ไขระยะเวลาต่ำสุด (มิลลิวินาที)", "doNotGoPastShotChange": "ไม่เกินจุดเปลี่ยนฉาก", "fixMaxDurationMs": "แก้ไขระยะเวลาสูงสุด (มิลลิวินาที)", + "fixMinDurationFrames": "แก้ไขระยะเวลาต่ำสุด (เฟรม)", + "fixMaxDurationFrames": "แก้ไขระยะเวลาสูงสุด (เฟรม)", "minimumDurationMilliseconds": "ระยะเวลาต่ำสุด (มิลลิวินาที):", "maximumDurationMilliseconds": "ระยะเวลาสูงสุด (มิลลิวินาที):", "maxDurationShouldBeHigherThanMinDuration": "ระยะเวลาสูงสุดควรมากกว่าระยะเวลาต่ำสุด", @@ -1924,12 +1992,8 @@ }, "beautifyTimeCodes": { "title": "ปรับแต่งไทม์โค้ด", - "beautifySettings": "การตั้งค่าการปรับแต่ง", "snapToFrames": "จัดชิดเฟรม", - "frameGap": "ช่องว่างเฟรม", "minDuration": "ระยะเวลาต่ำสุด (มิลลิวินาที)", - "shotChangeThreshold": "เกณฑ์จุดเปลี่ยนฉาก (มิลลิวินาที)", - "shotChangeOffset": "ออฟเซตจุดเปลี่ยนฉาก (เฟรม)", "original": "ต้นฉบับ", "beautified": "ปรับแต่งแล้ว", "subtitlesCount": "ซับไตเติล", @@ -1958,13 +2022,80 @@ "timeCodesExtractFailed": "ไม่สามารถอ่านรหัสเวลาจากวิดีโอได้", "timeCodesHint": "ดึงคิวเข้าหาเวลาเฟรมจริงของวิดีโอ แทนการสมมติว่าอัตราเฟรมคงที่ ต้องอ่านวิดีโอทั้งไฟล์หนึ่งครั้ง ผลลัพธ์จะถูกแคชไว้" }, + "improveTimeCodes": { + "title": "ปรับปรุงไทม์โค้ด (forced alignment)", + "aligner": "ตัวจัดแนว", + "alignerHint": "โมเดลที่ฟังหาแต่ละบรรทัดในเสียง ตัวเลือกที่ดีที่สุดสำหรับภาษาของคำบรรยายจะแสดงไว้ก่อน", + "maxShift": "การเลื่อนสูงสุด (วินาที)", + "maxShiftHint": "คำบรรยายอาจคลาดเคลื่อนได้มากเพียงใด บรรทัดที่เวลาเริ่มต้นจะเลื่อนไปไกลกว่านี้จะถูกคงไว้ตามเดิมและถูกทำเครื่องหมาย ไม่ว่าค่าจะเป็นเท่าใด บรรทัดเดี่ยวที่ต้องการเลื่อนไกลกว่าบรรทัดรอบข้างเกินครึ่งวินาทีจะไม่ถูกเลื่อนโดยไม่ตรวจสอบ: จะเลื่อนตามบรรทัดข้างเคียงหากบรรทัดเหล่านั้นเลื่อนทั้งหมด มิฉะนั้นจะแสดงโดยไม่ได้ทำเครื่องหมายถูกให้คุณตรวจสอบ", + "adjustStartTimes": "ปรับเวลาเริ่มต้น", + "adjustEndTimes": "ปรับเวลาสิ้นสุด", + "isolateSpeech": "แยกเสียงพูดก่อน (ช้า)", + "isolateSpeechHint": "ลบดนตรีและเสียงประกอบออกก่อนจัดแนว เพื่อให้ตัวจัดแนวได้ยินเฉพาะบทพูด ช่วยในบรรทัดที่พูดทับดนตรีเสียงดังหรือฉากแอ็กชัน แต่ใช้เวลาประมาณเท่ากับความยาวเสียงเมื่อมี GPU - และนานกว่านั้นหลายเท่าหากไม่มี", + "isolatingSpeech": "กำลังแยกเสียงพูด... (ใช้เวลาสักครู่)", + "isolateSpeechFailed": "ไม่สามารถแยกเสียงพูดได้ - จึงจัดแนวกับเสียงต้นฉบับแทน", + "checkWithSpeechToText": "ตรวจสอบด้วยการแปลงเสียงเป็นข้อความ", + "checkWithSpeechToTextHint": "ถอดเสียงด้วย และตรวจสอบการเลื่อนทุกครั้งเทียบกับตำแหน่งที่ได้ยินคำจริง การเลื่อนขนาดใหญ่ที่เสียงพูดยืนยันจะถูกทำเครื่องหมายถูกให้ และการเลื่อนออกจากตำแหน่งที่ได้ยินบรรทัดจะไม่ถูกทำเครื่องหมายเพื่อให้คุณตรวจสอบ ยังแสดงด้วยว่าได้ยินแต่ละบรรทัดมากเพียงใด - สัดส่วนต่ำหมายความว่าข้อความต่างจากสิ่งที่พูด คำบรรยายที่คลาดเคลื่อนมากกว่าการเลื่อนสูงสุด - การเหลื่อมคงที่ การเลื่อนไหลจากอัตราเฟรมที่ไม่ตรงกัน หรือการกระโดดในจุดที่ตัดฉาก - จะถูกซิงค์ด้วยคำที่ได้ยินก่อน ใช้ Crisp ASR Parakeet ซึ่งใช้เวลาไม่กี่นาทีสำหรับภาพยนตร์เรื่องยาวเมื่อมี GPU", + "checkWithSpeechToTextNotAvailableX": "การตรวจสอบด้วยการแปลงเสียงเป็นข้อความไม่พร้อมใช้งานสำหรับ {0}", + "transcribingToCheck": "กำลังถอดเสียงเพื่อตรวจสอบการจัดแนว...", + "speechToTextFailed": "การแปลงเสียงเป็นข้อความล้มเหลว - ไทม์โค้ดไม่ได้ถูกตรวจสอบเทียบกับผลนั้น", + "speechToTextX": "แปลงเสียงเป็นข้อความ: {0}", + "speechToTextConfirmedXDisputedY": "เสียงพูด: ยืนยัน {0}, โต้แย้ง {1}", + "hardlyHeardX": "การแปลงเสียงเป็นข้อความได้ยินเพียง {0}% ของคำในคำบรรยาย คำบรรยายเป็นภาษาเดียวกับที่พูดหรือไม่? ตัวจัดแนวไม่สามารถวางคำแปลได้", + "heard": "ได้ยิน", + "heardHint": "การแปลงเสียงเป็นข้อความได้ยินบรรทัดนี้มากเพียงใด สัดส่วนต่ำหมายความว่าข้อความต่างจากสิ่งที่พูด - ซึ่งเป็นจุดที่ตัวจัดแนวมีแนวโน้มผิดพลาดมากที่สุด", + "statusConfirmedBySpeech": "ยืนยันโดยเสียงพูด", + "statusDisputedBySpeech": "ได้ยินที่ตำแหน่งเดิม", + "statusMovedWithSync": "เลื่อนตามการซิงค์", + "syncedFirstXY": "ซิงค์ก่อน: {0} วินาทีที่ช่วงต้น, {1} วินาทีที่ช่วงท้าย", + "showSpeechOnly": "แสดงเฉพาะเสียงพูด", + "showSpeechOnlyHint": "วาดรูปคลื่นเสียงที่จัดแนวแล้วจากเสียงที่ลบดนตรีและเสียงประกอบออก รูปคลื่นเสียงต้นฉบับยังคงเดิมเพื่อใช้เปรียบเทียบ ใช้ได้เมื่อแยกเสียงพูดแล้ว - ที่นี่ หรือด้วย \"แสดงเฉพาะเสียงพูด\" ในรูปคลื่นเสียงของหน้าต่างหลัก", + "align": "จัดแนว", + "original": "ต้นฉบับ", + "aligned": "จัดแนวแล้ว", + "intro": "เลือกตัวจัดแนวแล้วกด \"จัดแนว\" เพื่อฟังหาทุกบรรทัดในเสียงและปรับไทม์โค้ดให้แม่นยำขึ้น", + "extractingAudio": "กำลังแยกเสียง...", + "aligningBatchXOfY": "กำลังจัดแนว... ชุดที่ {0} จาก {1}", + "extractAudioFailed": "ไม่สามารถแยกเสียงด้วย ffmpeg ได้", + "modelWillBeDownloaded": "จะดาวน์โหลด {0}", + "summaryXRetimedYKeptZSkipped": "ปรับเวลาใหม่: {0} · คงไว้: {1} · ไม่มีเสียงพูด: {2}", + "meanShiftX": "การเลื่อนเฉลี่ย: {0} ms", + "toCheckX": "ต้องตรวจสอบ: {0}", + "largeMovesToCheckX": "{0} บรรทัดจะเลื่อนไปไกลเพียงลำพังและไม่ได้ทำเครื่องหมายถูก - ฟังบรรทัดเหล่านั้น (F5 เล่นบรรทัดที่จัดแนวแล้ว, Shift+F5 เล่นต้นฉบับ) และทำเครื่องหมายถูกบรรทัดที่ถูกต้อง", + "linesToCheckX": "{0} บรรทัดไม่ได้ทำเครื่องหมายถูกเพื่อให้ตรวจสอบ - ฟังบรรทัดเหล่านั้น (F5 เล่นบรรทัดที่จัดแนวแล้ว, Shift+F5 เล่นต้นฉบับ) และทำเครื่องหมายถูกบรรทัดที่ถูกต้อง", + "changeXOfY": "การเปลี่ยนแปลง {0} จาก {1}", + "previousChange": "การเปลี่ยนแปลงก่อนหน้า", + "nextChange": "การเปลี่ยนแปลงถัดไป", + "startShift": "เริ่มต้น", + "endShift": "สิ้นสุด", + "statusRetimed": "ปรับเวลาใหม่แล้ว", + "statusMovedWithNeighbours": "เลื่อนตามบรรทัดข้างเคียง", + "statusLargeMoveUnconfirmed": "เลื่อนมาก - ฟังแล้วทำเครื่องหมายถูกเพื่อนำไปใช้", + "statusUnchanged": "อยู่ในตำแหน่งแล้ว", + "statusNoSpeech": "ไม่มีเสียงพูด", + "statusShiftTooLarge": "คงไว้ - เลื่อนมากเกินไป", + "statusFailed": "คงไว้ - ตัวจัดแนวล้มเหลว", + "statusAdjustedByHand": "ปรับด้วยตนเอง", + "alignedWaveformHint": "ลากบรรทัดเพื่อย้าย หรือลากขอบเพื่อเปลี่ยนเวลาเริ่มต้นหรือสิ้นสุด", + "playLine": "เล่นบรรทัด", + "undoAdjustment": "เลิกทำการปรับ", + "playPause": "เล่น / หยุดชั่วคราว", + "playAligned": "จัดแนวแล้ว", + "playAlignedHint": "เล่นบรรทัดที่เลือกด้วยไทม์โค้ดที่จัดแนวแล้ว", + "apply": "นำไปใช้", + "applyHint": "ยกเลิกเครื่องหมายถูกที่บรรทัดเพื่อคงไทม์โค้ดเดิมไว้ ดับเบิลคลิกบรรทัดเพื่อเล่น สามารถย้ายหรือปรับขนาดบรรทัดในรูปคลื่นเสียงที่จัดแนวแล้วได้ด้วย" + }, "beautifyTimeCodesProfile": { "title": "แก้ไขโปรไฟล์การปรับแต่งไทม์โค้ด", "loadPreset": "โหลดพรีเซ็ต", "presetDefault": "ค่าเริ่มต้น", "presetNetflix": "Netflix", "presetSdi": "SDI", - "general": "ทั่วไป", + "saveAsProfile": "บันทึกเป็นโปรไฟล์...", + "deleteProfile": "ลบโปรไฟล์", + "profileName": "ชื่อโปรไฟล์", + "overwriteProfileX": "มีโปรไฟล์ชื่อ \"{0}\" อยู่แล้ว เขียนทับหรือไม่?", + "deleteProfileX": "ลบโปรไฟล์ \"{0}\" หรือไม่?", "gap": "ช่องว่าง", "gapSuffix": "เฟรม (จะอัปเดตช่องช่องว่างที่ไม่เป็นศูนย์ด้านล่างด้วย)", "milliseconds": "มิลลิวินาที", @@ -1993,9 +2124,12 @@ "hintChaining": "วิธีจัดการซับไตเติลที่เกือบชิดกัน — ขยายให้ติดกัน หรือดันออกจากกัน ขึ้นอยู่กับว่ามีจุดเปลี่ยนฉากอยู่ระหว่างกลางหรือไม่" }, "bridgeGaps": { - "title": "เชื่อมช่องว่าง", "bridgeGapsSmallerThan": "เชื่อมช่องว่างที่เล็กกว่า (มิลลิวินาที)", "minGap": "ช่องว่างต่ำสุด (มิลลิวินาที)", + "bridgeGapsSmallerThanFrames": "เชื่อมช่องว่างที่เล็กกว่า (เฟรม)", + "minGapFrames": "ช่องว่างต่ำสุด (เฟรม)", + "calculateBridgeGapsSmallerThanDotDotDot": "คำนวณขีดจำกัดการเชื่อมช่องว่างจากอัตราเฟรม...", + "useXMsAsBridgeGapsSmallerThan": "เชื่อมช่องว่างที่เล็กกว่า \"{0}\" มิลลิวินาทีหรือไม่?", "numberOfSmallGapsBridgedX": "จำนวนช่องว่างเล็กที่เชื่อมแล้ว: {0}", "percentFoPrevious": "ช่องว่างสำหรับรายการก่อนหน้า (%)", "gapChange": "การเปลี่ยนแปลงช่องว่าง" @@ -2007,8 +2141,6 @@ "sortBy": { "title": "เรียงลำดับซับไตเติล", "sortOrder": "ลำดับการเรียง", - "sortByNumber": "หมายเลข", - "sortByStartTime": "เวลาเริ่มต้น", "sortByEndTime": "เวลาสิ้นสุด" }, "batchConvert": { @@ -2031,6 +2163,7 @@ "addColor": "เพิ่มสี", "deleteLinesWithSpecificActorsOrStyles": "ลบบรรทัดที่มีนักแสดงหรือสไตล์ (คั่นหลายรายการด้วยจุลภาค)", "useSourceStylesIfPossible": "ใช้สไตล์ต้นทางหากเป็นไปได้", + "keepSourceEmbeddedFonts": "คงฟอนต์ที่ฝังในต้นฉบับ (ส่วนท้าย)", "editStyles": "แก้ไขสไตล์", "editProperties": "แก้ไขคุณสมบัติ", "editAttachments": "แก้ไขไฟล์แนบ", @@ -2051,7 +2184,6 @@ "assaChangeStyleFromStyle": "เปลี่ยนสไตล์จาก", "assaChangeStyleToStyle": "เป็น", "assaChangeStyleImportStyle": "นำเข้าสไตล์...", - "assaChangeStyleImportedX": "นำเข้าแล้ว: {0}", "assaChangeStyleTrimUnusedStyles": "ตัดสไตล์ที่ไม่ได้ใช้ออก", "assaChangeStylePropertiesTitle": "เปลี่ยนคุณสมบัติของสไตล์", "assaChangeStylePropertiesInfo": "เปลี่ยนเฉพาะฟิลด์ที่เลือกในทุกสไตล์ในไฟล์ โดยไม่แตะต้องตัวสไตล์เอง", @@ -2063,13 +2195,52 @@ "convertColorsToDialogReBreakLines": "ตัดบรรทัดใหม่", "snapTimeCodesToFramesInfo": "ปัดเวลาเริ่มต้นและสิ้นสุดทุกค่าให้ตรงเฟรมที่ใกล้ที่สุด อัตราเฟรมจะอ่านจากไฟล์วิดีโอที่ชื่อเดียวกับไฟล์คำบรรยาย เว้นแต่จะเลือกอัตราเฟรมคงที่", "addFolderDotDotDot": "เพิ่มโฟลเดอร์...", + "addFolderRecursiveDotDotDot": "เพิ่มโฟลเดอร์รวมโฟลเดอร์ย่อย...", "selectFolderToConvert": "เลือกโฟลเดอร์ที่มีไฟล์ที่ต้องการแปลง", "includeSubfolders": "รวมโฟลเดอร์ย่อยเมื่อเพิ่มโฟลเดอร์", + "videoFilesWhenAddingFolder": "ไฟล์วิดีโอ (mkv, mp4, ts...) เมื่อเพิ่มโฟลเดอร์", + "videoFilesAsk": "ถาม", + "videoFilesInclude": "เพิ่มแทร็กคำบรรยายของไฟล์เหล่านั้น", + "videoFilesSkip": "ข้าม", + "folderContainsXVideoFiles": "โฟลเดอร์มีไฟล์วิดีโอ {0} ไฟล์ (mkv, mp4, ts...)\n\nเพิ่มแทร็กคำบรรยายที่ฝังอยู่ด้วย หรือเฉพาะไฟล์คำบรรยาย?", + "addVideoFiles": "เพิ่มไฟล์วิดีโอ", + "skipVideoFiles": "เฉพาะไฟล์คำบรรยาย", + "doNotAskAgainVideoFiles": "ไม่ต้องถามอีก (เปลี่ยนได้ในการตั้งค่าแปลงเป็นชุด)", + "settingsSectionOutput": "ผลลัพธ์", + "settingsSectionAddingFiles": "การเพิ่มไฟล์", + "settingsSectionImageBasedSubtitles": "คำบรรยายแบบภาพ (OCR)", + "settingsSectionOther": "อื่นๆ", "keepSourceFileTimestamp": "คงวันที่/เวลาของไฟล์ต้นทางไว้ในไฟล์ผลลัพธ์", - "scanningFolderX": "กำลังสแกน {0}..." + "preventSleepWhileConverting": "ป้องกันไม่ให้คอมพิวเตอร์เข้าสู่โหมดสลีประหว่างการแปลง", + "scanningFolderX": "กำลังสแกน {0}...", + "transportStreamSettings": "การตั้งค่า Transport Stream", + "transportStreamSettingsDotDotDot": "การตั้งค่า Transport Stream...", + "transportStreamSettingsInfo": "ใช้กับแทร็กคำบรรยายที่แยกจากไฟล์ Transport Stream (.ts/.m2ts) ตำแหน่งและขนาดวิดีโอมีผลเฉพาะแทร็กภาพ DVB ที่ส่งออกเป็นรูปแบบแบบภาพเท่านั้น", + "transportStreamOverrideXPosition": "แทนที่ตำแหน่ง X เดิม", + "transportStreamOverrideYPosition": "แทนที่ตำแหน่ง Y เดิม", + "transportStreamOverrideVideoSize": "แทนที่ขนาดวิดีโอเดิม", + "transportStreamBottomMargin": "ระยะขอบล่าง", + "transportStreamFileNameEnding": "ส่วนท้ายชื่อไฟล์", + "transportStreamFileNameEndingInfo": "เพิ่มไว้หน้านามสกุลไฟล์สำหรับแต่ละแทร็กที่แยกออกมา เว้นว่างไว้เพื่อใช้รหัสภาษาต่อท้ายปกติ", + "transportStreamOnlyTeletext": "เฉพาะเทเลเท็กซ์", + "transportStreamGetSizeFromVideo": "รับขนาดจากวิดีโอ...", + "twoLetterLanguageCodeUppercase": "รหัสภาษาสองตัวอักษร (ตัวพิมพ์ใหญ่)", + "threeLetterLanguageCodeUppercase": "รหัสภาษาสามตัวอักษร (ตัวพิมพ์ใหญ่)", + "formatContainsDotDotDot": "รูปแบบมีคำว่า...", + "extensionIsDotDotDot": "นามสกุลไฟล์คือ...", + "statusIsError": "สถานะเป็นข้อผิดพลาด", + "forcedTracksOnly": "เฉพาะแทร็กบังคับแสดง", + "xConvertedYFailedInZ": "แปลงแล้ว {0:#,###,##0}, ล้มเหลว {1:#,###,##0} ในเวลา {2}", + "xNotProcessed": "ไม่ได้ประมวลผล {0:#,###,##0}", + "removeUnicodeControlCharactersTitle": "ลบอักขระควบคุม Unicode", + "removeUnicodeControlCharactersInfo": "ลบอักขระควบคุมทิศทาง Unicode ที่มองไม่เห็น (LRM, RLM, LRE, RLE, PDF, LRO และ RLO) ออกจากข้อความ และแทนที่ช่องว่างไม่ตัดบรรทัดด้วยช่องว่างปกติ", + "preset": "พรีเซ็ต", + "savePresetDotDotDot": "บันทึกพรีเซ็ต...", + "deletePreset": "ลบพรีเซ็ต", + "presetName": "ชื่อพรีเซ็ต", + "deletePresetX": "ลบพรีเซ็ต \"{0}\" หรือไม่?" }, "changeCasing": { - "title": "เปลี่ยนตัวพิมพ์", "fixNames": "แก้ไขชื่อ", "namesX": "ชื่อ: {0:#,##0}", "hitsX": "พบ: {0:#,##0}", @@ -2080,9 +2251,7 @@ "allUppercase": "ตัวพิมพ์ใหญ่ทั้งหมด", "allLowercase": "ตัวพิมพ์เล็กทั้งหมด" }, - "changeFormatting": { - "title": "เปลี่ยนการจัดรูปแบบ" - }, + "changeFormatting": {}, "convertActors": { "title": "แปลงนักแสดง", "convertActorFrom": "แปลงนักแสดงจาก", @@ -2111,18 +2280,12 @@ "outputFormatAssa": "Advanced Sub Station Alpha (.ass)", "style1": "สไตล์ 1", "style2": "สไตล์ 2", - "outlineWidth": "ความหนาเส้นขอบ", - "shadowWidth": "ความกว้างเงา", - "alignTop": "บน", - "alignBottom": "ล่าง", - "merge": "ผสาน", - "pleaseLoadTwoSubtitles": "โปรดโหลดคำบรรยายสองไฟล์" + "merge": "ผสาน" }, "splitSubtitle": { "title": "แยกคำบรรยาย", "numberOfEqualParts": "จำนวนส่วนที่เท่ากัน", "saveSplitParts": "บันทึกส่วนที่แยก", - "subtitleSplitIntoXParts": "แยกคำบรรยายเป็น {0} ส่วนแล้ว", "xPartsSavedInFormatYToFolder": "บันทึก {0} ส่วนในรูปแบบ {1} ไปยังโฟลเดอร์:" }, "splitBreakLongLines": { @@ -2156,18 +2319,17 @@ "columnMerge": "ผสาน", "columnFirst": "บรรทัดแรก", "columnSecond": "บรรทัดที่สอง", - "columnMerged": "ผสานแล้ว", - "selectAll": "เลือกทั้งหมด", - "inverseSelection": "สลับการเลือก" + "columnMerged": "ผสานแล้ว" }, "mergeLinesWithSameText": { - "title": "ผสานบรรทัดที่มีข้อความเหมือนกัน", "maxMsBetweenLines": "มิลลิวินาทีสูงสุดระหว่างบรรทัด", - "includeIncrementingLines": "รวมบรรทัดที่มีข้อความเพิ่มขึ้นทีละส่วน" + "maxFramesBetweenLines": "จำนวนเฟรมสูงสุดระหว่างบรรทัด", + "includeIncrementingLines": "รวมบรรทัดที่มีข้อความเพิ่มขึ้นทีละส่วน", + "includeRollUpCaptions": "รวมคำบรรยายแบบ roll-up (เลื่อน)" }, "mergeLinesWithSameTimeCodes": { - "title": "ผสานบรรทัดที่มีรหัสเวลาเหมือนกัน", "maxMsDifference": "ความต่างสูงสุด (มิลลิวินาที)", + "maxFramesDifference": "ความต่างสูงสุด (เฟรม)", "makeDialog": "ทำเป็นบทสนทนา" }, "netflixCheckAndFix": { @@ -2190,7 +2352,6 @@ "shotChanges": "จุดเปลี่ยนฉาก", "startNumberSpellOut": "เขียนตัวเลขต้นประโยคเป็นตัวหนังสือ", "textforHiUseBrackets": "ข้อความสำหรับผู้บกพร่องทางการได้ยิน ใช้วงเล็บเหลี่ยม", - "frameRate": "อัตราเฟรม", "twoFrameGrap": "ช่องว่างสองเฟรม", "whiteSpace": "ช่องว่าง", "childrensProgram": "รายการสำหรับเด็ก", @@ -2228,7 +2389,6 @@ "italicsFixed": "แก้ไขตัวเอียงแล้ว", "italicsNotAllowed": "ไม่อนุญาตให้ใช้ตัวเอียง", "bridgeGapsXToYFrames": "ช่องว่าง 3-{0} เฟรม => ช่องว่าง 2 เฟรม", - "startNumberSpellOutComment": "เมื่อตัวเลขขึ้นต้นประโยค ควรเขียนเป็นตัวหนังสือเสมอ", "maximumXCharactersPerSecond": "สูงสุด {0} อักขระต่อวินาที" }, "imageBasedEdit": { @@ -2237,10 +2397,8 @@ "resizeImagesDotDotDot": "ปรับขนาดรูปภาพ...", "adjustBrightnessDotDotDot": "ปรับความสว่าง...", "adjustAlphaDotDotDot": "ปรับค่าอัลฟา...", - "centerHorizontally": "จัดกึ่งกลางแนวนอน", "cropImages": "ตัดขอบรูปภาพ", "importTimeCodes": "นำเข้ารหัสเวลา...", - "setTextForSubtitle": "กำหนดข้อความให้คำบรรยาย", "setText": "กำหนดข้อความ", "screenWidth": "ความกว้างหน้าจอ", "screenHeight": "ความสูงหน้าจอ", @@ -2271,15 +2429,31 @@ "letterboxCustom": "ความสูงแถบกำหนดเอง", "barHeightPx": "ความสูงแถบ (พิกเซล)", "titleSafePercent": "พื้นที่ปลอดภัยของข้อความ (%)", + "moveCaptions": "ย้ายคำบรรยาย", + "moveCaptionsDotDotDot": "ย้ายคำบรรยาย...", + "moveCaptionsTo": "ย้ายคำบรรยาย", + "moveCaptionsIntoBars": "เข้าไปในแถบดำ letterbox (นอกภาพ)", + "moveCaptionsIntoPicture": "ภายในภาพ (ออกจากแถบดำ)", + "moveCaptionsOffset": "ระยะห่างจากขอบ (px)", + "letterboxBarHeightXPxYOfZCaptionsWillMove": "ความสูงแถบ: {0} px - คำบรรยาย {1} จาก {2} รายการจะถูกย้าย", + "removeFadeInOut": "ลบเฟดเข้า/ออก", + "removeFadeInOutXLinesRemoved": "รวมบรรทัดเฟด {0} บรรทัดเข้ากับบรรทัดที่เป็นเจ้าของแล้ว", + "removeFadeInOutNothingFound": "ไม่พบบรรทัดเฟดเข้า/ออก", "positionSummary": "{0}×{1} - {2} คำบรรยาย - ความสูงแถบ: {3} พิกเซล", "xInActivePicture": "{0} ในภาพ", "xInTopBar": "{0} ในแถบบน", "xInBottomBar": "{0} ในแถบล่าง", "noImageSubtitlesLoaded": "ไม่ได้โหลดคำบรรยายแบบรูปภาพ", - "imageBasedFormatNotSupported": "ไม่พบ/ไม่รองรับรูปแบบคำบรรยายแบบภาพ" + "imageBasedFormatNotSupported": "ไม่พบ/ไม่รองรับรูปแบบคำบรรยายแบบภาพ", + "frameRateDeclaredInFile": "อัตราเฟรมที่ระบุในไฟล์ Blu-ray sup", + "frameRateDetectedFromTimeCodesX": "อัตราเฟรมที่ตรวจพบจากไทม์โค้ดของ Blu-ray sup (ไฟล์ระบุไว้ {0})", + "frameRateFromVideo": "อัตราเฟรมของวิดีโอ", + "frameRateCurrent": "อัตราเฟรมปัจจุบัน", + "frameRateSetManually": "อัตราเฟรมที่กำหนดเอง", + "frameRateUsageInfo": "การส่งออก Blu-ray sup, BDN XML, DOST และ FCP จะเขียนด้วยอัตราเฟรมนี้ ไม่ได้เปลี่ยนเวลาใดๆ - ใช้ \"เปลี่ยนอัตราเฟรม\" สำหรับกรณีนั้น", + "useVideoFrameRateXInsteadOfY": "อัตราเฟรมของวิดีโอคือ {0} แต่คำบรรยายถูกตั้งไว้ที่ {1}\n\nใช้อัตราเฟรมของวิดีโอ ({0}) หรือไม่?" }, "removeTextForHearingImpaired": { - "title": "ลบข้อความสำหรับผู้บกพร่องทางการได้ยิน", "interjections": "คำอุทาน", "skipIfStartWith": "ข้ามหากขึ้นต้นด้วย", "removeTextBetween": "ลบข้อความระหว่าง", @@ -2298,7 +2472,6 @@ "removeInterjections": "ลบคำอุทาน", "linesFoundX": "พบบรรทัด: {0}" }, - "pickAlignmentTitle": "เลือกการจัดตำแหน่ง", "removeUnicodeCharacters": { "title": "ลบ/แทนที่อักขระ Unicode", "charactersFoundX": "พบอักขระ Unicode: {0}", @@ -2308,13 +2481,13 @@ "pickFontNameInstalledFonts": "ฟอนต์ที่ติดตั้งไว้", "pickFontNameCollectedFonts": "ฟอนต์ที่รวบรวมไว้", "colorPickerTitle": "เลือกสี", + "colorPickerBrightness": "ความสว่าง", "filterLayersTitle": "กรองเลเยอร์สำหรับการแสดงผล", "filterLayersHideFromWaveform": "ซ่อนจากรูปคลื่นเสียง/สเปกโตรแกรม", "filterLayersHideFromSubtitleGrid": "ซ่อนจากตารางคำบรรยาย", "filterLayersHideFromVideoPreview": "ซ่อนจากตัวอย่างวิดีโอ", "pickSubtitleFormat": "เลือกรูปแบบคำบรรยาย", "pickSubtitleFormatImageBasedNoPreview": "รูปแบบคำบรรยายแบบรูปภาพ\nบิตแมปจะถูกสร้างระหว่างการแปลง - ไม่มีตัวอย่างข้อความ", - "pickLayerTitle": "กำหนดเลเยอร์", "recentColors": "สีที่ใช้ล่าสุด", "fixCommonErrorsSettingsTitle": "การตั้งค่าการแก้ไขข้อผิดพลาดทั่วไป" }, @@ -2359,59 +2532,51 @@ "correctWordsX": "คำที่ถูกต้อง: {0}", "namesX": "ชื่อ: {0}", "addedToDictionaryX": "เพิ่มลงพจนานุกรม: {0}", - "doNotShowThisAgain": "ไม่ต้องแสดงข้อความนี้อีก" + "doNotShowThisAgain": "ไม่ต้องแสดงข้อความนี้อีก", + "editUseAlwaysList": "แก้ไขรายการ \"ใช้เสมอ\"...", + "useAlwaysListTitle": "ตรวจสอบการสะกด - รายการ \"ใช้เสมอ\"", + "useAlwaysListHeader": "รายการ \"ใช้เสมอ\"", + "useAlwaysListDescription": "คำที่ตัวตรวจสอบการสะกดแทนที่โดยอัตโนมัติ บันทึกโดย \"เปลี่ยนทั้งหมด\" และ \"ใช้เสมอ\" คู่คำจะถูกใช้เฉพาะเมื่อคำนั้นสะกดผิดเท่านั้น", + "useAlwaysWord": "คำที่สะกดผิด", + "xPairs": "{0:#,##0} คู่", + "xUnusedPairs": "ไม่เคยใช้ {0:#,##0} คู่", + "useAlwaysUnusedHint": "ไม่เคยใช้ - คำนี้สะกดถูกต้อง (หรือเป็นชื่อหรือคำของผู้ใช้) ตัวตรวจสอบการสะกดจึงไม่เคยทำเครื่องหมาย", + "removeUnused": "ลบที่ไม่ได้ใช้", + "useAlwaysEmpty": "ยังไม่มีคู่คำ \"ใช้เสมอ\" สำหรับภาษานี้", + "useAlwaysEmptyHint": "คู่คำจะถูกเพิ่มเมื่อคุณคลิก \"เปลี่ยนทั้งหมด\" หรือ \"ใช้เสมอ\" ในการตรวจสอบการสะกด", + "useAlwaysNoMatch": "ไม่มีคู่คำที่ตรงกับการค้นหา", + "useAlwaysSameWord": "คำที่ใช้แทนต้องแตกต่างจากคำเดิม", + "useAlwaysRememberOff": "\"จดจำรายการ 'ใช้เสมอ'\" ถูกปิดอยู่ในการตั้งค่า ตัวตรวจสอบการสะกดจึงไม่ใช้หรือบันทึกรายการนี้" }, "video": { "burnIn": { "title": "สร้างวิดีโอพร้อมคำบรรยายฝังถาวร", - "infoAssaOff": "หมายเหตุ: รองรับการจัดสไตล์ Advanced SubStation Alpha", - "infoAssaOn": "หมายเหตุ: จะใช้การจัดสไตล์ Advanced SubStation Alpha :)", - "xGeneratedWithBurnedInSubsInX": "สร้าง \"{0}\" พร้อมคำบรรยายฝังถาวรใน {1} แล้ว", - "timeRemainingMinutes": "เวลาที่เหลือ: {0} นาที", - "timeRemainingOneMinute": "เวลาที่เหลือ: หนึ่งนาที", - "timeRemainingSeconds": "เวลาที่เหลือ: {0} วินาที", - "timeRemainingAFewSeconds": "เวลาที่เหลือ: อีกไม่กี่วินาที", - "timeRemainingMinutesAndSeconds": "เวลาที่เหลือ: {0} นาที {1} วินาที", - "timeRemainingOneMinuteAndSeconds": "เวลาที่เหลือ: หนึ่งนาที {0} วินาที", - "targetFileName": "ชื่อไฟล์ปลายทาง: {0}", "targetFileSize": "ขนาดไฟล์เป้าหมาย (ต้องเข้ารหัสแบบ 2 รอบ)", "fileSizeMb": "ขนาดไฟล์เป็น MB", "matchSourceVideoSize": "ให้ตรงกับขนาดวิดีโอต้นฉบับ", - "passX": "รอบที่ {0}", - "encoding": "การเข้ารหัส", "bitRate": "อัตราบิต", "totalBitRateX": "อัตราบิตรวม: {0}", "sampleRate": "อัตราสุ่มตัวอย่าง", "audio": "เสียง", - "stereo": "สเตอริโอ", "preset": "พรีเซ็ต", + "tune": "Tune", "pixelFormat": "รูปแบบพิกเซล", "crf": "CRF", - "tuneFor": "ปรับจูนสำหรับ", - "alignRight": "จัดชิดขวา", - "getStartPosition": "รับตำแหน่งเริ่มต้น", - "getEndPosition": "รับตำแหน่งสิ้นสุด", "useSource": "ใช้ต้นฉบับ", - "useSourceFolder": "ใช้โฟลเดอร์ต้นฉบับ", - "useSourceResolution": "ใช้ความละเอียดต้นฉบับ", - "outputSettings": "ไฟล์/โฟลเดอร์ผลลัพธ์...", "fontSizeFactor": "ตัวคูณขนาดฟอนต์", "boxType": "ชนิดกล่อง", - "fixRightToLeft": "แก้ไขข้อความขวาไปซ้าย", - "cut": "ตัด", "fromTime": "จากเวลา", "toTime": "ถึงเวลา", "audioEncoding": "การเข้ารหัสเสียง", "outputProperties": "คุณสมบัติผลลัพธ์...", "videoFileSize": "ขนาดไฟล์วิดีโอ", "oneBox": "กล่องเดียว", - "boxPerLine": "กล่องต่อบรรทัด", - "logoInfo": "เลือกรูปภาพ PNG แล้วลากเพื่อจัดวางตำแหน่งบนวิดีโอ" + "logoInfo": "เลือกรูปภาพ PNG แล้วลากเพื่อจัดวางตำแหน่งบนวิดีโอ", + "noSubtitlesGenerateAnyway": "ไม่มีบรรทัดคำบรรยายที่จะฝัง\n\nสร้างวิดีโอโดยไม่มีคำบรรยายหรือไม่?", + "noSubtitlesInXItemsGenerateAnyway": "{0} รายการในชุดไม่มีบรรทัดคำบรรยายที่จะฝัง\n\nสร้างวิดีโอเหล่านั้นโดยไม่มีคำบรรยายหรือไม่?" }, "videoTransparent": { - "title": "สร้างวิดีโอโปร่งใสพร้อมคำบรรยาย", - "infoAssaOff": "หมายเหตุ: รองรับการจัดสไตล์ Advanced SubStation Alpha", - "infoAssaOn": "หมายเหตุ: จะใช้การจัดสไตล์ Advanced SubStation Alpha :)" + "title": "สร้างวิดีโอโปร่งใสพร้อมคำบรรยาย" }, "audioToText": { "title": "แปลงเสียงพูดเป็นข้อความ", @@ -2419,6 +2584,21 @@ "translateToEnglish": "แปลเป็นภาษาอังกฤษ", "transcribing": "กำลังถอดเสียง...", "transcribingXOfY": "กำลังถอดเสียง {0} จาก {1}...", + "linesTranscribedXOfYFailedZ": "ถอดเสียงแล้ว {0} จาก {1} บรรทัด - {2} บรรทัดล้มเหลวหรือไม่มีเสียงพูด\n\nบรรทัดที่ล้มเหลวจะคงข้อความปัจจุบันไว้ นำบรรทัดที่ถอดเสียงแล้วไปใช้ หรือลองบรรทัดที่ล้มเหลวอีกครั้ง (คุณสามารถเปลี่ยนเอนจินหรือการตั้งค่าก่อนได้)?", + "linesTranscribedXOfYCancelled": "การถอดเสียงถูกยกเลิกหลังจาก {0} จาก {1} บรรทัด\n\nนำบรรทัดที่ถอดเสียงแล้วไปใช้หรือไม่? บรรทัดอื่นจะคงข้อความปัจจุบันไว้", + "noLinesTranscribed": "ไม่สามารถถอดเสียงบรรทัดใดจาก {0} บรรทัดได้\nโปรดตรวจสอบบันทึกของเครื่องมือเพื่อดูรายละเอียด", + "applyTranscribedLines": "นำที่ถอดเสียงแล้วไปใช้", + "retryFailedLines": "ลองที่ล้มเหลวอีกครั้ง", + "isolateSpeech": "แยกเสียงพูด (ช้า)", + "isolateSpeechHint": "ลบดนตรีและเสียงประกอบออกก่อนถอดเสียง ทำให้พลาดบรรทัดน้อยลงและเวลาแม่นยำขึ้นในเสียงที่มีดนตรีดัง แต่ใช้เวลาประมาณเท่ากับความยาวเสียงเมื่อมี GPU - และนานกว่านั้นหลายเท่าหากไม่มี", + "isolatingSpeech": "กำลังแยกเสียงพูด...", + "isolateSpeechFailed": "ไม่สามารถแยกเสียงพูดได้ - จึงถอดเสียงจากเสียงต้นฉบับแทน", + "detectSpeakers": "ตรวจจับผู้พูด", + "detectSpeakersHint": "ขึ้นต้นแต่ละบรรทัดด้วยผู้พูด เช่น \"(speaker 0)\" โดยใช้ Sortformer diarization ของ NVIDIA (สูงสุด 8 ผู้พูด) ทำงานได้ดีที่สุดกับแบ็กเอนด์ที่แบ่งบรรทัดตรงที่ผู้พูดเปลี่ยน เช่น Parakeet และ Cohere", + "detectSpeakersNeedsNewerCrispAsr": "\"ตรวจจับผู้พูด\" ต้องใช้ Crisp ASR v{0} หรือใหม่กว่า แต่ติดตั้ง v{1} อยู่\n\nอัปเดต Crisp ASR (ปุ่มรูปเฟืองข้างเอนจิน) หรือปิด \"ตรวจจับผู้พูด\"", + "xNeedsNewerCrispAsr": "{0} ต้องใช้ Crisp ASR v{1} หรือใหม่กว่า แต่ติดตั้ง v{2} อยู่\n\nดาวน์โหลดและติดตั้งอัปเดตตอนนี้หรือไม่?", + "vad": "VAD", + "vadHint": "การตรวจจับเสียงพูด (Voice activity detection): ถอดเสียงเฉพาะเสียงพูด โดยตัดตรงที่ผู้พูดหยุด\nอัตโนมัติ: Silero และเฉพาะเอนจินที่ต้องใช้ (Cohere, Mega, Index-Echo)\nSilero, FireRedVAD, Whisper-VAD, WebRTC: เปิดเสมอสำหรับทุกเอนจิน Crisp ASR FireRedVAD แม่นยำที่สุด Whisper-VAD ให้การตัดน้อยที่สุดและสะอาดที่สุดแต่ช้าที่สุด\nIndex-Echo ใช้ Silero เสมอ --vad, --vad-model หรือ --chunk-seconds ในการตั้งค่าขั้นสูงมีลำดับความสำคัญสูงกว่า", "inputLanguage": "ภาษาต้นทาง", "advancedWhisperSettings": "พารามิเตอร์แปลงเสียงพูดเป็นข้อความขั้นสูง", "downloadingSpeechToTextEngine": "กำลังดาวน์โหลดเอนจินแปลงเสียงพูดเป็นข้อความ", @@ -2433,8 +2613,6 @@ "addCustomModelDotDotDot": "เพิ่มโมเดลกำหนดเอง...", "customModelHelp": "คุณสามารถใช้โมเดลของคุณเอง: เลือกไฟล์โมเดล (whisper.cpp ggml '.bin') หรือโฟลเดอร์โมเดล (โฟลเดอร์ faster-whisper ที่มี 'model.bin' อยู่ภายใน) โมเดลจะถูกคัดลอกไปยังโฟลเดอร์โมเดลและเพิ่มลงในรายการด้านบน", "viewToolsLogFile": "ดูไฟล์บันทึกของเครื่องมือ", - "reDownloadX": "ดาวน์โหลด {0} อีกครั้ง", - "downloadX": "ดาวน์โหลด {0}", "updateXTitle": "อัปเดต {0} หรือไม่?", "updateXMessage": "มี {0} เวอร์ชันใหม่กว่า{1}{1}ดาวน์โหลดและติดตั้งการอัปเดตตอนนี้หรือไม่?", "downloadingSpeechToTextModel": "กำลังดาวน์โหลดโมเดลแปลงเสียงพูดเป็นข้อความ", @@ -2446,14 +2624,6 @@ "fixCasing": "แก้ไขตัวพิมพ์", "addPeriods": "เพิ่มจุด", "changeUnderlineToColor": "เปลี่ยนขีดเส้นใต้เป็นสี", - "cueBuilding": "การสร้างคิว (MLX Whisper)", - "cueRebuild": "สร้างคิวใหม่จากเวลากำกับระดับคำ", - "cueMaxChars": "อักขระสูงสุดต่อคิว", - "cueMaxSeconds": "วินาทีสูงสุดต่อคิว", - "cueMaxCps": "อักขระสูงสุดต่อวินาที", - "vocabularyPrompt": "พรอมต์คำศัพท์ (ชื่อ สถานที่ คำเฉพาะ)", - "beamSize": "ขนาด beam", - "beamSizeNote": "0 ถอดรหัสเร็วที่สุด 5 ให้ความแม่นยำดีที่สุด (เหมือน Faster Whisper) โดยใช้เวลาเพิ่มขึ้นราว 20-30% ค่าที่มากกว่า 5 แทบไม่ช่วยอะไร", "removeNonSpeechLines": "ลบบรรทัดที่ไม่ใช่เสียงพูด", "removeNonSpeechLinesHint": "ตัดบรรทัดที่บรรยายเฉพาะเสียงประกอบออก เช่น \"[Music]\" หรือ \"(waves crashing)\"", "removeRepeatedLines": "ลบบรรทัดที่ซ้ำกัน", @@ -2489,14 +2659,11 @@ }, "textToSpeech": { "title": "แปลงข้อความเป็นเสียงพูด", - "textToSpeechEngine": "เอนจินแปลงข้อความเป็นเสียงพูด", "reviewAudioSegments": "TTS - ตรวจทานส่วนเสียง", "reviewAudioSegmentsHistory": "TTS - ตรวจทานประวัติเสียง", "stability": "ความเสถียร", "similarity": "ความคล้ายคลึง", "speakerBoost": "เพิ่มพลังเสียงผู้พูด", - "styleExaggeration": "การเน้นสไตล์", - "regenerateAudioSelectedLine": "สร้างเสียงใหม่สำหรับบรรทัดที่เลือก", "generateSpeechFromText": "สร้างเสียงพูดจากข้อความ", "testVoice": "ทดสอบเสียง", "addAudioToVideoFile": "เพิ่มเสียงลงไฟล์วิดีโอ", @@ -2515,6 +2682,19 @@ "videoEncodingSettings": "TTS - การตั้งค่าการเข้ารหัสวิดีโอ", "elevenLabsSettings": "TTS - การตั้งค่า ElevenLabs", "elevenLabsSettingsResetHint": "คืนค่าการตั้งค่า ElevenLabs เป็นค่าเริ่มต้น", + "provider": "ผู้ให้บริการ", + "openAiCompatibleSettings": "TTS - การตั้งค่าที่เข้ากันได้กับ OpenAI", + "customServerUrl": "URL เซิร์ฟเวอร์กำหนดเอง", + "customServerUrlHint": "ปลายทางเสียงพูดแบบเต็มของเซิร์ฟเวอร์ที่เข้ากันได้กับ OpenAI ใช้เมื่อผู้ให้บริการเป็น \"Custom\" เช่น http://localhost:8880/v1/audio/speech", + "customModels": "โมเดลกำหนดเอง", + "customModelsHint": "ชื่อโมเดลคั่นด้วยจุลภาคที่แสดงให้เลือกเมื่อผู้ให้บริการเป็น \"Custom\"", + "customVoices": "เสียงกำหนดเอง", + "customVoicesHint": "ชื่อเสียงคั่นด้วยจุลภาค ใช้โดยผู้ให้บริการ \"Custom\" และโดยโมเดล OpenRouter ที่ไม่ได้เผยแพร่รายการเสียง", + "instructions": "คำสั่ง", + "instructionsHint": "สไตล์การพูด (ไม่บังคับ) เช่น \"Speak in a calm, warm voice\" ส่งเฉพาะไปยังโมเดลที่รองรับ (ไม่ใช่ tts-1/tts-1-hd)", + "openAiSpeedHint": "ความเร็วเสียงพูดตั้งแต่ 0.25 ถึง 4.0 (1.0 คือปกติ) ไม่ใช่ทุกโมเดลที่รองรับ", + "outputFormat": "รูปแบบผลลัพธ์", + "outputFormatHint": "รูปแบบเสียงที่ขอจากเซิร์ฟเวอร์ \"Auto\" จะขอ MP3 และเปลี่ยนเป็น PCM สำหรับโมเดลที่ส่งคืนเฉพาะ PCM (เช่น Gemini TTS)", "regenerateAudio": "สร้างเสียงใหม่", "autoContinuePlaying": "เล่นต่ออัตโนมัติ", "playLine": "เล่นบรรทัด", @@ -2523,6 +2703,13 @@ "addingAudioToVideoFileDotDotDot": "กำลังเพิ่มเสียงลงไฟล์วิดีโอ...", "preparingMergeDotDotDot": "กำลังเตรียมการรวม...", "importVoiceDotDotDot": "นำเข้าเสียง...", + "renameVoiceDotDotDot": "เปลี่ยนชื่อเสียง...", + "renameVoiceTitle": "เปลี่ยนชื่อเสียง", + "voiceXCouldNotBeRenamedX": "ไม่สามารถเปลี่ยนชื่อเสียง '{0}': {1}", + "deleteVoiceDotDotDot": "ลบเสียง...", + "deleteVoiceTitle": "ลบเสียง", + "deleteVoiceXQuestion": "ลบเสียง '{0}' หรือไม่?\n\nไฟล์บันทึกเสียงอ้างอิงและไฟล์ที่เกี่ยวข้องจะถูกลบออกจากดิสก์", + "voiceXCouldNotBeDeletedX": "ไม่สามารถลบเสียง '{0}': {1}", "voiceImportSuccessTitle": "นำเข้าเสียงแล้ว", "voiceXImported": "นำเข้าเสียง '{0}' สำเร็จ", "voiceXCouldNotBeImported": "ไม่สามารถนำเข้าเสียง '{0}' ได้ - ดูรายละเอียดในบันทึก", @@ -2532,6 +2719,49 @@ "dropAudioFileHereToImportVoice": "วางไฟล์เสียงที่นี่เพื่อนำเข้าเสียง", "dropAudioFileHereHint": ".wav หรือ .mp3", "voiceCloneTranscriptTitle": "ป้อนบทถอดเสียงของไฟล์เสียง (จำเป็นสำหรับการโคลนเสียง)", + "voiceManagerTitle": "TTS - ตัวจัดการเสียง", + "voiceManagerDotDotDot": "ตัวจัดการเสียง...", + "voiceManagerMenuItem": "ตัวจัดการเสียง TTS...", + "speakFromCurrentLineTitle": "พูดจากบรรทัดปัจจุบัน", + "speakFromCurrentLineDotDotDot": "พูดจากบรรทัดปัจจุบัน...", + "speakFromCurrentLineHint": "อ่านบรรทัดออกเสียงทีละบรรทัด เริ่มจากบรรทัดปัจจุบัน กด Escape หรือคลิกบรรทัดอื่นเพื่อหยุด", + "stopSpeaking": "หยุดพูด", + "speakingLineX": "กำลังพูดบรรทัด {0}... (Escape เพื่อหยุด)", + "playWithSpeechTitle": "เล่นพร้อมเสียงพูด", + "playWithSpeechDotDotDot": "เล่นพร้อมเสียงพูด...", + "playWithSpeechHint": "เล่นวิดีโอและพูดแต่ละบรรทัดเมื่อวิดีโอไปถึงบรรทัดนั้น หยุดชั่วคราวและเลื่อนตำแหน่งได้ตามปกติ กด Escape เพื่อหยุด", + "lowerVideoVolumeWhileSpeaking": "ลดระดับเสียงวิดีโอขณะพูด", + "pauseVideoWhenSpeechRunsLate": "หยุดวิดีโอชั่วคราวเมื่อเสียงพูดล้ำเข้าไปในบรรทัดถัดไป", + "waitingForSpeechLineX": "กำลังสร้างเสียงพูดสำหรับบรรทัด {0}... (Escape เพื่อหยุด)", + "voiceKindClone": "เสียงที่โคลน", + "voiceKindPreset": "ในตัว", + "voiceKindModel": "โมเดลเสียง", + "voiceKindOnline": "ออนไลน์", + "transcript": "บทถอดเสียง", + "transcriptRequiredHint": "จำเป็น - เอนจินจะอ่านบทถอดเสียงของไฟล์บันทึกนี้ (ref-text) เมื่อโคลน", + "transcriptOptionalHint": "ไม่บังคับ - บทถอดเสียงที่แม่นยำของไฟล์บันทึกช่วยให้การโคลนดีขึ้น", + "saveTranscript": "บันทึกบทถอดเสียง", + "searchVoices": "ค้นหาเสียง", + "loadingVoicesDotDotDot": "กำลังโหลดเสียง...", + "noVoicesForEngine": "ไม่มีเสียง - นำเข้าไฟล์บันทึกเสียงหรือดาวน์โหลดชุดเสียง", + "noVoicesMatchFilter": "ไม่มีเสียงที่ตรงกับการค้นหา", + "copyVoiceTo": "คัดลอกเสียงไปยัง", + "copyVoiceToDotDotDot": "คัดลอกเสียงไปยังเอนจิน...", + "copyingVoiceXToYDotDotDot": "กำลังคัดลอกเสียง '{0}' ไปยัง {1}...", + "voiceXCopiedToY": "คัดลอกเสียง '{0}' ไปยัง {1} แล้ว", + "voiceXCouldNotBeCopiedToY": "ไม่สามารถคัดลอกเสียง '{0}' ไปยัง {1} - ดูรายละเอียดในบันทึก", + "voiceXAlreadyExistsInYContinue": "มีเสียงชื่อ '{0}' อยู่แล้วใน {1}\n\nคัดลอกต่อหรือไม่?", + "openVoicesFolder": "เปิดโฟลเดอร์เสียง", + "downloadVoicePacksDotDotDot": "ดาวน์โหลดชุดเสียง...", + "downloadVoicePacksTitle": "TTS - ดาวน์โหลดชุดเสียง", + "voicePackIntro": "ชุดเสียงคือชุดไฟล์บันทึกเสียงสั้นๆ ที่สะอาดพร้อมบทถอดเสียง พร้อมสำหรับการโคลน แต่ละชุดจะถูกนำเข้าไปยังเอนจินที่เลือก (เสียงที่มีอยู่แล้วจะถูกข้าม) และมีไฟล์ ATTRIBUTION.txt ระบุสัญญาอนุญาตและแหล่งที่มา", + "voicePackDetailsXVoicesYSizeZLicense": "{0} เสียง · {1} · {2}", + "installToEngine": "ติดตั้งไปยังเอนจิน", + "downloadAndInstall": "ดาวน์โหลดและติดตั้ง", + "selectAtLeastOneVoicePack": "เลือกชุดเสียงอย่างน้อยหนึ่งชุด", + "downloadingXDotDotDot": "กำลังดาวน์โหลด {0}...", + "installingXDotDotDot": "กำลังติดตั้ง {0}...", + "xVoicesInstalledYSkipped": "ติดตั้งแล้ว {0} เสียง ({1} มีอยู่แล้ว)", "useSpeechToTextDotDotDot": "ใช้แปลงเสียงพูดเป็นข้อความ...", "autoCastMenuItem": "ค้นหาเสียงในวิดีโอและโคลน...", "autoCastSpeakersTitle": "เสียงที่พบในวิดีโอ", @@ -2550,7 +2780,7 @@ "cloneVoicePerLinePreparing": "กำลังดึงเสียงของแต่ละบรรทัดจากวิดีโอ...", "cloneVoicePerLineNeedsVideo": "การโคลนเสียงของแต่ละบรรทัดต้องใช้วิดีโอที่คำบรรยายนี้สังกัดอยู่ กรุณาเปิดวิดีโอแล้วลองใหม่", "cloneVoicePerLineNoClips": "ไม่สามารถดึงเสียงจากวิดีโอได้ จึงไม่มีอะไรให้โคลน กรุณาตรวจสอบว่าวิดีโอมีแทร็กเสียง", - "cloneVoicePerLineNeedsOriginalSubtitleX": "{0} จะโคลนบรรทัดได้ก็ต่อเมื่อรู้ว่าในวิดีโอพูดอะไรที่บรรทัดนั้น ซึ่งมาจากคำบรรยายภาษาต้นฉบับ ขณะนี้ยังไม่ได้โหลดคำบรรยายต้นฉบับ ทุกบรรทัดจึงจะถูกพูดด้วยเสียงแรกที่นำเข้าแทนเสียงในวิดีโอ\n\nเปิดคำบรรยายต้นฉบับ (ไฟล์ > เปิดต้นฉบับ...) ควบคู่กับคำแปล แล้วสร้างใหม่อีกครั้ง", + "cloneVoicePerLineTranscribeClipsXYZ": "{0} จะโคลนบรรทัดได้ก็ต่อเมื่อรู้ว่าในวิดีโอพูดอะไรที่บรรทัดนั้น ข้อมูลนี้ไม่ทราบสำหรับ {1} จาก {2} บรรทัด - ปกติจะมาจากคำบรรยายภาษาต้นฉบับ (ไฟล์ > เปิดต้นฉบับ...)\n\nถอดเสียงบรรทัดเหล่านั้นจากวิดีโอด้วยการแปลงเสียงเป็นข้อความตอนนี้หรือไม่?\n\nใช่: เลือกเอนจินและภาษาที่พูดในวิดีโอ แล้วคลิปอ้างอิงจะถูกถอดเสียงก่อนสร้างเสียงพูด\nไม่: สร้างต่อไป - บรรทัดเหล่านั้นจะพูดด้วยเสียงทั่วไปแทนเสียงในวิดีโอ", "voiceCloneConsentTitle": "การโคลนเสียง - ก่อนเริ่มต้น", "voiceCloneConsentHeader": "คุณกำลังจะโคลนเสียง", "voiceCloneConsentIntro": "การโคลนคือการคัดลอกเสียงของบุคคลจริง ซึ่งมีข้อกำหนดในเกือบทุกประเทศ และในสหภาพยุโรปมีหน้าที่ตามกฎหมายที่ตกอยู่กับคุณ ไม่ใช่กับ Subtitle Edit กรุณาอ่านข้อความนี้สักครั้ง", @@ -2567,10 +2797,29 @@ "advancedTtsSettings": "การตั้งค่า TTS ขั้นสูง", "advancedTtsAudioProcessing": "การประมวลผลเสียง", "advancedTtsOutput": "ผลลัพธ์", + "advancedTtsVoiceCloning": "การโคลนเสียง", + "cleanCloneReferences": "ทำความสะอาดเสียงอ้างอิงสำหรับโคลน (Sidon)", + "cleanCloneReferencesDescription": "ลบดนตรี เสียงรบกวน และเสียงก้องออกจากเสียงที่ใช้โคลน: คลิปที่ตัดจากวิดีโอและไฟล์บันทึกเสียงที่นำเข้าในตัวจัดการเสียง ใช้โมเดลฟื้นฟูเสียงพูด Sidon บน audio.cpp (ดาวน์โหลด 940 MB) เสียงที่โคลนจะไม่คัดลอกเสียงพื้นหลังอีกต่อไป แต่เสียงพูดที่เบามากหรือพูดซ้อนกันอาจฟังอู้อี้", + "cleaningCloneReferencesDotDotDot": "กำลังทำความสะอาดเสียงอ้างอิงสำหรับโคลน...", + "downloadSidonTitle": "ดาวน์โหลด Sidon หรือไม่?", + "downloadSidonQuestionX": "การทำความสะอาดเสียงอ้างอิงสำหรับโคลนต้องใช้โมเดลฟื้นฟูเสียงพูด Sidon ({0})\n\nดาวน์โหลดตอนนี้หรือไม่? หากไม่มีโมเดลนี้ จะใช้เสียงอ้างอิงตามเดิม", "proAudioPostProcessing": "การประมวลผลเสียงระดับมืออาชีพ", "proAudioPostProcessingDescription": "ปรับใช้ EQ เพิ่มความอบอุ่น noise gate การบีบอัด การปรับความดังมาตรฐาน (-16 LUFS) และเสียงค่อยๆ ดัง/เบาลงกับแต่ละส่วน", + "generatingSpeechSegmentXOfY": "กำลังสร้างเสียงพูด: ส่วนที่ {0} จาก {1}", + "adjustingSpeedSegmentXOfY": "กำลังปรับความเร็ว: ส่วนที่ {0} จาก {1}", + "mergingAudioSegmentXOfY": "กำลังรวมเสียง: ส่วนที่ {0} จาก {1}", + "postProcessingSegmentXOfY": "กำลังประมวลผลภายหลัง: ส่วนที่ {0} จาก {1}", "audioDucking": "การลดเสียงพื้นหลัง (ducking)", "audioDuckingDescription": "ลดระดับเสียงต้นฉบับของวิดีโอแล้วผสมกับเสียง TTS ทำให้ยังได้ยินเสียงประกอบต้นฉบับแผ่วๆ", + "removeOriginalSpeech": "ลบเสียงพูดต้นฉบับ (ช้า)", + "removeOriginalSpeechDescription": "คงดนตรีและเสียงประกอบของวิดีโอต้นฉบับไว้แต่ลบเสียงพูดออก แล้วผสมเสียงพูดใหม่ทับลงไป ใช้เวลาประมาณเท่ากับความยาววิดีโอเมื่อมี GPU และนานกว่านั้นหลายเท่าหากไม่มี เมื่อเปิดการลดเสียงพื้นหลัง (ducking) ระดับเสียงจะมีผลกับดนตรีและเสียงประกอบ มีผลเฉพาะเมื่อเพิ่มเสียงพูดลงในไฟล์วิดีโอเท่านั้น", + "removingOriginalSpeech": "กำลังลบเสียงพูดต้นฉบับ...", + "removeOriginalSpeechFailed": "ไม่สามารถลบเสียงพูดต้นฉบับได้ - เสียงพูดใหม่ถูกเพิ่มลงในวิดีโอโดยไม่ได้ลบ", + "speechRemovalModel": "โมเดล", + "speechRemovalModelDescription": "คุณภาพดีที่สุดจะคงดนตรีและเสียงประกอบไว้ได้มากที่สุด แบบเร็วเร็วกว่าประมาณสามเท่า แต่สูญเสียดนตรีไปมากขึ้นเล็กน้อย", + "speechRemovalBestQuality": "คุณภาพดีที่สุด (Mel-Band RoFormer)", + "speechRemovalFast": "เร็ว (HTDemucs)", + "videoTruncatedWarning": "ภาพในไฟล์วิดีโอใหม่หยุดหลังจาก {0} แต่วิดีโอต้นฉบับยาว {1} - ภาพจะค้างขณะที่เสียงยังเล่นต่อ\n\nไฟล์ถูกเก็บไว้ ดู error-log.txt ในโฟลเดอร์ข้อมูลของ Subtitle Edit สำหรับผลลัพธ์ของ ffmpeg", "originalVolumePercent": "% ระดับเสียงต้นฉบับ", "vadSilenceCompression": "การบีบอัดช่วงเงียบด้วย VAD", "vadSilenceCompressionDescription": "ย่นช่วงหยุดระหว่างคำก่อนปรับความเร็ว ใช้ Voice Activity Detection เพื่อบีบอัดเฉพาะช่วงเงียบโดยไม่แตะต้องเสียงพูด นี่คือขั้นตอนแรกที่แนะนำ — ลดระยะเวลาโดยไม่สูญเสียคุณภาพใดๆ", @@ -2644,7 +2893,6 @@ "subtitleMergedLinesAppliedPlural": "ปรับใช้การผสาน {0} บรรทัดจากการแปลงข้อความเป็นเสียงพูด" }, "shotChanges": { - "titleGenerateOrImport": "สร้าง/นำเข้าจุดเปลี่ยนฉาก", "generateShotChanges": "สร้างจุดเปลี่ยนฉาก", "importShotChanges": "นำเข้าจุดเปลี่ยนฉาก", "generateShotChangesWithFfmpeg": "สร้างจุดเปลี่ยนฉากด้วย ffmpeg", @@ -2654,7 +2902,9 @@ "timeCodeFormatColon": "รูปแบบรหัสเวลา:", "shotChangesList": "รายการจุดเปลี่ยนฉาก", "shotChangesClearQuestion": "คุณแน่ใจหรือไม่ว่าต้องการล้างจุดเปลี่ยนฉากทั้งหมด?", - "deleteSelectedShotChangeQuestion": "คุณแน่ใจหรือไม่ว่าต้องการลบจุดเปลี่ยนฉากที่เลือก?" + "deleteSelectedShotChangeQuestion": "คุณแน่ใจหรือไม่ว่าต้องการลบจุดเปลี่ยนฉากที่เลือก?", + "shotChanges": "จุดเปลี่ยนฉาก", + "exportShotChanges": "ส่งออกจุดเปลี่ยนฉาก" }, "chapters": { "title": "บท", @@ -2684,7 +2934,6 @@ "scaleTimesDescription": "ปรับสเกลเวลาของทุกบทจากอัตราเฟรมหนึ่งไปยังอีกอัตราเฟรมหนึ่ง", "fromFrameRate": "จาก", "toFrameRate": "เป็น", - "apply": "นำไปใช้", "writeToVideo": "เขียนลงวิดีโอ...", "writeToVideoTitle": "เขียนบทลงในวิดีโอ", "writeToVideoDescription": "บทจะถูกเขียนลงในสำเนาของไฟล์วิดีโอ ไม่มีการเข้ารหัสใหม่ จึงรวดเร็วและไม่สูญเสียคุณภาพ", @@ -2694,7 +2943,6 @@ "writtenToX": "เขียนบทลงใน {0} แล้ว", "unableToWriteChapters": "ไม่สามารถเขียนบทลงในไฟล์วิดีโอได้", "chaptersSavedToX": "บันทึกบทลงใน {0} แล้ว", - "noVideoLoaded": "ยังไม่ได้โหลดวิดีโอ", "emptyListCallToAction": "ยังไม่มีบท - เพิ่มบทที่ตำแหน่งวิดีโอ หรือนำเข้าจากวิดีโอหรือไฟล์" }, "videoOcr": { @@ -2705,10 +2953,6 @@ "bottomHalf": "ครึ่งล่าง", "fullFrame": "เต็มเฟรม", "engine": "เอนจิน OCR", - "language": "ภาษา", - "url": "URL", - "model": "โมเดล", - "apiKey": "คีย์ API", "scan": "สแกน", "framesPerSecond": "เฟรมต่อวินาที", "textBrightnessMinimum": "ความสว่างข้อความขั้นต่ำ (0=ปิด)", @@ -2723,10 +2967,10 @@ "runningOcrXY": "กำลังรัน OCR... {0}/{1}", "noLinesFoundTitle": "ไม่พบคำบรรยาย", "noLinesFoundMessage": "ไม่พบข้อความในพื้นที่สแกน - ลองปรับพื้นที่สแกน เอนจิน หรือความสว่างขั้นต่ำ", + "ocrStoppedEarlyMessage": "เอนจิน OCR หยุดก่อนอ่านครบทุกเฟรม จึงเก็บบรรทัดที่พบจนถึงตอนนี้ไว้ และส่วนที่เหลือขาดหายไป{0}{0}{1}", "linesFoundX": "พบ {0} บรรทัด", "refiningTimingXY": "กำลังปรับจังหวะเวลาให้ละเอียด... {0}/{1}", "fixOcrErrors": "แก้ไขข้อผิดพลาด OCR", - "dictionary": "พจนานุกรม", "editLineX": "แก้ไขบรรทัด {0}", "downloadDictionaryHint": "ดาวน์โหลดพจนานุกรมตรวจสอบตัวสะกด...", "framesPerSecondHint": "จำนวนเฟรมต่อวินาทีที่จะสแกน ค่า 5 ครอบคลุมคำบรรยายทั่วไป ค่าที่สูงขึ้นจะจับบรรทัดสั้นมากได้และจัดกลุ่มแม่นยำขึ้น แต่ใช้เวลา OCR มากกว่า", @@ -2743,7 +2987,58 @@ "testOcr": "ทดสอบเฟรมปัจจุบัน", "testOcrRunning": "กำลังทดสอบ OCR บนเฟรมปัจจุบัน...", "testOcrResultX": "ผลการทดสอบ: {0}", - "testOcrNoTextFound": "ทดสอบ: ไม่พบข้อความในพื้นที่สแกน" + "testOcrNoTextFound": "ทดสอบ: ไม่พบข้อความในพื้นที่สแกน", + "engineSettings": "การตั้งค่าเอนจิน OCR", + "engineBuiltIn": "มีในระบบปฏิบัติการ", + "engineExternalServer": "เซิร์ฟเวอร์ภายนอก (ติดตั้งและจัดการภายนอก Subtitle Edit)", + "engineCloudApi": "Cloud API (ต้องใช้ API key)", + "engineNothingToInstall": "ไม่มีอะไรต้องติดตั้ง", + "engineModelsMissing": "ติดตั้งเอนจินแล้ว แต่ไม่มีโมเดล", + "website": "เว็บไซต์" + }, + "backgroundMusic": { + "title": "สร้างดนตรีพื้นหลัง", + "generateBackgroundMusicDotDotDot": "สร้างดนตรีพื้นหลัง...", + "preset": "พรีเซ็ต", + "prompt": "พรอมต์", + "tempoBpm": "จังหวะ (BPM)", + "generateSeconds": "สร้าง (วินาที)", + "seed": "Seed", + "randomSeed": "สุ่ม", + "outputLength": "ความยาวผลลัพธ์", + "outputLengthVideoX": "{0} (ความยาววิดีโอ - ดนตรีจะวนซ้ำอย่างต่อเนื่อง)", + "musicVolumePercent": "ระดับเสียงดนตรี (%)", + "removeExistingAudioTracks": "ลบแทร็กเสียงที่มีอยู่", + "originalAudioVolumePercent": "ระดับเสียงต้นฉบับ (%)", + "saveAudioDotDotDot": "บันทึกเสียง...", + "addToVideoDotDotDot": "เพิ่มลงในวิดีโอ...", + "engineInfo": "ACE-Step 1.5 Turbo ผ่าน audio.cpp (สัญญาอนุญาต MIT) - ทำงานภายในเครื่อง", + "presetCooking": "ทำอาหาร / อบขนม", + "presetTech": "เทคโนโลยี / รีวิวแกดเจ็ต", + "presetForest": "ป่า / ธรรมชาติ", + "presetRelaxingPiano": "เปียโนผ่อนคลาย", + "presetLofiVlog": "Lo-fi vlog", + "presetCorporate": "องค์กร / อธิบาย", + "loadingModel": "กำลังโหลดโมเดล...", + "composing": "กำลังแต่งเพลง...", + "generatingAudio": "กำลังสร้างเสียง...", + "decodingAudio": "กำลังถอดรหัสเสียง...", + "creatingLoop": "กำลังสร้างลูปต่อเนื่อง...", + "resultLoopXYZ": "ลูป: {0} ที่ {1} BPM, ความต่อเนื่องของรอยต่อ {2}%", + "resultNoLoopX": "ดนตรีพร้อมแล้ว: {0}", + "addingMusicToVideo": "กำลังเพิ่มดนตรีลงในวิดีโอ...", + "downloadModelTitle": "ดาวน์โหลดโมเดลดนตรีหรือไม่?", + "downloadModelQuestionX": "การสร้างดนตรีต้องใช้โมเดล ACE-Step 1.5 ({0})\n\nดาวน์โหลดโมเดลหรือไม่?", + "downloadingModel": "กำลังดาวน์โหลดโมเดล ACE-Step 1.5...", + "saveAudioTitle": "บันทึกดนตรีพื้นหลัง", + "audioFileSavedX": "บันทึกไฟล์เสียงแล้ว: {0}", + "unableToGenerateMusic": "ไม่สามารถสร้างดนตรีได้", + "unableToAddMusicToVideo": "ไม่สามารถเพิ่มดนตรีลงในวิดีโอได้", + "outputLengthTextToSpeech": "ตามเสียงพูดที่สร้างขึ้น - ดนตรีจะวนซ้ำอย่างต่อเนื่องและเบาลงขณะมีคนพูด", + "addBackgroundMusic": "เพิ่มดนตรีพื้นหลัง", + "addBackgroundMusicHint": "สร้างดนตรีภายในเครื่องด้วย ACE-Step 1.5 และผสมไว้ใต้เสียงพูด - ดนตรีจะเบาลงขณะมีคนพูด", + "generatingBackgroundMusicDotDotDot": "กำลังสร้างดนตรีพื้นหลัง...", + "mixingBackgroundMusicDotDotDot": "กำลังผสมดนตรีพื้นหลัง..." }, "goToVideoPosition": "ไปยังตำแหน่งวิดีโอ", "goToVideoPositionDotDotDot": "ไปที่ตำแหน่งวิดีโอ...", @@ -2754,30 +3049,88 @@ "openSecondarySubtitleOnVideoPlayerDotDotDot": "คำบรรยายรองบนเครื่องเล่นวิดีโอ, เปิด...", "openSecondarySubtitleOnVideoPlayer": "คำบรรยายรอง (บนเครื่องเล่นวิดีโอ)", "removeSecondarySubtitleOnVideoPlayer": "คำบรรยายรองบนเครื่องเล่นวิดีโอ, ลบออก", + "editSecondarySubtitleSettings": "แก้ไขการตั้งค่าคำบรรยายรอง", + "editSecondarySubtitleSettingsDotDotDot": "แก้ไขการตั้งค่าคำบรรยายรอง...", + "secondarySubtitleRememberSettings": "จดจำการตั้งค่าเหล่านี้", + "secondarySubtitleDoNotShowAgain": "ไม่ต้องแสดงกล่องโต้ตอบนี้อีก", + "secondarySubtitleJustifyLines": "จัดบรรทัดชิดขอบเต็มแนว", "openRecentVideo": "เปิดวิดีโอล่าสุด", "clearRecentVideos": "ล้างรายการวิดีโอล่าสุด", "cutVideoTitle": "ตัดวิดีโอ", "cutVideoDotDotDot": "ตัดวิดีโอ...", + "remuxVideoTitle": "รีมักซ์วิดีโอ", + "remuxVideoDotDotDot": "รีมักซ์วิดีโอ...", + "remuxVideoInputVideo": "วิดีโอต้นทาง:", + "remuxVideoMultipleTracksRequiresMkv": "แทร็กเสียงหรือคำบรรยายหลายแทร็กต้องใช้คอนเทนเนอร์ MKV รูปแบบผลลัพธ์ถูกเปลี่ยนเป็น .mkv โดยอัตโนมัติ", + "remuxVideoOutputFormat": "รูปแบบผลลัพธ์:", + "remuxVideoOutputFile": "ไฟล์ผลลัพธ์:", + "remuxVideoRemuxing": "กำลังรีมักซ์วิดีโอและเสียง...", + "remuxVideoFinalizing": "กำลังจัดการไฟล์ผลลัพธ์ขั้นสุดท้าย (ย้ายดัชนีไปไว้ด้านหน้า)...", + "remuxVideoCompleted": "รีมักซ์วิดีโอสำเร็จ", + "remuxVideoFailed": "รีมักซ์วิดีโอไม่สำเร็จ", + "remuxVideoPleaseSelectBoth": "โปรดเลือกทั้งไฟล์วิดีโอและไฟล์เสียง", + "remuxVideoAssRequiresMkv": "คำบรรยาย ASS/SSA ต้องใช้คอนเทนเนอร์ MKV เพื่อรักษาการจัดรูปแบบและสไตล์ทั้งหมด", + "remuxVideoSccRequiresMov": "คำบรรยาย Scenarist (.scc) จะถูกฝังเป็นคำบรรยายปิด CEA-608 ซึ่งต้องใช้คอนเทนเนอร์ MOV หรือ MPG รูปแบบผลลัพธ์ถูกเปลี่ยนเป็น .mov โดยอัตโนมัติ", + "remuxVideoSccNotWithWebM": "คำบรรยายปิด Scenarist (.scc) ต้องใช้คอนเทนเนอร์ MOV ซึ่งไม่สามารถเก็บวิดีโอ VP9/AV1 ของไฟล์ .webm ได้ ลบไฟล์ .scc หรือแปลงเป็นรูปแบบคำบรรยายอื่น", + "remuxVideoMpgMaxTwoCaptionTracks": "ไฟล์ .mpg เก็บแทร็กคำบรรยายปิดได้สูงสุดสองแทร็ก (CC1 และ CC3) - ลบไฟล์คำบรรยายให้เหลือหนึ่งหรือสองไฟล์", + "remuxVideoMccRequiresMpg": "คำบรรยาย MacCaption (.mcc) จะถูกฝังเป็นคำบรรยายปิด CEA-608/708 ในวิดีโอ MPEG-2 ซึ่งต้องใช้คอนเทนเนอร์ MPG รูปแบบผลลัพธ์ถูกเปลี่ยนเป็น .mpg โดยอัตโนมัติ", + "remuxVideoMpgReencodeVideoX": "คำบรรยายปิดในไฟล์ .mpg ต้องใช้วิดีโอ MPEG-2 แต่วิดีโอเป็น {0} เข้ารหัสวิดีโอใหม่เป็น MPEG-2 หรือไม่? จะใช้เวลานานขึ้นและคุณภาพลดลงเล็กน้อย", + "remuxVideoFileHasNoAudioX": "\"{0}\" ไม่มีแทร็กเสียง จึงไม่สามารถใช้เป็นแหล่งเสียงได้", + "remuxVideoAddingClosedCaptions": "กำลังเพิ่มคำบรรยายปิด...", + "remuxVideoSelectAudioTrack": "เลือกแทร็กเสียง", + "remuxVideoSelectAudioTrackFor": "เลือกแทร็กเสียงสำหรับ {0}", + "remuxVideoSelectAudioTrackPrompt": "ไฟล์ '{0}' มีแทร็กเสียง {1} แทร็ก โปรดเลือกแทร็กเสียงที่จะรีมักซ์:", + "remuxVideoVideoFile": "วิดีโอ", + "remuxVideoAudioFile": "เสียง", + "remuxVideoAudioFiles": "ไฟล์เสียง:", + "remuxVideoSubtitleFiles": "ไฟล์คำบรรยาย (ไม่บังคับ, คำบรรยายแบบซอฟต์):", + "remuxVideoSelectAudioTrackDotDotDot": "เลือกแทร็กเสียง...", + "remuxVideoFilesX": "{0} ไฟล์", + "remuxVideoMixAudio": "ผสมเสียงเป็นแทร็กเดียว", + "remuxVideoVolumePercent": "ระดับเสียง %:", + "remuxVideoVolumeX": "ระดับเสียง {0}%", + "remuxVideoFastStart": "ปรับให้เหมาะกับการสตรีมบนเว็บ (ย้ายดัชนีไปไว้ด้านหน้า - ช้ากว่า)", "embedSubtitlesDotDotDot": "เพิ่ม/ลบคำบรรยายฝังในไฟล์...", "generateTimeCodes": "สร้างรหัสเวลา", "checkeredImage": "ภาพลายตาราง", - "saveVideoAsTitle": "บันทึกวิดีโอเป็น", "promptForFfmpegParamsAndGenerate": "ถามพารามิเตอร์ ffmpeg แล้วสร้าง", "cutVideoCutSegments": "ตัดส่วน", "cutVideoMergeSegments": "ผสานส่วน", "cutVideoSplitSegments": "บันทึกแต่ละส่วนแยกกัน", - "cutVideoType": "ชนิดการตัด", - "mpvRenderAuto": "อัตโนมัติ", - "mpvRenderNative": "เนทีฟ", - "mpvRenderOpenGl": "OpenGL", - "mpvRenderSoftware": "ซอฟต์แวร์ (ช้า)", "cutVideoAlsoCutSubtitle": "ตัดคำบรรยายด้วย", "cutVideoSubtitleFileGeneratedX": "สร้างไฟล์คำบรรยายแล้ว: {0}", + "cutVideoTransition": "ทรานซิชันระหว่างส่วน", + "cutVideoTransitionDurationSeconds": "ระยะเวลา (วินาที)", + "cutVideoFadeInSeconds": "เฟดจากสีดำตอนเริ่ม (วินาที)", + "cutVideoFadeOutSeconds": "เฟดเป็นสีดำตอนจบ (วินาที)", + "cutVideoPreviewTransition": "แสดงตัวอย่างทรานซิชัน", + "cutVideoPreviewTransitionTitle": "ตัวอย่างทรานซิชัน", + "cutVideoPreviewNeedsJoin": "ไม่มีรอยต่อให้แสดงตัวอย่าง - เพิ่มอย่างน้อยสองส่วนเพื่อรวม หรือหนึ่งส่วนเพื่อตัดออกจากตรงกลาง", + "cutVideoTransitionInfoX": "แต่ละทรานซิชันจะซ้อนทับส่วนที่เชื่อมต่อ ผลลัพธ์จึงสั้นลง {0} วินาทีต่อรอยต่อ คำบรรยายที่ตัดจะถูกปรับเวลาใหม่ให้ตรงกัน", + "cutVideoTransitionFade": "เฟด", + "cutVideoTransitionFadeBlack": "เฟดผ่านสีดำ", + "cutVideoTransitionFadeWhite": "เฟดผ่านสีขาว", + "cutVideoTransitionFadeGrays": "เฟดผ่านสีเทา", + "cutVideoTransitionDissolve": "ละลายภาพ", + "cutVideoTransitionWipeLeft": "กวาดไปทางซ้าย", + "cutVideoTransitionWipeRight": "กวาดไปทางขวา", + "cutVideoTransitionWipeUp": "กวาดขึ้น", + "cutVideoTransitionWipeDown": "กวาดลง", + "cutVideoTransitionSlideLeft": "เลื่อนไปทางซ้าย", + "cutVideoTransitionSlideRight": "เลื่อนไปทางขวา", + "cutVideoTransitionSlideUp": "เลื่อนขึ้น", + "cutVideoTransitionSlideDown": "เลื่อนลง", + "cutVideoTransitionSmoothLeft": "นุ่มนวลไปทางซ้าย", + "cutVideoTransitionSmoothRight": "นุ่มนวลไปทางขวา", + "cutVideoTransitionCircleOpen": "วงกลมเปิด", + "cutVideoTransitionCircleClose": "วงกลมปิด", + "cutVideoTransitionRadial": "แนวรัศมี", + "cutVideoTransitionPixelize": "แตกเป็นพิกเซล", + "cutVideoTransitionBlur": "เบลอ", "importCurrentSubtitle": "นำเข้าคำบรรยายปัจจุบัน", "addRemoveEmbeddedSubtitlesTitle": "เพิ่ม/ลบคำบรรยายฝังในไฟล์", "addRemoveEmbeddedSubtitlesMp4Title": "เพิ่ม/ลบคำบรรยายฝังในไฟล์ (MP4)", "addCurrentSubtitle": "เพิ่มคำบรรยายปัจจุบัน", - "embeddedTrackColumnNew": "ใหม่", "embeddedTrackNoSubtitlesFoundTitle": "ไม่พบคำบรรยาย", "embeddedTrackNoSubtitlesFoundMessage": "ไฟล์คำบรรยายที่เลือกไม่มีคำบรรยายใดๆ", "embeddedTrackNoTracksTitle": "ไม่ได้เพิ่มแทร็ก", @@ -2789,7 +3142,7 @@ "embeddedTrackGeneratingVideoXY": "กำลังสร้างวิดีโอ... {0}% {1}", "embeddedTrackGeneratingVideo": "กำลังสร้างวิดีโอ...", "mp4FilesFilter": "ไฟล์ MP4", - "titleOrLanguage": "ชื่อเรื่อง/ภาษา", + "embeddedTracksPickVideoHint": "เลือกไฟล์วิดีโอ หรือลากมาวางที่นี่ เพื่อดูแทร็กคำบรรยาย", "viewMatroskaTrackX": "ดูแทร็ก Matroska - {0}", "resolutionSeparator": "x", "openFromUrlTitle": "เปิดไฟล์วิดีโอจาก URL", @@ -2799,9 +3152,7 @@ "openFromUrlDownloadAndOpen": "ดาวน์โหลดแล้วเปิด", "openFromUrlDownloadAndOpenDescription": "บันทึกวิดีโอลงเครื่องแล้วจึงเปิด", "openFromUrlDownloadAndOpenNote": "จำเป็นสำหรับการแปลงเสียงพูดเป็นข้อความและกราฟรูปคลื่นเสียง", - "openFromUrlMissing": "โปรดป้อน URL วิดีโอก่อน", "openFromUrlDownloadingTitle": "กำลังดาวน์โหลดวิดีโอ", - "openFromUrlSaveAs": "บันทึกวิดีโอเป็น", "openFromUrlDownloadSubtitles": "ดาวน์โหลดคำบรรยายด้วย", "openFromUrlDownloadVideo": "ดาวน์โหลดวิดีโอ", "openFromUrlIncludeAutoGeneratedSubtitles": "รวมคำบรรยายที่สร้างอัตโนมัติ (ภาษาต้นฉบับ)", @@ -2838,12 +3189,15 @@ "reEncodeGeneratingDone": "สร้างเสร็จแล้ว", "reEncodeGeneratedFilesX": "ไฟล์ที่สร้างแล้ว ({0}):", "reEncodeFfmpegParameters": "พารามิเตอร์ ffmpeg", + "analyzingVideoX": "กำลังวิเคราะห์วิดีโอ... {0}% {1}", + "analyzingVideoXofY": "กำลังวิเคราะห์วิดีโอ {0}/{1}... {2}% {3}", + "backgroundImageFileNotSelected": "ยังไม่ได้เลือกไฟล์ภาพพื้นหลัง", + "backgroundImageFileDoesNotExistX": "ไม่มีไฟล์ภาพพื้นหลัง: {0}", "voices": "เสียง", "presets": "พรีเซ็ต", "talkerX": "ผู้พูด {0}", "baseModel": "โมเดลพื้นฐาน", "turboModel": "โมเดลเทอร์โบ", - "ttsUpdateX": "อัปเดต {0}", "ttsEngineNotInstalled": "ยังไม่ได้ติดตั้ง {0}", "ttsEngineUpdateAvailable": "{0} - มีอัปเดตใหม่", "ttsEngineTooOldUpdateRequired": "{0} เก่าเกินไป - ต้องอัปเดต", @@ -2866,19 +3220,25 @@ "cloneVoiceTo": "โคลนเสียงไปยัง", "cloneVoiceNameTitle": "ชื่อของเสียงใหม่", "cloneVoiceExtractFailed": "ไม่สามารถดึงเสียงสำหรับบรรทัดที่เลือกได้", + "lineXHasNoDuration": "บรรทัด {0} ไม่มีระยะเวลา (เวลาสิ้นสุดไม่ได้อยู่หลังเวลาเริ่มต้น) จึงไม่มีเสียงให้แยก", + "audioClipsSkippedLinesX": "ไม่สามารถแยกเสียงสำหรับบรรทัดเหล่านี้ได้ จึงข้ามไป: {0}", "toggleShotChange": "สลับจุดเปลี่ยนฉาก", "resetWaveformZoomAndSpeed": "รีเซ็ตการซูมและความเร็วของรูปคลื่นเสียง", "showOriginalSubtitle": "แสดงคำบรรยายต้นฉบับ", + "timelineGroupTracksBy": "จัดกลุ่มแทร็กคำบรรยายตาม", + "timelineShowThumbnails": "แสดงภาพขนาดย่อของวิดีโอ", + "showSpeechOnly": "แสดงเฉพาะเสียงพูด (สร้างช้า)", + "isolatingSpeechForWaveformX": "กำลังแยกเสียงพูดสำหรับรูปคลื่นเสียง... {0}", + "isolatingSpeechForWaveformFailed": "ไม่สามารถแยกเสียงพูดสำหรับรูปคลื่นเสียงได้ - ดูบันทึกของเครื่องมือ", + "speechOnlyWaveformReady": "รูปคลื่นเสียงแสดงเฉพาะเสียงพูดแล้ว", "showOnlyWaveform": "แสดงเฉพาะรูปคลื่นเสียง", "showOnlySpectrogram": "แสดงเฉพาะสเปกโตรแกรม", "showWaveformAndSpectrogram": "แสดงรูปคลื่นเสียงและสเปกโตรแกรม", - "spectrogramClassic": "คลาสสิก", "spectrogramClassicViridis": "Viridis", "spectrogramClassicPlasma": "Plasma", "spectrogramClassicInferno": "Inferno", "spectrogramClassicTurbo": "Turbo", "spectrogramNeon": "นีออน", - "waveformDrawStyleClassic": "คลาสสิก", "waveformDrawStyleFancy": "หรูหรา", "setVideoPositionAndPauseAndSelectSubtitle": "ตั้งตำแหน่งวิดีโอ หยุดชั่วคราว และเลือกคำบรรยาย", "setVideopositionAndPauseAndSelectSubtitleAndCenter": "ตั้งตำแหน่งวิดีโอ หยุดชั่วคราว เลือกคำบรรยาย และจัดกึ่งกลาง", @@ -2888,7 +3248,6 @@ }, "sync": { "visualSync": "ซิงก์แบบเห็นภาพ", - "sync": "ซิงก์", "startScene": "ฉากเริ่มต้น", "endScene": "ฉากจบ", "playTwoSecondsAndBack": "เล่น 2 วินาทีแล้วย้อนกลับ", @@ -2904,19 +3263,18 @@ "adjustSelectedLinesAndForward": "ปรับบรรทัดที่เลือกและถัดไปทั้งหมด", "toFrameRate": "เป็นอัตราเฟรม", "fromFrameRate": "จากอัตราเฟรม", + "videoXFrameRateY": "{0} - {1} fps", "adjustAllTimes": "ปรับเวลาทั้งหมด (แสดงเร็วขึ้น/ช้าลง)", "showEarlier": "แสดงเร็วขึ้น", "showLater": "แสดงช้าลง", - "changeFrameRate": "เปลี่ยนอัตราเฟรม", "setSyncPoint": "ตั้งจุดซิงก์", "setSyncPointViaVideo": "กำหนดจุดซิงค์ผ่านวิดีโอ...", "syncPointTimeCode": "ไทม์โค้ดของจุดซิงค์", "syncPoints": "จุดซิงก์", "pointSync": "ซิงก์แบบจุด", "pointSyncViaOther": "ซิงก์แบบจุดผ่านคำบรรยายอื่น", - "adjustmentX": "การปรับ: {0}", - "totalAdjustmentX": "การปรับทั้งหมด: {0}", - "syncPointCandidateInfo": "บรรทัดที่เริ่มหลังช่วงเงียบตั้งแต่ {0} วินาทีขึ้นไปจะถูกเน้นไว้ - มักใช้เป็นจุดซิงค์ที่เชื่อถือได้", + "syncPointCandidateAfterInfo": "บรรทัดที่ตามด้วยความเงียบ {0}+ วินาทีจะถูกเน้น - มักเป็นจุดซิงค์ที่เชื่อถือได้", + "gapAfter": "ช่องว่างหลัง", "adjustAllShortcuts": "แป้นพิมพ์ลัด:\r\n\r\n• Shift + ซ้าย/ขวา: เลื่อน 10 มิลลิวินาที\r\n• Ctrl + ซ้าย/ขวา: เลื่อน 100 มิลลิวินาที\r\n• Alt + ซ้าย/ขวา: เลื่อน 500 มิลลิวินาที", "adjustAllShortcutsFrames": "แป้นพิมพ์ลัด:\r\n\r\n• Shift + ซ้าย/ขวา: เลื่อน 1 เฟรม\r\n• Ctrl + ซ้าย/ขวา: เลื่อน 10 เฟรม\r\n• Alt + ซ้าย/ขวา: เลื่อน 1 วินาที", "offsetInSeconds": "ออฟเซ็ตเป็นวินาที", @@ -2936,6 +3294,7 @@ "lineMerge": "การรวมบรรทัด", "delayInSecondsBetweenRequests": "หน่วงเวลาเป็นวินาทีระหว่างคำขอ", "maxBytesPerRequest": "จำนวนไบต์สูงสุดต่อคำขอ", + "keepMusicLinesUntranslated": "ไม่แปลบรรทัดที่อยู่ในสัญลักษณ์ดนตรี (♪ ... ♪)", "promptText": "ข้อความพรอมต์", "resetPromptToDefault": "รีเซ็ตพรอมต์เป็นค่าเริ่มต้น", "translateEachLineSeparately": "แปลแต่ละบรรทัดแยกกัน", @@ -2949,11 +3308,10 @@ "readyToTranslate": "พร้อมแปล", "translating": "กำลังแปล...", "translationComplete": "แปลเสร็จสมบูรณ์", + "translateInPlaceNoOriginal": "แปลแทนที่ (ไม่สร้างต้นฉบับ)", "translationCancelled": "ยกเลิกการแปลแล้ว", "swapLanguages": "สลับภาษาต้นทางและปลายทาง", "xIsAlreadyDownloadedReDownload": "{0} ถูกดาวน์โหลดไว้แล้ว ดาวน์โหลดใหม่หรือไม่?", - "advancedDotDotDot": "ขั้นสูง...", - "advancedSettings": "การตั้งค่าขั้นสูง", "advancedSettingsSubtitle": "ตัวเลือกแบตช์ บริบท และการสุ่มตัวอย่างสำหรับเอนจิน llama.cpp ขั้นสูง", "context": "บริบท", "synopsis": "เรื่องย่อ", @@ -2977,6 +3335,7 @@ "useOnlyExtraServerParametersHint": "เริ่ม llama-server ด้วยพารามิเตอร์ข้างต้นแทนการปรับแต่งของ Subtitle Edit เอง - จะยังคงกำหนดเฉพาะโมเดล โฮสต์ และพอร์ตเท่านั้น", "serverRunningAtX": "เซิร์ฟเวอร์กำลังทำงานที่ {0}", "customPromptHint": "คำสั่งกำหนดเอง ({0} = ภาษาต้นทาง, {1} = ภาษาปลายทาง); เว้นว่าง = ใช้พรอมต์ในตัว", + "editTranslationHint": "คลิกคำแปลของแถวที่เลือกเพื่อแก้ไข (Enter บันทึก, Escape ยกเลิก)", "llamaCppDownloadEngineAndModelPrompt": "llama.cpp ต้องดาวน์โหลดเอนจิน llama-server และโมเดลการแปล ดาวน์โหลดตอนนี้หรือไม่?", "llamaCppDownloadEnginePrompt": "llama.cpp ต้องดาวน์โหลดเอนจิน llama-server ดาวน์โหลดตอนนี้หรือไม่?", "llamaCppDownloadModelPrompt": "llama.cpp ต้องดาวน์โหลดโมเดลการแปลที่เลือก ดาวน์โหลดตอนนี้หรือไม่?" @@ -2989,7 +3348,6 @@ "dialogStyleDashBothLinesWithSpace": "ขีดหน้าทั้งสองบรรทัดพร้อมเว้นวรรค", "dialogStyleDashBothLinesWithoutSpace": "ขีดหน้าทั้งสองบรรทัดไม่เว้นวรรค", "continuationStyle": "รูปแบบความต่อเนื่อง", - "continuationStyleNone": "ไม่มี", "continuationStyleNoneTrailingDots": "ไม่มี, ใช้จุดสำหรับช่วงหยุด (ท้ายเท่านั้น)", "continuationStyleNoneLeadingTrailingDots": "ไม่มี, ใช้จุดสำหรับช่วงหยุด", "continuationStyleNoneTrailingEllipsis": "ไม่มี, ใช้จุดไข่ปลาสำหรับช่วงหยุด (ท้ายเท่านั้น)", @@ -3000,7 +3358,6 @@ "continuationStyleLeadingTrailingEllipsis": "จุดไข่ปลา", "continuationStyleLeadingTrailingDash": "ขีด", "continuationStyleLeadingTrailingDashDots": "ขีด แต่ใช้จุดสำหรับช่วงหยุด", - "continuationStyleCustom": "กำหนดเอง", "cpsLineLengthStyle": "CPS/ความยาวบรรทัด", "cpsLineLengthStyleCalcAll": "นับอักขระทั้งหมด", "cpsLineLengthStyleCalcNoSpaceCpsOnly": "นับทั้งหมดยกเว้นช่องว่าง, เฉพาะ CPS", @@ -3013,14 +3370,6 @@ "cpsLineLengthStyleCalcNoSpaceOrPunctuationCpsOnly": "ไม่รวมช่องว่างหรือเครื่องหมายวรรคตอน, เฉพาะ CPS", "cpsLineLengthStyleCalcIgnoreArabicDiacritics": "ไม่นับเครื่องหมายกำกับเสียงอาหรับ", "cpsLineLengthStyleCalcIgnoreArabicDiacriticsNoSpace": "ไม่นับเครื่องหมายกำกับเสียงอาหรับ, ไม่รวมช่องว่าง", - "timeCodeModeHhMmSsMs": "HH:MM:SS:MS", - "timeCodeModeHhMmSsFf": "HH:MM:SS:FF", - "splitBehaviorPrevious": "เพิ่มช่องว่างทางซ้ายของจุดแยก (โฟกัสด้านขวา)", - "splitBehaviorHalf": "เพิ่มช่องว่างตรงกลางจุดแยก (โฟกัสด้านซ้าย)", - "splitBehaviorNext": "เพิ่มช่องว่างทางขวาของจุดแยก (โฟกัสด้านซ้าย)", - "subtitleListActionNothing": "ไม่ทำอะไร", - "subtitleListActionVideoGoToPositionAndPause": "ไปยังตำแหน่งวิดีโอและหยุดชั่วคราว", - "subtitleListActionVideoGoToPositionAndPlay": "ไปยังตำแหน่งวิดีโอและเล่น", "subtitleGridCenterText": "จัดข้อความกึ่งกลางในตารางคำบรรยาย", "minGapCalculateDotDotDot": "คำนวณช่องว่างต่ำสุดจากอัตราเฟรม...", "minGapCalculateTitle": "ช่องว่างต่ำสุดระหว่างคำบรรยาย (มิลลิวินาที)", @@ -3052,8 +3401,14 @@ "checkForUpdatesChannelStableAndBeta": "เวอร์ชันเสถียรและเบต้า", "assaAutoSetResolution": "ASSA: กำหนดความละเอียด (PlayResX/PlayResY) จากวิดีโอเมื่อเปิดวิดีโอ", "assaAutoSetResolutionPrompt": "ASSA: ถามก่อนเปลี่ยนความละเอียดให้ตรงกับวิดีโอ (ไม่เช่นนั้นจะปรับสเกลอัตโนมัติ)", + "videoControls": "ตัวควบคุมวิดีโอ", + "videoControlsEdit": "แก้ไขตัวควบคุมวิดีโอ...", + "videoControlsPositionText": "ข้อความตำแหน่ง / ระยะเวลา", + "videoControlsVideoFileName": "ชื่อไฟล์วิดีโอ", + "videoControlsPlayerName": "ชื่อเครื่องเล่นวิดีโอ", "mergeKeepEndTime": "รวมบรรทัด: คงเวลาสิ้นสุดไว้ (ยอมให้ซ้อนทับกับคำบรรยายถัดไป)", "mergeKeepEndTimeOnlyAssa": "รวมบรรทัด: คงเวลาสิ้นสุดไว้เฉพาะ \"Advanced Sub Station Alpha\"", + "mergeAutoBreak": "รวมบรรทัด: ตัดบรรทัดอัตโนมัติสำหรับข้อความที่รวม (ปิด = คงการตัดบรรทัดเดิมไว้)", "defaultSaveLocation": "ตำแหน่งบันทึกเริ่มต้น", "defaultSaveLocationSourceFileFolder": "โฟลเดอร์ของไฟล์คำบรรยาย/วิดีโอ", "defaultSaveLocationLastUsedFolder": "โฟลเดอร์ที่ใช้ล่าสุด", @@ -3061,18 +3416,10 @@ "defaultSaveLocationSubtitleFileFolder": "โฟลเดอร์ของไฟล์คำบรรยาย", "defaultSaveLocationCustomFolder": "โฟลเดอร์ที่กำหนดเอง", "subtitleListActionVideoGoToPositionAndPlayCurrentAndPause": "ไปยังตำแหน่งวิดีโอ เล่นบรรทัดปัจจุบัน แล้วหยุดชั่วคราว", - "subtitleListActionEditText": "ไปยังกล่องแก้ไขข้อความ", "subtitleListActionVideoGoToPositionMinus1SecAndPause": "ไปยังตำแหน่งวิดีโอ - 1 วินาทีและหยุดชั่วคราว", "subtitleListActionVideoGoToPositionMinusHalfSecAndPause": "ไปยังตำแหน่งวิดีโอ - 0.5 วินาทีและหยุดชั่วคราว", "subtitleListActionVideoGoToPositionMinus1SecAndPlay": "ไปยังตำแหน่งวิดีโอ - 1 วินาทีและเล่น", - "subtitleListActionEditTextAndPause": "ไปยังกล่องแก้ไขข้อความ และหยุดชั่วคราวที่ตำแหน่งวิดีโอ", - "autoBackupEveryMinute": "ทุกนาที", - "autoBackupEveryXthMinute": "ทุก {0} นาที", - "profiles": "โปรไฟล์", - "autoBackupDeleteAfterXMonths": "ลบข้อมูลสำรองอัตโนมัติหลังจาก {0} เดือน", "searchSettingsDotDotDot": "ค้นหาการตั้งค่า...", - "syntaxColoring": "การระบายสีไวยากรณ์", - "waveformSpectrogram": "รูปคลื่นเสียง/สเปกโตรแกรม", "network": "เครือข่าย", "fileTypeAssociations": "การเชื่อมโยงประเภทไฟล์", "textBoxColorTags": "แท็กสี (HTML/ASSA) ในกล่องข้อความคำบรรยาย", @@ -3105,10 +3452,9 @@ "darkThemeForegroundColor": "สีพื้นหน้าธีมมืด", "darkThemeBackgroundColor": "สีพื้นหลังธีมมืด", "showGridLines": "แสดงเส้นตาราง", - "resetSettings": "รีเซ็ตการตั้งค่า?", - "resetSettingsDetail": "การดำเนินการนี้จะรีเซ็ตการตั้งค่าทั้งหมดเป็นค่าเริ่มต้น\n\nดำเนินการต่อหรือไม่?", "showHorizontalLineAboveToolbar": "แสดงเส้นแนวนอนเหนือแถบเครื่องมือ", "bookmarkColor": "สีบุ๊กมาร์ก", + "spellCheckHighlightColor": "สีเน้นของการตรวจสอบการสะกด", "singleLineMaxLength": "ความยาวสูงสุดต่อบรรทัด", "optimalCharsPerSec": "อักขระ/วินาทีที่เหมาะสม", "maxCharsPerSec": "อักขระ/วินาทีสูงสุด", @@ -3121,13 +3467,21 @@ "unbreakSubtitlesShortThan": "ยกเลิกการตัดบรรทัดคำบรรยายที่สั้นกว่า", "newEmptyDefaultMs": "ระยะเวลาเริ่มต้นของคำบรรยายใหม่ (มิลลิวินาที)", "timeCodeUpDownStepMs": "ขั้นการเพิ่ม/ลดเวลา (มิลลิวินาที)", + "moveSelectedLinesStepMs": "ระยะก้าวของปุ่มลัดย้ายบรรทัดที่เลือก (ms)", + "moveStartEndStepMs": "ระยะก้าวของปุ่มลัดย้ายเวลาเริ่มต้น/สิ้นสุด (ms)", + "moveLinesShortenNeighbor": "ย้ายบรรทัด: ย่อบรรทัดก่อนหน้า/ถัดไปแทนการซ้อนทับ", "promptBeforeDelete": "ถามก่อนลบ", "rememberPositionAndSize": "จดจำตำแหน่งและขนาดหน้าต่าง", + "titleBarFullFileName": "แสดงพาธไฟล์แบบเต็มในแถบชื่อเรื่อง", + "showRecentFiles": "แสดงไฟล์ล่าสุด (สำหรับเปิดซ้ำ)", "openLastFileOnStart": "เปิดไฟล์ล่าสุดเมื่อเริ่มโปรแกรม", "autoSave": "บันทึกอัตโนมัติ (บันทึกไฟล์ที่เปิดอยู่ระหว่างแก้ไข)", "autoBackupOn": "สำรองข้อมูลอัตโนมัติ", "autoBackupIntervalMinutes": "ช่วงเวลาสำรองข้อมูลอัตโนมัติ (นาที)", "autoBackupDeleteAfterDays": "ระยะเวลาเก็บข้อมูลสำรองอัตโนมัติ (วัน)", + "settingsBackupOn": "สำรองการตั้งค่าอัตโนมัติ", + "settingsBackupIntervalDays": "ช่วงเวลาสำรองการตั้งค่า (วัน, 0 = ทุกครั้งที่เริ่ม)", + "settingsBackupMaxCount": "จำนวนข้อมูลสำรองการตั้งค่าที่จะเก็บ", "autoConvertToUtf8": "แปลงการเข้ารหัสเป็น UTF-8 อัตโนมัติเมื่อเปิด", "autoTrimWhiteSpace": "ตัดช่องว่างอัตโนมัติ", "defaultEncoding": "การเข้ารหัสเริ่มต้น", @@ -3143,23 +3497,14 @@ "colorGapTooShort": "ระบายสีหากช่องว่างสั้นเกินไป", "errorBackgroundColor": "สีพื้นหลังข้อผิดพลาด", "waveformDrawGridLines": "วาดเส้นตาราง", - "waveformCenterVideoPosition": "จัดตำแหน่งวิดีโอกึ่งกลาง", + "waveformUseSkiaRenderer": "ใช้ตัวเรนเดอร์แบบเร็ว (ทดลอง)", "waveformCenterVideoPositionAlsoWhenPaused": "จัดตำแหน่งวิดีโอกึ่งกลางแม้ขณะหยุดชั่วคราว", + "waveformSelectCurrentSubtitleWhilePaused": "เลือกคำบรรยายปัจจุบันขณะหยุดชั่วคราวด้วย", + "waveformRememberZoom": "จดจำการซูม (แนวนอนและแนวตั้ง)", "waveformShowToolbar": "แสดงแถบเครื่องมือ", "waveformShowToolbarEdit": "แก้ไขแถบเครื่องมือ...", "waveformShowToolbarEditLabel": "รายการในแถบเครื่องมือ", "waveformSpectrogramCombinedWaveformHeight": "รูปคลื่นเสียง/สเปกโตรแกรมรวมกัน, ความสูงรูปคลื่นเสียง %", - "showWaveformToolbarPlay": "แถบเครื่องมือ: แสดงปุ่มเล่น", - "showWaveformToolbarRepeat": "แถบเครื่องมือ: แสดงปุ่มเล่นซ้ำ", - "showWaveformToolbarRemoveBlankLines": "แถบเครื่องมือ: แสดงปุ่มลบบรรทัดว่าง", - "showWaveformToolbarNew": "แถบเครื่องมือ: แสดงปุ่มคำบรรยายใหม่", - "showWaveformToolbarSetStart": "แถบเครื่องมือ: แสดงปุ่มตั้งจุดเริ่มต้น", - "showWaveformToolbarSetEnd": "แถบเครื่องมือ: แสดงปุ่มตั้งจุดสิ้นสุด", - "showWaveformToolbarStartAndOffsetTheRest": "แถบเครื่องมือ: แสดงปุ่มตั้งจุดเริ่มต้นและเลื่อนส่วนที่เหลือ", - "showWaveformToolbarHorizontalZoom": "แถบเครื่องมือ: แสดงแถบเลื่อนซูมแนวนอน", - "showWaveformToolbarVerticalZoom": "แถบเครื่องมือ: แสดงแถบเลื่อนซูมแนวตั้ง", - "showWaveformToolbarVideoPositionSlider": "แถบเครื่องมือ: แสดงแถบเลื่อนตำแหน่งวิดีโอ", - "showWaveformToolbarPlaybackSpeed": "แถบเครื่องมือ: แสดงความเร็วการเล่น", "waveformFocusTextboxAfterInsertNew": "โฟกัสกล่องข้อความหลังแทรก", "waveformInvertMouseWheel": "กลับทิศทางล้อเมาส์", "waveformMouseWheelSetsVideoPosition": "ล้อเมาส์ตั้งตำแหน่งวิดีโอ", @@ -3180,20 +3525,20 @@ "fixCommonErrorsSkipStep1": "แก้ไขข้อผิดพลาดทั่วไป: ข้ามขั้นตอนที่ 1 (เลือกการแก้ไข)", "showPluginsMenu": "แสดงเมนูปลั๊กอิน", "proxyAddress": "ที่อยู่พร็อกซี", - "username": "ชื่อผู้ใช้", "password": "รหัสผ่าน", "defaultFormat": "รูปแบบเริ่มต้น", "defaultSaveAsFormat": "รูปแบบ \"บันทึกเป็น\" เริ่มต้น", "favoriteSubtitleFormats": "รูปแบบคำบรรยายโปรด", "favoriteLanguages": "ภาษาโปรด", "webVttUseXTimestampMap": "WebVTT: ใช้ X-TIMESTAMP-MAP (เลื่อนรหัสเวลาเมื่อโหลด)", - "showStopButton": "แสดงปุ่มหยุด", - "showFullscreenButton": "แสดงปุ่มเต็มหน้าจอ", "fullscreenHideControls": "ซ่อนแถบควบคุมวิดีโอในโหมดเต็มหน้าจอ", + "showSecondarySubtitleDialog": "แสดงกล่องโต้ตอบสไตล์เมื่อเปิดไฟล์คำบรรยายรอง", + "rememberSecondarySubtitleFile": "จดจำไฟล์คำบรรยายรอง", "autoOpenVideoFile": "เปิดไฟล์วิดีโออัตโนมัติเมื่อเปิดคำบรรยาย", "downloadMpv": "ดาวน์โหลด mpv", "downloadVlc": "ดาวน์โหลด VLC", "allowSingleLetterShortcutsInTextbox": "อนุญาตแป้นลัดตัวอักษรเดี่ยวในกล่องข้อความ", + "allowTextNavigationShortcutsInTextbox": "อนุญาตปุ่มลัดบนปุ่มนำทางข้อความ (Ctrl+Left/Right, Home/End) ในกล่องข้อความ", "spellCheckEnglishTreatInApostropheAsIng": "ตรวจการสะกด: ถือว่าคำที่ลงท้ายด้วย 'in'' เป็น 'ing' (ภาษาอังกฤษเท่านั้น)", "goToLineNumberSetsVideoPosition": "การไปยังหมายเลขบรรทัดตั้งตำแหน่งวิดีโอด้วย", "adjustAllTimesRememberLineSelectionChoice": "ปรับเวลาทั้งหมด จดจำตัวเลือกการเลือกบรรทัด", @@ -3205,6 +3550,7 @@ "showAssaLayer": "แสดงกล่องเลเยอร์ ASSA", "waveformCursorColor": "สีเคอร์เซอร์/หัวอ่านรูปคลื่นเสียง", "waveformShotChangeColor": "สีจุดเปลี่ยนฉากบนรูปคลื่นเสียง", + "waveformGridColor": "สีเส้นตารางของรูปคลื่นเสียง", "waveformParagraphLeftColor": "สีขอบซ้ายรูปคลื่นเสียง", "waveformParagraphRightColor": "สีขอบขวารูปคลื่นเสียง", "waveformFancyHighColor": "สีช่วงสูงแบบหรูหราของรูปคลื่นเสียง", @@ -3232,27 +3578,31 @@ "resetSyntaxColoring": "รีเซ็ตการระบายสีไวยากรณ์", "resetWaveform": "รีเซ็ตรูปคลื่นเสียง", "resetRules": "รีเซ็ตกฎ", - "useFrameMode": "ใช้โหมดเฟรม (hh.mm.ss.ff)", + "timeCodeMode": "โหมดไทม์โค้ด", + "timeCodeModeTime": "เวลา (hh:mm:ss,zzz)", + "timeCodeModeFrames": "เฟรม (hh:mm:ss:ff)", + "timeCodeModeFrameNumbers": "หมายเลขเฟรม", + "frameModeSnapToVideoFrames": "เก็บเวลาตามเฟรมวิดีโอ (ไม่ใช่เฟรมภายในแต่ละวินาที)", "textBoxLimitNewLines": "จำกัดจำนวนบรรทัดในกล่องข้อความคำบรรยาย", - "mpvVideoOutput": "เอาต์พุตวิดีโอ (mpv)", "mpvOpenGl": "libmpv - OpenGL", "mpvSoftwareRendering": "libmpv - การเรนเดอร์ด้วยซอฟต์แวร์ (ช้า)", + "mpvMetalRendering": "libmpv - Metal (ทดลอง, ต้องเริ่มใหม่)", "mpvWidRendering": "libmpv - การเรนเดอร์ Native Window ID", "waveFormsAndSpectrogramFoldersContainsX": "โฟลเดอร์ \"Waveforms\" และ \"spectrogram\" มีขนาด {0}", "deleteWaveformAndSpectrogramFoldersQuestion": "ลบไฟล์ \"Waveforms\" และ \"Spectrogram\" หรือไม่?", "waveformGenerateSpectrogram": "สร้างสเปกโตรแกรม", "waveformAutoGenerate": "สร้างรูปคลื่นเสียงอัตโนมัติเมื่อเปิดวิดีโอ", "waveformSpectrogramMode": "โหมดสเปกโตรแกรม", - "waveformCenterOnSingleClick": "จัดกึ่งกลางเมื่อคลิกครั้งเดียว", - "waveformSingleClickSelectsSubtitle": "เลือกคำบรรยายเมื่อคลิกครั้งเดียว", "waveformRightClickSelectsSubtitle": "เลือกคำบรรยายเมื่อคลิกขวา", - "waveformPauseOnSingleClick": "หยุดชั่วคราวเมื่อคลิกครั้งเดียว", "waveformDrawStyle": "รูปแบบการวาดรูปคลื่นเสียง", "waveformExtractAudioFormat": "รูปแบบการแยกเสียง", "waveformExtractAudioSampleRate": "อัตราสุ่มตัวอย่างการแยกเสียง", "waveformExtractAudioSampleRateOriginal": "ต้นฉบับ (คงตามแหล่งที่มา)", "waveformExtractAudioBitRate": "บิตเรตการแยกเสียง (MP3/M4A)", + "ffmpegUseCenterChannelOnly": "ใช้เฉพาะช่องกลางด้านหน้าสำหรับเสียง 5.1 (FFmpeg)", "vlcWidRendering": "libVLC - การเรนเดอร์ Native Window ID", + "ffmpegSoftwareRendering": "FFmpeg", + "downloadFfmpegLibs": "ดาวน์โหลดไลบรารี FFmpeg (สำหรับเครื่องเล่นวิดีโอ FFmpeg)", "subtitleGridEnterKeyAction": "การกระทำเมื่อกด Enter ในตารางคำบรรยาย", "subtitleSingleClickAction": "การกระทำเมื่อคลิกครั้งเดียวในตารางคำบรรยาย", "subtitleDoubleClickAction": "การกระทำเมื่อดับเบิลคลิกในตารางคำบรรยาย", @@ -3270,6 +3620,7 @@ "subtitleGridFormattingShowFormatting": "แสดงการจัดรูปแบบ", "subtitleGridFormattingShowTags": "แสดงแท็ก", "subtitleGridFormattingHideTags": "ซ่อนแท็ก", + "subtitleGridFormattingShowFormattingKeepTags": "แสดงการจัดรูปแบบ คงแท็กที่ไม่ใช่การแสดงผลไว้", "waveformParagraphBackgroundColor": "สีพื้นหลังคำบรรยายบนรูปคลื่นเสียง", "waveformParagraphSelectedBackgroundColor": "สีพื้นหลังคำบรรยายที่เลือกบนรูปคลื่นเสียง", "waveformAllowOverlap": "อนุญาตการทับซ้อน (เมื่อย้าย/ปรับขนาด)", @@ -3279,17 +3630,18 @@ "saveAsBehaviorUseVideoFileName": "ใช้ชื่อไฟล์วิดีโอ", "saveAsBehaviorUseSubtitleFileName": "ใช้ชื่อไฟล์คำบรรยาย", "saveAsAppendLanguageCodeTwoLetter": "สองตัวอักษร", - "saveAsAppendLanguageCodeThreeLetter": "สามตัวอักษร (ISO 639-2/T)", - "saveAsAppendLanguageCodeThreeLetterBibliographic": "สามตัวอักษร (ISO 639-2/B)", "saveAsAppendLanguageCodeLanguageName": "ชื่อภาษา", "splitOddLineActionWeightTop": "ให้น้ำหนักด้านบน", "splitOddLineActionWeightBottom": "ให้น้ำหนักด้านล่าง", "splitOddLinesAction": "การกระทำเมื่อแยกบรรทัดคี่", "ocrUseWordSplitList": "OCR: ใช้รายการแยกคำ", "ocrGuessUnknownWords": "OCR: พยายามเดาคำที่ไม่รู้จัก", + "spellCheckRememberUseAlwaysList": "ตรวจสอบการสะกด: จดจำรายการ \"ใช้เสมอ\"", + "fixShortDisplayTimesAllowMoveStartTime": "แก้ไขข้อผิดพลาดทั่วไป: การแก้ไขเวลาแสดงผลที่สั้นอาจย้ายเวลาเริ่มต้น", "speechToTextSelectedLinesPromptFirstTimeOnly": "แปลงเสียงพูดเป็นข้อความ: บรรทัดที่เลือก, ถามภาษา/เอนจินเฉพาะครั้งแรก", "multipleReplaceShowDotDotDotButtons": "การแทนที่หลายรายการ: แสดงปุ่มเมนูบริบท", "gridFocusTextboxAfterInsertNew": "ตาราง: โฟกัสกล่องข้อความหลังแทรกคำบรรยายใหม่", + "undoRedoGoToChangedLine": "เลิกทำ/ทำซ้ำ: ไปยังบรรทัดที่เปลี่ยนแปลง (และตำแหน่งวิดีโอ)", "musicSymbol": "สัญลักษณ์ดนตรี", "musicSymbolsToReplace": "สัญลักษณ์ดนตรีที่จะแทนที่ (คั่นด้วยจุลภาค)", "textToSpeechPromptMergeContinuationLines": "แปลงข้อความเป็นเสียงพูด: ถามเพื่อรวมบรรทัดต่อเนื่อง", @@ -3298,8 +3650,7 @@ "useFocusedButtonBackgroundColor": "ใช้สีพื้นหลังปุ่มที่โฟกัส", "focusedButtonBackgroundColor": "สีพื้นหลังปุ่มที่โฟกัส", "forceCrLfOnSave": "บังคับใช้ CR+LF เมื่อบันทึก (ไฟล์คำบรรยายข้อความ)", - "showWaveformToolbarPlayNext": "แถบเครื่องมือ: แสดงปุ่มเล่นถัดไป", - "showWaveformToolbarPlaySelection": "แถบเครื่องมือ: แสดงปุ่มเล่นส่วนที่เลือก", + "linuxClipboardUseExternalTool": "คัดลอกข้อความผ่าน xclip/wl-copy (คงอักขระที่มีเครื่องหมายกำกับ)", "showFormatLimitWarning": "เตือนเมื่อบันทึกหากบรรทัดเกินข้อจำกัดของรูปแบบ (เช่น SCC 32 อักขระ/บรรทัด)", "textBoxButtonShowAutoBreak": "กล่องข้อความ: แสดงปุ่มตัดบรรทัดอัตโนมัติ", "textBoxButtonShowUnbreak": "กล่องข้อความ: แสดงปุ่มยกเลิกการตัดบรรทัด", @@ -3311,12 +3662,18 @@ "waveformDoubleClickAction": "การกระทำเมื่อดับเบิลคลิกบนรูปคลื่นเสียง (หลังการกระทำคลิกครั้งเดียว)", "allSettings": "การตั้งค่าทั้งหมด", "uiScale": "มาตราส่วน UI (%)", + "fontScale": "มาตราส่วนฟอนต์ (%)", + "textSelectionOpacity": "ความทึบของการเลือกข้อความ (%)", "waveformToolbarItems": "รายการแถบเครื่องมือรูปคลื่นเสียง", "matchIconColorToDarkTheme": "ปรับสีไอคอนให้เข้ากับสีพื้นหน้าธีมมืด", "subtitlePreviewProperties": "คุณสมบัติตัวอย่างคำบรรยาย", "usePositionFromSubtitleFile": "ใช้ตำแหน่งจากไฟล์คำบรรยาย (TTML/PAC/EBU STL)", "marginIsPartOfSubtitleArea": "ระยะขอบเป็นส่วนหนึ่งของพื้นที่คำบรรยาย", "textJustify": "จัดบรรทัดชิดขอบเต็มแนว", + "deinterlace": "Deinterlace วิดีโอ", + "deinterlaceOff": "ปิด", + "deinterlaceAuto": "อัตโนมัติ (เฉพาะเฟรมแบบ interlaced)", + "deinterlaceAlways": "เสมอ", "pixelWidthInfo": "เส้นสีเขียว = ขีดจำกัดความกว้างสูงสุด | พื้นที่สีแดง = ข้อความเกินขีดจำกัด", "spellCheckEngineHunSpelll": "Hunspell", "spellCheckEngineMsWord": "MS Word", @@ -3325,117 +3682,89 @@ "waveformColorTheme": "ธีมสี:", "waveformSaveAsCustomTheme": "บันทึกเป็นธีมกำหนดเอง...", "waveformLoadThemeDotDotDot": "โหลดธีม...", - "waveformExportThemeDotDotDot": "ส่งออกธีม..." + "waveformExportThemeDotDotDot": "ส่งออกธีม...", + "waveformSaveCustomThemeTitle": "บันทึกธีมกำหนดเอง", + "waveformThemeFileType": "ธีมรูปคลื่นเสียงของ Subtitle Edit", + "waveformThemeHighContrast": "คอนทราสต์สูง", + "waveformThemeOceanBlue": "น้ำเงินมหาสมุทร", + "waveformThemeWarmSunset": "อาทิตย์ตกอบอุ่น", + "waveformThemeForest": "ป่าไม้", + "waveformThemeMidnightPurple": "ม่วงเที่ยงคืน", + "waveformThemeRetroAmber": "อำพันย้อนยุค", + "waveformThemeIce": "น้ำแข็ง" }, "shortcuts": { - "title": "แป้นลัด", "searchShortcuts": "ค้นหาแป้นลัด...", - "filter": "ตัวกรอง", - "categoryGeneral": "ทั่วไป", "categorySubtitleGridAndTextBox": "มุมมองรายการคำบรรยายและกล่องข้อความ", "categorySubtitleGrid": "มุมมองรายการคำบรรยาย", - "categoryWaveform": "รูปคลื่นเสียง", "categoryTextBox": "กล่องข้อความ", "categoryFile": "ไฟล์", "categoryVideo": "วิดีโอ", "categorySync": "การซิงก์", - "categoryTranslate": "แปล", - "categorySearch": "ค้นหา", - "categoryTools": "เครื่องมือ", "categoryAi": "AI", + "categoryCustom": "กำหนดเอง", + "editCustomShortcut": "แก้ไขปุ่มลัดกำหนดเอง", + "customShortcutTextBoxKeyHint": "ใช้ปุ่มร่วมกับ Ctrl/Alt ที่นี่ - ปุ่มเดี่ยวจะไม่สามารถพิมพ์ในกล่องข้อความได้อีก", + "customShortcutNumberX": "ปุ่มลัดกำหนดเอง #{0}", + "customShortcutNumberXY": "ปุ่มลัดกำหนดเอง #{0}: {1}", + "customShortcutStep": "ขั้นตอน", + "customShortcutSteps": "ขั้นตอน", + "customShortcutStepRunCommand": "เรียกใช้คำสั่ง", + "customShortcutStepInsertText": "แทรกข้อความ", + "customShortcutStepReplace": "ค้นหาและแทนที่", + "customShortcutInsertAtCursor": "ที่เคอร์เซอร์", + "customShortcutInsertAtStart": "ที่ต้นข้อความ", + "customShortcutInsertAtEnd": "ที่ท้ายข้อความ", + "customShortcutSummaryRunCommandX": "เรียกใช้: {0}", + "customShortcutSummaryInsertXY": "แทรก \"{0}\" ({1})", + "customShortcutSummaryReplaceXY": "แทนที่ \"{0}\" ด้วย \"{1}\"", + "customShortcutSummaryReplaceRegexXY": "แทนที่ regex \"{0}\" ด้วย \"{1}\"", + "customShortcutPickCommand": "โปรดเลือกคำสั่งที่จะเรียกใช้", + "customShortcutEnterText": "โปรดป้อนข้อความที่จะแทรก", + "customShortcutEnterFind": "โปรดป้อนข้อความที่จะค้นหา", + "customShortcutInvalidRegexX": "นิพจน์ทั่วไปไม่ถูกต้อง: {0}", + "customShortcutStepsHint": "ขั้นตอนจะทำงานจากบนลงล่าง ขั้นตอนข้อความทำงานกับบรรทัดที่เลือก คำสั่งที่เปิดหน้าต่างจะทำงานต่อหลังจากหน้าต่างปิด", "activeIn": "ใช้งานได้ใน", "activeInEverywhere": "ทุกที่", - "generalSetupLikeSe4": "ตั้งค่าเหมือน Subtitle Edit 4 (ธีม, แป้นลัด, กฎการแทนที่)", - "generalMergeSelectedLines": "รวมบรรทัดที่เลือก", - "generalMergeWithPrevious": "รวมกับบรรทัดก่อนหน้า", - "generalMergeWithNext": "รวมกับบรรทัดถัดไป", - "generalMergeWithPreviousAndUnbreak": "รวมกับบรรทัดก่อนหน้าและยกเลิกการตัดบรรทัด", - "generalMergeWithNextAndUnbreak": "รวมกับบรรทัดถัดไปและยกเลิกการตัดบรรทัด", - "generalMergeWithPreviousAndAutoBreak": "รวมกับบรรทัดก่อนหน้าและตัดบรรทัดอัตโนมัติ", - "generalMergeWithNextAndAutoBreak": "รวมกับบรรทัดถัดไปและตัดบรรทัดอัตโนมัติ", - "generalMergeSelectedLinesAndAutoBreak": "รวมบรรทัดที่เลือกและตัดบรรทัดอัตโนมัติ", "generalMergeSelectedLinesAndUnbreak": "รวมบรรทัดที่เลือกและยกเลิกการตัดบรรทัด", "generalMergeSelectedLinesAndUnbreakCjk": "รวมบรรทัดที่เลือกและยกเลิกการตัดบรรทัดโดยไม่มีช่องว่าง (CJK)", "generalUnbreakNoSpaceCjk": "ยกเลิกการตัดบรรทัดโดยไม่มีช่องว่าง (CJK)", - "generalMergeSelectedLinesOnlyFirstText": "รวมบรรทัดที่เลือกเฉพาะข้อความแรก", + "generalToggleBreak": "สลับตัดบรรทัด/รวมบรรทัด", "generalMergeSelectedLinesBilingual": "รวมบรรทัดที่เลือกแบบสองภาษา", - "generalMergeWithPreviousBilingual": "รวมกับบรรทัดก่อนหน้าแบบสองภาษา", - "generalMergeWithNextBilingual": "รวมกับบรรทัดถัดไปแบบสองภาษา", - "generalMergeOriginalAndTranslation": "รวมต้นฉบับและคำแปล", "generalToggleTranslationMode": "สลับโหมดการแปล", - "generalSwitchOriginalAndTranslation": "สลับต้นฉบับและคำแปล", - "generalSwitchOriginalAndTranslationTextBoxes": "สลับกล่องข้อความต้นฉบับและคำแปล", "generalToggleTranslationAndOriginalInPreviews": "สลับคำแปลและต้นฉบับในตัวอย่างวิดีโอ/เสียง", "generalChooseLayout": "เลือกเลย์เอาต์", - "generalLayoutChooseX": "เลย์เอาต์ {0}", - "generalPlayFirstSelected": "เล่นบรรทัดแรกที่เลือก", - "generalGoToFirstSelectedLine": "ไปยังบรรทัดแรกที่เลือก", - "generalGoToNextEmptyLine": "ไปยังบรรทัดว่างถัดไป", "generalGoToNextSubtitle": "ไปยังคำบรรยายถัดไป", "generalGoToNextSubtitlePlayTranslate": "ไปยังคำบรรยายถัดไป (เล่นขณะแปล)", "generalGoToNextSubtitleCursorAtEnd": "ไปยังคำบรรยายถัดไป (เคอร์เซอร์ที่ท้าย)", "generalGoToPrevSubtitle": "ไปยังคำบรรยายก่อนหน้า", "generalGoToPrevSubtitlePlayTranslate": "ไปยังคำบรรยายก่อนหน้า (เล่นขณะแปล)", - "generalGoToStartOfCurrentSubtitle": "ไปยังจุดเริ่มต้นของคำบรรยายปัจจุบัน", - "generalGoToEndOfCurrentSubtitle": "ไปยังจุดสิ้นสุดของคำบรรยายปัจจุบัน", - "generalGoToPreviousSubtitleAndFocusVideo": "ไปยังคำบรรยายก่อนหน้าและโฟกัสวิดีโอ", - "generalGoToNextSubtitleAndFocusVideo": "ไปยังคำบรรยายถัดไปและโฟกัสวิดีโอ", - "generalGoToPrevSubtitleAndPlay": "ไปยังคำบรรยายก่อนหน้าและเล่น", - "generalGoToNextSubtitleAndPlay": "ไปยังคำบรรยายถัดไปและเล่น", - "generalGoToPreviousSubtitleAndFocusWaveform": "ไปยังคำบรรยายก่อนหน้าและโฟกัสรูปคลื่นเสียง", - "generalGoToNextSubtitleAndFocusWaveform": "ไปยังคำบรรยายถัดไปและโฟกัสรูปคลื่นเสียง", - "generalGoToLineNumber": "ไปยังหมายเลขบรรทัด", "generalGoToFirstLine": "ไปที่บรรทัดแรก", "generalGoToLastLine": "ไปที่บรรทัดสุดท้าย", "alsoSetVideoPosition": "กำหนดตำแหน่งวิดีโอด้วย", "generalGoToVideoPosition": "ไปยังตำแหน่งวิดีโอ", "generalToggleItalic": "สลับตัวเอียง", "generalToggleBold": "สลับตัวหนา", - "generalToggleBookmarks": "สลับบุ๊กมาร์ก", - "generalFocusTextBox": "โฟกัสกล่องข้อความ", - "generalToggleBookmarksWithText": "สลับบุ๊กมาร์กพร้อมข้อความ", - "generalEditBookmarks": "แก้ไขบุ๊กมาร์ก", - "fileNew": "ใหม่", "generalToggleUnderline": "สลับขีดเส้นใต้", + "generalToggleBox": "สลับกล่อง (EBU STL)", "fileOpen": "เปิด", "fileOpenKeepVideo": "เปิด (คงวิดีโอไว้)", "fileSave": "บันทึก", "fileSaveAs": "บันทึกเป็น", - "fileSaveAll": "บันทึกทั้งหมด", "fileSaveOriginal": "บันทึกต้นฉบับ", - "fileSaveOriginalAs": "บันทึกต้นฉบับเป็น", - "fileOpenOriginalSubtitle": "เปิดคำบรรยายต้นฉบับ", - "fileCloseOriginalSubtitle": "ปิดคำบรรยายต้นฉบับ", - "fileTranslatedSubtitle": "คำบรรยายที่แปลแล้ว", "fileCompare": "เปรียบเทียบ", "fileStatistics": "สถิติ", "fileImportPlainText": "นำเข้าข้อความธรรมดา", - "fileImportBluRaySupForOcr": "นำเข้า Blu-ray SUP สำหรับ OCR", - "fileImportBluRaySupForEdit": "นำเข้า Blu-ray SUP สำหรับแก้ไข", - "fileImportTimeCodes": "นำเข้ารหัสเวลา", "fileExportEbuStl": "ส่งออก EBU STL", "fileExportPac": "ส่งออก PAC", - "fileExportEdlClipName": "ส่งออกชื่อคลิป EDL", - "fileExportPlainText": "ส่งออกข้อความธรรมดา", - "fileExportCustomTextFormat1": "ส่งออกรูปแบบข้อความกำหนดเอง 1", - "fileExportCustomTextFormat2": "ส่งออกรูปแบบข้อความกำหนดเอง 2", - "fileExportCustomTextFormat3": "ส่งออกรูปแบบข้อความกำหนดเอง 3", "fileExit": "ออก", "openSeDataFolder": "เปิดโฟลเดอร์ Subtitle Edit", - "editFind": "ค้นหา", - "editFindNext": "ค้นหาถัดไป", "editFindPrevious": "ค้นหาก่อนหน้า", - "editReplace": "แทนที่", - "editMultipleReplace": "แทนที่หลายรายการ", "fillSelectedLinesWithClipboard": "เติมบรรทัดที่เลือกด้วยข้อความจากคลิปบอร์ด", - "editModifySelection": "ปรับเปลี่ยนการเลือก", - "listSelectAll": "เลือกทั้งหมด", - "listSelectFirst": "เลือกรายการแรก", - "listSelectLast": "เลือกรายการสุดท้าย", "listInverseSelection": "กลับการเลือก", "listDeleteSelection": "ลบส่วนที่เลือก", + "deleteSelectionEverywhere": "ลบส่วนที่เลือก (ทุกที่)", "rippleDeleteSelection": "ลบส่วนที่เลือกแบบริปเปิล", - "settings": "การตั้งค่า", "assigned": "กำหนดแล้ว", "unassigned": "ยังไม่กำหนด", "pressedKeyX": "ปุ่มที่กด: {0}", @@ -3443,6 +3772,7 @@ "detectKey": "ตรวจจับปุ่ม", "control": "Control", "alt": "Alt", + "altGr": "AltGr", "win": "Win", "shift": "Shift", "controlMac": "⌃", @@ -3453,11 +3783,14 @@ "resetShortcutsDetail": "คุณต้องการรีเซ็ตแป้นลัดทั้งหมดเป็นค่าเริ่มต้นหรือไม่?", "togglePlayPause": "สลับเล่น/หยุดชั่วคราว", "toggleLockTimeCodes": "สลับการล็อกรหัสเวลา", + "toggleTimeCodeMode": "สลับโหมดไทม์โค้ด (เวลา/เฟรม/หมายเลขเฟรม)", + "toggleScreenPrivacy": "สลับความเป็นส่วนตัวของหน้าจอ (ซ่อนชื่อไฟล์ แล้วซ่อนข้อความด้วย)", + "screenPrivacyFileNamesHidden": "ความเป็นส่วนตัวของหน้าจอ: ซ่อนชื่อไฟล์แล้ว", + "screenPrivacyFileNamesAndTextsHidden": "ความเป็นส่วนตัวของหน้าจอ: ซ่อนชื่อไฟล์และข้อความแล้ว", + "screenPrivacyOff": "ความเป็นส่วนตัวของหน้าจอ: ปิด", "duplicateSelectedLines": "ทำสำเนาบรรทัดที่เลือก", "sourceView": "มุมมองซอร์ส", - "showAlignmentPicker": "การจัดตำแหน่ง", "addOrEditBookmark": "เพิ่มหรือแก้ไขบุ๊กมาร์ก", - "listBookmarks": "แสดงรายการบุ๊กมาร์ก", "toggleBookmark": "สลับบุ๊กมาร์ก (บรรทัดที่เลือก, ไม่มีข้อความ)", "toggleForcedSelectedLines": "สลับบังคับแสดง (บรรทัดที่เลือก)", "goToNextBookmark": "ไปยังบุ๊กมาร์กถัดไป", @@ -3468,12 +3801,12 @@ "waveformSetStartAndSetEndOfPreviousMinusGap": "ตั้งจุดเริ่มต้นและตั้งจุดสิ้นสุดของบรรทัดก่อนหน้าลบช่องว่าง", "waveformSetEndAndStartOfNextAfterGap": "ตั้งจุดสิ้นสุดและจุดเริ่มต้นของบรรทัดถัดไปบวกช่องว่าง", "waveformSetEndAndStartOfNextAfterGapAndGoToNext": "ตั้งจุดสิ้นสุดและจุดเริ่มต้นของบรรทัดถัดไปบวกช่องว่างแล้วไปยังบรรทัดถัดไป", - "fetchFirstWordFromNextSubtitle": "ดึงคำแรกจากคำบรรยายถัดไป", "moveLastWordToNextSubtitle": "ย้ายคำสุดท้ายไปยังคำบรรยายถัดไป", "moveLastWordFromFirstLineDownCurrentSubtitle": "ย้ายคำสุดท้ายจากบรรทัดแรกลง (คำบรรยายปัจจุบัน)", "moveFirstWordFromNextLineUpCurrentSubtitle": "ย้ายคำแรกจากบรรทัดถัดไปขึ้น (คำบรรยายปัจจุบัน)", "moveTextFromCursorToNextAndGoToNext": "ย้ายข้อความหลังตำแหน่งเคอร์เซอร์ไปยังคำบรรยายถัดไปและไปยังบรรทัดถัดไป", "moveTextFromCursorToNextAndGoToNextAndPlay": "ย้ายข้อความหลังตำแหน่งเคอร์เซอร์ไปยังคำบรรยายถัดไป ไปยังบรรทัดถัดไปแล้วเล่น", + "moveTextFromCursorToNextAndGoToNextAndPlayAndPause": "ย้ายข้อความหลังตำแหน่งเคอร์เซอร์ไปยังคำบรรยายถัดไป ไปยังคำบรรยายถัดไป เล่น และหยุดชั่วคราวเมื่อจบ", "breakAtFirstSpaceFromCursor": "ตัดบรรทัดที่ช่องว่างแรกจากตำแหน่งเคอร์เซอร์", "breakAtFirstSpaceFromCursorAndGoToNext": "ตัดบรรทัดที่ช่องว่างแรกจากตำแหน่งเคอร์เซอร์แล้วไปบรรทัดถัดไป", "toggleFocusGridAndWaveform": "สลับโฟกัสระหว่างตารางคำบรรยายและรูปคลื่นเสียง/สเปกโตรแกรม", @@ -3485,7 +3818,6 @@ "videoGoToPreviousTimeCode": "ไปยังไทม์โค้ดก่อนหน้า", "videoGoToNextTimeCode": "ไปยังไทม์โค้ดถัดไป", "goToNextLineAndSetVideoPosition": "ไปยังคำบรรยายถัดไปและตั้งตำแหน่งวิดีโอ", - "textBoxDeleteSelectionNoClipboard": "กล่องข้อความ: ลบส่วนที่เลือก (ไม่ใช้คลิปบอร์ด)", "textBoxDeleteForward": "กล่องข้อความ: ลบส่วนที่เลือกหรืออักขระถัดไป (ลบไปข้างหน้า)", "textBoxCut": "กล่องข้อความ: ตัด", "textBoxCut2": "กล่องข้อความ: ตัด (ทางเลือก)", @@ -3504,6 +3836,7 @@ "listViewColumnTextUp": "คอลัมน์, เลื่อนข้อความขึ้น", "listViewColumnTextDown": "คอลัมน์, เลื่อนข้อความลง", "autoTranslateSelectedLines": "แปลอัตโนมัติบรรทัดที่เลือก...", + "autoTranslateSelectedLinesNoPrompt": "แปลอัตโนมัติบรรทัดที่เลือก (ไม่ถาม ใช้เอนจิน/ภาษาล่าสุด)", "setAssaResolution": "ตั้งความละเอียด ASSA (PlayResX/PlayResY)", "setShortcutForX": "ตั้งปุ่มลัดสำหรับ \"{0}\"", "commandFileNewKeepVideo": "ใหม่ (คงวิดีโอไว้)", @@ -3511,29 +3844,22 @@ "fileCloseOriginal": "ปิดต้นฉบับ", "fileCloseTranslation": "ปิดคำแปล", "restoreAutoBackup": "กู้คืนข้อมูลสำรองอัตโนมัติ", - "openContainingFolder": "เปิดโฟลเดอร์ที่เก็บไฟล์", "importTimeCodes": "นำเข้าไทม์โค้ด", "importSubtitleWithManuallyChosenEncoding": "นำเข้าคำบรรยายโดยเลือกการเข้ารหัสด้วยตนเอง", "exportBluRaySup": "ส่งออก Blu-ray SUP", "exportCustomTextFormat": "ส่งออกรูปแบบข้อความกำหนดเอง", "exportPlainText": "ส่งออกข้อความธรรมดา", - "showHistory": "แสดงประวัติ", "toggleRightToLeft": "สลับขวาไปซ้าย", "modifySelection": "ปรับเปลี่ยนส่วนที่เลือก", "adjustDurations": "ปรับระยะเวลา", "applyDurationLimits": "ใช้ขีดจำกัดระยะเวลา", "batchConvert": "แปลงเป็นชุด", - "bridgeGaps": "เชื่อมช่องว่าง", "applyMinGap": "ใช้ช่องว่างต่ำสุด", - "fixCommonErrors": "แก้ไขข้อผิดพลาดทั่วไป", "makeEmptyTranslationFromCurrentSubtitle": "สร้างคำแปลว่างจากคำบรรยายปัจจุบัน", - "mergeLinesWithSameText": "รวมบรรทัดที่มีข้อความเหมือนกัน", - "mergeLinesWithSameTimeCodes": "รวมบรรทัดที่มีไทม์โค้ดเหมือนกัน", "splitBreakLongLines": "แยก/จัดสมดุลบรรทัดยาว", "mergeShortLines": "รวมบรรทัดสั้น", "mergeContinuationLines": "รวมบรรทัดต่อเนื่อง", "snapAllTimesToFrames": "ปรับเวลาทั้งหมดให้ตรงเฟรม", - "removeTextForHearingImpaired": "ลบข้อความสำหรับผู้บกพร่องทางการได้ยิน", "joinSubtitles": "รวมไฟล์คำบรรยาย", "splitSubtitle": "แยกคำบรรยาย", "spellCheck": "ตรวจการสะกด", @@ -3555,7 +3881,6 @@ "adjustAllTimes": "ปรับเวลาทั้งหมด", "visualSync": "ซิงก์ด้วยภาพ", "translateViaCopyPaste": "แปลผ่านการคัดลอก-วาง", - "shortcuts": "ปุ่มลัด", "wordLists": "รายการคำ", "chooseUiLanguage": "เลือกภาษาส่วนติดต่อผู้ใช้", "chooseRuleProfile": "เลือกโปรไฟล์กฎ", @@ -3566,6 +3891,7 @@ "textBoxItalic": "กล่องข้อความ ตัวเอียง", "textBoxBold": "กล่องข้อความ ตัวหนา", "textBoxUnderline": "กล่องข้อความ ขีดเส้นใต้", + "textBoxBox": "กล่องในกล่องข้อความ (EBU STL)", "resetWaveformZoomAndSpeed": "รีเซ็ตการซูมรูปคลื่นเสียงและความเร็วการเล่น", "togglePlaybackSpeed": "สลับความเร็วการเล่น", "playbackSpeedSlower": "ลดความเร็วการเล่น", @@ -3577,22 +3903,28 @@ "seekSilenceForward": "ค้นหาช่วงเงียบไปข้างหน้า", "waveformGuessStart": "เดาเวลาเริ่มต้นจากคลื่นเสียง", "waveformGuessEnd": "เดาเวลาสิ้นสุดจากคลื่นเสียง", + "waveformGuessStartAndEnd": "เดาเวลาเริ่มต้นและสิ้นสุดจากรูปคลื่นเสียง", "setVideoPositionCurrentSubtitleStart": "ตั้งตำแหน่งวิดีโอไปที่จุดเริ่มต้นของบรรทัดปัจจุบัน", "goToSubtitlePositionAndPause": "ไปที่ตำแหน่งคำบรรยายและหยุดชั่วคราว", "setVideoPositionCurrentSubtitleEnd": "ตั้งตำแหน่งวิดีโอไปที่จุดสิ้นสุดของบรรทัดปัจจุบัน", "toggleAudioTracks": "สลับแทร็กเสียง", - "listErrors": "แสดงรายการข้อผิดพลาด", "goToNextError": "ไปยังข้อผิดพลาดถัดไป", "goToPreviousError": "ไปยังข้อผิดพลาดก่อนหน้า", "addNameToNameList": "เพิ่มชื่อลงในรายการชื่อ", "findDoubleWords": "ค้นหาคำซ้ำ", "findDoubleLines": "ค้นหาบรรทัดซ้ำ", "colorX": "สี {0}", - "removeColor": "ลบสี", "surroundWith": "ครอบด้วย...", "surroundWithXY": "ครอบด้วย {0}/{1}", - "repeatLine": "เล่นบรรทัดซ้ำ", "surroundWithNumberX": "ครอบด้วย #{0}", + "surroundWithBehavior": "พฤติกรรม", + "surroundWithBehaviorToggle": "สลับ (เพิ่ม หรือลบหากมีอยู่)", + "surroundWithBehaviorAdd": "เพิ่ม (ทุกครั้ง)", + "surroundWithBehaviorRemove": "ลบ (ทั้งหมด)", + "surroundWithBehaviorRemoveOnce": "ลบ (ครั้งละหนึ่ง)", + "surroundWithWorksOn": "ทำงานกับ", + "surroundWithScopeSelectionOrText": "ส่วนที่เลือก หรือทั้งข้อความ", + "surroundWithScopeEachLine": "แต่ละบรรทัด", "searchVia": "ค้นหาผ่าน", "searchViaX": "ค้นหาผ่าน {0}", "searchViaNumberX": "ค้นหาผ่าน #{0}", @@ -3620,6 +3952,11 @@ "copySubtitlePathToClipboard": "คัดลอกเส้นทางไฟล์คำบรรยายไปยังคลิปบอร์ด", "copySubtitleOriginalPathToClipboard": "คัดลอกเส้นทางไฟล์คำบรรยายต้นฉบับไปยังคลิปบอร์ด", "focusTextBox": "โฟกัสกล่องข้อความ", + "focusSubtitleListView": "โฟกัสมุมมองรายการคำบรรยาย", + "focusWaveform": "โฟกัสรูปคลื่นเสียง", + "focusOriginalTextBox": "โฟกัสกล่องข้อความต้นฉบับ", + "textBoxGoToStart": "กล่องข้อความ ไปที่จุดเริ่มต้น", + "textBoxGoToEnd": "กล่องข้อความ ไปที่จุดสิ้นสุด", "sortByNumber": "เรียงตามหมายเลข", "sortByStartTime": "เรียงตามเวลา \"แสดง\"", "sortByEndTime": "เรียงตามเวลา \"ซ่อน\"", @@ -3644,7 +3981,6 @@ "assaStyles": "สไตล์ ASSA", "assaProperties": "คุณสมบัติ ASSA", "assaAttachments": "ไฟล์แนบ ASSA", - "assaVideoColorPicker": "ตัวเลือกสีจากวิดีโอ ASSA", "recalculateDurationSelectedLines": "คำนวณระยะเวลาใหม่ (บรรทัดที่เลือก)", "setDurationMaxCpsSelectedLines": "ตั้งระยะเวลาเป็น CPS สูงสุด (บรรทัดที่เลือก)", "toggleWaveformAndSpectrogramHeight": "สลับการแบ่งความสูงรูปคลื่นเสียง/สเปกโตรแกรม", @@ -3658,9 +3994,9 @@ "selectionToSentenceCase": "กล่องข้อความ: ส่วนที่เลือกเป็นตัวพิมพ์แบบประโยค", "selectionToggleCasing": "กล่องข้อความ: สลับตัวพิมพ์ (UPPER -> lower -> Title Case)", "googleIt": "ค้นหาด้วย Google (ข้อความที่เลือก)", - "setActorXY": "ตั้งนักแสดง {0}: {1}", + "setActorX": "กำหนดนักแสดง: {0}", "setNewActor": "ตั้งนักแสดงใหม่...", - "setActorSlotXTitle": "ช่องนักแสดง {0}", + "setStyleX": "กำหนดสไตล์: {0}", "toggleDialogDashes": "สลับขีดบทสนทนา", "mergeWithLineBeforeAsDialog": "รวมกับบรรทัดก่อนหน้าเป็นบทสนทนา", "mergeWithLineAfterAsDialog": "รวมกับบรรทัดถัดไปเป็นบทสนทนา", @@ -3671,13 +4007,27 @@ "moveStartOneFrameBackKeepGapPrev": "เลื่อนจุดเริ่มต้นถอยหลังหนึ่งเฟรม (คงช่องว่างกับบรรทัดก่อนหน้าหากอยู่ใกล้)", "moveStartOneFrameForwardKeepGapPrev": "เลื่อนจุดเริ่มต้นไปข้างหน้าหนึ่งเฟรม (คงช่องว่างกับบรรทัดก่อนหน้าหากอยู่ใกล้)", "moveEndOneFrameBackKeepGapNext": "เลื่อนจุดสิ้นสุดถอยหลังหนึ่งเฟรม (คงช่องว่างกับบรรทัดถัดไปหากอยู่ใกล้)", - "moveEndOneFrameForwardKeepGapNext": "เลื่อนจุดสิ้นสุดไปข้างหน้าหนึ่งเฟรม (คงช่องว่างกับบรรทัดถัดไปหากอยู่ใกล้)" + "moveEndOneFrameForwardKeepGapNext": "เลื่อนจุดสิ้นสุดไปข้างหน้าหนึ่งเฟรม (คงช่องว่างกับบรรทัดถัดไปหากอยู่ใกล้)", + "moveStartXMsBack": "ย้ายเวลาเริ่มต้นถอยหลัง X ms (กำหนด X ในการตั้งค่า ใช้ในซิงค์ด้วยภาพด้วย)", + "moveStartXMsForward": "ย้ายเวลาเริ่มต้นเดินหน้า X ms (กำหนด X ในการตั้งค่า ใช้ในซิงค์ด้วยภาพด้วย)", + "moveEndXMsBack": "ย้ายเวลาสิ้นสุดถอยหลัง X ms (กำหนด X ในการตั้งค่า ใช้ในซิงค์ด้วยภาพด้วย)", + "moveEndXMsForward": "ย้ายเวลาสิ้นสุดเดินหน้า X ms (กำหนด X ในการตั้งค่า ใช้ในซิงค์ด้วยภาพด้วย)", + "moveSelectedLinesXMsBack": "ย้ายบรรทัดที่เลือกถอยหลัง X ms (กำหนด X ในการตั้งค่า)", + "moveSelectedLinesXMsForward": "ย้ายบรรทัดที่เลือกเดินหน้า X ms (กำหนด X ในการตั้งค่า)", + "moveSelectedLinesAndForwardXMsBack": "ย้ายบรรทัดที่เลือกและบรรทัดถัดไปทั้งหมดถอยหลัง X ms (กำหนด X ในการตั้งค่า)", + "moveSelectedLinesAndForwardXMsForward": "ย้ายบรรทัดที่เลือกและบรรทัดถัดไปทั้งหมดเดินหน้า X ms (กำหนด X ในการตั้งค่า)", + "moveAllLinesXMsBack": "ย้ายทุกบรรทัดถอยหลัง X ms (กำหนด X ในการตั้งค่า)", + "moveAllLinesXMsForward": "ย้ายทุกบรรทัดเดินหน้า X ms (กำหนด X ในการตั้งค่า)", + "moveSelectedLinesCustomMsBack": "ย้ายบรรทัดที่เลือก ถอยหลังตามมิลลิวินาทีที่กำหนดเอง ({0:#,###,##0}), {1}", + "moveSelectedLinesCustomMsForward": "ย้ายบรรทัดที่เลือก เดินหน้าตามมิลลิวินาทีที่กำหนดเอง ({0:#,###,##0}), {1}", + "moveSelectedLinesAndForwardCustomMsBack": "ย้ายบรรทัดที่เลือกและบรรทัดถัดไปทั้งหมด ถอยหลังตามมิลลิวินาทีที่กำหนดเอง ({0:#,###,##0}), {1}", + "moveSelectedLinesAndForwardCustomMsForward": "ย้ายบรรทัดที่เลือกและบรรทัดถัดไปทั้งหมด เดินหน้าตามมิลลิวินาทีที่กำหนดเอง ({0:#,###,##0}), {1}", + "moveAllLinesCustomMsBack": "ย้ายทุกบรรทัด ถอยหลังตามมิลลิวินาทีที่กำหนดเอง ({0:#,###,##0}), {1}", + "moveAllLinesCustomMsForward": "ย้ายทุกบรรทัด เดินหน้าตามมิลลิวินาทีที่กำหนดเอง ({0:#,###,##0}), {1}" }, "wordLists": { "title": "รายการคำ", "addName": "เพิ่มชื่อ", - "addWord": "เพิ่มคำ", - "addPair": "เพิ่มคู่คำ", "nameAndIgnoreList": "รายการชื่อ/คำที่ละเว้น", "userWords": "รายการคำของผู้ใช้", "ocrFixList": "รายการแก้ไข OCR", @@ -3694,7 +4044,6 @@ "noPluginsInstalled": "ไม่มีปลั๊กอินติดตั้งอยู่", "getPluginsOnline": "ดาวน์โหลดปลั๊กอินออนไลน์...", "getPluginsTitle": "ดาวน์โหลดปลั๊กอิน", - "enabled": "เปิดใช้งาน", "disabled": "ปิดใช้งาน", "notSupportedOnThisOs": "ไม่รองรับบนระบบปฏิบัติการนี้", "removePluginXConfirm": "ลบปลั๊กอิน '{0}' หรือไม่? การดำเนินการนี้จะลบโฟลเดอร์ปลั๊กอิน:{1}", @@ -3711,17 +4060,19 @@ "downloadingXPercent": "กำลังดาวน์โหลด {0}%...", "pluginXInstalled": "ติดตั้ง '{0}' แล้ว", "couldNotInstallPluginX": "ไม่สามารถติดตั้ง '{0}': {1}", - "notInstalled": "ยังไม่ได้ติดตั้ง", "install": "ติดตั้ง", "reinstall": "ติดตั้งใหม่", - "update": "อัปเดต", "installedX": "ติดตั้งแล้ว ({0})", "updateAvailableXToY": "มีอัปเดต ({0} → {1})", "applyPluginToWhichLinesX": "ใช้ '{0}' กับ:", "applyToSelectedLinesX": "บรรทัดที่เลือก ({0})", "applyToAllLinesX": "ทุกบรรทัด ({0})", "updateAllXAvailable": "อัปเดตทั้งหมด ({0})", - "updatingXOfY": "กำลังอัปเดต {0}/{1}..." + "updatingXOfY": "กำลังอัปเดต {0}/{1}...", + "doNotAskAgain": "ไม่ต้องถามอีก (เปลี่ยนได้ในจัดการปลั๊กอิน)", + "applyPluginsTo": "ใช้ปลั๊กอินกับ", + "askEachTime": "ถามทุกครั้ง", + "allLines": "ทุกบรรทัด" }, "help": { "aboutSubtitleEdit": "เกี่ยวกับ Subtitle Edit", @@ -3765,6 +4116,8 @@ "nOcrDatabase": "ฐานข้อมูล nOCR", "drawMode": "โหมดวาด:", "addNewCharcter": "เพิ่มอักขระใหม่", + "addNOcrCharacterTitle": "เพิ่มอักขระ nOCR สำหรับบรรทัด {0} อักขระที่ {1} จาก {2} โดยใช้ฐานข้อมูล \"{3}\"", + "addBinaryOcrCharacterTitle": "เพิ่มอักขระ Binary OCR สำหรับบรรทัด {0} อักขระที่ {1} จาก {2} โดยใช้ฐานข้อมูล \"{3}\"", "lineIndexX": "บรรทัด {0}", "inspectNOcrAdditions": "ตรวจสอบรายการ nOCR ที่เพิ่มใหม่", "ocrSelectedLines": "OCR บรรทัดที่เลือก", @@ -3813,6 +4166,8 @@ "binaryImageCompareInspectImageMatches": "\"Binary image compare\" - ตรวจสอบภาพที่ตรงกัน", "saveBlankTextTitle": "บันทึกข้อความว่างหรือไม่?", "saveBlankTextPrompt": "บันทึกข้อความว่างสำหรับภาพนี้หรือไม่?", + "drawnLinesDoNotMatchTitle": "เส้นไม่ตรงกับภาพ", + "drawnLinesDoNotMatchPrompt": "เส้นที่วาดบางเส้นไม่ตรงกับภาพ (เส้นสีเขียวต้องอยู่บนอักขระ เส้นสีแดงต้องอยู่นอกอักขระ) ดังนั้นอักขระนี้จะไม่ถูกจดจำ\n\nบันทึกต่อหรือไม่?", "yesAndNeverAskAgain": "ใช่ และไม่ต้องถามอีก", "importTextFromSubtitleDotDotDot": "นำเข้าข้อความจากคำบรรยาย...", "importTextFromSubtitleOverwritePrompt": "บางบรรทัดมีข้อความ OCR อยู่แล้ว เขียนทับข้อความเดิมหรือไม่?", @@ -3838,7 +4193,6 @@ "vobSubColorsTitle": "สี VobSub/DVD", "vobSubColorsHeader": "ปรับแต่งสี VobSub ทั้งสี่สี", "vobSubColorsDescription": "คำบรรยาย VobSub/DVD ใช้สีดัชนีสี่สี: พื้นหลัง แพตเทิร์น เน้น 1 และเน้น 2 เลือกแต่ละสีด้านล่างเพื่อแทนที่จานสีที่ฝังมา - มีประโยชน์เมื่อสีดั้งเดิมให้ความคมชัดไม่ดีสำหรับ OCR", - "vobSubColorBackground": "พื้นหลัง", "vobSubColorPattern": "แพตเทิร์น", "vobSubColorEmphasis1": "เน้น 1", "vobSubColorEmphasis2": "เน้น 2", @@ -3872,9 +4226,26 @@ "crispEmbedSettingsTitle": "การตั้งค่า CrispEmbed", "crispEmbedDescription": "เอนจิน OCR แบบโลคัลที่มีแบ็กเอนด์โมเดลหลายแบบ - ดาวน์โหลดเอนจินและโมเดลได้ที่นี่", "trainNOcrDatabase": "ฝึกฐานข้อมูล nOCR...", + "optimizeNOcrDatabase": "ปรับฐานข้อมูล nOCR ให้เหมาะสม...", + "optimizeNOcrDatabaseQuestionXY": "ลบเส้นที่ไม่ช่วยแยกอักขระออกจากอักขระที่คล้ายกันในฐานข้อมูล nOCR \"{0}\" หรือไม่?\n\nจะทำให้ฐานข้อมูลเล็กลงและ OCR เร็วขึ้น ปกติจะมีข้อผิดพลาดน้อยลงด้วย ใช้เวลาไม่เกินหนึ่งนาที\n\nข้อมูลสำรองจะถูกบันทึกเป็น \"{1}\"", + "optimizingNOcrDatabaseXY": "กำลังปรับฐานข้อมูล nOCR ให้เหมาะสม... {0}/{1}", + "nOcrDatabaseOptimizedXYZ": "ปรับฐานข้อมูล nOCR \"{0}\" ให้เหมาะสมแล้ว: {1:#,##0} -> {2:#,##0} เส้น", "startTraining": "เริ่มการฝึก", "abortTraining": "ยกเลิกการฝึก", "trainingOptions": "ตัวเลือกการฝึก", + "trainNOcrSubtitle": "เรนเดอร์อักขระด้วยฟอนต์ที่ติดตั้งไว้และสอนรูปร่างให้ nOCR เลือกฟอนต์ที่คำบรรยายของคุณใช้", + "xFontsSelected": "เลือกแล้ว {0:#,##0}", + "subtitleFonts": "ฟอนต์คำบรรยาย", + "emptyDatabase": "ฐานข้อมูลว่าง", + "startFromDatabase": "เริ่มจาก", + "startFromDatabaseHint": "เพิ่มอักขระที่ฝึกแล้วลงในสำเนาของฐานข้อมูลที่มีอยู่ (แนะนำ) หรือเริ่มจากฐานข้อมูลว่าง", + "fontSizes": "ขนาดฟอนต์", + "fontSizesHint": "ขนาดฟอนต์เป็นพิกเซลที่จะเรนเดอร์ คั่นด้วยจุลภาค เช่น \"30, 40\"", + "numberOfLineSegmentsHint": "จำนวนเส้นที่ตรวจสอบต่ออักขระ - ยิ่งมากยิ่งเข้มงวดแต่ช้าลง", + "enterDatabaseName": "ป้อนชื่อสำหรับฐานข้อมูล", + "trainingFontXSizeYCharacterZ": "กำลังฝึก \"{0}\" ที่ {1} px: {2}", + "xLearned": "เรียนรู้แล้ว {0:#,##0}", + "xSkipped": "ข้ามแล้ว {0:#,##0}", "charactersToTrain": "อักขระที่จะฝึก", "letterCombinationsToTrain": "ชุดตัวอักษรที่อาจถูกแยกเป็นภาพเดียว", "importCharactersFromSubtitleFile": "นำเข้าอักขระจากไฟล์คำบรรยาย...", @@ -3901,6 +4272,8 @@ "drawZoomOut": "ซูมออก (Ctrl+-)", "drawResetView": "รีเซ็ตมุมมอง (Ctrl+0)", "drawToggleGrid": "สลับเส้นตาราง (Ctrl+G)", + "drawTogglePreview": "สลับตัวอย่าง (F9)", + "drawPreviewFailed": "แสดงตัวอย่างไม่สำเร็จ - ต้องใช้ ffmpeg ที่มี libass (subtitles filter)", "drawCopyToClipboard": "คัดลอกไปยังคลิปบอร์ด (Ctrl+C)", "drawShapes": "รูปทรง", "drawSelectedPoint": "จุดที่เลือก", @@ -3908,16 +4281,86 @@ "drawSelectedLayer": "เลเยอร์ที่เลือก", "drawToolX": "เครื่องมือ: {0}", "drawHelpText": "คลิกเพื่อเพิ่มจุด • Enter/F8 เพื่อปิดรูปทรง • Shift+ลาก เพื่อเลื่อนมุมมอง • Ctrl+เลื่อนล้อ เพื่อซูม", + "drawMoveToLayer": "ย้ายไปยังเลเยอร์", + "drawFlipHorizontal": "พลิกแนวนอน", + "drawFlipVertical": "พลิกแนวตั้ง", + "drawHideShape": "ซ่อนรูปทรง", + "drawShowShape": "แสดงรูปทรง", + "drawHideLayer": "ซ่อนเลเยอร์", + "drawShowLayer": "แสดงเลเยอร์", + "drawDeleteLayer": "ลบเลเยอร์", + "drawDeletePoint": "ลบจุด", + "drawCancelDrawing": "ยกเลิกการวาด", + "drawLayerX": "เลเยอร์ {0}", + "drawImportSvg": "นำเข้าภาพ SVG...", + "drawRotateClockwise": "หมุน 90° ตามเข็มนาฬิกา", + "drawConvertToCurve": "แปลงส่วนเป็นเส้นโค้ง", + "drawBackground": "พื้นหลัง", + "drawPosition": "ตำแหน่ง", + "drawZoom": "ซูม", + "drawShapeTool": "เครื่องมือรูปทรง", + "drawColorPickerTool": "เลือกสีจากรูปทรงหรือพื้นหลัง", + "drawShapeLibrary": "คลังรูปทรง", + "drawShapeLibraryHint": "ลากบนพื้นที่วาดเพื่อวางรูปทรง หรือคลิกเพื่อใช้ขนาดเริ่มต้น Shift จะคงสัดส่วนไว้", + "drawAddToLibrary": "เพิ่มลงในคลังรูปทรง...", + "drawRemoveFromLibrary": "ลบออกจากคลังรูปทรง", + "drawShapeName": "ชื่อรูปทรง", + "drawCategorySpeechBubbles": "กรอบคำพูด", + "drawCategoryArrows": "ลูกศร", + "drawCategoryBasicShapes": "รูปทรงพื้นฐาน", + "drawCategorySymbols": "สัญลักษณ์", + "drawCategoryMyShapes": "รูปทรงของฉัน", + "drawShapeSpeechBubble": "กรอบคำพูด", + "drawShapeSpeechBubbleRight": "กรอบคำพูด หางขวา", + "drawShapeSpeechBubbleUp": "กรอบคำพูด หางขึ้น", + "drawShapeOvalBubble": "กรอบคำพูดวงรี", + "drawShapeBoxBubble": "กรอบคำพูดสี่เหลี่ยม", + "drawShapeThoughtBubble": "กรอบความคิด", + "drawShapeShoutBubble": "กรอบตะโกน", + "drawShapeCaptionBox": "กล่องคำบรรยาย", + "drawShapeArrow": "ลูกศร", + "drawShapeDoubleArrow": "ลูกศรสองหัว", + "drawShapeChevron": "บั้ง", + "drawShapeCurvedArrow": "ลูกศรโค้ง", + "drawShapeRoundedRectangle": "สี่เหลี่ยมมุมมน", + "drawShapeTriangle": "สามเหลี่ยม", + "drawShapeDiamond": "ข้าวหลามตัด", + "drawShapePentagon": "ห้าเหลี่ยม", + "drawShapeHexagon": "หกเหลี่ยม", + "drawShapeOctagon": "แปดเหลี่ยม", + "drawShapeStar": "ดาว", + "drawShapeRing": "วงแหวน", + "drawShapePlus": "เครื่องหมายบวก", + "drawShapeBanner": "แบนเนอร์", + "drawShapeHeart": "หัวใจ", + "drawShapeCheck": "เครื่องหมายถูก", + "drawShapeMusicNote": "ตัวโน้ตดนตรี", + "drawShapeMusicNotes": "ตัวโน้ตดนตรีหลายตัว", + "drawShapeLightning": "สายฟ้า", + "drawShapeCloud": "เมฆ", + "drawShapeMoon": "พระจันทร์", + "drawShapeBadge": "เหรียญตรา", + "drawSize": "ขนาด", + "drawLayer": "เลเยอร์", + "drawBackgroundVideoFrame": "เฟรมวิดีโอ (ตำแหน่งปัจจุบัน)", + "drawBackgroundVideoFrameAt": "เฟรมวิดีโอที่...", + "drawBackgroundImage": "ไฟล์ภาพ...", + "drawBackgroundNone": "ไม่มีพื้นหลัง", + "drawBackgroundStretch": "ยืดภาพให้เต็มพื้นที่วาด", + "drawBackgroundOpacity": "ความทึบของพื้นหลัง", + "drawBackgroundVideoFrameFailed": "ไม่สามารถรับเฟรมวิดีโอที่ {0}", + "drawImages": "ภาพ", + "drawConvertToLine": "แปลงส่วนเป็นเส้นตรง", + "drawConvertShapeToCurves": "แปลงเส้นตรงทั้งหมดเป็นเส้นโค้ง", + "drawConvertShapeToLines": "แปลงเส้นโค้งทั้งหมดเป็นเส้นตรง", + "drawRotateCounterClockwise": "หมุน 90° ทวนเข็มนาฬิกา", + "drawSvgImages": "ภาพ SVG", + "drawSvgNoShapes": "ไม่พบรูปทรงที่มีการเติมสีหรือเส้นในไฟล์ SVG", "progressBarTitle": "แถบความคืบหน้า ASSA", "progressBarSettings": "แถบความคืบหน้า", - "progressBarPosition": "ตำแหน่ง", - "progressBarBottom": "ด้านล่าง", - "progressBarTop": "ด้านบน", "mouseOverColor": "สีเมื่อชี้เมาส์", "clickedColor": "สีเมื่อคลิก", "progressBarForeColor": "สีพื้นหน้า", - "progressBarBackColor": "สีพื้นหลัง", - "progressBarStyle": "สไตล์", "progressBarSquareCorners": "มุมเหลี่ยม", "progressBarRoundedCorners": "มุมโค้งมน", "progressBarChapters": "ตอน", @@ -3926,8 +4369,6 @@ "progressBarXAdjustment": "การปรับแกน X", "progressBarYAdjustment": "การปรับแกน Y", "progressBarTextAlignment": "การจัดตำแหน่งข้อความ", - "progressBarTakePosFromVideo": "ใช้ตำแหน่งจากวิดีโอ", - "progressBarPreview": "ดูตัวอย่าง", "fontCollectorTitle": "ตัวรวบรวมฟอนต์", "fontCollectorUsedIn": "ใช้ใน", "fontCollectorFontFiles": "ไฟล์ฟอนต์", @@ -3974,9 +4415,7 @@ "resolutionResamplerVideoDiffers": "ความละเอียดของคำบรรยาย ({0}x{1}) ต่างจากความละเอียดของวิดีโอ ({2}x{3})\nต้องการเปลี่ยนคำบรรยายให้ตรงกับวิดีโอหรือไม่?", "resolutionResamplerAskOnVideoOpen": "ถามเมื่อเปิดวิดีโอที่มีความละเอียดต่างกัน", "backgroundBoxGenerator": "สร้างกล่องพื้นหลัง", - "backgroundBoxPadding": "ระยะห่างภายใน", "backgroundBoxFillWidth": "เติมเต็มความกว้าง", - "backgroundBoxBoxColor": "สีกล่อง", "backgroundBoxRadius": "รัศมี", "backgroundBoxCircle": "วงกลม", "backgroundBoxSpikes": "หนามแหลม", @@ -3988,12 +4427,9 @@ "backgroundBoxTornPaperDouble": "กระดาษฉีก (บนและล่าง)", "backgroundBoxStarburst": "ประกายดาว", "backgroundBoxScroll": "ม้วนกระดาษ / หนังสัตว์", - "stylesTitle": "สไตล์ Advanced Sub Station Alpha", "stylesInFile": "สไตล์ในไฟล์", "stylesSaved": "สไตล์ที่บันทึกไว้", - "category": "หมวดหมู่", "allCategories": "[ทั้งหมด]", - "defaultCategory": "ค่าเริ่มต้น", "newCategory": "หมวดหมู่ใหม่...", "renameCategory": "เปลี่ยนชื่อหมวดหมู่...", "deleteCategory": "ลบหมวดหมู่", @@ -4016,13 +4452,26 @@ "graphics": "กราฟิก", "copyToStorageStyles": "คัดลอกไปยังสไตล์ที่จัดเก็บ", "copyToFileStyles": "คัดลอกไปยังสไตล์ในไฟล์", + "styleXAlreadyExistsInStorage": "มีสไตล์ชื่อ \"{0}\" อยู่แล้วในที่เก็บ\n\nเขียนทับ หรือเก็บไว้ทั้งสอง (สำเนาจะได้ชื่อใหม่)?", + "styleXAlreadyExistsInFile": "มีสไตล์ชื่อ \"{0}\" อยู่แล้วในไฟล์\n\nเขียนทับ หรือเก็บไว้ทั้งสอง (สำเนาจะได้ชื่อใหม่)?", + "styleNameCannotBeEmpty": "ชื่อสไตล์ต้องไม่ว่างเปล่า", + "styleNameXIsUsedMoreThanOnce": "ชื่อสไตล์ \"{0}\" ถูกใช้โดยมากกว่าหนึ่งสไตล์ในไฟล์", + "overwrite": "เขียนทับ", + "keepBoth": "เก็บไว้ทั้งสอง", + "doThisForAllConflictingStyles": "ทำเช่นนี้กับสไตล์ที่ขัดแย้งกันทั้งหมด", "setStyleAsDefault": "ตั้งสไตล์เป็นค่าเริ่มต้น", "takeUsagesFromDotDotDot": "นำการใช้งานมาจาก...", "replaceStyleWithDotDotDot": "แทนที่สไตล์ด้วย...", "noAttachmentsFound": "ไม่พบไฟล์แนบในไฟล์ ASSA ที่เลือก", "deleteStyleQuestion": "ลบสไตล์หรือไม่?", "deleteStylesQuestion": "ลบสไตล์หลายรายการหรือไม่?", + "deleteXStylesQuestion": "คุณต้องการลบ {0} สไตล์หรือไม่?", + "deleteXStylesFromStorageQuestion": "คุณต้องการลบ {0} สไตล์ออกจากที่เก็บหรือไม่?", + "deleteStyleXFromStorageQuestion": "คุณต้องการลบสไตล์ \"{0}\" ออกจากที่เก็บหรือไม่?", + "deleteStyleXFromFileQuestion": "คุณต้องการลบสไตล์ \"{0}\" ออกจากไฟล์ปัจจุบันหรือไม่?", "openStyleImportFile": "เปิดไฟล์คำบรรยายเพื่อนำเข้าสไตล์", + "styleImportFiles": "ไฟล์สไตล์ (.ass, .sty, หมวดหมู่ Subtitle Edit 4 .template)", + "ssaStyleImportFiles": "ไฟล์สไตล์ (.ssa, .ass, .sty)", "primary": "หลัก", "secondary": "รอง", "applyOverrideTags": "ใช้แท็กแทนที่", @@ -4037,7 +4486,6 @@ "spaceIncrease": "เพิ่มระยะห่าง", "popIn": "เด้งเข้า", "spinIn": "หมุนเข้า", - "playCurrent": "เล่นบรรทัดปัจจุบัน", "setPosition": "ตั้งตำแหน่ง", "rotation": "การหมุน", "scaleX": "สเกล X", @@ -4081,10 +4529,8 @@ "advancedEffectOldMovieDescription": "จำลองเครื่องฉายฟิล์ม 35 มม. ยุคเก่า พร้อมสัญญาณรบกวน ขอบมืด และรอยขีดข่วน", "advancedEffectNeonBurst": "นีออนระเบิด", "advancedEffectNeonBurstDescription": "คำบรรยายสไตล์ 'Shorts' พลังสูง พร้อมแสงนีออนเรืองรองและแอนิเมชัน 'เด้ง' ขยายเข้า", - "advancedEffectSettings": "การตั้งค่า", "advancedEffectSnowFlakeCount": "จำนวนเกล็ดหิมะ", "advancedEffectStarfieldStarCount": "จำนวนดาว", - "advancedEffectStarfieldSpeed": "ความเร็ว", "advancedEffectGlitch": "กลิตช์", "advancedEffectGlitchDescription": "กลิตช์ดิจิทัลพร้อมความคลาดสี แสงแฟลชสีขาว และภาพซ้อนบิดเบือน", "advancedEffectBounceIn": "เด้งเข้า", @@ -4093,8 +4539,6 @@ "advancedEffectFirefliesDescription": "จุดเรืองแสงสีเหลืองอำพันอบอุ่นลอยไปมาอย่างเป็นธรรมชาติทั่วหน้าจอพร้อมแสงกะพริบ", "advancedEffectMatrix": "Matrix", "advancedEffectMatrixDescription": "คอลัมน์อักขระสีเขียวตกลงมา โดยข้อความคำบรรยายค่อย ๆ ก่อตัวทีละตัวอักษรจากสายฝน", - "advancedEffectAudioBars": "แถบเสียง (รูปคลื่นเสียง)", - "advancedEffectAudioBarsDescription": "แถบอีควอไลเซอร์เคลื่อนไหวตามรูปคลื่นเสียง — แถบเลื่อนไปทางซ้ายเป็นรูปคลื่นและเต้นตามจังหวะเสียง", "advancedEffectAudioPulse": "ข้อความเต้นตามเสียง", "advancedEffectAudioPulseDescription": "ข้อความคำบรรยายเรืองแสงและเต้นตามระดับเสียง — ช่วงเงียบแสดงข้อความปกติ ช่วงเสียงดังเพิ่มรัศมีสีและขยายขึ้นเล็กน้อย", "advancedEffectConfetti": "คอนเฟตตีระเบิด", @@ -4108,7 +4552,6 @@ "advancedEffectFancyKaraokeGlow": "แสงเรืองของคำที่กำลังร้อง", "advancedEffectFancyKaraokeActiveColor": "สีคำที่กำลังร้อง", "advancedEffectFancyKaraokeInactiveColor": "สีคำอื่น", - "advancedEffectFancyKaraokeInactiveOpacity": "ความทึบของคำอื่น (0-255)", "advancedEffectWordSpacingPixels": "ระยะห่าง (พิกเซล)", "advancedEffectSlowZoomOut": "ซูมออกช้า ๆ", "advancedEffectSlowZoomOutDescription": "ข้อความเริ่มต้นใหญ่กว่าเล็กน้อยแล้วค่อย ๆ หดลงเป็นขนาดปกติตลอดระยะเวลาของคำบรรยาย", @@ -4124,7 +4567,6 @@ "advancedEffectSlideInLeftDescription": "ข้อความเลื่อนเข้าจากนอกจอด้านซ้าย ค้างไว้ แล้วออกกลับไปทางซ้าย", "advancedEffectSlideInRight": "เลื่อนเข้าจากขวา", "advancedEffectSlideInRightDescription": "ข้อความเลื่อนเข้าจากนอกจอด้านขวา ค้างไว้ แล้วออกกลับไปทางขวา", - "advancedEffectFadeInOut": "เฟดเข้า/ออก", "advancedEffectFadeInOutDescription": "ข้อความเฟดเข้าตอนเริ่มต้นและเฟดออกตอนท้ายของแต่ละคำบรรยาย", "advancedEffectWordFlip3D": "พลิกคำสามมิติ", "advancedEffectWordFlip3DDescription": "แต่ละคำพับเข้ามาด้วยการพลิกสามมิติและเด้งแบบยืดหยุ่นขณะถูกพูด — สไตล์คำบรรยายแบบ 'Shorts' สมัยใหม่", diff --git a/src/ui/Assets/Languages/Turkish.json b/src/ui/Assets/Languages/Turkish.json index be02e9a1bc5..49403eab7f9 100644 --- a/src/ui/Assets/Languages/Turkish.json +++ b/src/ui/Assets/Languages/Turkish.json @@ -1,19 +1,19 @@ { "title": "Subtitle Edit", - "version": "v5.3.0-rc2", + "version": "v5.3.0", "translatedBy": "Utku Gogus; Son güncelleme: Ahmet Murat Özhan (11.10.2026)", "cultureName": "tr-TR", "general": { "abort": "İptal", "actor": "Aktör", "actors": "Aktörler", - "setActorDotDotDot": "Aktör belirle...", + "setActorDotDotDot": "Aktör belirle...", "actorPickerTitle": "Aktör belirle", "actorPickerFilterHint": "Filtrelemek için yazın veya yeni bir aktör adı yazın", "actorPickerKeysHint": "Aktörü belirlemek için bir sayıya veya Enter tuşuna basın. Alt+Yukarı/Aşağı sırayı değiştirir, Delete aktörü kaldırır.", "actorPickerNewActorX": "Yeni aktör \"{0}\"", "actorPickerLinesSelectedX": "Seçilen satır sayısı: {0}", - "setStyleDotDotDot": "Biçem ayarla...", + "setStyleDotDotDot": "Biçem ayarla...", "stylePickerTitle": "Biçem ayarla", "stylePickerFilterHint": "Filtrelemek için yazın veya yeni bir biçem adı girin", "stylePickerKeysHint": "Seçili tüm satırlara biçemi uygulamak için bir sayıya (1-9, 0) basın.", @@ -43,24 +43,24 @@ "alphaAdjustment": "Alfa ayarı", "alphaThreshold": "Alfa eşiği", "apiKey": "API anahtarı", - "formality": "Resmiyet seviyesi", + "formality": "Resmiyet seviyesi", "apiSecret": "Gizli", "appearance": "Görünüm", "append": "Sona ekle", "apply": "Uygula", "applyTo": "Şuna uygula", "ascending": "Artan", - "assistedMove": "Yardımlı taşıma", - "assistedMoveChooseMove": "Kelimeleri taşımak için bir seçeneğe tıklayın veya numarasına basın", - "assistedMoveDotDotDot": "Yardımlı taşıma...", - "assistedMoveNoSuggestions": "Taşıma önerisi yok - cümle önceki/sonraki alt yazıda devam etmiyor", - "assistedSplitNoSuggestions": "Bu satır için bölme önerisi yok", - "assistedSplit": "Yardımlı bölme", - "assistedSplitChooseSplitPoint": "Satırı bölmek için bir seçeneğe tıklayın veya numarasına basın", - "assistedSplitDotDotDot": "Yardımlı bölme...", + "assistedMove": "Yardımlı taşıma", + "assistedMoveChooseMove": "Kelimeleri taşımak için bir seçeneğe tıklayın veya numarasına basın", + "assistedMoveDotDotDot": "Yardımlı taşıma...", + "assistedMoveNoSuggestions": "Taşıma önerisi yok - cümle önceki/sonraki alt yazıda devam etmiyor", + "assistedSplitNoSuggestions": "Bu satır için bölme önerisi yok", + "assistedSplit": "Yardımlı bölme", + "assistedSplitChooseSplitPoint": "Satırı bölmek için bir seçeneğe tıklayın veya numarasına basın", + "assistedSplitDotDotDot": "Yardımlı bölme...", "descending": "Azalan", "attachDotDotDot": "Ekle...", - "audioFileSaved": "Ses dosyası kaydedildi", + "audioFileSaved": "Ses dosyası kaydedildi", "audioFiles": "Ses dosyaları", "audioVisualizer": "Ses görselleştirici", "auto": "Otomatik", @@ -75,7 +75,7 @@ "backward": "Geri", "batchMode": "Toplu mod", "bdnXml": "BDN/xml", - "bdnXml8Bit": "BDN/XML 8 bit", + "bdnXml8Bit": "BDN/XML 8 bit", "before": "Önce", "beginning": "Başlangıç", "bluRaySup": "Blu-ray (sup)", @@ -99,12 +99,12 @@ "boxPadding": "Kutu iç boşluğu", "boxPerLine": "Satır başına kutu", "bridgeGaps": "Boşlukları köprüle", - "bytes": "bayt", + "bytes": "bayt", "cancel": "İ_ptal", "cancelled": "İptal edildi", "caseInsensitive": "Büyük/küçük harf duyarsız", "caseSensitive": "Büyük/küçük harf duyarlı", - "casing": "Harf durumu", + "casing": "Harf durumu", "category": "Kategori", "center": "Orta", "centerHorizontally": "Yatay ortala", @@ -128,25 +128,25 @@ "codec": "Kodek", "collapse": "Daralt", "color": "Renk", - "colorBlack": "Siyah", + "colorBlack": "Siyah", "colorBlue": "Mavi", "colorCyan": "Camgöbeği", "colorDotDotDot": "Renk...", - "colorGray": "Gri", - "colorGreen": "Yeşil", - "colorHalfBlue": "Yarı mavi", - "colorHalfCyan": "Yarı siyan", - "colorHalfGreen": "Yarı yeşil", - "colorHalfMagenta": "Yarı eflatun", - "colorHalfRed": "Yarı kırmızı", - "colorHalfYellow": "Yarı sarı", + "colorGray": "Gri", + "colorGreen": "Yeşil", + "colorHalfBlue": "Yarı mavi", + "colorHalfCyan": "Yarı siyan", + "colorHalfGreen": "Yarı yeşil", + "colorHalfMagenta": "Yarı eflatun", + "colorHalfRed": "Yarı kırmızı", + "colorHalfYellow": "Yarı sarı", "colorMagenta": "Eflatun", "colorRed": "Kırmızı", "colorWhite": "Beyaz", "colorYellow": "Sarı", "column": "Sütun", - "columns": "Sütunlar", - "columnsDotDotDot": "Sütunlar...", + "columns": "Sütunlar", + "columnsDotDotDot": "Sütunlar...", "consoleLog": "Konsol günlüğü", "contentAlignment": "İçerik hizalaması", "continueFindTitle": "Aramaya devam edilsin mi?", @@ -158,14 +158,14 @@ "convertingXofYDotDoDot": "Dönüştürülüyor {0:#,###,##0}/{1:#,###,##0}...", "copy": "Kopyala", "copyImageToClipboard": "Resmi panoya kopyala", - "copySubtitle": "Alt yazıyı kopyala", + "copySubtitle": "Alt yazıyı kopyala", "copyTextOnly": "Kopyala (yalnızca metin)", "copyTextToClipboard": "Metni panoya kopyala", "couldNotSaveFileXErrorY": "\"{0}\" dosyası kaydedilemedi. Hata: {1}", - "couldNotOpenFileXErrorY": "\"{0}\" dosyası açılamadı. Hata: {1}", + "couldNotOpenFileXErrorY": "\"{0}\" dosyası açılamadı. Hata: {1}", "count": "Sayım", "cps": "Kar/sn", - "cudaRuntime": "CUDA çalışma zamanı", + "cudaRuntime": "CUDA çalışma zamanı", "currentVideoPosition": "Mevcut video konumu", "custom": "Özel", "cut": "Kes", @@ -192,7 +192,7 @@ "downloadX": "{0} İndir", "downloadingX": "{0} indiriliyor", "downloadingXPercent": "%{0} indiriliyor", - "downloadFailed": "İndirme başarısız oldu", + "downloadFailed": "İndirme başarısız oldu", "downloadCanceled": "İndirme iptal edildi", "unpackFailed": "Paket açılamadı: {0}", "unpackingFailed": "Paket çıkarma başarısız oldu", @@ -215,9 +215,9 @@ "engine": "Motor", "enterProfileName": "Profil adı girin", "error": "Hata", - "start": "Başlat", - "vobSub": "VobSub", - "outputFileCannotBeTheInputFile": "Çıktı dosyası, girdi dosyası ile aynı olamaz.", + "start": "Başlat", + "vobSub": "VobSub", + "outputFileCannotBeTheInputFile": "Çıktı dosyası, girdi dosyası ile aynı olamaz.", "errorX": "Hata; {0}", "example": "Örnek", "exampleX": "Örnek: {0}", @@ -230,8 +230,8 @@ "extendSelectedLinesToPreviousShotChange": "Seçili satırları önceki sahne değişimine (veya önceki alt yazıya) kadar uzat", "extendSelectedToNext": "Seçili olanı sonrakine uzat", "extendSelectedToPrevious": "Seçili olanı öncekine uzat", - "extendPreviousEndToSelectedStart": "Önceki satırın bitişini seçilenin başlangıcına uzat", - "extendNextStartToSelectedEnd": "Sonraki satırın başlangıcını seçilenin bitişine uzat", + "extendPreviousEndToSelectedStart": "Önceki satırın bitişini seçilenin başlangıcına uzat", + "extendNextStartToSelectedEnd": "Sonraki satırın başlangıcını seçilenin bitişine uzat", "extractingAudioClips": "Ses klipleri çıkarılıyor...", "exitFullScreen": "Tam ekrandan çık", "fade": "Kararma", @@ -282,7 +282,7 @@ "generateImportShotChanges": "Sahne değişimlerini oluştur/içe aktar", "generating": "Oluşturuluyor...", "generatingImageXofY": "Resim oluşturuluyor {0:#,##0} / {1:#,##0}...", - "generatingAudioFile": "Ses dosyası oluşturuluyor...", + "generatingAudioFile": "Ses dosyası oluşturuluyor...", "getAudioClips": "Ses kliplerini al", "goTo": "Git", "goToLineNumber": "Satır numarasına git", @@ -302,7 +302,7 @@ "imageSaved": "Resim kaydedildi", "imageBasedSubtitles": "Resim tabanlı alt yazılar", "images": "Resimler", - "imagesWithHtmlIndex": "HTML indeksli resimler", + "imagesWithHtmlIndex": "HTML indeksli resimler", "imagesWithTimeCode": "Zaman kodlu resimler", "import": "İçe Aktar", "importDotDotDot": "İçe aktar...", @@ -316,7 +316,7 @@ "insertNewSelection": "Yeni seçim ekle", "insertSubtitleAfterCurrentLine": "Mevcut satırdan sonra alt yazı ekle...", "insertSubtitleFileAtVideoPositionDotDotDot": "Video konumuna alt yazı dosyası ekle...", - "invalidSize": "Geçersiz boyut", + "invalidSize": "Geçersiz boyut", "inspectAdditions": "Eklemeleri incele...", "installFolder": "Kurulum klasörü", "invertSelection": "Seçimi tersine çevir", @@ -341,7 +341,7 @@ "loadDefaults": "Varsayılanları yükle", "lockTimeCodes": "Zaman kodlarını kilitle", "logo": "Logo", - "lookUpX": "\"{0}\" ögesini ara", + "lookUpX": "\"{0}\" ögesini ara", "margin": "Kenar boşluğu", "match": "Eşleşme", "maxCharactersPerSecond": "Maksimum karakter/saniye", @@ -357,7 +357,7 @@ "mergeSelectedLinesDialog": "Seçili satırları diyalog olarak birleştir", "mergeWithLineAfterAndAutoBreak": "Sonraki satırla birleştir ve otomatik böl", "mergeWithLineAfterKeepBreaks": "Sonraki satırla birleştir (satır sonlarını koru)", - "mergeWithLineAfterAndUnbreak": "Sonraki satırla birleştir ve satır sonunu kaldır", + "mergeWithLineAfterAndUnbreak": "Sonraki satırla birleştir ve satır sonunu kaldır", "mergeWithLineBeforeAndAutoBreak": "Önceki satırla birleştir ve otomatik böl", "mergeWithLineBeforeKeepBreaks": "Önceki satırla birleştir (satır sonlarını koru)", "middleCenter": "Orta-Merkez", @@ -372,21 +372,21 @@ "moveAllShotChangeOneFrameForward": "Tüm sahne değişimlerini bir kare ileri taşı", "multiMode": "Çoklu mod", "moveDown": "Aşağı taşı", - "increase": "Artır", + "increase": "Artır", "decrease": "Azalt", "moveUp": "Yukarı taşı", - "moveToTop": "En üste taşı", + "moveToTop": "En üste taşı", "moveToBottom": "En alta taşı", "multipleReplace": "Çoklu değiştir", "name": "Ad", "new": "Yeni", "newDotDotDot": "Yeni...", "newProfile": "Yeni profil", - "next": "Sonraki", + "next": "Sonraki", "newSubtitleStartKeyDownSetEndKeyUp": "Alt yazı ekle: Tuşa basınca başlat, bırakınca bitir", "setSubtitleStartAtVideoPositionSetEndAtKeyUpAndGoToNext": "Başlangıcı video konumuna, bitişi tuş bırakmaya ayarla ve sonrakine geç", "no": "Hayır", - "noColor": "Renk yok", + "noColor": "Renk yok", "noFilesToConvert": "Dönüştürülecek dosya yok", "noLanguageCode": "Dil kodu yok", "noSubtitleLoaded": "Alt yazı yüklenmedi", @@ -397,7 +397,7 @@ "normalCasing": "Normal yazım", "notAvailable": "Yok", "notInstalled": "Yüklü değil", - "nothingSelected": "Hiçbir şey seçilmedi", + "nothingSelected": "Hiçbir şey seçilmedi", "number": "Numara", "numberSymbol": "#", "ocrDotDotDot": "OCR...", @@ -423,8 +423,8 @@ "openVideoFileTitle": "Video dosyası aç", "optimalCharactersPerSecond": "Optimal karakter/saniye", "options": "Seçenekler", - "originalSubtitleClosed": "Orijinal alt yazı kapatıldı", - "originalSubtitleLoadedX": "Orijinal alt yazı yüklendi: {0}", + "originalSubtitleClosed": "Orijinal alt yazı kapatıldı", + "originalSubtitleLoadedX": "Orijinal alt yazı yüklendi: {0}", "originalText": "Orijinal metin", "outline": "Dış hat", "outlineColor": "Dış hat rengi", @@ -443,14 +443,14 @@ "percent": "Yüzde", "pickLayer": "Katman ayarla", "pickOllamaModel": "Ollama modelini seç", - "pickModel": "Model seçin", - "llamaCppUseRemoteServer": "Harici sunucu kullan (URL)", - "llamaCppEngineSettings": "llama.cpp motoru ayarları", + "pickModel": "Model seçin", + "llamaCppUseRemoteServer": "Harici sunucu kullan (URL)", + "llamaCppEngineSettings": "llama.cpp motoru ayarları", "llamaCppEngineSettingsSubtitle": "Yerel llama.cpp sunucu sürümü", - "startServer": "Sunucuyu başlat", + "startServer": "Sunucuyu başlat", "stopServer": "Sunucuyu durdur", "pickOutputFolder": "Çıkış klasörünü seç", - "pleaseSelectOutputFolder": "Lütfen bir çıktı klasörü seçin", + "pleaseSelectOutputFolder": "Lütfen bir çıktı klasörü seçin", "pickResolutionFromCurrentVideo": "Mevcut videodan çözünürlüğü al", "pickResolutionFromVideoDotDotDot": "Videodan çözünürlüğü al...", "pickSubtitleFile": "Alt yazı dosyası seç...", @@ -458,13 +458,13 @@ "pickVideoPosition": "Video konumu seç", "play": "Oynat", "playFromStartOfVideo": "Videonun başından oynat", - "playFromJustBeforeText": "Metinden hemen önce oynat", + "playFromJustBeforeText": "Metinden hemen önce oynat", "playNext": "Sonrakini oynat", - "playNextAndStop": "Sonrakini oynat (ve dur)", - "playNextAndLoop": "Sonrakini oynat (ve döngüye al)", - "playPrevious": "Öncekini oynat", - "playPreviousAndStop": "Öncekini oynat (ve dur)", - "playPreviousAndLoop": "Öncekini oynat (ve döngüye al)", + "playNextAndStop": "Sonrakini oynat (ve dur)", + "playNextAndLoop": "Sonrakini oynat (ve döngüye al)", + "playPrevious": "Öncekini oynat", + "playPreviousAndStop": "Öncekini oynat (ve dur)", + "playPreviousAndLoop": "Öncekini oynat (ve döngüye al)", "playSelectedLines": "Seçili satırları oynat", "playSelectedLinesWithLoop": "Seçili satırları döngüde oynat", "playSelectedLinesAndFocusWaveform": "Seçili satırları oynat ve ses dalgasına odaklan", @@ -477,10 +477,10 @@ "poweredBy": "Destekleyen", "prefix": "Ön ek", "preview": "Ön izleme", - "previewAlignmentInListView": "Liste görünümünde hizalamayı ön izle", - "previous": "Önceki", + "previewAlignmentInListView": "Liste görünümünde hizalamayı ön izle", + "previous": "Önceki", "profile": "Profil", - "quality": "Kalite", + "quality": "Kalite", "profileName": "Profil adı", "profiles": "Profiller", "property": "Özellik", @@ -488,7 +488,7 @@ "reDownloadX": "{0} ögesini tekrar indir", "reason": "Sebep", "recentFiles": "Son dosyalar", - "recentlyUsedVideoOffsets": "Son kullanılan video zaman kaydırmaları", + "recentlyUsedVideoOffsets": "Son kullanılan video zaman kaydırmaları", "redo": "Yinele", "redownload": "Yeniden indir...", "release": "Sürüm", @@ -508,19 +508,19 @@ "removeItalic": "İtalik yazıyı kaldır", "removeRightToLeftUnicodeTags": "RTL Unicode etiketlerini kaldır", "removeSelectedFile": "Seçili dosya kaldırılsın mı?", - "removeSelectedFilesX": "Seçili {0} dosya kaldırılsın mı?", + "removeSelectedFilesX": "Seçili {0} dosya kaldırılsın mı?", "removeStyling": "Stili kaldır", "removeTextForHearingImpaired": "İşitme engelliler için metni kaldır", "removeUnderline": "Alt çizgiyi kaldır", "rename": "Yeniden adlandır", "replace": "Değiştir", - "replaceLine": "Satırı değiştir", + "replaceLine": "Satırı değiştir", "replaceWith": "Şununla değiştir", "requiresRestart": "Yeniden başlatma gerektirir", "reset": "Sıfırla", - "resizePanels": "Panelleri yeniden boyutlandır", + "resizePanels": "Panelleri yeniden boyutlandır", "resolution": "Çözünürlük", - "retry": "Yeniden dene", + "retry": "Yeniden dene", "reverseRightToLeftStartEnd": "RTL başlangıç/bitişini tersine çevir", "right": "Sağ", "rightToLeft": "Sağdan sola", @@ -547,13 +547,13 @@ "seconds": "Saniye", "selectAll": "Tümünü seç", "selectFilesToConvert": "Dönüştürülecek dosyaları seç", - "selectForcedLines": "Zorunlu satırları seç", + "selectForcedLines": "Zorunlu satırları seç", "selectNone": "Hiçbirini seçme", - "selectNonForcedLines": "Zorunlu olmayan satırları seç", + "selectNonForcedLines": "Zorunlu olmayan satırları seç", "selectSaveFolder": "Kaydetmek için bir klasör seçin", "selectSubtitle": "Alt yazı seç", "selectedAFolderToSaveTo": "Kaydetmek için bir klasör seçildi", - "selectionToLowercase": "Seçimi küçük harfe dönüştür", + "selectionToLowercase": "Seçimi küçük harfe dönüştür", "selectionToSentenceCase": "Seçimi cümle düzenine dönüştür", "selectionToUppercase": "Seçimi büyük harfe dönüştür", "selectedLines": "Seçili satırlar", @@ -562,8 +562,8 @@ "separator": "Ayırıcı", "setEnd": "Bitiş ayarla", "setEndAndGoToNext": "Bitişi ayarla ve sonrakine git", - "setEndAddNewAndGoToNew": "Bitişi ayarla, yeni ekle ve yeniye git", - "setEndAddNewAndGoToNewNoFocusTextBox": "Bitişi ayarla, yeni ekle ve yeniye git (metin kutusuna odaklanma)", + "setEndAddNewAndGoToNew": "Bitişi ayarla, yeni ekle ve yeniye git", + "setEndAddNewAndGoToNewNoFocusTextBox": "Bitişi ayarla, yeni ekle ve yeniye git (metin kutusuna odaklanma)", "setEndAndOffsetTheRest": "Bitişi ayarla ve geri kalanını kaydır", "setFontDotDotDot": "Yazı tipini ayarla...", "setInCueToClosestShotChangeLeftGreenZone": "Giriş zamanını en yakın sahne değişimine ayarla (sol yeşil bölgeye hizala)", @@ -571,18 +571,18 @@ "setOutCueToClosestShotChangeLeftGreenZone": "Bitiş zamanını en yakın sahne değişimine ayarla (sol yeşil bölgeye hizala)", "setOutCueToClosestShotChangeRightGreenZone": "Bitiş zamanını en yakın sahne değişimine ayarla (sağ yeşil bölgeye hizala)", "setStart": "Başlangıç ayarla", - "setStartAndGoToNext": "Başlangıcı ayarla ve sonrakine git", - "setStartAndKeepDuration": "Başlangıcı ayarla ve süreyi koru", + "setStartAndGoToNext": "Başlangıcı ayarla ve sonrakine git", + "setStartAndKeepDuration": "Başlangıcı ayarla ve süreyi koru", "setStartAndOffsetTheRest": "Başlangıcı ayarla ve geri kalanını kaydır", - "setUpLikeSubtitleEdit4": "Subtitle Edit 4 gibi kur (tema, kısayollar, değiştirme kuralları)", - "imageBackgroundColorDotDotDot": "Görüntü arka plan rengi...", + "setUpLikeSubtitleEdit4": "Subtitle Edit 4 gibi kur (tema, kısayollar, değiştirme kuralları)", + "imageBackgroundColorDotDotDot": "Görüntü arka plan rengi...", "setVideoOffset": "Video farkını ayarla", "settings": "Ayarlar", "shadow": "Gölge", "shadowColor": "Gölge rengi", "shadowWidth": "Gölge genişliği", - "shiftLineBy": "Satırı şu kadar kaydır", - "shotIn": "Sahne girişi", + "shiftLineBy": "Satırı şu kadar kaydır", + "shotIn": "Sahne girişi", "shotOut": "Sahne çıkışı", "shortcut": "Kısayol", "shortcuts": "Kısayollar", @@ -591,25 +591,25 @@ "showCpsColumn": "\"Kar/sn\" sütununu göster", "showDurationColumn": "\"Süre\" sütununu göster", "showGapColumn": "\"Boşluk\" sütununu göster", - "showForcedColumn": "\"Zorunlu\" sütununu göster", + "showForcedColumn": "\"Zorunlu\" sütununu göster", "showStartColumn": "\"Başlangıç\" sütununu göster", "showHideColumn": "\"Gizle\" sütununu göster", "showHistory": "Geçmişi göster", - "clearHistory": "Geçmişi temizle", + "clearHistory": "Geçmişi temizle", "removeFromHistory": "Geçmişten kaldır", "showLayerColumn": "\"Katman\" sütununu göster", "showPreview": "Ön izlemeyi göster", "showShotChangesList": "Sahne değişimleri listesini göster", - "showShotInColumn": "\"Sahne girişi\" sütununu göster", + "showShotInColumn": "\"Sahne girişi\" sütununu göster", "showShotOutColumn": "\"Sahne çıkışı\" sütununu göster", "showStyleColumn": "\"Stil\" sütununu göster", - "showTeletext": "Liste görünümünde teletext sütununu göster", + "showTeletext": "Liste görünümünde teletext sütununu göster", "showTimeCodes": "Zaman kodlarını göster", "showWpmColumn": "\"Kelime/dk\" sütununu göster", "showPixelWidthColumn": "\"Piksel genişliği\" sütununu göster", "shrink": "Küçült", "singleMode": "Tekli mod", - "textTotalLength": "Metin - toplam uzunluk", + "textTotalLength": "Metin - toplam uzunluk", "textSingleLineMaxLength": "Metin - tek satır maks. uzunluk", "size": "Boyut", "sizeX": "Boyut: {0}", @@ -627,21 +627,21 @@ "speechToTextSelectedLinesPromptAlways": "Seçili satırlar için sesten metne (her zaman motor/dil sor)", "speechToTextSelectedLinesPromptFirstTime": "Seçili satırlar için sesten metne (motor/dil sadece ilk seferde sor)", "speed": "Hız", - "balanceWithNextSubtitle": "Sonraki alt yazı ile dengele", - "balanceWithPreviousSubtitle": "Önceki alt yazı ile dengele", - "fetchLastWordFromPreviousSubtitle": "Önceki alt yazıdan son kelimeyi al", - "fetchRestOfSentenceFromNextSubtitle": "Sonraki alt yazıdan cümlenin kalanını al", - "fetchUnfinishedSentenceFromPreviousSubtitle": "Önceki alt yazıdan tamamlanmamış cümleyi al", - "moveFirstWordToPreviousSubtitle": "İlk kelimeyi önceki alt yazıya taşı", - "moveRestOfSentenceToPreviousSubtitle": "Cümlenin kalanını önceki alt yazıya taşı", - "moveUnfinishedSentenceToNextSubtitle": "Tamamlanmamış cümleyi sonraki alt yazıya taşı", - "splitAtComma": "Virgülden böl", - "splitAtDialogDash": "Diyalog tire işaretinden böl", - "splitAtLineBreak": "Satır sonundan böl", - "splitAtSentenceEnd": "Cümle sonundan böl", + "balanceWithNextSubtitle": "Sonraki alt yazı ile dengele", + "balanceWithPreviousSubtitle": "Önceki alt yazı ile dengele", + "fetchLastWordFromPreviousSubtitle": "Önceki alt yazıdan son kelimeyi al", + "fetchRestOfSentenceFromNextSubtitle": "Sonraki alt yazıdan cümlenin kalanını al", + "fetchUnfinishedSentenceFromPreviousSubtitle": "Önceki alt yazıdan tamamlanmamış cümleyi al", + "moveFirstWordToPreviousSubtitle": "İlk kelimeyi önceki alt yazıya taşı", + "moveRestOfSentenceToPreviousSubtitle": "Cümlenin kalanını önceki alt yazıya taşı", + "moveUnfinishedSentenceToNextSubtitle": "Tamamlanmamış cümleyi sonraki alt yazıya taşı", + "splitAtComma": "Virgülden böl", + "splitAtDialogDash": "Diyalog tire işaretinden böl", + "splitAtLineBreak": "Satır sonundan böl", + "splitAtSentenceEnd": "Cümle sonundan böl", "splitAtTextBoxCursorPosition": "İmleç konumundan böl", "splitLine": "Satırı böl", - "splitNearMiddle": "Ortaya en yakın boşluktan böl", + "splitNearMiddle": "Ortaya en yakın boşluktan böl", "splitLineAtTextBoxCursorPosition": "Satırı imleç konumundan böl", "splitLineAtVideoAndTextBoxPosition": "Satırı video ve metin kutusu konumundan böl", "splitLineAtVideoPosition": "Satırı video konumundan böl", @@ -651,21 +651,21 @@ "startingDotDotDot": "Başlatılıyor...", "status": "Durum", "stereo": "Stereo", - "spreadsheetFiles": "Hesap tablosu dosyaları", + "spreadsheetFiles": "Hesap tablosu dosyaları", "stop": "Durdur", "strikeout": "Üstü çizili", "style": "Stil", "styleExaggeration": "Stil abartısı", "styles": "Stiller", "subtitleFile": "Alt yazı dosyası", - "subtitleFileChangedOutsideX": "\"{0}\" dosyası Subtitle Edit haricinde değiştirildi.\n\nYeniden yüklensin mi? Değişiklikleriniz kaybolacaktır.", + "subtitleFileChangedOutsideX": "\"{0}\" dosyası Subtitle Edit haricinde değiştirildi.\n\nYeniden yüklensin mi? Değişiklikleriniz kaybolacaktır.", "subtitleFileName": "Alt yazı dosya adı", "subtitleFileSaved": "Alt yazı dosyası kaydedildi", "subtitleFileSavedToX": "Alt yazı dosyası {0} konumuna kaydedildi", "subtitleFiles": "Alt yazı dosyaları", "subtitleFormats": "Alt yazı formatları", "subtitleLoadedX": "Alt yazı yüklendi: {0}", - "subtitleReloadedChangedOutsideX": "Alt yazı yeniden yüklendi (Subtitle Edit haricinde değiştirildi): {0}", + "subtitleReloadedChangedOutsideX": "Alt yazı yeniden yüklendi (Subtitle Edit haricinde değiştirildi): {0}", "suffix": "Son ek", "suggestions": "Öneriler", "sync": "Senkronizasyon", @@ -673,8 +673,8 @@ "system": "Sistem", "targetEncoding": "Hedef kodlama", "targetFormat": "Hedef format", - "teletextAlignment": "Teletext hizalaması", - "teletextColor": "Teletext rengi", + "teletextAlignment": "Teletext hizalaması", + "teletextColor": "Teletext rengi", "teletextLine": "Teletext satırı", "tenHours": "10 saat", "text": "Metin", @@ -691,10 +691,10 @@ "toggleCasing": "Büyük/küçük harf değiştir", "toggleDirection": "Yönü değiştir", "toggleForced": "Zorunluyu aç/kapat", - "markedXLinesAsForced": "{0} satır zorunlu olarak işaretlendi", - "unmarkedXLinesAsForced": "{0} satırın zorunlu işareti kaldırıldı", - "noForcedLinesFound": "Zorunlu satır bulunamadı", - "saveForcedLinesAs": "Zorunlu satırları farklı kaydet...", + "markedXLinesAsForced": "{0} satır zorunlu olarak işaretlendi", + "unmarkedXLinesAsForced": "{0} satırın zorunlu işareti kaldırıldı", + "noForcedLinesFound": "Zorunlu satır bulunamadı", + "saveForcedLinesAs": "Zorunlu satırları farklı kaydet...", "toggleShotChangesAtVideoPosition": "Video konumunda sahne değişimlerini aç/kapat", "toolbar": "Araç çubuğu", "tools": "Araçlar", @@ -704,14 +704,14 @@ "top": "Üst", "totalAdjustmentX": "Toplam düzeltme: {0}", "translate": "Çevir", - "translatePercentX": "Çevriliyor: %{0}", + "translatePercentX": "Çevriliyor: %{0}", "translateRow": "Satırı çevir", "translation": "Çeviri", "twoLetterLanguageCode": "İki harfli dil kodu", "type": "Tür", "unbreak": "Metni birleştir", "unbreakLines": "Satırları birleştir", - "undelete": "Silmeyi geri al", + "undelete": "Silmeyi geri al", "underline": "Altı çizili", "undo": "Geri al", "unknown": "Bilinmiyor", @@ -724,7 +724,7 @@ "updateAndClose": "Güncelle ve kapat", "updateAvailable": "Güncelleme mevcut", "updateDetails": "Güncelleme detayları", - "updateX": "{0} güncelle", + "updateX": "{0} güncelle", "updatedBy": "Güncelleyen", "upToDate": "Güncel", "url": "URL", @@ -745,11 +745,11 @@ "videoCustom1ForwardX": "Video, özel milisaniye ({0:#,###,##0}) ileri, 1", "videoCustom2BackX": "Video, özel milisaniye ({0:#,###,##0}) geri, 2", "videoCustom2ForwardX": "Video, özel milisaniye ({0:#,###,##0}) ileri, 2", - "videoCustom3BackX": "Video, özel milisaniye ({0:#,###,##0}) geri, 3", - "videoCustom3ForwardX": "Video, özel milisaniye ({0:#,###,##0}) ileri, 3", - "videoCustom4BackX": "Video, özel milisaniye ({0:#,###,##0}) geri, 4", - "videoCustom4ForwardX": "Video, özel milisaniye ({0:#,###,##0}) ileri, 4", - "videoCustom1BackAndPauseX": "Video, özel milisaniye ({0:#,###,##0}) geri ve duraklat, 1", + "videoCustom3BackX": "Video, özel milisaniye ({0:#,###,##0}) geri, 3", + "videoCustom3ForwardX": "Video, özel milisaniye ({0:#,###,##0}) ileri, 3", + "videoCustom4BackX": "Video, özel milisaniye ({0:#,###,##0}) geri, 4", + "videoCustom4ForwardX": "Video, özel milisaniye ({0:#,###,##0}) ileri, 4", + "videoCustom1BackAndPauseX": "Video, özel milisaniye ({0:#,###,##0}) geri ve duraklat, 1", "videoCustom1ForwardAndPauseX": "Video, özel milisaniye ({0:#,###,##0}) ileri ve duraklat, 1", "videoCustom2BackAndPauseX": "Video, özel milisaniye ({0:#,###,##0}) geri ve duraklat, 2", "videoCustom2ForwardAndPauseX": "Video, özel milisaniye ({0:#,###,##0}) ileri ve duraklat, 2", @@ -759,19 +759,19 @@ "videoCustom4ForwardAndPauseX": "Video, özel milisaniye ({0:#,###,##0}) ileri ve duraklat, 4", "videoExtension": "Video dosyası uzantısı", "videoFile": "Video dosyası", - "videoFileGenerated": "Video dosyası oluşturuldu", - "videoFileGeneratedX": "Video dosyası oluşturuldu: \"{0}\"", - "videoFilesGenerated": "Video dosyaları oluşturuldu", + "videoFileGenerated": "Video dosyası oluşturuldu", + "videoFileGeneratedX": "Video dosyası oluşturuldu: \"{0}\"", + "videoFilesGenerated": "Video dosyaları oluşturuldu", "xVideosGenerated": "{0} video oluşturuldu", "xOfYVideosGenerated": "{0} / {1} video oluşturuldu", "savedInX": "{0} konumuna kaydedildi", "savedInXFolders": "{0} klasöre kaydedildi", "videoFiles": "Video dosyaları", - "videoAndAudioFiles": "Video ve ses dosyaları", + "videoAndAudioFiles": "Video ve ses dosyaları", "videoOffset": "Video farkı", "videoOneFrameBack": "Video, bir kare geri", "videoOneFrameForward": "Video, bir kare ileri", - "videoOneFrameBackWithPlay": "Video, oynatarak bir kare geri", + "videoOneFrameBackWithPlay": "Video, oynatarak bir kare geri", "videoOneFrameForwardWithPlay": "Video, oynatarak bir kare ileri", "videoOneSecondBack": "Video, bir saniye geri", "videoOneSecondForward": "Video, bir saniye ileri", @@ -780,9 +780,9 @@ "videoResolution": "Video çözünürlüğü", "visible": "Görünür", "voice": "Ses", - "volume": "Ses düzeyi", + "volume": "Ses düzeyi", "warning": "Uyarı", - "waveform": "Ses dalgası", + "waveform": "Ses dalgası", "waveformCenterOnVideoPosition": "Dalga formunu video konumuna ortala", "waveformPasteFromClipboard": "Panodan yapıştır", "waveformSpectrogram": "Dalga formu/Spektrogram", @@ -791,7 +791,7 @@ "pixelWidth": "Piksel genişliği", "wpm": "Kelime/dak", "xFiles": "{0:#,###,##0} dosya", - "xOfYFiles": "{0:#,###,##0} / {1:#,###,##0} dosya", + "xOfYFiles": "{0:#,###,##0} / {1:#,###,##0} dosya", "xFilesConvertedInY": "{0:#,###,##0} dosya {1} içinde dönüştürüldü", "xNoLinesUpdated": "{0}: Hiçbir satır güncellenmedi", "xNotFound": "\"{0}\" bulunamadı", @@ -799,11 +799,11 @@ "xOfYLinesUpdatedAndZNeighborsAdjusted": "{0}: {2} satırdan {1} tanesi güncellendi, {3} komşu satır ayarlandı", "noBookmarksFound": "Başka yer imi bulunamadı", "xRequiresAnApiKey": "{0} bir API anahtarı gerektirir", - "xRequiresAValidUrl": "{0} geçerli bir URL gerektirir (örn. \"http://...\" veya \"https://...\")", + "xRequiresAValidUrl": "{0} geçerli bir URL gerektirir (örn. \"http://...\" veya \"https://...\")", "yes": "Evet", - "zeroBytes": "0 bayt", + "zeroBytes": "0 bayt", "copyToClipboard": "Panoya kopyala", - "copiedToClipboard": "Panoya kopyalandı", + "copiedToClipboard": "Panoya kopyalandı", "playCurrent": "Mevcut olanı oynat", "leftMargin": "Sol kenar boşluğu", "rightMargin": "Sağ kenar boşluğu", @@ -828,14 +828,14 @@ "openAiCompatibleSttLanguage": "Dil İpucu", "openAiCompatibleSttTemperature": "Sıcaklık", "openAiCompatibleSttPrompt": "Komut istemi", - "openAiCompatibleSttModelRejectedHint": "Sunucu model adını reddetti. Her OpenAI uyumlu uç nokta model adı kabul etmez; örneğin xAI'ın https://api.x.ai/v1/stt adresi hiç 'model' parametresi almaz. Model alanını temizlemeyi deneyin.", - "dashScopeSttRegion": "Bölge", - "dashScopeSttEnableWords": "Kelime düzeyinde zaman damgaları", - "dashScopeSttRegionKeyHint": "Not: Alibaba Cloud Model Studio API anahtarları bölgeye özeldir; seçilen bölgenin, API anahtarının oluşturulduğu bölgeyle (Çin veya Uluslararası) eşleştiğinden emin olun.", - "googleCloudSttProjectId": "Proje Kimliği (yalnızca Uygulama Varsayılan Kimlik Bilgileri için)", - "googleCloudSttBucketName": "Depolama kovası (boş = otomatik)", - "googleCloudSttDynamicBatching": "Dinamik toplu işleme (yaklaşık 5 kat daha ucuz, gecikme süresi garantisi yok)", - "onlineSttApiKeyMissing": "Bir API anahtarı gereklidir. Lütfen API anahtarınızı girip tekrar deneyin.", + "openAiCompatibleSttModelRejectedHint": "Sunucu model adını reddetti. Her OpenAI uyumlu uç nokta model adı kabul etmez; örneğin xAI'ın https://api.x.ai/v1/stt adresi hiç 'model' parametresi almaz. Model alanını temizlemeyi deneyin.", + "dashScopeSttRegion": "Bölge", + "dashScopeSttEnableWords": "Kelime düzeyinde zaman damgaları", + "dashScopeSttRegionKeyHint": "Not: Alibaba Cloud Model Studio API anahtarları bölgeye özeldir; seçilen bölgenin, API anahtarının oluşturulduğu bölgeyle (Çin veya Uluslararası) eşleştiğinden emin olun.", + "googleCloudSttProjectId": "Proje Kimliği (yalnızca Uygulama Varsayılan Kimlik Bilgileri için)", + "googleCloudSttBucketName": "Depolama kovası (boş = otomatik)", + "googleCloudSttDynamicBatching": "Dinamik toplu işleme (yaklaşık 5 kat daha ucuz, gecikme süresi garantisi yok)", + "onlineSttApiKeyMissing": "Bir API anahtarı gereklidir. Lütfen API anahtarınızı girip tekrar deneyin.", "openAiCompatibleSttAutoTranscribeOnAudioSelection": "Ses dalgası üzerindeki yeni seçimi ses tanıma ile otomatik yazıya dök", "openAiCompatibleSttStream": "Yanıtı akış olarak al", "openAiCompatibleSttStreamHint": "Canlı metin parçacıkları (SSE) almak için 'stream=true' gönderir. 'stream' parametresini reddeden sunucular (örn. Groq) için bunu devre dışı bırakın.", @@ -860,15 +860,15 @@ "file": "_Dosya", "new": "_Yeni", "newKeepVideo": "Yeni (_videoyu koru)", - "newWindow": "Yeni _pencere", + "newWindow": "Yeni _pencere", "windowTitle": "Pencere", - "windowMinimize": "Simge durumuna küçült", + "windowMinimize": "Simge durumuna küçült", "windowZoom": "Yakınlaştır", "closeWindow": "Pencereyi kapat", "open": "_Aç...", "openKeepVideo": "Aç (_videoyu koru)...", "openOriginal": "_Orijinali aç...", - "editOriginalSubtitle": "Orijinal alt yazıyı düzenle", + "editOriginalSubtitle": "Orijinal alt yazıyı düzenle", "closeOriginal": "Orijinali _kapat", "closeTranslation": "Çeviriyi _kapat", "reopen": "Yeniden _aç...", @@ -892,7 +892,7 @@ "multipleReplace": "Ç_oklu değiştir...", "fillSelectedLinesWithClipboard": "Seçili satırları panodaki metinle doldur", "goToLineNumber": "_Satır numarasına git...", - "commandPalette": "Komut _paleti...", + "commandPalette": "Komut _paleti...", "rightToLeftMode": "S_ağdan sola modu", "modifySelectionDotDotDot": "Seçimi _değiştir...", "tools": "_Araçlar", @@ -900,32 +900,32 @@ "applyDurationLimits": "Süre _limitlerini uygula...", "batchConvert": "_Toplu dönüştür...", "beautifyTimeCodes": "Zaman kodlarını optimize et...", - "improveTimeCodes": "Zaman kodlarını iyileştir (zorlamalı hizalama)...", + "improveTimeCodes": "Zaman kodlarını iyileştir (zorlamalı hizalama)...", "bridgeGaps": "Boşlukları köp_rüle...", "applyMinGap": "Alt yazılar arası min. boşluğu uygula...", "changeCasing": "_Büyük/küçük harf değiştir...", "changeFormatting": "Biçimlendirmeyi değiştir...", - "aiReview": "Yapay zekâ incelemesi...", + "aiReview": "Yapay zekâ incelemesi...", "fixCommonErrors": "Yaygın hataları _düzelt...", "checkAndFixNetflixErrors": "Netfli_x hatalarını kontrol et ve düzelt...", - "listErrors": "Hataları l_istele...", + "listErrors": "Hataları l_istele...", "makeEmptyTranslationFromCurrentSubtitle": "Mevcut alt yazıdan yeni _boş çeviri yap", "mergeLinesWithSameText": "Aynı metne sahip satırları bir_leştir...", "mergeLinesWithSameTimeCodes": "Aynı zaman kodlu satırları birleştir...", "splitBreakLongLines": "Uzun satırları böl/dengele...", - "evenlyDistributeLines": "Satırları dengeli dağıt (CPS'ye göre)", + "evenlyDistributeLines": "Satırları dengeli dağıt (CPS'ye göre)", "mergeShortLines": "Kısa satırları birleştir...", "mergeContinuationLines": "Devam eden satırları birleştir...", "snapAllTimesToFrames": "Tüm zamanları karelere yasla", "renumber": "Yeniden numaralandır...", "removeTextForHearingImpaired": "İşitme engelliler için metni _kaldır...", - "removeUnicodeCharacters": "_Unicode karakterleri kaldır/değiştir...", + "removeUnicodeCharacters": "_Unicode karakterleri kaldır/değiştir...", "convertActors": "Aktörleri dönüştür...", "joinSubtitles": "Alt yazıları _birleştir...", "splitSubtitle": "Alt yazıyı _böl...", "mergeTwoSubtitles": "İki alt yazıyı birleştir...", "assaTools": "_ASSA araçları", - "ssaTools": "_SSA araçları", + "ssaTools": "_SSA araçları", "assaProgressBar": "İ_lerleme çubuğu oluştur...", "assaChangeResolution": "Çözünürlüğü _değiştir...", "assaGenerateBackground": "Arka plan _kutuları oluştur...", @@ -937,7 +937,7 @@ "assaStyles": "S_tiller...", "assaProperties": "Ö_zellikler...", "assaAttachments": "_Ekler...", - "assaFontCollector": "_Yazı tipi toplayıcı...", + "assaFontCollector": "_Yazı tipi toplayıcı...", "spellCheckTitle": "_Yazım denetimi", "spellCheck": "_Yazım denetimi...", "findDoubleWords": "Çift kelimeleri _bul...", @@ -951,11 +951,11 @@ "audioTracks": "Ses _izleri", "speechToText": "Sesten _metne...", "textToSpeech": "_Metinden sese...", - "videoOcr": "Gömülü alt yazıyı _OCR ile tara...", + "videoOcr": "Gömülü alt yazıyı _OCR ile tara...", "setVideoOffset": "Video farkını ayarla...", "updateVideoOffsetX": "Video farkını {0} değerinden güncelle...", "smpteTiming": "SMPTE zamanlaması (tam sayı olmayan kare hızı)", - "waveformToolbar": "Ses dalgası araç çubuğu", + "waveformToolbar": "Ses dalgası araç çubuğu", "generateBurnIn": "Gömülü alt yazılı video _oluştur...", "generateTransparent": "_Alt yazılı şeffaf video oluştur...", "generateImportShotChanges": "Sahne deği_şimlerini oluştur/içe aktar...", @@ -964,7 +964,7 @@ "dockVideoControls": "Video kontrollerini sa_bitle", "synchronization": "S_enkronizasyon", "adjustAllTimes": "_Tüm zamanları ayarla...", - "showSelectedLinesEarlierLater": "Seçili _alt yazı satırlarını erkene/sonraya al...", + "showSelectedLinesEarlierLater": "Seçili _alt yazı satırlarını erkene/sonraya al...", "changeFrameRate": "Kare _hızını değiştir...", "changeSpeed": "_Hızı değiştir...", "visualSync": "_Görsel senkronizasyon...", @@ -990,7 +990,7 @@ "filterLayersForDisplayDotDotDot": "Görüntülenecek katmanları filtrele...", "toggleSelectSubtitleWhilePlayingCurrentlyOn": "Oynatırken alt yazıyı seçmeyi aç/kapat (şu anda: açık)", "toggleSelectSubtitleWhilePlayingCurrentlyOff": "Oynatırken alt yazıyı seçmeyi aç/kapat (şu anda: kapalı)", - "selectSubtitleWhilePlaying": "Oynatılırken geçerli alt yazıyı seç" + "selectSubtitleWhilePlaying": "Oynatılırken geçerli alt yazıyı seç" }, "toolbar": { "newHint": "Yeni bir alt yazı dosyası başlat {0}", @@ -1000,15 +1000,15 @@ "saveAsHint": "Alt yazıyı yeni bir adla kaydet {0}", "findHint": "Alt yazılarda metin bul {0}", "replaceHint": "Metni bul ve değiştir {0}", - "multipleReplaceHint": "Çoklu değiştirme {0}", + "multipleReplaceHint": "Çoklu değiştirme {0}", "spellCheckHint": "Alt yazıları yazım hataları için kontrol et {0}", "fixCommonErrorsHint": "Yaygın alt yazı hatalarını düzelt {0}", "removeTextForHiHint": "İşitme engelliler için metni kaldır {0}", "visualSyncHint": "Görsel eşitleme {0}", - "pointSyncHint": "Başka bir alt yazı üzerinden nokta senkronizasyonu: {0}", + "pointSyncHint": "Başka bir alt yazı üzerinden nokta senkronizasyonu: {0}", "beautifyTimeCodesHint": "Zaman kodlarını optimize et {0}", "burnInHint": "Alt yazıları video dosyasına göm {0}", - "autoTranslateHint": "Otomatik çeviri: {0}", + "autoTranslateHint": "Otomatik çeviri: {0}", "speechToTextHint": "Sesten metne dönüştürme: {0}", "settingsHint": "Program ayarlarını ve tercihlerini düzenle {0}", "layoutHint": "Araç çubuğu ve panel düzenini değiştir {0}", @@ -1019,11 +1019,11 @@ "assaPropertiesHint": "Advanced Sub Station Alpha özellikleri", "assaAttachmentsHint": "Advanced Sub Station Alpha ekleri", "assaDrawHint": "Advanced Sub Station Alpha şekil çizimi", - "ssaStylesHint": "Sub Station Alpha stilleri", - "webVttStylesHint": "WebVTT stil yöneticisi", - "ssaPropertiesHint": "Sub Station Alpha özellikleri", - "ssaAttachmentsHint": "Sub Station Alpha ekleri", - "getFrameRateFromVideoFileHint": "Kare hızını video dosyasından al" + "ssaStylesHint": "Sub Station Alpha stilleri", + "webVttStylesHint": "WebVTT stil yöneticisi", + "ssaPropertiesHint": "Sub Station Alpha özellikleri", + "ssaAttachmentsHint": "Sub Station Alpha ekleri", + "getFrameRateFromVideoFileHint": "Kare hızını video dosyasından al" }, "waveform": { "playPauseHint": "Oynat / Duraklat {0}", @@ -1038,24 +1038,24 @@ "zoomVerticalHint": "Dikey yakınlaştırma {0}", "selectCurrentLineWhilePlayingHint": "Oynatırken mevcut alt yazıyı seç {0}", "videoPosition": "Video konumu {0}", - "videoPositionTextBox": "Video konumu metin kutusu {0}", - "toolbarLineBreakX": "Satır sonu {0} (araç çubuğunun kalanı yeni satırda devam eder)", - "initialText": "Seçili satırın ilk metni", + "videoPositionTextBox": "Video konumu metin kutusu {0}", + "toolbarLineBreakX": "Satır sonu {0} (araç çubuğunun kalanı yeni satırda devam eder)", + "initialText": "Seçili satırın ilk metni", "initialTextHint": "Seçili satırın seçildiği andaki metni", "hideWaveformToolbar": "Araç çubuğunu gizle {0}", "resetZoomAndSpeed": "Yakınlaştırmayı ve oynatma hızını sıfırla {0}", "removeBlankLines": "Boş satırları kaldır {0}", "playSelectedRepeatHint": "Seçili alt yazıları tekrar modunda oynat {0}", - "seekBackHint": "Videoyu {0} geri sar", - "seekForwardHint": "Videoyu {0} ileri sar", - "seekAmountHint": "Sarılacak süre/kare: {0}", - "seekVideo": "Videoyu sar (<< >>)", - "configureToolbarItems": "Araç çubuğu ögelerini yapılandır...", - "textPreviousHint": "Önceki alt yazıyı oynat ve sonunda dur {0}", + "seekBackHint": "Videoyu {0} geri sar", + "seekForwardHint": "Videoyu {0} ileri sar", + "seekAmountHint": "Sarılacak süre/kare: {0}", + "seekVideo": "Videoyu sar (<< >>)", + "configureToolbarItems": "Araç çubuğu ögelerini yapılandır...", + "textPreviousHint": "Önceki alt yazıyı oynat ve sonunda dur {0}", "textPlayHint": "Mevcut alt yazıyı oynat ve sonunda dur {0}", "textPauseHint": "Oynatmayı duraklat {0}", "textNextHint": "Sonraki alt yazıyı oynat ve sonunda dur {0}", - "moveSelectedLines": "Seçili satırları taşı (geri/ileri düğmeleri)", + "moveSelectedLines": "Seçili satırları taşı (geri/ileri düğmeleri)", "moveSelectedLinesAndFollowing": "Seçili satırları ve sonraki tümünü taşı (geri/ileri düğmeleri)", "moveAllLines": "Tüm satırları taşı (geri/ileri düğmeleri)", "moveSelectedLinesBackHint": "Seçili satırları {0} ms geri taşı {1}", @@ -1064,20 +1064,20 @@ "moveSelectedLinesAndFollowingForwardHint": "Seçili satırları ve sonraki tümünü {0} ms ileri taşı {1}", "moveAllLinesBackHint": "Tüm satırları {0} ms geri taşı {1}", "moveAllLinesForwardHint": "Tüm satırları {0} ms ileri taşı {1}", - "guessStartNoLineSelected": "Başlangıcı tahmin et: Satır seçilmedi", - "guessStartTimeCodesLocked": "Başlangıcı tahmin et: Zaman kodları kilitli", - "guessStartNoWaveform": "Başlangıcı tahmin et: Ses dalgası yok", - "guessStartNoRoomBeforePreviousLineX": "Başlangıcı tahmin et: {0}. satır ile önceki satır arasında boşluk yok", - "guessStartLineXAlreadyAtBoundary": "Başlangıcı tahmin et: {0}. satır zaten konuşmanın başladığı yerde duruyor", - "guessStartNoSilenceFoundNearLineX": "Başlangıcı tahmin et: {0}. satırın yakınında sessizlik bulunamadı", - "guessStartMovedLineXByYMs": "Başlangıcı tahmin et: {0}. satırın başlangıcı {1} ms kaydırıldı", - "guessEndNoLineSelected": "Bitişi tahmin et: Satır seçilmedi", - "guessEndTimeCodesLocked": "Bitişi tahmin et: Zaman kodları kilitli", - "guessEndNoWaveform": "Bitişi tahmin et: Ses dalgası yok", - "guessEndNoRoomBeforeNextLineX": "Bitişi tahmin et: {0}. satır ile sonraki satır arasında boşluk yok", - "guessEndLineXAlreadyAtBoundary": "Bitişi tahmin et: {0}. satır zaten konuşmanın bittiği yerde duruyor", - "guessEndNoSilenceFoundNearLineX": "Bitişi tahmin et: {0}. satırın yakınında sessizlik bulunamadı", - "guessEndMovedLineXByYMs": "Bitişi tahmin et: {0}. satırın bitişi {1} ms kaydırıldı" + "guessStartNoLineSelected": "Başlangıcı tahmin et: Satır seçilmedi", + "guessStartTimeCodesLocked": "Başlangıcı tahmin et: Zaman kodları kilitli", + "guessStartNoWaveform": "Başlangıcı tahmin et: Ses dalgası yok", + "guessStartNoRoomBeforePreviousLineX": "Başlangıcı tahmin et: {0}. satır ile önceki satır arasında boşluk yok", + "guessStartLineXAlreadyAtBoundary": "Başlangıcı tahmin et: {0}. satır zaten konuşmanın başladığı yerde duruyor", + "guessStartNoSilenceFoundNearLineX": "Başlangıcı tahmin et: {0}. satırın yakınında sessizlik bulunamadı", + "guessStartMovedLineXByYMs": "Başlangıcı tahmin et: {0}. satırın başlangıcı {1} ms kaydırıldı", + "guessEndNoLineSelected": "Bitişi tahmin et: Satır seçilmedi", + "guessEndTimeCodesLocked": "Bitişi tahmin et: Zaman kodları kilitli", + "guessEndNoWaveform": "Bitişi tahmin et: Ses dalgası yok", + "guessEndNoRoomBeforeNextLineX": "Bitişi tahmin et: {0}. satır ile sonraki satır arasında boşluk yok", + "guessEndLineXAlreadyAtBoundary": "Bitişi tahmin et: {0}. satır zaten konuşmanın bittiği yerde duruyor", + "guessEndNoSilenceFoundNearLineX": "Bitişi tahmin et: {0}. satırın yakınında sessizlik bulunamadı", + "guessEndMovedLineXByYMs": "Bitişi tahmin et: {0}. satırın bitişi {1} ms kaydırıldı" }, "audioTrackIsNowX": "Ses izi artık \"{0}\"", "audioTrackX": "Ses izi {0}", @@ -1085,7 +1085,7 @@ "charactersPerSecond": "Kar/saniye: {0}", "chooseColumn": "Sütun seç", "columnPaste": "Sütun yapıştır", - "commandPaletteTitle": "Komut paleti", + "commandPaletteTitle": "Komut paleti", "commandPaletteSearchPlaceholder": "Komut aramak için yazın...", "commandPaletteNoCommandsFound": "Eşleşen komut bulunamadı", "createdEmptyTranslation": "Mevcut alt yazıdan boş çeviri oluşturuldu", @@ -1100,7 +1100,7 @@ "errorLoadJpg": "Bu dosya bir JPG resim dosyası gibi görünüyor.\n\nSubtitle Edit resim dosyalarını açamaz.", "errorLoadPng": "Bu dosya bir PNG resim dosyası gibi görünüyor.\n\nSubtitle Edit resim dosyalarını açamaz.", "errorLoadRar": "Bu dosya sıkıştırılmış bir RAR dosyası gibi görünüyor.\n\nSubtitle Edit sıkıştırılmış dosyaları açamaz.", - "errorLoadRawPgsPrompt": "Bu dosya, Blu-ray SUP \"PG\" üst bilgilerini içermeyen ham bir PGS akışı gibi görünüyor; bu nedenle zaman damgası barındırmıyor.\n\nBu durum genellikle bir Matroska PGS izi \"ham\" (raw) modda dışa aktarıldığında gerçekleşir. Doğru zamanlamalar için izi normal modda tekrar dışa aktarın (ör. --raw parametresi olmadan \"mkvextract tracks\" kullanarak) veya orijinal Matroska dosyasını doğrudan Subtitle Edit ile açın.\n\nYine de alt yazı görsellerini, manuel olarak düzeltilmesi gereken otomatik üretilmiş geçici zamanlamalarla içe aktarmak istiyor musunuz?", + "errorLoadRawPgsPrompt": "Bu dosya, Blu-ray SUP \"PG\" üst bilgilerini içermeyen ham bir PGS akışı gibi görünüyor; bu nedenle zaman damgası barındırmıyor.\n\nBu durum genellikle bir Matroska PGS izi \"ham\" (raw) modda dışa aktarıldığında gerçekleşir. Doğru zamanlamalar için izi normal modda tekrar dışa aktarın (ör. --raw parametresi olmadan \"mkvextract tracks\" kullanarak) veya orijinal Matroska dosyasını doğrudan Subtitle Edit ile açın.\n\nYine de alt yazı görsellerini, manuel olarak düzeltilmesi gereken otomatik üretilmiş geçici zamanlamalarla içe aktarmak istiyor musunuz?", "errorLoadSrr": "Bu dosya bir ReScene SRR dosyası gibi görünüyor.\n\nSubtitle Edit SRR dosyalarını açamaz.", "errorLoadTorrent": "Bu dosya bir BitTorrent dosyası gibi görünüyor.\n\nSubtitle Edit torrent dosyalarını açamaz.", "errorLoadZip": "Bu dosya sıkıştırılmış bir ZIP dosyası gibi görünüyor.\n\nSubtitle Edit sıkıştırılmış dosyaları açamaz.", @@ -1120,20 +1120,20 @@ "lineXTextChangedFromYToZ": "Satır {0}: Metin \"{1}\" değerinden \"{2}\" değerine değişti", "lineXTimingChanged": "Satır {0}: Zamanlama değişti", "loadingWaveInfoFromCache": "Dalga bilgisi önbellekten yükleniyor...", - "clickToGenerateWaveform": "Ses dalgası oluşturmak için tıklayın", - "waveformFfmpegNotFoundClickToSetUp": "FFmpeg bulunamadı - FFmpeg kurmak ve ses dalga şeklini oluşturmak için buraya tıklayın", + "clickToGenerateWaveform": "Ses dalgası oluşturmak için tıklayın", + "waveformFfmpegNotFoundClickToSetUp": "FFmpeg bulunamadı - FFmpeg kurmak ve ses dalga şeklini oluşturmak için buraya tıklayın", "noTextInClipboard": "Panoda metin yok", - "numberOfLinesEvenlyDistributedX": "{0} satır dengeli şekilde dağıtıldı", + "numberOfLinesEvenlyDistributedX": "{0} satır dengeli şekilde dağıtıldı", "oneLineCopiedFromOriginal": "Orijinal alt yazıdan bir satır kopyalandı", - "oneLineCopiedToOriginal": "Orijinal alt yazıya bir satır kopyalandı", - "onlineOnlyVideo": "Yalnızca çevrim içi video", + "oneLineCopiedToOriginal": "Orijinal alt yazıya bir satır kopyalandı", + "onlineOnlyVideo": "Yalnızca çevrim içi video", "onlineOnlyVideoXSizeYDownloadAndOpen": "\"{0}\" ({1}) video dosyası yalnızca çevrim içi olarak (örneğin Dropbox veya iCloud Drive üzerinde) depolanıyor ve açılabilmesi için önce indirilmesi gerekiyor.\n\nŞimdi indirilip açılsın mı?", "oneLineMerged": "Bir satır birleştirildi", "oneLineSwitched": "Bir satır değiştirildi", "overwriteExistingCells": "Mevcut hücrelerin üzerine yaz", "overwriteOrShiftCellsDown": "Üzerine yaz/hücreleri aşağı kaydır", "parsingMatroskaFile": "Matroska dosyası ayrıştırılıyor...", - "readingDvdSubtitles": "DVD alt yazıları okunuyor...", + "readingDvdSubtitles": "DVD alt yazıları okunuyor...", "pasteFromClipboardDotDotDot": "Panodan yapıştır...", "redoPerformed": "Yineleme yapıldı", "redoPerformedXActionLeft": "Yineleme yapıldı (kalan işlem: {0})", @@ -1147,7 +1147,7 @@ "shiftTextCellsDown": "Metin hücrelerini aşağı kaydır", "singleLineLength": "Satır uzunluğu: ", "speedIsNowX": "Hız artık \"{0}\"", - "subtitleGridFormattingX": "Izgara biçimlendirmesi: {0}", + "subtitleGridFormattingX": "Izgara biçimlendirmesi: {0}", "subtitleImportedFromMatroskaFile": "Alt yazı Matroska dosyasından içe aktarıldı", "textDown": "Metin aşağı", "textOnly": "Sadece metin", @@ -1158,35 +1158,35 @@ "unbreakHint": "Seçili satırları birleştir", "undoPerformed": "Geri alma yapıldı", "undoPerformedXActionLeft": "Geri alma yapıldı (kalan işlem: {0})", - "videoAndWaveformPreviewTextX": "Video/ses dalgası ön izleme: {0}", + "videoAndWaveformPreviewTextX": "Video/ses dalgası ön izleme: {0}", "xLinesCopiedFromOriginal": "Orijinal alt yazıdan {0} satır kopyalandı", - "xLinesCopiedToOriginal": "Orijinal alt yazıya {0} satır kopyalandı", + "xLinesCopiedToOriginal": "Orijinal alt yazıya {0} satır kopyalandı", "xLinesMerged": "{0} satır birleştirildi", "xLinesSelectedOfY": "{0} / {1} satır seçildi", "xLinesSwitched": "{0} satır değiştirildi", "xShotChangedLoaded": "{0} sahne değişimi yüklendi", "youtubeDlDownloadedSuccessfully": "\"yt-dlp\" başarıyla indirildi.", "youtubeDlNotInstalledDownloadNow": "\"yt-dlp\" yüklü değil ve çevrim içi videoları oynatmak için gerekli.\n\nŞimdi indirilsin mi?", - "promptInsertSubtitleOverlap": "Eklenen satırlar mevcut satırlarla çakışacak.\n\nYine de eklensin mi?", + "promptInsertSubtitleOverlap": "Eklenen satırlar mevcut satırlarla çakışacak.\n\nYine de eklensin mi?", "youtubeDlOutdatedDownloadNow": "\"yt-dlp\" sürümü güncel değil ve çevrimiçi videolarla çalışmayabilir.\n\nGüncel sürüm şimdi indirilsin mi?", "insertUnicodeSymbol": "Unicode sembolü ekle", "trimmedXLines": "{0} alt yazı satırı kırpıldı", - "pastedXLinesOverSelectedLines": "Seçilen satırların üzerine {0} satır yapıştırıldı", + "pastedXLinesOverSelectedLines": "Seçilen satırların üzerine {0} satır yapıştırıldı", "pastedXOfYLinesOverSelectedLines": "Seçilen satırların üzerine {0} / {1} satır yapıştırıldı - geri kalanı seçime sığmadı", - "insertedXLinesFromClipboard": "Pano içerisinden {0} satır eklendi", + "insertedXLinesFromClipboard": "Pano içerisinden {0} satır eklendi", "openOriginalDifferentNumberOfSubtitlesXY": "Orijinal alt yazı dosyası mevcut alt yazı dosyasıyla aynı sayıda alt yazıya sahip değil.\n\n• Orijinal alt yazılar: {0}\n• Mevcut alt yazılar: {1}", - "originalTextReadOnly": "Orijinal metin (salt okunur)", - "originalTextEditMode": "Orijinal metin (düzenleme modu)", + "originalTextReadOnly": "Orijinal metin (salt okunur)", + "originalTextEditMode": "Orijinal metin (düzenleme modu)", "originalIsReadOnlyNotSaved": "Orijinal alt yazı salt okunur bir referanstır ve kaydedilmedi", - "originalIsEmptyNotSaved": "Orijinal alt yazıda hiç metin yok ve kaydedilmedi", + "originalIsEmptyNotSaved": "Orijinal alt yazıda hiç metin yok ve kaydedilmedi", "originalIsReadOnlyReference": "Orijinal alt yazı salt okunur bir referans olarak açık", - "closeTranslationQuestionXY": "\"{0}\" çevirisi kapatılsın mı?\n\nOrijinal alt yazı \"{1}\", düzenlediğiniz alt yazı haline gelir. Çeviri dosyasının kendisi değiştirilmez.", + "closeTranslationQuestionXY": "\"{0}\" çevirisi kapatılsın mı?\n\nOrijinal alt yazı \"{1}\", düzenlediğiniz alt yazı haline gelir. Çeviri dosyasının kendisi değiştirilmez.", "allowEditOfOriginalSubtitle": "Orijinal alt yazının düzenlenmesine izin ver", "showAllOriginalLinesX": "Tüm orijinal satırları göster (burada {0} tanesinin eşleşmesi yok)", "showAllOriginalLinesHint": "Çeviride neyin eksik olduğunu görebilmeniz için eşleşmesi olmayan satırlar ekstra satır olarak gösterilir.", "showAllOriginalLinesNote": "O satırı alt yazınıza eklemek için ekstra satıra yazın", - "showMatchingOriginalLinesX": "Yalnızca eşleşen {0} orijinal satırı göster", - "showMatchingOriginalLinesHint": "Diğer {0} orijinal satır hiçbir yerde gösterilmiyor.", + "showMatchingOriginalLinesX": "Yalnızca eşleşen {0} orijinal satırı göster", + "showMatchingOriginalLinesHint": "Diğer {0} orijinal satır hiçbir yerde gösterilmiyor.", "showMatchingOriginalLinesNote": "Siz orijinali kaydetmediğiniz sürece dosyada kalmaya devam ederler", "allowEditHintReadOnly": "Orijinal dosya salt okunur olarak gösterilir ve asla kaydedilmez; bu nedenle dosyası değişemez.", "allowEditHintAllLines": "Orijinalin tamamı ekrandadır; bu nedenle kaydetmek tüm satırları korur.", @@ -1195,7 +1195,7 @@ "sortedByStartTime": "\"Göster\" zamanına göre sıralandı", "sortedByEndTime": "\"Gizle\" zamanına göre sıralandı", "sortedByNumber": "Numaraya göre sıralandı", - "sortedByDuration": "Süreye göre sıralandı", + "sortedByDuration": "Süreye göre sıralandı", "sortedByGap": "Boşluğa göre sıralandı", "sortedByText": "Metne göre sıralandı", "sortedByTextTotalLength": "Metin toplam uzunluğuna göre sıralandı", @@ -1206,7 +1206,7 @@ "sortedByActor": "Oyuncuya göre sıralandı", "sortedByStyle": "Stile göre sıralandı", "videoBrightnessSetTo": "Parlaklık: {0}", - "videoContrastSetTo": "Karşıtlık: {0}", + "videoContrastSetTo": "Karşıtlık: {0}", "colorHint": "Seçili satırları renklendir", "removeFormattingHint": "Seçili satırlardan biçimlendirmeyi kaldır", "assaResolutionResamplerDone": "ASSA çözünürlüğü değiştirildi.", @@ -1215,13 +1215,13 @@ "fileExportedInFormatXToY": "Dosya {0} formatında {1} konumuna aktarıldı", "fileExportedInFormatXToFileY": "Dosya \"{0}\" formatında \"{1}\" dosyasına aktarıldı", "fixedXLines": "{0} satır düzeltildi", - "toolXChangedYLines": "{0}: {1} satır değiştirildi", + "toolXChangedYLines": "{0}: {1} satır değiştirildi", "transcriptionCompletedWithXLines": "Transkripsiyon {0} satır ile tamamlandı", "openSubtitleFileX": "\"{0}\" alt yazı dosyası açılsın mı?", "replacedXOccurrences": "{0} eşleşme değiştirildi", "ffmpegDownloadedAndInstalledToX": "ffmpeg indirildi ve {0} konumuna kuruldu", "nothingToSave": "Kaydedilecek bir şey yok", - "formatLimitWarningTitle": "Biçim sınırları aşıldı", + "formatLimitWarningTitle": "Biçim sınırları aşıldı", "formatLimitWarningXLinesExceedLimitsOfY": "{0} alt yazı \"{1}\" biçiminin sınırlarını aşıyor:", "formatLimitWarningMaxXCharactersPerLine": "Satır başına en fazla {0} karakter", "formatLimitWarningMaxXLines": "Alt yazı başına en fazla {0} satır", @@ -1233,16 +1233,16 @@ "liveSpellCheckLanguageXLoaded": "Canlı yazım denetimi dili {0} yüklendi", "downloadFfmpegTitle": "FFmpeg indirilsin mi?", "downloadFfmpegQuestion": "Çevrim içi videoları oynatmak ve bazı video düzenleme özellikleri için FFmpeg gereklidir.\n\nŞimdi FFmpeg indirilsin mi?", - "ffmpegNotFoundInstallHint": "FFmpeg bulunamadı.\n\nLütfen PATH değişkeninde erişilebilir olacak şekilde FFmpeg kurun (ör. paket yöneticiniz aracılığıyla) veya FFmpeg yolunu Seçenekler -> Ayarlar menüsünden belirleyin.", + "ffmpegNotFoundInstallHint": "FFmpeg bulunamadı.\n\nLütfen PATH değişkeninde erişilebilir olacak şekilde FFmpeg kurun (ör. paket yöneticiniz aracılığıyla) veya FFmpeg yolunu Seçenekler -> Ayarlar menüsünden belirleyin.", "selectCurrentSubtitleWhilePlayingOn": "Oynatırken geçerli alt yazıyı seç: AÇIK", "selectCurrentSubtitleWhilePlayingOff": "Oynatırken geçerli alt yazıyı seç: KAPALI", - "setUpLikeSe4Question": "Bu işlem Subtitle Edit 4 kısayollarını ve değiştirme kurallarını içe aktaracak; Subtitle Edit 4 temasını, araç çubuğunu ve ses dalgası görünümünü uygulayacaktır.\n\nDevam etmek istiyor musunuz?", - "setUpLikeSe4ImportedSettingsX": "Subtitle Edit 4 sürümünden içe aktarılan ayarlar:\n{0}", - "setUpLikeSe4NoSettingsXmlFound": "Subtitle Edit 4 Settings.xml dosyası bulunamadı - Subtitle Edit 4 varsayılan değerleri uygulandı.", - "setUpLikeSe4ShortcutsAddedX": "Eklenen/güncellenen kısayollar: {0}", - "setUpLikeSe4ShortcutsSkippedX": "Atlanan kısayollar (eşleşme yok): {0}", - "setUpLikeSe4ReplaceRulesAddedXY": "Eklenen değiştirme kuralları: {0} ({1} yeni kategori içinde)", - "setUpLikeSe4ThemeSet": "Tema, araç çubuğu simgeleri ve ses dalgası Subtitle Edit 4 görünümüne ayarlandı." + "setUpLikeSe4Question": "Bu işlem Subtitle Edit 4 kısayollarını ve değiştirme kurallarını içe aktaracak; Subtitle Edit 4 temasını, araç çubuğunu ve ses dalgası görünümünü uygulayacaktır.\n\nDevam etmek istiyor musunuz?", + "setUpLikeSe4ImportedSettingsX": "Subtitle Edit 4 sürümünden içe aktarılan ayarlar:\n{0}", + "setUpLikeSe4NoSettingsXmlFound": "Subtitle Edit 4 Settings.xml dosyası bulunamadı - Subtitle Edit 4 varsayılan değerleri uygulandı.", + "setUpLikeSe4ShortcutsAddedX": "Eklenen/güncellenen kısayollar: {0}", + "setUpLikeSe4ShortcutsSkippedX": "Atlanan kısayollar (eşleşme yok): {0}", + "setUpLikeSe4ReplaceRulesAddedXY": "Eklenen değiştirme kuralları: {0} ({1} yeni kategori içinde)", + "setUpLikeSe4ThemeSet": "Tema, araç çubuğu simgeleri ve ses dalgası Subtitle Edit 4 görünümüne ayarlandı." }, "file": { "ebuSaveOptions": { @@ -1275,19 +1275,19 @@ "teletext": "Teletekst", "useBox": "Metin etrafında kutu kullan", "doubleHeight": "Metin için çift yükseklik kullan", - "videoPreview": "Video ön izleme", - "previewFont": "Özel yazı tipi", - "previewFontDefault": "(video ön izleme yazı tipini kullan)", - "previewFontSample": "Pijamalı hasta yağız şoföre çabucak güvendi - 0123456789", + "videoPreview": "Video ön izleme", + "previewFont": "Özel yazı tipi", + "previewFontDefault": "(video ön izleme yazı tipini kullan)", + "previewFontSample": "Pijamalı hasta yağız şoföre çabucak güvendi - 0123456789", "errors": "Hatalar", "errorsX": "Hatalar: {0}", "maxLengthError": "Satır {0}, maksimum uzunluğu ({1}) {2} karakter aşıyor: {3}", - "maxLengthErrorTeletextColored": "Satır {0}-{1}: Çift yükseklikteki renkli metin için en fazla karakter sayısı 36 ({2} değil) olmalıdır", + "maxLengthErrorTeletextColored": "Satır {0}-{1}: Çift yükseklikteki renkli metin için en fazla karakter sayısı 36 ({2} değil) olmalıdır", "maxLengthErrorTeletextWhite": "Satır {0}-{1}: Çift yükseklikteki beyaz metin için en fazla karakter sayısı 37 ({2} değil) olmalıdır", "maxLengthErrorTeletextSingleHeight": "Satır {0}: Tek yükseklikteki beyaz metin için en fazla karakter sayısı 38 ({1} değil) olmalıdır", "discSequenceNumber": "Disk sıra numarası" }, - "import": { + "import": { "importTimeCodes": "Zaman kodlarını içe aktar...", "plainTextDotDotDot": "Düz metin...", "titleImportPlainText": "Düz metin içe aktar", @@ -1306,7 +1306,7 @@ "formattingDotDotDot": "Biçimlendiriliyor...", "imageBasedSubtitleForEditDotDotDot": "Düzenleme için resim tabanlı alt yazı...", "imageBasedSubtitleForOcrDotDotDot": "OCR için resim tabanlı alt yazı...", - "dvdSubtitlesDotDotDot": "DVD'den alt yazılar (IFO/VOB)...", + "dvdSubtitlesDotDotDot": "DVD'den alt yazılar (IFO/VOB)...", "titleImportDvd": "DVD'den alt yazıları içe aktar", "dvdIfoFile": "IFO dosyası", "dvdVobFiles": "VOB dosyaları", @@ -1331,21 +1331,22 @@ "mergeShortLines": "Kısa satırları birleştir", "fixed": "Sabit", "numberOfSubtitlesX": "Alt yazı sayısı: {0}", - "numberOfSubtitlesXForcedY": "Alt yazı sayısı: {0} ({1} zorunlu)", + "numberOfSubtitlesXForcedY": "Alt yazı sayısı: {0} ({1} zorunlu)", "gapMs": "Boşluk (ms)", "useFixedDuration": "Sabit süre kullan", "fixedDurationMs": "Sabit süre (ms)", - "alignViaSpeechToText": "Zaman kodlarını \"Sesten metne\" ile hizala...", + "alignViaSpeechToText": "Zaman kodlarını \"Sesten metne\" ile hizala...", "alignViaForcedAligner": "Zaman kodlarını zorunlu hizalayıcı ile hizala...", "forcedAlignerProgress": "Hizalanıyor... pencere {0} / {1}, {2} / {3} satır", "forcedAlignerSetupTitle": "Zaman kodlarını zorunlu hizalayıcı ile hizala", "forcedAlignerSetupIntro": "Zaman kodlarını zorunlu hizalayıcı, önceden deşifre etmeden elinizdeki alt yazı metnini doğrudan sesle eşleştirir. Uzun videolar parçalar hâlinde hizalandığı için her uzunluktaki dosyada çalışır.", - "forcedAlignerEndsFromSpeech": "Bitiş zamanlarını ayrıştırılmış konuşmaya göre ayarla (yavaş)", + "forcedAlignerEndsFromSpeech": "Bitiş zamanlarını ayrıştırılmış konuşmaya göre ayarla (yavaş)", "forcedAlignerEndsFromSpeechHint": "Zorlamalı hizalayıcı bir satırın nerede başladığını bulur, nerede bittiğini değil; bu nedenle bitiş zamanları normalde okuma süresinden alınır. Bu seçenek açık olduğunda, ses dosyasındaki müzik ve ses efektleri önceden temizlenir ve her satır konuşmanın sessizleştiği yerde biter. Ekran kartı (GPU) ile yaklaşık video süresi kadar sürer - GPU olmadan ise çok daha uzun zaman alır.", "forcedAlignerIsolatingSpeech": "Konuşma ayrıştırılıyor... {0}", "forcedAlignerIsolatingSpeechFailed": "Konuşma ayrıştırılamadı - bitiş zamanları bunun yerine okuma süresine göre ayarlandı.", "forcedAlignerModel": "Hizalayıcı modeli", - "forcedAlignerDownloadEngine": "Motoru indir / güncelle..." + "forcedAlignerDownloadEngine": "Motoru indir / güncelle...", + "forcedAlignerEstimatedXOfY": "{1} satırdan {0} tanesi hizalanamadı ve okuma süresine göre yerleştirildi - zaman kodları yalnızca tahminidir. Metin büyük olasılıkla videoda konuşulandan daha uzun." }, "export": { "exportImagesProfiles": "Resim dışa aktarma profilleri", @@ -1355,9 +1356,9 @@ "paddingLeftRight": "Sol/sağ iç boşluk", "paddingTopBottom": "Üst/alt iç boşluk", "previewTitle": "Ön izleme - mevcut boyut: {0}x{1}, hedef boyut: {2}x{3}, yakınlaştırma: %{4}", - "titleExportDvdSup": "DVD sup alt yazısını dışa aktar (MuxMan/Scenarist)", + "titleExportDvdSup": "DVD sup alt yazısını dışa aktar (MuxMan/Scenarist)", "titleExportVobSub": "VobSub (sub/idx)", - "titleExportImscImage": "IMSC 1.1 görüntü profili", + "titleExportImscImage": "IMSC 1.1 görüntü profili", "titleExportDostPng": "DOST/png", "titleExportFcpImage": "Final Cut Pro + görüntü", "titleExportWebVttThumbnails": "WebVTT png", @@ -1367,9 +1368,9 @@ "exportCavenaTranslator": "Çevirmen", "exportCavenaComment": "Yorum", "exportCavenaStartOfProgramme": "Program başlangıcı", - "titleExportDvbTeletext": "DVB teletext (Manzanita)", + "titleExportDvbTeletext": "DVB teletext (Manzanita)", "exportDvbTeletextPageNumber": "Teletext sayfa numarası", - "exportDvbTeletextSubtitleType": "Alt yazı türü", + "exportDvbTeletextSubtitleType": "Alt yazı türü", "exportDvbTeletextSubtitleTypeNormal": "Normal", "exportDvbTeletextSubtitleTypeHearingImpaired": "İşitme engelli", "customTextFormatsDotDotDot": "_Özel metin formatları...", @@ -1392,9 +1393,9 @@ "titleExportDCinemaInteropPng": "D-Cinema interop/png", "titleExportDCinemaSmpte2014Png": "D-Cinema SMPTE 2014/png", "imageBasedSubtitleSaved": "Resim tabanlı alt yazı kaydedildi", - "fullFrameImage": "Tam kare görsel", + "fullFrameImage": "Tam kare görsel", "fullFrameImageHint": "Her bir görseli, alt yazı kendi konumunda olacak şekilde video karesi boyutunda yap", - "stereo3D": "3D", + "stereo3D": "3D", "stereo3DHint": "3D video için: Alt yazıyı kare alanının her iki göz yarısına ayrı ayrı çizer", "halfSideBySide": "Yarım yan yana (Half SBS)", "halfTopBottom": "Yarım üst/alt (Half TAB)", @@ -1406,7 +1407,7 @@ "unableToLoadPlane3DX": "3D-Düzlem yüklenemedi: {0}", "plane3DXDepthYToZ": "{0} (derinlik {1} - {2})", "plane3DXNoDepth": "{0} (derinlik değeri yok)", - "textEffect": "Metin efekti", + "textEffect": "Metin efekti", "textEffectSettingsTitle": "Metin efekti ayarları", "textEffectPreset": "Ön ayar", "textEffectStrength": "Şiddet", @@ -1505,15 +1506,15 @@ }, "restoreAutoBackup": { "title": "Otomatik yedekten geri yükle", - "xBackups": "{0} yedek", + "xBackups": "{0} yedek", "openAutoBackupFolder": "Otomatik yedek klasörünü aç", "restoreAutoBackupFile": "Otomatik yedek dosyasını geri yükle", - "restoreXFromY": "\"{0}\" dosyasını {1} tarihinden geri yüklemek ister misiniz?", + "restoreXFromY": "\"{0}\" dosyasını {1} tarihinden geri yüklemek ister misiniz?", "deleteAllSubtitleBackups": "Tüm alt yazı yedek dosyalarını silmek istiyor musunuz?", "deleteAll": "Tümünü sil", - "subtitles": "Alt yazılar", + "subtitles": "Alt yazılar", "settingsBackupInfo": "Settings.json dosyası Subtitle Edit başladığında, en fazla {0} günde bir ve yalnızca ayarlar değişmişse yedeklenir. En yeni {1} yedek saklanır.", - "settingsBackupInfoEveryStart": "Settings.json dosyası ayarlar değişmişse Subtitle Edit her başladığında yedeklenir. En yeni {0} yedek saklanır.", + "settingsBackupInfoEveryStart": "Settings.json dosyası ayarlar değişmişse Subtitle Edit her başladığında yedeklenir. En yeni {0} yedek saklanır.", "settingsBackupOff": "Otomatik ayar yedekleme kapalı (Seçenekler → Ayarlar → Dosya).", "lastSettingsBackupX": "Son yedekleme: {0}", "noSettingsBackupsYet": "Henüz ayar yedeği yok", @@ -1524,7 +1525,7 @@ "restoreSettingsFromX": "Mevcut ayarlarınızı {0} tarihli yedekle değiştirmek istiyor musunuz?\n\nÖncelikle mevcut ayarlarınız yedeklenecektir. Bazı değişiklikler yalnızca Subtitle Edit yeniden başlatıldıktan sonra geçerli olur.", "settingsRestored": "Ayarlar geri yüklendi. Tüm değişikliklerin geçerli olması için lütfen Subtitle Edit yazılımını yeniden başlatın.", "settingsRestoreFailed": "Ayarlar geri yüklenemedi: {0}", - "settingsBackupNotValid": "Yedek geçerli bir ayar dosyası olmadığından mevcut ayarlarınız korundu", + "settingsBackupNotValid": "Yedek geçerli bir ayar dosyası olmadığından mevcut ayarlarınız korundu", "deleteAllSettingsBackups": "Tüm ayar yedekleme dosyalarını silmek istiyor musunuz?" }, "propertiesDCinema": { @@ -1547,12 +1548,12 @@ "topBottomMargin": "Üst/alt kenar boşluğu", "fadeUpTime": "Belirme süresi", "fadeDownTime": "Kaybolma süresi", - "zPosition": "Z konumu", + "zPosition": "Z konumu", "zPositionHelp": "Pozitif sayılar metni uzaklaştırır, negatif sayılar metni yakınlaştırır, Z konumu sıfır ise metin 2B olur" }, - "webVtt": { + "webVtt": { "setStylesForSelectedLinesTitle": "Seçilen satırlar için WebVTT stillerini ayarla", - "voice": "Ses", + "voice": "Ses", "showVoiceColumn": "\"Ses\" sütununu göster", "voices": "Sesler", "newVoiceDotDotDot": "Yeni ses...", @@ -1564,7 +1565,7 @@ "openStyleFileTitle": "Stillerin içe aktarılacağı WebVTT dosyasını aç", "noStylesToImport": "Seçilen dosyada stil bulunamadı", "duplicateStyleNamesX": "Yinelenen stil adları: {0}", - "Settings": "Hizalama için satır ayarları", + "cueSettings": "Hizalama için satır ayarları", "cueSettingsHint": "Bir satır hizalandığında yazılan satır ayarları, örn. üst-orta için \"line:20%\"", "useXTimeStamp": "X-TIMESTAMP-MAP üst bilgi değerini kullan", "mergeLines": "Yükleme sırasında aynı metne sahip satırları birleştir", @@ -1582,11 +1583,11 @@ "compareTextOrTimeDifference": "Metin/zaman farkı", "compareNumberDifference": "Numara farkı", "ignoreWhitespace": "Boşlukları yok say", - "ignoreWhitespaceHint": "Yalnızca boşluk, sekme veya satır sonu bakımından farklılık gösteren satırlar farklı kabul edilmez", + "ignoreWhitespaceHint": "Yalnızca boşluk, sekme veya satır sonu bakımından farklılık gösteren satırlar farklı kabul edilmez", "ignoreFormatting": "Biçimlendirmeyi yok say", - "ignoreFormattingHint": "Yalnızca veya {\\an8} gibi biçimlendirme etiketleri bakımından farklılık gösteren satırlar farklı kabul edilmez", + "ignoreFormattingHint": "Yalnızca veya {\\an8} gibi biçimlendirme etiketleri bakımından farklılık gösteren satırlar farklı kabul edilmez", "showOnlyDifferencesInText": "Sadece metindeki farklar", - "ignoreNumbering": "Numaralandırmayı yok say", + "ignoreNumbering": "Numaralandırmayı yok say", "ignoreNumberingHint": "Yalnızca numarası farklı olan satırlar farklı kabul edilmez", "ignoreTimeDifferenceMs": "Zaman toleransı (ms)", "ignoreTimeDifferenceMsHint": "Başlangıç ve bitiş zamanları bu kadar milisaniye veya daha az farklı olan satırlar farklı kabul edilmez", @@ -1632,28 +1633,28 @@ "compareChangeSyncShiftXYZ": "#{0}-#{1} {2} kaydırıldı (eşitleme noktası)", "compareTimingDeltaHint": "Referansın mevcut satırdan ne kadar sonra (+) veya daha önce (−) başlayıp bittiğini gösterir", "loadXFromFile": "\"{0}\" dosyasından yükle", - "compareLoadSavedFile": "Kaydedilen dosyayı yükle", + "compareLoadSavedFile": "Kaydedilen dosyayı yükle", "saveCompareHtmlTitle": "Karşılaştırma HTML dosyasını kaydet", "pickMatroskaTrackX": "Matroska izi seç - {0}", - "pickTransportStreamTrackX": "Taşıma akışı izini seç - {0}", - "chapters": "Bölümler", + "pickTransportStreamTrackX": "Taşıma akışı izini seç - {0}", + "chapters": "Bölümler", "dvdVobFilesMissingX": "VOB dosyaları eksik (%{0} bulundu)", - "pickMp4TrackX": "MP4 izi seç - {0}", - "pickMxfTrackX": "MXF izini seç - {0}", + "pickMp4TrackX": "MP4 izi seç - {0}", + "pickMxfTrackX": "MXF izini seç - {0}", "pickMpegTrackX": "MPEG izini seç - {0}", "rosettaProperties": "Timed Text Rosetta IMSC özellikleri", "rosettaFontSize": "Yazı tipi boyutu (satır yüksekliği)", - "propertyTimeBase": "Zaman tabanı", - "propertyFrameRateMultiplier": "Kare hızı çarpanı", - "propertyDropMode": "Kare atlama modu", - "propertyDefaultStyle": "Varsayılan stil", - "propertyDefaultRegion": "Varsayılan bölge", - "propertyStyleAttributeName": "Stil özniteliği adı", - "propertyTimeCodeFormat": "Zaman kodu biçimi", - "propertyTopOrigin": "Üst başlangıç noktası", - "propertyTopExtent": "Üst genişleme alanı", - "propertyBottomOrigin": "Alt başlangıç noktası", - "propertyBottomExtent": "Alt genişleme alanı", + "propertyTimeBase": "Zaman tabanı", + "propertyFrameRateMultiplier": "Kare hızı çarpanı", + "propertyDropMode": "Kare atlama modu", + "propertyDefaultStyle": "Varsayılan stil", + "propertyDefaultRegion": "Varsayılan bölge", + "propertyStyleAttributeName": "Stil özniteliği adı", + "propertyTimeCodeFormat": "Zaman kodu biçimi", + "propertyTopOrigin": "Üst başlangıç noktası", + "propertyTopExtent": "Üst genişleme alanı", + "propertyBottomOrigin": "Alt başlangıç noktası", + "propertyBottomExtent": "Alt genişleme alanı", "xProperties": "{0} özellikleri" }, "edit": { @@ -1679,10 +1680,10 @@ "exactlyTwoLines": "Tam olarak iki satır", "moreThanTwoLines": "İkiden fazla satır", "bookmarked": "Yer imli", - "forced": "Zorunlu", + "forced": "Zorunlu", "bookmarkContains": "Yer imi içeriği", "blankLines": "Boş satırlar", - "hearingImpaired": "İşitme engelli (SDH)", + "hearingImpaired": "İşitme engelli (SDH)", "hearingImpairedBrackets": "Köşeli parantezler [...]", "hearingImpairedCurlyBrackets": "Süslü parantezler {...}", "hearingImpairedParentheses": "Parantezler (...)", @@ -1705,7 +1706,7 @@ "newCategory": "Yeni kategori", "categoryName": "Kategori adı", "exportReplaceRules": "Kuralları dışa aktar", - "importReplaceRules": "Kuralları içe aktar", + "importReplaceRules": "Kuralları içe aktar", "noCategoriesSelected": "Hiçbir kural kategorisi seçilmedi", "appliedRules": "Uygulanan kurallar", "findRule": "Kural bul", @@ -1714,9 +1715,9 @@ "deleteRuleConfirm": "'{0}' kuralı silinsin mi?", "findWhat": "Aranan", "descriptionOptional": "Açıklama (isteğe bağlı)", - "invalidRegularExpressionX": "Geçersiz düzenli ifade: {0}", - "regularExpressionTooSlowX": "Regular expression gave up after {0} seconds - the rule is skipped, try a simpler pattern", - "moveToCategory": "Kategoriye taşı" + "invalidRegularExpressionX": "Geçersiz düzenli ifade: {0}", + "regularExpressionTooSlowX": "Regular expression gave up after {0} seconds - the rule is skipped, try a simpler pattern", + "moveToCategory": "Kategoriye taşı" }, "find": { "searchTextWatermark": "Aranacak metin...", @@ -1726,14 +1727,14 @@ "replaceAndFindNext": "_Değiştir ve sonrakini bul", "replaceAll": "_Tümünü değiştir", "replaceTextWatermark": "Şununla değiştir...", - "replaceIn": "Şurada değiştir/ara", + "replaceIn": "Şurada değiştir/ara", "replaceInTextAndOriginal": "Metin ve orijinal metin", "replaceInTextOnly": "Yalnızca metin", "replaceInOriginalOnly": "Yalnızca orijinal metin", - "replacedOneOccurrence": "1 eşleşme değiştirildi", + "replacedOneOccurrence": "1 eşleşme değiştirildi", "replacedXOccurrences": "{0} eşleşme değiştirildi" }, - "regularExpressionContextMenu": { + "regularExpressionContextMenu": { "wordBoundary": "Kelime sınırı (\\b)", "nonWordBoundary": "Kelime sınırı olmayan (\\B)", "newLine": "Yeni satır (\\r\\n)", @@ -1750,7 +1751,7 @@ }, "showHistory": "Geri alma geçmişi", "restoreSelected": "Seçilenleri geri yükle", - "noSubtitleSelected": "Hiçbir alt yazı seçilmedi", + "noSubtitleSelected": "Hiçbir alt yazı seçilmedi", "pleaseSelectExactlyOneSubtitle": "Lütfen yalnızca bir alt yazı seçin." }, "sourceView": { @@ -1759,11 +1760,11 @@ "xLinesYSubtitles": "{0} satır, {1} alt yazı", "xLinesYSubtitlesZErrors": "{0} satır, {1} alt yazı, {2} hata", "couldNotParseAsX": "{0} olarak ayrıştırılamadı", - "readAsXUseAsY": "Kaynak {1} olarak okunamadı fakat {0} olarak okunabildi.\nSatırlar kullanılsın ve {1} olarak kaydedilmeye devam edilsin mi?", + "readAsXUseAsY": "Kaynak {1} olarak okunamadı fakat {0} olarak okunabildi.\nSatırlar kullanılsın ve {1} olarak kaydedilmeye devam edilsin mi?", "selectedXCharactersYLines": "Seçilen: {1} satırda {0} karakter", "replacedXOccurrences": "{0} eşleşme değiştirildi", "invalidRegularExpression": "Geçersiz düzenli ifade", - "regularExpressionTooSlowX": "Düzenli ifade {0} saniye sonra zaman aşımına uğradı - daha basit bir desen deneyin", + "regularExpressionTooSlowX": "Düzenli ifade {0} saniye sonra zaman aşımına uğradı - daha basit bir desen deneyin", "moveLineUp": "Satırı yukarı taşı", "moveLineDown": "Satırı aşağı taşı", "duplicateLine": "Satırı çoğalt", @@ -1771,33 +1772,33 @@ "closeSearch": "Aramayı kapat" }, "tools": { - "aiReview": { - "title": "Yapay zekâ incelemesi", - "review": "İncele", - "editPrompt": "Komut istemini düzenle...", - "editPromptTitle": "İnceleme komut istemini düzenle", - "promptInfo": "Modele gönderilen talimatlar - {language} ifadesi alt yazı dili ile değiştirilir.", - "protocolInfo": "Subtitle Edit, komut istemine katı bir JSON protokolü ekler: Satırlar numaralandırılmış JSON olarak gönderilir ve model yalnızca değiştirilen satırları içeren geçerli bir JSON ile yanıt vermelidir. Protokolü bozan yanıtlar yeniden denenir, ardından atlanır.", - "resetToDefault": "Varsayılana sıfırla", - "applyXFixes": "{0} düzeltmeyi uygula", - "xSuggestionsYSelected": "{0} öneri - {1} seçildi", - "reviewingLineXOfY": "Satır {0} / {1} inceleniyor...", - "reviewDone": "İnceleme tamamlandı - {1} satırda {0} öneri", - "noIssuesFound": "Hiçbir sorun bulunamadı", - "categorySpelling": "Yazım", - "categoryGrammar": "Dil Bilgisi", - "categoryPunctuation": "Noktalama", - "categoryCasing": "Büyük/Küçük Harf", - "categoryOther": "Diğer", - "xNeedACloserLook": "{0} önerinin daha yakından incelenmesi gerekiyor", - "lineX": "Satır {0}", - "linesXToY": "Satır {0}-{1}", - "largeChangeWarning": "Büyük değişiklik - yeniden yazılmış gibi görünüyor, dikkatlice inceleyin", - "mismatchWarning": "Orijinaline benzemiyor - farklı bir satıra ait olabilir", - "engineError": "Yapay zekâ motoruna ulaşılamadı: {0}", - "playCurrentLineHint": "Seçili satırı video oynatıcıda oynatır ve sonunda duraklatır (ayrıca bir satıra çift tıklayın)", - "editAfterHint": "Uygulamadan önce düzeltmeyi düzenlemek için seçili satırın metnine tıklayın (Enter kaydeder, Escape iptal eder)", - "template": "Şablon", + "aiReview": { + "title": "Yapay zekâ incelemesi", + "review": "İncele", + "editPrompt": "Komut istemini düzenle...", + "editPromptTitle": "İnceleme komut istemini düzenle", + "promptInfo": "Modele gönderilen talimatlar - {language} ifadesi alt yazı dili ile değiştirilir.", + "protocolInfo": "Subtitle Edit, komut istemine katı bir JSON protokolü ekler: Satırlar numaralandırılmış JSON olarak gönderilir ve model yalnızca değiştirilen satırları içeren geçerli bir JSON ile yanıt vermelidir. Protokolü bozan yanıtlar yeniden denenir, ardından atlanır.", + "resetToDefault": "Varsayılana sıfırla", + "applyXFixes": "{0} düzeltmeyi uygula", + "xSuggestionsYSelected": "{0} öneri - {1} seçildi", + "reviewingLineXOfY": "Satır {0} / {1} inceleniyor...", + "reviewDone": "İnceleme tamamlandı - {1} satırda {0} öneri", + "noIssuesFound": "Hiçbir sorun bulunamadı", + "categorySpelling": "Yazım", + "categoryGrammar": "Dil Bilgisi", + "categoryPunctuation": "Noktalama", + "categoryCasing": "Büyük/Küçük Harf", + "categoryOther": "Diğer", + "xNeedACloserLook": "{0} önerinin daha yakından incelenmesi gerekiyor", + "lineX": "Satır {0}", + "linesXToY": "Satır {0}-{1}", + "largeChangeWarning": "Büyük değişiklik - yeniden yazılmış gibi görünüyor, dikkatlice inceleyin", + "mismatchWarning": "Orijinaline benzemiyor - farklı bir satıra ait olabilir", + "engineError": "Yapay zekâ motoruna ulaşılamadı: {0}", + "playCurrentLineHint": "Seçili satırı video oynatıcıda oynatır ve sonunda duraklatır (ayrıca bir satıra çift tıklayın)", + "editAfterHint": "Uygulamadan önce düzeltmeyi düzenlemek için seçili satırın metnine tıklayın (Enter kaydeder, Escape iptal eder)", + "template": "Şablon", "templatePlaceholder": "Komut isteminin yerini alacak bir şablon seçin", "templateStandard": "Standart düzelti", "templateSpeechToText": "Sesten metne dönüştürme temizliği", @@ -1826,14 +1827,14 @@ "result": "Öneri", "apply": "Satıra uygula", "hint": "Bu satır için yapay zekâ asistanı", - "showReasoning": "Modelin akıl yürütmesini göster" + "showReasoning": "Modelin akıl yürütmesini göster" }, "fixCommonErrors": { "back": "< &Geri", "next": "&İleri >", "fixes": "Düzeltmeler", "removedEmptyLine": "Boş satırı kaldır", - "ruleDetailsDotDotDot": "Kural ayrıntıları...", + "ruleDetailsDotDotDot": "Kural ayrıntıları...", "showOnlyThisRule": "Yalnızca bu kuralın düzeltmelerini göster", "appliedRulesForLineX": "{0}. satır için uygulanan kural", "ruleX": "Kural: {0}", @@ -1845,7 +1846,7 @@ "fixShortDisplayTimes": "Kısa görüntüleme zamanlarını düzelt", "fixLongDisplayTimes": "Uzun görüntüleme zamanlarını düzelt", "fixShortGaps": "Kısa boşlukları düzelt", - "fixShortGap": "Kısa boşluğu düzelt", + "fixShortGap": "Kısa boşluğu düzelt", "fixInvalidItalicTags": "Geçersiz italik etiketlerini düzelt", "removeUnneededSpaces": "Gereksiz boşlukları kaldır", "removeUnneededPeriods": "Gereksiz noktaları kaldır", @@ -1857,7 +1858,7 @@ "removeLineBreaksPixelWidth": "Tek satıra sığabilecek alt yazıları birleştir (piksel genişliği)", "fixUppercaseIInsideLowercaseWords": "Küçük harfli kelimelerin içindeki büyük 'I' harflerini düzelt (OCR hatası)", "fixDoubleApostrophes": "Çift kesme işaretlerini ('') tek tırnağa (\") dönüştür", - "fixMisreadQuotes": "Çift tırnak olarak yanlış okunan kesme işaretlerini düzelt (OCR, İngilizce)", + "fixMisreadQuotes": "Çift tırnak olarak yanlış okunan kesme işaretlerini düzelt (OCR, İngilizce)", "addPeriods": "Sonraki satırın büyük harfle başladığı satırların sonuna nokta ekle", "startWithUppercaseLetterAfterParagraph": "Paragraftan sonra büyük harfle başla", "startWithUppercaseLetterAfterPeriodInsideParagraph": "Paragraf içindeki noktadan sonra büyük harfle başla", @@ -1880,7 +1881,7 @@ "fixCommasExample": ",, -> ,", "removeLineBreaksExample": "Merhaba
dünya! -> Merhaba dünya!", "fixDoubleApostrophesExample": "''Çift tekli tırnak var'' -> \"Tek bir çift tırnak var\"", - "fixMisreadQuotesExample": "\"Hello' -> \"Hello\"", + "fixMisreadQuotesExample": "\"Hello' -> \"Hello\"", "addPeriodsExample": "Merhaba dünya -> Merhaba dünya.", "startWithUppercaseLetterAfterParagraphExample": "p1: Merhaba! || p2: merhaba! -> p1: Merhaba! || p2: Merhaba!", "startWithUppercaseLetterAfterPeriodInsideParagraphExample": "Nasılsın? iyi misin? -> Nasılsın? İyi misin?", @@ -1891,14 +1892,14 @@ "fix3PlusLines": "İkiden fazla satırı olan alt yazıları düzelt", "analysing": "Analiz ediliyor...", "nothingToFix": "Düzeltilecek bir şey yok :)", - "log": "Günlük", + "log": "Günlük", "nothingFixableBut": "Otomatik olarak hiçbir şey düzeltilemedi. Alt yazı hatalar içeriyor - ayrıntılar için günlüğe bakın", "xFixedBut": "{0} sorun düzeltildi ancak alt yazı hâlâ hatalar içeriyor - ayrıntılar için günlüğe bakın", "xCouldBeFixedBut": "{0} sorun düzeltilebilir ancak alt yazı yine de hatalar içerecektir - ayrıntılar için günlüğe bakın", "numberOfImportantLogMessages": "{0} önemli günlük mesajı!", "fixedOkXY": "Düzeltildi ve Tamam - '{0}': {1}", "xFixesApplied": "Uygulanan düzeltmeler: {0}", - "xFixesYSelected": "{0} düzeltme - {1} seçildi", + "xFixesYSelected": "{0} düzeltme - {1} seçildi", "fixFirstLetterToUppercaseAfterParagraph": "Paragraftan sonraki ilk harfi büyük harf yap", "mergeShortLine": "Kısa satırı birleştir (tek cümle)", "mergeShortLineAll": "Kısa satırı birleştir (diyaloglar hariç tümü)", @@ -1963,16 +1964,16 @@ "note": "Not: Görüntüleme süresi sonraki metnin başlangıç zamanıyla çakışmayacaktır", "fixed": "Sabit", "extendOnly": "Sadece uzat", - "recalculateRequiresOcrNote": "Yeniden hesaplama kullanılamıyor — bir veya daha fazla alt yazıda OCR metni yok. Önce OCR işlemini çalıştırın." + "recalculateRequiresOcrNote": "Yeniden hesaplama kullanılamıyor — bir veya daha fazla alt yazıda OCR metni yok. Önce OCR işlemini çalıştırın." }, "applyDurationLimits": { "title": "Süre limitlerini uygula", "fixMinDurationMs": "Minimum süreyi düzelt (ms)", "doNotGoPastShotChange": "Sahne değişimini geçme", "fixMaxDurationMs": "Maksimum süreyi düzelt (ms)", - "fixMinDurationFrames": "En az süreyi düzelt (kare)", + "fixMinDurationFrames": "En az süreyi düzelt (kare)", "fixMaxDurationFrames": "En çok süreyi düzelt (kare)", - "minimumDurationMilliseconds": "En az süre (milisaniye):", + "minimumDurationMilliseconds": "En az süre (milisaniye):", "maximumDurationMilliseconds": "En çok süre (milisaniye):", "maxDurationShouldBeHigherThanMinDuration": "Maksimum süre minimum süreden daha yüksek olmalıdır", "changedDurationFromXToYCommentZ": "Süre {0} değerinden {1} değerine değiştirildi {2}", @@ -2009,11 +2010,11 @@ "previousChange": "Önceki değişiklik", "nextChange": "Sonraki değişiklik", "editProfile": "Profili düzenle...", - "batchSnapToShotChanges": "İşaretçileri sahne değişimlerine yapıştır (varsa)", + "batchSnapToShotChanges": "İşaretçileri sahne değişimlerine yapıştır (varsa)", "batchFrameRateFromVideo": "Alt yazı dosyasıyla aynı ada sahip video dosyasının kare hızını kullan", "batchFrameRateFixed": "Sabit kare hızı kullan", "batchInfo": "Sahne değişimleri, bulunması durumunda alt yazı dosyasıyla aynı ada sahip video dosyasından okunur.", - "useExactTimeCodes": "Videodan tam zaman kodlarını kullan", + "useExactTimeCodes": "Videodan tam zaman kodlarını kullan", "extractTimeCodes": "Zaman kodlarını çıkar", "extractingTimeCodes": "Zaman kodları çıkarılıyor...", "noTimeCodesLoaded": "Zaman kodu yüklenmedi", @@ -2021,7 +2022,7 @@ "timeCodesExtractFailed": "Videodan zaman kodları okunamadı", "timeCodesHint": "Sabit bir kare hızı varsaymak yerine işaretçileri videonun gerçek kare zamanlarına hizalayın. Bütün videonun bir kez okunmasını gerektirir; sonuç önbelleğe alınır." }, - "improveTimeCodes": { + "improveTimeCodes": { "title": "Zaman kodlarını iyileştir (zorlamalı hizalama)", "aligner": "Hizalayıcı", "alignerHint": "Sesteki her bir satırı dinleyen model. Alt yazının dili için en uygun seçenekler en başta listelenir.", @@ -2033,7 +2034,7 @@ "isolateSpeechHint": "Hizalamadan önce müzik ve ses efektlerini temizler, böylece hizalayıcı yalnızca diyalogları duyar. Yüksek sesli müzik veya aksiyon üzerindeki satırlarda yardımcı olur, ancak ekran kartı (GPU) ile yaklaşık ses süresi kadar sürer - GPU olmadan ise çok daha uzun zaman alır.", "isolatingSpeech": "Konuşma ayrıştırılıyor... (bu işlem biraz zaman alır)", "isolateSpeechFailed": "Konuşma ayrıştırılamadı - bunun yerine orijinal sese göre hizalandı.", - "checkWithSpeechToText": "Sesten metne dönüştürme ile denetle", + "checkWithSpeechToText": "Sesten metne dönüştürme ile denetle", "checkWithSpeechToTextHint": "Sesi de yazıya döker ve her hareketi sözcüklerin gerçekten duyulduğu konuma göre denetler. Konuşmanın doğruladığı büyük hareketler sizin için işaretlenir, bir satırın duyulduğu yerden uzaklaşan hareketler ise denetlemeniz için işaretlenmeden bırakılır. Ayrıca her satırın ne kadarının duyulduğunu gösterir; düşük bir oran, metnin söylenenlerden farklı olduğu anlamına gelir. Maksimum kaydırmadan daha fazla senkronizasyon dışı olan bir alt yazı (sabit bir kayma, kare hızı uyumsuzluğundan kaynaklanan sapma veya sahnelerin kesildiği yerlerdeki atlamalar) önce duyulan sözcüklere göre eşitlenir. GPU ile uzun bir sinema filmi için birkaç dakika süren Crisp ASR Parakeet sistemini kullanır.", "checkWithSpeechToTextNotAvailableX": "Sesten metne dönüştürme denetimi {0} için kullanılamıyor", "transcribingToCheck": "Hizalamayı denetlemek için yazıya dökülüyor...", @@ -2059,22 +2060,22 @@ "modelWillBeDownloaded": "{0} indirilecek", "summaryXRetimedYKeptZSkipped": "Yeniden zamanlandı: {0} · Korundu: {1} · Konuşma yok: {2}", "meanShiftX": "Ortalama kayma: {0} ms", - "toCheckX": "Kontrol edilecek: {0}", + "toCheckX": "Kontrol edilecek: {0}", "largeMovesToCheckX": "{0} satır kendi başına uzun bir mesafe kaydırılacak ve işaretlenmeden bırakıldı - bunları dinleyin (F5 hizalanmış satırı, Shift+F5 orijinal satırı çalar) ve doğru olanları işaretleyin.", - "linesToCheckX": "Denetlenmesi için {0} satır işaretlenmeden bırakıldı - bunları dinleyin (F5 hizalanmış satırı, Shift+F5 orijinal satırı çalar) ve doğru olanları işaretleyin.", + "linesToCheckX": "Denetlenmesi için {0} satır işaretlenmeden bırakıldı - bunları dinleyin (F5 hizalanmış satırı, Shift+F5 orijinal satırı çalar) ve doğru olanları işaretleyin.", "changeXOfY": "Değişiklik {0} / {1}", "previousChange": "Önceki değişiklik", "nextChange": "Sonraki değişiklik", "startShift": "Başlangıç", "endShift": "Bitiş", "statusRetimed": "Yeniden zamanlandı", - "statusMovedWithNeighbours": "Komşularıyla birlikte taşındı", + "statusMovedWithNeighbours": "Komşularıyla birlikte taşındı", "statusLargeMoveUnconfirmed": "Büyük kayma - dinleyin, uygulamak için işaretleyin", "statusUnchanged": "Zaten yerinde", "statusNoSpeech": "Konuşma yok", "statusShiftTooLarge": "Korundu - kayma çok büyük", "statusFailed": "Korundu - hizalayıcı başarısız oldu", - "statusAdjustedByHand": "Elle ayarlandı", + "statusAdjustedByHand": "Elle ayarlandı", "alignedWaveformHint": "Bir satırı taşımak için sürükleyin ya da başlangıcını veya bitişini değiştirmek için kenarlarından sürükleyin", "playLine": "Satırı oynat", "undoAdjustment": "Ayarlamayı geri al", @@ -2083,14 +2084,14 @@ "playAlignedHint": "Seçili satırı hizalanmış zaman kodlarıyla oynatır", "apply": "Uygula", "applyHint": "Orijinal zaman kodlarını korumak için bir satırın işaretini kaldırın. Oynatmak için bir satıra çift tıklayın. Satırlar ayrıca hizalanmış ses dalgasında taşınabilir veya yeniden boyutlandırılabilir." - }, + }, "beautifyTimeCodesProfile": { "title": "Zaman kodlarını optimize etme profilini düzenle", "loadPreset": "Hazır ayar yükle", "presetDefault": "Varsayılan", "presetNetflix": "Netflix", "presetSdi": "SDI", - "saveAsProfile": "Profil olarak kaydet...", + "saveAsProfile": "Profil olarak kaydet...", "deleteProfile": "Profili sil", "profileName": "Profil adı", "overwriteProfileX": "\"{0}\" adında bir profil zaten var. Üzerine yazılsın mı?", @@ -2125,7 +2126,7 @@ "bridgeGaps": { "bridgeGapsSmallerThan": "Şundan küçük boşlukları köprüle (ms)", "minGap": "Minimum boşluk (ms)", - "bridgeGapsSmallerThanFrames": "Şundan küçük boşlukları birleştir (kare):", + "bridgeGapsSmallerThanFrames": "Şundan küçük boşlukları birleştir (kare):", "minGapFrames": "En az boşluk (kare)", "calculateBridgeGapsSmallerThanDotDotDot": "Birleştirilecek boşluk sınırını kare hızından hesapla...", "useXMsAsBridgeGapsSmallerThan": "Şundan küçük boşluklar birleştirilsin mi: \"{0}\" ms?", @@ -2153,7 +2154,7 @@ "fileNameContainsDotDotDot": "Dosya adı şunları içerir...", "trackLanguageContainsDotDotDot": "İz dili şunları içerir...", "batchConvertSettings": "Toplu dönüştürme ayarları", - "assaSource": "ASSA kaynağı", + "assaSource": "ASSA kaynağı", "addFormatting": "Biçimlendirme ekle", "addItalic": "İtalik ekle", "addBold": "Kalın ekle", @@ -2162,7 +2163,7 @@ "addColor": "Renk ekle", "deleteLinesWithSpecificActorsOrStyles": "Belirli aktör veya stillere sahip satırları sil (virgülle ayırın)", "useSourceStylesIfPossible": "Mümkünse kaynak stillerini kullan", - "keepSourceEmbeddedFonts": "Kaynak gömülü yazı tiplerini koru (alt bilgi)", + "keepSourceEmbeddedFonts": "Kaynak gömülü yazı tiplerini koru (alt bilgi)", "editStyles": "Stilleri düzenle", "editProperties": "Özellikleri düzenle", "editAttachments": "Ekleri düzenle", @@ -2176,28 +2177,28 @@ "fontSizeBounceIn": "Yazı boyutu zıplayarak giriş", "assaChangeResolutionOnlyAppliesToAssa": "Yalnızca Advanced Sub Station Alpha (ASSA) alt yazılarına uygulanır", "assaChangeStyleTitle": "Stili Değiştir", - "assaEmbedFontsTitle": "Yazı tiplerini göm", + "assaEmbedFontsTitle": "Yazı tiplerini göm", "assaEmbedFontsInfo": "Alt yazının kullandığı yazı tipi dosyalarını (stiller ve satır içi yazı tipi etiketleri) çıktı dosyasının [Fonts] bölümüne gömer. Yazı tipleri Subtitle Edit yazı tipi klasöründe ve sistem yazı tipi klasörlerinde aranır; önceden gömülmüş yazı tipleri korunur. Yalnızca hedef biçim Advanced Sub Station Alpha olduğunda geçerlidir.", - "adjustImageColorsTitle": "Görsel parlaklığını/alfasını/rengini ayarla", + "adjustImageColorsTitle": "Görsel parlaklığını/alfasını/rengini ayarla", "adjustImageColorsInfo": "Yalnızca görsel tabanlı alt yazılar yine görsel tabanlı bir biçime dönüştürülürken uygulanır (ör. Blu-ray SUP dosyasından Blu-ray SUP dosyasına).", "assaChangeStyleFromStyle": "Şu stili değiştir:", "assaChangeStyleToStyle": "şununla:", "assaChangeStyleImportStyle": "Stil içe aktar...", "assaChangeStyleTrimUnusedStyles": "Kullanılmayan stilleri kırp", - "assaChangeStylePropertiesTitle": "Stil özelliklerini değiştir", + "assaChangeStylePropertiesTitle": "Stil özelliklerini değiştir", "assaChangeStylePropertiesInfo": "Stillerin kendilerine dokunmadan, dosyadaki tüm stillerde seçilen alanları değiştirir.", "assaChangeStylePropertiesSetSpacing": "Karakter aralığını ayarla:", "assaChangeStylePropertiesSetAlignment": "Hizalamayı ayarla:", - "convertColorsToDialogTitle": "Renkleri diyaloğa dönüştür", + "convertColorsToDialogTitle": "Renkleri diyaloğa dönüştür", "convertColorsToDialogRemoveColorTags": "Renk etiketlerini kaldır", "convertColorsToDialogAddNewLines": "Yeni satırlar ekle", "convertColorsToDialogReBreakLines": "Satırları yeniden böl", "snapTimeCodesToFramesInfo": "Her başlangıç ve bitiş zamanını en yakın kareye yuvarlar. Sabit bir kare hızı seçilmediği sürece kare hızı, alt yazı dosyasıyla aynı ada sahip video dosyasından okunur.", "addFolderDotDotDot": "Klasör ekle...", - "addFolderRecursiveDotDotDot": "Özyinelemeli klasör ekle...", + "addFolderRecursiveDotDotDot": "Özyinelemeli klasör ekle...", "selectFolderToConvert": "Dönüştürülecek dosyaları içeren klasörü seçin", "includeSubfolders": "Klasör eklerken alt klasörleri dahil et", - "videoFilesWhenAddingFolder": "Klasör eklerken video dosyaları (mkv, mp4, ts...)", + "videoFilesWhenAddingFolder": "Klasör eklerken video dosyaları (mkv, mp4, ts...)", "videoFilesAsk": "Sor", "videoFilesInclude": "Alt yazı izlerini ekle", "videoFilesSkip": "Atla", @@ -2209,23 +2210,23 @@ "settingsSectionAddingFiles": "Dosya ekleme", "settingsSectionImageBasedSubtitles": "Resim tabanlı alt yazılar (OCR)", "settingsSectionOther": "Diğer", - "keepSourceFileTimestamp": "Çıktı dosyalarında kaynak dosya tarihini/saatini koru", - "preventSleepWhileConverting": "Dönüştürme sırasında bilgisayarın uyku moduna geçmesini önle", + "keepSourceFileTimestamp": "Çıktı dosyalarında kaynak dosya tarihini/saatini koru", + "preventSleepWhileConverting": "Dönüştürme sırasında bilgisayarın uyku moduna geçmesini önle", "scanningFolderX": "{0} taranıyor...", - "transportStreamSettings": "Taşıma Akışı ayarları", - "transportStreamSettingsDotDotDot": "Taşıma Akışı ayarları...", - "transportStreamSettingsInfo": "Taşıma Akışı dosyalarından (.ts/.m2ts) çıkarılan alt yazı izlerine uygulanır. Konum ve video boyutu yalnızca görüntü tabanlı bir biçime dışa aktarılan DVB görüntü izlerini etkiler.", - "transportStreamOverrideXPosition": "Orijinal X konumunu geçersiz kıl", - "transportStreamOverrideYPosition": "Orijinal Y konumunu geçersiz kıl", - "transportStreamOverrideVideoSize": "Orijinal video boyutunu geçersiz kıl", - "transportStreamBottomMargin": "Alt kenar boşluğu", - "transportStreamFileNameEnding": "Dosya adı son eki", - "transportStreamFileNameEndingInfo": "Çıkarılan her iz için uzantıdan önce eklenir. Standart dil son ekini kullanmak için boş bırakın.", - "transportStreamOnlyTeletext": "Yalnızca teletekst", - "transportStreamGetSizeFromVideo": "Boyutu videodan al...", - "twoLetterLanguageCodeUppercase": "İki harfli dil kodu (büyük harf)", - "threeLetterLanguageCodeUppercase": "Üç harfli dil kodu (büyük harf)", - "formatContainsDotDotDot": "Biçim şunu içeriyor...", + "transportStreamSettings": "Taşıma Akışı ayarları", + "transportStreamSettingsDotDotDot": "Taşıma Akışı ayarları...", + "transportStreamSettingsInfo": "Taşıma Akışı dosyalarından (.ts/.m2ts) çıkarılan alt yazı izlerine uygulanır. Konum ve video boyutu yalnızca görüntü tabanlı bir biçime dışa aktarılan DVB görüntü izlerini etkiler.", + "transportStreamOverrideXPosition": "Orijinal X konumunu geçersiz kıl", + "transportStreamOverrideYPosition": "Orijinal Y konumunu geçersiz kıl", + "transportStreamOverrideVideoSize": "Orijinal video boyutunu geçersiz kıl", + "transportStreamBottomMargin": "Alt kenar boşluğu", + "transportStreamFileNameEnding": "Dosya adı son eki", + "transportStreamFileNameEndingInfo": "Çıkarılan her iz için uzantıdan önce eklenir. Standart dil son ekini kullanmak için boş bırakın.", + "transportStreamOnlyTeletext": "Yalnızca teletekst", + "transportStreamGetSizeFromVideo": "Boyutu videodan al...", + "twoLetterLanguageCodeUppercase": "İki harfli dil kodu (büyük harf)", + "threeLetterLanguageCodeUppercase": "Üç harfli dil kodu (büyük harf)", + "formatContainsDotDotDot": "Biçim şunu içeriyor...", "extensionIsDotDotDot": "Uzantı şudur...", "statusIsError": "Durum hata gösteriyor", "forcedTracksOnly": "Yalnızca zorunlu izler", @@ -2241,7 +2242,7 @@ }, "changeCasing": { "fixNames": "İsimleri düzelt", - "namesX": "İsimler: {0:#,##0}", + "namesX": "İsimler: {0:#,##0}", "hitsX": "Eşleşmeler: {0:#,##0}", "extraNames": "Ek isimler", "enterExtraNamesHint": "Düzeltilecek ek isimleri virgülle ayırarak girin", @@ -2267,19 +2268,19 @@ "addMsAfterEachFile": "Her dosyadan sonra milisaniye ekle", "join": "_Birleştir" }, - "mergeTwoSubtitles": { - "title": "İki alt yazıyı birleştir", - "subtitle1": "Alt yazı 1", - "subtitle2": "Alt yazı 2", - "loadFromCurrentText": "Mevcut olandan yükle (metin)", - "loadFromCurrentTranslation": "Mevcut olandan yükle (çeviri)", - "loadFromFile": "Dosyadan yükle...", - "outputFormat": "Çıkış formatı", - "outputFormatSubRip": "SubRip (.srt)", - "outputFormatAssa": "Advanced Sub Station Alpha (.ass)", - "style1": "Stil 1", - "style2": "Stil 2", - "merge": "_Birleştir" + "mergeTwoSubtitles": { + "title": "İki alt yazıyı birleştir", + "subtitle1": "Alt yazı 1", + "subtitle2": "Alt yazı 2", + "loadFromCurrentText": "Mevcut olandan yükle (metin)", + "loadFromCurrentTranslation": "Mevcut olandan yükle (çeviri)", + "loadFromFile": "Dosyadan yükle...", + "outputFormat": "Çıkış formatı", + "outputFormatSubRip": "SubRip (.srt)", + "outputFormatAssa": "Advanced Sub Station Alpha (.ass)", + "style1": "Stil 1", + "style2": "Stil 2", + "merge": "_Birleştir" }, "splitSubtitle": { "title": "Alt yazıyı böl", @@ -2291,13 +2292,13 @@ "title": "Uzun satırları böl/dengele", "splitLongLines": "Uzun satırları böl (birden fazla satıra)", "rebalanceLongLines": "Uzun satırları dengele", - "rebalanceOnlyLinesTooLong": "Yalnızca çok uzun satırı olan alt yazılar", + "rebalanceOnlyLinesTooLong": "Yalnızca çok uzun satırı olan alt yazılar", "rebalanceOnlyLinesTooLongHint": "Tüm satırlar zaten tek satır azami uzunluk sınırları dahilindeyse mevcut satır sonlarını değiştirmeyin - kasıtlı olarak dengelenmemiş bir alt yazı editoryal olarak doğru olabilir", "splitLongLine": "Uzun satırı böl", "rebalanceLongLine": "Uzun satırı dengele", "splitIntoXLines": "{0} satıra böl: '{1}' → '{2}...'", "linesSplitX": "Bölünen satırlar: {0}", - "linesSplitXLinesRebalancedY": "Bölünen satırlar: {0}, yeniden dengelenen satırlar: {1}", + "linesSplitXLinesRebalancedY": "Bölünen satırlar: {0}, yeniden dengelenen satırlar: {1}", "unbreakLinesShorterThanHint": "Bu değerden kısa metinler tek satırda kalır (veya tek satırda birleştirilir) - tek satıra sığabilecek her metnin bölünmeden kalmasını sağlamak için bu değeri tek satır en fazla uzunluğuna ayarlayın" }, "mergeShortLines": { @@ -2322,13 +2323,13 @@ }, "mergeLinesWithSameText": { "maxMsBetweenLines": "Satırlar arası maksimum milisaniye", - "maxFramesBetweenLines": "Satırlar arasındaki en çok kare sayısı", + "maxFramesBetweenLines": "Satırlar arasındaki en çok kare sayısı", "includeIncrementingLines": "Artan metne sahip satırları dahil et", - "includeRollUpCaptions": "Kayar (yukarı doğru kayan) alt yazıları dahil et" + "includeRollUpCaptions": "Kayar (yukarı doğru kayan) alt yazıları dahil et" }, "mergeLinesWithSameTimeCodes": { "maxMsDifference": "Maksimum fark (milisaniye)", - "maxFramesDifference": "En çok fark (kare)", + "maxFramesDifference": "En çok fark (kare)", "makeDialog": "Diyalog yap" }, "netflixCheckAndFix": { @@ -2353,7 +2354,7 @@ "textforHiUseBrackets": "İşitme engelli (HI) metni, köşeli parantez kullan", "twoFrameGrap": "İki kare boşluk", "whiteSpace": "Boşluk karakteri", - "childrensProgram": "Çocuk programı", + "childrensProgram": "Çocuk programı", "sdh": "SDH (işitme engelliler için)", "glyphCheckReport": "Sütun {1} üzerinde geçersiz karakter {0} bulundu", "whiteSpaceCheckForXReport": "Sütun {1} üzerinde boşluk karakteri sorunu ({0}) bulundu.", @@ -2398,7 +2399,7 @@ "adjustAlphaDotDotDot": "Alfayı ayarla...", "cropImages": "Resimleri kırp", "importTimeCodes": "Zaman kodlarını içe aktar...", - "setText": "Metni ayarla", + "setText": "Metni ayarla", "screenWidth": "Ekran genişliği", "screenHeight": "Ekran yüksekliği", "alphaThresholdInfo": "Alfa değeri eşiğin altındaki pikseller tam şeffaf olur", @@ -2412,23 +2413,23 @@ "resizeImages": "Resimleri yeniden boyutlandır", "percentage": "Yüzde", "resizeImagesInfo": "Resimleri yeniden boyutlandırmak için yüzdeyi girin.\nÖn izleme otomatik güncellenir.", - "originalSizeXNewSizeY": "Orijinal: {0} × {1} px\nYeni: {2} × {3} px", - "adjustColorDotDotDot": "Rengi ayarla...", - "adjustColor": "Rengi ayarla", - "colorAdjustmentInfo": "Renk seçmek için renk kutucuğuna tıklayın. Parlak alt yazı pikselleri seçilen renk tonuna kaydırılır; koyu dış hatlar ve gölgeler korunur.\nÖn izleme, seçilen ilk alt yazıyı gösterir.", - "topAlignLines": "Üste hizala", - "bottomAlignLines": "Alta hizala", - "appendSubtitleDotDotDot": "Alt yazı ekle...", - "shiftTimeCodes": "Zaman kodlarını şu kadar kaydır:", - "sortByStartTime": "Başlangıç zamanına göre sırala", - "videoPreview": "Video", + "originalSizeXNewSizeY": "Orijinal: {0} × {1} px\nYeni: {2} × {3} px", + "adjustColorDotDotDot": "Rengi ayarla...", + "adjustColor": "Rengi ayarla", + "colorAdjustmentInfo": "Renk seçmek için renk kutucuğuna tıklayın. Parlak alt yazı pikselleri seçilen renk tonuna kaydırılır; koyu dış hatlar ve gölgeler korunur.\nÖn izleme, seçilen ilk alt yazıyı gösterir.", + "topAlignLines": "Üste hizala", + "bottomAlignLines": "Alta hizala", + "appendSubtitleDotDotDot": "Alt yazı ekle...", + "shiftTimeCodes": "Zaman kodlarını şu kadar kaydır:", + "sortByStartTime": "Başlangıç zamanına göre sırala", + "videoPreview": "Video", "positionMap": "Konum haritası", "letterbox": "Sinematik şerit (Letterbox)", "letterboxOff": "Kapalı (sinematik şerit yok)", "letterboxCustom": "Özel şerit yüksekliği", "barHeightPx": "Şerit yüksekliği (px)", "titleSafePercent": "Güvenli alan oranı (%)", - "moveCaptions": "Alt yazıları taşı", + "moveCaptions": "Alt yazıları taşı", "moveCaptionsDotDotDot": "Alt yazıları taşı...", "moveCaptionsTo": "Alt yazıları taşı", "moveCaptionsIntoBars": "Letterbox siyah bantlarına (görüntünün dışına)", @@ -2443,8 +2444,8 @@ "xInTopBar": "Üst şeritte: {0}", "xInBottomBar": "Alt şeritte: {0}", "noImageSubtitlesLoaded": "Hiçbir görüntü tabanlı alt yazı yüklenmedi", - "imageBasedFormatNotSupported": "Görüntü tabanlı alt yazı biçimi bulunamadı/desteklenmiyor.", - "frameRateDeclaredInFile": "Blu-ray sup dosyasında bildirilen kare hızı", + "imageBasedFormatNotSupported": "Görüntü tabanlı alt yazı biçimi bulunamadı/desteklenmiyor.", + "frameRateDeclaredInFile": "Blu-ray sup dosyasında bildirilen kare hızı", "frameRateDetectedFromTimeCodesX": "Blu-ray sup zaman kodlarından algılanan kare hızı (dosya {0} bildiriyor)", "frameRateFromVideo": "Videonun kare hızı", "frameRateCurrent": "Mevcut kare hızı", @@ -2465,30 +2466,30 @@ "onlyIfTextIsUppercase": "Yalnızca metin büyük harfliyse", "onlyOnSeparateLine": "Yalnızca ayrı satırdaysa", "ifLineIsUppercase": "Satır büyük harfliyse", - "keepUppercaseWords": "Bu kelimeleri büyük harfli koru (virgülle ayrılmış)", + "keepUppercaseWords": "Bu kelimeleri büyük harfli koru (virgülle ayrılmış)", "ifLineContains": "Satır şunu içeriyorsa:", "ifLineOnlyContainsMusicSymbols": "Satır yalnızca müzik sembolleri içeriyorsa", "removeInterjections": "Ünlemleri/Tepki sözcüklerini kaldır", - "linesFoundX": "Bulunan satır sayısı: {0}" + "linesFoundX": "Bulunan satır sayısı: {0}" }, - "removeUnicodeCharacters": { + "removeUnicodeCharacters": { "title": "Unicode karakterleri kaldır/değiştir", "charactersFoundX": "Bulunan Unicode karakterleri: {0}", "noCharactersFound": "Unicode karakteri bulunamadı" }, "pickFontNameTitle": "Yazı tipi seçin", - "pickFontNameInstalledFonts": "Yüklü yazı tipleri", + "pickFontNameInstalledFonts": "Yüklü yazı tipleri", "pickFontNameCollectedFonts": "Toplanan yazı tipleri", "colorPickerTitle": "Renk seçin", - "colorPickerBrightness": "Parlaklık", + "colorPickerBrightness": "Parlaklık", "filterLayersTitle": "Görüntülenecek katmanları filtrele", "filterLayersHideFromWaveform": "Ses dalgası/spektrogram üzerinde gizle", "filterLayersHideFromSubtitleGrid": "Alt yazı ızgarasında gizle", "filterLayersHideFromVideoPreview": "Video ön izlemesinde gizle", "pickSubtitleFormat": "Alt yazı formatı seçin", - "pickSubtitleFormatImageBasedNoPreview": "Resim tabanlı alt yazı biçimi.\nDönüştürme sırasında bit eşlemler (bitmap) oluşturulur; metin ön izlemesi yoktur.", + "pickSubtitleFormatImageBasedNoPreview": "Resim tabanlı alt yazı biçimi.\nDönüştürme sırasında bit eşlemler (bitmap) oluşturulur; metin ön izlemesi yoktur.", "recentColors": "Son kullanılan renkler", - "fixCommonErrorsSettingsTitle": "Yaygın hataları düzelt ayarları" + "fixCommonErrorsSettingsTitle": "Yaygın hataları düzelt ayarları" }, "spellCheck": { "spellCheck": "Yazım denetimi", @@ -2504,7 +2505,7 @@ "xOfYNamesImported": "{1} isimden {0} tanesi içe aktarıldı (geri kalanlar zaten mevcut olabilir).", "noDictionariesFound": "Sözlük bulunamadı", "wordNotFound": "Kelime bulunamadı", - "loadSourceImage": "Kaynak resimleri de göster...", + "loadSourceImage": "Kaynak resimleri de göster...", "lineXofY": "Yazım denetimi - satır {0} / {1}", "changeWordFromXToY": "Kelimeyi '{0}' yerine '{1}' olarak değiştir", "changeAllWordsFromXToY": "Tüm '{0}' kelimelerini '{1}' olarak değiştir", @@ -2519,20 +2520,20 @@ "pickSpellCheckDictionaryDotDotDot": "Yazım denetimi sözlüğü seç...", "chooseSpellCheckDictionary": "Yazım denetimi sözlüğü seç", "pickLiveSpellCheckDictionary": "Canlı yazım denetimi sözlüğü seç", - "undoX": "Geri al: {0}", - "playCurrentLineHint": "Geçerli satırı video oynatıcıda oynatır ve sonunda duraklatır", - "editWholeText": "Tüm metni düzenle", - "editWholeTextTitle": "Yazım denetimi - Tüm metni düzenle", - "continueFromCurrentLine": "Yazım denetimine bulunulan satırdan mı devam edilsin?\n\nEvet = Bulunulan satırdan devam et\nHayır = Baştan başla", - "continueFromTop": "Yazım denetimi alt yazının sonuna ulaştı.\n\nBaştan devam edilsin mi?", - "spellCheckCompleted": "Yazım denetimi tamamlandı", - "changedWordsX": "Değiştirilen kelime sayısı: {0}", - "skippedWordsX": "Atlanan kelime sayısı: {0}", - "correctWordsX": "Doğru kelime sayısı: {0}", - "namesX": "Özel ad sayısı: {0}", - "addedToDictionaryX": "Sözlüğe eklenen sayı: {0}", - "doNotShowThisAgain": "Bu mesajı bir daha gösterme", - "editUseAlwaysList": "\"Her zaman kullan\" listesini düzenle...", + "undoX": "Geri al: {0}", + "playCurrentLineHint": "Geçerli satırı video oynatıcıda oynatır ve sonunda duraklatır", + "editWholeText": "Tüm metni düzenle", + "editWholeTextTitle": "Yazım denetimi - Tüm metni düzenle", + "continueFromCurrentLine": "Yazım denetimine bulunulan satırdan mı devam edilsin?\n\nEvet = Bulunulan satırdan devam et\nHayır = Baştan başla", + "continueFromTop": "Yazım denetimi alt yazının sonuna ulaştı.\n\nBaştan devam edilsin mi?", + "spellCheckCompleted": "Yazım denetimi tamamlandı", + "changedWordsX": "Değiştirilen kelime sayısı: {0}", + "skippedWordsX": "Atlanan kelime sayısı: {0}", + "correctWordsX": "Doğru kelime sayısı: {0}", + "namesX": "Özel ad sayısı: {0}", + "addedToDictionaryX": "Sözlüğe eklenen sayı: {0}", + "doNotShowThisAgain": "Bu mesajı bir daha gösterme", + "editUseAlwaysList": "\"Her zaman kullan\" listesini düzenle...", "useAlwaysListTitle": "Yazım denetimi - \"Her zaman kullan\" listesi", "useAlwaysListHeader": "\"Her zaman kullan\" listesi", "useAlwaysListDescription": "Yazım denetiminin otomatik olarak değiştirdiği, \"Tümünü değiştir\" ve \"Her zaman kullan\" ile kaydedilen sözcükler. Bir çift yalnızca sözcük yanlış yazıldığında kullanılır.", @@ -2552,13 +2553,13 @@ "title": "Gömülü alt yazılı video oluştur", "targetFileSize": "Hedef dosya boyutu (2 geçişli kodlama gerektirir)", "fileSizeMb": "MB cinsinden dosya boyutu", - "matchSourceVideoSize": "Kaynak video boyutuyla eşitle", + "matchSourceVideoSize": "Kaynak video boyutuyla eşitle", "bitRate": "Bit hızı", "totalBitRateX": "Toplam bit hızı: {0}", "sampleRate": "Örnekleme hızı", "audio": "Ses", "preset": "Hazır ayar", - "tune": "İnce ayar yap", + "tune": "İnce ayar yap", "pixelFormat": "Piksel formatı", "crf": "CRF", "useSource": "Kaynağı kullan", @@ -2571,7 +2572,7 @@ "videoFileSize": "Video dosya boyutu", "oneBox": "Tek kutu", "logoInfo": "Bir PNG resmi seçin ve video üzerindeki konumunu ayarlamak için sürükleyin.", - "noSubtitlesGenerateAnyway": "Gömülecek alt yazı satırı yok.\n\nVideo alt yazısız olarak oluşturulsun mu?", + "noSubtitlesGenerateAnyway": "Gömülecek alt yazı satırı yok.\n\nVideo alt yazısız olarak oluşturulsun mu?", "noSubtitlesInXItemsGenerateAnyway": "{0} toplu işlem ögesinde gömülecek alt yazı satırı yok.\n\nBu videolar alt yazısız olarak oluşturulsun mu?" }, "videoTransparent": { @@ -2583,19 +2584,19 @@ "translateToEnglish": "İngilizceye çevir", "transcribing": "Metne dökülüyor...", "transcribingXOfY": "Metne dökülüyor {0} / {1}...", - "linesTranscribedXOfYFailedZ": "{1} satırın {0} tanesi yazıya döküldü - {2} satır başarısız oldu veya konuşma içermiyordu.\n\nBaşarısız satırlar mevcut metinlerini korur. Yazıya dökülen satırlar uygulansın mı, yoksa başarısız olanlar yeniden mi denensin (önce motoru veya ayarları değiştirebilirsiniz)?", + "linesTranscribedXOfYFailedZ": "{1} satırın {0} tanesi yazıya döküldü - {2} satır başarısız oldu veya konuşma içermiyordu.\n\nBaşarısız satırlar mevcut metinlerini korur. Yazıya dökülen satırlar uygulansın mı, yoksa başarısız olanlar yeniden mi denensin (önce motoru veya ayarları değiştirebilirsiniz)?", "linesTranscribedXOfYCancelled": "Yazıya dökme işlemi {1} satırın {0} tanesinden sonra iptal edildi.\n\nYazıya dökülen satırlar uygulansın mı? Diğer satırlar mevcut metinlerini korur.", "noLinesTranscribed": "{0} satırın hiçbiri yazıya dökülemedi.\nAyrıntılar için lütfen araçlar günlüğünü denetleyin.", "applyTranscribedLines": "Yazıya dökülenleri uygula", "retryFailedLines": "Başarısızları yeniden dene", - "isolateSpeech": "Konuşmayı ayrıştır (yavaş)", + "isolateSpeech": "Konuşmayı ayrıştır (yavaş)", "isolateSpeechHint": "Döküm çıkarmadan önce müzik ve ses efektlerini temizler. Yüksek sesli müzik içeren seslerde daha az kaçırılan satır ve daha iyi zamanlama sağlar, ancak ekran kartı (GPU) ile yaklaşık ses süresi kadar sürer - GPU olmadan ise çok daha uzun zaman alır.", "isolatingSpeech": "Konuşma ayrıştırılıyor...", "isolateSpeechFailed": "Konuşma ayrıştırılamadı - bunun yerine orijinal sesin dökümü çıkarılıyor.", - "detectSpeakers": "Konuşmacıları algıla", + "detectSpeakers": "Konuşmacıları algıla", "detectSpeakersHint": "NVIDIA Sortformer konuşmacı ayrıştırma (diarization - 8 konuşmacıya kadar) özelliğini kullanarak her satırın başına \"(konuşmacı 0)\" gibi konuşmacıyı ekler. Parakeet ve Cohere gibi konuşmacının değiştiği yerlerde satırları bölen altyapılarla en iyi şekilde çalışır.", "detectSpeakersNeedsNewerCrispAsr": "\"Konuşmacıları algıla\" özelliği Crisp ASR v{0} veya üzerini gerektirir, ancak v{1} sürümü yüklü.\n\nCrisp ASR'yi güncelleyin (motorun yanındaki dişli çark düğmesi) veya \"Konuşmacıları algıla\" seçeneğini kapatın.", - "xNeedsNewerCrispAsr": "{0}, Crisp ASR v{1} veya daha yeni bir sürümü gerektiriyor ancak v{2} yüklü.\n\nGüncelleme şimdi indirilip yüklensin mi?", + "xNeedsNewerCrispAsr": "{0}, Crisp ASR v{1} veya daha yeni bir sürümü gerektiriyor ancak v{2} yüklü.\n\nGüncelleme şimdi indirilip yüklensin mi?", "vad": "VAD", "vadHint": "Ses etkinliği algılama: yalnızca konuşmalar yazıya dökülür, konuşmacının durakladığı yerlerde kesilir.\nOtomatik: Silero kullanılır ve yalnızca ihtiyaç duyan motorlar içindir (Cohere, Mega, Index-Echo).\nSilero, FireRedVAD, WebRTC: Her Crisp ASR motoru için her zaman açıktır. FireRedVAD en doğru olandır.\nIndex-Echo her zaman Silero kullanır. Gelişmiş ayarlardaki --vad, --vad-model veya --chunk-seconds komutları önceliklidir.", "inputLanguage": "Giriş dili", @@ -2609,8 +2610,8 @@ "whisperXxlSingleWords": "Tek kelimeler", "whisperXxlHighlightWord": "Kelime vurgula", "selectModel": "Model seç", - "addCustomModelDotDotDot": "Özel model ekle...", - "customModelHelp": "Kendi modelinizi kullanabilirsiniz: Model dosyasını (whisper.cpp ggml '.bin') veya model klasörünü (içinde 'model.bin' bulunan faster-whisper klasörü) seçin. Seçtiğiniz model, modeller klasörüne kopyalanacak ve yukarıdaki listeye eklenecektir.", + "addCustomModelDotDotDot": "Özel model ekle...", + "customModelHelp": "Kendi modelinizi kullanabilirsiniz: Model dosyasını (whisper.cpp ggml '.bin') veya model klasörünü (içinde 'model.bin' bulunan faster-whisper klasörü) seçin. Seçtiğiniz model, modeller klasörüne kopyalanacak ve yukarıdaki listeye eklenecektir.", "viewToolsLogFile": "Whisper günlüğünü görüntüle", "updateXTitle": "{0} Güncellensin mi?", "updateXMessage": "{0} yazılımının daha yeni bir sürümü mevcut.{1}{1}Güncelleme şimdi indirilip kurulsun mu?", @@ -2623,7 +2624,7 @@ "fixCasing": "Büyük/küçük harf kullanımını düzelt", "addPeriods": "Nokta ekle", "changeUnderlineToColor": "Alt çizgiyi renge dönüştür", - "removeNonSpeechLines": "Konuşma dışı satırları kaldır", + "removeNonSpeechLines": "Konuşma dışı satırları kaldır", "removeNonSpeechLinesHint": "\"\"[Müzik]\"\" veya \"\"(dalga sesleri)\"\" gibi yalnızca sesi tanımlayan satırları çıkarın", "removeRepeatedLines": "Yinelenen satırları kaldır", "removeRepeatedLinesHint": "Önceki satırı kelimesi kelimesine yineleyen satırları çıkarın (motor döngüleri)", @@ -2653,7 +2654,7 @@ "engineSettings": "Ses tanıma motoru ayarları", "engineSettingsSubtitle": "Ses tanıma motoru", "backendAndUpdateStatus": "Arka uç ve güncelleme durumu", - "addLanguageCodeToFileName": "Dosya adına dil kodu ekle", + "addLanguageCodeToFileName": "Dosya adına dil kodu ekle", "addLanguageCodeToFileNameHint": "Oluşturulan alt yazıları \"video.srt\" yerine \"video.en.srt\" şeklinde adlandırır" }, "textToSpeech": { @@ -2666,22 +2667,22 @@ "generateSpeechFromText": "Metinden ses oluştur", "testVoice": "Sesi test et", "addAudioToVideoFile": "Videoya ses ekle", - "engineAndVoice": "Motor ve ses", - "output": "Çıktı", - "xLinesFromY": "{1} satırdan {0} satır", - "xLines": "{0} satır", - "xVoices": "{0} ses", - "reviewAudioSegmentsHint": "Son karışımdan (mix) önce her satırı kontrol edin", - "addAudioToVideoFileHint": "Sonucu yeni bir video dosyasına göm (Mux)", - "xElapsedYLeft": "{0} geçen süre · ~{1} kaldı", - "xElapsed": "{0} geçen süre", + "engineAndVoice": "Motor ve ses", + "output": "Çıktı", + "xLinesFromY": "{1} satırdan {0} satır", + "xLines": "{0} satır", + "xVoices": "{0} ses", + "reviewAudioSegmentsHint": "Son karışımdan (mix) önce her satırı kontrol edin", + "addAudioToVideoFileHint": "Sonucu yeni bir video dosyasına göm (Mux)", + "xElapsedYLeft": "{0} geçen süre · ~{1} kaldı", + "xElapsed": "{0} geçen süre", "voiceSettings": "TTS - Ses ayarları", "voiceSampleText": "Ses örnek metni", "refreshVoices": "Sesleri yenile", "videoEncodingSettings": "TTS - Video kodlama ayarları", "elevenLabsSettings": "TTS - ElevenLabs ayarları", "elevenLabsSettingsResetHint": "ElevenLabs ayarlarını varsayılana sıfırla", - "provider": "Sağlayıcı", + "provider": "Sağlayıcı", "openAiCompatibleSettings": "TTS - OpenAI uyumlu ayarlar", "customServerUrl": "Özel sunucu URL'si", "customServerUrlHint": "Sağlayıcı \"Özel\" olduğunda kullanılan, OpenAI uyumlu bir sunucunun tam konuşma uç noktası (ör. http://localhost:8880/v1/audio/speech)", @@ -2692,17 +2693,17 @@ "instructions": "Talimatlar", "instructionsHint": "İsteğe bağlı konuşma biçemi, ör. \"Sakin ve sıcak bir ses tonuyla konuş\". Yalnızca bunu destekleyen modellere gönderilir (tts-1/tts-1-hd hariç)", "openAiSpeedHint": "0,25 ile 4,0 arasında konuşma hızı (1,0 normaldir). Her model bunu desteklemez", - "outputFormat": "Çıktı biçimi", + "outputFormat": "Çıktı biçimi", "outputFormatHint": "Sunucudan istenen ses biçimi. \"Otomatik\", MP3 ister ve yalnızca PCM döndüren modeller için (ör. Gemini TTS) PCM biçimine geçer", "regenerateAudio": "Sesi yeniden oluştur", "autoContinuePlaying": "Oynatmaya otomatik devam et", - "playLine": "Satırı oynat", + "playLine": "Satırı oynat", "fitDurationToGeneratedAudio": "Süreyi oluşturulan sese sığdır", "resetTiming": "Zamanlamayı sıfırla", "addingAudioToVideoFileDotDotDot": "Videoya ses ekleniyor...", "preparingMergeDotDotDot": "Birleştirme hazırlanıyor...", "importVoiceDotDotDot": "Ses içe aktar...", - "renameVoiceDotDotDot": "Sesi yeniden adlandır...", + "renameVoiceDotDotDot": "Sesi yeniden adlandır...", "renameVoiceTitle": "Sesi yeniden adlandır", "voiceXCouldNotBeRenamedX": "'{0}' sesi yeniden adlandırılamadı: {1}", "deleteVoiceDotDotDot": "Sesi sil...", @@ -2711,17 +2712,17 @@ "voiceXCouldNotBeDeletedX": "'{0}' sesi silinemedi: {1}", "voiceImportSuccessTitle": "Ses içe aktarıldı", "voiceXImported": "'{0}' sesi başarıyla içe aktarıldı", - "voiceXCouldNotBeImported": "'{0}' sesi içe aktarılamadı - ayrıntılar için günlüğe bakın", - "importPiperVoiceTitle": "Piper ses modelini aç (.onnx)", + "voiceXCouldNotBeImported": "'{0}' sesi içe aktarılamadı - ayrıntılar için günlüğe bakın", + "importPiperVoiceTitle": "Piper ses modelini aç (.onnx)", "piperVoiceConfigMissingTitle": "Yapılandırma dosyası eksik", "piperVoiceConfigMissingMessage": "Bir Piper ses modelinin yapılandırma dosyası, model dosyasının yanında bulunmalıdır: {0}", "dropAudioFileHereToImportVoice": "Ses içe aktarmak için ses dosyasını buraya bırakın", "dropAudioFileHereHint": ".wav veya .mp3", "voiceCloneTranscriptTitle": "Ses kaydının metnini (deşifresini) girin (ses klonlama için gereklidir)", - "voiceManagerTitle": "TTS - Ses yöneticisi", + "voiceManagerTitle": "TTS - Ses yöneticisi", "voiceManagerDotDotDot": "Ses yöneticisi...", "voiceManagerMenuItem": "TTS ses yöneticisi...", - "speakFromCurrentLineTitle": "Mevcut satırdan itibaren seslendir", + "speakFromCurrentLineTitle": "Mevcut satırdan itibaren seslendir", "speakFromCurrentLineDotDotDot": "Mevcut satırdan itibaren seslendir...", "speakFromCurrentLineHint": "Mevcut satırdan başlayarak satırları sırayla yüksek sesle okur. Durdurmak için Escape tuşuna basın veya başka bir satıra tıklayın.", "stopSpeaking": "Seslendirmeyi durdur", @@ -2762,7 +2763,7 @@ "installingXDotDotDot": "{0} kuruluyor...", "xVoicesInstalledYSkipped": "{0} ses kuruldu ({1} zaten mevcut)", "useSpeechToTextDotDotDot": "Ses tanımayı kullan...", - "autoCastMenuItem": "Videodaki sesleri bul ve klonla...", + "autoCastMenuItem": "Videodaki sesleri bul ve klonla...", "autoCastSpeakersTitle": "Videoda bulunan sesler", "autoCastSpeakersSubtitle": "Her konuşmacıyı adlandırın - bu ad, alt yazıda konuşmacı ve klonlanan sesin adı olur. İki konuşmacıyı tek bir seste birleştirmek için aynı adı verin.", "autoCastFoundXSpeakersInYLines": "{1} satırda {0} konuşmacı", @@ -2779,8 +2780,8 @@ "cloneVoicePerLinePreparing": "Her satırın sesi videodan alınıyor...", "cloneVoicePerLineNeedsVideo": "Her satırın sesini klonlamak için alt yazının ait olduğu video gereklidir. Videoyu açıp tekrar deneyin.", "cloneVoicePerLineNoClips": "Videodan hiçbir ses alınamadı, bu nedenle klonlanacak bir şey yok. Videonun bir ses izine sahip olduğunu kontrol edin.", - "cloneVoicePerLineTranscribeClipsXYZ": "{0}, yalnızca ilgili satırda videoda ne söylendiğini bildiğinde o satırın sesini klonlayabilir. {2} satırdan {1} tanesi için bu bilgi mevcut değil - bu bilgi normalde orijinal dildeki alt yazıdan alınır (Dosya > Orijinal aç...).\n\nBu satırların dökümü videodan metne dönüştürme (STT) ile şimdi çıkarılsın mı?\n\nEvet: Motoru ve videoda konuşulan dili seçin; ses üretilmeden önce referans kliplerin dökümü çıkarılır.\nHayır: Yine de üret - bu satırlar videodaki ses yerine sıradan bir sesle seslendirilir.", - "voiceCloneConsentTitle": "Ses klonlama - başlamadan önce", + "cloneVoicePerLineTranscribeClipsXYZ": "{0}, yalnızca ilgili satırda videoda ne söylendiğini bildiğinde o satırın sesini klonlayabilir. {2} satırdan {1} tanesi için bu bilgi mevcut değil - bu bilgi normalde orijinal dildeki alt yazıdan alınır (Dosya > Orijinal aç...).\n\nBu satırların dökümü videodan metne dönüştürme (STT) ile şimdi çıkarılsın mı?\n\nEvet: Motoru ve videoda konuşulan dili seçin; ses üretilmeden önce referans kliplerin dökümü çıkarılır.\nHayır: Yine de üret - bu satırlar videodaki ses yerine sıradan bir sesle seslendirilir.", + "voiceCloneConsentTitle": "Ses klonlama - başlamadan önce", "voiceCloneConsentHeader": "Bir sesi klonlamak üzeresiniz", "voiceCloneConsentIntro": "Klonlama, gerçek bir kişinin sesini kopyalar. Bu durum çoğu yerde kuralları ve AB'de Subtitle Edit'e değil size düşen yasal yükümlülükleri beraberinde getirir. Lütfen bunu bir kez okuyun.", "voiceCloneConsentPointPermission": "Yalnızca kullanma hakkına sahip olduğunuz bir sesi klonlayın - kendinizin veya konuşmacının izin verdiği bir sesi. Ses, kişisel bir veri ve kişilik hakkıdır; bu nedenle izin olmadan klonlamak hukuka aykırı olabilir.", @@ -2794,9 +2795,9 @@ "voiceCloneConsentAccept": "Kabul et ve devam et", "voiceCloneConsentDeclined": "Bu şartlar kabul edilene kadar ses klonlama kullanılamaz.", "advancedTtsSettings": "Gelişmiş TTS ayarları", - "advancedTtsAudioProcessing": "Ses işleme", - "advancedTtsOutput": "Çıktı", - "advancedTtsVoiceCloning": "Ses klonlama", + "advancedTtsAudioProcessing": "Ses işleme", + "advancedTtsOutput": "Çıktı", + "advancedTtsVoiceCloning": "Ses klonlama", "cleanCloneReferences": "Ses klonlama referanslarını temizle (Sidon)", "cleanCloneReferencesDescription": "Sesin klonlandığı sesteki müziği, gürültüyü ve çınlamayı (yankıyı) kaldırır: videodan alınan klipler ve ses yöneticisine aktarılan kayıtlar. audio.cpp üzerindeki Sidon konuşma onarım modelini kullanır (940 MB indirme). Klonlanan ses artık arka planı kopyalamaz, ancak çok kısık veya üst üste binen konuşmalar boğuk çıkabilir.", "cleaningCloneReferencesDotDotDot": "Ses klonlama referansları temizleniyor...", @@ -2804,32 +2805,36 @@ "downloadSidonQuestionX": "Ses klonlama referanslarını temizlemek için Sidon konuşma onarım modeli ({0}) gereklidir.\n\nŞimdi indirilsin mi? O olmadan referanslar olduğu gibi kullanılır.", "proAudioPostProcessing": "Profesyonel ses son işlemesi", "proAudioPostProcessingDescription": "Her klip için EQ, gürültü kapısı, sıkıştırma, ses yüksekliği normalleştirmesi (-16 LUFS) ve kısa bir baş/son solması ekler.", - "generatingSpeechSegmentXOfY": "Konuşma oluşturuluyor: {1} bölümün {0}. bölümü", + "generatingSpeechSegmentXOfY": "Konuşma oluşturuluyor: {1} bölümün {0}. bölümü", "adjustingSpeedSegmentXOfY": "Hız ayarlanıyor: {1} bölümün {0}. bölümü", "mergingAudioSegmentXOfY": "Ses birleştiriliyor: {1} bölümün {0}. bölümü", "postProcessingSegmentXOfY": "Son işleme: {1} bölümün {0}. bölümü", "audioDucking": "Ses kısma", "audioDuckingDescription": "Orijinal parçanın hafifçe duyulabilir kalması için orijinal video sesini kısıp konuşmayı üzerine karıştırır. Yalnızca konuşma video dosyasına eklendiğinde uygulanır.", - "removeOriginalSpeech": "Orijinal konuşmayı kaldır (yavaş)", + "removeOriginalSpeech": "Orijinal konuşmayı kaldır (yavaş)", "removeOriginalSpeechDescription": "Orijinal videonun müzik ve ses efektlerini korur ancak konuşmasını kaldırır ve yeni konuşmayı bunun üzerine miksler. Ekran kartı (GPU) ile yaklaşık video süresi kadar sürer - GPU olmadan ise çok daha uzun zaman alır. Ses kısma (ducking) açık olduğunda, ses seviyesi müziğe ve efektlere uygulanır. Yalnızca konuşma video dosyasına eklendiğinde geçerlidir.", "removingOriginalSpeech": "Orijinal konuşma kaldırılıyor...", "removeOriginalSpeechFailed": "Orijinal konuşma kaldırılamadı - konuşma video dosyasına orijinal konuşma temizlenmeden eklendi.", - "videoTruncatedWarning": "Yeni video dosyasındaki görüntü {0} süresinden sonra duruyor, ancak orijinal video {1} uzunluğunda - ses devam ederken görüntü donacaktır.\n\nDosya tutuldu. ffmpeg çıktısı için Subtitle Edit veri klasöründeki error-log.txt dosyasına bakın.", + "speechRemovalModel": "Model", + "speechRemovalModelDescription": "En iyi kalite, müziğin ve ses efektlerinin çoğunu korur. Hızlı seçenek yaklaşık üç kat daha hızlıdır ancak müziğin biraz daha fazlası kaybolur.", + "speechRemovalBestQuality": "En iyi kalite (Mel-Band RoFormer)", + "speechRemovalFast": "Hızlı (HTDemucs)", + "videoTruncatedWarning": "Yeni video dosyasındaki görüntü {0} süresinden sonra duruyor, ancak orijinal video {1} uzunluğunda - ses devam ederken görüntü donacaktır.\n\nDosya tutuldu. ffmpeg çıktısı için Subtitle Edit veri klasöründeki error-log.txt dosyasına bakın.", "originalVolumePercent": "Orijinal ses düzeyi %", "vadSilenceCompression": "VAD sessizlik sıkıştırması", "vadSilenceCompressionDescription": "Bir klibin kalite kaybı olmadan sığması için konuşmayı hızlandırmak yerine kelimeler arasındaki duraklamaları kısaltır.", "maxSilenceMs": "Maksimum sessizlik (ms)", "highQualityTimeStretch": "Yüksek kaliteli zaman esnetme", - "highQualityTimeStretchDescription": "Varsayılana kıyasla daha doğal duyulan rubberband algoritması ile konuşmayı hızlandırır. FFmpeg içinde librubberband gerektirir; aksi takdirde varsayılan kullanılır.", - "rubberbandInstalled": "(yüklü)", - "rubberbandNotFound": "(FFmpeg içinde bulunamadı)", + "highQualityTimeStretchDescription": "Varsayılana kıyasla daha doğal duyulan rubberband algoritması ile konuşmayı hızlandırır. FFmpeg içinde librubberband gerektirir; aksi takdirde varsayılan kullanılır.", + "rubberbandInstalled": "(yüklü)", + "rubberbandNotFound": "(FFmpeg içinde bulunamadı)", "silencePaddingMs": "Sessizlik payı (ms)", "silencePaddingMsDescription": "Cümleler arasında boşluk bırakmak için her klibin sonuna fazladan sessizlik ekler.", - "outputSampleRate": "Çıktı örnekleme hızı", - "outputSampleRateDescription": "Her klibi yeniden örnekler (örneğin 44100 veya 48000). Orijinal hızı korumak için 0 kullanın.", + "outputSampleRate": "Çıktı örnekleme hızı", + "outputSampleRateDescription": "Her klibi yeniden örnekler (örneğin 44100 veya 48000). Orijinal hızı korumak için 0 kullanın.", "generationFolder": "Oluşturma klasörü", "generationFolderDescription": "Oluşturma sırasında kliplerin yazılacağı yer. Sistem geçici klasörü için boş bırakın. Her çalıştırma kendi alt klasörünü alır.", - "deleteTempFiles": "Kapatırken oluşturulan klipleri sil", + "deleteTempFiles": "Kapatırken oluşturulan klipleri sil", "deleteTempFilesDescription": "Metinden sese penceresi kapandığında oluşturma klasörünü siler. Kaydedilen ses ve video dosyaları korunur.", "edgeTtsRate": "Edge-TTS hızı", "edgeTtsRateDescription": "Konuşma hızı, örneğin \"+50%\" veya \"-30%\". Varsayılan için \"+0%\" kullanın.", @@ -2847,7 +2852,7 @@ "qwen3TtsSettings": "Qwen3 TTS ayarları", "kokoroTtsSettings": "Kokoro TTS ayarları", "chatterboxTtsSettings": "Chatterbox TTS ayarları", - "piperSettings": "Piper ayarları", + "piperSettings": "Piper ayarları", "voiceInstruction": "Ses tasarımı (Karakterizasyon)", "voiceInstructionHint": "İsteğe bağlı - örn. \"Sakin ve dost canlısı bir tonda konuş\"", "voiceGender": "Cinsiyet", @@ -2871,7 +2876,7 @@ "noWebVttVoicesFoundMessage": "WebVTT dosyasında hiçbir (ses) etiketi bulunamadı.", "mergeContinuationLinesPromptTitle": "Ardışık satırlar birleştirilsin mi?", "mergeContinuationLinesPromptMessage": "Bazı satırlar, birden fazla alt yazıya bölünmüş tek bir cümle gibi görünüyor.\n\nSes üretiminden önce bunları birleştirmek, TTS motorunun her bir düşünceyi tek bir nefes grubu olarak seslendirmesini sağlar ve bu da genellikle daha doğal duyulur.\n\nBirleştirmeleri şimdi gözden geçirip uygulamak ister misiniz?", - "skipNoiseLinesPromptTitle": "Ses ve müzik satırları atlansın mı?", + "skipNoiseLinesPromptTitle": "Ses ve müzik satırları atlansın mı?", "skipNoiseLinesPromptMessage": "Bazı satırlar yalnızca ses veya müzik içeriyor - ♪ veya [kapı çarpar] gibi.\n\nKonuşma motorları bu tür satırları yüksek sesle okumaya çalışır ve genellikle kelimeler uydurur.\n\nBu satırları gözden geçirip sessiz bırakmak ister misiniz?", "skipNoiseLinesTitle": "Yalnızca ses veya müzik içeren satırlar", "skipNoiseLinesFoundX": "{0} satır yalnızca ses veya müzik içeriyor - işaretlenen satırlar sessiz bırakılır", @@ -2882,9 +2887,9 @@ "detectSpeakersFoundX": "{0} konuşmacı etiketi bulundu ({1} konuşmacı) - işaretlenen adlar oyuncu olur ve yüksek sesle okunmaz", "detectSpeakersColumnUse": "Kullan", "detectSpeakersSticky": "Adı olmayan satırlar önceki konuşmacıyı devam ettirir", - "subtitleUpdatedFromReviewSingular": "Ses incelemesindeki değişikliklere göre bir satır güncellendi", - "subtitleUpdatedFromReviewPlural": "Ses incelemesindeki değişikliklere göre {0} satır güncellendi", - "subtitleMergedLinesAppliedSingular": "Metinden sese dönüşümdeki bir satır birleştirmesi uygulandı", + "subtitleUpdatedFromReviewSingular": "Ses incelemesindeki değişikliklere göre bir satır güncellendi", + "subtitleUpdatedFromReviewPlural": "Ses incelemesindeki değişikliklere göre {0} satır güncellendi", + "subtitleMergedLinesAppliedSingular": "Metinden sese dönüşümdeki bir satır birleştirmesi uygulandı", "subtitleMergedLinesAppliedPlural": "Metinden sese dönüşümdeki {0} satır birleştirmesi uygulandı" }, "shotChanges": { @@ -2898,10 +2903,10 @@ "shotChangesList": "Sahne değişimleri listesi", "shotChangesClearQuestion": "Tüm sahne değişimlerini temizlemek istediğinize emin misiniz?", "deleteSelectedShotChangeQuestion": "Seçili sahne değişimini silmek istediğinize emin misiniz?", - "shotChanges": "Çekim değişimleri", + "shotChanges": "Çekim değişimleri", "exportShotChanges": "Çekim değişimlerini dışa aktar" }, - "chapters": { + "chapters": { "title": "Bölümler", "chaptersDotDotDot": "Bölümler...", "chapters": "Bölümler", @@ -2940,7 +2945,7 @@ "chaptersSavedToX": "Bölümler şuraya kaydedildi: {0}", "emptyListCallToAction": "Henüz bölüm yok - video konumunda bir tane ekleyin veya bir videodan ya da dosyadan içe aktarın." }, - "videoOcr": { + "videoOcr": { "title": "Gömülü alt yazıyı OCR ile tara", "scanArea": "Tarama alanı", "scanAreaInfo": "Alt yazıların bulunduğu yeri seçmek için video karesi üzerinde sürükleyin", @@ -2962,18 +2967,18 @@ "runningOcrXY": "OCR çalıştırılıyor... {0}/{1}", "noLinesFoundTitle": "Alt yazı bulunamadı", "noLinesFoundMessage": "Tarama alanında metin bulunamadı; tarama alanını, motoru veya minimum parlaklığı ayarlamayı deneyin.", - "ocrStoppedEarlyMessage": "OCR motoru tüm kareler okunmadan önce durdu, bu nedenle şimdiye kadar bulunan satırlar korundu ve geri kalanı eksik.{0}{0}{1}", + "ocrStoppedEarlyMessage": "OCR motoru tüm kareler okunmadan önce durdu, bu nedenle şimdiye kadar bulunan satırlar korundu ve geri kalanı eksik.{0}{0}{1}", "linesFoundX": "{0} satır bulundu", - "refiningTimingXY": "Zamanlama iyileştiriliyor... {0}/{1}", - "fixOcrErrors": "OCR hatalarını düzelt", - "editLineX": "{0}. satırı düzenle", - "downloadDictionaryHint": "Yazım denetimi sözlüğü indir...", - "framesPerSecondHint": "Taranacak saniye başına kare sayısı. 5 değeri normal alt yazıları kapsar; daha yüksek değerler çok kısa satırları yakalar ve gruplamayı sıkılaştırır, ancak OCR süresini artırır.", - "textBrightnessMinimumHint": "Bu değerden daha koyu pikseller alt yazı metni olarak sayılmaz: Kare gruplaması parlak metni takip eder, parlak piksel içermeyen kareler atlanır ve Paddle motoru bu değerden koyu olan her şeyi karartarak kareleri okur. Koyu renkli alt yazı metinleri için 0 olarak ayarlayın.", - "textSimilarityPercentHint": "Ardışık iki OCR sonucunun aynı alt yazı olarak kabul edilip tek bir satırda birleştirilmesi için ne kadar benzer (yüzde olarak) olması gerektiği.", - "maxGapHint": "İki benzer OCR sonucunun hâlâ tek bir alt yazıda birleştirilmesini sağlayan en uzun duraklama süresi - tek karelik kırpmaların önüne geçer. Çok yüksek bir değer, üst üste iki kez gösterilen bir satırı birleştirebilir.", - "minDurationHint": "Ekranda bundan daha kısa süre kalan satırlar OCR hatası/paraziti olarak silinir.", - "dictionaryHint": "OCR düzeltme motoru ve kelime renklendirmesi tarafından kullanılan yazım denetimi sözlüğü: Bilinen kelimeler yeşil, bilinmeyen kelimeler kırmızı görünür.", + "refiningTimingXY": "Zamanlama iyileştiriliyor... {0}/{1}", + "fixOcrErrors": "OCR hatalarını düzelt", + "editLineX": "{0}. satırı düzenle", + "downloadDictionaryHint": "Yazım denetimi sözlüğü indir...", + "framesPerSecondHint": "Taranacak saniye başına kare sayısı. 5 değeri normal alt yazıları kapsar; daha yüksek değerler çok kısa satırları yakalar ve gruplamayı sıkılaştırır, ancak OCR süresini artırır.", + "textBrightnessMinimumHint": "Bu değerden daha koyu pikseller alt yazı metni olarak sayılmaz: Kare gruplaması parlak metni takip eder, parlak piksel içermeyen kareler atlanır ve Paddle motoru bu değerden koyu olan her şeyi karartarak kareleri okur. Koyu renkli alt yazı metinleri için 0 olarak ayarlayın.", + "textSimilarityPercentHint": "Ardışık iki OCR sonucunun aynı alt yazı olarak kabul edilip tek bir satırda birleştirilmesi için ne kadar benzer (yüzde olarak) olması gerektiği.", + "maxGapHint": "İki benzer OCR sonucunun hâlâ tek bir alt yazıda birleştirilmesini sağlayan en uzun duraklama süresi - tek karelik kırpmaların önüne geçer. Çok yüksek bir değer, üst üste iki kez gösterilen bir satırı birleştirebilir.", + "minDurationHint": "Ekranda bundan daha kısa süre kalan satırlar OCR hatası/paraziti olarak silinir.", + "dictionaryHint": "OCR düzeltme motoru ve kelime renklendirmesi tarafından kullanılan yazım denetimi sözlüğü: Bilinen kelimeler yeşil, bilinmeyen kelimeler kırmızı görünür.", "previewPosition": "Ön izleme konumu", "unableToReadVideoTitle": "Video okunamıyor", "unableToReadVideoMessage": "Şuradan video bilgisi okunamadı: {0}", @@ -2983,7 +2988,7 @@ "testOcrRunning": "Geçerli karede OCR test ediliyor...", "testOcrResultX": "Test sonucu: {0}", "testOcrNoTextFound": "Test: Tarama alanında metin bulunamadı", - "engineSettings": "OCR motoru ayarları", + "engineSettings": "OCR motoru ayarları", "engineBuiltIn": "İşletim sistemine yerleşik", "engineExternalServer": "Harici sunucu (Subtitle Edit dışında kurulur ve yönetilir)", "engineCloudApi": "Bulut API'si (API anahtarı gerektirir)", @@ -2991,7 +2996,7 @@ "engineModelsMissing": "Motor yüklendi, modeller eksik", "website": "Web sitesi" }, - "backgroundMusic": { + "backgroundMusic": { "title": "Arka plan müziği oluştur", "generateBackgroundMusicDotDotDot": "Arka plan müziği oluştur...", "preset": "Ön ayar", @@ -3036,7 +3041,7 @@ "mixingBackgroundMusicDotDotDot": "Arka plan müziği karıştırılıyor..." }, "goToVideoPosition": "Video konumuna git", - "goToVideoPositionDotDotDot": "Video konumuna git...", + "goToVideoPositionDotDotDot": "Video konumuna git...", "generateBlankVideoDotDotDot": "Boş video oluştur...", "generateBlankVideoTitle": "Boş video oluştur", "reEncodeVideoForBetterSubtitlingTitle": "Daha iyi alt yazı işlemi için videoyu yeniden kodla", @@ -3044,28 +3049,28 @@ "openSecondarySubtitleOnVideoPlayerDotDotDot": "İkinci alt yazı dosyasını aç...", "openSecondarySubtitleOnVideoPlayer": "İkinci alt yazı dosyası (video oynatıcıda)", "removeSecondarySubtitleOnVideoPlayer": "İkinci alt yazı dosyasını kaldır", - "editSecondarySubtitleSettings": "İkinci alt yazı ayarlarını düzenle", + "editSecondarySubtitleSettings": "İkinci alt yazı ayarlarını düzenle", "editSecondarySubtitleSettingsDotDotDot": "İkinci alt yazı ayarlarını düzenle...", - "secondarySubtitleRememberSettings": "Bu ayarları hatırla", + "secondarySubtitleRememberSettings": "Bu ayarları hatırla", "secondarySubtitleDoNotShowAgain": "Bu pencereyi bir daha gösterme", - "secondarySubtitleJustifyLines": "Satırları iki yana yasla", - "openRecentVideo": "Son kullanılan videoyu aç", + "secondarySubtitleJustifyLines": "Satırları iki yana yasla", + "openRecentVideo": "Son kullanılan videoyu aç", "clearRecentVideos": "Son kullanılan videoları temizle", "cutVideoTitle": "Videoyu kes", "cutVideoDotDotDot": "Videoyu kes...", - "remuxVideoTitle": "Videoyu remux yap", + "remuxVideoTitle": "Videoyu remux yap", "remuxVideoDotDotDot": "Videoyu remux yap...", "remuxVideoInputVideo": "Girdi videosu:", "remuxVideoMultipleTracksRequiresMkv": "Birden fazla ses veya alt yazı izi MKV kapsayıcısı gerektirir. Çıktı biçimi otomatik olarak .mkv olarak değiştirildi.", "remuxVideoOutputFormat": "Çıktı biçimi:", "remuxVideoOutputFile": "Çıktı dosyası:", "remuxVideoRemuxing": "Video ve ses remux ediliyor...", - "remuxVideoFinalizing": "Çıktı dosyası tamamlanıyor (dizin başa taşınıyor)...", + "remuxVideoFinalizing": "Çıktı dosyası tamamlanıyor (dizin başa taşınıyor)...", "remuxVideoCompleted": "Video başarıyla remux edildi.", "remuxVideoFailed": "Video remux edilemedi.", "remuxVideoPleaseSelectBoth": "Lütfen hem video hem de ses dosyalarını seçin.", "remuxVideoAssRequiresMkv": "ASS/SSA alt yazıları tüm biçimlendirme ve stilleri korumak için MKV kapsayıcısı gerektirir.", - "remuxVideoSccRequiresMov": "Scenarist (.scc) alt yazıları, MOV veya MPG kapsayıcısı gerektiren CEA-608 kapalı alt yazıları (CC) olarak gömülür. Çıktı biçimi otomatik olarak .mov olarak değiştirildi.", + "remuxVideoSccRequiresMov": "Scenarist (.scc) alt yazıları, MOV veya MPG kapsayıcısı gerektiren CEA-608 kapalı alt yazıları (CC) olarak gömülür. Çıktı biçimi otomatik olarak .mov olarak değiştirildi.", "remuxVideoSccNotWithWebM": "Scenarist (.scc) kapalı alt yazıları (CC), bir .webm dosyasının VP9/AV1 videosunu barındıramayan MOV kapsayıcısına ihtiyaç duyar. .scc dosyasını çıkarın veya başka bir alt yazı biçimine dönüştürün.", "remuxVideoMpgMaxTwoCaptionTracks": "Bir .mpg dosyası en fazla iki kapalı alt yazı izi (CC1 ve CC3) barındırabilir - geriye bir veya iki tane kalacak şekilde alt yazı dosyalarını çıkarın.", "remuxVideoMccRequiresMpg": "MacCaption (.mcc) alt yazıları, MPEG-2 videosunda MPG kapsayıcısı gerektiren CEA-608/708 kapalı alt yazıları (CC) olarak gömülür. Çıktı biçimi otomatik olarak .mpg olarak değiştirildi.", @@ -3081,10 +3086,10 @@ "remuxVideoSubtitleFiles": "Alt yazı dosyaları (isteğe bağlı, gömülü alt yazılar):", "remuxVideoSelectAudioTrackDotDotDot": "Ses izini seç...", "remuxVideoFilesX": "{0} dosya", - "remuxVideoMixAudio": "Sesleri tek bir izde birleştir", + "remuxVideoMixAudio": "Sesleri tek bir izde birleştir", "remuxVideoVolumePercent": "Ses düzeyi %:", "remuxVideoVolumeX": "Ses düzeyi %{0}", - "remuxVideoFastStart": "Web akışı için optimize et (dizini başa taşı - daha yavaş)", + "remuxVideoFastStart": "Web akışı için optimize et (dizini başa taşı - daha yavaş)", "embedSubtitlesDotDotDot": "Gömülü alt yazıları ekle/kaldır...", "generateTimeCodes": "Zaman kodları oluştur", "checkeredImage": "Damalı resim", @@ -3092,9 +3097,9 @@ "cutVideoCutSegments": "Segmentleri kes", "cutVideoMergeSegments": "Segmentleri birleştir", "cutVideoSplitSegments": "Segmentleri ayrı ayrı kaydet", - "cutVideoAlsoCutSubtitle": "Alt yazıyı da kes", + "cutVideoAlsoCutSubtitle": "Alt yazıyı da kes", "cutVideoSubtitleFileGeneratedX": "Alt yazı dosyası oluşturuldu: {0}", - "cutVideoTransition": "Bölümler arası geçiş", + "cutVideoTransition": "Bölümler arası geçiş", "cutVideoTransitionDurationSeconds": "Süre (saniye)", "cutVideoFadeInSeconds": "Başlangıçta siyahtan belirme (saniye)", "cutVideoFadeOutSeconds": "Sonda siyaha kararma (saniye)", @@ -3137,7 +3142,7 @@ "embeddedTrackGeneratingVideoXY": "Video oluşturuluyor... {0}% {1}", "embeddedTrackGeneratingVideo": "Video oluşturuluyor...", "mp4FilesFilter": "MP4 dosyaları", - "embeddedTracksPickVideoHint": "Alt yazı izlerini görmek için bir video dosyası seçin veya buraya bırakın.", + "embeddedTracksPickVideoHint": "Alt yazı izlerini görmek için bir video dosyası seçin veya buraya bırakın.", "viewMatroskaTrackX": "Matroska izini görüntüle - {0}", "resolutionSeparator": "x", "openFromUrlTitle": "Bağlantıdan (URL) video dosyası aç", @@ -3148,15 +3153,15 @@ "openFromUrlDownloadAndOpenDescription": "Videoyu yerel olarak kaydeder ve ardından açar.", "openFromUrlDownloadAndOpenNote": "Ses tanıma ve ses dalgası grafikleri için bu mod gereklidir.", "openFromUrlDownloadingTitle": "Video indiriliyor", - "openFromUrlDownloadSubtitles": "Alt yazıları indir", + "openFromUrlDownloadSubtitles": "Alt yazıları indir", "openFromUrlDownloadVideo": "Videoyu indir", "openFromUrlIncludeAutoGeneratedSubtitles": "Otomatik oluşturulan alt yazıları dahil et (orijinal dil)", - "assaStyleWillBeUsed": "Mevcut ASSA stili kullanılacak\n\nStilleri buradan ayarlamak istiyorsanız\nalt yazı biçimini değiştirin", + "assaStyleWillBeUsed": "Mevcut ASSA stili kullanılacak\n\nStilleri buradan ayarlamak istiyorsanız\nalt yazı biçimini değiştirin", "pickOnlineSubtitleTitle": "İndirilecek alt yazıyı seçin", - "outputSettingsTitle": "Çıktı ayarları", + "outputSettingsTitle": "Çıktı ayarları", "ttsCrispAsrSettingsTitle": "{0} (CrispASR) ayarları", "ttsDownloadEngineTitle": "TTS - Motoru indir", - "indexTts25LicenseTitle": "IndexTTS 2.5 - model lisansı", + "indexTts25LicenseTitle": "IndexTTS 2.5 - model lisansı", "indexTts25LicenseHeader": "IndexTTS 2.5 modeli kendi lisansına sahiptir", "indexTts25LicenseIntro": "audio.cpp motoru Apache-2.0 lisanslıdır, ancak IndexTTS 2.5 model ağırlıkları bilibili tarafından açık kaynak olmayan şartlar altında lisanslanmıştır. Lütfen indirmeden önce ana maddeleri okuyun.", "indexTts25LicenseReadFullText": "Lisansın tamamını oku", @@ -3169,10 +3174,10 @@ "indexTts25EmotionStrength": "Duygu yoğunluğu", "pickOnlineSubtitleFetching": "Alt yazılar indiriliyor...", "pickOnlineSubtitleNoneFound": "Bu bağlantı için alt yazı bulunamadı.", - "forcedAligner": "Zorunlu hizalayıcı", + "forcedAligner": "Zorunlu hizalayıcı", "postProcessing": "Son işleme...", "toggleCurrentSubtitleWhilePlaying": "Oynatma sırasında mevcut alt yazıyı aç/kapat", - "toggleSubtitlesOnVideoPlayer": "Video oynatıcıda alt yazıları aç/kapat", + "toggleSubtitlesOnVideoPlayer": "Video oynatıcıda alt yazıları aç/kapat", "subtitlesOnVideoPlayerOn": "Video oynatıcıda alt yazılar: Açık", "subtitlesOnVideoPlayerOff": "Video oynatıcıda alt yazılar: Kapalı", "onlyMkvCanSupportEmbeddedSubtitleEditing": "Gömülü alt yazıların düzenlenmesi yalnızca Matroska (.mkv, .webm) dosyalarında desteklenmektedir.", @@ -3184,11 +3189,11 @@ "reEncodeGeneratingDone": "Oluşturma işlemi tamamlandı", "reEncodeGeneratedFilesX": "Oluşturulan dosyalar ({0}):", "reEncodeFfmpegParameters": "ffmpeg parametreleri", - "analyzingVideoX": "Video analiz ediliyor... %{0} {1}", + "analyzingVideoX": "Video analiz ediliyor... %{0} {1}", "analyzingVideoXofY": "Video analiz ediliyor {0}/{1}... %{2} {3}", "backgroundImageFileNotSelected": "Arka plan görseli dosyası seçilmedi", "backgroundImageFileDoesNotExistX": "Arka plan görseli dosyası mevcut değil: {0}", - "voices": "Sesler", + "voices": "Sesler", "presets": "Ön ayarlar", "talkerX": "Konuşmacı {0}", "baseModel": "Temel model", @@ -3212,17 +3217,17 @@ "guessTimeCodesDotDotDot": "Zaman kodlarını tahmin et...", "seekSilenceDotDotDot": "Sessizlik ara...", "extractAudioDotDotDot": "Sesi dışa aktar...", - "cloneVoiceTo": "Sesi şuraya klonla", + "cloneVoiceTo": "Sesi şuraya klonla", "cloneVoiceNameTitle": "Yeni sesin adı", "cloneVoiceExtractFailed": "Seçili satır için ses çıkarılamadı.", - "lineXHasNoDuration": "{0}. satırın süresi yok (bitiş zamanı başlangıç zamanından sonra değil), bu nedenle çıkarılacak ses yok.", + "lineXHasNoDuration": "{0}. satırın süresi yok (bitiş zamanı başlangıç zamanından sonra değil), bu nedenle çıkarılacak ses yok.", "audioClipsSkippedLinesX": "Bu satırlar için ses çıkarılamadı, bu yüzden atlandı: {0}", "toggleShotChange": "Sahne değişimini aç/kapat", "resetWaveformZoomAndSpeed": "Ses dalgası yakınlaştırmasını ve hızını sıfırla", - "showOriginalSubtitle": "Orijinal alt yazıyı göster", - "timelineGroupTracksBy": "Alt yazı izlerini şuna göre grupla", + "showOriginalSubtitle": "Orijinal alt yazıyı göster", + "timelineGroupTracksBy": "Alt yazı izlerini şuna göre grupla", "timelineShowThumbnails": "Video küçük resimlerini göster", - "showSpeechOnly": "Yalnızca konuşmayı göster (oluşturması yavaş)", + "showSpeechOnly": "Yalnızca konuşmayı göster (oluşturması yavaş)", "isolatingSpeechForWaveformX": "Ses dalgası için konuşma ayrıştırılıyor... {0}", "isolatingSpeechForWaveformFailed": "Ses dalgası için konuşma ayrıştırılamadı - araçlar günlüğüne bakın", "speechOnlyWaveformReady": "Ses dalgası artık yalnızca konuşmayı gösteriyor", @@ -3258,23 +3263,23 @@ "adjustSelectedLinesAndForward": "Seçili satırları ve sonrasını ayarla", "toFrameRate": "Hedef kare hızı", "fromFrameRate": "Kaynak kare hızı", - "videoXFrameRateY": "{0} - {1} fps", + "videoXFrameRateY": "{0} - {1} fps", "adjustAllTimes": "Tüm zamanları ayarla (erken/geç göster)", "showEarlier": "Daha erken göster", "showLater": "Daha geç göster", "setSyncPoint": "Senkronizasyon noktası ayarla", - "setSyncPointViaVideo": "Video üzerinden senkronizasyon noktası ayarla...", + "setSyncPointViaVideo": "Video üzerinden senkronizasyon noktası ayarla...", "syncPointTimeCode": "Senkronizasyon noktası zaman kodu", "syncPoints": "Senkronizasyon noktaları", "pointSync": "Nokta senkronizasyonu", "pointSyncViaOther": "Diğer alt yazı üzerinden nokta senkronizasyonu", - "syncPointCandidateAfterInfo": "{0}+ saniye sessizlik içeren satırlar vurgulanır - bunlar genellikle güvenilir eşitleme noktaları oluşturur", - "gapAfter": "Sonraki boşluk", + "syncPointCandidateAfterInfo": "{0}+ saniye sessizlik içeren satırlar vurgulanır - bunlar genellikle güvenilir eşitleme noktaları oluşturur", + "gapAfter": "Sonraki boşluk", "adjustAllShortcuts": "Klavye kısayolları:\n\n• Shift + Sol/Sağ: 10 ms taşı\n• Ctrl + Sol/Sağ: 100 ms taşı\n• Alt + Sol/Sağ: 500 ms taşı", - "adjustAllShortcutsFrames": "Klavye kısayolları:\n\n• Shift + Sol/Sağ: 1 kare taşı\n• Ctrl + Sol/Sağ: 10 kare taşı\n• Alt + Sol/Sağ: 1 saniye taşı", + "adjustAllShortcutsFrames": "Klavye kısayolları:\n\n• Shift + Sol/Sağ: 1 kare taşı\n• Ctrl + Sol/Sağ: 10 kare taşı\n• Alt + Sol/Sağ: 1 saniye taşı", "offsetInSeconds": "Saniye cinsinden fark", "speedFactor": "Hız faktörü", - "showEarlierLaterDotDotDot": "Öncekileri/sonrakileri göster..." + "showEarlierLaterDotDotDot": "Öncekileri/sonrakileri göster..." }, "translate": { "translateViaCopyPaste": "Kopyala/yapıştır ile otomatik çeviri", @@ -3289,9 +3294,9 @@ "lineMerge": "Satır birleştirme", "delayInSecondsBetweenRequests": "İstekler arasındaki saniye cinsinden gecikme", "maxBytesPerRequest": "İstek başına maksimum bayt (byte)", - "keepMusicLinesUntranslated": "Müzik simgeleri içindeki satırları çevirme (♪ ... ♪)", + "keepMusicLinesUntranslated": "Müzik simgeleri içindeki satırları çevirme (♪ ... ♪)", "promptText": "Sistem yönergesi (Prompt)", - "resetPromptToDefault": "İstemi varsayılana sıfırla", + "resetPromptToDefault": "İstemi varsayılana sıfırla", "translateEachLineSeparately": "Her satırı ayrı ayrı çevir", "translationError": "Çeviri hatası", "translationFailedMessage": "{0} çevirisi başarısız oldu.", @@ -3303,7 +3308,7 @@ "readyToTranslate": "Çeviriye hazır", "translating": "Çevriliyor...", "translationComplete": "Çeviri tamamlandı", - "translateInPlaceNoOriginal": "Yerinde çevir (özgün metin oluşturulmaz)", + "translateInPlaceNoOriginal": "Yerinde çevir (özgün metin oluşturulmaz)", "translationCancelled": "Çeviri iptal edildi", "swapLanguages": "Kaynak ve hedef dilleri yer değiştir", "xIsAlreadyDownloadedReDownload": "{0} zaten indirilmiş durumda. Yeniden indirilsin mi?", @@ -3324,14 +3329,14 @@ "repeatPenalty": "Tekrar cezası (Repeat penalty)", "maxTokensPerReply": "Yanıt başına en fazla jeton (tokens)", "serverContextSizeTokens": "Sunucu bağlam boyutu (jeton)", - "extraServerParameters": "Ek sunucu parametreleri", + "extraServerParameters": "Ek sunucu parametreleri", "extraServerParametersHint": "Ek llama-server komut satırı bağımsız değişkenleri, örn. \"-ngl 30 --no-mmap\" - yerel sunucu başlatıldığında uygulanır", - "useOnlyExtraServerParameters": "Yalnızca bu parametreleri kullan", + "useOnlyExtraServerParameters": "Yalnızca bu parametreleri kullan", "useOnlyExtraServerParametersHint": "llama-server yazılımını Subtitle Edit'in kendi ince ayarları yerine yukarıdaki parametrelerle başlatın - yalnızca model, ana bilgisayar ve bağlantı noktası ayarlanmaya devam eder", "serverRunningAtX": "Sunucu şurada çalışıyor: {0}", "customPromptHint": "Özel talimatlar ({0} = kaynak dil, {1} = hedef dil); boş = yerleşik komut istemi", - "editTranslationHint": "Düzenlemek için seçili satırın çevirisine tıklayın (Enter kaydeder, Escape iptal eder)", - "llamaCppDownloadEngineAndModelPrompt": "llama.cpp, llama-server motorunun ve bir çeviri modelinin indirilmesini gerektiriyor. Şimdi indirilsin mi?", + "editTranslationHint": "Düzenlemek için seçili satırın çevirisine tıklayın (Enter kaydeder, Escape iptal eder)", + "llamaCppDownloadEngineAndModelPrompt": "llama.cpp, llama-server motorunun ve bir çeviri modelinin indirilmesini gerektiriyor. Şimdi indirilsin mi?", "llamaCppDownloadEnginePrompt": "llama.cpp, llama-server motorunun indirilmesini gerektiriyor. Şimdi indirilsin mi?", "llamaCppDownloadModelPrompt": "llama.cpp, seçilen çeviri modelinin indirilmesini gerektiriyor. Şimdi indirilsin mi?" }, @@ -3376,13 +3381,13 @@ "subtitleTextBoxAndGridFontName": "Alt yazı metin kutusu ve ızgarasında UI yazı tipi", "subtitleGridFontSize": "Alt yazı ızgarasında yazı tipi boyutu", "subtitleGridTextSingleLine": "Izgarada alt yazı metnini tek satır göster", - "subtitleGridTextSingleLineSeparator": "Tek satır ayırıcı (ör.
)", + "subtitleGridTextSingleLineSeparator": "Tek satır ayırıcı (ör.
)", "subtitleGridTextDisplay": "Alt yazı metninin tabloya sığdırılması", "subtitleGridTextDisplayClip": "Kırp (tek satır)", "subtitleGridTextDisplayWrap": "Pencereye sığacak şekilde kaydır", "subtitleGridTextDisplayEllipsis": "Üç nokta ile tek satır", "subtitleGridLiveSpellCheck": "Alt yazı ızgarasında canlı yazım denetimi", - "subtitleGridCenterText": "Alt yazı ızgarasında metni ortala", + "subtitleGridCenterText": "Alt yazı ızgarasında metni ortala", "subtitleGridShowFormatting": "Alt yazı ızgarasında biçimlendirilmiş (HTML/ASSA) metni göster", "showUpDownStartTime": "\"Göster\" için yukarı/aşağı kontrolünü göster", "showUpDownEndTime": "\"Gizle\" için yukarı/aşağı kontrolünü göster", @@ -3390,9 +3395,9 @@ "showUpDownLabels": "Yukarı/aşağı kontrolleri için etiketleri göster (Göster/Gizle/Süre)", "showButtonHints": "Buton ipuçlarını göster", "gridCompactMode": "Izgaralar için kompakt mod kullan", - "gridAlternatingRows": "Izgarada ardışık satır renkleri kullan", - "gridAlternatingRowColor": "Ardışık satır rengi (açık tema)", - "gridAlternatingRowColorDark": "Ardışık satır rengi (koyu tema)", + "gridAlternatingRows": "Izgarada ardışık satır renkleri kullan", + "gridAlternatingRowColor": "Ardışık satır rengi (açık tema)", + "gridAlternatingRowColorDark": "Ardışık satır rengi (koyu tema)", "uiFont": "Arayüz (UI) yazı tipi", "theme": "Tema", "iconTheme": "Simge teması", @@ -3401,7 +3406,7 @@ "showGridLines": "Izgara çizgilerini göster", "showHorizontalLineAboveToolbar": "Araç çubuğunun üzerinde yatay çizgi göster", "bookmarkColor": "Yer imi rengi", - "spellCheckHighlightColor": "Yazım denetimi vurgu rengi", + "spellCheckHighlightColor": "Yazım denetimi vurgu rengi", "singleLineMaxLength": "Tek satır maksimum uzunluğu", "optimalCharsPerSec": "Optimal Kar/sn", "maxCharsPerSec": "Maks Kar/sn", @@ -3410,60 +3415,60 @@ "maxDurationMs": "Maks süre (ms)", "minGapMs": "Min boşluk (ms)", "minGapFrames": "Asgari boşluk (kare sayısı)", - "minGapCalculateDotDotDot": "Kare hızından en küçük boşluğu hesapla...", + "minGapCalculateDotDotDot": "Kare hızından en küçük boşluğu hesapla...", "minGapCalculateTitle": "Alt yazılar arasındaki en küçük boşluk (ms)", "minGapCalculateFrames": "Kare cinsinden en küçük boşluk", "minGapCalculateXFramesAtYGivesZMs": "{1} kare hızında {0} kare {2} milisaniye verir.", "minGapCalculateUseXAsNewGap": "Yeni en küçük boşluk olarak \"{0}\" milisaniye kullanılsın mı?", "maxLines": "Maksimum satır sayısı", "unbreakSubtitlesShortThan": "Şundan kısa alt yazıları birleştir", - "autoBreakLineEndingEarly": "Cümle sonlarında erken otomatik satır böl (.!?)", + "autoBreakLineEndingEarly": "Cümle sonlarında erken otomatik satır böl (.!?)", "autoBreakCommaBreakEarly": "Virgülde erken otomatik satır böl", "autoBreakDashEarly": "Tire işaretinde erken otomatik satır böl (diyaloglar)", "autoBreakUsePixelWidth": "Piksel genişliğine göre otomatik satır böl", "autoBreakPreferBottomHeavy": "Otomatik satır bölmede alt satırı daha uzun tut", - "autoBreakPreferBottomPercent": "Otomatik bölmede alt satır ağırlık yüzdesi", + "autoBreakPreferBottomPercent": "Otomatik bölmede alt satır ağırlık yüzdesi", "useDoNotBreakAfterList": "Sonrasında bölünmeyecekler listesini kullan", "newEmptyDefaultMs": "Varsayılan yeni alt yazı süresi (ms)", - "timeCodeUpDownStepMs": "Zaman artırma/azaltma kademesi (ms)", - "moveSelectedLinesStepMs": "Seçili satırları taşıma kısayol adımı (ms)", - "moveStartEndStepMs": "Başlangıç/bitiş taşıma kısayolu adımı (ms)", - "moveLinesShortenNeighbor": "Satırları taşı: çakışma olmaması için önceki/sonraki satırı kısalt", - "promptBeforeDelete": "Silmeden önce sor", + "timeCodeUpDownStepMs": "Zaman artırma/azaltma kademesi (ms)", + "moveSelectedLinesStepMs": "Seçili satırları taşıma kısayol adımı (ms)", + "moveStartEndStepMs": "Başlangıç/bitiş taşıma kısayolu adımı (ms)", + "moveLinesShortenNeighbor": "Satırları taşı: çakışma olmaması için önceki/sonraki satırı kısalt", + "promptBeforeDelete": "Silmeden önce sor", "rememberPositionAndSize": "Pencere konumunu ve boyutunu hatırla", - "titleBarFullFileName": "Başlık çubuğunda tam dosya yolunu göster", - "showRecentFiles": "Son kullanılan dosyaları göster (yeniden açmak için)", + "titleBarFullFileName": "Başlık çubuğunda tam dosya yolunu göster", + "showRecentFiles": "Son kullanılan dosyaları göster (yeniden açmak için)", "openLastFileOnStart": "Başlangıçta son kullanılan dosyayı aç", - "autoSave": "Otomatik kaydet (düzenlerken açık olan dosyayı kaydeder)", + "autoSave": "Otomatik kaydet (düzenlerken açık olan dosyayı kaydeder)", "autoBackupOn": "Otomatik yedekleme", "autoBackupIntervalMinutes": "Otomatik yedekleme aralığı (dakika)", "autoBackupDeleteAfterDays": "Otomatik yedek saklama süresi (gün)", - "settingsBackupOn": "Ayarları otomatik yedekle", + "settingsBackupOn": "Ayarları otomatik yedekle", "settingsBackupIntervalDays": "Ayarlar yedekleme aralığı (gün, 0 = her başlangıçta)", "settingsBackupMaxCount": "Saklanacak ayar yedeği sayısı", "autoConvertToUtf8": "Açarken otomatik UTF-8'e dönüştür", "autoTrimWhiteSpace": "Otomatik boşlukları kırp", - "removeBlankLinesWhenOpening": "Bir alt yazı açılırken boş satırları kaldır", + "removeBlankLinesWhenOpening": "Bir alt yazı açılırken boş satırları kaldır", "defaultEncoding": "Varsayılan kodlama", "colorDurationTooShort": "Süre çok kısaysa renklendir", "colorDurationTooLong": "Süre çok uzunsa renklendir", "colorTextTooLong": "Metin çok uzunsa renklendir", "colorTextTooWide": "Metin çok genişse renklendir (piksel)", "colorTextTooManyLines": "2 satırdan fazlaysa renklendir", - "colorTextTooManyLinesX": "{0} satırdan fazlaysa metni renklendir", - "colorCharactersPerSecond": "Çok yüksekse saniyeyi/karakteri renklendir", - "colorWordsPerMinute": "Çok yüksekse dakikayı/kelimeyi renklendir", + "colorTextTooManyLinesX": "{0} satırdan fazlaysa metni renklendir", + "colorCharactersPerSecond": "Çok yüksekse saniyeyi/karakteri renklendir", + "colorWordsPerMinute": "Çok yüksekse dakikayı/kelimeyi renklendir", "colorOverlap": "Zaman kodu çakışmasını renklendir", "colorGapTooShort": "Boşluk çok kısaysa renklendir", "errorBackgroundColor": "Hata arka plan rengi", "waveformDrawGridLines": "Izgara çizgilerini çiz", - "waveformUseSkiaRenderer": "Deneysel hızlı işleyiciyi kullan", - "waveformCenterVideoPositionAlsoWhenPaused": "Duraklatıldığında da video konumunu merkezde tut", - "waveformSelectCurrentSubtitleWhilePaused": "Duraklatıldığında da mevcut alt yazıyı seç", + "waveformUseSkiaRenderer": "Deneysel hızlı işleyiciyi kullan", + "waveformCenterVideoPositionAlsoWhenPaused": "Duraklatıldığında da video konumunu merkezde tut", + "waveformSelectCurrentSubtitleWhilePaused": "Duraklatıldığında da mevcut alt yazıyı seç", "waveformRememberZoom": "Yakınlaştırmayı hatırla (yatay ve dikey)", "waveformShowToolbar": "Araç çubuğunu göster", "waveformShowToolbarEdit": "Araç çubuğunu düzenle...", - "waveformShowToolbarEditLabel": "Araç çubuğu ögeleri", + "waveformShowToolbarEditLabel": "Araç çubuğu ögeleri", "waveformSpectrogramCombinedWaveformHeight": "Ses dalgası/spektrogram birleşik, ses dalgası yüksekliği %", "waveformFocusTextboxAfterInsertNew": "Yeni ekledikten sonra metin kutusuna odaklan", "waveformInvertMouseWheel": "Fare tekerleğini tersine çevir", @@ -3474,11 +3479,11 @@ "waveformMouseWheelStepMilliseconds": "{0} ms", "waveformSnapToShotChanges": "Sahne değişimlerine yasla", "waveformSnapToFrames": "Karelere hizala", - "waveformSnapToShotChangesPixels": "Sürüklerken hizalama uzaklığı (piksel)", + "waveformSnapToShotChangesPixels": "Sürüklerken hizalama uzaklığı (piksel)", "waveformSnapToShotChangeStartMaxSeconds": "En yakın sahne değişimine hizala: en fazla başlangıç uzaklığı (saniye)", "waveformSnapToShotChangeEndMaxSeconds": "En yakın sahne değişimine hizala: en fazla bitiş uzaklığı (saniye)", "waveformSnapToShotChangeSameShotEndMaxSeconds": "En yakın sahne değişimine hizala: başlangıç ve bitiş aynı kesmeyi paylaştığında en fazla bitiş uzaklığı (saniye)", - "waveformGuessStartOffsetMs": "Ses dalga şeklinden başlangıç zamanını tahmin et: başlangıcı şu kadar önceye koy (ms)", + "waveformGuessStartOffsetMs": "Ses dalga şeklinden başlangıç zamanını tahmin et: başlangıcı şu kadar önceye koy (ms)", "waveformGuessEndOffsetMs": "Ses dalga şeklinden bitiş zamanını tahmin et: bitişi şu kadar sonraya koy (ms)", "waveformShotChangesAutoGenerate": "Sahne değişimlerini otomatik oluştur", "waveformTextFontSize": "Ses dalgası metin yazı tipi boyutu", @@ -3488,15 +3493,15 @@ "waveformBackgroundColor": "Ses dalgası arka plan rengi", "waveformSelectedColor": "Ses dalgası seçim rengi", "downloadFfmpeg": "ffmpeg indir", - "fixCommonErrorsSkipStep1": "Ortak hataları düzelt: 1. adımı atla (düzeltmeleri seç)", + "fixCommonErrorsSkipStep1": "Ortak hataları düzelt: 1. adımı atla (düzeltmeleri seç)", "showPluginsMenu": "Eklentiler menüsünü göster", "proxyAddress": "Proxy adresi", "password": "Şifre", - "proxyDomain": "Etki alanı", + "proxyDomain": "Etki alanı", "proxyUseSystemCredentials": "Sistem kimlik bilgilerini kullan", "proxyBypassList": "Vekil sunucuyu şu adresler için devre dışı bırak", "proxyBypassListHint": "Doğrudan bağlanacak noktalı virgülle ayrılmış ana bilgisayar adları, örn. \"dahili.sirket.com;ornek.org\"", - "updates": "Güncellemeler", + "updates": "Güncellemeler", "checkForUpdatesOnStartup": "Uygulama başladığında güncellemeleri kontrol et", "checkForUpdatesChannel": "Bildirilecek sürümler", "checkForUpdatesChannelStable": "Yalnızca kararlı sürümler", @@ -3504,29 +3509,29 @@ "defaultFormat": "Varsayılan format", "defaultSaveAsFormat": "Varsayılan \"Farklı kaydet\" formatı", "favoriteSubtitleFormats": "Favori alt yazı formatları", - "favoriteLanguages": "Favori diller", - "webVttUseXTimestampMap": "WebVTT: X-TIMESTAMP-MAP kullan (yükleme sırasında zaman kodlarını ötele)", - "assaAutoSetResolution": "ASSA: Bir video açıldığında çözünürlüğü (PlayResX/PlayResY) videodan ayarla", + "favoriteLanguages": "Favori diller", + "webVttUseXTimestampMap": "WebVTT: X-TIMESTAMP-MAP kullan (yükleme sırasında zaman kodlarını ötele)", + "assaAutoSetResolution": "ASSA: Bir video açıldığında çözünürlüğü (PlayResX/PlayResY) videodan ayarla", "assaAutoSetResolutionPrompt": "ASSA: Çözünürlüğü videoya uyarlamak için değiştirmeden önce sor (aksi takdirde otomatik olarak yeniden ölçeklendir)", "videoControls": "Video denetimleri", - "videoControlsEdit": "Video denetimlerini düzenle...", + "videoControlsEdit": "Video denetimlerini düzenle...", "videoControlsPositionText": "Konum / süre metni", - "videoControlsVideoFileName": "Video dosyası adı", + "videoControlsVideoFileName": "Video dosyası adı", "videoControlsPlayerName": "Video oynatıcı adı", "fullscreenHideControls": "Tam ekranda video kontrollerini gizle", - "showSecondarySubtitleDialog": "İkinci bir alt yazı dosyası açılırken biçem penceresini göster", - "rememberSecondarySubtitleFile": "İkinci alt yazı dosyasını hatırla", + "showSecondarySubtitleDialog": "İkinci bir alt yazı dosyası açılırken biçem penceresini göster", + "rememberSecondarySubtitleFile": "İkinci alt yazı dosyasını hatırla", "autoOpenVideoFile": "Alt yazı açıldığında video dosyasını otomatik aç", "downloadMpv": "mpv indir", "downloadVlc": "VLC indir", "allowSingleLetterShortcutsInTextbox": "Metin kutusunda tek harfli kısayollara izin ver", - "allowTextNavigationShortcutsInTextbox": "Metin kutusunda metin gezinme kısayollarına (Ctrl+Sol/Sağ, Home/End) izin ver", - "spellCheckEnglishTreatInApostropheAsIng": "Yazım denetimi: 'in'' ile biten kelimeleri 'ing' olarak işle (Yalnızca İngilizce)", + "allowTextNavigationShortcutsInTextbox": "Metin kutusunda metin gezinme kısayollarına (Ctrl+Sol/Sağ, Home/End) izin ver", + "spellCheckEnglishTreatInApostropheAsIng": "Yazım denetimi: 'in'' ile biten kelimeleri 'ing' olarak işle (Yalnızca İngilizce)", "goToLineNumberSetsVideoPosition": "Satır numarasına git video konumunu da ayarlar", "adjustAllTimesRememberLineSelectionChoice": "Tüm zamanları ayarla, satır seçim tercihini hatırla", - "mergeKeepEndTime": "Satırları birleştir: Bitiş zamanını koru (sonraki alt yazıyla üst üste binmeye izin ver)", + "mergeKeepEndTime": "Satırları birleştir: Bitiş zamanını koru (sonraki alt yazıyla üst üste binmeye izin ver)", "mergeKeepEndTimeOnlyAssa": "Satırları birleştir: Bitiş zamanını yalnızca \"Advanced Sub Station Alpha\" için koru", - "mergeAutoBreak": "Satırları birleştir: birleştirilen metni otomatik satırla (kapalı, satırların kendi satır sonlarını korur)", + "mergeAutoBreak": "Satırları birleştir: birleştirilen metni otomatik satırla (kapalı, satırların kendi satır sonlarını korur)", "filesAndLogs": "Dosyalar ve günlükler", "showErrorLogFile": "Hata günlüğünü göster", "showToolsLogFile": "Whisper günlüğünü göster", @@ -3535,7 +3540,7 @@ "showAssaLayer": "ASSA katman kutusunu göster", "waveformCursorColor": "Ses dalgası imleç/kafa rengi", "waveformShotChangeColor": "Ses dalgası sahne değişimi rengi", - "waveformGridColor": "Ses dalgası ızgara rengi", + "waveformGridColor": "Ses dalgası ızgara rengi", "waveformParagraphLeftColor": "Ses dalgası sol kenarlık rengi", "waveformParagraphRightColor": "Ses dalgası sağ kenarlık rengi", "waveformFancyHighColor": "Ses dalgası süslü yüksek rengi", @@ -3563,36 +3568,37 @@ "resetSyntaxColoring": "Söz dizimi renklendirmeyi sıfırla", "resetWaveform": "Ses dalgasını sıfırla", "resetRules": "Kuralları sıfırla", - "timeCodeMode": "Zaman kodu modu", + "timeCodeMode": "Zaman kodu modu", "timeCodeModeTime": "Zaman (ss:dd:sn,ms)", "timeCodeModeFrames": "Kareler (ss:dd:sn:kk)", "timeCodeModeFrameNumbers": "Kare numaraları", + "frameModeSnapToVideoFrames": "Zamanları video karelerine göre kaydet (her saniyedeki karelere göre değil)", "textBoxLimitNewLines": "Alt yazı metin kutusundaki satır sayısını sınırla", "mpvOpenGl": "libmpv - OpenGL", "mpvSoftwareRendering": "libmpv - Yazılımsal oluşturma (yavaş)", - "mpvMetalRendering": "libmpv - Metal (deneysel, yeniden başlatma gerekir)", + "mpvMetalRendering": "libmpv - Metal (deneysel, yeniden başlatma gerekir)", "mpvWidRendering": "libmpv - Yerel Pencere ID oluşturma (Windows/Linux'ta daha hızlı)", "waveFormsAndSpectrogramFoldersContainsX": "\"Ses dalgaları\" ve \"spektrogram\" klasörleri {0} içeriyor", "deleteWaveformAndSpectrogramFoldersQuestion": "\"Ses dalgaları\" ve \"Spektrogram\" dosyaları silinsin mi?", "waveformGenerateSpectrogram": "Spektrogram oluştur", - "waveformAutoGenerate": "Bir video açıldığında ses dalgasını otomatik oluştur", + "waveformAutoGenerate": "Bir video açıldığında ses dalgasını otomatik oluştur", "waveformSpectrogramMode": "Spektrogram modu", "waveformRightClickSelectsSubtitle": "Sağ tıklamada alt yazıyı seç", "waveformDrawStyle": "Ses dalgası çizim stili", - "waveformExtractAudioFormat": "Ses çıkarma biçimi", + "waveformExtractAudioFormat": "Ses çıkarma biçimi", "waveformExtractAudioSampleRate": "Ses çıkarma örnekleme hızı", "waveformExtractAudioSampleRateOriginal": "Orijinal (kaynağı koru)", "waveformExtractAudioBitRate": "Ses çıkarma bit hızı (MP3/M4A)", - "ffmpegUseCenterChannelOnly": "5.1 ses için yalnızca ön orta kanalı kullan (FFmpeg)", + "ffmpegUseCenterChannelOnly": "5.1 ses için yalnızca ön orta kanalı kullan (FFmpeg)", "vlcWidRendering": "libVLC - Yerel Pencere ID oluşturma", - "ffmpegSoftwareRendering": "FFmpeg", + "ffmpegSoftwareRendering": "FFmpeg", "downloadFfmpegLibs": "FFmpeg kitaplıklarını indir (FFmpeg video oynatıcısı için)", "subtitleGridEnterKeyAction": "Alt yazı ızgarası Enter tuşu eylemi", "subtitleSingleClickAction": "Alt yazı ızgarası tek tıklama eylemi", "subtitleDoubleClickAction": "Alt yazı ızgarası çift tıklama eylemi", "subtitleGridCenterSelectedRow": "Alt yazı tablosunda öncekine/sonrakine geçerken satırı ortala", "saveAsBehavior": "\"Farklı kaydet\" davranışı", - "defaultSaveLocation": "Varsayılan kaydetme konumu", + "defaultSaveLocation": "Varsayılan kaydetme konumu", "defaultSaveLocationSourceFileFolder": "Alt yazı/video dosyasının klasörü", "defaultSaveLocationLastUsedFolder": "Son kullanılan klasör", "defaultSaveLocationVideoFileFolder": "Video dosyası klasörü", @@ -3605,16 +3611,16 @@ "gridGoToSubtitleAndPause": "Alt yazıya git ve duraklat", "gridGoToSubtitleAndPlay": "Alt yazıya git ve oynat", "gridGoToSubtitleAndPauseAndFocusTextBox": "Alt yazıya git, duraklat ve metin kutusuna odaklan", - "gridGoToSubtitleAndPlayAndFocusTextBox": "Alt yazıya git, oynat ve metin kutusuna odaklan", - "subtitleListActionVideoGoToPositionAndPlayCurrentAndPause": "Video konumuna git, geçerliyi oynat ve duraklat", + "gridGoToSubtitleAndPlayAndFocusTextBox": "Alt yazıya git, oynat ve metin kutusuna odaklan", + "subtitleListActionVideoGoToPositionAndPlayCurrentAndPause": "Video konumuna git, geçerliyi oynat ve duraklat", "subtitleListActionVideoGoToPositionMinus1SecAndPause": "Video konumuna git - 1 sn ve duraklat", "subtitleListActionVideoGoToPositionMinusHalfSecAndPause": "Video konumuna git - 0,5 sn ve duraklat", "subtitleListActionVideoGoToPositionMinus1SecAndPlay": "Video konumuna git - 1 sn ve oynat", "subtitleGridFormattingNone": "Biçimlendirme yok", "subtitleGridFormattingShowFormatting": "Biçimlendirmeyi göster", "subtitleGridFormattingShowTags": "Etiketleri göster", - "subtitleGridFormattingHideTags": "Etiketleri gizle", - "subtitleGridFormattingShowFormattingKeepTags": "Biçimlendirmeyi göster, görsel olmayan etiketleri koru", + "subtitleGridFormattingHideTags": "Etiketleri gizle", + "subtitleGridFormattingShowFormattingKeepTags": "Biçimlendirmeyi göster, görsel olmayan etiketleri koru", "waveformParagraphBackgroundColor": "Ses dalgası alt yazı arka plan rengi", "waveformParagraphSelectedBackgroundColor": "Ses dalgası seçili alt yazı arka plan rengi", "waveformAllowOverlap": "Çakışmaya izin ver (taşırken/boyutlandırırken)", @@ -3629,42 +3635,42 @@ "splitOddLineActionWeightBottom": "Alta ağırlık ver", "splitOddLinesAction": "Tek sayılı satırları bölme eylemi", "ocrUseWordSplitList": "OCR: Kelime bölme listesini kullan", - "ocrGuessUnknownWords": "OCR: Bilinmeyen kelimeleri tahmin etmeye çalış", - "spellCheckRememberUseAlwaysList": "Yazım denetimi: \"Her zaman kullan\" listesini hatırla", + "ocrGuessUnknownWords": "OCR: Bilinmeyen kelimeleri tahmin etmeye çalış", + "spellCheckRememberUseAlwaysList": "Yazım denetimi: \"Her zaman kullan\" listesini hatırla", "fixShortDisplayTimesAllowMoveStartTime": "Ortak hataları düzelt: Kısa görüntülenme sürelerini düzeltmek başlangıç zamanını kaydırabilir", "speechToTextSelectedLinesPromptFirstTimeOnly": "Sesten metne: Seçili satırlar için dil/motor tercihini yalnızca ilk seferde sor", "multipleReplaceShowDotDotDotButtons": "Çoklu değiştir: Bağlam menüsü butonlarını göster", "gridFocusTextboxAfterInsertNew": "Izgara: Yeni alt yazı ekledikten sonra metin kutusuna odaklan", - "undoRedoGoToChangedLine": "Geri al/yinele: Değiştirilen satıra (ve video konumuna) git", - "musicSymbol": "Müzik sembolü", - "musicSymbolsToReplace": "Değiştirilecek müzik sembolleri (virgülle ayrılmış)", + "undoRedoGoToChangedLine": "Geri al/yinele: Değiştirilen satıra (ve video konumuna) git", + "musicSymbol": "Müzik sembolü", + "musicSymbolsToReplace": "Değiştirilecek müzik sembolleri (virgülle ayrılmış)", "textToSpeechPromptMergeContinuationLines": "Metinden sese: Devam eden satırları birleştirmek için sor", - "textToSpeechPromptSkipNoiseLines": "Metinden konuşmaya: Ses/müzik satırlarını atlama istemi", + "textToSpeechPromptSkipNoiseLines": "Metinden konuşmaya: Ses/müzik satırlarını atlama istemi", "textToSpeechPromptDetectSpeakers": "Metinden konuşmaya: Metindeki konuşmacı adlarını algılama istemi", "useFocusedButtonBackgroundColor": "Odaklanmış buton arka plan rengini kullan", "focusedButtonBackgroundColor": "Odaklanmış buton arka plan rengi", "forceCrLfOnSave": "Kaydederken CR+LF zorla (metin al yazı dosyaları)", - "linuxClipboardUseExternalTool": "Metni xclip/wl-copy ile kopyala (aksanlı karakterleri korur)", - "showFormatLimitWarning": "Satırlar biçim sınırlarını aştığında kaydederken uyar (ör. SCC satır başına 32 karakter)", + "linuxClipboardUseExternalTool": "Metni xclip/wl-copy ile kopyala (aksanlı karakterleri korur)", + "showFormatLimitWarning": "Satırlar biçim sınırlarını aştığında kaydederken uyar (ör. SCC satır başına 32 karakter)", "textBoxButtonShowAutoBreak": "Metin kutusu: Otomatik bölme butonunu göster", "textBoxButtonShowUnbreak": "Metin kutusu: Birleştirme butonunu göster", "textBoxButtonShowItalic": "Metin kutusu: İtalik butonunu göster", "textBoxButtonShowColor": "Metin kutusu: Renk butonunu göster", "textBoxButtonShowRemoveFormatting": "Metin kutusu: Biçimlendirmeyi kaldır butonunu göster", - "textBoxButtonShowAiAssistant": "Metin kutusu: Yapay zekâ asistanı butonunu göster", + "textBoxButtonShowAiAssistant": "Metin kutusu: Yapay zekâ asistanı butonunu göster", "waveformSingleClickAction": "Ses dalgası tek tıklama eylemi", "waveformDoubleClickAction": "Ses dalgası çift tıklama eylemi (tek tıklama eyleminden sonra)", "allSettings": "Tüm ayarlar", "uiScale": "Arayüz ölçeği (%)", - "fontScale": "Yazı tipi ölçeği (%)", - "textSelectionOpacity": "Metin seçimi matlığı (%)", + "fontScale": "Yazı tipi ölçeği (%)", + "textSelectionOpacity": "Metin seçimi matlığı (%)", "waveformToolbarItems": "Ses dalgası araç çubuğu ögeleri", "matchIconColorToDarkTheme": "Simge rengini koyu tema ön plan rengiyle eşleştir", "subtitlePreviewProperties": "Alt yazı ön izleme özellikleri", - "usePositionFromSubtitleFile": "Alt yazı dosyasındaki konumu kullan (TTML/PAC/EBU STL)", - "marginIsPartOfSubtitleArea": "Kenar boşluğu alt yazı alanına dahildir", - "textJustify": "Satırları iki yana yasla", - "deinterlace": "Videodaki taramayı kaldır (Deinterlace)", + "usePositionFromSubtitleFile": "Alt yazı dosyasındaki konumu kullan (TTML/PAC/EBU STL)", + "marginIsPartOfSubtitleArea": "Kenar boşluğu alt yazı alanına dahildir", + "textJustify": "Satırları iki yana yasla", + "deinterlace": "Videodaki taramayı kaldır (Deinterlace)", "deinterlaceOff": "Kapalı", "deinterlaceAuto": "Otomatik (yalnızca taranmış kareler)", "deinterlaceAlways": "Her zaman", @@ -3677,7 +3683,7 @@ "waveformSaveAsCustomTheme": "Özel tema olarak kaydet...", "waveformLoadThemeDotDotDot": "Tema yükle...", "waveformExportThemeDotDotDot": "Temayı dışa aktar...", - "waveformSaveCustomThemeTitle": "Özel temayı kaydet", + "waveformSaveCustomThemeTitle": "Özel temayı kaydet", "waveformThemeFileType": "Subtitle Edit ses dalgası teması", "waveformThemeHighContrast": "Yüksek karşıtlık", "waveformThemeOceanBlue": "Okyanus mavisı", @@ -3691,12 +3697,12 @@ "searchShortcuts": "Kısayollarda ara...", "categorySubtitleGridAndTextBox": "Alt yazı listesi ve metin kutusu", "categorySubtitleGrid": "Alt yazı listesi", - "categoryTextBox": "Metin kutusu", - "categoryFile": "Dosya", + "categoryTextBox": "Metin kutusu", + "categoryFile": "Dosya", "categoryVideo": "Video", "categorySync": "Senkronizasyon", "categoryAi": "Yapay Zekâ", - "categoryCustom": "Özel", + "categoryCustom": "Özel", "editCustomShortcut": "Özel kısayolu düzenle", "customShortcutTextBoxKeyHint": "Burada Ctrl/Alt ile bir tuş kullanın - sade bir tuş artık metin kutusuna yazı yazmaz.", "customShortcutNumberX": "Özel kısayol #{0}", @@ -3722,30 +3728,30 @@ "activeInEverywhere": "Her yer", "generalMergeSelectedLinesAndUnbreak": "Seçili satırları birleştir ve tek satır yap", "generalMergeSelectedLinesAndUnbreakCjk": "Seçili satırları birleştir ve boşluksuz tek satır yap (CJK)", - "generalUnbreakNoSpaceCjk": "Boşluksuz birleştir (CJK)", - "generalToggleBreak": "Satır bölmeyi/birleştirmeyi değiştir", + "generalUnbreakNoSpaceCjk": "Boşluksuz birleştir (CJK)", + "generalToggleBreak": "Satır bölmeyi/birleştirmeyi değiştir", "generalMergeSelectedLinesBilingual": "Seçili satırları iki dilli birleştir", "generalToggleTranslationMode": "Çeviri modunu aç/kapat", - "generalToggleTranslationAndOriginalInPreviews": "Video/ses ön izlemesinde çeviri ve orijinali değiştir", + "generalToggleTranslationAndOriginalInPreviews": "Video/ses ön izlemesinde çeviri ve orijinali değiştir", "generalChooseLayout": "Düzen seç", "generalGoToNextSubtitle": "Sonraki alt yazıya git", - "generalGoToNextSubtitlePlayTranslate": "Sonraki alt yazıya git (çeviriyi oynat)", - "generalGoToNextSubtitleCursorAtEnd": "Sonraki alt yazıya git ve imleci sona yerleştir", + "generalGoToNextSubtitlePlayTranslate": "Sonraki alt yazıya git (çeviriyi oynat)", + "generalGoToNextSubtitleCursorAtEnd": "Sonraki alt yazıya git ve imleci sona yerleştir", "generalGoToPrevSubtitle": "Önceki alt yazıya git", - "generalGoToPrevSubtitlePlayTranslate": "Önceki alt yazıya git (çeviriyi oynat)", - "generalGoToFirstLine": "İlk satıra git", + "generalGoToPrevSubtitlePlayTranslate": "Önceki alt yazıya git (çeviriyi oynat)", + "generalGoToFirstLine": "İlk satıra git", "generalGoToLastLine": "Son satıra git", "alsoSetVideoPosition": "Video konumunu da ayarla", "generalGoToVideoPosition": "Video konumuna git", "generalToggleItalic": "İtaliği aç/kapat", "generalToggleBold": "Kalını aç/kapat", - "generalToggleUnderline": "Altı çizili durumunu değiştir", - "generalToggleBox": "Kutu durumunu değiştir (EBU STL)", + "generalToggleUnderline": "Altı çizili durumunu değiştir", + "generalToggleBox": "Kutu durumunu değiştir (EBU STL)", "fileOpen": "Aç", "fileOpenKeepVideo": "Aç (videoyu koru)", "fileSave": "Kaydet", "fileSaveAs": "Farklı kaydet", - "fileSaveOriginal": "Orijinalini kaydet", + "fileSaveOriginal": "Orijinalini kaydet", "fileCompare": "Karşılaştır", "fileStatistics": "İstatistikler", "fileImportPlainText": "Düz metin içe aktar", @@ -3757,7 +3763,7 @@ "fillSelectedLinesWithClipboard": "Seçili satırları panodaki metinle doldur", "listInverseSelection": "Seçimi tersine çevir", "listDeleteSelection": "Seçimi sil", - "deleteSelectionEverywhere": "Seçimi sil (her yerden)", + "deleteSelectionEverywhere": "Seçimi sil (her yerden)", "rippleDeleteSelection": "Seçimi sil ve boşluğu kapat (Ripple delete)", "assigned": "Atanmış", "unassigned": "Atanmamış", @@ -3766,7 +3772,7 @@ "detectKey": "Tuşu algıla", "control": "Kontrol", "alt": "Alt", - "altGr": "AltGr", + "altGr": "AltGr", "win": "Win", "shift": "Shift", "controlMac": "⌃", @@ -3777,7 +3783,7 @@ "resetShortcutsDetail": "Tüm kısayolları varsayılan değerlere sıfırlamak istiyor musunuz?", "togglePlayPause": "Oynat/Duraklat aç/kapat", "toggleLockTimeCodes": "Zaman kodlarını kilitlemeyi aç/kapat", - "toggleTimeCodeMode": "Zaman kodu modunu değiştir (zaman/kareler/kare numaraları)", + "toggleTimeCodeMode": "Zaman kodu modunu değiştir (zaman/kareler/kare numaraları)", "toggleScreenPrivacy": "Ekran gizliliğini değiştir (dosya adlarını gizle, ardından metinleri de gizle)", "screenPrivacyFileNamesHidden": "Ekran gizliliği: dosya adları gizlendi", "screenPrivacyFileNamesAndTextsHidden": "Ekran gizliliği: dosya adları ve metinler gizlendi", @@ -3786,22 +3792,22 @@ "sourceView": "Kaynak görünümü", "addOrEditBookmark": "Yer imi ekle veya düzenle", "toggleBookmark": "Yer imini aç/kapat (seçili satırlar, metin yok)", - "toggleForcedSelectedLines": "Zorunlu durumunu değiştir (seçili satırlar)", + "toggleForcedSelectedLines": "Zorunlu durumunu değiştir (seçili satırlar)", "goToNextBookmark": "Sonraki yer imine git", - "clearBookmarks": "Yer imlerini temizle", - "goToNextEmptyLine": "Sonraki boş satıra git", + "clearBookmarks": "Yer imlerini temizle", + "goToNextEmptyLine": "Sonraki boş satıra git", "toggleWaveformToolbar": "Ses dalgası araç çubuğunu aç/kapat", - "toggleSubtitleGridFormatting": "Izgara biçimlendirmesini değiştir (biçimlendirmeyi göster/etiketleri göster/biçimlendirme yok)", + "toggleSubtitleGridFormatting": "Izgara biçimlendirmesini değiştir (biçimlendirmeyi göster/etiketleri göster/biçimlendirme yok)", "waveformSetStartAndSetEndOfPreviousMinusGap": "Başlangıcı ayarla ve öncekini boşluk kadar geri çek", "waveformSetEndAndStartOfNextAfterGap": "Bitişi ayarla ve sonrakini boşluk kadar ileri taşı", "waveformSetEndAndStartOfNextAfterGapAndGoToNext": "Bitişi ayarla, sonrakini boşluk kadar ileri taşı ve sonrakine git", "moveLastWordToNextSubtitle": "Son kelimeyi sonraki alt yazıya taşı", "moveLastWordFromFirstLineDownCurrentSubtitle": "Son kelimeyi ilk satırdan aşağı taşı (mevcut alt yazı)", - "moveFirstWordFromNextLineUpCurrentSubtitle": "İlk kelimeyi sonraki satırdan yukarı taşı (mevcut alt yazı)", - "moveTextFromCursorToNextAndGoToNext": "İmleçten sonraki metni sonraki alt yazıya taşı ve sonrakine geç", - "moveTextFromCursorToNextAndGoToNextAndPlay": "İmleçten sonraki metni sonraki alt yazıya taşı, sonrakine geç ve oynat", - "moveTextFromCursorToNextAndGoToNextAndPlayAndPause": "İmleç konumundan sonraki metni sonraki alt yazıya taşı, sonrakine git, oynat ve sonunda duraklat", - "breakAtFirstSpaceFromCursor": "Imleç konumundan sonraki ilk boşluktan böl", + "moveFirstWordFromNextLineUpCurrentSubtitle": "İlk kelimeyi sonraki satırdan yukarı taşı (mevcut alt yazı)", + "moveTextFromCursorToNextAndGoToNext": "İmleçten sonraki metni sonraki alt yazıya taşı ve sonrakine geç", + "moveTextFromCursorToNextAndGoToNextAndPlay": "İmleçten sonraki metni sonraki alt yazıya taşı, sonrakine geç ve oynat", + "moveTextFromCursorToNextAndGoToNextAndPlayAndPause": "İmleç konumundan sonraki metni sonraki alt yazıya taşı, sonrakine git, oynat ve sonunda duraklat", + "breakAtFirstSpaceFromCursor": "Imleç konumundan sonraki ilk boşluktan böl", "breakAtFirstSpaceFromCursorAndGoToNext": "Imleç konumundan sonraki ilk boşluktan böl ve sonrakine geç", "toggleFocusGridAndWaveform": "Alt yazı ızgarası ve ses dalgası arasında odak değiştir", "toggleFocusTextBoxAndWaveform": "Metin kutusu ve ses dalgası arasında odak değiştir", @@ -3809,29 +3815,29 @@ "goToPreviousLineAndSetVideoPosition": "Önceki alt yazıya git ve video konumunu ayarla", "goToPreviousLineFromVideoPosition": "Önceki alt yazıya git (mevcut video konumundan)", "goToNextLineFromVideoPosition": "Sonraki alt yazıya git (mevcut video konumundan)", - "videoGoToPreviousTimeCode": "Önceki zaman koduna git", - "videoGoToNextTimeCode": "Sonraki zaman koduna git", + "videoGoToPreviousTimeCode": "Önceki zaman koduna git", + "videoGoToNextTimeCode": "Sonraki zaman koduna git", "goToNextLineAndSetVideoPosition": "Sonraki alt yazıya git ve video konumunu ayarla", "textBoxDeleteForward": "Metin kutusu: Seçimi veya sonraki karakteri sil (ileri silme)", "textBoxCut": "Metin kutusu: Kes", "textBoxCut2": "Metin kutusu: Kes (alternatif)", "textBoxPaste": "Metin kutusu: Yapıştır", - "textBoxPaste2": "Metin kutusu: Yapıştır (alternatif)", + "textBoxPaste2": "Metin kutusu: Yapıştır (alternatif)", "textBoxCopy": "Metin kutusu: Kopyala", - "textBoxCopy2": "Metin kutusu: Kopyala (alternatif)", + "textBoxCopy2": "Metin kutusu: Kopyala (alternatif)", "textBoxSelectAll": "Metin kutusu: Tümünü seç", "subtitleGridCut": "Alt yazı ızgarası: Kes", "subtitleGridCopy": "Alt yazı ızgarası: Kopyala", "subtitleGridPaste": "Alt yazı ızgarası: Yapıştır", - "listViewColumnDeleteText": "Sütun, metni sil", - "listViewColumnDeleteTextAndShiftUp": "Sütun, metni sil ve yukarı kaydır", - "listViewColumnInsertText": "Sütun, metin ekle", - "listViewColumnPaste": "Sütun, yapıştır", - "listViewColumnTextUp": "Sütun, metni yukarı taşı", - "listViewColumnTextDown": "Sütun, metni aşağı taşı", - "autoTranslateSelectedLines": "Seçili satırları otomatik çevir...", - "autoTranslateSelectedLinesNoPrompt": "Seçili satırları otomatik çevir (istem olmadan, son motoru/dilleri kullan)", - "setAssaResolution": "ASSA çözünürlüğünü ayarla (PlayResX/PlayResY)", + "listViewColumnDeleteText": "Sütun, metni sil", + "listViewColumnDeleteTextAndShiftUp": "Sütun, metni sil ve yukarı kaydır", + "listViewColumnInsertText": "Sütun, metin ekle", + "listViewColumnPaste": "Sütun, yapıştır", + "listViewColumnTextUp": "Sütun, metni yukarı taşı", + "listViewColumnTextDown": "Sütun, metni aşağı taşı", + "autoTranslateSelectedLines": "Seçili satırları otomatik çevir...", + "autoTranslateSelectedLinesNoPrompt": "Seçili satırları otomatik çevir (istem olmadan, son motoru/dilleri kullan)", + "setAssaResolution": "ASSA çözünürlüğünü ayarla (PlayResX/PlayResY)", "setShortcutForX": "\"{0}\" için kısayol ayarla", "commandFileNewKeepVideo": "Yeni (videoyu koru)", "fileOpenOriginal": "Orijinali aç", @@ -3863,7 +3869,7 @@ "closeVideo": "Videoyu kapat", "speechToText": "Sesten metne (Whisper)", "textToSpeech": "Metinden sese", - "videoOcr": "Gömülü alt yazıyı OCR ile tara", + "videoOcr": "Gömülü alt yazıyı OCR ile tara", "burnIn": "Gömülü alt yazılı video oluştur", "generateTransparent": "Alt yazılı şeffaf video oluştur", "undockVideoControls": "Video kontrollerini ayır", @@ -3880,12 +3886,12 @@ "chooseRuleProfile": "Kural profilini seç", "videoFullScreen": "Video tam ekran", "copyTextFromOriginalSelectedLines": "Metni orijinalden kopyala (seçili satırlar)", - "copyTextToOriginalSelectedLines": "Metni orijinale kopyala (seçili satırlar)", + "copyTextToOriginalSelectedLines": "Metni orijinale kopyala (seçili satırlar)", "textBoxRemoveAllFormatting": "Metin kutusu, tüm biçimlendirmeyi kaldır", "textBoxItalic": "Metin kutusu italik", - "textBoxBold": "Metin kutusunu kalın yap", + "textBoxBold": "Metin kutusunu kalın yap", "textBoxUnderline": "Metin kutusunu altı çizili yap", - "textBoxBox": "Metin kutusu kutusu (EBU STL)", + "textBoxBox": "Metin kutusu kutusu (EBU STL)", "resetWaveformZoomAndSpeed": "Ses dalgası yakınlaştırmayı ve oynatma hızını sıfırla", "togglePlaybackSpeed": "Oynatma hızını aç/kapat", "playbackSpeedSlower": "Oynatma hızı daha yavaş", @@ -3895,11 +3901,11 @@ "seekSilence": "Sessizlik ara", "seekSilenceBack": "Geriye doğru sessizlik ara", "seekSilenceForward": "İleriye doğru sessizlik ara", - "waveformGuessStart": "Ses dalgasından başlangıç zamanını tahmin et", - "waveformGuessEnd": "Ses dalga şeklinden bitiş zamanını tahmin et", - "waveformGuessStartAndEnd": "Başlangıç ve bitiş süresini ses dalgasından tahmin et", + "waveformGuessStart": "Ses dalgasından başlangıç zamanını tahmin et", + "waveformGuessEnd": "Ses dalga şeklinden bitiş zamanını tahmin et", + "waveformGuessStartAndEnd": "Başlangıç ve bitiş süresini ses dalgasından tahmin et", "setVideoPositionCurrentSubtitleStart": "Video konumunu mevcut satır başına ayarla", - "goToSubtitlePositionAndPause": "Alt yazı konumuna git ve duraklat", + "goToSubtitlePositionAndPause": "Alt yazı konumuna git ve duraklat", "setVideoPositionCurrentSubtitleEnd": "Video konumunu mevcut satır sonuna ayarla", "toggleAudioTracks": "Ses izlerini aç/kapat", "goToNextError": "Sonraki hataya git", @@ -3910,8 +3916,8 @@ "colorX": "Renk {0}", "surroundWith": "Şununla çevrele...", "surroundWithXY": "{0}/{1} ile çevrele", - "surroundWithNumberX": "#{0} ile çevrele", - "surroundWithBehavior": "Davranış", + "surroundWithNumberX": "#{0} ile çevrele", + "surroundWithBehavior": "Davranış", "surroundWithBehaviorToggle": "Değiştir (ekle veya varsa kaldır)", "surroundWithBehaviorAdd": "Ekle (her zaman)", "surroundWithBehaviorRemove": "Kaldır (tümünü)", @@ -3919,10 +3925,10 @@ "surroundWithWorksOn": "Uygulama alanı", "surroundWithScopeSelectionOrText": "Seçim, yoksa tüm metin", "surroundWithScopeEachLine": "Her satır", - "searchVia": "Şununla ara:", - "searchViaX": "{0} ile ara", - "searchViaNumberX": "#{0} ile ara", - "searchViaUrlHint": "Aranan metin URL içindeki {0} parametresinin yerini alır", + "searchVia": "Şununla ara:", + "searchViaX": "{0} ile ara", + "searchViaNumberX": "#{0} ile ara", + "searchViaUrlHint": "Aranan metin URL içindeki {0} parametresinin yerini alır", "moveVideoPositionMilliseconds": "Video konumunu milisaniye olarak taşı", "importShortcutsTitle": "Kısayolları içe aktar", "exportShortcutsTitle": "Kısayolları dışa aktar", @@ -3937,8 +3943,8 @@ "showMediaInformation": "Medya bilgisini göster", "chooseSubtitleFormat": "Alt yazı formatı seç", "trimWhitespaceSelectedLines": "Boşlukları kırp (seçili satırlar)", - "waveformInsertNewSelection": "Ses dalgasına yeni seçim ekle", - "waveformNewSelectionPasteFromClipboard": "Ses dalgası panodaki metni yeni seçime yapıştırır", + "waveformInsertNewSelection": "Ses dalgasına yeni seçim ekle", + "waveformNewSelectionPasteFromClipboard": "Ses dalgası panodaki metni yeni seçime yapıştırır", "waveformHorizontalZoomInCommand": "Ses dalgası yatay yakınlaştır", "waveformHorizontalZoomOutCommand": "Ses dalgası yatay uzaklaştır", "waveformVerticalZoomInCommand": "Ses dalgası dikey yakınlaştır", @@ -3946,7 +3952,7 @@ "copySubtitlePathToClipboard": "Alt yazı yolunu panoya kopyala", "copySubtitleOriginalPathToClipboard": "Orijinal alt yazı yolunu panoya kopyala", "focusTextBox": "Metin kutusuna odaklan", - "focusSubtitleListView": "Alt yazı liste görünümüne odaklan", + "focusSubtitleListView": "Alt yazı liste görünümüne odaklan", "focusWaveform": "Dalga şekline odaklan", "focusOriginalTextBox": "Orijinal metin kutusuna odaklan", "textBoxGoToStart": "Metin kutusu, başa git", @@ -3954,7 +3960,7 @@ "sortByNumber": "Numaraya göre sırala", "sortByStartTime": "\"Göster\" zamanına göre sırala", "sortByEndTime": "\"Gizle\" zamanına göre sırala", - "sortByDuration": "Süreye göre sırala", + "sortByDuration": "Süreye göre sırala", "sortByGap": "Boşluğa göre sırala", "sortByText": "Metne göre sırala - alfabetik", "sortByTextTotalLength": "Metne göre sırala - toplam uzunluk", @@ -3965,7 +3971,7 @@ "sortByActor": "Oyuncuya göre sırala", "sortByStyle": "Stile göre sırala", "videoToggleBrightness": "Video: parlaklığı aç/kapat (yalnızca mpv)", - "videoToggleContrast": "Video: karşıtlığı aç/kapat (yalnızca mpv)", + "videoToggleContrast": "Video: karşıtlığı aç/kapat (yalnızca mpv)", "duplicatesFound": "Aşağıdaki çakışan kısayollar bulundu:", "copyTextToClipboard": "Metni panoya kopyala (seçili satırlar)", "copyTextFromOriginalToClipboard": "Orijinal metni panoya kopyala (seçili satırlar)", @@ -3985,12 +3991,12 @@ "goToPreviousBookmark": "Önceki yer imine git", "selectionToLower": "Metin kutusu: Seçimi küçük harf yap", "selectionToUpper": "Metin kutusu: Seçimi büyük harf yap", - "selectionToSentenceCase": "Metin kutusu: Seçimi cümle düzenine dönüştür", + "selectionToSentenceCase": "Metin kutusu: Seçimi cümle düzenine dönüştür", "selectionToggleCasing": "Metin kutusu: Harf düzenini değiştir (BÜYÜK -> küçük -> Baş Harfleri Büyük)", "googleIt": "Google'da ara (seçili metin)", "setActorX": "Aktörü ayarla: {0}", "setNewActor": "Yeni konuşmacı ayarla...", - "setStyleX": "Stili ayarla: {0}", + "setStyleX": "Stili ayarla: {0}", "toggleDialogDashes": "Diyalog çizgilerini aç/kapat", "mergeWithLineBeforeAsDialog": "Önceki satırla diyalog olarak birleştir", "mergeWithLineAfterAsDialog": "Sonraki satırla diyalog olarak birleştir", @@ -3998,19 +4004,19 @@ "moveStartOneFrameForward": "Başlangıcı bir kare ileri taşı", "moveEndOneFrameBack": "Bitişi bir kare geri taşı", "moveEndOneFrameForward": "Bitişi bir kare ileri taşı", - "moveStartOneFrameBackKeepGapPrev": "Başlangıcı bir kare geri taşı (yakınsa öncekiyle boşluğu koru)", + "moveStartOneFrameBackKeepGapPrev": "Başlangıcı bir kare geri taşı (yakınsa öncekiyle boşluğu koru)", "moveStartOneFrameForwardKeepGapPrev": "Başlangıcı bir kare ileri taşı (yakınsa öncekiyle boşluğu koru)", "moveEndOneFrameBackKeepGapNext": "Bitişi bir kare geri taşı (yakınsa sonrakiyle boşluğu koru)", "moveEndOneFrameForwardKeepGapNext": "Bitişi bir kare ileri taşı (yakınsa sonrakiyle boşluğu koru)", - "moveStartXMsBack": "Başlangıcı X ms geriye taşı (X Ayarlar'da ve Görsel Eşleme'de belirlenir)", + "moveStartXMsBack": "Başlangıcı X ms geriye taşı (X Ayarlar'da ve Görsel Eşleme'de belirlenir)", "moveStartXMsForward": "Başlangıcı X ms ileriye taşı (X Ayarlar'da ve Görsel Eşleme'de belirlenir)", "moveEndXMsBack": "Bitişi X ms geriye taşı (X Ayarlar'da ve Görsel Eşleme'de belirlenir)", "moveEndXMsForward": "Bitişi X ms ileriye taşı (X Ayarlar'da ve Görsel Eşleme'de belirlenir)", - "moveSelectedLinesXMsBack": "Seçili satırları X ms geriye taşı (X Ayarlarda belirlenir)", + "moveSelectedLinesXMsBack": "Seçili satırları X ms geriye taşı (X Ayarlarda belirlenir)", "moveSelectedLinesXMsForward": "Seçili satırları X ms ileriye taşı (X Ayarlarda belirlenir)", "moveSelectedLinesAndForwardXMsBack": "Seçili satırları ve sonraki tümünü X ms geriye taşı (X Ayarlarda belirlenir)", "moveSelectedLinesAndForwardXMsForward": "Seçili satırları ve sonraki tümünü X ms ileriye taşı (X Ayarlarda belirlenir)", - "moveAllLinesXMsBack": "Tüm satırları X ms geriye taşı (X ayarı Ayarlar'dan yapılır)", + "moveAllLinesXMsBack": "Tüm satırları X ms geriye taşı (X ayarı Ayarlar'dan yapılır)", "moveAllLinesXMsForward": "Tüm satırları X ms ileriye taşı (X ayarı Ayarlar'dan yapılır)", "moveSelectedLinesCustomMsBack": "Seçili satırları özel ms kadar ({0:#,###,##0}) geriye taşı, {1}", "moveSelectedLinesCustomMsForward": "Seçili satırları özel ms kadar ({0:#,###,##0}) ileriye taşı, {1}", @@ -4063,7 +4069,7 @@ "applyToAllLinesX": "Tüm satırlar ({0})", "updateAllXAvailable": "Tümünü güncelle ({0})", "updatingXOfY": "{0}/{1} güncelleniyor...", - "doNotAskAgain": "Tekrar sorma (Eklentileri yönet kısmından değiştirin)", + "doNotAskAgain": "Tekrar sorma (Eklentileri yönet kısmından değiştirin)", "applyPluginsTo": "Eklentileri şuna uygula", "askEachTime": "Her defasında sor", "allLines": "Tüm satırlar" @@ -4085,11 +4091,11 @@ "pauseOcr": "OCR _duraklat", "inspectLine": "Satırı incele...", "ocrEngine": "OCR Motoru", - "tesseractEngineMode": "Motor modu", - "tesseractEngineModeLegacy": "Yalnızca özgün Tesseract (italik algılayabilir)", - "tesseractEngineModeNeural": "Yalnızca LSTM sinir ağları", - "tesseractEngineModeBoth": "Tesseract + LSTM", - "tesseractEngineModeDefault": "Varsayılan (kullanılabilir olana göre)", + "tesseractEngineMode": "Motor modu", + "tesseractEngineModeLegacy": "Yalnızca özgün Tesseract (italik algılayabilir)", + "tesseractEngineModeNeural": "Yalnızca LSTM sinir ağları", + "tesseractEngineModeBoth": "Tesseract + LSTM", + "tesseractEngineModeDefault": "Varsayılan (kullanılabilir olana göre)", "database": "Veritabanı", "maxWrongPixels": "Maksimum hatalı piksel", "maxErrorPct": "Maksimum hata %", @@ -4104,13 +4110,13 @@ "expandInfoX": "Genişleme sayısı: {0}", "editNOcrDatabaseXWithYItems": "nOCR veritabanı {0} düzenleniyor, {1:#,###,##0} öge var", "newNOcrDatabase": "Yeni nOCR veritabanı", - "newBinaryImageCompareDatabase": "Yeni İkili Görüntü Karşılaştırma veritabanı", + "newBinaryImageCompareDatabase": "Yeni İkili Görüntü Karşılaştırma veritabanı", "renameNOcrDatabase": "nOCR veritabanını yeniden adlandır", - "renameBinaryImageCompareDatabase": "İkili Görüntü Karşılaştırma veritabanını yeniden adlandır", + "renameBinaryImageCompareDatabase": "İkili Görüntü Karşılaştırma veritabanını yeniden adlandır", "nOcrDatabase": "nOCR veritabanı", "drawMode": "Çizim modu:", "addNewCharcter": "Yeni karakter ekle", - "addNOcrCharacterTitle": "Satır {0}, karakter {1} / {2} için \"{3}\" veritabanı kullanılarak nOCR karakteri ekle", + "addNOcrCharacterTitle": "Satır {0}, karakter {1} / {2} için \"{3}\" veritabanı kullanılarak nOCR karakteri ekle", "addBinaryOcrCharacterTitle": "Satır {0}, karakter {1} / {2} için \"{3}\" veritabanı kullanılarak ikili OCR karakteri ekle", "lineIndexX": "Satır {0}", "inspectNOcrAdditions": "Yeni nOCR eklemelerini incele", @@ -4146,7 +4152,7 @@ "binarize": "Siyah beyaz yap (Binarize)", "borderSize": "Kenarlık boyutu", "captureTopAlign": "ASSA hizalamasını otomatik algıla", - "showOnlyForcedSubtitles": "Yalnızca zorunlu alt yazıları göster", + "showOnlyForcedSubtitles": "Yalnızca zorunlu alt yazıları göster", "ocrImage": "OCR resmi", "oneColor": "Tek renk (beyaz)", "darknessThreshold": "Karanlık eşiği", @@ -4160,7 +4166,7 @@ "binaryImageCompareInspectImageMatches": "\"İkili resim karşılaştırma\" - Resim eşleşmelerini incele", "saveBlankTextTitle": "Boş metin kaydedilsin mi?", "saveBlankTextPrompt": "Resim için boş metin kaydedilsin mi?", - "drawnLinesDoNotMatchTitle": "Çizilen çizgiler görselle eşleşmiyor", + "drawnLinesDoNotMatchTitle": "Çizilen çizgiler görselle eşleşmiyor", "drawnLinesDoNotMatchPrompt": "Çizilen çizgilerin bazıları görselle eşleşmiyor (yeşil çizgiler karakterin üzerinde, kırmızı çizgiler karakterin dışında olmalıdır); bu nedenle bu karakter tanınmayacak.\n\nYine de kaydedilsin mi?", "yesAndNeverAskAgain": "Evet ve bir daha sorma", "importTextFromSubtitleDotDotDot": "Alt yazıdan metin aktar...", @@ -4169,7 +4175,7 @@ "importTextFromSubtitleXLinesImported": "{0} satır için metin aktarıldı.", "exportTextAsSubtitleDotDotDot": "Metni alt yazı olarak dışa aktar...", "exportTextAsSubtitleNoText": "Dışa aktarılacak OCR metni yok. Önce OCR işlemini çalıştırın veya metin aktarın.", - "saveAllImagesWithHtmlIndexDotDotDot": "Tüm görselleri HTML dizini ile kaydet...", + "saveAllImagesWithHtmlIndexDotDotDot": "Tüm görselleri HTML dizini ile kaydet...", "saveAllImagesWithHtmlIndexPickFolder": "Görseller ve HTML dizini için bir klasör seçin", "saveAllImagesWithHtmlIndexSaved": "{0} görsel bir HTML dizini ile kaydedildi", "savingImagesDotDotDot": "Görseller kaydediliyor...", @@ -4191,10 +4197,10 @@ "vobSubColorEmphasis1": "Vurgu 1", "vobSubColorEmphasis2": "Vurgu 2", "vobSubColorsInvert": "Arka planı / vurgu 1'i ters çevir", - "vobSubIsolateColors": "OCR için VobSub renklerini ayrıştır", + "vobSubIsolateColors": "OCR için VobSub renklerini ayrıştır", "vobSubIsolateColorsHint": "En sık kullanılan rengi (metin) tutup dış hat/yumuşatma renklerini atarak her bir VobSub görüntüsünü OCR öncesinde net bir siyah-beyaz olarak yeniden oluşturur. Gri dış hatların karakterleri birbirine bağladığı disklerde doğruluğu artırır.", "llamaCppOcrSettingsTitle": "llama.cpp OCR ayarları", - "selectTesseractDictionary": "Tesseract sözlüğünü seçin:", + "selectTesseractDictionary": "Tesseract sözlüğünü seçin:", "inspectBinaryOcrAdditionsTitle": "İkili OCR Eklemlerini İncele", "llamaCppOcr": "llama.cpp OCR", "llamaCppOcrDescription": "OCR için kullanılan yerel llama.cpp sunucusu (çok modlu model).", @@ -4209,21 +4215,25 @@ "fallbackOcrDatabase": "Alternatif OCR veritabanı", "showAllOllamaModels": "Tüm modelleri göster (yalnızca görme yeteneği olanları değil)", "ollamaModelLikelyWrong": "Ollama hiç metin döndürmedi - seçilen model OCR veya görme (vision) özelliğini desteklemiyor olabilir", - "llamaCppNotDownloaded": "llama.cpp motoru/modeli indirilmemiş - toplu dönüştürme ayarlarından indirin", + "llamaCppNotDownloaded": "llama.cpp motoru/modeli indirilmemiş - toplu dönüştürme ayarlarından indirin", "llamaCppReturnedNoText": "llama.cpp metin döndürmedi - sunucuyu ve modeli kontrol edin", - "llamaCppDownloadEngineAndModelPrompt": "llama.cpp, llama-server motorunun ve seçilen OCR modelinin indirilmesini gerektiriyor. Şimdi indirilsin mi?", + "llamaCppDownloadEngineAndModelPrompt": "llama.cpp, llama-server motorunun ve seçilen OCR modelinin indirilmesini gerektiriyor. Şimdi indirilsin mi?", "llamaCppDownloadEnginePrompt": "llama.cpp, llama-server motorunun indirilmesini gerektiriyor. Şimdi indirilsin mi?", "llamaCppDownloadModelPrompt": "llama.cpp, seçilen OCR modelinin indirilmesini gerektiriyor. Şimdi indirilsin mi?", - "crispEmbedNotDownloaded": "CrispEmbed motoru/modeli indirilmedi - toplu dönüştürme ayarlarından indirin", + "crispEmbedNotDownloaded": "CrispEmbed motoru/modeli indirilmedi - toplu dönüştürme ayarlarından indirin", "crispEmbedReturnedNoText": "CrispEmbed hiç metin döndürmedi - modeli kontrol edin", - "appleVisionReturnedNoText": "Apple Vision metin döndürmedi - dili kontrol edin", - "crispEmbedSettingsTitle": "CrispEmbed ayarları", + "appleVisionReturnedNoText": "Apple Vision metin döndürmedi - dili kontrol edin", + "crispEmbedSettingsTitle": "CrispEmbed ayarları", "crispEmbedDescription": "Birden fazla model arka ucu içeren yerel OCR motoru - motoru ve modelleri buradan indirin.", - "trainNOcrDatabase": "nOCR veritabanını eğit...", + "trainNOcrDatabase": "nOCR veritabanını eğit...", + "optimizeNOcrDatabase": "nOCR veritabanını iyileştir...", + "optimizeNOcrDatabaseQuestionXY": "\"{0}\" nOCR veritabanından, karakterleri benzer karakterlerden ayırt etmeye yaramayan satırlar kaldırılsın mı?\n\nBu, veritabanını küçültür ve OCR'yi hızlandırır; genellikle hatalar da azalır. Bir dakikaya kadar sürebilir.\n\nYedek \"{1}\" olarak kaydedilir.", + "optimizingNOcrDatabaseXY": "nOCR veritabanı iyileştiriliyor... {0}/{1}", + "nOcrDatabaseOptimizedXYZ": "\"{0}\" nOCR veritabanı iyileştirildi: {1:#,##0} -> {2:#,##0} satır.", "startTraining": "Eğitimi başlat", "abortTraining": "Eğitimi iptal et", "trainingOptions": "Eğitim seçenekleri", - "trainNOcrSubtitle": "Yüklü yazı tiplerindeki karakterleri oluşturur ve nOCR'ye bunların şekillerini öğretir. Alt yazılarınızın kullandığı yazı tiplerini seçin.", + "trainNOcrSubtitle": "Yüklü yazı tiplerindeki karakterleri oluşturur ve nOCR'ye bunların şekillerini öğretir. Alt yazılarınızın kullandığı yazı tiplerini seçin.", "xFontsSelected": "{0:#,##0} seçildi", "subtitleFonts": "Alt yazı yazı tipleri", "emptyDatabase": "Boş veritabanı", @@ -4249,7 +4259,7 @@ "assa": { "assaDraw": "ASSA Çizim", "drawSelectTool": "Seç (noktaları taşı)", - "drawUseShapeForErase": "Silme için şekil kullan (iclip)", + "drawUseShapeForErase": "Silme için şekil kullan (iclip)", "drawLineTool": "Çizgi Aracı (F4)", "drawBezierTool": "Bezier Eğrisi (F5)", "drawRectangleTool": "Dikdörtgen (F6)", @@ -4262,7 +4272,7 @@ "drawZoomOut": "Uzaklaştır (Ctrl+-)", "drawResetView": "Görünümü Sıfırla (Ctrl+0)", "drawToggleGrid": "Izgarayı Aç/Kapat (Ctrl+G)", - "drawTogglePreview": "Ön izlemeyi değiştir (F9)", + "drawTogglePreview": "Ön izlemeyi değiştir (F9)", "drawPreviewFailed": "Ön izleme başarısız oldu - libass (subtitles filtresi) içeren ffmpeg gereklidir", "drawCopyToClipboard": "Panoya Kopyala (Ctrl+C)", "drawShapes": "Şekiller", @@ -4271,7 +4281,7 @@ "drawSelectedLayer": "Seçili katman", "drawToolX": "Araç: {0}", "drawHelpText": "Nokta eklemek için tıklayın • Seçenekler için sağ tıklayın • Seçim aracı: boyutlandırmak/döndürmek için tutamaçları sürükleyin (Shift = oranı/15°'yi koru) • Şekli kapatmak için Enter/F8 • Kaydırmak için Shift+sürükle veya orta fare tuşu • Yakınlaştırmak/uzaklaştırmak için Ctrl+Fare Tekerleği", - "drawMoveToLayer": "Katmana taşı", + "drawMoveToLayer": "Katmana taşı", "drawFlipHorizontal": "Yatay olarak çevir", "drawFlipVertical": "Dikey olarak çevir", "drawHideShape": "Şekli gizle", @@ -4289,7 +4299,7 @@ "drawPosition": "Konum", "drawZoom": "Yakınlaştırma", "drawShapeTool": "Şekil aracı", - "drawColorPickerTool": "Bir şekilden veya arka plandan renk seç", + "drawColorPickerTool": "Bir şekilden veya arka plandan renk seç", "drawShapeLibrary": "Şekil kitaplığı", "drawShapeLibraryHint": "Bir şekil yerleştirmek için tuval üzerine sürükleyin veya varsayılan boyut için tıklayın. Shift tuşu oranlarını korur.", "drawAddToLibrary": "Şekil kitaplığına ekle...", @@ -4338,7 +4348,7 @@ "drawBackgroundNone": "Arka plan yok", "drawBackgroundStretch": "Görseli tuvale esnet", "drawBackgroundOpacity": "Arka plan opaklığı", - "drawBackgroundVideoFrameFailed": "{0} konumunda bir video karesi alınamadı.", + "drawBackgroundVideoFrameFailed": "{0} konumunda bir video karesi alınamadı.", "drawImages": "Görseller", "drawConvertToLine": "Segmenti düz çizgiye dönüştür", "drawConvertShapeToCurves": "Tüm çizgileri eğrilere dönüştür", @@ -4348,7 +4358,7 @@ "drawSvgNoShapes": "SVG dosyasında doldurulmuş şekil veya kontur bulunamadı.", "progressBarTitle": "ASSA ilerleme çubuğu", "progressBarSettings": "İlerleme çubuğu", - "mouseOverColor": "Fareyle üzerine gelme rengi", + "mouseOverColor": "Fareyle üzerine gelme rengi", "clickedColor": "Tıklama rengi", "progressBarForeColor": "Ön plan rengi", "progressBarSquareCorners": "Kare köşeler", @@ -4377,12 +4387,12 @@ "attachments": "Eklentiler", "fontCollectorNoFontsToCopy": "Kopyalanacak yazı tipi dosyası bulunamadı.", "fontCollectorXFontFilesCopiedToY": "{0} yazı tipi dosyası {1} konumuna kopyalandı", - "fontCollectorEmbedFontsDotDotDot": "Yazı tiplerini alt yazıya göm...", + "fontCollectorEmbedFontsDotDotDot": "Yazı tiplerini alt yazıya göm...", "fontCollectorEmbedXFontsSizeYZPrompt": "{0} yazı tipi dosyası alt yazıya gömülsün mü?\n\nToplam yazı tipi boyutu: {1} - ASSA dosyasında metin olarak yaklaşık {2}.\n\n{3}", "fontCollectorAndXMoreFonts": "...ve {0} tane daha", "fontCollectorNoFontsToEmbed": "Gömülecek yeni yazı tipi yok - gerekli yazı tipleri zaten gömülü veya bulunamadı.", "fontCollectorXFontFilesEmbedded": "{0} yazı tipi dosyası alt yazıya gömüldü.", - "fontCollectorTrimFonts": "Yalnızca kullanılan karakterleri göm (yazı tiplerini kırp)", + "fontCollectorTrimFonts": "Yalnızca kullanılan karakterleri göm (yazı tiplerini kırp)", "trimFontsDotDotDot": "Yazı tiplerini kullanılan karakterlere kırp...", "trimFontsPromptX": "{0} gömülü yazı tipi yalnızca şu anda kullanılan karakterlere kırpılsın mı?\n\nAlt yazıya daha sonra eklenecek metinlerde eksik karakterler görünebilir, bu nedenle kırpma işlemini son adım olarak yapın.", "trimFontsNoFontsToTrim": "Kırpılacak gömülü yazı tipi yok.", @@ -4402,7 +4412,7 @@ "resolutionResamplerFromVideo": "Videodan...", "resolutionResamplerSourceAndTargetEqual": "Kaynak ve hedef çözünürlük aynı - yapılacak bir şey yok.", "resolutionResamplerNothingSelected": "Lütfen değiştirmek için en az bir seçenek belirleyin.", - "resolutionResamplerVideoDiffers": "Alt yazı çözünürlüğü ({0}x{1}), video çözünürlüğünden ({2}x{3}) farklı.\nAlt yazı çözünürlüğü videoya uyarlanacak şekilde değiştirilsin mi?", + "resolutionResamplerVideoDiffers": "Alt yazı çözünürlüğü ({0}x{1}), video çözünürlüğünden ({2}x{3}) farklı.\nAlt yazı çözünürlüğü videoya uyarlanacak şekilde değiştirilsin mi?", "resolutionResamplerAskOnVideoOpen": "Farklı çözünürlükte bir video açıldığında sor", "backgroundBoxGenerator": "Arka plan kutusu oluştur", "backgroundBoxFillWidth": "Genişliği doldur", @@ -4419,12 +4429,12 @@ "backgroundBoxScroll": "Parşömen / Kaydırma", "stylesInFile": "Dosyadaki stiller", "stylesSaved": "Stiller kaydedildi", - "allCategories": "[Tümü]", - "newCategory": "Yeni kategori...", - "renameCategory": "Kategoriyi yeniden adlandır...", - "deleteCategory": "Kategoriyi sil", - "moveToCategoryDotDotDot": "Kategoriye taşı...", - "deleteCategoryQuestion": "\"{0}\" kategorisi silinsin mi? İçindeki stiller varsayılan kategoriye taşınacaktır.", + "allCategories": "[Tümü]", + "newCategory": "Yeni kategori...", + "renameCategory": "Kategoriyi yeniden adlandır...", + "deleteCategory": "Kategoriyi sil", + "moveToCategoryDotDotDot": "Kategoriye taşı...", + "deleteCategoryQuestion": "\"{0}\" kategorisi silinsin mi? İçindeki stiller varsayılan kategoriye taşınacaktır.", "stylesTitleX": "Stiller - {0}", "propertiesTitleX": "Özellikler - {0}", "attachmentsTitleX": "Ekler - {0}", @@ -4435,14 +4445,14 @@ "fontsAndGraphics": "Yazı tipleri ve grafikler", "wrapStyle": "Sarma stili", "borderAndShadowScaling": "Kenarlık ve gölge ölçeklendirme", - "collisions": "Çakışmalar", + "collisions": "Çakışmalar", "playDepth": "Oynatma derinliği", "timer": "Zamanlayıcı", "originalScript": "Orijinal betik", "graphics": "Grafikler", "copyToStorageStyles": "Depo stillerine kopyala", "copyToFileStyles": "Dosya stillerine kopyala", - "styleXAlreadyExistsInStorage": "\"{0}\" adında bir stil depolamada zaten var.\n\nÜzerine yazılsın mı yoksa ikisi de korunsun mu (kopyaya yeni bir ad verilir)?", + "styleXAlreadyExistsInStorage": "\"{0}\" adında bir stil depolamada zaten var.\n\nÜzerine yazılsın mı yoksa ikisi de korunsun mu (kopyaya yeni bir ad verilir)?", "styleXAlreadyExistsInFile": "\"{0}\" adında bir stil dosyada zaten var.\n\nÜzerine yazılsın mı yoksa ikisi de korunsun mu (kopyaya yeni bir ad verilir)?", "styleNameCannotBeEmpty": "Stil adı boş bırakılamaz.", "styleNameXIsUsedMoreThanOnce": "\"{0}\" stil adı dosyada birden fazla stil tarafından kullanılıyor.", @@ -4451,16 +4461,16 @@ "doThisForAllConflictingStyles": "Bunu çakışan tüm stiller için yap", "setStyleAsDefault": "Stili varsayılan yap", "takeUsagesFromDotDotDot": "Kullanımları şuradan al...", - "replaceStyleWithDotDotDot": "Stili şununla değiştir...", + "replaceStyleWithDotDotDot": "Stili şununla değiştir...", "noAttachmentsFound": "Seçili ASSA dosyasında ek bulunamadı.", "deleteStyleQuestion": "Stil silinsin mi?", "deleteStylesQuestion": "Stiller silinsin mi?", - "deleteXStylesQuestion": "{0} stil silinsin mi?", + "deleteXStylesQuestion": "{0} stil silinsin mi?", "deleteXStylesFromStorageQuestion": "Depolamadan {0} stil silinsin mi?", "deleteStyleXFromStorageQuestion": "\"{0}\" stili depolamadan silinsin mi?", "deleteStyleXFromFileQuestion": "\"{0}\" stili mevcut dosyadan silinsin mi?", "openStyleImportFile": "Stilleri içe aktarmak için alt yazı dosyası aç", - "styleImportFiles": "Stil dosyaları (.ass, .sty, Subtitle Edit 4 kategorisi .template)", + "styleImportFiles": "Stil dosyaları (.ass, .sty, Subtitle Edit 4 kategorisi .template)", "ssaStyleImportFiles": "Stil dosyaları (.ssa, .ass, .sty)", "primary": "Birincil", "secondary": "İkincil", @@ -4477,8 +4487,8 @@ "popIn": "Aniden belirme", "spinIn": "Dönerek belirme", "setPosition": "Konumu ayarla", - "rotation": "Döndürme", - "scaleX": "Ölçek X", + "rotation": "Döndürme", + "scaleX": "Ölçek X", "scaleY": "Ölçek Y", "spacing": "Aralık", "angle": "Açı", @@ -4537,7 +4547,7 @@ "advancedEffectHeartsDescription": "Üç farklı şekildeki kalpler ekranın üstünden nazikçe yağar, her alt yazı boyunca takla atarak solar", "advancedEffectWordSpacing": "Kelime aralığı", "advancedEffectWordSpacingDescription": "Daha iyi okunabilirlik için \\fsp etiketi kullanarak kelimeler arasındaki boşluğu artırır", - "advancedEffectKaraokeRightToLeft": "Sağdan sola", + "advancedEffectKaraokeRightToLeft": "Sağdan sola", "advancedEffectFancyKaraokeAutoDetectActiveWord": "Aktif kelimeyi otomatik algıla", "advancedEffectFancyKaraokeGlow": "Aktif kelime parlaması", "advancedEffectFancyKaraokeActiveColor": "Aktif renk", @@ -4558,7 +4568,7 @@ "advancedEffectSlideInRight": "Sağdan kayarak giriş", "advancedEffectSlideInRightDescription": "Metin ekranın sağ dışından içeri kayar, bekler, sonra tekrar sağa çıkar", "advancedEffectFadeInOutDescription": "Metin her alt yazının başında belirir ve sonunda kararır", - "advancedEffectWordFlip3D": "3D kelime çevirme", + "advancedEffectWordFlip3D": "3D kelime çevirme", "advancedEffectWordFlip3DDescription": "Her kelime konuşulurken 3D çevirme ve esnek sıçrama ile katlanarak girer — modern \"Shorts\" alt yazı stili", "advancedEffectCinematicTitle": "Sinematik başlık", "advancedEffectCinematicTitleDescription": "Harfler yumuşak bir bulanıklıktan netleşirken geniş aralıktan normale doğru yaklaşır — klasik film başlığı ortaya çıkarma", @@ -4609,7 +4619,7 @@ "detailGapToNext": "sonrakine: {0} < {1} ms", "detail": "Ayrıntı", "tip": "Bir satıra gitmek için üzerine çift tıklayın (veya Enter tuşuna basın). Hangi denetimlerin çalışacağı ve sınırları Ayarlar > Genel bölümünden ayarlanır.", - "exportAsText": "Metin dosyası (.txt)...", + "exportAsText": "Metin dosyası (.txt)...", "exportAsExcel": "Excel dosyası (.xlsx)...", "exportAsHtml": "Web sayfası (.html)...", "exportFileX": "Dosya: {0}", diff --git a/src/ui/Assets/Languages/Ukrainian.json b/src/ui/Assets/Languages/Ukrainian.json index 72e8ab6c268..503050b496b 100644 --- a/src/ui/Assets/Languages/Ukrainian.json +++ b/src/ui/Assets/Languages/Ukrainian.json @@ -1,6 +1,6 @@ { "title": "Subtitle Edit", - "version": "v5.2.0-rc7", + "version": "v5.3.0", "translatedBy": "Kilo Code", "cultureName": "uk-UA", "general": { @@ -71,6 +71,24 @@ "abort": "Перервати", "actor": "Актор", "actors": "Актори", + "SetActorDotDotDot": "Встановити актора...", + "ActorPickerTitle": "Встановити актора", + "ActorPickerFilterHint": "Введіть текст для фільтрування або ім'я нового актора", + "ActorPickerKeysHint": "Натисніть цифру або Enter, щоб встановити актора. Alt+Вгору/Вниз змінює порядок, Delete видаляє актора.", + "ActorPickerNewActorX": "Новий актор \"{0}\"", + "ActorPickerLinesSelectedX": "Виділено рядків: {0}", + "SetStyleDotDotDot": "Встановити стиль...", + "StylePickerTitle": "Встановити стиль", + "StylePickerFilterHint": "Введіть текст для фільтрування або назву нового стилю", + "StylePickerKeysHint": "Натисніть цифру (1-9, 0), щоб встановити стиль для всіх виділених рядків.", + "StylePickerNewStyleX": "Новий стиль \"{0}\"", + "StylePickerNewStyleInfo": "Натисніть Enter, щоб додати цей стиль із типовими параметрами та встановити його для виділених рядків. Пізніше змінити шрифт, кольори та позицію можна через \"Керування стилями...\".", + "StylePickerCurrentStyleX": "Поточний стиль: {0}", + "StylePickerLinesUsingStyleX": "Рядків із цим стилем: {0}", + "StylePickerMarginsXYZ": "Ліве {0}, праве {1}, вертикальне {2}", + "StylePickerSetStyle": "_Встановити стиль", + "StylePickerManageStyles": "Керування стилями...", + "StylePickerManageStylesHint": "Закрити це вікно та відкрити керування стилями для редагування шрифтів, кольорів, позиції тощо", "add": "Додати", "addDotDotDot": "Додати...", "addToNamesListCaseSensitive": "Додати до списку імен (з урахуванням регістру)", @@ -79,7 +97,6 @@ "adjustAlpha": "Налаштувати прозорість", "adjustDisplayDuration": "Налаштувати тривалість показу", "adjustment": "Коригування", - "advanced": "Розширені", "advancedSettings": "Розширені налаштування", "after": "Після", "alignment": "Вирівнювання", @@ -112,7 +129,6 @@ "batchMode": "Пакетний режим", "bdnXml": "BDN/xml", "before": "До", - "beforeX": "До \"{0}\"", "beginning": "Початок", "bluRaySup": "Blu-ray (sup)", "bold": "Жирний", @@ -124,7 +140,6 @@ "bookmarkEdit": "Редагувати закладку", "bookmarkEditForSelectedLinesX": "Редагувати закладку для {0} виділених рядків", "bookmarksList": "Список закладок", - "borderColor": "Колір рамки", "borderStyle": "Стиль рамки", "bottomCenter": "Знизу-по центру", "bottomLeft": "Знизу-ліворуч", @@ -147,7 +162,6 @@ "changeCasing": "Змінити регістр", "changeFormatting": "Змінити форматування", "changeFrameRate": "Змінити частоту кадрів", - "changeLanguageFilter": "Змінити фільтр мови...", "changeOnce": "Змінити один раз", "changeSpeed": "Змінити швидкість", "character": "Символ", @@ -169,7 +183,6 @@ "conversionCancelledByUser": "Перетворення скасовано користувачем", "convert": "Перетворити", "converted": "Перетворено", - "convertingDotDotDot": "Перетворення...", "convertingXofYDotDoDot": "Перетворення {0:#,###,##0}/{1:#,###,##0}...", "copy": "Копіювати", "copyImageToClipboard": "Копіювати зображення в буфер обміну", @@ -177,7 +190,6 @@ "couldNotSaveFileXErrorY": "Не вдалося зберегти файл \"{0}\". Помилка: {1}", "count": "Кількість", "cps": "Символів/с", - "currentSubtitle": "Поточні субтитри", "currentVideoPosition": "Поточна позиція відео", "cut": "Вирізати", "dark": "Темна", @@ -222,7 +234,6 @@ "expand": "Розгорнути", "export": "Експорт", "exportDotDotDot": "Експорт...", - "exportToX": "Експортувати в {0}", "extendAfter": "Розширити/скоротити до наступного рядка", "extendBefore": "Розширити/скоротити до попереднього рядка", "extendSelectedLinesToNextShotChangeOrNextSubtitle": "Розширити виділені рядки до наступного зміни кадру (або наступних субтитрів)", @@ -237,7 +248,6 @@ "fileExtension": "Розширення файлу", "fileName": "Ім'я файлу", "fileNameX": "Ім'я файлу: {0}", - "fileNameXAndSize": "Ім'я файлу: {0} ({1})", "fileSaved": "Файл збережено", "fileSavedToX": "Файл збережено в {0}", "fileXAlreadyExists": "Файл \"{0}\" вже існує.", @@ -247,8 +257,6 @@ "find": "Знайти", "findNext": "Знайти далі", "findTextX": "Знайти текст - {0}", - "fiveHundredMilliseconds": "500 мілісекунд", - "fiveSeconds": "П'ять секунд", "fix": "Виправити", "fixCommonErrors": "Виправити загальні помилки", "fixRightToLeft": "Виправити з правого на ліве", @@ -272,13 +280,11 @@ "foundXInLineYZ": "Знайдено \"{0}\" в рядку {1}, позиція {2}", "foundXMatches": "Знайдено {0} збігів", "frameRate": "Частота кадрів", - "frameRateX": "Частота кадрів: {0:0.0###}", "frames": "Кадри", "from": "Від", "fromCurrentVideoPosition": "від поточної позиції відео", "gap": "Прогалина", "general": "Загальне", - "generalText": "Загальне", "generate": "Створити", "generateImportShotChanges": "Створити/імпортувати зміни кадрів", "generating": "Створення...", @@ -295,12 +301,8 @@ "help": "Допомога", "hex": "Шістнадцятковий", "hide": "Приховати", - "hidePreview": "Приховати попередній перегляд", "history": "Історія", "horizontal": "Горизонтально", - "hourMinutesSecondsDecimalSeparatorMilliseconds": "Год:хв:с{0}мс", - "hourMinutesSecondsFrames": "Год:хв:с:кадри", - "ip": "IP", "id": "ID", "image": "Зображення", "imageSaved": "Зображення збережено", @@ -357,6 +359,7 @@ "mergeSelectedLinesDialog": "Об'єднати виділені рядки як діалог", "mergeWithLineAfterAndAutoBreak": "Об'єднати з наступним і автоматично розбити", "mergeWithLineAfterKeepBreaks": "Об'єднати з наступним (зберегти розбиття)", + "MergeWithLineAfterAndUnbreak": "Об'єднати з наступним і прибрати розриви рядків", "mergeWithLineBeforeAndAutoBreak": "Об'єднати з попереднім і автоматично розбити", "mergeWithLineBeforeKeepBreaks": "Об'єднати з попереднім (зберегти розбиття)", "middleCenter": "Посередині-по центру", @@ -370,10 +373,11 @@ "moveAllShotChangeOneFrameBack": "Перемістити всі зміни кадрів на один кадр назад", "moveAllShotChangeOneFrameForward": "Перемістити всі зміни кадрів на один кадр вперед", "moveDown": "Перемістити вниз", + "Increase": "Збільшити", + "Decrease": "Зменшити", "moveUp": "Перемістити вгору", "multipleReplace": "Множинна заміна", "name": "Ім'я", - "negative": "Негатив", "new": "Новий", "newDotDotDot": "Новий...", "newProfile": "Новий профіль", @@ -400,20 +404,15 @@ "offsetY": "Зсув Y", "ok": "_Гаразд", "oneFile": "Один файл", - "oneHundredMilliseconds": "100 мілісекунд", - "oneSecond": "Одна секунда", "oneSecondBack": "Одна секунда назад", "oneSecondForward": "Одна секунда", - "onlineVideoFeatureNotAvailable": "Функція недоступна для онлайн відео", "openContainingFolder": "Відкрити теку, що містить", "openDictionaryFolder": "Відкрити теку словника", "openFile": "Відкрити файл", "openImageBasedSubtitle": "Відкрити субтитри на основі зображень", "openImageFile": "Відкрити файл зображення", - "openOriginalSubtitleFile": "Відкрити оригінальний файл субтитрів...", "openOriginalSubtitleFileTitle": "Відкрити оригінальний файл субтитрів", "openOutputFolder": "Відкрити теку виводу", - "openSubtitle": "Відкрити субтитри...", "openSubtitleFileTitle": "Відкрити файл субтитрів", "openSubtitles": "Відкрити субтитри...", "openVideoFile": "Відкрити відеофайл...", @@ -426,11 +425,6 @@ "outlineWidth": "Ширина контуру", "outputFolder": "Тека виводу", "outputProperties": "Властивості виводу", - "overlap": "Перекриття", - "overlapNextX": "Перекриття наступного ({0:#;##0.###})", - "overlapPreviousLineX": "Перекриття попереднього рядка ({0:#;##0.###})", - "overlapStartAndEnd": "Перекриття початку і кінця", - "overlapX": "Перекриття ({0:#;##0.###})", "overwriteExistingFiles": "Перезаписати існуючі файли", "overwriteFilesInFolderX": "Перезаписати файли в теці: \"{0}\"", "overwriteQuestion": "Перезаписати?", @@ -443,6 +437,7 @@ "percent": "Відсоток", "pickLayer": "Встановити шар", "pickOllamaModel": "Вибрати модель Ollama", + "PickModel": "Вибрати модель", "llamaCppUseRemoteServer": "Використовувати зовнішній сервер (URL)", "pickOutputFolder": "Вибрати теку виводу", "pickResolutionFromCurrentVideo": "Вибрати роздільну здатність з поточного відео", @@ -482,7 +477,6 @@ "refresh": "Оновити", "region": "Регіон", "regularExpression": "Регулярний вираз", - "regularExpressionIsNotValid": "Регулярний вираз недійсний!", "relativeToCurrentVideoPosition": "Відносно поточної позиції відео", "remove": "Видалити", "removeAlignment": "Видалити вирівнювання", @@ -507,7 +501,7 @@ "reset": "Скинути", "resizePanels": "Змінити розмір панелей", "resolution": "Роздільна здатність", - "resolutionX": "Роздільна здатність: {0}", + "Retry": "Повторити", "reverseRightToLeftStartEnd": "Зворотній RTL початок/кінець", "right": "Праворуч", "rightToLeft": "Справа наліво", @@ -545,8 +539,6 @@ "selectedlinesX": "Виділені рядки: {0}", "sensitivity": "Чутливість", "separator": "Роздільник", - "sessionKey": "Ключ сесії", - "sessionKeyGenerate": "Створити новий ключ", "setEnd": "Встановити кінець", "setEndAndGoToNext": "Встановити кінець і перейти до наступного", "setEndAddNewAndGoToNew": "Встановити кінець, додати новий і перейти до нового", @@ -565,8 +557,9 @@ "shadowColor": "Колір тіні", "shadowWidth": "Ширина тіні", "shiftLineBy": "Зсунути рядок на", + "ShotIn": "Початок кадру", + "ShotOut": "Кінець кадру", "shortcut": "Ярлик", - "shortcutX": "Ярлик: {0}", "shortcuts": "Ярлики", "show": "Показати", "showActorColumn": "Показати стовпець \"Актор\"", @@ -577,17 +570,19 @@ "showStartColumn": "Показати стовпець \"Початок\"", "showHideColumn": "Показати стовпець \"Приховати\"", "showHistory": "Показати історію", + "ClearHistory": "Очистити історію", + "RemoveFromHistory": "Видалити з історії", "showLayerColumn": "Показати стовпець \"Шар\"", "showPreview": "Показати попередній перегляд", "showShotChangesList": "Показати список змін кадрів", + "ShowShotInColumn": "Показати стовпець \"Початок кадру\"", + "ShowShotOutColumn": "Показати стовпець \"Кінець кадру\"", "showStyleColumn": "Показати стовпець \"Стиль\"", "showTeletext": "Показати стовпець телетексту в списку", "showTimeCodes": "Показати коди часу", "showWpmColumn": "Показати стовпець \"Слів/хв\"", "showPixelWidthColumn": "Показати стовпець \"Ширина в пікселях\"", "shrink": "Зменшити", - "singleBox": "Одна коробка", - "singleLineLengths": "Довжина одного рядка:", "singleMode": "Один режим", "textTotalLength": "Текст - загальна довжина", "textSingleLineMaxLength": "Текст - максимальна довжина одного рядка", @@ -633,16 +628,16 @@ "strikeout": "Закреслення", "style": "Стиль", "styleExaggeration": "Переоцінка стилю", - "styleLanguage": "Стиль / Мова", "styles": "Стилі", "subtitleFile": "Файл субтитрів", + "SubtitleFileChangedOutsideX": "Файл \"{0}\" було змінено поза Subtitle Edit.\n\nПерезавантажити його і втратити ваші зміни?", "subtitleFileName": "Ім'я файлу субтитрів", "subtitleFileSaved": "Файл субтитрів збережено", "subtitleFileSavedToX": "Файл субтитрів збережено в {0}", "subtitleFiles": "Файли субтитрів", "subtitleFormats": "Формати субтитрів", "subtitleLoadedX": "Субтитри завантажено: {0}", - "subtitleXOfY": "Субтитри {0} з {1}", + "SubtitleReloadedChangedOutsideX": "Субтитри перезавантажено (змінено поза Subtitle Edit): {0}", "suffix": "Суфікс", "suggestions": "Пропозиції", "sync": "Синхронізація", @@ -654,7 +649,6 @@ "teletextColor": "Колір телетексту", "teletextLine": "Рядок телетексту", "tenHours": "10 годин", - "tenMilliseconds": "10 мілісекунд", "text": "Текст", "textColor": "Колір тексту", "textFiles": "Текстові файли", @@ -679,9 +673,6 @@ "topLeft": "Зверху-ліворуч", "topRight": "Зверху-праворуч", "totalAdjustmentX": "Загальне коригування: {0}", - "totalFramesX": "Всього кадрів: {0:#;##0.##}", - "totalLengthX": "Загальна довжина: {0}", - "totalLengthXSplitLine": "Загальна довжина: {0} (розділити рядок!)", "translate": "Перекласти", "translatePercentX": "Переклад: {0}%", "translateRow": "Перекласти рядок", @@ -690,6 +681,7 @@ "type": "Тип", "unbreak": "Розбити текст", "unbreakLines": "Розбити рядки", + "Undelete": "Відновити видалене", "underline": "Підкреслення", "undo": "Скасувати", "unknown": "Невідомо", @@ -706,13 +698,11 @@ "usages": "Використання", "use": "Використовувати", "useAlways": "Використовувати завжди", - "useLargerFontForThisWindow": "Використовувати більший шрифт для цього вікна", "useOnce": "Використовувати один раз", "useOutputFolder": "Використовувати теку виводу", "useSourceFolder": "Використовувати вихідну теку", "useSourceResolution": "Використовувати вихідну роздільну здатність", "userName": "Ім'я користувача", - "userNameAlreadyInUse": "Ім'я користувача вже використовується", "vertical": "Вертикально", "video100MsBack": "Відео, 100 мілісекунд назад", "video100MsForward": "Відео, 100 мілісекунд вперед", @@ -726,14 +716,25 @@ "videoCustom3ForwardX": "Відео, користувацькі мілісекунди ({0:#,###,##0}) вперед, 3", "videoCustom4BackX": "Відео, користувацькі мілісекунди ({0:#,###,##0}) назад, 4", "videoCustom4ForwardX": "Відео, користувацькі мілісекунди ({0:#,###,##0}) вперед, 4", - "videoEncodingX": "Кодування відео: {0}", + "VideoCustom1BackAndPauseX": "Відео, користувацькі мілісекунди ({0:#,###,##0}) назад і пауза, 1", + "VideoCustom1ForwardAndPauseX": "Відео, користувацькі мілісекунди ({0:#,###,##0}) вперед і пауза, 1", + "VideoCustom2BackAndPauseX": "Відео, користувацькі мілісекунди ({0:#,###,##0}) назад і пауза, 2", + "VideoCustom2ForwardAndPauseX": "Відео, користувацькі мілісекунди ({0:#,###,##0}) вперед і пауза, 2", + "VideoCustom3BackAndPauseX": "Відео, користувацькі мілісекунди ({0:#,###,##0}) назад і пауза, 3", + "VideoCustom3ForwardAndPauseX": "Відео, користувацькі мілісекунди ({0:#,###,##0}) вперед і пауза, 3", + "VideoCustom4BackAndPauseX": "Відео, користувацькі мілісекунди ({0:#,###,##0}) назад і пауза, 4", + "VideoCustom4ForwardAndPauseX": "Відео, користувацькі мілісекунди ({0:#,###,##0}) вперед і пауза, 4", "videoExtension": "Розширення відеофайлу", "videoFile": "Відеофайл", "videoFileGenerated": "Відеофайл створено", "videoFileGeneratedX": "Відеофайл створено: \"{0}\"", + "VideoFilesGenerated": "Відеофайли створено", + "XVideosGenerated": "Створено відео: {0}", + "XOfYVideosGenerated": "Створено {0} з {1} відео", + "SavedInX": "Збережено в {0}", + "SavedInXFolders": "Збережено в {0} теках", "videoFiles": "Відеофайли", "videoAndAudioFiles": "Відео- та аудіофайли", - "videoInformation": "Інформація про відео", "videoOffset": "Зсув відео", "videoOneFrameBack": "Відео, один кадр назад", "videoOneFrameForward": "Відео, один кадр вперед", @@ -744,7 +745,6 @@ "videoPlayer": "Відеопрогравач", "videoPosition": "Позиція відео", "videoResolution": "Роздільна здатність відео", - "viewX": "Перегляд {0}", "visible": "Видимий", "voice": "Голос", "warning": "Попередження", @@ -752,9 +752,7 @@ "waveformCenterOnVideoPosition": "Центр хвильової форми на позиції відео", "waveformPasteFromClipboard": "Вставити з буфера обміну", "waveformSpectrogram": "Хвильова форма/спектрограма", - "webServiceUrl": "URL веб-сервісу", "width": "Ширина", - "windowPositionAndSize": "Позиція і розмір вікна", "wordsPerMin": "Слів/хв", "pixelWidth": "Ширина в пікселях", "wpm": "Слів/хв", @@ -764,8 +762,6 @@ "xNotFound": "\"{0}\" не знайдено", "xRequiresAnApiKey": "{0} потребує ключ API", "xRequiresAValidUrl": "{0} вимагає дійсний URL (напр. \"http://...\" або \"https://...\")", - "xSeconds": "{0:0.0##} секунд", - "xSubtitles": "{0:#,###,##0} субтитрів", "yes": "Так", "zeroBytes": "0 байт", "copyToClipboard": "Копіювати в буфер обміну", @@ -789,7 +785,6 @@ "installFolder": "Тека встановлення", "lineHeight": "Висота рядка", "multiMode": "Мультирежим", - "mute": "Без звуку", "setSubtitleStartAtVideoPositionSetEndAtKeyUpAndGoToNext": "Встановити початок у позиції відео, встановити кінець при відпусканні клавіші і перейти до наступного", "playSelectedLinesAndFocusWaveform": "Відтворити виділені рядки і сфокусувати хвильову форму", "playSelectedLinesWithLoopAndFocusWaveform": "Відтворити виділені рядки з циклом і сфокусувати хвильову форму", @@ -826,10 +821,8 @@ "playbackSpeed": "Швидкість відтворення", "hoursMinutesSecondsMilliseconds": "ГГ:ХХ:СС.МММ", "imageFiles": "Файли зображень", - "openAiCompatibleStt": "OpenAI-сумісний STT", "openAiCompatibleSttEndpoint": "URL кінцевої точки STT", "openAiCompatibleSttApiKey": "Ключ API", - "openAiCompatibleSttModel": "Модель", "openAiCompatibleSttExtraHeaders": "Додаткові заголовки", "openAiCompatibleSttTimeout": "Тайм-аут (секунди)", "openAiCompatibleSttLanguage": "Підказка мови", @@ -839,6 +832,9 @@ "dashScopeSttRegion": "Регіон", "dashScopeSttEnableWords": "Часові мітки на рівні слів", "dashScopeSttRegionKeyHint": "Примітка: ключі API Alibaba Cloud Model Studio прив'язані до регіону - переконайтеся, що вибраний регіон відповідає регіону, де було створено ключ API (Китай чи Міжнародний).", + "GoogleCloudSttProjectId": "ID проєкту (лише для Application Default Credentials)", + "GoogleCloudSttBucketName": "Сховище bucket (порожньо = авто)", + "GoogleCloudSttDynamicBatching": "Динамічне пакетування (приблизно в 5 разів дешевше, без гарантії затримки)", "onlineSttApiKeyMissing": "Потрібен ключ API. Будь ласка, введіть свій ключ API і спробуйте ще раз.", "openAiCompatibleSttAutoTranscribeOnAudioSelection": "Автоматично транскрибувати новий вибір хвильової форми через розпізнавання мови", "openAiCompatibleSttStream": "Потокова відповідь", @@ -848,11 +844,9 @@ "configurationRequired": "Потрібне налаштування", "transcriptionError": "Помилка транскрипції", "transcriptionComplete": "Транскрипцію завершено", - "transcribingWithOpenAi": "Транскрибування за допомогою сервісу OpenAI...", "processingResponse": "Обробка відповіді...", "unauthorizedApiKey": "Неавторизовано: недійсний ключ API або недостатньо дозволів.", "requestTimeout": "Тайм-аут запиту: сервер відповідав занадто довго.", - "noSegmentsReturned": "Сервіс транскрипції не повернув жодного сегмента.", "openAiCompatibleSttUrlMissing": "Потрібен URL кінцевої точки OpenAI-сумісного STT. Будь ласка, введіть URL і спробуйте ще раз.", "openAiCompatibleSttUrlNotResponding": "Кінцева точка OpenAI-сумісного STT не відповіла:\n{0}\n\nБудь ласка, перевірте URL і чи запущено сервер.", "transcriptionFailed": "Транскрипція не вдалася", @@ -896,10 +890,10 @@ "replace": "_Замінити...", "multipleReplace": "_Множинна заміна...", "goToLineNumber": "_Перейти до номера рядка...", + "commandPalette": "_Палітра команд...", "rightToLeftMode": "Режим справа наліво", "modifySelectionDotDotDot": "Змінити _вибір...", "tools": "_Інструменти", - "toolsSelectedLines": "_Інструменти (виділені рядки)", "adjustDurations": "_Налаштувати тривалість...", "applyDurationLimits": "Застосувати обмеження тривалості...", "batchConvert": "_Пакетне перетворення...", @@ -986,6 +980,7 @@ "closeTranslation": "Закрити _переклад", "fillSelectedLinesWithClipboard": "Заповнити виділені рядки текстом з буфера обміну", "beautifyTimeCodes": "Покращити коди часу...", + "improveTimeCodes": "Уточнити коди часу (примусове вирівнювання)...", "evenlyDistributeLines": "Рівномірно розподілити рядки (за CPS)", "mergeContinuationLines": "Об'єднати рядки продовження...", "snapAllTimesToFrames": "Прив'язати усі часи до кадрів", @@ -1044,6 +1039,9 @@ "selectCurrentLineWhilePlayingHint": "Вибрати поточні субтитри під час відтворення {0}", "videoPosition": "Позиція відео {0}", "videoPositionTextBox": "Текстове поле позиції відео {0}", + "toolbarLineBreakX": "Розрив рядка {0} (решта панелі інструментів продовжується в новому ряду)", + "initialText": "Початковий текст виділеного рядка", + "initialTextHint": "Текст виділеного рядка в тому вигляді, яким він був у момент виділення", "hideWaveformToolbar": "Приховати панель інструментів {0}", "resetZoomAndSpeed": "Скинути масштабування та швидкість відтворення {0}", "removeBlankLines": "Видалити порожні рядки {0}", @@ -1057,6 +1055,15 @@ "textPlayHint": "Відтворити поточні субтитри і зупинитися в кінці {0}", "textPauseHint": "Призупинити відтворення {0}", "textNextHint": "Відтворити наступні субтитри і зупинитися в кінці {0}", + "moveSelectedLines": "Перемістити виділені рядки (кнопки назад/вперед)", + "moveSelectedLinesAndFollowing": "Перемістити виділені рядки та всі наступні (кнопки назад/вперед)", + "moveAllLines": "Перемістити всі рядки (кнопки назад/вперед)", + "moveSelectedLinesBackHint": "Перемістити виділені рядки на {0} ms назад {1}", + "moveSelectedLinesForwardHint": "Перемістити виділені рядки на {0} ms вперед {1}", + "moveSelectedLinesAndFollowingBackHint": "Перемістити виділені рядки та всі наступні на {0} ms назад {1}", + "moveSelectedLinesAndFollowingForwardHint": "Перемістити виділені рядки та всі наступні на {0} ms вперед {1}", + "moveAllLinesBackHint": "Перемістити всі рядки на {0} ms назад {1}", + "moveAllLinesForwardHint": "Перемістити всі рядки на {0} ms вперед {1}", "guessStartNoLineSelected": "Вгадати початок: рядок не вибрано", "guessStartTimeCodesLocked": "Вгадати початок: коди часу заблоковано", "guessStartNoWaveform": "Вгадати початок: немає хвильової форми", @@ -1078,6 +1085,9 @@ "charactersPerSecond": "Символів/секунду: {0}", "chooseColumn": "Вибрати стовпець", "columnPaste": "Вставити в стовпець", + "commandPaletteTitle": "Палітра команд", + "commandPaletteSearchPlaceholder": "Введіть текст для пошуку команд...", + "commandPaletteNoCommandsFound": "Немає відповідних команд", "createdEmptyTranslation": "Створено порожній переклад з поточних субтитрів", "deleteText": "Видалити текст", "deleteTextAndShiftCellsUp": "Видалити текст і зсунути комірки вгору", @@ -1113,18 +1123,20 @@ "noTextInClipboard": "Немає тексту в буфері обміну", "oneLineCopiedFromOriginal": "Один рядок скопійовано з оригінальних субтитрів", "oneLineCopiedToOriginal": "Один рядок скопійовано в оригінальні субтитри", + "onlineOnlyVideo": "Відео лише в мережі", + "onlineOnlyVideoXSizeYDownloadAndOpen": "Відеофайл \"{0}\" ({1}) зберігається лише в мережі, наприклад у Dropbox або iCloud Drive, і його потрібно завантажити, перш ніж відкрити.\n\nЗавантажити та відкрити його зараз?", "oneLineMerged": "Один рядок об'єднано", "oneLineSwitched": "Один рядок перемкнено", "overwriteExistingCells": "Перезаписати існуючі комірки", "overwriteOrShiftCellsDown": "Перезаписати/зсунути комірки вниз", "parsingMatroskaFile": "Аналіз файлу Matroska...", + "readingDvdSubtitles": "Читання субтитрів DVD...", "pasteFromClipboardDotDotDot": "Вставити з буфера обміну...", "redoPerformed": "Повторено", "redoPerformedXActionLeft": "Повторено (дій залишилося: {0})", "removedUnicodeControlCharactersX": "Видалено символи керування Unicode з {0} рядків", "removedXBlankLines": "Видалено {0} порожніх рядків", "replacedXWithYCountZ": "Замінено \"{0}\" на \"{1}\" ({2} входжень)", - "replacedXWithYInLineZ": "Замінено \"{0}\" на \"{1}\" в рядку {2}", "reversedStartAndEndingsForRightToLeftX": "Зворотній початок і кінці для RTL в {0} рядках", "ruleProfileIsX": "Профіль правил тепер \"{0}\"", "saveLanguageFile": "Зберегти файл мови", @@ -1132,7 +1144,6 @@ "shiftTextCellsDown": "Зсунути текстові комірки вниз", "singleLineLength": "Довжина рядка: ", "speedIsNowX": "Швидкість тепер \"{0}\"", - "spellCheckResult": "Перевірку орфографії завершено. \n\n• Змінені слова: {0}\n• Пропущені слова: {1}", "subtitleGridFormattingX": "Форматування таблиці: {0}", "subtitleImportedFromMatroskaFile": "Субтитри імпортовано з файлу Matroska", "textDown": "Текст вниз", @@ -1158,13 +1169,14 @@ "trimmedXLines": "Обрізано {0} рядків субтитрів", "pastedXLinesOverSelectedLines": "Вставлено {0} рядок(ів) поверх виділених рядків", "pastedXOfYLinesOverSelectedLines": "Вставлено {0} з {1} рядка(ів) поверх виділених рядків - решта не вмістилася у виділення", + "insertedXLinesFromClipboard": "Вставлено рядків із буфера обміну: {0}", "openOriginalDifferentNumberOfSubtitlesXY": "Оригінальний файл субтитрів не має такої ж кількості субтитрів, як поточний файл субтитрів.\n\n• Оригінальні субтитри: {0}\n• Поточні субтитри: {1}", - "importXMatchingOriginalLines": "Імпортувати {0} відповідних оригінальних субтитрів?", "originalTextReadOnly": "Оригінальний текст (лише читання)", "originalTextEditMode": "Оригінальний текст (режим редагування)", "originalIsReadOnlyNotSaved": "Оригінальні субтитри є довідкою лише для читання і не були збережені", "originalIsEmptyNotSaved": "Оригінальні субтитри взагалі не містять тексту і не були збережені", "originalIsReadOnlyReference": "Оригінальні субтитри відкрито як довідку лише для читання", + "closeTranslationQuestionXY": "Закрити переклад \"{0}\"?\n\nОригінальні субтитри \"{1}\" стануть субтитрами, які ви редагуєте. Сам файл перекладу не змінюється.", "allowEditOfOriginalSubtitle": "Дозволити редагування оригінальних субтитрів", "showAllOriginalLinesX": "Показати всі оригінальні рядки ({0} не мають тут відповідника)", "showAllOriginalLinesHint": "Рядки без відповідника показані як додаткові рядки, щоб ви бачили, чого бракує в перекладі.", @@ -1186,6 +1198,7 @@ "fileExportedInFormatXToY": "Файл експортовано у форматі {0} до {1}", "fileExportedInFormatXToFileY": "Файл експортовано у форматі \"{0}\" до файлу \"{1}\"", "fixedXLines": "Виправлено {0} рядків", + "toolXChangedYLines": "{0}: змінено рядків: {1}", "transcriptionCompletedWithXLines": "Транскрипцію завершено з {0} рядками", "replacedXOccurrences": "Замінено {0} входжень", "ffmpegDownloadedAndInstalledToX": "ffmpeg завантажено і встановлено в {0}", @@ -1234,13 +1247,9 @@ "file": { "ebuSaveOptions": { "title": "Параметри збереження EBU", - "generalSubtitleInformation": "Загальна інформація про субтитри", "codePageNumber": "Номер кодової сторінки", "diskFormatCode": "Код формату диска", "displayStandardCode": "Код стандарту відображення", - "colorRequiresTeletext": "Кольори вимагають телетексту!", - "alignmentRequiresTeletext": "Вирівнювання вимагає телетексту!", - "teletextCharsShouldBe38": "'Макс. кількість символів на рядок' для телетексту має бути 38!", "characterCodeTable": "Таблиця символів", "languageCode": "Код мови", "originalProgramTitle": "Назва оригінальної програми", @@ -1257,7 +1266,6 @@ "maxNumberOfDisplayableRows": "Макс. кількість рядків", "diskSequenceNumber": "Номер диска", "totalNumberOfDisks": "Загальна кількість дисків", - "import": "Імпорт...", "textAndTimingInformation": "Інформація про текст і таймінг", "justificationCode": "Код вирівнювання", "verticalPosition": "Вертикальна позиція", @@ -1274,11 +1282,6 @@ "errors": "Помилки", "errorsX": "Помилки: {0}", "maxLengthError": "Рядок {0} перевищує максимальну довжину ({1}) на {2}: {3}", - "textUnchangedPresentation": "Незмінне представлення", - "textLeftJustifiedText": "Текст зліва", - "textCenteredText": "Текст по центру", - "textRightJustifiedText": "Текст справа", - "useBoxForOneNewLine": "Позначте 'Використовувати коробку навколо тексту' лише для одного нового рядка", "maxLengthErrorTeletextColored": "Рядок {0}-{1}: 36 (а не {2}) має бути максимумом символів для кольорового тексту подвійної висоти", "maxLengthErrorTeletextWhite": "Рядок {0}-{1}: 37 (а не {2}) має бути максимумом символів для білого тексту подвійної висоти", "maxLengthErrorTeletextSingleHeight": "Рядок {0}: 38 (а не {1}) має бути максимумом символів для білого тексту одинарної висоти", @@ -1297,22 +1300,29 @@ "formattingDotDotDot": "Форматування...", "imageBasedSubtitleForEditDotDotDot": "Субтитри на основі зображень для редагування...", "imageBasedSubtitleForOcrDotDotDot": "Субтитри на основі зображень для OCR...", + "dvdSubtitlesDotDotDot": "Субтитри з DVD (IFO/VOB)...", + "titleImportDvd": "Імпорт субтитрів із DVD", + "dvdIfoFile": "Файл IFO", + "dvdVobFiles": "Файли VOB", + "dvdVideoStandard": "Стандарт відео", + "dvdStartRipping": "Почати вилучення", + "dvdRippingAborted": "Вилучення перервано", + "dvdOpenIfoFile": "Відкрити файл DVD IFO", + "dvdAddVobFiles": "Додати файли VOB", + "dvdIfoFiles": "Файли DVD IFO", + "dvdVobFileType": "Файли DVD VOB", + "dvdDropHint": "Відкрийте файл IFO (VIDEO_TS\\VTS_01_0.IFO) або додайте файли VOB - чи перетягніть їх сюди", + "dvdNotAnIfoFileX": "{0} не є файлом DVD IFO", + "dvdMenuIfoNoTitleSetsX": "Поруч із {0} не знайдено наборів розділів (VTS_xx_0.IFO)", + "dvdEncryptedXOfY": "{0} з {1} пакетів субтитрів досі зашифровані CSS - ці файли VOB скопійовано без дешифрування, тому деякі зображення субтитрів будуть спотворені.\n\nДля чистого результату спершу дешифруйте DVD інструментом для вилучення DVD.\n\nВсе одно продовжити?", "splitTextAt": "Розділити текст на", "blankLines": "Порожні рядки", "oneLineIsOneSubtitle": "Один рядок - один субтитр", "twoLinesAreOneSubtitle": "Два рядки - один субтитр", "importFilesDotDotDot": "Імпорт файлів...", "multipleFiles": "Імпорт з кількох текстових файлів (один файл - один субтитр)", - "importOptions": "Параметри імпорту", - "autoSplitText": "Автоматичне розділення тексту", - "lineMode": "Режим рядка", "maxLineLength": "Максимальна довжина рядка", - "maxLinesPerSubtitle": "Максимальна кількість рядків на субтитр", - "minGapBetweenSubtitles": "Мінімальна прогалина між субтитрами", "mergeShortLines": "Об'єднати короткі рядки", - "removeLinesWithoutLetters": "Видалити рядки без літер", - "splitAtEndCharsSetting": "Розділити в кінцевих символах", - "takeTimeFromCurrentFile": "Взяти час з поточного файлу", "fixed": "Фіксовано", "numberOfSubtitlesX": "Кількість субтитрів: {0}", "numberOfSubtitlesXForcedY": "Кількість субтитрів: {0} ({1} примусових)", @@ -1330,14 +1340,18 @@ "forcedAlignerProgress": "Вирівнювання... вікно {0} з {1}, {2} з {3} рядків", "forcedAlignerSetupTitle": "Вирівняти коди часу через примусовий вирівнювач", "forcedAlignerSetupIntro": "Примусовий вирівнювач зіставляє наявний текст субтитрів зі звуком, не транскрибуючи його спочатку. Довгі відео вирівнюються частинами, тож підходить будь-яка тривалість.", + "forcedAlignerEndsFromSpeech": "Встановити час кінця за ізольованим мовленням (повільно)", + "forcedAlignerEndsFromSpeechHint": "Примусовий вирівнювач знаходить, де рядок починається, але не де закінчується, тому час кінця зазвичай визначається часом читання. Якщо це увімкнено, спершу з аудіо видаляються музика та звукові ефекти, і кожен рядок закінчується там, де стихає його мовлення. З GPU це триває приблизно стільки ж, скільки саме відео, - а без нього в багато разів довше.", + "forcedAlignerIsolatingSpeech": "Ізоляція мовлення... {0}", + "forcedAlignerIsolatingSpeechFailed": "Не вдалося ізолювати мовлення - час кінця встановлено за часом читання.", "forcedAlignerModel": "Модель вирівнювача", - "forcedAlignerDownloadEngine": "Завантажити / оновити двигун..." + "forcedAlignerDownloadEngine": "Завантажити / оновити двигун...", + "forcedAlignerEstimatedXOfY": "{0} з {1} рядків не вдалося вирівняти, тому їх розміщено за часом читання - їхні коди часу лише приблизні. Ймовірно, сценарій довший за те, що звучить у відео." }, "export": { "exportImagesProfiles": "Експорт профілів зображень", "leftRightMargin": "Ліве/праве поле", "topBottomMargin": "Верхнє/нижнє поле", - "titleExportBluRaySup": "Експорт Blu-ray (sup)", "lineSpacingPercent": "Відстань між рядками %", "paddingLeftRight": "Внутрішній відступ ліворуч/праворуч", "paddingTopBottom": "Внутрішній відступ зверху/знизу", @@ -1378,10 +1392,21 @@ "titleExportDCinemaInteropPng": "D-Cinema interop/png", "titleExportDCinemaSmpte2014Png": "D-Cinema SMPTE 2014/png", "imageBasedSubtitleSaved": "Субтитри на основі зображень збережено", - "titleExportBdnXml": "Експорт BDN/xml", "titleExportWebVttThumbnails": "Експорт WebVTT (мініатюри)", "fullFrameImage": "Зображення на весь кадр", "fullFrameImageHint": "Робити кожне зображення розміром з кадр відео, із субтитрами на своєму місці", + "stereo3D": "3D", + "stereo3DHint": "Для 3D-відео: малювати субтитри по одному разу в половині кадру для кожного ока", + "halfSideBySide": "Половина поруч (side-by-side)", + "halfTopBottom": "Половина зверху/знизу", + "depth3D": "Глибина", + "depth3DHint": "Наскільки субтитри виступають з екрана - додатне значення наближає їх, від'ємне відсуває назад", + "plane3D": "3D-Plane", + "plane3DHint": "Задати кожним субтитрам глибину, яку для них зберігає 3D Blu-ray, з файлу 3D-Plane (.ofs), створеного BD3D2MK3D або OFSExtractor. Глибина використовується там, де 3D-Plane її не має", + "openPlane3DTitle": "Відкрити файл 3D-Plane", + "unableToLoadPlane3DX": "Не вдалося завантажити 3D-Plane: {0}", + "plane3DXDepthYToZ": "{0} (глибина від {1} до {2})", + "plane3DXNoDepth": "{0} (немає значень глибини)", "textEffect": "Текстовий ефект", "textEffectSettingsTitle": "Налаштування текстового ефекту", "textEffectPreset": "Пресет", @@ -1413,13 +1438,10 @@ "statistics": { "title": "Статистика", "titleWithFileName": "Статистика - {0}", - "generalStatistics": "Загальна статистика", - "mostUsed": "Найбільш використовувані...", "mostUsedLines": "Найбільш використовувані рядки", "mostUsedWords": "Найбільш використовувані слова", "nothingFound": "Нічого не знайдено", "numberOfLinesX": "Кількість рядків субтитрів: {0:#,##0}", - "numberOfFilesX": "Кількість файлів субтитрів: {0:#,##0}", "lengthInFormatXinCharactersY": "Кількість символів як {0}: {1:#,###,##0}", "numberOfCharactersInTextOnly": "Кількість символів в тексті: {0:#,###,##0}", "totalDuration": "Загальна тривалість всіх субтитрів: {0:#,##0}", @@ -1460,7 +1482,6 @@ "gapMaximum": "Прогалина - максимум: {0:#,##0} мс", "gapAverage": "Прогалина - середня: {0:#,##0.##} мс", "gapExceedingMinimum": "Прогалина - нижче мінімуму ({0:#,##0} мс): {1} ({2:0.00}%)", - "export": "Експорт...", "subtitles": "Субтитри", "words": "Слова", "totalDurationShort": "Загальна тривалість", @@ -1490,6 +1511,21 @@ "restoreAutoBackupFile": "Відновити файл автозбереження", "deleteAllSubtitleBackups": "Бажаєте видалити всі файли резервних копій субтитрів?", "deleteAll": "Видалити все", + "subtitles": "Субтитри", + "settingsBackupInfo": "Резервна копія Settings.json створюється під час запуску Subtitle Edit, не частіше ніж раз на {0} дн. і лише якщо налаштування змінилися. Зберігаються {1} найновіших резервних копій.", + "settingsBackupInfoEveryStart": "Резервна копія Settings.json створюється під час кожного запуску Subtitle Edit, якщо налаштування змінилися. Зберігаються {0} найновіших резервних копій.", + "settingsBackupOff": "Автоматичне резервне копіювання налаштувань вимкнено (Опції → Налаштування → Файл).", + "lastSettingsBackupX": "Остання резервна копія: {0}", + "noSettingsBackupsYet": "Резервних копій налаштувань ще немає", + "openSettingsBackupFolder": "Відкрити теку резервних копій налаштувань", + "backUpSettingsNow": "Створити резервну копію зараз", + "settingsBackedUp": "Резервну копію поточних налаштувань створено.", + "restoreSettings": "Відновити налаштування", + "restoreSettingsFromX": "Замінити ваші поточні налаштування резервною копією від {0}?\n\nСпершу буде створено резервну копію поточних налаштувань. Деякі зміни набудуть чинності лише після перезапуску Subtitle Edit.", + "settingsRestored": "Налаштування відновлено. Перезапустіть Subtitle Edit, щоб усі зміни набули чинності.", + "settingsRestoreFailed": "Не вдалося відновити налаштування: {0}", + "settingsBackupNotValid": "резервна копія не є дійсним файлом налаштувань, тому поточні налаштування збережено", + "deleteAllSettingsBackups": "Видалити всі файли резервних копій налаштувань?", "restoreXFromY": "Бажаєте відновити \"{0}\" з {1}?" }, "propertiesDCinema": { @@ -1504,22 +1540,16 @@ "editRate": "Частота редагування", "timeCodeRate": "Частота коду часу", "startTime": "Час початку", - "font": "Шрифт", "fontId": "ID шрифту", "fontUri": "URI шрифту", - "generate": "Створити", - "fontColor": "Колір шрифту", "chooseColor": "Вибрати колір", "fontEffect": "Ефект шрифту", "effectColor": "Колір ефекту", - "fontSize": "Розмір шрифту", "topBottomMargin": "Верхнє/нижнє поле", "fadeUpTime": "Час затухання вгору", "fadeDownTime": "Час затухання вниз", - "frames": "Кадри", "zPosition": "Z-позиція", - "zPositionHelp": "Додатні числа віддаляють текст, від'ємні наближають; якщо z-позиція дорівнює нулю, то це 2D", - "export": "Експорт..." + "zPositionHelp": "Додатні числа віддаляють текст, від'ємні наближають; якщо z-позиція дорівнює нулю, то це 2D" }, "webVtt": { "setStylesForSelectedLinesTitle": "Встановити стилі WebVTT для виділених рядків", @@ -1552,20 +1582,66 @@ "compareOnlyInOneFile": "Лише в одному файлі", "compareTextOrTimeDifference": "Різниця тексту/часу", "compareNumberDifference": "Різниця номерів", - "ignoreLineBreaks": "Ігнорувати розбиття рядків", "ignoreWhitespace": "Ігнорувати пробіли", "ignoreWhitespaceHint": "Рядки, які відрізняються лише пробілами, табуляціями чи розривами рядків, не вважаються різними", "ignoreFormatting": "Ігнорувати форматування", - "onlyLookForDifferencesInText": "Шукати відмінності тільки в тексті", - "cannotCompareWithImageBasedSubtitles": "Не можна порівняти з субтитрами на основі зображень", - "statisticsTitle": "Статистика", "ignoreFormattingHint": "Рядки, які відрізняються лише тегами форматування, як-от чи {\\an8}, не вважаються різними", "showOnlyDifferencesInText": "Тільки відмінності в тексті", + "ignoreNumbering": "Ігнорувати нумерацію", + "ignoreNumberingHint": "Рядки, які відрізняються лише номером, не вважаються різними", + "ignoreTimeDifferenceMs": "Допуск часу (ms)", + "ignoreTimeDifferenceMsHint": "Час початку та кінця, що відрізняється на цю кількість мілісекунд або менше, не вважається різним", + "compareDifferences": "Відмінності", + "compareTextDifferences": "Відмінності тексту", + "compareCurrent": "Поточні", + "compareReference": "Еталон", + "compareEditable": "Редаговані", + "compareEditableHint": "Зміни, зроблені тут, переносяться в субтитри, коли ви натискаєте Застосувати", + "compareReadOnly": "Лише читання", + "compareTakeFromReference": "Взяти текст і час з еталона", + "compareInsertFromReference": "Вставити цей рядок у поточні субтитри", + "compareDeleteFromCurrent": "Видалити цей рядок із поточних субтитрів", + "compareTakeText": "Взяти текст", + "compareTakeTiming": "Взяти час", + "compareEditLine": "Редагувати рядок", + "compareEditHint": "Ctrl+Enter - зберегти, Esc - скасувати", + "compareEdited": "Відредаговано", + "compareOnePendingChange": "1 незастосована зміна", + "compareXPendingChanges": "Незастосованих змін: {0}", + "compareDiscardXChanges": "Скасувати зміни ({0}), зроблені в Порівнянні?", + "compareXChangesApplied": "Порівняння: застосовано змін: {0}", + "compareChangeTextX": "#{0} текст з еталона", + "compareChangeTimingX": "#{0} час з еталона", + "compareChangeTextAndTimingX": "#{0} текст і час з еталона", + "compareChangeEditedX": "#{0} відредаговано", + "compareChangeInsertedX": "#{0} вставлено", + "compareChangeDeletedX": "#{0} видалено", + "compareSyncPoint": "Точка синхронізації", + "compareSyncPointHint": "Точка синхронізації - ці два рядки завжди показуються парою, а рядки вище та нижче вирівнюються окремо", + "compareSyncPickCurrent": "Точка синхронізації: використати цей поточний рядок", + "compareSyncPickReference": "Точка синхронізації: використати цей еталонний рядок", + "compareSyncRemove": "Видалити точку синхронізації", + "compareClearXSyncPoints": "Очистити точки синхронізації ({0})", + "compareSyncCurrentPickedX": "Поточний #{0} вибрано як точку синхронізації - виділіть відповідний еталонний рядок і натисніть Синхронізація", + "compareSyncReferencePickedX": "Еталонний #{0} вибрано як точку синхронізації - виділіть відповідний поточний рядок і натисніть Синхронізація", + "compareSyncWithCurrentX": "Синхронізувати з поточним #{0}", + "compareSyncWithReferenceX": "Синхронізувати з еталонним #{0}", + "compareSyncApply": "Синхронізація", + "compareSyncPointSetXY": "Точку синхронізації встановлено: поточний #{0} і еталонний #{1} тепер показуються парою", + "compareSyncAlreadyPairedXY": "Поточний #{0} і еталонний #{1} вже показуються парою з однаковим початком - нічого синхронізувати", + "compareSyncPointShiftedXYZW": "Точку синхронізації встановлено: поточні #{0}-#{1} зсунуто на {2}, щоб почати з еталонним #{3}", + "compareChangeSyncShiftXYZ": "#{0}-#{1} зсунуто на {2} (точка синхронізації)", + "compareTimingDeltaHint": "Наскільки пізніше (+) або раніше (−) еталон починається та закінчується порівняно з поточним рядком", "loadXFromFile": "Завантажити \"{0}\" з файлу", + "compareLoadSavedFile": "Завантажити збережений файл", "saveCompareHtmlTitle": "Зберегти файл порівняння HTML", "pickMatroskaTrackX": "Вибрати доріжку Matroska - {0}", "pickTransportStreamTrackX": "Виберіть доріжку транспортного потоку - {0}", + "chapters": "Розділи", + "dvdVobFilesMissingX": "Бракує файлів VOB (знайдено {0}%)", "pickMp4TrackX": "Виберіть доріжку MP4 - {0}", + "pickMxfTrackX": "Виберіть доріжку MXF - {0}", + "pickMpegTrackX": "Виберіть доріжку MPEG - {0}", "rosettaProperties": "Властивості Timed Text Rosetta IMSC", "rosettaFontSize": "Розмір шрифту (висота рядка)", "propertyTimeBase": "База часу", @@ -1575,7 +1651,6 @@ "propertyDefaultRegion": "Типова область", "propertyStyleAttributeName": "Назва атрибута стилю", "propertyTimeCodeFormat": "Формат коду часу", - "propertyFileExtension": "Розширення файлу", "propertyTopOrigin": "Верхній початок", "propertyTopExtent": "Верхній розмір", "propertyBottomOrigin": "Нижній початок", @@ -1625,7 +1700,6 @@ "gapGreaterThan": "Прогалина в мс >" }, "multipleReplace": { - "title": "Множинна заміна", "editRule": "Редагувати правило", "newRule": "Нове правило", "editCategory": "Редагувати категорію", @@ -1642,24 +1716,23 @@ "findWhat": "Що знайти", "descriptionOptional": "Опис (необов'язково)", "invalidRegularExpressionX": "Неприпустимий регулярний вираз: {0}", - "regularExpressionTooSlowX": "Регулярний вираз припинив роботу через {0} с - правило пропущено, спробуйте простіший шаблон" + "regularExpressionTooSlowX": "Регулярний вираз припинив роботу через {0} с - правило пропущено, спробуйте простіший шаблон", + "moveToCategory": "Перемістити до категорії" }, "find": { "searchTextWatermark": "Пошук тексту...", "wholeWord": "Ціле слово", - "caseSensitive": "З урахуванням регістру", - "caseInsensitive": "Без урахування регістру", "findPrevious": "Знайти _попереднє", "findNext": "_Знайти далі", "replaceAndFindNext": "_Замінити і знайти далі", "replaceAll": "Замінити _все", - "replaceTitle": "Замінити", - "replaceWith": "Замінити на", "replaceTextWatermark": "Замінити на...", "replaceIn": "Замінювати/шукати в", "replaceInTextAndOriginal": "Тексті й оригінальному тексті", "replaceInTextOnly": "Лише тексті", - "replaceInOriginalOnly": "Лише оригінальному тексті" + "replaceInOriginalOnly": "Лише оригінальному тексті", + "replacedOneOccurrence": "Замінено одне входження", + "replacedXOccurrences": "Замінено входжень: {0}" }, "regularExpressionContextMenu": { "wordBoundary": "Межа слова (\\b)", @@ -1687,6 +1760,7 @@ "xLinesYSubtitles": "{0} рядків, {1} субтитрів", "xLinesYSubtitlesZErrors": "{0} рядків, {1} субтитрів, {2} помилок", "couldNotParseAsX": "Не вдалося розібрати як {0}", + "readAsXUseAsY": "Джерело не вдалося прочитати як {1}, але вдалося прочитати як {0}.\nВикористати рядки та й далі зберігати як {1}?", "selectedXCharactersYLines": "Виділено: {0} символів у {1} рядку(ах)", "replacedXOccurrences": "Замінено {0} збіг(ів)", "invalidRegularExpression": "Неприпустимий регулярний вираз", @@ -1701,7 +1775,6 @@ "aiReview": { "title": "Рецензія ШІ", "review": "Рецензувати", - "stop": "Зупинити", "editPrompt": "Редагувати запит...", "editPromptTitle": "Редагувати запит рецензії", "promptInfo": "Інструкції, що надсилаються моделі - {language} замінюється мовою субтитрів.", @@ -1712,7 +1785,6 @@ "reviewingLineXOfY": "Рецензування рядка {0} з {1}...", "reviewDone": "Рецензію завершено - {0} пропозицій у {1} рядках", "noIssuesFound": "Проблем не знайдено", - "categoryAll": "Всі", "categorySpelling": "Орфографія", "categoryGrammar": "Граматика", "categoryPunctuation": "Пунктуація", @@ -1724,8 +1796,23 @@ "largeChangeWarning": "Велика зміна - схоже на переписування, перевірте уважно", "mismatchWarning": "Не схоже на оригінал - можливо, належить до іншого рядка", "engineError": "Не вдалося зв'язатися з двигуном ШІ: {0}", - "setupHint": "Будь ласка, спочатку виберіть двигун і модель в Інструменти -> Рецензія ШІ.", - "playCurrentLineHint": "Відтворити виділений рядок у програвачі та зупинитися в його кінці (також подвійне клацання по рядку)" + "playCurrentLineHint": "Відтворити виділений рядок у програвачі та зупинитися в його кінці (також подвійне клацання по рядку)", + "editAfterHint": "Клацніть текст виділеного рядка, щоб відредагувати виправлення перед застосуванням (Enter зберігає, Escape скасовує)", + "template": "Шаблон", + "templatePlaceholder": "Виберіть шаблон, щоб замінити запит", + "templateStandard": "Стандартна вичитка", + "templateSpeechToText": "Очищення розпізнаного мовлення", + "templateMachineTranslation": "Виправлення машинного перекладу", + "templateSdh": "SDH / для людей із вадами слуху", + "referenceContext": "Довідковий контекст", + "referenceContextInfo": "Необов'язково. Імена, персонажі, глосарій, синопсис або примітки щодо стилю - модель вважає це авторитетним джерелом для імен і термінів. Надсилається з кожним пакетом.", + "referenceContextPlaceholder": "Імена: Кайто, тітка Мей, д-р Голліс\nТерміни: Сінкансен, варп-ядро\nСинопсис: Кайто їде до Токіо, щоб знайти зниклого батька.", + "generateContext": "Створити за допомогою ШІ", + "generateContextHint": "Дозволити вибраному двигуну ШІ прочитати субтитри та підготувати імена, терміни й синопсис - перевірте результат перед використанням", + "generatingContextPartXOfY": "Читання частини {0} з {1}...", + "generatingContextSynopsis": "Написання синопсису...", + "replaceContextQuestion": "Замінити поточний довідковий контекст?", + "noContextGenerated": "Двигун ШІ не повернув придатної довідкової інформації." }, "aiAssistant": { "title": "Помічник ШІ", @@ -1743,22 +1830,14 @@ "showReasoning": "Показати міркування моделі" }, "fixCommonErrors": { - "title": "Виправити загальні помилки", - "step1": "Крок 1/2 - Виберіть, які помилки виправляти", - "whatToFix": "Що виправляти", - "example": "Приклад", - "selectAll": "Вибрати все", - "inverseSelection": "Інвертувати вибір", "back": "< &Назад", "next": "&Далі >", - "step2": "Крок 2/2 - Перевірте виправлення", "fixes": "Виправлення", "ruleDetailsDotDotDot": "Подробиці правила...", "showOnlyThisRule": "Показати лише виправлення цього правила", "appliedRulesForLineX": "Застосовані правила для рядка {0}", "ruleX": "Правило: {0}", "log": "Журнал", - "function": "Функція", "removedEmptyLine": "Видалити порожній рядок", "removedEmptyLineAtTop": "Видалити порожній рядок зверху", "removedEmptyLineAtBottom": "Видалити порожній рядок знизу", @@ -1779,13 +1858,13 @@ "removeLineBreaksPixelWidth": "Розбити субтитри, які можуть поміститися в один рядок (ширина в пікселях)", "fixUppercaseIInsideLowercaseWords": "Виправити велику 'i' всередині малих слів (помилка OCR)", "fixDoubleApostrophes": "Виправити подвійні лапки ('') на одну лапку (\")", + "fixMisreadQuotes": "Виправити апострофи, хибно розпізнані як подвійні лапки (OCR, англійська)", "addPeriods": "Додати крапку після рядків, де наступний рядок починається з великої літери", "startWithUppercaseLetterAfterParagraph": "Починати з великої літери після абзацу", "startWithUppercaseLetterAfterPeriodInsideParagraph": "Починати з великої літери після крапки всередині абзацу", "startWithUppercaseLetterAfterColon": "Починати з великої літери після двокрапки/крапки з комою", "fixLowercaseIToUppercaseI": "Виправити окрему малу 'i' на 'I' (англійська)", "fixCommonOcrErrors": "Виправити загальні помилки OCR (використовуючи список заміни OCR)", - "commonOcrErrorsFixed": "Загальні помилки OCR виправлено (використано файл OcrReplaceList): {0}", "removeSpaceBetweenNumber": "Видалити пробіл між числами", "breakDialogsOnOneLine": "Розділити діалоги в одному рядку", "removeDialogFirstInNonDialogs": "Видалити початковий тире в першому рядку для не-діалогів", @@ -1798,12 +1877,10 @@ "removeHyphensSingleLine": "Видалити тире діалогів в одному рядку", "fixHyphensInDialogs": "Виправити тире в діалогах через стиль: {0}", "addMissingQuotesExample": "\"Як справи? -> \"Як справи?\"", - "xMissingQuotesAdded": "Додано відсутні лапки: {0}", "fix3PlusLine": "Виправити субтитри з більш ніж двома рядками", "fix3PlusLines": "Виправити субтитри з більш ніж двома рядками", "analysing": "Аналіз...", "nothingToFix": "Нічого виправляти :)", - "fixesFoundX": "Знайдено виправлень: {0}", "xFixesApplied": "Застосовано виправлень: {0}", "xFixesYSelected": "{0} виправлень - {1} вибрано", "nothingFixableBut": "Нічого не можна виправити автоматично. Субтитри містять помилки - див. журнал для деталей", @@ -1812,7 +1889,6 @@ "fixFirstLetterToUppercaseAfterParagraph": "Виправити першу літеру на велику після абзацу", "mergeShortLine": "Об'єднати короткий рядок (одне речення)", "mergeShortLineAll": "Об'єднати короткий рядок (все, крім діалогів)", - "unbreakShortLinePixelWidth": "Розбити короткий рядок (ширина в пікселях)", "breakLongLine": "Розбити довгий рядок", "fixLongDisplayTime": "Виправити довгий час відображення", "fixInvalidItalicTag": "Виправити недійсний тег курсиву", @@ -1835,7 +1911,6 @@ "fixUppercaseIInsideLowercaseWord": "Виправити велику 'i' всередині малого слова", "fixMissingPeriodAtEndOfLine": "Додати відсутню крапку в кінці рядка", "refreshFixes": "Оновити доступні виправлення", - "applyFixes": "Застосувати вибрані виправлення та закрити", "autoBreak": "Авто &розділити", "unbreak": "&Розбити", "fixDoubleDash": "Виправити '--' -> '...'", @@ -1854,8 +1929,6 @@ "fixSpaceBetweenNumbersExample": "1 100 -> 1100", "fixDialogsOneLineExample": "Привіт Джон! - Привіт Іда! -> Привіт Джон!
- Привіт Іда!", "removeDialogFirstInNonDialogsExample": "- Як справи? -> Як справи?", - "selectDefault": "Вибрати типове", - "setDefault": "Встановити поточні виправлення як типові", "fixContinuationStyleX": "Виправити стиль продовження: {0}", "fixUnnecessaryLeadingDots": "Видалити непотрібні початкові крапки", "fixCommonErrorsProfiles": "Профілі виправлення загальних помилок", @@ -1863,13 +1936,13 @@ "searchRulesDotDotDot": "Пошук правил...", "goToApplyFixes": "Перейти до застосування виправлень", "backToFixList": "Назад до списку виправлень", - "applyFixesAndClose": "Застосувати виправлення та закрити", "fixCommonOcrErrorsStep2": "Виправити загальні помилки, крок 2 (застосування виправлень)", "fixCommonOcrErrorsStep2FixesFoundX": "Виправити загальні помилки, крок 2 - Виправлення знайдено: {0}", "removedEmptyLinesUnusedLineBreaksExample": "Has only one valid line!
-> Has only one valid line!", "fixCommasExample": ",, -> ,", "removeLineBreaksExample": "Foo
bar! -> Foo bar!", "fixDoubleApostrophesExample": "''Has double single quotes'' -> \"Has single double quote\"", + "fixMisreadQuotesExample": "\"Hello' -> \"Hello\"", "addPeriodsExample": "Hello world -> Hello world.", "startWithUppercaseLetterAfterParagraphExample": "p1: Foobar! || p2: foobar! -> p1: Foobar! || p2: Foobar!", "startWithUppercaseLetterAfterPeriodInsideParagraphExample": "Hello there! how are you? -> Hello there! How are you?", @@ -1886,18 +1959,11 @@ "adjustDurations": { "title": "Налаштувати тривалість", "adjustVia": "Налаштувати через", - "seconds": "Секунди", - "percent": "Відсоток", "recalculate": "Перерахувати", "addSeconds": "Додати секунди", - "setAsPercent": "Встановити як відсоток від тривалості", "note": "Примітка: Час відображення не буде перекривати час початку наступного тексту", "fixed": "Фіксовано", - "milliseconds": "Мілісекунди", "extendOnly": "Тільки розширити", - "enforceDurationLimits": "Застосувати мінімальну та максимальну тривалість", - "checkShotChanges": "Не розширювати після змін кадрів", - "batchCheckShotChanges": "Дотримуватися змін кадрів (якщо доступно)", "recalculateRequiresOcrNote": "Перерахунок недоступний — один або більше субтитрів не мають тексту OCR. Спочатку запустіть OCR." }, "applyDurationLimits": { @@ -1905,6 +1971,8 @@ "fixMinDurationMs": "Виправити мінімальну тривалість (мс)", "doNotGoPastShotChange": "Не переходити через зміну кадру", "fixMaxDurationMs": "Виправити максимальну тривалість (мс)", + "fixMinDurationFrames": "Виправити мінімальну тривалість (кадри)", + "fixMaxDurationFrames": "Виправити максимальну тривалість (кадри)", "minimumDurationMilliseconds": "Мінімальна тривалість (мілісекунди):", "maximumDurationMilliseconds": "Максимальна тривалість (мілісекунди):", "maxDurationShouldBeHigherThanMinDuration": "Максимальна тривалість має бути більшою за мінімальну тривалість", @@ -1924,12 +1992,8 @@ }, "beautifyTimeCodes": { "title": "Покращити коди часу", - "beautifySettings": "Параметри покращення", "snapToFrames": "Прив'язати до кадрів", - "frameGap": "Прогалина між кадрами", "minDuration": "Мінімальна тривалість (мс)", - "shotChangeThreshold": "Поріг зміни кадру (мс)", - "shotChangeOffset": "Зсув зміни кадру (кадри)", "original": "Оригінал", "beautified": "Покращено", "subtitlesCount": "Субтитри", @@ -1958,10 +2022,76 @@ "timeCodesExtractFailed": "Не вдалося зчитати коди часу з відео", "timeCodesHint": "Прив'язувати точки до реального часу кадрів відео замість припущення про сталу частоту кадрів. Потребує одноразового читання всього відео; результат кешується." }, + "improveTimeCodes": { + "title": "Уточнити коди часу (примусове вирівнювання)", + "aligner": "Вирівнювач", + "alignerHint": "Модель, яка шукає кожен рядок в аудіо. Найкращі варіанти для мови субтитрів наведено першими.", + "maxShift": "Макс. зсув (секунди)", + "maxShiftHint": "Наскільки субтитри можуть бути розсинхронізовані. Рядок, початок якого мав би зсунутися далі, залишається без змін і позначається. За будь-якого значення окремий рядок, який хоче зсунутися більш ніж на пів секунди далі, ніж сусідні рядки, не зсувається наосліп: він слідує за сусідами, якщо всі вони зсунулися, а інакше пропонується без позначки для вашої перевірки.", + "adjustStartTimes": "Коригувати час початку", + "adjustEndTimes": "Коригувати час кінця", + "isolateSpeech": "Спершу ізолювати мовлення (повільно)", + "isolateSpeechHint": "Видаляє музику та звукові ефекти перед вирівнюванням, щоб вирівнювач чув лише діалог. Допомагає для рядків, промовлених на тлі гучної музики чи екшну, але з GPU триває приблизно стільки ж, скільки саме аудіо, - а без нього в багато разів довше.", + "isolatingSpeech": "Ізоляція мовлення... (це триває деякий час)", + "isolateSpeechFailed": "Не вдалося ізолювати мовлення - натомість вирівняно за оригінальним аудіо.", + "checkWithSpeechToText": "Перевірити розпізнаванням мови", + "checkWithSpeechToTextHint": "Також транскрибує аудіо та перевіряє кожне переміщення відносно того, де слова насправді прозвучали. Великі переміщення, які підтверджує мовлення, позначаються автоматично, а переміщення від місця, де рядок чути, залишаються без позначки для вашої перевірки. Також показує, яку частку кожного рядка було почуто - низька частка означає, що текст відрізняється від сказаного. Субтитри, розсинхронізовані сильніше за макс. зсув - постійне зміщення, дрейф через невідповідність частоти кадрів або стрибки там, де вирізано сцени, - спершу синхронізуються за почутими словами. Використовує Crisp ASR Parakeet, що з GPU займає кілька хвилин для повнометражного фільму.", + "checkWithSpeechToTextNotAvailableX": "Перевірка розпізнаванням мови недоступна для {0}", + "transcribingToCheck": "Транскрибування для перевірки вирівнювання...", + "speechToTextFailed": "Розпізнавання мови не вдалося - коди часу не перевірено.", + "speechToTextX": "Розпізнавання мови: {0}", + "speechToTextConfirmedXDisputedY": "Мовлення: підтверджено {0}, сумнівних {1}", + "hardlyHeardX": "Розпізнавання мови почуло лише {0}% слів субтитрів. Чи субтитри мовою, якою говорять? Вирівнювач не може розмістити переклад.", + "heard": "Почуто", + "heardHint": "Яку частку рядка почуло розпізнавання мови. Низька частка означає, що текст відрізняється від сказаного - саме там вирівнювач найімовірніше помиляється.", + "statusConfirmedBySpeech": "Підтверджено мовленням", + "statusDisputedBySpeech": "Почуто там, де був", + "statusMovedWithSync": "Зсунуто разом із синхронізацією", + "syncedFirstXY": "Спершу синхронізовано: {0} с на початку, {1} с у кінці", + "showSpeechOnly": "Показати лише мовлення", + "showSpeechOnlyHint": "Малювати вирівняну хвильову форму з аудіо без музики та звукових ефектів; оригінальна хвильова форма залишається як є, для порівняння. Доступно після ізоляції мовлення - тут або через \"Показати лише мовлення\" у хвильовій формі головного вікна.", + "align": "Вирівняти", + "original": "Оригінал", + "aligned": "Вирівняно", + "intro": "Виберіть вирівнювач і натисніть \"Вирівняти\", щоб знайти кожен рядок в аудіо та уточнити його коди часу.", + "extractingAudio": "Вилучення аудіо...", + "aligningBatchXOfY": "Вирівнювання... пакет {0} з {1}", + "extractAudioFailed": "Не вдалося вилучити аудіо за допомогою ffmpeg.", + "modelWillBeDownloaded": "{0} буде завантажено", + "summaryXRetimedYKeptZSkipped": "Змінено час: {0} · Залишено: {1} · Без мовлення: {2}", + "meanShiftX": "Середній зсув: {0} ms", + "toCheckX": "Для перевірки: {0}", + "largeMovesToCheckX": "Рядків, які самі по собі зсунулися б дуже далеко і залишені без позначки: {0} - прослухайте їх (F5 відтворює вирівняний рядок, Shift+F5 - оригінал) і позначте правильні.", + "linesToCheckX": "Рядків, залишених без позначки для перевірки: {0} - прослухайте їх (F5 відтворює вирівняний рядок, Shift+F5 - оригінал) і позначте правильні.", + "changeXOfY": "Зміна {0} з {1}", + "previousChange": "Попередня зміна", + "nextChange": "Наступна зміна", + "startShift": "Початок", + "endShift": "Кінець", + "statusRetimed": "Час змінено", + "statusMovedWithNeighbours": "Зсунуто разом із сусідами", + "statusLargeMoveUnconfirmed": "Великий зсув - прослухайте, позначте для застосування", + "statusUnchanged": "Вже на місці", + "statusNoSpeech": "Без мовлення", + "statusShiftTooLarge": "Залишено - завеликий зсув", + "statusFailed": "Залишено - помилка вирівнювача", + "statusAdjustedByHand": "Скориговано вручну", + "alignedWaveformHint": "Перетягніть рядок, щоб перемістити його, або перетягніть його краї, щоб змінити початок чи кінець.", + "playLine": "Відтворити рядок", + "undoAdjustment": "Скасувати коригування", + "playPause": "Відтворити / пауза", + "playAligned": "Вирівняно", + "playAlignedHint": "Відтворити виділений рядок із його вирівняними кодами часу", + "apply": "Застосувати", + "applyHint": "Зніміть позначку з рядка, щоб зберегти його оригінальні коди часу. Двічі клацніть рядок, щоб відтворити його. Рядки також можна переміщувати чи змінювати їхній розмір у вирівняній хвильовій формі." + }, "bridgeGaps": { - "title": "Замкнути прогалини", "bridgeGapsSmallerThan": "Замкнути прогалини менші за (мс)", "minGap": "Мінімальна прогалина (мс)", + "bridgeGapsSmallerThanFrames": "Замкнути прогалини, менші за (кадри)", + "minGapFrames": "Мінімальна прогалина (кадри)", + "calculateBridgeGapsSmallerThanDotDotDot": "Обчислити межу замикання прогалин за частотою кадрів...", + "useXMsAsBridgeGapsSmallerThan": "Замкнути прогалини, менші за \"{0}\" мілісекунд?", "numberOfSmallGapsBridgedX": "Кількість замкнутих малих прогалин: {0}", "percentFoPrevious": "Прогалина для попереднього (%)", "gapChange": "Зміна прогалини" @@ -1973,8 +2103,6 @@ "sortBy": { "title": "Сортувати субтитри", "sortOrder": "Порядок сортування", - "sortByNumber": "Номер", - "sortByStartTime": "Час початку", "sortByEndTime": "Час завершення" }, "batchConvert": { @@ -1997,6 +2125,7 @@ "addColor": "Додати колір", "deleteLinesWithSpecificActorsOrStyles": "Видалити рядки з акторами або стилями (окремі значення через кому)", "useSourceStylesIfPossible": "Використовувати вихідні стилі, якщо можливо", + "keepSourceEmbeddedFonts": "Зберегти вбудовані шрифти джерела (footer)", "editStyles": "Редагувати стилі", "editProperties": "Редагувати властивості", "editAttachments": "Редагувати вкладення", @@ -2017,7 +2146,6 @@ "assaChangeStyleFromStyle": "Змінити стиль з", "assaChangeStyleToStyle": "на", "assaChangeStyleImportStyle": "Імпортувати стиль...", - "assaChangeStyleImportedX": "Імпортовано: {0}", "assaChangeStyleTrimUnusedStyles": "Обрізати невикористані стилі", "assaChangeStylePropertiesTitle": "Змінити властивості стилю", "assaChangeStylePropertiesInfo": "Змінює вибрані поля в кожному стилі файлу, не чіпаючи самі стилі.", @@ -2029,13 +2157,52 @@ "convertColorsToDialogReBreakLines": "Перерозбити рядки", "snapTimeCodesToFramesInfo": "Округлює кожен час початку і кінця до найближчого кадру. Частота кадрів зчитується з відеофайлу з такою самою назвою, як файл субтитрів, якщо не вибрано фіксовану частоту кадрів.", "addFolderDotDotDot": "Додати папку...", + "addFolderRecursiveDotDotDot": "Додати теку рекурсивно...", "selectFolderToConvert": "Виберіть папку з файлами для конвертування", "includeSubfolders": "Включати підпапки під час додавання папки", + "videoFilesWhenAddingFolder": "Відеофайли (mkv, mp4, ts...) під час додавання теки", + "videoFilesAsk": "Запитувати", + "videoFilesInclude": "Додавати їхні доріжки субтитрів", + "videoFilesSkip": "Пропускати", + "folderContainsXVideoFiles": "Тека містить відеофайлів (mkv, mp4, ts...): {0}.\n\nДодати також їхні вбудовані доріжки субтитрів чи лише файли субтитрів?", + "addVideoFiles": "Додати відеофайли", + "skipVideoFiles": "Лише файли субтитрів", + "doNotAskAgainVideoFiles": "Більше не запитувати (змінюється в параметрах пакетного перетворення)", + "settingsSectionOutput": "Вивід", + "settingsSectionAddingFiles": "Додавання файлів", + "settingsSectionImageBasedSubtitles": "Субтитри на основі зображень (OCR)", + "settingsSectionOther": "Інше", "keepSourceFileTimestamp": "Зберігати дату/час вихідного файлу на створених файлах", - "scanningFolderX": "Сканування {0}..." + "preventSleepWhileConverting": "Не давати комп'ютеру переходити в режим сну під час перетворення", + "scanningFolderX": "Сканування {0}...", + "transportStreamSettings": "Налаштування Transport Stream", + "transportStreamSettingsDotDotDot": "Налаштування Transport Stream...", + "transportStreamSettingsInfo": "Застосовується до доріжок субтитрів, вилучених із файлів Transport Stream (.ts/.m2ts). Позиція та розмір відео впливають лише на графічні доріжки DVB, експортовані у формат на основі зображень.", + "transportStreamOverrideXPosition": "Перевизначити оригінальну позицію X", + "transportStreamOverrideYPosition": "Перевизначити оригінальну позицію Y", + "transportStreamOverrideVideoSize": "Перевизначити оригінальний розмір відео", + "transportStreamBottomMargin": "Нижній відступ", + "transportStreamFileNameEnding": "Закінчення імені файлу", + "transportStreamFileNameEndingInfo": "Додається перед розширенням для кожної вилученої доріжки. Залиште порожнім, щоб використати звичайний постфікс мови.", + "transportStreamOnlyTeletext": "Лише телетекст", + "transportStreamGetSizeFromVideo": "Отримати розмір із відео...", + "twoLetterLanguageCodeUppercase": "Дволітерний код мови (великими літерами)", + "threeLetterLanguageCodeUppercase": "Трилітерний код мови (великими літерами)", + "formatContainsDotDotDot": "Формат містить...", + "extensionIsDotDotDot": "Розширення...", + "statusIsError": "Статус - помилка", + "forcedTracksOnly": "Лише примусові доріжки", + "xConvertedYFailedInZ": "Перетворено {0:#,###,##0}, з помилкою {1:#,###,##0} за {2}", + "xNotProcessed": "Не оброблено: {0:#,###,##0}", + "removeUnicodeControlCharactersTitle": "Видалити символи керування Unicode", + "removeUnicodeControlCharactersInfo": "Видаляє з тексту невидимі символи керування напрямком Unicode (LRM, RLM, LRE, RLE, PDF, LRO і RLO) та замінює нерозривні пробіли звичайними.", + "preset": "Пресет", + "savePresetDotDotDot": "Зберегти пресет...", + "deletePreset": "Видалити пресет", + "presetName": "Назва пресету", + "deletePresetX": "Видалити пресет \"{0}\"?" }, "changeCasing": { - "title": "Змінити регістр", "fixNames": "Виправити імена", "namesX": "Імена: {0:#,##0}", "hitsX": "Збіги: {0:#,##0}", @@ -2046,9 +2213,7 @@ "allUppercase": "Всі великі літери", "allLowercase": "Всі малі літери" }, - "changeFormatting": { - "title": "Змінити форматування" - }, + "changeFormatting": {}, "convertActors": { "title": "Конвертувати акторів", "convertActorFrom": "Конвертувати актора з", @@ -2069,7 +2234,6 @@ "title": "Розділити субтитри", "numberOfEqualParts": "Кількість рівних частин", "saveSplitParts": "_Зберегти розділені частини", - "subtitleSplitIntoXParts": "Субтитри розділено на {0} частин.", "xPartsSavedInFormatYToFolder": "{0} частин збережено у форматі {1} в теку:" }, "splitBreakLongLines": { @@ -2095,13 +2259,14 @@ "onlyContinuationLines": "Лише рядки продовження" }, "mergeLinesWithSameText": { - "title": "Об'єднати рядки з однаковим текстом", "maxMsBetweenLines": "Макс. мілісекунд між рядками", - "includeIncrementingLines": "Включити рядки з інкрементним текстом" + "maxFramesBetweenLines": "Макс. кадрів між рядками", + "includeIncrementingLines": "Включити рядки з інкрементним текстом", + "includeRollUpCaptions": "Включати прокручувані (roll-up) титри" }, "mergeLinesWithSameTimeCodes": { - "title": "Об'єднати рядки з однаковими кодами часу", "maxMsDifference": "Макс. різниця (мілісекунди)", + "maxFramesDifference": "Макс. різниця (кадри)", "makeDialog": "Створити діалог" }, "netflixCheckAndFix": { @@ -2124,7 +2289,6 @@ "shotChanges": "Зміни кадрів", "startNumberSpellOut": "Число на початку прописом", "textforHiUseBrackets": "Текст для HI, використовувати дужки", - "frameRate": "Частота кадрів", "twoFrameGrap": "Прогалина у два кадри", "whiteSpace": "Пробіл", "childrensProgram": "Дитяча програма", @@ -2162,7 +2326,6 @@ "italicsFixed": "Виправлено курсив", "italicsNotAllowed": "Курсив не дозволено", "bridgeGapsXToYFrames": "прогалина 3-{0} кадрів => прогалина 2 кадри", - "startNumberSpellOutComment": "Коли число починає речення, його завжди слід писати прописом", "maximumXCharactersPerSecond": "Максимум {0} символів на секунду" }, "imageBasedEdit": { @@ -2172,10 +2335,8 @@ "resizeImagesDotDotDot": "Змінити розмір зображень...", "adjustBrightnessDotDotDot": "Налаштувати яскравість...", "adjustAlphaDotDotDot": "Налаштувати прозорість...", - "centerHorizontally": "Центрувати горизонтально", "cropImages": "Обрізати зображення", "importTimeCodes": "Імпорт кодів часу...", - "setTextForSubtitle": "Встановити текст для субтитрів", "screenWidth": "Ширина екрану", "screenHeight": "Висота екрану", "alphaThresholdInfo": "Пікселі з прозорістю нижче порогу стають повністю прозорими", @@ -2205,14 +2366,30 @@ "letterboxCustom": "Користувацька висота смуги", "barHeightPx": "Висота смуги (px)", "titleSafePercent": "Безпечна зона заголовка (%)", + "moveCaptions": "Перемістити титри", + "moveCaptionsDotDotDot": "Перемістити титри...", + "moveCaptionsTo": "Перемістити титри", + "moveCaptionsIntoBars": "У чорні смуги letterbox (поза зображенням)", + "moveCaptionsIntoPicture": "Всередину зображення (зі смуг)", + "moveCaptionsOffset": "Відступ від краю (px)", + "letterboxBarHeightXPxYOfZCaptionsWillMove": "Висота смуги: {0} px - буде переміщено {1} з {2} титрів", + "removeFadeInOut": "Видалити появу/зникнення", + "removeFadeInOutXLinesRemoved": "Рядків затухання, об'єднаних із рядками, до яких вони належать: {0}", + "removeFadeInOutNothingFound": "Рядків появи/зникнення не знайдено", "positionSummary": "{0}×{1} - {2} субтитрів - висота смуги: {3} px", "xInActivePicture": "{0} у кадрі", "xInTopBar": "{0} у верхній смузі", "xInBottomBar": "{0} у нижній смузі", "noImageSubtitlesLoaded": "Субтитри-зображення не завантажено", - "imageBasedFormatNotSupported": "Формат субтитрів на основі зображень не знайдено/не підтримується." + "imageBasedFormatNotSupported": "Формат субтитрів на основі зображень не знайдено/не підтримується.", + "frameRateDeclaredInFile": "Частота кадрів, указана у файлі Blu-ray sup", + "frameRateDetectedFromTimeCodesX": "Частоту кадрів визначено за кодами часу Blu-ray sup (у файлі вказано {0})", + "frameRateFromVideo": "Частота кадрів відео", + "frameRateCurrent": "Поточна частота кадрів", + "frameRateSetManually": "Частоту кадрів задано вручну", + "frameRateUsageInfo": "Експорт у Blu-ray sup, BDN XML, DOST і FCP записується з цією частотою кадрів. Це не змінює жодного часу - для цього використовуйте \"Змінити частоту кадрів\".", + "useVideoFrameRateXInsteadOfY": "Частота кадрів відео - {0}, але для субтитрів встановлено {1}.\n\nВикористати частоту кадрів відео ({0})?" }, - "pickAlignmentTitle": "Вибрати вирівнювання", "removeUnicodeCharacters": { "title": "Видалити/замінити символи Unicode", "charactersFoundX": "Знайдено символів Unicode: {0}", @@ -2222,13 +2399,13 @@ "pickFontNameInstalledFonts": "Встановлені шрифти", "pickFontNameCollectedFonts": "Зібрані шрифти", "colorPickerTitle": "Вибрати колір", + "colorPickerBrightness": "Яскравість", "filterLayersTitle": "Фільтр шарів для відображення", "filterLayersHideFromWaveform": "Приховати з хвильової форми/спектрограми", "filterLayersHideFromSubtitleGrid": "Приховати з таблиці субтитрів", "filterLayersHideFromVideoPreview": "Приховати з попереднього перегляду відео", "pickSubtitleFormat": "Вибрати формат субтитрів", "pickSubtitleFormatImageBasedNoPreview": "Формат субтитрів на основі зображень.\nРастрові зображення створюються під час перетворення - без попереднього перегляду тексту.", - "pickLayerTitle": "Встановити шар", "recentColors": "Останні кольори", "beautifyTimeCodesProfile": { "title": "Редагувати профіль покращення кодів часу", @@ -2236,7 +2413,11 @@ "presetDefault": "За замовчуванням", "presetNetflix": "Netflix", "presetSdi": "SDI", - "general": "Загальне", + "saveAsProfile": "Зберегти як профіль...", + "deleteProfile": "Видалити профіль", + "profileName": "Назва профілю", + "overwriteProfileX": "Профіль із назвою \"{0}\" вже існує. Перезаписати його?", + "deleteProfileX": "Видалити профіль \"{0}\"?", "gap": "Прогалина", "gapSuffix": "кадрів (також оновлює ненульові поля прогалин нижче)", "milliseconds": "мс", @@ -2276,12 +2457,7 @@ "outputFormatAssa": "Advanced Sub Station Alpha (.ass)", "style1": "Стиль 1", "style2": "Стиль 2", - "outlineWidth": "Ширина контуру", - "shadowWidth": "Ширина тіні", - "alignTop": "Зверху", - "alignBottom": "Знизу", - "merge": "_Об'єднати", - "pleaseLoadTwoSubtitles": "Будь ласка, завантажте двоє субтитрів" + "merge": "_Об'єднати" }, "mergeContinuationLines": { "title": "Об'єднати рядки продовження", @@ -2292,12 +2468,9 @@ "columnMerge": "Об'єднати", "columnFirst": "Перший рядок", "columnSecond": "Другий рядок", - "columnMerged": "Об'єднано", - "selectAll": "Вибрати все", - "inverseSelection": "Інвертувати вибір" + "columnMerged": "Об'єднано" }, "removeTextForHearingImpaired": { - "title": "Видалити текст для людей з втратою слуху", "interjections": "Вигуки", "skipIfStartWith": "Пропустити, якщо починається з", "removeTextBetween": "Видалити текст між", @@ -2359,59 +2532,51 @@ "correctWordsX": "Правильні слова: {0}", "namesX": "Імена: {0}", "addedToDictionaryX": "Додано до словника: {0}", - "doNotShowThisAgain": "Більше не показувати це повідомлення" + "doNotShowThisAgain": "Більше не показувати це повідомлення", + "editUseAlwaysList": "Редагувати список \"Використовувати завжди\"...", + "useAlwaysListTitle": "Перевірка орфографії - список \"Використовувати завжди\"", + "useAlwaysListHeader": "Список \"Використовувати завжди\"", + "useAlwaysListDescription": "Слова, які перевірка орфографії замінює автоматично, збережені через \"Змінити все\" та \"Використовувати завжди\". Пара використовується лише тоді, коли слово написано з помилкою.", + "useAlwaysWord": "Слово з помилкою", + "xPairs": "Пар: {0:#,##0}", + "xUnusedPairs": "Жодного разу не використано: {0:#,##0}", + "useAlwaysUnusedHint": "Жодного разу не використано - це слово написано правильно (або це ім'я чи слово користувача), тому перевірка орфографії ніколи його не позначає", + "removeUnused": "Видалити невикористані", + "useAlwaysEmpty": "Для цієї мови ще немає пар \"Використовувати завжди\"", + "useAlwaysEmptyHint": "Пари додаються, коли ви натискаєте \"Змінити все\" або \"Використовувати завжди\" під час перевірки орфографії.", + "useAlwaysNoMatch": "Немає пар, що відповідають пошуку", + "useAlwaysSameWord": "Заміна має відрізнятися від слова.", + "useAlwaysRememberOff": "\"Запам'ятовувати список 'Використовувати завжди'\" вимкнено в Налаштуваннях, тому перевірка орфографії не використовує і не зберігає цей список." }, "video": { "burnIn": { "title": "Створити відео з вбудованими субтитрами", - "infoAssaOff": "Примітка: Підтримка стилю Advanced SubStation Alpha.", - "infoAssaOn": "Примітка: Буде використано Advanced SubStation Alpha стилі :)", - "xGeneratedWithBurnedInSubsInX": "\"{0}\" створено з вбудованими субтитрами в {1}.", - "timeRemainingMinutes": "Час, що залишився: {0} хвилин", - "timeRemainingOneMinute": "Час, що залишився: Одна хвилина", - "timeRemainingSeconds": "Час, що залишився: {0} секунд", - "timeRemainingAFewSeconds": "Час, що залишився: Кілька секунд", - "timeRemainingMinutesAndSeconds": "Час, що залишився: {0} хвилин і {1} секунд", - "timeRemainingOneMinuteAndSeconds": "Час, що залишився: Одна хвилина і {0} секунд", - "targetFileName": "Назва цільового файлу: {0}", "targetFileSize": "Цільовий розмір файлу (потрібне двократне кодування)", "fileSizeMb": "Розмір файлу в МБ", "matchSourceVideoSize": "Відповідати розміру вихідного відео", - "passX": "Прохід {0}", - "encoding": "Кодування", "bitRate": "Бітрейт", "totalBitRateX": "Загальний бітрейт: {0}", "sampleRate": "Частота дискретизації", "audio": "Аудіо", - "stereo": "Стерео", "preset": "Попередньо встановлено", + "tune": "Tune", "pixelFormat": "Формат пікселів", "crf": "CRF", - "tuneFor": "Тюнінг для", - "alignRight": "Вирівняти праворуч", - "getStartPosition": "Отримати початкову позицію", - "getEndPosition": "Отримати кінцеву позицію", "useSource": "Використовувати джерело", - "useSourceFolder": "Використовувати вихідну теку", - "useSourceResolution": "Використовувати вихідну роздільну здатність", - "outputSettings": "Вихідний файл/тека...", "fontSizeFactor": "Коефіцієнт розміру шрифту", "boxType": "Тип коробки", - "fixRightToLeft": "Виправити з правого на ліве", - "cut": "Вирізати", "fromTime": "З часу", "toTime": "До часу", "audioEncoding": "Кодування аудіо", "outputProperties": "Властивості виводу...", "videoFileSize": "Розмір відеофайлу", "oneBox": "Одна коробка", - "boxPerLine": "Коробка на рядок", - "logoInfo": "Виберіть зображення PNG і перетягніть його, щоб розташувати на відео." + "logoInfo": "Виберіть зображення PNG і перетягніть його, щоб розташувати на відео.", + "noSubtitlesGenerateAnyway": "Немає рядків субтитрів для вбудовування.\n\nСтворити відео без субтитрів?", + "noSubtitlesInXItemsGenerateAnyway": "Елементів пакета без рядків субтитрів для вбудовування: {0}.\n\nСтворити ці відео без субтитрів?" }, "videoTransparent": { - "title": "Створити прозоре відео з субтитрами", - "infoAssaOff": "Примітка: Підтримка стилю Advanced SubStation Alpha.", - "infoAssaOn": "Примітка: Буде використано Advanced SubStation Alpha стилі :)" + "title": "Створити прозоре відео з субтитрами" }, "audioToText": { "title": "Розпізнавання мови", @@ -2419,6 +2584,21 @@ "translateToEnglish": "Перекласти на англійську", "transcribing": "Транскрибування...", "transcribingXOfY": "Транскрибування {0} з {1}...", + "linesTranscribedXOfYFailedZ": "Транскрибовано {0} з {1} рядків - {2} з помилкою або без мовлення.\n\nРядки з помилкою зберігають поточний текст. Застосувати транскрибовані рядки чи повторити невдалі (спершу можна змінити двигун або налаштування)?", + "linesTranscribedXOfYCancelled": "Транскрибування скасовано після {0} з {1} рядків.\n\nЗастосувати транскрибовані рядки? Інші рядки зберігають поточний текст.", + "noLinesTranscribed": "Жоден із {0} рядків не вдалося транскрибувати.\nПодробиці дивіться в журналі інструментів.", + "applyTranscribedLines": "Застосувати транскрибовані", + "retryFailedLines": "Повторити невдалі", + "isolateSpeech": "Ізолювати мовлення (повільно)", + "isolateSpeechHint": "Видаляє музику та звукові ефекти перед транскрибуванням. Дає менше пропущених рядків і кращий таймінг для аудіо з гучною музикою, але з GPU триває приблизно стільки ж, скільки саме аудіо, - а без нього в багато разів довше.", + "isolatingSpeech": "Ізоляція мовлення...", + "isolateSpeechFailed": "Не вдалося ізолювати мовлення - транскрибується оригінальне аудіо.", + "detectSpeakers": "Визначати мовців", + "detectSpeakersHint": "Починає кожен рядок із мовця, наприклад \"(speaker 0)\", за допомогою діаризації NVIDIA Sortformer (до 8 мовців). Найкраще працює з двигунами, які розділяють рядки там, де змінюється мовець, як-от Parakeet і Cohere.", + "detectSpeakersNeedsNewerCrispAsr": "\"Визначати мовців\" потребує Crisp ASR v{0} або новішої, але встановлено v{1}.\n\nОновіть Crisp ASR (кнопка з шестернею поруч із двигуном) або вимкніть \"Визначати мовців\".", + "xNeedsNewerCrispAsr": "{0} потребує Crisp ASR v{1} або новішої, але встановлено v{2}.\n\nЗавантажити та встановити оновлення зараз?", + "vad": "VAD", + "vadHint": "Визначення голосової активності: транскрибується лише мовлення, розрізане там, де мовець робить паузу.\nАвто: Silero, і лише для двигунів, яким це потрібно (Cohere, Mega, Index-Echo).\nSilero, FireRedVAD, Whisper-VAD, WebRTC: завжди увімкнено, для кожного двигуна Crisp ASR. FireRedVAD найточніший, Whisper-VAD дає найменше та найчистіших розрізів, але найповільніший.\nIndex-Echo завжди використовує Silero. --vad, --vad-model або --chunk-seconds у розширених налаштуваннях мають пріоритет.", "inputLanguage": "Мова вводу", "advancedWhisperSettings": "Розширені налаштування Whisper", "enableVad": "Увімкнути VAD", @@ -2431,11 +2611,9 @@ "addCustomModelDotDotDot": "Додати користувацьку модель...", "customModelHelp": "Ви можете використовувати власну модель: виберіть файл моделі (whisper.cpp ggml '.bin') або теку моделі (тека faster-whisper з 'model.bin' всередині). Її буде скопійовано до теки моделей і додано до списку вище.", "viewToolsLogFile": "Переглянути файл журналу Whisper", - "reDownloadX": "Перезавантажити {0}", "downloadingSpeechToTextModel": "Завантаження моделі розпізнавання мови", "downloadingSpeechToTextEngine": "Завантаження двигуна розпізнавання мови", "unpackingSpeechToTextEngine": "Розпакування двигуна розпізнавання мови", - "downloadX": "Завантажити {0}", "updateXTitle": "Оновити {0}?", "updateXMessage": "Доступна новіша версія {0}.{1}{1}Завантажити і встановити оновлення зараз?", "whisperPostProcessingTitle": "Постобробка Whisper", @@ -2446,14 +2624,6 @@ "fixCasing": "Виправити регістр", "addPeriods": "Додати крапки", "changeUnderlineToColor": "Змінити підкреслення на колір", - "cueBuilding": "Побудова реплік (MLX Whisper)", - "cueRebuild": "Перебудувати репліки з часових міток слів", - "cueMaxChars": "Макс. символів на репліку", - "cueMaxSeconds": "Макс. секунд на репліку", - "cueMaxCps": "Макс. символів на секунду", - "vocabularyPrompt": "Словниковий запит (імена, місця, терміни)", - "beamSize": "Розмір променя (beam)", - "beamSizeNote": "0 декодує найшвидше. 5 дає найкращу точність (як Faster Whisper) приблизно на 20-30% довше. Значення понад 5 рідко допомагають.", "removeNonSpeechLines": "Видаляти немовленнєві рядки", "removeNonSpeechLinesHint": "Відкидати рядки, які лише описують звук, як-от \"[Музика]\" чи \"(шум хвиль)\"", "removeRepeatedLines": "Видаляти повторювані рядки", @@ -2512,7 +2682,7 @@ "cloneVoicePerLinePreparing": "Отримання голосу кожного рядка з відео...", "cloneVoicePerLineNeedsVideo": "Клонування голосу кожного рядка потребує відео, до якого належать субтитри. Відкрийте відео і спробуйте ще раз.", "cloneVoicePerLineNoClips": "З відео не вдалося отримати аудіо, тож немає з чого клонувати. Перевірте, чи має відео звукову доріжку.", - "cloneVoicePerLineNeedsOriginalSubtitleX": "{0} може клонувати рядок лише тоді, коли знає, що сказано у відео в цьому рядку, а це береться із субтитрів мовою оригіналу. Оригінальні субтитри не завантажено, тож кожен рядок озвучував би перший імпортований голос замість голосу з відео.\n\nВідкрийте оригінальні субтитри (Файл > Відкрити оригінал...) поруч із перекладом, а потім створіть знову.", + "cloneVoicePerLineTranscribeClipsXYZ": "{0} може клонувати рядок, лише коли відомо, що говорять у відео в цьому рядку. Це невідомо для {1} з {2} рядків - зазвичай це береться з субтитрів мовою оригіналу (Файл > Відкрити оригінал...).\n\nТранскрибувати ці рядки з відео за допомогою розпізнавання мови зараз?\n\nТак: виберіть двигун і мову, якою говорять у відео, і еталонні фрагменти буде транскрибовано перед створенням мовлення.\nНі: все одно створити - ці рядки буде озвучено звичайним голосом замість голосу з відео.", "voiceCloneConsentTitle": "Клонування голосу - перед початком", "voiceCloneConsentHeader": "Ви збираєтеся клонувати голос", "voiceCloneConsentIntro": "Клонування копіює голос реальної людини. У більшості місць це супроводжується правилами, а в ЄС - юридичними обов'язками, які лягають на вас, а не на Subtitle Edit. Прочитайте це один раз.", @@ -2528,6 +2698,12 @@ "voiceCloneConsentDeclined": "Клонування голосу недоступне, доки ці умови не прийнято.", "advancedTtsAudioProcessing": "Обробка аудіо", "advancedTtsOutput": "Вихід", + "advancedTtsVoiceCloning": "Клонування голосу", + "cleanCloneReferences": "Очищати еталони для клонування голосу (Sidon)", + "cleanCloneReferencesDescription": "Видаляє музику, шум і реверберацію з аудіо, з якого клонується голос: фрагментів, узятих із відео, і записів, імпортованих у керуванні голосами. Використовує модель відновлення мовлення Sidon на audio.cpp (завантаження 940 MB). Клонований голос більше не копіює фон, але дуже тихе мовлення або мовлення, що накладається, може звучати приглушено.", + "cleaningCloneReferencesDotDotDot": "Очищення еталонів для клонування голосу...", + "downloadSidonTitle": "Завантажити Sidon?", + "downloadSidonQuestionX": "Для очищення еталонів клонування голосу потрібна модель відновлення мовлення Sidon ({0}).\n\nЗавантажити її зараз? Без неї еталони використовуються як є.", "rubberbandInstalled": "(встановлено)", "rubberbandNotFound": "(не знайдено у FFmpeg)", "generationFolder": "Папка генерації", @@ -2536,14 +2712,11 @@ "deleteTempFilesDescription": "Видаляє папку генерації при закритті вікна Синтез мовлення. Збережені аудіо- та відеофайли залишаються.", "piperSettings": "Налаштування Piper", "title": "Синтез мовлення", - "textToSpeechEngine": "Двигун синтезу мовлення", "reviewAudioSegments": "TTS - Переглянути аудіо сегменти", "reviewAudioSegmentsHistory": "TTS - Переглянути історію аудіо", "stability": "Стабільність", "similarity": "Схожість", "speakerBoost": "Підсилення спікерів", - "styleExaggeration": "Переоцінка стилю", - "regenerateAudioSelectedLine": "Перестворити аудіо для виділеного рядка", "generateSpeechFromText": "Створити мовлення з тексту", "testVoice": "Тест голосу", "addAudioToVideoFile": "Додати аудіо до відеофайлу", @@ -2562,22 +2735,98 @@ "videoEncodingSettings": "TTS - Налаштування кодування відео", "elevenLabsSettings": "TTS - Налаштування ElevenLabs", "elevenLabsSettingsResetHint": "Скинути налаштування ElevenLabs до типових значень", + "provider": "Постачальник", + "openAiCompatibleSettings": "TTS - налаштування, сумісні з OpenAI", + "customServerUrl": "URL власного сервера", + "customServerUrlHint": "Повна кінцева точка мовлення сервера, сумісного з OpenAI, яка використовується, коли постачальник \"Custom\", наприклад http://localhost:8880/v1/audio/speech", + "customModels": "Власні моделі", + "customModelsHint": "Назви моделей через кому, що пропонуються, коли постачальник \"Custom\"", + "customVoices": "Власні голоси", + "customVoicesHint": "Назви голосів через кому, що використовуються постачальником \"Custom\" і моделями OpenRouter, які не публікують список голосів", + "instructions": "Інструкції", + "instructionsHint": "Необов'язковий стиль мовлення, наприклад \"Говори спокійним, теплим голосом\". Надсилається лише моделям, які це підтримують (не tts-1/tts-1-hd)", + "openAiSpeedHint": "Швидкість мовлення від 0.25 до 4.0 (1.0 - звичайна). Підтримується не всіма моделями", + "outputFormat": "Вихідний формат", + "outputFormatHint": "Аудіоформат, що запитується в сервера. \"Auto\" запитує MP3 і перемикається на PCM для моделей, які повертають лише PCM (наприклад, Gemini TTS)", "regenerateAudio": "Перестворити аудіо", "autoContinuePlaying": "Автоперехід відтворення", "addingAudioToVideoFileDotDotDot": "Додавання аудіо до відеофайлу...", "preparingMergeDotDotDot": "Підготовка об'єднання...", "importVoiceDotDotDot": "Імпортувати голос...", + "renameVoiceDotDotDot": "Перейменувати голос...", + "renameVoiceTitle": "Перейменувати голос", + "voiceXCouldNotBeRenamedX": "Не вдалося перейменувати голос '{0}': {1}", + "deleteVoiceDotDotDot": "Видалити голос...", + "deleteVoiceTitle": "Видалити голос", + "deleteVoiceXQuestion": "Видалити голос '{0}'?\n\nЕталонний запис і його файли буде видалено з диска.", + "voiceXCouldNotBeDeletedX": "Не вдалося видалити голос '{0}': {1}", "voiceImportSuccessTitle": "Голос імпортовано", "voiceXImported": "Голос '{0}' успішно імпортовано", "dropAudioFileHereToImportVoice": "Перетягніть аудіофайл сюди, щоб імпортувати голос", "dropAudioFileHereHint": ".wav або .mp3", "voiceCloneTranscriptTitle": "Введіть транскрипцію аудіо (потрібно для клонування голосу)", + "voiceManagerTitle": "TTS - Керування голосами", + "voiceManagerDotDotDot": "Керування голосами...", + "voiceManagerMenuItem": "Керування голосами TTS...", + "speakFromCurrentLineTitle": "Читати вголос від поточного рядка", + "speakFromCurrentLineDotDotDot": "Читати вголос від поточного рядка...", + "speakFromCurrentLineHint": "Читає рядки вголос один за одним, починаючи з поточного рядка. Натисніть Escape або клацніть інший рядок, щоб зупинити.", + "stopSpeaking": "Зупинити читання", + "speakingLineX": "Читання рядка {0}... (Escape - зупинити)", + "playWithSpeechTitle": "Відтворити з мовленням", + "playWithSpeechDotDotDot": "Відтворити з мовленням...", + "playWithSpeechHint": "Відтворює відео та промовляє кожен рядок, коли відео доходить до нього. Пауза та перемотування як зазвичай; натисніть Escape, щоб зупинити.", + "lowerVideoVolumeWhileSpeaking": "Зменшувати гучність відео під час мовлення", + "pauseVideoWhenSpeechRunsLate": "Призупиняти відео, коли мовлення заходить на наступний рядок", + "waitingForSpeechLineX": "Створення мовлення для рядка {0}... (Escape - зупинити)", + "voiceKindClone": "Клонований голос", + "voiceKindPreset": "Вбудований", + "voiceKindModel": "Модель голосу", + "voiceKindOnline": "Онлайн", + "transcript": "Транскрипт", + "transcriptRequiredHint": "Обов'язково - під час клонування двигун читає цей транскрипт запису (ref-text)", + "transcriptOptionalHint": "Необов'язково - точний транскрипт запису покращує клонування", + "saveTranscript": "Зберегти транскрипт", + "searchVoices": "Пошук голосів", + "loadingVoicesDotDotDot": "Завантаження голосів...", + "noVoicesForEngine": "Немає голосів - імпортуйте запис або завантажте пакет голосів", + "noVoicesMatchFilter": "Немає голосів, що відповідають пошуку", + "copyVoiceTo": "Копіювати голос до", + "copyVoiceToDotDotDot": "Копіювати голос до двигуна...", + "copyingVoiceXToYDotDotDot": "Копіювання голосу '{0}' до {1}...", + "voiceXCopiedToY": "Голос '{0}' скопійовано до {1}", + "voiceXCouldNotBeCopiedToY": "Не вдалося скопіювати голос '{0}' до {1} - подробиці в журналі", + "voiceXAlreadyExistsInYContinue": "Голос із назвою '{0}' вже існує в {1}.\n\nВсе одно копіювати?", + "openVoicesFolder": "Відкрити теку голосів", + "downloadVoicePacksDotDotDot": "Завантажити пакети голосів...", + "downloadVoicePacksTitle": "TTS - Завантаження пакетів голосів", + "voicePackIntro": "Пакети голосів - це набори коротких чистих записів із транскриптами, готових до клонування. Кожен пакет імпортується у вибраний двигун (наявні голоси пропускаються) і містить ATTRIBUTION.txt із ліцензією та джерелами.", + "voicePackDetailsXVoicesYSizeZLicense": "Голосів: {0} · {1} · {2}", + "installToEngine": "Встановити до двигуна", + "downloadAndInstall": "Завантажити та встановити", + "selectAtLeastOneVoicePack": "Виберіть принаймні один пакет голосів", + "downloadingXDotDotDot": "Завантаження {0}...", + "installingXDotDotDot": "Встановлення {0}...", + "xVoicesInstalledYSkipped": "Встановлено голосів: {0} (вже наявних: {1})", "useSpeechToTextDotDotDot": "Використати розпізнавання мови...", "advancedTtsSettings": "Розширені налаштування TTS", "proAudioPostProcessing": "Професійна постобробка аудіо", "proAudioPostProcessingDescription": "Застосовує теплоту еквалайзера, шумовий гейт, компресію, нормалізацію гучності (-16 LUFS) і появу/зникнення до кожного сегмента.", + "generatingSpeechSegmentXOfY": "Створення мовлення: сегмент {0} з {1}", + "adjustingSpeedSegmentXOfY": "Коригування швидкості: сегмент {0} з {1}", + "mergingAudioSegmentXOfY": "Об'єднання аудіо: сегмент {0} з {1}", + "postProcessingSegmentXOfY": "Постобробка: сегмент {0} з {1}", "audioDucking": "Приглушення аудіо", "audioDuckingDescription": "Зменшує гучність оригінального аудіо відео і змішує його з аудіо TTS, тому оригінальна звукова доріжка все ще ледь чутна.", + "removeOriginalSpeech": "Видалити оригінальне мовлення (повільно)", + "removeOriginalSpeechDescription": "Зберігає музику та звукові ефекти оригінального відео, але видаляє з нього мовлення і накладає поверх нове мовлення. З GPU триває приблизно стільки ж, скільки саме відео, а без нього в багато разів довше. Якщо приглушення аудіо увімкнено, його гучність застосовується до музики та ефектів. Діє лише тоді, коли мовлення додається до відеофайлу.", + "removingOriginalSpeech": "Видалення оригінального мовлення...", + "removeOriginalSpeechFailed": "Не вдалося видалити оригінальне мовлення - мовлення додано до відео без цього.", + "speechRemovalModel": "Модель", + "speechRemovalModelDescription": "Найкраща якість зберігає найбільше музики та звукових ефектів. Швидкий варіант приблизно втричі швидший, але трохи більше музики втрачається.", + "speechRemovalBestQuality": "Найкраща якість (Mel-Band RoFormer)", + "speechRemovalFast": "Швидко (HTDemucs)", + "videoTruncatedWarning": "Зображення в новому відеофайлі зупиняється після {0}, але оригінальне відео триває {1} - зображення застигне, поки звук продовжуватиметься.\n\nФайл збережено. Вивід ffmpeg дивіться в error-log.txt у теці даних Subtitle Edit.", "originalVolumePercent": "Оригінальна гучність %", "vadSilenceCompression": "VAD-стиснення тиші", "vadSilenceCompressionDescription": "Скорочує паузи між словами перед зміною темпу. Використовує виявлення голосової активності (VAD) для стиснення лише проміжків тиші, залишаючи мовлення недоторканим. Це бажаний перший крок — він зменшує тривалість без жодної втрати якості.", @@ -2644,7 +2893,6 @@ "subtitleMergedLinesAppliedPlural": "Застосовано {0} об'єднань рядків з перетворення тексту в мовлення" }, "shotChanges": { - "titleGenerateOrImport": "Створити/імпортувати зміни кадрів", "generateShotChanges": "Створити зміни кадрів", "importShotChanges": "Імпортувати зміни кадрів", "generateShotChangesWithFfmpeg": "Створити зміни кадрів з ffmpeg", @@ -2654,7 +2902,9 @@ "timeCodeFormatColon": "Формат коду часу:", "shotChangesList": "Список змін кадрів", "shotChangesClearQuestion": "Ви впевнені, що хочете очистити всі зміни кадрів?", - "deleteSelectedShotChangeQuestion": "Ви впевнені, що хочете видалити обрану зміну кадру?" + "deleteSelectedShotChangeQuestion": "Ви впевнені, що хочете видалити обрану зміну кадру?", + "shotChanges": "Зміни кадрів", + "exportShotChanges": "Експортувати зміни кадрів" }, "chapters": { "title": "Розділи", @@ -2684,7 +2934,6 @@ "scaleTimesDescription": "Масштабувати час кожного розділу з однієї частоти кадрів на іншу.", "fromFrameRate": "З", "toFrameRate": "На", - "apply": "Застосувати", "writeToVideo": "Записати у відео...", "writeToVideoTitle": "Записати розділи у відео", "writeToVideoDescription": "Розділи записуються в копію відеофайлу. Нічого не перекодовується, тож це швидко і без втрат.", @@ -2694,7 +2943,6 @@ "writtenToX": "Розділи записано в {0}", "unableToWriteChapters": "Не вдалося записати розділи у відеофайл.", "chaptersSavedToX": "Розділи збережено в {0}", - "noVideoLoaded": "Відео не завантажено", "emptyListCallToAction": "Розділів ще немає - додайте розділ у позиції відео або імпортуйте їх з відео чи файлу." }, "videoOcr": { @@ -2705,10 +2953,6 @@ "bottomHalf": "Нижня половина", "fullFrame": "Весь кадр", "engine": "Двигун OCR", - "language": "Мова", - "url": "URL", - "model": "Модель", - "apiKey": "Ключ API", "scan": "Сканувати", "framesPerSecond": "Кадрів на секунду", "textBrightnessMinimum": "Мінімальна яскравість тексту (0=вимк.)", @@ -2723,10 +2967,10 @@ "runningOcrXY": "Виконання OCR... {0}/{1}", "noLinesFoundTitle": "Субтитрів не знайдено", "noLinesFoundMessage": "В області сканування не знайдено тексту - спробуйте налаштувати область сканування, двигун або мінімальну яскравість.", + "ocrStoppedEarlyMessage": "Двигун OCR зупинився до того, як було прочитано всі кадри, тому знайдені рядки збережено, а решти бракує.{0}{0}{1}", "linesFoundX": "Знайдено {0} рядків", "refiningTimingXY": "Уточнення таймінгу... {0}/{1}", "fixOcrErrors": "Виправляти помилки OCR", - "dictionary": "Словник", "editLineX": "Редагувати рядок {0}", "downloadDictionaryHint": "Завантажити словник для перевірки орфографії...", "framesPerSecondHint": "Скільки кадрів за секунду сканується. 5 покриває звичайні субтитри; більше значення вловлює дуже короткі рядки і покращує групування, але потребує більше часу на OCR.", @@ -2743,7 +2987,58 @@ "testOcr": "Тестувати поточний кадр", "testOcrRunning": "Тестування OCR на поточному кадрі...", "testOcrResultX": "Результат тесту: {0}", - "testOcrNoTextFound": "Тест: в області сканування текст не знайдено" + "testOcrNoTextFound": "Тест: в області сканування текст не знайдено", + "engineSettings": "Налаштування двигуна OCR", + "engineBuiltIn": "Вбудований в операційну систему", + "engineExternalServer": "Зовнішній сервер (встановлюється та керується поза Subtitle Edit)", + "engineCloudApi": "Хмарний API (потрібен ключ API)", + "engineNothingToInstall": "Нічого встановлювати", + "engineModelsMissing": "Двигун встановлено, моделей бракує", + "website": "Вебсайт" + }, + "backgroundMusic": { + "title": "Створити фонову музику", + "generateBackgroundMusicDotDotDot": "Створити фонову музику...", + "preset": "Пресет", + "prompt": "Запит", + "tempoBpm": "Темп (BPM)", + "generateSeconds": "Створити (секунди)", + "seed": "Seed", + "randomSeed": "Випадковий", + "outputLength": "Тривалість результату", + "outputLengthVideoX": "{0} (тривалість відео - музика безшовно зациклюється)", + "musicVolumePercent": "Гучність музики (%)", + "removeExistingAudioTracks": "Видалити наявні аудіодоріжки", + "originalAudioVolumePercent": "Гучність оригінального аудіо (%)", + "saveAudioDotDotDot": "Зберегти аудіо...", + "addToVideoDotDotDot": "Додати до відео...", + "engineInfo": "ACE-Step 1.5 Turbo через audio.cpp (ліцензія MIT) - працює локально", + "presetCooking": "Кулінарія / випічка", + "presetTech": "Огляд техніки / гаджетів", + "presetForest": "Ліс / природа", + "presetRelaxingPiano": "Розслаблююче фортепіано", + "presetLofiVlog": "Lo-fi влог", + "presetCorporate": "Корпоративне / пояснювальне відео", + "loadingModel": "Завантаження моделі...", + "composing": "Компонування...", + "generatingAudio": "Створення аудіо...", + "decodingAudio": "Декодування аудіо...", + "creatingLoop": "Створення безшовного циклу...", + "resultLoopXYZ": "Цикл: {0} при {1} BPM, збіг шва {2}%", + "resultNoLoopX": "Музика готова: {0}", + "addingMusicToVideo": "Додавання музики до відео...", + "downloadModelTitle": "Завантажити музичну модель?", + "downloadModelQuestionX": "Для створення музики потрібна модель ACE-Step 1.5 ({0}).\n\nЗавантажити модель?", + "downloadingModel": "Завантаження моделі ACE-Step 1.5...", + "saveAudioTitle": "Зберегти фонову музику", + "audioFileSavedX": "Аудіофайл збережено: {0}", + "unableToGenerateMusic": "Не вдалося створити музику", + "unableToAddMusicToVideo": "Не вдалося додати музику до відео", + "outputLengthTextToSpeech": "Відповідає створеному мовленню - музика безшовно зациклюється і стихає, коли хтось говорить", + "addBackgroundMusic": "Додати фонову музику", + "addBackgroundMusicHint": "Створити музику локально за допомогою ACE-Step 1.5 і змікшувати її під мовленням - вона стихає, коли хтось говорить", + "generatingBackgroundMusicDotDotDot": "Створення фонової музики...", + "mixingBackgroundMusicDotDotDot": "Мікшування фонової музики..." }, "goToVideoPosition": "Перейти до позиції відео", "goToVideoPositionDotDotDot": "Перейти до позиції відео...", @@ -2754,32 +3049,89 @@ "openSecondarySubtitleOnVideoPlayerDotDotDot": "Додаткові субтитри на відеопрогравачі, відкрити...", "openSecondarySubtitleOnVideoPlayer": "Додаткові субтитри (на відеопрогравачі)", "removeSecondarySubtitleOnVideoPlayer": "Додаткові субтитри на відеопрогравачі, видалити", + "editSecondarySubtitleSettings": "Редагувати налаштування додаткових субтитрів", + "editSecondarySubtitleSettingsDotDotDot": "Редагувати налаштування додаткових субтитрів...", + "secondarySubtitleRememberSettings": "Запам'ятати ці налаштування", + "secondarySubtitleDoNotShowAgain": "Більше не показувати це вікно", + "secondarySubtitleJustifyLines": "Вирівнювати рядки по ширині", "openRecentVideo": "Відкрити недавнє відео", "clearRecentVideos": "Очистити недавні відео", "cutVideoTitle": "Вирізати відео", "cutVideoDotDotDot": "Вирізати відео...", + "remuxVideoTitle": "Перепакувати відео", + "remuxVideoDotDotDot": "Перепакувати відео...", + "remuxVideoInputVideo": "Вхідне відео:", + "remuxVideoMultipleTracksRequiresMkv": "Кілька аудіодоріжок або доріжок субтитрів потребують контейнера MKV. Вихідний формат автоматично змінено на .mkv.", + "remuxVideoOutputFormat": "Вихідний формат:", + "remuxVideoOutputFile": "Вихідний файл:", + "remuxVideoRemuxing": "Перепакування відео та аудіо...", + "remuxVideoFinalizing": "Завершення вихідного файлу (переміщення індексу на початок)...", + "remuxVideoCompleted": "Відео успішно перепаковано.", + "remuxVideoFailed": "Не вдалося перепакувати відео.", + "remuxVideoPleaseSelectBoth": "Виберіть і відеофайл, і аудіофайли.", + "remuxVideoAssRequiresMkv": "Субтитри ASS/SSA потребують контейнера MKV, щоб зберегти все форматування та стилі.", + "remuxVideoSccRequiresMov": "Субтитри Scenarist (.scc) вбудовуються як приховані субтитри CEA-608, які потребують контейнера MOV або MPG. Вихідний формат автоматично змінено на .mov.", + "remuxVideoSccNotWithWebM": "Приховані субтитри Scenarist (.scc) потребують контейнера MOV, який не може містити відео VP9/AV1 з файлу .webm. Видаліть файл .scc або перетворіть його в інший формат субтитрів.", + "remuxVideoMpgMaxTwoCaptionTracks": "Файл .mpg містить щонайбільше дві доріжки прихованих субтитрів (CC1 і CC3) - видаліть файли субтитрів, щоб залишилося один або два.", + "remuxVideoMccRequiresMpg": "Субтитри MacCaption (.mcc) вбудовуються як приховані субтитри CEA-608/708 у відео MPEG-2, що потребує контейнера MPG. Вихідний формат автоматично змінено на .mpg.", + "remuxVideoMpgReencodeVideoX": "Приховані субтитри у файлі .mpg потребують відео MPEG-2, а відео - {0}. Перекодувати відео в MPEG-2? Це триває довше і трохи знижує якість.", + "remuxVideoFileHasNoAudioX": "\"{0}\" не має аудіодоріжки, тому його не можна використати як джерело аудіо.", + "remuxVideoAddingClosedCaptions": "Додавання прихованих субтитрів...", + "remuxVideoSelectAudioTrack": "Вибрати аудіодоріжку", + "remuxVideoSelectAudioTrackFor": "Вибрати аудіодоріжку для {0}", + "remuxVideoSelectAudioTrackPrompt": "Файл '{0}' містить аудіодоріжок: {1}. Виберіть аудіодоріжку для перепакування:", + "remuxVideoVideoFile": "відео", + "remuxVideoAudioFile": "аудіо", + "remuxVideoAudioFiles": "Аудіофайли:", + "remuxVideoSubtitleFiles": "Файли субтитрів (необов'язково, м'які субтитри):", + "remuxVideoSelectAudioTrackDotDotDot": "Вибрати аудіодоріжку...", + "remuxVideoFilesX": "Файлів: {0}", + "remuxVideoMixAudio": "Змікшувати аудіо в одну доріжку", + "remuxVideoVolumePercent": "Гучність %:", + "remuxVideoVolumeX": "Гучність {0}%", + "remuxVideoFastStart": "Оптимізувати для веб-потокового передавання (індекс на початок - повільніше)", "embedSubtitlesDotDotDot": "Додати/видалити вбудовані субтитри...", "generateTimeCodes": "Створити коди часу", "checkeredImage": "Шахова картинка", - "saveVideoAsTitle": "Зберегти відео як", "promptForFfmpegParamsAndGenerate": "Запитати параметри ffmpeg і створити", "cutVideoCutSegments": "Вирізати сегменти", "cutVideoMergeSegments": "Об'єднати сегменти", "cutVideoSplitSegments": "Зберегти сегменти окремо", - "cutVideoType": "Тип вирізання", - "mpvRenderAuto": "Авто", - "mpvRenderNative": "Нативний", - "mpvRenderOpenGl": "OpenGL", - "mpvRenderSoftware": "Програмне (повільно)", "cutVideoAlsoCutSubtitle": "Також обрізати субтитри", "cutVideoSubtitleFileGeneratedX": "Створено файл субтитрів: {0}", + "cutVideoTransition": "Перехід між сегментами", + "cutVideoTransitionDurationSeconds": "Тривалість (секунди)", + "cutVideoFadeInSeconds": "Поява з чорного на початку (секунди)", + "cutVideoFadeOutSeconds": "Затухання в чорне в кінці (секунди)", + "cutVideoPreviewTransition": "Попередній перегляд переходу", + "cutVideoPreviewTransitionTitle": "Попередній перегляд переходу", + "cutVideoPreviewNeedsJoin": "Немає з'єднання для попереднього перегляду - додайте принаймні два сегменти для об'єднання або сегмент, який треба вирізати з середини.", + "cutVideoTransitionInfoX": "Кожен перехід перекриває частини, які він з'єднує, тому результат стає коротшим на {0} секунд за кожне з'єднання. Час вирізаних субтитрів коригується відповідно.", + "cutVideoTransitionFade": "Затухання", + "cutVideoTransitionFadeBlack": "Затухання через чорне", + "cutVideoTransitionFadeWhite": "Затухання через біле", + "cutVideoTransitionFadeGrays": "Затухання через сіре", + "cutVideoTransitionDissolve": "Розчинення", + "cutVideoTransitionWipeLeft": "Витирання вліво", + "cutVideoTransitionWipeRight": "Витирання вправо", + "cutVideoTransitionWipeUp": "Витирання вгору", + "cutVideoTransitionWipeDown": "Витирання вниз", + "cutVideoTransitionSlideLeft": "Зсув вліво", + "cutVideoTransitionSlideRight": "Зсув вправо", + "cutVideoTransitionSlideUp": "Зсув вгору", + "cutVideoTransitionSlideDown": "Зсув вниз", + "cutVideoTransitionSmoothLeft": "Плавно вліво", + "cutVideoTransitionSmoothRight": "Плавно вправо", + "cutVideoTransitionCircleOpen": "Коло відкриття", + "cutVideoTransitionCircleClose": "Коло закриття", + "cutVideoTransitionRadial": "Радіальний", + "cutVideoTransitionPixelize": "Пікселізація", + "cutVideoTransitionBlur": "Розмиття", "importCurrentSubtitle": "Імпортувати поточні субтитри", "addRemoveEmbeddedSubtitlesTitle": "Додати/видалити вбудовані субтитри", "addCurrentSubtitle": "Додати поточні субтитри", - "titleOrLanguage": "Назва/мова", "viewMatroskaTrackX": "Переглянути доріжку Matroska - {0}", "addRemoveEmbeddedSubtitlesMp4Title": "Додати/видалити вбудовані субтитри (MP4)", - "embeddedTrackColumnNew": "Новий", "embeddedTrackNoSubtitlesFoundTitle": "Субтитри не знайдено", "embeddedTrackNoSubtitlesFoundMessage": "Вибраний файл субтитрів не містить жодних субтитрів.", "embeddedTrackNoTracksTitle": "Доріжки не додано", @@ -2791,6 +3143,7 @@ "embeddedTrackGeneratingVideoXY": "Створення відео... {0}% {1}", "embeddedTrackGeneratingVideo": "Створення відео...", "mp4FilesFilter": "Файли MP4", + "embeddedTracksPickVideoHint": "Виберіть відеофайл або перетягніть його сюди, щоб побачити його доріжки субтитрів.", "resolutionSeparator": "x", "openFromUrlTitle": "Відкрити відеофайл з URL", "openFromUrlOpenOnline": "Відкрити онлайн", @@ -2799,9 +3152,7 @@ "openFromUrlDownloadAndOpen": "Завантажити і відкрити", "openFromUrlDownloadAndOpenDescription": "Зберегти відео локально, потім відкрити його.", "openFromUrlDownloadAndOpenNote": "Потрібно для розпізнавання мови і піків хвильової форми.", - "openFromUrlMissing": "Будь ласка, спочатку введіть URL відео.", "openFromUrlDownloadingTitle": "Завантаження відео", - "openFromUrlSaveAs": "Зберегти відео як", "openFromUrlDownloadSubtitles": "Також завантажити субтитри", "openFromUrlDownloadVideo": "Завантажити відео", "openFromUrlIncludeAutoGeneratedSubtitles": "Включати автоматично створені субтитри (мовою оригіналу)", @@ -2838,12 +3189,15 @@ "reEncodeGeneratingDone": "Створення завершено", "reEncodeGeneratedFilesX": "Створені файли ({0}):", "reEncodeFfmpegParameters": "Параметри ffmpeg", + "analyzingVideoX": "Аналіз відео... {0}% {1}", + "analyzingVideoXofY": "Аналіз відео {0}/{1}... {2}% {3}", + "backgroundImageFileNotSelected": "Файл фонового зображення не вибрано", + "backgroundImageFileDoesNotExistX": "Файл фонового зображення не існує: {0}", "voices": "Голоси", "presets": "Пресети", "talkerX": "Мовець {0}", "baseModel": "Базова модель", "turboModel": "Модель Turbo", - "ttsUpdateX": "Оновити {0}", "ttsEngineNotInstalled": "{0} не встановлено", "ttsEngineUpdateAvailable": "{0} - доступне оновлення", "ttsEngineTooOldUpdateRequired": "{0} застарілий - потрібне оновлення", @@ -2864,19 +3218,25 @@ "cloneVoiceTo": "Клонувати голос у", "cloneVoiceNameTitle": "Назва нового голосу", "cloneVoiceExtractFailed": "Не вдалося видобути аудіо для вибраного рядка.", + "lineXHasNoDuration": "Рядок {0} не має тривалості (час кінця не пізніше за час початку), тому немає аудіо для вилучення.", + "audioClipsSkippedLinesX": "Для цих рядків не вдалося вилучити аудіо, тому їх пропущено: {0}", "toggleShotChange": "Перемкнути зміну кадру", "resetWaveformZoomAndSpeed": "Скинути масштабування та швидкість хвильової форми", "showOriginalSubtitle": "Показати оригінальні субтитри", + "timelineGroupTracksBy": "Групувати доріжки субтитрів за", + "timelineShowThumbnails": "Показувати мініатюри відео", + "showSpeechOnly": "Показати лише мовлення (повільне створення)", + "isolatingSpeechForWaveformX": "Ізоляція мовлення для хвильової форми... {0}", + "isolatingSpeechForWaveformFailed": "Не вдалося ізолювати мовлення для хвильової форми - дивіться журнал інструментів", + "speechOnlyWaveformReady": "Хвильова форма тепер показує лише мовлення", "showOnlyWaveform": "Показати тільки хвильову форму", "showOnlySpectrogram": "Показати тільки спектрограму", "showWaveformAndSpectrogram": "Показати хвильову форму і спектрограму", - "spectrogramClassic": "Класична", "spectrogramClassicViridis": "Viridis", "spectrogramClassicPlasma": "Plasma", "spectrogramClassicInferno": "Inferno", "spectrogramClassicTurbo": "Turbo", "spectrogramNeon": "Неон", - "waveformDrawStyleClassic": "Класична", "waveformDrawStyleFancy": "Красива", "setVideoPositionAndPauseAndSelectSubtitle": "Встановити позицію відео, паузу і вибрати субтитри", "setVideopositionAndPauseAndSelectSubtitleAndCenter": "Встановити позицію відео, паузу, вибрати субтитри і центрувати", @@ -2888,7 +3248,6 @@ }, "sync": { "visualSync": "Візуальна синхронізація", - "sync": "Синхронізація", "startScene": "Початкова сцена", "endScene": "Кінцева сцена", "playTwoSecondsAndBack": "Відтворити 2 сек і назад", @@ -2904,24 +3263,23 @@ "adjustSelectedLinesAndForward": "Налаштувати виділені рядки і вперед", "toFrameRate": "До частоти кадрів", "fromFrameRate": "З частоти кадрів", + "videoXFrameRateY": "{0} - {1} fps", "adjustAllTimes": "Налаштувати всі часи (показати раніше/пізніше)", "showEarlier": "Показати раніше", "showLater": "Показати пізніше", - "changeFrameRate": "Змінити частоту кадрів", "setSyncPoint": "Встановити точку синхронізації", "setSyncPointViaVideo": "Встановити точку синхронізації через відео...", "syncPointTimeCode": "Код часу точки синхронізації", "syncPoints": "Точки синхронізації", "pointSync": "Синхронізація точок", "pointSyncViaOther": "Синхронізація точок через інші субтитри", - "adjustmentX": "Коригування: {0}", - "syncPointCandidateInfo": "Рядки, що починаються після {0}+ секунд тиші, підсвічено - вони часто дають надійні точки синхронізації", + "syncPointCandidateAfterInfo": "Рядки, після яких іде {0}+ секунд тиші, підсвічено - вони часто є надійними точками синхронізації", + "gapAfter": "Прогалина після", "adjustAllShortcuts": "Клавіатурні скорочення:\r\n\r\n• Shift + Ліворуч/Праворуч: Перемістити на 10 мс\r\n• Ctrl + Ліворуч/Праворуч: Перемістити на 100 мс\r\n• Alt + Ліворуч/Праворуч: Перемістити на 500 мс", "adjustAllShortcutsFrames": "Ярлики клавіатури:\r\n\r\n• Shift + Вліво/Вправо: Пересунути на 1 кадр\r\n• Ctrl + Вліво/Вправо: Пересунути на 10 кадрів\r\n• Alt + Вліво/Вправо: Пересунути на 1 секунду", "offsetInSeconds": "Зсув в секундах", "speedFactor": "Коефіцієнт швидкості", - "showEarlierLaterDotDotDot": "Показати раніше/пізніше...", - "totalAdjustmentX": "Загальне коригування: {0}" + "showEarlierLaterDotDotDot": "Показати раніше/пізніше..." }, "translate": { "translateViaCopyPaste": "Автоматичний переклад через копіювання/вставку", @@ -2936,6 +3294,7 @@ "lineMerge": "Об'єднання рядків", "delayInSecondsBetweenRequests": "Затримка в секундах між запитами", "maxBytesPerRequest": "Макс. байтів на запит", + "keepMusicLinesUntranslated": "Не перекладати рядки в музичних символах (♪ ... ♪)", "promptText": "Текст підказки", "resetPromptToDefault": "Скинути підказку до типової", "translateEachLineSeparately": "Перекладати кожен рядок окремо", @@ -2949,11 +3308,10 @@ "readyToTranslate": "Готово до перекладу", "translating": "Переклад...", "translationComplete": "Переклад завершено", + "translateInPlaceNoOriginal": "Перекладати на місці (не створювати оригінал)", "translationCancelled": "Переклад скасовано", "swapLanguages": "Поміняти місцями вихідну і цільову мови", "xIsAlreadyDownloadedReDownload": "{0} вже завантажено. Перезавантажити?", - "advancedDotDotDot": "Додатково...", - "advancedSettings": "Додаткові налаштування", "advancedSettingsSubtitle": "Параметри пакетів, контексту і семплювання для розширеного двигуна llama.cpp", "context": "Контекст", "synopsis": "Синопсис", @@ -2977,6 +3335,7 @@ "useOnlyExtraServerParametersHint": "Запускати llama-server із наведеними вище параметрами замість власного налаштування Subtitle Edit - встановлюються лише модель, хост і порт", "serverRunningAtX": "Сервер працює за адресою {0}", "customPromptHint": "Власні інструкції ({0} = мова оригіналу, {1} = цільова мова); порожнє = вбудована підказка", + "editTranslationHint": "Клацніть переклад виділеного рядка, щоб відредагувати його (Enter зберігає, Escape скасовує)", "llamaCppDownloadEngineAndModelPrompt": "llama.cpp потребує завантаження двигуна llama-server і моделі перекладу. Завантажити зараз?", "llamaCppDownloadEnginePrompt": "llama.cpp потребує завантаження двигуна llama-server. Завантажити зараз?", "llamaCppDownloadModelPrompt": "llama.cpp потребує завантаження вибраної моделі перекладу. Завантажити зараз?" @@ -3014,8 +3373,14 @@ "checkForUpdatesChannelStableAndBeta": "Стабільні й бета-версії", "assaAutoSetResolution": "ASSA: встановлювати роздільну здатність (PlayResX/PlayResY) з відео під час його відкриття", "assaAutoSetResolutionPrompt": "ASSA: питати перед зміною роздільної здатності відповідно до відео (інакше перераховувати автоматично)", + "videoControls": "Елементи керування відео", + "videoControlsEdit": "Редагувати елементи керування відео...", + "videoControlsPositionText": "Текст позиції / тривалості", + "videoControlsVideoFileName": "Ім'я відеофайлу", + "videoControlsPlayerName": "Назва відеопрогравача", "mergeKeepEndTime": "Об'єднання рядків: зберігати час кінця (дозволити перекриття з наступними субтитрами)", "mergeKeepEndTimeOnlyAssa": "Об'єднання рядків: зберігати час кінця лише для \"Advanced Sub Station Alpha\"", + "mergeAutoBreak": "Об'єднання рядків: автоматично розбивати об'єднаний текст (якщо вимкнено, зберігаються власні розриви рядків)", "defaultSaveLocation": "Типове розташування збереження", "defaultSaveLocationSourceFileFolder": "Папка файлу субтитрів/відео", "defaultSaveLocationLastUsedFolder": "Остання використана папка", @@ -3023,6 +3388,7 @@ "defaultSaveLocationSubtitleFileFolder": "Папка файлу субтитрів", "defaultSaveLocationCustomFolder": "Власна папка", "subtitleGridFormattingHideTags": "Приховати теги", + "subtitleGridFormattingShowFormattingKeepTags": "Показувати форматування, зберігати невізуальні теги", "musicSymbolsToReplace": "Музичні символи для заміни (розділені комою)", "musicSymbol": "Музичний символ", "colorWordsPerMinute": "Підсвічувати слів/хв, якщо завелико", @@ -3033,7 +3399,6 @@ "dialogStyleDashBothLinesWithSpace": "Тире обох рядків з пробілом", "dialogStyleDashBothLinesWithoutSpace": "Тире обох рядків без пробілу", "continuationStyle": "Стиль продовження", - "continuationStyleNone": "Немає", "continuationStyleNoneTrailingDots": "Немає, крапки для пауз (тільки в кінці)", "continuationStyleNoneLeadingTrailingDots": "Немає, крапки для пауз", "continuationStyleNoneTrailingEllipsis": "Немає, еліпс для пауз (тільки в кінці)", @@ -3044,7 +3409,6 @@ "continuationStyleLeadingTrailingEllipsis": "Еліпс", "continuationStyleLeadingTrailingDash": "Тире", "continuationStyleLeadingTrailingDashDots": "Тире, але крапки для пауз", - "continuationStyleCustom": "Користувацький", "cpsLineLengthStyle": "Cps/довжина рядка", "cpsLineLengthStyleCalcAll": "Порахувати всі символи", "cpsLineLengthStyleCalcNoSpaceCpsOnly": "Порахувати всі, крім пробілу, тільки cps", @@ -3057,26 +3421,10 @@ "cpsLineLengthStyleCalcNoSpaceOrPunctuationCpsOnly": "Без пробілу або пунктуації, тільки cps", "cpsLineLengthStyleCalcIgnoreArabicDiacritics": "Ігнорувати арабські діакритики", "cpsLineLengthStyleCalcIgnoreArabicDiacriticsNoSpace": "Ігнорувати арабські діакритики, без пробілу", - "timeCodeModeHhMmSsMs": "ГГ:ХХ:СС:МС", - "timeCodeModeHhMmSsFf": "ГГ:ХХ:СС:ФФ", - "splitBehaviorPrevious": "Додати прогалину ліворуч від точки розділення (фокус праворуч)", - "splitBehaviorHalf": "Додати прогалину в центрі точки розділення (фокус ліворуч)", - "splitBehaviorNext": "Додати прогалину праворуч від точки розділення (фокус ліворуч)", - "subtitleListActionNothing": "Нічого", - "subtitleListActionVideoGoToPositionAndPause": "Перейти до позиції відео і пауза", - "subtitleListActionVideoGoToPositionAndPlay": "Перейти до позиції відео і відтворити", "subtitleListActionVideoGoToPositionAndPlayCurrentAndPause": "Перейти до позиції відео, відтворити поточний і пауза", - "subtitleListActionEditText": "Перейти до поля редагування тексту", "subtitleListActionVideoGoToPositionMinus1SecAndPause": "Перейти до позиції відео - 1 с і пауза", "subtitleListActionVideoGoToPositionMinusHalfSecAndPause": "Перейти до позиції відео - 0.5 с і пауза", "subtitleListActionVideoGoToPositionMinus1SecAndPlay": "Перейти до позиції відео - 1 с і відтворити", - "subtitleListActionEditTextAndPause": "Перейти до поля редагування тексту і пауза на позиції відео", - "autoBackupEveryMinute": "Кожну хвилину", - "autoBackupEveryXthMinute": "Кожні {0} хвилин", - "profiles": "Профілі", - "autoBackupDeleteAfterXMonths": "Видалити автозбереження після {0} місяців", - "syntaxColoring": "Кольорове підсвічування синтаксису", - "waveformSpectrogram": "Хвильова форма/спектрограма", "network": "Мережа", "fileTypeAssociations": "Асоціації типів файлів", "textBoxColorTags": "Кольорові теги (HTML/ASSA) в полі тексту субтитрів", @@ -3109,10 +3457,9 @@ "darkThemeForegroundColor": "Колір переднього плану темної теми", "darkThemeBackgroundColor": "Колір фону темної теми", "showGridLines": "Показати лінії таблиці", - "resetSettings": "Скинути налаштування?", - "resetSettingsDetail": "Це скине всі налаштування до типових значень.\n\nПродовжити?", "showHorizontalLineAboveToolbar": "Показати горизонтальну лінію над панеллю інструментів", "bookmarkColor": "Колір закладки", + "spellCheckHighlightColor": "Колір підсвічування перевірки орфографії", "singleLineMaxLength": "Максимальна довжина одного рядка", "optimalCharsPerSec": "Оптимальні символи/с", "maxCharsPerSec": "Максимальні символи/с", @@ -3124,10 +3471,18 @@ "unbreakSubtitlesShortThan": "Розбити субтитри коротші за", "newEmptyDefaultMs": "Типова тривалість нового субтитра (мс)", "timeCodeUpDownStepMs": "Крок часу вгору/вниз (мс)", + "moveSelectedLinesStepMs": "Крок ярлика переміщення виділених рядків (ms)", + "moveStartEndStepMs": "Крок ярлика переміщення початку/кінця (ms)", + "moveLinesShortenNeighbor": "Переміщення рядків: скорочувати попередній/наступний рядок замість перекриття", "rememberPositionAndSize": "Запам'ятати позицію і розмір вікна", + "titleBarFullFileName": "Показувати повний шлях до файлу в заголовку вікна", + "showRecentFiles": "Показувати останні файли (для повторного відкриття)", "autoBackupOn": "Автозбереження", "autoBackupIntervalMinutes": "Інтервал автозбереження (хвилини)", "autoBackupDeleteAfterDays": "Тривалість зберігання автозбережень (дні)", + "settingsBackupOn": "Автоматичне резервне копіювання налаштувань", + "settingsBackupIntervalDays": "Інтервал резервного копіювання налаштувань (дні, 0 = кожен запуск)", + "settingsBackupMaxCount": "Кількість резервних копій налаштувань для зберігання", "autoConvertToUtf8": "Автоматично конвертувати в UTF-8 при відкритті", "autoTrimWhiteSpace": "Автоматично обрізати пробіли", "defaultEncoding": "Типове кодування", @@ -3141,23 +3496,14 @@ "colorGapTooShort": "Колір, якщо прогалина занадто коротка", "errorBackgroundColor": "Колір фону помилки", "waveformDrawGridLines": "Малювати лінії сітки", - "waveformCenterVideoPosition": "Центрувати позицію відео", + "waveformUseSkiaRenderer": "Використовувати експериментальний швидкий рендерер", "waveformCenterVideoPositionAlsoWhenPaused": "Центрувати позицію відео також під час паузи", + "waveformSelectCurrentSubtitleWhilePaused": "Виділяти поточні субтитри також під час паузи", + "waveformRememberZoom": "Запам'ятовувати масштаб (горизонтальний і вертикальний)", "waveformShowToolbar": "Показати панель інструментів", "waveformShowToolbarEdit": "Редагувати панель інструментів...", "waveformShowToolbarEditLabel": "Елементи панелі інструментів", "waveformSpectrogramCombinedWaveformHeight": "Хвильова форма/спектрограма об'єднані, висота хвильової форми %", - "showWaveformToolbarPlay": "Панель: показати кнопку відтворення", - "showWaveformToolbarRepeat": "Панель: показати кнопку повтору", - "showWaveformToolbarRemoveBlankLines": "Панель: показати кнопку видалення порожніх рядків", - "showWaveformToolbarNew": "Панель: показати кнопку нового субтитра", - "showWaveformToolbarSetStart": "Панель: показати кнопку встановлення початку", - "showWaveformToolbarSetEnd": "Панель: показати кнопку встановлення кінця", - "showWaveformToolbarStartAndOffsetTheRest": "Панель: показати кнопку встановлення початку і зсуву решти", - "showWaveformToolbarHorizontalZoom": "Панель: показати повзунок горизонтального масштабування", - "showWaveformToolbarVerticalZoom": "Панель: показати повзунок вертикального масштабування", - "showWaveformToolbarVideoPositionSlider": "Панель: показати повзунок позиції відео", - "showWaveformToolbarPlaybackSpeed": "Панель: показати швидкість відтворення", "waveformFocusTextboxAfterInsertNew": "Фокусувати поле тексту після вставки", "waveformInvertMouseWheel": "Інвертувати коліщатко миші", "waveformMouseWheelSetsVideoPosition": "Коліщатко миші встановлює позицію відео", @@ -3175,15 +3521,12 @@ "waveformSelectedColor": "Колір виділеної хвильової форми", "downloadFfmpeg": "Завантажити ffmpeg", "proxyAddress": "Адреса проксі", - "username": "Ім'я користувача", "password": "Пароль", "defaultFormat": "Типовий формат", "defaultSaveAsFormat": "Типовий формат \"Зберегти як\"", "favoriteSubtitleFormats": "Улюблені формати субтитрів", "favoriteLanguages": "Улюблені мови", "webVttUseXTimestampMap": "WebVTT: використовувати X-TIMESTAMP-MAP (зсув кодів часу при завантаженні)", - "showStopButton": "Показати кнопку зупинки", - "showFullscreenButton": "Показати кнопку повноекранного режиму", "autoOpenVideoFile": "Автоматично відкривати відеофайл при відкритті субтитрів", "downloadMpv": "Завантажити mpv", "downloadVlc": "Завантажити VLC", @@ -3222,23 +3565,26 @@ "resetSyntaxColoring": "Скинути кольорове підсвічування синтаксису", "resetWaveform": "Скинути хвильову форму", "resetRules": "Скинути правила", - "useFrameMode": "Використовувати режим кадрів (hh.mm.ss.ff)", + "timeCodeMode": "Режим коду часу", + "timeCodeModeTime": "Час (hh:mm:ss,zzz)", + "timeCodeModeFrames": "Кадри (hh:mm:ss:ff)", + "timeCodeModeFrameNumbers": "Номери кадрів", + "frameModeSnapToVideoFrames": "Зберігати час на кадрах відео (не кадрах у межах кожної секунди)", "textBoxLimitNewLines": "Обмежити кількість рядків в полі тексту субтитрів", - "mpvVideoOutput": "Вивід відео (mpv)", "mpvOpenGl": "libmpv - OpenGL", "mpvSoftwareRendering": "libmpv - Програмне відображення (повільно)", + "mpvMetalRendering": "libmpv - Metal (експериментально, потрібен перезапуск)", "mpvWidRendering": "libmpv - Відображення ID вікна", "waveFormsAndSpectrogramFoldersContainsX": "Теки \"Хвильові форми\" і \"Спектрограми\" містять {0}", "deleteWaveformAndSpectrogramFoldersQuestion": "Видалити файли \"Хвильових форм\" і \"Спектрограм\"?", "waveformGenerateSpectrogram": "Створити спектрограму", "waveformAutoGenerate": "Автоматично створювати хвильову форму при відкритті відео", "waveformSpectrogramMode": "Режим спектрограми", - "waveformCenterOnSingleClick": "Центрувати при одинарному кліку", - "waveformSingleClickSelectsSubtitle": "Вибирати субтитри при одинарному кліку", "waveformRightClickSelectsSubtitle": "Вибирати субтитри при кліку правою кнопкою", - "waveformPauseOnSingleClick": "Пауза при одинарному кліку", "waveformDrawStyle": "Стиль малювання хвильової форми", "vlcWidRendering": "libVLC - Відображення ID вікна", + "ffmpegSoftwareRendering": "FFmpeg", + "downloadFfmpegLibs": "Завантажити бібліотеки FFmpeg (для відеопрогравача FFmpeg)", "subtitleGridEnterKeyAction": "Дія клавіші Enter в таблиці субтитрів", "subtitleSingleClickAction": "Дія одинарного кліку в таблиці субтитрів", "subtitleDoubleClickAction": "Дія подвійного кліку в таблиці субтитрів", @@ -3262,22 +3608,23 @@ "saveAsBehaviorUseVideoFileName": "Використовувати ім'я файлу відео", "saveAsBehaviorUseSubtitleFileName": "Використовувати ім'я файлу субтитрів", "saveAsAppendLanguageCodeTwoLetter": "Двобуквений", - "saveAsAppendLanguageCodeThreeLetter": "Трибуквений", "saveAsAppendLanguageCodeLanguageName": "Назва мови", "splitOddLineActionWeightTop": "Вага зверху", "splitOddLineActionWeightBottom": "Вага знизу", "splitOddLinesAction": "Дія розділення непарних рядків", "ocrUseWordSplitList": "OCR: використовувати список розділення слів", "ocrGuessUnknownWords": "OCR: намагатися вгадати невідомі слова", + "spellCheckRememberUseAlwaysList": "Перевірка орфографії: запам'ятовувати список \"Використовувати завжди\"", + "fixShortDisplayTimesAllowMoveStartTime": "Виправлення загальних помилок: виправлення короткого часу показу може зсувати час початку", "multipleReplaceShowDotDotDotButtons": "Множинна заміна: показати кнопки контекстного меню", "gridFocusTextboxAfterInsertNew": "Таблиця: фокусувати поле тексту після вставки нового субтитра", + "undoRedoGoToChangedLine": "Скасування/повтор: переходити до зміненого рядка (і позиції відео)", "textToSpeechPromptSkipNoiseLines": "Синтез мовлення: пропонувати пропускати рядки зі звуками/музикою", "textToSpeechPromptDetectSpeakers": "Синтез мовлення: пропонувати виявляти імена мовців у тексті", "useFocusedButtonBackgroundColor": "Використовувати колір фону активної кнопки", "focusedButtonBackgroundColor": "Колір фону активної кнопки", "forceCrLfOnSave": "Примусово CR+LF при збереженні (текстові файли субтитрів)", - "showWaveformToolbarPlayNext": "Панель: показати кнопку відтворення наступного", - "showWaveformToolbarPlaySelection": "Панель: показати кнопку відтворення вибору", + "linuxClipboardUseExternalTool": "Копіювати текст через xclip/wl-copy (зберігає символи з діакритикою)", "showFormatLimitWarning": "Попереджати під час збереження, коли рядки перевищують обмеження формату (напр. SCC 32 символи/рядок)", "textBoxButtonShowAutoBreak": "Поле тексту: показати кнопку автоматичного розбиття", "textBoxButtonShowUnbreak": "Поле тексту: показати кнопку розбиття", @@ -3289,6 +3636,8 @@ "waveformDoubleClickAction": "Дія подвійного кліку хвильової форми (після дії одинарного кліку)", "allSettings": "Всі налаштування", "uiScale": "Масштаб інтерфейсу (%)", + "fontScale": "Масштаб шрифту (%)", + "textSelectionOpacity": "Непрозорість виділення тексту (%)", "waveformToolbarItems": "Елементи панелі інструментів хвильової форми", "matchIconColorToDarkTheme": "Збіг кольору іконки з кольором переднього плану темної теми", "searchSettingsDotDotDot": "Пошук налаштувань...", @@ -3300,23 +3649,31 @@ "fixCommonErrorsSkipStep1": "Виправити загальні помилки: пропустити крок 1 (вибір виправлень)", "showPluginsMenu": "Показати меню плагінів", "fullscreenHideControls": "Приховати елементи керування відео в повноекранному режимі", + "showSecondarySubtitleDialog": "Показувати вікно стилю під час відкриття додаткового файлу субтитрів", + "rememberSecondarySubtitleFile": "Запам'ятовувати додатковий файл субтитрів", "allowSingleLetterShortcutsInTextbox": "Дозволити однолітерні ярлики в текстовому полі", + "allowTextNavigationShortcutsInTextbox": "Дозволити ярлики на клавішах навігації по тексту (Ctrl+Left/Right, Home/End) у полі тексту", "spellCheckEnglishTreatInApostropheAsIng": "Перевірка орфографії: трактувати слова, що закінчуються на 'in'', як 'ing' (лише англійська)", "writeToolsLog": "Записувати журнал інструментів", "waveformShotChangeColor": "Колір зміни кадру хвильової форми", + "waveformGridColor": "Колір сітки хвильової форми", "waveformExtractAudioFormat": "Формат видобутого аудіо", "waveformExtractAudioSampleRate": "Частота дискретизації видобутого аудіо", "waveformExtractAudioSampleRateOriginal": "Оригінальна (зберегти джерело)", "waveformExtractAudioBitRate": "Бітрейт видобутого аудіо (MP3/M4A)", + "ffmpegUseCenterChannelOnly": "Використовувати лише передній центральний канал для аудіо 5.1 (FFmpeg)", "subtitleGridCenterSelectedRow": "Таблиця субтитрів, центрувати при виборі попереднього/наступного рядка", "waveformSetVideoPositionOnMoveStartEnd": "Встановлювати позицію відео при переміщенні початку/кінця", - "saveAsAppendLanguageCodeThreeLetterBibliographic": "Трибуквений (ISO 639-2/B)", "speechToTextSelectedLinesPromptFirstTimeOnly": "Розпізнавання мови: виділені рядки, запитувати мову/двигун лише перший раз", "textToSpeechPromptMergeContinuationLines": "Синтез мовлення: запитувати про об'єднання рядків продовження", "subtitlePreviewProperties": "Властивості попереднього перегляду субтитрів", "usePositionFromSubtitleFile": "Використовувати позицію з файлу субтитрів (TTML/PAC/EBU STL)", "marginIsPartOfSubtitleArea": "Поле є частиною області субтитрів", "textJustify": "Вирівнювати рядки по ширині", + "deinterlace": "Деінтерлейсинг відео", + "deinterlaceOff": "Вимк.", + "deinterlaceAuto": "Авто (лише черезрядкові кадри)", + "deinterlaceAlways": "Завжди", "pixelWidthInfo": "Зелені лінії = обмеження макс. ширини | Червона область = текст перевищує обмеження", "spellCheckEngineHunSpelll": "Hunspell", "spellCheckEngineMsWord": "MS Word", @@ -3325,116 +3682,88 @@ "waveformColorTheme": "Колірна тема:", "waveformSaveAsCustomTheme": "Зберегти як користувацьку тему...", "waveformLoadThemeDotDotDot": "Завантажити тему...", - "waveformExportThemeDotDotDot": "Експортувати тему..." + "waveformExportThemeDotDotDot": "Експортувати тему...", + "waveformSaveCustomThemeTitle": "Зберегти власну тему", + "waveformThemeFileType": "Тема хвильової форми Subtitle Edit", + "waveformThemeHighContrast": "Висока контрастність", + "waveformThemeOceanBlue": "Океанська синь", + "waveformThemeWarmSunset": "Теплий захід сонця", + "waveformThemeForest": "Ліс", + "waveformThemeMidnightPurple": "Опівнічний фіолетовий", + "waveformThemeRetroAmber": "Ретро-бурштин", + "waveformThemeIce": "Лід" }, "shortcuts": { "generalUnbreakNoSpaceCjk": "Об'єднати без пробілу (CJK)", - "title": "Ярлики", + "generalToggleBreak": "Перемкнути розрив/прибрання розриву", "searchShortcuts": "Пошук ярликів...", - "filter": "Фільтр", - "categoryGeneral": "Загальне", "categorySubtitleGridAndTextBox": "Таблиця субтитрів і поле тексту", "categorySubtitleGrid": "Таблиця субтитрів", - "categoryWaveform": "Хвильова форма", "categoryTextBox": "Поле тексту", "categoryFile": "Файл", "categoryVideo": "Відео", "categorySync": "Синхронізація", - "categoryTranslate": "Переклад", - "categorySearch": "Пошук", - "categoryTools": "Інструменти", "categoryAi": "ШІ", + "categoryCustom": "Користувацький", + "editCustomShortcut": "Редагувати користувацький ярлик", + "customShortcutTextBoxKeyHint": "Використовуйте тут клавішу з Ctrl/Alt - звичайна клавіша більше не вводитиме текст у поле тексту.", + "customShortcutNumberX": "Користувацький ярлик #{0}", + "customShortcutNumberXY": "Користувацький ярлик #{0}: {1}", + "customShortcutStep": "Крок", + "customShortcutSteps": "Кроки", + "customShortcutStepRunCommand": "Виконати команду", + "customShortcutStepInsertText": "Вставити текст", + "customShortcutStepReplace": "Знайти та замінити", + "customShortcutInsertAtCursor": "У позиції курсора", + "customShortcutInsertAtStart": "На початку тексту", + "customShortcutInsertAtEnd": "У кінці тексту", + "customShortcutSummaryRunCommandX": "Виконати: {0}", + "customShortcutSummaryInsertXY": "Вставити \"{0}\" ({1})", + "customShortcutSummaryReplaceXY": "Замінити \"{0}\" на \"{1}\"", + "customShortcutSummaryReplaceRegexXY": "Замінити регулярний вираз \"{0}\" на \"{1}\"", + "customShortcutPickCommand": "Виберіть команду для виконання.", + "customShortcutEnterText": "Введіть текст для вставлення.", + "customShortcutEnterFind": "Введіть текст для пошуку.", + "customShortcutInvalidRegexX": "Неприпустимий регулярний вираз: {0}", + "customShortcutStepsHint": "Кроки виконуються згори донизу. Текстові кроки працюють із виділеними рядками; команди, які відкривають вікно, продовжуються після його закриття.", "activeIn": "Активний у", "activeInEverywhere": "Всюди", - "generalSetupLikeSe4": "Налаштувати як Subtitle Edit 4 (тема, ярлики, правила заміни)", - "generalMergeSelectedLines": "Об'єднати виділені рядки", - "generalMergeWithPrevious": "Об'єднати з попереднім", - "generalMergeWithNext": "Об'єднати з наступним", - "generalMergeWithPreviousAndUnbreak": "Об'єднати з попереднім і розбити", - "generalMergeWithNextAndUnbreak": "Об'єднати з наступним і розбити", - "generalMergeWithPreviousAndAutoBreak": "Об'єднати з попереднім і автоматично розбити", - "generalMergeWithNextAndAutoBreak": "Об'єднати з наступним і автоматично розбити", - "generalMergeSelectedLinesAndAutoBreak": "Об'єднати виділені рядки і автоматично розбити", "generalMergeSelectedLinesAndUnbreak": "Об'єднати виділені рядки і розбити", "generalMergeSelectedLinesAndUnbreakCjk": "Об'єднати виділені рядки і розбити CJK", - "generalMergeSelectedLinesOnlyFirstText": "Об'єднати виділені рядки тільки перший текст", "generalMergeSelectedLinesBilingual": "Об'єднати виділені рядки двомовно", - "generalMergeWithPreviousBilingual": "Об'єднати з попереднім двомовно", - "generalMergeWithNextBilingual": "Об'єднати з наступним двомовно", - "generalMergeOriginalAndTranslation": "Об'єднати оригінал і переклад", "generalToggleTranslationMode": "Перемкнути режим перекладу", - "generalSwitchOriginalAndTranslation": "Перемкнути оригінал і переклад", - "generalSwitchOriginalAndTranslationTextBoxes": "Перемкнути оригінал і переклад поля тексту", "generalToggleTranslationAndOriginalInPreviews": "Перемкнути переклад і оригінал у перегляді відео/аудіо", "generalChooseLayout": "Вибрати розкладку", - "generalLayoutChooseX": "Розкладка {0}", - "generalPlayFirstSelected": "Відтворити перший виділений", - "generalGoToFirstSelectedLine": "Перейти до першого виділеного рядка", - "generalGoToNextEmptyLine": "Перейти до наступного порожнього рядка", "generalGoToNextSubtitle": "Перейти до наступного субтитра", "generalGoToNextSubtitlePlayTranslate": "Перейти до наступного субтитра (відтворити переклад)", "generalGoToNextSubtitleCursorAtEnd": "Перейти до наступного субтитра (курсор в кінці)", "generalGoToPrevSubtitle": "Перейти до попереднього субтитра", "generalGoToPrevSubtitlePlayTranslate": "Перейти до попереднього субтитра (відтворити переклад)", - "generalGoToStartOfCurrentSubtitle": "Перейти до початку поточного субтитра", - "generalGoToEndOfCurrentSubtitle": "Перейти до кінця поточного субтитра", - "generalGoToPreviousSubtitleAndFocusVideo": "Перейти до попереднього субтитра і фокусувати відео", - "generalGoToNextSubtitleAndFocusVideo": "Перейти до наступного субтитра і фокусувати відео", - "generalGoToPrevSubtitleAndPlay": "Перейти до попереднього субтитра і відтворити", - "generalGoToNextSubtitleAndPlay": "Перейти до наступного субтитра і відтворити", - "generalGoToPreviousSubtitleAndFocusWaveform": "Перейти до попереднього субтитра і фокусувати хвильову форму", - "generalGoToNextSubtitleAndFocusWaveform": "Перейти до наступного субтитра і фокусувати хвильову форму", - "generalGoToLineNumber": "Перейти до номера рядка", "generalGoToFirstLine": "Перейти до першого рядка", "generalGoToLastLine": "Перейти до останнього рядка", "alsoSetVideoPosition": "Також встановлювати позицію відео", "generalGoToVideoPosition": "Перейти до позиції відео", "generalToggleItalic": "Перемкнути курсив", "generalToggleBold": "Перемкнути жирний", - "generalToggleBookmarks": "Перемкнути закладки", - "generalFocusTextBox": "Фокусувати поле тексту", - "generalToggleBookmarksWithText": "Перемкнути закладки з текстом", - "generalEditBookmarks": "Редагувати закладки", - "fileNew": "Новий", "generalToggleUnderline": "Перемкнути підкреслення", + "generalToggleBox": "Перемкнути коробку (EBU STL)", "fileOpen": "Відкрити", "fileOpenKeepVideo": "Відкрити (зберегти відео)", "fileSave": "Зберегти", "fileSaveAs": "Зберегти як", - "fileSaveAll": "Зберегти все", "fileSaveOriginal": "Зберегти оригінал", - "fileSaveOriginalAs": "Зберегти оригінал як", - "fileOpenOriginalSubtitle": "Відкрити оригінал субтитрів", - "fileCloseOriginalSubtitle": "Закрити оригінал субтитрів", - "fileTranslatedSubtitle": "Перекладені субтитри", "fileCompare": "Порівняти", "fileStatistics": "Статистика", "fileImportPlainText": "Імпорт простого тексту", - "fileImportBluRaySupForOcr": "Імпорт Blu-ray SUP для OCR", - "fileImportBluRaySupForEdit": "Імпорт Blu-ray SUP для редагування", - "fileImportTimeCodes": "Імпорт кодів часу", "fileExportEbuStl": "Експорт EBU STL", "fileExportPac": "Експорт PAC", - "fileExportEdlClipName": "Експорт EDL імені кліпу", - "fileExportPlainText": "Експорт простого тексту", - "fileExportCustomTextFormat1": "Експорт користувацького текстового формату 1", - "fileExportCustomTextFormat2": "Експорт користувацького текстового формату 2", - "fileExportCustomTextFormat3": "Експорт користувацького текстового формату 3", "fileExit": "Вийти", "openSeDataFolder": "Відкрити теку Subtitle Edit", - "editFind": "Знайти", - "editFindNext": "Знайти далі", "editFindPrevious": "Знайти попереднє", - "editReplace": "Замінити", - "editMultipleReplace": "Множинна заміна", - "editModifySelection": "Змінити вибір", - "listSelectAll": "Вибрати все", - "listSelectFirst": "Вибрати перший", - "listSelectLast": "Вибрати останній", "listInverseSelection": "Інвертувати вибір", "listDeleteSelection": "Видалити вибір", + "deleteSelectionEverywhere": "Видалити виділення (всюди)", "rippleDeleteSelection": "Видалити вибір з розповсюдженням", - "settings": "Налаштування", "assigned": "Призначено", "unassigned": "Не призначено", "pressedKeyX": "Натиснута клавіша: {0}", @@ -3442,6 +3771,7 @@ "detectKey": "Визначити клавішу", "control": "Контроль", "alt": "Alt", + "altGr": "AltGr", "win": "Win", "shift": "Shift", "controlMac": "⌃", @@ -3452,11 +3782,14 @@ "resetShortcutsDetail": "Бажаєте скинути всі ярлики до типових значень?", "togglePlayPause": "Перемкнути відтворення/паузу", "toggleLockTimeCodes": "Перемкнути блокування кодів часу", + "toggleTimeCodeMode": "Перемкнути режим коду часу (час/кадри/номери кадрів)", + "toggleScreenPrivacy": "Перемкнути приватність екрана (приховати імена файлів, потім і тексти)", + "screenPrivacyFileNamesHidden": "Приватність екрана: імена файлів приховано", + "screenPrivacyFileNamesAndTextsHidden": "Приватність екрана: імена файлів і тексти приховано", + "screenPrivacyOff": "Приватність екрана: вимк.", "duplicateSelectedLines": "Дублювати виділені рядки", "sourceView": "Вихідний вигляд", - "showAlignmentPicker": "Вирівнювання", "addOrEditBookmark": "Додати або редагувати закладку", - "listBookmarks": "Список закладок", "toggleBookmark": "Перемкнути закладку (виділені рядки, без тексту)", "toggleForcedSelectedLines": "Перемкнути примусово (виділені рядки)", "goToNextBookmark": "Перейти до наступної закладки", @@ -3467,12 +3800,12 @@ "waveformSetStartAndSetEndOfPreviousMinusGap": "Встановити початок і встановити кінець попереднього мінус прогалина", "waveformSetEndAndStartOfNextAfterGap": "Встановити кінець і початок наступного плюс прогалина", "waveformSetEndAndStartOfNextAfterGapAndGoToNext": "Встановити кінець і початок наступного плюс прогалина і перейти до наступного", - "fetchFirstWordFromNextSubtitle": "Отримати перше слово з наступних субтитрів", "moveLastWordToNextSubtitle": "Перемістити останнє слово до наступного субтитра", "moveLastWordFromFirstLineDownCurrentSubtitle": "Перемістити останнє слово з першого рядка вниз (поточний субтитр)", "moveFirstWordFromNextLineUpCurrentSubtitle": "Перемістити перше слово з наступного рядка вгору (поточний субтитр)", "moveTextFromCursorToNextAndGoToNext": "Перемістити текст після курсора до наступних субтитрів і перейти до наступного", "moveTextFromCursorToNextAndGoToNextAndPlay": "Перемістити текст після курсора до наступних субтитрів, перейти до наступного і відтворити", + "moveTextFromCursorToNextAndGoToNextAndPlayAndPause": "Перемістити текст після позиції курсора до наступних субтитрів, перейти до наступних, відтворити їх і призупинити в кінці", "breakAtFirstSpaceFromCursor": "Розбити на першому пробілі від позиції курсора", "breakAtFirstSpaceFromCursorAndGoToNext": "Розбити на першому пробілі від позиції курсора і перейти до наступного", "toggleFocusGridAndWaveform": "Перемкнути фокус між таблицею субтитрів і хвильовою формою/спектрограмою", @@ -3484,7 +3817,6 @@ "videoGoToPreviousTimeCode": "Перейти до попереднього коду часу", "videoGoToNextTimeCode": "Перейти до наступного коду часу", "goToNextLineAndSetVideoPosition": "Перейти до наступного субтитра і встановити позицію відео", - "textBoxDeleteSelectionNoClipboard": "Поле тексту: Видалити вибір (без буфера обміну)", "textBoxDeleteForward": "Текстове поле: видалити виділене або наступний символ (видалення вперед)", "textBoxCut": "Поле тексту: Вирізати", "textBoxCut2": "Поле тексту: Вирізати (альтернатива)", @@ -3503,33 +3835,27 @@ "listViewColumnTextUp": "Стовпець, текст вгору", "listViewColumnTextDown": "Стовпець, текст вниз", "autoTranslateSelectedLines": "Автоматичний переклад виділених рядків...", + "autoTranslateSelectedLinesNoPrompt": "Автоматичний переклад виділених рядків (без запиту, останній двигун/мови)", "setAssaResolution": "Встановити роздільну здатність ASSA (PlayResX/PlayResY)", "setShortcutForX": "Встановити ярлик для \"{0}\"", "commandFileNewKeepVideo": "Новий (зберегти відео)", "fileOpenOriginal": "Відкрити оригінал", "fileCloseOriginal": "Закрити оригінал", "restoreAutoBackup": "Відновити автозбереження", - "openContainingFolder": "Відкрити теку, що містить", "importTimeCodes": "Імпорт кодів часу", "importSubtitleWithManuallyChosenEncoding": "Імпорт субтитрів з вручну обраним кодуванням", "exportBluRaySup": "Експорт Blu-ray SUP", "exportCustomTextFormat": "Експорт користувацького текстового формату", "exportPlainText": "Експорт простого тексту", - "showHistory": "Показати історію", "toggleRightToLeft": "Перемкнути з правого на ліве", "modifySelection": "Змінити вибір", "adjustDurations": "Налаштувати тривалість", "applyDurationLimits": "Застосувати обмеження тривалості", "batchConvert": "Пакетне перетворення", - "bridgeGaps": "Замкнути прогалини", "applyMinGap": "Застосувати мінімальну прогалину", - "fixCommonErrors": "Виправити загальні помилки", "makeEmptyTranslationFromCurrentSubtitle": "Створити порожній переклад з поточних субтитрів", - "mergeLinesWithSameText": "Об'єднати рядки з однаковим текстом", - "mergeLinesWithSameTimeCodes": "Об'єднати рядки з однаковими кодами часу", "splitBreakLongLines": "Розділити/перебалансувати довгі рядки", "mergeShortLines": "Об'єднати короткі рядки", - "removeTextForHearingImpaired": "Видалити текст для людей з втратою слуху", "joinSubtitles": "Об'єднати субтитри", "splitSubtitle": "Розділити субтитри", "spellCheck": "Перевірка орфографії", @@ -3551,7 +3877,6 @@ "adjustAllTimes": "Налаштувати всі часи", "visualSync": "Візуальна синхронізація", "translateViaCopyPaste": "Переклад через копіювання/вставку", - "shortcuts": "Ярлики", "wordLists": "Списки слів", "chooseUiLanguage": "Вибрати мову інтерфейсу", "chooseRuleProfile": "Вибрати профіль правил", @@ -3562,6 +3887,7 @@ "textBoxItalic": "Поле тексту курсив", "textBoxBold": "Текстове поле: жирний", "textBoxUnderline": "Текстове поле: підкреслений", + "textBoxBox": "Коробка поля тексту (EBU STL)", "resetWaveformZoomAndSpeed": "Скинути масштабування хвильової форми і швидкість відтворення (швидкість)", "togglePlaybackSpeed": "Перемкнути швидкість відтворення (швидкість)", "playbackSpeedSlower": "Швидкість відтворення повільніше (швидкість)", @@ -3571,22 +3897,28 @@ "seekSilence": "Шукати тишу", "waveformGuessStart": "Вгадати час початку з хвильової форми", "waveformGuessEnd": "Вгадати час кінця з хвильової форми", + "waveformGuessStartAndEnd": "Вгадати час початку та кінця за хвильовою формою", "setVideoPositionCurrentSubtitleStart": "Встановити позицію відео на початок поточного рядка", "goToSubtitlePositionAndPause": "Перейти до позиції субтитрів і призупинити", "setVideoPositionCurrentSubtitleEnd": "Встановити позицію відео на кінець поточного рядка", "toggleAudioTracks": "Перемкнути аудіодоріжки", - "listErrors": "Список помилок", "goToNextError": "Перейти до наступної помилки", "goToPreviousError": "Перейти до попередньої помилки", "addNameToNameList": "Додати ім'я до списку імен", "findDoubleWords": "Знайти подвійні слова", "findDoubleLines": "Знайти подвійні рядки", "colorX": "Колір {0}", - "removeColor": "Видалити колір", "surroundWith": "Обгорнути...", "surroundWithXY": "Обгорнути {0}/{1}", - "repeatLine": "Повторити рядок", "surroundWithNumberX": "Оточити #{0}", + "surroundWithBehavior": "Поведінка", + "surroundWithBehaviorToggle": "Перемикати (додати або видалити, якщо є)", + "surroundWithBehaviorAdd": "Додавати (щоразу)", + "surroundWithBehaviorRemove": "Видаляти (усі)", + "surroundWithBehaviorRemoveOnce": "Видаляти (по одному щоразу)", + "surroundWithWorksOn": "Діє на", + "surroundWithScopeSelectionOrText": "Виділення, інакше весь текст", + "surroundWithScopeEachLine": "Кожен рядок", "searchVia": "Шукати через", "searchViaX": "Шукати через {0}", "searchViaNumberX": "Шукати через #{0}", @@ -3611,6 +3943,11 @@ "copySubtitlePathToClipboard": "Копіювати шлях субтитрів в буфер обміну", "copySubtitleOriginalPathToClipboard": "Копіювати шлях оригіналу субтитрів в буфер обміну", "focusTextBox": "Фокусувати поле тексту", + "focusSubtitleListView": "Фокус на таблиці субтитрів", + "focusWaveform": "Фокус на хвильовій формі", + "focusOriginalTextBox": "Фокус на полі оригінального тексту", + "textBoxGoToStart": "Поле тексту, перейти на початок", + "textBoxGoToEnd": "Поле тексту, перейти в кінець", "sortByStartTime": "Сортувати за часом \"Показати\"", "sortByEndTime": "Сортувати за часом \"Приховати\"", "sortByDuration": "Сортувати за тривалістю", @@ -3633,7 +3970,6 @@ "assaStyles": "Стилі ASSA", "assaProperties": "Властивості ASSA", "assaAttachments": "Вкладення ASSA", - "assaVideoColorPicker": "Вибір кольору відео ASSA", "recalculateDurationSelectedLines": "Перерахувати тривалість (виділені рядки)", "toggleWaveformAndSpectrogramHeight": "Перемкнути розділену висоту хвильової форми/спектрограми", "toggleSpectrogramStyle": "Перемкнути стиль спектрограми", @@ -3658,9 +3994,9 @@ "selectionToSentenceCase": "Текстове поле: виділене в Регістр речень", "selectionToggleCasing": "Текстове поле: перемкнути регістр (ВЕРХНІЙ -> нижній -> Регістр Заголовка)", "googleIt": "Шукати в Google (вибраний текст)", - "setActorXY": "Встановити актора {0}: {1}", + "setActorX": "Встановити актора: {0}", "setNewActor": "Встановити нового актора...", - "setActorSlotXTitle": "Слот актора {0}", + "setStyleX": "Встановити стиль: {0}", "toggleDialogDashes": "Перемкнути дефіси діалогу", "mergeWithLineBeforeAsDialog": "Об'єднати з попереднім рядком як діалог", "mergeWithLineAfterAsDialog": "Об'єднати з наступним рядком як діалог", @@ -3671,13 +4007,27 @@ "moveStartOneFrameBackKeepGapPrev": "Перемістити початок на один кадр назад (зберегти прогалину до попереднього, якщо близько)", "moveStartOneFrameForwardKeepGapPrev": "Перемістити початок на один кадр вперед (зберегти прогалину до попереднього, якщо близько)", "moveEndOneFrameBackKeepGapNext": "Перемістити кінець на один кадр назад (зберегти прогалину до наступного, якщо близько)", - "moveEndOneFrameForwardKeepGapNext": "Перемістити кінець на один кадр вперед (зберегти прогалину до наступного, якщо близько)" + "moveEndOneFrameForwardKeepGapNext": "Перемістити кінець на один кадр вперед (зберегти прогалину до наступного, якщо близько)", + "moveStartXMsBack": "Зсунути початок на X ms назад (X задається в Налаштуваннях, також у Візуальній синхронізації)", + "moveStartXMsForward": "Зсунути початок на X ms вперед (X задається в Налаштуваннях, також у Візуальній синхронізації)", + "moveEndXMsBack": "Зсунути кінець на X ms назад (X задається в Налаштуваннях, також у Візуальній синхронізації)", + "moveEndXMsForward": "Зсунути кінець на X ms вперед (X задається в Налаштуваннях, також у Візуальній синхронізації)", + "moveSelectedLinesXMsBack": "Перемістити виділені рядки на X ms назад (X задається в Налаштуваннях)", + "moveSelectedLinesXMsForward": "Перемістити виділені рядки на X ms вперед (X задається в Налаштуваннях)", + "moveSelectedLinesAndForwardXMsBack": "Перемістити виділені рядки та всі наступні на X ms назад (X задається в Налаштуваннях)", + "moveSelectedLinesAndForwardXMsForward": "Перемістити виділені рядки та всі наступні на X ms вперед (X задається в Налаштуваннях)", + "moveAllLinesXMsBack": "Перемістити всі рядки на X ms назад (X задається в Налаштуваннях)", + "moveAllLinesXMsForward": "Перемістити всі рядки на X ms вперед (X задається в Налаштуваннях)", + "moveSelectedLinesCustomMsBack": "Перемістити виділені рядки, користувацькі мілісекунди ({0:#,###,##0}) назад, {1}", + "moveSelectedLinesCustomMsForward": "Перемістити виділені рядки, користувацькі мілісекунди ({0:#,###,##0}) вперед, {1}", + "moveSelectedLinesAndForwardCustomMsBack": "Перемістити виділені рядки та всі наступні, користувацькі мілісекунди ({0:#,###,##0}) назад, {1}", + "moveSelectedLinesAndForwardCustomMsForward": "Перемістити виділені рядки та всі наступні, користувацькі мілісекунди ({0:#,###,##0}) вперед, {1}", + "moveAllLinesCustomMsBack": "Перемістити всі рядки, користувацькі мілісекунди ({0:#,###,##0}) назад, {1}", + "moveAllLinesCustomMsForward": "Перемістити всі рядки, користувацькі мілісекунди ({0:#,###,##0}) вперед, {1}" }, "wordLists": { "title": "Списки слів", "addName": "Додати ім'я", - "addWord": "Додати слово", - "addPair": "Додати пару", "nameAndIgnoreList": "Список імен/ігнорування", "userWords": "Список користувацьких слів", "ocrFixList": "Список виправлень OCR", @@ -3730,6 +4080,8 @@ "nOcrDatabase": "База даних nOCR", "drawMode": "Режим малювання:", "addNewCharcter": "Додати новий символ", + "addNOcrCharacterTitle": "Додати символ nOCR для рядка {0}, символ {1} з {2}, база даних \"{3}\"", + "addBinaryOcrCharacterTitle": "Додати символ бінарного OCR для рядка {0}, символ {1} з {2}, база даних \"{3}\"", "lineIndexX": "Рядок {0}", "inspectNOcrAdditions": "Перевірити нові додавання nOCR", "ocrSelectedLines": "OCR виділених рядків", @@ -3778,6 +4130,8 @@ "fillSelectedLinesWithClipboard": "Заповнити виділені рядки текстом з буфера обміну", "saveBlankTextTitle": "Зберегти порожній текст?", "saveBlankTextPrompt": "Зберегти порожній текст для зображення?", + "drawnLinesDoNotMatchTitle": "Лінії не відповідають зображенню", + "drawnLinesDoNotMatchPrompt": "Деякі намальовані лінії не відповідають зображенню (зелені лінії мають бути на символі, червоні - поза ним), тому цей символ не буде розпізнано.\n\nВсе одно зберегти?", "yesAndNeverAskAgain": "Так і більше не запитувати", "importTextFromSubtitleDotDotDot": "Імпортувати текст із субтитрів...", "importTextFromSubtitleOverwritePrompt": "Деякі рядки вже мають текст OCR. Перезаписати існуючий текст?", @@ -3803,7 +4157,6 @@ "vobSubColorsTitle": "Кольори VobSub/DVD", "vobSubColorsHeader": "Налаштувати чотири кольори VobSub", "vobSubColorsDescription": "Субтитри VobSub/DVD використовують чотири індексовані кольори: фон, візерунок, акцент 1 і акцент 2. Виберіть кожен колір нижче, щоб перевизначити вбудовану палітру - корисно, коли оригінальні кольори дають поганий контраст для OCR.", - "vobSubColorBackground": "Фон", "vobSubColorPattern": "Візерунок", "vobSubColorEmphasis1": "Акцент 1", "vobSubColorEmphasis2": "Акцент 2", @@ -3837,9 +4190,26 @@ "crispEmbedSettingsTitle": "Налаштування CrispEmbed", "crispEmbedDescription": "Локальний двигун OCR з кількома бекендами моделей - завантажте двигун і моделі тут.", "trainNOcrDatabase": "Навчити базу даних nOCR...", + "optimizeNOcrDatabase": "Оптимізувати базу даних nOCR...", + "optimizeNOcrDatabaseQuestionXY": "Видалити з бази даних nOCR \"{0}\" лінії, які не допомагають відрізнити символи від схожих?\n\nЦе зменшує базу даних і пришвидшує OCR, зазвичай із меншою кількістю помилок. Триває до хвилини.\n\nРезервну копію буде збережено як \"{1}\".", + "optimizingNOcrDatabaseXY": "Оптимізація бази даних nOCR... {0}/{1}", + "nOcrDatabaseOptimizedXYZ": "Базу даних nOCR \"{0}\" оптимізовано: {1:#,##0} -> {2:#,##0} ліній.", "startTraining": "Почати навчання", "abortTraining": "Перервати навчання", "trainingOptions": "Параметри навчання", + "trainNOcrSubtitle": "Рендерить символи встановленими шрифтами та навчає nOCR їхніх форм. Виберіть шрифти, які використовують ваші субтитри.", + "xFontsSelected": "Вибрано: {0:#,##0}", + "subtitleFonts": "Шрифти субтитрів", + "emptyDatabase": "Порожня база даних", + "startFromDatabase": "Почати з", + "startFromDatabaseHint": "Додати навчені символи до копії наявної бази даних (рекомендовано) або почати з порожньої", + "fontSizes": "Розміри шрифту", + "fontSizesHint": "Розміри шрифту в пікселях для рендерингу, через кому, наприклад \"30, 40\"", + "numberOfLineSegmentsHint": "Ліній, що перевіряються на символ - більше означає суворіше, але повільніше", + "enterDatabaseName": "Введіть назву бази даних", + "trainingFontXSizeYCharacterZ": "Навчання \"{0}\" при {1} px: {2}", + "xLearned": "Вивчено: {0:#,##0}", + "xSkipped": "Пропущено: {0:#,##0}", "charactersToTrain": "Символи для навчання", "letterCombinationsToTrain": "Комбінації літер, які можуть бути розділені на одне зображення", "importCharactersFromSubtitleFile": "Імпортувати символи з файлу субтитрів...", @@ -3866,6 +4236,8 @@ "drawZoomOut": "Зменшити (Ctrl+-)", "drawResetView": "Скинути вигляд (Ctrl+0)", "drawToggleGrid": "Перемкнути сітку (Ctrl+G)", + "drawTogglePreview": "Перемкнути попередній перегляд (F9)", + "drawPreviewFailed": "Помилка попереднього перегляду - потрібен ffmpeg із libass (фільтр subtitles)", "drawCopyToClipboard": "Копіювати в буфер обміну (Ctrl+C)", "drawShapes": "Форми", "drawSelectedPoint": "Вибрана точка", @@ -3873,16 +4245,86 @@ "drawSelectedLayer": "Вибраний шар", "drawToolX": "Інструмент: {0}", "drawHelpText": "Клацніть, щоб додати точки • Enter/F8 щоб закрити форму • Shift+Перетягування для панорамування • Ctrl+Прокрутка для масштабування", + "drawMoveToLayer": "Перемістити на шар", + "drawFlipHorizontal": "Віддзеркалити по горизонталі", + "drawFlipVertical": "Віддзеркалити по вертикалі", + "drawHideShape": "Приховати фігуру", + "drawShowShape": "Показати фігуру", + "drawHideLayer": "Приховати шар", + "drawShowLayer": "Показати шар", + "drawDeleteLayer": "Видалити шар", + "drawDeletePoint": "Видалити точку", + "drawCancelDrawing": "Скасувати малювання", + "drawLayerX": "Шар {0}", + "drawImportSvg": "Імпортувати зображення SVG...", + "drawRotateClockwise": "Повернути на 90° за годинниковою стрілкою", + "drawConvertToCurve": "Перетворити сегмент на криву", + "drawBackground": "Фон", + "drawPosition": "Позиція", + "drawZoom": "Масштаб", + "drawShapeTool": "Інструмент фігур", + "drawColorPickerTool": "Взяти колір із фігури або фону", + "drawShapeLibrary": "Бібліотека фігур", + "drawShapeLibraryHint": "Перетягніть на полотні, щоб розмістити фігуру, або клацніть для типового розміру. Shift зберігає пропорції.", + "drawAddToLibrary": "Додати до бібліотеки фігур...", + "drawRemoveFromLibrary": "Видалити з бібліотеки фігур", + "drawShapeName": "Назва фігури", + "drawCategorySpeechBubbles": "Бульбашки мовлення", + "drawCategoryArrows": "Стрілки", + "drawCategoryBasicShapes": "Основні фігури", + "drawCategorySymbols": "Символи", + "drawCategoryMyShapes": "Мої фігури", + "drawShapeSpeechBubble": "Бульбашка мовлення", + "drawShapeSpeechBubbleRight": "Бульбашка мовлення, хвіст праворуч", + "drawShapeSpeechBubbleUp": "Бульбашка мовлення, хвіст угору", + "drawShapeOvalBubble": "Овальна бульбашка мовлення", + "drawShapeBoxBubble": "Прямокутна бульбашка мовлення", + "drawShapeThoughtBubble": "Бульбашка думок", + "drawShapeShoutBubble": "Бульбашка крику", + "drawShapeCaptionBox": "Рамка підпису", + "drawShapeArrow": "Стрілка", + "drawShapeDoubleArrow": "Подвійна стрілка", + "drawShapeChevron": "Шеврон", + "drawShapeCurvedArrow": "Вигнута стрілка", + "drawShapeRoundedRectangle": "Заокруглений прямокутник", + "drawShapeTriangle": "Трикутник", + "drawShapeDiamond": "Ромб", + "drawShapePentagon": "П'ятикутник", + "drawShapeHexagon": "Шестикутник", + "drawShapeOctagon": "Восьмикутник", + "drawShapeStar": "Зірка", + "drawShapeRing": "Кільце", + "drawShapePlus": "Плюс", + "drawShapeBanner": "Банер", + "drawShapeHeart": "Серце", + "drawShapeCheck": "Галочка", + "drawShapeMusicNote": "Нота", + "drawShapeMusicNotes": "Ноти", + "drawShapeLightning": "Блискавка", + "drawShapeCloud": "Хмара", + "drawShapeMoon": "Місяць", + "drawShapeBadge": "Значок", + "drawSize": "Розмір", + "drawLayer": "Шар", + "drawBackgroundVideoFrame": "Кадр відео (поточна позиція)", + "drawBackgroundVideoFrameAt": "Кадр відео в...", + "drawBackgroundImage": "Файл зображення...", + "drawBackgroundNone": "Без фону", + "drawBackgroundStretch": "Розтягнути зображення на полотно", + "drawBackgroundOpacity": "Непрозорість фону", + "drawBackgroundVideoFrameFailed": "Не вдалося отримати кадр відео в {0}.", + "drawImages": "Зображення", + "drawConvertToLine": "Перетворити сегмент на лінію", + "drawConvertShapeToCurves": "Перетворити всі лінії на криві", + "drawConvertShapeToLines": "Перетворити всі криві на лінії", + "drawRotateCounterClockwise": "Повернути на 90° проти годинникової стрілки", + "drawSvgImages": "Зображення SVG", + "drawSvgNoShapes": "У файлі SVG не знайдено заповнених фігур або контурів.", "progressBarTitle": "Індикатор прогресу ASSA", "progressBarSettings": "Індикатор прогресу", - "progressBarPosition": "Позиція", - "progressBarBottom": "Знизу", - "progressBarTop": "Зверху", "mouseOverColor": "Колір при наведенні", "clickedColor": "Колір при натисканні", "progressBarForeColor": "Колір переднього плану", - "progressBarBackColor": "Колір фону", - "progressBarStyle": "Стиль", "progressBarSquareCorners": "Квадратні кути", "progressBarRoundedCorners": "Закруглені кути", "progressBarChapters": "Глави", @@ -3891,8 +4333,6 @@ "progressBarXAdjustment": "Коригування X", "progressBarYAdjustment": "Коригування Y", "progressBarTextAlignment": "Вирівнювання тексту", - "progressBarTakePosFromVideo": "Взяти позицію з відео", - "progressBarPreview": "Попередній перегляд", "fontCollectorTitle": "Збирач шрифтів", "fontCollectorUsedIn": "Використано в", "fontCollectorFontFiles": "Файл(и) шрифтів", @@ -3939,19 +4379,14 @@ "resolutionResamplerVideoDiffers": "Роздільна здатність субтитрів ({0}x{1}) відрізняється від роздільної здатності відео ({2}x{3}).\nЗмінити субтитри відповідно до відео?", "resolutionResamplerAskOnVideoOpen": "Питати при відкритті відео з іншою роздільною здатністю", "backgroundBoxGenerator": "Створити фонову коробку", - "backgroundBoxPadding": "Внутрішній відступ", "backgroundBoxFillWidth": "Ширина заповнення", - "backgroundBoxBoxColor": "Колір коробки", "backgroundBoxRadius": "Радіус", "backgroundBoxCircle": "Коло", "backgroundBoxSpikes": "Шипи", "backgroundBoxBubbles": "Бульбашки", - "stylesTitle": "Розширені стилі Sub Station Alpha", "stylesInFile": "Стилі в файлі", "stylesSaved": "Стилі збережено", - "category": "Категорія", "allCategories": "[Всі]", - "defaultCategory": "Типова", "newCategory": "Нова категорія...", "renameCategory": "Перейменувати категорію...", "deleteCategory": "Видалити категорію", @@ -3971,13 +4406,26 @@ "graphics": "Графіка", "copyToStorageStyles": "Копіювати до стилів сховища", "copyToFileStyles": "Копіювати до файлових стилів", + "styleXAlreadyExistsInStorage": "Стиль із назвою \"{0}\" вже існує в сховищі.\n\nПерезаписати його чи зберегти обидва (копія отримає нову назву)?", + "styleXAlreadyExistsInFile": "Стиль із назвою \"{0}\" вже існує у файлі.\n\nПерезаписати його чи зберегти обидва (копія отримає нову назву)?", + "styleNameCannotBeEmpty": "Назва стилю не може бути порожньою.", + "styleNameXIsUsedMoreThanOnce": "Назва стилю \"{0}\" використовується кількома стилями у файлі.", + "overwrite": "Перезаписати", + "keepBoth": "Зберегти обидва", + "doThisForAllConflictingStyles": "Зробити це для всіх конфліктних стилів", "setStyleAsDefault": "Встановити стиль як типовий", "takeUsagesFromDotDotDot": "Взяти використання з...", "replaceStyleWithDotDotDot": "Замінити стиль на...", "noAttachmentsFound": "В обраному файлі ASSA не знайдено вкладень.", "deleteStyleQuestion": "Видалити стиль?", "deleteStylesQuestion": "Видалити стилі?", + "deleteXStylesQuestion": "Видалити стилі ({0})?", + "deleteXStylesFromStorageQuestion": "Видалити стилі ({0}) зі сховища?", + "deleteStyleXFromStorageQuestion": "Видалити стиль \"{0}\" зі сховища?", + "deleteStyleXFromFileQuestion": "Видалити стиль \"{0}\" з поточного файлу?", "openStyleImportFile": "Відкрити файл субтитрів для імпорту стилів", + "styleImportFiles": "Файли стилів (.ass, .sty, категорії Subtitle Edit 4 .template)", + "ssaStyleImportFiles": "Файли стилів (.ssa, .ass, .sty)", "primary": "Основний", "secondary": "Додатковий", "applyOverrideTags": "Застосувати теги перевизначення", @@ -3992,7 +4440,6 @@ "spaceIncrease": "Збільшення простору", "popIn": "Поява", "spinIn": "Обертання", - "playCurrent": "Відтворити поточний", "setPosition": "Встановити позицію", "rotation": "Обертання", "scaleX": "Масштаб X", @@ -4036,10 +4483,8 @@ "advancedEffectOldMovieDescription": "Симулює старий 35мм проектор з шумом, відтінком і царапинами", "advancedEffectNeonBurst": "Неоновий вибух", "advancedEffectNeonBurstDescription": "Висока енергія 'Shorts' стилі з неоновим світлом і анімацією виходу", - "advancedEffectSettings": "Налаштування", "advancedEffectSnowFlakeCount": "Кількість сніжинок", "advancedEffectStarfieldStarCount": "Кількість зір", - "advancedEffectStarfieldSpeed": "Швидкість", "advancedEffectGlitch": "Гліч", "advancedEffectGlitchDescription": "Цифровий гліч з хроматичною аберацією, білими вспливи та накладанням спотворень", "advancedEffectBounceIn": "Відскок", @@ -4048,8 +4493,6 @@ "advancedEffectFirefliesDescription": "Теплі жовто-амберні крапки, що плавають органічно по екрану з пульсаючим світлом", "advancedEffectMatrix": "Матриця", "advancedEffectMatrixDescription": "Падіння зелених стовпців символів з текстом субтитрів, що будується літера за літерою з дощу", - "advancedEffectAudioBars": "Аудіо смуги (хвильова форма)", - "advancedEffectAudioBarsDescription": "Анімовані смуги еквалайзера, що керуються аудіо хвильовою формою — смуги прокручуються вліво як хвильова форма і пульсують з аудіо", "advancedEffectAudioPulse": "Пульсація тексту", "advancedEffectAudioPulseDescription": "Текст субтитрів світиться і пульсує з аудіо об'ємом — тихі секції показують нормальний текст, гучні піки додають кольорове облямування і легке збільшення", "advancedEffectConfetti": "Конфеті", @@ -4063,7 +4506,6 @@ "advancedEffectFancyKaraokeGlow": "Виділіть активне слово", "advancedEffectFancyKaraokeActiveColor": "Активний колір", "advancedEffectFancyKaraokeInactiveColor": "Неактивний колір", - "advancedEffectFancyKaraokeInactiveOpacity": "Непрозорість неактивного (0-255)", "advancedEffectWordSpacingPixels": "Пропуск (пікселі)", "advancedEffectSlowZoomOut": "Повільне зменшення", "advancedEffectSlowZoomOutDescription": "Текст починається трохи більшим і повільно зменшується до нормального розміру протягом тривалості субтитрів", @@ -4097,7 +4539,6 @@ "advancedEffectSlideInLeftDescription": "Текст виїжджає з-за лівого краю екрана, затримується, потім виходить назад ліворуч", "advancedEffectSlideInRight": "Виїзд справа", "advancedEffectSlideInRightDescription": "Текст виїжджає з-за правого краю екрана, затримується, потім виходить назад праворуч", - "advancedEffectFadeInOut": "Поява/зникнення", "advancedEffectFadeInOutDescription": "Текст з'являється на початку і зникає в кінці кожного субтитру" }, "about": { @@ -4118,7 +4559,6 @@ "noPluginsInstalled": "Плагіни не встановлено", "getPluginsOnline": "Отримати плагіни онлайн...", "getPluginsTitle": "Отримати плагіни", - "enabled": "Увімкнено", "disabled": "Вимкнено", "notSupportedOnThisOs": "Не підтримується в цій операційній системі", "removePluginXConfirm": "Видалити плагін '{0}'? Це видалить теку плагіна:{1}", @@ -4135,17 +4575,19 @@ "downloadingXPercent": "Завантаження {0}%...", "pluginXInstalled": "'{0}' встановлено.", "couldNotInstallPluginX": "Не вдалося встановити '{0}': {1}", - "notInstalled": "Не встановлено", "install": "Встановити", "reinstall": "Перевстановити", - "update": "Оновити", "installedX": "Встановлено ({0})", "updateAvailableXToY": "Доступне оновлення ({0} → {1})", "applyPluginToWhichLinesX": "Застосувати '{0}' до:", "applyToSelectedLinesX": "Виділені рядки ({0})", "applyToAllLinesX": "Усі рядки ({0})", "updateAllXAvailable": "Оновити все ({0})", - "updatingXOfY": "Оновлення {0}/{1}..." + "updatingXOfY": "Оновлення {0}/{1}...", + "doNotAskAgain": "Більше не запитувати (змінюється в Керуванні плагінами)", + "applyPluginsTo": "Застосовувати плагіни до", + "askEachTime": "Запитувати щоразу", + "allLines": "Усі рядки" }, "errorList": { "title": "Список помилок", diff --git a/src/ui/Assets/Languages/Vietnamese.json b/src/ui/Assets/Languages/Vietnamese.json index ace98b231b3..b1239cdec47 100644 --- a/src/ui/Assets/Languages/Vietnamese.json +++ b/src/ui/Assets/Languages/Vietnamese.json @@ -1,12 +1,30 @@ { "title": "Subtitle Edit", - "version": "v5.2.0-rc7", + "version": "v5.3.0", "translatedBy": "Claude (Anthropic)", "cultureName": "vi-VN", "general": { "abort": "Hủy bỏ", "actor": "Diễn viên", "actors": "Diễn viên", + "setActorDotDotDot": "Đặt diễn viên...", + "actorPickerTitle": "Đặt diễn viên", + "actorPickerFilterHint": "Gõ để lọc, hoặc gõ tên diễn viên mới", + "actorPickerKeysHint": "Nhấn một số hoặc Enter để đặt diễn viên. Alt+Lên/Xuống thay đổi thứ tự, Delete xóa diễn viên.", + "actorPickerNewActorX": "Diễn viên mới \"{0}\"", + "actorPickerLinesSelectedX": "Số dòng đã chọn: {0}", + "setStyleDotDotDot": "Đặt kiểu...", + "stylePickerTitle": "Đặt kiểu", + "stylePickerFilterHint": "Gõ để lọc, hoặc gõ tên kiểu mới", + "stylePickerKeysHint": "Nhấn một số (1-9, 0) để đặt kiểu cho tất cả các dòng đã chọn.", + "stylePickerNewStyleX": "Kiểu mới \"{0}\"", + "stylePickerNewStyleInfo": "Nhấn Enter để thêm kiểu này với cài đặt mặc định và đặt nó cho các dòng đã chọn. Dùng \"Quản lý kiểu...\" để thay đổi phông chữ, màu sắc và vị trí sau.", + "stylePickerCurrentStyleX": "Kiểu hiện tại: {0}", + "stylePickerLinesUsingStyleX": "Số dòng dùng kiểu này: {0}", + "stylePickerMarginsXYZ": "Trái {0}, phải {1}, dọc {2}", + "stylePickerSetStyle": "Đặt _kiểu", + "stylePickerManageStyles": "Quản lý kiểu...", + "stylePickerManageStylesHint": "Đóng cửa sổ này và mở trình quản lý kiểu để chỉnh sửa phông chữ, màu sắc, vị trí và hơn thế nữa", "add": "Thêm", "addDotDotDot": "Thêm...", "addToNamesListCaseSensitive": "Thêm vào danh sách tên (phân biệt chữ hoa/thường)", @@ -15,7 +33,6 @@ "adjustAlpha": "Điều chỉnh alpha (độ trong suốt)", "adjustDisplayDuration": "Điều chỉnh thời lượng hiển thị", "adjustment": "Điều chỉnh", - "advanced": "Nâng cao", "advancedSettings": "Cài đặt nâng cao", "after": "Sau", "alignment": "Căn chỉnh", @@ -60,7 +77,6 @@ "bdnXml": "BDN/xml", "bdnXml8Bit": "BDN/xml 8-bit", "before": "Trước", - "beforeX": "Trước \"{0}\"", "beginning": "Bắt đầu", "bluRaySup": "Blu-ray (sup)", "bold": "Đậm", @@ -72,7 +88,6 @@ "bookmarkEdit": "Chỉnh sửa dấu trang", "bookmarkEditForSelectedLinesX": "Chỉnh sửa dấu trang cho {0} dòng đã chọn", "bookmarksList": "Danh sách dấu trang", - "borderColor": "Màu viền", "borderStyle": "Kiểu viền", "bottomCenter": "Dưới-Giữa", "bottomLeft": "Dưới-Trái", @@ -99,7 +114,6 @@ "changeCasing": "Thay đổi chữ hoa/thường", "changeFormatting": "Thay đổi định dạng", "changeFrameRate": "Thay đổi tốc độ khung hình", - "changeLanguageFilter": "Thay đổi bộ lọc ngôn ngữ...", "changeOnce": "Thay đổi một lần", "changeSpeed": "Thay đổi tốc độ", "character": "Ký tự", @@ -141,7 +155,6 @@ "conversionCancelledByUser": "Chuyển đổi đã bị người dùng hủy", "convert": "Chuyển đổi", "converted": "Đã chuyển đổi", - "convertingDotDotDot": "Đang chuyển đổi...", "convertingXofYDotDoDot": "Đang chuyển đổi {0:#,###,##0}/{1:#,###,##0}...", "copy": "Sao chép", "copyImageToClipboard": "Sao chép hình ảnh vào clipboard", @@ -152,7 +165,6 @@ "couldNotOpenFileXErrorY": "Không thể mở tệp \"{0}\". Lỗi: {1}", "count": "Số lượng", "cps": "Ký tự/giây", - "currentSubtitle": "Phụ đề hiện tại", "cudaRuntime": "CUDA runtime", "currentVideoPosition": "Vị trí video hiện tại", "custom": "Tùy chỉnh", @@ -212,7 +224,6 @@ "expand": "Mở rộng", "export": "Xuất", "exportDotDotDot": "Xuất...", - "exportToX": "Xuất sang {0}", "extendAfter": "Kéo dài/rút ngắn đến dòng sau", "extendBefore": "Kéo dài/rút ngắn đến dòng trước", "extendSelectedLinesToNextShotChangeOrNextSubtitle": "Kéo dài các dòng đã chọn đến điểm chuyển cảnh tiếp theo (hoặc phụ đề tiếp theo)", @@ -229,7 +240,6 @@ "fileExtension": "Phần mở rộng tệp", "fileName": "Tên tệp", "fileNameX": "Tên tệp: {0}", - "fileNameXAndSize": "Tên tệp: {0} ({1})", "fileSaved": "Đã lưu tệp", "fileSavedToX": "Đã lưu tệp vào {0}", "fileXAlreadyExists": "Tệp \"{0}\" đã tồn tại.", @@ -239,8 +249,6 @@ "find": "Tìm", "findNext": "Tìm tiếp", "findTextX": "Tìm văn bản - {0}", - "fiveHundredMilliseconds": "500 mili giây", - "fiveSeconds": "Năm giây", "fix": "Sửa", "fixCommonErrors": "Sửa lỗi thông thường", "fixRightToLeft": "Sửa văn bản phải-sang-trái", @@ -264,14 +272,12 @@ "foundXInLineYZ": "Tìm thấy \"{0}\" ở dòng {1}, vị trí {2}", "foundXMatches": "Tìm thấy {0} kết quả", "frameRate": "Tốc độ khung hình", - "frameRateX": "Tốc độ khung hình: {0:0.0###}", "frames": "Khung hình", "from": "Từ", "fromCurrentVideoPosition": "từ vị trí video hiện tại", "fullScreen": "Toàn màn hình", "gap": "Khoảng cách", "general": "Chung", - "generalText": "Chung", "generate": "Tạo", "generateImportShotChanges": "Tạo/nhập điểm chuyển cảnh", "generating": "Đang tạo...", @@ -289,12 +295,8 @@ "help": "Trợ giúp", "hex": "Hex", "hide": "Ẩn", - "hidePreview": "Ẩn xem trước", "history": "Lịch sử", "horizontal": "Ngang", - "hourMinutesSecondsDecimalSeparatorMilliseconds": "Giờ:phút:giây{0}ms", - "hourMinutesSecondsFrames": "Giờ:phút:giây:khung hình", - "ip": "IP", "id": "ID", "image": "Hình ảnh", "imageSaved": "Đã lưu hình ảnh", @@ -355,6 +357,7 @@ "mergeSelectedLinesDialog": "Gộp các dòng đã chọn thành hội thoại", "mergeWithLineAfterAndAutoBreak": "Gộp với dòng sau và tự động ngắt dòng", "mergeWithLineAfterKeepBreaks": "Gộp với dòng sau (giữ ngắt dòng)", + "mergeWithLineAfterAndUnbreak": "Gộp với dòng sau và bỏ ngắt dòng", "mergeWithLineBeforeAndAutoBreak": "Gộp với dòng trước và tự động ngắt dòng", "mergeWithLineBeforeKeepBreaks": "Gộp với dòng trước (giữ ngắt dòng)", "middleCenter": "Giữa-Giữa", @@ -369,13 +372,13 @@ "moveAllShotChangeOneFrameForward": "Di chuyển tất cả điểm chuyển cảnh tiến một khung hình", "multiMode": "Chế độ đa dòng", "moveDown": "Di chuyển xuống", + "increase": "Tăng", + "decrease": "Giảm", "moveUp": "Di chuyển lên", "moveToTop": "Chuyển lên đầu", "moveToBottom": "Chuyển xuống cuối", "multipleReplace": "Thay thế nhiều", - "mute": "Tắt tiếng", "name": "Tên", - "negative": "Âm", "new": "Mới", "newDotDotDot": "Mới...", "newProfile": "Hồ sơ mới", @@ -405,20 +408,15 @@ "offsetY": "Độ lệch Y", "ok": "_OK", "oneFile": "Một tệp", - "oneHundredMilliseconds": "100 mili giây", - "oneSecond": "Một giây", "oneSecondBack": "Lùi một giây", "oneSecondForward": "Tiến một giây", - "onlineVideoFeatureNotAvailable": "Tính năng không khả dụng cho video trực tuyến", "openContainingFolder": "Mở thư mục chứa tệp", "openDictionaryFolder": "Mở thư mục từ điển", "openFile": "Mở tệp", "openImageBasedSubtitle": "Mở phụ đề dạng hình ảnh", "openImageFile": "Mở tệp hình ảnh", - "openOriginalSubtitleFile": "Mở tệp phụ đề gốc...", "openOriginalSubtitleFileTitle": "Mở tệp phụ đề gốc", "openOutputFolder": "Mở thư mục đầu ra", - "openSubtitle": "Mở phụ đề...", "openSubtitleFileTitle": "Mở tệp phụ đề", "openSubtitles": "Mở phụ đề...", "openVideoFile": "Mở tệp video...", @@ -433,11 +431,6 @@ "outlineWidth": "Độ rộng viền chữ", "outputFolder": "Thư mục đầu ra", "outputProperties": "Thuộc tính đầu ra", - "overlap": "Chồng lấp", - "overlapNextX": "Chồng lấp dòng sau ({0:#;##0.###})", - "overlapPreviousLineX": "Chồng lấp dòng trước ({0:#;##0.###})", - "overlapStartAndEnd": "Chồng lấp đầu và cuối", - "overlapX": "Chồng lấp ({0:#;##0.###})", "overwriteExistingFiles": "Ghi đè các tệp hiện có", "overwriteFilesInFolderX": "Ghi đè các tệp trong thư mục: \"{0}\"", "overwriteQuestion": "Ghi đè?", @@ -450,6 +443,7 @@ "percent": "Phần trăm", "pickLayer": "Đặt lớp", "pickOllamaModel": "Chọn mô hình Ollama", + "pickModel": "Chọn mô hình", "llamaCppUseRemoteServer": "Sử dụng máy chủ bên ngoài (URL)", "llamaCppEngineSettings": "Cài đặt công cụ llama.cpp", "llamaCppEngineSettingsSubtitle": "Bản dựng máy chủ llama.cpp cục bộ", @@ -501,7 +495,6 @@ "refresh": "Làm mới", "region": "Vùng", "regularExpression": "Biểu thức chính quy", - "regularExpressionIsNotValid": "Biểu thức chính quy không hợp lệ!", "relativeToCurrentVideoPosition": "Tương đối so với vị trí video hiện tại", "remove": "Xóa", "removeAlignment": "Xóa căn chỉnh", @@ -527,7 +520,7 @@ "reset": "Đặt lại", "resizePanels": "Thay đổi kích thước bảng", "resolution": "Độ phân giải", - "resolutionX": "Độ phân giải: {0}", + "retry": "Thử lại", "reverseRightToLeftStartEnd": "Đảo ngược đầu/cuối RTL", "right": "Phải", "rightToLeft": "Phải sang trái", @@ -567,8 +560,6 @@ "selectedlinesX": "Dòng đã chọn: {0}", "sensitivity": "Độ nhạy", "separator": "Dấu phân cách", - "sessionKey": "Khóa phiên", - "sessionKeyGenerate": "Tạo khóa mới", "setEnd": "Đặt điểm kết thúc", "setEndAndGoToNext": "Đặt điểm kết thúc và chuyển đến dòng tiếp theo", "setEndAddNewAndGoToNew": "Đặt điểm kết thúc, thêm dòng mới và chuyển đến dòng mới", @@ -591,8 +582,9 @@ "shadowColor": "Màu bóng đổ", "shadowWidth": "Độ rộng bóng đổ", "shiftLineBy": "Dịch chuyển dòng đi", + "shotIn": "Cảnh vào", + "shotOut": "Cảnh ra", "shortcut": "Phím tắt", - "shortcutX": "Phím tắt: {0}", "shortcuts": "Phím tắt", "show": "Hiển thị", "showActorColumn": "Hiển thị cột \"Diễn viên\"", @@ -603,17 +595,19 @@ "showStartColumn": "Hiển thị cột \"Bắt đầu\"", "showHideColumn": "Hiển thị cột \"Ẩn\"", "showHistory": "Hiển thị lịch sử", + "clearHistory": "Xóa lịch sử", + "removeFromHistory": "Xóa khỏi lịch sử", "showLayerColumn": "Hiển thị cột \"Lớp\"", "showPreview": "Hiển thị xem trước", "showShotChangesList": "Hiển thị danh sách thay đổi cảnh", + "showShotInColumn": "Hiển thị cột \"Cảnh vào\"", + "showShotOutColumn": "Hiển thị cột \"Cảnh ra\"", "showStyleColumn": "Hiển thị cột \"Kiểu\"", "showTeletext": "Hiển thị cột teletext trong danh sách", "showTimeCodes": "Hiển thị mã thời gian", "showWpmColumn": "Hiển thị cột \"Từ/phút\"", "showPixelWidthColumn": "Hiển thị cột \"Độ rộng điểm ảnh\"", "shrink": "Thu nhỏ", - "singleBox": "Hộp đơn", - "singleLineLengths": "Độ dài dòng đơn:", "singleMode": "Chế độ đơn", "textTotalLength": "Văn bản - tổng độ dài", "textSingleLineMaxLength": "Văn bản - độ dài tối đa mỗi dòng", @@ -662,16 +656,16 @@ "strikeout": "Gạch ngang", "style": "Kiểu", "styleExaggeration": "Cường điệu phong cách", - "styleLanguage": "Kiểu / Ngôn ngữ", "styles": "Kiểu", "subtitleFile": "Tệp phụ đề", + "subtitleFileChangedOutsideX": "Tệp \"{0}\" đã bị thay đổi bên ngoài Subtitle Edit.\n\nTải lại tệp và mất các thay đổi của bạn?", "subtitleFileName": "Tên tệp phụ đề", "subtitleFileSaved": "Đã lưu tệp phụ đề", "subtitleFileSavedToX": "Đã lưu tệp phụ đề vào {0}", "subtitleFiles": "Tệp phụ đề", "subtitleFormats": "Định dạng phụ đề", "subtitleLoadedX": "Đã tải phụ đề: {0}", - "subtitleXOfY": "Phụ đề {0} trên {1}", + "subtitleReloadedChangedOutsideX": "Đã tải lại phụ đề (bị thay đổi bên ngoài Subtitle Edit): {0}", "suffix": "Hậu tố", "suggestions": "Gợi ý", "sync": "Đồng bộ", @@ -683,7 +677,6 @@ "teletextColor": "Màu teletext", "teletextLine": "Dòng teletext", "tenHours": "10 giờ", - "tenMilliseconds": "10 mili giây", "text": "Văn bản", "textColor": "Màu chữ", "textFiles": "Tệp văn bản", @@ -710,9 +703,6 @@ "topRight": "Trên-Phải", "top": "Trên", "totalAdjustmentX": "Tổng điều chỉnh: {0}", - "totalFramesX": "Tổng số khung hình: {0:#;##0.##}", - "totalLengthX": "Tổng độ dài: {0}", - "totalLengthXSplitLine": "Tổng độ dài: {0} (hãy tách dòng!)", "translate": "Dịch", "translatePercentX": "Đang dịch: {0}%", "translateRow": "Dịch hàng", @@ -721,6 +711,7 @@ "type": "Loại", "unbreak": "Gộp dòng văn bản", "unbreakLines": "Gộp các dòng", + "undelete": "Khôi phục đã xóa", "underline": "Gạch chân", "undo": "Hoàn tác", "unknown": "Không xác định", @@ -740,13 +731,11 @@ "usages": "Cách dùng", "use": "Dùng", "useAlways": "Luôn dùng", - "useLargerFontForThisWindow": "Dùng phông chữ lớn hơn cho cửa sổ này", "useOnce": "Dùng một lần", "useOutputFolder": "Dùng thư mục xuất", "useSourceFolder": "Dùng thư mục nguồn", "useSourceResolution": "Dùng độ phân giải nguồn", "userName": "Tên người dùng", - "userNameAlreadyInUse": "Tên người dùng đã được sử dụng", "vertical": "Dọc", "video100MsBack": "Video, lùi 100 mili giây", "video100MsForward": "Video, tiến 100 mili giây", @@ -760,14 +749,25 @@ "videoCustom3ForwardX": "Video, tiến số mili giây tùy chỉnh ({0:#,###,##0}), 3", "videoCustom4BackX": "Video, lùi số mili giây tùy chỉnh ({0:#,###,##0}), 4", "videoCustom4ForwardX": "Video, tiến số mili giây tùy chỉnh ({0:#,###,##0}), 4", - "videoEncodingX": "Mã hóa video: {0}", + "videoCustom1BackAndPauseX": "Video, lùi số mili giây tùy chỉnh ({0:#,###,##0}) và tạm dừng, 1", + "videoCustom1ForwardAndPauseX": "Video, tiến số mili giây tùy chỉnh ({0:#,###,##0}) và tạm dừng, 1", + "videoCustom2BackAndPauseX": "Video, lùi số mili giây tùy chỉnh ({0:#,###,##0}) và tạm dừng, 2", + "videoCustom2ForwardAndPauseX": "Video, tiến số mili giây tùy chỉnh ({0:#,###,##0}) và tạm dừng, 2", + "videoCustom3BackAndPauseX": "Video, lùi số mili giây tùy chỉnh ({0:#,###,##0}) và tạm dừng, 3", + "videoCustom3ForwardAndPauseX": "Video, tiến số mili giây tùy chỉnh ({0:#,###,##0}) và tạm dừng, 3", + "videoCustom4BackAndPauseX": "Video, lùi số mili giây tùy chỉnh ({0:#,###,##0}) và tạm dừng, 4", + "videoCustom4ForwardAndPauseX": "Video, tiến số mili giây tùy chỉnh ({0:#,###,##0}) và tạm dừng, 4", "videoExtension": "Phần mở rộng tệp video", "videoFile": "Tệp video", "videoFileGenerated": "Đã tạo tệp video", "videoFileGeneratedX": "Đã tạo tệp video: \"{0}\"", + "videoFilesGenerated": "Đã tạo tệp video", + "xVideosGenerated": "Đã tạo {0} video", + "xOfYVideosGenerated": "Đã tạo {0}/{1} video", + "savedInX": "Đã lưu trong {0}", + "savedInXFolders": "Đã lưu trong {0} thư mục", "videoFiles": "Tệp video", "videoAndAudioFiles": "Tệp video và âm thanh", - "videoInformation": "Thông tin video", "videoOffset": "Độ lệch video", "videoOneFrameBack": "Video, lùi một khung hình", "videoOneFrameForward": "Video, tiến một khung hình", @@ -778,7 +778,6 @@ "videoPlayer": "Trình phát video", "videoPosition": "Vị trí video", "videoResolution": "Độ phân giải video", - "viewX": "Xem {0}", "visible": "Hiển thị", "voice": "Giọng nói", "volume": "Âm lượng", @@ -787,9 +786,7 @@ "waveformCenterOnVideoPosition": "Căn giữa dạng sóng theo vị trí video", "waveformPasteFromClipboard": "Dán từ khay nhớ tạm", "waveformSpectrogram": "Dạng sóng/quang phổ", - "webServiceUrl": "URL dịch vụ web", "width": "Chiều rộng", - "windowPositionAndSize": "Vị trí và kích thước cửa sổ", "wordsPerMin": "Từ/phút", "pixelWidth": "Độ rộng điểm ảnh", "wpm": "Từ/phút", @@ -803,8 +800,6 @@ "noBookmarksFound": "Không tìm thấy dấu trang nào nữa", "xRequiresAnApiKey": "{0} yêu cầu khóa API", "xRequiresAValidUrl": "{0} yêu cầu một URL hợp lệ (ví dụ: \"http://...\" hoặc \"https://...\")", - "xSeconds": "{0:0.0##} giây", - "xSubtitles": "{0:#,###,##0} phụ đề", "yes": "Có", "zeroBytes": "0 byte", "copyToClipboard": "Sao chép vào khay nhớ tạm", @@ -826,10 +821,8 @@ "playbackSpeed": "Tốc độ phát", "hoursMinutesSecondsMilliseconds": "HH:MM:SS.FFF", "imageFiles": "Tệp hình ảnh", - "openAiCompatibleStt": "STT tương thích OpenAI", "openAiCompatibleSttEndpoint": "URL điểm cuối STT", "openAiCompatibleSttApiKey": "Khóa API", - "openAiCompatibleSttModel": "Mô hình", "openAiCompatibleSttExtraHeaders": "Header bổ sung", "openAiCompatibleSttTimeout": "Thời gian chờ (giây)", "openAiCompatibleSttLanguage": "Gợi ý ngôn ngữ", @@ -839,6 +832,9 @@ "dashScopeSttRegion": "Khu vực", "dashScopeSttEnableWords": "Dấu thời gian theo từng từ", "dashScopeSttRegionKeyHint": "Lưu ý: Khóa API của Alibaba Cloud Model Studio phụ thuộc vào khu vực - hãy đảm bảo khu vực đã chọn khớp với khu vực nơi khóa API được tạo (Trung Quốc so với Quốc tế).", + "googleCloudSttProjectId": "ID dự án (chỉ dành cho Application Default Credentials)", + "googleCloudSttBucketName": "Bucket lưu trữ (trống = tự động)", + "googleCloudSttDynamicBatching": "Xử lý lô động (rẻ hơn khoảng 5 lần, không đảm bảo độ trễ)", "onlineSttApiKeyMissing": "Cần có khóa API. Vui lòng nhập khóa API và thử lại.", "openAiCompatibleSttAutoTranscribeOnAudioSelection": "Tự động chuyển vùng chọn dạng sóng mới thành văn bản qua giọng nói-thành-văn bản", "openAiCompatibleSttStream": "Truyền phản hồi trực tiếp", @@ -848,11 +844,9 @@ "configurationRequired": "Yêu cầu cấu hình", "transcriptionError": "Lỗi phiên âm", "transcriptionComplete": "Phiên âm hoàn tất", - "transcribingWithOpenAi": "Đang phiên âm bằng dịch vụ OpenAI...", "processingResponse": "Đang xử lý phản hồi...", "unauthorizedApiKey": "Không được phép: Khóa API không hợp lệ hoặc không đủ quyền.", "requestTimeout": "Hết thời gian yêu cầu: Máy chủ phản hồi quá lâu.", - "noSegmentsReturned": "Dịch vụ phiên âm không trả về phân đoạn nào.", "openAiCompatibleSttUrlMissing": "Cần có URL điểm cuối STT tương thích OpenAI. Vui lòng nhập URL và thử lại.", "openAiCompatibleSttUrlNotResponding": "Điểm cuối STT tương thích OpenAI không phản hồi:\n{0}\n\nVui lòng kiểm tra URL và đảm bảo máy chủ đang chạy.", "transcriptionFailed": "Phiên âm thất bại", @@ -898,14 +892,15 @@ "multipleReplace": "Thay thế hàng l_oạt...", "fillSelectedLinesWithClipboard": "Điền các dòng đã chọn bằng văn bản trong khay nhớ tạm", "goToLineNumber": "Đi đến số dòn_g...", + "commandPalette": "Bảng _lệnh...", "rightToLeftMode": "Chế độ phả_i-sang-trái", "modifySelectionDotDotDot": "Sửa đổi vùng chọ_n...", "tools": "Công c_ụ", - "toolsSelectedLines": "Công c_ụ (các dòng đã chọn)", "adjustDurations": "Điều chỉnh thời lượn_g...", "applyDurationLimits": "Áp dụng giới hạn thời _lượng...", "batchConvert": "Chuyển đổi hàng l_oạt...", "beautifyTimeCodes": "Làm đẹp mã thời gian...", + "improveTimeCodes": "Cải thiện mã thời gian (căn chỉnh cưỡng bức)...", "bridgeGaps": "Nối các khoản_g trống...", "applyMinGap": "Áp dụng khoảng cách tối thiểu giữa các phụ đề...", "changeCasing": "Đổi _chữ hoa/thường...", @@ -1044,6 +1039,9 @@ "selectCurrentLineWhilePlayingHint": "Chọn phụ đề hiện tại khi đang phát {0}", "videoPosition": "Vị trí video {0}", "videoPositionTextBox": "Ô nhập vị trí video {0}", + "toolbarLineBreakX": "Ngắt dòng {0} (phần còn lại của thanh công cụ tiếp tục ở hàng mới)", + "initialText": "Văn bản ban đầu của dòng đã chọn", + "initialTextHint": "Văn bản của dòng đã chọn như lúc dòng được chọn", "hideWaveformToolbar": "Ẩn thanh công cụ {0}", "resetZoomAndSpeed": "Đặt lại thu phóng & tốc độ phát {0}", "removeBlankLines": "Xóa các dòng trống {0}", @@ -1057,6 +1055,15 @@ "textPlayHint": "Phát phụ đề hiện tại và dừng ở cuối {0}", "textPauseHint": "Tạm dừng phát {0}", "textNextHint": "Phát phụ đề tiếp theo và dừng ở cuối {0}", + "moveSelectedLines": "Di chuyển các dòng đã chọn (nút lùi/tiến)", + "moveSelectedLinesAndFollowing": "Di chuyển các dòng đã chọn và tất cả các dòng sau (nút lùi/tiến)", + "moveAllLines": "Di chuyển tất cả các dòng (nút lùi/tiến)", + "moveSelectedLinesBackHint": "Lùi các dòng đã chọn {0} ms {1}", + "moveSelectedLinesForwardHint": "Tiến các dòng đã chọn {0} ms {1}", + "moveSelectedLinesAndFollowingBackHint": "Lùi các dòng đã chọn và tất cả các dòng sau {0} ms {1}", + "moveSelectedLinesAndFollowingForwardHint": "Tiến các dòng đã chọn và tất cả các dòng sau {0} ms {1}", + "moveAllLinesBackHint": "Lùi tất cả các dòng {0} ms {1}", + "moveAllLinesForwardHint": "Tiến tất cả các dòng {0} ms {1}", "guessStartNoLineSelected": "Đoán điểm bắt đầu: chưa chọn dòng nào", "guessStartTimeCodesLocked": "Đoán điểm bắt đầu: mã thời gian đang bị khóa", "guessStartNoWaveform": "Đoán điểm bắt đầu: không có dạng sóng", @@ -1078,12 +1085,15 @@ "charactersPerSecond": "Ký tự/giây: {0}", "chooseColumn": "Chọn cột", "columnPaste": "Dán cột", + "commandPaletteTitle": "Bảng lệnh", + "commandPaletteSearchPlaceholder": "Gõ để tìm lệnh...", + "commandPaletteNoCommandsFound": "Không có lệnh phù hợp", "createdEmptyTranslation": "Đã tạo bản dịch trống từ phụ đề hiện tại", "deleteText": "Xóa văn bản", "deleteTextAndShiftCellsUp": "Xóa văn bản và dịch các ô lên trên", "endTimeMustBeAfterStartTime": "Thời gian kết thúc phải sau thời gian bắt đầu.", "errorLoad7Zip": "Tệp này có vẻ là tệp nén 7-Zip.\n\nSubtitle Edit không thể mở tệp nén.", - "errorLoadBinaryZeroes": "Rất tiếc, tệp này chỉ chứa toàn số 0 nhị phân!\n\nNếu bạn đã chỉnh sửa tệp này bằng Subtitle Edit, bạn có thể tìm bản sao lưu qua mục menu Tệp -> Khôi phục bản sao lưu tự động...", + "errorLoadBinaryZeroes": "Rất tiếc, tệp này chỉ chứa toàn số 0 nhị phân!\n\nNếu bạn đã chỉnh sửa tệp này bằng Subtitle Edit, bạn có thể tìm bản sao lưu qua mục menu Tệp -> Khôi phục bản sao lưu tự động...", "errorLoadGZip": "Tệp này có vẻ là tệp nén GZip.\n\nSubtitle Edit không thể mở tệp nén.", "errorLoadLargeFile": "Tệp này quá lớn để mở dưới dạng tệp phụ đề.", "errorLoadVideoFilePrompt": "Tệp này có vẻ là tệp video:\n{0}\n\nBạn có thể mở video qua \"Video -> Mở video\" hoặc kéo và thả video vào trình phát video.\n\nBạn có muốn mở nó dưới dạng tệp video không?", @@ -1116,18 +1126,20 @@ "numberOfLinesEvenlyDistributedX": "Đã phân bổ đều {0} dòng", "oneLineCopiedFromOriginal": "Đã sao chép một dòng từ phụ đề gốc", "oneLineCopiedToOriginal": "Đã sao chép một dòng sang phụ đề gốc", + "onlineOnlyVideo": "Video chỉ lưu trực tuyến", + "onlineOnlyVideoXSizeYDownloadAndOpen": "Tệp video \"{0}\" ({1}) chỉ được lưu trữ trực tuyến, ví dụ trong Dropbox hoặc iCloud Drive, và phải được tải xuống trước khi có thể mở.\n\nTải xuống và mở ngay bây giờ?", "oneLineMerged": "Đã gộp một dòng", "oneLineSwitched": "Đã hoán đổi một dòng", "overwriteExistingCells": "Ghi đè các ô hiện có", "overwriteOrShiftCellsDown": "Ghi đè/dịch các ô xuống dưới", "parsingMatroskaFile": "Đang phân tích tệp Matroska...", + "readingDvdSubtitles": "Đang đọc phụ đề DVD...", "pasteFromClipboardDotDotDot": "Dán từ bộ nhớ tạm...", "redoPerformed": "Đã thực hiện làm lại", "redoPerformedXActionLeft": "Đã thực hiện làm lại (thao tác còn lại: {0})", "removedUnicodeControlCharactersX": "Đã xóa ký tự điều khiển Unicode khỏi {0} dòng", "removedXBlankLines": "Đã xóa {0} dòng trống", "replacedXWithYCountZ": "Đã thay thế \"{0}\" bằng \"{1}\" ({2} lần xuất hiện)", - "replacedXWithYInLineZ": "Đã thay thế \"{0}\" bằng \"{1}\" trong dòng {2}", "reversedStartAndEndingsForRightToLeftX": "Đã đảo đầu và cuối cho hướng phải-sang-trái trong {0} dòng", "ruleProfileIsX": "Hồ sơ quy tắc hiện là \"{0}\"", "saveLanguageFile": "Lưu tệp ngôn ngữ", @@ -1135,7 +1147,6 @@ "shiftTextCellsDown": "Dịch các ô văn bản xuống dưới", "singleLineLength": "Độ dài dòng: ", "speedIsNowX": "Tốc độ hiện là \"{0}\"", - "spellCheckResult": "Kiểm tra chính tả hoàn tất. \n\n• Từ đã thay đổi: {0}\n• Từ đã bỏ qua: {1}", "subtitleGridFormattingX": "Định dạng lưới: {0}", "subtitleImportedFromMatroskaFile": "Đã nhập phụ đề từ tệp Matroska", "textDown": "Chuyển văn bản xuống", @@ -1162,13 +1173,14 @@ "trimmedXLines": "Đã cắt gọn {0} dòng phụ đề", "pastedXLinesOverSelectedLines": "Đã dán {0} dòng đè lên các dòng đã chọn", "pastedXOfYLinesOverSelectedLines": "Đã dán {0} trong số {1} dòng đè lên các dòng đã chọn - phần còn lại không vừa với vùng chọn", + "insertedXLinesFromClipboard": "Đã chèn {0} dòng từ clipboard", "openOriginalDifferentNumberOfSubtitlesXY": "Tệp phụ đề gốc không có cùng số lượng phụ đề với tệp phụ đề hiện tại.\n\n• Phụ đề gốc: {0}\n• Phụ đề hiện tại: {1}", - "importXMatchingOriginalLines": "Nhập {0} phụ đề gốc khớp nhau?", "originalTextReadOnly": "Văn bản gốc (chỉ đọc)", "originalTextEditMode": "Văn bản gốc (chế độ chỉnh sửa)", "originalIsReadOnlyNotSaved": "Phụ đề gốc là tài liệu tham chiếu chỉ đọc nên không được lưu", "originalIsEmptyNotSaved": "Phụ đề gốc hoàn toàn không có văn bản nên không được lưu", "originalIsReadOnlyReference": "Phụ đề gốc được mở dưới dạng tham chiếu chỉ đọc", + "closeTranslationQuestionXY": "Đóng bản dịch \"{0}\"?\n\nPhụ đề gốc \"{1}\" sẽ trở thành phụ đề bạn chỉnh sửa. Bản thân tệp bản dịch không bị thay đổi.", "allowEditOfOriginalSubtitle": "Cho phép chỉnh sửa phụ đề gốc", "showAllOriginalLinesX": "Hiển thị tất cả các dòng gốc ({0} dòng không khớp với dòng nào ở đây)", "showAllOriginalLinesHint": "Các dòng không khớp được hiển thị dưới dạng hàng bổ sung, để bạn thấy bản dịch còn thiếu gì.", @@ -1203,6 +1215,7 @@ "fileExportedInFormatXToY": "Đã xuất tệp ở định dạng {0} sang {1}", "fileExportedInFormatXToFileY": "Đã xuất tệp ở định dạng \"{0}\" sang tệp \"{1}\"", "fixedXLines": "Đã sửa {0} dòng", + "toolXChangedYLines": "{0}: đã thay đổi {1} dòng", "transcriptionCompletedWithXLines": "Phiên âm hoàn tất với {0} dòng", "openSubtitleFileX": "Mở tệp phụ đề \"{0}\"?", "replacedXOccurrences": "Đã thay thế {0} lần xuất hiện", @@ -1234,13 +1247,9 @@ "file": { "ebuSaveOptions": { "title": "Tùy chọn lưu EBU", - "generalSubtitleInformation": "Thông tin phụ đề chung", "codePageNumber": "Số trang mã", "diskFormatCode": "Mã định dạng đĩa", "displayStandardCode": "Mã chuẩn hiển thị", - "colorRequiresTeletext": "Màu sắc yêu cầu teletext!", - "alignmentRequiresTeletext": "Căn chỉnh yêu cầu teletext!", - "teletextCharsShouldBe38": "'Số ký tự tối đa mỗi hàng' cho teletext phải là 38!", "characterCodeTable": "Bảng ký tự", "languageCode": "Mã ngôn ngữ", "originalProgramTitle": "Tên chương trình gốc", @@ -1257,7 +1266,6 @@ "maxNumberOfDisplayableRows": "Số hàng tối đa", "diskSequenceNumber": "Số thứ tự đĩa", "totalNumberOfDisks": "Tổng số đĩa", - "import": "Nhập...", "textAndTimingInformation": "Thông tin văn bản và thời gian", "justificationCode": "Mã căn lề", "verticalPosition": "Vị trí dọc", @@ -1274,11 +1282,6 @@ "errors": "Lỗi", "errorsX": "Lỗi: {0}", "maxLengthError": "Dòng {0} vượt quá độ dài tối đa ({1}) là {2}: {3}", - "textUnchangedPresentation": "Trình bày không thay đổi", - "textLeftJustifiedText": "Văn bản căn trái", - "textCenteredText": "Văn bản căn giữa", - "textRightJustifiedText": "Văn bản căn phải", - "useBoxForOneNewLine": "Chọn 'Dùng hộp bao quanh văn bản' cho chỉ một dòng mới", "maxLengthErrorTeletextColored": "Dòng {0}-{1}: số ký tự tối đa cho văn bản màu chiều cao đôi phải là 36 (không phải {2})", "maxLengthErrorTeletextWhite": "Dòng {0}-{1}: số ký tự tối đa cho văn bản trắng chiều cao đôi phải là 37 (không phải {2})", "maxLengthErrorTeletextSingleHeight": "Dòng {0}: số ký tự tối đa cho văn bản trắng chiều cao đơn phải là 38 (không phải {1})", @@ -1303,22 +1306,29 @@ "formattingDotDotDot": "Định dạng...", "imageBasedSubtitleForEditDotDotDot": "Phụ đề dạng hình ảnh để chỉnh sửa...", "imageBasedSubtitleForOcrDotDotDot": "Phụ đề dạng hình ảnh cho OCR...", + "dvdSubtitlesDotDotDot": "Phụ đề từ DVD (IFO/VOB)...", + "titleImportDvd": "Nhập phụ đề từ DVD", + "dvdIfoFile": "Tệp IFO", + "dvdVobFiles": "Tệp VOB", + "dvdVideoStandard": "Chuẩn video", + "dvdStartRipping": "Bắt đầu trích xuất", + "dvdRippingAborted": "Đã hủy trích xuất", + "dvdOpenIfoFile": "Mở tệp DVD IFO", + "dvdAddVobFiles": "Thêm tệp VOB", + "dvdIfoFiles": "Tệp DVD IFO", + "dvdVobFileType": "Tệp DVD VOB", + "dvdDropHint": "Mở tệp IFO (VIDEO_TS\\VTS_01_0.IFO) hoặc thêm tệp VOB - hoặc thả chúng vào đây", + "dvdNotAnIfoFileX": "{0} không phải là tệp DVD IFO", + "dvdMenuIfoNoTitleSetsX": "Không tìm thấy title set (VTS_xx_0.IFO) nào cạnh {0}", + "dvdEncryptedXOfY": "{0} trong số {1} gói phụ đề vẫn bị mã hóa CSS - các tệp VOB này đã được sao chép mà không giải mã, nên một số hình ảnh phụ đề sẽ bị lỗi.\n\nHãy giải mã DVD bằng công cụ trích xuất DVD trước để có kết quả sạch.\n\nVẫn tiếp tục?", "splitTextAt": "Tách văn bản tại", "blankLines": "Dòng trống", "oneLineIsOneSubtitle": "Một dòng là một phụ đề", "twoLinesAreOneSubtitle": "Hai dòng là một phụ đề", "importFilesDotDotDot": "Nhập tệp...", "multipleFiles": "Nhập từ nhiều tệp văn bản (một tệp là một phụ đề)", - "importOptions": "Tùy chọn nhập", - "autoSplitText": "Tự động tách văn bản", - "lineMode": "Chế độ dòng", "maxLineLength": "Độ dài dòng tối đa", - "maxLinesPerSubtitle": "Số dòng tối đa mỗi phụ đề", - "minGapBetweenSubtitles": "Khoảng cách tối thiểu giữa các phụ đề", "mergeShortLines": "Gộp các dòng ngắn", - "removeLinesWithoutLetters": "Xóa các dòng không có chữ cái", - "splitAtEndCharsSetting": "Tách tại ký tự kết thúc", - "takeTimeFromCurrentFile": "Lấy thời gian từ tệp hiện tại", "fixed": "Cố định", "numberOfSubtitlesX": "Số lượng phụ đề: {0}", "numberOfSubtitlesXForcedY": "Số lượng phụ đề: {0} ({1} bắt buộc)", @@ -1330,14 +1340,18 @@ "forcedAlignerProgress": "Đang căn chỉnh... khung {0}/{1}, {2}/{3} dòng", "forcedAlignerSetupTitle": "Căn chỉnh mã thời gian bằng bộ căn chỉnh cưỡng bức", "forcedAlignerSetupIntro": "Bộ căn chỉnh cưỡng bức đối chiếu văn bản phụ đề bạn đã có với âm thanh mà không cần phiên âm trước. Video dài được căn chỉnh theo từng đoạn, nên độ dài nào cũng được.", + "forcedAlignerEndsFromSpeech": "Đặt thời gian kết thúc từ giọng nói đã tách riêng (chậm)", + "forcedAlignerEndsFromSpeechHint": "Bộ căn chỉnh cưỡng bức tìm nơi một dòng bắt đầu, không phải nơi nó kết thúc, nên thời gian kết thúc thường dựa trên thời gian đọc. Khi bật tùy chọn này, nhạc và hiệu ứng âm thanh được loại bỏ khỏi âm thanh trước và mỗi dòng kết thúc khi giọng nói của nó lặng đi. Mất khoảng thời gian bằng chính video nếu có GPU - và lâu hơn nhiều lần nếu không có.", + "forcedAlignerIsolatingSpeech": "Đang tách riêng giọng nói... {0}", + "forcedAlignerIsolatingSpeechFailed": "Không thể tách riêng giọng nói - thời gian kết thúc đã được đặt theo thời gian đọc.", "forcedAlignerModel": "Mô hình căn chỉnh", - "forcedAlignerDownloadEngine": "Tải xuống / cập nhật công cụ..." + "forcedAlignerDownloadEngine": "Tải xuống / cập nhật công cụ...", + "forcedAlignerEstimatedXOfY": "{0} trong số {1} dòng không thể căn chỉnh và đã được đặt theo thời gian đọc - mã thời gian của chúng chỉ là ước tính. Kịch bản có lẽ dài hơn những gì được nói trong video." }, "export": { "exportImagesProfiles": "Hồ sơ xuất hình ảnh", "leftRightMargin": "Lề trái/phải", "topBottomMargin": "Lề trên/dưới", - "titleExportBluRaySup": "Xuất Blu-ray (sup)", "lineSpacingPercent": "Giãn dòng %", "paddingLeftRight": "Đệm trái/phải", "paddingTopBottom": "Đệm trên/dưới", @@ -1378,10 +1392,21 @@ "titleExportDCinemaInteropPng": "D-Cinema interop/png", "titleExportDCinemaSmpte2014Png": "D-Cinema SMPTE 2014/png", "imageBasedSubtitleSaved": "Đã lưu phụ đề dạng hình ảnh", - "titleExportBdnXml": "Xuất BDN/xml", "titleExportWebVttThumbnails": "Xuất WebVTT (hình thu nhỏ)", "fullFrameImage": "Hình ảnh toàn khung", "fullFrameImageHint": "Tạo mỗi hình ảnh có kích thước bằng khung hình video, với phụ đề nằm đúng vị trí", + "stereo3D": "3D", + "stereo3DHint": "Cho video 3D: vẽ phụ đề một lần trong mỗi nửa khung hình dành cho từng mắt", + "halfSideBySide": "Nửa cạnh nhau (side-by-side)", + "halfTopBottom": "Nửa trên/dưới", + "depth3D": "Độ sâu", + "depth3DHint": "Mức phụ đề nổi ra khỏi màn hình - dương đưa nó lại gần hơn, âm đẩy nó ra xa", + "plane3D": "3D-Plane", + "plane3DHint": "Gán cho mỗi phụ đề độ sâu mà đĩa Blu-ray 3D lưu cho nó, từ tệp 3D-Plane (.ofs) tạo bởi BD3D2MK3D hoặc OFSExtractor. Độ sâu được dùng ở những chỗ 3D-Plane không có", + "openPlane3DTitle": "Mở tệp 3D-Plane", + "unableToLoadPlane3DX": "Không thể tải 3D-Plane: {0}", + "plane3DXDepthYToZ": "{0} (độ sâu {1} đến {2})", + "plane3DXNoDepth": "{0} (không có giá trị độ sâu)", "textEffect": "Hiệu ứng văn bản", "textEffectSettingsTitle": "Cài đặt hiệu ứng văn bản", "textEffectPreset": "Mẫu dựng sẵn", @@ -1413,13 +1438,10 @@ "statistics": { "title": "Thống kê", "titleWithFileName": "Thống kê - {0}", - "generalStatistics": "Thống kê chung", - "mostUsed": "Dùng nhiều nhất...", "mostUsedLines": "Các dòng dùng nhiều nhất", "mostUsedWords": "Các từ dùng nhiều nhất", "nothingFound": "Không tìm thấy gì", "numberOfLinesX": "Số dòng phụ đề: {0:#,##0}", - "numberOfFilesX": "Số tệp phụ đề: {0:#,##0}", "lengthInFormatXinCharactersY": "Số ký tự theo {0}: {1:#,###,##0}", "numberOfCharactersInTextOnly": "Số ký tự chỉ trong văn bản: {0:#,###,##0}", "totalDuration": "Tổng thời lượng của tất cả phụ đề: {0:#,##0}", @@ -1460,7 +1482,6 @@ "gapMaximum": "Khoảng cách - tối đa: {0:#,##0} ms", "gapAverage": "Khoảng cách - trung bình: {0:#,##0.##} ms", "gapExceedingMinimum": "Khoảng cách - dưới tối thiểu ({0:#,##0} ms): {1} ({2:0.00}%)", - "export": "Xuất...", "subtitles": "Phụ đề", "words": "Từ", "totalDurationShort": "Tổng thời lượng", @@ -1490,7 +1511,22 @@ "restoreAutoBackupFile": "Khôi phục tệp sao lưu tự động", "restoreXFromY": "Bạn có muốn khôi phục \"{0}\" từ {1} không?", "deleteAllSubtitleBackups": "Bạn có muốn xóa tất cả các tệp sao lưu phụ đề không?", - "deleteAll": "Xóa tất cả" + "deleteAll": "Xóa tất cả", + "subtitles": "Phụ đề", + "settingsBackupInfo": "Settings.json được sao lưu khi Subtitle Edit khởi động, tối đa một lần mỗi {0} ngày và chỉ khi cài đặt đã thay đổi. {1} bản sao lưu mới nhất được giữ lại.", + "settingsBackupInfoEveryStart": "Settings.json được sao lưu mỗi lần Subtitle Edit khởi động, nếu cài đặt đã thay đổi. {0} bản sao lưu mới nhất được giữ lại.", + "settingsBackupOff": "Sao lưu cài đặt tự động đang tắt (Tùy chọn → Cài đặt → Tệp).", + "lastSettingsBackupX": "Lần sao lưu cuối: {0}", + "noSettingsBackupsYet": "Chưa có bản sao lưu cài đặt nào", + "openSettingsBackupFolder": "Mở thư mục sao lưu cài đặt", + "backUpSettingsNow": "Sao lưu ngay", + "settingsBackedUp": "Cài đặt hiện tại đã được sao lưu.", + "restoreSettings": "Khôi phục cài đặt", + "restoreSettingsFromX": "Bạn có muốn thay thế cài đặt hiện tại bằng bản sao lưu từ {0} không?\n\nCài đặt hiện tại của bạn sẽ được sao lưu trước. Một số thay đổi chỉ có hiệu lực sau khi khởi động lại Subtitle Edit.", + "settingsRestored": "Cài đặt đã được khôi phục. Vui lòng khởi động lại Subtitle Edit để mọi thay đổi có hiệu lực.", + "settingsRestoreFailed": "Không thể khôi phục cài đặt: {0}", + "settingsBackupNotValid": "bản sao lưu không phải là tệp cài đặt hợp lệ, nên cài đặt hiện tại của bạn được giữ nguyên", + "deleteAllSettingsBackups": "Bạn có muốn xóa tất cả các tệp sao lưu cài đặt không?" }, "propertiesDCinema": { "title": "Thuộc tính D-Cinema SMPTE", @@ -1504,22 +1540,16 @@ "editRate": "Tốc độ chỉnh sửa", "timeCodeRate": "Tốc độ mã thời gian", "startTime": "Thời gian bắt đầu", - "font": "Phông chữ", "fontId": "ID phông chữ", "fontUri": "URI phông chữ", - "generate": "Tạo", - "fontColor": "Màu phông chữ", "chooseColor": "Chọn màu", "fontEffect": "Hiệu ứng phông chữ", "effectColor": "Màu hiệu ứng", - "fontSize": "Cỡ phông chữ", "topBottomMargin": "Lề trên/dưới", "fadeUpTime": "Thời gian hiện dần", "fadeDownTime": "Thời gian mờ dần", - "frames": "Khung hình", "zPosition": "Vị trí Z", - "zPositionHelp": "Số dương đẩy văn bản ra xa, số âm đưa văn bản lại gần, nếu vị trí Z bằng không thì là 2D", - "export": "Xuất..." + "zPositionHelp": "Số dương đẩy văn bản ra xa, số âm đưa văn bản lại gần, nếu vị trí Z bằng không thì là 2D" }, "webVtt": { "setStylesForSelectedLinesTitle": "Đặt kiểu WebVTT cho các dòng đã chọn", @@ -1552,20 +1582,66 @@ "compareOnlyInOneFile": "Chỉ có trong một tệp", "compareTextOrTimeDifference": "Khác biệt văn bản/thời gian", "compareNumberDifference": "Khác biệt số thứ tự", - "ignoreLineBreaks": "Bỏ qua ngắt dòng", "ignoreWhitespace": "Bỏ qua khoảng trắng", "ignoreWhitespaceHint": "Các dòng chỉ khác nhau ở khoảng trắng, tab hoặc ngắt dòng thì không tính là khác nhau", "ignoreFormatting": "Bỏ qua định dạng", - "onlyLookForDifferencesInText": "Chỉ tìm khác biệt trong văn bản", - "cannotCompareWithImageBasedSubtitles": "Không thể so sánh với phụ đề dạng hình ảnh", - "statisticsTitle": "Thống kê", "ignoreFormattingHint": "Các dòng chỉ khác nhau ở thẻ định dạng, như hoặc {\\an8}, thì không tính là khác nhau", "showOnlyDifferencesInText": "Chỉ các khác biệt trong văn bản", + "ignoreNumbering": "Bỏ qua đánh số", + "ignoreNumberingHint": "Các dòng chỉ khác nhau ở số thứ tự không được tính là khác nhau", + "ignoreTimeDifferenceMs": "Dung sai thời gian (ms)", + "ignoreTimeDifferenceMsHint": "Thời gian bắt đầu và kết thúc chênh lệch bằng hoặc ít hơn số mili giây này không được tính là khác nhau", + "compareDifferences": "Khác biệt", + "compareTextDifferences": "Khác biệt văn bản", + "compareCurrent": "Hiện tại", + "compareReference": "Tham chiếu", + "compareEditable": "Có thể chỉnh sửa", + "compareEditableHint": "Các thay đổi thực hiện ở đây sẽ được đưa về phụ đề khi bạn nhấn Áp dụng", + "compareReadOnly": "Chỉ đọc", + "compareTakeFromReference": "Lấy văn bản và thời gian từ tham chiếu", + "compareInsertFromReference": "Chèn dòng này vào phụ đề hiện tại", + "compareDeleteFromCurrent": "Xóa dòng này khỏi phụ đề hiện tại", + "compareTakeText": "Lấy văn bản", + "compareTakeTiming": "Lấy thời gian", + "compareEditLine": "Chỉnh sửa dòng", + "compareEditHint": "Ctrl+Enter để lưu, Esc để hủy", + "compareEdited": "Đã chỉnh sửa", + "compareOnePendingChange": "1 thay đổi đang chờ", + "compareXPendingChanges": "{0} thay đổi đang chờ", + "compareDiscardXChanges": "Hủy bỏ {0} thay đổi đã thực hiện trong So sánh?", + "compareXChangesApplied": "So sánh: đã áp dụng {0} thay đổi", + "compareChangeTextX": "#{0} văn bản từ tham chiếu", + "compareChangeTimingX": "#{0} thời gian từ tham chiếu", + "compareChangeTextAndTimingX": "#{0} văn bản và thời gian từ tham chiếu", + "compareChangeEditedX": "#{0} đã chỉnh sửa", + "compareChangeInsertedX": "#{0} đã chèn", + "compareChangeDeletedX": "#{0} đã xóa", + "compareSyncPoint": "Điểm đồng bộ", + "compareSyncPointHint": "Điểm đồng bộ - hai dòng này luôn được hiển thị thành một cặp, và các dòng phía trên và phía dưới được căn hàng riêng", + "compareSyncPickCurrent": "Điểm đồng bộ: dùng dòng hiện tại này", + "compareSyncPickReference": "Điểm đồng bộ: dùng dòng tham chiếu này", + "compareSyncRemove": "Xóa điểm đồng bộ", + "compareClearXSyncPoints": "Xóa các điểm đồng bộ ({0})", + "compareSyncCurrentPickedX": "Đã chọn dòng hiện tại #{0} làm điểm đồng bộ - chọn dòng tham chiếu tương ứng và nhấn Đồng bộ", + "compareSyncReferencePickedX": "Đã chọn dòng tham chiếu #{0} làm điểm đồng bộ - chọn dòng hiện tại tương ứng và nhấn Đồng bộ", + "compareSyncWithCurrentX": "Đồng bộ với dòng hiện tại #{0}", + "compareSyncWithReferenceX": "Đồng bộ với dòng tham chiếu #{0}", + "compareSyncApply": "Đồng bộ", + "compareSyncPointSetXY": "Đã đặt điểm đồng bộ: dòng hiện tại #{0} và dòng tham chiếu #{1} giờ được hiển thị thành một cặp", + "compareSyncAlreadyPairedXY": "Dòng hiện tại #{0} và dòng tham chiếu #{1} đã được hiển thị thành một cặp với cùng thời điểm bắt đầu - không có gì để đồng bộ", + "compareSyncPointShiftedXYZW": "Đã đặt điểm đồng bộ: dòng hiện tại #{0}-#{1} được dịch {2} để bắt đầu cùng dòng tham chiếu #{3}", + "compareChangeSyncShiftXYZ": "#{0}-#{1} được dịch {2} (điểm đồng bộ)", + "compareTimingDeltaHint": "Tham chiếu bắt đầu và kết thúc muộn hơn (+) hoặc sớm hơn (−) bao nhiêu so với dòng hiện tại", "loadXFromFile": "Tải \"{0}\" từ tệp", + "compareLoadSavedFile": "Tải tệp đã lưu", "saveCompareHtmlTitle": "Lưu tệp HTML so sánh", "pickMatroskaTrackX": "Chọn bản Matroska - {0}", "pickTransportStreamTrackX": "Chọn rãnh transport stream - {0}", + "chapters": "Chương", + "dvdVobFilesMissingX": "Thiếu tệp VOB (tìm thấy {0}%)", "pickMp4TrackX": "Chọn rãnh MP4 - {0}", + "pickMxfTrackX": "Chọn rãnh MXF - {0}", + "pickMpegTrackX": "Chọn rãnh MPEG - {0}", "rosettaProperties": "Thuộc tính Timed Text Rosetta IMSC", "rosettaFontSize": "Cỡ phông chữ (chiều cao hàng)", "propertyTimeBase": "Cơ sở thời gian", @@ -1575,7 +1651,6 @@ "propertyDefaultRegion": "Vùng mặc định", "propertyStyleAttributeName": "Tên thuộc tính kiểu", "propertyTimeCodeFormat": "Định dạng mã thời gian", - "propertyFileExtension": "Phần mở rộng tệp", "propertyTopOrigin": "Gốc phía trên", "propertyTopExtent": "Phạm vi phía trên", "propertyBottomOrigin": "Gốc phía dưới", @@ -1625,7 +1700,6 @@ "selectionIntersect": "Giao với vùng chọn" }, "multipleReplace": { - "title": "Thay thế hàng loạt", "editRule": "Chỉnh sửa quy tắc", "newRule": "Quy tắc mới", "editCategory": "Chỉnh sửa danh mục", @@ -1642,24 +1716,23 @@ "findWhat": "Tìm gì", "descriptionOptional": "Mô tả (tùy chọn)", "invalidRegularExpressionX": "Biểu thức chính quy không hợp lệ: {0}", - "regularExpressionTooSlowX": "Biểu thức chính quy đã dừng sau {0} giây - quy tắc bị bỏ qua, hãy thử mẫu đơn giản hơn" + "regularExpressionTooSlowX": "Biểu thức chính quy đã dừng sau {0} giây - quy tắc bị bỏ qua, hãy thử mẫu đơn giản hơn", + "moveToCategory": "Chuyển sang danh mục" }, "find": { "searchTextWatermark": "Tìm kiếm văn bản...", "wholeWord": "Cả từ", - "caseSensitive": "Phân biệt chữ hoa/thường", - "caseInsensitive": "Không phân biệt chữ hoa/thường", "findPrevious": "Tìm _trước", "findNext": "_Tìm tiếp", "replaceAndFindNext": "_Thay thế & tìm tiếp", "replaceAll": "Thay thế tất _cả", - "replaceTitle": "Thay thế", - "replaceWith": "Thay thế bằng", "replaceTextWatermark": "Thay thế bằng...", "replaceIn": "Thay thế/tìm trong", "replaceInTextAndOriginal": "Văn bản và văn bản gốc", "replaceInTextOnly": "Chỉ văn bản", - "replaceInOriginalOnly": "Chỉ văn bản gốc" + "replaceInOriginalOnly": "Chỉ văn bản gốc", + "replacedOneOccurrence": "Đã thay thế một lần xuất hiện", + "replacedXOccurrences": "Đã thay thế {0} lần xuất hiện" }, "regularExpressionContextMenu": { "wordBoundary": "Ranh giới từ (\\b)", @@ -1687,6 +1760,7 @@ "xLinesYSubtitles": "{0} dòng, {1} phụ đề", "xLinesYSubtitlesZErrors": "{0} dòng, {1} phụ đề, {2} lỗi", "couldNotParseAsX": "Không thể phân tích dưới dạng {0}", + "readAsXUseAsY": "Không thể đọc nguồn dưới dạng {1}, nhưng có thể đọc dưới dạng {0}.\nDùng các dòng này và tiếp tục lưu dưới dạng {1}?", "selectedXCharactersYLines": "Đã chọn: {0} ký tự trong {1} dòng", "replacedXOccurrences": "Đã thay thế {0} lần xuất hiện", "invalidRegularExpression": "Biểu thức chính quy không hợp lệ", @@ -1701,7 +1775,6 @@ "aiReview": { "title": "Duyệt bằng AI", "review": "Duyệt", - "stop": "Dừng", "editPrompt": "Chỉnh sửa lời nhắc...", "editPromptTitle": "Chỉnh sửa lời nhắc duyệt", "promptInfo": "Hướng dẫn gửi đến mô hình - {language} được thay bằng ngôn ngữ của phụ đề.", @@ -1712,7 +1785,6 @@ "reviewingLineXOfY": "Đang duyệt dòng {0} trên {1}...", "reviewDone": "Duyệt xong - {0} đề xuất trong {1} dòng", "noIssuesFound": "Không tìm thấy vấn đề nào", - "categoryAll": "Tất cả", "categorySpelling": "Chính tả", "categoryGrammar": "Ngữ pháp", "categoryPunctuation": "Dấu câu", @@ -1724,8 +1796,23 @@ "largeChangeWarning": "Thay đổi lớn - có vẻ như viết lại, hãy xem xét cẩn thận", "mismatchWarning": "Không giống bản gốc - có thể thuộc về một dòng khác", "engineError": "Không thể kết nối đến công cụ AI: {0}", - "setupHint": "Vui lòng chọn công cụ và mô hình trong Công cụ -> Duyệt bằng AI trước.", - "playCurrentLineHint": "Phát dòng đã chọn trong trình phát video và tạm dừng ở cuối dòng (cũng có thể nhấp đúp vào một dòng)" + "playCurrentLineHint": "Phát dòng đã chọn trong trình phát video và tạm dừng ở cuối dòng (cũng có thể nhấp đúp vào một dòng)", + "editAfterHint": "Nhấn vào văn bản của hàng đã chọn để chỉnh sửa bản sửa trước khi áp dụng (Enter để lưu, Escape để hủy)", + "template": "Mẫu", + "templatePlaceholder": "Chọn một mẫu để thay thế lời nhắc", + "templateStandard": "Hiệu đính tiêu chuẩn", + "templateSpeechToText": "Dọn dẹp kết quả chuyển giọng nói thành văn bản", + "templateMachineTranslation": "Sửa bản dịch máy", + "templateSdh": "SDH / người khiếm thính", + "referenceContext": "Ngữ cảnh tham chiếu", + "referenceContextInfo": "Tùy chọn. Tên, nhân vật, bảng thuật ngữ, tóm tắt hoặc ghi chú văn phong - mô hình coi đây là căn cứ chuẩn cho tên và thuật ngữ. Được gửi kèm mỗi lô.", + "referenceContextPlaceholder": "Tên: Kaito, dì Mei, Tiến sĩ Hollis\nThuật ngữ: Shinkansen, lõi warp\nTóm tắt: Kaito đến Tokyo để tìm người cha mất tích.", + "generateContext": "Tạo bằng AI", + "generateContextHint": "Để công cụ AI đã chọn đọc phụ đề và soạn thảo tên, thuật ngữ và tóm tắt - hãy kiểm tra kết quả trước khi dùng", + "generatingContextPartXOfY": "Đang đọc phần {0}/{1}...", + "generatingContextSynopsis": "Đang viết tóm tắt...", + "replaceContextQuestion": "Thay thế ngữ cảnh tham chiếu hiện tại?", + "noContextGenerated": "Công cụ AI không trả về thông tin tham chiếu nào dùng được." }, "aiAssistant": { "title": "Trợ lý AI", @@ -1743,22 +1830,14 @@ "showReasoning": "Hiển thị lập luận của mô hình" }, "fixCommonErrors": { - "title": "Sửa lỗi thường gặp", - "step1": "Bước 1/2 - Chọn lỗi cần sửa", - "whatToFix": "Nội dung cần sửa", - "example": "Ví dụ", - "selectAll": "Chọn tất cả", - "inverseSelection": "Đảo vùng chọn", "back": "< &Quay lại", "next": "&Tiếp >", - "step2": "Bước 2/2 - Xác nhận các sửa lỗi", "fixes": "Sửa lỗi", "ruleDetailsDotDotDot": "Chi tiết quy tắc...", "showOnlyThisRule": "Chỉ hiển thị các sửa lỗi của quy tắc này", "appliedRulesForLineX": "Quy tắc đã áp dụng cho dòng {0}", "ruleX": "Quy tắc: {0}", "log": "Nhật ký", - "function": "Chức năng", "removedEmptyLine": "Xóa dòng trống", "removedEmptyLineAtTop": "Xóa dòng trống ở đầu", "removedEmptyLineAtBottom": "Xóa dòng trống ở cuối", @@ -1779,13 +1858,13 @@ "removeLineBreaksPixelWidth": "Gộp phụ đề có thể vừa một dòng (độ rộng pixel)", "fixUppercaseIInsideLowercaseWords": "Sửa chữ 'i' hoa trong từ viết thường (lỗi OCR)", "fixDoubleApostrophes": "Sửa hai dấu nháy đơn ('') thành một dấu nháy kép (\")", + "fixMisreadQuotes": "Sửa dấu nháy đơn bị đọc nhầm thành dấu nháy kép (OCR, tiếng Anh)", "addPeriods": "Thêm dấu chấm sau dòng khi dòng kế tiếp bắt đầu bằng chữ hoa", "startWithUppercaseLetterAfterParagraph": "Bắt đầu bằng chữ hoa sau đoạn", "startWithUppercaseLetterAfterPeriodInsideParagraph": "Bắt đầu bằng chữ hoa sau dấu chấm trong đoạn", "startWithUppercaseLetterAfterColon": "Bắt đầu bằng chữ hoa sau dấu hai chấm/chấm phẩy", "fixLowercaseIToUppercaseI": "Sửa chữ 'i' thường đứng riêng thành 'I' (tiếng Anh)", "fixCommonOcrErrors": "Sửa lỗi OCR thường gặp (dùng danh sách thay thế OCR)", - "commonOcrErrorsFixed": "Lỗi OCR thường gặp đã sửa (dùng tệp OcrReplaceList): {0}", "removeSpaceBetweenNumber": "Xóa khoảng trắng giữa các số", "breakDialogsOnOneLine": "Tách hội thoại trên một dòng", "removeDialogFirstInNonDialogs": "Xóa gạch đầu dòng ở dòng đầu cho câu không phải hội thoại", @@ -1802,18 +1881,17 @@ "fixCommasExample": ",, -> ,", "removeLineBreaksExample": "Foo
bar! -> Foo bar!", "fixDoubleApostrophesExample": "''Has double single quotes'' -> \"Has single double quote\"", + "fixMisreadQuotesExample": "\"Hello' -> \"Hello\"", "addPeriodsExample": "Hello world -> Hello world.", "startWithUppercaseLetterAfterParagraphExample": "p1: Foobar! || p2: foobar! -> p1: Foobar! || p2: Foobar!", "startWithUppercaseLetterAfterPeriodInsideParagraphExample": "Hello there! how are you? -> Hello there! How are you?", "startWithUppercaseLetterAfterColonExample": "Speaker: hello world! -> Speaker: Hello world!", "removeHyphensSingleLineExample": "- Foobar. -> Foobar.", "fix3PlusLinesExample": "Foo
bar
baz! -> Foo bar baz!", - "xMissingQuotesAdded": "Số dấu nháy kép bị thiếu đã thêm: {0}", "fix3PlusLine": "Sửa phụ đề có hơn hai dòng", "fix3PlusLines": "Sửa các phụ đề có hơn hai dòng", "analysing": "Đang phân tích...", "nothingToFix": "Không có gì cần sửa :)", - "fixesFoundX": "Số sửa lỗi tìm thấy: {0}", "xFixesApplied": "Số sửa lỗi đã áp dụng: {0}", "xFixesYSelected": "{0} sửa lỗi - {1} đã chọn", "nothingFixableBut": "Không thể tự động sửa gì. Phụ đề vẫn có lỗi - xem nhật ký để biết chi tiết", @@ -1822,7 +1900,6 @@ "fixFirstLetterToUppercaseAfterParagraph": "Sửa chữ đầu thành chữ hoa sau đoạn", "mergeShortLine": "Gộp dòng ngắn (một câu)", "mergeShortLineAll": "Gộp dòng ngắn (tất cả trừ hội thoại)", - "unbreakShortLinePixelWidth": "Gộp dòng ngắn (độ rộng pixel)", "breakLongLine": "Ngắt dòng dài", "fixLongDisplayTime": "Sửa thời gian hiển thị quá dài", "fixInvalidItalicTag": "Sửa thẻ in nghiêng không hợp lệ", @@ -1845,7 +1922,6 @@ "fixUppercaseIInsideLowercaseWord": "Sửa chữ 'i' hoa trong từ viết thường", "fixMissingPeriodAtEndOfLine": "Thêm dấu chấm bị thiếu ở cuối dòng", "refreshFixes": "Làm mới các sửa lỗi khả dụng", - "applyFixes": "Áp dụng các sửa lỗi đã chọn & đóng", "autoBreak": "Ngắt dòng tự &động", "unbreak": "&Gộp dòng", "fixDoubleDash": "Sửa '--' -> '...'", @@ -1864,8 +1940,6 @@ "fixSpaceBetweenNumbersExample": "1 100 -> 1100", "fixDialogsOneLineExample": "Hi John! - Hi Ida! -> Hi John!
- Hi Ida!", "removeDialogFirstInNonDialogsExample": "- How are you? -> How are you?", - "selectDefault": "Chọn mặc định", - "setDefault": "Đặt các sửa lỗi hiện tại làm mặc định", "fixContinuationStyleX": "Sửa kiểu câu nối tiếp: {0}", "fixUnnecessaryLeadingDots": "Xóa các dấu chấm thừa ở đầu", "fixCommonErrorsProfiles": "Hồ sơ sửa lỗi thường gặp", @@ -1873,7 +1947,6 @@ "searchRulesDotDotDot": "Tìm quy tắc...", "goToApplyFixes": "Đến bước áp dụng sửa lỗi", "backToFixList": "Quay lại danh sách sửa lỗi", - "applyFixesAndClose": "Áp dụng sửa lỗi & đóng", "fixCommonOcrErrorsStep2": "Sửa lỗi thường gặp, bước 2 (áp dụng sửa lỗi)", "fixCommonOcrErrorsStep2FixesFoundX": "Sửa lỗi thường gặp, bước 2 - Số sửa lỗi tìm thấy: {0}", "action": "Hành động", @@ -1886,18 +1959,11 @@ "adjustDurations": { "title": "Điều chỉnh thời lượng", "adjustVia": "Điều chỉnh theo", - "seconds": "Giây", - "percent": "Phần trăm", "recalculate": "Tính lại", "addSeconds": "Thêm giây", - "setAsPercent": "Đặt theo phần trăm của thời lượng", "note": "Lưu ý: Thời gian hiển thị sẽ không chồng lấn thời gian bắt đầu của văn bản kế tiếp", "fixed": "Cố định", - "milliseconds": "Mili giây", "extendOnly": "Chỉ kéo dài", - "enforceDurationLimits": "Bắt buộc thời lượng tối thiểu và tối đa", - "checkShotChanges": "Không kéo dài qua điểm chuyển cảnh", - "batchCheckShotChanges": "Tôn trọng điểm chuyển cảnh (nếu có)", "recalculateRequiresOcrNote": "Không thể tính lại — một hoặc nhiều phụ đề không có văn bản OCR. Hãy chạy OCR trước." }, "applyDurationLimits": { @@ -1905,6 +1971,8 @@ "fixMinDurationMs": "Sửa thời lượng tối thiểu (ms)", "doNotGoPastShotChange": "Không vượt qua điểm chuyển cảnh", "fixMaxDurationMs": "Sửa thời lượng tối đa (ms)", + "fixMinDurationFrames": "Sửa thời lượng tối thiểu (khung hình)", + "fixMaxDurationFrames": "Sửa thời lượng tối đa (khung hình)", "minimumDurationMilliseconds": "Thời lượng tối thiểu (mili giây):", "maximumDurationMilliseconds": "Thời lượng tối đa (mili giây):", "maxDurationShouldBeHigherThanMinDuration": "Thời lượng tối đa phải lớn hơn thời lượng tối thiểu", @@ -1924,12 +1992,8 @@ }, "beautifyTimeCodes": { "title": "Làm đẹp mã thời gian", - "beautifySettings": "Cài đặt làm đẹp", "snapToFrames": "Bám theo khung hình", - "frameGap": "Khoảng cách khung hình", "minDuration": "Thời lượng tối thiểu (ms)", - "shotChangeThreshold": "Ngưỡng chuyển cảnh (ms)", - "shotChangeOffset": "Độ lệch chuyển cảnh (khung hình)", "original": "Gốc", "beautified": "Đã làm đẹp", "subtitlesCount": "Phụ đề", @@ -1958,13 +2022,80 @@ "timeCodesExtractFailed": "Không thể đọc mã thời gian từ video", "timeCodesHint": "Bám cue vào thời điểm khung hình thực của video thay vì giả định tốc độ khung hình không đổi. Cần đọc toàn bộ video một lần; kết quả được lưu vào bộ nhớ đệm." }, + "improveTimeCodes": { + "title": "Cải thiện mã thời gian (căn chỉnh cưỡng bức)", + "aligner": "Bộ căn chỉnh", + "alignerHint": "Mô hình lắng nghe từng dòng trong âm thanh. Các lựa chọn tốt nhất cho ngôn ngữ của phụ đề được liệt kê đầu tiên.", + "maxShift": "Độ dịch tối đa (giây)", + "maxShiftHint": "Mức phụ đề có thể bị lệch đồng bộ. Dòng nào có thời điểm bắt đầu phải dịch xa hơn mức này sẽ được giữ nguyên và đánh dấu. Dù giá trị là bao nhiêu, một dòng đơn lẻ muốn dịch xa hơn nửa giây so với các dòng xung quanh sẽ không bị dịch một cách mù quáng: nó đi theo các dòng lân cận khi tất cả chúng đều dịch, nếu không nó được đưa ra ở trạng thái bỏ chọn để bạn kiểm tra.", + "adjustStartTimes": "Điều chỉnh thời gian bắt đầu", + "adjustEndTimes": "Điều chỉnh thời gian kết thúc", + "isolateSpeech": "Tách riêng giọng nói trước (chậm)", + "isolateSpeechHint": "Loại bỏ nhạc và hiệu ứng âm thanh trước khi căn chỉnh, để bộ căn chỉnh chỉ nghe thấy lời thoại. Hữu ích với các dòng được nói trên nền nhạc lớn hoặc cảnh hành động, nhưng mất khoảng thời gian bằng chính âm thanh nếu có GPU - và lâu hơn nhiều lần nếu không có.", + "isolatingSpeech": "Đang tách riêng giọng nói... (việc này mất một lúc)", + "isolateSpeechFailed": "Không thể tách riêng giọng nói - đã căn chỉnh theo âm thanh gốc thay thế.", + "checkWithSpeechToText": "Kiểm tra bằng chuyển giọng nói thành văn bản", + "checkWithSpeechToTextHint": "Đồng thời phiên âm âm thanh và kiểm tra mọi lần dịch chuyển so với nơi các từ thực sự được nghe thấy. Các dịch chuyển lớn được giọng nói xác nhận sẽ được đánh dấu chọn sẵn, còn các dịch chuyển rời xa nơi dòng được nghe thấy sẽ để bỏ chọn để bạn kiểm tra. Cũng hiển thị tỷ lệ mỗi dòng được nghe thấy - tỷ lệ thấp nghĩa là văn bản khác với lời nói. Phụ đề bị lệch đồng bộ nhiều hơn độ dịch tối đa - lệch cố định, trôi do sai tốc độ khung hình, hoặc nhảy ở chỗ cắt cảnh - sẽ được đồng bộ theo các từ nghe được trước. Dùng Crisp ASR Parakeet, mất vài phút cho một bộ phim dài nếu có GPU.", + "checkWithSpeechToTextNotAvailableX": "Không có kiểm tra chuyển giọng nói thành văn bản cho {0}", + "transcribingToCheck": "Đang phiên âm để kiểm tra căn chỉnh...", + "speechToTextFailed": "Chuyển giọng nói thành văn bản thất bại - mã thời gian không được kiểm tra đối chiếu.", + "speechToTextX": "Chuyển giọng nói thành văn bản: {0}", + "speechToTextConfirmedXDisputedY": "Giọng nói: {0} xác nhận, {1} không khớp", + "hardlyHeardX": "Chuyển giọng nói thành văn bản chỉ nghe được {0}% số từ của phụ đề. Phụ đề có đúng ngôn ngữ được nói không? Bộ căn chỉnh không thể đặt một bản dịch.", + "heard": "Nghe được", + "heardHint": "Tỷ lệ dòng mà chuyển giọng nói thành văn bản nghe được. Tỷ lệ thấp nghĩa là văn bản khác với lời nói - nơi bộ căn chỉnh dễ sai nhất.", + "statusConfirmedBySpeech": "Được giọng nói xác nhận", + "statusDisputedBySpeech": "Nghe thấy ở vị trí cũ", + "statusMovedWithSync": "Dịch theo đồng bộ", + "syncedFirstXY": "Đồng bộ trước: {0} giây ở đầu, {1} giây ở cuối", + "showSpeechOnly": "Chỉ hiện giọng nói", + "showSpeechOnlyHint": "Vẽ dạng sóng đã căn chỉnh từ âm thanh đã loại bỏ nhạc và hiệu ứng âm thanh; dạng sóng gốc giữ nguyên để so sánh. Có sẵn khi giọng nói đã được tách riêng - tại đây, hoặc bằng \"Chỉ hiện giọng nói\" trong dạng sóng của cửa sổ chính.", + "align": "Căn chỉnh", + "original": "Gốc", + "aligned": "Đã căn chỉnh", + "intro": "Chọn bộ căn chỉnh và nhấn \"Căn chỉnh\" để lắng nghe từng dòng trong âm thanh và làm chính xác mã thời gian của nó.", + "extractingAudio": "Đang trích xuất âm thanh...", + "aligningBatchXOfY": "Đang căn chỉnh... lô {0}/{1}", + "extractAudioFailed": "Không thể trích xuất âm thanh bằng ffmpeg.", + "modelWillBeDownloaded": "{0} sẽ được tải xuống", + "summaryXRetimedYKeptZSkipped": "Đã chỉnh thời gian: {0} · Giữ nguyên: {1} · Không có giọng nói: {2}", + "meanShiftX": "Độ dịch trung bình: {0} ms", + "toCheckX": "Cần kiểm tra: {0}", + "largeMovesToCheckX": "{0} dòng sẽ tự dịch chuyển rất xa và được để bỏ chọn - hãy nghe chúng (F5 phát dòng đã căn chỉnh, Shift+F5 phát bản gốc) và đánh dấu những dòng đúng.", + "linesToCheckX": "{0} dòng được để bỏ chọn để kiểm tra - hãy nghe chúng (F5 phát dòng đã căn chỉnh, Shift+F5 phát bản gốc) và đánh dấu những dòng đúng.", + "changeXOfY": "Thay đổi {0}/{1}", + "previousChange": "Thay đổi trước", + "nextChange": "Thay đổi tiếp theo", + "startShift": "Bắt đầu", + "endShift": "Kết thúc", + "statusRetimed": "Đã chỉnh thời gian", + "statusMovedWithNeighbours": "Dịch theo các dòng lân cận", + "statusLargeMoveUnconfirmed": "Dịch chuyển lớn - hãy nghe, đánh dấu để áp dụng", + "statusUnchanged": "Đã đúng vị trí", + "statusNoSpeech": "Không có giọng nói", + "statusShiftTooLarge": "Giữ nguyên - độ dịch quá lớn", + "statusFailed": "Giữ nguyên - bộ căn chỉnh thất bại", + "statusAdjustedByHand": "Điều chỉnh thủ công", + "alignedWaveformHint": "Kéo một dòng để di chuyển nó, hoặc kéo các cạnh để thay đổi thời điểm bắt đầu hoặc kết thúc.", + "playLine": "Phát dòng", + "undoAdjustment": "Hoàn tác điều chỉnh", + "playPause": "Phát / tạm dừng", + "playAligned": "Đã căn chỉnh", + "playAlignedHint": "Phát dòng đã chọn với mã thời gian đã căn chỉnh", + "apply": "Áp dụng", + "applyHint": "Bỏ chọn một dòng để giữ mã thời gian gốc. Nhấp đúp vào một dòng để phát nó. Các dòng cũng có thể được di chuyển hoặc thay đổi kích thước trong dạng sóng đã căn chỉnh." + }, "beautifyTimeCodesProfile": { "title": "Chỉnh sửa hồ sơ làm đẹp mã thời gian", "loadPreset": "Tải cài đặt sẵn", "presetDefault": "Mặc định", "presetNetflix": "Netflix", "presetSdi": "SDI", - "general": "Chung", + "saveAsProfile": "Lưu thành hồ sơ...", + "deleteProfile": "Xóa hồ sơ", + "profileName": "Tên hồ sơ", + "overwriteProfileX": "Đã tồn tại hồ sơ tên \"{0}\". Ghi đè?", + "deleteProfileX": "Xóa hồ sơ \"{0}\"?", "gap": "Khoảng cách", "gapSuffix": "khung hình (cũng cập nhật các trường khoảng cách khác 0 bên dưới)", "milliseconds": "ms", @@ -1993,9 +2124,12 @@ "hintChaining": "Cách xử lý các phụ đề gần chạm nhau — kéo dài chúng lại với nhau, hoặc đẩy ra xa, tùy có điểm chuyển cảnh nằm giữa hay không." }, "bridgeGaps": { - "title": "Nối khoảng trống", "bridgeGapsSmallerThan": "Nối khoảng trống nhỏ hơn (ms)", "minGap": "Khoảng cách tối thiểu (ms)", + "bridgeGapsSmallerThanFrames": "Nối khoảng trống nhỏ hơn (khung hình)", + "minGapFrames": "Khoảng cách tối thiểu (khung hình)", + "calculateBridgeGapsSmallerThanDotDotDot": "Tính giới hạn nối khoảng trống từ tốc độ khung hình...", + "useXMsAsBridgeGapsSmallerThan": "Nối các khoảng trống nhỏ hơn \"{0}\" mili giây?", "numberOfSmallGapsBridgedX": "Số khoảng trống nhỏ đã nối: {0}", "percentFoPrevious": "Khoảng cách cho phụ đề trước (%)", "gapChange": "Thay đổi khoảng cách" @@ -2007,8 +2141,6 @@ "sortBy": { "title": "Sắp xếp phụ đề", "sortOrder": "Thứ tự sắp xếp", - "sortByNumber": "Số", - "sortByStartTime": "Thời gian bắt đầu", "sortByEndTime": "Thời gian kết thúc" }, "batchConvert": { @@ -2031,6 +2163,7 @@ "addColor": "Thêm màu", "deleteLinesWithSpecificActorsOrStyles": "Xóa các dòng có diễn viên hoặc kiểu (ngăn cách nhiều mục bằng dấu phẩy)", "useSourceStylesIfPossible": "Dùng kiểu của nguồn nếu có thể", + "keepSourceEmbeddedFonts": "Giữ phông chữ nhúng của nguồn (phần cuối)", "editStyles": "Chỉnh sửa kiểu", "editProperties": "Chỉnh sửa thuộc tính", "editAttachments": "Chỉnh sửa tệp đính kèm", @@ -2051,7 +2184,6 @@ "assaChangeStyleFromStyle": "Đổi kiểu từ", "assaChangeStyleToStyle": "thành", "assaChangeStyleImportStyle": "Nhập kiểu...", - "assaChangeStyleImportedX": "Đã nhập: {0}", "assaChangeStyleTrimUnusedStyles": "Loại bỏ kiểu không dùng", "assaChangeStylePropertiesTitle": "Thay đổi thuộc tính kiểu", "assaChangeStylePropertiesInfo": "Thay đổi các trường đã chọn trong mọi kiểu của tệp, không đụng đến bản thân các kiểu.", @@ -2063,13 +2195,52 @@ "convertColorsToDialogReBreakLines": "Ngắt dòng lại", "snapTimeCodesToFramesInfo": "Làm tròn mọi thời điểm bắt đầu và kết thúc về khung hình gần nhất. Tốc độ khung hình được đọc từ tệp video cùng tên với tệp phụ đề, trừ khi chọn tốc độ khung hình cố định.", "addFolderDotDotDot": "Thêm thư mục...", + "addFolderRecursiveDotDotDot": "Thêm thư mục (đệ quy)...", "selectFolderToConvert": "Chọn thư mục chứa các tệp cần chuyển đổi", "includeSubfolders": "Bao gồm thư mục con khi thêm một thư mục", + "videoFilesWhenAddingFolder": "Tệp video (mkv, mp4, ts...) khi thêm thư mục", + "videoFilesAsk": "Hỏi", + "videoFilesInclude": "Thêm các rãnh phụ đề của chúng", + "videoFilesSkip": "Bỏ qua", + "folderContainsXVideoFiles": "Thư mục chứa {0} tệp video (mkv, mp4, ts...).\n\nThêm cả các rãnh phụ đề nhúng của chúng, hay chỉ các tệp phụ đề?", + "addVideoFiles": "Thêm tệp video", + "skipVideoFiles": "Chỉ tệp phụ đề", + "doNotAskAgainVideoFiles": "Không hỏi lại (thay đổi trong Cài đặt chuyển đổi hàng loạt)", + "settingsSectionOutput": "Đầu ra", + "settingsSectionAddingFiles": "Thêm tệp", + "settingsSectionImageBasedSubtitles": "Phụ đề dạng hình ảnh (OCR)", + "settingsSectionOther": "Khác", "keepSourceFileTimestamp": "Giữ ngày/giờ của tệp nguồn cho tệp đầu ra", - "scanningFolderX": "Đang quét {0}..." + "preventSleepWhileConverting": "Ngăn máy tính chuyển sang chế độ ngủ khi đang chuyển đổi", + "scanningFolderX": "Đang quét {0}...", + "transportStreamSettings": "Cài đặt Transport Stream", + "transportStreamSettingsDotDotDot": "Cài đặt Transport Stream...", + "transportStreamSettingsInfo": "Áp dụng cho các rãnh phụ đề trích xuất từ tệp Transport Stream (.ts/.m2ts). Vị trí và kích thước video chỉ ảnh hưởng đến các rãnh hình ảnh DVB được xuất sang định dạng dựa trên hình ảnh.", + "transportStreamOverrideXPosition": "Ghi đè vị trí X gốc", + "transportStreamOverrideYPosition": "Ghi đè vị trí Y gốc", + "transportStreamOverrideVideoSize": "Ghi đè kích thước video gốc", + "transportStreamBottomMargin": "Lề dưới", + "transportStreamFileNameEnding": "Phần cuối tên tệp", + "transportStreamFileNameEndingInfo": "Được thêm trước phần mở rộng cho mỗi rãnh được trích xuất. Để trống để dùng hậu tố ngôn ngữ thông thường.", + "transportStreamOnlyTeletext": "Chỉ teletext", + "transportStreamGetSizeFromVideo": "Lấy kích thước từ video...", + "twoLetterLanguageCodeUppercase": "Mã ngôn ngữ hai chữ cái (viết hoa)", + "threeLetterLanguageCodeUppercase": "Mã ngôn ngữ ba chữ cái (viết hoa)", + "formatContainsDotDotDot": "Định dạng chứa...", + "extensionIsDotDotDot": "Phần mở rộng là...", + "statusIsError": "Trạng thái là lỗi", + "forcedTracksOnly": "Chỉ các rãnh bắt buộc (forced)", + "xConvertedYFailedInZ": "Đã chuyển đổi {0:#,###,##0}, thất bại {1:#,###,##0} trong {2}", + "xNotProcessed": "{0:#,###,##0} chưa được xử lý", + "removeUnicodeControlCharactersTitle": "Xóa ký tự điều khiển Unicode", + "removeUnicodeControlCharactersInfo": "Xóa các ký tự điều khiển hướng Unicode vô hình (LRM, RLM, LRE, RLE, PDF, LRO và RLO) khỏi văn bản, và thay thế dấu cách không ngắt bằng dấu cách thông thường.", + "preset": "Cấu hình sẵn", + "savePresetDotDotDot": "Lưu cấu hình sẵn...", + "deletePreset": "Xóa cấu hình sẵn", + "presetName": "Tên cấu hình sẵn", + "deletePresetX": "Xóa cấu hình sẵn \"{0}\"?" }, "changeCasing": { - "title": "Đổi chữ hoa/thường", "fixNames": "Sửa tên riêng", "namesX": "Tên: {0:#,##0}", "hitsX": "Kết quả khớp: {0:#,##0}", @@ -2080,9 +2251,7 @@ "allUppercase": "Tất cả chữ hoa", "allLowercase": "Tất cả chữ thường" }, - "changeFormatting": { - "title": "Đổi định dạng" - }, + "changeFormatting": {}, "convertActors": { "title": "Chuyển đổi diễn viên", "convertActorFrom": "Chuyển diễn viên từ", @@ -2111,18 +2280,12 @@ "outputFormatAssa": "Advanced Sub Station Alpha (.ass)", "style1": "Kiểu 1", "style2": "Kiểu 2", - "outlineWidth": "Độ rộng viền", - "shadowWidth": "Độ rộng bóng", - "alignTop": "Trên", - "alignBottom": "Dưới", - "merge": "_Gộp", - "pleaseLoadTwoSubtitles": "Vui lòng tải hai phụ đề" + "merge": "_Gộp" }, "splitSubtitle": { "title": "Chia phụ đề", "numberOfEqualParts": "Số phần bằng nhau", "saveSplitParts": "_Lưu các phần đã chia", - "subtitleSplitIntoXParts": "Phụ đề đã được chia thành {0} phần.", "xPartsSavedInFormatYToFolder": "{0} phần đã lưu ở định dạng {1} vào thư mục:" }, "splitBreakLongLines": { @@ -2156,18 +2319,17 @@ "columnMerge": "Gộp", "columnFirst": "Dòng thứ nhất", "columnSecond": "Dòng thứ hai", - "columnMerged": "Đã gộp", - "selectAll": "Chọn tất cả", - "inverseSelection": "Đảo vùng chọn" + "columnMerged": "Đã gộp" }, "mergeLinesWithSameText": { - "title": "Gộp các dòng có cùng văn bản", "maxMsBetweenLines": "Số mili giây tối đa giữa các dòng", - "includeIncrementingLines": "Bao gồm các dòng có văn bản tăng dần" + "maxFramesBetweenLines": "Số khung hình tối đa giữa các dòng", + "includeIncrementingLines": "Bao gồm các dòng có văn bản tăng dần", + "includeRollUpCaptions": "Bao gồm phụ đề cuộn lên (roll-up)" }, "mergeLinesWithSameTimeCodes": { - "title": "Gộp các dòng có cùng mã thời gian", "maxMsDifference": "Chênh lệch tối đa (mili giây)", + "maxFramesDifference": "Chênh lệch tối đa (khung hình)", "makeDialog": "Tạo hội thoại" }, "netflixCheckAndFix": { @@ -2190,7 +2352,6 @@ "shotChanges": "Chuyển cảnh", "startNumberSpellOut": "Viết chữ số ở đầu câu", "textforHiUseBrackets": "Văn bản cho HI, dùng ngoặc vuông", - "frameRate": "Tốc độ khung hình", "twoFrameGrap": "Khoảng cách hai khung hình", "whiteSpace": "Khoảng trắng", "childrensProgram": "Chương trình thiếu nhi", @@ -2228,7 +2389,6 @@ "italicsFixed": "Đã sửa chữ nghiêng", "italicsNotAllowed": "Không cho phép chữ nghiêng", "bridgeGapsXToYFrames": "Khoảng cách 3-{0} khung hình => khoảng cách 2 khung hình", - "startNumberSpellOutComment": "Khi một số bắt đầu câu, nó luôn phải được viết bằng chữ", "maximumXCharactersPerSecond": "Tối đa {0} ký tự mỗi giây" }, "imageBasedEdit": { @@ -2237,10 +2397,8 @@ "resizeImagesDotDotDot": "Đổi kích thước ảnh...", "adjustBrightnessDotDotDot": "Điều chỉnh độ sáng...", "adjustAlphaDotDotDot": "Điều chỉnh alpha...", - "centerHorizontally": "Căn giữa theo chiều ngang", "cropImages": "Cắt ảnh", "importTimeCodes": "Nhập mã thời gian...", - "setTextForSubtitle": "Đặt văn bản cho phụ đề", "setText": "Đặt văn bản", "screenWidth": "Chiều rộng màn hình", "screenHeight": "Chiều cao màn hình", @@ -2271,15 +2429,31 @@ "letterboxCustom": "Chiều cao thanh tùy chỉnh", "barHeightPx": "Chiều cao thanh (px)", "titleSafePercent": "Vùng an toàn tiêu đề (%)", + "moveCaptions": "Di chuyển phụ đề", + "moveCaptionsDotDotDot": "Di chuyển phụ đề...", + "moveCaptionsTo": "Di chuyển phụ đề", + "moveCaptionsIntoBars": "Vào các dải đen letterbox (bên ngoài hình ảnh)", + "moveCaptionsIntoPicture": "Vào trong hình ảnh (ra khỏi các dải đen)", + "moveCaptionsOffset": "Khoảng cách từ cạnh (px)", + "letterboxBarHeightXPxYOfZCaptionsWillMove": "Chiều cao dải đen: {0} px - {1}/{2} phụ đề sẽ được di chuyển", + "removeFadeInOut": "Xóa mờ dần vào/ra", + "removeFadeInOutXLinesRemoved": "Đã gộp {0} dòng mờ dần vào các dòng chúng thuộc về", + "removeFadeInOutNothingFound": "Không tìm thấy dòng mờ dần vào/ra nào", "positionSummary": "{0}×{1} - {2} phụ đề - chiều cao thanh: {3} px", "xInActivePicture": "{0} trong hình", "xInTopBar": "{0} trong thanh trên", "xInBottomBar": "{0} trong thanh dưới", "noImageSubtitlesLoaded": "Chưa nạp phụ đề dạng ảnh nào", - "imageBasedFormatNotSupported": "Không tìm thấy/không hỗ trợ định dạng phụ đề dạng hình ảnh." + "imageBasedFormatNotSupported": "Không tìm thấy/không hỗ trợ định dạng phụ đề dạng hình ảnh.", + "frameRateDeclaredInFile": "Tốc độ khung hình khai báo trong tệp Blu-ray sup", + "frameRateDetectedFromTimeCodesX": "Tốc độ khung hình phát hiện từ mã thời gian Blu-ray sup (tệp khai báo {0})", + "frameRateFromVideo": "Tốc độ khung hình của video", + "frameRateCurrent": "Tốc độ khung hình hiện tại", + "frameRateSetManually": "Tốc độ khung hình đặt thủ công", + "frameRateUsageInfo": "Các bản xuất Blu-ray sup, BDN XML, DOST và FCP được ghi ở tốc độ khung hình này. Nó không thay đổi bất kỳ thời gian nào - dùng \"Thay đổi tốc độ khung hình\" cho việc đó.", + "useVideoFrameRateXInsteadOfY": "Tốc độ khung hình của video là {0}, nhưng phụ đề được đặt là {1}.\n\nDùng tốc độ khung hình của video ({0})?" }, "removeTextForHearingImpaired": { - "title": "Xóa văn bản dành cho người khiếm thính", "interjections": "Thán từ", "skipIfStartWith": "Bỏ qua nếu bắt đầu bằng", "removeTextBetween": "Xóa văn bản giữa", @@ -2298,7 +2472,6 @@ "removeInterjections": "Xóa thán từ", "linesFoundX": "Số dòng tìm thấy: {0}" }, - "pickAlignmentTitle": "Chọn căn chỉnh", "removeUnicodeCharacters": { "title": "Xóa/thay thế ký tự Unicode", "charactersFoundX": "Số ký tự Unicode tìm thấy: {0}", @@ -2308,13 +2481,13 @@ "pickFontNameInstalledFonts": "Phông chữ đã cài đặt", "pickFontNameCollectedFonts": "Phông chữ đã thu thập", "colorPickerTitle": "Chọn màu", + "colorPickerBrightness": "Độ sáng", "filterLayersTitle": "Lọc lớp để hiển thị", "filterLayersHideFromWaveform": "Ẩn khỏi dạng sóng/phổ âm", "filterLayersHideFromSubtitleGrid": "Ẩn khỏi lưới phụ đề", "filterLayersHideFromVideoPreview": "Ẩn khỏi xem trước video", "pickSubtitleFormat": "Chọn định dạng phụ đề", "pickSubtitleFormatImageBasedNoPreview": "Định dạng phụ đề dạng ảnh.\nBitmap được tạo trong quá trình chuyển đổi - không có xem trước văn bản.", - "pickLayerTitle": "Đặt lớp", "recentColors": "Màu gần đây", "fixCommonErrorsSettingsTitle": "Cài đặt sửa lỗi thường gặp" }, @@ -2359,59 +2532,51 @@ "correctWordsX": "Số từ đúng: {0}", "namesX": "Số tên: {0}", "addedToDictionaryX": "Đã thêm vào từ điển: {0}", - "doNotShowThisAgain": "Không hiển thị thông báo này nữa" + "doNotShowThisAgain": "Không hiển thị thông báo này nữa", + "editUseAlwaysList": "Chỉnh sửa danh sách \"Luôn dùng\"...", + "useAlwaysListTitle": "Kiểm tra chính tả - danh sách \"Luôn dùng\"", + "useAlwaysListHeader": "Danh sách \"Luôn dùng\"", + "useAlwaysListDescription": "Các từ mà kiểm tra chính tả tự động thay thế, được lưu bởi \"Thay đổi tất cả\" và \"Luôn dùng\". Một cặp chỉ được dùng khi từ đó bị sai chính tả.", + "useAlwaysWord": "Từ sai chính tả", + "xPairs": "{0:#,##0} cặp", + "xUnusedPairs": "{0:#,##0} chưa từng dùng", + "useAlwaysUnusedHint": "Chưa từng dùng - từ này được viết đúng chính tả (hoặc là tên hay từ của người dùng), nên kiểm tra chính tả không bao giờ đánh dấu nó", + "removeUnused": "Xóa mục không dùng", + "useAlwaysEmpty": "Chưa có cặp \"Luôn dùng\" nào cho ngôn ngữ này", + "useAlwaysEmptyHint": "Các cặp được thêm khi bạn nhấn \"Thay đổi tất cả\" hoặc \"Luôn dùng\" trong kiểm tra chính tả.", + "useAlwaysNoMatch": "Không có cặp nào khớp với tìm kiếm", + "useAlwaysSameWord": "Từ thay thế phải khác với từ gốc.", + "useAlwaysRememberOff": "\"Nhớ danh sách 'Luôn dùng'\" đang tắt trong Cài đặt, nên kiểm tra chính tả không dùng hoặc lưu danh sách này." }, "video": { "burnIn": { "title": "Tạo video với phụ đề ghi cứng", - "infoAssaOff": "Lưu ý: Hỗ trợ định kiểu Advanced SubStation Alpha.", - "infoAssaOn": "Lưu ý: Định kiểu Advanced SubStation Alpha sẽ được sử dụng :)", - "xGeneratedWithBurnedInSubsInX": "Đã tạo \"{0}\" với phụ đề ghi cứng trong {1}.", - "timeRemainingMinutes": "Thời gian còn lại: {0} phút", - "timeRemainingOneMinute": "Thời gian còn lại: Một phút", - "timeRemainingSeconds": "Thời gian còn lại: {0} giây", - "timeRemainingAFewSeconds": "Thời gian còn lại: Vài giây", - "timeRemainingMinutesAndSeconds": "Thời gian còn lại: {0} phút {1} giây", - "timeRemainingOneMinuteAndSeconds": "Thời gian còn lại: Một phút {0} giây", - "targetFileName": "Tên tệp đích: {0}", "targetFileSize": "Kích thước tệp đích (yêu cầu mã hóa 2 lượt)", "fileSizeMb": "Kích thước tệp tính bằng MB", "matchSourceVideoSize": "Khớp kích thước video nguồn", - "passX": "Lượt {0}", - "encoding": "Mã hóa", "bitRate": "Tốc độ bit", "totalBitRateX": "Tổng tốc độ bit: {0}", "sampleRate": "Tần số lấy mẫu", "audio": "Âm thanh", - "stereo": "Stereo", "preset": "Cấu hình sẵn", + "tune": "Tinh chỉnh (tune)", "pixelFormat": "Định dạng điểm ảnh", "crf": "CRF", - "tuneFor": "Tinh chỉnh cho", - "alignRight": "Căn phải", - "getStartPosition": "Lấy vị trí bắt đầu", - "getEndPosition": "Lấy vị trí kết thúc", "useSource": "Dùng nguồn", - "useSourceFolder": "Dùng thư mục nguồn", - "useSourceResolution": "Dùng độ phân giải nguồn", - "outputSettings": "Tệp/thư mục đầu ra...", "fontSizeFactor": "Hệ số cỡ chữ", "boxType": "Kiểu hộp", - "fixRightToLeft": "Sửa phải-sang-trái", - "cut": "Cắt", "fromTime": "Từ thời điểm", "toTime": "Đến thời điểm", "audioEncoding": "Mã hóa âm thanh", "outputProperties": "Thuộc tính đầu ra...", "videoFileSize": "Kích thước tệp video", "oneBox": "Một hộp", - "boxPerLine": "Hộp cho mỗi dòng", - "logoInfo": "Chọn một ảnh PNG và kéo để đặt vị trí trên video." + "logoInfo": "Chọn một ảnh PNG và kéo để đặt vị trí trên video.", + "noSubtitlesGenerateAnyway": "Không có dòng phụ đề nào để ghi vào video.\n\nTạo video không có phụ đề?", + "noSubtitlesInXItemsGenerateAnyway": "{0} mục trong lô không có dòng phụ đề nào để ghi vào video.\n\nTạo các video đó không có phụ đề?" }, "videoTransparent": { - "title": "Tạo video trong suốt với phụ đề", - "infoAssaOff": "Lưu ý: Hỗ trợ định kiểu Advanced SubStation Alpha.", - "infoAssaOn": "Lưu ý: Định kiểu Advanced SubStation Alpha sẽ được sử dụng :)" + "title": "Tạo video trong suốt với phụ đề" }, "audioToText": { "title": "Chuyển giọng nói thành văn bản", @@ -2419,6 +2584,21 @@ "translateToEnglish": "Dịch sang tiếng Anh", "transcribing": "Đang phiên âm...", "transcribingXOfY": "Đang phiên âm {0} trên {1}...", + "linesTranscribedXOfYFailedZ": "Đã phiên âm {0}/{1} dòng - {2} dòng thất bại hoặc không có giọng nói.\n\nCác dòng thất bại giữ nguyên văn bản hiện tại. Áp dụng các dòng đã phiên âm, hay thử lại các dòng thất bại (bạn có thể đổi công cụ hoặc cài đặt trước)?", + "linesTranscribedXOfYCancelled": "Đã hủy phiên âm sau {0}/{1} dòng.\n\nÁp dụng các dòng đã phiên âm? Các dòng khác giữ nguyên văn bản hiện tại.", + "noLinesTranscribed": "Không phiên âm được dòng nào trong số {0} dòng.\nVui lòng kiểm tra nhật ký công cụ để biết chi tiết.", + "applyTranscribedLines": "Áp dụng phần đã phiên âm", + "retryFailedLines": "Thử lại phần thất bại", + "isolateSpeech": "Tách riêng giọng nói (chậm)", + "isolateSpeechHint": "Loại bỏ nhạc và hiệu ứng âm thanh trước khi phiên âm. Ít bỏ sót dòng hơn và thời gian chính xác hơn với âm thanh có nhạc lớn, nhưng mất khoảng thời gian bằng chính âm thanh nếu có GPU - và lâu hơn nhiều lần nếu không có.", + "isolatingSpeech": "Đang tách riêng giọng nói...", + "isolateSpeechFailed": "Không thể tách riêng giọng nói - đang phiên âm âm thanh gốc thay thế.", + "detectSpeakers": "Phát hiện người nói", + "detectSpeakersHint": "Bắt đầu mỗi dòng bằng người nói, như \"(speaker 0)\", dùng tính năng phân biệt người nói Sortformer của NVIDIA (tối đa 8 người nói). Hoạt động tốt nhất với các backend tách dòng khi người nói thay đổi, như Parakeet và Cohere.", + "detectSpeakersNeedsNewerCrispAsr": "\"Phát hiện người nói\" cần Crisp ASR v{0} trở lên, nhưng đã cài v{1}.\n\nCập nhật Crisp ASR (nút bánh răng cạnh công cụ) hoặc tắt \"Phát hiện người nói\".", + "xNeedsNewerCrispAsr": "{0} cần Crisp ASR v{1} trở lên, nhưng đã cài v{2}.\n\nTải xuống và cài đặt bản cập nhật ngay?", + "vad": "VAD", + "vadHint": "Phát hiện hoạt động giọng nói: chỉ phần giọng nói được phiên âm, cắt ở chỗ người nói ngừng.\nTự động: Silero, và chỉ cho các công cụ cần nó (Cohere, Mega, Index-Echo).\nSilero, FireRedVAD, Whisper-VAD, WebRTC: luôn bật, cho mọi công cụ Crisp ASR. FireRedVAD chính xác nhất, Whisper-VAD cho ít đoạn cắt nhất và gọn nhất nhưng chậm nhất.\nIndex-Echo luôn dùng Silero. --vad, --vad-model hoặc --chunk-seconds trong cài đặt nâng cao được ưu tiên.", "inputLanguage": "Ngôn ngữ đầu vào", "advancedWhisperSettings": "Tham số chuyển giọng nói thành văn bản nâng cao", "downloadingSpeechToTextEngine": "Đang tải xuống công cụ chuyển giọng nói thành văn bản", @@ -2433,8 +2613,6 @@ "addCustomModelDotDotDot": "Thêm mô hình tùy chỉnh...", "customModelHelp": "Bạn có thể dùng mô hình của riêng mình: chọn tệp mô hình (whisper.cpp ggml '.bin') hoặc thư mục mô hình (thư mục faster-whisper có 'model.bin' bên trong). Nó sẽ được sao chép vào thư mục mô hình và thêm vào danh sách ở trên.", "viewToolsLogFile": "Xem tệp nhật ký công cụ", - "reDownloadX": "Tải lại {0}", - "downloadX": "Tải xuống {0}", "updateXTitle": "Cập nhật {0}?", "updateXMessage": "Đã có phiên bản mới hơn của {0}.{1}{1}Tải xuống và cài đặt bản cập nhật ngay?", "downloadingSpeechToTextModel": "Đang tải xuống mô hình chuyển giọng nói thành văn bản", @@ -2446,14 +2624,6 @@ "fixCasing": "Sửa chữ hoa/thường", "addPeriods": "Thêm dấu chấm", "changeUnderlineToColor": "Đổi gạch chân thành màu", - "cueBuilding": "Dựng cue (MLX Whisper)", - "cueRebuild": "Dựng lại cue từ dấu thời gian của từ", - "cueMaxChars": "Số ký tự tối đa mỗi cue", - "cueMaxSeconds": "Số giây tối đa mỗi cue", - "cueMaxCps": "Số ký tự tối đa mỗi giây", - "vocabularyPrompt": "Gợi ý từ vựng (tên, địa danh, thuật ngữ)", - "beamSize": "Beam size", - "beamSizeNote": "0 giải mã nhanh nhất. 5 cho độ chính xác tốt nhất (như Faster Whisper) với thời gian nhiều hơn khoảng 20-30%. Giá trị trên 5 hiếm khi hữu ích.", "removeNonSpeechLines": "Xóa các dòng không phải lời nói", "removeNonSpeechLinesHint": "Bỏ các dòng chỉ mô tả âm thanh, như \"[Music]\" hoặc \"(waves crashing)\"", "removeRepeatedLines": "Xóa các dòng lặp lại", @@ -2489,14 +2659,11 @@ }, "textToSpeech": { "title": "Chuyển văn bản thành giọng nói", - "textToSpeechEngine": "Công cụ chuyển văn bản thành giọng nói", "reviewAudioSegments": "TTS - Duyệt các đoạn âm thanh", "reviewAudioSegmentsHistory": "TTS - Duyệt lịch sử âm thanh", "stability": "Độ ổn định", "similarity": "Độ tương đồng", "speakerBoost": "Tăng cường người nói", - "styleExaggeration": "Cường điệu phong cách", - "regenerateAudioSelectedLine": "Tạo lại âm thanh cho dòng đã chọn", "generateSpeechFromText": "Tạo giọng nói từ văn bản", "testVoice": "Thử giọng", "addAudioToVideoFile": "Thêm âm thanh vào tệp video", @@ -2515,6 +2682,19 @@ "videoEncodingSettings": "TTS - Cài đặt mã hóa video", "elevenLabsSettings": "TTS - Cài đặt ElevenLabs", "elevenLabsSettingsResetHint": "Đặt lại cài đặt ElevenLabs về giá trị mặc định", + "provider": "Nhà cung cấp", + "openAiCompatibleSettings": "TTS - Cài đặt tương thích OpenAI", + "customServerUrl": "URL máy chủ tùy chỉnh", + "customServerUrlHint": "Endpoint giọng nói đầy đủ của máy chủ tương thích OpenAI, dùng khi nhà cung cấp là \"Tùy chỉnh\", ví dụ http://localhost:8880/v1/audio/speech", + "customModels": "Mô hình tùy chỉnh", + "customModelsHint": "Tên các mô hình, phân cách bằng dấu phẩy, được đưa ra khi nhà cung cấp là \"Tùy chỉnh\"", + "customVoices": "Giọng tùy chỉnh", + "customVoicesHint": "Tên các giọng, phân cách bằng dấu phẩy, dùng bởi nhà cung cấp \"Tùy chỉnh\" và các mô hình OpenRouter không công bố danh sách giọng", + "instructions": "Hướng dẫn", + "instructionsHint": "Phong cách nói tùy chọn, ví dụ \"Nói bằng giọng điềm tĩnh, ấm áp\". Chỉ gửi đến các mô hình hỗ trợ (không phải tts-1/tts-1-hd)", + "openAiSpeedHint": "Tốc độ nói từ 0.25 đến 4.0 (1.0 là bình thường). Không phải mô hình nào cũng hỗ trợ", + "outputFormat": "Định dạng đầu ra", + "outputFormatHint": "Định dạng âm thanh yêu cầu từ máy chủ. \"Tự động\" yêu cầu MP3 và chuyển sang PCM cho các mô hình chỉ trả về PCM (ví dụ Gemini TTS)", "regenerateAudio": "Tạo lại âm thanh", "autoContinuePlaying": "Tự động phát tiếp", "playLine": "Phát dòng", @@ -2523,6 +2703,13 @@ "addingAudioToVideoFileDotDotDot": "Đang thêm âm thanh vào tệp video...", "preparingMergeDotDotDot": "Đang chuẩn bị gộp...", "importVoiceDotDotDot": "Nhập giọng nói...", + "renameVoiceDotDotDot": "Đổi tên giọng...", + "renameVoiceTitle": "Đổi tên giọng", + "voiceXCouldNotBeRenamedX": "Không thể đổi tên giọng '{0}': {1}", + "deleteVoiceDotDotDot": "Xóa giọng...", + "deleteVoiceTitle": "Xóa giọng", + "deleteVoiceXQuestion": "Xóa giọng '{0}'?\n\nBản ghi tham chiếu và các tệp của nó sẽ bị xóa khỏi ổ đĩa.", + "voiceXCouldNotBeDeletedX": "Không thể xóa giọng '{0}': {1}", "voiceImportSuccessTitle": "Đã nhập giọng nói", "voiceXImported": "Đã nhập giọng nói '{0}' thành công", "voiceXCouldNotBeImported": "Không thể nhập giọng '{0}' - xem nhật ký để biết chi tiết", @@ -2532,6 +2719,49 @@ "dropAudioFileHereToImportVoice": "Thả tệp âm thanh vào đây để nhập giọng nói", "dropAudioFileHereHint": ".wav hoặc .mp3", "voiceCloneTranscriptTitle": "Nhập bản chép lời của âm thanh (bắt buộc để nhân bản giọng nói)", + "voiceManagerTitle": "TTS - Trình quản lý giọng", + "voiceManagerDotDotDot": "Trình quản lý giọng...", + "voiceManagerMenuItem": "Trình quản lý giọng TTS...", + "speakFromCurrentLineTitle": "Đọc từ dòng hiện tại", + "speakFromCurrentLineDotDotDot": "Đọc từ dòng hiện tại...", + "speakFromCurrentLineHint": "Đọc to lần lượt từng dòng, bắt đầu từ dòng hiện tại. Nhấn Escape hoặc nhấp vào dòng khác để dừng.", + "stopSpeaking": "Dừng đọc", + "speakingLineX": "Đang đọc dòng {0}... (Escape để dừng)", + "playWithSpeechTitle": "Phát kèm giọng nói", + "playWithSpeechDotDotDot": "Phát kèm giọng nói...", + "playWithSpeechHint": "Phát video và đọc từng dòng khi video đến dòng đó. Tạm dừng và tua như bình thường; nhấn Escape để dừng.", + "lowerVideoVolumeWhileSpeaking": "Giảm âm lượng video khi đang đọc", + "pauseVideoWhenSpeechRunsLate": "Tạm dừng video khi giọng nói kéo sang dòng tiếp theo", + "waitingForSpeechLineX": "Đang tạo giọng nói cho dòng {0}... (Escape để dừng)", + "voiceKindClone": "Giọng nhân bản", + "voiceKindPreset": "Tích hợp sẵn", + "voiceKindModel": "Mô hình giọng", + "voiceKindOnline": "Trực tuyến", + "transcript": "Bản ghi lời", + "transcriptRequiredHint": "Bắt buộc - công cụ đọc bản ghi lời này của bản ghi âm (ref-text) khi nhân bản", + "transcriptOptionalHint": "Tùy chọn - bản ghi lời chính xác của bản ghi âm giúp nhân bản tốt hơn", + "saveTranscript": "Lưu bản ghi lời", + "searchVoices": "Tìm giọng", + "loadingVoicesDotDotDot": "Đang tải giọng...", + "noVoicesForEngine": "Không có giọng - hãy nhập một bản ghi âm hoặc tải xuống gói giọng", + "noVoicesMatchFilter": "Không có giọng nào khớp với tìm kiếm", + "copyVoiceTo": "Sao chép giọng sang", + "copyVoiceToDotDotDot": "Sao chép giọng sang công cụ...", + "copyingVoiceXToYDotDotDot": "Đang sao chép giọng '{0}' sang {1}...", + "voiceXCopiedToY": "Đã sao chép giọng '{0}' sang {1}", + "voiceXCouldNotBeCopiedToY": "Không thể sao chép giọng '{0}' sang {1} - xem nhật ký để biết chi tiết", + "voiceXAlreadyExistsInYContinue": "Đã tồn tại giọng tên '{0}' trong {1}.\n\nVẫn sao chép?", + "openVoicesFolder": "Mở thư mục giọng", + "downloadVoicePacksDotDotDot": "Tải xuống gói giọng...", + "downloadVoicePacksTitle": "TTS - Tải xuống gói giọng", + "voicePackIntro": "Gói giọng là tập hợp các bản ghi âm ngắn, sạch kèm bản ghi lời, sẵn sàng để nhân bản. Mỗi gói được nhập vào công cụ đã chọn (các giọng đã có sẽ bị bỏ qua) và kèm tệp ATTRIBUTION.txt với giấy phép và nguồn của nó.", + "voicePackDetailsXVoicesYSizeZLicense": "{0} giọng · {1} · {2}", + "installToEngine": "Cài đặt vào công cụ", + "downloadAndInstall": "Tải xuống và cài đặt", + "selectAtLeastOneVoicePack": "Chọn ít nhất một gói giọng", + "downloadingXDotDotDot": "Đang tải xuống {0}...", + "installingXDotDotDot": "Đang cài đặt {0}...", + "xVoicesInstalledYSkipped": "Đã cài đặt {0} giọng ({1} đã có sẵn)", "useSpeechToTextDotDotDot": "Dùng chuyển giọng nói thành văn bản...", "autoCastMenuItem": "Tìm giọng nói trong video và nhân bản...", "autoCastSpeakersTitle": "Các giọng nói tìm thấy trong video", @@ -2550,7 +2780,7 @@ "cloneVoicePerLinePreparing": "Đang lấy giọng của từng dòng từ video...", "cloneVoicePerLineNeedsVideo": "Việc nhân bản giọng của từng dòng cần video mà phụ đề thuộc về. Hãy mở video và thử lại.", "cloneVoicePerLineNoClips": "Không lấy được âm thanh nào từ video, nên không có gì để nhân bản. Hãy kiểm tra xem video có rãnh âm thanh không.", - "cloneVoicePerLineNeedsOriginalSubtitleX": "{0} chỉ có thể nhân bản một dòng khi biết trong video nói gì ở dòng đó, điều này lấy từ phụ đề ngôn ngữ gốc. Chưa tải phụ đề gốc nào, nên mọi dòng sẽ được đọc bằng giọng nhập đầu tiên thay vì giọng trong video.\n\nHãy mở phụ đề gốc (Tệp > Mở bản gốc...) bên cạnh bản dịch, rồi tạo lại.", + "cloneVoicePerLineTranscribeClipsXYZ": "{0} chỉ có thể nhân bản một dòng khi biết những gì được nói trong video tại dòng đó. Điều này chưa biết đối với {1}/{2} dòng - thông thường nó đến từ phụ đề ngôn ngữ gốc (Tệp > Mở bản gốc...).\n\nPhiên âm các dòng đó từ video bằng chuyển giọng nói thành văn bản ngay bây giờ?\n\nCó: chọn công cụ và ngôn ngữ được nói trong video, các đoạn tham chiếu sẽ được phiên âm trước khi tạo giọng nói.\nKhông: vẫn tạo - các dòng đó sẽ được đọc bằng giọng thông thường thay vì giọng trong video.", "voiceCloneConsentTitle": "Nhân bản giọng nói - trước khi bạn bắt đầu", "voiceCloneConsentHeader": "Bạn sắp nhân bản một giọng nói", "voiceCloneConsentIntro": "Nhân bản là sao chép giọng của một người thật. Điều đó đi kèm quy định ở hầu hết các nơi, và ở EU là các nghĩa vụ pháp lý thuộc về bạn, không phải Subtitle Edit. Vui lòng đọc phần này một lần.", @@ -2567,10 +2797,29 @@ "advancedTtsSettings": "Cài đặt TTS nâng cao", "advancedTtsAudioProcessing": "Xử lý âm thanh", "advancedTtsOutput": "Đầu ra", + "advancedTtsVoiceCloning": "Nhân bản giọng", + "cleanCloneReferences": "Làm sạch tham chiếu nhân bản giọng (Sidon)", + "cleanCloneReferencesDescription": "Loại bỏ nhạc, tiếng ồn và tiếng vang khỏi âm thanh dùng để nhân bản giọng: các đoạn lấy từ video và các bản ghi âm được nhập trong trình quản lý giọng. Dùng mô hình phục hồi giọng nói Sidon trên audio.cpp (tải xuống 940 MB). Giọng nhân bản không còn sao chép âm nền, nhưng giọng nói rất nhỏ hoặc chồng lấn có thể bị nghẹt.", + "cleaningCloneReferencesDotDotDot": "Đang làm sạch tham chiếu nhân bản giọng...", + "downloadSidonTitle": "Tải xuống Sidon?", + "downloadSidonQuestionX": "Làm sạch tham chiếu nhân bản giọng cần mô hình phục hồi giọng nói Sidon ({0}).\n\nTải xuống ngay? Nếu không có nó, các tham chiếu sẽ được dùng nguyên trạng.", "proAudioPostProcessing": "Hậu xử lý âm thanh chuyên nghiệp", "proAudioPostProcessingDescription": "Áp dụng EQ làm ấm, cổng chống ồn, nén, chuẩn hóa độ lớn (-16 LUFS) và fade in/out cho từng đoạn.", + "generatingSpeechSegmentXOfY": "Đang tạo giọng nói: đoạn {0}/{1}", + "adjustingSpeedSegmentXOfY": "Đang điều chỉnh tốc độ: đoạn {0}/{1}", + "mergingAudioSegmentXOfY": "Đang gộp âm thanh: đoạn {0}/{1}", + "postProcessingSegmentXOfY": "Đang hậu xử lý: đoạn {0}/{1}", "audioDucking": "Giảm nền âm thanh (ducking)", "audioDuckingDescription": "Giảm âm lượng âm thanh gốc của video và trộn với âm thanh TTS, để nhạc nền gốc vẫn nghe được thoang thoảng.", + "removeOriginalSpeech": "Loại bỏ giọng nói gốc (chậm)", + "removeOriginalSpeechDescription": "Giữ nhạc và hiệu ứng âm thanh của video gốc nhưng loại bỏ giọng nói, rồi trộn giọng nói mới lên trên. Mất khoảng thời gian bằng chính video nếu có GPU, và lâu hơn nhiều lần nếu không có. Khi bật giảm nền âm thanh (ducking), âm lượng của nó áp dụng cho nhạc và hiệu ứng. Chỉ áp dụng khi giọng nói được thêm vào tệp video.", + "removingOriginalSpeech": "Đang loại bỏ giọng nói gốc...", + "removeOriginalSpeechFailed": "Không thể loại bỏ giọng nói gốc - giọng nói đã được thêm vào video mà không loại bỏ.", + "speechRemovalModel": "Mô hình", + "speechRemovalModelDescription": "Chất lượng tốt nhất giữ lại nhiều nhạc và hiệu ứng âm thanh nhất. Nhanh thì nhanh hơn khoảng ba lần, nhưng mất thêm một chút nhạc.", + "speechRemovalBestQuality": "Chất lượng tốt nhất (Mel-Band RoFormer)", + "speechRemovalFast": "Nhanh (HTDemucs)", + "videoTruncatedWarning": "Hình ảnh trong tệp video mới dừng sau {0}, nhưng video gốc dài {1} - hình ảnh sẽ đứng yên trong khi âm thanh tiếp tục.\n\nTệp đã được giữ lại. Xem error-log.txt trong thư mục dữ liệu Subtitle Edit để biết kết quả ffmpeg.", "originalVolumePercent": "% âm lượng gốc", "vadSilenceCompression": "Nén khoảng lặng bằng VAD", "vadSilenceCompressionDescription": "Rút ngắn khoảng dừng giữa các từ trước khi thay đổi nhịp độ. Dùng Voice Activity Detection để chỉ nén các khoảng lặng trong khi giữ nguyên phần giọng nói. Đây là bước đầu tiên nên dùng — nó giảm thời lượng mà không mất chất lượng.", @@ -2644,7 +2893,6 @@ "subtitleMergedLinesAppliedPlural": "Đã áp dụng {0} lần gộp dòng từ chuyển văn bản thành giọng nói" }, "shotChanges": { - "titleGenerateOrImport": "Tạo/nhập chuyển cảnh", "generateShotChanges": "Tạo chuyển cảnh", "importShotChanges": "Nhập chuyển cảnh", "generateShotChangesWithFfmpeg": "Tạo chuyển cảnh bằng ffmpeg", @@ -2654,7 +2902,9 @@ "timeCodeFormatColon": "Định dạng mã thời gian:", "shotChangesList": "Danh sách chuyển cảnh", "shotChangesClearQuestion": "Bạn có chắc muốn xóa tất cả chuyển cảnh?", - "deleteSelectedShotChangeQuestion": "Bạn có chắc muốn xóa chuyển cảnh đã chọn?" + "deleteSelectedShotChangeQuestion": "Bạn có chắc muốn xóa chuyển cảnh đã chọn?", + "shotChanges": "Điểm chuyển cảnh", + "exportShotChanges": "Xuất điểm chuyển cảnh" }, "chapters": { "title": "Chương", @@ -2684,7 +2934,6 @@ "scaleTimesDescription": "Quy đổi thời gian của mọi chương từ tốc độ khung hình này sang tốc độ khung hình khác.", "fromFrameRate": "Từ", "toFrameRate": "Sang", - "apply": "Áp dụng", "writeToVideo": "Ghi vào video...", "writeToVideoTitle": "Ghi chương vào video", "writeToVideoDescription": "Các chương được ghi vào một bản sao của tệp video. Không có gì bị mã hóa lại, nên thao tác này nhanh và không mất chất lượng.", @@ -2694,7 +2943,6 @@ "writtenToX": "Đã ghi chương vào {0}", "unableToWriteChapters": "Không thể ghi chương vào tệp video.", "chaptersSavedToX": "Đã lưu chương vào {0}", - "noVideoLoaded": "Chưa tải video nào", "emptyListCallToAction": "Chưa có chương nào - hãy thêm một chương tại vị trí video, hoặc nhập chúng từ video hay tệp." }, "videoOcr": { @@ -2705,10 +2953,6 @@ "bottomHalf": "Nửa dưới", "fullFrame": "Toàn khung hình", "engine": "Công cụ OCR", - "language": "Ngôn ngữ", - "url": "URL", - "model": "Mô hình", - "apiKey": "Khóa API", "scan": "Quét", "framesPerSecond": "Khung hình mỗi giây", "textBrightnessMinimum": "Độ sáng văn bản tối thiểu (0=tắt)", @@ -2723,10 +2967,10 @@ "runningOcrXY": "Đang chạy OCR... {0}/{1}", "noLinesFoundTitle": "Không tìm thấy phụ đề", "noLinesFoundMessage": "Không tìm thấy văn bản trong vùng quét - hãy thử điều chỉnh vùng quét, công cụ hoặc độ sáng tối thiểu.", + "ocrStoppedEarlyMessage": "Công cụ OCR đã dừng trước khi đọc hết các khung hình, nên các dòng tìm được cho đến nay được giữ lại và phần còn lại bị thiếu.{0}{0}{1}", "linesFoundX": "Tìm thấy {0} dòng", "refiningTimingXY": "Đang tinh chỉnh thời gian... {0}/{1}", "fixOcrErrors": "Sửa lỗi OCR", - "dictionary": "Từ điển", "editLineX": "Chỉnh sửa dòng {0}", "downloadDictionaryHint": "Tải xuống từ điển kiểm tra chính tả...", "framesPerSecondHint": "Số khung hình được quét mỗi giây. Mức 5 đủ cho phụ đề thông thường; mức cao hơn bắt được các dòng rất ngắn và gom nhóm chặt hơn, nhưng tốn thêm thời gian OCR.", @@ -2743,7 +2987,58 @@ "testOcr": "Thử khung hình hiện tại", "testOcrRunning": "Đang thử OCR trên khung hình hiện tại...", "testOcrResultX": "Kết quả thử: {0}", - "testOcrNoTextFound": "Thử: không tìm thấy văn bản trong vùng quét" + "testOcrNoTextFound": "Thử: không tìm thấy văn bản trong vùng quét", + "engineSettings": "Cài đặt công cụ OCR", + "engineBuiltIn": "Tích hợp sẵn trong hệ điều hành", + "engineExternalServer": "Máy chủ bên ngoài (được cài đặt và quản lý bên ngoài Subtitle Edit)", + "engineCloudApi": "API đám mây (cần khóa API)", + "engineNothingToInstall": "Không cần cài đặt gì", + "engineModelsMissing": "Đã cài công cụ, thiếu mô hình", + "website": "Trang web" + }, + "backgroundMusic": { + "title": "Tạo nhạc nền", + "generateBackgroundMusicDotDotDot": "Tạo nhạc nền...", + "preset": "Cấu hình sẵn", + "prompt": "Lời nhắc", + "tempoBpm": "Nhịp độ (BPM)", + "generateSeconds": "Tạo (giây)", + "seed": "Seed", + "randomSeed": "Ngẫu nhiên", + "outputLength": "Độ dài đầu ra", + "outputLengthVideoX": "{0} (độ dài video - nhạc lặp liền mạch)", + "musicVolumePercent": "Âm lượng nhạc (%)", + "removeExistingAudioTracks": "Xóa các rãnh âm thanh hiện có", + "originalAudioVolumePercent": "Âm lượng âm thanh gốc (%)", + "saveAudioDotDotDot": "Lưu âm thanh...", + "addToVideoDotDotDot": "Thêm vào video...", + "engineInfo": "ACE-Step 1.5 Turbo qua audio.cpp (giấy phép MIT) - chạy cục bộ", + "presetCooking": "Nấu ăn / làm bánh", + "presetTech": "Công nghệ / đánh giá thiết bị", + "presetForest": "Rừng / thiên nhiên", + "presetRelaxingPiano": "Piano thư giãn", + "presetLofiVlog": "Vlog lo-fi", + "presetCorporate": "Doanh nghiệp / giải thích", + "loadingModel": "Đang tải mô hình...", + "composing": "Đang sáng tác...", + "generatingAudio": "Đang tạo âm thanh...", + "decodingAudio": "Đang giải mã âm thanh...", + "creatingLoop": "Đang tạo vòng lặp liền mạch...", + "resultLoopXYZ": "Vòng lặp: {0} ở {1} BPM, độ khớp mối nối {2}%", + "resultNoLoopX": "Nhạc đã sẵn sàng: {0}", + "addingMusicToVideo": "Đang thêm nhạc vào video...", + "downloadModelTitle": "Tải xuống mô hình nhạc?", + "downloadModelQuestionX": "Tạo nhạc cần mô hình ACE-Step 1.5 ({0}).\n\nTải xuống mô hình?", + "downloadingModel": "Đang tải xuống mô hình ACE-Step 1.5...", + "saveAudioTitle": "Lưu nhạc nền", + "audioFileSavedX": "Đã lưu tệp âm thanh: {0}", + "unableToGenerateMusic": "Không thể tạo nhạc", + "unableToAddMusicToVideo": "Không thể thêm nhạc vào video", + "outputLengthTextToSpeech": "Theo giọng nói được tạo - nhạc lặp liền mạch và nhỏ đi khi có người nói", + "addBackgroundMusic": "Thêm nhạc nền", + "addBackgroundMusicHint": "Tạo nhạc cục bộ bằng ACE-Step 1.5 và trộn dưới giọng nói - nhạc nhỏ đi khi có người nói", + "generatingBackgroundMusicDotDotDot": "Đang tạo nhạc nền...", + "mixingBackgroundMusicDotDotDot": "Đang trộn nhạc nền..." }, "goToVideoPosition": "Đi đến vị trí video", "goToVideoPositionDotDotDot": "Đi tới vị trí video...", @@ -2754,6 +3049,11 @@ "openSecondarySubtitleOnVideoPlayerDotDotDot": "Phụ đề phụ trên trình phát video, mở...", "openSecondarySubtitleOnVideoPlayer": "Phụ đề phụ (trên trình phát video)", "removeSecondarySubtitleOnVideoPlayer": "Phụ đề phụ trên trình phát video, xóa", + "editSecondarySubtitleSettings": "Chỉnh sửa cài đặt phụ đề phụ", + "editSecondarySubtitleSettingsDotDotDot": "Chỉnh sửa cài đặt phụ đề phụ...", + "secondarySubtitleRememberSettings": "Nhớ các cài đặt này", + "secondarySubtitleDoNotShowAgain": "Không hiển thị lại hộp thoại này", + "secondarySubtitleJustifyLines": "Canh đều các dòng", "openRecentVideo": "Mở video gần đây", "clearRecentVideos": "Xóa danh sách video gần đây", "cutVideoTitle": "Cắt video", @@ -2765,10 +3065,18 @@ "remuxVideoOutputFormat": "Định dạng xuất:", "remuxVideoOutputFile": "Tập tin xuất:", "remuxVideoRemuxing": "Đang remux video và audio...", + "remuxVideoFinalizing": "Đang hoàn tất tệp đầu ra (di chuyển chỉ mục lên đầu)...", "remuxVideoCompleted": "Remux video thành công.", "remuxVideoFailed": "Lỗi khi remux video.", "remuxVideoPleaseSelectBoth": "Vui lòng chọn cả tập tin video và audio.", "remuxVideoAssRequiresMkv": "Phụ đề ASS/SSA yêu cầu container MKV để bảo toàn kiểu dáng và định dạng.", + "remuxVideoSccRequiresMov": "Phụ đề Scenarist (.scc) được nhúng dưới dạng phụ đề CEA-608 (closed captions), yêu cầu container MOV hoặc MPG. Định dạng đầu ra đã được tự động chuyển sang .mov.", + "remuxVideoSccNotWithWebM": "Phụ đề Scenarist (.scc) cần container MOV, vốn không chứa được video VP9/AV1 của tệp .webm. Hãy xóa tệp .scc hoặc chuyển đổi nó sang định dạng phụ đề khác.", + "remuxVideoMpgMaxTwoCaptionTracks": "Tệp .mpg chứa tối đa hai rãnh closed caption (CC1 và CC3) - hãy xóa bớt tệp phụ đề để chỉ còn một hoặc hai.", + "remuxVideoMccRequiresMpg": "Phụ đề MacCaption (.mcc) được nhúng dưới dạng closed captions CEA-608/708 trong video MPEG-2, yêu cầu container MPG. Định dạng đầu ra đã được tự động chuyển sang .mpg.", + "remuxVideoMpgReencodeVideoX": "Closed captions trong tệp .mpg cần video MPEG-2, còn video hiện là {0}. Mã hóa lại video sang MPEG-2? Việc này mất nhiều thời gian hơn và giảm chất lượng một chút.", + "remuxVideoFileHasNoAudioX": "\"{0}\" không có rãnh âm thanh, nên không thể dùng làm nguồn âm thanh.", + "remuxVideoAddingClosedCaptions": "Đang thêm closed captions...", "remuxVideoSelectAudioTrack": "Chọn track audio", "remuxVideoSelectAudioTrackFor": "Chọn track audio cho {0}", "remuxVideoSelectAudioTrackPrompt": "Tệp '{0}' có chứa {1} track audio. Vui lòng chọn track audio bạn muốn remux:", @@ -2778,26 +3086,51 @@ "remuxVideoSubtitleFiles": "Tập tin phụ đề (tùy chọn, phụ đề mềm):", "remuxVideoSelectAudioTrackDotDotDot": "Chọn track audio...", "remuxVideoFilesX": "{0} tập tin", + "remuxVideoMixAudio": "Trộn âm thanh thành một rãnh", + "remuxVideoVolumePercent": "Âm lượng %:", + "remuxVideoVolumeX": "Âm lượng {0}%", + "remuxVideoFastStart": "Tối ưu cho phát trực tuyến trên web (đưa chỉ mục lên đầu - chậm hơn)", "embedSubtitlesDotDotDot": "Thêm/xóa phụ đề nhúng...", "generateTimeCodes": "Tạo mã thời gian", "checkeredImage": "Ảnh ca-rô", - "saveVideoAsTitle": "Lưu video thành", "promptForFfmpegParamsAndGenerate": "Hỏi tham số ffmpeg và tạo", "cutVideoCutSegments": "Cắt các đoạn", "cutVideoMergeSegments": "Gộp các đoạn", "cutVideoSplitSegments": "Lưu các đoạn riêng lẻ", - "cutVideoType": "Kiểu cắt", - "mpvRenderAuto": "Tự động", - "mpvRenderNative": "Gốc", - "mpvRenderOpenGl": "OpenGL", - "mpvRenderSoftware": "Phần mềm (chậm)", "cutVideoAlsoCutSubtitle": "Cắt cả phụ đề", "cutVideoSubtitleFileGeneratedX": "Đã tạo tệp phụ đề: {0}", + "cutVideoTransition": "Hiệu ứng chuyển tiếp giữa các đoạn", + "cutVideoTransitionDurationSeconds": "Thời lượng (giây)", + "cutVideoFadeInSeconds": "Hiện dần từ màu đen ở đầu (giây)", + "cutVideoFadeOutSeconds": "Mờ dần sang màu đen ở cuối (giây)", + "cutVideoPreviewTransition": "Xem trước hiệu ứng chuyển tiếp", + "cutVideoPreviewTransitionTitle": "Xem trước hiệu ứng chuyển tiếp", + "cutVideoPreviewNeedsJoin": "Không có mối nối nào để xem trước - hãy thêm ít nhất hai đoạn để gộp, hoặc một đoạn để cắt bỏ ở giữa.", + "cutVideoTransitionInfoX": "Mỗi hiệu ứng chuyển tiếp chồng lên các phần nó nối, nên đầu ra ngắn đi {0} giây cho mỗi mối nối. Phụ đề đã cắt được chỉnh lại thời gian cho khớp.", + "cutVideoTransitionFade": "Mờ dần", + "cutVideoTransitionFadeBlack": "Mờ dần qua màu đen", + "cutVideoTransitionFadeWhite": "Mờ dần qua màu trắng", + "cutVideoTransitionFadeGrays": "Mờ dần qua màu xám", + "cutVideoTransitionDissolve": "Hòa tan", + "cutVideoTransitionWipeLeft": "Quét sang trái", + "cutVideoTransitionWipeRight": "Quét sang phải", + "cutVideoTransitionWipeUp": "Quét lên", + "cutVideoTransitionWipeDown": "Quét xuống", + "cutVideoTransitionSlideLeft": "Trượt sang trái", + "cutVideoTransitionSlideRight": "Trượt sang phải", + "cutVideoTransitionSlideUp": "Trượt lên", + "cutVideoTransitionSlideDown": "Trượt xuống", + "cutVideoTransitionSmoothLeft": "Mượt sang trái", + "cutVideoTransitionSmoothRight": "Mượt sang phải", + "cutVideoTransitionCircleOpen": "Mở vòng tròn", + "cutVideoTransitionCircleClose": "Đóng vòng tròn", + "cutVideoTransitionRadial": "Xoay tròn", + "cutVideoTransitionPixelize": "Điểm ảnh hóa", + "cutVideoTransitionBlur": "Làm mờ", "importCurrentSubtitle": "Nhập phụ đề hiện tại", "addRemoveEmbeddedSubtitlesTitle": "Thêm/xóa phụ đề nhúng", "addRemoveEmbeddedSubtitlesMp4Title": "Thêm/xóa phụ đề nhúng (MP4)", "addCurrentSubtitle": "Thêm phụ đề hiện tại", - "embeddedTrackColumnNew": "Mới", "embeddedTrackNoSubtitlesFoundTitle": "Không tìm thấy phụ đề", "embeddedTrackNoSubtitlesFoundMessage": "Tệp phụ đề đã chọn không chứa phụ đề nào.", "embeddedTrackNoTracksTitle": "Chưa thêm luồng nào", @@ -2809,7 +3142,7 @@ "embeddedTrackGeneratingVideoXY": "Đang tạo video... {0}% {1}", "embeddedTrackGeneratingVideo": "Đang tạo video...", "mp4FilesFilter": "Tệp MP4", - "titleOrLanguage": "Tiêu đề/ngôn ngữ", + "embeddedTracksPickVideoHint": "Chọn một tệp video, hoặc thả vào đây, để xem các rãnh phụ đề của nó.", "viewMatroskaTrackX": "Xem luồng Matroska - {0}", "resolutionSeparator": "x", "openFromUrlTitle": "Mở tệp video từ URL", @@ -2819,9 +3152,7 @@ "openFromUrlDownloadAndOpen": "Tải xuống và mở", "openFromUrlDownloadAndOpenDescription": "Lưu video vào máy, sau đó mở nó.", "openFromUrlDownloadAndOpenNote": "Bắt buộc cho chuyển giọng nói thành văn bản và các đỉnh sóng âm.", - "openFromUrlMissing": "Vui lòng nhập URL video trước.", "openFromUrlDownloadingTitle": "Đang tải video xuống", - "openFromUrlSaveAs": "Lưu video thành", "openFromUrlDownloadSubtitles": "Đồng thời tải phụ đề xuống", "openFromUrlDownloadVideo": "Tải video xuống", "openFromUrlIncludeAutoGeneratedSubtitles": "Bao gồm phụ đề tự động tạo (ngôn ngữ gốc)", @@ -2858,12 +3189,15 @@ "reEncodeGeneratingDone": "Đã tạo xong", "reEncodeGeneratedFilesX": "Các tệp đã tạo ({0}):", "reEncodeFfmpegParameters": "Tham số ffmpeg", + "analyzingVideoX": "Đang phân tích video... {0}% {1}", + "analyzingVideoXofY": "Đang phân tích video {0}/{1}... {2}% {3}", + "backgroundImageFileNotSelected": "Chưa chọn tệp hình nền", + "backgroundImageFileDoesNotExistX": "Tệp hình nền không tồn tại: {0}", "voices": "Giọng", "presets": "Mẫu dựng sẵn", "talkerX": "Người nói {0}", "baseModel": "Mô hình cơ bản", "turboModel": "Mô hình turbo", - "ttsUpdateX": "Cập nhật {0}", "ttsEngineNotInstalled": "Chưa cài đặt {0}", "ttsEngineUpdateAvailable": "{0} - có bản cập nhật", "ttsEngineTooOldUpdateRequired": "{0} quá cũ - cần cập nhật", @@ -2886,19 +3220,25 @@ "cloneVoiceTo": "Nhân bản giọng thành", "cloneVoiceNameTitle": "Tên của giọng mới", "cloneVoiceExtractFailed": "Không thể trích xuất âm thanh cho dòng đã chọn.", + "lineXHasNoDuration": "Dòng {0} không có thời lượng (thời gian kết thúc không sau thời gian bắt đầu), nên không có âm thanh để trích xuất.", + "audioClipsSkippedLinesX": "Không trích xuất được âm thanh cho các dòng này, nên chúng đã bị bỏ qua: {0}", "toggleShotChange": "Bật/tắt chuyển cảnh", "resetWaveformZoomAndSpeed": "Đặt lại thu phóng và tốc độ sóng âm", "showOriginalSubtitle": "Hiển thị phụ đề gốc", + "timelineGroupTracksBy": "Nhóm các rãnh phụ đề theo", + "timelineShowThumbnails": "Hiển thị hình thu nhỏ video", + "showSpeechOnly": "Chỉ hiện giọng nói (tạo chậm)", + "isolatingSpeechForWaveformX": "Đang tách riêng giọng nói cho dạng sóng... {0}", + "isolatingSpeechForWaveformFailed": "Không thể tách riêng giọng nói cho dạng sóng - xem nhật ký công cụ", + "speechOnlyWaveformReady": "Dạng sóng giờ chỉ hiển thị giọng nói", "showOnlyWaveform": "Chỉ hiện sóng âm", "showOnlySpectrogram": "Chỉ hiện phổ âm", "showWaveformAndSpectrogram": "Hiện sóng âm và phổ âm", - "spectrogramClassic": "Cổ điển", "spectrogramClassicViridis": "Viridis", "spectrogramClassicPlasma": "Plasma", "spectrogramClassicInferno": "Inferno", "spectrogramClassicTurbo": "Turbo", "spectrogramNeon": "Neon", - "waveformDrawStyleClassic": "Cổ điển", "waveformDrawStyleFancy": "Cầu kỳ", "setVideoPositionAndPauseAndSelectSubtitle": "Đặt vị trí video, tạm dừng và chọn phụ đề", "setVideopositionAndPauseAndSelectSubtitleAndCenter": "Đặt vị trí video, tạm dừng, chọn phụ đề và căn giữa", @@ -2908,7 +3248,6 @@ }, "sync": { "visualSync": "Đồng bộ trực quan", - "sync": "Đồng bộ", "startScene": "Cảnh bắt đầu", "endScene": "Cảnh kết thúc", "playTwoSecondsAndBack": "Phát 2 giây rồi quay lại", @@ -2924,19 +3263,18 @@ "adjustSelectedLinesAndForward": "Điều chỉnh các dòng đã chọn và về sau", "toFrameRate": "Đến tốc độ khung hình", "fromFrameRate": "Từ tốc độ khung hình", + "videoXFrameRateY": "{0} - {1} fps", "adjustAllTimes": "Điều chỉnh tất cả thời gian (hiện sớm hơn/muộn hơn)", "showEarlier": "Hiện sớm hơn", "showLater": "Hiện muộn hơn", - "changeFrameRate": "Đổi tốc độ khung hình", "setSyncPoint": "Đặt điểm đồng bộ", "setSyncPointViaVideo": "Đặt điểm đồng bộ qua video...", "syncPointTimeCode": "Mã thời gian của điểm đồng bộ", "syncPoints": "Các điểm đồng bộ", "pointSync": "Đồng bộ theo điểm", "pointSyncViaOther": "Đồng bộ theo điểm qua phụ đề khác", - "adjustmentX": "Điều chỉnh: {0}", - "totalAdjustmentX": "Tổng điều chỉnh: {0}", - "syncPointCandidateInfo": "Các dòng bắt đầu sau {0}+ giây im lặng được làm nổi bật - chúng thường là điểm đồng bộ đáng tin cậy", + "syncPointCandidateAfterInfo": "Các dòng theo sau là {0}+ giây im lặng được làm nổi bật - chúng thường là điểm đồng bộ đáng tin cậy", + "gapAfter": "Khoảng cách sau", "adjustAllShortcuts": "Phím tắt:\r\n\r\n• Shift + Trái/Phải: Dịch 10 ms\r\n• Ctrl + Trái/Phải: Dịch 100 ms\r\n• Alt + Trái/Phải: Dịch 500 ms", "adjustAllShortcutsFrames": "Phím tắt:\r\n\r\n• Shift + Trái/Phải: Dịch 1 khung hình\r\n• Ctrl + Trái/Phải: Dịch 10 khung hình\r\n• Alt + Trái/Phải: Dịch 1 giây", "offsetInSeconds": "Độ lệch theo giây", @@ -2956,6 +3294,7 @@ "lineMerge": "Gộp dòng", "delayInSecondsBetweenRequests": "Độ trễ tính bằng giây giữa các yêu cầu", "maxBytesPerRequest": "Số byte tối đa mỗi yêu cầu", + "keepMusicLinesUntranslated": "Không dịch các dòng nằm trong ký hiệu nhạc (♪ ... ♪)", "promptText": "Văn bản prompt", "resetPromptToDefault": "Đặt lại prompt về mặc định", "translateEachLineSeparately": "Dịch từng dòng riêng biệt", @@ -2969,11 +3308,10 @@ "readyToTranslate": "Sẵn sàng dịch", "translating": "Đang dịch...", "translationComplete": "Dịch hoàn tất", + "translateInPlaceNoOriginal": "Dịch tại chỗ (không tạo bản gốc)", "translationCancelled": "Đã hủy dịch", "swapLanguages": "Hoán đổi ngôn ngữ nguồn và đích", "xIsAlreadyDownloadedReDownload": "{0} đã được tải xuống. Tải lại?", - "advancedDotDotDot": "Nâng cao...", - "advancedSettings": "Cài đặt nâng cao", "advancedSettingsSubtitle": "Tùy chọn lô, ngữ cảnh và lấy mẫu cho công cụ llama.cpp nâng cao", "context": "Ngữ cảnh", "synopsis": "Tóm tắt nội dung", @@ -2997,6 +3335,7 @@ "useOnlyExtraServerParametersHint": "Khởi động llama-server với các tham số ở trên thay vì thiết lập riêng của Subtitle Edit - chỉ mô hình, host và cổng vẫn được đặt", "serverRunningAtX": "Máy chủ đang chạy tại {0}", "customPromptHint": "Hướng dẫn tùy chỉnh ({0} = ngôn ngữ nguồn, {1} = ngôn ngữ đích); để trống = dùng prompt tích hợp", + "editTranslationHint": "Nhấn vào bản dịch của hàng đã chọn để chỉnh sửa (Enter để lưu, Escape để hủy)", "llamaCppDownloadEngineAndModelPrompt": "llama.cpp cần tải xuống công cụ llama-server và một mô hình dịch. Tải xuống ngay?", "llamaCppDownloadEnginePrompt": "llama.cpp cần tải xuống công cụ llama-server. Tải xuống ngay?", "llamaCppDownloadModelPrompt": "llama.cpp cần tải xuống mô hình dịch đã chọn. Tải xuống ngay?" @@ -3009,7 +3348,6 @@ "dialogStyleDashBothLinesWithSpace": "Gạch đầu cả hai dòng có khoảng trắng", "dialogStyleDashBothLinesWithoutSpace": "Gạch đầu cả hai dòng không có khoảng trắng", "continuationStyle": "Kiểu nối tiếp", - "continuationStyleNone": "Không", "continuationStyleNoneTrailingDots": "Không, dấu chấm cho khoảng ngắt (chỉ ở cuối)", "continuationStyleNoneLeadingTrailingDots": "Không, dấu chấm cho khoảng ngắt", "continuationStyleNoneTrailingEllipsis": "Không, dấu ba chấm cho khoảng ngắt (chỉ ở cuối)", @@ -3020,7 +3358,6 @@ "continuationStyleLeadingTrailingEllipsis": "Dấu ba chấm", "continuationStyleLeadingTrailingDash": "Gạch ngang", "continuationStyleLeadingTrailingDashDots": "Gạch ngang, nhưng dấu chấm cho khoảng ngắt", - "continuationStyleCustom": "Tùy chỉnh", "cpsLineLengthStyle": "CPS/độ dài dòng", "cpsLineLengthStyleCalcAll": "Đếm tất cả ký tự", "cpsLineLengthStyleCalcNoSpaceCpsOnly": "Đếm tất cả trừ khoảng trắng, chỉ CPS", @@ -3033,14 +3370,6 @@ "cpsLineLengthStyleCalcNoSpaceOrPunctuationCpsOnly": "Không khoảng trắng hoặc dấu câu, chỉ CPS", "cpsLineLengthStyleCalcIgnoreArabicDiacritics": "Bỏ qua dấu phụ tiếng Ả Rập", "cpsLineLengthStyleCalcIgnoreArabicDiacriticsNoSpace": "Bỏ qua dấu phụ tiếng Ả Rập, không khoảng trắng", - "timeCodeModeHhMmSsMs": "HH:MM:SS:MS", - "timeCodeModeHhMmSsFf": "HH:MM:SS:FF", - "splitBehaviorPrevious": "Thêm khoảng cách bên trái điểm chia (chọn bên phải)", - "splitBehaviorHalf": "Thêm khoảng cách ở giữa điểm chia (chọn bên trái)", - "splitBehaviorNext": "Thêm khoảng cách bên phải điểm chia (chọn bên trái)", - "subtitleListActionNothing": "Không làm gì", - "subtitleListActionVideoGoToPositionAndPause": "Đến vị trí video và tạm dừng", - "subtitleListActionVideoGoToPositionAndPlay": "Đến vị trí video và phát", "subtitleGridCenterText": "Căn giữa văn bản trong lưới phụ đề", "minGapCalculateDotDotDot": "Tính khoảng cách tối thiểu từ tốc độ khung hình...", "minGapCalculateTitle": "Khoảng cách tối thiểu giữa các phụ đề tính bằng ms", @@ -3072,8 +3401,14 @@ "checkForUpdatesChannelStableAndBeta": "Phiên bản ổn định và beta", "assaAutoSetResolution": "ASSA: đặt độ phân giải (PlayResX/PlayResY) theo video khi mở video", "assaAutoSetResolutionPrompt": "ASSA: hỏi trước khi đổi độ phân giải cho khớp video (nếu không sẽ tự động lấy mẫu lại)", + "videoControls": "Điều khiển video", + "videoControlsEdit": "Chỉnh sửa điều khiển video...", + "videoControlsPositionText": "Văn bản vị trí / thời lượng", + "videoControlsVideoFileName": "Tên tệp video", + "videoControlsPlayerName": "Tên trình phát video", "mergeKeepEndTime": "Gộp dòng: giữ thời điểm kết thúc (cho phép chồng lấn với phụ đề tiếp theo)", "mergeKeepEndTimeOnlyAssa": "Gộp dòng: chỉ giữ thời điểm kết thúc với \"Advanced Sub Station Alpha\"", + "mergeAutoBreak": "Gộp dòng: tự động ngắt dòng văn bản đã gộp (tắt thì giữ ngắt dòng riêng của các dòng)", "defaultSaveLocation": "Vị trí lưu mặc định", "defaultSaveLocationSourceFileFolder": "Thư mục của tệp phụ đề/video", "defaultSaveLocationLastUsedFolder": "Thư mục dùng lần cuối", @@ -3081,18 +3416,10 @@ "defaultSaveLocationSubtitleFileFolder": "Thư mục tệp phụ đề", "defaultSaveLocationCustomFolder": "Thư mục tùy chỉnh", "subtitleListActionVideoGoToPositionAndPlayCurrentAndPause": "Đến vị trí video, phát dòng hiện tại và tạm dừng", - "subtitleListActionEditText": "Đến ô soạn văn bản", "subtitleListActionVideoGoToPositionMinus1SecAndPause": "Đến vị trí video - 1 giây và tạm dừng", "subtitleListActionVideoGoToPositionMinusHalfSecAndPause": "Đến vị trí video - 0.5 giây và tạm dừng", "subtitleListActionVideoGoToPositionMinus1SecAndPlay": "Đến vị trí video - 1 giây và phát", - "subtitleListActionEditTextAndPause": "Đến ô soạn văn bản và tạm dừng tại vị trí video", - "autoBackupEveryMinute": "Mỗi phút", - "autoBackupEveryXthMinute": "Mỗi {0} phút", - "profiles": "Hồ sơ", - "autoBackupDeleteAfterXMonths": "Xóa bản sao lưu tự động sau {0} tháng", "searchSettingsDotDotDot": "Tìm kiếm cài đặt...", - "syntaxColoring": "Tô màu cú pháp", - "waveformSpectrogram": "Sóng âm/phổ âm", "network": "Mạng", "fileTypeAssociations": "Liên kết loại tệp", "textBoxColorTags": "Tô màu thẻ (HTML/ASSA) trong ô văn bản phụ đề", @@ -3125,10 +3452,9 @@ "darkThemeForegroundColor": "Màu chữ giao diện tối", "darkThemeBackgroundColor": "Màu nền giao diện tối", "showGridLines": "Hiện đường kẻ lưới", - "resetSettings": "Đặt lại cài đặt?", - "resetSettingsDetail": "Thao tác này sẽ đặt lại tất cả cài đặt về giá trị mặc định.\n\nTiếp tục?", "showHorizontalLineAboveToolbar": "Hiện đường kẻ ngang phía trên thanh công cụ", "bookmarkColor": "Màu dấu trang", + "spellCheckHighlightColor": "Màu tô sáng kiểm tra chính tả", "singleLineMaxLength": "Độ dài tối đa một dòng", "optimalCharsPerSec": "Số ký tự/giây tối ưu", "maxCharsPerSec": "Số ký tự/giây tối đa", @@ -3141,13 +3467,21 @@ "unbreakSubtitlesShortThan": "Bỏ ngắt dòng phụ đề ngắn hơn", "newEmptyDefaultMs": "Thời lượng mặc định của phụ đề mới (ms)", "timeCodeUpDownStepMs": "Bước tăng/giảm thời gian (ms)", + "moveSelectedLinesStepMs": "Bước phím tắt di chuyển các dòng đã chọn (ms)", + "moveStartEndStepMs": "Bước phím tắt di chuyển bắt đầu/kết thúc (ms)", + "moveLinesShortenNeighbor": "Di chuyển dòng: rút ngắn dòng trước/sau thay vì chồng lên nó", "promptBeforeDelete": "Hỏi trước khi xóa", "rememberPositionAndSize": "Nhớ vị trí và kích thước cửa sổ", + "titleBarFullFileName": "Hiển thị đường dẫn tệp đầy đủ trên thanh tiêu đề", + "showRecentFiles": "Hiển thị tệp gần đây (để mở lại)", "openLastFileOnStart": "Mở tệp gần đây nhất khi khởi động", "autoSave": "Tự động lưu (lưu tệp đang mở trong khi chỉnh sửa)", "autoBackupOn": "Tự động sao lưu", "autoBackupIntervalMinutes": "Khoảng thời gian tự động sao lưu (phút)", "autoBackupDeleteAfterDays": "Thời gian giữ bản sao lưu tự động (ngày)", + "settingsBackupOn": "Tự động sao lưu cài đặt", + "settingsBackupIntervalDays": "Chu kỳ sao lưu cài đặt (ngày, 0 = mỗi lần khởi động)", + "settingsBackupMaxCount": "Số bản sao lưu cài đặt cần giữ", "autoConvertToUtf8": "Tự động chuyển mã hóa sang UTF-8 khi mở", "autoTrimWhiteSpace": "Tự động cắt khoảng trắng", "defaultEncoding": "Mã hóa mặc định", @@ -3163,23 +3497,14 @@ "colorGapTooShort": "Tô màu nếu khoảng cách quá ngắn", "errorBackgroundColor": "Màu nền lỗi", "waveformDrawGridLines": "Vẽ đường kẻ lưới", - "waveformCenterVideoPosition": "Căn giữa vị trí video", + "waveformUseSkiaRenderer": "Dùng trình kết xuất nhanh thử nghiệm", "waveformCenterVideoPositionAlsoWhenPaused": "Căn giữa vị trí video cả khi tạm dừng", + "waveformSelectCurrentSubtitleWhilePaused": "Chọn phụ đề hiện tại cả khi tạm dừng", + "waveformRememberZoom": "Nhớ mức thu phóng (ngang và dọc)", "waveformShowToolbar": "Hiện thanh công cụ", "waveformShowToolbarEdit": "Chỉnh sửa thanh công cụ...", "waveformShowToolbarEditLabel": "Mục trên thanh công cụ", "waveformSpectrogramCombinedWaveformHeight": "Kết hợp sóng âm/phổ âm, % chiều cao sóng âm", - "showWaveformToolbarPlay": "Thanh công cụ: hiện nút phát", - "showWaveformToolbarRepeat": "Thanh công cụ: hiện nút lặp lại", - "showWaveformToolbarRemoveBlankLines": "Thanh công cụ: hiện nút xóa dòng trống", - "showWaveformToolbarNew": "Thanh công cụ: hiện nút phụ đề mới", - "showWaveformToolbarSetStart": "Thanh công cụ: hiện nút đặt điểm bắt đầu", - "showWaveformToolbarSetEnd": "Thanh công cụ: hiện nút đặt điểm kết thúc", - "showWaveformToolbarStartAndOffsetTheRest": "Thanh công cụ: hiện nút đặt điểm bắt đầu và dịch phần còn lại", - "showWaveformToolbarHorizontalZoom": "Thanh công cụ: hiện thanh trượt thu phóng ngang", - "showWaveformToolbarVerticalZoom": "Thanh công cụ: hiện thanh trượt thu phóng dọc", - "showWaveformToolbarVideoPositionSlider": "Thanh công cụ: hiện thanh trượt vị trí video", - "showWaveformToolbarPlaybackSpeed": "Thanh công cụ: hiện tốc độ phát", "waveformFocusTextboxAfterInsertNew": "Chuyển tiêu điểm vào ô văn bản sau khi chèn", "waveformInvertMouseWheel": "Đảo ngược bánh xe chuột", "waveformMouseWheelSetsVideoPosition": "Bánh xe chuột đặt vị trí video", @@ -3200,20 +3525,20 @@ "fixCommonErrorsSkipStep1": "Sửa lỗi thường gặp: bỏ qua bước 1 (chọn mục sửa)", "showPluginsMenu": "Hiện menu Plugin", "proxyAddress": "Địa chỉ proxy", - "username": "Tên người dùng", "password": "Mật khẩu", "defaultFormat": "Định dạng mặc định", "defaultSaveAsFormat": "Định dạng \"Lưu thành\" mặc định", "favoriteSubtitleFormats": "Định dạng phụ đề yêu thích", "favoriteLanguages": "Ngôn ngữ yêu thích", "webVttUseXTimestampMap": "WebVTT: dùng X-TIMESTAMP-MAP (dịch mã thời gian khi tải)", - "showStopButton": "Hiện nút dừng", - "showFullscreenButton": "Hiện nút toàn màn hình", "fullscreenHideControls": "Ẩn điều khiển video khi toàn màn hình", + "showSecondarySubtitleDialog": "Hiển thị hộp thoại kiểu khi mở tệp phụ đề phụ", + "rememberSecondarySubtitleFile": "Nhớ tệp phụ đề phụ", "autoOpenVideoFile": "Tự động mở tệp video khi mở phụ đề", "downloadMpv": "Tải mpv xuống", "downloadVlc": "Tải VLC xuống", "allowSingleLetterShortcutsInTextbox": "Cho phép phím tắt một chữ cái trong ô văn bản", + "allowTextNavigationShortcutsInTextbox": "Cho phép phím tắt trên các phím điều hướng văn bản (Ctrl+Left/Right, Home/End) trong ô văn bản", "spellCheckEnglishTreatInApostropheAsIng": "Kiểm tra chính tả: Xem các từ kết thúc bằng 'in'' là 'ing' (chỉ tiếng Anh)", "goToLineNumberSetsVideoPosition": "Đi đến số dòng cũng đặt vị trí video", "adjustAllTimesRememberLineSelectionChoice": "Điều chỉnh tất cả thời gian, nhớ lựa chọn dòng", @@ -3225,6 +3550,7 @@ "showAssaLayer": "Hiện ô lớp ASSA", "waveformCursorColor": "Màu con trỏ/đầu đọc sóng âm", "waveformShotChangeColor": "Màu điểm chuyển cảnh sóng âm", + "waveformGridColor": "Màu lưới dạng sóng", "waveformParagraphLeftColor": "Màu viền trái sóng âm", "waveformParagraphRightColor": "Màu viền phải sóng âm", "waveformFancyHighColor": "Màu cao kiểu cầu kỳ của sóng âm", @@ -3252,27 +3578,31 @@ "resetSyntaxColoring": "Đặt lại tô màu cú pháp", "resetWaveform": "Đặt lại sóng âm", "resetRules": "Đặt lại quy tắc", - "useFrameMode": "Dùng chế độ khung hình (hh.mm.ss.ff)", + "timeCodeMode": "Chế độ mã thời gian", + "timeCodeModeTime": "Thời gian (hh:mm:ss,zzz)", + "timeCodeModeFrames": "Khung hình (hh:mm:ss:ff)", + "timeCodeModeFrameNumbers": "Số khung hình", + "frameModeSnapToVideoFrames": "Lưu thời gian theo khung hình video (không theo khung hình trong mỗi giây)", "textBoxLimitNewLines": "Giới hạn số dòng trong ô văn bản phụ đề", - "mpvVideoOutput": "Đầu ra video (mpv)", "mpvOpenGl": "libmpv - OpenGL", "mpvSoftwareRendering": "libmpv - Kết xuất phần mềm (chậm)", + "mpvMetalRendering": "libmpv - Metal (thử nghiệm, cần khởi động lại)", "mpvWidRendering": "libmpv - Kết xuất Window ID gốc", "waveFormsAndSpectrogramFoldersContainsX": "Thư mục \"Waveforms\" và \"spectrogram\" chứa {0}", "deleteWaveformAndSpectrogramFoldersQuestion": "Xóa các tệp \"Waveforms\" và \"Spectrogram\"?", "waveformGenerateSpectrogram": "Tạo phổ âm", "waveformAutoGenerate": "Tự động tạo sóng âm khi mở video", "waveformSpectrogramMode": "Chế độ phổ âm", - "waveformCenterOnSingleClick": "Căn giữa khi nhấp đơn", - "waveformSingleClickSelectsSubtitle": "Chọn phụ đề khi nhấp đơn", "waveformRightClickSelectsSubtitle": "Chọn phụ đề khi nhấp chuột phải", - "waveformPauseOnSingleClick": "Tạm dừng khi nhấp đơn", "waveformDrawStyle": "Kiểu vẽ sóng âm", "waveformExtractAudioFormat": "Định dạng trích xuất âm thanh", "waveformExtractAudioSampleRate": "Tần số lấy mẫu trích xuất âm thanh", "waveformExtractAudioSampleRateOriginal": "Gốc (giữ nguyên nguồn)", "waveformExtractAudioBitRate": "Bitrate trích xuất âm thanh (MP3/M4A)", + "ffmpegUseCenterChannelOnly": "Chỉ dùng kênh giữa phía trước cho âm thanh 5.1 (FFmpeg)", "vlcWidRendering": "libVLC - Kết xuất Window ID gốc", + "ffmpegSoftwareRendering": "FFmpeg", + "downloadFfmpegLibs": "Tải xuống thư viện FFmpeg (cho trình phát video FFmpeg)", "subtitleGridEnterKeyAction": "Hành động phím Enter trong lưới phụ đề", "subtitleSingleClickAction": "Hành động nhấp đơn trong lưới phụ đề", "subtitleDoubleClickAction": "Hành động nhấp đúp trong lưới phụ đề", @@ -3290,6 +3620,7 @@ "subtitleGridFormattingShowFormatting": "Hiện định dạng", "subtitleGridFormattingShowTags": "Hiện thẻ", "subtitleGridFormattingHideTags": "Ẩn thẻ", + "subtitleGridFormattingShowFormattingKeepTags": "Hiển thị định dạng, giữ các thẻ không hiển thị", "waveformParagraphBackgroundColor": "Màu nền phụ đề sóng âm", "waveformParagraphSelectedBackgroundColor": "Màu nền phụ đề được chọn trên sóng âm", "waveformAllowOverlap": "Cho phép chồng lấn (khi di chuyển/thay đổi kích thước)", @@ -3299,17 +3630,18 @@ "saveAsBehaviorUseVideoFileName": "Dùng tên tệp video", "saveAsBehaviorUseSubtitleFileName": "Dùng tên tệp phụ đề", "saveAsAppendLanguageCodeTwoLetter": "Hai chữ cái", - "saveAsAppendLanguageCodeThreeLetter": "Ba chữ cái (ISO 639-2/T)", - "saveAsAppendLanguageCodeThreeLetterBibliographic": "Ba chữ cái (ISO 639-2/B)", "saveAsAppendLanguageCodeLanguageName": "Tên ngôn ngữ", "splitOddLineActionWeightTop": "Ưu tiên dòng trên", "splitOddLineActionWeightBottom": "Ưu tiên dòng dưới", "splitOddLinesAction": "Hành động chia dòng lẻ", "ocrUseWordSplitList": "OCR: dùng danh sách tách từ", "ocrGuessUnknownWords": "OCR: thử đoán các từ không xác định", + "spellCheckRememberUseAlwaysList": "Kiểm tra chính tả: nhớ danh sách \"Luôn dùng\"", + "fixShortDisplayTimesAllowMoveStartTime": "Sửa lỗi thường gặp: sửa thời gian hiển thị ngắn có thể di chuyển thời gian bắt đầu", "speechToTextSelectedLinesPromptFirstTimeOnly": "Chuyển giọng nói thành văn bản: các dòng đã chọn, chỉ hỏi ngôn ngữ/công cụ lần đầu", "multipleReplaceShowDotDotDotButtons": "Thay thế hàng loạt: hiện nút menu ngữ cảnh", "gridFocusTextboxAfterInsertNew": "Lưới: chuyển tiêu điểm vào ô văn bản sau khi chèn phụ đề mới", + "undoRedoGoToChangedLine": "Hoàn tác/làm lại: đi tới dòng đã thay đổi (và vị trí video)", "musicSymbol": "Ký hiệu âm nhạc", "musicSymbolsToReplace": "Các ký hiệu âm nhạc cần thay thế (phân cách bằng dấu phẩy)", "textToSpeechPromptMergeContinuationLines": "Chuyển văn bản thành giọng nói: hỏi gộp các dòng nối tiếp", @@ -3318,8 +3650,7 @@ "useFocusedButtonBackgroundColor": "Dùng màu nền cho nút đang có tiêu điểm", "focusedButtonBackgroundColor": "Màu nền nút đang có tiêu điểm", "forceCrLfOnSave": "Buộc CR+LF khi lưu (tệp phụ đề văn bản)", - "showWaveformToolbarPlayNext": "Thanh công cụ: hiện nút phát tiếp theo", - "showWaveformToolbarPlaySelection": "Thanh công cụ: hiện nút phát vùng chọn", + "linuxClipboardUseExternalTool": "Sao chép văn bản qua xclip/wl-copy (giữ ký tự có dấu)", "showFormatLimitWarning": "Cảnh báo khi lưu nếu các dòng vượt quá giới hạn của định dạng (ví dụ SCC 32 ký tự/dòng)", "textBoxButtonShowAutoBreak": "Ô văn bản: hiện nút tự động ngắt dòng", "textBoxButtonShowUnbreak": "Ô văn bản: hiện nút bỏ ngắt dòng", @@ -3331,12 +3662,18 @@ "waveformDoubleClickAction": "Hành động nhấp đúp trên sóng âm (sau hành động nhấp đơn)", "allSettings": "Tất cả cài đặt", "uiScale": "Tỷ lệ giao diện (%)", + "fontScale": "Tỷ lệ phông chữ (%)", + "textSelectionOpacity": "Độ mờ đục vùng chọn văn bản (%)", "waveformToolbarItems": "Mục trên thanh công cụ sóng âm", "matchIconColorToDarkTheme": "Khớp màu biểu tượng với màu chữ giao diện tối", "subtitlePreviewProperties": "Thuộc tính xem trước phụ đề", "usePositionFromSubtitleFile": "Dùng vị trí từ tệp phụ đề (TTML/PAC/EBU STL)", "marginIsPartOfSubtitleArea": "Lề là một phần của vùng phụ đề", "textJustify": "Canh đều các dòng", + "deinterlace": "Khử xen kẽ video", + "deinterlaceOff": "Tắt", + "deinterlaceAuto": "Tự động (chỉ khung hình xen kẽ)", + "deinterlaceAlways": "Luôn luôn", "pixelWidthInfo": "Đường màu xanh lá = giới hạn chiều rộng tối đa | Vùng màu đỏ = văn bản vượt quá giới hạn", "spellCheckEngineHunSpelll": "Hunspell", "spellCheckEngineMsWord": "MS Word", @@ -3345,117 +3682,89 @@ "waveformColorTheme": "Chủ đề màu:", "waveformSaveAsCustomTheme": "Lưu thành chủ đề tùy chỉnh...", "waveformLoadThemeDotDotDot": "Tải chủ đề...", - "waveformExportThemeDotDotDot": "Xuất chủ đề..." + "waveformExportThemeDotDotDot": "Xuất chủ đề...", + "waveformSaveCustomThemeTitle": "Lưu chủ đề tùy chỉnh", + "waveformThemeFileType": "Chủ đề dạng sóng Subtitle Edit", + "waveformThemeHighContrast": "Tương phản cao", + "waveformThemeOceanBlue": "Xanh đại dương", + "waveformThemeWarmSunset": "Hoàng hôn ấm", + "waveformThemeForest": "Rừng xanh", + "waveformThemeMidnightPurple": "Tím nửa đêm", + "waveformThemeRetroAmber": "Hổ phách cổ điển", + "waveformThemeIce": "Băng giá" }, "shortcuts": { - "title": "Phím tắt", "searchShortcuts": "Tìm phím tắt...", - "filter": "Lọc", - "categoryGeneral": "Chung", "categorySubtitleGridAndTextBox": "Danh sách phụ đề & ô văn bản", "categorySubtitleGrid": "Danh sách phụ đề", - "categoryWaveform": "Dạng sóng", "categoryTextBox": "Ô văn bản", "categoryFile": "Tệp", "categoryVideo": "Video", "categorySync": "Đồng bộ hóa", - "categoryTranslate": "Dịch", - "categorySearch": "Tìm kiếm", - "categoryTools": "Công cụ", "categoryAi": "AI", + "categoryCustom": "Tùy chỉnh", + "editCustomShortcut": "Chỉnh sửa phím tắt tùy chỉnh", + "customShortcutTextBoxKeyHint": "Hãy dùng phím kèm Ctrl/Alt ở đây - một phím đơn sẽ không còn gõ được trong ô văn bản.", + "customShortcutNumberX": "Phím tắt tùy chỉnh #{0}", + "customShortcutNumberXY": "Phím tắt tùy chỉnh #{0}: {1}", + "customShortcutStep": "Bước", + "customShortcutSteps": "Các bước", + "customShortcutStepRunCommand": "Chạy lệnh", + "customShortcutStepInsertText": "Chèn văn bản", + "customShortcutStepReplace": "Tìm và thay thế", + "customShortcutInsertAtCursor": "Tại con trỏ", + "customShortcutInsertAtStart": "Ở đầu văn bản", + "customShortcutInsertAtEnd": "Ở cuối văn bản", + "customShortcutSummaryRunCommandX": "Chạy: {0}", + "customShortcutSummaryInsertXY": "Chèn \"{0}\" ({1})", + "customShortcutSummaryReplaceXY": "Thay \"{0}\" bằng \"{1}\"", + "customShortcutSummaryReplaceRegexXY": "Thay biểu thức chính quy \"{0}\" bằng \"{1}\"", + "customShortcutPickCommand": "Vui lòng chọn lệnh để chạy.", + "customShortcutEnterText": "Vui lòng nhập văn bản cần chèn.", + "customShortcutEnterFind": "Vui lòng nhập văn bản cần tìm.", + "customShortcutInvalidRegexX": "Biểu thức chính quy không hợp lệ: {0}", + "customShortcutStepsHint": "Các bước chạy từ trên xuống dưới. Các bước văn bản tác động lên các dòng đã chọn; các lệnh mở cửa sổ sẽ tiếp tục sau khi cửa sổ đóng.", "activeIn": "Hoạt động trong", "activeInEverywhere": "Mọi nơi", - "generalSetupLikeSe4": "Thiết lập giống Subtitle Edit 4 (chủ đề, phím tắt, quy tắc thay thế)", - "generalMergeSelectedLines": "Gộp các dòng đã chọn", - "generalMergeWithPrevious": "Gộp với dòng trước", - "generalMergeWithNext": "Gộp với dòng sau", - "generalMergeWithPreviousAndUnbreak": "Gộp với dòng trước và bỏ ngắt dòng", - "generalMergeWithNextAndUnbreak": "Gộp với dòng sau và bỏ ngắt dòng", - "generalMergeWithPreviousAndAutoBreak": "Gộp với dòng trước và tự động ngắt dòng", - "generalMergeWithNextAndAutoBreak": "Gộp với dòng sau và tự động ngắt dòng", - "generalMergeSelectedLinesAndAutoBreak": "Gộp các dòng đã chọn và tự động ngắt dòng", "generalMergeSelectedLinesAndUnbreak": "Gộp các dòng đã chọn và bỏ ngắt dòng", "generalMergeSelectedLinesAndUnbreakCjk": "Gộp các dòng đã chọn và bỏ ngắt dòng không có khoảng trắng (CJK)", "generalUnbreakNoSpaceCjk": "Bỏ ngắt dòng không có khoảng trắng (CJK)", - "generalMergeSelectedLinesOnlyFirstText": "Gộp các dòng đã chọn, chỉ giữ văn bản đầu tiên", + "generalToggleBreak": "Bật/tắt ngắt dòng", "generalMergeSelectedLinesBilingual": "Gộp các dòng đã chọn dạng song ngữ", - "generalMergeWithPreviousBilingual": "Gộp với dòng trước dạng song ngữ", - "generalMergeWithNextBilingual": "Gộp với dòng sau dạng song ngữ", - "generalMergeOriginalAndTranslation": "Gộp bản gốc và bản dịch", "generalToggleTranslationMode": "Bật/tắt chế độ dịch", - "generalSwitchOriginalAndTranslation": "Hoán đổi bản gốc và bản dịch", - "generalSwitchOriginalAndTranslationTextBoxes": "Hoán đổi ô văn bản bản gốc và bản dịch", "generalToggleTranslationAndOriginalInPreviews": "Chuyển đổi bản dịch và bản gốc trong xem trước video/âm thanh", "generalChooseLayout": "Chọn bố cục", - "generalLayoutChooseX": "Bố cục {0}", - "generalPlayFirstSelected": "Phát dòng được chọn đầu tiên", - "generalGoToFirstSelectedLine": "Đi tới dòng được chọn đầu tiên", - "generalGoToNextEmptyLine": "Đi tới dòng trống tiếp theo", "generalGoToNextSubtitle": "Đi tới phụ đề tiếp theo", "generalGoToNextSubtitlePlayTranslate": "Đi tới phụ đề tiếp theo (phát khi dịch)", "generalGoToNextSubtitleCursorAtEnd": "Đi tới phụ đề tiếp theo (con trỏ ở cuối)", "generalGoToPrevSubtitle": "Đi tới phụ đề trước", "generalGoToPrevSubtitlePlayTranslate": "Đi tới phụ đề trước (phát khi dịch)", - "generalGoToStartOfCurrentSubtitle": "Đi tới đầu phụ đề hiện tại", - "generalGoToEndOfCurrentSubtitle": "Đi tới cuối phụ đề hiện tại", - "generalGoToPreviousSubtitleAndFocusVideo": "Đi tới phụ đề trước và chuyển tiêu điểm sang video", - "generalGoToNextSubtitleAndFocusVideo": "Đi tới phụ đề tiếp theo và chuyển tiêu điểm sang video", - "generalGoToPrevSubtitleAndPlay": "Đi tới phụ đề trước và phát", - "generalGoToNextSubtitleAndPlay": "Đi tới phụ đề tiếp theo và phát", - "generalGoToPreviousSubtitleAndFocusWaveform": "Đi tới phụ đề trước và chuyển tiêu điểm sang dạng sóng", - "generalGoToNextSubtitleAndFocusWaveform": "Đi tới phụ đề tiếp theo và chuyển tiêu điểm sang dạng sóng", - "generalGoToLineNumber": "Đi tới số dòng", "generalGoToFirstLine": "Đi tới dòng đầu tiên", "generalGoToLastLine": "Đi tới dòng cuối cùng", "alsoSetVideoPosition": "Đồng thời đặt vị trí video", "generalGoToVideoPosition": "Đi tới vị trí video", "generalToggleItalic": "Bật/tắt in nghiêng", "generalToggleBold": "Bật/tắt in đậm", - "generalToggleBookmarks": "Bật/tắt dấu trang", - "generalFocusTextBox": "Chuyển tiêu điểm sang ô văn bản", - "generalToggleBookmarksWithText": "Bật/tắt dấu trang kèm văn bản", - "generalEditBookmarks": "Chỉnh sửa dấu trang", - "fileNew": "Mới", "generalToggleUnderline": "Bật/tắt gạch chân", + "generalToggleBox": "Bật/tắt hộp (EBU STL)", "fileOpen": "Mở", "fileOpenKeepVideo": "Mở (giữ video)", "fileSave": "Lưu", "fileSaveAs": "Lưu thành", - "fileSaveAll": "Lưu tất cả", "fileSaveOriginal": "Lưu bản gốc", - "fileSaveOriginalAs": "Lưu bản gốc thành", - "fileOpenOriginalSubtitle": "Mở phụ đề gốc", - "fileCloseOriginalSubtitle": "Đóng phụ đề gốc", - "fileTranslatedSubtitle": "Phụ đề đã dịch", "fileCompare": "So sánh", "fileStatistics": "Thống kê", "fileImportPlainText": "Nhập văn bản thuần", - "fileImportBluRaySupForOcr": "Nhập Blu-ray SUP để OCR", - "fileImportBluRaySupForEdit": "Nhập Blu-ray SUP để chỉnh sửa", - "fileImportTimeCodes": "Nhập mã thời gian", "fileExportEbuStl": "Xuất EBU STL", "fileExportPac": "Xuất PAC", - "fileExportEdlClipName": "Xuất tên clip EDL", - "fileExportPlainText": "Xuất văn bản thuần", - "fileExportCustomTextFormat1": "Xuất định dạng văn bản tùy chỉnh 1", - "fileExportCustomTextFormat2": "Xuất định dạng văn bản tùy chỉnh 2", - "fileExportCustomTextFormat3": "Xuất định dạng văn bản tùy chỉnh 3", "fileExit": "Thoát", "openSeDataFolder": "Mở thư mục Subtitle Edit", - "editFind": "Tìm", - "editFindNext": "Tìm tiếp", "editFindPrevious": "Tìm trước", - "editReplace": "Thay thế", - "editMultipleReplace": "Thay thế nhiều lần", "fillSelectedLinesWithClipboard": "Điền các dòng đã chọn bằng văn bản trong bộ nhớ tạm", - "editModifySelection": "Sửa đổi vùng chọn", - "listSelectAll": "Chọn tất cả", - "listSelectFirst": "Chọn dòng đầu", - "listSelectLast": "Chọn dòng cuối", "listInverseSelection": "Đảo ngược vùng chọn", "listDeleteSelection": "Xóa vùng chọn", + "deleteSelectionEverywhere": "Xóa vùng chọn (mọi nơi)", "rippleDeleteSelection": "Xóa vùng chọn kiểu dồn (ripple)", - "settings": "Cài đặt", "assigned": "Đã gán", "unassigned": "Chưa gán", "pressedKeyX": "Phím đã nhấn: {0}", @@ -3463,6 +3772,7 @@ "detectKey": "Phát hiện phím", "control": "Control", "alt": "Alt", + "altGr": "AltGr", "win": "Win", "shift": "Shift", "controlMac": "⌃", @@ -3473,11 +3783,14 @@ "resetShortcutsDetail": "Bạn có muốn đặt lại tất cả phím tắt về giá trị mặc định không?", "togglePlayPause": "Bật/tắt phát/tạm dừng", "toggleLockTimeCodes": "Bật/tắt khóa mã thời gian", + "toggleTimeCodeMode": "Chuyển chế độ mã thời gian (thời gian/khung hình/số khung hình)", + "toggleScreenPrivacy": "Bật/tắt riêng tư màn hình (ẩn tên tệp, rồi ẩn cả văn bản)", + "screenPrivacyFileNamesHidden": "Riêng tư màn hình: đã ẩn tên tệp", + "screenPrivacyFileNamesAndTextsHidden": "Riêng tư màn hình: đã ẩn tên tệp và văn bản", + "screenPrivacyOff": "Riêng tư màn hình: tắt", "duplicateSelectedLines": "Nhân đôi các dòng đã chọn", "sourceView": "Xem mã nguồn", - "showAlignmentPicker": "Căn chỉnh", "addOrEditBookmark": "Thêm hoặc chỉnh sửa dấu trang", - "listBookmarks": "Liệt kê dấu trang", "toggleBookmark": "Bật/tắt dấu trang (các dòng đã chọn, không có văn bản)", "toggleForcedSelectedLines": "Bật/tắt bắt buộc (các dòng đã chọn)", "goToNextBookmark": "Đi tới dấu trang tiếp theo", @@ -3488,12 +3801,12 @@ "waveformSetStartAndSetEndOfPreviousMinusGap": "Đặt điểm bắt đầu và đặt điểm kết thúc của dòng trước trừ khoảng cách", "waveformSetEndAndStartOfNextAfterGap": "Đặt điểm kết thúc và điểm bắt đầu của dòng sau cộng khoảng cách", "waveformSetEndAndStartOfNextAfterGapAndGoToNext": "Đặt điểm kết thúc và điểm bắt đầu của dòng sau cộng khoảng cách rồi đi tới dòng sau", - "fetchFirstWordFromNextSubtitle": "Lấy từ đầu tiên từ phụ đề tiếp theo", "moveLastWordToNextSubtitle": "Chuyển từ cuối cùng sang phụ đề tiếp theo", "moveLastWordFromFirstLineDownCurrentSubtitle": "Chuyển từ cuối của dòng đầu xuống dưới (phụ đề hiện tại)", "moveFirstWordFromNextLineUpCurrentSubtitle": "Chuyển từ đầu của dòng sau lên trên (phụ đề hiện tại)", "moveTextFromCursorToNextAndGoToNext": "Chuyển văn bản sau vị trí con trỏ sang phụ đề tiếp theo và đi tới phụ đề đó", "moveTextFromCursorToNextAndGoToNextAndPlay": "Chuyển văn bản sau vị trí con trỏ sang phụ đề tiếp theo, đi tới phụ đề đó và phát", + "moveTextFromCursorToNextAndGoToNextAndPlayAndPause": "Chuyển văn bản sau vị trí con trỏ sang phụ đề tiếp theo, đi tới phụ đề đó, phát và tạm dừng ở cuối", "breakAtFirstSpaceFromCursor": "Ngắt tại khoảng trắng đầu tiên từ vị trí con trỏ", "breakAtFirstSpaceFromCursorAndGoToNext": "Ngắt tại khoảng trắng đầu tiên từ vị trí con trỏ và đi tới dòng tiếp theo", "toggleFocusGridAndWaveform": "Chuyển tiêu điểm giữa lưới phụ đề và dạng sóng/phổ âm", @@ -3505,7 +3818,6 @@ "videoGoToPreviousTimeCode": "Đi tới mã thời gian trước", "videoGoToNextTimeCode": "Đi tới mã thời gian tiếp theo", "goToNextLineAndSetVideoPosition": "Đi tới phụ đề tiếp theo và đặt vị trí video", - "textBoxDeleteSelectionNoClipboard": "Ô văn bản: Xóa vùng chọn (không dùng bộ nhớ tạm)", "textBoxDeleteForward": "Ô văn bản: Xóa vùng chọn hoặc ký tự kế tiếp (xóa tới trước)", "textBoxCut": "Ô văn bản: Cắt", "textBoxCut2": "Ô văn bản: Cắt (thay thế)", @@ -3524,6 +3836,7 @@ "listViewColumnTextUp": "Cột, chuyển văn bản lên", "listViewColumnTextDown": "Cột, chuyển văn bản xuống", "autoTranslateSelectedLines": "Tự động dịch các dòng đã chọn...", + "autoTranslateSelectedLinesNoPrompt": "Tự động dịch các dòng đã chọn (không hỏi, dùng công cụ/ngôn ngữ lần trước)", "setAssaResolution": "Đặt độ phân giải ASSA (PlayResX/PlayResY)", "setShortcutForX": "Đặt phím tắt cho \"{0}\"", "commandFileNewKeepVideo": "Mới (giữ video)", @@ -3531,29 +3844,22 @@ "fileCloseOriginal": "Đóng bản gốc", "fileCloseTranslation": "Đóng bản dịch", "restoreAutoBackup": "Khôi phục sao lưu tự động", - "openContainingFolder": "Mở thư mục chứa tệp", "importTimeCodes": "Nhập mã thời gian", "importSubtitleWithManuallyChosenEncoding": "Nhập phụ đề với bảng mã chọn thủ công", "exportBluRaySup": "Xuất Blu-ray SUP", "exportCustomTextFormat": "Xuất định dạng văn bản tùy chỉnh", "exportPlainText": "Xuất văn bản thuần", - "showHistory": "Hiện lịch sử", "toggleRightToLeft": "Bật/tắt phải sang trái", "modifySelection": "Sửa đổi vùng chọn", "adjustDurations": "Điều chỉnh thời lượng", "applyDurationLimits": "Áp dụng giới hạn thời lượng", "batchConvert": "Chuyển đổi hàng loạt", - "bridgeGaps": "Nối khoảng cách", "applyMinGap": "Áp dụng khoảng cách tối thiểu", - "fixCommonErrors": "Sửa lỗi thường gặp", "makeEmptyTranslationFromCurrentSubtitle": "Tạo bản dịch trống từ phụ đề hiện tại", - "mergeLinesWithSameText": "Gộp các dòng có cùng văn bản", - "mergeLinesWithSameTimeCodes": "Gộp các dòng có cùng mã thời gian", "splitBreakLongLines": "Tách/cân bằng lại các dòng dài", "mergeShortLines": "Gộp các dòng ngắn", "mergeContinuationLines": "Gộp các dòng nối tiếp", "snapAllTimesToFrames": "Căn tất cả thời gian theo khung hình", - "removeTextForHearingImpaired": "Xóa văn bản dành cho người khiếm thính", "joinSubtitles": "Ghép các phụ đề", "splitSubtitle": "Tách phụ đề", "spellCheck": "Kiểm tra chính tả", @@ -3575,7 +3881,6 @@ "adjustAllTimes": "Điều chỉnh tất cả thời gian", "visualSync": "Đồng bộ trực quan", "translateViaCopyPaste": "Dịch qua sao chép-dán", - "shortcuts": "Phím tắt", "wordLists": "Danh sách từ", "chooseUiLanguage": "Chọn ngôn ngữ giao diện", "chooseRuleProfile": "Chọn hồ sơ quy tắc", @@ -3586,6 +3891,7 @@ "textBoxItalic": "Ô văn bản in nghiêng", "textBoxBold": "Ô văn bản in đậm", "textBoxUnderline": "Ô văn bản gạch chân", + "textBoxBox": "Hộp trong ô văn bản (EBU STL)", "resetWaveformZoomAndSpeed": "Đặt lại thu phóng dạng sóng và tốc độ phát (play rate)", "togglePlaybackSpeed": "Bật/tắt tốc độ phát (play rate)", "playbackSpeedSlower": "Giảm tốc độ phát (play rate)", @@ -3597,22 +3903,28 @@ "seekSilenceForward": "Tìm khoảng lặng tiến tới", "waveformGuessStart": "Đoán thời điểm bắt đầu từ dạng sóng", "waveformGuessEnd": "Đoán thời điểm kết thúc từ dạng sóng", + "waveformGuessStartAndEnd": "Đoán thời gian bắt đầu và kết thúc từ dạng sóng", "setVideoPositionCurrentSubtitleStart": "Đặt vị trí video tại điểm bắt đầu dòng hiện tại", "goToSubtitlePositionAndPause": "Đi tới vị trí phụ đề và tạm dừng", "setVideoPositionCurrentSubtitleEnd": "Đặt vị trí video tại điểm kết thúc dòng hiện tại", "toggleAudioTracks": "Chuyển đổi các luồng âm thanh", - "listErrors": "Liệt kê lỗi", "goToNextError": "Đi tới lỗi tiếp theo", "goToPreviousError": "Đi tới lỗi trước", "addNameToNameList": "Thêm tên vào danh sách tên", "findDoubleWords": "Tìm từ bị lặp", "findDoubleLines": "Tìm dòng bị lặp", "colorX": "Màu {0}", - "removeColor": "Xóa màu", "surroundWith": "Bao quanh bằng...", "surroundWithXY": "Bao quanh bằng {0}/{1}", - "repeatLine": "Lặp lại dòng", "surroundWithNumberX": "Bao quanh bằng #{0}", + "surroundWithBehavior": "Hành vi", + "surroundWithBehaviorToggle": "Bật/tắt (thêm, hoặc xóa nếu đã có)", + "surroundWithBehaviorAdd": "Thêm (mỗi lần)", + "surroundWithBehaviorRemove": "Xóa (tất cả)", + "surroundWithBehaviorRemoveOnce": "Xóa (mỗi lần một)", + "surroundWithWorksOn": "Tác động lên", + "surroundWithScopeSelectionOrText": "Vùng chọn, nếu không thì toàn bộ văn bản", + "surroundWithScopeEachLine": "Từng dòng", "searchVia": "Tìm kiếm qua", "searchViaX": "Tìm kiếm qua {0}", "searchViaNumberX": "Tìm kiếm qua #{0}", @@ -3640,6 +3952,11 @@ "copySubtitlePathToClipboard": "Sao chép đường dẫn phụ đề vào bộ nhớ tạm", "copySubtitleOriginalPathToClipboard": "Sao chép đường dẫn phụ đề gốc vào bộ nhớ tạm", "focusTextBox": "Chuyển tiêu điểm sang ô văn bản", + "focusSubtitleListView": "Tập trung vào danh sách phụ đề", + "focusWaveform": "Tập trung vào dạng sóng", + "focusOriginalTextBox": "Tập trung vào ô văn bản gốc", + "textBoxGoToStart": "Ô văn bản, đi tới đầu", + "textBoxGoToEnd": "Ô văn bản, đi tới cuối", "sortByNumber": "Sắp xếp theo số", "sortByStartTime": "Sắp xếp theo thời gian \"Hiện\"", "sortByEndTime": "Sắp xếp theo thời gian \"Ẩn\"", @@ -3664,7 +3981,6 @@ "assaStyles": "Kiểu ASSA", "assaProperties": "Thuộc tính ASSA", "assaAttachments": "Tệp đính kèm ASSA", - "assaVideoColorPicker": "ASSA Chọn màu từ video", "recalculateDurationSelectedLines": "Tính lại thời lượng (các dòng đã chọn)", "setDurationMaxCpsSelectedLines": "Đặt thời lượng theo CPS tối đa (các dòng đã chọn)", "toggleWaveformAndSpectrogramHeight": "Bật/tắt chia chiều cao dạng sóng/phổ âm", @@ -3678,9 +3994,9 @@ "selectionToSentenceCase": "Ô văn bản: Phần đã chọn thành Kiểu câu", "selectionToggleCasing": "Ô văn bản: Chuyển đổi kiểu chữ (HOA -> thường -> Kiểu Tiêu Đề)", "googleIt": "Tìm trên Google (văn bản đã chọn)", - "setActorXY": "Đặt diễn viên {0}: {1}", + "setActorX": "Đặt diễn viên: {0}", "setNewActor": "Đặt diễn viên mới...", - "setActorSlotXTitle": "Ô diễn viên {0}", + "setStyleX": "Đặt kiểu: {0}", "toggleDialogDashes": "Bật/tắt gạch đầu dòng hội thoại", "mergeWithLineBeforeAsDialog": "Gộp với dòng trước thành hội thoại", "mergeWithLineAfterAsDialog": "Gộp với dòng sau thành hội thoại", @@ -3691,13 +4007,27 @@ "moveStartOneFrameBackKeepGapPrev": "Lùi điểm bắt đầu một khung hình (giữ khoảng cách với dòng trước nếu gần)", "moveStartOneFrameForwardKeepGapPrev": "Tiến điểm bắt đầu một khung hình (giữ khoảng cách với dòng trước nếu gần)", "moveEndOneFrameBackKeepGapNext": "Lùi điểm kết thúc một khung hình (giữ khoảng cách với dòng sau nếu gần)", - "moveEndOneFrameForwardKeepGapNext": "Tiến điểm kết thúc một khung hình (giữ khoảng cách với dòng sau nếu gần)" + "moveEndOneFrameForwardKeepGapNext": "Tiến điểm kết thúc một khung hình (giữ khoảng cách với dòng sau nếu gần)", + "moveStartXMsBack": "Lùi thời điểm bắt đầu X ms (X đặt trong Cài đặt, cũng dùng trong Đồng bộ trực quan)", + "moveStartXMsForward": "Tiến thời điểm bắt đầu X ms (X đặt trong Cài đặt, cũng dùng trong Đồng bộ trực quan)", + "moveEndXMsBack": "Lùi thời điểm kết thúc X ms (X đặt trong Cài đặt, cũng dùng trong Đồng bộ trực quan)", + "moveEndXMsForward": "Tiến thời điểm kết thúc X ms (X đặt trong Cài đặt, cũng dùng trong Đồng bộ trực quan)", + "moveSelectedLinesXMsBack": "Lùi các dòng đã chọn X ms (X đặt trong Cài đặt)", + "moveSelectedLinesXMsForward": "Tiến các dòng đã chọn X ms (X đặt trong Cài đặt)", + "moveSelectedLinesAndForwardXMsBack": "Lùi các dòng đã chọn và tất cả các dòng sau X ms (X đặt trong Cài đặt)", + "moveSelectedLinesAndForwardXMsForward": "Tiến các dòng đã chọn và tất cả các dòng sau X ms (X đặt trong Cài đặt)", + "moveAllLinesXMsBack": "Lùi tất cả các dòng X ms (X đặt trong Cài đặt)", + "moveAllLinesXMsForward": "Tiến tất cả các dòng X ms (X đặt trong Cài đặt)", + "moveSelectedLinesCustomMsBack": "Di chuyển các dòng đã chọn, lùi số mili giây tùy chỉnh ({0:#,###,##0}), {1}", + "moveSelectedLinesCustomMsForward": "Di chuyển các dòng đã chọn, tiến số mili giây tùy chỉnh ({0:#,###,##0}), {1}", + "moveSelectedLinesAndForwardCustomMsBack": "Di chuyển các dòng đã chọn và tất cả các dòng sau, lùi số mili giây tùy chỉnh ({0:#,###,##0}), {1}", + "moveSelectedLinesAndForwardCustomMsForward": "Di chuyển các dòng đã chọn và tất cả các dòng sau, tiến số mili giây tùy chỉnh ({0:#,###,##0}), {1}", + "moveAllLinesCustomMsBack": "Di chuyển tất cả các dòng, lùi số mili giây tùy chỉnh ({0:#,###,##0}), {1}", + "moveAllLinesCustomMsForward": "Di chuyển tất cả các dòng, tiến số mili giây tùy chỉnh ({0:#,###,##0}), {1}" }, "wordLists": { "title": "Danh sách từ", "addName": "Thêm tên", - "addWord": "Thêm từ", - "addPair": "Thêm cặp", "nameAndIgnoreList": "Danh sách tên/bỏ qua", "userWords": "Danh sách từ của người dùng", "ocrFixList": "Danh sách sửa lỗi OCR", @@ -3714,7 +4044,6 @@ "noPluginsInstalled": "Chưa cài đặt phần mở rộng nào", "getPluginsOnline": "Tải phần mở rộng trực tuyến...", "getPluginsTitle": "Tải phần mở rộng", - "enabled": "Đã bật", "disabled": "Đã tắt", "notSupportedOnThisOs": "Không được hỗ trợ trên hệ điều hành này", "removePluginXConfirm": "Xóa phần mở rộng '{0}'? Thao tác này sẽ xóa thư mục phần mở rộng:{1}", @@ -3731,17 +4060,19 @@ "downloadingXPercent": "Đang tải xuống {0}%...", "pluginXInstalled": "Đã cài đặt '{0}'.", "couldNotInstallPluginX": "Không thể cài đặt '{0}': {1}", - "notInstalled": "Chưa cài đặt", "install": "Cài đặt", "reinstall": "Cài đặt lại", - "update": "Cập nhật", "installedX": "Đã cài đặt ({0})", "updateAvailableXToY": "Có bản cập nhật ({0} → {1})", "applyPluginToWhichLinesX": "Áp dụng '{0}' cho:", "applyToSelectedLinesX": "Các dòng đã chọn ({0})", "applyToAllLinesX": "Tất cả các dòng ({0})", "updateAllXAvailable": "Cập nhật tất cả ({0})", - "updatingXOfY": "Đang cập nhật {0}/{1}..." + "updatingXOfY": "Đang cập nhật {0}/{1}...", + "doNotAskAgain": "Không hỏi lại (thay đổi trong Quản lý phần mở rộng)", + "applyPluginsTo": "Áp dụng phần mở rộng cho", + "askEachTime": "Hỏi mỗi lần", + "allLines": "Tất cả các dòng" }, "help": { "aboutSubtitleEdit": "Giới thiệu Subtitle Edit", @@ -3785,6 +4116,8 @@ "nOcrDatabase": "Cơ sở dữ liệu nOCR", "drawMode": "Chế độ vẽ:", "addNewCharcter": "Thêm ký tự mới", + "addNOcrCharacterTitle": "Thêm ký tự nOCR cho dòng {0}, ký tự {1}/{2} dùng cơ sở dữ liệu \"{3}\"", + "addBinaryOcrCharacterTitle": "Thêm ký tự OCR nhị phân cho dòng {0}, ký tự {1}/{2} dùng cơ sở dữ liệu \"{3}\"", "lineIndexX": "Dòng {0}", "inspectNOcrAdditions": "Kiểm tra các bổ sung nOCR mới", "ocrSelectedLines": "OCR các dòng đã chọn", @@ -3833,6 +4166,8 @@ "binaryImageCompareInspectImageMatches": "\"So sánh hình ảnh nhị phân\" - Kiểm tra các kết quả khớp hình ảnh", "saveBlankTextTitle": "Lưu văn bản trống?", "saveBlankTextPrompt": "Lưu văn bản trống cho hình ảnh?", + "drawnLinesDoNotMatchTitle": "Các đường không khớp với hình ảnh", + "drawnLinesDoNotMatchPrompt": "Một số đường đã vẽ không khớp với hình ảnh (đường xanh lá phải nằm trên ký tự, đường đỏ phải nằm ngoài ký tự), nên ký tự này sẽ không được nhận dạng.\n\nVẫn lưu?", "yesAndNeverAskAgain": "Có và không bao giờ hỏi lại", "importTextFromSubtitleDotDotDot": "Nhập văn bản từ phụ đề...", "importTextFromSubtitleOverwritePrompt": "Một số dòng đã có văn bản OCR. Ghi đè văn bản hiện có?", @@ -3858,7 +4193,6 @@ "vobSubColorsTitle": "Màu VobSub/DVD", "vobSubColorsHeader": "Tùy chỉnh bốn màu VobSub", "vobSubColorsDescription": "Phụ đề VobSub/DVD sử dụng bốn màu được lập chỉ mục: nền, hoa văn, nhấn mạnh 1 và nhấn mạnh 2. Chọn từng màu bên dưới để ghi đè bảng màu nhúng - hữu ích khi màu gốc cho độ tương phản OCR kém.", - "vobSubColorBackground": "Nền", "vobSubColorPattern": "Hoa văn", "vobSubColorEmphasis1": "Nhấn mạnh 1", "vobSubColorEmphasis2": "Nhấn mạnh 2", @@ -3892,9 +4226,26 @@ "crispEmbedSettingsTitle": "Cài đặt CrispEmbed", "crispEmbedDescription": "Công cụ OCR cục bộ với nhiều backend mô hình - tải công cụ và các mô hình tại đây.", "trainNOcrDatabase": "Huấn luyện cơ sở dữ liệu nOCR...", + "optimizeNOcrDatabase": "Tối ưu cơ sở dữ liệu nOCR...", + "optimizeNOcrDatabaseQuestionXY": "Xóa các đường không giúp phân biệt ký tự với các ký tự tương tự trong cơ sở dữ liệu nOCR \"{0}\"?\n\nViệc này làm cơ sở dữ liệu nhỏ hơn và OCR nhanh hơn, thường ít lỗi hơn. Mất tối đa một phút.\n\nBản sao lưu được lưu thành \"{1}\".", + "optimizingNOcrDatabaseXY": "Đang tối ưu cơ sở dữ liệu nOCR... {0}/{1}", + "nOcrDatabaseOptimizedXYZ": "Đã tối ưu cơ sở dữ liệu nOCR \"{0}\": {1:#,##0} -> {2:#,##0} đường.", "startTraining": "Bắt đầu huấn luyện", "abortTraining": "Hủy huấn luyện", "trainingOptions": "Tùy chọn huấn luyện", + "trainNOcrSubtitle": "Kết xuất ký tự bằng các phông chữ đã cài đặt và dạy nOCR hình dạng của chúng. Chọn các phông chữ mà phụ đề của bạn dùng.", + "xFontsSelected": "Đã chọn {0:#,##0}", + "subtitleFonts": "Phông chữ phụ đề", + "emptyDatabase": "Cơ sở dữ liệu trống", + "startFromDatabase": "Bắt đầu từ", + "startFromDatabaseHint": "Thêm các ký tự đã huấn luyện vào bản sao của cơ sở dữ liệu hiện có (khuyến nghị) hoặc bắt đầu từ cơ sở dữ liệu trống", + "fontSizes": "Cỡ phông chữ", + "fontSizesHint": "Các cỡ phông chữ tính bằng pixel để kết xuất, phân cách bằng dấu phẩy, ví dụ \"30, 40\"", + "numberOfLineSegmentsHint": "Số đường kiểm tra cho mỗi ký tự - càng nhiều càng chặt chẽ nhưng chậm hơn", + "enterDatabaseName": "Nhập tên cho cơ sở dữ liệu", + "trainingFontXSizeYCharacterZ": "Đang huấn luyện \"{0}\" ở {1} px: {2}", + "xLearned": "Đã học {0:#,##0}", + "xSkipped": "Đã bỏ qua {0:#,##0}", "charactersToTrain": "Các ký tự cần huấn luyện", "letterCombinationsToTrain": "Các tổ hợp chữ cái có thể bị tách thành một hình ảnh", "importCharactersFromSubtitleFile": "Nhập ký tự từ tệp phụ đề...", @@ -3921,6 +4272,8 @@ "drawZoomOut": "Thu nhỏ (Ctrl+-)", "drawResetView": "Đặt lại chế độ xem (Ctrl+0)", "drawToggleGrid": "Bật/tắt lưới (Ctrl+G)", + "drawTogglePreview": "Bật/tắt xem trước (F9)", + "drawPreviewFailed": "Xem trước thất bại - cần ffmpeg với libass (bộ lọc subtitles)", "drawCopyToClipboard": "Sao chép vào bộ nhớ tạm (Ctrl+C)", "drawShapes": "Hình", "drawSelectedPoint": "Điểm đã chọn", @@ -3928,16 +4281,86 @@ "drawSelectedLayer": "Lớp đã chọn", "drawToolX": "Công cụ: {0}", "drawHelpText": "Nhấp để thêm điểm • Enter/F8 để đóng hình • Shift+Kéo để di chuyển • Ctrl+Cuộn để thu phóng", + "drawMoveToLayer": "Chuyển sang lớp", + "drawFlipHorizontal": "Lật ngang", + "drawFlipVertical": "Lật dọc", + "drawHideShape": "Ẩn hình", + "drawShowShape": "Hiện hình", + "drawHideLayer": "Ẩn lớp", + "drawShowLayer": "Hiện lớp", + "drawDeleteLayer": "Xóa lớp", + "drawDeletePoint": "Xóa điểm", + "drawCancelDrawing": "Hủy vẽ", + "drawLayerX": "Lớp {0}", + "drawImportSvg": "Nhập hình ảnh SVG...", + "drawRotateClockwise": "Xoay 90° theo chiều kim đồng hồ", + "drawConvertToCurve": "Chuyển đoạn thành đường cong", + "drawBackground": "Nền", + "drawPosition": "Vị trí", + "drawZoom": "Thu phóng", + "drawShapeTool": "Công cụ hình", + "drawColorPickerTool": "Lấy màu từ một hình hoặc nền", + "drawShapeLibrary": "Thư viện hình", + "drawShapeLibraryHint": "Kéo trên khung vẽ để đặt một hình, hoặc nhấp để dùng kích thước mặc định. Shift giữ nguyên tỷ lệ.", + "drawAddToLibrary": "Thêm vào thư viện hình...", + "drawRemoveFromLibrary": "Xóa khỏi thư viện hình", + "drawShapeName": "Tên hình", + "drawCategorySpeechBubbles": "Bong bóng thoại", + "drawCategoryArrows": "Mũi tên", + "drawCategoryBasicShapes": "Hình cơ bản", + "drawCategorySymbols": "Ký hiệu", + "drawCategoryMyShapes": "Hình của tôi", + "drawShapeSpeechBubble": "Bong bóng thoại", + "drawShapeSpeechBubbleRight": "Bong bóng thoại, đuôi bên phải", + "drawShapeSpeechBubbleUp": "Bong bóng thoại, đuôi hướng lên", + "drawShapeOvalBubble": "Bong bóng thoại hình bầu dục", + "drawShapeBoxBubble": "Bong bóng thoại hình hộp", + "drawShapeThoughtBubble": "Bong bóng suy nghĩ", + "drawShapeShoutBubble": "Bong bóng hét", + "drawShapeCaptionBox": "Hộp chú thích", + "drawShapeArrow": "Mũi tên", + "drawShapeDoubleArrow": "Mũi tên hai chiều", + "drawShapeChevron": "Chevron", + "drawShapeCurvedArrow": "Mũi tên cong", + "drawShapeRoundedRectangle": "Hình chữ nhật bo góc", + "drawShapeTriangle": "Tam giác", + "drawShapeDiamond": "Hình thoi", + "drawShapePentagon": "Ngũ giác", + "drawShapeHexagon": "Lục giác", + "drawShapeOctagon": "Bát giác", + "drawShapeStar": "Ngôi sao", + "drawShapeRing": "Vòng tròn", + "drawShapePlus": "Dấu cộng", + "drawShapeBanner": "Băng rôn", + "drawShapeHeart": "Trái tim", + "drawShapeCheck": "Dấu kiểm", + "drawShapeMusicNote": "Nốt nhạc", + "drawShapeMusicNotes": "Các nốt nhạc", + "drawShapeLightning": "Tia sét", + "drawShapeCloud": "Đám mây", + "drawShapeMoon": "Mặt trăng", + "drawShapeBadge": "Huy hiệu", + "drawSize": "Kích thước", + "drawLayer": "Lớp", + "drawBackgroundVideoFrame": "Khung hình video (vị trí hiện tại)", + "drawBackgroundVideoFrameAt": "Khung hình video tại...", + "drawBackgroundImage": "Tệp hình ảnh...", + "drawBackgroundNone": "Không có nền", + "drawBackgroundStretch": "Kéo giãn hình ảnh theo khung vẽ", + "drawBackgroundOpacity": "Độ mờ đục nền", + "drawBackgroundVideoFrameFailed": "Không thể lấy khung hình video tại {0}.", + "drawImages": "Hình ảnh", + "drawConvertToLine": "Chuyển đoạn thành đường thẳng", + "drawConvertShapeToCurves": "Chuyển tất cả đường thẳng thành đường cong", + "drawConvertShapeToLines": "Chuyển tất cả đường cong thành đường thẳng", + "drawRotateCounterClockwise": "Xoay 90° ngược chiều kim đồng hồ", + "drawSvgImages": "Hình ảnh SVG", + "drawSvgNoShapes": "Không tìm thấy hình tô màu hoặc nét vẽ nào trong tệp SVG.", "progressBarTitle": "Thanh tiến trình ASSA", "progressBarSettings": "Thanh tiến trình", - "progressBarPosition": "Vị trí", - "progressBarBottom": "Dưới cùng", - "progressBarTop": "Trên cùng", "mouseOverColor": "Màu khi rê chuột", "clickedColor": "Màu khi nhấp", "progressBarForeColor": "Màu tiền cảnh", - "progressBarBackColor": "Màu nền", - "progressBarStyle": "Kiểu", "progressBarSquareCorners": "Góc vuông", "progressBarRoundedCorners": "Góc bo tròn", "progressBarChapters": "Chương", @@ -3946,8 +4369,6 @@ "progressBarXAdjustment": "Điều chỉnh X", "progressBarYAdjustment": "Điều chỉnh Y", "progressBarTextAlignment": "Căn chỉnh văn bản", - "progressBarTakePosFromVideo": "Lấy vị trí từ video", - "progressBarPreview": "Xem trước", "fontCollectorTitle": "Trình thu thập phông chữ", "fontCollectorUsedIn": "Dùng trong", "fontCollectorFontFiles": "Tệp phông chữ", @@ -3994,9 +4415,7 @@ "resolutionResamplerVideoDiffers": "Độ phân giải phụ đề ({0}x{1}) khác với độ phân giải video ({2}x{3}).\nĐổi phụ đề cho khớp với video?", "resolutionResamplerAskOnVideoOpen": "Hỏi khi mở video có độ phân giải khác", "backgroundBoxGenerator": "Tạo hộp nền", - "backgroundBoxPadding": "Đệm", "backgroundBoxFillWidth": "Lấp đầy chiều rộng", - "backgroundBoxBoxColor": "Màu hộp", "backgroundBoxRadius": "Bán kính", "backgroundBoxCircle": "Hình tròn", "backgroundBoxSpikes": "Gai nhọn", @@ -4008,12 +4427,9 @@ "backgroundBoxTornPaperDouble": "Giấy rách (trên & dưới)", "backgroundBoxStarburst": "Tia sáng hình sao", "backgroundBoxScroll": "Cuộn giấy / Giấy da", - "stylesTitle": "Kiểu Advanced Sub Station Alpha", "stylesInFile": "Kiểu trong tệp", "stylesSaved": "Kiểu đã lưu", - "category": "Danh mục", "allCategories": "[Tất cả]", - "defaultCategory": "Mặc định", "newCategory": "Danh mục mới...", "renameCategory": "Đổi tên danh mục...", "deleteCategory": "Xóa danh mục", @@ -4036,13 +4452,26 @@ "graphics": "Đồ họa", "copyToStorageStyles": "Sao chép vào kiểu lưu trữ", "copyToFileStyles": "Sao chép vào kiểu của tệp", + "styleXAlreadyExistsInStorage": "Đã tồn tại kiểu tên \"{0}\" trong kho lưu trữ.\n\nGhi đè, hay giữ cả hai (bản sao sẽ có tên mới)?", + "styleXAlreadyExistsInFile": "Đã tồn tại kiểu tên \"{0}\" trong tệp.\n\nGhi đè, hay giữ cả hai (bản sao sẽ có tên mới)?", + "styleNameCannotBeEmpty": "Tên kiểu không được để trống.", + "styleNameXIsUsedMoreThanOnce": "Tên kiểu \"{0}\" được dùng bởi nhiều hơn một kiểu trong tệp.", + "overwrite": "Ghi đè", + "keepBoth": "Giữ cả hai", + "doThisForAllConflictingStyles": "Làm như vậy cho tất cả các kiểu bị trùng", "setStyleAsDefault": "Đặt kiểu làm mặc định", "takeUsagesFromDotDotDot": "Lấy các lượt sử dụng từ...", "replaceStyleWithDotDotDot": "Thay thế kiểu bằng...", "noAttachmentsFound": "Không tìm thấy tệp đính kèm trong tệp ASSA đã chọn.", "deleteStyleQuestion": "Xóa kiểu?", "deleteStylesQuestion": "Xóa các kiểu?", + "deleteXStylesQuestion": "Bạn có muốn xóa {0} kiểu không?", + "deleteXStylesFromStorageQuestion": "Bạn có muốn xóa {0} kiểu khỏi kho lưu trữ không?", + "deleteStyleXFromStorageQuestion": "Bạn có muốn xóa kiểu \"{0}\" khỏi kho lưu trữ không?", + "deleteStyleXFromFileQuestion": "Bạn có muốn xóa kiểu \"{0}\" khỏi tệp hiện tại không?", "openStyleImportFile": "Mở tệp phụ đề để nhập kiểu từ đó", + "styleImportFiles": "Tệp kiểu (.ass, .sty, danh mục Subtitle Edit 4 .template)", + "ssaStyleImportFiles": "Tệp kiểu (.ssa, .ass, .sty)", "primary": "Chính", "secondary": "Phụ", "applyOverrideTags": "Áp dụng thẻ ghi đè", @@ -4057,7 +4486,6 @@ "spaceIncrease": "Tăng khoảng cách", "popIn": "Bật vào", "spinIn": "Xoay vào", - "playCurrent": "Phát dòng hiện tại", "setPosition": "Đặt vị trí", "rotation": "Xoay", "scaleX": "Tỷ lệ X", @@ -4101,10 +4529,8 @@ "advancedEffectOldMovieDescription": "Mô phỏng máy chiếu phim 35mm cổ điển với nhiễu, tối góc và vết xước", "advancedEffectNeonBurst": "Bùng nổ neon", "advancedEffectNeonBurstDescription": "Phụ đề kiểu 'Shorts' năng lượng cao với ánh sáng neon rực rỡ và hoạt ảnh 'bật' phóng to vào", - "advancedEffectSettings": "Cài đặt", "advancedEffectSnowFlakeCount": "Số bông tuyết", "advancedEffectStarfieldStarCount": "Số ngôi sao", - "advancedEffectStarfieldSpeed": "Tốc độ", "advancedEffectGlitch": "Nhiễu số", "advancedEffectGlitchDescription": "Nhiễu kỹ thuật số với quang sai màu, chớp trắng và lớp phủ méo hình", "advancedEffectBounceIn": "Nảy vào", @@ -4113,8 +4539,6 @@ "advancedEffectFirefliesDescription": "Các chấm vàng hổ phách phát sáng ấm áp trôi tự nhiên trên màn hình với ánh sáng nhấp nháy", "advancedEffectMatrix": "Matrix", "advancedEffectMatrixDescription": "Các cột ký tự xanh lá rơi xuống với văn bản phụ đề hiện dần từng chữ từ cơn mưa ký tự", - "advancedEffectAudioBars": "Thanh âm thanh (dạng sóng)", - "advancedEffectAudioBarsDescription": "Các thanh cân bằng hoạt hình theo dạng sóng âm thanh — các thanh cuộn sang trái như dạng sóng và nhấp nhô theo âm thanh", "advancedEffectAudioPulse": "Nhịp văn bản theo âm thanh", "advancedEffectAudioPulseDescription": "Văn bản phụ đề phát sáng và nhấp nhô theo âm lượng — đoạn yên tĩnh hiển thị văn bản bình thường, đỉnh âm lớn thêm quầng sáng màu và phóng to nhẹ", "advancedEffectConfetti": "Bùng nổ pháo giấy", @@ -4128,7 +4552,6 @@ "advancedEffectFancyKaraokeGlow": "Phát sáng từ đang hát", "advancedEffectFancyKaraokeActiveColor": "Màu từ đang hát", "advancedEffectFancyKaraokeInactiveColor": "Màu từ không hoạt động", - "advancedEffectFancyKaraokeInactiveOpacity": "Độ mờ từ không hoạt động (0-255)", "advancedEffectWordSpacingPixels": "Khoảng cách (điểm ảnh)", "advancedEffectSlowZoomOut": "Thu nhỏ chậm", "advancedEffectSlowZoomOutDescription": "Văn bản bắt đầu hơi lớn hơn và thu nhỏ nhẹ nhàng về kích thước bình thường trong suốt thời lượng phụ đề", @@ -4144,7 +4567,6 @@ "advancedEffectSlideInLeftDescription": "Văn bản trượt vào từ ngoài màn hình bên trái, dừng lại, rồi thoát ra lại bên trái", "advancedEffectSlideInRight": "Trượt vào từ bên phải", "advancedEffectSlideInRightDescription": "Văn bản trượt vào từ ngoài màn hình bên phải, dừng lại, rồi thoát ra lại bên phải", - "advancedEffectFadeInOut": "Hiện dần/mờ dần", "advancedEffectFadeInOutDescription": "Văn bản hiện dần ở đầu và mờ dần ở cuối mỗi phụ đề", "advancedEffectWordFlip3D": "Lật chữ 3D", "advancedEffectWordFlip3DDescription": "Mỗi từ gập vào với hiệu ứng lật 3D và bật đàn hồi khi được nói ra — kiểu phụ đề 'Shorts' hiện đại", diff --git a/src/ui/Logic/Config/Language/Main/LanguageMain.cs b/src/ui/Logic/Config/Language/Main/LanguageMain.cs index 7afe73aacea..cf7fd325bd0 100644 --- a/src/ui/Logic/Config/Language/Main/LanguageMain.cs +++ b/src/ui/Logic/Config/Language/Main/LanguageMain.cs @@ -187,7 +187,7 @@ public LanguageMain() DeleteTextAndShiftCellsUp = "Delete text and shift cells up"; EndTimeMustBeAfterStartTime = "End time must be after start time."; ErrorLoad7Zip = "This file seems to be a compressed 7-Zip file.\n\nSubtitle Edit cannot open compressed files."; - ErrorLoadBinaryZeroes = "Sorry, this file contains only binary zeroes!\n\nIf you have edited this file with Subtitle Edit you might be able to find a backup via the menu item File -> Restore auto-backup..."; + ErrorLoadBinaryZeroes = "Sorry, this file contains only binary zeroes!\n\nIf you have edited this file with Subtitle Edit you might be able to find a backup via the menu item File -> Restore auto-backup..."; ErrorLoadGZip = "This file seems to be a compressed GZip file.\n\nSubtitle Edit cannot open compressed files."; ErrorLoadLargeFile = "This file is too large to open as a subtitle file."; ErrorLoadVideoFilePrompt = "This file seems to be a video file:\n{0}\n\nYou can open a video via \"Video -> Open video\" or by dragging and dropping a video onto the video player.\n\nDo you want to open it as a video file?"; diff --git a/src/ui/Logic/Config/Se.cs b/src/ui/Logic/Config/Se.cs index a3f845683c1..eac55c7f252 100644 --- a/src/ui/Logic/Config/Se.cs +++ b/src/ui/Logic/Config/Se.cs @@ -21,7 +21,7 @@ public class Se internal const int CurrentShortcutsMigrationVersion = 6; internal const int CurrentLayoutMigrationVersion = 2; - public static string Version { get; set; } = "v5.3.0-rc4"; + public static string Version { get; set; } = "v5.3.0"; public SeGeneral General { get; set; } = new(); public List Shortcuts { get; set; } = new(); From d22e16e932f46d591f7ad55b4cf9866fc5d3f8f1 Mon Sep 17 00:00:00 2001 From: niksedk Date: Wed, 14 Oct 2026 06:31:26 +0200 Subject: [PATCH 134/306] Release v5.3.0: Flatpak metainfo entry, libse version, Changelog.txt - Flatpak metainfo: add the 5.3.0 entry, required by the stable release workflow's verify-metainfo-release-entry check. - LibSE.csproj: package version 5.3.0. - Changelog.txt: 5.3.0 summary of the changes since 5.2.0. Co-Authored-By: Claude Opus 5.5 --- Changelog.txt | 72 +++++++++++++++++++ .../dk.nikse.subtitleedit.metainfo.xml | 6 ++ src/libse/LibSE.csproj | 2 +- 3 files changed, 79 insertions(+), 1 deletion(-) diff --git a/Changelog.txt b/Changelog.txt index a69c5b6bf10..6c91ff11f6c 100644 --- a/Changelog.txt +++ b/Changelog.txt @@ -1,5 +1,77 @@ Subtitle Edit Changelog +5.3.0 (14th of October 2026) + + Subtitle Edit 5.3 collects the work of 26 betas and 4 release candidates. + A summary of the changes since 5.2.0: + +New features: +* Command palette (Ctrl+Shift+K) +* Import subtitles directly from DVD (IFO/VOB) with title picker, fast rip and exact cell timing +* Improve time codes (forced alignment) - re-time a roughly synced subtitle, with a speech-to-text check and waveform preview +* Remux video - video, multi-track audio (with mixing) and soft subtitles, also embedded CEA-608 captions +* Cut video transitions and fades, burn-in for selected lines, and 3D (half side-by-side / top-bottom) burn-in and image export +* Generate background music locally (ACE-Step) +* Compare: merge view, sync points, time tolerance and taking only text or timing per row +* Custom shortcut slots built from steps, AltGr modifier, per-plugin shortcuts and many new shortcuts +* Actor picker, "Set style..." quick pick, and "Shot in"/"Shot out" grid columns +* Reload the subtitle when it is changed outside Subtitle Edit, and "Frame numbers" time code mode +* Taskbar/Dock progress and prevent sleep for long jobs (speech to text, text to speech, burn-in, batch convert) + +Video player and waveform: +* New FFmpeg video player with hardware decoding, audio track picker, real frame stepping and Linux sound +* Experimental mpv Metal video player on macOS, deinterlace setting, and customizable player controls +* Editor-style layout 14 with a track timeline, and layouts 12 and 13 (text box below the video) +* Experimental fast waveform renderer, "Show speech only" waveform, grid color and remembered zoom +* Waveform toolbar: initial text box, move-lines buttons, line breaks and wrapping + +Speech to text: +* Crisp ASR: speaker detection (Sortformer), VAD choice (Silero, FireRedVAD, WebRTC, Whisper-VAD) and "Isolate speech" +* New models: Parakeet Ultra, Phonon-2, ReazonSpeech/Japanese Parakeet, Voxtral F16 and Index-Echo +* OpenAI-compatible API works with vLLM; retry of failed clips on selected lines + +Text to speech: +* New engines: OpenAI-compatible TTS, ElevenLabs v4/v4 Turbo, KugelAudio and Supertonic-3 +* Voice manager with transcripts, voice packs and copying voices between engines +* "Remove original speech", "Speak from current line" and "Play with speech" +* Optional Sidon cleaning of voice-clone reference audio + +Auto-translate and AI: +* New engines: LM Studio advanced and API Route, model browsing, and new llama.cpp models (Index-Translate, TranslatePsy-AfriSLM) +* Translate in place, translate one file into several languages in batch convert, and a no-prompt shortcut +* 22 more languages for the AI engines, MyMemory without an API key, and line breaks kept for ChatGPT/Anthropic +* AI review: prompt templates and reference context + +OCR: +* nOCR: much better accuracy, new Cyrillic database, "Optimize database", and a redesigned trainer +* Find and Replace in the OCR window, remembered pre-processing settings, and a per-language spell check dictionary +* Romanian and Croatian OCR fix lists; better word splitting + +Subtitle formats: +* Read CEA-608/708 closed captions from .ts, .mkv, .mp4, .mxf, .vob, .mpg and .m2v +* New: EZTitles binary (.ezt), PSP UMD Video, HD-DVD sup, Cheetah Caption ASC, ELR Studio and more SE 4 formats opened again +* Transport stream, MP4 caption, CEA-708 and Blu-ray sup timing fixes; PAC code page detection and secondary code page +* ASSA draw: redesigned with layers, shape library, curve tools, SVG import and live preview +* ASSA styles: overwrite/keep both on import, reorder, font collector; Source view syntax highlighting + +Batch convert and seconv: +* Batch convert: named presets, new functions and filters, multi-language translate, OCR language per file, and many format settings +* seconv: DVD sup and IMSC image outputs, openai-compatible and DeepL translate, nOCR optimize, and more SE 4 formats + +Accessibility and UI: +* Screen reader names across settings and tool windows, F1 help in 145 more dialogs, and remembered dialog sizes/positions +* Settings show one category at a time with search and back/forward navigation; settings auto-backup +* macOS-standard default shortcuts and a new rounded app icon + +Translations and engines: +* All language files synced with English +* Updated engines: Crisp ASR v0.8.43, whisper.cpp v1.9.5, llama.cpp b11553, CrispEmbed v0.17.12, audio.cpp v0.9.1, WhisperX build 103, FFmpeg 9.0.2 (Windows), libVLC 3.0.24 (Windows), Avalonia 12.1.4 and Flatpak runtime 26.08 + + A big thank you to everyone who tested the pre-releases, reported issues and + contributed code and translations - and a special thanks to Anthropic for + sponsoring a Claude subscription used in the development of this release :) + + 5.2.0 (10th of September 2026) Subtitle Edit 5.2 collects the work of 32 betas and 7 release candidates. diff --git a/installer/flatpak/dk.nikse.subtitleedit.metainfo.xml b/installer/flatpak/dk.nikse.subtitleedit.metainfo.xml index 1d4b74ad3c1..739569519c9 100644 --- a/installer/flatpak/dk.nikse.subtitleedit.metainfo.xml +++ b/installer/flatpak/dk.nikse.subtitleedit.metainfo.xml @@ -74,6 +74,12 @@ + + https://github.com/SubtitleEdit/subtitleedit/releases/tag/v5.3.0 + +

A command palette, DVD import, forced-alignment time code improvement, video remuxing, a new FFmpeg video player, closed captions from video files, a redesigned ASSA drawing tool, much better nOCR, new speech to text, text to speech and translation engines, and many accessibility and bug fixes.

+
+
https://github.com/SubtitleEdit/subtitleedit/releases/tag/v5.2.0 diff --git a/src/libse/LibSE.csproj b/src/libse/LibSE.csproj index beb7c4f179a..215fac8638a 100644 --- a/src/libse/LibSE.csproj +++ b/src/libse/LibSE.csproj @@ -8,7 +8,7 @@ false libse - 5.2.0 + 5.3.0 Subtitle Edit's subtitle library - read/write 300+ subtitle formats (SubRip, ASSA, WebVTT, EBU STL, Blu-ray sup, and many more), fix common errors, auto-break lines, convert formats, and more. https://github.com/SubtitleEdit/subtitleedit/tree/main/src/libse https://github.com/SubtitleEdit/subtitleedit From 88aaa0a3ced54da7eb9456c0fbbcae0e2dfd95c0 Mon Sep 17 00:00:00 2001 From: niksedk Date: Wed, 14 Oct 2026 09:24:41 +0200 Subject: [PATCH 135/306] Flathub manifest: pin v5.3.0, sync icons with the CI manifest - Pin the git source to v5.3.0 (89f14c827). - Drop the shell source that rewrote the metainfo screenshot URL: v5.3.0's metainfo already points at screenshots/main-window.png, so it was only needed while the pin was v5.2.0. - Install the 16-128 px icons like the CI manifest does (#15648, blank menu icon on some desktops); v5.3.0 ships them. The two manifests now differ only in the source block, as the README says they should. Co-Authored-By: Claude Opus 5.5 --- .../flatpak/flathub/dk.nikse.subtitleedit.yaml | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/installer/flatpak/flathub/dk.nikse.subtitleedit.yaml b/installer/flatpak/flathub/dk.nikse.subtitleedit.yaml index a19b6513072..82bd4a80a03 100644 --- a/installer/flatpak/flathub/dk.nikse.subtitleedit.yaml +++ b/installer/flatpak/flathub/dk.nikse.subtitleedit.yaml @@ -468,8 +468,14 @@ modules: # AppStream metainfo - install -Dm644 installer/flatpak/dk.nikse.subtitleedit.metainfo.xml /app/share/metainfo/dk.nikse.subtitleedit.metainfo.xml - # Icon (256x256 PNG from the source tree) + # Icon (256x256 PNG from the source tree, plus pre-scaled smaller sizes so + # desktop menus/panels don't show a blank icon when they look up e.g. 48x48) - install -Dm644 src/ui/Assets/SE.png /app/share/icons/hicolor/256x256/apps/dk.nikse.subtitleedit.png + - | + for size in 16 24 32 48 64 128; do + install -Dm644 "installer/flatpak/icons/dk.nikse.subtitleedit-${size}.png" \ + "/app/share/icons/hicolor/${size}x${size}/apps/dk.nikse.subtitleedit.png" + done # MIT license — distribute the project's terms alongside the app (issue #11278). - install -Dm644 LICENSE /app/share/licenses/dk.nikse.subtitleedit/LICENSE @@ -486,16 +492,12 @@ modules: # regeneration pass before merging; see ../flathub/README.md. - type: git url: https://github.com/SubtitleEdit/subtitleedit.git - tag: v5.2.0 - commit: d8e3b8b41e856a896c541ce7e59b490a99c21196 + tag: v5.3.0 + commit: 89f14c8273cac44bae33a613d3adf4a6efe6b3ad x-checker-data: type: git tag-pattern: ^v(\d+\.\d+\.\d+)$ - - type: shell - commands: - - sed -i 's|/subtitleedit/images/main.png|/subtitleedit/screenshots/main-window.png|' installer/flatpak/dk.nikse.subtitleedit.metainfo.xml - # NuGet packages pre-fetched for the exact commit pinned above. # Regenerate with ./prepare-flathub-release.sh whenever the pin changes. - nuget-sources.json From fcd177b6ca27f3ccda1a6bf06e39bcbacb0ff237 Mon Sep 17 00:00:00 2001 From: niksedk Date: Wed, 14 Oct 2026 10:30:51 +0200 Subject: [PATCH 136/306] Scroll the content of auto-sized windows clamped to a short screen (#16026) UiUtil.ClampToWorkingArea already shrinks auto-sized windows taller than the working area, but most of them have no ScrollViewer and can't be resized, so the bottom rows (e.g. batch convert settings' OCR options and OK button at 125% on a laptop) were just clipped. After clamping, wrap the content in a ScrollViewer scrolling only the clamped auto-sized dimension(s). Windows with their own height-filling ScrollViewer (burn-in, transparent subtitles, ...) are left alone. Co-Authored-By: Claude Opus 5.5 --- src/ui/Logic/UiUtil.cs | 46 ++++++ .../Logic/WrapContentInScrollViewerTests.cs | 132 ++++++++++++++++++ 2 files changed, 178 insertions(+) create mode 100644 tests/UI/Logic/WrapContentInScrollViewerTests.cs diff --git a/src/ui/Logic/UiUtil.cs b/src/ui/Logic/UiUtil.cs index fc40e72c17d..96be2345203 100644 --- a/src/ui/Logic/UiUtil.cs +++ b/src/ui/Logic/UiUtil.cs @@ -3416,16 +3416,25 @@ private static void ClampToWorkingArea(Window window) { // Stop content-driven sizing before shrinking, or the next measure pass // would just grow the window back. + var sizeToContent = window.SizeToContent; window.SizeToContent = SizeToContent.Manual; + var clampedWidth = false; + var clampedHeight = false; if (window.Bounds.Width > maxWidth) { window.Width = maxWidth; + clampedWidth = sizeToContent is SizeToContent.Width or SizeToContent.WidthAndHeight; } if (window.Bounds.Height > maxHeight) { window.Height = maxHeight; + clampedHeight = sizeToContent is SizeToContent.Height or SizeToContent.WidthAndHeight; } + + // A shrunk auto-sized window clips its bottom rows (often the OK/Cancel bar), + // and many of them can't be resized - so let the content scroll. (#16026) + WrapContentInScrollViewer(window, clampedWidth, clampedHeight); } // Keep the whole frame inside the working area so the bottom buttons stay @@ -3443,6 +3452,43 @@ private static void ClampToWorkingArea(Window window) } } + // Puts an auto-sized window's content in a ScrollViewer after the window was clamped to the + // screen. Only a clamped auto-sized dimension scrolls: the content was laid out with unlimited + // room in that direction anyway, so its layout is unchanged - it just scrolls instead of + // being clipped. A fixed dimension stays disabled so text wrapping and stretching still work. + internal static void WrapContentInScrollViewer(Window window, bool horizontal, bool vertical) + { + if ((!horizontal && !vertical) || window.Content is not Control content) + { + return; + } + + // Windows with a ScrollViewer of their own that takes up the spare height (e.g. burn-in: + // scrolling settings over a fixed button bar) already handle a short screen, and an outer + // one would stop theirs scrolling. A fixed-size one (spell check suggestions, message box + // text) is just a part of the content and scrolls fine inside the outer one. + if (content is ScrollViewer || + content.GetLogicalDescendants().OfType().Any(p => double.IsNaN(p.Height) && double.IsPositiveInfinity(p.MaxHeight))) + { + return; + } + + var focused = window.FocusManager?.GetFocusedElement() as Control; + window.Content = null; + window.Content = new ScrollViewer + { + HorizontalScrollBarVisibility = horizontal ? ScrollBarVisibility.Auto : ScrollBarVisibility.Disabled, + VerticalScrollBarVisibility = vertical ? ScrollBarVisibility.Auto : ScrollBarVisibility.Disabled, + Content = content, + }; + + // Re-parenting detaches the content from the visual tree, which drops keyboard focus. + if (focused != null && content.IsVisualAncestorOf(focused)) + { + focused.Focus(); + } + } + public static void SaveWindowPosition(Window? window) { if (!Se.Settings.General.RememberPositionAndSize || window == null || window.Name == null) diff --git a/tests/UI/Logic/WrapContentInScrollViewerTests.cs b/tests/UI/Logic/WrapContentInScrollViewerTests.cs new file mode 100644 index 00000000000..032bada2717 --- /dev/null +++ b/tests/UI/Logic/WrapContentInScrollViewerTests.cs @@ -0,0 +1,132 @@ +using System; +using System.Linq; +using Avalonia; +using Avalonia.Controls; +using Avalonia.Controls.Primitives; +using Avalonia.Headless.XUnit; +using Avalonia.LogicalTree; +using Nikse.SubtitleEdit.Features.Tools.BatchConvert; +using Nikse.SubtitleEdit.Logic; +using Nikse.SubtitleEdit.Logic.Media; + +namespace UITests.Logic; + +/// +/// An auto-sized window taller than the screen gets clamped to the working area; its content +/// must then scroll instead of clipping the bottom rows - e.g. the batch convert settings +/// window at 125% on a laptop lost its OCR settings and the OK button (#16026). +/// +public class WrapContentInScrollViewerTests : IDisposable +{ + private readonly List _windows = new(); + + public void Dispose() + { + foreach (var window in _windows) + { + Avalonia.Threading.Dispatcher.UIThread.RunJobs(); + window.Close(); + } + + _windows.Clear(); + } + + private sealed class NullServiceProvider : IServiceProvider + { + public object? GetService(Type serviceType) => null; + } + + private Window Show(Window window) + { + _windows.Add(window); + window.Show(); + Avalonia.Threading.Dispatcher.UIThread.RunJobs(); + window.UpdateLayout(); + return window; + } + + private static void Shrink(Window window, double height) + { + window.SizeToContent = SizeToContent.Manual; + window.MinHeight = 0; + window.Height = height; + Avalonia.Threading.Dispatcher.UIThread.RunJobs(); + window.UpdateLayout(); + } + + [AvaloniaFact] + public void BatchConvertSettings_ScrollsToTheButtons_OnAShortScreen() + { + var vm = new BatchConvertSettingsViewModel(new FolderHelper(), new WindowService(new NullServiceProvider())); + var window = Show(new BatchConvertSettingsWindow(vm)); + var naturalHeight = window.ClientSize.Height; + + Shrink(window, 500); + UiUtil.WrapContentInScrollViewer(window, horizontal: false, vertical: true); + window.UpdateLayout(); + + var scroller = Assert.IsType(window.Content); + Assert.Equal(ScrollBarVisibility.Disabled, scroller.HorizontalScrollBarVisibility); + Assert.True( + scroller.Extent.Height >= naturalHeight - 1.5, + $"Content was squeezed instead of scrolled (extent {scroller.Extent.Height:0.#}, natural {naturalHeight:0.#})."); + Assert.True(scroller.Viewport.Height <= 500 + 1.5); + + var okButton = window.GetLogicalDescendants().OfType
public (float Left, float Top, float Right, float Bottom)? TextArea { get; } + /// + /// The parts keep their own colors (saved shapes, clip art) instead of taking the current color. + /// + public bool HasOwnColors { get; } + public ShapeLibraryItem(string name, string category, Func> build, SeAssaDrawShape? userShape = null, - (float Left, float Top, float Right, float Bottom)? textArea = null) + (float Left, float Top, float Right, float Bottom)? textArea = null, bool ownColors = false) { Name = name; Category = category; _build = build; UserShape = userShape; TextArea = textArea; + HasOwnColors = ownColors || userShape != null; } /// @@ -92,7 +98,7 @@ public List CreateShapes(float x, float y, float width, float height, { var shape = template.Clone(); shape.Layer = firstLayer + template.Layer; - shape.ForeColor = UserShape != null ? template.ForeColor : color; + shape.ForeColor = HasOwnColors ? template.ForeColor : color; shape.Hidden = false; foreach (var point in shape.Points) { @@ -103,20 +109,83 @@ public List CreateShapes(float x, float y, float width, float height, result.Add(shape); } - // The text area belongs to the biggest part (the bubble, not the dots of a thought bubble) + // The text area belongs to the topmost part under its center (a burst's yellow middle, + // not the red edge below it - text goes on its shape's layer), else the biggest part if (TextArea is { } area && result.Count > 0) { - var host = result.OrderByDescending(s => { var b = s.GetBounds(); return (b.Right - b.Left) * (b.Bottom - b.Top); }).First(); - host.SetTextBounds( - x + (area.Left - left) * scaleX, - y + (area.Top - top) * scaleY, - x + (area.Right - left) * scaleX, - y + (area.Bottom - top) * scaleY); + var textLeft = x + (area.Left - left) * scaleX; + var textTop = y + (area.Top - top) * scaleY; + var textRight = x + (area.Right - left) * scaleX; + var textBottom = y + (area.Bottom - top) * scaleY; + var centerX = (textLeft + textRight) / 2; + var centerY = (textTop + textBottom) / 2; + var host = result + .Where(s => !s.IsEraser && s.HitTest(centerX, centerY, 0)) + .OrderByDescending(s => s.Layer) + .ThenByDescending(Area) + .FirstOrDefault() ?? result.OrderByDescending(Area).First(); + host.SetTextBounds(textLeft, textTop, textRight, textBottom); } return result; } + private static float Area(DrawShape shape) + { + var (left, top, right, bottom) = shape.GetBounds(); + return (right - left) * (bottom - top); + } + + /// + /// Thumbnail geometry per layer with its color, bottom layer first, moved so the shape starts at 0,0. + /// + public List<(Color Color, Geometry Geometry)> ToColoredGeometries() + { + var (left, top, _, _) = Bounds; + var result = new List<(Color, Geometry)>(); + foreach (var layer in Template.Where(s => s.Points.Count > 2 && !s.IsEraser).GroupBy(s => s.Layer).OrderBy(g => g.Key)) + { + var geometry = new StreamGeometry(); + using (var ctx = geometry.Open()) + { + ctx.SetFillRule(FillRule.NonZero); + foreach (var shape in layer) + { + AddFigure(ctx, shape, left, top); + } + } + + result.Add((layer.First().ForeColor, geometry)); + } + + return result; + } + + private static void AddFigure(StreamGeometryContext ctx, DrawShape shape, float left, float top) + { + ctx.BeginFigure(new Avalonia.Point(shape.Points[0].X - left, shape.Points[0].Y - top), true); + var i = 1; + while (i < shape.Points.Count) + { + var point = shape.Points[i]; + if (point.DrawType == DrawCoordinateType.BezierCurveSupport1 && i + 2 < shape.Points.Count) + { + ctx.CubicBezierTo( + new Avalonia.Point(point.X - left, point.Y - top), + new Avalonia.Point(shape.Points[i + 1].X - left, shape.Points[i + 1].Y - top), + new Avalonia.Point(shape.Points[i + 2].X - left, shape.Points[i + 2].Y - top)); + i += 3; + } + else + { + ctx.LineTo(new Avalonia.Point(point.X - left, point.Y - top)); + i++; + } + } + + ctx.EndFigure(true); + } + /// /// Thumbnail geometry in the shape's own coordinates (non-zero fill keeps holes open). /// @@ -169,6 +238,7 @@ public static class ShapeLibrary Se.Language.Assa.DrawCategoryArrows, Se.Language.Assa.DrawCategoryBasicShapes, Se.Language.Assa.DrawCategorySymbols, + Se.Language.Assa.DrawCategoryClipArt, ]; public static List GetUserShapes() @@ -240,6 +310,20 @@ private static ShapeLibraryItem TextItem(string name, string category, (float Le return new ShapeLibraryItem(name, category, () => SvgImporter.FromPathData(union), textArea: (l * 10, t * 10, r * 10, b * 10)); } + /// + /// Colored clip art: an SVG body for a 100 x 100 view box, imported like an SVG file + /// (one layer per color run), with an optional text area in the same coordinates. + /// + private static ShapeLibraryItem Clip(string name, (float Left, float Top, float Right, float Bottom)? textArea, string svgBody) + { + var svg = "" + svgBody + ""; + + // Imported at 1000 x 1000, so the view box is scaled by 10 like the path data shapes + (float, float, float, float)? area = textArea is { } a ? (a.Left * 10, a.Top * 10, a.Right * 10, a.Bottom * 10) : null; + return new ShapeLibraryItem(name, Se.Language.Assa.DrawCategoryClipArt, () => SvgImporter.Import(svg, 1000, 1000, 0).Shapes, + textArea: area, ownColors: true); + } + private static string F(double value) => value.ToString("0.##", CultureInfo.InvariantCulture); private static string Circle(double cx, double cy, double r) => @@ -349,6 +433,48 @@ private static List MakeBuiltIn() [Circle(34, 36, 8), Circle(66, 36, 8), "M26 58 A24 24 0 0 0 74 58 L64 58 A14 14 0 0 1 36 58 Z"]), Item(l.DrawShapeSadSmiley, symbols, [Circle(50, 50, 50)], [Circle(34, 36, 8), Circle(66, 36, 8), "M26 82 A24 24 0 0 1 74 82 L64 82 A14 14 0 0 0 36 82 Z"]), + + // Clip art (own colors; each color is one ASSA line, so a few colors each) + Clip(l.DrawClipArtRedHeart, (22, 18, 78, 56), + """"""), + Clip(l.DrawClipArtAngerMark, null, + """"""), + Clip(l.DrawClipArtSweatDrop, null, + """"""), + Clip(l.DrawClipArtSparkles, null, + """"""), + Clip(l.DrawClipArtBlush, null, + """"""), + Clip(l.DrawClipArtSurpriseLines, null, + """"""), + Clip(l.DrawClipArtBamBurst, (28, 32, 72, 68), + """"""), + Clip(l.DrawClipArtZzz, null, + """"""), + Clip(l.DrawClipArtSun, (30, 30, 70, 70), + """"""), + Clip(l.DrawClipArtFlame, null, + """"""), + Clip(l.DrawClipArtLightBulb, null, + """"""), + Clip(l.DrawClipArtLightningBolt, null, + """"""), + Clip(l.DrawClipArtColoredMusicNotes, null, + """"""), + Clip(l.DrawClipArtPhone, (31, 14, 69, 80), + """"""), + Clip(l.DrawClipArtQuestionMark, null, + """"""), + Clip(l.DrawClipArtExclamationMark, null, + """"""), + Clip(l.DrawClipArtGreenCheck, null, + """"""), + Clip(l.DrawClipArtRedCross, null, + """"""), + Clip(l.DrawClipArtThumbsUp, null, + """"""), + Clip(l.DrawClipArtGoldStar, null, + """"""), ]; } } diff --git a/src/ui/Logic/Config/Language/Assa/LanguageAssa.cs b/src/ui/Logic/Config/Language/Assa/LanguageAssa.cs index 416a974d2c8..98c04350edf 100644 --- a/src/ui/Logic/Config/Language/Assa/LanguageAssa.cs +++ b/src/ui/Logic/Config/Language/Assa/LanguageAssa.cs @@ -55,6 +55,27 @@ public class LanguageAssa public string DrawCategoryArrows { get; set; } public string DrawCategoryBasicShapes { get; set; } public string DrawCategorySymbols { get; set; } + public string DrawCategoryClipArt { get; set; } + public string DrawClipArtRedHeart { get; set; } + public string DrawClipArtAngerMark { get; set; } + public string DrawClipArtSweatDrop { get; set; } + public string DrawClipArtSparkles { get; set; } + public string DrawClipArtBlush { get; set; } + public string DrawClipArtSurpriseLines { get; set; } + public string DrawClipArtBamBurst { get; set; } + public string DrawClipArtZzz { get; set; } + public string DrawClipArtSun { get; set; } + public string DrawClipArtFlame { get; set; } + public string DrawClipArtLightBulb { get; set; } + public string DrawClipArtLightningBolt { get; set; } + public string DrawClipArtColoredMusicNotes { get; set; } + public string DrawClipArtPhone { get; set; } + public string DrawClipArtQuestionMark { get; set; } + public string DrawClipArtExclamationMark { get; set; } + public string DrawClipArtGreenCheck { get; set; } + public string DrawClipArtRedCross { get; set; } + public string DrawClipArtThumbsUp { get; set; } + public string DrawClipArtGoldStar { get; set; } public string DrawCategoryMyShapes { get; set; } public string DrawShapeSpeechBubble { get; set; } public string DrawShapeSpeechBubbleRight { get; set; } @@ -437,6 +458,27 @@ public LanguageAssa() DrawCategoryArrows = "Arrows"; DrawCategoryBasicShapes = "Basic shapes"; DrawCategorySymbols = "Symbols"; + DrawCategoryClipArt = "Clip art"; + DrawClipArtRedHeart = "Red heart"; + DrawClipArtAngerMark = "Anger mark"; + DrawClipArtSweatDrop = "Sweat drop"; + DrawClipArtSparkles = "Sparkles"; + DrawClipArtBlush = "Blush"; + DrawClipArtSurpriseLines = "Surprise lines"; + DrawClipArtBamBurst = "Bam! burst"; + DrawClipArtZzz = "Zzz"; + DrawClipArtSun = "Sun"; + DrawClipArtFlame = "Flame"; + DrawClipArtLightBulb = "Light bulb"; + DrawClipArtLightningBolt = "Lightning bolt"; + DrawClipArtColoredMusicNotes = "Colored music notes"; + DrawClipArtPhone = "Phone"; + DrawClipArtQuestionMark = "Question mark"; + DrawClipArtExclamationMark = "Exclamation mark"; + DrawClipArtGreenCheck = "Green check"; + DrawClipArtRedCross = "Red cross"; + DrawClipArtThumbsUp = "Thumbs up"; + DrawClipArtGoldStar = "Gold star"; DrawCategoryMyShapes = "My shapes"; DrawShapeSpeechBubble = "Speech bubble"; DrawShapeSpeechBubbleRight = "Speech bubble, tail right"; diff --git a/tests/UI/Features/Assa/AssaDrawClipArtTests.cs b/tests/UI/Features/Assa/AssaDrawClipArtTests.cs new file mode 100644 index 00000000000..ca77251cb5f --- /dev/null +++ b/tests/UI/Features/Assa/AssaDrawClipArtTests.cs @@ -0,0 +1,64 @@ +using Avalonia.Media; +using Nikse.SubtitleEdit.Core.Common; +using Nikse.SubtitleEdit.Core.SubtitleFormats; +using Nikse.SubtitleEdit.Features.Assa.AssaDraw; +using Nikse.SubtitleEdit.Features.Main; +using Nikse.SubtitleEdit.Logic.Config; +using Nikse.SubtitleEdit.Logic.Media; + +namespace UITests.Features.Assa; + +/// +/// The colored clip art of the ASSA draw shape library. +/// +public class AssaDrawClipArtTests +{ + private static List ClipArt => + ShapeLibrary.BuiltIn.Where(i => i.Category == Se.Language.Assa.DrawCategoryClipArt).ToList(); + + [Fact] + public void ClipArt_KeepsItsColors_InAFewLayers() + { + Assert.Equal(20, ClipArt.Count); + foreach (var item in ClipArt) + { + Assert.True(item.HasOwnColors, item.Name); + Assert.NotEmpty(item.Template); + + // Each color is one ASSA line - keep it cheap + var layers = item.Template.Select(s => s.Layer).Distinct().Count(); + Assert.InRange(layers, 1, 4); + Assert.Contains(item.Template, s => s.ForeColor != Colors.White); + Assert.Equal(layers, item.ToColoredGeometries().Count); + } + } + + [Fact] + public void Insert_KeepsColors_OnLayersAboveTheDrawing() + { + var vm = new AssaDrawViewModel(new FileHelper(), new StubWindowService()); + var line = new SubtitleLineViewModel(new Paragraph("{\\p1}m 0 0 l 10 0 10 10{\\p0}", 0, 2000) { Extra = "Default" }, new AdvancedSubStationAlpha()); + vm.Initialize(new Subtitle { Header = AdvancedSubStationAlpha.DefaultHeader }, [line], 1920, 1080); + vm.LayerColor = Colors.Green; + vm.CurrentLibraryShape = ClipArt.First(i => i.Name == Se.Language.Assa.DrawClipArtSun); + + vm.InsertLibraryShape(new CanvasInsertEventArgs(100, 100, 400, 400, isClick: false)); + + var inserted = vm.Shapes.Skip(1).ToList(); + Assert.DoesNotContain(inserted, s => s.ForeColor == Colors.Green); + Assert.Equal([1, 2, 3], inserted.Select(s => s.Layer).Distinct().Order()); + Assert.Contains(inserted, s => s.ForeColor == Color.Parse("#FFC107")); + } + + [Fact] + public void TextArea_IsOnTheTopPartUnderItsCenter() + { + var burst = ClipArt.First(i => i.Name == Se.Language.Assa.DrawClipArtBamBurst); + var shapes = burst.CreateShapes(0, 0, 1000, 1000, Colors.White, 0); + + // The yellow middle (painted last, top layer), so the text isn't covered by it + var host = Assert.Single(shapes, s => s.TextFrame != null); + Assert.Equal(Color.Parse("#FFEB3B"), host.ForeColor); + Assert.Equal(shapes.Max(s => s.Layer), host.Layer); + } +} From d47bf0064d021fea2687388d03b641f4bf4673f1 Mon Sep 17 00:00:00 2001 From: niksedk Date: Wed, 14 Oct 2026 20:44:13 +0200 Subject: [PATCH 148/306] ASSA draw: 14 more clip art shapes Broken heart, ghost, skull, snowflake, moon and stars, rain cloud, bomb, gift, crown, clock, clapperboard, microphone, warning sign, no entry. Text areas on the rain cloud and the clapperboard. Co-Authored-By: Claude Opus 5.5 --- src/ui/Features/Assa/AssaDraw/ShapeLibrary.cs | 28 +++++++++++++++++++ .../Config/Language/Assa/LanguageAssa.cs | 28 +++++++++++++++++++ .../UI/Features/Assa/AssaDrawClipArtTests.cs | 2 +- 3 files changed, 57 insertions(+), 1 deletion(-) diff --git a/src/ui/Features/Assa/AssaDraw/ShapeLibrary.cs b/src/ui/Features/Assa/AssaDraw/ShapeLibrary.cs index a301b15f327..1552abe02ed 100644 --- a/src/ui/Features/Assa/AssaDraw/ShapeLibrary.cs +++ b/src/ui/Features/Assa/AssaDraw/ShapeLibrary.cs @@ -475,6 +475,34 @@ private static List MakeBuiltIn() """"""), Clip(l.DrawClipArtGoldStar, null, """"""), + Clip(l.DrawClipArtBrokenHeart, null, + """"""), + Clip(l.DrawClipArtGhost, null, + """"""), + Clip(l.DrawClipArtSkull, null, + """"""), + Clip(l.DrawClipArtSnowflake, null, + """"""), + Clip(l.DrawClipArtMoonAndStars, null, + """"""), + Clip(l.DrawClipArtRainCloud, (24, 24, 80, 58), + """"""), + Clip(l.DrawClipArtBomb, null, + """"""), + Clip(l.DrawClipArtGift, null, + """"""), + Clip(l.DrawClipArtCrown, null, + """"""), + Clip(l.DrawClipArtClock, null, + """"""), + Clip(l.DrawClipArtClapperboard, (10, 54, 90, 92), + """"""), + Clip(l.DrawClipArtMicrophone, null, + """"""), + Clip(l.DrawClipArtWarningSign, null, + """"""), + Clip(l.DrawClipArtNoEntry, null, + """"""), ]; } } diff --git a/src/ui/Logic/Config/Language/Assa/LanguageAssa.cs b/src/ui/Logic/Config/Language/Assa/LanguageAssa.cs index 98c04350edf..427c7cabadf 100644 --- a/src/ui/Logic/Config/Language/Assa/LanguageAssa.cs +++ b/src/ui/Logic/Config/Language/Assa/LanguageAssa.cs @@ -76,6 +76,20 @@ public class LanguageAssa public string DrawClipArtRedCross { get; set; } public string DrawClipArtThumbsUp { get; set; } public string DrawClipArtGoldStar { get; set; } + public string DrawClipArtBrokenHeart { get; set; } + public string DrawClipArtGhost { get; set; } + public string DrawClipArtSkull { get; set; } + public string DrawClipArtSnowflake { get; set; } + public string DrawClipArtMoonAndStars { get; set; } + public string DrawClipArtRainCloud { get; set; } + public string DrawClipArtBomb { get; set; } + public string DrawClipArtGift { get; set; } + public string DrawClipArtCrown { get; set; } + public string DrawClipArtClock { get; set; } + public string DrawClipArtClapperboard { get; set; } + public string DrawClipArtMicrophone { get; set; } + public string DrawClipArtWarningSign { get; set; } + public string DrawClipArtNoEntry { get; set; } public string DrawCategoryMyShapes { get; set; } public string DrawShapeSpeechBubble { get; set; } public string DrawShapeSpeechBubbleRight { get; set; } @@ -479,6 +493,20 @@ public LanguageAssa() DrawClipArtRedCross = "Red cross"; DrawClipArtThumbsUp = "Thumbs up"; DrawClipArtGoldStar = "Gold star"; + DrawClipArtBrokenHeart = "Broken heart"; + DrawClipArtGhost = "Ghost"; + DrawClipArtSkull = "Skull"; + DrawClipArtSnowflake = "Snowflake"; + DrawClipArtMoonAndStars = "Moon and stars"; + DrawClipArtRainCloud = "Rain cloud"; + DrawClipArtBomb = "Bomb"; + DrawClipArtGift = "Gift"; + DrawClipArtCrown = "Crown"; + DrawClipArtClock = "Clock"; + DrawClipArtClapperboard = "Clapperboard"; + DrawClipArtMicrophone = "Microphone"; + DrawClipArtWarningSign = "Warning sign"; + DrawClipArtNoEntry = "No entry"; DrawCategoryMyShapes = "My shapes"; DrawShapeSpeechBubble = "Speech bubble"; DrawShapeSpeechBubbleRight = "Speech bubble, tail right"; diff --git a/tests/UI/Features/Assa/AssaDrawClipArtTests.cs b/tests/UI/Features/Assa/AssaDrawClipArtTests.cs index ca77251cb5f..dbcbe55df3f 100644 --- a/tests/UI/Features/Assa/AssaDrawClipArtTests.cs +++ b/tests/UI/Features/Assa/AssaDrawClipArtTests.cs @@ -19,7 +19,7 @@ public class AssaDrawClipArtTests [Fact] public void ClipArt_KeepsItsColors_InAFewLayers() { - Assert.Equal(20, ClipArt.Count); + Assert.Equal(34, ClipArt.Count); foreach (var item in ClipArt) { Assert.True(item.HasOwnColors, item.Name); From 6c7f600b6342888bd94e4ebf0b4562b52ecc9d1e Mon Sep 17 00:00:00 2001 From: niksedk Date: Wed, 14 Oct 2026 20:48:12 +0200 Subject: [PATCH 149/306] ASSA draw: Popcorn and Coffee cup clip art Co-Authored-By: Claude Opus 5.5 --- src/ui/Features/Assa/AssaDraw/ShapeLibrary.cs | 4 ++++ src/ui/Logic/Config/Language/Assa/LanguageAssa.cs | 4 ++++ tests/UI/Features/Assa/AssaDrawClipArtTests.cs | 2 +- 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/ui/Features/Assa/AssaDraw/ShapeLibrary.cs b/src/ui/Features/Assa/AssaDraw/ShapeLibrary.cs index 1552abe02ed..ae24de26157 100644 --- a/src/ui/Features/Assa/AssaDraw/ShapeLibrary.cs +++ b/src/ui/Features/Assa/AssaDraw/ShapeLibrary.cs @@ -503,6 +503,10 @@ private static List MakeBuiltIn() """"""), Clip(l.DrawClipArtNoEntry, null, """"""), + Clip(l.DrawClipArtPopcorn, null, + """"""), + Clip(l.DrawClipArtCoffeeCup, null, + """"""), ]; } } diff --git a/src/ui/Logic/Config/Language/Assa/LanguageAssa.cs b/src/ui/Logic/Config/Language/Assa/LanguageAssa.cs index 427c7cabadf..b7be40952a2 100644 --- a/src/ui/Logic/Config/Language/Assa/LanguageAssa.cs +++ b/src/ui/Logic/Config/Language/Assa/LanguageAssa.cs @@ -90,6 +90,8 @@ public class LanguageAssa public string DrawClipArtMicrophone { get; set; } public string DrawClipArtWarningSign { get; set; } public string DrawClipArtNoEntry { get; set; } + public string DrawClipArtPopcorn { get; set; } + public string DrawClipArtCoffeeCup { get; set; } public string DrawCategoryMyShapes { get; set; } public string DrawShapeSpeechBubble { get; set; } public string DrawShapeSpeechBubbleRight { get; set; } @@ -507,6 +509,8 @@ public LanguageAssa() DrawClipArtMicrophone = "Microphone"; DrawClipArtWarningSign = "Warning sign"; DrawClipArtNoEntry = "No entry"; + DrawClipArtPopcorn = "Popcorn"; + DrawClipArtCoffeeCup = "Coffee cup"; DrawCategoryMyShapes = "My shapes"; DrawShapeSpeechBubble = "Speech bubble"; DrawShapeSpeechBubbleRight = "Speech bubble, tail right"; diff --git a/tests/UI/Features/Assa/AssaDrawClipArtTests.cs b/tests/UI/Features/Assa/AssaDrawClipArtTests.cs index dbcbe55df3f..c362cf1a689 100644 --- a/tests/UI/Features/Assa/AssaDrawClipArtTests.cs +++ b/tests/UI/Features/Assa/AssaDrawClipArtTests.cs @@ -19,7 +19,7 @@ public class AssaDrawClipArtTests [Fact] public void ClipArt_KeepsItsColors_InAFewLayers() { - Assert.Equal(34, ClipArt.Count); + Assert.Equal(36, ClipArt.Count); foreach (var item in ClipArt) { Assert.True(item.HasOwnColors, item.Name); From 783b9c680b45b195155d346db4304082de2d3b4b Mon Sep 17 00:00:00 2001 From: niksedk Date: Wed, 14 Oct 2026 22:09:15 +0200 Subject: [PATCH 150/306] ASSA Set background: ASSA draw look, style gallery, nine new borders - Laid out like ASSA draw (always dark, preview in the middle, properties panel on the right, compact fields, accent OK button), reusing its editor styles and helpers. - Box styles are a gallery of thumbnails drawn by the generator itself. - New borders: pill, cut corners, slanted, ticket, speech bubble, ribbon, brush stroke (seeded, so the preview doesn't jitter), corner brackets, underline bar. - Padding as a box diagram; shadow distance gets a field; radius only editable for rounded corners; outline width and shadow distance are saved like the other settings. Co-Authored-By: Claude Opus 5.5 --- .../Features/Assa/AssaDraw/AssaDrawWindow.cs | 48 +- .../AssSetBackgroundViewModel.cs | 209 ++++++- .../AssSetBackgroundWindow.cs | 537 ++++++++++-------- .../Config/Language/Assa/LanguageAssa.cs | 18 + src/ui/Logic/Config/SeAssa.cs | 4 + .../Assa/AssaSetBackgroundGalleryTests.cs | 35 ++ 6 files changed, 572 insertions(+), 279 deletions(-) create mode 100644 tests/UI/Features/Assa/AssaSetBackgroundGalleryTests.cs diff --git a/src/ui/Features/Assa/AssaDraw/AssaDrawWindow.cs b/src/ui/Features/Assa/AssaDraw/AssaDrawWindow.cs index 25d044be89d..132fccd4b82 100644 --- a/src/ui/Features/Assa/AssaDraw/AssaDrawWindow.cs +++ b/src/ui/Features/Assa/AssaDraw/AssaDrawWindow.cs @@ -30,24 +30,24 @@ namespace Nikse.SubtitleEdit.Features.Assa.AssaDraw; /// public class AssaDrawWindow : Window { - private static readonly IBrush WindowBrush = new ImmutableSolidColorBrush(Color.FromRgb(30, 31, 36)); - private static readonly IBrush PanelBrush = new ImmutableSolidColorBrush(Color.FromRgb(36, 38, 44)); - private static readonly IBrush StatusBrush = new ImmutableSolidColorBrush(Color.FromRgb(27, 28, 33)); - private static readonly IBrush ButtonBarBrush = new ImmutableSolidColorBrush(Color.FromRgb(32, 34, 39)); - private static readonly IBrush LineBrush = new ImmutableSolidColorBrush(Color.FromRgb(47, 50, 58)); - private static readonly IBrush SeparatorBrush = new ImmutableSolidColorBrush(Color.FromRgb(55, 58, 67)); - private static readonly IBrush DimTextBrush = new ImmutableSolidColorBrush(Color.FromRgb(140, 146, 160)); - private static readonly IBrush FaintTextBrush = new ImmutableSolidColorBrush(Color.FromRgb(111, 117, 131)); - private static readonly IBrush IconBrush = new ImmutableSolidColorBrush(Color.FromRgb(174, 180, 194)); - private static readonly IBrush HoverBrush = new ImmutableSolidColorBrush(Color.FromRgb(51, 54, 63)); - private static readonly IBrush PressedBrush = new ImmutableSolidColorBrush(Color.FromRgb(59, 62, 71)); - private static readonly IBrush AccentBrush = new ImmutableSolidColorBrush(Color.FromRgb(76, 110, 245)); - private static readonly IBrush AccentHoverBrush = new ImmutableSolidColorBrush(Color.FromRgb(92, 124, 250)); - private static readonly IBrush AccentSoftBrush = new ImmutableSolidColorBrush(Color.FromArgb(70, 76, 110, 245)); - private static readonly IBrush FieldBrush = new ImmutableSolidColorBrush(Color.FromRgb(27, 28, 33)); - private static readonly IBrush FieldBorderBrush = new ImmutableSolidColorBrush(Color.FromRgb(54, 57, 68)); - private static readonly IBrush MenuBrush = new ImmutableSolidColorBrush(Color.FromRgb(42, 44, 51)); - private static readonly IBrush MenuBorderBrush = new ImmutableSolidColorBrush(Color.FromRgb(66, 69, 79)); + internal static readonly IBrush WindowBrush = new ImmutableSolidColorBrush(Color.FromRgb(30, 31, 36)); + internal static readonly IBrush PanelBrush = new ImmutableSolidColorBrush(Color.FromRgb(36, 38, 44)); + internal static readonly IBrush StatusBrush = new ImmutableSolidColorBrush(Color.FromRgb(27, 28, 33)); + internal static readonly IBrush ButtonBarBrush = new ImmutableSolidColorBrush(Color.FromRgb(32, 34, 39)); + internal static readonly IBrush LineBrush = new ImmutableSolidColorBrush(Color.FromRgb(47, 50, 58)); + internal static readonly IBrush SeparatorBrush = new ImmutableSolidColorBrush(Color.FromRgb(55, 58, 67)); + internal static readonly IBrush DimTextBrush = new ImmutableSolidColorBrush(Color.FromRgb(140, 146, 160)); + internal static readonly IBrush FaintTextBrush = new ImmutableSolidColorBrush(Color.FromRgb(111, 117, 131)); + internal static readonly IBrush IconBrush = new ImmutableSolidColorBrush(Color.FromRgb(174, 180, 194)); + internal static readonly IBrush HoverBrush = new ImmutableSolidColorBrush(Color.FromRgb(51, 54, 63)); + internal static readonly IBrush PressedBrush = new ImmutableSolidColorBrush(Color.FromRgb(59, 62, 71)); + internal static readonly IBrush AccentBrush = new ImmutableSolidColorBrush(Color.FromRgb(76, 110, 245)); + internal static readonly IBrush AccentHoverBrush = new ImmutableSolidColorBrush(Color.FromRgb(92, 124, 250)); + internal static readonly IBrush AccentSoftBrush = new ImmutableSolidColorBrush(Color.FromArgb(70, 76, 110, 245)); + internal static readonly IBrush FieldBrush = new ImmutableSolidColorBrush(Color.FromRgb(27, 28, 33)); + internal static readonly IBrush FieldBorderBrush = new ImmutableSolidColorBrush(Color.FromRgb(54, 57, 68)); + internal static readonly IBrush MenuBrush = new ImmutableSolidColorBrush(Color.FromRgb(42, 44, 51)); + internal static readonly IBrush MenuBorderBrush = new ImmutableSolidColorBrush(Color.FromRgb(66, 69, 79)); private readonly AssaDrawViewModel _vm; private readonly AssaDrawCanvas _canvas; @@ -142,7 +142,7 @@ private void OnLoaded(object? sender, global::Avalonia.Interactivity.RoutedEvent /// /// Flat icon buttons with an accent "active" state, compact number fields and accent menus. /// - private static void AddEditorStyles(Styles styles) + internal static void AddEditorStyles(Styles styles) { static Style ToolPresenter(Func button, params (AvaloniaProperty Property, object Value)[] setters) { @@ -600,7 +600,7 @@ private static Border CreateToolbar(AssaDrawViewModel vm) }; } - private static TextBlock MakeDimLabel(string text, Thickness margin) + internal static TextBlock MakeDimLabel(string text, Thickness margin) { return new TextBlock { @@ -615,7 +615,7 @@ private static TextBlock MakeDimLabel(string text, Thickness margin) /// /// Compact number field without spinner buttons, optionally with a dim prefix like "X". /// - private static NumericUpDown MakeField(string property, double minimum, double maximum, double width = double.NaN, string? prefix = null, string format = "0") + internal static NumericUpDown MakeField(string property, double minimum, double maximum, double width = double.NaN, string? prefix = null, string format = "0") { var field = new NumericUpDown { @@ -655,7 +655,7 @@ private static Border MakeToolbarSeparator() }; } - private static Button CreateToolButton(string icon, string tooltip, System.Windows.Input.ICommand? command, double size = 30, string? shortcut = null) + internal static Button CreateToolButton(string icon, string tooltip, System.Windows.Input.ICommand? command, double size = 30, string? shortcut = null) { Control content = new Optris.Icons.Avalonia.Icon { Value = icon, FontSize = size > 34 ? 15 : 13 }; if (shortcut != null) @@ -1005,7 +1005,7 @@ private Grid CreateContentArea(AssaDrawViewModel vm, out AssaDrawCanvas canvas) return contentGrid; } - private static TextBlock MakeSectionHeader(string text) + internal static TextBlock MakeSectionHeader(string text) { return new TextBlock { @@ -1188,7 +1188,7 @@ private static Border MakePropertySection(string title, Control content, string /// /// A "Label [field] [field]" row of the properties panel. /// - private static Grid MakePropertyRow(string label, params Control[] fields) + internal static Grid MakePropertyRow(string label, params Control[] fields) { var grid = new Grid { ColumnSpacing = 6 }; grid.ColumnDefinitions.Add(new ColumnDefinition(new GridLength(70))); diff --git a/src/ui/Features/Assa/AssaSetBackground/AssSetBackgroundViewModel.cs b/src/ui/Features/Assa/AssaSetBackground/AssSetBackgroundViewModel.cs index d7fd0c005f1..11f1f6b0d59 100644 --- a/src/ui/Features/Assa/AssaSetBackground/AssSetBackgroundViewModel.cs +++ b/src/ui/Features/Assa/AssaSetBackground/AssSetBackgroundViewModel.cs @@ -46,7 +46,9 @@ public partial class AssSetBackgroundViewModel : ObservableObject [ObservableProperty] private Color _shadowColor = Color.FromRgb(128, 128, 128); [ObservableProperty] private int _outlineWidth = 2; [ObservableProperty] private int _shadowDistance = 1; - [ObservableProperty] private int _boxStyleIndex; // 0=square, 1=rounded, 2=circle + [ObservableProperty] + [NotifyPropertyChangedFor(nameof(IsRadiusEnabled), nameof(BoxStyleName))] + private int _boxStyleIndex; // 0=square, 1=rounded, 2=circle [ObservableProperty] private ObservableCollection _boxStyles = []; [ObservableProperty] private int _radius = 10; @@ -75,6 +77,13 @@ public partial class AssSetBackgroundViewModel : ObservableObject public Subtitle ResultSubtitle => _subtitle; + /// + /// Only the rounded corners style uses the radius. + /// + public bool IsRadiusEnabled => BoxStyleIndex == 1; + + public string BoxStyleName => BoxStyleIndex >= 0 && BoxStyleIndex < BoxStyles.Count ? BoxStyles[BoxStyleIndex] : string.Empty; + public AssSetBackgroundViewModel() { BoxStyles.Add(Se.Language.Assa.ProgressBarSquareCorners); @@ -89,6 +98,15 @@ public AssSetBackgroundViewModel() BoxStyles.Add(Se.Language.Assa.BackgroundBoxTornPaperDouble); BoxStyles.Add(Se.Language.Assa.BackgroundBoxStarburst); BoxStyles.Add(Se.Language.Assa.BackgroundBoxScroll); + BoxStyles.Add(Se.Language.Assa.BackgroundBoxPill); + BoxStyles.Add(Se.Language.Assa.BackgroundBoxCutCorners); + BoxStyles.Add(Se.Language.Assa.BackgroundBoxSlanted); + BoxStyles.Add(Se.Language.Assa.BackgroundBoxTicket); + BoxStyles.Add(Se.Language.Assa.BackgroundBoxSpeechBubble); + BoxStyles.Add(Se.Language.Assa.BackgroundBoxRibbon); + BoxStyles.Add(Se.Language.Assa.BackgroundBoxBrushStroke); + BoxStyles.Add(Se.Language.Assa.BackgroundBoxCornerBrackets); + BoxStyles.Add(Se.Language.Assa.BackgroundBoxUnderline); VideoPlayerControl = new VideoPlayerControl(new EmptyVideoPlayer()); @@ -112,6 +130,8 @@ private void LoadSettings() BoxStyleIndex = Se.Settings.Assa.BackgroundBoxesStyleIndex; Radius = Se.Settings.Assa.BackgroundBoxesStyleRadius; + OutlineWidth = Se.Settings.Assa.BackgroundBoxesOutlineWidth; + ShadowDistance = Se.Settings.Assa.BackgroundBoxesShadowDistance; BoxColor = Se.Settings.Assa.BackgroundBoxesBoxColor.FromHexToColor(); ShadowColor = Se.Settings.Assa.BackgroundBoxesShadowColor.FromHexToColor(); OutlineColor = Se.Settings.Assa.BackgroundBoxesOutlineColor.FromHexToColor(); @@ -131,6 +151,8 @@ private void SaveSettings() Se.Settings.Assa.BackgroundBoxesStyleIndex = BoxStyleIndex; Se.Settings.Assa.BackgroundBoxesStyleRadius = Radius; + Se.Settings.Assa.BackgroundBoxesOutlineWidth = OutlineWidth; + Se.Settings.Assa.BackgroundBoxesShadowDistance = ShadowDistance; Se.Settings.Assa.BackgroundBoxesBoxColor = BoxColor.FromColorToHex(); Se.Settings.Assa.BackgroundBoxesShadowColor = ShadowColor.FromColorToHex(); Se.Settings.Assa.BackgroundBoxesOutlineColor = OutlineColor.FromColorToHex(); @@ -315,18 +337,24 @@ private SsaStyle MakeBoxStyle(string boxStyleName) }; } - private string GenerateBackgroundBox(int left, int right, int top, int bottom) + private string GenerateBackgroundBox(int left, int right, int top, int bottom) => + GenerateBackgroundBox(BoxStyleIndex, Radius, left, right, top, bottom); + + /// + /// The box drawing for a style (index in ) - also draws the style gallery thumbnails. + /// + internal static string GenerateBackgroundBox(int boxStyleIndex, int radius, int left, int right, int top, int bottom) { var width = right - left; var height = bottom - top; - if (BoxStyleIndex == 1 && Radius > 0) // Rounded corners + if (boxStyleIndex == 1 && radius > 0) // Rounded corners { - var r = Math.Min(Radius, Math.Min(width / 2, height / 2)); + var r = Math.Min(radius, Math.Min(width / 2, height / 2)); return $"{{\\p1}}m {left + r} {top} l {right - r} {top} b {right} {top} {right} {top + r} {right} {top + r} l {right} {bottom - r} b {right} {bottom} {right - r} {bottom} {right - r} {bottom} l {left + r} {bottom} b {left} {bottom} {left} {bottom - r} {left} {bottom - r} l {left} {top + r} b {left} {top} {left + r} {top} {left + r} {top}{{\\p0}}"; } - else if (BoxStyleIndex == 2) // Circle + else if (boxStyleIndex == 2) // Circle { var centerX = (left + right) / 2; var centerY = (top + bottom) / 2; @@ -341,48 +369,201 @@ private string GenerateBackgroundBox(int left, int right, int top, int bottom) return $"{{\\p1}}m {centerX} {centerY - radiusY} b {centerX + ox} {centerY - radiusY} {centerX + radiusX} {centerY - oy} {centerX + radiusX} {centerY} b {centerX + radiusX} {centerY + oy} {centerX + ox} {centerY + radiusY} {centerX} {centerY + radiusY} b {centerX - ox} {centerY + radiusY} {centerX - radiusX} {centerY + oy} {centerX - radiusX} {centerY} b {centerX - radiusX} {centerY - oy} {centerX - ox} {centerY - radiusY} {centerX} {centerY - radiusY}{{\\p0}}"; } - else if (BoxStyleIndex == 3) // Spikes + else if (boxStyleIndex == 3) // Spikes { return GenerateSpikesBox(left, right, top, bottom); } - else if (BoxStyleIndex == 4) // Bubbles + else if (boxStyleIndex == 4) // Bubbles { return GenerateBubblesBox(left, right, top, bottom); } - else if (BoxStyleIndex == 5) // Wave + else if (boxStyleIndex == 5) // Wave { return GenerateWaveBox(left, right, top, bottom); } - else if (BoxStyleIndex == 6) // Hexagon + else if (boxStyleIndex == 6) // Hexagon { return GenerateHexagonBox(left, right, top, bottom); } - else if (BoxStyleIndex == 7) // Torn paper + else if (boxStyleIndex == 7) // Torn paper { return GenerateTornPaperBox(left, right, top, bottom); } - else if (BoxStyleIndex == 8) // Cloud + else if (boxStyleIndex == 8) // Cloud { return GenerateCloudBox(left, right, top, bottom); } - else if (BoxStyleIndex == 9) // Torn paper (top & bottom) + else if (boxStyleIndex == 9) // Torn paper (top & bottom) { return GenerateTornPaperDoubleBox(left, right, top, bottom); } - else if (BoxStyleIndex == 10) // Starburst + else if (boxStyleIndex == 10) // Starburst { return GenerateStarburstBox(left, right, top, bottom); } - else if (BoxStyleIndex == 11) // Scroll / Parchment + else if (boxStyleIndex == 11) // Scroll / Parchment { return GenerateScrollBox(left, right, top, bottom); } + else if (boxStyleIndex == 12) // Pill: fully round ends + { + return GenerateBackgroundBox(1, Math.Max(1, height / 2), left, right, top, bottom); + } + else if (boxStyleIndex == 13) + { + return GenerateCutCornersBox(left, right, top, bottom); + } + else if (boxStyleIndex == 14) + { + return GenerateSlantedBox(left, right, top, bottom); + } + else if (boxStyleIndex == 15) + { + return GenerateTicketBox(left, right, top, bottom); + } + else if (boxStyleIndex == 16) + { + return GenerateSpeechBubbleBox(left, right, top, bottom); + } + else if (boxStyleIndex == 17) + { + return GenerateRibbonBox(left, right, top, bottom); + } + else if (boxStyleIndex == 18) + { + return GenerateBrushStrokeBox(left, right, top, bottom); + } + else if (boxStyleIndex == 19) + { + return GenerateCornerBracketsBox(left, right, top, bottom); + } + else if (boxStyleIndex == 20) + { + return GenerateUnderlineBox(left, right, top, bottom); + } // Square corners (default) return $"{{\\p1}}m {left} {top} l {right} {top} {right} {bottom} {left} {bottom}{{\\p0}}"; } + private static string GenerateCutCornersBox(int left, int right, int top, int bottom) + { + var c = Math.Max(2, Math.Min((bottom - top) / 3, (right - left) / 4)); + return $"{{\\p1}}m {left + c} {top} l {right - c} {top} {right} {top + c} {right} {bottom - c} {right - c} {bottom} {left + c} {bottom} {left} {bottom - c} {left} {top + c}{{\\p0}}"; + } + + /// + /// A parallelogram leaning right, wide enough that the text stays inside. + /// + private static string GenerateSlantedBox(int left, int right, int top, int bottom) + { + var s = Math.Max(2, (bottom - top) * 35 / 100); + return $"{{\\p1}}m {left} {top} l {right + s} {top} {right} {bottom} {left - s} {bottom}{{\\p0}}"; + } + + /// + /// A ticket: half-round notches cut into the middle of both ends. + /// + private static string GenerateTicketBox(int left, int right, int top, int bottom) + { + var cy = (top + bottom) / 2; + var n = Math.Max(2, (bottom - top) / 4); + var k = (int)Math.Round(n * 1.333); + return $"{{\\p1}}m {left} {top} l {right} {top} {right} {cy - n} b {right - k} {cy - n} {right - k} {cy + n} {right} {cy + n} " + + $"l {right} {bottom} {left} {bottom} {left} {cy + n} b {left + k} {cy + n} {left + k} {cy - n} {left} {cy - n}{{\\p0}}"; + } + + /// + /// A rounded box with a tail under its left part, pointing down at the speaker. + /// + private static string GenerateSpeechBubbleBox(int left, int right, int top, int bottom) + { + var width = right - left; + var height = bottom - top; + var r = Math.Max(1, Math.Min(18, Math.Min(width / 4, height / 3))); + var tailLeft = left + width / 5; + var tailRight = tailLeft + Math.Max(8, width / 10); + var tipX = tailLeft - Math.Max(4, width / 30); + var tipY = bottom + Math.Max(10, height * 45 / 100); + return $"{{\\p1}}m {left + r} {top} l {right - r} {top} b {right} {top} {right} {top} {right} {top + r} " + + $"l {right} {bottom - r} b {right} {bottom} {right} {bottom} {right - r} {bottom} " + + $"l {tailRight} {bottom} {tipX} {tipY} {tailLeft} {bottom} {left + r} {bottom} b {left} {bottom} {left} {bottom} {left} {bottom - r} " + + $"l {left} {top + r} b {left} {top} {left} {top} {left + r} {top}{{\\p0}}"; + } + + /// + /// A banner with V-cut ends, reaching out past the text on both sides. + /// + private static string GenerateRibbonBox(int left, int right, int top, int bottom) + { + var d = Math.Max(4, (bottom - top) * 40 / 100); + var cy = (top + bottom) / 2; + return $"{{\\p1}}m {left - d} {top} l {right + d} {top} {right} {cy} {right + d} {bottom} {left - d} {bottom} {left} {cy}{{\\p0}}"; + } + + /// + /// A rough paint stroke: uneven top and bottom edges and ragged ends. The roughness is seeded + /// by the box size, so the preview doesn't jitter and lines of equal size match. + /// + private static string GenerateBrushStrokeBox(int left, int right, int top, int bottom) + { + var width = right - left; + var height = bottom - top; + var random = new Random(width * 7919 + height); + var amplitude = Math.Max(2, height / 12); + var step = Math.Max(8, height / 3); + var sb = new StringBuilder(); + sb.Append($"m {left} {top + random.Next(0, amplitude)} l "); + for (var x = left + step; x < right; x += step) + { + sb.Append($"{x} {top + random.Next(-amplitude, amplitude + 1)} "); + } + + // Ragged right end + for (var i = 0; i <= 3; i++) + { + sb.Append($"{right + random.Next(-amplitude, amplitude * 2 + 1)} {top + height * i / 3} "); + } + + for (var x = right - step; x > left; x -= step) + { + sb.Append($"{x} {bottom + random.Next(-amplitude, amplitude + 1)} "); + } + + // Ragged left end + for (var i = 3; i >= 1; i--) + { + sb.Append($"{left - random.Next(-amplitude, amplitude * 2 + 1)} {top + height * i / 3} "); + } + + return $"{{\\p1}}{sb.ToString().TrimEnd()}{{\\p0}}"; + } + + /// + /// Four L-shaped corner marks framing the text, no fill in between. + /// + private static string GenerateCornerBracketsBox(int left, int right, int top, int bottom) + { + var t = Math.Max(3, (bottom - top) / 12); + var a = Math.Max(t * 2, Math.Min(right - left, bottom - top) / 3); + return "{\\p1}" + + $"m {left} {top} l {left + a} {top} {left + a} {top + t} {left + t} {top + t} {left + t} {top + a} {left} {top + a} " + + $"m {right} {top} l {right} {top + a} {right - t} {top + a} {right - t} {top + t} {right - a} {top + t} {right - a} {top} " + + $"m {right} {bottom} l {right - a} {bottom} {right - a} {bottom - t} {right - t} {bottom - t} {right - t} {bottom - a} {right} {bottom - a} " + + $"m {left} {bottom} l {left} {bottom - a} {left + t} {bottom - a} {left + t} {bottom - t} {left + a} {bottom - t} {left + a} {bottom}" + + "{\\p0}"; + } + + /// + /// Just a bar along the bottom of the padded box, under the text. + /// + private static string GenerateUnderlineBox(int left, int right, int top, int bottom) + { + var t = Math.Max(4, (bottom - top) / 10); + return $"{{\\p1}}m {left} {bottom - t} l {right} {bottom - t} {right} {bottom} {left} {bottom}{{\\p0}}"; + } + private static string GenerateWaveBox(int left, int right, int top, int bottom) { const int amplitude = 6; diff --git a/src/ui/Features/Assa/AssaSetBackground/AssSetBackgroundWindow.cs b/src/ui/Features/Assa/AssaSetBackground/AssSetBackgroundWindow.cs index 6a28a748c19..636c32a1474 100644 --- a/src/ui/Features/Assa/AssaSetBackground/AssSetBackgroundWindow.cs +++ b/src/ui/Features/Assa/AssaSetBackground/AssSetBackgroundWindow.cs @@ -1,343 +1,398 @@ -using Avalonia; +using System.Collections.Generic; +using System.Linq; +using Avalonia; +using Avalonia.Automation; using Avalonia.Controls; +using Avalonia.Controls.Primitives; using Avalonia.Data; using Avalonia.Interactivity; using Avalonia.Layout; using Avalonia.Media; +using Avalonia.Styling; +using Nikse.SubtitleEdit.Features.Assa.AssaDraw; using Nikse.SubtitleEdit.Features.Main.Layout; using Nikse.SubtitleEdit.Logic; using Nikse.SubtitleEdit.Logic.Config; namespace Nikse.SubtitleEdit.Features.Assa.AssaSetBackground; +/// +/// Background box generator, laid out like ASSA draw: always dark, the libass preview in the +/// middle and a properties panel on the right with the box styles as a gallery. +/// public class AssSetBackgroundWindow : Window { + private static readonly IBrush ThumbnailBrush = new SolidColorBrush(Color.FromRgb(214, 218, 226)); + public AssSetBackgroundWindow(AssSetBackgroundViewModel vm) { UiUtil.InitializeWindow(this, GetType().Name); Title = Se.Language.Assa.BackgroundBoxGenerator; CanResize = true; - Width = 1100; - Height = 700; - MinWidth = 900; + Width = 1200; + Height = 760; + MinWidth = 950; MinHeight = 650; + RequestedThemeVariant = ThemeVariant.Dark; + Background = AssaDrawWindow.WindowBrush; + AssaDrawWindow.AddEditorStyles(Styles); vm.Window = this; DataContext = vm; vm.VideoPlayerControl = InitVideoPlayer.MakeVideoPlayer(); vm.VideoPlayerControl.FullScreenIsVisible = false; - var videoPanel = UiUtil.MakeBorderForControl(vm.VideoPlayerControl); - - var leftPanel = new Grid + var videoPanel = new Border { - RowDefinitions = - { - new RowDefinition { Height = GridLength.Auto }, - new RowDefinition { Height = GridLength.Auto }, - new RowDefinition { Height = GridLength.Auto }, - new RowDefinition { Height = GridLength.Auto }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Star) }, - new RowDefinition { Height = GridLength.Auto }, - }, - RowSpacing = 12, + Child = vm.VideoPlayerControl, + Background = Brushes.Black, + Margin = new Thickness(12), + CornerRadius = new CornerRadius(6), + ClipToBounds = true, }; - // Padding settings - leftPanel.Add(CreatePaddingPanel(vm, out var paddingLeftBox), 0); - - // Fill width settings - leftPanel.Add(CreateFillWidthPanel(vm), 1); - - // Style settings - leftPanel.Add(CreateStylePanel(vm), 2); + var sidePanel = CreateSidePanel(vm, out var firstInput); - // Colors - leftPanel.Add(CreateColorsPanel(vm), 3); - - var mainGrid = new Grid + var content = new Grid { ColumnDefinitions = { - new ColumnDefinition { Width = GridLength.Auto }, - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) }, + new ColumnDefinition(new GridLength(1, GridUnitType.Star)), + new ColumnDefinition(new GridLength(320)), }, + }; + content.Add(videoPanel, 0, 0); + content.Add(sidePanel, 0, 1); + + var mainGrid = new Grid + { RowDefinitions = { - new RowDefinition { Height = new GridLength(1, GridUnitType.Star) }, - new RowDefinition { Height = GridLength.Auto }, + new RowDefinition(new GridLength(1, GridUnitType.Star)), + new RowDefinition(GridLength.Auto), }, - Margin = UiUtil.MakeWindowMargin(), - ColumnSpacing = 12, }; - - // Progress text - var labelProgress = UiUtil.MakeLabel().WithBindText(vm, nameof(vm.ProgressText)); - - // Buttons - var buttonOk = UiUtil.MakeButtonOk(vm.OkCommand); - var buttonCancel = UiUtil.MakeButtonCancel(vm.CancelCommand); - var panelButtons = UiUtil.MakeButtonBar(buttonOk, buttonCancel); - - mainGrid.Add(leftPanel, 0, 0); - mainGrid.Add(videoPanel, 0, 1); - mainGrid.Add(labelProgress, 1, 0, 1, 2); - mainGrid.Add(panelButtons, 1, 0, 1, 2); - + mainGrid.Add(content, 0, 0); + mainGrid.Add(CreateButtonBar(vm), 1, 0); Content = mainGrid; // initial focus on an input, not an action button - a focused button clicks on bare Space - UiUtil.FocusOnFirstActivation(this, paddingLeftBox); + UiUtil.FocusOnFirstActivation(this, firstInput); AddHandler(KeyDownEvent, vm.KeyDown, RoutingStrategies.Tunnel | RoutingStrategies.Bubble, handledEventsToo: false); Loaded += (_, _) => vm.OnLoaded(); Closing += (_, _) => vm.OnClosing(); } - private static Border CreatePaddingPanel(AssSetBackgroundViewModel vm, out NumericUpDown firstInput) + private static Border CreateSidePanel(AssSetBackgroundViewModel vm, out NumericUpDown firstInput) { - var grid = new Grid + var sections = new StackPanel(); + sections.Children.Add(CreateStyleSection(vm)); + sections.Children.Add(CreatePaddingSection(vm, out firstInput)); + sections.Children.Add(CreateFillWidthSection(vm)); + sections.Children.Add(CreateColorsSection(vm)); + + return new Border { - RowDefinitions = - { - new RowDefinition { Height = GridLength.Auto }, - new RowDefinition { Height = GridLength.Auto }, - new RowDefinition { Height = GridLength.Auto }, - }, - ColumnDefinitions = - { - new ColumnDefinition { Width = GridLength.Auto }, - new ColumnDefinition { Width = GridLength.Auto }, - new ColumnDefinition { Width = GridLength.Auto }, - new ColumnDefinition { Width = GridLength.Auto }, - }, - RowSpacing = 8, - ColumnSpacing = 10, + Child = new ScrollViewer { Content = sections, HorizontalScrollBarVisibility = ScrollBarVisibility.Disabled }, + Background = AssaDrawWindow.PanelBrush, + BorderBrush = AssaDrawWindow.LineBrush, + BorderThickness = new Thickness(1, 0, 0, 0), }; + } - var titleLabel = new TextBlock + /// + /// A properties section like ASSA draw's: small caps header (with optional text on the right), line on top. + /// + private static Border MakeSection(string title, Control content, Control? headerRight = null, bool first = false) + { + var header = new DockPanel(); + if (headerRight != null) { - Text = Se.Language.General.Padding, - FontWeight = FontWeight.Bold, - Margin = new Thickness(0, 0, 0, 5), + DockPanel.SetDock(headerRight, Dock.Right); + header.Children.Add(headerRight); + } + + header.Children.Add(AssaDrawWindow.MakeSectionHeader(title)); + var panel = new StackPanel { Spacing = 10 }; + panel.Children.Add(header); + panel.Children.Add(content); + return new Border + { + Child = panel, + BorderBrush = AssaDrawWindow.LineBrush, + BorderThickness = new Thickness(0, first ? 0 : 1, 0, 0), + Padding = new Thickness(14, 12, 14, 14), }; - grid.Add(titleLabel, 0, 0, 1, 4); - - // Left - var leftLabel = new TextBlock { Text = Se.Language.General.Left, VerticalAlignment = VerticalAlignment.Center }; - grid.Add(leftLabel, 1, 0); - - var leftBox = UiUtil.MakeNumericUpDownInt(0, 500, 0, 120, vm, nameof(vm.PaddingLeft)); - firstInput = leftBox; - grid.Add(leftBox, 1, 1); + } - // Right - var rightLabel = new TextBlock { Text = Se.Language.General.Right, VerticalAlignment = VerticalAlignment.Center }; - grid.Add(rightLabel, 1, 2); + /// + /// The box styles as a gallery, each thumbnail drawn by the generator itself. + /// + private static Border CreateStyleSection(AssSetBackgroundViewModel vm) + { + const int columns = 3; + var gallery = new UniformGrid { Columns = columns }; + var tiles = new List
- private static string BuildRequestJson(string systemPrompt, string userContent, string? responseFormatJson, string? model, int defaultMaxTokens, double temperatureBump = 0) + internal static string BuildRequestJson(string systemPrompt, string userContent, string? responseFormatJson, string? model, int defaultMaxTokens, double temperatureBump = 0, bool disableThinking = false) { using var stream = new System.IO.MemoryStream(); using (var writer = new Utf8JsonWriter(stream)) @@ -105,6 +119,13 @@ private static string BuildRequestJson(string systemPrompt, string userContent, WriteSampling(writer, defaultMaxTokens, temperatureBump); + if (disableThinking) + { + writer.WriteStartObject("chat_template_kwargs"); + writer.WriteBoolean("enable_thinking", false); + writer.WriteEndObject(); + } + if (responseFormatJson != null) { writer.WritePropertyName("response_format"); diff --git a/tests/UI/Features/Translate/LlamaCppAdvancedClientReasoningTests.cs b/tests/UI/Features/Translate/LlamaCppAdvancedClientReasoningTests.cs index fe3584192e2..984d7f9b795 100644 --- a/tests/UI/Features/Translate/LlamaCppAdvancedClientReasoningTests.cs +++ b/tests/UI/Features/Translate/LlamaCppAdvancedClientReasoningTests.cs @@ -197,7 +197,45 @@ public async Task OrdinaryContentStreamIsNotFlaggedAsReasoning() Assert.False(client.ReplyFromReasoning); } - private static async Task ChatAgainstFakeServerAsync(LlamaCppAdvancedClient client, string sseBody) + [Fact] + public void RequestLeavesThinkingAloneByDefault() + { + var json = LlamaCppAdvancedClient.BuildRequestJson("system", "user", null, null, 100); + + Assert.DoesNotContain("chat_template_kwargs", json); + } + + [Fact] + public void RequestCanTurnThinkingOff() + { + var json = LlamaCppAdvancedClient.BuildRequestJson("system", "user", null, null, 100, disableThinking: true); + + using var doc = System.Text.Json.JsonDocument.Parse(json); + Assert.False(doc.RootElement.GetProperty("chat_template_kwargs").GetProperty("enable_thinking").GetBoolean()); + } + + /// + /// A hybrid Qwen model on a server started without "--reasoning off" thinks until the token + /// budget runs out, leaving only reasoning_content; the next request must ask for no thinking. + /// + [Fact] + public async Task ReasoningOnlyReplyTurnsThinkingOffForTheNextRequest() + { + using var client = new LlamaCppAdvancedClient(); + var firstRequest = string.Empty; + await ChatAgainstFakeServerAsync(client, "data: " + Chunk("reasoning_content", "Okay, let's see. The user wants Danish.") + "\n\ndata: [DONE]\n\n", body => firstRequest = body); + Assert.True(client.DisableThinking); + Assert.DoesNotContain("enable_thinking", firstRequest); + + var secondRequest = string.Empty; + var reply = await ChatAgainstFakeServerAsync(client, "data: " + Chunk("content", TranslationJson) + "\n\ndata: [DONE]\n\n", body => secondRequest = body); + + Assert.Equal(TranslationJson, reply); + Assert.False(client.ReplyFromReasoning); + Assert.Contains("\"enable_thinking\":false", secondRequest); + } + + private static async Task ChatAgainstFakeServerAsync(LlamaCppAdvancedClient client, string sseBody, Action? onRequest = null) { var (listener, url) = StartListener(); using (listener) @@ -207,7 +245,8 @@ private static async Task ChatAgainstFakeServerAsync(LlamaCppAdvancedCli var context = await listener.GetContextAsync(); using (var reader = new StreamReader(context.Request.InputStream)) { - await reader.ReadToEndAsync(); + var requestBody = await reader.ReadToEndAsync(); + onRequest?.Invoke(requestBody); } var bytes = Encoding.UTF8.GetBytes(sseBody); From 05c4ce42c55268af49132e98de71eef668f5764d Mon Sep 17 00:00:00 2001 From: niksedk Date: Thu, 15 Oct 2026 12:50:19 +0200 Subject: [PATCH 178/306] ASSA: resampler, font collector, style picker, tag history in the ASSA draw look MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Resolution resampler: W × H fields with presets and from-video button, options as switches, video-prompt banner, scale factor in the button bar - Font collector: view switcher + per-view actions on top, status badges, preview stage, scan status and trim switch in the button bar - Style picker: section header with check all / none, accent accept button - Tag history: monospace list with delete in the header - Shared MakeToggleRow helper (Set position uses it too) All follow the app theme (light/dark). Co-Authored-By: Claude Opus 5.5 --- .../AssaTagHistoryWindow.cs | 97 ++-- .../Features/Assa/AssaDraw/AssaDrawWindow.cs | 20 + .../AssaResolutionResamplerWindow.cs | 347 ++++++-------- .../AssaSetPosition/AssaSetPositionWindow.cs | 23 +- src/ui/Features/Assa/AssaStylePickerWindow.cs | 87 ++-- .../Assa/FontCollector/FontCollectorWindow.cs | 452 ++++++++++-------- 6 files changed, 540 insertions(+), 486 deletions(-) diff --git a/src/ui/Features/Assa/AssaApplyCustomOverrideTags/AssaTagHistoryWindow.cs b/src/ui/Features/Assa/AssaApplyCustomOverrideTags/AssaTagHistoryWindow.cs index 098b60e94f1..7a0e0409657 100644 --- a/src/ui/Features/Assa/AssaApplyCustomOverrideTags/AssaTagHistoryWindow.cs +++ b/src/ui/Features/Assa/AssaApplyCustomOverrideTags/AssaTagHistoryWindow.cs @@ -1,70 +1,89 @@ -using Avalonia.Controls; +using Avalonia; +using Avalonia.Automation; +using Avalonia.Controls; +using Avalonia.Controls.Templates; using Avalonia.Data; using Avalonia.Layout; +using Avalonia.Media; +using Nikse.SubtitleEdit.Features.Assa.AssaDraw; using Nikse.SubtitleEdit.Logic; using Nikse.SubtitleEdit.Logic.Config; namespace Nikse.SubtitleEdit.Features.Assa.AssaApplyCustomOverrideTags; +/// +/// Earlier override tags, newest first - styled like ASSA draw and the apply override tags window. +/// public class AssaTagHistoryWindow : Window { + private static readonly FontFamily MonoFont = new("Menlo, Consolas, Cascadia Mono, DejaVu Sans Mono, monospace"); + public AssaTagHistoryWindow(AssaTagHistoryViewModel vm) { UiUtil.InitializeWindow(this, GetType().Name); Title = Se.Language.Assa.OverrideTagsHistory; CanResize = false; - SizeToContent = SizeToContent.WidthAndHeight; - MinWidth = 450; + Width = 520; + SizeToContent = SizeToContent.Height; + Background = AssaDrawWindow.WindowBrush; + AssaDrawWindow.AddEditorStyles(Styles); vm.Window = this; DataContext = vm; - var grid = new Grid - { - RowDefinitions = - { - new RowDefinition { Height = new GridLength(300) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - }, - ColumnDefinitions = - { - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) }, - }, - Margin = UiUtil.MakeWindowMargin(), - RowSpacing = 15, - Width = double.NaN, - HorizontalAlignment = HorizontalAlignment.Stretch, - }; - var listBox = new ListBox { [!ListBox.ItemsSourceProperty] = new Binding(nameof(vm.OverrideTags)) { Mode = BindingMode.OneWay }, [!ListBox.SelectedItemProperty] = new Binding(nameof(vm.SelectedOverrideTag)) { Mode = BindingMode.TwoWay }, - Width = double.NaN, - HorizontalAlignment = HorizontalAlignment.Stretch, - }; - listBox.DoubleTapped += (s, e) => vm.OkCommand.Execute(null); - grid.Add(listBox, 0); - - var buttonDelete = UiUtil.MakeButton(Se.Language.General.Delete, vm.DeleteItemCommand); - var panelDelete = new StackPanel - { - Orientation = Orientation.Horizontal, - Children = { buttonDelete }, + Height = 300, + Background = AssaDrawWindow.FieldBrush, + BorderBrush = AssaDrawWindow.FieldBorderBrush, + BorderThickness = new Thickness(1), + CornerRadius = new CornerRadius(8), + Padding = new Thickness(4), + ItemTemplate = new FuncDataTemplate((_, _) => new TextBlock + { + FontFamily = MonoFont, + FontSize = 12.5, + TextTrimming = TextTrimming.CharacterEllipsis, + [!TextBlock.TextProperty] = new Binding("."), + }), }; - grid.Add(panelDelete, 1); + AutomationProperties.SetName(listBox, Se.Language.Assa.OverrideTagsHistory); + listBox.DoubleTapped += (_, _) => vm.OkCommand.Execute(null); - // Buttons - var buttonOk = UiUtil.MakeButtonOk(vm.OkCommand); - var buttonCancel = UiUtil.MakeButtonCancel(vm.CancelCommand); - var panelButtons = UiUtil.MakeButtonBar(buttonOk, buttonCancel); - grid.Add(panelButtons, 2); + var delete = AssaDrawWindow.CreateToolButton("fa-solid fa-trash", Se.Language.General.Delete, vm.DeleteItemCommand, 24); + var section = AssaDrawWindow.MakeEditorSection(Se.Language.General.History, listBox, delete, first: true); + section.Background = AssaDrawWindow.PanelBrush; - Content = grid; + var root = new DockPanel(); + var buttonBar = MakeButtonBar(vm); + DockPanel.SetDock(buttonBar, Dock.Bottom); + root.Children.Add(buttonBar); + root.Children.Add(section); + Content = root; // initial focus on an input, not an action button - a focused button clicks on bare Space UiUtil.FocusOnFirstActivation(this, listBox); KeyDown += vm.KeyDown; } + + private static Border MakeButtonBar(AssaTagHistoryViewModel vm) + { + var buttonOk = UiUtil.MakeButtonOk(vm.OkCommand); + buttonOk.Classes.Add("primary"); + var buttonCancel = UiUtil.MakeButtonCancel(vm.CancelCommand); + var buttons = new StackPanel { Orientation = Orientation.Horizontal, Spacing = 10, HorizontalAlignment = HorizontalAlignment.Right }; + buttons.Children.Add(buttonCancel); + buttons.Children.Add(buttonOk); + + return new Border + { + Child = buttons, + Background = AssaDrawWindow.ButtonBarBrush, + BorderBrush = AssaDrawWindow.LineBrush, + BorderThickness = new Thickness(0, 1, 0, 0), + Padding = new Thickness(16, 10), + }; + } } diff --git a/src/ui/Features/Assa/AssaDraw/AssaDrawWindow.cs b/src/ui/Features/Assa/AssaDraw/AssaDrawWindow.cs index 695c058d428..81656d7fe17 100644 --- a/src/ui/Features/Assa/AssaDraw/AssaDrawWindow.cs +++ b/src/ui/Features/Assa/AssaDraw/AssaDrawWindow.cs @@ -1084,6 +1084,26 @@ internal static Border MakeEditorSection(string title, Control content, Control? }; } + /// + /// A compact on/off switch with its label to the right. + /// + internal static StackPanel MakeToggleRow(string property, string text) + { + var toggle = new ToggleSwitch + { + OnContent = null, + OffContent = null, + MinWidth = 0, + VerticalAlignment = VerticalAlignment.Center, + [!ToggleSwitch.IsCheckedProperty] = new Binding(property) { Mode = BindingMode.TwoWay }, + }; + AutomationProperties.SetName(toggle, text); + var row = new StackPanel { Orientation = Orientation.Horizontal, Spacing = 8 }; + row.Children.Add(toggle); + row.Children.Add(new TextBlock { Text = text, FontSize = 12.5, VerticalAlignment = VerticalAlignment.Center, TextWrapping = TextWrapping.Wrap }); + return row; + } + internal static TextBlock MakeSectionHeader(string text) { return new TextBlock diff --git a/src/ui/Features/Assa/AssaResolutionResampler/AssaResolutionResamplerWindow.cs b/src/ui/Features/Assa/AssaResolutionResampler/AssaResolutionResamplerWindow.cs index f5439859486..e9804d63fc8 100644 --- a/src/ui/Features/Assa/AssaResolutionResampler/AssaResolutionResamplerWindow.cs +++ b/src/ui/Features/Assa/AssaResolutionResampler/AssaResolutionResamplerWindow.cs @@ -1,93 +1,62 @@ -using Avalonia; +using System; +using Avalonia; +using Avalonia.Automation; using Avalonia.Controls; using Avalonia.Data; using Avalonia.Layout; using Avalonia.Media; +using Nikse.SubtitleEdit.Features.Assa.AssaDraw; using Nikse.SubtitleEdit.Logic; using Nikse.SubtitleEdit.Logic.Config; namespace Nikse.SubtitleEdit.Features.Assa.ResolutionResampler; +/// +/// Changes the script resolution and rescales what depends on it - styled like ASSA draw: +/// source and target as compact W × H fields with presets, the options as toggles, and +/// the scale factor in the button bar. +/// public class AssaResolutionResamplerWindow : Window { + private static readonly (int Width, int Height)[] Presets = [(640, 360), (1280, 720), (1920, 1080), (3840, 2160)]; + public AssaResolutionResamplerWindow(AssaResolutionResamplerViewModel vm) { UiUtil.InitializeWindow(this, GetType().Name); Title = Se.Language.Assa.ResolutionResamplerTitle; CanResize = false; - SizeToContent = SizeToContent.WidthAndHeight; - MinWidth = 450; + Width = 560; + SizeToContent = SizeToContent.Height; + Background = AssaDrawWindow.WindowBrush; + AssaDrawWindow.AddEditorStyles(Styles); vm.Window = this; DataContext = vm; - var grid = new Grid - { - ColumnDefinitions = - { - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) }, - }, - Margin = UiUtil.MakeWindowMargin(), - RowSpacing = 15, - Width = double.NaN, - HorizontalAlignment = HorizontalAlignment.Stretch, - }; - - void AddRow(Control control) - { - grid.RowDefinitions.Add(new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }); - Grid.SetRow(control, grid.RowDefinitions.Count - 1); - grid.Children.Add(control); - } + var sections = new StackPanel { Background = AssaDrawWindow.PanelBrush }; // Opened as the video-open prompt: say why the dialog appeared (#14367) if (vm.IsVideoPrompt) { - AddRow(new TextBlock - { - Text = vm.PromptText, - TextWrapping = TextWrapping.Wrap, - MaxWidth = 520, - HorizontalAlignment = HorizontalAlignment.Left, - }); + sections.Children.Add(MakePromptBanner(vm.PromptText)); } - // Source resolution panel - var sourcePanel = CreateResolutionPanel( - Se.Language.Assa.ResolutionResamplerSourceRes, - nameof(vm.SourceWidth), - nameof(vm.SourceHeight), - vm.SourceFromVideoCommand, - out var sourceWidthBox); - AddRow(sourcePanel); - - // Target resolution panel - var targetPanel = CreateResolutionPanel( - Se.Language.Assa.ResolutionResamplerTargetRes, - nameof(vm.TargetWidth), - nameof(vm.TargetHeight), - vm.TargetFromVideoCommand, - out _); - AddRow(targetPanel); - - // Options panel - AddRow(CreateOptionsPanel(vm)); - - if (vm.IsVideoPrompt) - { - AddRow(new CheckBox - { - Content = Se.Language.Assa.ResolutionResamplerAskOnVideoOpen, - [!CheckBox.IsCheckedProperty] = new Binding(nameof(vm.AskOnVideoOpen)) { Mode = BindingMode.TwoWay }, - }); - } - - // Buttons - var buttonOk = UiUtil.MakeButtonOk(vm.OkCommand); - var buttonCancel = UiUtil.MakeButtonCancel(vm.CancelCommand); - AddRow(UiUtil.MakeButtonBar(buttonOk, buttonCancel)); - - Content = grid; + sections.Children.Add(MakeResolutionSection(vm, Se.Language.Assa.ResolutionResamplerSourceRes, + nameof(vm.SourceWidth), nameof(vm.SourceHeight), vm.SourceFromVideoCommand, + () => (vm.SourceWidth, vm.SourceHeight), (w, h) => { vm.SourceWidth = w; vm.SourceHeight = h; }, + first: !vm.IsVideoPrompt, out var sourceWidthBox)); + sections.Children.Add(MakeResolutionSection(vm, Se.Language.Assa.ResolutionResamplerTargetRes, + nameof(vm.TargetWidth), nameof(vm.TargetHeight), vm.TargetFromVideoCommand, + () => (vm.TargetWidth, vm.TargetHeight), (w, h) => { vm.TargetWidth = w; vm.TargetHeight = h; }, + first: false, out _)); + sections.Children.Add(MakeOptionsSection(vm)); + + var root = new DockPanel(); + var buttonBar = MakeButtonBar(vm); + DockPanel.SetDock(buttonBar, Dock.Bottom); + root.Children.Add(buttonBar); + root.Children.Add(sections); + Content = root; // initial focus on an input, not an action button - a focused button clicks on bare Space UiUtil.FocusOnFirstActivation(this, sourceWidthBox); @@ -95,157 +64,155 @@ void AddRow(Control control) Closing += (_, _) => vm.OnClosing(); } - private static Border CreateResolutionPanel(string title, string widthBinding, string heightBinding, System.Windows.Input.ICommand fromVideoCommand, out NumericUpDown firstInput) + private static Border MakePromptBanner(string text) { - var panelGrid = new Grid - { - RowDefinitions = - { - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - }, - ColumnDefinitions = - { - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Auto) }, - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Auto) }, - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Auto) }, - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Auto) }, - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) }, - }, - RowSpacing = 8, - ColumnSpacing = 10, - }; + var row = new StackPanel { Orientation = Orientation.Horizontal, Spacing = 10 }; + row.Children.Add(new Optris.Icons.Avalonia.Icon + { + Value = "fa-solid fa-circle-info", + FontSize = 14, + Foreground = AssaDrawWindow.AccentBrush, + VerticalAlignment = VerticalAlignment.Top, + Margin = new Thickness(0, 1, 0, 0), + }); + row.Children.Add(new TextBlock + { + Text = text, + FontSize = 12.5, + TextWrapping = TextWrapping.Wrap, + MaxWidth = 490, + }); - // Title - var titleLabel = new TextBlock - { - Text = title, - FontWeight = FontWeight.Bold, - Margin = new Thickness(0, 0, 0, 5), - }; - Grid.SetRow(titleLabel, 0); - Grid.SetColumnSpan(titleLabel, 5); - panelGrid.Children.Add(titleLabel); - - // Width - var widthLabel = new TextBlock - { - Text = Se.Language.General.Width + ":", - VerticalAlignment = VerticalAlignment.Center, - }; - Grid.SetRow(widthLabel, 1); - Grid.SetColumn(widthLabel, 0); - panelGrid.Children.Add(widthLabel); - - var widthBox = new NumericUpDown + return new Border { - Minimum = 1, - Maximum = 8192, - Width = 120, - Increment = 1, - FormatString = "0", - [!NumericUpDown.ValueProperty] = new Binding(widthBinding) { Mode = BindingMode.TwoWay }, + Child = row, + Background = AssaDrawWindow.AccentSoftBrush, + Padding = new Thickness(14, 10), }; - firstInput = widthBox; - Grid.SetRow(widthBox, 1); - Grid.SetColumn(widthBox, 1); - panelGrid.Children.Add(widthBox); + } - // Height - var heightLabel = new TextBlock - { - Text = Se.Language.General.Height + ":", - VerticalAlignment = VerticalAlignment.Center, - }; - Grid.SetRow(heightLabel, 1); - Grid.SetColumn(heightLabel, 2); - panelGrid.Children.Add(heightLabel); + private static Border MakeResolutionSection( + AssaResolutionResamplerViewModel vm, + string title, + string widthProperty, + string heightProperty, + System.Windows.Input.ICommand fromVideoCommand, + Func<(int Width, int Height)> get, + Action set, + bool first, + out NumericUpDown widthBox) + { + widthBox = AssaDrawWindow.MakeField(widthProperty, 1, 8192, 90, "W"); + var heightBox = AssaDrawWindow.MakeField(heightProperty, 1, 8192, 90, "H"); + AutomationProperties.SetName(widthBox, title + " - " + Se.Language.General.Width); + AutomationProperties.SetName(heightBox, title + " - " + Se.Language.General.Height); + var fromVideo = AssaDrawWindow.CreateToolButton("fa-solid fa-film", Se.Language.Assa.ResolutionResamplerFromVideo, fromVideoCommand, 28); + + var size = new StackPanel { Orientation = Orientation.Horizontal, Spacing = 6 }; + size.Children.Add(widthBox); + size.Children.Add(new TextBlock { Text = "×", Foreground = AssaDrawWindow.DimTextBrush, VerticalAlignment = VerticalAlignment.Center }); + size.Children.Add(heightBox); + size.Children.Add(fromVideo); + + // Common sizes, one click each; the active one lit + var presets = new StackPanel { Orientation = Orientation.Horizontal, Spacing = 4 }; + foreach (var (w, h) in Presets) + { + var text = $"{w} × {h}"; + var button = new Button + { + Content = new TextBlock { Text = text, FontSize = 12, Margin = new Thickness(8, 0) }, + Height = 26, + VerticalContentAlignment = VerticalAlignment.Center, + }; + button.Classes.Add("tool"); + AutomationProperties.SetName(button, title + " " + text); + button.Click += (_, _) => set(w, h); + + void Update() => button.Classes.Set("active", get() == (w, h)); + Update(); + vm.PropertyChanged += (_, e) => + { + if (e.PropertyName == widthProperty || e.PropertyName == heightProperty) + { + Update(); + } + }; + presets.Children.Add(button); + } - var heightBox = new NumericUpDown - { - Minimum = 1, - Maximum = 8192, - Width = 120, - Increment = 1, - FormatString = "0", - [!NumericUpDown.ValueProperty] = new Binding(heightBinding) { Mode = BindingMode.TwoWay }, - }; - Grid.SetRow(heightBox, 1); - Grid.SetColumn(heightBox, 3); - panelGrid.Children.Add(heightBox); + var content = new StackPanel { Spacing = 8 }; + content.Children.Add(size); + content.Children.Add(presets); + return AssaDrawWindow.MakeEditorSection(title, content, first: first); + } - // From video button - var fromVideoButton = new Button + private static Border MakeOptionsSection(AssaResolutionResamplerViewModel vm) + { + var content = new StackPanel { Spacing = 4 }; + content.Children.Add(AssaDrawWindow.MakeToggleRow(nameof(vm.ChangeMargins), Se.Language.Assa.ResolutionResamplerChangeMargins)); + content.Children.Add(AssaDrawWindow.MakeToggleRow(nameof(vm.ChangeFontSize), Se.Language.Assa.ResolutionResamplerChangeFontSize)); + content.Children.Add(AssaDrawWindow.MakeToggleRow(nameof(vm.ChangePositions), Se.Language.Assa.ResolutionResamplerChangePositions)); + content.Children.Add(AssaDrawWindow.MakeToggleRow(nameof(vm.ChangeDrawing), Se.Language.Assa.ResolutionResamplerChangeDrawing)); + if (vm.IsVideoPrompt) { - Content = Se.Language.Assa.ResolutionResamplerFromVideo, - Command = fromVideoCommand, - Margin = new Thickness(10, 0, 0, 0), - }; - Grid.SetRow(fromVideoButton, 1); - Grid.SetColumn(fromVideoButton, 4); - panelGrid.Children.Add(fromVideoButton); + content.Children.Add(AssaDrawWindow.MakeToggleRow(nameof(vm.AskOnVideoOpen), Se.Language.Assa.ResolutionResamplerAskOnVideoOpen)); + } - return new Border - { - Child = panelGrid, - BorderBrush = UiUtil.GetBorderBrush(), - BorderThickness = new Thickness(1), - CornerRadius = new CornerRadius(4), - Padding = new Thickness(10), - }; + return AssaDrawWindow.MakeEditorSection(Se.Language.General.Options, content); } - private static Border CreateOptionsPanel(AssaResolutionResamplerViewModel vm) + private static Border MakeButtonBar(AssaResolutionResamplerViewModel vm) { - var stackPanel = new StackPanel + // How much everything is scaled, e.g. "×5 · ×3.75" + var scale = new TextBlock { - Orientation = Orientation.Vertical, - Spacing = 8, + FontSize = 12, + Foreground = AssaDrawWindow.DimTextBrush, + VerticalAlignment = VerticalAlignment.Center, }; - var titleLabel = new TextBlock + void UpdateScale() { - Text = Se.Language.General.Options, - FontWeight = FontWeight.Bold, - Margin = new Thickness(0, 0, 0, 5), - }; - stackPanel.Children.Add(titleLabel); + if (vm.SourceWidth <= 0 || vm.SourceHeight <= 0) + { + scale.Text = string.Empty; + return; + } - var checkBoxMargins = new CheckBox - { - Content = Se.Language.Assa.ResolutionResamplerChangeMargins, - [!CheckBox.IsCheckedProperty] = new Binding(nameof(vm.ChangeMargins)) { Mode = BindingMode.TwoWay }, - }; - stackPanel.Children.Add(checkBoxMargins); + var x = (double)vm.TargetWidth / vm.SourceWidth; + var y = (double)vm.TargetHeight / vm.SourceHeight; + scale.Text = Math.Abs(x - y) < 0.005 ? $"×{x:0.##}" : $"×{x:0.##} · ×{y:0.##}"; + } - var checkBoxFontSize = new CheckBox + UpdateScale(); + vm.PropertyChanged += (_, e) => { - Content = Se.Language.Assa.ResolutionResamplerChangeFontSize, - [!CheckBox.IsCheckedProperty] = new Binding(nameof(vm.ChangeFontSize)) { Mode = BindingMode.TwoWay }, + if (e.PropertyName is nameof(vm.SourceWidth) or nameof(vm.SourceHeight) or nameof(vm.TargetWidth) or nameof(vm.TargetHeight)) + { + UpdateScale(); + } }; - stackPanel.Children.Add(checkBoxFontSize); - var checkBoxPositions = new CheckBox - { - Content = Se.Language.Assa.ResolutionResamplerChangePositions, - [!CheckBox.IsCheckedProperty] = new Binding(nameof(vm.ChangePositions)) { Mode = BindingMode.TwoWay }, - }; - stackPanel.Children.Add(checkBoxPositions); + var buttonOk = UiUtil.MakeButtonOk(vm.OkCommand); + buttonOk.Classes.Add("primary"); + var buttonCancel = UiUtil.MakeButtonCancel(vm.CancelCommand); + var buttons = new StackPanel { Orientation = Orientation.Horizontal, Spacing = 10, HorizontalAlignment = HorizontalAlignment.Right }; + buttons.Children.Add(buttonCancel); + buttons.Children.Add(buttonOk); - var checkBoxDrawing = new CheckBox - { - Content = Se.Language.Assa.ResolutionResamplerChangeDrawing, - [!CheckBox.IsCheckedProperty] = new Binding(nameof(vm.ChangeDrawing)) { Mode = BindingMode.TwoWay }, - }; - stackPanel.Children.Add(checkBoxDrawing); + var bar = new DockPanel(); + DockPanel.SetDock(buttons, Dock.Right); + bar.Children.Add(buttons); + bar.Children.Add(scale); return new Border { - Child = stackPanel, - BorderBrush = UiUtil.GetBorderBrush(), - BorderThickness = new Thickness(1), - CornerRadius = new CornerRadius(4), - Padding = new Thickness(10), + Child = bar, + Background = AssaDrawWindow.ButtonBarBrush, + BorderBrush = AssaDrawWindow.LineBrush, + BorderThickness = new Thickness(0, 1, 0, 0), + Padding = new Thickness(16, 10), }; } } diff --git a/src/ui/Features/Assa/AssaSetPosition/AssaSetPositionWindow.cs b/src/ui/Features/Assa/AssaSetPosition/AssaSetPositionWindow.cs index b1a7e89defe..18e8d082676 100644 --- a/src/ui/Features/Assa/AssaSetPosition/AssaSetPositionWindow.cs +++ b/src/ui/Features/Assa/AssaSetPosition/AssaSetPositionWindow.cs @@ -196,27 +196,10 @@ private static Border CreateRotationSection(AssaSetPositionViewModel vm) private static Border CreateGuidesSection(AssaSetPositionViewModel vm) { - static StackPanel Toggle(string property, string text) - { - var toggle = new ToggleSwitch - { - OnContent = null, - OffContent = null, - MinWidth = 0, - VerticalAlignment = VerticalAlignment.Center, - [!ToggleSwitch.IsCheckedProperty] = new Binding(property) { Mode = BindingMode.TwoWay }, - }; - AutomationProperties.SetName(toggle, text); - var row = new StackPanel { Orientation = Orientation.Horizontal, Spacing = 8 }; - row.Children.Add(toggle); - row.Children.Add(new TextBlock { Text = text, FontSize = 12.5, VerticalAlignment = VerticalAlignment.Center }); - return row; - } - var content = new StackPanel { Spacing = 4 }; - content.Children.Add(Toggle(nameof(vm.ShowSafeArea), Se.Language.Assa.SetPositionSafeArea)); - content.Children.Add(Toggle(nameof(vm.ShowCenterLines), Se.Language.Assa.SetPositionCenterLines)); - content.Children.Add(Toggle(nameof(vm.SnapToGuides), Se.Language.Assa.SetPositionSnapToGuides)); + content.Children.Add(AssaDrawWindow.MakeToggleRow(nameof(vm.ShowSafeArea), Se.Language.Assa.SetPositionSafeArea)); + content.Children.Add(AssaDrawWindow.MakeToggleRow(nameof(vm.ShowCenterLines), Se.Language.Assa.SetPositionCenterLines)); + content.Children.Add(AssaDrawWindow.MakeToggleRow(nameof(vm.SnapToGuides), Se.Language.Assa.SetPositionSnapToGuides)); return AssaDrawWindow.MakeEditorSection(Se.Language.Assa.SetPositionGuides, content); } diff --git a/src/ui/Features/Assa/AssaStylePickerWindow.cs b/src/ui/Features/Assa/AssaStylePickerWindow.cs index 5133b82aee7..c0797abcd3d 100644 --- a/src/ui/Features/Assa/AssaStylePickerWindow.cs +++ b/src/ui/Features/Assa/AssaStylePickerWindow.cs @@ -1,15 +1,22 @@ using Avalonia; +using Avalonia.Automation; using Avalonia.Controls; using Avalonia.Controls.Primitives; using Avalonia.Controls.Templates; using Avalonia.Data; using Avalonia.Layout; using Avalonia.Media; +using CommunityToolkit.Mvvm.Input; +using Nikse.SubtitleEdit.Features.Assa.AssaDraw; using Nikse.SubtitleEdit.Logic; using Nikse.SubtitleEdit.Logic.Config; namespace Nikse.SubtitleEdit.Features.Assa; +/// +/// Pick styles to import/export/copy - styled like ASSA draw: the list with check all/none, +/// the accept button in the accent. +/// public class AssaStylePickerWindow : Window { public AssaStylePickerWindow(AssaStylePickerViewModel vm) @@ -25,48 +32,64 @@ public AssaStylePickerWindow(AssaStylePickerViewModel vm) Height = 550; MinWidth = 550; MinHeight = 400; + Background = AssaDrawWindow.WindowBrush; + AssaDrawWindow.AddEditorStyles(Styles); vm.Window = this; DataContext = vm; - var grid = new Grid - { - RowDefinitions = - { - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(2, GridUnitType.Star) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - }, - ColumnDefinitions = - { - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) }, - }, - Margin = UiUtil.MakeWindowMargin(), - ColumnSpacing = 5, - RowSpacing = 5, - Width = double.NaN, - HorizontalAlignment = HorizontalAlignment.Stretch, - }; + // Header: title with check all / none + var actions = new StackPanel { Orientation = Orientation.Horizontal, Spacing = 2 }; + actions.Children.Add(AssaDrawWindow.CreateToolButton("fa-solid fa-square-check", Se.Language.General.SelectAll, new RelayCommand(() => SetAll(vm, true)), 24)); + actions.Children.Add(AssaDrawWindow.CreateToolButton("fa-regular fa-square", Se.Language.General.SelectNone, new RelayCommand(() => SetAll(vm, false)), 24)); + var header = new DockPanel { Margin = new Thickness(14, 10, 10, 8) }; + DockPanel.SetDock(actions, Dock.Right); + header.Children.Add(actions); + header.Children.Add(AssaDrawWindow.MakeSectionHeader(Se.Language.General.Styles)); - var labelFontsAndImages = UiUtil.MakeLabel(Se.Language.General.Styles); + var list = new Grid { RowDefinitions = { new RowDefinition(GridLength.Auto), new RowDefinition(new GridLength(1, GridUnitType.Star)) } }; + list.Add(header, 0, 0); + list.Add(MakeDataGrid(vm, out var stylesGrid), 1, 0); - var buttonImport = UiUtil.MakeButton(string.Empty, vm.OkCommand).WithBindContent(nameof(vm.ButtonAcceptText)); - buttonImport.IsDefault = true; // the dialog's accept button - Enter runs it (#14586) - var buttonCancel = UiUtil.MakeButtonCancel(vm.CancelCommand); - var panelButtons = UiUtil.MakeButtonBar(buttonImport, buttonCancel); - - grid.Add(labelFontsAndImages, 0); - grid.Add(MakeDataGrid(vm, out var stylesGrid), 1); - grid.Add(panelButtons, 2); - - Content = grid; + var mainGrid = new Grid { RowDefinitions = { new RowDefinition(new GridLength(1, GridUnitType.Star)), new RowDefinition(GridLength.Auto) } }; + mainGrid.Add(new Border { Child = list, Background = AssaDrawWindow.PanelBrush }, 0, 0); + mainGrid.Add(MakeButtonBar(vm), 1, 0); + Content = mainGrid; // initial focus on an input, not an action button - a focused button clicks on bare Space UiUtil.FocusOnFirstActivation(this, () => { TableViewExtras.FocusRow(stylesGrid); }); KeyDown += vm.KeyDown; } - private static Border MakeDataGrid(AssaStylePickerViewModel vm, out TableView tableView) + private static void SetAll(AssaStylePickerViewModel vm, bool isSelected) + { + foreach (var style in vm.Styles) + { + style.IsSelected = isSelected; + } + } + + private static Border MakeButtonBar(AssaStylePickerViewModel vm) + { + var buttonAccept = UiUtil.MakeButton(string.Empty, vm.OkCommand).WithBindContent(nameof(vm.ButtonAcceptText)); + buttonAccept.IsDefault = true; // the dialog's accept button - Enter runs it (#14586) + buttonAccept.Classes.Add("primary"); + var buttonCancel = UiUtil.MakeButtonCancel(vm.CancelCommand); + var buttons = new StackPanel { Orientation = Orientation.Horizontal, Spacing = 10, HorizontalAlignment = HorizontalAlignment.Right }; + buttons.Children.Add(buttonCancel); + buttons.Children.Add(buttonAccept); + + return new Border + { + Child = buttons, + Background = AssaDrawWindow.ButtonBarBrush, + BorderBrush = AssaDrawWindow.LineBrush, + BorderThickness = new Thickness(0, 1, 0, 0), + Padding = new Thickness(16, 10), + }; + } + + private static TableView MakeDataGrid(AssaStylePickerViewModel vm, out TableView tableView) { var usagesColumn = new SeTableViewColumn { @@ -155,6 +178,8 @@ private static Border MakeDataGrid(AssaStylePickerViewModel vm, out TableView ta dataGrid.Bind(TableView.SelectedItemProperty, new Binding(nameof(vm.SelectedStyle)) { Source = vm }); - return UiUtil.MakeBorderForControl(dataGrid); + dataGrid.Margin = new Thickness(8, 0, 8, 8); + AutomationProperties.SetName(dataGrid, Se.Language.General.Styles); + return dataGrid; } } diff --git a/src/ui/Features/Assa/FontCollector/FontCollectorWindow.cs b/src/ui/Features/Assa/FontCollector/FontCollectorWindow.cs index 1009194bcd2..47013f69a7a 100644 --- a/src/ui/Features/Assa/FontCollector/FontCollectorWindow.cs +++ b/src/ui/Features/Assa/FontCollector/FontCollectorWindow.cs @@ -1,17 +1,29 @@ using Avalonia; +using Avalonia.Automation; using Avalonia.Controls; +using Avalonia.Controls.Templates; using Avalonia.Data; +using Avalonia.Data.Converters; using Avalonia.Input; using Avalonia.Layout; using Avalonia.Media; +using Avalonia.Media.Immutable; +using Nikse.SubtitleEdit.Features.Assa.AssaDraw; using Nikse.SubtitleEdit.Logic; using Nikse.SubtitleEdit.Logic.Config; -using Optris.Icons.Avalonia; namespace Nikse.SubtitleEdit.Features.Assa.FontCollector; +/// +/// Fonts used by the subtitle, installed fonts and SE's collected fonts - styled like ASSA draw: +/// view switcher and actions on top, the list, the sample-text preview under it, status in the +/// button bar. +/// public class FontCollectorWindow : Window { + private static readonly IBrush FoundBrush = new ImmutableSolidColorBrush(Color.FromRgb(46, 160, 90)); + private static readonly IBrush NotFoundBrush = new ImmutableSolidColorBrush(Color.FromRgb(220, 70, 70)); + private readonly FontCollectorViewModel _vm; public FontCollectorWindow(FontCollectorViewModel vm) @@ -19,103 +31,173 @@ public FontCollectorWindow(FontCollectorViewModel vm) UiUtil.InitializeWindow(this, GetType().Name); Title = Se.Language.Assa.FontCollectorTitle; CanResize = true; - Width = 800; - Height = 500; - MinWidth = 600; - MinHeight = 400; + Width = 960; + Height = 680; + MinWidth = 700; + MinHeight = 480; WindowStartupLocation = WindowStartupLocation.CenterOwner; + Background = AssaDrawWindow.WindowBrush; + AssaDrawWindow.AddEditorStyles(Styles); _vm = vm; vm.Window = this; DataContext = vm; - var tabControl = new TabControl + // One list per view; only the selected one shows + var lists = new Panel(); + lists.Children.Add(OnTab(vm, 0, MakeCurrentSubtitleList(vm))); + lists.Children.Add(OnTab(vm, 1, MakeInstalledFontsList(vm))); + lists.Children.Add(OnTab(vm, 2, MakeCollectedFontsList(vm))); + + var content = new Grid { - HorizontalAlignment = HorizontalAlignment.Stretch, - VerticalAlignment = VerticalAlignment.Stretch, - Items = + RowDefinitions = { - new TabItem - { - Header = MakeTabHeader(IconNames.ClosedCaption, Se.Language.Assa.FontCollectorCurrentSubtitle), - Content = MakeCurrentSubtitleView(vm), - }, - new TabItem - { - Header = MakeTabHeader(IconNames.CaseSensitiveAlt, Se.Language.Tools.PickFontNameInstalledFonts), - Content = MakeInstalledFontsView(vm), - }, - new TabItem - { - Header = MakeTabHeader(IconNames.Folder, Se.Language.Tools.PickFontNameCollectedFonts), - Content = MakeCollectedFontsView(vm), - }, + new RowDefinition(new GridLength(1, GridUnitType.Star)), + new RowDefinition(new GridLength(190)), }, }; - tabControl.Bind(TabControl.SelectedIndexProperty, new Binding(nameof(vm.SelectedTabIndex)) { Source = vm, Mode = BindingMode.TwoWay }); + content.Add(new Border + { + Child = lists, + Background = AssaDrawWindow.PanelBrush, + Padding = new Thickness(8), + }, 0, 0); + content.Add(MakePreview(vm), 1, 0); - var grid = new Grid + var mainGrid = new Grid { RowDefinitions = { - new RowDefinition { Height = new GridLength(1, GridUnitType.Star) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, + new RowDefinition(GridLength.Auto), + new RowDefinition(new GridLength(1, GridUnitType.Star)), + new RowDefinition(GridLength.Auto), }, - ColumnDefinitions = - { - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) }, - }, - Margin = UiUtil.MakeWindowMargin(), - RowSpacing = 5, - Width = double.NaN, - HorizontalAlignment = HorizontalAlignment.Stretch, }; - - grid.Add(tabControl, 0); - grid.Add(UiUtil.MakeButtonBar(UiUtil.MakeButtonDone(vm.CloseCommand)), 1); - - Content = grid; + mainGrid.Add(MakeToolbar(vm), 0, 0); + mainGrid.Add(content, 1, 0); + mainGrid.Add(MakeButtonBar(vm), 2, 0); + Content = mainGrid; } - /// The fonts the current subtitle uses, with found/not-found status and copy actions. - private static Grid MakeCurrentSubtitleView(FontCollectorViewModel vm) + private static T OnTab(FontCollectorViewModel vm, int index, T control) where T : Control { - var dataGrid = TableViewExtras.MakeTableView().WithAccessibleName(Se.Language.Assa.FontCollectorCurrentSubtitle); // the tab header (#12087) - dataGrid.DataContext = vm; - dataGrid.ItemsSource = vm.FontItems; + control.Bind(IsVisibleProperty, new Binding(nameof(vm.SelectedTabIndex)) + { + Source = vm, + Converter = new FuncValueConverter(selected => selected == index), + }); + return control; + } - var fontNameColumn = new SeTableViewColumn + /// + /// Icon button with its label beside it, for the toolbar. + /// + private static Button MakeAction(string icon, string text, System.Windows.Input.ICommand? command) + { + var content = new StackPanel { Orientation = Orientation.Horizontal, Spacing = 7, Margin = new Thickness(10, 0) }; + content.Children.Add(new Optris.Icons.Avalonia.Icon { Value = icon, FontSize = 13, VerticalAlignment = VerticalAlignment.Center }); + content.Children.Add(new TextBlock { Text = text, FontSize = 12.5, VerticalAlignment = VerticalAlignment.Center }); + var button = new Button { - Header = Se.Language.General.FontName, - CellTheme = UiUtil.TableViewCellTheme, - HeaderTheme = UiUtil.TableViewColumnHeaderTheme, - Binding = new Binding(nameof(FontCollectorItem.FontName)), - Width = new GridLength(180), + Content = content, + Height = 30, + Command = command, + VerticalContentAlignment = VerticalAlignment.Center, }; - var usedInColumn = new SeTableViewColumn + button.Classes.Add("tool"); + + // Icon + label in a panel: a screen reader needs the name spelled out (#12087) + AutomationProperties.SetName(button, text); + return button; + } + + private static Border MakeToolbar(FontCollectorViewModel vm) + { + // View switcher: the active view lit + var views = new StackPanel { Orientation = Orientation.Horizontal, Spacing = 2 }; + foreach (var (index, icon, text) in new[] + { + (0, "fa-solid fa-closed-captioning", Se.Language.Assa.FontCollectorCurrentSubtitle), + (1, "fa-solid fa-font", Se.Language.Tools.PickFontNameInstalledFonts), + (2, "fa-solid fa-folder", Se.Language.Tools.PickFontNameCollectedFonts), + }) { - Header = Se.Language.Assa.FontCollectorUsedIn, - CellTheme = UiUtil.TableViewCellTheme, - HeaderTheme = UiUtil.TableViewColumnHeaderTheme, - Binding = new Binding(nameof(FontCollectorItem.UsedIn)), - Width = new GridLength(1, GridUnitType.Star), + var button = MakeAction(icon, text, null); + button.Click += (_, _) => vm.SelectedTabIndex = index; + + void Update() => button.Classes.Set("active", vm.SelectedTabIndex == index); + Update(); + vm.PropertyChanged += (_, e) => + { + if (e.PropertyName == nameof(vm.SelectedTabIndex)) + { + Update(); + } + }; + views.Children.Add(button); + } + + // Actions for the view on show + var subtitleActions = OnTab(vm, 0, new StackPanel { Orientation = Orientation.Horizontal, Spacing = 2 }); + subtitleActions.Children.Add(MakeAction("fa-solid fa-paperclip", Se.Language.Assa.FontCollectorEmbedFontsDotDotDot, vm.EmbedFontsInSubtitleCommand)); + subtitleActions.Children.Add(MakeAction("fa-solid fa-copy", Se.Language.Assa.FontCollectorCopyFontsToSeFontsFolder, vm.CopyFontsToSeFontsFolderCommand)); + + var installedActions = OnTab(vm, 1, new StackPanel { Orientation = Orientation.Horizontal, Spacing = 2 }); + installedActions.Children.Add(MakeAction("fa-solid fa-copy", Se.Language.Assa.FontCollectorCopyFontsToSeFontsFolder, vm.CopyInstalledFontToSeFontsFolderCommand)); + + var collectedActions = OnTab(vm, 2, new StackPanel { Orientation = Orientation.Horizontal, Spacing = 2 }); + collectedActions.Children.Add(MakeAction("fa-solid fa-file-import", Se.Language.Assa.FontCollectorImportFontDotDotDot, vm.ImportFontCommand)); + collectedActions.Children.Add(MakeAction("fa-solid fa-folder-open", Se.Language.Assa.FontCollectorOpenFontsFolder, vm.OpenSeFontsFolderCommand)); + + var actions = new Panel(); + actions.Children.Add(subtitleActions); + actions.Children.Add(installedActions); + actions.Children.Add(collectedActions); + + var panel = new StackPanel { Orientation = Orientation.Horizontal, Spacing = 2, VerticalAlignment = VerticalAlignment.Center }; + panel.Children.Add(views); + panel.Children.Add(AssaDrawWindow.MakeToolbarSeparator()); + panel.Children.Add(actions); + + return new Border + { + Child = panel, + Background = AssaDrawWindow.PanelBrush, + BorderBrush = AssaDrawWindow.LineBrush, + BorderThickness = new Thickness(0, 0, 0, 1), + Padding = new Thickness(10, 6), }; + } + + private static SeTableViewColumn Column(string header, string property, GridLength width) => new() + { + Header = header, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, + Binding = new Binding(property), + Width = width, + }; + + /// The fonts the current subtitle uses, with a found/embedded/not-found badge. + private static TableView MakeCurrentSubtitleList(FontCollectorViewModel vm) + { + var dataGrid = TableViewExtras.MakeTableView().WithAccessibleName(Se.Language.Assa.FontCollectorCurrentSubtitle); // the view name (#12087) + dataGrid.DataContext = vm; + dataGrid.ItemsSource = vm.FontItems; + + var fontNameColumn = Column(Se.Language.General.FontName, nameof(FontCollectorItem.FontName), new GridLength(200)); + var usedInColumn = Column(Se.Language.Assa.FontCollectorUsedIn, nameof(FontCollectorItem.UsedIn), new GridLength(1, GridUnitType.Star)); var statusColumn = new SeTableViewColumn { Header = Se.Language.General.Status, CellTheme = UiUtil.TableViewCellTheme, HeaderTheme = UiUtil.TableViewColumnHeaderTheme, - Binding = new Binding(nameof(FontCollectorItem.Status)), + CellTemplate = new FuncDataTemplate((_, _) => MakeStatusBadge()), + NameBinding = new Binding(nameof(FontCollectorItem.Status)), Width = new GridLength(120), }; - var fontFilesColumn = new SeTableViewColumn - { - Header = Se.Language.Assa.FontCollectorFontFiles, - CellTheme = UiUtil.TableViewCellTheme, - HeaderTheme = UiUtil.TableViewColumnHeaderTheme, - Binding = new Binding(nameof(FontCollectorItem.FileDisplay)), - Width = new GridLength(1, GridUnitType.Star), - }; + var fontFilesColumn = Column(Se.Language.Assa.FontCollectorFontFiles, nameof(FontCollectorItem.FileDisplay), new GridLength(1, GridUnitType.Star)); dataGrid.Columns.Add(fontNameColumn); dataGrid.Columns.Add(usedInColumn); dataGrid.Columns.Add(statusColumn); @@ -132,117 +214,66 @@ private static Grid MakeCurrentSubtitleView(FontCollectorViewModel vm) .AddSortable(statusColumn, x => x.Status) .AddSortable(fontFilesColumn, x => x.FileDisplay); - var statusText = new TextBlock + return dataGrid; + } + + /// + /// Status as a small colored pill: found green, embedded accent, not found red. + /// + private static Border MakeStatusBadge() + { + IBrush BrushFor(string? status) { - VerticalAlignment = VerticalAlignment.Center, - Opacity = 0.8, - [!TextBlock.TextProperty] = new Binding(nameof(vm.StatusText)), - }; + if (status == Se.Language.Assa.FontCollectorFound) + { + return FoundBrush; + } + + if (status == Se.Language.Assa.FontCollectorNotFound) + { + return NotFoundBrush; + } - var buttonEmbedFonts = UiUtil.MakeButton(Se.Language.Assa.FontCollectorEmbedFontsDotDotDot, vm.EmbedFontsInSubtitleCommand) - .WithIconLeft(IconNames.Paperclip); - var buttonCopyToSeFolder = UiUtil.MakeButton(Se.Language.Assa.FontCollectorCopyFontsToSeFontsFolder, vm.CopyFontsToSeFontsFolderCommand) - .WithIconLeft(IconNames.FormatFont); - var checkBoxTrimFonts = UiUtil.MakeCheckBox(Se.Language.Assa.FontCollectorTrimFonts, vm, nameof(vm.TrimFontsToUsedCharacters)); - var buttonBar = UiUtil.MakeButtonBar(buttonEmbedFonts, buttonCopyToSeFolder, checkBoxTrimFonts); + return AssaDrawWindow.AccentBrush; + } - var grid = new Grid + return new Border { - RowDefinitions = - { - new RowDefinition { Height = new GridLength(2, GridUnitType.Star) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Star) }, - }, - ColumnDefinitions = + CornerRadius = new CornerRadius(9), + Padding = new Thickness(8, 1), + HorizontalAlignment = HorizontalAlignment.Left, + VerticalAlignment = VerticalAlignment.Center, + [!Border.BackgroundProperty] = new Binding(nameof(FontCollectorItem.Status)) { Converter = new FuncValueConverter(BrushFor) }, + [!Border.IsVisibleProperty] = new Binding(nameof(FontCollectorItem.Status)) { Converter = StringConverters.IsNotNullOrEmpty }, + Child = new TextBlock { - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) }, - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Auto) }, + FontSize = 11.5, + FontWeight = FontWeight.SemiBold, + Foreground = Brushes.White, + [!TextBlock.TextProperty] = new Binding(nameof(FontCollectorItem.Status)), }, - RowSpacing = 5, - ColumnSpacing = 5, - Width = double.NaN, - HorizontalAlignment = HorizontalAlignment.Stretch, }; - - grid.Add(dataGrid, 0, 0, 1, 2); - grid.Add(statusText, 1, 0); - grid.Add(buttonBar, 1, 1); - grid.Add(MakeFontPreviewView(vm), 2, 0, 1, 2); - - return grid; } - /// All fonts installed on this machine, with a sample-text preview. - private static Grid MakeInstalledFontsView(FontCollectorViewModel vm) + /// All fonts installed on this machine. + private static TableView MakeInstalledFontsList(FontCollectorViewModel vm) { var dataGrid = TableViewExtras.MakeTableView(multiSelect: false).WithAccessibleName(Se.Language.Tools.PickFontNameInstalledFonts); dataGrid.DataContext = vm; dataGrid.ItemsSource = vm.InstalledFontNames; - - var fontNameColumn = new SeTableViewColumn - { - Header = Se.Language.General.FontName, - CellTheme = UiUtil.TableViewCellTheme, - HeaderTheme = UiUtil.TableViewColumnHeaderTheme, - Binding = new Binding("."), - Width = new GridLength(1, GridUnitType.Star), - }; - dataGrid.Columns.Add(fontNameColumn); + dataGrid.Columns.Add(Column(Se.Language.General.FontName, ".", new GridLength(1, GridUnitType.Star))); dataGrid.Bind(TableView.SelectedItemProperty, new Binding(nameof(vm.SelectedInstalledFontName))); - - var buttonCopyToSeFolder = UiUtil.MakeButton(Se.Language.Assa.FontCollectorCopyFontsToSeFontsFolder, vm.CopyInstalledFontToSeFontsFolderCommand) - .WithIconLeft(IconNames.FormatFont); - - var grid = new Grid - { - RowDefinitions = - { - new RowDefinition { Height = new GridLength(2, GridUnitType.Star) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Star) }, - }, - ColumnDefinitions = - { - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) }, - }, - RowSpacing = 5, - Width = double.NaN, - HorizontalAlignment = HorizontalAlignment.Stretch, - }; - - grid.Add(UiUtil.MakeBorderForControlNoPadding(dataGrid), 0); - grid.Add(UiUtil.MakeButtonBar(buttonCopyToSeFolder), 1); - grid.Add(MakeFontPreviewView(vm), 2); - - return grid; + return dataGrid; } - /// The fonts collected in SE's own Fonts folder, with a sample-text preview. - private static Grid MakeCollectedFontsView(FontCollectorViewModel vm) + /// The fonts collected in SE's own Fonts folder. + private static TableView MakeCollectedFontsList(FontCollectorViewModel vm) { var dataGrid = TableViewExtras.MakeTableView(multiSelect: false).WithAccessibleName(Se.Language.Tools.PickFontNameCollectedFonts); dataGrid.DataContext = vm; dataGrid.ItemsSource = vm.CollectedFonts; - - var fontNameColumn = new SeTableViewColumn - { - Header = Se.Language.General.FontName, - CellTheme = UiUtil.TableViewCellTheme, - HeaderTheme = UiUtil.TableViewColumnHeaderTheme, - Binding = new Binding(nameof(CollectedFont.Name)), - Width = new GridLength(220), - }; - var fileColumn = new SeTableViewColumn - { - Header = Se.Language.Assa.FontCollectorFontFiles, - CellTheme = UiUtil.TableViewCellTheme, - HeaderTheme = UiUtil.TableViewColumnHeaderTheme, - Binding = new Binding(nameof(CollectedFont.FilePath)), - Width = new GridLength(1, GridUnitType.Star), - }; - dataGrid.Columns.Add(fontNameColumn); - dataGrid.Columns.Add(fileColumn); + dataGrid.Columns.Add(Column(Se.Language.General.FontName, nameof(CollectedFont.Name), new GridLength(240))); + dataGrid.Columns.Add(Column(Se.Language.Assa.FontCollectorFontFiles, nameof(CollectedFont.FilePath), new GridLength(1, GridUnitType.Star))); dataGrid.Bind(TableView.SelectedItemProperty, new Binding(nameof(vm.SelectedCollectedFont))); dataGrid.KeyDown += vm.CollectedFontsGridKeyDown; @@ -251,73 +282,82 @@ private static Grid MakeCollectedFontsView(FontCollectorViewModel vm) dataGrid.ContextFlyout = flyout; UiUtil.AttachMacContextFlyoutHandler(dataGrid); - var menuItemDelete = new Avalonia.Controls.MenuItem + var menuItemDelete = new MenuItem { Header = Se.Language.General.Delete, DataContext = vm, Command = vm.DeleteCollectedFontCommand, }; - menuItemDelete.Bind(Avalonia.Controls.MenuItem.IsVisibleProperty, new Binding(nameof(vm.IsDeleteCollectedFontVisible)) { Source = vm }); + menuItemDelete.Bind(MenuItem.IsVisibleProperty, new Binding(nameof(vm.IsDeleteCollectedFontVisible)) { Source = vm }); flyout.Items.Add(menuItemDelete); - var buttonImportFont = UiUtil.MakeButton(Se.Language.Assa.FontCollectorImportFontDotDotDot, vm.ImportFontCommand) - .WithIconLeft(IconNames.Import); - var buttonOpenFolder = UiUtil.MakeButton(Se.Language.Assa.FontCollectorOpenFontsFolder, vm.OpenSeFontsFolderCommand) - .WithIconLeft(IconNames.FolderOpen); - - var grid = new Grid - { - RowDefinitions = - { - new RowDefinition { Height = new GridLength(2, GridUnitType.Star) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Star) }, - }, - ColumnDefinitions = - { - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) }, - }, - RowSpacing = 5, - Width = double.NaN, - HorizontalAlignment = HorizontalAlignment.Stretch, - }; - - grid.Add(UiUtil.MakeBorderForControlNoPadding(dataGrid), 0); - grid.Add(UiUtil.MakeButtonBar(buttonImportFont, buttonOpenFolder), 1); - grid.Add(MakeFontPreviewView(vm), 2); - - return grid; + return dataGrid; } - private static StackPanel MakeTabHeader(string iconName, string text) + private static Border MakePreview(FontCollectorViewModel vm) { - var icon = new ContentControl { VerticalAlignment = VerticalAlignment.Center }; - Attached.SetIcon(icon, iconName); + var image = new Image + { + [!Image.SourceProperty] = new Binding(nameof(vm.FontPreview)), + DataContext = vm, + HorizontalAlignment = HorizontalAlignment.Left, + VerticalAlignment = VerticalAlignment.Center, + Stretch = Stretch.Uniform, + StretchDirection = StretchDirection.DownOnly, + }; + var stage = new Border + { + Child = image, + Background = AssaDrawWindow.FieldBrush, + BorderBrush = AssaDrawWindow.FieldBorderBrush, + BorderThickness = new Thickness(1), + CornerRadius = new CornerRadius(8), + Padding = new Thickness(12), + }; - return new StackPanel + var panel = new Grid { RowSpacing = 8, RowDefinitions = { new RowDefinition(GridLength.Auto), new RowDefinition(new GridLength(1, GridUnitType.Star)) } }; + panel.Add(AssaDrawWindow.MakeSectionHeader(Se.Language.General.Preview), 0, 0); + panel.Add(stage, 1, 0); + return new Border { - Orientation = Orientation.Horizontal, - Spacing = 6, - Children = - { - icon, - new TextBlock { Text = text, VerticalAlignment = VerticalAlignment.Center }, - }, + Child = panel, + BorderBrush = AssaDrawWindow.LineBrush, + BorderThickness = new Thickness(0, 1, 0, 0), + Padding = new Thickness(14, 10, 14, 12), }; } - private static Border MakeFontPreviewView(FontCollectorViewModel vm) + private static Border MakeButtonBar(FontCollectorViewModel vm) { - var image = new Image + var status = new TextBlock { - [!Image.SourceProperty] = new Binding(nameof(vm.FontPreview)), - DataContext = vm, - HorizontalAlignment = HorizontalAlignment.Left, - VerticalAlignment = VerticalAlignment.Top, - Stretch = Stretch.Uniform, + FontSize = 12, + Foreground = AssaDrawWindow.DimTextBrush, + VerticalAlignment = VerticalAlignment.Center, + [!TextBlock.TextProperty] = new Binding(nameof(vm.StatusText)), }; - return UiUtil.MakeBorderForControl(image); + var buttonDone = UiUtil.MakeButtonDone(vm.CloseCommand); + buttonDone.Classes.Add("primary"); + + // Embed option, beside Done while the subtitle's fonts show + var trim = OnTab(vm, 0, AssaDrawWindow.MakeToggleRow(nameof(vm.TrimFontsToUsedCharacters), Se.Language.Assa.FontCollectorTrimFonts)); + trim.Margin = new Thickness(0, 0, 16, 0); + + var bar = new DockPanel(); + DockPanel.SetDock(buttonDone, Dock.Right); + DockPanel.SetDock(trim, Dock.Right); + bar.Children.Add(buttonDone); + bar.Children.Add(trim); + bar.Children.Add(status); + return new Border + { + Child = bar, + Background = AssaDrawWindow.ButtonBarBrush, + BorderBrush = AssaDrawWindow.LineBrush, + BorderThickness = new Thickness(0, 1, 0, 0), + Padding = new Thickness(16, 10), + }; } protected override void OnKeyDown(KeyEventArgs e) From 432b2859c7f03ef061e32f0f2d681835ce7b8630 Mon Sep 17 00:00:00 2001 From: niksedk Date: Thu, 15 Oct 2026 13:02:24 +0200 Subject: [PATCH 179/306] Remove unused AssaSingleStyleWindow The window and its view model were registered but never opened. Co-Authored-By: Claude Opus 5.5 --- src/ui/DependencyInjectionExtensions.cs | 1 - .../Features/Assa/AssaSingleStyleViewModel.cs | 80 ----- src/ui/Features/Assa/AssaSingleStyleWindow.cs | 306 ------------------ .../Assa/AssaStyleEditorDataLossTests.cs | 2 +- 4 files changed, 1 insertion(+), 388 deletions(-) delete mode 100644 src/ui/Features/Assa/AssaSingleStyleViewModel.cs delete mode 100644 src/ui/Features/Assa/AssaSingleStyleWindow.cs diff --git a/src/ui/DependencyInjectionExtensions.cs b/src/ui/DependencyInjectionExtensions.cs index 34afe8ba35f..897fcc810fd 100644 --- a/src/ui/DependencyInjectionExtensions.cs +++ b/src/ui/DependencyInjectionExtensions.cs @@ -363,7 +363,6 @@ public static void AddSubtitleEditServices(this IServiceCollection collection) collection.AddTransient(); collection.AddTransient(); collection.AddTransient(); - collection.AddTransient(); collection.AddTransient(); collection.AddTransient(); collection.AddTransient(); diff --git a/src/ui/Features/Assa/AssaSingleStyleViewModel.cs b/src/ui/Features/Assa/AssaSingleStyleViewModel.cs deleted file mode 100644 index f567049ebb2..00000000000 --- a/src/ui/Features/Assa/AssaSingleStyleViewModel.cs +++ /dev/null @@ -1,80 +0,0 @@ -using Avalonia.Controls; -using Avalonia.Input; -using Avalonia.Media.Imaging; -using CommunityToolkit.Mvvm.ComponentModel; -using CommunityToolkit.Mvvm.Input; -using Nikse.SubtitleEdit.Core.Common; -using Nikse.SubtitleEdit.Logic; -using System.Collections.ObjectModel; - -namespace Nikse.SubtitleEdit.Features.Assa; - -public partial class AssaSingleStyleViewModel : ObservableObject -{ - [ObservableProperty] private StyleDisplay? _currentStyle; - [ObservableProperty] private ObservableCollection _fonts; - [ObservableProperty] private ObservableCollection _borderTypes; - [ObservableProperty] private BorderStyleItem _selectedBorderType; - [ObservableProperty] private Bitmap? _imagePreview; - - public Window? Window { get; set; } - - public bool OkPressed { get; private set; } - - public AssaSingleStyleViewModel() - { - Fonts = new ObservableCollection(FontHelper.GetLibAssaFonts()); - BorderTypes = new ObservableCollection(BorderStyleItem.List()); - SelectedBorderType = BorderTypes[0]; - } - - public void Initialize(SsaStyle style) - { - CurrentStyle = new StyleDisplay(style); - } - - /// - /// The font combo box binds SelectedItem to CurrentStyle.FontName; a font missing from - /// the item list would make Avalonia clear the selection and null out the style's font. - /// Make sure the font is listed before the style becomes current (#13101). - /// - partial void OnCurrentStyleChanging(StyleDisplay? value) - { - var fontName = value?.FontName; - if (!string.IsNullOrEmpty(fontName) && !Fonts.Contains(fontName)) - { - Fonts.Insert(0, fontName); - } - } - - [RelayCommand] - private void Ok() - { - OkPressed = true; - Window?.Close(); - } - - [RelayCommand] - private void Cancel() - { - Window?.Close(); - } - - internal void OnKeyDown(KeyEventArgs e) - { - if (e.Key == Key.Escape) - { - e.Handled = true; - Window?.Close(); - } - else if (UiUtil.IsHelp(e)) - { - e.Handled = true; - UiUtil.ShowHelp("features/assa-styles"); - } - } - - internal void BorderTypeChanged(object? sender, SelectionChangedEventArgs e) - { - } -} \ No newline at end of file diff --git a/src/ui/Features/Assa/AssaSingleStyleWindow.cs b/src/ui/Features/Assa/AssaSingleStyleWindow.cs deleted file mode 100644 index ad500a8994c..00000000000 --- a/src/ui/Features/Assa/AssaSingleStyleWindow.cs +++ /dev/null @@ -1,306 +0,0 @@ -using Avalonia.Controls; -using Avalonia.Data; -using Avalonia.Layout; -using Avalonia.Media; -using Nikse.SubtitleEdit.Logic; -using Nikse.SubtitleEdit.Logic.Config; - -namespace Nikse.SubtitleEdit.Features.Assa; - -public class AssaSingleStyleWindow : Window -{ - public AssaSingleStyleWindow(AssaSingleStyleViewModel vm) - { - UiUtil.InitializeWindow(this, GetType().Name); - Title = Se.Language.Tools.BatchConvert.BatchConvertSettings; - SizeToContent = SizeToContent.WidthAndHeight; - CanResize = false; - vm.Window = this; - DataContext = vm; - - var buttonOk = UiUtil.MakeButtonOk(vm.OkCommand); - var buttonCancel = UiUtil.MakeButtonCancel(vm.CancelCommand); - var panelButtons = UiUtil.MakeButtonBar(buttonOk, buttonCancel); - - var grid = new Grid - { - RowDefinitions = - { - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - }, - ColumnDefinitions = - { - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Auto) }, - }, - Margin = UiUtil.MakeWindowMargin(), - ColumnSpacing = 10, - RowSpacing = 10, - Width = double.NaN, - HorizontalAlignment = HorizontalAlignment.Stretch, - }; - - grid.Add(MakeSelectedStyleView(vm), 0); - grid.Add(MakePreviewView(vm), 1); - grid.Add(panelButtons, 2); - - Content = grid; - - Loaded += delegate { buttonOk.Focus(); }; // hack to make OnKeyDown work - KeyDown += (_, e) => vm.OnKeyDown(e); - } - - private static Border MakeSelectedStyleView(AssaSingleStyleViewModel vm) - { - var grid = new Grid - { - RowDefinitions = - { - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - }, - ColumnDefinitions = - { - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) }, - }, - Width = double.NaN, - HorizontalAlignment = HorizontalAlignment.Stretch, - ColumnSpacing = 5, - RowSpacing = 5, - }; - - var label = UiUtil.MakeLabel().WithBold(); //.WithBindText(vm, nameof(vm.CurrentTitle)); - - var labelName = UiUtil.MakeLabel(Se.Language.General.Name); - var textBoxName = UiUtil.MakeTextBox(200, vm, nameof(vm.CurrentStyle) + "." + nameof(StyleDisplay.Name)); - var panelName = UiUtil.MakeHorizontalPanel(labelName, textBoxName).WithMarginBottom(10); - - var labelFontName = UiUtil.MakeLabel(Se.Language.General.FontName); - var comboBoxFontName = UiUtil.MakeComboBox(vm.Fonts, vm, nameof(vm.CurrentStyle) + "." + nameof(StyleDisplay.FontName)).WithMinWidth(150); - var labelFontSize = UiUtil.MakeLabel(Se.Language.General.FontSize); - var numericUpDownFontSize = UiUtil.MakeNumericUpDownOneDecimal(1, 1000, 130, vm, nameof(vm.CurrentStyle) + "." + nameof(StyleDisplay.FontSize)); - numericUpDownFontSize.Increment = 1; - var panelFont = UiUtil.MakeHorizontalPanel(labelFontName, comboBoxFontName, labelFontSize, numericUpDownFontSize); - - var checkBoxBold = UiUtil.MakeCheckBox(Se.Language.General.Bold, vm, nameof(vm.CurrentStyle) + "." + nameof(StyleDisplay.Bold)); - var checkBoxItalic = UiUtil.MakeCheckBox(Se.Language.General.Italic, vm, nameof(vm.CurrentStyle) + "." + nameof(StyleDisplay.Italic)); - var checkBoxUnderline = UiUtil.MakeCheckBox(Se.Language.General.Underline, vm, nameof(vm.CurrentStyle) + "." + nameof(StyleDisplay.Underline)); - var checkBoxStrikeout = UiUtil.MakeCheckBox(Se.Language.General.Strikeout, vm, nameof(vm.CurrentStyle) + "." + nameof(StyleDisplay.Strikeout)); - var panelFontStyle = UiUtil.MakeHorizontalPanel(checkBoxBold, checkBoxItalic, checkBoxUnderline, checkBoxStrikeout).WithMarginBottom(10); - - var labelScaleX = UiUtil.MakeLabel(Se.Language.Assa.ScaleX).WithMinWidth(60); - var numericUpDownScaleX = UiUtil.MakeNumericUpDownOneDecimal(1, 1000, 130, vm, nameof(vm.CurrentStyle) + "." + nameof(StyleDisplay.ScaleX)); - numericUpDownScaleX.Increment = 1; - var labelScaleY = UiUtil.MakeLabel(Se.Language.Assa.ScaleY).WithMinWidth(60); - var numericUpDownScaleY = UiUtil.MakeNumericUpDownOneDecimal(1, 1000, 130, vm, nameof(vm.CurrentStyle) + "." + nameof(StyleDisplay.ScaleY)); - numericUpDownScaleY.Increment = 1; - var panelTransform1 = UiUtil.MakeHorizontalPanel(labelScaleX, numericUpDownScaleX, labelScaleY, numericUpDownScaleY); - - var labelSpacing = UiUtil.MakeLabel(Se.Language.Assa.Spacing).WithMinWidth(60); - var numericUpDownSpacing = UiUtil.MakeNumericUpDownOneDecimal(-100, 100, 130, vm, nameof(vm.CurrentStyle) + "." + nameof(StyleDisplay.Spacing)); - numericUpDownSpacing.Increment = 1; - var labelAngle = UiUtil.MakeLabel(Se.Language.Assa.Angle).WithMinWidth(60); - var numericUpDownAngle = UiUtil.MakeNumericUpDownOneDecimal(-360, 360, 130, vm, nameof(vm.CurrentStyle) + "." + nameof(StyleDisplay.Angle)); - numericUpDownAngle.Increment = 1; - var panelTransform2 = UiUtil.MakeHorizontalPanel(labelSpacing, numericUpDownSpacing, labelAngle, numericUpDownAngle).WithMarginBottom(10); - - var labelColorPrimary = UiUtil.MakeLabel(Se.Language.Assa.Primary); - var colorPickerPrimary = UiUtil.MakeColorPickerButton(vm, nameof(vm.CurrentStyle) + "." + nameof(StyleDisplay.ColorPrimary)); - var labelColorOutline = UiUtil.MakeLabel(Se.Language.General.Outline); - var colorPickerOutline = UiUtil.MakeColorPickerButton(vm, nameof(vm.CurrentStyle) + "." + nameof(StyleDisplay.ColorOutline)); - var labelColorShadow = UiUtil.MakeLabel(Se.Language.General.Shadow); - var colorPickerShadow = UiUtil.MakeColorPickerButton(vm, nameof(vm.CurrentStyle) + "." + nameof(StyleDisplay.ColorShadow)); - var labelColorSecondary = UiUtil.MakeLabel(Se.Language.Assa.Secondary); - var colorPickerSecondary = UiUtil.MakeColorPickerButton(vm, nameof(vm.CurrentStyle) + "." + nameof(StyleDisplay.ColorSecondary)); - var panelColors = UiUtil.MakeHorizontalPanel( - labelColorPrimary, - colorPickerPrimary, - labelColorOutline, - colorPickerOutline, - labelColorShadow, - colorPickerShadow, - labelColorSecondary, - colorPickerSecondary - ).WithMarginBottom(10); - - var alignmentView = MakeAlignmentView(vm); - var marginView = MakeMarginView(vm); - var borderView = MakeBorderView(vm); - var panelMore = UiUtil.MakeHorizontalPanel(alignmentView, marginView, borderView); - - grid.Add(label, 0); - grid.Add(panelName, 1); - grid.Add(panelFont, 2); - grid.Add(panelFontStyle, 3); - grid.Add(panelTransform1, 4); - grid.Add(panelTransform2, 5); - grid.Add(panelColors, 6); - grid.Add(panelMore, 7); - - return UiUtil.MakeBorderForControl(grid).WithMarginBottom(5); - } - - private static Border MakeAlignmentView(AssaSingleStyleViewModel vm) - { - var grid = new Grid - { - RowDefinitions = - { - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - }, - ColumnDefinitions = - { - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Auto) }, - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Auto) }, - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Auto) }, - }, - Width = double.NaN, - HorizontalAlignment = HorizontalAlignment.Stretch, - }; - - var label = UiUtil.MakeLabel(Se.Language.General.Alignment); - - grid.Add(label, 0, 0, 1, 3); - grid.Add(UiUtil.MakeRadioButton(string.Empty, vm, nameof(vm.CurrentStyle) + "." + nameof(StyleDisplay.AlignmentAn7), "align"), 1, 0); - grid.Add(UiUtil.MakeRadioButton(string.Empty, vm, nameof(vm.CurrentStyle) + "." + nameof(StyleDisplay.AlignmentAn8), "align"), 1, 1); - grid.Add(UiUtil.MakeRadioButton(string.Empty, vm, nameof(vm.CurrentStyle) + "." + nameof(StyleDisplay.AlignmentAn9), "align"), 1, 2); - grid.Add(UiUtil.MakeRadioButton(string.Empty, vm, nameof(vm.CurrentStyle) + "." + nameof(StyleDisplay.AlignmentAn4), "align"), 2, 0); - grid.Add(UiUtil.MakeRadioButton(string.Empty, vm, nameof(vm.CurrentStyle) + "." + nameof(StyleDisplay.AlignmentAn5), "align"), 2, 1); - grid.Add(UiUtil.MakeRadioButton(string.Empty, vm, nameof(vm.CurrentStyle) + "." + nameof(StyleDisplay.AlignmentAn6), "align"), 2, 2); - grid.Add(UiUtil.MakeRadioButton(string.Empty, vm, nameof(vm.CurrentStyle) + "." + nameof(StyleDisplay.AlignmentAn1), "align"), 3, 0); - grid.Add(UiUtil.MakeRadioButton(string.Empty, vm, nameof(vm.CurrentStyle) + "." + nameof(StyleDisplay.AlignmentAn2), "align"), 3, 1); - grid.Add(UiUtil.MakeRadioButton(string.Empty, vm, nameof(vm.CurrentStyle) + "." + nameof(StyleDisplay.AlignmentAn3), "align"), 3, 2); - - return UiUtil.MakeBorderForControl(grid); - } - - private static Border MakeMarginView(AssaSingleStyleViewModel vm) - { - var grid = new Grid - { - RowDefinitions = - { - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - }, - ColumnDefinitions = - { - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) }, - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) }, - }, - Width = double.NaN, - HorizontalAlignment = HorizontalAlignment.Stretch, - RowSpacing = 5, - }; - - var label = UiUtil.MakeLabel(Se.Language.General.Margin); - grid.Add(label, 0); - - var labelMarginLeft = UiUtil.MakeLabel(Se.Language.General.Left); - var numericUpDownMarginLeft = UiUtil.MakeNumericUpDownInt(0, 1000, 10, 130, vm, nameof(vm.CurrentStyle) + "." + nameof(StyleDisplay.MarginLeft)); - grid.Add(labelMarginLeft, 1, 0); - grid.Add(numericUpDownMarginLeft, 1, 1); - - var labelMarginRight = UiUtil.MakeLabel(Se.Language.General.Right); - var numericUpDownMarginRight = UiUtil.MakeNumericUpDownInt(0, 1000, 10, 130, vm, nameof(vm.CurrentStyle) + "." + nameof(StyleDisplay.MarginRight)); - grid.Add(labelMarginRight, 2, 0); - grid.Add(numericUpDownMarginRight, 2, 1); - - var labelMarginVertical = UiUtil.MakeLabel(Se.Language.General.Vertical); - var numericUpDownMarginVertical = UiUtil.MakeNumericUpDownInt(0, 1000, 10, 130, vm, nameof(vm.CurrentStyle) + "." + nameof(StyleDisplay.MarginVertical)); - grid.Add(labelMarginVertical, 3, 0); - grid.Add(numericUpDownMarginVertical, 3, 1); - - return UiUtil.MakeBorderForControl(grid); - } - - private static Border MakeBorderView(AssaSingleStyleViewModel vm) - { - var grid = new Grid - { - RowDefinitions = - { - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - }, - ColumnDefinitions = - { - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Auto) }, - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Auto) }, - }, - Width = double.NaN, - HorizontalAlignment = HorizontalAlignment.Stretch, - RowSpacing = 5, - }; - - var label = UiUtil.MakeLabel(Se.Language.General.BorderStyle); - grid.Add(label, 1, 0); - - var comboBoxBorderType = UiUtil.MakeComboBox(vm.BorderTypes, vm, nameof(vm.SelectedBorderType)); - comboBoxBorderType.SelectionChanged += vm.BorderTypeChanged; - grid.Add(comboBoxBorderType, 2, 0, 1, 2); - - var labelOutlineWidth = UiUtil.MakeLabel(Se.Language.General.OutlineWidth); - var numericUpDownOutlineWidth = UiUtil.MakeNumericUpDownOneDecimal(0, 100, 130, vm, nameof(vm.CurrentStyle) + "." + nameof(StyleDisplay.OutlineWidth)); - numericUpDownOutlineWidth.Increment = 0.5m; - grid.Add(labelOutlineWidth, 3, 0); - grid.Add(numericUpDownOutlineWidth, 3, 1); - - var labelShadowWidth = UiUtil.MakeLabel(Se.Language.General.ShadowWidth); - var numericUpDownShadowWidth = UiUtil.MakeNumericUpDownOneDecimal(0, 100, 130, vm, nameof(vm.CurrentStyle) + "." + nameof(StyleDisplay.ShadowWidth)); - numericUpDownShadowWidth.Increment = 0.5m; - grid.Add(labelShadowWidth, 4, 0); - grid.Add(numericUpDownShadowWidth, 4, 1); - - return UiUtil.MakeBorderForControl(grid); - } - - private static Border MakePreviewView(AssaSingleStyleViewModel vm) - { - var grid = new Grid - { - RowDefinitions = - { - new RowDefinition { Height = new GridLength(2, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(2, GridUnitType.Star) }, - }, - ColumnDefinitions = - { - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) }, - }, - Width = double.NaN, - HorizontalAlignment = HorizontalAlignment.Stretch, - }; - - var label = UiUtil.MakeLabel(Se.Language.General.Preview).WithBold(); - - var image = new Image - { - [!Image.SourceProperty] = new Binding(nameof(vm.ImagePreview)), - DataContext = vm, - HorizontalAlignment = HorizontalAlignment.Left, - VerticalAlignment = VerticalAlignment.Top, - Stretch = Stretch.None, // Prevents stretching of the image - }; - - grid.Add(label, 0); - grid.Add(image, 1); - - return UiUtil.MakeBorderForControl(grid); - } -} diff --git a/tests/UI/Features/Assa/AssaStyleEditorDataLossTests.cs b/tests/UI/Features/Assa/AssaStyleEditorDataLossTests.cs index 795ad14170a..7b2b53a505b 100644 --- a/tests/UI/Features/Assa/AssaStyleEditorDataLossTests.cs +++ b/tests/UI/Features/Assa/AssaStyleEditorDataLossTests.cs @@ -43,7 +43,7 @@ private static AssaStylesViewModel MakeVm() private static StackPanel MakeAlignmentRadios(AssaStylesViewModel vm) { - // Same construction as AssaStylesWindow/SsaStylesWindow/AssaSingleStyleWindow. + // Same construction as AssaStylesWindow/SsaStylesWindow. var panel = new StackPanel(); panel.Children.Add(UiUtil.MakeRadioButton(string.Empty, vm, nameof(vm.CurrentStyle) + "." + nameof(StyleDisplay.AlignmentAn7), "align")); panel.Children.Add(UiUtil.MakeRadioButton(string.Empty, vm, nameof(vm.CurrentStyle) + "." + nameof(StyleDisplay.AlignmentAn8), "align")); From c8fdedd39dafccc7032e62e3db73f1ac79f90f6b Mon Sep 17 00:00:00 2001 From: niksedk Date: Thu, 15 Oct 2026 14:02:03 +0200 Subject: [PATCH 180/306] SSA styles, properties and attachments in the ASSA draw look Mirrors the redesigned ASSA windows, keeping only what an SSA v4 script supports (no underline/strikeout/transform, colors without alpha, SSA options: collisions, play depth, timer). All follow the app theme. Also ports the ASSA styles fix where clicking the preselected first row left the editor empty. Co-Authored-By: Claude Opus 5.5 --- src/ui/Features/Ssa/SsaAttachmentsWindow.cs | 241 +++-- src/ui/Features/Ssa/SsaPropertiesWindow.cs | 321 +++--- src/ui/Features/Ssa/SsaStylesViewModel.cs | 13 + src/ui/Features/Ssa/SsaStylesWindow.cs | 1005 ++++++++++--------- 4 files changed, 888 insertions(+), 692 deletions(-) diff --git a/src/ui/Features/Ssa/SsaAttachmentsWindow.cs b/src/ui/Features/Ssa/SsaAttachmentsWindow.cs index c28fc1032d7..a6144ed7d01 100644 --- a/src/ui/Features/Ssa/SsaAttachmentsWindow.cs +++ b/src/ui/Features/Ssa/SsaAttachmentsWindow.cs @@ -1,16 +1,25 @@ +using Avalonia; +using Avalonia.Automation; using Avalonia.Controls; using Avalonia.Data; using Avalonia.Input; using Avalonia.Interactivity; using Avalonia.Layout; using Avalonia.Media; -using System.Collections; +using System.Linq; +using Avalonia.Styling; +using Nikse.SubtitleEdit.Core.Common; using Nikse.SubtitleEdit.Features.Assa; +using Nikse.SubtitleEdit.Features.Assa.AssaDraw; using Nikse.SubtitleEdit.Logic; using Nikse.SubtitleEdit.Logic.Config; namespace Nikse.SubtitleEdit.Features.Ssa; +/// +/// Fonts and graphics embedded in the SSA script, styled like ASSA draw: actions on top, the list on +/// the left, the preview on the right. +/// public class SsaAttachmentsWindow : Window { public SsaAttachmentsWindow(SsaAttachmentsViewModel vm) @@ -23,58 +32,39 @@ public SsaAttachmentsWindow(SsaAttachmentsViewModel vm) }); CanResize = true; Width = 1200; - Height = 850; - MinWidth = 1100; - MinHeight = 600; + Height = 820; + MinWidth = 900; + MinHeight = 550; + Background = AssaDrawWindow.WindowBrush; + AssaDrawWindow.AddEditorStyles(Styles); vm.Window = this; DataContext = vm; - var grid = new Grid + var content = new Grid { - RowDefinitions = - { - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(2, GridUnitType.Star) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - }, ColumnDefinitions = { - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) }, - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) }, + new ColumnDefinition(new GridLength(480)), + new ColumnDefinition(new GridLength(1, GridUnitType.Star)), }, - Margin = UiUtil.MakeWindowMargin(), - ColumnSpacing = 5, - RowSpacing = 5, - Width = double.NaN, - HorizontalAlignment = HorizontalAlignment.Stretch, }; + content.Add(MakeLeftView(vm, out var attachmentsGrid), 0, 0); + content.Add(MakeRightView(vm), 0, 1); - var labelFontsAndImages = UiUtil.MakeLabel(Se.Language.Assa.FontsAndGraphics); - var labelPreview = UiUtil.MakeLabel().WithBindText(vm, nameof(vm.PreviewTitle)); - var buttonCopyToClipboard = UiUtil.MakeButton(Se.Language.General.CopyToClipboard, vm.CopyFontNameToClipboardCommand) - .WithBindIsVisible(nameof(vm.IsCopyFontnameToClipboardVisible)); - var buttonCopyToSeFontsFolder = UiUtil.MakeButton(Se.Language.Assa.FontCollectorCopyFontsToSeFontsFolder, vm.CopyToSeFontsFolderCommand) - .WithIconLeft(IconNames.FormatFont) - .WithBindIsVisible(nameof(vm.IsCopyFontnameToClipboardVisible)); - var previewLine = UiUtil.MakeHorizontalPanel(labelPreview, buttonCopyToClipboard, buttonCopyToSeFontsFolder); - - var buttonAttach = UiUtil.MakeButton(Se.Language.General.AttachDotDotDot, vm.FileAttachCommand); - var buttonImport = UiUtil.MakeButton(Se.Language.General.ImportDotDotDot, vm.FileImportCommand); - var buttonExport = UiUtil.MakeButton(Se.Language.General.ExportDotDotDot, vm.FileExportCommand); - var buttonTrimFonts = UiUtil.MakeButton(Se.Language.Assa.TrimFontsDotDotDot, vm.TrimFontsToUsedCharactersCommand) - .WithIconLeft(IconNames.ContentCut); - var buttonOk = UiUtil.MakeButtonOk(vm.OkCommand); - var buttonCancel = UiUtil.MakeButtonCancel(vm.CancelCommand); - var panelButtons = UiUtil.MakeButtonBar(buttonAttach, buttonImport, buttonExport, buttonTrimFonts, buttonOk, buttonCancel); - - grid.Add(labelFontsAndImages, 0); - grid.Add(previewLine, 0, 1); - grid.Add(MakeLeftView(vm, out var attachmentsGrid), 1); - grid.Add(MakeRightView(vm), 1, 1); - grid.Add(panelButtons, 3, 0, 1, 2); - - Content = grid; + var mainGrid = new Grid + { + RowDefinitions = + { + new RowDefinition(GridLength.Auto), + new RowDefinition(new GridLength(1, GridUnitType.Star)), + new RowDefinition(GridLength.Auto), + }, + }; + mainGrid.Add(MakeToolbar(vm), 0, 0); + mainGrid.Add(content, 1, 0); + mainGrid.Add(MakeButtonBar(vm), 2, 0); + Content = mainGrid; // initial focus on an input, not an action button - a focused button clicks on bare Space UiUtil.FocusOnFirstActivation(this, () => { TableViewExtras.FocusRow(attachmentsGrid); }); @@ -84,6 +74,93 @@ public SsaAttachmentsWindow(SsaAttachmentsViewModel vm) Loaded += delegate { UiUtil.RestoreWindowPosition(this); }; } + /// + /// Icon button with its label beside it, for the actions bar. + /// + private static Button MakeAction(string icon, string text, System.Windows.Input.ICommand command) + { + var content = new StackPanel { Orientation = Orientation.Horizontal, Spacing = 7, Margin = new Thickness(10, 0) }; + content.Children.Add(new Optris.Icons.Avalonia.Icon { Value = icon, FontSize = 13, VerticalAlignment = VerticalAlignment.Center }); + content.Children.Add(new TextBlock { Text = text, FontSize = 12.5, VerticalAlignment = VerticalAlignment.Center }); + var button = new Button + { + Content = content, + Height = 30, + Command = command, + VerticalContentAlignment = VerticalAlignment.Center, + }; + button.Classes.Add("tool"); + + // Icon + label in a panel: a screen reader needs the name spelled out (#12087) + AutomationProperties.SetName(button, text); + return button; + } + + private static Border MakeToolbar(SsaAttachmentsViewModel vm) + { + var panel = new StackPanel { Orientation = Orientation.Horizontal, Spacing = 2, VerticalAlignment = VerticalAlignment.Center }; + panel.Children.Add(MakeAction("fa-solid fa-paperclip", Se.Language.General.AttachDotDotDot, vm.FileAttachCommand)); + panel.Children.Add(MakeAction("fa-solid fa-file-import", Se.Language.General.ImportDotDotDot, vm.FileImportCommand)); + panel.Children.Add(MakeAction("fa-solid fa-file-export", Se.Language.General.ExportDotDotDot, vm.FileExportCommand)); + panel.Children.Add(AssaDrawWindow.MakeToolbarSeparator()); + panel.Children.Add(MakeAction("fa-solid fa-scissors", Se.Language.Assa.TrimFontsDotDotDot, vm.TrimFontsToUsedCharactersCommand)); + + return new Border + { + Child = panel, + Background = AssaDrawWindow.PanelBrush, + BorderBrush = AssaDrawWindow.LineBrush, + BorderThickness = new Thickness(0, 0, 0, 1), + Padding = new Thickness(10, 6), + }; + } + + private static Border MakeButtonBar(SsaAttachmentsViewModel vm) + { + // "2 fonts · 1 graphics · 120 KB" + var summary = new TextBlock + { + FontSize = 12, + Foreground = AssaDrawWindow.DimTextBrush, + VerticalAlignment = VerticalAlignment.Center, + }; + void UpdateSummary() + { + var fonts = vm.Attachments.Count(a => a.Category == Se.Language.General.Fonts); + var bytes = vm.Attachments.Sum(a => (long)a.Bytes.Length); + summary.Text = string.Format(Se.Language.Assa.AttachmentsSummary, fonts, vm.Attachments.Count - fonts, Utilities.FormatBytesToDisplayFileSize(bytes)); + } + + UpdateSummary(); + vm.Attachments.CollectionChanged += (_, _) => UpdateSummary(); + + var buttonOk = UiUtil.MakeButtonOk(vm.OkCommand); + buttonOk.Classes.Add("primary"); + var buttonCancel = UiUtil.MakeButtonCancel(vm.CancelCommand); + var buttons = new StackPanel { Orientation = Orientation.Horizontal, Spacing = 10 }; + buttons.Children.Add(buttonCancel); + buttons.Children.Add(buttonOk); + + var grid = new Grid + { + ColumnDefinitions = + { + new ColumnDefinition(new GridLength(1, GridUnitType.Star)), + new ColumnDefinition(GridLength.Auto), + }, + }; + grid.Add(summary, 0, 0); + grid.Add(buttons, 0, 1); + return new Border + { + Child = grid, + Background = AssaDrawWindow.ButtonBarBrush, + BorderBrush = AssaDrawWindow.LineBrush, + BorderThickness = new Thickness(0, 1, 0, 0), + Padding = new Thickness(16, 10), + }; + } + private static Border MakeLeftView(SsaAttachmentsViewModel vm, out TableView tableView) { var grid = new Grid @@ -105,6 +182,7 @@ private static Border MakeLeftView(SsaAttachmentsViewModel vm, out TableView tab // order is not presentation-only. var dataGrid = TableViewExtras.MakeTableView(multiSelect: false); tableView = dataGrid; + AutomationProperties.SetName(dataGrid, Se.Language.Assa.FontsAndGraphics); dataGrid.DataContext = vm; dataGrid.ItemsSource = vm.Attachments; @@ -167,36 +245,77 @@ private static Border MakeLeftView(SsaAttachmentsViewModel vm, out TableView tab grid.Add(dataGrid, 0); - return UiUtil.MakeBorderForControlNoPadding(grid); + // Header: title with move/delete actions, then the list + var actions = new StackPanel { Orientation = Orientation.Horizontal, Spacing = 2 }; + actions.Children.Add(AssaDrawWindow.CreateToolButton("fa-solid fa-arrow-up", Se.Language.General.MoveUp, vm.MoveUpCommand, 24)); + actions.Children.Add(AssaDrawWindow.CreateToolButton("fa-solid fa-arrow-down", Se.Language.General.MoveDown, vm.MoveDownCommand, 24)); + actions.Children.Add(AssaDrawWindow.CreateToolButton("fa-solid fa-trash", Se.Language.General.Delete, vm.AttachmentRemoveCommand, 24)); + var header = new DockPanel { Margin = new Thickness(14, 10, 10, 8) }; + DockPanel.SetDock(actions, Dock.Right); + header.Children.Add(actions); + header.Children.Add(AssaDrawWindow.MakeSectionHeader(Se.Language.Assa.FontsAndGraphics)); + + var panel = new Grid { RowDefinitions = { new RowDefinition(GridLength.Auto), new RowDefinition(new GridLength(1, GridUnitType.Star)) } }; + grid.Margin = new Thickness(8, 0); + panel.Add(header, 0, 0); + panel.Add(grid, 1, 0); + return new Border + { + Child = panel, + Background = AssaDrawWindow.PanelBrush, + BorderBrush = AssaDrawWindow.LineBrush, + BorderThickness = new Thickness(0, 0, 1, 0), + }; } - private static Border MakeRightView(SsaAttachmentsViewModel vm) + private static Control MakeRightView(SsaAttachmentsViewModel vm) { - var grid = new Grid + var title = new TextBlock { - RowDefinitions = - { - new RowDefinition { Height = new GridLength(1, GridUnitType.Star) }, - }, - ColumnDefinitions = - { - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) }, - }, - Width = double.NaN, - HorizontalAlignment = HorizontalAlignment.Stretch, + FontSize = 12.5, + Foreground = AssaDrawWindow.DimTextBrush, + VerticalAlignment = VerticalAlignment.Center, + TextTrimming = TextTrimming.CharacterEllipsis, + [!TextBlock.TextProperty] = new Binding(nameof(vm.PreviewTitle)), + }; + + // Font actions, only for a font + var fontActions = new StackPanel + { + Orientation = Orientation.Horizontal, + Spacing = 2, + [!StackPanel.IsVisibleProperty] = new Binding(nameof(vm.IsCopyFontnameToClipboardVisible)), }; + fontActions.Children.Add(AssaDrawWindow.CreateToolButton("fa-solid fa-copy", Se.Language.General.CopyToClipboard, vm.CopyFontNameToClipboardCommand, 28)); + fontActions.Children.Add(AssaDrawWindow.CreateToolButton("fa-solid fa-font", Se.Language.Assa.FontCollectorCopyFontsToSeFontsFolder, vm.CopyToSeFontsFolderCommand, 28)); + + var header = new DockPanel { Margin = new Thickness(0, 0, 0, 10) }; + DockPanel.SetDock(fontActions, Dock.Right); + header.Children.Add(fontActions); + header.Children.Add(title); var image = new Image { [!Image.SourceProperty] = new Binding(nameof(vm.PreviewImage)), DataContext = vm, - HorizontalAlignment = HorizontalAlignment.Left, - VerticalAlignment = VerticalAlignment.Top, + HorizontalAlignment = HorizontalAlignment.Center, + VerticalAlignment = VerticalAlignment.Center, Stretch = Stretch.Uniform, + StretchDirection = StretchDirection.DownOnly, + }; + var stage = new Border + { + Child = image, + Background = AssaDrawWindow.FieldBrush, + BorderBrush = AssaDrawWindow.FieldBorderBrush, + BorderThickness = new Thickness(1), + CornerRadius = new CornerRadius(8), + Padding = new Thickness(16), }; - grid.Add(image, 0); - - return UiUtil.MakeBorderForControl(grid); + var panel = new Grid { Margin = new Thickness(16, 12), RowDefinitions = { new RowDefinition(GridLength.Auto), new RowDefinition(new GridLength(1, GridUnitType.Star)) } }; + panel.Add(header, 0, 0); + panel.Add(stage, 1, 0); + return panel; } } diff --git a/src/ui/Features/Ssa/SsaPropertiesWindow.cs b/src/ui/Features/Ssa/SsaPropertiesWindow.cs index 19a9f028d91..a5533821ef1 100644 --- a/src/ui/Features/Ssa/SsaPropertiesWindow.cs +++ b/src/ui/Features/Ssa/SsaPropertiesWindow.cs @@ -1,11 +1,18 @@ +using Avalonia; +using Avalonia.Automation; using Avalonia.Controls; using Avalonia.Data; using Avalonia.Layout; +using Avalonia.Media; +using Nikse.SubtitleEdit.Features.Assa.AssaDraw; using Nikse.SubtitleEdit.Logic; using Nikse.SubtitleEdit.Logic.Config; namespace Nikse.SubtitleEdit.Features.Ssa; +/// +/// The script's [Script Info] (SSA): credits, resolution and playback options - styled like ASSA draw. +/// public class SsaPropertiesWindow : Window { public SsaPropertiesWindow(SsaPropertiesViewModel vm) @@ -16,206 +23,192 @@ public SsaPropertiesWindow(SsaPropertiesViewModel vm) Source = vm, Mode = BindingMode.TwoWay, }); - SizeToContent = SizeToContent.WidthAndHeight; + Width = 640; + SizeToContent = SizeToContent.Height; CanResize = false; + Background = AssaDrawWindow.WindowBrush; + AssaDrawWindow.AddEditorStyles(Styles); vm.Window = this; DataContext = vm; - var grid = new Grid - { - RowDefinitions = - { - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - }, - ColumnDefinitions = - { - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) }, - }, - Margin = UiUtil.MakeWindowMargin(), - ColumnSpacing = 5, - RowSpacing = 5, - Width = double.NaN, - HorizontalAlignment = HorizontalAlignment.Stretch, - }; - - var buttonOk = UiUtil.MakeButtonOk(vm.OkCommand); - var buttonCancel = UiUtil.MakeButtonCancel(vm.CancelCommand); - var panelButtons = UiUtil.MakeButtonBar(buttonOk, buttonCancel); + var sections = new StackPanel { Background = AssaDrawWindow.PanelBrush }; + sections.Children.Add(MakeScriptSection(vm, out var textBoxTitle)); + sections.Children.Add(MakeResolutionSection(vm)); + sections.Children.Add(MakeOptionsSection(vm)); - grid.Add(MakeScriptView(vm, out var textBoxTitle), 0); - grid.Add(MakeResolutionView(vm), 1); - grid.Add(MakeOptionsView(vm), 2); - grid.Add(panelButtons, 3); - - Content = grid; + var root = new DockPanel(); + var buttonBar = MakeButtonBar(vm); + DockPanel.SetDock(buttonBar, Dock.Bottom); + root.Children.Add(buttonBar); + root.Children.Add(sections); + Content = root; // initial focus on an input, not an action button - a focused button clicks on bare Space UiUtil.FocusOnFirstActivation(this, textBoxTitle); KeyDown += vm.KeyDown; } - private static Border MakeScriptView(SsaPropertiesViewModel vm, out TextBox firstInput) + /// + /// A dark text field with its label above it. + /// + private static TextBox MakeTextBox(string label, string property) { - var grid = new Grid + var textBox = new TextBox { - RowDefinitions = - { - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - }, - ColumnDefinitions = - { - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Auto) }, - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) }, - }, - Width = double.NaN, - HorizontalAlignment = HorizontalAlignment.Stretch, - RowSpacing = 5, + FontSize = 12.5, + MinHeight = 28, + Background = AssaDrawWindow.FieldBrush, + BorderBrush = AssaDrawWindow.FieldBorderBrush, + CornerRadius = new CornerRadius(6), + [!TextBox.TextProperty] = new Binding(property) { Mode = BindingMode.TwoWay }, }; + AutomationProperties.SetName(textBox, label); + return textBox; + } - var label = UiUtil.MakeLabel(Se.Language.General.Script).WithBold().WithMarginBottom(10); - - var labelTitle = UiUtil.MakeLabel(Se.Language.General.Title); - var textBoxTitle = UiUtil.MakeTextBox(200, vm, nameof(vm.ScriptTitle)); - firstInput = textBoxTitle; - - var labelOriginalScript = UiUtil.MakeLabel(Se.Language.Assa.OriginalScript); - var textBoxOriginalScript = UiUtil.MakeTextBox(200, vm, nameof(vm.OriginalScript)); - - var labelTranslation = UiUtil.MakeLabel(Se.Language.General.Translation); - var textBoxTranslation = UiUtil.MakeTextBox(200, vm, nameof(vm.Translation)); - - var labelEditing = UiUtil.MakeLabel(Se.Language.General.Editing); - var textBoxEditing = UiUtil.MakeTextBox(200, vm, nameof(vm.Editing)); - - var labelTiming = UiUtil.MakeLabel(Se.Language.General.Timing); - var textBoxTiming = UiUtil.MakeTextBox(200, vm, nameof(vm.Timing)); - - var labelSyncPoint = UiUtil.MakeLabel(Se.Language.General.Sync); - var textBoxSyncPoint = UiUtil.MakeTextBox(200, vm, nameof(vm.SyncPoint)); - - var labelUpdatedBy = UiUtil.MakeLabel(Se.Language.General.UpdatedBy); - var textBoxUpdatedBy = UiUtil.MakeTextBox(200, vm, nameof(vm.UpdatedBy)); - - var labelUpdateDetails = UiUtil.MakeLabel(Se.Language.General.UpdateDetails); - var textBoxUpdateDetails = UiUtil.MakeTextBox(200, vm, nameof(vm.UpdateDetails)); + /// + /// A text field with its label above it. + /// + private static StackPanel MakeTextField(string label, string property, out TextBox textBox) + { + textBox = MakeTextBox(label, property); + var panel = new StackPanel { Spacing = 4 }; + panel.Children.Add(new TextBlock { Text = label, FontSize = 12, Foreground = AssaDrawWindow.DimTextBrush }); + panel.Children.Add(textBox); + return panel; + } - grid.Add(label, 0, 0, 1, 2); - grid.Add(labelTitle, 1, 0); - grid.Add(textBoxTitle, 1, 1); - grid.Add(labelOriginalScript, 2, 0); - grid.Add(textBoxOriginalScript, 2, 1); - grid.Add(labelTranslation, 3, 0); - grid.Add(textBoxTranslation, 3, 1); - grid.Add(labelEditing, 4, 0); - grid.Add(textBoxEditing, 4, 1); - grid.Add(labelTiming, 5, 0); - grid.Add(textBoxTiming, 5, 1); - grid.Add(labelSyncPoint, 6, 0); - grid.Add(textBoxSyncPoint, 6, 1); - grid.Add(labelUpdatedBy, 7, 0); - grid.Add(textBoxUpdatedBy, 7, 1); - grid.Add(labelUpdateDetails, 8, 0); - grid.Add(textBoxUpdateDetails, 8, 1); + private static Border MakeScriptSection(SsaPropertiesViewModel vm, out TextBox firstInput) + { + var title = MakeTextField(Se.Language.General.Title, nameof(vm.ScriptTitle), out firstInput); - return UiUtil.MakeBorderForControl(grid); + // The credits two by two + var grid = new Grid + { + ColumnSpacing = 12, + RowSpacing = 10, + ColumnDefinitions = { new ColumnDefinition(new GridLength(1, GridUnitType.Star)), new ColumnDefinition(new GridLength(1, GridUnitType.Star)) }, + RowDefinitions = { new RowDefinition(GridLength.Auto), new RowDefinition(GridLength.Auto), new RowDefinition(GridLength.Auto) }, + }; + grid.Add(MakeTextField(Se.Language.Assa.OriginalScript, nameof(vm.OriginalScript), out _), 0, 0); + grid.Add(MakeTextField(Se.Language.General.Translation, nameof(vm.Translation), out _), 0, 1); + grid.Add(MakeTextField(Se.Language.General.Editing, nameof(vm.Editing), out _), 1, 0); + grid.Add(MakeTextField(Se.Language.General.Timing, nameof(vm.Timing), out _), 1, 1); + grid.Add(MakeTextField(Se.Language.General.Sync, nameof(vm.SyncPoint), out _), 2, 0); + grid.Add(MakeTextField(Se.Language.General.UpdatedBy, nameof(vm.UpdatedBy), out _), 2, 1); + + var content = new StackPanel { Spacing = 10 }; + content.Children.Add(title); + content.Children.Add(grid); + content.Children.Add(MakeTextField(Se.Language.General.UpdateDetails, nameof(vm.UpdateDetails), out _)); + return AssaDrawWindow.MakeEditorSection(Se.Language.General.Script, content, first: true); } - private static Border MakeResolutionView(SsaPropertiesViewModel vm) + private static Border MakeResolutionSection(SsaPropertiesViewModel vm) { - var grid = new Grid + var width = AssaDrawWindow.MakeField(nameof(vm.VideoWidth), 0, 10000, 90, "W"); + var height = AssaDrawWindow.MakeField(nameof(vm.VideoHeight), 0, 10000, 90, "H"); + AutomationProperties.SetName(width, Se.Language.General.Width); + AutomationProperties.SetName(height, Se.Language.General.Height); + + var browse = AssaDrawWindow.CreateToolButton("fa-solid fa-folder-open", Se.Language.General.VideoResolution, vm.BrowseResolutionCommand, 28); + var fromVideo = AssaDrawWindow.CreateToolButton("fa-solid fa-film", Se.Language.General.PickResolutionFromCurrentVideo, vm.GetResolutionFromCurrentVideoCommand, 28); + fromVideo.Bind(Button.IsVisibleProperty, new Binding(nameof(vm.ShowGetResolutionFromCurrentVideo)) { Source = vm }); + + var size = new StackPanel { Orientation = Orientation.Horizontal, Spacing = 6 }; + size.Children.Add(width); + size.Children.Add(new TextBlock { Text = "×", Foreground = AssaDrawWindow.DimTextBrush, VerticalAlignment = VerticalAlignment.Center }); + size.Children.Add(height); + size.Children.Add(browse); + size.Children.Add(fromVideo); + + // Common sizes, one click each; the active one lit + var presets = new StackPanel { Orientation = Orientation.Horizontal, Spacing = 4 }; + foreach (var (text, w, h) in new[] { ("640 × 360", 640, 360), ("1280 × 720", 1280, 720), ("1920 × 1080", 1920, 1080), ("3840 × 2160", 3840, 2160) }) { - RowDefinitions = + var button = new Button { - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - }, - ColumnDefinitions = + Content = new TextBlock { Text = text, FontSize = 12, Margin = new Thickness(8, 0) }, + Height = 26, + VerticalContentAlignment = VerticalAlignment.Center, + }; + button.Classes.Add("tool"); + AutomationProperties.SetName(button, text); + button.Click += (_, _) => { - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Auto) }, - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Auto) }, - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Auto) }, - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Auto) }, - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Auto) }, - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Auto) }, - }, - Width = double.NaN, - HorizontalAlignment = HorizontalAlignment.Stretch, - ColumnSpacing = 5, - RowSpacing = 5, - }; - - var label = UiUtil.MakeLabel(Se.Language.General.Resolution).WithBold().WithMarginBottom(10); + vm.VideoWidth = w; + vm.VideoHeight = h; + }; - var labelVideoResolution = UiUtil.MakeLabel(Se.Language.General.VideoResolution); - var numericUpDownWidth = UiUtil.MakeNumericUpDownInt(0, 10000, 1920, 120, vm, nameof(vm.VideoWidth)); - var labelX = UiUtil.MakeLabel("x"); - var numericUpDownHeight = UiUtil.MakeNumericUpDownInt(0, 10000, 1080, 120, vm, nameof(vm.VideoHeight)); - var buttonBrowseResolution = UiUtil.MakeButtonBrowse(vm.BrowseResolutionCommand, null, Se.Language.General.VideoResolution); - var buttonFromCurrentVideo = UiUtil.MakeButton(Se.Language.General.PickResolutionFromCurrentVideo, vm.GetResolutionFromCurrentVideoCommand); - - grid.Add(label, 0, 0, 1, 6); - grid.Add(labelVideoResolution, 1, 0); - grid.Add(numericUpDownWidth, 1, 1); - grid.Add(labelX, 1, 2); - grid.Add(numericUpDownHeight, 1, 3); - grid.Add(buttonBrowseResolution, 1, 4); - grid.Add(buttonFromCurrentVideo, 1, 5); - - return UiUtil.MakeBorderForControl(grid); + void Update() => button.Classes.Set("active", vm.VideoWidth == w && vm.VideoHeight == h); + Update(); + vm.PropertyChanged += (_, e) => + { + if (e.PropertyName is nameof(vm.VideoWidth) or nameof(vm.VideoHeight)) + { + Update(); + } + }; + presets.Children.Add(button); + } + + var content = new StackPanel { Spacing = 8 }; + content.Children.Add(size); + content.Children.Add(presets); + return AssaDrawWindow.MakeEditorSection(Se.Language.General.VideoResolution, content); } - private static Border MakeOptionsView(SsaPropertiesViewModel vm) + private static Border MakeOptionsSection(SsaPropertiesViewModel vm) { + var collisions = UiUtil.MakeComboBox(vm.Collisions, vm, nameof(vm.SelectedCollision)); + collisions.HorizontalAlignment = HorizontalAlignment.Stretch; + AutomationProperties.SetName(collisions, Se.Language.Assa.Collisions); + var playDepth = AssaDrawWindow.MakeField(nameof(vm.PlayDepth), 0, 32); + playDepth.HorizontalAlignment = HorizontalAlignment.Stretch; + AutomationProperties.SetName(playDepth, Se.Language.Assa.PlayDepth); + var timer = MakeTextBox(Se.Language.Assa.Timer, nameof(vm.Timer)); + var grid = new Grid { - RowDefinitions = - { - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - }, + ColumnSpacing = 12, ColumnDefinitions = { - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Auto) }, - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) }, + new ColumnDefinition(new GridLength(2, GridUnitType.Star)), + new ColumnDefinition(new GridLength(1, GridUnitType.Star)), + new ColumnDefinition(new GridLength(1, GridUnitType.Star)), }, - Width = double.NaN, - HorizontalAlignment = HorizontalAlignment.Stretch, - RowSpacing = 5, }; + grid.Add(Labeled(Se.Language.Assa.Collisions, collisions), 0, 0); + grid.Add(Labeled(Se.Language.Assa.PlayDepth, playDepth), 0, 1); + grid.Add(Labeled(Se.Language.Assa.Timer, timer), 0, 2); + return AssaDrawWindow.MakeEditorSection(Se.Language.General.Options, grid); + } - var label = UiUtil.MakeLabel(Se.Language.General.Options).WithBold().WithMarginBottom(10); - - var labelCollisions = UiUtil.MakeLabel(Se.Language.Assa.Collisions); - var comboBoxCollisions = UiUtil.MakeComboBox(vm.Collisions, vm, nameof(vm.SelectedCollision)); - - var labelPlayDepth = UiUtil.MakeLabel(Se.Language.Assa.PlayDepth); - var numericUpDownPlayDepth = UiUtil.MakeNumericUpDownInt(0, 32, 0, 120, vm, nameof(vm.PlayDepth)); - - var labelTimer = UiUtil.MakeLabel(Se.Language.Assa.Timer); - var textBoxTimer = UiUtil.MakeTextBox(120, vm, nameof(vm.Timer)); + private static StackPanel Labeled(string label, Control control) + { + var panel = new StackPanel { Spacing = 4 }; + panel.Children.Add(new TextBlock { Text = label, FontSize = 12, Foreground = AssaDrawWindow.DimTextBrush, TextTrimming = TextTrimming.CharacterEllipsis }); + panel.Children.Add(control); + return panel; + } - grid.Add(label, 0, 0, 1, 2); - grid.Add(labelCollisions, 1, 0); - grid.Add(comboBoxCollisions, 1, 1); - grid.Add(labelPlayDepth, 2, 0); - grid.Add(numericUpDownPlayDepth, 2, 1); - grid.Add(labelTimer, 3, 0); - grid.Add(textBoxTimer, 3, 1); + private static Border MakeButtonBar(SsaPropertiesViewModel vm) + { + var buttonOk = UiUtil.MakeButtonOk(vm.OkCommand); + buttonOk.Classes.Add("primary"); + var buttonCancel = UiUtil.MakeButtonCancel(vm.CancelCommand); + var buttons = new StackPanel { Orientation = Orientation.Horizontal, Spacing = 10, HorizontalAlignment = HorizontalAlignment.Right }; + buttons.Children.Add(buttonCancel); + buttons.Children.Add(buttonOk); - return UiUtil.MakeBorderForControl(grid); + return new Border + { + Child = buttons, + Background = AssaDrawWindow.ButtonBarBrush, + BorderBrush = AssaDrawWindow.LineBrush, + BorderThickness = new Thickness(0, 1, 0, 0), + Padding = new Thickness(16, 10), + }; } } diff --git a/src/ui/Features/Ssa/SsaStylesViewModel.cs b/src/ui/Features/Ssa/SsaStylesViewModel.cs index 39114213a43..4d2d3e2db64 100644 --- a/src/ui/Features/Ssa/SsaStylesViewModel.cs +++ b/src/ui/Features/Ssa/SsaStylesViewModel.cs @@ -1155,6 +1155,13 @@ internal void StorageStylesGotFocus(object? sender, FocusChangedEventArgs e) private void SwitchToFileStyle() { + // The grid can show a selection the view model never got (the first row is selected + // when the grid fills): clicking that row only raises GotFocus, and the editor stayed empty + if (SelectedFileStyle == null && FileStyleGrid.SelectedItem is StyleDisplay gridSelected) + { + SelectedFileStyle = gridSelected; + } + var selectedStyle = SelectedFileStyle; CurrentStyle = selectedStyle; CurrentTitle = Se.Language.Assa.StylesInFile; @@ -1165,6 +1172,12 @@ private void SwitchToFileStyle() private void SwitchToStorageStyle() { + // See SwitchToFileStyle: the first saved style showed selected but could not be edited + if (SelectedStorageStyle == null && StorageStyleGrid.SelectedItem is StyleDisplay gridSelected) + { + SelectedStorageStyle = gridSelected; + } + var selectedStyle = SelectedStorageStyle; CurrentStyle = selectedStyle; CurrentTitle = Se.Language.Assa.StylesSaved; diff --git a/src/ui/Features/Ssa/SsaStylesWindow.cs b/src/ui/Features/Ssa/SsaStylesWindow.cs index 59cfacd811f..b1d086215ac 100644 --- a/src/ui/Features/Ssa/SsaStylesWindow.cs +++ b/src/ui/Features/Ssa/SsaStylesWindow.cs @@ -1,20 +1,40 @@ +using System; +using System.Collections.Generic; using Avalonia; +using Avalonia.Automation; using Avalonia.Controls; +using Avalonia.Controls.Presenters; +using Avalonia.Controls.Primitives; +using Avalonia.Controls.Shapes; +using Avalonia.Controls.Templates; using Avalonia.Data; +using Avalonia.Data.Converters; using Avalonia.Input; using Avalonia.Interactivity; using Avalonia.Layout; using Avalonia.Media; -using System.Collections; +using Avalonia.Styling; using Nikse.SubtitleEdit.Features.Assa; +using Nikse.SubtitleEdit.Features.Assa.AssaDraw; using Nikse.SubtitleEdit.Features.Shared.ColorPicker; using Nikse.SubtitleEdit.Logic; using Nikse.SubtitleEdit.Logic.Config; namespace Nikse.SubtitleEdit.Features.Ssa; +/// +/// SSA styles, styled like ASSA draw and the ASSA styles window: the style lists (in the file, +/// saved) on the left, the preview on the right with the style editor under it. +/// public class SsaStylesWindow : Window { + private const string Current = nameof(SsaStylesViewModel.CurrentStyle) + "."; + private static readonly IBrush DefaultStarBrush = new SolidColorBrush(Color.FromRgb(255, 193, 7)); + + // Which list the edited style comes from: file styles in the editor accent, saved styles in amber + private static readonly IBrush SavedBrush = new SolidColorBrush(Color.FromRgb(240, 168, 48)); + private static readonly IBrush SavedSoftBrush = new SolidColorBrush(Color.FromArgb(46, 240, 168, 48)); + public SsaStylesWindow(SsaStylesViewModel vm) { UiUtil.InitializeWindow(this, GetType().Name); @@ -25,43 +45,52 @@ public SsaStylesWindow(SsaStylesViewModel vm) }); CanResize = true; Width = 1200; - Height = 850; - MinWidth = 1100; + Height = 800; + MinWidth = 1000; MinHeight = 600; + Background = AssaDrawWindow.WindowBrush; + AssaDrawWindow.AddEditorStyles(Styles); + AddStyles(Styles); vm.Window = this; DataContext = vm; - var grid = new Grid + var content = new Grid { - RowDefinitions = - { - new RowDefinition { Height = new GridLength(2, GridUnitType.Star) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - }, ColumnDefinitions = { - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) }, - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) }, + new ColumnDefinition(new GridLength(360)), + new ColumnDefinition(new GridLength(1, GridUnitType.Star)), }, - Margin = UiUtil.MakeWindowMargin(), - ColumnSpacing = 5, - RowSpacing = 5, - Width = double.NaN, - HorizontalAlignment = HorizontalAlignment.Stretch, }; - var buttonApply = UiUtil.MakeButton(Se.Language.General.Apply, vm.ApplyCommand) - .WithBindIsVisible(nameof(vm.IsApplyVisible)); - var buttonOk = UiUtil.MakeButtonOk(vm.OkCommand); - var buttonCancel = UiUtil.MakeButtonCancel(vm.CancelCommand); - var panelButtons = UiUtil.MakeButtonBar(buttonApply, buttonOk, buttonCancel); + var right = new Grid + { + RowDefinitions = + { + new RowDefinition(new GridLength(1, GridUnitType.Star)), + new RowDefinition(GridLength.Auto), + new RowDefinition(GridLength.Auto), + }, + }; + right.Add(MakePreview(vm), 0, 0); + right.Add(MakeEditingBanner(vm), 1, 0); + right.Add(MakeEditorPanel(vm), 2, 0); - grid.Add(MakeLeftView(vm), 0); - grid.Add(MakeRightView(vm), 0, 1); - grid.Add(panelButtons, 3, 0, 1, 2); + content.Add(MakeListsPanel(vm), 0, 0); + content.Add(right, 0, 1); - Content = grid; + var mainGrid = new Grid + { + RowDefinitions = + { + new RowDefinition(new GridLength(1, GridUnitType.Star)), + new RowDefinition(GridLength.Auto), + }, + }; + mainGrid.Add(content, 0, 0); + mainGrid.Add(MakeButtonBar(vm), 1, 0); + Content = mainGrid; // initial focus on an input, not an action button - a focused button clicks on bare Space UiUtil.FocusOnFirstActivation(this, () => { TableViewExtras.FocusRow(vm.FileStyleGrid); }); @@ -71,87 +100,169 @@ public SsaStylesWindow(SsaStylesViewModel vm) Loaded += delegate { UiUtil.RestoreWindowPosition(this); }; } - private static Grid MakeLeftView(SsaStylesViewModel vm) + /// + /// Toggle buttons (B/I) and the alignment cells, in the editor accent. + /// + private static void AddStyles(Styles styles) { - var grid = new Grid + styles.Add(new Style(x => x.OfType().Class("tool")) { - RowDefinitions = + Setters = { - new RowDefinition { Height = new GridLength(1, GridUnitType.Star) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Star) }, + new Setter(ToggleButton.BackgroundProperty, Brushes.Transparent), + new Setter(ToggleButton.BorderThicknessProperty, new Thickness(0)), + new Setter(ToggleButton.CornerRadiusProperty, new CornerRadius(6)), + new Setter(ToggleButton.ForegroundProperty, AssaDrawWindow.IconBrush), + new Setter(ToggleButton.PaddingProperty, new Thickness(0)), }, - ColumnDefinitions = + }); + styles.Add(new Style(x => x.OfType().Class("tool").Class(":checked").Template().OfType().Name("PART_ContentPresenter")) + { + Setters = { - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) }, + new Setter(ContentPresenter.BackgroundProperty, AssaDrawWindow.AccentBrush), + new Setter(ContentPresenter.ForegroundProperty, Brushes.White), }, - Width = double.NaN, - HorizontalAlignment = HorizontalAlignment.Stretch, - }; - - grid.Add(MakeFileStyles(vm), 0); - grid.Add(MakeStorageStyles(vm), 1); + }); - return grid; + styles.Add(new Style(x => x.OfType().Class("anchor").Template().OfType()) + { + Setters = + { + new Setter(Border.BackgroundProperty, AssaDrawWindow.FieldBrush), + new Setter(Border.BorderBrushProperty, AssaDrawWindow.FieldBorderBrush), + }, + }); + styles.Add(new Style(x => x.OfType().Class("anchor").Class(":pointerover").Template().OfType()) + { + Setters = { new Setter(Border.BackgroundProperty, AssaDrawWindow.HoverBrush) }, + }); + styles.Add(new Style(x => x.OfType().Class("anchor").Class(":checked").Template().OfType()) + { + Setters = + { + new Setter(Border.BackgroundProperty, AssaDrawWindow.AccentBrush), + new Setter(Border.BorderBrushProperty, AssaDrawWindow.AccentBrush), + }, + }); + styles.Add(new Style(x => x.OfType().Class("anchor").Template().OfType()) + { + Setters = { new Setter(Shape.FillProperty, AssaDrawWindow.FaintTextBrush) }, + }); + styles.Add(new Style(x => x.OfType().Class("anchor").Class(":checked").Template().OfType()) + { + Setters = { new Setter(Shape.FillProperty, Brushes.White) }, + }); } - private static Border MakeFileStyles(SsaStylesViewModel vm) + private static Border MakeListsPanel(SsaStylesViewModel vm) { var grid = new Grid { RowDefinitions = { - new RowDefinition { Height = new GridLength(2, GridUnitType.Auto) }, // label - new RowDefinition { Height = new GridLength(2, GridUnitType.Star) }, // data grid - new RowDefinition { Height = new GridLength(2, GridUnitType.Auto) }, // buttons + new RowDefinition(new GridLength(1, GridUnitType.Star)), + new RowDefinition(new GridLength(1, GridUnitType.Star)), }, - ColumnDefinitions = + }; + grid.Add(MakeFileStyles(vm), 0, 0); + grid.Add(MakeStorageStyles(vm), 1, 0); + + return new Border + { + Child = grid, + Background = AssaDrawWindow.PanelBrush, + BorderBrush = AssaDrawWindow.LineBrush, + BorderThickness = new Thickness(0, 0, 1, 0), + }; + } + + /// + /// "⋯" button: a menu built when it opens, so the items reflect the current selection. + /// + private static Button MakeMoreButton(Func> items) + { + var button = AssaDrawWindow.CreateToolButton("fa-solid fa-ellipsis", Se.Language.General.More, null, 24); + var menu = new ContextMenu { Placement = PlacementMode.BottomEdgeAlignedRight }; + button.Click += (_, _) => + { + menu.ItemsSource = items(); + menu.Open(button); + }; + return button; + } + + private static MenuItem MenuItem(string header, string? icon, System.Windows.Input.ICommand command, bool isEnabled = true) + { + var item = AssaDrawWindow.MakeMenuItem(header, icon, command); + item.IsEnabled = isEnabled; + return item; + } + + /// + /// A list section: title, the actions under it, then the table filling the rest. + /// + private static Grid MakeListSection(string title, Control actions, TableView table, bool first) + { + actions.HorizontalAlignment = HorizontalAlignment.Left; + actions.Margin = new Thickness(0, 6, 0, 0); + + var header = new StackPanel { Margin = new Thickness(14, first ? 10 : 12, 10, 8) }; + var titleText = AssaDrawWindow.MakeSectionHeader(title); + titleText.Bind(TextBlock.ForegroundProperty, new Binding(nameof(SsaStylesViewModel.CurrentTitle)) + { + Converter = new FuncValueConverter(current => current == title ? (title == Se.Language.Assa.StylesSaved ? SavedBrush : AssaDrawWindow.AccentBrush) : AssaDrawWindow.DimTextBrush), + }); + header.Children.Add(titleText); + header.Children.Add(actions); + + var grid = new Grid + { + RowDefinitions = { - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) }, + new RowDefinition(GridLength.Auto), + new RowDefinition(new GridLength(1, GridUnitType.Star)), }, - Width = double.NaN, - HorizontalAlignment = HorizontalAlignment.Stretch, }; + grid.Add(new Border + { + Child = header, + BorderBrush = AssaDrawWindow.LineBrush, + BorderThickness = new Thickness(0, first ? 0 : 1, 0, 0), + }, 0, 0); + + table.Margin = new Thickness(8, 0, 8, 8); + grid.Add(table, 1, 0); + return grid; + } - var label = UiUtil.MakeLabel(Se.Language.Assa.StylesInFile).WithBold(); + private static SeTableViewColumn Column(string header, string property, GridLength width) => new() + { + Header = header, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, + Binding = new Binding(property), + Width = width, + }; + + private static void AddMenuItem(MenuFlyout flyout, SsaStylesViewModel vm, string header, System.Windows.Input.ICommand command, string isVisibleProperty) + { + var item = new MenuItem { Header = header, DataContext = vm, Command = command }; + item.Bind(Avalonia.Controls.MenuItem.IsVisibleProperty, new Binding(isVisibleProperty) { Source = vm }); + flyout.Items.Add(item); + } + private static Grid MakeFileStyles(SsaStylesViewModel vm) + { // No header sorting: SSA styles are written to the file header in list // order on OK, so the collection order is not presentation-only. var dataGrid = TableViewExtras.MakeTableView(); + AutomationProperties.SetName(dataGrid, Se.Language.Assa.StylesInFile); dataGrid.DataContext = vm; dataGrid.ItemsSource = vm.FileStyles; - - dataGrid.Columns.Add(new SeTableViewColumn - { - Header = Se.Language.General.Name, - CellTheme = UiUtil.TableViewCellTheme, - HeaderTheme = UiUtil.TableViewColumnHeaderTheme, - Binding = new Binding(nameof(StyleDisplay.Name)), - Width = new GridLength(1, GridUnitType.Star), - }); - dataGrid.Columns.Add(new SeTableViewColumn - { - Header = Se.Language.General.FontName, - CellTheme = UiUtil.TableViewCellTheme, - HeaderTheme = UiUtil.TableViewColumnHeaderTheme, - Binding = new Binding(nameof(StyleDisplay.FontName)), - Width = new GridLength(150), - }); - dataGrid.Columns.Add(new SeTableViewColumn - { - Header = Se.Language.General.FontSize, - CellTheme = UiUtil.TableViewCellTheme, - HeaderTheme = UiUtil.TableViewColumnHeaderTheme, - Binding = new Binding(nameof(StyleDisplay.FontSize)), - Width = new GridLength(90), - }); - dataGrid.Columns.Add(new SeTableViewColumn - { - Header = Se.Language.General.Usages, - CellTheme = UiUtil.TableViewCellTheme, - HeaderTheme = UiUtil.TableViewColumnHeaderTheme, - Binding = new Binding(nameof(StyleDisplay.UsageCount)), - Width = new GridLength(90), - }); + dataGrid.Columns.Add(Column(Se.Language.General.Name, nameof(StyleDisplay.Name), new GridLength(1, GridUnitType.Star))); + dataGrid.Columns.Add(Column(Se.Language.General.FontSize, nameof(StyleDisplay.FontSize), new GridLength(70))); + dataGrid.Columns.Add(Column(Se.Language.General.Usages, nameof(StyleDisplay.UsageCount), new GridLength(70))); dataGrid.Bind(TableView.SelectedItemProperty, new Binding(nameof(vm.SelectedFileStyle)) { Source = vm }); dataGrid.SelectionChanged += vm.FileStylesChanged; @@ -165,133 +276,62 @@ private static Border MakeFileStyles(SsaStylesViewModel vm) flyout.Opening += vm.FilesContextMenuOpening; dataGrid.ContextFlyout = flyout; UiUtil.AttachMacContextFlyoutHandler(dataGrid); - - var menuItemCopyToStorageStyles = new MenuItem - { - Header = Se.Language.Assa.CopyToStorageStyles, - DataContext = vm, - Command = vm.FileCopyToStorageCommand, - }; - menuItemCopyToStorageStyles.Bind(MenuItem.IsVisibleProperty, new Binding(nameof(vm.IsFileStyleSelected)) { Source = vm }); - flyout.Items.Add(menuItemCopyToStorageStyles); - - var menuItemDelete = new MenuItem - { - Header = Se.Language.General.Delete, - DataContext = vm, - Command = vm.FileRemoveCommand, - }; - menuItemDelete.Bind(MenuItem.IsVisibleProperty, new Binding(nameof(vm.IsDeleteVisible)) { Source = vm }); - flyout.Items.Add(menuItemDelete); - - var menuItemClear = new MenuItem - { - Header = Se.Language.General.Clear, - DataContext = vm, - Command = vm.FileRemoveAllCommand, - }; - menuItemClear.Bind(MenuItem.IsVisibleProperty, new Binding(nameof(vm.IsDeleteAllVisible)) { Source = vm }); - flyout.Items.Add(menuItemClear); - - var menuItemTakeUsagesFrom = new MenuItem - { - Header = Se.Language.Assa.TakeUsagesFromDotDotDot, - DataContext = vm, - Command = vm.FileTakeUsagesFromCommand, - }; - menuItemTakeUsagesFrom.Bind(MenuItem.IsVisibleProperty, new Binding(nameof(vm.IsTakeUsagesFromVisible)) { Source = vm }); - flyout.Items.Add(menuItemTakeUsagesFrom); - - var menuItemReplaceWith = new MenuItem - { - Header = Se.Language.Assa.ReplaceStyleWithDotDotDot, - DataContext = vm, - Command = vm.FileReplaceWithCommand, - }; - menuItemReplaceWith.Bind(MenuItem.IsVisibleProperty, new Binding(nameof(vm.IsFileStyleSelected)) { Source = vm }); - flyout.Items.Add(menuItemReplaceWith); - + AddMenuItem(flyout, vm, Se.Language.Assa.CopyToStorageStyles, vm.FileCopyToStorageCommand, nameof(vm.IsFileStyleSelected)); + AddMenuItem(flyout, vm, Se.Language.General.Delete, vm.FileRemoveCommand, nameof(vm.IsDeleteVisible)); + AddMenuItem(flyout, vm, Se.Language.General.Clear, vm.FileRemoveAllCommand, nameof(vm.IsDeleteAllVisible)); + AddMenuItem(flyout, vm, Se.Language.Assa.TakeUsagesFromDotDotDot, vm.FileTakeUsagesFromCommand, nameof(vm.IsTakeUsagesFromVisible)); + AddMenuItem(flyout, vm, Se.Language.Assa.ReplaceStyleWithDotDotDot, vm.FileReplaceWithCommand, nameof(vm.IsFileStyleSelected)); StylesDialogHelper.AddMoveMenuItems(flyout, vm, nameof(vm.IsMoveVisible), vm.FileMoveUpCommand, vm.FileMoveDownCommand, vm.FileMoveToTopCommand, vm.FileMoveToBottomCommand); - var buttonNew = UiUtil.MakeButton(vm.FileNewCommand, IconNames.Plus, Se.Language.General.New); - var buttonRemove = UiUtil.MakeButton(vm.FileRemoveCommand, IconNames.Trash, Se.Language.General.Delete); - var buttonDuplicate = UiUtil.MakeButton(vm.FilesDuplicateCommand, IconNames.Duplicate, Se.Language.General.Duplicate); - var buttonImport = UiUtil.MakeButton(vm.FileImportCommand, IconNames.Import, Se.Language.General.Import); - var buttonExport = UiUtil.MakeButton(vm.FileExportCommand, IconNames.Export, Se.Language.General.Export); - var buttonCopyToStorage = UiUtil.MakeButton(Se.Language.Assa.CopyToStorageStyles, vm.FileCopyToStorageCommand).WithBindEnabled(nameof(vm.IsFileStyleSelected)); - var panelButtons = UiUtil.MakeButtonBar( - buttonNew, - buttonDuplicate, - buttonRemove, - buttonImport, - buttonExport, - buttonCopyToStorage - ).WithAlignmentLeft(); - - grid.Add(label, 0, 0); - grid.Add(dataGrid, 1, 0); - grid.Add(panelButtons, 2, 0); - - return UiUtil.MakeBorderForControl(grid).WithMarginBottom(5); + var actions = new StackPanel { Orientation = Orientation.Horizontal, Spacing = 2 }; + actions.Children.Add(AssaDrawWindow.CreateToolButton("fa-solid fa-plus", Se.Language.General.New, vm.FileNewCommand, 24)); + actions.Children.Add(AssaDrawWindow.CreateToolButton("fa-regular fa-clone", Se.Language.General.Duplicate, vm.FilesDuplicateCommand, 24)); + actions.Children.Add(AssaDrawWindow.CreateToolButton("fa-solid fa-trash", Se.Language.General.Delete, vm.FileRemoveCommand, 24)); + actions.Children.Add(MakeMoreButton(() => + [ + MenuItem(Se.Language.Assa.CopyToStorageStyles, "fa-solid fa-arrow-down", vm.FileCopyToStorageCommand, vm.IsFileStyleSelected), + new Separator(), + MenuItem(Se.Language.General.Import, "fa-solid fa-file-import", vm.FileImportCommand), + MenuItem(Se.Language.General.Export, "fa-solid fa-file-export", vm.FileExportCommand), + new Separator(), + MenuItem(Se.Language.Assa.TakeUsagesFromDotDotDot, null, vm.FileTakeUsagesFromCommand, vm.IsFileStyleSelected), + MenuItem(Se.Language.Assa.ReplaceStyleWithDotDotDot, null, vm.FileReplaceWithCommand, vm.IsFileStyleSelected), + ])); + + return MakeListSection(Se.Language.Assa.StylesInFile, actions, dataGrid, first: true); } - private static Border MakeStorageStyles(SsaStylesViewModel vm) + private static Grid MakeStorageStyles(SsaStylesViewModel vm) { - var grid = new Grid - { - RowDefinitions = - { - new RowDefinition { Height = new GridLength(2, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(2, GridUnitType.Star) }, - new RowDefinition { Height = new GridLength(2, GridUnitType.Auto) }, - }, - ColumnDefinitions = - { - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) }, - }, - Width = double.NaN, - HorizontalAlignment = HorizontalAlignment.Stretch, - }; - - var label = UiUtil.MakeLabel(Se.Language.Assa.StylesSaved).WithBold(); - // No header sorting: the storage style order is persisted to settings in // list order on OK, so the collection order is not presentation-only. var dataGrid = TableViewExtras.MakeTableView(); + AutomationProperties.SetName(dataGrid, Se.Language.Assa.StylesSaved); dataGrid.DataContext = vm; dataGrid.ItemsSource = vm.StorageStyles; + dataGrid.Columns.Add(Column(Se.Language.General.Name, nameof(StyleDisplay.Name), new GridLength(1, GridUnitType.Star))); + dataGrid.Columns.Add(Column(Se.Language.General.FontSize, nameof(StyleDisplay.FontSize), new GridLength(70))); - dataGrid.Columns.Add(new SeTableViewColumn - { - Header = Se.Language.General.Name, - CellTheme = UiUtil.TableViewCellTheme, - HeaderTheme = UiUtil.TableViewColumnHeaderTheme, - Binding = new Binding(nameof(StyleDisplay.Name)), - Width = new GridLength(1, GridUnitType.Star), - }); - dataGrid.Columns.Add(new SeTableViewColumn - { - Header = Se.Language.General.FontName, - CellTheme = UiUtil.TableViewCellTheme, - HeaderTheme = UiUtil.TableViewColumnHeaderTheme, - Binding = new Binding(nameof(StyleDisplay.FontName)), - Width = new GridLength(150), - }); - dataGrid.Columns.Add(new SeTableViewColumn - { - Header = Se.Language.General.FontSize, - CellTheme = UiUtil.TableViewCellTheme, - HeaderTheme = UiUtil.TableViewColumnHeaderTheme, - Binding = new Binding(nameof(StyleDisplay.FontSize)), - Width = new GridLength(90), - }); + // The default style gets a yellow star instead of "True"/"False" dataGrid.Columns.Add(new SeTableViewColumn { Header = Se.Language.General.IsDefault, CellTheme = UiUtil.TableViewCellTheme, HeaderTheme = UiUtil.TableViewColumnHeaderTheme, - Binding = new Binding(nameof(StyleDisplay.IsDefault)), - Width = new GridLength(90), + CellTemplate = new FuncDataTemplate((_, _) => new Optris.Icons.Avalonia.Icon + { + Value = "fa-solid fa-star", + FontSize = 13, + Foreground = DefaultStarBrush, + HorizontalAlignment = HorizontalAlignment.Center, + VerticalAlignment = VerticalAlignment.Center, + [!Visual.IsVisibleProperty] = new Binding(nameof(StyleDisplay.IsDefault)), + }), + NameBinding = new Binding(nameof(StyleDisplay.IsDefault)) + { + Converter = new FuncValueConverter(isDefault => isDefault ? Se.Language.General.IsDefault : string.Empty), + }, + Width = new GridLength(70), }); dataGrid.Bind(TableView.SelectedItemProperty, new Binding(nameof(vm.SelectedStorageStyle)) { Source = vm }); @@ -305,344 +345,374 @@ private static Border MakeStorageStyles(SsaStylesViewModel vm) flyout.Opening += vm.StoreContextMenuOpening; dataGrid.ContextFlyout = flyout; UiUtil.AttachMacContextFlyoutHandler(dataGrid); - - var menuItemCopyToFileStyles = new MenuItem - { - Header = Se.Language.Assa.CopyToFileStyles, - DataContext = vm, - Command = vm.StorageCopyToFilesCommand, - }; - menuItemCopyToFileStyles.Bind(MenuItem.IsVisibleProperty, new Binding(nameof(vm.IsCopyToFileStylesVisible)) { Source = vm }); - flyout.Items.Add(menuItemCopyToFileStyles); - - var menuItemDelete = new MenuItem - { - Header = Se.Language.General.Delete, - DataContext = vm, - Command = vm.StorageRemoveCommand, - }; - menuItemDelete.Bind(MenuItem.IsVisibleProperty, new Binding(nameof(vm.IsDeleteVisible)) { Source = vm }); - flyout.Items.Add(menuItemDelete); - - var menuItemClear = new MenuItem - { - Header = Se.Language.General.Clear, - DataContext = vm, - Command = vm.StorageRemoveAllCommand, - }; - menuItemClear.Bind(MenuItem.IsVisibleProperty, new Binding(nameof(vm.IsDeleteAllVisible)) { Source = vm }); - flyout.Items.Add(menuItemClear); - - var menuItemClearSetAsDefault = new MenuItem - { - Header = Se.Language.Assa.SetStyleAsDefault, - DataContext = vm, - Command = vm.StorageSetDefaultCommand, - }; - menuItemClearSetAsDefault.Bind(MenuItem.IsVisibleProperty, new Binding(nameof(vm.IsSetStyleAsDefaultVisible)) { Source = vm }); - flyout.Items.Add(menuItemClearSetAsDefault); - + AddMenuItem(flyout, vm, Se.Language.Assa.CopyToFileStyles, vm.StorageCopyToFilesCommand, nameof(vm.IsCopyToFileStylesVisible)); + AddMenuItem(flyout, vm, Se.Language.General.Delete, vm.StorageRemoveCommand, nameof(vm.IsDeleteVisible)); + AddMenuItem(flyout, vm, Se.Language.General.Clear, vm.StorageRemoveAllCommand, nameof(vm.IsDeleteAllVisible)); + AddMenuItem(flyout, vm, Se.Language.Assa.SetStyleAsDefault, vm.StorageSetDefaultCommand, nameof(vm.IsSetStyleAsDefaultVisible)); StylesDialogHelper.AddMoveMenuItems(flyout, vm, nameof(vm.IsMoveVisible), vm.StorageMoveUpCommand, vm.StorageMoveDownCommand, vm.StorageMoveToTopCommand, vm.StorageMoveToBottomCommand); - var buttonNew = UiUtil.MakeButton(vm.StorageNewCommand, IconNames.Plus, Se.Language.General.New); - var buttonDuplicate = UiUtil.MakeButton(vm.StorageDuplicateCommand, IconNames.Duplicate, Se.Language.General.Duplicate); - var buttonRemove = UiUtil.MakeButton(vm.StorageRemoveCommand, IconNames.Trash, Se.Language.General.Delete); - var buttonImport = UiUtil.MakeButton(vm.StorageImportCommand, IconNames.Import, Se.Language.General.Import); - var buttonExport = UiUtil.MakeButton(vm.StorageExportCommand, IconNames.Export, Se.Language.General.Export); - var buttonSetDefault = UiUtil.MakeButton(vm.StorageSetDefaultCommand, IconNames.Check, Se.Language.Assa.SetStyleAsDefault); - var buttonCopyToFiles = UiUtil.MakeButton(Se.Language.Assa.CopyToFileStyles, vm.StorageCopyToFilesCommand).WithBindEnabled(nameof(vm.IsStorageStyleSelected)); - var panelButtons = UiUtil.MakeButtonBar( - buttonNew, - buttonDuplicate, - buttonRemove, - buttonImport, - buttonExport, - buttonSetDefault, - buttonCopyToFiles - ).WithAlignmentLeft(); - - grid.Add(label, 0, 0); - grid.Add(dataGrid, 1, 0); - grid.Add(panelButtons, 2, 0); - - return UiUtil.MakeBorderForControl(grid); + var actions = new StackPanel { Orientation = Orientation.Horizontal, Spacing = 2 }; + actions.Children.Add(AssaDrawWindow.CreateToolButton("fa-solid fa-arrow-up", Se.Language.Assa.CopyToFileStyles, vm.StorageCopyToFilesCommand, 24)); + actions.Children.Add(AssaDrawWindow.CreateToolButton("fa-solid fa-plus", Se.Language.General.New, vm.StorageNewCommand, 24)); + actions.Children.Add(AssaDrawWindow.CreateToolButton("fa-solid fa-trash", Se.Language.General.Delete, vm.StorageRemoveCommand, 24)); + actions.Children.Add(MakeMoreButton(() => + [ + MenuItem(Se.Language.General.Duplicate, "fa-regular fa-clone", vm.StorageDuplicateCommand, vm.IsStorageStyleSelected), + MenuItem(Se.Language.Assa.SetStyleAsDefault, "fa-solid fa-star", vm.StorageSetDefaultCommand, vm.IsStorageStyleSelected), + new Separator(), + MenuItem(Se.Language.General.Import, "fa-solid fa-file-import", vm.StorageImportCommand), + MenuItem(Se.Language.General.Export, "fa-solid fa-file-export", vm.StorageExportCommand), + ])); + + return MakeListSection(Se.Language.Assa.StylesSaved, actions, dataGrid, first: false); } - private static Grid MakeRightView(SsaStylesViewModel vm) + /// + /// The style editor for "[V4 Styles]". Underline, strikeout, scale, spacing and angle are + /// deliberately absent: a Sub Station Alpha v4 style line has no such fields, so editing them + /// here would only have thrown the values away on save (#13734). Use .ass for those. + /// + private static Border MakeEditorPanel(SsaStylesViewModel vm) { - var grid = new Grid + StackPanel Column(params Border[] sections) { - RowDefinitions = + var column = new StackPanel(); + foreach (var section in sections) { - new RowDefinition { Height = new GridLength(2, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Star) }, - }, + column.Children.Add(section); + } + + return column; + } + + var columns = new Grid + { ColumnDefinitions = { - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) }, + new ColumnDefinition(new GridLength(1, GridUnitType.Star)), + new ColumnDefinition(new GridLength(1, GridUnitType.Star)), + new ColumnDefinition(new GridLength(1, GridUnitType.Star)), }, - Width = double.NaN, - HorizontalAlignment = HorizontalAlignment.Stretch, }; + columns.Add(Column(MakeNameSection(), MakeFontSection(vm)), 0, 0); + columns.Add(new Border + { + Child = Column(MakeColorsSection(vm), MakeBorderSection(vm)), + BorderBrush = AssaDrawWindow.LineBrush, + BorderThickness = new Thickness(1, 0), + }, 0, 1); + columns.Add(Column(MakeAlignmentSection()), 0, 2); + + return new Border + { + Child = columns, + Background = AssaDrawWindow.PanelBrush, + BorderBrush = AssaDrawWindow.LineBrush, + BorderThickness = new Thickness(0, 1, 0, 0), + [!Border.IsEnabledProperty] = new Binding(nameof(vm.CurrentStyle)) { Converter = ObjectConverters.IsNotNull }, + }; + } - grid.Add(MakeSelectedStyleView(vm), 0); - grid.Add(MakePreviewView(vm), 1); + private static NumericUpDown Field(string property, double min, double max, string prefix, string name, string format = "0.#", decimal increment = 1) + { + var field = AssaDrawWindow.MakeField(Current + property, min, max, prefix: prefix, format: format); + field.Increment = increment; + AutomationProperties.SetName(field, name); + if (Se.Settings.Appearance.ShowHints) + { + ToolTip.SetTip(field, name); + } - return grid; + return field; } /// - /// The style editor for "[V4 Styles]". Underline, strikeout, scale, spacing and angle are - /// deliberately absent: a Sub Station Alpha v4 style line has no such fields, so editing them - /// here would only have thrown the values away on save (#13734). Use .ass for those. + /// What is being edited, above the editor: "Editing Default" with an "In file" (blue) or + /// "Saved" (amber) badge - changing a saved style does not change the subtitle, and the + /// other way round, so this has to be hard to miss. /// - private static Border MakeSelectedStyleView(SsaStylesViewModel vm) + private static Border MakeEditingBanner(SsaStylesViewModel vm) { - var grid = new Grid + Binding Pick(object file, object saved) => new Binding(nameof(vm.CurrentTitle)) { - RowDefinitions = - { - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - }, - ColumnDefinitions = - { - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) }, - }, - Width = double.NaN, - HorizontalAlignment = HorizontalAlignment.Stretch, - ColumnSpacing = 5, - RowSpacing = 5, + Converter = new FuncValueConverter(title => title == Se.Language.Assa.StylesSaved ? saved : file), }; - var label = UiUtil.MakeLabel().WithBold().WithBindText(vm, nameof(vm.CurrentTitle)); - - var labelName = UiUtil.MakeLabel(Se.Language.General.Name); - var textBoxName = UiUtil.MakeTextBox(200, vm, nameof(vm.CurrentStyle) + "." + nameof(StyleDisplay.Name)); - var panelName = UiUtil.MakeHorizontalPanel(labelName, textBoxName).WithMarginBottom(10); - - var labelFontName = UiUtil.MakeLabel(Se.Language.General.FontName); - var comboBoxFontName = UiUtil.MakeComboBox(vm.Fonts, vm, nameof(vm.CurrentStyle) + "." + nameof(StyleDisplay.FontName)).WithMinWidth(150); - var buttonFontBrowse = UiUtil.MakeButtonBrowse(vm.PickFontNameCommand, null, Se.Language.Tools.PickFontNameTitle); - var buttonFontAttachments = UiUtil.MakeButton(vm.BrowseFontNameCommand, IconNames.Paperclip, Se.Language.Assa.Attachments); - var labelFontSize = UiUtil.MakeLabel(Se.Language.General.FontSize); - var numericUpDownFontSize = UiUtil.MakeNumericUpDownOneDecimal(1, 1000, 130, vm, nameof(vm.CurrentStyle) + "." + nameof(StyleDisplay.FontSize)); - numericUpDownFontSize.Increment = 1; - var panelFont = UiUtil.MakeHorizontalPanel(labelFontName, comboBoxFontName, buttonFontBrowse, buttonFontAttachments, labelFontSize, numericUpDownFontSize); - - var checkBoxBold = UiUtil.MakeCheckBox(Se.Language.General.Bold, vm, nameof(vm.CurrentStyle) + "." + nameof(StyleDisplay.Bold)); - var checkBoxItalic = UiUtil.MakeCheckBox(Se.Language.General.Italic, vm, nameof(vm.CurrentStyle) + "." + nameof(StyleDisplay.Italic)); - var panelFontStyle = UiUtil.MakeHorizontalPanel(checkBoxBold, checkBoxItalic).WithMarginBottom(10); - - var labelColorPrimary = UiUtil.MakeLabel(Se.Language.Assa.Primary); - var colorPickerPrimary = MakeStyleColorPickerButton(vm, nameof(StyleDisplay.ColorPrimary)); - var labelColorOutline = UiUtil.MakeLabel(Se.Language.General.Outline); - var colorPickerOutline = MakeStyleColorPickerButton(vm, nameof(StyleDisplay.ColorOutline)); - var labelColorShadow = UiUtil.MakeLabel(Se.Language.General.Shadow); - var colorPickerShadow = MakeStyleColorPickerButton(vm, nameof(StyleDisplay.ColorShadow)); - var labelColorSecondary = UiUtil.MakeLabel(Se.Language.Assa.Secondary); - var colorPickerSecondary = MakeStyleColorPickerButton(vm, nameof(StyleDisplay.ColorSecondary)); - var panelColors = UiUtil.MakeHorizontalPanel( - labelColorPrimary, - colorPickerPrimary, - labelColorOutline, - colorPickerOutline, - labelColorShadow, - colorPickerShadow, - labelColorSecondary, - colorPickerSecondary - ).WithMarginBottom(10); - - var alignmentView = MakeAlignmentView(vm); - var marginView = MakeMarginView(vm); - var borderView = MakeBorderView(vm); - var panelMore = UiUtil.MakeHorizontalPanel(alignmentView, marginView, borderView); - - grid.Add(label, 0, 0); - grid.Add(panelName, 1, 0); - grid.Add(panelFont, 2, 0); - grid.Add(panelFontStyle, 3, 0); - grid.Add(panelColors, 4, 0); - grid.Add(panelMore, 5, 0); - - return UiUtil.MakeBorderForControl(grid).WithMarginBottom(5); - } + var icon = new Optris.Icons.Avalonia.Icon { FontSize = 14, VerticalAlignment = VerticalAlignment.Center }; + icon.Bind(Optris.Icons.Avalonia.Icon.ValueProperty, Pick("fa-solid fa-file-lines", "fa-solid fa-bookmark")); + icon.Bind(Optris.Icons.Avalonia.Icon.ForegroundProperty, Pick(AssaDrawWindow.AccentBrush, SavedBrush)); - private static Border MakeAlignmentView(SsaStylesViewModel vm) - { - var grid = new Grid + var editing = new TextBlock { - RowDefinitions = - { - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - }, - ColumnDefinitions = - { - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Auto) }, - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Auto) }, - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Auto) }, - }, - Width = double.NaN, - HorizontalAlignment = HorizontalAlignment.Stretch, + Text = Se.Language.Assa.StylesEditing, + FontSize = 12.5, + Foreground = AssaDrawWindow.DimTextBrush, + VerticalAlignment = VerticalAlignment.Center, }; + var name = new TextBlock + { + FontSize = 13.5, + FontWeight = FontWeight.SemiBold, + VerticalAlignment = VerticalAlignment.Center, + TextTrimming = TextTrimming.CharacterEllipsis, + [!TextBlock.TextProperty] = new Binding(Current + nameof(StyleDisplay.Name)), + }; + var badgeText = new TextBlock + { + FontSize = 11.5, + FontWeight = FontWeight.SemiBold, + [!TextBlock.TextProperty] = new Binding(nameof(vm.CurrentTitle)), + }; + var badge = new Border + { + Child = badgeText, + CornerRadius = new CornerRadius(9), + Padding = new Thickness(9, 2), + VerticalAlignment = VerticalAlignment.Center, + }; + badge.Bind(Border.BackgroundProperty, Pick(AssaDrawWindow.AccentBrush, SavedBrush)); + badgeText.Bind(TextBlock.ForegroundProperty, Pick(Brushes.White, new SolidColorBrush(Color.FromRgb(60, 38, 0)))); - var label = UiUtil.MakeLabel(Se.Language.General.Alignment); - - grid.Add(label, 0, 0, 1, 3); - grid.Add(UiUtil.MakeRadioButton(string.Empty, vm, nameof(vm.CurrentStyle) + "." + nameof(StyleDisplay.AlignmentAn7), "align"), 1, 0); - grid.Add(UiUtil.MakeRadioButton(string.Empty, vm, nameof(vm.CurrentStyle) + "." + nameof(StyleDisplay.AlignmentAn8), "align"), 1, 1); - grid.Add(UiUtil.MakeRadioButton(string.Empty, vm, nameof(vm.CurrentStyle) + "." + nameof(StyleDisplay.AlignmentAn9), "align"), 1, 2); - grid.Add(UiUtil.MakeRadioButton(string.Empty, vm, nameof(vm.CurrentStyle) + "." + nameof(StyleDisplay.AlignmentAn4), "align"), 2, 0); - grid.Add(UiUtil.MakeRadioButton(string.Empty, vm, nameof(vm.CurrentStyle) + "." + nameof(StyleDisplay.AlignmentAn5), "align"), 2, 1); - grid.Add(UiUtil.MakeRadioButton(string.Empty, vm, nameof(vm.CurrentStyle) + "." + nameof(StyleDisplay.AlignmentAn6), "align"), 2, 2); - grid.Add(UiUtil.MakeRadioButton(string.Empty, vm, nameof(vm.CurrentStyle) + "." + nameof(StyleDisplay.AlignmentAn1), "align"), 3, 0); - grid.Add(UiUtil.MakeRadioButton(string.Empty, vm, nameof(vm.CurrentStyle) + "." + nameof(StyleDisplay.AlignmentAn2), "align"), 3, 1); - grid.Add(UiUtil.MakeRadioButton(string.Empty, vm, nameof(vm.CurrentStyle) + "." + nameof(StyleDisplay.AlignmentAn3), "align"), 3, 2); + var row = new StackPanel { Orientation = Orientation.Horizontal, Spacing = 8 }; + row.Children.Add(icon); + row.Children.Add(editing); + row.Children.Add(name); + row.Children.Add(badge); - return UiUtil.MakeBorderForControl(grid); + var banner = new Border + { + Child = row, + Padding = new Thickness(14, 8), + BorderThickness = new Thickness(3, 1, 0, 0), + [!Border.IsVisibleProperty] = new Binding(nameof(vm.CurrentStyle)) { Converter = ObjectConverters.IsNotNull }, + }; + banner.Bind(Border.BackgroundProperty, Pick(AssaDrawWindow.AccentSoftBrush, SavedSoftBrush)); + banner.Bind(Border.BorderBrushProperty, Pick(AssaDrawWindow.AccentBrush, SavedBrush)); + AutomationProperties.SetName(banner, Se.Language.Assa.StylesEditing); + return banner; } - private static Border MakeMarginView(SsaStylesViewModel vm) + private static Border MakeNameSection() { - var grid = new Grid + var name = new TextBox { - RowDefinitions = - { - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - }, - ColumnDefinitions = - { - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) }, - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) }, - }, - Width = double.NaN, - HorizontalAlignment = HorizontalAlignment.Stretch, - RowSpacing = 5, + FontSize = 12.5, + Background = AssaDrawWindow.FieldBrush, + BorderBrush = AssaDrawWindow.FieldBorderBrush, + CornerRadius = new CornerRadius(6), + [!TextBox.TextProperty] = new Binding(Current + nameof(StyleDisplay.Name)) { Mode = BindingMode.TwoWay }, }; + AutomationProperties.SetName(name, Se.Language.General.Name); - var label = UiUtil.MakeLabel(Se.Language.General.Margin); - grid.Add(label, 0); - - var labelMarginLeft = UiUtil.MakeLabel(Se.Language.General.Left); - var numericUpDownMarginLeft = UiUtil.MakeNumericUpDownInt(0, 1000, 10, 130, vm, nameof(vm.CurrentStyle) + "." + nameof(StyleDisplay.MarginLeft)); - grid.Add(labelMarginLeft, 1, 0); - grid.Add(numericUpDownMarginLeft, 1, 1); - - var labelMarginRight = UiUtil.MakeLabel(Se.Language.General.Right); - var numericUpDownMarginRight = UiUtil.MakeNumericUpDownInt(0, 1000, 10, 130, vm, nameof(vm.CurrentStyle) + "." + nameof(StyleDisplay.MarginRight)); - grid.Add(labelMarginRight, 2, 0); - grid.Add(numericUpDownMarginRight, 2, 1); - - var labelMarginVertical = UiUtil.MakeLabel(Se.Language.General.Vertical); - var numericUpDownMarginVertical = UiUtil.MakeNumericUpDownInt(0, 1000, 10, 130, vm, nameof(vm.CurrentStyle) + "." + nameof(StyleDisplay.MarginVertical)); - grid.Add(labelMarginVertical, 3, 0); - grid.Add(numericUpDownMarginVertical, 3, 1); - - return UiUtil.MakeBorderForControl(grid); + return AssaDrawWindow.MakeEditorSection(Se.Language.General.Name, name, first: true); } - private static Border MakeBorderView(SsaStylesViewModel vm) + private static Border MakeFontSection(SsaStylesViewModel vm) { - var grid = new Grid - { - RowDefinitions = + var fontName = UiUtil.MakeComboBox(vm.Fonts, vm, Current + nameof(StyleDisplay.FontName)); + fontName.HorizontalAlignment = HorizontalAlignment.Stretch; + AutomationProperties.SetName(fontName, Se.Language.General.FontName); + var pick = AssaDrawWindow.CreateToolButton("fa-solid fa-magnifying-glass", Se.Language.Tools.PickFontNameTitle, vm.PickFontNameCommand, 28); + var attachments = AssaDrawWindow.CreateToolButton("fa-solid fa-paperclip", Se.Language.Assa.Attachments, vm.BrowseFontNameCommand, 28); + var fontRow = new Grid { ColumnSpacing = 4, ColumnDefinitions = { new ColumnDefinition(new GridLength(1, GridUnitType.Star)), new ColumnDefinition(GridLength.Auto), new ColumnDefinition(GridLength.Auto) } }; + fontRow.Add(fontName, 0, 0); + fontRow.Add(pick, 0, 1); + fontRow.Add(attachments, 0, 2); + + var size = Field(nameof(StyleDisplay.FontSize), 1, 1000, "Aa", Se.Language.General.FontSize); + size.Width = 76; + + ToggleButton Toggle(string text, string property, string name, FontWeight weight = FontWeight.Normal, FontStyle style = FontStyle.Normal) + { + var toggle = new ToggleButton { - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - }, - ColumnDefinitions = + Content = new TextBlock { Text = text, FontWeight = weight, FontStyle = style, FontSize = 13 }, + Width = 28, + Height = 28, + HorizontalContentAlignment = HorizontalAlignment.Center, + VerticalContentAlignment = VerticalAlignment.Center, + [!ToggleButton.IsCheckedProperty] = new Binding(Current + property) { Mode = BindingMode.TwoWay }, + }; + toggle.Classes.Add("tool"); + AutomationProperties.SetName(toggle, name); + if (Se.Settings.Appearance.ShowHints) { - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Auto) }, - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Auto) }, - }, - Width = double.NaN, - HorizontalAlignment = HorizontalAlignment.Stretch, - RowSpacing = 5, - }; + ToolTip.SetTip(toggle, name); + } - var label = UiUtil.MakeLabel(Se.Language.General.BorderStyle); - grid.Add(label, 1, 0); + return toggle; + } - var comboBoxBorderType = UiUtil.MakeComboBox(vm.BorderTypes, vm, nameof(vm.SelectedBorderType)); - comboBoxBorderType.SelectionChanged += vm.BorderTypeChanged; - grid.Add(comboBoxBorderType, 2, 0, 1, 2); + var styleRow = new StackPanel { Orientation = Orientation.Horizontal, Spacing = 4 }; + styleRow.Children.Add(size); + styleRow.Children.Add(new Border { Width = 6 }); + styleRow.Children.Add(Toggle("B", nameof(StyleDisplay.Bold), Se.Language.General.Bold, FontWeight.Bold)); + styleRow.Children.Add(Toggle("I", nameof(StyleDisplay.Italic), Se.Language.General.Italic, style: FontStyle.Italic)); - var labelOutlineWidth = UiUtil.MakeLabel(Se.Language.General.OutlineWidth); - var numericUpDownOutlineWidth = UiUtil.MakeNumericUpDownOneDecimal(0, 100, 130, vm, nameof(vm.CurrentStyle) + "." + nameof(StyleDisplay.OutlineWidth)); - numericUpDownOutlineWidth.Increment = 0.5m; - grid.Add(labelOutlineWidth, 3, 0); - grid.Add(numericUpDownOutlineWidth, 3, 1); + var content = new StackPanel { Spacing = 8 }; + content.Children.Add(fontRow); + content.Children.Add(styleRow); + return AssaDrawWindow.MakeEditorSection(Se.Language.General.Font, content); + } - var labelShadowWidth = UiUtil.MakeLabel(Se.Language.General.ShadowWidth); - var numericUpDownShadowWidth = UiUtil.MakeNumericUpDownOneDecimal(0, 100, 130, vm, nameof(vm.CurrentStyle) + "." + nameof(StyleDisplay.ShadowWidth)); - numericUpDownShadowWidth.Increment = 0.5m; - grid.Add(labelShadowWidth, 4, 0); - grid.Add(numericUpDownShadowWidth, 4, 1); + private static Border MakeColorsSection(SsaStylesViewModel vm) + { + var grid = new UniformGrid { Columns = 4 }; + foreach (var (property, name) in new[] + { + (nameof(StyleDisplay.ColorPrimary), Se.Language.Assa.Primary), + (nameof(StyleDisplay.ColorSecondary), Se.Language.Assa.Secondary), + (nameof(StyleDisplay.ColorOutline), Se.Language.General.Outline), + (nameof(StyleDisplay.ColorShadow), Se.Language.General.Shadow), + }) + { + var cell = new StackPanel { Spacing = 4, HorizontalAlignment = HorizontalAlignment.Center }; + var button = MakeStyleColorPickerButton(vm, property); + button.HorizontalAlignment = HorizontalAlignment.Center; + AutomationProperties.SetName(button, name); + cell.Children.Add(button); + cell.Children.Add(new TextBlock + { + Text = name, + FontSize = 11, + Foreground = AssaDrawWindow.DimTextBrush, + HorizontalAlignment = HorizontalAlignment.Center, + TextTrimming = TextTrimming.CharacterEllipsis, + }); + grid.Children.Add(cell); + } + + return AssaDrawWindow.MakeEditorSection(Se.Language.General.Color, grid, first: true); + } - return UiUtil.MakeBorderForControl(grid); + private static Border MakeBorderSection(SsaStylesViewModel vm) + { + var borderType = UiUtil.MakeComboBox(vm.BorderTypes, vm, nameof(vm.SelectedBorderType)); + borderType.HorizontalAlignment = HorizontalAlignment.Stretch; + borderType.SelectionChanged += vm.BorderTypeChanged; + AutomationProperties.SetName(borderType, Se.Language.General.BorderStyle); + + var outline = Field(nameof(StyleDisplay.OutlineWidth), 0, 100, "W", Se.Language.General.OutlineWidth, increment: 0.5m); + var shadow = Field(nameof(StyleDisplay.ShadowWidth), 0, 100, "S", Se.Language.General.ShadowWidth, increment: 0.5m); + + var content = new StackPanel { Spacing = 8 }; + content.Children.Add(borderType); + content.Children.Add(AssaDrawWindow.MakePropertyRow(Se.Language.General.Size, outline, shadow)); + return AssaDrawWindow.MakeEditorSection(Se.Language.General.BorderStyle, content); } - private static Border MakePreviewView(SsaStylesViewModel vm) + /// + /// The 3 x 3 alignment grid (top left ... bottom right) beside the margins. + /// + private static Border MakeAlignmentSection() { - var grid = new Grid + var anchors = new UniformGrid { Columns = 3, Rows = 3, Width = 90, Height = 90, VerticalAlignment = VerticalAlignment.Top }; + foreach (var an in new[] { 7, 8, 9, 4, 5, 6, 1, 2, 3 }) { - RowDefinitions = + var radio = new RadioButton { - new RowDefinition { Height = new GridLength(2, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(2, GridUnitType.Star) }, - }, - ColumnDefinitions = - { - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) }, - }, - Width = double.NaN, - HorizontalAlignment = HorizontalAlignment.Stretch, - }; - - var label = UiUtil.MakeLabel(Se.Language.General.Preview).WithBold(); + GroupName = "ssa-styles-alignment", + Width = 26, + Height = 26, + Margin = new Thickness(0, 0, 4, 4), + Template = new FuncControlTemplate((_, _) => new Border + { + CornerRadius = new CornerRadius(6), + BorderThickness = new Thickness(1), + Child = new Ellipse { Width = 6, Height = 6, HorizontalAlignment = HorizontalAlignment.Center, VerticalAlignment = VerticalAlignment.Center }, + }), + [!RadioButton.IsCheckedProperty] = new Binding(Current + "AlignmentAn" + an) { Mode = BindingMode.TwoWay }, + }; + radio.Classes.Add("anchor"); + AutomationProperties.SetName(radio, Se.Language.General.Alignment + " " + an); + anchors.Children.Add(radio); + } + + var margins = new StackPanel { Spacing = 6 }; + margins.Children.Add(Field(nameof(StyleDisplay.MarginLeft), 0, 1000, "L", Se.Language.General.Left, "0")); + margins.Children.Add(Field(nameof(StyleDisplay.MarginRight), 0, 1000, "R", Se.Language.General.Right, "0")); + margins.Children.Add(Field(nameof(StyleDisplay.MarginVertical), 0, 1000, "V", Se.Language.General.Vertical, "0")); + + var row = new Grid { ColumnSpacing = 14, ColumnDefinitions = { new ColumnDefinition(GridLength.Auto), new ColumnDefinition(new GridLength(1, GridUnitType.Star)) } }; + row.Add(anchors, 0, 0); + row.Add(margins, 0, 1); + return AssaDrawWindow.MakeEditorSection(Se.Language.General.Alignment + " · " + Se.Language.General.Margin, row, first: true); + } + private static Control MakePreview(SsaStylesViewModel vm) + { var image = new Image { [!Image.SourceProperty] = new Binding(nameof(vm.ImagePreview)), - DataContext = vm, + Stretch = Stretch.Uniform, HorizontalAlignment = HorizontalAlignment.Stretch, - VerticalAlignment = VerticalAlignment.Top, - Stretch = Stretch.Uniform, // Scale the preview frame to fit while keeping aspect ratio - MinHeight = 150, - MaxHeight = 220, + VerticalAlignment = VerticalAlignment.Center, + }; + UiUtil.AttachHoverTooltip(image, Se.Language.General.Preview); + AutomationProperties.SetName(image, Se.Language.General.Preview); + + return new Border { Child = image, Margin = new Thickness(16, 12) }; + } + + private static Border MakeButtonBar(SsaStylesViewModel vm) + { + // "2 styles in file · 5 saved" + var summary = new TextBlock + { + FontSize = 12, + Foreground = AssaDrawWindow.DimTextBrush, + VerticalAlignment = VerticalAlignment.Center, }; + void UpdateSummary() => summary.Text = string.Format(Se.Language.Assa.StylesSummary, vm.FileStyles.Count, vm.StorageStyles.Count); + UpdateSummary(); + vm.FileStyles.CollectionChanged += (_, _) => UpdateSummary(); + vm.StorageStyles.CollectionChanged += (_, _) => UpdateSummary(); - grid.Add(label, 0); - grid.Add(image, 1); + var buttonApply = UiUtil.MakeButton(Se.Language.General.Apply, vm.ApplyCommand) + .WithBindIsVisible(nameof(vm.IsApplyVisible)); + var buttonOk = UiUtil.MakeButtonOk(vm.OkCommand); + buttonOk.Classes.Add("primary"); + var buttonCancel = UiUtil.MakeButtonCancel(vm.CancelCommand); + var buttons = new StackPanel { Orientation = Orientation.Horizontal, Spacing = 10 }; + buttons.Children.Add(buttonApply); + buttons.Children.Add(buttonCancel); + buttons.Children.Add(buttonOk); - return UiUtil.MakeBorderForControl(grid); + var grid = new Grid + { + ColumnDefinitions = + { + new ColumnDefinition(new GridLength(1, GridUnitType.Star)), + new ColumnDefinition(GridLength.Auto), + }, + }; + grid.Add(summary, 0, 0); + grid.Add(buttons, 0, 1); + return new Border + { + Child = grid, + Background = AssaDrawWindow.ButtonBarBrush, + BorderBrush = AssaDrawWindow.LineBrush, + BorderThickness = new Thickness(0, 1, 0, 0), + Padding = new Thickness(16, 10), + }; } + /// + /// A color swatch that opens the color picker - without alpha: SSA v4 colors have none. + /// private static Button MakeStyleColorPickerButton(SsaStylesViewModel vm, string colorPropertyName) { var swatch = new Border { - Width = 30, - Height = 20, - CornerRadius = new CornerRadius(3), + Width = 34, + Height = 22, + CornerRadius = new CornerRadius(5), BorderThickness = new Thickness(1), - BorderBrush = new SolidColorBrush(Colors.Gray), + BorderBrush = AssaDrawWindow.MenuBorderBrush, VerticalAlignment = VerticalAlignment.Center, }; - swatch.Bind(Border.BackgroundProperty, new Binding(nameof(vm.CurrentStyle) + "." + colorPropertyName) + swatch.Bind(Border.BackgroundProperty, new Binding(Current + colorPropertyName) { Source = vm, Converter = new ColorToBrushConverter(), @@ -651,9 +721,10 @@ private static Button MakeStyleColorPickerButton(SsaStylesViewModel vm, string c var button = new Button { Content = swatch, - Padding = new Thickness(4, 2), + Padding = new Thickness(3), VerticalAlignment = VerticalAlignment.Center, }; + button.Classes.Add("tool"); button.Click += async (_, _) => { From ddb02765288574afcbbbe1befcdce99d9b85066a Mon Sep 17 00:00:00 2001 From: niksedk Date: Thu, 15 Oct 2026 14:12:46 +0200 Subject: [PATCH 181/306] WebVTT styles and style picker in the ASSA draw look - Styles: list with tool buttons on the left, duplicate-name warning strip, preview on top, editor sections (name, font with B/I/U/S, colors with on/off switches, CSS before/after) under it - Style picker: section header with select all / invert, CSS preview, accent accept button Both follow the app theme. Co-Authored-By: Claude Opus 5.5 --- .../WebVtt/WebVttStylePickerWindow.cs | 107 ++-- src/ui/Features/WebVtt/WebVttStylesWindow.cs | 577 ++++++++++-------- 2 files changed, 385 insertions(+), 299 deletions(-) diff --git a/src/ui/Features/WebVtt/WebVttStylePickerWindow.cs b/src/ui/Features/WebVtt/WebVttStylePickerWindow.cs index b74dbc9072f..f64fe62196f 100644 --- a/src/ui/Features/WebVtt/WebVttStylePickerWindow.cs +++ b/src/ui/Features/WebVtt/WebVttStylePickerWindow.cs @@ -1,17 +1,25 @@ using Avalonia; +using Avalonia.Automation; using Avalonia.Controls; using Avalonia.Controls.Primitives; using Avalonia.Controls.Templates; using Avalonia.Data; using Avalonia.Layout; using Avalonia.Media; +using Nikse.SubtitleEdit.Features.Assa.AssaDraw; using Nikse.SubtitleEdit.Logic; using Nikse.SubtitleEdit.Logic.Config; namespace Nikse.SubtitleEdit.Features.WebVtt; +/// +/// Pick WebVTT styles to import/export - styled like ASSA draw: the list with select all / +/// invert, the selected style's CSS under it, the accept button in the accent. +/// public class WebVttStylePickerWindow : Window { + private static readonly FontFamily MonoFont = new("Menlo, Consolas, Cascadia Mono, DejaVu Sans Mono, monospace"); + public WebVttStylePickerWindow(WebVttStylePickerViewModel vm) { UiUtil.InitializeWindow(this, GetType().Name); @@ -21,57 +29,86 @@ public WebVttStylePickerWindow(WebVttStylePickerViewModel vm) Mode = BindingMode.TwoWay, }); CanResize = true; - Width = 600; - Height = 500; - MinWidth = 400; - MinHeight = 300; + Width = 640; + Height = 540; + MinWidth = 420; + MinHeight = 320; + Background = AssaDrawWindow.WindowBrush; + AssaDrawWindow.AddEditorStyles(Styles); vm.Window = this; DataContext = vm; - var grid = new Grid + // Header: title with select all / invert + var actions = new StackPanel { Orientation = Orientation.Horizontal, Spacing = 2 }; + actions.Children.Add(AssaDrawWindow.CreateToolButton("fa-solid fa-square-check", Se.Language.General.SelectAll, vm.SelectAllCommand, 24)); + actions.Children.Add(AssaDrawWindow.CreateToolButton("fa-solid fa-right-left", Se.Language.General.InvertSelection, vm.InvertSelectionCommand, 24)); + var header = new DockPanel { Margin = new Thickness(14, 10, 10, 8) }; + DockPanel.SetDock(actions, Dock.Right); + header.Children.Add(actions); + header.Children.Add(AssaDrawWindow.MakeSectionHeader(Se.Language.General.Styles)); + + var css = new Border { - RowDefinitions = + Background = AssaDrawWindow.FieldBrush, + BorderBrush = AssaDrawWindow.FieldBorderBrush, + BorderThickness = new Thickness(1), + CornerRadius = new CornerRadius(6), + Padding = new Thickness(8, 6), + MinHeight = 64, + Margin = new Thickness(8, 0, 8, 10), + Child = new TextBlock { - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Star) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, + FontFamily = MonoFont, + FontSize = 11.5, + TextWrapping = TextWrapping.Wrap, + [!TextBlock.TextProperty] = new Binding(nameof(vm.SelectedStyleCss)), }, - ColumnDefinitions = + }; + + var list = new Grid + { + RowDefinitions = { - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) }, + new RowDefinition(GridLength.Auto), + new RowDefinition(new GridLength(1, GridUnitType.Star)), + new RowDefinition(GridLength.Auto), }, - Margin = UiUtil.MakeWindowMargin(), - ColumnSpacing = 5, - RowSpacing = 5, - Width = double.NaN, - HorizontalAlignment = HorizontalAlignment.Stretch, }; + list.Add(header, 0, 0); + list.Add(MakeStylesView(vm, out var stylesGrid), 1, 0); + list.Add(css, 2, 0); - var labelStyles = UiUtil.MakeLabel(Se.Language.General.Styles).WithBold(); + var mainGrid = new Grid { RowDefinitions = { new RowDefinition(new GridLength(1, GridUnitType.Star)), new RowDefinition(GridLength.Auto) } }; + mainGrid.Add(new Border { Child = list, Background = AssaDrawWindow.PanelBrush }, 0, 0); + mainGrid.Add(MakeButtonBar(vm), 1, 0); + Content = mainGrid; - var labelCss = UiUtil.MakeLabel().WithBindText(vm, nameof(vm.SelectedStyleCss)); - labelCss.MinHeight = 60; - labelCss.VerticalContentAlignment = VerticalAlignment.Top; + // initial focus on an input, not an action button - a focused button clicks on bare Space + UiUtil.FocusOnFirstActivation(this, () => { TableViewExtras.FocusRow(stylesGrid); }); + KeyDown += vm.KeyDown; + } + private static Border MakeButtonBar(WebVttStylePickerViewModel vm) + { var buttonOk = UiUtil.MakeButton(string.Empty, vm.OkCommand).WithBindContent(nameof(vm.ButtonAcceptText)); + buttonOk.Classes.Add("primary"); var buttonCancel = UiUtil.MakeButtonCancel(vm.CancelCommand); - var panelButtons = UiUtil.MakeButtonBar(buttonOk, buttonCancel); - - grid.Add(labelStyles, 0); - grid.Add(MakeStylesView(vm, out var stylesGrid), 1); - grid.Add(UiUtil.MakeBorderForControl(labelCss), 2); - grid.Add(panelButtons, 3); + var buttons = new StackPanel { Orientation = Orientation.Horizontal, Spacing = 10, HorizontalAlignment = HorizontalAlignment.Right }; + buttons.Children.Add(buttonCancel); + buttons.Children.Add(buttonOk); - Content = grid; - - // initial focus on an input, not an action button - a focused button clicks on bare Space - UiUtil.FocusOnFirstActivation(this, () => { TableViewExtras.FocusRow(stylesGrid); }); - KeyDown += vm.KeyDown; + return new Border + { + Child = buttons, + Background = AssaDrawWindow.ButtonBarBrush, + BorderBrush = AssaDrawWindow.LineBrush, + BorderThickness = new Thickness(0, 1, 0, 0), + Padding = new Thickness(16, 10), + }; } - private static Border MakeStylesView(WebVttStylePickerViewModel vm, out TableView tableView) + private static TableView MakeStylesView(WebVttStylePickerViewModel vm, out TableView tableView) { // No header sorting: checked styles are applied/written in list order. var dataGrid = TableViewExtras.MakeTableView(multiSelect: false); @@ -141,6 +178,8 @@ private static Border MakeStylesView(WebVttStylePickerViewModel vm, out TableVie dataGrid.ContextFlyout = flyout; UiUtil.AttachMacContextFlyoutHandler(dataGrid); - return UiUtil.MakeBorderForControl(dataGrid); + dataGrid.Margin = new Thickness(8, 0, 8, 8); + AutomationProperties.SetName(dataGrid, Se.Language.General.Styles); + return dataGrid; } } diff --git a/src/ui/Features/WebVtt/WebVttStylesWindow.cs b/src/ui/Features/WebVtt/WebVttStylesWindow.cs index fe67f20c683..8bcb40996ff 100644 --- a/src/ui/Features/WebVtt/WebVttStylesWindow.cs +++ b/src/ui/Features/WebVtt/WebVttStylesWindow.cs @@ -1,12 +1,16 @@ using Avalonia; +using Avalonia.Automation; using Avalonia.Controls; +using Avalonia.Controls.Presenters; using Avalonia.Controls.Primitives; -using Avalonia.Data.Converters; using Avalonia.Data; +using Avalonia.Data.Converters; using Avalonia.Input; using Avalonia.Interactivity; using Avalonia.Layout; using Avalonia.Media; +using Avalonia.Styling; +using Nikse.SubtitleEdit.Features.Assa.AssaDraw; using Nikse.SubtitleEdit.Features.Shared.ColorPicker; using Nikse.SubtitleEdit.Logic; using Nikse.SubtitleEdit.Logic.Config; @@ -14,8 +18,15 @@ namespace Nikse.SubtitleEdit.Features.WebVtt; +/// +/// WebVTT styles (::cue classes), styled like ASSA draw: the style list on the left, the preview +/// on the right with the style editor and the CSS before/after under it. +/// public class WebVttStylesWindow : Window { + private const string Current = nameof(WebVttStylesViewModel.SelectedStyle) + "."; + private static readonly FontFamily MonoFont = new("Menlo, Consolas, Cascadia Mono, DejaVu Sans Mono, monospace"); + public WebVttStylesWindow(WebVttStylesViewModel vm) { UiUtil.InitializeWindow(this, GetType().Name); @@ -25,44 +36,50 @@ public WebVttStylesWindow(WebVttStylesViewModel vm) Mode = BindingMode.TwoWay, }); CanResize = true; - Width = 1150; - Height = 750; - MinWidth = 900; - MinHeight = 560; + Width = 1200; + Height = 800; + MinWidth = 950; + MinHeight = 600; + Background = AssaDrawWindow.WindowBrush; + AssaDrawWindow.AddEditorStyles(Styles); + AddStyles(Styles); vm.Window = this; DataContext = vm; - var grid = new Grid + var right = new Grid { RowDefinitions = { - new RowDefinition { Height = new GridLength(1, GridUnitType.Star) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, + new RowDefinition(new GridLength(1, GridUnitType.Star)), + new RowDefinition(GridLength.Auto), }, + }; + right.Add(MakePreview(vm), 0, 0); + right.Add(MakeEditorPanel(vm), 1, 0); + + var content = new Grid + { ColumnDefinitions = { - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) }, - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) }, + new ColumnDefinition(new GridLength(380)), + new ColumnDefinition(new GridLength(1, GridUnitType.Star)), }, - Margin = UiUtil.MakeWindowMargin(), - ColumnSpacing = 5, - RowSpacing = 5, - Width = double.NaN, - HorizontalAlignment = HorizontalAlignment.Stretch, }; + content.Add(MakeStylesPanel(vm), 0, 0); + content.Add(right, 0, 1); - var buttonApply = UiUtil.MakeButton(Se.Language.General.Apply, vm.ApplyCommand) - .WithBindIsVisible(nameof(vm.IsApplyVisible)); - var buttonOk = UiUtil.MakeButtonOk(vm.OkCommand); - var buttonCancel = UiUtil.MakeButtonCancel(vm.CancelCommand); - var panelButtons = UiUtil.MakeButtonBar(buttonApply, buttonOk, buttonCancel); - - grid.Add(MakeStylesView(vm), 0, 0); - grid.Add(MakeRightView(vm), 0, 1); - grid.Add(panelButtons, 1, 0, 1, 2); - - Content = grid; + var mainGrid = new Grid + { + RowDefinitions = + { + new RowDefinition(new GridLength(1, GridUnitType.Star)), + new RowDefinition(GridLength.Auto), + }, + }; + mainGrid.Add(content, 0, 0); + mainGrid.Add(MakeButtonBar(vm), 1, 0); + Content = mainGrid; // initial focus on an input, not an action button - a focused button clicks on bare Space UiUtil.FocusOnFirstActivation(this, () => { TableViewExtras.FocusRow(vm.StyleGrid); }); @@ -72,74 +89,43 @@ public WebVttStylesWindow(WebVttStylesViewModel vm) Loaded += delegate { UiUtil.RestoreWindowPosition(this); }; } - private static Border MakeStylesView(WebVttStylesViewModel vm) + /// + /// Toggle buttons (B/I/U/S) in the editor accent. + /// + private static void AddStyles(Styles styles) { - var grid = new Grid + styles.Add(new Style(x => x.OfType().Class("tool")) { - RowDefinitions = + Setters = { - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, // label - new RowDefinition { Height = new GridLength(1, GridUnitType.Star) }, // styles - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, // duplicate names warning - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, // buttons + new Setter(ToggleButton.BackgroundProperty, Brushes.Transparent), + new Setter(ToggleButton.BorderThicknessProperty, new Thickness(0)), + new Setter(ToggleButton.CornerRadiusProperty, new CornerRadius(6)), + new Setter(ToggleButton.ForegroundProperty, AssaDrawWindow.IconBrush), + new Setter(ToggleButton.PaddingProperty, new Thickness(0)), }, - ColumnDefinitions = + }); + styles.Add(new Style(x => x.OfType().Class("tool").Class(":checked").Template().OfType().Name("PART_ContentPresenter")) + { + Setters = { - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) }, + new Setter(ContentPresenter.BackgroundProperty, AssaDrawWindow.AccentBrush), + new Setter(ContentPresenter.ForegroundProperty, Brushes.White), }, - Width = double.NaN, - HorizontalAlignment = HorizontalAlignment.Stretch, - RowSpacing = 5, - }; - - var label = UiUtil.MakeLabel(Se.Language.General.Styles).WithBold(); + }); + } + private static Border MakeStylesPanel(WebVttStylesViewModel vm) + { // No header sorting: the styles are written to the file header in list order on // OK/Apply, so the collection order is not presentation-only. var dataGrid = TableViewExtras.MakeTableView(); + AutomationProperties.SetName(dataGrid, Se.Language.General.Styles); dataGrid.DataContext = vm; dataGrid.ItemsSource = vm.Styles; - - dataGrid.Columns.Add(new SeTableViewColumn - { - Header = Se.Language.General.Name, - CellTheme = UiUtil.TableViewCellTheme, - HeaderTheme = UiUtil.TableViewColumnHeaderTheme, - Binding = new Binding(nameof(WebVttStyleDisplay.Name)), - Width = new GridLength(1, GridUnitType.Star), - }); - dataGrid.Columns.Add(new SeTableViewColumn - { - Header = Se.Language.General.FontName, - CellTheme = UiUtil.TableViewCellTheme, - HeaderTheme = UiUtil.TableViewColumnHeaderTheme, - Binding = new Binding(nameof(WebVttStyleDisplay.FontNameDisplay)), - Width = new GridLength(150), - }); - dataGrid.Columns.Add(new SeTableViewColumn - { - Header = Se.Language.General.FontSize, - CellTheme = UiUtil.TableViewCellTheme, - HeaderTheme = UiUtil.TableViewColumnHeaderTheme, - Binding = new Binding(nameof(WebVttStyleDisplay.FontSizeDisplay)), - Width = new GridLength(80), - }); - dataGrid.Columns.Add(new SeTableViewColumn - { - Header = Se.Language.General.Italic, - CellTheme = UiUtil.TableViewCellTheme, - HeaderTheme = UiUtil.TableViewColumnHeaderTheme, - Binding = new Binding(nameof(WebVttStyleDisplay.ItalicDisplay)), - Width = new GridLength(70), - }); - dataGrid.Columns.Add(new SeTableViewColumn - { - Header = Se.Language.General.Usages, - CellTheme = UiUtil.TableViewCellTheme, - HeaderTheme = UiUtil.TableViewColumnHeaderTheme, - Binding = new Binding(nameof(WebVttStyleDisplay.UsageCount)), - Width = new GridLength(80), - }); + dataGrid.Columns.Add(Column(Se.Language.General.Name, nameof(WebVttStyleDisplay.Name), new GridLength(1, GridUnitType.Star))); + dataGrid.Columns.Add(Column(Se.Language.General.FontSize, nameof(WebVttStyleDisplay.FontSizeDisplay), new GridLength(70))); + dataGrid.Columns.Add(Column(Se.Language.General.Usages, nameof(WebVttStyleDisplay.UsageCount), new GridLength(70))); dataGrid.Bind(TableView.SelectedItemProperty, new Binding(nameof(vm.SelectedStyle)) { Source = vm }); dataGrid.AddHandler(InputElement.KeyDownEvent, vm.StylesMoveKeyDown, RoutingStrategies.Tunnel); @@ -147,252 +133,312 @@ private static Border MakeStylesView(WebVttStylesViewModel vm) vm.StyleGrid = dataGrid; var flyout = new MenuFlyout(); - flyout.Items.Add(new MenuItem - { - Header = Se.Language.General.Delete, - DataContext = vm, - Command = vm.RemoveCommand, - }); - flyout.Items.Add(new MenuItem - { - Header = Se.Language.General.Clear, - DataContext = vm, - Command = vm.RemoveAllCommand, - }); + flyout.Items.Add(new MenuItem { Header = Se.Language.General.Delete, DataContext = vm, Command = vm.RemoveCommand }); + flyout.Items.Add(new MenuItem { Header = Se.Language.General.Clear, DataContext = vm, Command = vm.RemoveAllCommand }); AddMoveMenuItems(flyout, vm); dataGrid.ContextFlyout = flyout; UiUtil.AttachMacContextFlyoutHandler(dataGrid); - var labelDuplicates = UiUtil.MakeLabel().WithBindText(vm, nameof(vm.DuplicateStyleNames)); - labelDuplicates.Foreground = Brushes.OrangeRed; - labelDuplicates.Bind(IsVisibleProperty, new Binding(nameof(vm.HasDuplicateStyleNames)) { Source = vm }); - - var buttonNew = UiUtil.MakeButton(vm.NewCommand, IconNames.Plus, Se.Language.General.New); - var buttonDuplicate = UiUtil.MakeButton(vm.DuplicateCommand, IconNames.Duplicate, Se.Language.General.Duplicate); - var buttonRemove = UiUtil.MakeButton(vm.RemoveCommand, IconNames.Trash, Se.Language.General.Delete); - var buttonImport = UiUtil.MakeButton(vm.ImportCommand, IconNames.Import, Se.Language.General.Import); - var buttonExport = UiUtil.MakeButton(vm.ExportCommand, IconNames.Export, Se.Language.General.Export); - var panelButtons = UiUtil.MakeButtonBar( - buttonNew, - buttonDuplicate, - buttonRemove, - buttonImport, - buttonExport - ).WithAlignmentLeft(); - - grid.Add(label, 0, 0); - grid.Add(dataGrid, 1, 0); - grid.Add(labelDuplicates, 2, 0); - grid.Add(panelButtons, 3, 0); - - return UiUtil.MakeBorderForControl(grid); - } + // Title, then the actions under it + var actions = new StackPanel { Orientation = Orientation.Horizontal, Spacing = 2, Margin = new Thickness(0, 6, 0, 0) }; + actions.Children.Add(AssaDrawWindow.CreateToolButton("fa-solid fa-plus", Se.Language.General.New, vm.NewCommand, 24)); + actions.Children.Add(AssaDrawWindow.CreateToolButton("fa-regular fa-clone", Se.Language.General.Duplicate, vm.DuplicateCommand, 24)); + actions.Children.Add(AssaDrawWindow.CreateToolButton("fa-solid fa-trash", Se.Language.General.Delete, vm.RemoveCommand, 24)); + actions.Children.Add(AssaDrawWindow.MakeToolbarSeparator()); + actions.Children.Add(AssaDrawWindow.CreateToolButton("fa-solid fa-file-import", Se.Language.General.Import, vm.ImportCommand, 24)); + actions.Children.Add(AssaDrawWindow.CreateToolButton("fa-solid fa-file-export", Se.Language.General.Export, vm.ExportCommand, 24)); + var header = new StackPanel { Margin = new Thickness(14, 10, 10, 8) }; + header.Children.Add(AssaDrawWindow.MakeSectionHeader(Se.Language.General.Styles)); + header.Children.Add(actions); + + // Two styles with one name would overwrite each other on save + var duplicates = new Border + { + Background = new SolidColorBrush(Color.FromArgb(40, 255, 69, 0)), + BorderBrush = Brushes.OrangeRed, + BorderThickness = new Thickness(3, 0, 0, 0), + Padding = new Thickness(10, 6), + Margin = new Thickness(8, 0, 8, 8), + Child = new TextBlock + { + FontSize = 12, + TextWrapping = TextWrapping.Wrap, + [!TextBlock.TextProperty] = new Binding(nameof(vm.DuplicateStyleNames)), + }, + [!Border.IsVisibleProperty] = new Binding(nameof(vm.HasDuplicateStyleNames)), + }; - private static Grid MakeRightView(WebVttStylesViewModel vm) - { + dataGrid.Margin = new Thickness(8, 0, 8, 8); var grid = new Grid { RowDefinitions = { - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Star) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, + new RowDefinition(GridLength.Auto), + new RowDefinition(new GridLength(1, GridUnitType.Star)), + new RowDefinition(GridLength.Auto), }, - ColumnDefinitions = - { - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) }, - }, - Width = double.NaN, - HorizontalAlignment = HorizontalAlignment.Stretch, - RowSpacing = 5, }; + grid.Add(header, 0, 0); + grid.Add(dataGrid, 1, 0); + grid.Add(duplicates, 2, 0); - grid.Add(MakeSelectedStyleView(vm), 0); - grid.Add(MakePreviewView(vm), 1); - grid.Add(MakeRawStyleView(vm), 2); - - return grid; + return new Border + { + Child = grid, + Background = AssaDrawWindow.PanelBrush, + BorderBrush = AssaDrawWindow.LineBrush, + BorderThickness = new Thickness(0, 0, 1, 0), + }; } - private static Border MakeSelectedStyleView(WebVttStylesViewModel vm) + private static SeTableViewColumn Column(string header, string property, GridLength width) => new() { - var grid = new Grid + Header = header, + CellTheme = UiUtil.TableViewCellTheme, + HeaderTheme = UiUtil.TableViewColumnHeaderTheme, + Binding = new Binding(property), + Width = width, + }; + + private static Border MakeEditorPanel(WebVttStylesViewModel vm) + { + var columns = new Grid { - RowDefinitions = - { - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - }, ColumnDefinitions = { - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) }, + new ColumnDefinition(new GridLength(1, GridUnitType.Star)), + new ColumnDefinition(new GridLength(1, GridUnitType.Star)), + new ColumnDefinition(new GridLength(1, GridUnitType.Star)), }, - Width = double.NaN, - HorizontalAlignment = HorizontalAlignment.Stretch, - ColumnSpacing = 5, - RowSpacing = 5, }; - var label = UiUtil.MakeLabel(Se.Language.General.Style).WithBold(); + var first = new StackPanel(); + first.Children.Add(MakeNameSection(vm)); + first.Children.Add(MakeFontSection(vm)); + columns.Add(first, 0, 0); + columns.Add(new Border + { + Child = MakeColorsSection(vm), + BorderBrush = AssaDrawWindow.LineBrush, + BorderThickness = new Thickness(1, 0), + }, 0, 1); + columns.Add(MakeCssSection(vm), 0, 2); + + return new Border + { + Child = columns, + Background = AssaDrawWindow.PanelBrush, + BorderBrush = AssaDrawWindow.LineBrush, + BorderThickness = new Thickness(0, 1, 0, 0), + [!Border.IsEnabledProperty] = new Binding(nameof(vm.SelectedStyle)) { Converter = ObjectConverters.IsNotNull }, + }; + } + + private static Border MakeNameSection(WebVttStylesViewModel vm) + { + var name = new TextBox + { + FontSize = 12.5, + Background = AssaDrawWindow.FieldBrush, + BorderBrush = AssaDrawWindow.FieldBorderBrush, + CornerRadius = new CornerRadius(6), + [!TextBox.TextProperty] = new Binding(Current + nameof(WebVttStyleDisplay.Name)) { Mode = BindingMode.TwoWay }, + }; + AutomationProperties.SetName(name, Se.Language.General.Name); - var labelName = UiUtil.MakeLabel(Se.Language.General.Name).WithMinWidth(70); - var textBoxName = UiUtil.MakeTextBox(220, vm, nameof(vm.SelectedStyle) + "." + nameof(WebVttStyleDisplay.Name)); // A blank or already-used name would silently overwrite another style on save. - textBoxName.Bind(TemplatedControl.BorderBrushProperty, new Binding(nameof(vm.IsNameInvalid)) + name.Bind(TemplatedControl.BorderBrushProperty, new Binding(nameof(vm.IsNameInvalid)) { Source = vm, - Converter = new FuncValueConverter(invalid => invalid ? Brushes.OrangeRed : null), + Converter = new FuncValueConverter(invalid => invalid ? Brushes.OrangeRed : AssaDrawWindow.FieldBorderBrush), }); - var panelName = UiUtil.MakeHorizontalPanel(labelName, textBoxName); - - var labelFontName = UiUtil.MakeLabel(Se.Language.General.FontName).WithMinWidth(70); - var comboBoxFontName = UiUtil.MakeComboBox(vm.Fonts, vm, nameof(vm.SelectedStyle) + "." + nameof(WebVttStyleDisplay.FontName)).WithMinWidth(160); - var labelFontSize = UiUtil.MakeLabel(Se.Language.General.FontSize); - var numericUpDownFontSize = UiUtil.MakeNumericUpDownOneDecimal(0, 500, 110, vm, nameof(vm.SelectedStyle) + "." + nameof(WebVttStyleDisplay.FontSize)); - numericUpDownFontSize.Increment = 1; - var panelFont = UiUtil.MakeHorizontalPanel(labelFontName, comboBoxFontName, labelFontSize, numericUpDownFontSize); - - var checkBoxBold = UiUtil.MakeCheckBox(Se.Language.General.Bold, vm, nameof(vm.SelectedStyle) + "." + nameof(WebVttStyleDisplay.Bold)); - var checkBoxItalic = UiUtil.MakeCheckBox(Se.Language.General.Italic, vm, nameof(vm.SelectedStyle) + "." + nameof(WebVttStyleDisplay.Italic)); - var checkBoxUnderline = UiUtil.MakeCheckBox(Se.Language.General.Underline, vm, nameof(vm.SelectedStyle) + "." + nameof(WebVttStyleDisplay.Underline)); - var checkBoxStrikeout = UiUtil.MakeCheckBox(Se.Language.General.Strikeout, vm, nameof(vm.SelectedStyle) + "." + nameof(WebVttStyleDisplay.Strikeout)); - var panelFontStyle = UiUtil.MakeHorizontalPanel(checkBoxBold, checkBoxItalic, checkBoxUnderline, checkBoxStrikeout); - - // Every CSS property is optional, so each color has its own "use it" check box - an - // unchecked color is left out of the style instead of being written as a default. - var panelColor = MakeColorRow( - vm, - Se.Language.General.Color, - nameof(WebVttStyleDisplay.UseColor), - nameof(WebVttStyleDisplay.Color)); - var panelBackgroundColor = MakeColorRow( - vm, - Se.Language.General.Background, - nameof(WebVttStyleDisplay.UseBackgroundColor), - nameof(WebVttStyleDisplay.BackgroundColor)); - - var panelShadow = MakeColorRow( - vm, - Se.Language.General.Shadow, - nameof(WebVttStyleDisplay.UseShadow), - nameof(WebVttStyleDisplay.ShadowColor)); - var labelShadowWidth = UiUtil.MakeLabel(Se.Language.General.ShadowWidth); - var numericUpDownShadowWidth = UiUtil.MakeNumericUpDownOneDecimal(0, 100, 110, vm, nameof(vm.SelectedStyle) + "." + nameof(WebVttStyleDisplay.ShadowWidth)); - numericUpDownShadowWidth.Increment = 1; - panelShadow.Children.Add(labelShadowWidth); - panelShadow.Children.Add(numericUpDownShadowWidth); - - grid.Add(label, 0, 0); - grid.Add(panelName, 1, 0); - grid.Add(panelFont, 2, 0); - grid.Add(panelFontStyle, 3, 0); - grid.Add(UiUtil.MakeHorizontalPanel(panelColor, panelBackgroundColor), 4, 0); - grid.Add(panelShadow, 5, 0); - - return UiUtil.MakeBorderForControl(grid); - } - private static StackPanel MakeColorRow(WebVttStylesViewModel vm, string text, string useProperty, string colorProperty) - { - var checkBox = UiUtil.MakeCheckBox(text, vm, nameof(vm.SelectedStyle) + "." + useProperty); - var button = MakeStyleColorPickerButton(vm, colorProperty); - return UiUtil.MakeHorizontalPanel(checkBox, button); + return AssaDrawWindow.MakeEditorSection(Se.Language.General.Name, name, first: true); } - private static Border MakePreviewView(WebVttStylesViewModel vm) + private static Border MakeFontSection(WebVttStylesViewModel vm) { - var grid = new Grid + var fontName = UiUtil.MakeComboBox(vm.Fonts, vm, Current + nameof(WebVttStyleDisplay.FontName)); + fontName.HorizontalAlignment = HorizontalAlignment.Stretch; + AutomationProperties.SetName(fontName, Se.Language.General.FontName); + + var size = AssaDrawWindow.MakeField(Current + nameof(WebVttStyleDisplay.FontSize), 0, 500, 76, "Aa", "0.#"); + AutomationProperties.SetName(size, Se.Language.General.FontSize); + + ToggleButton Toggle(string text, string property, string name, FontWeight weight = FontWeight.Normal, FontStyle style = FontStyle.Normal, TextDecorationCollection? decorations = null) { - RowDefinitions = + var toggle = new ToggleButton { - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Star) }, - }, - ColumnDefinitions = + Content = new TextBlock { Text = text, FontWeight = weight, FontStyle = style, TextDecorations = decorations, FontSize = 13 }, + Width = 28, + Height = 28, + HorizontalContentAlignment = HorizontalAlignment.Center, + VerticalContentAlignment = VerticalAlignment.Center, + [!ToggleButton.IsCheckedProperty] = new Binding(Current + property) { Mode = BindingMode.TwoWay }, + }; + toggle.Classes.Add("tool"); + AutomationProperties.SetName(toggle, name); + if (Se.Settings.Appearance.ShowHints) { - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) }, - }, - Width = double.NaN, - HorizontalAlignment = HorizontalAlignment.Stretch, - }; + ToolTip.SetTip(toggle, name); + } - var label = UiUtil.MakeLabel(Se.Language.General.Preview).WithBold(); + return toggle; + } - var image = new Image + var styleRow = new StackPanel { Orientation = Orientation.Horizontal, Spacing = 4 }; + styleRow.Children.Add(size); + styleRow.Children.Add(new Border { Width = 6 }); + styleRow.Children.Add(Toggle("B", nameof(WebVttStyleDisplay.Bold), Se.Language.General.Bold, FontWeight.Bold)); + styleRow.Children.Add(Toggle("I", nameof(WebVttStyleDisplay.Italic), Se.Language.General.Italic, style: FontStyle.Italic)); + styleRow.Children.Add(Toggle("U", nameof(WebVttStyleDisplay.Underline), Se.Language.General.Underline, decorations: TextDecorations.Underline)); + styleRow.Children.Add(Toggle("S", nameof(WebVttStyleDisplay.Strikeout), Se.Language.General.Strikeout, decorations: TextDecorations.Strikethrough)); + + var content = new StackPanel { Spacing = 8 }; + content.Children.Add(fontName); + content.Children.Add(styleRow); + return AssaDrawWindow.MakeEditorSection(Se.Language.General.Font, content); + } + + /// + /// Every CSS property is optional, so each color has its own on/off switch - a color that is + /// off is left out of the style instead of being written as a default. + /// + private static Border MakeColorsSection(WebVttStylesViewModel vm) + { + var rows = new StackPanel { Spacing = 6 }; + rows.Children.Add(MakeColorRow(vm, Se.Language.General.Color, nameof(WebVttStyleDisplay.UseColor), nameof(WebVttStyleDisplay.Color))); + rows.Children.Add(MakeColorRow(vm, Se.Language.General.Background, nameof(WebVttStyleDisplay.UseBackgroundColor), nameof(WebVttStyleDisplay.BackgroundColor))); + rows.Children.Add(MakeColorRow(vm, Se.Language.General.Shadow, nameof(WebVttStyleDisplay.UseShadow), nameof(WebVttStyleDisplay.ShadowColor))); + + var shadowWidth = AssaDrawWindow.MakeField(Current + nameof(WebVttStyleDisplay.ShadowWidth), 0, 100, double.NaN, "W", "0.#"); + AutomationProperties.SetName(shadowWidth, Se.Language.General.ShadowWidth); + shadowWidth.Bind(IsEnabledProperty, new Binding(Current + nameof(WebVttStyleDisplay.UseShadow))); + rows.Children.Add(AssaDrawWindow.MakePropertyRow(Se.Language.General.Width, shadowWidth)); + + return AssaDrawWindow.MakeEditorSection(Se.Language.General.Color, rows, first: true); + } + + private static Grid MakeColorRow(WebVttStylesViewModel vm, string text, string useProperty, string colorProperty) + { + var toggle = new ToggleSwitch { - [!Image.SourceProperty] = new Binding(nameof(vm.ImagePreview)), - DataContext = vm, - HorizontalAlignment = HorizontalAlignment.Stretch, - VerticalAlignment = VerticalAlignment.Top, - Stretch = Stretch.Uniform, // Scale the preview frame to fit while keeping aspect ratio - MinHeight = 140, - MaxHeight = 220, + OnContent = null, + OffContent = null, + MinWidth = 0, + VerticalAlignment = VerticalAlignment.Center, + [!ToggleSwitch.IsCheckedProperty] = new Binding(Current + useProperty) { Mode = BindingMode.TwoWay }, }; + AutomationProperties.SetName(toggle, text); - grid.Add(label, 0); - grid.Add(image, 1); + var swatch = MakeStyleColorPickerButton(vm, colorProperty); + AutomationProperties.SetName(swatch, text); + swatch.Bind(IsEnabledProperty, new Binding(Current + useProperty)); - return UiUtil.MakeBorderForControl(grid); + var row = new Grid + { + ColumnSpacing = 8, + ColumnDefinitions = { new ColumnDefinition(GridLength.Auto), new ColumnDefinition(new GridLength(1, GridUnitType.Star)), new ColumnDefinition(GridLength.Auto) }, + }; + row.Add(toggle, 0, 0); + row.Add(new TextBlock { Text = text, FontSize = 12.5, VerticalAlignment = VerticalAlignment.Center }, 0, 1); + row.Add(swatch, 0, 2); + return row; } /// /// The CSS the style was loaded from next to the CSS it will be saved as, so an edit to a /// hand-written style shows exactly what it changes. /// - private static Border MakeRawStyleView(WebVttStylesViewModel vm) + private static Border MakeCssSection(WebVttStylesViewModel vm) { - var grid = new Grid + Border Code(string property) => new() { - RowDefinitions = - { - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - }, - ColumnDefinitions = + Background = AssaDrawWindow.FieldBrush, + BorderBrush = AssaDrawWindow.FieldBorderBrush, + BorderThickness = new Thickness(1), + CornerRadius = new CornerRadius(6), + Padding = new Thickness(8, 6), + MinHeight = 64, + Child = new TextBlock { - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) }, - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) }, + FontFamily = MonoFont, + FontSize = 11.5, + TextWrapping = TextWrapping.Wrap, + [!TextBlock.TextProperty] = new Binding(property), }, - Width = double.NaN, - HorizontalAlignment = HorizontalAlignment.Stretch, - ColumnSpacing = 5, }; - var labelBeforeTitle = UiUtil.MakeLabel(Se.Language.General.Before).WithBold(); - var labelAfterTitle = UiUtil.MakeLabel(Se.Language.General.After).WithBold(); + var content = new StackPanel { Spacing = 4 }; + content.Children.Add(AssaDrawWindow.MakeDimLabel(Se.Language.General.Before, new Thickness(0))); + content.Children.Add(Code(nameof(vm.CssBefore))); + content.Children.Add(AssaDrawWindow.MakeDimLabel(Se.Language.General.After, new Thickness(0, 6, 0, 0))); + content.Children.Add(Code(nameof(vm.CssAfter))); + return AssaDrawWindow.MakeEditorSection("CSS", content, first: true); + } - var labelBefore = UiUtil.MakeLabel().WithBindText(vm, nameof(vm.CssBefore)); - labelBefore.MinHeight = 70; - labelBefore.VerticalContentAlignment = VerticalAlignment.Top; + private static Control MakePreview(WebVttStylesViewModel vm) + { + var image = new Image + { + [!Image.SourceProperty] = new Binding(nameof(vm.ImagePreview)), + Stretch = Stretch.Uniform, + HorizontalAlignment = HorizontalAlignment.Stretch, + VerticalAlignment = VerticalAlignment.Center, + }; + AutomationProperties.SetName(image, Se.Language.General.Preview); - var labelAfter = UiUtil.MakeLabel().WithBindText(vm, nameof(vm.CssAfter)); - labelAfter.MinHeight = 70; - labelAfter.VerticalContentAlignment = VerticalAlignment.Top; + return new Border { Child = image, Margin = new Thickness(16, 12) }; + } - grid.Add(labelBeforeTitle, 0, 0); - grid.Add(labelAfterTitle, 0, 1); - grid.Add(labelBefore, 1, 0); - grid.Add(labelAfter, 1, 1); + private static Border MakeButtonBar(WebVttStylesViewModel vm) + { + var summary = new TextBlock + { + FontSize = 12, + Foreground = AssaDrawWindow.DimTextBrush, + VerticalAlignment = VerticalAlignment.Center, + }; + void UpdateSummary() => summary.Text = $"{vm.Styles.Count} {Se.Language.General.Styles.ToLowerInvariant()}"; + UpdateSummary(); + vm.Styles.CollectionChanged += (_, _) => UpdateSummary(); - return UiUtil.MakeBorderForControl(grid); + var buttonApply = UiUtil.MakeButton(Se.Language.General.Apply, vm.ApplyCommand) + .WithBindIsVisible(nameof(vm.IsApplyVisible)); + var buttonOk = UiUtil.MakeButtonOk(vm.OkCommand); + buttonOk.Classes.Add("primary"); + var buttonCancel = UiUtil.MakeButtonCancel(vm.CancelCommand); + var buttons = new StackPanel { Orientation = Orientation.Horizontal, Spacing = 10 }; + buttons.Children.Add(buttonApply); + buttons.Children.Add(buttonCancel); + buttons.Children.Add(buttonOk); + + var bar = new DockPanel(); + DockPanel.SetDock(buttons, Dock.Right); + bar.Children.Add(buttons); + bar.Children.Add(summary); + return new Border + { + Child = bar, + Background = AssaDrawWindow.ButtonBarBrush, + BorderBrush = AssaDrawWindow.LineBrush, + BorderThickness = new Thickness(0, 1, 0, 0), + Padding = new Thickness(16, 10), + }; } private static Button MakeStyleColorPickerButton(WebVttStylesViewModel vm, string colorPropertyName) { var swatch = new Border { - Width = 30, - Height = 20, - CornerRadius = new CornerRadius(3), + Width = 34, + Height = 22, + CornerRadius = new CornerRadius(5), BorderThickness = new Thickness(1), - BorderBrush = new SolidColorBrush(Colors.Gray), + BorderBrush = AssaDrawWindow.MenuBorderBrush, VerticalAlignment = VerticalAlignment.Center, }; - swatch.Bind(Border.BackgroundProperty, new Binding(nameof(vm.SelectedStyle) + "." + colorPropertyName) + swatch.Bind(Border.BackgroundProperty, new Binding(Current + colorPropertyName) { Source = vm, Converter = new ColorToBrushConverter(), @@ -401,9 +447,10 @@ private static Button MakeStyleColorPickerButton(WebVttStylesViewModel vm, strin var button = new Button { Content = swatch, - Padding = new Thickness(4, 2), + Padding = new Thickness(3), VerticalAlignment = VerticalAlignment.Center, }; + button.Classes.Add("tool"); button.Click += async (_, _) => { From 513b117905e387a200f2ed963496c9684e2b4449 Mon Sep 17 00:00:00 2001 From: niksedk Date: Thu, 15 Oct 2026 14:22:12 +0200 Subject: [PATCH 182/306] Case conversion escapes in Multiple replace regex replacements Regular expression rules in Multiple replace (and Batch convert, custom text shortcuts) now support \u, \l, \U, \L and \E in the replacement, e.g. "^Um,\s*(\w)" -> "\u$1" capitalizes the word left after removing a filler. \\u keeps a literal \u. Discussion #16033. Co-Authored-By: Claude Opus 5.5 --- docs/features/edit.md | 2 +- src/libse/Common/RegexUtils.cs | 158 ++++++++++++++++++++++++++- tests/libse/Common/RegexUtilsTest.cs | 36 +++++- 3 files changed, 192 insertions(+), 4 deletions(-) diff --git a/docs/features/edit.md b/docs/features/edit.md index b310e9cc364..30d167245df 100644 --- a/docs/features/edit.md +++ b/docs/features/edit.md @@ -113,7 +113,7 @@ Each rule has one of three match types, shown as an icon in the tree: The **Whole word** checkbox in the rule dialog makes a plain-text rule match only whole words (it is disabled for regular expression rules, where `\b` does the same). -Unlike Find and Replace, regular expression rules here are matched in multiline mode: `^` and `$` match at the start and the end of *every* line, so `^- ` strips the dash from both lines of a two-line subtitle. Put `(?-m)` in front of the pattern to anchor to the whole subtitle text instead. The replacement text follows the same rules as in Replace above - `$1` and `\n` work, other backslash escapes do not. +Unlike Find and Replace, regular expression rules here are matched in multiline mode: `^` and `$` match at the start and the end of *every* line, so `^- ` strips the dash from both lines of a two-line subtitle. Put `(?-m)` in front of the pattern to anchor to the whole subtitle text instead. In the replacement, `$1` and `\n` work like in Replace above, and regular expression rules here also support the case conversion escapes `\u` / `\l` (next character to upper / lower case) and `\U` / `\L` (upper / lower case until `\E` or the end). For example `^(?:Um|Uh),\s*(\w)` replaced with `\u$1` removes a leading "Um, " and capitalizes the word after it, and `\u\L$1` gives "Word" for "wORD". Write `\\u` for a literal `\u`; other backslash escapes are not supported. The Replace window does not support the case conversion escapes. > **Slow patterns:** the same five-second match timeout as in Find applies to each rule on each line. A rule that runs out of time is retired for the rest of the pass and flagged in the tree with "Regular expression gave up after 5 seconds - the rule is skipped, try a simpler pattern", so a catastrophically backtracking pattern shows up as an error rather than as a rule that silently matches nothing. The timeout is fixed; there is no setting for it. diff --git a/src/libse/Common/RegexUtils.cs b/src/libse/Common/RegexUtils.cs index 73896ec1e15..6c8ed3427cb 100644 --- a/src/libse/Common/RegexUtils.cs +++ b/src/libse/Common/RegexUtils.cs @@ -299,13 +299,167 @@ public static string ReplaceNewLineSafe(Regex regularExpression, string text, st // unchanged through the SplitToLines+Join normalization, so skip the four allocations. if (!ContainsNonLineFeedNewLine(text) && !ContainsNonLineFeedNewLine(replaceWith)) { - return RestorePlatformNewLines(regularExpression.Replace(text, replaceWith, count, startIndex)); + return RestorePlatformNewLines(Replace(regularExpression, text, replaceWith, count, startIndex)); } - text = regularExpression.Replace(string.Join("\n", text.SplitToLines()), replaceWith, count, startIndex); + text = Replace(regularExpression, string.Join("\n", text.SplitToLines()), replaceWith, count, startIndex); return RestorePlatformNewLines(text); } + private static string Replace(Regex regularExpression, string text, string replaceWith, int count, int startIndex) + { + var parts = ParseCaseConversionReplacement(replaceWith); + if (parts == null) + { + return regularExpression.Replace(text, replaceWith, count, startIndex); + } + + return regularExpression.Replace(text, m => ApplyCaseConversion(m, parts), count, startIndex); + } + + private enum CaseConversion + { + None, + UpperNext, + LowerNext, + Upper, + Lower, + End, + } + + private readonly struct ReplacementPart + { + public ReplacementPart(string text, CaseConversion conversion) + { + Text = text; + Conversion = conversion; + } + + public string Text { get; } + public CaseConversion Conversion { get; } + } + + /// + /// Splits a replacement on the Perl-style case conversion escapes \u, \l (next character to + /// upper/lower case), \U, \L (upper/lower case until \E or the end) and \E (#16033), so a + /// rule like "^Um,\s*(\w)" -> "\u$1" can capitalize the word left at the start. "\\u" keeps + /// a literal "\u". Returns null when the replacement has none of them - plain .NET replace. + /// + private static List ParseCaseConversionReplacement(string replaceWith) + { + if (string.IsNullOrEmpty(replaceWith) || replaceWith.IndexOf('\\') < 0) + { + return null; + } + + List parts = null; + var sb = new System.Text.StringBuilder(); + for (var i = 0; i < replaceWith.Length; i++) + { + var ch = replaceWith[i]; + if (ch != '\\' || i + 1 >= replaceWith.Length) + { + sb.Append(ch); + continue; + } + + var next = replaceWith[i + 1]; + if (next == '\\' && i + 2 < replaceWith.Length && GetCaseConversion(replaceWith[i + 2]) != CaseConversion.None) + { + // "\\u" is an escaped backslash followed by a plain "u". + sb.Append('\\').Append(replaceWith[i + 2]); + i += 2; + parts = parts ?? new List(); + continue; + } + + var conversion = GetCaseConversion(next); + if (conversion == CaseConversion.None) + { + sb.Append(ch); + continue; + } + + parts = parts ?? new List(); + parts.Add(new ReplacementPart(sb.ToString(), conversion)); + sb.Clear(); + i++; + } + + if (parts == null) + { + return null; + } + + parts.Add(new ReplacementPart(sb.ToString(), CaseConversion.None)); + return parts; + } + + private static CaseConversion GetCaseConversion(char ch) + { + switch (ch) + { + case 'u': return CaseConversion.UpperNext; + case 'l': return CaseConversion.LowerNext; + case 'U': return CaseConversion.Upper; + case 'L': return CaseConversion.Lower; + case 'E': return CaseConversion.End; + default: return CaseConversion.None; + } + } + + /// + /// Builds one match's replacement: each part's text is expanded with + /// (so $1 etc. work), then cased by the \U/\L mode in effect, then the first character after a + /// \u/\l gets that one-shot conversion - so "\u\L$1" gives "Word" like in Perl. + /// + private static string ApplyCaseConversion(Match match, List parts) + { + var sb = new System.Text.StringBuilder(); + var mode = CaseConversion.None; + var oneShot = CaseConversion.None; + foreach (var part in parts) + { + var expanded = part.Text.Length == 0 ? string.Empty : match.Result(part.Text); + if (mode == CaseConversion.Upper) + { + expanded = expanded.ToUpperInvariant(); + } + else if (mode == CaseConversion.Lower) + { + expanded = expanded.ToLowerInvariant(); + } + + if (oneShot != CaseConversion.None && expanded.Length > 0) + { + var first = oneShot == CaseConversion.UpperNext + ? char.ToUpperInvariant(expanded[0]) + : char.ToLowerInvariant(expanded[0]); + expanded = first + expanded.Substring(1); + oneShot = CaseConversion.None; + } + + sb.Append(expanded); + + switch (part.Conversion) + { + case CaseConversion.UpperNext: + case CaseConversion.LowerNext: + oneShot = part.Conversion; + break; + case CaseConversion.Upper: + case CaseConversion.Lower: + mode = part.Conversion; + break; + case CaseConversion.End: + mode = CaseConversion.None; + break; + } + } + + return sb.ToString(); + } + /// /// Re-joins line-feed-normalized text with . The rest of /// libse keeps paragraph text with platform newlines, and the format writers rely on it - diff --git a/tests/libse/Common/RegexUtilsTest.cs b/tests/libse/Common/RegexUtilsTest.cs index f0329070ada..f81a3587cb5 100644 --- a/tests/libse/Common/RegexUtilsTest.cs +++ b/tests/libse/Common/RegexUtilsTest.cs @@ -1,4 +1,4 @@ -using Nikse.SubtitleEdit.Core.Common; +using Nikse.SubtitleEdit.Core.Common; using Nikse.SubtitleEdit.Core.SubtitleFormats; using System.Text.RegularExpressions; using Xunit; @@ -118,4 +118,38 @@ public void BuildWholeWordPattern_Matches(string searchText, string text, bool e { Assert.Equal(expected, Regex.IsMatch(text, RegexUtils.BuildWholeWordPattern(searchText))); } + + // #16033: Perl-style case conversion escapes in the replacement - \u/\l change the next + // character, \U/\L change everything up to \E (or the end). + [Theory] + [InlineData(@"^Um,\s*(\w)", "Um, so I went home.", @"\u$1", "So I went home.")] + [InlineData(@"(\w+) (\w+)", "hello world", @"\U$1\E $2", "HELLO world")] + [InlineData(@"(\w+) (\w+)", "HELLO WORLD", @"\L$1 $2", "hello world")] + [InlineData(@"(\w+)", "HELLO", @"\l$1", "hELLO")] + [InlineData(@"(\w+)", "wORD", @"\u\L$1", "Word")] + [InlineData(@"(\w+)", "word", @"x\Uy$1\Ez", "xYWORDz")] + [InlineData(@"(\w+)", "word", @"\\u$1", @"\uword")] + [InlineData(@"(\w+)", "word", @"a\b$1", @"a\bword")] + [InlineData(@"(?\w+)", "word", @"\U${w}", "WORD")] + public void ReplaceNewLineSafe_CaseConversion(string pattern, string text, string replaceWith, string expected) + { + var regex = new Regex(pattern, RegexOptions.Multiline); + Assert.Equal(expected, RegexUtils.ReplaceNewLineSafe(regex, text, replaceWith)); + } + + [Fact] + public void ReplaceNewLineSafe_CaseConversion_EachLineOfTwoLineText() + { + var regex = new Regex(RegexUtils.FixNewLine(@"^(?:Um|Uh),\s*(\w)"), RegexOptions.Multiline); + var result = RegexUtils.ReplaceNewLineSafe(regex, "Um, so I went home.\r\nUh, and then?", @"\u$1"); + Assert.Equal("So I went home." + Environment.NewLine + "And then?", result); + } + + [Fact] + public void ReplaceNewLineSafe_CaseConversion_HonoursCount() + { + var regex = new Regex(@"\b(\w)"); + var result = RegexUtils.ReplaceNewLineSafe(regex, "one two three", @"\u$1", 1, 0); + Assert.Equal("One two three", result); + } } From d9d4ce14c5f79dbd2837e092e932d95721231cf0 Mon Sep 17 00:00:00 2001 From: niksedk Date: Thu, 15 Oct 2026 14:36:24 +0200 Subject: [PATCH 183/306] Burn-in in the editor look; shared EditorChrome - Moves the editor palette and helpers (theme-aware brushes, tool buttons, compact fields, section headers, menus) out of AssaDrawWindow into Logic/EditorChrome, so windows outside ASSA can use them - Burn-in: style column (subtitle style, alignment grid + margins, effect, logo) on the left, preview with the cut range on the right, video output / audio / target size sections under it; Single/Batch switch, progress and an accent Generate at the bottom. In batch mode the job list takes the place of the preview. Follows the app theme. - Burn-in window tests rewritten for the new layout (short-screen reachability, progress visibility, batch swap, alignment grid) Co-Authored-By: Claude Opus 5.5 --- .../AssaApplyCustomOverrideTagsWindow.cs | 39 +- .../AssaTagHistoryWindow.cs | 19 +- src/ui/Features/Assa/AssaAttachmentsWindow.cs | 41 +- .../Features/Assa/AssaDraw/AssaDrawCanvas.cs | 4 +- .../Features/Assa/AssaDraw/AssaDrawWindow.cs | 382 +--- .../AssaImageColorPickerWindow.cs | 33 +- .../AssaProgressBar/AssaProgressBarWindow.cs | 78 +- src/ui/Features/Assa/AssaPropertiesWindow.cs | 35 +- .../AssaResolutionResamplerWindow.cs | 39 +- .../AssSetBackgroundWindow.cs | 54 +- .../AssaSetPosition/AssaSetPositionWindow.cs | 61 +- src/ui/Features/Assa/AssaStylePickerWindow.cs | 17 +- src/ui/Features/Assa/AssaStylesWindow.cs | 109 +- .../Assa/FontCollector/FontCollectorWindow.cs | 31 +- src/ui/Features/Ssa/SsaAttachmentsWindow.cs | 39 +- src/ui/Features/Ssa/SsaPropertiesWindow.cs | 37 +- src/ui/Features/Ssa/SsaStylesWindow.cs | 95 +- src/ui/Features/Video/BurnIn/BurnInWindow.cs | 1623 +++++++---------- .../WebVtt/WebVttStylePickerWindow.cs | 21 +- src/ui/Features/WebVtt/WebVttStylesWindow.cs | 69 +- .../Config/Language/Video/LanguageBurnIn.cs | 6 + src/ui/Logic/EditorChrome.cs | 404 ++++ tests/UI/Features/Video/BurnInWindowTests.cs | 313 +--- 23 files changed, 1538 insertions(+), 2011 deletions(-) create mode 100644 src/ui/Logic/EditorChrome.cs diff --git a/src/ui/Features/Assa/AssaApplyCustomOverrideTags/AssaApplyCustomOverrideTagsWindow.cs b/src/ui/Features/Assa/AssaApplyCustomOverrideTags/AssaApplyCustomOverrideTagsWindow.cs index edb073d7540..16c4033478f 100644 --- a/src/ui/Features/Assa/AssaApplyCustomOverrideTags/AssaApplyCustomOverrideTagsWindow.cs +++ b/src/ui/Features/Assa/AssaApplyCustomOverrideTags/AssaApplyCustomOverrideTagsWindow.cs @@ -9,7 +9,6 @@ using Avalonia.Layout; using Avalonia.Media; using Avalonia.Styling; -using Nikse.SubtitleEdit.Features.Assa.AssaDraw; using Nikse.SubtitleEdit.Features.Main.Layout; using Nikse.SubtitleEdit.Features.Sync.VisualSync; using Nikse.SubtitleEdit.Logic; @@ -34,8 +33,8 @@ public AssaApplyCustomOverrideTagsWindow(AssaApplyCustomOverrideTagsViewModel vm Height = 780; MinWidth = 950; MinHeight = 600; - Background = AssaDrawWindow.WindowBrush; - AssaDrawWindow.AddEditorStyles(Styles); + Background = EditorChrome.WindowBrush; + EditorChrome.AddEditorStyles(Styles); vm.Window = this; DataContext = vm; @@ -89,8 +88,8 @@ private static Border MakeSettingsPanel(AssaApplyCustomOverrideTagsViewModel vm, return new Border { Child = grid, - Background = AssaDrawWindow.PanelBrush, - BorderBrush = AssaDrawWindow.LineBrush, + Background = EditorChrome.PanelBrush, + BorderBrush = EditorChrome.LineBrush, BorderThickness = new Thickness(0, 0, 1, 0), }; } @@ -110,13 +109,13 @@ private static Control MakePresetsSection(AssaApplyCustomOverrideTagsViewModel v return new Panel(); } - var use = AssaDrawWindow.CreateToolButton("fa-solid fa-check", Se.Language.General.Use + ": " + preset.Name, null, 24); + var use = EditorChrome.CreateToolButton("fa-solid fa-check", Se.Language.General.Use + ": " + preset.Name, null, 24); use.Click += (_, _) => { vm.SelectedOverrideTag = preset; vm.UseCommand.Execute(null); }; - var append = AssaDrawWindow.CreateToolButton("fa-solid fa-plus", Se.Language.General.Append + ": " + preset.Name, null, 24); + var append = EditorChrome.CreateToolButton("fa-solid fa-plus", Se.Language.General.Append + ": " + preset.Name, null, 24); append.Click += (_, _) => { vm.SelectedOverrideTag = preset; @@ -130,7 +129,7 @@ private static Control MakePresetsSection(AssaApplyCustomOverrideTagsViewModel v Text = preset.Tag, FontFamily = MonoFont, FontSize = 11, - Foreground = AssaDrawWindow.FaintTextBrush, + Foreground = EditorChrome.FaintTextBrush, TextTrimming = TextTrimming.CharacterEllipsis, }); @@ -166,14 +165,14 @@ private static Control MakePresetsSection(AssaApplyCustomOverrideTagsViewModel v } }; border.Cursor = new Cursor(StandardCursorType.Hand); - border.PointerEntered += (_, _) => border.Background = AssaDrawWindow.HoverBrush; + border.PointerEntered += (_, _) => border.Background = EditorChrome.HoverBrush; border.PointerExited += (_, _) => border.Background = Brushes.Transparent; return border; }), }; var header = new DockPanel { Margin = new Thickness(14, 10, 10, 6) }; - header.Children.Add(AssaDrawWindow.MakeSectionHeader(Se.Language.Assa.ChooseOverrideTagToAdd)); + header.Children.Add(EditorChrome.MakeSectionHeader(Se.Language.Assa.ChooseOverrideTagToAdd)); var grid = new Grid { @@ -199,8 +198,8 @@ private static Border MakeTagsSection(AssaApplyCustomOverrideTagsViewModel vm, o MaxHeight = 140, FontFamily = MonoFont, FontSize = 12.5, - Background = AssaDrawWindow.FieldBrush, - BorderBrush = AssaDrawWindow.FieldBorderBrush, + Background = EditorChrome.FieldBrush, + BorderBrush = EditorChrome.FieldBorderBrush, CornerRadius = new CornerRadius(6), [!TextBox.TextProperty] = new Binding(nameof(vm.CurrentTag)) { Mode = BindingMode.TwoWay }, }; @@ -208,12 +207,12 @@ private static Border MakeTagsSection(AssaApplyCustomOverrideTagsViewModel vm, o firstInput = textBox; var actions = new StackPanel { Orientation = Orientation.Horizontal, Spacing = 2 }; - actions.Children.Add(AssaDrawWindow.CreateToolButton("fa-solid fa-eraser", Se.Language.General.Clear, vm.ClearCommand, 24)); - var history = AssaDrawWindow.CreateToolButton("fa-solid fa-clock-rotate-left", Se.Language.General.History, vm.ShowHistoryCommand, 24); + actions.Children.Add(EditorChrome.CreateToolButton("fa-solid fa-eraser", Se.Language.General.Clear, vm.ClearCommand, 24)); + var history = EditorChrome.CreateToolButton("fa-solid fa-clock-rotate-left", Se.Language.General.History, vm.ShowHistoryCommand, 24); history.Bind(IsVisibleProperty, new Binding(nameof(vm.IsHistoryVisible))); actions.Children.Add(history); - return AssaDrawWindow.MakeEditorSection(Se.Language.Assa.ApplyOverrideTags, textBox, actions); + return EditorChrome.MakeEditorSection(Se.Language.Assa.ApplyOverrideTags, textBox, actions); } private static Border MakeApplyToSection(AssaApplyCustomOverrideTagsViewModel vm) @@ -232,12 +231,12 @@ private static Border MakeApplyToSection(AssaApplyCustomOverrideTagsViewModel vm content.Children.Add(new TextBlock { FontSize = 11.5, - Foreground = AssaDrawWindow.FaintTextBrush, + Foreground = EditorChrome.FaintTextBrush, Margin = new Thickness(0, 4, 0, 0), TextWrapping = TextWrapping.Wrap, [!TextBlock.TextProperty] = new Binding(nameof(vm.SelectionInfo)), }); - return AssaDrawWindow.MakeEditorSection(Se.Language.General.Lines, content); + return EditorChrome.MakeEditorSection(Se.Language.General.Lines, content); } private static Control MakePreview(AssaApplyCustomOverrideTagsViewModel vm) @@ -253,7 +252,7 @@ private static Control MakePreview(AssaApplyCustomOverrideTagsViewModel vm) vm.ComboBoxLeft = comboBoxLeft; comboBoxLeft.SelectionChanged += vm.ComboBoxParagraphsChanged; - var play = AssaDrawWindow.CreateToolButton("fa-solid fa-play", Se.Language.General.PlayCurrent, vm.PlayAndBackCommand, 40); + var play = EditorChrome.CreateToolButton("fa-solid fa-play", Se.Language.General.PlayCurrent, vm.PlayAndBackCommand, 40); var lineRow = new Grid { @@ -287,8 +286,8 @@ private static Border MakeButtonBar(AssaApplyCustomOverrideTagsViewModel vm) return new Border { Child = buttons, - Background = AssaDrawWindow.ButtonBarBrush, - BorderBrush = AssaDrawWindow.LineBrush, + Background = EditorChrome.ButtonBarBrush, + BorderBrush = EditorChrome.LineBrush, BorderThickness = new Thickness(0, 1, 0, 0), Padding = new Thickness(16, 10), }; diff --git a/src/ui/Features/Assa/AssaApplyCustomOverrideTags/AssaTagHistoryWindow.cs b/src/ui/Features/Assa/AssaApplyCustomOverrideTags/AssaTagHistoryWindow.cs index 7a0e0409657..63aa53cdd36 100644 --- a/src/ui/Features/Assa/AssaApplyCustomOverrideTags/AssaTagHistoryWindow.cs +++ b/src/ui/Features/Assa/AssaApplyCustomOverrideTags/AssaTagHistoryWindow.cs @@ -5,7 +5,6 @@ using Avalonia.Data; using Avalonia.Layout; using Avalonia.Media; -using Nikse.SubtitleEdit.Features.Assa.AssaDraw; using Nikse.SubtitleEdit.Logic; using Nikse.SubtitleEdit.Logic.Config; @@ -25,8 +24,8 @@ public AssaTagHistoryWindow(AssaTagHistoryViewModel vm) CanResize = false; Width = 520; SizeToContent = SizeToContent.Height; - Background = AssaDrawWindow.WindowBrush; - AssaDrawWindow.AddEditorStyles(Styles); + Background = EditorChrome.WindowBrush; + EditorChrome.AddEditorStyles(Styles); vm.Window = this; DataContext = vm; @@ -36,8 +35,8 @@ public AssaTagHistoryWindow(AssaTagHistoryViewModel vm) [!ListBox.ItemsSourceProperty] = new Binding(nameof(vm.OverrideTags)) { Mode = BindingMode.OneWay }, [!ListBox.SelectedItemProperty] = new Binding(nameof(vm.SelectedOverrideTag)) { Mode = BindingMode.TwoWay }, Height = 300, - Background = AssaDrawWindow.FieldBrush, - BorderBrush = AssaDrawWindow.FieldBorderBrush, + Background = EditorChrome.FieldBrush, + BorderBrush = EditorChrome.FieldBorderBrush, BorderThickness = new Thickness(1), CornerRadius = new CornerRadius(8), Padding = new Thickness(4), @@ -52,9 +51,9 @@ public AssaTagHistoryWindow(AssaTagHistoryViewModel vm) AutomationProperties.SetName(listBox, Se.Language.Assa.OverrideTagsHistory); listBox.DoubleTapped += (_, _) => vm.OkCommand.Execute(null); - var delete = AssaDrawWindow.CreateToolButton("fa-solid fa-trash", Se.Language.General.Delete, vm.DeleteItemCommand, 24); - var section = AssaDrawWindow.MakeEditorSection(Se.Language.General.History, listBox, delete, first: true); - section.Background = AssaDrawWindow.PanelBrush; + var delete = EditorChrome.CreateToolButton("fa-solid fa-trash", Se.Language.General.Delete, vm.DeleteItemCommand, 24); + var section = EditorChrome.MakeEditorSection(Se.Language.General.History, listBox, delete, first: true); + section.Background = EditorChrome.PanelBrush; var root = new DockPanel(); var buttonBar = MakeButtonBar(vm); @@ -80,8 +79,8 @@ private static Border MakeButtonBar(AssaTagHistoryViewModel vm) return new Border { Child = buttons, - Background = AssaDrawWindow.ButtonBarBrush, - BorderBrush = AssaDrawWindow.LineBrush, + Background = EditorChrome.ButtonBarBrush, + BorderBrush = EditorChrome.LineBrush, BorderThickness = new Thickness(0, 1, 0, 0), Padding = new Thickness(16, 10), }; diff --git a/src/ui/Features/Assa/AssaAttachmentsWindow.cs b/src/ui/Features/Assa/AssaAttachmentsWindow.cs index 3988bb0d701..fb075e418db 100644 --- a/src/ui/Features/Assa/AssaAttachmentsWindow.cs +++ b/src/ui/Features/Assa/AssaAttachmentsWindow.cs @@ -8,7 +8,6 @@ using Avalonia.Media; using System.Collections; using Avalonia.Styling; -using Nikse.SubtitleEdit.Features.Assa.AssaDraw; using Nikse.SubtitleEdit.Logic; using Nikse.SubtitleEdit.Logic.Config; @@ -33,8 +32,8 @@ public AssaAttachmentsWindow(AssaAttachmentsViewModel vm) Height = 820; MinWidth = 900; MinHeight = 550; - Background = AssaDrawWindow.WindowBrush; - AssaDrawWindow.AddEditorStyles(Styles); + Background = EditorChrome.WindowBrush; + EditorChrome.AddEditorStyles(Styles); vm.Window = this; DataContext = vm; @@ -105,14 +104,14 @@ private static Border MakeToolbar(AssaAttachmentsViewModel vm) panel.Children.Add(MakeAction("fa-solid fa-paperclip", Se.Language.General.AttachDotDotDot, vm.FileAttachCommand)); panel.Children.Add(MakeAction("fa-solid fa-file-import", Se.Language.General.ImportDotDotDot, vm.FileImportCommand)); panel.Children.Add(MakeAction("fa-solid fa-file-export", Se.Language.General.ExportDotDotDot, vm.FileExportCommand)); - panel.Children.Add(AssaDrawWindow.MakeToolbarSeparator()); + panel.Children.Add(EditorChrome.MakeToolbarSeparator()); panel.Children.Add(MakeAction("fa-solid fa-scissors", Se.Language.Assa.TrimFontsDotDotDot, vm.TrimFontsToUsedCharactersCommand)); return new Border { Child = panel, - Background = AssaDrawWindow.PanelBrush, - BorderBrush = AssaDrawWindow.LineBrush, + Background = EditorChrome.PanelBrush, + BorderBrush = EditorChrome.LineBrush, BorderThickness = new Thickness(0, 0, 0, 1), Padding = new Thickness(10, 6), }; @@ -123,7 +122,7 @@ private static Border MakeButtonBar(AssaAttachmentsViewModel vm) var summary = new TextBlock { FontSize = 12, - Foreground = AssaDrawWindow.DimTextBrush, + Foreground = EditorChrome.DimTextBrush, VerticalAlignment = VerticalAlignment.Center, [!TextBlock.TextProperty] = new Binding(nameof(vm.SummaryText)), }; @@ -148,8 +147,8 @@ private static Border MakeButtonBar(AssaAttachmentsViewModel vm) return new Border { Child = grid, - Background = AssaDrawWindow.ButtonBarBrush, - BorderBrush = AssaDrawWindow.LineBrush, + Background = EditorChrome.ButtonBarBrush, + BorderBrush = EditorChrome.LineBrush, BorderThickness = new Thickness(0, 1, 0, 0), Padding = new Thickness(16, 10), }; @@ -241,19 +240,19 @@ private static Border MakeLeftView(AssaAttachmentsViewModel vm, out TableView ta // Header: title with move/delete actions; the list; a hint about dropping files var actions = new StackPanel { Orientation = Orientation.Horizontal, Spacing = 2 }; - actions.Children.Add(AssaDrawWindow.CreateToolButton("fa-solid fa-arrow-up", Se.Language.General.MoveUp, vm.MoveUpCommand, 24)); - actions.Children.Add(AssaDrawWindow.CreateToolButton("fa-solid fa-arrow-down", Se.Language.General.MoveDown, vm.MoveDownCommand, 24)); - actions.Children.Add(AssaDrawWindow.CreateToolButton("fa-solid fa-trash", Se.Language.General.Delete, vm.AttachmentRemoveCommand, 24)); + actions.Children.Add(EditorChrome.CreateToolButton("fa-solid fa-arrow-up", Se.Language.General.MoveUp, vm.MoveUpCommand, 24)); + actions.Children.Add(EditorChrome.CreateToolButton("fa-solid fa-arrow-down", Se.Language.General.MoveDown, vm.MoveDownCommand, 24)); + actions.Children.Add(EditorChrome.CreateToolButton("fa-solid fa-trash", Se.Language.General.Delete, vm.AttachmentRemoveCommand, 24)); var header = new DockPanel { Margin = new Thickness(14, 10, 10, 8) }; DockPanel.SetDock(actions, Dock.Right); header.Children.Add(actions); - header.Children.Add(AssaDrawWindow.MakeSectionHeader(Se.Language.Assa.FontsAndGraphics)); + header.Children.Add(EditorChrome.MakeSectionHeader(Se.Language.Assa.FontsAndGraphics)); var hint = new TextBlock { Text = Se.Language.Assa.AttachmentsDropHint, FontSize = 11.5, - Foreground = AssaDrawWindow.FaintTextBrush, + Foreground = EditorChrome.FaintTextBrush, Margin = new Thickness(14, 8, 14, 10), TextWrapping = TextWrapping.Wrap, }; @@ -266,8 +265,8 @@ private static Border MakeLeftView(AssaAttachmentsViewModel vm, out TableView ta return new Border { Child = panel, - Background = AssaDrawWindow.PanelBrush, - BorderBrush = AssaDrawWindow.LineBrush, + Background = EditorChrome.PanelBrush, + BorderBrush = EditorChrome.LineBrush, BorderThickness = new Thickness(0, 0, 1, 0), }; } @@ -277,7 +276,7 @@ private static Control MakeRightView(AssaAttachmentsViewModel vm) var title = new TextBlock { FontSize = 12.5, - Foreground = AssaDrawWindow.DimTextBrush, + Foreground = EditorChrome.DimTextBrush, VerticalAlignment = VerticalAlignment.Center, TextTrimming = TextTrimming.CharacterEllipsis, [!TextBlock.TextProperty] = new Binding(nameof(vm.PreviewTitle)), @@ -290,8 +289,8 @@ private static Control MakeRightView(AssaAttachmentsViewModel vm) Spacing = 2, [!StackPanel.IsVisibleProperty] = new Binding(nameof(vm.IsCopyFontnameToClipboardVisible)), }; - fontActions.Children.Add(AssaDrawWindow.CreateToolButton("fa-solid fa-copy", Se.Language.General.CopyToClipboard, vm.CopyFontNameToClipboardCommand, 28)); - fontActions.Children.Add(AssaDrawWindow.CreateToolButton("fa-solid fa-font", Se.Language.Assa.FontCollectorCopyFontsToSeFontsFolder, vm.CopyToSeFontsFolderCommand, 28)); + fontActions.Children.Add(EditorChrome.CreateToolButton("fa-solid fa-copy", Se.Language.General.CopyToClipboard, vm.CopyFontNameToClipboardCommand, 28)); + fontActions.Children.Add(EditorChrome.CreateToolButton("fa-solid fa-font", Se.Language.Assa.FontCollectorCopyFontsToSeFontsFolder, vm.CopyToSeFontsFolderCommand, 28)); var header = new DockPanel { Margin = new Thickness(0, 0, 0, 10) }; DockPanel.SetDock(fontActions, Dock.Right); @@ -310,8 +309,8 @@ private static Control MakeRightView(AssaAttachmentsViewModel vm) var stage = new Border { Child = image, - Background = AssaDrawWindow.FieldBrush, - BorderBrush = AssaDrawWindow.FieldBorderBrush, + Background = EditorChrome.FieldBrush, + BorderBrush = EditorChrome.FieldBorderBrush, BorderThickness = new Thickness(1), CornerRadius = new CornerRadius(8), Padding = new Thickness(16), diff --git a/src/ui/Features/Assa/AssaDraw/AssaDrawCanvas.cs b/src/ui/Features/Assa/AssaDraw/AssaDrawCanvas.cs index f5aca0f2cdd..6daaac637ba 100644 --- a/src/ui/Features/Assa/AssaDraw/AssaDrawCanvas.cs +++ b/src/ui/Features/Assa/AssaDraw/AssaDrawCanvas.cs @@ -7,6 +7,8 @@ using Avalonia.Media; using Avalonia.Media.Immutable; +using Nikse.SubtitleEdit.Logic; + namespace Nikse.SubtitleEdit.Features.Assa.AssaDraw; /// @@ -67,7 +69,7 @@ public class AssaDrawCanvas : Control private static readonly Color AccentColor = Color.FromRgb(76, 110, 245); private static readonly Color HandleFillColor = Colors.White; - private static bool IsLight => AssaDrawWindow.IsLightTheme; + private static bool IsLight => EditorChrome.IsLightTheme; private static IBrush WorkspaceBrush => IsLight ? LightWorkspaceBrush : DarkWorkspaceBrush; private static IBrush CheckerBrush => IsLight ? LightCheckerBrush : DarkCheckerBrush; private static Color CanvasColor => IsLight ? LightCanvasColor : DrawSettings.BackgroundColor; diff --git a/src/ui/Features/Assa/AssaDraw/AssaDrawWindow.cs b/src/ui/Features/Assa/AssaDraw/AssaDrawWindow.cs index 81656d7fe17..7a2f402a905 100644 --- a/src/ui/Features/Assa/AssaDraw/AssaDrawWindow.cs +++ b/src/ui/Features/Assa/AssaDraw/AssaDrawWindow.cs @@ -20,6 +20,7 @@ using Avalonia.Styling; using Nikse.SubtitleEdit.Logic; using Nikse.SubtitleEdit.Logic.Config; +using static Nikse.SubtitleEdit.Logic.EditorChrome; namespace Nikse.SubtitleEdit.Features.Assa.AssaDraw; @@ -30,80 +31,6 @@ namespace Nikse.SubtitleEdit.Features.Assa.AssaDraw; /// public class AssaDrawWindow : Window { - // The editor palette, in a dark and a light version: these windows follow the app theme - // (picked when a window is built). Accent colors are the same in both. - internal static bool IsLightTheme => Application.Current?.ActualThemeVariant == ThemeVariant.Light; - - /// - /// Text and icons on hover - white on the dark palette, near black on the light one. - /// - internal static IBrush StrongTextBrush => IsLightTheme ? Brushes.Black : Brushes.White; - - /// - /// Shape thumbnails (shape library, box and bar styles): light gray on dark, dark gray on light. - /// - internal static IBrush ThumbnailBrush => IsLightTheme ? LightThumbnail : DarkThumbnail; - internal static IBrush ThumbnailMutedBrush => IsLightTheme ? LightThumbnailMuted : DarkThumbnailMuted; - private static readonly IBrush DarkThumbnail = new ImmutableSolidColorBrush(Color.FromRgb(214, 218, 226)); - private static readonly IBrush LightThumbnail = new ImmutableSolidColorBrush(Color.FromRgb(72, 78, 92)); - private static readonly IBrush DarkThumbnailMuted = new ImmutableSolidColorBrush(Color.FromRgb(96, 102, 116)); - private static readonly IBrush LightThumbnailMuted = new ImmutableSolidColorBrush(Color.FromRgb(188, 193, 204)); - - private static readonly IBrush DarkWindowBrush = new ImmutableSolidColorBrush(Color.FromRgb(30, 31, 36)); - private static readonly IBrush LightWindowBrush = new ImmutableSolidColorBrush(Color.FromRgb(243, 244, 247)); - internal static IBrush WindowBrush => IsLightTheme ? LightWindowBrush : DarkWindowBrush; - private static readonly IBrush DarkPanelBrush = new ImmutableSolidColorBrush(Color.FromRgb(36, 38, 44)); - private static readonly IBrush LightPanelBrush = new ImmutableSolidColorBrush(Color.FromRgb(250, 250, 252)); - internal static IBrush PanelBrush => IsLightTheme ? LightPanelBrush : DarkPanelBrush; - private static readonly IBrush DarkStatusBrush = new ImmutableSolidColorBrush(Color.FromRgb(27, 28, 33)); - private static readonly IBrush LightStatusBrush = new ImmutableSolidColorBrush(Color.FromRgb(236, 238, 242)); - internal static IBrush StatusBrush => IsLightTheme ? LightStatusBrush : DarkStatusBrush; - private static readonly IBrush DarkButtonBarBrush = new ImmutableSolidColorBrush(Color.FromRgb(32, 34, 39)); - private static readonly IBrush LightButtonBarBrush = new ImmutableSolidColorBrush(Color.FromRgb(238, 240, 244)); - internal static IBrush ButtonBarBrush => IsLightTheme ? LightButtonBarBrush : DarkButtonBarBrush; - private static readonly IBrush DarkLineBrush = new ImmutableSolidColorBrush(Color.FromRgb(47, 50, 58)); - private static readonly IBrush LightLineBrush = new ImmutableSolidColorBrush(Color.FromRgb(222, 225, 232)); - internal static IBrush LineBrush => IsLightTheme ? LightLineBrush : DarkLineBrush; - private static readonly IBrush DarkSeparatorBrush = new ImmutableSolidColorBrush(Color.FromRgb(55, 58, 67)); - private static readonly IBrush LightSeparatorBrush = new ImmutableSolidColorBrush(Color.FromRgb(208, 212, 220)); - internal static IBrush SeparatorBrush => IsLightTheme ? LightSeparatorBrush : DarkSeparatorBrush; - private static readonly IBrush DarkDimTextBrush = new ImmutableSolidColorBrush(Color.FromRgb(140, 146, 160)); - private static readonly IBrush LightDimTextBrush = new ImmutableSolidColorBrush(Color.FromRgb(92, 98, 112)); - internal static IBrush DimTextBrush => IsLightTheme ? LightDimTextBrush : DarkDimTextBrush; - private static readonly IBrush DarkFaintTextBrush = new ImmutableSolidColorBrush(Color.FromRgb(111, 117, 131)); - private static readonly IBrush LightFaintTextBrush = new ImmutableSolidColorBrush(Color.FromRgb(122, 128, 142)); - internal static IBrush FaintTextBrush => IsLightTheme ? LightFaintTextBrush : DarkFaintTextBrush; - private static readonly IBrush DarkIconBrush = new ImmutableSolidColorBrush(Color.FromRgb(174, 180, 194)); - private static readonly IBrush LightIconBrush = new ImmutableSolidColorBrush(Color.FromRgb(70, 76, 90)); - internal static IBrush IconBrush => IsLightTheme ? LightIconBrush : DarkIconBrush; - private static readonly IBrush DarkHoverBrush = new ImmutableSolidColorBrush(Color.FromRgb(51, 54, 63)); - private static readonly IBrush LightHoverBrush = new ImmutableSolidColorBrush(Color.FromRgb(226, 229, 236)); - internal static IBrush HoverBrush => IsLightTheme ? LightHoverBrush : DarkHoverBrush; - private static readonly IBrush DarkPressedBrush = new ImmutableSolidColorBrush(Color.FromRgb(59, 62, 71)); - private static readonly IBrush LightPressedBrush = new ImmutableSolidColorBrush(Color.FromRgb(214, 218, 227)); - internal static IBrush PressedBrush => IsLightTheme ? LightPressedBrush : DarkPressedBrush; - private static readonly IBrush DarkAccentBrush = new ImmutableSolidColorBrush(Color.FromRgb(76, 110, 245)); - private static readonly IBrush LightAccentBrush = new ImmutableSolidColorBrush(Color.FromRgb(76, 110, 245)); - internal static IBrush AccentBrush => IsLightTheme ? LightAccentBrush : DarkAccentBrush; - private static readonly IBrush DarkAccentHoverBrush = new ImmutableSolidColorBrush(Color.FromRgb(92, 124, 250)); - private static readonly IBrush LightAccentHoverBrush = new ImmutableSolidColorBrush(Color.FromRgb(92, 124, 250)); - internal static IBrush AccentHoverBrush => IsLightTheme ? LightAccentHoverBrush : DarkAccentHoverBrush; - private static readonly IBrush DarkAccentSoftBrush = new ImmutableSolidColorBrush(Color.FromArgb(70, 76, 110, 245)); - private static readonly IBrush LightAccentSoftBrush = new ImmutableSolidColorBrush(Color.FromArgb(46, 76, 110, 245)); - internal static IBrush AccentSoftBrush => IsLightTheme ? LightAccentSoftBrush : DarkAccentSoftBrush; - private static readonly IBrush DarkFieldBrush = new ImmutableSolidColorBrush(Color.FromRgb(27, 28, 33)); - private static readonly IBrush LightFieldBrush = new ImmutableSolidColorBrush(Color.FromRgb(255, 255, 255)); - internal static IBrush FieldBrush => IsLightTheme ? LightFieldBrush : DarkFieldBrush; - private static readonly IBrush DarkFieldBorderBrush = new ImmutableSolidColorBrush(Color.FromRgb(54, 57, 68)); - private static readonly IBrush LightFieldBorderBrush = new ImmutableSolidColorBrush(Color.FromRgb(204, 209, 219)); - internal static IBrush FieldBorderBrush => IsLightTheme ? LightFieldBorderBrush : DarkFieldBorderBrush; - private static readonly IBrush DarkMenuBrush = new ImmutableSolidColorBrush(Color.FromRgb(42, 44, 51)); - private static readonly IBrush LightMenuBrush = new ImmutableSolidColorBrush(Color.FromRgb(255, 255, 255)); - internal static IBrush MenuBrush => IsLightTheme ? LightMenuBrush : DarkMenuBrush; - private static readonly IBrush DarkMenuBorderBrush = new ImmutableSolidColorBrush(Color.FromRgb(66, 69, 79)); - private static readonly IBrush LightMenuBorderBrush = new ImmutableSolidColorBrush(Color.FromRgb(208, 212, 220)); - internal static IBrush MenuBorderBrush => IsLightTheme ? LightMenuBorderBrush : DarkMenuBorderBrush; - private readonly AssaDrawViewModel _vm; private readonly AssaDrawCanvas _canvas; private readonly ContextMenu _canvasMenu; @@ -193,123 +120,6 @@ private void OnLoaded(object? sender, global::Avalonia.Interactivity.RoutedEvent _vm.Initialize(); } - /// - /// Flat icon buttons with an accent "active" state, compact number fields and accent menus. - /// - internal static void AddEditorStyles(Styles styles) - { - static Style ToolPresenter(Func button, params (AvaloniaProperty Property, object Value)[] setters) - { - var style = new Style(x => button(x).Template().OfType().Name("PART_ContentPresenter")); - foreach (var (property, value) in setters) - { - style.Setters.Add(new Setter(property, value)); - } - - return style; - } - - styles.Add(new Style(x => x.OfType - public static StackPanel MakeProgressView() + public static Border MakeProgressView() { var progressBar = UiUtil.MakeProgressBar(); progressBar.Bind(ProgressBar.ValueProperty, new Binding("ProgressValue")); + return VideoSubDialogChrome.MakeProgressStrip(progressBar, "ProgressText", "IsGenerating"); + } - var statusText = new TextBlock { Margin = new Thickness(2, 0, 0, 0) }; - statusText.Bind(TextBlock.TextProperty, new Binding("ProgressText")); + /// + /// The track list: a header with the actions, the table in a field frame under it. + /// + public static Grid MakeTracksPanel(TableView tracksTable, Commands commands, string? addEnabledPath) + { + var header = new DockPanel { Margin = new Thickness(0, 0, 0, 8), MinHeight = 26 }; + var actions = MakeButtons(commands, addEnabledPath); + DockPanel.SetDock(actions, Dock.Right); + header.Children.Add(actions); + header.Children.Add(EditorChrome.MakeSectionHeader(Se.Language.Video.EmbeddedTracks)); - var panel = new StackPanel + var panel = new Grid { - Spacing = 4, - Children = { progressBar, statusText }, + RowDefinitions = + { + new RowDefinition(GridLength.Auto), + new RowDefinition(new GridLength(1, GridUnitType.Star)), + }, + Margin = new Thickness(14, 12, 14, 14), }; - panel.Bind(Visual.IsVisibleProperty, new Binding("IsGenerating")); + panel.Add(header, 0); + panel.Add(VideoSubDialogChrome.MakeListFrame(MakeTracksWithEmptyHint(tracksTable)), 1); return panel; } - private static StackPanel MakeIconText(string iconName, string text) + /// + /// The video file row on the panel color: icon and label, the (read-only) file name, its size + /// and browse. + /// + public static Border MakeVideoFileBar(TextBox textBoxVideoFileName, Button buttonBrowse) { - var icon = new ContentControl(); - Attached.SetIcon(icon, iconName); - return new StackPanel + var icon = new Icon { - Orientation = Orientation.Horizontal, - Children = { icon, new TextBlock { Text = text, Padding = new Thickness(4, 0, 0, 0) } }, + Value = "fa-solid fa-file-video", + FontSize = 13, + Foreground = EditorChrome.IconBrush, + VerticalAlignment = VerticalAlignment.Center, + }; + var label = EditorChrome.MakeDimLabel(Se.Language.General.VideoFile, new Thickness(0)); + var fileSize = VideoSubDialogChrome.MakeHint("VideoFileSize"); + + var grid = new Grid + { + ColumnDefinitions = + { + new ColumnDefinition(GridLength.Auto), // icon + new ColumnDefinition(GridLength.Auto), // label + new ColumnDefinition(new GridLength(1, GridUnitType.Star)), // file name + new ColumnDefinition(GridLength.Auto), // file size + new ColumnDefinition(GridLength.Auto), // browse + }, + ColumnSpacing = 8, + }; + grid.Add(icon, 0, 0); + grid.Add(label, 0, 1); + grid.Add(textBoxVideoFileName, 0, 2); + grid.Add(fileSize, 0, 3); + grid.Add(buttonBrowse, 0, 4); + + return new Border + { + Child = grid, + Background = EditorChrome.PanelBrush, + BorderBrush = EditorChrome.LineBrush, + BorderThickness = new Thickness(0, 0, 0, 1), + Padding = new Thickness(14, 10), }; } diff --git a/src/ui/Features/Video/ReEncodeVideo/ReEncodeVideoWindow.cs b/src/ui/Features/Video/ReEncodeVideo/ReEncodeVideoWindow.cs index eecff430c68..d92152ee6af 100644 --- a/src/ui/Features/Video/ReEncodeVideo/ReEncodeVideoWindow.cs +++ b/src/ui/Features/Video/ReEncodeVideo/ReEncodeVideoWindow.cs @@ -1,10 +1,10 @@ using Avalonia; +using Avalonia.Automation; using Avalonia.Controls; using Avalonia.Controls.Primitives; using Avalonia.Data; using Avalonia.Input; using Avalonia.Layout; -using Avalonia.Styling; using Nikse.SubtitleEdit.Controls; using Nikse.SubtitleEdit.Logic; using Nikse.SubtitleEdit.Logic.Config; @@ -12,11 +12,17 @@ namespace Nikse.SubtitleEdit.Features.Video.ReEncodeVideo; +/// +/// Re-encode video for better subtitling, in the editor look (see ): +/// the info and the video settings in a section, progress and Generate at the bottom. +/// public class ReEncodeVideoWindow : Window { private readonly ReEncodeVideoViewModel _vm; private ComboBox? _comboBoxFrameRate; + private const double LabelWidth = 130; + public ReEncodeVideoWindow(ReEncodeVideoViewModel vm) { UiUtil.InitializeWindow(this, GetType().Name); @@ -24,15 +30,25 @@ public ReEncodeVideoWindow(ReEncodeVideoViewModel vm) SizeToContent = SizeToContent.WidthAndHeight; MinWidth = 600; CanResize = false; + VideoSubDialogChrome.Apply(this); _vm = vm; vm.Window = this; DataContext = vm; - var labelInfoText = UiUtil.MakeLabel(string.Empty).WithBindText(vm, nameof(vm.InfoText)).WithMarginBottom(8); + var labelInfoText = new TextBlock + { + FontSize = 12.5, + TextWrapping = Avalonia.Media.TextWrapping.Wrap, + MaxWidth = 560, + HorizontalAlignment = HorizontalAlignment.Left, + [!TextBlock.TextProperty] = new Binding(nameof(vm.InfoText)), + }; - var videoSettingsView = MakeVideoSettingsView(vm); - var progressView = MakeProgressView(vm); + var videoSettingsView = MakeVideoSettingsView(vm, labelInfoText); + var progressBar = UiUtil.MakeProgressBar(); + progressBar.Bind(ProgressBar.ValueProperty, new Binding(nameof(vm.ProgressValue))); + var progressView = VideoSubDialogChrome.MakeProgressStrip(progressBar, nameof(vm.ProgressText), nameof(vm.IsGenerating)); var buttonGenerate = new SeSplitButton { Content = Se.Language.General.Generate, @@ -49,54 +65,47 @@ public ReEncodeVideoWindow(ReEncodeVideoViewModel vm) } } }; + buttonGenerate.Classes.Add("primary"); buttonGenerate.Bind(SplitButton.IsEnabledProperty, new Binding(nameof(vm.IsGenerating)) { Converter = InverseBooleanConverter.Instance }); + AutomationProperties.SetName(buttonGenerate, Se.Language.General.Generate); var buttonDone = UiUtil.MakeButtonDone(vm.OkCommand).WithBindEnabled(nameof(vm.IsGenerating), InverseBooleanConverter.Instance); - var buttonPanel = UiUtil.MakeButtonBar( - buttonGenerate, + var buttonPanel = VideoSubDialogChrome.MakeButtonBar( + null, + UiUtil.MakeButtonCancel(vm.CancelCommand).WithBindIsVisible(nameof(vm.IsGenerating)), buttonDone, - UiUtil.MakeButtonCancel(vm.CancelCommand).WithBindIsVisible(nameof(vm.IsGenerating)) + buttonGenerate ); var grid = new Grid { RowDefinitions = { - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, // info - new RowDefinition { Height = new GridLength(1, GridUnitType.Star) }, // settings + new RowDefinition { Height = new GridLength(1, GridUnitType.Star) }, // info + settings new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, // progress bar new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, // buttons }, - ColumnDefinitions = - { - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) }, - }, - Margin = UiUtil.MakeWindowMargin(), - Width = double.NaN, - HorizontalAlignment = HorizontalAlignment.Stretch, }; - grid.Add(labelInfoText, 0); - grid.Add(videoSettingsView, 1); - grid.Add(progressView, 2); - grid.Add(buttonPanel, 3); + grid.Add(VideoSubDialogChrome.MakePanel(videoSettingsView), 0); + grid.Add(progressView, 1); + grid.Add(buttonPanel, 2); Content = grid; UiUtil.FocusOnFirstActivation(this, () => { _comboBoxFrameRate?.Focus(); }); // initial focus on an input, not an action button - a focused button clicks on bare Space } - private Border MakeVideoSettingsView(ReEncodeVideoViewModel vm) + private Border MakeVideoSettingsView(ReEncodeVideoViewModel vm, TextBlock labelInfoText) { - var labelResolution = UiUtil.MakeLabel(Se.Language.General.Resolution); - var textBoxWidth = UiUtil.MakeTextBox(100, vm, nameof(vm.VideoWidth)); - var labelX = UiUtil.MakeLabel("x"); - var textBoxHeight = UiUtil.MakeTextBox(100, vm, nameof(vm.VideoHeight)); - var buttonResolution = UiUtil.MakeButtonBrowse(vm.BrowseResolutionCommand, accessibleName: Se.Language.General.Resolution); + var textBoxWidth = VideoSubDialogChrome.MakeTextBox(nameof(vm.VideoWidth), Se.Language.General.Width, 76); + var labelX = EditorChrome.MakeDimLabel(Se.Language.Video.ResolutionSeparator, new Thickness(0)); + var textBoxHeight = VideoSubDialogChrome.MakeTextBox(nameof(vm.VideoHeight), Se.Language.General.Height, 76); + var buttonResolution = EditorChrome.CreateToolButton("fa-solid fa-ellipsis", Se.Language.General.Resolution, vm.BrowseResolutionCommand, 28); var panelResolution = new StackPanel { Orientation = Orientation.Horizontal, - Spacing = 5, + Spacing = 6, Children = { textBoxWidth, @@ -106,12 +115,12 @@ private Border MakeVideoSettingsView(ReEncodeVideoViewModel vm) } }.WithBindVisible(vm, nameof(vm.UseSourceResolution), InverseBooleanConverter.Instance); - var labelSourceResolution = UiUtil.MakeLabel(Se.Language.General.UseSourceResolution).WithBindVisible(vm, nameof(vm.UseSourceResolution)); - var buttonResolutionSource = UiUtil.MakeButtonBrowse(vm.BrowseResolutionCommand, accessibleName: Se.Language.General.Resolution); + var labelSourceResolution = new TextBlock { Text = Se.Language.General.UseSourceResolution, FontSize = 12.5, VerticalAlignment = VerticalAlignment.Center }; + var buttonResolutionSource = EditorChrome.CreateToolButton("fa-solid fa-ellipsis", Se.Language.General.Resolution, vm.BrowseResolutionCommand, 28); var panelResolutionSource = new StackPanel { Orientation = Orientation.Horizontal, - Spacing = 5, + Spacing = 6, Children = { labelSourceResolution, @@ -119,79 +128,32 @@ private Border MakeVideoSettingsView(ReEncodeVideoViewModel vm) } }.WithBindVisible(vm, nameof(vm.UseSourceResolution)); - var labelFrameRate = UiUtil.MakeLabel(Se.Language.General.FrameRate); var comboBoxFrameRate = UiUtil.MakeComboBox(vm.FrameRates, vm, nameof(vm.SelectedFrameRate)).WithFrameRateDisplay(); + comboBoxFrameRate.MinWidth = 140; + AutomationProperties.SetName(comboBoxFrameRate, Se.Language.General.FrameRate); _comboBoxFrameRate = comboBoxFrameRate; - var labelVideoExtension = UiUtil.MakeLabel(Se.Language.General.VideoExtension); var comboBoxVideoExtensions = UiUtil.MakeComboBox(vm.VideoExtensions, vm, nameof(vm.SelectedVideoExtension)); + comboBoxVideoExtensions.MinWidth = 140; + AutomationProperties.SetName(comboBoxVideoExtensions, Se.Language.General.VideoExtension); - var grid = new Grid - { - RowDefinitions = - { - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - }, - ColumnDefinitions = - { - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Auto) }, - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Auto) }, - }, - ColumnSpacing = 5, - RowSpacing = 5, - Width = double.NaN, - HorizontalAlignment = HorizontalAlignment.Stretch, - }; - - //grid.Add(labelResolution, 0, 0); - //grid.Add(panelResolution, 0, 1); - //grid.Add(panelResolutionSource, 0, 1); + var rows = new StackPanel { Spacing = 8 }; + rows.Children.Add(labelInfoText); - grid.Add(labelFrameRate, 1, 0); - grid.Add(comboBoxFrameRate, 1, 1); + // The resolution rows are not shown (yet) + //rows.Children.Add(VideoSubDialogChrome.MakeRow(Se.Language.General.Resolution, panelResolution, LabelWidth)); + //rows.Children.Add(VideoSubDialogChrome.MakeRow(Se.Language.General.Resolution, panelResolutionSource, LabelWidth)); - grid.Add(labelVideoExtension, 2, 0); - grid.Add(comboBoxVideoExtensions, 2, 1); + rows.Children.Add(VideoSubDialogChrome.MakeRow(Se.Language.General.FrameRate, WrapLeft(comboBoxFrameRate), LabelWidth)); + rows.Children.Add(VideoSubDialogChrome.MakeRow(Se.Language.General.VideoExtension, WrapLeft(comboBoxVideoExtensions), LabelWidth)); - return UiUtil.MakeBorderForControl(grid).WithMarginBottom(5).WithMarginRight(5); + return EditorChrome.MakeEditorSection(Se.Language.Video.BurnIn.VideoOutput, rows, first: true); } - private static Grid MakeProgressView(ReEncodeVideoViewModel vm) + private static Control WrapLeft(Control control) { - var progressBar = UiUtil.MakeProgressBar(); - progressBar.Bind(ProgressBar.ValueProperty, new Binding(nameof(vm.ProgressValue))); - progressBar.Bind(ProgressBar.IsVisibleProperty, new Binding(nameof(vm.IsGenerating))); - - var statusText = new TextBlock - { - Margin = new Thickness(5, 0, 0, 0), - }; - statusText.Bind(TextBlock.TextProperty, new Binding(nameof(vm.ProgressText))); - statusText.Bind(TextBlock.IsVisibleProperty, new Binding(nameof(vm.IsGenerating))); - - var grid = new Grid - { - RowDefinitions = - { - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - }, - ColumnDefinitions = - { - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) }, - }, - RowSpacing = 10, - Margin = new Thickness(0, 5, 0, 0), - Width = double.NaN, - HorizontalAlignment = HorizontalAlignment.Stretch, - }; - - grid.Add(progressBar, 0, 0); - grid.Add(statusText, 1, 0); - - return grid; + control.HorizontalAlignment = HorizontalAlignment.Left; + return control; } protected override void OnClosing(WindowClosingEventArgs e) diff --git a/src/ui/Features/Video/RemuxVideo/PickAudioTrackWindow.cs b/src/ui/Features/Video/RemuxVideo/PickAudioTrackWindow.cs index da02be3cb70..28becf33bd5 100644 --- a/src/ui/Features/Video/RemuxVideo/PickAudioTrackWindow.cs +++ b/src/ui/Features/Video/RemuxVideo/PickAudioTrackWindow.cs @@ -7,6 +7,9 @@ namespace Nikse.SubtitleEdit.Features.Video.RemuxVideo; +/// +/// Pick one of the audio tracks of a video, in the editor look (see ). +/// public class PickAudioTrackWindow : Window { public PickAudioTrackWindow(PickAudioTrackViewModel vm) @@ -18,13 +21,14 @@ public PickAudioTrackWindow(PickAudioTrackViewModel vm) CanResize = false; WindowStartupLocation = WindowStartupLocation.CenterOwner; MinWidth = 500; + VideoSubDialogChrome.Apply(this); DataContext = vm; var titleBlock = new TextBlock { FontSize = UiUtil.ScaledFontSize(14), - FontWeight = FontWeight.Bold, + FontWeight = FontWeight.SemiBold, TextWrapping = TextWrapping.Wrap, }; titleBlock.Bind(TextBlock.TextProperty, new Binding(nameof(PickAudioTrackViewModel.TitleText))); @@ -32,6 +36,10 @@ public PickAudioTrackWindow(PickAudioTrackViewModel vm) var messageBlock = new TextBlock { TextWrapping = TextWrapping.Wrap, + FontSize = 12.5, + Foreground = EditorChrome.DimTextBrush, + MaxWidth = 560, + HorizontalAlignment = HorizontalAlignment.Left, }; messageBlock.Bind(TextBlock.TextProperty, new Binding(nameof(PickAudioTrackViewModel.MessageText))); @@ -42,22 +50,36 @@ public PickAudioTrackWindow(PickAudioTrackViewModel vm) }; comboBox.Bind(ComboBox.ItemsSourceProperty, new Binding(nameof(PickAudioTrackViewModel.Tracks))); comboBox.Bind(ComboBox.SelectedItemProperty, new Binding(nameof(PickAudioTrackViewModel.SelectedTrack))); + Avalonia.Automation.AutomationProperties.SetName(comboBox, Se.Language.Video.RemuxVideoSelectAudioTrack); var buttonOk = UiUtil.MakeButtonOk(vm.OkCommand); + buttonOk.Classes.Add("primary"); var buttonCancel = UiUtil.MakeButtonCancel(vm.CancelCommand); - var buttonBar = UiUtil.MakeButtonBar(buttonOk, buttonCancel); + var buttonBar = VideoSubDialogChrome.MakeButtonBar(null, buttonCancel, buttonOk); - Content = new StackPanel + var panel = new StackPanel { Spacing = 10, - Margin = UiUtil.MakeWindowMargin(), + Margin = new Avalonia.Thickness(16, 14, 16, 16), Children = { titleBlock, messageBlock, comboBox, - buttonBar, } }; + + var grid = new Grid + { + RowDefinitions = + { + new RowDefinition { Height = new GridLength(1, GridUnitType.Star) }, + new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, + }, + }; + grid.Add(VideoSubDialogChrome.MakePanel(panel), 0); + grid.Add(buttonBar, 1); + + Content = grid; } } diff --git a/src/ui/Features/Video/TransparentSubtitles/TransparentSettingsWindow.cs b/src/ui/Features/Video/TransparentSubtitles/TransparentSettingsWindow.cs index 3d7c8fd6b3a..445c5d7cac7 100644 --- a/src/ui/Features/Video/TransparentSubtitles/TransparentSettingsWindow.cs +++ b/src/ui/Features/Video/TransparentSubtitles/TransparentSettingsWindow.cs @@ -1,3 +1,4 @@ +using Avalonia; using Avalonia.Controls; using Avalonia.Data; using Avalonia.Layout; @@ -14,12 +15,14 @@ public TransparentSettingsWindow(TransparentSettingsViewModel vm) Title = Se.Language.Video.OutputSettingsTitle; SizeToContent = SizeToContent.WidthAndHeight; CanResize = false; + VideoSubDialogChrome.Apply(this); vm.Window = this; DataContext = vm; var checkBoxUseSourceFolder = new RadioButton { Content = Se.Language.General.UseSourceFolder, + FontSize = 12.5, IsChecked = vm.UseSourceFolder, VerticalAlignment = VerticalAlignment.Center, [!Avalonia.Controls.Primitives.ToggleButton.IsCheckedProperty] = new Binding(nameof(vm.UseSourceFolder)) { Mode = BindingMode.TwoWay, UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged }, @@ -28,6 +31,7 @@ public TransparentSettingsWindow(TransparentSettingsViewModel vm) var checkBoxUseOutputFolder = new RadioButton { Content = Se.Language.General.UseOutputFolder, + FontSize = 12.5, IsChecked = vm.UseOutputFolder, VerticalAlignment = VerticalAlignment.Center, [!Avalonia.Controls.Primitives.ToggleButton.IsCheckedProperty] = new Binding(nameof(vm.UseOutputFolder)) { Mode = BindingMode.TwoWay, UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged }, @@ -44,14 +48,17 @@ public TransparentSettingsWindow(TransparentSettingsViewModel vm) [!Control.IsEnabledProperty] = new Binding(nameof(vm.UseOutputFolder)) { Mode = BindingMode.OneWay }, Width = 400, }; + textBoxOutputFolder.Classes.Add("text"); + Avalonia.Automation.AutomationProperties.SetName(textBoxOutputFolder, Se.Language.General.OutputFolder); - var buttonBrowse = UiUtil.MakeButtonBrowse(vm.BrowseOutputFolderCommand, accessibleName: "Use output folder"); + var buttonBrowse = EditorChrome.CreateToolButton("fa-solid fa-folder-open", Se.Language.General.UseOutputFolder, vm.BrowseOutputFolderCommand, 28); var panelOutputFolder = new StackPanel { Orientation = Orientation.Horizontal, VerticalAlignment = VerticalAlignment.Center, - Spacing = 5, + Spacing = 4, + Margin = new Thickness(28, 0, 0, 0), // under the radio button's text Children = { textBoxOutputFolder, @@ -60,33 +67,32 @@ public TransparentSettingsWindow(TransparentSettingsViewModel vm) }; var buttonOk = UiUtil.MakeButtonOk(vm.OkCommand); + buttonOk.Classes.Add("primary"); var buttonCancel = UiUtil.MakeButtonCancel(vm.CancelCommand); - var panelButtons = UiUtil.MakeButtonBar(buttonOk, buttonCancel); + var panelButtons = VideoSubDialogChrome.MakeButtonBar(null, buttonCancel, buttonOk); + + var rows = new StackPanel + { + Spacing = 6, + Children = + { + checkBoxUseSourceFolder, + checkBoxUseOutputFolder, + panelOutputFolder, + }, + }; var grid = new Grid { RowDefinitions = { + new RowDefinition { Height = new GridLength(1, GridUnitType.Star) }, new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - }, - ColumnDefinitions = - { - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Auto) }, }, - Margin = UiUtil.MakeWindowMargin(), - ColumnSpacing = 10, - RowSpacing = 10, - Width = double.NaN, - HorizontalAlignment = HorizontalAlignment.Stretch, }; - grid.Add(checkBoxUseSourceFolder, 0, 0); - grid.Add(checkBoxUseOutputFolder, 1, 0); - grid.Add(panelOutputFolder, 2, 0); - grid.Add(panelButtons, 3, 0); + grid.Add(VideoSubDialogChrome.MakePanel(EditorChrome.MakeEditorSection(Se.Language.General.OutputFolder, rows, first: true)), 0); + grid.Add(panelButtons, 1); Content = grid; diff --git a/src/ui/Features/Video/VideoSubDialogChrome.cs b/src/ui/Features/Video/VideoSubDialogChrome.cs new file mode 100644 index 00000000000..ca081059365 --- /dev/null +++ b/src/ui/Features/Video/VideoSubDialogChrome.cs @@ -0,0 +1,326 @@ +using Avalonia; +using Avalonia.Automation; +using Avalonia.Controls; +using Avalonia.Controls.Presenters; +using Avalonia.Controls.Primitives; +using Avalonia.Data; +using Avalonia.Layout; +using Avalonia.Media; +using Avalonia.Styling; +using Nikse.SubtitleEdit.Logic; + +namespace Nikse.SubtitleEdit.Features.Video; + +/// +/// The pieces the smaller video dialogs (embedded subtitles, re-encode, background music, the +/// burn-in sub-dialogs, ...) share to get the editor look of : compact +/// combo boxes and text fields, an accent primary (also as a split button), the button bar at the +/// bottom and the progress strip above it. +/// +internal static class VideoSubDialogChrome +{ + /// + /// Window background, the editor styles and the compact field styles. + /// + internal static void Apply(Window window) + { + window.Background = EditorChrome.WindowBrush; + EditorChrome.AddEditorStyles(window.Styles); + + var styles = window.Styles; + styles.Add(new Style(x => x.OfType()) + { + Setters = + { + new Setter(TemplatedControl.FontSizeProperty, 12.5), + new Setter(TemplatedControl.MinHeightProperty, 28.0), + new Setter(TemplatedControl.PaddingProperty, new Thickness(8, 2, 4, 2)), + new Setter(TemplatedControl.CornerRadiusProperty, new CornerRadius(6)), + }, + }); + styles.Add(new Style(x => x.OfType().Class("text")) + { + Setters = + { + new Setter(TemplatedControl.FontSizeProperty, 12.5), + new Setter(TemplatedControl.MinHeightProperty, 28.0), + new Setter(TemplatedControl.PaddingProperty, new Thickness(8, 4)), + new Setter(TemplatedControl.CornerRadiusProperty, new CornerRadius(6)), + new Setter(TemplatedControl.BackgroundProperty, EditorChrome.FieldBrush), + new Setter(TemplatedControl.BorderBrushProperty, EditorChrome.FieldBorderBrush), + new Setter(TextBox.VerticalContentAlignmentProperty, VerticalAlignment.Center), + }, + }); + + // A disabled primary (while generating) must not keep the accent - it looked clickable + styles.Add(new Style(x => x.OfType
public class AudioCppTtsSettingsWindow : Window { private const int LabelWidth = 150; - private const int ValueWidth = 360; private readonly AudioCppTtsSettingsViewModel _vm; @@ -28,204 +22,38 @@ public AudioCppTtsSettingsWindow(AudioCppTtsSettingsViewModel vm) { UiUtil.InitializeWindow(this, GetType().Name); Title = vm.Adapter.EngineName + " settings"; - SizeToContent = SizeToContent.WidthAndHeight; - CanResize = false; - MinWidth = 620; + TtsSettingsChrome.Apply(this); _vm = vm; vm.Window = this; DataContext = vm; - Content = BuildContent(vm); - - var ok = UiUtil.MakeButtonOk(vm.OkCommand); - UiUtil.FocusOnFirstActivation(this, ok); - } - - private Border BuildContent(AudioCppTtsSettingsViewModel vm) - { - var stack = new StackPanel - { - Orientation = Orientation.Vertical, - Spacing = 14, - Children = { BuildHeader(vm), BuildDetails(vm), BuildActions(vm) }, - }; - - var outerGrid = new Grid { Margin = UiUtil.MakeWindowMargin() }; - outerGrid.Children.Add(stack); - - return new Border - { - Child = outerGrid, - Padding = new Thickness(4), - }; - } - - private static StackPanel BuildHeader(AudioCppTtsSettingsViewModel vm) - { - var title = new TextBlock - { - Text = vm.Adapter.EngineName, - FontSize = UiUtil.ScaledFontSize(18), - FontWeight = FontWeight.SemiBold, - }; - - var subtitle = new TextBlock - { - Text = vm.Adapter.Description, - FontSize = UiUtil.ScaledFontSize(12), - Opacity = 0.75, - Margin = new Thickness(0, 2, 0, 0), - }; - - return new StackPanel - { - Orientation = Orientation.Vertical, - Spacing = 0, - Children = { title, subtitle }, - }; - } - - private static Border BuildDetails(AudioCppTtsSettingsViewModel vm) - { - var grid = new Grid - { - ColumnDefinitions = - { - new ColumnDefinition { Width = new GridLength(LabelWidth, GridUnitType.Pixel) }, - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) }, - }, - RowDefinitions = - { - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, // engine - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, // default model - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, // alt model - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, // voices - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, // folder - }, - ColumnSpacing = 12, - RowSpacing = 10, - }; - - grid.Add(MakeLabel(Se.Language.General.Engine), 0, 0); - var enginePanel = MakeStatusPanel(nameof(vm.EngineBrush), nameof(vm.EngineLabel)); - var engineButton = UiUtil.MakeButton(string.Empty, vm.RedownloadEngineCommand) - .WithIconLeftBindText(IconNames.Download, nameof(vm.EngineDownloadButtonText)) - .WithMarginLeft(12); - enginePanel.Children.Add(engineButton); - grid.Add(enginePanel, 0, 1); - - grid.Add(MakeLabel("Model " + vm.Adapter.ModelKeyDefault), 1, 0); - var defaultPanel = MakeStatusPanel(nameof(vm.ModelDefaultBrush), nameof(vm.ModelDefaultLabel)); - defaultPanel.Children.Add(UiUtil.MakeButton(string.Empty, vm.DownloadModelCommand, vm.Adapter.ModelKeyDefault) - .WithIconLeft(IconNames.Download) - .WithMarginLeft(12)); - grid.Add(defaultPanel, 1, 1); - - grid.Add(MakeLabel("Model " + vm.Adapter.ModelKeyAlt), 2, 0); - var altPanel = MakeStatusPanel(nameof(vm.ModelAltBrush), nameof(vm.ModelAltLabel)); - altPanel.Children.Add(UiUtil.MakeButton(string.Empty, vm.DownloadModelCommand, vm.Adapter.ModelKeyAlt) - .WithIconLeft(IconNames.Download) - .WithMarginLeft(12)); - grid.Add(altPanel, 2, 1); - - grid.Add(MakeLabel(Se.Language.Video.Voices), 3, 0); - grid.Add(new TextBlock - { - VerticalAlignment = VerticalAlignment.Center, - FontWeight = FontWeight.SemiBold, - [!TextBlock.TextProperty] = new Binding(nameof(vm.VoicesLabel)), - }, 3, 1); - - grid.Add(MakeLabel(Se.Language.General.InstallFolder), 4, 0); - grid.Add(new TextBox - { - IsReadOnly = true, - Width = ValueWidth, - BorderThickness = new Thickness(0), - Background = Brushes.Transparent, - Padding = new Thickness(0), - VerticalContentAlignment = VerticalAlignment.Center, - FontSize = UiUtil.ScaledFontSize(12), - [!TextBox.TextProperty] = new Binding(nameof(vm.ModelsFolder)), - }, 4, 1); - - return new Border - { - Child = grid, - Padding = new Thickness(14), - CornerRadius = new CornerRadius(6), - BorderThickness = new Thickness(1), - BorderBrush = new SolidColorBrush(Color.FromArgb(0x40, 0x80, 0x80, 0x80)), - }; - } + var close = UiUtil.MakeButtonOk(vm.OkCommand); + Content = TtsSettingsChrome.MakeContent( + TtsSettingsChrome.MakeHeader(vm.Adapter.EngineName, vm.Adapter.Description), + TtsSettingsChrome.MakeButtonBar(close), + BuildModels(vm)); - private static StackPanel MakeStatusPanel(string brushBindingPath, string labelBindingPath) - { - var dot = new Ellipse - { - Width = 10, - Height = 10, - VerticalAlignment = VerticalAlignment.Center, - [!Shape.FillProperty] = new Binding(brushBindingPath), - }; - var text = new TextBlock - { - VerticalAlignment = VerticalAlignment.Center, - FontWeight = FontWeight.SemiBold, - Margin = new Thickness(8, 0, 0, 0), - [!TextBlock.TextProperty] = new Binding(labelBindingPath), - }; - return new StackPanel - { - Orientation = Orientation.Horizontal, - Children = { dot, text }, - }; + UiUtil.FocusOnFirstActivation(this, close); } - private static Control BuildActions(AudioCppTtsSettingsViewModel vm) + private static Border BuildModels(AudioCppTtsSettingsViewModel vm) { - var openModelsFolder = UiUtil.MakeButton(Se.Language.General.OpenContainingFolder, vm.OpenModelsFolderCommand).WithIconLeft(IconNames.FolderOpen); - var openVoicesFolder = UiUtil.MakeButton(Se.Language.Video.Voices, vm.OpenVoicesFolderCommand).WithIconLeft(IconNames.FolderOpen); + var openVoicesFolder = TtsSettingsChrome.MakeFolderButton(Se.Language.Video.TextToSpeech.OpenVoicesFolder, vm.OpenVoicesFolderCommand); openVoicesFolder.IsVisible = vm.HasVoicesFolder; - var close = UiUtil.MakeButtonOk(vm.OkCommand); - - var leftPanel = new StackPanel - { - Orientation = Orientation.Horizontal, - Spacing = 8, - Children = { openModelsFolder, openVoicesFolder }, - }; - var rightPanel = new StackPanel - { - Orientation = Orientation.Horizontal, - HorizontalAlignment = HorizontalAlignment.Right, - Spacing = 8, - Children = { close }, - }; - - var grid = new Grid - { - ColumnDefinitions = - { - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Auto) }, - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) }, - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Auto) }, - }, - }; - grid.Add(leftPanel, 0, 0); - grid.Add(rightPanel, 0, 2); - return grid; + var rows = TtsSettingsChrome.MakeRows(LabelWidth, + (Se.Language.General.Engine, TtsSettingsChrome.MakeStatus(nameof(vm.EngineBrush), nameof(vm.EngineLabel), + TtsSettingsChrome.MakeEngineDownloadButton(vm.RedownloadEngineCommand, nameof(vm.EngineDownloadButtonText)))), + ("Model " + vm.Adapter.ModelKeyDefault, TtsSettingsChrome.MakeStatus(nameof(vm.ModelDefaultBrush), nameof(vm.ModelDefaultLabel), + TtsSettingsChrome.MakeModelDownloadButton(vm.DownloadModelCommand, vm.Adapter.ModelKeyDefault))), + ("Model " + vm.Adapter.ModelKeyAlt, TtsSettingsChrome.MakeStatus(nameof(vm.ModelAltBrush), nameof(vm.ModelAltLabel), + TtsSettingsChrome.MakeModelDownloadButton(vm.DownloadModelCommand, vm.Adapter.ModelKeyAlt))), + (Se.Language.Video.Voices, TtsSettingsChrome.MakeValue(nameof(vm.VoicesLabel), openVoicesFolder)), + (Se.Language.General.InstallFolder, TtsSettingsChrome.MakeFolder(nameof(vm.ModelsFolder), vm.OpenModelsFolderCommand))); + return TtsSettingsChrome.MakeSection(Se.Language.General.Models, rows); } - private static TextBlock MakeLabel(string text) => new() - { - Text = text, - Opacity = 0.7, - VerticalAlignment = VerticalAlignment.Center, - }; - protected override void OnKeyDown(KeyEventArgs e) { base.OnKeyDown(e); diff --git a/src/ui/Features/Video/TextToSpeech/Confucius4TtsCrispAsrSettings/Confucius4TtsCrispAsrSettingsWindow.cs b/src/ui/Features/Video/TextToSpeech/Confucius4TtsCrispAsrSettings/Confucius4TtsCrispAsrSettingsWindow.cs index fa110277aae..a2ad0f37005 100644 --- a/src/ui/Features/Video/TextToSpeech/Confucius4TtsCrispAsrSettings/Confucius4TtsCrispAsrSettingsWindow.cs +++ b/src/ui/Features/Video/TextToSpeech/Confucius4TtsCrispAsrSettings/Confucius4TtsCrispAsrSettingsWindow.cs @@ -1,20 +1,17 @@ -using Avalonia; using Avalonia.Controls; -using Avalonia.Controls.Shapes; -using Avalonia.Data; using Avalonia.Input; -using Avalonia.Layout; -using Avalonia.Media; using Nikse.SubtitleEdit.Features.Video.TextToSpeech.Engines; using Nikse.SubtitleEdit.Logic; using Nikse.SubtitleEdit.Logic.Config; namespace Nikse.SubtitleEdit.Features.Video.TextToSpeech.Confucius4TtsCrispAsrSettings; +/// +/// Confucius4-TTS (CrispASR) settings, in the editor look (see ). +/// public class Confucius4TtsCrispAsrSettingsWindow : Window { private const int LabelWidth = 150; - private const int ValueWidth = 360; private readonly Confucius4TtsCrispAsrSettingsViewModel _vm; @@ -22,247 +19,46 @@ public Confucius4TtsCrispAsrSettingsWindow(Confucius4TtsCrispAsrSettingsViewMode { UiUtil.InitializeWindow(this, GetType().Name); Title = string.Format(Se.Language.Video.TtsCrispAsrSettingsTitle, "Confucius4-TTS"); - SizeToContent = SizeToContent.WidthAndHeight; - CanResize = false; - MinWidth = 580; + TtsSettingsChrome.Apply(this); _vm = vm; vm.Window = this; DataContext = vm; - Content = BuildContent(vm); - - var ok = UiUtil.MakeButtonOk(vm.OkCommand); - UiUtil.FocusOnFirstActivation(this, ok); - } - - private Border BuildContent(Confucius4TtsCrispAsrSettingsViewModel vm) - { - var header = BuildHeader(); - var details = BuildDetails(vm); - var actions = BuildActions(vm); - - var stack = new StackPanel - { - Orientation = Orientation.Vertical, - Spacing = 14, - Children = { header, details, actions }, - }; - - var outerGrid = new Grid - { - Margin = UiUtil.MakeWindowMargin(), - }; - outerGrid.Children.Add(stack); - - return new Border - { - Child = outerGrid, - Padding = new Thickness(4), - }; - } - - private static StackPanel BuildHeader() - { - var title = new TextBlock - { - Text = "Confucius4-TTS (CrispASR)", - FontSize = UiUtil.ScaledFontSize(18), - FontWeight = FontWeight.SemiBold, - }; - - var subtitle = new TextBlock - { - Text = new Confucius4TtsCrispAsr().Description, - FontSize = UiUtil.ScaledFontSize(12), - Opacity = 0.75, - Margin = new Thickness(0, 2, 0, 0), - }; - - return new StackPanel - { - Orientation = Orientation.Vertical, - Spacing = 0, - Children = { title, subtitle }, - }; - } - - private static Border BuildDetails(Confucius4TtsCrispAsrSettingsViewModel vm) - { - var grid = new Grid - { - ColumnDefinitions = - { - new ColumnDefinition { Width = new GridLength(LabelWidth, GridUnitType.Pixel) }, - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) }, - }, - RowDefinitions = - { - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - }, - ColumnSpacing = 12, - RowSpacing = 10, - }; - - grid.Add(MakeLabel(Se.Language.General.Engine), 0, 0); - var enginePanel = MakeStatusPanel(nameof(vm.EngineBrush), nameof(vm.EngineLabel)); - var engineButton = UiUtil.MakeButton(string.Empty, vm.RedownloadEngineCommand) - .WithIconLeftBindText(IconNames.Download, nameof(vm.EngineDownloadButtonText)) - .WithMarginLeft(12); - enginePanel.Children.Add(engineButton); - grid.Add(enginePanel, 0, 1); - - grid.Add(MakeLabel("Model " + Confucius4TtsCrispAsr.ModelKeyQ8_0), 1, 0); - grid.Add(MakeStatusPanel(nameof(vm.CoreQ8_0Brush), nameof(vm.CoreQ8_0Label)), 1, 1); - - grid.Add(MakeLabel("Model " + Confucius4TtsCrispAsr.ModelKeyF16), 2, 0); - grid.Add(MakeStatusPanel(nameof(vm.CoreF16Brush), nameof(vm.CoreF16Label)), 2, 1); - - grid.Add(MakeLabel("BigVGAN vocoder"), 3, 0); - grid.Add(MakeStatusPanel(nameof(vm.VocoderBrush), nameof(vm.VocoderLabel)), 3, 1); - - grid.Add(MakeLabel("w2v-BERT encoder"), 4, 0); - grid.Add(MakeStatusPanel(nameof(vm.W2vBrush), nameof(vm.W2vLabel)), 4, 1); - - grid.Add(MakeLabel(Se.Language.Video.Voices), 5, 0); - var voicesText = new TextBlock - { - VerticalAlignment = VerticalAlignment.Center, - FontWeight = FontWeight.SemiBold, - [!TextBlock.TextProperty] = new Binding(nameof(vm.VoicesLabel)), - }; - grid.Add(voicesText, 5, 1); - - grid.Add(MakeLabel("Quality"), 6, 0); - grid.Add(MakeOdeStepsPanel(), 6, 1); - - grid.Add(MakeLabel(Se.Language.General.InstallFolder), 7, 0); - var folderText = new TextBox - { - IsReadOnly = true, - Width = ValueWidth, - BorderThickness = new Thickness(0), - Background = Brushes.Transparent, - Padding = new Thickness(0), - VerticalContentAlignment = VerticalAlignment.Center, - FontSize = UiUtil.ScaledFontSize(12), - [!TextBox.TextProperty] = new Binding(nameof(vm.ModelsFolder)), - }; - grid.Add(folderText, 7, 1); - - return new Border - { - Child = grid, - Padding = new Thickness(14), - CornerRadius = new CornerRadius(6), - BorderThickness = new Thickness(1), - BorderBrush = new SolidColorBrush(Color.FromArgb(0x40, 0x80, 0x80, 0x80)), - }; - } + var close = UiUtil.MakeButton(Se.Language.General.Close, vm.OkCommand); + Content = TtsSettingsChrome.MakeContent( + TtsSettingsChrome.MakeHeader("Confucius4-TTS (CrispASR)", new Confucius4TtsCrispAsr().Description), + TtsSettingsChrome.MakeButtonBar(close), + BuildModels(vm), + BuildVoice(vm)); - /// - /// Flow-matching Euler steps for the S2A stage — the quality/speed knob this backend exposes - /// (--tts-steps; CrispASR default 20, upstream reference samples at 25). - /// - private static StackPanel MakeOdeStepsPanel() - { - var slider = new Slider - { - Minimum = Confucius4TtsCrispAsr.MinOdeSteps, - Maximum = Confucius4TtsCrispAsr.MaxOdeSteps, - Width = 220, - TickFrequency = 1, - IsSnapToTickEnabled = true, - VerticalAlignment = VerticalAlignment.Center, - [!Slider.ValueProperty] = new Binding(nameof(Confucius4TtsCrispAsrSettingsViewModel.OdeSteps)), - }; - var label = new TextBlock - { - VerticalAlignment = VerticalAlignment.Center, - FontWeight = FontWeight.SemiBold, - Margin = new Thickness(8, 0, 0, 0), - Width = 80, - [!TextBlock.TextProperty] = new Binding(nameof(Confucius4TtsCrispAsrSettingsViewModel.OdeStepsLabel)), - }; - return new StackPanel - { - Orientation = Orientation.Horizontal, - Children = { slider, label }, - }; + UiUtil.FocusOnFirstActivation(this, close); } - private static StackPanel MakeStatusPanel(string brushBindingPath, string labelBindingPath) + private static Border BuildModels(Confucius4TtsCrispAsrSettingsViewModel vm) { - var dot = new Ellipse - { - Width = 10, - Height = 10, - VerticalAlignment = VerticalAlignment.Center, - [!Ellipse.FillProperty] = new Binding(brushBindingPath), - }; - var text = new TextBlock - { - VerticalAlignment = VerticalAlignment.Center, - FontWeight = FontWeight.SemiBold, - Margin = new Thickness(8, 0, 0, 0), - [!TextBlock.TextProperty] = new Binding(labelBindingPath), - }; - return new StackPanel - { - Orientation = Orientation.Horizontal, - Children = { dot, text }, - }; + var rows = TtsSettingsChrome.MakeRows(LabelWidth, + (Se.Language.General.Engine, TtsSettingsChrome.MakeStatus(nameof(vm.EngineBrush), nameof(vm.EngineLabel), + TtsSettingsChrome.MakeEngineDownloadButton(vm.RedownloadEngineCommand, nameof(vm.EngineDownloadButtonText)))), + ("Model " + Confucius4TtsCrispAsr.ModelKeyQ8_0, TtsSettingsChrome.MakeStatus(nameof(vm.CoreQ8_0Brush), nameof(vm.CoreQ8_0Label))), + ("Model " + Confucius4TtsCrispAsr.ModelKeyF16, TtsSettingsChrome.MakeStatus(nameof(vm.CoreF16Brush), nameof(vm.CoreF16Label))), + ("BigVGAN vocoder", TtsSettingsChrome.MakeStatus(nameof(vm.VocoderBrush), nameof(vm.VocoderLabel))), + ("w2v-BERT encoder", TtsSettingsChrome.MakeStatus(nameof(vm.W2vBrush), nameof(vm.W2vLabel))), + (Se.Language.General.InstallFolder, TtsSettingsChrome.MakeFolder(nameof(vm.ModelsFolder), vm.OpenModelsFolderCommand))); + return TtsSettingsChrome.MakeSection(Se.Language.General.Models, rows); } - private static Grid BuildActions(Confucius4TtsCrispAsrSettingsViewModel vm) + private static Border BuildVoice(Confucius4TtsCrispAsrSettingsViewModel vm) { - var openModelsFolder = UiUtil.MakeButton(Se.Language.General.OpenContainingFolder, vm.OpenModelsFolderCommand).WithIconLeft(IconNames.FolderOpen); - var openVoicesFolder = UiUtil.MakeButton("Voices folder", vm.OpenVoicesFolderCommand).WithIconLeft(IconNames.FolderOpen); - var close = UiUtil.MakeButton(Se.Language.General.Close, vm.OkCommand); - - var leftPanel = new StackPanel - { - Orientation = Orientation.Horizontal, - Spacing = 8, - Children = { openModelsFolder, openVoicesFolder }, - }; - - var rightPanel = new StackPanel - { - Orientation = Orientation.Horizontal, - HorizontalAlignment = HorizontalAlignment.Right, - Spacing = 8, - Children = { close }, - }; - - var grid = new Grid - { - ColumnDefinitions = - { - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Auto) }, - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) }, - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Auto) }, - }, - }; - grid.Add(leftPanel, 0, 0); - grid.Add(rightPanel, 0, 2); - return grid; + var rows = TtsSettingsChrome.MakeRows(LabelWidth, + (Se.Language.Video.Voices, TtsSettingsChrome.MakeValue(nameof(vm.VoicesLabel), + TtsSettingsChrome.MakeFolderButton(Se.Language.Video.TextToSpeech.OpenVoicesFolder, vm.OpenVoicesFolderCommand))), + // Flow-matching Euler steps for the S2A stage — the quality/speed knob this backend exposes + // (--tts-steps; CrispASR default 20, upstream reference samples at 25). + (Se.Language.General.Quality, TtsSettingsChrome.MakeSlider(Se.Language.General.Quality, Confucius4TtsCrispAsr.MinOdeSteps, Confucius4TtsCrispAsr.MaxOdeSteps, 1, nameof(vm.OdeSteps), nameof(vm.OdeStepsLabel)))); + return TtsSettingsChrome.MakeSection(Se.Language.General.Voice, rows); } - private static TextBlock MakeLabel(string text) => new() - { - Text = text, - Opacity = 0.7, - VerticalAlignment = VerticalAlignment.Center, - }; - protected override void OnKeyDown(KeyEventArgs e) { base.OnKeyDown(e); diff --git a/src/ui/Features/Video/TextToSpeech/CosyVoice3CrispAsrSettings/CosyVoice3CrispAsrSettingsWindow.cs b/src/ui/Features/Video/TextToSpeech/CosyVoice3CrispAsrSettings/CosyVoice3CrispAsrSettingsWindow.cs index e833c57bc85..ae016d2614e 100644 --- a/src/ui/Features/Video/TextToSpeech/CosyVoice3CrispAsrSettings/CosyVoice3CrispAsrSettingsWindow.cs +++ b/src/ui/Features/Video/TextToSpeech/CosyVoice3CrispAsrSettings/CosyVoice3CrispAsrSettingsWindow.cs @@ -1,20 +1,18 @@ -using Avalonia; +using Avalonia.Automation; using Avalonia.Controls; -using Avalonia.Controls.Shapes; -using Avalonia.Data; using Avalonia.Input; -using Avalonia.Layout; -using Avalonia.Media; using Nikse.SubtitleEdit.Features.Video.TextToSpeech.Engines; using Nikse.SubtitleEdit.Logic; using Nikse.SubtitleEdit.Logic.Config; namespace Nikse.SubtitleEdit.Features.Video.TextToSpeech.CosyVoice3CrispAsrSettings; +/// +/// CosyVoice3 (CrispASR) settings, in the editor look (see ). +/// public class CosyVoice3CrispAsrSettingsWindow : Window { private const int LabelWidth = 150; - private const int ValueWidth = 360; private readonly CosyVoice3CrispAsrSettingsViewModel _vm; @@ -22,275 +20,65 @@ public CosyVoice3CrispAsrSettingsWindow(CosyVoice3CrispAsrSettingsViewModel vm) { UiUtil.InitializeWindow(this, GetType().Name); Title = string.Format(Se.Language.Video.TtsCrispAsrSettingsTitle, "CosyVoice3"); - SizeToContent = SizeToContent.WidthAndHeight; - CanResize = false; - MinWidth = 580; + TtsSettingsChrome.Apply(this); _vm = vm; vm.Window = this; DataContext = vm; - Content = BuildContent(vm); + var close = UiUtil.MakeButton(Se.Language.General.Close, vm.OkCommand); + Content = TtsSettingsChrome.MakeContent( + TtsSettingsChrome.MakeHeader("CosyVoice3 (CrispASR)", new CosyVoice3CrispAsr().Description), + TtsSettingsChrome.MakeButtonBar(close), + BuildModels(vm), + BuildVoice(vm)); - var ok = UiUtil.MakeButtonOk(vm.OkCommand); - UiUtil.FocusOnFirstActivation(this, ok); + UiUtil.FocusOnFirstActivation(this, close); } - private Border BuildContent(CosyVoice3CrispAsrSettingsViewModel vm) + private static Border BuildModels(CosyVoice3CrispAsrSettingsViewModel vm) { - var header = BuildHeader(); - var details = BuildDetails(vm); - var actions = BuildActions(vm); - - var stack = new StackPanel - { - Orientation = Orientation.Vertical, - Spacing = 14, - Children = { header, details, actions }, - }; - - var outerGrid = new Grid - { - Margin = UiUtil.MakeWindowMargin(), - }; - outerGrid.Children.Add(stack); - - return new Border - { - Child = outerGrid, - Padding = new Thickness(4), - }; - } - - private static StackPanel BuildHeader() - { - var title = new TextBlock - { - Text = "CosyVoice3 (CrispASR)", - FontSize = UiUtil.ScaledFontSize(18), - FontWeight = FontWeight.SemiBold, - }; - - var subtitle = new TextBlock - { - Text = new CosyVoice3CrispAsr().Description, - FontSize = UiUtil.ScaledFontSize(12), - Opacity = 0.75, - Margin = new Thickness(0, 2, 0, 0), - }; - - return new StackPanel - { - Orientation = Orientation.Vertical, - Spacing = 0, - Children = { title, subtitle }, - }; + var rows = TtsSettingsChrome.MakeRows(LabelWidth, + (Se.Language.General.Engine, TtsSettingsChrome.MakeStatus(nameof(vm.EngineBrush), nameof(vm.EngineLabel), + TtsSettingsChrome.MakeEngineDownloadButton(vm.RedownloadEngineCommand, nameof(vm.EngineDownloadButtonText)))), + (CosyVoice3CrispAsr.ModelKeyQ4K, TtsSettingsChrome.MakeStatus(nameof(vm.Q4KBundleBrush), nameof(vm.Q4KBundleLabel))), + (CosyVoice3CrispAsr.ModelKeyF16, TtsSettingsChrome.MakeStatus(nameof(vm.F16BundleBrush), nameof(vm.F16BundleLabel))), + // The RL talker bundles (#13272) share every companion with the two above - only the LLM + // file differs - so an installed RL row means one extra GGUF on disk, not a second bundle. + (CosyVoice3CrispAsr.ModelKeyRlQ4K, TtsSettingsChrome.MakeStatus(nameof(vm.RlQ4KBundleBrush), nameof(vm.RlQ4KBundleLabel))), + (CosyVoice3CrispAsr.ModelKeyRlF16, TtsSettingsChrome.MakeStatus(nameof(vm.RlF16BundleBrush), nameof(vm.RlF16BundleLabel))), + (Se.Language.General.InstallFolder, TtsSettingsChrome.MakeFolder(nameof(vm.ModelsFolder), vm.OpenModelsFolderCommand))); + return TtsSettingsChrome.MakeSection(Se.Language.General.Models, rows); } - private static Border BuildDetails(CosyVoice3CrispAsrSettingsViewModel vm) + private static Border BuildVoice(CosyVoice3CrispAsrSettingsViewModel vm) { - var grid = new Grid - { - ColumnDefinitions = - { - new ColumnDefinition { Width = new GridLength(LabelWidth, GridUnitType.Pixel) }, - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) }, - }, - RowDefinitions = - { - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - }, - ColumnSpacing = 12, - RowSpacing = 10, - }; - - grid.Add(MakeLabel(Se.Language.General.Engine), 0, 0); - var enginePanel = MakeStatusPanel(nameof(vm.EngineBrush), nameof(vm.EngineLabel)); - var engineButton = UiUtil.MakeButton(string.Empty, vm.RedownloadEngineCommand) - .WithIconLeftBindText(IconNames.Download, nameof(vm.EngineDownloadButtonText)) - .WithMarginLeft(12); - enginePanel.Children.Add(engineButton); - grid.Add(enginePanel, 0, 1); - - grid.Add(MakeLabel(CosyVoice3CrispAsr.ModelKeyQ4K), 1, 0); - grid.Add(MakeStatusPanel(nameof(vm.Q4KBundleBrush), nameof(vm.Q4KBundleLabel)), 1, 1); - - grid.Add(MakeLabel(CosyVoice3CrispAsr.ModelKeyF16), 2, 0); - grid.Add(MakeStatusPanel(nameof(vm.F16BundleBrush), nameof(vm.F16BundleLabel)), 2, 1); - - // The RL talker bundles (#13272) share every companion with the two above - only the LLM - // file differs - so an installed RL row means one extra GGUF on disk, not a second bundle. - grid.Add(MakeLabel(CosyVoice3CrispAsr.ModelKeyRlQ4K), 3, 0); - grid.Add(MakeStatusPanel(nameof(vm.RlQ4KBundleBrush), nameof(vm.RlQ4KBundleLabel)), 3, 1); - - grid.Add(MakeLabel(CosyVoice3CrispAsr.ModelKeyRlF16), 4, 0); - grid.Add(MakeStatusPanel(nameof(vm.RlF16BundleBrush), nameof(vm.RlF16BundleLabel)), 4, 1); - - grid.Add(MakeLabel(Se.Language.Video.Presets), 5, 0); - var presetsText = new TextBlock - { - VerticalAlignment = VerticalAlignment.Center, - FontWeight = FontWeight.SemiBold, - [!TextBlock.TextProperty] = new Binding(nameof(vm.PresetsLabel)), - }; - grid.Add(presetsText, 5, 1); - - grid.Add(MakeLabel(Se.Language.Video.Voices), 6, 0); - var voicesText = new TextBlock - { - VerticalAlignment = VerticalAlignment.Center, - FontWeight = FontWeight.SemiBold, - [!TextBlock.TextProperty] = new Binding(nameof(vm.VoicesLabel)), - }; - grid.Add(voicesText, 6, 1); - - grid.Add(MakeLabel(Se.Language.General.Speed), 7, 0); - grid.Add(MakeSpeedPanel(), 7, 1); - // Language spoken in imported reference WAVs — sent as `source_lang` so cross-lingual // cloning engages when the target language differs (the server cannot detect the // reference language itself for Latin scripts). Presets carry their own bank language. - grid.Add(MakeLabel("Reference language"), 8, 0); var sourceLanguageCombo = UiUtil.MakeComboBox(vm.SourceLanguages, vm, nameof(vm.SelectedSourceLanguage)); - var sourceLanguageHint = new TextBlock + sourceLanguageCombo.MinWidth = 200; + sourceLanguageCombo.HorizontalAlignment = Avalonia.Layout.HorizontalAlignment.Left; + AutomationProperties.SetName(sourceLanguageCombo, "Reference language"); + var sourceLanguage = new StackPanel { - Text = "Language spoken in imported reference WAVs (for cross-lingual cloning). " - + "Auto detects it from each voice's transcript.", - FontSize = UiUtil.ScaledFontSize(12), - Opacity = 0.75, - VerticalAlignment = VerticalAlignment.Center, - Margin = new Thickness(8, 0, 0, 0), - }; - grid.Add(new StackPanel - { - Orientation = Orientation.Horizontal, - Children = { sourceLanguageCombo, sourceLanguageHint }, - }, 8, 1); - - grid.Add(MakeLabel(Se.Language.General.InstallFolder), 9, 0); - var folderText = new TextBox - { - IsReadOnly = true, - Width = ValueWidth, - BorderThickness = new Thickness(0), - Background = Brushes.Transparent, - Padding = new Thickness(0), - VerticalContentAlignment = VerticalAlignment.Center, - FontSize = UiUtil.ScaledFontSize(12), - [!TextBox.TextProperty] = new Binding(nameof(vm.ModelsFolder)), - }; - grid.Add(folderText, 9, 1); - - return new Border - { - Child = grid, - Padding = new Thickness(14), - CornerRadius = new CornerRadius(6), - BorderThickness = new Thickness(1), - BorderBrush = new SolidColorBrush(Color.FromArgb(0x40, 0x80, 0x80, 0x80)), - }; - } - - private static StackPanel MakeSpeedPanel() - { - var slider = new Slider - { - Minimum = 0.5, - Maximum = 2.0, - Width = 220, - TickFrequency = 0.05, - IsSnapToTickEnabled = true, - VerticalAlignment = VerticalAlignment.Center, - [!Slider.ValueProperty] = new Binding(nameof(CosyVoice3CrispAsrSettingsViewModel.Speed)), - }; - var label = new TextBlock - { - VerticalAlignment = VerticalAlignment.Center, - FontWeight = FontWeight.SemiBold, - Margin = new Thickness(8, 0, 0, 0), - Width = 80, - [!TextBlock.TextProperty] = new Binding(nameof(CosyVoice3CrispAsrSettingsViewModel.SpeedLabel)), - }; - return new StackPanel - { - Orientation = Orientation.Horizontal, - Children = { slider, label }, - }; - } - - private static StackPanel MakeStatusPanel(string brushBindingPath, string labelBindingPath) - { - var dot = new Ellipse - { - Width = 10, - Height = 10, - VerticalAlignment = VerticalAlignment.Center, - [!Ellipse.FillProperty] = new Binding(brushBindingPath), - }; - var text = new TextBlock - { - VerticalAlignment = VerticalAlignment.Center, - FontWeight = FontWeight.SemiBold, - Margin = new Thickness(8, 0, 0, 0), - [!TextBlock.TextProperty] = new Binding(labelBindingPath), - }; - return new StackPanel - { - Orientation = Orientation.Horizontal, - Children = { dot, text }, - }; - } - - private static Grid BuildActions(CosyVoice3CrispAsrSettingsViewModel vm) - { - var openModelsFolder = UiUtil.MakeButton(Se.Language.General.OpenContainingFolder, vm.OpenModelsFolderCommand).WithIconLeft(IconNames.FolderOpen); - var openVoicesFolder = UiUtil.MakeButton("Voices folder", vm.OpenVoicesFolderCommand).WithIconLeft(IconNames.FolderOpen); - var close = UiUtil.MakeButton(Se.Language.General.Close, vm.OkCommand); - - var leftPanel = new StackPanel - { - Orientation = Orientation.Horizontal, - Spacing = 8, - Children = { openModelsFolder, openVoicesFolder }, - }; - - var rightPanel = new StackPanel - { - Orientation = Orientation.Horizontal, - HorizontalAlignment = HorizontalAlignment.Right, - Spacing = 8, - Children = { close }, - }; - - var grid = new Grid - { - ColumnDefinitions = + Spacing = 4, + Children = { - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Auto) }, - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) }, - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Auto) }, + sourceLanguageCombo, + TtsSettingsChrome.MakeHint("Language spoken in imported reference WAVs (for cross-lingual cloning). " + + "Auto detects it from each voice's transcript."), }, }; - grid.Add(leftPanel, 0, 0); - grid.Add(rightPanel, 0, 2); - return grid; - } - private static TextBlock MakeLabel(string text) => new() - { - Text = text, - Opacity = 0.7, - VerticalAlignment = VerticalAlignment.Center, - }; + var rows = TtsSettingsChrome.MakeRows(LabelWidth, + (Se.Language.Video.Presets, TtsSettingsChrome.MakeValue(nameof(vm.PresetsLabel))), + (Se.Language.Video.Voices, TtsSettingsChrome.MakeValue(nameof(vm.VoicesLabel), + TtsSettingsChrome.MakeFolderButton(Se.Language.Video.TextToSpeech.OpenVoicesFolder, vm.OpenVoicesFolderCommand))), + (Se.Language.General.Speed, TtsSettingsChrome.MakeSlider(Se.Language.General.Speed, 0.5, 2.0, 0.05, nameof(vm.Speed), nameof(vm.SpeedLabel))), + ("Reference language", sourceLanguage)); + return TtsSettingsChrome.MakeSection(Se.Language.General.Voice, rows); + } protected override void OnKeyDown(KeyEventArgs e) { diff --git a/src/ui/Features/Video/TextToSpeech/DotsTtsCrispAsrSettings/DotsTtsCrispAsrSettingsWindow.cs b/src/ui/Features/Video/TextToSpeech/DotsTtsCrispAsrSettings/DotsTtsCrispAsrSettingsWindow.cs index c18c3b8aa9b..08f70b2c35d 100644 --- a/src/ui/Features/Video/TextToSpeech/DotsTtsCrispAsrSettings/DotsTtsCrispAsrSettingsWindow.cs +++ b/src/ui/Features/Video/TextToSpeech/DotsTtsCrispAsrSettings/DotsTtsCrispAsrSettingsWindow.cs @@ -1,20 +1,17 @@ -using Avalonia; using Avalonia.Controls; -using Avalonia.Controls.Shapes; -using Avalonia.Data; using Avalonia.Input; -using Avalonia.Layout; -using Avalonia.Media; using Nikse.SubtitleEdit.Features.Video.TextToSpeech.Engines; using Nikse.SubtitleEdit.Logic; using Nikse.SubtitleEdit.Logic.Config; namespace Nikse.SubtitleEdit.Features.Video.TextToSpeech.DotsTtsCrispAsrSettings; +/// +/// dots.tts (CrispASR) settings, in the editor look (see ). +/// public class DotsTtsCrispAsrSettingsWindow : Window { private const int LabelWidth = 150; - private const int ValueWidth = 360; private readonly DotsTtsCrispAsrSettingsViewModel _vm; @@ -22,252 +19,48 @@ public DotsTtsCrispAsrSettingsWindow(DotsTtsCrispAsrSettingsViewModel vm) { UiUtil.InitializeWindow(this, GetType().Name); Title = string.Format(Se.Language.Video.TtsCrispAsrSettingsTitle, "dots.tts"); - SizeToContent = SizeToContent.WidthAndHeight; - CanResize = false; - MinWidth = 580; + TtsSettingsChrome.Apply(this); _vm = vm; vm.Window = this; DataContext = vm; - Content = BuildContent(vm); - - var ok = UiUtil.MakeButtonOk(vm.OkCommand); - UiUtil.FocusOnFirstActivation(this, ok); - } - - private Border BuildContent(DotsTtsCrispAsrSettingsViewModel vm) - { - var header = BuildHeader(); - var details = BuildDetails(vm); - var actions = BuildActions(vm); - - var stack = new StackPanel - { - Orientation = Orientation.Vertical, - Spacing = 14, - Children = { header, details, actions }, - }; - - var outerGrid = new Grid - { - Margin = UiUtil.MakeWindowMargin(), - }; - outerGrid.Children.Add(stack); - - return new Border - { - Child = outerGrid, - Padding = new Thickness(4), - }; - } - - private static StackPanel BuildHeader() - { - var title = new TextBlock - { - Text = "dots.tts (CrispASR)", - FontSize = UiUtil.ScaledFontSize(18), - FontWeight = FontWeight.SemiBold, - }; - - var subtitle = new TextBlock - { - Text = new DotsTtsCrispAsr().Description, - FontSize = UiUtil.ScaledFontSize(12), - Opacity = 0.75, - Margin = new Thickness(0, 2, 0, 0), - }; - - return new StackPanel - { - Orientation = Orientation.Vertical, - Spacing = 0, - Children = { title, subtitle }, - }; - } - - private static Border BuildDetails(DotsTtsCrispAsrSettingsViewModel vm) - { - var grid = new Grid - { - ColumnDefinitions = - { - new ColumnDefinition { Width = new GridLength(LabelWidth, GridUnitType.Pixel) }, - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) }, - }, - RowDefinitions = - { - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - }, - ColumnSpacing = 12, - RowSpacing = 10, - }; - - grid.Add(MakeLabel(Se.Language.General.Engine), 0, 0); - var enginePanel = MakeStatusPanel(nameof(vm.EngineBrush), nameof(vm.EngineLabel)); - var engineButton = UiUtil.MakeButton(string.Empty, vm.RedownloadEngineCommand) - .WithIconLeftBindText(IconNames.Download, nameof(vm.EngineDownloadButtonText)) - .WithMarginLeft(12); - enginePanel.Children.Add(engineButton); - grid.Add(enginePanel, 0, 1); - - grid.Add(MakeLabel("Model " + DotsTtsCrispAsr.ModelKeyQ4K), 1, 0); - grid.Add(MakeStatusPanel(nameof(vm.CoreQ4KBrush), nameof(vm.CoreQ4KLabel)), 1, 1); - - grid.Add(MakeLabel("Model " + DotsTtsCrispAsr.ModelKeyQ8_0), 2, 0); - grid.Add(MakeStatusPanel(nameof(vm.CoreQ8_0Brush), nameof(vm.CoreQ8_0Label)), 2, 1); - - grid.Add(MakeLabel("Model " + DotsTtsCrispAsr.ModelKeyF16), 3, 0); - grid.Add(MakeStatusPanel(nameof(vm.CoreF16Brush), nameof(vm.CoreF16Label)), 3, 1); - - grid.Add(MakeLabel("BigVGAN vocoder"), 4, 0); - grid.Add(MakeStatusPanel(nameof(vm.VocoderBrush), nameof(vm.VocoderLabel)), 4, 1); - - grid.Add(MakeLabel("CAM++ speaker encoder"), 5, 0); - grid.Add(MakeStatusPanel(nameof(vm.SpeakerEncoderBrush), nameof(vm.SpeakerEncoderLabel)), 5, 1); - - grid.Add(MakeLabel(Se.Language.Video.Voices), 6, 0); - var voicesText = new TextBlock - { - VerticalAlignment = VerticalAlignment.Center, - FontWeight = FontWeight.SemiBold, - [!TextBlock.TextProperty] = new Binding(nameof(vm.VoicesLabel)), - }; - grid.Add(voicesText, 6, 1); - - grid.Add(MakeLabel("Quality"), 7, 0); - grid.Add(MakeOdeStepsPanel(), 7, 1); - - grid.Add(MakeLabel(Se.Language.General.InstallFolder), 8, 0); - var folderText = new TextBox - { - IsReadOnly = true, - Width = ValueWidth, - BorderThickness = new Thickness(0), - Background = Brushes.Transparent, - Padding = new Thickness(0), - VerticalContentAlignment = VerticalAlignment.Center, - FontSize = UiUtil.ScaledFontSize(12), - [!TextBox.TextProperty] = new Binding(nameof(vm.ModelsFolder)), - }; - grid.Add(folderText, 8, 1); - - return new Border - { - Child = grid, - Padding = new Thickness(14), - CornerRadius = new CornerRadius(6), - BorderThickness = new Thickness(1), - BorderBrush = new SolidColorBrush(Color.FromArgb(0x40, 0x80, 0x80, 0x80)), - }; - } + var close = UiUtil.MakeButton(Se.Language.General.Close, vm.OkCommand); + Content = TtsSettingsChrome.MakeContent( + TtsSettingsChrome.MakeHeader("dots.tts (CrispASR)", new DotsTtsCrispAsr().Description), + TtsSettingsChrome.MakeButtonBar(close), + BuildModels(vm), + BuildVoice(vm)); - /// - /// Flow-matching Euler steps: the one real quality/speed knob dots.tts exposes. Measured on - /// M4 Metal with the F16 core, 4 steps synthesised in 25 s where 16 steps took 38 s for a - /// comparable line, so this is worth surfacing rather than hard-coding. - /// - private static StackPanel MakeOdeStepsPanel() - { - var slider = new Slider - { - Minimum = DotsTtsCrispAsr.MinOdeSteps, - Maximum = DotsTtsCrispAsr.MaxOdeSteps, - Width = 220, - TickFrequency = 1, - IsSnapToTickEnabled = true, - VerticalAlignment = VerticalAlignment.Center, - [!Slider.ValueProperty] = new Binding(nameof(DotsTtsCrispAsrSettingsViewModel.OdeSteps)), - }; - var label = new TextBlock - { - VerticalAlignment = VerticalAlignment.Center, - FontWeight = FontWeight.SemiBold, - Margin = new Thickness(8, 0, 0, 0), - Width = 80, - [!TextBlock.TextProperty] = new Binding(nameof(DotsTtsCrispAsrSettingsViewModel.OdeStepsLabel)), - }; - return new StackPanel - { - Orientation = Orientation.Horizontal, - Children = { slider, label }, - }; + UiUtil.FocusOnFirstActivation(this, close); } - private static StackPanel MakeStatusPanel(string brushBindingPath, string labelBindingPath) + private static Border BuildModels(DotsTtsCrispAsrSettingsViewModel vm) { - var dot = new Ellipse - { - Width = 10, - Height = 10, - VerticalAlignment = VerticalAlignment.Center, - [!Ellipse.FillProperty] = new Binding(brushBindingPath), - }; - var text = new TextBlock - { - VerticalAlignment = VerticalAlignment.Center, - FontWeight = FontWeight.SemiBold, - Margin = new Thickness(8, 0, 0, 0), - [!TextBlock.TextProperty] = new Binding(labelBindingPath), - }; - return new StackPanel - { - Orientation = Orientation.Horizontal, - Children = { dot, text }, - }; + var rows = TtsSettingsChrome.MakeRows(LabelWidth, + (Se.Language.General.Engine, TtsSettingsChrome.MakeStatus(nameof(vm.EngineBrush), nameof(vm.EngineLabel), + TtsSettingsChrome.MakeEngineDownloadButton(vm.RedownloadEngineCommand, nameof(vm.EngineDownloadButtonText)))), + ("Model " + DotsTtsCrispAsr.ModelKeyQ4K, TtsSettingsChrome.MakeStatus(nameof(vm.CoreQ4KBrush), nameof(vm.CoreQ4KLabel))), + ("Model " + DotsTtsCrispAsr.ModelKeyQ8_0, TtsSettingsChrome.MakeStatus(nameof(vm.CoreQ8_0Brush), nameof(vm.CoreQ8_0Label))), + ("Model " + DotsTtsCrispAsr.ModelKeyF16, TtsSettingsChrome.MakeStatus(nameof(vm.CoreF16Brush), nameof(vm.CoreF16Label))), + ("BigVGAN vocoder", TtsSettingsChrome.MakeStatus(nameof(vm.VocoderBrush), nameof(vm.VocoderLabel))), + ("CAM++ speaker encoder", TtsSettingsChrome.MakeStatus(nameof(vm.SpeakerEncoderBrush), nameof(vm.SpeakerEncoderLabel))), + (Se.Language.General.InstallFolder, TtsSettingsChrome.MakeFolder(nameof(vm.ModelsFolder), vm.OpenModelsFolderCommand))); + return TtsSettingsChrome.MakeSection(Se.Language.General.Models, rows); } - private static Grid BuildActions(DotsTtsCrispAsrSettingsViewModel vm) + private static Border BuildVoice(DotsTtsCrispAsrSettingsViewModel vm) { - var openModelsFolder = UiUtil.MakeButton(Se.Language.General.OpenContainingFolder, vm.OpenModelsFolderCommand).WithIconLeft(IconNames.FolderOpen); - var openVoicesFolder = UiUtil.MakeButton("Voices folder", vm.OpenVoicesFolderCommand).WithIconLeft(IconNames.FolderOpen); - var close = UiUtil.MakeButton(Se.Language.General.Close, vm.OkCommand); - - var leftPanel = new StackPanel - { - Orientation = Orientation.Horizontal, - Spacing = 8, - Children = { openModelsFolder, openVoicesFolder }, - }; - - var rightPanel = new StackPanel - { - Orientation = Orientation.Horizontal, - HorizontalAlignment = HorizontalAlignment.Right, - Spacing = 8, - Children = { close }, - }; - - var grid = new Grid - { - ColumnDefinitions = - { - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Auto) }, - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) }, - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Auto) }, - }, - }; - grid.Add(leftPanel, 0, 0); - grid.Add(rightPanel, 0, 2); - return grid; + var rows = TtsSettingsChrome.MakeRows(LabelWidth, + (Se.Language.Video.Voices, TtsSettingsChrome.MakeValue(nameof(vm.VoicesLabel), + TtsSettingsChrome.MakeFolderButton(Se.Language.Video.TextToSpeech.OpenVoicesFolder, vm.OpenVoicesFolderCommand))), + // Flow-matching Euler steps: the one real quality/speed knob dots.tts exposes. Measured on + // M4 Metal with the F16 core, 4 steps synthesised in 25 s where 16 steps took 38 s for a + // comparable line, so this is worth surfacing rather than hard-coding. + (Se.Language.General.Quality, TtsSettingsChrome.MakeSlider(Se.Language.General.Quality, DotsTtsCrispAsr.MinOdeSteps, DotsTtsCrispAsr.MaxOdeSteps, 1, nameof(vm.OdeSteps), nameof(vm.OdeStepsLabel)))); + return TtsSettingsChrome.MakeSection(Se.Language.General.Voice, rows); } - private static TextBlock MakeLabel(string text) => new() - { - Text = text, - Opacity = 0.7, - VerticalAlignment = VerticalAlignment.Center, - }; - protected override void OnKeyDown(KeyEventArgs e) { base.OnKeyDown(e); diff --git a/src/ui/Features/Video/TextToSpeech/F5TtsCrispAsrSettings/F5TtsCrispAsrSettingsWindow.cs b/src/ui/Features/Video/TextToSpeech/F5TtsCrispAsrSettings/F5TtsCrispAsrSettingsWindow.cs index 5f86c3f7072..3baeab0a910 100644 --- a/src/ui/Features/Video/TextToSpeech/F5TtsCrispAsrSettings/F5TtsCrispAsrSettingsWindow.cs +++ b/src/ui/Features/Video/TextToSpeech/F5TtsCrispAsrSettings/F5TtsCrispAsrSettingsWindow.cs @@ -1,20 +1,17 @@ -using Avalonia; using Avalonia.Controls; -using Avalonia.Controls.Shapes; -using Avalonia.Data; using Avalonia.Input; -using Avalonia.Layout; -using Avalonia.Media; using Nikse.SubtitleEdit.Features.Video.TextToSpeech.Engines; using Nikse.SubtitleEdit.Logic; using Nikse.SubtitleEdit.Logic.Config; namespace Nikse.SubtitleEdit.Features.Video.TextToSpeech.F5TtsCrispAsrSettings; +/// +/// F5-TTS (CrispASR) settings, in the editor look (see ). +/// public class F5TtsCrispAsrSettingsWindow : Window { private const int LabelWidth = 150; - private const int ValueWidth = 360; private readonly F5TtsCrispAsrSettingsViewModel _vm; @@ -22,231 +19,41 @@ public F5TtsCrispAsrSettingsWindow(F5TtsCrispAsrSettingsViewModel vm) { UiUtil.InitializeWindow(this, GetType().Name); Title = string.Format(Se.Language.Video.TtsCrispAsrSettingsTitle, "F5-TTS"); - SizeToContent = SizeToContent.WidthAndHeight; - CanResize = false; - MinWidth = 580; + TtsSettingsChrome.Apply(this); _vm = vm; vm.Window = this; DataContext = vm; - Content = BuildContent(vm); - - var ok = UiUtil.MakeButtonOk(vm.OkCommand); - UiUtil.FocusOnFirstActivation(this, ok); - } - - private Border BuildContent(F5TtsCrispAsrSettingsViewModel vm) - { - var header = BuildHeader(); - var details = BuildDetails(vm); - var actions = BuildActions(vm); - - var stack = new StackPanel - { - Orientation = Orientation.Vertical, - Spacing = 14, - Children = { header, details, actions }, - }; - - var outerGrid = new Grid - { - Margin = UiUtil.MakeWindowMargin(), - }; - outerGrid.Children.Add(stack); - - return new Border - { - Child = outerGrid, - Padding = new Thickness(4), - }; - } - - private static StackPanel BuildHeader() - { - var title = new TextBlock - { - Text = "F5-TTS (CrispASR)", - FontSize = UiUtil.ScaledFontSize(18), - FontWeight = FontWeight.SemiBold, - }; - - var subtitle = new TextBlock - { - Text = new F5TtsCrispAsr().Description, - FontSize = UiUtil.ScaledFontSize(12), - Opacity = 0.75, - Margin = new Thickness(0, 2, 0, 0), - }; - - return new StackPanel - { - Orientation = Orientation.Vertical, - Spacing = 0, - Children = { title, subtitle }, - }; - } - - private static Border BuildDetails(F5TtsCrispAsrSettingsViewModel vm) - { - var grid = new Grid - { - ColumnDefinitions = - { - new ColumnDefinition { Width = new GridLength(LabelWidth, GridUnitType.Pixel) }, - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) }, - }, - RowDefinitions = - { - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - }, - ColumnSpacing = 12, - RowSpacing = 10, - }; - - grid.Add(MakeLabel(Se.Language.General.Engine), 0, 0); - var enginePanel = MakeStatusPanel(nameof(vm.EngineBrush), nameof(vm.EngineLabel)); - var engineButton = UiUtil.MakeButton(string.Empty, vm.RedownloadEngineCommand) - .WithIconLeftBindText(IconNames.Download, nameof(vm.EngineDownloadButtonText)) - .WithMarginLeft(12); - enginePanel.Children.Add(engineButton); - grid.Add(enginePanel, 0, 1); - - grid.Add(MakeLabel(string.Format(Se.Language.Video.TalkerX, F5TtsCrispAsr.ModelKeyF16)), 1, 0); - grid.Add(MakeStatusPanel(nameof(vm.TalkerF16Brush), nameof(vm.TalkerF16Label)), 1, 1); - - grid.Add(MakeLabel(Se.Language.Video.Voices), 2, 0); - var voicesText = new TextBlock - { - VerticalAlignment = VerticalAlignment.Center, - FontWeight = FontWeight.SemiBold, - [!TextBlock.TextProperty] = new Binding(nameof(vm.VoicesLabel)), - }; - grid.Add(voicesText, 2, 1); - - grid.Add(MakeLabel(Se.Language.General.Speed), 3, 0); - grid.Add(MakeSpeedPanel(), 3, 1); - - grid.Add(MakeLabel(Se.Language.General.InstallFolder), 4, 0); - var folderText = new TextBox - { - IsReadOnly = true, - Width = ValueWidth, - BorderThickness = new Thickness(0), - Background = Brushes.Transparent, - Padding = new Thickness(0), - VerticalContentAlignment = VerticalAlignment.Center, - FontSize = UiUtil.ScaledFontSize(12), - [!TextBox.TextProperty] = new Binding(nameof(vm.ModelsFolder)), - }; - grid.Add(folderText, 4, 1); + var close = UiUtil.MakeButton(Se.Language.General.Close, vm.OkCommand); + Content = TtsSettingsChrome.MakeContent( + TtsSettingsChrome.MakeHeader("F5-TTS (CrispASR)", new F5TtsCrispAsr().Description), + TtsSettingsChrome.MakeButtonBar(close), + BuildModels(vm), + BuildVoice(vm)); - return new Border - { - Child = grid, - Padding = new Thickness(14), - CornerRadius = new CornerRadius(6), - BorderThickness = new Thickness(1), - BorderBrush = new SolidColorBrush(Color.FromArgb(0x40, 0x80, 0x80, 0x80)), - }; + UiUtil.FocusOnFirstActivation(this, close); } - private static StackPanel MakeSpeedPanel() + private static Border BuildModels(F5TtsCrispAsrSettingsViewModel vm) { - var slider = new Slider - { - Minimum = 0.5, - Maximum = 2.0, - Width = 220, - TickFrequency = 0.05, - IsSnapToTickEnabled = true, - VerticalAlignment = VerticalAlignment.Center, - [!Slider.ValueProperty] = new Binding(nameof(F5TtsCrispAsrSettingsViewModel.Speed)), - }; - var label = new TextBlock - { - VerticalAlignment = VerticalAlignment.Center, - FontWeight = FontWeight.SemiBold, - Margin = new Thickness(8, 0, 0, 0), - Width = 80, - [!TextBlock.TextProperty] = new Binding(nameof(F5TtsCrispAsrSettingsViewModel.SpeedLabel)), - }; - return new StackPanel - { - Orientation = Orientation.Horizontal, - Children = { slider, label }, - }; + var rows = TtsSettingsChrome.MakeRows(LabelWidth, + (Se.Language.General.Engine, TtsSettingsChrome.MakeStatus(nameof(vm.EngineBrush), nameof(vm.EngineLabel), + TtsSettingsChrome.MakeEngineDownloadButton(vm.RedownloadEngineCommand, nameof(vm.EngineDownloadButtonText)))), + (string.Format(Se.Language.Video.TalkerX, F5TtsCrispAsr.ModelKeyF16), TtsSettingsChrome.MakeStatus(nameof(vm.TalkerF16Brush), nameof(vm.TalkerF16Label))), + (Se.Language.General.InstallFolder, TtsSettingsChrome.MakeFolder(nameof(vm.ModelsFolder), vm.OpenModelsFolderCommand))); + return TtsSettingsChrome.MakeSection(Se.Language.General.Models, rows); } - private static StackPanel MakeStatusPanel(string brushBindingPath, string labelBindingPath) + private static Border BuildVoice(F5TtsCrispAsrSettingsViewModel vm) { - var dot = new Ellipse - { - Width = 10, - Height = 10, - VerticalAlignment = VerticalAlignment.Center, - [!Ellipse.FillProperty] = new Binding(brushBindingPath), - }; - var text = new TextBlock - { - VerticalAlignment = VerticalAlignment.Center, - FontWeight = FontWeight.SemiBold, - Margin = new Thickness(8, 0, 0, 0), - [!TextBlock.TextProperty] = new Binding(labelBindingPath), - }; - return new StackPanel - { - Orientation = Orientation.Horizontal, - Children = { dot, text }, - }; + var rows = TtsSettingsChrome.MakeRows(LabelWidth, + (Se.Language.Video.Voices, TtsSettingsChrome.MakeValue(nameof(vm.VoicesLabel), + TtsSettingsChrome.MakeFolderButton(Se.Language.Video.TextToSpeech.OpenVoicesFolder, vm.OpenVoicesFolderCommand))), + (Se.Language.General.Speed, TtsSettingsChrome.MakeSlider(Se.Language.General.Speed, 0.5, 2.0, 0.05, nameof(vm.Speed), nameof(vm.SpeedLabel)))); + return TtsSettingsChrome.MakeSection(Se.Language.General.Voice, rows); } - private static Grid BuildActions(F5TtsCrispAsrSettingsViewModel vm) - { - var openModelsFolder = UiUtil.MakeButton(Se.Language.General.OpenContainingFolder, vm.OpenModelsFolderCommand).WithIconLeft(IconNames.FolderOpen); - var openVoicesFolder = UiUtil.MakeButton("Voices folder", vm.OpenVoicesFolderCommand).WithIconLeft(IconNames.FolderOpen); - var close = UiUtil.MakeButton(Se.Language.General.Close, vm.OkCommand); - - var leftPanel = new StackPanel - { - Orientation = Orientation.Horizontal, - Spacing = 8, - Children = { openModelsFolder, openVoicesFolder }, - }; - - var rightPanel = new StackPanel - { - Orientation = Orientation.Horizontal, - HorizontalAlignment = HorizontalAlignment.Right, - Spacing = 8, - Children = { close }, - }; - - var grid = new Grid - { - ColumnDefinitions = - { - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Auto) }, - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) }, - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Auto) }, - }, - }; - grid.Add(leftPanel, 0, 0); - grid.Add(rightPanel, 0, 2); - return grid; - } - - private static TextBlock MakeLabel(string text) => new() - { - Text = text, - Opacity = 0.7, - VerticalAlignment = VerticalAlignment.Center, - }; - protected override void OnKeyDown(KeyEventArgs e) { base.OnKeyDown(e); diff --git a/src/ui/Features/Video/TextToSpeech/IndexTts25AudioCppSettings/IndexTts25AudioCppSettingsWindow.cs b/src/ui/Features/Video/TextToSpeech/IndexTts25AudioCppSettings/IndexTts25AudioCppSettingsWindow.cs index bfc5e9f8023..337d80a903a 100644 --- a/src/ui/Features/Video/TextToSpeech/IndexTts25AudioCppSettings/IndexTts25AudioCppSettingsWindow.cs +++ b/src/ui/Features/Video/TextToSpeech/IndexTts25AudioCppSettings/IndexTts25AudioCppSettingsWindow.cs @@ -1,20 +1,20 @@ -using Avalonia; +using Avalonia.Automation; using Avalonia.Controls; -using Avalonia.Controls.Shapes; using Avalonia.Data; using Avalonia.Input; using Avalonia.Layout; -using Avalonia.Media; using Nikse.SubtitleEdit.Features.Video.TextToSpeech.Engines; using Nikse.SubtitleEdit.Logic; using Nikse.SubtitleEdit.Logic.Config; namespace Nikse.SubtitleEdit.Features.Video.TextToSpeech.IndexTts25AudioCppSettings; +/// +/// IndexTTS 2.5 (audio.cpp) settings, in the editor look (see ). +/// public class IndexTts25AudioCppSettingsWindow : Window { private const int LabelWidth = 150; - private const int ValueWidth = 360; private readonly IndexTts25AudioCppSettingsViewModel _vm; @@ -22,287 +22,62 @@ public IndexTts25AudioCppSettingsWindow(IndexTts25AudioCppSettingsViewModel vm) { UiUtil.InitializeWindow(this, GetType().Name); Title = Se.Language.Video.IndexTts25SettingsTitle; - SizeToContent = SizeToContent.WidthAndHeight; - CanResize = false; - MinWidth = 620; + TtsSettingsChrome.Apply(this); _vm = vm; vm.Window = this; DataContext = vm; - Content = BuildContent(vm); + var close = UiUtil.MakeButtonOk(vm.OkCommand); + Content = TtsSettingsChrome.MakeContent( + TtsSettingsChrome.MakeHeader("IndexTTS 2.5 (audio.cpp)", new IndexTts25AudioCpp().Description), + TtsSettingsChrome.MakeButtonBar(close), + BuildModels(vm), + BuildVoice(vm)); - var ok = UiUtil.MakeButtonOk(vm.OkCommand); - UiUtil.FocusOnFirstActivation(this, ok); + UiUtil.FocusOnFirstActivation(this, close); } - private Border BuildContent(IndexTts25AudioCppSettingsViewModel vm) + private static Border BuildModels(IndexTts25AudioCppSettingsViewModel vm) { - var stack = new StackPanel - { - Orientation = Orientation.Vertical, - Spacing = 14, - Children = { BuildHeader(), BuildDetails(vm), BuildActions(vm) }, - }; - - var outerGrid = new Grid { Margin = UiUtil.MakeWindowMargin() }; - outerGrid.Children.Add(stack); - - return new Border - { - Child = outerGrid, - Padding = new Thickness(4), - }; + var rows = TtsSettingsChrome.MakeRows(LabelWidth, + (Se.Language.General.Engine, TtsSettingsChrome.MakeStatus(nameof(vm.EngineBrush), nameof(vm.EngineLabel), + TtsSettingsChrome.MakeEngineDownloadButton(vm.RedownloadEngineCommand, nameof(vm.EngineDownloadButtonText)))), + ("Model " + IndexTts25AudioCpp.ModelKeyQ8_0, TtsSettingsChrome.MakeStatus(nameof(vm.ModelQ8_0Brush), nameof(vm.ModelQ8_0Label), + TtsSettingsChrome.MakeModelDownloadButton(vm.DownloadModelCommand, IndexTts25AudioCpp.ModelKeyQ8_0))), + ("Model " + IndexTts25AudioCpp.ModelKeyF16, TtsSettingsChrome.MakeStatus(nameof(vm.ModelF16Brush), nameof(vm.ModelF16Label), + TtsSettingsChrome.MakeModelDownloadButton(vm.DownloadModelCommand, IndexTts25AudioCpp.ModelKeyF16))), + (Se.Language.General.InstallFolder, TtsSettingsChrome.MakeFolder(nameof(vm.ModelsFolder), vm.OpenModelsFolderCommand))); + return TtsSettingsChrome.MakeSection(Se.Language.General.Models, rows); } - private static StackPanel BuildHeader() + private static Border BuildVoice(IndexTts25AudioCppSettingsViewModel vm) { - var title = new TextBlock - { - Text = "IndexTTS 2.5 (audio.cpp)", - FontSize = UiUtil.ScaledFontSize(18), - FontWeight = FontWeight.SemiBold, - }; - - var subtitle = new TextBlock - { - Text = new IndexTts25AudioCpp().Description, - FontSize = UiUtil.ScaledFontSize(12), - Opacity = 0.75, - Margin = new Thickness(0, 2, 0, 0), - }; - - return new StackPanel - { - Orientation = Orientation.Vertical, - Spacing = 0, - Children = { title, subtitle }, - }; - } - - private static Border BuildDetails(IndexTts25AudioCppSettingsViewModel vm) - { - var grid = new Grid - { - ColumnDefinitions = - { - new ColumnDefinition { Width = new GridLength(LabelWidth, GridUnitType.Pixel) }, - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) }, - }, - RowDefinitions = - { - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, // engine - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, // Q8_0 - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, // F16 - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, // voices - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, // emotion - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, // emotion strength - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, // speed - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, // folder - }, - ColumnSpacing = 12, - RowSpacing = 10, - }; - - grid.Add(MakeLabel(Se.Language.General.Engine), 0, 0); - var enginePanel = MakeStatusPanel(nameof(vm.EngineBrush), nameof(vm.EngineLabel)); - var engineButton = UiUtil.MakeButton(string.Empty, vm.RedownloadEngineCommand) - .WithIconLeftBindText(IconNames.Download, nameof(vm.EngineDownloadButtonText)) - .WithMarginLeft(12); - enginePanel.Children.Add(engineButton); - grid.Add(enginePanel, 0, 1); - - grid.Add(MakeLabel("Model " + IndexTts25AudioCpp.ModelKeyQ8_0), 1, 0); - var q8Panel = MakeStatusPanel(nameof(vm.ModelQ8_0Brush), nameof(vm.ModelQ8_0Label)); - q8Panel.Children.Add(UiUtil.MakeButton(string.Empty, vm.DownloadModelCommand, IndexTts25AudioCpp.ModelKeyQ8_0) - .WithIconLeft(IconNames.Download) - .WithMarginLeft(12)); - grid.Add(q8Panel, 1, 1); - - grid.Add(MakeLabel("Model " + IndexTts25AudioCpp.ModelKeyF16), 2, 0); - var f16Panel = MakeStatusPanel(nameof(vm.ModelF16Brush), nameof(vm.ModelF16Label)); - f16Panel.Children.Add(UiUtil.MakeButton(string.Empty, vm.DownloadModelCommand, IndexTts25AudioCpp.ModelKeyF16) - .WithIconLeft(IconNames.Download) - .WithMarginLeft(12)); - grid.Add(f16Panel, 2, 1); - - grid.Add(MakeLabel(Se.Language.Video.Voices), 3, 0); - grid.Add(new TextBlock - { - VerticalAlignment = VerticalAlignment.Center, - FontWeight = FontWeight.SemiBold, - [!TextBlock.TextProperty] = new Binding(nameof(vm.VoicesLabel)), - }, 3, 1); - - grid.Add(MakeLabel(Se.Language.Video.IndexTts25Emotion), 4, 0); var emotionCombo = new ComboBox { Width = 200, + HorizontalAlignment = HorizontalAlignment.Left, VerticalAlignment = VerticalAlignment.Center, [!ItemsControl.ItemsSourceProperty] = new Binding(nameof(vm.Emotions)), [!ComboBox.SelectedItemProperty] = new Binding(nameof(vm.SelectedEmotion)), }; - grid.Add(emotionCombo, 4, 1); - - grid.Add(MakeLabel(Se.Language.Video.IndexTts25EmotionStrength), 5, 0); - grid.Add(MakeEmotionAlphaPanel(), 5, 1); - - grid.Add(MakeLabel(Se.Language.General.Speed), 6, 0); - grid.Add(MakeSpeedPanel(), 6, 1); - - grid.Add(MakeLabel(Se.Language.General.InstallFolder), 7, 0); - grid.Add(new TextBox - { - IsReadOnly = true, - Width = ValueWidth, - BorderThickness = new Thickness(0), - Background = Brushes.Transparent, - Padding = new Thickness(0), - VerticalContentAlignment = VerticalAlignment.Center, - FontSize = UiUtil.ScaledFontSize(12), - [!TextBox.TextProperty] = new Binding(nameof(vm.ModelsFolder)), - }, 7, 1); - - return new Border - { - Child = grid, - Padding = new Thickness(14), - CornerRadius = new CornerRadius(6), - BorderThickness = new Thickness(1), - BorderBrush = new SolidColorBrush(Color.FromArgb(0x40, 0x80, 0x80, 0x80)), - }; - } - - /// - /// Speaking rate, presented the way users expect: right is faster. The engine converts - /// this to IndexTTS-2.5's own `duration_factor`, which runs the other way (>1 = longer - /// output = slower speech). Range capped at 0.5-2.0, which is what the model supports. - /// - private static StackPanel MakeSpeedPanel() - { - var slider = new Slider - { - Minimum = 0.5, - Maximum = 2.0, - Width = 220, - TickFrequency = 0.05, - IsSnapToTickEnabled = true, - VerticalAlignment = VerticalAlignment.Center, - [!Slider.ValueProperty] = new Binding(nameof(IndexTts25AudioCppSettingsViewModel.Speed)), - }; - var label = new TextBlock - { - VerticalAlignment = VerticalAlignment.Center, - FontWeight = FontWeight.SemiBold, - Margin = new Thickness(8, 0, 0, 0), - Width = 90, - [!TextBlock.TextProperty] = new Binding(nameof(IndexTts25AudioCppSettingsViewModel.SpeedLabel)), - }; - return new StackPanel - { - Orientation = Orientation.Horizontal, - Children = { slider, label }, - }; + AutomationProperties.SetName(emotionCombo, Se.Language.Video.IndexTts25Emotion); + + var rows = TtsSettingsChrome.MakeRows(LabelWidth, + (Se.Language.Video.Voices, TtsSettingsChrome.MakeValue(nameof(vm.VoicesLabel), + TtsSettingsChrome.MakeFolderButton(Se.Language.Video.TextToSpeech.OpenVoicesFolder, vm.OpenVoicesFolderCommand))), + (Se.Language.Video.IndexTts25Emotion, emotionCombo), + // Emotion blend strength. Disabled while the emotion is "None", since the model ignores + // it without an emotion vector — a live slider that changes nothing reads as a bug. + (Se.Language.Video.IndexTts25EmotionStrength, TtsSettingsChrome.MakeSlider(Se.Language.Video.IndexTts25EmotionStrength, 0.0, 1.0, 0.05, + nameof(vm.EmotionAlpha), nameof(vm.EmotionAlphaLabel), nameof(vm.IsEmotionAlphaEnabled))), + // Speaking rate, presented the way users expect: right is faster. The engine converts + // this to IndexTTS-2.5's own `duration_factor`, which runs the other way (>1 = longer + // output = slower speech). Range capped at 0.5-2.0, which is what the model supports. + (Se.Language.General.Speed, TtsSettingsChrome.MakeSlider(Se.Language.General.Speed, 0.5, 2.0, 0.05, nameof(vm.Speed), nameof(vm.SpeedLabel)))); + return TtsSettingsChrome.MakeSection(Se.Language.General.Voice, rows); } - /// - /// Emotion blend strength. Disabled while the emotion is "None", since the model ignores - /// it without an emotion vector — a live slider that changes nothing reads as a bug. - /// - private static StackPanel MakeEmotionAlphaPanel() - { - var slider = new Slider - { - Minimum = 0.0, - Maximum = 1.0, - Width = 220, - TickFrequency = 0.05, - IsSnapToTickEnabled = true, - VerticalAlignment = VerticalAlignment.Center, - [!Slider.ValueProperty] = new Binding(nameof(IndexTts25AudioCppSettingsViewModel.EmotionAlpha)), - [!IsEnabledProperty] = new Binding(nameof(IndexTts25AudioCppSettingsViewModel.IsEmotionAlphaEnabled)), - }; - var label = new TextBlock - { - VerticalAlignment = VerticalAlignment.Center, - FontWeight = FontWeight.SemiBold, - Margin = new Thickness(8, 0, 0, 0), - Width = 150, - [!TextBlock.TextProperty] = new Binding(nameof(IndexTts25AudioCppSettingsViewModel.EmotionAlphaLabel)), - [!IsEnabledProperty] = new Binding(nameof(IndexTts25AudioCppSettingsViewModel.IsEmotionAlphaEnabled)), - }; - return new StackPanel - { - Orientation = Orientation.Horizontal, - Children = { slider, label }, - }; - } - - private static StackPanel MakeStatusPanel(string brushBindingPath, string labelBindingPath) - { - var dot = new Ellipse - { - Width = 10, - Height = 10, - VerticalAlignment = VerticalAlignment.Center, - [!Shape.FillProperty] = new Binding(brushBindingPath), - }; - var text = new TextBlock - { - VerticalAlignment = VerticalAlignment.Center, - FontWeight = FontWeight.SemiBold, - Margin = new Thickness(8, 0, 0, 0), - [!TextBlock.TextProperty] = new Binding(labelBindingPath), - }; - return new StackPanel - { - Orientation = Orientation.Horizontal, - Children = { dot, text }, - }; - } - - private static Control BuildActions(IndexTts25AudioCppSettingsViewModel vm) - { - var openModelsFolder = UiUtil.MakeButton(Se.Language.General.OpenContainingFolder, vm.OpenModelsFolderCommand).WithIconLeft(IconNames.FolderOpen); - var openVoicesFolder = UiUtil.MakeButton(Se.Language.Video.Voices, vm.OpenVoicesFolderCommand).WithIconLeft(IconNames.FolderOpen); - var close = UiUtil.MakeButtonOk(vm.OkCommand); - - var leftPanel = new StackPanel - { - Orientation = Orientation.Horizontal, - Spacing = 8, - Children = { openModelsFolder, openVoicesFolder }, - }; - - var rightPanel = new StackPanel - { - Orientation = Orientation.Horizontal, - HorizontalAlignment = HorizontalAlignment.Right, - Spacing = 8, - Children = { close }, - }; - - var grid = new Grid - { - ColumnDefinitions = - { - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Auto) }, - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) }, - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Auto) }, - }, - }; - grid.Add(leftPanel, 0, 0); - grid.Add(rightPanel, 0, 2); - return grid; - } - - private static TextBlock MakeLabel(string text) => new() - { - Text = text, - Opacity = 0.7, - VerticalAlignment = VerticalAlignment.Center, - }; - protected override void OnKeyDown(KeyEventArgs e) { base.OnKeyDown(e); diff --git a/src/ui/Features/Video/TextToSpeech/IndexTtsCrispAsrSettings/IndexTtsCrispAsrSettingsWindow.cs b/src/ui/Features/Video/TextToSpeech/IndexTtsCrispAsrSettings/IndexTtsCrispAsrSettingsWindow.cs index 1978ad4626b..80883f07334 100644 --- a/src/ui/Features/Video/TextToSpeech/IndexTtsCrispAsrSettings/IndexTtsCrispAsrSettingsWindow.cs +++ b/src/ui/Features/Video/TextToSpeech/IndexTtsCrispAsrSettings/IndexTtsCrispAsrSettingsWindow.cs @@ -1,20 +1,17 @@ -using Avalonia; using Avalonia.Controls; -using Avalonia.Controls.Shapes; -using Avalonia.Data; using Avalonia.Input; -using Avalonia.Layout; -using Avalonia.Media; using Nikse.SubtitleEdit.Features.Video.TextToSpeech.Engines; using Nikse.SubtitleEdit.Logic; using Nikse.SubtitleEdit.Logic.Config; namespace Nikse.SubtitleEdit.Features.Video.TextToSpeech.IndexTtsCrispAsrSettings; +/// +/// IndexTTS (CrispASR) settings, in the editor look (see ). +/// public class IndexTtsCrispAsrSettingsWindow : Window { private const int LabelWidth = 150; - private const int ValueWidth = 360; private readonly IndexTtsCrispAsrSettingsViewModel _vm; @@ -22,245 +19,46 @@ public IndexTtsCrispAsrSettingsWindow(IndexTtsCrispAsrSettingsViewModel vm) { UiUtil.InitializeWindow(this, GetType().Name); Title = string.Format(Se.Language.Video.TtsCrispAsrSettingsTitle, "IndexTTS"); - SizeToContent = SizeToContent.WidthAndHeight; - CanResize = false; - MinWidth = 580; + TtsSettingsChrome.Apply(this); _vm = vm; vm.Window = this; DataContext = vm; - Content = BuildContent(vm); - - var ok = UiUtil.MakeButtonOk(vm.OkCommand); - UiUtil.FocusOnFirstActivation(this, ok); - } - - private Border BuildContent(IndexTtsCrispAsrSettingsViewModel vm) - { - var header = BuildHeader(); - var details = BuildDetails(vm); - var actions = BuildActions(vm); - - var stack = new StackPanel - { - Orientation = Orientation.Vertical, - Spacing = 14, - Children = { header, details, actions }, - }; - - var outerGrid = new Grid - { - Margin = UiUtil.MakeWindowMargin(), - }; - outerGrid.Children.Add(stack); - - return new Border - { - Child = outerGrid, - Padding = new Thickness(4), - }; - } - - private static StackPanel BuildHeader() - { - var title = new TextBlock - { - Text = "IndexTTS (CrispASR)", - FontSize = UiUtil.ScaledFontSize(18), - FontWeight = FontWeight.SemiBold, - }; - - var subtitle = new TextBlock - { - Text = new IndexTtsCrispAsr().Description, - FontSize = UiUtil.ScaledFontSize(12), - Opacity = 0.75, - Margin = new Thickness(0, 2, 0, 0), - }; - - return new StackPanel - { - Orientation = Orientation.Vertical, - Spacing = 0, - Children = { title, subtitle }, - }; - } - - private static Border BuildDetails(IndexTtsCrispAsrSettingsViewModel vm) - { - var grid = new Grid - { - ColumnDefinitions = - { - new ColumnDefinition { Width = new GridLength(LabelWidth, GridUnitType.Pixel) }, - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) }, - }, - RowDefinitions = - { - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - }, - ColumnSpacing = 12, - RowSpacing = 10, - }; - - grid.Add(MakeLabel(Se.Language.General.Engine), 0, 0); - var enginePanel = MakeStatusPanel(nameof(vm.EngineBrush), nameof(vm.EngineLabel)); - var engineButton = UiUtil.MakeButton(string.Empty, vm.RedownloadEngineCommand) - .WithIconLeftBindText(IconNames.Download, nameof(vm.EngineDownloadButtonText)) - .WithMarginLeft(12); - enginePanel.Children.Add(engineButton); - grid.Add(enginePanel, 0, 1); - - grid.Add(MakeLabel("GPT " + IndexTtsCrispAsr.ModelKeyQ4K), 1, 0); - grid.Add(MakeStatusPanel(nameof(vm.TalkerQ4KBrush), nameof(vm.TalkerQ4KLabel)), 1, 1); - - grid.Add(MakeLabel("GPT " + IndexTtsCrispAsr.ModelKeyQ8_0), 2, 0); - grid.Add(MakeStatusPanel(nameof(vm.TalkerQ8_0Brush), nameof(vm.TalkerQ8_0Label)), 2, 1); - - grid.Add(MakeLabel("GPT " + IndexTtsCrispAsr.ModelKeyF16), 3, 0); - grid.Add(MakeStatusPanel(nameof(vm.TalkerF16Brush), nameof(vm.TalkerF16Label)), 3, 1); - - grid.Add(MakeLabel("BigVGAN codec"), 4, 0); - grid.Add(MakeStatusPanel(nameof(vm.CodecBrush), nameof(vm.CodecLabel)), 4, 1); - - grid.Add(MakeLabel(Se.Language.Video.Voices), 5, 0); - var voicesText = new TextBlock - { - VerticalAlignment = VerticalAlignment.Center, - FontWeight = FontWeight.SemiBold, - [!TextBlock.TextProperty] = new Binding(nameof(vm.VoicesLabel)), - }; - grid.Add(voicesText, 5, 1); - - grid.Add(MakeLabel(Se.Language.General.Speed), 6, 0); - grid.Add(MakeSpeedPanel(), 6, 1); - - grid.Add(MakeLabel(Se.Language.General.InstallFolder), 7, 0); - var folderText = new TextBox - { - IsReadOnly = true, - Width = ValueWidth, - BorderThickness = new Thickness(0), - Background = Brushes.Transparent, - Padding = new Thickness(0), - VerticalContentAlignment = VerticalAlignment.Center, - FontSize = UiUtil.ScaledFontSize(12), - [!TextBox.TextProperty] = new Binding(nameof(vm.ModelsFolder)), - }; - grid.Add(folderText, 7, 1); - - return new Border - { - Child = grid, - Padding = new Thickness(14), - CornerRadius = new CornerRadius(6), - BorderThickness = new Thickness(1), - BorderBrush = new SolidColorBrush(Color.FromArgb(0x40, 0x80, 0x80, 0x80)), - }; - } + var close = UiUtil.MakeButton(Se.Language.General.Close, vm.OkCommand); + Content = TtsSettingsChrome.MakeContent( + TtsSettingsChrome.MakeHeader("IndexTTS (CrispASR)", new IndexTtsCrispAsr().Description), + TtsSettingsChrome.MakeButtonBar(close), + BuildModels(vm), + BuildVoice(vm)); - private static StackPanel MakeSpeedPanel() - { - // Server accepts 0.25-4.0; cap UI at 0.5-2.0 since anything outside that range is - // already a hack rather than a "preferred pace" choice. - var slider = new Slider - { - Minimum = 0.5, - Maximum = 2.0, - Width = 220, - TickFrequency = 0.05, - IsSnapToTickEnabled = true, - VerticalAlignment = VerticalAlignment.Center, - [!Slider.ValueProperty] = new Binding(nameof(IndexTtsCrispAsrSettingsViewModel.Speed)), - }; - var label = new TextBlock - { - VerticalAlignment = VerticalAlignment.Center, - FontWeight = FontWeight.SemiBold, - Margin = new Thickness(8, 0, 0, 0), - Width = 80, - [!TextBlock.TextProperty] = new Binding(nameof(IndexTtsCrispAsrSettingsViewModel.SpeedLabel)), - }; - return new StackPanel - { - Orientation = Orientation.Horizontal, - Children = { slider, label }, - }; + UiUtil.FocusOnFirstActivation(this, close); } - private static StackPanel MakeStatusPanel(string brushBindingPath, string labelBindingPath) + private static Border BuildModels(IndexTtsCrispAsrSettingsViewModel vm) { - var dot = new Ellipse - { - Width = 10, - Height = 10, - VerticalAlignment = VerticalAlignment.Center, - [!Ellipse.FillProperty] = new Binding(brushBindingPath), - }; - var text = new TextBlock - { - VerticalAlignment = VerticalAlignment.Center, - FontWeight = FontWeight.SemiBold, - Margin = new Thickness(8, 0, 0, 0), - [!TextBlock.TextProperty] = new Binding(labelBindingPath), - }; - return new StackPanel - { - Orientation = Orientation.Horizontal, - Children = { dot, text }, - }; + var rows = TtsSettingsChrome.MakeRows(LabelWidth, + (Se.Language.General.Engine, TtsSettingsChrome.MakeStatus(nameof(vm.EngineBrush), nameof(vm.EngineLabel), + TtsSettingsChrome.MakeEngineDownloadButton(vm.RedownloadEngineCommand, nameof(vm.EngineDownloadButtonText)))), + ("GPT " + IndexTtsCrispAsr.ModelKeyQ4K, TtsSettingsChrome.MakeStatus(nameof(vm.TalkerQ4KBrush), nameof(vm.TalkerQ4KLabel))), + ("GPT " + IndexTtsCrispAsr.ModelKeyQ8_0, TtsSettingsChrome.MakeStatus(nameof(vm.TalkerQ8_0Brush), nameof(vm.TalkerQ8_0Label))), + ("GPT " + IndexTtsCrispAsr.ModelKeyF16, TtsSettingsChrome.MakeStatus(nameof(vm.TalkerF16Brush), nameof(vm.TalkerF16Label))), + ("BigVGAN codec", TtsSettingsChrome.MakeStatus(nameof(vm.CodecBrush), nameof(vm.CodecLabel))), + (Se.Language.General.InstallFolder, TtsSettingsChrome.MakeFolder(nameof(vm.ModelsFolder), vm.OpenModelsFolderCommand))); + return TtsSettingsChrome.MakeSection(Se.Language.General.Models, rows); } - private static Grid BuildActions(IndexTtsCrispAsrSettingsViewModel vm) + private static Border BuildVoice(IndexTtsCrispAsrSettingsViewModel vm) { - var openModelsFolder = UiUtil.MakeButton(Se.Language.General.OpenContainingFolder, vm.OpenModelsFolderCommand).WithIconLeft(IconNames.FolderOpen); - var openVoicesFolder = UiUtil.MakeButton("Voices folder", vm.OpenVoicesFolderCommand).WithIconLeft(IconNames.FolderOpen); - var close = UiUtil.MakeButton(Se.Language.General.Close, vm.OkCommand); - - var leftPanel = new StackPanel - { - Orientation = Orientation.Horizontal, - Spacing = 8, - Children = { openModelsFolder, openVoicesFolder }, - }; - - var rightPanel = new StackPanel - { - Orientation = Orientation.Horizontal, - HorizontalAlignment = HorizontalAlignment.Right, - Spacing = 8, - Children = { close }, - }; - - var grid = new Grid - { - ColumnDefinitions = - { - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Auto) }, - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) }, - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Auto) }, - }, - }; - grid.Add(leftPanel, 0, 0); - grid.Add(rightPanel, 0, 2); - return grid; + var rows = TtsSettingsChrome.MakeRows(LabelWidth, + (Se.Language.Video.Voices, TtsSettingsChrome.MakeValue(nameof(vm.VoicesLabel), + TtsSettingsChrome.MakeFolderButton(Se.Language.Video.TextToSpeech.OpenVoicesFolder, vm.OpenVoicesFolderCommand))), + // Server accepts 0.25-4.0; cap UI at 0.5-2.0 since anything outside that range is + // already a hack rather than a "preferred pace" choice. + (Se.Language.General.Speed, TtsSettingsChrome.MakeSlider(Se.Language.General.Speed, 0.5, 2.0, 0.05, nameof(vm.Speed), nameof(vm.SpeedLabel)))); + return TtsSettingsChrome.MakeSection(Se.Language.General.Voice, rows); } - private static TextBlock MakeLabel(string text) => new() - { - Text = text, - Opacity = 0.7, - VerticalAlignment = VerticalAlignment.Center, - }; - protected override void OnKeyDown(KeyEventArgs e) { base.OnKeyDown(e); diff --git a/src/ui/Features/Video/TextToSpeech/MossTtsCrispAsrSettings/MossTtsCrispAsrSettingsWindow.cs b/src/ui/Features/Video/TextToSpeech/MossTtsCrispAsrSettings/MossTtsCrispAsrSettingsWindow.cs index 8810b4c06cb..59d48182764 100644 --- a/src/ui/Features/Video/TextToSpeech/MossTtsCrispAsrSettings/MossTtsCrispAsrSettingsWindow.cs +++ b/src/ui/Features/Video/TextToSpeech/MossTtsCrispAsrSettings/MossTtsCrispAsrSettingsWindow.cs @@ -1,20 +1,17 @@ -using Avalonia; -using Avalonia.Controls; -using Avalonia.Controls.Shapes; -using Avalonia.Data; +using Avalonia.Controls; using Avalonia.Input; -using Avalonia.Layout; -using Avalonia.Media; using Nikse.SubtitleEdit.Features.Video.TextToSpeech.Engines; using Nikse.SubtitleEdit.Logic; using Nikse.SubtitleEdit.Logic.Config; namespace Nikse.SubtitleEdit.Features.Video.TextToSpeech.MossTtsCrispAsrSettings; +/// +/// MOSS-TTS (CrispASR) settings, in the editor look (see ). +/// public class MossTtsCrispAsrSettingsWindow : Window { private const int LabelWidth = 150; - private const int ValueWidth = 360; private readonly MossTtsCrispAsrSettingsViewModel _vm; @@ -22,243 +19,44 @@ public MossTtsCrispAsrSettingsWindow(MossTtsCrispAsrSettingsViewModel vm) { UiUtil.InitializeWindow(this, GetType().Name); Title = string.Format(Se.Language.Video.TtsCrispAsrSettingsTitle, "MOSS-TTS"); - SizeToContent = SizeToContent.WidthAndHeight; - CanResize = false; - MinWidth = 580; + TtsSettingsChrome.Apply(this); _vm = vm; vm.Window = this; DataContext = vm; - Content = BuildContent(vm); - - var ok = UiUtil.MakeButtonOk(vm.OkCommand); - UiUtil.FocusOnFirstActivation(this, ok); - } - - private Border BuildContent(MossTtsCrispAsrSettingsViewModel vm) - { - var header = BuildHeader(); - var details = BuildDetails(vm); - var actions = BuildActions(vm); - - var stack = new StackPanel - { - Orientation = Orientation.Vertical, - Spacing = 14, - Children = { header, details, actions }, - }; - - var outerGrid = new Grid - { - Margin = UiUtil.MakeWindowMargin(), - }; - outerGrid.Children.Add(stack); - - return new Border - { - Child = outerGrid, - Padding = new Thickness(4), - }; - } - - private static StackPanel BuildHeader() - { - var title = new TextBlock - { - Text = "MOSS-TTS (CrispASR)", - FontSize = UiUtil.ScaledFontSize(18), - FontWeight = FontWeight.SemiBold, - }; - - var subtitle = new TextBlock - { - Text = new MossTtsCrispAsr().Description, - FontSize = UiUtil.ScaledFontSize(12), - Opacity = 0.75, - Margin = new Thickness(0, 2, 0, 0), - }; - - return new StackPanel - { - Orientation = Orientation.Vertical, - Spacing = 0, - Children = { title, subtitle }, - }; - } - - private static Border BuildDetails(MossTtsCrispAsrSettingsViewModel vm) - { - var grid = new Grid - { - ColumnDefinitions = - { - new ColumnDefinition { Width = new GridLength(LabelWidth, GridUnitType.Pixel) }, - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) }, - }, - RowDefinitions = - { - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - }, - ColumnSpacing = 12, - RowSpacing = 10, - }; - - grid.Add(MakeLabel(Se.Language.General.Engine), 0, 0); - var enginePanel = MakeStatusPanel(nameof(vm.EngineBrush), nameof(vm.EngineLabel)); - var engineButton = UiUtil.MakeButton(string.Empty, vm.RedownloadEngineCommand) - .WithIconLeftBindText(IconNames.Download, nameof(vm.EngineDownloadButtonText)) - .WithMarginLeft(12); - enginePanel.Children.Add(engineButton); - grid.Add(enginePanel, 0, 1); - - grid.Add(MakeLabel("Model " + MossTtsCrispAsr.ModelKeyQ4K), 1, 0); - grid.Add(MakeStatusPanel(nameof(vm.ModelQ4KBrush), nameof(vm.ModelQ4KLabel)), 1, 1); - - grid.Add(MakeLabel("Model " + MossTtsCrispAsr.ModelKeyQ6K), 2, 0); - grid.Add(MakeStatusPanel(nameof(vm.ModelQ6KBrush), nameof(vm.ModelQ6KLabel)), 2, 1); - - grid.Add(MakeLabel("Model " + MossTtsCrispAsr.ModelKeyQ8), 3, 0); - grid.Add(MakeStatusPanel(nameof(vm.ModelQ8Brush), nameof(vm.ModelQ8Label)), 3, 1); - - grid.Add(MakeLabel("Model " + MossTtsCrispAsr.ModelKeyF16), 4, 0); - grid.Add(MakeStatusPanel(nameof(vm.ModelF16Brush), nameof(vm.ModelF16Label)), 4, 1); - - grid.Add(MakeLabel(Se.Language.Video.Voices), 5, 0); - var voicesText = new TextBlock - { - VerticalAlignment = VerticalAlignment.Center, - FontWeight = FontWeight.SemiBold, - [!TextBlock.TextProperty] = new Binding(nameof(vm.VoicesLabel)), - }; - grid.Add(voicesText, 5, 1); - - grid.Add(MakeLabel(Se.Language.General.Speed), 6, 0); - grid.Add(MakeSpeedPanel(), 6, 1); - - grid.Add(MakeLabel(Se.Language.General.InstallFolder), 7, 0); - var folderText = new TextBox - { - IsReadOnly = true, - Width = ValueWidth, - BorderThickness = new Thickness(0), - Background = Brushes.Transparent, - Padding = new Thickness(0), - VerticalContentAlignment = VerticalAlignment.Center, - FontSize = UiUtil.ScaledFontSize(12), - [!TextBox.TextProperty] = new Binding(nameof(vm.ModelsFolder)), - }; - grid.Add(folderText, 7, 1); - - return new Border - { - Child = grid, - Padding = new Thickness(14), - CornerRadius = new CornerRadius(6), - BorderThickness = new Thickness(1), - BorderBrush = new SolidColorBrush(Color.FromArgb(0x40, 0x80, 0x80, 0x80)), - }; - } + var close = UiUtil.MakeButton(Se.Language.General.Close, vm.OkCommand); + Content = TtsSettingsChrome.MakeContent( + TtsSettingsChrome.MakeHeader("MOSS-TTS (CrispASR)", new MossTtsCrispAsr().Description), + TtsSettingsChrome.MakeButtonBar(close), + BuildModels(vm), + BuildVoice(vm)); - private static StackPanel MakeSpeedPanel() - { - var slider = new Slider - { - Minimum = 0.5, - Maximum = 2.0, - Width = 220, - TickFrequency = 0.05, - IsSnapToTickEnabled = true, - VerticalAlignment = VerticalAlignment.Center, - [!Slider.ValueProperty] = new Binding(nameof(MossTtsCrispAsrSettingsViewModel.Speed)), - }; - var label = new TextBlock - { - VerticalAlignment = VerticalAlignment.Center, - FontWeight = FontWeight.SemiBold, - Margin = new Thickness(8, 0, 0, 0), - Width = 80, - [!TextBlock.TextProperty] = new Binding(nameof(MossTtsCrispAsrSettingsViewModel.SpeedLabel)), - }; - return new StackPanel - { - Orientation = Orientation.Horizontal, - Children = { slider, label }, - }; + UiUtil.FocusOnFirstActivation(this, close); } - private static StackPanel MakeStatusPanel(string brushBindingPath, string labelBindingPath) + private static Border BuildModels(MossTtsCrispAsrSettingsViewModel vm) { - var dot = new Ellipse - { - Width = 10, - Height = 10, - VerticalAlignment = VerticalAlignment.Center, - [!Ellipse.FillProperty] = new Binding(brushBindingPath), - }; - var text = new TextBlock - { - VerticalAlignment = VerticalAlignment.Center, - FontWeight = FontWeight.SemiBold, - Margin = new Thickness(8, 0, 0, 0), - [!TextBlock.TextProperty] = new Binding(labelBindingPath), - }; - return new StackPanel - { - Orientation = Orientation.Horizontal, - Children = { dot, text }, - }; + var rows = TtsSettingsChrome.MakeRows(LabelWidth, + (Se.Language.General.Engine, TtsSettingsChrome.MakeStatus(nameof(vm.EngineBrush), nameof(vm.EngineLabel), + TtsSettingsChrome.MakeEngineDownloadButton(vm.RedownloadEngineCommand, nameof(vm.EngineDownloadButtonText)))), + ("Model " + MossTtsCrispAsr.ModelKeyQ4K, TtsSettingsChrome.MakeStatus(nameof(vm.ModelQ4KBrush), nameof(vm.ModelQ4KLabel))), + ("Model " + MossTtsCrispAsr.ModelKeyQ6K, TtsSettingsChrome.MakeStatus(nameof(vm.ModelQ6KBrush), nameof(vm.ModelQ6KLabel))), + ("Model " + MossTtsCrispAsr.ModelKeyQ8, TtsSettingsChrome.MakeStatus(nameof(vm.ModelQ8Brush), nameof(vm.ModelQ8Label))), + ("Model " + MossTtsCrispAsr.ModelKeyF16, TtsSettingsChrome.MakeStatus(nameof(vm.ModelF16Brush), nameof(vm.ModelF16Label))), + (Se.Language.General.InstallFolder, TtsSettingsChrome.MakeFolder(nameof(vm.ModelsFolder), vm.OpenModelsFolderCommand))); + return TtsSettingsChrome.MakeSection(Se.Language.General.Models, rows); } - private static Grid BuildActions(MossTtsCrispAsrSettingsViewModel vm) + private static Border BuildVoice(MossTtsCrispAsrSettingsViewModel vm) { - var openModelsFolder = UiUtil.MakeButton(Se.Language.General.OpenContainingFolder, vm.OpenModelsFolderCommand).WithIconLeft(IconNames.FolderOpen); - var openVoicesFolder = UiUtil.MakeButton("Voices folder", vm.OpenVoicesFolderCommand).WithIconLeft(IconNames.FolderOpen); - var close = UiUtil.MakeButton(Se.Language.General.Close, vm.OkCommand); - - var leftPanel = new StackPanel - { - Orientation = Orientation.Horizontal, - Spacing = 8, - Children = { openModelsFolder, openVoicesFolder }, - }; - - var rightPanel = new StackPanel - { - Orientation = Orientation.Horizontal, - HorizontalAlignment = HorizontalAlignment.Right, - Spacing = 8, - Children = { close }, - }; - - var grid = new Grid - { - ColumnDefinitions = - { - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Auto) }, - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) }, - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Auto) }, - }, - }; - grid.Add(leftPanel, 0, 0); - grid.Add(rightPanel, 0, 2); - return grid; + var rows = TtsSettingsChrome.MakeRows(LabelWidth, + (Se.Language.Video.Voices, TtsSettingsChrome.MakeValue(nameof(vm.VoicesLabel), + TtsSettingsChrome.MakeFolderButton(Se.Language.Video.TextToSpeech.OpenVoicesFolder, vm.OpenVoicesFolderCommand))), + (Se.Language.General.Speed, TtsSettingsChrome.MakeSlider(Se.Language.General.Speed, 0.5, 2.0, 0.05, nameof(vm.Speed), nameof(vm.SpeedLabel)))); + return TtsSettingsChrome.MakeSection(Se.Language.General.Voice, rows); } - private static TextBlock MakeLabel(string text) => new() - { - Text = text, - Opacity = 0.7, - VerticalAlignment = VerticalAlignment.Center, - }; - protected override void OnKeyDown(KeyEventArgs e) { base.OnKeyDown(e); diff --git a/src/ui/Features/Video/TextToSpeech/OmniVoiceCrispAsrSettings/OmniVoiceCrispAsrSettingsWindow.cs b/src/ui/Features/Video/TextToSpeech/OmniVoiceCrispAsrSettings/OmniVoiceCrispAsrSettingsWindow.cs index 955b3f69ada..deeb6003a03 100644 --- a/src/ui/Features/Video/TextToSpeech/OmniVoiceCrispAsrSettings/OmniVoiceCrispAsrSettingsWindow.cs +++ b/src/ui/Features/Video/TextToSpeech/OmniVoiceCrispAsrSettings/OmniVoiceCrispAsrSettingsWindow.cs @@ -1,20 +1,17 @@ -using Avalonia; using Avalonia.Controls; -using Avalonia.Controls.Shapes; -using Avalonia.Data; using Avalonia.Input; -using Avalonia.Layout; -using Avalonia.Media; using Nikse.SubtitleEdit.Features.Video.TextToSpeech.Engines; using Nikse.SubtitleEdit.Logic; using Nikse.SubtitleEdit.Logic.Config; namespace Nikse.SubtitleEdit.Features.Video.TextToSpeech.OmniVoiceCrispAsrSettings; +/// +/// OmniVoice TTS (CrispASR) settings, in the editor look (see ). +/// public class OmniVoiceCrispAsrSettingsWindow : Window { private const int LabelWidth = 150; - private const int ValueWidth = 360; private readonly OmniVoiceCrispAsrSettingsViewModel _vm; @@ -22,241 +19,43 @@ public OmniVoiceCrispAsrSettingsWindow(OmniVoiceCrispAsrSettingsViewModel vm) { UiUtil.InitializeWindow(this, GetType().Name); Title = string.Format(Se.Language.Video.TtsCrispAsrSettingsTitle, "OmniVoice TTS"); - // Explicit width + height-only auto-sizing: SizeToContent.WidthAndHeight comes out far - // too wide on macOS. - Width = 620; - SizeToContent = SizeToContent.Height; - CanResize = false; + TtsSettingsChrome.Apply(this); _vm = vm; vm.Window = this; DataContext = vm; - Content = BuildContent(vm); - - var ok = UiUtil.MakeButtonOk(vm.OkCommand); - UiUtil.FocusOnFirstActivation(this, ok); - } - - private Border BuildContent(OmniVoiceCrispAsrSettingsViewModel vm) - { - var header = BuildHeader(); - var details = BuildDetails(vm); - var actions = BuildActions(vm); - - var stack = new StackPanel - { - Orientation = Orientation.Vertical, - Spacing = 14, - Children = { header, details, actions }, - }; - - var outerGrid = new Grid - { - Margin = UiUtil.MakeWindowMargin(), - }; - outerGrid.Children.Add(stack); - - return new Border - { - Child = outerGrid, - Padding = new Thickness(4), - }; - } - - private static StackPanel BuildHeader() - { - var title = new TextBlock - { - Text = "OmniVoice TTS (CrispASR)", - FontSize = UiUtil.ScaledFontSize(18), - FontWeight = FontWeight.SemiBold, - }; - - var subtitle = new TextBlock - { - Text = new OmniVoiceCrispAsr().Description, - FontSize = UiUtil.ScaledFontSize(12), - Opacity = 0.75, - Margin = new Thickness(0, 2, 0, 0), - }; - - return new StackPanel - { - Orientation = Orientation.Vertical, - Spacing = 0, - Children = { title, subtitle }, - }; - } - - private static Border BuildDetails(OmniVoiceCrispAsrSettingsViewModel vm) - { - var grid = new Grid - { - ColumnDefinitions = - { - new ColumnDefinition { Width = new GridLength(LabelWidth, GridUnitType.Pixel) }, - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) }, - }, - RowDefinitions = - { - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - }, - ColumnSpacing = 12, - RowSpacing = 10, - }; - - grid.Add(MakeLabel(Se.Language.General.Engine), 0, 0); - var enginePanel = MakeStatusPanel(nameof(vm.EngineBrush), nameof(vm.EngineLabel)); - var engineButton = UiUtil.MakeButton(string.Empty, vm.RedownloadEngineCommand) - .WithIconLeftBindText(IconNames.Download, nameof(vm.EngineDownloadButtonText)) - .WithMarginLeft(12); - enginePanel.Children.Add(engineButton); - grid.Add(enginePanel, 0, 1); - - grid.Add(MakeLabel("Model " + OmniVoiceCrispAsr.ModelKeyQ4K), 1, 0); - grid.Add(MakeStatusPanel(nameof(vm.ModelQ4KBrush), nameof(vm.ModelQ4KLabel)), 1, 1); - - grid.Add(MakeLabel("Model " + OmniVoiceCrispAsr.ModelKeyQ8_0), 2, 0); - grid.Add(MakeStatusPanel(nameof(vm.ModelQ8_0Brush), nameof(vm.ModelQ8_0Label)), 2, 1); - - grid.Add(MakeLabel("Model " + OmniVoiceCrispAsr.ModelKeyF16), 3, 0); - grid.Add(MakeStatusPanel(nameof(vm.ModelF16Brush), nameof(vm.ModelF16Label)), 3, 1); - - grid.Add(MakeLabel(Se.Language.Video.Voices), 4, 0); - var voicesText = new TextBlock - { - VerticalAlignment = VerticalAlignment.Center, - FontWeight = FontWeight.SemiBold, - [!TextBlock.TextProperty] = new Binding(nameof(vm.VoicesLabel)), - }; - grid.Add(voicesText, 4, 1); - - grid.Add(MakeLabel(Se.Language.General.Speed), 5, 0); - grid.Add(MakeSpeedPanel(), 5, 1); - - grid.Add(MakeLabel(Se.Language.General.InstallFolder), 6, 0); - var folderText = new TextBox - { - IsReadOnly = true, - Width = ValueWidth, - BorderThickness = new Thickness(0), - Background = Brushes.Transparent, - Padding = new Thickness(0), - VerticalContentAlignment = VerticalAlignment.Center, - FontSize = UiUtil.ScaledFontSize(12), - [!TextBox.TextProperty] = new Binding(nameof(vm.ModelsFolder)), - }; - grid.Add(folderText, 6, 1); - - return new Border - { - Child = grid, - Padding = new Thickness(14), - CornerRadius = new CornerRadius(6), - BorderThickness = new Thickness(1), - BorderBrush = new SolidColorBrush(Color.FromArgb(0x40, 0x80, 0x80, 0x80)), - }; - } + var close = UiUtil.MakeButton(Se.Language.General.Close, vm.OkCommand); + Content = TtsSettingsChrome.MakeContent( + TtsSettingsChrome.MakeHeader("OmniVoice TTS (CrispASR)", new OmniVoiceCrispAsr().Description), + TtsSettingsChrome.MakeButtonBar(close), + BuildModels(vm), + BuildVoice(vm)); - private static StackPanel MakeSpeedPanel() - { - var slider = new Slider - { - Minimum = 0.5, - Maximum = 2.0, - Width = 220, - TickFrequency = 0.05, - IsSnapToTickEnabled = true, - VerticalAlignment = VerticalAlignment.Center, - [!Slider.ValueProperty] = new Binding(nameof(OmniVoiceCrispAsrSettingsViewModel.Speed)), - }; - var label = new TextBlock - { - VerticalAlignment = VerticalAlignment.Center, - FontWeight = FontWeight.SemiBold, - Margin = new Thickness(8, 0, 0, 0), - Width = 80, - [!TextBlock.TextProperty] = new Binding(nameof(OmniVoiceCrispAsrSettingsViewModel.SpeedLabel)), - }; - return new StackPanel - { - Orientation = Orientation.Horizontal, - Children = { slider, label }, - }; + UiUtil.FocusOnFirstActivation(this, close); } - private static StackPanel MakeStatusPanel(string brushBindingPath, string labelBindingPath) + private static Border BuildModels(OmniVoiceCrispAsrSettingsViewModel vm) { - var dot = new Ellipse - { - Width = 10, - Height = 10, - VerticalAlignment = VerticalAlignment.Center, - [!Ellipse.FillProperty] = new Binding(brushBindingPath), - }; - var text = new TextBlock - { - VerticalAlignment = VerticalAlignment.Center, - FontWeight = FontWeight.SemiBold, - Margin = new Thickness(8, 0, 0, 0), - [!TextBlock.TextProperty] = new Binding(labelBindingPath), - }; - return new StackPanel - { - Orientation = Orientation.Horizontal, - Children = { dot, text }, - }; + var rows = TtsSettingsChrome.MakeRows(LabelWidth, + (Se.Language.General.Engine, TtsSettingsChrome.MakeStatus(nameof(vm.EngineBrush), nameof(vm.EngineLabel), + TtsSettingsChrome.MakeEngineDownloadButton(vm.RedownloadEngineCommand, nameof(vm.EngineDownloadButtonText)))), + ("Model " + OmniVoiceCrispAsr.ModelKeyQ4K, TtsSettingsChrome.MakeStatus(nameof(vm.ModelQ4KBrush), nameof(vm.ModelQ4KLabel))), + ("Model " + OmniVoiceCrispAsr.ModelKeyQ8_0, TtsSettingsChrome.MakeStatus(nameof(vm.ModelQ8_0Brush), nameof(vm.ModelQ8_0Label))), + ("Model " + OmniVoiceCrispAsr.ModelKeyF16, TtsSettingsChrome.MakeStatus(nameof(vm.ModelF16Brush), nameof(vm.ModelF16Label))), + (Se.Language.General.InstallFolder, TtsSettingsChrome.MakeFolder(nameof(vm.ModelsFolder), vm.OpenModelsFolderCommand))); + return TtsSettingsChrome.MakeSection(Se.Language.General.Models, rows); } - private static Grid BuildActions(OmniVoiceCrispAsrSettingsViewModel vm) + private static Border BuildVoice(OmniVoiceCrispAsrSettingsViewModel vm) { - var openModelsFolder = UiUtil.MakeButton(Se.Language.General.OpenContainingFolder, vm.OpenModelsFolderCommand).WithIconLeft(IconNames.FolderOpen); - var openVoicesFolder = UiUtil.MakeButton("Voices folder", vm.OpenVoicesFolderCommand).WithIconLeft(IconNames.FolderOpen); - var close = UiUtil.MakeButton(Se.Language.General.Close, vm.OkCommand); - - var leftPanel = new StackPanel - { - Orientation = Orientation.Horizontal, - Spacing = 8, - Children = { openModelsFolder, openVoicesFolder }, - }; - - var rightPanel = new StackPanel - { - Orientation = Orientation.Horizontal, - HorizontalAlignment = HorizontalAlignment.Right, - Spacing = 8, - Children = { close }, - }; - - var grid = new Grid - { - ColumnDefinitions = - { - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Auto) }, - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) }, - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Auto) }, - }, - }; - grid.Add(leftPanel, 0, 0); - grid.Add(rightPanel, 0, 2); - return grid; + var rows = TtsSettingsChrome.MakeRows(LabelWidth, + (Se.Language.Video.Voices, TtsSettingsChrome.MakeValue(nameof(vm.VoicesLabel), + TtsSettingsChrome.MakeFolderButton(Se.Language.Video.TextToSpeech.OpenVoicesFolder, vm.OpenVoicesFolderCommand))), + (Se.Language.General.Speed, TtsSettingsChrome.MakeSlider(Se.Language.General.Speed, 0.5, 2.0, 0.05, nameof(vm.Speed), nameof(vm.SpeedLabel)))); + return TtsSettingsChrome.MakeSection(Se.Language.General.Voice, rows); } - private static TextBlock MakeLabel(string text) => new() - { - Text = text, - Opacity = 0.7, - VerticalAlignment = VerticalAlignment.Center, - }; - protected override void OnKeyDown(KeyEventArgs e) { base.OnKeyDown(e); diff --git a/src/ui/Features/Video/TextToSpeech/PocketTtsCrispAsrSettings/PocketTtsCrispAsrSettingsWindow.cs b/src/ui/Features/Video/TextToSpeech/PocketTtsCrispAsrSettings/PocketTtsCrispAsrSettingsWindow.cs index 689611c0463..d61568276f1 100644 --- a/src/ui/Features/Video/TextToSpeech/PocketTtsCrispAsrSettings/PocketTtsCrispAsrSettingsWindow.cs +++ b/src/ui/Features/Video/TextToSpeech/PocketTtsCrispAsrSettings/PocketTtsCrispAsrSettingsWindow.cs @@ -1,20 +1,17 @@ -using Avalonia; using Avalonia.Controls; -using Avalonia.Controls.Shapes; -using Avalonia.Data; using Avalonia.Input; -using Avalonia.Layout; -using Avalonia.Media; using Nikse.SubtitleEdit.Features.Video.TextToSpeech.Engines; using Nikse.SubtitleEdit.Logic; using Nikse.SubtitleEdit.Logic.Config; namespace Nikse.SubtitleEdit.Features.Video.TextToSpeech.PocketTtsCrispAsrSettings; +/// +/// Pocket TTS (CrispASR) settings, in the editor look (see ). +/// public class PocketTtsCrispAsrSettingsWindow : Window { private const int LabelWidth = 170; - private const int ValueWidth = 360; private readonly PocketTtsCrispAsrSettingsViewModel _vm; @@ -22,263 +19,52 @@ public PocketTtsCrispAsrSettingsWindow(PocketTtsCrispAsrSettingsViewModel vm) { UiUtil.InitializeWindow(this, GetType().Name); Title = string.Format(Se.Language.Video.TtsCrispAsrSettingsTitle, "Pocket TTS"); - SizeToContent = SizeToContent.WidthAndHeight; - CanResize = false; - MinWidth = 580; + TtsSettingsChrome.Apply(this); _vm = vm; vm.Window = this; DataContext = vm; - Content = BuildContent(vm); - - var ok = UiUtil.MakeButtonOk(vm.OkCommand); - UiUtil.FocusOnFirstActivation(this, ok); - } - - private Border BuildContent(PocketTtsCrispAsrSettingsViewModel vm) - { - var header = BuildHeader(vm); - var details = BuildDetails(vm); - var actions = BuildActions(vm); - - var stack = new StackPanel - { - Orientation = Orientation.Vertical, - Spacing = 14, - Children = { header, details, actions }, - }; - - var outerGrid = new Grid - { - Margin = UiUtil.MakeWindowMargin(), - }; - outerGrid.Children.Add(stack); - - return new Border - { - Child = outerGrid, - Padding = new Thickness(4), - }; - } - - private static StackPanel BuildHeader(PocketTtsCrispAsrSettingsViewModel vm) - { - var title = new TextBlock - { - Text = "Pocket TTS (CrispASR)", - FontSize = UiUtil.ScaledFontSize(18), - FontWeight = FontWeight.SemiBold, - }; - - var subtitle = new TextBlock - { - Text = new PocketTtsCrispAsr().Description, - FontSize = UiUtil.ScaledFontSize(12), - Opacity = 0.75, - Margin = new Thickness(0, 2, 0, 0), - }; - // The weights are CC-BY-4.0 with Kyutai's additional gated-use conditions; surface that // (with a link to the model page) rather than leaving it buried in the download source. - var license = UiUtil.MakeLink("License: CC-BY-4.0 + Kyutai gated-use conditions", vm.OpenLicensePageCommand); - license.Margin = new Thickness(0, 2, 0, 0); - license.FontSize = UiUtil.ScaledFontSize(12); - - return new StackPanel - { - Orientation = Orientation.Vertical, - Spacing = 0, - Children = { title, subtitle, license }, - }; - } - - private static Border BuildDetails(PocketTtsCrispAsrSettingsViewModel vm) - { - var grid = new Grid - { - ColumnDefinitions = - { - new ColumnDefinition { Width = new GridLength(LabelWidth, GridUnitType.Pixel) }, - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) }, - }, - RowDefinitions = - { - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - }, - ColumnSpacing = 12, - RowSpacing = 10, - }; - - grid.Add(MakeLabel(Se.Language.General.Engine), 0, 0); - var enginePanel = MakeStatusPanel(nameof(vm.EngineBrush), nameof(vm.EngineLabel)); - var engineButton = UiUtil.MakeButton(string.Empty, vm.RedownloadEngineCommand) - .WithIconLeftBindText(IconNames.Download, nameof(vm.EngineDownloadButtonText)) - .WithMarginLeft(12); - enginePanel.Children.Add(engineButton); - grid.Add(enginePanel, 0, 1); - - grid.Add(MakeLabel(PocketTtsCrispAsr.ModelKeyEnglishF16), 1, 0); - grid.Add(MakeStatusPanel(nameof(vm.EnglishF16Brush), nameof(vm.EnglishF16Label)), 1, 1); - - grid.Add(MakeLabel(PocketTtsCrispAsr.ModelKeyEnglishQ8_0), 2, 0); - grid.Add(MakeStatusPanel(nameof(vm.EnglishQ8_0Brush), nameof(vm.EnglishQ8_0Label)), 2, 1); - - grid.Add(MakeLabel(PocketTtsCrispAsr.ModelKeyGermanQ8_0), 3, 0); - grid.Add(MakeStatusPanel(nameof(vm.GermanBrush), nameof(vm.GermanLabel)), 3, 1); - - grid.Add(MakeLabel(PocketTtsCrispAsr.ModelKeySpanishQ8_0), 4, 0); - grid.Add(MakeStatusPanel(nameof(vm.SpanishBrush), nameof(vm.SpanishLabel)), 4, 1); - - grid.Add(MakeLabel(PocketTtsCrispAsr.ModelKeyItalianQ8_0), 5, 0); - grid.Add(MakeStatusPanel(nameof(vm.ItalianBrush), nameof(vm.ItalianLabel)), 5, 1); - - grid.Add(MakeLabel(PocketTtsCrispAsr.ModelKeyPortugueseQ8_0), 6, 0); - grid.Add(MakeStatusPanel(nameof(vm.PortugueseBrush), nameof(vm.PortugueseLabel)), 6, 1); - - grid.Add(MakeLabel(PocketTtsCrispAsr.ModelKeyFrenchQ8_0), 7, 0); - grid.Add(MakeStatusPanel(nameof(vm.FrenchBrush), nameof(vm.FrenchLabel)), 7, 1); - - grid.Add(MakeLabel(Se.Language.Video.Voices), 8, 0); - var voicesText = new TextBlock - { - VerticalAlignment = VerticalAlignment.Center, - FontWeight = FontWeight.SemiBold, - [!TextBlock.TextProperty] = new Binding(nameof(vm.VoicesLabel)), - }; - grid.Add(voicesText, 8, 1); - - grid.Add(MakeLabel(Se.Language.General.Speed), 9, 0); - grid.Add(MakeSpeedPanel(), 9, 1); - - grid.Add(MakeLabel(Se.Language.General.InstallFolder), 10, 0); - var folderText = new TextBox - { - IsReadOnly = true, - Width = ValueWidth, - BorderThickness = new Thickness(0), - Background = Brushes.Transparent, - Padding = new Thickness(0), - VerticalContentAlignment = VerticalAlignment.Center, - FontSize = UiUtil.ScaledFontSize(12), - [!TextBox.TextProperty] = new Binding(nameof(vm.ModelsFolder)), - }; - grid.Add(folderText, 10, 1); - - return new Border - { - Child = grid, - Padding = new Thickness(14), - CornerRadius = new CornerRadius(6), - BorderThickness = new Thickness(1), - BorderBrush = new SolidColorBrush(Color.FromArgb(0x40, 0x80, 0x80, 0x80)), - }; - } + var license = TtsSettingsChrome.MakeLicenseLink("License: CC-BY-4.0 + Kyutai gated-use conditions", vm.OpenLicensePageCommand); + var close = UiUtil.MakeButton(Se.Language.General.Close, vm.OkCommand); + Content = TtsSettingsChrome.MakeContent( + TtsSettingsChrome.MakeHeader("Pocket TTS (CrispASR)", new PocketTtsCrispAsr().Description, license), + TtsSettingsChrome.MakeButtonBar(close), + BuildModels(vm), + BuildVoice(vm)); - private static StackPanel MakeSpeedPanel() - { - // Server accepts 0.25-4.0; cap UI at 0.5-2.0 since anything outside that range is - // already a hack rather than a "preferred pace" choice. - var slider = new Slider - { - Minimum = 0.5, - Maximum = 2.0, - Width = 220, - TickFrequency = 0.05, - IsSnapToTickEnabled = true, - VerticalAlignment = VerticalAlignment.Center, - [!Slider.ValueProperty] = new Binding(nameof(PocketTtsCrispAsrSettingsViewModel.Speed)), - }; - var label = new TextBlock - { - VerticalAlignment = VerticalAlignment.Center, - FontWeight = FontWeight.SemiBold, - Margin = new Thickness(8, 0, 0, 0), - Width = 80, - [!TextBlock.TextProperty] = new Binding(nameof(PocketTtsCrispAsrSettingsViewModel.SpeedLabel)), - }; - return new StackPanel - { - Orientation = Orientation.Horizontal, - Children = { slider, label }, - }; + UiUtil.FocusOnFirstActivation(this, close); } - private static StackPanel MakeStatusPanel(string brushBindingPath, string labelBindingPath) + private static Border BuildModels(PocketTtsCrispAsrSettingsViewModel vm) { - var dot = new Ellipse - { - Width = 10, - Height = 10, - VerticalAlignment = VerticalAlignment.Center, - [!Ellipse.FillProperty] = new Binding(brushBindingPath), - }; - var text = new TextBlock - { - VerticalAlignment = VerticalAlignment.Center, - FontWeight = FontWeight.SemiBold, - Margin = new Thickness(8, 0, 0, 0), - [!TextBlock.TextProperty] = new Binding(labelBindingPath), - }; - return new StackPanel - { - Orientation = Orientation.Horizontal, - Children = { dot, text }, - }; + var rows = TtsSettingsChrome.MakeRows(LabelWidth, + (Se.Language.General.Engine, TtsSettingsChrome.MakeStatus(nameof(vm.EngineBrush), nameof(vm.EngineLabel), + TtsSettingsChrome.MakeEngineDownloadButton(vm.RedownloadEngineCommand, nameof(vm.EngineDownloadButtonText)))), + (PocketTtsCrispAsr.ModelKeyEnglishF16, TtsSettingsChrome.MakeStatus(nameof(vm.EnglishF16Brush), nameof(vm.EnglishF16Label))), + (PocketTtsCrispAsr.ModelKeyEnglishQ8_0, TtsSettingsChrome.MakeStatus(nameof(vm.EnglishQ8_0Brush), nameof(vm.EnglishQ8_0Label))), + (PocketTtsCrispAsr.ModelKeyGermanQ8_0, TtsSettingsChrome.MakeStatus(nameof(vm.GermanBrush), nameof(vm.GermanLabel))), + (PocketTtsCrispAsr.ModelKeySpanishQ8_0, TtsSettingsChrome.MakeStatus(nameof(vm.SpanishBrush), nameof(vm.SpanishLabel))), + (PocketTtsCrispAsr.ModelKeyItalianQ8_0, TtsSettingsChrome.MakeStatus(nameof(vm.ItalianBrush), nameof(vm.ItalianLabel))), + (PocketTtsCrispAsr.ModelKeyPortugueseQ8_0, TtsSettingsChrome.MakeStatus(nameof(vm.PortugueseBrush), nameof(vm.PortugueseLabel))), + (PocketTtsCrispAsr.ModelKeyFrenchQ8_0, TtsSettingsChrome.MakeStatus(nameof(vm.FrenchBrush), nameof(vm.FrenchLabel))), + (Se.Language.General.InstallFolder, TtsSettingsChrome.MakeFolder(nameof(vm.ModelsFolder), vm.OpenModelsFolderCommand))); + return TtsSettingsChrome.MakeSection(Se.Language.General.Models, rows); } - private static Grid BuildActions(PocketTtsCrispAsrSettingsViewModel vm) + private static Border BuildVoice(PocketTtsCrispAsrSettingsViewModel vm) { - var openModelsFolder = UiUtil.MakeButton(Se.Language.General.OpenContainingFolder, vm.OpenModelsFolderCommand).WithIconLeft(IconNames.FolderOpen); - var openVoicesFolder = UiUtil.MakeButton("Voices folder", vm.OpenVoicesFolderCommand).WithIconLeft(IconNames.FolderOpen); - var close = UiUtil.MakeButton(Se.Language.General.Close, vm.OkCommand); - - var leftPanel = new StackPanel - { - Orientation = Orientation.Horizontal, - Spacing = 8, - Children = { openModelsFolder, openVoicesFolder }, - }; - - var rightPanel = new StackPanel - { - Orientation = Orientation.Horizontal, - HorizontalAlignment = HorizontalAlignment.Right, - Spacing = 8, - Children = { close }, - }; - - var grid = new Grid - { - ColumnDefinitions = - { - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Auto) }, - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) }, - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Auto) }, - }, - }; - grid.Add(leftPanel, 0, 0); - grid.Add(rightPanel, 0, 2); - return grid; + var rows = TtsSettingsChrome.MakeRows(LabelWidth, + (Se.Language.Video.Voices, TtsSettingsChrome.MakeValue(nameof(vm.VoicesLabel), + TtsSettingsChrome.MakeFolderButton(Se.Language.Video.TextToSpeech.OpenVoicesFolder, vm.OpenVoicesFolderCommand))), + // Server accepts 0.25-4.0; cap UI at 0.5-2.0 since anything outside that range is + // already a hack rather than a "preferred pace" choice. + (Se.Language.General.Speed, TtsSettingsChrome.MakeSlider(Se.Language.General.Speed, 0.5, 2.0, 0.05, nameof(vm.Speed), nameof(vm.SpeedLabel)))); + return TtsSettingsChrome.MakeSection(Se.Language.General.Voice, rows); } - private static TextBlock MakeLabel(string text) => new() - { - Text = text, - Opacity = 0.7, - VerticalAlignment = VerticalAlignment.Center, - }; - protected override void OnKeyDown(KeyEventArgs e) { base.OnKeyDown(e); diff --git a/src/ui/Features/Video/TextToSpeech/Qwen3TtsCrispAsrSettings/Qwen3TtsCrispAsrSettingsWindow.cs b/src/ui/Features/Video/TextToSpeech/Qwen3TtsCrispAsrSettings/Qwen3TtsCrispAsrSettingsWindow.cs index 9632fb43696..e15ae5a61cd 100644 --- a/src/ui/Features/Video/TextToSpeech/Qwen3TtsCrispAsrSettings/Qwen3TtsCrispAsrSettingsWindow.cs +++ b/src/ui/Features/Video/TextToSpeech/Qwen3TtsCrispAsrSettings/Qwen3TtsCrispAsrSettingsWindow.cs @@ -1,20 +1,17 @@ -using Avalonia; using Avalonia.Controls; -using Avalonia.Controls.Shapes; -using Avalonia.Data; using Avalonia.Input; -using Avalonia.Layout; -using Avalonia.Media; using Nikse.SubtitleEdit.Features.Video.TextToSpeech.Engines; using Nikse.SubtitleEdit.Logic; using Nikse.SubtitleEdit.Logic.Config; namespace Nikse.SubtitleEdit.Features.Video.TextToSpeech.Qwen3TtsCrispAsrSettings; +/// +/// Qwen3 TTS (CrispASR) settings, in the editor look (see ). +/// public class Qwen3TtsCrispAsrSettingsWindow : Window { private const int LabelWidth = 150; - private const int ValueWidth = 360; private readonly Qwen3TtsCrispAsrSettingsViewModel _vm; @@ -22,212 +19,43 @@ public Qwen3TtsCrispAsrSettingsWindow(Qwen3TtsCrispAsrSettingsViewModel vm) { UiUtil.InitializeWindow(this, GetType().Name); Title = string.Format(Se.Language.Video.TtsCrispAsrSettingsTitle, "Qwen3 TTS"); - SizeToContent = SizeToContent.WidthAndHeight; - CanResize = false; - MinWidth = 580; + TtsSettingsChrome.Apply(this); _vm = vm; vm.Window = this; DataContext = vm; - Content = BuildContent(vm); - - var ok = UiUtil.MakeButtonOk(vm.OkCommand); - UiUtil.FocusOnFirstActivation(this, ok); - } - - private Border BuildContent(Qwen3TtsCrispAsrSettingsViewModel vm) - { - var header = BuildHeader(); - var details = BuildDetails(vm); - var actions = BuildActions(vm); - - var stack = new StackPanel - { - Orientation = Orientation.Vertical, - Spacing = 14, - Children = { header, details, actions }, - }; - - var outerGrid = new Grid - { - Margin = UiUtil.MakeWindowMargin(), - }; - outerGrid.Children.Add(stack); - - return new Border - { - Child = outerGrid, - Padding = new Thickness(4), - }; - } - - private static StackPanel BuildHeader() - { - var title = new TextBlock - { - Text = "Qwen3 TTS (CrispASR)", - FontSize = UiUtil.ScaledFontSize(18), - FontWeight = FontWeight.SemiBold, - }; - - var subtitle = new TextBlock - { - Text = new Qwen3TtsCrispAsr().Description, - FontSize = UiUtil.ScaledFontSize(12), - Opacity = 0.75, - Margin = new Thickness(0, 2, 0, 0), - }; - - return new StackPanel - { - Orientation = Orientation.Vertical, - Spacing = 0, - Children = { title, subtitle }, - }; - } - - private static Border BuildDetails(Qwen3TtsCrispAsrSettingsViewModel vm) - { - var grid = new Grid - { - ColumnDefinitions = - { - new ColumnDefinition { Width = new GridLength(LabelWidth, GridUnitType.Pixel) }, - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) }, - }, - RowDefinitions = - { - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - }, - ColumnSpacing = 12, - RowSpacing = 10, - }; - - grid.Add(MakeLabel(Se.Language.General.Engine), 0, 0); - var enginePanel = MakeStatusPanel(nameof(vm.EngineBrush), nameof(vm.EngineLabel)); - var engineButton = UiUtil.MakeButton(string.Empty, vm.RedownloadEngineCommand) - .WithIconLeftBindText(IconNames.Download, nameof(vm.EngineDownloadButtonText)) - .WithMarginLeft(12); - enginePanel.Children.Add(engineButton); - grid.Add(enginePanel, 0, 1); - - grid.Add(MakeLabel("1.7B VoiceDesign"), 1, 0); - grid.Add(MakeStatusPanel(nameof(vm.VoiceDesignTalkerBrush), nameof(vm.VoiceDesignTalkerLabel)), 1, 1); - - grid.Add(MakeLabel("1.7B CustomVoice"), 2, 0); - grid.Add(MakeStatusPanel(nameof(vm.CustomVoiceTalkerBrush), nameof(vm.CustomVoiceTalkerLabel)), 2, 1); - - grid.Add(MakeLabel("1.7B Voice clone"), 3, 0); - grid.Add(MakeStatusPanel(nameof(vm.CloneTalkerBrush), nameof(vm.CloneTalkerLabel)), 3, 1); - - grid.Add(MakeLabel("Codec (12 Hz)"), 4, 0); - grid.Add(MakeStatusPanel(nameof(vm.CodecBrush), nameof(vm.CodecLabel)), 4, 1); - - grid.Add(MakeLabel(Se.Language.Video.Voices), 5, 0); - var voicesText = new TextBlock - { - VerticalAlignment = VerticalAlignment.Center, - FontWeight = FontWeight.SemiBold, - [!TextBlock.TextProperty] = new Binding(nameof(vm.VoicesLabel)), - }; - grid.Add(voicesText, 5, 1); - - grid.Add(MakeLabel(Se.Language.General.InstallFolder), 6, 0); - var folderText = new TextBox - { - IsReadOnly = true, - Width = ValueWidth, - BorderThickness = new Thickness(0), - Background = Brushes.Transparent, - Padding = new Thickness(0), - VerticalContentAlignment = VerticalAlignment.Center, - FontSize = UiUtil.ScaledFontSize(12), - [!TextBox.TextProperty] = new Binding(nameof(vm.ModelsFolder)), - }; - grid.Add(folderText, 6, 1); + var close = UiUtil.MakeButton(Se.Language.General.Close, vm.OkCommand); + Content = TtsSettingsChrome.MakeContent( + TtsSettingsChrome.MakeHeader("Qwen3 TTS (CrispASR)", new Qwen3TtsCrispAsr().Description), + TtsSettingsChrome.MakeButtonBar(close), + BuildModels(vm), + BuildVoice(vm)); - return new Border - { - Child = grid, - Padding = new Thickness(14), - CornerRadius = new CornerRadius(6), - BorderThickness = new Thickness(1), - BorderBrush = new SolidColorBrush(Color.FromArgb(0x40, 0x80, 0x80, 0x80)), - }; + UiUtil.FocusOnFirstActivation(this, close); } - private static StackPanel MakeStatusPanel(string brushBindingPath, string labelBindingPath) + private static Border BuildModels(Qwen3TtsCrispAsrSettingsViewModel vm) { - var dot = new Ellipse - { - Width = 10, - Height = 10, - VerticalAlignment = VerticalAlignment.Center, - [!Ellipse.FillProperty] = new Binding(brushBindingPath), - }; - var text = new TextBlock - { - VerticalAlignment = VerticalAlignment.Center, - FontWeight = FontWeight.SemiBold, - Margin = new Thickness(8, 0, 0, 0), - [!TextBlock.TextProperty] = new Binding(labelBindingPath), - }; - return new StackPanel - { - Orientation = Orientation.Horizontal, - Children = { dot, text }, - }; + var rows = TtsSettingsChrome.MakeRows(LabelWidth, + (Se.Language.General.Engine, TtsSettingsChrome.MakeStatus(nameof(vm.EngineBrush), nameof(vm.EngineLabel), + TtsSettingsChrome.MakeEngineDownloadButton(vm.RedownloadEngineCommand, nameof(vm.EngineDownloadButtonText)))), + ("1.7B VoiceDesign", TtsSettingsChrome.MakeStatus(nameof(vm.VoiceDesignTalkerBrush), nameof(vm.VoiceDesignTalkerLabel))), + ("1.7B CustomVoice", TtsSettingsChrome.MakeStatus(nameof(vm.CustomVoiceTalkerBrush), nameof(vm.CustomVoiceTalkerLabel))), + ("1.7B Voice clone", TtsSettingsChrome.MakeStatus(nameof(vm.CloneTalkerBrush), nameof(vm.CloneTalkerLabel))), + ("Codec (12 Hz)", TtsSettingsChrome.MakeStatus(nameof(vm.CodecBrush), nameof(vm.CodecLabel))), + (Se.Language.General.InstallFolder, TtsSettingsChrome.MakeFolder(nameof(vm.ModelsFolder), vm.OpenModelsFolderCommand))); + return TtsSettingsChrome.MakeSection(Se.Language.General.Models, rows); } - private static Grid BuildActions(Qwen3TtsCrispAsrSettingsViewModel vm) + private static Border BuildVoice(Qwen3TtsCrispAsrSettingsViewModel vm) { - var openModelsFolder = UiUtil.MakeButton(Se.Language.General.OpenContainingFolder, vm.OpenModelsFolderCommand).WithIconLeft(IconNames.FolderOpen); - var openVoicesFolder = UiUtil.MakeButton("Voices folder", vm.OpenVoicesFolderCommand).WithIconLeft(IconNames.FolderOpen); - var close = UiUtil.MakeButton(Se.Language.General.Close, vm.OkCommand); - - var leftPanel = new StackPanel - { - Orientation = Orientation.Horizontal, - Spacing = 8, - Children = { openModelsFolder, openVoicesFolder }, - }; - - var rightPanel = new StackPanel - { - Orientation = Orientation.Horizontal, - HorizontalAlignment = HorizontalAlignment.Right, - Spacing = 8, - Children = { close }, - }; - - var grid = new Grid - { - ColumnDefinitions = - { - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Auto) }, - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) }, - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Auto) }, - }, - }; - grid.Add(leftPanel, 0, 0); - grid.Add(rightPanel, 0, 2); - return grid; + var rows = TtsSettingsChrome.MakeRows(LabelWidth, + (Se.Language.Video.Voices, TtsSettingsChrome.MakeValue(nameof(vm.VoicesLabel), + TtsSettingsChrome.MakeFolderButton(Se.Language.Video.TextToSpeech.OpenVoicesFolder, vm.OpenVoicesFolderCommand)))); + return TtsSettingsChrome.MakeSection(Se.Language.General.Voice, rows); } - private static TextBlock MakeLabel(string text) => new() - { - Text = text, - Opacity = 0.7, - VerticalAlignment = VerticalAlignment.Center, - }; - protected override void OnKeyDown(KeyEventArgs e) { base.OnKeyDown(e); diff --git a/src/ui/Features/Video/TextToSpeech/SupertonicCrispAsrSettings/SupertonicCrispAsrSettingsWindow.cs b/src/ui/Features/Video/TextToSpeech/SupertonicCrispAsrSettings/SupertonicCrispAsrSettingsWindow.cs index 804098d5a66..13f498107d6 100644 --- a/src/ui/Features/Video/TextToSpeech/SupertonicCrispAsrSettings/SupertonicCrispAsrSettingsWindow.cs +++ b/src/ui/Features/Video/TextToSpeech/SupertonicCrispAsrSettings/SupertonicCrispAsrSettingsWindow.cs @@ -1,20 +1,17 @@ -using Avalonia; using Avalonia.Controls; -using Avalonia.Controls.Shapes; -using Avalonia.Data; using Avalonia.Input; -using Avalonia.Layout; -using Avalonia.Media; using Nikse.SubtitleEdit.Features.Video.TextToSpeech.Engines; using Nikse.SubtitleEdit.Logic; using Nikse.SubtitleEdit.Logic.Config; namespace Nikse.SubtitleEdit.Features.Video.TextToSpeech.SupertonicCrispAsrSettings; +/// +/// Supertonic (CrispASR) settings, in the editor look (see ). +/// public class SupertonicCrispAsrSettingsWindow : Window { private const int LabelWidth = 170; - private const int ValueWidth = 360; private readonly SupertonicCrispAsrSettingsViewModel _vm; @@ -22,239 +19,46 @@ public SupertonicCrispAsrSettingsWindow(SupertonicCrispAsrSettingsViewModel vm) { UiUtil.InitializeWindow(this, GetType().Name); Title = string.Format(Se.Language.Video.TtsCrispAsrSettingsTitle, "Supertonic"); - SizeToContent = SizeToContent.WidthAndHeight; - CanResize = false; - MinWidth = 580; + TtsSettingsChrome.Apply(this); _vm = vm; vm.Window = this; DataContext = vm; - Content = BuildContent(vm); - - var ok = UiUtil.MakeButtonOk(vm.OkCommand); - UiUtil.FocusOnFirstActivation(this, ok); - } - - private Border BuildContent(SupertonicCrispAsrSettingsViewModel vm) - { - var header = BuildHeader(vm); - var details = BuildDetails(vm); - var actions = BuildActions(vm); - - var stack = new StackPanel - { - Orientation = Orientation.Vertical, - Spacing = 14, - Children = { header, details, actions }, - }; - - var outerGrid = new Grid - { - Margin = UiUtil.MakeWindowMargin(), - }; - outerGrid.Children.Add(stack); - - return new Border - { - Child = outerGrid, - Padding = new Thickness(4), - }; - } - - private static StackPanel BuildHeader(SupertonicCrispAsrSettingsViewModel vm) - { - var title = new TextBlock - { - Text = "Supertonic (CrispASR)", - FontSize = UiUtil.ScaledFontSize(18), - FontWeight = FontWeight.SemiBold, - }; - - var subtitle = new TextBlock - { - Text = new SupertonicCrispAsr().Description, - FontSize = UiUtil.ScaledFontSize(12), - Opacity = 0.75, - Margin = new Thickness(0, 2, 0, 0), - }; - // The weights are OpenRAIL-M: commercial use is allowed, but the licence carries use // restrictions and an attribution requirement. Surface that (with a link to the model // page) rather than leaving it buried in the download source. - var license = UiUtil.MakeLink("License: OpenRAIL-M (use restrictions + attribution)", vm.OpenLicensePageCommand); - license.Margin = new Thickness(0, 2, 0, 0); - license.FontSize = UiUtil.ScaledFontSize(12); - - return new StackPanel - { - Orientation = Orientation.Vertical, - Spacing = 0, - Children = { title, subtitle, license }, - }; - } - - private static Border BuildDetails(SupertonicCrispAsrSettingsViewModel vm) - { - var grid = new Grid - { - ColumnDefinitions = - { - new ColumnDefinition { Width = new GridLength(LabelWidth, GridUnitType.Pixel) }, - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) }, - }, - RowDefinitions = - { - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - }, - ColumnSpacing = 12, - RowSpacing = 10, - }; - - grid.Add(MakeLabel(Se.Language.General.Engine), 0, 0); - var enginePanel = MakeStatusPanel(nameof(vm.EngineBrush), nameof(vm.EngineLabel)); - var engineButton = UiUtil.MakeButton(string.Empty, vm.RedownloadEngineCommand) - .WithIconLeftBindText(IconNames.Download, nameof(vm.EngineDownloadButtonText)) - .WithMarginLeft(12); - enginePanel.Children.Add(engineButton); - grid.Add(enginePanel, 0, 1); - - grid.Add(MakeLabel(Se.Language.General.Model), 1, 0); - grid.Add(MakeStatusPanel(nameof(vm.ModelBrush), nameof(vm.ModelLabel)), 1, 1); - - grid.Add(MakeLabel(Se.Language.Video.Voices), 2, 0); - var voicesText = new TextBlock - { - VerticalAlignment = VerticalAlignment.Center, - FontWeight = FontWeight.SemiBold, - Text = "10 preset voices (no voice cloning)", - }; - grid.Add(voicesText, 2, 1); - - grid.Add(MakeLabel(Se.Language.General.Speed), 3, 0); - grid.Add(MakeSpeedPanel(), 3, 1); - - grid.Add(MakeLabel(Se.Language.General.InstallFolder), 4, 0); - var folderText = new TextBox - { - IsReadOnly = true, - Width = ValueWidth, - BorderThickness = new Thickness(0), - Background = Brushes.Transparent, - Padding = new Thickness(0), - VerticalContentAlignment = VerticalAlignment.Center, - FontSize = UiUtil.ScaledFontSize(12), - [!TextBox.TextProperty] = new Binding(nameof(vm.ModelsFolder)), - }; - grid.Add(folderText, 4, 1); + var license = TtsSettingsChrome.MakeLicenseLink("License: OpenRAIL-M (use restrictions + attribution)", vm.OpenLicensePageCommand); + var close = UiUtil.MakeButton(Se.Language.General.Close, vm.OkCommand); + Content = TtsSettingsChrome.MakeContent( + TtsSettingsChrome.MakeHeader("Supertonic (CrispASR)", new SupertonicCrispAsr().Description, license), + TtsSettingsChrome.MakeButtonBar(close), + BuildModels(vm), + BuildVoice(vm)); - return new Border - { - Child = grid, - Padding = new Thickness(14), - CornerRadius = new CornerRadius(6), - BorderThickness = new Thickness(1), - BorderBrush = new SolidColorBrush(Color.FromArgb(0x40, 0x80, 0x80, 0x80)), - }; + UiUtil.FocusOnFirstActivation(this, close); } - private static StackPanel MakeSpeedPanel() + private static Border BuildModels(SupertonicCrispAsrSettingsViewModel vm) { - // Server accepts 0.25-4.0; cap UI at 0.5-2.0 since anything outside that range is - // already a hack rather than a "preferred pace" choice. - var slider = new Slider - { - Minimum = 0.5, - Maximum = 2.0, - Width = 220, - TickFrequency = 0.05, - IsSnapToTickEnabled = true, - VerticalAlignment = VerticalAlignment.Center, - [!Slider.ValueProperty] = new Binding(nameof(SupertonicCrispAsrSettingsViewModel.Speed)), - }; - var label = new TextBlock - { - VerticalAlignment = VerticalAlignment.Center, - FontWeight = FontWeight.SemiBold, - Margin = new Thickness(8, 0, 0, 0), - Width = 100, - [!TextBlock.TextProperty] = new Binding(nameof(SupertonicCrispAsrSettingsViewModel.SpeedLabel)), - }; - return new StackPanel - { - Orientation = Orientation.Horizontal, - Children = { slider, label }, - }; + var rows = TtsSettingsChrome.MakeRows(LabelWidth, + (Se.Language.General.Engine, TtsSettingsChrome.MakeStatus(nameof(vm.EngineBrush), nameof(vm.EngineLabel), + TtsSettingsChrome.MakeEngineDownloadButton(vm.RedownloadEngineCommand, nameof(vm.EngineDownloadButtonText)))), + (Se.Language.General.Model, TtsSettingsChrome.MakeStatus(nameof(vm.ModelBrush), nameof(vm.ModelLabel))), + (Se.Language.General.InstallFolder, TtsSettingsChrome.MakeFolder(nameof(vm.ModelsFolder), vm.OpenModelsFolderCommand))); + return TtsSettingsChrome.MakeSection(Se.Language.General.Models, rows); } - private static StackPanel MakeStatusPanel(string brushBindingPath, string labelBindingPath) + private static Border BuildVoice(SupertonicCrispAsrSettingsViewModel vm) { - var dot = new Ellipse - { - Width = 10, - Height = 10, - VerticalAlignment = VerticalAlignment.Center, - [!Ellipse.FillProperty] = new Binding(brushBindingPath), - }; - var text = new TextBlock - { - VerticalAlignment = VerticalAlignment.Center, - FontWeight = FontWeight.SemiBold, - Margin = new Thickness(8, 0, 0, 0), - [!TextBlock.TextProperty] = new Binding(labelBindingPath), - }; - return new StackPanel - { - Orientation = Orientation.Horizontal, - Children = { dot, text }, - }; + var rows = TtsSettingsChrome.MakeRows(LabelWidth, + (Se.Language.Video.Voices, TtsSettingsChrome.MakeFixedValue("10 preset voices (no voice cloning)")), + // Server accepts 0.25-4.0; cap UI at 0.5-2.0 since anything outside that range is + // already a hack rather than a "preferred pace" choice. + (Se.Language.General.Speed, TtsSettingsChrome.MakeSlider(Se.Language.General.Speed, 0.5, 2.0, 0.05, nameof(vm.Speed), nameof(vm.SpeedLabel)))); + return TtsSettingsChrome.MakeSection(Se.Language.General.Voice, rows); } - private static Grid BuildActions(SupertonicCrispAsrSettingsViewModel vm) - { - var openModelsFolder = UiUtil.MakeButton(Se.Language.General.OpenContainingFolder, vm.OpenModelsFolderCommand).WithIconLeft(IconNames.FolderOpen); - var close = UiUtil.MakeButton(Se.Language.General.Close, vm.OkCommand); - - var leftPanel = new StackPanel - { - Orientation = Orientation.Horizontal, - Spacing = 8, - Children = { openModelsFolder }, - }; - - var rightPanel = new StackPanel - { - Orientation = Orientation.Horizontal, - HorizontalAlignment = HorizontalAlignment.Right, - Spacing = 8, - Children = { close }, - }; - - var grid = new Grid - { - ColumnDefinitions = - { - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Auto) }, - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) }, - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Auto) }, - }, - }; - grid.Add(leftPanel, 0, 0); - grid.Add(rightPanel, 0, 2); - return grid; - } - - private static TextBlock MakeLabel(string text) => new() - { - Text = text, - Opacity = 0.7, - VerticalAlignment = VerticalAlignment.Center, - }; - protected override void OnKeyDown(KeyEventArgs e) { base.OnKeyDown(e); diff --git a/src/ui/Features/Video/TextToSpeech/TtsSettingsChrome.cs b/src/ui/Features/Video/TextToSpeech/TtsSettingsChrome.cs new file mode 100644 index 00000000000..24c124fa17a --- /dev/null +++ b/src/ui/Features/Video/TextToSpeech/TtsSettingsChrome.cs @@ -0,0 +1,397 @@ +using Avalonia; +using Avalonia.Automation; +using Avalonia.Controls; +using Avalonia.Controls.Primitives; +using Avalonia.Controls.Shapes; +using Avalonia.Data; +using Avalonia.Layout; +using Avalonia.Media; +using Avalonia.Styling; +using Nikse.SubtitleEdit.Logic; +using Nikse.SubtitleEdit.Logic.Config; +using System.Windows.Input; + +namespace Nikse.SubtitleEdit.Features.Video.TextToSpeech; + +/// +/// The shared editor look (see ) of the local TTS engine settings +/// dialogs (CrispASR and audio.cpp engines): an engine header, a "Models" section with the +/// runtime/model status rows, a "Voice" section with the per-request knobs, and a button bar. +/// +internal static class TtsSettingsChrome +{ + internal const double DefaultLabelWidth = 150; + internal const double SliderWidth = 200; + + /// + /// A fixed width with height-only auto-sizing: SizeToContent.WidthAndHeight comes out far + /// too wide on macOS (OmniVoice), and a fixed width lets long descriptions wrap. + /// + internal const double WindowWidth = 620; + + /// + /// Window chrome: editor background and styles, fixed width, height from the content. + /// + internal static void Apply(Window window, double width = WindowWidth) + { + window.Width = width; + window.SizeToContent = SizeToContent.Height; + window.CanResize = false; + window.Background = EditorChrome.WindowBrush; + EditorChrome.AddEditorStyles(window.Styles); + + // Compact combo boxes + window.Styles.Add(new Style(x => x.OfType()) + { + Setters = + { + new Setter(TemplatedControl.FontSizeProperty, 12.5), + new Setter(TemplatedControl.MinHeightProperty, 28.0), + new Setter(TemplatedControl.PaddingProperty, new Thickness(8, 2, 4, 2)), + new Setter(TemplatedControl.CornerRadiusProperty, new CornerRadius(6)), + }, + }); + } + + /// + /// The settings (scrollable on short screens) on the panel color, the button bar below. + /// + internal static Grid MakeContent(Control header, Border buttonBar, params Border[] sections) + { + var stack = new StackPanel(); + stack.Children.Add(header); + foreach (var section in sections) + { + stack.Children.Add(section); + } + + var root = new Grid + { + RowDefinitions = + { + new RowDefinition(new GridLength(1, GridUnitType.Star)), + new RowDefinition(GridLength.Auto), + }, + }; + root.Add(new Border + { + Background = EditorChrome.PanelBrush, + Child = new ScrollViewer + { + Content = stack, + VerticalScrollBarVisibility = ScrollBarVisibility.Auto, + HorizontalScrollBarVisibility = ScrollBarVisibility.Disabled, + }, + }, 0, 0); + root.Add(buttonBar, 1, 0); + return root; + } + + /// + /// Engine name, its description and optional extra lines (like a license link). + /// + internal static Border MakeHeader(string title, string description, params Control[] extra) + { + var panel = new StackPanel { Spacing = 3 }; + panel.Children.Add(new TextBlock + { + Text = title, + FontSize = 15, + FontWeight = FontWeight.SemiBold, + Foreground = EditorChrome.StrongTextBrush, + }); + panel.Children.Add(new TextBlock + { + Text = description, + FontSize = 12, + Foreground = EditorChrome.DimTextBrush, + TextWrapping = TextWrapping.Wrap, + }); + foreach (var control in extra) + { + panel.Children.Add(control); + } + + return new Border + { + Child = panel, + Padding = new Thickness(14, 14, 14, 12), + }; + } + + /// + /// A license link for the header. + /// + internal static TextBlock MakeLicenseLink(string text, CommunityToolkit.Mvvm.Input.IRelayCommand command) + { + var link = UiUtil.MakeLink(text, command); + link.FontSize = 12; + link.Margin = new Thickness(0, 2, 0, 0); + return link; + } + + /// + /// Rows of "label content", the labels in one column of fixed width so the rows line up. + /// + internal static Grid MakeRows(double labelWidth, params (string Label, Control Content)[] rows) + { + var grid = new Grid + { + ColumnSpacing = 12, + RowSpacing = 8, + ColumnDefinitions = + { + new ColumnDefinition(new GridLength(labelWidth)), + new ColumnDefinition(new GridLength(1, GridUnitType.Star)), + }, + }; + + for (var i = 0; i < rows.Length; i++) + { + grid.RowDefinitions.Add(new RowDefinition(GridLength.Auto)); + var label = EditorChrome.MakeDimLabel(rows[i].Label, new Thickness(0)); + label.TextTrimming = TextTrimming.CharacterEllipsis; + label.MinHeight = 28; + label.VerticalAlignment = VerticalAlignment.Top; + label.Padding = new Thickness(0, 6, 0, 0); + grid.Add(label, i, 0); + grid.Add(rows[i].Content, i, 1); + } + + return grid; + } + + /// + /// A status dot (installed/missing color from the view model) with its status text, and + /// optional actions after it. + /// + internal static StackPanel MakeStatus(string brushBindingPath, string labelBindingPath, params Control[] actions) + { + var panel = new StackPanel + { + Orientation = Orientation.Horizontal, + Spacing = 8, + MinHeight = 28, + Children = + { + new Ellipse + { + Width = 9, + Height = 9, + VerticalAlignment = VerticalAlignment.Center, + [!Shape.FillProperty] = new Binding(brushBindingPath), + }, + new TextBlock + { + FontSize = 12.5, + VerticalAlignment = VerticalAlignment.Center, + [!TextBlock.TextProperty] = new Binding(labelBindingPath), + }, + }, + }; + + foreach (var action in actions) + { + panel.Children.Add(action); + } + + return panel; + } + + /// + /// The engine (runtime) re-download button; its text comes from the view model + /// ("Download" / "Re-download"). + /// + internal static Button MakeEngineDownloadButton(ICommand command, string textBindingPath) + { + var button = new Button + { + Command = command, + Height = 28, + Padding = new Thickness(8, 0, 10, 0), + Margin = new Thickness(4, 0, 0, 0), + VerticalAlignment = VerticalAlignment.Center, + VerticalContentAlignment = VerticalAlignment.Center, + Content = new StackPanel + { + Orientation = Orientation.Horizontal, + Spacing = 6, + Children = + { + new Optris.Icons.Avalonia.Icon { Value = IconNames.Download, FontSize = 14, VerticalAlignment = VerticalAlignment.Center }, + new TextBlock { FontSize = 12.5, VerticalAlignment = VerticalAlignment.Center, [!TextBlock.TextProperty] = new Binding(textBindingPath) }, + }, + }, + }; + button.Classes.Add("tool"); + AutomationProperties.SetName(button, Se.Language.General.Download); + return button; + } + + /// + /// An icon-only download button for one model quant. + /// + internal static Button MakeModelDownloadButton(ICommand command, string modelKey) + { + var button = EditorChrome.CreateToolButton(IconNames.Download, Se.Language.General.Download + " " + modelKey, command, 26); + button.CommandParameter = modelKey; + button.Margin = new Thickness(2, 0, 0, 0); + return button; + } + + /// + /// An icon-only "open folder" button, placed beside the row it belongs to. + /// + internal static Button MakeFolderButton(string tooltip, ICommand command) + { + var button = EditorChrome.CreateToolButton(IconNames.FolderOpen, tooltip, command, 26); + button.Margin = new Thickness(2, 0, 0, 0); + return button; + } + + /// + /// A read-only value bound to the view model, with optional actions after it. + /// + internal static StackPanel MakeValue(string bindingPath, params Control[] actions) => + MakeValuePanel(new TextBlock { [!TextBlock.TextProperty] = new Binding(bindingPath) }, actions); + + /// + /// A fixed text value, with optional actions after it. + /// + internal static StackPanel MakeFixedValue(string text, params Control[] actions) => + MakeValuePanel(new TextBlock { Text = text }, actions); + + private static StackPanel MakeValuePanel(TextBlock text, Control[] actions) + { + text.FontSize = 12.5; + text.VerticalAlignment = VerticalAlignment.Center; + var panel = new StackPanel + { + Orientation = Orientation.Horizontal, + Spacing = 8, + MinHeight = 28, + Children = { text }, + }; + + foreach (var action in actions) + { + panel.Children.Add(action); + } + + return panel; + } + + /// + /// A slider with its value text after it. + /// + internal static StackPanel MakeSlider( + string accessibleName, + double minimum, + double maximum, + double tickFrequency, + string valueBindingPath, + string labelBindingPath, + string? isEnabledBindingPath = null) + { + var slider = new Slider + { + Minimum = minimum, + Maximum = maximum, + Width = SliderWidth, + TickFrequency = tickFrequency, + IsSnapToTickEnabled = true, + VerticalAlignment = VerticalAlignment.Center, + Margin = new Thickness(0, -6), + [!RangeBase.ValueProperty] = new Binding(valueBindingPath), + }; + AutomationProperties.SetName(slider, accessibleName); + + var label = new TextBlock + { + FontSize = 12.5, + Foreground = EditorChrome.DimTextBrush, + VerticalAlignment = VerticalAlignment.Center, + [!TextBlock.TextProperty] = new Binding(labelBindingPath), + }; + + if (isEnabledBindingPath != null) + { + slider[!Avalonia.Input.InputElement.IsEnabledProperty] = new Binding(isEnabledBindingPath); + label[!Avalonia.Input.InputElement.IsEnabledProperty] = new Binding(isEnabledBindingPath); + } + + return new StackPanel + { + Orientation = Orientation.Horizontal, + Spacing = 12, + MinHeight = 28, + Children = { slider, label }, + }; + } + + /// + /// The install folder path (selectable, read-only) with an "open folder" button after it. + /// + internal static Grid MakeFolder(string bindingPath, ICommand openCommand) + { + var text = new TextBox + { + IsReadOnly = true, + BorderThickness = new Thickness(0), + Background = Brushes.Transparent, + Foreground = EditorChrome.DimTextBrush, + Padding = new Thickness(0), + MinHeight = 0, + TextWrapping = TextWrapping.Wrap, + VerticalAlignment = VerticalAlignment.Center, + VerticalContentAlignment = VerticalAlignment.Center, + FontSize = 12, + [!TextBox.TextProperty] = new Binding(bindingPath), + }; + AutomationProperties.SetName(text, Se.Language.General.InstallFolder); + + var grid = new Grid + { + ColumnSpacing = 6, + MinHeight = 28, + ColumnDefinitions = + { + new ColumnDefinition(new GridLength(1, GridUnitType.Star)), + new ColumnDefinition(GridLength.Auto), + }, + }; + grid.Add(text, 0, 0); + grid.Add(MakeFolderButton(Se.Language.General.OpenContainingFolder, openCommand), 0, 1); + return grid; + } + + /// + /// A dim, wrapping hint below a field. + /// + internal static TextBlock MakeHint(string text) => new() + { + Text = text, + FontSize = 11.5, + Foreground = EditorChrome.DimTextBrush, + TextWrapping = TextWrapping.Wrap, + }; + + internal static Border MakeSection(string title, Control content) => EditorChrome.MakeEditorSection(title, content); + + /// + /// The bottom button bar with the accent close button on the right. + /// + internal static Border MakeButtonBar(Button close) + { + close.Classes.Add("primary"); + close.HorizontalAlignment = HorizontalAlignment.Right; + return new Border + { + Child = close, + Background = EditorChrome.ButtonBarBrush, + BorderBrush = EditorChrome.LineBrush, + BorderThickness = new Thickness(0, 1, 0, 0), + Padding = new Thickness(16, 10), + }; + } +} diff --git a/src/ui/Features/Video/TextToSpeech/VibeVoiceCrispAsrSettings/VibeVoiceCrispAsrSettingsWindow.cs b/src/ui/Features/Video/TextToSpeech/VibeVoiceCrispAsrSettings/VibeVoiceCrispAsrSettingsWindow.cs index ec99e739d8c..86df78d2429 100644 --- a/src/ui/Features/Video/TextToSpeech/VibeVoiceCrispAsrSettings/VibeVoiceCrispAsrSettingsWindow.cs +++ b/src/ui/Features/Video/TextToSpeech/VibeVoiceCrispAsrSettings/VibeVoiceCrispAsrSettingsWindow.cs @@ -1,20 +1,17 @@ -using Avalonia; using Avalonia.Controls; -using Avalonia.Controls.Shapes; -using Avalonia.Data; using Avalonia.Input; -using Avalonia.Layout; -using Avalonia.Media; using Nikse.SubtitleEdit.Features.Video.TextToSpeech.Engines; using Nikse.SubtitleEdit.Logic; using Nikse.SubtitleEdit.Logic.Config; namespace Nikse.SubtitleEdit.Features.Video.TextToSpeech.VibeVoiceCrispAsrSettings; +/// +/// VibeVoice (CrispASR) settings, in the editor look (see ). +/// public class VibeVoiceCrispAsrSettingsWindow : Window { private const int LabelWidth = 150; - private const int ValueWidth = 360; private readonly VibeVoiceCrispAsrSettingsViewModel _vm; @@ -22,241 +19,45 @@ public VibeVoiceCrispAsrSettingsWindow(VibeVoiceCrispAsrSettingsViewModel vm) { UiUtil.InitializeWindow(this, GetType().Name); Title = string.Format(Se.Language.Video.TtsCrispAsrSettingsTitle, "VibeVoice"); - SizeToContent = SizeToContent.WidthAndHeight; - CanResize = false; - MinWidth = 580; + TtsSettingsChrome.Apply(this); _vm = vm; vm.Window = this; DataContext = vm; - Content = BuildContent(vm); - - var ok = UiUtil.MakeButtonOk(vm.OkCommand); - UiUtil.FocusOnFirstActivation(this, ok); - } - - private Border BuildContent(VibeVoiceCrispAsrSettingsViewModel vm) - { - var header = BuildHeader(); - var details = BuildDetails(vm); - var actions = BuildActions(vm); - - var stack = new StackPanel - { - Orientation = Orientation.Vertical, - Spacing = 14, - Children = { header, details, actions }, - }; - - var outerGrid = new Grid - { - Margin = UiUtil.MakeWindowMargin(), - }; - outerGrid.Children.Add(stack); - - return new Border - { - Child = outerGrid, - Padding = new Thickness(4), - }; - } - - private static StackPanel BuildHeader() - { - var title = new TextBlock - { - Text = "VibeVoice (CrispASR)", - FontSize = UiUtil.ScaledFontSize(18), - FontWeight = FontWeight.SemiBold, - }; - - var subtitle = new TextBlock - { - Text = new VibeVoiceCrispAsr().Description, - FontSize = UiUtil.ScaledFontSize(12), - Opacity = 0.75, - Margin = new Thickness(0, 2, 0, 0), - }; - - return new StackPanel - { - Orientation = Orientation.Vertical, - Spacing = 0, - Children = { title, subtitle }, - }; - } - - private static Border BuildDetails(VibeVoiceCrispAsrSettingsViewModel vm) - { - var grid = new Grid - { - ColumnDefinitions = - { - new ColumnDefinition { Width = new GridLength(LabelWidth, GridUnitType.Pixel) }, - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) }, - }, - RowDefinitions = - { - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - }, - ColumnSpacing = 12, - RowSpacing = 10, - }; - - grid.Add(MakeLabel(Se.Language.General.Engine), 0, 0); - var enginePanel = MakeStatusPanel(nameof(vm.EngineBrush), nameof(vm.EngineLabel)); - var engineButton = UiUtil.MakeButton(string.Empty, vm.RedownloadEngineCommand) - .WithIconLeftBindText(IconNames.Download, nameof(vm.EngineDownloadButtonText)) - .WithMarginLeft(12); - enginePanel.Children.Add(engineButton); - grid.Add(enginePanel, 0, 1); - - grid.Add(MakeLabel("1.5B " + VibeVoiceCrispAsr.ModelKeyQ4K), 1, 0); - grid.Add(MakeStatusPanel(nameof(vm.TalkerQ4KBrush), nameof(vm.TalkerQ4KLabel)), 1, 1); - - grid.Add(MakeLabel("1.5B " + VibeVoiceCrispAsr.ModelKeyQ8_0), 2, 0); - grid.Add(MakeStatusPanel(nameof(vm.TalkerQ8_0Brush), nameof(vm.TalkerQ8_0Label)), 2, 1); - - grid.Add(MakeLabel("1.5B " + VibeVoiceCrispAsr.ModelKeyF16), 3, 0); - grid.Add(MakeStatusPanel(nameof(vm.TalkerF16Brush), nameof(vm.TalkerF16Label)), 3, 1); - - grid.Add(MakeLabel(Se.Language.Video.Voices), 4, 0); - var voicesText = new TextBlock - { - VerticalAlignment = VerticalAlignment.Center, - FontWeight = FontWeight.SemiBold, - [!TextBlock.TextProperty] = new Binding(nameof(vm.VoicesLabel)), - }; - grid.Add(voicesText, 4, 1); - - grid.Add(MakeLabel(Se.Language.General.Speed), 5, 0); - grid.Add(MakeSpeedPanel(), 5, 1); - - grid.Add(MakeLabel(Se.Language.General.InstallFolder), 6, 0); - var folderText = new TextBox - { - IsReadOnly = true, - Width = ValueWidth, - BorderThickness = new Thickness(0), - Background = Brushes.Transparent, - Padding = new Thickness(0), - VerticalContentAlignment = VerticalAlignment.Center, - FontSize = UiUtil.ScaledFontSize(12), - [!TextBox.TextProperty] = new Binding(nameof(vm.ModelsFolder)), - }; - grid.Add(folderText, 6, 1); - - return new Border - { - Child = grid, - Padding = new Thickness(14), - CornerRadius = new CornerRadius(6), - BorderThickness = new Thickness(1), - BorderBrush = new SolidColorBrush(Color.FromArgb(0x40, 0x80, 0x80, 0x80)), - }; - } + var close = UiUtil.MakeButton(Se.Language.General.Close, vm.OkCommand); + Content = TtsSettingsChrome.MakeContent( + TtsSettingsChrome.MakeHeader("VibeVoice (CrispASR)", new VibeVoiceCrispAsr().Description), + TtsSettingsChrome.MakeButtonBar(close), + BuildModels(vm), + BuildVoice(vm)); - private static StackPanel MakeSpeedPanel() - { - // Server accepts 0.25-4.0; cap UI at 0.5-2.0 since anything outside that range is - // already a hack rather than a "preferred pace" choice. - var slider = new Slider - { - Minimum = 0.5, - Maximum = 2.0, - Width = 220, - TickFrequency = 0.05, - IsSnapToTickEnabled = true, - VerticalAlignment = VerticalAlignment.Center, - [!Slider.ValueProperty] = new Binding(nameof(VibeVoiceCrispAsrSettingsViewModel.Speed)), - }; - var label = new TextBlock - { - VerticalAlignment = VerticalAlignment.Center, - FontWeight = FontWeight.SemiBold, - Margin = new Thickness(8, 0, 0, 0), - Width = 80, - [!TextBlock.TextProperty] = new Binding(nameof(VibeVoiceCrispAsrSettingsViewModel.SpeedLabel)), - }; - return new StackPanel - { - Orientation = Orientation.Horizontal, - Children = { slider, label }, - }; + UiUtil.FocusOnFirstActivation(this, close); } - private static StackPanel MakeStatusPanel(string brushBindingPath, string labelBindingPath) + private static Border BuildModels(VibeVoiceCrispAsrSettingsViewModel vm) { - var dot = new Ellipse - { - Width = 10, - Height = 10, - VerticalAlignment = VerticalAlignment.Center, - [!Ellipse.FillProperty] = new Binding(brushBindingPath), - }; - var text = new TextBlock - { - VerticalAlignment = VerticalAlignment.Center, - FontWeight = FontWeight.SemiBold, - Margin = new Thickness(8, 0, 0, 0), - [!TextBlock.TextProperty] = new Binding(labelBindingPath), - }; - return new StackPanel - { - Orientation = Orientation.Horizontal, - Children = { dot, text }, - }; + var rows = TtsSettingsChrome.MakeRows(LabelWidth, + (Se.Language.General.Engine, TtsSettingsChrome.MakeStatus(nameof(vm.EngineBrush), nameof(vm.EngineLabel), + TtsSettingsChrome.MakeEngineDownloadButton(vm.RedownloadEngineCommand, nameof(vm.EngineDownloadButtonText)))), + ("1.5B " + VibeVoiceCrispAsr.ModelKeyQ4K, TtsSettingsChrome.MakeStatus(nameof(vm.TalkerQ4KBrush), nameof(vm.TalkerQ4KLabel))), + ("1.5B " + VibeVoiceCrispAsr.ModelKeyQ8_0, TtsSettingsChrome.MakeStatus(nameof(vm.TalkerQ8_0Brush), nameof(vm.TalkerQ8_0Label))), + ("1.5B " + VibeVoiceCrispAsr.ModelKeyF16, TtsSettingsChrome.MakeStatus(nameof(vm.TalkerF16Brush), nameof(vm.TalkerF16Label))), + (Se.Language.General.InstallFolder, TtsSettingsChrome.MakeFolder(nameof(vm.ModelsFolder), vm.OpenModelsFolderCommand))); + return TtsSettingsChrome.MakeSection(Se.Language.General.Models, rows); } - private static Grid BuildActions(VibeVoiceCrispAsrSettingsViewModel vm) + private static Border BuildVoice(VibeVoiceCrispAsrSettingsViewModel vm) { - var openModelsFolder = UiUtil.MakeButton(Se.Language.General.OpenContainingFolder, vm.OpenModelsFolderCommand).WithIconLeft(IconNames.FolderOpen); - var openVoicesFolder = UiUtil.MakeButton("Voices folder", vm.OpenVoicesFolderCommand).WithIconLeft(IconNames.FolderOpen); - var close = UiUtil.MakeButton(Se.Language.General.Close, vm.OkCommand); - - var leftPanel = new StackPanel - { - Orientation = Orientation.Horizontal, - Spacing = 8, - Children = { openModelsFolder, openVoicesFolder }, - }; - - var rightPanel = new StackPanel - { - Orientation = Orientation.Horizontal, - HorizontalAlignment = HorizontalAlignment.Right, - Spacing = 8, - Children = { close }, - }; - - var grid = new Grid - { - ColumnDefinitions = - { - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Auto) }, - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) }, - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Auto) }, - }, - }; - grid.Add(leftPanel, 0, 0); - grid.Add(rightPanel, 0, 2); - return grid; + var rows = TtsSettingsChrome.MakeRows(LabelWidth, + (Se.Language.Video.Voices, TtsSettingsChrome.MakeValue(nameof(vm.VoicesLabel), + TtsSettingsChrome.MakeFolderButton(Se.Language.Video.TextToSpeech.OpenVoicesFolder, vm.OpenVoicesFolderCommand))), + // Server accepts 0.25-4.0; cap UI at 0.5-2.0 since anything outside that range is + // already a hack rather than a "preferred pace" choice. + (Se.Language.General.Speed, TtsSettingsChrome.MakeSlider(Se.Language.General.Speed, 0.5, 2.0, 0.05, nameof(vm.Speed), nameof(vm.SpeedLabel)))); + return TtsSettingsChrome.MakeSection(Se.Language.General.Voice, rows); } - private static TextBlock MakeLabel(string text) => new() - { - Text = text, - Opacity = 0.7, - VerticalAlignment = VerticalAlignment.Center, - }; - protected override void OnKeyDown(KeyEventArgs e) { base.OnKeyDown(e); diff --git a/src/ui/Features/Video/TextToSpeech/VoxCPM2CrispAsrSettings/VoxCPM2CrispAsrSettingsWindow.cs b/src/ui/Features/Video/TextToSpeech/VoxCPM2CrispAsrSettings/VoxCPM2CrispAsrSettingsWindow.cs index 914a6b18815..d64036ae7f1 100644 --- a/src/ui/Features/Video/TextToSpeech/VoxCPM2CrispAsrSettings/VoxCPM2CrispAsrSettingsWindow.cs +++ b/src/ui/Features/Video/TextToSpeech/VoxCPM2CrispAsrSettings/VoxCPM2CrispAsrSettingsWindow.cs @@ -1,20 +1,17 @@ -using Avalonia; using Avalonia.Controls; -using Avalonia.Controls.Shapes; -using Avalonia.Data; using Avalonia.Input; -using Avalonia.Layout; -using Avalonia.Media; using Nikse.SubtitleEdit.Features.Video.TextToSpeech.Engines; using Nikse.SubtitleEdit.Logic; using Nikse.SubtitleEdit.Logic.Config; namespace Nikse.SubtitleEdit.Features.Video.TextToSpeech.VoxCPM2CrispAsrSettings; +/// +/// VoxCPM2 (CrispASR) settings, in the editor look (see ). +/// public class VoxCPM2CrispAsrSettingsWindow : Window { private const int LabelWidth = 150; - private const int ValueWidth = 360; private readonly VoxCPM2CrispAsrSettingsViewModel _vm; @@ -22,235 +19,42 @@ public VoxCPM2CrispAsrSettingsWindow(VoxCPM2CrispAsrSettingsViewModel vm) { UiUtil.InitializeWindow(this, GetType().Name); Title = string.Format(Se.Language.Video.TtsCrispAsrSettingsTitle, "VoxCPM2"); - SizeToContent = SizeToContent.WidthAndHeight; - CanResize = false; - MinWidth = 580; + TtsSettingsChrome.Apply(this); _vm = vm; vm.Window = this; DataContext = vm; - Content = BuildContent(vm); - - var ok = UiUtil.MakeButtonOk(vm.OkCommand); - UiUtil.FocusOnFirstActivation(this, ok); - } - - private Border BuildContent(VoxCPM2CrispAsrSettingsViewModel vm) - { - var header = BuildHeader(); - var details = BuildDetails(vm); - var actions = BuildActions(vm); - - var stack = new StackPanel - { - Orientation = Orientation.Vertical, - Spacing = 14, - Children = { header, details, actions }, - }; - - var outerGrid = new Grid - { - Margin = UiUtil.MakeWindowMargin(), - }; - outerGrid.Children.Add(stack); - - return new Border - { - Child = outerGrid, - Padding = new Thickness(4), - }; - } - - private static StackPanel BuildHeader() - { - var title = new TextBlock - { - Text = "VoxCPM2 (CrispASR)", - FontSize = UiUtil.ScaledFontSize(18), - FontWeight = FontWeight.SemiBold, - }; - - var subtitle = new TextBlock - { - Text = new VoxCPM2CrispAsr().Description, - FontSize = UiUtil.ScaledFontSize(12), - Opacity = 0.75, - Margin = new Thickness(0, 2, 0, 0), - }; - - return new StackPanel - { - Orientation = Orientation.Vertical, - Spacing = 0, - Children = { title, subtitle }, - }; - } - - private static Border BuildDetails(VoxCPM2CrispAsrSettingsViewModel vm) - { - var grid = new Grid - { - ColumnDefinitions = - { - new ColumnDefinition { Width = new GridLength(LabelWidth, GridUnitType.Pixel) }, - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) }, - }, - RowDefinitions = - { - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - }, - ColumnSpacing = 12, - RowSpacing = 10, - }; - - grid.Add(MakeLabel(Se.Language.General.Engine), 0, 0); - var enginePanel = MakeStatusPanel(nameof(vm.EngineBrush), nameof(vm.EngineLabel)); - var engineButton = UiUtil.MakeButton(string.Empty, vm.RedownloadEngineCommand) - .WithIconLeftBindText(IconNames.Download, nameof(vm.EngineDownloadButtonText)) - .WithMarginLeft(12); - enginePanel.Children.Add(engineButton); - grid.Add(enginePanel, 0, 1); - - grid.Add(MakeLabel("Model " + VoxCPM2CrispAsr.ModelKeyQ4K), 1, 0); - grid.Add(MakeStatusPanel(nameof(vm.ModelQ4KBrush), nameof(vm.ModelQ4KLabel)), 1, 1); - - grid.Add(MakeLabel("Model " + VoxCPM2CrispAsr.ModelKeyF16), 2, 0); - grid.Add(MakeStatusPanel(nameof(vm.ModelF16Brush), nameof(vm.ModelF16Label)), 2, 1); - - grid.Add(MakeLabel(Se.Language.Video.Voices), 3, 0); - var voicesText = new TextBlock - { - VerticalAlignment = VerticalAlignment.Center, - FontWeight = FontWeight.SemiBold, - [!TextBlock.TextProperty] = new Binding(nameof(vm.VoicesLabel)), - }; - grid.Add(voicesText, 3, 1); - - grid.Add(MakeLabel(Se.Language.General.Speed), 4, 0); - grid.Add(MakeSpeedPanel(), 4, 1); - - grid.Add(MakeLabel(Se.Language.General.InstallFolder), 5, 0); - var folderText = new TextBox - { - IsReadOnly = true, - Width = ValueWidth, - BorderThickness = new Thickness(0), - Background = Brushes.Transparent, - Padding = new Thickness(0), - VerticalContentAlignment = VerticalAlignment.Center, - FontSize = UiUtil.ScaledFontSize(12), - [!TextBox.TextProperty] = new Binding(nameof(vm.ModelsFolder)), - }; - grid.Add(folderText, 5, 1); + var close = UiUtil.MakeButton(Se.Language.General.Close, vm.OkCommand); + Content = TtsSettingsChrome.MakeContent( + TtsSettingsChrome.MakeHeader("VoxCPM2 (CrispASR)", new VoxCPM2CrispAsr().Description), + TtsSettingsChrome.MakeButtonBar(close), + BuildModels(vm), + BuildVoice(vm)); - return new Border - { - Child = grid, - Padding = new Thickness(14), - CornerRadius = new CornerRadius(6), - BorderThickness = new Thickness(1), - BorderBrush = new SolidColorBrush(Color.FromArgb(0x40, 0x80, 0x80, 0x80)), - }; + UiUtil.FocusOnFirstActivation(this, close); } - private static StackPanel MakeSpeedPanel() + private static Border BuildModels(VoxCPM2CrispAsrSettingsViewModel vm) { - var slider = new Slider - { - Minimum = 0.5, - Maximum = 2.0, - Width = 220, - TickFrequency = 0.05, - IsSnapToTickEnabled = true, - VerticalAlignment = VerticalAlignment.Center, - [!Slider.ValueProperty] = new Binding(nameof(VoxCPM2CrispAsrSettingsViewModel.Speed)), - }; - var label = new TextBlock - { - VerticalAlignment = VerticalAlignment.Center, - FontWeight = FontWeight.SemiBold, - Margin = new Thickness(8, 0, 0, 0), - Width = 80, - [!TextBlock.TextProperty] = new Binding(nameof(VoxCPM2CrispAsrSettingsViewModel.SpeedLabel)), - }; - return new StackPanel - { - Orientation = Orientation.Horizontal, - Children = { slider, label }, - }; + var rows = TtsSettingsChrome.MakeRows(LabelWidth, + (Se.Language.General.Engine, TtsSettingsChrome.MakeStatus(nameof(vm.EngineBrush), nameof(vm.EngineLabel), + TtsSettingsChrome.MakeEngineDownloadButton(vm.RedownloadEngineCommand, nameof(vm.EngineDownloadButtonText)))), + ("Model " + VoxCPM2CrispAsr.ModelKeyQ4K, TtsSettingsChrome.MakeStatus(nameof(vm.ModelQ4KBrush), nameof(vm.ModelQ4KLabel))), + ("Model " + VoxCPM2CrispAsr.ModelKeyF16, TtsSettingsChrome.MakeStatus(nameof(vm.ModelF16Brush), nameof(vm.ModelF16Label))), + (Se.Language.General.InstallFolder, TtsSettingsChrome.MakeFolder(nameof(vm.ModelsFolder), vm.OpenModelsFolderCommand))); + return TtsSettingsChrome.MakeSection(Se.Language.General.Models, rows); } - private static StackPanel MakeStatusPanel(string brushBindingPath, string labelBindingPath) + private static Border BuildVoice(VoxCPM2CrispAsrSettingsViewModel vm) { - var dot = new Ellipse - { - Width = 10, - Height = 10, - VerticalAlignment = VerticalAlignment.Center, - [!Ellipse.FillProperty] = new Binding(brushBindingPath), - }; - var text = new TextBlock - { - VerticalAlignment = VerticalAlignment.Center, - FontWeight = FontWeight.SemiBold, - Margin = new Thickness(8, 0, 0, 0), - [!TextBlock.TextProperty] = new Binding(labelBindingPath), - }; - return new StackPanel - { - Orientation = Orientation.Horizontal, - Children = { dot, text }, - }; + var rows = TtsSettingsChrome.MakeRows(LabelWidth, + (Se.Language.Video.Voices, TtsSettingsChrome.MakeValue(nameof(vm.VoicesLabel), + TtsSettingsChrome.MakeFolderButton(Se.Language.Video.TextToSpeech.OpenVoicesFolder, vm.OpenVoicesFolderCommand))), + (Se.Language.General.Speed, TtsSettingsChrome.MakeSlider(Se.Language.General.Speed, 0.5, 2.0, 0.05, nameof(vm.Speed), nameof(vm.SpeedLabel)))); + return TtsSettingsChrome.MakeSection(Se.Language.General.Voice, rows); } - private static Grid BuildActions(VoxCPM2CrispAsrSettingsViewModel vm) - { - var openModelsFolder = UiUtil.MakeButton(Se.Language.General.OpenContainingFolder, vm.OpenModelsFolderCommand).WithIconLeft(IconNames.FolderOpen); - var openVoicesFolder = UiUtil.MakeButton("Voices folder", vm.OpenVoicesFolderCommand).WithIconLeft(IconNames.FolderOpen); - var close = UiUtil.MakeButton(Se.Language.General.Close, vm.OkCommand); - - var leftPanel = new StackPanel - { - Orientation = Orientation.Horizontal, - Spacing = 8, - Children = { openModelsFolder, openVoicesFolder }, - }; - - var rightPanel = new StackPanel - { - Orientation = Orientation.Horizontal, - HorizontalAlignment = HorizontalAlignment.Right, - Spacing = 8, - Children = { close }, - }; - - var grid = new Grid - { - ColumnDefinitions = - { - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Auto) }, - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) }, - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Auto) }, - }, - }; - grid.Add(leftPanel, 0, 0); - grid.Add(rightPanel, 0, 2); - return grid; - } - - private static TextBlock MakeLabel(string text) => new() - { - Text = text, - Opacity = 0.7, - VerticalAlignment = VerticalAlignment.Center, - }; - protected override void OnKeyDown(KeyEventArgs e) { base.OnKeyDown(e); From c247ac7085e2eded84e4c6d65b6c3e13745639aa Mon Sep 17 00:00:00 2001 From: niksedk Date: Thu, 15 Oct 2026 15:34:10 +0200 Subject: [PATCH 190/306] TTS engine and voice settings windows in the editor look Co-Authored-By: Claude Opus 5.5 --- .../AdvancedTtsSettingsWindow.cs | 261 ++++++++++------ .../ChatterboxTtsSettingsWindow.cs | 281 +++++++++--------- .../ElevenLabsSettingsWindow.cs | 204 ++++++------- .../EncodingSettingsWindow.cs | 114 ++++--- .../KokoroTtsSettingsWindow.cs | 266 ++++++++--------- .../OmniVoiceSettingsWindow.cs | 270 ++++++++--------- .../OpenAiCompatibleSettingsWindow.cs | 166 ++++++++--- .../PiperSettings/PiperSettingsWindow.cs | 266 ++++++++--------- .../Qwen3TtsSettingsWindow.cs | 266 ++++++++--------- .../VoiceSettings/VoiceSettingsWindow.cs | 143 ++++++--- .../AdvancedTtsSettingsWindowTests.cs | 27 +- 11 files changed, 1229 insertions(+), 1035 deletions(-) diff --git a/src/ui/Features/Video/TextToSpeech/AdvancedTtsSettings/AdvancedTtsSettingsWindow.cs b/src/ui/Features/Video/TextToSpeech/AdvancedTtsSettings/AdvancedTtsSettingsWindow.cs index 68bc5422786..41c3697a779 100644 --- a/src/ui/Features/Video/TextToSpeech/AdvancedTtsSettings/AdvancedTtsSettingsWindow.cs +++ b/src/ui/Features/Video/TextToSpeech/AdvancedTtsSettings/AdvancedTtsSettingsWindow.cs @@ -6,6 +6,7 @@ using Avalonia.Input; using Avalonia.Layout; using Avalonia.Media; +using Avalonia.Styling; using Nikse.SubtitleEdit.Logic; using Nikse.SubtitleEdit.Logic.Config; using System.IO; @@ -17,6 +18,8 @@ namespace Nikse.SubtitleEdit.Features.Video.TextToSpeech.AdvancedTtsSettings; /// made the window several screens tall - on a laptop it was clamped to the working area and the /// OK/Cancel buttons ended up off-screen (#14331). The explanations now live in hover hints, the /// options are grouped, and the content scrolls with the buttons pinned below it. +/// In the editor look (see ): sections on the panel color, switches for +/// the on/off options, compact fields, Cancel/OK in the button bar. ///
public class AdvancedTtsSettingsWindow : Window { @@ -32,6 +35,9 @@ public AdvancedTtsSettingsWindow(AdvancedTtsSettingsViewModel vm) Width = 560; SizeToContent = SizeToContent.Height; CanResize = false; + Background = EditorChrome.WindowBrush; + EditorChrome.AddEditorStyles(Styles); + AddStyles(); _vm = vm; vm.Window = this; @@ -39,91 +45,93 @@ public AdvancedTtsSettingsWindow(AdvancedTtsSettingsViewModel vm) var l = Se.Language.Video.TextToSpeech; - var checkBoxProAudio = MakeCheckBox(l.ProAudioPostProcessing, nameof(vm.DoProAudioChain)); - var checkBoxDucking = MakeCheckBox(l.AudioDucking, nameof(vm.DoAudioDucking)); - var checkBoxRemoveSpeech = MakeCheckBox(l.RemoveOriginalSpeech, nameof(vm.DoRemoveOriginalSpeech)); - var checkBoxVad = MakeCheckBox(l.VadSilenceCompression, nameof(vm.DoVadSilenceCompression)); - var checkBoxTimeStretch = MakeCheckBox(l.HighQualityTimeStretch, nameof(vm.DoHighQualityTimeStretch)); - var checkBoxDeleteTempFiles = MakeCheckBox(l.DeleteTempFiles, nameof(vm.DoDeleteTempFiles)); - var checkBoxCleanCloneReferences = MakeCheckBox(l.CleanCloneReferences, nameof(vm.DoCleanCloneReferences)); + var switchProAudio = MakeSwitch(l.ProAudioPostProcessing, nameof(vm.DoProAudioChain), out var toggleProAudio); + var switchDucking = MakeSwitch(l.AudioDucking, nameof(vm.DoAudioDucking), out var toggleDucking); + var switchRemoveSpeech = MakeSwitch(l.RemoveOriginalSpeech, nameof(vm.DoRemoveOriginalSpeech), out var toggleRemoveSpeech); + var switchVad = MakeSwitch(l.VadSilenceCompression, nameof(vm.DoVadSilenceCompression), out var toggleVad); + var switchTimeStretch = MakeSwitch(l.HighQualityTimeStretch, nameof(vm.DoHighQualityTimeStretch), out var toggleTimeStretch); + var switchDeleteTempFiles = MakeSwitch(l.DeleteTempFiles, nameof(vm.DoDeleteTempFiles), out var toggleDeleteTempFiles); + var switchCleanCloneReferences = MakeSwitch(l.CleanCloneReferences, nameof(vm.DoCleanCloneReferences), out var toggleCleanCloneReferences); - var groupAudio = MakeGroupBox(l.AdvancedTtsAudioProcessing, new StackPanel + var speechRemovalModel = new ComboBox { - Spacing = 6, + Width = 240, + ItemsSource = vm.SpeechRemovalModels, + [!SelectingItemsControl.SelectedIndexProperty] = new Binding(nameof(vm.SpeechRemovalModelIndex)) { Mode = BindingMode.TwoWay }, + [!IsEnabledProperty] = new Binding(nameof(vm.DoRemoveOriginalSpeech)) { Mode = BindingMode.OneWay }, + }; + + var groupAudio = EditorChrome.MakeEditorSection(l.AdvancedTtsAudioProcessing, new StackPanel + { + Spacing = 8, Children = { - MakeRow(checkBoxProAudio, l.ProAudioPostProcessingDescription), + MakeRow(switchProAudio, l.ProAudioPostProcessingDescription, describes: toggleProAudio), - MakeRow(checkBoxDucking, l.AudioDuckingDescription, + MakeRow(switchDucking, l.AudioDuckingDescription, MakeField(l.OriginalVolumePercent, - UiUtil.MakeNumericUpDownInt(0, 100, 15, 110, vm, nameof(vm.AudioDuckingVolume)), - nameof(vm.DoAudioDucking))), + MakeNumber(UiUtil.MakeNumericUpDownInt(0, 100, 15, 90, vm, nameof(vm.AudioDuckingVolume)), l.OriginalVolumePercent), + nameof(vm.DoAudioDucking)), + describes: toggleDucking), - MakeRow(checkBoxRemoveSpeech, l.RemoveOriginalSpeechDescription), + MakeRow(switchRemoveSpeech, l.RemoveOriginalSpeechDescription, describes: toggleRemoveSpeech), - MakeRow(UiUtil.MakeLabel(l.SpeechRemovalModel), l.SpeechRemovalModelDescription, - new ComboBox - { - Width = 240, - ItemsSource = vm.SpeechRemovalModels, - [!SelectingItemsControl.SelectedIndexProperty] = new Binding(nameof(vm.SpeechRemovalModelIndex)) { Mode = BindingMode.TwoWay }, - [!IsEnabledProperty] = new Binding(nameof(vm.DoRemoveOriginalSpeech)) { Mode = BindingMode.OneWay }, - }), + MakeRow(MakeLabel(l.SpeechRemovalModel), l.SpeechRemovalModelDescription, speechRemovalModel), - MakeRow(checkBoxVad, l.VadSilenceCompressionDescription, + MakeRow(switchVad, l.VadSilenceCompressionDescription, MakeField(l.MaxSilenceMs, - UiUtil.MakeNumericUpDownInt(0, 5000, 150, 110, vm, nameof(vm.VadMaxSilenceMs)), - nameof(vm.DoVadSilenceCompression))), + MakeNumber(UiUtil.MakeNumericUpDownInt(0, 5000, 150, 90, vm, nameof(vm.VadMaxSilenceMs)), l.MaxSilenceMs), + nameof(vm.DoVadSilenceCompression)), + describes: toggleVad), - MakeRow(checkBoxTimeStretch, l.HighQualityTimeStretchDescription, - status: MakeStatusLabel(nameof(vm.RubberbandStatus))), + MakeRow(switchTimeStretch, l.HighQualityTimeStretchDescription, + status: MakeStatusLabel(nameof(vm.RubberbandStatus)), describes: toggleTimeStretch), } - }); + }, first: true); - var groupOutput = MakeGroupBox(l.AdvancedTtsOutput, new StackPanel + var groupOutput = EditorChrome.MakeEditorSection(l.AdvancedTtsOutput, new StackPanel { - Spacing = 6, + Spacing = 8, Children = { - MakeRow(UiUtil.MakeLabel(l.SilencePaddingMs), l.SilencePaddingMsDescription, - UiUtil.MakeNumericUpDownInt(0, 10000, 0, 110, vm, nameof(vm.SilencePaddingMs))), + MakeRow(MakeLabel(l.SilencePaddingMs), l.SilencePaddingMsDescription, + MakeNumber(UiUtil.MakeNumericUpDownInt(0, 10000, 0, 110, vm, nameof(vm.SilencePaddingMs)), null)), - MakeRow(UiUtil.MakeLabel(l.OutputSampleRate), l.OutputSampleRateDescription, - UiUtil.MakeNumericUpDownInt(0, 192000, 0, 120, vm, nameof(vm.OutputSampleRate))), + MakeRow(MakeLabel(l.OutputSampleRate), l.OutputSampleRateDescription, + MakeNumber(UiUtil.MakeNumericUpDownInt(0, 192000, 0, 110, vm, nameof(vm.OutputSampleRate)), null)), } }); - var groupVoiceCloning = MakeGroupBox(l.AdvancedTtsVoiceCloning, new StackPanel + var groupVoiceCloning = EditorChrome.MakeEditorSection(l.AdvancedTtsVoiceCloning, new StackPanel { - Spacing = 6, + Spacing = 8, Children = { - MakeRow(checkBoxCleanCloneReferences, l.CleanCloneReferencesDescription), + MakeRow(switchCleanCloneReferences, l.CleanCloneReferencesDescription, describes: toggleCleanCloneReferences), } }); - var groupFolder = MakeGroupBox(l.GenerationFolder, MakeGenerationFolderContent(vm, checkBoxDeleteTempFiles)); + var groupFolder = EditorChrome.MakeEditorSection(l.GenerationFolder, MakeGenerationFolderContent(vm, switchDeleteTempFiles, toggleDeleteTempFiles)); - var groupEdgeTts = MakeGroupBox("Edge-TTS", new StackPanel + var groupEdgeTts = EditorChrome.MakeEditorSection("Edge-TTS", new StackPanel { - Spacing = 6, + Spacing = 8, Children = { - MakeRow(UiUtil.MakeLabel(l.EdgeTtsRate), l.EdgeTtsRateDescription, - UiUtil.MakeTextBox(110, vm, nameof(vm.EdgeTtsRate))), + MakeRow(MakeLabel(l.EdgeTtsRate), l.EdgeTtsRateDescription, + MakeTextBox(UiUtil.MakeTextBox(110, vm, nameof(vm.EdgeTtsRate)))), - MakeRow(UiUtil.MakeLabel(l.EdgeTtsPitch), l.EdgeTtsPitchDescription, - UiUtil.MakeTextBox(110, vm, nameof(vm.EdgeTtsPitch))), + MakeRow(MakeLabel(l.EdgeTtsPitch), l.EdgeTtsPitchDescription, + MakeTextBox(UiUtil.MakeTextBox(110, vm, nameof(vm.EdgeTtsPitch)))), - MakeRow(UiUtil.MakeLabel(l.EdgeTtsVolume), l.EdgeTtsVolumeDescription, - UiUtil.MakeTextBox(110, vm, nameof(vm.EdgeTtsVolume))), + MakeRow(MakeLabel(l.EdgeTtsVolume), l.EdgeTtsVolumeDescription, + MakeTextBox(UiUtil.MakeTextBox(110, vm, nameof(vm.EdgeTtsVolume)))), } }); groupEdgeTts[!IsVisibleProperty] = new Binding(nameof(vm.IsEdgeTtsEngine)) { Mode = BindingMode.OneWay }; var content = new StackPanel { - Spacing = 10, Children = { groupAudio, groupVoiceCloning, groupOutput, groupFolder, groupEdgeTts }, }; @@ -135,62 +143,119 @@ public AdvancedTtsSettingsWindow(AdvancedTtsSettingsViewModel vm) VerticalScrollBarVisibility = ScrollBarVisibility.Auto, }; - var buttonOk = UiUtil.MakeButtonOk(vm.OkCommand); - var buttonCancel = UiUtil.MakeButtonCancel(vm.CancelCommand); - var panelButtons = UiUtil.MakeButtonBar(buttonOk, buttonCancel); - var grid = new Grid { - Margin = UiUtil.MakeWindowMargin(), - RowSpacing = 10, RowDefinitions = { new RowDefinition { Height = new GridLength(1, GridUnitType.Star) }, new RowDefinition { Height = GridLength.Auto }, }, }; - grid.Add(scrollViewer, 0); - grid.Add(panelButtons, 1); + grid.Add(new Border { Background = EditorChrome.PanelBrush, Child = scrollViewer }, 0); + grid.Add(MakeButtonBar(vm), 1); Content = grid; // initial focus on an input, not an action button - a focused button clicks on bare Space - UiUtil.FocusOnFirstActivation(this, () => checkBoxProAudio.Focus()); + UiUtil.FocusOnFirstActivation(this, () => toggleProAudio.Focus()); } - private static CheckBox MakeCheckBox(string title, string binding) + /// + /// Compact combo boxes. + /// + private void AddStyles() { - return new CheckBox + Styles.Add(new Style(x => x.OfType()) { - // A TextBlock (not a bare string) so long translations wrap instead of widening the row. - Content = new TextBlock { Text = title, TextWrapping = TextWrapping.Wrap }, + Setters = + { + new Setter(TemplatedControl.FontSizeProperty, 12.5), + new Setter(TemplatedControl.MinHeightProperty, 28.0), + new Setter(TemplatedControl.PaddingProperty, new Thickness(8, 2, 4, 2)), + new Setter(TemplatedControl.CornerRadiusProperty, new CornerRadius(6)), + }, + }); + } + + /// + /// An on/off option: a compact switch with its title beside it. The title wraps instead of + /// widening the row for long translations. + /// + private static Grid MakeSwitch(string title, string binding, out ToggleSwitch toggle) + { + toggle = new ToggleSwitch + { + OnContent = null, + OffContent = null, + MinWidth = 0, VerticalAlignment = VerticalAlignment.Center, - [!CheckBox.IsCheckedProperty] = new Binding(binding) { Mode = BindingMode.TwoWay }, + [!ToggleSwitch.IsCheckedProperty] = new Binding(binding) { Mode = BindingMode.TwoWay }, + }; + AutomationProperties.SetName(toggle, title); + + var row = new Grid + { + ColumnSpacing = 8, + ColumnDefinitions = { new ColumnDefinition(GridLength.Auto), new ColumnDefinition(GridLength.Auto) }, }; + row.Add(toggle, 0, 0); + row.Add(new TextBlock { Text = title, FontSize = 12.5, VerticalAlignment = VerticalAlignment.Center, TextWrapping = TextWrapping.Wrap }, 0, 1); + return row; } - private static Label MakeStatusLabel(string statusBinding) + private static Label MakeLabel(string text) { - return new Label + var label = UiUtil.MakeLabel(text); + label.FontSize = 12.5; + label.Padding = new Thickness(0); + label.VerticalAlignment = VerticalAlignment.Center; + return label; + } + + private static NumericUpDown MakeNumber(NumericUpDown field, string? name) + { + field.Classes.Add("field"); + if (name != null) { - Opacity = 0.6, + AutomationProperties.SetName(field, name); + } + + return field; + } + + private static TextBox MakeTextBox(TextBox textBox) + { + textBox.FontSize = 12.5; + textBox.MinHeight = 28; + textBox.Background = EditorChrome.FieldBrush; + textBox.BorderBrush = EditorChrome.FieldBorderBrush; + textBox.CornerRadius = new CornerRadius(6); + textBox.VerticalContentAlignment = VerticalAlignment.Center; + return textBox; + } + + private static TextBlock MakeStatusLabel(string statusBinding) + { + return new TextBlock + { + FontSize = 12, + Foreground = EditorChrome.DimTextBrush, FontStyle = FontStyle.Italic, VerticalAlignment = VerticalAlignment.Center, - Padding = new Thickness(0), - [!ContentControl.ContentProperty] = new Binding(statusBinding) { Mode = BindingMode.OneWay }, + [!TextBlock.TextProperty] = new Binding(statusBinding) { Mode = BindingMode.OneWay }, }; } /// - /// One option: its label/check box plus hint icon on the left, its input right-aligned so the + /// One option: its label/switch plus hint icon on the left, its input right-aligned so the /// inputs of a group line up. /// - private static Grid MakeRow(Control label, string hint, Control? input = null, Control? status = null) + private static Grid MakeRow(Control label, string hint, Control? input = null, Control? status = null, Control? describes = null) { var leftPanel = new StackPanel { Orientation = Orientation.Horizontal, - Spacing = 5, + Spacing = 6, VerticalAlignment = VerticalAlignment.Center, Children = { label }, }; @@ -200,13 +265,14 @@ private static Grid MakeRow(Control label, string hint, Control? input = null, C leftPanel.Children.Add(status); } - // Screen readers get the hint as help text on the control it belongs to: the check box when + // Screen readers get the hint as help text on the control it belongs to: the switch when // the option is a toggle, otherwise the input the label names. - leftPanel.Children.Add(UiUtil.MakeHintIcon(hint, label is CheckBox ? label : input ?? label)); + leftPanel.Children.Add(UiUtil.MakeHintIcon(hint, describes ?? input ?? label)); var grid = new Grid { ColumnSpacing = 10, + MinHeight = 28, ColumnDefinitions = { new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) }, @@ -218,6 +284,7 @@ private static Grid MakeRow(Control label, string hint, Control? input = null, C if (input != null) { input.HorizontalAlignment = HorizontalAlignment.Right; + input.VerticalAlignment = VerticalAlignment.Center; grid.Add(input, 0, 1); // The label sits inside leftPanel, not beside the input in the grid, so link it by hand (#12087). if (label is Label && !AccessibleLabels.HasAccessibleName(input)) @@ -237,11 +304,18 @@ private static Control MakeField(string label, Control input, string isEnabledBi var panel = new StackPanel { Orientation = Orientation.Horizontal, - Spacing = 5, + Spacing = 6, VerticalAlignment = VerticalAlignment.Center, Children = { - new Label { Content = label, VerticalAlignment = VerticalAlignment.Center, Padding = new Thickness(0) }, + new Label + { + Content = label, + FontSize = 12, + Foreground = EditorChrome.DimTextBrush, + VerticalAlignment = VerticalAlignment.Center, + Padding = new Thickness(0), + }, input, }, [!IsEnabledProperty] = new Binding(isEnabledBinding) { Mode = BindingMode.OneWay }, @@ -254,24 +328,24 @@ private static Control MakeField(string label, Control input, string isEnabledBi /// Where the per-line clips are written during a run, and whether they are swept when the /// window closes. Before #13332 they went loose into the system temp folder and stayed there. ///
- private static Control MakeGenerationFolderContent(AdvancedTtsSettingsViewModel vm, CheckBox checkBoxDeleteTempFiles) + private static Control MakeGenerationFolderContent(AdvancedTtsSettingsViewModel vm, Control switchDeleteTempFiles, ToggleSwitch toggleDeleteTempFiles) { var l = Se.Language.Video.TextToSpeech; - var textBoxFolder = new TextBox + var textBoxFolder = MakeTextBox(new TextBox { VerticalAlignment = VerticalAlignment.Center, HorizontalAlignment = HorizontalAlignment.Stretch, PlaceholderText = Path.GetTempPath(), [!TextBox.TextProperty] = new Binding(nameof(vm.GenerationFolder)) { Mode = BindingMode.TwoWay, UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged }, - }; + }); AutomationProperties.SetName(textBoxFolder, l.GenerationFolder); - var buttonBrowse = UiUtil.MakeButtonBrowse(vm.BrowseGenerationFolderCommand, accessibleName: l.GenerationFolder); + var buttonBrowse = EditorChrome.CreateToolButton(IconNames.FolderOpen, l.GenerationFolder, vm.BrowseGenerationFolderCommand, 28); var folderRow = new Grid { - ColumnSpacing = 5, + ColumnSpacing = 6, ColumnDefinitions = { new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) }, @@ -285,28 +359,35 @@ private static Control MakeGenerationFolderContent(AdvancedTtsSettingsViewModel return new StackPanel { - Spacing = 6, + Spacing = 8, Children = { folderRow, - MakeRow(checkBoxDeleteTempFiles, l.DeleteTempFilesDescription), + MakeRow(switchDeleteTempFiles, l.DeleteTempFilesDescription, describes: toggleDeleteTempFiles), } }; } - private static Border MakeGroupBox(string title, Control content) + private static Border MakeButtonBar(AdvancedTtsSettingsViewModel vm) { - var header = new TextBlock - { - Text = title, - FontWeight = FontWeight.SemiBold, - Margin = new Thickness(0, 0, 0, 6), - }; + var buttonOk = UiUtil.MakeButtonOk(vm.OkCommand); + buttonOk.Classes.Add("primary"); + var buttonCancel = UiUtil.MakeButtonCancel(vm.CancelCommand); - return UiUtil.MakeBorderForControl(new StackPanel + return new Border { - Children = { header, content }, - }); + Child = new StackPanel + { + Orientation = Orientation.Horizontal, + HorizontalAlignment = HorizontalAlignment.Right, + Spacing = 10, + Children = { buttonCancel, buttonOk }, + }, + Background = EditorChrome.ButtonBarBrush, + BorderBrush = EditorChrome.LineBrush, + BorderThickness = new Thickness(0, 1, 0, 0), + Padding = new Thickness(16, 10), + }; } protected override void OnKeyDown(KeyEventArgs e) diff --git a/src/ui/Features/Video/TextToSpeech/ChatterboxTtsSettings/ChatterboxTtsSettingsWindow.cs b/src/ui/Features/Video/TextToSpeech/ChatterboxTtsSettings/ChatterboxTtsSettingsWindow.cs index fc4731540c7..c990b50a0d4 100644 --- a/src/ui/Features/Video/TextToSpeech/ChatterboxTtsSettings/ChatterboxTtsSettingsWindow.cs +++ b/src/ui/Features/Video/TextToSpeech/ChatterboxTtsSettings/ChatterboxTtsSettingsWindow.cs @@ -1,21 +1,29 @@ using Avalonia; +using Avalonia.Automation; using Avalonia.Controls; +using Avalonia.Controls.Primitives; using Avalonia.Controls.Shapes; using Avalonia.Controls.Templates; using Avalonia.Data; using Avalonia.Input; using Avalonia.Layout; using Avalonia.Media; +using Avalonia.Styling; using Nikse.SubtitleEdit.Features.Video.TextToSpeech.Engines; using Nikse.SubtitleEdit.Logic; using Nikse.SubtitleEdit.Logic.Config; namespace Nikse.SubtitleEdit.Features.Video.TextToSpeech.ChatterboxTtsSettings; +/// +/// Chatterbox engine and model status, in the editor look (see ): engine +/// name and description on top, then engine, models and voice cloning sections, Close in the +/// button bar. +/// public class ChatterboxTtsSettingsWindow : Window { - private const int LabelWidth = 130; - private const int ValueWidth = 360; + private const double LabelWidth = 120; + private const double ValueWidth = 340; private readonly ChatterboxTtsSettingsViewModel _vm; @@ -25,144 +33,106 @@ public ChatterboxTtsSettingsWindow(ChatterboxTtsSettingsViewModel vm) Title = Se.Language.Video.TextToSpeech.ChatterboxTtsSettings; SizeToContent = SizeToContent.WidthAndHeight; CanResize = false; - MinWidth = 580; + MinWidth = 560; + Background = EditorChrome.WindowBrush; + EditorChrome.AddEditorStyles(Styles); + AddStyles(); _vm = vm; vm.Window = this; DataContext = vm; - Content = BuildContent(vm); + var sections = new StackPanel { Background = EditorChrome.PanelBrush }; + sections.Children.Add(MakeHeader("Chatterbox TTS", new ChatterboxTtsCpp().Description)); + sections.Children.Add(MakeEngineSection(vm)); + sections.Children.Add(EditorChrome.MakeEditorSection(Se.Language.General.Models, MakeModelList(vm))); + sections.Children.Add(MakeVoiceCloningSection(vm, out var sourceLanguageCombo)); - var ok = UiUtil.MakeButtonOk(vm.OkCommand); - UiUtil.FocusOnFirstActivation(this, ok); + var root = new DockPanel(); + var buttonBar = MakeButtonBar(vm); + DockPanel.SetDock(buttonBar, Dock.Bottom); + root.Children.Add(buttonBar); + root.Children.Add(sections); + Content = root; + + // initial focus on an input, not an action button - a focused button clicks on bare Space + UiUtil.FocusOnFirstActivation(this, sourceLanguageCombo); } - private Border BuildContent(ChatterboxTtsSettingsViewModel vm) + /// + /// Compact combo boxes. + /// + private void AddStyles() { - var header = BuildHeader(); - var details = BuildDetails(vm); - var actions = BuildActions(vm); - - var stack = new StackPanel - { - Orientation = Orientation.Vertical, - Spacing = 14, - Children = { header, details, actions }, - }; - - var outerGrid = new Grid - { - Margin = UiUtil.MakeWindowMargin(), - }; - outerGrid.Children.Add(stack); - - return new Border + Styles.Add(new Style(x => x.OfType()) { - Child = outerGrid, - Padding = new Thickness(4), - }; + Setters = + { + new Setter(TemplatedControl.FontSizeProperty, 12.5), + new Setter(TemplatedControl.MinHeightProperty, 28.0), + new Setter(TemplatedControl.PaddingProperty, new Thickness(8, 2, 4, 2)), + new Setter(TemplatedControl.CornerRadiusProperty, new CornerRadius(6)), + }, + }); } - private static StackPanel BuildHeader() + private static Border MakeHeader(string title, string description) { - var title = new TextBlock + var panel = new StackPanel { Spacing = 2 }; + panel.Children.Add(new TextBlock { - Text = "Chatterbox TTS", - FontSize = UiUtil.ScaledFontSize(18), + Text = title, + FontSize = 16, FontWeight = FontWeight.SemiBold, - }; - - var subtitle = new TextBlock + }); + panel.Children.Add(new TextBlock { - Text = new ChatterboxTtsCpp().Description, - FontSize = UiUtil.ScaledFontSize(12), - Opacity = 0.75, - Margin = new Thickness(0, 2, 0, 0), - }; + Text = description, + FontSize = 12, + Foreground = EditorChrome.DimTextBrush, + TextWrapping = TextWrapping.Wrap, + MaxWidth = 500, + HorizontalAlignment = HorizontalAlignment.Left, + }); - return new StackPanel - { - Orientation = Orientation.Vertical, - Spacing = 0, - Children = { title, subtitle }, - }; + return new Border { Child = panel, Padding = new Thickness(14, 14, 14, 12) }; } - private static Border BuildDetails(ChatterboxTtsSettingsViewModel vm) + private static Border MakeEngineSection(ChatterboxTtsSettingsViewModel vm) { - var grid = new Grid - { - ColumnDefinitions = - { - new ColumnDefinition { Width = new GridLength(LabelWidth, GridUnitType.Pixel) }, - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) }, - }, - RowDefinitions = - { - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - }, - ColumnSpacing = 12, - RowSpacing = 10, - }; + var rows = new StackPanel { Spacing = 8 }; - grid.Add(MakeLabel(Se.Language.General.Engine), 0, 0); var enginePanel = MakeStatusPanel(nameof(vm.EngineBrush), nameof(vm.EngineLabel)); var engineButton = UiUtil.MakeButton(string.Empty, vm.RedownloadEngineCommand) - .WithIconLeftBindText(IconNames.Download, nameof(vm.EngineDownloadButtonText)) - .WithMarginLeft(12); + .WithIconLeftBindText(IconNames.Download, nameof(vm.EngineDownloadButtonText)); enginePanel.Children.Add(engineButton); - grid.Add(enginePanel, 0, 1); - - grid.Add(MakeLabel(Se.Language.General.Models), 1, 0); - grid.Add(MakeModelList(vm), 1, 1); + rows.Children.Add(Row(Se.Language.General.Status, enginePanel)); - grid.Add(MakeLabel(Se.Language.General.InstallFolder), 2, 0); var folderText = new TextBox { IsReadOnly = true, Width = ValueWidth, - BorderThickness = new Thickness(0), - Background = Brushes.Transparent, - Padding = new Thickness(0), + Background = EditorChrome.FieldBrush, + BorderBrush = EditorChrome.FieldBorderBrush, + CornerRadius = new CornerRadius(6), + Padding = new Thickness(8, 0), + MinHeight = 28, + Height = 28, + VerticalAlignment = VerticalAlignment.Center, VerticalContentAlignment = VerticalAlignment.Center, - FontSize = UiUtil.ScaledFontSize(12), + FontSize = 12, [!TextBox.TextProperty] = new Binding(nameof(vm.ModelsFolder)), }; - // Row 2, beside its own label - every sibling settings window pairs (N,0) with (N,1). - // At row 3 the "Install folder" caption sat alone and the path appeared a row lower. - grid.Add(folderText, 2, 1); - - // Language spoken in imported reference WAVs — sent as `source_lang` so cross-lingual - // cloning engages when the target language differs. Chatterbox clones from the WAV alone - // and never asks for a transcript, so there is normally nothing to detect this from. - grid.Add(MakeLabel("Reference language"), 4, 0); - var sourceLanguageCombo = UiUtil.MakeComboBox(vm.SourceLanguages, vm, nameof(vm.SelectedSourceLanguage)); - var sourceLanguageHint = new TextBlock - { - Text = "Language spoken in imported reference WAVs (for cross-lingual cloning).", - FontSize = UiUtil.ScaledFontSize(12), - Opacity = 0.75, - VerticalAlignment = VerticalAlignment.Center, - Margin = new Thickness(8, 0, 0, 0), - }; - grid.Add(new StackPanel + AutomationProperties.SetName(folderText, Se.Language.General.InstallFolder); + var openFolder = EditorChrome.CreateToolButton(IconNames.FolderOpen, Se.Language.General.OpenContainingFolder, vm.OpenModelsFolderCommand, 28); + rows.Children.Add(Row(Se.Language.General.InstallFolder, new StackPanel { Orientation = Orientation.Horizontal, - Children = { sourceLanguageCombo, sourceLanguageHint }, - }, 4, 1); + Spacing = 6, + Children = { folderText, openFolder }, + })); - return new Border - { - Child = grid, - Padding = new Thickness(14), - CornerRadius = new CornerRadius(6), - BorderThickness = new Thickness(1), - BorderBrush = new SolidColorBrush(Color.FromArgb(0x40, 0x80, 0x80, 0x80)), - }; + return EditorChrome.MakeEditorSection(Se.Language.General.Engine, rows); } /// @@ -179,14 +149,16 @@ private static ItemsControl MakeModelList(ChatterboxTtsSettingsViewModel vm) { var name = new TextBlock { - Width = 150, + Width = LabelWidth + 30, + FontSize = 12.5, VerticalAlignment = VerticalAlignment.Center, + TextTrimming = TextTrimming.CharacterEllipsis, [!TextBlock.TextProperty] = new Binding(nameof(ChatterboxModelStatusViewModel.DisplayName)), }; var status = MakeStatusPanel( nameof(ChatterboxModelStatusViewModel.StatusBrush), nameof(ChatterboxModelStatusViewModel.StatusLabel)); - status.Width = 110; + status.Width = 120; var button = UiUtil.MakeButton(string.Empty) .WithIconLeftBindText(IconNames.Download, nameof(ChatterboxModelStatusViewModel.DownloadButtonText), @@ -205,70 +177,93 @@ private static ItemsControl MakeModelList(ChatterboxTtsSettingsViewModel vm) }; } + private static Border MakeVoiceCloningSection(ChatterboxTtsSettingsViewModel vm, out ComboBox sourceLanguageCombo) + { + // Language spoken in imported reference WAVs — sent as `source_lang` so cross-lingual + // cloning engages when the target language differs. Chatterbox clones from the WAV alone + // and never asks for a transcript, so there is normally nothing to detect this from. + sourceLanguageCombo = UiUtil.MakeComboBox(vm.SourceLanguages, vm, nameof(vm.SelectedSourceLanguage)); + sourceLanguageCombo.MinWidth = 160; + AutomationProperties.SetName(sourceLanguageCombo, "Reference language"); + var sourceLanguageHint = new TextBlock + { + Text = "Language spoken in imported reference WAVs (for cross-lingual cloning).", + FontSize = 11.5, + Foreground = EditorChrome.DimTextBrush, + TextWrapping = TextWrapping.Wrap, + MaxWidth = ValueWidth, + HorizontalAlignment = HorizontalAlignment.Left, + }; + + var rows = new StackPanel { Spacing = 6 }; + rows.Children.Add(Row("Reference language", sourceLanguageCombo)); + rows.Children.Add(Row(string.Empty, sourceLanguageHint)); + return EditorChrome.MakeEditorSection(Se.Language.Video.TextToSpeech.AdvancedTtsVoiceCloning, rows); + } + + /// + /// A dim label of fixed width and the content after it, so the rows line up. + /// + private static Grid Row(string label, Control content) + { + var text = EditorChrome.MakeDimLabel(label, new Thickness(0)); + text.TextTrimming = TextTrimming.CharacterEllipsis; + var grid = new Grid + { + ColumnSpacing = 10, + ColumnDefinitions = { new ColumnDefinition(new GridLength(LabelWidth)), new ColumnDefinition(new GridLength(1, GridUnitType.Star)) }, + }; + grid.Add(text, 0, 0); + grid.Add(content, 0, 1); + return grid; + } + private static StackPanel MakeStatusPanel(string brushBindingPath, string labelBindingPath) { var dot = new Ellipse { - Width = 10, - Height = 10, + Width = 9, + Height = 9, VerticalAlignment = VerticalAlignment.Center, [!Ellipse.FillProperty] = new Binding(brushBindingPath), }; var text = new TextBlock { - VerticalAlignment = VerticalAlignment.Center, + FontSize = 12.5, FontWeight = FontWeight.SemiBold, - Margin = new Thickness(8, 0, 0, 0), + VerticalAlignment = VerticalAlignment.Center, + Margin = new Thickness(0, 0, 6, 0), [!TextBlock.TextProperty] = new Binding(labelBindingPath), }; return new StackPanel { Orientation = Orientation.Horizontal, + Spacing = 8, Children = { dot, text }, }; } - private static Grid BuildActions(ChatterboxTtsSettingsViewModel vm) + private static Border MakeButtonBar(ChatterboxTtsSettingsViewModel vm) { - var openFolder = UiUtil.MakeButton(Se.Language.General.OpenContainingFolder, vm.OpenModelsFolderCommand).WithIconLeft(IconNames.FolderOpen); var close = UiUtil.MakeButton(Se.Language.General.Close, vm.OkCommand); + close.Classes.Add("primary"); - var leftPanel = new StackPanel - { - Orientation = Orientation.Horizontal, - Spacing = 8, - Children = { openFolder }, - }; - - var rightPanel = new StackPanel - { - Orientation = Orientation.Horizontal, - HorizontalAlignment = HorizontalAlignment.Right, - Spacing = 8, - Children = { close }, - }; - - var grid = new Grid + return new Border { - ColumnDefinitions = + Child = new StackPanel { - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Auto) }, - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) }, - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Auto) }, + Orientation = Orientation.Horizontal, + HorizontalAlignment = HorizontalAlignment.Right, + Spacing = 10, + Children = { close }, }, + Background = EditorChrome.ButtonBarBrush, + BorderBrush = EditorChrome.LineBrush, + BorderThickness = new Thickness(0, 1, 0, 0), + Padding = new Thickness(16, 10), }; - grid.Add(leftPanel, 0, 0); - grid.Add(rightPanel, 0, 2); - return grid; } - private static TextBlock MakeLabel(string text) => new() - { - Text = text, - Opacity = 0.7, - VerticalAlignment = VerticalAlignment.Center, - }; - protected override void OnKeyDown(KeyEventArgs e) { base.OnKeyDown(e); diff --git a/src/ui/Features/Video/TextToSpeech/ElevenLabsSettings/ElevenLabsSettingsWindow.cs b/src/ui/Features/Video/TextToSpeech/ElevenLabsSettings/ElevenLabsSettingsWindow.cs index 634d2c0e9dc..2cab8e509be 100644 --- a/src/ui/Features/Video/TextToSpeech/ElevenLabsSettings/ElevenLabsSettingsWindow.cs +++ b/src/ui/Features/Video/TextToSpeech/ElevenLabsSettings/ElevenLabsSettingsWindow.cs @@ -1,16 +1,25 @@ using Avalonia; +using Avalonia.Automation; using Avalonia.Controls; using Avalonia.Data; using Avalonia.Input; using Avalonia.Layout; +using Avalonia.Media; using Nikse.SubtitleEdit.Logic; using Nikse.SubtitleEdit.Logic.Config; using Nikse.SubtitleEdit.Logic.ValueConverters; namespace Nikse.SubtitleEdit.Features.Video.TextToSpeech.ElevenLabsSettings; +/// +/// ElevenLabs voice settings, in the editor look (see ): one section with +/// a slider row per setting (value + help), more info and reset as icon buttons in its header, +/// Cancel/OK in the button bar. +/// public class ElevenLabsSettingsWindow : Window { + private const double LabelWidth = 130; + private readonly ElevenLabsSettingsViewModel _vm; public ElevenLabsSettingsWindow(ElevenLabsSettingsViewModel vm) @@ -19,137 +28,114 @@ public ElevenLabsSettingsWindow(ElevenLabsSettingsViewModel vm) Title = Se.Language.Video.TextToSpeech.ElevenLabsSettings; SizeToContent = SizeToContent.WidthAndHeight; CanResize = false; + Background = EditorChrome.WindowBrush; + EditorChrome.AddEditorStyles(Styles); _vm = vm; vm.Window = this; DataContext = vm; - var labelStability = UiUtil.MakeLabel(Se.Language.Video.TextToSpeech.Stability); - var sliderStability = new Slider - { - Minimum = 0, - Maximum = 1, - Value = vm.Stability, - Width = 200, - Margin = new Thickness(5, 0, 0, 0), - [!Slider.ValueProperty] = new Binding(nameof(ElevenLabsSettingsViewModel.Stability)), - }; - var labelStabilityValue = UiUtil.MakeLabel().WithBindText(vm, nameof(ElevenLabsSettingsViewModel.Stability), new DoubleToTwoDecimalConverter()); - var buttonStability = UiUtil.MakeButton(vm.ShowStabilityHelpCommand, IconNames.Help, $"{Se.Language.Video.TextToSpeech.Stability} - {Se.Language.General.Help}"); + var l = Se.Language.Video.TextToSpeech; + var sliderStability = MakeSlider(0, 1, vm.Stability, nameof(ElevenLabsSettingsViewModel.Stability), l.Stability); + var sliderSimilarity = MakeSlider(0, 1, vm.Similarity, nameof(ElevenLabsSettingsViewModel.Similarity), l.Similarity); + var sliderSpeakerBoost = MakeSlider(0, 100, vm.SpeakerBoost, nameof(ElevenLabsSettingsViewModel.SpeakerBoost), l.SpeakerBoost); + var sliderSpeed = MakeSlider(0.7, 1.2, vm.Speed, nameof(ElevenLabsSettingsViewModel.Speed), Se.Language.General.Speed); + var sliderStyleExaggeration = MakeSlider(0.0, 1.0, vm.StyleExaggeration, nameof(ElevenLabsSettingsViewModel.StyleExaggeration), Se.Language.General.StyleExaggeration); + + var rows = new StackPanel { Spacing = 6 }; + rows.Children.Add(SliderRow(l.Stability, sliderStability, nameof(ElevenLabsSettingsViewModel.Stability), vm.ShowStabilityHelpCommand)); + rows.Children.Add(SliderRow(l.Similarity, sliderSimilarity, nameof(ElevenLabsSettingsViewModel.Similarity), vm.ShowSimilarityHelpCommand)); + rows.Children.Add(SliderRow(l.SpeakerBoost, sliderSpeakerBoost, nameof(ElevenLabsSettingsViewModel.SpeakerBoost), vm.ShowSpeakerBoostHelpCommand)); + rows.Children.Add(SliderRow(Se.Language.General.Speed, sliderSpeed, nameof(ElevenLabsSettingsViewModel.Speed), vm.ShowSpeedHelpCommand)); + rows.Children.Add(SliderRow(Se.Language.General.StyleExaggeration, sliderStyleExaggeration, nameof(ElevenLabsSettingsViewModel.StyleExaggeration), vm.ShowStyleExaggerationHelpCommand)); + + var headerActions = new StackPanel { Orientation = Orientation.Horizontal, Spacing = 2 }; + headerActions.Children.Add(EditorChrome.CreateToolButton(IconNames.Web, Se.Language.General.MoreInfo, vm.ShowMoreOnWebCommand, 26)); + headerActions.Children.Add(EditorChrome.CreateToolButton(IconNames.Repeat, Se.Language.General.Reset, vm.ResetCommand, 26)); + + var sections = new StackPanel { Background = EditorChrome.PanelBrush }; + sections.Children.Add(EditorChrome.MakeEditorSection(Se.Language.General.Voice, rows, headerActions, first: true)); + + var root = new DockPanel(); + var buttonBar = MakeButtonBar(vm); + DockPanel.SetDock(buttonBar, Dock.Bottom); + root.Children.Add(buttonBar); + root.Children.Add(sections); + Content = root; - var labelSimilarity = UiUtil.MakeLabel(Se.Language.Video.TextToSpeech.Similarity); - var sliderSimilarity = new Slider - { - Minimum = 0, - Maximum = 1, - Value = vm.Similarity, - Width = 200, - Margin = new Thickness(5, 0, 0, 0), - [!Slider.ValueProperty] = new Binding(nameof(ElevenLabsSettingsViewModel.Similarity)), - }; - var labelSimilarityValue = UiUtil.MakeLabel().WithBindText(vm, nameof(ElevenLabsSettingsViewModel.Similarity), new DoubleToTwoDecimalConverter()); - var buttonSimilarity = UiUtil.MakeButton(vm.ShowSimilarityHelpCommand, IconNames.Help, $"{Se.Language.Video.TextToSpeech.Similarity} - {Se.Language.General.Help}"); + UiUtil.FocusOnFirstActivation(this, sliderStability); // initial focus on an input, not an action button - a focused button clicks on bare Space + } - var labelSpeakerBoost = UiUtil.MakeLabel(Se.Language.Video.TextToSpeech.SpeakerBoost); - var sliderSpeakerBoost = new Slider + private static Slider MakeSlider(double minimum, double maximum, double value, string property, string name) + { + var slider = new Slider { - Minimum = 0, - Maximum = 100, - Value = vm.SpeakerBoost, - Width = 200, - Margin = new Thickness(5, 0, 0, 0), - [!Slider.ValueProperty] = new Binding(nameof(ElevenLabsSettingsViewModel.SpeakerBoost)), + Minimum = minimum, + Maximum = maximum, + Value = value, + Width = 220, + VerticalAlignment = VerticalAlignment.Center, + [!Slider.ValueProperty] = new Binding(property), }; - var labelSpeakerBoostValue = UiUtil.MakeLabel().WithBindText(vm, nameof(ElevenLabsSettingsViewModel.SpeakerBoost), new DoubleToTwoDecimalConverter()); - var buttonSpeakerBoost = UiUtil.MakeButton(vm.ShowSpeakerBoostHelpCommand, IconNames.Help, $"{Se.Language.Video.TextToSpeech.SpeakerBoost} - {Se.Language.General.Help}"); + AutomationProperties.SetName(slider, name); + return slider; + } - var labelSpeed = UiUtil.MakeLabel(Se.Language.General.Speed); - var sliderSpeed = new Slider + /// + /// "Label [slider] value (?)" - the label of fixed width so the sliders line up. + /// + private static Grid SliderRow(string label, Slider slider, string property, System.Windows.Input.ICommand helpCommand) + { + var text = EditorChrome.MakeDimLabel(label, new Thickness(0)); + text.TextTrimming = TextTrimming.CharacterEllipsis; + var value = new TextBlock { - Minimum = 0.7, - Maximum = 1.2, - Value = vm.Speed, - Width = 200, - Margin = new Thickness(5, 0, 0, 0), - [!Slider.ValueProperty] = new Binding(nameof(ElevenLabsSettingsViewModel.Speed)), + FontSize = 12.5, + MinWidth = 40, + TextAlignment = TextAlignment.Right, + VerticalAlignment = VerticalAlignment.Center, + [!TextBlock.TextProperty] = new Binding(property) { Converter = new DoubleToTwoDecimalConverter() }, }; - var labelSpeedValue = UiUtil.MakeLabel().WithBindText(vm, nameof(ElevenLabsSettingsViewModel.Speed), new DoubleToTwoDecimalConverter()); - var buttonSpeed = UiUtil.MakeButton(vm.ShowSpeedHelpCommand, IconNames.Help, $"{Se.Language.General.Speed} - {Se.Language.General.Help}"); + var help = EditorChrome.CreateToolButton(IconNames.Help, $"{label} - {Se.Language.General.Help}", helpCommand, 26); - var labelStyleExaggeration = UiUtil.MakeLabel(Se.Language.General.StyleExaggeration); - var sliderStyleExaggeration = new Slider + var grid = new Grid { - Minimum = 0.0, - Maximum = 1.0, - Value = vm.StyleExaggeration, - Width = 200, - Margin = new Thickness(5, 0, 0, 0), - [!Slider.ValueProperty] = new Binding(nameof(ElevenLabsSettingsViewModel.StyleExaggeration)), + ColumnSpacing = 10, + ColumnDefinitions = + { + new ColumnDefinition(new GridLength(LabelWidth)), + new ColumnDefinition(GridLength.Auto), + new ColumnDefinition(GridLength.Auto), + new ColumnDefinition(GridLength.Auto), + }, }; - var labelStyleExaggerationValue = UiUtil.MakeLabel().WithBindText(vm, nameof(ElevenLabsSettingsViewModel.StyleExaggeration), new DoubleToTwoDecimalConverter()); - var buttonStyleExaggeration = UiUtil.MakeButton(vm.ShowStyleExaggerationHelpCommand, IconNames.Help, $"{Se.Language.General.StyleExaggeration} - {Se.Language.General.Help}"); + grid.Add(text, 0, 0); + grid.Add(slider, 0, 1); + grid.Add(value, 0, 2); + grid.Add(help, 0, 3); + return grid; + } - var buttonWeb = UiUtil.MakeButton(Se.Language.General.MoreInfo, vm.ShowMoreOnWebCommand).WithIconLeft(IconNames.Web); - var buttonReset = UiUtil.MakeButton(Se.Language.General.Reset, vm.ResetCommand).WithIconLeft(IconNames.Repeat); + private static Border MakeButtonBar(ElevenLabsSettingsViewModel vm) + { var buttonOk = UiUtil.MakeButtonOk(vm.OkCommand); + buttonOk.Classes.Add("primary"); var buttonCancel = UiUtil.MakeButtonCancel(vm.CancelCommand); - var panelButtons = UiUtil.MakeButtonBar(buttonWeb, buttonReset, buttonOk, buttonCancel); - var grid = new Grid + return new Border { - RowDefinitions = + Child = new StackPanel { - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, + Orientation = Orientation.Horizontal, + HorizontalAlignment = HorizontalAlignment.Right, + Spacing = 10, + Children = { buttonCancel, buttonOk }, }, - ColumnDefinitions = - { - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Auto) }, - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Auto) }, - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Auto) }, - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Auto) }, - }, - Margin = UiUtil.MakeWindowMargin(), - ColumnSpacing = 5, - RowSpacing = 10, - Width = double.NaN, - HorizontalAlignment = HorizontalAlignment.Stretch, + Background = EditorChrome.ButtonBarBrush, + BorderBrush = EditorChrome.LineBrush, + BorderThickness = new Thickness(0, 1, 0, 0), + Padding = new Thickness(16, 10), }; - - grid.Add(labelStability, 0, 0); - grid.Add(sliderStability, 0, 1); - grid.Add(labelStabilityValue, 0, 2); - grid.Add(buttonStability, 0, 3); - - grid.Add(labelSimilarity, 1, 0); - grid.Add(sliderSimilarity, 1, 1); - grid.Add(labelSimilarityValue, 1, 2); - grid.Add(buttonSimilarity, 1, 3); - - grid.Add(labelSpeakerBoost, 2, 0); - grid.Add(sliderSpeakerBoost, 2, 1); - grid.Add(labelSpeakerBoostValue, 2, 2); - grid.Add(buttonSpeakerBoost, 2, 3); - - grid.Add(labelSpeed, 3, 0); - grid.Add(sliderSpeed, 3, 1); - grid.Add(labelSpeedValue, 3, 2); - grid.Add(buttonSpeed, 3, 3); - - grid.Add(labelStyleExaggeration, 4, 0); - grid.Add(sliderStyleExaggeration, 4, 1); - grid.Add(labelStyleExaggerationValue, 4, 2); - grid.Add(buttonStyleExaggeration, 4, 3); - - grid.Add(panelButtons, 5, 0, 1, 3); - - Content = grid; - - UiUtil.FocusOnFirstActivation(this, sliderStability); // initial focus on an input, not an action button - a focused button clicks on bare Space } protected override void OnKeyDown(KeyEventArgs e) @@ -157,4 +143,4 @@ protected override void OnKeyDown(KeyEventArgs e) base.OnKeyDown(e); _vm.OnKeyDown(e); } -} \ No newline at end of file +} diff --git a/src/ui/Features/Video/TextToSpeech/EncodingSettings/EncodingSettingsWindow.cs b/src/ui/Features/Video/TextToSpeech/EncodingSettings/EncodingSettingsWindow.cs index a8a52774088..47998832913 100644 --- a/src/ui/Features/Video/TextToSpeech/EncodingSettings/EncodingSettingsWindow.cs +++ b/src/ui/Features/Video/TextToSpeech/EncodingSettings/EncodingSettingsWindow.cs @@ -1,12 +1,20 @@ +using Avalonia; +using Avalonia.Automation; using Avalonia.Controls; +using Avalonia.Controls.Primitives; using Avalonia.Data; using Avalonia.Input; using Avalonia.Layout; +using Avalonia.Styling; using Nikse.SubtitleEdit.Logic; using Nikse.SubtitleEdit.Logic.Config; namespace Nikse.SubtitleEdit.Features.Video.TextToSpeech.EncodingSettings; +/// +/// The audio encoding of the TTS video, in the editor look (see ): one +/// section with the encoding and a stereo switch, Cancel/OK in the button bar. +/// public class EncodingSettingsWindow : Window { private readonly EncodingSettingsViewModel _vm; @@ -17,67 +25,103 @@ public EncodingSettingsWindow(EncodingSettingsViewModel vm) Title = Se.Language.Video.TextToSpeech.VideoEncodingSettings; SizeToContent = SizeToContent.WidthAndHeight; CanResize = false; + MinWidth = 340; + Background = EditorChrome.WindowBrush; + EditorChrome.AddEditorStyles(Styles); + AddStyles(); _vm = vm; vm.Window = this; DataContext = vm; - var label = new Label - { - Content = Se.Language.General.Encoding, - VerticalAlignment = VerticalAlignment.Center, - }; - var comboEncoding = new ComboBox { ItemsSource = vm.Encodings, VerticalAlignment = VerticalAlignment.Center, + HorizontalAlignment = HorizontalAlignment.Stretch, MinWidth = 150, }.WithBindSelected(nameof(vm.SelectedEncoding)); + AutomationProperties.SetName(comboEncoding, Se.Language.General.Encoding); - var checkBoxStereo = new CheckBox + var switchStereo = new ToggleSwitch { - Content = Se.Language.General.Stereo, + OnContent = null, + OffContent = null, + MinWidth = 0, IsChecked = vm.IsStereo, VerticalAlignment = VerticalAlignment.Center, - [!CheckBox.IsCheckedProperty] = new Binding(nameof(vm.IsStereo)) { Mode = BindingMode.TwoWay, UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged }, - [!CheckBox.IsEnabledProperty] = new Binding(nameof(vm.SelectedEncoding) + "." + nameof(EncodingDisplayItem.IsStereoEnabled)) { Mode = BindingMode.OneWay, UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged }, + [!ToggleSwitch.IsCheckedProperty] = new Binding(nameof(vm.IsStereo)) { Mode = BindingMode.TwoWay, UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged }, }; - - var buttonOk = UiUtil.MakeButtonOk(vm.OkCommand); - var buttonCancel = UiUtil.MakeButtonCancel(vm.CancelCommand); - var panelButtons = UiUtil.MakeButtonBar(buttonOk, buttonCancel); - - var grid = new Grid + AutomationProperties.SetName(switchStereo, Se.Language.General.Stereo); + var stereoRow = new StackPanel { - RowDefinitions = - { - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Star) }, - }, - ColumnDefinitions = + Orientation = Orientation.Horizontal, + Spacing = 8, + Children = { - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Auto) }, - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) }, + switchStereo, + new TextBlock { Text = Se.Language.General.Stereo, FontSize = 12.5, VerticalAlignment = VerticalAlignment.Center }, }, - Margin = UiUtil.MakeWindowMargin(), - ColumnSpacing = 10, - RowSpacing = 10, - Width = double.NaN, - HorizontalAlignment = HorizontalAlignment.Stretch, + // Not every encoding can do stereo - the row greys out with its switch + [!IsEnabledProperty] = new Binding(nameof(vm.SelectedEncoding) + "." + nameof(EncodingDisplayItem.IsStereoEnabled)) { Mode = BindingMode.OneWay, UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged }, }; - grid.Add(label, 0, 0); - grid.Add(comboEncoding, 0, 1); - grid.Add(checkBoxStereo, 1, 1); - grid.Add(panelButtons, 2, 0, 1, 2); + var rows = new StackPanel { Spacing = 10 }; + rows.Children.Add(comboEncoding); + rows.Children.Add(stereoRow); - Content = grid; + var sections = new StackPanel { Background = EditorChrome.PanelBrush }; + sections.Children.Add(EditorChrome.MakeEditorSection(Se.Language.General.Encoding, rows, first: true)); + + var root = new DockPanel(); + var buttonBar = MakeButtonBar(vm); + DockPanel.SetDock(buttonBar, Dock.Bottom); + root.Children.Add(buttonBar); + root.Children.Add(sections); + Content = root; UiUtil.FocusOnFirstActivation(this, comboEncoding); // initial focus on an input, not an action button - a focused button clicks on bare Space } + /// + /// Compact combo boxes. + /// + private void AddStyles() + { + Styles.Add(new Style(x => x.OfType()) + { + Setters = + { + new Setter(TemplatedControl.FontSizeProperty, 12.5), + new Setter(TemplatedControl.MinHeightProperty, 28.0), + new Setter(TemplatedControl.PaddingProperty, new Thickness(8, 2, 4, 2)), + new Setter(TemplatedControl.CornerRadiusProperty, new CornerRadius(6)), + }, + }); + } + + private static Border MakeButtonBar(EncodingSettingsViewModel vm) + { + var buttonOk = UiUtil.MakeButtonOk(vm.OkCommand); + buttonOk.Classes.Add("primary"); + var buttonCancel = UiUtil.MakeButtonCancel(vm.CancelCommand); + + return new Border + { + Child = new StackPanel + { + Orientation = Orientation.Horizontal, + HorizontalAlignment = HorizontalAlignment.Right, + Spacing = 10, + Children = { buttonCancel, buttonOk }, + }, + Background = EditorChrome.ButtonBarBrush, + BorderBrush = EditorChrome.LineBrush, + BorderThickness = new Thickness(0, 1, 0, 0), + Padding = new Thickness(16, 10), + }; + } + protected override void OnKeyDown(KeyEventArgs e) { base.OnKeyDown(e); diff --git a/src/ui/Features/Video/TextToSpeech/KokoroTtsSettings/KokoroTtsSettingsWindow.cs b/src/ui/Features/Video/TextToSpeech/KokoroTtsSettings/KokoroTtsSettingsWindow.cs index 57f760e145f..98272e19283 100644 --- a/src/ui/Features/Video/TextToSpeech/KokoroTtsSettings/KokoroTtsSettingsWindow.cs +++ b/src/ui/Features/Video/TextToSpeech/KokoroTtsSettings/KokoroTtsSettingsWindow.cs @@ -1,4 +1,5 @@ using Avalonia; +using Avalonia.Automation; using Avalonia.Controls; using Avalonia.Controls.Shapes; using Avalonia.Data; @@ -11,10 +12,15 @@ namespace Nikse.SubtitleEdit.Features.Video.TextToSpeech.KokoroTtsSettings; +/// +/// Kokoro TTS engine status, in the editor look (see ): engine name and +/// description on top, an engine section (backend, status + download, release, install folder), +/// Close in the button bar. +/// public class KokoroTtsSettingsWindow : Window { - private const int LabelWidth = 110; - private const int ValueWidth = 360; + private const double LabelWidth = 100; + private const double ValueWidth = 340; private readonly KokoroTtsSettingsViewModel _vm; @@ -24,197 +30,179 @@ public KokoroTtsSettingsWindow(KokoroTtsSettingsViewModel vm) Title = Se.Language.Video.TextToSpeech.KokoroTtsSettings; SizeToContent = SizeToContent.WidthAndHeight; CanResize = false; - MinWidth = 540; + MinWidth = 520; + Background = EditorChrome.WindowBrush; + EditorChrome.AddEditorStyles(Styles); _vm = vm; vm.Window = this; DataContext = vm; - Content = BuildContent(vm); + var sections = new StackPanel { Background = EditorChrome.PanelBrush }; + sections.Children.Add(MakeHeader("Kokoro TTS", new KokoroTtsCpp().Description)); + sections.Children.Add(MakeEngineSection(vm, out var folderText)); - var ok = UiUtil.MakeButtonOk(vm.OkCommand); - UiUtil.FocusOnFirstActivation(this, ok); + var root = new DockPanel(); + var buttonBar = MakeButtonBar(vm); + DockPanel.SetDock(buttonBar, Dock.Bottom); + root.Children.Add(buttonBar); + root.Children.Add(sections); + Content = root; + + // initial focus on an input, not an action button - a focused button clicks on bare Space; + // a focused control also makes sure OnKeyDown fires + UiUtil.FocusOnFirstActivation(this, folderText); } - private Border BuildContent(KokoroTtsSettingsViewModel vm) + private static Border MakeHeader(string title, string description) { - var header = BuildHeader(); - var details = BuildDetails(vm); - var actions = BuildActions(vm); - - var stack = new StackPanel - { - Orientation = Orientation.Vertical, - Spacing = 14, - Children = { header, details, actions }, - }; - - var outerGrid = new Grid - { - Margin = UiUtil.MakeWindowMargin(), - }; - outerGrid.Children.Add(stack); - - return new Border + var panel = new StackPanel { Spacing = 2 }; + panel.Children.Add(new TextBlock { - Child = outerGrid, - Padding = new Thickness(4), - }; + Text = title, + FontSize = 16, + FontWeight = FontWeight.SemiBold, + }); + panel.Children.Add(new TextBlock + { + Text = description, + FontSize = 12, + Foreground = EditorChrome.DimTextBrush, + TextWrapping = TextWrapping.Wrap, + MaxWidth = 480, + HorizontalAlignment = HorizontalAlignment.Left, + }); + + return new Border { Child = panel, Padding = new Thickness(14, 14, 14, 12) }; } - private static StackPanel BuildHeader() + private static Border MakeEngineSection(KokoroTtsSettingsViewModel vm, out TextBox folderText) { - var title = new TextBlock + var rows = new StackPanel { Spacing = 8 }; + + rows.Children.Add(Row(Se.Language.General.Backend, new TextBlock { - Text = "Kokoro TTS", - FontSize = UiUtil.ScaledFontSize(18), + FontSize = 12.5, FontWeight = FontWeight.SemiBold, - }; + VerticalAlignment = VerticalAlignment.Center, + [!TextBlock.TextProperty] = new Binding(nameof(vm.BackendLabel)), + })); - var subtitle = new TextBlock - { - Text = new KokoroTtsCpp().Description, - FontSize = UiUtil.ScaledFontSize(12), - Opacity = 0.75, - Margin = new Thickness(0, 2, 0, 0), - }; + // Status: colored dot + text, the (re)download button beside it + var redownload = UiUtil.MakeButton(string.Empty, vm.RedownloadCommand) + .WithIconLeftBindText(IconNames.Download, nameof(vm.DownloadButtonLabel)); + var status = MakeStatusPanel(nameof(vm.StatusBrush), nameof(vm.StatusLabel)); + status.Children.Add(redownload); + rows.Children.Add(Row(Se.Language.General.Status, status)); - return new StackPanel + rows.Children.Add(Row(Se.Language.General.Release, new TextBlock { - Orientation = Orientation.Vertical, - Spacing = 0, - Children = { title, subtitle }, - }; + FontFamily = new FontFamily("Cascadia Mono,Consolas,Menlo,Monaco,monospace"), + FontSize = 12, + VerticalAlignment = VerticalAlignment.Center, + [!TextBlock.TextProperty] = new Binding(nameof(vm.ReleaseTag)), + })); + + rows.Children.Add(Row(Se.Language.General.InstallFolder, MakeFolderRow(nameof(vm.InstallFolder), vm.OpenFolderCommand, out folderText))); + + return EditorChrome.MakeEditorSection(Se.Language.General.Engine, rows); } - private static Border BuildDetails(KokoroTtsSettingsViewModel vm) + /// + /// A dim label of fixed width and the content after it, so the rows line up. + /// + private static Grid Row(string label, Control content) { + var text = EditorChrome.MakeDimLabel(label, new Thickness(0)); + text.TextTrimming = TextTrimming.CharacterEllipsis; var grid = new Grid { - ColumnDefinitions = - { - new ColumnDefinition { Width = new GridLength(LabelWidth, GridUnitType.Pixel) }, - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) }, - }, - RowDefinitions = - { - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - }, - ColumnSpacing = 12, - RowSpacing = 10, + ColumnSpacing = 10, + MinHeight = 28, + ColumnDefinitions = { new ColumnDefinition(new GridLength(LabelWidth)), new ColumnDefinition(new GridLength(1, GridUnitType.Star)) }, }; + grid.Add(text, 0, 0); + grid.Add(content, 0, 1); + return grid; + } - grid.Add(MakeLabel(Se.Language.General.Backend), 0, 0); - grid.Add(MakeValue(nameof(vm.BackendLabel)), 0, 1); - - grid.Add(MakeLabel(Se.Language.General.Status), 1, 0); - var statusDot = new Ellipse + private static StackPanel MakeStatusPanel(string brushBindingPath, string labelBindingPath) + { + var dot = new Ellipse { - Width = 10, - Height = 10, + Width = 9, + Height = 9, VerticalAlignment = VerticalAlignment.Center, - [!Ellipse.FillProperty] = new Binding(nameof(vm.StatusBrush)), + [!Ellipse.FillProperty] = new Binding(brushBindingPath), }; - var statusText = new TextBlock + var text = new TextBlock { + FontSize = 12.5, + FontWeight = FontWeight.SemiBold, VerticalAlignment = VerticalAlignment.Center, - Margin = new Thickness(8, 0, 0, 0), - [!TextBlock.TextProperty] = new Binding(nameof(vm.StatusLabel)), + Margin = new Thickness(0, 0, 6, 0), + [!TextBlock.TextProperty] = new Binding(labelBindingPath), }; - var statusPanel = new StackPanel + return new StackPanel { Orientation = Orientation.Horizontal, - Children = { statusDot, statusText }, - }; - grid.Add(statusPanel, 1, 1); - - grid.Add(MakeLabel(Se.Language.General.Release), 2, 0); - var releaseText = new TextBlock - { - FontFamily = new FontFamily("Cascadia Mono,Consolas,Menlo,Monaco,monospace"), - FontSize = UiUtil.ScaledFontSize(12), - VerticalAlignment = VerticalAlignment.Center, - [!TextBlock.TextProperty] = new Binding(nameof(vm.ReleaseTag)), + Spacing = 8, + Children = { dot, text }, }; - grid.Add(releaseText, 2, 1); + } - grid.Add(MakeLabel(Se.Language.General.InstallFolder), 3, 0); - var folderText = new TextBox + /// + /// The read-only install folder with an "open folder" icon button after it. + /// + private static StackPanel MakeFolderRow(string folderBindingPath, System.Windows.Input.ICommand openFolderCommand, out TextBox folderText) + { + folderText = new TextBox { IsReadOnly = true, Width = ValueWidth, - BorderThickness = new Thickness(0), - Background = Brushes.Transparent, - Padding = new Thickness(0), + Background = EditorChrome.FieldBrush, + BorderBrush = EditorChrome.FieldBorderBrush, + CornerRadius = new CornerRadius(6), + Padding = new Thickness(8, 0), + MinHeight = 28, + Height = 28, + VerticalAlignment = VerticalAlignment.Center, VerticalContentAlignment = VerticalAlignment.Center, - FontSize = UiUtil.ScaledFontSize(12), - [!TextBox.TextProperty] = new Binding(nameof(vm.InstallFolder)), + FontSize = 12, + [!TextBox.TextProperty] = new Binding(folderBindingPath), }; - grid.Add(folderText, 3, 1); + AutomationProperties.SetName(folderText, Se.Language.General.InstallFolder); - return new Border + var openFolder = EditorChrome.CreateToolButton(IconNames.FolderOpen, Se.Language.General.OpenContainingFolder, openFolderCommand, 28); + return new StackPanel { - Child = grid, - Padding = new Thickness(14), - CornerRadius = new CornerRadius(6), - BorderThickness = new Thickness(1), - BorderBrush = new SolidColorBrush(Color.FromArgb(0x40, 0x80, 0x80, 0x80)), + Orientation = Orientation.Horizontal, + Spacing = 6, + Children = { folderText, openFolder }, }; } - private static Grid BuildActions(KokoroTtsSettingsViewModel vm) + private static Border MakeButtonBar(KokoroTtsSettingsViewModel vm) { - var redownload = UiUtil.MakeButton(string.Empty, vm.RedownloadCommand) - .WithIconLeftBindText(IconNames.Download, nameof(vm.DownloadButtonLabel)); - var openFolder = UiUtil.MakeButton(Se.Language.General.OpenContainingFolder, vm.OpenFolderCommand).WithIconLeft(IconNames.FolderOpen); var close = UiUtil.MakeButton(Se.Language.General.Close, vm.OkCommand); + close.Classes.Add("primary"); - var leftPanel = new StackPanel - { - Orientation = Orientation.Horizontal, - Spacing = 8, - Children = { redownload, openFolder }, - }; - - var rightPanel = new StackPanel - { - Orientation = Orientation.Horizontal, - HorizontalAlignment = HorizontalAlignment.Right, - Spacing = 8, - Children = { close }, - }; - - var grid = new Grid + return new Border { - ColumnDefinitions = + Child = new StackPanel { - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Auto) }, - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) }, - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Auto) }, + Orientation = Orientation.Horizontal, + HorizontalAlignment = HorizontalAlignment.Right, + Spacing = 10, + Children = { close }, }, + Background = EditorChrome.ButtonBarBrush, + BorderBrush = EditorChrome.LineBrush, + BorderThickness = new Thickness(0, 1, 0, 0), + Padding = new Thickness(16, 10), }; - grid.Add(leftPanel, 0, 0); - grid.Add(rightPanel, 0, 2); - return grid; } - private static TextBlock MakeLabel(string text) => new() - { - Text = text, - Opacity = 0.7, - VerticalAlignment = VerticalAlignment.Center, - }; - - private static TextBlock MakeValue(string bindingPath) => new() - { - FontWeight = FontWeight.SemiBold, - VerticalAlignment = VerticalAlignment.Center, - [!TextBlock.TextProperty] = new Binding(bindingPath), - }; - protected override void OnKeyDown(KeyEventArgs e) { base.OnKeyDown(e); diff --git a/src/ui/Features/Video/TextToSpeech/OmniVoiceSettings/OmniVoiceSettingsWindow.cs b/src/ui/Features/Video/TextToSpeech/OmniVoiceSettings/OmniVoiceSettingsWindow.cs index 5465eabcbe7..07ab032e0c9 100644 --- a/src/ui/Features/Video/TextToSpeech/OmniVoiceSettings/OmniVoiceSettingsWindow.cs +++ b/src/ui/Features/Video/TextToSpeech/OmniVoiceSettings/OmniVoiceSettingsWindow.cs @@ -1,4 +1,5 @@ using Avalonia; +using Avalonia.Automation; using Avalonia.Controls; using Avalonia.Controls.Shapes; using Avalonia.Data; @@ -11,10 +12,15 @@ namespace Nikse.SubtitleEdit.Features.Video.TextToSpeech.OmniVoiceSettings; +/// +/// OmniVoice engine status, in the editor look (see ): engine name and +/// description on top, an engine section (backend, status + download, release, install folder), +/// Close in the button bar. +/// public class OmniVoiceSettingsWindow : Window { - private const int LabelWidth = 110; - private const int ValueWidth = 360; + private const double LabelWidth = 100; + private const double ValueWidth = 340; private readonly OmniVoiceSettingsViewModel _vm; @@ -24,201 +30,179 @@ public OmniVoiceSettingsWindow(OmniVoiceSettingsViewModel vm) Title = Se.Language.Video.TextToSpeech.OmniVoiceTtsSettings; SizeToContent = SizeToContent.WidthAndHeight; CanResize = false; - MinWidth = 540; + MinWidth = 520; + Background = EditorChrome.WindowBrush; + EditorChrome.AddEditorStyles(Styles); _vm = vm; vm.Window = this; DataContext = vm; - Content = BuildContent(vm); + var sections = new StackPanel { Background = EditorChrome.PanelBrush }; + sections.Children.Add(MakeHeader("OmniVoice TTS", new OmniVoiceTtsCpp().Description)); + sections.Children.Add(MakeEngineSection(vm, out var folderText)); - var ok = UiUtil.MakeButtonOk(vm.OkCommand); - UiUtil.FocusOnFirstActivation(this, ok); // ensure OnKeyDown fires + var root = new DockPanel(); + var buttonBar = MakeButtonBar(vm); + DockPanel.SetDock(buttonBar, Dock.Bottom); + root.Children.Add(buttonBar); + root.Children.Add(sections); + Content = root; + + // initial focus on an input, not an action button - a focused button clicks on bare Space; + // a focused control also makes sure OnKeyDown fires + UiUtil.FocusOnFirstActivation(this, folderText); } - private Border BuildContent(OmniVoiceSettingsViewModel vm) + private static Border MakeHeader(string title, string description) { - var header = BuildHeader(); - var details = BuildDetails(vm); - var actions = BuildActions(vm); - - var stack = new StackPanel - { - Orientation = Orientation.Vertical, - Spacing = 14, - Children = { header, details, actions }, - }; - - var outerGrid = new Grid - { - Margin = UiUtil.MakeWindowMargin(), - }; - outerGrid.Children.Add(stack); - - return new Border + var panel = new StackPanel { Spacing = 2 }; + panel.Children.Add(new TextBlock { - Child = outerGrid, - Padding = new Thickness(4), - }; + Text = title, + FontSize = 16, + FontWeight = FontWeight.SemiBold, + }); + panel.Children.Add(new TextBlock + { + Text = description, + FontSize = 12, + Foreground = EditorChrome.DimTextBrush, + TextWrapping = TextWrapping.Wrap, + MaxWidth = 480, + HorizontalAlignment = HorizontalAlignment.Left, + }); + + return new Border { Child = panel, Padding = new Thickness(14, 14, 14, 12) }; } - private static StackPanel BuildHeader() + private static Border MakeEngineSection(OmniVoiceSettingsViewModel vm, out TextBox folderText) { - var title = new TextBlock + var rows = new StackPanel { Spacing = 8 }; + + rows.Children.Add(Row(Se.Language.General.Backend, new TextBlock { - Text = "OmniVoice TTS", - FontSize = UiUtil.ScaledFontSize(18), + FontSize = 12.5, FontWeight = FontWeight.SemiBold, - }; + VerticalAlignment = VerticalAlignment.Center, + [!TextBlock.TextProperty] = new Binding(nameof(vm.BackendLabel)), + })); - var subtitle = new TextBlock - { - Text = new OmniVoiceTtsCpp().Description, - FontSize = UiUtil.ScaledFontSize(12), - Opacity = 0.75, - Margin = new Thickness(0, 2, 0, 0), - }; + // Status: colored dot + text, the (re)download button beside it + var redownload = UiUtil.MakeButton(string.Empty, vm.RedownloadCommand) + .WithIconLeftBindText(IconNames.Download, nameof(vm.DownloadButtonLabel)); + var status = MakeStatusPanel(nameof(vm.StatusBrush), nameof(vm.StatusLabel)); + status.Children.Add(redownload); + rows.Children.Add(Row(Se.Language.General.Status, status)); - return new StackPanel + rows.Children.Add(Row(Se.Language.General.Release, new TextBlock { - Orientation = Orientation.Vertical, - Spacing = 0, - Children = { title, subtitle }, - }; + FontFamily = new FontFamily("Cascadia Mono,Consolas,Menlo,Monaco,monospace"), + FontSize = 12, + VerticalAlignment = VerticalAlignment.Center, + [!TextBlock.TextProperty] = new Binding(nameof(vm.ReleaseTag)), + })); + + rows.Children.Add(Row(Se.Language.General.InstallFolder, MakeFolderRow(nameof(vm.InstallFolder), vm.OpenFolderCommand, out folderText))); + + return EditorChrome.MakeEditorSection(Se.Language.General.Engine, rows); } - private static Border BuildDetails(OmniVoiceSettingsViewModel vm) + /// + /// A dim label of fixed width and the content after it, so the rows line up. + /// + private static Grid Row(string label, Control content) { + var text = EditorChrome.MakeDimLabel(label, new Thickness(0)); + text.TextTrimming = TextTrimming.CharacterEllipsis; var grid = new Grid { - ColumnDefinitions = - { - new ColumnDefinition { Width = new GridLength(LabelWidth, GridUnitType.Pixel) }, - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) }, - }, - RowDefinitions = - { - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - }, - ColumnSpacing = 12, - RowSpacing = 10, + ColumnSpacing = 10, + MinHeight = 28, + ColumnDefinitions = { new ColumnDefinition(new GridLength(LabelWidth)), new ColumnDefinition(new GridLength(1, GridUnitType.Star)) }, }; + grid.Add(text, 0, 0); + grid.Add(content, 0, 1); + return grid; + } - // Backend - grid.Add(MakeLabel(Se.Language.General.Backend), 0, 0); - grid.Add(MakeValue(nameof(vm.BackendLabel)), 0, 1); - - // Status (colored dot + text) - grid.Add(MakeLabel(Se.Language.General.Status), 1, 0); - var statusDot = new Ellipse + private static StackPanel MakeStatusPanel(string brushBindingPath, string labelBindingPath) + { + var dot = new Ellipse { - Width = 10, - Height = 10, + Width = 9, + Height = 9, VerticalAlignment = VerticalAlignment.Center, - [!Ellipse.FillProperty] = new Binding(nameof(vm.StatusBrush)), + [!Ellipse.FillProperty] = new Binding(brushBindingPath), }; - var statusText = new TextBlock + var text = new TextBlock { + FontSize = 12.5, + FontWeight = FontWeight.SemiBold, VerticalAlignment = VerticalAlignment.Center, - Margin = new Thickness(8, 0, 0, 0), - [!TextBlock.TextProperty] = new Binding(nameof(vm.StatusLabel)), + Margin = new Thickness(0, 0, 6, 0), + [!TextBlock.TextProperty] = new Binding(labelBindingPath), }; - var statusPanel = new StackPanel + return new StackPanel { Orientation = Orientation.Horizontal, - Children = { statusDot, statusText }, - }; - grid.Add(statusPanel, 1, 1); - - // Release - grid.Add(MakeLabel(Se.Language.General.Release), 2, 0); - var releaseText = new TextBlock - { - FontFamily = new FontFamily("Cascadia Mono,Consolas,Menlo,Monaco,monospace"), - FontSize = UiUtil.ScaledFontSize(12), - VerticalAlignment = VerticalAlignment.Center, - [!TextBlock.TextProperty] = new Binding(nameof(vm.ReleaseTag)), + Spacing = 8, + Children = { dot, text }, }; - grid.Add(releaseText, 2, 1); + } - // Install folder - grid.Add(MakeLabel(Se.Language.General.InstallFolder), 3, 0); - var folderText = new TextBox + /// + /// The read-only install folder with an "open folder" icon button after it. + /// + private static StackPanel MakeFolderRow(string folderBindingPath, System.Windows.Input.ICommand openFolderCommand, out TextBox folderText) + { + folderText = new TextBox { IsReadOnly = true, Width = ValueWidth, - BorderThickness = new Thickness(0), - Background = Brushes.Transparent, - Padding = new Thickness(0), + Background = EditorChrome.FieldBrush, + BorderBrush = EditorChrome.FieldBorderBrush, + CornerRadius = new CornerRadius(6), + Padding = new Thickness(8, 0), + MinHeight = 28, + Height = 28, + VerticalAlignment = VerticalAlignment.Center, VerticalContentAlignment = VerticalAlignment.Center, - FontSize = UiUtil.ScaledFontSize(12), - [!TextBox.TextProperty] = new Binding(nameof(vm.InstallFolder)), + FontSize = 12, + [!TextBox.TextProperty] = new Binding(folderBindingPath), }; - grid.Add(folderText, 3, 1); + AutomationProperties.SetName(folderText, Se.Language.General.InstallFolder); - return new Border + var openFolder = EditorChrome.CreateToolButton(IconNames.FolderOpen, Se.Language.General.OpenContainingFolder, openFolderCommand, 28); + return new StackPanel { - Child = grid, - Padding = new Thickness(14), - CornerRadius = new CornerRadius(6), - BorderThickness = new Thickness(1), - BorderBrush = new SolidColorBrush(Color.FromArgb(0x40, 0x80, 0x80, 0x80)), + Orientation = Orientation.Horizontal, + Spacing = 6, + Children = { folderText, openFolder }, }; } - private static Grid BuildActions(OmniVoiceSettingsViewModel vm) + private static Border MakeButtonBar(OmniVoiceSettingsViewModel vm) { - var redownload = UiUtil.MakeButton(string.Empty, vm.RedownloadCommand) - .WithIconLeftBindText(IconNames.Download, nameof(vm.DownloadButtonLabel)); - var openFolder = UiUtil.MakeButton(Se.Language.General.OpenContainingFolder, vm.OpenFolderCommand).WithIconLeft(IconNames.FolderOpen); var close = UiUtil.MakeButton(Se.Language.General.Close, vm.OkCommand); + close.Classes.Add("primary"); - var leftPanel = new StackPanel - { - Orientation = Orientation.Horizontal, - Spacing = 8, - Children = { redownload, openFolder }, - }; - - var rightPanel = new StackPanel - { - Orientation = Orientation.Horizontal, - HorizontalAlignment = HorizontalAlignment.Right, - Spacing = 8, - Children = { close }, - }; - - var grid = new Grid + return new Border { - ColumnDefinitions = + Child = new StackPanel { - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Auto) }, - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) }, - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Auto) }, + Orientation = Orientation.Horizontal, + HorizontalAlignment = HorizontalAlignment.Right, + Spacing = 10, + Children = { close }, }, + Background = EditorChrome.ButtonBarBrush, + BorderBrush = EditorChrome.LineBrush, + BorderThickness = new Thickness(0, 1, 0, 0), + Padding = new Thickness(16, 10), }; - grid.Add(leftPanel, 0, 0); - grid.Add(rightPanel, 0, 2); - return grid; } - private static TextBlock MakeLabel(string text) => new() - { - Text = text, - Opacity = 0.7, - VerticalAlignment = VerticalAlignment.Center, - }; - - private static TextBlock MakeValue(string bindingPath) => new() - { - FontWeight = FontWeight.SemiBold, - VerticalAlignment = VerticalAlignment.Center, - [!TextBlock.TextProperty] = new Binding(bindingPath), - }; - protected override void OnKeyDown(KeyEventArgs e) { base.OnKeyDown(e); diff --git a/src/ui/Features/Video/TextToSpeech/OpenAiCompatibleSettings/OpenAiCompatibleSettingsWindow.cs b/src/ui/Features/Video/TextToSpeech/OpenAiCompatibleSettings/OpenAiCompatibleSettingsWindow.cs index 518bb402391..ba18c706e87 100644 --- a/src/ui/Features/Video/TextToSpeech/OpenAiCompatibleSettings/OpenAiCompatibleSettingsWindow.cs +++ b/src/ui/Features/Video/TextToSpeech/OpenAiCompatibleSettings/OpenAiCompatibleSettingsWindow.cs @@ -1,15 +1,26 @@ using Avalonia; +using Avalonia.Automation; using Avalonia.Controls; +using Avalonia.Controls.Primitives; using Avalonia.Input; using Avalonia.Layout; using Avalonia.Media; +using Avalonia.Styling; using Nikse.SubtitleEdit.Logic; using Nikse.SubtitleEdit.Logic.Config; namespace Nikse.SubtitleEdit.Features.Video.TextToSpeech.OpenAiCompatibleSettings; +/// +/// OpenAI-compatible TTS server settings, in the editor look (see ): +/// a server section (url, models, voices) and a voice section (instructions, speed, format), +/// more info and reset as icon buttons in the first header, Cancel/OK in the button bar. +/// public class OpenAiCompatibleSettingsWindow : Window { + private const double LabelWidth = 120; + private const double InputWidth = 380; + private readonly OpenAiCompatibleSettingsViewModel _vm; public OpenAiCompatibleSettingsWindow(OpenAiCompatibleSettingsViewModel vm) @@ -18,71 +29,144 @@ public OpenAiCompatibleSettingsWindow(OpenAiCompatibleSettingsViewModel vm) Title = Se.Language.Video.TextToSpeech.OpenAiCompatibleSettings; SizeToContent = SizeToContent.WidthAndHeight; CanResize = false; + Background = EditorChrome.WindowBrush; + EditorChrome.AddEditorStyles(Styles); + AddStyles(); _vm = vm; vm.Window = this; DataContext = vm; var l = Se.Language.Video.TextToSpeech; - const double inputWidth = 400; - var textBoxUrl = UiUtil.MakeTextBox(inputWidth, vm, nameof(vm.CustomUrl)); - var textBoxModels = UiUtil.MakeTextBox(inputWidth, vm, nameof(vm.CustomModels)); - var textBoxVoices = UiUtil.MakeTextBox(inputWidth, vm, nameof(vm.CustomVoices)); - var textBoxInstructions = UiUtil.MakeTextBox(inputWidth, vm, nameof(vm.Instructions)); + var textBoxUrl = MakeTextBox(UiUtil.MakeTextBox(InputWidth, vm, nameof(vm.CustomUrl)), l.CustomServerUrl); + var textBoxModels = MakeTextBox(UiUtil.MakeTextBox(InputWidth, vm, nameof(vm.CustomModels)), l.CustomModels); + var textBoxVoices = MakeTextBox(UiUtil.MakeTextBox(InputWidth, vm, nameof(vm.CustomVoices)), l.CustomVoices); + var textBoxInstructions = MakeTextBox(UiUtil.MakeTextBox(InputWidth, vm, nameof(vm.Instructions)), l.Instructions); textBoxInstructions.AcceptsReturn = true; textBoxInstructions.TextWrapping = TextWrapping.Wrap; textBoxInstructions.Height = 70; + textBoxInstructions.VerticalContentAlignment = VerticalAlignment.Top; + textBoxInstructions.Padding = new Thickness(8, 6); var numericSpeed = UiUtil.MakeNumericUpDownTwoDecimals(0.25m, 4.0m, 120, vm, nameof(vm.Speed), defaultValue: 1.0m); + numericSpeed.Classes.Add("field"); + numericSpeed.HorizontalAlignment = HorizontalAlignment.Left; + AutomationProperties.SetName(numericSpeed, Se.Language.General.Speed); var comboBoxResponseFormat = UiUtil.MakeComboBox(vm.ResponseFormats, vm, nameof(vm.SelectedResponseFormat)); comboBoxResponseFormat.MinWidth = 120; + comboBoxResponseFormat.HorizontalAlignment = HorizontalAlignment.Left; + AutomationProperties.SetName(comboBoxResponseFormat, l.OutputFormat); - var buttonWeb = UiUtil.MakeButton(Se.Language.General.MoreInfo, vm.ShowMoreOnWebCommand).WithIconLeft(IconNames.Web); - var buttonReset = UiUtil.MakeButton(Se.Language.General.Reset, vm.ResetCommand).WithIconLeft(IconNames.Repeat); - var buttonOk = UiUtil.MakeButtonOk(vm.OkCommand); - var buttonCancel = UiUtil.MakeButtonCancel(vm.CancelCommand); - var panelButtons = UiUtil.MakeButtonBar(buttonWeb, buttonReset, buttonOk, buttonCancel); + var server = new StackPanel { Spacing = 8 }; + server.Children.Add(Row(l.CustomServerUrl, textBoxUrl, l.CustomServerUrlHint)); + server.Children.Add(Row(l.CustomModels, textBoxModels, l.CustomModelsHint)); + server.Children.Add(Row(l.CustomVoices, textBoxVoices, l.CustomVoicesHint)); - var grid = new Grid - { - ColumnDefinitions = - { - new ColumnDefinition { Width = GridLength.Auto }, - new ColumnDefinition { Width = GridLength.Auto }, - new ColumnDefinition { Width = GridLength.Auto }, - }, - Margin = UiUtil.MakeWindowMargin(), - ColumnSpacing = 8, - RowSpacing = 10, - }; + var voice = new StackPanel { Spacing = 8 }; + voice.Children.Add(Row(l.Instructions, textBoxInstructions, l.InstructionsHint)); + voice.Children.Add(Row(Se.Language.General.Speed, numericSpeed, l.OpenAiSpeedHint)); + voice.Children.Add(Row(l.OutputFormat, comboBoxResponseFormat, l.OutputFormatHint)); - var row = 0; - AddRow(grid, ref row, l.CustomServerUrl, textBoxUrl, l.CustomServerUrlHint); - AddRow(grid, ref row, l.CustomModels, textBoxModels, l.CustomModelsHint); - AddRow(grid, ref row, l.CustomVoices, textBoxVoices, l.CustomVoicesHint); - AddRow(grid, ref row, l.Instructions, textBoxInstructions, l.InstructionsHint); - AddRow(grid, ref row, Se.Language.General.Speed, numericSpeed, l.OpenAiSpeedHint); - AddRow(grid, ref row, l.OutputFormat, comboBoxResponseFormat, l.OutputFormatHint); + var headerActions = new StackPanel { Orientation = Orientation.Horizontal, Spacing = 2 }; + headerActions.Children.Add(EditorChrome.CreateToolButton(IconNames.Web, Se.Language.General.MoreInfo, vm.ShowMoreOnWebCommand, 26)); + headerActions.Children.Add(EditorChrome.CreateToolButton(IconNames.Repeat, Se.Language.General.Reset, vm.ResetCommand, 26)); - grid.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto }); - grid.Add(panelButtons, row, 0, 1, 3); + var sections = new StackPanel { Background = EditorChrome.PanelBrush }; + sections.Children.Add(EditorChrome.MakeEditorSection(Se.Language.General.Settings, server, headerActions, first: true)); + sections.Children.Add(EditorChrome.MakeEditorSection(Se.Language.General.Voice, voice)); - Content = grid; + var root = new DockPanel(); + var buttonBar = MakeButtonBar(vm); + DockPanel.SetDock(buttonBar, Dock.Bottom); + root.Children.Add(buttonBar); + root.Children.Add(sections); + Content = root; UiUtil.FocusOnFirstActivation(this, textBoxUrl); // initial focus on an input, not an action button - a focused button clicks on bare Space } - private static void AddRow(Grid grid, ref int row, string label, Control input, string hint) + /// + /// Compact combo boxes. + /// + private void AddStyles() + { + Styles.Add(new Style(x => x.OfType()) + { + Setters = + { + new Setter(TemplatedControl.FontSizeProperty, 12.5), + new Setter(TemplatedControl.MinHeightProperty, 28.0), + new Setter(TemplatedControl.PaddingProperty, new Thickness(8, 2, 4, 2)), + new Setter(TemplatedControl.CornerRadiusProperty, new CornerRadius(6)), + }, + }); + } + + private static TextBox MakeTextBox(TextBox textBox, string name) + { + textBox.FontSize = 12.5; + textBox.MinHeight = 28; + textBox.Background = EditorChrome.FieldBrush; + textBox.BorderBrush = EditorChrome.FieldBorderBrush; + textBox.CornerRadius = new CornerRadius(6); + textBox.VerticalContentAlignment = VerticalAlignment.Center; + AutomationProperties.SetName(textBox, name); + return textBox; + } + + /// + /// "Label [input] (i)" - the label of fixed width so the inputs line up; a multi-line input + /// gets its label and hint at the top. + /// + private static Grid Row(string label, Control input, string hint) { - grid.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto }); - var labelControl = UiUtil.MakeLabel(label); - labelControl.VerticalAlignment = input is TextBox { AcceptsReturn: true } ? VerticalAlignment.Top : VerticalAlignment.Center; - grid.Add(labelControl, row, 0); - grid.Add(input, row, 1); + var alignment = input is TextBox { AcceptsReturn: true } ? VerticalAlignment.Top : VerticalAlignment.Center; + var text = EditorChrome.MakeDimLabel(label, new Thickness(0, alignment == VerticalAlignment.Top ? 6 : 0, 0, 0)); + text.VerticalAlignment = alignment; + text.TextTrimming = TextTrimming.CharacterEllipsis; var hintIcon = UiUtil.MakeHintIcon(hint, input); - hintIcon.VerticalAlignment = labelControl.VerticalAlignment; - grid.Add(hintIcon, row, 2); - row++; + hintIcon.VerticalAlignment = alignment; + if (alignment == VerticalAlignment.Top) + { + hintIcon.Margin = new Thickness(0, 6, 0, 0); + } + + var grid = new Grid + { + ColumnSpacing = 8, + ColumnDefinitions = + { + new ColumnDefinition(new GridLength(LabelWidth)), + new ColumnDefinition(new GridLength(InputWidth)), + new ColumnDefinition(GridLength.Auto), + }, + }; + grid.Add(text, 0, 0); + grid.Add(input, 0, 1); + grid.Add(hintIcon, 0, 2); + return grid; + } + + private static Border MakeButtonBar(OpenAiCompatibleSettingsViewModel vm) + { + var buttonOk = UiUtil.MakeButtonOk(vm.OkCommand); + buttonOk.Classes.Add("primary"); + var buttonCancel = UiUtil.MakeButtonCancel(vm.CancelCommand); + + return new Border + { + Child = new StackPanel + { + Orientation = Orientation.Horizontal, + HorizontalAlignment = HorizontalAlignment.Right, + Spacing = 10, + Children = { buttonCancel, buttonOk }, + }, + Background = EditorChrome.ButtonBarBrush, + BorderBrush = EditorChrome.LineBrush, + BorderThickness = new Thickness(0, 1, 0, 0), + Padding = new Thickness(16, 10), + }; } protected override void OnKeyDown(KeyEventArgs e) diff --git a/src/ui/Features/Video/TextToSpeech/PiperSettings/PiperSettingsWindow.cs b/src/ui/Features/Video/TextToSpeech/PiperSettings/PiperSettingsWindow.cs index 127dc2a5b52..5aed2ef5216 100644 --- a/src/ui/Features/Video/TextToSpeech/PiperSettings/PiperSettingsWindow.cs +++ b/src/ui/Features/Video/TextToSpeech/PiperSettings/PiperSettingsWindow.cs @@ -1,4 +1,5 @@ using Avalonia; +using Avalonia.Automation; using Avalonia.Controls; using Avalonia.Controls.Shapes; using Avalonia.Data; @@ -11,10 +12,15 @@ namespace Nikse.SubtitleEdit.Features.Video.TextToSpeech.PiperSettings; +/// +/// Piper engine status, in the editor look (see ): engine name and +/// description on top, an engine section (backend, status + download, release, install folder), +/// Close in the button bar. +/// public class PiperSettingsWindow : Window { - private const int LabelWidth = 110; - private const int ValueWidth = 360; + private const double LabelWidth = 100; + private const double ValueWidth = 340; private readonly PiperSettingsViewModel _vm; @@ -24,197 +30,179 @@ public PiperSettingsWindow(PiperSettingsViewModel vm) Title = Se.Language.Video.TextToSpeech.PiperSettings; SizeToContent = SizeToContent.WidthAndHeight; CanResize = false; - MinWidth = 540; + MinWidth = 520; + Background = EditorChrome.WindowBrush; + EditorChrome.AddEditorStyles(Styles); _vm = vm; vm.Window = this; DataContext = vm; - Content = BuildContent(vm); + var sections = new StackPanel { Background = EditorChrome.PanelBrush }; + sections.Children.Add(MakeHeader("Piper", new Piper(null!).Description)); + sections.Children.Add(MakeEngineSection(vm, out var folderText)); - var ok = UiUtil.MakeButtonOk(vm.OkCommand); - UiUtil.FocusOnFirstActivation(this, ok); + var root = new DockPanel(); + var buttonBar = MakeButtonBar(vm); + DockPanel.SetDock(buttonBar, Dock.Bottom); + root.Children.Add(buttonBar); + root.Children.Add(sections); + Content = root; + + // initial focus on an input, not an action button - a focused button clicks on bare Space; + // a focused control also makes sure OnKeyDown fires + UiUtil.FocusOnFirstActivation(this, folderText); } - private Border BuildContent(PiperSettingsViewModel vm) + private static Border MakeHeader(string title, string description) { - var header = BuildHeader(); - var details = BuildDetails(vm); - var actions = BuildActions(vm); - - var stack = new StackPanel - { - Orientation = Orientation.Vertical, - Spacing = 14, - Children = { header, details, actions }, - }; - - var outerGrid = new Grid - { - Margin = UiUtil.MakeWindowMargin(), - }; - outerGrid.Children.Add(stack); - - return new Border + var panel = new StackPanel { Spacing = 2 }; + panel.Children.Add(new TextBlock { - Child = outerGrid, - Padding = new Thickness(4), - }; + Text = title, + FontSize = 16, + FontWeight = FontWeight.SemiBold, + }); + panel.Children.Add(new TextBlock + { + Text = description, + FontSize = 12, + Foreground = EditorChrome.DimTextBrush, + TextWrapping = TextWrapping.Wrap, + MaxWidth = 480, + HorizontalAlignment = HorizontalAlignment.Left, + }); + + return new Border { Child = panel, Padding = new Thickness(14, 14, 14, 12) }; } - private static StackPanel BuildHeader() + private static Border MakeEngineSection(PiperSettingsViewModel vm, out TextBox folderText) { - var title = new TextBlock + var rows = new StackPanel { Spacing = 8 }; + + rows.Children.Add(Row(Se.Language.General.Backend, new TextBlock { - Text = "Piper", - FontSize = UiUtil.ScaledFontSize(18), + FontSize = 12.5, FontWeight = FontWeight.SemiBold, - }; + VerticalAlignment = VerticalAlignment.Center, + [!TextBlock.TextProperty] = new Binding(nameof(vm.BackendLabel)), + })); - var subtitle = new TextBlock - { - Text = new Piper(null!).Description, - FontSize = UiUtil.ScaledFontSize(12), - Opacity = 0.75, - Margin = new Thickness(0, 2, 0, 0), - }; + // Status: colored dot + text, the (re)download button beside it + var redownload = UiUtil.MakeButton(string.Empty, vm.RedownloadCommand) + .WithIconLeftBindText(IconNames.Download, nameof(vm.DownloadButtonLabel)); + var status = MakeStatusPanel(nameof(vm.StatusBrush), nameof(vm.StatusLabel)); + status.Children.Add(redownload); + rows.Children.Add(Row(Se.Language.General.Status, status)); - return new StackPanel + rows.Children.Add(Row(Se.Language.General.Release, new TextBlock { - Orientation = Orientation.Vertical, - Spacing = 0, - Children = { title, subtitle }, - }; + FontFamily = new FontFamily("Cascadia Mono,Consolas,Menlo,Monaco,monospace"), + FontSize = 12, + VerticalAlignment = VerticalAlignment.Center, + [!TextBlock.TextProperty] = new Binding(nameof(vm.ReleaseTag)), + })); + + rows.Children.Add(Row(Se.Language.General.InstallFolder, MakeFolderRow(nameof(vm.InstallFolder), vm.OpenFolderCommand, out folderText))); + + return EditorChrome.MakeEditorSection(Se.Language.General.Engine, rows); } - private static Border BuildDetails(PiperSettingsViewModel vm) + /// + /// A dim label of fixed width and the content after it, so the rows line up. + /// + private static Grid Row(string label, Control content) { + var text = EditorChrome.MakeDimLabel(label, new Thickness(0)); + text.TextTrimming = TextTrimming.CharacterEllipsis; var grid = new Grid { - ColumnDefinitions = - { - new ColumnDefinition { Width = new GridLength(LabelWidth, GridUnitType.Pixel) }, - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) }, - }, - RowDefinitions = - { - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - }, - ColumnSpacing = 12, - RowSpacing = 10, + ColumnSpacing = 10, + MinHeight = 28, + ColumnDefinitions = { new ColumnDefinition(new GridLength(LabelWidth)), new ColumnDefinition(new GridLength(1, GridUnitType.Star)) }, }; + grid.Add(text, 0, 0); + grid.Add(content, 0, 1); + return grid; + } - grid.Add(MakeLabel(Se.Language.General.Backend), 0, 0); - grid.Add(MakeValue(nameof(vm.BackendLabel)), 0, 1); - - grid.Add(MakeLabel(Se.Language.General.Status), 1, 0); - var statusDot = new Ellipse + private static StackPanel MakeStatusPanel(string brushBindingPath, string labelBindingPath) + { + var dot = new Ellipse { - Width = 10, - Height = 10, + Width = 9, + Height = 9, VerticalAlignment = VerticalAlignment.Center, - [!Ellipse.FillProperty] = new Binding(nameof(vm.StatusBrush)), + [!Ellipse.FillProperty] = new Binding(brushBindingPath), }; - var statusText = new TextBlock + var text = new TextBlock { + FontSize = 12.5, + FontWeight = FontWeight.SemiBold, VerticalAlignment = VerticalAlignment.Center, - Margin = new Thickness(8, 0, 0, 0), - [!TextBlock.TextProperty] = new Binding(nameof(vm.StatusLabel)), + Margin = new Thickness(0, 0, 6, 0), + [!TextBlock.TextProperty] = new Binding(labelBindingPath), }; - var statusPanel = new StackPanel + return new StackPanel { Orientation = Orientation.Horizontal, - Children = { statusDot, statusText }, - }; - grid.Add(statusPanel, 1, 1); - - grid.Add(MakeLabel(Se.Language.General.Release), 2, 0); - var releaseText = new TextBlock - { - FontFamily = new FontFamily("Cascadia Mono,Consolas,Menlo,Monaco,monospace"), - FontSize = UiUtil.ScaledFontSize(12), - VerticalAlignment = VerticalAlignment.Center, - [!TextBlock.TextProperty] = new Binding(nameof(vm.ReleaseTag)), + Spacing = 8, + Children = { dot, text }, }; - grid.Add(releaseText, 2, 1); + } - grid.Add(MakeLabel(Se.Language.General.InstallFolder), 3, 0); - var folderText = new TextBox + /// + /// The read-only install folder with an "open folder" icon button after it. + /// + private static StackPanel MakeFolderRow(string folderBindingPath, System.Windows.Input.ICommand openFolderCommand, out TextBox folderText) + { + folderText = new TextBox { IsReadOnly = true, Width = ValueWidth, - BorderThickness = new Thickness(0), - Background = Brushes.Transparent, - Padding = new Thickness(0), + Background = EditorChrome.FieldBrush, + BorderBrush = EditorChrome.FieldBorderBrush, + CornerRadius = new CornerRadius(6), + Padding = new Thickness(8, 0), + MinHeight = 28, + Height = 28, + VerticalAlignment = VerticalAlignment.Center, VerticalContentAlignment = VerticalAlignment.Center, - FontSize = UiUtil.ScaledFontSize(12), - [!TextBox.TextProperty] = new Binding(nameof(vm.InstallFolder)), + FontSize = 12, + [!TextBox.TextProperty] = new Binding(folderBindingPath), }; - grid.Add(folderText, 3, 1); + AutomationProperties.SetName(folderText, Se.Language.General.InstallFolder); - return new Border + var openFolder = EditorChrome.CreateToolButton(IconNames.FolderOpen, Se.Language.General.OpenContainingFolder, openFolderCommand, 28); + return new StackPanel { - Child = grid, - Padding = new Thickness(14), - CornerRadius = new CornerRadius(6), - BorderThickness = new Thickness(1), - BorderBrush = new SolidColorBrush(Color.FromArgb(0x40, 0x80, 0x80, 0x80)), + Orientation = Orientation.Horizontal, + Spacing = 6, + Children = { folderText, openFolder }, }; } - private static Grid BuildActions(PiperSettingsViewModel vm) + private static Border MakeButtonBar(PiperSettingsViewModel vm) { - var redownload = UiUtil.MakeButton(string.Empty, vm.RedownloadCommand) - .WithIconLeftBindText(IconNames.Download, nameof(vm.DownloadButtonLabel)); - var openFolder = UiUtil.MakeButton(Se.Language.General.OpenContainingFolder, vm.OpenFolderCommand).WithIconLeft(IconNames.FolderOpen); var close = UiUtil.MakeButton(Se.Language.General.Close, vm.OkCommand); + close.Classes.Add("primary"); - var leftPanel = new StackPanel - { - Orientation = Orientation.Horizontal, - Spacing = 8, - Children = { redownload, openFolder }, - }; - - var rightPanel = new StackPanel - { - Orientation = Orientation.Horizontal, - HorizontalAlignment = HorizontalAlignment.Right, - Spacing = 8, - Children = { close }, - }; - - var grid = new Grid + return new Border { - ColumnDefinitions = + Child = new StackPanel { - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Auto) }, - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) }, - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Auto) }, + Orientation = Orientation.Horizontal, + HorizontalAlignment = HorizontalAlignment.Right, + Spacing = 10, + Children = { close }, }, + Background = EditorChrome.ButtonBarBrush, + BorderBrush = EditorChrome.LineBrush, + BorderThickness = new Thickness(0, 1, 0, 0), + Padding = new Thickness(16, 10), }; - grid.Add(leftPanel, 0, 0); - grid.Add(rightPanel, 0, 2); - return grid; } - private static TextBlock MakeLabel(string text) => new() - { - Text = text, - Opacity = 0.7, - VerticalAlignment = VerticalAlignment.Center, - }; - - private static TextBlock MakeValue(string bindingPath) => new() - { - FontWeight = FontWeight.SemiBold, - VerticalAlignment = VerticalAlignment.Center, - [!TextBlock.TextProperty] = new Binding(bindingPath), - }; - protected override void OnKeyDown(KeyEventArgs e) { base.OnKeyDown(e); diff --git a/src/ui/Features/Video/TextToSpeech/Qwen3TtsSettings/Qwen3TtsSettingsWindow.cs b/src/ui/Features/Video/TextToSpeech/Qwen3TtsSettings/Qwen3TtsSettingsWindow.cs index 7fc29a2e5c9..262c5522934 100644 --- a/src/ui/Features/Video/TextToSpeech/Qwen3TtsSettings/Qwen3TtsSettingsWindow.cs +++ b/src/ui/Features/Video/TextToSpeech/Qwen3TtsSettings/Qwen3TtsSettingsWindow.cs @@ -1,4 +1,5 @@ using Avalonia; +using Avalonia.Automation; using Avalonia.Controls; using Avalonia.Controls.Shapes; using Avalonia.Data; @@ -11,10 +12,15 @@ namespace Nikse.SubtitleEdit.Features.Video.TextToSpeech.Qwen3TtsSettings; +/// +/// Qwen3 TTS engine status, in the editor look (see ): engine name and +/// description on top, an engine section (backend, status + download, release, install folder), +/// Close in the button bar. +/// public class Qwen3TtsSettingsWindow : Window { - private const int LabelWidth = 110; - private const int ValueWidth = 360; + private const double LabelWidth = 100; + private const double ValueWidth = 340; private readonly Qwen3TtsSettingsViewModel _vm; @@ -24,197 +30,179 @@ public Qwen3TtsSettingsWindow(Qwen3TtsSettingsViewModel vm) Title = Se.Language.Video.TextToSpeech.Qwen3TtsSettings; SizeToContent = SizeToContent.WidthAndHeight; CanResize = false; - MinWidth = 540; + MinWidth = 520; + Background = EditorChrome.WindowBrush; + EditorChrome.AddEditorStyles(Styles); _vm = vm; vm.Window = this; DataContext = vm; - Content = BuildContent(vm); + var sections = new StackPanel { Background = EditorChrome.PanelBrush }; + sections.Children.Add(MakeHeader("Qwen3 TTS", new Qwen3TtsCpp().Description)); + sections.Children.Add(MakeEngineSection(vm, out var folderText)); - var ok = UiUtil.MakeButtonOk(vm.OkCommand); - UiUtil.FocusOnFirstActivation(this, ok); // ensure OnKeyDown fires + var root = new DockPanel(); + var buttonBar = MakeButtonBar(vm); + DockPanel.SetDock(buttonBar, Dock.Bottom); + root.Children.Add(buttonBar); + root.Children.Add(sections); + Content = root; + + // initial focus on an input, not an action button - a focused button clicks on bare Space; + // a focused control also makes sure OnKeyDown fires + UiUtil.FocusOnFirstActivation(this, folderText); } - private Border BuildContent(Qwen3TtsSettingsViewModel vm) + private static Border MakeHeader(string title, string description) { - var header = BuildHeader(); - var details = BuildDetails(vm); - var actions = BuildActions(vm); - - var stack = new StackPanel - { - Orientation = Orientation.Vertical, - Spacing = 14, - Children = { header, details, actions }, - }; - - var outerGrid = new Grid - { - Margin = UiUtil.MakeWindowMargin(), - }; - outerGrid.Children.Add(stack); - - return new Border + var panel = new StackPanel { Spacing = 2 }; + panel.Children.Add(new TextBlock { - Child = outerGrid, - Padding = new Thickness(4), - }; + Text = title, + FontSize = 16, + FontWeight = FontWeight.SemiBold, + }); + panel.Children.Add(new TextBlock + { + Text = description, + FontSize = 12, + Foreground = EditorChrome.DimTextBrush, + TextWrapping = TextWrapping.Wrap, + MaxWidth = 480, + HorizontalAlignment = HorizontalAlignment.Left, + }); + + return new Border { Child = panel, Padding = new Thickness(14, 14, 14, 12) }; } - private static StackPanel BuildHeader() + private static Border MakeEngineSection(Qwen3TtsSettingsViewModel vm, out TextBox folderText) { - var title = new TextBlock + var rows = new StackPanel { Spacing = 8 }; + + rows.Children.Add(Row(Se.Language.General.Backend, new TextBlock { - Text = "Qwen3 TTS", - FontSize = UiUtil.ScaledFontSize(18), + FontSize = 12.5, FontWeight = FontWeight.SemiBold, - }; + VerticalAlignment = VerticalAlignment.Center, + [!TextBlock.TextProperty] = new Binding(nameof(vm.BackendLabel)), + })); - var subtitle = new TextBlock - { - Text = new Qwen3TtsCpp().Description, - FontSize = UiUtil.ScaledFontSize(12), - Opacity = 0.75, - Margin = new Thickness(0, 2, 0, 0), - }; + // Status: colored dot + text, the (re)download button beside it + var redownload = UiUtil.MakeButton(string.Empty, vm.RedownloadCommand) + .WithIconLeftBindText(IconNames.Download, nameof(vm.DownloadButtonLabel)); + var status = MakeStatusPanel(nameof(vm.StatusBrush), nameof(vm.StatusLabel)); + status.Children.Add(redownload); + rows.Children.Add(Row(Se.Language.General.Status, status)); - return new StackPanel + rows.Children.Add(Row(Se.Language.General.Release, new TextBlock { - Orientation = Orientation.Vertical, - Spacing = 0, - Children = { title, subtitle }, - }; + FontFamily = new FontFamily("Cascadia Mono,Consolas,Menlo,Monaco,monospace"), + FontSize = 12, + VerticalAlignment = VerticalAlignment.Center, + [!TextBlock.TextProperty] = new Binding(nameof(vm.ReleaseTag)), + })); + + rows.Children.Add(Row(Se.Language.General.InstallFolder, MakeFolderRow(nameof(vm.InstallFolder), vm.OpenFolderCommand, out folderText))); + + return EditorChrome.MakeEditorSection(Se.Language.General.Engine, rows); } - private static Border BuildDetails(Qwen3TtsSettingsViewModel vm) + /// + /// A dim label of fixed width and the content after it, so the rows line up. + /// + private static Grid Row(string label, Control content) { + var text = EditorChrome.MakeDimLabel(label, new Thickness(0)); + text.TextTrimming = TextTrimming.CharacterEllipsis; var grid = new Grid { - ColumnDefinitions = - { - new ColumnDefinition { Width = new GridLength(LabelWidth, GridUnitType.Pixel) }, - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) }, - }, - RowDefinitions = - { - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - }, - ColumnSpacing = 12, - RowSpacing = 10, + ColumnSpacing = 10, + MinHeight = 28, + ColumnDefinitions = { new ColumnDefinition(new GridLength(LabelWidth)), new ColumnDefinition(new GridLength(1, GridUnitType.Star)) }, }; + grid.Add(text, 0, 0); + grid.Add(content, 0, 1); + return grid; + } - grid.Add(MakeLabel(Se.Language.General.Backend), 0, 0); - grid.Add(MakeValue(nameof(vm.BackendLabel)), 0, 1); - - grid.Add(MakeLabel(Se.Language.General.Status), 1, 0); - var statusDot = new Ellipse + private static StackPanel MakeStatusPanel(string brushBindingPath, string labelBindingPath) + { + var dot = new Ellipse { - Width = 10, - Height = 10, + Width = 9, + Height = 9, VerticalAlignment = VerticalAlignment.Center, - [!Ellipse.FillProperty] = new Binding(nameof(vm.StatusBrush)), + [!Ellipse.FillProperty] = new Binding(brushBindingPath), }; - var statusText = new TextBlock + var text = new TextBlock { + FontSize = 12.5, + FontWeight = FontWeight.SemiBold, VerticalAlignment = VerticalAlignment.Center, - Margin = new Thickness(8, 0, 0, 0), - [!TextBlock.TextProperty] = new Binding(nameof(vm.StatusLabel)), + Margin = new Thickness(0, 0, 6, 0), + [!TextBlock.TextProperty] = new Binding(labelBindingPath), }; - var statusPanel = new StackPanel + return new StackPanel { Orientation = Orientation.Horizontal, - Children = { statusDot, statusText }, - }; - grid.Add(statusPanel, 1, 1); - - grid.Add(MakeLabel(Se.Language.General.Release), 2, 0); - var releaseText = new TextBlock - { - FontFamily = new FontFamily("Cascadia Mono,Consolas,Menlo,Monaco,monospace"), - FontSize = UiUtil.ScaledFontSize(12), - VerticalAlignment = VerticalAlignment.Center, - [!TextBlock.TextProperty] = new Binding(nameof(vm.ReleaseTag)), + Spacing = 8, + Children = { dot, text }, }; - grid.Add(releaseText, 2, 1); + } - grid.Add(MakeLabel(Se.Language.General.InstallFolder), 3, 0); - var folderText = new TextBox + /// + /// The read-only install folder with an "open folder" icon button after it. + /// + private static StackPanel MakeFolderRow(string folderBindingPath, System.Windows.Input.ICommand openFolderCommand, out TextBox folderText) + { + folderText = new TextBox { IsReadOnly = true, Width = ValueWidth, - BorderThickness = new Thickness(0), - Background = Brushes.Transparent, - Padding = new Thickness(0), + Background = EditorChrome.FieldBrush, + BorderBrush = EditorChrome.FieldBorderBrush, + CornerRadius = new CornerRadius(6), + Padding = new Thickness(8, 0), + MinHeight = 28, + Height = 28, + VerticalAlignment = VerticalAlignment.Center, VerticalContentAlignment = VerticalAlignment.Center, - FontSize = UiUtil.ScaledFontSize(12), - [!TextBox.TextProperty] = new Binding(nameof(vm.InstallFolder)), + FontSize = 12, + [!TextBox.TextProperty] = new Binding(folderBindingPath), }; - grid.Add(folderText, 3, 1); + AutomationProperties.SetName(folderText, Se.Language.General.InstallFolder); - return new Border + var openFolder = EditorChrome.CreateToolButton(IconNames.FolderOpen, Se.Language.General.OpenContainingFolder, openFolderCommand, 28); + return new StackPanel { - Child = grid, - Padding = new Thickness(14), - CornerRadius = new CornerRadius(6), - BorderThickness = new Thickness(1), - BorderBrush = new SolidColorBrush(Color.FromArgb(0x40, 0x80, 0x80, 0x80)), + Orientation = Orientation.Horizontal, + Spacing = 6, + Children = { folderText, openFolder }, }; } - private static Grid BuildActions(Qwen3TtsSettingsViewModel vm) + private static Border MakeButtonBar(Qwen3TtsSettingsViewModel vm) { - var redownload = UiUtil.MakeButton(string.Empty, vm.RedownloadCommand) - .WithIconLeftBindText(IconNames.Download, nameof(vm.DownloadButtonLabel)); - var openFolder = UiUtil.MakeButton(Se.Language.General.OpenContainingFolder, vm.OpenFolderCommand).WithIconLeft(IconNames.FolderOpen); var close = UiUtil.MakeButton(Se.Language.General.Close, vm.OkCommand); + close.Classes.Add("primary"); - var leftPanel = new StackPanel - { - Orientation = Orientation.Horizontal, - Spacing = 8, - Children = { redownload, openFolder }, - }; - - var rightPanel = new StackPanel - { - Orientation = Orientation.Horizontal, - HorizontalAlignment = HorizontalAlignment.Right, - Spacing = 8, - Children = { close }, - }; - - var grid = new Grid + return new Border { - ColumnDefinitions = + Child = new StackPanel { - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Auto) }, - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) }, - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Auto) }, + Orientation = Orientation.Horizontal, + HorizontalAlignment = HorizontalAlignment.Right, + Spacing = 10, + Children = { close }, }, + Background = EditorChrome.ButtonBarBrush, + BorderBrush = EditorChrome.LineBrush, + BorderThickness = new Thickness(0, 1, 0, 0), + Padding = new Thickness(16, 10), }; - grid.Add(leftPanel, 0, 0); - grid.Add(rightPanel, 0, 2); - return grid; } - private static TextBlock MakeLabel(string text) => new() - { - Text = text, - Opacity = 0.7, - VerticalAlignment = VerticalAlignment.Center, - }; - - private static TextBlock MakeValue(string bindingPath) => new() - { - FontWeight = FontWeight.SemiBold, - VerticalAlignment = VerticalAlignment.Center, - [!TextBlock.TextProperty] = new Binding(bindingPath), - }; - protected override void OnKeyDown(KeyEventArgs e) { base.OnKeyDown(e); diff --git a/src/ui/Features/Video/TextToSpeech/VoiceSettings/VoiceSettingsWindow.cs b/src/ui/Features/Video/TextToSpeech/VoiceSettings/VoiceSettingsWindow.cs index b6b212d3a7c..fe7d6918a36 100644 --- a/src/ui/Features/Video/TextToSpeech/VoiceSettings/VoiceSettingsWindow.cs +++ b/src/ui/Features/Video/TextToSpeech/VoiceSettings/VoiceSettingsWindow.cs @@ -1,4 +1,5 @@ using Avalonia; +using Avalonia.Automation; using Avalonia.Collections; using Avalonia.Controls; using Avalonia.Controls.Shapes; @@ -13,6 +14,11 @@ namespace Nikse.SubtitleEdit.Features.Video.TextToSpeech.VoiceSettings; +/// +/// Voice sample text and voice import, in the editor look (see ): the +/// sample text section, a drop area for importing a voice below it, import/refresh on the left of +/// the button bar and Cancel/OK on the right. +/// public class VoiceSettingsWindow : Window { private readonly VoiceSettingsViewModel _vm; @@ -30,20 +36,22 @@ public VoiceSettingsWindow(VoiceSettingsViewModel vm) MinWidth = 400; MinHeight = 320; CanResize = true; + Background = EditorChrome.WindowBrush; + EditorChrome.AddEditorStyles(Styles); _vm = vm; vm.Window = this; DataContext = vm; - var label = new Label - { - Content = Se.Language.Video.TextToSpeech.VoiceSampleText, - VerticalAlignment = VerticalAlignment.Center, - }; - var textBox = UiUtil.MakeTextBox(250, vm, nameof(vm.VoiceTestText)); textBox.Width = double.NaN; textBox.HorizontalAlignment = HorizontalAlignment.Stretch; + textBox.FontSize = 12.5; + textBox.Background = EditorChrome.FieldBrush; + textBox.BorderBrush = EditorChrome.FieldBorderBrush; + textBox.CornerRadius = new CornerRadius(6); + textBox.Padding = new Thickness(8, 6); + AutomationProperties.SetName(textBox, Se.Language.Video.TextToSpeech.VoiceSampleText); // Multi-line and growing with the window, so a longer sample text is readable (#14726). // Line breaks are fine - the sample is unbroken before it is spoken. @@ -53,19 +61,22 @@ public VoiceSettingsWindow(VoiceSettingsViewModel vm) textBox.VerticalContentAlignment = VerticalAlignment.Top; textBox.MinHeight = 60; - var dropArea = BuildDropArea(vm); + // The sample text section: header on top, the text box takes the rest of the height + var sampleHeader = EditorChrome.MakeSectionHeader(Se.Language.Video.TextToSpeech.VoiceSampleText); + sampleHeader.Margin = new Thickness(0, 0, 0, 10); + DockPanel.SetDock(sampleHeader, Dock.Top); + var sampleSection = new Border + { + Padding = new Thickness(14, 12, 14, 14), + Child = new DockPanel { Children = { sampleHeader, textBox } }, + }; - var buttonImport = UiUtil.MakeButton(Se.Language.Video.TextToSpeech.ImportVoiceDotDotDot, vm.ImportVoiceCommand).WithBindIsVisible(nameof(vm.IsImportVoiceVisible)); - var buttonRefresh = UiUtil.MakeButton(Se.Language.Video.TextToSpeech.RefreshVoices, vm.RefreshVoiceListCommand); - var buttonOk = UiUtil.MakeButtonOk(vm.OkCommand); - var buttonCancel = UiUtil.MakeButtonCancel(vm.CancelCommand); - var panelButtons = UiUtil.MakeButtonBar(buttonImport, buttonRefresh, buttonOk, buttonCancel); + var dropArea = BuildDropArea(vm); var grid = new Grid { RowDefinitions = { - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, new RowDefinition { Height = new GridLength(1, GridUnitType.Star) }, new RowDefinition { Height = new GridLength(1, GridUnitType.Star) }, new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, @@ -74,19 +85,17 @@ public VoiceSettingsWindow(VoiceSettingsViewModel vm) { new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) }, }, - Margin = UiUtil.MakeWindowMargin(), - ColumnSpacing = 10, - RowSpacing = 10, - Width = double.NaN, - HorizontalAlignment = HorizontalAlignment.Stretch, }; - _dropRow = grid.RowDefinitions[2]; + _dropRow = grid.RowDefinitions[1]; - grid.Add(label, 0, 0); - grid.Add(textBox, 1, 0); - grid.Add(dropArea, 2, 0); - grid.Add(panelButtons, 3, 0); + // The panel color behind both sections, whichever of them is showing + var panel = new Border { Background = EditorChrome.PanelBrush }; + grid.Add(panel, 0, 0); + Grid.SetRowSpan(panel, 2); + grid.Add(sampleSection, 0, 0); + grid.Add(dropArea, 1, 0); + grid.Add(MakeButtonBar(vm), 2, 0); Content = grid; @@ -105,9 +114,9 @@ private Control BuildDropArea(VoiceSettingsViewModel vm) var icon = new Icon { Value = IconNames.Import, - FontSize = 32, + FontSize = 30, HorizontalAlignment = HorizontalAlignment.Center, - Foreground = UiUtil.GetTextColor(0.55d), + Foreground = EditorChrome.FaintTextBrush, }; _dropIcon = icon; @@ -116,7 +125,8 @@ private Control BuildDropArea(VoiceSettingsViewModel vm) Text = Se.Language.Video.TextToSpeech.DropAudioFileHereToImportVoice, HorizontalAlignment = HorizontalAlignment.Center, TextAlignment = TextAlignment.Center, - Foreground = UiUtil.GetTextColor(0.8d), + TextWrapping = TextWrapping.Wrap, + FontSize = 12.5, }; var hintText = new TextBlock @@ -124,8 +134,9 @@ private Control BuildDropArea(VoiceSettingsViewModel vm) Text = Se.Language.Video.TextToSpeech.DropAudioFileHereHint, HorizontalAlignment = HorizontalAlignment.Center, TextAlignment = TextAlignment.Center, - FontSize = UiUtil.ScaledFontSize(11), - Foreground = UiUtil.GetTextColor(0.5d), + TextWrapping = TextWrapping.Wrap, + FontSize = 11.5, + Foreground = EditorChrome.DimTextBrush, }; var content = new StackPanel @@ -133,14 +144,14 @@ private Control BuildDropArea(VoiceSettingsViewModel vm) Orientation = Orientation.Vertical, HorizontalAlignment = HorizontalAlignment.Center, VerticalAlignment = VerticalAlignment.Center, - Spacing = 8, - Margin = new Thickness(24, 18), + Spacing = 6, + Margin = new Thickness(24, 14), Children = { icon, primaryText, hintText }, }; var dashRect = new Rectangle { - Stroke = UiUtil.GetTextColor(0.35d), + Stroke = EditorChrome.SeparatorBrush, StrokeThickness = 1.5, StrokeDashArray = new AvaloniaList { 4, 3 }, RadiusX = 8, @@ -160,21 +171,66 @@ private Control BuildDropArea(VoiceSettingsViewModel vm) { Background = Brushes.Transparent, CornerRadius = new CornerRadius(8), - Padding = new Thickness(16), MinHeight = 96, VerticalAlignment = VerticalAlignment.Stretch, Child = inner, }; _dropBorder = border; - DragDrop.SetAllowDrop(border, true); - border.AddHandler(DragDrop.DragOverEvent, vm.OnDragOver, RoutingStrategies.Bubble); - border.AddHandler(DragDrop.DragLeaveEvent, vm.OnDragLeave, RoutingStrategies.Bubble); - border.AddHandler(DragDrop.DropEvent, vm.OnDrop, RoutingStrategies.Bubble); + // The whole section takes drops, not only the dashed area inside its padding + var section = new Border + { + Background = Brushes.Transparent, + BorderBrush = EditorChrome.LineBrush, + BorderThickness = new Thickness(0, 1, 0, 0), + Padding = new Thickness(14), + Child = border, + }; + + DragDrop.SetAllowDrop(section, true); + section.AddHandler(DragDrop.DragOverEvent, vm.OnDragOver, RoutingStrategies.Bubble); + section.AddHandler(DragDrop.DragLeaveEvent, vm.OnDragLeave, RoutingStrategies.Bubble); + section.AddHandler(DragDrop.DropEvent, vm.OnDrop, RoutingStrategies.Bubble); - border.WithBindIsVisible(nameof(vm.IsImportVoiceVisible)); + section.WithBindIsVisible(nameof(vm.IsImportVoiceVisible)); - return border; + return section; + } + + private static Border MakeButtonBar(VoiceSettingsViewModel vm) + { + var buttonImport = UiUtil.MakeButton(Se.Language.Video.TextToSpeech.ImportVoiceDotDotDot, vm.ImportVoiceCommand) + .WithIconLeft(IconNames.Import) + .WithBindIsVisible(nameof(vm.IsImportVoiceVisible)); + var buttonRefresh = UiUtil.MakeButton(Se.Language.Video.TextToSpeech.RefreshVoices, vm.RefreshVoiceListCommand) + .WithIconLeft(IconNames.Refresh); + var left = new StackPanel { Orientation = Orientation.Horizontal, Spacing = 8, Children = { buttonImport, buttonRefresh } }; + + var buttonOk = UiUtil.MakeButtonOk(vm.OkCommand); + buttonOk.Classes.Add("primary"); + var buttonCancel = UiUtil.MakeButtonCancel(vm.CancelCommand); + var right = new StackPanel { Orientation = Orientation.Horizontal, Spacing = 10, Children = { buttonCancel, buttonOk } }; + + var grid = new Grid + { + ColumnSpacing = 10, + ColumnDefinitions = + { + new ColumnDefinition(new GridLength(1, GridUnitType.Star)), + new ColumnDefinition(GridLength.Auto), + }, + }; + grid.Add(left, 0, 0); + grid.Add(right, 0, 1); + + return new Border + { + Child = grid, + Background = EditorChrome.ButtonBarBrush, + BorderBrush = EditorChrome.LineBrush, + BorderThickness = new Thickness(0, 1, 0, 0), + Padding = new Thickness(16, 10), + }; } private void OnViewModelPropertyChanged(object? sender, PropertyChangedEventArgs e) @@ -208,17 +264,16 @@ private void ApplyDropVisualState(bool isDragOver) if (isDragOver) { - var accent = new SolidColorBrush(Color.FromRgb(0x2E, 0x8B, 0xC0)); - _dropDashRect.Stroke = accent; + _dropDashRect.Stroke = EditorChrome.AccentBrush; _dropDashRect.StrokeThickness = 2.0; - _dropIcon.Foreground = accent; - _dropBorder.Background = new SolidColorBrush(Color.FromArgb(0x22, 0x2E, 0x8B, 0xC0)); + _dropIcon.Foreground = EditorChrome.AccentBrush; + _dropBorder.Background = EditorChrome.AccentSoftBrush; } else { - _dropDashRect.Stroke = UiUtil.GetTextColor(0.35d); + _dropDashRect.Stroke = EditorChrome.SeparatorBrush; _dropDashRect.StrokeThickness = 1.5; - _dropIcon.Foreground = UiUtil.GetTextColor(0.55d); + _dropIcon.Foreground = EditorChrome.FaintTextBrush; _dropBorder.Background = Brushes.Transparent; } } diff --git a/tests/UI/Features/Video/TextToSpeech/AdvancedTtsSettingsWindowTests.cs b/tests/UI/Features/Video/TextToSpeech/AdvancedTtsSettingsWindowTests.cs index 646f464613d..80778f78bf6 100644 --- a/tests/UI/Features/Video/TextToSpeech/AdvancedTtsSettingsWindowTests.cs +++ b/tests/UI/Features/Video/TextToSpeech/AdvancedTtsSettingsWindowTests.cs @@ -1,4 +1,5 @@ using Avalonia; +using Avalonia.Automation; using Avalonia.Controls; using Avalonia.Headless.XUnit; using Avalonia.LogicalTree; @@ -45,7 +46,7 @@ public void Window_Constructs() } [AvaloniaFact] - public void GenerationFolderSection_ShowsTheConfiguredFolderAndTheSweepCheckBox() + public void GenerationFolderSection_ShowsTheConfiguredFolderAndTheSweepSwitch() { using var _ = new SettingsScope( "Video.TextToSpeech.GenerationFolder", @@ -59,10 +60,10 @@ public void GenerationFolderSection_ShowsTheConfiguredFolderAndTheSweepCheckBox( window.GetLogicalDescendants().OfType(), t => t.Text == Se.Settings.Video.TextToSpeech.GenerationFolder); - var sweepCheckBox = Assert.Single( - window.GetLogicalDescendants().OfType(), - c => IsCheckBoxFor(c, Se.Language.Video.TextToSpeech.DeleteTempFiles)); - Assert.False(sweepCheckBox.IsChecked); + var sweepSwitch = Assert.Single( + window.GetLogicalDescendants().OfType(), + c => IsSwitchFor(c, Se.Language.Video.TextToSpeech.DeleteTempFiles)); + Assert.False(sweepSwitch.IsChecked); } /// @@ -199,26 +200,26 @@ private static IEnumerable FindNumericUpDownsAfter(Window window, } /// - /// "Clean voice-clone references" has its own checkbox, which shows the stored setting. + /// "Clean voice-clone references" has its own switch, which shows the stored setting. /// [AvaloniaTheory] [InlineData(false)] [InlineData(true)] - public void CleanCloneReferences_CheckBoxShowsTheSetting(bool enabled) + public void CleanCloneReferences_SwitchShowsTheSetting(bool enabled) { using var _ = new SettingsScope("Video.TextToSpeech.CleanCloneReferences"); Se.Settings.Video.TextToSpeech.CleanCloneReferences = enabled; var window = BuildWindow(); - var checkBox = Assert.Single( - window.GetLogicalDescendants().OfType(), - c => IsCheckBoxFor(c, Se.Language.Video.TextToSpeech.CleanCloneReferences)); - Assert.Equal(enabled, checkBox.IsChecked); + var toggle = Assert.Single( + window.GetLogicalDescendants().OfType(), + c => IsSwitchFor(c, Se.Language.Video.TextToSpeech.CleanCloneReferences)); + Assert.Equal(enabled, toggle.IsChecked); } - private static bool IsCheckBoxFor(CheckBox checkBox, string text) + private static bool IsSwitchFor(ToggleSwitch toggle, string text) { - return Equals(checkBox.Content, text) || (checkBox.Content is TextBlock tb && tb.Text == text); + return AutomationProperties.GetName(toggle) == text; } } From 69647a25d3bc4c88c9893d8ab26e2d1f7b029044 Mon Sep 17 00:00:00 2001 From: niksedk Date: Thu, 15 Oct 2026 15:39:09 +0200 Subject: [PATCH 191/306] Video OCR, open from URL, shot changes and go to position windows in the editor look Co-Authored-By: Claude Opus 5.5 --- .../GoToVideoPositionWindow.cs | 53 ++- .../OpenFromUrl/DownloadVideoFromUrlWindow.cs | 45 +-- .../Video/OpenFromUrl/OpenFromUrlWindow.cs | 250 ++++++------- .../PickOnlineSubtitleWindow.cs | 43 +-- .../OpenSecondarySubtitleWindow.cs | 149 +++++--- .../Video/ShotChanges/ShotChangeListWindow.cs | 41 ++- .../Video/ShotChanges/ShotChangesWindow.cs | 154 ++++---- .../VideoOcrEngineSettingsWindow.cs | 81 ++--- .../Video/VideoOcr/VideoDialogChrome.cs | 283 +++++++++++++++ .../Features/Video/VideoOcr/VideoOcrWindow.cs | 342 +++++++++++------- 10 files changed, 900 insertions(+), 541 deletions(-) create mode 100644 src/ui/Features/Video/VideoOcr/VideoDialogChrome.cs diff --git a/src/ui/Features/Video/GoToVideoPosition/GoToVideoPositionWindow.cs b/src/ui/Features/Video/GoToVideoPosition/GoToVideoPositionWindow.cs index b2f6d8c8fb3..c4a1ce55af9 100644 --- a/src/ui/Features/Video/GoToVideoPosition/GoToVideoPositionWindow.cs +++ b/src/ui/Features/Video/GoToVideoPosition/GoToVideoPositionWindow.cs @@ -1,14 +1,19 @@ using Avalonia; +using Avalonia.Automation; using Avalonia.Controls; using Avalonia.Data; using Avalonia.Input; using Avalonia.Layout; using Nikse.SubtitleEdit.Controls; +using Nikse.SubtitleEdit.Features.Video.VideoOcr; using Nikse.SubtitleEdit.Logic; using Nikse.SubtitleEdit.Logic.Config; namespace Nikse.SubtitleEdit.Features.Video.GoToVideoPosition; +/// +/// Go to video position, in the editor look (see ). +/// public class GoToVideoPositionWindow : Window { private readonly GoToVideoPositionViewModel _vm; @@ -19,6 +24,7 @@ public GoToVideoPositionWindow(GoToVideoPositionViewModel vm) Title = Se.Language.Video.GoToVideoPosition; SizeToContent = SizeToContent.WidthAndHeight; CanResize = false; + VideoDialogChrome.Apply(this); _vm = vm; vm.Window = this; @@ -27,45 +33,36 @@ public GoToVideoPositionWindow(GoToVideoPositionViewModel vm) vm.UpDown = new TimeCodeUpDown { VerticalAlignment = VerticalAlignment.Center, + HorizontalAlignment = HorizontalAlignment.Left, [!TimeCodeUpDown.ValueProperty] = new Binding(nameof(_vm.Time)) { Mode = BindingMode.TwoWay, }, }; + AutomationProperties.SetName(vm.UpDown, Se.Language.General.VideoPosition); - var panel = new StackPanel - { - Orientation = Orientation.Horizontal, - Spacing = 15, - Margin = new Thickness(10, 20, 10, 10), - Children = - { - new Label - { - Content = Se.Language.General.VideoPosition, - VerticalAlignment = VerticalAlignment.Center, - }, - vm.UpDown, - } - }; + var section = EditorChrome.MakeEditorSection( + Se.Language.General.VideoPosition, + vm.UpDown, + first: true); + section.Background = EditorChrome.PanelBrush; + section.MinWidth = 300; - var buttonPanel = UiUtil.MakeButtonBar( - UiUtil.MakeButtonOk(vm.OkCommand), - UiUtil.MakeButtonCancel(vm.CancelCommand)); + var buttonOk = UiUtil.MakeButtonOk(vm.OkCommand).Primary(); + var buttonCancel = UiUtil.MakeButtonCancel(vm.CancelCommand); - var contentPanel = new StackPanel + var root = new Grid { - Orientation = Orientation.Vertical, - Spacing = 15, - Margin = UiUtil.MakeWindowMargin(), - Children = + RowDefinitions = { - panel, - buttonPanel, - } + new RowDefinition(GridLength.Auto), + new RowDefinition(GridLength.Auto), + }, }; + root.Add(section, 0, 0); + root.Add(VideoDialogChrome.ButtonBar(null, buttonCancel, buttonOk), 1, 0); - Content = contentPanel; + Content = root; UiUtil.FocusOnFirstActivation(this, () => { vm.Activated(); }); } @@ -75,4 +72,4 @@ protected override void OnKeyDown(KeyEventArgs e) base.OnKeyDown(e); _vm.OnKeyDown(e); } -} \ No newline at end of file +} diff --git a/src/ui/Features/Video/OpenFromUrl/DownloadVideoFromUrlWindow.cs b/src/ui/Features/Video/OpenFromUrl/DownloadVideoFromUrlWindow.cs index ab6d03087c8..a2823dae4c4 100644 --- a/src/ui/Features/Video/OpenFromUrl/DownloadVideoFromUrlWindow.cs +++ b/src/ui/Features/Video/OpenFromUrl/DownloadVideoFromUrlWindow.cs @@ -1,15 +1,17 @@ using Avalonia; using Avalonia.Controls; -using Avalonia.Controls.Primitives; using Avalonia.Data; using Avalonia.Layout; using Avalonia.Media; -using Avalonia.Styling; +using Nikse.SubtitleEdit.Features.Video.VideoOcr; using Nikse.SubtitleEdit.Logic; using Nikse.SubtitleEdit.Logic.Config; namespace Nikse.SubtitleEdit.Features.Video.OpenFromUrl; +/// +/// Download progress for "Open from URL", in the editor look (see ). +/// public class DownloadVideoFromUrlWindow : Window { public DownloadVideoFromUrlWindow(DownloadVideoFromUrlViewModel vm) @@ -21,37 +23,31 @@ public DownloadVideoFromUrlWindow(DownloadVideoFromUrlViewModel vm) CanResize = false; Width = 480; WindowStartupLocation = WindowStartupLocation.CenterOwner; + VideoDialogChrome.Apply(this); DataContext = vm; - var heading = new TextBlock - { - Text = Se.Language.Video.OpenFromUrlDownloadingTitle, - FontSize = UiUtil.ScaledFontSize(18), - FontWeight = FontWeight.SemiBold, - }; - var fileName = new TextBlock { - Opacity = 0.75, + FontSize = 12.5, TextWrapping = TextWrapping.Wrap, [!TextBlock.TextProperty] = new Binding(nameof(vm.FileNameDisplay)), }; var progressBar = UiUtil.MakeProgressBar(); - progressBar.MinWidth = 400; - progressBar.Margin = new Thickness(0, 4, 0, 0); + progressBar.HorizontalAlignment = HorizontalAlignment.Stretch; + progressBar.Margin = new Thickness(0, 2, 0, 0); progressBar.Bind(ProgressBar.ValueProperty, new Binding(nameof(vm.Progress))); // Post-processing reports nothing, so the bar animates instead of sitting frozen at 100%. progressBar.Bind(ProgressBar.IsIndeterminateProperty, new Binding(nameof(vm.IsProgressIndeterminate))); - var statusText = new TextBlock { Opacity = 0.85 }; - statusText.Bind(TextBlock.TextProperty, new Binding(nameof(vm.StatusText))); + var statusText = VideoDialogChrome.Hint(nameof(vm.StatusText)); var errorText = new TextBlock { Foreground = new SolidColorBrush(Color.FromRgb(0xC0, 0x39, 0x2B)), TextWrapping = TextWrapping.Wrap, + FontSize = 12.5, }; errorText.Bind(TextBlock.TextProperty, new Binding(nameof(vm.Error))); errorText.Bind(IsVisibleProperty, new Binding(nameof(vm.Error)) @@ -59,23 +55,30 @@ public DownloadVideoFromUrlWindow(DownloadVideoFromUrlViewModel vm) Converter = new Avalonia.Data.Converters.FuncValueConverter(s => !string.IsNullOrEmpty(s)), }); - var buttonCancel = UiUtil.MakeButtonCancel(vm.CommandCancelCommand); - var buttonBar = UiUtil.MakeButtonBar(buttonCancel); - - Content = new StackPanel + var rows = new StackPanel { Spacing = 8, - Margin = UiUtil.MakeWindowMargin(), Children = { - heading, fileName, progressBar, statusText, errorText, - buttonBar, }, }; + var section = EditorChrome.MakeEditorSection(Se.Language.Video.OpenFromUrlDownloadingTitle, rows, first: true); + section.Background = EditorChrome.PanelBrush; + section.Width = 480; // SizeToContent ignores the window Width: fixed here so the bar has room and long names wrap + + var buttonCancel = UiUtil.MakeButtonCancel(vm.CommandCancelCommand); + + var root = new Grid + { + RowDefinitions = { new RowDefinition(GridLength.Auto), new RowDefinition(GridLength.Auto) }, + }; + root.Add(section, 0, 0); + root.Add(VideoDialogChrome.ButtonBar(null, buttonCancel), 1, 0); + Content = root; Loaded += delegate { diff --git a/src/ui/Features/Video/OpenFromUrl/OpenFromUrlWindow.cs b/src/ui/Features/Video/OpenFromUrl/OpenFromUrlWindow.cs index d71f5e8ff22..155018909fc 100644 --- a/src/ui/Features/Video/OpenFromUrl/OpenFromUrlWindow.cs +++ b/src/ui/Features/Video/OpenFromUrl/OpenFromUrlWindow.cs @@ -1,22 +1,26 @@ -using Avalonia; +using Avalonia; using Avalonia.Automation; using Avalonia.Controls; +using Avalonia.Controls.Presenters; using Avalonia.Data; using Avalonia.Input; using Avalonia.Layout; using Avalonia.Media; using Avalonia.Styling; using CommunityToolkit.Mvvm.Input; +using Nikse.SubtitleEdit.Features.Video.VideoOcr; using Nikse.SubtitleEdit.Logic; using Nikse.SubtitleEdit.Logic.Config; namespace Nikse.SubtitleEdit.Features.Video.OpenFromUrl; +/// +/// Open a video from a URL, in the editor look (see ): the URL, the +/// two ways to open it as cards, and the download options. +/// public class OpenFromUrlWindow : Window { private const double CardCornerRadius = 8; - private const double CardPadding = 16; - private const double CardSpacing = 12; private readonly OpenFromUrlViewModel _vm; private readonly TextBox _urlTextBox; @@ -29,79 +33,24 @@ public OpenFromUrlWindow(OpenFromUrlViewModel vm) CanResize = false; Width = 520; WindowStartupLocation = WindowStartupLocation.CenterOwner; + VideoDialogChrome.Apply(this); + AddStyles(); _vm = vm; vm.Window = this; DataContext = vm; - var heading = new TextBlock - { - Text = Se.Language.Video.OpenFromUrlTitle, - FontSize = UiUtil.ScaledFontSize(18), - FontWeight = FontWeight.SemiBold, - Margin = new Thickness(0, 0, 0, 4), - }; - - var urlLabel = new TextBlock - { - Text = Se.Language.General.Url, - Opacity = 0.75, - Margin = new Thickness(0, 4, 0, 4), - }; - _urlTextBox = new TextBox { PlaceholderText = "https://...", HorizontalAlignment = HorizontalAlignment.Stretch, [!TextBox.TextProperty] = new Binding(nameof(vm.Url)) { Mode = BindingMode.TwoWay }, }; - - var downloadVideoCheckBox = new CheckBox - { - Content = Se.Language.Video.OpenFromUrlDownloadVideo, - HorizontalAlignment = HorizontalAlignment.Left, - Margin = new Thickness(0, 4, 0, 0), - [!CheckBox.IsCheckedProperty] = new Binding(nameof(vm.DownloadVideo)) { Mode = BindingMode.TwoWay }, - }; - - var downloadSubtitlesCheckBox = new CheckBox - { - Content = Se.Language.Video.OpenFromUrlDownloadSubtitles, - HorizontalAlignment = HorizontalAlignment.Left, - [!CheckBox.IsCheckedProperty] = new Binding(nameof(vm.DownloadSubtitles)) { Mode = BindingMode.TwoWay }, - }; - - var includeAutoGeneratedCheckBox = new CheckBox - { - Content = Se.Language.Video.OpenFromUrlIncludeAutoGeneratedSubtitles, - HorizontalAlignment = HorizontalAlignment.Left, - Margin = new Thickness(24, 0, 0, 4), - [!CheckBox.IsCheckedProperty] = new Binding(nameof(vm.IncludeAutoGeneratedSubtitles)) { Mode = BindingMode.TwoWay }, - [!Control.IsEnabledProperty] = new Binding(nameof(vm.DownloadSubtitles)), - }; - - var cookiesLabel = new TextBlock - { - Text = Se.Language.Video.OpenFromUrlCookiesFromBrowser, - VerticalAlignment = VerticalAlignment.Center, - Margin = new Thickness(0, 0, 8, 0), - }; - var cookiesComboBox = UiUtil.MakeComboBox(vm.CookiesBrowsers, vm, nameof(vm.SelectedCookiesBrowser)); - cookiesComboBox.MinWidth = 140; - cookiesComboBox[AutomationProperties.NameProperty] = Se.Language.Video.OpenFromUrlCookiesFromBrowser; - var cookiesRow = new StackPanel - { - Orientation = Orientation.Horizontal, - Margin = new Thickness(0, 4, 0, 4), - Children = { cookiesLabel, cookiesComboBox }, - }; - if (Se.Settings.Appearance.ShowHints) - { - ToolTip.SetTip(cookiesRow, Se.Language.Video.OpenFromUrlCookiesFromBrowserHint); - } + _urlTextBox.Classes.Add("text"); + AutomationProperties.SetName(_urlTextBox, Se.Language.General.Url); var openOnlineCard = BuildCard( - "▶", // ▶ + "fa-solid fa-play", Se.Language.Video.OpenFromUrlOpenOnline, Se.Language.Video.OpenFromUrlOpenOnlineDescription, Se.Language.Video.OpenFromUrlOpenOnlineNote, @@ -109,36 +58,57 @@ public OpenFromUrlWindow(OpenFromUrlViewModel vm) isRecommended: false); var downloadCard = BuildCard( - "⬇", // ⬇ + "fa-solid fa-download", Se.Language.Video.OpenFromUrlDownloadAndOpen, Se.Language.Video.OpenFromUrlDownloadAndOpenDescription, Se.Language.Video.OpenFromUrlDownloadAndOpenNote, vm.DownloadAndOpenCommand, isRecommended: true); - var cancelBar = UiUtil.MakeButtonBar(UiUtil.MakeButtonCancel(vm.CancelCommand)); + var urlSection = new StackPanel { Spacing = 12 }; + urlSection.Children.Add(_urlTextBox); + urlSection.Children.Add(openOnlineCard); + urlSection.Children.Add(downloadCard); + + var downloadVideo = VideoDialogChrome.SwitchRow(nameof(vm.DownloadVideo), Se.Language.Video.OpenFromUrlDownloadVideo); + var downloadSubtitles = VideoDialogChrome.SwitchRow(nameof(vm.DownloadSubtitles), Se.Language.Video.OpenFromUrlDownloadSubtitles); + var includeAutoGenerated = VideoDialogChrome.SwitchRow(nameof(vm.IncludeAutoGeneratedSubtitles), Se.Language.Video.OpenFromUrlIncludeAutoGeneratedSubtitles); + includeAutoGenerated.Margin = new Thickness(24, 0, 0, 0); + includeAutoGenerated.Bind(IsEnabledProperty, new Binding(nameof(vm.DownloadSubtitles))); - var content = new StackPanel + var cookiesComboBox = UiUtil.MakeComboBox(vm.CookiesBrowsers, vm, nameof(vm.SelectedCookiesBrowser)); + cookiesComboBox.MinWidth = 140; + cookiesComboBox.HorizontalAlignment = HorizontalAlignment.Left; + cookiesComboBox[AutomationProperties.NameProperty] = Se.Language.Video.OpenFromUrlCookiesFromBrowser; + var cookiesRow = VideoDialogChrome.Row(Se.Language.Video.OpenFromUrlCookiesFromBrowser, cookiesComboBox, 150); + if (Se.Settings.Appearance.ShowHints) { - Margin = UiUtil.MakeWindowMargin(), - Spacing = 8, - Children = - { - heading, - urlLabel, - _urlTextBox, - new Border { Height = 4 }, // spacer - openOnlineCard, - downloadCard, - downloadVideoCheckBox, - downloadSubtitlesCheckBox, - includeAutoGeneratedCheckBox, - cookiesRow, - cancelBar, - }, + ToolTip.SetTip(cookiesRow, Se.Language.Video.OpenFromUrlCookiesFromBrowserHint); + } + + var options = new StackPanel { Spacing = 8 }; + options.Children.Add(downloadVideo); + options.Children.Add(downloadSubtitles); + options.Children.Add(includeAutoGenerated); + options.Children.Add(cookiesRow); + + var sections = new StackPanel { Background = EditorChrome.PanelBrush }; + sections.Children.Add(EditorChrome.MakeEditorSection(Se.Language.General.Url, urlSection, first: true)); + sections.Children.Add(EditorChrome.MakeEditorSection(Se.Language.General.Options, options)); + + var root = new Grid + { + RowDefinitions = { new RowDefinition(new GridLength(1, GridUnitType.Star)), new RowDefinition(GridLength.Auto) }, }; + root.Add(new ScrollViewer + { + Content = sections, + VerticalScrollBarVisibility = Avalonia.Controls.Primitives.ScrollBarVisibility.Auto, + HorizontalScrollBarVisibility = Avalonia.Controls.Primitives.ScrollBarVisibility.Disabled, + }, 0, 0); + root.Add(VideoDialogChrome.ButtonBar(null, UiUtil.MakeButtonCancel(vm.CancelCommand)), 1, 0); - Content = content; + Content = root; // Focus the URL box on first activation (the pattern used by the other windows) - // the Loaded + Input-priority post this used to do never stuck on macOS. Guarded so @@ -151,60 +121,93 @@ public OpenFromUrlWindow(OpenFromUrlViewModel vm) }); } - private Button BuildCard(string iconGlyph, string title, string description, string note, IRelayCommand command, bool isRecommended) + /// + /// The cards: field-colored, a hover tint, and an accent border on hover. + /// + private void AddStyles() { - var accent = isRecommended ? AccentBrush(0.9) : UiUtil.GetBorderBrush(); - var hoverAccent = AccentBrush(1.0); + Styles.Add(new Style(x => x.OfType private static void AddLabeledSetting(Panel panel, string label, Control control) { - var labelControl = UiUtil.MakeLabel(label); + var labelControl = EditorChrome.MakeDimLabel(label, new Thickness(0)); panel.Children.Add(labelControl); panel.Children.Add(control.WithLabeledBy(labelControl)); } - private static Border MakeLinesView(VideoOcrViewModel vm) + private static Grid MakeLinesView(VideoOcrViewModel vm) { var fullTimeConverter = new TimeSpanToDisplayFullConverter(); var shortTimeConverter = new TimeSpanToDisplayShortConverter(); @@ -685,7 +754,7 @@ e.Source is not TextBox && } }, RoutingStrategies.Tunnel); - return UiUtil.MakeBorderForControl(tableView); + return VideoDialogChrome.ListSection(Se.Language.General.Lines, tableView); } /// @@ -721,36 +790,47 @@ internal static void RefocusWhenReEnabled(Button button) }; } - private static Grid MakeProgressView(VideoOcrViewModel vm) + /// + /// Progress bar and status in a strip above the buttons while the OCR runs. + /// + private static Border MakeProgressView(VideoOcrViewModel vm) { var progressBar = UiUtil.MakeProgressBar(); + progressBar.VerticalAlignment = VerticalAlignment.Center; progressBar.Bind(ProgressBar.ValueProperty, new Binding(nameof(vm.ProgressValue))); - progressBar.Bind(ProgressBar.IsVisibleProperty, new Binding(nameof(vm.IsRunning))); progressBar.DataContext = vm; - var statusText = new TextBlock - { - Margin = new Thickness(5, 0, 0, 0), - DataContext = vm, - }; - statusText.Bind(TextBlock.TextProperty, new Binding(nameof(vm.ProgressText))); + var statusText = VideoDialogChrome.Hint(nameof(vm.ProgressText)); + statusText.DataContext = vm; + statusText.MaxWidth = 420; - // Bar and text each get their own row - overlaying them in one cell with a fixed - // top margin on the text made the two collide. var grid = new Grid { - RowDefinitions = + ColumnSpacing = 12, + ColumnDefinitions = { - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, // progress bar - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, // status text + new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) }, // progress bar + new ColumnDefinition { Width = new GridLength(1, GridUnitType.Auto) }, // status text }, - RowSpacing = 3, HorizontalAlignment = HorizontalAlignment.Stretch, }; grid.Add(progressBar, 0, 0); - grid.Add(statusText, 1, 0); + grid.Add(statusText, 0, 1); - return grid; + var strip = VideoDialogChrome.ProgressStrip(grid); + strip.Bind(IsVisibleProperty, new Binding(nameof(vm.IsRunning))); + return strip; + } + + /// + /// The status (test result, lines found) in the button bar while the OCR is not running - + /// while it runs the text is in the progress strip. + /// + private static TextBlock MakeStatusText(VideoOcrViewModel vm) + { + var statusText = VideoDialogChrome.Hint(nameof(vm.ProgressText)); + statusText.Bind(IsVisibleProperty, new Binding(nameof(vm.IsRunning)) { Converter = InverseBooleanConverter.Instance }); + return statusText; } } From cab1f6647cf951b9a98bbf5b02a12abb5348486d Mon Sep 17 00:00:00 2001 From: niksedk Date: Thu, 15 Oct 2026 15:40:04 +0200 Subject: [PATCH 192/306] TTS voice manager, actor voices and license windows in the editor look Co-Authored-By: Claude Opus 5.5 --- .../ActorVoices/ActorVoiceMappingWindow.cs | 70 ++++-- .../ActorVoiceRowSettingsWindow.cs | 121 ++++++---- .../DownloadTts/DownloadTtsWindow.cs | 59 +++-- .../IndexTts25LicenseWindow.cs | 87 ++----- .../ModelLicense/ModelLicenseWindow.cs | 87 ++----- .../VoiceCloneConsentWindow.cs | 88 ++----- .../VoiceManager/TtsVoicesChrome.cs | 223 ++++++++++++++++++ .../VoiceManager/VoiceManagerWindow.cs | 172 ++++++++------ .../VoicePacks/DownloadVoicePacksWindow.cs | 85 ++++--- 9 files changed, 605 insertions(+), 387 deletions(-) create mode 100644 src/ui/Features/Video/TextToSpeech/VoiceManager/TtsVoicesChrome.cs diff --git a/src/ui/Features/Video/TextToSpeech/ActorVoices/ActorVoiceMappingWindow.cs b/src/ui/Features/Video/TextToSpeech/ActorVoices/ActorVoiceMappingWindow.cs index f0e8c5a3fc3..d668d1d41b0 100644 --- a/src/ui/Features/Video/TextToSpeech/ActorVoices/ActorVoiceMappingWindow.cs +++ b/src/ui/Features/Video/TextToSpeech/ActorVoices/ActorVoiceMappingWindow.cs @@ -8,6 +8,7 @@ using Avalonia.Media; using Nikse.SubtitleEdit.Features.Video.TextToSpeech.Engines; using Nikse.SubtitleEdit.Features.Video.TextToSpeech.Voices; +using Nikse.SubtitleEdit.Features.Video.TextToSpeech.VoiceManager; using Nikse.SubtitleEdit.Logic; using Nikse.SubtitleEdit.Logic.Config; using Nikse.SubtitleEdit.Logic.ValueConverters; @@ -28,7 +29,7 @@ namespace Nikse.SubtitleEdit.Features.Video.TextToSpeech.ActorVoices; // │ │ Maria │ ElevenLabs│ Rachel │ │ ▶ │ │ // │ └───────────┴───────────┴───────────┴────────────────────────┴─────┘ │ // ├──────────────────────────────────────────────────────────────────────┤ -// │ [Apply default to all] [Clear all] [OK] [Cancel] │ +// │ [Apply default to all] [Clear all] [Cancel] [OK] │ // └──────────────────────────────────────────────────────────────────────┘ public class ActorVoiceMappingWindow : Window { @@ -43,6 +44,7 @@ public ActorVoiceMappingWindow(ActorVoiceMappingViewModel vm) MinWidth = 700; MinHeight = 400; CanResize = true; + TtsVoicesChrome.Apply(this); _vm = vm; vm.Window = this; @@ -52,6 +54,7 @@ public ActorVoiceMappingWindow(ActorVoiceMappingViewModel vm) var grid = BuildGrid(vm); var buttons = BuildButtons(vm); + // The grid gives up height first, so the button bar stays reachable on a short screen. var root = new Grid { RowDefinitions = @@ -64,14 +67,17 @@ public ActorVoiceMappingWindow(ActorVoiceMappingViewModel vm) { new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) }, }, - Margin = UiUtil.MakeWindowMargin(), - RowSpacing = 12, HorizontalAlignment = HorizontalAlignment.Stretch, VerticalAlignment = VerticalAlignment.Stretch, }; root.Add(header, 0, 0); - root.Add(grid, 1, 0); + root.Add(new Border + { + Background = EditorChrome.PanelBrush, + Padding = new Thickness(14, 12, 14, 14), + Child = grid, + }, 1, 0); root.Add(buttons, 2, 0); Content = root; @@ -84,15 +90,15 @@ private static Control BuildHeader(ActorVoiceMappingViewModel vm) var icon = new Icon { Value = IconNames.PoliceBadge, - FontSize = 28, - Foreground = UiUtil.GetTextColor(0.8d), + FontSize = 24, + Foreground = EditorChrome.IconBrush, VerticalAlignment = VerticalAlignment.Center, }; var title = new TextBlock { Text = Se.Language.Video.TextToSpeech.ActorVoicesTitle, - FontSize = UiUtil.ScaledFontSize(16), + FontSize = 16, FontWeight = FontWeight.SemiBold, VerticalAlignment = VerticalAlignment.Center, }; @@ -100,15 +106,15 @@ private static Control BuildHeader(ActorVoiceMappingViewModel vm) var subtitle = new TextBlock { Text = Se.Language.Video.TextToSpeech.ActorVoicesSubtitle, - FontSize = UiUtil.ScaledFontSize(12), - Foreground = UiUtil.GetTextColor(0.6d), + FontSize = 12.5, + Foreground = EditorChrome.DimTextBrush, TextWrapping = TextWrapping.Wrap, }; var status = new TextBlock { - FontSize = UiUtil.ScaledFontSize(11), - Foreground = UiUtil.GetTextColor(0.55d), + FontSize = 11.5, + Foreground = EditorChrome.FaintTextBrush, VerticalAlignment = VerticalAlignment.Center, Margin = new Thickness(0, 4, 0, 0), [!TextBlock.TextProperty] = new Binding(nameof(vm.StatusText)) { Mode = BindingMode.OneWay }, @@ -132,7 +138,10 @@ private static Control BuildHeader(ActorVoiceMappingViewModel vm) return new Border { - Padding = new Thickness(4, 0), + Background = EditorChrome.PanelBrush, + BorderBrush = EditorChrome.LineBrush, + BorderThickness = new Thickness(0, 0, 0, 1), + Padding = new Thickness(16, 12), Child = row, }; } @@ -142,12 +151,21 @@ private static Border BuildGrid(ActorVoiceMappingViewModel vm) var tableView = TableViewExtras.MakeTableView(multiSelect: false); tableView[!TableView.ItemsSourceProperty] = new Binding(nameof(vm.Rows)); tableView[!TableView.SelectedItemProperty] = new Binding(nameof(vm.SelectedRow)) { Mode = BindingMode.TwoWay }; + tableView.Margin = new Thickness(2); tableView.Columns.Add(new SeTableViewColumn { Header = Se.Language.Video.TextToSpeech.ActorOrVoice, - Binding = new Binding(nameof(ActorVoiceRow.Actor)), + // Centered like the combo boxes in the other cells of the row + NameBinding = new Binding(nameof(ActorVoiceRow.Actor)), + CellTemplate = new FuncDataTemplate((_, _) => new TextBlock + { + VerticalAlignment = VerticalAlignment.Center, + Margin = new Thickness(4, 0), + TextTrimming = TextTrimming.CharacterEllipsis, + [!TextBlock.TextProperty] = new Binding(nameof(ActorVoiceRow.Actor)) { Mode = BindingMode.OneWay }, + }, true), Width = new GridLength(180), - CellTheme = UiUtil.TableViewCellTheme, + CellTheme = UiUtil.TableViewNoPaddingCellTheme, HeaderTheme = UiUtil.TableViewColumnHeaderTheme, }); tableView.Columns.Add(new SeTableViewColumn @@ -280,18 +298,16 @@ private static Border BuildGrid(ActorVoiceMappingViewModel vm) // Per-row "..." button → opens ActorVoiceRowSettingsWindow for voice // instruction + OmniVoice keyword picker. Disabled for engines that have // no advanced settings so the user doesn't open an empty dialog. - var settingsBtn = UiUtil.MakeButton(vm.ShowRowSettingsCommand, IconNames.Settings, - Se.Language.Video.TextToSpeech.ActorVoicesRowSettingsTitle); + var settingsBtn = EditorChrome.CreateToolButton("fa-solid fa-sliders", + Se.Language.Video.TextToSpeech.ActorVoicesRowSettingsTitle, vm.ShowRowSettingsCommand, 28); settingsBtn.CommandParameter = row; - settingsBtn.Margin = new Thickness(4, 2); settingsBtn.Bind(InputElement.IsEnabledProperty, new Binding(nameof(ActorVoiceRow.HasAdvancedSettings)) { Mode = BindingMode.OneWay, }); - var testBtn = UiUtil.MakeButton(vm.TestRowCommand, "fa-solid fa-play", Se.Language.Video.TextToSpeech.TestVoice); + var testBtn = EditorChrome.CreateToolButton("fa-solid fa-play", Se.Language.Video.TextToSpeech.TestVoice, vm.TestRowCommand, 28); testBtn.CommandParameter = row; - testBtn.Margin = new Thickness(4, 2); testBtn.Bind(InputElement.IsEnabledProperty, new Binding(nameof(ActorVoiceRow.SelectedVoice)) { Mode = BindingMode.OneWay, @@ -302,6 +318,7 @@ private static Border BuildGrid(ActorVoiceMappingViewModel vm) { Orientation = Orientation.Horizontal, HorizontalAlignment = HorizontalAlignment.Center, + VerticalAlignment = VerticalAlignment.Center, Spacing = 4, Children = { settingsBtn, testBtn }, }; @@ -309,17 +326,22 @@ private static Border BuildGrid(ActorVoiceMappingViewModel vm) Width = new GridLength(110), }); - return UiUtil.MakeBorderForControl(tableView); + return TtsVoicesChrome.MakeListFrame(tableView); } - private static StackPanel BuildButtons(ActorVoiceMappingViewModel vm) + /// + /// Apply default / clear on the left, Cancel and OK on the right. + /// + private static Border BuildButtons(ActorVoiceMappingViewModel vm) { - var applyDefault = UiUtil.MakeButton(Se.Language.Video.TextToSpeech.ApplyDefaultToAll, vm.ApplyDefaultToAllCommand); - var clearAll = UiUtil.MakeButton(Se.Language.General.Clear, vm.ClearAllCommand); + var applyDefault = TtsVoicesChrome.MakeAction("fa-solid fa-wand-magic-sparkles", Se.Language.Video.TextToSpeech.ApplyDefaultToAll, vm.ApplyDefaultToAllCommand); + var clearAll = TtsVoicesChrome.MakeAction("fa-solid fa-eraser", Se.Language.General.Clear, vm.ClearAllCommand); var ok = UiUtil.MakeButtonOk(vm.OkCommand); + ok.Classes.Add("primary"); var cancel = UiUtil.MakeButtonCancel(vm.CancelCommand); - return UiUtil.MakeButtonBar(applyDefault, clearAll, ok, cancel); + var actions = new StackPanel { Orientation = Orientation.Horizontal, Spacing = 2, Margin = new Thickness(-10, 0, 0, 0), Children = { applyDefault, clearAll } }; + return TtsVoicesChrome.MakeButtonBar(actions, cancel, ok); } protected override void OnKeyDown(KeyEventArgs e) diff --git a/src/ui/Features/Video/TextToSpeech/ActorVoices/ActorVoiceRowSettingsWindow.cs b/src/ui/Features/Video/TextToSpeech/ActorVoices/ActorVoiceRowSettingsWindow.cs index cf2021e610c..2d2d4e7f172 100644 --- a/src/ui/Features/Video/TextToSpeech/ActorVoices/ActorVoiceRowSettingsWindow.cs +++ b/src/ui/Features/Video/TextToSpeech/ActorVoices/ActorVoiceRowSettingsWindow.cs @@ -1,5 +1,6 @@ using System.Collections.ObjectModel; using Avalonia; +using Avalonia.Automation; using Avalonia.Controls; using Avalonia.Data; using Avalonia.Input; @@ -7,6 +8,7 @@ using Avalonia.Layout; using Avalonia.Media; using Nikse.SubtitleEdit.Features.Video.TextToSpeech.Engines; +using Nikse.SubtitleEdit.Features.Video.TextToSpeech.VoiceManager; using Nikse.SubtitleEdit.Logic; using Nikse.SubtitleEdit.Logic.Config; @@ -26,9 +28,9 @@ namespace Nikse.SubtitleEdit.Features.Video.TextToSpeech.ActorVoices; // │ Voice design (OmniVoice only) │ // │ Gender [Female ▾] Age [Adult ▾] │ // │ Pitch [Low ▾] Accent [British ▾] │ -// │ ☐ Whisper │ +// │ (o) Whisper │ // ├────────────────────────────────────────────────────────┤ -// │ [OK] [Cancel] │ +// │ [Cancel] [OK] │ // └────────────────────────────────────────────────────────┘ public class ActorVoiceRowSettingsWindow : Window { @@ -41,6 +43,7 @@ public ActorVoiceRowSettingsWindow(ActorVoiceRowSettingsViewModel vm) SizeToContent = SizeToContent.WidthAndHeight; CanResize = false; MinWidth = 460; + TtsVoicesChrome.Apply(this); _vm = vm; vm.Window = this; @@ -51,18 +54,36 @@ public ActorVoiceRowSettingsWindow(ActorVoiceRowSettingsViewModel vm) var omniBlock = BuildOmniVoicePicker(vm); var buttonOk = UiUtil.MakeButtonOk(vm.OkCommand); + buttonOk.Classes.Add("primary"); var buttonCancel = UiUtil.MakeButtonCancel(vm.CancelCommand); - var buttons = UiUtil.MakeButtonBar(buttonOk, buttonCancel); + var buttons = TtsVoicesChrome.MakeButtonBar(null, buttonCancel, buttonOk); - var stack = new StackPanel + var sections = new StackPanel { Orientation = Orientation.Vertical, - Spacing = 12, - Margin = UiUtil.MakeWindowMargin(), - Children = { header, instructionBlock, omniBlock, buttons }, + Children = { instructionBlock, omniBlock }, }; - Content = stack; + // Sections scroll on a short screen, so the buttons stay reachable. + var root = new Grid + { + RowDefinitions = + { + new RowDefinition(GridLength.Auto), + new RowDefinition(new GridLength(1, GridUnitType.Star)), + new RowDefinition(GridLength.Auto), + }, + }; + root.Add(header, 0, 0); + root.Add(new ScrollViewer + { + Content = new Border { Background = EditorChrome.PanelBrush, Child = sections }, + VerticalScrollBarVisibility = Avalonia.Controls.Primitives.ScrollBarVisibility.Auto, + HorizontalScrollBarVisibility = Avalonia.Controls.Primitives.ScrollBarVisibility.Disabled, + }, 1, 0); + root.Add(buttons, 2, 0); + + Content = root; Loaded += (_, _) => UiUtil.RestoreWindowPosition(this); } @@ -71,7 +92,7 @@ private static Control BuildHeader(ActorVoiceRowSettingsViewModel vm) { var title = new TextBlock { - FontSize = UiUtil.ScaledFontSize(15), + FontSize = 15, FontWeight = FontWeight.SemiBold, [!TextBlock.TextProperty] = new Binding(nameof(vm.Actor)) { @@ -82,8 +103,8 @@ private static Control BuildHeader(ActorVoiceRowSettingsViewModel vm) var sub = new TextBlock { - FontSize = UiUtil.ScaledFontSize(11), - Foreground = UiUtil.GetTextColor(0.6d), + FontSize = 12, + Foreground = EditorChrome.DimTextBrush, [!TextBlock.TextProperty] = new MultiBinding { StringFormat = "{0} / {1}", @@ -95,23 +116,29 @@ private static Control BuildHeader(ActorVoiceRowSettingsViewModel vm) }, }; - return new StackPanel { Orientation = Orientation.Vertical, Spacing = 2, Children = { title, sub } }; + return new Border + { + Background = EditorChrome.PanelBrush, + BorderBrush = EditorChrome.LineBrush, + BorderThickness = new Thickness(0, 0, 0, 1), + Padding = new Thickness(16, 12), + Child = new StackPanel { Orientation = Orientation.Vertical, Spacing = 2, Children = { title, sub } }, + }; } private static Border BuildInstructionBlock(ActorVoiceRowSettingsViewModel vm) { - var label = new Label - { - Content = Se.Language.Video.TextToSpeech.VoiceInstruction, - FontWeight = FontWeight.SemiBold, - }; - var textBox = new TextBox { AcceptsReturn = true, TextWrapping = TextWrapping.Wrap, Height = 90, Width = double.NaN, + MinWidth = 420, + FontSize = 12.5, + Background = EditorChrome.FieldBrush, + BorderBrush = EditorChrome.FieldBorderBrush, + CornerRadius = new CornerRadius(6), HorizontalAlignment = HorizontalAlignment.Stretch, VerticalContentAlignment = VerticalAlignment.Top, PlaceholderText = Se.Language.Video.TextToSpeech.VoiceInstructionHint, @@ -121,35 +148,26 @@ private static Border BuildInstructionBlock(ActorVoiceRowSettingsViewModel vm) UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged, }, }; + AutomationProperties.SetName(textBox, Se.Language.Video.TextToSpeech.VoiceInstruction); - var hint = new TextBlock - { - Text = Se.Language.Video.TextToSpeech.VoiceInstructionFreeTextHint, - FontSize = UiUtil.ScaledFontSize(11), - Foreground = UiUtil.GetTextColor(0.55d), - TextWrapping = TextWrapping.Wrap, - }; + var hint = TtsVoicesChrome.MakeHint(Se.Language.Video.TextToSpeech.VoiceInstructionFreeTextHint); + hint.MaxWidth = 430; + hint.HorizontalAlignment = HorizontalAlignment.Left; var inner = new StackPanel { Orientation = Orientation.Vertical, Spacing = 6, - Children = { label, textBox, hint }, + Children = { textBox, hint }, }; - var border = UiUtil.MakeBorderForControl(inner); + var border = EditorChrome.MakeEditorSection(Se.Language.Video.TextToSpeech.VoiceInstruction, inner, first: true); border.Bind(Visual.IsVisibleProperty, new Binding(nameof(vm.IsInstructionTextVisible)) { Mode = BindingMode.OneWay }); return border; } private static Border BuildOmniVoicePicker(ActorVoiceRowSettingsViewModel vm) { - var label = new Label - { - Content = Se.Language.Video.TextToSpeech.VoiceDesign, - FontWeight = FontWeight.SemiBold, - }; - var grid = new Grid { ColumnDefinitions = @@ -175,21 +193,19 @@ private static Border BuildOmniVoicePicker(ActorVoiceRowSettingsViewModel vm) AddPickerRow(grid, 2, Se.Language.Video.TextToSpeech.VoicePitch, vm, vm.OmniVoicePitches, nameof(vm.SelectedOmniVoicePitch)); AddPickerRow(grid, 3, Se.Language.Video.TextToSpeech.VoiceAccent, vm, vm.OmniVoiceAccents, nameof(vm.SelectedOmniVoiceAccent)); - var whisper = new CheckBox - { - // Use the engine's own constant — same label as the main TTS window and Review window. - Content = OmniVoiceTtsCpp.InstructionWhisper, - Margin = new Thickness(0, 4, 0, 0), - [!CheckBox.IsCheckedProperty] = new Binding(nameof(vm.OmniVoiceWhisper)) { Mode = BindingMode.TwoWay }, - }; + // Use the engine's own constant — same label as the main TTS window and Review window. + var whisper = EditorChrome.MakeToggleRow(nameof(vm.OmniVoiceWhisper), OmniVoiceTtsCpp.InstructionWhisper); + whisper.Margin = new Thickness(0, 4, 0, 0); grid.Add(whisper, 4, 0, 1, 2); var clonedNote = new TextBlock { Text = Se.Language.Video.TextToSpeech.VoiceInstructionClonedVoiceNote, TextWrapping = TextWrapping.Wrap, - FontSize = UiUtil.ScaledFontSize(11), - Foreground = UiUtil.GetTextColor(0.7d), + FontSize = 11.5, + Foreground = EditorChrome.DimTextBrush, + MaxWidth = 430, + HorizontalAlignment = HorizontalAlignment.Left, Margin = new Thickness(0, 6, 0, 0), [!Visual.IsVisibleProperty] = new Binding(nameof(vm.IsClonedVoiceNoteVisible)) { Mode = BindingMode.OneWay }, }; @@ -198,10 +214,16 @@ private static Border BuildOmniVoicePicker(ActorVoiceRowSettingsViewModel vm) { Orientation = Orientation.Vertical, Spacing = 6, - Children = { label, grid, clonedNote }, + Children = { grid, clonedNote }, }; - var border = UiUtil.MakeBorderForControl(inner); + // A top line only when the instruction section is shown above it. + var border = EditorChrome.MakeEditorSection(Se.Language.Video.TextToSpeech.VoiceDesign, inner); + border.Bind(Border.BorderThicknessProperty, new Binding(nameof(vm.IsInstructionTextVisible)) + { + Mode = BindingMode.OneWay, + Converter = new Avalonia.Data.Converters.FuncValueConverter(v => new Thickness(0, v ? 1 : 0, 0, 0)), + }); border.Bind(Visual.IsVisibleProperty, new Binding(nameof(vm.IsOmniVoicePickerVisible)) { Mode = BindingMode.OneWay }); return border; } @@ -209,13 +231,10 @@ private static Border BuildOmniVoicePicker(ActorVoiceRowSettingsViewModel vm) private static void AddPickerRow(Grid grid, int row, string label, object vm, ObservableCollection items, string selectedPropertyPath) { - grid.Add(new Label - { - Content = label, - VerticalAlignment = VerticalAlignment.Center, - MinWidth = 60, - }, row, 0); - grid.Add(UiUtil.MakeComboBox(items, vm, selectedPropertyPath).WithWidth(200), row, 1); + grid.Add(EditorChrome.MakeDimLabel(label, new Thickness(0)).WithMinWidth(70), row, 0); + var combo = UiUtil.MakeComboBox(items, vm, selectedPropertyPath).WithWidth(200); + AutomationProperties.SetName(combo, label); + grid.Add(combo, row, 1); } protected override void OnKeyDown(KeyEventArgs e) diff --git a/src/ui/Features/Video/TextToSpeech/DownloadTts/DownloadTtsWindow.cs b/src/ui/Features/Video/TextToSpeech/DownloadTts/DownloadTtsWindow.cs index b4ac5b325a8..6a833af8e36 100644 --- a/src/ui/Features/Video/TextToSpeech/DownloadTts/DownloadTtsWindow.cs +++ b/src/ui/Features/Video/TextToSpeech/DownloadTts/DownloadTtsWindow.cs @@ -1,7 +1,9 @@ +using Avalonia; using Avalonia.Controls; using Avalonia.Input; using Avalonia.Media; using Avalonia.Data; +using Nikse.SubtitleEdit.Features.Video.TextToSpeech.VoiceManager; using Nikse.SubtitleEdit.Logic; using Nikse.SubtitleEdit.Logic.Config; using Avalonia.Controls.Primitives; @@ -24,10 +26,14 @@ public DownloadTtsWindow(DownloadTtsViewModel vm) DataContext = vm; _vm = vm; + TtsVoicesChrome.Apply(this); + var titleText = new TextBlock - { - FontSize = UiUtil.ScaledFontSize(20), - FontWeight = FontWeight.Bold, + { + FontSize = 17, + FontWeight = FontWeight.SemiBold, + TextWrapping = TextWrapping.Wrap, + MaxWidth = 460, [!TextBlock.TextProperty] = new Binding(nameof(vm.TitleText)), }; @@ -35,7 +41,13 @@ public DownloadTtsWindow(DownloadTtsViewModel vm) progressBar.MinWidth = 400; progressBar.Bind(ProgressBar.ValueProperty, new Binding(nameof(vm.ProgressValue))); - var statusText = new TextBlock(); + var statusText = new TextBlock + { + FontSize = 12, + Foreground = EditorChrome.DimTextBrush, + TextTrimming = TextTrimming.CharacterEllipsis, + MaxWidth = 460, + }; statusText.Bind(TextBlock.TextProperty, new Binding(nameof(vm.ProgressText))); // The view models set Error on every failure path, but nothing rendered it - the user saw @@ -45,6 +57,8 @@ public DownloadTtsWindow(DownloadTtsViewModel vm) { Foreground = new SolidColorBrush(Color.FromRgb(0xC0, 0x39, 0x2B)), TextWrapping = TextWrapping.Wrap, + FontSize = 12, + MaxWidth = 460, }; errorText.Bind(TextBlock.TextProperty, new Binding(nameof(vm.Error))); errorText.Bind(IsVisibleProperty, new Binding(nameof(vm.Error)) @@ -53,21 +67,36 @@ public DownloadTtsWindow(DownloadTtsViewModel vm) }); var buttonCancel = UiUtil.MakeButtonCancel(vm.CancelCommand); - var buttonBar = UiUtil.MakeButtonBar(buttonCancel); + var buttonBar = TtsVoicesChrome.MakeButtonBar(null, buttonCancel); + + var body = new Border + { + Background = EditorChrome.PanelBrush, + Padding = new Thickness(20, 18), + Child = new StackPanel + { + Spacing = 10, + Children = + { + titleText, + progressBar, + statusText, + errorText, + }, + }, + }; - Content = new StackPanel + var root = new Grid { - Spacing = 8, - Margin = UiUtil.MakeWindowMargin(), - Children = + RowDefinitions = { - titleText, - progressBar, - statusText, - errorText, - buttonBar, - } + new RowDefinition(GridLength.Auto), + new RowDefinition(GridLength.Auto), + }, }; + root.Add(body, 0, 0); + root.Add(buttonBar, 1, 0); + Content = root; UiUtil.FocusOnFirstActivation(this, () => { diff --git a/src/ui/Features/Video/TextToSpeech/IndexTts25License/IndexTts25LicenseWindow.cs b/src/ui/Features/Video/TextToSpeech/IndexTts25License/IndexTts25LicenseWindow.cs index 65aa9e1f44e..95f87b61d4d 100644 --- a/src/ui/Features/Video/TextToSpeech/IndexTts25License/IndexTts25LicenseWindow.cs +++ b/src/ui/Features/Video/TextToSpeech/IndexTts25License/IndexTts25LicenseWindow.cs @@ -2,6 +2,7 @@ using Avalonia.Controls; using Avalonia.Layout; using Avalonia.Media; +using Nikse.SubtitleEdit.Features.Video.TextToSpeech.VoiceManager; using Nikse.SubtitleEdit.Logic; using Nikse.SubtitleEdit.Logic.Config; @@ -23,6 +24,7 @@ public IndexTts25LicenseWindow(IndexTts25LicenseViewModel vm) Width = 640; SizeToContent = SizeToContent.Height; CanResize = false; + TtsVoicesChrome.Apply(this); vm.Window = this; DataContext = vm; @@ -30,7 +32,7 @@ public IndexTts25LicenseWindow(IndexTts25LicenseViewModel vm) Content = BuildContent(vm); } - private static Border BuildContent(IndexTts25LicenseViewModel vm) + private static Grid BuildContent(IndexTts25LicenseViewModel vm) { var stack = new StackPanel { @@ -38,82 +40,31 @@ private static Border BuildContent(IndexTts25LicenseViewModel vm) Spacing = 14, Children = { - BuildHeader(), - BuildSummary(), + TtsVoicesChrome.MakeTitle(Se.Language.Video.IndexTts25LicenseHeader, Se.Language.Video.IndexTts25LicenseIntro), + TtsVoicesChrome.MakeBulletCard(IndexTts25LicenseViewModel.LicenseSummaryPoints), BuildLinks(vm), BuildAcceptRow(vm), - BuildActions(vm), }, }; - var outerGrid = new Grid { Margin = UiUtil.MakeWindowMargin() }; - outerGrid.Children.Add(stack); - - return new Border - { - Child = outerGrid, - Padding = new Thickness(4), - }; - } - - private static StackPanel BuildHeader() - { - var title = new TextBlock - { - Text = Se.Language.Video.IndexTts25LicenseHeader, - FontSize = UiUtil.ScaledFontSize(18), - FontWeight = FontWeight.SemiBold, - TextWrapping = TextWrapping.Wrap, - }; - - var subtitle = new TextBlock + var body = new Border { - Text = Se.Language.Video.IndexTts25LicenseIntro, - FontSize = UiUtil.ScaledFontSize(12), - Opacity = 0.75, - TextWrapping = TextWrapping.Wrap, - Margin = new Thickness(0, 4, 0, 0), - }; - - return new StackPanel - { - Orientation = Orientation.Vertical, - Spacing = 0, - Children = { title, subtitle }, - }; - } - - private static Border BuildSummary() - { - var stack = new StackPanel - { - Orientation = Orientation.Vertical, - Spacing = 8, + Child = stack, + Background = EditorChrome.PanelBrush, + Padding = new Thickness(20, 18), }; - foreach (var point in IndexTts25LicenseViewModel.LicenseSummaryPoints) + var grid = new Grid { - var row = new StackPanel + RowDefinitions = { - Orientation = Orientation.Horizontal, - Spacing = 8, - Children = - { - new TextBlock { Text = "•", Opacity = 0.7, VerticalAlignment = VerticalAlignment.Top }, - new TextBlock { Text = point, TextWrapping = TextWrapping.Wrap, MaxWidth = 560 }, - }, - }; - stack.Children.Add(row); - } - - return new Border - { - Child = stack, - Padding = new Thickness(12), - CornerRadius = new CornerRadius(4), - BorderThickness = new Thickness(1), - BorderBrush = new SolidColorBrush(Color.FromArgb(60, 128, 128, 128)), + new RowDefinition(GridLength.Auto), + new RowDefinition(GridLength.Auto), + }, }; + grid.Add(body, 0, 0); + grid.Add(BuildActions(vm), 1, 0); + return grid; } private static StackPanel BuildLinks(IndexTts25LicenseViewModel vm) @@ -147,8 +98,10 @@ private static Control BuildActions(IndexTts25LicenseViewModel vm) Button.IsEnabledProperty, new Avalonia.Data.Binding(nameof(vm.IsAccepted))); + accept.Classes.Add("primary"); + var cancel = UiUtil.MakeButtonCancel(vm.CancelCommand); - return UiUtil.MakeButtonBar(accept, cancel); + return TtsVoicesChrome.MakeButtonBar(null, cancel, accept); } } diff --git a/src/ui/Features/Video/TextToSpeech/ModelLicense/ModelLicenseWindow.cs b/src/ui/Features/Video/TextToSpeech/ModelLicense/ModelLicenseWindow.cs index 126a1ce143d..a9f8ad2196e 100644 --- a/src/ui/Features/Video/TextToSpeech/ModelLicense/ModelLicenseWindow.cs +++ b/src/ui/Features/Video/TextToSpeech/ModelLicense/ModelLicenseWindow.cs @@ -2,6 +2,7 @@ using Avalonia.Controls; using Avalonia.Layout; using Avalonia.Media; +using Nikse.SubtitleEdit.Features.Video.TextToSpeech.VoiceManager; using Nikse.SubtitleEdit.Logic; using Nikse.SubtitleEdit.Logic.Config; @@ -25,6 +26,7 @@ public ModelLicenseWindow(ModelLicenseViewModel vm) Width = 640; SizeToContent = SizeToContent.Height; CanResize = false; + TtsVoicesChrome.Apply(this); vm.Window = this; DataContext = vm; @@ -32,7 +34,7 @@ public ModelLicenseWindow(ModelLicenseViewModel vm) Content = BuildContent(vm); } - private static Border BuildContent(ModelLicenseViewModel vm) + private static Grid BuildContent(ModelLicenseViewModel vm) { var stack = new StackPanel { @@ -40,82 +42,31 @@ private static Border BuildContent(ModelLicenseViewModel vm) Spacing = 14, Children = { - BuildHeader(vm), - BuildSummary(vm), + TtsVoicesChrome.MakeTitle(vm.Definition.Header, vm.Definition.Intro), + TtsVoicesChrome.MakeBulletCard(vm.Definition.SummaryPoints), BuildLinks(vm), BuildAcceptRow(vm), - BuildActions(vm), }, }; - var outerGrid = new Grid { Margin = UiUtil.MakeWindowMargin() }; - outerGrid.Children.Add(stack); - - return new Border - { - Child = outerGrid, - Padding = new Thickness(4), - }; - } - - private static StackPanel BuildHeader(ModelLicenseViewModel vm) - { - var title = new TextBlock - { - Text = vm.Definition.Header, - FontSize = UiUtil.ScaledFontSize(18), - FontWeight = FontWeight.SemiBold, - TextWrapping = TextWrapping.Wrap, - }; - - var subtitle = new TextBlock + var body = new Border { - Text = vm.Definition.Intro, - FontSize = UiUtil.ScaledFontSize(12), - Opacity = 0.75, - TextWrapping = TextWrapping.Wrap, - Margin = new Thickness(0, 4, 0, 0), - }; - - return new StackPanel - { - Orientation = Orientation.Vertical, - Spacing = 0, - Children = { title, subtitle }, - }; - } - - private static Border BuildSummary(ModelLicenseViewModel vm) - { - var stack = new StackPanel - { - Orientation = Orientation.Vertical, - Spacing = 8, + Child = stack, + Background = EditorChrome.PanelBrush, + Padding = new Thickness(20, 18), }; - foreach (var point in vm.Definition.SummaryPoints) + var grid = new Grid { - var row = new StackPanel + RowDefinitions = { - Orientation = Orientation.Horizontal, - Spacing = 8, - Children = - { - new TextBlock { Text = "•", Opacity = 0.7, VerticalAlignment = VerticalAlignment.Top }, - new TextBlock { Text = point, TextWrapping = TextWrapping.Wrap, MaxWidth = 560 }, - }, - }; - stack.Children.Add(row); - } - - return new Border - { - Child = stack, - Padding = new Thickness(12), - CornerRadius = new CornerRadius(4), - BorderThickness = new Thickness(1), - BorderBrush = new SolidColorBrush(Color.FromArgb(60, 128, 128, 128)), + new RowDefinition(GridLength.Auto), + new RowDefinition(GridLength.Auto), + }, }; + grid.Add(body, 0, 0); + grid.Add(BuildActions(vm), 1, 0); + return grid; } private static StackPanel BuildLinks(ModelLicenseViewModel vm) @@ -149,8 +100,10 @@ private static Control BuildActions(ModelLicenseViewModel vm) Button.IsEnabledProperty, new Avalonia.Data.Binding(nameof(vm.IsAccepted))); + accept.Classes.Add("primary"); + var cancel = UiUtil.MakeButtonCancel(vm.CancelCommand); - return UiUtil.MakeButtonBar(accept, cancel); + return TtsVoicesChrome.MakeButtonBar(null, cancel, accept); } } diff --git a/src/ui/Features/Video/TextToSpeech/VoiceCloneConsent/VoiceCloneConsentWindow.cs b/src/ui/Features/Video/TextToSpeech/VoiceCloneConsent/VoiceCloneConsentWindow.cs index 9b9bc3ad168..d454bdb264a 100644 --- a/src/ui/Features/Video/TextToSpeech/VoiceCloneConsent/VoiceCloneConsentWindow.cs +++ b/src/ui/Features/Video/TextToSpeech/VoiceCloneConsent/VoiceCloneConsentWindow.cs @@ -2,6 +2,7 @@ using Avalonia.Controls; using Avalonia.Layout; using Avalonia.Media; +using Nikse.SubtitleEdit.Features.Video.TextToSpeech.VoiceManager; using Nikse.SubtitleEdit.Logic; using Nikse.SubtitleEdit.Logic.Config; @@ -23,6 +24,7 @@ public VoiceCloneConsentWindow(VoiceCloneConsentViewModel vm) Width = 640; SizeToContent = SizeToContent.Height; CanResize = false; + TtsVoicesChrome.Apply(this); vm.Window = this; DataContext = vm; @@ -31,7 +33,7 @@ public VoiceCloneConsentWindow(VoiceCloneConsentViewModel vm) KeyDown += (_, e) => vm.OnKeyDown(e); } - private static Border BuildContent(VoiceCloneConsentViewModel vm) + private static Grid BuildContent(VoiceCloneConsentViewModel vm) { var stack = new StackPanel { @@ -39,82 +41,31 @@ private static Border BuildContent(VoiceCloneConsentViewModel vm) Spacing = 14, Children = { - BuildHeader(), - BuildPoints(), + TtsVoicesChrome.MakeTitle(Se.Language.Video.TextToSpeech.VoiceCloneConsentHeader, Se.Language.Video.TextToSpeech.VoiceCloneConsentIntro), + TtsVoicesChrome.MakeBulletCard(VoiceCloneConsentViewModel.ConsentPoints), BuildLinks(vm), BuildAcceptRow(vm), - BuildActions(vm), }, }; - var outerGrid = new Grid { Margin = UiUtil.MakeWindowMargin() }; - outerGrid.Children.Add(stack); - - return new Border - { - Child = outerGrid, - Padding = new Thickness(4), - }; - } - - private static StackPanel BuildHeader() - { - var title = new TextBlock - { - Text = Se.Language.Video.TextToSpeech.VoiceCloneConsentHeader, - FontSize = UiUtil.ScaledFontSize(18), - FontWeight = FontWeight.SemiBold, - TextWrapping = TextWrapping.Wrap, - }; - - var subtitle = new TextBlock - { - Text = Se.Language.Video.TextToSpeech.VoiceCloneConsentIntro, - FontSize = UiUtil.ScaledFontSize(12), - Opacity = 0.75, - TextWrapping = TextWrapping.Wrap, - Margin = new Thickness(0, 4, 0, 0), - }; - - return new StackPanel + var body = new Border { - Orientation = Orientation.Vertical, - Spacing = 0, - Children = { title, subtitle }, - }; - } - - private static Border BuildPoints() - { - var stack = new StackPanel - { - Orientation = Orientation.Vertical, - Spacing = 8, + Child = stack, + Background = EditorChrome.PanelBrush, + Padding = new Thickness(20, 18), }; - foreach (var point in VoiceCloneConsentViewModel.ConsentPoints) + var grid = new Grid { - var row = new StackPanel + RowDefinitions = { - Orientation = Orientation.Horizontal, - Spacing = 8, - Children = - { - new TextBlock { Text = "•", Opacity = 0.7, VerticalAlignment = VerticalAlignment.Top }, - new TextBlock { Text = point, TextWrapping = TextWrapping.Wrap, MaxWidth = 560 }, - }, - }; - stack.Children.Add(row); - } - - return new Border - { - Child = stack, - Padding = new Thickness(12), - CornerRadius = new CornerRadius(4), - BorderThickness = new Thickness(1), - BorderBrush = new SolidColorBrush(Color.FromArgb(60, 128, 128, 128)), + new RowDefinition(GridLength.Auto), + new RowDefinition(GridLength.Auto), + }, }; + grid.Add(body, 0, 0); + grid.Add(BuildActions(vm), 1, 0); + return grid; } private static StackPanel BuildLinks(VoiceCloneConsentViewModel vm) @@ -143,6 +94,7 @@ private static CheckBox BuildAcceptRow(VoiceCloneConsentViewModel vm) TextWrapping = TextWrapping.Wrap, MaxWidth = 560, }; + checkBox.FontWeight = FontWeight.SemiBold; return checkBox; } @@ -155,8 +107,10 @@ private static Control BuildActions(VoiceCloneConsentViewModel vm) Button.IsEnabledProperty, new Avalonia.Data.Binding(nameof(vm.IsAccepted))); + accept.Classes.Add("primary"); + var cancel = UiUtil.MakeButtonCancel(vm.CancelCommand); - return UiUtil.MakeButtonBar(accept, cancel); + return TtsVoicesChrome.MakeButtonBar(null, cancel, accept); } } diff --git a/src/ui/Features/Video/TextToSpeech/VoiceManager/TtsVoicesChrome.cs b/src/ui/Features/Video/TextToSpeech/VoiceManager/TtsVoicesChrome.cs new file mode 100644 index 00000000000..4628464dba1 --- /dev/null +++ b/src/ui/Features/Video/TextToSpeech/VoiceManager/TtsVoicesChrome.cs @@ -0,0 +1,223 @@ +using System.Collections.Generic; +using Avalonia; +using Avalonia.Automation; +using Avalonia.Controls; +using Avalonia.Controls.Presenters; +using Avalonia.Controls.Primitives; +using Avalonia.Layout; +using Avalonia.Media; +using Avalonia.Styling; +using Nikse.SubtitleEdit.Logic; + +namespace Nikse.SubtitleEdit.Features.Video.TextToSpeech.VoiceManager; + +/// +/// The editor look (see ) for the TTS voice windows: voice manager, +/// voice packs, cast, the licence and consent gates and the engine download. Compact inputs, +/// a bottom button bar with the accent button on the right and a progress strip above it. +/// +internal static class TtsVoicesChrome +{ + /// + /// Window background plus the shared editor styles and the compact inputs used here. + /// + internal static void Apply(Window window) + { + window.Background = EditorChrome.WindowBrush; + EditorChrome.AddEditorStyles(window.Styles); + var styles = window.Styles; + + styles.Add(new Style(x => x.OfType()) + { + Setters = + { + new Setter(TemplatedControl.FontSizeProperty, 12.5), + new Setter(TemplatedControl.MinHeightProperty, 28.0), + new Setter(TemplatedControl.PaddingProperty, new Thickness(8, 2, 4, 2)), + new Setter(TemplatedControl.CornerRadiusProperty, new CornerRadius(6)), + }, + }); + styles.Add(new Style(x => x.OfType().Class("text")) + { + Setters = + { + new Setter(TemplatedControl.FontSizeProperty, 12.5), + new Setter(TemplatedControl.MinHeightProperty, 28.0), + new Setter(TemplatedControl.PaddingProperty, new Thickness(8, 4)), + new Setter(TemplatedControl.CornerRadiusProperty, new CornerRadius(6)), + new Setter(TemplatedControl.BackgroundProperty, EditorChrome.FieldBrush), + new Setter(TemplatedControl.BorderBrushProperty, EditorChrome.FieldBorderBrush), + }, + }); + + // A disabled accent button (Accept before the box is ticked, Download while busy) must not + // keep the accent - it looked clickable. + styles.Add(new Style(x => x.OfType // DeepSeek-OCR-2 was evaluated for this list and rejected (2026-08-26): through // mainline llama.cpp with the OCR prompt it captions the image ("The image displays a @@ -351,6 +355,23 @@ public static class LlamaCppServerManager "https://huggingface.co/ggml-org/GLM-OCR-GGUF/resolve/main/GLM-OCR-Q8_0.gguf", MmprojFileName: "mmproj-GLM-OCR-Q8_0.gguf", MmprojUrl: "https://huggingface.co/ggml-org/GLM-OCR-GGUF/resolve/main/mmproj-GLM-OCR-Q8_0.gguf"), + // LightOnOCR-3 4B (LightOn, Apache-2.0, Qwen 3.5 VL) as mradermacher's Q8_0 - no official + // GGUF exists. As accurate as GLM-OCR on the 390 real images (384 vs 385 exact) and ~2.5x + // faster (1.3-1.8 s vs 3.5-5 s per image), and right where GLM-OCR fails: it keeps the line + // break in two-line ♪ lyrics and reads capital I where GLM-OCR writes "lst"/"ledereen". Its + // misses: two misread words, a closing quote added to a quotation that continues in the next + // cue, and "- Hi!" for "-Hi!" on English dialogue dashes. Second, not first, because it is + // a 5.2 GB download against GLM-OCR's 1.4 GB. Q8_0 only: the i1-Q4_K_M was no better than + // the 0.8B (143/150 Dutch vs 147/150 for Q8_0). + // NoThinking: the Qwen 3.5 template thinks by default and the answer then lands in + // reasoning_content, leaving content empty on most images. Border: the square cost it + // accuracy on every corpus (synthetic 16/19 vs 17/19) and ~2.5x the time. + new LlamaCppModel("LightOnOCR-3 4B (Q8_0)", "LightOnOCR-3-4B.Q8_0.gguf", "5.2 GB", + "https://huggingface.co/mradermacher/LightOnOCR-3-4B-GGUF/resolve/main/LightOnOCR-3-4B.Q8_0.gguf", + MmprojFileName: "mmproj-LightOnOCR-3-4B.Q8_0.gguf", + MmprojUrl: "https://huggingface.co/mradermacher/LightOnOCR-3-4B-GGUF/resolve/main/LightOnOCR-3-4B.mmproj-f16.gguf", + NoThinking: true, + OcrPadding: AiOcrPadding.Border), // Liquid AI's general vision model (3B), not an OCR specialist. Measured 2026-09-05 on the // same 14-image corpus: 0.00% character error - flawless across all eight scripts, italics, // busy video backgrounds and small text, where GLM-OCR misread one Russian word - and with @@ -410,14 +431,21 @@ public static class LlamaCppServerManager MmprojFileName: "mmproj-Qari-OCR-0.2.2.1-VL-2B-Instruct-merged.Q8_0.gguf", MmprojUrl: "https://huggingface.co/mradermacher/Qari-OCR-0.2.2.1-VL-2B-Instruct-merged-GGUF/resolve/main/Qari-OCR-0.2.2.1-VL-2B-Instruct-merged.mmproj-Q8_0.gguf", OcrPadding: AiOcrPadding.Border), - // Last on purpose: the weakest and by far the slowest of the four on subtitle images - // (9/14 recognized, 2.53% character error, 18.5 s/image against GLM-OCR's 2.5 s). It loses - // line breaks, drops ♪ note marks, misreads Cyrillic ё as е and Chinese 的, and wraps - // Japanese output in ``` fences. Kept for users who already rely on it. - new LlamaCppModel("LightOnOCR 1B (Q8_0)", "LightOnOCR-1B-1025-Q8_0.gguf", "1.2 GB", - "https://huggingface.co/ggml-org/LightOnOCR-1B-1025-GGUF/resolve/main/LightOnOCR-1B-1025-Q8_0.gguf", - MmprojFileName: "mmproj-LightOnOCR-1B-1025-Q8_0.gguf", - MmprojUrl: "https://huggingface.co/ggml-org/LightOnOCR-1B-1025-GGUF/resolve/main/mmproj-LightOnOCR-1B-1025-Q8_0.gguf"), + // LightOnOCR-3 0.8B (LightOn, Apache-2.0, Qwen 3.5 VL) as Prathat's Q8_0 - no official GGUF. + // Replaces LightOnOCR 1B (18.5 s/image, last in every measurement); anyone who already + // downloaded that one still sees it, as a custom entry from the models folder. The fast + // pick: 0.35-0.85 s per real image, 6-12x GLM-OCR, at about 1.0 GB. Last because it is the + // least accurate of the general models on real data (379/390 vs GLM-OCR's 385): five + // fluent misreads (four of them Dutch), a closing quote added to a quotation that continues + // in the next cue, and "- " after dialogue dashes. Best of all on the synthetic corpus + // (18/19), so the misses are language-prior guesses rather than unreadable images. + // Q8_0 and f16 gave identical output. NoThinking and Border for the same reasons as the 4B. + new LlamaCppModel("LightOnOCR-3 0.8B (Q8_0)", "LightOnOCR-3-0.8B-Q8_0.gguf", "1.0 GB", + "https://huggingface.co/Prathat/LightOnOCR-3-0.8B-GGUF/resolve/main/LightOnOCR-3-0.8B-Q8_0.gguf", + MmprojFileName: "mmproj-LightOnOCR-3-0.8B-Q8_0.gguf", + MmprojUrl: "https://huggingface.co/Prathat/LightOnOCR-3-0.8B-GGUF/resolve/main/LightOnOCR-3-0.8B-mmproj-f16.gguf", + NoThinking: true, + OcrPadding: AiOcrPadding.Border), }; /// diff --git a/src/libuilogic/Ocr/OcrHelper.cs b/src/libuilogic/Ocr/OcrHelper.cs index a63f3103ad5..d459216e843 100644 --- a/src/libuilogic/Ocr/OcrHelper.cs +++ b/src/libuilogic/Ocr/OcrHelper.cs @@ -46,6 +46,27 @@ public static string FixAiOcrPunctuationSpaces(string? input, string? language) return text; } + /// + /// Trims every line of an AI OCR result, not only the whole text. LightOnOCR-3 ends each + /// line but the last with a markdown hard break (two spaces), which would otherwise stay in + /// the subtitle text. + /// + public static string TrimAiOcrLines(string? input) + { + if (string.IsNullOrEmpty(input)) + { + return string.Empty; + } + + var lines = input.SplitToLines(); + for (var i = 0; i < lines.Count; i++) + { + lines[i] = lines[i].Trim(); + } + + return string.Join(Environment.NewLine, lines).Trim(); + } + /// /// French and Breton typography puts a real space before "?" and "!", so removing it /// damages correct OCR output. Same rule as Utilities.FixOcrErrors in libse. diff --git a/src/seconv/Core/LlamaCppOcrEngine.cs b/src/seconv/Core/LlamaCppOcrEngine.cs index 986b8d50cbf..d33442dff92 100644 --- a/src/seconv/Core/LlamaCppOcrEngine.cs +++ b/src/seconv/Core/LlamaCppOcrEngine.cs @@ -68,7 +68,7 @@ public static LlamaCppOcrEngine Create(ConversionOptions options) : url.TrimEnd('/') + "/v1/chat/completions"; var modelName = string.IsNullOrWhiteSpace(options.OcrModel) ? "glmocr" - : Path.GetFileNameWithoutExtension(options.OcrModel.Trim()); + : ModelStem(options.OcrModel); // Only for the prompt: a curated model named by file/stem still gets its own tuned // prompt against a user-managed server. The engine itself stays in URL mode. var curated = LlamaCppServerManager.OcrModels.FirstOrDefault(m => @@ -82,6 +82,20 @@ public static LlamaCppOcrEngine Create(ConversionOptions options) return new LlamaCppOcrEngine(null, model, Path.GetFileNameWithoutExtension(model.FileName), options.OcrLanguage, options.OcrPrompt, options.Quiet); } + /// + /// The model name for a user-managed server: the file name without a trailing .gguf, + /// the form the curated lookups (padding, prompt) match on. Only .gguf is stripped - + /// would cut "LightOnOCR-3-0.8B-Q8_0" + /// to "LightOnOCR-3-0", so the model lost its border padding and was sent the square. + /// + internal static string ModelStem(string model) + { + var fileName = Path.GetFileName(model.Trim()); + return fileName.EndsWith(".gguf", StringComparison.OrdinalIgnoreCase) + ? fileName[..^".gguf".Length] + : fileName; + } + public string Recognize(SKBitmap bitmap) { if (bitmap is null || bitmap.Width == 0 || bitmap.Height == 0) @@ -129,7 +143,7 @@ public string Recognize(SKBitmap bitmap) var contents = parser.GetAllTagsByNameAsStrings(json, "content"); var text = string.Join(string.Empty, contents).Trim(); text = text.Replace("\\n", Environment.NewLine).Replace("\\\"", "\""); - return text.Trim(); + return OcrHelper.TrimAiOcrLines(text); } /// diff --git a/src/ui/Features/Ocr/Engines/LlamaCppOcr.cs b/src/ui/Features/Ocr/Engines/LlamaCppOcr.cs index 15216dc8ff7..fbef6b51a2b 100644 --- a/src/ui/Features/Ocr/Engines/LlamaCppOcr.cs +++ b/src/ui/Features/Ocr/Engines/LlamaCppOcr.cs @@ -84,6 +84,7 @@ public async Task Ocr(SKBitmap bitmap, string url, string model, string // sanitize resultText = resultText.Trim(); resultText = resultText.Replace("\\n", Environment.NewLine); + resultText = OcrHelper.TrimAiOcrLines(resultText); resultText = OcrHelper.FixAiOcrPunctuationSpaces(resultText, language); return resultText.Trim(); diff --git a/tests/libuilogic/Ocr/LlamaCppOcrModelOrderTests.cs b/tests/libuilogic/Ocr/LlamaCppOcrModelOrderTests.cs index 1eb6c8c25cb..8fba937a113 100644 --- a/tests/libuilogic/Ocr/LlamaCppOcrModelOrderTests.cs +++ b/tests/libuilogic/Ocr/LlamaCppOcrModelOrderTests.cs @@ -1,4 +1,5 @@ using Nikse.SubtitleEdit.UiLogic.LlamaCpp; +using Nikse.SubtitleEdit.UiLogic.Ocr; using System.Linq; namespace LibUiLogicTests.Ocr; @@ -19,9 +20,27 @@ public void GlmOcr_IsFirst_SoItIsTheDefaultPick() } [Fact] - public void LightOnOcr_IsLast_TheWeakestAndSlowestOfTheCuratedModels() + public void LightOnOcr3_4B_IsSecond_AsAccurateAsGlmOcrButFourTimesTheDownload() { - Assert.Equal("LightOnOCR-1B-1025-Q8_0.gguf", LlamaCppServerManager.OcrModels[^1].FileName); + Assert.Equal("LightOnOCR-3-4B.Q8_0.gguf", LlamaCppServerManager.OcrModels[1].FileName); + } + + [Fact] + public void LightOnOcr3_08B_IsLast_TheFastestButLeastAccurateOnRealSubtitles() + { + Assert.Equal("LightOnOCR-3-0.8B-Q8_0.gguf", LlamaCppServerManager.OcrModels[^1].FileName); + } + + [Theory] + [InlineData("LightOnOCR-3-4B.Q8_0.gguf")] + [InlineData("LightOnOCR-3-0.8B-Q8_0.gguf")] + public void LightOnOcr3_RunsWithoutThinkingAndWithTheBorder(string fileName) + { + // Qwen 3.5 VL thinks by default and then answers into reasoning_content, leaving the + // content field the OCR engines read empty; the square pad cost accuracy on every corpus. + var model = LlamaCppServerManager.OcrModels.Single(m => m.FileName == fileName); + Assert.True(model.NoThinking); + Assert.Equal(AiOcrPadding.Border, LlamaCppServerManager.GetOcrPadding(System.IO.Path.GetFileNameWithoutExtension(fileName))); } [Fact] diff --git a/tests/libuilogic/Ocr/LlamaCppOcrPromptResolveTests.cs b/tests/libuilogic/Ocr/LlamaCppOcrPromptResolveTests.cs index 6cd36faa9da..51283cb8551 100644 --- a/tests/libuilogic/Ocr/LlamaCppOcrPromptResolveTests.cs +++ b/tests/libuilogic/Ocr/LlamaCppOcrPromptResolveTests.cs @@ -55,8 +55,8 @@ public void ModelWithoutOwnPrompt_GetsSharedPrompt() } [Fact] - public void Lfm_IsRankedSecond_BehindGlmOcr() + public void Lfm_IsRankedThird_BehindGlmOcrAndLightOnOcr3_4B() { - Assert.Equal("LFM2.5-VL-3B-Q8_0.gguf", LlamaCppServerManager.OcrModels[1].FileName); + Assert.Equal("LFM2.5-VL-3B-Q8_0.gguf", LlamaCppServerManager.OcrModels[2].FileName); } } diff --git a/tests/libuilogic/Ocr/OcrHelperTrimLinesTests.cs b/tests/libuilogic/Ocr/OcrHelperTrimLinesTests.cs new file mode 100644 index 00000000000..2171f2a5a97 --- /dev/null +++ b/tests/libuilogic/Ocr/OcrHelperTrimLinesTests.cs @@ -0,0 +1,39 @@ +using Nikse.SubtitleEdit.UiLogic.Ocr; +using System; + +namespace LibUiLogicTests.Ocr; + +/// +/// LightOnOCR-3 ends every line but the last with a markdown hard break (two trailing spaces); +/// the llama.cpp OCR engines used to trim only the whole result, so the spaces stayed in the +/// subtitle text. +/// +public class OcrHelperTrimLinesTests +{ + [Fact] + public void TrimsMarkdownHardBreakAtLineEnd() + { + var input = "I told you not to come back here. " + Environment.NewLine + "You never listen to me!"; + var expected = "I told you not to come back here." + Environment.NewLine + "You never listen to me!"; + Assert.Equal(expected, OcrHelper.TrimAiOcrLines(input)); + } + + [Fact] + public void NormalizesBareLineFeeds() + { + Assert.Equal("a" + Environment.NewLine + "b", OcrHelper.TrimAiOcrLines(" a \n b ")); + } + + [Fact] + public void LeavesSingleLineUntouched() + { + Assert.Equal("♪ And I will always love you ♪", OcrHelper.TrimAiOcrLines("♪ And I will always love you ♪")); + } + + [Fact] + public void EmptyAndNullReturnEmpty() + { + Assert.Equal(string.Empty, OcrHelper.TrimAiOcrLines(null)); + Assert.Equal(string.Empty, OcrHelper.TrimAiOcrLines(string.Empty)); + } +} diff --git a/tests/seconv/Core/LlamaCppOcrModelStemTest.cs b/tests/seconv/Core/LlamaCppOcrModelStemTest.cs new file mode 100644 index 00000000000..f8c9fdb1633 --- /dev/null +++ b/tests/seconv/Core/LlamaCppOcrModelStemTest.cs @@ -0,0 +1,33 @@ +using Nikse.SubtitleEdit.UiLogic.LlamaCpp; +using Nikse.SubtitleEdit.UiLogic.Ocr; +using SeConv.Core; +using Xunit; + +namespace SeConvTests.Core; + +/// +/// With --ocr-url, --ocr-model is only a name, and it is what the curated padding and prompt +/// lookups match on. A dotted name given without ".gguf" ("LightOnOCR-3-0.8B-Q8_0") used to lose +/// everything after its last dot, so the model was sent the square instead of its border. +/// +public class LlamaCppOcrModelStemTest +{ + [Theory] + [InlineData("LightOnOCR-3-0.8B-Q8_0", "LightOnOCR-3-0.8B-Q8_0")] + [InlineData("LightOnOCR-3-0.8B-Q8_0.gguf", "LightOnOCR-3-0.8B-Q8_0")] + [InlineData("LightOnOCR-3-4B.Q8_0", "LightOnOCR-3-4B.Q8_0")] + [InlineData("LightOnOCR-3-4B.Q8_0.GGUF", "LightOnOCR-3-4B.Q8_0")] + [InlineData(" GLM-OCR-Q8_0.gguf ", "GLM-OCR-Q8_0")] + public void StripsOnlyTheGgufExtension(string model, string expected) + { + Assert.Equal(expected, LlamaCppOcrEngine.ModelStem(model)); + } + + [Theory] + [InlineData("LightOnOCR-3-0.8B-Q8_0")] + [InlineData("LightOnOCR-3-4B.Q8_0")] + public void DottedCuratedNamesWithoutExtension_GetTheirBorderPadding(string model) + { + Assert.Equal(AiOcrPadding.Border, LlamaCppServerManager.GetOcrPadding(LlamaCppOcrEngine.ModelStem(model))); + } +} From 09bffcd204240f755af598e8b6a2530952498c29 Mon Sep 17 00:00:00 2001 From: niksedk Date: Thu, 15 Oct 2026 19:29:09 +0200 Subject: [PATCH 208/306] Fix flaky FrameNumbersModeTests focus race window.KeyPress runs queued dispatcher jobs before delivering the key to the focused element. A focus/activate job left queued by an earlier test's window could take focus away from the text box, so Up never reached SecondsUpDown and the duration stayed "62" instead of "63". Drain pending jobs before showing, activate the test window, focus the text box and assert focus landed before sending keys. Co-Authored-By: Claude Opus 5.5 --- tests/UI/Controls/FrameNumbersModeTests.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/tests/UI/Controls/FrameNumbersModeTests.cs b/tests/UI/Controls/FrameNumbersModeTests.cs index aeddc781346..6b552cb52e5 100644 --- a/tests/UI/Controls/FrameNumbersModeTests.cs +++ b/tests/UI/Controls/FrameNumbersModeTests.cs @@ -49,14 +49,25 @@ public void Dispose() _scope.Dispose(); } + // Keys sent with window.KeyPress go to the focused element, and KeyPress first runs any + // queued dispatcher jobs. A window an earlier test left open can have a focus/activate job + // still queued; run before our Focus() it lets that window take focus, the Up key never + // reaches this text box and the value stays put ("62" instead of "63" in a full run). + // Drain those jobs first, then activate and focus, and check focus actually landed. private (Window window, TextBox textBox) Show(Control control) { + Dispatcher.UIThread.RunJobs(); + var window = new Window { Content = control }; _windows.Add(window); window.Show(); + Dispatcher.UIThread.RunJobs(); + var textBox = control.GetVisualDescendants().OfType().Single(); + window.Activate(); textBox.Focus(); Dispatcher.UIThread.RunJobs(); + Assert.True(textBox.IsFocused, $"Focus is on {window.FocusManager?.GetFocusedElement()}"); return (window, textBox); } From bb525c29e460486bd10d585586e112d2bc756e01 Mon Sep 17 00:00:00 2001 From: niksedk Date: Thu, 15 Oct 2026 19:43:31 +0200 Subject: [PATCH 209/306] Merge lines and small tool windows in the editor look Co-Authored-By: Claude Opus 5.5 --- .../ChangeFormattingWindow.cs | 84 ++++---- .../ConvertActors/ConvertActorsWindow.cs | 125 +++++------- src/ui/Features/Tools/LineToolsChrome.cs | 183 ++++++++++++++++++ .../MergeContinuationLinesWindow.cs | 113 +++-------- .../MergeShortLines/MergeShortLinesWindow.cs | 115 ++++------- .../MergeSameTextWindow.cs | 67 +++---- .../MergeSameTimeCodesWindow.cs | 67 +++---- .../RemoveUnicodeCharactersWindow.cs | 48 ++--- .../Features/Tools/Renumber/RenumberWindow.cs | 43 ++-- src/ui/Features/Tools/SortBy/SortByWindow.cs | 100 ++++------ .../SplitBreakLongLinesWindow.cs | 164 +++++----------- 11 files changed, 502 insertions(+), 607 deletions(-) create mode 100644 src/ui/Features/Tools/LineToolsChrome.cs diff --git a/src/ui/Features/Tools/ChangeFormatting/ChangeFormattingWindow.cs b/src/ui/Features/Tools/ChangeFormatting/ChangeFormattingWindow.cs index 948b3234452..3bc59e0bad8 100644 --- a/src/ui/Features/Tools/ChangeFormatting/ChangeFormattingWindow.cs +++ b/src/ui/Features/Tools/ChangeFormatting/ChangeFormattingWindow.cs @@ -10,6 +10,10 @@ namespace Nikse.SubtitleEdit.Features.Tools.ChangeFormatting; +/// +/// Change formatting, in the editor look (see ): from/to (and color) on +/// top, the before/after preview below, the summary plus Cancel and OK in the button bar. +/// public class ChangeFormattingWindow : Window { public ChangeFormattingWindow(ChangeFormattingViewModel vm) @@ -21,62 +25,56 @@ public ChangeFormattingWindow(ChangeFormattingViewModel vm) Height = 800; MinWidth = 900; MinHeight = 500; + LineToolsChrome.Apply(this); vm.Window = this; DataContext = vm; - var labelFrom = UiUtil.MakeLabel(Se.Language.General.From); - var comboBoxFrom = UiUtil.MakeComboBox(vm.FromTypes, vm, nameof(vm.SelectedFromType)); + var comboBoxFrom = UiUtil.MakeComboBox(vm.FromTypes, vm, nameof(vm.SelectedFromType)) + .WithAccessibleName(Se.Language.General.From); comboBoxFrom.SelectionChanged += vm.SelectionChanged; - var labelTo = UiUtil.MakeLabel(Se.Language.General.To); - var comboBoxTo = UiUtil.MakeComboBox(vm.ToTypes, vm, nameof(vm.SelectedToType)); + var comboBoxTo = UiUtil.MakeComboBox(vm.ToTypes, vm, nameof(vm.SelectedToType)) + .WithAccessibleName(Se.Language.General.To); comboBoxTo.SelectionChanged += vm.SelectionChanged; - var labelColor = UiUtil.MakeLabel(Se.Language.General.Color); - var colorPicker = UiUtil.MakeColorPickerButton(vm, nameof(vm.SelectedColor)); - colorPicker.Bind(Visual.IsVisibleProperty, new Binding(nameof(vm.IsColorVisible))); + var colorPicker = UiUtil.MakeColorPickerButton(vm, nameof(vm.SelectedColor)) + .WithAccessibleName(Se.Language.General.Color); - var panelControls = UiUtil.MakeHorizontalPanel( - labelFrom, - comboBoxFrom, - labelTo, - comboBoxTo, - labelColor, - colorPicker); - - var subtitleView = MakeSubtitleView(vm); - - var labelStatus = UiUtil.MakeLabel(string.Empty).WithBindText(vm, nameof(vm.StatusText)).WithAlignmentTop(); - - var buttonOk = UiUtil.MakeButtonOk(vm.OkCommand); - var buttonCancel = UiUtil.MakeButtonCancel(vm.CancelCommand); - var panelButtons = UiUtil.MakeButtonBar(buttonOk, buttonCancel); + // The color only matters when converting to color, so its label hides with it. + var panelColor = new StackPanel + { + Orientation = Orientation.Horizontal, + Spacing = 8, + Margin = new Thickness(16, 0, 0, 0), + Children = { EditorChrome.MakeDimLabel(Se.Language.General.Color, new Thickness(0)), colorPicker }, + }; + panelColor.Bind(Visual.IsVisibleProperty, new Binding(nameof(vm.IsColorVisible))); - var grid = new Grid + var panelControls = new StackPanel { - RowDefinitions = - { - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, // Bridge gap smaller than - new RowDefinition { Height = new GridLength(1, GridUnitType.Star) }, // Subtitle view - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, // Buttons - }, - ColumnDefinitions = + Orientation = Orientation.Horizontal, + Spacing = 8, + MinHeight = 30, + Children = { - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) }, + EditorChrome.MakeDimLabel(Se.Language.General.From, new Thickness(0)), + comboBoxFrom, + EditorChrome.MakeDimLabel(Se.Language.General.To, new Thickness(16, 0, 0, 0)), + comboBoxTo, + panelColor, }, - Margin = UiUtil.MakeWindowMargin(), - ColumnSpacing = 10, - RowSpacing = 10, - Width = double.NaN, - HorizontalAlignment = HorizontalAlignment.Stretch, }; - grid.Add(panelControls, 0); - grid.Add(subtitleView, 1); - grid.Add(labelStatus, 2); - grid.Add(panelButtons, 2); + var buttonOk = UiUtil.MakeButtonOk(vm.OkCommand); + var buttonCancel = UiUtil.MakeButtonCancel(vm.CancelCommand); - Content = grid; + Content = LineToolsChrome.MakeRoot( + LineToolsChrome.MakeSections( + EditorChrome.MakeEditorSection(Se.Language.General.Convert, panelControls, first: true), + MakeSubtitleView(vm)), + LineToolsChrome.MakeStatusText(vm, nameof(vm.StatusText)), + buttonCancel, + buttonOk); UiUtil.FocusOnFirstActivation(this, comboBoxFrom); // initial focus on an input, not an action button - a focused button clicks on bare Space KeyDown += (_, e) => vm.OnKeyDown(e); @@ -92,7 +90,7 @@ private static Border MakeSubtitleView(ChangeFormattingViewModel vm) var colorConverter = new TextWithSubtitleSyntaxHighlightingConverter(); // No header-click sorting (the DataGrid's CanUserSortColumns is not carried // over): before/after formatting previews in subtitle order. - var dataGridSubtitle = TableViewExtras.MakeTableView(multiSelect: false); + var dataGridSubtitle = TableViewExtras.MakeTableView(multiSelect: false).WithAccessibleName(Se.Language.General.Preview); dataGridSubtitle.Width = double.NaN; dataGridSubtitle.Height = double.NaN; dataGridSubtitle.DataContext = vm; @@ -183,6 +181,6 @@ private static Border MakeSubtitleView(ChangeFormattingViewModel vm) }, }); - return UiUtil.MakeBorderForControlNoPadding(dataGridSubtitle); + return LineToolsChrome.MakeListSection(Se.Language.General.Preview, dataGridSubtitle); } } diff --git a/src/ui/Features/Tools/ConvertActors/ConvertActorsWindow.cs b/src/ui/Features/Tools/ConvertActors/ConvertActorsWindow.cs index b9a674aa4d5..019de7c187f 100644 --- a/src/ui/Features/Tools/ConvertActors/ConvertActorsWindow.cs +++ b/src/ui/Features/Tools/ConvertActors/ConvertActorsWindow.cs @@ -13,6 +13,10 @@ namespace Nikse.SubtitleEdit.Features.Tools.ConvertActors; +/// +/// Convert actors, in the editor look (see ): from/to and the options +/// side by side, the preview below, the summary plus Cancel and OK in the button bar. +/// public class ConvertActorsWindow : Window { public ConvertActorsWindow(ConvertActorsViewModel vm) @@ -24,111 +28,78 @@ public ConvertActorsWindow(ConvertActorsViewModel vm) Height = 800; MinWidth = 900; MinHeight = 500; + LineToolsChrome.Apply(this); vm.Window = this; DataContext = vm; - var labelFrom = UiUtil.MakeLabel(Se.Language.Tools.ConvertActors.ConvertActorFrom); - var comboBoxFrom = UiUtil.MakeComboBox(vm.FromTypes, vm, nameof(vm.SelectedFromType)); + var comboBoxFrom = UiUtil.MakeComboBox(vm.FromTypes, vm, nameof(vm.SelectedFromType)) + .WithAccessibleName(Se.Language.Tools.ConvertActors.ConvertActorFrom); comboBoxFrom.SelectionChanged += vm.SelectionChanged; - var labelTo = UiUtil.MakeLabel(Se.Language.Tools.ConvertActors.ConvertActorTo); - var comboBoxTo = UiUtil.MakeComboBox(vm.ToTypes, vm, nameof(vm.SelectedToType)); + var comboBoxTo = UiUtil.MakeComboBox(vm.ToTypes, vm, nameof(vm.SelectedToType)) + .WithAccessibleName(Se.Language.Tools.ConvertActors.ConvertActorTo); comboBoxTo.SelectionChanged += vm.SelectionChanged; - var checkBoxSetColor = UiUtil.MakeCheckBox(Se.Language.Tools.ConvertActors.SetColor, vm, nameof(vm.SetColor)); - var colorPicker = UiUtil.MakeColorPickerButton(vm, nameof(vm.SelectedColor)); + var rowSetColor = LineToolsChrome.MakeSwitchRow(nameof(vm.SetColor), Se.Language.Tools.ConvertActors.SetColor, out _); + var colorPicker = UiUtil.MakeColorPickerButton(vm, nameof(vm.SelectedColor)) + .WithAccessibleName(Se.Language.General.Color); colorPicker.Bind(Visual.IsVisibleProperty, new Binding(nameof(vm.SetColor)) { Source = vm, Mode = BindingMode.OneWay }); + colorPicker.Margin = new Thickness(4, 0, 0, 0); + rowSetColor.Children.Add(colorPicker); - var checkBoxChangeCasing = UiUtil.MakeCheckBox(Se.Language.General.ChangeCasing, vm, nameof(vm.ChangeCasing)); + var rowChangeCasing = LineToolsChrome.MakeSwitchRow(nameof(vm.ChangeCasing), Se.Language.General.ChangeCasing, out _); var comboBoxCasing = new ComboBox { ItemsSource = vm.CasingOptions, DataContext = vm, HorizontalAlignment = HorizontalAlignment.Left, VerticalAlignment = VerticalAlignment.Center, - }; + Margin = new Thickness(4, 0, 0, 0), + }.WithAccessibleName(Se.Language.General.Casing); comboBoxCasing.Bind(ComboBox.SelectedIndexProperty, new Binding(nameof(vm.SelectedCasingIndex)) { Mode = BindingMode.TwoWay }); comboBoxCasing.Bind(ComboBox.IsVisibleProperty, new Binding(nameof(vm.IsCasingVisible)) { Source = vm, Mode = BindingMode.OneWay }); comboBoxCasing.SelectionChanged += vm.SelectionChanged; + rowChangeCasing.Children.Add(comboBoxCasing); - var checkBoxOnlyNames = UiUtil.MakeCheckBox(Se.Language.Tools.ConvertActors.OnlyNames, vm, nameof(vm.OnlyNames)); + var rowOnlyNames = LineToolsChrome.MakeSwitchRow(nameof(vm.OnlyNames), Se.Language.Tools.ConvertActors.OnlyNames, out _); - var panelConversion = new Grid + // The color and casing pickers show up next to their switch, so the rows keep a fixed + // height and the list below does not jump. + foreach (var row in new[] { rowSetColor, rowChangeCasing, rowOnlyNames }) { - ColumnDefinitions = - { - new ColumnDefinition { Width = GridLength.Auto }, - new ColumnDefinition { Width = GridLength.Auto }, - }, - RowDefinitions = - { - new RowDefinition { Height = GridLength.Auto }, - new RowDefinition { Height = GridLength.Auto }, - }, - ColumnSpacing = 5, - RowSpacing = 5, - }; - panelConversion.Add(labelFrom, 0, 0); - panelConversion.Add(comboBoxFrom, 0, 1); - panelConversion.Add(labelTo, 1, 0); - panelConversion.Add(comboBoxTo, 1, 1); + row.MinHeight = 30; + } - var panelOptions = UiUtil.MakeVerticalPanel( - UiUtil.MakeHorizontalPanel(checkBoxSetColor, colorPicker), - UiUtil.MakeHorizontalPanel(checkBoxChangeCasing, comboBoxCasing), - checkBoxOnlyNames); + var panelConversion = LineToolsChrome.MakeFieldGrid( + (Se.Language.Tools.ConvertActors.ConvertActorFrom, comboBoxFrom), + (Se.Language.Tools.ConvertActors.ConvertActorTo, comboBoxTo)); - var panelControls = new Grid + var panelOptions = new StackPanel { - ColumnDefinitions = - { - new ColumnDefinition { Width = GridLength.Auto }, - new ColumnDefinition { Width = GridLength.Auto }, - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) }, - }, - RowDefinitions = - { - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - }, - ColumnSpacing = 24, - HorizontalAlignment = HorizontalAlignment.Stretch, + Spacing = 4, + Children = { rowSetColor, rowChangeCasing, rowOnlyNames }, }; - panelControls.Add(panelConversion, 0, 0); - panelControls.Add(panelOptions, 0, 1); - - var subtitleView = MakeSubtitleView(vm); - - var labelStatus = UiUtil.MakeLabel(string.Empty).WithBindText(vm, nameof(vm.StatusText)).WithAlignmentTop(); - - var buttonOk = UiUtil.MakeButtonOk(vm.OkCommand); - var buttonCancel = UiUtil.MakeButtonCancel(vm.CancelCommand); - var panelButtons = UiUtil.MakeButtonBar(buttonOk, buttonCancel); - var grid = new Grid + var panelControls = new Grid { - RowDefinitions = - { - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Star) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - }, - ColumnDefinitions = - { - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) }, - }, - Margin = UiUtil.MakeWindowMargin(), - ColumnSpacing = 10, - RowSpacing = 10, - Width = double.NaN, - HorizontalAlignment = HorizontalAlignment.Stretch, + ColumnDefinitions = { new ColumnDefinition(GridLength.Auto), new ColumnDefinition(new GridLength(1, GridUnitType.Star)) }, }; + panelControls.Add(EditorChrome.MakeEditorSection(Se.Language.General.Convert, panelConversion, first: true), 0, 0); + panelControls.Add(new Border + { + Child = EditorChrome.MakeEditorSection(Se.Language.General.Options, panelOptions, first: true), + BorderBrush = EditorChrome.LineBrush, + BorderThickness = new Thickness(1, 0, 0, 0), + }, 0, 1); - grid.Add(panelControls, 0); - grid.Add(subtitleView, 1); - grid.Add(labelStatus, 2); - grid.Add(panelButtons, 2); + var buttonOk = UiUtil.MakeButtonOk(vm.OkCommand); + var buttonCancel = UiUtil.MakeButtonCancel(vm.CancelCommand); - Content = grid; + Content = LineToolsChrome.MakeRoot( + LineToolsChrome.MakeSections(panelControls, MakeSubtitleView(vm)), + LineToolsChrome.MakeStatusText(vm, nameof(vm.StatusText)), + buttonCancel, + buttonOk); UiUtil.FocusOnFirstActivation(this, comboBoxFrom); // initial focus on an input, not an action button - a focused button clicks on bare Space KeyDown += (_, e) => vm.OnKeyDown(e); @@ -145,7 +116,7 @@ private static Border MakeSubtitleView(ConvertActorsViewModel vm) // Sorting dropped in the DataGrid -> TableView conversion: the grid previews // subtitle lines in timeline order. - var dataGrid = TableViewExtras.MakeTableView(); + var dataGrid = TableViewExtras.MakeTableView().WithAccessibleName(Se.Language.General.Preview); dataGrid.Width = double.NaN; dataGrid.Height = double.NaN; dataGrid.DataContext = vm; @@ -237,6 +208,6 @@ private static Border MakeSubtitleView(ConvertActorsViewModel vm) TableViewExtras.AddSpaceToggle(dataGrid, item => item.IsChecked, (item, v) => item.IsChecked = v); - return UiUtil.MakeBorderForControlNoPadding(dataGrid); + return LineToolsChrome.MakeListSection(Se.Language.General.Preview, dataGrid); } } diff --git a/src/ui/Features/Tools/LineToolsChrome.cs b/src/ui/Features/Tools/LineToolsChrome.cs new file mode 100644 index 00000000000..628fcbcd0d3 --- /dev/null +++ b/src/ui/Features/Tools/LineToolsChrome.cs @@ -0,0 +1,183 @@ +using Avalonia; +using Avalonia.Automation; +using Avalonia.Controls; +using Avalonia.Data; +using Avalonia.Layout; +using Avalonia.Media; +using Nikse.SubtitleEdit.Features.Edit; +using Nikse.SubtitleEdit.Features.Sync; +using Nikse.SubtitleEdit.Logic; +using Nikse.SubtitleEdit.Logic.Config; +using System.Linq; + +namespace Nikse.SubtitleEdit.Features.Tools; + +/// +/// The shared layout of the Tools menu's line tools (split/merge lines, convert actors, change +/// formatting, sort, remove Unicode characters, renumber) in the editor look (see +/// ): the options on top, the preview list(s) below, a summary plus +/// Cancel and OK in the button bar. +/// +internal static class LineToolsChrome +{ + /// + /// Window background, the editor styles, compact combo boxes and text fields. + /// + internal static void Apply(Window window) + { + EditDialogChrome.Apply(window); + } + + /// + /// A number field from made compact (no spinner), named for screen readers. + /// + internal static NumericUpDown MakeCompact(NumericUpDown field, string accessibleName, double width = 100) + { + field.Classes.Add("field"); + field.Width = width; + field.HorizontalAlignment = HorizontalAlignment.Left; + field.VerticalAlignment = VerticalAlignment.Center; + AutomationProperties.SetName(field, accessibleName.TrimEnd(':')); + return field; + } + + /// + /// "Label [field]" rows with the labels in a column sized to the longest one (so + /// translations do not get clipped). + /// + internal static Grid MakeFieldGrid(params (string Label, Control Field)[] rows) + { + var grid = new Grid + { + ColumnSpacing = 12, + RowSpacing = 8, + ColumnDefinitions = { new ColumnDefinition(GridLength.Auto), new ColumnDefinition(GridLength.Auto) }, + }; + for (var i = 0; i < rows.Length; i++) + { + grid.RowDefinitions.Add(new RowDefinition(GridLength.Auto)); + grid.Add(EditorChrome.MakeDimLabel(rows[i].Label.TrimEnd(':'), new Thickness(0)), i, 0); + grid.Add(rows[i].Field, i, 1); + } + + return grid; + } + + /// + /// An on/off switch with its text, bound to of the inherited data context. + /// + internal static StackPanel MakeSwitchRow(string property, string text, out ToggleSwitch toggle) + { + var row = EditorChrome.MakeToggleRow(property, text); + toggle = row.Children.OfType().First(); + return row; + } + + /// + /// Icon actions for a list section header (select all / none / invert). + /// + internal static StackPanel MakeListActions(params Control[] buttons) + { + var panel = new StackPanel + { + Orientation = Orientation.Horizontal, + Spacing = 2, + VerticalAlignment = VerticalAlignment.Center, + }; + foreach (var button in buttons) + { + panel.Children.Add(button); + } + + return panel; + } + + internal static Button MakeSelectAllButton(System.Windows.Input.ICommand command) => + EditorChrome.CreateToolButton("fa-solid fa-square-check", Se.Language.General.SelectAll, command, 26); + + internal static Button MakeSelectNoneButton(System.Windows.Input.ICommand command) => + EditorChrome.CreateToolButton("fa-regular fa-square", Se.Language.General.SelectNone, command, 26); + + internal static Button MakeInvertSelectionButton(System.Windows.Input.ICommand command) => + EditorChrome.CreateToolButton("fa-solid fa-right-left", Se.Language.General.InvertSelection, command, 26); + + /// + /// A section whose list fills the rest of the window: the small caps header (with optional + /// actions on the right) above the list in a rounded frame. + /// + internal static Border MakeListSection(string title, Control list, Control? headerRight = null, bool first = false) + { + var header = new DockPanel { MinHeight = 26 }; + if (headerRight != null) + { + DockPanel.SetDock(headerRight, Dock.Right); + header.Children.Add(headerRight); + } + + header.Children.Add(EditorChrome.MakeSectionHeader(title)); + + var content = new Grid + { + RowSpacing = 8, + RowDefinitions = { new RowDefinition(GridLength.Auto), new RowDefinition(new GridLength(1, GridUnitType.Star)) }, + }; + content.Add(header, 0, 0); + content.Add(EditDialogChrome.MakeListFrame(list), 1, 0); + return new Border + { + Child = content, + BorderBrush = EditorChrome.LineBrush, + BorderThickness = new Thickness(0, first ? 0 : 1, 0, 0), + Padding = new Thickness(14, 12, 14, 14), + }; + } + + /// + /// The options (auto height) above one or more list sections that share the rest. + /// + internal static Grid MakeSections(Control? options, params Control[] lists) + { + var grid = new Grid(); + var row = 0; + if (options != null) + { + grid.RowDefinitions.Add(new RowDefinition(GridLength.Auto)); + grid.Add(options, row++, 0); + } + + foreach (var list in lists) + { + grid.RowDefinitions.Add(new RowDefinition(new GridLength(1, GridUnitType.Star)) { MinHeight = 120 }); + grid.Add(list, row++, 0); + } + + return grid; + } + + /// + /// A summary text (the fixes found, lines changed, ...) for the left side of the button bar. + /// + internal static TextBlock MakeStatusText(object vm, string property) + { + return new TextBlock + { + DataContext = vm, + Foreground = EditorChrome.DimTextBrush, + FontSize = 12.5, + VerticalAlignment = VerticalAlignment.Center, + TextTrimming = TextTrimming.CharacterEllipsis, + Margin = new Thickness(0, 0, 12, 0), + [!TextBlock.TextProperty] = new Binding(property), + }; + } + + /// + /// The sections on the panel color above the button bar with the summary, Cancel and the + /// accent OK. + /// + internal static Grid MakeRoot(Control sections, Control? status, Button buttonCancel, Button buttonOk) + { + buttonOk.Classes.Add("primary"); + return SyncTimingChrome.MakeRoot(sections, SyncTimingChrome.MakeButtonBar(status, buttonCancel, buttonOk)); + } +} diff --git a/src/ui/Features/Tools/MergeContinuationLines/MergeContinuationLinesWindow.cs b/src/ui/Features/Tools/MergeContinuationLines/MergeContinuationLinesWindow.cs index 2d13276206b..30c4947383b 100644 --- a/src/ui/Features/Tools/MergeContinuationLines/MergeContinuationLinesWindow.cs +++ b/src/ui/Features/Tools/MergeContinuationLines/MergeContinuationLinesWindow.cs @@ -14,6 +14,10 @@ namespace Nikse.SubtitleEdit.Features.Tools.MergeContinuationLines; +/// +/// Merge continuation lines, in the editor look (see ): the options on +/// top, the candidates below, the summary plus Cancel and OK in the button bar. +/// public class MergeContinuationLinesWindow : Window { private NumericUpDown _numericGap = null!; @@ -27,39 +31,18 @@ public MergeContinuationLinesWindow(MergeContinuationLinesViewModel vm) Height = 700; MinWidth = 700; MinHeight = 400; + LineToolsChrome.Apply(this); vm.Window = this; DataContext = vm; var buttonOk = UiUtil.MakeButtonOk(vm.OkCommand); var buttonCancel = UiUtil.MakeButtonCancel(vm.CancelCommand); - var panelButtons = UiUtil.MakeButtonBar(buttonOk, buttonCancel); - var grid = new Grid - { - RowDefinitions = - { - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Star) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - }, - ColumnDefinitions = - { - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) }, - }, - Margin = UiUtil.MakeWindowMargin(), - ColumnSpacing = 10, - RowSpacing = 10, - Width = double.NaN, - HorizontalAlignment = HorizontalAlignment.Stretch, - }; - - grid.Add(MakeControlsView(vm), 0); - grid.Add(MakeCandidatesView(vm), 1); - grid.Add(MakeSelectionButtonsView(vm), 2); - grid.Add(panelButtons, 3); - - Content = grid; + Content = LineToolsChrome.MakeRoot( + LineToolsChrome.MakeSections(MakeControlsView(vm), MakeCandidatesView(vm)), + LineToolsChrome.MakeStatusText(vm, nameof(vm.CandidatesInfo)), + buttonCancel, + buttonOk); UiUtil.FocusOnFirstActivation(this, _numericGap); // initial focus on an input, not an action button - a focused button clicks on bare Space KeyDown += vm.KeyDown; @@ -69,71 +52,27 @@ public MergeContinuationLinesWindow(MergeContinuationLinesViewModel vm) Loaded += delegate { UiUtil.RestoreWindowPosition(this); }; } - private Grid MakeControlsView(MergeContinuationLinesViewModel vm) + private Border MakeControlsView(MergeContinuationLinesViewModel vm) { - var grid = new Grid - { - RowDefinitions = - { - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - }, - ColumnDefinitions = - { - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Auto) }, - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Auto) }, - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) }, - }, - ColumnSpacing = 10, - RowSpacing = 10, - HorizontalAlignment = HorizontalAlignment.Stretch, - }; - - var labelGap = UiUtil.MakeLabel(Se.Language.Tools.MergeContinuationLines.MaxMillisecondsBetweenLines); - var numericGap = UiUtil.MakeNumericUpDownInt(0, 10000, 250, 150, vm, nameof(vm.MaxMillisecondsBetweenLines)); + var labelGap = Se.Language.Tools.MergeContinuationLines.MaxMillisecondsBetweenLines; + var numericGap = LineToolsChrome.MakeCompact(UiUtil.MakeNumericUpDownInt(0, 10000, 250, 150, vm, nameof(vm.MaxMillisecondsBetweenLines)), labelGap); _numericGap = numericGap; numericGap.ValueChanged += (_, _) => vm.SetChanged(); - var labelMax = UiUtil.MakeLabel(Se.Language.Tools.MergeContinuationLines.MaxCharacters); - var numericMax = UiUtil.MakeNumericUpDownInt(20, 1000, 100, 150, vm, nameof(vm.MaxCharacters)); + var labelMax = Se.Language.Tools.MergeContinuationLines.MaxCharacters; + var numericMax = LineToolsChrome.MakeCompact(UiUtil.MakeNumericUpDownInt(20, 1000, 100, 150, vm, nameof(vm.MaxCharacters)), labelMax); numericMax.ValueChanged += (_, _) => vm.SetChanged(); - grid.Add(labelGap, 0, 0); - grid.Add(numericGap, 1, 0); - - grid.Add(labelMax, 0, 1); - grid.Add(numericMax, 1, 1); - - return grid; + var fields = LineToolsChrome.MakeFieldGrid((labelGap, numericGap), (labelMax, numericMax)); + return EditorChrome.MakeEditorSection(Se.Language.General.Options, fields, first: true); } - private static Grid MakeCandidatesView(MergeContinuationLinesViewModel vm) + private static Border MakeCandidatesView(MergeContinuationLinesViewModel vm) { - var grid = new Grid - { - RowDefinitions = - { - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Star) }, - }, - ColumnDefinitions = - { - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) }, - }, - ColumnSpacing = 10, - RowSpacing = 10, - HorizontalAlignment = HorizontalAlignment.Stretch, - }; - - var labelInfo = UiUtil.MakeLabel() - .WithBindText(vm, nameof(vm.CandidatesInfo)) - .WithMarginTop(10) - .WithMarginLeft(10); - // The DataGrid this replaces used DataGridCheckboxMultiSelect for extended // selection + Space toggling; shift/ctrl multi-select is native ListBox behavior // on TableView, so only the Space toggle needs wiring (below). - var dataGrid = TableViewExtras.MakeTableView(); + var dataGrid = TableViewExtras.MakeTableView().WithAccessibleName(Se.Language.General.Preview); dataGrid.Width = double.NaN; dataGrid.Height = double.NaN; dataGrid.DataContext = vm; @@ -204,16 +143,10 @@ private static Grid MakeCandidatesView(MergeContinuationLinesViewModel vm) item => item.IsSelected, (item, value) => item.IsSelected = value); - grid.Add(labelInfo, 0); - grid.Add(UiUtil.MakeBorderForControlNoPadding(dataGrid), 1); - - return grid; - } + var actions = LineToolsChrome.MakeListActions( + LineToolsChrome.MakeSelectAllButton(vm.SelectAllCommand), + LineToolsChrome.MakeInvertSelectionButton(vm.InverseSelectionCommand)); - private static StackPanel MakeSelectionButtonsView(MergeContinuationLinesViewModel vm) - { - return UiUtil.MakeButtonBar( - UiUtil.MakeButton(Se.Language.General.SelectAll, vm.SelectAllCommand), - UiUtil.MakeButton(Se.Language.General.InvertSelection, vm.InverseSelectionCommand)); + return LineToolsChrome.MakeListSection(Se.Language.General.Preview, dataGrid, actions); } } diff --git a/src/ui/Features/Tools/MergeShortLines/MergeShortLinesWindow.cs b/src/ui/Features/Tools/MergeShortLines/MergeShortLinesWindow.cs index a11a9b0b88f..3c359908a8d 100644 --- a/src/ui/Features/Tools/MergeShortLines/MergeShortLinesWindow.cs +++ b/src/ui/Features/Tools/MergeShortLines/MergeShortLinesWindow.cs @@ -14,6 +14,10 @@ namespace Nikse.SubtitleEdit.Features.Tools.MergeShortLines; +/// +/// Merge short lines, in the editor look (see ): the options on top, +/// the preview of the merges below, the summary plus Cancel and OK in the button bar. +/// public class MergeShortLinesWindow : Window { private NumericUpDown _numericUpDownSingleLineMaxLength = null!; @@ -27,37 +31,18 @@ public MergeShortLinesWindow(MergeShortLinesViewModel vm) Height = 800; MinWidth = 600; MinHeight = 400; + LineToolsChrome.Apply(this); vm.Window = this; DataContext = vm; var buttonOk = UiUtil.MakeButtonOk(vm.OkCommand); var buttonCancel = UiUtil.MakeButtonCancel(vm.CancelCommand); - var panelButtons = UiUtil.MakeButtonBar(buttonOk, buttonCancel); - var grid = new Grid - { - RowDefinitions = - { - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Star) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - }, - ColumnDefinitions = - { - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) }, - }, - Margin = UiUtil.MakeWindowMargin(), - ColumnSpacing = 10, - RowSpacing = 10, - Width = double.NaN, - HorizontalAlignment = HorizontalAlignment.Stretch, - }; - - grid.Add(MakeControlsView(vm), 0); - grid.Add(MakeFixesView(vm), 1); - grid.Add(panelButtons, 2); - - Content = grid; + Content = LineToolsChrome.MakeRoot( + LineToolsChrome.MakeSections(MakeControlsView(vm), MakeFixesView(vm)), + LineToolsChrome.MakeStatusText(vm, nameof(vm.FixesInfo)), + buttonCancel, + buttonOk); UiUtil.FocusOnFirstActivation(this, _numericUpDownSingleLineMaxLength); // initial focus on an input, not an action button - a focused button clicks on bare Space KeyDown += vm.KeyDown; @@ -67,75 +52,39 @@ public MergeShortLinesWindow(MergeShortLinesViewModel vm) Loaded += delegate { UiUtil.RestoreWindowPosition(this); }; } - private Grid MakeControlsView(MergeShortLinesViewModel vm) + private Border MakeControlsView(MergeShortLinesViewModel vm) { - var grid = new Grid - { - RowDefinitions = - { - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - }, - ColumnDefinitions = - { - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Auto) }, - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) }, - }, - ColumnSpacing = 10, - RowSpacing = 10, - HorizontalAlignment = HorizontalAlignment.Stretch, - }; - - var labelSingleLineMaxLength = UiUtil.MakeLabel(Se.Language.Options.Settings.SingleLineMaxLength); - var numericUpDownSingleLineMaxLength = UiUtil.MakeNumericUpDownInt(5, 1000, 10, 130, vm, nameof(vm.SingleLineMaxLength)); + var labelSingleLineMaxLength = Se.Language.Options.Settings.SingleLineMaxLength; + var numericUpDownSingleLineMaxLength = LineToolsChrome.MakeCompact(UiUtil.MakeNumericUpDownInt(5, 1000, 10, 130, vm, nameof(vm.SingleLineMaxLength)), labelSingleLineMaxLength); _numericUpDownSingleLineMaxLength = numericUpDownSingleLineMaxLength; numericUpDownSingleLineMaxLength.ValueChanged += (s, e) => vm.SetChanged(); - var labelMaxNumberOfLines = UiUtil.MakeLabel(Se.Language.Options.Settings.MaxLines); - var numericUpDownMaxNumberOfLines = UiUtil.MakeNumericUpDownInt(2, 10, 2, 130, vm, nameof(vm.MaxNumberOfLines)); + var labelMaxNumberOfLines = Se.Language.Options.Settings.MaxLines; + var numericUpDownMaxNumberOfLines = LineToolsChrome.MakeCompact(UiUtil.MakeNumericUpDownInt(2, 10, 2, 130, vm, nameof(vm.MaxNumberOfLines)), labelMaxNumberOfLines); numericUpDownMaxNumberOfLines.ValueChanged += (s, e) => vm.SetChanged(); - var checkBoxHighlightParts = UiUtil.MakeCheckBox(Se.Language.Tools.MergeShortLines.HighlightParts, vm, nameof(vm.HighLight)); + var rowHighlightParts = LineToolsChrome.MakeSwitchRow(nameof(vm.HighLight), Se.Language.Tools.MergeShortLines.HighlightParts, out var checkBoxHighlightParts); checkBoxHighlightParts.IsCheckedChanged += (s, e) => vm.SetChanged(); - grid.Add(labelSingleLineMaxLength, 0, 0); - grid.Add(numericUpDownSingleLineMaxLength, 0, 1); - - grid.Add(labelMaxNumberOfLines, 1, 0); - grid.Add(numericUpDownMaxNumberOfLines, 1, 1); - - grid.Add(checkBoxHighlightParts, 2, 1); - - return grid; - } - - private static Grid MakeFixesView(MergeShortLinesViewModel vm) - { var grid = new Grid { - RowDefinitions = - { - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Star) }, - }, - ColumnDefinitions = - { - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) }, - }, - ColumnSpacing = 10, - RowSpacing = 10, - HorizontalAlignment = HorizontalAlignment.Stretch, + ColumnSpacing = 40, + ColumnDefinitions = { new ColumnDefinition(GridLength.Auto), new ColumnDefinition(GridLength.Auto) }, }; + grid.Add(LineToolsChrome.MakeFieldGrid( + (labelSingleLineMaxLength, numericUpDownSingleLineMaxLength), + (labelMaxNumberOfLines, numericUpDownMaxNumberOfLines)), 0, 0); + rowHighlightParts.VerticalAlignment = VerticalAlignment.Top; + grid.Add(rowHighlightParts, 0, 1); - var labelFixesAvailable = UiUtil.MakeLabel() - .WithBindText(vm, nameof(vm.FixesInfo)) - .WithMarginTop(10) - .WithMarginLeft(10); + return EditorChrome.MakeEditorSection(Se.Language.General.Options, grid, first: true); + } + private static Border MakeFixesView(MergeShortLinesViewModel vm) + { // Sorting dropped in the DataGrid -> TableView conversion: the grid previews // merge candidates in subtitle order. - var dataGrid = TableViewExtras.MakeTableView(); + var dataGrid = TableViewExtras.MakeTableView().WithAccessibleName(Se.Language.General.Preview); dataGrid.Width = double.NaN; dataGrid.Height = double.NaN; dataGrid.DataContext = vm; @@ -246,9 +195,11 @@ private static Grid MakeFixesView(MergeShortLinesViewModel vm) } }, RoutingStrategies.Tunnel); - grid.Add(labelFixesAvailable, 0); - grid.Add(UiUtil.MakeBorderForControlNoPadding(dataGrid), 1); + var actions = LineToolsChrome.MakeListActions( + LineToolsChrome.MakeSelectAllButton(vm.SelectAllCommand), + LineToolsChrome.MakeSelectNoneButton(vm.SelectNoneCommand), + LineToolsChrome.MakeInvertSelectionButton(vm.InvertSelectionCommand)); - return grid; + return LineToolsChrome.MakeListSection(Se.Language.General.Preview, dataGrid, actions); } } diff --git a/src/ui/Features/Tools/MergeSubtitlesWithSameText/MergeSameTextWindow.cs b/src/ui/Features/Tools/MergeSubtitlesWithSameText/MergeSameTextWindow.cs index c8e60878217..bf33971ccdf 100644 --- a/src/ui/Features/Tools/MergeSubtitlesWithSameText/MergeSameTextWindow.cs +++ b/src/ui/Features/Tools/MergeSubtitlesWithSameText/MergeSameTextWindow.cs @@ -15,6 +15,10 @@ namespace Nikse.SubtitleEdit.Features.Tools.MergeSubtitlesWithSameText; +/// +/// Merge lines with same text, in the editor look (see ): the options on top, the +/// lines to merge and a preview of the result below, Cancel and OK in the button bar. +/// public class MergeSameTextWindow : Window { public MergeSameTextWindow(MergeSameTextViewModel vm) @@ -26,39 +30,21 @@ public MergeSameTextWindow(MergeSameTextViewModel vm) Height = 800; MinWidth = 600; MinHeight = 400; + LineToolsChrome.Apply(this); vm.Window = this; DataContext = vm; var buttonOk = UiUtil.MakeButtonOk(vm.OkCommand).WithBindEnabled(nameof(vm.IsOkEnabled)); var buttonCancel = UiUtil.MakeButtonCancel(vm.CancelCommand); - var panelButtons = UiUtil.MakeButtonBar(buttonOk, buttonCancel); - var grid = new Grid - { - RowDefinitions = - { - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Star) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Star) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - }, - ColumnDefinitions = - { - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) }, - }, - Margin = UiUtil.MakeWindowMargin(), - ColumnSpacing = 10, - RowSpacing = 10, - Width = double.NaN, - HorizontalAlignment = HorizontalAlignment.Stretch, - }; - - grid.Add(MakeControlsView(vm), 0); - grid.Add(MakeMergesView(vm), 1); - grid.Add(MakeSubtitlesView(vm), 2); - grid.Add(panelButtons, 3); - - Content = grid; + Content = LineToolsChrome.MakeRoot( + LineToolsChrome.MakeSections( + EditorChrome.MakeEditorSection(Se.Language.General.Options, MakeControlsView(vm), first: true), + LineToolsChrome.MakeListSection(Se.Language.General.MergeLines, MakeMergesView(vm)), + LineToolsChrome.MakeListSection(Se.Language.General.Preview, MakeSubtitlesView(vm))), + null, + buttonCancel, + buttonOk); UiUtil.FocusOnFirstActivation(this, buttonCancel); // hack to make OnKeyDown work KeyDown += vm.OnKeyDown; @@ -67,21 +53,26 @@ public MergeSameTextWindow(MergeSameTextViewModel vm) Loaded += delegate { UiUtil.RestoreWindowPosition(this); }; } - private static StackPanel MakeControlsView(MergeSameTextViewModel vm) + private static Grid MakeControlsView(MergeSameTextViewModel vm) { - var labelGap = UiUtil.MakeLabel(vm.MaxBetweenLinesLabel); - var numericUpDownGap = UiUtil.MakeNumericUpDownInt(0, 10000, vm.MaxMsOrFramesBetweenLines, 130, vm, nameof(vm.MaxMsOrFramesBetweenLines)); + var numericUpDownGap = LineToolsChrome.MakeCompact(UiUtil.MakeNumericUpDownInt(0, 10000, vm.MaxMsOrFramesBetweenLines, 130, vm, nameof(vm.MaxMsOrFramesBetweenLines)), vm.MaxBetweenLinesLabel); numericUpDownGap.ValueChanged += (s, e) => { vm.SetDirty(); }; - var checkBoxIncludeIncrementText = UiUtil.MakeCheckBox(Se.Language.Tools.MergeLinesWithSameText.IncludeIncrementingLines, vm, nameof(vm.IncludeIncrementingLines)); + var rowIncludeIncrementText = LineToolsChrome.MakeSwitchRow(nameof(vm.IncludeIncrementingLines), Se.Language.Tools.MergeLinesWithSameText.IncludeIncrementingLines, out var checkBoxIncludeIncrementText); checkBoxIncludeIncrementText.IsCheckedChanged += (s, e) => { vm.SetDirty(); }; - var checkBoxIncludeRollUp = UiUtil.MakeCheckBox(Se.Language.Tools.MergeLinesWithSameText.IncludeRollUpCaptions, vm, nameof(vm.IncludeRollUpCaptions)); + var rowIncludeRollUp = LineToolsChrome.MakeSwitchRow(nameof(vm.IncludeRollUpCaptions), Se.Language.Tools.MergeLinesWithSameText.IncludeRollUpCaptions, out var checkBoxIncludeRollUp); checkBoxIncludeRollUp.IsCheckedChanged += (s, e) => { vm.SetDirty(); }; - var panelGap = UiUtil.MakeHorizontalPanel(labelGap, numericUpDownGap, checkBoxIncludeIncrementText, checkBoxIncludeRollUp); - return panelGap; + var grid = new Grid + { + ColumnSpacing = 40, + ColumnDefinitions = { new ColumnDefinition(GridLength.Auto), new ColumnDefinition(GridLength.Auto) }, + }; + grid.Add(LineToolsChrome.MakeFieldGrid((vm.MaxBetweenLinesLabel, numericUpDownGap)), 0, 0); + grid.Add(new StackPanel { Spacing = 8, Children = { rowIncludeIncrementText, rowIncludeRollUp } }, 0, 1); + return grid; } - private static Border MakeMergesView(MergeSameTextViewModel vm) + private static TableView MakeMergesView(MergeSameTextViewModel vm) { // Sorting dropped in the DataGrid -> TableView conversion: the grid shows // merge candidates in subtitle order. @@ -152,10 +143,10 @@ private static Border MakeMergesView(MergeSameTextViewModel vm) } }, RoutingStrategies.Tunnel); - return UiUtil.MakeBorderForControlNoPadding(dataGrid); + return dataGrid; } - private static Border MakeSubtitlesView(MergeSameTextViewModel vm) + private static TableView MakeSubtitlesView(MergeSameTextViewModel vm) { var fullTimeConverter = new TimeSpanToDisplayFullConverter(); @@ -225,6 +216,6 @@ private static Border MakeSubtitlesView(MergeSameTextViewModel vm) } }, RoutingStrategies.Tunnel); - return UiUtil.MakeBorderForControlNoPadding(dataGrid); + return dataGrid; } } diff --git a/src/ui/Features/Tools/MergeSubtitlesWithSameTimeCodes/MergeSameTimeCodesWindow.cs b/src/ui/Features/Tools/MergeSubtitlesWithSameTimeCodes/MergeSameTimeCodesWindow.cs index 14db2882cc3..40d62cede37 100644 --- a/src/ui/Features/Tools/MergeSubtitlesWithSameTimeCodes/MergeSameTimeCodesWindow.cs +++ b/src/ui/Features/Tools/MergeSubtitlesWithSameTimeCodes/MergeSameTimeCodesWindow.cs @@ -15,6 +15,10 @@ namespace Nikse.SubtitleEdit.Features.Tools.MergeSubtitlesWithSameTimeCodes; +/// +/// Merge lines with same time codes, in the editor look (see ): the options on top, the +/// lines to merge and a preview of the result below, Cancel and OK in the button bar. +/// public class MergeSameTimeCodesWindow : Window { private readonly MergeSameTimeCodesViewModel _vm; @@ -29,6 +33,7 @@ public MergeSameTimeCodesWindow(MergeSameTimeCodesViewModel vm) Height = 750; MinWidth = 600; MinHeight = 400; + LineToolsChrome.Apply(this); _vm = vm; vm.Window = this; @@ -36,34 +41,15 @@ public MergeSameTimeCodesWindow(MergeSameTimeCodesViewModel vm) var buttonOk = UiUtil.MakeButtonOk(vm.OkCommand).WithBindEnabled(nameof(vm.IsOkEnabled)); var buttonCancel = UiUtil.MakeButtonCancel(vm.CancelCommand); - var panelButtons = UiUtil.MakeButtonBar(buttonOk, buttonCancel); - var grid = new Grid - { - RowDefinitions = - { - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Star) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Star) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - }, - ColumnDefinitions = - { - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) }, - }, - Margin = UiUtil.MakeWindowMargin(), - ColumnSpacing = 10, - RowSpacing = 10, - Width = double.NaN, - HorizontalAlignment = HorizontalAlignment.Stretch, - }; - - grid.Add(MakeControlsView(vm), 0); - grid.Add(MakeMergesView(vm), 1); - grid.Add(MakeSubtitlesView(vm), 2); - grid.Add(panelButtons, 3); - - Content = grid; + Content = LineToolsChrome.MakeRoot( + LineToolsChrome.MakeSections( + EditorChrome.MakeEditorSection(Se.Language.General.Options, MakeControlsView(vm), first: true), + LineToolsChrome.MakeListSection(Se.Language.General.MergeLines, MakeMergesView(vm)), + LineToolsChrome.MakeListSection(Se.Language.General.Preview, MakeSubtitlesView(vm))), + null, + buttonCancel, + buttonOk); UiUtil.FocusOnFirstActivation(this, _numericUpDownMaxDiff); // initial focus on an input, not an action button - a focused button clicks on bare Space KeyDown += _vm.OnKeyDown; @@ -72,22 +58,27 @@ public MergeSameTimeCodesWindow(MergeSameTimeCodesViewModel vm) Loaded += delegate { UiUtil.RestoreWindowPosition(this); }; } - private StackPanel MakeControlsView(MergeSameTimeCodesViewModel vm) + private Grid MakeControlsView(MergeSameTimeCodesViewModel vm) { - var labelMaxDiff = UiUtil.MakeLabel(vm.MaxDifferenceLabel); - var numericUpDownMaxDiff = UiUtil.MakeNumericUpDownInt(0, 10000, vm.MaxMsOrFramesDifference, 130, vm, nameof(vm.MaxMsOrFramesDifference)); + var numericUpDownMaxDiff = LineToolsChrome.MakeCompact(UiUtil.MakeNumericUpDownInt(0, 10000, vm.MaxMsOrFramesDifference, 130, vm, nameof(vm.MaxMsOrFramesDifference)), vm.MaxDifferenceLabel); _numericUpDownMaxDiff = numericUpDownMaxDiff; numericUpDownMaxDiff.ValueChanged += (s, e) => { vm.SetDirty(); }; - var checkBoxMergeAsDialog = UiUtil.MakeCheckBox(Se.Language.Tools.MergeLinesWithSameTimeCodes.MakeDialog, vm, nameof(vm.MergeDialog)); + var rowMergeAsDialog = LineToolsChrome.MakeSwitchRow(nameof(vm.MergeDialog), Se.Language.Tools.MergeLinesWithSameTimeCodes.MakeDialog, out var checkBoxMergeAsDialog); checkBoxMergeAsDialog.IsCheckedChanged += (s, e) => { vm.SetDirty(); }; - var checkBoxAutoBreak = UiUtil.MakeCheckBox(Se.Language.General.AutoBreak, vm, nameof(vm.AutoBreak)); + var rowAutoBreak = LineToolsChrome.MakeSwitchRow(nameof(vm.AutoBreak), Se.Language.General.AutoBreak, out var checkBoxAutoBreak); checkBoxAutoBreak.IsCheckedChanged += (s, e) => { vm.SetDirty(); }; - var panel = UiUtil.MakeHorizontalPanel(labelMaxDiff, numericUpDownMaxDiff, checkBoxMergeAsDialog, checkBoxAutoBreak); - return panel; + var grid = new Grid + { + ColumnSpacing = 40, + ColumnDefinitions = { new ColumnDefinition(GridLength.Auto), new ColumnDefinition(GridLength.Auto) }, + }; + grid.Add(LineToolsChrome.MakeFieldGrid((vm.MaxDifferenceLabel, numericUpDownMaxDiff)), 0, 0); + grid.Add(new StackPanel { Spacing = 8, Children = { rowMergeAsDialog, rowAutoBreak } }, 0, 1); + return grid; } - private static Border MakeMergesView(MergeSameTimeCodesViewModel vm) + private static TableView MakeMergesView(MergeSameTimeCodesViewModel vm) { // Sorting dropped in the DataGrid -> TableView conversion: the grid shows // merge candidates in subtitle order. @@ -158,10 +149,10 @@ private static Border MakeMergesView(MergeSameTimeCodesViewModel vm) } }, RoutingStrategies.Tunnel); - return UiUtil.MakeBorderForControlNoPadding(dataGrid); + return dataGrid; } - private static Border MakeSubtitlesView(MergeSameTimeCodesViewModel vm) + private static TableView MakeSubtitlesView(MergeSameTimeCodesViewModel vm) { var fullTimeConverter = new TimeSpanToDisplayFullConverter(); @@ -231,6 +222,6 @@ private static Border MakeSubtitlesView(MergeSameTimeCodesViewModel vm) } }, RoutingStrategies.Tunnel); - return UiUtil.MakeBorderForControlNoPadding(dataGrid); + return dataGrid; } } diff --git a/src/ui/Features/Tools/RemoveUnicodeCharacters/RemoveUnicodeCharactersWindow.cs b/src/ui/Features/Tools/RemoveUnicodeCharacters/RemoveUnicodeCharactersWindow.cs index 6ac08c1e9f8..f4be37cc4c4 100644 --- a/src/ui/Features/Tools/RemoveUnicodeCharacters/RemoveUnicodeCharactersWindow.cs +++ b/src/ui/Features/Tools/RemoveUnicodeCharacters/RemoveUnicodeCharactersWindow.cs @@ -11,6 +11,10 @@ namespace Nikse.SubtitleEdit.Features.Tools.RemoveUnicodeCharacters; +/// +/// Remove Unicode characters, in the editor look (see ): the characters +/// found (with what to replace them with), the summary plus Cancel and OK in the button bar. +/// public class RemoveUnicodeCharactersWindow : Window { public RemoveUnicodeCharactersWindow(RemoveUnicodeCharactersViewModel vm) @@ -22,6 +26,7 @@ public RemoveUnicodeCharactersWindow(RemoveUnicodeCharactersViewModel vm) Height = 600; MinWidth = 600; MinHeight = 400; + LineToolsChrome.Apply(this); vm.Window = this; DataContext = vm; @@ -29,7 +34,7 @@ public RemoveUnicodeCharactersWindow(RemoveUnicodeCharactersViewModel vm) // Sorting dropped in the DataGrid -> TableView conversion: the characters are // listed in order of first appearance in the subtitle. - var dataGrid = TableViewExtras.MakeTableView(); + var dataGrid = TableViewExtras.MakeTableView().WithAccessibleName(Se.Language.General.Characters); dataGrid.Width = double.NaN; dataGrid.Height = double.NaN; dataGrid.DataContext = vm; @@ -91,7 +96,7 @@ public RemoveUnicodeCharactersWindow(RemoveUnicodeCharactersViewModel vm) VerticalAlignment = VerticalAlignment.Center, HorizontalAlignment = HorizontalAlignment.Stretch, Margin = new Thickness(2), - }), + }.WithAccessibleName(Se.Language.General.ReplaceWith)), }, new SeTableViewColumn { @@ -130,41 +135,18 @@ public RemoveUnicodeCharactersWindow(RemoveUnicodeCharactersViewModel vm) e.Handled = true; }, Avalonia.Interactivity.RoutingStrategies.Tunnel); - var buttonSelectAll = UiUtil.MakeButton(Se.Language.General.SelectAll, vm.SelectAllCommand); - var buttonInvertSelection = UiUtil.MakeButton(Se.Language.General.InvertSelection, vm.InvertSelectionCommand); - var panelSelection = UiUtil.MakeHorizontalPanel(buttonSelectAll, buttonInvertSelection); - - var labelStatus = UiUtil.MakeLabel(string.Empty).WithBindText(vm, nameof(vm.StatusText)).WithAlignmentTop(); + var actions = LineToolsChrome.MakeListActions( + LineToolsChrome.MakeSelectAllButton(vm.SelectAllCommand), + LineToolsChrome.MakeInvertSelectionButton(vm.InvertSelectionCommand)); var buttonOk = UiUtil.MakeButtonOk(vm.OkCommand); var buttonCancel = UiUtil.MakeButtonCancel(vm.CancelCommand); - var panelButtons = UiUtil.MakeButtonBar(buttonOk, buttonCancel); - - var grid = new Grid - { - RowDefinitions = - { - new RowDefinition { Height = new GridLength(1, GridUnitType.Star) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - }, - ColumnDefinitions = - { - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) }, - }, - Margin = UiUtil.MakeWindowMargin(), - ColumnSpacing = 10, - RowSpacing = 10, - Width = double.NaN, - HorizontalAlignment = HorizontalAlignment.Stretch, - }; - - grid.Add(UiUtil.MakeBorderForControlNoPadding(dataGrid), 0); - grid.Add(panelSelection, 1); - grid.Add(labelStatus, 2); - grid.Add(panelButtons, 2); - Content = grid; + Content = LineToolsChrome.MakeRoot( + LineToolsChrome.MakeSections(null, LineToolsChrome.MakeListSection(Se.Language.General.Characters, dataGrid, actions, first: true)), + LineToolsChrome.MakeStatusText(vm, nameof(vm.StatusText)), + buttonCancel, + buttonOk); UiUtil.FocusOnFirstActivation(this, () => { TableViewExtras.FocusRow(dataGrid); }); // initial focus on an input, not an action button - a focused button clicks on bare Space KeyDown += (_, e) => vm.OnKeyDown(e); diff --git a/src/ui/Features/Tools/Renumber/RenumberWindow.cs b/src/ui/Features/Tools/Renumber/RenumberWindow.cs index d5b521391d7..b4474fa5589 100644 --- a/src/ui/Features/Tools/Renumber/RenumberWindow.cs +++ b/src/ui/Features/Tools/Renumber/RenumberWindow.cs @@ -1,10 +1,13 @@ using Avalonia.Controls; -using Avalonia.Layout; using Nikse.SubtitleEdit.Logic; using Nikse.SubtitleEdit.Logic.Config; namespace Nikse.SubtitleEdit.Features.Tools.Renumber; +/// +/// Renumber, in the editor look (see ): the start number in a section, +/// Cancel and OK in the button bar. +/// public class RenumberWindow : Window { public RenumberWindow(RenumberViewModel vm) @@ -12,41 +15,25 @@ public RenumberWindow(RenumberViewModel vm) UiUtil.InitializeWindow(this, GetType().Name); Title = UiUtil.MakeWindowTitle(Se.Language.Tools.Renumber.Title); CanResize = false; - Width = 350; - Height = 180; + SizeToContent = SizeToContent.Height; + Width = 360; + LineToolsChrome.Apply(this); vm.Window = this; DataContext = vm; - var label = UiUtil.MakeLabel(Se.Language.Tools.Renumber.StartFromNumber); - var numericUpDown = UiUtil.MakeNumericUpDownInt(0, 99999, 1, 150, vm, nameof(vm.StartNumber)); + var numericUpDown = LineToolsChrome.MakeCompact( + UiUtil.MakeNumericUpDownInt(0, 99999, 1, 150, vm, nameof(vm.StartNumber)), + Se.Language.Tools.Renumber.StartFromNumber); - var panelInput = UiUtil.MakeVerticalPanel(label, numericUpDown); + var section = EditorChrome.MakeEditorSection( + Se.Language.Tools.Renumber.Title, + LineToolsChrome.MakeFieldGrid((Se.Language.Tools.Renumber.StartFromNumber, numericUpDown)), + first: true); var buttonOk = UiUtil.MakeButtonOk(vm.OkCommand); var buttonCancel = UiUtil.MakeButtonCancel(vm.CancelCommand); - var panelButtons = UiUtil.MakeButtonBar(buttonOk, buttonCancel); - var grid = new Grid - { - RowDefinitions = - { - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Star) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - }, - ColumnDefinitions = - { - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) }, - }, - Margin = UiUtil.MakeWindowMargin(), - RowSpacing = 10, - HorizontalAlignment = HorizontalAlignment.Stretch, - }; - - grid.Add(panelInput, 0); - grid.Add(panelButtons, 2); - - Content = grid; + Content = LineToolsChrome.MakeRoot(section, null, buttonCancel, buttonOk); Loaded += delegate { numericUpDown.Focus(); }; KeyDown += (_, e) => vm.OnKeyDown(e); diff --git a/src/ui/Features/Tools/SortBy/SortByWindow.cs b/src/ui/Features/Tools/SortBy/SortByWindow.cs index ec428afa9df..6e7661a603b 100644 --- a/src/ui/Features/Tools/SortBy/SortByWindow.cs +++ b/src/ui/Features/Tools/SortBy/SortByWindow.cs @@ -6,7 +6,9 @@ using Avalonia.Interactivity; using Avalonia.Layout; using System.Collections; +using Nikse.SubtitleEdit.Features.Edit; using Nikse.SubtitleEdit.Features.Main; +using Nikse.SubtitleEdit.Features.Sync; using Nikse.SubtitleEdit.Features.Tools.BridgeGaps; using Nikse.SubtitleEdit.Logic; using Nikse.SubtitleEdit.Logic.Config; @@ -14,6 +16,10 @@ namespace Nikse.SubtitleEdit.Features.Tools.SortBy; +/// +/// Sort subtitles, in the editor look (see ): the sort order (criteria +/// list with icon actions) on top, the sorted preview below, Cancel and OK in the button bar. +/// public class SortByWindow : Window { public SortByWindow(SortByViewModel vm) @@ -25,41 +31,18 @@ public SortByWindow(SortByViewModel vm) Height = 800; MinWidth = 1000; MinHeight = 500; + LineToolsChrome.Apply(this); vm.Window = this; DataContext = vm; - var gridControls = MakeSortControls(vm); - - var subtitleView = MakeSubtitleView(vm); - var buttonOk = UiUtil.MakeButtonOk(vm.OkCommand); var buttonCancel = UiUtil.MakeButtonCancel(vm.CancelCommand); - var panelButtons = UiUtil.MakeButtonBar(buttonOk, buttonCancel); - - var grid = new Grid - { - RowDefinitions = - { - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, // Bridge gap smaller than - new RowDefinition { Height = new GridLength(1, GridUnitType.Star) }, // Subtitle view - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, // Buttons - }, - ColumnDefinitions = - { - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) }, - }, - Margin = UiUtil.MakeWindowMargin(), - ColumnSpacing = 10, - RowSpacing = 10, - Width = double.NaN, - HorizontalAlignment = HorizontalAlignment.Stretch, - }; - - grid.Add(gridControls, 0); - grid.Add(subtitleView, 1); - grid.Add(panelButtons, 2); - Content = grid; + Content = LineToolsChrome.MakeRoot( + LineToolsChrome.MakeSections(MakeSortControls(vm), MakeSubtitleView(vm)), + null, + buttonCancel, + buttonOk); Loaded += delegate { buttonOk.Focus(); }; // hack to make OnKeyDown work KeyDown += (_, e) => vm.OnKeyDown(e); @@ -68,70 +51,64 @@ public SortByWindow(SortByViewModel vm) Loaded += delegate { UiUtil.RestoreWindowPosition(this); }; } - private Grid MakeSortControls(SortByViewModel vm) + private Border MakeSortControls(SortByViewModel vm) { - var labelProperty = UiUtil.MakeLabel(Se.Language.General.Property); var comboBoxProperty = new ComboBox { Width = 200, DataContext = vm, ItemsSource = vm.AvailableProperties, - }; + VerticalAlignment = VerticalAlignment.Center, + }.WithAccessibleName(Se.Language.General.Property); comboBoxProperty.Bind(ComboBox.SelectedItemProperty, new Binding(nameof(SortByViewModel.SelectedAvailableProperty)) { Mode = BindingMode.TwoWay }); - var checkBoxAscending = UiUtil.MakeCheckBox(Se.Language.General.Ascending, vm, nameof(SortByViewModel.NewCriterionAscending)); + var rowAscending = LineToolsChrome.MakeSwitchRow(nameof(SortByViewModel.NewCriterionAscending), Se.Language.General.Ascending, out _); - var buttonAdd = UiUtil.MakeButton(Se.Language.General.Add, vm.AddSortCriterionCommand).WithMarginLeft(10); + var buttonAdd = SyncTimingChrome.MakeTextToolButton("fa-solid fa-plus", Se.Language.General.Add, vm.AddSortCriterionCommand); var listBoxSortCriteria = new ListBox { Width = 300, - Height = 200, + Height = 170, DataContext = vm, - }; + }.WithAccessibleName(Se.Language.Tools.SortBy.SortOrder); listBoxSortCriteria.Bind(ListBox.ItemsSourceProperty, new Binding(nameof(SortByViewModel.SortCriteria))); listBoxSortCriteria.Bind(ListBox.SelectedItemProperty, new Binding(nameof(SortByViewModel.SelectedSortCriterion)) { Mode = BindingMode.TwoWay }); listBoxSortCriteria.ItemTemplate = new FuncDataTemplate((criterion, _) => { var textBlock = new TextBlock { - Margin = new Thickness(5) + Margin = new Thickness(5), + FontSize = 12.5, }; textBlock.Bind(TextBlock.TextProperty, new Binding(nameof(SortCriterion.DisplayText))); return textBlock; }, true); - var buttonRemove = UiUtil.MakeButton(Se.Language.General.Remove, vm.RemoveSortCriterionCommand).WithMinWidth(130).WithMarginTop(15); - var buttonMoveUp = UiUtil.MakeButton(Se.Language.General.MoveUp, vm.MoveSortCriterionUpCommand).WithMinWidth(130); - var buttonMoveDown = UiUtil.MakeButton(Se.Language.General.MoveDown, vm.MoveSortCriterionDownCommand).WithMinWidth(130); - var buttonToggle = UiUtil.MakeButton(Se.Language.General.ToggleDirection, vm.ToggleSortDirectionCommand).WithMinWidth(130); - var buttonClear = UiUtil.MakeButton(Se.Language.General.Clear, vm.ClearSortCriteriaCommand).WithMinWidth(130); - var stackPanelButtons = new StackPanel { Orientation = Orientation.Vertical, - Spacing = 5, - Margin = new Thickness(10, 0, 0, 0), + Spacing = 2, + Margin = new Thickness(6, 0, 0, 0), Children = { - buttonRemove, - buttonMoveUp, - buttonMoveDown, - buttonToggle, - buttonClear + EditorChrome.CreateToolButton("fa-solid fa-arrow-up", Se.Language.General.MoveUp, vm.MoveSortCriterionUpCommand, 28), + EditorChrome.CreateToolButton("fa-solid fa-arrow-down", Se.Language.General.MoveDown, vm.MoveSortCriterionDownCommand, 28), + EditorChrome.CreateToolButton("fa-solid fa-arrow-down-up-across-line", Se.Language.General.ToggleDirection, vm.ToggleSortDirectionCommand, 28), + EditorChrome.CreateToolButton("fa-solid fa-minus", Se.Language.General.Remove, vm.RemoveSortCriterionCommand, 28), + EditorChrome.CreateToolButton("fa-solid fa-eraser", Se.Language.General.Clear, vm.ClearSortCriteriaCommand, 28), } }; var stackPanelAdd = new StackPanel { Orientation = Orientation.Horizontal, - Spacing = 10, - Margin = new Thickness(0, 0, 0, 10), + Spacing = 12, Children = { - labelProperty, + EditorChrome.MakeDimLabel(Se.Language.General.Property, new Thickness(0)), comboBoxProperty, - checkBoxAscending, + rowAscending, buttonAdd } }; @@ -141,30 +118,23 @@ private Grid MakeSortControls(SortByViewModel vm) Orientation = Orientation.Horizontal, Children = { - UiUtil.MakeBorderForControl(listBoxSortCriteria), + EditDialogChrome.MakeListFrame(listBoxSortCriteria), stackPanelButtons } }; - var labelSortOrder = UiUtil.MakeLabel(Se.Language.Tools.SortBy.SortOrder).WithBold().WithMarginBottom(5); - listBoxSortCriteria.WithLabeledBy(labelSortOrder); - var mainPanel = new StackPanel { Orientation = Orientation.Vertical, - Margin = new Thickness(10), + Spacing = 10, Children = { - labelSortOrder, stackPanelAdd, stackPanelList } }; - return new Grid - { - Children = { mainPanel } - }; + return EditorChrome.MakeEditorSection(Se.Language.Tools.SortBy.SortOrder, mainPanel, first: true); } private Border MakeSubtitleView(SortByViewModel vm) @@ -236,6 +206,6 @@ private Border MakeSubtitleView(SortByViewModel vm) } }, RoutingStrategies.Tunnel); - return UiUtil.MakeBorderForControlNoPadding(dataGridSubtitle); + return LineToolsChrome.MakeListSection(Se.Language.General.Preview, dataGridSubtitle); } } diff --git a/src/ui/Features/Tools/SplitBreakLongLines/SplitBreakLongLinesWindow.cs b/src/ui/Features/Tools/SplitBreakLongLines/SplitBreakLongLinesWindow.cs index 4972f9c4e7b..a55e3797fac 100644 --- a/src/ui/Features/Tools/SplitBreakLongLines/SplitBreakLongLinesWindow.cs +++ b/src/ui/Features/Tools/SplitBreakLongLines/SplitBreakLongLinesWindow.cs @@ -5,6 +5,7 @@ using Avalonia.Controls.Templates; using Avalonia.Media; using Avalonia.Data; +using Avalonia.Data.Converters; using Avalonia.Input; using Avalonia.Interactivity; using Avalonia.Layout; @@ -14,9 +15,13 @@ namespace Nikse.SubtitleEdit.Features.Tools.SplitBreakLongLines; +/// +/// Split/break long lines, in the editor look (see ): the options on +/// top, the preview of the fixes below, the summary plus Cancel and OK in the button bar. +/// public class SplitBreakLongLinesWindow : Window { - private CheckBox _checkBoxSplitLongLines = null!; + private Control _checkBoxSplitLongLines = null!; public SplitBreakLongLinesWindow(SplitBreakLongLinesViewModel vm) { @@ -27,37 +32,18 @@ public SplitBreakLongLinesWindow(SplitBreakLongLinesViewModel vm) Height = 800; MinWidth = 600; MinHeight = 400; + LineToolsChrome.Apply(this); vm.Window = this; DataContext = vm; var buttonOk = UiUtil.MakeButtonOk(vm.OkCommand); var buttonCancel = UiUtil.MakeButtonCancel(vm.CancelCommand); - var panelButtons = UiUtil.MakeButtonBar(buttonOk, buttonCancel); - var grid = new Grid - { - RowDefinitions = - { - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Star) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - }, - ColumnDefinitions = - { - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) }, - }, - Margin = UiUtil.MakeWindowMargin(), - ColumnSpacing = 10, - RowSpacing = 10, - Width = double.NaN, - HorizontalAlignment = HorizontalAlignment.Stretch, - }; - - grid.Add(MakeControlsView(vm), 0); - grid.Add(MakeFixesView(vm), 1); - grid.Add(panelButtons, 2); - - Content = grid; + Content = LineToolsChrome.MakeRoot( + LineToolsChrome.MakeSections(MakeControlsView(vm), MakeFixesView(vm)), + LineToolsChrome.MakeStatusText(vm, nameof(vm.FixesInfo)), + buttonCancel, + buttonOk); UiUtil.FocusOnFirstActivation(this, _checkBoxSplitLongLines); // initial focus on an input, not an action button - a focused button clicks on bare Space KeyDown += vm.KeyDown; @@ -67,116 +53,66 @@ public SplitBreakLongLinesWindow(SplitBreakLongLinesViewModel vm) Loaded += delegate { UiUtil.RestoreWindowPosition(this); }; } - private Grid MakeControlsView(SplitBreakLongLinesViewModel vm) + private Border MakeControlsView(SplitBreakLongLinesViewModel vm) { - var grid = new Grid - { - RowDefinitions = - { - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - }, - ColumnDefinitions = - { - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Auto) }, - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Auto) }, - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Auto) }, - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) }, - }, - ColumnSpacing = 10, - RowSpacing = 10, - HorizontalAlignment = HorizontalAlignment.Stretch, - }; - - var checkBoxSplitLongLines = UiUtil.MakeCheckBox(Se.Language.Tools.SplitBreakLongLines.SplitLongLines, vm, nameof(vm.SplitLongLines)) - .WithMarginRight(40); + var rowSplitLongLines = LineToolsChrome.MakeSwitchRow(nameof(vm.SplitLongLines), Se.Language.Tools.SplitBreakLongLines.SplitLongLines, out var checkBoxSplitLongLines); _checkBoxSplitLongLines = checkBoxSplitLongLines; checkBoxSplitLongLines.IsCheckedChanged += (s, e) => vm.SetChanged(); - var checkBoxRebalanceLongLines = UiUtil.MakeCheckBox(Se.Language.Tools.SplitBreakLongLines.RebalanceLongLines, vm, nameof(vm.RebalanceLongLines)) - .WithMarginRight(40); + var rowRebalanceLongLines = LineToolsChrome.MakeSwitchRow(nameof(vm.RebalanceLongLines), Se.Language.Tools.SplitBreakLongLines.RebalanceLongLines, out var checkBoxRebalanceLongLines); checkBoxRebalanceLongLines.IsCheckedChanged += (s, e) => vm.SetChanged(); - var checkBoxRebalanceOnlyTooLong = UiUtil.MakeCheckBox(Se.Language.Tools.SplitBreakLongLines.RebalanceOnlyLinesTooLong, vm, nameof(vm.RebalanceOnlyLinesTooLong)) - .WithMarginLeft(25) - .WithMarginRight(40); - checkBoxRebalanceOnlyTooLong[!InputElement.IsEnabledProperty] = new Binding(nameof(vm.RebalanceLongLines)); + var rowRebalanceOnlyTooLong = LineToolsChrome.MakeSwitchRow(nameof(vm.RebalanceOnlyLinesTooLong), Se.Language.Tools.SplitBreakLongLines.RebalanceOnlyLinesTooLong, out var checkBoxRebalanceOnlyTooLong); + rowRebalanceOnlyTooLong.Margin = new Thickness(25, 0, 0, 0); + rowRebalanceOnlyTooLong[!InputElement.IsEnabledProperty] = new Binding(nameof(vm.RebalanceLongLines)); + rowRebalanceOnlyTooLong[!Visual.OpacityProperty] = new Binding(nameof(vm.RebalanceLongLines)) { Converter = new FuncValueConverter(on => on ? 1.0 : 0.5) }; checkBoxRebalanceOnlyTooLong.IsCheckedChanged += (s, e) => vm.SetChanged(); - var labelSingleLineMaxLength = UiUtil.MakeLabel(Se.Language.Options.Settings.SingleLineMaxLength); - var numericUpDownSingleLineMaxLength = UiUtil.MakeNumericUpDownInt(5, 1000, 10, 130, vm, nameof(vm.SingleLineMaxLength)); + var labelSingleLineMaxLength = Se.Language.Options.Settings.SingleLineMaxLength; + var numericUpDownSingleLineMaxLength = LineToolsChrome.MakeCompact(UiUtil.MakeNumericUpDownInt(5, 1000, 10, 130, vm, nameof(vm.SingleLineMaxLength)), labelSingleLineMaxLength); numericUpDownSingleLineMaxLength.ValueChanged += (s, e) => vm.SetChanged(); - var labelMaxNumberOfLines = UiUtil.MakeLabel(Se.Language.Options.Settings.MaxLines); - var numericUpDownMaxNumberOfLines = UiUtil.MakeNumericUpDownInt(1, 10, 2, 130, vm, nameof(vm.MaxNumberOfLines)); + var labelMaxNumberOfLines = Se.Language.Options.Settings.MaxLines; + var numericUpDownMaxNumberOfLines = LineToolsChrome.MakeCompact(UiUtil.MakeNumericUpDownInt(1, 10, 2, 130, vm, nameof(vm.MaxNumberOfLines)), labelMaxNumberOfLines); numericUpDownMaxNumberOfLines.ValueChanged += (s, e) => vm.SetChanged(); - var labelUnbreakLinesShorterThan = UiUtil.MakeLabel(Se.Language.Options.Settings.UnbreakSubtitlesShortThan); - var numericUpDownUnbreakLinesShorterThan = UiUtil.MakeNumericUpDownInt(1, 1000, 33, 130, vm, nameof(vm.UnbreakLinesShorterThan)); + var labelUnbreakLinesShorterThan = Se.Language.Options.Settings.UnbreakSubtitlesShortThan; + var numericUpDownUnbreakLinesShorterThan = LineToolsChrome.MakeCompact(UiUtil.MakeNumericUpDownInt(1, 1000, 33, 130, vm, nameof(vm.UnbreakLinesShorterThan)), labelUnbreakLinesShorterThan); numericUpDownUnbreakLinesShorterThan.ValueChanged += (s, e) => vm.SetChanged(); + + var fields = LineToolsChrome.MakeFieldGrid( + (labelSingleLineMaxLength, numericUpDownSingleLineMaxLength), + (labelMaxNumberOfLines, numericUpDownMaxNumberOfLines), + (labelUnbreakLinesShorterThan, numericUpDownUnbreakLinesShorterThan)); if (Se.Settings.Appearance.ShowHints) { - ToolTip.SetTip(labelUnbreakLinesShorterThan, Se.Language.Tools.SplitBreakLongLines.UnbreakLinesShorterThanHint); + ToolTip.SetTip(fields.Children[4], Se.Language.Tools.SplitBreakLongLines.UnbreakLinesShorterThanHint); // the label ToolTip.SetTip(numericUpDownUnbreakLinesShorterThan, Se.Language.Tools.SplitBreakLongLines.UnbreakLinesShorterThanHint); - ToolTip.SetTip(checkBoxRebalanceOnlyTooLong, Se.Language.Tools.SplitBreakLongLines.RebalanceOnlyLinesTooLongHint); + ToolTip.SetTip(rowRebalanceOnlyTooLong, Se.Language.Tools.SplitBreakLongLines.RebalanceOnlyLinesTooLongHint); } - grid.Add(checkBoxSplitLongLines, 0); - grid.Add(labelSingleLineMaxLength, 0, 1); - grid.Add(numericUpDownSingleLineMaxLength, 0, 2); - - grid.Add(checkBoxRebalanceLongLines, 1); - grid.Add(labelMaxNumberOfLines, 1, 1); - grid.Add(numericUpDownMaxNumberOfLines, 1, 2); - - grid.Add(checkBoxRebalanceOnlyTooLong, 2); - grid.Add(labelUnbreakLinesShorterThan, 2, 1); - grid.Add(numericUpDownUnbreakLinesShorterThan, 2, 2); - - return grid; - } - - private static Grid MakeFixesView(SplitBreakLongLinesViewModel vm) - { - var grid = new Grid + var switches = new StackPanel { - RowDefinitions = - { - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Star) }, - }, - ColumnDefinitions = - { - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) }, - }, - ColumnSpacing = 10, - RowSpacing = 10, - HorizontalAlignment = HorizontalAlignment.Stretch, + Spacing = 8, + Children = { rowSplitLongLines, rowRebalanceLongLines, rowRebalanceOnlyTooLong }, }; - var labelFixesAvailable = UiUtil.MakeLabel() - .WithBindText(vm, nameof(vm.FixesInfo)) - .WithMarginLeft(10); - - // Select all / none only touch the rebalance rows (the ones with a checkbox). - var panelHeader = new StackPanel + var grid = new Grid { - Orientation = Orientation.Horizontal, - Spacing = 5, - VerticalAlignment = VerticalAlignment.Center, - Margin = new Thickness(0, 10, 0, 0), - Children = - { - labelFixesAvailable.WithMarginRight(10), - UiUtil.MakeButton(Se.Language.General.SelectAll, vm.SelectAllCommand), - UiUtil.MakeButton(Se.Language.General.SelectNone, vm.SelectNoneCommand), - }, + ColumnSpacing = 40, + ColumnDefinitions = { new ColumnDefinition(GridLength.Auto), new ColumnDefinition(GridLength.Auto) }, }; + grid.Add(switches, 0, 0); + grid.Add(fields, 0, 1); + return EditorChrome.MakeEditorSection(Se.Language.General.Options, grid, first: true); + } + + private static Border MakeFixesView(SplitBreakLongLinesViewModel vm) + { // Sorting dropped in the DataGrid -> TableView conversion: the grid previews // split/rebalance fixes in subtitle order. - var dataGrid = TableViewExtras.MakeTableView(multiSelect: false); + var dataGrid = TableViewExtras.MakeTableView(multiSelect: false).WithAccessibleName(Se.Language.General.Preview); dataGrid.Width = double.NaN; dataGrid.Height = double.NaN; dataGrid.DataContext = vm; @@ -280,9 +216,11 @@ private static Grid MakeFixesView(SplitBreakLongLinesViewModel vm) } }, RoutingStrategies.Tunnel); - grid.Add(panelHeader, 0); - grid.Add(UiUtil.MakeBorderForControlNoPadding(dataGrid), 1); + // Select all / none only touch the rebalance rows (the ones with a checkbox). + var actions = LineToolsChrome.MakeListActions( + LineToolsChrome.MakeSelectAllButton(vm.SelectAllCommand), + LineToolsChrome.MakeSelectNoneButton(vm.SelectNoneCommand)); - return grid; + return LineToolsChrome.MakeListSection(Se.Language.General.Preview, dataGrid, actions); } } From c79e27b0e3cdd02fe653ef143777d26ede5dacdd Mon Sep 17 00:00:00 2001 From: niksedk Date: Thu, 15 Oct 2026 19:44:35 +0200 Subject: [PATCH 210/306] Fix common errors windows in the editor look Co-Authored-By: Claude Opus 5.5 --- .../FixCommonErrorsLogWindow.cs | 75 ++- .../FixCommonErrorsProfileWindow.cs | 171 +++---- .../FixCommonErrors/FixCommonErrorsWindow.cs | 437 ++++++++++-------- .../FixNetflixErrorsWindow.cs | 137 +++--- 4 files changed, 446 insertions(+), 374 deletions(-) diff --git a/src/ui/Features/Tools/FixCommonErrors/FixCommonErrorsLogWindow.cs b/src/ui/Features/Tools/FixCommonErrors/FixCommonErrorsLogWindow.cs index 88fd56f8625..ba055b1b693 100644 --- a/src/ui/Features/Tools/FixCommonErrors/FixCommonErrorsLogWindow.cs +++ b/src/ui/Features/Tools/FixCommonErrors/FixCommonErrorsLogWindow.cs @@ -4,8 +4,12 @@ using Avalonia.Data; using Avalonia.Layout; using Avalonia.Media; +using Nikse.SubtitleEdit.Features.Edit; +using Nikse.SubtitleEdit.Features.Sync; +using Nikse.SubtitleEdit.Features.Video; using Nikse.SubtitleEdit.Logic; using Nikse.SubtitleEdit.Logic.Config; +using Optris.Icons.Avalonia; namespace Nikse.SubtitleEdit.Features.Tools.FixCommonErrors; @@ -20,26 +24,50 @@ public FixCommonErrorsLogWindow(FixCommonErrorsLogViewModel vm) Height = 500; MinWidth = 400; MinHeight = 250; + EditDialogChrome.Apply(this); vm.Window = this; DataContext = vm; - var buttonCopy = UiUtil.MakeButton(vm.CopyCommand, IconNames.Copy, Se.Language.General.CopyToClipboard); + // Icon-only; the view model swaps the icon to a check mark for a moment after copying. + var buttonCopy = new Button + { + Command = vm.CopyCommand, + Width = 26, + Height = 26, + HorizontalContentAlignment = HorizontalAlignment.Center, + VerticalContentAlignment = VerticalAlignment.Center, + VerticalAlignment = VerticalAlignment.Center, + }; + buttonCopy.Classes.Add("tool"); + Attached.SetIcon(buttonCopy, IconNames.Copy); + AutomationProperties.SetName(buttonCopy, Se.Language.General.CopyToClipboard); + if (Se.Settings.Appearance.ShowHints) + { + ToolTip.SetTip(buttonCopy, Se.Language.General.CopyToClipboard); + } + vm.CopyButton = buttonCopy; - var labelImportant = UiUtil.MakeTextBlock(string.Empty); - labelImportant.Foreground = new SolidColorBrush(UiTheme.IsDarkThemeEnabled() - ? Color.FromRgb(0xff, 0x8a, 0x80) - : Color.FromRgb(0xc4, 0x28, 0x28)); - labelImportant.VerticalAlignment = VerticalAlignment.Center; + var labelImportant = new TextBlock + { + FontSize = 12, + Foreground = new SolidColorBrush(EditorChrome.IsLightTheme + ? Color.FromRgb(0xc4, 0x28, 0x28) + : Color.FromRgb(0xff, 0x8a, 0x80)), + VerticalAlignment = VerticalAlignment.Center, + TextTrimming = TextTrimming.CharacterEllipsis, + Margin = new Thickness(12, 0), + }; labelImportant.Bind(TextBlock.TextProperty, new Binding(nameof(vm.ImportantMessagesText))); labelImportant.Bind(IsVisibleProperty, new Binding(nameof(vm.ImportantMessagesIsVisible))); - var panelHeader = new Grid - { - ColumnDefinitions = new ColumnDefinitions("*,Auto"), - }; - panelHeader.Add(labelImportant, 0, 0); - panelHeader.Add(buttonCopy, 0, 1); + var panelHeader = new DockPanel { Margin = new Thickness(0, 0, 0, 8) }; + DockPanel.SetDock(buttonCopy, Dock.Right); + panelHeader.Children.Add(buttonCopy); + var title = EditorChrome.MakeSectionHeader(Se.Language.Tools.FixCommonErrors.Log); + DockPanel.SetDock(title, Dock.Left); + panelHeader.Children.Add(title); + panelHeader.Children.Add(labelImportant); var textBoxLog = new TextBox { @@ -48,30 +76,27 @@ public FixCommonErrorsLogWindow(FixCommonErrorsLogViewModel vm) TextWrapping = TextWrapping.NoWrap, HorizontalAlignment = HorizontalAlignment.Stretch, VerticalAlignment = VerticalAlignment.Stretch, + VerticalContentAlignment = VerticalAlignment.Top, Width = double.NaN, Height = double.NaN, [!TextBox.TextProperty] = new Binding(nameof(vm.LogText)), }; + textBoxLog.Classes.Add("text"); AutomationProperties.SetName(textBoxLog, Se.Language.Tools.FixCommonErrors.Log); var buttonDone = UiUtil.MakeButtonDone(vm.CloseCommand); - var panelButtons = UiUtil.MakeButtonBar(buttonDone); + buttonDone.Classes.Add("primary"); + var panelButtons = VideoSubDialogChrome.MakeButtonBar(null, buttonDone); - var grid = new Grid + var body = new Grid { - RowDefinitions = new RowDefinitions("Auto,*,Auto"), - ColumnDefinitions = new ColumnDefinitions("*"), - Margin = UiUtil.MakeWindowMargin(), - ColumnSpacing = 10, - RowSpacing = 10, - Width = double.NaN, - HorizontalAlignment = HorizontalAlignment.Stretch, + RowDefinitions = new RowDefinitions("Auto,*"), + Margin = new Thickness(14, 12, 14, 14), }; - grid.Add(panelHeader, 0); - grid.Add(textBoxLog, 1); - grid.Add(panelButtons, 2); + body.Add(panelHeader, 0); + body.Add(textBoxLog, 1); - Content = grid; + Content = SyncTimingChrome.MakeRoot(body, panelButtons); UiUtil.FocusOnFirstActivation(this, buttonDone); KeyDown += (s, e) => vm.OnKeyDown(e); diff --git a/src/ui/Features/Tools/FixCommonErrors/FixCommonErrorsProfileWindow.cs b/src/ui/Features/Tools/FixCommonErrors/FixCommonErrorsProfileWindow.cs index 47d35a15e96..0874c238e53 100644 --- a/src/ui/Features/Tools/FixCommonErrors/FixCommonErrorsProfileWindow.cs +++ b/src/ui/Features/Tools/FixCommonErrors/FixCommonErrorsProfileWindow.cs @@ -1,9 +1,14 @@ using Avalonia; +using Avalonia.Automation; using Avalonia.Controls; using Avalonia.Controls.Templates; using Avalonia.Data; using Avalonia.Input; using Avalonia.Layout; +using Avalonia.Media; +using Nikse.SubtitleEdit.Features.Edit; +using Nikse.SubtitleEdit.Features.Sync; +using Nikse.SubtitleEdit.Features.Video; using Nikse.SubtitleEdit.Logic; using Nikse.SubtitleEdit.Logic.Config; using Optris.Icons.Avalonia; @@ -18,119 +23,117 @@ public FixCommonErrorsProfileWindow(FixCommonErrorsProfileViewModel vm) { UiUtil.InitializeWindow(this, GetType().Name); Title = Se.Language.Tools.FixCommonErrors.FixCommonErrorsProfiles; - Width = 800; - Height = 440; + Width = 680; + Height = 420; CanResize = false; + EditDialogChrome.Apply(this); _vm = vm; vm.Window = this; DataContext = vm; - var grid = new Grid + // Left: the profile list, "New profile" in its header + var listBoxProfiles = new ListBox { - ColumnDefinitions = new ColumnDefinitions("*,*"), - RowDefinitions = new RowDefinitions("Auto,*"), - Margin = UiUtil.MakeWindowMargin(), - }; - - // Left column: Profile list - var profileListPanel = new StackPanel - { - Margin = new Thickness(10), - Spacing = 10, - Children = + DataContext = vm, + ItemsSource = vm.Profiles, + Background = Brushes.Transparent, + [!ListBox.SelectedItemProperty] = new Binding(nameof(vm.SelectedProfile)) { Mode = BindingMode.TwoWay }, + ItemTemplate = new FuncDataTemplate((profile, _) => { - new Button + var grid = new Grid { - Content = Se.Language.General.NewProfile, - Command = vm.NewProfileCommand - }, - new ListBox + ColumnDefinitions = new ColumnDefinitions("*,Auto"), + Margin = new Thickness(0), + }; + + // Name text (left-aligned) + var nameText = new TextBlock + { + VerticalAlignment = VerticalAlignment.Center, + TextTrimming = TextTrimming.CharacterEllipsis, + FontSize = 12.5, + [!TextBlock.TextProperty] = new Binding(nameof(ProfileDisplayItem.Name)) + }; + Grid.SetColumn(nameText, 0); + grid.Children.Add(nameText); + + // Delete button (right-aligned) + var deleteButton = new Button { + Command = vm.DeleteCommand, + CommandParameter = profile, DataContext = vm, - ItemsSource = vm.Profiles, - [!ListBox.SelectedItemProperty] = new Binding(nameof(vm.SelectedProfile)) { Mode = BindingMode.TwoWay }, - ItemTemplate = new FuncDataTemplate((profile, _) => - { - var grid = new Grid - { - ColumnDefinitions = new ColumnDefinitions("*,Auto"), - Margin = new Thickness(2) - }; - - // Name text (left-aligned) - var nameText = new TextBlock - { - VerticalAlignment = VerticalAlignment.Center, - [!TextBlock.TextProperty] = new Binding(nameof(ProfileDisplayItem.Name)) - }; - Grid.SetColumn(nameText, 0); - grid.Children.Add(nameText); - - // Delete button (right-aligned) - var deleteButton = new Button - { - Command = vm.DeleteCommand, - CommandParameter = profile, - DataContext = vm, - }; - deleteButton.Bind(Button.IsEnabledProperty, new Binding(nameof(vm.IsProfileDeleteEnabled)) { Mode = BindingMode.OneWay }); - Attached.SetIcon(deleteButton, "fa-solid fa-trash"); - Grid.SetColumn(deleteButton, 1); - grid.Children.Add(deleteButton); - - return grid; - }) + Width = 24, + Height = 24, + HorizontalContentAlignment = HorizontalAlignment.Center, + VerticalContentAlignment = VerticalAlignment.Center, + }; + deleteButton.Classes.Add("tool"); + deleteButton.Bind(Button.IsEnabledProperty, new Binding(nameof(vm.IsProfileDeleteEnabled)) { Mode = BindingMode.OneWay }); + Attached.SetIcon(deleteButton, "fa-solid fa-trash"); + AutomationProperties.SetName(deleteButton, Se.Language.General.Delete); + if (Se.Settings.Appearance.ShowHints) + { + ToolTip.SetTip(deleteButton, Se.Language.General.Delete); } - } - }; - grid.Children.Add(profileListPanel); - Grid.SetRow(profileListPanel, 1); - Grid.SetColumn(profileListPanel, 0); - // Right column: Profile editor - var editorGrid = new Grid - { - Margin = new Thickness(10), - RowDefinitions = new RowDefinitions("Auto,Auto,Auto,*,Auto"), + Grid.SetColumn(deleteButton, 1); + grid.Children.Add(deleteButton); + + return grid; + }) }; + AutomationProperties.SetName(listBoxProfiles, Se.Language.General.Profiles); + + var buttonNewProfile = SyncTimingChrome.MakeTextToolButton("fa-solid fa-plus", Se.Language.General.NewProfile, vm.NewProfileCommand); + var profilesHeader = new DockPanel { Margin = new Thickness(0, 0, 0, 8) }; + DockPanel.SetDock(buttonNewProfile, Dock.Right); + profilesHeader.Children.Add(buttonNewProfile); + profilesHeader.Children.Add(EditorChrome.MakeSectionHeader(Se.Language.General.Profiles)); - // "Edit Profile" heading - var heading = new TextBlock + var profilesPanel = new Grid { - Text = Se.Language.General.ProfileName, - FontSize = UiUtil.ScaledFontSize(20), - Padding = new Thickness(0, 25, 0, 0), + RowDefinitions = new RowDefinitions("Auto,*"), + Margin = new Thickness(14, 8, 14, 14), }; - editorGrid.Children.Add(heading); - Grid.SetRow(heading, 0); + profilesPanel.Add(profilesHeader, 0); + profilesPanel.Add(EditDialogChrome.MakeListFrame(listBoxProfiles), 1); - // Name textbox + // Right: the selected profile's name var nameBox = new TextBox { PlaceholderText = Se.Language.General.EnterProfileName, + HorizontalAlignment = HorizontalAlignment.Stretch, }; + nameBox.Classes.Add("text"); nameBox.Bind(TextBox.TextProperty, new Binding($"{nameof(vm.SelectedProfile)}.{nameof(ProfileDisplayItem.Name)}")); nameBox.KeyDown += vm.ProfileNameTextBoxOnKeyDown; - editorGrid.Children.Add(nameBox); - Grid.SetRow(nameBox, 1); + AutomationProperties.SetName(nameBox, Se.Language.General.ProfileName); vm.FocusProfileName = () => Avalonia.Threading.Dispatcher.UIThread.Post(() => nameBox.Focus()); - var buttonRow = UiUtil.MakeButtonBar( - UiUtil.MakeButtonOk(vm.OkCommand), - UiUtil.MakeButtonCancel(vm.CancelCommand) - ); - Grid.SetRow(buttonRow, 4); - editorGrid.Children.Add(buttonRow); + var editorSection = EditorChrome.MakeEditorSection(Se.Language.General.ProfileName, nameBox, first: true); // Hide if no profile is selected - editorGrid.Bind(IsVisibleProperty, new Binding(nameof(vm.IsProfileSelected))); + editorSection.Bind(IsVisibleProperty, new Binding(nameof(vm.IsProfileSelected))); + + var columns = new Grid + { + ColumnDefinitions = new ColumnDefinitions("*,*"), + }; + columns.Add(profilesPanel, 0, 0); + columns.Add(new Border + { + Child = editorSection, + BorderBrush = EditorChrome.LineBrush, + BorderThickness = new Thickness(1, 0, 0, 0), + }, 0, 1); - Grid.SetRow(editorGrid, 1); - Grid.SetColumn(editorGrid, 1); - grid.Children.Add(editorGrid); + var buttonOk = UiUtil.MakeButtonOk(vm.OkCommand); + buttonOk.Classes.Add("primary"); + var buttonBar = VideoSubDialogChrome.MakeButtonBar(null, UiUtil.MakeButtonCancel(vm.CancelCommand), buttonOk); - Content = grid; + Content = SyncTimingChrome.MakeRoot(columns, buttonBar); UiUtil.FocusOnFirstActivation(this, () => { Focus(); }); // hack to make OnKeyDown work } @@ -140,4 +143,4 @@ protected override void OnKeyDown(KeyEventArgs e) base.OnKeyDown(e); _vm.OnKeyDown(e); } -} \ No newline at end of file +} diff --git a/src/ui/Features/Tools/FixCommonErrors/FixCommonErrorsWindow.cs b/src/ui/Features/Tools/FixCommonErrors/FixCommonErrorsWindow.cs index db0c4e92bd7..f4d4848cf3f 100644 --- a/src/ui/Features/Tools/FixCommonErrors/FixCommonErrorsWindow.cs +++ b/src/ui/Features/Tools/FixCommonErrors/FixCommonErrorsWindow.cs @@ -9,9 +9,14 @@ using Avalonia.Layout; using Avalonia.Media; using Avalonia.Threading; +using Avalonia.Controls.Presenters; +using Avalonia.Styling; using System; +using Nikse.SubtitleEdit.Features.Edit; using Nikse.SubtitleEdit.Features.Files.Compare; using Nikse.SubtitleEdit.Features.Main; +using Nikse.SubtitleEdit.Features.Sync; +using Nikse.SubtitleEdit.Features.Video; using Nikse.SubtitleEdit.Logic; using Nikse.SubtitleEdit.Logic.Config; using Nikse.SubtitleEdit.Logic.ValueConverters; @@ -32,36 +37,62 @@ public FixCommonErrorsWindow(FixCommonErrorsViewModel vm) MinWidth = 800; MinHeight = 600; CanResize = true; + EditDialogChrome.Apply(this); + AddChipStyles(Styles); _vm = vm; vm.Window = this; DataContext = vm; - var labelStep1 = new Label + var labelStep1 = new TextBlock { - Content = Se.Language.Tools.FixCommonErrors.FixCommonOcrErrorsStep1, + Text = Se.Language.Tools.FixCommonErrors.FixCommonOcrErrorsStep1, + FontSize = 13, + FontWeight = FontWeight.SemiBold, VerticalAlignment = VerticalAlignment.Center, + TextTrimming = TextTrimming.CharacterEllipsis, + Margin = new Thickness(4, 0, 12, 0), }; labelStep1.Bind(IsVisibleProperty, new Binding(nameof(vm.Step1IsVisible))); - var labelStep2 = new Label + var labelStep2 = new TextBlock { + FontSize = 13, + FontWeight = FontWeight.SemiBold, VerticalAlignment = VerticalAlignment.Center, + TextTrimming = TextTrimming.CharacterEllipsis, + Margin = new Thickness(4, 0, 12, 0), }; - labelStep2.Bind(Label.ContentProperty, new Binding(nameof(vm.Step2Title))); + labelStep2.Bind(TextBlock.TextProperty, new Binding(nameof(vm.Step2Title))); labelStep2.Bind(IsVisibleProperty, new Binding(nameof(vm.Step2IsVisible))); - var textBoxSearch = UiUtil.MakeTextBox(250, vm, nameof(vm.SearchText)).WithMarginRight(25) + var textBoxSearch = UiUtil.MakeTextBox(250, vm, nameof(vm.SearchText)).WithMarginRight(16) .WithAccessibleName(Se.Language.Tools.FixCommonErrors.SearchRulesDotDotDot) .WithSearchAndClearIcons(); + textBoxSearch.Classes.Add("text"); + textBoxSearch.VerticalAlignment = VerticalAlignment.Center; textBoxSearch.PlaceholderText = Se.Language.Tools.FixCommonErrors.SearchRulesDotDotDot; textBoxSearch.Bind(IsVisibleProperty, new Binding(nameof(vm.Step1IsVisible))); // Off by default (#12441) - keep it reachable here, next to the language it depends on, // instead of only in the OCR window where a Fix-common-errors user would never look. - var checkBoxGuessUnknownWords = UiUtil.MakeCheckBox(Se.Language.Ocr.TryToGuessUnknownWords, vm, nameof(vm.TryToGuessUnknownWords)) - .WithMarginRight(25); - checkBoxGuessUnknownWords.Bind(IsVisibleProperty, new Binding(nameof(vm.Step2IsVisible))); + var switchGuessUnknownWords = VideoSubDialogChrome.MakeSwitch(nameof(vm.TryToGuessUnknownWords), Se.Language.Ocr.TryToGuessUnknownWords); + var panelGuessUnknownWords = new StackPanel + { + Orientation = Orientation.Horizontal, + Spacing = 8, + VerticalAlignment = VerticalAlignment.Center, + Margin = new Thickness(0, 0, 16, 0), + Children = + { + switchGuessUnknownWords, + new TextBlock { Text = Se.Language.Ocr.TryToGuessUnknownWords, FontSize = 12.5, VerticalAlignment = VerticalAlignment.Center }, + }, + }; + panelGuessUnknownWords.Bind(IsVisibleProperty, new Binding(nameof(vm.Step2IsVisible))); + var comboLanguage = UiUtil.MakeComboBox(vm.Languages, vm, nameof(vm.SelectedLanguage)) + .WithAccessibleName(Se.Language.General.Language); + comboLanguage.VerticalAlignment = VerticalAlignment.Center; var panelTopRight = new StackPanel { Orientation = Orientation.Horizontal, @@ -69,15 +100,21 @@ public FixCommonErrorsWindow(FixCommonErrorsViewModel vm) HorizontalAlignment = HorizontalAlignment.Right, Children = { - // Filters the step 1 rule list by name (#14893); step 2 shows the checkbox instead. + // Filters the step 1 rule list by name (#14893); step 2 shows the switch instead. textBoxSearch, - checkBoxGuessUnknownWords, - UiUtil.MakeTextBlock(Se.Language.General.Language).WithMarginRight(5), - UiUtil.MakeComboBox(vm.Languages, vm, nameof(vm.SelectedLanguage)) - .WithAccessibleName(Se.Language.General.Language), + panelGuessUnknownWords, + EditorChrome.MakeDimLabel(Se.Language.General.Language, new Thickness(0, 0, 8, 0)), + comboLanguage, }, }; + var toolbarContent = new DockPanel { MinHeight = 32 }; + DockPanel.SetDock(panelTopRight, Dock.Right); + toolbarContent.Children.Add(panelTopRight); + toolbarContent.Children.Add(new Panel { Children = { labelStep1, labelStep2 }, VerticalAlignment = VerticalAlignment.Center }); + var toolbar = VideoSubDialogChrome.MakeToolbar(toolbarContent); + toolbar.Padding = new Thickness(12, 8); + var rulesGrid = TableViewExtras.MakeTableView(); rulesGrid[!TableView.ItemsSourceProperty] = new Binding($"{nameof(vm.SelectedProfile)}.{nameof(ProfileDisplayItem.FixRules)}"); @@ -125,7 +162,6 @@ public FixCommonErrorsWindow(FixCommonErrorsViewModel vm) rulesGrid.Columns.Add(rulesEnabledColumn); rulesGrid.Columns.Add(rulesNameColumn); rulesGrid.Columns.Add(rulesExampleColumn); - rulesGrid.Bind(IsVisibleProperty, new Binding(nameof(vm.Step1IsVisible))); AutomationProperties.SetName(rulesGrid, Se.Language.General.Rules); // Extended selection is native ListBox behavior on TableView; Space toggling every // selected row's checkbox is the piece the old DataGridCheckboxMultiSelect provided. @@ -140,22 +176,51 @@ public FixCommonErrorsWindow(FixCommonErrorsViewModel vm) .AddSortable(rulesNameColumn, x => x.Name) .AddSortable(rulesExampleColumn, x => x.Example); - var step2Grid = MakeStep2Grid(); - step2Grid.Bind(IsVisibleProperty, new Binding(nameof(_vm.Step2IsVisible))); + // Rules header: select all / invert, then the profile picker and its editor. var comboProfile = UiUtil.MakeComboBox(vm.Profiles, vm, nameof(vm.SelectedProfile)) .WithAccessibleName(Se.Language.General.Profile); - var buttonPanelRules = UiUtil.MakeButtonBar( - UiUtil.MakeButton(Se.Language.General.SelectAll, vm.RulesSelectAllCommand), - UiUtil.MakeButton(Se.Language.General.InvertSelection, vm.RulesInverseSelectedCommand), - UiUtil.MakeTextBlock(Se.Language.General.Profile).WithMarginLeft(25).WithMarginRight(10), - comboProfile, - UiUtil.MakeButton("...", vm.ShowProfileCommand).Compact().WithAccessibleName(Se.Language.General.Profiles) - ); - buttonPanelRules.Bind(IsVisibleProperty, new Binding(nameof(vm.Step1IsVisible))); + comboProfile.VerticalAlignment = VerticalAlignment.Center; + var rulesActions = new StackPanel + { + Orientation = Orientation.Horizontal, + Spacing = 2, + VerticalAlignment = VerticalAlignment.Center, + Children = + { + EditorChrome.CreateToolButton("fa-solid fa-square-check", Se.Language.General.SelectAll, vm.RulesSelectAllCommand, 26), + EditorChrome.CreateToolButton("fa-solid fa-right-left", Se.Language.General.InvertSelection, vm.RulesInverseSelectedCommand, 26), + EditorChrome.MakeToolbarSeparator(), + EditorChrome.MakeDimLabel(Se.Language.General.Profile, new Thickness(0, 0, 8, 0)), + comboProfile, + EditorChrome.CreateToolButton("fa-solid fa-sliders", Se.Language.General.Profiles, vm.ShowProfileCommand, 28), + }, + }; + var rulesHeader = new DockPanel { Margin = new Thickness(0, 0, 0, 8) }; + DockPanel.SetDock(rulesActions, Dock.Right); + rulesHeader.Children.Add(rulesActions); + rulesHeader.Children.Add(EditorChrome.MakeSectionHeader(Se.Language.General.Rules)); + + var rulesPanel = new Grid + { + RowDefinitions = + { + new RowDefinition(GridLength.Auto), + new RowDefinition(new GridLength(1, GridUnitType.Star)), + }, + Margin = new Thickness(14, 12, 14, 14), + }; + rulesPanel.Add(rulesHeader, 0); + rulesPanel.Add(EditDialogChrome.MakeListFrame(rulesGrid), 1); + var step1View = VideoSubDialogChrome.MakePanel(rulesPanel); + step1View.Bind(IsVisibleProperty, new Binding(nameof(vm.Step1IsVisible))); + + var step2Grid = MakeStep2Grid(); + step2Grid.Bind(IsVisibleProperty, new Binding(nameof(_vm.Step2IsVisible))); var buttonToApplyFixes = UiUtil.MakeButton(Se.Language.Tools.FixCommonErrors.GoToApplyFixes, vm.ToApplyFixesCommand) .WithIconRight("fa-solid fa-arrow-right") .BindIsVisible(vm, nameof(vm.Step1IsVisible)); + buttonToApplyFixes.Classes.Add("primary"); var buttonBackToFixList = UiUtil.MakeButton(Se.Language.Tools.FixCommonErrors.BackToFixList, vm.BackToFixListCommand) .WithIconLeft("fa-solid fa-arrow-left") @@ -163,73 +228,24 @@ public FixCommonErrorsWindow(FixCommonErrorsViewModel vm) var buttonDone = UiUtil.MakeButton(Se.Language.General.Done, vm.OkCommand) .BindIsVisible(vm, nameof(vm.Step2IsVisible)); + buttonDone.Classes.Add("primary"); - var buttonPanelRight = UiUtil.MakeButtonBar( - buttonBackToFixList, - buttonToApplyFixes, - buttonDone, - UiUtil.MakeButtonCancel(vm.CancelCommand) - ); - - var grid = new Grid - { - RowDefinitions = - { - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Star) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - }, - ColumnDefinitions = - { - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) }, - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) }, - }, - Margin = UiUtil.MakeWindowMargin(), - ColumnSpacing = 10, - RowSpacing = 10, - Width = double.NaN, - HorizontalAlignment = HorizontalAlignment.Stretch, - }; - - grid.Children.Add(labelStep1); - Grid.SetRow(labelStep1, 0); - Grid.SetColumn(labelStep1, 0); - grid.Children.Add(labelStep2); - Grid.SetRow(labelStep2, 0); - Grid.SetColumn(labelStep2, 0); - - grid.Children.Add(panelTopRight); - Grid.SetRow(panelTopRight, 0); - Grid.SetColumn(panelTopRight, 0); - Grid.SetColumnSpan(panelTopRight, 2); - - grid.Children.Add(rulesGrid); - Grid.SetRow(rulesGrid, 1); - Grid.SetColumn(rulesGrid, 0); - Grid.SetColumnSpan(rulesGrid, 2); - - grid.Children.Add(step2Grid); - Grid.SetRow(step2Grid, 1); - Grid.SetColumn(step2Grid, 0); - Grid.SetColumnSpan(step2Grid, 2); - - grid.Children.Add(buttonPanelRules); - Grid.SetRow(buttonPanelRules, 2); - Grid.SetColumn(buttonPanelRules, 0); - - var labelFixesApplied = UiUtil.MakeTextBlock(string.Empty); - labelFixesApplied.Bind(TextBlock.TextProperty, new Binding(nameof(vm.FixesAppliedText))); + var labelFixesApplied = VideoSubDialogChrome.MakeHint(nameof(vm.FixesAppliedText)); + labelFixesApplied.FontSize = 12; labelFixesApplied.Bind(IsVisibleProperty, new Binding(nameof(vm.FixesAppliedText)) { Converter = StringConverters.IsNotNullOrEmpty }); - labelFixesApplied.VerticalAlignment = VerticalAlignment.Center; // Green check + "Nothing to fix" so an empty re-scan gives visible feedback; the counters // stay as they were, which on its own reads as "the button did nothing" (#12849). - var nothingToFixBrush = new SolidColorBrush(UiTheme.IsDarkThemeEnabled() - ? Color.FromRgb(0x6e, 0xcb, 0x87) - : Color.FromRgb(0x1e, 0x7e, 0x34)); - var nothingToFixText = UiUtil.MakeTextBlock(Se.Language.Tools.FixCommonErrors.NothingToFix); - nothingToFixText.Foreground = nothingToFixBrush; - nothingToFixText.VerticalAlignment = VerticalAlignment.Center; + var nothingToFixBrush = new SolidColorBrush(EditorChrome.IsLightTheme + ? Color.FromRgb(0x1e, 0x7e, 0x34) + : Color.FromRgb(0x6e, 0xcb, 0x87)); + var nothingToFixText = new TextBlock + { + Text = Se.Language.Tools.FixCommonErrors.NothingToFix, + FontSize = 12, + Foreground = nothingToFixBrush, + VerticalAlignment = VerticalAlignment.Center, + }; var nothingToFixPanel = new StackPanel { Orientation = Orientation.Horizontal, @@ -240,7 +256,7 @@ public FixCommonErrorsWindow(FixCommonErrorsViewModel vm) new Optris.Icons.Avalonia.Icon { Value = IconNames.CheckCircle, - FontSize = 16, + FontSize = 14, Foreground = nothingToFixBrush, VerticalAlignment = VerticalAlignment.Center, }, @@ -252,19 +268,20 @@ public FixCommonErrorsWindow(FixCommonErrorsViewModel vm) // Errors the rules found but could not fix (e.g. a display time that is too short with no // room to extend it). Without this they are silent, and a subtitle full of them still shows // the green "Nothing to fix" - click the warning to see one log line per error (#13645). - var errorsFoundBrush = new SolidColorBrush(UiTheme.IsDarkThemeEnabled() - ? Color.FromRgb(0xff, 0x8a, 0x80) - : Color.FromRgb(0xc4, 0x28, 0x28)); + var errorsFoundBrush = new SolidColorBrush(EditorChrome.IsLightTheme + ? Color.FromRgb(0xc4, 0x28, 0x28) + : Color.FromRgb(0xff, 0x8a, 0x80)); var errorsFoundIcon = new Optris.Icons.Avalonia.Icon { Value = IconNames.Alert, - FontSize = 16, + FontSize = 14, Foreground = errorsFoundBrush, VerticalAlignment = VerticalAlignment.Center, Cursor = new Cursor(StandardCursorType.Hand), }; errorsFoundIcon.PointerPressed += (_, _) => vm.ShowLogCommand.Execute(null); var errorsFoundText = UiUtil.MakeLink(string.Empty, vm.ShowLogCommand); + errorsFoundText.FontSize = 12; errorsFoundText.Foreground = errorsFoundBrush; errorsFoundText.Bind(TextBlock.TextProperty, new Binding(nameof(vm.ErrorsFoundText))); var errorsFoundPanel = new StackPanel @@ -284,13 +301,18 @@ public FixCommonErrorsWindow(FixCommonErrorsViewModel vm) // The log also holds what the applies changed, so it stays reachable when a subtitle has // no errors left - the warning above links to the same window when it does. var logLink = UiUtil.MakeLink(Se.Language.Tools.FixCommonErrors.Log, vm.ShowLogCommand); + logLink.FontSize = 12; logLink.Bind(IsVisibleProperty, new Binding(nameof(vm.LogIsVisible))); // "Analyzing..." while a re-scan runs, so a scan that ends in the same state as it started // still shows that it ran - this is what SE4 does on every re-scan (#12849). - var analysingText = UiUtil.MakeTextBlock(Se.Language.Tools.FixCommonErrors.Analysing); - analysingText.VerticalAlignment = VerticalAlignment.Center; - analysingText.Opacity = 0.75; + var analysingText = new TextBlock + { + Text = Se.Language.Tools.FixCommonErrors.Analysing, + FontSize = 12, + Foreground = EditorChrome.DimTextBrush, + VerticalAlignment = VerticalAlignment.Center, + }; analysingText.Bind(IsVisibleProperty, new Binding(nameof(vm.AnalysingIsVisible))); var panelStep2Status = new StackPanel @@ -302,13 +324,28 @@ public FixCommonErrorsWindow(FixCommonErrorsViewModel vm) Children = { labelFixesApplied, nothingToFixPanel, errorsFoundPanel, logLink, analysingText }, }; panelStep2Status.Bind(IsVisibleProperty, new Binding(nameof(vm.Step2IsVisible))); - grid.Children.Add(panelStep2Status); - Grid.SetRow(panelStep2Status, 2); - Grid.SetColumn(panelStep2Status, 0); - grid.Children.Add(buttonPanelRight); - Grid.SetRow(buttonPanelRight, 2); - Grid.SetColumn(buttonPanelRight, 1); + var buttonBar = VideoSubDialogChrome.MakeButtonBar( + panelStep2Status, + buttonBackToFixList, + UiUtil.MakeButtonCancel(vm.CancelCommand), + buttonToApplyFixes, + buttonDone); + + var body = new Panel { Children = { step1View, step2Grid } }; + + var grid = new Grid + { + RowDefinitions = + { + new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, + new RowDefinition { Height = new GridLength(1, GridUnitType.Star) }, + new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, + }, + }; + grid.Add(toolbar, 0); + grid.Add(body, 1); + grid.Add(buttonBar, 2); Content = grid; @@ -367,26 +404,6 @@ void FocusStepButton() private Grid MakeStep2Grid() { - // top - var gridFixes = new Grid - { - RowDefinitions = - { - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Star) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - }, - ColumnDefinitions = - { - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) }, - }, - ColumnSpacing = 0, - RowSpacing = 0, - Width = double.NaN, - HorizontalAlignment = HorizontalAlignment.Stretch, - VerticalAlignment = VerticalAlignment.Stretch, - }; - var dataGridFixes = TableViewExtras.MakeTableView(); dataGridFixes.DataContext = _vm; dataGridFixes.ItemsSource = _vm.VisibleFixes; @@ -541,47 +558,42 @@ private Grid MakeStep2Grid() } }; - var leftButtons = new StackPanel - { - Orientation = Orientation.Horizontal, - HorizontalAlignment = HorizontalAlignment.Left, - VerticalAlignment = VerticalAlignment.Center, - Spacing = 0, - }; - var buttonSelectAll = UiUtil.MakeButton(Se.Language.General.SelectAll, _vm.FixesSelectAllCommand); + // Fixes header: select all/none, re-scan and apply - the repeated step 2 action. + var buttonSelectAll = SyncTimingChrome.MakeTextToolButton("fa-solid fa-square-check", Se.Language.General.SelectAll, _vm.FixesSelectAllCommand); // Caption toggles between "Select all" and "Select none" as the current category fills up. - buttonSelectAll.Bind(Button.ContentProperty, new Binding(nameof(_vm.FixesSelectAllText)) { Source = _vm }); - leftButtons.Children.Add(buttonSelectAll); - - var rightButtons = new StackPanel + if (buttonSelectAll.Content is StackPanel { Children: [_, TextBlock selectAllText] }) { - Orientation = Orientation.Horizontal, - HorizontalAlignment = HorizontalAlignment.Right, - VerticalAlignment = VerticalAlignment.Center, - Spacing = 0, - }; - rightButtons.Children.Add(UiUtil.MakeButton(Se.Language.Tools.FixCommonErrors.RefreshFixes, _vm.DoRefreshFixesCommand).WithIconLeft("fa-solid fa-rotate")); + selectAllText.Bind(TextBlock.TextProperty, new Binding(nameof(_vm.FixesSelectAllText)) { Source = _vm }); + } + + buttonSelectAll.Bind(AutomationProperties.NameProperty, new Binding(nameof(_vm.FixesSelectAllText)) { Source = _vm }); + var buttonRefreshFixes = SyncTimingChrome.MakeTextToolButton("fa-solid fa-rotate", Se.Language.Tools.FixCommonErrors.RefreshFixes, _vm.DoRefreshFixesCommand); + // Caption carries the live count of fixes the button will apply, e.g. "Apply selected fixes (706)". _buttonApplySelectedFixes = UiUtil.MakeButton(Se.Language.Tools.FixCommonErrors.ApplySelectedFixes, _vm.DoApplyFixesCommand) .WithIconLeftBindText("fa-solid fa-check", nameof(_vm.ApplySelectedFixesText)); AutomationProperties.SetName(_buttonApplySelectedFixes, Se.Language.Tools.FixCommonErrors.ApplySelectedFixes); - rightButtons.Children.Add(_buttonApplySelectedFixes); + _buttonApplySelectedFixes.Margin = new Thickness(6, 0, 0, 0); + _buttonApplySelectedFixes.MinHeight = 28; + _buttonApplySelectedFixes.Padding = new Thickness(10, 4); + _buttonApplySelectedFixes.FontSize = 12.5; + _buttonApplySelectedFixes.VerticalAlignment = VerticalAlignment.Center; - var buttonBarFixes = new Grid + var fixesActions = new StackPanel { - Margin = new Thickness(10, 10, 10, 10), - ColumnDefinitions = - { - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Auto) }, - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) }, - }, + Orientation = Orientation.Horizontal, + Spacing = 2, + VerticalAlignment = VerticalAlignment.Center, + Children = { buttonSelectAll, EditorChrome.MakeToolbarSeparator(), buttonRefreshFixes, _buttonApplySelectedFixes }, }; - buttonBarFixes.Add(leftButtons, 0, 0); - buttonBarFixes.Add(rightButtons, 0, 1); + var fixesHeader = new DockPanel { Margin = new Thickness(0, 0, 0, 6) }; + DockPanel.SetDock(fixesActions, Dock.Right); + fixesHeader.Children.Add(fixesActions); + fixesHeader.Children.Add(EditorChrome.MakeSectionHeader(Se.Language.Tools.FixCommonErrors.Fixes)); var chipsItems = new ItemsControl { - Margin = new Thickness(10, 8, 10, 4), + Margin = new Thickness(0, 0, 0, 8), ItemsSource = _vm.FixChips, ItemsPanel = new FuncTemplate(() => new WrapPanel { Orientation = Orientation.Horizontal, ItemSpacing = 6, LineSpacing = 6 }), ItemTemplate = new FuncDataTemplate((chip, _) => @@ -609,6 +621,8 @@ private Grid MakeStep2Grid() var toggle = new ToggleButton { + Classes = { "chip" }, + FontSize = 12, Padding = new Thickness(10, 3), CornerRadius = new CornerRadius(12), Command = _vm.SetFixFilterCommand, @@ -621,37 +635,21 @@ private Grid MakeStep2Grid() }), }; - gridFixes.Children.Add(chipsItems); - Grid.SetRow(chipsItems, 0); - Grid.SetColumn(chipsItems, 0); - - gridFixes.Children.Add(dataGridFixes); - Grid.SetRow(dataGridFixes, 1); - Grid.SetColumn(dataGridFixes, 0); - - gridFixes.Children.Add(buttonBarFixes); - Grid.SetRow(buttonBarFixes, 2); - Grid.SetColumn(buttonBarFixes, 0); - - var borderFixes = UiUtil.MakeBorderForControlNoPadding(gridFixes); - - // bottom - var gridSubtitles = new Grid + var gridFixes = new Grid { RowDefinitions = { - new RowDefinition { Height = new GridLength(1, GridUnitType.Star) }, new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, + new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, + new RowDefinition { Height = new GridLength(1, GridUnitType.Star) }, }, - ColumnDefinitions = - { - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) }, - }, - ColumnSpacing = 10, - RowSpacing = 10, - Width = double.NaN, - HorizontalAlignment = HorizontalAlignment.Stretch, + Margin = new Thickness(14, 12, 14, 12), }; + gridFixes.Add(fixesHeader, 0); + gridFixes.Add(chipsItems, 1); + gridFixes.Add(EditDialogChrome.MakeListFrame(dataGridFixes), 2); + + var borderFixes = VideoSubDialogChrome.MakePanel(gridFixes); var fullTimeConverter = new TimeSpanToDisplayFullConverter(); var shortTimeConverter = new TimeSpanToDisplayShortConverter(); @@ -771,15 +769,23 @@ private Grid MakeStep2Grid() var gridCurrentSubtbtitle = MakeStep2EditPanel(); - gridSubtitles.Children.Add(dataGridSubtitles); - Grid.SetRow(dataGridSubtitles, 0); - Grid.SetColumn(dataGridSubtitles, 0); - - gridSubtitles.Children.Add(gridCurrentSubtbtitle); - Grid.SetRow(gridCurrentSubtbtitle, 1); - Grid.SetColumn(gridCurrentSubtbtitle, 0); + var gridSubtitles = new Grid + { + RowDefinitions = + { + new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, + new RowDefinition { Height = new GridLength(1, GridUnitType.Star) }, + new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, + }, + Margin = new Thickness(14, 10, 14, 12), + }; + var subtitlesHeader = EditorChrome.MakeSectionHeader(Se.Language.General.Preview); + subtitlesHeader.Margin = new Thickness(0, 0, 0, 8); + gridSubtitles.Add(subtitlesHeader, 0); + gridSubtitles.Add(EditDialogChrome.MakeListFrame(dataGridSubtitles), 1); + gridSubtitles.Add(gridCurrentSubtbtitle, 2); - var borderSubtitles = UiUtil.MakeBorderForControlNoPadding(gridSubtitles); + var borderSubtitles = VideoSubDialogChrome.MakePanel(gridSubtitles); var grid = new Grid { @@ -789,35 +795,20 @@ private Grid MakeStep2Grid() new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, new RowDefinition { Height = new GridLength(1, GridUnitType.Star), MinHeight = 150 }, }, - ColumnDefinitions = - { - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) }, - }, - ColumnSpacing = 0, - RowSpacing = 0, - Width = double.NaN, - HorizontalAlignment = HorizontalAlignment.Stretch, }; + // The splitter doubles as the line between the fixes and the subtitle preview. var splitter = new GridSplitter { - Height = UiUtil.SplitterWidthOrHeight, + Height = 5, + Background = EditorChrome.LineBrush, ResizeDirection = GridResizeDirection.Rows, HorizontalAlignment = HorizontalAlignment.Stretch, - Margin = new Thickness(0, 2), }; - grid.Children.Add(borderFixes); - Grid.SetRow(borderFixes, 0); - Grid.SetColumn(borderFixes, 0); - - grid.Children.Add(splitter); - Grid.SetRow(splitter, 1); - Grid.SetColumn(splitter, 0); - - grid.Children.Add(borderSubtitles); - Grid.SetRow(borderSubtitles, 2); - Grid.SetColumn(borderSubtitles, 0); + grid.Add(borderFixes, 0); + grid.Add(splitter, 1); + grid.Add(borderSubtitles, 2); return grid; } @@ -827,7 +818,7 @@ private Grid MakeStep2EditPanel() var textEditGrid = new Grid { RowDefinitions = new RowDefinitions("*,Auto"), - Margin = new Thickness(10), + Margin = new Thickness(0, 10, 0, 0), Width = double.NaN, HorizontalAlignment = HorizontalAlignment.Stretch, }; @@ -854,6 +845,7 @@ private Grid MakeStep2EditPanel() } AutomationProperties.SetName(textBox, Se.Language.General.Text); + textBox.Classes.Add("text"); textEditGrid.Add(textBox, 0, 0); @@ -880,6 +872,43 @@ private Grid MakeStep2EditPanel() return textEditGrid; } + /// + /// The filter chips above the fixes: a field-colored pill, the soft accent while active. + /// + private static void AddChipStyles(Styles styles) + { + styles.Add(new Style(x => x.OfType().Class("chip")) + { + Setters = + { + new Setter(TemplatedControl.BackgroundProperty, EditorChrome.FieldBrush), + new Setter(TemplatedControl.BorderBrushProperty, EditorChrome.FieldBorderBrush), + new Setter(TemplatedControl.BorderThicknessProperty, new Thickness(1)), + new Setter(TemplatedControl.MinHeightProperty, 0.0), + }, + }); + + static Style Presenter(Func selector, IBrush background, IBrush border, IBrush foreground) => + new(x => selector(x).Template().OfType().Name("PART_ContentPresenter")) + { + Setters = + { + new Setter(ContentPresenter.BackgroundProperty, background), + new Setter(ContentPresenter.BorderBrushProperty, border), + new Setter(ContentPresenter.ForegroundProperty, foreground), + }, + }; + + styles.Add(Presenter(x => x.OfType().Class("chip").Class(":pointerover"), + EditorChrome.HoverBrush, EditorChrome.SeparatorBrush, EditorChrome.StrongTextBrush)); + styles.Add(Presenter(x => x.OfType().Class("chip").Class(":checked"), + EditorChrome.AccentSoftBrush, EditorChrome.AccentBrush, EditorChrome.StrongTextBrush)); + styles.Add(Presenter(x => x.OfType().Class("chip").Class(":checked").Class(":pointerover"), + EditorChrome.AccentSoftBrush, EditorChrome.AccentHoverBrush, EditorChrome.StrongTextBrush)); + styles.Add(Presenter(x => x.OfType().Class("chip").Class(":pressed"), + EditorChrome.PressedBrush, EditorChrome.SeparatorBrush, EditorChrome.StrongTextBrush)); + } + protected override void OnKeyDown(KeyEventArgs e) { base.OnKeyDown(e); diff --git a/src/ui/Features/Tools/FixNetflixErrors/FixNetflixErrorsWindow.cs b/src/ui/Features/Tools/FixNetflixErrors/FixNetflixErrorsWindow.cs index 8e94c96c26a..9c5664d83f6 100644 --- a/src/ui/Features/Tools/FixNetflixErrors/FixNetflixErrorsWindow.cs +++ b/src/ui/Features/Tools/FixNetflixErrors/FixNetflixErrorsWindow.cs @@ -9,7 +9,10 @@ using Avalonia.Layout; using Avalonia.Threading; using Avalonia.Media; +using Nikse.SubtitleEdit.Features.Edit; using Nikse.SubtitleEdit.Features.Files.Compare; +using Nikse.SubtitleEdit.Features.Sync; +using Nikse.SubtitleEdit.Features.Video; using Nikse.SubtitleEdit.Logic; using Nikse.SubtitleEdit.Logic.Config; @@ -29,6 +32,7 @@ public FixNetflixErrorsWindow(FixNetflixErrorsViewModel vm) MinWidth = 900; MinHeight = 640; CanResize = true; + EditDialogChrome.Apply(this); _vm = vm; vm.Window = this; @@ -40,52 +44,34 @@ public FixNetflixErrorsWindow(FixNetflixErrorsViewModel vm) var buttonGenerateReport = UiUtil.MakeButton(Se.Language.Tools.NetflixCheckAndFix.GenerateReport, vm.GenerateReportCommand) .WithIconLeft(IconNames.Netflix); var buttonOk = UiUtil.MakeButtonOk(vm.OkCommand); - var panelButtons = UiUtil.MakeButtonBar( - buttonGenerateReport, - buttonOk, - UiUtil.MakeButtonCancel(vm.CancelCommand) - ); - - var summaryText = new TextBlock - { - VerticalAlignment = VerticalAlignment.Center, - Opacity = 0.8, - Margin = new Thickness(4, 0, 10, 0), - }; - summaryText.Bind(TextBlock.TextProperty, new Binding(nameof(vm.FixesSummaryText))); + buttonOk.Classes.Add("primary"); - var buttonSelectPanel = UiUtil.MakeButtonBar( - UiUtil.MakeButton(Se.Language.General.SelectAll, vm.ChecksSelectAllCommand), - UiUtil.MakeButton(Se.Language.General.InvertSelection, vm.ChecksInverseSelectionCommand), - summaryText - ).WithAlignmentLeft().WithAlignmentTop(); + var summaryText = VideoSubDialogChrome.MakeHint(nameof(vm.FixesSummaryText)); + summaryText.FontSize = 12; + var panelButtons = VideoSubDialogChrome.MakeButtonBar( + summaryText, + buttonGenerateReport, + UiUtil.MakeButtonCancel(vm.CancelCommand), + buttonOk); - var grid = new Grid + var columns = new Grid { - RowDefinitions = - { - new RowDefinition { Height = new GridLength(1, GridUnitType.Star) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - }, ColumnDefinitions = { - new ColumnDefinition { Width = new GridLength(310, GridUnitType.Pixel) }, + new ColumnDefinition { Width = new GridLength(320, GridUnitType.Pixel) }, new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) }, }, - Margin = UiUtil.MakeWindowMargin(), - ColumnSpacing = 10, - RowSpacing = 10, - Width = double.NaN, - HorizontalAlignment = HorizontalAlignment.Stretch, }; + columns.Add(settingsView, 0, 0); + columns.Add(new Border + { + Child = fixesView, + BorderBrush = EditorChrome.LineBrush, + BorderThickness = new Thickness(1, 0, 0, 0), + }, 0, 1); - grid.Add(settingsView, 0, 0); - grid.Add(fixesView, 0, 1); - grid.Add(buttonSelectPanel, 1); - grid.Add(panelButtons, 1, 0, 1, 2); - - Content = grid; + Content = SyncTimingChrome.MakeRoot(columns, panelButtons); UiUtil.FocusOnFirstActivation(this, _comboBoxLanguage); // initial focus on an input, not an action button - a focused button clicks on bare Space @@ -96,31 +82,25 @@ public FixNetflixErrorsWindow(FixNetflixErrorsViewModel vm) private Border MakeSettingsView(FixNetflixErrorsViewModel vm) { _comboBoxLanguage = UiUtil.MakeComboBox(vm.Languages, vm, nameof(vm.SelectedLanguage)); - var panelLanguage = new StackPanel - { - Orientation = Orientation.Horizontal, - VerticalAlignment = VerticalAlignment.Center, - HorizontalAlignment = HorizontalAlignment.Left, - Children = - { - UiUtil.MakeTextBlock(Se.Language.General.Language).WithMarginRight(5), - _comboBoxLanguage - } - }; + _comboBoxLanguage.HorizontalAlignment = HorizontalAlignment.Stretch; + _comboBoxLanguage.Width = double.NaN; + AutomationProperties.SetName(_comboBoxLanguage, Se.Language.General.Language); // Netflix allows higher reading speeds for SDH and requires lower ones for // children's programs, so both change which limits the checks run against. var panelTop = new StackPanel { Orientation = Orientation.Vertical, - HorizontalAlignment = HorizontalAlignment.Left, - Margin = new Thickness(8), - Spacing = 4, + Spacing = 10, Children = { - panelLanguage, - UiUtil.MakeCheckBox(Se.Language.Tools.NetflixCheckAndFix.ChildrensProgram, vm, nameof(vm.IsChildrenProgram)), - UiUtil.MakeCheckBox(Se.Language.Tools.NetflixCheckAndFix.Sdh, vm, nameof(vm.IsSdh)), + VideoSubDialogChrome.MakeRow(Se.Language.General.Language, _comboBoxLanguage, 80), + VideoSubDialogChrome.MakeSwitchRow( + VideoSubDialogChrome.MakeSwitch(nameof(vm.IsChildrenProgram), Se.Language.Tools.NetflixCheckAndFix.ChildrensProgram), + Se.Language.Tools.NetflixCheckAndFix.ChildrensProgram), + VideoSubDialogChrome.MakeSwitchRow( + VideoSubDialogChrome.MakeSwitch(nameof(vm.IsSdh), Se.Language.Tools.NetflixCheckAndFix.Sdh), + Se.Language.Tools.NetflixCheckAndFix.Sdh), } }; @@ -173,6 +153,30 @@ private Border MakeSettingsView(FixNetflixErrorsViewModel vm) TableViewExtras.AddSpaceToggle(dataGrid, item => item.IsSelected, (item, v) => item.IsSelected = v); + var checksActions = new StackPanel + { + Orientation = Orientation.Horizontal, + Spacing = 2, + VerticalAlignment = VerticalAlignment.Center, + Children = + { + EditorChrome.CreateToolButton("fa-solid fa-square-check", Se.Language.General.SelectAll, vm.ChecksSelectAllCommand, 26), + EditorChrome.CreateToolButton("fa-solid fa-right-left", Se.Language.General.InvertSelection, vm.ChecksInverseSelectionCommand, 26), + }, + }; + var checksHeader = new DockPanel { Margin = new Thickness(0, 0, 0, 8) }; + DockPanel.SetDock(checksActions, Dock.Right); + checksHeader.Children.Add(checksActions); + checksHeader.Children.Add(EditorChrome.MakeSectionHeader(Se.Language.General.Rules)); + + var checksPanel = new Grid + { + RowDefinitions = new RowDefinitions("Auto,*"), + Margin = new Thickness(14, 10, 14, 14), + }; + checksPanel.Add(checksHeader, 0); + checksPanel.Add(EditDialogChrome.MakeListFrame(dataGrid), 1); + var grid = new Grid { RowDefinitions = @@ -180,16 +184,17 @@ private Border MakeSettingsView(FixNetflixErrorsViewModel vm) new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, new RowDefinition { Height = new GridLength(1, GridUnitType.Star) }, }, - ColumnDefinitions = - { - new ColumnDefinition { Width = new GridLength(306, GridUnitType.Pixel) }, - }, }; - grid.Add(panelTop, 0, 0); - grid.Add(dataGrid, 1, 0); + grid.Add(EditorChrome.MakeEditorSection(Se.Language.General.Settings, panelTop, first: true), 0, 0); + grid.Add(new Border + { + Child = checksPanel, + BorderBrush = EditorChrome.LineBrush, + BorderThickness = new Thickness(0, 1, 0, 0), + }, 1, 0); - return UiUtil.MakeBorderForControlNoPadding(grid); + return VideoSubDialogChrome.MakePanel(grid); } private Border MakeFixesView(FixNetflixErrorsViewModel vm) @@ -309,7 +314,17 @@ private Border MakeFixesView(FixNetflixErrorsViewModel vm) } }); - return UiUtil.MakeBorderForControlNoPadding(dataGrid); + var fixesPanel = new Grid + { + RowDefinitions = new RowDefinitions("Auto,*"), + Margin = new Thickness(14, 12, 14, 14), + }; + var fixesHeader = EditorChrome.MakeSectionHeader(Se.Language.Tools.FixCommonErrors.Fixes); + fixesHeader.Margin = new Thickness(0, 0, 0, 10); + fixesPanel.Add(fixesHeader, 0); + fixesPanel.Add(EditDialogChrome.MakeListFrame(dataGrid), 1); + + return VideoSubDialogChrome.MakePanel(fixesPanel); } protected override void OnKeyDown(KeyEventArgs e) From f71223280fb9db14992125e8217de986bc3c5409 Mon Sep 17 00:00:00 2001 From: niksedk Date: Thu, 15 Oct 2026 19:47:54 +0200 Subject: [PATCH 211/306] Timing tool windows in the editor look Co-Authored-By: Claude Opus 5.5 --- .../AdjustDuration/AdjustDurationWindow.cs | 278 ++++-------- .../ApplyDurationLimitsWindow.cs | 135 ++---- .../Tools/ApplyMinGap/ApplyMinGapWindow.cs | 61 ++- .../BeautifyTimeCodesWindow.cs | 117 ++--- .../Profile/BeautifyTimeCodesProfileWindow.cs | 409 ++++++++++-------- .../Tools/BridgeGaps/BridgeGapsWindow.cs | 83 ++-- src/ui/Features/Tools/TimingToolChrome.cs | 135 ++++++ 7 files changed, 643 insertions(+), 575 deletions(-) create mode 100644 src/ui/Features/Tools/TimingToolChrome.cs diff --git a/src/ui/Features/Tools/AdjustDuration/AdjustDurationWindow.cs b/src/ui/Features/Tools/AdjustDuration/AdjustDurationWindow.cs index a0eeb6c9764..708d9d2526e 100644 --- a/src/ui/Features/Tools/AdjustDuration/AdjustDurationWindow.cs +++ b/src/ui/Features/Tools/AdjustDuration/AdjustDurationWindow.cs @@ -1,18 +1,25 @@ using Avalonia; +using Avalonia.Automation; using Avalonia.Controls; using Avalonia.Controls.Primitives; using Avalonia.Data; using Avalonia.Layout; +using Avalonia.Media; +using Nikse.SubtitleEdit.Features.Sync; using Nikse.SubtitleEdit.Logic; using Nikse.SubtitleEdit.Logic.Config; using Nikse.SubtitleEdit.Logic.ValueConverters; namespace Nikse.SubtitleEdit.Features.Tools.AdjustDuration; +/// +/// Adjust durations, in the editor look (see ): how to adjust and the +/// value(s) for the chosen way in one section, the overlap note under it, Cancel/OK at the bottom. +/// public class AdjustDurationWindow : Window { - private const int LabelMinWidth = 100; - private const int NumericUpDownWidth = 150; + private const double LabelWidth = 200; + private const double FieldWidth = 110; public AdjustDurationWindow(AdjustDurationViewModel vm) { @@ -20,20 +27,15 @@ public AdjustDurationWindow(AdjustDurationViewModel vm) Title = UiUtil.MakeWindowTitle(Se.Language.Tools.AdjustDurations.Title); SizeToContent = SizeToContent.WidthAndHeight; CanResize = false; + SyncTimingChrome.Apply(this); vm.Window = this; DataContext = vm; - var label = new Label - { - Content = Se.Language.Tools.AdjustDurations.AdjustVia, - VerticalAlignment = VerticalAlignment.Center, - Margin = new Thickness(10, 0, 0, 0), - }; - var combo = new ComboBox { ItemsSource = vm.AdjustTypes, VerticalAlignment = VerticalAlignment.Center, + HorizontalAlignment = HorizontalAlignment.Left, MinWidth = 180, }; combo.Bind(SelectingItemsControl.SelectedValueProperty, new Binding @@ -42,78 +44,61 @@ public AdjustDurationWindow(AdjustDurationViewModel vm) Mode = BindingMode.TwoWay, Source = vm, }); + AutomationProperties.SetName(combo, Se.Language.Tools.AdjustDurations.AdjustVia); var panelSeconds = MakeAdjustSeconds(vm); var panelPercent = MakeAdjustPercent(vm); var panelFixed = MakeAdjustFixed(vm); var panelRecalculate = MakeAdjustRecalculate(vm); + // Only one of the four is visible at a time - the one for the chosen way to adjust. + var panelValues = new Panel { Children = { panelSeconds, panelPercent, panelFixed, panelRecalculate } }; + var labelInfo = new TextBlock { - VerticalAlignment = VerticalAlignment.Bottom, - HorizontalAlignment = HorizontalAlignment.Left, Text = Se.Language.Tools.AdjustDurations.Note, - Margin = new Thickness(10, 25, 10, 15), + FontSize = 12, + Foreground = EditorChrome.DimTextBrush, + TextWrapping = TextWrapping.Wrap, + MaxWidth = 360, + HorizontalAlignment = HorizontalAlignment.Left, }; + var rows = new StackPanel { Spacing = 10 }; + rows.Children.Add(SyncTimingChrome.Row(Se.Language.Tools.AdjustDurations.AdjustVia, combo, LabelWidth)); + rows.Children.Add(panelValues); + rows.Children.Add(labelInfo); + var section = EditorChrome.MakeEditorSection(Se.Language.General.Adjustment, rows, first: true); + section.MinWidth = 440; + var buttonOk = UiUtil.MakeButtonOk(vm.OkCommand); + buttonOk.Classes.Add("primary"); var buttonCancel = UiUtil.MakeButtonCancel(vm.CancelCommand); - var panelButtons = UiUtil.MakeButtonBar(buttonOk, buttonCancel); - var grid = new Grid - { - RowDefinitions = - { - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Star) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - }, - ColumnDefinitions = - { - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Auto) }, - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) }, - }, - Margin = UiUtil.MakeWindowMargin(), - ColumnSpacing = 10, - RowSpacing = 10, - Width = double.NaN, - HorizontalAlignment = HorizontalAlignment.Stretch, - }; - - grid.Add(label, 0); - grid.Add(combo, 0, 1); - - grid.Add(panelSeconds, 1, 0, 1, 2); - grid.Add(panelPercent, 1, 0, 1, 2); - grid.Add(panelFixed, 1, 0, 1, 2); - grid.Add(panelRecalculate, 1, 0, 1, 2); - - grid.Add(labelInfo, 2, 0, 1, 2); - - grid.Add(panelButtons, 3, 0, 1, 2); - - Content = grid; + Content = SyncTimingChrome.MakeRoot(section, SyncTimingChrome.MakeButtonBar(null, buttonCancel, buttonOk)); UiUtil.FocusOnFirstActivation(this, combo); // initial focus on an input, not an action button - a focused button clicks on bare Space KeyDown += (_, e) => vm.OnKeyDown(e); } - private static StackPanel MakeAdjustSeconds(AdjustDurationViewModel vm) + private static NumericUpDown MakeNumber(string label, decimal minimum, decimal maximum) { - var textBlockSeconds = new TextBlock - { - Text = Se.Language.General.Seconds, - VerticalAlignment = VerticalAlignment.Center, - MinWidth = LabelMinWidth, - }; - var numericUpDownSeconds = new NumericUpDown + var numericUpDown = new NumericUpDown { - Minimum = -1000000, - Maximum = 1000000, - Width = NumericUpDownWidth, + Minimum = minimum, + Maximum = maximum, + Width = FieldWidth, + HorizontalAlignment = HorizontalAlignment.Left, VerticalAlignment = VerticalAlignment.Center, }; + numericUpDown.Classes.Add("field"); + AutomationProperties.SetName(numericUpDown, label); + return numericUpDown; + } + + private static Grid MakeAdjustSeconds(AdjustDurationViewModel vm) + { + var numericUpDownSeconds = MakeNumber(Se.Language.General.Seconds, -1000000, 1000000); numericUpDownSeconds.Bind(NumericUpDown.ValueProperty, new Binding { Path = nameof(vm.AdjustSeconds), @@ -122,18 +107,8 @@ private static StackPanel MakeAdjustSeconds(AdjustDurationViewModel vm) Converter = new DoubleToThreeDecimalConverter(), }); - var panel = new StackPanel - { - Orientation = Orientation.Horizontal, - VerticalAlignment = VerticalAlignment.Center, - Margin = new Thickness(10, 25, 0, 0), - Children = - { - textBlockSeconds, - numericUpDownSeconds - } - }; - panel.Bind(StackPanel.IsVisibleProperty, new Binding() + var panel = SyncTimingChrome.Row(Se.Language.General.Seconds, numericUpDownSeconds, LabelWidth); + panel.Bind(IsVisibleProperty, new Binding() { Path = $"{nameof(vm.SelectedAdjustType)}.{nameof(AdjustDurationDisplay.IsSecondsVisible)}", Source = vm, @@ -143,22 +118,10 @@ private static StackPanel MakeAdjustSeconds(AdjustDurationViewModel vm) return panel; } - private static StackPanel MakeAdjustPercent(AdjustDurationViewModel vm) + private static Grid MakeAdjustPercent(AdjustDurationViewModel vm) { - var textBlockSeconds = new TextBlock - { - Text = Se.Language.General.Percent, - VerticalAlignment = VerticalAlignment.Center, - MinWidth = LabelMinWidth, - }; - var numericUpDownSeconds = new NumericUpDown - { - Minimum = -1000000, - Maximum = 1000000, - Width = NumericUpDownWidth, - VerticalAlignment = VerticalAlignment.Center, - }; - numericUpDownSeconds.Bind(NumericUpDown.ValueProperty, new Binding + var numericUpDownPercent = MakeNumber(Se.Language.General.Percent, -1000000, 1000000); + numericUpDownPercent.Bind(NumericUpDown.ValueProperty, new Binding { Path = nameof(vm.AdjustPercent), Mode = BindingMode.TwoWay, @@ -166,18 +129,8 @@ private static StackPanel MakeAdjustPercent(AdjustDurationViewModel vm) Converter = new NullableIntConverter(), }); - var panel = new StackPanel - { - Orientation = Orientation.Horizontal, - VerticalAlignment = VerticalAlignment.Center, - Margin = new Thickness(10, 25, 0, 0), - Children = - { - textBlockSeconds, - numericUpDownSeconds - } - }; - panel.Bind(StackPanel.IsVisibleProperty, new Binding + var panel = SyncTimingChrome.Row(Se.Language.General.Percent, numericUpDownPercent, LabelWidth); + panel.Bind(IsVisibleProperty, new Binding { Path = $"{nameof(vm.SelectedAdjustType)}.{nameof(AdjustDurationDisplay.IsPercentVisible)}", Source = vm, @@ -187,23 +140,10 @@ private static StackPanel MakeAdjustPercent(AdjustDurationViewModel vm) return panel; } - - private static StackPanel MakeAdjustFixed(AdjustDurationViewModel vm) + private static Grid MakeAdjustFixed(AdjustDurationViewModel vm) { - var textBlockSeconds = new TextBlock - { - Text = Se.Language.Tools.AdjustDurations.Fixed, - VerticalAlignment = VerticalAlignment.Center, - MinWidth = LabelMinWidth, - }; - var numericUpDownSeconds = new NumericUpDown - { - Minimum = -1000000, - Maximum = 1000000, - Width = NumericUpDownWidth, - VerticalAlignment = VerticalAlignment.Center, - }; - numericUpDownSeconds.Bind(NumericUpDown.ValueProperty, new Binding + var numericUpDownFixed = MakeNumber(Se.Language.Tools.AdjustDurations.Fixed, -1000000, 1000000); + numericUpDownFixed.Bind(NumericUpDown.ValueProperty, new Binding { Mode = BindingMode.TwoWay, Source = vm, @@ -211,18 +151,8 @@ private static StackPanel MakeAdjustFixed(AdjustDurationViewModel vm) Converter = new DoubleToThreeDecimalConverter(), }); - var panel = new StackPanel - { - Orientation = Orientation.Horizontal, - VerticalAlignment = VerticalAlignment.Center, - Margin = new Thickness(10, 25, 0, 0), - Children = - { - textBlockSeconds, - numericUpDownSeconds - } - }; - panel.Bind(StackPanel.IsVisibleProperty, new Binding + var panel = SyncTimingChrome.Row(Se.Language.Tools.AdjustDurations.Fixed, numericUpDownFixed, LabelWidth); + panel.Bind(IsVisibleProperty, new Binding { Path = $"{nameof(vm.SelectedAdjustType)}.{nameof(AdjustDurationDisplay.IsFixedVisible)}", Source = vm, @@ -231,21 +161,9 @@ private static StackPanel MakeAdjustFixed(AdjustDurationViewModel vm) return panel; } - private static Grid MakeAdjustRecalculate(AdjustDurationViewModel vm) + private static StackPanel MakeAdjustRecalculate(AdjustDurationViewModel vm) { - var textBlockMax = new TextBlock - { - Text = Se.Language.General.MaxCharactersPerSecond, - VerticalAlignment = VerticalAlignment.Center, - }; - var numericUpDownMax = new NumericUpDown - { - Minimum = 0, - Maximum = 1000, - Width = NumericUpDownWidth, - VerticalAlignment = VerticalAlignment.Center, - Margin = new Thickness(10, 0, 0, 0), - }; + var numericUpDownMax = MakeNumber(Se.Language.General.MaxCharactersPerSecond, 0, 1000); numericUpDownMax.Bind(NumericUpDown.ValueProperty, new Binding { Path = nameof(vm.AdjustRecalculateMaxCharacterPerSecond), @@ -254,19 +172,7 @@ private static Grid MakeAdjustRecalculate(AdjustDurationViewModel vm) Converter = new DoubleToOneDecimalConverter(), }); - var textBlockOptimal = new TextBlock - { - Text = Se.Language.General.OptimalCharactersPerSecond, - VerticalAlignment = VerticalAlignment.Center, - }; - var numericUpDownOptimal = new NumericUpDown - { - Minimum = 0, - Maximum = 1000, - Width = NumericUpDownWidth, - VerticalAlignment = VerticalAlignment.Center, - Margin = new Thickness(10, 0, 0, 0), - }; + var numericUpDownOptimal = MakeNumber(Se.Language.General.OptimalCharactersPerSecond, 0, 1000); numericUpDownOptimal.Bind(NumericUpDown.ValueProperty, new Binding { Path = nameof(vm.AdjustRecalculateOptimalCharacterPerSecond), @@ -275,61 +181,49 @@ private static Grid MakeAdjustRecalculate(AdjustDurationViewModel vm) Converter = new DoubleToOneDecimalConverter(), }); - var grid = new Grid + var toggleExtendOnly = new ToggleSwitch { - ColumnDefinitions = - { - new ColumnDefinition { Width = GridLength.Auto }, - new ColumnDefinition { Width = GridLength.Auto }, - }, - RowDefinitions = - { - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - }, - HorizontalAlignment = HorizontalAlignment.Stretch, + OnContent = null, + OffContent = null, + MinWidth = 0, VerticalAlignment = VerticalAlignment.Center, - RowSpacing = 10, - Margin = new Thickness(10, 25, 0, 0), }; - - var checkBoxExtendOnly = new CheckBox - { - Content = Se.Language.Tools.AdjustDurations.ExtendOnly, - VerticalAlignment = VerticalAlignment.Center, - Margin = new Thickness(10, 0, 0, 0), - }; - checkBoxExtendOnly.Bind(CheckBox.IsCheckedProperty, new Binding + toggleExtendOnly.Bind(ToggleButton.IsCheckedProperty, new Binding { Path = nameof(vm.ExtendOnly), Mode = BindingMode.TwoWay, Source = vm, }); + AutomationProperties.SetName(toggleExtendOnly, Se.Language.Tools.AdjustDurations.ExtendOnly); + var rowExtendOnly = new StackPanel + { + Orientation = Orientation.Horizontal, + Spacing = 8, + Children = + { + toggleExtendOnly, + new TextBlock { Text = Se.Language.Tools.AdjustDurations.ExtendOnly, FontSize = 12.5, VerticalAlignment = VerticalAlignment.Center }, + }, + }; - grid.Children.Add(textBlockMax); - Grid.SetColumn(textBlockMax, 0); - - grid.Children.Add(numericUpDownMax); - Grid.SetColumn(numericUpDownMax, 1); - - grid.Children.Add(textBlockOptimal); - Grid.SetColumn(textBlockOptimal, 0); - Grid.SetRow(textBlockOptimal, 1); - - grid.Children.Add(numericUpDownOptimal); - Grid.SetColumn(numericUpDownOptimal, 1); - Grid.SetRow(numericUpDownOptimal, 1); - - grid.Add(checkBoxExtendOnly, 2, 1); + var panel = new StackPanel + { + Spacing = 10, + Children = + { + SyncTimingChrome.Row(Se.Language.General.MaxCharactersPerSecond, numericUpDownMax, LabelWidth), + SyncTimingChrome.Row(Se.Language.General.OptimalCharactersPerSecond, numericUpDownOptimal, LabelWidth), + SyncTimingChrome.Row(string.Empty, rowExtendOnly, LabelWidth), + }, + }; - grid.Bind(Grid.IsVisibleProperty, new Binding + panel.Bind(IsVisibleProperty, new Binding { Path = $"{nameof(vm.SelectedAdjustType)}.{nameof(AdjustDurationDisplay.IsRecalculateVisible)}", Source = vm, Mode = BindingMode.OneWay, }); - return grid; + return panel; } } diff --git a/src/ui/Features/Tools/ApplyDurationLimits/ApplyDurationLimitsWindow.cs b/src/ui/Features/Tools/ApplyDurationLimits/ApplyDurationLimitsWindow.cs index cf55853298e..eef132d28fb 100644 --- a/src/ui/Features/Tools/ApplyDurationLimits/ApplyDurationLimitsWindow.cs +++ b/src/ui/Features/Tools/ApplyDurationLimits/ApplyDurationLimitsWindow.cs @@ -10,15 +10,21 @@ using Avalonia.Media; using System.Collections; using Nikse.SubtitleEdit.Features.Main; +using Nikse.SubtitleEdit.Features.Sync; using Nikse.SubtitleEdit.Logic; using Nikse.SubtitleEdit.Logic.Config; using Nikse.SubtitleEdit.Logic.ValueConverters; namespace Nikse.SubtitleEdit.Features.Tools.ApplyDurationLimits; +/// +/// Apply duration limits, in the editor look (see ): the limits on top, +/// the fixes (with their Apply check boxes) and the lines that cannot be fixed below, Cancel/OK in +/// the button bar. +/// public class ApplyDurationLimitsWindow : Window { - private CheckBox _checkBoxFixMinDuration = null!; + private ToggleSwitch _toggleFixMinDuration = null!; public ApplyDurationLimitsWindow(ApplyDurationLimitsViewModel vm) { @@ -29,41 +35,31 @@ public ApplyDurationLimitsWindow(ApplyDurationLimitsViewModel vm) Height = 800; MinWidth = 600; MinHeight = 400; + SyncTimingChrome.Apply(this); vm.Window = this; DataContext = vm; - var buttonOk = UiUtil.MakeButtonOk(vm.OkCommand); - var buttonCancel = UiUtil.MakeButtonCancel(vm.CancelCommand); - var panelButtons = UiUtil.MakeButtonBar(buttonOk, buttonCancel); - - var grid = new Grid + var sections = new Grid { RowDefinitions = { new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, new RowDefinition { Height = new GridLength(2, GridUnitType.Star) }, new RowDefinition { Height = new GridLength(1, GridUnitType.Star) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, }, - ColumnDefinitions = - { - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) }, - }, - Margin = UiUtil.MakeWindowMargin(), - ColumnSpacing = 10, - RowSpacing = 10, - Width = double.NaN, - HorizontalAlignment = HorizontalAlignment.Stretch, }; - grid.Add(MakeControlsView(vm), 0); - grid.Add(MakeFixesView(vm), 1); - grid.Add(MakeCannotFixView(vm), 2); - grid.Add(panelButtons, 3); + sections.Add(EditorChrome.MakeEditorSection(Se.Language.General.Options, MakeControlsView(vm), first: true), 0); + sections.Add(MakeFixesView(vm), 1); + sections.Add(MakeCannotFixView(vm), 2); - Content = grid; + var buttonOk = UiUtil.MakeButtonOk(vm.OkCommand); + buttonOk.Classes.Add("primary"); + var buttonCancel = UiUtil.MakeButtonCancel(vm.CancelCommand); - UiUtil.FocusOnFirstActivation(this, _checkBoxFixMinDuration); // initial focus on an input, not an action button - a focused button clicks on bare Space + Content = SyncTimingChrome.MakeRoot(sections, SyncTimingChrome.MakeButtonBar(null, buttonCancel, buttonOk)); + + UiUtil.FocusOnFirstActivation(this, _toggleFixMinDuration); // initial focus on an input, not an action button - a focused button clicks on bare Space KeyDown += vm.KeyDown; Closing += delegate { UiUtil.SaveWindowPosition(this); }; @@ -84,61 +80,49 @@ private Grid MakeControlsView(ApplyDurationLimitsViewModel vm) new ColumnDefinition { Width = new GridLength(1, GridUnitType.Auto) }, new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) }, }, - ColumnSpacing = 10, + ColumnSpacing = 14, RowSpacing = 10, HorizontalAlignment = HorizontalAlignment.Stretch, }; - var checkBoxFixMinDuration = UiUtil.MakeCheckBox(vm.FixMinDurationLabel, vm, nameof(vm.FixMinDurationMs)); - _checkBoxFixMinDuration = checkBoxFixMinDuration; - checkBoxFixMinDuration.IsCheckedChanged += (s, e) => vm.SetChanged(); - var numericUpDownMinDuration = UiUtil.MakeNumericUpDownInt(1, 10000, 1000, 150, vm, nameof(vm.MinDurationMsOrFrames)) - .WithBindEnabled(nameof(vm.FixMinDurationMs)); + var switchFixMinDuration = TimingToolChrome.MakeSwitch(vm.FixMinDurationLabel, nameof(vm.FixMinDurationMs), out var toggleFixMinDuration); + _toggleFixMinDuration = toggleFixMinDuration; + toggleFixMinDuration.IsCheckedChanged += (s, e) => vm.SetChanged(); + var numericUpDownMinDuration = TimingToolChrome.Field( + UiUtil.MakeNumericUpDownInt(1, 10000, 1000, 90, vm, nameof(vm.MinDurationMsOrFrames)), vm.FixMinDurationLabel) + .WithBindEnabled(nameof(vm.FixMinDurationMs)); numericUpDownMinDuration.ValueChanged += (s, e) => vm.SetChanged(); - var checkBoxDoNotGoPastShotChange = UiUtil.MakeCheckBox(Se.Language.Tools.ApplyDurationLimits.DoNotGoPastShotChange, vm, nameof(vm.DoNotGoPastShotChange)) - .WithBindEnabled(nameof(vm.FixMinDurationMs)) - .WithMarginLeft(5) + var switchDoNotGoPastShotChange = TimingToolChrome.MakeSwitch(Se.Language.Tools.ApplyDurationLimits.DoNotGoPastShotChange, nameof(vm.DoNotGoPastShotChange), out var toggleDoNotGoPastShotChange) + .WithBindIsEnabled(nameof(vm.FixMinDurationMs)) + .WithMarginLeft(14) .BindIsVisible(vm, nameof(vm.IsDoNotGoPastShotChangeVisible)); - checkBoxDoNotGoPastShotChange.IsCheckedChanged += (s, e) => vm.SetChanged(); - var panelMin = UiUtil.MakeHorizontalPanel(numericUpDownMinDuration, checkBoxDoNotGoPastShotChange); + toggleDoNotGoPastShotChange.IsCheckedChanged += (s, e) => vm.SetChanged(); + var panelMin = new StackPanel + { + Orientation = Orientation.Horizontal, + Spacing = 8, + Children = { numericUpDownMinDuration, switchDoNotGoPastShotChange }, + }; - var checkBoxFixMaxDuration = UiUtil.MakeCheckBox(vm.FixMaxDurationLabel, vm, nameof(vm.FixMaxDurationMs)); - checkBoxFixMaxDuration.IsCheckedChanged += (s, e) => vm.SetChanged(); - var numericUpDownMaxDuration = UiUtil.MakeNumericUpDownInt(1, 10000, 1000, 150, vm, nameof(vm.MaxDurationMsOrFrames)) + var switchFixMaxDuration = TimingToolChrome.MakeSwitch(vm.FixMaxDurationLabel, nameof(vm.FixMaxDurationMs), out var toggleFixMaxDuration); + toggleFixMaxDuration.IsCheckedChanged += (s, e) => vm.SetChanged(); + var numericUpDownMaxDuration = TimingToolChrome.Field( + UiUtil.MakeNumericUpDownInt(1, 10000, 1000, 90, vm, nameof(vm.MaxDurationMsOrFrames)), vm.FixMaxDurationLabel) .WithBindEnabled(nameof(vm.FixMaxDurationMs)); numericUpDownMaxDuration.ValueChanged += (s, e) => vm.SetChanged(); - grid.Add(checkBoxFixMinDuration, 0, 0); + grid.Add(switchFixMinDuration, 0, 0); grid.Add(panelMin, 0, 1); - grid.Add(checkBoxFixMaxDuration, 1, 0); + grid.Add(switchFixMaxDuration, 1, 0); grid.Add(numericUpDownMaxDuration, 1, 1); return grid; } - private static Grid MakeFixesView(ApplyDurationLimitsViewModel vm) + private static Border MakeFixesView(ApplyDurationLimitsViewModel vm) { - var grid = new Grid - { - RowDefinitions = - { - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Star) }, - }, - ColumnDefinitions = - { - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) }, - }, - ColumnSpacing = 10, - RowSpacing = 10, - HorizontalAlignment = HorizontalAlignment.Stretch, - }; - - var labelFixesAvailable = UiUtil.MakeLabel() - .WithBindText(vm, nameof(vm.FixesInfo)) - .WithMarginTop(10) - .WithMarginLeft(10); + var labelFixesAvailable = TimingToolChrome.MakeBoundHeader(nameof(vm.FixesInfo)); // No header-click sorting (the DataGrid's CanUserSortColumns is not carried // over): the fixes are previews in subtitle order. The DataGrid-era @@ -190,34 +174,12 @@ private static Grid MakeFixesView(ApplyDurationLimitsViewModel vm) TableViewExtras.AddSpaceToggle(dataGrid, item => item.Apply, (item, v) => item.Apply = v); - grid.Add(labelFixesAvailable, 0); - grid.Add(UiUtil.MakeBorderForControlNoPadding(dataGrid), 1); - - return grid; + return TimingToolChrome.MakeListSection(labelFixesAvailable, dataGrid); } - private static Grid MakeCannotFixView(ApplyDurationLimitsViewModel vm) + private static Border MakeCannotFixView(ApplyDurationLimitsViewModel vm) { - var grid = new Grid - { - RowDefinitions = - { - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Star) }, - }, - ColumnDefinitions = - { - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) }, - }, - ColumnSpacing = 10, - RowSpacing = 10, - HorizontalAlignment = HorizontalAlignment.Stretch, - }; - - var labelFixesAvailable = UiUtil.MakeLabel() - .WithBindText(vm, nameof(vm.FixesSkippedInfo)) - .WithMarginTop(10) - .WithMarginLeft(10); + var labelFixesAvailable = TimingToolChrome.MakeBoundHeader(nameof(vm.FixesSkippedInfo)); var fullTimeConverter = new TimeSpanToDisplayFullConverter(); var shortTimeConverter = new TimeSpanToDisplayShortConverter(); @@ -280,9 +242,6 @@ private static Grid MakeCannotFixView(ApplyDurationLimitsViewModel vm) } }, RoutingStrategies.Tunnel); - grid.Add(labelFixesAvailable, 0); - grid.Add(UiUtil.MakeBorderForControlNoPadding(dataGrid), 1); - - return grid; + return TimingToolChrome.MakeListSection(labelFixesAvailable, dataGrid); } } diff --git a/src/ui/Features/Tools/ApplyMinGap/ApplyMinGapWindow.cs b/src/ui/Features/Tools/ApplyMinGap/ApplyMinGapWindow.cs index 4b2eaec5a2a..8849c2f7732 100644 --- a/src/ui/Features/Tools/ApplyMinGap/ApplyMinGapWindow.cs +++ b/src/ui/Features/Tools/ApplyMinGap/ApplyMinGapWindow.cs @@ -1,8 +1,10 @@ +using Avalonia; using Avalonia.Controls; using Avalonia.Data; using Avalonia.Input; using Avalonia.Interactivity; using Avalonia.Layout; +using Nikse.SubtitleEdit.Features.Sync; using Nikse.SubtitleEdit.Logic; using Nikse.SubtitleEdit.Logic.Config; using Nikse.SubtitleEdit.Logic.ValueConverters; @@ -10,6 +12,10 @@ namespace Nikse.SubtitleEdit.Features.Tools.ApplyMinGap; +/// +/// Apply minimum gaps, in the editor look (see ): the minimum gap on top, +/// the preview of the changed gaps below it, the count and Cancel/OK in the button bar. +/// public class ApplyMinGapWindow : Window { public ApplyMinGapWindow(ApplyMinGapViewModel vm) @@ -21,52 +27,45 @@ public ApplyMinGapWindow(ApplyMinGapViewModel vm) Height = 800; MinWidth = 900; MinHeight = 500; + SyncTimingChrome.Apply(this); vm.Window = this; DataContext = vm; - var labelMinXBetweenLines = UiUtil.MakeLabel().WithBindText(vm, nameof(vm.MinXBetweenLines)); - var numericUpDownMinGap = UiUtil.MakeNumericUpDownInt(0, 1000, Se.Settings.Tools.BridgeGaps.MinGapMs, 130, vm, nameof(vm.MinGapMsOrFrames)); + var numericUpDownMinGap = TimingToolChrome.Field( + UiUtil.MakeNumericUpDownInt(0, 1000, Se.Settings.Tools.BridgeGaps.MinGapMs, 90, vm, nameof(vm.MinGapMsOrFrames)), + vm.MinXBetweenLines); numericUpDownMinGap.ValueChanged += vm.ValueChanged; - var buttonCalculate = UiUtil.MakeButtonBrowse(vm.CalculateMinGapMsCommand, - accessibleName: Se.Language.Options.Settings.MinGapCalculateDotDotDot) + var buttonCalculate = EditorChrome.CreateToolButton("fa-solid fa-calculator", Se.Language.Options.Settings.MinGapCalculateDotDotDot, vm.CalculateMinGapMsCommand, 28) .WithBindIsVisible(vm, nameof(vm.IsMsMode)); - var panelControls = UiUtil.MakeHorizontalPanel(labelMinXBetweenLines, numericUpDownMinGap, buttonCalculate); + var labelMinXBetweenLines = EditorChrome.MakeDimLabel(string.Empty, new Thickness(0)).WithBindText(vm, nameof(vm.MinXBetweenLines)); + var panelControls = new StackPanel { Orientation = Orientation.Horizontal, Spacing = 8 }; + panelControls.Children.Add(labelMinXBetweenLines); + panelControls.Children.Add(numericUpDownMinGap); + panelControls.Children.Add(buttonCalculate); + var optionsSection = EditorChrome.MakeEditorSection(Se.Language.General.Options, panelControls, first: true); - var subtitleView = MakeSubtitleView(vm); + var listSection = TimingToolChrome.MakeListSection(EditorChrome.MakeSectionHeader(Se.Language.General.Preview), MakeSubtitleView(vm)); - var labelStatus = UiUtil.MakeLabel(string.Empty).WithBindText(vm, nameof(vm.StatusText)).WithAlignmentTop(); - - var buttonOk = UiUtil.MakeButtonOk(vm.OkCommand); - var buttonCancel = UiUtil.MakeButtonCancel(vm.CancelCommand); - var panelButtons = UiUtil.MakeButtonBar(buttonOk, buttonCancel); - - var grid = new Grid + var sections = new Grid { RowDefinitions = { - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, // Min gap + new RowDefinition { Height = GridLength.Auto }, // Min gap new RowDefinition { Height = new GridLength(1, GridUnitType.Star) }, // Subtitle view - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, // Buttons }, - ColumnDefinitions = - { - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) }, - }, - Margin = UiUtil.MakeWindowMargin(), - ColumnSpacing = 10, - RowSpacing = 10, - Width = double.NaN, - HorizontalAlignment = HorizontalAlignment.Stretch, }; + sections.Add(optionsSection, 0); + sections.Add(listSection, 1); - grid.Add(panelControls, 0); - grid.Add(subtitleView, 1); - grid.Add(labelStatus, 2); - grid.Add(panelButtons, 2); + var buttonOk = UiUtil.MakeButtonOk(vm.OkCommand); + buttonOk.Classes.Add("primary"); + var buttonCancel = UiUtil.MakeButtonCancel(vm.CancelCommand); - Content = grid; + Content = SyncTimingChrome.MakeRoot( + sections, + SyncTimingChrome.MakeButtonBar(TimingToolChrome.MakeStatus(nameof(vm.StatusText)), buttonCancel, buttonOk)); UiUtil.FocusOnFirstActivation(this, numericUpDownMinGap); // initial focus on an input, not an action button - a focused button clicks on bare Space KeyDown += (_, e) => vm.OnKeyDown(e); @@ -75,7 +74,7 @@ public ApplyMinGapWindow(ApplyMinGapViewModel vm) Loaded += delegate { UiUtil.RestoreWindowPosition(this); }; } - private static Border MakeSubtitleView(ApplyMinGapViewModel vm) + private static Control MakeSubtitleView(ApplyMinGapViewModel vm) { var fullTimeConverter = new TimeSpanToDisplayFullConverter(); var shortTimeConverter = new TimeSpanToDisplayShortConverter(); @@ -146,6 +145,6 @@ private static Border MakeSubtitleView(ApplyMinGapViewModel vm) } }, RoutingStrategies.Tunnel); - return UiUtil.MakeBorderForControlNoPadding(dataGridSubtitle); + return dataGridSubtitle; } } diff --git a/src/ui/Features/Tools/BeautifyTimeCodes/BeautifyTimeCodesWindow.cs b/src/ui/Features/Tools/BeautifyTimeCodes/BeautifyTimeCodesWindow.cs index 6ae68dba042..345f480bfb1 100644 --- a/src/ui/Features/Tools/BeautifyTimeCodes/BeautifyTimeCodesWindow.cs +++ b/src/ui/Features/Tools/BeautifyTimeCodes/BeautifyTimeCodesWindow.cs @@ -1,14 +1,21 @@ using Avalonia; +using Avalonia.Automation; using Avalonia.Controls; +using Avalonia.Controls.Primitives; using Avalonia.Data; using Avalonia.Layout; using Avalonia.Media; +using Nikse.SubtitleEdit.Features.Sync; using Nikse.SubtitleEdit.Logic; using Nikse.SubtitleEdit.Logic.Config; -using Optris.Icons.Avalonia; namespace Nikse.SubtitleEdit.Features.Tools.BeautifyTimeCodes; +/// +/// Beautify time codes, in the editor look (see ): the exact time codes +/// toolbar on top, the original and beautified waveforms, the change navigator under them, Edit +/// profile and Cancel/OK in the button bar. +/// public class BeautifyTimeCodesWindow : Window { public BeautifyTimeCodesWindow(BeautifyTimeCodesViewModel vm) @@ -20,29 +27,21 @@ public BeautifyTimeCodesWindow(BeautifyTimeCodesViewModel vm) Height = 600; MinWidth = 900; MinHeight = 460; + SyncTimingChrome.Apply(this); vm.Window = this; DataContext = vm; - Content = new Grid + // The waveforms give up height first, so the change navigator and the buttons stay + // reachable on a short screen. + var sections = new Grid { - RowDefinitions = new RowDefinitions("Auto,*,Auto,Auto"), - ColumnDefinitions = new ColumnDefinitions("*"), - Margin = UiUtil.MakeWindowMargin(), - RowSpacing = 10, - Children = - { - BuildTopBar(vm), - BuildVisualizerArea(vm), - BuildChangeNavBar(vm), - BuildButtonBar(vm), - }, + RowDefinitions = new RowDefinitions("Auto,*,Auto"), }; + sections.Add(BuildTopBar(vm), 0, 0); + sections.Add(BuildVisualizerArea(vm), 1, 0); + sections.Add(BuildChangeNavBar(vm), 2, 0); - var c = (Grid)Content; - Grid.SetRow(c.Children[0], 0); - Grid.SetRow(c.Children[1], 1); - Grid.SetRow(c.Children[2], 2); - Grid.SetRow(c.Children[3], 3); + Content = SyncTimingChrome.MakeRoot(sections, BuildButtonBar(vm)); // Change navigation keys (arrows, PageUp/Down, Home/End) are handled at window level, so // they work whichever control has focus - nothing here needs to own them. @@ -56,8 +55,10 @@ private static Control BuildTopBar(BeautifyTimeCodesViewModel vm) { VerticalAlignment = VerticalAlignment.Center, HorizontalAlignment = HorizontalAlignment.Right, - Opacity = 0.8, - FontSize = UiUtil.ScaledFontSize(12), + Foreground = EditorChrome.DimTextBrush, + FontSize = 12, + Margin = new Thickness(12, 0, 0, 0), + TextTrimming = TextTrimming.CharacterEllipsis, [!TextBlock.TextProperty] = new Binding(nameof(vm.StatsLine)) { Source = vm }, }; @@ -67,7 +68,13 @@ private static Control BuildTopBar(BeautifyTimeCodesViewModel vm) }; grid.Add(BuildTimeCodesBar(vm), 0, 0); grid.Add(stats, 0, 1); - return grid; + return new Border + { + Child = grid, + BorderBrush = EditorChrome.LineBrush, + BorderThickness = new Thickness(0, 0, 0, 1), + Padding = new Thickness(14, 8), + }; } /// @@ -78,36 +85,42 @@ private static Control BuildTimeCodesBar(BeautifyTimeCodesViewModel vm) { var l = Se.Language.Tools.BeautifyTimeCodes; - var checkBoxExact = new CheckBox + var toggleExact = new ToggleSwitch { - Content = l.UseExactTimeCodes, + OnContent = null, + OffContent = null, + MinWidth = 0, VerticalAlignment = VerticalAlignment.Center, - [!CheckBox.IsCheckedProperty] = new Binding(nameof(vm.UseExactTimeCodes)) + [!ToggleButton.IsCheckedProperty] = new Binding(nameof(vm.UseExactTimeCodes)) { Source = vm, Mode = BindingMode.TwoWay, UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged, }, }; + AutomationProperties.SetName(toggleExact, l.UseExactTimeCodes); + var labelExact = new TextBlock { Text = l.UseExactTimeCodes, FontSize = 12.5, VerticalAlignment = VerticalAlignment.Center }; if (Se.Settings.Appearance.ShowHints) { - ToolTip.SetTip(checkBoxExact, l.TimeCodesHint); + ToolTip.SetTip(toggleExact, l.TimeCodesHint); + ToolTip.SetTip(labelExact, l.TimeCodesHint); } var status = new TextBlock { VerticalAlignment = VerticalAlignment.Center, - Opacity = 0.8, - FontSize = UiUtil.ScaledFontSize(12), + Foreground = EditorChrome.DimTextBrush, + FontSize = 12, + Margin = new Thickness(6, 0, 0, 0), [!TextBlock.TextProperty] = new Binding(nameof(vm.TimeCodesStatus)) { Source = vm }, }; - var buttonExtract = UiUtil.MakeButton(l.ExtractTimeCodes, vm.ExtractTimeCodesCommand); + var buttonExtract = SyncTimingChrome.MakeTextToolButton("fa-solid fa-film", l.ExtractTimeCodes, vm.ExtractTimeCodesCommand); buttonExtract.VerticalAlignment = VerticalAlignment.Center; buttonExtract.Bind(Button.IsVisibleProperty, new Binding(nameof(vm.CanExtractTimeCodes)) { Source = vm }); - var progress = UiUtil.MakeProgressBar(); + var progress = UiUtil.MakeProgressBar(6); progress.Width = 140; progress.VerticalAlignment = VerticalAlignment.Center; progress.Bind(ProgressBar.ValueProperty, new Binding(nameof(vm.ExtractProgressValue)) { Source = vm }); @@ -118,7 +131,7 @@ private static Control BuildTimeCodesBar(BeautifyTimeCodesViewModel vm) Orientation = Orientation.Horizontal, Spacing = 8, VerticalAlignment = VerticalAlignment.Center, - Children = { checkBoxExact, status, buttonExtract, progress }, + Children = { toggleExact, labelExact, status, buttonExtract, progress }, }; } @@ -150,8 +163,9 @@ private static Control BuildVisualizerArea(BeautifyTimeCodesViewModel vm) var grid = new Grid { - RowDefinitions = new RowDefinitions("Auto,3*,10,Auto,3*"), + RowDefinitions = new RowDefinitions("Auto,3*,14,Auto,3*"), ColumnDefinitions = new ColumnDefinitions("*"), + Margin = new Thickness(14, 12, 14, 14), }; grid.Add(labelOriginal, 0, 0); grid.Add(borderOriginal, 1, 0); @@ -162,12 +176,8 @@ private static Control BuildVisualizerArea(BeautifyTimeCodesViewModel vm) private static (TextBlock label, Border border, Controls.AudioVisualizerControl.AudioVisualizer av) BuildLabeledVisualizer(string title) { - var label = new TextBlock - { - Text = title, - FontWeight = FontWeight.Bold, - Margin = new Thickness(5, 5, 5, 2), - }; + var label = EditorChrome.MakeSectionHeader(title); + label.Margin = new Thickness(0, 0, 0, 8); var av = new Controls.AudioVisualizerControl.AudioVisualizer { @@ -179,10 +189,13 @@ private static (TextBlock label, Border border, Controls.AudioVisualizerControl. ParagraphSelectedBackground = Color.FromArgb(210, 230, 160, 40), // current change: amber }; + // The waveform itself stays dark in both themes; only its frame follows the editor palette. var border = new Border { - BorderBrush = Brushes.Gray, + BorderBrush = EditorChrome.FieldBorderBrush, BorderThickness = new Thickness(1), + CornerRadius = new CornerRadius(6), + ClipToBounds = true, Background = Brushes.Black, Child = av, }; @@ -195,16 +208,17 @@ private static Control BuildChangeNavBar(BeautifyTimeCodesViewModel vm) // The same steps are on the keyboard (see BeautifyTimeCodesViewModel.OnKeyDown); the // tooltips are the only place that advertises it, so name the keys there. - var buttonPrev = UiUtil.MakeButton(vm.PreviousChangeCommand, IconNames.ArrowUpThin, $"{l.PreviousChange} (Up / PageUp)"); + var buttonPrev = EditorChrome.CreateToolButton("fa-solid fa-chevron-up", $"{l.PreviousChange} (Up / PageUp)", vm.PreviousChangeCommand, 28); buttonPrev.Bind(Button.IsEnabledProperty, new Binding(nameof(vm.CanGoPrevious)) { Source = vm }); - var buttonNext = UiUtil.MakeButton(vm.NextChangeCommand, IconNames.ArrowDownThin, $"{l.NextChange} (Down / PageDown)"); + var buttonNext = EditorChrome.CreateToolButton("fa-solid fa-chevron-down", $"{l.NextChange} (Down / PageDown)", vm.NextChangeCommand, 28); buttonNext.Bind(Button.IsEnabledProperty, new Binding(nameof(vm.CanGoNext)) { Source = vm }); var labelPosition = new TextBlock { VerticalAlignment = VerticalAlignment.Center, FontWeight = FontWeight.SemiBold, + FontSize = 12.5, MinWidth = 120, TextAlignment = TextAlignment.Center, [!TextBlock.TextProperty] = new Binding(nameof(vm.ChangePositionLabel)) { Source = vm }, @@ -215,7 +229,8 @@ private static Control BuildChangeNavBar(BeautifyTimeCodesViewModel vm) HorizontalAlignment = HorizontalAlignment.Stretch, TextWrapping = TextWrapping.Wrap, TextTrimming = TextTrimming.CharacterEllipsis, - Margin = new Thickness(0, 4, 0, 0), + FontSize = 12.5, + Margin = new Thickness(4, 4, 0, 0), [!TextBlock.TextProperty] = new Binding(nameof(vm.ChangeDetail)) { Source = vm }, }; @@ -224,15 +239,17 @@ private static Control BuildChangeNavBar(BeautifyTimeCodesViewModel vm) HorizontalAlignment = HorizontalAlignment.Stretch, TextWrapping = TextWrapping.Wrap, TextTrimming = TextTrimming.CharacterEllipsis, - Opacity = 0.75, + Foreground = EditorChrome.DimTextBrush, + FontSize = 12, FontStyle = FontStyle.Italic, + Margin = new Thickness(4, 0, 0, 0), [!TextBlock.TextProperty] = new Binding(nameof(vm.ChangeNotes)) { Source = vm }, }; var navRow = new StackPanel { Orientation = Orientation.Horizontal, - Spacing = 6, + Spacing = 4, VerticalAlignment = VerticalAlignment.Center, Children = { buttonPrev, labelPosition, buttonNext }, }; @@ -246,18 +263,20 @@ private static Control BuildChangeNavBar(BeautifyTimeCodesViewModel vm) return new Border { - BorderBrush = new SolidColorBrush(Color.FromArgb(120, 128, 128, 128)), - BorderThickness = new Thickness(0, 1, 0, 1), - Padding = new Thickness(6, 6, 6, 6), + BorderBrush = EditorChrome.LineBrush, + BorderThickness = new Thickness(0, 1, 0, 0), + Padding = new Thickness(10, 8, 14, 10), Child = inner, }; } - private static Control BuildButtonBar(BeautifyTimeCodesViewModel vm) + private static Border BuildButtonBar(BeautifyTimeCodesViewModel vm) { - var buttonEditProfile = UiUtil.MakeButton(Se.Language.Tools.BeautifyTimeCodes.EditProfile, vm.EditProfileCommand); + var buttonEditProfile = SyncTimingChrome.MakeTextToolButton("fa-solid fa-sliders", Se.Language.Tools.BeautifyTimeCodes.EditProfile, vm.EditProfileCommand); + buttonEditProfile.HorizontalAlignment = HorizontalAlignment.Left; var buttonOk = UiUtil.MakeButtonOk(vm.OkCommand); + buttonOk.Classes.Add("primary"); var buttonCancel = UiUtil.MakeButtonCancel(vm.CancelCommand); - return UiUtil.MakeButtonBar(buttonEditProfile, buttonOk, buttonCancel); + return SyncTimingChrome.MakeButtonBar(buttonEditProfile, buttonCancel, buttonOk); } } diff --git a/src/ui/Features/Tools/BeautifyTimeCodes/Profile/BeautifyTimeCodesProfileWindow.cs b/src/ui/Features/Tools/BeautifyTimeCodes/Profile/BeautifyTimeCodesProfileWindow.cs index 7bec6a68aba..6da4f56463b 100644 --- a/src/ui/Features/Tools/BeautifyTimeCodes/Profile/BeautifyTimeCodesProfileWindow.cs +++ b/src/ui/Features/Tools/BeautifyTimeCodes/Profile/BeautifyTimeCodesProfileWindow.cs @@ -1,23 +1,37 @@ -using Avalonia; +using Avalonia; +using Avalonia.Automation; using Avalonia.Controls; using Avalonia.Controls.Primitives; +using Avalonia.Controls.Shapes; using Avalonia.Data; using Avalonia.Data.Converters; using Avalonia.Layout; using Avalonia.Media; -using CommunityToolkit.Mvvm.Input; using Nikse.SubtitleEdit.Controls; +using Nikse.SubtitleEdit.Features.Sync; using Nikse.SubtitleEdit.Logic; using Nikse.SubtitleEdit.Logic.Config; using Nikse.SubtitleEdit.Logic.Config.Language.Tools; -using Optris.Icons.Avalonia; +using System; using System.Globalization; using System.Linq; namespace Nikse.SubtitleEdit.Features.Tools.BeautifyTimeCodes.Profile; +/// +/// The beautify time codes profile, in the editor look (see ): the base +/// gap on top, in/out cues and connected/chaining side by side in sections (each with its cue +/// preview), the presets plus Cancel/OK in the button bar. The sections scroll on a short screen. +/// The in/out and general/in/out pages of the connected and chaining sections are segmented +/// buttons bound to the same view model indices the tab controls used. +/// public class BeautifyTimeCodesProfileWindow : Window { + private const double LabelWidth = 80; + + private static readonly IBrush GreenZoneBrush = new SolidColorBrush(Color.FromRgb(0, 160, 0)); + private static readonly IBrush RedZoneBrush = new SolidColorBrush(Color.FromRgb(200, 30, 30)); + private readonly LanguageBeautifyTimeCodesProfile _l; private readonly BeautifyTimeCodesProfileViewModel _vm; @@ -37,50 +51,33 @@ public BeautifyTimeCodesProfileWindow(BeautifyTimeCodesProfileViewModel vm) UiUtil.InitializeWindow(this, GetType().Name); Title = _l.Title; CanResize = true; - Width = 1220; - Height = 760; - MinWidth = 1180; - MinHeight = 640; + Width = 1080; + Height = 640; + MinWidth = 960; + MinHeight = 520; + SyncTimingChrome.Apply(this); vm.Window = this; DataContext = vm; - var grid = new Grid - { - RowDefinitions = new RowDefinitions("Auto,*,Auto"), - ColumnDefinitions = new ColumnDefinitions("*,*"), - ColumnSpacing = 10, - RowSpacing = 10, - Margin = UiUtil.MakeWindowMargin(), - }; - - grid.Add(BuildGeneralSection(), 0, 0, 1, 2); - grid.Add(BuildInCuesGroup(), 1, 0); - grid.Add(BuildOutCuesGroup(), 1, 1); + var sections = new StackPanel(); + sections.Children.Add(BuildGeneralSection()); + sections.Children.Add(SideBySide(BuildInCuesGroup(), BuildOutCuesGroup())); + sections.Children.Add(SideBySide(BuildConnectedGroup(), BuildChainingGroup())); - var bottomGrid = new Grid + // Keeps the buttons reachable when the window is clamped to a small working area. + var scrollViewer = new ScrollViewer { - ColumnDefinitions = new ColumnDefinitions("*,*"), - ColumnSpacing = 10, + Content = sections, + HorizontalScrollBarVisibility = ScrollBarVisibility.Disabled, + VerticalScrollBarVisibility = ScrollBarVisibility.Auto, }; - bottomGrid.Add(BuildConnectedGroup(), 0, 0); - bottomGrid.Add(BuildChainingGroup(), 0, 1); - grid.Add(bottomGrid, 2, 0, 1, 2); var presetButton = BuildPresetButton(); var ok = UiUtil.MakeButtonOk(vm.OkCommand); + ok.Classes.Add("primary"); var cancel = UiUtil.MakeButtonCancel(vm.CancelCommand); - var buttonBar = UiUtil.MakeButtonBar(ok, cancel); - buttonBar.Children.Insert(0, presetButton); - var outerGrid = new Grid - { - RowDefinitions = new RowDefinitions("*,Auto"), - Margin = UiUtil.MakeWindowMargin(), - RowSpacing = 10, - }; - outerGrid.Add(new ScrollViewer { Content = grid, VerticalScrollBarVisibility = ScrollBarVisibility.Auto }, 0, 0); - outerGrid.Add(buttonBar, 1, 0); - Content = outerGrid; + Content = SyncTimingChrome.MakeRoot(scrollViewer, SyncTimingChrome.MakeButtonBar(presetButton, cancel, ok)); UiUtil.FocusOnFirstActivation(this, ok); KeyDown += (_, e) => vm.OnKeyDown(e); @@ -173,7 +170,7 @@ private Control BuildPresetButton() { Content = _l.LoadPreset, HorizontalAlignment = HorizontalAlignment.Left, - Margin = new Thickness(0, 0, 20, 0), + VerticalAlignment = VerticalAlignment.Center, Flyout = flyout, }; } @@ -217,19 +214,9 @@ private void FillPresetMenu(MenuFlyout flyout) private Control BuildGeneralSection() { - var labelGap = UiUtil.MakeLabel(_l.Gap + ":"); - var nudGap = MakeFrameNud(nameof(_vm.Gap)); - var suffix = UiUtil.MakeLabel(_l.GapSuffix); - - var content = new StackPanel - { - Orientation = Orientation.Horizontal, - Spacing = 6, - VerticalAlignment = VerticalAlignment.Center, - Children = { labelGap, nudGap, suffix, UiUtil.MakeHintIcon(_l.HintGap) }, - }; - - return MakeGroupBox(Se.Language.General.General, content); + var nudGap = MakeFrameNud(nameof(_vm.Gap), _l.Gap); + var content = Row(_l.Gap, nudGap, MakeUnit(_l.GapSuffix), UiUtil.MakeHintIcon(_l.HintGap, nudGap)); + return EditorChrome.MakeEditorSection(Se.Language.General.General, content, first: true); } private Control BuildInCuesGroup() @@ -243,11 +230,11 @@ private Control BuildInCuesGroup() var stack = new StackPanel { - Spacing = 6, - Children = { _previewIn, rowGap, rowZones }, + Spacing = 8, + Children = { FramePreview(_previewIn), rowGap, rowZones }, }; - return MakeGroupBox(_l.InCues, stack); + return EditorChrome.MakeEditorSection(_l.InCues, stack); } private Control BuildOutCuesGroup() @@ -261,11 +248,11 @@ private Control BuildOutCuesGroup() var stack = new StackPanel { - Spacing = 6, - Children = { _previewOut, rowGap, rowZones }, + Spacing = 8, + Children = { FramePreview(_previewOut), rowGap, rowZones }, }; - return MakeGroupBox(_l.OutCues, stack); + return EditorChrome.MakeEditorSection(_l.OutCues, stack); } private Control BuildConnectedGroup() @@ -277,24 +264,22 @@ private Control BuildConnectedGroup() previewHost.Children.Add(_previewConnectedIn); previewHost.Children.Add(_previewConnectedOut); - var tabs = new TabControl - { - [!SelectingItemsControl.SelectedIndexProperty] = new Binding(nameof(_vm.SelectedConnectedTabIndex)) { Source = _vm, Mode = BindingMode.TwoWay }, - }; - tabs.Items.Add(new TabItem { Header = MakeTabHeader(_l.InCueClosest) }); - tabs.Items.Add(new TabItem { Header = MakeTabHeader(_l.OutCueClosest) }); - tabs.SelectionChanged += (_, _) => - { - _previewConnectedIn.IsVisible = _vm.SelectedConnectedTabIndex == 0; - _previewConnectedOut.IsVisible = _vm.SelectedConnectedTabIndex == 1; - }; + var pages = MakeSegmented( + new[] { _l.InCueClosest, _l.OutCueClosest }, + nameof(_vm.SelectedConnectedTabIndex), + () => _vm.SelectedConnectedTabIndex, + index => _vm.SelectedConnectedTabIndex = index, + () => + { + _previewConnectedIn.IsVisible = _vm.SelectedConnectedTabIndex == 0; + _previewConnectedOut.IsVisible = _vm.SelectedConnectedTabIndex == 1; + }); - // Gap row — two NUDs depending on selected tab - var labelGap = UiUtil.MakeLabel(_l.Gap + ":"); - var nudGapInLeft = MakeFrameNud(nameof(_vm.ConnectedInCueClosestLeftGap)); - var nudGapInRight = MakeFrameNud(nameof(_vm.ConnectedInCueClosestRightGap)); - var nudGapOutLeft = MakeFrameNud(nameof(_vm.ConnectedOutCueClosestLeftGap)); - var nudGapOutRight = MakeFrameNud(nameof(_vm.ConnectedOutCueClosestRightGap)); + // Gap row — two NUDs depending on selected page + var nudGapInLeft = MakeFrameNud(nameof(_vm.ConnectedInCueClosestLeftGap), _l.Gap); + var nudGapInRight = MakeFrameNud(nameof(_vm.ConnectedInCueClosestRightGap), _l.Gap); + var nudGapOutLeft = MakeFrameNud(nameof(_vm.ConnectedOutCueClosestLeftGap), _l.Gap); + var nudGapOutRight = MakeFrameNud(nameof(_vm.ConnectedOutCueClosestRightGap), _l.Gap); var conv = new IndexEqualsConverter(); nudGapInLeft.Bind(IsVisibleProperty, new Binding(nameof(_vm.SelectedConnectedTabIndex)) { Source = _vm, Converter = conv, ConverterParameter = 0 }); @@ -302,12 +287,7 @@ private Control BuildConnectedGroup() nudGapOutLeft.Bind(IsVisibleProperty, new Binding(nameof(_vm.SelectedConnectedTabIndex)) { Source = _vm, Converter = conv, ConverterParameter = 1 }); nudGapOutRight.Bind(IsVisibleProperty, new Binding(nameof(_vm.SelectedConnectedTabIndex)) { Source = _vm, Converter = conv, ConverterParameter = 1 }); - var rowGap = new StackPanel - { - Orientation = Orientation.Horizontal, - Spacing = 6, - Children = { labelGap, nudGapInLeft, nudGapInRight, nudGapOutLeft, nudGapOutRight, UiUtil.MakeHintIcon(_l.HintConnected) }, - }; + var rowGap = Row(_l.Gap, nudGapInLeft, nudGapInRight, nudGapOutLeft, nudGapOutRight, MakeUnit(_l.Frames), UiUtil.MakeHintIcon(_l.HintConnected)); var rowZones = MakeZonesRow( nameof(_vm.ConnectedLeftGreenZone), nameof(_vm.ConnectedLeftRedZone), @@ -316,22 +296,22 @@ private Control BuildConnectedGroup() var rowTreatConnected = new StackPanel { Orientation = Orientation.Horizontal, - Spacing = 6, + Spacing = 8, Children = { - UiUtil.MakeLabel(_l.TreatAsConnected), - MakeMsNud(nameof(_vm.ConnectedTreatConnectedMs)), - UiUtil.MakeLabel(_l.Milliseconds), + EditorChrome.MakeDimLabel(_l.TreatAsConnected, new Thickness(0)), + MakeMsNud(nameof(_vm.ConnectedTreatConnectedMs), _l.TreatAsConnected), + MakeUnit(_l.Milliseconds), } }; var stack = new StackPanel { - Spacing = 6, - Children = { previewHost, tabs, rowGap, rowZones, rowTreatConnected }, + Spacing = 8, + Children = { FramePreview(previewHost), pages, rowGap, rowZones, rowTreatConnected }, }; - return MakeGroupBox(_l.ConnectedSubtitles, stack); + return EditorChrome.MakeEditorSection(_l.ConnectedSubtitles, stack); } private Control BuildChainingGroup() @@ -345,19 +325,17 @@ private Control BuildChainingGroup() previewHost.Children.Add(_previewChainInOnShot); previewHost.Children.Add(_previewChainOutOnShot); - var tabs = new TabControl - { - [!SelectingItemsControl.SelectedIndexProperty] = new Binding(nameof(_vm.SelectedChainingTabIndex)) { Source = _vm, Mode = BindingMode.TwoWay }, - }; - tabs.Items.Add(new TabItem { Header = MakeTabHeader(Se.Language.General.General) }); - tabs.Items.Add(new TabItem { Header = MakeTabHeader(_l.InCueOnShot) }); - tabs.Items.Add(new TabItem { Header = MakeTabHeader(_l.OutCueOnShot) }); - tabs.SelectionChanged += (_, _) => - { - _previewChainGeneral.IsVisible = _vm.SelectedChainingTabIndex == 0; - _previewChainInOnShot.IsVisible = _vm.SelectedChainingTabIndex == 1; - _previewChainOutOnShot.IsVisible = _vm.SelectedChainingTabIndex == 2; - }; + var pages = MakeSegmented( + new[] { Se.Language.General.General, _l.InCueOnShot, _l.OutCueOnShot }, + nameof(_vm.SelectedChainingTabIndex), + () => _vm.SelectedChainingTabIndex, + index => _vm.SelectedChainingTabIndex = index, + () => + { + _previewChainGeneral.IsVisible = _vm.SelectedChainingTabIndex == 0; + _previewChainInOnShot.IsVisible = _vm.SelectedChainingTabIndex == 1; + _previewChainOutOnShot.IsVisible = _vm.SelectedChainingTabIndex == 2; + }); var conv = new IndexEqualsConverter(); @@ -404,11 +382,11 @@ private Control BuildChainingGroup() var stack = new StackPanel { - Spacing = 6, - Children = { previewHost, tabs, hostPanel }, + Spacing = 8, + Children = { FramePreview(previewHost), pages, hostPanel }, }; - return MakeGroupBox(_l.Chaining, stack); + return EditorChrome.MakeEditorSection(_l.Chaining, stack); } private Control BuildChainingSubPanel( @@ -421,22 +399,26 @@ private Control BuildChainingSubPanel( { GroupName = "chaining_" + useZonesPath, Content = _l.MaxGap, + FontSize = 12.5, + MinWidth = 110, VerticalAlignment = VerticalAlignment.Center, [!ToggleButton.IsCheckedProperty] = new Binding(useMaxGapPath) { Source = _vm, Mode = BindingMode.TwoWay }, }; - var nudMax = MakeMsNud(maxGapPath); + var nudMax = MakeMsNud(maxGapPath, _l.MaxGap); nudMax.Bind(IsEnabledProperty, new Binding(useMaxGapPath) { Source = _vm }); var maxRow = new StackPanel { Orientation = Orientation.Horizontal, - Spacing = 6, - Children = { radioMax, nudMax, UiUtil.MakeLabel(_l.Milliseconds) }, + Spacing = 8, + Children = { radioMax, nudMax, MakeUnit(_l.Milliseconds) }, }; var radioZones = new RadioButton { GroupName = "chaining_" + useZonesPath, Content = _l.Zones, + FontSize = 12.5, + MinWidth = 110, VerticalAlignment = VerticalAlignment.Center, [!ToggleButton.IsCheckedProperty] = new Binding(useZonesPath) { Source = _vm, Mode = BindingMode.TwoWay }, }; @@ -444,19 +426,19 @@ private Control BuildChainingSubPanel( var nudFields = new StackPanel { Orientation = Orientation.Horizontal, Spacing = 6 }; if (leftGreenPath != null) { - nudFields.Children.Add(MakeFrameNud(leftGreenPath)); + nudFields.Children.Add(MakeZoneNud(leftGreenPath, isGreen: true)); } if (leftRedPath != null) { - nudFields.Children.Add(MakeFrameNud(leftRedPath)); + nudFields.Children.Add(MakeZoneNud(leftRedPath, isGreen: false)); } if (rightRedPath != null) { - nudFields.Children.Add(MakeFrameNud(rightRedPath)); + nudFields.Children.Add(MakeZoneNud(rightRedPath, isGreen: false)); } if (rightGreenPath != null) { - nudFields.Children.Add(MakeFrameNud(rightGreenPath)); + nudFields.Children.Add(MakeZoneNud(rightGreenPath, isGreen: true)); } // Only the first field would inherit the "Zones" radio as its label; name them all (#12087). foreach (var nud in nudFields.Children) @@ -467,89 +449,176 @@ private Control BuildChainingSubPanel( var zonesRow = new StackPanel { Orientation = Orientation.Horizontal, - Spacing = 6, + Spacing = 8, Children = { radioZones, nudFields }, }; - var stack = new StackPanel { Spacing = 6 }; + var stack = new StackPanel { Spacing = 8 }; stack.Children.Add(maxRow); stack.Children.Add(zonesRow); if (includeShotChangeBehavior) { - var labelBehavior = UiUtil.MakeLabel(_l.ShotChangeBehavior); var combo = new ComboBox { Width = 220, [!ItemsControl.ItemsSourceProperty] = new Binding(nameof(_vm.ShotChangeBehaviors)) { Source = _vm }, [!SelectingItemsControl.SelectedIndexProperty] = new Binding(nameof(_vm.SelectedChainingShotChangeBehaviorIndex)) { Source = _vm, Mode = BindingMode.TwoWay }, }; + AutomationProperties.SetName(combo, _l.ShotChangeBehavior); var behaviorRow = new StackPanel { Orientation = Orientation.Horizontal, - Spacing = 6, - Children = { labelBehavior, combo, UiUtil.MakeHintIcon(_l.HintChaining) }, + Spacing = 8, + Margin = new Thickness(0, 2, 0, 0), + Children = { EditorChrome.MakeDimLabel(_l.ShotChangeBehavior, new Thickness(0)), combo, UiUtil.MakeHintIcon(_l.HintChaining, combo) }, }; stack.Children.Add(behaviorRow); } if (checkGeneralPath != null) { - var cb = new CheckBox - { - Content = _l.CheckGeneral, - [!ToggleButton.IsCheckedProperty] = new Binding(checkGeneralPath) { Source = _vm, Mode = BindingMode.TwoWay }, - }; - stack.Children.Add(cb); + var switchRow = TimingToolChrome.MakeSwitch(_l.CheckGeneral, checkGeneralPath, out _); + switchRow.Margin = new Thickness(0, 2, 0, 0); + stack.Children.Add(switchRow); } return stack; } - private Control MakeRowLabelNudSuffix(string label, string bindingPath, string suffix) + /// + /// Two sections side by side with a line between them, the columns sharing the width. + /// + private static Grid SideBySide(Control left, Control right) { - return new StackPanel + var grid = new Grid { - Orientation = Orientation.Horizontal, - Spacing = 6, - Children = - { - UiUtil.MakeLabel(label + ":"), - MakeFrameNud(bindingPath), - UiUtil.MakeLabel(suffix), - }, + ColumnDefinitions = { new ColumnDefinition(1, GridUnitType.Star), new ColumnDefinition(1, GridUnitType.Star) }, }; + grid.Add(left, 0, 0); + grid.Add(new Border + { + Child = right, + BorderBrush = EditorChrome.LineBrush, + BorderThickness = new Thickness(1, 0, 0, 0), + }, 0, 1); + return grid; } - private Control MakeZonesRow(string leftGreenPath, string leftRedPath, string rightRedPath, string rightGreenPath) + /// + /// The pages of a section (in/out cue closest, general/in/out on shot change) as segmented + /// buttons; the selected page index lives in the view model, as it did for the tab controls. + /// + private Control MakeSegmented(string[] titles, string indexProperty, Func getIndex, Action setIndex, Action onChanged) { - return new StackPanel + var panel = new StackPanel { Orientation = Orientation.Horizontal, Spacing = 2 }; + var buttons = new Button[titles.Length]; + for (var i = 0; i < titles.Length; i++) { - Orientation = Orientation.Horizontal, - Spacing = 6, - Children = + var index = i; + var button = new Button + { + Content = new TextBlock { Text = titles[i], FontSize = 12, VerticalAlignment = VerticalAlignment.Center }, + Height = 26, + Padding = new Thickness(10, 0), + VerticalContentAlignment = VerticalAlignment.Center, + }; + button.Classes.Add("tool"); + button.Click += (_, _) => setIndex(index); + AutomationProperties.SetName(button, titles[i]); + buttons[i] = button; + panel.Children.Add(button); + } + + void Update() + { + var selected = getIndex(); + for (var i = 0; i < buttons.Length; i++) { - UiUtil.MakeLabel(_l.Zones + ":"), - MakeZoneNud(leftGreenPath, isGreen: true), - MakeZoneNud(leftRedPath, isGreen: false), - MakeZoneNud(rightRedPath, isGreen: false), - MakeZoneNud(rightGreenPath, isGreen: true), - UiUtil.MakeHintIcon(_l.HintZones), - }, + buttons[i].Classes.Set("active", i == selected); + } + + onChanged(); + } + + _vm.PropertyChanged += (_, e) => + { + if (e.PropertyName == indexProperty) + { + Update(); + } + }; + Update(); + + return new Border + { + Child = panel, + Background = EditorChrome.FieldBrush, + BorderBrush = EditorChrome.FieldBorderBrush, + BorderThickness = new Thickness(1), + CornerRadius = new CornerRadius(8), + Padding = new Thickness(2), + HorizontalAlignment = HorizontalAlignment.Left, }; } - private static TextBlock MakeTabHeader(string text) => new() + /// + /// A cue preview in a rounded frame. + /// + private static Border FramePreview(Control preview) { - Text = text, - FontSize = UiUtil.ScaledFontSize(12), - }; + return new Border + { + Child = preview, + BorderBrush = EditorChrome.FieldBorderBrush, + BorderThickness = new Thickness(1), + CornerRadius = new CornerRadius(6), + ClipToBounds = true, + }; + } - private NumericUpDown MakeFrameNud(string bindingPath) + /// + /// A dim label of fixed width and the controls after it on one line. + /// + private static Grid Row(string label, params Control[] controls) { - return new NumericUpDown + var panel = new StackPanel { Orientation = Orientation.Horizontal, Spacing = 6, VerticalAlignment = VerticalAlignment.Center }; + foreach (var control in controls) { - Width = 115, + panel.Children.Add(control); + } + + return SyncTimingChrome.Row(label, panel, LabelWidth); + } + + private static TextBlock MakeUnit(string text) + { + var unit = EditorChrome.MakeDimLabel(text, new Thickness(2, 0, 0, 0)); + unit.Foreground = EditorChrome.FaintTextBrush; + return unit; + } + + private Control MakeRowLabelNudSuffix(string label, string bindingPath, string suffix) + { + return Row(label, MakeFrameNud(bindingPath, label), MakeUnit(suffix)); + } + + private Control MakeZonesRow(string leftGreenPath, string leftRedPath, string rightRedPath, string rightGreenPath) + { + var first = MakeZoneNud(leftGreenPath, isGreen: true); + return Row(_l.Zones, + first, + MakeZoneNud(leftRedPath, isGreen: false), + MakeZoneNud(rightRedPath, isGreen: false), + MakeZoneNud(rightGreenPath, isGreen: true), + UiUtil.MakeHintIcon(_l.HintZones, first)); + } + + private NumericUpDown MakeFrameNud(string bindingPath, string accessibleName) + { + var nud = new NumericUpDown + { + Width = 64, Minimum = 0, Maximum = 100, Increment = 1, @@ -557,13 +626,16 @@ private NumericUpDown MakeFrameNud(string bindingPath) VerticalAlignment = VerticalAlignment.Center, [!NumericUpDown.ValueProperty] = new Binding(bindingPath) { Source = _vm, Mode = BindingMode.TwoWay, Converter = new IntDecimalConverter() }, }; + nud.Classes.Add("field"); + AutomationProperties.SetName(nud, accessibleName); + return nud; } - private NumericUpDown MakeMsNud(string bindingPath) + private NumericUpDown MakeMsNud(string bindingPath, string accessibleName) { - return new NumericUpDown + var nud = new NumericUpDown { - Width = 130, + Width = 80, Minimum = 0, Maximum = 5000, Increment = 10, @@ -571,30 +643,27 @@ private NumericUpDown MakeMsNud(string bindingPath) VerticalAlignment = VerticalAlignment.Center, [!NumericUpDown.ValueProperty] = new Binding(bindingPath) { Source = _vm, Mode = BindingMode.TwoWay, Converter = new IntDecimalConverter() }, }; - } - - private NumericUpDown MakeZoneNud(string bindingPath, bool isGreen) - { - var nud = MakeFrameNud(bindingPath); - nud.Background = new SolidColorBrush(isGreen ? Color.FromArgb(80, 0, 160, 0) : Color.FromArgb(80, 200, 30, 30)); - nud.Width = 110; + nud.Classes.Add("field"); + AutomationProperties.SetName(nud, accessibleName); return nud; } - private static Control MakeGroupBox(string title, Control content) + /// + /// A zone field: a green (warn) or red (snap) dot inside it, the colors of the cue preview. + /// + private NumericUpDown MakeZoneNud(string bindingPath, bool isGreen) { - var header = new TextBlock - { - Text = title, - FontWeight = FontWeight.Bold, - Margin = new Thickness(0, 0, 0, 6), - }; - var inner = new StackPanel + var nud = MakeFrameNud(bindingPath, _l.Zones); + nud.Width = 72; + nud.InnerLeftContent = new Ellipse { - Spacing = 4, - Children = { header, content }, + Width = 8, + Height = 8, + Fill = isGreen ? GreenZoneBrush : RedZoneBrush, + VerticalAlignment = VerticalAlignment.Center, + Margin = new Thickness(8, 0, 0, 0), }; - return UiUtil.MakeBorderForControl(inner); + return nud; } private sealed class IndexEqualsConverter : IValueConverter diff --git a/src/ui/Features/Tools/BridgeGaps/BridgeGapsWindow.cs b/src/ui/Features/Tools/BridgeGaps/BridgeGapsWindow.cs index eb1ed34946d..a0175e8be14 100644 --- a/src/ui/Features/Tools/BridgeGaps/BridgeGapsWindow.cs +++ b/src/ui/Features/Tools/BridgeGaps/BridgeGapsWindow.cs @@ -1,8 +1,10 @@ +using Avalonia; using Avalonia.Controls; using Avalonia.Data; using Avalonia.Input; using Avalonia.Interactivity; using Avalonia.Layout; +using Nikse.SubtitleEdit.Features.Sync; using Nikse.SubtitleEdit.Logic; using Nikse.SubtitleEdit.Logic.Config; using Nikse.SubtitleEdit.Logic.ValueConverters; @@ -10,6 +12,10 @@ namespace Nikse.SubtitleEdit.Features.Tools.BridgeGaps; +/// +/// Bridge gaps, in the editor look (see ): the limits on top, the +/// preview of the bridged gaps below them, the count and Cancel/OK in the button bar. +/// public class BridgeGapsWindow : Window { public BridgeGapsWindow(BridgeGapsViewModel vm) @@ -21,70 +27,57 @@ public BridgeGapsWindow(BridgeGapsViewModel vm) Height = 800; MinWidth = 900; MinHeight = 500; + SyncTimingChrome.Apply(this); vm.Window = this; DataContext = vm; - var labelBridgeGapSmallerThan = UiUtil.MakeLabel(vm.BridgeGapsSmallerThanLabel); - var numericUpDownBridgeGapSmallerThan = UiUtil.MakeNumericUpDownInt(1, 10000, vm.BridgeGapsSmallerThanMsOrFrames, 130, vm, nameof(vm.BridgeGapsSmallerThanMsOrFrames)); + var numericUpDownBridgeGapSmallerThan = TimingToolChrome.Field( + UiUtil.MakeNumericUpDownInt(1, 10000, vm.BridgeGapsSmallerThanMsOrFrames, 90, vm, nameof(vm.BridgeGapsSmallerThanMsOrFrames)), + vm.BridgeGapsSmallerThanLabel); numericUpDownBridgeGapSmallerThan.ValueChanged += vm.ValueChanged; - var buttonCalculateBridgeGapSmallerThan = UiUtil.MakeButtonBrowse(vm.CalculateBridgeGapsSmallerThanMsCommand, - accessibleName: Se.Language.Tools.BridgeGaps.CalculateBridgeGapsSmallerThanDotDotDot) + var buttonCalculateBridgeGapSmallerThan = EditorChrome.CreateToolButton("fa-solid fa-calculator", + Se.Language.Tools.BridgeGaps.CalculateBridgeGapsSmallerThanDotDotDot, vm.CalculateBridgeGapsSmallerThanMsCommand, 28) .WithBindIsVisible(vm, nameof(vm.IsMsMode)); - var labelMinGap = UiUtil.MakeLabel(vm.MinGapLabel); - var numericUpDownMinGap = UiUtil.MakeNumericUpDownInt(0, 1000, vm.MinGapMsOrFrames, 130, vm, nameof(vm.MinGapMsOrFrames)); + var numericUpDownMinGap = TimingToolChrome.Field( + UiUtil.MakeNumericUpDownInt(0, 1000, vm.MinGapMsOrFrames, 90, vm, nameof(vm.MinGapMsOrFrames)), + vm.MinGapLabel); numericUpDownMinGap.ValueChanged += vm.ValueChanged; - var buttonCalculateMinGap = UiUtil.MakeButtonBrowse(vm.CalculateMinGapMsCommand, - accessibleName: Se.Language.Options.Settings.MinGapCalculateDotDotDot) + var buttonCalculateMinGap = EditorChrome.CreateToolButton("fa-solid fa-calculator", + Se.Language.Options.Settings.MinGapCalculateDotDotDot, vm.CalculateMinGapMsCommand, 28) .WithBindIsVisible(vm, nameof(vm.IsMsMode)); - var labelPercentForLeft = UiUtil.MakeLabel(Se.Language.Tools.BridgeGaps.PercentFoPrevious); - var numericUpDownPercentForLeft = UiUtil.MakeNumericUpDownInt(0, 100, Se.Settings.Tools.BridgeGaps.PercentForLeft, 130, vm, nameof(vm.PercentForLeft)); + var numericUpDownPercentForLeft = TimingToolChrome.Field( + UiUtil.MakeNumericUpDownInt(0, 100, Se.Settings.Tools.BridgeGaps.PercentForLeft, 90, vm, nameof(vm.PercentForLeft)), + Se.Language.Tools.BridgeGaps.PercentFoPrevious); numericUpDownPercentForLeft.ValueChanged += vm.ValueChanged; - var panelControls = UiUtil.MakeHorizontalPanel( - labelBridgeGapSmallerThan, - numericUpDownBridgeGapSmallerThan, - buttonCalculateBridgeGapSmallerThan, - labelMinGap, - numericUpDownMinGap, - buttonCalculateMinGap, - labelPercentForLeft, - numericUpDownPercentForLeft); + var panelControls = new WrapPanel { Orientation = Orientation.Horizontal, ItemSpacing = 28, LineSpacing = 8 }; + panelControls.Children.Add(TimingToolChrome.LabeledField(vm.BridgeGapsSmallerThanLabel, numericUpDownBridgeGapSmallerThan, buttonCalculateBridgeGapSmallerThan)); + panelControls.Children.Add(TimingToolChrome.LabeledField(vm.MinGapLabel, numericUpDownMinGap, buttonCalculateMinGap)); + panelControls.Children.Add(TimingToolChrome.LabeledField(Se.Language.Tools.BridgeGaps.PercentFoPrevious, numericUpDownPercentForLeft)); + var optionsSection = EditorChrome.MakeEditorSection(Se.Language.General.Options, panelControls, first: true); - var subtitleView = MakeSubtitleView(vm); + var listSection = TimingToolChrome.MakeListSection(EditorChrome.MakeSectionHeader(Se.Language.General.Preview), MakeSubtitleView(vm)); - var labelStatus = UiUtil.MakeLabel(string.Empty).WithBindText(vm, nameof(vm.StatusText)).WithAlignmentTop(); - - var buttonOk = UiUtil.MakeButtonOk(vm.OkCommand); - var buttonCancel = UiUtil.MakeButtonCancel(vm.CancelCommand); - var panelButtons = UiUtil.MakeButtonBar(buttonOk, buttonCancel); - - var grid = new Grid + var sections = new Grid { RowDefinitions = { - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, // Bridge gap smaller than + new RowDefinition { Height = GridLength.Auto }, // Bridge gap smaller than new RowDefinition { Height = new GridLength(1, GridUnitType.Star) }, // Subtitle view - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, // Buttons }, - ColumnDefinitions = - { - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) }, - }, - Margin = UiUtil.MakeWindowMargin(), - ColumnSpacing = 10, - RowSpacing = 10, - Width = double.NaN, - HorizontalAlignment = HorizontalAlignment.Stretch, }; + sections.Add(optionsSection, 0); + sections.Add(listSection, 1); - grid.Add(panelControls, 0); - grid.Add(subtitleView, 1); - grid.Add(labelStatus, 2); - grid.Add(panelButtons, 2); + var buttonOk = UiUtil.MakeButtonOk(vm.OkCommand); + buttonOk.Classes.Add("primary"); + var buttonCancel = UiUtil.MakeButtonCancel(vm.CancelCommand); - Content = grid; + Content = SyncTimingChrome.MakeRoot( + sections, + SyncTimingChrome.MakeButtonBar(TimingToolChrome.MakeStatus(nameof(vm.StatusText)), buttonCancel, buttonOk)); UiUtil.FocusOnFirstActivation(this, numericUpDownBridgeGapSmallerThan); // initial focus on an input, not an action button - a focused button clicks on bare Space KeyDown += (_, e) => vm.OnKeyDown(e); @@ -93,7 +86,7 @@ public BridgeGapsWindow(BridgeGapsViewModel vm) Loaded += delegate { UiUtil.RestoreWindowPosition(this); }; } - private Border MakeSubtitleView(BridgeGapsViewModel vm) + private static Control MakeSubtitleView(BridgeGapsViewModel vm) { var fullTimeConverter = new TimeSpanToDisplayFullConverter(); var shortTimeConverter = new TimeSpanToDisplayShortConverter(); @@ -164,6 +157,6 @@ private Border MakeSubtitleView(BridgeGapsViewModel vm) } }, RoutingStrategies.Tunnel); - return UiUtil.MakeBorderForControlNoPadding(dataGridSubtitle); + return dataGridSubtitle; } } diff --git a/src/ui/Features/Tools/TimingToolChrome.cs b/src/ui/Features/Tools/TimingToolChrome.cs new file mode 100644 index 00000000000..2e5830a71e7 --- /dev/null +++ b/src/ui/Features/Tools/TimingToolChrome.cs @@ -0,0 +1,135 @@ +using Avalonia; +using Avalonia.Automation; +using Avalonia.Controls; +using Avalonia.Data; +using Avalonia.Layout; +using Avalonia.Media; +using Nikse.SubtitleEdit.Logic; + +namespace Nikse.SubtitleEdit.Features.Tools; + +/// +/// Small building blocks shared by the timing tools of the Tools menu (adjust durations, apply +/// duration limits, apply min. gap, bridge gaps, beautify time codes) in the editor look (see +/// ). The window chrome itself comes from Sync's SyncTimingChrome. +/// +internal static class TimingToolChrome +{ + /// + /// A compact number field (no spinner) with a fixed width and a screen reader name. + /// + internal static NumericUpDown Field(NumericUpDown field, string accessibleName, double width = 90) + { + field.Classes.Add("field"); + field.Width = width; + field.VerticalAlignment = VerticalAlignment.Center; + AutomationProperties.SetName(field, accessibleName); + return field; + } + + /// + /// A dim label, a field and the controls after it on one line. + /// + internal static StackPanel LabeledField(string label, Control field, params Control[] after) + { + var panel = new StackPanel { Orientation = Orientation.Horizontal, Spacing = 8, VerticalAlignment = VerticalAlignment.Center }; + panel.Children.Add(EditorChrome.MakeDimLabel(label, new Thickness(0))); + panel.Children.Add(field); + foreach (var control in after) + { + panel.Children.Add(control); + } + + return panel; + } + + /// + /// A compact on/off switch with its title beside it. + /// + internal static StackPanel MakeSwitch(string title, string binding, out ToggleSwitch toggle) + { + toggle = new ToggleSwitch + { + OnContent = null, + OffContent = null, + MinWidth = 0, + VerticalAlignment = VerticalAlignment.Center, + [!ToggleSwitch.IsCheckedProperty] = new Binding(binding) { Mode = BindingMode.TwoWay }, + }; + AutomationProperties.SetName(toggle, title); + + var row = new StackPanel { Orientation = Orientation.Horizontal, Spacing = 8, VerticalAlignment = VerticalAlignment.Center }; + row.Children.Add(toggle); + row.Children.Add(new TextBlock { Text = title, FontSize = 12.5, VerticalAlignment = VerticalAlignment.Center }); + return row; + } + + /// + /// A list header whose text comes from the view model (counts, "No changes needed", ...). + /// + internal static TextBlock MakeBoundHeader(string binding) + { + return new TextBlock + { + FontSize = 12, + FontWeight = FontWeight.SemiBold, + Foreground = EditorChrome.DimTextBrush, + VerticalAlignment = VerticalAlignment.Center, + TextTrimming = TextTrimming.CharacterEllipsis, + [!TextBlock.TextProperty] = new Binding(binding), + }; + } + + /// + /// Dim status text for the left side of the button bar. + /// + internal static TextBlock MakeStatus(string binding) + { + var label = EditorChrome.MakeDimLabel(string.Empty, new Thickness(0)); + label.TextTrimming = TextTrimming.CharacterEllipsis; + label[!TextBlock.TextProperty] = new Binding(binding); + return label; + } + + /// + /// A rounded field-colored frame around a list. + /// + internal static Border MakeListFrame(Control list) + { + return new Border + { + Child = list, + Background = EditorChrome.FieldBrush, + BorderBrush = EditorChrome.FieldBorderBrush, + BorderThickness = new Thickness(1), + CornerRadius = new CornerRadius(8), + ClipToBounds = true, + }; + } + + /// + /// A list section: header (title or bound text, optional actions on the right) above a framed + /// list that takes the remaining height. + /// + internal static Border MakeListSection(Control header, Control list, bool first = false) + { + var grid = new Grid + { + RowSpacing = 8, + RowDefinitions = + { + new RowDefinition(GridLength.Auto), + new RowDefinition(new GridLength(1, GridUnitType.Star)), + }, + }; + grid.Add(header, 0); + grid.Add(MakeListFrame(list), 1); + return new Border + { + Child = grid, + BorderBrush = EditorChrome.LineBrush, + BorderThickness = new Thickness(0, first ? 0 : 1, 0, 0), + Padding = new Thickness(14, 12, 14, 14), + }; + } +} From 23371ab8edfb4cd4e9b8e65c35ea8170ee023fad Mon Sep 17 00:00:00 2001 From: niksedk Date: Thu, 15 Oct 2026 19:48:59 +0200 Subject: [PATCH 212/306] Batch convert windows in the editor look Co-Authored-By: Claude Opus 5.5 --- .../BatchConvert/BatchConvertAssaWindow.cs | 121 +++-- .../Tools/BatchConvert/BatchConvertChrome.cs | 64 +++ ...tchConvertFixCommonErrorsSettingsWindow.cs | 52 +- .../BatchConvertSettingsWindow.cs | 293 +++++------ .../BatchConvertTsSettingsWindow.cs | 204 +++++--- .../Tools/BatchConvert/BatchConvertWindow.cs | 488 ++++++++++-------- .../BatchErrorList/BatchErrorListWindow.cs | 42 +- 7 files changed, 725 insertions(+), 539 deletions(-) create mode 100644 src/ui/Features/Tools/BatchConvert/BatchConvertChrome.cs diff --git a/src/ui/Features/Tools/BatchConvert/BatchConvertAssaWindow.cs b/src/ui/Features/Tools/BatchConvert/BatchConvertAssaWindow.cs index 7846a5261ba..2e9f4059745 100644 --- a/src/ui/Features/Tools/BatchConvert/BatchConvertAssaWindow.cs +++ b/src/ui/Features/Tools/BatchConvert/BatchConvertAssaWindow.cs @@ -1,12 +1,17 @@ +using Avalonia; using Avalonia.Controls; using Avalonia.Data; using Avalonia.Layout; -using Avalonia.Media; +using Nikse.SubtitleEdit.Features.Video; using Nikse.SubtitleEdit.Logic; using Nikse.SubtitleEdit.Logic.Config; namespace Nikse.SubtitleEdit.Features.Tools.BatchConvert; +/// +/// Batch convert's ASSA target settings, in the editor look (see ): the +/// source style options on top, the ASSA header with its edit actions under them, OK in the button bar. +/// public class BatchConvertAssaWindow : Window { public BatchConvertAssaWindow(BatchConvertAssaViewModel vm) @@ -18,34 +23,27 @@ public BatchConvertAssaWindow(BatchConvertAssaViewModel vm) Height = 700; MinWidth = 600; MinHeight = 400; + VideoSubDialogChrome.Apply(this); vm.Window = this; DataContext = vm; - var checkBoxOverwrite = new CheckBox - { - Content = Se.Language.Tools.BatchConvert.UseSourceStylesIfPossible, - VerticalAlignment = VerticalAlignment.Center, - [!CheckBox.IsCheckedProperty] = new Binding(nameof(vm.UseSourceStylesIfPossible)), - }; - - var checkBoxKeepSourceFonts = new CheckBox - { - Content = Se.Language.Tools.BatchConvert.KeepSourceEmbeddedFonts, - VerticalAlignment = VerticalAlignment.Center, - [!CheckBox.IsCheckedProperty] = new Binding(nameof(vm.KeepSourceEmbeddedFonts)), - [!CheckBox.IsEnabledProperty] = new Binding(nameof(vm.KeepSourceEmbeddedFontsIsEnabled)), - }; + var keepSourceFonts = BatchConvertChrome.MakeSwitchRow(nameof(vm.KeepSourceEmbeddedFonts), Se.Language.Tools.BatchConvert.KeepSourceEmbeddedFonts); + keepSourceFonts.Bind(IsEnabledProperty, new Binding(nameof(vm.KeepSourceEmbeddedFontsIsEnabled))); - var panelCheckBoxes = new StackPanel + var panelOptions = new StackPanel { Orientation = Orientation.Vertical, - Spacing = 5, - Children = { checkBoxOverwrite, checkBoxKeepSourceFonts }, + Spacing = 8, + Children = + { + BatchConvertChrome.MakeSwitchRow(nameof(vm.UseSourceStylesIfPossible), Se.Language.Tools.BatchConvert.UseSourceStylesIfPossible), + keepSourceFonts, + }, }; var buttonOk = UiUtil.MakeButtonOk(vm.OkCommand); + buttonOk.Classes.Add("primary"); var buttonCancel = UiUtil.MakeButtonCancel(vm.CancelCommand); - var panelButtons = UiUtil.MakeButtonBar(buttonOk, buttonCancel); var grid = new Grid { @@ -55,20 +53,17 @@ public BatchConvertAssaWindow(BatchConvertAssaViewModel vm) new RowDefinition { Height = new GridLength(1, GridUnitType.Star) }, new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, }, - ColumnDefinitions = - { - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) }, - }, - Margin = UiUtil.MakeWindowMargin(), - ColumnSpacing = 10, - RowSpacing = 10, - Width = double.NaN, - HorizontalAlignment = HorizontalAlignment.Stretch, }; - grid.Add(panelCheckBoxes, 0); - grid.Add(MakeEditView(vm), 1); - grid.Add(panelButtons, 2); + grid.Add(new Border + { + Child = EditorChrome.MakeEditorSection(Se.Language.General.Options, panelOptions, first: true), + Background = EditorChrome.PanelBrush, + BorderBrush = EditorChrome.LineBrush, + BorderThickness = new Thickness(0, 0, 0, 1), + }, 0, 0); + grid.Add(MakeEditView(vm), 1, 0); + grid.Add(VideoSubDialogChrome.MakeButtonBar(null, buttonCancel, buttonOk), 2, 0); Content = grid; @@ -82,27 +77,6 @@ public BatchConvertAssaWindow(BatchConvertAssaViewModel vm) private static Border MakeEditView(BatchConvertAssaViewModel vm) { - var grid = new Grid - { - RowDefinitions = - { - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Star) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - }, - ColumnDefinitions = - { - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) }, - }, - Width = double.NaN, - Height = double.NaN, - HorizontalAlignment = HorizontalAlignment.Stretch, - VerticalAlignment = VerticalAlignment.Stretch, - ColumnSpacing = 5, - RowSpacing = 5, - }; - - var labelAssaSource = UiUtil.MakeLabel(Se.Language.Tools.BatchConvert.AssaSource).WithBold(); var contentBorder = new Border { VerticalAlignment = VerticalAlignment.Stretch, @@ -111,16 +85,41 @@ private static Border MakeEditView(BatchConvertAssaViewModel vm) Height = double.NaN, }; vm.TextBoxContainer = contentBorder; - - var buttonEditStyle = UiUtil.MakeButton(Se.Language.Tools.BatchConvert.EditStyles, vm.EditStylesCommand); - var buttonEditProperties = UiUtil.MakeButton(Se.Language.Tools.BatchConvert.EditProperties, vm.EditPropertiesCommand); - var buttonEditAttachment = UiUtil.MakeButton(Se.Language.Tools.BatchConvert.EditAttachments, vm.EditAttachmentCommand); - var panelButtons = UiUtil.MakeButtonBar(buttonEditStyle, buttonEditProperties, buttonEditAttachment); - grid.Add(labelAssaSource, 0); - grid.Add(contentBorder, 1); - grid.Add(panelButtons, 2); + var actions = new StackPanel + { + Orientation = Orientation.Horizontal, + Spacing = 2, + Children = + { + VideoSubDialogChrome.MakeAction("fa-solid fa-palette", Se.Language.Tools.BatchConvert.EditStyles, vm.EditStylesCommand), + VideoSubDialogChrome.MakeAction("fa-solid fa-sliders", Se.Language.Tools.BatchConvert.EditProperties, vm.EditPropertiesCommand), + VideoSubDialogChrome.MakeAction("fa-solid fa-paperclip", Se.Language.Tools.BatchConvert.EditAttachments, vm.EditAttachmentCommand), + }, + }; + + var header = new DockPanel(); + DockPanel.SetDock(actions, Dock.Right); + header.Children.Add(actions); + header.Children.Add(EditorChrome.MakeSectionHeader(Se.Language.Tools.BatchConvert.AssaSource)); + + var grid = new Grid + { + RowDefinitions = + { + new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, + new RowDefinition { Height = new GridLength(1, GridUnitType.Star) }, + }, + RowSpacing = 8, + }; + + grid.Add(header, 0, 0); + grid.Add(VideoSubDialogChrome.MakeListFrame(contentBorder), 1, 0); - return UiUtil.MakeBorderForControl(grid).WithMarginBottom(5); + return new Border + { + Child = grid, + Padding = new Thickness(14, 10, 14, 14), + }; } } diff --git a/src/ui/Features/Tools/BatchConvert/BatchConvertChrome.cs b/src/ui/Features/Tools/BatchConvert/BatchConvertChrome.cs new file mode 100644 index 00000000000..017b322b95f --- /dev/null +++ b/src/ui/Features/Tools/BatchConvert/BatchConvertChrome.cs @@ -0,0 +1,64 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Data; +using Avalonia.Layout; +using Avalonia.Media; +using Nikse.SubtitleEdit.Features.Video; +using Nikse.SubtitleEdit.Logic; + +namespace Nikse.SubtitleEdit.Features.Tools.BatchConvert; + +/// +/// Small pieces the batch convert dialogs share on top of . +/// +internal static class BatchConvertChrome +{ + /// + /// A label of fixed width and the control after it, so the rows line up. The label wraps + /// (translated labels like "Language post-fix (mkv/mp4/auto-translate)" are long), and the row + /// can follow . + /// + internal static Grid MakeRow(string label, Control content, double labelWidth, string? visibleProperty = null) + { + var text = EditorChrome.MakeDimLabel(label, new Thickness(0)); + text.TextWrapping = TextWrapping.Wrap; + var grid = new Grid + { + ColumnSpacing = 8, + ColumnDefinitions = { new ColumnDefinition(new GridLength(labelWidth)), new ColumnDefinition(new GridLength(1, GridUnitType.Star)) }, + }; + grid.Add(text, 0, 0); + grid.Add(content, 0, 1); + if (visibleProperty != null) + { + grid.Bind(Visual.IsVisibleProperty, new Binding(visibleProperty)); + } + + return grid; + } + + /// + /// An on/off switch bound to with its label beside it. + /// + internal static Grid MakeSwitchRow(string property, string text, out ToggleSwitch toggle) + { + toggle = VideoSubDialogChrome.MakeSwitch(property, text); + return VideoSubDialogChrome.MakeSwitchRow(toggle, text); + } + + internal static Grid MakeSwitchRow(string property, string text) => MakeSwitchRow(property, text, out _); + + /// + /// The sections column of a dialog on the panel color. + /// + internal static StackPanel MakeSections(params Control[] sections) + { + var panel = new StackPanel(); + foreach (var section in sections) + { + panel.Children.Add(section); + } + + return panel; + } +} diff --git a/src/ui/Features/Tools/BatchConvert/BatchConvertFixCommonErrorsSettingsWindow.cs b/src/ui/Features/Tools/BatchConvert/BatchConvertFixCommonErrorsSettingsWindow.cs index d501c7accc8..ebbefa95cc4 100644 --- a/src/ui/Features/Tools/BatchConvert/BatchConvertFixCommonErrorsSettingsWindow.cs +++ b/src/ui/Features/Tools/BatchConvert/BatchConvertFixCommonErrorsSettingsWindow.cs @@ -3,17 +3,19 @@ using Avalonia.Controls.Primitives; using Avalonia.Controls.Templates; using Avalonia.Data; -using Avalonia.Input; -using Avalonia.Interactivity; using Avalonia.Layout; -using Avalonia.Threading; using Avalonia.Media; using Nikse.SubtitleEdit.Features.Tools.FixCommonErrors; +using Nikse.SubtitleEdit.Features.Video; using Nikse.SubtitleEdit.Logic; using Nikse.SubtitleEdit.Logic.Config; namespace Nikse.SubtitleEdit.Features.Tools.BatchConvert; +/// +/// The fix common errors rules for batch convert, in the editor look (see ): +/// the rule checklist, the profile and OK in the button bar. +/// public class BatchConvertFixCommonErrorsSettingsWindow : Window { public BatchConvertFixCommonErrorsSettingsWindow(BatchConvertFixCommonErrorsSettingsViewModel vm) @@ -25,16 +27,22 @@ public BatchConvertFixCommonErrorsSettingsWindow(BatchConvertFixCommonErrorsSett Height = 800; MinWidth = 600; MinHeight = 400; + VideoSubDialogChrome.Apply(this); vm.Window = this; DataContext = vm; - var rulesView = MakeRulesView(vm); - var buttonOk = UiUtil.MakeButtonOk(vm.OkCommand); + buttonOk.Classes.Add("primary"); var buttonCancel = UiUtil.MakeButtonCancel(vm.CancelCommand); var comboProfile = UiUtil.MakeComboBox(vm.Profiles, vm, nameof(vm.SelectedProfile)) .WithAccessibleName(Se.Language.General.Profile); - var panelButtons = UiUtil.MakeButtonBar(buttonOk, buttonCancel); + comboProfile.MinWidth = 200; + var profile = new StackPanel + { + Orientation = Orientation.Horizontal, + Spacing = 8, + Children = { EditorChrome.MakeDimLabel(Se.Language.General.Profile, new Thickness(0)), comboProfile }, + }; var grid = new Grid { @@ -43,20 +51,10 @@ public BatchConvertFixCommonErrorsSettingsWindow(BatchConvertFixCommonErrorsSett new RowDefinition { Height = new GridLength(1, GridUnitType.Star) }, new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, }, - ColumnDefinitions = - { - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) }, - }, - Margin = UiUtil.MakeWindowMargin(), - ColumnSpacing = 10, - RowSpacing = 10, - Width = double.NaN, - HorizontalAlignment = HorizontalAlignment.Stretch, }; - grid.Add(rulesView, 0, 0); - grid.Add(comboProfile, 1, 0); - grid.Add(panelButtons, 1, 0); + grid.Add(MakeRulesView(vm), 0, 0); + grid.Add(VideoSubDialogChrome.MakeButtonBar(profile, buttonCancel, buttonOk), 1, 0); Content = grid; UiUtil.FocusOnFirstActivation(this, comboProfile); // initial focus on an input, not an action button - a focused button clicks on bare Space @@ -113,7 +111,21 @@ private static Border MakeRulesView(BatchConvertFixCommonErrorsSettingsViewModel }); TableViewExtras.AddSpaceToggle(rulesGrid, item => item.IsSelected, (item, v) => item.IsSelected = v); + rulesGrid.WithAccessibleName(Se.Language.General.Rules); - return UiUtil.MakeBorderForControl(rulesGrid); + var header = EditorChrome.MakeSectionHeader(Se.Language.General.Rules); + var grid = new Grid + { + RowSpacing = 8, + RowDefinitions = { new RowDefinition(GridLength.Auto), new RowDefinition(new GridLength(1, GridUnitType.Star)) }, + }; + grid.Add(header, 0, 0); + grid.Add(VideoSubDialogChrome.MakeListFrame(rulesGrid), 1, 0); + + return new Border + { + Child = grid, + Padding = new Thickness(14, 12, 14, 14), + }; } -} \ No newline at end of file +} diff --git a/src/ui/Features/Tools/BatchConvert/BatchConvertSettingsWindow.cs b/src/ui/Features/Tools/BatchConvert/BatchConvertSettingsWindow.cs index 706701540cb..d4fe18b8c40 100644 --- a/src/ui/Features/Tools/BatchConvert/BatchConvertSettingsWindow.cs +++ b/src/ui/Features/Tools/BatchConvert/BatchConvertSettingsWindow.cs @@ -1,247 +1,210 @@ -using Avalonia.Controls; +using Avalonia; +using Avalonia.Automation; +using Avalonia.Controls; using Avalonia.Controls.Templates; using Avalonia.Data; using Avalonia.Layout; using Nikse.SubtitleEdit.Features.Ocr.Engines; using Nikse.SubtitleEdit.Features.Translate; +using Nikse.SubtitleEdit.Features.Video; using Nikse.SubtitleEdit.Logic; using Nikse.SubtitleEdit.Logic.Config; using Nikse.SubtitleEdit.UiLogic.Ocr; namespace Nikse.SubtitleEdit.Features.Tools.BatchConvert; +/// +/// Batch convert settings, in the editor look (see ): output on the left, +/// adding files and OCR on the right, OK in the button bar. +/// public class BatchConvertSettingsWindow : Window { + private const double LabelWidth = 150; + private const double ColumnWidth = 440; + public BatchConvertSettingsWindow(BatchConvertSettingsViewModel vm) { UiUtil.InitializeWindow(this, GetType().Name); Title = Se.Language.Tools.BatchConvert.BatchConvertSettings; SizeToContent = SizeToContent.WidthAndHeight; CanResize = false; + VideoSubDialogChrome.Apply(this); vm.Window = this; DataContext = vm; - var labelTargetEncoding = UiUtil.MakeLabel(Se.Language.General.TargetEncoding); - var comboBoxTargetEncoding = UiUtil.MakeComboBox(vm.TargetEncodings, vm, nameof(vm.SelectedTargetEncoding)); - var panelTargetEncoding = new StackPanel - { - Orientation = Orientation.Horizontal, - Spacing = 5, - Children = { labelTargetEncoding, comboBoxTargetEncoding } - }; - - var checkBoxOverwrite = new CheckBox - { - Content = Se.Language.General.OverwriteExistingFiles, - IsChecked = vm.Overwrite, - VerticalAlignment = VerticalAlignment.Center, - [!CheckBox.IsCheckedProperty] = new Binding(nameof(vm.Overwrite)) { Mode = BindingMode.TwoWay, UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged }, - }; - - var checkBoxKeepSourceTimestamp = new CheckBox - { - Content = Se.Language.Tools.BatchConvert.KeepSourceFileTimestamp, - IsChecked = vm.KeepSourceTimestamp, - VerticalAlignment = VerticalAlignment.Center, - [!CheckBox.IsCheckedProperty] = new Binding(nameof(vm.KeepSourceTimestamp)) { Mode = BindingMode.TwoWay, UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged }, - }; - - var checkBoxPreventSleep = new CheckBox - { - Content = Se.Language.Tools.BatchConvert.PreventSleepWhileConverting, - IsChecked = vm.PreventSleep, - VerticalAlignment = VerticalAlignment.Center, - [!CheckBox.IsCheckedProperty] = new Binding(nameof(vm.PreventSleep)) { Mode = BindingMode.TwoWay, UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged }, - }; + var comboBoxTargetEncoding = Combo(vm.TargetEncodings, vm, nameof(vm.SelectedTargetEncoding), Se.Language.General.TargetEncoding); - var checkBoxScanFolderRecursive = new CheckBox - { - Content = Se.Language.Tools.BatchConvert.IncludeSubfolders, - IsChecked = vm.ScanFolderRecursive, - VerticalAlignment = VerticalAlignment.Center, - [!CheckBox.IsCheckedProperty] = new Binding(nameof(vm.ScanFolderRecursive)) { Mode = BindingMode.TwoWay, UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged }, - }; - - var labelScanFolderVideoFiles = UiUtil.MakeLabel(Se.Language.Tools.BatchConvert.VideoFilesWhenAddingFolder); - var comboBoxScanFolderVideoFiles = UiUtil.MakeComboBox(vm.ScanFolderVideoFilesOptions, vm, nameof(vm.SelectedScanFolderVideoFilesOption)); - var panelScanFolderVideoFiles = new StackPanel - { - Orientation = Orientation.Horizontal, - Spacing = 5, - Children = { labelScanFolderVideoFiles, comboBoxScanFolderVideoFiles } - }; - - var checkBoxUseSourceFolder = new RadioButton + var radioUseSourceFolder = new RadioButton { Content = Se.Language.General.UseSourceFolder, IsChecked = vm.UseSourceFolder, + FontSize = 12.5, VerticalAlignment = VerticalAlignment.Center, [!RadioButton.IsCheckedProperty] = new Binding(nameof(vm.UseSourceFolder)) { Mode = BindingMode.TwoWay, UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged }, }; - var checkBoxUseOutputFolder = new RadioButton + var radioUseOutputFolder = new RadioButton { Content = Se.Language.General.UseOutputFolder, IsChecked = vm.UseOutputFolder, + FontSize = 12.5, VerticalAlignment = VerticalAlignment.Center, [!RadioButton.IsCheckedProperty] = new Binding(nameof(vm.UseOutputFolder)) { Mode = BindingMode.TwoWay, UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged }, }; - var textBoxOutputFolder = new TextBox + var textBoxOutputFolder = VideoSubDialogChrome.MakeTextBox(nameof(vm.OutputFolder), Se.Language.General.OutputFolder); + textBoxOutputFolder.Text = vm.OutputFolder; + textBoxOutputFolder.Bind(IsEnabledProperty, new Binding(nameof(vm.UseOutputFolder)) { Mode = BindingMode.OneWay }); + + var buttonBrowse = EditorChrome.CreateToolButton("fa-solid fa-folder-open", Se.Language.General.UseOutputFolder, vm.BrowseOutputFolderCommand, 28); + + var panelOutputFolder = new Grid { - Text = vm.OutputFolder, - VerticalAlignment = VerticalAlignment.Center, - [!TextBox.TextProperty] = new Binding(nameof(vm.OutputFolder)) { Mode = BindingMode.TwoWay, UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged }, - [!Control.IsEnabledProperty] = new Binding(nameof(vm.UseOutputFolder)) { Mode = BindingMode.OneWay }, - Width = 400, + Margin = new Thickness(28, 0, 0, 0), // under the "Use output folder" radio button + ColumnSpacing = 4, + ColumnDefinitions = { new ColumnDefinition(new GridLength(1, GridUnitType.Star)), new ColumnDefinition(GridLength.Auto) }, }; + panelOutputFolder.Add(textBoxOutputFolder, 0, 0); + panelOutputFolder.Add(buttonBrowse, 0, 1); + + var folder = new StackPanel { Spacing = 4, Children = { radioUseSourceFolder, radioUseOutputFolder, panelOutputFolder } }; - var buttonBrowse = UiUtil.MakeButtonBrowse(vm.BrowseOutputFolderCommand, accessibleName: Se.Language.General.UseOutputFolder); + var comboBoxLanguagePostFix = Combo(vm.LanguagePostFixes, vm, nameof(vm.SelectedLanguagePostFix), Se.Language.General.LanguagePostFix); - var panelOutputFolder = new StackPanel + var output = new StackPanel { - Orientation = Orientation.Horizontal, - Margin = new Avalonia.Thickness(28, 0, 0, 0), // under the "Use output folder" radio button - VerticalAlignment = VerticalAlignment.Center, - Spacing = 5, + Spacing = 8, Children = { - textBoxOutputFolder, - buttonBrowse + BatchConvertChrome.MakeRow(Se.Language.General.TargetEncoding, comboBoxTargetEncoding, LabelWidth), + folder, + BatchConvertChrome.MakeSwitchRow(nameof(vm.Overwrite), Se.Language.General.OverwriteExistingFiles), + BatchConvertChrome.MakeSwitchRow(nameof(vm.KeepSourceTimestamp), Se.Language.Tools.BatchConvert.KeepSourceFileTimestamp), + BatchConvertChrome.MakeRow(Se.Language.General.LanguagePostFix, comboBoxLanguagePostFix, LabelWidth), } }; - var labelOcrEngine = UiUtil.MakeLabel(Se.Language.Ocr.OcrEngine); - var comboBoxOcrEngine = UiUtil.MakeComboBox(vm.OcrEngines, vm, nameof(vm.SelectedOcrEngine)); - var labelOcLanguage = UiUtil.MakeLabel(Se.Language.General.Language).WithBindVisible(vm, nameof(vm.IsOcrLanguageVisible)).WithMarginLeft(10); - var labelBinaryOcrDatabase = UiUtil.MakeLabel(Se.Language.Ocr.Database).WithBindVisible(vm, nameof(vm.IsBinaryOcrVisible)).WithMarginLeft(10); - var comboBoxTesseractLanguages = UiUtil.MakeComboBox(vm.TesseractDictionaryItems, vm, nameof(vm.SelectedTesseractDictionaryItem)) - .WithBindVisible(nameof(vm.IsTesseractOcrVisible)); - var labelTesseractEngineMode = UiUtil.MakeLabel(Se.Language.Ocr.TesseractEngineMode).WithBindVisible(vm, nameof(vm.IsTesseractOcrVisible)).WithMarginLeft(10); - var comboBoxTesseractEngineMode = UiUtil.MakeComboBox(vm.TesseractEngineModes, vm, nameof(vm.SelectedTesseractEngineMode)) + var comboBoxScanFolderVideoFiles = Combo(vm.ScanFolderVideoFilesOptions, vm, nameof(vm.SelectedScanFolderVideoFilesOption), Se.Language.Tools.BatchConvert.VideoFilesWhenAddingFolder); + var addingFiles = new StackPanel + { + Spacing = 8, + Children = + { + BatchConvertChrome.MakeSwitchRow(nameof(vm.ScanFolderRecursive), Se.Language.Tools.BatchConvert.IncludeSubfolders), + BatchConvertChrome.MakeRow(Se.Language.Tools.BatchConvert.VideoFilesWhenAddingFolder, comboBoxScanFolderVideoFiles, LabelWidth), + } + }; + + var comboBoxOcrEngine = Combo(vm.OcrEngines, vm, nameof(vm.SelectedOcrEngine), Se.Language.Ocr.OcrEngine); + var comboBoxTesseractLanguages = Combo(vm.TesseractDictionaryItems, vm, nameof(vm.SelectedTesseractDictionaryItem), Se.Language.General.Language) .WithBindVisible(nameof(vm.IsTesseractOcrVisible)); - var comboBoxPaddleLanguages = UiUtil.MakeComboBox(vm.PaddleOcrLanguages, vm, nameof(vm.SelectedPaddleOcrLanguage)) + var comboBoxTesseractEngineMode = Combo(vm.TesseractEngineModes, vm, nameof(vm.SelectedTesseractEngineMode), Se.Language.Ocr.TesseractEngineMode); + var comboBoxPaddleLanguages = Combo(vm.PaddleOcrLanguages, vm, nameof(vm.SelectedPaddleOcrLanguage), Se.Language.General.Language) .WithBindVisible(nameof(vm.IsPaddleOCrVisible)); - var comboBoxBinaryOcrDatabases = UiUtil.MakeComboBox(vm.BinaryOcrDatabases, vm, nameof(vm.SelectedBinaryOcrDatabase)) - .WithBindVisible(nameof(vm.IsBinaryOcrVisible)); - var labelBinaryOcrFallback = UiUtil.MakeLabel(Se.Language.Ocr.BinaryOcrNOcrFallbackDatabase).WithBindVisible(vm, nameof(vm.IsBinaryOcrVisible)).WithMarginLeft(10); - var comboBoxBinaryOcrFallback = UiUtil.MakeComboBox(vm.BinaryOcrFallbackNOcrDatabases, vm, nameof(vm.SelectedBinaryOcrFallbackNOcrDatabase)) - .WithBindVisible(nameof(vm.IsBinaryOcrVisible)); - var labelNOcrDatabase = UiUtil.MakeLabel(Se.Language.Ocr.Database).WithBindVisible(vm, nameof(vm.IsNOcrVisible)).WithMarginLeft(10); - var comboBoxNOcrDatabases = UiUtil.MakeComboBox(vm.NOcrDatabases, vm, nameof(vm.SelectedNOcrDatabase)) - .WithBindVisible(nameof(vm.IsNOcrVisible)); - var labelNOcrFallback = UiUtil.MakeLabel(Se.Language.Ocr.NOcrBinaryOcrFallbackDatabase).WithBindVisible(vm, nameof(vm.IsNOcrVisible)).WithMarginLeft(10); - var comboBoxNOcrFallback = UiUtil.MakeComboBox(vm.NOcrFallbackBinaryOcrDatabases, vm, nameof(vm.SelectedNOcrFallbackBinaryOcrDatabase)) - .WithBindVisible(nameof(vm.IsNOcrVisible)); - var labelOllamaModel = UiUtil.MakeLabel(Se.Language.General.Model).WithBindVisible(vm, nameof(vm.IsOllamaVisible)).WithMarginLeft(10); - var comboBoxOllamaModels = UiUtil.MakeComboBox(vm.OllamaModels, vm, nameof(vm.SelectedOllamaModel)) - .WithBindVisible(nameof(vm.IsOllamaVisible)); - var buttonOllamaModelBrowse = UiUtil.MakeButtonBrowse(vm.PickOllamaModelCommand, nameof(vm.IsOllamaVisible), Se.Language.General.Model).WithMarginLeft(3); - var labelLlamaCppModel = UiUtil.MakeLabel(Se.Language.General.Model).WithBindVisible(vm, nameof(vm.IsLlamaCppVisible)).WithMarginLeft(10); - var comboBoxLlamaCppModels = UiUtil.MakeComboBox(vm.LlamaCppOcrModels, vm, nameof(vm.SelectedLlamaCppOcrModel)) - .WithBindVisible(nameof(vm.IsLlamaCppVisible)); + var comboBoxBinaryOcrDatabases = Combo(vm.BinaryOcrDatabases, vm, nameof(vm.SelectedBinaryOcrDatabase), Se.Language.Ocr.Database); + var comboBoxBinaryOcrFallback = Combo(vm.BinaryOcrFallbackNOcrDatabases, vm, nameof(vm.SelectedBinaryOcrFallbackNOcrDatabase), Se.Language.Ocr.BinaryOcrNOcrFallbackDatabase); + var comboBoxNOcrDatabases = Combo(vm.NOcrDatabases, vm, nameof(vm.SelectedNOcrDatabase), Se.Language.Ocr.Database); + var comboBoxNOcrFallback = Combo(vm.NOcrFallbackBinaryOcrDatabases, vm, nameof(vm.SelectedNOcrFallbackBinaryOcrDatabase), Se.Language.Ocr.NOcrBinaryOcrFallbackDatabase); + var comboBoxOllamaModels = Combo(vm.OllamaModels, vm, nameof(vm.SelectedOllamaModel), Se.Language.General.Model); + var buttonOllamaModelBrowse = EditorChrome.CreateToolButton("fa-solid fa-ellipsis", Se.Language.General.Model, vm.PickOllamaModelCommand, 28); + var ollama = new Grid + { + ColumnSpacing = 4, + ColumnDefinitions = { new ColumnDefinition(new GridLength(1, GridUnitType.Star)), new ColumnDefinition(GridLength.Auto) }, + }; + ollama.Add(comboBoxOllamaModels, 0, 0); + ollama.Add(buttonOllamaModelBrowse, 0, 1); + var comboBoxLlamaCppModels = Combo(vm.LlamaCppOcrModels, vm, nameof(vm.SelectedLlamaCppOcrModel), Se.Language.General.Model); comboBoxLlamaCppModels.ItemTemplate = StatusDots.ComboItemTemplate( model => model.Model.DisplayName, model => model.Model.Size, model => model.IsInstalled ? DownloadDotStatus.UpToDate : DownloadDotStatus.NotInstalled); // Apple Vision: a language and nothing else - no model, no backend, no download. - var labelAppleVisionLanguage = UiUtil.MakeLabel(Se.Language.General.Language).WithBindVisible(vm, nameof(vm.IsAppleVisionVisible)).WithMarginLeft(10); - var comboBoxAppleVisionLanguages = UiUtil.MakeComboBox(vm.AppleVisionLanguages, vm, nameof(vm.SelectedAppleVisionLanguage)) - .WithBindVisible(nameof(vm.IsAppleVisionVisible)); - var labelCrispEmbedBackend = UiUtil.MakeLabel(Se.Language.General.Backend).WithBindVisible(vm, nameof(vm.IsCrispEmbedVisible)).WithMarginLeft(10); - var comboBoxCrispEmbedBackends = UiUtil.MakeComboBox(vm.CrispEmbedBackends, vm, nameof(vm.SelectedCrispEmbedBackend)) - .WithBindVisible(nameof(vm.IsCrispEmbedVisible)); - var labelCrispEmbedModel = UiUtil.MakeLabel(Se.Language.General.Model).WithBindVisible(vm, nameof(vm.IsCrispEmbedVisible)).WithMarginLeft(10); - var comboBoxCrispEmbedModels = UiUtil.MakeComboBox(vm.CrispEmbedModels, vm, nameof(vm.SelectedCrispEmbedModel)) - .WithBindVisible(nameof(vm.IsCrispEmbedVisible)); + var comboBoxAppleVisionLanguages = Combo(vm.AppleVisionLanguages, vm, nameof(vm.SelectedAppleVisionLanguage), Se.Language.General.Language); + var comboBoxCrispEmbedBackends = Combo(vm.CrispEmbedBackends, vm, nameof(vm.SelectedCrispEmbedBackend), Se.Language.General.Backend); + var comboBoxCrispEmbedModels = Combo(vm.CrispEmbedModels, vm, nameof(vm.SelectedCrispEmbedModel), Se.Language.General.Model); comboBoxCrispEmbedModels.ItemTemplate = MakeCrispEmbedModelItemTemplate(); vm.RefreshCrispEmbedModelCombo = () => comboBoxCrispEmbedModels.ItemTemplate = MakeCrispEmbedModelItemTemplate(); - var panelOcrEngine = new StackPanel + comboBoxOcrEngine.SelectionChanged += (s, e) => vm.OnOcrEngineChanged(); + + var vobSubIsolateColors = BatchConvertChrome.MakeSwitchRow(nameof(vm.VobSubIsolateColors), Se.Language.Ocr.VobSubIsolateColors, out var toggleVobSubIsolateColors); + if (Se.Settings.Appearance.ShowHints) + { + ToolTip.SetTip(toggleVobSubIsolateColors, Se.Language.Ocr.VobSubIsolateColorsHint); + } + + // One row per engine setting; each shows only for the engine it belongs to + var ocr = new StackPanel { - Orientation = Orientation.Horizontal, - Children = { labelOcrEngine, comboBoxOcrEngine, labelOcLanguage, comboBoxTesseractLanguages, labelTesseractEngineMode, comboBoxTesseractEngineMode, comboBoxPaddleLanguages, labelBinaryOcrDatabase, comboBoxBinaryOcrDatabases, labelBinaryOcrFallback, comboBoxBinaryOcrFallback, labelNOcrDatabase, comboBoxNOcrDatabases, labelNOcrFallback, comboBoxNOcrFallback, labelOllamaModel, comboBoxOllamaModels, buttonOllamaModelBrowse, labelLlamaCppModel, comboBoxLlamaCppModels, labelCrispEmbedBackend, comboBoxCrispEmbedBackends, labelCrispEmbedModel, comboBoxCrispEmbedModels, labelAppleVisionLanguage, comboBoxAppleVisionLanguages } + Spacing = 8, + Children = + { + BatchConvertChrome.MakeRow(Se.Language.Ocr.OcrEngine, comboBoxOcrEngine, LabelWidth), + BatchConvertChrome.MakeRow(Se.Language.General.Language, new Panel { Children = { comboBoxTesseractLanguages, comboBoxPaddleLanguages } }, LabelWidth, nameof(vm.IsOcrLanguageVisible)), + BatchConvertChrome.MakeRow(Se.Language.Ocr.TesseractEngineMode, comboBoxTesseractEngineMode, LabelWidth, nameof(vm.IsTesseractOcrVisible)), + BatchConvertChrome.MakeRow(Se.Language.Ocr.Database, comboBoxBinaryOcrDatabases, LabelWidth, nameof(vm.IsBinaryOcrVisible)), + BatchConvertChrome.MakeRow(Se.Language.Ocr.BinaryOcrNOcrFallbackDatabase, comboBoxBinaryOcrFallback, LabelWidth, nameof(vm.IsBinaryOcrVisible)), + BatchConvertChrome.MakeRow(Se.Language.Ocr.Database, comboBoxNOcrDatabases, LabelWidth, nameof(vm.IsNOcrVisible)), + BatchConvertChrome.MakeRow(Se.Language.Ocr.NOcrBinaryOcrFallbackDatabase, comboBoxNOcrFallback, LabelWidth, nameof(vm.IsNOcrVisible)), + BatchConvertChrome.MakeRow(Se.Language.General.Model, ollama, LabelWidth, nameof(vm.IsOllamaVisible)), + BatchConvertChrome.MakeRow(Se.Language.General.Model, comboBoxLlamaCppModels, LabelWidth, nameof(vm.IsLlamaCppVisible)), + BatchConvertChrome.MakeRow(Se.Language.General.Backend, comboBoxCrispEmbedBackends, LabelWidth, nameof(vm.IsCrispEmbedVisible)), + BatchConvertChrome.MakeRow(Se.Language.General.Model, comboBoxCrispEmbedModels, LabelWidth, nameof(vm.IsCrispEmbedVisible)), + BatchConvertChrome.MakeRow(Se.Language.General.Language, comboBoxAppleVisionLanguages, LabelWidth, nameof(vm.IsAppleVisionVisible)), + vobSubIsolateColors, + } }; - comboBoxOcrEngine.SelectionChanged += (s, e) => vm.OnOcrEngineChanged(); - var checkBoxVobSubIsolateColors = new CheckBox + var other = BatchConvertChrome.MakeSwitchRow(nameof(vm.PreventSleep), Se.Language.Tools.BatchConvert.PreventSleepWhileConverting); + + var left = new Border { - Content = Se.Language.Ocr.VobSubIsolateColors, - IsChecked = vm.VobSubIsolateColors, - VerticalAlignment = VerticalAlignment.Center, - [!CheckBox.IsCheckedProperty] = new Binding(nameof(vm.VobSubIsolateColors)) { Mode = BindingMode.TwoWay, UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged }, + Width = ColumnWidth, + BorderBrush = EditorChrome.LineBrush, + BorderThickness = new Thickness(0, 0, 1, 0), + Child = BatchConvertChrome.MakeSections( + EditorChrome.MakeEditorSection(Se.Language.Tools.BatchConvert.SettingsSectionOutput, output, first: true), + EditorChrome.MakeEditorSection(Se.Language.Tools.BatchConvert.SettingsSectionOther, other)), + }; + var right = new Border + { + Width = ColumnWidth, + Child = BatchConvertChrome.MakeSections( + EditorChrome.MakeEditorSection(Se.Language.Tools.BatchConvert.SettingsSectionAddingFiles, addingFiles, first: true), + EditorChrome.MakeEditorSection(Se.Language.Tools.BatchConvert.SettingsSectionImageBasedSubtitles, ocr)), }; - ToolTip.SetTip(checkBoxVobSubIsolateColors, Se.Language.Ocr.VobSubIsolateColorsHint); - var labelLanguagePostFix = UiUtil.MakeLabel(Se.Language.General.LanguagePostFix); - var comboBoxLanguagePostFix = UiUtil.MakeComboBox(vm.LanguagePostFixes, vm, nameof(vm.SelectedLanguagePostFix)); - var panelLanguagePostFix = new StackPanel + var columns = new Grid { - Orientation = Orientation.Horizontal, - Spacing = 5, - Children = { labelLanguagePostFix, comboBoxLanguagePostFix } + ColumnDefinitions = { new ColumnDefinition(GridLength.Auto), new ColumnDefinition(GridLength.Auto) }, }; + columns.Add(left, 0, 0); + columns.Add(right, 0, 1); var buttonOk = UiUtil.MakeButtonOk(vm.OkCommand); + buttonOk.Classes.Add("primary"); var buttonCancel = UiUtil.MakeButtonCancel(vm.CancelCommand); - var panelButtons = UiUtil.MakeButtonBar(buttonOk, buttonCancel); - var panelSections = new StackPanel + // No scroller of its own: a window clamped to a short screen wraps it all in one (#16026) + Content = new StackPanel { - Orientation = Orientation.Vertical, - Spacing = 12, - Margin = UiUtil.MakeWindowMargin(), Children = { - MakeSection(Se.Language.Tools.BatchConvert.SettingsSectionOutput, - panelTargetEncoding, - checkBoxUseSourceFolder, - checkBoxUseOutputFolder, - panelOutputFolder, - checkBoxOverwrite, - checkBoxKeepSourceTimestamp, - panelLanguagePostFix), - MakeSection(Se.Language.Tools.BatchConvert.SettingsSectionAddingFiles, - checkBoxScanFolderRecursive, - panelScanFolderVideoFiles), - MakeSection(Se.Language.Tools.BatchConvert.SettingsSectionImageBasedSubtitles, - panelOcrEngine, - checkBoxVobSubIsolateColors), - MakeSection(Se.Language.Tools.BatchConvert.SettingsSectionOther, - checkBoxPreventSleep), - panelButtons, + VideoSubDialogChrome.MakePanel(columns), + VideoSubDialogChrome.MakeButtonBar(null, buttonCancel, buttonOk), } }; - Content = panelSections; - UiUtil.FocusOnFirstActivation(this, comboBoxTargetEncoding); // initial focus on an input, not an action button - a focused button clicks on bare Space KeyDown += (s, e) => vm.OnKeyDown(e); } - // A bold title over a bordered group - keeps the long list of options scannable. - private static Control MakeSection(string title, params Control[] controls) + private static ComboBox Combo(System.Collections.ObjectModel.ObservableCollection items, BatchConvertSettingsViewModel vm, string property, string name) { - var panel = new StackPanel - { - Orientation = Orientation.Vertical, - Spacing = 8, - Margin = new Avalonia.Thickness(5), - }; - foreach (var control in controls) - { - panel.Children.Add(control); - } - - return new StackPanel - { - Orientation = Orientation.Vertical, - Spacing = 4, - Children = - { - UiUtil.MakeLabel(title).WithBold(), - UiUtil.MakeBorderForControl(panel), - } - }; + var comboBox = UiUtil.MakeComboBox(items, vm, property); + comboBox.HorizontalAlignment = HorizontalAlignment.Stretch; + AutomationProperties.SetName(comboBox, name); + return comboBox; } // Model combo item template: a dot (green = downloaded, grey = not downloaded yet) plus the diff --git a/src/ui/Features/Tools/BatchConvert/BatchConvertTsSettingsWindow.cs b/src/ui/Features/Tools/BatchConvert/BatchConvertTsSettingsWindow.cs index b4e5cf2800f..48f5dd1307b 100644 --- a/src/ui/Features/Tools/BatchConvert/BatchConvertTsSettingsWindow.cs +++ b/src/ui/Features/Tools/BatchConvert/BatchConvertTsSettingsWindow.cs @@ -1,72 +1,81 @@ +using Avalonia; +using Avalonia.Automation; using Avalonia.Controls; using Avalonia.Layout; +using Avalonia.Media; +using Nikse.SubtitleEdit.Features.Video; using Nikse.SubtitleEdit.Logic; using Nikse.SubtitleEdit.Logic.Config; using Nikse.SubtitleEdit.UiLogic.BatchConvert; namespace Nikse.SubtitleEdit.Features.Tools.BatchConvert; +/// +/// Transport Stream settings for batch convert, in the editor look (see ): +/// position and video size overrides, the file name ending, OK in the button bar. +/// public class BatchConvertTsSettingsWindow : Window { + private const double LabelWidth = 130; + public BatchConvertTsSettingsWindow(BatchConvertTsSettingsViewModel vm) { UiUtil.InitializeWindow(this, GetType().Name); Title = Se.Language.Tools.BatchConvert.TransportStreamSettings; SizeToContent = SizeToContent.WidthAndHeight; CanResize = false; + VideoSubDialogChrome.Apply(this); vm.Window = this; DataContext = vm; - var labelInfo = UiUtil.MakeTextBlock(Se.Language.Tools.BatchConvert.TransportStreamSettingsInfo); - labelInfo.TextWrapping = Avalonia.Media.TextWrapping.Wrap; + var labelInfo = VideoSubDialogChrome.MakeHintText(Se.Language.Tools.BatchConvert.TransportStreamSettingsInfo); labelInfo.MaxWidth = 560; - labelInfo.Opacity = 0.7; - labelInfo.Margin = new Avalonia.Thickness(0, 0, 0, 6); + labelInfo.HorizontalAlignment = HorizontalAlignment.Left; // X position - var checkBoxOverrideX = UiUtil.MakeCheckBox(Se.Language.Tools.BatchConvert.TransportStreamOverrideXPosition, vm, nameof(vm.OverrideXPosition)); - var labelAlignment = UiUtil.MakeLabel(Se.Language.General.Alignment).WithMarginLeft(25); + var switchOverrideX = BatchConvertChrome.MakeSwitchRow(nameof(vm.OverrideXPosition), Se.Language.Tools.BatchConvert.TransportStreamOverrideXPosition, out var toggleOverrideX); var comboBoxAlignment = UiUtil.MakeComboBox(vm.HorizontalAlignments, vm, nameof(vm.SelectedHorizontalAlignment)) .WithBindEnabled(nameof(vm.OverrideXPosition)); - var labelHMargin = UiUtil.MakeLabel(Se.Language.File.Export.LeftRightMargin).WithMarginLeft(15); - var numericHMargin = UiUtil.MakeNumericUpDownInt(0, 100, 5, 110, vm, nameof(vm.HorizontalMarginPercent)) + comboBoxAlignment.MinWidth = 140; + AutomationProperties.SetName(comboBoxAlignment, Se.Language.General.Alignment); + var numericHMargin = Field(UiUtil.MakeNumericUpDownInt(0, 100, 5, 80, vm, nameof(vm.HorizontalMarginPercent)), Se.Language.File.Export.LeftRightMargin) .WithBindEnabled(nameof(vm.OverrideXPosition)); - var panelX = new StackPanel - { - Orientation = Orientation.Horizontal, - Children = { labelAlignment, comboBoxAlignment, labelHMargin, numericHMargin, UiUtil.MakeLabel("%") } - }; + var panelX = Indented( + BatchConvertChrome.MakeRow(Se.Language.General.Alignment, comboBoxAlignment, LabelWidth), + BatchConvertChrome.MakeRow(Se.Language.File.Export.LeftRightMargin, Percent(numericHMargin), LabelWidth)); // Y position - var checkBoxOverrideY = UiUtil.MakeCheckBox(Se.Language.Tools.BatchConvert.TransportStreamOverrideYPosition, vm, nameof(vm.OverrideYPosition)); - var labelBottomMargin = UiUtil.MakeLabel(Se.Language.Tools.BatchConvert.TransportStreamBottomMargin).WithMarginLeft(25); - var numericBottomMargin = UiUtil.MakeNumericUpDownInt(0, 100, 5, 110, vm, nameof(vm.BottomMarginPercent)) + var switchOverrideY = BatchConvertChrome.MakeSwitchRow(nameof(vm.OverrideYPosition), Se.Language.Tools.BatchConvert.TransportStreamOverrideYPosition); + var numericBottomMargin = Field(UiUtil.MakeNumericUpDownInt(0, 100, 5, 80, vm, nameof(vm.BottomMarginPercent)), Se.Language.Tools.BatchConvert.TransportStreamBottomMargin) .WithBindEnabled(nameof(vm.OverrideYPosition)); - var panelY = new StackPanel - { - Orientation = Orientation.Horizontal, - Children = { labelBottomMargin, numericBottomMargin, UiUtil.MakeLabel("%") } - }; + var panelY = Indented(BatchConvertChrome.MakeRow(Se.Language.Tools.BatchConvert.TransportStreamBottomMargin, Percent(numericBottomMargin), LabelWidth)); + + var position = new StackPanel { Spacing = 8, Children = { switchOverrideX, panelX, switchOverrideY, panelY } }; // Video size - var checkBoxOverrideSize = UiUtil.MakeCheckBox(Se.Language.Tools.BatchConvert.TransportStreamOverrideVideoSize, vm, nameof(vm.OverrideVideoSize)); - var labelWidth = UiUtil.MakeLabel(Se.Language.General.Width).WithMarginLeft(25); - var numericWidth = UiUtil.MakeNumericUpDownInt(1, 9999, 1920, 120, vm, nameof(vm.VideoWidth)) + var switchOverrideSize = BatchConvertChrome.MakeSwitchRow(nameof(vm.OverrideVideoSize), Se.Language.Tools.BatchConvert.TransportStreamOverrideVideoSize); + var numericWidth = Field(UiUtil.MakeNumericUpDownInt(1, 9999, 1920, 80, vm, nameof(vm.VideoWidth)), Se.Language.General.Width, "W") .WithBindEnabled(nameof(vm.OverrideVideoSize)); - var labelHeight = UiUtil.MakeLabel(Se.Language.General.Height).WithMarginLeft(15); - var numericHeight = UiUtil.MakeNumericUpDownInt(1, 9999, 1080, 120, vm, nameof(vm.VideoHeight)) + var numericHeight = Field(UiUtil.MakeNumericUpDownInt(1, 9999, 1080, 80, vm, nameof(vm.VideoHeight)), Se.Language.General.Height, "H") .WithBindEnabled(nameof(vm.OverrideVideoSize)); - var buttonGetSizeFromVideo = UiUtil.MakeButton(Se.Language.Tools.BatchConvert.TransportStreamGetSizeFromVideo, vm.GetSizeFromVideoCommand) - .WithMarginLeft(15); + var buttonGetSizeFromVideo = VideoSubDialogChrome.MakeAction("fa-solid fa-film", Se.Language.Tools.BatchConvert.TransportStreamGetSizeFromVideo, vm.GetSizeFromVideoCommand); var panelSize = new StackPanel { Orientation = Orientation.Horizontal, - Children = { labelWidth, numericWidth, labelHeight, numericHeight, buttonGetSizeFromVideo } + Spacing = 6, + Margin = new Thickness(46, 0, 0, 0), // under the switch's label + Children = + { + numericWidth, + new TextBlock { Text = "×", Foreground = EditorChrome.DimTextBrush, VerticalAlignment = VerticalAlignment.Center }, + numericHeight, + buttonGetSizeFromVideo, + } }; + var videoSize = new StackPanel { Spacing = 8, Children = { switchOverrideSize, panelSize } }; // File name ending - var labelFileNameEnding = UiUtil.MakeLabel(Se.Language.Tools.BatchConvert.TransportStreamFileNameEnding); - var textBoxFileNameEnding = UiUtil.MakeTextBox(260, vm, nameof(vm.FileNameEnding)); + var textBoxFileNameEnding = VideoSubDialogChrome.MakeTextBox(nameof(vm.FileNameEnding), Se.Language.Tools.BatchConvert.TransportStreamFileNameEnding, 220); vm.GetFileNameEndingCaretIndex = () => textBoxFileNameEnding.CaretIndex; vm.SetFileNameEndingCaretIndex = index => { @@ -75,7 +84,11 @@ public BatchConvertTsSettingsWindow(BatchConvertTsSettingsViewModel vm) }; var buttonInsertPlaceholder = new Button { - Content = "{ }", + Content = new TextBlock { Text = "{ }", FontSize = 12.5, FontWeight = FontWeight.SemiBold }, + Width = 34, + Height = 28, + HorizontalContentAlignment = HorizontalAlignment.Center, + VerticalContentAlignment = VerticalAlignment.Center, VerticalAlignment = VerticalAlignment.Center, Flyout = new MenuFlyout { @@ -87,63 +100,106 @@ public BatchConvertTsSettingsWindow(BatchConvertTsSettingsViewModel vm) MakePlaceholderMenuItem(vm, Se.Language.Tools.BatchConvert.ThreeLetterLanguageCodeUppercase, TransportStreamExportSettings.PlaceholderThreeLetterUppercase), } }, - }.WithMarginLeft(5); - ToolTip.SetTip(buttonInsertPlaceholder, Se.Language.Tools.BatchConvert.TransportStreamFileNameEndingInfo); - var labelSample = UiUtil.MakeLabel(string.Empty).WithMarginLeft(10).WithOpacity(0.7); - labelSample.Bind(ContentControl.ContentProperty, new Avalonia.Data.Binding(nameof(vm.FileNameEndingSample)) { Source = vm }); + }; + buttonInsertPlaceholder.Classes.Add("tool"); + AutomationProperties.SetName(buttonInsertPlaceholder, Se.Language.Tools.BatchConvert.TransportStreamFileNameEndingInfo); + if (Se.Settings.Appearance.ShowHints) + { + ToolTip.SetTip(buttonInsertPlaceholder, Se.Language.Tools.BatchConvert.TransportStreamFileNameEndingInfo); + } + + var labelSample = VideoSubDialogChrome.MakeHint(nameof(vm.FileNameEndingSample)); var panelFileNameEnding = new StackPanel { Orientation = Orientation.Horizontal, - Children = { labelFileNameEnding, textBoxFileNameEnding, buttonInsertPlaceholder, labelSample } + Spacing = 6, + Children = { textBoxFileNameEnding, buttonInsertPlaceholder, labelSample } }; - var checkBoxOnlyTeletext = UiUtil.MakeCheckBox(Se.Language.Tools.BatchConvert.TransportStreamOnlyTeletext, vm, nameof(vm.OnlyTeletext)); + var fileNameInfo = VideoSubDialogChrome.MakeHintText(Se.Language.Tools.BatchConvert.TransportStreamFileNameEndingInfo); + fileNameInfo.MaxWidth = 480; + fileNameInfo.HorizontalAlignment = HorizontalAlignment.Left; + fileNameInfo.Margin = new Thickness(LabelWidth + 8, 0, 0, 0); + + var outputFiles = new StackPanel + { + Spacing = 8, + Children = + { + BatchConvertChrome.MakeRow(Se.Language.Tools.BatchConvert.TransportStreamFileNameEnding, panelFileNameEnding, LabelWidth), + fileNameInfo, + BatchConvertChrome.MakeSwitchRow(nameof(vm.OnlyTeletext), Se.Language.Tools.BatchConvert.TransportStreamOnlyTeletext), + } + }; var buttonOk = UiUtil.MakeButtonOk(vm.OkCommand); + buttonOk.Classes.Add("primary"); var buttonCancel = UiUtil.MakeButtonCancel(vm.CancelCommand); - var panelButtons = UiUtil.MakeButtonBar(buttonOk, buttonCancel); - var grid = new Grid + var sections = BatchConvertChrome.MakeSections( + new Border { Child = labelInfo, Padding = new Thickness(14, 12, 14, 0) }, + EditorChrome.MakeEditorSection(Se.Language.General.Position, position, first: true), + EditorChrome.MakeEditorSection(Se.Language.General.VideoResolution, videoSize), + EditorChrome.MakeEditorSection(Se.Language.Tools.BatchConvert.SettingsSectionOutput, outputFiles)); + sections.MinWidth = 600; + + Content = new StackPanel { - RowDefinitions = - { - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - }, - ColumnDefinitions = + Children = { - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Auto) }, - }, - Margin = UiUtil.MakeWindowMargin(), - ColumnSpacing = 10, - RowSpacing = 8, - Width = double.NaN, - HorizontalAlignment = HorizontalAlignment.Stretch, + VideoSubDialogChrome.MakePanel(sections), + VideoSubDialogChrome.MakeButtonBar(null, buttonCancel, buttonOk), + } }; - grid.Add(labelInfo, 0, 0); - grid.Add(checkBoxOverrideX, 1, 0); - grid.Add(panelX, 2, 0); - grid.Add(checkBoxOverrideY, 3, 0); - grid.Add(panelY, 4, 0); - grid.Add(checkBoxOverrideSize, 5, 0); - grid.Add(panelSize, 6, 0); - grid.Add(panelFileNameEnding, 7, 0); - grid.Add(checkBoxOnlyTeletext, 8, 0); - grid.Add(panelButtons, 9, 0); + UiUtil.FocusOnFirstActivation(this, toggleOverrideX); // initial focus on an input, not an action button - a focused button clicks on bare Space + KeyDown += (s, e) => vm.OnKeyDown(e); + } - Content = grid; + /// + /// A compact number field (see ) with an optional dim prefix. + /// + private static NumericUpDown Field(NumericUpDown field, string name, string? prefix = null) + { + field.Classes.Add("field"); + AutomationProperties.SetName(field, name); + if (prefix != null) + { + field.InnerLeftContent = new TextBlock + { + Text = prefix, + Foreground = EditorChrome.FaintTextBrush, + FontSize = 11, + VerticalAlignment = VerticalAlignment.Center, + Margin = new Thickness(8, 0, 0, 0), + }; + } - UiUtil.FocusOnFirstActivation(this, checkBoxOverrideX); // initial focus on an input, not an action button - a focused button clicks on bare Space - KeyDown += (s, e) => vm.OnKeyDown(e); + return field; + } + + private static StackPanel Percent(NumericUpDown field) + { + return new StackPanel + { + Orientation = Orientation.Horizontal, + Spacing = 6, + Children = { field, new TextBlock { Text = "%", Foreground = EditorChrome.DimTextBrush, VerticalAlignment = VerticalAlignment.Center } }, + }; + } + + /// + /// The rows that belong to the switch above, indented under its label. + /// + private static StackPanel Indented(params Control[] rows) + { + var panel = new StackPanel { Spacing = 6, Margin = new Thickness(46, 0, 0, 0) }; + foreach (var row in rows) + { + panel.Children.Add(row); + } + + return panel; } private static MenuItem MakePlaceholderMenuItem(BatchConvertTsSettingsViewModel vm, string header, string placeholder) diff --git a/src/ui/Features/Tools/BatchConvert/BatchConvertWindow.cs b/src/ui/Features/Tools/BatchConvert/BatchConvertWindow.cs index 32629d32394..771adb3a334 100644 --- a/src/ui/Features/Tools/BatchConvert/BatchConvertWindow.cs +++ b/src/ui/Features/Tools/BatchConvert/BatchConvertWindow.cs @@ -8,13 +8,12 @@ using Avalonia.Input; using Avalonia.Interactivity; using Avalonia.Layout; -using Avalonia.Threading; using Avalonia.Media; using System; -using System.Collections; using System.Collections.Generic; using System.Linq; using Nikse.SubtitleEdit.Controls; +using Nikse.SubtitleEdit.Features.Video; using Nikse.SubtitleEdit.Logic; using Nikse.SubtitleEdit.Logic.Config; using Nikse.SubtitleEdit.Logic.ValueConverters; @@ -22,6 +21,11 @@ namespace Nikse.SubtitleEdit.Features.Tools.BatchConvert; +/// +/// Batch convert, in the editor look (see ): the file toolbar and filter +/// on top, the file list with the target format and output under it, the function checklist and the +/// selected function's settings at the bottom; progress and Convert in the button bar. +/// public class BatchConvertWindow : Window { public BatchConvertWindow(BatchConvertViewModel vm) @@ -33,31 +37,10 @@ public BatchConvertWindow(BatchConvertViewModel vm) MinWidth = 900; MinHeight = 600; CanResize = true; + VideoSubDialogChrome.Apply(this); vm.Window = this; DataContext = vm; - var fileView = MakeFileView(vm); - var functionsListView = MakeFunctionsListView(vm); - var functionView = MakeFunctionView(vm); - - var labelFunctionsSelected = UiUtil.MakeLabel().WithBindText(vm, nameof(vm.ActionsSelected)) - .WithAlignmentTop(); - - var labelBatchItemsInfo = UiUtil.MakeLabel() - .WithBindText(vm, nameof(vm.BatchItemsInfo)) - .WithAlignmentTop(); - - var panelInfo = new StackPanel - { - Orientation = Orientation.Vertical, - Children = - { - labelFunctionsSelected, - labelBatchItemsInfo, - } - }; - panelInfo.WithBindVisible(vm, nameof(vm.IsConverting), InverseBooleanConverter.Instance); - var buttonConvert = new SeSplitButton { Content = Se.Language.General.Convert, @@ -79,48 +62,43 @@ public BatchConvertWindow(BatchConvertViewModel vm) } } }; + buttonConvert.Classes.Add("primary"); buttonConvert.Bind(SplitButton.IsEnabledProperty, new Binding(nameof(vm.IsConverting)) { Converter = InverseBooleanConverter.Instance }); + AutomationProperties.SetName(buttonConvert, Se.Language.General.Convert); var buttonDone = UiUtil.MakeButtonDone(vm.DoneCommand).WithBindIsVisible(nameof(vm.IsConverting), InverseBooleanConverter.Instance); var buttonCancel = UiUtil.MakeButtonCancel(vm.CancelCommand).WithBindIsVisible(vm, nameof(vm.IsConverting)); - var buttonPanel = UiUtil.MakeButtonBar( - buttonConvert, - buttonCancel, - buttonDone - ); - var progressText = UiUtil.MakeLabel() - .WithBindText(vm, nameof(vm.ProgressText)) - .WithAlignmentTop(); + // "No files to convert" and the end-of-run summary + var statusText = VideoSubDialogChrome.MakeHint(nameof(vm.StatusText)); + statusText.FontSize = 12; + var buttonBar = VideoSubDialogChrome.MakeButtonBar(statusText, buttonCancel, buttonDone, buttonConvert); - var grid = new Grid + var progressBar = UiUtil.MakeProgressBar(6); + progressBar.Maximum = 1; // ProgressValue is the converted fraction + progressBar.Bind(ProgressBar.ValueProperty, new Binding(nameof(vm.ProgressValue))); + var progressStrip = VideoSubDialogChrome.MakeProgressStrip(progressBar, nameof(vm.ProgressText), nameof(vm.IsConverting)); + + // The file list and the functions share the height (the functions up to what they need), + // so both stay usable and the progress and the buttons reachable at the minimum height. + var root = new Grid { RowDefinitions = { - new RowDefinition { Height = new GridLength(1, GridUnitType.Star) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, + new RowDefinition(GridLength.Auto), // file toolbar + filter + new RowDefinition(new GridLength(1, GridUnitType.Star)) { MinHeight = 180 }, // file list, target format, output + new RowDefinition(new GridLength(1, GridUnitType.Star)) { MinHeight = 220, MaxHeight = 330 }, // functions + the selected function's settings + new RowDefinition(GridLength.Auto), // progress, while converting + new RowDefinition(GridLength.Auto), // status + buttons }, - ColumnDefinitions = - { - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Auto) }, - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) }, - }, - Margin = UiUtil.MakeWindowMargin(), - ColumnSpacing = 10, - RowSpacing = 5, - Width = double.NaN, - HorizontalAlignment = HorizontalAlignment.Stretch, }; + root.Add(MakeToolbar(vm), 0, 0); + root.Add(MakeFileView(vm), 1, 0); + root.Add(MakeFunctionsPanel(vm), 2, 0); + root.Add(progressStrip, 3, 0); + root.Add(buttonBar, 4, 0); - grid.Add(fileView, 0, 0, 1, 2); - grid.Add(functionsListView, 1, 0); - grid.Add(functionView, 1, 1); - grid.Add(panelInfo, 2, 0); - grid.Add(progressText, 2, 0); - grid.Add(buttonPanel, 2, 0, 1, 2); - - Content = grid; + Content = root; UiUtil.FocusOnFirstActivation(this, buttonDone); // hack to make OnKeyDown work Loaded += vm.Onloaded; @@ -128,24 +106,93 @@ public BatchConvertWindow(BatchConvertViewModel vm) KeyDown += (s, e) => vm.OnKeyDown(e); } - private static Border MakeFileView(BatchConvertViewModel vm) + /// + /// A section that fills its height: small caps header (with optional content on the right) over + /// , which gets the rest - unlike , + /// which stacks, so a list in it would get unlimited height. + /// + private static Grid MakeFillSection(string title, Control content, Control? headerRight = null) { + var header = new DockPanel(); + if (headerRight != null) + { + DockPanel.SetDock(headerRight, Dock.Right); + header.Children.Add(headerRight); + } + + header.Children.Add(EditorChrome.MakeSectionHeader(title)); + var grid = new Grid { - RowDefinitions = + RowSpacing = 8, + RowDefinitions = { new RowDefinition(GridLength.Auto), new RowDefinition(new GridLength(1, GridUnitType.Star)) }, + }; + grid.Add(header, 0, 0); + grid.Add(content, 1, 0); + return grid; + } + + private static Border MakeToolbar(BatchConvertViewModel vm) + { + // Add/Remove/Clear are locked while converting: the run works on a snapshot of the list, + // so removed files would still be converted and added files silently skipped (#15116). + Button Action(Button button) + { + button.WithBindEnabled(nameof(vm.AreControlsEnabled)); + return button; + } + + var actions = new StackPanel + { + Orientation = Orientation.Horizontal, + Spacing = 2, + VerticalAlignment = VerticalAlignment.Center, + Children = { - new RowDefinition { Height = new GridLength(1, GridUnitType.Star) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - }, - ColumnDefinitions = + Action(VideoSubDialogChrome.MakeAction("fa-solid fa-plus", Se.Language.General.Add, vm.AddFilesCommand)), + Action(VideoSubDialogChrome.MakeAction("fa-solid fa-folder-plus", Se.Language.Tools.BatchConvert.AddFolderDotDotDot, vm.AddFolderCommand)), + Action(VideoSubDialogChrome.MakeAction("fa-solid fa-folder-tree", Se.Language.Tools.BatchConvert.AddFolderRecursiveDotDotDot, vm.AddFolderRecursiveCommand)), + EditorChrome.MakeToolbarSeparator(), + Action(EditorChrome.CreateToolButton("fa-solid fa-minus", Se.Language.General.Remove, vm.RemoveSelectedFilesCommand)), + Action(EditorChrome.CreateToolButton("fa-solid fa-trash", Se.Language.General.Clear, vm.ClearAllFilesCommand)), + } + }; + + // Not while converting: rows mid-OCR ("OCR...", "OCR x%") would be filtered as errors. + var comboBoxFilter = UiUtil.MakeComboBox(vm.FilterItems, vm, nameof(vm.SelectedFilterItem)) + .WithBindEnabled(nameof(vm.AreControlsEnabled)); + comboBoxFilter.SelectionChanged += (_, _) => vm.FilterComboBoxChanged(); + comboBoxFilter.MinWidth = 150; + AutomationProperties.SetName(comboBoxFilter, Se.Language.General.Filter); + var textBoxFilter = UiUtil.MakeTextBox(200, vm, nameof(vm.FilterText)) + .WithBindIsVisible(nameof(vm.IsFilterTextVisible)) + .WithBindEnabled(nameof(vm.AreControlsEnabled)) + .WithSearchAndClearIcons(); + textBoxFilter.Classes.Add("text"); + textBoxFilter.TextChanged += (_, _) => vm.FilterTextChanged(); + AutomationProperties.SetName(textBoxFilter, Se.Language.General.Filter); + var filter = new StackPanel + { + Orientation = Orientation.Horizontal, + Spacing = 6, + VerticalAlignment = VerticalAlignment.Center, + Children = { - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) }, - }, - ColumnSpacing = 10, - RowSpacing = 0, + new Optris.Icons.Avalonia.Icon { Value = "fa-solid fa-filter", FontSize = 12, Foreground = EditorChrome.FaintTextBrush, VerticalAlignment = VerticalAlignment.Center }, + comboBoxFilter, + textBoxFilter, + } }; + var bar = new DockPanel(); + DockPanel.SetDock(filter, Dock.Right); + bar.Children.Add(filter); + bar.Children.Add(actions); + return VideoSubDialogChrome.MakeToolbar(bar); + } + + private static Border MakeFileView(BatchConvertViewModel vm) + { var columnFileName = new SeTableViewColumn { Header = Se.Language.General.FileName, @@ -236,76 +283,6 @@ private static Border MakeFileView(BatchConvertViewModel vm) .AddSortable(columnFormat, x => x.Format) .AddSortable(columnStatus, x => x.Status); - var comboBoxSubtitleFormat = UiUtil.MakeComboBox(vm.TargetFormats, vm, nameof(vm.SelectedTargetFormat)); - comboBoxSubtitleFormat.SelectionChanged += (_, _) => vm.ComboBoxSubtitleFormatChanged(); - // Tunnel phase so right-click / Mac Ctrl+click opens the format picker before the ComboBox - // consumes the click to open its dropdown (matches the main window's format combo). - comboBoxSubtitleFormat.AddHandler(InputElement.PointerPressedEvent, - vm.ComboBoxSubtitleFormatPointerPressed, - RoutingStrategies.Tunnel, - handledEventsToo: true); - comboBoxSubtitleFormat.Width = 240; - - var buttonTargetFormatSettings = UiUtil.MakeButton(vm.ShowTargetFormatSettingsCommand, IconNames.Settings, Se.Language.Tools.BatchConvert.TargetFormatSettings) - .WithMarginLeft(5) - .WithMarginRight(5); - buttonTargetFormatSettings.WithBindIsVisible(vm, nameof(vm.IsTargetFormatSettingsVisible)); - // Only offered while a Transport Stream file is in the list - the settings apply to nothing else. - var buttonTransportStreamSettings = UiUtil.MakeButton(vm.ShowTransportStreamSettingsCommand, IconNames.FileCog, Se.Language.Tools.BatchConvert.TransportStreamSettingsDotDotDot) - .WithMarginLeft(5) - .WithMarginRight(5); - buttonTransportStreamSettings.WithBindIsVisible(vm, nameof(vm.IsTransportStreamSettingsVisible)); - var buttonSettings = UiUtil.MakeButton(vm.ShowOutputPropertiesCommand, IconNames.Settings, Se.Language.General.Settings).WithMarginLeft(15).WithMarginRight(5); - - // Add/Remove/Clear are locked while converting: the run works on a snapshot of the list, - // so removed files would still be converted and added files silently skipped (#15116). - var panelFileControls = new StackPanel - { - Orientation = Orientation.Horizontal, - HorizontalAlignment = HorizontalAlignment.Stretch, - VerticalAlignment = VerticalAlignment.Center, - Margin = new Thickness(0, 0, 0, 0), - Children = - { - UiUtil.MakeButton(vm.AddFilesCommand, IconNames.Plus, Se.Language.General.Add).WithMarginLeft(10).WithBindEnabled(nameof(vm.AreControlsEnabled)), - UiUtil.MakeButton(vm.AddFolderCommand, IconNames.Folder, Se.Language.Tools.BatchConvert.AddFolderDotDotDot).WithMarginLeft(5).WithBindEnabled(nameof(vm.AreControlsEnabled)), - UiUtil.MakeButton(vm.AddFolderRecursiveCommand, IconNames.FolderMultiple, Se.Language.Tools.BatchConvert.AddFolderRecursiveDotDotDot).WithMarginLeft(5).WithBindEnabled(nameof(vm.AreControlsEnabled)), - UiUtil.MakeButton(vm.RemoveSelectedFilesCommand, IconNames.Trash, Se.Language.General.Remove).WithMarginLeft(5).WithBindEnabled(nameof(vm.AreControlsEnabled)), - UiUtil.MakeButton(vm.ClearAllFilesCommand, IconNames.Close, Se.Language.General.Clear).WithMarginLeft(5).WithBindEnabled(nameof(vm.AreControlsEnabled)), - UiUtil.MakeLabel(Se.Language.General.TargetFormat).WithMarginLeft(15), - comboBoxSubtitleFormat, - buttonTargetFormatSettings, - buttonTransportStreamSettings, - buttonSettings, - MakeOutputPropertiesGrid(vm), - } - }; - - var labelFilter = UiUtil.MakeLabel(Se.Language.General.Filter); - // Not while converting: rows mid-OCR ("OCR...", "OCR x%") would be filtered as errors. - var comboBoxFilter = UiUtil.MakeComboBox(vm.FilterItems, vm, nameof(vm.SelectedFilterItem)) - .WithMarginRight(3) - .WithBindEnabled(nameof(vm.AreControlsEnabled)); - comboBoxFilter.SelectionChanged += (_, _) => vm.FilterComboBoxChanged(); - var textBoxFilter = UiUtil.MakeTextBox(200, vm, nameof(vm.FilterText)) - .WithBindIsVisible(nameof(vm.IsFilterTextVisible)) - .WithBindEnabled(nameof(vm.AreControlsEnabled)) - .WithSearchAndClearIcons(); - textBoxFilter.TextChanged += (_, _) => vm.FilterTextChanged(); - var panelFilter = new StackPanel - { - Orientation = Orientation.Horizontal, - HorizontalAlignment = HorizontalAlignment.Left, - VerticalAlignment = VerticalAlignment.Center, - Margin = new Thickness(10, 0, 0, 10), - Children = - { - labelFilter, - comboBoxFilter, - textBoxFilter, - } - }; - var flyout = new MenuFlyout(); flyout.Opening += (_, _) => vm.FileGridContextMenuOpening(); var menuItemRemove = new MenuItem @@ -377,30 +354,119 @@ private static Border MakeFileView(BatchConvertViewModel vm) { Children = { - dropHost, + VideoSubDialogChrome.MakeListFrame(dropHost), MakeBusyOverlay(vm), } }; - grid.Add(fileGridContainer, 0, 0); - grid.Add(panelFileControls, 1, 0); - grid.Add(panelFilter, 2, 0); + var count = VideoSubDialogChrome.MakeHint(nameof(vm.BatchItemsInfo)); + var grid = new Grid + { + RowSpacing = 10, + RowDefinitions = { new RowDefinition(new GridLength(1, GridUnitType.Star)), new RowDefinition(GridLength.Auto) }, + }; + grid.Add(MakeFillSection(Se.Language.General.SubtitleFiles, fileGridContainer, count), 0, 0); + grid.Add(MakeTargetRow(vm), 1, 0); - var border = UiUtil.MakeBorderForControlNoPadding(grid); - return border; + return new Border + { + Child = grid, + Padding = new Thickness(14, 12, 14, 12), + }; } - private static Border MakeBusyOverlay(BatchConvertViewModel vm) + /// + /// Target format (with its settings) on the left, where the files go and with which encoding on + /// the right, and the output settings button. + /// + private static Grid MakeTargetRow(BatchConvertViewModel vm) { - var isDark = Application.Current?.ActualThemeVariant == Avalonia.Styling.ThemeVariant.Dark; - var cardBackground = new SolidColorBrush(isDark ? UiUtil.GetDarkThemeBackgroundColor() : Colors.White); + var comboBoxSubtitleFormat = UiUtil.MakeComboBox(vm.TargetFormats, vm, nameof(vm.SelectedTargetFormat)); + comboBoxSubtitleFormat.SelectionChanged += (_, _) => vm.ComboBoxSubtitleFormatChanged(); + // Tunnel phase so right-click / Mac Ctrl+click opens the format picker before the ComboBox + // consumes the click to open its dropdown (matches the main window's format combo). + comboBoxSubtitleFormat.AddHandler(InputElement.PointerPressedEvent, + vm.ComboBoxSubtitleFormatPointerPressed, + RoutingStrategies.Tunnel, + handledEventsToo: true); + comboBoxSubtitleFormat.Width = 240; + AutomationProperties.SetName(comboBoxSubtitleFormat, Se.Language.General.TargetFormat); + + var buttonTargetFormatSettings = EditorChrome.CreateToolButton("fa-solid fa-gear", Se.Language.Tools.BatchConvert.TargetFormatSettings, vm.ShowTargetFormatSettingsCommand, 28); + buttonTargetFormatSettings.WithBindIsVisible(vm, nameof(vm.IsTargetFormatSettingsVisible)); + // Only offered while a Transport Stream file is in the list - the settings apply to nothing else. + var buttonTransportStreamSettings = EditorChrome.CreateToolButton("fa-solid fa-file-video", Se.Language.Tools.BatchConvert.TransportStreamSettingsDotDotDot, vm.ShowTransportStreamSettingsCommand, 28); + buttonTransportStreamSettings.WithBindIsVisible(vm, nameof(vm.IsTransportStreamSettingsVisible)); + + var target = new StackPanel + { + Orientation = Orientation.Horizontal, + Spacing = 6, + VerticalAlignment = VerticalAlignment.Center, + Children = + { + EditorChrome.MakeDimLabel(Se.Language.General.TargetFormat, new Thickness(0, 0, 2, 0)), + comboBoxSubtitleFormat, + buttonTargetFormatSettings, + buttonTransportStreamSettings, + } + }; + + // Output folder: "Source folder" as text, or the chosen folder as a link that opens it + var labelOutputSourceFolder = new TextBlock + { + FontSize = 12, + VerticalAlignment = VerticalAlignment.Center, + TextTrimming = TextTrimming.CharacterEllipsis, + [!TextBlock.TextProperty] = new Binding(nameof(vm.OutputFolderLabel)), + }; + var linkLabelOutputFolder = UiUtil.MakeLink(string.Empty, vm.OpenOutputFolderCommand, vm, nameof(vm.OutputFolderLinkLabel)) + .WithAlignmentLeft(); + linkLabelOutputFolder.FontSize = 12; + linkLabelOutputFolder.TextTrimming = TextTrimming.CharacterEllipsis; + var folder = new Panel { VerticalAlignment = VerticalAlignment.Center, Children = { labelOutputSourceFolder, linkLabelOutputFolder } }; + var encoding = VideoSubDialogChrome.MakeHint(nameof(vm.OutputEncodingLabel)); + + var output = new Grid + { + ColumnSpacing = 6, + ColumnDefinitions = { new ColumnDefinition(GridLength.Auto), new ColumnDefinition(new GridLength(1, GridUnitType.Star)) }, + RowDefinitions = { new RowDefinition(GridLength.Auto), new RowDefinition(GridLength.Auto) }, + VerticalAlignment = VerticalAlignment.Center, + }; + output.Add(new Optris.Icons.Avalonia.Icon { Value = "fa-solid fa-folder", FontSize = 12, Foreground = EditorChrome.FaintTextBrush, VerticalAlignment = VerticalAlignment.Center }, 0, 0); + output.Add(folder, 0, 1); + output.Add(encoding, 1, 1); + + var buttonSettings = EditorChrome.CreateToolButton("fa-solid fa-sliders", Se.Language.General.Settings, vm.ShowOutputPropertiesCommand, 28); + var row = new Grid + { + ColumnSpacing = 8, + ColumnDefinitions = + { + new ColumnDefinition(GridLength.Auto), + new ColumnDefinition(GridLength.Auto), + new ColumnDefinition(new GridLength(1, GridUnitType.Star)), + new ColumnDefinition(GridLength.Auto), + }, + }; + row.Add(target, 0, 0); + row.Add(EditorChrome.MakeToolbarSeparator(), 0, 1); + row.Add(output, 0, 2); + row.Add(buttonSettings, 0, 3); + return row; + } + + private static Border MakeBusyOverlay(BatchConvertViewModel vm) + { var fileNameLabel = new TextBlock { HorizontalAlignment = HorizontalAlignment.Center, MaxWidth = 360, TextTrimming = TextTrimming.CharacterEllipsis, - Opacity = 0.8, + FontSize = 12, + Foreground = EditorChrome.DimTextBrush, Margin = new Thickness(0, 6, 0, 0), }; fileNameLabel.Bind(TextBlock.TextProperty, new Binding(nameof(vm.AddingFilesStatus)) { Source = vm }); @@ -423,10 +489,10 @@ private static Border MakeBusyOverlay(BatchConvertViewModel vm) var card = new Border { - Background = cardBackground, - BorderBrush = UiUtil.GetBorderBrush(), + Background = EditorChrome.PanelBrush, + BorderBrush = EditorChrome.LineBrush, BorderThickness = new Thickness(1), - CornerRadius = new CornerRadius(8), + CornerRadius = new CornerRadius(10), Padding = new Thickness(30, 22), HorizontalAlignment = HorizontalAlignment.Center, VerticalAlignment = VerticalAlignment.Center, @@ -438,6 +504,7 @@ private static Border MakeBusyOverlay(BatchConvertViewModel vm) new TextBlock { Text = Se.Language.General.PleaseWait, + FontWeight = FontWeight.SemiBold, HorizontalAlignment = HorizontalAlignment.Center, }, fileNameLabel, @@ -450,6 +517,7 @@ private static Border MakeBusyOverlay(BatchConvertViewModel vm) var overlay = new Border { Background = new SolidColorBrush(Colors.Black, 0.35), + CornerRadius = new CornerRadius(8), IsVisible = false, Child = card, }; @@ -458,64 +526,75 @@ private static Border MakeBusyOverlay(BatchConvertViewModel vm) return overlay; } - private static Grid MakeOutputPropertiesGrid(BatchConvertViewModel vm) + /// + /// The function checklist (with the presets above it) and the selected function's settings + /// beside it, on the panel color. + /// + private static Border MakeFunctionsPanel(BatchConvertViewModel vm) { - var grid = new Grid + var comboBoxPreset = UiUtil.MakeComboBox(vm.PresetNames, vm, nameof(vm.SelectedPresetName)); + comboBoxPreset.HorizontalAlignment = HorizontalAlignment.Stretch; + comboBoxPreset.PlaceholderText = Se.Language.Tools.BatchConvert.Preset; + comboBoxPreset.WithAccessibleName(Se.Language.Tools.BatchConvert.Preset); + var buttonSavePreset = EditorChrome.CreateToolButton("fa-solid fa-floppy-disk", Se.Language.Tools.BatchConvert.SavePresetDotDotDot, vm.SavePresetCommand, 28); + var buttonDeletePreset = EditorChrome.CreateToolButton("fa-solid fa-trash", Se.Language.Tools.BatchConvert.DeletePreset, vm.DeletePresetCommand, 28); + var panelPresets = new Grid { + ColumnSpacing = 4, ColumnDefinitions = { - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Auto) }, - }, - RowDefinitions = - { - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, + new ColumnDefinition(GridLength.Auto), + new ColumnDefinition(new GridLength(1, GridUnitType.Star)), + new ColumnDefinition(GridLength.Auto), + new ColumnDefinition(GridLength.Auto), }, - ColumnSpacing = 0, - RowSpacing = 0, }; + panelPresets.Add(EditorChrome.MakeDimLabel(Se.Language.Tools.BatchConvert.Preset, new Thickness(0, 0, 4, 0)), 0, 0); + panelPresets.Add(comboBoxPreset, 0, 1); + panelPresets.Add(buttonSavePreset, 0, 2); + panelPresets.Add(buttonDeletePreset, 0, 3); - var labelOutputSourceFolder = UiUtil.MakeLabel(new Binding(nameof(vm.OutputFolderLabel))); - var linkLabelOutputFolder = UiUtil.MakeLink(string.Empty, vm.OpenOutputFolderCommand, vm, nameof(vm.OutputFolderLinkLabel)) - .WithAlignmentLeft(); - var labelOutputEncoding = UiUtil.MakeLabel(new Binding(nameof(vm.OutputEncodingLabel))).WithAlignmentTop(); + var functions = new Grid + { + RowSpacing = 8, + RowDefinitions = { new RowDefinition(GridLength.Auto), new RowDefinition(new GridLength(1, GridUnitType.Star)) }, + }; + functions.Add(panelPresets, 0, 0); + functions.Add(MakeFunctionsList(vm), 1, 0); - grid.Add(labelOutputSourceFolder, 0); - grid.Add(linkLabelOutputFolder, 0); - grid.Add(labelOutputEncoding, 1); + // "N actions selected" beside the header + var actionsSelected = VideoSubDialogChrome.MakeHint(nameof(vm.ActionsSelected)); + var left = new Border + { + Child = MakeFillSection(Se.Language.General.Options, functions, actionsSelected), + BorderBrush = EditorChrome.LineBrush, + BorderThickness = new Thickness(0, 0, 1, 0), + Padding = new Thickness(14, 12, 14, 14), + }; - return grid; - } + var right = new Border + { + Child = MakeFillSection(Se.Language.General.Settings, MakeFunctionView(vm)), + Padding = new Thickness(14, 12, 14, 14), + }; - private static Control MakeFunctionsListView(BatchConvertViewModel vm) - { - var comboBoxPreset = UiUtil.MakeComboBox(vm.PresetNames, vm, nameof(vm.SelectedPresetName)); - comboBoxPreset.Width = 150; - comboBoxPreset.PlaceholderText = Se.Language.Tools.BatchConvert.Preset; - comboBoxPreset.WithAccessibleName(Se.Language.Tools.BatchConvert.Preset); - var buttonSavePreset = UiUtil.MakeButton(Se.Language.Tools.BatchConvert.SavePresetDotDotDot, vm.SavePresetCommand); - var buttonDeletePreset = UiUtil.MakeButton(Se.Language.General.Delete, vm.DeletePresetCommand); - var panelPresets = new StackPanel + var columns = new Grid { - Orientation = Orientation.Horizontal, - Spacing = 5, - Margin = new Thickness(0, 0, 0, 5), - Children = + ColumnDefinitions = { - comboBoxPreset, - buttonSavePreset, - buttonDeletePreset, - } + new ColumnDefinition(GridLength.Auto), + new ColumnDefinition(new GridLength(1, GridUnitType.Star)), + }, }; + columns.Add(left, 0, 0); + columns.Add(right, 0, 1); - return new StackPanel + return new Border { - Orientation = Orientation.Vertical, - Children = - { - panelPresets, - MakeFunctionsList(vm), - } + Child = columns, + Background = EditorChrome.PanelBrush, + BorderBrush = EditorChrome.LineBrush, + BorderThickness = new Thickness(0, 1, 0, 0), }; } @@ -530,7 +609,7 @@ private static Border MakeFunctionsList(BatchConvertViewModel vm) // with a star column measured without a width constraint demands more than // the whole window (star columns have no content-based size). dataGrid.Width = FunctionsListMinWidth; - dataGrid.Height = 300; + dataGrid.Height = double.NaN; // fills the functions panel // Widen to the longest function name once the effective font is known - 360 fits English, // but Russian/Bulgarian/French names are cut off (#15901). dataGrid.Loaded += (_, _) => dataGrid.Width = GetFunctionsListWidth(dataGrid, vm.BatchFunctions.Select(f => f.Name)); @@ -593,7 +672,9 @@ private static Border MakeFunctionsList(BatchConvertViewModel vm) item => item.IsSelected, (item, v) => item.IsSelected = v); UiUtil.AttachMacContextFlyoutHandler(dataGrid); - return UiUtil.MakeBorderForControl(dataGrid); + var frame = VideoSubDialogChrome.MakeListFrame(dataGrid); + frame.HorizontalAlignment = HorizontalAlignment.Left; + return frame; } private const double FunctionsListMinWidth = 360; @@ -641,17 +722,9 @@ private static Border MakeFunctionView(BatchConvertViewModel vm) { HorizontalScrollBarVisibility = ScrollBarVisibility.Auto, VerticalScrollBarVisibility = ScrollBarVisibility.Auto, - Padding = new Thickness(10, 15, 10, 10), + Padding = new Thickness(12, 10, 12, 10), Width = double.NaN, - Height = 300, - }; - - var border = new Border - { - BorderThickness = new Thickness(1), - BorderBrush = UiUtil.GetBorderBrush(), - Margin = new Thickness(0, 0, 0, 10), - Padding = new Thickness(5), + Height = double.NaN, // fills the functions panel }; vm.FunctionContainer = scrollViewer; @@ -665,6 +738,13 @@ private static Border MakeFunctionView(BatchConvertViewModel vm) } }; - return UiUtil.MakeBorderForControl(scrollViewer); + return new Border + { + Child = scrollViewer, + Background = EditorChrome.WindowBrush, + BorderBrush = EditorChrome.FieldBorderBrush, + BorderThickness = new Thickness(1), + CornerRadius = new CornerRadius(8), + }; } } diff --git a/src/ui/Features/Tools/BatchConvert/BatchErrorList/BatchErrorListWindow.cs b/src/ui/Features/Tools/BatchConvert/BatchErrorList/BatchErrorListWindow.cs index 3ae3ff9edcc..1e920b5d5b4 100644 --- a/src/ui/Features/Tools/BatchConvert/BatchErrorList/BatchErrorListWindow.cs +++ b/src/ui/Features/Tools/BatchConvert/BatchErrorList/BatchErrorListWindow.cs @@ -8,6 +8,7 @@ using Avalonia.Layout; using Avalonia.Media; using Nikse.SubtitleEdit.Features.Tools.BatchConvert.BatchErrorList; +using Nikse.SubtitleEdit.Features.Video; using Nikse.SubtitleEdit.Logic; using Nikse.SubtitleEdit.Logic.Config; using System.Collections; @@ -24,6 +25,7 @@ public class BatchErrorListWindow : Window public BatchErrorListWindow(BatchErrorListViewModel vm) { UiUtil.InitializeWindow(this, GetType().Name); + VideoSubDialogChrome.Apply(this); Title = Se.Language.ErrorList.Title; CanResize = true; Width = 1100; @@ -38,34 +40,40 @@ public BatchErrorListWindow(BatchErrorListViewModel vm) var labelTitle = new TextBlock { Text = l.Title, - FontSize = UiUtil.ScaledFontSize(20), - FontWeight = FontWeight.Bold, + FontSize = UiUtil.ScaledFontSize(18), + FontWeight = FontWeight.SemiBold, }; var labelSummary = new TextBlock { - FontSize = UiUtil.ScaledFontSize(14), - Opacity = 0.75, + FontSize = UiUtil.ScaledFontSize(13), + Foreground = EditorChrome.DimTextBrush, Margin = new Thickness(0, 4, 0, 0), [!TextBlock.TextProperty] = new Binding(nameof(vm.Summary)), }; var header = new StackPanel { Orientation = Orientation.Vertical, - Margin = new Thickness(0, 0, 0, 12), Children = { labelTitle, labelSummary }, }; var cards = SummaryCard.MakeCardsPanel(vm.Cards, vm.SetFilterCommand); - var buttonExport = UiUtil.MakeButton(Se.Language.General.ExportDotDotDot, vm.ExportCommand).WithBindIsEnabled(nameof(vm.HasErrors)); + var top = new StackPanel + { + Orientation = Orientation.Vertical, + Spacing = 12, + Children = { header, cards }, + }; + + var buttonExport = VideoSubDialogChrome.MakeAction("fa-solid fa-file-export", Se.Language.General.ExportDotDotDot, vm.ExportCommand) + .WithBindIsEnabled(nameof(vm.HasErrors)); var buttonCancel = UiUtil.MakeButtonDone(vm.CancelCommand); - var panelButtons = UiUtil.MakeButtonBar(buttonExport, buttonCancel); + buttonCancel.Classes.Add("primary"); var grid = new Grid { RowDefinitions = { - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, new RowDefinition { Height = new GridLength(1, GridUnitType.Star) }, new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, @@ -74,14 +82,18 @@ public BatchErrorListWindow(BatchErrorListViewModel vm) { new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) }, }, - Margin = UiUtil.MakeWindowMargin(), - RowSpacing = 10, }; - grid.Add(header, 0, 0); - grid.Add(cards, 1, 0); - grid.Add(MakeErrorsGridView(vm), 2, 0); - grid.Add(panelButtons, 3, 0); + grid.Add(new Border + { + Child = top, + Background = EditorChrome.PanelBrush, + BorderBrush = EditorChrome.LineBrush, + BorderThickness = new Thickness(0, 0, 0, 1), + Padding = new Thickness(16, 14, 16, 14), + }, 0, 0); + grid.Add(new Border { Child = MakeErrorsGridView(vm), Padding = new Thickness(14, 12, 14, 14) }, 1, 0); + grid.Add(VideoSubDialogChrome.MakeButtonBar(buttonExport, buttonCancel), 2, 0); Content = grid; @@ -189,6 +201,6 @@ private static Border MakeErrorsGridView(BatchErrorListViewModel vm) } }, RoutingStrategies.Tunnel); - return UiUtil.MakeBorderForControlNoPadding(dataGrid); + return VideoSubDialogChrome.MakeListFrame(dataGrid); } } From 4f0ede205024c5b3a9c2917ef84f4a1a0935b082 Mon Sep 17 00:00:00 2001 From: niksedk Date: Thu, 15 Oct 2026 19:49:10 +0200 Subject: [PATCH 213/306] AI review and improve time codes windows in the editor look Co-Authored-By: Claude Opus 5.5 --- .../Tools/AiReview/AiReviewPromptWindow.cs | 184 +++++--- .../Features/Tools/AiReview/AiReviewWindow.cs | 392 +++++++++++------- .../ImproveTimeCodesWindow.cs | 193 +++++---- .../Config/Language/Tools/LanguageAiReview.cs | 2 + 4 files changed, 489 insertions(+), 282 deletions(-) diff --git a/src/ui/Features/Tools/AiReview/AiReviewPromptWindow.cs b/src/ui/Features/Tools/AiReview/AiReviewPromptWindow.cs index 57b264487a4..5f8b44a6b7d 100644 --- a/src/ui/Features/Tools/AiReview/AiReviewPromptWindow.cs +++ b/src/ui/Features/Tools/AiReview/AiReviewPromptWindow.cs @@ -3,11 +3,17 @@ using Avalonia.Data; using Avalonia.Layout; using Avalonia.Media; +using Nikse.SubtitleEdit.Features.Video; using Nikse.SubtitleEdit.Logic; using Nikse.SubtitleEdit.Logic.Config; namespace Nikse.SubtitleEdit.Features.Tools.AiReview; +/// +/// The AI review prompt editor in the editor look (see ): the prompt +/// and the reference context as two sections, the JSON contract note under them, Reset/Cancel/OK +/// in the button bar. +/// public class AiReviewPromptWindow : Window { public AiReviewPromptWindow(AiReviewPromptViewModel vm) @@ -19,28 +25,17 @@ public AiReviewPromptWindow(AiReviewPromptViewModel vm) Height = 700; MinHeight = 520; CanResize = true; + // The Edit dialogs' styles: compact fields that keep the field color while focused + Features.Edit.EditDialogChrome.Apply(this); vm.Window = this; DataContext = vm; var l = Se.Language.Tools.AiReview; - var labelInfo = UiUtil.MakeTextBlock(l.PromptInfo); - labelInfo.TextWrapping = TextWrapping.Wrap; - labelInfo.Opacity = 0.75; - - var textBox = new TextBox - { - AcceptsReturn = true, - TextWrapping = TextWrapping.Wrap, - MinHeight = 140, - HorizontalAlignment = HorizontalAlignment.Stretch, - VerticalAlignment = VerticalAlignment.Stretch, - [!TextBox.TextProperty] = new Binding(nameof(vm.PromptText)) { Mode = BindingMode.TwoWay }, - }; + // ---------- prompt ---------- + var textBox = MakeMultiLineTextBox(nameof(vm.PromptText), 140); Avalonia.Automation.AutomationProperties.SetName(textBox, l.EditPromptTitle); - var labelTemplate = UiUtil.MakeTextBlock(l.Template); - labelTemplate.VerticalAlignment = VerticalAlignment.Center; var comboTemplate = new ComboBox { ItemsSource = vm.Templates, @@ -54,90 +49,95 @@ public AiReviewPromptWindow(AiReviewPromptViewModel vm) { Orientation = Orientation.Horizontal, Spacing = 8, - Children = { labelTemplate, comboTemplate }, + Children = { EditorChrome.MakeDimLabel(l.Template, new Thickness(0)), comboTemplate }, }; - var labelContext = UiUtil.MakeTextBlock(l.ReferenceContext); - labelContext.FontWeight = FontWeight.SemiBold; - labelContext.VerticalAlignment = VerticalAlignment.Center; + var promptSection = MakeSection(l.Prompt, panelTemplate, l.PromptInfo, textBox, first: true); + + // ---------- reference context ---------- var buttonGenerate = UiUtil.MakeButton(l.GenerateContext, vm.GenerateContextCommand); buttonGenerate.Bind(Button.ContentProperty, new Binding(nameof(vm.GenerateButtonText))); buttonGenerate.Bind(IsVisibleProperty, new Binding(nameof(vm.CanGenerate))); + buttonGenerate.MinHeight = 28; + buttonGenerate.Height = 28; + buttonGenerate.Padding = new Thickness(10, 0); + buttonGenerate.FontSize = 12.5; + buttonGenerate.VerticalContentAlignment = VerticalAlignment.Center; if (Se.Settings.Appearance.ShowHints) { ToolTip.SetTip(buttonGenerate, l.GenerateContextHint); } - var labelGenerateStatus = UiUtil.MakeTextBlock(string.Empty); - labelGenerateStatus.Opacity = 0.75; - labelGenerateStatus.VerticalAlignment = VerticalAlignment.Center; - labelGenerateStatus.Bind(TextBlock.TextProperty, new Binding(nameof(vm.GenerateStatus))); - var panelContextHeader = new StackPanel + var labelGenerateStatus = VideoSubDialogChrome.MakeHint(nameof(vm.GenerateStatus)); + labelGenerateStatus.FontSize = 12; + labelGenerateStatus.MaxWidth = 260; + var panelGenerate = new StackPanel { Orientation = Orientation.Horizontal, Spacing = 10, - Children = { labelContext, buttonGenerate, labelGenerateStatus }, + Children = { labelGenerateStatus, buttonGenerate }, }; - var labelContextInfo = UiUtil.MakeTextBlock(l.ReferenceContextInfo); - labelContextInfo.TextWrapping = TextWrapping.Wrap; - labelContextInfo.Opacity = 0.75; - - var textBoxContext = new TextBox - { - AcceptsReturn = true, - TextWrapping = TextWrapping.Wrap, - MinHeight = 90, - PlaceholderText = l.ReferenceContextPlaceholder, - HorizontalAlignment = HorizontalAlignment.Stretch, - VerticalAlignment = VerticalAlignment.Stretch, - [!TextBox.TextProperty] = new Binding(nameof(vm.ContextText)) { Mode = BindingMode.TwoWay }, - [!TextBox.IsReadOnlyProperty] = new Binding(nameof(vm.IsGenerating)), - }; + var textBoxContext = MakeMultiLineTextBox(nameof(vm.ContextText), 90); + textBoxContext.PlaceholderText = l.ReferenceContextPlaceholder; + textBoxContext[!TextBox.IsReadOnlyProperty] = new Binding(nameof(vm.IsGenerating)); Avalonia.Automation.AutomationProperties.SetName(textBoxContext, l.ReferenceContext); - var labelProtocol = UiUtil.MakeTextBlock(l.ProtocolInfo); - labelProtocol.TextWrapping = TextWrapping.Wrap; - labelProtocol.Opacity = 0.6; - labelProtocol.FontSize = UiUtil.ScaledFontSize(12); + var contextSection = MakeSection(l.ReferenceContext, panelGenerate, l.ReferenceContextInfo, textBoxContext, first: false); + + // ---------- protocol note ---------- + var labelProtocol = VideoSubDialogChrome.MakeHintText(l.ProtocolInfo); var borderProtocol = new Border { - BorderBrush = UiUtil.GetBorderBrush(), - BorderThickness = new Thickness(1), - CornerRadius = new CornerRadius(6), - Padding = new Thickness(10), - Child = labelProtocol, + BorderBrush = EditorChrome.LineBrush, + BorderThickness = new Thickness(0, 1, 0, 0), + Padding = new Thickness(14, 10, 14, 12), + Child = new Grid + { + ColumnDefinitions = new ColumnDefinitions("Auto,*"), + ColumnSpacing = 8, + Children = + { + new Optris.Icons.Avalonia.Icon + { + Value = "fa-solid fa-circle-info", + FontSize = 12, + Foreground = EditorChrome.FaintTextBrush, + VerticalAlignment = VerticalAlignment.Top, + Margin = new Thickness(0, 2, 0, 0), + }, + labelProtocol, + }, + }, }; + Grid.SetColumn(labelProtocol, 1); - var buttonReset = UiUtil.MakeButton(l.ResetToDefault, vm.ResetToDefaultCommand); + // ---------- buttons ---------- + var buttonReset = VideoSubDialogChrome.MakeAction("fa-solid fa-rotate-left", l.ResetToDefault, vm.ResetToDefaultCommand); var buttonOk = UiUtil.MakeButtonOk(vm.OkCommand); + buttonOk.Classes.Add("primary"); var buttonCancel = UiUtil.MakeButtonCancel(vm.CancelCommand); - var buttonBar = new Grid - { - ColumnDefinitions = new ColumnDefinitions("Auto,*,Auto"), - }; - buttonBar.Add(buttonReset, 0, 0); - buttonBar.Add(UiUtil.MakeButtonBar(buttonOk, buttonCancel), 0, 2); + var buttonBar = VideoSubDialogChrome.MakeButtonBar(buttonReset, buttonCancel, buttonOk); // The prompt editor takes the star row so a long prompt scrolls inside the // text box instead of growing the window past the screen edge and pushing // the OK/Cancel buttons out of reach. - var panel = new Grid + var sections = new Grid { - Margin = UiUtil.MakeWindowMargin(), - RowDefinitions = new RowDefinitions("Auto,Auto,3*,Auto,Auto,2*,Auto,Auto"), - RowSpacing = 10, + RowDefinitions = new RowDefinitions("3*,2*,Auto"), }; - panel.Add(labelInfo, 0, 0); - panel.Add(panelTemplate, 1, 0); - panel.Add(textBox, 2, 0); - panel.Add(panelContextHeader, 3, 0); - panel.Add(labelContextInfo, 4, 0); - panel.Add(textBoxContext, 5, 0); - panel.Add(borderProtocol, 6, 0); - panel.Add(buttonBar, 7, 0); + sections.Add(promptSection, 0, 0); + sections.Add(contextSection, 1, 0); + sections.Add(borderProtocol, 2, 0); - Content = panel; + var root = new Grid + { + RowDefinitions = new RowDefinitions("*,Auto"), + }; + root.Add(VideoSubDialogChrome.MakePanel(sections), 0, 0); + root.Add(buttonBar, 1, 0); + + Content = root; Loaded += delegate { @@ -151,4 +151,50 @@ public AiReviewPromptWindow(AiReviewPromptViewModel vm) }; KeyDown += (_, e) => vm.OnKeyDown(e); } + + private static TextBox MakeMultiLineTextBox(string property, double minHeight) + { + var textBox = new TextBox + { + AcceptsReturn = true, + TextWrapping = TextWrapping.Wrap, + MinHeight = minHeight, + HorizontalAlignment = HorizontalAlignment.Stretch, + VerticalAlignment = VerticalAlignment.Stretch, + VerticalContentAlignment = VerticalAlignment.Top, + Padding = new Thickness(8, 6), + [!TextBox.TextProperty] = new Binding(property) { Mode = BindingMode.TwoWay }, + }; + textBox.Classes.Add("text"); + return textBox; + } + + /// + /// A section whose text box takes the rest of its height: header (with actions on the right), + /// a dim explanation, then the text box. + /// + private static Border MakeSection(string title, Control headerRight, string info, TextBox textBox, bool first) + { + var header = new DockPanel { MinHeight = 28 }; + DockPanel.SetDock(headerRight, Dock.Right); + header.Children.Add(headerRight); + header.Children.Add(EditorChrome.MakeSectionHeader(title)); + + var grid = new Grid + { + RowDefinitions = new RowDefinitions("Auto,Auto,*"), + RowSpacing = 8, + }; + grid.Add(header, 0, 0); + grid.Add(VideoSubDialogChrome.MakeHintText(info), 1, 0); + grid.Add(textBox, 2, 0); + + return new Border + { + Child = grid, + BorderBrush = EditorChrome.LineBrush, + BorderThickness = new Thickness(0, first ? 0 : 1, 0, 0), + Padding = new Thickness(14, 12, 14, 14), + }; + } } diff --git a/src/ui/Features/Tools/AiReview/AiReviewWindow.cs b/src/ui/Features/Tools/AiReview/AiReviewWindow.cs index 204a0c54505..078c85ab710 100644 --- a/src/ui/Features/Tools/AiReview/AiReviewWindow.cs +++ b/src/ui/Features/Tools/AiReview/AiReviewWindow.cs @@ -1,19 +1,30 @@ using Avalonia; using Avalonia.Automation; using Avalonia.Controls; +using Avalonia.Controls.Presenters; using Avalonia.Controls.Primitives; using Avalonia.Controls.Templates; using Avalonia.Data; +using Avalonia.Data.Converters; using Avalonia.Layout; using Avalonia.Media; +using Avalonia.Styling; +using System.Linq; using Nikse.SubtitleEdit.Features.Files.Compare; using Nikse.SubtitleEdit.Features.Main; +using Nikse.SubtitleEdit.Features.Video; using Nikse.SubtitleEdit.Logic; using Nikse.SubtitleEdit.Logic.Config; using Nikse.SubtitleEdit.Logic.Platform.Progress; namespace Nikse.SubtitleEdit.Features.Tools.AiReview; +/// +/// AI review in the editor look (see ): engine and model in a toolbar +/// on top, the category filter chips and the suggestions list in the middle, the reason and the +/// neighbouring lines of the selected suggestion under it, then the status/progress strip and +/// the button bar. +/// public class AiReviewWindow : Window { public AiReviewWindow(AiReviewViewModel vm) @@ -25,6 +36,9 @@ public AiReviewWindow(AiReviewViewModel vm) MinWidth = 900; MinHeight = 500; CanResize = true; + // The Edit dialogs' styles: compact fields that keep the field color while focused + Features.Edit.EditDialogChrome.Apply(this); + AddChipStyles(Styles); vm.Window = this; DataContext = vm; @@ -37,41 +51,52 @@ public AiReviewWindow(AiReviewViewModel vm) // Ollama may run on another machine or port, so the URL is editable here like in // auto-translate - it was only reachable by hand-editing Settings.json before. - var textBoxOllamaUrl = UiUtil.MakeTextBox(250, vm, nameof(vm.OllamaUrl)) - .WithAccessibleName(Se.Language.General.Url); + var textBoxOllamaUrl = Field(UiUtil.MakeTextBox(250, vm, nameof(vm.OllamaUrl)) + .WithAccessibleName(Se.Language.General.Url)); textBoxOllamaUrl.PlaceholderText = Se.Language.General.Url; - var textBoxOllamaModel = UiUtil.MakeTextBox(220, vm, nameof(vm.OllamaModel)) - .WithAccessibleName(Se.Language.General.Model); - var buttonPickOllamaModel = UiUtil.MakeButton("...", vm.PickOllamaModelCommand) - .Compact() - .WithAccessibleName(Se.Language.General.PickOllamaModel); + var textBoxOllamaModel = Field(UiUtil.MakeTextBox(220, vm, nameof(vm.OllamaModel)) + .WithAccessibleName(Se.Language.General.Model)); + var buttonPickOllamaModel = EditorChrome.CreateToolButton("fa-solid fa-ellipsis", Se.Language.General.PickOllamaModel, vm.PickOllamaModelCommand, 28); var panelOllama = new StackPanel { Orientation = Orientation.Horizontal, - Spacing = 5, + Spacing = 6, VerticalAlignment = VerticalAlignment.Center, Children = { textBoxOllamaUrl, textBoxOllamaModel, buttonPickOllamaModel }, }; panelOllama.Bind(IsVisibleProperty, new Binding(nameof(vm.IsOllamaVisible))); - var textBoxOpenAiUrl = UiUtil.MakeTextBox(250, vm, nameof(vm.OpenAiCompatibleUrl)) - .WithAccessibleName(Se.Language.General.Url); - var textBoxOpenAiModel = UiUtil.MakeTextBox(150, vm, nameof(vm.OpenAiCompatibleModel)) - .WithAccessibleName(Se.Language.General.Model); + var textBoxOpenAiUrl = Field(UiUtil.MakeTextBox(250, vm, nameof(vm.OpenAiCompatibleUrl)) + .WithAccessibleName(Se.Language.General.Url)); + var textBoxOpenAiModel = Field(UiUtil.MakeTextBox(150, vm, nameof(vm.OpenAiCompatibleModel)) + .WithAccessibleName(Se.Language.General.Model)); textBoxOpenAiModel.PlaceholderText = Se.Language.General.Model; var panelOpenAiApiKey = UiUtil.MakeApiKeyTextBox(130, vm, nameof(vm.OpenAiCompatibleApiKey), placeholderText: Se.Language.General.ApiKey); + foreach (var textBox in panelOpenAiApiKey.Children.OfType()) + { + Field(textBox); + } + + foreach (var button in panelOpenAiApiKey.Children.OfType public class WaveformGuessTimeCodesWindow : Window { @@ -25,20 +27,19 @@ public WaveformGuessTimeCodesWindow(WaveformGuessTimeCodesViewModel vm) Title = Se.Language.Waveform.GuessTimeCodes; CanResize = false; SizeToContent = SizeToContent.WidthAndHeight; + SharedDialogChrome.Apply(this); vm.Window = this; DataContext = vm; - var buttonOk = UiUtil.MakeButtonOk(vm.OkCommand); + var buttonOk = SharedDialogChrome.Primary(UiUtil.MakeButtonOk(vm.OkCommand)); var buttonCancel = UiUtil.MakeButtonCancel(vm.CancelCommand); - var panelButtons = UiUtil.MakeButtonBar(buttonOk, buttonCancel); - // Two columns: the two short "which lines" boxes stacked on the left, the taller settings - // box on the right - about half the height of one stacked column, which is what keeps the - // window on screen at a high UI scale. + // Two columns: the two short "which lines" sections stacked on the left, the taller + // settings section on the right - about half the height of one stacked column, which is + // what keeps the window on screen at a high UI scale. var panelLeft = new StackPanel { Orientation = Orientation.Vertical, - Spacing = 10, VerticalAlignment = VerticalAlignment.Top, Children = { @@ -58,11 +59,14 @@ public WaveformGuessTimeCodesWindow(WaveformGuessTimeCodesViewModel vm) new ColumnDefinition { Width = new GridLength(1, GridUnitType.Auto) }, new ColumnDefinition { Width = new GridLength(1, GridUnitType.Auto) }, }, - ColumnSpacing = 10, }; panelOptions.Add(panelLeft, 0); - var borderDetectOptions = MakeDetectOptionsView(vm); - borderDetectOptions.VerticalAlignment = VerticalAlignment.Top; + var borderDetectOptions = new Border + { + Child = MakeDetectOptionsView(vm), + BorderBrush = EditorChrome.LineBrush, + BorderThickness = new Thickness(1, 0, 0, 0), + }; panelOptions.Add(borderDetectOptions, 0, 1); // Keeps the OK/Cancel buttons reachable when the window is clamped to the working area. @@ -74,28 +78,7 @@ public WaveformGuessTimeCodesWindow(WaveformGuessTimeCodesViewModel vm) VerticalScrollBarVisibility = ScrollBarVisibility.Auto, }; - var grid = new Grid - { - RowDefinitions = - { - new RowDefinition { Height = new GridLength(1, GridUnitType.Star) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - }, - ColumnDefinitions = - { - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Auto) }, - }, - Margin = UiUtil.MakeWindowMargin(), - ColumnSpacing = 10, - RowSpacing = 10, - Width = double.NaN, - HorizontalAlignment = HorizontalAlignment.Stretch, - }; - - grid.Add(scrollViewer, 0); - grid.Add(panelButtons, 1); - - Content = grid; + Content = SharedDialogChrome.MakeRoot(scrollViewer, SharedDialogChrome.MakeButtonBar(null, buttonCancel, buttonOk)); UiUtil.FocusOnFirstActivation(this, radioStartFromVideoPosition); // initial focus on an input, not an action button - a focused button clicks on bare Space KeyDown += vm.KeyDown; @@ -103,91 +86,80 @@ public WaveformGuessTimeCodesWindow(WaveformGuessTimeCodesViewModel vm) private static Border MakeStartFromView(WaveformGuessTimeCodesViewModel vm, out Control radioStartFromVideoPosition) { - var labelStartFrom = UiUtil.MakeLabel(Se.Language.General.StartFrom); - var checkBoxStartFromVideoPosition = UiUtil.MakeRadioButton(Se.Language.General.CurrentVideoPosition, vm, nameof(vm.StartFromVideoPosition), "start") - .WithMarginLeft(10); + var checkBoxStartFromVideoPosition = UiUtil.MakeRadioButton(Se.Language.General.CurrentVideoPosition, vm, nameof(vm.StartFromVideoPosition), "start"); radioStartFromVideoPosition = checkBoxStartFromVideoPosition; - var checkBoxStartFromBeginning = UiUtil.MakeRadioButton(Se.Language.General.Beginning, vm, nameof(vm.StartFromBeginning), "start") - .WithMarginLeft(10); + var checkBoxStartFromBeginning = UiUtil.MakeRadioButton(Se.Language.General.Beginning, vm, nameof(vm.StartFromBeginning), "start"); var stackPanel = new StackPanel { Orientation = Orientation.Vertical, - Spacing = 10, + Spacing = 2, Children = { - labelStartFrom, checkBoxStartFromVideoPosition, checkBoxStartFromBeginning, } }; - return UiUtil.MakeBorderForControl(stackPanel); + return SharedDialogChrome.MakeSection(Se.Language.General.StartFrom, stackPanel, first: true); } private static Border MakeDeleteLinesView(WaveformGuessTimeCodesViewModel vm) { - var labelDeleteLines = UiUtil.MakeLabel(Se.Language.General.DeleteLines); - var checkBoxDeleteAll = UiUtil.MakeRadioButton(Se.Language.General.All, vm, nameof(vm.DeleteLinesAll), "del") - .WithMarginLeft(10); - var checkBoxDeleteNone = UiUtil.MakeRadioButton(Se.Language.General.None, vm, nameof(vm.DeleteLinesNone), "del") - .WithMarginLeft(10); - var checkBoxDeleteFromVideoPosition = UiUtil.MakeRadioButton(Se.Language.General.FromCurrentVideoPosition, vm, nameof(vm.DeleteLinesFromVideoPosition), "del") - .WithMarginLeft(10); + var checkBoxDeleteAll = UiUtil.MakeRadioButton(Se.Language.General.All, vm, nameof(vm.DeleteLinesAll), "del"); + var checkBoxDeleteNone = UiUtil.MakeRadioButton(Se.Language.General.None, vm, nameof(vm.DeleteLinesNone), "del"); + var checkBoxDeleteFromVideoPosition = UiUtil.MakeRadioButton(Se.Language.General.FromCurrentVideoPosition, vm, nameof(vm.DeleteLinesFromVideoPosition), "del"); var stackPanel = new StackPanel { Orientation = Orientation.Vertical, - Spacing = 10, + Spacing = 2, Children = { - labelDeleteLines, checkBoxDeleteAll, checkBoxDeleteNone, checkBoxDeleteFromVideoPosition, } }; - return UiUtil.MakeBorderForControl(stackPanel); + return SharedDialogChrome.MakeSection(Se.Language.General.DeleteLines, stackPanel); } private static Border MakeDetectOptionsView(WaveformGuessTimeCodesViewModel vm) { - var labelSettings = UiUtil.MakeLabel(Se.Language.General.Settings); - - var labelScanBlockSize = UiUtil.MakeLabel(Se.Language.Waveform.GuessTimeCodesScanBlockSize); - var numeriUpDownScanBlockSize = UiUtil.MakeNumericUpDownInt(50, 5000, 100, 130, vm, nameof(vm.ScanBlockSize)); - var panelScanBlockSize = UiUtil.MakeHorizontalPanel(labelScanBlockSize, numeriUpDownScanBlockSize); - - var labelScanBlockAverageMin = UiUtil.MakeLabel(Se.Language.Waveform.GuessTimeCodesScanBlockAverageMin); - var numeriUpDownScanBlockAverageMin = UiUtil.MakeNumericUpDownInt(0, 100, 35, 130, vm, nameof(vm.ScanBlockAverageMin)); - var panelScanBlockAverageMin = UiUtil.MakeHorizontalPanel(labelScanBlockAverageMin, numeriUpDownScanBlockAverageMin); - - var labelScanBlockAverageMax = UiUtil.MakeLabel(Se.Language.Waveform.GuessTimeCodesScanBlockAverageMax); - var numeriUpDownScanBlockAverageMax = UiUtil.MakeNumericUpDownInt(0, 100, 70, 130, vm, nameof(vm.ScanBlockAverageMax)); - var panelScanBlockAverageMax = UiUtil.MakeHorizontalPanel(labelScanBlockAverageMax, numeriUpDownScanBlockAverageMax); - - var labelSpace = UiUtil.MakeLabel(); - - var labelSplitLongSubtitlesAt = UiUtil.MakeLabel(Se.Language.Waveform.GuessTimeCodesSplitLongSubtitlesAt); - var numeriUpDownSplitLongSubtitlesAt = UiUtil.MakeNumericUpDownInt(500, 20000, 3500, 130, vm, nameof(vm.SplitLongSubtitlesAtMs)); - var panelSplitLongSubtitlesAt = UiUtil.MakeHorizontalPanel(labelSplitLongSubtitlesAt, numeriUpDownSplitLongSubtitlesAt); + var grid = new Grid + { + ColumnDefinitions = + { + new ColumnDefinition { Width = GridLength.Auto }, + new ColumnDefinition { Width = GridLength.Auto }, + }, + ColumnSpacing = 12, + RowSpacing = 8, + }; - var stackPanel = new StackPanel + void AddRow(string label, NumericUpDown field, Thickness? margin = null) { - Orientation = Orientation.Vertical, - Spacing = 10, - Children = + var row = grid.RowDefinitions.Count; + grid.RowDefinitions.Add(new RowDefinition(GridLength.Auto)); + var text = EditorChrome.MakeDimLabel(label, margin ?? new Thickness(0)); + AutomationProperties.SetName(field, label); + if (margin != null) { - labelSettings, - panelScanBlockSize, - panelScanBlockAverageMin, - panelScanBlockAverageMax, - labelSpace, - panelSplitLongSubtitlesAt, + field.Margin = margin.Value; } - }; - return UiUtil.MakeBorderForControl(stackPanel); + grid.Add(text, row, 0); + grid.Add(field, row, 1); + } + + AddRow(Se.Language.Waveform.GuessTimeCodesScanBlockSize, UiUtil.MakeNumericUpDownInt(50, 5000, 100, 130, vm, nameof(vm.ScanBlockSize))); + AddRow(Se.Language.Waveform.GuessTimeCodesScanBlockAverageMin, UiUtil.MakeNumericUpDownInt(0, 100, 35, 130, vm, nameof(vm.ScanBlockAverageMin))); + AddRow(Se.Language.Waveform.GuessTimeCodesScanBlockAverageMax, UiUtil.MakeNumericUpDownInt(0, 100, 70, 130, vm, nameof(vm.ScanBlockAverageMax))); + + // A gap: splitting long subtitles is a step of its own, after the detection + AddRow(Se.Language.Waveform.GuessTimeCodesSplitLongSubtitlesAt, UiUtil.MakeNumericUpDownInt(500, 20000, 3500, 130, vm, nameof(vm.SplitLongSubtitlesAtMs)), new Thickness(0, 12, 0, 0)); + + return SharedDialogChrome.MakeSection(Se.Language.General.Settings, grid, first: true); } } diff --git a/src/ui/Features/Shared/WaveformSeekSilence/WaveformSeekSilenceWindow.cs b/src/ui/Features/Shared/WaveformSeekSilence/WaveformSeekSilenceWindow.cs index fef96971594..ffcfe205409 100644 --- a/src/ui/Features/Shared/WaveformSeekSilence/WaveformSeekSilenceWindow.cs +++ b/src/ui/Features/Shared/WaveformSeekSilence/WaveformSeekSilenceWindow.cs @@ -1,3 +1,5 @@ +using Avalonia; +using Avalonia.Automation; using Avalonia.Controls; using Avalonia.Layout; using Nikse.SubtitleEdit.Logic; @@ -5,67 +7,68 @@ namespace Nikse.SubtitleEdit.Features.Shared.WaveformSeekSilence; +/// +/// Seek silence, in the editor look (see ). +/// public class WaveformSeekSilenceWindow : Window { public WaveformSeekSilenceWindow(WaveformSeekSilenceViewModel vm) { UiUtil.InitializeWindow(this, GetType().Name); Title = Se.Language.Waveform.SeekSilence; - CanResize = true; CanResize = false; SizeToContent = SizeToContent.WidthAndHeight; + SharedDialogChrome.Apply(this); vm.Window = this; DataContext = vm; - var label = UiUtil.MakeLabel(Se.Language.General.SearchDirection); - - var checkBoxSeekForward = UiUtil.MakeRadioButton(Se.Language.General.Forward, vm, nameof(vm.SeekForward), "seek") - .WithMarginLeft(10); - var checkBoxSeekBackward = UiUtil.MakeRadioButton(Se.Language.General.Backward, vm, nameof(vm.SeekBackward), "seek") - .WithMarginLeft(10); + var checkBoxSeekForward = UiUtil.MakeRadioButton(Se.Language.General.Forward, vm, nameof(vm.SeekForward), "seek"); + var checkBoxSeekBackward = UiUtil.MakeRadioButton(Se.Language.General.Backward, vm, nameof(vm.SeekBackward), "seek"); + var panelDirection = new StackPanel + { + Orientation = Orientation.Horizontal, + Spacing = 16, + Children = { checkBoxSeekForward, checkBoxSeekBackward }, + }; - var labelMinSilenceDuration = UiUtil.MakeLabel(Se.Language.Waveform.MinSilenceDurationSeconds); var numericUpDownMinSilenceDuration = UiUtil.MakeNumericUpDownThreeDecimals(0, 10000, 140, vm, nameof(vm.SilenceMinDuration)); - var panelMinSilenceDuration = UiUtil.MakeHorizontalPanel(labelMinSilenceDuration, numericUpDownMinSilenceDuration); - - var labelMaxSilenceVolume = UiUtil.MakeLabel(Se.Language.Waveform.MaxSilenceVolume); + AutomationProperties.SetName(numericUpDownMinSilenceDuration, Se.Language.Waveform.MinSilenceDurationSeconds); var numericUpDownMaxSilenceVolume = UiUtil.MakeNumericUpDownTwoDecimals(0, 1, 140, vm, nameof(vm.SilenceMaxVolume)); - var panelMaxSilenceVolume = UiUtil.MakeHorizontalPanel(labelMaxSilenceVolume, numericUpDownMaxSilenceVolume); + AutomationProperties.SetName(numericUpDownMaxSilenceVolume, Se.Language.Waveform.MaxSilenceVolume); - var buttonOk = UiUtil.MakeButtonOk(vm.OkCommand); - var buttonCancel = UiUtil.MakeButtonCancel(vm.CancelCommand); - var panelButtons = UiUtil.MakeButtonBar(buttonOk, buttonCancel); - - var grid = new Grid + var gridSettings = new Grid { + ColumnDefinitions = + { + new ColumnDefinition { Width = GridLength.Auto }, + new ColumnDefinition { Width = GridLength.Auto }, + }, RowDefinitions = { - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, - new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, + new RowDefinition { Height = GridLength.Auto }, + new RowDefinition { Height = GridLength.Auto }, }, - ColumnDefinitions = + ColumnSpacing = 12, + RowSpacing = 8, + }; + gridSettings.Add(EditorChrome.MakeDimLabel(Se.Language.Waveform.MinSilenceDurationSeconds, new Thickness(0)), 0, 0); + gridSettings.Add(numericUpDownMinSilenceDuration, 0, 1); + gridSettings.Add(EditorChrome.MakeDimLabel(Se.Language.Waveform.MaxSilenceVolume, new Thickness(0)), 1, 0); + gridSettings.Add(numericUpDownMaxSilenceVolume, 1, 1); + + var sections = new StackPanel + { + Children = { - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Auto) }, + SharedDialogChrome.MakeSection(Se.Language.General.SearchDirection, panelDirection, first: true), + SharedDialogChrome.MakeSection(Se.Language.General.Settings, gridSettings), }, - Margin = UiUtil.MakeWindowMargin(), - ColumnSpacing = 10, - RowSpacing = 10, - Width = double.NaN, - HorizontalAlignment = HorizontalAlignment.Stretch, }; - grid.Add(label, 0); - grid.Add(checkBoxSeekForward, 1); - grid.Add(checkBoxSeekBackward, 2); - grid.Add(panelMinSilenceDuration, 3); - grid.Add(panelMaxSilenceVolume, 4); - grid.Add(panelButtons, 5); + var buttonOk = SharedDialogChrome.Primary(UiUtil.MakeButtonOk(vm.OkCommand)); + var buttonCancel = UiUtil.MakeButtonCancel(vm.CancelCommand); - Content = grid; + Content = SharedDialogChrome.MakeRoot(sections, SharedDialogChrome.MakeButtonBar(null, buttonCancel, buttonOk)); UiUtil.FocusOnFirstActivation(this, checkBoxSeekForward); // initial focus on an input, not an action button - a focused button clicks on bare Space KeyDown += vm.KeyDown; diff --git a/tests/UI/Features/Shared/WaveformGuessTimeCodesWindowTests.cs b/tests/UI/Features/Shared/WaveformGuessTimeCodesWindowTests.cs index 6c460c109aa..f3d3974b441 100644 --- a/tests/UI/Features/Shared/WaveformGuessTimeCodesWindowTests.cs +++ b/tests/UI/Features/Shared/WaveformGuessTimeCodesWindowTests.cs @@ -145,12 +145,13 @@ public void OptionBoxes_AreLaidOutInTwoColumns() window.UpdateLayout(); Dispatcher.UIThread.RunJobs(); + // The section headers are small caps (upper case) text blocks var settings = Assert.Single( - window.GetLogicalDescendants().OfType public class OpenOriginalMismatchWindow : Window { @@ -28,6 +32,7 @@ public OpenOriginalMismatchWindow(OpenOriginalMismatchViewModel vm) SizeToContent = SizeToContent.Height; Width = ContentWidth + 40; CanResize = false; + EditDialogChrome.Apply(this); vm.Window = this; DataContext = vm; @@ -52,29 +57,29 @@ public OpenOriginalMismatchWindow(OpenOriginalMismatchViewModel vm) nameof(vm.ShowAllOriginalLinesNote), out _); - var checkBoxAllowEdit = new CheckBox + var toggleAllowEdit = new ToggleSwitch { + OnContent = null, + OffContent = null, + MinWidth = 0, VerticalAlignment = VerticalAlignment.Center, - Content = Se.Language.Main.AllowEditOfOriginalSubtitle, [!ToggleButton.IsCheckedProperty] = new Binding(nameof(vm.AllowEditOfOriginal)) { Mode = BindingMode.TwoWay }, }; - AutomationProperties.SetName(checkBoxAllowEdit, Se.Language.Main.AllowEditOfOriginalSubtitle); + AutomationProperties.SetName(toggleAllowEdit, Se.Language.Main.AllowEditOfOriginalSubtitle); var allowEditRow = new StackPanel { Orientation = Orientation.Horizontal, Spacing = 8, - Margin = new Thickness(2, 4, 0, 0), Children = { - new Icon + toggleAllowEdit, + new TextBlock { - Value = IconNames.Pencil, - FontSize = 18, + Text = Se.Language.Main.AllowEditOfOriginalSubtitle, + FontSize = 12.5, VerticalAlignment = VerticalAlignment.Center, - IsHitTestVisible = false, }, - checkBoxAllowEdit, }, }; @@ -83,33 +88,44 @@ public OpenOriginalMismatchWindow(OpenOriginalMismatchViewModel vm) var allowEditHint = new TextBlock { TextWrapping = TextWrapping.Wrap, - MaxWidth = ContentWidth - 28, - Margin = new Thickness(28, 0, 0, 0), - Opacity = 0.75, - FontSize = UiUtil.ScaledFontSize(12), + MaxWidth = ContentWidth - 60, + Margin = new Thickness(60, 0, 0, 0), // under the label, past the switch + HorizontalAlignment = HorizontalAlignment.Left, + Foreground = EditorChrome.DimTextBrush, + FontSize = 12, [!TextBlock.TextProperty] = new Binding(nameof(vm.AllowEditHint)), }; - var buttonPanel = UiUtil.MakeButtonBar( - UiUtil.MakeButtonOk(vm.OkCommand), - UiUtil.MakeButtonCancel(vm.CancelCommand)); + var allowEdit = new StackPanel + { + Orientation = Orientation.Vertical, + Spacing = 2, + Children = { allowEditRow, allowEditHint }, + }; + + var buttonOk = UiUtil.MakeButtonOk(vm.OkCommand); + buttonOk.Classes.Add("primary"); + var buttonCancel = UiUtil.MakeButtonCancel(vm.CancelCommand); - Content = new StackPanel + var modes = new StackPanel { Orientation = Orientation.Vertical, Spacing = 10, - Margin = UiUtil.MakeWindowMargin(), + Children = { header, cardMatching, cardAll }, + }; + + var sections = new StackPanel + { + Orientation = Orientation.Vertical, Children = { - header, - cardMatching, - cardAll, - allowEditRow, - allowEditHint, - buttonPanel, + new Border { Child = modes, Padding = new Thickness(16, 16, 16, 14) }, + EditorChrome.MakeEditorSection(Se.Language.General.Options, allowEdit), }, }; + Content = SyncTimingChrome.MakeRoot(sections, SyncTimingChrome.MakeButtonBar(null, buttonCancel, buttonOk)); + UiUtil.FocusOnFirstActivation(this, radioMatching); // hack to make OnKeyDown work KeyDown += (_, e) => vm.OnKeyDown(e); } @@ -120,6 +136,7 @@ private static Control MakeHeader(OpenOriginalMismatchViewModel vm) { TextWrapping = TextWrapping.Wrap, MaxWidth = ContentWidth - 40, + FontSize = 13, VerticalAlignment = VerticalAlignment.Center, [!TextBlock.TextProperty] = new Binding(nameof(vm.InfoText)), }; @@ -133,7 +150,8 @@ private static Control MakeHeader(OpenOriginalMismatchViewModel vm) new Icon { Value = IconNames.Information, - FontSize = 26, + FontSize = 24, + Foreground = EditorChrome.AccentBrush, VerticalAlignment = VerticalAlignment.Top, IsHitTestVisible = false, }, @@ -173,7 +191,8 @@ private static Control MakeModeCard( new Icon { Value = modeIcon, - FontSize = 20, + FontSize = 18, + Foreground = EditorChrome.IconBrush, VerticalAlignment = VerticalAlignment.Center, IsHitTestVisible = false, }, @@ -186,8 +205,9 @@ private static Control MakeModeCard( TextWrapping = TextWrapping.Wrap, MaxWidth = ContentWidth - 60, Margin = new Thickness(28, 0, 0, 0), - Opacity = 0.8, - FontSize = UiUtil.ScaledFontSize(12), + HorizontalAlignment = HorizontalAlignment.Left, + Foreground = EditorChrome.DimTextBrush, + FontSize = 12, [!TextBlock.TextProperty] = new Binding(hintProperty), }; @@ -196,7 +216,7 @@ private static Control MakeModeCard( TextWrapping = TextWrapping.Wrap, MaxWidth = ContentWidth - 90, VerticalAlignment = VerticalAlignment.Center, - FontSize = UiUtil.ScaledFontSize(12), + FontSize = 12, [!TextBlock.TextProperty] = new Binding(noteProperty), }; @@ -205,13 +225,13 @@ private static Control MakeModeCard( Orientation = Orientation.Horizontal, Spacing = 6, Margin = new Thickness(28, 2, 0, 0), - Opacity = 0.9, Children = { new Icon { Value = noteIcon, - FontSize = 15, + FontSize = 14, + Foreground = EditorChrome.IconBrush, VerticalAlignment = VerticalAlignment.Center, IsHitTestVisible = false, }, @@ -219,11 +239,16 @@ private static Control MakeModeCard( }, }; + // The picked mode's card gets the accent border, so the choice shows on the whole card. return new Border { BorderThickness = new Thickness(1), - BorderBrush = new SolidColorBrush(Colors.Gray, 0.35), - CornerRadius = new CornerRadius(6), + Background = EditorChrome.FieldBrush, + [!Border.BorderBrushProperty] = new Binding(isCheckedProperty) + { + Converter = new FuncValueConverter(isChecked => isChecked ? EditorChrome.AccentBrush : EditorChrome.FieldBorderBrush), + }, + CornerRadius = new CornerRadius(8), Padding = new Thickness(12, 10), Child = new StackPanel { diff --git a/tests/UI/Features/Shared/ErrorList/ErrorListExportCommandTests.cs b/tests/UI/Features/Shared/ErrorList/ErrorListExportCommandTests.cs index 6a6424a3023..ba52d55d528 100644 --- a/tests/UI/Features/Shared/ErrorList/ErrorListExportCommandTests.cs +++ b/tests/UI/Features/Shared/ErrorList/ErrorListExportCommandTests.cs @@ -1,5 +1,6 @@ using System.Threading.Tasks; using Avalonia; +using Avalonia.Automation; using Avalonia.Controls; using Avalonia.Headless.XUnit; using Avalonia.LogicalTree; @@ -103,7 +104,7 @@ public void ExportMenu_HasTheFourTargets() { var (vm, window, _) = MakeWindow(); - var button = window.GetLogicalDescendants().OfType + /// + /// Time fields for the edit box: the text gets room inside a rounded field that follows the + /// theme (the narrow stacked up/down buttons come from UseCompactSpinner). + /// + private static void AddCompactTimeControlStyles(Styles styles) + { + foreach (var type in new[] { typeof(TimeCodeUpDown), typeof(SecondsUpDown) }) + { + styles.Add(new Style(x => x.OfType(type).Template().OfType()) + { + Setters = + { + new Setter(TemplatedControl.MinHeightProperty, 30.0), + new Setter(TemplatedControl.CornerRadiusProperty, new CornerRadius(6)), + new Setter(TemplatedControl.BackgroundProperty, EditorChrome.FieldBrush), + new Setter(TemplatedControl.BorderBrushProperty, EditorChrome.FieldBorderBrush), + }, + }); + styles.Add(new Style(x => x.OfType(type).Template().OfType()) + { + Setters = + { + new Setter(TemplatedControl.PaddingProperty, new Thickness(10, 2, 4, 2)), + new Setter(TemplatedControl.BackgroundProperty, Brushes.Transparent), + new Setter(TemplatedControl.FontSizeProperty, 13.0), + }, + }); + } + } + private static RowDefinition MakeEditSectionRow(MainViewModel? vm) { var floor = EditGridMinimumHeight + EditGridMargin * 2; From 9f09cfd2212d21adb40b23b8ed7977cf0b7843c4 Mon Sep 17 00:00:00 2001 From: niksedk Date: Fri, 16 Oct 2026 06:31:27 +0200 Subject: [PATCH 233/306] Time controls: compact stacked spinner everywhere The narrow stacked up/down pair and the roomier, rounded field are now the default for every TimeCodeUpDown and SecondsUpDown instead of an opt-in for the edit box; the edit box goes back to its original styles. Co-Authored-By: Claude Opus 5.5 --- src/ui/Controls/CompactSpinner.cs | 3 +- src/ui/Controls/SecondsUpDown.cs | 11 ++---- src/ui/Controls/TimeCodeUpDown.cs | 15 +++----- .../Main/Layout/InitListViewAndEditBox.cs | 37 +------------------ 4 files changed, 11 insertions(+), 55 deletions(-) diff --git a/src/ui/Controls/CompactSpinner.cs b/src/ui/Controls/CompactSpinner.cs index 1171121d423..e2112121213 100644 --- a/src/ui/Controls/CompactSpinner.cs +++ b/src/ui/Controls/CompactSpinner.cs @@ -5,7 +5,8 @@ namespace Nikse.SubtitleEdit.Controls; /// -/// Turns a ButtonSpinner's two wide side-by-side up/down buttons into a narrow stacked pair. +/// Turns a ButtonSpinner's two wide side-by-side up/down buttons into a narrow stacked pair +/// (used by TimeCodeUpDown and SecondsUpDown everywhere). /// The theme sets the panel orientation and button sizes inside its template, where they beat /// any style, so they are changed here once the template is applied. /// diff --git a/src/ui/Controls/SecondsUpDown.cs b/src/ui/Controls/SecondsUpDown.cs index 3e482b200d2..ffc73c4fc56 100644 --- a/src/ui/Controls/SecondsUpDown.cs +++ b/src/ui/Controls/SecondsUpDown.cs @@ -17,12 +17,6 @@ namespace Nikse.SubtitleEdit.Controls; public class SecondsUpDown : TemplatedControl { - /// - /// Narrow stacked up/down buttons instead of the two wide side-by-side ones (set before the - /// template is applied) - used where space is tight, like the main window's edit box. - /// - public bool UseCompactSpinner { get; init; } - private TextBox? _textBox; private ButtonSpinner? _spinner; @@ -98,7 +92,7 @@ private static FuncControlTemplate CreateTemplate() { Name = "PART_TextBox", IsReadOnly = false, - Padding = new Thickness(9, 2, 2, 2), + Padding = new Thickness(10, 2, 4, 2), Margin = new Thickness(0), VerticalContentAlignment = VerticalAlignment.Center, HorizontalContentAlignment = HorizontalAlignment.Left, @@ -131,6 +125,7 @@ private static FuncControlTemplate CreateTemplate() Width = double.NaN, Margin = new Thickness(0), Padding = new Thickness(0), + CornerRadius = new CornerRadius(6), }; scope.Register("PART_Spinner", spinner); @@ -146,7 +141,7 @@ protected override void OnApplyTemplate(TemplateAppliedEventArgs e) _textBox = e.NameScope.Find("PART_TextBox"); _spinner = e.NameScope.Find("PART_Spinner"); - if (UseCompactSpinner && _spinner != null) + if (_spinner != null) { CompactSpinner.Attach(_spinner); } diff --git a/src/ui/Controls/TimeCodeUpDown.cs b/src/ui/Controls/TimeCodeUpDown.cs index 5deb01d8720..6d47c143220 100644 --- a/src/ui/Controls/TimeCodeUpDown.cs +++ b/src/ui/Controls/TimeCodeUpDown.cs @@ -20,12 +20,6 @@ namespace Nikse.SubtitleEdit.Controls { public class TimeCodeUpDown : TemplatedControl { - /// - /// Narrow stacked up/down buttons instead of the two wide side-by-side ones (set before the - /// template is applied) - used where space is tight, like the main window's edit box. - /// - public bool UseCompactSpinner { get; init; } - public bool UseVideoOffset { get; set; } = false; private TextBox? _textBox; @@ -103,7 +97,7 @@ protected override void OnApplyTemplate(TemplateAppliedEventArgs e) _textBox = e.NameScope.Find("PART_TextBox"); _spinner = e.NameScope.Find("PART_Spinner"); - if (UseCompactSpinner && _spinner != null) + if (_spinner != null) { CompactSpinner.Attach(_spinner); } @@ -181,8 +175,8 @@ private void UpdateMinWidth() if (spinnerButtonsWidth <= 0) { // Conservative estimate for spinner buttons (varies by platform) - // macOS: ~84px, Windows/Linux: ~50-60px - spinnerButtonsWidth = 84; + // The narrow stacked up/down pair (CompactSpinner) + spinnerButtonsWidth = 24; } // Account for textbox internal padding (9 left, 2 right from template) @@ -229,7 +223,7 @@ private static FuncControlTemplate CreateTemplate() { Name = "PART_TextBox", IsReadOnly = false, - Padding = new Thickness(9, 2, 2, 2), + Padding = new Thickness(10, 2, 4, 2), Margin = new Thickness(0), VerticalContentAlignment = VerticalAlignment.Center, HorizontalContentAlignment = HorizontalAlignment.Left, @@ -263,6 +257,7 @@ private static FuncControlTemplate CreateTemplate() Width = double.NaN, Margin = new Thickness(0), Padding = new Thickness(0), + CornerRadius = new CornerRadius(6), }; scope.Register("PART_Spinner", spinner); diff --git a/src/ui/Features/Main/Layout/InitListViewAndEditBox.cs b/src/ui/Features/Main/Layout/InitListViewAndEditBox.cs index 500b426cffe..a512d93eebb 100644 --- a/src/ui/Features/Main/Layout/InitListViewAndEditBox.cs +++ b/src/ui/Features/Main/Layout/InitListViewAndEditBox.cs @@ -4,7 +4,6 @@ using Avalonia; using Avalonia.Automation; using Avalonia.Controls; -using Avalonia.Controls.Primitives; using Avalonia.Controls.Templates; using Avalonia.Data; using Avalonia.Data.Converters; @@ -1596,8 +1595,7 @@ internal static Grid MakeLayoutListViewAndEditBox(MainView mainPage, MainViewMod Margin = new Thickness(0, 0, 0, 0), VerticalAlignment = VerticalAlignment.Top, }; - AddCompactTimeControlStyles(timeControlsPanel.Styles); - + // Start Time controls var startTimePanel = new StackPanel { @@ -1613,7 +1611,6 @@ internal static Grid MakeLayoutListViewAndEditBox(MainView mainPage, MainViewMod startTimePanel.Children.Add(startTimeLabel); var timeCodeUpDown = new TimeCodeUpDown { - UseCompactSpinner = true, DataContext = vm, UseVideoOffset = true, [AutomationProperties.NameProperty] = Se.Language.General.StartTime, @@ -1655,7 +1652,6 @@ internal static Grid MakeLayoutListViewAndEditBox(MainView mainPage, MainViewMod endTimePanel.Children.Add(endTimeLabel); var endCodeUpDown = new TimeCodeUpDown { - UseCompactSpinner = true, DataContext = vm, // Like the start time above: the row holds a video-relative time, the box shows // (and reads back) "time code + video offset", so it matches the grid's Hide column. @@ -1691,7 +1687,6 @@ internal static Grid MakeLayoutListViewAndEditBox(MainView mainPage, MainViewMod durationPanel.Children.Add(durationLabel); var durationUpDown = new SecondsUpDown { - UseCompactSpinner = true, DataContext = vm, [AutomationProperties.NameProperty] = Se.Language.General.Duration, [!SecondsUpDown.ValueProperty] = new Binding($"{nameof(vm.SelectedSubtitle)}.{nameof(SubtitleLineViewModel.Duration)}") @@ -2483,36 +2478,6 @@ private static Avalonia.Controls.Control MakeTextBox(MainViewModel vm) /// layout pass replaces both with the measured value (TrackEditSectionMinimumHeight), which is /// exactly what the Auto row used to settle at, so the default look is unchanged. /// - /// - /// Time fields for the edit box: the text gets room inside a rounded field that follows the - /// theme (the narrow stacked up/down buttons come from UseCompactSpinner). - /// - private static void AddCompactTimeControlStyles(Styles styles) - { - foreach (var type in new[] { typeof(TimeCodeUpDown), typeof(SecondsUpDown) }) - { - styles.Add(new Style(x => x.OfType(type).Template().OfType()) - { - Setters = - { - new Setter(TemplatedControl.MinHeightProperty, 30.0), - new Setter(TemplatedControl.CornerRadiusProperty, new CornerRadius(6)), - new Setter(TemplatedControl.BackgroundProperty, EditorChrome.FieldBrush), - new Setter(TemplatedControl.BorderBrushProperty, EditorChrome.FieldBorderBrush), - }, - }); - styles.Add(new Style(x => x.OfType(type).Template().OfType()) - { - Setters = - { - new Setter(TemplatedControl.PaddingProperty, new Thickness(10, 2, 4, 2)), - new Setter(TemplatedControl.BackgroundProperty, Brushes.Transparent), - new Setter(TemplatedControl.FontSizeProperty, 13.0), - }, - }); - } - } - private static RowDefinition MakeEditSectionRow(MainViewModel? vm) { var floor = EditGridMinimumHeight + EditGridMargin * 2; From fc8b63ba9e20b9064178a43254c754816336d02c Mon Sep 17 00:00:00 2001 From: niksedk Date: Fri, 16 Oct 2026 06:38:26 +0200 Subject: [PATCH 234/306] Time controls: ASSA field look, arrows on hover Soft field background with a subtle border and no divider; the stacked up/down pair only shows while the pointer is over the field or it has keyboard focus (keeping its space). Up/Down and the wheel always step. Co-Authored-By: Claude Opus 5.5 --- src/ui/Controls/CompactSpinner.cs | 37 +++++++++++++++++++++++++++++-- src/ui/Controls/SecondsUpDown.cs | 2 ++ src/ui/Controls/TimeCodeUpDown.cs | 2 ++ 3 files changed, 39 insertions(+), 2 deletions(-) diff --git a/src/ui/Controls/CompactSpinner.cs b/src/ui/Controls/CompactSpinner.cs index e2112121213..6a2a7bb33f8 100644 --- a/src/ui/Controls/CompactSpinner.cs +++ b/src/ui/Controls/CompactSpinner.cs @@ -1,12 +1,18 @@ using Avalonia.Controls; +using Avalonia.Input; using Avalonia.Controls.Primitives; using Avalonia.Layout; +using Avalonia.Media; +using Nikse.SubtitleEdit.Logic; namespace Nikse.SubtitleEdit.Controls; /// -/// Turns a ButtonSpinner's two wide side-by-side up/down buttons into a narrow stacked pair -/// (used by TimeCodeUpDown and SecondsUpDown everywhere). +/// Gives TimeCodeUpDown and SecondsUpDown the editor field look (as in the ASSA editors): a soft +/// field background with a subtle border, and a narrow stacked up/down pair with no divider +/// instead of the two wide side-by-side buttons. The pair only shows while the pointer is over +/// the field or it has keyboard focus (it keeps its space, so the field never changes width); +/// Up/Down and the mouse wheel step the value either way. /// The theme sets the panel orientation and button sizes inside its template, where they beat /// any style, so they are changed here once the template is applied. /// @@ -16,6 +22,9 @@ internal static class CompactSpinner internal static void Attach(ButtonSpinner spinner) { + spinner.Background = EditorChrome.FieldBrush; + spinner.BorderBrush = EditorChrome.FieldBorderBrush; + spinner.MinHeight = 28; spinner.TemplateApplied -= OnTemplateApplied; spinner.TemplateApplied += OnTemplateApplied; } @@ -26,6 +35,26 @@ private static void OnTemplateApplied(object? sender, TemplateAppliedEventArgs e { panel.Orientation = Orientation.Vertical; panel.VerticalAlignment = VerticalAlignment.Center; + + if (sender is ButtonSpinner spinner) + { + void Update() + { + var active = spinner.IsPointerOver || spinner.IsKeyboardFocusWithin; + panel.Opacity = active ? 1 : 0; + spinner.BorderBrush = active ? EditorChrome.SeparatorBrush : EditorChrome.FieldBorderBrush; + } + + Update(); + spinner.PropertyChanged += (_, change) => + { + if (change.Property == InputElement.IsPointerOverProperty || + change.Property == InputElement.IsKeyboardFocusWithinProperty) + { + Update(); + } + }; + } } foreach (var name in new[] { "PART_IncreaseButton", "PART_DecreaseButton" }) @@ -37,6 +66,10 @@ private static void OnTemplateApplied(object? sender, TemplateAppliedEventArgs e button.Height = 13; button.MinHeight = 0; button.Padding = new Avalonia.Thickness(0); + button.BorderThickness = new Avalonia.Thickness(0); + button.Background = Brushes.Transparent; + button.Foreground = EditorChrome.IconBrush; + button.RenderTransform = new ScaleTransform(0.8, 0.8); } } } diff --git a/src/ui/Controls/SecondsUpDown.cs b/src/ui/Controls/SecondsUpDown.cs index ffc73c4fc56..7ff59ff9323 100644 --- a/src/ui/Controls/SecondsUpDown.cs +++ b/src/ui/Controls/SecondsUpDown.cs @@ -99,6 +99,8 @@ private static FuncControlTemplate CreateTemplate() HorizontalAlignment = HorizontalAlignment.Stretch, Width = double.NaN, BorderBrush = Brushes.Transparent, + BorderThickness = new Thickness(0), + Background = Brushes.Transparent, }; var grid = new Grid diff --git a/src/ui/Controls/TimeCodeUpDown.cs b/src/ui/Controls/TimeCodeUpDown.cs index 6d47c143220..af03b81ab08 100644 --- a/src/ui/Controls/TimeCodeUpDown.cs +++ b/src/ui/Controls/TimeCodeUpDown.cs @@ -230,6 +230,8 @@ private static FuncControlTemplate CreateTemplate() HorizontalAlignment = HorizontalAlignment.Stretch, Width = double.NaN, BorderBrush = Brushes.Transparent, + BorderThickness = new Thickness(0), + Background = Brushes.Transparent, CaretIndex = control.LastPartCaretIndex, }; From 154b16a05d2f3be55f9a34e3d98ca7686b8df349 Mon Sep 17 00:00:00 2001 From: niksedk Date: Fri, 16 Oct 2026 06:44:21 +0200 Subject: [PATCH 235/306] Add copy plain text shortcuts (text and original) SE4 had separate "copy text" and "copy plain text" shortcuts for the current and original text; SE5 only had the text ones, and the SE4 importer folded both onto them. Add plain-text (tags removed) variants and map the SE4 keys to them. Fixes #16089. Co-Authored-By: Claude Opus 5.5 --- src/ui/Features/Main/MainViewModel.cs | 53 +++++++++++++++---- .../Options/Shortcuts/Se4ShortcutsImporter.cs | 4 +- .../Options/LanguageSettingsShortcuts.cs | 4 ++ src/ui/Logic/ShortcutsMain.cs | 4 ++ 4 files changed, 54 insertions(+), 11 deletions(-) diff --git a/src/ui/Features/Main/MainViewModel.cs b/src/ui/Features/Main/MainViewModel.cs index d2f48583224..a0c1f66b90f 100644 --- a/src/ui/Features/Main/MainViewModel.cs +++ b/src/ui/Features/Main/MainViewModel.cs @@ -8553,15 +8553,25 @@ private async Task CopyTextFromOriginalToClipboard() return; } - var sb = new StringBuilder(); - foreach (var subtitle in selectedItems) + await CopyLinesToClipboard(selectedItems.Select(p => p.OriginalText)); + } + + [RelayCommand] + private async Task CopyPlainTextFromOriginalToClipboard() + { + var selectedItems = SubtitleGridSelectedItemsWithReference; + if (Window == null || selectedItems.Count == 0 || !ShowColumnOriginalText) { - sb.AppendLine(subtitle.OriginalText); + return; } - await ClipboardHelper.SetTextAsync(Window, sb.ToString()); + if (IsEmpty) + { + ShowSubtitleNotLoadedMessage(); + return; + } - ShowStatus(string.Format("{0} lines copied to clipboard", selectedItems.Count)); + await CopyLinesToClipboard(selectedItems.Select(p => HtmlUtil.RemoveHtmlTags(p.OriginalText, true))); } [RelayCommand] @@ -8579,15 +8589,40 @@ private async Task CopyTextToClipboard() return; } + await CopyLinesToClipboard(selectedItems.Select(p => p.Text)); + } + + [RelayCommand] + private async Task CopyPlainTextToClipboard() + { + var selectedItems = SubtitleGridSelectedItems.Cast().ToList(); + if (Window == null || selectedItems.Count == 0) + { + return; + } + + if (IsEmpty) + { + ShowSubtitleNotLoadedMessage(); + return; + } + + await CopyLinesToClipboard(selectedItems.Select(p => HtmlUtil.RemoveHtmlTags(p.Text, true))); + } + + private async Task CopyLinesToClipboard(IEnumerable lines) + { var sb = new StringBuilder(); - foreach (var subtitle in selectedItems) + var count = 0; + foreach (var line in lines) { - sb.AppendLine(subtitle.Text); + sb.AppendLine(line); + count++; } - await ClipboardHelper.SetTextAsync(Window, sb.ToString()); + await ClipboardHelper.SetTextAsync(Window!, sb.ToString()); - ShowStatus(string.Format("{0} lines copied to clipboard", selectedItems.Count)); + ShowStatus(string.Format("{0} lines copied to clipboard", count)); } /// diff --git a/src/ui/Features/Options/Shortcuts/Se4ShortcutsImporter.cs b/src/ui/Features/Options/Shortcuts/Se4ShortcutsImporter.cs index 8607fc522dd..ea9056a9e41 100644 --- a/src/ui/Features/Options/Shortcuts/Se4ShortcutsImporter.cs +++ b/src/ui/Features/Options/Shortcuts/Se4ShortcutsImporter.cs @@ -243,8 +243,8 @@ public sealed class ImportResult ["MainListViewSetNewActor"] = nameof(MainViewModel.SetNewActorCommand), ["MainListViewSplit"] = nameof(MainViewModel.SplitCommand), ["MainListViewToggleDashes"] = nameof(MainViewModel.ToggleDialogDashesCommand), - ["MainListViewCopyPlainText"] = nameof(MainViewModel.CopyTextToClipboardCommand), - ["MainListViewCopyOriginalPlainText"] = nameof(MainViewModel.CopyTextFromOriginalToClipboardCommand), + ["MainListViewCopyPlainText"] = nameof(MainViewModel.CopyPlainTextToClipboardCommand), + ["MainListViewCopyOriginalPlainText"] = nameof(MainViewModel.CopyPlainTextFromOriginalToClipboardCommand), ["MainRemoveFormatting"] = nameof(MainViewModel.TextBoxRemoveAllFormattingCommand), // Text box diff --git a/src/ui/Logic/Config/Language/Options/LanguageSettingsShortcuts.cs b/src/ui/Logic/Config/Language/Options/LanguageSettingsShortcuts.cs index 7eb1f3c688e..2456956a941 100644 --- a/src/ui/Logic/Config/Language/Options/LanguageSettingsShortcuts.cs +++ b/src/ui/Logic/Config/Language/Options/LanguageSettingsShortcuts.cs @@ -293,6 +293,8 @@ public class LanguageSettingsShortcuts public string DuplicatesFound { get; set; } public string CopyTextToClipboard { get; set; } public string CopyTextFromOriginalToClipboard { get; set; } + public string CopyPlainTextToClipboard { get; set; } + public string CopyPlainTextFromOriginalToClipboard { get; set; } public string AssaDraw { get; set; } public string AssaGenerateProgressBar { get; set; } public string AssaGenerateBackgroundBox { get; set; } @@ -638,6 +640,8 @@ public LanguageSettingsShortcuts() DuplicatesFound = "The following duplicate shortcuts were found:"; CopyTextToClipboard = "Copy text to clipboard (selected lines)"; CopyTextFromOriginalToClipboard = "Copy text from original to clipboard (selected lines)"; + CopyPlainTextToClipboard = "Copy plain text to clipboard (selected lines)"; + CopyPlainTextFromOriginalToClipboard = "Copy plain text from original to clipboard (selected lines)"; AssaDraw = "ASSA Draw"; AssaGenerateProgressBar = "ASSA Generate progress bar"; AssaGenerateBackgroundBox = "ASSA Generate background box"; diff --git a/src/ui/Logic/ShortcutsMain.cs b/src/ui/Logic/ShortcutsMain.cs index 03497430ef4..4a9c3b9b093 100644 --- a/src/ui/Logic/ShortcutsMain.cs +++ b/src/ui/Logic/ShortcutsMain.cs @@ -730,6 +730,8 @@ private static Dictionary BuildCommandTranslations() { nameof(MainViewModel.ShowPickLayerCommand), Se.Language.General.PickLayer }, { nameof(MainViewModel.CopyTextToClipboardCommand), Se.Language.Options.Shortcuts.CopyTextToClipboard }, { nameof(MainViewModel.CopyTextFromOriginalToClipboardCommand), Se.Language.Options.Shortcuts.CopyTextFromOriginalToClipboard }, + { nameof(MainViewModel.CopyPlainTextToClipboardCommand), Se.Language.Options.Shortcuts.CopyPlainTextToClipboard }, + { nameof(MainViewModel.CopyPlainTextFromOriginalToClipboardCommand), Se.Language.Options.Shortcuts.CopyPlainTextFromOriginalToClipboard }, { nameof(MainViewModel.ShowAssaImageColorPickerCommand), Se.Language.Assa.ImageColorPicker }, { nameof(MainViewModel.ShowAssaSetPositionCommand), Se.Language.Assa.SetPosition }, { nameof(MainViewModel.ShowAssaApplyCustomOverrideTagsCommand), Se.Language.Assa.ApplyOverrideTags }, @@ -1284,6 +1286,8 @@ private static List GetAllAvailableShortcuts(MainViewModel vm AddShortcut(shortcuts, vm.ShowPickLayerCommand, nameof(vm.ShowPickLayerCommand), ShortcutCategory.General); AddShortcut(shortcuts, vm.CopyTextToClipboardCommand, nameof(vm.CopyTextToClipboardCommand), ShortcutCategory.General); AddShortcut(shortcuts, vm.CopyTextFromOriginalToClipboardCommand, nameof(vm.CopyTextFromOriginalToClipboardCommand), ShortcutCategory.General); + AddShortcut(shortcuts, vm.CopyPlainTextToClipboardCommand, nameof(vm.CopyPlainTextToClipboardCommand), ShortcutCategory.General); + AddShortcut(shortcuts, vm.CopyPlainTextFromOriginalToClipboardCommand, nameof(vm.CopyPlainTextFromOriginalToClipboardCommand), ShortcutCategory.General); AddShortcut(shortcuts, vm.ShowAssaImageColorPickerCommand, nameof(vm.ShowAssaImageColorPickerCommand), ShortcutCategory.General); AddShortcut(shortcuts, vm.ShowAssaSetPositionCommand, nameof(vm.ShowAssaSetPositionCommand), ShortcutCategory.General); AddShortcut(shortcuts, vm.ShowAssaApplyCustomOverrideTagsCommand, nameof(vm.ShowAssaApplyCustomOverrideTagsCommand), ShortcutCategory.General); From 85b022c8b43af891d5cb8066dd66d559e6ede878 Mon Sep 17 00:00:00 2001 From: niksedk Date: Fri, 16 Oct 2026 06:49:07 +0200 Subject: [PATCH 236/306] Auto-wrap while typing in subtitle text box (#16087) Ports SE4's AutoWrapLineWhileTyping: when typing makes a single-line subtitle longer than the single line max length, break at the last space (auto-break when far too long). Off by default, as in SE4; Options > General > Auto-wrap while typing. Co-Authored-By: Claude Opus 5.5 --- .../Main/Layout/InitListViewAndEditBox.cs | 4 + src/ui/Features/Main/MainViewModel.cs | 2 +- .../Features/Options/Settings/SettingsPage.cs | 1 + .../Options/Settings/SettingsViewModel.cs | 3 + .../Shared/TextBoxUtils/TextBoxAutoWrap.cs | 93 +++++++++++++++++++ .../Language/Options/LanguageSettings.cs | 2 + src/ui/Logic/Config/SeGeneral.cs | 1 + tests/UI/Features/TextBoxAutoWrapTests.cs | 92 ++++++++++++++++++ 8 files changed, 197 insertions(+), 1 deletion(-) create mode 100644 src/ui/Features/Shared/TextBoxUtils/TextBoxAutoWrap.cs create mode 100644 tests/UI/Features/TextBoxAutoWrapTests.cs diff --git a/src/ui/Features/Main/Layout/InitListViewAndEditBox.cs b/src/ui/Features/Main/Layout/InitListViewAndEditBox.cs index a512d93eebb..f00a606f418 100644 --- a/src/ui/Features/Main/Layout/InitListViewAndEditBox.cs +++ b/src/ui/Features/Main/Layout/InitListViewAndEditBox.cs @@ -2453,6 +2453,7 @@ private static Avalonia.Controls.Control MakeTextBox(MainViewModel vm) }); textBox.TextChanged += vm.SubtitleTextChanged; + TextBoxAutoWrap.Attach(textBox, vm.GetDetectedLanguageCode); textBox.GotFocus += (_, _) => vm.SubtitleTextBoxGotFocus(); textBox.AddHandler(InputElement.PointerPressedEvent, (_, e) => vm.StoreTextEditorPointerArgs(e), RoutingStrategies.Tunnel); @@ -2669,6 +2670,9 @@ private static Avalonia.Controls.Control MakeTextBoxOriginal(MainViewModel vm) Source = vm }); + // The cached language is the working subtitle's; the original's would need its own + // detection, and only the rare far-too-long auto-break uses it. + TextBoxAutoWrap.Attach(textBox, () => string.Empty); SetupMacContextMenuForTextBox(textBox, vm); MainHelpers.RightToLeftHelper.FollowContentDirection(textBox); diff --git a/src/ui/Features/Main/MainViewModel.cs b/src/ui/Features/Main/MainViewModel.cs index d2f48583224..f334406e7f9 100644 --- a/src/ui/Features/Main/MainViewModel.cs +++ b/src/ui/Features/Main/MainViewModel.cs @@ -26139,7 +26139,7 @@ private bool ImportSubtitleFromDivX(string fileName, bool skipLoadVideo = false) // so the code is cached here and reset on file load/new and after translation. private string? _detectedLanguageCode; - private string GetDetectedLanguageCode() + internal string GetDetectedLanguageCode() { return _detectedLanguageCode ??= Subtitles.AutoDetectGoogleLanguageOrNull() ?? string.Empty; } diff --git a/src/ui/Features/Options/Settings/SettingsPage.cs b/src/ui/Features/Options/Settings/SettingsPage.cs index cc24aef91d9..82201cb7def 100644 --- a/src/ui/Features/Options/Settings/SettingsPage.cs +++ b/src/ui/Features/Options/Settings/SettingsPage.cs @@ -468,6 +468,7 @@ private List CreateSections() MakeCheckboxSetting(Se.Language.Options.Settings.FrameModeSnapToVideoFrames, nameof(_vm.FrameModeSnapToVideoFrames), new Binding(nameof(_vm.IsFramesTimeCodeMode)) { Source = _vm }), MakeCheckboxSetting(Se.Language.Options.Settings.TextBoxLimitNewLines, nameof(_vm.TextBoxLimitNewLines)), + MakeCheckboxSetting(Se.Language.Options.Settings.TextBoxAutoWrapWhileTyping, nameof(_vm.TextBoxAutoWrapWhileTyping)), MakeCheckboxSetting(Se.Language.General.LockTimeCodes, nameof(_vm.LockTimeCodes)), MakeCheckboxSetting(Se.Language.Options.Settings.RememberPositionAndSize, nameof(_vm.RememberPositionAndSize)), MakeCheckboxSetting(Se.Language.Options.Settings.TitleBarFullFileName, nameof(_vm.TitleBarFullFileName)), diff --git a/src/ui/Features/Options/Settings/SettingsViewModel.cs b/src/ui/Features/Options/Settings/SettingsViewModel.cs index 9387eef51a4..291d12e1efa 100644 --- a/src/ui/Features/Options/Settings/SettingsViewModel.cs +++ b/src/ui/Features/Options/Settings/SettingsViewModel.cs @@ -160,6 +160,7 @@ partial void OnSelectedTimeCodeModeChanged(TimeCodeModeDisplay value) ? Se.Language.Options.Settings.MinGapFrames : Se.Language.Options.Settings.MinGapMs; [ObservableProperty] private bool _textBoxLimitNewLines; + [ObservableProperty] private bool _textBoxAutoWrapWhileTyping; [ObservableProperty] private bool _autoSave; [ObservableProperty] private bool _autoBackupOn; [ObservableProperty] private int? _autoBackupIntervalMinutes; @@ -837,6 +838,7 @@ private void LoadSettings() SelectedTimeCodeMode = TimeCodeModes.First(p => p.Mode == timeCodeMode); FrameModeSnapToVideoFrames = general.FrameModeSnapToVideoFrames; TextBoxLimitNewLines = general.SubtitleTextBoxLimitNewLines; + TextBoxAutoWrapWhileTyping = general.SubtitleTextBoxAutoWrapWhileTyping; NewEmptyDefaultMs = general.NewEmptyDefaultMs; TimeCodeUpDownStepMs = general.TimeCodeUpDownStepMs; MoveSelectedLinesStepMs = general.MoveSelectedLinesStepMs; @@ -1752,6 +1754,7 @@ private void SaveSettings() general.UseFrameNumbersPersisted = SelectedTimeCodeMode.Mode == TimeCodeMode.FrameNumbers; general.FrameModeSnapToVideoFrames = FrameModeSnapToVideoFrames; general.SubtitleTextBoxLimitNewLines = TextBoxLimitNewLines; + general.SubtitleTextBoxAutoWrapWhileTyping = TextBoxAutoWrapWhileTyping; general.NewEmptyDefaultMs = NewEmptyDefaultMs ?? general.NewEmptyDefaultMs; general.TimeCodeUpDownStepMs = TimeCodeUpDownStepMs ?? general.TimeCodeUpDownStepMs; general.MoveSelectedLinesStepMs = MoveSelectedLinesStepMs ?? general.MoveSelectedLinesStepMs; diff --git a/src/ui/Features/Shared/TextBoxUtils/TextBoxAutoWrap.cs b/src/ui/Features/Shared/TextBoxUtils/TextBoxAutoWrap.cs new file mode 100644 index 00000000000..4d32fea5a25 --- /dev/null +++ b/src/ui/Features/Shared/TextBoxUtils/TextBoxAutoWrap.cs @@ -0,0 +1,93 @@ +using Avalonia.Controls; +using Avalonia.Input; +using Avalonia.Interactivity; +using Nikse.SubtitleEdit.Core.Common; +using Nikse.SubtitleEdit.Logic.Config; +using System; + +namespace Nikse.SubtitleEdit.Features.Shared.TextBoxUtils; + +/// +/// "Auto-wrap while typing" from SE4 (#16087): when typing makes a single-line subtitle longer +/// than the single line max length, the line is broken at its last space, so the next words go +/// on line two. Only typed text triggers it - not paste, not the text swapping in when another +/// row is selected - and only a single line, so a line the user broke by hand is left alone. +/// +public static class TextBoxAutoWrap +{ + /// + /// Wraps while typing when + /// is on. gives the language for the auto-break used when the + /// line is far too long for a single break. + /// + public static void Attach(TextBox textBox, Func getLanguage) + { + // TextBox handles TextInput itself, so listen to handled events too - by then the typed + // character is in the text. + textBox.AddHandler(InputElement.TextInputEvent, (_, _) => + { + if (!Se.Settings.General.SubtitleTextBoxAutoWrapWhileTyping || textBox.IsReadOnly) + { + return; + } + + var text = textBox.Text ?? string.Empty; + if (TryWrap(text, textBox.CaretIndex, Se.Settings.General.SubtitleLineMaximumLength, getLanguage, out var newText, out var newCaret)) + { + textBox.Text = newText; + textBox.CaretIndex = newCaret; + } + }, RoutingStrategies.Bubble, handledEventsToo: true); + } + + internal static bool TryWrap(string text, int caretIndex, int maxLength, Func getLanguage, out string newText, out int newCaretIndex) + { + newText = text; + newCaretIndex = caretIndex; + + if (maxLength <= 0 || text.Contains('\n') || text.Contains('\r')) + { + return false; + } + + var length = HtmlUtil.RemoveHtmlTags(text, true).Length; + if (length <= maxLength) + { + return false; + } + + string wrapped; + if (length > maxLength + 30) + { + wrapped = Utilities.AutoBreakLine(text, maxLength, 0, getLanguage()); + } + else + { + var lastSpace = text.LastIndexOf(' '); + if (lastSpace <= 0) + { + return false; + } + + wrapped = text.Remove(lastSpace, 1).Insert(lastSpace, Environment.NewLine); + } + + var breakIndex = wrapped.IndexOf(Environment.NewLine, StringComparison.Ordinal); + if (breakIndex <= 0 || wrapped == text) + { + return false; + } + + newText = wrapped; + if (caretIndex >= text.Length) + { + newCaretIndex = wrapped.Length; + } + else if (caretIndex > breakIndex) + { + newCaretIndex = Math.Min(wrapped.Length, caretIndex + wrapped.Length - text.Length); + } + + return true; + } +} diff --git a/src/ui/Logic/Config/Language/Options/LanguageSettings.cs b/src/ui/Logic/Config/Language/Options/LanguageSettings.cs index fadc7cd5912..4b8206ab2f3 100644 --- a/src/ui/Logic/Config/Language/Options/LanguageSettings.cs +++ b/src/ui/Logic/Config/Language/Options/LanguageSettings.cs @@ -250,6 +250,7 @@ public class LanguageSettings public string TimeCodeModeFrameNumbers { get; set; } public string FrameModeSnapToVideoFrames { get; set; } public string TextBoxLimitNewLines { get; set; } + public string TextBoxAutoWrapWhileTyping { get; set; } public string MpvOpenGl { get; set; } public string MpvSoftwareRendering { get; set; } public string MpvMetalRendering { get; set; } @@ -614,6 +615,7 @@ public LanguageSettings() TimeCodeModeFrameNumbers = "Frame numbers"; FrameModeSnapToVideoFrames = "Store times on video frames (not frames within each second)"; TextBoxLimitNewLines = "Limit number of lines in subtitle text box"; + TextBoxAutoWrapWhileTyping = "Auto-wrap while typing"; MpvOpenGl = "libmpv - OpenGL"; MpvWidRendering = "libmpv - Native Window ID rendering"; MpvSoftwareRendering = "libmpv - Software rendering (slow)"; diff --git a/src/ui/Logic/Config/SeGeneral.cs b/src/ui/Logic/Config/SeGeneral.cs index 0420616cdfe..fe4bf222689 100644 --- a/src/ui/Logic/Config/SeGeneral.cs +++ b/src/ui/Logic/Config/SeGeneral.cs @@ -22,6 +22,7 @@ public class SeGeneral public int SubtitleLineMaximumLength { get; set; } = 43; public int MaxNumberOfLines { get; set; } public bool SubtitleTextBoxLimitNewLines { get; set; } = true; + public bool SubtitleTextBoxAutoWrapWhileTyping { get; set; } public double SubtitleMaximumCharactersPerSeconds { get; set; } public double SubtitleOptimalCharactersPerSeconds { get; set; } public double SubtitleMaximumWordsPerMinute { get; set; } diff --git a/tests/UI/Features/TextBoxAutoWrapTests.cs b/tests/UI/Features/TextBoxAutoWrapTests.cs new file mode 100644 index 00000000000..cb9f275e580 --- /dev/null +++ b/tests/UI/Features/TextBoxAutoWrapTests.cs @@ -0,0 +1,92 @@ +using Avalonia.Controls; +using Avalonia.Headless.XUnit; +using Avalonia.Input; +using Nikse.SubtitleEdit.Features.Shared.TextBoxUtils; +using Nikse.SubtitleEdit.Logic.Config; + +namespace UITests.Features; + +/// +/// "Auto-wrap while typing" from SE4 (#16087). These tests change the global Se.Settings, so +/// restore what was there afterwards - other tests in this assembly read the same instance. +/// +public class TextBoxAutoWrapTests : IDisposable +{ + private readonly bool _originalAutoWrap = Se.Settings.General.SubtitleTextBoxAutoWrapWhileTyping; + private readonly int _originalMaxLength = Se.Settings.General.SubtitleLineMaximumLength; + + public void Dispose() + { + Se.Settings.General.SubtitleTextBoxAutoWrapWhileTyping = _originalAutoWrap; + Se.Settings.General.SubtitleLineMaximumLength = _originalMaxLength; + } + + [Fact] + public void TryWrap_BreaksAtLastSpace_WhenLineGetsTooLong() + { + const string text = "This is a line that is too long"; + + var wrapped = TextBoxAutoWrap.TryWrap(text, text.Length, 30, () => "en", out var newText, out var caret); + + Assert.True(wrapped); + Assert.Equal("This is a line that is too" + Environment.NewLine + "long", newText); + Assert.Equal(newText.Length, caret); + } + + [Fact] + public void TryWrap_TypedSpaceAtEnd_StartsLineTwo() + { + const string text = "This is a line that is too long "; + + TextBoxAutoWrap.TryWrap(text, text.Length, 30, () => "en", out var newText, out var caret); + + Assert.Equal("This is a line that is too long" + Environment.NewLine, newText); + Assert.Equal(newText.Length, caret); + } + + [Fact] + public void TryWrap_CaretBeforeBreak_StaysPut() + { + const string text = "This is a line that is too long"; + + TextBoxAutoWrap.TryWrap(text, 4, 30, () => "en", out _, out var caret); + + Assert.Equal(4, caret); + } + + [Theory] + [InlineData("Short line")] + [InlineData("This is a line that is too long\r\nbut already has two lines")] + [InlineData("Thisisalinewithoutanyspacesthatistoolong")] + [InlineData("This is a line that fits")] + public void TryWrap_LeavesTextAlone(string text) + { + var wrapped = TextBoxAutoWrap.TryWrap(text, text.Length, 30, () => "en", out var newText, out _); + + Assert.False(wrapped); + Assert.Equal(text, newText); + } + + [AvaloniaFact] + public void Typing_WrapsOnlyWhenSettingIsOn() + { + Se.Settings.General.SubtitleLineMaximumLength = 30; + var textBox = new TextBox { AcceptsReturn = true, Text = "This is a line that is too lon" }; + textBox.CaretIndex = textBox.Text.Length; + TextBoxAutoWrap.Attach(textBox, () => "en"); + + Se.Settings.General.SubtitleTextBoxAutoWrapWhileTyping = false; + Type(textBox, "g"); + Assert.Equal("This is a line that is too long", textBox.Text); + + Se.Settings.General.SubtitleTextBoxAutoWrapWhileTyping = true; + Type(textBox, "s"); + Assert.Equal("This is a line that is too" + Environment.NewLine + "longs", textBox.Text); + Assert.Equal(textBox.Text.Length, textBox.CaretIndex); + } + + private static void Type(TextBox textBox, string text) + { + textBox.RaiseEvent(new TextInputEventArgs { RoutedEvent = InputElement.TextInputEvent, Text = text }); + } +} From 0ca76f4c28feb1f85c7f7d751fe242f82e7f211e Mon Sep 17 00:00:00 2001 From: niksedk Date: Fri, 16 Oct 2026 06:54:16 +0200 Subject: [PATCH 237/306] Export D-Cinema SMPTE 2014 as MXF track file (SMPTE ST 429-5) A SMPTE DCP carries its subtitles as an MXF-wrapped timed text track file with the fonts (and PNGs) embedded - SE wrote the XML only, so users had to wrap it in another tool. - libse TimedTextMxfWriter: OP-Atom clip wrapping of the subtitle XML, each resource in its own generic stream partition, footer index + RIP. Layout matches asdcplib byte for byte (checked with asdcp-info/unwrap, ffprobe; regression test against an asdcp-wrap reference file). - File > Export > D-Cinema SMPTE 2014 (MXF): the SMPTE properties dialog with a font file row, then save as .mxf. Co-Authored-By: Claude Opus 5.5 --- .../TimedTextMxfResource.cs | 25 + .../TimedTextMxfWriter.cs | 654 ++++++++++++++++++ .../DCinemaSmptePropertiesViewModel.cs | 52 +- .../DCinemaSmptePropertiesWindow.cs | 7 + src/ui/Features/Main/Layout/InitMenu.cs | 5 + .../Features/Main/Layout/InitNativeMacMenu.cs | 1 + src/ui/Features/Main/MainViewModel.cs | 59 ++ .../Config/Language/File/LanguageExport.cs | 2 + .../File/LanguageFilePropertiesDCinema.cs | 6 + src/ui/Logic/Config/SeDCinemaSmpte.cs | 2 + .../Files/DCinemaSmptePropertiesMxfTests.cs | 96 +++ .../TimedTextMxfWriterTest.cs | 139 ++++ .../libse/Files/sample_mxf_429-5_asdcplib.mxf | Bin 0 -> 19281 bytes .../libse/Files/sample_mxf_429-5_asdcplib.xml | 22 + tests/libse/LibSETests.csproj | 6 + 15 files changed, 1075 insertions(+), 1 deletion(-) create mode 100644 src/libse/ContainerFormats/MaterialExchangeFormat/TimedTextMxfResource.cs create mode 100644 src/libse/ContainerFormats/MaterialExchangeFormat/TimedTextMxfWriter.cs create mode 100644 tests/UI/Features/Files/DCinemaSmptePropertiesMxfTests.cs create mode 100644 tests/libse/ContainerFormats/TimedTextMxfWriterTest.cs create mode 100644 tests/libse/Files/sample_mxf_429-5_asdcplib.mxf create mode 100644 tests/libse/Files/sample_mxf_429-5_asdcplib.xml diff --git a/src/libse/ContainerFormats/MaterialExchangeFormat/TimedTextMxfResource.cs b/src/libse/ContainerFormats/MaterialExchangeFormat/TimedTextMxfResource.cs new file mode 100644 index 00000000000..a3b957b2d17 --- /dev/null +++ b/src/libse/ContainerFormats/MaterialExchangeFormat/TimedTextMxfResource.cs @@ -0,0 +1,25 @@ +using System; + +namespace Nikse.SubtitleEdit.Core.ContainerFormats.MaterialExchangeFormat +{ + /// + /// An ancillary resource (font or PNG image) of a SMPTE ST 429-5 timed text track file. The + /// XML refers to it by "urn:uuid:" + (LoadFont / Image). + /// + public class TimedTextMxfResource + { + public const string MimeTypeFont = "application/x-font-opentype"; + public const string MimeTypePng = "image/png"; + + public Guid Id { get; set; } + public string MimeType { get; set; } + public byte[] Data { get; set; } + + public TimedTextMxfResource(Guid id, string mimeType, byte[] data) + { + Id = id; + MimeType = mimeType; + Data = data; + } + } +} diff --git a/src/libse/ContainerFormats/MaterialExchangeFormat/TimedTextMxfWriter.cs b/src/libse/ContainerFormats/MaterialExchangeFormat/TimedTextMxfWriter.cs new file mode 100644 index 00000000000..ba04671fd13 --- /dev/null +++ b/src/libse/ContainerFormats/MaterialExchangeFormat/TimedTextMxfWriter.cs @@ -0,0 +1,654 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Xml; + +namespace Nikse.SubtitleEdit.Core.ContainerFormats.MaterialExchangeFormat +{ + /// + /// Writes a SMPTE ST 429-5 timed text track file: a D-Cinema SMPTE subtitle XML (ST 428-7) + /// clip-wrapped in OP-Atom MXF, each font/PNG it references in its own generic stream + /// partition. This is the subtitle asset of a SMPTE DCP. The layout follows asdcplib (the + /// reference implementation DCP tools and verifiers are built on) byte for byte, except + /// for the identification strings and the random instance ids. + /// + public class TimedTextMxfWriter + { + /// + /// The MXF asset id - the id the DCP's CPL and PKL refer to the track file by. It must not + /// be the subtitle XML's own Id. + /// + public Guid AssetId { get; set; } = Guid.NewGuid(); + + public DateTime ModifiedUtc { get; set; } = DateTime.UtcNow; + + public List Resources { get; } = new List(); + + /// + /// Generates the instance ids of the metadata sets - replaceable for reproducible output in tests. + /// + public Func NewInstanceId { get; set; } = Guid.NewGuid; + + public string CompanyName { get; set; } = "Nikse"; + public string ProductName { get; set; } = "Subtitle Edit"; + + private const uint IndexSid = 129; + private const uint BodySid = 1; + private const uint FirstResourceStreamId = 10; + + private static readonly byte[] OperationalPatternAtom = Hex("060e2b34040101020d01020110000000"); + private static readonly byte[] OperationalPatternGenericStream = Hex("060e2b34040101010d01020101010100"); + private static readonly byte[] EssenceContainerMultiple = Hex("060e2b34040101030d010301027f0100"); + private static readonly byte[] EssenceContainerTimedText = Hex("060e2b340401010a0d01030102130101"); + private static readonly byte[] DataDefinitionTimecode = Hex("060e2b34040101010103020101000000"); + private static readonly byte[] DataDefinitionData = Hex("060e2b34040101010103020203000000"); + private static readonly byte[] TimedTextEssenceKey = Hex("060e2b34010201010d01030117010b01"); + private static readonly byte[] GenericStreamDataKey = Hex("060e2b340101010c0d01050901000000"); + private static readonly byte[] FillKey = Hex("060e2b34010101020301021001000000"); + private static readonly byte[] PrimerKey = Hex("060e2b34020501010d01020101050100"); + private static readonly byte[] IndexTableSegmentKey = Hex("060e2b34025301010d01020101100100"); + private static readonly byte[] RandomIndexPackKey = Hex("060e2b34020501010d01020101110100"); + private static readonly byte[] UmidPrefix = Hex("060a2b340101010501010f2013000000"); + + // Local tag -> UL, all static so the primer is always the same; unused entries are allowed + private static readonly (ushort Tag, string Ul)[] PrimerEntries = + { + (0x0201, "060e2b34010101020407010000000000"), + (0x0202, "060e2b34010101020702020101030000"), + (0x1001, "060e2b34010101020601010406090000"), + (0x1101, "060e2b34010101020601010301000000"), + (0x1102, "060e2b34010101020601010302000000"), + (0x1201, "060e2b34010101020702010301040000"), + (0x1501, "060e2b34010101020702010301050000"), + (0x1502, "060e2b34010101020404010102060000"), + (0x1503, "060e2b34010101010404010105000000"), + (0x1901, "060e2b34010101020601010405010000"), + (0x1902, "060e2b34010101020601010405020000"), + (0x2701, "060e2b34010101020601010601000000"), + (0x3001, "060e2b34010101010406010100000000"), + (0x3002, "060e2b34010101010406010200000000"), + (0x3004, "060e2b34010101020601010401020000"), + (0x3006, "060e2b34010101050601010305000000"), + (0x3b02, "060e2b34010101020702011002040000"), + (0x3b03, "060e2b34010101020601010402010000"), + (0x3b05, "060e2b34010101020301020105000000"), + (0x3b06, "060e2b34010101020601010406040000"), + (0x3b07, "060e2b34010101020301020104000000"), + (0x3b08, "060e2b34010101040601010401080000"), + (0x3b09, "060e2b34010101050102020300000000"), + (0x3b0a, "060e2b34010101050102021002010000"), + (0x3b0b, "060e2b34010101050102021002020000"), + (0x3c01, "060e2b34010101020520070102010000"), + (0x3c02, "060e2b34010101020520070103010000"), + (0x3c03, "060e2b34010101020520070104000000"), + (0x3c04, "060e2b34010101020520070105010000"), + (0x3c05, "060e2b34010101020520070107000000"), + (0x3c06, "060e2b34010101020702011002030000"), + (0x3c07, "060e2b3401010102052007010a000000"), + (0x3c08, "060e2b34010101020520070106010000"), + (0x3c09, "060e2b34010101020520070101000000"), + (0x3c0a, "060e2b34010101010101150200000000"), + (0x3e01, "060e2b34010101030403030200000000"), + (0x3f05, "060e2b34010101040406020100000000"), + (0x3f06, "060e2b34010101040103040500000000"), + (0x3f07, "060e2b34010101040103040400000000"), + (0x3f08, "060e2b34010101040404040101000000"), + (0x3f09, "060e2b34010101050404040106000000"), + (0x3f0a, "060e2b34010101050404040205000000"), + (0x3f0b, "060e2b34010101050530040600000000"), + (0x3f0c, "060e2b340101010507020103010a0000"), + (0x3f0d, "060e2b34010101050702020101020000"), + (0x3f0e, "060e2b34010101050404040107000000"), + (0x4401, "060e2b34010101010101151000000000"), + (0x4402, "060e2b34010101010103030201000000"), + (0x4403, "060e2b34010101020601010406050000"), + (0x4404, "060e2b34010101020702011002050000"), + (0x4405, "060e2b34010101020702011001030000"), + (0x4701, "060e2b34010101020601010402030000"), + (0x4801, "060e2b34010101020107010100000000"), + (0x4802, "060e2b34010101020107010201000000"), + (0x4803, "060e2b34010101020601010402040000"), + (0x4804, "060e2b34010101020104010300000000"), + (0x4b01, "060e2b34010101020530040500000000"), + (0x4b02, "060e2b34010101020702010301030000"), + (0xfff9, "060e2b34010101070409020100000000"), // TimedTextResourceSubDescriptor MIMEMediaType + (0xfffa, "060e2b340101010c0101151300000000"), // TimedTextResourceSubDescriptor AncillaryResourceID + (0xfffb, "060e2b340101010e0301010202160000"), // TimedTextDescriptor RFC5646LanguageTagList + (0xfffc, "060e2b34010101080102010501000000"), // TimedTextDescriptor NamespaceURI + (0xfffd, "060e2b340101010c0409050000000000"), // TimedTextDescriptor UCSEncoding + (0xfffe, "060e2b340101010c0101151200000000"), // TimedTextDescriptor ResourceID + (0xffff, "060e2b34010101090601010406100000"), // GenericDescriptor SubDescriptors + }; + + public void Write(string fileName, string xml) + { + using (var fs = new FileStream(fileName, FileMode.Create, FileAccess.Write, FileShare.None)) + { + Write(fs, xml); + } + } + + public void Write(Stream stream, string xml) + { + var info = TimedTextInfo.Parse(xml); + var xmlBytes = new UTF8Encoding(false).GetBytes(xml); + + var metadata = MakeHeaderMetadata(info); + + // asdcplib reserves 16 KiB for the header plus room per resource sub-descriptor + long headerSize = 16384; + if (Resources.Count > 0) + { + headerSize += 12 + Resources.Sum(r => 88 + 2 * r.MimeType.Length); + } + + const int partitionPackSize = 16 + 4 + 120; + byte[] fill = null; + var fillLength = headerSize - partitionPackSize - metadata.Length - 20; + if (fillLength >= 0) + { + fill = Klv(FillKey, new byte[fillLength]); + } + else + { + headerSize = partitionPackSize + metadata.Length; + } + + var bodyPosition = headerSize; + var position = bodyPosition + partitionPackSize + KlvSize(xmlBytes.Length); + var resourcePositions = new List(); + foreach (var resource in Resources) + { + resourcePositions.Add(position); + position += partitionPackSize + KlvSize(resource.Data.Length); + } + + var footerPosition = position; + var indexSegment = MakeIndexTableSegment(info); + var lastPartition = resourcePositions.Count > 0 ? resourcePositions[resourcePositions.Count - 1] : bodyPosition; + + WriteBytes(stream, PartitionPack(0x02, 0, 0, footerPosition, headerSize - partitionPackSize, 0, 0, 0, OperationalPatternAtom)); + WriteBytes(stream, metadata); + if (fill != null) + { + WriteBytes(stream, fill); + } + + WriteBytes(stream, PartitionPack(0x03, bodyPosition, 0, 0, 0, 0, 0, BodySid, OperationalPatternAtom)); + WriteKlv(stream, TimedTextEssenceKey, xmlBytes); + + for (var i = 0; i < Resources.Count; i++) + { + var previous = i == 0 ? bodyPosition : resourcePositions[i - 1]; + WriteBytes(stream, PartitionPack(0x03, resourcePositions[i], previous, 0, 0, 0, 0, FirstResourceStreamId + (uint)i, OperationalPatternGenericStream)); + WriteKlv(stream, GenericStreamDataKey, Resources[i].Data); + } + + WriteBytes(stream, PartitionPack(0x04, footerPosition, lastPartition, footerPosition, 0, indexSegment.Length, IndexSid, 0, OperationalPatternAtom)); + WriteBytes(stream, indexSegment); + + var rip = new MemoryStream(); + WriteUInt32(rip, 0); + WriteUInt64(rip, 0); + WriteUInt32(rip, BodySid); + WriteUInt64(rip, (ulong)bodyPosition); + for (var i = 0; i < Resources.Count; i++) + { + WriteUInt32(rip, FirstResourceStreamId + (uint)i); + WriteUInt64(rip, (ulong)resourcePositions[i]); + } + + WriteUInt32(rip, 0); + WriteUInt64(rip, (ulong)footerPosition); + WriteUInt32(rip, (uint)(16 + 4 + rip.Length + 4)); + WriteKlv(stream, RandomIndexPackKey, rip.ToArray()); + } + + private byte[] MakeHeaderMetadata(TimedTextInfo info) + { + var preface = NewInstanceId(); + var identification = NewInstanceId(); + var contentStorage = NewInstanceId(); + var essenceContainerData = NewInstanceId(); + var materialPackage = NewInstanceId(); + var materialTracks = new[] { NewInstanceId(), NewInstanceId() }; + var materialSequences = new[] { NewInstanceId(), NewInstanceId() }; + var materialComponents = new[] { NewInstanceId(), NewInstanceId() }; + var sourcePackage = NewInstanceId(); + var sourceTracks = new[] { NewInstanceId(), NewInstanceId() }; + var sourceSequences = new[] { NewInstanceId(), NewInstanceId() }; + var sourceComponents = new[] { NewInstanceId(), NewInstanceId() }; + var descriptor = NewInstanceId(); + var subDescriptors = Resources.Select(_ => NewInstanceId()).ToList(); + var thisGeneration = NewInstanceId(); + var materialPackageUmid = NewInstanceId(); + var productUid = NewInstanceId(); + + var now = Timestamp(ModifiedUtc); + var version = (Assembly.GetEntryAssembly() ?? typeof(TimedTextMxfWriter).Assembly).GetName().Version ?? new Version(0, 0, 0, 0); + var productVersion = ProductVersion(version); + var editRate = Rational(info.EditRateNumerator, info.EditRateDenominator); + var duration = UInt64(info.Duration); + + var ms = new MemoryStream(); + + var primer = new MemoryStream(); + WriteUInt32(primer, (uint)PrimerEntries.Length); + WriteUInt32(primer, 18); + foreach (var entry in PrimerEntries) + { + WriteUInt16(primer, entry.Tag); + WriteBytes(primer, Hex(entry.Ul)); + } + + WriteKlv(ms, PrimerKey, primer.ToArray()); + + WriteSet(ms, 0x2f, preface, + (0x3b02, now), + (0x3b05, UInt16(0x0102)), + (0x3b07, UInt32(1)), + (0x3b08, Uuid(sourcePackage)), + (0x3b06, Batch(Uuid(identification))), + (0x3b03, Uuid(contentStorage)), + (0x3b09, OperationalPatternAtom), + (0x3b0a, Batch(EssenceContainerMultiple, EssenceContainerTimedText)), + (0x3b0b, Batch(16))); + + WriteSet(ms, 0x30, identification, + (0x3c09, Uuid(thisGeneration)), + (0x3c01, Utf16(CompanyName)), + (0x3c02, Utf16(ProductName)), + (0x3c03, productVersion), + (0x3c04, Utf16(version.ToString(3))), + (0x3c05, Uuid(productUid)), + (0x3c06, now), + (0x3c07, productVersion), + (0x3c08, Utf16("dotnet"))); + + WriteSet(ms, 0x18, contentStorage, + (0x1901, Batch(Uuid(sourcePackage), Uuid(materialPackage))), + (0x1902, Batch(Uuid(essenceContainerData)))); + + WriteSet(ms, 0x23, essenceContainerData, + (0x2701, Umid(AssetId)), + (0x3f06, UInt32(IndexSid)), + (0x3f07, UInt32(BodySid))); + + WriteSet(ms, 0x36, materialPackage, + (0x4401, Umid(materialPackageUmid)), + (0x4402, Utf16("Material Package")), + (0x4405, now), + (0x4404, now), + (0x4403, Batch(Uuid(materialTracks[0]), Uuid(materialTracks[1])))); + WriteTracks(ms, materialTracks, materialSequences, materialComponents, editRate, duration, info.TimecodeRate, Umid(AssetId), 2); + + WriteSet(ms, 0x37, sourcePackage, + (0x4401, Umid(AssetId)), + (0x4402, Utf16("File Package: SMPTE 429-5 clip wrapping of D-Cinema Timed Text data")), + (0x4405, now), + (0x4404, now), + (0x4403, Batch(Uuid(sourceTracks[0]), Uuid(sourceTracks[1]))), + (0x4701, Uuid(descriptor))); + WriteTracks(ms, sourceTracks, sourceSequences, sourceComponents, editRate, duration, info.TimecodeRate, new byte[32], 0); + + var descriptorItems = new List<(ushort, byte[])>(); + if (subDescriptors.Count > 0) + { + descriptorItems.Add((0xffff, Batch(subDescriptors.Select(Uuid).ToArray()))); + } + + descriptorItems.Add((0x3006, UInt32(2))); + descriptorItems.Add((0x3001, editRate)); + descriptorItems.Add((0x3002, duration)); + descriptorItems.Add((0x3004, EssenceContainerTimedText)); + descriptorItems.Add((0x3e01, new byte[16])); + descriptorItems.Add((0xfffe, Uuid(info.ResourceId))); + descriptorItems.Add((0xfffd, Utf16("UTF-8"))); + descriptorItems.Add((0xfffc, Utf16(info.NamespaceUri))); + if (!string.IsNullOrEmpty(info.Language)) + { + descriptorItems.Add((0xfffb, Utf16(info.Language))); + } + + WriteSet(ms, 0x64, descriptor, descriptorItems.ToArray()); + + for (var i = 0; i < Resources.Count; i++) + { + WriteSet(ms, 0x65, subDescriptors[i], + (0xfffa, Uuid(Resources[i].Id)), + (0xfff9, Utf16(Resources[i].MimeType)), + (0x3f07, UInt32(FirstResourceStreamId + (uint)i))); + } + + return ms.ToArray(); + } + + /// + /// A package's timecode track (1) and timed text track (2), each with a sequence of one component. + /// + private static void WriteTracks(Stream ms, Guid[] tracks, Guid[] sequences, Guid[] components, byte[] editRate, byte[] duration, int timecodeRate, byte[] sourcePackageUmid, uint sourceTrackId) + { + WriteSet(ms, 0x3b, tracks[0], + (0x4801, UInt32(1)), + (0x4804, UInt32(0)), + (0x4802, Utf16("Timecode Track")), + (0x4803, Uuid(sequences[0])), + (0x4b01, editRate), + (0x4b02, UInt64(0))); + WriteSet(ms, 0x0f, sequences[0], + (0x0201, DataDefinitionTimecode), + (0x0202, duration), + (0x1001, Batch(Uuid(components[0])))); + WriteSet(ms, 0x14, components[0], + (0x0201, DataDefinitionTimecode), + (0x0202, duration), + (0x1502, UInt16((ushort)timecodeRate)), + (0x1501, UInt64(0)), + (0x1503, new byte[] { 0 })); + + WriteSet(ms, 0x3b, tracks[1], + (0x4801, UInt32(2)), + (0x4804, UInt32(0)), + (0x4802, Utf16("Timed Text Track")), + (0x4803, Uuid(sequences[1])), + (0x4b01, editRate), + (0x4b02, UInt64(0))); + WriteSet(ms, 0x0f, sequences[1], + (0x0201, DataDefinitionData), + (0x0202, duration), + (0x1001, Batch(Uuid(components[1])))); + WriteSet(ms, 0x11, components[1], + (0x0201, DataDefinitionData), + (0x0202, duration), + (0x1201, UInt64(0)), + (0x1101, sourcePackageUmid), + (0x1102, UInt32(sourceTrackId))); + } + + /// + /// The footer index: one edit unit (the clip-wrapped XML) at stream offset 0. + /// + private byte[] MakeIndexTableSegment(TimedTextInfo info) + { + var ms = new MemoryStream(); + WriteLocalSetItems(ms, + (0x3c0a, Uuid(NewInstanceId())), + (0x3f0b, Rational(info.EditRateNumerator, info.EditRateDenominator)), + (0x3f0c, UInt64(0)), + (0x3f0d, UInt64(1)), + (0x3f05, UInt32(0)), + (0x3f06, UInt32(IndexSid)), + (0x3f07, UInt32(BodySid)), + (0x3f08, new byte[] { 0 }), + (0x3f0e, new byte[] { 0 }), + (0x3f09, BatchOfSize(6, new byte[6])), + (0x3f0a, BatchOfSize(11, new byte[] { 0, 0, 0x80, 0, 0, 0, 0, 0, 0, 0, 0 }))); + return Klv(IndexTableSegmentKey, ms.ToArray()); + } + + private static byte[] PartitionPack(byte kind, long thisPartition, long previousPartition, long footerPartition, long headerByteCount, long indexByteCount, uint indexSid, uint bodySid, byte[] operationalPattern) + { + var key = Hex("060e2b34020501010d01020101000400"); + key[13] = kind; + if (operationalPattern == OperationalPatternGenericStream) + { + key[14] = 0x11; // generic stream partition + } + + var ms = new MemoryStream(); + WriteUInt16(ms, 1); // major version + WriteUInt16(ms, 2); // minor version + WriteUInt32(ms, 1); // KAG size + WriteUInt64(ms, (ulong)thisPartition); + WriteUInt64(ms, (ulong)previousPartition); + WriteUInt64(ms, (ulong)footerPartition); + WriteUInt64(ms, (ulong)headerByteCount); + WriteUInt64(ms, (ulong)indexByteCount); + WriteUInt32(ms, indexSid); + WriteUInt64(ms, 0); // body offset + WriteUInt32(ms, bodySid); + WriteBytes(ms, operationalPattern); + WriteBytes(ms, Batch(EssenceContainerMultiple, EssenceContainerTimedText)); + return Klv(key, ms.ToArray()); + } + + private static void WriteSet(Stream stream, byte setType, Guid instanceId, params (ushort Tag, byte[] Value)[] items) + { + var key = Hex("060e2b34025301010d01010101010000"); + key[14] = setType; + var ms = new MemoryStream(); + WriteLocalSetItems(ms, (0x3c0a, Uuid(instanceId))); + WriteLocalSetItems(ms, items); + WriteKlv(stream, key, ms.ToArray()); + } + + private static void WriteLocalSetItems(Stream stream, params (ushort Tag, byte[] Value)[] items) + { + foreach (var item in items) + { + WriteUInt16(stream, item.Tag); + WriteUInt16(stream, (ushort)item.Value.Length); + WriteBytes(stream, item.Value); + } + } + + private static long KlvSize(long length) => 16 + BerLength(length).Length + length; + + private static byte[] Klv(byte[] key, byte[] value) + { + var ms = new MemoryStream(); + WriteKlv(ms, key, value); + return ms.ToArray(); + } + + private static void WriteKlv(Stream stream, byte[] key, byte[] value) + { + WriteBytes(stream, key); + WriteBytes(stream, BerLength(value.Length)); + WriteBytes(stream, value); + } + + /// + /// Four-byte BER length like asdcplib, eight bytes for values of 16 MiB or more. + /// + private static byte[] BerLength(long length) + { + if (length < 0x1000000) + { + return new[] { (byte)0x83, (byte)(length >> 16), (byte)(length >> 8), (byte)length }; + } + + var bytes = new byte[9]; + bytes[0] = 0x88; + for (var i = 0; i < 8; i++) + { + bytes[8 - i] = (byte)(length >> (8 * i)); + } + + return bytes; + } + + private static byte[] Batch(params byte[][] items) => BatchOfSize(16, items); + + private static byte[] Batch(int itemSize) => BatchOfSize(itemSize); + + private static byte[] BatchOfSize(int itemSize, params byte[][] items) + { + var ms = new MemoryStream(); + WriteUInt32(ms, (uint)items.Length); + WriteUInt32(ms, (uint)itemSize); + foreach (var item in items) + { + WriteBytes(ms, item); + } + + return ms.ToArray(); + } + + /// + /// UUID in RFC 4122 byte order (Guid.ToByteArray swaps the first three fields). + /// + private static byte[] Uuid(Guid guid) => Hex(guid.ToString("N")); + + private static byte[] Umid(Guid material) + { + var umid = new byte[32]; + Array.Copy(UmidPrefix, umid, 16); + Array.Copy(Uuid(material), 0, umid, 16, 16); + return umid; + } + + private static byte[] Rational(int numerator, int denominator) + { + var bytes = new byte[8]; + Array.Copy(UInt32((uint)numerator), 0, bytes, 0, 4); + Array.Copy(UInt32((uint)denominator), 0, bytes, 4, 4); + return bytes; + } + + private static byte[] Timestamp(DateTime utc) + { + return new[] + { + (byte)(utc.Year >> 8), (byte)utc.Year, (byte)utc.Month, (byte)utc.Day, + (byte)utc.Hour, (byte)utc.Minute, (byte)utc.Second, (byte)(utc.Millisecond / 4), + }; + } + + private static byte[] ProductVersion(Version version) + { + var ms = new MemoryStream(); + WriteUInt16(ms, (ushort)Math.Max(0, version.Major)); + WriteUInt16(ms, (ushort)Math.Max(0, version.Minor)); + WriteUInt16(ms, (ushort)Math.Max(0, version.Build)); + WriteUInt16(ms, (ushort)Math.Max(0, version.Revision)); + WriteUInt16(ms, 0); // release type: unknown + return ms.ToArray(); + } + + private static byte[] Utf16(string s) => Encoding.BigEndianUnicode.GetBytes(s ?? string.Empty); + + private static byte[] UInt16(ushort value) => new[] { (byte)(value >> 8), (byte)value }; + + private static byte[] UInt32(uint value) => new[] { (byte)(value >> 24), (byte)(value >> 16), (byte)(value >> 8), (byte)value }; + + private static byte[] UInt64(long value) => UInt64((ulong)value); + + private static byte[] UInt64(ulong value) + { + var bytes = new byte[8]; + for (var i = 0; i < 8; i++) + { + bytes[7 - i] = (byte)(value >> (8 * i)); + } + + return bytes; + } + + private static void WriteUInt16(Stream stream, ushort value) => WriteBytes(stream, UInt16(value)); + + private static void WriteUInt32(Stream stream, uint value) => WriteBytes(stream, UInt32(value)); + + private static void WriteUInt64(Stream stream, ulong value) => WriteBytes(stream, UInt64(value)); + + private static void WriteBytes(Stream stream, byte[] bytes) => stream.Write(bytes, 0, bytes.Length); + + private static byte[] Hex(string hex) + { + var bytes = new byte[hex.Length / 2]; + for (var i = 0; i < bytes.Length; i++) + { + bytes[i] = Convert.ToByte(hex.Substring(i * 2, 2), 16); + } + + return bytes; + } + + /// + /// What the descriptor needs from the subtitle XML: Id, namespace, language, edit rate, + /// and the duration in edit units from StartTime to the latest TimeOut (like asdcplib). + /// + private class TimedTextInfo + { + public string NamespaceUri { get; private set; } + public Guid ResourceId { get; private set; } + public string Language { get; private set; } + public int EditRateNumerator { get; private set; } + public int EditRateDenominator { get; private set; } + public int TimecodeRate { get; private set; } + public long Duration { get; private set; } + + public static TimedTextInfo Parse(string xml) + { + var doc = new XmlDocument { XmlResolver = null }; + doc.LoadXml(xml); + var root = doc.DocumentElement ?? throw new InvalidDataException("Timed text XML has no root element"); + var info = new TimedTextInfo { NamespaceUri = root.NamespaceURI }; + + var id = Child(root, "Id"); + if (id == null || !Guid.TryParse(id.Trim().Replace("urn:uuid:", string.Empty), out var resourceId)) + { + throw new InvalidDataException("Timed text XML has no urn:uuid Id"); + } + + info.ResourceId = resourceId; + info.Language = Child(root, "Language")?.Trim(); + + var editRate = (Child(root, "EditRate") ?? string.Empty).Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); + if (editRate.Length != 2 || + !int.TryParse(editRate[0], NumberStyles.None, CultureInfo.InvariantCulture, out var numerator) || + !int.TryParse(editRate[1], NumberStyles.None, CultureInfo.InvariantCulture, out var denominator) || + numerator <= 0 || denominator <= 0) + { + throw new InvalidDataException("Timed text XML has no valid EditRate"); + } + + info.EditRateNumerator = numerator; + info.EditRateDenominator = denominator; + info.TimecodeRate = (int)Math.Round(numerator / (double)denominator); + + var start = FramesFromTimeCode(Child(root, "StartTime"), info.TimecodeRate); + long end = 0; + foreach (var element in root.GetElementsByTagName("*").OfType().Where(e => e.LocalName == "Subtitle")) + { + end = Math.Max(end, FramesFromTimeCode(element.GetAttribute("TimeOut"), info.TimecodeRate)); + } + + if (end <= start) + { + throw new InvalidDataException("Timed text XML has no subtitles"); + } + + info.Duration = end - start; + return info; + } + + private static string Child(XmlElement root, string localName) + { + return root.ChildNodes.OfType().FirstOrDefault(e => e.LocalName == localName)?.InnerText; + } + + /// + /// HH:MM:SS:FF (frames) or HH:MM:SS:EEE at TimeCodeRate ticks - both count frames here. + /// + private static long FramesFromTimeCode(string timeCode, int frameRate) + { + var parts = (timeCode ?? string.Empty).Trim().Split(':'); + if (parts.Length != 4 || parts.Any(p => !int.TryParse(p, NumberStyles.None, CultureInfo.InvariantCulture, out _))) + { + return 0; + } + + var p0 = int.Parse(parts[0], CultureInfo.InvariantCulture); + var p1 = int.Parse(parts[1], CultureInfo.InvariantCulture); + var p2 = int.Parse(parts[2], CultureInfo.InvariantCulture); + var p3 = int.Parse(parts[3], CultureInfo.InvariantCulture); + return ((p0 * 60L + p1) * 60 + p2) * frameRate + p3; + } + } + } +} diff --git a/src/ui/Features/Files/FormatProperties/DcinemaSmpteProperties/DCinemaSmptePropertiesViewModel.cs b/src/ui/Features/Files/FormatProperties/DcinemaSmpteProperties/DCinemaSmptePropertiesViewModel.cs index 85687a05347..a9c6434f03d 100644 --- a/src/ui/Features/Files/FormatProperties/DcinemaSmpteProperties/DCinemaSmptePropertiesViewModel.cs +++ b/src/ui/Features/Files/FormatProperties/DcinemaSmpteProperties/DCinemaSmptePropertiesViewModel.cs @@ -6,6 +6,7 @@ using Nikse.SubtitleEdit.Core.Common; using Nikse.SubtitleEdit.Core.Forms; using Nikse.SubtitleEdit.Core.SubtitleFormats; +using Nikse.SubtitleEdit.Features.Shared; using Nikse.SubtitleEdit.Features.Shared.ColorPicker; using Nikse.SubtitleEdit.Logic; using Nikse.SubtitleEdit.Logic.Config; @@ -13,6 +14,7 @@ using System; using System.Collections.ObjectModel; using System.Globalization; +using System.IO; using System.Linq; using System.Threading.Tasks; @@ -46,6 +48,10 @@ public partial class DCinemaSmptePropertiesViewModel : ObservableObject [ObservableProperty] private int _fadeUpTime; [ObservableProperty] private int _fadeDownTime; + // MXF track file export: the font embedded as the LoadFont resource + [ObservableProperty] private bool _isMxfExport; + [ObservableProperty] private string _mxfFontFile = string.Empty; + public Window? Window { get; set; } public Subtitle Subtitle { get; set; } = new(); @@ -183,6 +189,8 @@ private void LoadSettings() FadeUpTime = ss.DCinemaFadeUpTime; FadeDownTime = ss.DCinemaFadeDownTime; } + + MxfFontFile = ss.MxfFontFile ?? string.Empty; } private void SaveSettings() @@ -226,6 +234,10 @@ private void SaveSettings() ss.DCinemaBottomMargin = TopBottomMargin; ss.DCinemaFadeUpTime = FadeUpTime; ss.DCinemaFadeDownTime = FadeDownTime; + if (IsMxfExport) + { + ss.MxfFontFile = MxfFontFile; + } Se.SaveSettings(); } @@ -425,8 +437,39 @@ private async Task Export() } [RelayCommand] - private void Ok() + private async Task BrowseMxfFontFile() { + if (Window == null) + { + return; + } + + var folder = string.IsNullOrEmpty(MxfFontFile) ? null : Path.GetDirectoryName(MxfFontFile); + var fileName = await _fileHelper.PickOpenFile(Window, Se.Language.File.PropertiesDCinema.ChooseFontFile, Se.Language.General.Font, ".ttf;.otf", suggestedStartFolder: folder); + if (!string.IsNullOrEmpty(fileName)) + { + MxfFontFile = fileName; + } + } + + [RelayCommand] + private async Task Ok() + { + if (IsMxfExport) + { + if (string.IsNullOrWhiteSpace(MxfFontFile) || !File.Exists(MxfFontFile)) + { + await MessageBox.Show(Window!, Se.Language.General.Error, Se.Language.File.PropertiesDCinema.FontFileMissing, MessageBoxButtons.OK, MessageBoxIcon.Error); + return; + } + + // The track file carries the font under the LoadFont urn:uuid + if (!FontUri.StartsWith("urn:uuid:", StringComparison.OrdinalIgnoreCase) || !Guid.TryParse(FontUri.Substring(9), out _)) + { + FontUri = "urn:uuid:" + Guid.NewGuid(); + } + } + SaveSettings(); OkPressed = true; Window?.Close(); @@ -462,6 +505,13 @@ internal void Initialize(Subtitle subtitle, SubtitleFormat format) InitializeCore(subtitle, format); } + internal void InitializeMxfExport(Subtitle subtitle) + { + IsMxfExport = true; + InitializeCore(subtitle, null); + WindowTitle = string.Format(Se.Language.File.XProperties, Se.Language.File.Export.TitleExportDCinemaSmpte2014Mxf); + } + private void InitializeCore(Subtitle? subtitle, SubtitleFormat? format) { if (subtitle != null) diff --git a/src/ui/Features/Files/FormatProperties/DcinemaSmpteProperties/DCinemaSmptePropertiesWindow.cs b/src/ui/Features/Files/FormatProperties/DcinemaSmpteProperties/DCinemaSmptePropertiesWindow.cs index a13304cb3c3..22197a3646f 100644 --- a/src/ui/Features/Files/FormatProperties/DcinemaSmpteProperties/DCinemaSmptePropertiesWindow.cs +++ b/src/ui/Features/Files/FormatProperties/DcinemaSmpteProperties/DCinemaSmptePropertiesWindow.cs @@ -1,3 +1,4 @@ +using Avalonia; using Avalonia.Controls; using Avalonia.Data; using Nikse.SubtitleEdit.Logic; @@ -62,7 +63,13 @@ public DCinemaSmptePropertiesWindow(DCinemaSmptePropertiesViewModel vm) var numericUpDownFadeUpTime = FormatPropertiesChrome.Number(UiUtil.MakeNumericUpDownInt(0, 50, 0, 90, vm, nameof(vm.FadeUpTime)), lang.FadeUpTime); var numericUpDownFadeDownTime = FormatPropertiesChrome.Number(UiUtil.MakeNumericUpDownInt(0, 50, 0, 90, vm, nameof(vm.FadeDownTime)), lang.FadeDownTime); + var textBoxMxfFontFile = FormatPropertiesChrome.Text(UiUtil.MakeTextBox(340, vm, nameof(vm.MxfFontFile)), lang.FontFile); + var buttonBrowseMxfFontFile = FormatPropertiesChrome.MakeAction("fa-solid fa-folder-open", lang.ChooseFontFile, vm.BrowseMxfFontFileCommand); + var rowMxfFontFile = FormatPropertiesChrome.Row(lang.FontFile, textBoxMxfFontFile, buttonBrowseMxfFontFile); + rowMxfFontFile.Bind(Visual.IsVisibleProperty, new Binding(nameof(vm.IsMxfExport)) { Source = vm, Mode = BindingMode.OneWay }); + var font = FormatPropertiesChrome.MakeRows( + rowMxfFontFile, FormatPropertiesChrome.Row(lang.FontId, textBoxFontId), FormatPropertiesChrome.Row(lang.FontUri, textBoxFontUri, buttonGenerateFontUri), FormatPropertiesChrome.Row(Se.Language.General.FontColor, buttonFontColor), diff --git a/src/ui/Features/Main/Layout/InitMenu.cs b/src/ui/Features/Main/Layout/InitMenu.cs index 24ff75f2a58..b5ce65c23c7 100644 --- a/src/ui/Features/Main/Layout/InitMenu.cs +++ b/src/ui/Features/Main/Layout/InitMenu.cs @@ -276,6 +276,11 @@ public static void Make(MainViewModel vm) Command = vm.ExportDCinemaSmpte2014PngCommand, }, new MenuItem + { + Header = Se.Language.File.Export.TitleExportDCinemaSmpte2014Mxf, + Command = vm.ExportDCinemaSmpte2014MxfCommand, + }, + new MenuItem { Header = Ebu.NameOfFormat, Command = vm.ExportEbuStlCommand, diff --git a/src/ui/Features/Main/Layout/InitNativeMacMenu.cs b/src/ui/Features/Main/Layout/InitNativeMacMenu.cs index bb2c938ac51..6789ea5e8db 100644 --- a/src/ui/Features/Main/Layout/InitNativeMacMenu.cs +++ b/src/ui/Features/Main/Layout/InitNativeMacMenu.cs @@ -251,6 +251,7 @@ public static void MakeStructure(NativeMenu root, Window window) exportItems.Items.Add(Item(lExport.TitleExportDvbTeletext, v => v.ExportDvbTeletextCommand)); exportItems.Items.Add(Item(lExport.TitleExportDCinemaInteropPng, v => v.ExportDCinemaInteropPngCommand)); exportItems.Items.Add(Item(lExport.TitleExportDCinemaSmpte2014Png, v => v.ExportDCinemaSmpte2014PngCommand)); + exportItems.Items.Add(Item(lExport.TitleExportDCinemaSmpte2014Mxf, v => v.ExportDCinemaSmpte2014MxfCommand)); exportItems.Items.Add(Item(Ebu.NameOfFormat, v => v.ExportEbuStlCommand)); exportItems.Items.Add(Item(lExport.TitleExportDostPng, v => v.ExportDostPngCommand)); exportItems.Items.Add(Item(lExport.TitleExportDvdSup, v => v.ExportDvdSupCommand)); diff --git a/src/ui/Features/Main/MainViewModel.cs b/src/ui/Features/Main/MainViewModel.cs index d2f48583224..61dc0dd32a9 100644 --- a/src/ui/Features/Main/MainViewModel.cs +++ b/src/ui/Features/Main/MainViewModel.cs @@ -5163,6 +5163,65 @@ private async Task ExportDCinemaSmpte2014Png() } } + /// + /// SMPTE ST 429-5 timed text track file - the subtitle asset of a SMPTE DCP: the D-Cinema + /// SMPTE 2014 XML wrapped in MXF with the font it loads embedded. + /// + [RelayCommand] + private async Task ExportDCinemaSmpte2014Mxf() + { + if (Window == null) + { + return; + } + + if (IsEmpty) + { + ShowSubtitleNotLoadedMessage(); + return; + } + + CopyCurrentDCinemaSettingsToSe(); + var result = await ShowDialogAsync(vm => { vm.InitializeMxfExport(GetUpdateSubtitle()); }); + CopyCurrentDCinemaSettingsFromSe(); + SetLibSeSettings(); + if (!result.OkPressed) + { + return; + } + + var title = Se.Language.File.Export.TitleExportDCinemaSmpte2014Mxf; + var fileName = await _fileHelper.PickSaveFile( + Window!, + new[] { (title, ".mxf") }, + Utilities.GetPathAndFileNameWithoutExtension(GetNewFileName()) + ".mxf", + string.Format(Se.Language.Main.SaveXFileAs, title)); + if (string.IsNullOrEmpty(fileName)) + { + return; + } + + try + { + var xml = new DCinemaSmpte2014().ToText(GetExportSubtitle(), Path.GetFileNameWithoutExtension(fileName)); + var fontId = Guid.Parse(Configuration.Settings.SubtitleSettings.CurrentDCinemaFontUri.Substring("urn:uuid:".Length)); + var writer = new TimedTextMxfWriter(); + writer.Resources.Add(new TimedTextMxfResource(fontId, TimedTextMxfResource.MimeTypeFont, await File.ReadAllBytesAsync(result.MxfFontFile))); + writer.Write(fileName, xml); + } + catch (Exception ex) + { + SeLogger.Error(ex, "Export to D-Cinema SMPTE MXF failed for " + fileName); + await MessageBox.Show(Window!, Se.Language.General.Error, + string.Format(Se.Language.General.CouldNotSaveFileXErrorY, fileName, ex.Message), + MessageBoxButtons.OK, MessageBoxIcon.Error); + return; + } + + ShowStatus(string.Format(Se.Language.Main.FileExportedInFormatXToY, title, fileName)); + await ShowExportFileSavedPrompt(fileName); + } + [RelayCommand] private async Task ExportDostPng() { diff --git a/src/ui/Logic/Config/Language/File/LanguageExport.cs b/src/ui/Logic/Config/Language/File/LanguageExport.cs index bd7010857d9..8c4e8fa7722 100644 --- a/src/ui/Logic/Config/Language/File/LanguageExport.cs +++ b/src/ui/Logic/Config/Language/File/LanguageExport.cs @@ -47,6 +47,7 @@ public class LanguageExport public string AddLineBetweenSubtitles { get; set; } public string TitleExportDCinemaInteropPng { get; set; } public string TitleExportDCinemaSmpte2014Png { get; set; } + public string TitleExportDCinemaSmpte2014Mxf { get; set; } public string ImageBasedSubtitleSaved { get; set; } public string FullFrameImage { get; set; } public string FullFrameImageHint { get; set; } @@ -137,6 +138,7 @@ public LanguageExport() AddLineBetweenSubtitles = "Add line between subtitles"; TitleExportDCinemaInteropPng = "D-Cinema interop/png"; TitleExportDCinemaSmpte2014Png = "D-Cinema SMPTE 2014/png"; + TitleExportDCinemaSmpte2014Mxf = "D-Cinema SMPTE 2014 (MXF)"; ImageBasedSubtitleSaved = "Image-based subtitle saved"; FullFrameImage = "Full frame image"; FullFrameImageHint = "Make each image the size of the video frame, with the subtitle in its place"; diff --git a/src/ui/Logic/Config/Language/File/LanguageFilePropertiesDCinema.cs b/src/ui/Logic/Config/Language/File/LanguageFilePropertiesDCinema.cs index 1ce18658b66..7148cc1aa8b 100644 --- a/src/ui/Logic/Config/Language/File/LanguageFilePropertiesDCinema.cs +++ b/src/ui/Logic/Config/Language/File/LanguageFilePropertiesDCinema.cs @@ -23,6 +23,9 @@ public class LanguageFilePropertiesDCinema public string FadeDownTime { get; internal set; } public string ZPosition { get; internal set; } public string ZPositionHelp { get; internal set; } + public string FontFile { get; internal set; } + public string ChooseFontFile { get; internal set; } + public string FontFileMissing { get; internal set; } public LanguageFilePropertiesDCinema() { @@ -47,6 +50,9 @@ public LanguageFilePropertiesDCinema() FadeDownTime = "Fade down time"; ZPosition = "Z-position"; ZPositionHelp = "Positive numbers move text away, negative numbers move text closer, if z-position is zero then it's 2D"; + FontFile = "Font file"; + ChooseFontFile = "Choose font file"; + FontFileMissing = "Choose the TrueType/OpenType font file to embed in the MXF track file."; } } \ No newline at end of file diff --git a/src/ui/Logic/Config/SeDCinemaSmpte.cs b/src/ui/Logic/Config/SeDCinemaSmpte.cs index e255bc3119a..b6de471097f 100644 --- a/src/ui/Logic/Config/SeDCinemaSmpte.cs +++ b/src/ui/Logic/Config/SeDCinemaSmpte.cs @@ -10,6 +10,7 @@ public class SeDCinemaSmpte public int DCinemaFadeUpTime { get; set; } public int DCinemaFadeDownTime { get; set; } public bool DCinemaAutoGenerateSubtitleId { get; set; } + public string MxfFontFile { get; set; } public string CurrentDCinemaSubtitleId { get; set; } public string CurrentDCinemaMovieTitle { get; set; } @@ -36,6 +37,7 @@ public SeDCinemaSmpte() DCinemaFadeUpTime = 0; DCinemaFadeDownTime = 0; DCinemaAutoGenerateSubtitleId = true; + MxfFontFile = string.Empty; CurrentDCinemaSubtitleId = string.Empty; CurrentDCinemaMovieTitle = string.Empty; diff --git a/tests/UI/Features/Files/DCinemaSmptePropertiesMxfTests.cs b/tests/UI/Features/Files/DCinemaSmptePropertiesMxfTests.cs new file mode 100644 index 00000000000..ee1f714637b --- /dev/null +++ b/tests/UI/Features/Files/DCinemaSmptePropertiesMxfTests.cs @@ -0,0 +1,96 @@ +using Avalonia.Headless.XUnit; +using Avalonia.Threading; +using Microsoft.Extensions.DependencyInjection; +using Nikse.SubtitleEdit; +using Nikse.SubtitleEdit.Core.Common; +using Nikse.SubtitleEdit.Core.SubtitleFormats; +using Nikse.SubtitleEdit.Features.Files.FormatProperties.DCinemaSmpteProperties; +using Nikse.SubtitleEdit.Logic.Config; + +namespace UITests.Features.Files; + +public class DCinemaSmptePropertiesMxfTests +{ + private static DCinemaSmptePropertiesViewModel MakeViewModel() + { + var services = new ServiceCollection(); + services.AddSubtitleEditServices(); + var provider = services.BuildServiceProvider(); + return provider.GetRequiredService(); + } + + private static Subtitle MakeSubtitle() + { + var subtitle = new Subtitle(); + subtitle.Paragraphs.Add(new Paragraph("Hello", 1000, 2000)); + return subtitle; + } + + /// + /// The MXF track file embeds the font under the LoadFont urn:uuid, so OK stores the chosen font + /// file and turns a font URI that is not a urn:uuid (an Interop-style file name) into one. + /// + [AvaloniaFact] + public void MxfExport_Ok_StoresFontFileAndMakesFontUriAUuid() + { + Se.Settings.File.DCinemaSmpte = new SeDCinemaSmpte { CurrentDCinemaFontUri = "Arial.ttf" }; + var fontFile = Path.GetTempFileName(); + var vm = MakeViewModel(); + vm.InitializeMxfExport(MakeSubtitle()); + var window = new DCinemaSmptePropertiesWindow(vm); + try + { + window.Show(); + Dispatcher.UIThread.RunJobs(); + Assert.True(vm.IsMxfExport); + Assert.Contains("MXF", vm.WindowTitle); + + vm.MxfFontFile = fontFile; + vm.OkCommand.Execute(null); + Dispatcher.UIThread.RunJobs(); + + Assert.True(vm.OkPressed); + var ss = Se.Settings.File.DCinemaSmpte; + Assert.Equal(fontFile, ss.MxfFontFile); + Assert.StartsWith("urn:uuid:", ss.CurrentDCinemaFontUri); + Assert.True(Guid.TryParse(ss.CurrentDCinemaFontUri.Substring(9), out _)); + } + finally + { + if (!vm.OkPressed) + { + window.Close(); + } + + File.Delete(fontFile); + } + } + + [AvaloniaFact] + public void Properties_Ok_DoesNotNeedFontFile() + { + Se.Settings.File.DCinemaSmpte = new SeDCinemaSmpte(); + var vm = MakeViewModel(); + vm.Initialize(MakeSubtitle(), new DCinemaSmpte2014()); + var window = new DCinemaSmptePropertiesWindow(vm); + try + { + window.Show(); + Dispatcher.UIThread.RunJobs(); + Assert.False(vm.IsMxfExport); + + vm.OkCommand.Execute(null); + Dispatcher.UIThread.RunJobs(); + + Assert.True(vm.OkPressed); + Assert.Equal(string.Empty, Se.Settings.File.DCinemaSmpte.MxfFontFile); + } + finally + { + if (!vm.OkPressed) + { + window.Close(); + } + } + } +} diff --git a/tests/libse/ContainerFormats/TimedTextMxfWriterTest.cs b/tests/libse/ContainerFormats/TimedTextMxfWriterTest.cs new file mode 100644 index 00000000000..23f5f6e1e99 --- /dev/null +++ b/tests/libse/ContainerFormats/TimedTextMxfWriterTest.cs @@ -0,0 +1,139 @@ +using Nikse.SubtitleEdit.Core.ContainerFormats.MaterialExchangeFormat; + +namespace LibSETests.ContainerFormats; + +public class TimedTextMxfWriterTest +{ + private static readonly Guid AssetId = Guid.Parse("11111111-2222-3333-4444-555555555555"); + private static readonly Guid FontId = Guid.Parse("3dec6dc0-39d0-498d-97d0-928d2eb78391"); + private static readonly Guid ImageId = Guid.Parse("aaaaaaaa-0000-4000-8000-000000000001"); + private static readonly byte[] Font = Enumerable.Range(0, 512).Select(i => (byte)i).ToArray(); + private static readonly byte[] Png = Convert.FromHexString( + "89504e470d0a1a0a0000000d494844520000000a000000040806000000b738aecd0000001249444154789c63f8cfc0f09f18cc30700a01887b4fb11f4f85f20000000049454e44ae426082"); + + // local set tags holding instance ids, references to them or dates - random/now per file + private static readonly HashSet VolatileTags = new() { 0x3c0a, 0x3b02, 0x3b03, 0x3b06, 0x3b08, 0x1901, 0x1902, 0x4403, 0x4404, 0x4405, 0x4803, 0x1001, 0x4701, 0xffff }; + + private static string FilePath(string name) => Path.Combine(Directory.GetCurrentDirectory(), "Files", name); + + private static byte[] WriteSample(string xml) + { + var writer = new TimedTextMxfWriter { AssetId = AssetId }; + writer.Resources.Add(new TimedTextMxfResource(FontId, TimedTextMxfResource.MimeTypeFont, Font)); + writer.Resources.Add(new TimedTextMxfResource(ImageId, TimedTextMxfResource.MimeTypePng, Png)); + using var ms = new MemoryStream(); + writer.Write(ms, xml); + return ms.ToArray(); + } + + /// + /// The reference was made by asdcplib 2.13.3 (asdcp-wrap -L) from the same XML, font and PNG. + /// Everything but the identification set and the random instance ids/dates must match - the + /// fill after the header metadata keeps all partition offsets identical too. + /// + [Fact] + public void MatchesAsdcplibReference() + { + var reference = ReadKlvs(File.ReadAllBytes(FilePath("sample_mxf_429-5_asdcplib.mxf"))); + var written = ReadKlvs(WriteSample(File.ReadAllText(FilePath("sample_mxf_429-5_asdcplib.xml")))); + + Assert.Equal(reference.Select(k => Convert.ToHexString(k.Key)), written.Select(k => Convert.ToHexString(k.Key))); + for (var i = 0; i < reference.Count; i++) + { + var key = reference[i].Key; + var isIdentification = key[5] == 0x53 && key[13] == 0x01 && key[14] == 0x30; + var isFill = key[4] == 0x01 && key[8] == 0x03 && key[9] == 0x01; + if (isIdentification || isFill) + { + continue; + } + + var isLocalSet = key[4] == 0x02 && key[5] == 0x53; + if (!isLocalSet) + { + Assert.Equal(reference[i].Offset, written[i].Offset); + Assert.Equal(Convert.ToHexString(reference[i].Value), Convert.ToHexString(written[i].Value)); + continue; + } + + var isMaterialPackage = key[14] == 0x36; + var referenceItems = ReadLocalSet(reference[i].Value); + var writtenItems = ReadLocalSet(written[i].Value); + Assert.Equal(referenceItems.Select(x => x.Tag), writtenItems.Select(x => x.Tag)); + for (var j = 0; j < referenceItems.Count; j++) + { + var tag = referenceItems[j].Tag; + if (VolatileTags.Contains(tag) || (isMaterialPackage && tag == 0x4401)) + { + Assert.Equal(referenceItems[j].Value.Length, writtenItems[j].Value.Length); + continue; + } + + Assert.True(referenceItems[j].Value.SequenceEqual(writtenItems[j].Value), $"set {key[14]:x2} tag {tag:x4} differs"); + } + } + } + + [Fact] + public void ReadBackWithMxfParser() + { + var xml = File.ReadAllText(FilePath("sample_mxf_429-5_asdcplib.xml")); + var parser = new MxfParser(new MemoryStream(WriteSample(xml))); + + Assert.True(parser.IsValid); + Assert.Equal(xml, Assert.Single(parser.GetSubtitles())); + Assert.Equal(Png, Assert.Single(parser.GetImages())); + } + + [Fact] + public void XmlWithoutIdThrows() + { + var xml = File.ReadAllText(FilePath("sample_mxf_429-5_asdcplib.xml")); + xml = xml.Replace("urn:uuid:7be835a3-cfb4-43d0-bb4b-f0b4c95e962e", string.Empty); + Assert.Throws(() => WriteSample(xml)); + } + + private static List<(long Offset, byte[] Key, byte[] Value)> ReadKlvs(byte[] data) + { + var list = new List<(long, byte[], byte[])>(); + var i = 0; + while (i + 17 <= data.Length) + { + var key = data[i..(i + 16)]; + int length = data[i + 16]; + var lengthSize = 1; + if (length >= 0x80) + { + var count = length & 0x7f; + length = 0; + for (var j = 0; j < count; j++) + { + length = length * 256 + data[i + 17 + j]; + } + + lengthSize += count; + } + + var start = i + 16 + lengthSize; + list.Add((i, key, data[start..(start + length)])); + i = start + length; + } + + return list; + } + + private static List<(int Tag, byte[] Value)> ReadLocalSet(byte[] value) + { + var list = new List<(int, byte[])>(); + var i = 0; + while (i + 4 <= value.Length) + { + var tag = (value[i] << 8) | value[i + 1]; + var length = (value[i + 2] << 8) | value[i + 3]; + list.Add((tag, value[(i + 4)..(i + 4 + length)])); + i += 4 + length; + } + + return list; + } +} diff --git a/tests/libse/Files/sample_mxf_429-5_asdcplib.mxf b/tests/libse/Files/sample_mxf_429-5_asdcplib.mxf new file mode 100644 index 0000000000000000000000000000000000000000..349b392ace5a12416b2cc264d932bdc94f878ac0 GIT binary patch literal 19281 zcmeHPdwf&%7609vN83P4T0kiE5rZ4P(%!V8ucQgkkR}vcqye}2Oq1I-*e12PwN<95 zT@}kfROSOabm&y@!BmjV2lFvSR0JQWL!GZ}0y;rZ5QJvu{O%*orHZiqvCn7s*Y@V- zd(ZDYzQ1$Mz5V0}%2poqTfvqAuse8!F02)E39<-rO zvnYi6%o484W^@cYkCEj}^!rGL!Rv}I1_pf5FNJxN;V+M+R~hs%Lv)N~Q67;D7JS!% z0Y%2hFnVJaA-RJQ9nQ;DW`T?j-O5vBGFWg-Psr$W!~lpgOOJsW0ntg;8AL#Y-9#Afe?>uUC-$BxD>=ATSo+gARgJe9tK@z zk8It52qYBo$kq{IQISXC&;Thw^$eF;rMU`$d9qUoRxltrGJ=|sAFmt*#zhO#D>Dz- zqoZMX73Oi3jbPrZFi%-UM2?;27*<@3tXa%4K>P~AWQh6fmu<0tP+R^qTbQeU#TH~3 z&8S*M55!JctyT2U*g-0}Uy3wCpCztE@_>X4he$^vW6AF%Bu5AAu*U&%P(m`X2yh9L zhvZrMENY4*2V?~FMm9K6X?|Wd?Sj&r(p4V-6W+{DD>92{(F`*58rrh zV9$*M^J{F8*|vN(wH{;p_vLTc;%`~r$(1y;?xq=$;%hwG^P@er+ysVLj3#=of;;U9 z3npeR46C)I#Os|!hsQ{B;%bkS8|kr>qVBs;x7t7N5W1Uoow;nyqG^vW{dijD+ID2lD9ZO%|@uhG4 z;(Ie!2#!Ss-Z!r2ubFm=Nl&|8iVr9H+>So0=lK~i|NVOUN&GB1o&>?KHt;V@;*c*q z_!p$v0v`Gy@8|9Aefpg~?<;Gzbf1v;w9j74(7eqDDw$fSFgcsjc^iQ~%s?O1^PykJ zwSmt8v{kVwJG?s+6o_t;ZCM@yx8 zzA8-mMDTr7zJE!1sChx{#cS+O?H%*lntkc9<|37?7^_B3qzHeS|^m*wZsSfx6<@YD=$(BtIz;NI0IH_Dx5o@p#rWHrTjD3TB$L$7lLt_dXcV~Y7uXhkSiMcLf3U!F4vcs{(tB3Us*}%uDSH32 zd(x>)6}VdNNH)|3F8BNI`;-b?)l4H;;G?j>)v29KrtWju+qW$^aZ}CS>^IQSF_xpdk{D1-ofdWdyDeFts9?@R3kY+%yYL*ynEB? z>djBq?pnR!f?Eochv4aE7L>OSO1?aKkX%4opri$836-P>e!A#SAv{&J0fu*U8@*2! zLaXS2mQe&(!Z1f{6bbl_t)?8L%HX`1aPXYvfHN;Nlm-$^eg};Z+D7_1@z{vxAWv#8 zk_U*n>Wj8#DmDie-n#jwg@tQ-ZcOecrO#wJh3KjRB1TjRB1TjRB1TjRB1TjRB1TjRB1TjRB1TjRB1TjRB1TjRB1T zjRB1TjRB1TjRB1TjRB1TjRB1TjRB1TjRB1TjRB1TjRB1TjRB1TjRB1TjRB1TjRB2; z|3L=myASdn2)d#6K$uM5;yw!3>D$Z4x(5Qhmke(e>zFajFb4KOc);c9Zfmt%%6BB9 z@mQ7BUdUN3d@LM~L}SfW)>%QHt=#Hyn_Q7_La1!$Y!sqGD?gLxTP-jzmZ(I$%Gx3b z?UhAEU0q#;iMDouw{?UH;~mXKiEsq#w%oG8EKMXj8v17=HODpXl`X59rm0}wt>=6Q1YBeMc8wbPYz(YA6@T)ZB^a!BdwCLq%5md1x zW{e8N3UiAuaw)BpzV4^mPR3U+w$UHmQmIa8rQ#Mf48FwmW|nE#@a&us=G?rIqedS$W^Derg5ys(@g&R1*72wO)&d+}Si*wIA|AGrI z3N?l!d{c8vbk4=CZLxU!B^?Q&^U|*F%Pyb$%PX#&ch%L`^!%##+Uw?D-?!k`3m4t6 z_{N)VUh&=_ul{DZy$d2@h6}D z{U87QY}@u7pMUYMFL&^Ha`K@!Jm^ z=_`i+o%|%V*UzY{$u^C{?J=?ge(y~9#B+TnZvV2j{Nau8kr(hz4tDp4zuolAt~M0p>fjlk+SS36t(-F+ zgScyiddCb;ChVQy$-zSw$pLq<=IO3ExVH|$O*ar3M&vg~r7c|KZUx&0fLoJ5WbnW# WFle`uHZ7r<0cvo_)Zsa$FZgfeh*FvW literal 0 HcmV?d00001 diff --git a/tests/libse/Files/sample_mxf_429-5_asdcplib.xml b/tests/libse/Files/sample_mxf_429-5_asdcplib.xml new file mode 100644 index 00000000000..27c647b925e --- /dev/null +++ b/tests/libse/Files/sample_mxf_429-5_asdcplib.xml @@ -0,0 +1,22 @@ + + + urn:uuid:7be835a3-cfb4-43d0-bb4b-f0b4c95e962e + Test + 2026-10-16T12:00:00 + 1 + da + 25 1 + 25 + 01:00:00:00 + urn:uuid:3dec6dc0-39d0-498d-97d0-928d2eb78391 + + + + Hej verden + + + + urn:uuid:aaaaaaaa-0000-4000-8000-000000000001 + + + diff --git a/tests/libse/LibSETests.csproj b/tests/libse/LibSETests.csproj index c0bc2dc25c4..72145d9fb99 100644 --- a/tests/libse/LibSETests.csproj +++ b/tests/libse/LibSETests.csproj @@ -108,6 +108,12 @@ PreserveNewest + + PreserveNewest + + + PreserveNewest + PreserveNewest From 0dc3f75a2bac6f487571d68e3c8b3b0153bde6e4 Mon Sep 17 00:00:00 2001 From: niksedk Date: Fri, 16 Oct 2026 07:05:34 +0200 Subject: [PATCH 238/306] Editor palette follows a theme change in open windows Each palette color is now one shared brush whose color is updated when the app theme changes, so windows that are already open repaint instead of keeping the palette they were built with. EditorChrome.Themed() makes such brushes for colors outside the palette. Co-Authored-By: Claude Opus 5.5 --- src/ui/Logic/EditorChrome.cs | 176 ++++++++++++++--------- tests/UI/Logic/EditorChromeThemeTests.cs | 40 ++++++ 2 files changed, 152 insertions(+), 64 deletions(-) create mode 100644 tests/UI/Logic/EditorChromeThemeTests.cs diff --git a/src/ui/Logic/EditorChrome.cs b/src/ui/Logic/EditorChrome.cs index 323a4c1993b..14a1eba39a4 100644 --- a/src/ui/Logic/EditorChrome.cs +++ b/src/ui/Logic/EditorChrome.cs @@ -21,79 +21,127 @@ namespace Nikse.SubtitleEdit.Logic; /// internal static class EditorChrome { - // The editor palette, in a dark and a light version: these windows follow the app theme - // (picked when a window is built). Accent colors are the same in both. + // The editor palette, in a dark and a light version. Each color is one shared brush whose color + // follows the app theme (also when it changes while windows are open), so everything built + // from it repaints without rebuilding. Accent colors are the same in both. internal static bool IsLightTheme => Application.Current?.ActualThemeVariant == ThemeVariant.Light; /// /// Text and icons on hover - white on the dark palette, near black on the light one. /// - internal static IBrush StrongTextBrush => IsLightTheme ? Brushes.Black : Brushes.White; + internal static IBrush StrongTextBrush => StrongTextColor.Brush; /// /// Shape thumbnails (shape library, box and bar styles): light gray on dark, dark gray on light. /// - internal static IBrush ThumbnailBrush => IsLightTheme ? LightThumbnail : DarkThumbnail; - internal static IBrush ThumbnailMutedBrush => IsLightTheme ? LightThumbnailMuted : DarkThumbnailMuted; - private static readonly IBrush DarkThumbnail = new ImmutableSolidColorBrush(Color.FromRgb(214, 218, 226)); - private static readonly IBrush LightThumbnail = new ImmutableSolidColorBrush(Color.FromRgb(72, 78, 92)); - private static readonly IBrush DarkThumbnailMuted = new ImmutableSolidColorBrush(Color.FromRgb(96, 102, 116)); - private static readonly IBrush LightThumbnailMuted = new ImmutableSolidColorBrush(Color.FromRgb(188, 193, 204)); - - private static readonly IBrush DarkWindowBrush = new ImmutableSolidColorBrush(Color.FromRgb(30, 31, 36)); - private static readonly IBrush LightWindowBrush = new ImmutableSolidColorBrush(Color.FromRgb(243, 244, 247)); - internal static IBrush WindowBrush => IsLightTheme ? LightWindowBrush : DarkWindowBrush; - private static readonly IBrush DarkPanelBrush = new ImmutableSolidColorBrush(Color.FromRgb(36, 38, 44)); - private static readonly IBrush LightPanelBrush = new ImmutableSolidColorBrush(Color.FromRgb(250, 250, 252)); - internal static IBrush PanelBrush => IsLightTheme ? LightPanelBrush : DarkPanelBrush; - private static readonly IBrush DarkStatusBrush = new ImmutableSolidColorBrush(Color.FromRgb(27, 28, 33)); - private static readonly IBrush LightStatusBrush = new ImmutableSolidColorBrush(Color.FromRgb(236, 238, 242)); - internal static IBrush StatusBrush => IsLightTheme ? LightStatusBrush : DarkStatusBrush; - private static readonly IBrush DarkButtonBarBrush = new ImmutableSolidColorBrush(Color.FromRgb(32, 34, 39)); - private static readonly IBrush LightButtonBarBrush = new ImmutableSolidColorBrush(Color.FromRgb(238, 240, 244)); - internal static IBrush ButtonBarBrush => IsLightTheme ? LightButtonBarBrush : DarkButtonBarBrush; - private static readonly IBrush DarkLineBrush = new ImmutableSolidColorBrush(Color.FromRgb(47, 50, 58)); - private static readonly IBrush LightLineBrush = new ImmutableSolidColorBrush(Color.FromRgb(222, 225, 232)); - internal static IBrush LineBrush => IsLightTheme ? LightLineBrush : DarkLineBrush; - private static readonly IBrush DarkSeparatorBrush = new ImmutableSolidColorBrush(Color.FromRgb(55, 58, 67)); - private static readonly IBrush LightSeparatorBrush = new ImmutableSolidColorBrush(Color.FromRgb(208, 212, 220)); - internal static IBrush SeparatorBrush => IsLightTheme ? LightSeparatorBrush : DarkSeparatorBrush; - private static readonly IBrush DarkDimTextBrush = new ImmutableSolidColorBrush(Color.FromRgb(140, 146, 160)); - private static readonly IBrush LightDimTextBrush = new ImmutableSolidColorBrush(Color.FromRgb(92, 98, 112)); - internal static IBrush DimTextBrush => IsLightTheme ? LightDimTextBrush : DarkDimTextBrush; - private static readonly IBrush DarkFaintTextBrush = new ImmutableSolidColorBrush(Color.FromRgb(111, 117, 131)); - private static readonly IBrush LightFaintTextBrush = new ImmutableSolidColorBrush(Color.FromRgb(122, 128, 142)); - internal static IBrush FaintTextBrush => IsLightTheme ? LightFaintTextBrush : DarkFaintTextBrush; - private static readonly IBrush DarkIconBrush = new ImmutableSolidColorBrush(Color.FromRgb(174, 180, 194)); - private static readonly IBrush LightIconBrush = new ImmutableSolidColorBrush(Color.FromRgb(70, 76, 90)); - internal static IBrush IconBrush => IsLightTheme ? LightIconBrush : DarkIconBrush; - private static readonly IBrush DarkHoverBrush = new ImmutableSolidColorBrush(Color.FromRgb(51, 54, 63)); - private static readonly IBrush LightHoverBrush = new ImmutableSolidColorBrush(Color.FromRgb(226, 229, 236)); - internal static IBrush HoverBrush => IsLightTheme ? LightHoverBrush : DarkHoverBrush; - private static readonly IBrush DarkPressedBrush = new ImmutableSolidColorBrush(Color.FromRgb(59, 62, 71)); - private static readonly IBrush LightPressedBrush = new ImmutableSolidColorBrush(Color.FromRgb(214, 218, 227)); - internal static IBrush PressedBrush => IsLightTheme ? LightPressedBrush : DarkPressedBrush; - private static readonly IBrush DarkAccentBrush = new ImmutableSolidColorBrush(Color.FromRgb(76, 110, 245)); - private static readonly IBrush LightAccentBrush = new ImmutableSolidColorBrush(Color.FromRgb(76, 110, 245)); - internal static IBrush AccentBrush => IsLightTheme ? LightAccentBrush : DarkAccentBrush; - private static readonly IBrush DarkAccentHoverBrush = new ImmutableSolidColorBrush(Color.FromRgb(92, 124, 250)); - private static readonly IBrush LightAccentHoverBrush = new ImmutableSolidColorBrush(Color.FromRgb(92, 124, 250)); - internal static IBrush AccentHoverBrush => IsLightTheme ? LightAccentHoverBrush : DarkAccentHoverBrush; - private static readonly IBrush DarkAccentSoftBrush = new ImmutableSolidColorBrush(Color.FromArgb(70, 76, 110, 245)); - private static readonly IBrush LightAccentSoftBrush = new ImmutableSolidColorBrush(Color.FromArgb(46, 76, 110, 245)); - internal static IBrush AccentSoftBrush => IsLightTheme ? LightAccentSoftBrush : DarkAccentSoftBrush; - private static readonly IBrush DarkFieldBrush = new ImmutableSolidColorBrush(Color.FromRgb(27, 28, 33)); - private static readonly IBrush LightFieldBrush = new ImmutableSolidColorBrush(Color.FromRgb(255, 255, 255)); - internal static IBrush FieldBrush => IsLightTheme ? LightFieldBrush : DarkFieldBrush; - private static readonly IBrush DarkFieldBorderBrush = new ImmutableSolidColorBrush(Color.FromRgb(54, 57, 68)); - private static readonly IBrush LightFieldBorderBrush = new ImmutableSolidColorBrush(Color.FromRgb(204, 209, 219)); - internal static IBrush FieldBorderBrush => IsLightTheme ? LightFieldBorderBrush : DarkFieldBorderBrush; - private static readonly IBrush DarkMenuBrush = new ImmutableSolidColorBrush(Color.FromRgb(42, 44, 51)); - private static readonly IBrush LightMenuBrush = new ImmutableSolidColorBrush(Color.FromRgb(255, 255, 255)); - internal static IBrush MenuBrush => IsLightTheme ? LightMenuBrush : DarkMenuBrush; - private static readonly IBrush DarkMenuBorderBrush = new ImmutableSolidColorBrush(Color.FromRgb(66, 69, 79)); - private static readonly IBrush LightMenuBorderBrush = new ImmutableSolidColorBrush(Color.FromRgb(208, 212, 220)); - internal static IBrush MenuBorderBrush => IsLightTheme ? LightMenuBorderBrush : DarkMenuBorderBrush; + internal static IBrush ThumbnailBrush => ThumbnailColor.Brush; + internal static IBrush ThumbnailMutedBrush => ThumbnailMutedColor.Brush; + + internal static IBrush WindowBrush => WindowColor.Brush; + internal static IBrush PanelBrush => PanelColor.Brush; + internal static IBrush StatusBrush => StatusColor.Brush; + internal static IBrush ButtonBarBrush => ButtonBarColor.Brush; + internal static IBrush LineBrush => LineColor.Brush; + internal static IBrush SeparatorBrush => SeparatorColor.Brush; + internal static IBrush DimTextBrush => DimTextColor.Brush; + internal static IBrush FaintTextBrush => FaintTextColor.Brush; + internal static IBrush IconBrush => IconColor.Brush; + internal static IBrush HoverBrush => HoverColor.Brush; + internal static IBrush PressedBrush => PressedColor.Brush; + internal static IBrush AccentBrush => AccentColor.Brush; + internal static IBrush AccentHoverBrush => AccentHoverColor.Brush; + internal static IBrush AccentSoftBrush => AccentSoftColor.Brush; + internal static IBrush FieldBrush => FieldColor.Brush; + internal static IBrush FieldBorderBrush => FieldBorderColor.Brush; + internal static IBrush MenuBrush => MenuColor.Brush; + internal static IBrush MenuBorderBrush => MenuBorderColor.Brush; + + private static readonly ThemedBrush StrongTextColor = new(Colors.White, Colors.Black); + private static readonly ThemedBrush ThumbnailColor = new(Color.FromRgb(214, 218, 226), Color.FromRgb(72, 78, 92)); + private static readonly ThemedBrush ThumbnailMutedColor = new(Color.FromRgb(96, 102, 116), Color.FromRgb(188, 193, 204)); + private static readonly ThemedBrush WindowColor = new(Color.FromRgb(30, 31, 36), Color.FromRgb(243, 244, 247)); + private static readonly ThemedBrush PanelColor = new(Color.FromRgb(36, 38, 44), Color.FromRgb(250, 250, 252)); + private static readonly ThemedBrush StatusColor = new(Color.FromRgb(27, 28, 33), Color.FromRgb(236, 238, 242)); + private static readonly ThemedBrush ButtonBarColor = new(Color.FromRgb(32, 34, 39), Color.FromRgb(238, 240, 244)); + private static readonly ThemedBrush LineColor = new(Color.FromRgb(47, 50, 58), Color.FromRgb(222, 225, 232)); + private static readonly ThemedBrush SeparatorColor = new(Color.FromRgb(55, 58, 67), Color.FromRgb(208, 212, 220)); + private static readonly ThemedBrush DimTextColor = new(Color.FromRgb(140, 146, 160), Color.FromRgb(92, 98, 112)); + private static readonly ThemedBrush FaintTextColor = new(Color.FromRgb(111, 117, 131), Color.FromRgb(122, 128, 142)); + private static readonly ThemedBrush IconColor = new(Color.FromRgb(174, 180, 194), Color.FromRgb(70, 76, 90)); + private static readonly ThemedBrush HoverColor = new(Color.FromRgb(51, 54, 63), Color.FromRgb(226, 229, 236)); + private static readonly ThemedBrush PressedColor = new(Color.FromRgb(59, 62, 71), Color.FromRgb(214, 218, 227)); + private static readonly ThemedBrush AccentColor = new(Color.FromRgb(76, 110, 245), Color.FromRgb(76, 110, 245)); + private static readonly ThemedBrush AccentHoverColor = new(Color.FromRgb(92, 124, 250), Color.FromRgb(92, 124, 250)); + private static readonly ThemedBrush AccentSoftColor = new(Color.FromArgb(70, 76, 110, 245), Color.FromArgb(46, 76, 110, 245)); + private static readonly ThemedBrush FieldColor = new(Color.FromRgb(27, 28, 33), Color.FromRgb(255, 255, 255)); + private static readonly ThemedBrush FieldBorderColor = new(Color.FromRgb(54, 57, 68), Color.FromRgb(204, 209, 219)); + private static readonly ThemedBrush MenuColor = new(Color.FromRgb(42, 44, 51), Color.FromRgb(255, 255, 255)); + private static readonly ThemedBrush MenuBorderColor = new(Color.FromRgb(66, 69, 79), Color.FromRgb(208, 212, 220)); + + /// + /// A brush outside the palette that also follows the app theme (e.g. a status color with a + /// darker light-theme version). Create it once - keep it in a static field - not per control. + /// + internal static IBrush Themed(Color dark, Color light) => new ThemedBrush(dark, light).Brush; + + /// + /// One palette entry: a single mutable brush shared by every control that uses it. Its color is + /// set from the current theme on first use and again whenever the app theme changes. + /// + private sealed class ThemedBrush + { + private static readonly System.Collections.Generic.List All = new(); + private static Application? _hookedApplication; + + private readonly Color _dark; + private readonly Color _light; + private readonly SolidColorBrush _brush; + + public ThemedBrush(Color dark, Color light) + { + _dark = dark; + _light = light; + _brush = new SolidColorBrush(dark); + All.Add(this); + } + + public IBrush Brush + { + get + { + Hook(); + Refresh(); + return _brush; + } + } + + private void Refresh() + { + var color = IsLightTheme ? _light : _dark; + if (_brush.Color != color) + { + _brush.Color = color; + } + } + + private static void Hook() + { + var application = Application.Current; + if (application == null || ReferenceEquals(application, _hookedApplication)) + { + return; + } + + _hookedApplication = application; + application.ActualThemeVariantChanged += (_, _) => + { + foreach (var entry in All) + { + entry.Refresh(); + } + }; + } + } /// /// Flat icon buttons with an accent "active" state, compact number fields and accent menus. diff --git a/tests/UI/Logic/EditorChromeThemeTests.cs b/tests/UI/Logic/EditorChromeThemeTests.cs new file mode 100644 index 00000000000..185f1790281 --- /dev/null +++ b/tests/UI/Logic/EditorChromeThemeTests.cs @@ -0,0 +1,40 @@ +using Avalonia; +using Avalonia.Headless.XUnit; +using Avalonia.Media; +using Avalonia.Styling; +using Nikse.SubtitleEdit.Logic; + +namespace UITests.Logic; + +/// +/// The editor palette is one shared brush per color whose color follows the app theme, so windows +/// that are already open repaint when the theme changes instead of keeping the old palette. +/// +public class EditorChromeThemeTests +{ + [AvaloniaFact] + public void PaletteBrush_IsShared_AndFollowsAThemeChange() + { + var application = Application.Current!; + var old = application.RequestedThemeVariant; + try + { + application.RequestedThemeVariant = ThemeVariant.Light; + var brush = Assert.IsAssignableFrom(EditorChrome.WindowBrush); + var light = brush.Color; + + application.RequestedThemeVariant = ThemeVariant.Dark; + + // Same instance (what an open window holds), new color + Assert.Same(brush, EditorChrome.WindowBrush); + Assert.NotEqual(light, brush.Color); + + application.RequestedThemeVariant = ThemeVariant.Light; + Assert.Equal(light, brush.Color); + } + finally + { + application.RequestedThemeVariant = old; + } + } +} From cd14ea42e94e642b54028a472be2d15501b55794 Mon Sep 17 00:00:00 2001 From: niksedk Date: Fri, 16 Oct 2026 07:20:48 +0200 Subject: [PATCH 239/306] Export D-Cinema SMPTE 2014 image subtitles as MXF track file The SMPTE 2014/png export writes a folder of PNGs + index.xml; a DCP needs them wrapped as one SMPTE ST 429-5 track file. ExportHandlerDCinemaSmpte2014Mxf reuses the png handler's XML and embeds each PNG under its urn:uuid via TimedTextMxfWriter (#16096). In File > Export and Binary Edit > Export. Co-Authored-By: Claude Opus 5.5 --- .../ExportHandlerDCinemaSmpte2014Mxf.cs | 37 ++++++++ .../ExportHandlerDCinemaSmpte2014Png.cs | 41 ++++++--- src/ui/Features/Main/Layout/InitMenu.cs | 5 ++ .../Features/Main/Layout/InitNativeMacMenu.cs | 1 + src/ui/Features/Main/MainViewModel.cs | 29 +++++++ .../Shared/BinaryEdit/BinaryEditViewModel.cs | 6 ++ .../Shared/BinaryEdit/BinaryEditWindow.cs | 5 ++ .../BinaryEdit/InitNativeMacMenuBinaryEdit.cs | 1 + .../Config/Language/File/LanguageExport.cs | 2 + .../ExportHandlerDCinemaSmpte2014MxfTests.cs | 84 +++++++++++++++++++ 10 files changed, 198 insertions(+), 13 deletions(-) create mode 100644 src/libuilogic/Export/ExportHandlerDCinemaSmpte2014Mxf.cs create mode 100644 tests/libuilogic/Export/ExportHandlerDCinemaSmpte2014MxfTests.cs diff --git a/src/libuilogic/Export/ExportHandlerDCinemaSmpte2014Mxf.cs b/src/libuilogic/Export/ExportHandlerDCinemaSmpte2014Mxf.cs new file mode 100644 index 00000000000..3726d485dc8 --- /dev/null +++ b/src/libuilogic/Export/ExportHandlerDCinemaSmpte2014Mxf.cs @@ -0,0 +1,37 @@ +using Nikse.SubtitleEdit.Core.ContainerFormats.MaterialExchangeFormat; + +namespace Nikse.SubtitleEdit.UiLogic.Export; + +/// +/// D-Cinema SMPTE 2014 image subtitles as one SMPTE ST 429-5 timed text track file: the +/// index.xml of with each PNG embedded as an +/// ancillary resource under its urn:uuid - the subtitle asset of a SMPTE DCP. +/// +public class ExportHandlerDCinemaSmpte2014Mxf : ExportHandlerDCinemaSmpte2014Png +{ + public override string Extension => ".mxf"; + public override bool UseFileName => true; + public override string Title => string.Format("Export to {0}", "DCinema SMPTE 2014 (MXF)"); + + private string _fileName = string.Empty; + private readonly List _images = new(); + + protected override void PrepareOutput(string fileOrFolderName) + { + _fileName = fileOrFolderName; + _images.Clear(); + } + + protected override void SaveImage(string uuid, byte[] png) + { + _images.Add(new TimedTextMxfResource(Guid.Parse(uuid), TimedTextMxfResource.MimeTypePng, png)); + } + + protected override void SaveXml(string xml) + { + var writer = new TimedTextMxfWriter(); + writer.Resources.AddRange(_images); + writer.Write(_fileName, xml); + _images.Clear(); + } +} diff --git a/src/libuilogic/Export/ExportHandlerDCinemaSmpte2014Png.cs b/src/libuilogic/Export/ExportHandlerDCinemaSmpte2014Png.cs index 23a5926db96..a8fa04a8278 100644 --- a/src/libuilogic/Export/ExportHandlerDCinemaSmpte2014Png.cs +++ b/src/libuilogic/Export/ExportHandlerDCinemaSmpte2014Png.cs @@ -9,9 +9,9 @@ namespace Nikse.SubtitleEdit.UiLogic.Export; public class ExportHandlerDCinemaSmpte2014Png : IExportHandler { public ExportImageType ExportImageType => ExportImageType.DCinemaPng; - public string Extension => ""; - public bool UseFileName => false; - public string Title => string.Format("Export to {0}", "DCinema SMPTE 2014/png"); + public virtual string Extension => ""; + public virtual bool UseFileName => false; + public virtual string Title => string.Format("Export to {0}", "DCinema SMPTE 2014/png"); public double FrameRate { get; set; } = 23.976; private int _width; @@ -34,12 +34,7 @@ public void WriteHeader(string fileOrFolderName, ImageParameter imageParameter) FrameRate = imageParameter.FramesPerSecond; } - _folderName = fileOrFolderName; - if (!Directory.Exists(_folderName)) - { - Directory.CreateDirectory(_folderName); - } - + PrepareOutput(fileOrFolderName); _sb.Clear(); _imagesSavedCount = 0; } @@ -53,8 +48,7 @@ public void WriteParagraph(ImageParameter param) // string numberString = $"{i:0000}"; _imagesSavedCount++; string uuidString = Guid.NewGuid().ToString().RemoveChar('-').Insert(8, "-").Insert(13, "-").Insert(18, "-").Insert(23, "-"); - string fileName = Path.Combine(_folderName, uuidString + ".png"); - File.WriteAllBytes(fileName, param.Bitmap.ToPngArray()); + SaveImage(uuidString, param.Bitmap.ToPngArray()); string verticalAlignment = "bottom"; string horizontalAlignment = "center"; @@ -145,9 +139,30 @@ public void WriteFooter() xml = xml.Replace("[FRAMERATE]", ((int)Math.Round(FrameRate, MidpointRounding.AwayFromZero)).ToString(CultureInfo.InvariantCulture)); doc.LoadXml(xml); - var fName = Path.Combine(_folderName, "index.xml"); + SaveXml(SubtitleFormat.ToUtf8XmlString(doc)); + } - File.WriteAllText(fName, SubtitleFormat.ToUtf8XmlString(doc)); + /// + /// The PNGs and index.xml go into a folder; see + /// for the same subtitles wrapped in one MXF track file. + /// + protected virtual void PrepareOutput(string fileOrFolderName) + { + _folderName = fileOrFolderName; + if (!Directory.Exists(_folderName)) + { + Directory.CreateDirectory(_folderName); + } + } + + protected virtual void SaveImage(string uuid, byte[] png) + { + File.WriteAllBytes(Path.Combine(_folderName, uuid + ".png"), png); + } + + protected virtual void SaveXml(string xml) + { + File.WriteAllText(Path.Combine(_folderName, "index.xml"), xml); } private static string FormatUtf8Xml(XmlDocument doc) diff --git a/src/ui/Features/Main/Layout/InitMenu.cs b/src/ui/Features/Main/Layout/InitMenu.cs index b5ce65c23c7..845a6afc9d6 100644 --- a/src/ui/Features/Main/Layout/InitMenu.cs +++ b/src/ui/Features/Main/Layout/InitMenu.cs @@ -276,6 +276,11 @@ public static void Make(MainViewModel vm) Command = vm.ExportDCinemaSmpte2014PngCommand, }, new MenuItem + { + Header = Se.Language.File.Export.TitleExportDCinemaSmpte2014PngMxf, + Command = vm.ExportDCinemaSmpte2014PngMxfCommand, + }, + new MenuItem { Header = Se.Language.File.Export.TitleExportDCinemaSmpte2014Mxf, Command = vm.ExportDCinemaSmpte2014MxfCommand, diff --git a/src/ui/Features/Main/Layout/InitNativeMacMenu.cs b/src/ui/Features/Main/Layout/InitNativeMacMenu.cs index 6789ea5e8db..f6167fba048 100644 --- a/src/ui/Features/Main/Layout/InitNativeMacMenu.cs +++ b/src/ui/Features/Main/Layout/InitNativeMacMenu.cs @@ -251,6 +251,7 @@ public static void MakeStructure(NativeMenu root, Window window) exportItems.Items.Add(Item(lExport.TitleExportDvbTeletext, v => v.ExportDvbTeletextCommand)); exportItems.Items.Add(Item(lExport.TitleExportDCinemaInteropPng, v => v.ExportDCinemaInteropPngCommand)); exportItems.Items.Add(Item(lExport.TitleExportDCinemaSmpte2014Png, v => v.ExportDCinemaSmpte2014PngCommand)); + exportItems.Items.Add(Item(lExport.TitleExportDCinemaSmpte2014PngMxf, v => v.ExportDCinemaSmpte2014PngMxfCommand)); exportItems.Items.Add(Item(lExport.TitleExportDCinemaSmpte2014Mxf, v => v.ExportDCinemaSmpte2014MxfCommand)); exportItems.Items.Add(Item(Ebu.NameOfFormat, v => v.ExportEbuStlCommand)); exportItems.Items.Add(Item(lExport.TitleExportDostPng, v => v.ExportDostPngCommand)); diff --git a/src/ui/Features/Main/MainViewModel.cs b/src/ui/Features/Main/MainViewModel.cs index e6c74e0e782..7a903b1f803 100644 --- a/src/ui/Features/Main/MainViewModel.cs +++ b/src/ui/Features/Main/MainViewModel.cs @@ -5163,6 +5163,35 @@ private async Task ExportDCinemaSmpte2014Png() } } + /// + /// D-Cinema SMPTE 2014 image subtitles as one SMPTE ST 429-5 track file, the PNGs embedded. + /// + [RelayCommand] + private async Task ExportDCinemaSmpte2014PngMxf() + { + if (Window == null) + { + return; + } + + if (IsEmpty) + { + ShowSubtitleNotLoadedMessage(); + return; + } + + IExportHandler exportHandler = new ExportHandlerDCinemaSmpte2014Mxf(); + var result = await ShowDialogAsync(vm => + { + vm.Initialize(exportHandler, Subtitles, _subtitleFileName, _videoFileName, _subtitle.Header, removeAssaCommentBlocks: IsFormatAssaOrSsa); + }); + + if (!result.OkPressed) + { + return; + } + } + /// /// SMPTE ST 429-5 timed text track file - the subtitle asset of a SMPTE DCP: the D-Cinema /// SMPTE 2014 XML wrapped in MXF with the font it loads embedded. diff --git a/src/ui/Features/Shared/BinaryEdit/BinaryEditViewModel.cs b/src/ui/Features/Shared/BinaryEdit/BinaryEditViewModel.cs index a96855b0761..9872071e985 100644 --- a/src/ui/Features/Shared/BinaryEdit/BinaryEditViewModel.cs +++ b/src/ui/Features/Shared/BinaryEdit/BinaryEditViewModel.cs @@ -1226,6 +1226,12 @@ private async Task ExportDCinemaSmpte2014Png() await DoExport(new ExportHandlerDCinemaSmpte2014Png(), string.Empty, false); } + [RelayCommand] + private async Task ExportDCinemaSmpte2014PngMxf() + { + await DoExport(new ExportHandlerDCinemaSmpte2014Mxf(), ".mxf"); + } + [RelayCommand] private async Task ExportImscImage() { diff --git a/src/ui/Features/Shared/BinaryEdit/BinaryEditWindow.cs b/src/ui/Features/Shared/BinaryEdit/BinaryEditWindow.cs index f52f48090f0..be951490b8d 100644 --- a/src/ui/Features/Shared/BinaryEdit/BinaryEditWindow.cs +++ b/src/ui/Features/Shared/BinaryEdit/BinaryEditWindow.cs @@ -251,6 +251,11 @@ private static Menu MakeTopMenu(BinaryEditViewModel vm) Command = vm.ExportDCinemaSmpte2014PngCommand, }, new MenuItem + { + Header = Se.Language.File.Export.TitleExportDCinemaSmpte2014PngMxf, + Command = vm.ExportDCinemaSmpte2014PngMxfCommand, + }, + new MenuItem { Header = Se.Language.File.Export.TitleExportDostPng, Command = vm.ExportDostPngCommand, diff --git a/src/ui/Features/Shared/BinaryEdit/InitNativeMacMenuBinaryEdit.cs b/src/ui/Features/Shared/BinaryEdit/InitNativeMacMenuBinaryEdit.cs index 049dabfddac..34645181a17 100644 --- a/src/ui/Features/Shared/BinaryEdit/InitNativeMacMenuBinaryEdit.cs +++ b/src/ui/Features/Shared/BinaryEdit/InitNativeMacMenuBinaryEdit.cs @@ -22,6 +22,7 @@ public static void Setup(Window window, BinaryEditViewModel vm) Add(exportMenu, Se.Language.File.Export.TitleExportImscImage, vm.ExportImscImageCommand); Add(exportMenu, Se.Language.File.Export.TitleExportDCinemaInteropPng, vm.ExportDCinemaInteropPngCommand); Add(exportMenu, Se.Language.File.Export.TitleExportDCinemaSmpte2014Png, vm.ExportDCinemaSmpte2014PngCommand); + Add(exportMenu, Se.Language.File.Export.TitleExportDCinemaSmpte2014PngMxf, vm.ExportDCinemaSmpte2014PngMxfCommand); Add(exportMenu, Se.Language.File.Export.TitleExportDostPng, vm.ExportDostPngCommand); Add(exportMenu, Se.Language.File.Export.TitleExportDvdSup, vm.ExportDvdSupCommand); Add(exportMenu, Se.Language.File.Export.TitleExportFcpImage, vm.ExportFcpPngCommand); diff --git a/src/ui/Logic/Config/Language/File/LanguageExport.cs b/src/ui/Logic/Config/Language/File/LanguageExport.cs index 8c4e8fa7722..ead8b703207 100644 --- a/src/ui/Logic/Config/Language/File/LanguageExport.cs +++ b/src/ui/Logic/Config/Language/File/LanguageExport.cs @@ -48,6 +48,7 @@ public class LanguageExport public string TitleExportDCinemaInteropPng { get; set; } public string TitleExportDCinemaSmpte2014Png { get; set; } public string TitleExportDCinemaSmpte2014Mxf { get; set; } + public string TitleExportDCinemaSmpte2014PngMxf { get; set; } public string ImageBasedSubtitleSaved { get; set; } public string FullFrameImage { get; set; } public string FullFrameImageHint { get; set; } @@ -139,6 +140,7 @@ public LanguageExport() TitleExportDCinemaInteropPng = "D-Cinema interop/png"; TitleExportDCinemaSmpte2014Png = "D-Cinema SMPTE 2014/png"; TitleExportDCinemaSmpte2014Mxf = "D-Cinema SMPTE 2014 (MXF)"; + TitleExportDCinemaSmpte2014PngMxf = "D-Cinema SMPTE 2014/png (MXF)"; ImageBasedSubtitleSaved = "Image-based subtitle saved"; FullFrameImage = "Full frame image"; FullFrameImageHint = "Make each image the size of the video frame, with the subtitle in its place"; diff --git a/tests/libuilogic/Export/ExportHandlerDCinemaSmpte2014MxfTests.cs b/tests/libuilogic/Export/ExportHandlerDCinemaSmpte2014MxfTests.cs new file mode 100644 index 00000000000..d0eb2dde70d --- /dev/null +++ b/tests/libuilogic/Export/ExportHandlerDCinemaSmpte2014MxfTests.cs @@ -0,0 +1,84 @@ +using System.Text.RegularExpressions; +using Nikse.SubtitleEdit.Core.ContainerFormats.MaterialExchangeFormat; +using Nikse.SubtitleEdit.UiLogic.Export; +using SkiaSharp; + +namespace LibUiLogicTests.Export; + +public class ExportHandlerDCinemaSmpte2014MxfTests : IDisposable +{ + private readonly string _dir = Path.Combine(Path.GetTempPath(), "dcinema_mxf_" + Guid.NewGuid().ToString("N")); + + public ExportHandlerDCinemaSmpte2014MxfTests() => Directory.CreateDirectory(_dir); + + public void Dispose() + { + try { Directory.Delete(_dir, true); } catch { /* best effort */ } + } + + private static ImageParameter Cue(int index, int startMs, int endMs) + { + var bitmap = new SKBitmap(200, 40); + using (var canvas = new SKCanvas(bitmap)) + { + canvas.Clear(SKColors.White); + } + + return new ImageParameter + { + Bitmap = bitmap, + StartTime = TimeSpan.FromMilliseconds(startMs), + EndTime = TimeSpan.FromMilliseconds(endMs), + Index = index, + ScreenWidth = 1920, + ScreenHeight = 1080, + FramesPerSecond = 24, + Alignment = ExportAlignment.BottomCenter, + }; + } + + private static void Export(IExportHandler handler, string fileOrFolderName) + { + var cues = new[] { Cue(0, 1000, 2000), Cue(1, 3000, 4500) }; + handler.WriteHeader(fileOrFolderName, cues[0]); + foreach (var cue in cues) + { + handler.WriteParagraph(cue); + } + + handler.WriteFooter(); + } + + [Fact] + public void WritesOneTrackFileWithEveryImageEmbedded() + { + var handler = new ExportHandlerDCinemaSmpte2014Mxf(); + Assert.True(handler.UseFileName); + Assert.Equal(".mxf", handler.Extension); + + var fileName = Path.Combine(_dir, "out.mxf"); + Export(handler, fileName); + + Assert.Equal(new[] { fileName }, Directory.GetFiles(_dir)); + var parser = new MxfParser(fileName); + var xml = Assert.Single(parser.GetSubtitles()); + Assert.Contains("24 1", xml); + Assert.Equal(2, parser.GetImages().Count); + Assert.Equal(2, Regex.Matches(xml, "]*>urn:uuid:[0-9a-f-]{36}").Count); + } + + [Fact] + public void PngExportStillWritesFolder() + { + Export(new ExportHandlerDCinemaSmpte2014Png(), _dir); + + Assert.True(File.Exists(Path.Combine(_dir, "index.xml"))); + var pngs = Directory.GetFiles(_dir, "*.png"); + Assert.Equal(2, pngs.Length); + var xml = File.ReadAllText(Path.Combine(_dir, "index.xml")); + foreach (var png in pngs) + { + Assert.Contains("urn:uuid:" + Path.GetFileNameWithoutExtension(png), xml); + } + } +} From a9c8aafa9f4bc35b1ee1fdb365fd797d324a30f2 Mon Sep 17 00:00:00 2001 From: niksedk Date: Fri, 16 Oct 2026 07:36:16 +0200 Subject: [PATCH 240/306] D-Cinema MXF: reel duration, resource check, Bv2.1/ClairMeta fixes Found by putting SE's MXFs in a DCP made by DCP-o-matic 2.19.2 and running its verifier (libdcp 1.10.63) and ClairMeta 1.6.3: - Every asset of a reel must have the reel's duration; the track ended at the last TimeOut (Bv2.1 error). TimedTextMxfWriter.ContainerDuration, "Reel duration" in the MXF dialog (prefilled from the video), image MXF takes the video's duration in the export dialog and Binary Edit. - The writer refuses XML whose LoadFont/Image is not in the track; the text export embeds the font under the id the XML loads it by. - SMPTE 2014/png XML: IssueDate without fractions/zone (Bv2.1 warning) and encoding="UTF-8" (ClairMeta error) - also for the folder export. Co-Authored-By: Claude Opus 5.5 --- .../TimedTextMxfWriter.cs | 93 ++++++++++++++++--- .../ExportHandlerDCinemaSmpte2014Mxf.cs | 12 +++ .../ExportHandlerDCinemaSmpte2014Png.cs | 6 +- .../ExportImageBasedViewModel.cs | 5 + .../DCinemaSmptePropertiesViewModel.cs | 32 ++++++- .../DCinemaSmptePropertiesWindow.cs | 11 ++- src/ui/Features/Main/MainViewModel.cs | 15 ++- .../Shared/BinaryEdit/BinaryEditViewModel.cs | 8 +- .../File/LanguageFilePropertiesDCinema.cs | 6 ++ .../Files/DCinemaSmptePropertiesMxfTests.cs | 3 +- .../TimedTextMxfWriterTest.cs | 58 ++++++++++++ .../ExportHandlerDCinemaSmpte2014MxfTests.cs | 15 +++ 12 files changed, 243 insertions(+), 21 deletions(-) diff --git a/src/libse/ContainerFormats/MaterialExchangeFormat/TimedTextMxfWriter.cs b/src/libse/ContainerFormats/MaterialExchangeFormat/TimedTextMxfWriter.cs index ba04671fd13..172c7e4ae78 100644 --- a/src/libse/ContainerFormats/MaterialExchangeFormat/TimedTextMxfWriter.cs +++ b/src/libse/ContainerFormats/MaterialExchangeFormat/TimedTextMxfWriter.cs @@ -26,6 +26,13 @@ public class TimedTextMxfWriter public DateTime ModifiedUtc { get; set; } = DateTime.UtcNow; + /// + /// The track's duration in edit units. 0 = from StartTime to the latest TimeOut, like + /// asdcplib - but in a DCP every asset of a reel must have the reel's duration (DCP-o-matic's + /// verifier rejects a shorter subtitle track), so set it to the picture's length. + /// + public long ContainerDuration { get; set; } + public List Resources { get; } = new List(); /// @@ -135,6 +142,17 @@ public void Write(string fileName, string xml) public void Write(Stream stream, string xml) { var info = TimedTextInfo.Parse(xml); + if (ContainerDuration > 0) + { + info.Duration = ContainerDuration; + } + + // A LoadFont/Image the track does not carry plays without its font or image + var missing = info.ResourceReferences.FirstOrDefault(id => Resources.All(r => r.Id != id)); + if (missing != Guid.Empty) + { + throw new InvalidDataException("Timed text XML references resource urn:uuid:" + missing + ", which is not in the track file"); + } var xmlBytes = new UTF8Encoding(false).GetBytes(xml); var metadata = MakeHeaderMetadata(info); @@ -580,7 +598,8 @@ private class TimedTextInfo public int EditRateNumerator { get; private set; } public int EditRateDenominator { get; private set; } public int TimecodeRate { get; private set; } - public long Duration { get; private set; } + public long Duration { get; set; } + public List ResourceReferences { get; } = new List(); public static TimedTextInfo Parse(string xml) { @@ -624,31 +643,79 @@ public static TimedTextInfo Parse(string xml) } info.Duration = end - start; + info.ResourceReferences.AddRange(GetResourceReferences(root, "LoadFont")); + info.ResourceReferences.AddRange(GetResourceReferences(root, "Image")); return info; } + public static IEnumerable GetResourceReferences(XmlElement root, string localName) + { + foreach (var element in root.GetElementsByTagName("*").OfType().Where(e => e.LocalName == localName)) + { + var text = element.InnerText.Trim(); + if (text.StartsWith("urn:uuid:", StringComparison.OrdinalIgnoreCase) && Guid.TryParse(text.Substring(9), out var id)) + { + yield return id; + } + } + } + private static string Child(XmlElement root, string localName) { return root.ChildNodes.OfType().FirstOrDefault(e => e.LocalName == localName)?.InnerText; } - /// - /// HH:MM:SS:FF (frames) or HH:MM:SS:EEE at TimeCodeRate ticks - both count frames here. - /// private static long FramesFromTimeCode(string timeCode, int frameRate) { - var parts = (timeCode ?? string.Empty).Trim().Split(':'); - if (parts.Length != 4 || parts.Any(p => !int.TryParse(p, NumberStyles.None, CultureInfo.InvariantCulture, out _))) + return TryParseTimeCode(timeCode, frameRate, out var frames) ? frames : 0; + } + } + + /// + /// The urn:uuid ids of the fonts the XML loads (LoadFont) - the ids to embed the fonts under. + /// + public static List GetLoadFontIds(string xml) + { + var doc = new XmlDocument { XmlResolver = null }; + doc.LoadXml(xml); + return doc.DocumentElement == null + ? new List() + : TimedTextInfo.GetResourceReferences(doc.DocumentElement, "LoadFont").Distinct().ToList(); + } + + /// + /// HH:MM:SS:FF as a frame count at the given (integer) time code rate. + /// + public static bool TryParseTimeCode(string timeCode, int frameRate, out long frames) + { + frames = 0; + var parts = (timeCode ?? string.Empty).Trim().Split(':'); + var values = new int[4]; + if (parts.Length != 4) + { + return false; + } + + for (var i = 0; i < 4; i++) + { + if (!int.TryParse(parts[i], NumberStyles.None, CultureInfo.InvariantCulture, out values[i])) { - return 0; + return false; } - - var p0 = int.Parse(parts[0], CultureInfo.InvariantCulture); - var p1 = int.Parse(parts[1], CultureInfo.InvariantCulture); - var p2 = int.Parse(parts[2], CultureInfo.InvariantCulture); - var p3 = int.Parse(parts[3], CultureInfo.InvariantCulture); - return ((p0 * 60L + p1) * 60 + p2) * frameRate + p3; } + + frames = ((values[0] * 60L + values[1]) * 60 + values[2]) * frameRate + values[3]; + return true; + } + + /// + /// A duration as HH:MM:SS:FF at the given (integer) time code rate, rounded to whole frames. + /// + public static string ToTimeCode(TimeSpan duration, int frameRate) + { + var frames = (long)Math.Round(duration.TotalSeconds * frameRate); + var seconds = frames / frameRate; + return string.Format(CultureInfo.InvariantCulture, "{0:00}:{1:00}:{2:00}:{3:00}", seconds / 3600, seconds / 60 % 60, seconds % 60, frames % frameRate); } } } diff --git a/src/libuilogic/Export/ExportHandlerDCinemaSmpte2014Mxf.cs b/src/libuilogic/Export/ExportHandlerDCinemaSmpte2014Mxf.cs index 3726d485dc8..a7868502855 100644 --- a/src/libuilogic/Export/ExportHandlerDCinemaSmpte2014Mxf.cs +++ b/src/libuilogic/Export/ExportHandlerDCinemaSmpte2014Mxf.cs @@ -13,6 +13,12 @@ public class ExportHandlerDCinemaSmpte2014Mxf : ExportHandlerDCinemaSmpte2014Png public override bool UseFileName => true; public override string Title => string.Format("Export to {0}", "DCinema SMPTE 2014 (MXF)"); + /// + /// The reel (picture) length - every asset of a DCP reel must have it, so the track runs to + /// it instead of ending at the last subtitle. Null = until the last subtitle. + /// + public TimeSpan? ReelDuration { get; set; } + private string _fileName = string.Empty; private readonly List _images = new(); @@ -30,6 +36,12 @@ protected override void SaveImage(string uuid, byte[] png) protected override void SaveXml(string xml) { var writer = new TimedTextMxfWriter(); + if (ReelDuration is { TotalMilliseconds: > 0 } reelDuration) + { + var frameRate = (int)Math.Round(FrameRate, MidpointRounding.AwayFromZero); + writer.ContainerDuration = (long)Math.Round(reelDuration.TotalSeconds * frameRate); + } + writer.Resources.AddRange(_images); writer.Write(_fileName, xml); _images.Clear(); diff --git a/src/libuilogic/Export/ExportHandlerDCinemaSmpte2014Png.cs b/src/libuilogic/Export/ExportHandlerDCinemaSmpte2014Png.cs index a8fa04a8278..5d5e3b78e0e 100644 --- a/src/libuilogic/Export/ExportHandlerDCinemaSmpte2014Png.cs +++ b/src/libuilogic/Export/ExportHandlerDCinemaSmpte2014Png.cs @@ -124,7 +124,8 @@ public void WriteFooter() " urn:uuid:" + guid + "" + Environment.NewLine + " Movie Title" + Environment.NewLine + " This is a subtitle file" + Environment.NewLine + - " " + DateTime.Now.ToString("o") + "" + Environment.NewLine + + // xs:dateTime without fractions or zone - DCP-o-matic's verifier warns on "o" (Bv2.1) + " " + DateTime.Now.ToString("s") + "" + Environment.NewLine + " 1" + Environment.NewLine + " en" + Environment.NewLine + " [FRAMERATE] 1" + Environment.NewLine + @@ -139,7 +140,8 @@ public void WriteFooter() xml = xml.Replace("[FRAMERATE]", ((int)Math.Round(FrameRate, MidpointRounding.AwayFromZero)).ToString(CultureInfo.InvariantCulture)); doc.LoadXml(xml); - SaveXml(SubtitleFormat.ToUtf8XmlString(doc)); + // "UTF-8" like the text D-Cinema formats - ClairMeta rejects the lowercase declaration + SaveXml(SubtitleFormat.ToUtf8XmlString(doc).Replace("encoding=\"utf-8\"", "encoding=\"UTF-8\"")); } /// diff --git a/src/ui/Features/Files/ExportImageBased/ExportImageBasedViewModel.cs b/src/ui/Features/Files/ExportImageBased/ExportImageBasedViewModel.cs index 625acf7dc2a..d7f012eb7ef 100644 --- a/src/ui/Features/Files/ExportImageBased/ExportImageBasedViewModel.cs +++ b/src/ui/Features/Files/ExportImageBased/ExportImageBasedViewModel.cs @@ -786,6 +786,11 @@ public void Initialize( _ = Task.Run(() => { var mediaInfo = FfmpegMediaInfo2.Parse(videoFileName); + if (exportHandler is ExportHandlerDCinemaSmpte2014Mxf mxfHandler && mediaInfo?.Duration is { TotalMilliseconds: > 0 } videoDuration) + { + mxfHandler.ReelDuration = TimeSpan.FromMilliseconds(videoDuration.TotalMilliseconds); + } + if (mediaInfo?.Dimension is { Width: > 0, Height: > 0 } dimension) { Dispatcher.UIThread.Post(() => diff --git a/src/ui/Features/Files/FormatProperties/DcinemaSmpteProperties/DCinemaSmptePropertiesViewModel.cs b/src/ui/Features/Files/FormatProperties/DcinemaSmpteProperties/DCinemaSmptePropertiesViewModel.cs index a9c6434f03d..83bdadffd27 100644 --- a/src/ui/Features/Files/FormatProperties/DcinemaSmpteProperties/DCinemaSmptePropertiesViewModel.cs +++ b/src/ui/Features/Files/FormatProperties/DcinemaSmpteProperties/DCinemaSmptePropertiesViewModel.cs @@ -4,6 +4,7 @@ using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; using Nikse.SubtitleEdit.Core.Common; +using Nikse.SubtitleEdit.Core.ContainerFormats.MaterialExchangeFormat; using Nikse.SubtitleEdit.Core.Forms; using Nikse.SubtitleEdit.Core.SubtitleFormats; using Nikse.SubtitleEdit.Features.Shared; @@ -51,6 +52,7 @@ public partial class DCinemaSmptePropertiesViewModel : ObservableObject // MXF track file export: the font embedded as the LoadFont resource [ObservableProperty] private bool _isMxfExport; [ObservableProperty] private string _mxfFontFile = string.Empty; + [ObservableProperty] private string _reelDuration = string.Empty; public Window? Window { get; set; } @@ -463,6 +465,12 @@ private async Task Ok() return; } + if (!string.IsNullOrWhiteSpace(ReelDuration) && !TimedTextMxfWriter.TryParseTimeCode(ReelDuration, 1, out _)) + { + await MessageBox.Show(Window!, Se.Language.General.Error, Se.Language.File.PropertiesDCinema.ReelDurationInvalid, MessageBoxButtons.OK, MessageBoxIcon.Error); + return; + } + // The track file carries the font under the LoadFont urn:uuid if (!FontUri.StartsWith("urn:uuid:", StringComparison.OrdinalIgnoreCase) || !Guid.TryParse(FontUri.Substring(9), out _)) { @@ -505,13 +513,35 @@ internal void Initialize(Subtitle subtitle, SubtitleFormat format) InitializeCore(subtitle, format); } - internal void InitializeMxfExport(Subtitle subtitle) + internal void InitializeMxfExport(Subtitle subtitle, TimeSpan? videoDuration) { IsMxfExport = true; InitializeCore(subtitle, null); + if (videoDuration is { TotalMilliseconds: > 0 } duration) + { + ReelDuration = TimedTextMxfWriter.ToTimeCode(duration, GetEditRateFrames()); + } + WindowTitle = string.Format(Se.Language.File.XProperties, Se.Language.File.Export.TitleExportDCinemaSmpte2014Mxf); } + /// + /// The integer time code rate of the edit rate ("24000 1001" -> 24), 24 if unparsable. + /// + internal int GetEditRateFrames() + { + var parts = (EditRate ?? string.Empty).Split(' ', StringSplitOptions.RemoveEmptyEntries); + if (parts.Length == 2 && + double.TryParse(parts[0], NumberStyles.Float, CultureInfo.InvariantCulture, out var numerator) && + double.TryParse(parts[1], NumberStyles.Float, CultureInfo.InvariantCulture, out var denominator) && + numerator > 0 && denominator > 0) + { + return (int)Math.Round(numerator / denominator, MidpointRounding.AwayFromZero); + } + + return 24; + } + private void InitializeCore(Subtitle? subtitle, SubtitleFormat? format) { if (subtitle != null) diff --git a/src/ui/Features/Files/FormatProperties/DcinemaSmpteProperties/DCinemaSmptePropertiesWindow.cs b/src/ui/Features/Files/FormatProperties/DcinemaSmpteProperties/DCinemaSmptePropertiesWindow.cs index 22197a3646f..012eccf3ea9 100644 --- a/src/ui/Features/Files/FormatProperties/DcinemaSmpteProperties/DCinemaSmptePropertiesWindow.cs +++ b/src/ui/Features/Files/FormatProperties/DcinemaSmpteProperties/DCinemaSmptePropertiesWindow.cs @@ -41,6 +41,14 @@ public DCinemaSmptePropertiesWindow(DCinemaSmptePropertiesViewModel vm) var textBoxEditRate = FormatPropertiesChrome.Text(UiUtil.MakeTextBox(90, vm, nameof(vm.EditRate)), lang.EditRate); var comboBoxTimeCodeRate = FormatPropertiesChrome.Combo(UiUtil.MakeComboBox(vm.TimeCodeRates, vm, nameof(vm.SelectedTimeCodeRate)), lang.TimeCodeRate, 90); var textBoxStartTime = FormatPropertiesChrome.Text(UiUtil.MakeTextBox(120, vm, nameof(vm.StartTime)), lang.StartTime); + var textBoxReelDuration = FormatPropertiesChrome.Text(UiUtil.MakeTextBox(120, vm, nameof(vm.ReelDuration)), lang.ReelDuration); + if (Se.Settings.Appearance.ShowHints) + { + ToolTip.SetTip(textBoxReelDuration, lang.ReelDurationHint); + } + + var rowReelDuration = FormatPropertiesChrome.Row(lang.ReelDuration, textBoxReelDuration); + rowReelDuration.Bind(Visual.IsVisibleProperty, new Binding(nameof(vm.IsMxfExport)) { Source = vm, Mode = BindingMode.OneWay }); var general = FormatPropertiesChrome.MakeRows( FormatPropertiesChrome.Row(string.Empty, switchGenerateIdAuto), @@ -49,7 +57,8 @@ public DCinemaSmptePropertiesWindow(DCinemaSmptePropertiesViewModel vm) FormatPropertiesChrome.Row(lang.ReelNumber, numericUpDownReelNumber, FormatPropertiesChrome.InlineLabel(Se.Language.General.Language), comboBoxLanguage), FormatPropertiesChrome.Row(lang.IssueDate, textBoxIssueDate, buttonToday), FormatPropertiesChrome.Row(lang.EditRate, textBoxEditRate, FormatPropertiesChrome.InlineLabel(lang.TimeCodeRate), comboBoxTimeCodeRate), - FormatPropertiesChrome.Row(lang.StartTime, textBoxStartTime)); + FormatPropertiesChrome.Row(lang.StartTime, textBoxStartTime), + rowReelDuration); // Font section var textBoxFontId = FormatPropertiesChrome.Text(UiUtil.MakeTextBox(200, vm, nameof(vm.FontId)), lang.FontId); diff --git a/src/ui/Features/Main/MainViewModel.cs b/src/ui/Features/Main/MainViewModel.cs index 7a903b1f803..ce5acd0eb8b 100644 --- a/src/ui/Features/Main/MainViewModel.cs +++ b/src/ui/Features/Main/MainViewModel.cs @@ -5211,7 +5211,7 @@ private async Task ExportDCinemaSmpte2014Mxf() } CopyCurrentDCinemaSettingsToSe(); - var result = await ShowDialogAsync(vm => { vm.InitializeMxfExport(GetUpdateSubtitle()); }); + var result = await ShowDialogAsync(vm => { vm.InitializeMxfExport(GetUpdateSubtitle(), _mediaInfo?.Duration is { } videoDuration ? TimeSpan.FromMilliseconds(videoDuration.TotalMilliseconds) : null); }); CopyCurrentDCinemaSettingsFromSe(); SetLibSeSettings(); if (!result.OkPressed) @@ -5233,8 +5233,19 @@ private async Task ExportDCinemaSmpte2014Mxf() try { var xml = new DCinemaSmpte2014().ToText(GetExportSubtitle(), Path.GetFileNameWithoutExtension(fileName)); - var fontId = Guid.Parse(Configuration.Settings.SubtitleSettings.CurrentDCinemaFontUri.Substring("urn:uuid:".Length)); + // Embed the font under the id the XML loads it by + var fontId = TimedTextMxfWriter.GetLoadFontIds(xml).FirstOrDefault(); + if (fontId == Guid.Empty) + { + fontId = Guid.Parse(Configuration.Settings.SubtitleSettings.CurrentDCinemaFontUri.Substring("urn:uuid:".Length)); + } + var writer = new TimedTextMxfWriter(); + if (TimedTextMxfWriter.TryParseTimeCode(result.ReelDuration, result.GetEditRateFrames(), out var reelFrames)) + { + writer.ContainerDuration = reelFrames; + } + writer.Resources.Add(new TimedTextMxfResource(fontId, TimedTextMxfResource.MimeTypeFont, await File.ReadAllBytesAsync(result.MxfFontFile))); writer.Write(fileName, xml); } diff --git a/src/ui/Features/Shared/BinaryEdit/BinaryEditViewModel.cs b/src/ui/Features/Shared/BinaryEdit/BinaryEditViewModel.cs index 9872071e985..a8b1430c30e 100644 --- a/src/ui/Features/Shared/BinaryEdit/BinaryEditViewModel.cs +++ b/src/ui/Features/Shared/BinaryEdit/BinaryEditViewModel.cs @@ -1229,7 +1229,13 @@ private async Task ExportDCinemaSmpte2014Png() [RelayCommand] private async Task ExportDCinemaSmpte2014PngMxf() { - await DoExport(new ExportHandlerDCinemaSmpte2014Mxf(), ".mxf"); + // A DCP reel's assets share one duration - with a video open, run the track to its end + var videoSeconds = VideoPlayerControl?.VideoPlayer?.Duration ?? 0; + var handler = new ExportHandlerDCinemaSmpte2014Mxf + { + ReelDuration = videoSeconds > 0 ? TimeSpan.FromSeconds(videoSeconds) : null, + }; + await DoExport(handler, ".mxf"); } [RelayCommand] diff --git a/src/ui/Logic/Config/Language/File/LanguageFilePropertiesDCinema.cs b/src/ui/Logic/Config/Language/File/LanguageFilePropertiesDCinema.cs index 7148cc1aa8b..dc698a2a18b 100644 --- a/src/ui/Logic/Config/Language/File/LanguageFilePropertiesDCinema.cs +++ b/src/ui/Logic/Config/Language/File/LanguageFilePropertiesDCinema.cs @@ -26,6 +26,9 @@ public class LanguageFilePropertiesDCinema public string FontFile { get; internal set; } public string ChooseFontFile { get; internal set; } public string FontFileMissing { get; internal set; } + public string ReelDuration { get; internal set; } + public string ReelDurationHint { get; internal set; } + public string ReelDurationInvalid { get; internal set; } public LanguageFilePropertiesDCinema() { @@ -53,6 +56,9 @@ public LanguageFilePropertiesDCinema() FontFile = "Font file"; ChooseFontFile = "Choose font file"; FontFileMissing = "Choose the TrueType/OpenType font file to embed in the MXF track file."; + ReelDuration = "Reel duration"; + ReelDurationHint = "HH:MM:SS:FF - the picture length, which every asset of a DCP reel must have. Empty = until the last subtitle."; + ReelDurationInvalid = "Reel duration must be empty or HH:MM:SS:FF."; } } \ No newline at end of file diff --git a/tests/UI/Features/Files/DCinemaSmptePropertiesMxfTests.cs b/tests/UI/Features/Files/DCinemaSmptePropertiesMxfTests.cs index ee1f714637b..6ac38ba08da 100644 --- a/tests/UI/Features/Files/DCinemaSmptePropertiesMxfTests.cs +++ b/tests/UI/Features/Files/DCinemaSmptePropertiesMxfTests.cs @@ -36,7 +36,7 @@ public void MxfExport_Ok_StoresFontFileAndMakesFontUriAUuid() Se.Settings.File.DCinemaSmpte = new SeDCinemaSmpte { CurrentDCinemaFontUri = "Arial.ttf" }; var fontFile = Path.GetTempFileName(); var vm = MakeViewModel(); - vm.InitializeMxfExport(MakeSubtitle()); + vm.InitializeMxfExport(MakeSubtitle(), TimeSpan.FromSeconds(12)); var window = new DCinemaSmptePropertiesWindow(vm); try { @@ -44,6 +44,7 @@ public void MxfExport_Ok_StoresFontFileAndMakesFontUriAUuid() Dispatcher.UIThread.RunJobs(); Assert.True(vm.IsMxfExport); Assert.Contains("MXF", vm.WindowTitle); + Assert.Equal("00:00:12:00", vm.ReelDuration); // the video's length at the default 24 fps vm.MxfFontFile = fontFile; vm.OkCommand.Execute(null); diff --git a/tests/libse/ContainerFormats/TimedTextMxfWriterTest.cs b/tests/libse/ContainerFormats/TimedTextMxfWriterTest.cs index 23f5f6e1e99..3d5497bbe86 100644 --- a/tests/libse/ContainerFormats/TimedTextMxfWriterTest.cs +++ b/tests/libse/ContainerFormats/TimedTextMxfWriterTest.cs @@ -93,6 +93,64 @@ public void XmlWithoutIdThrows() Assert.Throws(() => WriteSample(xml)); } + /// + /// A DCP reel's assets must share one duration - DCP-o-matic's verifier rejects a subtitle + /// track ending at its last TimeOut when the picture runs longer. + /// + [Fact] + public void ContainerDurationOverridesLastTimeOut() + { + var xml = File.ReadAllText(FilePath("sample_mxf_429-5_asdcplib.xml")); + var writer = new TimedTextMxfWriter { AssetId = AssetId, ContainerDuration = 300 }; + writer.Resources.Add(new TimedTextMxfResource(FontId, TimedTextMxfResource.MimeTypeFont, Font)); + writer.Resources.Add(new TimedTextMxfResource(ImageId, TimedTextMxfResource.MimeTypePng, Png)); + using var ms = new MemoryStream(); + writer.Write(ms, xml); + + var durations = ReadKlvs(ms.ToArray()) + .Where(k => k.Key[4] == 0x02 && k.Key[5] == 0x53) + .SelectMany(k => ReadLocalSet(k.Value)) + .Where(x => x.Tag is 0x0202 or 0x3002) + .Select(x => Convert.ToHexString(x.Value)) + .Distinct(); + Assert.Equal("000000000000012C", Assert.Single(durations)); // 300 in every sequence, component and the descriptor + } + + [Fact] + public void ResourceMissingFromTrackThrows() + { + var xml = File.ReadAllText(FilePath("sample_mxf_429-5_asdcplib.xml")); + var writer = new TimedTextMxfWriter(); + writer.Resources.Add(new TimedTextMxfResource(FontId, TimedTextMxfResource.MimeTypeFont, Font)); // no PNG + + var exception = Assert.Throws(() => writer.Write(new MemoryStream(), xml)); + Assert.Contains(ImageId.ToString(), exception.Message); + } + + [Fact] + public void GetLoadFontIds() + { + var xml = File.ReadAllText(FilePath("sample_mxf_429-5_asdcplib.xml")); + Assert.Equal(FontId, Assert.Single(TimedTextMxfWriter.GetLoadFontIds(xml))); + } + + [Theory] + [InlineData("00:00:12:00", 24, 288)] + [InlineData("01:00:00:01", 25, 90001)] + public void TryParseTimeCode(string timeCode, int frameRate, long expected) + { + Assert.True(TimedTextMxfWriter.TryParseTimeCode(timeCode, frameRate, out var frames)); + Assert.Equal(expected, frames); + Assert.False(TimedTextMxfWriter.TryParseTimeCode("12.5", frameRate, out _)); + } + + [Fact] + public void ToTimeCode() + { + Assert.Equal("00:00:12:00", TimedTextMxfWriter.ToTimeCode(TimeSpan.FromSeconds(12), 24)); + Assert.Equal("01:02:03:12", TimedTextMxfWriter.ToTimeCode(new TimeSpan(0, 1, 2, 3, 500), 24)); + } + private static List<(long Offset, byte[] Key, byte[] Value)> ReadKlvs(byte[] data) { var list = new List<(long, byte[], byte[])>(); diff --git a/tests/libuilogic/Export/ExportHandlerDCinemaSmpte2014MxfTests.cs b/tests/libuilogic/Export/ExportHandlerDCinemaSmpte2014MxfTests.cs index d0eb2dde70d..7cc39a9850f 100644 --- a/tests/libuilogic/Export/ExportHandlerDCinemaSmpte2014MxfTests.cs +++ b/tests/libuilogic/Export/ExportHandlerDCinemaSmpte2014MxfTests.cs @@ -67,6 +67,18 @@ public void WritesOneTrackFileWithEveryImageEmbedded() Assert.Equal(2, Regex.Matches(xml, "]*>urn:uuid:[0-9a-f-]{36}").Count); } + [Fact] + public void ReelDurationSetsTrackDuration() + { + var fileName = Path.Combine(_dir, "out.mxf"); + Export(new ExportHandlerDCinemaSmpte2014Mxf { ReelDuration = TimeSpan.FromSeconds(12) }, fileName); + + // ContainerDuration (local tag 3002) of the descriptor: 12 s at 24 fps + var bytes = File.ReadAllBytes(fileName); + var tag = new byte[] { 0x30, 0x02, 0x00, 0x08, 0, 0, 0, 0, 0, 0, 0x01, 0x20 }; + Assert.True(bytes.AsSpan().IndexOf(tag) > 0); + } + [Fact] public void PngExportStillWritesFolder() { @@ -76,6 +88,9 @@ public void PngExportStillWritesFolder() var pngs = Directory.GetFiles(_dir, "*.png"); Assert.Equal(2, pngs.Length); var xml = File.ReadAllText(Path.Combine(_dir, "index.xml")); + // DCP-o-matic warns on a fractional/zoned IssueDate, ClairMeta rejects encoding="utf-8" + Assert.StartsWith("", xml); + Assert.Matches("\\d{4}-\\d\\d-\\d\\dT\\d\\d:\\d\\d:\\d\\d", xml); foreach (var png in pngs) { Assert.Contains("urn:uuid:" + Path.GetFileNameWithoutExtension(png), xml); From 49f7e15237eb6f3cd66a92279dc5c7083c83b45b Mon Sep 17 00:00:00 2001 From: niksedk Date: Fri, 16 Oct 2026 07:43:01 +0200 Subject: [PATCH 241/306] seconv: D-Cinema SMPTE 2014 MXF outputs (text and png) - dcinemasmpte2014mxf / dcinema-smpte-mxf: SMPTE 2014 XML as a SMPTE ST 429-5 track file with --dcinema-font embedded under its LoadFont urn:uuid. - dcinemasmpte2014pngmxf / dcinema-smpte-png-mxf: rendered (or source .sup) images as one track file. - --reel-duration:hh:mm:ss:ff for both - every asset of a DCP reel must have the reel's duration. Both outputs verified OK in a DCP-o-matic-made DCP by dcpomatic2_verify_cli and ClairMeta. Co-Authored-By: Claude Opus 5.5 --- docs/reference/command-line.md | 14 ++- src/seconv/Commands/ConvertCommand.cs | 10 ++ src/seconv/Core/ImageOutputWriter.cs | 27 ++++++ src/seconv/Core/LibSEIntegration.cs | 88 ++++++++++++++++- src/seconv/Core/SubtitleConverter.cs | 6 ++ src/seconv/Helpers/HelpDisplay.cs | 2 + tests/seconv/Core/DCinemaMxfOutputTest.cs | 110 ++++++++++++++++++++++ 7 files changed, 255 insertions(+), 2 deletions(-) create mode 100644 tests/seconv/Core/DCinemaMxfOutputTest.cs diff --git a/docs/reference/command-line.md b/docs/reference/command-line.md index 07f572a8d7f..201cfc9ad27 100644 --- a/docs/reference/command-line.md +++ b/docs/reference/command-line.md @@ -10,7 +10,7 @@ - **Container input** — Matroska (`.mkv` / `.mks`), MP4, MCC, MXF, AVI (`.avi` / `.divx`), transport stream teletext, Blu-Ray `.sup`. - **OCR for image-based sources** via seven engines (Tesseract subprocess, nOCR built-in, BinaryOCR built-in, Ollama HTTP, llama.cpp HTTP with automatic server start, PaddleOCR subprocess, Apple Vision built into macOS). - **Auto-translate** via local LLMs (llama.cpp with automatic server start, Ollama, LM Studio), any OpenAI-compatible API, DeepL, or self-hosted services (LibreTranslate, NLLB). -- **Image-based output** — Blu-Ray sup, BDN-XML, DOST, FCP (Final Cut Pro + image), D-Cinema interop / SMPTE 2014, images-with-time-code. +- **Image-based output** — Blu-Ray sup, BDN-XML, DOST, FCP (Final Cut Pro + image), D-Cinema interop / SMPTE 2014 (also as a DCP-ready SMPTE MXF track file), images-with-time-code. - **Operations pipeline** — offset, fps change, change-speed, renumber, adjust-duration, fix-common-errors, merge/split, balance, redo casing, RTL fixes, multiple-replace, custom-text format, plain text. - **Cross-platform** — Windows, Linux, macOS. Only requires the .NET runtime; no display or GUI needed. @@ -162,6 +162,8 @@ seconv lint *.srt --json # CI-friendly: exit 1 on any issue | `--pac-codepage:` | PAC | Code page name (`Latin`, `Greek`, `Hebrew`, …) or numeric (0–12). See `seconv list-pac-codepages` | | `--pac-secondary-codepage:` | PAC | Code page for lines in another script (e.g. `Cyrillic` for the Russian lines of a Hebrew file); those lines are flagged "secondary code page". Reading detects it automatically | | `--ebu-header-file:` | EBU STL | Reuse the GSI header block from an existing `.stl` file | +| `--dcinema-font:` | D-Cinema SMPTE MXF (`dcinemasmpte2014mxf`) | Font (`.ttf`/`.otf`) embedded in the track file under the `LoadFont` `urn:uuid` (required) | +| `--reel-duration:` | D-Cinema SMPTE MXF (text and png) | Reel (picture) length at the output frame rate. Every asset of a DCP reel must have it, so set it to the picture's length; default ends the track at the last subtitle | | `--plaintext-merge` | Plain text (`txt`) | Merge all subtitles into one space-separated block (no blank lines). Takes precedence over the two options below | | `--plaintext-unbreak` | Plain text (`txt`) | Unbreak each subtitle, joining its lines into one | | `--plaintext-no-blank-line` | Plain text (`txt`) | Do not put a blank line between subtitles (default keeps it) | @@ -797,6 +799,8 @@ This mirrors the desktop app, where batch convert's *Remove formatting* function | `fcpimage`, `fcp` | FCP/image | | `dcinemainterop`, `dcinema-interop` | D-Cinema interop/png | | `dcinemasmpte2014`, `dcinema-smpte` | D-Cinema SMPTE 2014/png | +| `dcinemasmpte2014mxf`, `dcinema-smpte-mxf` | D-Cinema SMPTE 2014 as a SMPTE ST 429-5 MXF track file (DCP subtitle asset), font embedded (requires `--dcinema-font`) | +| `dcinemasmpte2014pngmxf`, `dcinema-smpte-png-mxf` | D-Cinema SMPTE 2014/png as one MXF track file, PNGs embedded — image | | `imageswithtimecode`, `imagesintc` | Images with time codes in file name | | `webvttthumbnail`, `webvtt-thumbnail`, `vttthumb` | WebVTT Thumbnail — image (sprite sheet + `.vtt`) | | `plaintext`, `text`, `txt` | Plain text (HTML stripped) | @@ -842,6 +846,14 @@ seconv subs.sup subrip \ --overwrite ``` +### DCP subtitle track file (SMPTE MXF) +```bash +# text, font embedded; reel duration = the picture's length (here 1:32:10 + 4 frames at 24 fps) +seconv movie.srt dcinemasmpte2014mxf --fps:24 --dcinema-font:Arial.ttf --reel-duration:01:32:10:04 +# rendered images instead of text, e.g. from a Blu-ray sup +seconv movie.sup dcinemasmpte2014pngmxf --fps:24 --reel-duration:01:32:10:04 +``` + ### Round-trip via image-based output ```bash seconv subs.srt bluraysup --resolution:1920x1080 --overwrite diff --git a/src/seconv/Commands/ConvertCommand.cs b/src/seconv/Commands/ConvertCommand.cs index f34d842e1d1..c9d51389f87 100644 --- a/src/seconv/Commands/ConvertCommand.cs +++ b/src/seconv/Commands/ConvertCommand.cs @@ -52,6 +52,14 @@ public sealed class Settings : CommandSettings [Description("ASSA style file name")] public string? AssaStyleFile { get; init; } + [CommandOption("--dcinema-font|--dcinemafont")] + [Description("Font file (.ttf/.otf) embedded in dcinemasmpte2014mxf output")] + public string? DCinemaFont { get; init; } + + [CommandOption("--reel-duration|--reelduration")] + [Description("D-Cinema MXF output: the reel (picture) length as hh:mm:ss:ff - every asset of a DCP reel must have it (default: until the last subtitle)")] + public string? ReelDuration { get; init; } + [CommandOption("--ebu-header-file|--ebuheaderfile")] [Description("EBU header file name")] public string? EbuHeaderFile { get; init; } @@ -824,6 +832,8 @@ public override async Task ExecuteAsync(CommandContext context, Settings se PacCodePage = pacCodePage, PacSecondaryCodePage = pacSecondaryCodePage, EbuHeaderFile = settings.EbuHeaderFile, + DCinemaFontFile = settings.DCinemaFont, + ReelDuration = settings.ReelDuration, MultipleReplaceFile = settings.MultipleReplace, CustomFormatFile = settings.CustomFormat, PlainTextMerge = settings.PlainTextMerge, diff --git a/src/seconv/Core/ImageOutputWriter.cs b/src/seconv/Core/ImageOutputWriter.cs index 14269d839e6..3758d3f4182 100644 --- a/src/seconv/Core/ImageOutputWriter.cs +++ b/src/seconv/Core/ImageOutputWriter.cs @@ -1,4 +1,5 @@ using Nikse.SubtitleEdit.Core.Common; +using Nikse.SubtitleEdit.Core.ContainerFormats.MaterialExchangeFormat; using SkiaSharp; using Nikse.SubtitleEdit.UiLogic.Export; @@ -14,6 +15,8 @@ internal static class ImageOutputWriter { public sealed record FormatTarget(string Name, IExportHandler Handler); + public const string DCinemaSmpte2014PngMxfName = "D-Cinema SMPTE 2014/png MXF"; + public static IExportHandler? TryCreateHandler(string normalizedFormat) { var n = normalizedFormat.Trim(); @@ -43,6 +46,9 @@ var x when x.Equals("D-Cinema interop/png", StringComparison.OrdinalIgnoreCase) => new ExportHandlerDCinemaInteropPng(), var x when x.Equals("D-Cinema SMPTE 2014/png", StringComparison.OrdinalIgnoreCase) || x.Equals("DCinemaSmpte2014", StringComparison.OrdinalIgnoreCase) => new ExportHandlerDCinemaSmpte2014Png(), + // The same images and XML as one SMPTE ST 429-5 track file - the subtitle asset of a SMPTE DCP. + var x when x.Equals(DCinemaSmpte2014PngMxfName, StringComparison.OrdinalIgnoreCase) || x.Equals("DCinemaSmpte2014PngMxf", StringComparison.OrdinalIgnoreCase) + => new ExportHandlerDCinemaSmpte2014Mxf(), var x when x.Equals("Images with time codes in file name", StringComparison.OrdinalIgnoreCase) || x.Equals("ImagesWithTimeCodes", StringComparison.OrdinalIgnoreCase) => new ExportHandlerImagesWithTimeCode(), // WebVTT thumbnail bundle: a directory of numbered PNG sprites plus an @@ -64,6 +70,7 @@ public static void Write(Subtitle subtitle, string filePath, IExportHandler hand } var (screenWidth, screenHeight) = options.Resolution ?? (1920, 1080); + ApplyReelDuration(handler, options); // "{\pos(x,y)}" is in the script's own resolution, the export canvas may be another one. var (scriptWidth, scriptHeight) = ExportTextTags.GetScriptResolution(subtitle.Header); @@ -95,6 +102,25 @@ public static void Write(Subtitle subtitle, string filePath, IExportHandler hand handler.WriteFooter(); } + /// + /// --reel-duration for the D-Cinema MXF track: hh:mm:ss:ff at the output frame rate. + /// + private static void ApplyReelDuration(IExportHandler handler, ConversionOptions options) + { + if (handler is not ExportHandlerDCinemaSmpte2014Mxf mxf || string.IsNullOrWhiteSpace(options.ReelDuration)) + { + return; + } + + var frameRate = (int)Math.Round(options.TargetFps ?? options.Fps ?? 25.0, MidpointRounding.AwayFromZero); + if (!TimedTextMxfWriter.TryParseTimeCode(options.ReelDuration, frameRate, out var frames)) + { + throw new InvalidOperationException($"--reel-duration must be hh:mm:ss:ff, not '{options.ReelDuration}'."); + } + + mxf.ReelDuration = TimeSpan.FromSeconds(frames / (double)frameRate); + } + /// /// Image-to-image variant: write a sequence of pre-rendered bitmaps (Blu-Ray .sup, /// VobSub, MKV PGS, DVB-sub) into without going through @@ -119,6 +145,7 @@ public static void WritePreservedBitmaps( var first = items[0]; var firstParam = BuildPreservedParameter(first, 0, defaultWidth, defaultHeight, options); + ApplyReelDuration(handler, options); handler.WriteHeader(filePath, firstParam); var apply3D = Stereo3DImage.IsModeSupported(handler.ExportImageType); diff --git a/src/seconv/Core/LibSEIntegration.cs b/src/seconv/Core/LibSEIntegration.cs index 2ccb58d380b..cdb7f9f640f 100644 --- a/src/seconv/Core/LibSEIntegration.cs +++ b/src/seconv/Core/LibSEIntegration.cs @@ -1,10 +1,13 @@ using Nikse.SubtitleEdit.Core.Common; +using Nikse.SubtitleEdit.Core.ContainerFormats.MaterialExchangeFormat; using Nikse.SubtitleEdit.Core.Forms; using Nikse.SubtitleEdit.Core.Interfaces; using Nikse.SubtitleEdit.Core.SubtitleFormats; using Nikse.SubtitleEdit.UiLogic.SubtitleLoading; using Spectre.Console; +using System.Globalization; using System.Text; +using System.Text.RegularExpressions; namespace SeConv.Core; @@ -477,6 +480,12 @@ public static void SaveSubtitle( return; } + if (IsDCinemaSmpte2014Mxf(formatName)) + { + SaveDCinemaSmpte2014Mxf(subtitle, filePath, sourceFormat, options); + return; + } + // Image-based output formats: render text → bitmaps → format-specific bytes var imageHandler = ImageOutputWriter.TryCreateHandler(formatName); if (imageHandler is not null) @@ -565,6 +574,71 @@ public static void SaveSubtitle( SaveTextFormat(subtitle, filePath, targetFormat, encodingName); } + public const string DCinemaSmpte2014MxfName = "D-Cinema SMPTE 2014 MXF"; + + private static bool IsDCinemaSmpte2014Mxf(string formatName) => + formatName.Replace(" ", "").Equals(DCinemaSmpte2014MxfName.Replace(" ", ""), StringComparison.OrdinalIgnoreCase); + + /// + /// D-Cinema SMPTE 2014 XML wrapped as a SMPTE ST 429-5 timed text track file (the subtitle + /// asset of a SMPTE DCP) with the --dcinema-font embedded under the id the XML loads it by. + /// + private static void SaveDCinemaSmpte2014Mxf(Subtitle subtitle, string filePath, SubtitleFormat? sourceFormat, ConversionOptions? options) + { + var fontFile = options?.DCinemaFontFile; + if (string.IsNullOrWhiteSpace(fontFile)) + { + throw new InvalidOperationException("D-Cinema SMPTE 2014 MXF output requires --dcinema-font= - the track file embeds the font."); + } + + if (!File.Exists(fontFile)) + { + throw new FileNotFoundException($"D-Cinema font file not found: {fontFile}", fontFile); + } + + var ss = Configuration.Settings.SubtitleSettings; + var oldFontUri = ss.CurrentDCinemaFontUri; + var xml = string.Empty; + try + { + // The track carries the font under a urn:uuid - an Interop-style file name won't do + if (!(ss.CurrentDCinemaFontUri ?? string.Empty).StartsWith("urn:uuid:", StringComparison.OrdinalIgnoreCase)) + { + ss.CurrentDCinemaFontUri = ss.DCinemaLoadFontResource; + } + + WithFreshDCinemaIdentity(subtitle, sourceFormat, () => xml = new DCinemaSmpte2014().ToText(subtitle, Path.GetFileNameWithoutExtension(filePath))); + } + finally + { + ss.CurrentDCinemaFontUri = oldFontUri; + } + + var fontId = TimedTextMxfWriter.GetLoadFontIds(xml).FirstOrDefault(); + if (fontId == Guid.Empty) + { + throw new InvalidOperationException("The D-Cinema font URI must be a urn:uuid to embed the font in the MXF."); + } + + var writer = new TimedTextMxfWriter(); + writer.Resources.Add(new TimedTextMxfResource(fontId, TimedTextMxfResource.MimeTypeFont, File.ReadAllBytes(fontFile))); + if (!string.IsNullOrWhiteSpace(options?.ReelDuration)) + { + var editRate = Regex.Match(xml, @"<(?:\w+:)?EditRate>\s*(\d+)\s+(\d+)\s*<"); + var frameRate = editRate.Success + ? (int)Math.Round(double.Parse(editRate.Groups[1].Value, CultureInfo.InvariantCulture) / double.Parse(editRate.Groups[2].Value, CultureInfo.InvariantCulture), MidpointRounding.AwayFromZero) + : 24; + if (!TimedTextMxfWriter.TryParseTimeCode(options.ReelDuration, frameRate, out var frames)) + { + throw new InvalidOperationException($"--reel-duration must be hh:mm:ss:ff, not '{options.ReelDuration}'."); + } + + writer.ContainerDuration = frames; + } + + AtomicFileWriter.Write(filePath, stream => writer.Write(stream, xml)); + } + private static bool IsDCinemaTextFormat(SubtitleFormat? format) => format is DCinemaInterop or DCinemaSmpte2007 or DCinemaSmpte2010 or DCinemaSmpte2014; @@ -575,6 +649,11 @@ private static bool IsDCinemaTextFormat(SubtitleFormat? format) => /// was D-Cinema (its loaded title is kept). /// private static void SaveWithFreshDCinemaIdentity(Subtitle subtitle, string filePath, SubtitleFormat format, string? encodingName, SubtitleFormat? sourceFormat) + { + WithFreshDCinemaIdentity(subtitle, sourceFormat, () => SaveTextFormat(subtitle, filePath, format, encodingName)); + } + + private static void WithFreshDCinemaIdentity(Subtitle subtitle, SubtitleFormat? sourceFormat, Action save) { var ss = Configuration.Settings.SubtitleSettings; var oldId = ss.CurrentDCinemaSubtitleId; @@ -587,7 +666,7 @@ private static void SaveWithFreshDCinemaIdentity(Subtitle subtitle, string fileP ss.CurrentDCinemaMovieTitle = string.Empty; } - SaveTextFormat(subtitle, filePath, format, encodingName); + save(); } finally { @@ -1212,6 +1291,11 @@ public static string GetExtensionForFormat(string formatName) return ".txt"; } + if (IsDCinemaSmpte2014Mxf(normalized)) + { + return ".mxf"; + } + var imageHandler = ImageOutputWriter.TryCreateHandler(normalized); if (imageHandler is not null) { @@ -1254,6 +1338,8 @@ public static string NormalizeFormatName(string formatName) "fcp" or "fcpimage" => "FCP/image", "dcinemainterop" or "dcinema-interop" => "D-Cinema interop/png", "dcinemasmpte2014" or "dcinema-smpte" => "D-Cinema SMPTE 2014/png", + "dcinemasmpte2014mxf" or "dcinema-smpte-mxf" => DCinemaSmpte2014MxfName, + "dcinemasmpte2014pngmxf" or "dcinema-smpte-png-mxf" => ImageOutputWriter.DCinemaSmpte2014PngMxfName, "imageswithtimecode" or "imagesintc" => "Images with time codes in file name", "webvttthumbnail" or "webvtt-thumbnail" or "vttthumb" => "WebVTT Thumbnail", "plaintext" or "text" or "txt" => "Plain text", diff --git a/src/seconv/Core/SubtitleConverter.cs b/src/seconv/Core/SubtitleConverter.cs index e21b93c0e1a..2492dac4523 100644 --- a/src/seconv/Core/SubtitleConverter.cs +++ b/src/seconv/Core/SubtitleConverter.cs @@ -1377,6 +1377,12 @@ internal record class ConversionOptions /// PAC code page for lines in another script, written flagged as "secondary code page". public int? PacSecondaryCodePage { get; init; } public string? EbuHeaderFile { get; init; } + + /// Font embedded in D-Cinema SMPTE 2014 MXF (text) output. + public string? DCinemaFontFile { get; init; } + + /// D-Cinema MXF output: reel length as hh:mm:ss:ff, null = until the last subtitle. + public string? ReelDuration { get; init; } public string? MultipleReplaceFile { get; init; } public string? CustomFormatFile { get; init; } public IReadOnlyList TrackNumbers { get; init; } = []; diff --git a/src/seconv/Helpers/HelpDisplay.cs b/src/seconv/Helpers/HelpDisplay.cs index fd3fed0fbc4..d8e2cefdcc2 100644 --- a/src/seconv/Helpers/HelpDisplay.cs +++ b/src/seconv/Helpers/HelpDisplay.cs @@ -42,6 +42,7 @@ private static void ShowHelp(IAnsiConsole console) ShowParameter(console, "--adjust-duration:", "Adjust duration in milliseconds"); ShowParameter(console, "--assa-style-file:", "ASSA style file"); ShowParameter(console, "--change-speed:", "Change speed by percent (e.g. 125 = 1.25x faster)"); + ShowParameter(console, "--dcinema-font:", "Font (.ttf/.otf) embedded in dcinemasmpte2014mxf output"); ShowParameter(console, "--ebu-header-file:", "EBU header file"); ShowParameter(console, "--encoding:", "Character encoding (e.g., utf-8, windows-1252, or 'source' to keep the input file's encoding)"); ShowParameter(console, "--input-encoding-fallback:", "Assumed input encoding when no BOM and not UTF-8 (skips ANSI guess)"); @@ -58,6 +59,7 @@ private static void ShowHelp(IAnsiConsole console) ShowParameter(console, "--pac-codepage:", "PAC code page"); ShowParameter(console, "--pac-secondary-codepage:", "PAC code page for lines in another script (e.g. Cyrillic lines in a Hebrew file)"); ShowParameter(console, "--profile:", "Profile name"); + ShowParameter(console, "--reel-duration:", "D-Cinema MXF output: reel (picture) length, which every asset of a DCP reel must have (default: until the last subtitle)"); ShowParameter(console, "--renumber:", "Renumber subtitles from this number"); ShowParameter(console, "--resolution:x", "Video resolution (e.g., 1920x1080)"); ShowParameter(console, "--target-fps:", "Target frame rate"); diff --git a/tests/seconv/Core/DCinemaMxfOutputTest.cs b/tests/seconv/Core/DCinemaMxfOutputTest.cs new file mode 100644 index 00000000000..591da81db20 --- /dev/null +++ b/tests/seconv/Core/DCinemaMxfOutputTest.cs @@ -0,0 +1,110 @@ +using Nikse.SubtitleEdit.Core.ContainerFormats.MaterialExchangeFormat; +using SeConv.Core; +using Xunit; + +namespace SeConvTests.Core; + +public class DCinemaMxfOutputTest : IDisposable +{ + private readonly string _tempRoot; + + public DCinemaMxfOutputTest() + { + _tempRoot = Path.Combine(Path.GetTempPath(), "DcpMxf_" + Guid.NewGuid().ToString("N")); + Directory.CreateDirectory(_tempRoot); + } + + public void Dispose() + { + if (Directory.Exists(_tempRoot)) + Directory.Delete(_tempRoot, recursive: true); + } + + private const string SrtContent = """ + 1 + 00:00:01,000 --> 00:00:04,000 + Hello, World! + + 2 + 00:00:05,000 --> 00:00:08,000 + This is a test subtitle. + + """; + + private async Task<(ConversionResult Result, string OutFolder)> Convert(string format, string? fontFile, string? reelDuration) + { + var input = Path.Combine(_tempRoot, "in.srt"); + await File.WriteAllTextAsync(input, SrtContent); + var outFolder = Path.Combine(_tempRoot, "out_" + Guid.NewGuid().ToString("N")); + Directory.CreateDirectory(outFolder); + + var result = await new SubtitleConverter().ConvertAsync(new ConversionOptions + { + Patterns = [input], + Format = format, + OutputFolder = outFolder, + Overwrite = true, + Fps = 24, + Resolution = (1998, 1080), + DCinemaFontFile = fontFile, + ReelDuration = reelDuration, + }); + return (result, outFolder); + } + + // ContainerDuration (local tag 3002) in the descriptor + private static bool HasContainerDuration(string mxfFile, long frames) + { + var tag = new byte[] { 0x30, 0x02, 0x00, 0x08 }.Concat(BitConverter.GetBytes(frames).Reverse()).ToArray(); + return File.ReadAllBytes(mxfFile).AsSpan().IndexOf(tag) > 0; + } + + [Fact] + public async Task TextMxf_EmbedsFontAndXml() + { + var font = Path.Combine(_tempRoot, "font.ttf"); + await File.WriteAllBytesAsync(font, Enumerable.Range(0, 1000).Select(i => (byte)i).ToArray()); + + var (result, outFolder) = await Convert("dcinemasmpte2014mxf", font, "00:00:12:00"); + + Assert.True(result.Success, string.Join("; ", result.Errors)); + var mxf = Assert.Single(Directory.GetFiles(outFolder, "*.mxf")); + var xml = Assert.Single(new MxfParser(mxf).GetSubtitles()); + Assert.Contains("http://www.smpte-ra.org/schemas/428-7/2014/DCST", xml); + Assert.Contains("Hello, World!", xml); + Assert.Single(TimedTextMxfWriter.GetLoadFontIds(xml)); + Assert.True(HasContainerDuration(mxf, 288), "reel duration 12 s at 24 fps"); + } + + [Fact] + public async Task TextMxf_WithoutFont_Fails() + { + var (result, outFolder) = await Convert("dcinema-smpte-mxf", null, null); + + Assert.False(result.Success); + Assert.Contains(result.Errors, e => e.Contains("--dcinema-font")); + Assert.Empty(Directory.GetFiles(outFolder)); + } + + [Fact] + public async Task PngMxf_EmbedsOneImagePerSubtitle() + { + var (result, outFolder) = await Convert("dcinemasmpte2014pngmxf", null, "00:00:12:00"); + + Assert.True(result.Success, string.Join("; ", result.Errors)); + var mxf = Assert.Single(Directory.GetFiles(outFolder, "*.mxf")); + var parser = new MxfParser(mxf); + Assert.Contains(" e.Contains("--reel-duration")); + } +} From 671b157982944aefaf323f6ad42eea9ba2cd3801 Mon Sep 17 00:00:00 2001 From: niksedk Date: Fri, 16 Oct 2026 07:09:02 +0200 Subject: [PATCH 242/306] Theme-aware colors in the remaining build-time picks Windows that picked a dark or light color while being built (shortcut count badges, fix common errors status and log, file(s) saved dialogs, multiple replace error/preview colors) now use EditorChrome.Themed brushes, so they follow a theme change while open. The ASSA draw canvas repaints on a theme change, and its dark shape line color no longer refers to itself (stack overflow) but to DrawSettings.ShapeLineColor. Co-Authored-By: Claude Opus 5.5 --- .../Features/Assa/AssaDraw/AssaDrawCanvas.cs | 29 +++++++++++++- .../MultipleReplace/MultipleReplaceWindow.cs | 13 ++++--- .../CustomShortcutStepWindow.cs | 2 +- .../Options/Shortcuts/ShortcutsWindow.cs | 2 +- .../PromptFileSaved/PromptFileSavedWindow.cs | 12 +++--- .../PromptFilesSavedWindow.cs | 23 +++++------ .../FixCommonErrorsLogWindow.cs | 7 ++-- .../FixCommonErrors/FixCommonErrorsWindow.cs | 12 +++--- src/ui/Logic/EditorChrome.cs | 6 +++ tests/UI/Logic/EditorChromeThemeTests.cs | 38 +++++++++++++++++++ 10 files changed, 106 insertions(+), 38 deletions(-) diff --git a/src/ui/Features/Assa/AssaDraw/AssaDrawCanvas.cs b/src/ui/Features/Assa/AssaDraw/AssaDrawCanvas.cs index 6daaac637ba..6c05782c631 100644 --- a/src/ui/Features/Assa/AssaDraw/AssaDrawCanvas.cs +++ b/src/ui/Features/Assa/AssaDraw/AssaDrawCanvas.cs @@ -74,7 +74,7 @@ public class AssaDrawCanvas : Control private static IBrush CheckerBrush => IsLight ? LightCheckerBrush : DarkCheckerBrush; private static Color CanvasColor => IsLight ? LightCanvasColor : DrawSettings.BackgroundColor; private static Color GridColor => IsLight ? LightGridColor : DrawSettings.GridColor; - private static Color ShapeLineColor => IsLight ? LightShapeLineColor : ShapeLineColor; + private static Color ShapeLineColor => IsLight ? LightShapeLineColor : DrawSettings.ShapeLineColor; private static Color GuideColor => IsLight ? LightGuideColor : DarkGuideColor; private static Color TextFrameColor => IsLight ? LightTextFrameColor : DarkTextFrameColor; @@ -325,6 +325,33 @@ public AssaDrawCanvas() Focusable = true; } + // The workspace colors are picked at render time from the app theme, so repaint when it + // changes while the window is open. + private Application? _themeApplication; + + protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) + { + base.OnAttachedToVisualTree(e); + _themeApplication = Application.Current; + if (_themeApplication != null) + { + _themeApplication.ActualThemeVariantChanged += OnThemeChanged; + } + } + + protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e) + { + if (_themeApplication != null) + { + _themeApplication.ActualThemeVariantChanged -= OnThemeChanged; + _themeApplication = null; + } + + base.OnDetachedFromVisualTree(e); + } + + private void OnThemeChanged(object? sender, EventArgs e) => InvalidateVisual(); + /// /// Zooms so the whole frame fits the visible area and centers it. /// diff --git a/src/ui/Features/Edit/MultipleReplace/MultipleReplaceWindow.cs b/src/ui/Features/Edit/MultipleReplace/MultipleReplaceWindow.cs index 26d82200ed7..24139c492a1 100644 --- a/src/ui/Features/Edit/MultipleReplace/MultipleReplaceWindow.cs +++ b/src/ui/Features/Edit/MultipleReplace/MultipleReplaceWindow.cs @@ -20,6 +20,10 @@ namespace Nikse.SubtitleEdit.Features.Edit.MultipleReplace; public class MultipleReplaceWindow : Window { + // Follow the app theme, also when it changes while the window is open + private static readonly IBrush ErrorBrush = EditorChrome.Themed(Color.FromRgb(255, 100, 100), Color.FromRgb(183, 28, 28)); + private static readonly IBrush ReplacedBrush = EditorChrome.Themed(Color.FromRgb(100, 220, 100), Color.FromRgb(27, 94, 32)); + public MultipleReplaceWindow(MultipleReplaceViewModel vm) { UiUtil.InitializeWindow(this, GetType().Name); @@ -233,9 +237,7 @@ private static Border MakeRulesView(MultipleReplaceViewModel vm, out TreeView ru // nothing (#13534). var labelError = new Label { - Foreground = new SolidColorBrush(EditorChrome.IsLightTheme - ? Color.FromRgb(183, 28, 28) - : Color.FromRgb(255, 100, 100)), + Foreground = ErrorBrush, VerticalAlignment = VerticalAlignment.Center, VerticalContentAlignment = VerticalAlignment.Center, Margin = new Thickness(0, 0, 4, 0), @@ -579,9 +581,8 @@ private static Grid MakeFixesView(MultipleReplaceViewModel vm) [!ItemsControl.ItemsSourceProperty] = new Binding(nameof(vm.SelectedFixHits)) { Source = vm }, ItemTemplate = new FuncDataTemplate((hit, _) => { - var isDark = !EditorChrome.IsLightTheme; - var redFg = new SolidColorBrush(isDark ? Color.FromRgb(255, 100, 100) : Color.FromRgb(183, 28, 28)); - var greenFg = new SolidColorBrush(isDark ? Color.FromRgb(100, 220, 100) : Color.FromRgb(27, 94, 32)); + var redFg = ErrorBrush; + var greenFg = ReplacedBrush; var iconLabel = new Label { Padding = new Thickness(0, 0, 2, 0), VerticalAlignment = VerticalAlignment.Center }; if (hit.RuleTreeNode != null) diff --git a/src/ui/Features/Options/Shortcuts/CustomShortcuts/CustomShortcutStepWindow.cs b/src/ui/Features/Options/Shortcuts/CustomShortcuts/CustomShortcutStepWindow.cs index 03ab7b52bcf..8a6a39c2807 100644 --- a/src/ui/Features/Options/Shortcuts/CustomShortcuts/CustomShortcutStepWindow.cs +++ b/src/ui/Features/Options/Shortcuts/CustomShortcuts/CustomShortcutStepWindow.cs @@ -55,7 +55,7 @@ public CustomShortcutStepWindow(CustomShortcutStepViewModel vm) FontSize = UiUtil.ScaledFontSize(10), FontWeight = FontWeight.SemiBold, VerticalAlignment = VerticalAlignment.Center, - Foreground = EditorChrome.IsLightTheme ? EditorChrome.AccentBrush : EditorChrome.StrongTextBrush, + Foreground = EditorChrome.BadgeTextBrush, }, }; var panelSearch = new Grid diff --git a/src/ui/Features/Options/Shortcuts/ShortcutsWindow.cs b/src/ui/Features/Options/Shortcuts/ShortcutsWindow.cs index e5d74029c0d..5533d31899a 100644 --- a/src/ui/Features/Options/Shortcuts/ShortcutsWindow.cs +++ b/src/ui/Features/Options/Shortcuts/ShortcutsWindow.cs @@ -74,7 +74,7 @@ public ShortcutsWindow(ShortcutsViewModel vm) FontSize = UiUtil.ScaledFontSize(10), FontWeight = FontWeight.SemiBold, VerticalAlignment = VerticalAlignment.Center, - Foreground = EditorChrome.IsLightTheme ? EditorChrome.AccentBrush : EditorChrome.StrongTextBrush, + Foreground = EditorChrome.BadgeTextBrush, HorizontalAlignment = HorizontalAlignment.Center } }; diff --git a/src/ui/Features/Shared/PromptFileSaved/PromptFileSavedWindow.cs b/src/ui/Features/Shared/PromptFileSaved/PromptFileSavedWindow.cs index 7e5b362e834..0ece97a49ec 100644 --- a/src/ui/Features/Shared/PromptFileSaved/PromptFileSavedWindow.cs +++ b/src/ui/Features/Shared/PromptFileSaved/PromptFileSavedWindow.cs @@ -15,6 +15,10 @@ namespace Nikse.SubtitleEdit.Features.Shared.PromptFileSaved; /// public class PromptFileSavedWindow : Window { + // Follow the app theme, also when it changes while the dialog is open + private static readonly IBrush OkForeground = EditorChrome.Themed(Color.FromRgb(76, 194, 122), Color.FromRgb(46, 158, 91)); + private static readonly IBrush OkBackground = EditorChrome.Themed(Color.FromArgb(60, 46, 158, 91), Color.FromArgb(36, 46, 158, 91)); + public PromptFileSavedWindow(PromptFileSavedViewModel vm) { UiUtil.InitializeWindow(this, GetType().Name); @@ -25,16 +29,12 @@ public PromptFileSavedWindow(PromptFileSavedViewModel vm) vm.Window = this; DataContext = vm; - var isDark = !EditorChrome.IsLightTheme; - var okForeground = new SolidColorBrush(isDark ? Color.FromRgb(76, 194, 122) : Color.FromRgb(46, 158, 91)); - var okBackground = new SolidColorBrush(isDark ? Color.FromArgb(60, 46, 158, 91) : Color.FromArgb(36, 46, 158, 91)); - // Green check in a soft circle: success is readable before the text is. var checkIcon = new ContentControl { HorizontalAlignment = HorizontalAlignment.Center, VerticalAlignment = VerticalAlignment.Center, - Foreground = okForeground, + Foreground = OkForeground, }; Attached.SetIcon(checkIcon, IconNames.Check); var okBall = new Border @@ -42,7 +42,7 @@ public PromptFileSavedWindow(PromptFileSavedViewModel vm) Width = 44, Height = 44, CornerRadius = new CornerRadius(22), - Background = okBackground, + Background = OkBackground, VerticalAlignment = VerticalAlignment.Top, Child = checkIcon, }; diff --git a/src/ui/Features/Shared/PromptFilesSaved/PromptFilesSavedWindow.cs b/src/ui/Features/Shared/PromptFilesSaved/PromptFilesSavedWindow.cs index cd68a41d3fe..425ca582f17 100644 --- a/src/ui/Features/Shared/PromptFilesSaved/PromptFilesSavedWindow.cs +++ b/src/ui/Features/Shared/PromptFilesSaved/PromptFilesSavedWindow.cs @@ -18,10 +18,11 @@ namespace Nikse.SubtitleEdit.Features.Shared.PromptFilesSaved; public class PromptFilesSavedWindow : Window { private readonly PromptFilesSavedViewModel _vm; - private readonly IBrush _okForeground; - private readonly IBrush _okBackground; - private readonly IBrush _warnForeground; - private readonly IBrush _warnBackground; + // Follow the app theme, also when it changes while the dialog is open + private static readonly IBrush OkForeground = EditorChrome.Themed(Color.FromRgb(76, 194, 122), Color.FromRgb(46, 158, 91)); + private static readonly IBrush OkBackground = EditorChrome.Themed(Color.FromArgb(60, 46, 158, 91), Color.FromArgb(36, 46, 158, 91)); + private static readonly IBrush WarnForeground = EditorChrome.Themed(Color.FromRgb(240, 180, 70), Color.FromRgb(196, 128, 16)); + private static readonly IBrush WarnBackground = EditorChrome.Themed(Color.FromArgb(60, 220, 150, 30), Color.FromArgb(36, 220, 150, 30)); private static IBrush ChipBackground => EditorChrome.HoverBrush; public PromptFilesSavedWindow(PromptFilesSavedViewModel vm) @@ -35,12 +36,6 @@ public PromptFilesSavedWindow(PromptFilesSavedViewModel vm) vm.Window = this; DataContext = vm; - var isDark = !EditorChrome.IsLightTheme; - _okForeground = new SolidColorBrush(isDark ? Color.FromRgb(76, 194, 122) : Color.FromRgb(46, 158, 91)); - _okBackground = new SolidColorBrush(isDark ? Color.FromArgb(60, 46, 158, 91) : Color.FromArgb(36, 46, 158, 91)); - _warnForeground = new SolidColorBrush(isDark ? Color.FromRgb(240, 180, 70) : Color.FromRgb(196, 128, 16)); - _warnBackground = new SolidColorBrush(isDark ? Color.FromArgb(60, 220, 150, 30) : Color.FromArgb(36, 220, 150, 30)); - // Header: same language as PromptFileSaved - a green check (amber alert when some jobs // produced nothing) in a soft circle, headline, where the files went and summary chips. var headerBall = MakeStatusBall(vm.AllSucceeded, 44, 22); @@ -143,7 +138,7 @@ private Border MakeStatusBall(bool isSuccess, double size, double iconFontSize) { HorizontalAlignment = HorizontalAlignment.Center, VerticalAlignment = VerticalAlignment.Center, - Foreground = isSuccess ? _okForeground : _warnForeground, + Foreground = isSuccess ? OkForeground : WarnForeground, FontSize = iconFontSize, }; Attached.SetIcon(icon, isSuccess ? IconNames.Check : IconNames.Alert); @@ -153,7 +148,7 @@ private Border MakeStatusBall(bool isSuccess, double size, double iconFontSize) Width = size, Height = size, CornerRadius = new CornerRadius(size / 2), - Background = isSuccess ? _okBackground : _warnBackground, + Background = isSuccess ? OkBackground : WarnBackground, VerticalAlignment = VerticalAlignment.Center, Child = icon, }; @@ -200,8 +195,8 @@ private Control MakeFileCard(SavedFileItem item) else { var statusChip = MakeChip(item.StatusText, emphasized: true); - statusChip.Background = _warnBackground; - ((TextBlock)statusChip.Child!).Foreground = _warnForeground; + statusChip.Background = WarnBackground; + ((TextBlock)statusChip.Child!).Foreground = WarnForeground; panelChips.Children.Add(statusChip); } diff --git a/src/ui/Features/Tools/FixCommonErrors/FixCommonErrorsLogWindow.cs b/src/ui/Features/Tools/FixCommonErrors/FixCommonErrorsLogWindow.cs index ba055b1b693..4ebb08a95e7 100644 --- a/src/ui/Features/Tools/FixCommonErrors/FixCommonErrorsLogWindow.cs +++ b/src/ui/Features/Tools/FixCommonErrors/FixCommonErrorsLogWindow.cs @@ -15,6 +15,9 @@ namespace Nikse.SubtitleEdit.Features.Tools.FixCommonErrors; public class FixCommonErrorsLogWindow : Window { + // Follows the app theme, also when it changes while the window is open + private static readonly IBrush ImportantBrush = EditorChrome.Themed(Color.FromRgb(0xff, 0x8a, 0x80), Color.FromRgb(0xc4, 0x28, 0x28)); + public FixCommonErrorsLogWindow(FixCommonErrorsLogViewModel vm) { UiUtil.InitializeWindow(this, GetType().Name); @@ -51,9 +54,7 @@ public FixCommonErrorsLogWindow(FixCommonErrorsLogViewModel vm) var labelImportant = new TextBlock { FontSize = 12, - Foreground = new SolidColorBrush(EditorChrome.IsLightTheme - ? Color.FromRgb(0xc4, 0x28, 0x28) - : Color.FromRgb(0xff, 0x8a, 0x80)), + Foreground = ImportantBrush, VerticalAlignment = VerticalAlignment.Center, TextTrimming = TextTrimming.CharacterEllipsis, Margin = new Thickness(12, 0), diff --git a/src/ui/Features/Tools/FixCommonErrors/FixCommonErrorsWindow.cs b/src/ui/Features/Tools/FixCommonErrors/FixCommonErrorsWindow.cs index f4d4848cf3f..4a471161db8 100644 --- a/src/ui/Features/Tools/FixCommonErrors/FixCommonErrorsWindow.cs +++ b/src/ui/Features/Tools/FixCommonErrors/FixCommonErrorsWindow.cs @@ -25,6 +25,10 @@ namespace Nikse.SubtitleEdit.Features.Tools.FixCommonErrors; public class FixCommonErrorsWindow : Window { + // Follow the app theme, also when it changes while the window is open + private static readonly IBrush NothingToFixBrush = EditorChrome.Themed(Color.FromRgb(0x6e, 0xcb, 0x87), Color.FromRgb(0x1e, 0x7e, 0x34)); + private static readonly IBrush ErrorsFoundBrush = EditorChrome.Themed(Color.FromRgb(0xff, 0x8a, 0x80), Color.FromRgb(0xc4, 0x28, 0x28)); + private readonly FixCommonErrorsViewModel _vm; private Button? _buttonApplySelectedFixes; @@ -236,9 +240,7 @@ public FixCommonErrorsWindow(FixCommonErrorsViewModel vm) // Green check + "Nothing to fix" so an empty re-scan gives visible feedback; the counters // stay as they were, which on its own reads as "the button did nothing" (#12849). - var nothingToFixBrush = new SolidColorBrush(EditorChrome.IsLightTheme - ? Color.FromRgb(0x1e, 0x7e, 0x34) - : Color.FromRgb(0x6e, 0xcb, 0x87)); + var nothingToFixBrush = NothingToFixBrush; var nothingToFixText = new TextBlock { Text = Se.Language.Tools.FixCommonErrors.NothingToFix, @@ -268,9 +270,7 @@ public FixCommonErrorsWindow(FixCommonErrorsViewModel vm) // Errors the rules found but could not fix (e.g. a display time that is too short with no // room to extend it). Without this they are silent, and a subtitle full of them still shows // the green "Nothing to fix" - click the warning to see one log line per error (#13645). - var errorsFoundBrush = new SolidColorBrush(EditorChrome.IsLightTheme - ? Color.FromRgb(0xc4, 0x28, 0x28) - : Color.FromRgb(0xff, 0x8a, 0x80)); + var errorsFoundBrush = ErrorsFoundBrush; var errorsFoundIcon = new Optris.Icons.Avalonia.Icon { Value = IconNames.Alert, diff --git a/src/ui/Logic/EditorChrome.cs b/src/ui/Logic/EditorChrome.cs index 14a1eba39a4..508f74957ba 100644 --- a/src/ui/Logic/EditorChrome.cs +++ b/src/ui/Logic/EditorChrome.cs @@ -56,6 +56,11 @@ internal static class EditorChrome internal static IBrush MenuBrush => MenuColor.Brush; internal static IBrush MenuBorderBrush => MenuBorderColor.Brush; + /// + /// Text on an AccentSoftBrush badge (counts): white on dark, accent on light. + /// + internal static IBrush BadgeTextBrush => BadgeTextColor.Brush; + private static readonly ThemedBrush StrongTextColor = new(Colors.White, Colors.Black); private static readonly ThemedBrush ThumbnailColor = new(Color.FromRgb(214, 218, 226), Color.FromRgb(72, 78, 92)); private static readonly ThemedBrush ThumbnailMutedColor = new(Color.FromRgb(96, 102, 116), Color.FromRgb(188, 193, 204)); @@ -77,6 +82,7 @@ internal static class EditorChrome private static readonly ThemedBrush FieldBorderColor = new(Color.FromRgb(54, 57, 68), Color.FromRgb(204, 209, 219)); private static readonly ThemedBrush MenuColor = new(Color.FromRgb(42, 44, 51), Color.FromRgb(255, 255, 255)); private static readonly ThemedBrush MenuBorderColor = new(Color.FromRgb(66, 69, 79), Color.FromRgb(208, 212, 220)); + private static readonly ThemedBrush BadgeTextColor = new(Colors.White, Color.FromRgb(76, 110, 245)); /// /// A brush outside the palette that also follows the app theme (e.g. a status color with a diff --git a/tests/UI/Logic/EditorChromeThemeTests.cs b/tests/UI/Logic/EditorChromeThemeTests.cs index 185f1790281..571ccd60fbd 100644 --- a/tests/UI/Logic/EditorChromeThemeTests.cs +++ b/tests/UI/Logic/EditorChromeThemeTests.cs @@ -1,7 +1,12 @@ +using System.Linq; using Avalonia; +using Avalonia.Controls; using Avalonia.Headless.XUnit; +using Avalonia.LogicalTree; using Avalonia.Media; using Avalonia.Styling; +using Avalonia.Threading; +using Nikse.SubtitleEdit.Features.Tools.FixCommonErrors; using Nikse.SubtitleEdit.Logic; namespace UITests.Logic; @@ -37,4 +42,37 @@ public void PaletteBrush_IsShared_AndFollowsAThemeChange() application.RequestedThemeVariant = old; } } + + [AvaloniaFact] + public void OpenWindow_SemanticColorOutsideThePalette_FollowsAThemeChange() + { + var application = Application.Current!; + var old = application.RequestedThemeVariant; + application.RequestedThemeVariant = ThemeVariant.Light; + var vm = new FixCommonErrorsLogViewModel(); + vm.Initialize(new[] { "a" }, System.Array.Empty(), 1); + var window = new FixCommonErrorsLogWindow(vm); + try + { + window.Show(); + Dispatcher.UIThread.RunJobs(); + + // The red "important messages" label was picked once when the window was built + var label = window.GetLogicalDescendants().OfType() + .First(t => t.Text == vm.ImportantMessagesText); + var brush = Assert.IsAssignableFrom(label.Foreground); + var light = brush.Color; + + application.RequestedThemeVariant = ThemeVariant.Dark; + Dispatcher.UIThread.RunJobs(); + + Assert.Same(brush, label.Foreground); + Assert.NotEqual(light, brush.Color); + } + finally + { + window.Close(); + application.RequestedThemeVariant = old; + } + } } From 7221145456674fa996bbec13c2769c2ecc825809 Mon Sep 17 00:00:00 2001 From: niksedk Date: Fri, 16 Oct 2026 07:28:34 +0200 Subject: [PATCH 243/306] Editor look polish - Text boxes in editor windows keep their own background while focused or hovered instead of the theme's black/white focus background. - A switch without on/off texts is just its 40px track, so the label sits 8px from it instead of 20px (the theme kept a content column after it). - Color picker: a channel value of 0 is shown (the label only updated on a change, and 0 is the default). - Shortcuts: group tile names wrap to two lines instead of being cut off. - Settings: wide fields (video player, profile, ... combo boxes, the favorites lists, the subtitle preview group) shrink with a narrow window instead of being clipped, and the label column gives up some width when narrow; while searching, the category highlight follows the results. Co-Authored-By: Claude Opus 5.5 --- .../Options/Settings/SettingsChrome.cs | 18 ++++++ .../Features/Options/Settings/SettingsItem.cs | 16 ++++- .../Features/Options/Settings/SettingsPage.cs | 50 +++++++++++---- .../Options/Shortcuts/ShortcutsWindow.cs | 5 ++ .../Shared/ColorPicker/ColorChannelSlider.cs | 3 + src/ui/Logic/EditorChrome.cs | 30 +++++++++ .../Settings/SettingsSearchHighlightTests.cs | 64 +++++++++++++++++++ 7 files changed, 170 insertions(+), 16 deletions(-) create mode 100644 tests/UI/Features/Options/Settings/SettingsSearchHighlightTests.cs diff --git a/src/ui/Features/Options/Settings/SettingsChrome.cs b/src/ui/Features/Options/Settings/SettingsChrome.cs index dbb9f16a922..95621f5b4a5 100644 --- a/src/ui/Features/Options/Settings/SettingsChrome.cs +++ b/src/ui/Features/Options/Settings/SettingsChrome.cs @@ -208,6 +208,24 @@ internal static void DimLabelWhenDisabled(Control control, TextBlock label) Update(); } + /// + /// Turns the fixed width of into the most it takes: it keeps that + /// width (left aligned) where there is room and shrinks with a narrow window instead of being + /// cut off at the right edge of the field column. + /// + internal static Grid ShrinkToFit(Control control) + { + var grid = new Grid + { + ColumnDefinitions = { new ColumnDefinition(1, GridUnitType.Star) { MaxWidth = control.Width } }, + VerticalAlignment = control.VerticalAlignment == VerticalAlignment.Stretch ? VerticalAlignment.Center : control.VerticalAlignment, + }; + control.Width = double.NaN; + control.HorizontalAlignment = HorizontalAlignment.Stretch; + grid.Children.Add(control); + return grid; + } + /// /// Cuts a long text (a file path) with an ellipsis at the edge of the field column. /// diff --git a/src/ui/Features/Options/Settings/SettingsItem.cs b/src/ui/Features/Options/Settings/SettingsItem.cs index 97cd07316f7..6818f743653 100644 --- a/src/ui/Features/Options/Settings/SettingsItem.cs +++ b/src/ui/Features/Options/Settings/SettingsItem.cs @@ -197,6 +197,12 @@ public Control Build(bool includeLabel = true) control.VerticalAlignment = VerticalAlignment.Center; } + // A wide combo box or text box shrinks with a narrow window instead of being cut off + // at the right edge - its width becomes the most it takes. + var field = control is ComboBox or TextBox && !double.IsNaN(control.Width) + ? SettingsChrome.ShrinkToFit(control) + : control; + // A control of fixed width would be centered in the field column if (control.HorizontalAlignment == HorizontalAlignment.Stretch && !double.IsNaN(control.Width)) { @@ -206,7 +212,7 @@ public Control Build(bool includeLabel = true) // Top aligned, in line with the first line of a taller control (a list, a group) labelTextBlock.VerticalAlignment = VerticalAlignment.Top; labelTextBlock.Margin = new Thickness(0, 6, 0, 0); - row = MakeColumns(labelTextBlock, control); + row = MakeColumns(labelTextBlock, field); } row.MinHeight = 30; @@ -224,10 +230,16 @@ public Control Build(bool includeLabel = true) /// private static Grid MakeColumns(Control left, Control content) { + // The label column has its full width where there is room; on a narrow window it gives + // up some of it (the labels wrap) so the fields are not cut off at the right edge. var grid = new Grid { ColumnSpacing = 12, - ColumnDefinitions = { new ColumnDefinition(new GridLength(SettingsChrome.LabelWidth)), new ColumnDefinition(new GridLength(1, GridUnitType.Star)) }, + ColumnDefinitions = + { + new ColumnDefinition(2, GridUnitType.Star) { MaxWidth = SettingsChrome.LabelWidth }, + new ColumnDefinition(3, GridUnitType.Star), + }, }; grid.Add(left, 0, 0); grid.Add(content, 0, 1); diff --git a/src/ui/Features/Options/Settings/SettingsPage.cs b/src/ui/Features/Options/Settings/SettingsPage.cs index cc24aef91d9..72461cdd834 100644 --- a/src/ui/Features/Options/Settings/SettingsPage.cs +++ b/src/ui/Features/Options/Settings/SettingsPage.cs @@ -31,6 +31,7 @@ public class SettingsPage : UserControl { private const double SidePanelMinWidth = 210; private const double SidePanelMaxWidth = 280; + private const string StretchInGroupClass = "stretch-in-group"; private readonly TextBox _searchBox; private readonly StackPanel _contentPanel; @@ -281,11 +282,22 @@ private void UpdateVisibleSections(string filter) UpdateMenuHighlight(); } + /// + /// The selected category is highlighted. While searching it is only when it has a match - + /// otherwise the first category with a match is (none when nothing matches), so the side + /// panel does not point at a category that is not in the results. + /// private void UpdateMenuHighlight() { + var highlighted = _vm.SelectedSection; + if (!string.IsNullOrWhiteSpace(_searchBox.Text) && highlighted is not { IsVisible: true }) + { + highlighted = _vm.Sections.FirstOrDefault(section => section.IsVisible); + } + foreach (var (section, button) in _menuButtons) { - button.Classes.Set(SettingsChrome.SelectedClass, section == _vm.SelectedSection); + button.Classes.Set(SettingsChrome.SelectedClass, section == highlighted); } } @@ -639,13 +651,7 @@ private List CreateSections() sections.Add(new SettingsSection(Se.Language.General.VideoPlayer, IconNames.PlayBox, "#a78bfa", [ - new SettingsItem(Se.Language.General.VideoPlayer, () => new StackPanel - { - Children = - { - MakeVideoPlayerComboBox() - } - }), + new SettingsItem(Se.Language.General.VideoPlayer, MakeVideoPlayerComboBox), new SettingsItem(Se.Language.Options.Settings.VideoControls, () => UiUtil.MakeButton(Se.Language.Options.Settings.VideoControlsEdit, _vm.EditVideoControlsCommand)), MakeCheckboxSetting(Se.Language.Options.Settings.FullscreenHideControls, nameof(_vm.FullscreenHideControls)), @@ -1213,7 +1219,6 @@ private Grid MakeLanguageFavoritesGrid(SettingsViewModel vm) private static Grid MakeFavoritesList(ListBox listBox, string listName, IRelayCommand add, IRelayCommand remove, IRelayCommand moveUp, IRelayCommand moveDown) { listBox.Height = 200; - listBox.Width = 250; listBox.Background = Brushes.Transparent; var buttonAdd = EditorChrome.CreateToolButton("fa-solid fa-plus", Se.Language.General.Add, add, 28); @@ -1233,13 +1238,14 @@ private static Grid MakeFavoritesList(ListBox listBox, string listName, IRelayCo Children = { buttonAdd, buttonRemove, buttonMoveUp, buttonMoveDown } }; + // The list is 250 wide where there is room and shrinks with a narrow window, the + // buttons stay beside it var grid = new Grid { ColumnSpacing = 6, - HorizontalAlignment = HorizontalAlignment.Left, ColumnDefinitions = { - new ColumnDefinition { Width = GridLength.Auto }, + new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star), MaxWidth = 250 }, new ColumnDefinition { Width = GridLength.Auto }, }, }; @@ -1280,6 +1286,7 @@ private Control MakeMpvPreviewSettings(SettingsViewModel vm) { var labelFontName = SettingsChrome.MakeLabel(Se.Language.General.FontName); var comboBoxFontName = UiUtil.MakeComboBox(vm.Fonts, vm, nameof(vm.MpvPreviewFontName)).WithMinWidth(150).WithLabeledBy(labelFontName); + comboBoxFontName.Classes.Add(StretchInGroupClass); var labelFontSize = SettingsChrome.MakeLabel(Se.Language.General.FontSize); var numericUpDownFontSize = UiUtil.MakeNumericUpDownOneDecimal(1, 1000, 130, vm, nameof(vm.MpvPreviewFontSize)).WithLabeledBy(labelFontSize); @@ -1289,9 +1296,11 @@ private Control MakeMpvPreviewSettings(SettingsViewModel vm) var labelAlignment = SettingsChrome.MakeLabel(Se.Language.General.Alignment); var comboBoxAlignment = UiUtil.MakeComboBox(vm.MpvPreviewFontAlignments, vm, nameof(vm.MpvPreviewSelectedFontAlignment)).WithLabeledBy(labelAlignment); + comboBoxAlignment.Classes.Add(StretchInGroupClass); var labelJustify = SettingsChrome.MakeLabel(Se.Language.Options.Settings.TextJustify); var comboBoxJustify = UiUtil.MakeComboBox(vm.MpvPreviewJustifyItems, vm, nameof(vm.MpvPreviewSelectedJustify)).WithLabeledBy(labelJustify); + comboBoxJustify.Classes.Add(StretchInGroupClass); var switchUsePositionFromFile = SettingsChrome.MakeSwitchRow( SettingsChrome.MakeSwitch(vm, nameof(vm.MpvPreviewUsePositionFromFile)), Se.Language.Options.Settings.UsePositionFromSubtitleFile); @@ -1314,6 +1323,7 @@ private Control MakeMpvPreviewSettings(SettingsViewModel vm) var labelBorderStyle = SettingsChrome.MakeLabel(Se.Language.General.BorderStyle); var comboBoxBorderType = UiUtil.MakeComboBox(vm.MpvPreviewBorderTypes, vm, nameof(vm.MpvPreviewSelectedBorderType)).WithLabeledBy(labelBorderStyle); + comboBoxBorderType.Classes.Add(StretchInGroupClass); var labelOutlineWidth = SettingsChrome.MakeLabel(Se.Language.General.OutlineWidth); var numericUpDownOutlineWidth = UiUtil.MakeNumericUpDownOneDecimal(0, 100, 130, vm, nameof(vm.MpvPreviewOutlineWidth)).WithLabeledBy(labelOutlineWidth); @@ -1323,12 +1333,14 @@ private Control MakeMpvPreviewSettings(SettingsViewModel vm) var numericUpDownShadowWidth = UiUtil.MakeNumericUpDownOneDecimal(0, 100, 130, vm, nameof(vm.MpvPreviewShadowWidth)).WithLabeledBy(labelShadowWidth); numericUpDownShadowWidth.Increment = 0.5m; + // The fields take the rest of the group (a star column), so on a narrow window the combo + // boxes shrink with it instead of being cut off at the right edge. var grid = new Grid { ColumnDefinitions = { new ColumnDefinition { Width = new GridLength(1, GridUnitType.Auto) }, - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Auto) }, + new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) }, }, ColumnSpacing = 12, RowSpacing = 6, @@ -1381,7 +1393,17 @@ private Control MakeMpvPreviewSettings(SettingsViewModel vm) { if (child is not Grid) { - child.HorizontalAlignment = HorizontalAlignment.Left; + // The combo boxes as wide as the widest of them, the rest as wide as they are + if (child.Classes.Contains(StretchInGroupClass)) + { + child.MinWidth = 0; + child.HorizontalAlignment = HorizontalAlignment.Stretch; + } + else + { + child.HorizontalAlignment = HorizontalAlignment.Left; + } + child.VerticalAlignment = VerticalAlignment.Center; } } @@ -1402,7 +1424,7 @@ private ComboBox MakeVideoPlayerComboBox() new TextBlock { [!TextBlock.TextProperty] = new Binding(nameof(VideoPlayerItem.Name)), - Width = 250, + TextTrimming = TextTrimming.CharacterEllipsis, }, true) }; diff --git a/src/ui/Features/Options/Shortcuts/ShortcutsWindow.cs b/src/ui/Features/Options/Shortcuts/ShortcutsWindow.cs index 5533d31899a..507119bf354 100644 --- a/src/ui/Features/Options/Shortcuts/ShortcutsWindow.cs +++ b/src/ui/Features/Options/Shortcuts/ShortcutsWindow.cs @@ -155,11 +155,16 @@ public ShortcutsWindow(ShortcutsViewModel vm) }; glyph.Bind(Border.BackgroundProperty, new Binding(nameof(ShortcutGroupTile.Brush))); + // Long group names ("Subtitle list view & ...") wrap to a second line instead of + // being cut off at the tile width. var name = new TextBlock { FontSize = UiUtil.ScaledFontSize(11), HorizontalAlignment = HorizontalAlignment.Center, TextAlignment = TextAlignment.Center, + TextWrapping = TextWrapping.Wrap, + MaxLines = 2, + TextTrimming = TextTrimming.CharacterEllipsis, }; name.Bind(TextBlock.TextProperty, new Binding(nameof(ShortcutGroupTile.Name))); diff --git a/src/ui/Features/Shared/ColorPicker/ColorChannelSlider.cs b/src/ui/Features/Shared/ColorPicker/ColorChannelSlider.cs index 2b06258808f..442e5f6ebe3 100644 --- a/src/ui/Features/Shared/ColorPicker/ColorChannelSlider.cs +++ b/src/ui/Features/Shared/ColorPicker/ColorChannelSlider.cs @@ -121,6 +121,9 @@ public ColorChannelSlider() Children.Add(sliderContainer); Children.Add(_valueLabel); + // A bound value of 0 is the default, so it raises no change below - show it from the start + _valueLabel.Text = Value.ToString(); + PropertyChanged += (s, e) => { if (e.Property == ValueProperty) diff --git a/src/ui/Logic/EditorChrome.cs b/src/ui/Logic/EditorChrome.cs index 508f74957ba..56461730edd 100644 --- a/src/ui/Logic/EditorChrome.cs +++ b/src/ui/Logic/EditorChrome.cs @@ -219,6 +219,36 @@ static Style ToolPresenter(Func button, params (AvaloniaPro }, }); + // Text boxes keep their own background while hovered/focused - the theme turns a focused + // box black (dark) or white (light), which reads as a different control on the field color. + foreach (var state in new[] { ":pointerover", ":focus" }) + { + styles.Add(new Style(x => x.OfType().Class(state).Template().OfType().Name("PART_BorderElement")) + { + Setters = + { + new Setter(Border.BackgroundProperty, new Binding(nameof(TextBox.Background)) + { + RelativeSource = new RelativeSource(RelativeSourceMode.TemplatedParent), + }), + }, + }); + } + + // A switch without on/off texts and header is just the 40px knob track - the theme keeps a + // 12px content column after it, which put a wide gap between a switch and its label. + styles.Add(new Style(x => x.OfType() + .PropertyEquals(ContentControl.ContentProperty, null) + .PropertyEquals(ToggleSwitch.OnContentProperty, null) + .PropertyEquals(ToggleSwitch.OffContentProperty, null)) + { + Setters = + { + new Setter(Layoutable.WidthProperty, 40.0), + new Setter(Layoutable.MinWidthProperty, 0.0), + }, + }); + // Menus: rounded, darker, accent highlight foreach (var menuType in new[] { typeof(ContextMenu), typeof(MenuFlyoutPresenter) }) { diff --git a/tests/UI/Features/Options/Settings/SettingsSearchHighlightTests.cs b/tests/UI/Features/Options/Settings/SettingsSearchHighlightTests.cs new file mode 100644 index 00000000000..336ad13ec07 --- /dev/null +++ b/tests/UI/Features/Options/Settings/SettingsSearchHighlightTests.cs @@ -0,0 +1,64 @@ +using System; +using System.Linq; +using Avalonia.Controls; +using Avalonia.Headless.XUnit; +using Avalonia.LogicalTree; +using Avalonia.Threading; +using Microsoft.Extensions.DependencyInjection; +using Nikse.SubtitleEdit; +using Nikse.SubtitleEdit.Features.Main; +using Nikse.SubtitleEdit.Features.Options.Settings; +using Nikse.SubtitleEdit.Logic; +using Nikse.SubtitleEdit.Logic.Config; + +namespace UITests.Features.Options.Settings; + +/// +/// While searching, the category highlight in the side panel follows the results instead of +/// staying on a category that has no match. +/// +public class SettingsSearchHighlightTests +{ + [AvaloniaFact] + public void Search_HighlightsTheFirstMatchingCategory_AndTheSelectedOneAgainWhenCleared() + { + var services = new ServiceCollection(); + services.AddSubtitleEditServices(); + Locator.Services = services.BuildServiceProvider(); + var vm = Locator.Services.GetRequiredService(); + var window = new SettingsWindow(vm); + try + { + window.Show(); + Dispatcher.UIThread.RunJobs(); + + var rules = vm.Sections.First(s => s.Title == Se.Language.General.Rules); + var waveform = vm.Sections.First(s => s.Title == Se.Language.General.WaveformSpectrogram); + vm.SelectedSection = rules; + Dispatcher.UIThread.RunJobs(); + + Button NavButton(SettingsSection section) => window.GetLogicalDescendants().OfType internal static void Apply(Window window) { - EditDialogChrome.Apply(window); + EditorChrome.ApplyWindow(window, EditorWindowStyles.Dialog); var styles = window.Styles; styles.Add(new Style(x => x.OfType private static Border MakeAdvancedSection(SpeechToTextViewModel vm) { - var buttonAdvancedSettings = Chrome.Tool(UiUtil.MakeButton(vm.ShowAdvancedSettingsCommand, IconNames.Settings), 26) + var buttonAdvancedSettings = EditorChrome.AsToolButton(UiUtil.MakeButton(vm.ShowAdvancedSettingsCommand, IconNames.Settings), 26) .BindIsEnabled(vm, nameof(vm.IsTranscribeEnabled)) .WithAccessibleName(Se.Language.General.AdvancedSettings); if (Se.Settings.Appearance.ShowHints) @@ -538,7 +537,7 @@ private static Border MakeAdvancedSection(SpeechToTextViewModel vm) /// private static Grid MakeLogPanel(SpeechToTextViewModel vm) { - var buttonCopyConsoleLog = Chrome.Tool(UiUtil.MakeButton( + var buttonCopyConsoleLog = EditorChrome.AsToolButton(UiUtil.MakeButton( vm.CopyConsoleLogCommand, IconNames.Copy, Se.Language.General.CopyTextToClipboard), 26); @@ -557,7 +556,7 @@ private static Grid MakeLogPanel(SpeechToTextViewModel vm) }, Margin = new Thickness(14, 12, 14, 14), }; - grid.Add(Chrome.ListHeader(Se.Language.General.ConsoleLog, buttonCopyConsoleLog), 0, 0); + grid.Add(SpeechToTextChrome.ListHeader(Se.Language.General.ConsoleLog, buttonCopyConsoleLog), 0, 0); grid.Add(body, 1, 0); return grid; } @@ -638,16 +637,14 @@ Button Action(string icon, string name, System.Windows.Input.ICommand command) var buttonClear = Action("fa-solid fa-trash", Se.Language.General.Clear, vm.ClearCommand); var labelAddLanguageCode = Label(Se.Language.Video.AudioToText.AddLanguageCodeToFileName); - var switchAddLanguageCode = Chrome.EnabledBy( - Chrome.Switch(vm, nameof(vm.AddLanguageCodeToFileName), Se.Language.Video.AudioToText.AddLanguageCodeToFileName), - vm, nameof(vm.IsTranscribeEnabled)); + var switchAddLanguageCode = EditorChrome.EnabledBy(EditorChrome.MakeSwitch(nameof(vm.AddLanguageCodeToFileName), Se.Language.Video.AudioToText.AddLanguageCodeToFileName, vm), vm, nameof(vm.IsTranscribeEnabled)); if (Se.Settings.Appearance.ShowHints) { ToolTip.SetTip(switchAddLanguageCode, Se.Language.Video.AudioToText.AddLanguageCodeToFileNameHint); ToolTip.SetTip(labelAddLanguageCode, Se.Language.Video.AudioToText.AddLanguageCodeToFileNameHint); } - var rowAddLanguageCode = Chrome.SwitchRow(switchAddLanguageCode, labelAddLanguageCode); + var rowAddLanguageCode = EditorChrome.MakeSwitchRow(switchAddLanguageCode, labelAddLanguageCode); rowAddLanguageCode.Margin = new Thickness(0, 8, 0, 0); // hack to make drag and drop work on the grid - also on empty rows @@ -660,7 +657,7 @@ Button Action(string icon, string name, System.Windows.Input.ICommand command) dropHost.AddHandler(DragDrop.DragOverEvent, vm.FileGridOnDragOver, RoutingStrategies.Bubble); dropHost.AddHandler(DragDrop.DropEvent, vm.FileGridOnDrop, RoutingStrategies.Bubble); - var batchHeader = Chrome.ListHeader(Se.Language.General.BatchMode, buttonAdd, buttonRemove, buttonClear); + var batchHeader = SpeechToTextChrome.ListHeader(Se.Language.General.BatchMode, buttonAdd, buttonRemove, buttonClear); batchHeader.Margin = new Thickness(0, 14, 0, 8); var grid = new Grid @@ -675,9 +672,9 @@ Button Action(string icon, string name, System.Windows.Input.ICommand command) HorizontalAlignment = HorizontalAlignment.Stretch, }; - grid.Add(Chrome.FieldFrame(textBoxConsoleLog), 0, 0); + grid.Add(EditorChrome.MakeListFrame(textBoxConsoleLog), 0, 0); grid.Add(batchHeader, 1, 0); - grid.Add(Chrome.FieldFrame(dropHost), 2, 0); + grid.Add(EditorChrome.MakeListFrame(dropHost), 2, 0); grid.Add(rowAddLanguageCode, 3, 0); grid.WithBindVisible(vm, nameof(vm.IsBatchMode)); @@ -690,7 +687,7 @@ private static Border MakeConsoleLogOnlyView(SpeechToTextViewModel vm) var textBoxConsoleLog = MakeConsoleLogTextBox(vm); vm.TextBoxConsoleLogSingle = textBoxConsoleLog; - var frame = Chrome.FieldFrame(textBoxConsoleLog); + var frame = EditorChrome.MakeListFrame(textBoxConsoleLog); frame.Bind(Visual.IsVisibleProperty, new Binding(nameof(vm.IsBatchMode)) { Source = vm, Converter = InverseBooleanConverter.Instance }); return frame; } @@ -712,9 +709,9 @@ private Border MakeProgressView(SpeechToTextViewModel vm) UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged, }); - var progressText = Chrome.Hint(nameof(vm.ProgressText)); - var elapsedTimeText = Chrome.Hint(nameof(vm.ElapsedText)); - var estimatedTimeText = Chrome.Hint(nameof(vm.EstimatedText)); + var progressText = EditorChrome.MakeBoundHint(nameof(vm.ProgressText), 12); + var elapsedTimeText = EditorChrome.MakeBoundHint(nameof(vm.ElapsedText), 12); + var estimatedTimeText = EditorChrome.MakeBoundHint(nameof(vm.EstimatedText), 12); var times = new StackPanel { Orientation = Orientation.Horizontal, Spacing = 16, VerticalAlignment = VerticalAlignment.Center }; times.Children.Add(elapsedTimeText); @@ -805,9 +802,9 @@ void Update() }, }); - var transcribeButton = Chrome.Primary(UiUtil.MakeButton(Se.Language.Video.AudioToText.Transcribe, vm.TranscribeCommand)) + var transcribeButton = EditorChrome.AsPrimary(UiUtil.MakeButton(Se.Language.Video.AudioToText.Transcribe, vm.TranscribeCommand)) .BindIsEnabled(vm, nameof(vm.IsTranscribeEnabled)); - var bar = Chrome.ButtonBar(modes, UiUtil.MakeButtonCancel(vm.CancelCommand), transcribeButton); + var bar = EditorChrome.MakeFlushButtonBar(modes, UiUtil.MakeButtonCancel(vm.CancelCommand), transcribeButton); bar.Name = ButtonBarName; return bar; } diff --git a/src/ui/Features/Video/TextToSpeech/ActorVoices/ActorVoiceMappingWindow.cs b/src/ui/Features/Video/TextToSpeech/ActorVoices/ActorVoiceMappingWindow.cs index d668d1d41b0..2141ea8fc8b 100644 --- a/src/ui/Features/Video/TextToSpeech/ActorVoices/ActorVoiceMappingWindow.cs +++ b/src/ui/Features/Video/TextToSpeech/ActorVoices/ActorVoiceMappingWindow.cs @@ -44,7 +44,7 @@ public ActorVoiceMappingWindow(ActorVoiceMappingViewModel vm) MinWidth = 700; MinHeight = 400; CanResize = true; - TtsVoicesChrome.Apply(this); + EditorChrome.ApplyWindow(this, EditorWindowStyles.TextFieldsUncentered | EditorWindowStyles.MuteDisabledPrimary); _vm = vm; vm.Window = this; @@ -326,7 +326,7 @@ private static Border BuildGrid(ActorVoiceMappingViewModel vm) Width = new GridLength(110), }); - return TtsVoicesChrome.MakeListFrame(tableView); + return EditorChrome.MakeListFrame(tableView); } /// @@ -334,14 +334,14 @@ private static Border BuildGrid(ActorVoiceMappingViewModel vm) /// private static Border BuildButtons(ActorVoiceMappingViewModel vm) { - var applyDefault = TtsVoicesChrome.MakeAction("fa-solid fa-wand-magic-sparkles", Se.Language.Video.TextToSpeech.ApplyDefaultToAll, vm.ApplyDefaultToAllCommand); - var clearAll = TtsVoicesChrome.MakeAction("fa-solid fa-eraser", Se.Language.General.Clear, vm.ClearAllCommand); + var applyDefault = EditorChrome.MakeActionButton("fa-solid fa-wand-magic-sparkles", Se.Language.Video.TextToSpeech.ApplyDefaultToAll, vm.ApplyDefaultToAllCommand); + var clearAll = EditorChrome.MakeActionButton("fa-solid fa-eraser", Se.Language.General.Clear, vm.ClearAllCommand); var ok = UiUtil.MakeButtonOk(vm.OkCommand); ok.Classes.Add("primary"); var cancel = UiUtil.MakeButtonCancel(vm.CancelCommand); var actions = new StackPanel { Orientation = Orientation.Horizontal, Spacing = 2, Margin = new Thickness(-10, 0, 0, 0), Children = { applyDefault, clearAll } }; - return TtsVoicesChrome.MakeButtonBar(actions, cancel, ok); + return EditorChrome.MakeFlushButtonBar(actions, cancel, ok); } protected override void OnKeyDown(KeyEventArgs e) diff --git a/src/ui/Features/Video/TextToSpeech/ActorVoices/ActorVoiceRowSettingsWindow.cs b/src/ui/Features/Video/TextToSpeech/ActorVoices/ActorVoiceRowSettingsWindow.cs index 2d2d4e7f172..6d819cb3736 100644 --- a/src/ui/Features/Video/TextToSpeech/ActorVoices/ActorVoiceRowSettingsWindow.cs +++ b/src/ui/Features/Video/TextToSpeech/ActorVoices/ActorVoiceRowSettingsWindow.cs @@ -43,7 +43,7 @@ public ActorVoiceRowSettingsWindow(ActorVoiceRowSettingsViewModel vm) SizeToContent = SizeToContent.WidthAndHeight; CanResize = false; MinWidth = 460; - TtsVoicesChrome.Apply(this); + EditorChrome.ApplyWindow(this, EditorWindowStyles.TextFieldsUncentered | EditorWindowStyles.MuteDisabledPrimary); _vm = vm; vm.Window = this; @@ -56,7 +56,7 @@ public ActorVoiceRowSettingsWindow(ActorVoiceRowSettingsViewModel vm) var buttonOk = UiUtil.MakeButtonOk(vm.OkCommand); buttonOk.Classes.Add("primary"); var buttonCancel = UiUtil.MakeButtonCancel(vm.CancelCommand); - var buttons = TtsVoicesChrome.MakeButtonBar(null, buttonCancel, buttonOk); + var buttons = EditorChrome.MakeFlushButtonBar(null, buttonCancel, buttonOk); var sections = new StackPanel { @@ -150,7 +150,7 @@ private static Border BuildInstructionBlock(ActorVoiceRowSettingsViewModel vm) }; AutomationProperties.SetName(textBox, Se.Language.Video.TextToSpeech.VoiceInstruction); - var hint = TtsVoicesChrome.MakeHint(Se.Language.Video.TextToSpeech.VoiceInstructionFreeTextHint); + var hint = EditorChrome.MakeHint(Se.Language.Video.TextToSpeech.VoiceInstructionFreeTextHint); hint.MaxWidth = 430; hint.HorizontalAlignment = HorizontalAlignment.Left; diff --git a/src/ui/Features/Video/TextToSpeech/AudioCppTtsSettings/AudioCppTtsSettingsWindow.cs b/src/ui/Features/Video/TextToSpeech/AudioCppTtsSettings/AudioCppTtsSettingsWindow.cs index b608bbf6471..55feee88ca1 100644 --- a/src/ui/Features/Video/TextToSpeech/AudioCppTtsSettings/AudioCppTtsSettingsWindow.cs +++ b/src/ui/Features/Video/TextToSpeech/AudioCppTtsSettings/AudioCppTtsSettingsWindow.cs @@ -49,7 +49,7 @@ private static Border BuildModels(AudioCppTtsSettingsViewModel vm) TtsSettingsChrome.MakeModelDownloadButton(vm.DownloadModelCommand, vm.Adapter.ModelKeyAlt))), (Se.Language.Video.Voices, TtsSettingsChrome.MakeValue(nameof(vm.VoicesLabel), openVoicesFolder)), (Se.Language.General.InstallFolder, TtsSettingsChrome.MakeFolder(nameof(vm.ModelsFolder), vm.OpenModelsFolderCommand))); - return TtsSettingsChrome.MakeSection(Se.Language.General.Models, rows); + return EditorChrome.MakeEditorSection(Se.Language.General.Models, rows); } protected override void OnKeyDown(KeyEventArgs e) diff --git a/src/ui/Features/Video/TextToSpeech/AutoCast/AutoCastSpeakersWindow.cs b/src/ui/Features/Video/TextToSpeech/AutoCast/AutoCastSpeakersWindow.cs index 414b7d1baaa..f3c51c718d9 100644 --- a/src/ui/Features/Video/TextToSpeech/AutoCast/AutoCastSpeakersWindow.cs +++ b/src/ui/Features/Video/TextToSpeech/AutoCast/AutoCastSpeakersWindow.cs @@ -40,7 +40,7 @@ public AutoCastSpeakersWindow(AutoCastSpeakersViewModel vm) MinWidth = 600; MinHeight = 360; CanResize = true; - TtsMainEditorLook.Apply(this); + EditorChrome.ApplyWindow(this, EditorWindowStyles.TextFieldsBare | EditorWindowStyles.MuteDisabledPrimary); vm.Window = this; DataContext = vm; @@ -185,7 +185,7 @@ private static Border BuildTable(AutoCastSpeakersViewModel vm) TableViewExtras.BindSelectedItem(tableView, vm, nameof(vm.SelectedRow)); AutomationProperties.SetName(tableView, Se.Language.Video.TextToSpeech.AutoCastSpeakersTitle); - return TtsMainEditorLook.MakeListFrame(tableView); + return EditorChrome.MakeListFrame(tableView); } private static Border BuildButtons(AutoCastSpeakersViewModel vm) @@ -209,8 +209,6 @@ private static Border BuildButtons(AutoCastSpeakersViewModel vm) Children = { EditorChrome.MakeDimLabel(Se.Language.General.Engine, new Thickness(0)), engineCombo }, }; - return TtsMainEditorLook.MakeButtonBar(enginePanel, - UiUtil.MakeButtonCancel(vm.CancelCommand), - TtsMainEditorLook.MakePrimary(UiUtil.MakeButtonOk(vm.OkCommand))); + return EditorChrome.MakeButtonBar(enginePanel, UiUtil.MakeButtonCancel(vm.CancelCommand), EditorChrome.AsPrimary(UiUtil.MakeButtonOk(vm.OkCommand))); } } diff --git a/src/ui/Features/Video/TextToSpeech/Confucius4TtsCrispAsrSettings/Confucius4TtsCrispAsrSettingsWindow.cs b/src/ui/Features/Video/TextToSpeech/Confucius4TtsCrispAsrSettings/Confucius4TtsCrispAsrSettingsWindow.cs index 63d436835a2..471ce60c47b 100644 --- a/src/ui/Features/Video/TextToSpeech/Confucius4TtsCrispAsrSettings/Confucius4TtsCrispAsrSettingsWindow.cs +++ b/src/ui/Features/Video/TextToSpeech/Confucius4TtsCrispAsrSettings/Confucius4TtsCrispAsrSettingsWindow.cs @@ -43,7 +43,7 @@ private static Border BuildModels(Confucius4TtsCrispAsrSettingsViewModel vm) ("BigVGAN vocoder", TtsSettingsChrome.MakeStatus(nameof(vm.VocoderBrush), nameof(vm.VocoderLabel))), ("w2v-BERT encoder", TtsSettingsChrome.MakeStatus(nameof(vm.W2vBrush), nameof(vm.W2vLabel))), (Se.Language.General.InstallFolder, TtsSettingsChrome.MakeFolder(nameof(vm.ModelsFolder), vm.OpenModelsFolderCommand))); - return TtsSettingsChrome.MakeSection(Se.Language.General.Models, rows); + return EditorChrome.MakeEditorSection(Se.Language.General.Models, rows); } private static Border BuildVoice(Confucius4TtsCrispAsrSettingsViewModel vm) @@ -54,7 +54,7 @@ private static Border BuildVoice(Confucius4TtsCrispAsrSettingsViewModel vm) // Flow-matching Euler steps for the S2A stage — the quality/speed knob this backend exposes // (--tts-steps; CrispASR default 20, upstream reference samples at 25). (Se.Language.General.Quality, TtsSettingsChrome.MakeSlider(Se.Language.General.Quality, Confucius4TtsCrispAsr.MinOdeSteps, Confucius4TtsCrispAsr.MaxOdeSteps, 1, nameof(vm.OdeSteps), nameof(vm.OdeStepsLabel)))); - return TtsSettingsChrome.MakeSection(Se.Language.General.Voice, rows); + return EditorChrome.MakeEditorSection(Se.Language.General.Voice, rows); } protected override void OnKeyDown(KeyEventArgs e) diff --git a/src/ui/Features/Video/TextToSpeech/CosyVoice3CrispAsrSettings/CosyVoice3CrispAsrSettingsWindow.cs b/src/ui/Features/Video/TextToSpeech/CosyVoice3CrispAsrSettings/CosyVoice3CrispAsrSettingsWindow.cs index 54b02fede7a..76057f2cbb7 100644 --- a/src/ui/Features/Video/TextToSpeech/CosyVoice3CrispAsrSettings/CosyVoice3CrispAsrSettingsWindow.cs +++ b/src/ui/Features/Video/TextToSpeech/CosyVoice3CrispAsrSettings/CosyVoice3CrispAsrSettingsWindow.cs @@ -46,7 +46,7 @@ private static Border BuildModels(CosyVoice3CrispAsrSettingsViewModel vm) (CosyVoice3CrispAsr.ModelKeyRlQ4K, TtsSettingsChrome.MakeStatus(nameof(vm.RlQ4KBundleBrush), nameof(vm.RlQ4KBundleLabel))), (CosyVoice3CrispAsr.ModelKeyRlF16, TtsSettingsChrome.MakeStatus(nameof(vm.RlF16BundleBrush), nameof(vm.RlF16BundleLabel))), (Se.Language.General.InstallFolder, TtsSettingsChrome.MakeFolder(nameof(vm.ModelsFolder), vm.OpenModelsFolderCommand))); - return TtsSettingsChrome.MakeSection(Se.Language.General.Models, rows); + return EditorChrome.MakeEditorSection(Se.Language.General.Models, rows); } private static Border BuildVoice(CosyVoice3CrispAsrSettingsViewModel vm) @@ -64,7 +64,7 @@ private static Border BuildVoice(CosyVoice3CrispAsrSettingsViewModel vm) Children = { sourceLanguageCombo, - TtsSettingsChrome.MakeHint("Language spoken in imported reference WAVs (for cross-lingual cloning). " + EditorChrome.MakeHint("Language spoken in imported reference WAVs (for cross-lingual cloning). " + "Auto detects it from each voice's transcript."), }, }; @@ -75,7 +75,7 @@ private static Border BuildVoice(CosyVoice3CrispAsrSettingsViewModel vm) TtsSettingsChrome.MakeFolderButton(Se.Language.Video.TextToSpeech.OpenVoicesFolder, vm.OpenVoicesFolderCommand))), (Se.Language.General.Speed, TtsSettingsChrome.MakeSlider(Se.Language.General.Speed, 0.5, 2.0, 0.05, nameof(vm.Speed), nameof(vm.SpeedLabel))), ("Reference language", sourceLanguage)); - return TtsSettingsChrome.MakeSection(Se.Language.General.Voice, rows); + return EditorChrome.MakeEditorSection(Se.Language.General.Voice, rows); } protected override void OnKeyDown(KeyEventArgs e) diff --git a/src/ui/Features/Video/TextToSpeech/DotsTtsCrispAsrSettings/DotsTtsCrispAsrSettingsWindow.cs b/src/ui/Features/Video/TextToSpeech/DotsTtsCrispAsrSettings/DotsTtsCrispAsrSettingsWindow.cs index 2343ee9dade..663d0b23c5c 100644 --- a/src/ui/Features/Video/TextToSpeech/DotsTtsCrispAsrSettings/DotsTtsCrispAsrSettingsWindow.cs +++ b/src/ui/Features/Video/TextToSpeech/DotsTtsCrispAsrSettings/DotsTtsCrispAsrSettingsWindow.cs @@ -44,7 +44,7 @@ private static Border BuildModels(DotsTtsCrispAsrSettingsViewModel vm) ("BigVGAN vocoder", TtsSettingsChrome.MakeStatus(nameof(vm.VocoderBrush), nameof(vm.VocoderLabel))), ("CAM++ speaker encoder", TtsSettingsChrome.MakeStatus(nameof(vm.SpeakerEncoderBrush), nameof(vm.SpeakerEncoderLabel))), (Se.Language.General.InstallFolder, TtsSettingsChrome.MakeFolder(nameof(vm.ModelsFolder), vm.OpenModelsFolderCommand))); - return TtsSettingsChrome.MakeSection(Se.Language.General.Models, rows); + return EditorChrome.MakeEditorSection(Se.Language.General.Models, rows); } private static Border BuildVoice(DotsTtsCrispAsrSettingsViewModel vm) @@ -56,7 +56,7 @@ private static Border BuildVoice(DotsTtsCrispAsrSettingsViewModel vm) // M4 Metal with the F16 core, 4 steps synthesised in 25 s where 16 steps took 38 s for a // comparable line, so this is worth surfacing rather than hard-coding. (Se.Language.General.Quality, TtsSettingsChrome.MakeSlider(Se.Language.General.Quality, DotsTtsCrispAsr.MinOdeSteps, DotsTtsCrispAsr.MaxOdeSteps, 1, nameof(vm.OdeSteps), nameof(vm.OdeStepsLabel)))); - return TtsSettingsChrome.MakeSection(Se.Language.General.Voice, rows); + return EditorChrome.MakeEditorSection(Se.Language.General.Voice, rows); } protected override void OnKeyDown(KeyEventArgs e) diff --git a/src/ui/Features/Video/TextToSpeech/DownloadTts/DownloadTtsWindow.cs b/src/ui/Features/Video/TextToSpeech/DownloadTts/DownloadTtsWindow.cs index 6a833af8e36..407446650d6 100644 --- a/src/ui/Features/Video/TextToSpeech/DownloadTts/DownloadTtsWindow.cs +++ b/src/ui/Features/Video/TextToSpeech/DownloadTts/DownloadTtsWindow.cs @@ -26,7 +26,7 @@ public DownloadTtsWindow(DownloadTtsViewModel vm) DataContext = vm; _vm = vm; - TtsVoicesChrome.Apply(this); + EditorChrome.ApplyWindow(this, EditorWindowStyles.TextFieldsUncentered | EditorWindowStyles.MuteDisabledPrimary); var titleText = new TextBlock { @@ -67,7 +67,7 @@ public DownloadTtsWindow(DownloadTtsViewModel vm) }); var buttonCancel = UiUtil.MakeButtonCancel(vm.CancelCommand); - var buttonBar = TtsVoicesChrome.MakeButtonBar(null, buttonCancel); + var buttonBar = EditorChrome.MakeFlushButtonBar(null, buttonCancel); var body = new Border { diff --git a/src/ui/Features/Video/TextToSpeech/F5TtsCrispAsrSettings/F5TtsCrispAsrSettingsWindow.cs b/src/ui/Features/Video/TextToSpeech/F5TtsCrispAsrSettings/F5TtsCrispAsrSettingsWindow.cs index 63a35b43844..b9a013a637d 100644 --- a/src/ui/Features/Video/TextToSpeech/F5TtsCrispAsrSettings/F5TtsCrispAsrSettingsWindow.cs +++ b/src/ui/Features/Video/TextToSpeech/F5TtsCrispAsrSettings/F5TtsCrispAsrSettingsWindow.cs @@ -40,7 +40,7 @@ private static Border BuildModels(F5TtsCrispAsrSettingsViewModel vm) TtsSettingsChrome.MakeEngineDownloadButton(vm.RedownloadEngineCommand, nameof(vm.EngineDownloadButtonText)))), (string.Format(Se.Language.Video.TalkerX, F5TtsCrispAsr.ModelKeyF16), TtsSettingsChrome.MakeStatus(nameof(vm.TalkerF16Brush), nameof(vm.TalkerF16Label))), (Se.Language.General.InstallFolder, TtsSettingsChrome.MakeFolder(nameof(vm.ModelsFolder), vm.OpenModelsFolderCommand))); - return TtsSettingsChrome.MakeSection(Se.Language.General.Models, rows); + return EditorChrome.MakeEditorSection(Se.Language.General.Models, rows); } private static Border BuildVoice(F5TtsCrispAsrSettingsViewModel vm) @@ -49,7 +49,7 @@ private static Border BuildVoice(F5TtsCrispAsrSettingsViewModel vm) (Se.Language.Video.Voices, TtsSettingsChrome.MakeValue(nameof(vm.VoicesLabel), TtsSettingsChrome.MakeFolderButton(Se.Language.Video.TextToSpeech.OpenVoicesFolder, vm.OpenVoicesFolderCommand))), (Se.Language.General.Speed, TtsSettingsChrome.MakeSlider(Se.Language.General.Speed, 0.5, 2.0, 0.05, nameof(vm.Speed), nameof(vm.SpeedLabel)))); - return TtsSettingsChrome.MakeSection(Se.Language.General.Voice, rows); + return EditorChrome.MakeEditorSection(Se.Language.General.Voice, rows); } protected override void OnKeyDown(KeyEventArgs e) diff --git a/src/ui/Features/Video/TextToSpeech/IndexTts25AudioCppSettings/IndexTts25AudioCppSettingsWindow.cs b/src/ui/Features/Video/TextToSpeech/IndexTts25AudioCppSettings/IndexTts25AudioCppSettingsWindow.cs index 5916accc176..6a24c37864c 100644 --- a/src/ui/Features/Video/TextToSpeech/IndexTts25AudioCppSettings/IndexTts25AudioCppSettingsWindow.cs +++ b/src/ui/Features/Video/TextToSpeech/IndexTts25AudioCppSettings/IndexTts25AudioCppSettingsWindow.cs @@ -46,7 +46,7 @@ private static Border BuildModels(IndexTts25AudioCppSettingsViewModel vm) ("Model " + IndexTts25AudioCpp.ModelKeyF16, TtsSettingsChrome.MakeStatus(nameof(vm.ModelF16Brush), nameof(vm.ModelF16Label), TtsSettingsChrome.MakeModelDownloadButton(vm.DownloadModelCommand, IndexTts25AudioCpp.ModelKeyF16))), (Se.Language.General.InstallFolder, TtsSettingsChrome.MakeFolder(nameof(vm.ModelsFolder), vm.OpenModelsFolderCommand))); - return TtsSettingsChrome.MakeSection(Se.Language.General.Models, rows); + return EditorChrome.MakeEditorSection(Se.Language.General.Models, rows); } private static Border BuildVoice(IndexTts25AudioCppSettingsViewModel vm) @@ -73,7 +73,7 @@ private static Border BuildVoice(IndexTts25AudioCppSettingsViewModel vm) // this to IndexTTS-2.5's own `duration_factor`, which runs the other way (>1 = longer // output = slower speech). Range capped at 0.5-2.0, which is what the model supports. (Se.Language.General.Speed, TtsSettingsChrome.MakeSlider(Se.Language.General.Speed, 0.5, 2.0, 0.05, nameof(vm.Speed), nameof(vm.SpeedLabel)))); - return TtsSettingsChrome.MakeSection(Se.Language.General.Voice, rows); + return EditorChrome.MakeEditorSection(Se.Language.General.Voice, rows); } protected override void OnKeyDown(KeyEventArgs e) diff --git a/src/ui/Features/Video/TextToSpeech/IndexTts25License/IndexTts25LicenseWindow.cs b/src/ui/Features/Video/TextToSpeech/IndexTts25License/IndexTts25LicenseWindow.cs index 95f87b61d4d..36d6ccae58a 100644 --- a/src/ui/Features/Video/TextToSpeech/IndexTts25License/IndexTts25LicenseWindow.cs +++ b/src/ui/Features/Video/TextToSpeech/IndexTts25License/IndexTts25LicenseWindow.cs @@ -24,7 +24,7 @@ public IndexTts25LicenseWindow(IndexTts25LicenseViewModel vm) Width = 640; SizeToContent = SizeToContent.Height; CanResize = false; - TtsVoicesChrome.Apply(this); + EditorChrome.ApplyWindow(this, EditorWindowStyles.TextFieldsUncentered | EditorWindowStyles.MuteDisabledPrimary); vm.Window = this; DataContext = vm; @@ -102,6 +102,6 @@ private static Control BuildActions(IndexTts25LicenseViewModel vm) var cancel = UiUtil.MakeButtonCancel(vm.CancelCommand); - return TtsVoicesChrome.MakeButtonBar(null, cancel, accept); + return EditorChrome.MakeFlushButtonBar(null, cancel, accept); } } diff --git a/src/ui/Features/Video/TextToSpeech/IndexTtsCrispAsrSettings/IndexTtsCrispAsrSettingsWindow.cs b/src/ui/Features/Video/TextToSpeech/IndexTtsCrispAsrSettings/IndexTtsCrispAsrSettingsWindow.cs index 2455a7e8463..e80f7caf225 100644 --- a/src/ui/Features/Video/TextToSpeech/IndexTtsCrispAsrSettings/IndexTtsCrispAsrSettingsWindow.cs +++ b/src/ui/Features/Video/TextToSpeech/IndexTtsCrispAsrSettings/IndexTtsCrispAsrSettingsWindow.cs @@ -43,7 +43,7 @@ private static Border BuildModels(IndexTtsCrispAsrSettingsViewModel vm) ("GPT " + IndexTtsCrispAsr.ModelKeyF16, TtsSettingsChrome.MakeStatus(nameof(vm.TalkerF16Brush), nameof(vm.TalkerF16Label))), ("BigVGAN codec", TtsSettingsChrome.MakeStatus(nameof(vm.CodecBrush), nameof(vm.CodecLabel))), (Se.Language.General.InstallFolder, TtsSettingsChrome.MakeFolder(nameof(vm.ModelsFolder), vm.OpenModelsFolderCommand))); - return TtsSettingsChrome.MakeSection(Se.Language.General.Models, rows); + return EditorChrome.MakeEditorSection(Se.Language.General.Models, rows); } private static Border BuildVoice(IndexTtsCrispAsrSettingsViewModel vm) @@ -54,7 +54,7 @@ private static Border BuildVoice(IndexTtsCrispAsrSettingsViewModel vm) // Server accepts 0.25-4.0; cap UI at 0.5-2.0 since anything outside that range is // already a hack rather than a "preferred pace" choice. (Se.Language.General.Speed, TtsSettingsChrome.MakeSlider(Se.Language.General.Speed, 0.5, 2.0, 0.05, nameof(vm.Speed), nameof(vm.SpeedLabel)))); - return TtsSettingsChrome.MakeSection(Se.Language.General.Voice, rows); + return EditorChrome.MakeEditorSection(Se.Language.General.Voice, rows); } protected override void OnKeyDown(KeyEventArgs e) diff --git a/src/ui/Features/Video/TextToSpeech/ModelLicense/ModelLicenseWindow.cs b/src/ui/Features/Video/TextToSpeech/ModelLicense/ModelLicenseWindow.cs index a9f8ad2196e..62b10c25e7e 100644 --- a/src/ui/Features/Video/TextToSpeech/ModelLicense/ModelLicenseWindow.cs +++ b/src/ui/Features/Video/TextToSpeech/ModelLicense/ModelLicenseWindow.cs @@ -26,7 +26,7 @@ public ModelLicenseWindow(ModelLicenseViewModel vm) Width = 640; SizeToContent = SizeToContent.Height; CanResize = false; - TtsVoicesChrome.Apply(this); + EditorChrome.ApplyWindow(this, EditorWindowStyles.TextFieldsUncentered | EditorWindowStyles.MuteDisabledPrimary); vm.Window = this; DataContext = vm; @@ -104,6 +104,6 @@ private static Control BuildActions(ModelLicenseViewModel vm) var cancel = UiUtil.MakeButtonCancel(vm.CancelCommand); - return TtsVoicesChrome.MakeButtonBar(null, cancel, accept); + return EditorChrome.MakeFlushButtonBar(null, cancel, accept); } } diff --git a/src/ui/Features/Video/TextToSpeech/MossTtsCrispAsrSettings/MossTtsCrispAsrSettingsWindow.cs b/src/ui/Features/Video/TextToSpeech/MossTtsCrispAsrSettings/MossTtsCrispAsrSettingsWindow.cs index 60e885eb29f..52125928195 100644 --- a/src/ui/Features/Video/TextToSpeech/MossTtsCrispAsrSettings/MossTtsCrispAsrSettingsWindow.cs +++ b/src/ui/Features/Video/TextToSpeech/MossTtsCrispAsrSettings/MossTtsCrispAsrSettingsWindow.cs @@ -1,4 +1,4 @@ -using Avalonia.Controls; +using Avalonia.Controls; using Avalonia.Input; using Nikse.SubtitleEdit.Features.Video.TextToSpeech.Engines; using Nikse.SubtitleEdit.Logic; @@ -43,7 +43,7 @@ private static Border BuildModels(MossTtsCrispAsrSettingsViewModel vm) ("Model " + MossTtsCrispAsr.ModelKeyQ8, TtsSettingsChrome.MakeStatus(nameof(vm.ModelQ8Brush), nameof(vm.ModelQ8Label))), ("Model " + MossTtsCrispAsr.ModelKeyF16, TtsSettingsChrome.MakeStatus(nameof(vm.ModelF16Brush), nameof(vm.ModelF16Label))), (Se.Language.General.InstallFolder, TtsSettingsChrome.MakeFolder(nameof(vm.ModelsFolder), vm.OpenModelsFolderCommand))); - return TtsSettingsChrome.MakeSection(Se.Language.General.Models, rows); + return EditorChrome.MakeEditorSection(Se.Language.General.Models, rows); } private static Border BuildVoice(MossTtsCrispAsrSettingsViewModel vm) @@ -52,7 +52,7 @@ private static Border BuildVoice(MossTtsCrispAsrSettingsViewModel vm) (Se.Language.Video.Voices, TtsSettingsChrome.MakeValue(nameof(vm.VoicesLabel), TtsSettingsChrome.MakeFolderButton(Se.Language.Video.TextToSpeech.OpenVoicesFolder, vm.OpenVoicesFolderCommand))), (Se.Language.General.Speed, TtsSettingsChrome.MakeSlider(Se.Language.General.Speed, 0.5, 2.0, 0.05, nameof(vm.Speed), nameof(vm.SpeedLabel)))); - return TtsSettingsChrome.MakeSection(Se.Language.General.Voice, rows); + return EditorChrome.MakeEditorSection(Se.Language.General.Voice, rows); } protected override void OnKeyDown(KeyEventArgs e) diff --git a/src/ui/Features/Video/TextToSpeech/OmniVoiceCrispAsrSettings/OmniVoiceCrispAsrSettingsWindow.cs b/src/ui/Features/Video/TextToSpeech/OmniVoiceCrispAsrSettings/OmniVoiceCrispAsrSettingsWindow.cs index edc1efb2eac..ba67af7e5be 100644 --- a/src/ui/Features/Video/TextToSpeech/OmniVoiceCrispAsrSettings/OmniVoiceCrispAsrSettingsWindow.cs +++ b/src/ui/Features/Video/TextToSpeech/OmniVoiceCrispAsrSettings/OmniVoiceCrispAsrSettingsWindow.cs @@ -42,7 +42,7 @@ private static Border BuildModels(OmniVoiceCrispAsrSettingsViewModel vm) ("Model " + OmniVoiceCrispAsr.ModelKeyQ8_0, TtsSettingsChrome.MakeStatus(nameof(vm.ModelQ8_0Brush), nameof(vm.ModelQ8_0Label))), ("Model " + OmniVoiceCrispAsr.ModelKeyF16, TtsSettingsChrome.MakeStatus(nameof(vm.ModelF16Brush), nameof(vm.ModelF16Label))), (Se.Language.General.InstallFolder, TtsSettingsChrome.MakeFolder(nameof(vm.ModelsFolder), vm.OpenModelsFolderCommand))); - return TtsSettingsChrome.MakeSection(Se.Language.General.Models, rows); + return EditorChrome.MakeEditorSection(Se.Language.General.Models, rows); } private static Border BuildVoice(OmniVoiceCrispAsrSettingsViewModel vm) @@ -51,7 +51,7 @@ private static Border BuildVoice(OmniVoiceCrispAsrSettingsViewModel vm) (Se.Language.Video.Voices, TtsSettingsChrome.MakeValue(nameof(vm.VoicesLabel), TtsSettingsChrome.MakeFolderButton(Se.Language.Video.TextToSpeech.OpenVoicesFolder, vm.OpenVoicesFolderCommand))), (Se.Language.General.Speed, TtsSettingsChrome.MakeSlider(Se.Language.General.Speed, 0.5, 2.0, 0.05, nameof(vm.Speed), nameof(vm.SpeedLabel)))); - return TtsSettingsChrome.MakeSection(Se.Language.General.Voice, rows); + return EditorChrome.MakeEditorSection(Se.Language.General.Voice, rows); } protected override void OnKeyDown(KeyEventArgs e) diff --git a/src/ui/Features/Video/TextToSpeech/PocketTtsCrispAsrSettings/PocketTtsCrispAsrSettingsWindow.cs b/src/ui/Features/Video/TextToSpeech/PocketTtsCrispAsrSettings/PocketTtsCrispAsrSettingsWindow.cs index efe2100cf78..55e5b6b2730 100644 --- a/src/ui/Features/Video/TextToSpeech/PocketTtsCrispAsrSettings/PocketTtsCrispAsrSettingsWindow.cs +++ b/src/ui/Features/Video/TextToSpeech/PocketTtsCrispAsrSettings/PocketTtsCrispAsrSettingsWindow.cs @@ -49,7 +49,7 @@ private static Border BuildModels(PocketTtsCrispAsrSettingsViewModel vm) (PocketTtsCrispAsr.ModelKeyPortugueseQ8_0, TtsSettingsChrome.MakeStatus(nameof(vm.PortugueseBrush), nameof(vm.PortugueseLabel))), (PocketTtsCrispAsr.ModelKeyFrenchQ8_0, TtsSettingsChrome.MakeStatus(nameof(vm.FrenchBrush), nameof(vm.FrenchLabel))), (Se.Language.General.InstallFolder, TtsSettingsChrome.MakeFolder(nameof(vm.ModelsFolder), vm.OpenModelsFolderCommand))); - return TtsSettingsChrome.MakeSection(Se.Language.General.Models, rows); + return EditorChrome.MakeEditorSection(Se.Language.General.Models, rows); } private static Border BuildVoice(PocketTtsCrispAsrSettingsViewModel vm) @@ -60,7 +60,7 @@ private static Border BuildVoice(PocketTtsCrispAsrSettingsViewModel vm) // Server accepts 0.25-4.0; cap UI at 0.5-2.0 since anything outside that range is // already a hack rather than a "preferred pace" choice. (Se.Language.General.Speed, TtsSettingsChrome.MakeSlider(Se.Language.General.Speed, 0.5, 2.0, 0.05, nameof(vm.Speed), nameof(vm.SpeedLabel)))); - return TtsSettingsChrome.MakeSection(Se.Language.General.Voice, rows); + return EditorChrome.MakeEditorSection(Se.Language.General.Voice, rows); } protected override void OnKeyDown(KeyEventArgs e) diff --git a/src/ui/Features/Video/TextToSpeech/Qwen3TtsCrispAsrSettings/Qwen3TtsCrispAsrSettingsWindow.cs b/src/ui/Features/Video/TextToSpeech/Qwen3TtsCrispAsrSettings/Qwen3TtsCrispAsrSettingsWindow.cs index 511d7e841cf..94f9a655177 100644 --- a/src/ui/Features/Video/TextToSpeech/Qwen3TtsCrispAsrSettings/Qwen3TtsCrispAsrSettingsWindow.cs +++ b/src/ui/Features/Video/TextToSpeech/Qwen3TtsCrispAsrSettings/Qwen3TtsCrispAsrSettingsWindow.cs @@ -43,7 +43,7 @@ private static Border BuildModels(Qwen3TtsCrispAsrSettingsViewModel vm) ("1.7B Voice clone", TtsSettingsChrome.MakeStatus(nameof(vm.CloneTalkerBrush), nameof(vm.CloneTalkerLabel))), ("Codec (12 Hz)", TtsSettingsChrome.MakeStatus(nameof(vm.CodecBrush), nameof(vm.CodecLabel))), (Se.Language.General.InstallFolder, TtsSettingsChrome.MakeFolder(nameof(vm.ModelsFolder), vm.OpenModelsFolderCommand))); - return TtsSettingsChrome.MakeSection(Se.Language.General.Models, rows); + return EditorChrome.MakeEditorSection(Se.Language.General.Models, rows); } private static Border BuildVoice(Qwen3TtsCrispAsrSettingsViewModel vm) @@ -51,7 +51,7 @@ private static Border BuildVoice(Qwen3TtsCrispAsrSettingsViewModel vm) var rows = TtsSettingsChrome.MakeRows(LabelWidth, (Se.Language.Video.Voices, TtsSettingsChrome.MakeValue(nameof(vm.VoicesLabel), TtsSettingsChrome.MakeFolderButton(Se.Language.Video.TextToSpeech.OpenVoicesFolder, vm.OpenVoicesFolderCommand)))); - return TtsSettingsChrome.MakeSection(Se.Language.General.Voice, rows); + return EditorChrome.MakeEditorSection(Se.Language.General.Voice, rows); } protected override void OnKeyDown(KeyEventArgs e) diff --git a/src/ui/Features/Video/TextToSpeech/ReviewSpeech/GeneratingAudioWindow.cs b/src/ui/Features/Video/TextToSpeech/ReviewSpeech/GeneratingAudioWindow.cs index d070c467d91..536c06d7589 100644 --- a/src/ui/Features/Video/TextToSpeech/ReviewSpeech/GeneratingAudioWindow.cs +++ b/src/ui/Features/Video/TextToSpeech/ReviewSpeech/GeneratingAudioWindow.cs @@ -26,7 +26,7 @@ public GeneratingAudioWindow(GeneratingAudioViewModel vm) SizeToContent = SizeToContent.Height; CanResize = false; WindowStartupLocation = WindowStartupLocation.CenterOwner; - TtsMainEditorLook.Apply(this); + EditorChrome.ApplyWindow(this, EditorWindowStyles.TextFieldsBare | EditorWindowStyles.MuteDisabledPrimary); DataContext = vm; @@ -67,7 +67,7 @@ public GeneratingAudioWindow(GeneratingAudioViewModel vm) Padding = new Thickness(24, 22), Child = panel, }, - TtsMainEditorLook.MakeButtonBar(null, buttonCancel), + EditorChrome.MakeButtonBar(null, buttonCancel), } }; diff --git a/src/ui/Features/Video/TextToSpeech/ReviewSpeech/ReviewSpeechWindow.cs b/src/ui/Features/Video/TextToSpeech/ReviewSpeech/ReviewSpeechWindow.cs index e3b3ed2a312..f8d8ee179d9 100644 --- a/src/ui/Features/Video/TextToSpeech/ReviewSpeech/ReviewSpeechWindow.cs +++ b/src/ui/Features/Video/TextToSpeech/ReviewSpeech/ReviewSpeechWindow.cs @@ -38,7 +38,7 @@ public ReviewSpeechWindow(ReviewSpeechViewModel vm) MinWidth = 760; MinHeight = 520; CanResize = true; - TtsMainEditorLook.Apply(this); + EditorChrome.ApplyWindow(this, EditorWindowStyles.TextFieldsBare | EditorWindowStyles.MuteDisabledPrimary); _vm = vm; vm.Window = this; @@ -126,9 +126,9 @@ private static Border MakeButtonBar(ReviewSpeechViewModel vm) // Disabled while a regenerate runs: its progress popup is non-modal, and publishing // (OK/Export) or closing mid-run would commit the row's half-updated step result. var buttonExport = UiUtil.MakeButton(Se.Language.General.ExportDotDotDot, vm.ExportCommand).WithBindEnabled(nameof(vm.IsRegenerateEnabled)); - var buttonOk = TtsMainEditorLook.MakePrimary(UiUtil.MakeButtonOk(vm.OkCommand).WithBindEnabled(nameof(vm.IsRegenerateEnabled))); + var buttonOk = EditorChrome.AsPrimary(UiUtil.MakeButtonOk(vm.OkCommand).WithBindEnabled(nameof(vm.IsRegenerateEnabled))); var buttonCancel = UiUtil.MakeButtonCancel(vm.CancelCommand).WithBindEnabled(nameof(vm.IsRegenerateEnabled)); - return TtsMainEditorLook.MakeButtonBar(left, buttonExport, buttonCancel, buttonOk); + return EditorChrome.MakeButtonBar(left, buttonExport, buttonCancel, buttonOk); } private static Grid MakeLineGrid(ReviewSpeechViewModel vm) @@ -282,12 +282,12 @@ private static Grid MakeLineGrid(ReviewSpeechViewModel vm) }; grid.Add(header, 0, 0); - grid.Add(TtsMainEditorLook.MakeListFrame(lineGrid), 1, 0); + grid.Add(EditorChrome.MakeListFrame(lineGrid), 1, 0); grid.Add(textBox, 2, 0); return grid; } - private static Grid Row(string? label, Control content, string? labelBinding = null) => TtsMainEditorLook.Row(label, content, LabelWidth, labelBinding); + private static Grid Row(string? label, Control content, string? labelBinding = null) => EditorChrome.MakeLabeledRow(label, content, LabelWidth, spacing: 6, labelBinding: labelBinding); /// /// A combo box filling the row, with optional tool buttons after it. @@ -483,7 +483,7 @@ private static Grid SliderRow(string label, Slider slider, Control value, Contro grid.Add(slider, 0, 0); grid.Add(value, 0, 1); grid.Add(help, 0, 2); - return TtsMainEditorLook.Row(label, grid, SliderLabelWidth); + return EditorChrome.MakeLabeledRow(label, grid, SliderLabelWidth, spacing: 6); } private static TextBlock SliderValue(ReviewSpeechViewModel vm, string property) diff --git a/src/ui/Features/Video/TextToSpeech/ReviewSpeechHistory/ReviewSpeechHistoryWindow.cs b/src/ui/Features/Video/TextToSpeech/ReviewSpeechHistory/ReviewSpeechHistoryWindow.cs index 0db7146c113..e5203f826f2 100644 --- a/src/ui/Features/Video/TextToSpeech/ReviewSpeechHistory/ReviewSpeechHistoryWindow.cs +++ b/src/ui/Features/Video/TextToSpeech/ReviewSpeechHistory/ReviewSpeechHistoryWindow.cs @@ -28,11 +28,11 @@ public ReviewSpeechHistoryWindow(ReviewSpeechHistoryViewModel vm) MinWidth = 600; MinHeight = 400; CanResize = true; - TtsMainEditorLook.Apply(this); + EditorChrome.ApplyWindow(this, EditorWindowStyles.TextFieldsBare | EditorWindowStyles.MuteDisabledPrimary); vm.Window = this; DataContext = vm; - var buttonOk = TtsMainEditorLook.MakePrimary(UiUtil.MakeButtonOk(vm.OkCommand)); + var buttonOk = EditorChrome.AsPrimary(UiUtil.MakeButtonOk(vm.OkCommand)); var buttonCancel = UiUtil.MakeButtonCancel(vm.CancelCommand); var header = EditorChrome.MakeSectionHeader(Se.Language.General.History); @@ -60,7 +60,7 @@ public ReviewSpeechHistoryWindow(ReviewSpeechHistoryViewModel vm) }; grid.Add(new Border { Background = EditorChrome.PanelBrush, Child = list }, 0); - grid.Add(TtsMainEditorLook.MakeButtonBar(null, buttonCancel, buttonOk), 1); + grid.Add(EditorChrome.MakeButtonBar(null, buttonCancel, buttonOk), 1); Content = grid; @@ -125,6 +125,6 @@ private Border MakeHistoryGrid(ReviewSpeechHistoryViewModel vm) Width = new GridLength(90), }); - return TtsMainEditorLook.MakeListFrame(tableView); + return EditorChrome.MakeListFrame(tableView); } } diff --git a/src/ui/Features/Video/TextToSpeech/SelectLinesWindowBuilder.cs b/src/ui/Features/Video/TextToSpeech/SelectLinesWindowBuilder.cs index 80c9da18b36..9cad1809132 100644 --- a/src/ui/Features/Video/TextToSpeech/SelectLinesWindowBuilder.cs +++ b/src/ui/Features/Video/TextToSpeech/SelectLinesWindowBuilder.cs @@ -28,11 +28,11 @@ public static void Initialize(Window window, SelectLinesViewModelBase(Window window, SelectLinesViewModelBase(Window window, SelectLinesViewModelBase private static Border MakeToolbar(TextToSpeechViewModel vm) { - var buttonImport = TtsMainEditorLook.MakeAction("fa-solid fa-file-import", Se.Language.General.ImportDotDotDot, vm.ImportCommand) + var buttonImport = EditorChrome.MakeActionButton("fa-solid fa-file-import", Se.Language.General.ImportDotDotDot, vm.ImportCommand) .WithBindIsEnabled("!" + nameof(vm.IsGenerating)); - var buttonCast = TtsMainEditorLook.MakeAction("fa-solid fa-users", null, vm.ShowCastCommand, nameof(vm.CastButtonText)) + var buttonCast = EditorChrome.MakeActionButton("fa-solid fa-users", null, vm.ShowCastCommand, nameof(vm.CastButtonText)) .WithBindIsVisible(nameof(vm.HasCast)) .WithBindIsEnabled("!" + nameof(vm.IsGenerating)); if (Se.Settings.Appearance.ShowHints) @@ -185,11 +185,11 @@ private static Border MakeButtonBar(TextToSpeechViewModel vm) var buttonGenerate = UiUtil.MakeButton(Se.Language.Video.TextToSpeech.GenerateSpeechFromText, vm.GenerateTtsCommand) .WithIconLeft(IconNames.Waveform) .WithBindIsEnabled("!" + nameof(vm.IsGenerating)); - TtsMainEditorLook.MakePrimary(buttonGenerate); + EditorChrome.AsPrimary(buttonGenerate); // The context is as wide as the buttons leave room for (trimmed), it never widens the // window - see WidthIgnoringPanel. - return TtsMainEditorLook.MakeButtonBar(new WidthIgnoringPanel { Children = { context } }, buttonCancel, buttonOk, buttonGenerate); + return EditorChrome.MakeButtonBar(new WidthIgnoringPanel { Children = { context } }, buttonCancel, buttonOk, buttonGenerate); } // Install-status dot for the engine combo: green = ready, amber = a newer build is available, @@ -287,7 +287,7 @@ public void RefreshDownloadDots() } } - private static Grid Row(string? label, Control content, string? labelBinding = null) => TtsMainEditorLook.Row(label, content, LabelWidth, labelBinding); + private static Grid Row(string? label, Control content, string? labelBinding = null) => EditorChrome.MakeLabeledRow(label, content, LabelWidth, spacing: 6, labelBinding: labelBinding); private static ComboBox Combo(ObservableCollection items, TextToSpeechViewModel vm, string property, string name, double width = ComboWidth) { @@ -319,7 +319,7 @@ private Border MakeEngineControls(TextToSpeechViewModel vm) var panelEngineDescription = Row(null, labelEngineDescription); panelEngineDescription.Bind(IsVisibleProperty, new Binding(nameof(vm.HasEngineDescription)) { Mode = BindingMode.OneWay }); - var buttonTestVoice = TtsMainEditorLook.MakeAction("fa-solid fa-play", Se.Language.Video.TextToSpeech.TestVoice, vm.TestVoiceCommand) + var buttonTestVoice = EditorChrome.MakeActionButton("fa-solid fa-play", Se.Language.Video.TextToSpeech.TestVoice, vm.TestVoiceCommand) .WithBindIsEnabled(nameof(vm.IsVoiceTestEnabled)); buttonTestVoice.Height = 28; @@ -553,7 +553,7 @@ private static Border MakeSettingsControls(TextToSpeechViewModel vm) var panelAddBackgroundMusicWithHint = TtsMainEditorLook.SwitchRow(nameof(vm.DoAddBackgroundMusic), backgroundMusic.AddBackgroundMusic, backgroundMusic.AddBackgroundMusicHint, buttonBackgroundMusicSettings); // Left-aligned under the switches above it. - var buttonAdvanced = TtsMainEditorLook.MakeAction("fa-solid fa-sliders", Se.Language.General.AdvancedDotDotDot, vm.ShowAdvancedSettingsCommand); + var buttonAdvanced = EditorChrome.MakeActionButton("fa-solid fa-sliders", Se.Language.General.AdvancedDotDotDot, vm.ShowAdvancedSettingsCommand); buttonAdvanced.HorizontalAlignment = HorizontalAlignment.Left; buttonAdvanced.Margin = new Thickness(-10, 0, 0, 0); diff --git a/src/ui/Features/Video/TextToSpeech/TtsMainEditorLook.cs b/src/ui/Features/Video/TextToSpeech/TtsMainEditorLook.cs index 51812c104b9..a22bc5a660c 100644 --- a/src/ui/Features/Video/TextToSpeech/TtsMainEditorLook.cs +++ b/src/ui/Features/Video/TextToSpeech/TtsMainEditorLook.cs @@ -1,87 +1,19 @@ using Avalonia; -using Avalonia.Automation; using Avalonia.Controls; -using Avalonia.Controls.Presenters; -using Avalonia.Controls.Primitives; using Avalonia.Data; using Avalonia.Layout; -using Avalonia.Media; -using Avalonia.Styling; using Nikse.SubtitleEdit.Logic; using Nikse.SubtitleEdit.Logic.Config; namespace Nikse.SubtitleEdit.Features.Video.TextToSpeech; /// -/// The pieces the text-to-speech windows (generate, review, history, speak from line, the -/// select-lines dialogs, auto cast) share for the editor look (see ): -/// compact combo/text boxes, label rows, switches, list frames and the bottom button bar. +/// The pieces only the text-to-speech windows (generate, review, history, speak from line, the +/// select-lines dialogs, auto cast) use; the rest of their editor look (window styles, rows, +/// switches, list frames, button bar, ...) comes from . /// internal static class TtsMainEditorLook { - /// - /// The editor styles plus compact combo boxes and text boxes, and a disabled primary button - /// that drops the accent (a disabled Generate looked clickable). - /// - internal static void Apply(Window window) - { - window.Background = EditorChrome.WindowBrush; - EditorChrome.AddEditorStyles(window.Styles); - - window.Styles.Add(new Style(x => x.OfType()) - { - Setters = - { - new Setter(TemplatedControl.FontSizeProperty, 12.5), - new Setter(TemplatedControl.MinHeightProperty, 28.0), - new Setter(TemplatedControl.PaddingProperty, new Thickness(8, 2, 4, 2)), - new Setter(TemplatedControl.CornerRadiusProperty, new CornerRadius(6)), - }, - }); - window.Styles.Add(new Style(x => x.OfType().Class("text")) - { - Setters = - { - new Setter(TemplatedControl.FontSizeProperty, 12.5), - new Setter(TemplatedControl.MinHeightProperty, 28.0), - new Setter(TemplatedControl.CornerRadiusProperty, new CornerRadius(6)), - new Setter(TemplatedControl.BackgroundProperty, EditorChrome.FieldBrush), - new Setter(TemplatedControl.BorderBrushProperty, EditorChrome.FieldBorderBrush), - }, - }); - window.Styles.Add(new Style(x => x.OfType internal static TextBlock Hint(string? text = null, string? binding = null) { - var hint = new TextBlock - { - Text = text, - FontSize = 11.5, - Foreground = EditorChrome.DimTextBrush, - TextWrapping = TextWrapping.Wrap, - VerticalAlignment = VerticalAlignment.Center, - }; + var hint = EditorChrome.MakeHint(text); + hint.VerticalAlignment = VerticalAlignment.Center; if (binding != null) { hint.Bind(TextBlock.TextProperty, new Binding(binding) { Mode = BindingMode.OneWay }); @@ -114,20 +40,6 @@ internal static TextBlock Hint(string? text = null, string? binding = null) return hint; } - internal static ToggleSwitch Switch(string property, string name) - { - var toggle = new ToggleSwitch - { - OnContent = null, - OffContent = null, - MinWidth = 0, - VerticalAlignment = VerticalAlignment.Center, - [!ToggleSwitch.IsCheckedProperty] = new Binding(property) { Mode = BindingMode.TwoWay }, - }; - AutomationProperties.SetName(toggle, name); - return toggle; - } - /// /// A switch with its label beside it (the label wraps instead of being cut off), optional /// extra controls after the label (a settings button) and an optional hint under the label. @@ -146,8 +58,8 @@ internal static Grid SwitchRow(string property, string text, string? hint = null }, RowDefinitions = { new RowDefinition(GridLength.Auto), new RowDefinition(GridLength.Auto) }, }; - row.Add(Switch(property, text), 0, 0); - row.Add(new TextBlock { Text = text, FontSize = 12.5, VerticalAlignment = VerticalAlignment.Center, TextWrapping = TextWrapping.Wrap }, 0, 1); + row.Add(EditorChrome.MakeSwitch(property, text), 0, 0); + row.Add(EditorChrome.MakeSwitchLabel(text), 0, 1); if (after != null) { after.VerticalAlignment = VerticalAlignment.Center; @@ -162,42 +74,6 @@ internal static Grid SwitchRow(string property, string text, string? hint = null return row; } - /// - /// Flat icon button with its label beside it, for tool bars. - /// - internal static Button MakeAction(string icon, string? text, System.Windows.Input.ICommand? command, string? textBinding = null) - { - var label = new TextBlock { Text = text, FontSize = 12.5, VerticalAlignment = VerticalAlignment.Center }; - if (textBinding != null) - { - label.Bind(TextBlock.TextProperty, new Binding(textBinding) { Mode = BindingMode.OneWay }); - } - - var content = new StackPanel { Orientation = Orientation.Horizontal, Spacing = 7, Margin = new Thickness(10, 0) }; - content.Children.Add(new Optris.Icons.Avalonia.Icon { Value = icon, FontSize = 13, VerticalAlignment = VerticalAlignment.Center }); - content.Children.Add(label); - var button = new Button - { - Content = content, - Height = 30, - Command = command, - VerticalContentAlignment = VerticalAlignment.Center, - }; - button.Classes.Add("tool"); - - // Icon + label in a panel: a screen reader needs the name spelled out (#12087) - if (textBinding != null) - { - button.Bind(AutomationProperties.NameProperty, new Binding(textBinding) { Mode = BindingMode.OneWay }); - } - else - { - AutomationProperties.SetName(button, text); - } - - return button; - } - /// /// Small grey pill, e.g. a voice count or the video file name. /// @@ -213,59 +89,6 @@ internal static Border MakeChip(Control content) }; } - /// - /// The rounded field frame a list (TableView) sits in. - /// - internal static Border MakeListFrame(Control list) - { - return new Border - { - Child = list, - Background = EditorChrome.FieldBrush, - BorderBrush = EditorChrome.FieldBorderBrush, - BorderThickness = new Thickness(1), - CornerRadius = new CornerRadius(8), - ClipToBounds = true, - }; - } - - /// - /// The bottom bar: (status, summary, options) on the left, the - /// buttons on the right - Cancel before the accent primary. - /// - internal static Border MakeButtonBar(Control? left, params Control[] buttons) - { - var right = Horizontal(buttons); - right.Spacing = 10; - - var grid = new Grid - { - ColumnSpacing = 12, - ColumnDefinitions = { new ColumnDefinition(new GridLength(1, GridUnitType.Star)), new ColumnDefinition(GridLength.Auto) }, - }; - if (left != null) - { - left.VerticalAlignment = VerticalAlignment.Center; - grid.Add(left, 0, 0); - } - - grid.Add(right, 0, 1); - return new Border - { - Child = grid, - Background = EditorChrome.ButtonBarBrush, - BorderBrush = EditorChrome.LineBrush, - BorderThickness = new Thickness(0, 1, 0, 0), - Padding = new Thickness(16, 10), - }; - } - - internal static Button MakePrimary(Button button) - { - button.Classes.Add("primary"); - return button; - } - internal static Button MakeHelpButton(System.Windows.Input.ICommand command, string name) { return EditorChrome.CreateToolButton("fa-solid fa-circle-question", name, command, 26); diff --git a/src/ui/Features/Video/TextToSpeech/TtsSettingsChrome.cs b/src/ui/Features/Video/TextToSpeech/TtsSettingsChrome.cs index 24c124fa17a..91e80b73f3f 100644 --- a/src/ui/Features/Video/TextToSpeech/TtsSettingsChrome.cs +++ b/src/ui/Features/Video/TextToSpeech/TtsSettingsChrome.cs @@ -6,7 +6,6 @@ using Avalonia.Data; using Avalonia.Layout; using Avalonia.Media; -using Avalonia.Styling; using Nikse.SubtitleEdit.Logic; using Nikse.SubtitleEdit.Logic.Config; using System.Windows.Input; @@ -14,9 +13,9 @@ namespace Nikse.SubtitleEdit.Features.Video.TextToSpeech; /// -/// The shared editor look (see ) of the local TTS engine settings -/// dialogs (CrispASR and audio.cpp engines): an engine header, a "Models" section with the -/// runtime/model status rows, a "Voice" section with the per-request knobs, and a button bar. +/// The layout of the local TTS engine settings dialogs (CrispASR and audio.cpp engines) on top of +/// : an engine header, a "Models" section with the runtime/model status +/// rows, a "Voice" section with the per-request knobs, and a button bar with Close. /// internal static class TtsSettingsChrome { @@ -37,20 +36,7 @@ internal static void Apply(Window window, double width = WindowWidth) window.Width = width; window.SizeToContent = SizeToContent.Height; window.CanResize = false; - window.Background = EditorChrome.WindowBrush; - EditorChrome.AddEditorStyles(window.Styles); - - // Compact combo boxes - window.Styles.Add(new Style(x => x.OfType()) - { - Setters = - { - new Setter(TemplatedControl.FontSizeProperty, 12.5), - new Setter(TemplatedControl.MinHeightProperty, 28.0), - new Setter(TemplatedControl.PaddingProperty, new Thickness(8, 2, 4, 2)), - new Setter(TemplatedControl.CornerRadiusProperty, new CornerRadius(6)), - }, - }); + EditorChrome.ApplyWindow(window); } /// @@ -365,19 +351,6 @@ internal static Grid MakeFolder(string bindingPath, ICommand openCommand) return grid; } - /// - /// A dim, wrapping hint below a field. - /// - internal static TextBlock MakeHint(string text) => new() - { - Text = text, - FontSize = 11.5, - Foreground = EditorChrome.DimTextBrush, - TextWrapping = TextWrapping.Wrap, - }; - - internal static Border MakeSection(string title, Control content) => EditorChrome.MakeEditorSection(title, content); - /// /// The bottom button bar with the accent close button on the right. /// @@ -385,13 +358,6 @@ internal static Border MakeButtonBar(Button close) { close.Classes.Add("primary"); close.HorizontalAlignment = HorizontalAlignment.Right; - return new Border - { - Child = close, - Background = EditorChrome.ButtonBarBrush, - BorderBrush = EditorChrome.LineBrush, - BorderThickness = new Thickness(0, 1, 0, 0), - Padding = new Thickness(16, 10), - }; + return EditorChrome.WrapButtonBar(close); } } diff --git a/src/ui/Features/Video/TextToSpeech/VibeVoiceCrispAsrSettings/VibeVoiceCrispAsrSettingsWindow.cs b/src/ui/Features/Video/TextToSpeech/VibeVoiceCrispAsrSettings/VibeVoiceCrispAsrSettingsWindow.cs index f0377565c94..9e54c4a6be0 100644 --- a/src/ui/Features/Video/TextToSpeech/VibeVoiceCrispAsrSettings/VibeVoiceCrispAsrSettingsWindow.cs +++ b/src/ui/Features/Video/TextToSpeech/VibeVoiceCrispAsrSettings/VibeVoiceCrispAsrSettingsWindow.cs @@ -42,7 +42,7 @@ private static Border BuildModels(VibeVoiceCrispAsrSettingsViewModel vm) ("1.5B " + VibeVoiceCrispAsr.ModelKeyQ8_0, TtsSettingsChrome.MakeStatus(nameof(vm.TalkerQ8_0Brush), nameof(vm.TalkerQ8_0Label))), ("1.5B " + VibeVoiceCrispAsr.ModelKeyF16, TtsSettingsChrome.MakeStatus(nameof(vm.TalkerF16Brush), nameof(vm.TalkerF16Label))), (Se.Language.General.InstallFolder, TtsSettingsChrome.MakeFolder(nameof(vm.ModelsFolder), vm.OpenModelsFolderCommand))); - return TtsSettingsChrome.MakeSection(Se.Language.General.Models, rows); + return EditorChrome.MakeEditorSection(Se.Language.General.Models, rows); } private static Border BuildVoice(VibeVoiceCrispAsrSettingsViewModel vm) @@ -53,7 +53,7 @@ private static Border BuildVoice(VibeVoiceCrispAsrSettingsViewModel vm) // Server accepts 0.25-4.0; cap UI at 0.5-2.0 since anything outside that range is // already a hack rather than a "preferred pace" choice. (Se.Language.General.Speed, TtsSettingsChrome.MakeSlider(Se.Language.General.Speed, 0.5, 2.0, 0.05, nameof(vm.Speed), nameof(vm.SpeedLabel)))); - return TtsSettingsChrome.MakeSection(Se.Language.General.Voice, rows); + return EditorChrome.MakeEditorSection(Se.Language.General.Voice, rows); } protected override void OnKeyDown(KeyEventArgs e) diff --git a/src/ui/Features/Video/TextToSpeech/VoiceCloneConsent/VoiceCloneConsentWindow.cs b/src/ui/Features/Video/TextToSpeech/VoiceCloneConsent/VoiceCloneConsentWindow.cs index d454bdb264a..b4b1dc6bd5d 100644 --- a/src/ui/Features/Video/TextToSpeech/VoiceCloneConsent/VoiceCloneConsentWindow.cs +++ b/src/ui/Features/Video/TextToSpeech/VoiceCloneConsent/VoiceCloneConsentWindow.cs @@ -24,7 +24,7 @@ public VoiceCloneConsentWindow(VoiceCloneConsentViewModel vm) Width = 640; SizeToContent = SizeToContent.Height; CanResize = false; - TtsVoicesChrome.Apply(this); + EditorChrome.ApplyWindow(this, EditorWindowStyles.TextFieldsUncentered | EditorWindowStyles.MuteDisabledPrimary); vm.Window = this; DataContext = vm; @@ -111,6 +111,6 @@ private static Control BuildActions(VoiceCloneConsentViewModel vm) var cancel = UiUtil.MakeButtonCancel(vm.CancelCommand); - return TtsVoicesChrome.MakeButtonBar(null, cancel, accept); + return EditorChrome.MakeFlushButtonBar(null, cancel, accept); } } diff --git a/src/ui/Features/Video/TextToSpeech/VoiceManager/TtsVoicesChrome.cs b/src/ui/Features/Video/TextToSpeech/VoiceManager/TtsVoicesChrome.cs index 4628464dba1..91965df6dde 100644 --- a/src/ui/Features/Video/TextToSpeech/VoiceManager/TtsVoicesChrome.cs +++ b/src/ui/Features/Video/TextToSpeech/VoiceManager/TtsVoicesChrome.cs @@ -1,169 +1,19 @@ using System.Collections.Generic; using Avalonia; -using Avalonia.Automation; using Avalonia.Controls; -using Avalonia.Controls.Presenters; -using Avalonia.Controls.Primitives; using Avalonia.Layout; using Avalonia.Media; -using Avalonia.Styling; using Nikse.SubtitleEdit.Logic; namespace Nikse.SubtitleEdit.Features.Video.TextToSpeech.VoiceManager; /// -/// The editor look (see ) for the TTS voice windows: voice manager, -/// voice packs, cast, the licence and consent gates and the engine download. Compact inputs, -/// a bottom button bar with the accent button on the right and a progress strip above it. +/// The licence and consent layout of the TTS voice windows (voice manager, voice packs, cast, the +/// licence and consent gates, the engine download); the rest of their editor look comes from +/// . /// internal static class TtsVoicesChrome { - /// - /// Window background plus the shared editor styles and the compact inputs used here. - /// - internal static void Apply(Window window) - { - window.Background = EditorChrome.WindowBrush; - EditorChrome.AddEditorStyles(window.Styles); - var styles = window.Styles; - - styles.Add(new Style(x => x.OfType()) - { - Setters = - { - new Setter(TemplatedControl.FontSizeProperty, 12.5), - new Setter(TemplatedControl.MinHeightProperty, 28.0), - new Setter(TemplatedControl.PaddingProperty, new Thickness(8, 2, 4, 2)), - new Setter(TemplatedControl.CornerRadiusProperty, new CornerRadius(6)), - }, - }); - styles.Add(new Style(x => x.OfType().Class("text")) - { - Setters = - { - new Setter(TemplatedControl.FontSizeProperty, 12.5), - new Setter(TemplatedControl.MinHeightProperty, 28.0), - new Setter(TemplatedControl.PaddingProperty, new Thickness(8, 4)), - new Setter(TemplatedControl.CornerRadiusProperty, new CornerRadius(6)), - new Setter(TemplatedControl.BackgroundProperty, EditorChrome.FieldBrush), - new Setter(TemplatedControl.BorderBrushProperty, EditorChrome.FieldBorderBrush), - }, - }); - - // A disabled accent button (Accept before the box is ticked, Download while busy) must not - // keep the accent - it looked clickable. - styles.Add(new Style(x => x.OfType -internal static class EditorChrome +internal static partial class EditorChrome { // The editor palette, in a dark and a light version. Each color is one shared brush whose color // follows the app theme (also when it changes while windows are open), so everything built diff --git a/src/ui/Logic/EditorChromeParts.cs b/src/ui/Logic/EditorChromeParts.cs new file mode 100644 index 00000000000..7ebcc2b06ae --- /dev/null +++ b/src/ui/Logic/EditorChromeParts.cs @@ -0,0 +1,1789 @@ +using System; +using Avalonia; +using Avalonia.Automation; +using Avalonia.Controls; +using Avalonia.Controls.Presenters; +using Avalonia.Controls.Primitives; +using Avalonia.Controls.Shapes; +using Avalonia.Data; +using Avalonia.Data.Converters; +using Avalonia.Input; +using Avalonia.Layout; +using Avalonia.Media; +using Avalonia.Styling; +using Nikse.SubtitleEdit.Controls; + +namespace Nikse.SubtitleEdit.Logic; + +/// +/// Which extra styles adds on top of the editor styles and +/// the compact combo boxes every editor window gets. +/// +[Flags] +internal enum EditorWindowStyles +{ + None = 0, + + /// Text boxes with class "text" on the field colors, padded, text centered. + TextFields = 1 << 0, + + /// Like , the text not centered vertically. + TextFieldsUncentered = 1 << 1, + + /// Like , with the theme's padding. + TextFieldsBare = 1 << 2, + + /// "text" boxes keep the field color while hovered/focused. + FieldStates = 1 << 3, + + /// "text" boxes keep the field color while hovered/focused, with an accent border on focus. + FieldStatesAccent = 1 << 4, + + /// The text box of an AutoCompleteBox in the field look. + AutoComplete = 1 << 5, + + /// Compact time code fields. + TimeCodes = 1 << 6, + + /// Compact time code fields with compact inner text box and spinner. + CompactTimeCodes = 1 << 7, + + /// A disabled primary button is faded. + FadeDisabledPrimary = 1 << 8, + + /// A disabled primary button drops the accent (it looked clickable). + MuteDisabledPrimary = 1 << 9, + + /// A split button with class "primary" in the accent (muted when disabled with ). + PrimarySplitButton = 1 << 10, + + /// Small tab headers (34px) instead of the big Fluent ones. + CompactTabs = 1 << 11, + + /// Picker lists: transparent list boxes with rounded rows and the soft accent selection, + /// compact spinners, small tabs (32px) and check/radio buttons in the field font size. + PickerLists = 1 << 12, + + /// Toggle buttons with class "tool": bordered, the accent while checked. + ToolToggles = 1 << 13, + + /// Like , sized for a text and with a hover color. + TextToolToggles = 1 << 14, + + /// Compact split buttons. + CompactSplitButtons = 1 << 15, + + /// List boxes with class "compact": small rounded rows. + CompactListRows = 1 << 16, + + /// The form dialogs (find, replace, line tools, pickers, ...): compact time codes and + /// text fields that keep the field color, AutoCompleteBox included, a faded disabled primary. + Dialog = TextFields | FieldStates | AutoComplete | TimeCodes | FadeDisabledPrimary, + + /// The video dialogs: text fields, a muted disabled primary, an accent split button. + Video = TextFields | MuteDisabledPrimary | PrimarySplitButton, + + /// The pickers and prompts: with . + Picker = Dialog | PickerLists, + + /// The format properties and export settings forms: with compact + /// time codes and tabs. + Form = Dialog | CompactTimeCodes | CompactTabs, + + /// The video tool windows (video OCR, shot changes, open from URL, binary edit, ...): + /// text fields, compact time codes and tabs, a muted disabled primary. + VideoTool = TextFields | CompactTimeCodes | CompactTabs | MuteDisabledPrimary, + + /// The engine settings and download dialogs (OCR, llama.cpp, ffmpeg, ...): text fields + /// with an accent border on focus, a muted disabled primary. + EngineDialog = TextFieldsUncentered | FieldStatesAccent | MuteDisabledPrimary, + + /// The speech-to-text windows: text fields with an accent border on focus, toggle tool + /// buttons, an accent split button. + SpeechToText = TextFields | FieldStatesAccent | ToolToggles | PrimarySplitButton, +} + +/// +/// The building blocks the editor-look windows share: window setup, the bottom button bar, the +/// progress strip, list frames and sections, switch and label rows, flat action buttons, hints +/// and the download dialog layout. +/// +internal static partial class EditorChrome +{ + /// The class of the compact text fields (see ). + internal const string TextClass = "text"; + + private static readonly IBrush DownloadErrorBrush = new SolidColorBrush(Color.FromRgb(0xC0, 0x39, 0x2B)); + + /// + /// Window background, the editor styles, compact combo boxes and the given extra styles. + /// + internal static void ApplyWindow(Window window, EditorWindowStyles extra = EditorWindowStyles.None) + { + window.Background = WindowBrush; + AddWindowStyles(window.Styles, extra); + } + + /// + /// The styles of without the window background. + /// + internal static void AddWindowStyles(Styles styles, EditorWindowStyles extra = EditorWindowStyles.None) + { + AddEditorStyles(styles); + styles.Add(new Style(x => x.OfType()) + { + Setters = + { + new Setter(TemplatedControl.FontSizeProperty, 12.5), + new Setter(TemplatedControl.MinHeightProperty, 28.0), + new Setter(TemplatedControl.PaddingProperty, new Thickness(8, 2, 4, 2)), + new Setter(TemplatedControl.CornerRadiusProperty, new CornerRadius(6)), + }, + }); + + if (extra.HasFlag(EditorWindowStyles.TimeCodes) || extra.HasFlag(EditorWindowStyles.CompactTimeCodes)) + { + styles.Add(new Style(x => x.OfType()) + { + Setters = + { + new Setter(TemplatedControl.FontSizeProperty, 12.5), + new Setter(TemplatedControl.MinHeightProperty, 28.0), + }, + }); + } + + if (extra.HasFlag(EditorWindowStyles.TextFields) || extra.HasFlag(EditorWindowStyles.TextFieldsUncentered) || extra.HasFlag(EditorWindowStyles.TextFieldsBare)) + { + AddTextFieldStyles(styles, extra); + } + + if (extra.HasFlag(EditorWindowStyles.FieldStatesAccent)) + { + // Keep the field color when hovered/focused (the theme turns it black/white otherwise) + foreach (var state in new[] { ":pointerover", ":focus" }) + { + var focus = state == ":focus"; + styles.Add(new Style(x => x.OfType().Class(TextClass).Class(state).Template().OfType().Name("PART_BorderElement")) + { + Setters = + { + new Setter(Border.BackgroundProperty, FieldBrush), + new Setter(Border.BorderBrushProperty, focus ? AccentBrush : SeparatorBrush), + }, + }); + } + } + + if (extra.HasFlag(EditorWindowStyles.FieldStates)) + { + // Keep the field color while hovered/focused (the theme turns a focused box black/white) + foreach (var state in new[] { ":pointerover", ":focus" }) + { + styles.Add(new Style(x => x.OfType().Class(TextClass).Class(state).Template().OfType().Name("PART_BorderElement")) + { + Setters = { new Setter(Border.BackgroundProperty, FieldBrush) }, + }); + if (extra.HasFlag(EditorWindowStyles.AutoComplete)) + { + styles.Add(new Style(x => x.OfType().Template().OfType().Class(state).Template().OfType().Name("PART_BorderElement")) + { + Setters = { new Setter(Border.BackgroundProperty, FieldBrush) }, + }); + } + } + } + + if (extra.HasFlag(EditorWindowStyles.FadeDisabledPrimary)) + { + // The accent of a primary button would hide that it is disabled (Restore selected + // without a selection) + styles.Add(new Style(x => x.OfType private void UpdateShapeTreeName(DrawShape shape) { - foreach (var layerItem in ShapeTreeItems) + var shapeItem = EnumerateShapeTreeItems().FirstOrDefault(i => i.Shape == shape); + if (shapeItem != null) { - for (var i = 0; i < layerItem.Children.Count; i++) + shapeItem.Name = GetShapeTreeName(shape, shapeItem.ShapeNumber, shapeItem.NameWithLayer); + } + } + + private static string GetShapeTreeName(DrawShape shape, int shapeNumber, bool withLayer) + { + var name = GetShapeTreeName(shape, shapeNumber); + return withLayer ? $"{string.Format(Se.Language.Assa.DrawLayerX, shape.Layer)} · {name}" : name; + } + + private static ShapeTreeItem MakeShapeTreeItem(DrawShape shape, int shapeNumber, bool withLayer) + { + var shapeItem = new ShapeTreeItem + { + Name = GetShapeTreeName(shape, shapeNumber, withLayer), + Shape = shape, + ShapeNumber = shapeNumber, + NameWithLayer = withLayer, + IsExpanded = shape.Expanded, + IconName = shape.IsTextBox ? "fa-solid fa-font" : shape.IsEraser ? "fa-solid fa-eraser" : "fa-solid fa-draw-polygon", + IsHidden = shape.Hidden, + Meta = shape.IsTextBox ? string.Empty : shape.Points.Count.ToString(CultureInfo.InvariantCulture), + }; + + // A text box is sized with the handles, its corners aren't points to edit + foreach (var point in shape.IsTextBox ? [] : shape.Points) + { + var isControlPoint = point.DrawType is DrawCoordinateType.BezierCurveSupport1 or DrawCoordinateType.BezierCurveSupport2; + shapeItem.Children.Add(new ShapeTreeItem { - if (layerItem.Children[i].Shape == shape) - { - layerItem.Children[i].Name = GetShapeTreeName(shape, i + 1); - return; - } - } + Name = point.GetText(point.X, point.Y), + Point = point, + IconName = isControlPoint ? "fa-regular fa-circle" : "fa-regular fa-square", + IsHidden = shape.Hidden, + }); } + + return shapeItem; } /// - /// The tree item for a point, shape or layer, with its parents expanded so it can be shown selected. + /// Groups collapsed in the tree (open by default), by group id. /// - private ShapeTreeItem? FindTreeItem(DrawCoordinate? point, DrawShape? shape, int? layer) + private readonly HashSet _collapsedGroups = []; + + private ShapeTreeItem MakeGroupTreeItem(int groupId) { - foreach (var layerItem in ShapeTreeItems) + var item = new ShapeTreeItem + { + Name = string.Format(Se.Language.Assa.DrawGroupX, groupId), + IsGroup = true, + GroupId = groupId, + IsExpanded = !_collapsedGroups.Contains(groupId), + IconName = "fa-regular fa-object-group", + }; + item.ExpandedChanged = expanded => { - if (layer.HasValue && point == null && shape == null && layerItem.Layer == layer.Value) + if (expanded) { - return layerItem; + _collapsedGroups.Remove(groupId); } - - foreach (var shapeItem in layerItem.Children) + else { - if (shape != null && point == null && shapeItem.Shape == shape) - { - layerItem.IsExpanded = true; - return shapeItem; - } + _collapsedGroups.Add(groupId); + } + }; + return item; + } - if (point == null) + private static void FinishGroupTreeItem(ShapeTreeItem groupItem) + { + groupItem.IsHidden = groupItem.Children.All(c => c.IsHidden); + groupItem.Meta = groupItem.Children.Count.ToString(CultureInfo.InvariantCulture); + } + + /// + /// A group left with one shape (the others deleted, moved out or ungrouped) is no group. + /// + private void NormalizeGroups() + { + foreach (var group in Shapes.Where(s => s.GroupId != 0).GroupBy(s => s.GroupId).Where(g => g.Count() == 1).ToList()) + { + group.First().GroupId = 0; + } + } + + /// + /// Every shape row of the tree, in layers and in groups. + /// + private IEnumerable EnumerateShapeTreeItems() + { + foreach (var topItem in ShapeTreeItems) + { + foreach (var child in topItem.Children) + { + if (child.Shape != null) { + yield return child; continue; } - foreach (var pointItem in shapeItem.Children) + foreach (var groupChild in child.Children.Where(c => c.Shape != null)) { - if (pointItem.Point == point) - { - layerItem.IsExpanded = true; - shapeItem.IsExpanded = true; - return pointItem; - } + yield return groupChild; } } } + } + + private ShapeTreeItem? FindGroupTreeItem(int groupId) + { + return groupId == 0 ? null : FindAndExpand(ShapeTreeItems, i => i.IsGroup && i.GroupId == groupId, i => i.IsLayer); + } + + /// + /// The tree item for a point, shape or layer, with its parents expanded so it can be shown selected. + /// + private ShapeTreeItem? FindTreeItem(DrawCoordinate? point, DrawShape? shape, int? layer) + { + if (point != null) + { + return FindAndExpand(ShapeTreeItems, i => i.Point == point, _ => true); + } + + if (shape != null) + { + // A shape row's children are its points - no need to look through those + return FindAndExpand(ShapeTreeItems, i => i.Shape == shape, i => i.Shape == null); + } + + return layer.HasValue ? ShapeTreeItems.FirstOrDefault(i => i.IsLayer && i.Layer == layer.Value) : null; + } + + private static ShapeTreeItem? FindAndExpand(IEnumerable items, Func match, Func descend) + { + foreach (var item in items) + { + if (match(item)) + { + return item; + } + + if (item.Children.Count == 0 || !descend(item)) + { + continue; + } + + var found = FindAndExpand(item.Children, match, descend); + if (found != null) + { + item.IsExpanded = true; + return found; + } + } return null; } @@ -2868,26 +3398,33 @@ private void ImportAssaDrawingFromText(string text, int layer, Color color, bool Shapes.AddRange(DrawShape.ParseAssa(text, layer, color, isEraser)); } - private readonly List<(int Layer, AssaDrawTextLine.Parsed Text, bool Hidden)> _importedTexts = []; + private readonly List<(int Layer, AssaDrawTextLine.Parsed Text, bool Hidden, int GroupId)> _importedTexts = []; /// /// One line of a script: its \iclip erase shapes, its \p1 drawing (with the line's outline), /// or an ASSA draw text line - those are attached to their shapes by . - /// A line (Comment line of a saved .assadraw) gives hidden shapes. + /// A line (Comment line of a saved .assadraw) gives hidden shapes, and + /// the line's (saved .assadraw) their groups. /// - private void ImportLine(string text, int layer, Color color, bool hidden = false) + private void ImportLine(string text, int layer, Color color, bool hidden = false, string? effect = null) { + var (drawGroups, eraseGroups) = ParseGroupEffect(effect); if (AssaDrawTextLine.TryParse(text, out var parsedText)) { - _importedTexts.Add((layer, parsedText, hidden)); + _importedTexts.Add((layer, parsedText, hidden, drawGroups.FirstOrDefault())); return; } var firstNewShape = Shapes.Count; ImportDrawingLine(text, layer, color); + int drawIndex = 0, eraseIndex = 0; for (var i = firstNewShape; i < Shapes.Count; i++) { - Shapes[i].Hidden = hidden; + var shape = Shapes[i]; + shape.Hidden = hidden; + var groups = shape.IsEraser ? eraseGroups : drawGroups; + var index = shape.IsEraser ? eraseIndex++ : drawIndex++; + shape.GroupId = index < groups.Count ? groups[index] : 0; } } @@ -2927,7 +3464,7 @@ private void ImportDrawingLine(string text, int layer, Color color) /// private void AttachTextLines() { - foreach (var (layer, parsed, hidden) in _importedTexts) + foreach (var (layer, parsed, hidden, groupId) in _importedTexts) { var (left, top, right, bottom) = parsed.Box; var centerX = (left + right) / 2; @@ -2944,6 +3481,7 @@ private void AttachTextLines() host.OutlineWidth = parsed.OutlineWidth; host.OutlineColor = parsed.OutlineColor; host.Hidden = hidden; + host.GroupId = groupId; Shapes.Add(host); } @@ -3008,7 +3546,7 @@ internal void LoadFromText(string text) } } - ImportLine(paragraph.Text, paragraph.Layer, color, hidden: paragraph.IsComment); + ImportLine(paragraph.Text, paragraph.Layer, color, hidden: paragraph.IsComment, effect: paragraph.Effect); } AttachTextLines(); @@ -3092,6 +3630,20 @@ internal async void OnKeyDown(KeyEventArgs e) e.Handled = true; } + else if ((e.KeyModifiers & ~KeyModifiers.Shift) is KeyModifiers.Control or KeyModifiers.Meta && e.Key == Key.G) + { + // Ctrl/Cmd+G groups the selection, Ctrl/Cmd+Shift+G ungroups it + if (e.KeyModifiers.HasFlag(KeyModifiers.Shift)) + { + UngroupShapes(); + } + else + { + GroupShapes(); + } + + e.Handled = true; + } else if (e.KeyModifiers.HasFlag(KeyModifiers.Alt) || (e.KeyModifiers.HasFlag(KeyModifiers.Control) && !e.KeyModifiers.HasFlag(KeyModifiers.Shift))) { @@ -3135,7 +3687,8 @@ internal async void OnKeyDown(KeyEventArgs e) e.Handled = true; } break; - case Key.G: + case Key.OemQuotes: + // Ctrl+' like other drawing programs - Ctrl+G is Group if (e.KeyModifiers.HasFlag(KeyModifiers.Control)) { ToggleGrid(); @@ -3292,21 +3845,18 @@ internal bool AdjustPosition(float xAdjust, float yAdjust) private void UpdatePointNames(List shapes) { var set = shapes.Count > 4 ? new HashSet(shapes) : null; - foreach (var layerItem in ShapeTreeItems) + foreach (var shapeItem in EnumerateShapeTreeItems()) { - foreach (var shapeItem in layerItem.Children) + if (shapeItem.Shape == null || !(set?.Contains(shapeItem.Shape) ?? shapes.Contains(shapeItem.Shape))) { - if (shapeItem.Shape == null || !(set?.Contains(shapeItem.Shape) ?? shapes.Contains(shapeItem.Shape))) - { - continue; - } + continue; + } - foreach (var pointItem in shapeItem.Children) + foreach (var pointItem in shapeItem.Children) + { + if (pointItem.Point is { } p) { - if (pointItem.Point is { } p) - { - pointItem.Name = p.GetText(p.X, p.Y); - } + pointItem.Name = p.GetText(p.X, p.Y); } } } @@ -3437,17 +3987,14 @@ private void UpdateSelectedPointName() } // Find and update the tree item that contains this point - foreach (var layerItem in ShapeTreeItems) + foreach (var shapeItem in EnumerateShapeTreeItems()) { - foreach (var shapeItem in layerItem.Children) + foreach (var pointItem in shapeItem.Children) { - foreach (var pointItem in shapeItem.Children) + if (pointItem.Point == ActivePoint) { - if (pointItem.Point == ActivePoint) - { - pointItem.Name = ActivePoint.GetText(ActivePoint.X, ActivePoint.Y); - return; - } + pointItem.Name = ActivePoint.GetText(ActivePoint.X, ActivePoint.Y); + return; } } } @@ -3487,6 +4034,10 @@ private void ApplyTreeSelection(ShapeTreeItem? value) ShapeIsEraser = value.Shape.IsEraser; SetLayerColorFromSelection(value.Shape.ForeColor); } + else if (value.IsGroup && ActiveShape != null && Shapes.Contains(ActiveShape)) + { + ActiveShape = null; + } } // Update layer selection state @@ -3494,11 +4045,21 @@ private void ApplyTreeSelection(ShapeTreeItem? value) IsShapeSelected = value?.Shape != null; // A tree pick ends a Ctrl+A selection - otherwise the arrow keys kept nudging every shape. - if (value != null) + // A group node is a selection of its own: all of its shapes. + if (value?.IsGroup == true) + { + var members = Shapes.Where(s => s.GroupId == value.GroupId).ToList(); + SelectedShapes = members; + if (members.Count > 0) + { + SetLayerColorFromSelection(members[0].ForeColor); + } + } + else if (value != null) { SelectedShapes = []; } - + if (value?.IsLayer == true) { // Get color from first shape in this layer @@ -3574,7 +4135,9 @@ private void ToggleItemVisibility(ShapeTreeItem? item) var shapes = item.IsLayer ? Shapes.Where(s => s.Layer == item.Layer).ToList() - : item.Shape != null ? [item.Shape] : []; + : item.IsGroup + ? Shapes.Where(s => s.GroupId == item.GroupId).ToList() + : item.Shape != null ? [item.Shape] : []; if (shapes.Count == 0) { return; @@ -3686,6 +4249,13 @@ private UndoState CaptureState() state.SelectedShapeIndex = selectedShape != null ? Shapes.IndexOf(selectedShape) : -1; state.SelectedPointIndex = item?.Point != null && selectedShape != null ? selectedShape.Points.IndexOf(item.Point) : -1; state.SelectedLayer = item?.IsLayer == true ? item.Layer : null; + + // A group or multi-selection comes back too (undo of group/ungroup, a moved group) + if (SelectedShapes.Count > 1) + { + state.SelectedShapeIndexes = SelectedShapes.Select(s => Shapes.IndexOf(s)).Where(i => i >= 0).ToList(); + } + return state; } @@ -3713,7 +4283,11 @@ private void RestoreState(UndoState state) RefreshTreeView(); - if (state.SelectedShapeIndex >= 0 && state.SelectedShapeIndex < Shapes.Count) + if (state.SelectedShapeIndexes is { Count: > 1 } indexes && indexes.All(i => i < Shapes.Count)) + { + SetSelection(indexes.Select(i => Shapes[i])); + } + else if (state.SelectedShapeIndex >= 0 && state.SelectedShapeIndex < Shapes.Count) { var shape = Shapes[state.SelectedShapeIndex]; if (state.SelectedPointIndex >= 0 && state.SelectedPointIndex < shape.Points.Count) @@ -3775,6 +4349,7 @@ internal sealed class UndoState public int SelectedShapeIndex { get; set; } = -1; public int SelectedPointIndex { get; set; } = -1; public int? SelectedLayer { get; set; } + public List? SelectedShapeIndexes { get; set; } } /// @@ -3795,8 +4370,25 @@ public partial class ShapeTreeItem : ObservableObject public DrawShape? Shape { get; set; } public DrawCoordinate? Point { get; set; } + /// + /// A group node: its children are the group's shapes. + /// + public bool IsGroup { get; set; } + public int GroupId { get; set; } + + /// + /// A shape row's number within its layer, and whether its name starts with the layer (in a group spanning layers). + /// + public int ShapeNumber { get; set; } + public bool NameWithLayer { get; set; } + + /// + /// Called when a group node is opened or closed, so a tree rebuild keeps it that way. + /// + public Action? ExpandedChanged { get; set; } + public bool HasSwatch => IsLayer; - public bool CanToggleVisibility => IsLayer || Shape != null; + public bool CanToggleVisibility => IsLayer || IsGroup || Shape != null; partial void OnIsExpandedChanged(bool value) { @@ -3805,6 +4397,8 @@ partial void OnIsExpandedChanged(bool value) { Shape.Expanded = value; } + + ExpandedChanged?.Invoke(value); } public override string ToString() => Name; diff --git a/src/ui/Features/Assa/AssaDraw/AssaDrawWindow.cs b/src/ui/Features/Assa/AssaDraw/AssaDrawWindow.cs index 1bcb811ca3f..56c70bb1422 100644 --- a/src/ui/Features/Assa/AssaDraw/AssaDrawWindow.cs +++ b/src/ui/Features/Assa/AssaDraw/AssaDrawWindow.cs @@ -158,6 +158,10 @@ private void OnTreeContextMenuOpening(ContextMenu menu, CancelEventArgs e) { AddLayerItems(items, item); } + else if (item?.IsGroup == true) + { + AddGroupNodeItems(items); + } else if (item?.Point != null) { AddPointItems(items, item.Point); @@ -203,6 +207,7 @@ private void AddPointItems(List items, DrawCoordinate point) private void AddShapeItems(List items, DrawShape shape) { items.Add(MakeMenuItem(Se.Language.General.Duplicate, "fa-regular fa-clone", _vm.DuplicateShapeCommand, new KeyGesture(Key.D, KeyModifiers.Control))); + AddGroupItems(items); var moveToLayer = MakeMenuItem(Se.Language.Assa.DrawMoveToLayer, "fa-solid fa-layer-group", null); var layerItems = new List(); @@ -250,6 +255,40 @@ private void AddShapeItems(List items, DrawShape shape) items.Add(MakeMenuItem(WithoutShortcut(Se.Language.Assa.DrawDeleteShape), "fa-solid fa-trash", _vm.DeleteShapeCommand, new KeyGesture(Key.Delete))); } + /// + /// Group / Ungroup, each shown only when it can do something. + /// + private void AddGroupItems(List items) + { + if (_vm.GroupShapesCommand.CanExecute(null)) + { + items.Add(MakeMenuItem(WithoutShortcut(Se.Language.Assa.DrawGroup), "fa-regular fa-object-group", _vm.GroupShapesCommand, new KeyGesture(Key.G, KeyModifiers.Control))); + } + + if (_vm.UngroupShapesCommand.CanExecute(null)) + { + items.Add(MakeMenuItem(WithoutShortcut(Se.Language.Assa.DrawUngroup), "fa-regular fa-object-ungroup", _vm.UngroupShapesCommand, new KeyGesture(Key.G, KeyModifiers.Control | KeyModifiers.Shift))); + } + } + + /// + /// A group node in the tree: what acts on the whole group. + /// + private void AddGroupNodeItems(List items) + { + items.Add(MakeMenuItem(Se.Language.General.Duplicate, "fa-regular fa-clone", _vm.DuplicateShapeCommand, new KeyGesture(Key.D, KeyModifiers.Control))); + AddGroupItems(items); + AddSeparator(items); + items.Add(MakeMenuItem(Se.Language.Assa.DrawRotateClockwise, "fa-solid fa-rotate-right", _vm.RotateShapeClockwiseCommand)); + items.Add(MakeMenuItem(Se.Language.Assa.DrawRotateCounterClockwise, "fa-solid fa-rotate-left", _vm.RotateShapeCounterClockwiseCommand)); + items.Add(MakeMenuItem(Se.Language.Assa.DrawFlipHorizontal, "fa-solid fa-left-right", _vm.FlipShapeHorizontalCommand)); + items.Add(MakeMenuItem(Se.Language.Assa.DrawFlipVertical, "fa-solid fa-up-down", _vm.FlipShapeVerticalCommand)); + AddSeparator(items); + items.Add(MakeMenuItem(Se.Language.Assa.DrawAddToLibrary, "fa-solid fa-shapes", _vm.AddToLibraryCommand)); + AddSeparator(items); + items.Add(MakeMenuItem(WithoutShortcut(Se.Language.Assa.DrawDeleteShape), "fa-solid fa-trash", _vm.DeleteShapeCommand, new KeyGesture(Key.Delete))); + } + private void AddLayerItems(List items, ShapeTreeItem layerItem) { items.Add(MakeMenuItem( @@ -285,7 +324,7 @@ private void AddCanvasItems(List items) items.Add(MakeMenuItem(WithoutShortcut(Se.Language.Assa.DrawResetView), "fa-solid fa-expand", _vm.ResetViewCommand, new KeyGesture(Key.D0, KeyModifiers.Control))); AddSeparator(items); - var grid = MakeMenuItem(WithoutShortcut(Se.Language.Assa.DrawToggleGrid), null, _vm.ToggleGridCommand, new KeyGesture(Key.G, KeyModifiers.Control)); + var grid = MakeMenuItem(WithoutShortcut(Se.Language.Assa.DrawToggleGrid), null, _vm.ToggleGridCommand, new KeyGesture(Key.OemQuotes, KeyModifiers.Control)); grid.ToggleType = MenuItemToggleType.CheckBox; grid.IsChecked = _vm.ShowGrid; items.Add(grid); @@ -381,6 +420,8 @@ private static Border CreateToolbar(AssaDrawViewModel vm) // Shape actions leftPanel.Children.Add(CreateToolButton("fa-solid fa-check", Se.Language.Assa.DrawCloseShape, vm.CloseShapeCommand)); leftPanel.Children.Add(CreateToolButton("fa-regular fa-clone", Se.Language.General.Duplicate + " (Ctrl+D)", vm.DuplicateShapeCommand)); + leftPanel.Children.Add(CreateToolButton("fa-regular fa-object-group", Se.Language.Assa.DrawGroup, vm.GroupShapesCommand)); + leftPanel.Children.Add(CreateToolButton("fa-regular fa-object-ungroup", Se.Language.Assa.DrawUngroup, vm.UngroupShapesCommand)); leftPanel.Children.Add(CreateToolButton("fa-solid fa-trash", Se.Language.Assa.DrawDeleteShape, vm.DeleteShapeCommand)); leftPanel.Children.Add(CreateToolButton("fa-solid fa-eraser", Se.Language.Assa.DrawClearAll, vm.ClearAllCommand)); leftPanel.Children.Add(MakeToolbarSeparator()); @@ -806,6 +847,21 @@ private Border CreateSidePanel(AssaDrawViewModel vm) [!TreeView.ItemsSourceProperty] = new Binding(nameof(vm.ShapeTreeItems)), [!TreeView.SelectedItemProperty] = new Binding(nameof(vm.SelectedTreeItem)) { Mode = BindingMode.TwoWay }, Margin = new Thickness(6, 0, 6, 6), + + // Ctrl/Shift+click picks several shapes or groups; a plain click still picks one + SelectionMode = SelectionMode.Multiple, + }; + treeView.SelectionChanged += (_, _) => + { + if (treeView.SelectedItems is { Count: > 1 } selectedItems) + { + vm.SetTreeMultiSelection(selectedItems.OfType().ToList()); + } + else if (treeView.SelectedItems is { Count: 1 } single && single[0] is ShapeTreeItem item) + { + // A plain click on the row that was already the tree's current item ends the multi-selection + vm.OnTreeSingleSelection(item); + } }; treeView.Styles.Add(new Style(x => x.OfType()) { diff --git a/src/ui/Features/Assa/AssaDraw/DrawShape.cs b/src/ui/Features/Assa/AssaDraw/DrawShape.cs index 2ac8ddc32c0..8f2a5217583 100644 --- a/src/ui/Features/Assa/AssaDraw/DrawShape.cs +++ b/src/ui/Features/Assa/AssaDraw/DrawShape.cs @@ -27,6 +27,12 @@ public class DrawShape public bool Hidden { get; set; } public bool Expanded { get; set; } + /// + /// Editor-only group (0 = none): a click selects every shape with the same id. Not part of + /// the ASSA output; an .assadraw file keeps it in the Effect field. + /// + public int GroupId { get; set; } + /// /// A text-only object: its rectangle is where the text goes, nothing is drawn for it. /// @@ -67,6 +73,7 @@ public DrawShape(DrawShape other) IsEraser = other.IsEraser; Hidden = other.Hidden; Expanded = other.Expanded; + GroupId = other.GroupId; IsTextBox = other.IsTextBox; Text = other.Text; FontName = other.FontName; diff --git a/src/ui/Logic/Config/Language/Assa/LanguageAssa.cs b/src/ui/Logic/Config/Language/Assa/LanguageAssa.cs index 41cb93e63a6..a4f49f1d56d 100644 --- a/src/ui/Logic/Config/Language/Assa/LanguageAssa.cs +++ b/src/ui/Logic/Config/Language/Assa/LanguageAssa.cs @@ -168,6 +168,9 @@ public class LanguageAssa public string DrawXPoints { get; set; } public string DrawShapeXDraw { get; set; } public string DrawShapeXErase { get; set; } + public string DrawGroup { get; set; } + public string DrawUngroup { get; set; } + public string DrawGroupX { get; set; } // Progress Bar Generator public string ProgressBarTitle { get; set; } @@ -508,7 +511,7 @@ public LanguageAssa() DrawZoomIn = "Zoom In (Ctrl++)"; DrawZoomOut = "Zoom Out (Ctrl+-)"; DrawResetView = "Reset View (Ctrl+0)"; - DrawToggleGrid = "Toggle Grid (Ctrl+G)"; + DrawToggleGrid = "Toggle Grid (Ctrl+')"; DrawTogglePreview = "Toggle preview (F9)"; DrawPreviewFailed = "Preview failed - ffmpeg with libass (subtitles filter) is required"; DrawCopyToClipboard = "Copy to Clipboard (Ctrl+C)"; @@ -659,6 +662,9 @@ public LanguageAssa() DrawXPoints = "{0} points"; DrawShapeXDraw = "Shape {0} (draw)"; DrawShapeXErase = "Shape {0} (erase)"; + DrawGroup = "Group (Ctrl+G)"; + DrawUngroup = "Ungroup (Ctrl+Shift+G)"; + DrawGroupX = "Group {0}"; // Progress Bar Generator ProgressBarTitle = "ASSA progress bar"; diff --git a/tests/UI/Features/Assa/AssaDrawGroupTests.cs b/tests/UI/Features/Assa/AssaDrawGroupTests.cs new file mode 100644 index 00000000000..f865ddbbcc3 --- /dev/null +++ b/tests/UI/Features/Assa/AssaDrawGroupTests.cs @@ -0,0 +1,553 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Headless; +using Avalonia.Headless.XUnit; +using Avalonia.Input; +using Avalonia.Input.Raw; +using Avalonia.LogicalTree; +using Avalonia.Media; +using Avalonia.Threading; +using Nikse.SubtitleEdit.Core.Common; +using Nikse.SubtitleEdit.Core.SubtitleFormats; +using Nikse.SubtitleEdit.Features.Assa.AssaDraw; +using Nikse.SubtitleEdit.Features.Main; +using Nikse.SubtitleEdit.Logic.Config; +using Nikse.SubtitleEdit.Logic.Media; + +namespace UITests.Features.Assa; + +/// +/// Groups (editor-only), rubber-band selection and Ctrl+click selection in ASSA draw. +/// +public class AssaDrawGroupTests +{ + private const string SquareA = "{\\p1}m 100 100 l 200 100 200 200 100 200{\\p0}"; + private const string SquareB = "{\\p1}m 300 100 l 400 100 400 200 300 200{\\p0}"; + private const string SquareC = "{\\p1}m 600 600 l 700 600 700 700 600 700{\\p0}"; + + private static AssaDrawViewModel MakeViewModel(params string[] lines) + { + var vm = new AssaDrawViewModel(new FileHelper(), new StubWindowService()); + var rows = lines.Select((t, i) => new SubtitleLineViewModel(new Paragraph(t, 0, 2000) { Extra = "Default", Layer = i }, new AdvancedSubStationAlpha())).ToList(); + vm.Initialize(new Subtitle { Header = AdvancedSubStationAlpha.DefaultHeader }, rows, 1920, 1080); + return vm; + } + + private static (AssaDrawWindow Window, AssaDrawViewModel Vm) Open(params string[] lines) + { + var vm = MakeViewModel(lines); + var window = new AssaDrawWindow(vm) { Width = 1200, Height = 800 }; + window.Show(); + Dispatcher.UIThread.RunJobs(); + vm.Canvas!.FitToView(); + vm.SelectToolCommand.Execute(null); + Dispatcher.UIThread.RunJobs(); + return (window, vm); + } + + /// + /// A, B grouped (layers 0 and 1), C on its own. + /// + private static (AssaDrawWindow Window, AssaDrawViewModel Vm) OpenWithGroup() + { + var (window, vm) = Open(SquareA, SquareB, SquareC); + vm.SetSelection([vm.Shapes[0], vm.Shapes[1]]); + vm.GroupShapesCommand.Execute(null); + Dispatcher.UIThread.RunJobs(); + return (window, vm); + } + + private static Point ToWindow(AssaDrawWindow window, AssaDrawCanvas canvas, double x, double y) + { + var origin = canvas.TranslatePoint(new Point(0, 0), window)!.Value; + var zoom = canvas.ZoomFactor; + var panX = (canvas.Bounds.Width - canvas.CanvasWidth * zoom) / 2; + var panY = (canvas.Bounds.Height - canvas.CanvasHeight * zoom) / 2; + return new Point(origin.X + panX + x * zoom, origin.Y + panY + y * zoom); + } + + private static void Click(AssaDrawWindow window, AssaDrawViewModel vm, double x, double y, RawInputModifiers modifiers = RawInputModifiers.None) + { + var point = ToWindow(window, vm.Canvas!, x, y); + window.MouseMove(point, modifiers); + window.MouseDown(point, MouseButton.Left, modifiers); + window.MouseUp(point, MouseButton.Left, modifiers); + Dispatcher.UIThread.RunJobs(); + } + + private static void Drag(AssaDrawWindow window, AssaDrawViewModel vm, (double X, double Y) from, (double X, double Y) to, RawInputModifiers modifiers = RawInputModifiers.None) + { + var start = ToWindow(window, vm.Canvas!, from.X, from.Y); + var end = ToWindow(window, vm.Canvas!, to.X, to.Y); + window.MouseMove(start, modifiers); + window.MouseDown(start, MouseButton.Left, modifiers); + window.MouseMove(new Point((start.X + end.X) / 2, (start.Y + end.Y) / 2), RawInputModifiers.LeftMouseButton | modifiers); + window.MouseMove(end, RawInputModifiers.LeftMouseButton | modifiers); + window.MouseUp(end, MouseButton.Left, modifiers); + Dispatcher.UIThread.RunJobs(); + } + + private static void Press(AssaDrawViewModel vm, Key key, KeyModifiers modifiers = KeyModifiers.None) + { + vm.OnKeyDown(new KeyEventArgs { Key = key, KeyModifiers = modifiers, RoutedEvent = InputElement.KeyDownEvent }); + Dispatcher.UIThread.RunJobs(); + } + + private static IEnumerable AllTreeItems(IEnumerable items) => + items.SelectMany(i => new[] { i }.Concat(AllTreeItems(i.Children))); + + [Fact] + public void InsertClipArt_IsOneGroup_SelectedAsAGroupNodeOfItsOwn() + { + var vm = MakeViewModel(SquareA); + vm.CurrentLibraryShape = ShapeLibrary.BuiltIn.First(i => i.Name == Se.Language.Assa.DrawClipArtSun); + + vm.InsertLibraryShape(new CanvasInsertEventArgs(100, 100, 400, 400, isClick: false)); + + var inserted = vm.Shapes.Skip(1).ToList(); + Assert.True(inserted.Count > 1); + Assert.NotEqual(0, inserted[0].GroupId); + Assert.All(inserted, s => Assert.Equal(inserted[0].GroupId, s.GroupId)); + Assert.Equal(0, vm.Shapes[0].GroupId); + Assert.Equal(inserted, vm.SelectedShapes); + + // Spread over several layers: one group node next to the layers, its shapes named with their layer + var groupItem = Assert.IsType(vm.SelectedTreeItem); + Assert.True(groupItem.IsGroup); + Assert.Contains(groupItem, vm.ShapeTreeItems); + Assert.Equal(string.Format(Se.Language.Assa.DrawGroupX, inserted[0].GroupId), groupItem.Name); + Assert.Equal(inserted.Count, groupItem.Children.Count); + Assert.StartsWith(string.Format(Se.Language.Assa.DrawLayerX, inserted[0].Layer), groupItem.Children[0].Name); + Assert.Equal(string.Format(Se.Language.Assa.DrawXShapes, inserted.Count), vm.SelectionText[(vm.SelectionText.IndexOf('·') + 2)..]); + } + + [Fact] + public void InsertRing_OnOneLayer_IsAGroupNodeInsideItsLayer() + { + var vm = MakeViewModel(); + vm.CurrentLibraryShape = ShapeLibrary.BuiltIn.First(i => i.Name == Se.Language.Assa.DrawShapeRing); + + vm.InsertLibraryShape(new CanvasInsertEventArgs(100, 100, 400, 400, isClick: false)); + + Assert.Equal(2, vm.Shapes.Count); + var layerItem = Assert.Single(vm.ShapeTreeItems); + Assert.True(layerItem.IsLayer); + var groupItem = Assert.Single(layerItem.Children); + Assert.True(groupItem.IsGroup); + Assert.Same(groupItem, vm.SelectedTreeItem); + Assert.Equal(2, groupItem.Children.Count); + Assert.Equal(string.Format(Se.Language.Assa.DrawShapeXDraw, 1), groupItem.Children[0].Name); + } + + [Fact] + public void SvgImport_IsOneGroup() + { + var vm = MakeViewModel(); + + Assert.True(vm.ImportSvgText("")); + + Assert.Equal(2, vm.Shapes.Count); + Assert.NotEqual(0, vm.Shapes[0].GroupId); + Assert.Equal(vm.Shapes[0].GroupId, vm.Shapes[1].GroupId); + Assert.Equal(2, vm.SelectedShapes.Count); + Assert.True(vm.SelectedTreeItem?.IsGroup); + } + + [AvaloniaFact] + public void Click_OnAGroupPart_SelectsTheWholeGroup_AltClickSelectsThePart() + { + var (window, vm) = OpenWithGroup(); + try + { + Click(window, vm, 650, 650); + Assert.Same(vm.Shapes[2], vm.SelectedTreeItem?.Shape); + Assert.Empty(vm.SelectedShapes); + + Click(window, vm, 150, 150); + Assert.Equal([vm.Shapes[0], vm.Shapes[1]], vm.SelectedShapes); + Assert.True(vm.SelectedTreeItem?.IsGroup); + Assert.Equal(vm.SelectedShapes, vm.Canvas!.SelectedShapes); + + // A drag on a part moves the whole group, as one undo step + Drag(window, vm, (180, 180), (200, 210)); + Assert.Equal(120, vm.Shapes[0].Points[0].X, 0); + Assert.Equal(320, vm.Shapes[1].Points[0].X, 0); + Assert.Equal(130, vm.Shapes[1].Points[0].Y, 0); + Assert.True(vm.SelectedTreeItem?.IsGroup); + + // Alt+click: the part alone, so its points can be edited + Click(window, vm, 350, 150, RawInputModifiers.Alt); + Assert.Same(vm.Shapes[1], vm.SelectedTreeItem?.Shape); + Assert.Empty(vm.SelectedShapes); + + Click(window, vm, 320, 130); + Assert.Same(vm.Shapes[1].Points[0], vm.ActivePoint); + + // Esc: point -> part -> nothing + Press(vm, Key.Escape); + Assert.Null(vm.ActivePoint); + Assert.Same(vm.Shapes[1], vm.SelectedTreeItem?.Shape); + Press(vm, Key.Escape); + Assert.Null(vm.SelectedTreeItem); + + // Undo of the drag + vm.UndoCommand.Execute(null); + Assert.Equal(100, vm.Shapes[0].Points[0].X, 0); + Assert.Equal(300, vm.Shapes[1].Points[0].X, 0); + } + finally + { + window.Close(); + } + } + + [AvaloniaFact] + public void Click_NearAGroupPartsPoint_SelectsTheGroup_NotThePoint() + { + var (window, vm) = OpenWithGroup(); + try + { + Click(window, vm, 650, 650); + Click(window, vm, 101, 101); + + Assert.Null(vm.ActivePoint); + Assert.True(vm.SelectedTreeItem?.IsGroup); + } + finally + { + window.Close(); + } + } + + [Fact] + public void CtrlG_Groups_CtrlShiftG_Ungroups_BothUndoable() + { + var vm = MakeViewModel(SquareA, SquareB, SquareC); + Assert.False(vm.GroupShapesCommand.CanExecute(null)); + + vm.SetSelection([vm.Shapes[0], vm.Shapes[2]]); + Assert.True(vm.GroupShapesCommand.CanExecute(null)); + Assert.False(vm.UngroupShapesCommand.CanExecute(null)); + + Press(vm, Key.G, KeyModifiers.Control); + + Assert.NotEqual(0, vm.Shapes[0].GroupId); + Assert.Equal(vm.Shapes[0].GroupId, vm.Shapes[2].GroupId); + Assert.Equal(0, vm.Shapes[1].GroupId); + Assert.True(vm.SelectedTreeItem?.IsGroup); + Assert.False(vm.GroupShapesCommand.CanExecute(null)); + Assert.True(vm.UngroupShapesCommand.CanExecute(null)); + + Press(vm, Key.G, KeyModifiers.Control | KeyModifiers.Shift); + + Assert.All(vm.Shapes, s => Assert.Equal(0, s.GroupId)); + Assert.Equal([vm.Shapes[0], vm.Shapes[2]], vm.SelectedShapes); + Assert.False(vm.SelectedTreeItem?.IsGroup == true); + + vm.UndoCommand.Execute(null); + Assert.NotEqual(0, vm.Shapes[0].GroupId); + Assert.Equal(vm.Shapes[0].GroupId, vm.Shapes[2].GroupId); + Assert.True(vm.SelectedTreeItem?.IsGroup); + Assert.Equal(2, vm.SelectedShapes.Count); + + vm.UndoCommand.Execute(null); + Assert.All(vm.Shapes, s => Assert.Equal(0, s.GroupId)); + } + + [Fact] + public void CtrlQuote_TogglesTheGrid() + { + var vm = MakeViewModel(SquareA); + var oldGrid = DrawSettings.ShowGrid; + try + { + var before = vm.ShowGrid; + Press(vm, Key.OemQuotes, KeyModifiers.Control); + Assert.NotEqual(before, vm.ShowGrid); + } + finally + { + DrawSettings.ShowGrid = oldGrid; + } + } + + [Fact] + public void Ungroup_OnePart_LeavesNoOneShapeGroup() + { + var vm = MakeViewModel(SquareA, SquareB); + vm.SetSelection(vm.Shapes); + vm.GroupShapesCommand.Execute(null); + + vm.SelectShape(vm.Shapes[0]); + vm.UngroupShapesCommand.Execute(null); + + Assert.All(vm.Shapes, s => Assert.Equal(0, s.GroupId)); + Assert.DoesNotContain(AllTreeItems(vm.ShapeTreeItems), i => i.IsGroup); + } + + [Fact] + public void Duplicate_OfAGroup_GetsANewGroup_ACopiedPartGetsNone() + { + var vm = MakeViewModel(SquareA, SquareB, SquareC); + vm.SetSelection([vm.Shapes[0], vm.Shapes[1]]); + vm.GroupShapesCommand.Execute(null); + var groupId = vm.Shapes[0].GroupId; + + Press(vm, Key.D, KeyModifiers.Control); + + Assert.Equal(5, vm.Shapes.Count); + var copies = vm.Shapes.Where(s => s.GroupId != groupId && s.GroupId != 0).ToList(); + Assert.Equal(2, copies.Count); + Assert.Equal(copies[0].GroupId, copies[1].GroupId); + Assert.Equal(2, vm.Shapes.Count(s => s.GroupId == groupId)); + Assert.Equal(copies, vm.SelectedShapes); + Assert.Equal(copies[0].GroupId, vm.SelectedTreeItem?.GroupId); + + // One part copied out of its group is no group member + vm.SelectShape(vm.Shapes[0]); + vm.DuplicateShapeCommand.Execute(null); + Assert.Equal(0, vm.SelectedTreeItem?.Shape?.GroupId); + Assert.Equal(2, vm.Shapes.Count(s => s.GroupId == groupId)); + } + + [Fact] + public void Delete_OfAGroupNode_DeletesTheWholeGroup() + { + var vm = MakeViewModel(SquareA, SquareB, SquareC); + vm.SetSelection([vm.Shapes[0], vm.Shapes[1]]); + vm.GroupShapesCommand.Execute(null); + + Press(vm, Key.Delete); + + var remaining = Assert.Single(vm.Shapes); + Assert.Equal(600, remaining.Points[0].X); + } + + [Fact] + public void SaveAndLoad_KeepsGroups_OutputHasNoGroupData() + { + var vm = MakeViewModel(SquareA, SquareB, SquareC); + vm.Shapes[1].Hidden = true; + vm.SetSelection([vm.Shapes[0], vm.Shapes[1]]); + vm.GroupShapesCommand.Execute(null); + + // An erase shape and a text box in a second group + vm.CurrentLibraryShape = ShapeLibrary.BuiltIn.First(i => i.Name == Se.Language.Assa.DrawShapeRing); + vm.InsertLibraryShape(new CanvasInsertEventArgs(800, 100, 200, 200, isClick: false)); + var ring = vm.Shapes.Skip(3).ToList(); + ring[1].IsEraser = true; + var textBox = vm.InsertTextBox(new CanvasInsertEventArgs(1000, 500, 300, 100, isClick: false)); + textBox.GroupId = ring[0].GroupId; + var expected = vm.Shapes.Select(s => (s.Layer, s.IsEraser, s.IsTextBox, s.Hidden, s.GroupId)).OrderBy(t => t.Layer).ThenBy(t => t.IsTextBox).ThenBy(t => t.IsEraser).ToList(); + + var text = vm.GetAssaDrawFileText(); + Assert.Contains("SeGroups:", text); + Assert.DoesNotContain("SeGroups", vm.GenerateAssaCode()); + vm.OkCommand.Execute(null); + Assert.All(vm.ResultSubtitle.Paragraphs, p => Assert.True(string.IsNullOrEmpty(p.Effect))); + + var loaded = MakeViewModel(); + loaded.LoadFromText(text); + + var actual = loaded.Shapes.Select(s => (s.Layer, s.IsEraser, s.IsTextBox, s.Hidden, s.GroupId)).OrderBy(t => t.Layer).ThenBy(t => t.IsTextBox).ThenBy(t => t.IsEraser).ToList(); + Assert.Equal(expected, actual); + Assert.Equal(2, AllTreeItems(loaded.ShapeTreeItems).Count(i => i.IsGroup)); + + // Saving again writes the same file + Assert.Equal(text, loaded.GetAssaDrawFileText()); + } + + [Fact] + public void LoadingAFileWithoutGroups_GivesNoGroups() + { + var vm = MakeViewModel(SquareA, SquareB); + var loaded = MakeViewModel(); + + loaded.LoadFromText(vm.GetAssaDrawFileText()); + + Assert.DoesNotContain("SeGroups", vm.GetAssaDrawFileText()); + Assert.All(loaded.Shapes, s => Assert.Equal(0, s.GroupId)); + } + + [AvaloniaFact] + public void Marquee_SelectsTouchedShapes_WithTheirGroups_AltJustTheParts_CtrlAdds() + { + var (window, vm) = OpenWithGroup(); + try + { + // Touches A only: B comes along as A's group + Drag(window, vm, (50, 50), (150, 150)); + Assert.Equal([vm.Shapes[0], vm.Shapes[1]], vm.SelectedShapes); + Assert.True(vm.SelectedTreeItem?.IsGroup); + + // Alt: just A + Drag(window, vm, (50, 50), (150, 150), RawInputModifiers.Alt); + Assert.Same(vm.Shapes[0], vm.SelectedTreeItem?.Shape); + Assert.Empty(vm.SelectedShapes); + + // Ctrl adds C to it + Drag(window, vm, (550, 550), (650, 650), RawInputModifiers.Control); + Assert.Equal([vm.Shapes[0], vm.Shapes[2]], vm.SelectedShapes); + Assert.Equal(string.Format(Se.Language.Assa.DrawXShapes, 2), vm.SelectionText); + Assert.True(vm.DeleteShapeCommand.CanExecute(null)); + Assert.True(vm.GroupShapesCommand.CanExecute(null)); + + // A plain click on empty canvas clears it + Click(window, vm, 1000, 900); + Assert.Empty(vm.SelectedShapes); + Assert.Null(vm.SelectedTreeItem); + Assert.False(vm.DeleteShapeCommand.CanExecute(null)); + } + finally + { + window.Close(); + } + } + + [AvaloniaFact] + public void ShiftDrag_OnEmptyCanvas_StillPans() + { + var (window, vm) = Open(SquareA); + try + { + var before = ToWindow(window, vm.Canvas!, 0, 0); + var start = new Point(before.X + 900, before.Y + 600); + window.MouseMove(start, RawInputModifiers.Shift); + window.MouseDown(start, MouseButton.Left, RawInputModifiers.Shift); + window.MouseMove(new Point(start.X + 40, start.Y + 30), RawInputModifiers.LeftMouseButton | RawInputModifiers.Shift); + window.MouseUp(new Point(start.X + 40, start.Y + 30), MouseButton.Left, RawInputModifiers.Shift); + Dispatcher.UIThread.RunJobs(); + + var (x, _) = vm.Canvas!.ToCanvasPoint(start); + Assert.NotEqual(900 / vm.Canvas.ZoomFactor, x, 0); + Assert.Empty(vm.SelectedShapes); + } + finally + { + window.Close(); + } + } + + [AvaloniaFact] + public void CtrlClick_TogglesAShapeOrItsGroup() + { + var (window, vm) = OpenWithGroup(); + try + { + Click(window, vm, 650, 650); + Click(window, vm, 150, 150, RawInputModifiers.Control); + Assert.Equal([vm.Shapes[0], vm.Shapes[1], vm.Shapes[2]], vm.SelectedShapes); + + // The whole group goes out again, leaving C selected on its own + Click(window, vm, 350, 150, RawInputModifiers.Control); + Assert.Empty(vm.SelectedShapes); + Assert.Same(vm.Shapes[2], vm.SelectedTreeItem?.Shape); + + // Cmd on macOS + Click(window, vm, 150, 150, RawInputModifiers.Meta); + Assert.Equal(3, vm.SelectedShapes.Count); + + Click(window, vm, 650, 650, RawInputModifiers.Control); + Assert.True(vm.SelectedTreeItem?.IsGroup); + Assert.Equal([vm.Shapes[0], vm.Shapes[1]], vm.SelectedShapes); + } + finally + { + window.Close(); + } + } + + [Fact] + public void GroupTreeNode_SelectsTheWholeGroup_AndKeepsIt() + { + var vm = MakeViewModel(SquareA, SquareB, SquareC); + vm.SetSelection([vm.Shapes[0], vm.Shapes[1]]); + vm.GroupShapesCommand.Execute(null); + vm.SelectShape(vm.Shapes[2]); + Assert.Empty(vm.SelectedShapes); + + var groupItem = AllTreeItems(vm.ShapeTreeItems).Single(i => i.IsGroup); + vm.SelectedTreeItem = groupItem; + + Assert.Equal([vm.Shapes[0], vm.Shapes[1]], vm.SelectedShapes); + Assert.True(vm.DuplicateShapeCommand.CanExecute(null)); + + // Commands keep it: rotate rebuilds the tree, the group stays selected + vm.RotateShapeClockwiseCommand.Execute(null); + Assert.True(vm.SelectedTreeItem?.IsGroup); + Assert.Equal(2, vm.SelectedShapes.Count); + + // Arrow nudge moves the group + Press(vm, Key.Right); + Assert.Equal(2, vm.SelectedShapes.Count); + + // Visibility toggle hides the group's shapes + vm.ToggleItemVisibilityCommand.Execute(vm.SelectedTreeItem); + Assert.True(vm.Shapes[0].Hidden && vm.Shapes[1].Hidden); + Assert.False(vm.Shapes[2].Hidden); + } + + [AvaloniaFact] + public void Tree_CtrlClick_SelectsSeveralShapesAndGroups() + { + var (window, vm) = OpenWithGroup(); + try + { + var treeView = window.GetLogicalDescendants().OfType().Single(); + Assert.Equal(SelectionMode.Multiple, treeView.SelectionMode); + + var shapeC = AllTreeItems(vm.ShapeTreeItems).Single(i => i.Shape == vm.Shapes[2]); + var groupItem = AllTreeItems(vm.ShapeTreeItems).Single(i => i.IsGroup); + + ClickTreeItem(window, treeView, shapeC, RawInputModifiers.None); + Assert.Same(shapeC, vm.SelectedTreeItem); + Assert.Empty(vm.SelectedShapes); + + ClickTreeItem(window, treeView, groupItem, RawInputModifiers.Control); + Assert.Equal([vm.Shapes[0], vm.Shapes[1], vm.Shapes[2]], vm.SelectedShapes); + Assert.Equal(string.Format(Se.Language.Assa.DrawXShapes, 3), vm.SelectionText); + + // A plain click picks one again + ClickTreeItem(window, treeView, shapeC, RawInputModifiers.None); + Assert.Same(shapeC, vm.SelectedTreeItem); + Assert.Empty(vm.SelectedShapes); + } + finally + { + window.Close(); + } + } + + private static void ClickTreeItem(AssaDrawWindow window, TreeView treeView, ShapeTreeItem item, RawInputModifiers modifiers) + { + Dispatcher.UIThread.RunJobs(); + var container = treeView.GetVisualDescendantsOfType().First(c => c.DataContext == item); + var header = container.GetVisualDescendantsOfType().First(p => p.Name == "PART_HeaderPresenter"); + var point = header.TranslatePoint(new Point(header.Bounds.Width / 2, header.Bounds.Height / 2), window)!.Value; + window.MouseMove(point, modifiers); + window.MouseDown(point, MouseButton.Left, modifiers); + window.MouseUp(point, MouseButton.Left, modifiers); + Dispatcher.UIThread.RunJobs(); + } + + [Fact] + public void HitTest_PicksTheHigherLayer_EvenWhenItComesFirstInTheList() + { + var vm = MakeViewModel(SquareA, SquareA); + vm.Shapes[0].Layer = 5; + var canvas = new AssaDrawCanvas { Shapes = vm.Shapes }; + + Assert.Same(vm.Shapes[0], canvas.HitTestShape(150, 150)); + + // Same layer: the later shape is drawn on top + vm.Shapes[0].Layer = 0; + vm.Shapes[1].Layer = 0; + Assert.Same(vm.Shapes[1], canvas.HitTestShape(150, 150)); + } +} + +internal static class VisualTestExtensions +{ + public static IEnumerable GetVisualDescendantsOfType(this Visual visual) where T : Visual => + Avalonia.VisualTree.VisualExtensions.GetVisualDescendants(visual).OfType(); +} From 9acd6d41d702f53721e5c384d7198e40213ad962 Mon Sep 17 00:00:00 2001 From: niksedk Date: Fri, 16 Oct 2026 14:23:48 +0200 Subject: [PATCH 255/306] ASSA draw group tests: run on the UI thread They called Dispatcher.UIThread.RunJobs() from plain [Fact] tests, which throws when UI work is queued - passing alone, failing in the full run. Co-Authored-By: Claude Opus 5.5 --- tests/UI/Features/Assa/AssaDrawGroupTests.cs | 24 ++++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/tests/UI/Features/Assa/AssaDrawGroupTests.cs b/tests/UI/Features/Assa/AssaDrawGroupTests.cs index f865ddbbcc3..b53c16ce742 100644 --- a/tests/UI/Features/Assa/AssaDrawGroupTests.cs +++ b/tests/UI/Features/Assa/AssaDrawGroupTests.cs @@ -96,7 +96,7 @@ private static void Press(AssaDrawViewModel vm, Key key, KeyModifiers modifiers private static IEnumerable AllTreeItems(IEnumerable items) => items.SelectMany(i => new[] { i }.Concat(AllTreeItems(i.Children))); - [Fact] + [AvaloniaFact] public void InsertClipArt_IsOneGroup_SelectedAsAGroupNodeOfItsOwn() { var vm = MakeViewModel(SquareA); @@ -121,7 +121,7 @@ public void InsertClipArt_IsOneGroup_SelectedAsAGroupNodeOfItsOwn() Assert.Equal(string.Format(Se.Language.Assa.DrawXShapes, inserted.Count), vm.SelectionText[(vm.SelectionText.IndexOf('·') + 2)..]); } - [Fact] + [AvaloniaFact] public void InsertRing_OnOneLayer_IsAGroupNodeInsideItsLayer() { var vm = MakeViewModel(); @@ -139,7 +139,7 @@ public void InsertRing_OnOneLayer_IsAGroupNodeInsideItsLayer() Assert.Equal(string.Format(Se.Language.Assa.DrawShapeXDraw, 1), groupItem.Children[0].Name); } - [Fact] + [AvaloniaFact] public void SvgImport_IsOneGroup() { var vm = MakeViewModel(); @@ -219,7 +219,7 @@ public void Click_NearAGroupPartsPoint_SelectsTheGroup_NotThePoint() } } - [Fact] + [AvaloniaFact] public void CtrlG_Groups_CtrlShiftG_Ungroups_BothUndoable() { var vm = MakeViewModel(SquareA, SquareB, SquareC); @@ -254,7 +254,7 @@ public void CtrlG_Groups_CtrlShiftG_Ungroups_BothUndoable() Assert.All(vm.Shapes, s => Assert.Equal(0, s.GroupId)); } - [Fact] + [AvaloniaFact] public void CtrlQuote_TogglesTheGrid() { var vm = MakeViewModel(SquareA); @@ -271,7 +271,7 @@ public void CtrlQuote_TogglesTheGrid() } } - [Fact] + [AvaloniaFact] public void Ungroup_OnePart_LeavesNoOneShapeGroup() { var vm = MakeViewModel(SquareA, SquareB); @@ -285,7 +285,7 @@ public void Ungroup_OnePart_LeavesNoOneShapeGroup() Assert.DoesNotContain(AllTreeItems(vm.ShapeTreeItems), i => i.IsGroup); } - [Fact] + [AvaloniaFact] public void Duplicate_OfAGroup_GetsANewGroup_ACopiedPartGetsNone() { var vm = MakeViewModel(SquareA, SquareB, SquareC); @@ -310,7 +310,7 @@ public void Duplicate_OfAGroup_GetsANewGroup_ACopiedPartGetsNone() Assert.Equal(2, vm.Shapes.Count(s => s.GroupId == groupId)); } - [Fact] + [AvaloniaFact] public void Delete_OfAGroupNode_DeletesTheWholeGroup() { var vm = MakeViewModel(SquareA, SquareB, SquareC); @@ -323,7 +323,7 @@ public void Delete_OfAGroupNode_DeletesTheWholeGroup() Assert.Equal(600, remaining.Points[0].X); } - [Fact] + [AvaloniaFact] public void SaveAndLoad_KeepsGroups_OutputHasNoGroupData() { var vm = MakeViewModel(SquareA, SquareB, SquareC); @@ -357,7 +357,7 @@ public void SaveAndLoad_KeepsGroups_OutputHasNoGroupData() Assert.Equal(text, loaded.GetAssaDrawFileText()); } - [Fact] + [AvaloniaFact] public void LoadingAFileWithoutGroups_GivesNoGroups() { var vm = MakeViewModel(SquareA, SquareB); @@ -457,7 +457,7 @@ public void CtrlClick_TogglesAShapeOrItsGroup() } } - [Fact] + [AvaloniaFact] public void GroupTreeNode_SelectsTheWholeGroup_AndKeepsIt() { var vm = MakeViewModel(SquareA, SquareB, SquareC); @@ -530,7 +530,7 @@ private static void ClickTreeItem(AssaDrawWindow window, TreeView treeView, Shap Dispatcher.UIThread.RunJobs(); } - [Fact] + [AvaloniaFact] public void HitTest_PicksTheHigherLayer_EvenWhenItComesFirstInTheList() { var vm = MakeViewModel(SquareA, SquareA); From 1f22433282d3591aabdb58dea47940d92633e4b5 Mon Sep 17 00:00:00 2001 From: niksedk Date: Fri, 16 Oct 2026 14:26:55 +0200 Subject: [PATCH 256/306] ASSA draw: six more clip arts Rainbow, trophy, rocket, balloon, camera and party popper. Co-Authored-By: Claude Opus 5.5 --- src/ui/Assets/Languages/English.json | 234 +++++++++++++++++- src/ui/Features/Assa/AssaDraw/ShapeLibrary.cs | 12 + .../Config/Language/Assa/LanguageAssa.cs | 12 + .../UI/Features/Assa/AssaDrawClipArtTests.cs | 2 +- 4 files changed, 248 insertions(+), 12 deletions(-) diff --git a/src/ui/Assets/Languages/English.json b/src/ui/Assets/Languages/English.json index e85086e3f05..02cc7edd092 100644 --- a/src/ui/Assets/Languages/English.json +++ b/src/ui/Assets/Languages/English.json @@ -1285,7 +1285,9 @@ "maxLengthErrorTeletextColored": "Line {0}-{1}: 36 (not {2}) should be maximum characters for double height colored text", "maxLengthErrorTeletextWhite": "Line {0}-{1}: 37 (not {2}) should be maximum characters for double height white text", "maxLengthErrorTeletextSingleHeight": "Line {0}: 38 (not {1}) should be maximum characters for single height white text", - "discSequenceNumber": "Disc sequence number" + "discSequenceNumber": "Disc sequence number", + "programme": "Programme", + "discAndDisplay": "Disc and display" }, "import": { "importTimeCodes": "Import time codes...", @@ -1373,8 +1375,17 @@ "exportDvbTeletextSubtitleType": "Subtitle type", "exportDvbTeletextSubtitleTypeNormal": "Normal", "exportDvbTeletextSubtitleTypeHearingImpaired": "Hearing impaired", + "exportPacCodePage": "Choose PAC code page", + "exportPacSecondaryCodePage": "Secondary code page", "customTextFormatsDotDotDot": "_Custom text formats...", "plainTextDotDotDot": "_Plain text...", + "dialogueListDotDotDot": "Excel dialogue list (.xlsx)...", + "titleExportDialogueList": "Export Excel dialogue list", + "dialogueListLineBreaksInCell": "Keep line breaks inside the cell (off: join lines with \" | \")", + "dialogueListHeaderRow": "Header row", + "dialogueListFramesFormat": "hh:mm:ss:ff (frames)", + "dialogueListNoColumns": "Choose at least one column.", + "dialogueListEnd": "End", "customTextFormats": "Custom text formats", "titleExportCustomFormat": "Export custom text format", "editCustomFormat": "Edit custom text format", @@ -1392,6 +1403,8 @@ "addLineBetweenSubtitles": "Add line between subtitles", "titleExportDCinemaInteropPng": "D-Cinema interop/png", "titleExportDCinemaSmpte2014Png": "D-Cinema SMPTE 2014/png", + "titleExportDCinemaSmpte2014Mxf": "D-Cinema SMPTE 2014 (MXF)", + "titleExportDCinemaSmpte2014PngMxf": "D-Cinema SMPTE 2014/png (MXF)", "imageBasedSubtitleSaved": "Image-based subtitle saved", "fullFrameImage": "Full frame image", "fullFrameImageHint": "Make each image the size of the video frame, with the subtitle in its place", @@ -1549,7 +1562,13 @@ "fadeUpTime": "Fade up time", "fadeDownTime": "Fade down time", "zPosition": "Z-position", - "zPositionHelp": "Positive numbers move text away, negative numbers move text closer, if z-position is zero then it's 2D" + "zPositionHelp": "Positive numbers move text away, negative numbers move text closer, if z-position is zero then it's 2D", + "fontFile": "Font file", + "chooseFontFile": "Choose font file", + "fontFileMissing": "Choose the TrueType/OpenType font file to embed in the MXF track file.", + "reelDuration": "Reel duration", + "reelDurationHint": "HH:MM:SS:FF - the picture length, which every asset of a DCP reel must have. Empty = until the last subtitle.", + "reelDurationInvalid": "Reel duration must be empty or HH:MM:SS:FF." }, "webVtt": { "setStylesForSelectedLinesTitle": "Set WebVTT styles for selected lines", @@ -1694,6 +1713,8 @@ "hearingImpairedMusicSymbols": "Only music symbols", "hearingImpairedInterjections": "Interjections", "hearingImpairedSettingsHint": "Details come from Tools > Remove text for hearing impaired", + "selection": "Selection", + "hearingImpairedOther": "Other", "selectionNew": "New selection", "selectionAdd": "Add to selection", "selectionSubtract": "Subtract from selection", @@ -1798,6 +1819,7 @@ "engineError": "The AI engine could not be reached: {0}", "playCurrentLineHint": "Play the selected line in the video player and pause at its end (also double-click a line)", "editAfterHint": "Click the selected row's text to edit the fix before applying it (Enter saves, Escape cancels)", + "prompt": "Prompt", "template": "Template", "templatePlaceholder": "Choose a template to replace the prompt", "templateStandard": "Standard proofreading", @@ -2286,7 +2308,8 @@ "title": "Split subtitle", "numberOfEqualParts": "Number of equal parts", "saveSplitParts": "_Save split parts", - "xPartsSavedInFormatYToFolder": "{0} parts saved in format {1} to folder:" + "xPartsSavedInFormatYToFolder": "{0} parts saved in format {1} to folder:", + "splitBy": "Split by" }, "splitBreakLongLines": { "title": "Split/rebalance long lines", @@ -2573,7 +2596,10 @@ "oneBox": "One box", "logoInfo": "Pick a PNG image and drag it to position it on the video.", "noSubtitlesGenerateAnyway": "There are no subtitle lines to burn in.\n\nGenerate the video without subtitles?", - "noSubtitlesInXItemsGenerateAnyway": "{0} batch item(s) have no subtitle lines to burn in.\n\nGenerate those videos without subtitles?" + "noSubtitlesInXItemsGenerateAnyway": "{0} batch item(s) have no subtitle lines to burn in.\n\nGenerate those videos without subtitles?", + "subtitleStyle": "Subtitle style", + "videoOutput": "Video output", + "targetSize": "Target size" }, "videoTransparent": { "title": "Generate transparent video with subtitles" @@ -3037,6 +3063,7 @@ "outputLengthTextToSpeech": "Follows the generated speech - the music loops seamlessly and dips while someone talks", "addBackgroundMusic": "Add background music", "addBackgroundMusicHint": "Generate music locally with ACE-Step 1.5 and mix it under the speech - it dips while someone talks", + "music": "Music", "generatingBackgroundMusicDotDotDot": "Generating background music...", "mixingBackgroundMusicDotDotDot": "Mixing background music..." }, @@ -3127,6 +3154,10 @@ "cutVideoTransitionRadial": "Radial", "cutVideoTransitionPixelize": "Pixelize", "cutVideoTransitionBlur": "Blur", + "cutVideoSegments": "Segments", + "cutVideoTransitions": "Transitions", + "cutVideoOutput": "Output", + "cutVideoCutType": "Cut type", "importCurrentSubtitle": "Import current subtitle", "addRemoveEmbeddedSubtitlesTitle": "Add/remove embedded subtitles", "addRemoveEmbeddedSubtitlesMp4Title": "Add/remove embedded subtitles (MP4)", @@ -3143,6 +3174,7 @@ "embeddedTrackGeneratingVideo": "Generating video...", "mp4FilesFilter": "MP4 files", "embeddedTracksPickVideoHint": "Choose a video file, or drop one here, to see its subtitle tracks.", + "embeddedTracks": "Tracks", "viewMatroskaTrackX": "View Matroska track - {0}", "resolutionSeparator": "x", "openFromUrlTitle": "Open video file from URL", @@ -3156,6 +3188,10 @@ "openFromUrlDownloadSubtitles": "Download subtitles", "openFromUrlDownloadVideo": "Download video", "openFromUrlIncludeAutoGeneratedSubtitles": "Include auto-generated subtitles (original language)", + "openFromUrlCookiesFromBrowser": "Cookies from browser", + "openFromUrlCookiesFromBrowserHint": "Send the cookies of a browser where you are signed in to YouTube - needed when YouTube asks to confirm you are not a bot.", + "openFromUrlBotCheckHint": "YouTube wants to confirm you are not a bot. Sign in to YouTube in your browser, then pick that browser in \"Cookies from browser\" in the \"Open video file from URL\" window and try again (Firefox works best). Turning off a VPN can also help.", + "openFromUrlBotCheckHintWithBrowserX": "YouTube still wants to confirm you are not a bot. Make sure you are signed in to YouTube in {0}. Chrome and Edge on Windows often block reading their cookies - close the browser first, or try Firefox.", "assaStyleWillBeUsed": "Current ASSA style will be used\n\nChange subtitle format if\nyou want to set styles here", "pickOnlineSubtitleTitle": "Pick subtitle to download", "outputSettingsTitle": "Output settings", @@ -3574,6 +3610,7 @@ "timeCodeModeFrameNumbers": "Frame numbers", "frameModeSnapToVideoFrames": "Store times on video frames (not frames within each second)", "textBoxLimitNewLines": "Limit number of lines in subtitle text box", + "textBoxAutoWrapWhileTyping": "Auto-wrap while typing", "mpvOpenGl": "libmpv - OpenGL", "mpvSoftwareRendering": "libmpv - Software rendering (slow)", "mpvMetalRendering": "libmpv - Metal (experimental, needs restart)", @@ -3975,6 +4012,8 @@ "duplicatesFound": "The following duplicate shortcuts were found:", "copyTextToClipboard": "Copy text to clipboard (selected lines)", "copyTextFromOriginalToClipboard": "Copy text from original to clipboard (selected lines)", + "copyPlainTextToClipboard": "Copy plain text to clipboard (selected lines)", + "copyPlainTextFromOriginalToClipboard": "Copy plain text from original to clipboard (selected lines)", "assaDraw": "ASSA Draw", "assaGenerateProgressBar": "ASSA Generate progress bar", "assaGenerateBackgroundBox": "ASSA Generate background box", @@ -4072,7 +4111,13 @@ "doNotAskAgain": "Do not ask again (change in Manage plugins)", "applyPluginsTo": "Apply plugins to", "askEachTime": "Ask each time", - "allLines": "All lines" + "allLines": "All lines", + "runPlugin": "Run plugin", + "plugin": "Plugin", + "configurePlugin": "Configure...", + "noBatchPluginsInstalled": "No installed plugin supports batch convert.", + "batchPluginInfo": "The plugin runs on each file without showing its window, using its saved settings.", + "pluginXNotAvailable": "Plugin '{0}' is not installed, is disabled, or does not support batch convert." }, "help": { "aboutSubtitleEdit": "About Subtitle Edit", @@ -4254,16 +4299,20 @@ "alsoTrainItalic": "Also train italic", "trainingXLearnedYSkipped": "Training \"{0}\" - {1:#,##0} characters learned, {2:#,##0} skipped", "trainingDoneXLearned": "Training done - {0:#,##0} characters learned", - "selectAtLeastOneFont": "Please select at least one font" + "selectAtLeastOneFont": "Please select at least one font", + "nOcrZoomIn": "Zoom in", + "nOcrZoomOut": "Zoom out", + "nOcrLineAlgorithm": "Algorithm", + "nOcrLineAlgorithmHint": "Algorithm used by Auto-draw to generate foreground/background lines" }, "assa": { "assaDraw": "ASSA Draw", - "drawSelectTool": "Select (move points)", + "drawSelectTool": "Select and move shapes", "drawUseShapeForErase": "Use shape for erase (iclip)", - "drawLineTool": "Line Tool (F4)", - "drawBezierTool": "Bezier Curve (F5)", - "drawRectangleTool": "Rectangle (F6)", - "drawCircleTool": "Circle (F7)", + "drawLineTool": "Line tool", + "drawBezierTool": "Bezier curve", + "drawRectangleTool": "Rectangle", + "drawCircleTool": "Circle", "drawCloseShape": "Close Shape (F8/Enter)", "drawDeleteShape": "Delete Shape (Del)", "drawChangeLayer": "Change Layer", @@ -4309,6 +4358,49 @@ "drawCategoryArrows": "Arrows", "drawCategoryBasicShapes": "Basic shapes", "drawCategorySymbols": "Symbols", + "drawCategoryClipArt": "Clip art", + "drawClipArtRedHeart": "Red heart", + "drawClipArtAngerMark": "Anger mark", + "drawClipArtSweatDrop": "Sweat drop", + "drawClipArtSparkles": "Sparkles", + "drawClipArtBlush": "Blush", + "drawClipArtSurpriseLines": "Surprise lines", + "drawClipArtBamBurst": "Bam! burst", + "drawClipArtZzz": "Zzz", + "drawClipArtSun": "Sun", + "drawClipArtFlame": "Flame", + "drawClipArtLightBulb": "Light bulb", + "drawClipArtLightningBolt": "Lightning bolt", + "drawClipArtColoredMusicNotes": "Colored music notes", + "drawClipArtPhone": "Phone", + "drawClipArtQuestionMark": "Question mark", + "drawClipArtExclamationMark": "Exclamation mark", + "drawClipArtGreenCheck": "Green check", + "drawClipArtRedCross": "Red cross", + "drawClipArtThumbsUp": "Thumbs up", + "drawClipArtGoldStar": "Gold star", + "drawClipArtBrokenHeart": "Broken heart", + "drawClipArtGhost": "Ghost", + "drawClipArtSkull": "Skull", + "drawClipArtSnowflake": "Snowflake", + "drawClipArtMoonAndStars": "Moon and stars", + "drawClipArtRainCloud": "Rain cloud", + "drawClipArtBomb": "Bomb", + "drawClipArtGift": "Gift", + "drawClipArtCrown": "Crown", + "drawClipArtClock": "Clock", + "drawClipArtClapperboard": "Clapperboard", + "drawClipArtMicrophone": "Microphone", + "drawClipArtWarningSign": "Warning sign", + "drawClipArtNoEntry": "No entry", + "drawClipArtPopcorn": "Popcorn", + "drawClipArtCoffeeCup": "Coffee cup", + "drawClipArtRainbow": "Rainbow", + "drawClipArtTrophy": "Trophy", + "drawClipArtRocket": "Rocket", + "drawClipArtBalloon": "Balloon", + "drawClipArtCamera": "Camera", + "drawClipArtPartyPopper": "Party popper", "drawCategoryMyShapes": "My shapes", "drawShapeSpeechBubble": "Speech bubble", "drawShapeSpeechBubbleRight": "Speech bubble, tail right", @@ -4318,10 +4410,16 @@ "drawShapeThoughtBubble": "Thought bubble", "drawShapeShoutBubble": "Shout bubble", "drawShapeCaptionBox": "Caption box", + "drawShapeSpeechBubbleSide": "Speech bubble, tail left side", + "drawShapeRoundBubble": "Round bubble", + "drawShapePillBubble": "Pill bubble", + "drawShapeScreamBubble": "Scream bubble", "drawShapeArrow": "Arrow", "drawShapeDoubleArrow": "Double arrow", "drawShapeChevron": "Chevron", "drawShapeCurvedArrow": "Curved arrow", + "drawShapeRepeatArrow": "Repeat arrow", + "drawShapeUTurnArrow": "U-turn arrow", "drawShapeRoundedRectangle": "Rounded rectangle", "drawShapeTriangle": "Triangle", "drawShapeDiamond": "Diamond", @@ -4332,6 +4430,8 @@ "drawShapeRing": "Ring", "drawShapePlus": "Plus", "drawShapeBanner": "Banner", + "drawShapeHalfCircle": "Half circle", + "drawShapeTeardrop": "Teardrop", "drawShapeHeart": "Heart", "drawShapeCheck": "Check mark", "drawShapeMusicNote": "Music note", @@ -4340,6 +4440,10 @@ "drawShapeCloud": "Cloud", "drawShapeMoon": "Moon", "drawShapeBadge": "Badge", + "drawShapeSpeaker": "Speaker", + "drawShapeCross": "Cross", + "drawShapeSmiley": "Smiley", + "drawShapeSadSmiley": "Sad smiley", "drawSize": "Size", "drawLayer": "Layer", "drawBackgroundVideoFrame": "Video frame (current position)", @@ -4356,6 +4460,23 @@ "drawRotateCounterClockwise": "Rotate 90° counter-clockwise", "drawSvgImages": "SVG images", "drawSvgNoShapes": "No filled shapes or strokes found in the SVG file.", + "drawTextTool": "Text tool", + "drawDefaultText": "Text", + "drawShrinkToFit": "Shrink to fit", + "drawTextColor": "Text color", + "drawTextBox": "Text box", + "drawDiscardChangesTitle": "Discard changes?", + "drawDiscardChanges": "The drawing has been changed.\nDiscard the changes?", + "drawColorPickerToolName": "Color picker", + "drawOpenDrawing": "Open ASSA drawing", + "drawSaveDrawing": "Save ASSA drawing", + "drawDrawingFiles": "ASSA drawing files", + "drawAssFiles": "ASS files", + "drawPositionXY": "Position: {0}, {1}", + "drawXShapes": "{0} shapes", + "drawXPoints": "{0} points", + "drawShapeXDraw": "Shape {0} (draw)", + "drawShapeXErase": "Shape {0} (erase)", "progressBarTitle": "ASSA progress bar", "progressBarSettings": "Progress bar", "mouseOverColor": "Mouse-over color", @@ -4369,6 +4490,25 @@ "progressBarXAdjustment": "X adjustment", "progressBarYAdjustment": "Y adjustment", "progressBarTextAlignment": "Text alignment", + "progressBarStyleCutCorners": "Cut corners", + "progressBarStyleArrow": "Arrow", + "progressBarStyleSlanted": "Slanted", + "progressBarStyleSegments": "Segments", + "progressBarStyleRoundedSegments": "Rounded segments", + "progressBarStyleDots": "Dots", + "progressBarStyleDiamonds": "Diamonds", + "progressBarStyleChevrons": "Chevrons", + "progressBarStyleStripes": "Stripes", + "progressBarStyleWave": "Wave", + "progressBarStyleZigzag": "Zigzag", + "progressBarStyleTapered": "Tapered", + "progressBarStyleRuler": "Ruler", + "progressBarStyleDoubleLine": "Double line", + "progressBarStyleEqualizer": "Equalizer", + "progressBarStyleStars": "Stars", + "progressBarPlayhead": "Playhead", + "progressBarEdgeMargin": "Edge margin", + "progressBarSplitter": "Splitter", "fontCollectorTitle": "Font collector", "fontCollectorUsedIn": "Used in", "fontCollectorFontFiles": "Font file(s)", @@ -4427,8 +4567,29 @@ "backgroundBoxTornPaperDouble": "Torn paper (top & bottom)", "backgroundBoxStarburst": "Starburst", "backgroundBoxScroll": "Scroll / Parchment", + "backgroundBoxPill": "Pill", + "backgroundBoxCutCorners": "Cut corners", + "backgroundBoxSlanted": "Slanted", + "backgroundBoxTicket": "Ticket", + "backgroundBoxSpeechBubble": "Speech bubble", + "backgroundBoxRibbon": "Ribbon", + "backgroundBoxBrushStroke": "Brush stroke", + "backgroundBoxCornerBrackets": "Corner brackets", + "backgroundBoxUnderline": "Underline bar", + "backgroundBoxFrame": "Frame", + "backgroundBoxArrowTag": "Arrow tag", + "backgroundBoxSideBar": "Side bar", "stylesInFile": "Styles in file", "stylesSaved": "Styles saved", + "stylesSummary": "{0} styles in file · {1} saved", + "stylesTransform": "Transform", + "stylesEditing": "Editing", + "stylesPreviewSampleText": "Sample text", + "stylesPreviewSampleTextHint": "Sample text - \\N for a new line", + "stylesScale": "Scale %", + "stylesPreviewDark": "Dark", + "stylesPreviewChecker": "Checker", + "stylesPreviewLight": "Light", "allCategories": "[All]", "newCategory": "New category...", "renameCategory": "Rename category...", @@ -4463,6 +4624,8 @@ "takeUsagesFromDotDotDot": "Take usages from...", "replaceStyleWithDotDotDot": "Replace style with...", "noAttachmentsFound": "No attachments found in selected ASSA file.", + "attachmentsDropHint": "Drop font or image files here to attach them", + "attachmentsSummary": "{0} fonts · {1} graphics · {2}", "deleteStyleQuestion": "Delete style?", "deleteStylesQuestion": "Delete styles?", "deleteXStylesQuestion": "Do you want to delete {0} styles?", @@ -4488,12 +4651,32 @@ "spinIn": "Spin in", "setPosition": "Set position", "rotation": "Rotation", + "setPositionTopLeft": "Top left", + "setPositionTopCenter": "Top center", + "setPositionTopRight": "Top right", + "setPositionMiddleLeft": "Middle left", + "setPositionCenter": "Center", + "setPositionMiddleRight": "Middle right", + "setPositionBottomLeft": "Bottom left", + "setPositionBottomCenter": "Bottom center", + "setPositionBottomRight": "Bottom right", + "setPositionCustom": "Custom", + "setPositionReset": "Reset to the line's position", + "setPositionResetRotation": "Reset rotation", + "setPositionGuides": "Guides", + "setPositionSafeArea": "Safe area", + "setPositionCenterLines": "Center lines", + "setPositionSnapToGuides": "Snap to guides", + "setPositionNudgeHint": "Arrow keys nudge 1 px, Shift 10 px", "scaleX": "Scale X", "scaleY": "Scale Y", "spacing": "Spacing", "angle": "Angle", "imageColorPicker": "Image color picker", "copyColorAsHextoClipboard": "Copy color as hex to clipboard", + "imageColorPickerHint": "Click the frame to pick a color", + "copyColorAsAssaToClipboard": "Copy color as ASSA (&HBBGGRR&) to clipboard", + "copyColorAsAssaTagToClipboard": "Copy color as ASSA \\c tag to clipboard", "generatingBackgroundBoxXOfY": "Generating background box {0} of {1}...", "applyAdvancedEffectTitle": "Apply advanced effect", "chooseEffect": "Choose effect", @@ -4575,6 +4758,35 @@ "advancedEffectLowerThird": "Lower third", "advancedEffectLowerThirdDescription": "Name and role banner bottom-left with an accent bar sliding in — interview/documentary style (first line = name, following lines = role)", "advancedEffectLowerThirdAccentColor": "Accent color", + "advancedEffectCategoryWords": "Words & karaoke", + "advancedEffectCategoryInOut": "In & out", + "advancedEffectCategoryTextFx": "Text effects", + "advancedEffectCategoryScenes": "Scenes", + "advancedEffectCategoryTitles": "Titles & credits", + "advancedEffectSearchWatermark": "Search effects...", + "advancedEffectNoMatches": "No effects match the search", + "advancedEffectCustomize": "Customize", + "advancedEffectNoSettings": "This effect has no settings - it works as is.", + "advancedEffectAmount": "Amount", + "advancedEffectPreviewNoVideo": "Sample preview - open a video to see the real rendering", + "advancedEffectHighlightBox": "Highlight box", + "advancedEffectHighlightBoxDescription": "A colored box jumps from word to word as the line is spoken - CapCut-style captions", + "advancedEffectSingleWordPop": "Single word pop", + "advancedEffectSingleWordPopDescription": "Shows one big word at a time, popping in as it is spoken - short-video caption style", + "advancedEffectBoldCaptions": "Bold captions", + "advancedEffectBoldCaptionsDescription": "Bold captions with a thick outline - the spoken word lights up in color and grows", + "advancedEffectBoxColor": "Box color", + "advancedEffectHighlightColor": "Highlight color", + "advancedEffectUpperCase": "Uppercase", + "advancedEffectTextSizePercent": "Text size (%)", + "advancedEffectSmoothFill": "Smooth fill", + "advancedEffectSmoothFillDescription": "A highlight color sweeps smoothly through each word as it is spoken", + "advancedEffectBlurInWords": "Blur-in words", + "advancedEffectBlurInWordsDescription": "Words appear one by one as they are spoken - each sharpens from a blur and rises into place", + "advancedEffectRollingCaptions": "Rolling captions", + "advancedEffectRollingCaptionsDescription": "Each line slides up into place and pushes the previous line up, like a live chat or teleprompter", + "advancedEffectFillColor": "Fill color", + "advancedEffectRise": "Rise from baseline", "overrideTagsHistory": "Override tags history" }, "about": { diff --git a/src/ui/Features/Assa/AssaDraw/ShapeLibrary.cs b/src/ui/Features/Assa/AssaDraw/ShapeLibrary.cs index ae24de26157..cd74b372264 100644 --- a/src/ui/Features/Assa/AssaDraw/ShapeLibrary.cs +++ b/src/ui/Features/Assa/AssaDraw/ShapeLibrary.cs @@ -507,6 +507,18 @@ private static List MakeBuiltIn() """"""), Clip(l.DrawClipArtCoffeeCup, null, """"""), + Clip(l.DrawClipArtRainbow, null, + """"""), + Clip(l.DrawClipArtTrophy, null, + """"""), + Clip(l.DrawClipArtRocket, null, + """"""), + Clip(l.DrawClipArtBalloon, null, + """"""), + Clip(l.DrawClipArtCamera, null, + """"""), + Clip(l.DrawClipArtPartyPopper, null, + """"""), ]; } } diff --git a/src/ui/Logic/Config/Language/Assa/LanguageAssa.cs b/src/ui/Logic/Config/Language/Assa/LanguageAssa.cs index 41cb93e63a6..0d9e80d9031 100644 --- a/src/ui/Logic/Config/Language/Assa/LanguageAssa.cs +++ b/src/ui/Logic/Config/Language/Assa/LanguageAssa.cs @@ -92,6 +92,12 @@ public class LanguageAssa public string DrawClipArtNoEntry { get; set; } public string DrawClipArtPopcorn { get; set; } public string DrawClipArtCoffeeCup { get; set; } + public string DrawClipArtRainbow { get; set; } + public string DrawClipArtTrophy { get; set; } + public string DrawClipArtRocket { get; set; } + public string DrawClipArtBalloon { get; set; } + public string DrawClipArtCamera { get; set; } + public string DrawClipArtPartyPopper { get; set; } public string DrawCategoryMyShapes { get; set; } public string DrawShapeSpeechBubble { get; set; } public string DrawShapeSpeechBubbleRight { get; set; } @@ -583,6 +589,12 @@ public LanguageAssa() DrawClipArtNoEntry = "No entry"; DrawClipArtPopcorn = "Popcorn"; DrawClipArtCoffeeCup = "Coffee cup"; + DrawClipArtRainbow = "Rainbow"; + DrawClipArtTrophy = "Trophy"; + DrawClipArtRocket = "Rocket"; + DrawClipArtBalloon = "Balloon"; + DrawClipArtCamera = "Camera"; + DrawClipArtPartyPopper = "Party popper"; DrawCategoryMyShapes = "My shapes"; DrawShapeSpeechBubble = "Speech bubble"; DrawShapeSpeechBubbleRight = "Speech bubble, tail right"; diff --git a/tests/UI/Features/Assa/AssaDrawClipArtTests.cs b/tests/UI/Features/Assa/AssaDrawClipArtTests.cs index c362cf1a689..e1cb0f0cb7d 100644 --- a/tests/UI/Features/Assa/AssaDrawClipArtTests.cs +++ b/tests/UI/Features/Assa/AssaDrawClipArtTests.cs @@ -19,7 +19,7 @@ public class AssaDrawClipArtTests [Fact] public void ClipArt_KeepsItsColors_InAFewLayers() { - Assert.Equal(36, ClipArt.Count); + Assert.Equal(42, ClipArt.Count); foreach (var item in ClipArt) { Assert.True(item.HasOwnColors, item.Name); From a0f0ba6c4187c95f20c1f0d66cdb382a51fac001 Mon Sep 17 00:00:00 2001 From: niksedk Date: Fri, 16 Oct 2026 12:48:53 +0200 Subject: [PATCH 257/306] Add Voxtral TTS (audio.cpp) engine Mistral's Voxtral 4B TTS (2603) through the shared audio.cpp runtime, family voxtral_tts. Fixed-voice engine next to KugelAudio: 20 presets embedded in the GGUF (5 English, plus native voices for French, German, Spanish, Italian, Portuguese, Dutch, Hindi and Arabic), picked per request with voice_id; no language option and no cloning (the open checkpoint lacks the codec encoder). An unknown voice id falls back to casual_male, since the server rejects unknown presets. Q8_0 (4.7 GB) is the default, BF16 (8.0 GB) the alternative, both from audio-cpp/audio.cpp-gguf with size and SHA-256 checks. The weights and voices are CC BY-NC 4.0, so the model download sits behind the licence window like Fish and Breeze. On an M4 (Metal, Q8_0) it runs about 1.35x slower than real time with ~9.5 GB resident; English, French, German, Spanish, Italian, Portuguese and Dutch round-tripped through Parakeet v3 word for word. Co-Authored-By: Claude Opus 5.5 --- src/ui/DependencyInjectionExtensions.cs | 1 + .../AudioCppTtsSettingsViewModel.cs | 14 + .../DownloadTts/DownloadTtsViewModel.cs | 57 ++ .../TextToSpeech/Engines/TtsEngineCatalog.cs | 5 + .../Engines/VoxtralTtsAudioCpp.cs | 673 ++++++++++++++++++ .../TextToSpeech/TextToSpeechViewModel.cs | 36 + .../Video/TextToSpeech/TextToSpeechWindow.cs | 2 + .../Video/TextToSpeech/TtsEngineInstaller.cs | 14 + .../TextToSpeech/TtsEngineSettingsDialog.cs | 5 + .../Video/TextToSpeech/TtsVoiceInstaller.cs | 1 + .../TextToSpeech/Voices/VoxtralTtsVoice.cs | 48 ++ src/ui/Logic/Config/SeVideoTextToSpeech.cs | 7 + src/ui/Logic/Download/DownloadHashManager.cs | 18 + .../VoxtralTtsAudioCppDownloadService.cs | 122 ++++ .../Engines/AudioCppRuntimeBuildInfoTests.cs | 16 +- .../Engines/VoxtralTtsAudioCppTests.cs | 95 +++ 16 files changed, 1113 insertions(+), 1 deletion(-) create mode 100644 src/ui/Features/Video/TextToSpeech/Engines/VoxtralTtsAudioCpp.cs create mode 100644 src/ui/Features/Video/TextToSpeech/Voices/VoxtralTtsVoice.cs create mode 100644 src/ui/Logic/Download/VoxtralTtsAudioCppDownloadService.cs create mode 100644 tests/UI/Features/Video/TextToSpeech/Engines/VoxtralTtsAudioCppTests.cs diff --git a/src/ui/DependencyInjectionExtensions.cs b/src/ui/DependencyInjectionExtensions.cs index 897fcc810fd..df450ab576c 100644 --- a/src/ui/DependencyInjectionExtensions.cs +++ b/src/ui/DependencyInjectionExtensions.cs @@ -327,6 +327,7 @@ public static void AddSubtitleEditServices(this IServiceCollection collection) collection.AddHttpClientWithProxy(); collection.AddHttpClientWithProxy(); collection.AddHttpClientWithProxy(); + collection.AddHttpClientWithProxy(); collection.AddHttpClientWithProxy(); collection.AddHttpClientWithProxy(); collection.AddHttpClientWithProxy(); diff --git a/src/ui/Features/Video/TextToSpeech/AudioCppTtsSettings/AudioCppTtsSettingsViewModel.cs b/src/ui/Features/Video/TextToSpeech/AudioCppTtsSettings/AudioCppTtsSettingsViewModel.cs index f281eb8635f..11cee2b1c5d 100644 --- a/src/ui/Features/Video/TextToSpeech/AudioCppTtsSettings/AudioCppTtsSettingsViewModel.cs +++ b/src/ui/Features/Video/TextToSpeech/AudioCppTtsSettings/AudioCppTtsSettingsViewModel.cs @@ -115,6 +115,20 @@ public static class AudioCppTtsSettingsAdapters IsLicenseAccepted: () => true, LicenseDefinition: null, StartDownloadModels: (vm, modelKey) => vm.StartDownloadKugelAudioAudioCppModels(modelKey)); + + public static AudioCppTtsSettingsAdapter VoxtralTts { get; } = new( + EngineName: "Voxtral TTS (audio.cpp)", + FamilyName: VoxtralTtsAudioCpp.FamilyName, + Description: new VoxtralTtsAudioCpp().Description, + ModelKeyDefault: VoxtralTtsAudioCpp.ModelKeyQ8_0, + ModelKeyAlt: VoxtralTtsAudioCpp.ModelKeyBf16, + ResolveModelKey: VoxtralTtsAudioCpp.ResolveModelKey, + AreModelsInstalled: VoxtralTtsAudioCpp.AreModelsInstalled, + GetModelsFolder: VoxtralTtsAudioCpp.GetSetModelsFolder, + GetVoicesFolder: null, + IsLicenseAccepted: VoxtralTtsAudioCpp.IsLicenseAccepted, + LicenseDefinition: VoxtralTtsAudioCpp.LicenseDefinition, + StartDownloadModels: (vm, modelKey) => vm.StartDownloadVoxtralTtsAudioCppModels(modelKey)); } public partial class AudioCppTtsSettingsViewModel : ObservableObject diff --git a/src/ui/Features/Video/TextToSpeech/DownloadTts/DownloadTtsViewModel.cs b/src/ui/Features/Video/TextToSpeech/DownloadTts/DownloadTtsViewModel.cs index 76ce8788aa9..44df5ba33cb 100644 --- a/src/ui/Features/Video/TextToSpeech/DownloadTts/DownloadTtsViewModel.cs +++ b/src/ui/Features/Video/TextToSpeech/DownloadTts/DownloadTtsViewModel.cs @@ -118,6 +118,8 @@ public partial class DownloadTtsViewModel : ObservableObject private Task? _downloadTaskBreezeTtsAudioCppVoices; private readonly IKugelAudioAudioCppDownloadService _kugelAudioAudioCppDownloadService; private Task? _downloadTaskKugelAudioAudioCppModels; + private readonly IVoxtralTtsAudioCppDownloadService _voxtralTtsAudioCppDownloadService; + private Task? _downloadTaskVoxtralTtsAudioCppModels; private readonly ISidonAudioCppDownloadService _sidonAudioCppDownloadService; private Task? _downloadTaskSidonAudioCppModel; private readonly ICosyVoice3CrispAsrDownloadService _cosyVoice3CrispAsrDownloadService; @@ -166,6 +168,7 @@ public DownloadTtsViewModel(ITtsDownloadService ttsDownloadService, IZipUnpacker IFireRedTts3AudioCppDownloadService fireRedTts3AudioCppDownloadService, IBreezeTtsAudioCppDownloadService breezeTtsAudioCppDownloadService, IKugelAudioAudioCppDownloadService kugelAudioAudioCppDownloadService, + IVoxtralTtsAudioCppDownloadService voxtralTtsAudioCppDownloadService, ISidonAudioCppDownloadService sidonAudioCppDownloadService, ICosyVoice3CrispAsrDownloadService cosyVoice3CrispAsrDownloadService, IF5TtsCrispAsrDownloadService f5TtsCrispAsrDownloadService, @@ -198,6 +201,7 @@ public DownloadTtsViewModel(ITtsDownloadService ttsDownloadService, IZipUnpacker _breezeTtsAudioCppDownloadService = breezeTtsAudioCppDownloadService; _downloadStreamBreezeTtsAudioCppVoices = new MemoryStream(); _kugelAudioAudioCppDownloadService = kugelAudioAudioCppDownloadService; + _voxtralTtsAudioCppDownloadService = voxtralTtsAudioCppDownloadService; _sidonAudioCppDownloadService = sidonAudioCppDownloadService; _cosyVoice3CrispAsrDownloadService = cosyVoice3CrispAsrDownloadService; _f5TtsCrispAsrDownloadService = f5TtsCrispAsrDownloadService; @@ -1770,6 +1774,36 @@ private void OnTimerOnElapsed(object? sender, ElapsedEventArgs args) return; } + if (_downloadTaskVoxtralTtsAudioCppModels is { IsCompletedSuccessfully: true }) + { + _timer.Stop(); + _downloadTaskVoxtralTtsAudioCppModels = null; + + // Model-only: the 20 preset voices are inside the GGUF and the engine does not + // clone, so there is no reference-voice pack to fetch afterwards. + OkPressed = true; + Close(); + return; + } + + if (_downloadTaskVoxtralTtsAudioCppModels is { IsFaulted: true }) + { + _timer.Stop(); + var ex = _downloadTaskVoxtralTtsAudioCppModels.Exception?.InnerException ?? _downloadTaskVoxtralTtsAudioCppModels.Exception; + if (ex is OperationCanceledException) + { + ProgressText = Se.Language.General.DownloadCanceled; + Close(); + } + else + { + ProgressText = Se.Language.General.DownloadFailed; + Error = ex?.Message ?? Se.Language.General.UnknownError; + } + + return; + } + if (_downloadTaskSupertonicCrispAsrModels is { IsCompletedSuccessfully: true }) { _timer.Stop(); @@ -3015,6 +3049,29 @@ public void StartDownloadKugelAudioAudioCppModels(string? modelKey = null) _kugelAudioAudioCppDownloadService.DownloadModels(resolved, downloadProgress, titleProgress, _cancellationTokenSource.Token); } + public void StartDownloadVoxtralTtsAudioCppModels(string? modelKey = null) + { + var resolved = VoxtralTtsAudioCpp.ResolveModelKey(modelKey); + var fileName = VoxtralTtsAudioCpp.GetModelFileName(resolved); + TitleText = string.Format(Se.Language.General.DownloadingX, $"Voxtral TTS model ({resolved}): {fileName}"); + + var downloadProgress = new Progress(number => + { + var percentage = (int)Math.Round(number * 100.0, MidpointRounding.AwayFromZero); + var pctString = percentage.ToString(CultureInfo.InvariantCulture); + ProgressValue = percentage; + ProgressText = string.Format(Se.Language.General.DownloadingXPercent, pctString); + }); + + var titleProgress = new Action(title => + { + Dispatcher.UIThread.Post(() => TitleText = title); + }); + + _downloadTaskVoxtralTtsAudioCppModels = + _voxtralTtsAudioCppDownloadService.DownloadModels(resolved, downloadProgress, titleProgress, _cancellationTokenSource.Token); + } + public void StartDownloadSidonAudioCppModel() { TitleText = string.Format(Se.Language.General.DownloadingX, $"{SidonAudioCpp.DisplayName} model: {SidonAudioCpp.ModelFileName}"); diff --git a/src/ui/Features/Video/TextToSpeech/Engines/TtsEngineCatalog.cs b/src/ui/Features/Video/TextToSpeech/Engines/TtsEngineCatalog.cs index bd7d6f86370..f2899b2bc51 100644 --- a/src/ui/Features/Video/TextToSpeech/Engines/TtsEngineCatalog.cs +++ b/src/ui/Features/Video/TextToSpeech/Engines/TtsEngineCatalog.cs @@ -48,6 +48,11 @@ public static List CreateAll(ITtsDownloadService ttsDownloadService) // (3-4x real time on an M4) and ~9 GB resident, but MIT-licensed. engines.Add(new KugelAudioAudioCpp()); + // Voxtral 4B TTS (audio.cpp) — 20 preset voices and no cloning, nine languages (English, + // French, German, Spanish, Italian, Portuguese, Dutch, Hindi, Arabic). ~1.35x real time + // on an M4 and ~9.5 GB resident; CC BY-NC 4.0, so it shows the licence gate first. + engines.Add(new VoxtralTtsAudioCpp()); + // Zonos (CrispASR) — not a cloning engine: the zonos backend has no speaker encoder, so // it speaks with one fixed default voice (CrispASR v0.8.34 dropped its voice-cloning // cap). Move it back into CreateVoiceCloningEngines when upstream ports the encoder. diff --git a/src/ui/Features/Video/TextToSpeech/Engines/VoxtralTtsAudioCpp.cs b/src/ui/Features/Video/TextToSpeech/Engines/VoxtralTtsAudioCpp.cs new file mode 100644 index 00000000000..e34d6157990 --- /dev/null +++ b/src/ui/Features/Video/TextToSpeech/Engines/VoxtralTtsAudioCpp.cs @@ -0,0 +1,673 @@ +using Nikse.SubtitleEdit.Features.Video.TextToSpeech.ModelLicense; +using Nikse.SubtitleEdit.Features.Video.TextToSpeech.Voices; +using Nikse.SubtitleEdit.Logic.Config; +using Nikse.SubtitleEdit.Logic.Download; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Net.Sockets; +using System.Text; +using System.Text.Json; +using System.Threading; +using System.Threading.Tasks; + +namespace Nikse.SubtitleEdit.Features.Video.TextToSpeech.Engines; + +/// +/// Mistral's Voxtral 4B TTS (2603) run through the audio.cpp runtime — the same pure C++/ggml +/// install the other audio.cpp engines use (see ), so the binaries +/// are downloaded once and shared. 24 kHz output. +/// +/// No cloning: the open checkpoint lacks the codec encoder, so only the 20 preset voices embedded +/// in the GGUF are available, picked per request with the voice_id option (switching voice +/// does not restart the server). Five general presets are English; the rest are one or two native +/// voices each for French, German, Spanish, Italian, Portuguese, Dutch, Hindi and Arabic. There is +/// no language option — the model reads the language from the text, and the preset sets the accent. +/// +/// On an M4 (Metal, Q8_0) it ran about 1.35x slower than real time with ~9.5 GB resident, and +/// English, French, German, Spanish, Italian, Portuguese and Dutch round-tripped through +/// speech-to-text word for word. Languages outside the nine come out as accented gibberish. +/// +/// Licence note: the audio.cpp binaries are Apache-2.0, but the weights and the preset voices are +/// CC BY-NC 4.0, so the engine sits behind the first-run licence gate like Fish and Breeze. +/// +public class VoxtralTtsAudioCpp : ITtsEngine +{ + public string Name => "Voxtral TTS (audio.cpp)"; + public string Description => "Mistral Voxtral preset voices in 9 languages, via audio.cpp"; + public bool HasLanguageParameter => false; + public bool HasApiKey => false; + public bool HasRegion => false; + public bool HasModel => true; + public bool HasKeyFile => false; + public bool SupportsVoiceCloning => false; + public bool SupportsPerLineVoiceCloning => false; + + // Q8_0 is the default: upstream validated it with the embedded presets (which stay BF16 in + // both files), and it round-tripped word for word in seven languages at 3.3 GB less than BF16. + public const string ModelKeyQ8_0 = "Q8_0 (~4.7 GB)"; + public const string ModelKeyBf16 = "BF16 (~8.0 GB)"; + public const string DefaultModelKey = ModelKeyQ8_0; + + public const string ModelQ8_0FileName = "voxtral-4b-tts-2603-q8_0.gguf"; + public const string ModelBf16FileName = "voxtral-4b-tts-2603-bf16.gguf"; + + /// Family name audio.cpp registers Voxtral TTS under. + public const string FamilyName = "voxtral_tts"; + + /// Model id used in the generated server config and in each request body. + private const string ServerModelId = "voxtraltts"; + + /// The preset ids the GGUF embeds, in display order (English first). + public static readonly string[] PresetVoices = + [ + "casual_female", "casual_male", "cheerful_female", "neutral_female", "neutral_male", + "fr_female", "fr_male", + "de_female", "de_male", + "es_female", "es_male", + "it_female", "it_male", + "pt_female", "pt_male", + "nl_female", "nl_male", + "hi_female", "hi_male", + "ar_male", + ]; + + public const string DefaultVoice = "casual_male"; + + /// + /// Exact byte sizes on the audio-cpp/audio.cpp-gguf HuggingFace repo. A truncated GGUF is + /// the single most common failure here — a download that dies partway leaves a file the + /// loader rejects with "GGUF tensor data range is out of bounds", so size is checked + /// before the server is ever started. Same guard as . + /// + private static readonly Dictionary ExpectedFileSizes = new(StringComparer.OrdinalIgnoreCase) + { + [ModelQ8_0FileName] = 4722918208L, + [ModelBf16FileName] = 8030847808L, + }; + + /// + /// Bumping this re-prompts every user with the licence window. Keep it in step with the + /// summary text in . + /// + public const string LicenseVersion = "cc-by-nc-4.0"; + + public static bool IsLicenseAccepted() => + string.Equals( + Se.Settings.Video.TextToSpeech.VoxtralTtsAudioCppLicenseAccepted, + LicenseVersion, + StringComparison.Ordinal); + + public static void AcceptLicense() => + Se.Settings.Video.TextToSpeech.VoxtralTtsAudioCppLicenseAccepted = LicenseVersion; + + /// What the first-run licence gate shows. + public static ModelLicenseDefinition LicenseDefinition { get; } = new( + DialogTitle: "Voxtral TTS - model license", + Header: "The Voxtral TTS model has its own license", + Intro: "The audio.cpp engine is Apache-2.0, but the Voxtral 4B TTS model weights and its " + + "preset voices are licensed by Mistral AI under Creative Commons " + + "Attribution-NonCommercial 4.0 (CC BY-NC 4.0). Please read the main points before downloading.", + SummaryPoints: new[] + { + "The model and its preset voices are free to use for non-commercial purposes only.", + "Commercial use needs a separate license from Mistral AI. That includes dubbing or voice-over for a client or for a fee, and audio for monetized videos, ads or sponsored content.", + "If you share the model or adapted versions of it, you must give credit to Mistral AI, link to the license and say if you changed it.", + "Do not use the voices to impersonate a real person or to mislead listeners.", + }, + LicenseUrl: "https://creativecommons.org/licenses/by-nc/4.0/", + ModelPageUrl: "https://huggingface.co/mistralai/Voxtral-4B-TTS-2603", + AcceptCheckBoxText: "I have read and accept the CC BY-NC 4.0 license", + Accept: AcceptLicense); + + public static string ResolveModelKey(string? modelKey) + { + if (string.IsNullOrEmpty(modelKey)) + { + var saved = Se.Settings.Video.TextToSpeech.VoxtralTtsAudioCppModel; + return string.IsNullOrEmpty(saved) ? DefaultModelKey : ResolveModelKey(saved); + } + + return modelKey == ModelKeyBf16 ? ModelKeyBf16 : ModelKeyQ8_0; + } + + public static string GetModelFileName(string? modelKey) => + ResolveModelKey(modelKey) == ModelKeyBf16 ? ModelBf16FileName : ModelQ8_0FileName; + + private static readonly HttpClient HttpClient = new() + { + Timeout = TimeSpan.FromMinutes(10), + }; + + private static readonly SemaphoreSlim ServerLock = new(1, 1); + private static Process? _serverProcess; + private static int _serverPort; + private static string? _serverLaunchCommand; + // Only the model and the backend are baked into the running server — voice is per request. + private static string? _serverModelKey; + private static string? _serverBackend; + private static string? _serverExeStamp; + private static bool _processExitHooked; + private static readonly StringBuilder _serverLog = new(); + + private static string ServerBaseUrl => $"http://127.0.0.1:{_serverPort}"; + + public Task IsInstalled(string? region) => Task.FromResult(File.Exists(AudioCppRuntime.GetServerExecutable())); + + public override string ToString() => Name; + + /// + /// Per-engine working folder under TextToSpeech (synthesis output). The audio.cpp binaries + /// are shared (); this is not. + /// + public static string GetSetFolder() + { + if (!Directory.Exists(Se.TextToSpeechFolder)) + { + Directory.CreateDirectory(Se.TextToSpeechFolder); + } + + var folder = Path.Combine(Se.TextToSpeechFolder, "VoxtralTtsAudioCpp"); + if (!Directory.Exists(folder)) + { + Directory.CreateDirectory(folder); + } + + return folder; + } + + /// + /// The GGUF gets its own folder under the shared models root: + /// <data>/audio.cpp/models/Voxtral-4B-TTS-2603-GGUF/. + /// + public static string GetSetModelsFolder() + { + var folder = Path.Combine(AudioCppRuntime.GetSetEngineFolder(), "models", "Voxtral-4B-TTS-2603-GGUF"); + if (!Directory.Exists(folder)) + { + Directory.CreateDirectory(folder); + } + + return folder; + } + + public static string GetModelPath(string? modelKey = null) => + Path.Combine(GetSetModelsFolder(), GetModelFileName(modelKey)); + + public static bool IsValidLocalModelFile(string path, string fileName) + { + if (!File.Exists(path)) + { + return false; + } + + if (!ExpectedFileSizes.TryGetValue(fileName, out var expected)) + { + return true; + } + + try + { + var info = new FileInfo(path); + + // FileInfo.Length reports the size of the *link* for a symlink, not of its target, + // so a symlinked GGUF would look truncated — and the caller deletes files it + // considers truncated. Resolve to the final target before measuring. + var length = info.ResolveLinkTarget(returnFinalTarget: true) is FileInfo target + ? target.Length + : info.Length; + + return length == expected; + } + catch + { + return false; + } + } + + public static bool AreModelsInstalled(string? modelKey = null) => + IsValidLocalModelFile(GetModelPath(modelKey), GetModelFileName(modelKey)); + + public static DownloadHashManager.UpdateStatus GetEngineUpdateStatus() + { + var exe = AudioCppRuntime.GetServerExecutable(); + if (!File.Exists(exe)) + { + return DownloadHashManager.UpdateStatus.Unknown; + } + + var folder = Path.GetDirectoryName(exe); + return string.IsNullOrEmpty(folder) + ? DownloadHashManager.UpdateStatus.Unknown + : DownloadHashManager.GetSidecarStatus(folder); + } + + public Task GetVoices(string language) + { + // No language parameter, so all presets are listed; each one names its language. + return Task.FromResult(PresetVoices.Select(v => new Voice(new VoxtralTtsVoice(v))).ToArray()); + } + + public bool IsVoiceInstalled(Voice voice) => true; + + public Task GetRegions() => Task.FromResult(Array.Empty()); + + public Task GetModels() => Task.FromResult(new[] { ModelKeyQ8_0, ModelKeyBf16 }); + + // No language option: the model infers the language from the text. + public Task GetLanguages(Voice voice, string? model) => + Task.FromResult(Array.Empty()); + + public Task RefreshVoices(string language, CancellationToken cancellationToken) => + GetVoices(language); + + /// + /// The preset to send for . A voice object left over from another + /// engine, or an unknown id, falls back to rather than being + /// passed on: the server answers an unknown voice_id with HTTP 500. + /// + internal static string ResolveVoiceId(Voice? voice) + { + var id = (voice?.EngineVoice as VoxtralTtsVoice)?.Voice; + return PresetVoices.FirstOrDefault(p => string.Equals(p, id, StringComparison.OrdinalIgnoreCase)) + ?? DefaultVoice; + } + + /// audio.cpp's OpenAI-style speech payload; the preset goes in as voice_id. + internal static Dictionary BuildSpeechPayload(string text, string voiceId) => new() + { + ["model"] = ServerModelId, + ["input"] = text, + ["response_format"] = "wav", + ["options"] = new Dictionary + { + ["voice_id"] = voiceId, + }, + }; + + public async Task Speak( + string text, + string outputFolder, + Voice voice, + TtsLanguage? language, + string? region, + string? model, + CancellationToken cancellationToken) + { + var voiceId = ResolveVoiceId(voice); + var modelKey = ResolveModelKey(model); + await EnsureServerRunningAsync(modelKey, cancellationToken); + + var outputFileName = Path.Combine(TtsOutputFolder.Resolve(outputFolder, GetSetFolder), Guid.NewGuid() + ".wav"); + + var body = JsonSerializer.Serialize(BuildSpeechPayload(text, voiceId)); + Se.WriteToolsLog($"Voxtral TTS (audio.cpp): POST {ServerBaseUrl}/v1/audio/speech (voice={voiceId}, textLen={text.Length})"); + + using var content = new StringContent(body, Encoding.UTF8, "application/json"); + HttpResponseMessage response; + try + { + response = await HttpClient.PostAsync($"{ServerBaseUrl}/v1/audio/speech", content, cancellationToken); + } + catch (HttpRequestException ex) + { + var serverLog = SnapshotServerLog(); + var launchCommand = _serverLaunchCommand; + var died = _serverProcess?.HasExited == true; + if (died) + { + StopServerInternal(); + } + + var failMsg = $"Voxtral TTS (audio.cpp) request failed — Voice: {voiceId}, Text: {text}, " + + $"RequestJson: {body}, ServerExited: {died}, ServerLog: {serverLog}" + + LaunchCmdSuffix(launchCommand); + Se.LogError(ex, failMsg); + Se.WriteToolsLog(failMsg); + + throw new InvalidOperationException( + (died + ? "Voxtral TTS (audio.cpp) — the audiocpp_server process crashed during synthesis." + : "Voxtral TTS (audio.cpp) request failed — the connection to audiocpp_server was dropped.") + + (string.IsNullOrEmpty(serverLog) ? string.Empty : $"{Environment.NewLine}Server log:{Environment.NewLine}{serverLog}") + + LaunchCmdSuffix(launchCommand), + ex); + } + + using (response) + { + if (!response.IsSuccessStatusCode) + { + var errorBody = await SafeReadErrorAsync(response, cancellationToken); + var serverLog = SnapshotServerLog(); + var launchCommand = _serverLaunchCommand; + var errMsg = $"Voxtral TTS (audio.cpp) server error {(int)response.StatusCode} {response.StatusCode} — " + + $"Voice: {voiceId}, Text: {text}, RequestJson: {body}, " + + $"ResponseBody: {errorBody}, ServerLog: {serverLog}" + + LaunchCmdSuffix(launchCommand); + Se.LogError(errMsg); + Se.WriteToolsLog(errMsg); + throw new InvalidOperationException( + $"Voxtral TTS (audio.cpp) synthesis failed ({(int)response.StatusCode}): {errorBody}" + + (string.IsNullOrEmpty(serverLog) ? string.Empty : $"{Environment.NewLine}Server log:{Environment.NewLine}{serverLog}") + + LaunchCmdSuffix(launchCommand)); + } + + await using var fileStream = File.Create(outputFileName); + await using var contentStream = await response.Content.ReadAsStreamAsync(cancellationToken); + await contentStream.CopyToAsync(fileStream, cancellationToken); + } + return new TtsResult(outputFileName, text); + } + + private static async Task EnsureServerRunningAsync(string modelKey, CancellationToken ct) + { + var backend = AudioCppRuntime.GetBackend(); + var exeStamp = AudioCppRuntime.GetServerExecutableStamp(); + + if (_serverProcess is { HasExited: false } && _serverPort != 0 + && string.Equals(_serverModelKey, modelKey, StringComparison.OrdinalIgnoreCase) + && string.Equals(_serverBackend, backend, StringComparison.OrdinalIgnoreCase) + && string.Equals(_serverExeStamp, exeStamp, StringComparison.Ordinal)) + { + return; + } + + await ServerLock.WaitAsync(ct); + try + { + if (_serverProcess is { HasExited: false } && _serverPort != 0 + && string.Equals(_serverModelKey, modelKey, StringComparison.OrdinalIgnoreCase) + && string.Equals(_serverBackend, backend, StringComparison.OrdinalIgnoreCase) + && string.Equals(_serverExeStamp, exeStamp, StringComparison.Ordinal)) + { + return; + } + + if (_serverProcess != null) + { + StopServerInternal(); + } + + var exe = AudioCppRuntime.GetServerExecutable(); + if (!File.Exists(exe)) + { + throw new FileNotFoundException( + "audio.cpp server not found. Download the Voxtral TTS engine first.", exe); + } + + var modelFileName = GetModelFileName(modelKey); + var modelPath = GetModelPath(modelKey); + if (!IsValidLocalModelFile(modelPath, modelFileName)) + { + throw new FileNotFoundException( + File.Exists(modelPath) + ? $"The Voxtral TTS model file is incomplete: {modelPath}. Delete it and download again." + : $"The Voxtral TTS model file is missing: {modelPath}", + modelPath); + } + + var port = FindFreeLoopbackPort(); + var configPath = WriteServerConfig(port, backend, modelKey); + + var psi = new ProcessStartInfo + { + WorkingDirectory = Path.GetDirectoryName(exe) ?? AudioCppRuntime.GetSetEngineFolder(), + FileName = exe, + UseShellExecute = false, + CreateNoWindow = true, + RedirectStandardError = true, + RedirectStandardOutput = true, + // The server writes UTF-8. Without these the reader decodes it in the OS default + // codepage, and non-ASCII text in the captured log - the line being synthesised, + // upstream's em dashes - reaches bug reports as mojibake (#13572). + StandardOutputEncoding = Encoding.UTF8, + StandardErrorEncoding = Encoding.UTF8, + }; + psi.ArgumentList.Add("--config"); + psi.ArgumentList.Add(configPath); + + var process = Process.Start(psi) + ?? throw new InvalidOperationException("Failed to start audiocpp_server (voxtral_tts)"); + + var launchCommand = FormatLaunchCommand(exe, psi.ArgumentList); + _serverLaunchCommand = launchCommand; + Se.WriteToolsLog($"Voxtral TTS (audio.cpp) server starting — PID: {process.Id}, Cmd: {launchCommand}"); + + lock (_serverLog) _serverLog.Clear(); + process.ErrorDataReceived += (_, e) => + { + if (e.Data != null) lock (_serverLog) _serverLog.AppendLine(e.Data); + }; + process.OutputDataReceived += (_, e) => + { + if (e.Data != null) lock (_serverLog) _serverLog.AppendLine(e.Data); + }; + process.BeginErrorReadLine(); + process.BeginOutputReadLine(); + + _serverProcess = process; + _serverPort = port; + _serverModelKey = modelKey; + _serverBackend = backend; + _serverExeStamp = AudioCppRuntime.GetServerExecutableStamp(); + HookProcessExitOnce(); + + // The config uses lazy_load, so /health answers within a second or two — the 4.7-8 GB + // model is only read on the first synthesis request. A long deadline here would just + // hide a crash-at-startup (e.g. a Vulkan build on a box with no Vulkan driver, which + // dies in the loader with 0xC0000135 before printing anything). + var deadline = DateTime.UtcNow.AddMinutes(2); + while (DateTime.UtcNow < deadline) + { + ct.ThrowIfCancellationRequested(); + if (process.HasExited) + { + var tail = SnapshotServerLog(); + var exitCode = process.ExitCode; + var exitedLaunchCommand = _serverLaunchCommand; + _serverProcess = null; + _serverPort = 0; + _serverLaunchCommand = null; + _serverModelKey = null; + _serverBackend = null; + _serverExeStamp = null; + throw new InvalidOperationException( + $"audiocpp_server exited during startup (code {exitCode}). " + + AudioCppRuntime.DescribeStartupExit(exitCode, backend) + + $" Output: {tail}" + + LaunchCmdSuffix(exitedLaunchCommand)); + } + + if (await ProbeHealthAsync(port, TimeSpan.FromSeconds(2), ct)) + { + return; + } + + await Task.Delay(TimeSpan.FromSeconds(1), ct); + } + + var lastOutput = SnapshotServerLog(); + var timeoutLaunchCommand = _serverLaunchCommand; + StopServerInternal(); + throw new TimeoutException( + $"audiocpp_server did not report healthy within 2 minutes. Last output: {lastOutput}" + + LaunchCmdSuffix(timeoutLaunchCommand)); + } + finally + { + ServerLock.Release(); + } + } + + /// + /// Writes the audio.cpp server config next to the binary. lazy_load keeps startup instant; + /// the model is read on first use and then stays resident until the server is stopped. + /// + /// + /// The model entry names the GGUF file, not its folder. Given a folder, audio.cpp picks + /// model.gguf or the sole *.gguf and refuses a folder holding several - so a user who had + /// downloaded both quantizations could not start the server until one was moved out of + /// sight (#14480). The file path is unambiguous, and auxiliary paths still resolve against + /// its parent. + /// + private static string WriteServerConfig(int port, string backend, string modelKey) + { + var config = new Dictionary + { + ["host"] = "127.0.0.1", + ["port"] = port, + ["backend"] = backend, + ["threads"] = Math.Max(1, Math.Min(8, Environment.ProcessorCount / 2)), + ["lazy_load"] = true, + ["models"] = new[] + { + new Dictionary + { + ["id"] = ServerModelId, + ["family"] = FamilyName, + ["path"] = GetModelPath(modelKey), + // Preset voices only: the open checkpoint has no codec encoder, so no clone task. + ["task"] = "tts", + ["mode"] = "offline", + }, + }, + }; + + var configPath = Path.Combine(AudioCppRuntime.GetSetEngineFolder(), "voxtral-tts-server.json"); + File.WriteAllText( + configPath, + JsonSerializer.Serialize(config, new JsonSerializerOptions { WriteIndented = true }), + Encoding.UTF8); + + Se.WriteToolsLog($"Voxtral TTS (audio.cpp): server config written to {configPath} (model={modelKey}, backend={backend})"); + return configPath; + } + + private static string FormatLaunchCommand(string exe, System.Collections.ObjectModel.Collection args) + { + static string Quote(string s) => + !string.IsNullOrEmpty(s) && s.IndexOfAny(new[] { ' ', '\t' }) >= 0 + ? "\"" + s.Replace("\"", "\\\"") + "\"" + : s; + + var sb = new StringBuilder(Quote(exe)); + foreach (var a in args) + { + sb.Append(' ').Append(Quote(a)); + } + + return sb.ToString(); + } + + private static string LaunchCmdSuffix(string? launchCommand) => + string.IsNullOrEmpty(launchCommand) + ? string.Empty + : $"{Environment.NewLine}Launch command: {launchCommand}"; + + private static async Task SafeReadErrorAsync(HttpResponseMessage response, CancellationToken ct) + { + try + { + return await response.Content.ReadAsStringAsync(ct); + } + catch (Exception ex) + { + return $""; + } + } + + private static string SnapshotServerLog() + { + lock (_serverLog) + { + var s = _serverLog.ToString().TrimEnd(); + return s.Length > 2000 ? s[^2000..] : s; + } + } + + private static async Task ProbeHealthAsync(int port, TimeSpan timeout, CancellationToken ct) + { + try + { + using var cts = CancellationTokenSource.CreateLinkedTokenSource(ct); + cts.CancelAfter(timeout); + using var resp = await HttpClient.GetAsync($"http://127.0.0.1:{port}/health", cts.Token); + return resp.IsSuccessStatusCode; + } + catch + { + return false; + } + } + + private static int FindFreeLoopbackPort() + { + var listener = new TcpListener(IPAddress.Loopback, 0); + listener.Start(); + try + { + return ((IPEndPoint)listener.LocalEndpoint).Port; + } + finally + { + listener.Stop(); + } + } + + private static void HookProcessExitOnce() + { + if (_processExitHooked) + { + return; + } + + _processExitHooked = true; + AppDomain.CurrentDomain.ProcessExit += (_, _) => StopServerInternal(); + } + + /// + /// Stop the audio.cpp server if running, releasing the loaded model's working set. + /// audio.cpp never unloads a model on its own once loaded, so this is the only way the + /// memory comes back. + /// + public static void StopServer() => StopServerInternal(); + + private static void StopServerInternal() + { + var p = _serverProcess; + _serverProcess = null; + _serverPort = 0; + _serverLaunchCommand = null; + _serverModelKey = null; + _serverBackend = null; + _serverExeStamp = null; + if (p == null) + { + return; + } + + try + { + if (!p.HasExited) + { + p.Kill(entireProcessTree: true); + p.WaitForExit(2000); + } + } + catch + { + // best effort + } + finally + { + p.Dispose(); + } + } + + public bool ImportVoice(string fileName) => false; +} diff --git a/src/ui/Features/Video/TextToSpeech/TextToSpeechViewModel.cs b/src/ui/Features/Video/TextToSpeech/TextToSpeechViewModel.cs index f1c18329c01..c7ea17c2fb8 100644 --- a/src/ui/Features/Video/TextToSpeech/TextToSpeechViewModel.cs +++ b/src/ui/Features/Video/TextToSpeech/TextToSpeechViewModel.cs @@ -425,6 +425,14 @@ private void SaveSettings() Se.Settings.Video.TextToSpeech.KugelAudioAudioCppVoice = kugelVoice.Voice; } } + else if (SelectedEngine is VoxtralTtsAudioCpp) + { + Se.Settings.Video.TextToSpeech.VoxtralTtsAudioCppModel = SelectedModel ?? VoxtralTtsAudioCpp.DefaultModelKey; + if (SelectedVoice?.EngineVoice is VoxtralTtsVoice voxtralVoice && !string.IsNullOrEmpty(voxtralVoice.Voice)) + { + Se.Settings.Video.TextToSpeech.VoxtralTtsAudioCppVoice = voxtralVoice.Voice; + } + } else if (SelectedEngine is CosyVoice3CrispAsr) { Se.Settings.Video.TextToSpeech.CosyVoice3CrispAsrModel = SelectedModel ?? CosyVoice3CrispAsr.DefaultModelKey; @@ -1521,6 +1529,10 @@ private static void StopOtherCrispAsrServers(ITtsEngine? keepAlive) { KugelAudioAudioCpp.StopServer(); } + if (keepAlive is not VoxtralTtsAudioCpp) + { + VoxtralTtsAudioCpp.StopServer(); + } if (keepAlive is not CosyVoice3CrispAsr) { CosyVoice3CrispAsr.StopServer(); @@ -1856,6 +1868,9 @@ private async Task RedownloadSelectedModel(ITtsEngine engine) case KugelAudioAudioCpp: await _windowService.ShowDialogAsync(Window!, vm => vm.StartDownloadKugelAudioAudioCppModels(KugelAudioAudioCpp.ResolveModelKey(SelectedModel))); break; + case VoxtralTtsAudioCpp: + await _windowService.ShowDialogAsync(Window!, vm => vm.StartDownloadVoxtralTtsAudioCppModels(VoxtralTtsAudioCpp.ResolveModelKey(SelectedModel))); + break; case CosyVoice3CrispAsr: await _windowService.ShowDialogAsync(Window!, vm => vm.StartDownloadCosyVoice3CrispAsrModels(CosyVoice3CrispAsr.ResolveModelKey(SelectedModel))); break; @@ -1956,6 +1971,9 @@ public DownloadDotStatus GetModelDotStatus(ITtsEngine? engine, string? modelKey) KugelAudioAudioCpp => KugelAudioAudioCpp.AreModelsInstalled(modelKey) ? DownloadDotStatus.UpToDate : DownloadDotStatus.NotInstalled, + VoxtralTtsAudioCpp => VoxtralTtsAudioCpp.AreModelsInstalled(modelKey) + ? DownloadDotStatus.UpToDate + : DownloadDotStatus.NotInstalled, CosyVoice3CrispAsr => CosyVoice3CrispAsr.AreModelsInstalled(modelKey) ? DownloadDotStatus.UpToDate : DownloadDotStatus.NotInstalled, @@ -5257,6 +5275,24 @@ internal void SelectedEngineChanged(object? sender, SelectionChangedEventArgs e) IsEngineSettingsVisible = true; IsModelDownloadVisible = true; } + else if (SelectedEngine is VoxtralTtsAudioCpp) + { + SelectedModel = Models.FirstOrDefault(p => p == Se.Settings.Video.TextToSpeech.VoxtralTtsAudioCppModel); + if (string.IsNullOrEmpty(SelectedModel)) + { + SelectedModel = Models.FirstOrDefault(); + } + + var savedVoice = Se.Settings.Video.TextToSpeech.VoxtralTtsAudioCppVoice; + var match = Voices.FirstOrDefault(v => v.EngineVoice is VoxtralTtsVoice vv && vv.Voice == savedVoice); + if (match != null) + { + SelectedVoice = match; + } + + IsEngineSettingsVisible = true; + IsModelDownloadVisible = true; + } else if (SelectedEngine is SupertonicCrispAsr) { // One fixed GGUF (no model dropdown); the settings dialog carries the speed. diff --git a/src/ui/Features/Video/TextToSpeech/TextToSpeechWindow.cs b/src/ui/Features/Video/TextToSpeech/TextToSpeechWindow.cs index 7eecf6ed94f..aa699a12e07 100644 --- a/src/ui/Features/Video/TextToSpeech/TextToSpeechWindow.cs +++ b/src/ui/Features/Video/TextToSpeech/TextToSpeechWindow.cs @@ -231,6 +231,8 @@ private static DownloadDotStatus GetTtsEngineDotStatus(ITtsEngine engine) return StatusDots.From(engine.IsInstalled(null).Result, BreezeTtsAudioCpp.GetEngineUpdateStatus()); case KugelAudioAudioCpp: return StatusDots.From(engine.IsInstalled(null).Result, KugelAudioAudioCpp.GetEngineUpdateStatus()); + case VoxtralTtsAudioCpp: + return StatusDots.From(engine.IsInstalled(null).Result, VoxtralTtsAudioCpp.GetEngineUpdateStatus()); case CosyVoice3CrispAsr: return StatusDots.From(engine.IsInstalled(null).Result, CosyVoice3CrispAsr.GetEngineUpdateStatus()); case F5TtsCrispAsr: diff --git a/src/ui/Features/Video/TextToSpeech/TtsEngineInstaller.cs b/src/ui/Features/Video/TextToSpeech/TtsEngineInstaller.cs index 820caf2ae31..ca35c904d6a 100644 --- a/src/ui/Features/Video/TextToSpeech/TtsEngineInstaller.cs +++ b/src/ui/Features/Video/TextToSpeech/TtsEngineInstaller.cs @@ -544,6 +544,20 @@ await Dispatcher.UIThread.InvokeAsync(async () => startDownloadModels: (vm, key) => vm.StartDownloadKugelAudioAudioCppModels(key)); } + if (engine is VoxtralTtsAudioCpp) + { + // CC BY-NC 4.0 weights and voices: licence gate first, like Fish and Breeze. + return await EnsureAudioCppEngineWithLicense( + window, windowService, refreshVoices, + engineDisplayName: "Voxtral TTS", + requiredFamily: VoxtralTtsAudioCpp.FamilyName, + licenseDefinition: VoxtralTtsAudioCpp.LicenseDefinition, + isLicenseAccepted: VoxtralTtsAudioCpp.IsLicenseAccepted, + modelKey: VoxtralTtsAudioCpp.ResolveModelKey(model), + areModelsInstalled: VoxtralTtsAudioCpp.AreModelsInstalled, + startDownloadModels: (vm, key) => vm.StartDownloadVoxtralTtsAudioCppModels(key)); + } + if (engine is ZonosTtsCrispAsr) { if (!await TtsVoiceInstaller.EnsureCrispAsrForZonos(window, windowService, forceRedownload: false)) diff --git a/src/ui/Features/Video/TextToSpeech/TtsEngineSettingsDialog.cs b/src/ui/Features/Video/TextToSpeech/TtsEngineSettingsDialog.cs index 3cea46e439e..239bf28951f 100644 --- a/src/ui/Features/Video/TextToSpeech/TtsEngineSettingsDialog.cs +++ b/src/ui/Features/Video/TextToSpeech/TtsEngineSettingsDialog.cs @@ -49,6 +49,7 @@ FishTtsAudioCpp or BreezeTtsAudioCpp or FireRedTts3AudioCpp or KugelAudioAudioCpp or + VoxtralTtsAudioCpp or IndexTtsCrispAsr or DotsTtsCrispAsr or Confucius4TtsCrispAsr or @@ -107,6 +108,10 @@ public static async Task ShowAsync(ITtsEngine? engine, Window window, IWindowSer { await windowService.ShowDialogAsync(window, vm => vm.Initialize(AudioCppTtsSettingsAdapters.KugelAudio)); } + else if (engine is VoxtralTtsAudioCpp) + { + await windowService.ShowDialogAsync(window, vm => vm.Initialize(AudioCppTtsSettingsAdapters.VoxtralTts)); + } else if (engine is IndexTtsCrispAsr) { await windowService.ShowDialogAsync(window, vm => vm.Initialize()); diff --git a/src/ui/Features/Video/TextToSpeech/TtsVoiceInstaller.cs b/src/ui/Features/Video/TextToSpeech/TtsVoiceInstaller.cs index e5fcb11e31b..0c969ddf761 100644 --- a/src/ui/Features/Video/TextToSpeech/TtsVoiceInstaller.cs +++ b/src/ui/Features/Video/TextToSpeech/TtsVoiceInstaller.cs @@ -377,6 +377,7 @@ await MessageBox.Show( BreezeTtsAudioCpp.StopServer(); FireRedTts3AudioCpp.StopServer(); KugelAudioAudioCpp.StopServer(); + VoxtralTtsAudioCpp.StopServer(); var dlResult = await windowService.ShowDialogAsync( window, vm => vm.StartDownloadIndexTts25AudioCppEngine(backend)); diff --git a/src/ui/Features/Video/TextToSpeech/Voices/VoxtralTtsVoice.cs b/src/ui/Features/Video/TextToSpeech/Voices/VoxtralTtsVoice.cs new file mode 100644 index 00000000000..f7b324a618b --- /dev/null +++ b/src/ui/Features/Video/TextToSpeech/Voices/VoxtralTtsVoice.cs @@ -0,0 +1,48 @@ +namespace Nikse.SubtitleEdit.Features.Video.TextToSpeech.Voices; + +public class VoxtralTtsVoice +{ + // Voxtral 4B TTS preset id ("casual_male", "fr_female", ...). The 20 voices are embedded in + // the GGUF; the open checkpoint cannot clone, so this is the only voice handle. + public string Voice { get; set; } + + public override string ToString() + { + return GetDisplayName(Voice); + } + + public VoxtralTtsVoice() + { + Voice = string.Empty; + } + + public VoxtralTtsVoice(string voice) + { + Voice = voice; + } + + public static string GetDisplayName(string id) => id switch + { + "casual_female" => "English - casual female", + "casual_male" => "English - casual male", + "cheerful_female" => "English - cheerful female", + "neutral_female" => "English - neutral female", + "neutral_male" => "English - neutral male", + "fr_female" => "French - female", + "fr_male" => "French - male", + "de_female" => "German - female", + "de_male" => "German - male", + "es_female" => "Spanish - female", + "es_male" => "Spanish - male", + "it_female" => "Italian - female", + "it_male" => "Italian - male", + "pt_female" => "Portuguese - female", + "pt_male" => "Portuguese - male", + "nl_female" => "Dutch - female", + "nl_male" => "Dutch - male", + "hi_female" => "Hindi - female", + "hi_male" => "Hindi - male", + "ar_male" => "Arabic - male", + _ => id ?? string.Empty, + }; +} diff --git a/src/ui/Logic/Config/SeVideoTextToSpeech.cs b/src/ui/Logic/Config/SeVideoTextToSpeech.cs index 0be8f83e267..3831800f6df 100644 --- a/src/ui/Logic/Config/SeVideoTextToSpeech.cs +++ b/src/ui/Logic/Config/SeVideoTextToSpeech.cs @@ -106,6 +106,10 @@ public class SeVideoTextToSpeech // Holds the accepted BreezeBlue licence version; see BreezeTtsAudioCpp.LicenseVersion. public string BreezeTtsAudioCppLicenseAccepted { get; set; } public string KugelAudioAudioCppVoice { get; set; } + public string VoxtralTtsAudioCppModel { get; set; } + public string VoxtralTtsAudioCppVoice { get; set; } + // Holds the accepted CC BY-NC licence version; see VoxtralTtsAudioCpp.LicenseVersion. + public string VoxtralTtsAudioCppLicenseAccepted { get; set; } public string CosyVoice3CrispAsrModel { get; set; } public double CosyVoice3CrispAsrSpeed { get; set; } // Display name of the picked CosyVoice3 target language ("Auto" = plain zero-shot cloning). @@ -277,6 +281,9 @@ public SeVideoTextToSpeech() BreezeTtsAudioCppInstruction = string.Empty; BreezeTtsAudioCppLicenseAccepted = string.Empty; KugelAudioAudioCppVoice = "default"; + VoxtralTtsAudioCppModel = "Q8_0 (~4.7 GB)"; + VoxtralTtsAudioCppVoice = "casual_male"; + VoxtralTtsAudioCppLicenseAccepted = string.Empty; CosyVoice3CrispAsrModel = "Q4_K (~1.6 GB total)"; CosyVoice3CrispAsrSpeed = 1.0; CosyVoice3CrispAsrLanguage = string.Empty; diff --git a/src/ui/Logic/Download/DownloadHashManager.cs b/src/ui/Logic/Download/DownloadHashManager.cs index 9d81e3b97fc..865289257d9 100644 --- a/src/ui/Logic/Download/DownloadHashManager.cs +++ b/src/ui/Logic/Download/DownloadHashManager.cs @@ -349,6 +349,14 @@ public static class KugelAudioAudioCpp public const string ModelQ8_0 = "KugelAudioAudioCpp.ModelQ8_0"; } + public static class VoxtralTtsAudioCpp + { + // SHA-256 of the Voxtral-4B-TTS-2603 GGUFs on audio-cpp/audio.cpp-gguf (HF LFS oid). + // The engine binaries are the shared audio.cpp archives keyed under IndexTts25AudioCpp. + public const string ModelQ8_0 = "VoxtralTtsAudioCpp.ModelQ8_0"; + public const string ModelBf16 = "VoxtralTtsAudioCpp.ModelBf16"; + } + public static class ZonosTtsCrispAsr { // SHA-256 of the Zonos-v0.1 transformer (Q8_0) and the shared DAC 44 kHz codec. @@ -2554,6 +2562,16 @@ public static class WhisperX "9cff404eeafed33ac419f855002fa5fc6f6ef719f160b6fe333095b9cfb5be52", // kugelaudio-0-open-q8_0.gguf }, + // Voxtral-4B-TTS-2603 weights, from audio-cpp/audio.cpp-gguf (HF LFS oid). + [VoxtralTtsAudioCpp.ModelQ8_0] = new[] + { + "46eca2bfc66c89c415b66995d3544e91dea3320c93765ec0596f5c616354972d", // voxtral-4b-tts-2603-q8_0.gguf + }, + [VoxtralTtsAudioCpp.ModelBf16] = new[] + { + "883f53bc9e54412110649f2725819c2453d871cc0f02d9dde1aaec495960081d", // voxtral-4b-tts-2603-bf16.gguf + }, + // audio.cpp engine archives we build in SubtitleEdit/support-files // (audiocpp-indextts25-2026-10-16). Newest first — index 0 is the pinned release, // so anything older prompts an update instead of being treated as current. diff --git a/src/ui/Logic/Download/VoxtralTtsAudioCppDownloadService.cs b/src/ui/Logic/Download/VoxtralTtsAudioCppDownloadService.cs new file mode 100644 index 00000000000..5f20dffcd06 --- /dev/null +++ b/src/ui/Logic/Download/VoxtralTtsAudioCppDownloadService.cs @@ -0,0 +1,122 @@ +using Nikse.SubtitleEdit.Features.Video.TextToSpeech.Engines; +using Nikse.SubtitleEdit.UiLogic; +using System; +using System.IO; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; + +namespace Nikse.SubtitleEdit.Logic.Download; + +public interface IVoxtralTtsAudioCppDownloadService +{ + /// Downloads the selected Voxtral TTS GGUF into audio.cpp's models folder. + Task DownloadModels(string modelKey, IProgress? progress, Action? titleProgress, CancellationToken cancellationToken); +} + +/// +/// Model weights only: the audio.cpp engine binaries are shared with the other audio.cpp +/// engines and downloaded through . The GGUF +/// comes straight from HuggingFace (audio.cpp's own conversion of Mistral's Voxtral-4B-TTS-2603). +/// The weights are CC BY-NC 4.0, so callers show the licence gate first (see +/// ). +/// +public class VoxtralTtsAudioCppDownloadService : IVoxtralTtsAudioCppDownloadService +{ + private readonly HttpClient _httpClient; + + private const string ModelUrlBase = + "https://huggingface.co/audio-cpp/audio.cpp-gguf/resolve/main/Voxtral-4B-TTS-2603-GGUF/"; + + public VoxtralTtsAudioCppDownloadService(HttpClient httpClient) + { + _httpClient = httpClient; + } + + public async Task DownloadModels(string modelKey, IProgress? progress, Action? titleProgress, CancellationToken cancellationToken) + { + var fileName = VoxtralTtsAudioCpp.GetModelFileName(modelKey); + var finalPath = VoxtralTtsAudioCpp.GetModelPath(modelKey); + + // A partial GGUF is worse than none: audio.cpp rejects it with "GGUF tensor data range + // is out of bounds", which reads like a corrupt model rather than a short download. + // Size-check on the threadpool — this runs from the download dialog's init callback. + await Task.Run(() => EnsureRemovedIfInvalid(finalPath, fileName), cancellationToken); + + if (VoxtralTtsAudioCpp.IsValidLocalModelFile(finalPath, fileName)) + { + return; + } + + titleProgress?.Invoke($"Downloading Voxtral TTS model: {fileName}"); + + var partPath = finalPath + ".part"; + try + { + await DownloadHelper.DownloadFileAsync(_httpClient, ModelUrlBase + fileName, partPath, progress, cancellationToken); + await VerifyFile(partPath, GetHashKey(fileName), fileName, cancellationToken); + File.Move(partPath, finalPath); + } + catch + { + TryDelete(partPath); + throw; + } + } + + private static string? GetHashKey(string fileName) + { + if (string.Equals(fileName, VoxtralTtsAudioCpp.ModelBf16FileName, StringComparison.OrdinalIgnoreCase)) + { + return DownloadHashManager.VoxtralTtsAudioCpp.ModelBf16; + } + + if (string.Equals(fileName, VoxtralTtsAudioCpp.ModelQ8_0FileName, StringComparison.OrdinalIgnoreCase)) + { + return DownloadHashManager.VoxtralTtsAudioCpp.ModelQ8_0; + } + + return null; + } + + private static async Task VerifyFile(string filePath, string? key, string fileName, CancellationToken cancellationToken) + { + if (string.IsNullOrEmpty(key)) + { + return; + } + + var expected = DownloadHashManager.GetLatestKnownHash(key); + if (string.IsNullOrEmpty(expected)) + { + return; + } + + string actual; + await using (var stream = File.OpenRead(filePath)) + { + actual = await Sha256Util.ComputeSha256Async(stream, cancellationToken); + } + + if (!string.Equals(expected, actual, StringComparison.OrdinalIgnoreCase)) + { + throw new IOException( + $"Voxtral TTS model {fileName} failed integrity check (expected SHA-256 {expected}, got {actual})."); + } + } + + private static void TryDelete(string path) + { + try { File.Delete(path); } catch { /* best-effort cleanup */ } + } + + private static void EnsureRemovedIfInvalid(string path, string fileName) + { + if (!File.Exists(path) || VoxtralTtsAudioCpp.IsValidLocalModelFile(path, fileName)) + { + return; + } + + TryDelete(path); + } +} diff --git a/tests/UI/Features/Video/TextToSpeech/Engines/AudioCppRuntimeBuildInfoTests.cs b/tests/UI/Features/Video/TextToSpeech/Engines/AudioCppRuntimeBuildInfoTests.cs index ebfb0f681db..d7c7e30f502 100644 --- a/tests/UI/Features/Video/TextToSpeech/Engines/AudioCppRuntimeBuildInfoTests.cs +++ b/tests/UI/Features/Video/TextToSpeech/Engines/AudioCppRuntimeBuildInfoTests.cs @@ -49,6 +49,13 @@ public class AudioCppRuntimeBuildInfoTests "models : index_tts2,higgs_audio_tts,fish_audio,fireredtts3,ace_step,kugelaudio,sidon,breeze_tts\n" + "backend : metal (arm64)\n"; + private const string BuildInfo2026_10_16b = + "audio.cpp build for SubtitleEdit's IndexTTS-2.5 engine\n" + + "source : https://github.com/0xShug0/audio.cpp\n" + + "ref : 0097d365a00b52a6991ad7618f0a04e8d2935e47\n" + + "models : index_tts2,higgs_audio_tts,fish_audio,fireredtts3,ace_step,kugelaudio,sidon,breeze_tts,voxtral_tts\n" + + "backend : metal (arm64)\n"; + [Fact] public void ParseBuiltModelFamilies_ReadsTheModelsLine() { @@ -80,7 +87,7 @@ public void ParseBuiltModelFamilies_NoModelsLine_IsUnknown() [Fact] public void EveryAudioCppEngineFamily_IsInThePinnedBuild() { - var families = AudioCppRuntime.ParseBuiltModelFamilies(BuildInfo2026_10_13)!; + var families = AudioCppRuntime.ParseBuiltModelFamilies(BuildInfo2026_10_16b)!; Assert.Contains(IndexTts25AudioCpp.FamilyName, families); Assert.Contains(HiggsTtsAudioCpp.FamilyName, families); @@ -88,12 +95,19 @@ public void EveryAudioCppEngineFamily_IsInThePinnedBuild() Assert.Contains(FireRedTts3AudioCpp.FamilyName, families); Assert.Contains(KugelAudioAudioCpp.FamilyName, families); Assert.Contains(BreezeTtsAudioCpp.FamilyName, families); + Assert.Contains(VoxtralTtsAudioCpp.FamilyName, families); // Video > Generate background music and the TTS window's background music. Assert.Contains(AceStepAudioCpp.FamilyName, families); // Text to speech > Advanced > Clean voice-clone references. Assert.Contains(SidonAudioCpp.FamilyName, families); } + [Fact] + public void PreviousBuild_LacksVoxtral_SoItAsksForTheUpdate() + { + Assert.DoesNotContain(VoxtralTtsAudioCpp.FamilyName, AudioCppRuntime.ParseBuiltModelFamilies(BuildInfo2026_10_13)!); + } + [Fact] public void PreviousBuild_LacksBreeze_SoItAsksForTheUpdate() { diff --git a/tests/UI/Features/Video/TextToSpeech/Engines/VoxtralTtsAudioCppTests.cs b/tests/UI/Features/Video/TextToSpeech/Engines/VoxtralTtsAudioCppTests.cs new file mode 100644 index 00000000000..71bf17618c8 --- /dev/null +++ b/tests/UI/Features/Video/TextToSpeech/Engines/VoxtralTtsAudioCppTests.cs @@ -0,0 +1,95 @@ +using System.Text.Json; +using Nikse.SubtitleEdit.Features.Video.TextToSpeech; +using Nikse.SubtitleEdit.Features.Video.TextToSpeech.Engines; +using Nikse.SubtitleEdit.Features.Video.TextToSpeech.Voices; + +namespace UITests.Features.Video.TextToSpeech.Engines; + +/// +/// Voxtral 4B TTS is a fixed-voice audio.cpp engine: 20 presets embedded in the GGUF, picked per +/// request with voice_id, and no language option (the model reads the language from the +/// text). The weights are CC BY-NC 4.0, so unlike KugelAudio it has a licence gate. +/// +public class VoxtralTtsAudioCppTests +{ + [Fact] + public void Engine_IsAFixedVoiceEngineWithoutALanguagePick() + { + var engine = new VoxtralTtsAudioCpp(); + + Assert.False(engine.HasLanguageParameter); + Assert.True(engine.HasModel); + Assert.False(engine.SupportsVoiceCloning); + Assert.False(engine.SupportsPerLineVoiceCloning); + Assert.False(engine.ImportVoice("/voices/ada.wav")); + } + + [Fact] + public void Catalog_OffersItButNotAsACloningEngine() + { + Assert.Contains(TtsEngineCatalog.CreateAll(null!), e => e is VoxtralTtsAudioCpp); + Assert.DoesNotContain(TtsEngineCatalog.CreateVoiceCloningEngines(), e => e is VoxtralTtsAudioCpp); + } + + [Fact] + public async Task Voices_AreTheTwentyPresetsWithReadableUniqueNames() + { + var voices = await new VoxtralTtsAudioCpp().GetVoices("fr"); + var ids = voices.Select(v => ((VoxtralTtsVoice)v.EngineVoice!).Voice).ToArray(); + + Assert.Equal(20, ids.Length); + Assert.Equal(VoxtralTtsAudioCpp.PresetVoices, ids); + Assert.Contains(VoxtralTtsAudioCpp.DefaultVoice, ids); + Assert.Equal("English - casual female", voices[0].Name); + Assert.Contains(voices, v => v.Name == "French - female"); + Assert.Equal(voices.Length, voices.Select(v => v.Name).Distinct().Count()); + Assert.DoesNotContain(voices, v => v.Name.Contains('_')); + } + + [Theory] + [InlineData("fr_male", "fr_male")] + [InlineData("AR_MALE", "ar_male")] + [InlineData("ar_female", VoxtralTtsAudioCpp.DefaultVoice)] + [InlineData("", VoxtralTtsAudioCpp.DefaultVoice)] + public void ResolveVoiceId_OnlyPassesOnPresetsTheServerKnows(string id, string expected) + { + Assert.Equal(expected, VoxtralTtsAudioCpp.ResolveVoiceId(new Voice(new VoxtralTtsVoice(id)))); + } + + [Fact] + public void ResolveVoiceId_ForeignEngineVoiceFallsBackToTheDefault() + { + Assert.Equal(VoxtralTtsAudioCpp.DefaultVoice, VoxtralTtsAudioCpp.ResolveVoiceId(new Voice(new KugelAudioVoice("default")))); + Assert.Equal(VoxtralTtsAudioCpp.DefaultVoice, VoxtralTtsAudioCpp.ResolveVoiceId(null)); + } + + [Fact] + public void BuildSpeechPayload_SendsThePresetAsVoiceIdAndNoLanguage() + { + var json = JsonSerializer.Serialize(VoxtralTtsAudioCpp.BuildSpeechPayload("Bonjour à tous.", "fr_female")); + using var doc = JsonDocument.Parse(json); + var root = doc.RootElement; + + Assert.Equal("voxtraltts", root.GetProperty("model").GetString()); + Assert.Equal("Bonjour à tous.", root.GetProperty("input").GetString()); + Assert.Equal("fr_female", root.GetProperty("options").GetProperty("voice_id").GetString()); + Assert.False(root.GetProperty("options").TryGetProperty("language", out _)); + Assert.False(root.TryGetProperty("voice_ref", out _)); + } + + [Theory] + [InlineData(VoxtralTtsAudioCpp.ModelKeyQ8_0, VoxtralTtsAudioCpp.ModelQ8_0FileName)] + [InlineData(VoxtralTtsAudioCpp.ModelKeyBf16, VoxtralTtsAudioCpp.ModelBf16FileName)] + [InlineData("something old", VoxtralTtsAudioCpp.ModelQ8_0FileName)] + public void GetModelFileName_DefaultsToQ8_0(string modelKey, string expected) + { + Assert.Equal(expected, VoxtralTtsAudioCpp.GetModelFileName(modelKey)); + } + + [Fact] + public void License_IsGated() + { + Assert.Equal("Voxtral TTS - model license", VoxtralTtsAudioCpp.LicenseDefinition.DialogTitle); + Assert.Contains("CC BY-NC 4.0", VoxtralTtsAudioCpp.LicenseDefinition.AcceptCheckBoxText); + } +} From 93d4f779e8dc0072ae9de6bf13ef2eb5a259811c Mon Sep 17 00:00:00 2001 From: niksedk Date: Fri, 16 Oct 2026 14:37:37 +0200 Subject: [PATCH 258/306] Pin audio.cpp runtime 2026-10-16b with the voxtral_tts family support-files release audiocpp-indextts25-2026-10-16b (run 38889216234) is the 2026-10-16 build (upstream v0.9.2 0097d365) plus voxtral_tts. Existing installs get the "audio.cpp update required" prompt when Voxtral TTS is picked, because their BUILD-INFO lacks the family. Co-Authored-By: Claude Opus 5.5 --- src/ui/Logic/Download/DownloadHashManager.cs | 9 ++++++++- .../Download/IndexTts25AudioCppDownloadService.cs | 10 ++++------ 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/src/ui/Logic/Download/DownloadHashManager.cs b/src/ui/Logic/Download/DownloadHashManager.cs index 865289257d9..3d5f9a540bb 100644 --- a/src/ui/Logic/Download/DownloadHashManager.cs +++ b/src/ui/Logic/Download/DownloadHashManager.cs @@ -2573,10 +2573,11 @@ public static class WhisperX }, // audio.cpp engine archives we build in SubtitleEdit/support-files - // (audiocpp-indextts25-2026-10-16). Newest first — index 0 is the pinned release, + // (audiocpp-indextts25-2026-10-16b). Newest first — index 0 is the pinned release, // so anything older prompts an update instead of being treated as current. [IndexTts25AudioCpp.EngineMacArm64] = new[] { + "fb88d4e3b1985fc56f3c4112a53b6e35ba115c689d7d3db1588b7e77e123c099", // audiocpp-indextts25-macos-arm64.tar.gz (2026-10-16b, upstream v0.9.2 0097d365 + index_tts2 + higgs_audio_tts + fish_audio + fireredtts3 + ace_step + kugelaudio + sidon + breeze_tts + voxtral_tts) "fcc138881e64bcee5c4d130d87a2ff6ec89b7c951d26f84d705eccef415955d6", // audiocpp-indextts25-macos-arm64.tar.gz (2026-10-16, upstream v0.9.2 0097d365 + index_tts2 + higgs_audio_tts + fish_audio + fireredtts3 + ace_step + kugelaudio + sidon + breeze_tts) "a1481228f53e4b2e9cd765894fdffdfebaec559eca0b03a1e61c0edfe50c212c", // audiocpp-indextts25-macos-arm64.tar.gz (2026-10-13, upstream v0.9.1 f04de8ec + index_tts2 + higgs_audio_tts + fish_audio + fireredtts3 + ace_step + kugelaudio + sidon + breeze_tts) "29f75844449b9e41afeb41e77d2dc7e4136dd950f93d85bd16903ded8a86c470", // audiocpp-indextts25-macos-arm64.tar.gz (2026-10-08b, upstream v0.9.1 f04de8ec + index_tts2 + higgs_audio_tts + fish_audio + fireredtts3 + ace_step + kugelaudio + sidon) @@ -2595,6 +2596,7 @@ public static class WhisperX }, [IndexTts25AudioCpp.EngineWindowsCpu] = new[] { + "7001cc8fb3da2654f7c92a4e985f1123b033cb57cd8748309017b53e3f016045", // audiocpp-indextts25-windows-x86_64-cpu.zip (2026-10-16b, upstream v0.9.2 0097d365 + index_tts2 + higgs_audio_tts + fish_audio + fireredtts3 + ace_step + kugelaudio + sidon + breeze_tts + voxtral_tts) "f6c0403a574c59322db0fb6377bc58e0569e89aa8cdcb8e63059ce409c2e4a8f", // audiocpp-indextts25-windows-x86_64-cpu.zip (2026-10-16, upstream v0.9.2 0097d365 + index_tts2 + higgs_audio_tts + fish_audio + fireredtts3 + ace_step + kugelaudio + sidon + breeze_tts) "2d5d3ffdb9e95c816f214ecdfdb40697ccce8d24dfd742befc05ac79fde93daf", // audiocpp-indextts25-windows-x86_64-cpu.zip (2026-10-13, upstream v0.9.1 f04de8ec + index_tts2 + higgs_audio_tts + fish_audio + fireredtts3 + ace_step + kugelaudio + sidon + breeze_tts) "96a43e292dd761e02e7e42f579c5f7f72a86c98603db94ade3772aea960666c7", // audiocpp-indextts25-windows-x86_64-cpu.zip (2026-10-08b, upstream v0.9.1 f04de8ec + index_tts2 + higgs_audio_tts + fish_audio + fireredtts3 + ace_step + kugelaudio + sidon) @@ -2613,6 +2615,7 @@ public static class WhisperX }, [IndexTts25AudioCpp.EngineWindowsVulkan] = new[] { + "dc4614c369673812818c1927bdca4ae9e17d5d09a7ee74080588eb602a3f388a", // audiocpp-indextts25-windows-x86_64-vulkan.zip (2026-10-16b, upstream v0.9.2 0097d365 + index_tts2 + higgs_audio_tts + fish_audio + fireredtts3 + ace_step + kugelaudio + sidon + breeze_tts + voxtral_tts) "ba0d5f83519ef2bc4ef47d3e0d09b242938c956297ad059e8f844931e7b878dc", // audiocpp-indextts25-windows-x86_64-vulkan.zip (2026-10-16, upstream v0.9.2 0097d365 + index_tts2 + higgs_audio_tts + fish_audio + fireredtts3 + ace_step + kugelaudio + sidon + breeze_tts) "d79517505418d4cbdf0d1d266a99cb983515920ec81b45891c50abe6a8202a48", // audiocpp-indextts25-windows-x86_64-vulkan.zip (2026-10-13, upstream v0.9.1 f04de8ec + index_tts2 + higgs_audio_tts + fish_audio + fireredtts3 + ace_step + kugelaudio + sidon + breeze_tts) "23c7aabf2df7b3c8513e3b39f9fb7069007a7a0ac12ac0982bc984754f475ba1", // audiocpp-indextts25-windows-x86_64-vulkan.zip (2026-10-08b, upstream v0.9.1 f04de8ec + index_tts2 + higgs_audio_tts + fish_audio + fireredtts3 + ace_step + kugelaudio + sidon) @@ -2631,6 +2634,7 @@ public static class WhisperX }, [IndexTts25AudioCpp.EngineWindowsCuda] = new[] { + "25ad9d61f6f3a8ab0de2a9a354950898a2f2e85e7af1ed550f66c290ce4443bd", // audiocpp-indextts25-windows-x86_64-cuda.zip (2026-10-16b, upstream v0.9.2 0097d365 + index_tts2 + higgs_audio_tts + fish_audio + fireredtts3 + ace_step + kugelaudio + sidon + breeze_tts + voxtral_tts) "ad3fdb88027c96852810c51b346bc6ce9cce8c982fadbb512a9b184b2e398b22", // audiocpp-indextts25-windows-x86_64-cuda.zip (2026-10-16, upstream v0.9.2 0097d365 + index_tts2 + higgs_audio_tts + fish_audio + fireredtts3 + ace_step + kugelaudio + sidon + breeze_tts) "bcb0cc492fedb356398ca1a10708ca92ec4e5f8cbda439b6eee40c5ad820b0a4", // audiocpp-indextts25-windows-x86_64-cuda.zip (2026-10-13, upstream v0.9.1 f04de8ec + index_tts2 + higgs_audio_tts + fish_audio + fireredtts3 + ace_step + kugelaudio + sidon + breeze_tts) "6b6544b231449b44cb94ef5319b64601abd7d0b32105b3e62b7af1a4c8ad34dd", // audiocpp-indextts25-windows-x86_64-cuda.zip (2026-10-08b, upstream v0.9.1 f04de8ec + index_tts2 + higgs_audio_tts + fish_audio + fireredtts3 + ace_step + kugelaudio + sidon) @@ -2649,6 +2653,7 @@ public static class WhisperX }, [IndexTts25AudioCpp.EngineLinuxCpu] = new[] { + "0237f6f7b72503096e77177b699653d53fbe00928870bd1133204a1b6a7d611c", // audiocpp-indextts25-linux-x86_64.tar.gz (2026-10-16b, upstream v0.9.2 0097d365 + index_tts2 + higgs_audio_tts + fish_audio + fireredtts3 + ace_step + kugelaudio + sidon + breeze_tts + voxtral_tts) "e09584fc98c0e52aaa8cb79305838d062e0ee4723022ddb3e7406674bfac4ea0", // audiocpp-indextts25-linux-x86_64.tar.gz (2026-10-16, upstream v0.9.2 0097d365 + index_tts2 + higgs_audio_tts + fish_audio + fireredtts3 + ace_step + kugelaudio + sidon + breeze_tts) "b43a004bf850b6b8b6f58bfdada8d0d38586f5c9b20666e9dc0acec1d0f5d316", // audiocpp-indextts25-linux-x86_64.tar.gz (2026-10-13, upstream v0.9.1 f04de8ec + index_tts2 + higgs_audio_tts + fish_audio + fireredtts3 + ace_step + kugelaudio + sidon + breeze_tts) "f9d6686c63014e2aebb3313fb34a19bd1785a7ba3410cb42c9573c437bbb55f1", // audiocpp-indextts25-linux-x86_64.tar.gz (2026-10-08b, upstream v0.9.1 f04de8ec + index_tts2 + higgs_audio_tts + fish_audio + fireredtts3 + ace_step + kugelaudio + sidon) @@ -2667,6 +2672,7 @@ public static class WhisperX }, [IndexTts25AudioCpp.EngineLinuxVulkan] = new[] { + "ebf97c7443d5324300406b442298555cd0fedf36036f5b257edf6c11fd5a09d2", // audiocpp-indextts25-linux-x86_64-vulkan.tar.gz (2026-10-16b, upstream v0.9.2 0097d365 + index_tts2 + higgs_audio_tts + fish_audio + fireredtts3 + ace_step + kugelaudio + sidon + breeze_tts + voxtral_tts) "2b1f3734347623b0e4157a9f0ea495ef7f9b3820e06a632dbe0f62f8b8dd9b36", // audiocpp-indextts25-linux-x86_64-vulkan.tar.gz (2026-10-16, upstream v0.9.2 0097d365 + index_tts2 + higgs_audio_tts + fish_audio + fireredtts3 + ace_step + kugelaudio + sidon + breeze_tts) "c438fb2def4f83b8bf10260254ba43c4749979227621d5b82b8e445db8392737", // audiocpp-indextts25-linux-x86_64-vulkan.tar.gz (2026-10-13, upstream v0.9.1 f04de8ec + index_tts2 + higgs_audio_tts + fish_audio + fireredtts3 + ace_step + kugelaudio + sidon + breeze_tts) "45e567e6df3d81ebb465307bf19f3bc782b9a8f8ad50978e7de79c635f658207", // audiocpp-indextts25-linux-x86_64-vulkan.tar.gz (2026-10-08b, upstream v0.9.1 f04de8ec + index_tts2 + higgs_audio_tts + fish_audio + fireredtts3 + ace_step + kugelaudio + sidon) @@ -2685,6 +2691,7 @@ public static class WhisperX }, [IndexTts25AudioCpp.EngineLinuxCuda] = new[] { + "d52e0091a8d0ef24e47cd532c85016d6cf54205522c4407f095e00eba11e472c", // audiocpp-indextts25-linux-x86_64-cuda.tar.gz (2026-10-16b, upstream v0.9.2 0097d365 + index_tts2 + higgs_audio_tts + fish_audio + fireredtts3 + ace_step + kugelaudio + sidon + breeze_tts + voxtral_tts) "c7144cd682bed5f5122d3200a897293ab97e44f1e9058c4de4b7398b9cfe9621", // audiocpp-indextts25-linux-x86_64-cuda.tar.gz (2026-10-16, upstream v0.9.2 0097d365 + index_tts2 + higgs_audio_tts + fish_audio + fireredtts3 + ace_step + kugelaudio + sidon + breeze_tts) "2838ccee80719d30de5b53f3acbd11cb00bd9740ef4a029938007d248d68c10a", // audiocpp-indextts25-linux-x86_64-cuda.tar.gz (2026-10-13, upstream v0.9.1 f04de8ec + index_tts2 + higgs_audio_tts + fish_audio + fireredtts3 + ace_step + kugelaudio + sidon + breeze_tts) "92dff4c562f3842e83b94cf562e79c15e1fafa2239576b0ff62089b74f9f26d1", // audiocpp-indextts25-linux-x86_64-cuda.tar.gz (2026-10-08b, upstream v0.9.1 f04de8ec + index_tts2 + higgs_audio_tts + fish_audio + fireredtts3 + ace_step + kugelaudio + sidon) diff --git a/src/ui/Logic/Download/IndexTts25AudioCppDownloadService.cs b/src/ui/Logic/Download/IndexTts25AudioCppDownloadService.cs index 4a22b7bed85..7eb1235157a 100644 --- a/src/ui/Logic/Download/IndexTts25AudioCppDownloadService.cs +++ b/src/ui/Logic/Download/IndexTts25AudioCppDownloadService.cs @@ -33,12 +33,10 @@ public class IndexTts25AudioCppDownloadService : IIndexTts25AudioCppDownloadServ { private readonly HttpClient _httpClient; - // 2026-10-16: upstream v0.9.2 @ 0097d365, same families as 2026-10-13 (index_tts2 + - // higgs_audio_tts + fish_audio + fireredtts3 + ace_step + kugelaudio + sidon + breeze_tts). - // On Metal, IndexTTS 2.5, Higgs, Fish, FireRedTTS3, KugelAudio, Sidon and ACE-Step output - // is byte-identical to 2026-10-13; Breeze TTS 2 changed (upstream voice-consistency work) - // and still accepts the "instruction" option. - private const string ReleaseTag = "audiocpp-indextts25-2026-10-16"; + // 2026-10-16b: the 2026-10-16 build (upstream v0.9.2 @ 0097d365, index_tts2 + higgs_audio_tts + // + fish_audio + fireredtts3 + ace_step + kugelaudio + sidon + breeze_tts) plus voxtral_tts + // for Voxtral TTS. Same upstream commit, so the other families are unchanged. + private const string ReleaseTag = "audiocpp-indextts25-2026-10-16b"; private const string ReleaseBase = "https://github.com/SubtitleEdit/support-files/releases/download/" + ReleaseTag + "/"; From 536ac431df55ac8cd38940bb4b11ce19b551dbbe Mon Sep 17 00:00:00 2001 From: niksedk Date: Fri, 16 Oct 2026 14:39:57 +0200 Subject: [PATCH 259/306] ASSA draw: six more clip arts Pizza slice, ice cream, umbrella, cat face, key and Christmas tree. Co-Authored-By: Claude Opus 5.5 --- src/ui/Features/Assa/AssaDraw/ShapeLibrary.cs | 12 ++++++++++++ src/ui/Logic/Config/Language/Assa/LanguageAssa.cs | 12 ++++++++++++ tests/UI/Features/Assa/AssaDrawClipArtTests.cs | 2 +- 3 files changed, 25 insertions(+), 1 deletion(-) diff --git a/src/ui/Features/Assa/AssaDraw/ShapeLibrary.cs b/src/ui/Features/Assa/AssaDraw/ShapeLibrary.cs index cd74b372264..09e372d4dc7 100644 --- a/src/ui/Features/Assa/AssaDraw/ShapeLibrary.cs +++ b/src/ui/Features/Assa/AssaDraw/ShapeLibrary.cs @@ -519,6 +519,18 @@ private static List MakeBuiltIn() """"""), Clip(l.DrawClipArtPartyPopper, null, """"""), + Clip(l.DrawClipArtPizzaSlice, null, + """"""), + Clip(l.DrawClipArtIceCream, null, + """"""), + Clip(l.DrawClipArtUmbrella, null, + """"""), + Clip(l.DrawClipArtCatFace, null, + """"""), + Clip(l.DrawClipArtKey, null, + """"""), + Clip(l.DrawClipArtChristmasTree, null, + """"""), ]; } } diff --git a/src/ui/Logic/Config/Language/Assa/LanguageAssa.cs b/src/ui/Logic/Config/Language/Assa/LanguageAssa.cs index 953d4c4e886..0b7bdd43439 100644 --- a/src/ui/Logic/Config/Language/Assa/LanguageAssa.cs +++ b/src/ui/Logic/Config/Language/Assa/LanguageAssa.cs @@ -98,6 +98,12 @@ public class LanguageAssa public string DrawClipArtBalloon { get; set; } public string DrawClipArtCamera { get; set; } public string DrawClipArtPartyPopper { get; set; } + public string DrawClipArtPizzaSlice { get; set; } + public string DrawClipArtIceCream { get; set; } + public string DrawClipArtUmbrella { get; set; } + public string DrawClipArtCatFace { get; set; } + public string DrawClipArtKey { get; set; } + public string DrawClipArtChristmasTree { get; set; } public string DrawCategoryMyShapes { get; set; } public string DrawShapeSpeechBubble { get; set; } public string DrawShapeSpeechBubbleRight { get; set; } @@ -598,6 +604,12 @@ public LanguageAssa() DrawClipArtBalloon = "Balloon"; DrawClipArtCamera = "Camera"; DrawClipArtPartyPopper = "Party popper"; + DrawClipArtPizzaSlice = "Pizza slice"; + DrawClipArtIceCream = "Ice cream"; + DrawClipArtUmbrella = "Umbrella"; + DrawClipArtCatFace = "Cat face"; + DrawClipArtKey = "Key"; + DrawClipArtChristmasTree = "Christmas tree"; DrawCategoryMyShapes = "My shapes"; DrawShapeSpeechBubble = "Speech bubble"; DrawShapeSpeechBubbleRight = "Speech bubble, tail right"; diff --git a/tests/UI/Features/Assa/AssaDrawClipArtTests.cs b/tests/UI/Features/Assa/AssaDrawClipArtTests.cs index e1cb0f0cb7d..41d9bd05202 100644 --- a/tests/UI/Features/Assa/AssaDrawClipArtTests.cs +++ b/tests/UI/Features/Assa/AssaDrawClipArtTests.cs @@ -19,7 +19,7 @@ public class AssaDrawClipArtTests [Fact] public void ClipArt_KeepsItsColors_InAFewLayers() { - Assert.Equal(42, ClipArt.Count); + Assert.Equal(48, ClipArt.Count); foreach (var item in ClipArt) { Assert.True(item.HasOwnColors, item.Name); From 86874b72394a36c13a1ee62ea51295dba09a81cd Mon Sep 17 00:00:00 2001 From: Ivandro Jao Date: Fri, 16 Oct 2026 14:05:00 +0100 Subject: [PATCH 260/306] Disable normal casing options when Normal casing is not selected Co-Authored-By: Claude Opus 5.5 (1M context) --- src/ui/Features/Tools/ChangeCasing/ChangeCasingWindow.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/ui/Features/Tools/ChangeCasing/ChangeCasingWindow.cs b/src/ui/Features/Tools/ChangeCasing/ChangeCasingWindow.cs index 960d672133a..8e2b216f7cb 100644 --- a/src/ui/Features/Tools/ChangeCasing/ChangeCasingWindow.cs +++ b/src/ui/Features/Tools/ChangeCasing/ChangeCasingWindow.cs @@ -28,9 +28,11 @@ public ChangeCasingWindow(ChangeCasingViewModel vm) var rowNormalCasingFixNames = EditorChrome.MakeToggleRow(nameof(vm.NormalCasingFixNames), Se.Language.Tools.ChangeCasing.FixNames); rowNormalCasingFixNames.Margin = new Thickness(28, 2, 0, 4); + rowNormalCasingFixNames.Bind(IsEnabledProperty, new Binding(nameof(vm.NormalCasing))); var rowNormalCasingOnlyUpper = EditorChrome.MakeToggleRow(nameof(vm.NormalCasingOnlyUpper), Se.Language.Tools.ChangeCasing.OnlyFixUppercaseLines); rowNormalCasingOnlyUpper.Margin = new Thickness(28, 0, 0, 8); + rowNormalCasingOnlyUpper.Bind(IsEnabledProperty, new Binding(nameof(vm.NormalCasing))); var panel = new StackPanel { From df2cf2915c4ad0bccdc17f199259f570eb0ed000 Mon Sep 17 00:00:00 2001 From: niksedk Date: Fri, 16 Oct 2026 15:23:47 +0200 Subject: [PATCH 261/306] Change casing: dim disabled normal casing option rows The toggle row's label is a plain TextBlock with no disabled look, so fade the whole row like Split/break long lines does. Co-Authored-By: Claude Opus 5.5 --- src/ui/Features/Tools/ChangeCasing/ChangeCasingWindow.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/ui/Features/Tools/ChangeCasing/ChangeCasingWindow.cs b/src/ui/Features/Tools/ChangeCasing/ChangeCasingWindow.cs index 8e2b216f7cb..97b59c01a09 100644 --- a/src/ui/Features/Tools/ChangeCasing/ChangeCasingWindow.cs +++ b/src/ui/Features/Tools/ChangeCasing/ChangeCasingWindow.cs @@ -1,6 +1,7 @@ using Avalonia; using Avalonia.Controls; using Avalonia.Data; +using Avalonia.Data.Converters; using Avalonia.Layout; using Nikse.SubtitleEdit.Logic; using Nikse.SubtitleEdit.Logic.Config; @@ -29,10 +30,12 @@ public ChangeCasingWindow(ChangeCasingViewModel vm) var rowNormalCasingFixNames = EditorChrome.MakeToggleRow(nameof(vm.NormalCasingFixNames), Se.Language.Tools.ChangeCasing.FixNames); rowNormalCasingFixNames.Margin = new Thickness(28, 2, 0, 4); rowNormalCasingFixNames.Bind(IsEnabledProperty, new Binding(nameof(vm.NormalCasing))); + rowNormalCasingFixNames.Bind(OpacityProperty, new Binding(nameof(vm.NormalCasing)) { Converter = EnabledOpacityConverter }); var rowNormalCasingOnlyUpper = EditorChrome.MakeToggleRow(nameof(vm.NormalCasingOnlyUpper), Se.Language.Tools.ChangeCasing.OnlyFixUppercaseLines); rowNormalCasingOnlyUpper.Margin = new Thickness(28, 0, 0, 8); rowNormalCasingOnlyUpper.Bind(IsEnabledProperty, new Binding(nameof(vm.NormalCasing))); + rowNormalCasingOnlyUpper.Bind(OpacityProperty, new Binding(nameof(vm.NormalCasing)) { Converter = EnabledOpacityConverter }); var panel = new StackPanel { @@ -61,6 +64,9 @@ public ChangeCasingWindow(ChangeCasingViewModel vm) KeyDown += (_, e) => vm.OnKeyDown(e); } + // a disabled row's label (plain TextBlock) has no disabled look, so dim the whole row + private static readonly FuncValueConverter EnabledOpacityConverter = new(on => on ? 1.0 : 0.5); + private static RadioButton MakeRadio(string text, string property) { return new RadioButton From 7372b13e3cfaed9274ffde33ea60499cf2d10dd9 Mon Sep 17 00:00:00 2001 From: niksedk Date: Fri, 16 Oct 2026 15:24:31 +0200 Subject: [PATCH 262/306] Check for updates: install and restart (Windows installer, Linux tarball) The Check for updates dialog gets an "Install and restart" button for the two packages where swapping in a new version is simple: - Windows x64 installed via Inno Setup (unins000.exe next to the exe): the new Setup.exe runs /SILENT after Subtitle Edit exits, elevated with one UAC prompt, then Subtitle Edit starts again as the normal user. - Linux tarball in a user-writable folder (not Flatpak/AppImage/distro package): the new tarball is extracted over the folder after exit, then Subtitle Edit starts again. The file comes from the GitHub release tagged with the new version and must match the SHA-256 digest GitHub publishes for the asset; the tarball is also rejected if it holds absolute/".." paths, links, or no SubtitleEdit binary. Editor windows close normally (unsaved-changes prompt); if the user stays, the update installs when they quit. Other packages keep "Download new version". Co-Authored-By: Claude Opus 5.5 --- src/ui/Assets/Languages/English.json | 6 +- src/ui/DependencyInjectionExtensions.cs | 1 + .../Help/CheckForUpdates/AutoUpdateService.cs | 396 ++++++++++++++++++ .../CheckForUpdatesViewModel.cs | 69 ++- .../CheckForUpdates/CheckForUpdatesWindow.cs | 30 +- .../Features/Main/Layout/MainWindowFactory.cs | 20 + .../Config/Language/Help/LanguageHelp.cs | 8 + .../Features/Help/AutoUpdateServiceTests.cs | 279 ++++++++++++ 8 files changed, 804 insertions(+), 5 deletions(-) create mode 100644 src/ui/Features/Help/CheckForUpdates/AutoUpdateService.cs create mode 100644 tests/UI/Features/Help/AutoUpdateServiceTests.cs diff --git a/src/ui/Assets/Languages/English.json b/src/ui/Assets/Languages/English.json index 02cc7edd092..69a0fd9d130 100644 --- a/src/ui/Assets/Languages/English.json +++ b/src/ui/Assets/Languages/English.json @@ -4126,7 +4126,11 @@ "checkForUpdatesUpToDate": "You are running the latest version.", "checkForUpdatesNewVersionAvailable": "New version available: {0}", "checkForUpdatesUnableToCheck": "Unable to check for updates.", - "checkForUpdatesDownloadNewVersion": "Download new version" + "checkForUpdatesDownloadNewVersion": "Download new version", + "checkForUpdatesInstallAndRestart": "Install and restart", + "checkForUpdatesDownloadingX": "Downloading {0}...", + "checkForUpdatesInstallFailed": "Automatic update failed - please use \"Download new version\" instead.", + "checkForUpdatesInstallsWhenClosed": "The update will be installed when Subtitle Edit closes." }, "ocr": { "linesToDraw": "Lines to draw", diff --git a/src/ui/DependencyInjectionExtensions.cs b/src/ui/DependencyInjectionExtensions.cs index 5d5f0ba9541..f674417d0de 100644 --- a/src/ui/DependencyInjectionExtensions.cs +++ b/src/ui/DependencyInjectionExtensions.cs @@ -312,6 +312,7 @@ public static void AddSubtitleEditServices(this IServiceCollection collection) collection.AddHttpClientWithProxy(); collection.AddHttpClientWithProxy(); collection.AddHttpClientWithProxy(); + collection.AddHttpClientWithProxy(); collection.AddHttpClientWithProxy(); collection.AddHttpClientWithProxy(); collection.AddHttpClientWithProxy(); diff --git a/src/ui/Features/Help/CheckForUpdates/AutoUpdateService.cs b/src/ui/Features/Help/CheckForUpdates/AutoUpdateService.cs new file mode 100644 index 00000000000..2bcf3db7142 --- /dev/null +++ b/src/ui/Features/Help/CheckForUpdates/AutoUpdateService.cs @@ -0,0 +1,396 @@ +using Nikse.SubtitleEdit.Logic.Config; +using Nikse.SubtitleEdit.Logic.Download; +using System; +using System.Diagnostics; +using System.Formats.Tar; +using System.IO; +using System.IO.Compression; +using System.Linq; +using System.Net.Http; +using System.Runtime.InteropServices; +using System.Security.Cryptography; +using System.Text; +using System.Text.Json; +using System.Threading; +using System.Threading.Tasks; + +namespace Nikse.SubtitleEdit.Features.Help.CheckForUpdates; + +/// How this copy of Subtitle Edit can update itself, if at all. +public enum AutoUpdateKind +{ + /// No in-app update - the user gets the download page (zip, dmg, Flatpak, AUR, ...). + None, + + /// Installed by the Inno Setup installer: run the new installer silently. + WindowsInstaller, + + /// Unpacked Linux tarball in a user-writable folder: extract the new tarball over it. + LinuxTarball, +} + +public class AutoUpdateAsset +{ + public string Name { get; set; } = string.Empty; + public string DownloadUrl { get; set; } = string.Empty; + public string Sha256 { get; set; } = string.Empty; +} + +public interface IAutoUpdateService +{ + AutoUpdateKind Kind { get; } + + /// Downloads the release file for this platform and verifies its SHA-256; returns the local path. + Task DownloadAsync(string version, IProgress? progress, CancellationToken cancellationToken); + + /// + /// Starts a detached helper that waits for this process to exit, installs the downloaded + /// update and starts Subtitle Edit again. The caller then closes the editor windows. + /// + void StartInstallAfterExit(string downloadedFile); +} + +/// +/// In-app update for the two packages where swapping in a new version is simple: the Windows +/// x64 installer (re-run silently - Inno keeps the install folder, language and tasks) and the +/// Linux tarball (extracted over the old folder once Subtitle Edit has exited). The file comes +/// from the GitHub release whose tag is the new version, and must match the SHA-256 digest +/// GitHub publishes for that release asset - no digest, no install. +/// +public class AutoUpdateService : IAutoUpdateService +{ + private const string ReleaseApiUrl = "https://api.github.com/repos/SubtitleEdit/subtitleedit/releases/tags/"; + internal const string DownloadUrlPrefix = "https://github.com/SubtitleEdit/subtitleedit/releases/download/"; + internal const string WindowsInstallerAsset = "SubtitleEdit-Windows-x64-Setup.exe"; + internal const string LinuxX64Asset = "SubtitleEdit-Linux-x64.tar.gz"; + internal const string LinuxArm64Asset = "SubtitleEdit-Linux-ARM64.tar.gz"; + internal const string LinuxExecutable = "SubtitleEdit"; + + private readonly HttpClient _httpClient; + private readonly Lazy _kind; + + public AutoUpdateService(HttpClient httpClient) + { + _httpClient = httpClient; + _kind = new Lazy(DetectCurrentKind); + } + + public AutoUpdateKind Kind => _kind.Value; + + private static AutoUpdateKind DetectCurrentKind() + { + try + { + return DetectKind( + OperatingSystem.IsWindows(), + OperatingSystem.IsLinux(), + RuntimeInformation.ProcessArchitecture, + UpdateCheckService.IsStoreManagedInstall || + !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("APPIMAGE")), + AppContext.BaseDirectory, + File.Exists, + IsDirectoryWritable); + } + catch (Exception exception) + { + Se.LogError(exception, "AutoUpdateService.DetectCurrentKind"); + return AutoUpdateKind.None; + } + } + + internal static AutoUpdateKind DetectKind( + bool isWindows, + bool isLinux, + Architecture architecture, + bool isPackageManaged, + string exeFolder, + Func fileExists, + Func isDirectoryWritable) + { + if (isPackageManaged) + { + return AutoUpdateKind.None; + } + + // Only the Inno Setup install leaves an uninstaller next to the exe; the portable + // zip (and the ARM64 build, which only ships as a zip) keep the download page. + if (isWindows) + { + return architecture == Architecture.X64 && fileExists(Path.Combine(exeFolder, "unins000.exe")) + ? AutoUpdateKind.WindowsInstaller + : AutoUpdateKind.None; + } + + // A distro package (e.g. AUR in /usr/lib) is not writable for the user and is + // updated by its package manager - only an unpacked tarball in a writable folder. + if (isLinux && + architecture is Architecture.X64 or Architecture.Arm64 && + fileExists(Path.Combine(exeFolder, LinuxExecutable)) && + (fileExists("/usr/bin/tar") || fileExists("/bin/tar")) && + isDirectoryWritable(exeFolder)) + { + return AutoUpdateKind.LinuxTarball; + } + + return AutoUpdateKind.None; + } + + internal static string GetAssetName(AutoUpdateKind kind, Architecture architecture) + { + return kind switch + { + AutoUpdateKind.WindowsInstaller => WindowsInstallerAsset, + AutoUpdateKind.LinuxTarball => architecture == Architecture.Arm64 ? LinuxArm64Asset : LinuxX64Asset, + _ => string.Empty, + }; + } + + private static bool IsDirectoryWritable(string folder) + { + try + { + var probe = Path.Combine(folder, ".se-update-probe-" + Guid.NewGuid().ToString("N")); + File.WriteAllText(probe, string.Empty); + File.Delete(probe); + return true; + } + catch + { + return false; + } + } + + public async Task DownloadAsync(string version, IProgress? progress, CancellationToken cancellationToken) + { + var assetName = GetAssetName(Kind, RuntimeInformation.ProcessArchitecture); + if (string.IsNullOrEmpty(assetName)) + { + throw new InvalidOperationException("Automatic update is not available for this installation"); + } + + var asset = await GetReleaseAssetAsync(version, assetName, cancellationToken); + + var folder = Path.Combine(Path.GetTempPath(), "SubtitleEdit-update"); + Directory.CreateDirectory(folder); + var path = Path.Combine(folder, asset.Name); + if (File.Exists(path)) + { + File.Delete(path); + } + + await DownloadHelper.DownloadFileAsync(_httpClient, asset.DownloadUrl, path, progress, cancellationToken); + + if (!await IsSha256MatchAsync(path, asset.Sha256, cancellationToken)) + { + TryDelete(path); + throw new InvalidDataException($"Checksum mismatch for downloaded update {asset.Name}"); + } + + if (Kind == AutoUpdateKind.LinuxTarball) + { + await Task.Run(() => ValidateLinuxTarball(path), cancellationToken); + } + + return path; + } + + private async Task GetReleaseAssetAsync(string version, string assetName, CancellationToken cancellationToken) + { + using var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); + cts.CancelAfter(TimeSpan.FromSeconds(30)); + + using var request = new HttpRequestMessage(HttpMethod.Get, ReleaseApiUrl + Uri.EscapeDataString(version)); + request.Headers.UserAgent.ParseAdd("SubtitleEdit/" + Se.Version.TrimStart('v')); + request.Headers.Accept.ParseAdd("application/vnd.github+json"); + using var response = await _httpClient.SendAsync(request, cts.Token); + response.EnsureSuccessStatusCode(); + var json = await response.Content.ReadAsStringAsync(cts.Token); + + return ParseReleaseAsset(json, assetName) + ?? throw new InvalidOperationException($"Release {version} has no verifiable asset {assetName}"); + } + + /// + /// Picks the named asset from a GitHub release JSON. Returns null when it is missing, has no + /// "sha256:" digest, or its download url is not a SubtitleEdit release url. + /// + internal static AutoUpdateAsset? ParseReleaseAsset(string releaseJson, string assetName) + { + using var document = JsonDocument.Parse(releaseJson); + if (!document.RootElement.TryGetProperty("assets", out var assets) || assets.ValueKind != JsonValueKind.Array) + { + return null; + } + + foreach (var asset in assets.EnumerateArray()) + { + if (GetString(asset, "name") != assetName) + { + continue; + } + + var url = GetString(asset, "browser_download_url"); + var digest = GetString(asset, "digest"); + const string sha256Prefix = "sha256:"; + if (!url.StartsWith(DownloadUrlPrefix, StringComparison.Ordinal) || + !url.EndsWith("/" + assetName, StringComparison.Ordinal) || + !digest.StartsWith(sha256Prefix, StringComparison.OrdinalIgnoreCase) || + digest.Length != sha256Prefix.Length + 64) + { + return null; + } + + return new AutoUpdateAsset + { + Name = assetName, + DownloadUrl = url, + Sha256 = digest.Substring(sha256Prefix.Length).ToLowerInvariant(), + }; + } + + return null; + } + + private static string GetString(JsonElement element, string propertyName) + { + return element.TryGetProperty(propertyName, out var value) && value.ValueKind == JsonValueKind.String + ? value.GetString() ?? string.Empty + : string.Empty; + } + + internal static async Task IsSha256MatchAsync(string path, string expectedSha256, CancellationToken cancellationToken) + { + await using var stream = File.OpenRead(path); + var hash = await SHA256.HashDataAsync(stream, cancellationToken); + return string.Equals(Convert.ToHexString(hash), expectedSha256, StringComparison.OrdinalIgnoreCase); + } + + /// + /// The tarball is extracted straight over the install folder, so refuse one that would write + /// outside it (absolute paths, "..", links) or that is not a Subtitle Edit build at all. + /// + internal static void ValidateLinuxTarball(string path) + { + using var fileStream = File.OpenRead(path); + using var gzip = new GZipStream(fileStream, CompressionMode.Decompress); + using var reader = new TarReader(gzip); + var hasExecutable = false; + while (reader.GetNextEntry() is { } entry) + { + var name = entry.Name; + if (name.StartsWith('/') || name.Split('/').Contains("..")) + { + throw new InvalidDataException("Update archive contains an unsafe path: " + name); + } + + if (entry.EntryType is TarEntryType.SymbolicLink or TarEntryType.HardLink) + { + throw new InvalidDataException("Update archive contains a link: " + name); + } + + if (name is LinuxExecutable or "./" + LinuxExecutable) + { + hasExecutable = true; + } + } + + if (!hasExecutable) + { + throw new InvalidDataException("Update archive does not contain " + LinuxExecutable); + } + } + + public void StartInstallAfterExit(string downloadedFile) + { + var processId = Environment.ProcessId; + var exeFolder = AppContext.BaseDirectory.TrimEnd(Path.DirectorySeparatorChar); + var scriptFolder = Path.GetDirectoryName(downloadedFile)!; + + switch (Kind) + { + case AutoUpdateKind.WindowsInstaller: + { + var script = Path.Combine(scriptFolder, "install-update.ps1"); + File.WriteAllText(script, WindowsScript, new UTF8Encoding(true)); + var startInfo = new ProcessStartInfo("powershell.exe") + { + UseShellExecute = false, + CreateNoWindow = true, + }; + foreach (var argument in new[] + { + "-NoProfile", "-ExecutionPolicy", "Bypass", "-WindowStyle", "Hidden", "-File", script, + "-ProcessId", processId.ToString(), "-Installer", downloadedFile, + "-App", Path.Combine(exeFolder, "SubtitleEdit.exe"), + }) + { + startInfo.ArgumentList.Add(argument); + } + + Process.Start(startInfo); + break; + } + case AutoUpdateKind.LinuxTarball: + { + var script = Path.Combine(scriptFolder, "install-update.sh"); + File.WriteAllText(script, LinuxScript.Replace("\r\n", "\n")); + var startInfo = new ProcessStartInfo("/bin/sh") { UseShellExecute = false }; + foreach (var argument in new[] + { + script, processId.ToString(), downloadedFile, exeFolder, + Path.Combine(exeFolder, LinuxExecutable), + }) + { + startInfo.ArgumentList.Add(argument); + } + + Process.Start(startInfo); + break; + } + default: + throw new InvalidOperationException("Automatic update is not available for this installation"); + } + } + + /// + /// Waits for Subtitle Edit to exit, runs the installer elevated (one UAC prompt) and starts the + /// app again as the normal user - also when the UAC prompt was declined, so the user is never + /// left without a running Subtitle Edit. /SILENT shows Inno's progress window only. + /// + internal const string WindowsScript = """ + param([int]$ProcessId, [string]$Installer, [string]$App) + try { Wait-Process -Id $ProcessId -ErrorAction SilentlyContinue } catch { } + try { + Start-Process -FilePath $Installer -ArgumentList '/SILENT', '/SUPPRESSMSGBOXES', '/NORESTART', '/SP-' -Verb RunAs -Wait + Remove-Item -LiteralPath $Installer -ErrorAction SilentlyContinue + } catch { } + Start-Process -FilePath $App + """; + + /// + /// Waits for Subtitle Edit to exit, extracts the tarball over the install folder and starts the + /// app again. Ignores SIGHUP so closing the terminal Subtitle Edit was started from does not + /// stop it halfway. + /// + internal const string LinuxScript = """ + #!/bin/sh + trap '' HUP + pid="$1"; archive="$2"; dir="$3"; app="$4" + while kill -0 "$pid" 2>/dev/null; do sleep 1; done + if tar -xzf "$archive" -C "$dir"; then rm -f "$archive"; fi + cd "$dir" || exit 1 + nohup "$app" >/dev/null 2>&1 & + """; + + private static void TryDelete(string path) + { + try + { + File.Delete(path); + } + catch + { + // left in the temp folder - the next download overwrites it + } + } +} diff --git a/src/ui/Features/Help/CheckForUpdates/CheckForUpdatesViewModel.cs b/src/ui/Features/Help/CheckForUpdates/CheckForUpdatesViewModel.cs index aadce423015..483a7d80174 100644 --- a/src/ui/Features/Help/CheckForUpdates/CheckForUpdatesViewModel.cs +++ b/src/ui/Features/Help/CheckForUpdates/CheckForUpdatesViewModel.cs @@ -3,7 +3,11 @@ using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; using Nikse.SubtitleEdit.Logic; +using Avalonia.Threading; +using Nikse.SubtitleEdit.Features.Main.Layout; using Nikse.SubtitleEdit.Logic.Config; +using System; +using System.Threading; using System.Threading.Tasks; namespace Nikse.SubtitleEdit.Features.Help.CheckForUpdates; @@ -13,10 +17,14 @@ public partial class CheckForUpdatesViewModel : ObservableObject private const string ReleasesUrl = "https://github.com/SubtitleEdit/subtitleedit/releases"; private readonly IUpdateCheckService _updateCheckService; + private readonly IAutoUpdateService _autoUpdateService; + private string _latestVersion = string.Empty; + private CancellationTokenSource? _downloadCancellation; - public CheckForUpdatesViewModel(IUpdateCheckService updateCheckService) + public CheckForUpdatesViewModel(IUpdateCheckService updateCheckService, IAutoUpdateService autoUpdateService) { _updateCheckService = updateCheckService; + _autoUpdateService = autoUpdateService; } public Window? Window { get; set; } @@ -27,10 +35,22 @@ public CheckForUpdatesViewModel(IUpdateCheckService updateCheckService) [ObservableProperty] private bool _isDownloadLinkVisible; + [ObservableProperty] private bool _isInstallVisible; + + [ObservableProperty] private bool _isDownloading; + + [ObservableProperty] private double _downloadProgress; + public async Task CheckForUpdates() { + if (IsDownloading) + { + return; // re-activated while the update downloads + } + StatusText = Se.Language.Help.CheckForUpdatesChecking; IsDownloadLinkVisible = false; + IsInstallVisible = false; try { @@ -47,6 +67,8 @@ public async Task CheckForUpdates() { StatusText = string.Format(Se.Language.Help.CheckForUpdatesNewVersionAvailable, result.LatestVersion); IsDownloadLinkVisible = true; + IsInstallVisible = _autoUpdateService.Kind != AutoUpdateKind.None; + _latestVersion = result.LatestVersion; } else { @@ -70,6 +92,51 @@ private async Task OpenDownloadPage() UiUtil.OpenUrl(ReleasesUrl); } + [RelayCommand] + private async Task InstallUpdate() + { + if (IsDownloading || string.IsNullOrEmpty(_latestVersion)) + { + return; + } + + IsInstallVisible = false; + IsDownloading = true; + DownloadProgress = 0; + StatusText = string.Format(Se.Language.Help.CheckForUpdatesDownloadingX, _latestVersion); + _downloadCancellation = new CancellationTokenSource(); + var progress = new Progress(p => DownloadProgress = p * 100.0); + + try + { + var file = await _autoUpdateService.DownloadAsync(_latestVersion, progress, _downloadCancellation.Token); + _autoUpdateService.StartInstallAfterExit(file); + } + catch (OperationCanceledException) + { + return; // dialog closed while downloading + } + catch (Exception exception) + { + Se.LogError(exception, "Automatic update"); + IsDownloading = false; + StatusText = Se.Language.Help.CheckForUpdatesInstallFailed; + return; + } + + // The helper now waits for this process to exit. Closing the editor windows still asks + // about unsaved changes; if the user stays, the update installs whenever they quit. + IsDownloading = false; + StatusText = Se.Language.Help.CheckForUpdatesInstallsWhenClosed; + Window?.Close(); + Dispatcher.UIThread.Post(MainWindowFactory.CloseAllMainWindows); + } + + public void OnClosed() + { + _downloadCancellation?.Cancel(); + } + [RelayCommand] private void Ok() { diff --git a/src/ui/Features/Help/CheckForUpdates/CheckForUpdatesWindow.cs b/src/ui/Features/Help/CheckForUpdates/CheckForUpdatesWindow.cs index 1b078db9ef1..4e2c4ec7c8f 100644 --- a/src/ui/Features/Help/CheckForUpdates/CheckForUpdatesWindow.cs +++ b/src/ui/Features/Help/CheckForUpdates/CheckForUpdatesWindow.cs @@ -10,7 +10,7 @@ namespace Nikse.SubtitleEdit.Features.Help.CheckForUpdates; /// /// Check for updates in the editor look (see ): the status line with the -/// download action on top, the changelog in a field frame under it, OK in the button bar. +/// download/install actions on top, the changelog in a field frame under it, OK in the button bar. /// public class CheckForUpdatesWindow : Window { @@ -51,14 +51,37 @@ public CheckForUpdatesWindow(CheckForUpdatesViewModel vm) downloadButton.DataContext = vm; downloadButton.Bind(Button.IsVisibleProperty, new Binding(nameof(vm.IsDownloadLinkVisible))); + // In-app update (Windows installer, Linux tarball): download, verify, install after exit. + var installButton = EditorChrome.MakeTextToolButton(IconNames.Download, Se.Language.Help.CheckForUpdatesInstallAndRestart, vm.InstallUpdateCommand); + installButton.Foreground = EditorChrome.AccentBrush; + installButton.VerticalAlignment = VerticalAlignment.Center; + installButton.DataContext = vm; + installButton.Bind(Button.IsVisibleProperty, new Binding(nameof(vm.IsInstallVisible))); + + var downloadProgress = new ProgressBar + { + Minimum = 0, + Maximum = 100, + Width = 120, + MinWidth = 0, + ShowProgressText = true, + ProgressTextFormat = "{0:0}%", + VerticalAlignment = VerticalAlignment.Center, + DataContext = vm, + }; + downloadProgress.Bind(ProgressBar.ValueProperty, new Binding(nameof(vm.DownloadProgress))); + downloadProgress.Bind(ProgressBar.IsVisibleProperty, new Binding(nameof(vm.IsDownloading))); + var statusRow = new Grid { ColumnSpacing = 10, - ColumnDefinitions = new ColumnDefinitions("Auto,*,Auto"), + ColumnDefinitions = new ColumnDefinitions("Auto,*,Auto,Auto,Auto"), }; statusRow.Add(statusIcon, 0, 0); statusRow.Add(statusLabel, 0, 1); - statusRow.Add(downloadButton, 0, 2); + statusRow.Add(downloadProgress, 0, 2); + statusRow.Add(installButton, 0, 3); + statusRow.Add(downloadButton, 0, 4); var statusStrip = new Border { Child = statusRow, @@ -110,5 +133,6 @@ public CheckForUpdatesWindow(CheckForUpdatesViewModel vm) await vm.CheckForUpdates(); }; KeyDown += vm.OnKeyDown; + Closed += delegate { vm.OnClosed(); }; } } diff --git a/src/ui/Features/Main/Layout/MainWindowFactory.cs b/src/ui/Features/Main/Layout/MainWindowFactory.cs index 719f716d7d4..cf9e245bda5 100644 --- a/src/ui/Features/Main/Layout/MainWindowFactory.cs +++ b/src/ui/Features/Main/Layout/MainWindowFactory.cs @@ -1,7 +1,10 @@ +using Avalonia; using Avalonia.Controls; +using Avalonia.Controls.ApplicationLifetimes; using Nikse.SubtitleEdit.Logic; using Nikse.SubtitleEdit.Logic.Config; using System; +using System.Linq; namespace Nikse.SubtitleEdit.Features.Main.Layout; @@ -81,6 +84,23 @@ public static Window Create(bool isPrimary) return window; } + /// + /// Closes every editor window the way the user would - each one still asks about unsaved + /// changes, and the process exits when the last one is gone (see ). + /// + public static void CloseAllMainWindows() + { + if (Application.Current?.ApplicationLifetime is not IClassicDesktopStyleApplicationLifetime lifetime) + { + return; + } + + foreach (var window in lifetime.Windows.Where(p => p.Name == "MainWindow").ToList()) + { + window.Close(); + } + } + /// File > New window: opens another independent editor window. public static void OpenNewWindow() { diff --git a/src/ui/Logic/Config/Language/Help/LanguageHelp.cs b/src/ui/Logic/Config/Language/Help/LanguageHelp.cs index 5624d22e700..3eef0642227 100644 --- a/src/ui/Logic/Config/Language/Help/LanguageHelp.cs +++ b/src/ui/Logic/Config/Language/Help/LanguageHelp.cs @@ -9,6 +9,10 @@ public class LanguageHelp public string CheckForUpdatesNewVersionAvailable { get; set; } public string CheckForUpdatesUnableToCheck { get; set; } public string CheckForUpdatesDownloadNewVersion { get; set; } + public string CheckForUpdatesInstallAndRestart { get; set; } + public string CheckForUpdatesDownloadingX { get; set; } + public string CheckForUpdatesInstallFailed { get; set; } + public string CheckForUpdatesInstallsWhenClosed { get; set; } public LanguageHelp() { @@ -19,6 +23,10 @@ public LanguageHelp() CheckForUpdatesNewVersionAvailable = "New version available: {0}"; CheckForUpdatesUnableToCheck = "Unable to check for updates."; CheckForUpdatesDownloadNewVersion = "Download new version"; + CheckForUpdatesInstallAndRestart = "Install and restart"; + CheckForUpdatesDownloadingX = "Downloading {0}..."; + CheckForUpdatesInstallFailed = "Automatic update failed - please use \"Download new version\" instead."; + CheckForUpdatesInstallsWhenClosed = "The update will be installed when Subtitle Edit closes."; } } \ No newline at end of file diff --git a/tests/UI/Features/Help/AutoUpdateServiceTests.cs b/tests/UI/Features/Help/AutoUpdateServiceTests.cs new file mode 100644 index 00000000000..4114b3f538c --- /dev/null +++ b/tests/UI/Features/Help/AutoUpdateServiceTests.cs @@ -0,0 +1,279 @@ +using Nikse.SubtitleEdit.Features.Help.CheckForUpdates; +using System.Formats.Tar; +using System.IO.Compression; +using System.Runtime.InteropServices; +using System.Security.Cryptography; + +namespace Tests.Features.Help; + +public class AutoUpdateServiceTests +{ + private const string Folder = "/app"; + + private static AutoUpdateKind Detect( + bool isWindows = false, + bool isLinux = false, + Architecture architecture = Architecture.X64, + bool isPackageManaged = false, + bool writable = true, + params string[] files) + { + var existing = new HashSet(files.Select(f => f.StartsWith('/') ? f : Path.Combine(Folder, f))); + return AutoUpdateService.DetectKind(isWindows, isLinux, architecture, isPackageManaged, Folder, existing.Contains, _ => writable); + } + + [Fact] + public void DetectKind_WindowsInnoInstall_IsWindowsInstaller() + { + Assert.Equal(AutoUpdateKind.WindowsInstaller, Detect(isWindows: true, files: "unins000.exe")); + } + + [Fact] + public void DetectKind_WindowsPortableZip_IsNone() + { + Assert.Equal(AutoUpdateKind.None, Detect(isWindows: true, files: "SubtitleEdit.exe")); + } + + [Fact] + public void DetectKind_WindowsArm64_IsNone() + { + Assert.Equal(AutoUpdateKind.None, Detect(isWindows: true, architecture: Architecture.Arm64, files: "unins000.exe")); + } + + [Theory] + [InlineData(Architecture.X64)] + [InlineData(Architecture.Arm64)] + public void DetectKind_LinuxWritableTarball_IsLinuxTarball(Architecture architecture) + { + Assert.Equal(AutoUpdateKind.LinuxTarball, Detect(isLinux: true, architecture: architecture, files: ["SubtitleEdit", "/usr/bin/tar"])); + } + + [Fact] + public void DetectKind_LinuxReadOnlyFolder_IsNone() + { + Assert.Equal(AutoUpdateKind.None, Detect(isLinux: true, writable: false, files: ["SubtitleEdit", "/usr/bin/tar"])); + } + + [Fact] + public void DetectKind_LinuxFlatpakOrAppImage_IsNone() + { + Assert.Equal(AutoUpdateKind.None, Detect(isLinux: true, isPackageManaged: true, files: ["SubtitleEdit", "/usr/bin/tar"])); + } + + [Fact] + public void DetectKind_LinuxWithoutTar_IsNone() + { + Assert.Equal(AutoUpdateKind.None, Detect(isLinux: true, files: "SubtitleEdit")); + } + + [Fact] + public void DetectKind_MacOs_IsNone() + { + Assert.Equal(AutoUpdateKind.None, Detect(files: "SubtitleEdit")); + } + + [Theory] + [InlineData(AutoUpdateKind.WindowsInstaller, Architecture.X64, "SubtitleEdit-Windows-x64-Setup.exe")] + [InlineData(AutoUpdateKind.LinuxTarball, Architecture.X64, "SubtitleEdit-Linux-x64.tar.gz")] + [InlineData(AutoUpdateKind.LinuxTarball, Architecture.Arm64, "SubtitleEdit-Linux-ARM64.tar.gz")] + [InlineData(AutoUpdateKind.None, Architecture.X64, "")] + public void GetAssetName(AutoUpdateKind kind, Architecture architecture, string expected) + { + Assert.Equal(expected, AutoUpdateService.GetAssetName(kind, architecture)); + } + + private const string Digest = "93b487e115560da33c4557c780d387e311d93a6ab6abe000093576ccb9d44889"; + + private static string MakeReleaseJson(string name, string url, string digest) => + $$""" + {"tag_name":"v5.3.0","assets":[ + {"name":"SubtitleEdit-macOS-x64.dmg","browser_download_url":"https://github.com/SubtitleEdit/subtitleedit/releases/download/v5.3.0/SubtitleEdit-macOS-x64.dmg","digest":"sha256:{{Digest}}"}, + {"name":"{{name}}","browser_download_url":"{{url}}","digest":"{{digest}}"}]} + """; + + [Fact] + public void ParseReleaseAsset_PicksNamedAssetWithDigest() + { + const string url = "https://github.com/SubtitleEdit/subtitleedit/releases/download/v5.3.0/SubtitleEdit-Linux-x64.tar.gz"; + var json = MakeReleaseJson("SubtitleEdit-Linux-x64.tar.gz", url, "sha256:" + Digest.ToUpperInvariant()); + + var asset = AutoUpdateService.ParseReleaseAsset(json, "SubtitleEdit-Linux-x64.tar.gz"); + + Assert.NotNull(asset); + Assert.Equal(url, asset.DownloadUrl); + Assert.Equal(Digest, asset.Sha256); + } + + [Fact] + public void ParseReleaseAsset_NoDigest_ReturnsNull() + { + var json = MakeReleaseJson("SubtitleEdit-Linux-x64.tar.gz", + "https://github.com/SubtitleEdit/subtitleedit/releases/download/v5.3.0/SubtitleEdit-Linux-x64.tar.gz", ""); + + Assert.Null(AutoUpdateService.ParseReleaseAsset(json, "SubtitleEdit-Linux-x64.tar.gz")); + } + + [Fact] + public void ParseReleaseAsset_ForeignUrl_ReturnsNull() + { + var json = MakeReleaseJson("SubtitleEdit-Linux-x64.tar.gz", "https://example.com/SubtitleEdit-Linux-x64.tar.gz", "sha256:" + Digest); + + Assert.Null(AutoUpdateService.ParseReleaseAsset(json, "SubtitleEdit-Linux-x64.tar.gz")); + } + + [Fact] + public void ParseReleaseAsset_MissingAsset_ReturnsNull() + { + var json = MakeReleaseJson("SubtitleEdit-Linux-x64.tar.gz", + "https://github.com/SubtitleEdit/subtitleedit/releases/download/v5.3.0/SubtitleEdit-Linux-x64.tar.gz", "sha256:" + Digest); + + Assert.Null(AutoUpdateService.ParseReleaseAsset(json, "SubtitleEdit-Windows-x64-Setup.exe")); + } + + [Fact] + public async Task IsSha256MatchAsync_ComparesCaseInsensitive() + { + var path = Path.GetTempFileName(); + try + { + await File.WriteAllTextAsync(path, "hello"); + var hash = Convert.ToHexString(SHA256.HashData("hello"u8.ToArray())); + + Assert.True(await AutoUpdateService.IsSha256MatchAsync(path, hash.ToLowerInvariant(), CancellationToken.None)); + Assert.False(await AutoUpdateService.IsSha256MatchAsync(path, Digest, CancellationToken.None)); + } + finally + { + File.Delete(path); + } + } + + private static string MakeTarball(params TarEntry[] entries) + { + var path = Path.GetTempFileName(); + using var file = File.Create(path); + using var gzip = new GZipStream(file, CompressionLevel.Fastest); + using var writer = new TarWriter(gzip); + foreach (var entry in entries) + { + writer.WriteEntry(entry); + } + + return path; + } + + private static PaxTarEntry Entry(string name) => new(TarEntryType.RegularFile, name) { DataStream = new MemoryStream([1]) }; + + [Fact] + public void ValidateLinuxTarball_PublishLayout_Passes() + { + var path = MakeTarball(new PaxTarEntry(TarEntryType.Directory, "./"), Entry("./SubtitleEdit"), Entry("./libSkiaSharp.so")); + try + { + AutoUpdateService.ValidateLinuxTarball(path); + } + finally + { + File.Delete(path); + } + } + + [Theory] + [InlineData("../evil")] + [InlineData("/etc/evil")] + [InlineData("./sub/../../evil")] + public void ValidateLinuxTarball_UnsafePath_Throws(string name) + { + var path = MakeTarball(Entry("./SubtitleEdit"), Entry(name)); + try + { + Assert.Throws(() => AutoUpdateService.ValidateLinuxTarball(path)); + } + finally + { + File.Delete(path); + } + } + + [Fact] + public void ValidateLinuxTarball_SymbolicLink_Throws() + { + var path = MakeTarball(Entry("./SubtitleEdit"), new PaxTarEntry(TarEntryType.SymbolicLink, "./link") { LinkName = "/etc/passwd" }); + try + { + Assert.Throws(() => AutoUpdateService.ValidateLinuxTarball(path)); + } + finally + { + File.Delete(path); + } + } + + [Fact] + public void ValidateLinuxTarball_NoExecutable_Throws() + { + var path = MakeTarball(Entry("./readme.txt")); + try + { + Assert.Throws(() => AutoUpdateService.ValidateLinuxTarball(path)); + } + finally + { + File.Delete(path); + } + } + + [Fact] + public async Task LinuxScript_ExtractsOverFolderAfterExitAndRelaunches() + { + if (OperatingSystem.IsWindows()) + { + return; + } + + var root = Directory.CreateTempSubdirectory("se-update-test").FullName; + try + { + var appFolder = Directory.CreateDirectory(Path.Combine(root, "app")).FullName; + await File.WriteAllTextAsync(Path.Combine(appFolder, "old.txt"), "old"); + await File.WriteAllTextAsync(Path.Combine(appFolder, "version.txt"), "1"); + + // The "new version": its executable records that it was started from the install folder. + var staging = Directory.CreateDirectory(Path.Combine(root, "staging")).FullName; + var marker = Path.Combine(root, "relaunched.txt"); + await File.WriteAllTextAsync(Path.Combine(staging, "SubtitleEdit"), $"#!/bin/sh\npwd > '{marker}'\n"); + File.SetUnixFileMode(Path.Combine(staging, "SubtitleEdit"), (UnixFileMode)0b111_101_101); + await File.WriteAllTextAsync(Path.Combine(staging, "version.txt"), "2"); + var archive = Path.Combine(root, "update.tar.gz"); + await RunAsync("tar", "-czf", archive, "-C", staging, "."); + + var exited = System.Diagnostics.Process.Start("/bin/sh", ["-c", "exit 0"]); + await exited.WaitForExitAsync(); + + var script = Path.Combine(root, "install-update.sh"); + await File.WriteAllTextAsync(script, AutoUpdateService.LinuxScript.Replace("\r\n", "\n")); + await RunAsync("/bin/sh", script, exited.Id.ToString(), archive, appFolder, Path.Combine(appFolder, "SubtitleEdit")); + + for (var i = 0; i < 50 && !File.Exists(marker); i++) + { + await Task.Delay(100); + } + + Assert.Equal("2", await File.ReadAllTextAsync(Path.Combine(appFolder, "version.txt"))); + Assert.True(File.Exists(Path.Combine(appFolder, "old.txt"))); // user files in the folder survive + Assert.False(File.Exists(archive)); + Assert.True(File.Exists(marker)); + } + finally + { + Directory.Delete(root, true); + } + } + + private static async Task RunAsync(string fileName, params string[] arguments) + { + using var process = System.Diagnostics.Process.Start(fileName, arguments); + await process.WaitForExitAsync(); + Assert.Equal(0, process.ExitCode); + } +} From 10d12ec323c025bd4a80b5748a0f88bae32a7101 Mon Sep 17 00:00:00 2001 From: niksedk Date: Fri, 16 Oct 2026 15:29:11 +0200 Subject: [PATCH 263/306] Check for updates: install and restart on macOS Adds the macOS app bundle to in-app updating. When the bundle sits in a user-writable folder (not the mounted dmg, not App Translocation), the release dmg is downloaded and checked against GitHub's SHA-256 digest, mounted read-only, and its app copied out with ditto. The copy must pass codesign --verify --deep --strict and carry the same Team ID as the running app. After Subtitle Edit exits, a helper renames the old bundle aside, moves the new one in under the same name (restoring the old one if the move fails) and opens it. Our own download has no quarantine flag, so there is no Gatekeeper prompt. Co-Authored-By: Claude Opus 5.5 --- .../Help/CheckForUpdates/AutoUpdateService.cs | 185 +++++++++++++++++- .../Features/Help/AutoUpdateServiceTests.cs | 100 +++++++++- 2 files changed, 280 insertions(+), 5 deletions(-) diff --git a/src/ui/Features/Help/CheckForUpdates/AutoUpdateService.cs b/src/ui/Features/Help/CheckForUpdates/AutoUpdateService.cs index 2bcf3db7142..f509d30e87b 100644 --- a/src/ui/Features/Help/CheckForUpdates/AutoUpdateService.cs +++ b/src/ui/Features/Help/CheckForUpdates/AutoUpdateService.cs @@ -19,7 +19,7 @@ namespace Nikse.SubtitleEdit.Features.Help.CheckForUpdates; /// How this copy of Subtitle Edit can update itself, if at all. public enum AutoUpdateKind { - /// No in-app update - the user gets the download page (zip, dmg, Flatpak, AUR, ...). + /// No in-app update - the user gets the download page (zip, Flatpak, AUR, ...). None, /// Installed by the Inno Setup installer: run the new installer silently. @@ -27,6 +27,9 @@ public enum AutoUpdateKind /// Unpacked Linux tarball in a user-writable folder: extract the new tarball over it. LinuxTarball, + + /// App bundle in a user-writable folder: swap in the bundle from the new dmg. + MacAppBundle, } public class AutoUpdateAsset @@ -65,6 +68,8 @@ public class AutoUpdateService : IAutoUpdateService internal const string LinuxX64Asset = "SubtitleEdit-Linux-x64.tar.gz"; internal const string LinuxArm64Asset = "SubtitleEdit-Linux-ARM64.tar.gz"; internal const string LinuxExecutable = "SubtitleEdit"; + internal const string MacArm64Asset = "SubtitleEdit-macOS-ARM64.dmg"; + internal const string MacX64Asset = "SubtitleEdit-macOS-x64.dmg"; private readonly HttpClient _httpClient; private readonly Lazy _kind; @@ -84,6 +89,7 @@ private static AutoUpdateKind DetectCurrentKind() return DetectKind( OperatingSystem.IsWindows(), OperatingSystem.IsLinux(), + OperatingSystem.IsMacOS(), RuntimeInformation.ProcessArchitecture, UpdateCheckService.IsStoreManagedInstall || !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("APPIMAGE")), @@ -101,6 +107,7 @@ private static AutoUpdateKind DetectCurrentKind() internal static AutoUpdateKind DetectKind( bool isWindows, bool isLinux, + bool isMacOs, Architecture architecture, bool isPackageManaged, string exeFolder, @@ -132,6 +139,17 @@ architecture is Architecture.X64 or Architecture.Arm64 && return AutoUpdateKind.LinuxTarball; } + // Swapping the bundle renames it inside its parent folder, so that must be writable - + // which also rules out running straight from the mounted dmg and App Translocation. + if (isMacOs && + architecture is Architecture.X64 or Architecture.Arm64 && + GetMacAppBundle(exeFolder) is { } bundle && + !bundle.Contains("/AppTranslocation/", StringComparison.Ordinal) && + isDirectoryWritable(Path.GetDirectoryName(bundle)!)) + { + return AutoUpdateKind.MacAppBundle; + } + return AutoUpdateKind.None; } @@ -141,10 +159,25 @@ internal static string GetAssetName(AutoUpdateKind kind, Architecture architectu { AutoUpdateKind.WindowsInstaller => WindowsInstallerAsset, AutoUpdateKind.LinuxTarball => architecture == Architecture.Arm64 ? LinuxArm64Asset : LinuxX64Asset, + AutoUpdateKind.MacAppBundle => architecture == Architecture.Arm64 ? MacArm64Asset : MacX64Asset, _ => string.Empty, }; } + /// "/Applications/Subtitle Edit.app" for ".../Subtitle Edit.app/Contents/MacOS/", else null. + internal static string? GetMacAppBundle(string exeFolder) + { + var macOsFolder = exeFolder.TrimEnd('/'); + var contents = Path.GetDirectoryName(macOsFolder); + var bundle = contents == null ? null : Path.GetDirectoryName(contents); + return Path.GetFileName(macOsFolder) == "MacOS" && + Path.GetFileName(contents) == "Contents" && + bundle != null && + bundle.EndsWith(".app", StringComparison.OrdinalIgnoreCase) + ? bundle + : null; + } + private static bool IsDirectoryWritable(string folder) { try @@ -191,6 +224,15 @@ public async Task DownloadAsync(string version, IProgress? progre await Task.Run(() => ValidateLinuxTarball(path), cancellationToken); } + if (Kind == AutoUpdateKind.MacAppBundle) + { + var currentBundle = GetMacAppBundle(AppContext.BaseDirectory) + ?? throw new InvalidOperationException("Not running from an app bundle"); + var stagedApp = await PrepareMacAppBundleAsync(path, folder, currentBundle, cancellationToken); + TryDelete(path); + return stagedApp; + } + return path; } @@ -300,6 +342,108 @@ internal static void ValidateLinuxTarball(string path) } } + /// + /// Copies the app bundle out of the dmg into the staging folder and checks it is signed + /// intact by the same Apple team as the running app - so the bundle swapped in after exit + /// is the genuine, notarized build. Our own download carries no quarantine flag, so the + /// new bundle opens without a Gatekeeper prompt. + /// + internal static async Task PrepareMacAppBundleAsync(string dmgPath, string folder, string currentBundle, CancellationToken cancellationToken) + { + var currentTeam = await GetMacTeamIdentifierAsync(currentBundle, cancellationToken); + if (string.IsNullOrEmpty(currentTeam)) + { + throw new InvalidOperationException("The running app is not signed - cannot verify the update"); + } + + var mountPoint = Path.Combine(folder, "mount"); + var staging = Path.Combine(folder, "staged"); + if (Directory.Exists(staging)) + { + Directory.Delete(staging, true); + } + + Directory.CreateDirectory(mountPoint); + Directory.CreateDirectory(staging); + + await RunAsync("hdiutil", cancellationToken, "attach", "-nobrowse", "-readonly", "-noautoopen", "-mountpoint", mountPoint, dmgPath); + string stagedApp; + try + { + var appInDmg = Directory.GetDirectories(mountPoint, "*.app").FirstOrDefault() + ?? throw new InvalidDataException("No app bundle in the update dmg"); + stagedApp = Path.Combine(staging, Path.GetFileName(appInDmg)); + await RunAsync("ditto", cancellationToken, appInDmg, stagedApp); + } + finally + { + await RunAsync("hdiutil", CancellationToken.None, "detach", mountPoint, "-force"); + } + + await RunAsync("codesign", cancellationToken, "--verify", "--deep", "--strict", stagedApp); + var newTeam = await GetMacTeamIdentifierAsync(stagedApp, cancellationToken); + if (!string.Equals(newTeam, currentTeam, StringComparison.Ordinal)) + { + Directory.Delete(staging, true); + throw new InvalidDataException($"Update is signed by team '{newTeam}', expected '{currentTeam}'"); + } + + return stagedApp; + } + + private static async Task GetMacTeamIdentifierAsync(string bundle, CancellationToken cancellationToken) + { + // codesign -d writes its details to stderr; an unsigned bundle exits non-zero. + var (exitCode, output) = await RunAsync("codesign", cancellationToken, false, "-dv", "--verbose=2", bundle); + return exitCode == 0 ? ParseTeamIdentifier(output) : string.Empty; + } + + internal static string ParseTeamIdentifier(string codesignOutput) + { + foreach (var line in codesignOutput.Split('\n')) + { + const string prefix = "TeamIdentifier="; + var trimmed = line.Trim(); + if (trimmed.StartsWith(prefix, StringComparison.Ordinal)) + { + var team = trimmed.Substring(prefix.Length); + return team == "not set" ? string.Empty : team; + } + } + + return string.Empty; + } + + private static Task RunAsync(string fileName, CancellationToken cancellationToken, params string[] arguments) => + RunAsync(fileName, cancellationToken, true, arguments); + + private static async Task<(int ExitCode, string Output)> RunAsync(string fileName, CancellationToken cancellationToken, bool throwOnError, params string[] arguments) + { + var startInfo = new ProcessStartInfo(fileName) + { + UseShellExecute = false, + RedirectStandardOutput = true, + RedirectStandardError = true, + CreateNoWindow = true, + }; + foreach (var argument in arguments) + { + startInfo.ArgumentList.Add(argument); + } + + using var process = Process.Start(startInfo) ?? throw new InvalidOperationException("Could not start " + fileName); + var stdout = process.StandardOutput.ReadToEndAsync(cancellationToken); + var stderr = process.StandardError.ReadToEndAsync(cancellationToken); + await process.WaitForExitAsync(cancellationToken); + var output = await stdout + await stderr; + if (throwOnError && process.ExitCode != 0) + { + throw new InvalidOperationException($"{fileName} failed with exit code {process.ExitCode}: {output.Trim()}"); + } + + return (process.ExitCode, output); + } + public void StartInstallAfterExit(string downloadedFile) { var processId = Environment.ProcessId; @@ -347,6 +491,22 @@ public void StartInstallAfterExit(string downloadedFile) Process.Start(startInfo); break; } + case AutoUpdateKind.MacAppBundle: + { + var script = Path.Combine(Path.GetDirectoryName(scriptFolder)!, "install-update.sh"); + File.WriteAllText(script, MacScript.Replace("\r\n", "\n")); + var startInfo = new ProcessStartInfo("/bin/sh") { UseShellExecute = false }; + foreach (var argument in new[] + { + script, processId.ToString(), downloadedFile, GetMacAppBundle(AppContext.BaseDirectory)!, + }) + { + startInfo.ArgumentList.Add(argument); + } + + Process.Start(startInfo); + break; + } default: throw new InvalidOperationException("Automatic update is not available for this installation"); } @@ -382,6 +542,29 @@ public void StartInstallAfterExit(string downloadedFile) nohup "$app" >/dev/null 2>&1 & """; + /// + /// Waits for Subtitle Edit to exit, then swaps the verified bundle in under the same name: + /// the old bundle is renamed aside first and restored if the move fails, so there is always + /// a working app. Opens the app again either way. + /// + internal const string MacScript = """ + #!/bin/sh + trap '' HUP + pid="$1"; staged="$2"; target="$3" + while kill -0 "$pid" 2>/dev/null; do sleep 1; done + backup="$target.se-update-old" + rm -rf "$backup" + if mv "$target" "$backup"; then + if mv "$staged" "$target"; then + rm -rf "$backup" + else + rm -rf "$target" + mv "$backup" "$target" + fi + fi + open "$target" + """; + private static void TryDelete(string path) { try diff --git a/tests/UI/Features/Help/AutoUpdateServiceTests.cs b/tests/UI/Features/Help/AutoUpdateServiceTests.cs index 4114b3f538c..beb78e71ab9 100644 --- a/tests/UI/Features/Help/AutoUpdateServiceTests.cs +++ b/tests/UI/Features/Help/AutoUpdateServiceTests.cs @@ -13,13 +13,15 @@ public class AutoUpdateServiceTests private static AutoUpdateKind Detect( bool isWindows = false, bool isLinux = false, + bool isMacOs = false, Architecture architecture = Architecture.X64, bool isPackageManaged = false, bool writable = true, + string folder = Folder, params string[] files) { - var existing = new HashSet(files.Select(f => f.StartsWith('/') ? f : Path.Combine(Folder, f))); - return AutoUpdateService.DetectKind(isWindows, isLinux, architecture, isPackageManaged, Folder, existing.Contains, _ => writable); + var existing = new HashSet(files.Select(f => f.StartsWith('/') ? f : Path.Combine(folder, f))); + return AutoUpdateService.DetectKind(isWindows, isLinux, isMacOs, architecture, isPackageManaged, folder, existing.Contains, _ => writable); } [Fact] @@ -66,16 +68,60 @@ public void DetectKind_LinuxWithoutTar_IsNone() Assert.Equal(AutoUpdateKind.None, Detect(isLinux: true, files: "SubtitleEdit")); } + private const string MacExeFolder = "/Applications/Subtitle Edit.app/Contents/MacOS/"; + + [Theory] + [InlineData(Architecture.X64)] + [InlineData(Architecture.Arm64)] + public void DetectKind_MacAppBundleInWritableFolder_IsMacAppBundle(Architecture architecture) + { + Assert.Equal(AutoUpdateKind.MacAppBundle, Detect(isMacOs: true, architecture: architecture, folder: MacExeFolder)); + } + [Fact] - public void DetectKind_MacOs_IsNone() + public void DetectKind_MacReadOnlyFolder_IsNone() { - Assert.Equal(AutoUpdateKind.None, Detect(files: "SubtitleEdit")); + Assert.Equal(AutoUpdateKind.None, Detect(isMacOs: true, writable: false, folder: MacExeFolder)); + } + + [Fact] + public void DetectKind_MacTranslocated_IsNone() + { + Assert.Equal(AutoUpdateKind.None, Detect(isMacOs: true, + folder: "/private/var/folders/x/AppTranslocation/ABC/d/Subtitle Edit.app/Contents/MacOS/")); + } + + [Fact] + public void DetectKind_MacNotInBundle_IsNone() + { + Assert.Equal(AutoUpdateKind.None, Detect(isMacOs: true, folder: "/Users/me/se/bin/Debug/net10.0/")); + } + + [Theory] + [InlineData("/Applications/Subtitle Edit.app/Contents/MacOS/", "/Applications/Subtitle Edit.app")] + [InlineData("/Users/me/Apps/SE.app/Contents/MacOS", "/Users/me/Apps/SE.app")] + [InlineData("/Users/me/se/bin/Debug/net10.0/", null)] + [InlineData("/Applications/Subtitle Edit/Contents/MacOS/", null)] + public void GetMacAppBundle(string exeFolder, string? expected) + { + Assert.Equal(expected, AutoUpdateService.GetMacAppBundle(exeFolder)); + } + + [Theory] + [InlineData("Identifier=dk.nikse.subtitleedit\nAuthority=Developer ID Application: X (MFC9L2T4RB)\nTeamIdentifier=MFC9L2T4RB\n", "MFC9L2T4RB")] + [InlineData("Identifier=x\nSignature=adhoc\nTeamIdentifier=not set\n", "")] + [InlineData("code object is not signed at all\n", "")] + public void ParseTeamIdentifier(string output, string expected) + { + Assert.Equal(expected, AutoUpdateService.ParseTeamIdentifier(output)); } [Theory] [InlineData(AutoUpdateKind.WindowsInstaller, Architecture.X64, "SubtitleEdit-Windows-x64-Setup.exe")] [InlineData(AutoUpdateKind.LinuxTarball, Architecture.X64, "SubtitleEdit-Linux-x64.tar.gz")] [InlineData(AutoUpdateKind.LinuxTarball, Architecture.Arm64, "SubtitleEdit-Linux-ARM64.tar.gz")] + [InlineData(AutoUpdateKind.MacAppBundle, Architecture.X64, "SubtitleEdit-macOS-x64.dmg")] + [InlineData(AutoUpdateKind.MacAppBundle, Architecture.Arm64, "SubtitleEdit-macOS-ARM64.dmg")] [InlineData(AutoUpdateKind.None, Architecture.X64, "")] public void GetAssetName(AutoUpdateKind kind, Architecture architecture, string expected) { @@ -270,6 +316,52 @@ public async Task LinuxScript_ExtractsOverFolderAfterExitAndRelaunches() } } + [Fact] + public async Task MacScript_SwapsBundleAfterExitAndOpensIt() + { + if (OperatingSystem.IsWindows()) + { + return; + } + + var root = Directory.CreateTempSubdirectory("se-update-test").FullName; + try + { + var target = Directory.CreateDirectory(Path.Combine(root, "Applications", "Subtitle Edit.app", "Contents")).Parent!.FullName; + await File.WriteAllTextAsync(Path.Combine(target, "Contents", "version.txt"), "1"); + var staged = Directory.CreateDirectory(Path.Combine(root, "staged", "Subtitle Edit.app", "Contents")).Parent!.FullName; + await File.WriteAllTextAsync(Path.Combine(staged, "Contents", "version.txt"), "2"); + + // A fake "open" first on PATH records what would be launched. + var bin = Directory.CreateDirectory(Path.Combine(root, "bin")).FullName; + var marker = Path.Combine(root, "opened.txt"); + await File.WriteAllTextAsync(Path.Combine(bin, "open"), $"#!/bin/sh\necho \"$1\" > '{marker}'\n"); + File.SetUnixFileMode(Path.Combine(bin, "open"), (UnixFileMode)0b111_101_101); + + var exited = System.Diagnostics.Process.Start("/bin/sh", ["-c", "exit 0"]); + await exited.WaitForExitAsync(); + + var script = Path.Combine(root, "install-update.sh"); + await File.WriteAllTextAsync(script, AutoUpdateService.MacScript.Replace("\r\n", "\n")); + var startInfo = new System.Diagnostics.ProcessStartInfo("/bin/sh", [script, exited.Id.ToString(), staged, target]); + startInfo.Environment["PATH"] = bin + ":" + Environment.GetEnvironmentVariable("PATH"); + using (var process = System.Diagnostics.Process.Start(startInfo)!) + { + await process.WaitForExitAsync(); + Assert.Equal(0, process.ExitCode); + } + + Assert.Equal("2", await File.ReadAllTextAsync(Path.Combine(target, "Contents", "version.txt"))); + Assert.False(Directory.Exists(staged)); + Assert.False(Directory.Exists(target + ".se-update-old")); + Assert.Equal(target, (await File.ReadAllTextAsync(marker)).Trim()); + } + finally + { + Directory.Delete(root, true); + } + } + private static async Task RunAsync(string fileName, params string[] arguments) { using var process = System.Diagnostics.Process.Start(fileName, arguments); From e49ba65d7fa3068f965db282ff4e46cbaab9648b Mon Sep 17 00:00:00 2001 From: Ivandro Jao Date: Fri, 16 Oct 2026 14:33:34 +0100 Subject: [PATCH 264/306] Spell check - get dictionaries: add Uninstall button Shown only when the selected dictionary is installed (and no download is running). Deletes the entry's .dic files plus the matching .aff files, or the Voikko folder for Finnish (Voikko); user word lists and names files are kept. Installed detection and uninstall share one file-matching helper. The legacy prefix match now requires a separator after the prefix, so Khmer "km" no longer matches Kurmanji "kmr_Latn.dic". Co-Authored-By: Claude Opus 5.5 (1M context) --- .../GetDictionariesViewModel.cs | 110 +++++++++++++++--- .../GetDictionaries/GetDictionariesWindow.cs | 7 +- .../Logic/Config/Language/LanguageGeneral.cs | 4 + .../Language/SpellCheck/LanguageSpellCheck.cs | 2 + 4 files changed, 106 insertions(+), 17 deletions(-) diff --git a/src/ui/Features/SpellCheck/GetDictionaries/GetDictionariesViewModel.cs b/src/ui/Features/SpellCheck/GetDictionaries/GetDictionariesViewModel.cs index 043257770f5..340bdb1133d 100644 --- a/src/ui/Features/SpellCheck/GetDictionaries/GetDictionariesViewModel.cs +++ b/src/ui/Features/SpellCheck/GetDictionaries/GetDictionariesViewModel.cs @@ -4,6 +4,7 @@ using Avalonia.Threading; using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; +using Nikse.SubtitleEdit.Features.Shared; using Nikse.SubtitleEdit.Logic; using Nikse.SubtitleEdit.Logic.Compression; using Nikse.SubtitleEdit.Logic.Config; @@ -30,6 +31,7 @@ public partial class GetDictionariesViewModel : ObservableObject, IClosingCleanu [ObservableProperty] private double _progress; [ObservableProperty] private string _statusText; [ObservableProperty] private bool _isDownloadEnabled; + [ObservableProperty] private bool _isUninstallVisible; [ObservableProperty] private bool _isProgressVisible; [ObservableProperty] private double _progressOpacity; [ObservableProperty] private string _selectedStatusText; @@ -387,11 +389,29 @@ partial void OnSelectedDictionaryChanged(GetSpellCheckDictionaryDisplay? value) UpdateSelectedStatus(); } + partial void OnIsDownloadEnabledChanged(bool value) + { + UpdateSelectedStatus(); + } + /// /// Marks each dictionary as installed or not by checking the dictionaries folder, /// and updates the green/gray status dots accordingly. /// private void RefreshInstalledStatus() + { + var installedDicFiles = GetInstalledDicFileNames(); + foreach (var dictionary in Dictionaries) + { + dictionary.IsInstalled = dictionary.Voikko + ? VoikkoSpellChecker.HasDictionary(Se.DictionariesFolder) + : GetInstalledDicFiles(dictionary, installedDicFiles).Count > 0; + } + + UpdateSelectedStatus(); + } + + private static List GetInstalledDicFileNames() { var installedDicFiles = new List(); if (Directory.Exists(Se.DictionariesFolder)) @@ -402,35 +422,30 @@ private void RefreshInstalledStatus() } } - foreach (var dictionary in Dictionaries) - { - dictionary.IsInstalled = IsEntryInstalled(dictionary, installedDicFiles); - } - - UpdateSelectedStatus(); + return installedDicFiles; } private void UpdateSelectedStatus() { var selected = SelectedDictionary; + IsUninstallVisible = selected is { IsInstalled: true } && IsDownloadEnabled; if (selected == null) { SelectedStatusText = string.Empty; DownloadButtonText = Se.Language.General.Download; return; } - + SelectedStatusText = selected.IsInstalled ? Se.Language.General.Installed : Se.Language.General.NotInstalled; DownloadButtonText = selected.IsInstalled ? Se.Language.General.Redownload : Se.Language.General.Download; } - private static bool IsEntryInstalled(GetSpellCheckDictionaryDisplay entry, List installedDicFiles) + /// + /// The installed .dic file names (from ) that belong to a + /// Hunspell entry. Used both for the installed state and for what Uninstall deletes. + /// + private static List GetInstalledDicFiles(GetSpellCheckDictionaryDisplay entry, List installedDicFiles) { - if (entry.Voikko) - { - return VoikkoSpellChecker.HasDictionary(Se.DictionariesFolder); - } - var dicNames = entry.Files .Select(GetFileNameFromUrl) .Where(name => name.EndsWith(".dic", StringComparison.OrdinalIgnoreCase)) @@ -438,11 +453,20 @@ private static bool IsEntryInstalled(GetSpellCheckDictionaryDisplay entry, List< if (dicNames.Count > 0) { - return dicNames.Any(name => installedDicFiles.Any(f => f.Equals(name, StringComparison.OrdinalIgnoreCase))); + return installedDicFiles + .Where(f => dicNames.Any(name => f.Equals(name, StringComparison.OrdinalIgnoreCase))) + .ToList(); + } + + if (!LegacyDicPrefixes.TryGetValue(entry.EnglishName, out var prefix)) + { + return new List(); } - return LegacyDicPrefixes.TryGetValue(entry.EnglishName, out var prefix) && - installedDicFiles.Any(f => f.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)); + // The prefix must be followed by a separator, so e.g. Khmer "km" does not match Kurmanji "kmr_Latn.dic". + return installedDicFiles + .Where(f => f.StartsWith(prefix, StringComparison.OrdinalIgnoreCase) && "_-.".Contains(f[prefix.Length])) + .ToList(); } [RelayCommand] @@ -470,6 +494,60 @@ private void Download() _downloadTask = DownloadAndUnpackAsync(selected, downloadProgress, _cancellationTokenSource.Token); } + /// + /// Deletes the selected dictionary: its .dic files plus the .aff files with the same name, or + /// the whole Voikko folder. User word lists and names files in the folder are kept. + /// + [RelayCommand] + private async Task Uninstall() + { + var selected = SelectedDictionary; + if (selected is not { IsInstalled: true } || Window == null) + { + return; + } + + var answer = await MessageBox.Show( + Window, + Se.Language.SpellCheck.GetDictionariesTitle, + string.Format(Se.Language.SpellCheck.UninstallDictionaryX, selected.DisplayName), + MessageBoxButtons.YesNo, + MessageBoxIcon.Question); + if (answer != MessageBoxResult.Yes) + { + return; + } + + try + { + if (selected.Voikko) + { + var voikkoFolder = VoikkoSpellChecker.GetVoikkoFolder(Se.DictionariesFolder); + if (Directory.Exists(voikkoFolder)) + { + Directory.Delete(voikkoFolder, true); + } + } + else + { + foreach (var dicFile in GetInstalledDicFiles(selected, GetInstalledDicFileNames())) + { + var dicFileName = Path.Combine(Se.DictionariesFolder, dicFile); + File.Delete(dicFileName); + File.Delete(Path.ChangeExtension(dicFileName, ".aff")); + } + } + + StatusText = Se.Language.General.Uninstalled; + } + catch (Exception exception) + { + await MessageBox.Show(Window, Se.Language.General.Error, exception.Message, MessageBoxButtons.OK, MessageBoxIcon.Error); + } + + RefreshInstalledStatus(); + } + [RelayCommand] private async Task OpenFolder() { diff --git a/src/ui/Features/SpellCheck/GetDictionaries/GetDictionariesWindow.cs b/src/ui/Features/SpellCheck/GetDictionaries/GetDictionariesWindow.cs index 22d959ece25..6b9fde51e64 100644 --- a/src/ui/Features/SpellCheck/GetDictionaries/GetDictionariesWindow.cs +++ b/src/ui/Features/SpellCheck/GetDictionaries/GetDictionariesWindow.cs @@ -39,6 +39,11 @@ public GetDictionariesWindow(GetDictionariesViewModel vm) downloadButton.Classes.Add("primary"); AutomationProperties.SetName(downloadButton, Se.Language.General.Download); + var uninstallButton = UiUtil.MakeButton(Se.Language.General.Uninstall, vm.UninstallCommand) + .WithIconLeft(IconNames.Trash) + .WithBindIsVisible(nameof(vm.IsUninstallVisible)); + AutomationProperties.SetName(uninstallButton, Se.Language.General.Uninstall); + var close = UiUtil.MakeButton(Se.Language.General.Close, vm.OkCommand) .WithBindIsVisible(nameof(vm.IsDownloadEnabled)); var cancel = UiUtil.MakeButtonCancel(vm.CancelCommand) @@ -64,7 +69,7 @@ public GetDictionariesWindow(GetDictionariesViewModel vm) }; root.Add(new Border { Background = EditorChrome.PanelBrush, Child = sections }, 0, 0); root.Add(BuildProgress(vm), 1, 0); - root.Add(EditorChrome.MakeDockedButtonBar(BuildStatus(vm), close, cancel, downloadButton), 2, 0); + root.Add(EditorChrome.MakeDockedButtonBar(BuildStatus(vm), close, cancel, uninstallButton, downloadButton), 2, 0); Content = root; UiUtil.FocusOnFirstActivation(this, downloadButton); // hack to make OnKeyDown work diff --git a/src/ui/Logic/Config/Language/LanguageGeneral.cs b/src/ui/Logic/Config/Language/LanguageGeneral.cs index 62a7d28e032..313e6c1aba3 100644 --- a/src/ui/Logic/Config/Language/LanguageGeneral.cs +++ b/src/ui/Logic/Config/Language/LanguageGeneral.cs @@ -714,6 +714,8 @@ public class LanguageGeneral public string Undelete { get; set; } public string Underline { get; set; } public string Undo { get; set; } + public string Uninstall { get; set; } + public string Uninstalled { get; set; } public string Unknown { get; set; } public string UnknownNoInstallRecord { get; set; } public string UnknownSubtitleFormat { get; set; } @@ -1567,6 +1569,8 @@ public LanguageGeneral() Undelete = "Undelete"; Underline = "Underline"; Undo = "Undo"; + Uninstall = "Uninstall"; + Uninstalled = "Uninstalled"; Unknown = "Unknown"; UnknownNoInstallRecord = "Unknown (no install record)"; UnknownSubtitleFormat = "Unknown subtitle format"; diff --git a/src/ui/Logic/Config/Language/SpellCheck/LanguageSpellCheck.cs b/src/ui/Logic/Config/Language/SpellCheck/LanguageSpellCheck.cs index 1d4a2561b94..0e97dc0b79d 100644 --- a/src/ui/Logic/Config/Language/SpellCheck/LanguageSpellCheck.cs +++ b/src/ui/Logic/Config/Language/SpellCheck/LanguageSpellCheck.cs @@ -7,6 +7,7 @@ public class LanguageSpellCheck public string SpellCheck { get; set; } public string GetDictionariesTitle { get; set; } public string GetDictionaryInstructions { get; set; } + public string UninstallDictionaryX { get; set; } public string AddNameToUserDictionary { get; set; } public string AddNameToNamesList { get; set; } public string AddNamesToNamesList { get; set; } @@ -65,6 +66,7 @@ public LanguageSpellCheck() SpellCheck = "Spell check"; GetDictionariesTitle = "Spell check - get dictionaries"; GetDictionaryInstructions = "Choose your language and click download"; + UninstallDictionaryX = "Uninstall dictionary \"{0}\"?"; AddNameToUserDictionary = "Add name to user dictionary"; AddNameToNamesList = "Add name to names list"; AddNamesToNamesList = "Add names to names list"; From 9617b6f6fa3c4c76e0328ab6a9a90cf326b1b8a3 Mon Sep 17 00:00:00 2001 From: niksedk Date: Fri, 16 Oct 2026 15:42:54 +0200 Subject: [PATCH 265/306] Video > Sync audio track: shift one audio track with live preview Plays the video in its own player with the chosen audio track (picker shown when there is more than one) delayed by an offset - mpv's audio-delay, live - with nudge buttons, then saves a copy of the video with only that track moved: ffmpeg stream copy, every stream mapped in its original order so the track keeps its place, language and flags. Earlier (negative) offsets allow negative timestamps - ffmpeg's default shifted the whole .mkv, video included, out of step with the subtitles - and on ffmpeg 5.1+ drop the audio packets before zero (noise bsf). Verified with a beep at 2.0 s on ffmpeg 4.4 and 9.0, .mkv and .mp4. FfmpegMediaInfo2 now records each stream's index. Co-Authored-By: Claude Opus 5.5 --- src/ui/DependencyInjectionExtensions.cs | 2 + src/ui/Features/Main/Layout/InitMenu.cs | 5 + .../Features/Main/Layout/InitNativeMacMenu.cs | 1 + src/ui/Features/Main/MainViewModel.cs | 26 ++ .../SyncAudioTrack/SyncAudioTrackViewModel.cs | 326 ++++++++++++++++++ .../SyncAudioTrack/SyncAudioTrackWindow.cs | 156 +++++++++ .../Config/Language/Video/LanguageVideo.cs | 18 + src/ui/Logic/Media/FfmpegGenerator.cs | 121 +++++++ src/ui/Logic/Media/FfmpegMediainfo2.cs | 15 +- src/ui/Logic/VideoPlayers/IVideoPlayer.cs | 11 + .../LibMpvDynamic/LibMpvDynamicPlayer.cs | 17 + .../SyncAudioTrackViewModelTests.cs | 67 ++++ tests/UI/Logic/FfmpegShiftAudioTrackTests.cs | 78 +++++ 13 files changed, 838 insertions(+), 5 deletions(-) create mode 100644 src/ui/Features/Video/SyncAudioTrack/SyncAudioTrackViewModel.cs create mode 100644 src/ui/Features/Video/SyncAudioTrack/SyncAudioTrackWindow.cs create mode 100644 tests/UI/Features/Video/SyncAudioTrack/SyncAudioTrackViewModelTests.cs create mode 100644 tests/UI/Logic/FfmpegShiftAudioTrackTests.cs diff --git a/src/ui/DependencyInjectionExtensions.cs b/src/ui/DependencyInjectionExtensions.cs index 5d5f0ba9541..569437ed116 100644 --- a/src/ui/DependencyInjectionExtensions.cs +++ b/src/ui/DependencyInjectionExtensions.cs @@ -22,6 +22,7 @@ using Nikse.SubtitleEdit.Features.Files.ExportImageBased; using Nikse.SubtitleEdit.Features.Files.ExportPac; using Nikse.SubtitleEdit.Features.Files.ExportDialogueList; +using Nikse.SubtitleEdit.Features.Video.SyncAudioTrack; using Nikse.SubtitleEdit.Features.Files.ExportPlainText; using Nikse.SubtitleEdit.Features.Files.FormatProperties.DCinemaInteropProperties; using Nikse.SubtitleEdit.Features.Files.FormatProperties.DCinemaSmpteProperties; @@ -469,6 +470,7 @@ public static void AddSubtitleEditServices(this IServiceCollection collection) collection.AddTransient(); collection.AddTransient(); collection.AddTransient(); + collection.AddTransient(); collection.AddTransient(); collection.AddTransient(); collection.AddTransient(); diff --git a/src/ui/Features/Main/Layout/InitMenu.cs b/src/ui/Features/Main/Layout/InitMenu.cs index b4b2da2e0e6..fcbec50647f 100644 --- a/src/ui/Features/Main/Layout/InitMenu.cs +++ b/src/ui/Features/Main/Layout/InitMenu.cs @@ -849,6 +849,11 @@ public static void Make(MainViewModel vm) Header = Se.Language.Video.EmbedSubtitlesDotDotDot, Command = vm.VideoEmbedCommand, }, + new MenuItem + { + Header = Se.Language.Video.SyncAudioTrackDotDotDot, + Command = vm.ShowSyncAudioTrackCommand, + }, new Separator(), new MenuItem { diff --git a/src/ui/Features/Main/Layout/InitNativeMacMenu.cs b/src/ui/Features/Main/Layout/InitNativeMacMenu.cs index e720ad426af..1df75914bcb 100644 --- a/src/ui/Features/Main/Layout/InitNativeMacMenu.cs +++ b/src/ui/Features/Main/Layout/InitNativeMacMenu.cs @@ -381,6 +381,7 @@ public static void MakeStructure(NativeMenu root, Window window) videoItems.Items.Add(Item(Clean(l.GenerateTransparent), v => v.ShowVideoTransparentSubtitlesCommand)); videoItems.Items.Add(Item(Clean(Se.Language.Video.GenerateBlankVideoDotDotDot), v => v.VideoGenerateBlankCommand)); videoItems.Items.Add(Item(Clean(Se.Language.Video.EmbedSubtitlesDotDotDot), v => v.VideoEmbedCommand)); + videoItems.Items.Add(Item(Clean(Se.Language.Video.SyncAudioTrackDotDotDot), v => v.ShowSyncAudioTrackCommand)); videoItems.Items.Add(new NativeMenuItemSeparator()); videoItems.Items.Add(Item(Clean(Se.Language.Video.Chapters.ChaptersDotDotDot), v => v.ShowVideoChaptersCommand)); videoItems.Items.Add(new NativeMenuItemSeparator()); diff --git a/src/ui/Features/Main/MainViewModel.cs b/src/ui/Features/Main/MainViewModel.cs index 32bb088f559..96bdb8c5009 100644 --- a/src/ui/Features/Main/MainViewModel.cs +++ b/src/ui/Features/Main/MainViewModel.cs @@ -174,6 +174,7 @@ using Nikse.SubtitleEdit.Features.Video.RemuxVideo; using Nikse.SubtitleEdit.Features.Video.Chapters; using Nikse.SubtitleEdit.Features.Video.ShotChanges; +using Nikse.SubtitleEdit.Features.Video.SyncAudioTrack; using Nikse.SubtitleEdit.Features.Video.SpeechToText; using Nikse.SubtitleEdit.Features.Video.SpeechToText.OpenAiCompatible; using Nikse.SubtitleEdit.Features.Video.VideoOcr; @@ -8089,6 +8090,31 @@ private async Task CutVideoSelectedLines() } } + [RelayCommand] + private async Task ShowSyncAudioTrack() + { + if (Window == null || !await RequireFfmpegOk()) + { + return; + } + + var videoFileName = _videoFileName; + if (string.IsNullOrEmpty(videoFileName) || !File.Exists(videoFileName)) + { + videoFileName = await _fileHelper.PickOpenVideoFile(Window, Se.Language.General.OpenVideoFileTitle, lastOpenedFilePath: _subtitleFileName); + if (string.IsNullOrEmpty(videoFileName)) + { + return; + } + } + + // One player at a time: the dialog plays the video with the shifted audio + var vp = GetVideoPlayerControl(); + vp?.VideoPlayer.Pause(); + var position = videoFileName == _videoFileName ? vp?.Position ?? 0 : 0; + await ShowDialogAsync(vm => vm.Initialize(videoFileName, position)); + } + [RelayCommand] private async Task VideoEmbed() { diff --git a/src/ui/Features/Video/SyncAudioTrack/SyncAudioTrackViewModel.cs b/src/ui/Features/Video/SyncAudioTrack/SyncAudioTrackViewModel.cs new file mode 100644 index 00000000000..b8f683971be --- /dev/null +++ b/src/ui/Features/Video/SyncAudioTrack/SyncAudioTrackViewModel.cs @@ -0,0 +1,326 @@ +using Avalonia.Controls; +using Avalonia.Input; +using Avalonia.Threading; +using CommunityToolkit.Mvvm.ComponentModel; +using CommunityToolkit.Mvvm.Input; +using Nikse.SubtitleEdit.Controls.VideoPlayer; +using Nikse.SubtitleEdit.Core.Common; +using Nikse.SubtitleEdit.Features.Shared; +using Nikse.SubtitleEdit.Features.Shared.PromptFileSaved; +using Nikse.SubtitleEdit.Features.Video.TextToSpeech; +using Nikse.SubtitleEdit.Logic; +using Nikse.SubtitleEdit.Logic.Config; +using Nikse.SubtitleEdit.Logic.Media; +using Nikse.SubtitleEdit.UiLogic.Media; +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Diagnostics; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace Nikse.SubtitleEdit.Features.Video.SyncAudioTrack; + +public sealed class SyncAudioTrackItem +{ + /// The ffmpeg stream index ("Stream #0:N"). + public int StreamIndex { get; init; } + + /// 1-based position among the audio tracks - mpv's "aid" when it reports no ffmpeg index. + public int AudioNumber { get; init; } + + public string Display { get; init; } = string.Empty; + + public override string ToString() => Display; +} + +/// +/// Video > Sync audio track: play the video with one audio track moved by an offset (live, via the +/// player's audio delay), then save a copy of the video with that track shifted - stream copy, no +/// re-encode (). +/// +public partial class SyncAudioTrackViewModel : ObservableObject +{ + [ObservableProperty] private ObservableCollection _audioTracks = new(); + [ObservableProperty] private SyncAudioTrackItem? _selectedAudioTrack; + [ObservableProperty] private bool _isAudioTrackPickerVisible; + [ObservableProperty] private int _offsetMs; + [ObservableProperty] private bool _isPreviewNotSupported; + [ObservableProperty] private bool _isSaving; + [ObservableProperty] private double _progressValue; + [ObservableProperty] private string _progressText = string.Empty; + [ObservableProperty] private string _errorText = string.Empty; + + public Window? Window { get; set; } + public VideoPlayerControl VideoPlayer { get; internal set; } + public string? SavedFileName { get; private set; } + + private readonly IFileHelper _fileHelper; + private readonly IWindowService _windowService; + private string _videoFileName = string.Empty; + private double _startPositionSeconds; + private List _streamIndexes = new(); + private double _durationSeconds; + private CancellationTokenSource? _cancellationTokenSource; + + public SyncAudioTrackViewModel(IFileHelper fileHelper, IWindowService windowService) + { + _fileHelper = fileHelper; + _windowService = windowService; + VideoPlayer = new VideoPlayerControl(new EmptyVideoPlayer()); + } + + public void Initialize(string videoFileName, double startPositionSeconds) + { + _videoFileName = videoFileName; + _startPositionSeconds = startPositionSeconds; + } + + /// + /// Fills the track list from ffmpeg's stream list: every stream index (for the remux) and the + /// audio streams (for the picker). + /// + internal void SetMediaInfo(FfmpegMediaInfo2 mediaInfo) + { + _streamIndexes = mediaInfo.Tracks.Select(t => t.StreamIndex).Where(i => i >= 0).ToList(); + if (_streamIndexes.Count != mediaInfo.Tracks.Count) + { + _streamIndexes.Clear(); // not all indexes known - the remux then appends the track + } + + _durationSeconds = mediaInfo.Duration?.TotalSeconds ?? 0; + AudioTracks.Clear(); + var audioNumber = 0; + foreach (var track in mediaInfo.Tracks.Where(t => t.TrackType == FfmpegTrackType.Audio)) + { + audioNumber++; + var language = string.IsNullOrEmpty(track.Language) ? string.Empty : track.Language + " - "; + AudioTracks.Add(new SyncAudioTrackItem + { + StreamIndex = track.StreamIndex >= 0 ? track.StreamIndex : audioNumber, + AudioNumber = audioNumber, + Display = $"#{audioNumber}: {language}{track.TrackInfo}", + }); + } + + IsAudioTrackPickerVisible = AudioTracks.Count > 1; + ErrorText = AudioTracks.Count == 0 ? Se.Language.Video.SyncAudioTrackNoAudio : string.Empty; + SelectedAudioTrack = AudioTracks.FirstOrDefault(); + } + + partial void OnOffsetMsChanged(int value) + { + VideoPlayer.VideoPlayer.SetAudioDelay(value / 1000.0); + } + + partial void OnSelectedAudioTrackChanged(SyncAudioTrackItem? value) + { + if (value == null) + { + return; + } + + // mpv numbers its audio tracks 1..n; match on the ffmpeg index when it reports one + var player = VideoPlayer.VideoPlayer; + var tracks = player.GetAudioTracks(); + var track = tracks.FirstOrDefault(t => t.FfIndex == value.StreamIndex); + player.SetAudioTrack(track?.Id ?? value.AudioNumber); + player.SetAudioDelay(OffsetMs / 1000.0); + } + + [RelayCommand] + private void Nudge(string? milliseconds) + { + if (int.TryParse(milliseconds, NumberStyles.AllowLeadingSign, CultureInfo.InvariantCulture, out var ms)) + { + OffsetMs += ms; + } + } + + [RelayCommand] + private void ResetOffset() + { + OffsetMs = 0; + } + + [RelayCommand] + private async Task SaveVideo() + { + if (Window == null || SelectedAudioTrack == null || IsSaving) + { + return; + } + + var extension = Path.GetExtension(_videoFileName); + var suggestedName = Path.Combine(Path.GetDirectoryName(_videoFileName) ?? string.Empty, + Path.GetFileNameWithoutExtension(_videoFileName) + "_synced" + extension); + var outputFileName = await _fileHelper.PickSaveFile(Window, extension, suggestedName, Se.Language.Video.SyncAudioTrackSaveVideoDotDotDot); + if (string.IsNullOrEmpty(outputFileName)) + { + return; + } + + if (string.Equals(Path.GetFullPath(outputFileName), Path.GetFullPath(_videoFileName), StringComparison.OrdinalIgnoreCase)) + { + // ffmpeg cannot write over the file it is reading + await MessageBox.Show(Window, Se.Language.General.Error, + string.Format(Se.Language.General.CouldNotSaveFileXErrorY, outputFileName, string.Empty), + MessageBoxButtons.OK, MessageBoxIcon.Error); + return; + } + + VideoPlayer.VideoPlayer.Pause(); + _cancellationTokenSource = new CancellationTokenSource(); + var output = new StringBuilder(); + var progressTracker = new FfmpegProgressTracker(_durationSeconds); + IsSaving = true; + ProgressValue = 0; + ProgressText = Se.Language.Video.SyncAudioTrackSaving; + int exitCode; + try + { + var dropNegativePackets = OffsetMs < 0 && await Task.Run(FfmpegGenerator.NoiseBitstreamFilterCanDrop); + using var process = FfmpegGenerator.ShiftAudioTrack(_videoFileName, outputFileName, _streamIndexes, + SelectedAudioTrack.StreamIndex, OffsetMs / 1000.0, dropNegativePackets, (_, e) => + { + if (string.IsNullOrEmpty(e.Data)) + { + return; + } + + lock (output) + { + output.AppendLine(e.Data); + } + + if (progressTracker.TryGetNewPercent(e.Data, out var percent)) + { + Dispatcher.UIThread.Post(() => ProgressValue = percent); + } + }); + await process.StartAndWaitAsync(_cancellationTokenSource.Token); + exitCode = process.ExitCode; + } + catch (OperationCanceledException) + { + TryDelete(outputFileName); + return; + } + finally + { + IsSaving = false; + ProgressText = string.Empty; + } + + if (exitCode != 0 || !File.Exists(outputFileName) || new FileInfo(outputFileName).Length == 0) + { + string log; + lock (output) + { + log = output.ToString(); + } + + SeLogger.Error($"Sync audio track: ffmpeg failed (exit code {exitCode}){Environment.NewLine}{log}"); + TryDelete(outputFileName); + await MessageBox.Show(Window, Se.Language.General.Error, + string.Format(Se.Language.General.CouldNotSaveFileXErrorY, outputFileName, LastLines(log, 6)), + MessageBoxButtons.OK, MessageBoxIcon.Error); + return; + } + + SavedFileName = outputFileName; + await _windowService.ShowDialogAsync(Window, vm => + { + vm.Initialize(Se.Language.General.FileSaved, string.Format(Se.Language.General.FileSavedToX, outputFileName), outputFileName, true, true); + }); + } + + private static string LastLines(string text, int count) + { + var lines = text.SplitToLines().Where(l => !string.IsNullOrWhiteSpace(l)).ToList(); + return string.Join(Environment.NewLine, lines.Skip(Math.Max(0, lines.Count - count))); + } + + private static void TryDelete(string fileName) + { + try + { + if (File.Exists(fileName)) + { + File.Delete(fileName); + } + } + catch + { + // best effort + } + } + + [RelayCommand] + private void Cancel() + { + if (IsSaving) + { + _cancellationTokenSource?.Cancel(); + return; + } + + Window?.Close(); + } + + internal void OnKeyDown(KeyEventArgs e) + { + if (e.Key == Key.Escape) + { + e.Handled = true; + Cancel(); + } + } + + internal void OnLoaded() + { + UiUtil.RestoreWindowPosition(Window); + IsPreviewNotSupported = !VideoPlayer.VideoPlayer.SupportsAudioDelay; + + _ = Task.Run(() => + { + var mediaInfo = FfmpegMediaInfo2.Parse(_videoFileName); + Dispatcher.UIThread.Post(() => SetMediaInfo(mediaInfo)); + }); + + // A freshly built player is not ready when the window loads + Dispatcher.UIThread.Post(async () => + { + try + { + await VideoPlayer.Open(_videoFileName, _startPositionSeconds); + await VideoPlayer.WaitForPlayersReadyAsync(); + if (VideoPlayer.IsDisposed) + { + return; + } + + OnSelectedAudioTrackChanged(SelectedAudioTrack); + } + catch (Exception exception) + { + Se.LogError(exception, "Sync audio track: unable to open " + _videoFileName); + } + }); + } + + internal void OnClosing() + { + _cancellationTokenSource?.Cancel(); + VideoPlayer.VideoPlayer.CloseFile(); + UiUtil.SaveWindowPosition(Window); + + // Here, not from Closed: the OpenGL deinit that frees mpv's render context runs before + // Closed, so disposing there leaked the preview's mpv core (issue #15971). + VideoPlayer.CloseAndDisposePlayer(); + } +} diff --git a/src/ui/Features/Video/SyncAudioTrack/SyncAudioTrackWindow.cs b/src/ui/Features/Video/SyncAudioTrack/SyncAudioTrackWindow.cs new file mode 100644 index 00000000000..b47978eb917 --- /dev/null +++ b/src/ui/Features/Video/SyncAudioTrack/SyncAudioTrackWindow.cs @@ -0,0 +1,156 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Controls.Primitives; +using Avalonia.Data; +using Avalonia.Layout; +using Avalonia.Media; +using Nikse.SubtitleEdit.Features.Main.Layout; +using Nikse.SubtitleEdit.Logic; +using Nikse.SubtitleEdit.Logic.Config; +using Nikse.SubtitleEdit.Logic.ValueConverters; + +namespace Nikse.SubtitleEdit.Features.Video.SyncAudioTrack; + +/// +/// Sync audio track, in the editor look (see ): the video playing with the +/// offset applied live on top, the audio track (when there is more than one) and the offset with +/// nudge buttons below, Cancel and Save video in the button bar. +/// +public class SyncAudioTrackWindow : Window +{ + public SyncAudioTrackWindow(SyncAudioTrackViewModel vm) + { + UiUtil.InitializeWindow(this, GetType().Name); + Title = Se.Language.Video.SyncAudioTrackTitle; + CanResize = true; + Width = 900; + Height = 700; + MinWidth = 600; + MinHeight = 450; + EditorChrome.ApplyWindow(this, EditorWindowStyles.Video); + vm.Window = this; + DataContext = vm; + + vm.VideoPlayer = InitVideoPlayer.MakeVideoPlayer(); + vm.VideoPlayer.FullScreenIsVisible = false; + + var lang = Se.Language.Video; + + // Audio track + var labelAudioTrack = EditorChrome.MakeDimLabel(lang.SyncAudioTrackAudioTrack, new Thickness(0, 0, 8, 0)); + var comboBoxAudioTrack = UiUtil.MakeComboBox(vm.AudioTracks, vm, nameof(vm.SelectedAudioTrack)) + .WithMinWidth(320) + .WithAccessibleName(lang.SyncAudioTrackAudioTrack); + comboBoxAudioTrack.Bind(ItemsControl.ItemsSourceProperty, new Binding(nameof(vm.AudioTracks))); + var panelAudioTrack = new StackPanel + { + Orientation = Orientation.Horizontal, + VerticalAlignment = VerticalAlignment.Center, + Margin = new Thickness(0, 0, 0, 10), + Children = { labelAudioTrack, comboBoxAudioTrack }, + }; + panelAudioTrack.Bind(IsVisibleProperty, new Binding(nameof(vm.IsAudioTrackPickerVisible))); + + // Offset + var labelOffset = EditorChrome.MakeDimLabel(lang.SyncAudioTrackOffset, new Thickness(0, 0, 8, 0)); + var numericUpDownOffset = UiUtil.MakeNumericUpDownInt(-600000, 600000, 0, 130, vm, nameof(vm.OffsetMs)) + .WithAccessibleName(lang.SyncAudioTrackOffset); + numericUpDownOffset.Increment = 10; + var panelOffset = new StackPanel + { + Orientation = Orientation.Horizontal, + Spacing = 4, + VerticalAlignment = VerticalAlignment.Center, + Children = + { + labelOffset, + numericUpDownOffset, + MakeNudgeButton(vm, "-1000"), + MakeNudgeButton(vm, "-100"), + MakeNudgeButton(vm, "-10"), + MakeNudgeButton(vm, "+10"), + MakeNudgeButton(vm, "+100"), + MakeNudgeButton(vm, "+1000"), + EditorChrome.MakeTextToolButton(IconNames.Restore, Se.Language.General.Reset, vm.ResetOffsetCommand), + }, + }; + + var textHint = new TextBlock + { + Text = lang.SyncAudioTrackHint, + FontSize = 12.5, + TextWrapping = TextWrapping.Wrap, + Foreground = EditorChrome.DimTextBrush, + Margin = new Thickness(0, 10, 0, 0), + }; + var textPreviewNeedsMpv = new TextBlock + { + Text = lang.SyncAudioTrackPreviewNeedsMpv, + FontSize = 12.5, + TextWrapping = TextWrapping.Wrap, + Margin = new Thickness(0, 6, 0, 0), + }; + textPreviewNeedsMpv.Bind(IsVisibleProperty, new Binding(nameof(vm.IsPreviewNotSupported))); + var textError = new TextBlock + { + FontSize = 12.5, + TextWrapping = TextWrapping.Wrap, + Margin = new Thickness(0, 6, 0, 0), + }; + textError.Bind(TextBlock.TextProperty, new Binding(nameof(vm.ErrorText))); + + var controls = new StackPanel + { + Margin = new Thickness(12, 4, 12, 8), + Children = { panelAudioTrack, panelOffset, textHint, textPreviewNeedsMpv, textError }, + }; + + var progressBar = UiUtil.MakeProgressBar(); + progressBar.Bind(RangeBase.ValueProperty, new Binding(nameof(vm.ProgressValue))); + var progressView = EditorChrome.MakeProgressStrip(progressBar, nameof(vm.ProgressText), nameof(vm.IsSaving)); + + var buttonSave = UiUtil.MakeButton(lang.SyncAudioTrackSaveVideoDotDotDot, vm.SaveVideoCommand); + buttonSave.Classes.Add("primary"); + buttonSave.Bind(IsEnabledProperty, new Binding(nameof(vm.IsSaving)) { Converter = InverseBooleanConverter.Instance }); + var buttonCancel = UiUtil.MakeButtonCancel(vm.CancelCommand); + var buttonBar = EditorChrome.MakeButtonBar(null, buttonCancel, buttonSave); + + var grid = new Grid + { + RowDefinitions = + { + new RowDefinition { Height = new GridLength(1, GridUnitType.Star) }, + new RowDefinition { Height = GridLength.Auto }, + new RowDefinition { Height = GridLength.Auto }, + new RowDefinition { Height = GridLength.Auto }, + }, + }; + + // The player in a plain border - no corner radius or clipping, the macOS native video + // surface disappears inside either + grid.Add(new Border + { + Child = EditorChrome.MakeVideoFrame(vm.VideoPlayer), + Padding = new Thickness(12), + }, 0, 0); + grid.Add(controls, 1, 0); + grid.Add(progressView, 2, 0); + grid.Add(buttonBar, 3, 0); + + Content = grid; + + Loaded += (_, _) => vm.OnLoaded(); + Closing += (_, _) => vm.OnClosing(); + KeyDown += (_, e) => vm.OnKeyDown(e); + } + + private static Button MakeNudgeButton(SyncAudioTrackViewModel vm, string milliseconds) + { + var button = UiUtil.MakeButton(milliseconds, vm.NudgeCommand); + button.CommandParameter = milliseconds; + button.MinWidth = 0; + button.Padding = new Thickness(8, 4); + Avalonia.Automation.AutomationProperties.SetName(button, milliseconds + " ms"); + return button; + } +} diff --git a/src/ui/Logic/Config/Language/Video/LanguageVideo.cs b/src/ui/Logic/Config/Language/Video/LanguageVideo.cs index d878da64baf..3112c8e86a0 100644 --- a/src/ui/Logic/Config/Language/Video/LanguageVideo.cs +++ b/src/ui/Logic/Config/Language/Video/LanguageVideo.cs @@ -63,6 +63,15 @@ public class LanguageVideo public string RemuxVideoVolumeX { get; set; } public string RemuxVideoFastStart { get; set; } public string EmbedSubtitlesDotDotDot { get; set; } + public string SyncAudioTrackDotDotDot { get; set; } + public string SyncAudioTrackTitle { get; set; } + public string SyncAudioTrackAudioTrack { get; set; } + public string SyncAudioTrackOffset { get; set; } + public string SyncAudioTrackHint { get; set; } + public string SyncAudioTrackPreviewNeedsMpv { get; set; } + public string SyncAudioTrackNoAudio { get; set; } + public string SyncAudioTrackSaveVideoDotDotDot { get; set; } + public string SyncAudioTrackSaving { get; set; } public string GenerateTimeCodes { get; set; } public string CheckeredImage { get; set; } public string PromptForFfmpegParamsAndGenerate { get; set; } @@ -237,6 +246,15 @@ public LanguageVideo() RemuxVideoVolumeX = "Volume {0}%"; RemuxVideoFastStart = "Optimize for web streaming (move index to front - slower)"; EmbedSubtitlesDotDotDot = "Add/remove embedded subtitles..."; + SyncAudioTrackDotDotDot = "Sync audio track..."; + SyncAudioTrackTitle = "Sync audio track"; + SyncAudioTrackAudioTrack = "Audio track"; + SyncAudioTrackOffset = "Offset (ms)"; + SyncAudioTrackHint = "Positive values play the audio later, negative values earlier. The saved video is a copy with only this track moved - nothing is re-encoded."; + SyncAudioTrackPreviewNeedsMpv = "The live preview of the offset needs the mpv video player (Options > Settings > Video) - the saved video gets the offset all the same."; + SyncAudioTrackNoAudio = "The video has no audio track."; + SyncAudioTrackSaveVideoDotDotDot = "Save video..."; + SyncAudioTrackSaving = "Saving video..."; GenerateTimeCodes = "Generate time codes"; CheckeredImage = "Checkered image"; PromptForFfmpegParamsAndGenerate = "Prompt for ffmpeg parameters and generate"; diff --git a/src/ui/Logic/Media/FfmpegGenerator.cs b/src/ui/Logic/Media/FfmpegGenerator.cs index c0f3e72d0c5..5b9eff4b1d2 100644 --- a/src/ui/Logic/Media/FfmpegGenerator.cs +++ b/src/ui/Logic/Media/FfmpegGenerator.cs @@ -12,6 +12,7 @@ using System.Globalization; using System.IO; using System.Linq; +using System.Text; using System.Text.RegularExpressions; namespace Nikse.SubtitleEdit.Logic.Media; @@ -979,6 +980,126 @@ private static string GetAddAudioTrackEncodingString(string audioEncoding) return "-c:a " + audioEncoding + (isExperimental ? " -strict -2 " : " "); } + /// + /// Video > Sync audio track: a copy of the video (no re-encode) with one audio stream moved + /// later (negative = earlier). The file is read twice; only + /// the chosen audio stream is taken from the second, time-shifted input (-itsoffset), every + /// other stream from the first - mapped in the original order, so the track keeps its place, + /// language, title and default flag. + /// + /// + /// Moving audio earlier puts its first packets before zero. ffmpeg's default then shifts the + /// whole file later in .mkv (the video started at the offset, out of step with the + /// subtitles), so negative timestamps are allowed through, and with + /// (ffmpeg 5.1+ "noise" bsf drop option) the audio + /// packets before zero are dropped instead. + /// + /// All stream indexes of the input in order ("Stream #0:N"); empty + /// = unknown, then the shifted track is added last instead of in its old place. + public static Process ShiftAudioTrack(string inputFileName, string outputFileName, IReadOnlyList streamIndexes, int audioStreamIndex, double offsetSeconds, bool dropNegativePackets, DataReceivedEventHandler? dataReceivedHandler = null) + { + var process = new Process + { + StartInfo = + { + FileName = GetFfmpegLocation(), + Arguments = GetShiftAudioTrackArguments(inputFileName, outputFileName, streamIndexes, audioStreamIndex, offsetSeconds, dropNegativePackets), + UseShellExecute = false, + CreateNoWindow = true + } + }; + + SetupDataReceiveHandler(dataReceivedHandler, process); + return process; + } + + internal static string GetShiftAudioTrackArguments(string inputFileName, string outputFileName, IReadOnlyList streamIndexes, int audioStreamIndex, double offsetSeconds, bool dropNegativePackets) + { + var offset = offsetSeconds.ToString("0.000", CultureInfo.InvariantCulture); + + var maps = new StringBuilder(); + var outputIndex = -1; + if (streamIndexes.Count > 0 && streamIndexes.Contains(audioStreamIndex)) + { + for (var i = 0; i < streamIndexes.Count; i++) + { + var index = streamIndexes[i]; + if (index == audioStreamIndex) + { + maps.Append($"-map 1:{index} "); + outputIndex = i; + } + else + { + maps.Append($"-map 0:{index} "); + } + } + } + else + { + maps.Append($"-map 0 -map -0:{audioStreamIndex} -map 1:{audioStreamIndex} "); + } + + var negative = string.Empty; + if (offsetSeconds < 0) + { + negative = dropNegativePackets && outputIndex >= 0 + ? $"-bsf:{outputIndex} \"noise=drop=lt(pts*tb\\,0)\" -avoid_negative_ts disabled " + : "-avoid_negative_ts disabled "; + } + + return $"{FfmpegProgressTracker.ProgressArguments} -nostdin -y {GeneratePtsForVideoCopy}-i \"{inputFileName}\" -itsoffset {offset} -i \"{inputFileName}\" {maps}-c copy {negative}-map_metadata 0 -map_chapters 0 \"{outputFileName}\""; + } + + private static bool? _noiseBsfHasDrop; + + /// + /// The "-drop" option line of "ffmpeg -h bsf=noise" - not "-dropamount", which older + /// versions (4.x) have and which cannot drop by timestamp. + /// + internal static bool NoiseHelpHasDropOption(string help) => + Regex.IsMatch(help, @"^\s*-drop\s", RegexOptions.Multiline); + + /// + /// True when this ffmpeg's "noise" bitstream filter has the drop option (ffmpeg 5.1+). + /// + public static bool NoiseBitstreamFilterCanDrop() + { + if (_noiseBsfHasDrop.HasValue) + { + return _noiseBsfHasDrop.Value; + } + + try + { + using var process = new Process + { + StartInfo = + { + FileName = GetFfmpegLocation(), + Arguments = "-hide_banner -h bsf=noise", + UseShellExecute = false, + CreateNoWindow = true, + RedirectStandardOutput = true, + RedirectStandardError = true, + } + }; +#pragma warning disable CA1416 // Validate platform compatibility + process.Start(); +#pragma warning restore CA1416 + var stdout = process.StandardOutput.ReadToEndAsync(); + var stderr = process.StandardError.ReadToEndAsync(); + process.WaitForExit(10000); + _noiseBsfHasDrop = NoiseHelpHasDropOption(stdout.Result + stderr.Result); + } + catch + { + _noiseBsfHasDrop = false; + } + + return _noiseBsfHasDrop.Value; + } + public static Process AddAudioTrack(string inputFileName, string audioFileName, string outputFileName, string audioEncoding, bool? stereo, DataReceivedEventHandler? dataReceivedHandler = null) { // Empty encoding = let ffmpeg pick the container's default encoder (same as the ducking diff --git a/src/ui/Logic/Media/FfmpegMediainfo2.cs b/src/ui/Logic/Media/FfmpegMediainfo2.cs index 19c4227247d..edfe1a05623 100644 --- a/src/ui/Logic/Media/FfmpegMediainfo2.cs +++ b/src/ui/Logic/Media/FfmpegMediainfo2.cs @@ -27,6 +27,9 @@ public class FfmpegMediaInfo2 // Match the language tag in the ffmpeg stream header prefix, e.g. // "Stream #0:2(eng): Subtitle: ..." or "Stream #0:2[0x3](eng): ...". // Captures the 2-3 letter ISO code in group "lang". + // "Stream #0:3..." - the stream index used in "-map 0:3" + private static readonly Regex StreamIndexRegex = new Regex(@"^Stream #\d+:(?\d+)", RegexOptions.Compiled); + private static readonly Regex StreamLanguageRegex = new Regex(@"^Stream #\d+:\d+(?:\[[^\]]+\])?\((?[a-zA-Z]{2,3})\)", RegexOptions.Compiled); // Rotation side data ffmpeg prints under a video stream, e.g. // " Side data:" @@ -124,7 +127,7 @@ public bool HasFrontCenterAudio(int trackNumber) info.Contains("9.1", StringComparison.Ordinal); } - private static FfmpegMediaInfo2 ParseLog(string log) + internal static FfmpegMediaInfo2 ParseLog(string log) { var info = new FfmpegMediaInfo2(); @@ -192,6 +195,8 @@ private static FfmpegMediaInfo2 ParseLog(string log) continue; } + var streamIndexMatch = StreamIndexRegex.Match(arr[0]); + var streamIndex = streamIndexMatch.Success ? int.Parse(streamIndexMatch.Groups["index"].Value, CultureInfo.InvariantCulture) : -1; var trackType = arr[1].Trim(); var trackInfo = arr[2].Trim(); // arr[0] still carries the "Stream #N:M(LANG)" prefix; parse the @@ -205,19 +210,19 @@ private static FfmpegMediaInfo2 ParseLog(string log) if (trackType == FfmpegTrackType.Audio.ToString()) { - info.Tracks.Add(new FfmpegTrackInfo { TrackType = FfmpegTrackType.Audio, TrackInfo = trackInfo, Language = language }); + info.Tracks.Add(new FfmpegTrackInfo { TrackType = FfmpegTrackType.Audio, TrackInfo = trackInfo, Language = language, StreamIndex = streamIndex }); } else if (trackType == FfmpegTrackType.Video.ToString()) { - info.Tracks.Add(new FfmpegTrackInfo { TrackType = FfmpegTrackType.Video, TrackInfo = trackInfo, Language = language }); + info.Tracks.Add(new FfmpegTrackInfo { TrackType = FfmpegTrackType.Video, TrackInfo = trackInfo, Language = language, StreamIndex = streamIndex }); } else if (trackType == FfmpegTrackType.Subtitle.ToString()) { - info.Tracks.Add(new FfmpegTrackInfo { TrackType = FfmpegTrackType.Subtitle, TrackInfo = trackInfo, Language = language }); + info.Tracks.Add(new FfmpegTrackInfo { TrackType = FfmpegTrackType.Subtitle, TrackInfo = trackInfo, Language = language, StreamIndex = streamIndex }); } else { - info.Tracks.Add(new FfmpegTrackInfo { TrackType = FfmpegTrackType.Other, TrackInfo = trackInfo, Language = language }); + info.Tracks.Add(new FfmpegTrackInfo { TrackType = FfmpegTrackType.Other, TrackInfo = trackInfo, Language = language, StreamIndex = streamIndex }); } } } diff --git a/src/ui/Logic/VideoPlayers/IVideoPlayer.cs b/src/ui/Logic/VideoPlayers/IVideoPlayer.cs index 0b3c9b8cf91..1f1cae4e632 100644 --- a/src/ui/Logic/VideoPlayers/IVideoPlayer.cs +++ b/src/ui/Logic/VideoPlayers/IVideoPlayer.cs @@ -42,6 +42,17 @@ void SetAudioTrack(int trackId) { } + /// True when moves the audio (mpv). + bool SupportsAudioDelay => false; + + /// + /// Plays the audio this many seconds later (negative = earlier) than the picture - the live + /// preview of Video > Sync audio track. + /// + void SetAudioDelay(double seconds) + { + } + bool IsPlaying { get; } bool IsPaused { get; } diff --git a/src/ui/Logic/VideoPlayers/LibMpvDynamic/LibMpvDynamicPlayer.cs b/src/ui/Logic/VideoPlayers/LibMpvDynamic/LibMpvDynamicPlayer.cs index ebf42f96a2a..ed5090d6691 100644 --- a/src/ui/Logic/VideoPlayers/LibMpvDynamic/LibMpvDynamicPlayer.cs +++ b/src/ui/Logic/VideoPlayers/LibMpvDynamic/LibMpvDynamicPlayer.cs @@ -2970,6 +2970,23 @@ public void SetAudioTrack(int trackId) } } + public bool SupportsAudioDelay => true; + + public void SetAudioDelay(double seconds) + { + EnsureNotDisposed(); + if (_mpv == IntPtr.Zero) + { + return; + } + + var err = DoMpvCommand("set", "audio-delay", seconds.ToString("0.000", CultureInfo.InvariantCulture)); + if (err < 0) + { + Se.LogError(new InvalidOperationException(GetErrorString(err)), "LibMpvDynamicPlayer SetAudioDelay"); + } + } + public void InitializeWithSoftwareRendering() { // LoadLib(), not LoadLibraryInternal(): the latter always calls mpv_create() and diff --git a/tests/UI/Features/Video/SyncAudioTrack/SyncAudioTrackViewModelTests.cs b/tests/UI/Features/Video/SyncAudioTrack/SyncAudioTrackViewModelTests.cs new file mode 100644 index 00000000000..e8b49f0c546 --- /dev/null +++ b/tests/UI/Features/Video/SyncAudioTrack/SyncAudioTrackViewModelTests.cs @@ -0,0 +1,67 @@ +using Avalonia.Headless.XUnit; +using Microsoft.Extensions.DependencyInjection; +using Nikse.SubtitleEdit; +using Nikse.SubtitleEdit.Features.Video.SyncAudioTrack; +using Nikse.SubtitleEdit.Logic.Media; + +namespace UITests.Features.Video.SyncAudioTrack; + +public class SyncAudioTrackViewModelTests +{ + private static SyncAudioTrackViewModel MakeViewModel() + { + var services = new ServiceCollection(); + services.AddSubtitleEditServices(); + return services.BuildServiceProvider().GetRequiredService(); + } + + private static FfmpegMediaInfo2 MediaInfo(params string[] streams) => FfmpegMediaInfo2.ParseLog( + "Input #0, matroska,webm, from 'in.mkv':\n Duration: 00:01:00.00, start: 0.000000, bitrate: 128 kb/s\n" + + string.Concat(streams.Select((s, i) => $" Stream #0:{i}{s}\n"))); + + [AvaloniaFact] + public void TwoAudioTracks_ShowsPicker() + { + var vm = MakeViewModel(); + vm.SetMediaInfo(MediaInfo(": Video: h264, yuv420p, 640x360, 25 fps", "(eng): Audio: aac (LC), 48000 Hz, stereo", "(dan): Audio: ac3, 48000 Hz, 5.1")); + + Assert.True(vm.IsAudioTrackPickerVisible); + Assert.Equal(new[] { 1, 2 }, vm.AudioTracks.Select(t => t.StreamIndex)); + Assert.StartsWith("#2: dan - ac3", vm.AudioTracks[1].Display); + Assert.Same(vm.AudioTracks[0], vm.SelectedAudioTrack); + Assert.Equal(string.Empty, vm.ErrorText); + } + + [AvaloniaFact] + public void OneAudioTrack_NoPicker() + { + var vm = MakeViewModel(); + vm.SetMediaInfo(MediaInfo(": Video: h264, yuv420p, 640x360, 25 fps", "(eng): Audio: aac (LC), 48000 Hz, stereo")); + + Assert.False(vm.IsAudioTrackPickerVisible); + Assert.Equal(1, vm.SelectedAudioTrack!.StreamIndex); + } + + [AvaloniaFact] + public void NoAudio_SaysSo() + { + var vm = MakeViewModel(); + vm.SetMediaInfo(MediaInfo(": Video: h264, yuv420p, 640x360, 25 fps")); + + Assert.Null(vm.SelectedAudioTrack); + Assert.False(string.IsNullOrEmpty(vm.ErrorText)); + } + + [AvaloniaFact] + public void Nudge_And_Reset() + { + var vm = MakeViewModel(); + vm.NudgeCommand.Execute("+100"); + vm.NudgeCommand.Execute("-10"); + vm.NudgeCommand.Execute("+1000"); + Assert.Equal(1090, vm.OffsetMs); + + vm.ResetOffsetCommand.Execute(null); + Assert.Equal(0, vm.OffsetMs); + } +} diff --git a/tests/UI/Logic/FfmpegShiftAudioTrackTests.cs b/tests/UI/Logic/FfmpegShiftAudioTrackTests.cs new file mode 100644 index 00000000000..834bc48d0ec --- /dev/null +++ b/tests/UI/Logic/FfmpegShiftAudioTrackTests.cs @@ -0,0 +1,78 @@ +using Nikse.SubtitleEdit.Logic.Media; +using Nikse.SubtitleEdit.UiLogic.Media; + +namespace UITests.Logic; + +/// +/// Video > Sync audio track: the ffmpeg command that copies a video with one audio track shifted. +/// Checked against real ffmpeg 4.4 and 9.0 by decoding a beep at 2.0 s: +0.5 s puts it at 2.5 s, +/// -0.5 s at 1.5 s, with the video still starting at 0 in .mkv and .mp4. +/// +public class FfmpegShiftAudioTrackTests +{ + private static readonly int[] Streams = { 0, 1, 2, 3 }; + + [Fact] + public void Later_KeepsStreamOrder_TakesTrackFromShiftedInput() + { + var args = FfmpegGenerator.GetShiftAudioTrackArguments("in.mkv", "out.mkv", Streams, 2, 0.5, dropNegativePackets: true); + + Assert.Contains("-i \"in.mkv\" -itsoffset 0.500 -i \"in.mkv\" -map 0:0 -map 0:1 -map 1:2 -map 0:3 -c copy ", args); + Assert.DoesNotContain("avoid_negative_ts", args); + Assert.DoesNotContain("-bsf", args); + Assert.EndsWith("-map_metadata 0 -map_chapters 0 \"out.mkv\"", args); + Assert.StartsWith(FfmpegProgressTracker.ProgressArguments, args); + } + + /// + /// ffmpeg's default shifts the whole .mkv when the moved audio starts before zero - the video + /// then started at the offset and no longer matched the subtitles. + /// + [Fact] + public void Earlier_AllowsNegativeTimestamps_AndDropsThemWhenSupported() + { + var withDrop = FfmpegGenerator.GetShiftAudioTrackArguments("in.mkv", "out.mkv", Streams, 2, -0.25, dropNegativePackets: true); + Assert.Contains("-itsoffset -0.250 ", withDrop); + Assert.Contains("-bsf:2 \"noise=drop=lt(pts*tb\\,0)\" -avoid_negative_ts disabled ", withDrop); + + var withoutDrop = FfmpegGenerator.GetShiftAudioTrackArguments("in.mkv", "out.mkv", Streams, 2, -0.25, dropNegativePackets: false); + Assert.Contains("-c copy -avoid_negative_ts disabled -map_metadata", withoutDrop); + Assert.DoesNotContain("-bsf", withoutDrop); + } + + [Fact] + public void NoiseHelp_DropOption_NotDropAmount() + { + const string ffmpeg44 = "noise AVOptions:\n -amount ...VA...B..\n -dropamount ...VA...B.. (from 0 to INT_MAX) (default 0)\n"; + const string ffmpeg9 = "noise AVOptions:\n -amount ...VA...B..\n -drop ...VA...B..\n -dropamount ...VA...B.. (from 0 to INT_MAX) (default 0)\n"; + + Assert.False(FfmpegGenerator.NoiseHelpHasDropOption(ffmpeg44)); + Assert.True(FfmpegGenerator.NoiseHelpHasDropOption(ffmpeg9)); + } + + [Fact] + public void UnknownStreamIndexes_AppendsTheShiftedTrack() + { + var args = FfmpegGenerator.GetShiftAudioTrackArguments("in.mp4", "out.mp4", Array.Empty(), 1, 1.0, dropNegativePackets: true); + + Assert.Contains("-map 0 -map -0:1 -map 1:1 -c copy ", args); + } + + [Fact] + public void MediaInfo_HasStreamIndexForEveryStream() + { + const string log = + "Input #0, matroska,webm, from 'in.mkv':\n" + + " Duration: 00:00:06.00, start: 0.000000, bitrate: 128 kb/s\n" + + " Stream #0:0: Video: h264 (High), yuv420p(progressive), 640x360, 25 fps, 25 tbr, 1k tbn (default)\n" + + " Stream #0:1(eng): Audio: aac (LC), 48000 Hz, stereo, fltp (default)\n" + + " Stream #0:2[0x3](dan): Audio: ac3, 48000 Hz, 5.1(side), fltp, 384 kb/s\n" + + " Stream #0:3: Attachment: ttf\n"; + + var info = FfmpegMediaInfo2.ParseLog(log); + + Assert.Equal(new[] { 0, 1, 2, 3 }, info.Tracks.Select(t => t.StreamIndex)); + Assert.Equal(new[] { FfmpegTrackType.Audio, FfmpegTrackType.Audio }, info.Tracks.Where(t => t.StreamIndex is 1 or 2).Select(t => t.TrackType)); + Assert.Equal("dan", info.Tracks[2].Language); + } +} From f3a63ee2ffe77a9e665b03b3fdbf95e713acea22 Mon Sep 17 00:00:00 2001 From: niksedk Date: Fri, 16 Oct 2026 15:42:56 +0200 Subject: [PATCH 266/306] ASSA draw: Enter in a property field no longer presses OK Enter in a number or text field commits the value; it fell through to the default OK button and closed the editor. Co-Authored-By: Claude Opus 5.5 --- .../Assa/AssaDraw/AssaDrawViewModel.cs | 7 ++- .../UI/Features/Assa/AssaDrawEnterKeyTests.cs | 57 +++++++++++++++++++ 2 files changed, 63 insertions(+), 1 deletion(-) create mode 100644 tests/UI/Features/Assa/AssaDrawEnterKeyTests.cs diff --git a/src/ui/Features/Assa/AssaDraw/AssaDrawViewModel.cs b/src/ui/Features/Assa/AssaDraw/AssaDrawViewModel.cs index 49dbf5524f3..6b97ef7e2f3 100644 --- a/src/ui/Features/Assa/AssaDraw/AssaDrawViewModel.cs +++ b/src/ui/Features/Assa/AssaDraw/AssaDrawViewModel.cs @@ -3577,7 +3577,12 @@ internal async void OnKeyDown(KeyEventArgs e) } else if (IsTextInput(e) && e.Key is Key.Enter or Key.Delete or Key.Back) { - // Typing in the text field (or a number box) - not a canvas command + // Typing in the text field (or a number box) - not a canvas command. Enter there only + // commits the value: handled here, it no longer reaches the default OK button. + if (e.Key == Key.Enter) + { + e.Handled = true; + } } else if (e.Key == Key.Enter) { diff --git a/tests/UI/Features/Assa/AssaDrawEnterKeyTests.cs b/tests/UI/Features/Assa/AssaDrawEnterKeyTests.cs new file mode 100644 index 00000000000..1130aa27fe1 --- /dev/null +++ b/tests/UI/Features/Assa/AssaDrawEnterKeyTests.cs @@ -0,0 +1,57 @@ +using Avalonia.Controls; +using Avalonia.Headless; +using Avalonia.Headless.XUnit; +using Avalonia.Input; +using Avalonia.Threading; +using Avalonia.VisualTree; +using Nikse.SubtitleEdit.Core.Common; +using Nikse.SubtitleEdit.Core.SubtitleFormats; +using Nikse.SubtitleEdit.Features.Assa.AssaDraw; +using Nikse.SubtitleEdit.Features.Main; +using Nikse.SubtitleEdit.Logic.Media; + +namespace UITests.Features.Assa; + +/// +/// Enter in a property field (layer, position, size...) commits the value - it must not press the +/// default OK button and close the editor. +/// +public class AssaDrawEnterKeyTests +{ + private const string Square = "{\\p1}m 100 100 l 200 100 200 200 100 200{\\p0}"; + + [AvaloniaFact] + public void Enter_InPropertyNumberField_DoesNotPressOk() + { + var vm = new AssaDrawViewModel(new FileHelper(), new StubWindowService()); + var rows = new List { new(new Paragraph(Square, 0, 2000) { Extra = "Default" }, new AdvancedSubStationAlpha()) }; + vm.Initialize(new Subtitle { Header = AdvancedSubStationAlpha.DefaultHeader }, rows, 1920, 1080); + var window = new AssaDrawWindow(vm) { Width = 1400, Height = 900 }; + window.Show(); + Dispatcher.UIThread.RunJobs(); + try + { + vm.SelectShape(vm.Shapes[0]); + Dispatcher.UIThread.RunJobs(); + + var fields = window.GetVisualDescendants().OfType().Where(n => n.IsEffectivelyVisible).ToList(); + Assert.NotEmpty(fields); + foreach (var field in fields) + { + var textBox = field.GetVisualDescendants().OfType().First(); + textBox.Focus(); + Dispatcher.UIThread.RunJobs(); + + window.KeyPress(Key.Enter, RawInputModifiers.None, PhysicalKey.Enter, null); + Dispatcher.UIThread.RunJobs(); + + Assert.False(vm.OkPressed); + Assert.True(window.IsVisible); + } + } + finally + { + window.Close(); + } + } +} From af1ff30a1e72942e8ce80bce7c26475cb5a84e7c Mon Sep 17 00:00:00 2001 From: niksedk Date: Fri, 16 Oct 2026 15:47:36 +0200 Subject: [PATCH 267/306] ASSA draw: OK is no longer the default button Enter means close shape on the canvas, and pressing it with focus on the layer tree or a check box closed the whole editor. Co-Authored-By: Claude Opus 5.5 --- .../Features/Assa/AssaDraw/AssaDrawWindow.cs | 4 +++ .../UI/Features/Assa/AssaDrawEnterKeyTests.cs | 31 ++++++++++++++++++- 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/src/ui/Features/Assa/AssaDraw/AssaDrawWindow.cs b/src/ui/Features/Assa/AssaDraw/AssaDrawWindow.cs index 56c70bb1422..e17d4c29f0d 100644 --- a/src/ui/Features/Assa/AssaDraw/AssaDrawWindow.cs +++ b/src/ui/Features/Assa/AssaDraw/AssaDrawWindow.cs @@ -1293,6 +1293,10 @@ private static Border CreateButtonBar(AssaDrawViewModel vm) var buttonOk = UiUtil.MakeButtonOk(vm.OkCommand); buttonOk.Classes.Add("primary"); + + // Not the default button: Enter means "close shape" on the canvas, and closing the + // editor from wherever focus happens to be (layer tree, check box...) is a surprise. + buttonOk.IsDefault = false; var buttonCancel = UiUtil.MakeButtonCancel(vm.CancelCommand); var buttons = new StackPanel { Orientation = Orientation.Horizontal, Spacing = 10 }; buttons.Children.Add(buttonCancel); diff --git a/tests/UI/Features/Assa/AssaDrawEnterKeyTests.cs b/tests/UI/Features/Assa/AssaDrawEnterKeyTests.cs index 1130aa27fe1..0d066de11ca 100644 --- a/tests/UI/Features/Assa/AssaDrawEnterKeyTests.cs +++ b/tests/UI/Features/Assa/AssaDrawEnterKeyTests.cs @@ -14,7 +14,8 @@ namespace UITests.Features.Assa; /// /// Enter in a property field (layer, position, size...) commits the value - it must not press the -/// default OK button and close the editor. +/// OK button and close the editor. OK isn't the default button at all, so Enter elsewhere (the layer +/// tree, a check box) doesn't close the editor either. /// public class AssaDrawEnterKeyTests { @@ -54,4 +55,32 @@ public void Enter_InPropertyNumberField_DoesNotPressOk() window.Close(); } } + + [AvaloniaFact] + public void Enter_InLayerTree_DoesNotPressOk() + { + var vm = new AssaDrawViewModel(new FileHelper(), new StubWindowService()); + var rows = new List { new(new Paragraph(Square, 0, 2000) { Extra = "Default" }, new AdvancedSubStationAlpha()) }; + vm.Initialize(new Subtitle { Header = AdvancedSubStationAlpha.DefaultHeader }, rows, 1920, 1080); + var window = new AssaDrawWindow(vm) { Width = 1400, Height = 900 }; + window.Show(); + Dispatcher.UIThread.RunJobs(); + try + { + var tree = window.GetVisualDescendants().OfType().First(t => t.IsEffectivelyVisible); + tree.Focus(); + Dispatcher.UIThread.RunJobs(); + + window.KeyPress(Key.Enter, RawInputModifiers.None, PhysicalKey.Enter, null); + Dispatcher.UIThread.RunJobs(); + + Assert.False(vm.OkPressed); + Assert.True(window.IsVisible); + Assert.DoesNotContain(window.GetVisualDescendants().OfType - private void DrawLayerFills(DrawingContext context, List shapes) + private void DrawLayerFills(DrawingContext context, List shapes, double? animationTime = null) { foreach (var layer in shapes.Where(s => !s.Hidden && s.Points.Count > 2).GroupBy(s => s.Layer).OrderBy(g => g.Key)) { + using var animation = animationTime is { } time ? PushAnimation(context, layer, time) : null; var drawShapes = layer.Where(s => !s.IsEraser && !s.IsTextBox).ToList(); if (drawShapes.Count > 0) { @@ -794,6 +813,44 @@ private void DrawLayerFills(DrawingContext context, List shapes) } } + /// + /// Moves, scales and turns what is drawn next around the layer's anchor, and fades it, as its + /// animation has it at . + /// + private AnimationScope? PushAnimation(DrawingContext context, IEnumerable layer, double timeMs) + { + var styleShape = layer.FirstOrDefault(s => !s.IsEraser); + if (styleShape == null || GetAnimationTransform == null) + { + return null; + } + + var (state, anchorX, anchorY) = GetAnimationTransform(styleShape, timeMs); + if (state == DrawAnimationState.Rest) + { + return null; + } + + var centerX = ToZoomFactorX(anchorX); + var centerY = ToZoomFactorY(anchorY); + var transform = context.PushTransform( + Matrix.CreateTranslation(-centerX, -centerY) * + Matrix.CreateScale(Math.Max(0.001, state.Scale), Math.Max(0.001, state.Scale)) * + Matrix.CreateRotation(state.Rotation * Math.PI / 180) * + Matrix.CreateTranslation(centerX + state.Dx * _zoomFactor, centerY + state.Dy * _zoomFactor)); + var opacity = context.PushOpacity(state.Opacity); + return new AnimationScope(transform, opacity); + } + + private sealed class AnimationScope(DrawingContext.PushedState transform, DrawingContext.PushedState opacity) : IDisposable + { + public void Dispose() + { + opacity.Dispose(); + transform.Dispose(); + } + } + /// /// The shape's text, wrapped and sized like the generated line (the F9 preview shows the real libass render). /// diff --git a/src/ui/Features/Assa/AssaDraw/AssaDrawViewModel.Animation.cs b/src/ui/Features/Assa/AssaDraw/AssaDrawViewModel.Animation.cs new file mode 100644 index 00000000000..94051bbf655 --- /dev/null +++ b/src/ui/Features/Assa/AssaDraw/AssaDrawViewModel.Animation.cs @@ -0,0 +1,264 @@ +using Avalonia.Threading; +using CommunityToolkit.Mvvm.ComponentModel; +using CommunityToolkit.Mvvm.Input; +using Nikse.SubtitleEdit.Logic.Config; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; + +namespace Nikse.SubtitleEdit.Features.Assa.AssaDraw; + +/// +/// In/out effects of the selected layers (a group's layers move as one) and playing them on the canvas. +/// +public partial class AssaDrawViewModel +{ + /// + /// Pause after the line ends before the canvas plays the animation again. + /// + private const int PlaybackPauseMs = 700; + + [ObservableProperty] private DrawAnimationEffect _animationIn; + [ObservableProperty] private DrawAnimationEffect _animationOut; + [ObservableProperty] private int _animationInMs = DrawAnimation.DefaultDurationMs; + [ObservableProperty] private int _animationOutMs = DrawAnimation.DefaultDurationMs; + + /// + /// The effect gallery edits the out effect (false: the in effect). + /// + [ObservableProperty] private bool _isEditingOutEffect; + [ObservableProperty] private bool _canAnimate; + [ObservableProperty] private bool _isAnimationSlideConflict; + [ObservableProperty] private bool _isPlayingAnimation; + [ObservableProperty] private bool _hasAnyAnimation; + [ObservableProperty] private bool _isAnimationExpanded = Se.Settings.Assa.DrawAnimationExpanded; + [ObservableProperty] private string _animationSummary = string.Empty; + + private bool _updatingAnimationFields; + private DispatcherTimer? _playbackTimer; + private readonly Stopwatch _playbackClock = new(); + + /// + /// The effect the gallery shows as picked: the in or the out effect. + /// + public DrawAnimationEffect EditedEffect + { + get => IsEditingOutEffect ? AnimationOut : AnimationIn; + set + { + if (IsEditingOutEffect) + { + AnimationOut = value; + } + else + { + AnimationIn = value; + } + } + } + + public int EditedEffectMs + { + get => IsEditingOutEffect ? AnimationOutMs : AnimationInMs; + set + { + if (IsEditingOutEffect) + { + AnimationOutMs = value; + } + else + { + AnimationInMs = value; + } + } + } + + /// + /// What the effects apply to: every shape on the selected shapes' layers (a layer is one line). + /// + internal List GetAnimationShapes() + { + var picked = GetSelectedShapeList(); + if (picked.Count == 0 && SelectedTreeItem?.IsLayer == true) + { + var layer = SelectedTreeItem.Layer; + return Shapes.Where(s => s.Layer == layer).ToList(); + } + + var layers = picked.Select(s => s.Layer).ToHashSet(); + return Shapes.Where(s => layers.Contains(s.Layer)).ToList(); + } + + [RelayCommand] + private void PickEffect(DrawAnimationEffect effect) => EditedEffect = effect; + + [RelayCommand] + private void EditInEffect() => IsEditingOutEffect = false; + + [RelayCommand] + private void EditOutEffect() => IsEditingOutEffect = true; + + private void RefreshAnimationFields() + { + var shapes = GetAnimationShapes(); + var animation = shapes.Select(s => s.Animation).FirstOrDefault(a => a != null) ?? new DrawAnimation(); + _updatingAnimationFields = true; + try + { + CanAnimate = shapes.Count > 0; + AnimationIn = animation.In; + AnimationOut = animation.Out; + AnimationInMs = animation.InMs; + AnimationOutMs = animation.OutMs; + } + finally + { + _updatingAnimationFields = false; + } + + UpdateAnimationState(); + } + + partial void OnAnimationInChanged(DrawAnimationEffect value) => ApplyAnimation("animation-in"); + + partial void OnAnimationOutChanged(DrawAnimationEffect value) => ApplyAnimation("animation-out"); + + partial void OnAnimationInMsChanged(int value) => ApplyAnimation("animation-in-ms"); + + partial void OnAnimationOutMsChanged(int value) => ApplyAnimation("animation-out-ms"); + + partial void OnIsEditingOutEffectChanged(bool value) + { + OnPropertyChanged(nameof(EditedEffect)); + OnPropertyChanged(nameof(EditedEffectMs)); + } + + private void ApplyAnimation(string undoKey) + { + OnPropertyChanged(nameof(EditedEffect)); + OnPropertyChanged(nameof(EditedEffectMs)); + if (_updatingAnimationFields) + { + return; + } + + var shapes = GetAnimationShapes(); + if (shapes.Count > 0) + { + var animation = new DrawAnimation + { + In = AnimationIn, + Out = AnimationOut, + InMs = Math.Clamp(AnimationInMs, 1, 60_000), + OutMs = Math.Clamp(AnimationOutMs, 1, 60_000), + }; + + if (shapes.Any(s => !Equals(s.Animation ?? new DrawAnimation(), animation))) + { + SaveUndo(undoKey); + foreach (var shape in shapes) + { + shape.Animation = animation.IsNone ? null : animation.Clone(); + } + + UpdateCodePreview(); + } + } + + UpdateAnimationState(); + + // Show what was picked + if (!IsPlayingAnimation && !_updatingAnimationFields && HasAnyAnimation && shapes.Count > 0) + { + StartAnimationPlayback(); + } + } + + partial void OnIsAnimationExpandedChanged(bool value) => Se.Settings.Assa.DrawAnimationExpanded = value; + + [RelayCommand] + private void ToggleAnimationExpanded() => IsAnimationExpanded = !IsAnimationExpanded; + + private void UpdateAnimationState() + { + AnimationSummary = AnimationIn == DrawAnimationEffect.None && AnimationOut == DrawAnimationEffect.None + ? string.Empty + : AssaDrawAnimationPanel.GetName(AnimationIn, isOut: false) + " · " + AssaDrawAnimationPanel.GetName(AnimationOut, isOut: true); + IsAnimationSlideConflict = DrawAnimation.IsSlide(AnimationIn) && DrawAnimation.IsSlide(AnimationOut); + HasAnyAnimation = Shapes.Any(s => s.Animation is { IsNone: false }); + if (!HasAnyAnimation && IsPlayingAnimation) + { + StopAnimationPlayback(); + } + } + + [RelayCommand] + private void ToggleAnimationPlayback() + { + if (IsPlayingAnimation) + { + StopAnimationPlayback(); + } + else + { + StartAnimationPlayback(); + } + } + + private void StartAnimationPlayback() + { + if (Canvas == null) + { + return; + } + + IsPlayingAnimation = true; + Canvas.GetAnimationTransform = GetCanvasAnimationTransform; + _playbackClock.Restart(); + _playbackTimer ??= new DispatcherTimer(TimeSpan.FromMilliseconds(16), DispatcherPriority.Render, OnPlaybackTick); + _playbackTimer.Start(); + OnPlaybackTick(null, EventArgs.Empty); + } + + internal void StopAnimationPlayback() + { + _playbackTimer?.Stop(); + _playbackClock.Reset(); + IsPlayingAnimation = false; + if (Canvas != null) + { + Canvas.AnimationTime = null; + Canvas.InvalidateVisual(); + } + } + + private void OnPlaybackTick(object? sender, EventArgs e) + { + if (Canvas == null) + { + _playbackTimer?.Stop(); + return; + } + + var loop = LineDurationMs + PlaybackPauseMs; + var time = _playbackClock.Elapsed.TotalMilliseconds % loop; + Canvas.AnimationTime = Math.Min(time, LineDurationMs); + Canvas.InvalidateVisual(); + } + + /// + /// The canvas asks this for each layer while playing: the state of the layer's animation at the + /// time, and the anchor it scales and turns around. + /// + internal (DrawAnimationState State, float AnchorX, float AnchorY) GetCanvasAnimationTransform(DrawShape styleShape, double timeMs) + { + if (styleShape.Animation is not { IsNone: false } animation) + { + return (DrawAnimationState.Rest, 0, 0); + } + + var anchor = GetAnimationAnchor(styleShape); + return (animation.GetState(timeMs, LineDurationMs, anchor.Bounds, CanvasWidth, CanvasHeight), anchor.X, anchor.Y); + } +} diff --git a/src/ui/Features/Assa/AssaDraw/AssaDrawViewModel.cs b/src/ui/Features/Assa/AssaDraw/AssaDrawViewModel.cs index 6b97ef7e2f3..f2fb8c11a2e 100644 --- a/src/ui/Features/Assa/AssaDraw/AssaDrawViewModel.cs +++ b/src/ui/Features/Assa/AssaDraw/AssaDrawViewModel.cs @@ -195,6 +195,10 @@ public void Initialize(Subtitle subtitle, List selectedLi { _subtitle = subtitle; _videoFileName = videoFileName; + if (selectedLines.Count > 0 && selectedLines[0].Duration.TotalMilliseconds >= 1) + { + LineDurationMs = (int)Math.Round(selectedLines[0].Duration.TotalMilliseconds); + } // Same frame Set position uses: the paused player position when it is inside the line _videoSeconds = selectedLines.Count > 0 @@ -964,6 +968,7 @@ private void UpdateSelectionInfo() ? item.Name : string.Empty; + RefreshAnimationFields(); UpdateCodePreview(); } @@ -2303,7 +2308,8 @@ internal void UpdatePreview() return; } - var subtitle = GenerateSubtitle(); + // The still preview shows the drawing at rest - the canvas plays the animation + var subtitle = GenerateSubtitle(withAnimation: false); var source = new AdvancedSubStationAlpha().ToText(subtitle, string.Empty) + CanvasWidth + "x" + CanvasHeight; if (source == _previewSource) { @@ -2657,8 +2663,9 @@ private async Task CopyToClipboard() /// The drawing as ASSA lines. (.assadraw save) writes hidden /// shapes as Comment lines, which turns back into hidden shapes. /// (.assadraw save too) writes the shapes' groups in the Effect field. + /// false (the still preview) leaves the in/out effects out. ///
- private Subtitle GenerateSubtitle(bool includeAll = false, bool hiddenAsComments = false, bool withGroups = false) + private Subtitle GenerateSubtitle(bool includeAll = false, bool hiddenAsComments = false, bool withGroups = false, bool withAnimation = true) { var subtitle = new Subtitle { @@ -2759,10 +2766,13 @@ private Subtitle GenerateSubtitle(bool includeAll = false, bool hiddenAsComments sbErase.Clear(); var firstShape = GetLayerStyleShape(layer); - // Collect draw shapes (normal shapes) + var animation = withAnimation ? GetLineAnimation(firstShape) : null; + var anchor = animation != null ? GetAnimationAnchor(firstShape!) : default; + + // Collect draw shapes (normal shapes) - relative to the anchor when animated foreach (var shape in layer.Where(p => !p.IsEraser && !p.IsTextBox)) { - sbDraw.Append(shape.ToAssa()); + sbDraw.Append(animation != null ? shape.ToAssa(anchor.X, anchor.Y) : shape.ToAssa()); sbDraw.Append(" "); } @@ -2799,6 +2809,14 @@ private Subtitle GenerateSubtitle(bool includeAll = false, bool hiddenAsComments finalText.Append($"\\p1}}{drawText}{{\\p0}}"); } + if (finalText.Length > 0 && animation != null) + { + finalText.Insert(0, "{\\an7" + + animation.GetPositionTag(anchor.X, anchor.Y, LineDurationMs, anchor.Bounds, CanvasWidth, CanvasHeight) + + animation.GetEffectTags(LineDurationMs) + "}" + + animation.FormatMarker(anchor.X, anchor.Y)); + } + if (finalText.Length > 0) { string styleName = string.Empty; @@ -2822,7 +2840,14 @@ private Subtitle GenerateSubtitle(bool includeAll = false, bool hiddenAsComments // layer on top, so the text shows over its bubble and under the layers above foreach (var shape in layer.Where(s => s.HasText && s.Points.Count > 0)) { - subtitle.Paragraphs.Add(new Paragraph(AssaDrawTextLine.Format(shape), 0, 10000) + var textAnimation = withAnimation ? GetLineAnimation(shape) : null; + var textLine = AssaDrawTextLine.Format(shape); + if (textAnimation != null) + { + textLine = AddTextAnimation(textLine, shape, textAnimation); + } + + subtitle.Paragraphs.Add(new Paragraph(textLine, 0, 10000) { Layer = layerKey, IsComment = isComment, @@ -2837,6 +2862,49 @@ private Subtitle GenerateSubtitle(bool includeAll = false, bool hiddenAsComments return subtitle; } + /// + /// Duration of the line the drawing is written to (the first selected line), for the out effect. + /// + public int LineDurationMs { get; set; } = 2000; + + private static DrawAnimation? GetLineAnimation(DrawShape? shape) => + shape?.Animation is { IsNone: false } animation ? animation : null; + + /// + /// What an animation scales and turns around, and what it slides out of the frame: the shape's + /// group, or else its layer. + /// + internal (float X, float Y, (float Left, float Top, float Right, float Bottom) Bounds) GetAnimationAnchor(DrawShape shape) + { + var members = shape.GroupId != 0 + ? Shapes.Where(s => s.GroupId == shape.GroupId && !s.IsEraser) + : Shapes.Where(s => s.Layer == shape.Layer && !s.IsEraser); + var bounds = DrawShape.GetBounds(members); + return ((bounds.Left + bounds.Right) / 2, (bounds.Top + bounds.Bottom) / 2, bounds); + } + + /// + /// A text line follows its shape's animation: \move instead of \pos for a slide, and after its + /// tag block the marker and the effects, turning around the shape's anchor (\org). + /// + private string AddTextAnimation(string textLine, DrawShape shape, DrawAnimation animation) + { + var anchor = GetAnimationAnchor(shape); + var (left, top, right, bottom) = shape.GetTextBounds(); + var position = animation.GetPositionTag((left + right) / 2, (top + bottom) / 2, LineDurationMs, anchor.Bounds, CanvasWidth, CanvasHeight); + var posStart = textLine.IndexOf("\\pos(", StringComparison.Ordinal); + if (posStart >= 0) + { + var posEnd = textLine.IndexOf(')', posStart); + textLine = textLine[..posStart] + position + textLine[(posEnd + 1)..]; + } + + var blockEnd = textLine.IndexOf('}'); + var effects = "{\\org(" + anchor.X.ToString("0.##", CultureInfo.InvariantCulture) + "," + anchor.Y.ToString("0.##", CultureInfo.InvariantCulture) + ")" + + animation.GetEffectTags(LineDurationMs, -shape.GetTextFrame().Angle) + "}"; + return textLine[..(blockEnd + 1)] + animation.FormatMarker(anchor.X, anchor.Y) + effects + textLine[(blockEnd + 1)..]; + } + /// /// The shape whose color names the layer's style: the first draw shape, or for a layer of only /// erase shapes (written as an \iclip-only line) the first erase shape. The style loop and the @@ -3398,7 +3466,7 @@ private void ImportAssaDrawingFromText(string text, int layer, Color color, bool Shapes.AddRange(DrawShape.ParseAssa(text, layer, color, isEraser)); } - private readonly List<(int Layer, AssaDrawTextLine.Parsed Text, bool Hidden, int GroupId)> _importedTexts = []; + private readonly List<(int Layer, AssaDrawTextLine.Parsed Text, bool Hidden, int GroupId, DrawAnimation? Animation)> _importedTexts = []; /// /// One line of a script: its \iclip erase shapes, its \p1 drawing (with the line's outline), @@ -3409,9 +3477,21 @@ private void ImportAssaDrawingFromText(string text, int layer, Color color, bool private void ImportLine(string text, int layer, Color color, bool hidden = false, string? effect = null) { var (drawGroups, eraseGroups) = ParseGroupEffect(effect); + + // An animated line: the effects come off, and its drawing (written relative to the anchor) goes back in place + DrawAnimation? animation = null; + float anchorX = 0, anchorY = 0; + var anchored = false; + if (DrawAnimation.TryParseMarker(text, out var parsedAnimation, out anchorX, out anchorY)) + { + animation = parsedAnimation.IsNone ? null : parsedAnimation; + anchored = DrawAnimation.IsAnchoredDrawingLine(text); + text = DrawAnimation.RemoveFromLine(text, parsedAnimation); + } + if (AssaDrawTextLine.TryParse(text, out var parsedText)) { - _importedTexts.Add((layer, parsedText, hidden, drawGroups.FirstOrDefault())); + _importedTexts.Add((layer, parsedText, hidden, drawGroups.FirstOrDefault(), animation)); return; } @@ -3425,6 +3505,11 @@ private void ImportLine(string text, int layer, Color color, bool hidden = false var groups = shape.IsEraser ? eraseGroups : drawGroups; var index = shape.IsEraser ? eraseIndex++ : drawIndex++; shape.GroupId = index < groups.Count ? groups[index] : 0; + shape.Animation = animation?.Clone(); + if (anchored && !shape.IsEraser) + { + shape.Offset(anchorX, anchorY); + } } } @@ -3464,7 +3549,7 @@ private void ImportDrawingLine(string text, int layer, Color color) /// private void AttachTextLines() { - foreach (var (layer, parsed, hidden, groupId) in _importedTexts) + foreach (var (layer, parsed, hidden, groupId, animation) in _importedTexts) { var (left, top, right, bottom) = parsed.Box; var centerX = (left + right) / 2; @@ -3485,6 +3570,8 @@ private void AttachTextLines() Shapes.Add(host); } + host.Animation ??= animation?.Clone(); + host.Text = parsed.Text; host.FontName = parsed.FontName; host.FontSize = parsed.FontSize; @@ -3945,6 +4032,7 @@ partial void OnSelectedShapesChanged(List value) } NotifyCommandStates(); + RefreshAnimationFields(); } partial void OnPointXChanged(float value) @@ -4337,6 +4425,7 @@ public void OnClosing() UiUtil.SaveWindowPosition(Window); _previewTimer?.Stop(); + StopAnimationPlayback(); if (Canvas != null) { (Canvas.PreviewImage as IDisposable)?.Dispose(); diff --git a/src/ui/Features/Assa/AssaDraw/AssaDrawWindow.cs b/src/ui/Features/Assa/AssaDraw/AssaDrawWindow.cs index e17d4c29f0d..c78bfe5a796 100644 --- a/src/ui/Features/Assa/AssaDraw/AssaDrawWindow.cs +++ b/src/ui/Features/Assa/AssaDraw/AssaDrawWindow.cs @@ -966,6 +966,7 @@ private Border CreateSidePanel(AssaDrawViewModel vm) properties.Children.Add(CreateShapePropertiesPanel(vm)); properties.Children.Add(CreateTextPropertiesPanel(vm)); properties.Children.Add(CreateLayerPropertiesPanel(vm)); + properties.Children.Add(AssaDrawAnimationPanel.Create(vm)); properties.Children.Add(CreatePointPropertiesPanel(vm)); var propertiesScroll = new ScrollViewer { diff --git a/src/ui/Features/Assa/AssaDraw/DrawAnimation.cs b/src/ui/Features/Assa/AssaDraw/DrawAnimation.cs new file mode 100644 index 00000000000..df101d0bfcf --- /dev/null +++ b/src/ui/Features/Assa/AssaDraw/DrawAnimation.cs @@ -0,0 +1,313 @@ +using System; +using System.Globalization; +using System.Text; +using System.Text.RegularExpressions; + +namespace Nikse.SubtitleEdit.Features.Assa.AssaDraw; + +/// +/// An in or out effect. Left/Right/Top/Bottom slide from (in) or to (out) that edge of the frame. +/// +public enum DrawAnimationEffect +{ + None, + Fade, + Pop, + Zoom, + Spin, + Left, + Right, + Top, + Bottom, +} + +/// +/// Where a layer (or a group's layers) is at a moment of its animation: moved by Dx/Dy, scaled and +/// turned around the anchor, faded to Opacity. Rotation is degrees clockwise on screen. +/// +public readonly record struct DrawAnimationState(float Dx, float Dy, float Scale, float Rotation, float Opacity) +{ + public static readonly DrawAnimationState Rest = new(0, 0, 1, 0, 1); +} + +/// +/// The in and out effect of a line. It is written as ASSA override tags (\move, \fad, \t with \fscx, +/// \fscy and \frz) around an anchor - the center of the layer or of its group, so a group scales and +/// turns as one - and kept in an {assadraw-anim ...} comment block so the editor can open it again. +/// +public sealed class DrawAnimation : IEquatable +{ + public const int DefaultDurationMs = 400; + private const float PopOvershoot = 1.1f; + private const float PopRiseShare = 0.7f; + private const double EaseOut = 0.5; + private const double EaseIn = 2; + + public DrawAnimationEffect In { get; set; } + public int InMs { get; set; } = DefaultDurationMs; + public DrawAnimationEffect Out { get; set; } + public int OutMs { get; set; } = DefaultDurationMs; + + public bool IsNone => In == DrawAnimationEffect.None && Out == DrawAnimationEffect.None; + + public DrawAnimation Clone() => new() { In = In, InMs = InMs, Out = Out, OutMs = OutMs }; + + public static bool IsSlide(DrawAnimationEffect effect) => effect >= DrawAnimationEffect.Left; + + /// + /// The out effect as written: ASSA moves a line only once (one \move), so a slide out after a + /// slide in fades out instead. + /// + public DrawAnimationEffect EffectiveOut => IsSlide(In) && IsSlide(Out) ? DrawAnimationEffect.Fade : Out; + + /// + /// The in effect gets the start of the line and the out effect the end; on a short line they + /// share it instead of overlapping. + /// + public (int InMs, int OutMs) GetDurations(int lineMs) + { + var inMs = In == DrawAnimationEffect.None ? 0 : Math.Max(1, InMs); + var outMs = EffectiveOut == DrawAnimationEffect.None ? 0 : Math.Max(1, OutMs); + if (lineMs > 0 && inMs + outMs > lineMs) + { + var factor = lineMs / (double)(inMs + outMs); + inMs = (int)(inMs * factor); + outMs = (int)(outMs * factor); + } + + return (inMs, outMs); + } + + /// + /// The state at into a line of , like libass shows it. + /// (what is animated) and the frame size give the slide distance. + /// + public DrawAnimationState GetState(double timeMs, int lineMs, (float Left, float Top, float Right, float Bottom) bounds, float frameWidth, float frameHeight) + { + var (inMs, outMs) = GetDurations(lineMs); + var outStart = lineMs - outMs; + float dx = 0, dy = 0, scale = 1, rotation = 0, opacity = 1; + + if (inMs > 0 && timeMs < inMs) + { + var p = Math.Clamp(timeMs / inMs, 0, 1); + var eased = (float)Math.Pow(p, EaseOut); + switch (In) + { + case DrawAnimationEffect.Fade: + opacity = (float)p; + break; + case DrawAnimationEffect.Zoom: + scale = eased; + break; + case DrawAnimationEffect.Pop: + scale = p < PopRiseShare + ? PopOvershoot * (float)Math.Pow(p / PopRiseShare, EaseOut) + : PopOvershoot - (PopOvershoot - 1) * (float)((p - PopRiseShare) / (1 - PopRiseShare)); + break; + case DrawAnimationEffect.Spin: + scale = eased; + rotation = 360 * (1 - eased); + break; + default: + var (sx, sy) = GetSlideOffset(In, bounds, frameWidth, frameHeight); + dx = sx * (float)(1 - p); + dy = sy * (float)(1 - p); + break; + } + } + else if (outMs > 0 && timeMs > outStart) + { + var q = Math.Clamp((timeMs - outStart) / outMs, 0, 1); + var eased = (float)Math.Pow(q, EaseIn); + switch (EffectiveOut) + { + case DrawAnimationEffect.Fade: + opacity = (float)(1 - q); + break; + case DrawAnimationEffect.Zoom: + scale = 1 - eased; + break; + case DrawAnimationEffect.Pop: + var rise = 1 - PopRiseShare; + scale = q < rise + ? 1 + (PopOvershoot - 1) * (float)(q / rise) + : PopOvershoot * (1 - (float)Math.Pow((q - rise) / PopRiseShare, EaseIn)); + break; + case DrawAnimationEffect.Spin: + scale = 1 - eased; + rotation = -360 * eased; + break; + default: + var (sx, sy) = GetSlideOffset(EffectiveOut, bounds, frameWidth, frameHeight); + dx = sx * (float)q; + dy = sy * (float)q; + break; + } + } + + // \frz turns counter-clockwise; the state is clockwise on screen like the canvas + return new DrawAnimationState(dx, dy, Math.Max(0, scale), -rotation, Math.Clamp(opacity, 0, 1)); + } + + /// + /// How far a slide moves: just out of the frame on that side. + /// + public static (float Dx, float Dy) GetSlideOffset(DrawAnimationEffect effect, (float Left, float Top, float Right, float Bottom) bounds, float frameWidth, float frameHeight) + { + const float margin = 4; + return effect switch + { + DrawAnimationEffect.Left => (-(bounds.Right + margin), 0), + DrawAnimationEffect.Right => (frameWidth - bounds.Left + margin, 0), + DrawAnimationEffect.Top => (0, -(bounds.Bottom + margin)), + DrawAnimationEffect.Bottom => (0, frameHeight - bounds.Top + margin), + _ => (0, 0), + }; + } + + /// + /// The position tag: \move for a slide, else \pos. + /// + public string GetPositionTag(float x, float y, int lineMs, (float Left, float Top, float Right, float Bottom) bounds, float frameWidth, float frameHeight) + { + var (inMs, outMs) = GetDurations(lineMs); + if (IsSlide(In)) + { + var (dx, dy) = GetSlideOffset(In, bounds, frameWidth, frameHeight); + return $"\\move({N(x + dx)},{N(y + dy)},{N(x)},{N(y)},0,{inMs})"; + } + + if (IsSlide(EffectiveOut)) + { + var (dx, dy) = GetSlideOffset(EffectiveOut, bounds, frameWidth, frameHeight); + return $"\\move({N(x)},{N(y)},{N(x + dx)},{N(y + dy)},{lineMs - outMs},{lineMs})"; + } + + return $"\\pos({N(x)},{N(y)})"; + } + + /// + /// \fad and the scale/rotation tags (everything but the position). is + /// the line's own \frz, which a spin turns from and back to. + /// + public string GetEffectTags(int lineMs, float baseFrz = 0) + { + var (inMs, outMs) = GetDurations(lineMs); + var outStart = lineMs - outMs; + var sb = new StringBuilder(); + + var fadeIn = In == DrawAnimationEffect.Fade ? inMs : 0; + var fadeOut = EffectiveOut == DrawAnimationEffect.Fade ? outMs : 0; + if (fadeIn > 0 || fadeOut > 0) + { + sb.Append(CultureInfo.InvariantCulture, $"\\fad({fadeIn},{fadeOut})"); + } + + switch (In) + { + case DrawAnimationEffect.Zoom: + sb.Append(CultureInfo.InvariantCulture, $"\\fscx0\\fscy0\\t(0,{inMs},{N(EaseOut)},\\fscx100\\fscy100)"); + break; + case DrawAnimationEffect.Pop: + var rise = (int)Math.Round(inMs * PopRiseShare); + sb.Append(CultureInfo.InvariantCulture, $"\\fscx0\\fscy0\\t(0,{rise},{N(EaseOut)},\\fscx{N(PopOvershoot * 100)}\\fscy{N(PopOvershoot * 100)})\\t({rise},{inMs},\\fscx100\\fscy100)"); + break; + case DrawAnimationEffect.Spin: + sb.Append(CultureInfo.InvariantCulture, $"\\frz{N(baseFrz + 360)}\\fscx0\\fscy0\\t(0,{inMs},{N(EaseOut)},\\frz{N(baseFrz)}\\fscx100\\fscy100)"); + break; + } + + switch (EffectiveOut) + { + case DrawAnimationEffect.Zoom: + sb.Append(CultureInfo.InvariantCulture, $"\\t({outStart},{lineMs},{N(EaseIn)},\\fscx0\\fscy0)"); + break; + case DrawAnimationEffect.Pop: + var rise = outStart + (int)Math.Round(outMs * (1 - PopRiseShare)); + sb.Append(CultureInfo.InvariantCulture, $"\\t({outStart},{rise},\\fscx{N(PopOvershoot * 100)}\\fscy{N(PopOvershoot * 100)})\\t({rise},{lineMs},{N(EaseIn)},\\fscx0\\fscy0)"); + break; + case DrawAnimationEffect.Spin: + sb.Append(CultureInfo.InvariantCulture, $"\\t({outStart},{lineMs},{N(EaseIn)},\\frz{N(baseFrz - 360)}\\fscx0\\fscy0)"); + break; + } + + return sb.ToString(); + } + + public bool HasRotation => In == DrawAnimationEffect.Spin || EffectiveOut == DrawAnimationEffect.Spin; + + private static readonly Regex MarkerRegex = new( + @"\{assadraw-anim c=(-?[\d.]+),(-?[\d.]+) in=(\w+),(\d+) out=(\w+),(\d+)\}", + RegexOptions.Compiled); + + /// + /// The comment block that keeps the animation and its anchor in the line. + /// + public string FormatMarker(float anchorX, float anchorY) => + $"{{assadraw-anim c={N(anchorX)},{N(anchorY)} in={In},{InMs.ToString(CultureInfo.InvariantCulture)} out={Out},{OutMs.ToString(CultureInfo.InvariantCulture)}}}"; + + /// + /// Reads the marker of an animated line; false for a line without one. + /// + public static bool TryParseMarker(string text, out DrawAnimation animation, out float anchorX, out float anchorY) + { + animation = new DrawAnimation(); + anchorX = anchorY = 0; + var match = MarkerRegex.Match(text ?? string.Empty); + if (!match.Success) + { + return false; + } + + anchorX = F(match.Groups[1].Value); + anchorY = F(match.Groups[2].Value); + animation.In = Enum.TryParse(match.Groups[3].Value, out var inEffect) ? inEffect : DrawAnimationEffect.None; + animation.InMs = int.Parse(match.Groups[4].Value, CultureInfo.InvariantCulture); + animation.Out = Enum.TryParse(match.Groups[5].Value, out var outEffect) ? outEffect : DrawAnimationEffect.None; + animation.OutMs = int.Parse(match.Groups[6].Value, CultureInfo.InvariantCulture); + return true; + } + + private static readonly Regex DrawingAnimationRegex = new(@"^\{\\an7[^}]*\}\{assadraw-anim [^}]*\}", RegexOptions.Compiled); + private static readonly Regex TextAnimationRegex = new(@"\{assadraw-anim [^}]*\}(\{[^}]*\})?", RegexOptions.Compiled); + private static readonly Regex MoveRegex = new(@"\\move\(\s*(-?[\d.]+)\s*,\s*(-?[\d.]+)\s*,\s*(-?[\d.]+)\s*,\s*(-?[\d.]+)[^)]*\)", RegexOptions.Compiled); + + /// + /// The line without its animation: a drawing line loses its leading position/effect block (its + /// coordinates are then relative to the anchor), a text line its effect block, and its \move + /// becomes the \pos it rests at. + /// + public static string RemoveFromLine(string text, DrawAnimation animation) + { + if (DrawingAnimationRegex.IsMatch(text)) + { + return DrawingAnimationRegex.Replace(text, string.Empty, 1); + } + + text = TextAnimationRegex.Replace(text, string.Empty, 1); + return MoveRegex.Replace(text, m => + { + var restX = IsSlide(animation.In) ? m.Groups[3].Value : m.Groups[1].Value; + var restY = IsSlide(animation.In) ? m.Groups[4].Value : m.Groups[2].Value; + return $"\\pos({restX},{restY})"; + }, 1); + } + + /// + /// True for a drawing line written relative to its anchor (see ). + /// + public static bool IsAnchoredDrawingLine(string text) => DrawingAnimationRegex.IsMatch(text ?? string.Empty); + + public bool Equals(DrawAnimation? other) => + other != null && In == other.In && InMs == other.InMs && Out == other.Out && OutMs == other.OutMs; + + public override bool Equals(object? obj) => Equals(obj as DrawAnimation); + + public override int GetHashCode() => HashCode.Combine(In, InMs, Out, OutMs); + + // No "-0" for a text with no angle of its own + private static string N(double value) => Math.Abs(value) < 0.005 ? "0" : value.ToString("0.##", CultureInfo.InvariantCulture); + + private static float F(string value) => float.Parse(value, NumberStyles.Float, CultureInfo.InvariantCulture); +} diff --git a/src/ui/Features/Assa/AssaDraw/DrawShape.cs b/src/ui/Features/Assa/AssaDraw/DrawShape.cs index 8f2a5217583..a9ba8b310c8 100644 --- a/src/ui/Features/Assa/AssaDraw/DrawShape.cs +++ b/src/ui/Features/Assa/AssaDraw/DrawShape.cs @@ -33,6 +33,12 @@ public class DrawShape /// public int GroupId { get; set; } + /// + /// The in/out effect of the shape's line (a line is a layer, so every shape of a layer has the + /// same one); null = not animated. + /// + public DrawAnimation? Animation { get; set; } + /// /// A text-only object: its rectangle is where the text goes, nothing is drawn for it. /// @@ -74,6 +80,7 @@ public DrawShape(DrawShape other) Hidden = other.Hidden; Expanded = other.Expanded; GroupId = other.GroupId; + Animation = other.Animation?.Clone(); IsTextBox = other.IsTextBox; Text = other.Text; FontName = other.FontName; @@ -97,9 +104,10 @@ public void AddPoint(DrawCoordinateType drawType, float x, float y, Color pointC } /// - /// Converts the shape to ASSA drawing commands. + /// Converts the shape to ASSA drawing commands, optionally relative to an anchor (an animated + /// line is positioned at its anchor with \pos or \move). /// - public string ToAssa() + public string ToAssa(float originX = 0, float originY = 0) { if (Points.Count == 0) { @@ -110,7 +118,7 @@ public string ToAssa() var first = Points[0]; // Start with move command using the first point - sb.Append(CultureInfo.InvariantCulture, $"m {first.X:0.##} {first.Y:0.##} "); + sb.Append(CultureInfo.InvariantCulture, $"m {first.X - originX:0.##} {first.Y - originY:0.##} "); // A shape can mix line and bezier segments (the importer appends "l" and "b" runs to // the same shape, and switching tools mid-shape does too). Emit each point under the @@ -130,7 +138,7 @@ public string ToAssa() currentCommand = command; } - sb.Append(CultureInfo.InvariantCulture, $"{point.X:0.##} {point.Y:0.##} "); + sb.Append(CultureInfo.InvariantCulture, $"{point.X - originX:0.##} {point.Y - originY:0.##} "); } return sb.ToString().Trim(); diff --git a/src/ui/Logic/Config/Language/Assa/LanguageAssa.cs b/src/ui/Logic/Config/Language/Assa/LanguageAssa.cs index 0b7bdd43439..b0e9b1c386f 100644 --- a/src/ui/Logic/Config/Language/Assa/LanguageAssa.cs +++ b/src/ui/Logic/Config/Language/Assa/LanguageAssa.cs @@ -104,6 +104,26 @@ public class LanguageAssa public string DrawClipArtCatFace { get; set; } public string DrawClipArtKey { get; set; } public string DrawClipArtChristmasTree { get; set; } + public string DrawAnimation { get; set; } + public string DrawAnimationIn { get; set; } + public string DrawAnimationOut { get; set; } + public string DrawAnimationNone { get; set; } + public string DrawAnimationFade { get; set; } + public string DrawAnimationPop { get; set; } + public string DrawAnimationZoom { get; set; } + public string DrawAnimationSpin { get; set; } + public string DrawAnimationFromLeft { get; set; } + public string DrawAnimationFromRight { get; set; } + public string DrawAnimationFromTop { get; set; } + public string DrawAnimationFromBottom { get; set; } + public string DrawAnimationToLeft { get; set; } + public string DrawAnimationToRight { get; set; } + public string DrawAnimationToTop { get; set; } + public string DrawAnimationToBottom { get; set; } + public string DrawAnimationPlay { get; set; } + public string DrawAnimationStop { get; set; } + public string DrawAnimationSlideConflict { get; set; } + public string DrawAnimationLayerHint { get; set; } public string DrawCategoryMyShapes { get; set; } public string DrawShapeSpeechBubble { get; set; } public string DrawShapeSpeechBubbleRight { get; set; } @@ -610,6 +630,26 @@ public LanguageAssa() DrawClipArtCatFace = "Cat face"; DrawClipArtKey = "Key"; DrawClipArtChristmasTree = "Christmas tree"; + DrawAnimation = "Animation"; + DrawAnimationIn = "In"; + DrawAnimationOut = "Out"; + DrawAnimationNone = "None"; + DrawAnimationFade = "Fade"; + DrawAnimationPop = "Pop"; + DrawAnimationZoom = "Zoom"; + DrawAnimationSpin = "Spin"; + DrawAnimationFromLeft = "From left"; + DrawAnimationFromRight = "From right"; + DrawAnimationFromTop = "From top"; + DrawAnimationFromBottom = "From bottom"; + DrawAnimationToLeft = "To left"; + DrawAnimationToRight = "To right"; + DrawAnimationToTop = "To top"; + DrawAnimationToBottom = "To bottom"; + DrawAnimationPlay = "Play animation"; + DrawAnimationStop = "Stop animation"; + DrawAnimationSlideConflict = "ASSA can move a line only once, so the out slide fades instead."; + DrawAnimationLayerHint = "Applies to the whole layer (one subtitle line); a group moves as one."; DrawCategoryMyShapes = "My shapes"; DrawShapeSpeechBubble = "Speech bubble"; DrawShapeSpeechBubbleRight = "Speech bubble, tail right"; diff --git a/src/ui/Logic/Config/SeAssa.cs b/src/ui/Logic/Config/SeAssa.cs index 0d772a8d00b..1e309ef2a0f 100644 --- a/src/ui/Logic/Config/SeAssa.cs +++ b/src/ui/Logic/Config/SeAssa.cs @@ -56,6 +56,11 @@ public class SeAssa ///
public bool DrawBackgroundStretch { get; set; } + /// + /// ASSA draw: the Animation section of the properties panel is open. + /// + public bool DrawAnimationExpanded { get; set; } + /// /// ASSA draw: shapes the user saved to the shape library ("My shapes"). /// diff --git a/tests/UI/Features/Assa/AssaDrawAnimationTests.cs b/tests/UI/Features/Assa/AssaDrawAnimationTests.cs new file mode 100644 index 00000000000..cb16099f0a9 --- /dev/null +++ b/tests/UI/Features/Assa/AssaDrawAnimationTests.cs @@ -0,0 +1,172 @@ +using Avalonia.Headless.XUnit; +using Avalonia.Threading; +using Nikse.SubtitleEdit.Core.Common; +using Nikse.SubtitleEdit.Core.SubtitleFormats; +using Nikse.SubtitleEdit.Features.Assa.AssaDraw; +using Nikse.SubtitleEdit.Features.Main; +using Nikse.SubtitleEdit.Logic.Media; + +namespace UITests.Features.Assa; + +/// +/// In/out effects in ASSA draw: written as \move, \fad and \t around the layer's or group's anchor, +/// kept in a marker so the editor opens them again, and played on the canvas. +/// +public class AssaDrawAnimationTests +{ + private const string SquareA = "{\\p1}m 100 100 l 200 100 200 200 100 200{\\p0}"; + private const string SquareB = "{\\p1}m 300 100 l 400 100 400 200 300 200{\\p0}"; + private const string SquareC = "{\\p1}m 600 600 l 700 600 700 700 600 700{\\p0}"; + + private static AssaDrawViewModel MakeViewModel(params string[] lines) + { + var vm = new AssaDrawViewModel(new FileHelper(), new StubWindowService()); + var rows = lines.Select((t, i) => new SubtitleLineViewModel(new Paragraph(t, 0, 3000) { Extra = "Default", Layer = i }, new AdvancedSubStationAlpha())).ToList(); + vm.Initialize(new Subtitle { Header = AdvancedSubStationAlpha.DefaultHeader }, rows, 1920, 1080); + return vm; + } + + private static List Points(AssaDrawViewModel vm) => + vm.Shapes.OrderBy(s => s.Layer).Select(s => s.ToAssa()).ToList(); + + [AvaloniaFact] + public void GroupAnimation_IsWrittenAroundTheGroupCenter_AndOpensAgain() + { + var vm = MakeViewModel(SquareA, SquareB, SquareC); + Assert.Equal(3000, vm.LineDurationMs); + vm.SetSelection([vm.Shapes[0], vm.Shapes[1]]); + vm.GroupShapesCommand.Execute(null); + Dispatcher.UIThread.RunJobs(); + + vm.AnimationIn = DrawAnimationEffect.Pop; + vm.IsEditingOutEffect = true; + vm.EditedEffect = DrawAnimationEffect.Fade; + vm.EditedEffectMs = 500; + + Assert.Equal(DrawAnimationEffect.Fade, vm.AnimationOut); + Assert.Equal(500, vm.AnimationOutMs); + Assert.NotNull(vm.Shapes[0].Animation); + Assert.NotNull(vm.Shapes[1].Animation); + Assert.Null(vm.Shapes[2].Animation); + + var expectedPoints = Points(vm); + var text = vm.GetAssaDrawFileText(); + vm.OkCommand.Execute(null); + var lines = vm.ResultSubtitle.Paragraphs; + + // Both group lines anchored at the group's center (100..400 x 100..200 -> 250,150), coordinates relative to it + Assert.StartsWith("{\\an7\\pos(250,150)\\fad(0,500)\\fscx0\\fscy0\\t(0,280,0.5,\\fscx110\\fscy110)\\t(280,400,\\fscx100\\fscy100)}{assadraw-anim c=250,150 in=Pop,400 out=Fade,500}", lines[0].Text); + Assert.Contains("m -150 -50 l -50 -50 -50 50 -150 50", lines[0].Text); + Assert.Contains("\\pos(250,150)", lines[1].Text); + Assert.Contains("m 50 -50 l 150 -50 150 50 50 50", lines[1].Text); + Assert.Equal(SquareC, lines[2].Text); + Assert.All(lines, l => Assert.True(MainViewModel.IsAssaDrawingText(l.Text))); + + var loaded = MakeViewModel(); + loaded.LoadFromText(text); + Assert.Equal(expectedPoints, Points(loaded)); + var animation = loaded.Shapes.First(s => s.Layer == 0).Animation; + Assert.Equal(new DrawAnimation { In = DrawAnimationEffect.Pop, Out = DrawAnimationEffect.Fade, OutMs = 500 }, animation); + Assert.Null(loaded.Shapes.First(s => s.Layer == 2).Animation); + + // Saving again writes the same file + Assert.Equal(text, loaded.GetAssaDrawFileText()); + } + + [AvaloniaFact] + public void SlideIn_UsesMove_AndASecondSlideOutFadesInstead() + { + var vm = MakeViewModel(SquareC); + vm.SetSelection([vm.Shapes[0]]); + vm.AnimationIn = DrawAnimationEffect.Left; + vm.AnimationOut = DrawAnimationEffect.Bottom; + + Assert.True(vm.IsAnimationSlideConflict); + vm.OkCommand.Execute(null); + var line = vm.ResultSubtitle.Paragraphs[0].Text; + + // From just left of the frame (right edge 700 + 4) to the anchor 650,650, then a 400 ms fade out + Assert.StartsWith("{\\an7\\move(-54,650,650,650,0,400)\\fad(0,400)}", line); + } + + [AvaloniaFact] + public void SlideOut_MovesAtTheEndOfTheLine() + { + var vm = MakeViewModel(SquareC); + vm.SetSelection([vm.Shapes[0]]); + vm.AnimationOut = DrawAnimationEffect.Right; + vm.AnimationOutMs = 600; + + vm.OkCommand.Execute(null); + + Assert.StartsWith("{\\an7\\move(650,650,1974,650,2400,3000)}", vm.ResultSubtitle.Paragraphs[0].Text); + } + + [AvaloniaFact] + public void TextInAnimatedShape_FollowsTheShape_AndOpensAgain() + { + var vm = MakeViewModel(SquareA); + vm.SetSelection([vm.Shapes[0]]); + vm.ShapeText = "Hi"; + vm.AnimationIn = DrawAnimationEffect.Spin; + vm.AnimationOut = DrawAnimationEffect.Top; + var text = vm.GetAssaDrawFileText(); + + vm.OkCommand.Execute(null); + var textLine = vm.ResultSubtitle.Paragraphs.Single(p => AssaDrawTextLine.IsTextLine(p.Text)).Text; + Assert.Contains("\\move(150,150,150,-54,2600,3000)", textLine); + Assert.DoesNotContain("\\pos(", textLine); + Assert.Contains("{\\org(150,150)\\frz360\\fscx0\\fscy0\\t(0,400,0.5,\\frz0\\fscx100\\fscy100)}", textLine); + + // The out effect is timed for the line, so the same line length writes the same file + var loaded = MakeViewModel(); + loaded.LineDurationMs = vm.LineDurationMs; + loaded.LoadFromText(text); + var shape = Assert.Single(loaded.Shapes); + Assert.Equal("Hi", shape.Text); + Assert.Equal("m 100 100 l 200 100 200 200 100 200", shape.ToAssa()); + Assert.Equal(DrawAnimationEffect.Spin, shape.Animation?.In); + Assert.Equal(text, loaded.GetAssaDrawFileText()); + } + + [AvaloniaFact] + public void ChoosingNone_RemovesTheAnimation() + { + var vm = MakeViewModel(SquareA); + vm.SetSelection([vm.Shapes[0]]); + vm.AnimationIn = DrawAnimationEffect.Zoom; + vm.AnimationIn = DrawAnimationEffect.None; + + Assert.Null(vm.Shapes[0].Animation); + vm.OkCommand.Execute(null); + Assert.Equal(SquareA, vm.ResultSubtitle.Paragraphs[0].Text); + } + + [AvaloniaFact] + public void Undo_RestoresThePreviousAnimation() + { + var vm = MakeViewModel(SquareA); + vm.SetSelection([vm.Shapes[0]]); + vm.AnimationIn = DrawAnimationEffect.Fade; + + vm.UndoCommand.Execute(null); + + Assert.Null(vm.Shapes[0].Animation); + } + + [Fact] + public void State_MatchesTheTags_AtTheEdges() + { + var animation = new DrawAnimation { In = DrawAnimationEffect.Zoom, Out = DrawAnimationEffect.Spin }; + var bounds = (100f, 100f, 200f, 200f); + + Assert.Equal(0, animation.GetState(0, 2000, bounds, 1920, 1080).Scale); + Assert.Equal(DrawAnimationState.Rest, animation.GetState(1000, 2000, bounds, 1920, 1080)); + var end = animation.GetState(2000, 2000, bounds, 1920, 1080); + Assert.Equal(0, end.Scale); + Assert.Equal(360, end.Rotation); + + // A line shorter than both effects shares it between them + Assert.Equal((150, 150), animation.GetDurations(300)); + } +} From b946e00d03275f9911d24b5a2124a59b8d0f8db9 Mon Sep 17 00:00:00 2001 From: niksedk Date: Fri, 16 Oct 2026 16:42:30 +0200 Subject: [PATCH 272/306] ASSA draw animation: slide in and slide out on the same line ASSA has one \move per line, so a slide out after a slide in faded out instead. The slide out now turns the line a tiny bit around an \org a million pixels away - a straight, eased move - while the slide in keeps \move. Text lines turn around the same \org as their drawing, and their position is written to allow for their own \frz turning around it (this also fixes turned text in a spinning shape showing out of place). Co-Authored-By: Claude Opus 5.5 --- .../Assa/AssaDraw/AssaDrawAnimationPanel.cs | 8 -- .../AssaDraw/AssaDrawViewModel.Animation.cs | 2 - .../Assa/AssaDraw/AssaDrawViewModel.cs | 37 ++++-- .../Features/Assa/AssaDraw/DrawAnimation.cs | 113 +++++++++++++----- .../Config/Language/Assa/LanguageAssa.cs | 2 - .../Features/Assa/AssaDrawAnimationTests.cs | 55 ++++++++- 6 files changed, 163 insertions(+), 54 deletions(-) diff --git a/src/ui/Features/Assa/AssaDraw/AssaDrawAnimationPanel.cs b/src/ui/Features/Assa/AssaDraw/AssaDrawAnimationPanel.cs index ac6125ced1c..ca511469407 100644 --- a/src/ui/Features/Assa/AssaDraw/AssaDrawAnimationPanel.cs +++ b/src/ui/Features/Assa/AssaDraw/AssaDrawAnimationPanel.cs @@ -75,14 +75,6 @@ public static Border Create(AssaDrawViewModel vm) rows.Children.Add(MakePropertyRow(Se.Language.General.Duration, duration, play)); - rows.Children.Add(new TextBlock - { - Text = Se.Language.Assa.DrawAnimationSlideConflict, - FontSize = 11.5, - TextWrapping = TextWrapping.Wrap, - Foreground = new SolidColorBrush(Color.FromRgb(230, 160, 40)), - [!TextBlock.IsVisibleProperty] = new Binding(nameof(vm.IsAnimationSlideConflict)), - }); rows.Children.Add(new TextBlock { Text = Se.Language.Assa.DrawAnimationLayerHint, diff --git a/src/ui/Features/Assa/AssaDraw/AssaDrawViewModel.Animation.cs b/src/ui/Features/Assa/AssaDraw/AssaDrawViewModel.Animation.cs index 94051bbf655..57c12ee419b 100644 --- a/src/ui/Features/Assa/AssaDraw/AssaDrawViewModel.Animation.cs +++ b/src/ui/Features/Assa/AssaDraw/AssaDrawViewModel.Animation.cs @@ -29,7 +29,6 @@ public partial class AssaDrawViewModel ///
/// Italic state of the nearest word with letters in (ties only /// when ), or null when there is none. @@ -246,7 +323,7 @@ private static WordSlant GetSlant(WordGroup word) for (var i = 0; i < word.Chars.Count; i++) { var ch = word.Chars[i]; - if (!HasLetterOrDigit(ch.Text)) + if (!HasLetterOrDigit(ch.Text) || HasItalicTag(ch.Text)) { continue; } diff --git a/tests/libuilogic/Ocr/ItalicTextMergerTests.cs b/tests/libuilogic/Ocr/ItalicTextMergerTests.cs index d20d26a3345..ea486b85843 100644 --- a/tests/libuilogic/Ocr/ItalicTextMergerTests.cs +++ b/tests/libuilogic/Ocr/ItalicTextMergerTests.cs @@ -135,4 +135,34 @@ public void BinaryOcrMatchesUseSameRules() Assert.Equal("- Hi.", ItalicTextMerger.MergeWithItalicTags(chars)); } + + [Fact] + public void TaggedGlyphInItalicWordSplitsTheTag() + { + // One glyph trained as "it": italic i joined to an upright t, inside italic text. + var chars = Chars("ya ?", "ii.i"); + chars.Insert(3, new NOcrChar { Text = "it", Italic = true }); + + Assert.Equal("ya it?", ItalicTextMerger.MergeWithItalicTags(chars)); + } + + [Fact] + public void TaggedGlyphInUprightWordKeepsOnlyItsOwnItalic() + { + var chars = Chars("No ", "..."); + chars.Add(new NOcrChar { Text = "it", Italic = false }); + + Assert.Equal("No it", ItalicTextMerger.MergeWithItalicTags(chars)); + } + + [Fact] + public void TaggedGlyphDoesNotVoteAndIgnoresTagCase() + { + // The tagged glyph is flagged upright, but only "ab" votes, so "c" stays italic. + var chars = Chars("ab", "ii"); + chars.Add(new NOcrChar { Text = "xy", Italic = false }); + chars.Add(new NOcrChar { Text = "c", Italic = false }); + + Assert.Equal("abxyc", ItalicTextMerger.MergeWithItalicTags(chars)); + } } From 836f762aa1e7b014daa735bd78be7f30eb0a9d5c Mon Sep 17 00:00:00 2001 From: niksedk Date: Sun, 11 Oct 2026 07:34:25 +0200 Subject: [PATCH 054/306] Docs: nOCR mixed italic glyph text Co-Authored-By: Claude Opus 5.5 --- docs/features/ocr.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/features/ocr.md b/docs/features/ocr.md index 7d4406574b6..d122269c8b5 100644 --- a/docs/features/ocr.md +++ b/docs/features/ocr.md @@ -31,6 +31,7 @@ Built-in trainable OCR engine. - Very accurate once trained - Best for consistent fonts (like DVD/Blu-ray subtitles) - **Train nOCR database...** - a database can also be trained from installed fonts instead of being taught during OCR. Open the nOCR database settings (the settings button next to the database list) and right-click anywhere in that dialog to reach it. Pick one or more fonts, a font size, the characters to train (or **Import characters from subtitle file...**), letter combinations that might end up in one image, whether to include bold and italic, and the number of line segments; **Start training** renders every character in every selected font and saves the result as a new `.nocr` database in the OCR folder, ready to pick from the database list +- **Mixed italic in one image** - when an italic letter touches an upright one and both end up in a single image, type the text with italic tags, e.g. `it`. The tagged part is italic and the rest upright, whatever the surrounding word is, so an italic line comes out as `... it?` instead of nested tags. This works the same in Binary OCR ### Binary OCR Binary image comparison engine, listed as **Binary image compare** in the engine dropdown. From 38fc5600b2d8128b7479396eb387df55bf2d4553 Mon Sep 17 00:00:00 2001 From: niksedk Date: Sun, 11 Oct 2026 08:07:31 +0200 Subject: [PATCH 055/306] Latin.nocr: drop lines that don't tell glyphs apart from lookalikes Discriminative pruning of every entry against rendered lookalikes (8 fonts x 24/30/40 px x regular/bold/italic): each lookalike keeps min(original wrong points, 26) wrong points and no lower wrong-point share, the entry's own glyphs keep their share, and entries keep at least 52 lines so they stay in the wide/deep-seek passes. 325k -> 187k lines, 842 KB -> 489 KB. End-to-end CER (A/B harness): dev 2.10 -> 1.98, held-out 2.08 -> 1.81, unseen fonts 8.71 -> 6.87. Co-Authored-By: Claude Opus 5.5 --- Ocr/Latin.nocr | Bin 863019 -> 501463 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/Ocr/Latin.nocr b/Ocr/Latin.nocr index 74383b76755dddd4e241a9ab8ff36a35c7f492a3..e9019e3acebb7faa50871ccc9b9307b0f0cdc498 100644 GIT binary patch literal 501463 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 literal 863019 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 z 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

    R7jbJpaG_A3+Cm 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

    |xqlS~!Sr4vasf?;R-^U>~naT7jB1i4f7geE! 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�NMKw$*?>ZlB$wvB-sUI9cov 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

    XUXVl?qv7`I^SZ&hkWh@kgAV>ht+iG# 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⪙VR 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

    &@0lA2=0D$r?9)^6Ya@hSHBvm8ua<Z0Ate%*a94 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 z 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

    DK3&uH<`0hP_xK!d5BBC*mWV+N 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 From aa9f715b0352adf312d47e31e225c8a40beba2f9 Mon Sep 17 00:00:00 2001 From: niksedk Date: Sun, 11 Oct 2026 08:18:00 +0200 Subject: [PATCH 056/306] nOCR: Optimize database (OCR window + seconv optimize-nocr) NOcrDbOptimizer drops the lines of each entry that don't tell it apart from lookalikes rendered in common subtitle fonts: every lookalike keeps min(original wrong points, budget + 1) wrong points and no lower share, the entry's own glyphs keep their share, and entries keep at least 52 lines so they stay in the wide/deep-seek passes. - OCR window: nOCR settings right-click > Optimize nOCR database..., works on a fresh copy, keeps .nocr.bak - seconv optimize-nocr [--output:] [--json] Co-Authored-By: Claude Opus 5.5 --- docs/features/ocr.md | 1 + docs/reference/command-line.md | 1 + src/libuilogic/Ocr/NOcrDbOptimizer.cs | 501 ++++++++++++++++++ src/seconv/Helpers/CliSchema.cs | 1 + src/seconv/Helpers/HelpDisplay.cs | 3 +- src/seconv/Program.cs | 122 ++++- .../Ocr/NOcr/NOcrSettingsViewModel.cs | 8 + .../Features/Ocr/NOcr/NOcrSettingsWindow.cs | 10 +- src/ui/Features/Ocr/OcrViewModel.cs | 72 +++ .../Logic/Config/Language/Ocr/LanguageOcr.cs | 10 + tests/libuilogic/Ocr/NOcrDbOptimizerTests.cs | 142 +++++ 11 files changed, 867 insertions(+), 4 deletions(-) create mode 100644 src/libuilogic/Ocr/NOcrDbOptimizer.cs create mode 100644 tests/libuilogic/Ocr/NOcrDbOptimizerTests.cs diff --git a/docs/features/ocr.md b/docs/features/ocr.md index d122269c8b5..c783659a8cb 100644 --- a/docs/features/ocr.md +++ b/docs/features/ocr.md @@ -31,6 +31,7 @@ Built-in trainable OCR engine. - Very accurate once trained - Best for consistent fonts (like DVD/Blu-ray subtitles) - **Train nOCR database...** - a database can also be trained from installed fonts instead of being taught during OCR. Open the nOCR database settings (the settings button next to the database list) and right-click anywhere in that dialog to reach it. Pick one or more fonts, a font size, the characters to train (or **Import characters from subtitle file...**), letter combinations that might end up in one image, whether to include bold and italic, and the number of line segments; **Start training** renders every character in every selected font and saves the result as a new `.nocr` database in the OCR folder, ready to pick from the database list +- **Optimize nOCR database...** - in the same right-click menu. Removes the lines of each character that don't tell it apart from similar characters (e.g. the stem that "i", "l" and "j" share), using glyphs rendered from common subtitle fonts as the check: every similar character must still be rejected, and the character itself must still match as well as before. The database gets smaller and OCR faster, usually with fewer errors - the shipped Latin database is already optimized this way. It takes up to a minute; the original is kept as `.nocr.bak` in the OCR folder. Also available in seconv as `seconv optimize-nocr ` - **Mixed italic in one image** - when an italic letter touches an upright one and both end up in a single image, type the text with italic tags, e.g. `it`. The tagged part is italic and the rest upright, whatever the surrounding word is, so an italic line comes out as `... it?` instead of nested tags. This works the same in Binary OCR ### Binary OCR diff --git a/docs/reference/command-line.md b/docs/reference/command-line.md index 2b0a6aa7952..f9a4edec309 100644 --- a/docs/reference/command-line.md +++ b/docs/reference/command-line.md @@ -85,6 +85,7 @@ seconv list-rf-rules # list remove-formatting rule IDs (alias: list-remov seconv dump-settings # print a full --settings JSON with libse defaults (alias: default-settings) seconv info # print format/encoding/duration/language for a file seconv lint # validate subtitle(s); exit 1 if issues found +seconv optimize-nocr # remove nOCR lines that don't tell characters apart (in place + .bak, or --output:) seconv --help # show help (same text as -h, /? and /help) seconv --help-json # print the whole command-line schema as JSON seconv --version # print version and exit diff --git a/src/libuilogic/Ocr/NOcrDbOptimizer.cs b/src/libuilogic/Ocr/NOcrDbOptimizer.cs new file mode 100644 index 00000000000..44f08038d1c --- /dev/null +++ b/src/libuilogic/Ocr/NOcrDbOptimizer.cs @@ -0,0 +1,501 @@ +using SkiaSharp; + +namespace Nikse.SubtitleEdit.UiLogic.Ocr; + +public sealed class NOcrDbOptimizerSettings +{ + ///

    Fonts to render the validation glyphs in; missing fonts are skipped. + public List FontNames { get; set; } = new(DefaultFontNames); + + public List FontSizes { get; set; } = new() { 24, 30, 40 }; + + /// + /// Entries keep at least this many lines. The wide and deep-seek match passes skip entries + /// with fewer than 41 / 51 lines, so going below 52 trades wrong letters for asterisks. + /// + public int MinLines { get; set; } = 52; + + /// The loosest wrong-pixel budget a lookalike must stay above (the deep-seek default). + public int MaxWrongPixels { get; set; } = 25; + + public static readonly string[] DefaultFontNames = + { + "Arial", "Helvetica", "Verdana", "Tahoma", "Trebuchet MS", "Arial Narrow", "Helvetica Neue", "Times New Roman", + }; +} + +public sealed class NOcrDbOptimizeResult +{ + public int Entries { get; init; } + public int LinesBefore { get; init; } + public int LinesAfter { get; init; } + public int ValidationGlyphs { get; init; } + public List FontsUsed { get; init; } = new(); +} + +/// +/// Removes the lines of each nOCR entry that don't tell it apart from its lookalikes. Most +/// trained lines are random and also hold for similar characters ("i" and "l" share the stem), +/// so they carry no evidence, and they dilute the share of wrong points the matcher ranks +/// candidates by. Every entry and its lookalikes are rendered in common subtitle fonts; the +/// entry then keeps the fewest lines for which each lookalike: +/// - still has at least min(original wrong points, budget + 1) wrong points, so it is still +/// rejected by every pass (and by the exact-size check, which needs one wrong point), and +/// - has a share of wrong points no lower than before, +/// while the entry's own rendered glyphs keep their share of wrong points. A/B on real +/// Blu-ray/DVD images: 43% fewer lines with a lower character error rate and fewer wrong letters. +/// Multi-part (expanded) entries are left as they are. +/// +public static class NOcrDbOptimizer +{ + private const int TwoColorThreshold = 200; + private const int PixelsAreSpace = 10; + private const int MinLineHeight = 25; + private const double PositiveShareMargin = 0.005; + + private sealed record Glyph(string Text, bool[] Ink, int Width, int Height); + + /// + /// Optimizes the single-part entries of in place (call + /// afterwards). Call from a background thread. Returns null, with + /// the database unchanged, when returns true. + /// + public static NOcrDbOptimizeResult? Optimize(NOcrDb db, NOcrDbOptimizerSettings settings, Func? abortRequested = null, Action? progress = null) + { + var fonts = GetInstalledFonts(settings.FontNames); + var glyphs = RenderGlyphs(db, settings, fonts); + var entries = db.OcrCharacters.ToList(); + var newLines = new (List Foreground, List Background)?[entries.Count]; + var done = 0; + var aborted = false; + + Parallel.For(0, entries.Count, (index, state) => + { + if (abortRequested?.Invoke() == true) + { + aborted = true; + state.Stop(); + return; + } + + newLines[index] = Prune(entries[index], glyphs, settings); + progress?.Invoke(Interlocked.Increment(ref done), entries.Count); + }); + + if (aborted) + { + return null; + } + + var before = 0; + var after = 0; + for (var i = 0; i < entries.Count; i++) + { + var oc = entries[i]; + before += oc.LinesForeground.Count + oc.LinesBackground.Count; + if (newLines[i] is { } lines) + { + oc.LinesForeground.Clear(); + oc.LinesForeground.AddRange(lines.Foreground); + oc.LinesBackground.Clear(); + oc.LinesBackground.AddRange(lines.Background); + } + + after += oc.LinesForeground.Count + oc.LinesBackground.Count; + } + + return new NOcrDbOptimizeResult + { + Entries = entries.Count, + LinesBefore = before, + LinesAfter = after, + ValidationGlyphs = glyphs.Count, + FontsUsed = fonts, + }; + } + + private static List GetInstalledFonts(List fontNames) + { + var installed = new HashSet(SKFontManager.Default.FontFamilies, StringComparer.OrdinalIgnoreCase); + var fonts = fontNames.Where(installed.Contains).Distinct(StringComparer.OrdinalIgnoreCase).ToList(); + if (fonts.Count == 0) + { + fonts.Add(SKTypeface.Default.FamilyName); + } + + return fonts; + } + + private static List RenderGlyphs(NOcrDb db, NOcrDbOptimizerSettings settings, List fonts) + { + // Every single-character text in the database is a possible lookalike, not just the + // default training set (e.g. "č" or "♪" taught during OCR). + var texts = new List(); + foreach (var text in NOcrTrainer.DefaultTrainingCharacters.Select(c => c.ToString()) + .Concat(db.OcrCharacters.Select(oc => oc.Text))) + { + if (text.Length == 1 && !char.IsWhiteSpace(text[0]) && !texts.Contains(text)) + { + texts.Add(text); + } + } + + var jobs = (from font in fonts + from size in settings.FontSizes + from style in new[] { (Bold: false, Italic: false), (Bold: true, Italic: false), (Bold: false, Italic: true) } + select (font, size, style)).ToList(); + var perJob = new List[jobs.Count]; + Parallel.For(0, jobs.Count, i => + { + var (font, size, style) = jobs[i]; + var list = new List(); + foreach (var text in texts) + { + var glyph = RenderGlyph(text, font, size, style.Bold, style.Italic); + if (glyph != null) + { + list.Add(glyph); + } + } + + perJob[i] = list; + }); + + return perJob.SelectMany(p => p).ToList(); + } + + private static Glyph? RenderGlyph(string text, string font, float size, bool bold, bool italic) + { + // Same rendering and splitting as NOcrTrainer: "H" sets the cap height, and only + // characters that come out as one connected glyph are used. + using var bitmap = NOcrTrainer.RenderCharacterImage("H " + text, font, size, bold, italic); + if (bitmap == null) + { + return null; + } + + var nikseBitmap = new NikseBitmap2(bitmap); + nikseBitmap.MakeTwoColor(TwoColorThreshold); + nikseBitmap.CropTop(0, new SKColor(0, 0, 0, 0)); + var list = NikseBitmapImageSplitter2.SplitBitmapToLettersNew(nikseBitmap, PixelsAreSpace, false, false, MinLineHeight, false); + if (list.Count != 3 || list[2].NikseBitmap == null) + { + return null; + } + + var glyph = list[2].NikseBitmap!; + var width = glyph.Width; + var height = glyph.Height; + var pixels = glyph.GetPixelData(); + var ink = new bool[width * height]; + for (var y = 0; y < height; y++) + { + for (var x = 0; x < width; x++) + { + ink[y * width + x] = pixels[x * 4 + y * width * 4 + 3] > 150; + } + } + + return new Glyph(text, ink, width, height); + } + + private static (List Foreground, List Background)? Prune(NOcrChar oc, List glyphs, NOcrDbOptimizerSettings settings) + { + var lines = oc.LinesForeground.Select(l => (Line: l, Foreground: true)) + .Concat(oc.LinesBackground.Select(l => (Line: l, Foreground: false))) + .ToList(); + var lineCount = lines.Count; + if (lineCount <= settings.MinLines || oc.Width <= 0 || oc.Height <= 0) + { + return null; + } + + var positives = new List(); + var negatives = new List(); + foreach (var glyph in glyphs) + { + if (glyph.Text == oc.Text) + { + positives.Add(glyph); + } + else if (!AreSameShape(oc.Text, glyph.Text) && IsNear(oc, glyph)) + { + negatives.Add(glyph); + } + } + + var negErrors = new int[lineCount, negatives.Count]; + var negPoints = new int[lineCount, negatives.Count]; + var posErrors = new int[lineCount, positives.Count]; + var posPoints = new int[lineCount, positives.Count]; + for (var i = 0; i < lineCount; i++) + { + for (var j = 0; j < negatives.Count; j++) + { + Count(oc, lines[i].Line, lines[i].Foreground, negatives[j], out negErrors[i, j], out negPoints[i, j]); + } + + for (var j = 0; j < positives.Count; j++) + { + Count(oc, lines[i].Line, lines[i].Foreground, positives[j], out posErrors[i, j], out posPoints[i, j]); + } + } + + var negTotalErrors = new int[negatives.Count]; + var negTotalPoints = new int[negatives.Count]; + var need = new int[negatives.Count]; + for (var j = 0; j < negatives.Count; j++) + { + for (var i = 0; i < lineCount; i++) + { + negTotalErrors[j] += negErrors[i, j]; + negTotalPoints[j] += negPoints[i, j]; + } + + // Mirrors the matcher's budget: scaled up for bigger glyphs, capped by glyph area. + var glyph = negatives[j]; + var scale = glyph.Height / (double)oc.Height; + var budget = Math.Min((int)Math.Round(settings.MaxWrongPixels * Math.Max(1, scale)), Math.Max(8, glyph.Width * glyph.Height / 8)); + need[j] = Math.Min(negTotalErrors[j], budget + 1); + } + + var posTotalErrors = new int[positives.Count]; + var posTotalPoints = new int[positives.Count]; + var posErrorsPerLine = new int[lineCount]; + var negErrorsPerLine = new int[lineCount]; + for (var i = 0; i < lineCount; i++) + { + for (var j = 0; j < positives.Count; j++) + { + posTotalErrors[j] += posErrors[i, j]; + posTotalPoints[j] += posPoints[i, j]; + posErrorsPerLine[i] += posErrors[i, j]; + } + + for (var j = 0; j < negatives.Count; j++) + { + negErrorsPerLine[i] += negErrors[i, j]; + } + } + + var selected = new bool[lineCount]; + var selectedCount = 0; + + // Greedy set multicover: the line that adds the most still-needed wrong points to the + // lookalikes, preferring lines the entry's own glyphs agree with. + var remaining = (int[])need.Clone(); + while (true) + { + var best = -1; + var bestScore = 0.0; + for (var i = 0; i < lineCount; i++) + { + if (selected[i]) + { + continue; + } + + var gain = 0; + for (var j = 0; j < negatives.Count; j++) + { + if (remaining[j] > 0) + { + gain += Math.Min(negErrors[i, j], remaining[j]); + } + } + + var score = gain - 0.1 * posErrorsPerLine[i]; + if (gain > 0 && (best < 0 || score > bestScore)) + { + best = i; + bestScore = score; + } + } + + if (best < 0) + { + break; + } + + selected[best] = true; + selectedCount++; + for (var j = 0; j < negatives.Count; j++) + { + remaining[j] = Math.Max(0, remaining[j] - negErrors[best, j]); + } + } + + // Fill up to the minimum with the most telling lines. + foreach (var i in Enumerable.Range(0, lineCount).OrderByDescending(i => negErrorsPerLine[i] - 10 * posErrorsPerLine[i])) + { + if (selectedCount >= settings.MinLines) + { + break; + } + + if (!selected[i]) + { + selected[i] = true; + selectedCount++; + } + } + + // Shares: a lookalike within the budget competes on its share of wrong points, so it + // must not look better than before; the entry's own glyphs must not look worse. + for (var round = 0; round < lineCount; round++) + { + var added = AddLineForNegativeShare(selected, negErrors, negPoints, negTotalErrors, negTotalPoints, need) ?? + AddLineForPositiveShare(selected, posErrors, posPoints, posTotalErrors, posTotalPoints); + if (added == null) + { + break; + } + + selected[added.Value] = true; + } + + var foreground = new List(); + var background = new List(); + for (var i = 0; i < lineCount; i++) + { + if (selected[i]) + { + (lines[i].Foreground ? foreground : background).Add(lines[i].Line); + } + } + + return (foreground, background); + } + + private static int? AddLineForNegativeShare(bool[] selected, int[,] errors, int[,] points, int[] totalErrors, int[] totalPoints, int[] need) + { + for (var j = 0; j < totalErrors.Length; j++) + { + // Never matched, or rejected by the budget whatever its share. + if (totalErrors[j] == 0 || totalErrors[j] > need[j]) + { + continue; + } + + long e = 0; + long p = 0; + for (var i = 0; i < selected.Length; i++) + { + if (selected[i]) + { + e += errors[i, j]; + p += points[i, j]; + } + } + + if (p > 0 && e * totalPoints[j] >= (long)totalErrors[j] * p) + { + continue; + } + + int? best = null; + var bestShare = -1.0; + for (var i = 0; i < selected.Length; i++) + { + if (!selected[i] && points[i, j] > 0 && errors[i, j] / (double)points[i, j] > bestShare) + { + best = i; + bestShare = errors[i, j] / (double)points[i, j]; + } + } + + if (best != null) + { + return best; + } + } + + return null; + } + + private static int? AddLineForPositiveShare(bool[] selected, int[,] errors, int[,] points, int[] totalErrors, int[] totalPoints) + { + for (var j = 0; j < totalErrors.Length; j++) + { + long e = 0; + long p = 0; + for (var i = 0; i < selected.Length; i++) + { + if (selected[i]) + { + e += errors[i, j]; + p += points[i, j]; + } + } + + var oldShare = totalPoints[j] == 0 ? 0 : totalErrors[j] / (double)totalPoints[j]; + if (p > 0 && e / (double)p <= oldShare + PositiveShareMargin) + { + continue; + } + + int? best = null; + var bestPoints = 0; + for (var i = 0; i < selected.Length; i++) + { + if (!selected[i] && errors[i, j] == 0 && points[i, j] > bestPoints) + { + best = i; + bestPoints = points[i, j]; + } + } + + if (best != null) + { + return best; + } + } + + return null; + } + + /// Case pairs (o/O) share the shape, and I/l/| are the same bar in sans-serif fonts. + private static bool AreSameShape(string a, string b) + { + return a.Equals(b, StringComparison.OrdinalIgnoreCase) || + a is "I" or "l" or "|" && b is "I" or "l" or "|"; + } + + /// The matcher's loosest size and aspect gates, widened. + private static bool IsNear(NOcrChar oc, Glyph glyph) + { + var area = glyph.Width * glyph.Height; + var ocArea = oc.Width * oc.Height; + var smallArea = Math.Min(area, ocArea); + if (smallArea < 150 && Math.Max(area, ocArea) > smallArea * 5) + { + return false; + } + + var aspect = glyph.Height * 100.0 / glyph.Width; + var ocAspect = oc.HeightToWidthPercent; + if (area < 150) + { + return Math.Max(aspect, ocAspect) / Math.Min(aspect, ocAspect) <= 2.0; + } + + return Math.Abs(aspect - ocAspect) * 100.0 / Math.Min(aspect, ocAspect) < 100; + } + + private static void Count(NOcrChar oc, NOcrLine line, bool foreground, Glyph glyph, out int errors, out int points) + { + errors = 0; + points = 0; + foreach (var p in line.ScaledWalkPoints(oc, glyph.Width, glyph.Height)) + { + if ((uint)p.X < (uint)glyph.Width && (uint)p.Y < (uint)glyph.Height) + { + points++; + if (glyph.Ink[p.Y * glyph.Width + p.X] != foreground) + { + errors++; + } + } + } + } +} diff --git a/src/seconv/Helpers/CliSchema.cs b/src/seconv/Helpers/CliSchema.cs index 6425a7ef4d7..fe501bc4145 100644 --- a/src/seconv/Helpers/CliSchema.cs +++ b/src/seconv/Helpers/CliSchema.cs @@ -374,6 +374,7 @@ public static string ToJson() new { name = "dump-settings", json = true, description = "Print a full --settings JSON with libse defaults. Always JSON; redirect to a file." }, new { name = "info", json = true, description = "Print format / encoding / duration / language for one file. Usage: seconv info " }, new { name = "lint", json = true, description = "Validate subtitles; exit 1 if any issues found. Usage: seconv lint ..." }, + new { name = "optimize-nocr", json = true, description = "Remove nOCR database lines that don't tell characters apart from lookalikes (smaller, faster, usually more accurate). In place with a .bak backup unless --output:. Usage: seconv optimize-nocr " }, new { name = "--help-json", json = true, description = "Print this schema." }, new { name = "--version", json = false, description = "Print the seconv version and exit." }, }, diff --git a/src/seconv/Helpers/HelpDisplay.cs b/src/seconv/Helpers/HelpDisplay.cs index 1e0bbc1ac9a..fd3fed0fbc4 100644 --- a/src/seconv/Helpers/HelpDisplay.cs +++ b/src/seconv/Helpers/HelpDisplay.cs @@ -1,4 +1,4 @@ -using Spectre.Console; +using Spectre.Console; namespace SeConv.Helpers; @@ -168,6 +168,7 @@ private static void ShowHelp(IAnsiConsole console) ShowParameter(console, "dump-settings", "Print a full --settings JSON with libse defaults (redirect to a file)"); ShowParameter(console, "info ", "Print format / encoding / duration / language info"); ShowParameter(console, "lint ", "Validate subtitle(s); exit 1 if any issues found"); + ShowParameter(console, "optimize-nocr ", "Remove nOCR lines that don't tell characters apart (in place + .bak, or --output:)"); console.WriteLine(); ShowSection(console, "Examples", null); diff --git a/src/seconv/Program.cs b/src/seconv/Program.cs index f049344ebc8..7c5e97e6609 100644 --- a/src/seconv/Program.cs +++ b/src/seconv/Program.cs @@ -1,4 +1,4 @@ -using Spectre.Console; +using Spectre.Console; using Spectre.Console.Cli; using SeConv.Commands; using SeConv.Core; @@ -30,6 +30,13 @@ static int Main(string[] args) // Encoding code pages and the headless EBU UI helper are wired by the // module initializer in SeConv.Core.Bootstrap. + // Before the convert-oriented rewrites below, which would read the database path as + // a positional format. + if (args.Length > 0 && args[0].TrimStart('/', '-').Equals("optimize-nocr", StringComparison.OrdinalIgnoreCase)) + { + return RunOptimizeNOcrCommand(args.Skip(1).ToArray()); + } + // Handle legacy /convert syntax and convert to modern syntax args = ConvertLegacyArguments(args); @@ -645,6 +652,119 @@ private static int RunLintCommand(string[] args) return hasError || reports.Any(r => r.Issues.Count > 0) ? 1 : 0; } + /// + /// Dispatches seconv optimize-nocr <database.nocr> [--output:<path>] [--json]: + /// removes the lines that don't tell characters apart from lookalikes (see + /// ). Without --output the + /// database is optimized in place and the original kept as <database.nocr>.bak. + /// + private static int RunOptimizeNOcrCommand(string[] args) + { + var json = HasJsonFlag(args); + string? output = null; + var positional = new List(); + for (var i = 0; i < args.Length; i++) + { + var arg = args[i]; + var name = arg.TrimStart('/', '-'); + var isOption = LooksLikeOption(arg) || arg.StartsWith("/output", StringComparison.OrdinalIgnoreCase); + if (isOption && name.StartsWith("output", StringComparison.OrdinalIgnoreCase)) + { + var rest = name.Substring("output".Length); + if (rest.Length > 0 && rest[0] is ':' or '=') + { + output = rest.Substring(1); + } + else if (rest.Length == 0 && i + 1 < args.Length) + { + output = args[++i]; + } + + continue; + } + + if (!LooksLikeOption(arg) && !arg.Equals("/json", StringComparison.OrdinalIgnoreCase)) + { + positional.Add(arg); + } + } + + if (positional.Count != 1 || output == string.Empty) + { + const string usage = "Usage: seconv optimize-nocr [--output:] [--json]"; + if (json) + { + Console.Error.WriteLine($"{{\"error\": {JsonOut.Serialize("'seconv optimize-nocr' requires exactly one .nocr file. " + usage)}}}"); + } + else + { + AnsiConsole.MarkupLine("[red]Error: 'seconv optimize-nocr' requires exactly one .nocr file.[/]"); + AnsiConsole.MarkupLine($"[dim]{Markup.Escape(usage)}[/]"); + } + + return 1; + } + + var input = positional[0]; + try + { + if (!File.Exists(input)) + { + throw new FileNotFoundException($"nOCR database not found: {input}", input); + } + + var stopwatch = System.Diagnostics.Stopwatch.StartNew(); + var db = new Nikse.SubtitleEdit.UiLogic.Ocr.NOcrDb(input); + var result = Nikse.SubtitleEdit.UiLogic.Ocr.NOcrDbOptimizer.Optimize(db, new Nikse.SubtitleEdit.UiLogic.Ocr.NOcrDbOptimizerSettings())!; + + string? backup = null; + if (output == null) + { + backup = input + ".bak"; + File.Copy(input, backup, true); + output = input; + } + + new Nikse.SubtitleEdit.UiLogic.Ocr.NOcrDb(db, output).Save(); + + if (json) + { + JsonOut.Write(new + { + success = true, + input, + output, + backup, + entries = result.Entries, + linesBefore = result.LinesBefore, + linesAfter = result.LinesAfter, + fonts = result.FontsUsed, + elapsedMs = stopwatch.ElapsedMilliseconds, + }); + } + else + { + AnsiConsole.MarkupLineInterpolated($"[green]Optimized {input}[/]: {result.LinesBefore:#,##0} -> {result.LinesAfter:#,##0} lines in {result.Entries:#,##0} characters ({stopwatch.Elapsed.TotalSeconds:0.0} s)"); + AnsiConsole.MarkupLineInterpolated($"[dim]Saved to {output}{(backup != null ? $", original kept as {backup}" : string.Empty)}. Fonts: {string.Join(", ", result.FontsUsed)}[/]"); + } + + return 0; + } + catch (Exception ex) + { + if (json) + { + Console.Error.WriteLine($"{{\"error\": {System.Text.Json.JsonSerializer.Serialize(ex.Message)}}}"); + } + else + { + AnsiConsole.MarkupLineInterpolated($"[red]Error: {ex.Message}[/]"); + } + + return 1; + } + } + private static bool HasJsonFlag(string[] args) => args.Any(a => a.Equals("--json", StringComparison.OrdinalIgnoreCase) || diff --git a/src/ui/Features/Ocr/NOcr/NOcrSettingsViewModel.cs b/src/ui/Features/Ocr/NOcr/NOcrSettingsViewModel.cs index f4790d4364a..3731548289d 100644 --- a/src/ui/Features/Ocr/NOcr/NOcrSettingsViewModel.cs +++ b/src/ui/Features/Ocr/NOcr/NOcrSettingsViewModel.cs @@ -22,6 +22,7 @@ public partial class NOcrSettingsViewModel : ObservableObject public bool NewPressed { get; set; } public bool RenamePressed { get; set; } public bool TrainPressed { get; set; } + public bool OptimizePressed { get; set; } private NOcrDb _nOcrDb; private readonly IWindowService _windowService; @@ -90,6 +91,13 @@ private void Train() Close(); } + [RelayCommand] + private void Optimize() + { + OptimizePressed = true; + Close(); + } + [RelayCommand] private void Cancel() { diff --git a/src/ui/Features/Ocr/NOcr/NOcrSettingsWindow.cs b/src/ui/Features/Ocr/NOcr/NOcrSettingsWindow.cs index d8a3b6ee09e..29245593ac6 100644 --- a/src/ui/Features/Ocr/NOcr/NOcrSettingsWindow.cs +++ b/src/ui/Features/Ocr/NOcr/NOcrSettingsWindow.cs @@ -48,14 +48,20 @@ public NOcrSettingsWindow(NOcrSettingsViewModel vm) Content = grid; - // Semi-hidden power-user feature: right-click anywhere in the dialog to train a new - // nOCR database from installed fonts (port of SE4's "Train nOCR"). + // Semi-hidden power-user features: right-click anywhere in the dialog to train a new + // nOCR database from installed fonts (port of SE4's "Train nOCR"), or to optimize the + // current one. var menuFlyout = new MenuFlyout(); menuFlyout.Items.Add(new MenuItem { Header = Se.Language.Ocr.TrainNOcrDatabase, Command = vm.TrainCommand, }); + menuFlyout.Items.Add(new MenuItem + { + Header = Se.Language.Ocr.OptimizeNOcrDatabase, + Command = vm.OptimizeCommand, + }); grid.ContextFlyout = menuFlyout; UiUtil.AttachMacContextFlyoutHandler(this, grid); diff --git a/src/ui/Features/Ocr/OcrViewModel.cs b/src/ui/Features/Ocr/OcrViewModel.cs index 14852706bd4..1ea2b22d4a2 100644 --- a/src/ui/Features/Ocr/OcrViewModel.cs +++ b/src/ui/Features/Ocr/OcrViewModel.cs @@ -1801,6 +1801,12 @@ await MessageBox.Show( return; } + if (result.OptimizePressed) + { + await OptimizeNOcrDb(); + return; + } + if (result.RenamePressed) { var newResult = await _windowService.ShowDialogAsync(Window!, @@ -4290,6 +4296,72 @@ private List OcrFixLineAndSetText(int i, OcrSubtitleItem item) } + /// + /// Removes the lines of the current nOCR database that don't tell characters apart from + /// lookalikes (see ). Works on a fresh copy loaded from disk and + /// swaps it in when done, so OCR never sees half-optimized entries. + /// + private async Task OptimizeNOcrDb() + { + if (Window == null || IsOcrRunning || _nOcrDb == null || !File.Exists(_nOcrDb.FileName)) + { + return; + } + + var fileName = _nOcrDb.FileName; + var name = Path.GetFileNameWithoutExtension(fileName); + var backupFileName = fileName + ".bak"; + var title = Se.Language.Ocr.OptimizeNOcrDatabase.TrimEnd('.'); + var answer = await MessageBox.Show( + Window, + title, + string.Format(Se.Language.Ocr.OptimizeNOcrDatabaseQuestionXY, name, Path.GetFileName(backupFileName)), + MessageBoxButtons.YesNoCancel, + MessageBoxIcon.Question); + if (answer != MessageBoxResult.Yes) + { + return; + } + + try + { + var (db, result) = await Task.Run(() => + { + File.Copy(fileName, backupFileName, true); + var optimizedDb = new NOcrDb(fileName); + var optimizeResult = NOcrDbOptimizer.Optimize(optimizedDb, new NOcrDbOptimizerSettings(), null, (done, total) => + { + if (done % 50 == 0 || done == total) + { + Dispatcher.UIThread.Post(() => ProgressText = string.Format(Se.Language.Ocr.OptimizingNOcrDatabaseXY, done, total)); + } + }); + optimizedDb.Save(); + return (optimizedDb, optimizeResult!); + }); + + _nOcrDb = db; + ProgressText = string.Empty; + await MessageBox.Show( + Window, + title, + string.Format(Se.Language.Ocr.NOcrDatabaseOptimizedXYZ, name, result.LinesBefore, result.LinesAfter), + MessageBoxButtons.OK, + MessageBoxIcon.Information); + } + catch (Exception exception) + { + Se.LogError(exception, $"Failed to optimize nOCR database '{fileName}'"); + ProgressText = string.Empty; + await MessageBox.Show( + Window, + title, + exception.Message, + MessageBoxButtons.OK, + MessageBoxIcon.Error); + } + } + private bool InitNOcrDb() { var fileName = GetNOcrLanguageFileName(); diff --git a/src/ui/Logic/Config/Language/Ocr/LanguageOcr.cs b/src/ui/Logic/Config/Language/Ocr/LanguageOcr.cs index 1661857e6f3..7e05a4af0b5 100644 --- a/src/ui/Logic/Config/Language/Ocr/LanguageOcr.cs +++ b/src/ui/Logic/Config/Language/Ocr/LanguageOcr.cs @@ -146,6 +146,10 @@ public class LanguageOcr public string CrispEmbedSettingsTitle { get; set; } public string CrispEmbedDescription { get; set; } public string TrainNOcrDatabase { get; set; } + public string OptimizeNOcrDatabase { get; set; } + public string OptimizeNOcrDatabaseQuestionXY { get; set; } + public string OptimizingNOcrDatabaseXY { get; set; } + public string NOcrDatabaseOptimizedXYZ { get; set; } public string StartTraining { get; set; } public string AbortTraining { get; set; } public string TrainingOptions { get; set; } @@ -322,6 +326,12 @@ public LanguageOcr() CrispEmbedDescription = "Local OCR engine with multiple model backends - download the engine and the models here."; TrainNOcrDatabase = "Train nOCR database..."; + OptimizeNOcrDatabase = "Optimize nOCR database..."; + OptimizeNOcrDatabaseQuestionXY = "Remove the lines that don't tell characters apart from similar characters in nOCR database \"{0}\"?" + Environment.NewLine + Environment.NewLine + + "This makes the database smaller and OCR faster, usually with fewer errors. Takes up to a minute." + Environment.NewLine + Environment.NewLine + + "A backup is saved as \"{1}\"."; + OptimizingNOcrDatabaseXY = "Optimizing nOCR database... {0}/{1}"; + NOcrDatabaseOptimizedXYZ = "nOCR database \"{0}\" optimized: {1:#,##0} -> {2:#,##0} lines."; StartTraining = "Start training"; AbortTraining = "Abort training"; TrainingOptions = "Training options"; diff --git a/tests/libuilogic/Ocr/NOcrDbOptimizerTests.cs b/tests/libuilogic/Ocr/NOcrDbOptimizerTests.cs new file mode 100644 index 00000000000..e9e24a70887 --- /dev/null +++ b/tests/libuilogic/Ocr/NOcrDbOptimizerTests.cs @@ -0,0 +1,142 @@ +using Nikse.SubtitleEdit.UiLogic.Ocr; +using SkiaSharp; +using System.Text; + +namespace LibUiLogicTests.Ocr; + +/// +/// drops lines that don't tell an entry apart from its lookalikes; +/// what it keeps must still read the trained text and still reject the other characters. +/// +public class NOcrDbOptimizerTests +{ + private const float FontSize = 50f; + private const string Characters = "HeloWrd123ilj!"; + + private static string TestFontName => SKTypeface.Default.FamilyName; + + private static NOcrDb Train() + { + var db = new NOcrDb(Path.Combine(Path.GetTempPath(), Path.GetRandomFileName() + ".nocr")); + var settings = new NOcrTrainerSettings + { + FontNames = { TestFontName }, + FontSize = FontSize, + CharactersToTrain = Characters, + NumberOfLineSegments = 100, + }; + Assert.True(new NOcrTrainer().Train(settings, db) > 0); + return db; + } + + private static NOcrDbOptimizerSettings Settings(int minLines) => new() + { + FontNames = { TestFontName }, + FontSizes = { FontSize }, + MinLines = minLines, + }; + + private static string Recognize(NOcrDb db, string text) + { + using var bmp = NOcrTrainer.RenderCharacterImage(text, TestFontName, FontSize, false, false); + Assert.NotNull(bmp); + var parent = new NikseBitmap2(bmp!); + parent.MakeTwoColor(200); + parent.CropTop(0, new SKColor(0, 0, 0, 0)); + var letters = NikseBitmapImageSplitter2.SplitBitmapToLettersNew(parent, 12, false, false, 25, false); + var sb = new StringBuilder(); + for (var i = 0; i < letters.Count; i++) + { + var item = letters[i]; + if (item.NikseBitmap == null) + { + sb.Append(item.SpecialCharacter); + continue; + } + + var match = db.GetMatch(parent, letters, item, item.Top, true, 25); + if (match is { ExpandCount: > 0 }) + { + i += match.ExpandCount - 1; + } + + sb.Append(match?.Text ?? "*"); + } + + return sb.ToString().Trim(); + } + + private static int LineCount(NOcrChar oc) => oc.LinesForeground.Count + oc.LinesBackground.Count; + + [Fact] + public void RemovesLinesAndStillReadsTrainedText() + { + var db = Train(); + var before = db.OcrCharacters.ToDictionary(oc => oc, LineCount); + Assert.Equal("Hello World 123", Recognize(db, "Hello World 123")); + + var result = NOcrDbOptimizer.Optimize(db, Settings(minLines: 10)); + + Assert.NotNull(result); + Assert.True(result!.LinesAfter < result.LinesBefore, $"{result.LinesBefore} -> {result.LinesAfter}"); + Assert.Equal(before.Values.Sum(), result.LinesBefore); + foreach (var oc in db.OcrCharacters) + { + Assert.InRange(LineCount(oc), Math.Min(before[oc], 10), before[oc]); + } + + Assert.Equal("Hello World 123", Recognize(db, "Hello World 123")); + Assert.Equal("ill", Recognize(db, "ill")); + } + + [Fact] + public void LookalikesKeepAtLeastOneWrongPoint() + { + var db = Train(); + + NOcrDbOptimizer.Optimize(db, Settings(minLines: 1)); + + // The exact-size check accepts the first entry with zero wrong points, so every + // other trained glyph must still have at least one against each entry. + foreach (var text in new[] { "i", "l", "j", "1", "!" }) + { + using var bmp = NOcrTrainer.RenderCharacterImage("H " + text, TestFontName, FontSize, false, false); + var nb = new NikseBitmap2(bmp!); + nb.MakeTwoColor(200); + nb.CropTop(0, new SKColor(0, 0, 0, 0)); + var glyph = NikseBitmapImageSplitter2.SplitBitmapToLettersNew(nb, 10, false, false, 25, false)[2].NikseBitmap!; + foreach (var oc in db.OcrCharacters.Where(oc => oc.Text != text && !(oc.Text is "I" or "l" && text is "I" or "l"))) + { + Assert.False(NOcrDb.IsMatch(glyph, oc, 0), $"'{oc.Text}' matches '{text}' with no wrong points"); + } + } + } + + [Fact] + public void EntriesAtOrBelowMinLinesAreUnchanged() + { + var db = Train(); + var max = db.OcrCharacters.Max(LineCount); + var before = db.OcrCharacters.Select(oc => oc.LinesForeground.Concat(oc.LinesBackground).ToList()).ToList(); + + var result = NOcrDbOptimizer.Optimize(db, Settings(minLines: max)); + + Assert.Equal(result!.LinesBefore, result.LinesAfter); + for (var i = 0; i < before.Count; i++) + { + Assert.Equal(before[i], db.OcrCharacters[i].LinesForeground.Concat(db.OcrCharacters[i].LinesBackground)); + } + } + + [Fact] + public void AbortLeavesDatabaseUnchanged() + { + var db = Train(); + var before = db.OcrCharacters.Sum(LineCount); + + var result = NOcrDbOptimizer.Optimize(db, Settings(minLines: 10), abortRequested: () => true); + + Assert.Null(result); + Assert.Equal(before, db.OcrCharacters.Sum(LineCount)); + } +} From 553cbac995111132a7a5b0888f014f2ff158dab7 Mon Sep 17 00:00:00 2001 From: niksedk Date: Sun, 11 Oct 2026 09:04:50 +0200 Subject: [PATCH 057/306] Stop ListBox scrollbars from overlaying item content (#15920) The Fluent ListBox template binds its ScrollViewer's AllowAutoHide to the attached property on the ListBox, which beats the app-wide ScrollViewer style, so list scrollbars stayed in overlay mode and covered right-aligned content such as the command palette shortcuts. Set the attached property on ListBox too, like the TextBox fix for #15033. Co-Authored-By: Claude Opus 5.5 --- src/ui/Logic/UiTheme.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/ui/Logic/UiTheme.cs b/src/ui/Logic/UiTheme.cs index b78ea6b1011..4856c1a21cb 100644 --- a/src/ui/Logic/UiTheme.cs +++ b/src/ui/Logic/UiTheme.cs @@ -618,6 +618,12 @@ public static void ApplyScrollBarStyle() { Setters = { new Setter(ScrollViewer.AllowAutoHideProperty, allowAutoHide) } }, + // Same template binding in the ListBox template - without this the list's scrollbar + // overlays right-aligned item content like the command palette shortcuts (#15920). + new Style(x => x.Is()) + { + Setters = { new Setter(ScrollViewer.AllowAutoHideProperty, allowAutoHide) } + }, }; From 17ec674fe691e63c93e42b9f1523bb6e2dd93b80 Mon Sep 17 00:00:00 2001 From: bilimiyorum <131397022+bilimiyorum@users.noreply.github.com> Date: Sun, 11 Oct 2026 10:16:31 +0300 Subject: [PATCH 058/306] Update Turkish.json Update the Turkish localization file. --- src/ui/Assets/Languages/Turkish.json | 426 ++++++++++++++++++++++++++- 1 file changed, 411 insertions(+), 15 deletions(-) diff --git a/src/ui/Assets/Languages/Turkish.json b/src/ui/Assets/Languages/Turkish.json index 3f96ce7d23b..be02e9a1bc5 100644 --- a/src/ui/Assets/Languages/Turkish.json +++ b/src/ui/Assets/Languages/Turkish.json @@ -1,7 +1,7 @@ { "title": "Subtitle Edit", - "version": "v5.3.0-beta19", - "translatedBy": "Utku Gogus; Son güncelleme: Ahmet Murat Özhan (02.10.2026)", + "version": "v5.3.0-rc2", + "translatedBy": "Utku Gogus; Son güncelleme: Ahmet Murat Özhan (11.10.2026)", "cultureName": "tr-TR", "general": { "abort": "İptal", @@ -13,6 +13,18 @@ "actorPickerKeysHint": "Aktörü belirlemek için bir sayıya veya Enter tuşuna basın. Alt+Yukarı/Aşağı sırayı değiştirir, Delete aktörü kaldırır.", "actorPickerNewActorX": "Yeni aktör \"{0}\"", "actorPickerLinesSelectedX": "Seçilen satır sayısı: {0}", + "setStyleDotDotDot": "Biçem ayarla...", + "stylePickerTitle": "Biçem ayarla", + "stylePickerFilterHint": "Filtrelemek için yazın veya yeni bir biçem adı girin", + "stylePickerKeysHint": "Seçili tüm satırlara biçemi uygulamak için bir sayıya (1-9, 0) basın.", + "stylePickerNewStyleX": "Yeni biçem \"{0}\"", + "stylePickerNewStyleInfo": "Bu biçemi varsayılan ayarlarla eklemek ve seçili satırlara uygulamak için Enter tuşuna basın. Yazı tipini, renkleri ve konumu daha sonra değiştirmek için \"Biçemleri yönet...\" seçeneğini kullanın.", + "stylePickerCurrentStyleX": "Mevcut biçem: {0}", + "stylePickerLinesUsingStyleX": "Bu biçemi kullanan satırlar: {0}", + "stylePickerMarginsXYZ": "Sol {0}, sağ {1}, dikey {2}", + "stylePickerSetStyle": "Biçemi _ayarla", + "stylePickerManageStyles": "Biçemleri yönet...", + "stylePickerManageStylesHint": "Yazı tiplerini, renkleri, konumu ve daha fazlasını düzenlemek için bu pencereyi kapatın ve biçem yöneticisini açın", "add": "Ekle", "addDotDotDot": "Ekle...", "addToNamesListCaseSensitive": "İsim listesine ekle (büyük/küçük harf duyarlı)", @@ -431,6 +443,7 @@ "percent": "Yüzde", "pickLayer": "Katman ayarla", "pickOllamaModel": "Ollama modelini seç", + "pickModel": "Model seçin", "llamaCppUseRemoteServer": "Harici sunucu kullan (URL)", "llamaCppEngineSettings": "llama.cpp motoru ayarları", "llamaCppEngineSettingsSubtitle": "Yerel llama.cpp sunucu sürümü", @@ -507,6 +520,7 @@ "reset": "Sıfırla", "resizePanels": "Panelleri yeniden boyutlandır", "resolution": "Çözünürlük", + "retry": "Yeniden dene", "reverseRightToLeftStartEnd": "RTL başlangıç/bitişini tersine çevir", "right": "Sağ", "rightToLeft": "Sağdan sola", @@ -568,6 +582,8 @@ "shadowColor": "Gölge rengi", "shadowWidth": "Gölge genişliği", "shiftLineBy": "Satırı şu kadar kaydır", + "shotIn": "Sahne girişi", + "shotOut": "Sahne çıkışı", "shortcut": "Kısayol", "shortcuts": "Kısayollar", "show": "Göster", @@ -579,9 +595,13 @@ "showStartColumn": "\"Başlangıç\" sütununu göster", "showHideColumn": "\"Gizle\" sütununu göster", "showHistory": "Geçmişi göster", + "clearHistory": "Geçmişi temizle", + "removeFromHistory": "Geçmişten kaldır", "showLayerColumn": "\"Katman\" sütununu göster", "showPreview": "Ön izlemeyi göster", "showShotChangesList": "Sahne değişimleri listesini göster", + "showShotInColumn": "\"Sahne girişi\" sütununu göster", + "showShotOutColumn": "\"Sahne çıkışı\" sütununu göster", "showStyleColumn": "\"Stil\" sütununu göster", "showTeletext": "Liste görünümünde teletext sütununu göster", "showTimeCodes": "Zaman kodlarını göster", @@ -638,12 +658,14 @@ "styleExaggeration": "Stil abartısı", "styles": "Stiller", "subtitleFile": "Alt yazı dosyası", + "subtitleFileChangedOutsideX": "\"{0}\" dosyası Subtitle Edit haricinde değiştirildi.\n\nYeniden yüklensin mi? Değişiklikleriniz kaybolacaktır.", "subtitleFileName": "Alt yazı dosya adı", "subtitleFileSaved": "Alt yazı dosyası kaydedildi", "subtitleFileSavedToX": "Alt yazı dosyası {0} konumuna kaydedildi", "subtitleFiles": "Alt yazı dosyaları", "subtitleFormats": "Alt yazı formatları", "subtitleLoadedX": "Alt yazı yüklendi: {0}", + "subtitleReloadedChangedOutsideX": "Alt yazı yeniden yüklendi (Subtitle Edit haricinde değiştirildi): {0}", "suffix": "Son ek", "suggestions": "Öneriler", "sync": "Senkronizasyon", @@ -727,10 +749,23 @@ "videoCustom3ForwardX": "Video, özel milisaniye ({0:#,###,##0}) ileri, 3", "videoCustom4BackX": "Video, özel milisaniye ({0:#,###,##0}) geri, 4", "videoCustom4ForwardX": "Video, özel milisaniye ({0:#,###,##0}) ileri, 4", + "videoCustom1BackAndPauseX": "Video, özel milisaniye ({0:#,###,##0}) geri ve duraklat, 1", + "videoCustom1ForwardAndPauseX": "Video, özel milisaniye ({0:#,###,##0}) ileri ve duraklat, 1", + "videoCustom2BackAndPauseX": "Video, özel milisaniye ({0:#,###,##0}) geri ve duraklat, 2", + "videoCustom2ForwardAndPauseX": "Video, özel milisaniye ({0:#,###,##0}) ileri ve duraklat, 2", + "videoCustom3BackAndPauseX": "Video, özel milisaniye ({0:#,###,##0}) geri ve duraklat, 3", + "videoCustom3ForwardAndPauseX": "Video, özel milisaniye ({0:#,###,##0}) ileri ve duraklat, 3", + "videoCustom4BackAndPauseX": "Video, özel milisaniye ({0:#,###,##0}) geri ve duraklat, 4", + "videoCustom4ForwardAndPauseX": "Video, özel milisaniye ({0:#,###,##0}) ileri ve duraklat, 4", "videoExtension": "Video dosyası uzantısı", "videoFile": "Video dosyası", "videoFileGenerated": "Video dosyası oluşturuldu", "videoFileGeneratedX": "Video dosyası oluşturuldu: \"{0}\"", + "videoFilesGenerated": "Video dosyaları oluşturuldu", + "xVideosGenerated": "{0} video oluşturuldu", + "xOfYVideosGenerated": "{0} / {1} video oluşturuldu", + "savedInX": "{0} konumuna kaydedildi", + "savedInXFolders": "{0} klasöre kaydedildi", "videoFiles": "Video dosyaları", "videoAndAudioFiles": "Video ve ses dosyaları", "videoOffset": "Video farkı", @@ -857,6 +892,7 @@ "multipleReplace": "Ç_oklu değiştir...", "fillSelectedLinesWithClipboard": "Seçili satırları panodaki metinle doldur", "goToLineNumber": "_Satır numarasına git...", + "commandPalette": "Komut _paleti...", "rightToLeftMode": "S_ağdan sola modu", "modifySelectionDotDotDot": "Seçimi _değiştir...", "tools": "_Araçlar", @@ -1049,6 +1085,9 @@ "charactersPerSecond": "Kar/saniye: {0}", "chooseColumn": "Sütun seç", "columnPaste": "Sütun yapıştır", + "commandPaletteTitle": "Komut paleti", + "commandPaletteSearchPlaceholder": "Komut aramak için yazın...", + "commandPaletteNoCommandsFound": "Eşleşen komut bulunamadı", "createdEmptyTranslation": "Mevcut alt yazıdan boş çeviri oluşturuldu", "deleteText": "Metni sil", "deleteTextAndShiftCellsUp": "Metni sil ve hücreleri yukarı kaydır", @@ -1134,12 +1173,14 @@ "trimmedXLines": "{0} alt yazı satırı kırpıldı", "pastedXLinesOverSelectedLines": "Seçilen satırların üzerine {0} satır yapıştırıldı", "pastedXOfYLinesOverSelectedLines": "Seçilen satırların üzerine {0} / {1} satır yapıştırıldı - geri kalanı seçime sığmadı", + "insertedXLinesFromClipboard": "Pano içerisinden {0} satır eklendi", "openOriginalDifferentNumberOfSubtitlesXY": "Orijinal alt yazı dosyası mevcut alt yazı dosyasıyla aynı sayıda alt yazıya sahip değil.\n\n• Orijinal alt yazılar: {0}\n• Mevcut alt yazılar: {1}", "originalTextReadOnly": "Orijinal metin (salt okunur)", "originalTextEditMode": "Orijinal metin (düzenleme modu)", "originalIsReadOnlyNotSaved": "Orijinal alt yazı salt okunur bir referanstır ve kaydedilmedi", "originalIsEmptyNotSaved": "Orijinal alt yazıda hiç metin yok ve kaydedilmedi", "originalIsReadOnlyReference": "Orijinal alt yazı salt okunur bir referans olarak açık", + "closeTranslationQuestionXY": "\"{0}\" çevirisi kapatılsın mı?\n\nOrijinal alt yazı \"{1}\", düzenlediğiniz alt yazı haline gelir. Çeviri dosyasının kendisi değiştirilmez.", "allowEditOfOriginalSubtitle": "Orijinal alt yazının düzenlenmesine izin ver", "showAllOriginalLinesX": "Tüm orijinal satırları göster (burada {0} tanesinin eşleşmesi yok)", "showAllOriginalLinesHint": "Çeviride neyin eksik olduğunu görebilmeniz için eşleşmesi olmayan satırlar ekstra satır olarak gösterilir.", @@ -1174,6 +1215,7 @@ "fileExportedInFormatXToY": "Dosya {0} formatında {1} konumuna aktarıldı", "fileExportedInFormatXToFileY": "Dosya \"{0}\" formatında \"{1}\" dosyasına aktarıldı", "fixedXLines": "{0} satır düzeltildi", + "toolXChangedYLines": "{0}: {1} satır değiştirildi", "transcriptionCompletedWithXLines": "Transkripsiyon {0} satır ile tamamlandı", "openSubtitleFileX": "\"{0}\" alt yazı dosyası açılsın mı?", "replacedXOccurrences": "{0} eşleşme değiştirildi", @@ -1544,13 +1586,61 @@ "ignoreFormatting": "Biçimlendirmeyi yok say", "ignoreFormattingHint": "Yalnızca veya {\\an8} gibi biçimlendirme etiketleri bakımından farklılık gösteren satırlar farklı kabul edilmez", "showOnlyDifferencesInText": "Sadece metindeki farklar", + "ignoreNumbering": "Numaralandırmayı yok say", + "ignoreNumberingHint": "Yalnızca numarası farklı olan satırlar farklı kabul edilmez", + "ignoreTimeDifferenceMs": "Zaman toleransı (ms)", + "ignoreTimeDifferenceMsHint": "Başlangıç ve bitiş zamanları bu kadar milisaniye veya daha az farklı olan satırlar farklı kabul edilmez", + "compareDifferences": "Farklar", + "compareTextDifferences": "Metin farkları", + "compareCurrent": "Mevcut", + "compareReference": "Referans", + "compareEditable": "Düzenlenebilir", + "compareEditableHint": "Burada yapılan değişiklikler Uygula seçeneğine tıklandığında alt yazıya yansıtılır", + "compareReadOnly": "Salt okunur", + "compareTakeFromReference": "Metni ve zamanlamayı referanstan al", + "compareInsertFromReference": "Bu satırı mevcut alt yazıya ekle", + "compareDeleteFromCurrent": "Bu satırı mevcut alt yazıdan sil", + "compareTakeText": "Metni al", + "compareTakeTiming": "Zamanlamayı al", + "compareEditLine": "Satırı düzenle", + "compareEditHint": "Kaydetmek için Ctrl+Enter, iptal etmek için Esc", + "compareEdited": "Düzenlendi", + "compareOnePendingChange": "1 bekleyen değişiklik", + "compareXPendingChanges": "{0} bekleyen değişiklik", + "compareDiscardXChanges": "Karşılaştır aracında yapılan {0} değişiklik gözden çıkarılsın mı?", + "compareXChangesApplied": "Karşılaştır: {0} değişiklik uygulandı", + "compareChangeTextX": "#{0} referanstan metin", + "compareChangeTimingX": "#{0} referanstan zamanlama", + "compareChangeTextAndTimingX": "#{0} referanstan metin ve zamanlama", + "compareChangeEditedX": "#{0} düzenlendi", + "compareChangeInsertedX": "#{0} eklendi", + "compareChangeDeletedX": "#{0} silindi", + "compareSyncPoint": "Eşitleme noktası", + "compareSyncPointHint": "Eşitleme noktası - bu iki satır her zaman bir çift olarak gösterilir; üstteki ve alttaki satırlar kendi içlerinde hizalanır", + "compareSyncPickCurrent": "Eşitleme noktası: bu mevcut satırı kullan", + "compareSyncPickReference": "Eşitleme noktası: bu referans satırı kullan", + "compareSyncRemove": "Eşitleme noktasını kaldır", + "compareClearXSyncPoints": "Eşitleme noktalarını temizle ({0})", + "compareSyncCurrentPickedX": "Mevcut #{0} eşitleme noktası olarak seçildi - eşleşen referans satırı seçin ve Eşitle seçeneğine tıklayın", + "compareSyncReferencePickedX": "Referans #{0} eşitleme noktası olarak seçildi - eşleşen mevcut satırı seçin ve Eşitle seçeneğine tıklayın", + "compareSyncWithCurrentX": "Mevcut #{0} ile eşitle", + "compareSyncWithReferenceX": "Referans #{0} ile eşitle", + "compareSyncApply": "Eşitle", + "compareSyncPointSetXY": "Eşitleme noktası ayarlandı: mevcut #{0} ve referans #{1} artık bir çift olarak gösteriliyor", + "compareSyncAlreadyPairedXY": "Mevcut #{0} ve referans #{1} zaten aynı başlangıçla bir çift olarak gösteriliyor - eşitlenecek bir şey yok", + "compareSyncPointShiftedXYZW": "Eşitleme noktası ayarlandı: mevcut #{0}-#{1}, referans #{3} ile başlayacak şekilde {2} kaydırıldı", + "compareChangeSyncShiftXYZ": "#{0}-#{1} {2} kaydırıldı (eşitleme noktası)", + "compareTimingDeltaHint": "Referansın mevcut satırdan ne kadar sonra (+) veya daha önce (−) başlayıp bittiğini gösterir", "loadXFromFile": "\"{0}\" dosyasından yükle", + "compareLoadSavedFile": "Kaydedilen dosyayı yükle", "saveCompareHtmlTitle": "Karşılaştırma HTML dosyasını kaydet", "pickMatroskaTrackX": "Matroska izi seç - {0}", "pickTransportStreamTrackX": "Taşıma akışı izini seç - {0}", "chapters": "Bölümler", "dvdVobFilesMissingX": "VOB dosyaları eksik (%{0} bulundu)", "pickMp4TrackX": "MP4 izi seç - {0}", + "pickMxfTrackX": "MXF izini seç - {0}", + "pickMpegTrackX": "MPEG izini seç - {0}", "rosettaProperties": "Timed Text Rosetta IMSC özellikleri", "rosettaFontSize": "Yazı tipi boyutu (satır yüksekliği)", "propertyTimeBase": "Zaman tabanı", @@ -1625,7 +1715,8 @@ "findWhat": "Aranan", "descriptionOptional": "Açıklama (isteğe bağlı)", "invalidRegularExpressionX": "Geçersiz düzenli ifade: {0}", - "regularExpressionTooSlowX": "Regular expression gave up after {0} seconds - the rule is skipped, try a simpler pattern" + "regularExpressionTooSlowX": "Regular expression gave up after {0} seconds - the rule is skipped, try a simpler pattern", + "moveToCategory": "Kategoriye taşı" }, "find": { "searchTextWatermark": "Aranacak metin...", @@ -1705,7 +1796,22 @@ "mismatchWarning": "Orijinaline benzemiyor - farklı bir satıra ait olabilir", "engineError": "Yapay zekâ motoruna ulaşılamadı: {0}", "playCurrentLineHint": "Seçili satırı video oynatıcıda oynatır ve sonunda duraklatır (ayrıca bir satıra çift tıklayın)", - "editAfterHint": "Uygulamadan önce düzeltmeyi düzenlemek için seçili satırın metnine tıklayın (Enter kaydeder, Escape iptal eder)" + "editAfterHint": "Uygulamadan önce düzeltmeyi düzenlemek için seçili satırın metnine tıklayın (Enter kaydeder, Escape iptal eder)", + "template": "Şablon", + "templatePlaceholder": "Komut isteminin yerini alacak bir şablon seçin", + "templateStandard": "Standart düzelti", + "templateSpeechToText": "Sesten metne dönüştürme temizliği", + "templateMachineTranslation": "Makine çevirisi düzeltme", + "templateSdh": "SDH / işitme engelli", + "referenceContext": "Referans bağlamı", + "referenceContextInfo": "İsteğe bağlı. İsimler, karakterler, terimler sözlüğü, özet veya biçem notları - model bunu isimler ve terimler için yetkili kaynak kabul eder. Her toplu işlemle birlikte gönderilir.", + "referenceContextPlaceholder": "İsimler: Kaito, Mei Teyze, Dr. Hollis\nTerimler: Shinkansen, ışınlanma çekirdeği\nÖzet: Kaito, kayıp babasını bulmak için Tokyo'ya seyahat eder.", + "generateContext": "Yapay zeka ile oluştur", + "generateContextHint": "Seçilen yapay zeka motorunun alt yazıyı okuyarak isimleri, terimleri ve bir özeti taslak haline getirmesine izin verin - kullanmadan önce sonucu kontrol edin", + "generatingContextPartXOfY": "Bölüm {0} / {1} okunuyor...", + "generatingContextSynopsis": "Özet yazılıyor...", + "replaceContextQuestion": "Mevcut referans bağlamının yeri değiştirilsin mi?", + "noContextGenerated": "Yapay zeka motoru kullanılabilir bir referans bilgisi döndürmedi." }, "aiAssistant": { "title": "Yapay zekâ asistanı", @@ -1751,6 +1857,7 @@ "removeLineBreaksPixelWidth": "Tek satıra sığabilecek alt yazıları birleştir (piksel genişliği)", "fixUppercaseIInsideLowercaseWords": "Küçük harfli kelimelerin içindeki büyük 'I' harflerini düzelt (OCR hatası)", "fixDoubleApostrophes": "Çift kesme işaretlerini ('') tek tırnağa (\") dönüştür", + "fixMisreadQuotes": "Çift tırnak olarak yanlış okunan kesme işaretlerini düzelt (OCR, İngilizce)", "addPeriods": "Sonraki satırın büyük harfle başladığı satırların sonuna nokta ekle", "startWithUppercaseLetterAfterParagraph": "Paragraftan sonra büyük harfle başla", "startWithUppercaseLetterAfterPeriodInsideParagraph": "Paragraf içindeki noktadan sonra büyük harfle başla", @@ -1773,6 +1880,7 @@ "fixCommasExample": ",, -> ,", "removeLineBreaksExample": "Merhaba
    dünya! -> Merhaba dünya!", "fixDoubleApostrophesExample": "''Çift tekli tırnak var'' -> \"Tek bir çift tırnak var\"", + "fixMisreadQuotesExample": "\"Hello' -> \"Hello\"", "addPeriodsExample": "Merhaba dünya -> Merhaba dünya.", "startWithUppercaseLetterAfterParagraphExample": "p1: Merhaba! || p2: merhaba! -> p1: Merhaba! || p2: Merhaba!", "startWithUppercaseLetterAfterPeriodInsideParagraphExample": "Nasılsın? iyi misin? -> Nasılsın? İyi misin?", @@ -1862,6 +1970,8 @@ "fixMinDurationMs": "Minimum süreyi düzelt (ms)", "doNotGoPastShotChange": "Sahne değişimini geçme", "fixMaxDurationMs": "Maksimum süreyi düzelt (ms)", + "fixMinDurationFrames": "En az süreyi düzelt (kare)", + "fixMaxDurationFrames": "En çok süreyi düzelt (kare)", "minimumDurationMilliseconds": "En az süre (milisaniye):", "maximumDurationMilliseconds": "En çok süre (milisaniye):", "maxDurationShouldBeHigherThanMinDuration": "Maksimum süre minimum süreden daha yüksek olmalıdır", @@ -1923,6 +2033,20 @@ "isolateSpeechHint": "Hizalamadan önce müzik ve ses efektlerini temizler, böylece hizalayıcı yalnızca diyalogları duyar. Yüksek sesli müzik veya aksiyon üzerindeki satırlarda yardımcı olur, ancak ekran kartı (GPU) ile yaklaşık ses süresi kadar sürer - GPU olmadan ise çok daha uzun zaman alır.", "isolatingSpeech": "Konuşma ayrıştırılıyor... (bu işlem biraz zaman alır)", "isolateSpeechFailed": "Konuşma ayrıştırılamadı - bunun yerine orijinal sese göre hizalandı.", + "checkWithSpeechToText": "Sesten metne dönüştürme ile denetle", + "checkWithSpeechToTextHint": "Sesi de yazıya döker ve her hareketi sözcüklerin gerçekten duyulduğu konuma göre denetler. Konuşmanın doğruladığı büyük hareketler sizin için işaretlenir, bir satırın duyulduğu yerden uzaklaşan hareketler ise denetlemeniz için işaretlenmeden bırakılır. Ayrıca her satırın ne kadarının duyulduğunu gösterir; düşük bir oran, metnin söylenenlerden farklı olduğu anlamına gelir. Maksimum kaydırmadan daha fazla senkronizasyon dışı olan bir alt yazı (sabit bir kayma, kare hızı uyumsuzluğundan kaynaklanan sapma veya sahnelerin kesildiği yerlerdeki atlamalar) önce duyulan sözcüklere göre eşitlenir. GPU ile uzun bir sinema filmi için birkaç dakika süren Crisp ASR Parakeet sistemini kullanır.", + "checkWithSpeechToTextNotAvailableX": "Sesten metne dönüştürme denetimi {0} için kullanılamıyor", + "transcribingToCheck": "Hizalamayı denetlemek için yazıya dökülüyor...", + "speechToTextFailed": "Sesten metne dönüştürme başarısız oldu - zaman kodları buna göre denetlenmedi.", + "speechToTextX": "Sesten metne dönüştürme: {0}", + "speechToTextConfirmedXDisputedY": "Konuşma: {0} doğrulandı, {1} çelişkili", + "hardlyHeardX": "Sesten metne dönüştürme sistemi alt yazının yalnızca %{0} kadarlık sözcüğünü duydu. Alt yazı konuşulan dilde mi? Hizalayıcı bir çeviriyi yerleştiremez.", + "heard": "Duyulan", + "heardHint": "Sesten metne dönüştürme sisteminin satırın ne kadarını duyduğu. Düşük bir oran, metnin söylenenlerden farklı olduğunu gösterir - hizalayıcının yanılma olasılığının en yüksek olduğu yer burasıdır.", + "statusConfirmedBySpeech": "Konuşma ile doğrulandı", + "statusDisputedBySpeech": "Olduğu yerde duyuldu", + "statusMovedWithSync": "Eşitleme ile taşındı", + "syncedFirstXY": "Önce eşitlenen: başlangıçta {0} sn, bitişte {1} sn", "showSpeechOnly": "Yalnızca konuşmayı göster", "showSpeechOnlyHint": "Müzik ve ses efektleri temizlenmiş sesten hizalanmış dalga şeklini çizer; karşılaştırma için orijinal dalga şekli olduğu gibi kalır. Konuşma ayrıştırıldıktan sonra kullanılabilir - burada veya ana pencerenin dalga şeklinde \"Yalnızca konuşmayı göster\" ile.", "align": "Hizala", @@ -1936,7 +2060,8 @@ "summaryXRetimedYKeptZSkipped": "Yeniden zamanlandı: {0} · Korundu: {1} · Konuşma yok: {2}", "meanShiftX": "Ortalama kayma: {0} ms", "toCheckX": "Kontrol edilecek: {0}", - "largeMovesToCheckX": "{0} satır kendi başına çok fazla kayacaktı ve işareti kaldırılmış olarak bırakıldı - Orijinal / Hizalanmış seçeneğini oynatın ve doğru olanları işaretleyin.", + "largeMovesToCheckX": "{0} satır kendi başına uzun bir mesafe kaydırılacak ve işaretlenmeden bırakıldı - bunları dinleyin (F5 hizalanmış satırı, Shift+F5 orijinal satırı çalar) ve doğru olanları işaretleyin.", + "linesToCheckX": "Denetlenmesi için {0} satır işaretlenmeden bırakıldı - bunları dinleyin (F5 hizalanmış satırı, Shift+F5 orijinal satırı çalar) ve doğru olanları işaretleyin.", "changeXOfY": "Değişiklik {0} / {1}", "previousChange": "Önceki değişiklik", "nextChange": "Sonraki değişiklik", @@ -1949,13 +2074,15 @@ "statusNoSpeech": "Konuşma yok", "statusShiftTooLarge": "Korundu - kayma çok büyük", "statusFailed": "Korundu - hizalayıcı başarısız oldu", + "statusAdjustedByHand": "Elle ayarlandı", + "alignedWaveformHint": "Bir satırı taşımak için sürükleyin ya da başlangıcını veya bitişini değiştirmek için kenarlarından sürükleyin", + "playLine": "Satırı oynat", + "undoAdjustment": "Ayarlamayı geri al", "playPause": "Oynat / duraklat", - "playOriginal": "Orijinal", "playAligned": "Hizalanmış", - "playOriginalHint": "Seçili satırı orijinal zaman kodlarıyla oynatır", "playAlignedHint": "Seçili satırı hizalanmış zaman kodlarıyla oynatır", "apply": "Uygula", - "applyHint": "Orijinal zaman kodlarını korumak için satırın işaretini kaldırın. Oynatmak için bir satıra çift tıklayın." + "applyHint": "Orijinal zaman kodlarını korumak için bir satırın işaretini kaldırın. Oynatmak için bir satıra çift tıklayın. Satırlar ayrıca hizalanmış ses dalgasında taşınabilir veya yeniden boyutlandırılabilir." }, "beautifyTimeCodesProfile": { "title": "Zaman kodlarını optimize etme profilini düzenle", @@ -2035,6 +2162,7 @@ "addColor": "Renk ekle", "deleteLinesWithSpecificActorsOrStyles": "Belirli aktör veya stillere sahip satırları sil (virgülle ayırın)", "useSourceStylesIfPossible": "Mümkünse kaynak stillerini kullan", + "keepSourceEmbeddedFonts": "Kaynak gömülü yazı tiplerini koru (alt bilgi)", "editStyles": "Stilleri düzenle", "editProperties": "Özellikleri düzenle", "editAttachments": "Ekleri düzenle", @@ -2069,6 +2197,18 @@ "addFolderRecursiveDotDotDot": "Özyinelemeli klasör ekle...", "selectFolderToConvert": "Dönüştürülecek dosyaları içeren klasörü seçin", "includeSubfolders": "Klasör eklerken alt klasörleri dahil et", + "videoFilesWhenAddingFolder": "Klasör eklerken video dosyaları (mkv, mp4, ts...)", + "videoFilesAsk": "Sor", + "videoFilesInclude": "Alt yazı izlerini ekle", + "videoFilesSkip": "Atla", + "folderContainsXVideoFiles": "Klasör {0} video dosyası (mkv, mp4, ts...) içeriyor.\n\nBunların gömülü alt yazı izleri de eklensin mi, yoksa yalnızca alt yazı dosyaları mı eklensin?", + "addVideoFiles": "Video dosyalarını ekle", + "skipVideoFiles": "Yalnızca alt yazı dosyaları", + "doNotAskAgainVideoFiles": "Bir daha sorma (Toplu dönüştürme ayarlarından değiştirin)", + "settingsSectionOutput": "Çıktı", + "settingsSectionAddingFiles": "Dosya ekleme", + "settingsSectionImageBasedSubtitles": "Resim tabanlı alt yazılar (OCR)", + "settingsSectionOther": "Diğer", "keepSourceFileTimestamp": "Çıktı dosyalarında kaynak dosya tarihini/saatini koru", "preventSleepWhileConverting": "Dönüştürme sırasında bilgisayarın uyku moduna geçmesini önle", "scanningFolderX": "{0} taranıyor...", @@ -2084,7 +2224,20 @@ "transportStreamOnlyTeletext": "Yalnızca teletekst", "transportStreamGetSizeFromVideo": "Boyutu videodan al...", "twoLetterLanguageCodeUppercase": "İki harfli dil kodu (büyük harf)", - "threeLetterLanguageCodeUppercase": "Üç harfli dil kodu (büyük harf)" + "threeLetterLanguageCodeUppercase": "Üç harfli dil kodu (büyük harf)", + "formatContainsDotDotDot": "Biçim şunu içeriyor...", + "extensionIsDotDotDot": "Uzantı şudur...", + "statusIsError": "Durum hata gösteriyor", + "forcedTracksOnly": "Yalnızca zorunlu izler", + "xConvertedYFailedInZ": "{0:#,###,##0} dönüştürüldü, {1:#,###,##0} başarısız oldu ({2})", + "xNotProcessed": "{0:#,###,##0} işlenmedi", + "removeUnicodeControlCharactersTitle": "Unicode denetim karakterlerini kaldır", + "removeUnicodeControlCharactersInfo": "Görünmeyen Unicode yön denetim karakterlerini (LRM, RLM, LRE, RLE, PDF, LRO ve RLO) metinden kaldırır ve bölünmeyen boşlukları normal boşluklarla değiştirir.", + "preset": "Ön ayar", + "savePresetDotDotDot": "Ön ayarı kaydet...", + "deletePreset": "Ön ayarı sil", + "presetName": "Ön ayar adı", + "deletePresetX": "\"{0}\" ön ayarı silinsin mi?" }, "changeCasing": { "fixNames": "İsimleri düzelt", @@ -2169,11 +2322,13 @@ }, "mergeLinesWithSameText": { "maxMsBetweenLines": "Satırlar arası maksimum milisaniye", + "maxFramesBetweenLines": "Satırlar arasındaki en çok kare sayısı", "includeIncrementingLines": "Artan metne sahip satırları dahil et", "includeRollUpCaptions": "Kayar (yukarı doğru kayan) alt yazıları dahil et" }, "mergeLinesWithSameTimeCodes": { "maxMsDifference": "Maksimum fark (milisaniye)", + "maxFramesDifference": "En çok fark (kare)", "makeDialog": "Diyalog yap" }, "netflixCheckAndFix": { @@ -2288,7 +2443,14 @@ "xInTopBar": "Üst şeritte: {0}", "xInBottomBar": "Alt şeritte: {0}", "noImageSubtitlesLoaded": "Hiçbir görüntü tabanlı alt yazı yüklenmedi", - "imageBasedFormatNotSupported": "Görüntü tabanlı alt yazı biçimi bulunamadı/desteklenmiyor." + "imageBasedFormatNotSupported": "Görüntü tabanlı alt yazı biçimi bulunamadı/desteklenmiyor.", + "frameRateDeclaredInFile": "Blu-ray sup dosyasında bildirilen kare hızı", + "frameRateDetectedFromTimeCodesX": "Blu-ray sup zaman kodlarından algılanan kare hızı (dosya {0} bildiriyor)", + "frameRateFromVideo": "Videonun kare hızı", + "frameRateCurrent": "Mevcut kare hızı", + "frameRateSetManually": "Elle ayarlanan kare hızı", + "frameRateUsageInfo": "Blu-ray sup, BDN XML, DOST ve FCP dışa aktarımları bu kare hızıyla yazılır. Bu işlem hiçbir zamanlamayı değiştirmez - bunun için \"Kare hızını değiştir\" işlevini kullanın.", + "useVideoFrameRateXInsteadOfY": "Videonun kare hızı {0}, ancak alt yazı {1} olarak ayarlanmış.\n\nVideonun kare hızı ({0}) kullanılsın mı?" }, "removeTextForHearingImpaired": { "interjections": "Ünlemler / Tepki sözcükleri", @@ -2318,6 +2480,7 @@ "pickFontNameInstalledFonts": "Yüklü yazı tipleri", "pickFontNameCollectedFonts": "Toplanan yazı tipleri", "colorPickerTitle": "Renk seçin", + "colorPickerBrightness": "Parlaklık", "filterLayersTitle": "Görüntülenecek katmanları filtrele", "filterLayersHideFromWaveform": "Ses dalgası/spektrogram üzerinde gizle", "filterLayersHideFromSubtitleGrid": "Alt yazı ızgarasında gizle", @@ -2368,7 +2531,21 @@ "correctWordsX": "Doğru kelime sayısı: {0}", "namesX": "Özel ad sayısı: {0}", "addedToDictionaryX": "Sözlüğe eklenen sayı: {0}", - "doNotShowThisAgain": "Bu mesajı bir daha gösterme" + "doNotShowThisAgain": "Bu mesajı bir daha gösterme", + "editUseAlwaysList": "\"Her zaman kullan\" listesini düzenle...", + "useAlwaysListTitle": "Yazım denetimi - \"Her zaman kullan\" listesi", + "useAlwaysListHeader": "\"Her zaman kullan\" listesi", + "useAlwaysListDescription": "Yazım denetiminin otomatik olarak değiştirdiği, \"Tümünü değiştir\" ve \"Her zaman kullan\" ile kaydedilen sözcükler. Bir çift yalnızca sözcük yanlış yazıldığında kullanılır.", + "useAlwaysWord": "Yanlış yazılmış sözcük", + "xPairs": "{0:#,##0} çift", + "xUnusedPairs": "{0:#,##0} hiç kullanılmadı", + "useAlwaysUnusedHint": "Hiç kullanılmadı - bu sözcük doğru yazılmış (veya bir ad ya da kullanıcı sözcüğü), bu nedenle yazım denetimi bunu hiçbir zaman işaretlemez", + "removeUnused": "Kullanılmayanları kaldır", + "useAlwaysEmpty": "Bu dil için henüz \"Her zaman kullan\" çifti yok", + "useAlwaysEmptyHint": "Çiftler, yazım denetiminde \"Tümünü değiştir\" veya \"Her zaman kullan\" seçeneğine tıkladığınızda eklenir.", + "useAlwaysNoMatch": "Aramayla eşleşen çift yok", + "useAlwaysSameWord": "Düzeltme sözcüğün kendisinden farklı olmalıdır.", + "useAlwaysRememberOff": "Ayarlar'da \"'Her zaman kullan' listesini hatırla\" kapatılmış, bu nedenle yazım denetimi bu listeyi kullanmaz veya kaydetmez." }, "video": { "burnIn": { @@ -2406,6 +2583,11 @@ "translateToEnglish": "İngilizceye çevir", "transcribing": "Metne dökülüyor...", "transcribingXOfY": "Metne dökülüyor {0} / {1}...", + "linesTranscribedXOfYFailedZ": "{1} satırın {0} tanesi yazıya döküldü - {2} satır başarısız oldu veya konuşma içermiyordu.\n\nBaşarısız satırlar mevcut metinlerini korur. Yazıya dökülen satırlar uygulansın mı, yoksa başarısız olanlar yeniden mi denensin (önce motoru veya ayarları değiştirebilirsiniz)?", + "linesTranscribedXOfYCancelled": "Yazıya dökme işlemi {1} satırın {0} tanesinden sonra iptal edildi.\n\nYazıya dökülen satırlar uygulansın mı? Diğer satırlar mevcut metinlerini korur.", + "noLinesTranscribed": "{0} satırın hiçbiri yazıya dökülemedi.\nAyrıntılar için lütfen araçlar günlüğünü denetleyin.", + "applyTranscribedLines": "Yazıya dökülenleri uygula", + "retryFailedLines": "Başarısızları yeniden dene", "isolateSpeech": "Konuşmayı ayrıştır (yavaş)", "isolateSpeechHint": "Döküm çıkarmadan önce müzik ve ses efektlerini temizler. Yüksek sesli müzik içeren seslerde daha az kaçırılan satır ve daha iyi zamanlama sağlar, ancak ekran kartı (GPU) ile yaklaşık ses süresi kadar sürer - GPU olmadan ise çok daha uzun zaman alır.", "isolatingSpeech": "Konuşma ayrıştırılıyor...", @@ -2413,6 +2595,9 @@ "detectSpeakers": "Konuşmacıları algıla", "detectSpeakersHint": "NVIDIA Sortformer konuşmacı ayrıştırma (diarization - 8 konuşmacıya kadar) özelliğini kullanarak her satırın başına \"(konuşmacı 0)\" gibi konuşmacıyı ekler. Parakeet ve Cohere gibi konuşmacının değiştiği yerlerde satırları bölen altyapılarla en iyi şekilde çalışır.", "detectSpeakersNeedsNewerCrispAsr": "\"Konuşmacıları algıla\" özelliği Crisp ASR v{0} veya üzerini gerektirir, ancak v{1} sürümü yüklü.\n\nCrisp ASR'yi güncelleyin (motorun yanındaki dişli çark düğmesi) veya \"Konuşmacıları algıla\" seçeneğini kapatın.", + "xNeedsNewerCrispAsr": "{0}, Crisp ASR v{1} veya daha yeni bir sürümü gerektiriyor ancak v{2} yüklü.\n\nGüncelleme şimdi indirilip yüklensin mi?", + "vad": "VAD", + "vadHint": "Ses etkinliği algılama: yalnızca konuşmalar yazıya dökülür, konuşmacının durakladığı yerlerde kesilir.\nOtomatik: Silero kullanılır ve yalnızca ihtiyaç duyan motorlar içindir (Cohere, Mega, Index-Echo).\nSilero, FireRedVAD, WebRTC: Her Crisp ASR motoru için her zaman açıktır. FireRedVAD en doğru olandır.\nIndex-Echo her zaman Silero kullanır. Gelişmiş ayarlardaki --vad, --vad-model veya --chunk-seconds komutları önceliklidir.", "inputLanguage": "Giriş dili", "advancedWhisperSettings": "Gelişmiş sesten metne dönüştürme parametreleri", "downloadingSpeechToTextEngine": "Sesten metne dönüştürme motoru indiriliyor", @@ -2507,6 +2692,8 @@ "instructions": "Talimatlar", "instructionsHint": "İsteğe bağlı konuşma biçemi, ör. \"Sakin ve sıcak bir ses tonuyla konuş\". Yalnızca bunu destekleyen modellere gönderilir (tts-1/tts-1-hd hariç)", "openAiSpeedHint": "0,25 ile 4,0 arasında konuşma hızı (1,0 normaldir). Her model bunu desteklemez", + "outputFormat": "Çıktı biçimi", + "outputFormatHint": "Sunucudan istenen ses biçimi. \"Otomatik\", MP3 ister ve yalnızca PCM döndüren modeller için (ör. Gemini TTS) PCM biçimine geçer", "regenerateAudio": "Sesi yeniden oluştur", "autoContinuePlaying": "Oynatmaya otomatik devam et", "playLine": "Satırı oynat", @@ -2534,6 +2721,17 @@ "voiceManagerTitle": "TTS - Ses yöneticisi", "voiceManagerDotDotDot": "Ses yöneticisi...", "voiceManagerMenuItem": "TTS ses yöneticisi...", + "speakFromCurrentLineTitle": "Mevcut satırdan itibaren seslendir", + "speakFromCurrentLineDotDotDot": "Mevcut satırdan itibaren seslendir...", + "speakFromCurrentLineHint": "Mevcut satırdan başlayarak satırları sırayla yüksek sesle okur. Durdurmak için Escape tuşuna basın veya başka bir satıra tıklayın.", + "stopSpeaking": "Seslendirmeyi durdur", + "speakingLineX": "{0}. satır seslendiriliyor... (Durdurmak için Escape)", + "playWithSpeechTitle": "Seslendirmeyle oynat", + "playWithSpeechDotDotDot": "Seslendirmeyle oynat...", + "playWithSpeechHint": "Videoyu oynatır ve video ilgili satıra ulaştığında her satırı seslendirir. Her zamanki gibi duraklatın ve sarın; durdurmak için Escape tuşuna basın.", + "lowerVideoVolumeWhileSpeaking": "Konuşurken video sesini kıs", + "pauseVideoWhenSpeechRunsLate": "Konuşma bir sonraki satıra taştığında videoyu duraklat", + "waitingForSpeechLineX": "{0}. satır için konuşma oluşturuluyor... (Durdurmak için Escape)", "voiceKindClone": "Klonlanmış ses", "voiceKindPreset": "Yerleşik", "voiceKindModel": "Ses modeli", @@ -2598,14 +2796,25 @@ "advancedTtsSettings": "Gelişmiş TTS ayarları", "advancedTtsAudioProcessing": "Ses işleme", "advancedTtsOutput": "Çıktı", + "advancedTtsVoiceCloning": "Ses klonlama", + "cleanCloneReferences": "Ses klonlama referanslarını temizle (Sidon)", + "cleanCloneReferencesDescription": "Sesin klonlandığı sesteki müziği, gürültüyü ve çınlamayı (yankıyı) kaldırır: videodan alınan klipler ve ses yöneticisine aktarılan kayıtlar. audio.cpp üzerindeki Sidon konuşma onarım modelini kullanır (940 MB indirme). Klonlanan ses artık arka planı kopyalamaz, ancak çok kısık veya üst üste binen konuşmalar boğuk çıkabilir.", + "cleaningCloneReferencesDotDotDot": "Ses klonlama referansları temizleniyor...", + "downloadSidonTitle": "Sidon indirilsin mi?", + "downloadSidonQuestionX": "Ses klonlama referanslarını temizlemek için Sidon konuşma onarım modeli ({0}) gereklidir.\n\nŞimdi indirilsin mi? O olmadan referanslar olduğu gibi kullanılır.", "proAudioPostProcessing": "Profesyonel ses son işlemesi", "proAudioPostProcessingDescription": "Her klip için EQ, gürültü kapısı, sıkıştırma, ses yüksekliği normalleştirmesi (-16 LUFS) ve kısa bir baş/son solması ekler.", + "generatingSpeechSegmentXOfY": "Konuşma oluşturuluyor: {1} bölümün {0}. bölümü", + "adjustingSpeedSegmentXOfY": "Hız ayarlanıyor: {1} bölümün {0}. bölümü", + "mergingAudioSegmentXOfY": "Ses birleştiriliyor: {1} bölümün {0}. bölümü", + "postProcessingSegmentXOfY": "Son işleme: {1} bölümün {0}. bölümü", "audioDucking": "Ses kısma", "audioDuckingDescription": "Orijinal parçanın hafifçe duyulabilir kalması için orijinal video sesini kısıp konuşmayı üzerine karıştırır. Yalnızca konuşma video dosyasına eklendiğinde uygulanır.", "removeOriginalSpeech": "Orijinal konuşmayı kaldır (yavaş)", "removeOriginalSpeechDescription": "Orijinal videonun müzik ve ses efektlerini korur ancak konuşmasını kaldırır ve yeni konuşmayı bunun üzerine miksler. Ekran kartı (GPU) ile yaklaşık video süresi kadar sürer - GPU olmadan ise çok daha uzun zaman alır. Ses kısma (ducking) açık olduğunda, ses seviyesi müziğe ve efektlere uygulanır. Yalnızca konuşma video dosyasına eklendiğinde geçerlidir.", "removingOriginalSpeech": "Orijinal konuşma kaldırılıyor...", "removeOriginalSpeechFailed": "Orijinal konuşma kaldırılamadı - konuşma video dosyasına orijinal konuşma temizlenmeden eklendi.", + "videoTruncatedWarning": "Yeni video dosyasındaki görüntü {0} süresinden sonra duruyor, ancak orijinal video {1} uzunluğunda - ses devam ederken görüntü donacaktır.\n\nDosya tutuldu. ffmpeg çıktısı için Subtitle Edit veri klasöründeki error-log.txt dosyasına bakın.", "originalVolumePercent": "Orijinal ses düzeyi %", "vadSilenceCompression": "VAD sessizlik sıkıştırması", "vadSilenceCompressionDescription": "Bir klibin kalite kaybı olmadan sığması için konuşmayı hızlandırmak yerine kelimeler arasındaki duraklamaları kısaltır.", @@ -2688,7 +2897,9 @@ "timeCodeFormatColon": "Zaman kodu formatı:", "shotChangesList": "Sahne değişimleri listesi", "shotChangesClearQuestion": "Tüm sahne değişimlerini temizlemek istediğinize emin misiniz?", - "deleteSelectedShotChangeQuestion": "Seçili sahne değişimini silmek istediğinize emin misiniz?" + "deleteSelectedShotChangeQuestion": "Seçili sahne değişimini silmek istediğinize emin misiniz?", + "shotChanges": "Çekim değişimleri", + "exportShotChanges": "Çekim değişimlerini dışa aktar" }, "chapters": { "title": "Bölümler", @@ -2751,6 +2962,7 @@ "runningOcrXY": "OCR çalıştırılıyor... {0}/{1}", "noLinesFoundTitle": "Alt yazı bulunamadı", "noLinesFoundMessage": "Tarama alanında metin bulunamadı; tarama alanını, motoru veya minimum parlaklığı ayarlamayı deneyin.", + "ocrStoppedEarlyMessage": "OCR motoru tüm kareler okunmadan önce durdu, bu nedenle şimdiye kadar bulunan satırlar korundu ve geri kalanı eksik.{0}{0}{1}", "linesFoundX": "{0} satır bulundu", "refiningTimingXY": "Zamanlama iyileştiriliyor... {0}/{1}", "fixOcrErrors": "OCR hatalarını düzelt", @@ -2853,6 +3065,13 @@ "remuxVideoFailed": "Video remux edilemedi.", "remuxVideoPleaseSelectBoth": "Lütfen hem video hem de ses dosyalarını seçin.", "remuxVideoAssRequiresMkv": "ASS/SSA alt yazıları tüm biçimlendirme ve stilleri korumak için MKV kapsayıcısı gerektirir.", + "remuxVideoSccRequiresMov": "Scenarist (.scc) alt yazıları, MOV veya MPG kapsayıcısı gerektiren CEA-608 kapalı alt yazıları (CC) olarak gömülür. Çıktı biçimi otomatik olarak .mov olarak değiştirildi.", + "remuxVideoSccNotWithWebM": "Scenarist (.scc) kapalı alt yazıları (CC), bir .webm dosyasının VP9/AV1 videosunu barındıramayan MOV kapsayıcısına ihtiyaç duyar. .scc dosyasını çıkarın veya başka bir alt yazı biçimine dönüştürün.", + "remuxVideoMpgMaxTwoCaptionTracks": "Bir .mpg dosyası en fazla iki kapalı alt yazı izi (CC1 ve CC3) barındırabilir - geriye bir veya iki tane kalacak şekilde alt yazı dosyalarını çıkarın.", + "remuxVideoMccRequiresMpg": "MacCaption (.mcc) alt yazıları, MPEG-2 videosunda MPG kapsayıcısı gerektiren CEA-608/708 kapalı alt yazıları (CC) olarak gömülür. Çıktı biçimi otomatik olarak .mpg olarak değiştirildi.", + "remuxVideoMpgReencodeVideoX": "Bir .mpg dosyasındaki kapalı alt yazılar (CC) MPEG-2 videosuna ihtiyaç duyuyor, ancak video {0}. Video MPEG-2 olarak yeniden kodlansın mı? Bu işlem daha uzun sürer ve kaliteyi biraz düşürür.", + "remuxVideoFileHasNoAudioX": "\"{0}\" bir ses izi içermediğinden ses kaynağı olarak kullanılamaz.", + "remuxVideoAddingClosedCaptions": "Kapalı alt yazılar (CC) ekleniyor...", "remuxVideoSelectAudioTrack": "Ses izini seç", "remuxVideoSelectAudioTrackFor": "{0} için ses izini seç", "remuxVideoSelectAudioTrackPrompt": "'{0}' dosyası {1} ses izi içeriyor. Lütfen remux edilecek ses izini seçin:", @@ -2865,6 +3084,7 @@ "remuxVideoMixAudio": "Sesleri tek bir izde birleştir", "remuxVideoVolumePercent": "Ses düzeyi %:", "remuxVideoVolumeX": "Ses düzeyi %{0}", + "remuxVideoFastStart": "Web akışı için optimize et (dizini başa taşı - daha yavaş)", "embedSubtitlesDotDotDot": "Gömülü alt yazıları ekle/kaldır...", "generateTimeCodes": "Zaman kodları oluştur", "checkeredImage": "Damalı resim", @@ -2964,6 +3184,10 @@ "reEncodeGeneratingDone": "Oluşturma işlemi tamamlandı", "reEncodeGeneratedFilesX": "Oluşturulan dosyalar ({0}):", "reEncodeFfmpegParameters": "ffmpeg parametreleri", + "analyzingVideoX": "Video analiz ediliyor... %{0} {1}", + "analyzingVideoXofY": "Video analiz ediliyor {0}/{1}... %{2} {3}", + "backgroundImageFileNotSelected": "Arka plan görseli dosyası seçilmedi", + "backgroundImageFileDoesNotExistX": "Arka plan görseli dosyası mevcut değil: {0}", "voices": "Sesler", "presets": "Ön ayarlar", "talkerX": "Konuşmacı {0}", @@ -3044,7 +3268,8 @@ "syncPoints": "Senkronizasyon noktaları", "pointSync": "Nokta senkronizasyonu", "pointSyncViaOther": "Diğer alt yazı üzerinden nokta senkronizasyonu", - "syncPointCandidateInfo": "Sessizlik süresi {0} saniyeyi geçen satırlar vurgulanır - bunlar genellikle güvenilir eşitleme noktaları oluşturur", + "syncPointCandidateAfterInfo": "{0}+ saniye sessizlik içeren satırlar vurgulanır - bunlar genellikle güvenilir eşitleme noktaları oluşturur", + "gapAfter": "Sonraki boşluk", "adjustAllShortcuts": "Klavye kısayolları:\n\n• Shift + Sol/Sağ: 10 ms taşı\n• Ctrl + Sol/Sağ: 100 ms taşı\n• Alt + Sol/Sağ: 500 ms taşı", "adjustAllShortcutsFrames": "Klavye kısayolları:\n\n• Shift + Sol/Sağ: 1 kare taşı\n• Ctrl + Sol/Sağ: 10 kare taşı\n• Alt + Sol/Sağ: 1 saniye taşı", "offsetInSeconds": "Saniye cinsinden fark", @@ -3202,9 +3427,12 @@ "newEmptyDefaultMs": "Varsayılan yeni alt yazı süresi (ms)", "timeCodeUpDownStepMs": "Zaman artırma/azaltma kademesi (ms)", "moveSelectedLinesStepMs": "Seçili satırları taşıma kısayol adımı (ms)", + "moveStartEndStepMs": "Başlangıç/bitiş taşıma kısayolu adımı (ms)", "moveLinesShortenNeighbor": "Satırları taşı: çakışma olmaması için önceki/sonraki satırı kısalt", "promptBeforeDelete": "Silmeden önce sor", "rememberPositionAndSize": "Pencere konumunu ve boyutunu hatırla", + "titleBarFullFileName": "Başlık çubuğunda tam dosya yolunu göster", + "showRecentFiles": "Son kullanılan dosyaları göster (yeniden açmak için)", "openLastFileOnStart": "Başlangıçta son kullanılan dosyayı aç", "autoSave": "Otomatik kaydet (düzenlerken açık olan dosyayı kaydeder)", "autoBackupOn": "Otomatik yedekleme", @@ -3231,6 +3459,8 @@ "waveformDrawGridLines": "Izgara çizgilerini çiz", "waveformUseSkiaRenderer": "Deneysel hızlı işleyiciyi kullan", "waveformCenterVideoPositionAlsoWhenPaused": "Duraklatıldığında da video konumunu merkezde tut", + "waveformSelectCurrentSubtitleWhilePaused": "Duraklatıldığında da mevcut alt yazıyı seç", + "waveformRememberZoom": "Yakınlaştırmayı hatırla (yatay ve dikey)", "waveformShowToolbar": "Araç çubuğunu göster", "waveformShowToolbarEdit": "Araç çubuğunu düzenle...", "waveformShowToolbarEditLabel": "Araç çubuğu ögeleri", @@ -3296,6 +3526,7 @@ "adjustAllTimesRememberLineSelectionChoice": "Tüm zamanları ayarla, satır seçim tercihini hatırla", "mergeKeepEndTime": "Satırları birleştir: Bitiş zamanını koru (sonraki alt yazıyla üst üste binmeye izin ver)", "mergeKeepEndTimeOnlyAssa": "Satırları birleştir: Bitiş zamanını yalnızca \"Advanced Sub Station Alpha\" için koru", + "mergeAutoBreak": "Satırları birleştir: birleştirilen metni otomatik satırla (kapalı, satırların kendi satır sonlarını korur)", "filesAndLogs": "Dosyalar ve günlükler", "showErrorLogFile": "Hata günlüğünü göster", "showToolsLogFile": "Whisper günlüğünü göster", @@ -3304,6 +3535,7 @@ "showAssaLayer": "ASSA katman kutusunu göster", "waveformCursorColor": "Ses dalgası imleç/kafa rengi", "waveformShotChangeColor": "Ses dalgası sahne değişimi rengi", + "waveformGridColor": "Ses dalgası ızgara rengi", "waveformParagraphLeftColor": "Ses dalgası sol kenarlık rengi", "waveformParagraphRightColor": "Ses dalgası sağ kenarlık rengi", "waveformFancyHighColor": "Ses dalgası süslü yüksek rengi", @@ -3331,10 +3563,14 @@ "resetSyntaxColoring": "Söz dizimi renklendirmeyi sıfırla", "resetWaveform": "Ses dalgasını sıfırla", "resetRules": "Kuralları sıfırla", - "useFrameMode": "Kare modunu kullan (ss.dd.sn.kk)", + "timeCodeMode": "Zaman kodu modu", + "timeCodeModeTime": "Zaman (ss:dd:sn,ms)", + "timeCodeModeFrames": "Kareler (ss:dd:sn:kk)", + "timeCodeModeFrameNumbers": "Kare numaraları", "textBoxLimitNewLines": "Alt yazı metin kutusundaki satır sayısını sınırla", "mpvOpenGl": "libmpv - OpenGL", "mpvSoftwareRendering": "libmpv - Yazılımsal oluşturma (yavaş)", + "mpvMetalRendering": "libmpv - Metal (deneysel, yeniden başlatma gerekir)", "mpvWidRendering": "libmpv - Yerel Pencere ID oluşturma (Windows/Linux'ta daha hızlı)", "waveFormsAndSpectrogramFoldersContainsX": "\"Ses dalgaları\" ve \"spektrogram\" klasörleri {0} içeriyor", "deleteWaveformAndSpectrogramFoldersQuestion": "\"Ses dalgaları\" ve \"Spektrogram\" dosyaları silinsin mi?", @@ -3408,6 +3644,7 @@ "useFocusedButtonBackgroundColor": "Odaklanmış buton arka plan rengini kullan", "focusedButtonBackgroundColor": "Odaklanmış buton arka plan rengi", "forceCrLfOnSave": "Kaydederken CR+LF zorla (metin al yazı dosyaları)", + "linuxClipboardUseExternalTool": "Metni xclip/wl-copy ile kopyala (aksanlı karakterleri korur)", "showFormatLimitWarning": "Satırlar biçim sınırlarını aştığında kaydederken uyar (ör. SCC satır başına 32 karakter)", "textBoxButtonShowAutoBreak": "Metin kutusu: Otomatik bölme butonunu göster", "textBoxButtonShowUnbreak": "Metin kutusu: Birleştirme butonunu göster", @@ -3420,12 +3657,17 @@ "allSettings": "Tüm ayarlar", "uiScale": "Arayüz ölçeği (%)", "fontScale": "Yazı tipi ölçeği (%)", + "textSelectionOpacity": "Metin seçimi matlığı (%)", "waveformToolbarItems": "Ses dalgası araç çubuğu ögeleri", "matchIconColorToDarkTheme": "Simge rengini koyu tema ön plan rengiyle eşleştir", "subtitlePreviewProperties": "Alt yazı ön izleme özellikleri", "usePositionFromSubtitleFile": "Alt yazı dosyasındaki konumu kullan (TTML/PAC/EBU STL)", "marginIsPartOfSubtitleArea": "Kenar boşluğu alt yazı alanına dahildir", "textJustify": "Satırları iki yana yasla", + "deinterlace": "Videodaki taramayı kaldır (Deinterlace)", + "deinterlaceOff": "Kapalı", + "deinterlaceAuto": "Otomatik (yalnızca taranmış kareler)", + "deinterlaceAlways": "Her zaman", "pixelWidthInfo": "Yeşil çizgiler = maks. genişlik sınırı | Kırmızı alan = metin sınırı aşıyor", "spellCheckEngineHunSpelll": "Hunspell", "spellCheckEngineMsWord": "MS Word", @@ -3454,11 +3696,34 @@ "categoryVideo": "Video", "categorySync": "Senkronizasyon", "categoryAi": "Yapay Zekâ", + "categoryCustom": "Özel", + "editCustomShortcut": "Özel kısayolu düzenle", + "customShortcutTextBoxKeyHint": "Burada Ctrl/Alt ile bir tuş kullanın - sade bir tuş artık metin kutusuna yazı yazmaz.", + "customShortcutNumberX": "Özel kısayol #{0}", + "customShortcutNumberXY": "Özel kısayol #{0}: {1}", + "customShortcutStep": "Adım", + "customShortcutSteps": "Adımlar", + "customShortcutStepRunCommand": "Komut çalıştır", + "customShortcutStepInsertText": "Metin ekle", + "customShortcutStepReplace": "Bul ve değiştir", + "customShortcutInsertAtCursor": "Imleç konumunda", + "customShortcutInsertAtStart": "Metin başlangıcında", + "customShortcutInsertAtEnd": "Metin sonunda", + "customShortcutSummaryRunCommandX": "Çalıştır: {0}", + "customShortcutSummaryInsertXY": "\"{0}\" ekle ({1})", + "customShortcutSummaryReplaceXY": "\"{0}\" metnini \"{1}\" ile değiştir", + "customShortcutSummaryReplaceRegexXY": "Düzenli ifade (regex) \"{0}\" metnini \"{1}\" ile değiştir", + "customShortcutPickCommand": "Lütfen çalıştırılacak bir komut seçin.", + "customShortcutEnterText": "Lütfen eklenecek metni girin.", + "customShortcutEnterFind": "Lütfen aranacak metni girin.", + "customShortcutInvalidRegexX": "Geçersiz düzenli ifade (regex): {0}", + "customShortcutStepsHint": "Adımlar yukarıdan aşağıya doğru çalışır. Metin adımları seçili satırlarda çalışır; pencere açan komutlar ise pencere kapandıktan sonra devam eder.", "activeIn": "Etkin olduğu yer", "activeInEverywhere": "Her yer", "generalMergeSelectedLinesAndUnbreak": "Seçili satırları birleştir ve tek satır yap", "generalMergeSelectedLinesAndUnbreakCjk": "Seçili satırları birleştir ve boşluksuz tek satır yap (CJK)", "generalUnbreakNoSpaceCjk": "Boşluksuz birleştir (CJK)", + "generalToggleBreak": "Satır bölmeyi/birleştirmeyi değiştir", "generalMergeSelectedLinesBilingual": "Seçili satırları iki dilli birleştir", "generalToggleTranslationMode": "Çeviri modunu aç/kapat", "generalToggleTranslationAndOriginalInPreviews": "Video/ses ön izlemesinde çeviri ve orijinali değiştir", @@ -3480,6 +3745,7 @@ "fileOpenKeepVideo": "Aç (videoyu koru)", "fileSave": "Kaydet", "fileSaveAs": "Farklı kaydet", + "fileSaveOriginal": "Orijinalini kaydet", "fileCompare": "Karşılaştır", "fileStatistics": "İstatistikler", "fileImportPlainText": "Düz metin içe aktar", @@ -3500,6 +3766,7 @@ "detectKey": "Tuşu algıla", "control": "Kontrol", "alt": "Alt", + "altGr": "AltGr", "win": "Win", "shift": "Shift", "controlMac": "⌃", @@ -3510,6 +3777,11 @@ "resetShortcutsDetail": "Tüm kısayolları varsayılan değerlere sıfırlamak istiyor musunuz?", "togglePlayPause": "Oynat/Duraklat aç/kapat", "toggleLockTimeCodes": "Zaman kodlarını kilitlemeyi aç/kapat", + "toggleTimeCodeMode": "Zaman kodu modunu değiştir (zaman/kareler/kare numaraları)", + "toggleScreenPrivacy": "Ekran gizliliğini değiştir (dosya adlarını gizle, ardından metinleri de gizle)", + "screenPrivacyFileNamesHidden": "Ekran gizliliği: dosya adları gizlendi", + "screenPrivacyFileNamesAndTextsHidden": "Ekran gizliliği: dosya adları ve metinler gizlendi", + "screenPrivacyOff": "Ekran gizliliği: kapalı", "duplicateSelectedLines": "Seçili satırları çoğalt", "sourceView": "Kaynak görünümü", "addOrEditBookmark": "Yer imi ekle veya düzenle", @@ -3558,6 +3830,7 @@ "listViewColumnTextUp": "Sütun, metni yukarı taşı", "listViewColumnTextDown": "Sütun, metni aşağı taşı", "autoTranslateSelectedLines": "Seçili satırları otomatik çevir...", + "autoTranslateSelectedLinesNoPrompt": "Seçili satırları otomatik çevir (istem olmadan, son motoru/dilleri kullan)", "setAssaResolution": "ASSA çözünürlüğünü ayarla (PlayResX/PlayResY)", "setShortcutForX": "\"{0}\" için kısayol ayarla", "commandFileNewKeepVideo": "Yeni (videoyu koru)", @@ -3638,6 +3911,14 @@ "surroundWith": "Şununla çevrele...", "surroundWithXY": "{0}/{1} ile çevrele", "surroundWithNumberX": "#{0} ile çevrele", + "surroundWithBehavior": "Davranış", + "surroundWithBehaviorToggle": "Değiştir (ekle veya varsa kaldır)", + "surroundWithBehaviorAdd": "Ekle (her zaman)", + "surroundWithBehaviorRemove": "Kaldır (tümünü)", + "surroundWithBehaviorRemoveOnce": "Kaldır (her seferinde bir tane)", + "surroundWithWorksOn": "Uygulama alanı", + "surroundWithScopeSelectionOrText": "Seçim, yoksa tüm metin", + "surroundWithScopeEachLine": "Her satır", "searchVia": "Şununla ara:", "searchViaX": "{0} ile ara", "searchViaNumberX": "#{0} ile ara", @@ -3665,6 +3946,11 @@ "copySubtitlePathToClipboard": "Alt yazı yolunu panoya kopyala", "copySubtitleOriginalPathToClipboard": "Orijinal alt yazı yolunu panoya kopyala", "focusTextBox": "Metin kutusuna odaklan", + "focusSubtitleListView": "Alt yazı liste görünümüne odaklan", + "focusWaveform": "Dalga şekline odaklan", + "focusOriginalTextBox": "Orijinal metin kutusuna odaklan", + "textBoxGoToStart": "Metin kutusu, başa git", + "textBoxGoToEnd": "Metin kutusu, sona git", "sortByNumber": "Numaraya göre sırala", "sortByStartTime": "\"Göster\" zamanına göre sırala", "sortByEndTime": "\"Gizle\" zamanına göre sırala", @@ -3704,6 +3990,7 @@ "googleIt": "Google'da ara (seçili metin)", "setActorX": "Aktörü ayarla: {0}", "setNewActor": "Yeni konuşmacı ayarla...", + "setStyleX": "Stili ayarla: {0}", "toggleDialogDashes": "Diyalog çizgilerini aç/kapat", "mergeWithLineBeforeAsDialog": "Önceki satırla diyalog olarak birleştir", "mergeWithLineAfterAsDialog": "Sonraki satırla diyalog olarak birleştir", @@ -3715,6 +4002,10 @@ "moveStartOneFrameForwardKeepGapPrev": "Başlangıcı bir kare ileri taşı (yakınsa öncekiyle boşluğu koru)", "moveEndOneFrameBackKeepGapNext": "Bitişi bir kare geri taşı (yakınsa sonrakiyle boşluğu koru)", "moveEndOneFrameForwardKeepGapNext": "Bitişi bir kare ileri taşı (yakınsa sonrakiyle boşluğu koru)", + "moveStartXMsBack": "Başlangıcı X ms geriye taşı (X Ayarlar'da ve Görsel Eşleme'de belirlenir)", + "moveStartXMsForward": "Başlangıcı X ms ileriye taşı (X Ayarlar'da ve Görsel Eşleme'de belirlenir)", + "moveEndXMsBack": "Bitişi X ms geriye taşı (X Ayarlar'da ve Görsel Eşleme'de belirlenir)", + "moveEndXMsForward": "Bitişi X ms ileriye taşı (X Ayarlar'da ve Görsel Eşleme'de belirlenir)", "moveSelectedLinesXMsBack": "Seçili satırları X ms geriye taşı (X Ayarlarda belirlenir)", "moveSelectedLinesXMsForward": "Seçili satırları X ms ileriye taşı (X Ayarlarda belirlenir)", "moveSelectedLinesAndForwardXMsBack": "Seçili satırları ve sonraki tümünü X ms geriye taşı (X Ayarlarda belirlenir)", @@ -3869,6 +4160,8 @@ "binaryImageCompareInspectImageMatches": "\"İkili resim karşılaştırma\" - Resim eşleşmelerini incele", "saveBlankTextTitle": "Boş metin kaydedilsin mi?", "saveBlankTextPrompt": "Resim için boş metin kaydedilsin mi?", + "drawnLinesDoNotMatchTitle": "Çizilen çizgiler görselle eşleşmiyor", + "drawnLinesDoNotMatchPrompt": "Çizilen çizgilerin bazıları görselle eşleşmiyor (yeşil çizgiler karakterin üzerinde, kırmızı çizgiler karakterin dışında olmalıdır); bu nedenle bu karakter tanınmayacak.\n\nYine de kaydedilsin mi?", "yesAndNeverAskAgain": "Evet ve bir daha sorma", "importTextFromSubtitleDotDotDot": "Alt yazıdan metin aktar...", "importTextFromSubtitleOverwritePrompt": "Bazı satırlarda zaten OCR metni var. Mevcut metnin üzerine yazılsın mı?", @@ -3930,6 +4223,19 @@ "startTraining": "Eğitimi başlat", "abortTraining": "Eğitimi iptal et", "trainingOptions": "Eğitim seçenekleri", + "trainNOcrSubtitle": "Yüklü yazı tiplerindeki karakterleri oluşturur ve nOCR'ye bunların şekillerini öğretir. Alt yazılarınızın kullandığı yazı tiplerini seçin.", + "xFontsSelected": "{0:#,##0} seçildi", + "subtitleFonts": "Alt yazı yazı tipleri", + "emptyDatabase": "Boş veritabanı", + "startFromDatabase": "Şuradan başlat", + "startFromDatabaseHint": "Eğitilen karakterleri mevcut bir veritabanının kopyasına ekleyin (önerilir) veya boş bir veritabanıyla başlayın", + "fontSizes": "Yazı tipi boyutları", + "fontSizesHint": "Piksel cinsinden oluşturulacak yazı tipi boyutları, virgülle ayrılmış şekilde, örn. \"30, 40\"", + "numberOfLineSegmentsHint": "Karakter başına kontrol edilen çizgi sayısı - sayı arttıkça doğruluk artar ancak işlem yavaşlar", + "enterDatabaseName": "Veritabanı için bir ad girin", + "trainingFontXSizeYCharacterZ": "\"{0}\", {1} piksel boyutunda eğitiliyor: {2}", + "xLearned": "{0:#,##0} öğrenildi", + "xSkipped": "{0:#,##0} atlandı", "charactersToTrain": "Eğitilecek karakterler", "letterCombinationsToTrain": "Tek bir görsele bölünebilecek harf kombinasyonları", "importCharactersFromSubtitleFile": "Alt yazı dosyasından karakter içe aktar...", @@ -3956,13 +4262,90 @@ "drawZoomOut": "Uzaklaştır (Ctrl+-)", "drawResetView": "Görünümü Sıfırla (Ctrl+0)", "drawToggleGrid": "Izgarayı Aç/Kapat (Ctrl+G)", + "drawTogglePreview": "Ön izlemeyi değiştir (F9)", + "drawPreviewFailed": "Ön izleme başarısız oldu - libass (subtitles filtresi) içeren ffmpeg gereklidir", "drawCopyToClipboard": "Panoya Kopyala (Ctrl+C)", "drawShapes": "Şekiller", "drawSelectedPoint": "Seçili nokta", "drawSelectedShape": "Seçili şekil", "drawSelectedLayer": "Seçili katman", "drawToolX": "Araç: {0}", - "drawHelpText": "Nokta eklemek için tıklayın • Şekli kapatmak için Enter/F8 • Kaydırmak için Shift+Sürükle • Yakınlaştırmak için Ctrl+Tekerlek", + "drawHelpText": "Nokta eklemek için tıklayın • Seçenekler için sağ tıklayın • Seçim aracı: boyutlandırmak/döndürmek için tutamaçları sürükleyin (Shift = oranı/15°'yi koru) • Şekli kapatmak için Enter/F8 • Kaydırmak için Shift+sürükle veya orta fare tuşu • Yakınlaştırmak/uzaklaştırmak için Ctrl+Fare Tekerleği", + "drawMoveToLayer": "Katmana taşı", + "drawFlipHorizontal": "Yatay olarak çevir", + "drawFlipVertical": "Dikey olarak çevir", + "drawHideShape": "Şekli gizle", + "drawShowShape": "Şekli göster", + "drawHideLayer": "Katmanı gizle", + "drawShowLayer": "Katmanı göster", + "drawDeleteLayer": "Katmanı sil", + "drawDeletePoint": "Noktayı sil", + "drawCancelDrawing": "Çizimi iptal et", + "drawLayerX": "Katman {0}", + "drawImportSvg": "SVG görseli içe aktar...", + "drawRotateClockwise": "Saat yönünde 90° döndür", + "drawConvertToCurve": "Segmenti eğriye dönüştür", + "drawBackground": "Arka plan", + "drawPosition": "Konum", + "drawZoom": "Yakınlaştırma", + "drawShapeTool": "Şekil aracı", + "drawColorPickerTool": "Bir şekilden veya arka plandan renk seç", + "drawShapeLibrary": "Şekil kitaplığı", + "drawShapeLibraryHint": "Bir şekil yerleştirmek için tuval üzerine sürükleyin veya varsayılan boyut için tıklayın. Shift tuşu oranlarını korur.", + "drawAddToLibrary": "Şekil kitaplığına ekle...", + "drawRemoveFromLibrary": "Şekil kitaplığından kaldır", + "drawShapeName": "Şekil adı", + "drawCategorySpeechBubbles": "Konuşma baloncukları", + "drawCategoryArrows": "Oklar", + "drawCategoryBasicShapes": "Temel şekiller", + "drawCategorySymbols": "Semboller", + "drawCategoryMyShapes": "Şekillerim", + "drawShapeSpeechBubble": "Konuşma baloncuğu", + "drawShapeSpeechBubbleRight": "Konuşma baloncuğu, kuyruk sağda", + "drawShapeSpeechBubbleUp": "Konuşma baloncuğu, kuyruk yukarıda", + "drawShapeOvalBubble": "Oval konuşma baloncuğu", + "drawShapeBoxBubble": "Kutu konuşma baloncuğu", + "drawShapeThoughtBubble": "Düşünce baloncuğu", + "drawShapeShoutBubble": "Haykırış baloncuğu", + "drawShapeCaptionBox": "Alt yazı kutusu", + "drawShapeArrow": "Ok", + "drawShapeDoubleArrow": "Çift ok", + "drawShapeChevron": "Chevron", + "drawShapeCurvedArrow": "Kavisli ok", + "drawShapeRoundedRectangle": "Yuvarlatılmış dikdörtgen", + "drawShapeTriangle": "Üçgen", + "drawShapeDiamond": "Eşkenar dörtgen", + "drawShapePentagon": "Beşgen", + "drawShapeHexagon": "Altıgen", + "drawShapeOctagon": "Sekizgen", + "drawShapeStar": "Yıldız", + "drawShapeRing": "Halka", + "drawShapePlus": "Artı", + "drawShapeBanner": "Pankart", + "drawShapeHeart": "Kalp", + "drawShapeCheck": "Onay işareti", + "drawShapeMusicNote": "Müzik notası", + "drawShapeMusicNotes": "Müzik notaları", + "drawShapeLightning": "Yıldırım", + "drawShapeCloud": "Bulut", + "drawShapeMoon": "Ay", + "drawShapeBadge": "Rozet", + "drawSize": "Boyut", + "drawLayer": "Katman", + "drawBackgroundVideoFrame": "Video karesi (mevcut konum)", + "drawBackgroundVideoFrameAt": "Belirli konumdaki video karesi...", + "drawBackgroundImage": "Görsel dosyası...", + "drawBackgroundNone": "Arka plan yok", + "drawBackgroundStretch": "Görseli tuvale esnet", + "drawBackgroundOpacity": "Arka plan opaklığı", + "drawBackgroundVideoFrameFailed": "{0} konumunda bir video karesi alınamadı.", + "drawImages": "Görseller", + "drawConvertToLine": "Segmenti düz çizgiye dönüştür", + "drawConvertShapeToCurves": "Tüm çizgileri eğrilere dönüştür", + "drawConvertShapeToLines": "Tüm eğrileri çizgilere dönüştür", + "drawRotateCounterClockwise": "Saat yönünün tersine 90° döndür", + "drawSvgImages": "SVG görselleri", + "drawSvgNoShapes": "SVG dosyasında doldurulmuş şekil veya kontur bulunamadı.", "progressBarTitle": "ASSA ilerleme çubuğu", "progressBarSettings": "İlerleme çubuğu", "mouseOverColor": "Fareyle üzerine gelme rengi", @@ -4059,13 +4442,26 @@ "graphics": "Grafikler", "copyToStorageStyles": "Depo stillerine kopyala", "copyToFileStyles": "Dosya stillerine kopyala", + "styleXAlreadyExistsInStorage": "\"{0}\" adında bir stil depolamada zaten var.\n\nÜzerine yazılsın mı yoksa ikisi de korunsun mu (kopyaya yeni bir ad verilir)?", + "styleXAlreadyExistsInFile": "\"{0}\" adında bir stil dosyada zaten var.\n\nÜzerine yazılsın mı yoksa ikisi de korunsun mu (kopyaya yeni bir ad verilir)?", + "styleNameCannotBeEmpty": "Stil adı boş bırakılamaz.", + "styleNameXIsUsedMoreThanOnce": "\"{0}\" stil adı dosyada birden fazla stil tarafından kullanılıyor.", + "overwrite": "Üzerine yaz", + "keepBoth": "İkisini de koru", + "doThisForAllConflictingStyles": "Bunu çakışan tüm stiller için yap", "setStyleAsDefault": "Stili varsayılan yap", "takeUsagesFromDotDotDot": "Kullanımları şuradan al...", "replaceStyleWithDotDotDot": "Stili şununla değiştir...", "noAttachmentsFound": "Seçili ASSA dosyasında ek bulunamadı.", "deleteStyleQuestion": "Stil silinsin mi?", "deleteStylesQuestion": "Stiller silinsin mi?", + "deleteXStylesQuestion": "{0} stil silinsin mi?", + "deleteXStylesFromStorageQuestion": "Depolamadan {0} stil silinsin mi?", + "deleteStyleXFromStorageQuestion": "\"{0}\" stili depolamadan silinsin mi?", + "deleteStyleXFromFileQuestion": "\"{0}\" stili mevcut dosyadan silinsin mi?", "openStyleImportFile": "Stilleri içe aktarmak için alt yazı dosyası aç", + "styleImportFiles": "Stil dosyaları (.ass, .sty, Subtitle Edit 4 kategorisi .template)", + "ssaStyleImportFiles": "Stil dosyaları (.ssa, .ass, .sty)", "primary": "Birincil", "secondary": "İkincil", "applyOverrideTags": "Özel etiketleri uygula", From d38628c24e73b483088c375f20a79db46907a161 Mon Sep 17 00:00:00 2001 From: niksedk Date: Sun, 11 Oct 2026 09:28:03 +0200 Subject: [PATCH 059/306] Update English.json (generated from test) Co-Authored-By: Claude Opus 5.5 --- src/ui/Assets/Languages/English.json | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/ui/Assets/Languages/English.json b/src/ui/Assets/Languages/English.json index 3be4d1f8a2b..d1592fc2736 100644 --- a/src/ui/Assets/Languages/English.json +++ b/src/ui/Assets/Languages/English.json @@ -4220,6 +4220,10 @@ "crispEmbedSettingsTitle": "CrispEmbed settings", "crispEmbedDescription": "Local OCR engine with multiple model backends - download the engine and the models here.", "trainNOcrDatabase": "Train nOCR database...", + "optimizeNOcrDatabase": "Optimize nOCR database...", + "optimizeNOcrDatabaseQuestionXY": "Remove the lines that don't tell characters apart from similar characters in nOCR database \"{0}\"?\n\nThis makes the database smaller and OCR faster, usually with fewer errors. Takes up to a minute.\n\nA backup is saved as \"{1}\".", + "optimizingNOcrDatabaseXY": "Optimizing nOCR database... {0}/{1}", + "nOcrDatabaseOptimizedXYZ": "nOCR database \"{0}\" optimized: {1:#,##0} -> {2:#,##0} lines.", "startTraining": "Start training", "abortTraining": "Abort training", "trainingOptions": "Training options", From 1cb8ba93187212b705ff086721f749e273fbd4e6 Mon Sep 17 00:00:00 2001 From: bovirus <1262554+bovirus@users.noreply.github.com> Date: Sun, 11 Oct 2026 11:25:40 +0200 Subject: [PATCH 060/306] Update Italian.json --- src/ui/Assets/Languages/Italian.json | 127 ++++++++++++++++++++++++--- 1 file changed, 113 insertions(+), 14 deletions(-) diff --git a/src/ui/Assets/Languages/Italian.json b/src/ui/Assets/Languages/Italian.json index d0044324487..31e54582699 100644 --- a/src/ui/Assets/Languages/Italian.json +++ b/src/ui/Assets/Languages/Italian.json @@ -1,7 +1,7 @@ { "title": "SubtitleEdit", - "version": "v. 5.3.0-beta26", - "translatedBy": "v. 09.10.2026 di bovirus e GitGianluc", + "version": "v. 5.3.0-rc2", + "translatedBy": "v. 11.10.2026 di bovirus e GitGianluc", "cultureName": "it-IT", "general": { "abort": "Annulla", @@ -18,7 +18,7 @@ "stylePickerFilterHint": "Digita per filtrare o digita un nuovo nome stile", "stylePickerKeysHint": "Per impostare lo stile su tutte le linee selezionate premi un numero (1-9, 0).", "stylePickerNewStyleX": "Nuovo stile '{0}'", - "stylePickerNewStyleInfo": "Premi Invio per aggiungere questo stile con le impostazioni predefinite e impostarlo sulle linee selezionate. Per modificare in un secondo momento la font, i colori e la posizione usa \"Gestisci stili...\".", + "stylePickerNewStyleInfo": "Premi Invio per aggiungere questo stile con le impostazioni predefinite e impostarlo sulle linee selezionate. Per modificare in un secondo momento la font, i colori e la posizione usa 'Gestisci stili...'.", "stylePickerCurrentStyleX": "Stile attuale: {0}", "stylePickerLinesUsingStyleX": "Righe che usano questo stile: {0}", "stylePickerMarginsXYZ": "Sinistra {0}, destra {1}, verticale {2}", @@ -595,6 +595,8 @@ "showStartColumn": "Visualizza colonna 'Inizio'.", "showHideColumn": "Visualizza colonna 'Nascondi'.", "showHistory": "Visualizza cronologia", + "clearHistory": "Cancella cronologia", + "removeFromHistory": "Rimuovi dalla cronologia", "showLayerColumn": "Visualizza colonna 'Livello'.", "showPreview": "Visualizza anteprima", "showShotChangesList": "Visualizza elenco modifiche alle riprese", @@ -890,6 +892,7 @@ "multipleReplace": "Sostituzione _multipla...", "fillSelectedLinesWithClipboard": "Riempi righe selezionate con testo degli appunti", "goToLineNumber": "_Vai alla riga numero...", + "commandPalette": "_Palette comandi...", "rightToLeftMode": "Modal_ità da Destra-a-Sinistra (DTS)", "modifySelectionDotDotDot": "Modifica _selezione...", "tools": "S_trumenti", @@ -1082,6 +1085,9 @@ "charactersPerSecond": "Caratteri/secondo: {0}", "chooseColumn": "Scegli colonna", "columnPaste": "Incolla colonna", + "commandPaletteTitle": "Palette comandi", + "commandPaletteSearchPlaceholder": "Digita per cercare i comandi...", + "commandPaletteNoCommandsFound": "Nessun comando corrispondente", "createdEmptyTranslation": "Creata traduzione vuota dal sottotitolo attuale", "deleteText": "Elimina testo", "deleteTextAndShiftCellsUp": "Elimina il testo e sposta le celle verso l'alto", @@ -2443,7 +2449,7 @@ "frameRateFromVideo": "Frequenza fotogrammi video", "frameRateCurrent": "Frequenza fotogrammi attuale", "frameRateSetManually": "Frequenza fotogrammi impostata manualmente", - "frameRateUsageInfo": "Le esportazioni Blu-ray sup, BDN XML, DOST e FCP vengono scritte con questa frequenza fotogrammi.\r\nNon cambia nessuna volta: per questo usa \"Modifica frequenza fotogrammi\".", + "frameRateUsageInfo": "Le esportazioni Blu-ray sup, BDN XML, DOST e FCP vengono scritte con questa frequenza fotogrammi.\r\nNon cambia nessuna volta: per questo usa 'Modifica frequenza fotogrammi'.", "useVideoFrameRateXInsteadOfY": "La frequenza fotogrammi del video è {0}, ma i sottotitoli sono impostati su {1}.\r\nVuoi usare la frequenza fotogrammi del video ({0})?" }, "removeTextForHearingImpaired": { @@ -2687,7 +2693,7 @@ "instructionsHint": "Stile conversazione opzionale, ad es. 'Parla con voce calma e calda'. Inviato solo ai modelli che lo supportano (non tts-1/tts-1-hd)", "openAiSpeedHint": "Velocità parlato da 0,25 a 4,0 (1,0 è normale). Non tutti i modelli lo supportano.", "outputFormat": "Formato destinazione", - "outputFormatHint": "Formato audio richiesto dal server.\r\n\"Automatico\" richiede MP3 e passa a PCM per i modelli che restituiscono solo PCM (ad esempio Gemini TTS)", + "outputFormatHint": "Formato audio richiesto dal server.\r\n'Automatico' richiede MP3 e passa a PCM per i modelli che restituiscono solo PCM (ad esempio Gemini TTS)", "regenerateAudio": "Rigenera audio", "autoContinuePlaying": "Continua automaticamente la riproduzione", "playLine": "Riproduci riga", @@ -3564,6 +3570,7 @@ "textBoxLimitNewLines": "Limita il numero di righe nella casella testo sottotitoli", "mpvOpenGl": "libmpv - OpenGL", "mpvSoftwareRendering": "libmpv - rendering software (lento)", + "mpvMetalRendering": "libmpv - Metal (sperimentale, necessita di riavvio)", "mpvWidRendering": "libmpv - rendering ID finestra nativa", "waveFormsAndSpectrogramFoldersContainsX": "Le cartelle 'Forme d'onda' e 'Spettrogramma' contengono {0}", "deleteWaveformAndSpectrogramFoldersQuestion": "Vuoi eliminare i file 'Forme d'onda' e 'Spettrogramma'?", @@ -4213,17 +4220,34 @@ "crispEmbedSettingsTitle": "Impostazioni CrispEmbed", "crispEmbedDescription": "Motore OCR locale con backend modelli multipli: scarica qui il motore e i modelli.", "trainNOcrDatabase": "Addestra database nOCR", + "optimizeNOcrDatabase": "Ottimizza database nOCR...", + "optimizeNOcrDatabaseQuestionXY": "Vuoi rimuovere le rrighe che non distinguono i caratteri da caratteri simili nel database nOCR '{0}'?\r\n\r\nCiò rende il database più piccolo e l'OCR più veloce, in genere con meno errori.\r\n\r\nRichiede fino a un minuto.\r\n\r\nVerrà creato un backup come '{1}'.", + "optimizingNOcrDatabaseXY": "Ottimizzazione database nOCR... {0}/{1}", + "nOcrDatabaseOptimizedXYZ": "Database nOCR '{0}' ottimizzato: {1:#,##0} -> {2:#,##0} righe.", "startTraining": "Inizia addestramento", "abortTraining": "Interrompi addestramento", "trainingOptions": "Opzioni addestramento", + "trainNOcrSubtitle": "Renderizza i caratteri dei font installati e insegna a nOCR le loro forme. Scegli i font usati dai sottotitoli.", + "xFontsSelected": "{0:#,##0} selezionato", + "subtitleFonts": "Font sottotitoli", + "emptyDatabase": "Database vuoto", + "startFromDatabase": "Inizia da", + "startFromDatabaseHint": "Aggiungi i caratteri addestrati a una copia di un database esistente (consigliato) o inizia da uno vuoto", + "fontSizes": "Dimensioni font", + "fontSizesHint": "Dimensioni font in pixel di cui eseguire il rendering, separati da virgole, ad es.\r\n'30, 40'", + "numberOfLineSegmentsHint": "Righe controllate per carattere: con valore più alto è più puntuale ma più lento", + "enterDatabaseName": "Inserisci un nome per il database", + "trainingFontXSizeYCharacterZ": "Apprendimento '{0}' a {1} px: {2}", + "xLearned": "{0:#,##0} appreso", + "xSkipped": "{0:#,##0} saltato", "charactersToTrain": "Caratteri da addestrare", - "letterCombinationsToTrain": "Combinazioni di lettere che potrebbero essere unite in una sola immagine", - "importCharactersFromSubtitleFile": "Importa caratteri da file sottotitoli...", - "numberOfLineSegments": "Numero segmenti di riga", - "alsoTrainBold": "Addestra anche grassetto", - "alsoTrainItalic": "Addestra anche corsivo", - "trainingXLearnedYSkipped": "Addestramento di '{0}' in corso - {1:#,##0} caratteri appresi, {2:#,##0} ignorati", - "trainingDoneXLearned": "Addestramento completato - {0:#,##0} caratteri appresi", + "letterCombinationsToTrain": "Combinazioni di lettere che potrebbero essere divise in un'unica immagine", + "importCharactersFromSubtitleFile": "Importa caratteri dal file sottotitoli...", + "numberOfLineSegments": "Numero segmenti riga", + "alsoTrainBold": "Addestra anche con il grassetto", + "alsoTrainItalic": "Addestra anche con il corsivo", + "trainingXLearnedYSkipped": "Addestramento '{0}' - {1:#,##0} caratteri appresi, {2:#,##0} saltati", + "trainingDoneXLearned": "Addestramento completato: {0:#,##0} caratteri appresi", "selectAtLeastOneFont": "Seleziona almeno un carattere" }, "assa": { @@ -4251,11 +4275,86 @@ "drawSelectedLayer": "Livello selezionato", "drawToolX": "Strumento: {0}", "drawHelpText": "Clic per aggiungere punti • Invio/F8 per chiudere la forma • Maiusc+trascina per eseguire la panoramica • Ctrl+scorri per ingrandire", + "drawMoveToLayer": "Passa al livello", + "drawFlipHorizontal": "Capovolgi orizzontalmente", + "drawFlipVertical": "Capovolgi verticalmente", + "drawHideShape": "Nascondi forma", + "drawShowShape": "Visualizza forma", + "drawHideLayer": "Nascondi livello", + "drawShowLayer": "Visualizza livello", + "drawDeleteLayer": "Elimina livello", + "drawDeletePoint": "Elimina punto", + "drawCancelDrawing": "Annulla disegno", + "drawLayerX": "Livello {0}", + "drawImportSvg": "Importa immagine SVG...", + "drawRotateClockwise": "Ruota di 90° in senso orario", + "drawConvertToCurve": "Converti segmento in curva", + "drawBackground": "Sfondo", + "drawPosition": "Posizione", + "drawZoom": "Zoom", + "drawShapeTool": "Strumento forma", + "drawColorPickerTool": "Scegli il colore da una forma o dallo sfondo", + "drawShapeLibrary": "Libreria forme", + "drawShapeLibraryHint": "Per posizionare una forma trascina sull'area di disegno o fai clic per ottenere la dimensione predefinita. 'Maiusc' mantiene le sue proporzioni.", + "drawAddToLibrary": "Aggiungi alla libreria forme...", + "drawRemoveFromLibrary": "Rimuovi dalla libreria forme", + "drawShapeName": "Nome forma", + "drawCategorySpeechBubbles": "Nuvoletta", + "drawCategoryArrows": "Frecce", + "drawCategoryBasicShapes": "Forme basilari", + "drawCategorySymbols": "Simboli", + "drawCategoryMyShapes": "Le mie forme", + "drawShapeSpeechBubble": "Nuvoletta", + "drawShapeSpeechBubbleRight": "Nuvoletta, coda a destra", + "drawShapeSpeechBubbleUp": "Nuvoletta, coda in su", + "drawShapeOvalBubble": "Nuvoletta ovale", + "drawShapeBoxBubble": "Nuvoletta quadrata", + "drawShapeThoughtBubble": "Nuvoletta pensiero", + "drawShapeShoutBubble": "Nuvoletta urlo", + "drawShapeCaptionBox": "Casella didascalia", + "drawShapeArrow": "Freccia", + "drawShapeDoubleArrow": "Doppia freccia", + "drawShapeChevron": "Freccia a V", + "drawShapeCurvedArrow": "Freccia curva", + "drawShapeRoundedRectangle": "Rettangolo arrotondato", + "drawShapeTriangle": "Triangolo", + "drawShapeDiamond": "Diamante", + "drawShapePentagon": "Pentagono", + "drawShapeHexagon": "Esagono", + "drawShapeOctagon": "Ottagono", + "drawShapeStar": "Stella", + "drawShapeRing": "Anello", + "drawShapePlus": "Più", + "drawShapeBanner": "Striscione", + "drawShapeHeart": "Cuore", + "drawShapeCheck": "Segno di spunta", + "drawShapeMusicNote": "Nota musicale", + "drawShapeMusicNotes": "Note musicali", + "drawShapeLightning": "Fulmine", + "drawShapeCloud": "Nuvola", + "drawShapeMoon": "Luna", + "drawShapeBadge": "Badge", + "drawSize": "Dimensione", + "drawLayer": "Strato", + "drawBackgroundVideoFrame": "Fotogramma video (posizione attuale)", + "drawBackgroundVideoFrameAt": "Fotogramma video a...", + "drawBackgroundImage": "File immagine...", + "drawBackgroundNone": "Nessuno sfondo", + "drawBackgroundStretch": "Allunga l'immagine alla tela", + "drawBackgroundOpacity": "Opacità sfondo", + "drawBackgroundVideoFrameFailed": "Impossibile ottenere un fotogramma video a {0}.", + "drawImages": "Immagini", + "drawConvertToLine": "Converti segmento in linea", + "drawConvertShapeToCurves": "Converti tutte le linee in curve", + "drawConvertShapeToLines": "Converti tutte le curve in linee", + "drawRotateCounterClockwise": "Ruota di 90° in senso antiorario", + "drawSvgImages": "Immagini SVG", + "drawSvgNoShapes": "Nessuna forma piena o tratto trovato nel file SVG.", "progressBarTitle": "Barra avanzamento ASSA", - "progressBarSettings": "Barra di avanzamento", + "progressBarSettings": "Barra avanzamento", "mouseOverColor": "Colore al passaggio del mouse", "clickedColor": "Colore elemento cliccato", - "progressBarForeColor": "Colore di primo piano", + "progressBarForeColor": "Colore primo piano", "progressBarSquareCorners": "Angoli squadrati", "progressBarRoundedCorners": "Angoli arrotondati", "progressBarChapters": "Capitoli", From 75747d54b69bc0338699940395dfa49bafebe056 Mon Sep 17 00:00:00 2001 From: Adam Malich <117507909+potplayer-fanpack@users.noreply.github.com> Date: Sun, 11 Oct 2026 12:45:43 +0200 Subject: [PATCH 061/306] Update Polish.json --- src/ui/Assets/Languages/Polish.json | 32 ++++++++++++++++++++++++++--- 1 file changed, 29 insertions(+), 3 deletions(-) diff --git a/src/ui/Assets/Languages/Polish.json b/src/ui/Assets/Languages/Polish.json index 8b3fa1bb020..943fb06460b 100644 --- a/src/ui/Assets/Languages/Polish.json +++ b/src/ui/Assets/Languages/Polish.json @@ -1,7 +1,7 @@ { "title": "Subtitle Edit", - "version": "v5.3.0-rc1", - "translatedBy": "Adam Malich (ostatnia aktualizacja: 10.10.2026)", + "version": "v5.3.0-rc2", + "translatedBy": "Adam Malich (ostatnia aktualizacja: 11.10.2026)", "cultureName": "pl-PL", "general": { "abort": "Przerwij", @@ -18,7 +18,7 @@ "stylePickerFilterHint": "Wpisz tekst, aby zastosować filtr, lub wpisz nazwę nowego stylu", "stylePickerKeysHint": "Naciśnij cyfrę (1–9, 0), aby ustawić styl dla wszystkich zaznaczonych linii.", "stylePickerNewStyleX": "Nowy styl \"{0}\"", - "stylePickerNewStyleInfo": "Naciśnij klawisz Enter, aby dodać ten styl z ustawieniami domyślnymi i zastosować go do zaznaczonych linii. Użyj \"Zarządzaj stylami...\" aby później zmienić jego czcionkę, kolory i położenie.", + "stylePickerNewStyleInfo": "Naciśnij klawisz Enter, aby dodać ten styl z ustawieniami domyślnymi i zastosować go do zaznaczonych linii. Aby później zmienić jego czcionkę, kolory i położenie, użyj \"Zarządzaj stylami...\".", "stylePickerCurrentStyleX": "Bieżący styl: {0}", "stylePickerLinesUsingStyleX": "Linie wykorzystujące ten styl: {0}", "stylePickerMarginsXYZ": "Lewo {0}, prawo {1}, pionowo {2}", @@ -595,6 +595,8 @@ "showStartColumn": "Pokaż kolumnę \"Początek\"", "showHideColumn": "Pokaż kolumnę \"Ukryj\"", "showHistory": "Pokaż historię", + "clearHistory": "Wyczyść historię", + "removeFromHistory": "Usuń z historii", "showLayerColumn": "Pokaż kolumnę \"Warstwa\"", "showPreview": "Pokaż podgląd", "showShotChangesList": "Pokaż listę zmian ujęcia", @@ -890,6 +892,7 @@ "multipleReplace": "_Wielokrotne zastąpienie...", "fillSelectedLinesWithClipboard": "Wypełnij zaznaczone linie tekstem ze schowka", "goToLineNumber": "_Przejdź do numeru linii...", + "commandPalette": "Zestaw _poleceń...", "rightToLeftMode": "Tryb od prawej do _lewej (PDL)", "modifySelectionDotDotDot": "Modyfikuj _zaznaczenie...", "tools": "_Narzędzia", @@ -1082,6 +1085,9 @@ "charactersPerSecond": "Znaków/sekundę: {0}", "chooseColumn": "Wybierz kolumnę", "columnPaste": "Wklej kolumnę", + "commandPaletteTitle": "Zestaw poleceń", + "commandPaletteSearchPlaceholder": "Wpisz, aby wyszukać polecenia...", + "commandPaletteNoCommandsFound": "Brak pasujących poleceń", "createdEmptyTranslation": "Utworzono puste tłumaczenie z bieżącego napisu", "deleteText": "Usuń tekst", "deleteTextAndShiftCellsUp": "Usuń tekst i przesuń komórki w górę", @@ -3564,6 +3570,7 @@ "textBoxLimitNewLines": "Ogranicz liczbę linii w polu tekstowym napisów", "mpvOpenGl": "libmpv - OpenGL", "mpvSoftwareRendering": "libmpv - renderowanie programowe (wolno)", + "mpvMetalRendering": "libmpv - Metal (eksperymentalne, wymaga ponownego uruchomienia)", "mpvWidRendering": "libmpv - renderowanie Native Window ID", "waveFormsAndSpectrogramFoldersContainsX": "Foldery \"Waveforms\" i \"Spectrogram\" zawierają {0}", "deleteWaveformAndSpectrogramFoldersQuestion": "Usunąć pliki \"Waveforms\" i \"Spectrogram\"?", @@ -4213,9 +4220,26 @@ "crispEmbedSettingsTitle": "Ustawienia CrispEmbed", "crispEmbedDescription": "Lokalny silnik OCR z obsługą wielu modeli – silnik i modele można pobrać tutaj.", "trainNOcrDatabase": "Trenowanie bazy danych nOCR...", + "optimizeNOcrDatabase": "Optymalizuj bazę danych nOCR...", + "optimizeNOcrDatabaseQuestionXY": "Czy usunąć linie, które nie pozwalają odróżnić znaków od podobnych znaków w bazie danych nOCR \"{0}\"?\n\nDzięki temu baza danych jest mniejsza, a proces OCR przebiega szybciej i zazwyczaj z mniejszą liczbą błędów. Zajmie to najwyżej minutę.\n\nKopia zapasowa jest zapisywana jako \"{1}\".", + "optimizingNOcrDatabaseXY": "Optymalizacja bazy danych nOCR... {0}/{1}", + "nOcrDatabaseOptimizedXYZ": "Baza danych nOCR \"{0}\" zoptymalizowana: {1:#,##0} -> {2:#,##0} linii.", "startTraining": "Rozpocznij trening", "abortTraining": "Przerwij trening", "trainingOptions": "Opcje treningowe", + "trainNOcrSubtitle": "Renderuje znaki przy użyciu zainstalowanych czcionek i uczy program nOCR ich kształtów. Wybierz czcionki używane w napisach.", + "xFontsSelected": "Wybrano {0:#,##0}", + "subtitleFonts": "Czcionki napisów", + "emptyDatabase": "Pusta baza danych", + "startFromDatabase": "Zacznij od", + "startFromDatabaseHint": "Dodaj wytrenowane znaki do kopii istniejącej bazy danych (zalecane) lub zacznij od pustej bazy", + "fontSizes": "Rozmiary czcionek", + "fontSizesHint": "Rozmiary czcionek w pikselach, w jakich mają być renderowane, oddzielone przecinkami, np. \"30, 40\"", + "numberOfLineSegmentsHint": "Liczba sprawdzanych linii na znak – wyższa wartość oznacza bardziej rygorystyczną, ale wolniejszą kontrolę", + "enterDatabaseName": "Wstaw nazwę bazy danych", + "trainingFontXSizeYCharacterZ": "Trening \"{0}\" przy {1} px: {2}", + "xLearned": "Nauczone {0:#,##0}", + "xSkipped": "Pominięte {0:#,##0}", "charactersToTrain": "Znaki do trenowania", "letterCombinationsToTrain": "Kombinacje liter, które można podzielić na jeden obraz", "importCharactersFromSubtitleFile": "Importuj znaki z pliku z napisami...", @@ -4269,6 +4293,7 @@ "drawPosition": "Pozycja", "drawZoom": "Powiększ", "drawShapeTool": "Narzędzie do kształtów", + "drawColorPickerTool": "Wybierz kolor z kształtu lub tła", "drawShapeLibrary": "Biblioteka kształtów", "drawShapeLibraryHint": "Przeciągnij po obszarze roboczym, aby umieścić kształt, lub kliknij, aby uzyskać rozmiar domyślny. Klawisz Shift pozwala zachować proporcje.", "drawAddToLibrary": "Dodaj do biblioteki kształtów...", @@ -4317,6 +4342,7 @@ "drawBackgroundNone": "Bez tła", "drawBackgroundStretch": "Rozciągnij obraz do rozmiaru płótna", "drawBackgroundOpacity": "Nieprzezroczystość tła", + "drawBackgroundVideoFrameFailed": "Nie udało się pobrać klatki wideo w {0}.", "drawImages": "Obrazy", "drawConvertToLine": "Konwertuj odcinek na linię", "drawConvertShapeToCurves": "Konwertuj wszystkie linie na krzywe", From 84193eb4c1a1c038c423683f7cecd35a897280ca Mon Sep 17 00:00:00 2001 From: Nikolaj Olsson Date: Sun, 11 Oct 2026 13:11:37 +0200 Subject: [PATCH 062/306] Update Latin.nocr --- Ocr/Latin.nocr | Bin 501463 -> 504954 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/Ocr/Latin.nocr b/Ocr/Latin.nocr index e9019e3acebb7faa50871ccc9b9307b0f0cdc498..cf4d34d54178648c8161367dee07b47442ed7e7f 100644 GIT binary patch delta 502137 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 )|L)XdWyu^`v&U&a?bTSxthUnvZ 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
    PuSIIgXSY1G(}S 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

    Q4 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%0A+5cPc8$vX|sMfP)mLc1cUlzaIepuB#Ir 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�SQ*2VzVi>e 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 dQvkZSKGa*;6(g*L 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 delta 498609 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≷?^f#~3gV$fM={~JX@*sa8 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
      y9<7 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%0Ae~4$^mb@?_h+rIu!^;Zf8KGMfQokB&f_Uwm3UE) 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 Date: Sun, 11 Oct 2026 13:33:35 +0200 Subject: [PATCH 063/306] Fix all compilation warnings - Add missing docs (CS1573) in LanguageAutoDetect, Utilities, IfoParser - Null guard / annotations for CS8602 in TTS view model and speak-from-line reader - TextBox.Watermark -> PlaceholderText (obsolete) - Mark SeTableViewColumn.IsVisible as new (base TableViewColumn now has one) - Tests: restore Providers.OpenRouterApiKey (SetApiKey writes there, old field leaked "sk-or"), PNG encoder options, nullable annotations, fake engine ToString Co-Authored-By: Claude Opus 5.5 --- src/libse/Common/LanguageAutoDetect.cs | 4 ++++ src/libse/Common/Utilities.cs | 2 ++ src/libse/VobSub/IfoParser.cs | 2 ++ .../Video/TextToSpeech/SpeakFromLine/SpeakFromLineReader.cs | 2 +- src/ui/Features/Video/TextToSpeech/TextToSpeechViewModel.cs | 2 +- .../Video/TextToSpeech/VoiceManager/VoiceManagerWindow.cs | 2 +- src/ui/Logic/AccessibleLabels.cs | 4 ++-- src/ui/Logic/TableViewExtras.cs | 4 ++-- tests/UI/Controls/SkiaAudioVisualizerRenderTests.cs | 2 +- tests/UI/Features/Files/Compare/CompareEditTests.cs | 4 ++-- tests/UI/Features/Main/EditorStyleLayoutTests.cs | 2 +- .../Video/TextToSpeech/Engines/OpenAiCompatibleSpeechTests.cs | 4 ++-- .../VoiceManager/DownloadVoicePacksViewModelTests.cs | 1 + .../TextToSpeech/VoiceManager/VoiceManagerViewModelTests.cs | 1 + tests/UI/Logic/AccessibleNamesTests.cs | 4 ++-- tests/libse/ContainerFormats/TransportStreamTestWriter.cs | 2 +- .../libse/SubtitleFormats/NonSubtitlePrecheckRoundTripTest.cs | 2 +- 17 files changed, 27 insertions(+), 17 deletions(-) diff --git a/src/libse/Common/LanguageAutoDetect.cs b/src/libse/Common/LanguageAutoDetect.cs index 67d72c80007..dc42f628bb4 100644 --- a/src/libse/Common/LanguageAutoDetect.cs +++ b/src/libse/Common/LanguageAutoDetect.cs @@ -597,6 +597,8 @@ private static string AutoDetectGoogleLanguage(string text, int bestCount) return AutoDetectGoogleLanguage(text, bestCount, out _); } + /// The text to detect the language of. + /// The minimum number of keyword hits for a match. /// The number of keyword hits for the returned language. private static string AutoDetectGoogleLanguage(string text, int bestCount, out int score) { @@ -1371,6 +1373,8 @@ public static string AutoDetectGoogleLanguageFromWordsOrNull(Subtitle subtitle, /// and trimmed at the end - what a copy of the subtitle /// with RemoveEmptyLines and GetAllTexts gave, without deep-copying every paragraph. ///

    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%0AsGKw6K^+28Ju2m6#bHBxO{+%I)CT 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