1#![cfg_attr(any(docsrs, feature = "unstable"), feature(doc_cfg))]
2pub mod args;
3pub mod ext;
4pub mod format;
5pub mod output_scripts;
6pub mod scripts;
7pub mod types;
8pub mod utils;
9
10use ext::path::PathBufExt;
11use scripts::base::ArchiveContent;
12
13fn escape_dep_string(s: &str) -> String {
14 s.replace("\\", "\\\\").replace(" ", "\\ ")
15}
16
17fn get_encoding(
18 arg: &args::Arg,
19 builder: &Box<dyn scripts::ScriptBuilder + Send + Sync>,
20) -> types::Encoding {
21 match &arg.encoding {
22 Some(enc) => {
23 return match enc {
24 &types::TextEncoding::Default => builder.default_encoding(),
25 &types::TextEncoding::Auto => types::Encoding::Auto,
26 &types::TextEncoding::Cp932 => types::Encoding::Cp932,
27 &types::TextEncoding::Utf8 => types::Encoding::Utf8,
28 &types::TextEncoding::Gb2312 => types::Encoding::Gb2312,
29 };
30 }
31 None => {}
32 }
33 #[cfg(windows)]
34 match &arg.code_page {
35 Some(code_page) => {
36 return types::Encoding::CodePage(*code_page);
37 }
38 None => {}
39 }
40 builder.default_encoding()
41}
42
43fn get_archived_encoding(
44 arg: &args::Arg,
45 builder: &Box<dyn scripts::ScriptBuilder + Send + Sync>,
46 encoding: types::Encoding,
47) -> types::Encoding {
48 match &arg.archive_encoding {
49 Some(enc) => {
50 return match enc {
51 &types::TextEncoding::Default => builder
52 .default_archive_encoding()
53 .unwrap_or_else(|| builder.default_encoding()),
54 &types::TextEncoding::Auto => types::Encoding::Auto,
55 &types::TextEncoding::Cp932 => types::Encoding::Cp932,
56 &types::TextEncoding::Utf8 => types::Encoding::Utf8,
57 &types::TextEncoding::Gb2312 => types::Encoding::Gb2312,
58 };
59 }
60 None => {}
61 }
62 #[cfg(windows)]
63 match &arg.archive_code_page {
64 Some(code_page) => {
65 return types::Encoding::CodePage(*code_page);
66 }
67 None => {}
68 }
69 builder.default_archive_encoding().unwrap_or(encoding)
70}
71
72fn get_input_output_script_encoding(arg: &args::Arg) -> types::Encoding {
73 match &arg.encoding {
74 Some(enc) => {
75 return match enc {
76 &types::TextEncoding::Default => types::Encoding::Utf8,
77 &types::TextEncoding::Auto => types::Encoding::Utf8,
78 &types::TextEncoding::Cp932 => types::Encoding::Cp932,
79 &types::TextEncoding::Utf8 => types::Encoding::Utf8,
80 &types::TextEncoding::Gb2312 => types::Encoding::Gb2312,
81 };
82 }
83 None => {}
84 }
85 #[cfg(windows)]
86 match &arg.code_page {
87 Some(code_page) => {
88 return types::Encoding::CodePage(*code_page);
89 }
90 None => {}
91 }
92 types::Encoding::Utf8
93}
94
95fn get_output_encoding(arg: &args::Arg) -> types::Encoding {
96 match &arg.output_encoding {
97 Some(enc) => {
98 return match enc {
99 &types::TextEncoding::Default => types::Encoding::Utf8,
100 &types::TextEncoding::Auto => types::Encoding::Utf8,
101 &types::TextEncoding::Cp932 => types::Encoding::Cp932,
102 &types::TextEncoding::Utf8 => types::Encoding::Utf8,
103 &types::TextEncoding::Gb2312 => types::Encoding::Gb2312,
104 };
105 }
106 None => {}
107 }
108 #[cfg(windows)]
109 match &arg.output_code_page {
110 Some(code_page) => {
111 return types::Encoding::CodePage(*code_page);
112 }
113 None => {}
114 }
115 types::Encoding::Utf8
116}
117
118fn get_patched_encoding(
119 arg: &args::ImportArgs,
120 builder: &Box<dyn scripts::ScriptBuilder + Send + Sync>,
121) -> types::Encoding {
122 match &arg.patched_encoding {
123 Some(enc) => {
124 return match enc {
125 &types::TextEncoding::Default => builder.default_patched_encoding(),
126 &types::TextEncoding::Auto => types::Encoding::Utf8,
127 &types::TextEncoding::Cp932 => types::Encoding::Cp932,
128 &types::TextEncoding::Utf8 => types::Encoding::Utf8,
129 &types::TextEncoding::Gb2312 => types::Encoding::Gb2312,
130 };
131 }
132 None => {}
133 }
134 #[cfg(windows)]
135 match &arg.patched_code_page {
136 Some(code_page) => {
137 return types::Encoding::CodePage(*code_page);
138 }
139 None => {}
140 }
141 builder.default_patched_encoding()
142}
143
144fn get_patched_archive_encoding(
145 arg: &args::ImportArgs,
146 builder: &Box<dyn scripts::ScriptBuilder + Send + Sync>,
147 encoding: types::Encoding,
148) -> types::Encoding {
149 match &arg.patched_archive_encoding {
150 Some(enc) => {
151 return match enc {
152 &types::TextEncoding::Default => {
153 builder.default_archive_encoding().unwrap_or(encoding)
154 }
155 &types::TextEncoding::Auto => types::Encoding::Utf8,
156 &types::TextEncoding::Cp932 => types::Encoding::Cp932,
157 &types::TextEncoding::Utf8 => types::Encoding::Utf8,
158 &types::TextEncoding::Gb2312 => types::Encoding::Gb2312,
159 };
160 }
161 None => {}
162 }
163 #[cfg(windows)]
164 match &arg.patched_archive_code_page {
165 Some(code_page) => {
166 return types::Encoding::CodePage(*code_page);
167 }
168 None => {}
169 }
170 builder.default_archive_encoding().unwrap_or(encoding)
171}
172
173pub fn parse_script(
174 filename: &str,
175 arg: &args::Arg,
176 config: std::sync::Arc<types::ExtraConfig>,
177) -> anyhow::Result<(
178 Box<dyn scripts::Script>,
179 &'static Box<dyn scripts::ScriptBuilder + Send + Sync>,
180)> {
181 match &arg.script_type {
182 Some(typ) => {
183 for builder in scripts::BUILDER.iter() {
184 if typ == builder.script_type() {
185 let encoding = get_encoding(arg, builder);
186 let archive_encoding = get_archived_encoding(arg, builder, encoding);
187 return Ok((
188 builder.build_script_from_file(
189 filename,
190 encoding,
191 archive_encoding,
192 &config,
193 None,
194 )?,
195 builder,
196 ));
197 }
198 }
199 }
200 _ => {}
201 }
202 let mut exts_builder = Vec::new();
203 for builder in scripts::BUILDER.iter() {
204 let exts = builder.extensions();
205 for ext in exts {
206 if filename.to_lowercase().ends_with(ext) {
207 exts_builder.push(builder);
208 break;
209 }
210 }
211 }
212 let exts_builder = if exts_builder.is_empty() {
213 scripts::BUILDER.iter().collect::<Vec<_>>()
214 } else {
215 exts_builder
216 };
217 if exts_builder.len() == 1 {
218 let builder = exts_builder.first().unwrap();
219 let encoding = get_encoding(arg, builder);
220 let archive_encoding = get_archived_encoding(arg, builder, encoding);
221 return Ok((
222 builder.build_script_from_file(filename, encoding, archive_encoding, &config, None)?,
223 builder,
224 ));
225 }
226 let mut buf = [0u8; 1024];
227 let mut size = 0;
228 if filename != "-" {
229 let mut f = std::fs::File::open(filename)?;
230 size = std::io::Read::read(&mut f, &mut buf)?;
231 }
232 let mut scores = Vec::new();
233 for builder in exts_builder.iter() {
234 if let Some(score) = builder.is_this_format(filename, &buf, size) {
235 scores.push((score, builder));
236 }
237 }
238 if scores.is_empty() {
239 return Err(anyhow::anyhow!("Unsupported script type"));
240 }
241 let max_score = scores.iter().map(|s| s.0).max().unwrap();
242 let mut best_builders = Vec::new();
243 for (score, builder) in scores.iter() {
244 if *score == max_score {
245 best_builders.push(builder);
246 }
247 }
248 if best_builders.len() == 1 {
249 let builder = best_builders.first().unwrap();
250 let encoding = get_encoding(arg, builder);
251 let archive_encoding = get_archived_encoding(arg, builder, encoding);
252 return Ok((
253 builder.build_script_from_file(filename, encoding, archive_encoding, &config, None)?,
254 builder,
255 ));
256 }
257 if best_builders.len() > 1 {
258 eprintln!(
259 "Multiple script types found for {}: {:?}",
260 filename, best_builders
261 );
262 return Err(anyhow::anyhow!("Multiple script types found"));
263 }
264 Err(anyhow::anyhow!("Unsupported script type"))
265}
266
267pub fn parse_script_from_archive<'a>(
268 file: &mut Box<dyn ArchiveContent + 'a>,
269 arg: &args::Arg,
270 config: std::sync::Arc<types::ExtraConfig>,
271 archive: &Box<dyn scripts::Script>,
272) -> anyhow::Result<(
273 Box<dyn scripts::Script>,
274 &'static Box<dyn scripts::ScriptBuilder + Send + Sync>,
275)> {
276 match file.script_type() {
277 Some(typ) => {
278 for builder in scripts::BUILDER.iter() {
279 if typ == builder.script_type() {
280 let encoding = get_encoding(arg, builder);
281 let archive_encoding = get_archived_encoding(arg, builder, encoding);
282 return Ok((
283 builder.build_script(
284 file.data()?,
285 file.name(),
286 encoding,
287 archive_encoding,
288 &config,
289 Some(archive),
290 )?,
291 builder,
292 ));
293 }
294 }
295 }
296 _ => {}
297 }
298 let mut exts_builder = Vec::new();
299 for builder in scripts::BUILDER.iter() {
300 let exts = builder.extensions();
301 for ext in exts {
302 if file.name().to_lowercase().ends_with(ext) {
303 exts_builder.push(builder);
304 break;
305 }
306 }
307 }
308 let exts_builder = if exts_builder.is_empty() {
309 scripts::BUILDER.iter().collect::<Vec<_>>()
310 } else {
311 exts_builder
312 };
313 if exts_builder.len() == 1 {
314 let builder = exts_builder.first().unwrap();
315 let encoding = get_encoding(arg, builder);
316 let archive_encoding = get_archived_encoding(arg, builder, encoding);
317 return Ok((
318 builder.build_script(
319 file.data()?,
320 file.name(),
321 encoding,
322 archive_encoding,
323 &config,
324 Some(archive),
325 )?,
326 builder,
327 ));
328 }
329 let buf = file.data()?;
330 let mut scores = Vec::new();
331 for builder in exts_builder.iter() {
332 if let Some(score) = builder.is_this_format(file.name(), buf.as_slice(), buf.len()) {
333 scores.push((score, builder));
334 }
335 }
336 if scores.is_empty() {
337 return Err(anyhow::anyhow!("Unsupported script type"));
338 }
339 let max_score = scores.iter().map(|s| s.0).max().unwrap();
340 let mut best_builders = Vec::new();
341 for (score, builder) in scores.iter() {
342 if *score == max_score {
343 best_builders.push(builder);
344 }
345 }
346 if best_builders.len() == 1 {
347 let builder = best_builders.first().unwrap();
348 let encoding = get_encoding(arg, builder);
349 let archive_encoding = get_archived_encoding(arg, builder, encoding);
350 return Ok((
351 builder.build_script(
352 buf,
353 file.name(),
354 encoding,
355 archive_encoding,
356 &config,
357 Some(archive),
358 )?,
359 builder,
360 ));
361 }
362 if best_builders.len() > 1 {
363 eprintln!(
364 "Multiple script types found for {}: {:?}",
365 file.name(),
366 best_builders
367 );
368 return Err(anyhow::anyhow!("Multiple script types found"));
369 }
370 Err(anyhow::anyhow!("Unsupported script type"))
371}
372
373pub fn export_script(
374 filename: &str,
375 arg: &args::Arg,
376 config: std::sync::Arc<types::ExtraConfig>,
377 output: &Option<String>,
378 root_dir: Option<&std::path::Path>,
379 #[cfg(feature = "image")] img_threadpool: Option<
380 &utils::threadpool::ThreadPool<Result<(), anyhow::Error>>,
381 >,
382) -> anyhow::Result<types::ScriptResult> {
383 eprintln!("Exporting {}", filename);
384 let script = parse_script(filename, arg, config.clone())?.0;
385 if script.is_archive() {
386 let odir = match output.as_ref() {
387 Some(output) => {
388 let mut pb = std::path::PathBuf::from(output);
389 let filename = std::path::PathBuf::from(filename);
390 if let Some(root_dir) = root_dir {
391 let rpath = utils::files::relative_path(root_dir, &filename);
392 if let Some(parent) = rpath.parent() {
393 pb.push(parent);
394 }
395 if let Some(fname) = filename.file_name() {
396 pb.push(fname);
397 }
398 }
399 pb.set_extension("");
400 if let Some(ext) = script.archive_output_ext() {
401 pb.set_extension(ext);
402 }
403 pb.to_string_lossy().into_owned()
404 }
405 None => {
406 let mut pb = std::path::PathBuf::from(filename);
407 pb.set_extension("");
408 if let Some(ext) = script.archive_output_ext() {
409 pb.set_extension(ext);
410 }
411 pb.to_string_lossy().into_owned()
412 }
413 };
414 if !std::fs::exists(&odir)? {
415 std::fs::create_dir_all(&odir)?;
416 }
417 for (i, filename) in script.iter_archive_filename()?.enumerate() {
418 let filename = match filename {
419 Ok(f) => f,
420 Err(e) => {
421 eprintln!("Error reading archive filename: {}", e);
422 COUNTER.inc_error();
423 if arg.backtrace {
424 eprintln!("Backtrace: {}", e.backtrace());
425 }
426 continue;
427 }
428 };
429 let mut f = match script.open_file(i) {
430 Ok(f) => f,
431 Err(e) => {
432 eprintln!("Error opening file {}: {}", filename, e);
433 COUNTER.inc_error();
434 if arg.backtrace {
435 eprintln!("Backtrace: {}", e.backtrace());
436 }
437 continue;
438 }
439 };
440 if arg.force_script || f.is_script() {
441 let (script_file, _) =
442 match parse_script_from_archive(&mut f, arg, config.clone(), &script) {
443 Ok(s) => s,
444 Err(e) => {
445 eprintln!("Error parsing script '{}' from archive: {}", filename, e);
446 COUNTER.inc_error();
447 if arg.backtrace {
448 eprintln!("Backtrace: {}", e.backtrace());
449 }
450 continue;
451 }
452 };
453 #[cfg(feature = "image")]
454 if script_file.is_image() {
455 if script_file.is_multi_image() {
456 for i in script_file.export_multi_image()? {
457 let img_data = match i {
458 Ok(data) => data,
459 Err(e) => {
460 eprintln!("Error exporting image: {}", e);
461 COUNTER.inc_error();
462 if arg.backtrace {
463 eprintln!("Backtrace: {}", e.backtrace());
464 }
465 continue;
466 }
467 };
468 let out_type = arg.image_type.unwrap_or(types::ImageOutputType::Png);
469 let mut out_path = std::path::PathBuf::from(&odir);
470 if !arg.image_output_flat {
471 out_path.push(f.name());
472 out_path.set_extension("");
473 out_path.push(img_data.name);
474 } else {
475 let name = std::path::Path::new(f.name());
476 out_path.push(format!(
477 "{}_{}",
478 name.file_stem().unwrap_or_default().to_string_lossy(),
479 img_data.name
480 ));
481 }
482 out_path.set_extension(out_type.as_ref());
483 match utils::files::make_sure_dir_exists(&out_path) {
484 Ok(_) => {}
485 Err(e) => {
486 eprintln!(
487 "Error creating parent directory for {}: {}",
488 out_path.display(),
489 e
490 );
491 COUNTER.inc_error();
492 continue;
493 }
494 }
495 if let Some(threadpool) = img_threadpool {
496 let outpath = out_path.to_string_lossy().into_owned();
497 let config = config.clone();
498 threadpool.execute(
499 move |_| {
500 utils::img::encode_img(
501 img_data.data,
502 out_type,
503 &outpath,
504 &config,
505 )
506 .map_err(|e| {
507 anyhow::anyhow!(
508 "Failed to encode image {}: {}",
509 outpath,
510 e
511 )
512 })
513 },
514 true,
515 )?;
516 continue;
517 } else {
518 match utils::img::encode_img(
519 img_data.data,
520 out_type,
521 &out_path.to_string_lossy(),
522 &config,
523 ) {
524 Ok(_) => {}
525 Err(e) => {
526 eprintln!("Error encoding image: {}", e);
527 COUNTER.inc_error();
528 continue;
529 }
530 }
531 COUNTER.inc(types::ScriptResult::Ok);
532 }
533 }
534 COUNTER.inc(types::ScriptResult::Ok);
535 continue;
536 }
537 let img_data = match script_file.export_image() {
538 Ok(data) => data,
539 Err(e) => {
540 eprintln!("Error exporting image: {}", e);
541 COUNTER.inc_error();
542 if arg.backtrace {
543 eprintln!("Backtrace: {}", e.backtrace());
544 }
545 continue;
546 }
547 };
548 let out_type = arg.image_type.unwrap_or(types::ImageOutputType::Png);
549 let mut out_path = std::path::PathBuf::from(&odir).join(f.name());
550 out_path.set_extension(out_type.as_ref());
551 match utils::files::make_sure_dir_exists(&out_path) {
552 Ok(_) => {}
553 Err(e) => {
554 eprintln!(
555 "Error creating parent directory for {}: {}",
556 out_path.display(),
557 e
558 );
559 COUNTER.inc_error();
560 continue;
561 }
562 }
563 if let Some(threadpool) = img_threadpool {
564 let outpath = out_path.to_string_lossy().into_owned();
565 let config = config.clone();
566 threadpool.execute(
567 move |_| {
568 utils::img::encode_img(img_data, out_type, &outpath, &config)
569 .map_err(|e| {
570 anyhow::anyhow!("Failed to encode image {}: {}", outpath, e)
571 })
572 },
573 true,
574 )?;
575 continue;
576 } else {
577 match utils::img::encode_img(
578 img_data,
579 out_type,
580 &out_path.to_string_lossy(),
581 &config,
582 ) {
583 Ok(_) => {}
584 Err(e) => {
585 eprintln!("Error encoding image: {}", e);
586 COUNTER.inc_error();
587 continue;
588 }
589 }
590 COUNTER.inc(types::ScriptResult::Ok);
591 }
592 continue;
593 }
594 let mut of = match &arg.output_type {
595 Some(t) => t.clone(),
596 None => script_file.default_output_script_type(),
597 };
598 if !script_file.is_output_supported(of) {
599 of = script_file.default_output_script_type();
600 }
601 if !arg.no_multi_message && !of.is_custom() && script_file.multiple_message_files()
602 {
603 let mmes = script_file.extract_multiple_messages()?;
604 if mmes.is_empty() {
605 eprintln!("No messages found in {}", f.name());
606 COUNTER.inc(types::ScriptResult::Ignored);
607 continue;
608 }
609 let ext = of.as_ref();
610 let mut out_dir = std::path::PathBuf::from(&odir).join(f.name());
611 if arg.output_no_extra_ext {
612 out_dir.remove_all_extensions();
613 } else {
614 out_dir.set_extension("");
615 }
616 std::fs::create_dir_all(&out_dir)?;
617 for (name, data) in mmes {
618 let ofp = out_dir.join(name).with_extension(ext);
619 match of {
620 types::OutputScriptType::Json => {
621 let enc = get_output_encoding(arg);
622 let s = match serde_json::to_string_pretty(&data) {
623 Ok(s) => s,
624 Err(e) => {
625 eprintln!("Error serializing messages to JSON: {}", e);
626 COUNTER.inc_error();
627 continue;
628 }
629 };
630 let b = match utils::encoding::encode_string(enc, &s, false) {
631 Ok(b) => b,
632 Err(e) => {
633 eprintln!("Error encoding string: {}", e);
634 COUNTER.inc_error();
635 continue;
636 }
637 };
638 let mut f = match utils::files::write_file(&ofp) {
639 Ok(f) => f,
640 Err(e) => {
641 eprintln!("Error writing file {}: {}", ofp.display(), e);
642 COUNTER.inc_error();
643 continue;
644 }
645 };
646 match f.write_all(&b) {
647 Ok(_) => {}
648 Err(e) => {
649 eprintln!("Error writing to file {}: {}", ofp.display(), e);
650 COUNTER.inc_error();
651 continue;
652 }
653 }
654 }
655 types::OutputScriptType::M3t
656 | types::OutputScriptType::M3ta
657 | types::OutputScriptType::M3tTxt => {
658 let enc = get_output_encoding(arg);
659 let s =
660 output_scripts::m3t::M3tDumper::dump(&data, arg.m3t_no_quote);
661 let b = match utils::encoding::encode_string(enc, &s, false) {
662 Ok(b) => b,
663 Err(e) => {
664 eprintln!("Error encoding string: {}", e);
665 COUNTER.inc_error();
666 continue;
667 }
668 };
669 let mut f = match utils::files::write_file(&ofp) {
670 Ok(f) => f,
671 Err(e) => {
672 eprintln!("Error writing file {}: {}", ofp.display(), e);
673 COUNTER.inc_error();
674 continue;
675 }
676 };
677 match f.write_all(&b) {
678 Ok(_) => {}
679 Err(e) => {
680 eprintln!("Error writing to file {}: {}", ofp.display(), e);
681 COUNTER.inc_error();
682 continue;
683 }
684 }
685 }
686 types::OutputScriptType::Yaml => {
687 let enc = get_output_encoding(arg);
688 let s = match serde_yaml_ng::to_string(&data) {
689 Ok(s) => s,
690 Err(e) => {
691 eprintln!("Error serializing messages to YAML: {}", e);
692 COUNTER.inc_error();
693 continue;
694 }
695 };
696 let b = match utils::encoding::encode_string(enc, &s, false) {
697 Ok(b) => b,
698 Err(e) => {
699 eprintln!("Error encoding string: {}", e);
700 COUNTER.inc_error();
701 continue;
702 }
703 };
704 let mut f = match utils::files::write_file(&ofp) {
705 Ok(f) => f,
706 Err(e) => {
707 eprintln!("Error writing file {}: {}", ofp.display(), e);
708 COUNTER.inc_error();
709 continue;
710 }
711 };
712 match f.write_all(&b) {
713 Ok(_) => {}
714 Err(e) => {
715 eprintln!("Error writing to file {}: {}", ofp.display(), e);
716 COUNTER.inc_error();
717 continue;
718 }
719 }
720 }
721 types::OutputScriptType::Pot | types::OutputScriptType::Po => {
722 let enc = get_output_encoding(arg);
723 let s = match output_scripts::po::PoDumper::new().dump(&data, enc) {
724 Ok(s) => s,
725 Err(e) => {
726 eprintln!("Error dumping messages to PO format: {}", e);
727 COUNTER.inc_error();
728 continue;
729 }
730 };
731 let b = match utils::encoding::encode_string(enc, &s, false) {
732 Ok(b) => b,
733 Err(e) => {
734 eprintln!("Error encoding string: {}", e);
735 COUNTER.inc_error();
736 continue;
737 }
738 };
739 let mut f = match utils::files::write_file(&ofp) {
740 Ok(f) => f,
741 Err(e) => {
742 eprintln!("Error writing file {}: {}", ofp.display(), e);
743 COUNTER.inc_error();
744 continue;
745 }
746 };
747 match f.write_all(&b) {
748 Ok(_) => {}
749 Err(e) => {
750 eprintln!("Error writing to file {}: {}", ofp.display(), e);
751 COUNTER.inc_error();
752 continue;
753 }
754 }
755 }
756 types::OutputScriptType::Custom => {}
757 }
758 }
759 COUNTER.inc(types::ScriptResult::Ok);
760 continue;
761 }
762 let mes = if of.is_custom() {
763 Vec::new()
764 } else {
765 match script_file.extract_messages() {
766 Ok(mes) => mes,
767 Err(e) => {
768 eprintln!("Error extracting messages from {}: {}", f.name(), e);
769 COUNTER.inc_error();
770 if arg.backtrace {
771 eprintln!("Backtrace: {}", e.backtrace());
772 }
773 continue;
774 }
775 }
776 };
777 if !of.is_custom() && mes.is_empty() {
778 eprintln!("No messages found in {}", f.name());
779 COUNTER.inc(types::ScriptResult::Ignored);
780 continue;
781 }
782 let mut out_path = std::path::PathBuf::from(&odir).join(f.name());
783 if arg.output_no_extra_ext {
784 out_path.remove_all_extensions();
785 }
786 out_path.set_extension(if of.is_custom() {
787 script_file.custom_output_extension()
788 } else {
789 of.as_ref()
790 });
791 match utils::files::make_sure_dir_exists(&out_path) {
792 Ok(_) => {}
793 Err(e) => {
794 eprintln!(
795 "Error creating parent directory for {}: {}",
796 out_path.display(),
797 e
798 );
799 COUNTER.inc_error();
800 continue;
801 }
802 }
803 match of {
804 types::OutputScriptType::Json => {
805 let enc = get_output_encoding(arg);
806 let s = match serde_json::to_string_pretty(&mes) {
807 Ok(s) => s,
808 Err(e) => {
809 eprintln!("Error serializing messages to JSON: {}", e);
810 COUNTER.inc_error();
811 continue;
812 }
813 };
814 let b = match utils::encoding::encode_string(enc, &s, false) {
815 Ok(b) => b,
816 Err(e) => {
817 eprintln!("Error encoding string: {}", e);
818 COUNTER.inc_error();
819 continue;
820 }
821 };
822 let mut f = match utils::files::write_file(&out_path) {
823 Ok(f) => f,
824 Err(e) => {
825 eprintln!("Error writing file {}: {}", out_path.display(), e);
826 COUNTER.inc_error();
827 continue;
828 }
829 };
830 match f.write_all(&b) {
831 Ok(_) => {}
832 Err(e) => {
833 eprintln!("Error writing to file {}: {}", out_path.display(), e);
834 COUNTER.inc_error();
835 continue;
836 }
837 }
838 }
839 types::OutputScriptType::M3t
840 | types::OutputScriptType::M3ta
841 | types::OutputScriptType::M3tTxt => {
842 let enc = get_output_encoding(arg);
843 let s = output_scripts::m3t::M3tDumper::dump(&mes, arg.m3t_no_quote);
844 let b = match utils::encoding::encode_string(enc, &s, false) {
845 Ok(b) => b,
846 Err(e) => {
847 eprintln!("Error encoding string: {}", e);
848 COUNTER.inc_error();
849 continue;
850 }
851 };
852 let mut f = match utils::files::write_file(&out_path) {
853 Ok(f) => f,
854 Err(e) => {
855 eprintln!("Error writing file {}: {}", out_path.display(), e);
856 COUNTER.inc_error();
857 continue;
858 }
859 };
860 match f.write_all(&b) {
861 Ok(_) => {}
862 Err(e) => {
863 eprintln!("Error writing to file {}: {}", out_path.display(), e);
864 COUNTER.inc_error();
865 continue;
866 }
867 }
868 }
869 types::OutputScriptType::Yaml => {
870 let enc = get_output_encoding(arg);
871 let s = match serde_yaml_ng::to_string(&mes) {
872 Ok(s) => s,
873 Err(e) => {
874 eprintln!("Error serializing messages to YAML: {}", e);
875 COUNTER.inc_error();
876 continue;
877 }
878 };
879 let b = match utils::encoding::encode_string(enc, &s, false) {
880 Ok(b) => b,
881 Err(e) => {
882 eprintln!("Error encoding string: {}", e);
883 COUNTER.inc_error();
884 continue;
885 }
886 };
887 let mut f = match utils::files::write_file(&out_path) {
888 Ok(f) => f,
889 Err(e) => {
890 eprintln!("Error writing file {}: {}", out_path.display(), e);
891 COUNTER.inc_error();
892 continue;
893 }
894 };
895 match f.write_all(&b) {
896 Ok(_) => {}
897 Err(e) => {
898 eprintln!("Error writing to file {}: {}", out_path.display(), e);
899 COUNTER.inc_error();
900 continue;
901 }
902 }
903 }
904 types::OutputScriptType::Pot | types::OutputScriptType::Po => {
905 let enc = get_output_encoding(arg);
906 let s = match output_scripts::po::PoDumper::new().dump(&mes, enc) {
907 Ok(s) => s,
908 Err(e) => {
909 eprintln!("Error dumping messages to PO format: {}", e);
910 COUNTER.inc_error();
911 continue;
912 }
913 };
914 let b = match utils::encoding::encode_string(enc, &s, false) {
915 Ok(b) => b,
916 Err(e) => {
917 eprintln!("Error encoding string: {}", e);
918 COUNTER.inc_error();
919 continue;
920 }
921 };
922 let mut f = match utils::files::write_file(&out_path) {
923 Ok(f) => f,
924 Err(e) => {
925 eprintln!("Error writing file {}: {}", out_path.display(), e);
926 COUNTER.inc_error();
927 continue;
928 }
929 };
930 match f.write_all(&b) {
931 Ok(_) => {}
932 Err(e) => {
933 eprintln!("Error writing to file {}: {}", out_path.display(), e);
934 COUNTER.inc_error();
935 continue;
936 }
937 }
938 }
939 types::OutputScriptType::Custom => {
940 let enc = get_output_encoding(arg);
941 if let Err(e) = script_file.custom_export(&out_path, enc) {
942 eprintln!("Error exporting custom script: {}", e);
943 COUNTER.inc_error();
944 continue;
945 }
946 }
947 }
948 } else {
949 let out_path = std::path::PathBuf::from(&odir).join(f.name());
950 match utils::files::make_sure_dir_exists(&out_path) {
951 Ok(_) => {}
952 Err(e) => {
953 eprintln!(
954 "Error creating parent directory for {}: {}",
955 out_path.display(),
956 e
957 );
958 COUNTER.inc_error();
959 continue;
960 }
961 }
962 match utils::files::write_file(&out_path) {
963 Ok(mut fi) => match std::io::copy(&mut f, &mut fi) {
964 Ok(_) => {}
965 Err(e) => {
966 eprintln!("Error writing to file {}: {}", out_path.display(), e);
967 COUNTER.inc_error();
968 continue;
969 }
970 },
971 Err(e) => {
972 eprintln!("Error writing file {}: {}", out_path.display(), e);
973 COUNTER.inc_error();
974 continue;
975 }
976 }
977 }
978 COUNTER.inc(types::ScriptResult::Ok);
979 }
980 return Ok(types::ScriptResult::Ok);
981 }
982 #[cfg(feature = "image")]
983 if script.is_image() {
984 if script.is_multi_image() {
985 for i in script.export_multi_image()? {
986 let img_data = match i {
987 Ok(data) => data,
988 Err(e) => {
989 eprintln!("Error exporting image: {}", e);
990 COUNTER.inc_error();
991 if arg.backtrace {
992 eprintln!("Backtrace: {}", e.backtrace());
993 }
994 continue;
995 }
996 };
997 let out_type = arg.image_type.unwrap_or(types::ImageOutputType::Png);
998 let f = match output.as_ref() {
999 Some(output) => {
1000 if let Some(root_dir) = root_dir {
1001 let f = std::path::PathBuf::from(filename);
1002 let mut pb = std::path::PathBuf::from(output);
1003 let rpath = utils::files::relative_path(root_dir, &f);
1004 if let Some(parent) = rpath.parent() {
1005 pb.push(parent);
1006 }
1007 if !arg.image_output_flat {
1008 if let Some(fname) = f.file_name() {
1009 pb.push(fname);
1010 if arg.output_no_extra_ext {
1011 pb.remove_all_extensions();
1012 } else {
1013 pb.set_extension("");
1014 }
1015 }
1016 pb.push(img_data.name);
1017 } else {
1018 pb.push(format!(
1019 "{}_{}",
1020 f.file_stem().unwrap_or_default().to_string_lossy(),
1021 img_data.name
1022 ));
1023 }
1024 pb.set_extension(out_type.as_ref());
1025 pb.to_string_lossy().into_owned()
1026 } else {
1027 let mut pb = std::path::PathBuf::from(output);
1028 if arg.image_output_flat {
1029 let f = std::path::PathBuf::from(filename);
1030 pb.push(format!(
1031 "{}_{}",
1032 f.file_stem().unwrap_or_default().to_string_lossy(),
1033 img_data.name
1034 ));
1035 } else {
1036 pb.push(img_data.name);
1037 if arg.output_no_extra_ext {
1038 pb.remove_all_extensions();
1039 } else {
1040 pb.set_extension("");
1041 }
1042 }
1043 pb.set_extension(out_type.as_ref());
1044 pb.to_string_lossy().into_owned()
1045 }
1046 }
1047 None => {
1048 let mut pb = std::path::PathBuf::from(filename);
1049 if arg.image_output_flat {
1050 let f = std::path::PathBuf::from(filename);
1051 pb.set_file_name(format!(
1052 "{}_{}",
1053 f.file_stem().unwrap_or_default().to_string_lossy(),
1054 img_data.name
1055 ));
1056 } else {
1057 if arg.output_no_extra_ext {
1058 pb.remove_all_extensions();
1059 } else {
1060 pb.set_extension("");
1061 }
1062 pb.push(img_data.name);
1063 }
1064 pb.set_extension(out_type.as_ref());
1065 pb.to_string_lossy().into_owned()
1066 }
1067 };
1068 match utils::files::make_sure_dir_exists(&f) {
1069 Ok(_) => {}
1070 Err(e) => {
1071 eprintln!("Error creating parent directory for {}: {}", f, e);
1072 COUNTER.inc_error();
1073 continue;
1074 }
1075 }
1076 if let Some(threadpool) = img_threadpool {
1077 let outpath = f.clone();
1078 let config = config.clone();
1079 threadpool.execute(
1080 move |_| {
1081 utils::img::encode_img(img_data.data, out_type, &outpath, &config)
1082 .map_err(|e| {
1083 anyhow::anyhow!("Failed to encode image {}: {}", outpath, e)
1084 })
1085 },
1086 true,
1087 )?;
1088 continue;
1089 } else {
1090 match utils::img::encode_img(img_data.data, out_type, &f, &config) {
1091 Ok(_) => {}
1092 Err(e) => {
1093 eprintln!("Error encoding image: {}", e);
1094 COUNTER.inc_error();
1095 continue;
1096 }
1097 }
1098 COUNTER.inc(types::ScriptResult::Ok);
1099 }
1100 }
1101 return Ok(types::ScriptResult::Ok);
1102 }
1103 let img_data = script.export_image()?;
1104 let out_type = arg.image_type.unwrap_or_else(|| {
1105 if root_dir.is_some() {
1106 types::ImageOutputType::Png
1107 } else {
1108 output
1109 .as_ref()
1110 .and_then(|s| types::ImageOutputType::try_from(std::path::Path::new(s)).ok())
1111 .unwrap_or(types::ImageOutputType::Png)
1112 }
1113 });
1114 let f = if filename == "-" {
1115 String::from("-")
1116 } else {
1117 match output.as_ref() {
1118 Some(output) => {
1119 if let Some(root_dir) = root_dir {
1120 let f = std::path::PathBuf::from(filename);
1121 let mut pb = std::path::PathBuf::from(output);
1122 let rpath = utils::files::relative_path(root_dir, &f);
1123 if let Some(parent) = rpath.parent() {
1124 pb.push(parent);
1125 }
1126 if let Some(fname) = f.file_name() {
1127 pb.push(fname);
1128 }
1129 if arg.output_no_extra_ext {
1130 pb.remove_all_extensions();
1131 }
1132 pb.set_extension(out_type.as_ref());
1133 pb.to_string_lossy().into_owned()
1134 } else {
1135 output.clone()
1136 }
1137 }
1138 None => {
1139 let mut pb = std::path::PathBuf::from(filename);
1140 if arg.output_no_extra_ext {
1141 pb.remove_all_extensions();
1142 }
1143 pb.set_extension(out_type.as_ref());
1144 pb.to_string_lossy().into_owned()
1145 }
1146 }
1147 };
1148 utils::files::make_sure_dir_exists(&f)?;
1149 if let Some(threadpool) = img_threadpool {
1150 let outpath = f.clone();
1151 let config = config.clone();
1152 threadpool.execute(
1153 move |_| {
1154 utils::img::encode_img(img_data, out_type, &outpath, &config)
1155 .map_err(|e| anyhow::anyhow!("Failed to encode image {}: {}", outpath, e))
1156 },
1157 true,
1158 )?;
1159 return Ok(types::ScriptResult::Uncount);
1160 } else {
1161 utils::img::encode_img(img_data, out_type, &f, &config)?;
1162 }
1163 return Ok(types::ScriptResult::Ok);
1164 }
1165 let mut of = match &arg.output_type {
1166 Some(t) => t.clone(),
1167 None => script.default_output_script_type(),
1168 };
1169 if !script.is_output_supported(of) {
1170 of = script.default_output_script_type();
1171 }
1172 if !arg.no_multi_message && !of.is_custom() && script.multiple_message_files() {
1173 let mmes = script.extract_multiple_messages()?;
1174 if mmes.is_empty() {
1175 eprintln!("No messages found");
1176 return Ok(types::ScriptResult::Ignored);
1177 }
1178 let ext = of.as_ref();
1179 let out_dir = if let Some(output) = output.as_ref() {
1180 if let Some(root_dir) = root_dir {
1181 let f = std::path::PathBuf::from(filename);
1182 let mut pb = std::path::PathBuf::from(output);
1183 let rpath = utils::files::relative_path(root_dir, &f);
1184 if let Some(parent) = rpath.parent() {
1185 pb.push(parent);
1186 }
1187 if let Some(fname) = f.file_name() {
1188 pb.push(fname);
1189 }
1190 if arg.output_no_extra_ext {
1191 pb.remove_all_extensions();
1192 } else {
1193 pb.set_extension("");
1194 }
1195 pb.to_string_lossy().into_owned()
1196 } else {
1197 output.clone()
1198 }
1199 } else {
1200 let mut pb = std::path::PathBuf::from(filename);
1201 if arg.output_no_extra_ext {
1202 pb.remove_all_extensions();
1203 } else {
1204 pb.set_extension("");
1205 }
1206 pb.to_string_lossy().into_owned()
1207 };
1208 std::fs::create_dir_all(&out_dir)?;
1209 let outdir = std::path::PathBuf::from(&out_dir);
1210 for (name, data) in mmes {
1211 let ofp = outdir.join(name).with_extension(ext);
1212 match of {
1213 types::OutputScriptType::Json => {
1214 let enc = get_output_encoding(arg);
1215 let s = match serde_json::to_string_pretty(&data) {
1216 Ok(s) => s,
1217 Err(e) => {
1218 eprintln!("Error serializing messages to JSON: {}", e);
1219 COUNTER.inc_error();
1220 continue;
1221 }
1222 };
1223 let b = match utils::encoding::encode_string(enc, &s, false) {
1224 Ok(b) => b,
1225 Err(e) => {
1226 eprintln!("Error encoding string: {}", e);
1227 COUNTER.inc_error();
1228 continue;
1229 }
1230 };
1231 let mut f = match utils::files::write_file(&ofp) {
1232 Ok(f) => f,
1233 Err(e) => {
1234 eprintln!("Error writing file {}: {}", ofp.display(), e);
1235 COUNTER.inc_error();
1236 continue;
1237 }
1238 };
1239 match f.write_all(&b) {
1240 Ok(_) => {}
1241 Err(e) => {
1242 eprintln!("Error writing to file {}: {}", ofp.display(), e);
1243 COUNTER.inc_error();
1244 continue;
1245 }
1246 }
1247 }
1248 types::OutputScriptType::M3t
1249 | types::OutputScriptType::M3ta
1250 | types::OutputScriptType::M3tTxt => {
1251 let enc = get_output_encoding(arg);
1252 let s = output_scripts::m3t::M3tDumper::dump(&data, arg.m3t_no_quote);
1253 let b = match utils::encoding::encode_string(enc, &s, false) {
1254 Ok(b) => b,
1255 Err(e) => {
1256 eprintln!("Error encoding string: {}", e);
1257 COUNTER.inc_error();
1258 continue;
1259 }
1260 };
1261 let mut f = match utils::files::write_file(&ofp) {
1262 Ok(f) => f,
1263 Err(e) => {
1264 eprintln!("Error writing file {}: {}", ofp.display(), e);
1265 COUNTER.inc_error();
1266 continue;
1267 }
1268 };
1269 match f.write_all(&b) {
1270 Ok(_) => {}
1271 Err(e) => {
1272 eprintln!("Error writing to file {}: {}", ofp.display(), e);
1273 COUNTER.inc_error();
1274 continue;
1275 }
1276 }
1277 }
1278 types::OutputScriptType::Yaml => {
1279 let enc = get_output_encoding(arg);
1280 let s = match serde_yaml_ng::to_string(&data) {
1281 Ok(s) => s,
1282 Err(e) => {
1283 eprintln!("Error serializing messages to YAML: {}", e);
1284 COUNTER.inc_error();
1285 continue;
1286 }
1287 };
1288 let b = match utils::encoding::encode_string(enc, &s, false) {
1289 Ok(b) => b,
1290 Err(e) => {
1291 eprintln!("Error encoding string: {}", e);
1292 COUNTER.inc_error();
1293 continue;
1294 }
1295 };
1296 let mut f = match utils::files::write_file(&ofp) {
1297 Ok(f) => f,
1298 Err(e) => {
1299 eprintln!("Error writing file {}: {}", ofp.display(), e);
1300 COUNTER.inc_error();
1301 continue;
1302 }
1303 };
1304 match f.write_all(&b) {
1305 Ok(_) => {}
1306 Err(e) => {
1307 eprintln!("Error writing to file {}: {}", ofp.display(), e);
1308 COUNTER.inc_error();
1309 continue;
1310 }
1311 }
1312 }
1313 types::OutputScriptType::Pot | types::OutputScriptType::Po => {
1314 let enc = get_output_encoding(arg);
1315 let s = match output_scripts::po::PoDumper::new().dump(&data, enc) {
1316 Ok(s) => s,
1317 Err(e) => {
1318 eprintln!("Error dumping messages to PO format: {}", e);
1319 COUNTER.inc_error();
1320 continue;
1321 }
1322 };
1323 let b = match utils::encoding::encode_string(enc, &s, false) {
1324 Ok(b) => b,
1325 Err(e) => {
1326 eprintln!("Error encoding string: {}", e);
1327 COUNTER.inc_error();
1328 continue;
1329 }
1330 };
1331 let mut f = match utils::files::write_file(&ofp) {
1332 Ok(f) => f,
1333 Err(e) => {
1334 eprintln!("Error writing file {}: {}", ofp.display(), e);
1335 COUNTER.inc_error();
1336 continue;
1337 }
1338 };
1339 match f.write_all(&b) {
1340 Ok(_) => {}
1341 Err(e) => {
1342 eprintln!("Error writing to file {}: {}", ofp.display(), e);
1343 COUNTER.inc_error();
1344 continue;
1345 }
1346 }
1347 }
1348 types::OutputScriptType::Custom => {}
1349 }
1350 COUNTER.inc(types::ScriptResult::Ok);
1351 }
1352 return Ok(types::ScriptResult::Ok);
1353 }
1354 let mes = if of.is_custom() {
1355 Vec::new()
1356 } else {
1357 script.extract_messages()?
1358 };
1359 if !of.is_custom() && mes.is_empty() {
1360 eprintln!("No messages found");
1361 return Ok(types::ScriptResult::Ignored);
1362 }
1363 let ext = if of.is_custom() {
1364 script.custom_output_extension()
1365 } else {
1366 of.as_ref()
1367 };
1368 let f = if filename == "-" {
1369 String::from("-")
1370 } else {
1371 match output.as_ref() {
1372 Some(output) => {
1373 if let Some(root_dir) = root_dir {
1374 let f = std::path::PathBuf::from(filename);
1375 let mut pb = std::path::PathBuf::from(output);
1376 let rpath = utils::files::relative_path(root_dir, &f);
1377 if let Some(parent) = rpath.parent() {
1378 pb.push(parent);
1379 }
1380 if let Some(fname) = f.file_name() {
1381 pb.push(fname);
1382 }
1383 if arg.output_no_extra_ext {
1384 pb.remove_all_extensions();
1385 }
1386 pb.set_extension(ext);
1387 pb.to_string_lossy().into_owned()
1388 } else {
1389 output.clone()
1390 }
1391 }
1392 None => {
1393 let mut pb = std::path::PathBuf::from(filename);
1394 if arg.output_no_extra_ext {
1395 pb.remove_all_extensions();
1396 }
1397 pb.set_extension(ext);
1398 pb.to_string_lossy().into_owned()
1399 }
1400 }
1401 };
1402 utils::files::make_sure_dir_exists(&f)?;
1403 match of {
1404 types::OutputScriptType::Json => {
1405 let enc = get_output_encoding(arg);
1406 let s = serde_json::to_string_pretty(&mes)?;
1407 let b = utils::encoding::encode_string(enc, &s, false)?;
1408 let mut f = utils::files::write_file(&f)?;
1409 f.write_all(&b)?;
1410 }
1411 types::OutputScriptType::M3t
1412 | types::OutputScriptType::M3ta
1413 | types::OutputScriptType::M3tTxt => {
1414 let enc = get_output_encoding(arg);
1415 let s = output_scripts::m3t::M3tDumper::dump(&mes, arg.m3t_no_quote);
1416 let b = utils::encoding::encode_string(enc, &s, false)?;
1417 let mut f = utils::files::write_file(&f)?;
1418 f.write_all(&b)?;
1419 }
1420 types::OutputScriptType::Yaml => {
1421 let enc = get_output_encoding(arg);
1422 let s = serde_yaml_ng::to_string(&mes)?;
1423 let b = utils::encoding::encode_string(enc, &s, false)?;
1424 let mut f = utils::files::write_file(&f)?;
1425 f.write_all(&b)?;
1426 }
1427 types::OutputScriptType::Pot | types::OutputScriptType::Po => {
1428 let enc = get_output_encoding(arg);
1429 let s = output_scripts::po::PoDumper::new().dump(&mes, enc)?;
1430 let b = utils::encoding::encode_string(enc, &s, false)?;
1431 let mut f = utils::files::write_file(&f)?;
1432 f.write_all(&b)?;
1433 }
1434 types::OutputScriptType::Custom => {
1435 let enc = get_output_encoding(arg);
1436 script.custom_export(f.as_ref(), enc)?;
1437 }
1438 }
1439 Ok(types::ScriptResult::Ok)
1440}
1441
1442pub fn import_script(
1443 filename: &str,
1444 arg: &args::Arg,
1445 config: std::sync::Arc<types::ExtraConfig>,
1446 imp_cfg: &args::ImportArgs,
1447 root_dir: Option<&std::path::Path>,
1448 name_csv: Option<&std::collections::HashMap<String, String>>,
1449 repl: Option<&types::ReplacementTable>,
1450 mut dep_graph: Option<&mut (String, Vec<String>)>,
1451) -> anyhow::Result<types::ScriptResult> {
1452 eprintln!("Importing {}", filename);
1453 if let Some(dep_graph) = dep_graph.as_mut() {
1454 dep_graph.1.push(filename.to_string());
1455 }
1456 let (script, builder) = parse_script(filename, arg, config.clone())?;
1457 if script.is_archive() {
1458 let odir = {
1459 let mut pb = std::path::PathBuf::from(&imp_cfg.output);
1460 let filename = std::path::PathBuf::from(filename);
1461 if let Some(root_dir) = root_dir {
1462 let rpath = utils::files::relative_path(root_dir, &filename);
1463 if let Some(parent) = rpath.parent() {
1464 pb.push(parent);
1465 }
1466 if let Some(fname) = filename.file_name() {
1467 pb.push(fname);
1468 }
1469 }
1470 pb.set_extension("");
1471 if let Some(ext) = script.archive_output_ext() {
1472 pb.set_extension(ext);
1473 }
1474 pb.to_string_lossy().into_owned()
1475 };
1476 let files: Vec<_> = script.iter_archive_filename()?.collect();
1477 let files = files.into_iter().filter_map(|f| f.ok()).collect::<Vec<_>>();
1478 let patched_f = if let Some(root_dir) = root_dir {
1479 let f = std::path::PathBuf::from(filename);
1480 let mut pb = std::path::PathBuf::from(&imp_cfg.patched);
1481 let rpath = utils::files::relative_path(root_dir, &f);
1482 if let Some(parent) = rpath.parent() {
1483 pb.push(parent);
1484 }
1485 if let Some(fname) = f.file_name() {
1486 pb.push(fname);
1487 }
1488 pb.set_extension(builder.extensions().first().unwrap_or(&""));
1489 pb.to_string_lossy().into_owned()
1490 } else {
1491 imp_cfg.patched.clone()
1492 };
1493 if let Some(dep_graph) = dep_graph.as_mut() {
1494 dep_graph.0 = patched_f.clone();
1495 }
1496 let files: Vec<_> = files.iter().map(|s| s.as_str()).collect();
1497 let pencoding = get_patched_encoding(imp_cfg, builder);
1498 let enc = get_patched_archive_encoding(imp_cfg, builder, pencoding);
1499 utils::files::make_sure_dir_exists(&patched_f)?;
1500 let mut arch = builder.create_archive(&patched_f, &files, enc, &config)?;
1501 for (index, filename) in script.iter_archive_filename()?.enumerate() {
1502 let filename = match filename {
1503 Ok(f) => f,
1504 Err(e) => {
1505 eprintln!("Error reading archive filename: {}", e);
1506 COUNTER.inc_error();
1507 if arg.backtrace {
1508 eprintln!("Backtrace: {}", e.backtrace());
1509 }
1510 continue;
1511 }
1512 };
1513 let mut f = match script.open_file(index) {
1514 Ok(f) => f,
1515 Err(e) => {
1516 eprintln!("Error opening file {}: {}", filename, e);
1517 COUNTER.inc_error();
1518 if arg.backtrace {
1519 eprintln!("Backtrace: {}", e.backtrace());
1520 }
1521 continue;
1522 }
1523 };
1524 if arg.force_script || f.is_script() {
1525 let mut writer = arch.new_file(f.name(), None)?;
1526 let (script_file, _) =
1527 match parse_script_from_archive(&mut f, arg, config.clone(), &script) {
1528 Ok(s) => s,
1529 Err(e) => {
1530 eprintln!("Error parsing script '{}' from archive: {}", filename, e);
1531 COUNTER.inc_error();
1532 if arg.backtrace {
1533 eprintln!("Backtrace: {}", e.backtrace());
1534 }
1535 continue;
1536 }
1537 };
1538 let mut of = match &arg.output_type {
1539 Some(t) => t.clone(),
1540 None => script_file.default_output_script_type(),
1541 };
1542 if !script_file.is_output_supported(of) {
1543 of = script_file.default_output_script_type();
1544 }
1545 if !arg.no_multi_message && !of.is_custom() && script_file.multiple_message_files()
1546 {
1547 let out_dir = std::path::PathBuf::from(&odir)
1548 .join(f.name())
1549 .with_extension("");
1550 let outfiles = utils::files::find_ext_files(
1551 &out_dir.to_string_lossy(),
1552 false,
1553 &[of.as_ref()],
1554 )?;
1555 if outfiles.is_empty() {
1556 if imp_cfg.warn_when_output_file_not_found {
1557 eprintln!(
1558 "Warning: No output files found in {}, using file from original archive.",
1559 out_dir.display()
1560 );
1561 COUNTER.inc_warning();
1562 } else {
1563 COUNTER.inc(types::ScriptResult::Ignored);
1564 }
1565 continue;
1566 }
1567 if let Some(dep_graph) = dep_graph.as_mut() {
1568 dep_graph.1.extend_from_slice(&outfiles);
1569 }
1570 let fmt = match imp_cfg.patched_format {
1571 Some(fmt) => match fmt {
1572 types::FormatType::Fixed => types::FormatOptions::Fixed {
1573 length: imp_cfg.patched_fixed_length.unwrap_or(32),
1574 keep_original: imp_cfg.patched_keep_original,
1575 break_words: imp_cfg.patched_break_words,
1576 insert_fullwidth_space_at_line_start: imp_cfg
1577 .patched_insert_fullwidth_space_at_line_start,
1578 break_with_sentence: imp_cfg.patched_break_with_sentence,
1579 #[cfg(feature = "jieba")]
1580 break_chinese_words: !imp_cfg.patched_no_break_chinese_words,
1581 #[cfg(feature = "jieba")]
1582 jieba_dict: arg.jieba_dict.clone(),
1583 no_remove_space_at_line_start: imp_cfg
1584 .patched_no_remove_space_at_line_start,
1585 },
1586 types::FormatType::None => types::FormatOptions::None,
1587 },
1588 None => script.default_format_type(),
1589 };
1590 let mut mmes = std::collections::HashMap::new();
1591 for out_f in outfiles {
1592 let name = utils::files::relative_path(&out_dir, &out_f)
1593 .with_extension("")
1594 .to_string_lossy()
1595 .into_owned();
1596 let mut mes = match of {
1597 types::OutputScriptType::Json => {
1598 let enc = get_output_encoding(arg);
1599 let b = match utils::files::read_file(&out_f) {
1600 Ok(b) => b,
1601 Err(e) => {
1602 eprintln!("Error reading file {}: {}", out_f, e);
1603 COUNTER.inc_error();
1604 continue;
1605 }
1606 };
1607 let s = match utils::encoding::decode_to_string(enc, &b, true) {
1608 Ok(s) => s,
1609 Err(e) => {
1610 eprintln!("Error decoding string: {}", e);
1611 COUNTER.inc_error();
1612 continue;
1613 }
1614 };
1615 match serde_json::from_str::<Vec<types::Message>>(&s) {
1616 Ok(mes) => mes,
1617 Err(e) => {
1618 eprintln!("Error parsing JSON: {}", e);
1619 COUNTER.inc_error();
1620 continue;
1621 }
1622 }
1623 }
1624 types::OutputScriptType::M3t
1625 | types::OutputScriptType::M3ta
1626 | types::OutputScriptType::M3tTxt => {
1627 let enc = get_output_encoding(arg);
1628 let b = match utils::files::read_file(&out_f) {
1629 Ok(b) => b,
1630 Err(e) => {
1631 eprintln!("Error reading file {}: {}", out_f, e);
1632 COUNTER.inc_error();
1633 continue;
1634 }
1635 };
1636 let s = match utils::encoding::decode_to_string(enc, &b, true) {
1637 Ok(s) => s,
1638 Err(e) => {
1639 eprintln!("Error decoding string: {}", e);
1640 COUNTER.inc_error();
1641 continue;
1642 }
1643 };
1644 let mut parser = output_scripts::m3t::M3tParser::new(
1645 &s,
1646 arg.llm_trans_mark.as_ref().map(|s| s.as_str()),
1647 arg.m3t_use_original_text,
1648 );
1649 match parser.parse() {
1650 Ok(mes) => mes,
1651 Err(e) => {
1652 eprintln!("Error parsing M3T: {}", e);
1653 COUNTER.inc_error();
1654 continue;
1655 }
1656 }
1657 }
1658 types::OutputScriptType::Yaml => {
1659 let enc = get_output_encoding(arg);
1660 let b = match utils::files::read_file(&out_f) {
1661 Ok(b) => b,
1662 Err(e) => {
1663 eprintln!("Error reading file {}: {}", out_f, e);
1664 COUNTER.inc_error();
1665 continue;
1666 }
1667 };
1668 let s = match utils::encoding::decode_to_string(enc, &b, true) {
1669 Ok(s) => s,
1670 Err(e) => {
1671 eprintln!("Error decoding string: {}", e);
1672 COUNTER.inc_error();
1673 continue;
1674 }
1675 };
1676 match serde_yaml_ng::from_str::<Vec<types::Message>>(&s) {
1677 Ok(mes) => mes,
1678 Err(e) => {
1679 eprintln!("Error parsing YAML: {}", e);
1680 COUNTER.inc_error();
1681 continue;
1682 }
1683 }
1684 }
1685 types::OutputScriptType::Pot | types::OutputScriptType::Po => {
1686 let enc = get_output_encoding(arg);
1687 let b = match utils::files::read_file(&out_f) {
1688 Ok(b) => b,
1689 Err(e) => {
1690 eprintln!("Error reading file {}: {}", out_f, e);
1691 COUNTER.inc_error();
1692 continue;
1693 }
1694 };
1695 let s = match utils::encoding::decode_to_string(enc, &b, true) {
1696 Ok(s) => s,
1697 Err(e) => {
1698 eprintln!("Error decoding string: {}", e);
1699 COUNTER.inc_error();
1700 continue;
1701 }
1702 };
1703 match output_scripts::po::PoParser::new(
1704 &s,
1705 arg.llm_trans_mark.as_ref().map(|s| s.as_str()),
1706 )
1707 .parse()
1708 {
1709 Ok(mes) => mes,
1710 Err(e) => {
1711 eprintln!("Error parsing PO: {}", e);
1712 COUNTER.inc_error();
1713 continue;
1714 }
1715 }
1716 }
1717 types::OutputScriptType::Custom => Vec::new(),
1718 };
1719 if mes.is_empty() {
1720 eprintln!(
1721 "No messages found in {}, using file from original archive.",
1722 out_f
1723 );
1724 continue;
1725 }
1726 match name_csv {
1727 Some(name_table) => {
1728 utils::name_replacement::replace_message(&mut mes, name_table);
1729 }
1730 None => {}
1731 }
1732 format::fmt_message(&mut mes, fmt.clone(), *builder.script_type())?;
1733 mmes.insert(name, mes);
1734 }
1735 if mmes.is_empty() {
1736 COUNTER.inc(types::ScriptResult::Ignored);
1737 continue;
1738 }
1739 let encoding = get_patched_encoding(imp_cfg, builder);
1740 match script_file.import_multiple_messages(
1741 mmes,
1742 writer,
1743 f.name(),
1744 encoding,
1745 repl,
1746 ) {
1747 Ok(_) => {}
1748 Err(e) => {
1749 eprintln!("Error importing messages to script '{}': {}", filename, e);
1750 COUNTER.inc_error();
1751 if arg.backtrace {
1752 eprintln!("Backtrace: {}", e.backtrace());
1753 }
1754 continue;
1755 }
1756 }
1757 COUNTER.inc(types::ScriptResult::Ok);
1758 continue;
1759 }
1760 #[cfg(feature = "image")]
1761 if script_file.is_image() {
1762 let out_type = arg.image_type.unwrap_or(types::ImageOutputType::Png);
1763 let mut out_path = std::path::PathBuf::from(&odir).join(f.name());
1764 if arg.output_no_extra_ext {
1765 out_path.remove_all_extensions();
1766 }
1767 out_path.set_extension(out_type.as_ref());
1768 if !out_path.exists() {
1769 out_path = std::path::PathBuf::from(&odir).join(f.name());
1770 if !out_path.exists() {
1771 if imp_cfg.warn_when_output_file_not_found {
1772 eprintln!(
1773 "Warning: File {} does not exist, using file from original archive.",
1774 out_path.display()
1775 );
1776 COUNTER.inc_warning();
1777 }
1778 match std::io::copy(&mut f, &mut writer) {
1779 Ok(_) => {}
1780 Err(e) => {
1781 eprintln!(
1782 "Error writing to file {}: {}",
1783 out_path.display(),
1784 e
1785 );
1786 COUNTER.inc_error();
1787 continue;
1788 }
1789 }
1790 } else {
1791 if let Some(dep_graph) = dep_graph.as_mut() {
1792 dep_graph.1.push(out_path.to_string_lossy().into_owned());
1793 }
1794 let file = match std::fs::File::open(&out_path) {
1795 Ok(f) => f,
1796 Err(e) => {
1797 eprintln!("Error opening file {}: {}", out_path.display(), e);
1798 COUNTER.inc_error();
1799 continue;
1800 }
1801 };
1802 let mut f = std::io::BufReader::new(file);
1803 match std::io::copy(&mut f, &mut writer) {
1804 Ok(_) => {}
1805 Err(e) => {
1806 eprintln!(
1807 "Error writing to file {}: {}",
1808 out_path.display(),
1809 e
1810 );
1811 COUNTER.inc_error();
1812 continue;
1813 }
1814 }
1815 }
1816 }
1817 if let Some(dep_graph) = dep_graph.as_mut() {
1818 dep_graph.1.push(out_path.to_string_lossy().into_owned());
1819 }
1820 let img_data =
1821 match utils::img::decode_img(out_type, &out_path.to_string_lossy()) {
1822 Ok(data) => data,
1823 Err(e) => {
1824 eprintln!("Error decoding image {}: {}", out_path.display(), e);
1825 COUNTER.inc_error();
1826 continue;
1827 }
1828 };
1829 if let Err(err) =
1830 script_file.import_image(img_data, &out_path.to_string_lossy(), writer)
1831 {
1832 eprintln!("Error importing image to script '{}': {}", filename, err);
1833 COUNTER.inc_error();
1834 if arg.backtrace {
1835 eprintln!("Backtrace: {}", err.backtrace());
1836 }
1837 continue;
1838 }
1839 continue;
1840 }
1841 let mut out_path = std::path::PathBuf::from(&odir).join(f.name());
1842 if arg.output_no_extra_ext {
1843 out_path.remove_all_extensions();
1844 }
1845 let ext = if of.is_custom() {
1846 script_file.custom_output_extension()
1847 } else {
1848 of.as_ref()
1849 };
1850 out_path.set_extension(ext);
1851 if !out_path.exists() {
1852 out_path = std::path::PathBuf::from(&odir).join(f.name());
1853 if !out_path.exists() {
1854 if imp_cfg.warn_when_output_file_not_found {
1855 eprintln!(
1856 "Warning: File {} does not exist, using file from original archive.",
1857 out_path.display()
1858 );
1859 COUNTER.inc_warning();
1860 }
1861 match std::io::copy(&mut f, &mut writer) {
1862 Ok(_) => {}
1863 Err(e) => {
1864 eprintln!("Error writing to file {}: {}", out_path.display(), e);
1865 COUNTER.inc_error();
1866 continue;
1867 }
1868 }
1869 COUNTER.inc(types::ScriptResult::Ok);
1870 continue;
1871 } else {
1872 if let Some(dep_graph) = dep_graph.as_mut() {
1873 dep_graph.1.push(out_path.to_string_lossy().into_owned());
1874 }
1875 let file = match std::fs::File::open(&out_path) {
1876 Ok(f) => f,
1877 Err(e) => {
1878 eprintln!("Error opening file {}: {}", out_path.display(), e);
1879 COUNTER.inc_error();
1880 continue;
1881 }
1882 };
1883 let mut f = std::io::BufReader::new(file);
1884 match std::io::copy(&mut f, &mut writer) {
1885 Ok(_) => {}
1886 Err(e) => {
1887 eprintln!("Error writing to file {}: {}", out_path.display(), e);
1888 COUNTER.inc_error();
1889 continue;
1890 }
1891 }
1892 COUNTER.inc(types::ScriptResult::Ok);
1893 continue;
1894 }
1895 }
1896 if let Some(dep_graph) = dep_graph.as_mut() {
1897 dep_graph.1.push(out_path.to_string_lossy().into_owned());
1898 }
1899 let mut mes = match of {
1900 types::OutputScriptType::Json => {
1901 let enc = get_output_encoding(arg);
1902 let b = match utils::files::read_file(&out_path) {
1903 Ok(b) => b,
1904 Err(e) => {
1905 eprintln!("Error reading file {}: {}", out_path.display(), e);
1906 COUNTER.inc_error();
1907 continue;
1908 }
1909 };
1910 let s = match utils::encoding::decode_to_string(enc, &b, true) {
1911 Ok(s) => s,
1912 Err(e) => {
1913 eprintln!("Error decoding string: {}", e);
1914 COUNTER.inc_error();
1915 continue;
1916 }
1917 };
1918 match serde_json::from_str::<Vec<types::Message>>(&s) {
1919 Ok(mes) => mes,
1920 Err(e) => {
1921 eprintln!("Error parsing JSON: {}", e);
1922 COUNTER.inc_error();
1923 continue;
1924 }
1925 }
1926 }
1927 types::OutputScriptType::M3t
1928 | types::OutputScriptType::M3ta
1929 | types::OutputScriptType::M3tTxt => {
1930 let enc = get_output_encoding(arg);
1931 let b = match utils::files::read_file(&out_path) {
1932 Ok(b) => b,
1933 Err(e) => {
1934 eprintln!("Error reading file {}: {}", out_path.display(), e);
1935 COUNTER.inc_error();
1936 continue;
1937 }
1938 };
1939 let s = match utils::encoding::decode_to_string(enc, &b, true) {
1940 Ok(s) => s,
1941 Err(e) => {
1942 eprintln!("Error decoding string: {}", e);
1943 COUNTER.inc_error();
1944 continue;
1945 }
1946 };
1947 let mut parser = output_scripts::m3t::M3tParser::new(
1948 &s,
1949 arg.llm_trans_mark.as_ref().map(|s| s.as_str()),
1950 arg.m3t_use_original_text,
1951 );
1952 match parser.parse() {
1953 Ok(mes) => mes,
1954 Err(e) => {
1955 eprintln!("Error parsing M3T: {}", e);
1956 COUNTER.inc_error();
1957 continue;
1958 }
1959 }
1960 }
1961 types::OutputScriptType::Yaml => {
1962 let enc = get_output_encoding(arg);
1963 let b = match utils::files::read_file(&out_path) {
1964 Ok(b) => b,
1965 Err(e) => {
1966 eprintln!("Error reading file {}: {}", out_path.display(), e);
1967 COUNTER.inc_error();
1968 continue;
1969 }
1970 };
1971 let s = match utils::encoding::decode_to_string(enc, &b, true) {
1972 Ok(s) => s,
1973 Err(e) => {
1974 eprintln!("Error decoding string: {}", e);
1975 COUNTER.inc_error();
1976 continue;
1977 }
1978 };
1979 match serde_yaml_ng::from_str::<Vec<types::Message>>(&s) {
1980 Ok(mes) => mes,
1981 Err(e) => {
1982 eprintln!("Error parsing YAML: {}", e);
1983 COUNTER.inc_error();
1984 continue;
1985 }
1986 }
1987 }
1988 types::OutputScriptType::Pot | types::OutputScriptType::Po => {
1989 let enc = get_output_encoding(arg);
1990 let b = match utils::files::read_file(&out_path) {
1991 Ok(b) => b,
1992 Err(e) => {
1993 eprintln!("Error reading file {}: {}", out_path.display(), e);
1994 COUNTER.inc_error();
1995 continue;
1996 }
1997 };
1998 let s = match utils::encoding::decode_to_string(enc, &b, true) {
1999 Ok(s) => s,
2000 Err(e) => {
2001 eprintln!("Error decoding string: {}", e);
2002 COUNTER.inc_error();
2003 continue;
2004 }
2005 };
2006 let mut parser = output_scripts::po::PoParser::new(
2007 &s,
2008 arg.llm_trans_mark.as_ref().map(|s| s.as_str()),
2009 );
2010 match parser.parse() {
2011 Ok(mes) => mes,
2012 Err(e) => {
2013 eprintln!("Error parsing PO: {}", e);
2014 COUNTER.inc_error();
2015 continue;
2016 }
2017 }
2018 }
2019 types::OutputScriptType::Custom => {
2020 Vec::new() }
2022 };
2023 if !of.is_custom() && mes.is_empty() {
2024 eprintln!("No messages found in {}", f.name());
2025 COUNTER.inc(types::ScriptResult::Ignored);
2026 continue;
2027 }
2028 let encoding = get_patched_encoding(imp_cfg, builder);
2029 if of.is_custom() {
2030 let enc = get_output_encoding(arg);
2031 match script_file.custom_import(
2032 &out_path.to_string_lossy(),
2033 writer,
2034 encoding,
2035 enc,
2036 ) {
2037 Ok(_) => {}
2038 Err(e) => {
2039 eprintln!("Error importing custom script: {}", e);
2040 COUNTER.inc_error();
2041 continue;
2042 }
2043 }
2044 COUNTER.inc(types::ScriptResult::Ok);
2045 continue;
2046 }
2047 let fmt = match imp_cfg.patched_format {
2048 Some(fmt) => match fmt {
2049 types::FormatType::Fixed => types::FormatOptions::Fixed {
2050 length: imp_cfg.patched_fixed_length.unwrap_or(32),
2051 keep_original: imp_cfg.patched_keep_original,
2052 break_words: imp_cfg.patched_break_words,
2053 insert_fullwidth_space_at_line_start: imp_cfg
2054 .patched_insert_fullwidth_space_at_line_start,
2055 break_with_sentence: imp_cfg.patched_break_with_sentence,
2056 #[cfg(feature = "jieba")]
2057 break_chinese_words: !imp_cfg.patched_no_break_chinese_words,
2058 #[cfg(feature = "jieba")]
2059 jieba_dict: arg.jieba_dict.clone(),
2060 no_remove_space_at_line_start: imp_cfg
2061 .patched_no_remove_space_at_line_start,
2062 },
2063 types::FormatType::None => types::FormatOptions::None,
2064 },
2065 None => script_file.default_format_type(),
2066 };
2067 match name_csv {
2068 Some(name_table) => {
2069 utils::name_replacement::replace_message(&mut mes, name_table);
2070 }
2071 None => {}
2072 }
2073 format::fmt_message(&mut mes, fmt, *builder.script_type())?;
2074 if let Err(e) = script_file.import_messages(
2075 mes,
2076 writer,
2077 &out_path.to_string_lossy(),
2078 encoding,
2079 repl,
2080 ) {
2081 eprintln!("Error importing messages: {}", e);
2082 COUNTER.inc_error();
2083 continue;
2084 }
2085 } else {
2086 let out_path = std::path::PathBuf::from(&odir).join(f.name());
2087 let size = if out_path.is_file() {
2088 match std::fs::metadata(&out_path) {
2089 Ok(meta) => Some(meta.len()),
2090 Err(e) => {
2091 eprintln!(
2092 "Error getting metadata for file {}: {}",
2093 out_path.display(),
2094 e
2095 );
2096 COUNTER.inc_error();
2097 continue;
2098 }
2099 }
2100 } else {
2101 None
2102 };
2103 let mut writer = arch.new_file_non_seek(f.name(), size)?;
2104 if out_path.is_file() {
2105 if let Some(dep_graph) = dep_graph.as_mut() {
2106 dep_graph.1.push(out_path.to_string_lossy().into_owned());
2107 }
2108 let f = match std::fs::File::open(&out_path) {
2109 Ok(f) => f,
2110 Err(e) => {
2111 eprintln!("Error opening file {}: {}", out_path.display(), e);
2112 COUNTER.inc_error();
2113 continue;
2114 }
2115 };
2116 let mut f = std::io::BufReader::new(f);
2117 match std::io::copy(&mut f, &mut writer) {
2118 Ok(_) => {}
2119 Err(e) => {
2120 eprintln!("Error writing to file {}: {}", out_path.display(), e);
2121 COUNTER.inc_error();
2122 continue;
2123 }
2124 }
2125 } else {
2126 eprintln!(
2127 "Warning: File {} does not exist, use file from original archive.",
2128 out_path.display()
2129 );
2130 COUNTER.inc_warning();
2131 match std::io::copy(&mut f, &mut writer) {
2132 Ok(_) => {}
2133 Err(e) => {
2134 eprintln!("Error writing to file {}: {}", out_path.display(), e);
2135 COUNTER.inc_error();
2136 continue;
2137 }
2138 }
2139 }
2140 }
2141 COUNTER.inc(types::ScriptResult::Ok);
2142 }
2143 arch.write_header()?;
2144 return Ok(types::ScriptResult::Ok);
2145 }
2146 #[cfg(feature = "image")]
2147 if script.is_image() {
2148 let out_type = arg.image_type.unwrap_or_else(|| {
2149 if root_dir.is_some() {
2150 types::ImageOutputType::Png
2151 } else {
2152 types::ImageOutputType::try_from(std::path::Path::new(&imp_cfg.output))
2153 .unwrap_or(types::ImageOutputType::Png)
2154 }
2155 });
2156 let out_f = if let Some(root_dir) = root_dir {
2157 let f = std::path::PathBuf::from(filename);
2158 let mut pb = std::path::PathBuf::from(&imp_cfg.output);
2159 let rpath = utils::files::relative_path(root_dir, &f);
2160 if let Some(parent) = rpath.parent() {
2161 pb.push(parent);
2162 }
2163 if let Some(fname) = f.file_name() {
2164 pb.push(fname);
2165 }
2166 if arg.output_no_extra_ext {
2167 pb.remove_all_extensions();
2168 }
2169 pb.set_extension(out_type.as_ref());
2170 pb.to_string_lossy().into_owned()
2171 } else {
2172 imp_cfg.output.clone()
2173 };
2174 if let Some(dep_graph) = dep_graph.as_mut() {
2175 dep_graph.1.push(out_f.clone());
2176 }
2177 let data = utils::img::decode_img(out_type, &out_f)?;
2178 let patched_f = if let Some(root_dir) = root_dir {
2179 let f = std::path::PathBuf::from(filename);
2180 let mut pb = std::path::PathBuf::from(&imp_cfg.patched);
2181 let rpath = utils::files::relative_path(root_dir, &f);
2182 if let Some(parent) = rpath.parent() {
2183 pb.push(parent);
2184 }
2185 if let Some(fname) = f.file_name() {
2186 pb.push(fname);
2187 }
2188 pb.set_extension(builder.extensions().first().unwrap_or(&""));
2189 pb.to_string_lossy().into_owned()
2190 } else {
2191 imp_cfg.patched.clone()
2192 };
2193 if let Some(dep_graph) = dep_graph.as_mut() {
2194 dep_graph.0 = patched_f.clone();
2195 }
2196 utils::files::make_sure_dir_exists(&patched_f)?;
2197 script.import_image_filename(data, &out_f, &patched_f)?;
2198 return Ok(types::ScriptResult::Ok);
2199 }
2200 let mut of = match &arg.output_type {
2201 Some(t) => t.clone(),
2202 None => script.default_output_script_type(),
2203 };
2204 if !script.is_output_supported(of) {
2205 of = script.default_output_script_type();
2206 }
2207 if !arg.no_multi_message && !of.is_custom() && script.multiple_message_files() {
2208 let out_dir = if let Some(root_dir) = root_dir {
2209 let f = std::path::PathBuf::from(filename);
2210 let mut pb = std::path::PathBuf::from(&imp_cfg.output);
2211 let rpath = utils::files::relative_path(root_dir, &f);
2212 if let Some(parent) = rpath.parent() {
2213 pb.push(parent);
2214 }
2215 if let Some(fname) = f.file_name() {
2216 pb.push(fname);
2217 }
2218 if arg.output_no_extra_ext {
2219 pb.remove_all_extensions();
2220 } else {
2221 pb.set_extension("");
2222 }
2223 pb.to_string_lossy().into_owned()
2224 } else {
2225 imp_cfg.output.clone()
2226 };
2227 let outfiles = utils::files::find_ext_files(&out_dir, false, &[of.as_ref()])?;
2228 if outfiles.is_empty() {
2229 eprintln!("No output files found");
2230 return Ok(types::ScriptResult::Ignored);
2231 }
2232 if let Some(dep_graph) = dep_graph.as_mut() {
2233 dep_graph.1.extend_from_slice(&outfiles);
2234 }
2235 let fmt = match imp_cfg.patched_format {
2236 Some(fmt) => match fmt {
2237 types::FormatType::Fixed => types::FormatOptions::Fixed {
2238 length: imp_cfg.patched_fixed_length.unwrap_or(32),
2239 keep_original: imp_cfg.patched_keep_original,
2240 break_words: imp_cfg.patched_break_words,
2241 insert_fullwidth_space_at_line_start: imp_cfg
2242 .patched_insert_fullwidth_space_at_line_start,
2243 break_with_sentence: imp_cfg.patched_break_with_sentence,
2244 #[cfg(feature = "jieba")]
2245 break_chinese_words: !imp_cfg.patched_no_break_chinese_words,
2246 #[cfg(feature = "jieba")]
2247 jieba_dict: arg.jieba_dict.clone(),
2248 no_remove_space_at_line_start: imp_cfg.patched_no_remove_space_at_line_start,
2249 },
2250 types::FormatType::None => types::FormatOptions::None,
2251 },
2252 None => script.default_format_type(),
2253 };
2254 let mut mmes = std::collections::HashMap::new();
2255 for out_f in outfiles {
2256 let name = utils::files::relative_path(&out_dir, &out_f)
2257 .with_extension("")
2258 .to_string_lossy()
2259 .into_owned();
2260 let mut mes = match of {
2261 types::OutputScriptType::Json => {
2262 let enc = get_output_encoding(arg);
2263 let b = utils::files::read_file(&out_f)?;
2264 let s = utils::encoding::decode_to_string(enc, &b, true)?;
2265 serde_json::from_str::<Vec<types::Message>>(&s)?
2266 }
2267 types::OutputScriptType::M3t
2268 | types::OutputScriptType::M3ta
2269 | types::OutputScriptType::M3tTxt => {
2270 let enc = get_output_encoding(arg);
2271 let b = utils::files::read_file(&out_f)?;
2272 let s = utils::encoding::decode_to_string(enc, &b, true)?;
2273 let mut parser = output_scripts::m3t::M3tParser::new(
2274 &s,
2275 arg.llm_trans_mark.as_ref().map(|s| s.as_str()),
2276 arg.m3t_use_original_text,
2277 );
2278 parser.parse()?
2279 }
2280 types::OutputScriptType::Yaml => {
2281 let enc = get_output_encoding(arg);
2282 let b = utils::files::read_file(&out_f)?;
2283 let s = utils::encoding::decode_to_string(enc, &b, true)?;
2284 serde_yaml_ng::from_str::<Vec<types::Message>>(&s)?
2285 }
2286 types::OutputScriptType::Pot | types::OutputScriptType::Po => {
2287 let enc = get_output_encoding(arg);
2288 let b = utils::files::read_file(&out_f)?;
2289 let s = utils::encoding::decode_to_string(enc, &b, true)?;
2290 let mut parser = output_scripts::po::PoParser::new(
2291 &s,
2292 arg.llm_trans_mark.as_ref().map(|s| s.as_str()),
2293 );
2294 parser.parse()?
2295 }
2296 types::OutputScriptType::Custom => {
2297 Vec::new() }
2299 };
2300 if mes.is_empty() {
2301 eprintln!("No messages found in {}", out_f);
2302 continue;
2303 }
2304 match name_csv {
2305 Some(name_table) => {
2306 utils::name_replacement::replace_message(&mut mes, name_table);
2307 }
2308 None => {}
2309 }
2310 format::fmt_message(&mut mes, fmt.clone(), *builder.script_type())?;
2311 mmes.insert(name, mes);
2312 }
2313 let patched_f = if let Some(root_dir) = root_dir {
2314 let f = std::path::PathBuf::from(filename);
2315 let mut pb = std::path::PathBuf::from(&imp_cfg.patched);
2316 let rpath = utils::files::relative_path(root_dir, &f);
2317 if let Some(parent) = rpath.parent() {
2318 pb.push(parent);
2319 }
2320 if let Some(fname) = f.file_name() {
2321 pb.push(fname);
2322 }
2323 pb.set_extension(builder.extensions().first().unwrap_or(&""));
2324 pb.to_string_lossy().into_owned()
2325 } else {
2326 imp_cfg.patched.clone()
2327 };
2328 if let Some(dep_graph) = dep_graph.as_mut() {
2329 dep_graph.0 = patched_f.clone();
2330 }
2331 utils::files::make_sure_dir_exists(&patched_f)?;
2332 let encoding = get_patched_encoding(imp_cfg, builder);
2333 script.import_multiple_messages_filename(mmes, &patched_f, encoding, repl)?;
2334 return Ok(types::ScriptResult::Ok);
2335 }
2336 let out_f = if let Some(root_dir) = root_dir {
2337 let f = std::path::PathBuf::from(filename);
2338 let mut pb = std::path::PathBuf::from(&imp_cfg.output);
2339 let rpath = utils::files::relative_path(root_dir, &f);
2340 if let Some(parent) = rpath.parent() {
2341 pb.push(parent);
2342 }
2343 if let Some(fname) = f.file_name() {
2344 pb.push(fname);
2345 }
2346 if arg.output_no_extra_ext {
2347 pb.remove_all_extensions();
2348 }
2349 pb.set_extension(if of.is_custom() {
2350 script.custom_output_extension()
2351 } else {
2352 of.as_ref()
2353 });
2354 pb.to_string_lossy().into_owned()
2355 } else {
2356 imp_cfg.output.clone()
2357 };
2358 if !std::fs::exists(&out_f).unwrap_or(false) {
2359 eprintln!("Output file does not exist");
2360 return Ok(types::ScriptResult::Ignored);
2361 }
2362 if let Some(dep_graph) = dep_graph.as_mut() {
2363 dep_graph.1.push(out_f.clone());
2364 }
2365 let mut mes = match of {
2366 types::OutputScriptType::Json => {
2367 let enc = get_output_encoding(arg);
2368 let b = utils::files::read_file(&out_f)?;
2369 let s = utils::encoding::decode_to_string(enc, &b, true)?;
2370 serde_json::from_str::<Vec<types::Message>>(&s)?
2371 }
2372 types::OutputScriptType::M3t
2373 | types::OutputScriptType::M3ta
2374 | types::OutputScriptType::M3tTxt => {
2375 let enc = get_output_encoding(arg);
2376 let b = utils::files::read_file(&out_f)?;
2377 let s = utils::encoding::decode_to_string(enc, &b, true)?;
2378 let mut parser = output_scripts::m3t::M3tParser::new(
2379 &s,
2380 arg.llm_trans_mark.as_ref().map(|s| s.as_str()),
2381 arg.m3t_use_original_text,
2382 );
2383 parser.parse()?
2384 }
2385 types::OutputScriptType::Yaml => {
2386 let enc = get_output_encoding(arg);
2387 let b = utils::files::read_file(&out_f)?;
2388 let s = utils::encoding::decode_to_string(enc, &b, true)?;
2389 serde_yaml_ng::from_str::<Vec<types::Message>>(&s)?
2390 }
2391 types::OutputScriptType::Pot | types::OutputScriptType::Po => {
2392 let enc = get_output_encoding(arg);
2393 let b = utils::files::read_file(&out_f)?;
2394 let s = utils::encoding::decode_to_string(enc, &b, true)?;
2395 let mut parser = output_scripts::po::PoParser::new(
2396 &s,
2397 arg.llm_trans_mark.as_ref().map(|s| s.as_str()),
2398 );
2399 parser.parse()?
2400 }
2401 types::OutputScriptType::Custom => {
2402 Vec::new() }
2404 };
2405 if !of.is_custom() && mes.is_empty() {
2406 eprintln!("No messages found");
2407 return Ok(types::ScriptResult::Ignored);
2408 }
2409 let encoding = get_patched_encoding(imp_cfg, builder);
2410 let patched_f = if let Some(root_dir) = root_dir {
2411 let f = std::path::PathBuf::from(filename);
2412 let mut pb = std::path::PathBuf::from(&imp_cfg.patched);
2413 let rpath = utils::files::relative_path(root_dir, &f);
2414 if let Some(parent) = rpath.parent() {
2415 pb.push(parent);
2416 }
2417 if let Some(fname) = f.file_name() {
2418 pb.push(fname);
2419 }
2420 pb.set_extension(builder.extensions().first().unwrap_or(&""));
2421 pb.to_string_lossy().into_owned()
2422 } else {
2423 imp_cfg.patched.clone()
2424 };
2425 if let Some(dep_graph) = dep_graph.as_mut() {
2426 dep_graph.0 = patched_f.clone();
2427 }
2428 utils::files::make_sure_dir_exists(&patched_f)?;
2429 if of.is_custom() {
2430 let enc = get_output_encoding(arg);
2431 script.custom_import_filename(&out_f, &patched_f, encoding, enc)?;
2432 return Ok(types::ScriptResult::Ok);
2433 }
2434 let fmt = match imp_cfg.patched_format {
2435 Some(fmt) => match fmt {
2436 types::FormatType::Fixed => types::FormatOptions::Fixed {
2437 length: imp_cfg.patched_fixed_length.unwrap_or(32),
2438 keep_original: imp_cfg.patched_keep_original,
2439 break_words: imp_cfg.patched_break_words,
2440 insert_fullwidth_space_at_line_start: imp_cfg
2441 .patched_insert_fullwidth_space_at_line_start,
2442 break_with_sentence: imp_cfg.patched_break_with_sentence,
2443 #[cfg(feature = "jieba")]
2444 break_chinese_words: !imp_cfg.patched_no_break_chinese_words,
2445 #[cfg(feature = "jieba")]
2446 jieba_dict: arg.jieba_dict.clone(),
2447 no_remove_space_at_line_start: imp_cfg.patched_no_remove_space_at_line_start,
2448 },
2449 types::FormatType::None => types::FormatOptions::None,
2450 },
2451 None => script.default_format_type(),
2452 };
2453 match name_csv {
2454 Some(name_table) => {
2455 utils::name_replacement::replace_message(&mut mes, name_table);
2456 }
2457 None => {}
2458 }
2459 format::fmt_message(&mut mes, fmt, *builder.script_type())?;
2460
2461 script.import_messages_filename(mes, &patched_f, encoding, repl)?;
2462 Ok(types::ScriptResult::Ok)
2463}
2464
2465pub fn pack_archive(
2466 input: &str,
2467 output: Option<&str>,
2468 arg: &args::Arg,
2469 config: std::sync::Arc<types::ExtraConfig>,
2470 backslash: bool,
2471) -> anyhow::Result<()> {
2472 let typ = match &arg.script_type {
2473 Some(t) => t,
2474 None => {
2475 return Err(anyhow::anyhow!("No script type specified"));
2476 }
2477 };
2478 let (mut files, isdir) = utils::files::collect_files(input, arg.recursive, true)
2479 .map_err(|e| anyhow::anyhow!("Error collecting files: {}", e))?;
2480 if !isdir {
2481 return Err(anyhow::anyhow!("Input must be a directory for packing"));
2482 }
2483 let re_files: Vec<String> = files
2484 .iter()
2485 .filter_map(|f| {
2486 std::path::PathBuf::from(f)
2487 .strip_prefix(input)
2488 .ok()
2489 .and_then(|p| {
2490 p.to_str().map(|s| {
2491 if backslash {
2492 s.replace("/", "\\").trim_start_matches("\\").to_owned()
2493 } else {
2494 s.replace("\\", "/").trim_start_matches("/").to_owned()
2495 }
2496 })
2497 })
2498 })
2499 .collect();
2500 let mut reff = re_files.iter().map(|s| s.as_str()).collect::<Vec<_>>();
2501 let builder = scripts::BUILDER
2502 .iter()
2503 .find(|b| b.script_type() == typ)
2504 .ok_or_else(|| anyhow::anyhow!("Unsupported script type"))?;
2505 let output = match output {
2506 Some(output) => output.to_string(),
2507 None => {
2508 let mut pb = std::path::PathBuf::from(input);
2509 let ext = builder.extensions().first().unwrap_or(&"unk");
2510 pb.set_extension(ext);
2511 if pb.to_string_lossy() == input {
2512 pb.set_extension(format!("{}.{}", ext, ext));
2513 }
2514 pb.to_string_lossy().into_owned()
2515 }
2516 };
2517 let mut archive = builder.create_archive(
2518 &output,
2519 &reff,
2520 get_archived_encoding(arg, builder, get_encoding(arg, builder)),
2521 &config,
2522 )?;
2523 if let Some(pre) = archive.prelist()? {
2524 let mut index = 0;
2525 for name in pre {
2526 let name = name?;
2527 if let Some(pos) = reff.iter().position(|&n| n == name) {
2528 let re = reff.remove(pos);
2529 let ff = files.remove(pos);
2530 reff.insert(index, re);
2531 files.insert(index, ff);
2532 index += 1;
2533 } else {
2534 eprintln!("Warning: Prelist file {} not found in input files", name);
2535 COUNTER.inc_warning();
2536 }
2537 }
2538 }
2539 for (file, name) in files.iter().zip(reff) {
2540 let mut f = match std::fs::File::open(file) {
2541 Ok(f) => f,
2542 Err(e) => {
2543 eprintln!("Error opening file {}: {}", file, e);
2544 COUNTER.inc_error();
2545 continue;
2546 }
2547 };
2548 let size = match std::fs::metadata(file) {
2549 Ok(meta) => meta.len(),
2550 Err(e) => {
2551 eprintln!("Error getting metadata for file {}: {}", file, e);
2552 COUNTER.inc_error();
2553 continue;
2554 }
2555 };
2556 let mut wf = match archive.new_file_non_seek(name, Some(size)) {
2557 Ok(f) => f,
2558 Err(e) => {
2559 eprintln!("Error creating file {} in archive: {}", name, e);
2560 COUNTER.inc_error();
2561 continue;
2562 }
2563 };
2564 match std::io::copy(&mut f, &mut wf) {
2565 Ok(_) => {
2566 COUNTER.inc(types::ScriptResult::Ok);
2567 }
2568 Err(e) => {
2569 eprintln!("Error writing to file {} in archive: {}", name, e);
2570 COUNTER.inc_error();
2571 continue;
2572 }
2573 }
2574 }
2575 archive.write_header()?;
2576 Ok(())
2577}
2578
2579pub fn pack_archive_v2(
2580 input: &[&str],
2581 output: Option<&str>,
2582 arg: &args::Arg,
2583 config: std::sync::Arc<types::ExtraConfig>,
2584 backslash: bool,
2585 no_dir: bool,
2586 dep_file: Option<&str>,
2587) -> anyhow::Result<()> {
2588 let typ = match &arg.script_type {
2589 Some(t) => t,
2590 None => {
2591 return Err(anyhow::anyhow!("No script type specified"));
2592 }
2593 };
2594 let mut files = Vec::new();
2596 let mut re_files = Vec::new();
2598 for i in input {
2599 let (fs, is_dir) = utils::files::collect_files(i, arg.recursive, true)?;
2600 if is_dir {
2601 files.extend_from_slice(&fs);
2602 for n in fs.iter() {
2603 if no_dir {
2604 if let Some(p) = std::path::PathBuf::from(n).file_name() {
2605 re_files.push(p.to_string_lossy().into_owned());
2606 } else {
2607 return Err(anyhow::anyhow!("Failed to get filename from {}", n));
2608 }
2609 } else {
2610 if let Some(p) = {
2611 std::path::PathBuf::from(n)
2612 .strip_prefix(i)
2613 .ok()
2614 .and_then(|p| {
2615 p.to_str().map(|s| {
2616 if backslash {
2617 s.replace("/", "\\").trim_start_matches("\\").to_owned()
2618 } else {
2619 s.replace("\\", "/").trim_start_matches("/").to_owned()
2620 }
2621 })
2622 })
2623 } {
2624 re_files.push(p);
2625 } else {
2626 return Err(anyhow::anyhow!("Failed to get relative path from {}", n));
2627 }
2628 }
2629 }
2630 } else {
2631 files.push(i.to_string());
2632 let p = std::path::PathBuf::from(i);
2633 if let Some(fname) = p.file_name() {
2634 re_files.push(fname.to_string_lossy().into_owned());
2635 } else {
2636 return Err(anyhow::anyhow!("Failed to get filename from {}", i));
2637 }
2638 }
2639 }
2640 let mut reff = re_files.iter().map(|s| s.as_str()).collect::<Vec<_>>();
2641 let builder = scripts::BUILDER
2642 .iter()
2643 .find(|b| b.script_type() == typ)
2644 .ok_or_else(|| anyhow::anyhow!("Unsupported script type"))?;
2645 let output = match output {
2646 Some(output) => output.to_string(),
2647 None => {
2648 let mut pb = std::path::PathBuf::from(input[0]);
2649 let ext = builder.extensions().first().unwrap_or(&"unk");
2650 pb.set_extension(ext);
2651 if pb.to_string_lossy() == input[0] {
2652 pb.set_extension(format!("{}.{}", ext, ext));
2653 }
2654 pb.to_string_lossy().into_owned()
2655 }
2656 };
2657 let mut archive = builder.create_archive(
2658 &output,
2659 &reff,
2660 get_archived_encoding(arg, builder, get_encoding(arg, builder)),
2661 &config,
2662 )?;
2663 if let Some(pre) = archive.prelist()? {
2664 let mut index = 0;
2665 for name in pre {
2666 let name = name?;
2667 if let Some(pos) = reff.iter().position(|&n| n == name) {
2668 let re = reff.remove(pos);
2669 let ff = files.remove(pos);
2670 reff.insert(index, re);
2671 files.insert(index, ff);
2672 index += 1;
2673 } else {
2674 eprintln!("Warning: Prelist file {} not found in input files", name);
2675 COUNTER.inc_warning();
2676 }
2677 }
2678 }
2679 if let Some(dep_file) = dep_file {
2680 let df = std::fs::File::create(dep_file)
2681 .map_err(|e| anyhow::anyhow!("Failed to create dep file {}: {}", dep_file, e))?;
2682 let mut df = std::io::BufWriter::new(df);
2683 use std::io::Write;
2684 write!(df, "{}:", escape_dep_string(&output))
2685 .map_err(|e| anyhow::anyhow!("Failed to write to dep file {}: {}", dep_file, e))?;
2686 for f in &files {
2687 write!(df, " {}", escape_dep_string(f))
2688 .map_err(|e| anyhow::anyhow!("Failed to write to dep file {}: {}", dep_file, e))?;
2689 }
2690 writeln!(df)
2691 .map_err(|e| anyhow::anyhow!("Failed to write to dep file {}: {}", dep_file, e))?;
2692 }
2693 for (file, name) in files.iter().zip(reff) {
2694 let mut f = match std::fs::File::open(file) {
2695 Ok(f) => f,
2696 Err(e) => {
2697 eprintln!("Error opening file {}: {}", file, e);
2698 COUNTER.inc_error();
2699 continue;
2700 }
2701 };
2702 let size = match std::fs::metadata(file) {
2703 Ok(meta) => meta.len(),
2704 Err(e) => {
2705 eprintln!("Error getting metadata for file {}: {}", file, e);
2706 COUNTER.inc_error();
2707 continue;
2708 }
2709 };
2710 let mut wf = match archive.new_file_non_seek(name, Some(size)) {
2711 Ok(f) => f,
2712 Err(e) => {
2713 eprintln!("Error creating file {} in archive: {}", name, e);
2714 COUNTER.inc_error();
2715 continue;
2716 }
2717 };
2718 match std::io::copy(&mut f, &mut wf) {
2719 Ok(_) => {
2720 COUNTER.inc(types::ScriptResult::Ok);
2721 }
2722 Err(e) => {
2723 eprintln!("Error writing to file {} in archive: {}", name, e);
2724 COUNTER.inc_error();
2725 continue;
2726 }
2727 }
2728 }
2729 archive.write_header()?;
2730 Ok(())
2731}
2732
2733pub fn unpack_archive(
2734 filename: &str,
2735 arg: &args::Arg,
2736 config: std::sync::Arc<types::ExtraConfig>,
2737 output: &Option<String>,
2738 root_dir: Option<&std::path::Path>,
2739) -> anyhow::Result<types::ScriptResult> {
2740 eprintln!("Unpacking {}", filename);
2741 let script = parse_script(filename, arg, config)?.0;
2742 if !script.is_archive() {
2743 return Ok(types::ScriptResult::Ignored);
2744 }
2745 let odir = match output.as_ref() {
2746 Some(output) => {
2747 let mut pb = std::path::PathBuf::from(output);
2748 let filename = std::path::PathBuf::from(filename);
2749 if let Some(root_dir) = root_dir {
2750 let rpath = utils::files::relative_path(root_dir, &filename);
2751 if let Some(parent) = rpath.parent() {
2752 pb.push(parent);
2753 }
2754 if let Some(fname) = filename.file_name() {
2755 pb.push(fname);
2756 }
2757 }
2758 pb.set_extension("");
2759 if let Some(ext) = script.archive_output_ext() {
2760 pb.set_extension(ext);
2761 }
2762 pb.to_string_lossy().into_owned()
2763 }
2764 None => {
2765 let mut pb = std::path::PathBuf::from(filename);
2766 pb.set_extension("");
2767 if let Some(ext) = script.archive_output_ext() {
2768 pb.set_extension(ext);
2769 }
2770 pb.to_string_lossy().into_owned()
2771 }
2772 };
2773 if !std::fs::exists(&odir)? {
2774 std::fs::create_dir_all(&odir)?;
2775 }
2776 for (index, filename) in script.iter_archive_filename()?.enumerate() {
2777 let filename = match filename {
2778 Ok(f) => f,
2779 Err(e) => {
2780 eprintln!("Error reading archive filename: {}", e);
2781 COUNTER.inc_error();
2782 if arg.backtrace {
2783 eprintln!("Backtrace: {}", e.backtrace());
2784 }
2785 continue;
2786 }
2787 };
2788 let mut f = match script.open_file(index) {
2789 Ok(f) => f,
2790 Err(e) => {
2791 eprintln!("Error opening file {}: {}", filename, e);
2792 COUNTER.inc_error();
2793 if arg.backtrace {
2794 eprintln!("Backtrace: {}", e.backtrace());
2795 }
2796 continue;
2797 }
2798 };
2799 let out_path = std::path::PathBuf::from(&odir).join(f.name());
2800 match utils::files::make_sure_dir_exists(&out_path) {
2801 Ok(_) => {}
2802 Err(e) => {
2803 eprintln!(
2804 "Error creating parent directory for {}: {}",
2805 out_path.display(),
2806 e
2807 );
2808 COUNTER.inc_error();
2809 continue;
2810 }
2811 }
2812 match utils::files::write_file(&out_path) {
2813 Ok(mut fi) => match std::io::copy(&mut f, &mut fi) {
2814 Ok(_) => {}
2815 Err(e) => {
2816 eprintln!("Error writing to file {}: {}", out_path.display(), e);
2817 COUNTER.inc_error();
2818 continue;
2819 }
2820 },
2821 Err(e) => {
2822 eprintln!("Error writing file {}: {}", out_path.display(), e);
2823 COUNTER.inc_error();
2824 continue;
2825 }
2826 }
2827 COUNTER.inc(types::ScriptResult::Ok);
2828 }
2829 Ok(types::ScriptResult::Ok)
2830}
2831
2832pub fn create_file(
2833 input: &str,
2834 output: Option<&str>,
2835 arg: &args::Arg,
2836 config: std::sync::Arc<types::ExtraConfig>,
2837) -> anyhow::Result<()> {
2838 let typ = match &arg.script_type {
2839 Some(t) => t,
2840 None => {
2841 return Err(anyhow::anyhow!("No script type specified"));
2842 }
2843 };
2844 let builder = scripts::BUILDER
2845 .iter()
2846 .find(|b| b.script_type() == typ)
2847 .ok_or_else(|| anyhow::anyhow!("Unsupported script type"))?;
2848
2849 #[cfg(feature = "image")]
2850 if builder.is_image() {
2851 if !builder.can_create_image_file() {
2852 return Err(anyhow::anyhow!(
2853 "Script type {:?} does not support image file creation",
2854 typ
2855 ));
2856 }
2857 let data = utils::img::decode_img(
2858 arg.image_type.unwrap_or_else(|| {
2859 types::ImageOutputType::try_from(std::path::Path::new(input))
2860 .unwrap_or(types::ImageOutputType::Png)
2861 }),
2862 input,
2863 )?;
2864 let output = match output {
2865 Some(output) => output.to_string(),
2866 None => {
2867 let mut pb = std::path::PathBuf::from(input);
2868 let ext = builder.extensions().first().unwrap_or(&"");
2869 pb.set_extension(ext);
2870 if pb.to_string_lossy() == input {
2871 if ext.is_empty() {
2872 pb.set_extension("unk");
2873 } else {
2874 pb.set_extension(format!("{}.{}", ext, ext));
2875 }
2876 }
2877 pb.to_string_lossy().into_owned()
2878 }
2879 };
2880 builder.create_image_file_filename(data, &output, input, &config)?;
2881 return Ok(());
2882 }
2883
2884 if !builder.can_create_file() {
2885 return Err(anyhow::anyhow!(
2886 "Script type {:?} does not support file creation",
2887 typ
2888 ));
2889 }
2890
2891 let output = match output {
2892 Some(output) => output.to_string(),
2893 None => {
2894 let mut pb = std::path::PathBuf::from(input);
2895 let ext = builder.extensions().first().unwrap_or(&"");
2896 pb.set_extension(ext);
2897 if pb.to_string_lossy() == input {
2898 if ext.is_empty() {
2899 pb.set_extension("unk");
2900 } else {
2901 pb.set_extension(format!("{}.{}", ext, ext));
2902 }
2903 }
2904 pb.to_string_lossy().into_owned()
2905 }
2906 };
2907
2908 crate::utils::files::make_sure_dir_exists(&output)?;
2909
2910 builder.create_file_filename(
2911 input,
2912 &output,
2913 get_encoding(arg, builder),
2914 get_output_encoding(arg),
2915 &config,
2916 )?;
2917 Ok(())
2918}
2919
2920pub fn parse_output_script_as_extend(
2921 input: &str,
2922 typ: types::OutputScriptType,
2923 arg: &args::Arg,
2924) -> anyhow::Result<Vec<types::ExtendedMessage>> {
2925 match typ {
2926 types::OutputScriptType::M3t
2927 | types::OutputScriptType::M3ta
2928 | types::OutputScriptType::M3tTxt => {
2929 let enc = get_input_output_script_encoding(arg);
2930 let b = utils::files::read_file(input)?;
2931 let s = utils::encoding::decode_to_string(enc, &b, true)?;
2932 let mut parser = output_scripts::m3t::M3tParser::new(
2933 &s,
2934 arg.llm_trans_mark.as_ref().map(|s| s.as_str()),
2935 arg.m3t_use_original_text,
2936 );
2937 let mes = parser.parse_as_extend()?;
2938 Ok(mes)
2939 }
2940 types::OutputScriptType::Po | types::OutputScriptType::Pot => {
2941 let enc = get_input_output_script_encoding(arg);
2942 let b = utils::files::read_file(input)?;
2943 let s = utils::encoding::decode_to_string(enc, &b, true)?;
2944 let mut parser = output_scripts::po::PoParser::new(
2945 &s,
2946 arg.llm_trans_mark.as_ref().map(|s| s.as_str()),
2947 );
2948 let mes = parser.parse_as_extend()?;
2949 Ok(mes)
2950 }
2951 _ => Err(anyhow::anyhow!(
2952 "Output script type {:?} does not support extended messages",
2953 typ
2954 )),
2955 }
2956}
2957
2958pub fn parse_output_script(
2959 input: &str,
2960 typ: types::OutputScriptType,
2961 arg: &args::Arg,
2962) -> anyhow::Result<Vec<types::Message>> {
2963 match typ {
2964 types::OutputScriptType::M3t
2965 | types::OutputScriptType::M3ta
2966 | types::OutputScriptType::M3tTxt => {
2967 let enc = get_input_output_script_encoding(arg);
2968 let b = utils::files::read_file(input)?;
2969 let s = utils::encoding::decode_to_string(enc, &b, true)?;
2970 let mut parser = output_scripts::m3t::M3tParser::new(
2971 &s,
2972 arg.llm_trans_mark.as_ref().map(|s| s.as_str()),
2973 arg.m3t_use_original_text,
2974 );
2975 let mes = parser.parse()?;
2976 Ok(mes)
2977 }
2978 types::OutputScriptType::Po | types::OutputScriptType::Pot => {
2979 let enc = get_input_output_script_encoding(arg);
2980 let b = utils::files::read_file(input)?;
2981 let s = utils::encoding::decode_to_string(enc, &b, true)?;
2982 let mut parser = output_scripts::po::PoParser::new(
2983 &s,
2984 arg.llm_trans_mark.as_ref().map(|s| s.as_str()),
2985 );
2986 let mes = parser.parse()?;
2987 Ok(mes)
2988 }
2989 types::OutputScriptType::Json => {
2990 let enc = get_input_output_script_encoding(arg);
2991 let b = utils::files::read_file(input)?;
2992 let s = utils::encoding::decode_to_string(enc, &b, true)?;
2993 let mes = serde_json::from_str::<Vec<types::Message>>(&s)?;
2994 Ok(mes)
2995 }
2996 types::OutputScriptType::Yaml => {
2997 let enc = get_input_output_script_encoding(arg);
2998 let b = utils::files::read_file(input)?;
2999 let s = utils::encoding::decode_to_string(enc, &b, true)?;
3000 let mes = serde_yaml_ng::from_str::<Vec<types::Message>>(&s)?;
3001 Ok(mes)
3002 }
3003 _ => Err(anyhow::anyhow!(
3004 "Output script type {:?} does not support message parsing",
3005 typ
3006 )),
3007 }
3008}
3009
3010pub fn dump_output_script_as_extend(
3011 output: &str,
3012 typ: types::OutputScriptType,
3013 mes: &[types::ExtendedMessage],
3014 arg: &args::Arg,
3015) -> anyhow::Result<()> {
3016 match typ {
3017 types::OutputScriptType::M3t
3018 | types::OutputScriptType::M3ta
3019 | types::OutputScriptType::M3tTxt => {
3020 let enc = get_output_encoding(arg);
3021 let s = output_scripts::m3t::M3tDumper::dump_extended(mes);
3022 let b = utils::encoding::encode_string(enc, &s, false)?;
3023 utils::files::write_file(output)?.write_all(&b)?;
3024 Ok(())
3025 }
3026 types::OutputScriptType::Po | types::OutputScriptType::Pot => {
3027 let enc = get_output_encoding(arg);
3028 let s = output_scripts::po::PoDumper::new();
3029 let s = s.dump_extended(mes, enc)?;
3030 let b = utils::encoding::encode_string(enc, &s, false)?;
3031 utils::files::write_file(output)?.write_all(&b)?;
3032 Ok(())
3033 }
3034 _ => Err(anyhow::anyhow!(
3035 "Output script type {:?} does not support extended messages",
3036 typ
3037 )),
3038 }
3039}
3040
3041pub fn dump_output_script(
3042 output: &str,
3043 typ: types::OutputScriptType,
3044 mes: &[types::Message],
3045 arg: &args::Arg,
3046) -> anyhow::Result<()> {
3047 match typ {
3048 types::OutputScriptType::M3t
3049 | types::OutputScriptType::M3ta
3050 | types::OutputScriptType::M3tTxt => {
3051 let enc = get_output_encoding(arg);
3052 let s = output_scripts::m3t::M3tDumper::dump(mes, arg.m3t_no_quote);
3053 let b = utils::encoding::encode_string(enc, &s, false)?;
3054 utils::files::write_file(output)?.write_all(&b)?;
3055 Ok(())
3056 }
3057 types::OutputScriptType::Po | types::OutputScriptType::Pot => {
3058 let enc = get_output_encoding(arg);
3059 let s = output_scripts::po::PoDumper::new();
3060 let s = s.dump(mes, enc)?;
3061 let b = utils::encoding::encode_string(enc, &s, false)?;
3062 utils::files::write_file(output)?.write_all(&b)?;
3063 Ok(())
3064 }
3065 types::OutputScriptType::Json => {
3066 let enc = get_output_encoding(arg);
3067 let s = serde_json::to_string_pretty(mes)?;
3068 let b = utils::encoding::encode_string(enc, &s, false)?;
3069 utils::files::write_file(output)?.write_all(&b)?;
3070 Ok(())
3071 }
3072 types::OutputScriptType::Yaml => {
3073 let enc = get_output_encoding(arg);
3074 let s = serde_yaml_ng::to_string(mes)?;
3075 let b = utils::encoding::encode_string(enc, &s, false)?;
3076 utils::files::write_file(output)?.write_all(&b)?;
3077 Ok(())
3078 }
3079 _ => Err(anyhow::anyhow!(
3080 "Output script type {:?} does not support message dumping",
3081 typ
3082 )),
3083 }
3084}
3085
3086pub fn convert_file(
3087 input: &str,
3088 input_type: types::OutputScriptType,
3089 output: Option<&str>,
3090 output_type: types::OutputScriptType,
3091 arg: &args::Arg,
3092 root_dir: Option<&std::path::Path>,
3093) -> anyhow::Result<types::ScriptResult> {
3094 let input_support_src = input_type.is_src_supported();
3095 let output_support_src = output_type.is_src_supported();
3096 let output = match output {
3097 Some(output) => match root_dir {
3098 Some(root_dir) => {
3099 let f = std::path::PathBuf::from(input);
3100 let mut pb = std::path::PathBuf::from(output);
3101 let rpath = utils::files::relative_path(root_dir, &f);
3102 if let Some(parent) = rpath.parent() {
3103 pb.push(parent);
3104 }
3105 if let Some(fname) = f.file_name() {
3106 pb.push(fname);
3107 }
3108 if arg.output_no_extra_ext {
3109 pb.remove_all_extensions();
3110 }
3111 pb.set_extension(output_type.as_ref());
3112 pb.to_string_lossy().into_owned()
3113 }
3114 None => output.to_string(),
3115 },
3116 None => {
3117 let mut pb = std::path::PathBuf::from(input);
3118 if arg.output_no_extra_ext {
3119 pb.remove_all_extensions();
3120 }
3121 pb.set_extension(output_type.as_ref());
3122 pb.to_string_lossy().into_owned()
3123 }
3124 };
3125 if input_support_src && output_support_src {
3126 let input_mes = parse_output_script_as_extend(input, input_type, arg)?;
3127 dump_output_script_as_extend(&output, output_type, &input_mes, arg)?;
3128 return Ok(types::ScriptResult::Ok);
3129 }
3130 let input_mes = parse_output_script(input, input_type, arg)?;
3131 dump_output_script(&output, output_type, &input_mes, arg)?;
3132 Ok(types::ScriptResult::Ok)
3133}
3134
3135lazy_static::lazy_static! {
3136 static ref COUNTER: utils::counter::Counter = utils::counter::Counter::new();
3137 static ref EXIT_LISTENER: std::sync::Mutex<std::collections::BTreeMap<usize, Box<dyn Fn() + Send + Sync>>> = std::sync::Mutex::new(std::collections::BTreeMap::new());
3138 #[allow(unused)]
3139 static ref EXIT_LISTENER_ID: std::sync::atomic::AtomicUsize = std::sync::atomic::AtomicUsize::new(0);
3140}
3141
3142#[allow(dead_code)]
3143fn add_exit_listener<F: Fn() + Send + Sync + 'static>(f: F) -> usize {
3144 let id = EXIT_LISTENER_ID.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
3145 EXIT_LISTENER
3146 .lock()
3147 .unwrap_or_else(|err| err.into_inner())
3148 .insert(id, Box::new(f));
3149 id
3150}
3151
3152#[allow(dead_code)]
3153fn remove_exit_listener(id: usize) {
3154 EXIT_LISTENER
3155 .lock()
3156 .unwrap_or_else(|err| err.into_inner())
3157 .remove(&id);
3158}
3159
3160fn main() {
3161 let _ = ctrlc::try_set_handler(|| {
3162 let listeners = EXIT_LISTENER.lock().unwrap_or_else(|err| err.into_inner());
3163 for (_, f) in listeners.iter() {
3164 f();
3165 }
3166 eprintln!("Aborted.");
3167 eprintln!("{}", std::ops::Deref::deref(&COUNTER));
3168 std::process::exit(1);
3169 });
3170 let arg = args::parse_args();
3171 let argn = std::sync::Arc::new(arg.clone());
3172 if arg.backtrace {
3173 unsafe { std::env::set_var("RUST_LIB_BACKTRACE", "1") };
3174 }
3175 let cfg = std::sync::Arc::new(types::ExtraConfig {
3176 #[cfg(feature = "circus")]
3177 circus_mes_type: arg.circus_mes_type.clone(),
3178 #[cfg(feature = "escude-arc")]
3179 escude_fake_compress: arg.escude_fake_compress,
3180 #[cfg(feature = "escude")]
3181 escude_enum_scr: arg.escude_enum_scr.clone(),
3182 #[cfg(feature = "bgi")]
3183 bgi_import_duplicate: arg.bgi_import_duplicate,
3184 #[cfg(feature = "bgi")]
3185 bgi_disable_append: arg.bgi_disable_append,
3186 #[cfg(feature = "image")]
3187 image_type: arg.image_type.clone(),
3188 #[cfg(all(feature = "bgi-arc", feature = "bgi-img"))]
3189 bgi_is_sysgrp_arc: arg.bgi_is_sysgrp_arc.clone(),
3190 #[cfg(feature = "bgi-img")]
3191 bgi_img_scramble: arg.bgi_img_scramble.clone(),
3192 #[cfg(feature = "cat-system-arc")]
3193 cat_system_int_encrypt_password: args::get_cat_system_int_encrypt_password(&arg)
3194 .expect("Failed to get CatSystem2 int encrypt password"),
3195 #[cfg(feature = "cat-system-img")]
3196 cat_system_image_canvas: arg.cat_system_image_canvas,
3197 #[cfg(feature = "kirikiri")]
3198 kirikiri_language_index: arg.kirikiri_language_index.clone(),
3199 #[cfg(feature = "kirikiri")]
3200 kirikiri_export_chat: arg.kirikiri_export_chat,
3201 #[cfg(feature = "kirikiri")]
3202 kirikiri_chat_key: arg.kirikiri_chat_key.clone(),
3203 #[cfg(feature = "kirikiri")]
3204 kirikiri_chat_json: args::load_kirikiri_chat_json(&arg)
3205 .expect("Failed to load Kirikiri chat JSON"),
3206 #[cfg(feature = "kirikiri")]
3207 kirikiri_languages: arg
3208 .kirikiri_languages
3209 .clone()
3210 .map(|s| std::sync::Arc::new(s)),
3211 #[cfg(feature = "kirikiri")]
3212 kirikiri_remove_empty_lines: arg.kirikiri_remove_empty_lines,
3213 #[cfg(feature = "kirikiri")]
3214 kirikiri_name_commands: std::sync::Arc::new(std::collections::HashSet::from_iter(
3215 arg.kirikiri_name_commands.iter().cloned(),
3216 )),
3217 #[cfg(feature = "kirikiri")]
3218 kirikiri_message_commands: std::sync::Arc::new(std::collections::HashSet::from_iter(
3219 arg.kirikiri_message_commands.iter().cloned(),
3220 )),
3221 #[cfg(feature = "bgi-arc")]
3222 bgi_compress_file: arg.bgi_compress_file,
3223 #[cfg(feature = "bgi-arc")]
3224 bgi_compress_min_len: arg.bgi_compress_min_len,
3225 #[cfg(feature = "emote-img")]
3226 emote_pimg_overlay: arg.emote_pimg_overlay,
3227 #[cfg(feature = "artemis-arc")]
3228 artemis_arc_disable_xor: arg.artemis_arc_disable_xor,
3229 #[cfg(feature = "artemis")]
3230 artemis_indent: arg.artemis_indent,
3231 #[cfg(feature = "artemis")]
3232 artemis_no_indent: arg.artemis_no_indent,
3233 #[cfg(feature = "artemis")]
3234 artemis_max_line_width: arg.artemis_max_line_width,
3235 #[cfg(feature = "artemis")]
3236 artemis_ast_lang: arg.artemis_ast_lang.clone(),
3237 #[cfg(feature = "cat-system")]
3238 cat_system_cstl_lang: arg.cat_system_cstl_lang.clone(),
3239 #[cfg(feature = "flate2")]
3240 zlib_compression_level: arg.zlib_compression_level,
3241 #[cfg(feature = "image")]
3242 png_compression_level: arg.png_compression_level,
3243 #[cfg(feature = "circus-img")]
3244 circus_crx_keep_original_bpp: arg.circus_crx_keep_original_bpp,
3245 #[cfg(feature = "circus-img")]
3246 circus_crx_zstd: arg.circus_crx_zstd,
3247 #[cfg(feature = "zstd")]
3248 zstd_compression_level: arg.zstd_compression_level,
3249 #[cfg(feature = "circus-img")]
3250 circus_crx_mode: arg.circus_crx_mode,
3251 #[cfg(feature = "ex-hibit")]
3252 ex_hibit_rld_xor_key: args::load_ex_hibit_rld_xor_key(&arg)
3253 .expect("Failed to load RLD XOR key"),
3254 #[cfg(feature = "ex-hibit")]
3255 ex_hibit_rld_def_xor_key: args::load_ex_hibit_rld_def_xor_key(&arg)
3256 .expect("Failed to load RLD DEF XOR key"),
3257 #[cfg(feature = "ex-hibit")]
3258 ex_hibit_rld_keys: scripts::ex_hibit::rld::load_keys(arg.ex_hibit_rld_keys.as_ref())
3259 .expect("Failed to load RLD keys"),
3260 #[cfg(feature = "ex-hibit")]
3261 ex_hibit_rld_def_keys: scripts::ex_hibit::rld::load_keys(
3262 arg.ex_hibit_rld_def_keys.as_ref(),
3263 )
3264 .expect("Failed to load RLD DEF keys"),
3265 #[cfg(feature = "mozjpeg")]
3266 jpeg_quality: arg.jpeg_quality,
3267 #[cfg(feature = "webp")]
3268 webp_lossless: arg.webp_lossless,
3269 #[cfg(feature = "webp")]
3270 webp_quality: arg.webp_quality,
3271 #[cfg(feature = "circus-img")]
3272 circus_crx_canvas: arg.circus_crx_canvas,
3273 custom_yaml: arg.custom_yaml.unwrap_or_else(|| {
3274 arg.output_type
3275 .map(|s| s == types::OutputScriptType::Yaml)
3276 .unwrap_or(false)
3277 }),
3278 #[cfg(feature = "entis-gls")]
3279 entis_gls_srcxml_lang: arg.entis_gls_srcxml_lang.clone(),
3280 #[cfg(feature = "will-plus")]
3281 will_plus_ws2_no_disasm: arg.will_plus_ws2_no_disasm,
3282 #[cfg(feature = "artemis-panmimisoft")]
3283 artemis_panmimisoft_txt_blacklist_names: std::sync::Arc::new(
3284 args::get_artemis_panmimisoft_txt_blacklist_names(&arg).unwrap(),
3285 ),
3286 #[cfg(feature = "artemis-panmimisoft")]
3287 artemis_panmimisoft_txt_lang: arg.artemis_panmimisoft_txt_lang.clone(),
3288 #[cfg(feature = "lossless-audio")]
3289 lossless_audio_fmt: arg.lossless_audio_fmt,
3290 #[cfg(feature = "audio-flac")]
3291 flac_compression_level: arg.flac_compression_level,
3292 #[cfg(feature = "artemis")]
3293 artemis_asb_format_lua: !arg.artemis_asb_no_format_lua,
3294 #[cfg(feature = "kirikiri")]
3295 kirikiri_title: arg.kirikiri_title,
3296 #[cfg(feature = "favorite")]
3297 favorite_hcb_filter_ascii: !arg.favorite_hcb_no_filter_ascii,
3298 #[cfg(feature = "bgi-img")]
3299 bgi_img_workers: arg.bgi_img_workers,
3300 #[cfg(feature = "image-jxl")]
3301 jxl_lossless: !arg.jxl_lossy,
3302 #[cfg(feature = "image-jxl")]
3303 jxl_distance: arg.jxl_distance,
3304 #[cfg(feature = "image-jxl")]
3305 jxl_workers: arg.jxl_workers,
3306 #[cfg(feature = "emote-img")]
3307 psb_process_tlg: !arg.psb_no_process_tlg,
3308 #[cfg(feature = "softpal-img")]
3309 pgd_fake_compress: !arg.pgd_compress,
3310 #[cfg(feature = "softpal")]
3311 softpal_add_message_index: arg.softpal_add_message_index,
3312 #[cfg(feature = "kirikiri")]
3313 kirikiri_chat_multilang: !arg.kirikiri_chat_no_multilang,
3314 #[cfg(feature = "kirikiri-arc")]
3315 xp3_simple_crypt: !arg.xp3_no_simple_crypt,
3316 #[cfg(feature = "kirikiri-arc")]
3317 xp3_mdf_decompress: !arg.xp3_no_mdf_decompress,
3318 #[cfg(feature = "kirikiri-arc")]
3319 xp3_segmenter: arg.xp3_segmenter,
3320 #[cfg(feature = "kirikiri-arc")]
3321 xp3_compress_files: !arg.xp3_no_compress_files,
3322 #[cfg(feature = "kirikiri-arc")]
3323 xp3_compress_index: !arg.xp3_no_compress_index,
3324 #[cfg(feature = "kirikiri-arc")]
3325 xp3_compress_workers: arg.xp3_compress_workers,
3326 #[cfg(feature = "kirikiri-arc")]
3327 xp3_zstd: arg.xp3_zstd,
3328 #[cfg(feature = "kirikiri-arc")]
3329 xp3_zopfli: arg.xp3_zopfli,
3330 #[cfg(feature = "kirikiri-arc")]
3331 xp3_pack_workers: arg.xp3_pack_workers,
3332 #[cfg(feature = "kirikiri")]
3333 kirikiri_language_insert: arg.kirikiri_language_insert,
3334 #[cfg(feature = "musica-arc")]
3335 musica_game_title: arg.musica_game_title.clone(),
3336 #[cfg(feature = "musica-arc")]
3337 musica_xor_key: arg.musica_xor_key,
3338 #[cfg(feature = "musica-arc")]
3339 musica_compress: arg.musica_compress,
3340 #[cfg(feature = "kirikiri-arc")]
3341 xp3_no_adler: arg.xp3_no_adler,
3342 #[cfg(feature = "bgi")]
3343 bgi_add_space: arg.bgi_add_space,
3344 #[cfg(feature = "escude")]
3345 escude_op: arg.escude_op,
3346 #[cfg(feature = "zopfli")]
3347 zopfli_iteration_count: arg.zopfli_iteration_count,
3348 #[cfg(feature = "zopfli")]
3349 zopfli_iterations_without_improvement: arg.zopfli_iterations_without_improvement,
3350 #[cfg(feature = "zopfli")]
3351 zopfli_maximum_block_splits: arg.zopfli_maximum_block_splits,
3352 #[cfg(feature = "artemis-panmimisoft")]
3353 artemis_panmimisoft_txt_multi_lang: arg.artemis_panmimisoft_txt_multi_lang,
3354 #[cfg(feature = "entis-gls")]
3355 entis_gls_csx_disasm: arg.entis_gls_csx_disasm,
3356 #[cfg(feature = "entis-gls")]
3357 entis_gls_csx_lf: arg.entis_gls_csx_lf.clone(),
3358 #[cfg(feature = "entis-gls")]
3359 entis_gls_csx_ver: arg.entis_gls_csx_ver,
3360 #[cfg(feature = "entis-gls")]
3361 entis_gls_csx_v2_ver: arg.entis_gls_csx_v2_ver,
3362 #[cfg(feature = "entis-gls")]
3363 entis_gls_csx_no_part_label: arg.entis_gls_csx_no_part_label,
3364 #[cfg(feature = "qlie-img")]
3365 qlie_abmp10_process_abmp10: !arg.qlie_abmp10_no_process_abmp10,
3366 #[cfg(feature = "qlie-arc")]
3367 qlie_pack_keyfile: arg.qlie_pack_keyfile.clone(),
3368 #[cfg(feature = "qlie-arc")]
3369 qlie_pack_compress_files: arg.qlie_pack_compress_files,
3370 #[cfg(feature = "qlie-img")]
3371 qlie_dpng_use_raw_png: arg.qlie_dpng_use_raw_png,
3372 #[cfg(feature = "qlie-img")]
3373 qlie_dpng_psd: arg.qlie_dpng_psd,
3374 #[cfg(feature = "utils-psd")]
3375 psd_compress: !arg.psd_no_compress,
3376 #[cfg(feature = "emote-img")]
3377 emote_pimg_psd: arg.emote_pimg_psd,
3378 #[cfg(feature = "kirikiri")]
3379 kirikiri_ks_hitret: arg.kirikiri_ks_hitret,
3380 #[cfg(feature = "kirikiri")]
3381 kirikiri_ks_lf: arg.kirikiri_ks_lf.clone(),
3382 #[cfg(feature = "kirikiri")]
3383 kirikiri_message_tags: std::sync::Arc::new(std::collections::HashSet::from_iter(
3384 arg.kirikiri_message_tags.iter().cloned(),
3385 )),
3386 #[cfg(feature = "kirikiri")]
3387 kirikiri_ks_bom: arg.kirikiri_ks_bom,
3388 #[cfg(feature = "emote-img")]
3389 bc7: arg.bc7,
3390 });
3391 match &arg.command {
3392 args::Command::Export { input, output } => {
3393 let (scripts, is_dir) =
3394 utils::files::collect_files(input, arg.recursive, false).unwrap();
3395 if is_dir {
3396 match &output {
3397 Some(output) => {
3398 let op = std::path::Path::new(output);
3399 if op.exists() {
3400 if !op.is_dir() {
3401 eprintln!("Output path is not a directory");
3402 std::process::exit(
3403 argn.exit_code_all_failed.unwrap_or(argn.exit_code),
3404 );
3405 }
3406 } else {
3407 std::fs::create_dir_all(op).unwrap();
3408 }
3409 }
3410 None => {}
3411 }
3412 }
3413 let root_dir = if is_dir {
3414 Some(std::path::Path::new(input))
3415 } else {
3416 None
3417 };
3418 #[cfg(feature = "image")]
3419 let img_threadpool = if arg.image_workers > 1 {
3420 let tp = std::sync::Arc::new(
3421 utils::threadpool::ThreadPool::<Result<(), anyhow::Error>>::new(
3422 arg.image_workers,
3423 Some("img-output-worker-"),
3424 false,
3425 )
3426 .expect("Failed to create image thread pool"),
3427 );
3428 let tp2 = tp.clone();
3429 let id = add_exit_listener(move || {
3430 for r in tp2.take_results() {
3431 if let Err(e) = r {
3432 eprintln!("{}", e);
3433 COUNTER.inc_error();
3434 } else {
3435 COUNTER.inc(types::ScriptResult::Ok);
3436 }
3437 }
3438 });
3439 Some((tp, id))
3440 } else {
3441 None
3442 };
3443 for script in scripts.iter() {
3444 #[cfg(feature = "image")]
3445 let re = export_script(
3446 &script,
3447 &arg,
3448 cfg.clone(),
3449 output,
3450 root_dir,
3451 img_threadpool.as_ref().map(|(t, _)| &**t),
3452 );
3453 #[cfg(not(feature = "image"))]
3454 let re = export_script(&script, &arg, cfg.clone(), output, root_dir);
3455 match re {
3456 Ok(s) => {
3457 COUNTER.inc(s);
3458 }
3459 Err(e) => {
3460 COUNTER.inc_error();
3461 eprintln!("Error exporting {}: {}", script, e);
3462 if arg.backtrace {
3463 eprintln!("Backtrace: {}", e.backtrace());
3464 }
3465 }
3466 }
3467 #[cfg(feature = "image")]
3468 img_threadpool.as_ref().map(|(t, _)| {
3469 for r in t.take_results() {
3470 if let Err(e) = r {
3471 COUNTER.inc_error();
3472 eprintln!("{}", e);
3473 } else {
3474 COUNTER.inc(types::ScriptResult::Ok);
3475 }
3476 }
3477 });
3478 }
3479 #[cfg(feature = "image")]
3480 img_threadpool.map(|(t, id)| {
3481 t.join();
3482 remove_exit_listener(id);
3483 for r in t.take_results() {
3484 if let Err(e) = r {
3485 COUNTER.inc_error();
3486 eprintln!("{}", e);
3487 } else {
3488 COUNTER.inc(types::ScriptResult::Ok);
3489 }
3490 }
3491 });
3492 }
3493 args::Command::Import(args) => {
3494 let name_csv = match &args.name_csv {
3495 Some(name_csv) => {
3496 let name_table = utils::name_replacement::read_csv(name_csv).unwrap();
3497 Some(name_table)
3498 }
3499 None => None,
3500 };
3501 let repl = std::sync::Arc::new(match &args.replacement_json {
3502 Some(replacement_json) => {
3503 let b = utils::files::read_file(replacement_json).unwrap();
3504 let s = String::from_utf8(b).unwrap();
3505 let table = serde_json::from_str::<types::ReplacementTable>(&s).unwrap();
3506 Some(table)
3507 }
3508 None => None,
3509 });
3510 let (scripts, is_dir) =
3511 utils::files::collect_files(&args.input, arg.recursive, false).unwrap();
3512 if is_dir {
3513 let pb = std::path::Path::new(&args.patched);
3514 if pb.exists() {
3515 if !pb.is_dir() {
3516 eprintln!("Patched path is not a directory");
3517 std::process::exit(argn.exit_code_all_failed.unwrap_or(argn.exit_code));
3518 }
3519 } else {
3520 std::fs::create_dir_all(pb).unwrap();
3521 }
3522 }
3523 let root_dir = if is_dir {
3524 Some(std::path::Path::new(&args.input))
3525 } else {
3526 None
3527 };
3528 let workers = if args.jobs > 1 {
3529 Some(
3530 utils::threadpool::ThreadPool::<()>::new(
3531 args.jobs,
3532 Some("import-worker-"),
3533 true,
3534 )
3535 .unwrap(),
3536 )
3537 } else {
3538 None
3539 };
3540 let dep_files = if args.dep_file.is_some() {
3541 Some(std::sync::Arc::new(std::sync::Mutex::new(
3542 std::collections::HashMap::new(),
3543 )))
3544 } else {
3545 None
3546 };
3547 for script in scripts.iter() {
3548 if let Some(workers) = workers.as_ref() {
3549 let arg = argn.clone();
3550 let cfg = cfg.clone();
3551 let script = script.clone();
3552 let name_csv = name_csv.as_ref().map(|s| s.clone());
3553 let repl = repl.clone();
3554 let root_dir = root_dir.map(|s| s.to_path_buf());
3555 let args = args.clone();
3556 let dep_files = dep_files.clone();
3557 if let Err(e) = workers.execute(
3558 move |_| {
3559 let mut dep_graph = if dep_files.is_some() {
3560 Some((String::new(), Vec::new()))
3561 } else {
3562 None
3563 };
3564 let re = import_script(
3565 &script,
3566 &arg,
3567 cfg,
3568 &args,
3569 root_dir.as_ref().map(|s| s.as_path()),
3570 name_csv.as_ref(),
3571 (*repl).as_ref(),
3572 dep_graph.as_mut(),
3573 );
3574 match re {
3575 Ok(s) => {
3576 COUNTER.inc(s);
3577 if let Some((fname, deps)) = dep_graph {
3578 if let Some(dep_files) = dep_files {
3579 let mut lock =
3580 crate::ext::mutex::MutexExt::lock_blocking(
3581 dep_files.as_ref(),
3582 );
3583 lock.insert(fname, deps);
3584 }
3585 }
3586 }
3587 Err(e) => {
3588 COUNTER.inc_error();
3589 eprintln!("Error exporting {}: {}", script, e);
3590 if arg.backtrace {
3591 eprintln!("Backtrace: {}", e.backtrace());
3592 }
3593 }
3594 }
3595 },
3596 true,
3597 ) {
3598 COUNTER.inc_error();
3599 eprintln!("Error executing import worker: {}", e);
3600 }
3601 } else {
3602 let mut dep_graph = if dep_files.is_some() {
3603 Some((String::new(), Vec::new()))
3604 } else {
3605 None
3606 };
3607 let re = import_script(
3608 &script,
3609 &arg,
3610 cfg.clone(),
3611 args,
3612 root_dir,
3613 name_csv.as_ref(),
3614 (*repl).as_ref(),
3615 dep_graph.as_mut(),
3616 );
3617 match re {
3618 Ok(s) => {
3619 COUNTER.inc(s);
3620 if let Some((fname, deps)) = dep_graph {
3621 if let Some(dep_files) = dep_files.as_ref() {
3622 let mut lock = crate::ext::mutex::MutexExt::lock_blocking(
3623 dep_files.as_ref(),
3624 );
3625 lock.insert(fname, deps);
3626 }
3627 }
3628 }
3629 Err(e) => {
3630 COUNTER.inc_error();
3631 eprintln!("Error exporting {}: {}", script, e);
3632 if arg.backtrace {
3633 eprintln!("Backtrace: {}", e.backtrace());
3634 }
3635 }
3636 }
3637 }
3638 }
3639 if let Some(map) = dep_files {
3640 let lock = crate::ext::mutex::MutexExt::lock_blocking(map.as_ref());
3641 if let Some(dep_file) = &args.dep_file {
3642 let df = std::fs::File::create(dep_file).unwrap();
3643 let mut df = std::io::BufWriter::new(df);
3644 use std::io::Write;
3645 for (fname, deps) in lock.iter() {
3646 write!(df, "{}:", escape_dep_string(fname)).unwrap();
3647 for d in deps {
3648 write!(df, " {}", escape_dep_string(d)).unwrap();
3649 }
3650 writeln!(df).unwrap();
3651 }
3652 }
3653 }
3654 }
3655 args::Command::Pack {
3656 input,
3657 output,
3658 backslash,
3659 } => {
3660 let re = pack_archive(
3661 input,
3662 output.as_ref().map(|s| s.as_str()),
3663 &arg,
3664 cfg.clone(),
3665 *backslash,
3666 );
3667 if let Err(e) = re {
3668 COUNTER.inc_error();
3669 eprintln!("Error packing archive: {}", e);
3670 }
3671 }
3672 args::Command::Unpack { input, output } => {
3673 let (scripts, is_dir) = utils::files::collect_arc_files(input, arg.recursive).unwrap();
3674 if is_dir {
3675 match &output {
3676 Some(output) => {
3677 let op = std::path::Path::new(output);
3678 if op.exists() {
3679 if !op.is_dir() {
3680 eprintln!("Output path is not a directory");
3681 std::process::exit(
3682 argn.exit_code_all_failed.unwrap_or(argn.exit_code),
3683 );
3684 }
3685 } else {
3686 std::fs::create_dir_all(op).unwrap();
3687 }
3688 }
3689 None => {}
3690 }
3691 }
3692 let root_dir = if is_dir {
3693 Some(std::path::Path::new(input))
3694 } else {
3695 None
3696 };
3697 for script in scripts.iter() {
3698 let re = unpack_archive(&script, &arg, cfg.clone(), output, root_dir);
3699 match re {
3700 Ok(s) => {
3701 COUNTER.inc(s);
3702 }
3703 Err(e) => {
3704 COUNTER.inc_error();
3705 eprintln!("Error unpacking {}: {}", script, e);
3706 if arg.backtrace {
3707 eprintln!("Backtrace: {}", e.backtrace());
3708 }
3709 }
3710 }
3711 }
3712 }
3713 args::Command::Create { input, output } => {
3714 let re = create_file(
3715 input,
3716 output.as_ref().map(|s| s.as_str()),
3717 &arg,
3718 cfg.clone(),
3719 );
3720 if let Err(e) = re {
3721 COUNTER.inc_error();
3722 eprintln!("Error creating file: {}", e);
3723 if arg.backtrace {
3724 eprintln!("Backtrace: {}", e.backtrace());
3725 }
3726 }
3727 }
3728 args::Command::PackV2 {
3729 output,
3730 input,
3731 backslash,
3732 no_dir,
3733 dep_file,
3734 } => {
3735 if !input.is_empty() {
3736 let input = input.iter().map(|s| s.as_str()).collect::<Vec<_>>();
3737 let re = pack_archive_v2(
3738 &input,
3739 output.as_ref().map(|s| s.as_str()),
3740 &arg,
3741 cfg.clone(),
3742 *backslash,
3743 *no_dir,
3744 dep_file.as_ref().map(|s| s.as_str()),
3745 );
3746 if let Err(e) = re {
3747 COUNTER.inc_error();
3748 eprintln!("Error packing archive: {}", e);
3749 }
3750 } else {
3751 eprintln!("No input files specified for packing.");
3752 }
3753 }
3754 args::Command::Convert {
3755 input_type,
3756 output_type,
3757 input,
3758 output,
3759 } => {
3760 if input_type.is_custom() {
3761 eprintln!("Custom input type is not supported for conversion.");
3762 std::process::exit(argn.exit_code_all_failed.unwrap_or(argn.exit_code));
3763 }
3764 if output_type.is_custom() {
3765 eprintln!("Custom output type is not supported for conversion.");
3766 std::process::exit(argn.exit_code_all_failed.unwrap_or(argn.exit_code));
3767 }
3768 let (scripts, is_dir) =
3769 utils::files::collect_ext_files(input, arg.recursive, &[input_type.as_ref()])
3770 .unwrap();
3771 if is_dir {
3772 match &output {
3773 Some(output) => {
3774 let op = std::path::Path::new(output);
3775 if op.exists() {
3776 if !op.is_dir() {
3777 eprintln!("Output path is not a directory");
3778 std::process::exit(
3779 argn.exit_code_all_failed.unwrap_or(argn.exit_code),
3780 );
3781 }
3782 } else {
3783 std::fs::create_dir_all(op).unwrap();
3784 }
3785 }
3786 None => {}
3787 }
3788 }
3789 let root_dir = if is_dir {
3790 Some(std::path::Path::new(input))
3791 } else {
3792 None
3793 };
3794 for script in scripts.iter() {
3795 let re = convert_file(
3796 &script,
3797 *input_type,
3798 output.as_ref().map(|s| s.as_str()),
3799 *output_type,
3800 &arg,
3801 root_dir,
3802 );
3803 match re {
3804 Ok(s) => {
3805 COUNTER.inc(s);
3806 }
3807 Err(e) => {
3808 COUNTER.inc_error();
3809 eprintln!("Error converting {}: {}", script, e);
3810 if arg.backtrace {
3811 eprintln!("Backtrace: {}", e.backtrace());
3812 }
3813 }
3814 }
3815 }
3816 }
3817 }
3818 let counter = std::ops::Deref::deref(&COUNTER);
3819 eprintln!("{}", counter);
3820 if counter.all_failed() {
3821 std::process::exit(argn.exit_code_all_failed.unwrap_or(argn.exit_code));
3822 } else if counter.has_error() {
3823 std::process::exit(argn.exit_code);
3824 }
3825}