Merge pull request #565 from pipecat-ai/mb/add-markdown-remover
Add a new processor which removes markdown and special chars from TTS text
This commit is contained in:
@@ -9,6 +9,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
### Added
|
||||
|
||||
- Added `MarkdownRemovalProcessor`. This processor removes markdown formatting
|
||||
from a TextFrame. It's intended to be used between the LLM and TTS in order
|
||||
to remove markdown from the text the TTS speaks.
|
||||
|
||||
- Added new `RTVIUserLLMTextProcessor`. This processor will send an RTVI
|
||||
`user-llm-text` message with the user content's that was sent to the LLM.
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ classifiers = [
|
||||
]
|
||||
dependencies = [
|
||||
"aiohttp~=3.10.3",
|
||||
"Markdown~=3.7",
|
||||
"numpy~=1.26.4",
|
||||
"loguru~=0.7.2",
|
||||
"Pillow~=10.4.0",
|
||||
|
||||
0
src/pipecat/processors/text/__init__.py
Normal file
0
src/pipecat/processors/text/__init__.py
Normal file
76
src/pipecat/processors/text/markdown_remover.py
Normal file
76
src/pipecat/processors/text/markdown_remover.py
Normal file
@@ -0,0 +1,76 @@
|
||||
#
|
||||
# Copyright (c) 2024, Daily
|
||||
#
|
||||
# SPDX-License-Identifier: BSD 2-Clause License
|
||||
#
|
||||
|
||||
import re
|
||||
|
||||
from markdown import Markdown
|
||||
|
||||
from pipecat.frames.frames import Frame, TextFrame
|
||||
from pipecat.processors.frame_processor import FrameDirection, FrameProcessor
|
||||
|
||||
|
||||
class MarkdownRemovalProcessor(FrameProcessor):
|
||||
"""Removes Markdown formatting from text in TextFrames.
|
||||
|
||||
Converts Markdown to plain text while preserving the overall structure,
|
||||
including leading and trailing spaces. Handles special cases like
|
||||
asterisks and table formatting.
|
||||
"""
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
self._md = Markdown()
|
||||
|
||||
async def process_frame(self, frame: Frame, direction: FrameDirection):
|
||||
await super().process_frame(frame, direction)
|
||||
|
||||
if isinstance(frame, TextFrame):
|
||||
cleaned_text = self._remove_markdown(frame.text)
|
||||
await self.push_frame(TextFrame(text=cleaned_text))
|
||||
else:
|
||||
await self.push_frame(frame, direction)
|
||||
|
||||
def _remove_markdown(self, markdown_string: str) -> str:
|
||||
# Replace newlines with spaces to handle cases with leading newlines
|
||||
markdown_string = markdown_string.replace("\n", " ")
|
||||
|
||||
# Preserve numbered list items with a unique marker, §NUM§
|
||||
markdown_string = re.sub(r"^(\d+\.)\s", r"§NUM§\1 ", markdown_string)
|
||||
|
||||
# Preserve leading/trailing spaces with a unique marker, §
|
||||
# Critical for word-by-word streaming in bot-tts-text
|
||||
preserved_markdown = re.sub(
|
||||
r"^( +)|\s+$", lambda m: "§" * len(m.group(0)), markdown_string, flags=re.MULTILINE
|
||||
)
|
||||
|
||||
# Convert markdown to HTML
|
||||
md = Markdown()
|
||||
html = md.convert(preserved_markdown)
|
||||
|
||||
# Remove HTML tags
|
||||
text = re.sub("<[^<]+?>", "", html)
|
||||
|
||||
# Replace HTML entities
|
||||
text = text.replace(" ", " ")
|
||||
text = text.replace("<", "<")
|
||||
text = text.replace(">", ">")
|
||||
text = text.replace("&", "&")
|
||||
|
||||
# Remove leading/trailing asterisks
|
||||
# Necessary for bot-tts-text, as they appear as literal asterisks
|
||||
text = re.sub(r"^\*{1,2}|\*{1,2}$", "", text)
|
||||
|
||||
# Remove Markdown table formatting
|
||||
text = re.sub(r"\|", "", text)
|
||||
text = re.sub(r"^\s*[-:]+\s*$", "", text, flags=re.MULTILINE)
|
||||
|
||||
# Restore numbered list items
|
||||
text = text.replace("§NUM§", "")
|
||||
|
||||
# Restore leading and trailing spaces
|
||||
text = re.sub("§", " ", text)
|
||||
|
||||
return text
|
||||
Reference in New Issue
Block a user