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(untagged, 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(3)]
353    /// Minimum length of match size for DSC compression. Possible values are 2-256.
354    pub bgi_compress_min_len: usize,
355    #[cfg(feature = "emote-img")]
356    /// Whether to overlay PIMG images. (By default, true if all layers are not group layers.)
357    pub emote_pimg_overlay: Option<bool>,
358    #[cfg(feature = "artemis-arc")]
359    /// Disable Artemis archive (.pfs) XOR encryption when packing.
360    pub artemis_arc_disable_xor: bool,
361    #[cfg(feature = "artemis")]
362    /// Artemis script indent size, used to format Artemis script.
363    /// Default is 4 spaces.
364    pub artemis_indent: Option<usize>,
365    #[cfg(feature = "artemis")]
366    /// Disable Artemis script indent, used to format Artemis script.
367    pub artemis_no_indent: bool,
368    #[cfg(feature = "artemis")]
369    #[default(100)]
370    /// Max line width in Artemis script, used to format Artemis script.
371    pub artemis_max_line_width: usize,
372    #[cfg(feature = "artemis")]
373    /// Specify the language of Artemis AST script.
374    /// If not specified, the first language will be used.
375    pub artemis_ast_lang: Option<String>,
376    #[cfg(feature = "cat-system")]
377    /// CatSystem2 CSTL script language, used to extract messages from CSTL script.
378    /// If not specified, the first language will be used.
379    pub cat_system_cstl_lang: Option<String>,
380    #[cfg(feature = "flate2")]
381    #[default(6)]
382    /// Zlib compression level. 0 means no compression, 9 means best compression.
383    pub zlib_compression_level: u32,
384    #[cfg(feature = "image")]
385    /// PNG compression level.
386    pub png_compression_level: PngCompressionLevel,
387    #[cfg(feature = "circus-img")]
388    /// Keep original BPP when importing Circus CRX images.
389    pub circus_crx_keep_original_bpp: bool,
390    #[cfg(feature = "circus-img")]
391    /// Use zstd compression for Circus CRX images. (CIRCUS Engine don't support this. Hook is required.)
392    pub circus_crx_zstd: bool,
393    #[cfg(feature = "zstd")]
394    #[default(3)]
395    /// Zstd compression level. 0 means default compression level (3), 22 means best compression.
396    pub zstd_compression_level: i32,
397    #[cfg(feature = "circus-img")]
398    /// Circus CRX image row type mode
399    pub circus_crx_mode: crate::scripts::circus::image::crx::CircusCrxMode,
400    #[cfg(feature = "ex-hibit")]
401    /// ExHibit xor key for rld script.
402    /// Use [ReExHIBIT](https://github.com/ZQF-ReVN/RxExHIBIT) to find the key.
403    pub ex_hibit_rld_xor_key: Option<u32>,
404    #[cfg(feature = "ex-hibit")]
405    /// ExHibit def.rld xor key.
406    pub ex_hibit_rld_def_xor_key: Option<u32>,
407    #[cfg(feature = "ex-hibit")]
408    /// ExHibit rld xor keys.
409    pub ex_hibit_rld_keys: Option<Box<[u32; 0x100]>>,
410    #[cfg(feature = "ex-hibit")]
411    /// ExHibit def.rld xor keys.
412    pub ex_hibit_rld_def_keys: Option<Box<[u32; 0x100]>>,
413    #[cfg(feature = "mozjpeg")]
414    #[default(80)]
415    /// JPEG quality for output images, 0-100. 100 means best quality.
416    pub jpeg_quality: u8,
417    #[cfg(feature = "webp")]
418    /// Use WebP lossless compression for output images.
419    pub webp_lossless: bool,
420    #[cfg(feature = "webp")]
421    #[default(80)]
422    /// WebP quality for output images, 0-100. 100 means best quality.
423    pub webp_quality: u8,
424    #[cfg(feature = "circus-img")]
425    /// Draw Circus CRX images on canvas (if canvas width and height are specified in file)
426    pub circus_crx_canvas: bool,
427    /// Try use YAML format instead of JSON when custom exporting.
428    pub custom_yaml: bool,
429    #[cfg(feature = "entis-gls")]
430    /// Entis GLS srcxml script language, used to extract messages from srcxml script.
431    /// If not specified, the first language will be used.
432    pub entis_gls_srcxml_lang: Option<String>,
433    #[cfg(feature = "will-plus")]
434    /// Disable disassembly for WillPlus ws2 script.
435    /// Use another parser to parse the script.
436    /// Should only be used when the default parser not works well.
437    pub will_plus_ws2_no_disasm: bool,
438    #[cfg(feature = "artemis-panmimisoft")]
439    /// Artemis Engine blacklist tag names for TXT script.
440    /// This is used to ignore these tags when finding names in Artemis TXT (ぱんみみそふと) script.
441    pub artemis_panmimisoft_txt_blacklist_names: std::sync::Arc<std::collections::HashSet<String>>,
442    #[cfg(feature = "artemis-panmimisoft")]
443    /// Specify the language of Artemis TXT (ぱんみみそふと) script.
444    /// If not specified, the first language will be used.
445    pub artemis_panmimisoft_txt_lang: Option<String>,
446    #[cfg(feature = "artemis-panmimisoft")]
447    /// Enable multiple language support for single language Artemis TXT (ぱんみみそふと) script.
448    /// artemis_panmimisoft_txt_lang must be set when enabling this.
449    pub artemis_panmimisoft_txt_multi_lang: bool,
450    #[cfg(feature = "lossless-audio")]
451    /// Audio format for output lossless audio files.
452    pub lossless_audio_fmt: LosslessAudioFormat,
453    #[cfg(feature = "audio-flac")]
454    #[default(5)]
455    /// FLAC compression level for output FLAC audio files. 0 means fastest compression, 8 means best compression. Default level is 5.
456    pub flac_compression_level: u32,
457    #[cfg(feature = "artemis")]
458    #[default(true)]
459    /// Format lua code in Artemis ASB script(.asb/.iet) when exporting.
460    pub artemis_asb_format_lua: bool,
461    #[cfg(feature = "kirikiri")]
462    /// Whether to handle title in Kirikiri SCN script.
463    pub kirikiri_title: bool,
464    #[cfg(feature = "favorite")]
465    #[default(true)]
466    /// Whether to filter ascii strings in Favorite HCB script.
467    pub favorite_hcb_filter_ascii: bool,
468    #[cfg(feature = "bgi-img")]
469    #[default(get_default_threads())]
470    /// Workers count for decode BGI compressed images v2 in parallel. Default is half of CPU cores.
471    /// Set this to 1 to disable parallel decoding. 0 means same as 1.
472    pub bgi_img_workers: usize,
473    #[cfg(feature = "image-jxl")]
474    #[default(true)]
475    /// Use JXL lossless compression for output images. Enabled by default.
476    pub jxl_lossless: bool,
477    #[cfg(feature = "image-jxl")]
478    #[default(1.0)]
479    /// JXL distance for output images. 0 means mathematically lossless compression. 1.0 means visually lossless compression.
480    /// Allowed range is 0.0-25.0. Recommended range is 0.5-3.0. Default value is 1.0.
481    pub jxl_distance: f32,
482    #[cfg(feature = "image-jxl")]
483    #[default(1)]
484    /// Workers count for encode JXL images in parallel. Default is 1.
485    /// Set this to 1 to disable parallel encoding. 0 means same as 1
486    pub jxl_workers: usize,
487    #[cfg(feature = "emote-img")]
488    #[default(true)]
489    /// Process tlg images.
490    pub psb_process_tlg: bool,
491    #[cfg(feature = "softpal-img")]
492    #[default(true)]
493    /// Whether to use fake compression for Softpal Pgd images. Enabled by default.
494    /// WARN: Compress may cause image broken.
495    pub pgd_fake_compress: bool,
496    #[cfg(feature = "softpal")]
497    /// Whether to add message index to Softpal src script when exporting.
498    pub softpal_add_message_index: bool,
499    #[cfg(feature = "kirikiri")]
500    #[default(true)]
501    /// Enable multi-language support for Kirikiri chat messages. Default is true.
502    /// Note: This requires [Self::kirikiri_language_index] and [Self::kirikiri_languages] to be set correctly.
503    pub kirikiri_chat_multilang: bool,
504    #[cfg(feature = "kirikiri-arc")]
505    #[default(true)]
506    /// Decrypt SimpleCrypt files in Kirikiri XP3 archive when extracting. Default is true.
507    pub xp3_simple_crypt: bool,
508    #[cfg(feature = "kirikiri-arc")]
509    #[default(true)]
510    /// Decompress mdf files in Kirikiri XP3 archive when extracting. Default is true.
511    pub xp3_mdf_decompress: bool,
512    #[cfg(feature = "kirikiri-arc")]
513    /// Configuration for Kirikiri XP3 segmenter when creating XP3 archive.
514    pub xp3_segmenter: crate::scripts::kirikiri::archive::xp3::SegmenterConfig,
515    #[cfg(feature = "kirikiri-arc")]
516    #[default(true)]
517    /// Compress files in Kirikiri XP3 archive when creating. Default is true.
518    pub xp3_compress_files: bool,
519    #[cfg(feature = "kirikiri-arc")]
520    #[default(true)]
521    /// Compress index in Kirikiri XP3 archive when creating. Default is true.
522    pub xp3_compress_index: bool,
523    #[cfg(feature = "kirikiri-arc")]
524    #[default(num_cpus::get())]
525    /// Workers count for compress files in Kirikiri XP3 archive when creating in parallel. Default is CPU cores count.
526    pub xp3_compress_workers: usize,
527    #[cfg(feature = "kirikiri-arc")]
528    /// Use zstd compression for files in Kirikiri XP3 archive when creating. (Warning: Kirikiri engine don't support this. Hook is required.)
529    pub xp3_zstd: bool,
530    #[cfg(feature = "kirikiri-arc")]
531    /// Use zopfli compression for files in Kirikiri XP3 archive when creating. This is very slow.
532    pub xp3_zopfli: bool,
533    #[cfg(feature = "kirikiri-arc")]
534    #[default(1)]
535    /// Workers count for packing file in Kirikiri XP3 archive in parallel. Default is 1.
536    /// This not works when segment is disabled.
537    pub xp3_pack_workers: usize,
538    #[cfg(feature = "kirikiri-arc")]
539    /// Disable adler32 checksum for Kirikiri XP3 archive when creating.
540    /// This will keep compatibility with https://github.com/arcusmaximus/KirikiriTools tool.
541    pub xp3_no_adler: bool,
542    #[cfg(feature = "kirikiri")]
543    /// Insert new language at the specified index in Kirikiri SCN script. If index is out of bounds, this flags will be ignored.
544    pub kirikiri_language_insert: bool,
545    #[cfg(feature = "musica-arc")]
546    /// Musica game title for paz archive.
547    pub musica_game_title: Option<String>,
548    #[cfg(feature = "musica-arc")]
549    /// Musica xor key for paz archive.
550    pub musica_xor_key: Option<u8>,
551    #[cfg(feature = "musica-arc")]
552    /// Whether to compress files in Musica paz archive when packing paz archive.
553    pub musica_compress: bool,
554    #[cfg(feature = "bgi")]
555    /// Add an additional space at the end of message in BGI scripts when importing.
556    /// This may help BGI engine to display the message correctly in save/load screen for some games.
557    pub bgi_add_space: bool,
558    #[cfg(feature = "escude")]
559    /// Escude game title
560    pub escude_op: Option<crate::scripts::escude::script::EscudeOp>,
561    #[cfg(feature = "zopfli")]
562    #[default(std::num::NonZeroU64::new(15).unwrap())]
563    /// Maximum amount of times to rerun forward and backward pass to optimize LZ77 compression cost.
564    /// Good values: 10, 15 for small files, 5 for files over several MB in size or it will be too slow.
565    /// Default is 15.
566    pub zopfli_iteration_count: std::num::NonZeroU64,
567    #[cfg(feature = "zopfli")]
568    #[default(std::num::NonZeroU64::new(u64::MAX).unwrap())]
569    /// Stop after rerunning forward and backward pass this many times without finding a smaller representation of the block.
570    /// Default value: practically infinite (maximum u64 value)
571    pub zopfli_iterations_without_improvement: std::num::NonZeroU64,
572    #[cfg(feature = "zopfli")]
573    #[default(15)]
574    /// Maximum amount of blocks to split into (0 for unlimited, but this can give extreme results that hurt compression on some files).
575    /// Default value: 15.
576    pub zopfli_maximum_block_splits: u16,
577    #[cfg(feature = "entis-gls")]
578    /// Whether to disassemble Entis GLS csx script when exporting in custom mode.
579    pub entis_gls_csx_disasm: bool,
580    #[cfg(feature = "entis-gls")]
581    #[default(String::from("/"))]
582    /// The line feed character used in Entis GLS csx script.
583    pub entis_gls_csx_lf: String,
584    #[cfg(feature = "entis-gls")]
585    /// Entis GLS csx script version.
586    /// If not specified. Will try use lower version first.
587    pub entis_gls_csx_ver: Option<crate::scripts::entis_gls::csx::CSXScriptVersion>,
588    #[cfg(feature = "entis-gls")]
589    /// Entis GLS csx script version2 full version.
590    /// If not specified. Will try use higher version first.
591    pub entis_gls_csx_v2_ver: Option<crate::scripts::entis_gls::csx::CSXScriptV2FullVer>,
592    #[cfg(feature = "entis-gls")]
593    /// Disable part labels in Entis GLS csx script when exporting.
594    pub entis_gls_csx_no_part_label: bool,
595    #[cfg(feature = "qlie-img")]
596    #[default(true)]
597    /// Whether to process ABMP10 images in ABMP10 images.
598    pub qlie_abmp10_process_abmp10: bool,
599    #[cfg(feature = "qlie-arc")]
600    /// Path to qlie pack archive key file (pack_keyfile_kfueheish15538fa9or.key)
601    pub qlie_pack_keyfile: Option<String>,
602    #[cfg(feature = "qlie-arc")]
603    /// Whether to compress files in Qlie pack archive.
604    pub qlie_pack_compress_files: bool,
605    #[cfg(feature = "qlie-img")]
606    /// Whether to use PNG file directly for Qlie DPNG images when importing.
607    /// Enable this will disable reencoding PNG files. Useful when the PNG files are already optimized by other tools.
608    pub qlie_dpng_use_raw_png: bool,
609    #[cfg(feature = "qlie-img")]
610    /// Export Qlie DPNG images as PSD files.
611    pub qlie_dpng_psd: bool,
612    #[cfg(feature = "utils-psd")]
613    #[default(true)]
614    /// Whether to use compression for image data in PSD files.
615    pub psd_compress: bool,
616    #[cfg(feature = "emote-img")]
617    /// Export Emote PIMG images as PSD files.
618    pub emote_pimg_psd: bool,
619    #[cfg(feature = "kirikiri")]
620    /// Whether to only extract message between Talk and Hitret command in Kirikiri KS script. Auto detect if not specified.
621    pub kirikiri_ks_hitret: Option<bool>,
622    #[cfg(feature = "kirikiri")]
623    /// The line feed character used in Kirikiri KS script.
624    pub kirikiri_ks_lf: Option<String>,
625    #[cfg(feature = "kirikiri")]
626    /// Kirikiri message tags, used to extract more message from ks script.
627    pub kirikiri_message_tags: std::sync::Arc<std::collections::HashSet<String>>,
628    #[cfg(feature = "kirikiri")]
629    /// Specifiy BOM type when creating new Kirikiri ks script. If not specified, detect from original script.
630    pub kirikiri_ks_bom: Option<BomType>,
631    #[cfg(feature = "emote-img")]
632    /// BC7 compress configuration
633    pub bc7: crate::scripts::emote::psb::BC7Config,
634}
635
636#[derive(Clone, Copy, Debug, ValueEnum, PartialEq, Eq, PartialOrd, Ord)]
637/// Script type
638pub enum ScriptType {
639    #[cfg(feature = "artemis")]
640    /// Artemis Engine AST script
641    Artemis,
642    #[cfg(feature = "artemis")]
643    /// Artemis Engine ASB script
644    ArtemisAsb,
645    #[cfg(feature = "artemis")]
646    /// Artemis Engine TXT (General) script
647    ArtemisTxt,
648    #[cfg(feature = "artemis-panmimisoft")]
649    /// Artemis Engine TXT (ぱんみみそふと) script
650    ArtemisPanmimisoftTxt,
651    #[cfg(feature = "artemis-arc")]
652    #[value(alias("pfs"))]
653    /// Artemis archive (pfs)
654    ArtemisArc,
655    #[cfg(feature = "artemis-arc")]
656    #[value(alias("pf2"))]
657    /// Artemis archive (pf2) (.pfs)
658    ArtemisPf2,
659    #[cfg(feature = "bgi")]
660    #[value(alias("ethornell"))]
661    /// Buriko General Interpreter/Ethornell Script
662    BGI,
663    #[cfg(feature = "bgi")]
664    #[value(alias("ethornell-bsi"))]
665    /// Buriko General Interpreter/Ethornell bsi script (._bsi)
666    BGIBsi,
667    #[cfg(feature = "bgi")]
668    #[value(alias("ethornell-bp"))]
669    /// Buriko General Interpreter/Ethornell bp script (._bp)
670    BGIBp,
671    #[cfg(feature = "bgi-arc")]
672    #[value(alias = "ethornell-arc-v1")]
673    /// Buriko General Interpreter/Ethornell archive v1
674    BGIArcV1,
675    #[cfg(feature = "bgi-arc")]
676    #[value(alias = "ethornell-arc-v2", alias = "bgi-arc", alias = "ethornell-arc")]
677    /// Buriko General Interpreter/Ethornell archive v2
678    BGIArcV2,
679    #[cfg(feature = "bgi-arc")]
680    #[value(alias("ethornell-dsc"))]
681    /// Buriko General Interpreter/Ethornell compressed file (DSC)
682    BGIDsc,
683    #[cfg(feature = "bgi-audio")]
684    #[value(alias("ethornell-audio"))]
685    /// Buriko General Interpreter/Ethornell audio file (Ogg/Vorbis)
686    BGIAudio,
687    #[cfg(feature = "bgi-img")]
688    #[value(alias("ethornell-img"))]
689    /// Buriko General Interpreter/Ethornell image (Image files in sysgrp.arc)
690    BGIImg,
691    #[cfg(feature = "bgi-img")]
692    #[value(alias("ethornell-cbg"))]
693    /// Buriko General Interpreter/Ethornell Compressed Background image (CBG)
694    BGICbg,
695    #[cfg(feature = "cat-system")]
696    /// CatSystem2 engine scene script
697    CatSystem,
698    #[cfg(feature = "cat-system")]
699    /// CatSystem2 engine CSTL script
700    CatSystemCstl,
701    #[cfg(feature = "cat-system-arc")]
702    /// CatSystem2 engine archive
703    CatSystemInt,
704    #[cfg(feature = "cat-system-img")]
705    /// CatSystem2 engine image
706    CatSystemHg3,
707    #[cfg(feature = "circus")]
708    /// Circus MES script
709    Circus,
710    #[cfg(feature = "circus-arc")]
711    /// Circus Image archive
712    CircusCrm,
713    #[cfg(feature = "circus-arc")]
714    /// Circus DAT archive
715    CircusDat,
716    #[cfg(feature = "circus-arc")]
717    /// Circus PCK archive
718    CircusPck,
719    #[cfg(feature = "circus-audio")]
720    /// Circus PCM audio
721    CircusPcm,
722    #[cfg(feature = "circus-img")]
723    /// Circus CRX Image
724    CircusCrx,
725    #[cfg(feature = "circus-img")]
726    /// Circus Differential Image
727    CircusCrxd,
728    #[cfg(feature = "emote-img")]
729    #[value(alias("psb"))]
730    /// Emote PSB (basic handle)
731    EmotePsb,
732    #[cfg(feature = "emote-img")]
733    #[value(alias("pimg"))]
734    /// Emote PIMG image
735    EmotePimg,
736    #[cfg(feature = "emote-img")]
737    #[value(alias("dref"))]
738    /// Emote DREF(DPAK-referenced) image
739    EmoteDref,
740    #[cfg(feature = "entis-gls")]
741    /// Entis GLS srcxml Script
742    EntisGls,
743    #[cfg(feature = "entis-gls")]
744    /// Entis GLS csx script
745    EntisGlsCsx,
746    #[cfg(feature = "escude-arc")]
747    /// Escude bin archive
748    EscudeArc,
749    #[cfg(feature = "escude")]
750    /// Escude bin script
751    Escude,
752    #[cfg(feature = "escude")]
753    /// Escude list script
754    EscudeList,
755    #[cfg(feature = "ex-hibit")]
756    /// ExHibit rld script
757    ExHibit,
758    #[cfg(feature = "ex-hibit-arc")]
759    /// ExHibit GRP archive
760    ExHibitGrp,
761    #[cfg(feature = "favorite")]
762    /// Favorite hcb script
763    Favorite,
764    #[cfg(feature = "hexen-haus")]
765    /// HexenHaus bin script
766    HexenHaus,
767    #[cfg(feature = "hexen-haus-arc")]
768    /// HexenHaus Arcc archive
769    HexenHausArcc,
770    #[cfg(feature = "hexen-haus-arc")]
771    /// HexenHaus Audio archive
772    HexenHausOdio,
773    #[cfg(feature = "hexen-haus-arc")]
774    /// HexenHaus WAG archive
775    HexenHausWag,
776    #[cfg(feature = "hexen-haus-img")]
777    /// HexenHaus PNG image
778    HexenHausPng,
779    #[cfg(feature = "kirikiri")]
780    #[value(alias("kr-scn"))]
781    /// Kirikiri SCN script
782    KirikiriScn,
783    #[cfg(feature = "kirikiri")]
784    #[value(alias("kr-simple-crypt"))]
785    /// Kirikiri SimpleCrypt's text file
786    KirikiriSimpleCrypt,
787    #[cfg(feature = "kirikiri")]
788    #[value(alias = "kr", alias = "kr-ks", alias = "kirikiri-ks")]
789    /// Kirikiri script
790    Kirikiri,
791    #[cfg(feature = "kirikiri-arc")]
792    #[value(alias = "kr-xp3", alias = "xp3")]
793    /// Kirikiri XP3 archive
794    KirikiriXp3,
795    #[cfg(feature = "kirikiri-img")]
796    #[value(alias("kr-tlg"))]
797    /// Kirikiri TLG image
798    KirikiriTlg,
799    #[cfg(feature = "kirikiri")]
800    #[value(alias("kr-mdf"))]
801    /// Kirikiri MDF (zlib compressed) file
802    KirikiriMdf,
803    #[cfg(feature = "kirikiri")]
804    #[value(alias("kr-tjs2"))]
805    /// Kirikiri compiled TJS2 script
806    KirikiriTjs2,
807    #[cfg(feature = "kirikiri")]
808    #[value(alias("kr-tjs-ns0"))]
809    /// Kirikiri TJS NS0 binary encoded script
810    KirikiriTjsNs0,
811    #[cfg(feature = "musica")]
812    /// Musica Script (.sc)
813    Musica,
814    #[cfg(feature = "musica-arc")]
815    /// Musica Engine Resource Archive (.paz)
816    MusicaPaz,
817    #[cfg(feature = "qlie")]
818    /// Qlie Engine Scenario script (.s)
819    Qlie,
820    #[cfg(feature = "qlie-arc")]
821    /// Qlie Pack Archive (.pack)
822    QliePack,
823    #[cfg(feature = "qlie-img")]
824    /// Qlie tiled PNG image (.png)
825    QlieDpng,
826    #[cfg(feature = "qlie-img")]
827    #[value(alias = "qlie-abmp11", alias = "qlie-abmp12")]
828    /// Qlie Abmp10/11/12 image (.b)
829    QlieAbmp10,
830    #[cfg(feature = "silky")]
831    /// Silky Engine Mes script
832    Silky,
833    #[cfg(feature = "silky")]
834    /// Silky Engine Map script
835    SilkyMap,
836    #[cfg(feature = "softpal")]
837    /// Softpal src script
838    Softpal,
839    #[cfg(feature = "softpal-arc")]
840    /// Softpal Pac archive
841    SoftpalPac,
842    #[cfg(feature = "softpal-arc")]
843    /// Softpal Pac/AMUSE archive
844    SoftpalPacAmuse,
845    #[cfg(feature = "softpal-img")]
846    #[value(alias = "pgd-ge", alias = "pgd")]
847    /// Softpal Pgd Ge image
848    SoftpalPgdGe,
849    #[cfg(feature = "softpal-img")]
850    #[value(alias = "softpal-pgd2", alias = "pgd3", alias = "pgd2")]
851    /// Softpal Pgd Differential image
852    SoftpalPgd3,
853    #[cfg(feature = "will-plus")]
854    #[value(alias("adv-hd-ws2"))]
855    /// WillPlus ws2 script
856    WillPlusWs2,
857    #[cfg(feature = "will-plus-img")]
858    #[value(alias("adv-hd-wip"))]
859    /// WillPlus WIP Image
860    WillPlusWip,
861    #[cfg(feature = "yaneurao-itufuru")]
862    #[value(alias("itufuru"))]
863    /// Yaneurao Itufuru script
864    YaneuraoItufuru,
865    #[cfg(feature = "yaneurao-itufuru")]
866    #[value(alias("itufuru-arc"))]
867    /// Yaneurao Itufuru script archive
868    YaneuraoItufuruArc,
869}
870
871#[derive(Clone, Debug, Serialize, Deserialize)]
872/// Message structure for scripts
873pub struct Message {
874    #[serde(skip_serializing_if = "Option::is_none")]
875    /// Optional name for the message, used in some scripts.
876    pub name: Option<String>,
877    /// The actual message content.
878    pub message: String,
879}
880
881impl Message {
882    /// Creates a new `Message` instance.
883    pub fn new(message: String, name: Option<String>) -> Self {
884        Message { message, name }
885    }
886}
887
888#[derive(Clone, Debug)]
889/// Extended message structure for scripts
890pub struct ExtendedMessage {
891    /// Optional name for the message, used in some scripts.
892    pub name: Option<String>,
893    /// Original source text.
894    pub source: String,
895    /// Translated text.
896    pub translated: String,
897    /// Optional LLM translated text.
898    pub llm: Option<String>,
899}
900
901/// Result of script operation.
902pub enum ScriptResult {
903    /// Operation completed successfully.
904    Ok,
905    /// Operation completed without any changes.
906    /// For example, no messages found in the script.
907    Ignored,
908    /// Operation not completed.
909    /// This will not count in statistics.
910    Uncount,
911}
912
913#[derive(Clone, Copy, Debug, ValueEnum, PartialEq, Eq, PartialOrd, Ord)]
914/// Format type (for CLI)
915pub enum FormatType {
916    /// Wrap line with fixed length
917    Fixed,
918    /// Do not wrap line
919    None,
920}
921
922#[derive(Clone)]
923/// Format options
924pub enum FormatOptions {
925    /// Wrap line with fixed length
926    Fixed {
927        /// Fixed length
928        length: usize,
929        /// Whether to keep original line breaks
930        keep_original: bool,
931        /// Whether to break words(ASCII only) at the end of the line
932        break_words: bool,
933        /// Whether to insert a full-width space after a line break when a sentence starts with a full-width quotation mark.
934        insert_fullwidth_space_at_line_start: bool,
935        /// If a line break occurs in the middle of some symbols, bring the sentence to next line
936        break_with_sentence: bool,
937        #[cfg(feature = "jieba")]
938        /// Whether to break Chinese words at the end of the line.
939        break_chinese_words: bool,
940        #[cfg(feature = "jieba")]
941        /// Path to custom jieba dictionary
942        jieba_dict: Option<String>,
943        /// Do not remove space at the start of the line
944        no_remove_space_at_line_start: bool,
945    },
946    /// Do not wrap line
947    None,
948}
949
950#[derive(Debug, Serialize, Deserialize)]
951/// Name table cell
952pub struct NameTableCell {
953    #[serde(rename = "JP_Name")]
954    /// Original name
955    pub jp_name: String,
956    #[serde(rename = "CN_Name")]
957    /// Translated name
958    pub cn_name: String,
959    #[serde(rename = "Count")]
960    /// Number of times this name appears in the script
961    pub count: usize,
962}
963
964#[derive(Debug, Serialize, Deserialize)]
965/// Replacement table for string replacements
966pub struct ReplacementTable {
967    #[serde(flatten)]
968    /// Map of original strings to their replacements
969    pub map: HashMap<String, String>,
970}
971
972#[cfg(feature = "image")]
973#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
974/// Image color type
975pub enum ImageColorType {
976    /// Grayscale image
977    Grayscale,
978    /// RGB image
979    Rgb,
980    /// RGBA image
981    Rgba,
982    /// BGR image
983    Bgr,
984    /// BGRA image
985    Bgra,
986}
987
988#[cfg(feature = "image")]
989impl ImageColorType {
990    /// Returns the number of bytes per pixel for the color type and depth.
991    pub fn bpp(&self, depth: u8) -> u16 {
992        match self {
993            ImageColorType::Grayscale => depth as u16,
994            ImageColorType::Rgb => depth as u16 * 3,
995            ImageColorType::Rgba => depth as u16 * 4,
996            ImageColorType::Bgr => depth as u16 * 3,
997            ImageColorType::Bgra => depth as u16 * 4,
998        }
999    }
1000}
1001
1002#[cfg(feature = "image")]
1003#[derive(Clone, Copy, Debug, ValueEnum, PartialEq, Eq, PartialOrd, Ord)]
1004/// Image output type
1005pub enum ImageOutputType {
1006    /// PNG image
1007    Png,
1008    #[cfg(feature = "image-jpg")]
1009    /// JPEG image
1010    Jpg,
1011    #[cfg(feature = "image-webp")]
1012    /// WebP image
1013    Webp,
1014    #[cfg(feature = "image-jxl")]
1015    /// JPEG XL image
1016    Jxl,
1017}
1018
1019#[cfg(feature = "image")]
1020impl TryFrom<&str> for ImageOutputType {
1021    type Error = anyhow::Error;
1022
1023    /// Try to convert a extension string to an `ImageOutputType`.
1024    /// Extensions are case-insensitive.
1025    fn try_from(value: &str) -> Result<Self, Self::Error> {
1026        match value.to_ascii_lowercase().as_str() {
1027            "png" => Ok(ImageOutputType::Png),
1028            #[cfg(feature = "image-jpg")]
1029            "jpg" => Ok(ImageOutputType::Jpg),
1030            #[cfg(feature = "image-jpg")]
1031            "jpeg" => Ok(ImageOutputType::Jpg),
1032            #[cfg(feature = "image-webp")]
1033            "webp" => Ok(ImageOutputType::Webp),
1034            #[cfg(feature = "image-jxl")]
1035            "jxl" => Ok(ImageOutputType::Jxl),
1036            _ => Err(anyhow::anyhow!("Unsupported image output type: {}", value)),
1037        }
1038    }
1039}
1040
1041#[cfg(feature = "image")]
1042impl TryFrom<&std::path::Path> for ImageOutputType {
1043    type Error = anyhow::Error;
1044
1045    fn try_from(value: &std::path::Path) -> Result<Self, Self::Error> {
1046        if let Some(ext) = value.extension() {
1047            Self::try_from(ext.to_string_lossy().as_ref())
1048        } else {
1049            Err(anyhow::anyhow!("No extension found in path"))
1050        }
1051    }
1052}
1053
1054#[cfg(feature = "image")]
1055impl AsRef<str> for ImageOutputType {
1056    /// Returns the extension for the image output type.
1057    fn as_ref(&self) -> &str {
1058        match self {
1059            ImageOutputType::Png => "png",
1060            #[cfg(feature = "image-jpg")]
1061            ImageOutputType::Jpg => "jpg",
1062            #[cfg(feature = "image-webp")]
1063            ImageOutputType::Webp => "webp",
1064            #[cfg(feature = "image-jxl")]
1065            ImageOutputType::Jxl => "jxl",
1066        }
1067    }
1068}
1069
1070#[cfg(feature = "image")]
1071#[derive(Clone, Debug)]
1072/// Image data
1073pub struct ImageData {
1074    /// Image width in pixels
1075    pub width: u32,
1076    /// Image height in pixels
1077    pub height: u32,
1078    /// Image color type
1079    pub color_type: ImageColorType,
1080    /// Image depth in bits per channel
1081    pub depth: u8,
1082    /// Image data
1083    pub data: Vec<u8>,
1084}
1085
1086#[cfg(feature = "image")]
1087#[derive(Clone, Debug)]
1088/// Image data with name
1089pub struct ImageDataWithName {
1090    /// Image name
1091    pub name: String,
1092    /// Image data
1093    pub data: ImageData,
1094}
1095
1096#[derive(Clone, Copy, Debug, ValueEnum, PartialEq, Eq, PartialOrd, Ord)]
1097/// BOM type
1098pub enum BomType {
1099    /// No BOM
1100    None,
1101    /// UTF-8 BOM
1102    Utf8,
1103    /// UTF-16 Little Endian BOM
1104    Utf16LE,
1105    /// UTF-16 Big Endian BOM
1106    Utf16BE,
1107}
1108
1109impl BomType {
1110    /// Returns the byte sequence for the BOM type.
1111    pub fn as_bytes(&self) -> &'static [u8] {
1112        match self {
1113            BomType::None => &[],
1114            BomType::Utf8 => b"\xEF\xBB\xBF",
1115            BomType::Utf16LE => b"\xFF\xFE",
1116            BomType::Utf16BE => b"\xFE\xFF",
1117        }
1118    }
1119}
1120
1121#[cfg(feature = "image")]
1122#[derive(Clone, Copy, Debug, ValueEnum, PartialEq, Eq, PartialOrd, Ord)]
1123/// PNG compression level
1124pub enum PngCompressionLevel {
1125    #[value(alias = "n")]
1126    /// No compression whatsoever. Fastest, but results in large files.
1127    NoCompression,
1128    #[value(alias = "d")]
1129    /// Default level (usually balanced)
1130    Default,
1131    /// Extremely fast but light compression.
1132    ///
1133    /// Note: When used in streaming mode, this compression level can actually result in files
1134    /// *larger* than would be produced by `NoCompression` on incompressible data because
1135    /// it doesn't do any buffering of the output stream to detect whether the data is being compressed or not.
1136    Fastest,
1137    #[value(alias = "f")]
1138    /// Fast minimal compression
1139    Fast,
1140    #[value(alias = "b")]
1141    /// Higher compression level. Same as high
1142    Best,
1143    #[value(alias = "h")]
1144    /// Spend much more time to produce a slightly smaller file than with `Balanced`.
1145    High,
1146}
1147
1148#[cfg(feature = "image")]
1149impl Default for PngCompressionLevel {
1150    fn default() -> Self {
1151        PngCompressionLevel::Default
1152    }
1153}
1154
1155#[cfg(feature = "image")]
1156impl PngCompressionLevel {
1157    /// Converts the [PngCompressionLevel] to a [png::Compression] enum.
1158    pub fn to_compression(&self) -> png::Compression {
1159        match self {
1160            PngCompressionLevel::NoCompression => png::Compression::NoCompression,
1161            PngCompressionLevel::Fastest => png::Compression::Fastest,
1162            PngCompressionLevel::Default => png::Compression::Balanced,
1163            PngCompressionLevel::Fast => png::Compression::Fast,
1164            PngCompressionLevel::Best => png::Compression::High,
1165            PngCompressionLevel::High => png::Compression::High,
1166        }
1167    }
1168}
1169
1170#[cfg(feature = "lossless-audio")]
1171#[derive(Clone, Copy, Debug, ValueEnum, PartialEq, Eq, PartialOrd, Ord)]
1172/// Lossless audio format
1173pub enum LosslessAudioFormat {
1174    /// Wav
1175    Wav,
1176    #[cfg(feature = "audio-flac")]
1177    /// FLAC Format
1178    Flac,
1179}
1180
1181#[cfg(feature = "lossless-audio")]
1182impl Default for LosslessAudioFormat {
1183    fn default() -> Self {
1184        LosslessAudioFormat::Wav
1185    }
1186}
1187
1188#[cfg(feature = "lossless-audio")]
1189impl TryFrom<&str> for LosslessAudioFormat {
1190    type Error = anyhow::Error;
1191    /// Try to convert a extension string to an `LosslessAudioFormat`.
1192    /// Extensions are case-insensitive.
1193    fn try_from(value: &str) -> Result<Self, Self::Error> {
1194        match value.to_ascii_lowercase().as_str() {
1195            "wav" => Ok(LosslessAudioFormat::Wav),
1196            #[cfg(feature = "audio-flac")]
1197            "flac" => Ok(LosslessAudioFormat::Flac),
1198            _ => Err(anyhow::anyhow!(
1199                "Unsupported lossless audio format: {}",
1200                value
1201            )),
1202        }
1203    }
1204}
1205
1206#[cfg(feature = "lossless-audio")]
1207impl TryFrom<&std::path::Path> for LosslessAudioFormat {
1208    type Error = anyhow::Error;
1209
1210    fn try_from(value: &std::path::Path) -> Result<Self, Self::Error> {
1211        if let Some(ext) = value.extension() {
1212            Self::try_from(ext.to_string_lossy().as_ref())
1213        } else {
1214            Err(anyhow::anyhow!("No extension found in path"))
1215        }
1216    }
1217}
1218
1219#[cfg(feature = "lossless-audio")]
1220impl AsRef<str> for LosslessAudioFormat {
1221    /// Returns the extension for the lossless audio format.
1222    fn as_ref(&self) -> &str {
1223        match self {
1224            LosslessAudioFormat::Wav => "wav",
1225            #[cfg(feature = "audio-flac")]
1226            LosslessAudioFormat::Flac => "flac",
1227        }
1228    }
1229}
1230
1231#[allow(unused)]
1232pub(crate) fn get_default_threads() -> usize {
1233    num_cpus::get().max(2) / 2
1234}