msg_tool/
types.rs

1//! Basic types
2use clap::ValueEnum;
3use serde::{Deserialize, Serialize};
4use std::collections::HashMap;
5
6#[derive(Copy, Clone, Serialize, Deserialize, Debug)]
7#[serde(rename_all = "camelCase")]
8/// Text Encoding
9pub enum Encoding {
10    /// Automatically detect encoding
11    Auto,
12    /// UTF-8 encoding
13    Utf8,
14    /// Shift-JIS encoding
15    Cp932,
16    /// GB2312 encoding
17    Gb2312,
18    /// UTF-16 Little Endian encoding
19    Utf16LE,
20    /// UTF-16 Big Endian encoding
21    Utf16BE,
22    /// Code page encoding (Windows only)
23    #[cfg(windows)]
24    CodePage(u32),
25}
26
27impl Default for Encoding {
28    fn default() -> Self {
29        Encoding::Utf8
30    }
31}
32
33impl Encoding {
34    /// Returns true if the encoding is Shift-JIS (CP932).
35    pub fn is_jis(&self) -> bool {
36        match self {
37            Self::Cp932 => true,
38            #[cfg(windows)]
39            Self::CodePage(code_page) => *code_page == 932,
40            _ => false,
41        }
42    }
43
44    /// Returns true if the encoding is UTF-16LE.
45    pub fn is_utf16le(&self) -> bool {
46        match self {
47            Self::Utf16LE => true,
48            #[cfg(windows)]
49            Self::CodePage(code_page) => *code_page == 1200,
50            _ => false,
51        }
52    }
53
54    /// Returns true if the encoding is UTF-16BE.
55    pub fn is_utf16be(&self) -> bool {
56        match self {
57            Self::Utf16BE => true,
58            #[cfg(windows)]
59            Self::CodePage(code_page) => *code_page == 1201,
60            _ => false,
61        }
62    }
63
64    /// Returns true if the encoding is UTF8.
65    pub fn is_utf8(&self) -> bool {
66        match self {
67            Self::Utf8 => true,
68            #[cfg(windows)]
69            Self::CodePage(code_page) => *code_page == 65001,
70            _ => false,
71        }
72    }
73
74    /// Returns the charset name
75    pub fn charset(&self) -> Option<&'static str> {
76        match self {
77            Self::Auto => None,
78            Self::Utf8 => Some("UTF-8"),
79            Self::Cp932 => Some("shift_jis"),
80            Self::Gb2312 => Some("gbk"),
81            Self::Utf16LE => Some("utf-16le"),
82            Self::Utf16BE => Some("utf-16be"),
83            #[cfg(windows)]
84            Self::CodePage(code_page) => match *code_page {
85                932 => Some("shift_jis"),
86                65001 => Some("utf-8"),
87                1200 => Some("utf-16le"),
88                936 => Some("gbk"),
89                _ => None,
90            },
91        }
92    }
93}
94
95#[derive(Clone, Copy, Debug, ValueEnum, PartialEq, Eq, PartialOrd, Ord)]
96/// Text Encoding (for CLI)
97pub enum TextEncoding {
98    /// Use script's default encoding
99    Default,
100    /// Automatically detect encoding
101    Auto,
102    /// UTF-8 encoding
103    Utf8,
104    #[value(alias("jis"))]
105    /// Shift-JIS encoding
106    Cp932,
107    #[value(alias("gbk"))]
108    /// GB2312 encoding
109    Gb2312,
110}
111
112#[derive(Clone, Copy, Debug, ValueEnum, PartialEq, Eq, PartialOrd, Ord)]
113/// Script type
114pub enum OutputScriptType {
115    /// Text script
116    M3t,
117    /// Same as M3t, buf different extension
118    M3ta,
119    /// Same as M3t, buf different extension
120    M3tTxt,
121    /// JSON which can be used for GalTransl
122    Json,
123    /// YAML (same as JSON, but with YAML syntax)
124    Yaml,
125    /// Gettext .pot file
126    Pot,
127    /// Gettext .po file
128    Po,
129    /// Custom output
130    Custom,
131}
132
133impl OutputScriptType {
134    /// Returns true if the script type is custom.
135    pub fn is_custom(&self) -> bool {
136        matches!(self, OutputScriptType::Custom)
137    }
138
139    /// Returns true if the script type is M3t/M3ta/M3tTxt.
140    pub fn is_m3t(&self) -> bool {
141        matches!(
142            self,
143            OutputScriptType::M3t | OutputScriptType::M3ta | OutputScriptType::M3tTxt
144        )
145    }
146
147    /// Returns true if the script type supports source messages.
148    pub fn is_src_supported(&self) -> bool {
149        matches!(
150            self,
151            OutputScriptType::M3t
152                | OutputScriptType::M3ta
153                | OutputScriptType::M3tTxt
154                | OutputScriptType::Po
155                | OutputScriptType::Pot
156        )
157    }
158}
159
160impl AsRef<str> for OutputScriptType {
161    /// Returns the extension for the script type.
162    fn as_ref(&self) -> &str {
163        match self {
164            OutputScriptType::M3t => "m3t",
165            OutputScriptType::M3ta => "m3ta",
166            OutputScriptType::M3tTxt => "txt",
167            OutputScriptType::Json => "json",
168            OutputScriptType::Yaml => "yaml",
169            OutputScriptType::Pot => "pot",
170            OutputScriptType::Po => "po",
171            OutputScriptType::Custom => "",
172        }
173    }
174}
175
176#[cfg(feature = "circus")]
177#[derive(Clone, Copy, Debug, ValueEnum, PartialEq, Eq, PartialOrd, Ord)]
178/// Circus MES game
179pub enum CircusMesType {
180    /// fortissimo//Akkord:Bsusvier
181    Ffexa,
182    /// fortissimo EXS//Akkord:nächsten Phase
183    Ffexs,
184    /// Eternal Fantasy
185    Ef,
186    /// D.C.〜ダ・カーポ〜 温泉編
187    Dcos,
188    /// ことり Love Ex P
189    Ktlep,
190    /// D.C.WhiteSeason
191    Dcws,
192    /// D.C. Summer Vacation
193    Dcsv,
194    /// D.C.P.C.(Vista)
195    Dcpc,
196    /// D.C.〜ダ・カーポ〜 MEMORIES DISC
197    Dcmems,
198    /// D.C. Dream X’mas
199    Dcdx,
200    /// D.C.A.S. 〜ダ・カーポ〜アフターシーズンズ
201    Dcas,
202    /// D.C.II 春風のアルティメットバトル!
203    Dcbs,
204    /// D.C.II Fall in Love
205    Dc2fl,
206    /// D.C.II 春風のアルティメットバトル!
207    Dc2bs,
208    /// D.C.II Dearest Marriage
209    Dc2dm,
210    /// D.C.II 〜featuring Yun2〜
211    Dc2fy,
212    /// D.C.II C.C. 月島小恋のらぶらぶバスルーム
213    Dc2cckko,
214    /// D.C.II C.C. 音姫先生のどきどき特別授業
215    Dc2ccotm,
216    /// D.C.II Spring Celebration
217    Dc2sc,
218    /// D.C.II To You
219    Dc2ty,
220    /// D.C.II P.C.
221    Dc2pc,
222    /// D.C.III RX-rated
223    Dc3rx,
224    /// D.C.III P.P.~ダ・カーポIII プラチナパートナー~
225    Dc3pp,
226    /// D.C.III WithYou
227    Dc3wy,
228    /// D.C.III DreamDays
229    Dc3dd,
230    /// D.C.4 ~ダ・カーポ4~
231    Dc4,
232    /// D.C.4 Plus Harmony 〜ダ・カーポ4〜 プラスハーモニー
233    Dc4ph,
234    /// D.S. -Dal Segno-
235    Ds,
236    /// D.S.i.F. -Dal Segno- in Future
237    Dsif,
238    /// てんぷれ!
239    Tmpl,
240    /// 百花百狼/Hyakka Hyakurou
241    Nightshade,
242}
243
244#[cfg(feature = "circus")]
245impl AsRef<str> for CircusMesType {
246    /// Returns the name.
247    fn as_ref(&self) -> &str {
248        match self {
249            CircusMesType::Ffexa => "ffexa",
250            CircusMesType::Ffexs => "ffexs",
251            CircusMesType::Ef => "ef",
252            CircusMesType::Dcos => "dcos",
253            CircusMesType::Ktlep => "ktlep",
254            CircusMesType::Dcws => "dcws",
255            CircusMesType::Dcsv => "dcsv",
256            CircusMesType::Dcpc => "dcpc",
257            CircusMesType::Dcmems => "dcmems",
258            CircusMesType::Dcdx => "dcdx",
259            CircusMesType::Dcas => "dcas",
260            CircusMesType::Dcbs => "dcbs",
261            CircusMesType::Dc2fl => "dc2fl",
262            CircusMesType::Dc2bs => "dc2bs",
263            CircusMesType::Dc2dm => "dc2dm",
264            CircusMesType::Dc2fy => "dc2fy",
265            CircusMesType::Dc2cckko => "dc2cckko",
266            CircusMesType::Dc2ccotm => "dc2ccotm",
267            CircusMesType::Dc2sc => "dc2sc",
268            CircusMesType::Dc2ty => "dc2ty",
269            CircusMesType::Dc2pc => "dc2pc",
270            CircusMesType::Dc3rx => "dc3rx",
271            CircusMesType::Dc3pp => "dc3pp",
272            CircusMesType::Dc3wy => "dc3wy",
273            CircusMesType::Dc3dd => "dc3dd",
274            CircusMesType::Dc4 => "dc4",
275            CircusMesType::Dc4ph => "dc4ph",
276            CircusMesType::Ds => "ds",
277            CircusMesType::Dsif => "dsif",
278            CircusMesType::Tmpl => "tmpl",
279            CircusMesType::Nightshade => "nightshade",
280        }
281    }
282}
283
284/// Extra configuration options for the script.
285#[derive(Debug, Clone, msg_tool_macro::Default)]
286pub struct ExtraConfig {
287    #[cfg(feature = "circus")]
288    /// Circus Game for circus MES script.
289    pub circus_mes_type: Option<CircusMesType>,
290    #[cfg(feature = "escude-arc")]
291    /// Whether to use fake compression for Escude archive
292    pub escude_fake_compress: bool,
293    #[cfg(feature = "escude")]
294    /// The path to the Escude enum script file (enum_scr.bin)
295    pub escude_enum_scr: Option<String>,
296    #[cfg(feature = "bgi")]
297    /// Duplicate same strings when importing into BGI scripts.
298    /// Enable this will cause BGI scripts to become very large.
299    pub bgi_import_duplicate: bool,
300    #[cfg(feature = "bgi")]
301    /// Disable appending new strings to the end of BGI scripts.
302    /// Disable may cause BGI scripts broken.
303    pub bgi_disable_append: bool,
304    #[cfg(feature = "image")]
305    /// Output image type
306    pub image_type: Option<ImageOutputType>,
307    #[cfg(all(feature = "bgi-arc", feature = "bgi-img"))]
308    /// Detect all files in BGI archive as SysGrp Images. By default, only files which name is `sysgrp.arc` will enabled this.
309    pub bgi_is_sysgrp_arc: Option<bool>,
310    #[cfg(feature = "bgi-img")]
311    /// Whether to create scrambled SysGrp images. When in import mode, the default value depends on the original image.
312    /// When in creation mode, it is not enabled by default.
313    pub bgi_img_scramble: Option<bool>,
314    #[cfg(feature = "cat-system-arc")]
315    /// CatSystem2 engine int archive password
316    pub cat_system_int_encrypt_password: Option<String>,
317    #[cfg(feature = "cat-system-img")]
318    /// Draw CatSystem2 image on canvas (if canvas width and height are specified in file)
319    pub cat_system_image_canvas: bool,
320    #[cfg(feature = "kirikiri")]
321    /// Kirikiri language index in script. If not specified, the first language will be used.
322    pub kirikiri_language_index: Option<usize>,
323    #[cfg(feature = "kirikiri")]
324    /// Export chat message to extra json file. (for Kirikiri SCN script.)
325    /// For example, CIRCUS's comu message. Yuzusoft's phone chat message.
326    pub kirikiri_export_chat: bool,
327    #[cfg(feature = "kirikiri")]
328    /// Kirikiri chat message key. For example, CIRCUS's key is "comumode". Yuzusoft's key is "phonechat".
329    /// If not specified, "comumode" will be used.
330    pub kirikiri_chat_key: Option<Vec<String>>,
331    #[cfg(feature = "kirikiri")]
332    /// Kirikiri chat message translation. The outter object's key is filename(`global` is a special key).
333    /// The inner object: key is original text, value is (translated text, original text count).
334    pub kirikiri_chat_json:
335        Option<std::sync::Arc<HashMap<String, HashMap<String, (String, usize)>>>>,
336    #[cfg(feature = "kirikiri")]
337    /// Kirikiri language list. First language code is code for language index 1.
338    pub kirikiri_languages: Option<std::sync::Arc<Vec<String>>>,
339    #[cfg(feature = "kirikiri")]
340    /// Remove empty lines in Kirikiri KS script.
341    pub kirikiri_remove_empty_lines: bool,
342    #[cfg(feature = "kirikiri")]
343    /// Kirikiri name commands, used to extract names from ks script.
344    pub kirikiri_name_commands: std::sync::Arc<std::collections::HashSet<String>>,
345    #[cfg(feature = "kirikiri")]
346    /// Kirikiri message commands, used to extract more message from ks script.
347    pub kirikiri_message_commands: std::sync::Arc<std::collections::HashSet<String>>,
348    #[cfg(feature = "bgi-arc")]
349    /// Whether to compress files in BGI archive when packing BGI archive.
350    pub bgi_compress_file: bool,
351    #[cfg(feature = "bgi-arc")]
352    #[default(9)]
353    /// Compress level for BGI Dsc file. 0 means store, 10 mean best compression.
354    /// 10 will use zopfli like compression method, this may cost a lot of time.
355    pub bgi_compress_level: u8,
356    #[cfg(feature = "emote-img")]
357    /// Whether to overlay PIMG images. (By default, true if all layers are not group layers.)
358    pub emote_pimg_overlay: Option<bool>,
359    #[cfg(feature = "artemis-arc")]
360    /// Disable Artemis archive (.pfs) XOR encryption when packing.
361    pub artemis_arc_disable_xor: bool,
362    #[cfg(feature = "artemis")]
363    /// Artemis script indent size, used to format Artemis script.
364    /// Default is 4 spaces.
365    pub artemis_indent: Option<usize>,
366    #[cfg(feature = "artemis")]
367    /// Disable Artemis script indent, used to format Artemis script.
368    pub artemis_no_indent: bool,
369    #[cfg(feature = "artemis")]
370    #[default(100)]
371    /// Max line width in Artemis script, used to format Artemis script.
372    pub artemis_max_line_width: usize,
373    #[cfg(feature = "artemis")]
374    /// Specify the language of Artemis AST script.
375    /// If not specified, the first language will be used.
376    pub artemis_ast_lang: Option<String>,
377    #[cfg(feature = "cat-system")]
378    /// CatSystem2 CSTL script language, used to extract messages from CSTL script.
379    /// If not specified, the first language will be used.
380    pub cat_system_cstl_lang: Option<String>,
381    #[cfg(feature = "flate2")]
382    #[default(6)]
383    /// Zlib compression level. 0 means no compression, 9 means best compression.
384    pub zlib_compression_level: u32,
385    #[cfg(feature = "image")]
386    /// PNG compression level.
387    pub png_compression_level: PngCompressionLevel,
388    #[cfg(feature = "circus-img")]
389    /// Keep original BPP when importing Circus CRX images.
390    pub circus_crx_keep_original_bpp: bool,
391    #[cfg(feature = "circus-img")]
392    /// Use zstd compression for Circus CRX images. (CIRCUS Engine don't support this. Hook is required.)
393    pub circus_crx_zstd: bool,
394    #[cfg(feature = "zstd")]
395    #[default(3)]
396    /// Zstd compression level. 0 means default compression level (3), 22 means best compression.
397    pub zstd_compression_level: i32,
398    #[cfg(feature = "circus-img")]
399    /// Circus CRX image row type mode
400    pub circus_crx_mode: crate::scripts::circus::image::crx::CircusCrxMode,
401    #[cfg(feature = "ex-hibit")]
402    /// ExHibit xor key for rld script.
403    /// Use [ReExHIBIT](https://github.com/ZQF-ReVN/RxExHIBIT) to find the key.
404    pub ex_hibit_rld_xor_key: Option<u32>,
405    #[cfg(feature = "ex-hibit")]
406    /// ExHibit def.rld xor key.
407    pub ex_hibit_rld_def_xor_key: Option<u32>,
408    #[cfg(feature = "ex-hibit")]
409    /// ExHibit rld xor keys.
410    pub ex_hibit_rld_keys: Option<Box<[u32; 0x100]>>,
411    #[cfg(feature = "ex-hibit")]
412    /// ExHibit def.rld xor keys.
413    pub ex_hibit_rld_def_keys: Option<Box<[u32; 0x100]>>,
414    #[cfg(feature = "mozjpeg")]
415    #[default(80)]
416    /// JPEG quality for output images, 0-100. 100 means best quality.
417    pub jpeg_quality: u8,
418    #[cfg(feature = "webp")]
419    /// Use WebP lossless compression for output images.
420    pub webp_lossless: bool,
421    #[cfg(feature = "webp")]
422    #[default(80)]
423    /// WebP quality for output images, 0-100. 100 means best quality.
424    pub webp_quality: u8,
425    #[cfg(feature = "circus-img")]
426    /// Draw Circus CRX images on canvas (if canvas width and height are specified in file)
427    pub circus_crx_canvas: bool,
428    /// Try use YAML format instead of JSON when custom exporting.
429    pub custom_yaml: bool,
430    #[cfg(feature = "entis-gls")]
431    /// Entis GLS srcxml script language, used to extract messages from srcxml script.
432    /// If not specified, the first language will be used.
433    pub entis_gls_srcxml_lang: Option<String>,
434    #[cfg(feature = "will-plus")]
435    /// Disable disassembly for WillPlus ws2 script.
436    /// Use another parser to parse the script.
437    /// Should only be used when the default parser not works well.
438    pub will_plus_ws2_no_disasm: bool,
439    #[cfg(feature = "artemis-panmimisoft")]
440    /// Artemis Engine blacklist tag names for TXT script.
441    /// This is used to ignore these tags when finding names in Artemis TXT (ぱんみみそふと) script.
442    pub artemis_panmimisoft_txt_blacklist_names: std::sync::Arc<std::collections::HashSet<String>>,
443    #[cfg(feature = "artemis-panmimisoft")]
444    /// Specify the language of Artemis TXT (ぱんみみそふと) script.
445    /// If not specified, the first language will be used.
446    pub artemis_panmimisoft_txt_lang: Option<String>,
447    #[cfg(feature = "artemis-panmimisoft")]
448    /// Enable multiple language support for single language Artemis TXT (ぱんみみそふと) script.
449    /// artemis_panmimisoft_txt_lang must be set when enabling this.
450    pub artemis_panmimisoft_txt_multi_lang: bool,
451    #[cfg(feature = "lossless-audio")]
452    /// Audio format for output lossless audio files.
453    pub lossless_audio_fmt: LosslessAudioFormat,
454    #[cfg(feature = "audio-flac")]
455    #[default(5)]
456    /// FLAC compression level for output FLAC audio files. 0 means fastest compression, 8 means best compression. Default level is 5.
457    pub flac_compression_level: u32,
458    #[cfg(feature = "artemis")]
459    #[default(true)]
460    /// Format lua code in Artemis ASB script(.asb/.iet) when exporting.
461    pub artemis_asb_format_lua: bool,
462    #[cfg(feature = "kirikiri")]
463    /// Whether to handle title in Kirikiri SCN script.
464    pub kirikiri_title: bool,
465    #[cfg(feature = "favorite")]
466    #[default(true)]
467    /// Whether to filter ascii strings in Favorite HCB script.
468    pub favorite_hcb_filter_ascii: bool,
469    #[cfg(feature = "bgi-img")]
470    #[default(get_default_threads())]
471    /// Workers count for decode BGI compressed images v2 in parallel. Default is half of CPU cores.
472    /// Set this to 1 to disable parallel decoding. 0 means same as 1.
473    pub bgi_img_workers: usize,
474    #[cfg(feature = "image-jxl")]
475    #[default(true)]
476    /// Use JXL lossless compression for output images. Enabled by default.
477    pub jxl_lossless: bool,
478    #[cfg(feature = "image-jxl")]
479    #[default(1.0)]
480    /// JXL distance for output images. 0 means mathematically lossless compression. 1.0 means visually lossless compression.
481    /// Allowed range is 0.0-25.0. Recommended range is 0.5-3.0. Default value is 1.0.
482    pub jxl_distance: f32,
483    #[cfg(feature = "image-jxl")]
484    #[default(1)]
485    /// Workers count for encode JXL images in parallel. Default is 1.
486    /// Set this to 1 to disable parallel encoding. 0 means same as 1
487    pub jxl_workers: usize,
488    #[cfg(feature = "emote-img")]
489    #[default(true)]
490    /// Process tlg images.
491    pub psb_process_tlg: bool,
492    #[cfg(feature = "softpal-img")]
493    #[default(true)]
494    /// Whether to use fake compression for Softpal Pgd images. Enabled by default.
495    /// WARN: Compress may cause image broken.
496    pub pgd_fake_compress: bool,
497    #[cfg(feature = "softpal")]
498    /// Whether to add message index to Softpal src script when exporting.
499    pub softpal_add_message_index: bool,
500    #[cfg(feature = "kirikiri")]
501    #[default(true)]
502    /// Enable multi-language support for Kirikiri chat messages. Default is true.
503    /// Note: This requires [Self::kirikiri_language_index] and [Self::kirikiri_languages] to be set correctly.
504    pub kirikiri_chat_multilang: bool,
505    #[cfg(feature = "kirikiri-arc")]
506    #[default(true)]
507    /// Decrypt SimpleCrypt files in Kirikiri XP3 archive when extracting. Default is true.
508    pub xp3_simple_crypt: bool,
509    #[cfg(feature = "kirikiri-arc")]
510    #[default(true)]
511    /// Decompress mdf files in Kirikiri XP3 archive when extracting. Default is true.
512    pub xp3_mdf_decompress: bool,
513    #[cfg(feature = "kirikiri-arc")]
514    /// Configuration for Kirikiri XP3 segmenter when creating XP3 archive.
515    pub xp3_segmenter: crate::scripts::kirikiri::archive::xp3::SegmenterConfig,
516    #[cfg(feature = "kirikiri-arc")]
517    #[default(true)]
518    /// Compress files in Kirikiri XP3 archive when creating. Default is true.
519    pub xp3_compress_files: bool,
520    #[cfg(feature = "kirikiri-arc")]
521    #[default(true)]
522    /// Compress index in Kirikiri XP3 archive when creating. Default is true.
523    pub xp3_compress_index: bool,
524    #[cfg(feature = "kirikiri-arc")]
525    #[default(num_cpus::get())]
526    /// Workers count for compress files in Kirikiri XP3 archive when creating in parallel. Default is CPU cores count.
527    pub xp3_compress_workers: usize,
528    #[cfg(feature = "kirikiri-arc")]
529    /// Use zstd compression for files in Kirikiri XP3 archive when creating. (Warning: Kirikiri engine don't support this. Hook is required.)
530    pub xp3_zstd: bool,
531    #[cfg(feature = "kirikiri-arc")]
532    /// Use zopfli compression for files in Kirikiri XP3 archive when creating. This is very slow.
533    pub xp3_zopfli: bool,
534    #[cfg(feature = "kirikiri-arc")]
535    #[default(1)]
536    /// Workers count for packing file in Kirikiri XP3 archive in parallel. Default is 1.
537    /// This not works when segment is disabled.
538    pub xp3_pack_workers: usize,
539    #[cfg(feature = "kirikiri-arc")]
540    /// Disable adler32 checksum for Kirikiri XP3 archive when creating.
541    /// This will keep compatibility with https://github.com/arcusmaximus/KirikiriTools tool.
542    pub xp3_no_adler: bool,
543    #[cfg(feature = "kirikiri")]
544    /// Insert new language at the specified index in Kirikiri SCN script. If index is out of bounds, this flags will be ignored.
545    pub kirikiri_language_insert: bool,
546    #[cfg(feature = "musica-arc")]
547    /// Musica game title for paz archive.
548    pub musica_game_title: Option<String>,
549    #[cfg(feature = "musica-arc")]
550    /// Musica xor key for paz archive.
551    pub musica_xor_key: Option<u8>,
552    #[cfg(feature = "musica-arc")]
553    /// Whether to compress files in Musica paz archive when packing paz archive.
554    pub musica_compress: bool,
555    #[cfg(feature = "bgi")]
556    /// Add an additional space at the end of message in BGI scripts when importing.
557    /// This may help BGI engine to display the message correctly in save/load screen for some games.
558    pub bgi_add_space: bool,
559    #[cfg(feature = "escude")]
560    /// Escude game title
561    pub escude_op: Option<crate::scripts::escude::script::EscudeOp>,
562    #[cfg(feature = "zopfli")]
563    #[default(std::num::NonZeroU64::new(15).unwrap())]
564    /// Maximum amount of times to rerun forward and backward pass to optimize LZ77 compression cost.
565    /// Good values: 10, 15 for small files, 5 for files over several MB in size or it will be too slow.
566    /// Default is 15.
567    pub zopfli_iteration_count: std::num::NonZeroU64,
568    #[cfg(feature = "zopfli")]
569    #[default(std::num::NonZeroU64::new(u64::MAX).unwrap())]
570    /// Stop after rerunning forward and backward pass this many times without finding a smaller representation of the block.
571    /// Default value: practically infinite (maximum u64 value)
572    pub zopfli_iterations_without_improvement: std::num::NonZeroU64,
573    #[cfg(feature = "zopfli")]
574    #[default(15)]
575    /// Maximum amount of blocks to split into (0 for unlimited, but this can give extreme results that hurt compression on some files).
576    /// Default value: 15.
577    pub zopfli_maximum_block_splits: u16,
578    #[cfg(feature = "entis-gls")]
579    /// Whether to disassemble Entis GLS csx script when exporting in custom mode.
580    pub entis_gls_csx_disasm: bool,
581    #[cfg(feature = "entis-gls")]
582    #[default(String::from("/"))]
583    /// The line feed character used in Entis GLS csx script.
584    pub entis_gls_csx_lf: String,
585    #[cfg(feature = "entis-gls")]
586    /// Entis GLS csx script version.
587    /// If not specified. Will try use lower version first.
588    pub entis_gls_csx_ver: Option<crate::scripts::entis_gls::csx::CSXScriptVersion>,
589    #[cfg(feature = "entis-gls")]
590    /// Entis GLS csx script version2 full version.
591    /// If not specified. Will try use higher version first.
592    pub entis_gls_csx_v2_ver: Option<crate::scripts::entis_gls::csx::CSXScriptV2FullVer>,
593    #[cfg(feature = "entis-gls")]
594    /// Disable part labels in Entis GLS csx script when exporting.
595    pub entis_gls_csx_no_part_label: bool,
596    #[cfg(feature = "qlie-img")]
597    #[default(true)]
598    /// Whether to process ABMP10 images in ABMP10 images.
599    pub qlie_abmp10_process_abmp10: bool,
600    #[cfg(feature = "qlie-arc")]
601    /// Path to qlie pack archive key file (pack_keyfile_kfueheish15538fa9or.key)
602    pub qlie_pack_keyfile: Option<String>,
603    #[cfg(feature = "qlie-arc")]
604    /// Whether to compress files in Qlie pack archive.
605    pub qlie_pack_compress_files: bool,
606    #[cfg(feature = "qlie-img")]
607    /// Whether to use PNG file directly for Qlie DPNG images when importing.
608    /// Enable this will disable reencoding PNG files. Useful when the PNG files are already optimized by other tools.
609    pub qlie_dpng_use_raw_png: bool,
610    #[cfg(feature = "qlie-img")]
611    /// Export Qlie DPNG images as PSD files.
612    pub qlie_dpng_psd: bool,
613    #[cfg(feature = "utils-psd")]
614    #[default(true)]
615    /// Whether to use compression for image data in PSD files.
616    pub psd_compress: bool,
617    #[cfg(feature = "emote-img")]
618    /// Export Emote PIMG images as PSD files.
619    pub emote_pimg_psd: bool,
620    #[cfg(feature = "kirikiri")]
621    /// Whether to only extract message between Talk and Hitret command in Kirikiri KS script. Auto detect if not specified.
622    pub kirikiri_ks_hitret: Option<bool>,
623    #[cfg(feature = "kirikiri")]
624    /// The line feed character used in Kirikiri KS script.
625    pub kirikiri_ks_lf: Option<String>,
626    #[cfg(feature = "kirikiri")]
627    /// Kirikiri message tags, used to extract more message from ks script.
628    pub kirikiri_message_tags: std::sync::Arc<std::collections::HashSet<String>>,
629    #[cfg(feature = "kirikiri")]
630    /// Specifiy BOM type when creating new Kirikiri ks script. If not specified, detect from original script.
631    pub kirikiri_ks_bom: Option<BomType>,
632    #[cfg(feature = "emote-img")]
633    /// BC7 compress configuration
634    pub bc7: crate::scripts::emote::psb::BC7Config,
635    #[cfg(feature = "artemis")]
636    #[default(default_artemis_asb_end_tags())]
637    /// A list of Artemis ASB script end tags, used to determine a dialogue block in script.
638    pub artemis_asb_end_tags: std::sync::Arc<std::collections::HashSet<String>>,
639    #[cfg(feature = "kirikiri-arc")]
640    /// Game title for Kirikiri XP3 archive. This is used to decrypt file in archives.
641    pub xp3_game_title: Option<String>,
642    #[cfg(feature = "kirikiri-arc")]
643    /// Print debug information for Kirikiri XP3 archive when extracting archive to stdout.
644    /// This is used to find correct configuration for unknown XP3 archives.
645    pub xp3_debug_archive: bool,
646    #[cfg(feature = "kirikiri-arc")]
647    /// Force extract encrypted files in Kirikiri XP3 archive without decryption.
648    pub xp3_force_extract: bool,
649    #[cfg(feature = "kirikiri-arc")]
650    /// Force decrypt files in Kirikiri xp3 archive even when flags are not set.
651    /// Some encrypted files in Kirikiri XP3 archive may not set encryption flag, but still encrypted. Enable this to force decrypt these files.
652    pub xp3_force_decrypt: bool,
653    #[cfg(feature = "emote-img")]
654    /// Disable diff handle when exporting Emote PIMG images to PSD files.
655    /// If enabled, no group layers will be crated if both layer don't have diff_id and group_layer_id attribute.
656    pub emote_pimg_psd_no_diff: bool,
657    #[cfg(feature = "kirikiri-arc")]
658    /// The path to the file list for Kirikiri XP3 archive. This is used to recover file names from hashed values.
659    /// Only works with some encyrption methods.
660    pub xp3_file_list_path: Option<String>,
661    #[cfg(feature = "kirikiri-arc")]
662    /// Control the behavior to how to extract files from Cxdec3/4(Hxv4) protected archives.
663    pub xp3_cxdec_file_hash: crate::scripts::kirikiri::archive::xp3::FileHashOption,
664    #[cfg(feature = "kirikiri-arc")]
665    /// Control the behavior to how to append path name to files from Cxdec3/4(Hxv4) protected archives.
666    pub xp3_cxdec_path_hash: crate::scripts::kirikiri::archive::xp3::PathHashOption,
667    #[cfg(feature = "yuris")]
668    /// Path to the ysc.ybn file
669    pub yuris_ysc_path: Option<String>,
670    #[cfg(feature = "yuris")]
671    /// Path to the ysl.ybn file
672    pub yuris_ysl_path: Option<String>,
673    #[cfg(feature = "yuris")]
674    /// Disasm Yu-RIS YSTB (.ybn) file
675    pub yuris_ystb_disasm: bool,
676    #[cfg(feature = "yuris")]
677    /// YuRis Tips map. Used to replace tip name in YuRis scenario file
678    pub yuris_tips_map: Option<std::sync::Arc<std::collections::HashMap<String, String>>>,
679    #[cfg(feature = "bgi-arc")]
680    #[default(num_cpus::get())]
681    /// Workers count for compress files in BGI archive when creating in parallel. Default is CPU cores count.
682    pub bgi_arc_workers: usize,
683    #[cfg(feature = "yuris-arc")]
684    /// Name hash type in the Yu-RIS archive (.ypf)
685    pub yuris_name_hash_type: crate::scripts::yuris::arc::ypf::NameHashType,
686    #[cfg(feature = "yuris-arc")]
687    /// Data hash type in the Yu-RIS archive (.ypf)
688    pub yuris_data_hash_type: crate::scripts::yuris::arc::ypf::DataHashType,
689    #[cfg(feature = "yuris-arc")]
690    /// Check hash when unpack Yu-RIS archive (.ypf)
691    pub yuris_check_hash: bool,
692    #[cfg(feature = "yuris-arc")]
693    /// Print debug information for Yu-RIS archive (.ypf) when extracting archive to stdout.
694    /// This is used to find correct configuration for Yu-RIS archives.
695    pub yuris_debug_archive: bool,
696    #[cfg(feature = "yuris-arc")]
697    /// Yu-RIS YPF engine version. Used when pack files into Yu-RIS archive.
698    pub yuris_ypf_version: Option<u32>,
699    #[cfg(feature = "yuris-arc")]
700    #[default(true)]
701    /// Compress file when packing files into Yu-RIS archive.
702    pub yuris_ypf_compress_file: bool,
703    #[cfg(feature = "yuris-arc")]
704    /// Use zopfli to compress files in Yu-RIS archive.
705    pub yuris_ypf_zopfli: bool,
706    #[cfg(feature = "yuris-arc")]
707    #[default(num_cpus::get())]
708    /// Workers count for compress files in Yu-RIS archive when creating in parallel. Default is CPU cores count.
709    pub yuris_ypf_workers: usize,
710    #[cfg(feature = "yuris-arc")]
711    /// Use new file type mapping for Yu-RIS archive (.ypf).
712    /// When enabled, ogg and wav are mapped to larger type values (OGG_ and WAV_).
713    pub yuris_use_new_file_type: bool,
714    #[cfg(feature = "kirikiri-arc")]
715    /// Dump xp3 file name hash into a json file. Only some protected archive may supported.
716    pub xp3_dump_file_hash_list: Option<String>,
717}
718
719#[cfg(feature = "artemis")]
720fn default_artemis_asb_end_tags() -> std::sync::Arc<std::collections::HashSet<String>> {
721    std::sync::Arc::new(
722        ["click", "hcls", "rpx"]
723            .into_iter()
724            .map(String::from)
725            .collect(),
726    )
727}
728
729#[derive(Clone, Copy, Debug, ValueEnum, PartialEq, Eq, PartialOrd, Ord, Deserialize, Serialize)]
730/// Script type
731pub enum ScriptType {
732    #[cfg(feature = "artemis")]
733    /// Artemis Engine AST script
734    Artemis,
735    #[cfg(feature = "artemis")]
736    /// Artemis Engine ASB script
737    ArtemisAsb,
738    #[cfg(feature = "artemis")]
739    /// Artemis Engine TXT (General) script
740    ArtemisTxt,
741    #[cfg(feature = "artemis-panmimisoft")]
742    /// Artemis Engine TXT (ぱんみみそふと) script
743    ArtemisPanmimisoftTxt,
744    #[cfg(feature = "artemis-arc")]
745    #[value(alias("pfs"))]
746    /// Artemis archive (pfs)
747    ArtemisArc,
748    #[cfg(feature = "artemis-arc")]
749    #[value(alias("pf2"))]
750    /// Artemis archive (pf2) (.pfs)
751    ArtemisPf2,
752    #[cfg(feature = "bgi")]
753    #[value(alias("ethornell"))]
754    /// Buriko General Interpreter/Ethornell Script
755    BGI,
756    #[cfg(feature = "bgi")]
757    #[value(alias("ethornell-bsi"))]
758    /// Buriko General Interpreter/Ethornell bsi script (._bsi)
759    BGIBsi,
760    #[cfg(feature = "bgi")]
761    #[value(alias("ethornell-bp"))]
762    /// Buriko General Interpreter/Ethornell bp script (._bp)
763    BGIBp,
764    #[cfg(feature = "bgi-arc")]
765    #[value(alias = "ethornell-arc-v1")]
766    /// Buriko General Interpreter/Ethornell archive v1
767    BGIArcV1,
768    #[cfg(feature = "bgi-arc")]
769    #[value(alias = "ethornell-arc-v2", alias = "bgi-arc", alias = "ethornell-arc")]
770    /// Buriko General Interpreter/Ethornell archive v2
771    BGIArcV2,
772    #[cfg(feature = "bgi-arc")]
773    #[value(alias("ethornell-dsc"))]
774    /// Buriko General Interpreter/Ethornell compressed file (DSC)
775    BGIDsc,
776    #[cfg(feature = "bgi-audio")]
777    #[value(alias("ethornell-audio"))]
778    /// Buriko General Interpreter/Ethornell audio file (Ogg/Vorbis)
779    BGIAudio,
780    #[cfg(feature = "bgi-img")]
781    #[value(alias("ethornell-img"))]
782    /// Buriko General Interpreter/Ethornell image (Image files in sysgrp.arc)
783    BGIImg,
784    #[cfg(feature = "bgi-img")]
785    #[value(alias("ethornell-cbg"))]
786    /// Buriko General Interpreter/Ethornell Compressed Background image (CBG)
787    BGICbg,
788    #[cfg(feature = "cat-system")]
789    /// CatSystem2 engine scene script
790    CatSystem,
791    #[cfg(feature = "cat-system")]
792    /// CatSystem2 engine CSTL script
793    CatSystemCstl,
794    #[cfg(feature = "cat-system-arc")]
795    /// CatSystem2 engine archive
796    CatSystemInt,
797    #[cfg(feature = "cat-system-img")]
798    /// CatSystem2 engine image
799    CatSystemHg3,
800    #[cfg(feature = "circus")]
801    /// Circus MES script
802    Circus,
803    #[cfg(feature = "circus-arc")]
804    /// Circus Image archive
805    CircusCrm,
806    #[cfg(feature = "circus-arc")]
807    /// Circus DAT archive
808    CircusDat,
809    #[cfg(feature = "circus-arc")]
810    /// Circus PCK archive
811    CircusPck,
812    #[cfg(feature = "circus-audio")]
813    /// Circus PCM audio
814    CircusPcm,
815    #[cfg(feature = "circus-img")]
816    /// Circus CRX Image
817    CircusCrx,
818    #[cfg(feature = "circus-img")]
819    /// Circus Differential Image
820    CircusCrxd,
821    #[cfg(feature = "emote-img")]
822    #[value(alias("psb"))]
823    /// Emote PSB (basic handle)
824    EmotePsb,
825    #[cfg(feature = "emote-img")]
826    #[value(alias("pimg"))]
827    /// Emote PIMG image
828    EmotePimg,
829    #[cfg(feature = "emote-img")]
830    #[value(alias("dref"))]
831    /// Emote DREF(DPAK-referenced) image
832    EmoteDref,
833    #[cfg(feature = "entis-gls")]
834    /// Entis GLS srcxml Script
835    EntisGls,
836    #[cfg(feature = "entis-gls")]
837    /// Entis GLS csx script
838    EntisGlsCsx,
839    #[cfg(feature = "escude-arc")]
840    /// Escude bin archive
841    EscudeArc,
842    #[cfg(feature = "escude")]
843    /// Escude bin script
844    Escude,
845    #[cfg(feature = "escude")]
846    /// Escude list script
847    EscudeList,
848    #[cfg(feature = "ex-hibit")]
849    /// ExHibit rld script
850    ExHibit,
851    #[cfg(feature = "ex-hibit-arc")]
852    /// ExHibit GRP archive
853    ExHibitGrp,
854    #[cfg(feature = "favorite")]
855    /// Favorite hcb script
856    Favorite,
857    #[cfg(feature = "hexen-haus")]
858    /// HexenHaus bin script
859    HexenHaus,
860    #[cfg(feature = "hexen-haus-arc")]
861    /// HexenHaus Arcc archive
862    HexenHausArcc,
863    #[cfg(feature = "hexen-haus-arc")]
864    /// HexenHaus Audio archive
865    HexenHausOdio,
866    #[cfg(feature = "hexen-haus-arc")]
867    /// HexenHaus WAG archive
868    HexenHausWag,
869    #[cfg(feature = "hexen-haus-img")]
870    /// HexenHaus PNG image
871    HexenHausPng,
872    #[cfg(feature = "kirikiri")]
873    #[value(alias("kr-scn"))]
874    /// Kirikiri SCN script
875    KirikiriScn,
876    #[cfg(feature = "kirikiri")]
877    #[value(alias("kr-simple-crypt"))]
878    /// Kirikiri SimpleCrypt's text file
879    KirikiriSimpleCrypt,
880    #[cfg(feature = "kirikiri")]
881    #[value(alias = "kr", alias = "kr-ks", alias = "kirikiri-ks")]
882    /// Kirikiri script
883    Kirikiri,
884    #[cfg(feature = "kirikiri-arc")]
885    #[value(alias = "kr-xp3", alias = "xp3")]
886    /// Kirikiri XP3 archive
887    KirikiriXp3,
888    #[cfg(feature = "kirikiri-img")]
889    #[value(alias("kr-tlg"))]
890    /// Kirikiri TLG image
891    KirikiriTlg,
892    #[cfg(feature = "kirikiri")]
893    #[value(alias("kr-mdf"))]
894    /// Kirikiri MDF (zlib compressed) file
895    KirikiriMdf,
896    #[cfg(feature = "kirikiri")]
897    #[value(alias("kr-tjs2"))]
898    /// Kirikiri compiled TJS2 script
899    KirikiriTjs2,
900    #[cfg(feature = "kirikiri")]
901    #[value(alias("kr-tjs-ns0"))]
902    /// Kirikiri TJS NS0 binary encoded script
903    KirikiriTjsNs0,
904    #[cfg(feature = "musica")]
905    /// Musica Script (.sc)
906    Musica,
907    #[cfg(feature = "musica-arc")]
908    /// Musica Engine Resource Archive (.paz)
909    MusicaPaz,
910    #[cfg(feature = "qlie")]
911    /// Qlie Engine Scenario script (.s)
912    Qlie,
913    #[cfg(feature = "qlie-arc")]
914    /// Qlie Pack Archive (.pack)
915    QliePack,
916    #[cfg(feature = "qlie-img")]
917    /// Qlie tiled PNG image (.png)
918    QlieDpng,
919    #[cfg(feature = "qlie-img")]
920    #[value(alias = "qlie-abmp11", alias = "qlie-abmp12")]
921    /// Qlie Abmp10/11/12 image (.b)
922    QlieAbmp10,
923    #[cfg(feature = "silky")]
924    /// Silky Engine Mes script
925    Silky,
926    #[cfg(feature = "silky")]
927    /// Silky Engine Map script
928    SilkyMap,
929    #[cfg(feature = "softpal")]
930    /// Softpal src script
931    Softpal,
932    #[cfg(feature = "softpal-arc")]
933    /// Softpal Pac archive
934    SoftpalPac,
935    #[cfg(feature = "softpal-arc")]
936    /// Softpal Pac/AMUSE archive
937    SoftpalPacAmuse,
938    #[cfg(feature = "softpal-img")]
939    #[value(alias = "pgd-ge", alias = "pgd")]
940    /// Softpal Pgd Ge image
941    SoftpalPgdGe,
942    #[cfg(feature = "softpal-img")]
943    #[value(alias = "softpal-pgd2", alias = "pgd3", alias = "pgd2")]
944    /// Softpal Pgd Differential image
945    SoftpalPgd3,
946    #[cfg(feature = "will-plus")]
947    #[value(alias("adv-hd-ws2"))]
948    /// WillPlus ws2 script
949    WillPlusWs2,
950    #[cfg(feature = "will-plus-img")]
951    #[value(alias("adv-hd-wip"))]
952    /// WillPlus WIP Image
953    WillPlusWip,
954    #[cfg(feature = "yaneurao-itufuru")]
955    #[value(alias("itufuru"))]
956    /// Yaneurao Itufuru script
957    YaneuraoItufuru,
958    #[cfg(feature = "yaneurao-itufuru")]
959    #[value(alias("itufuru-arc"))]
960    /// Yaneurao Itufuru script archive
961    YaneuraoItufuruArc,
962    #[cfg(feature = "yuris")]
963    /// Yu-Ris YSCM(opcodes metadata) file (.ybn)
964    YurisYSCM,
965    #[cfg(feature = "yuris")]
966    /// Yu-Ris YSER(error message) file (.ybn)
967    YurisYSER,
968    #[cfg(feature = "yuris")]
969    /// Yu-Ris YSCFG(config) file (.ybn)
970    YurisYSCFG,
971    #[cfg(feature = "yuris")]
972    /// Yu-Ris YSTB(compiled script) file (.ybn)
973    YurisYSTB,
974    #[cfg(feature = "yuris")]
975    /// Yu-Ris scenario text file (.txt)
976    YurisTxt,
977    #[cfg(feature = "yuris")]
978    /// Yu-Ris YSTL(file list) file (.ybn)
979    YurisYSTL,
980    #[cfg(feature = "yuris")]
981    /// Yu-Ris YSLB(labels) file (.ybn)
982    YurisYSLB,
983    #[cfg(feature = "yuris")]
984    /// Yu-Ris YSVR(Variables) file (.ybn)
985    YurisYSVR,
986    #[cfg(feature = "yuris")]
987    /// Yu-Ris YSTD(Global counts) file (.ybn)
988    YurisYSTD,
989    #[cfg(feature = "yuris-arc")]
990    /// Yu-Ris Archive (.ypf)
991    YurisYPF,
992    #[cfg(feature = "yuris-img")]
993    /// YU-RIS compressed image file (.ydg)
994    YurisYDG,
995}
996
997#[derive(Clone, Debug, Serialize, Deserialize)]
998/// Message structure for scripts
999pub struct Message {
1000    #[serde(skip_serializing_if = "Option::is_none")]
1001    /// Optional name for the message, used in some scripts.
1002    pub name: Option<String>,
1003    /// The actual message content.
1004    pub message: String,
1005}
1006
1007impl Message {
1008    /// Creates a new `Message` instance.
1009    pub fn new(message: String, name: Option<String>) -> Self {
1010        Message { message, name }
1011    }
1012}
1013
1014#[derive(Clone, Debug)]
1015/// Extended message structure for scripts
1016pub struct ExtendedMessage {
1017    /// Optional name for the message, used in some scripts.
1018    pub name: Option<String>,
1019    /// Original source text.
1020    pub source: String,
1021    /// Translated text.
1022    pub translated: String,
1023    /// Optional LLM translated text.
1024    pub llm: Option<String>,
1025}
1026
1027/// Result of script operation.
1028pub enum ScriptResult {
1029    /// Operation completed successfully.
1030    Ok,
1031    /// Operation completed without any changes.
1032    /// For example, no messages found in the script.
1033    Ignored,
1034    /// Operation not completed.
1035    /// This will not count in statistics.
1036    Uncount,
1037}
1038
1039#[derive(Clone, Copy, Debug, ValueEnum, PartialEq, Eq, PartialOrd, Ord)]
1040/// Format type (for CLI)
1041pub enum FormatType {
1042    /// Wrap line with fixed length
1043    Fixed,
1044    /// Do not wrap line
1045    None,
1046}
1047
1048#[derive(Clone)]
1049/// Format options
1050pub enum FormatOptions {
1051    /// Wrap line with fixed length
1052    Fixed {
1053        /// Fixed length
1054        length: usize,
1055        /// Whether to keep original line breaks
1056        keep_original: bool,
1057        /// Whether to break words(ASCII only) at the end of the line
1058        break_words: bool,
1059        /// Whether to insert a full-width space after a line break when a sentence starts with a full-width quotation mark.
1060        insert_fullwidth_space_at_line_start: bool,
1061        /// If a line break occurs in the middle of some symbols, bring the sentence to next line
1062        break_with_sentence: bool,
1063        #[cfg(feature = "jieba")]
1064        /// Whether to break Chinese words at the end of the line.
1065        break_chinese_words: bool,
1066        #[cfg(feature = "jieba")]
1067        /// Path to custom jieba dictionary
1068        jieba_dict: Option<String>,
1069        /// Do not remove space at the start of the line
1070        no_remove_space_at_line_start: bool,
1071    },
1072    /// Do not wrap line
1073    None,
1074}
1075
1076#[derive(Debug, Serialize, Deserialize)]
1077/// Name table cell
1078pub struct NameTableCell {
1079    #[serde(rename = "JP_Name")]
1080    /// Original name
1081    pub jp_name: String,
1082    #[serde(rename = "CN_Name")]
1083    /// Translated name
1084    pub cn_name: String,
1085    #[serde(rename = "Count")]
1086    /// Number of times this name appears in the script
1087    pub count: usize,
1088}
1089
1090#[derive(Debug, Serialize, Deserialize)]
1091/// Replacement table for string replacements
1092pub struct ReplacementTable {
1093    #[serde(flatten)]
1094    /// Map of original strings to their replacements
1095    pub map: HashMap<String, String>,
1096}
1097
1098#[cfg(feature = "image")]
1099#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
1100/// Image color type
1101pub enum ImageColorType {
1102    /// Grayscale image
1103    Grayscale,
1104    /// RGB image
1105    Rgb,
1106    /// RGBA image
1107    Rgba,
1108    /// BGR image
1109    Bgr,
1110    /// BGRA image
1111    Bgra,
1112}
1113
1114#[cfg(feature = "image")]
1115impl ImageColorType {
1116    /// Returns the number of bytes per pixel for the color type and depth.
1117    pub fn bpp(&self, depth: u8) -> u16 {
1118        match self {
1119            ImageColorType::Grayscale => depth as u16,
1120            ImageColorType::Rgb => depth as u16 * 3,
1121            ImageColorType::Rgba => depth as u16 * 4,
1122            ImageColorType::Bgr => depth as u16 * 3,
1123            ImageColorType::Bgra => depth as u16 * 4,
1124        }
1125    }
1126}
1127
1128#[cfg(feature = "image")]
1129#[derive(Clone, Copy, Debug, ValueEnum, PartialEq, Eq, PartialOrd, Ord)]
1130/// Image output type
1131pub enum ImageOutputType {
1132    /// PNG image
1133    Png,
1134    #[cfg(feature = "image-jpg")]
1135    /// JPEG image
1136    Jpg,
1137    #[cfg(feature = "image-webp")]
1138    /// WebP image
1139    Webp,
1140    #[cfg(feature = "image-jxl")]
1141    /// JPEG XL image
1142    Jxl,
1143}
1144
1145#[cfg(feature = "image")]
1146impl TryFrom<&str> for ImageOutputType {
1147    type Error = anyhow::Error;
1148
1149    /// Try to convert a extension string to an `ImageOutputType`.
1150    /// Extensions are case-insensitive.
1151    fn try_from(value: &str) -> Result<Self, Self::Error> {
1152        match value.to_ascii_lowercase().as_str() {
1153            "png" => Ok(ImageOutputType::Png),
1154            #[cfg(feature = "image-jpg")]
1155            "jpg" => Ok(ImageOutputType::Jpg),
1156            #[cfg(feature = "image-jpg")]
1157            "jpeg" => Ok(ImageOutputType::Jpg),
1158            #[cfg(feature = "image-webp")]
1159            "webp" => Ok(ImageOutputType::Webp),
1160            #[cfg(feature = "image-jxl")]
1161            "jxl" => Ok(ImageOutputType::Jxl),
1162            _ => Err(anyhow::anyhow!("Unsupported image output type: {}", value)),
1163        }
1164    }
1165}
1166
1167#[cfg(feature = "image")]
1168impl TryFrom<&std::path::Path> for ImageOutputType {
1169    type Error = anyhow::Error;
1170
1171    fn try_from(value: &std::path::Path) -> Result<Self, Self::Error> {
1172        if let Some(ext) = value.extension() {
1173            Self::try_from(ext.to_string_lossy().as_ref())
1174        } else {
1175            Err(anyhow::anyhow!("No extension found in path"))
1176        }
1177    }
1178}
1179
1180#[cfg(feature = "image")]
1181impl AsRef<str> for ImageOutputType {
1182    /// Returns the extension for the image output type.
1183    fn as_ref(&self) -> &str {
1184        match self {
1185            ImageOutputType::Png => "png",
1186            #[cfg(feature = "image-jpg")]
1187            ImageOutputType::Jpg => "jpg",
1188            #[cfg(feature = "image-webp")]
1189            ImageOutputType::Webp => "webp",
1190            #[cfg(feature = "image-jxl")]
1191            ImageOutputType::Jxl => "jxl",
1192        }
1193    }
1194}
1195
1196#[cfg(feature = "image")]
1197#[derive(Clone, Debug)]
1198/// Image data
1199pub struct ImageData {
1200    /// Image width in pixels
1201    pub width: u32,
1202    /// Image height in pixels
1203    pub height: u32,
1204    /// Image color type
1205    pub color_type: ImageColorType,
1206    /// Image depth in bits per channel
1207    pub depth: u8,
1208    /// Image data
1209    pub data: Vec<u8>,
1210}
1211
1212#[cfg(feature = "image")]
1213#[derive(Clone, Debug)]
1214/// Image data with name
1215pub struct ImageDataWithName {
1216    /// Image name
1217    pub name: String,
1218    /// Image data
1219    pub data: ImageData,
1220}
1221
1222#[derive(Clone, Copy, Debug, ValueEnum, PartialEq, Eq, PartialOrd, Ord)]
1223/// BOM type
1224pub enum BomType {
1225    /// No BOM
1226    None,
1227    /// UTF-8 BOM
1228    Utf8,
1229    /// UTF-16 Little Endian BOM
1230    Utf16LE,
1231    /// UTF-16 Big Endian BOM
1232    Utf16BE,
1233}
1234
1235impl BomType {
1236    /// Returns the byte sequence for the BOM type.
1237    pub fn as_bytes(&self) -> &'static [u8] {
1238        match self {
1239            BomType::None => &[],
1240            BomType::Utf8 => b"\xEF\xBB\xBF",
1241            BomType::Utf16LE => b"\xFF\xFE",
1242            BomType::Utf16BE => b"\xFE\xFF",
1243        }
1244    }
1245}
1246
1247#[cfg(feature = "image")]
1248#[derive(Clone, Copy, Debug, ValueEnum, PartialEq, Eq, PartialOrd, Ord)]
1249/// PNG compression level
1250pub enum PngCompressionLevel {
1251    #[value(alias = "n")]
1252    /// No compression whatsoever. Fastest, but results in large files.
1253    NoCompression,
1254    #[value(alias = "d")]
1255    /// Default level (usually balanced)
1256    Default,
1257    /// Extremely fast but light compression.
1258    ///
1259    /// Note: When used in streaming mode, this compression level can actually result in files
1260    /// *larger* than would be produced by `NoCompression` on incompressible data because
1261    /// it doesn't do any buffering of the output stream to detect whether the data is being compressed or not.
1262    Fastest,
1263    #[value(alias = "f")]
1264    /// Fast minimal compression
1265    Fast,
1266    #[value(alias = "b")]
1267    /// Higher compression level. Same as high
1268    Best,
1269    #[value(alias = "h")]
1270    /// Spend much more time to produce a slightly smaller file than with `Balanced`.
1271    High,
1272}
1273
1274#[cfg(feature = "image")]
1275impl Default for PngCompressionLevel {
1276    fn default() -> Self {
1277        PngCompressionLevel::Default
1278    }
1279}
1280
1281#[cfg(feature = "image")]
1282impl PngCompressionLevel {
1283    /// Converts the [PngCompressionLevel] to a [png::Compression] enum.
1284    pub fn to_compression(&self) -> png::Compression {
1285        match self {
1286            PngCompressionLevel::NoCompression => png::Compression::NoCompression,
1287            PngCompressionLevel::Fastest => png::Compression::Fastest,
1288            PngCompressionLevel::Default => png::Compression::Balanced,
1289            PngCompressionLevel::Fast => png::Compression::Fast,
1290            PngCompressionLevel::Best => png::Compression::High,
1291            PngCompressionLevel::High => png::Compression::High,
1292        }
1293    }
1294}
1295
1296#[cfg(feature = "lossless-audio")]
1297#[derive(Clone, Copy, Debug, ValueEnum, PartialEq, Eq, PartialOrd, Ord)]
1298/// Lossless audio format
1299pub enum LosslessAudioFormat {
1300    /// Wav
1301    Wav,
1302    #[cfg(feature = "audio-flac")]
1303    /// FLAC Format
1304    Flac,
1305}
1306
1307#[cfg(feature = "lossless-audio")]
1308impl Default for LosslessAudioFormat {
1309    fn default() -> Self {
1310        LosslessAudioFormat::Wav
1311    }
1312}
1313
1314#[cfg(feature = "lossless-audio")]
1315impl TryFrom<&str> for LosslessAudioFormat {
1316    type Error = anyhow::Error;
1317    /// Try to convert a extension string to an `LosslessAudioFormat`.
1318    /// Extensions are case-insensitive.
1319    fn try_from(value: &str) -> Result<Self, Self::Error> {
1320        match value.to_ascii_lowercase().as_str() {
1321            "wav" => Ok(LosslessAudioFormat::Wav),
1322            #[cfg(feature = "audio-flac")]
1323            "flac" => Ok(LosslessAudioFormat::Flac),
1324            _ => Err(anyhow::anyhow!(
1325                "Unsupported lossless audio format: {}",
1326                value
1327            )),
1328        }
1329    }
1330}
1331
1332#[cfg(feature = "lossless-audio")]
1333impl TryFrom<&std::path::Path> for LosslessAudioFormat {
1334    type Error = anyhow::Error;
1335
1336    fn try_from(value: &std::path::Path) -> Result<Self, Self::Error> {
1337        if let Some(ext) = value.extension() {
1338            Self::try_from(ext.to_string_lossy().as_ref())
1339        } else {
1340            Err(anyhow::anyhow!("No extension found in path"))
1341        }
1342    }
1343}
1344
1345#[cfg(feature = "lossless-audio")]
1346impl AsRef<str> for LosslessAudioFormat {
1347    /// Returns the extension for the lossless audio format.
1348    fn as_ref(&self) -> &str {
1349        match self {
1350            LosslessAudioFormat::Wav => "wav",
1351            #[cfg(feature = "audio-flac")]
1352            LosslessAudioFormat::Flac => "flac",
1353        }
1354    }
1355}
1356
1357#[allow(unused)]
1358pub(crate) fn get_default_threads() -> usize {
1359    num_cpus::get().max(2) / 2
1360}