CoolFace
Apppublic

SARTAZAI/Step-Audio-EditX

sourceHugging Faceupdated 8mo agoView on Hugging Face
0likes
app.py341 linesDownload Raw Back to root
1"""2Step-Audio-EditX - Audio Editing Demo using StepFun API3"""4import logging5import gradio as gr6 7from stepfun_api import get_api_token, process_audio  # transcribe_audio8 9# Configure logging10logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')11logger = logging.getLogger(__name__)12 13 14def get_supported_edit_types():15    """16    获取支持的编辑类型和选项17 18    Returns:19        Dict[str, list]: Dictionary of edit types and their options20    """21    return {22        "clone": [],23        "emotion": [24            'happy', 'angry', 'sad', 'humour', 'confusion', 'disgusted',25            'empathy', 'embarrass', 'fear', 'surprised', 'excited',26            'depressed', 'coldness', 'admiration', 'remove'27        ],28        "style": [29            'serious', 'arrogant', 'child', 'older', 'girl', 'pure',30            'sister', 'sweet', 'ethereal', 'whisper', 'gentle', 'recite',31            'generous', 'act_coy', 'warm', 'shy', 'comfort', 'authority',32            'chat', 'radio', 'soulful', 'story', 'vivid', 'program',33            'news', 'advertising', 'roar', 'murmur', 'shout', 'deeply', 'loudly',34            'remove', 'exaggerated'35        ],36        "vad": [],37        "denoise": [],38        "paralinguistic": [],39        "speed": ["faster", "slower", "more faster", "more slower"],40    }41 42 43class EditxTab:44    """Audio editing and voice cloning interface tab"""45 46    def __init__(self):47        self.edit_type_list = list(get_supported_edit_types().keys())48        self.logger = logging.getLogger(f"{__name__}.{self.__class__.__name__}")49 50    def history_messages_to_show(self, messages):51        """Convert message history to gradio chatbot format"""52        show_msgs = []53        for message in messages:54            edit_type = message['edit_type']55            edit_info = message['edit_info']56            source_text = message['source_text']57            target_text = message['target_text']58            raw_audio_path = message['raw_audio_path']59            edit_audio_path = message['edit_audio_path']60            type_str = f"{edit_type}-{edit_info}" if edit_info is not None else f"{edit_type}"61            show_msgs.extend([62                {"role": "user", "content": f"任务类型:{type_str}\n文本:{source_text}"},63                {"role": "user", "content": gr.Audio(value=raw_audio_path, interactive=False)},64                {"role": "assistant", "content": f"输出音频:\n文本:{target_text}"},65                {"role": "assistant", "content": gr.Audio(value=edit_audio_path, interactive=False)}66            ])67        return show_msgs68 69    def generate_clone(self, prompt_text_input, prompt_audio_input, generated_text, edit_type, edit_info, state):70        """Generate cloned audio using API"""71        self.logger.info("Starting voice cloning via API")72 73        # Input validation74        if not prompt_text_input or prompt_text_input.strip() == "":75            error_msg = "[Error] Uploaded text cannot be empty."76            self.logger.error(error_msg)77            return self.history_messages_to_show(state["history_messages"]) + [{"role": "user", "content": error_msg}], state78        if not prompt_audio_input:79            error_msg = "[Error] Uploaded audio cannot be empty."80            self.logger.error(error_msg)81            return self.history_messages_to_show(state["history_messages"]) + [{"role": "user", "content": error_msg}], state82        if not generated_text or generated_text.strip() == "":83            error_msg = "[Error] Clone content cannot be empty."84            self.logger.error(error_msg)85            return self.history_messages_to_show(state["history_messages"]) + [{"role": "user", "content": error_msg}], state86        if edit_type != "clone":87            error_msg = "[Error] CLONE button must use clone task."88            self.logger.error(error_msg)89            return self.history_messages_to_show(state["history_messages"]) + [{"role": "user", "content": error_msg}], state90 91        try:92            # Call API93            output_path = process_audio(94                prompt_audio_input, prompt_text_input, generated_text, edit_type, edit_info95            )96 97            # Create message for history98            cur_msg = {99                "edit_type": edit_type,100                "edit_info": edit_info,101                "source_text": prompt_text_input,102                "target_text": generated_text,103                "raw_audio_path": prompt_audio_input,104                "edit_audio_path": output_path,105            }106            state["history_audio"].append((output_path, generated_text))107            state["history_messages"].append(cur_msg)108 109            show_msgs = self.history_messages_to_show(state["history_messages"])110            self.logger.info("Voice cloning completed successfully")111            return show_msgs, state112 113        except Exception as e:114            error_msg = f"[Error] Clone failed: {str(e)}"115            self.logger.error(error_msg)116            return self.history_messages_to_show(state["history_messages"]) + [{"role": "user", "content": error_msg}], state117        118    def generate_edit(self, prompt_text_input, prompt_audio_input, generated_text, edit_type, edit_info, state):119        """Generate edited audio using API"""120        self.logger.info("Starting audio editing via API")121 122        # Input validation123        if not prompt_audio_input:124            error_msg = "[Error] Uploaded audio cannot be empty."125            self.logger.error(error_msg)126            return self.history_messages_to_show(state["history_messages"]) + [{"role": "user", "content": error_msg}], state127 128        try:129            # Determine which audio to use130            if len(state["history_audio"]) == 0:131                audio_to_edit = prompt_audio_input132                text_to_use = prompt_text_input133            else:134                audio_to_edit, previous_text = state["history_audio"][-1]135                text_to_use = previous_text136 137            # For para-linguistic, use generated_text; otherwise use source text138            if edit_type not in {"paralinguistic"}:139                generated_text = text_to_use140 141            # Call API142            output_path = process_audio(143                audio_to_edit, text_to_use, generated_text, edit_type, edit_info144            )145 146            # Create message for history147            cur_msg = {148                "edit_type": edit_type,149                "edit_info": edit_info,150                "source_text": text_to_use,151                "target_text": generated_text,152                "raw_audio_path": audio_to_edit,153                "edit_audio_path": output_path,154            }155            state["history_audio"].append((output_path, generated_text))156            state["history_messages"].append(cur_msg)157 158            show_msgs = self.history_messages_to_show(state["history_messages"])159            self.logger.info("Audio editing completed successfully")160            return show_msgs, state161 162        except Exception as e:163            error_msg = f"[Error] Edit failed: {str(e)}"164            self.logger.error(error_msg)165            return self.history_messages_to_show(state["history_messages"]) + [{"role": "user", "content": error_msg}], state166 167    def clear_history(self, state):168        """Clear conversation history"""169        state["history_messages"] = []170        state["history_audio"] = []171        return [], state172 173    # def auto_transcribe_audio(self, audio_path, state):174    #     """175    #     自动转录音频文件,一次性返回最终结果176 177    #     Args:178    #         audio_path: 音频文件路径179    #         state: 状态字典180 181    #     Returns:182    #         转录的文本内容和更新后的状态183    #     """184    #     if not audio_path:185    #         return "", state186 187    #     # 防止重复调用 - 简化逻辑188    #     if state.get("last_audio_path") == audio_path:189    #         self.logger.debug(f"⚠️ Skipping duplicate transcription request for: {audio_path}")190    #         return state.get("last_transcribed_text", ""), state191 192    #     try:193    #         # 更新音频路径194    #         state["last_audio_path"] = audio_path195 196    #         self.logger.info(f"🎙️ Starting auto transcription for: {audio_path}")197 198    #         # 使用stepfun_api中的transcribe_audio函数,不使用streaming模式199    #         transcribed_text = transcribe_audio(audio_path, streaming=False)200 201    #         # 转录完成,缓存结果202    #         state["last_transcribed_text"] = transcribed_text203 204    #         self.logger.info(f"✅ Auto transcription completed: {transcribed_text}")205    #         return transcribed_text, state206 207    #     except Exception as e:208    #         error_msg = f"[转录失败: {str(e)}]"209    #         self.logger.error(f"❌ Auto transcription failed: {str(e)}")210    #         state["last_transcribed_text"] = error_msg211    #         return error_msg, state212 213    def init_state(self):214        """Initialize conversation state"""215        return {216            "history_messages": [],217            "history_audio": []218            # # ASR相关状态(已禁用)219            # "last_audio_path": None,  # 用于防重复调用220            # "last_transcribed_text": ""  # 缓存最后的转录结果221        }222 223    def update_edit_info(self, category):224        """Update sub-task dropdown based on main task selection"""225        category_items = get_supported_edit_types()226        choices = category_items.get(category, [])227        value = None if len(choices) == 0 else choices[0]228        return gr.Dropdown(label="Sub-task", choices=choices, value=value)229 230    def register_components(self):231        """Register gradio components - maintaining exact layout from original"""232        with gr.Tab("Editx"):233            with gr.Row():234                with gr.Column():235                    self.model_input = gr.Textbox(label="Model Name", value="Step-Audio-EditX", scale=1)236                    self.prompt_text_input = gr.Textbox(label="Prompt Text", value="", scale=1)237                    self.prompt_audio_input = gr.Audio(238                        sources=["upload", "microphone"],239                        format="wav",240                        type="filepath",241                        label="Input Audio",242                    )243                    self.generated_text = gr.Textbox(label="Target Text", lines=1, max_lines=200, max_length=1000)244                with gr.Column():245                    with gr.Row():246                        self.edit_type = gr.Dropdown(label="Task", choices=self.edit_type_list, value="clone")247                        self.edit_info = gr.Dropdown(label="Sub-task", choices=[], value=None)248                    self.chat_box = gr.Chatbot(label="History", type="messages", height=480*1)249            with gr.Row():250                with gr.Column():251                    with gr.Row():252                        self.button_tts = gr.Button("CLONE", variant="primary")253                        self.button_edit = gr.Button("EDIT", variant="primary")254                with gr.Column():255                    self.clean_history_submit = gr.Button("Clear History", variant="primary")256 257            gr.Markdown("---")258            gr.Markdown("""259                **Button Description:**260                - CLONE: Synthesizes audio based on uploaded audio and text, only used for clone mode, will clear history information when used.261                - EDIT: Edits based on uploaded audio, or continues to stack edit effects based on the previous round of generated audio.262                """)263            gr.Markdown("""264                **Operation Workflow:**265                - Upload the audio to be edited on the left side and fill in the corresponding text content of the audio;266                - If the task requires modifying text content (such as clone, para-linguistic), fill in the text to be synthesized in the "target text" field. For all other tasks, keep the uploaded audio text content unchanged;267                - Select tasks and subtasks on the right side (some tasks have no subtasks, such as vad, etc.);268                - Click the "CLONE" or "EDIT" button on the left side, and audio will be generated in the dialog box on the right side.269                """)270            gr.Markdown("""271                **Para-linguistic Description:**272                - Supported tags include: [Breathing] [Laughter] [Surprise-oh] [Confirmation-en] [Uhm] [Surprise-ah] [Surprise-wa] [Sigh] [Question-ei] [Dissatisfaction-hnn]273                - Example:274                    - Fill in "target text" field: "Great, the weather is so nice today." Click the "CLONE" button to get audio.275                    - Change "target text" field to: "Great[Laughter], the weather is so nice today[Surprise-ah]." Click the "EDIT" button to get para-linguistic audio.276                """)277 278    def register_events(self):279        """Register event handlers"""280        state = gr.State(self.init_state())281 282        self.button_tts.click(283            self.generate_clone,284            inputs=[self.prompt_text_input, self.prompt_audio_input, self.generated_text, self.edit_type, self.edit_info, state],285            outputs=[self.chat_box, state]286        )287        self.button_edit.click(288            self.generate_edit,289            inputs=[self.prompt_text_input, self.prompt_audio_input, self.generated_text, self.edit_type, self.edit_info, state],290            outputs=[self.chat_box, state]291        )292        self.clean_history_submit.click(self.clear_history, inputs=[state], outputs=[self.chat_box, state])293        self.edit_type.change(294            fn=self.update_edit_info,295            inputs=self.edit_type,296            outputs=self.edit_info,297        )298 299        # # 音频上传时自动转录300        # self.prompt_audio_input.change(301        #     fn=self.auto_transcribe_audio,302        #     inputs=[self.prompt_audio_input, state],303        #     outputs=[self.prompt_text_input, state]304        # )305 306 307def create_demo():308    """Create and return the Gradio demo"""309    editx_tab = EditxTab()310    311    with gr.Blocks(312        theme=gr.themes.Soft(),313        title="🎙️ Step-Audio-EditX",314        css="""315:root {316    --font: "Helvetica Neue", Helvetica, Arial, sans-serif;317    --font-mono: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace;318}319"""320    ) as demo:321        gr.Markdown("## 🎙️ Step-Audio-EditX")322        gr.Markdown("Audio Editing and Zero-Shot Cloning using Step-Audio-EditX")323        324        editx_tab.register_components()325        editx_tab.register_events()326    327    return demo328 329 330# Main entry point331if __name__ == "__main__":332    logger.info("🚀 Starting Step-Audio-EditX Demo (API Mode)")333    logger.info(f"API Token configured: {'Yes' if get_api_token() else 'No'}")334    335    demo = create_demo()336    demo.queue().launch(337        server_name="0.0.0.0",338        server_port=7860,339        share=False340    )341