video_utils.py 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154
  1. import pandas as pd
  2. from pathlib import Path
  3. import subprocess
  4. import shutil
  5. import os
  6. import chardet
  7. import zipfile
  8. from io import BytesIO
  9. from translate import Translator
  10. from langdetect import detect
  11. from chardet.universaldetector import UniversalDetector
  12. import numpy as np
  13. from openai import OpenAI
  14. from iso639 import Lang
  15. DEFAULT_ENCODING = "utf-8"
  16. client = OpenAI(base_url="http://192.168.192.84:8080/v1", api_key='choozmo9')
  17. system_prompt = (
  18. "You are a precise and literal translator. "
  19. "Translate the user's input from {from_lang} to {to_lang} as faithfully and literally as possible. "
  20. "Preserve the original structure and vocabulary. "
  21. "Avoid paraphrasing, interpretation, or creative rewrites. "
  22. "Do not add explanations. Just return the translated text only."
  23. )
  24. def guess_codec(filenames: list) -> str:
  25. codec_detector = UniversalDetector()
  26. for filename in filenames:
  27. codec_detector.feed(filename.encode('cp437'))
  28. if codec_detector.done:
  29. break
  30. result = codec_detector.close()
  31. encoding = result.get("encoding")
  32. return encoding or DEFAULT_ENCODING
  33. def check_zip(zip_filepath:str):
  34. path = Path(zip_filepath)
  35. with zipfile.ZipFile(str(path)) as zf:
  36. filenames = [x for x in zf.namelist() if not x.endswith('/')]
  37. result = guess_codec(filenames)
  38. true_filenames = [x.encode('cp437').decode(result) for x in zf.namelist() if not x.endswith('/')]
  39. # print(true_filenames)
  40. scenarios_files = [(x, i) for i, x in enumerate(true_filenames) if Path(x).suffix in [".xlsx", ".csv"] and not Path(x).name.startswith("._") and Path(x).stem != "style"]
  41. # print(scenarios_files)
  42. if len(scenarios_files) == 0:
  43. raise ValueError("no excel or csv file in zip.")
  44. if len(scenarios_files) > 1:
  45. raise ValueError("too many excel or csv file in zip.")
  46. f = zf.read(filenames[scenarios_files[0][1]])
  47. if Path(scenarios_files[0][0]).suffix == ".xlsx":
  48. table = pd.read_excel(BytesIO(f), dtype=object)
  49. elif Path(scenarios_files[0][0]).suffix == ".csv":
  50. table = pd.read_csv(BytesIO(f), dtype=object)
  51. table.reset_index(inplace=True)
  52. # print(table)
  53. stems = [Path(x).stem for x in true_filenames]
  54. for i in range(len(table)):
  55. # excel 裡的圖檔跟zip裡的檔案要一致
  56. if table.loc[i, ['素材']].isna().item():
  57. img = table.loc[i, ['素材']].item()
  58. print(img)
  59. img_files = [x.strip() for x in img.split(',')]
  60. for img in img_files:
  61. print(img)
  62. n = stems.count(img)
  63. if n == 0:
  64. raise ValueError(f"{img}: no such media file in zip.")
  65. elif n > 1:
  66. raise ValueError(f'too many same name media files as {img} in zip')
  67. # 需要tts文字或音檔
  68. if table.loc[i, ['字幕']].isna().item():
  69. if table.loc[i, ['音檔']].isna().item():
  70. raise ValueError(f'text or voice file is needed at scene {i+1}.')
  71. voice_file = table.loc[i, ['音檔']].item()
  72. n = stems.count(voice_file)
  73. if n != 1:
  74. raise ValueError(f"voice file is can't find is zip at scene {i+1}.")
  75. def update_zip(zip_path, lang, new_filename, voice):
  76. with zipfile.ZipFile(zip_path, 'r') as zip_in, zipfile.ZipFile(new_filename, 'w') as zip_out:
  77. for item in zip_in.infolist():
  78. with zip_in.open(item.filename) as src_file:
  79. if item.filename.split('.')[-1] == "xlsx":
  80. table = pd.read_excel(src_file, dtype=object)
  81. table['聲音'] = np.nan
  82. table.loc[0, ['聲音']] = voice
  83. table['發音'] = np.nan
  84. table = translate_table(table, lang)
  85. table.to_excel(Path(item.filename).name ,sheet_name='Sheet_name_1')
  86. zip_out.write(Path(item.filename).name, item.filename)
  87. os.remove(Path(item.filename).name)
  88. elif item.filename.split('.')[-1] == "csv":
  89. table = pd.read_csv(src_file, dtype=object)
  90. table = translate_table(table, lang)
  91. table['聲音'] = np.nan
  92. table.loc[0, ['聲音']] = voice
  93. table['發音'] = np.nan
  94. table.to_excel(Path(item.filename).name ,sheet_name='Sheet_name_1')
  95. zip_out.write(Path(item.filename).name, item.filename)
  96. os.remove(Path(item.filename).name)
  97. else:
  98. # それ以外のファイルはそのままコピー
  99. with zip_out.open(item.filename, 'w') as dst_file:
  100. shutil.copyfileobj(src_file, dst_file)
  101. # 旧ZIPを削除し、新ZIPをリネーム
  102. os.remove(zip_path)
  103. def translate_table(table, lang):
  104. print(f"translate to {lang}")
  105. for i in range(len(table)):
  106. if (not table.loc[i, ['大標']].isna().item()) and (text:=table.loc[i, ['字幕']].item()):
  107. print("大標:",text)
  108. #translator= Translator(to_lang=lang, from_lang=detect(text))
  109. #translation = translator.translate(text)
  110. translation = translate(text, lang)
  111. print("大標翻譯:",translation)
  112. table.loc[i, ['大標']] = translation
  113. if (not table.loc[i, ['字幕']].isna().item()) and (text:=table.loc[i, ['字幕']].item()):
  114. print('字幕:',text)
  115. #translator= Translator(to_lang=lang, from_lang=detect(text))
  116. #translation = translator.translate(text)
  117. translation = translate(text, lang)
  118. print('字幕翻譯:',translation)
  119. table.loc[i, ['字幕']] = translation
  120. return table
  121. def translate(text, to_lang:str):
  122. from_lang = Lang(detect(text)).name
  123. to_lang = Lang(to_lang.split("-")).name
  124. if to_lang == "Chinese":
  125. to_lang = "Traditional Chinese"
  126. completion = client.chat.completions.create(
  127. model="gemma",
  128. messages=[
  129. {
  130. "role": "system",
  131. "content": system_prompt.format(from_lang=from_lang, to_lang=to_lang)
  132. },
  133. {
  134. "role": "user",
  135. "content": text
  136. }
  137. ]
  138. )
  139. return completion.choices[0].message.content