content_input.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283
  1. # Copyright (C) 2025 AIDC-AI
  2. #
  3. # Licensed under the Apache License, Version 2.0 (the "License");
  4. # you may not use this file except in compliance with the License.
  5. # You may obtain a copy of the License at
  6. # http://www.apache.org/licenses/LICENSE-2.0
  7. # Unless required by applicable law or agreed to in writing, software
  8. # distributed under the License is distributed on an "AS IS" BASIS,
  9. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. # See the License for the specific language governing permissions and
  11. # limitations under the License.
  12. """
  13. Content input components for web UI (left column)
  14. """
  15. import streamlit as st
  16. from web.i18n import tr
  17. from web.utils.async_helpers import get_project_version
  18. def render_content_input():
  19. """Render content input section (left column) with batch support"""
  20. with st.container(border=True):
  21. st.markdown(f"**{tr('section.content_input')}**")
  22. # ====================================================================
  23. # Step 1: Batch mode toggle (highest priority)
  24. # ====================================================================
  25. batch_mode = st.checkbox(
  26. tr("batch.mode_label"),
  27. value=False,
  28. help=tr("batch.mode_help")
  29. )
  30. if not batch_mode:
  31. # ================================================================
  32. # Single task mode (original logic, unchanged)
  33. # ================================================================
  34. # Processing mode selection
  35. mode = st.radio(
  36. "Processing Mode",
  37. ["generate", "fixed"],
  38. horizontal=True,
  39. format_func=lambda x: tr(f"mode.{x}"),
  40. label_visibility="collapsed"
  41. )
  42. # Text input (unified for both modes)
  43. text_placeholder = tr("input.topic_placeholder") if mode == "generate" else tr("input.content_placeholder")
  44. text_height = 120 if mode == "generate" else 200
  45. text_help = tr("input.text_help_generate") if mode == "generate" else tr("input.text_help_fixed")
  46. text = st.text_area(
  47. tr("input.text"),
  48. placeholder=text_placeholder,
  49. height=text_height,
  50. help=text_help
  51. )
  52. # Split mode selector (only show in fixed mode)
  53. if mode == "fixed":
  54. split_mode_options = {
  55. "paragraph": tr("split.mode_paragraph"),
  56. "line": tr("split.mode_line"),
  57. "sentence": tr("split.mode_sentence"),
  58. }
  59. split_mode = st.selectbox(
  60. tr("split.mode_label"),
  61. options=list(split_mode_options.keys()),
  62. format_func=lambda x: split_mode_options[x],
  63. index=0, # Default to paragraph mode
  64. help=tr("split.mode_help")
  65. )
  66. else:
  67. split_mode = "paragraph" # Default for generate mode (not used)
  68. # Title input (optional for both modes)
  69. title = st.text_input(
  70. tr("input.title"),
  71. placeholder=tr("input.title_placeholder"),
  72. help=tr("input.title_help")
  73. )
  74. # Number of scenes (only show in generate mode)
  75. if mode == "generate":
  76. n_scenes = st.slider(
  77. tr("video.frames"),
  78. min_value=3,
  79. max_value=30,
  80. value=5,
  81. help=tr("video.frames_help"),
  82. label_visibility="collapsed"
  83. )
  84. st.caption(tr("video.frames_label", n=n_scenes))
  85. else:
  86. # Fixed mode: n_scenes is ignored, set default value
  87. n_scenes = 5
  88. st.info(tr("video.frames_fixed_mode_hint"))
  89. return {
  90. "batch_mode": False,
  91. "mode": mode,
  92. "text": text,
  93. "title": title,
  94. "n_scenes": n_scenes,
  95. "split_mode": split_mode
  96. }
  97. else:
  98. # ================================================================
  99. # Batch mode (simplified YAGNI version)
  100. # ================================================================
  101. st.markdown(f"**{tr('batch.section_title')}**")
  102. # Batch rules info
  103. st.info(f"""
  104. **{tr('batch.rules_title')}**
  105. - ✅ {tr('batch.rule_1')}
  106. - ✅ {tr('batch.rule_2')}
  107. - ✅ {tr('batch.rule_3')}
  108. """)
  109. # Batch topics input
  110. text_input = st.text_area(
  111. tr("batch.topics_label"),
  112. height=300,
  113. placeholder=tr("batch.topics_placeholder"),
  114. help=tr("batch.topics_help")
  115. )
  116. # Split topics by newline
  117. if text_input:
  118. # Simple split by newline, filter empty lines
  119. topics = [
  120. line.strip()
  121. for line in text_input.strip().split('\n')
  122. if line.strip()
  123. ]
  124. if topics:
  125. # Check count limit
  126. if len(topics) > 100:
  127. st.error(tr("batch.count_error", count=len(topics)))
  128. topics = []
  129. else:
  130. st.success(tr("batch.count_success", count=len(topics)))
  131. # Preview topics list
  132. with st.expander(tr("batch.preview_title"), expanded=False):
  133. for i, topic in enumerate(topics, 1):
  134. st.markdown(f"`{i}.` {topic}")
  135. else:
  136. topics = []
  137. else:
  138. topics = []
  139. st.markdown("---")
  140. # Title prefix (optional)
  141. title_prefix = st.text_input(
  142. tr("batch.title_prefix_label"),
  143. placeholder=tr("batch.title_prefix_placeholder"),
  144. help=tr("batch.title_prefix_help")
  145. )
  146. # Number of scenes (unified for all videos)
  147. n_scenes = st.slider(
  148. tr("batch.n_scenes_label"),
  149. min_value=3,
  150. max_value=30,
  151. value=5,
  152. help=tr("batch.n_scenes_help")
  153. )
  154. st.caption(tr("batch.n_scenes_caption", n=n_scenes))
  155. # Config info
  156. st.info(f"📌 {tr('batch.config_info')}")
  157. return {
  158. "batch_mode": True,
  159. "topics": topics,
  160. "mode": "generate", # Fixed to AI generate content
  161. "title_prefix": title_prefix,
  162. "n_scenes": n_scenes,
  163. }
  164. def render_bgm_section(key_prefix=""):
  165. """Render BGM selection section"""
  166. with st.container(border=True):
  167. st.markdown(f"**{tr('section.bgm')}**")
  168. with st.expander(tr("help.feature_description"), expanded=False):
  169. st.markdown(f"**{tr('help.what')}**")
  170. st.markdown(tr("bgm.what"))
  171. st.markdown(f"**{tr('help.how')}**")
  172. st.markdown(tr("bgm.how"))
  173. # Dynamically scan bgm folder for music files (merged from bgm/ and data/bgm/)
  174. from pixelle_video.utils.os_util import list_resource_files
  175. try:
  176. all_files = list_resource_files("bgm")
  177. # Filter to audio files only
  178. audio_extensions = ('.mp3', '.wav', '.flac', '.m4a', '.aac', '.ogg')
  179. bgm_files = sorted([f for f in all_files if f.lower().endswith(audio_extensions)])
  180. except Exception as e:
  181. st.warning(f"Failed to load BGM files: {e}")
  182. bgm_files = []
  183. # Add special "None" option
  184. bgm_options = [tr("bgm.none")] + bgm_files
  185. # Default to "default.mp3" if exists, otherwise first option
  186. default_index = 0
  187. if "default.mp3" in bgm_files:
  188. default_index = bgm_options.index("default.mp3")
  189. bgm_choice = st.selectbox(
  190. "BGM",
  191. bgm_options,
  192. index=default_index,
  193. label_visibility="collapsed",
  194. key=f"{key_prefix}bgm_selector"
  195. )
  196. # BGM volume slider (only show when BGM is selected)
  197. if bgm_choice != tr("bgm.none"):
  198. bgm_volume = st.slider(
  199. tr("bgm.volume"),
  200. min_value=0.0,
  201. max_value=0.5,
  202. value=0.2,
  203. step=0.01,
  204. format="%.2f",
  205. key=f"{key_prefix}bgm_volume_slider",
  206. help=tr("bgm.volume_help")
  207. )
  208. else:
  209. bgm_volume = 0.2 # Default value when no BGM selected
  210. # BGM preview button (only if BGM is not "None")
  211. if bgm_choice != tr("bgm.none"):
  212. if st.button(tr("bgm.preview"), key=f"{key_prefix}preview_bgm", use_container_width=True):
  213. from pixelle_video.utils.os_util import get_resource_path, resource_exists
  214. try:
  215. if resource_exists("bgm", bgm_choice):
  216. bgm_file_path = get_resource_path("bgm", bgm_choice)
  217. st.audio(bgm_file_path)
  218. else:
  219. st.error(tr("bgm.preview_failed", file=bgm_choice))
  220. except Exception as e:
  221. st.error(f"{tr('bgm.preview_failed', file=bgm_choice)}: {e}")
  222. # Use full filename for bgm_path (including extension)
  223. bgm_path = None if bgm_choice == tr("bgm.none") else bgm_choice
  224. return {
  225. "bgm_path": bgm_path,
  226. "bgm_volume": bgm_volume
  227. }
  228. def render_version_info():
  229. """Render version info and GitHub link"""
  230. with st.container(border=True):
  231. st.markdown(f"**{tr('version.title')}**")
  232. version = get_project_version()
  233. github_url = "https://github.com/AIDC-AI/Pixelle-Video"
  234. # Version and GitHub link in one line
  235. github_url = "https://github.com/AIDC-AI/Pixelle-Video"
  236. badge_url = "https://img.shields.io/github/stars/AIDC-AI/Pixelle-Video"
  237. st.markdown(
  238. f'{tr("version.current")}: `{version}`    '
  239. f'<a href="{github_url}" target="_blank">'
  240. f'<img src="{badge_url}" alt="GitHub stars" style="vertical-align: middle;">'
  241. f'</a>',
  242. unsafe_allow_html=True)