Ariver
2026-06-30 abdb22f7391d4128a4f89d8b668781061aedf86d
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
package textproc
 
import (
    "regexp"
    "strings"
    "unicode"
)
 
// tagRE matches SenseVoice special tokens like <|zh|>, <|NEUTRAL|>, <|Speech|>.
var tagRE = regexp.MustCompile(`<\|[^|]*\|>`)
 
// English fillers: whole-word match, case-insensitive
var enFillerRE = regexp.MustCompile(`(?i)\b(?:um|uh|hmm|hm|eh|ah|er|erm|uhm)\b`)
 
// Chinese multi-char fillers: always safe to remove
var cnFillerMultiRE = regexp.MustCompile(`(?:嗯嗯|啊啊|呃呃|哦哦|嗯啊|嗯呢)`)
 
// Chinese single-char fillers at start of text (followed by optional punctuation)
var cnFillerStartRE = regexp.MustCompile(`^[嗯呃额哦唔][,,、\s]*`)
 
// Chinese single-char fillers between punctuation: ,嗯,→ ,
var cnFillerMidRE = regexp.MustCompile(`([,。!?、,!?\s])[嗯呃额哦唔]([,。!?、,!?\s])`)
 
// isCJK returns true for CJK ideographs (Chinese, Japanese kanji, Korean hanja)
// but NOT CJK punctuation, so we don't insert spaces around punctuation.
func isCJK(r rune) bool {
    return (r >= 0x4E00 && r <= 0x9FFF) || // CJK Unified Ideographs
        (r >= 0x3400 && r <= 0x4DBF) || // CJK Extension A
        (r >= 0x3040 && r <= 0x309F) || // Hiragana
        (r >= 0x30A0 && r <= 0x30FF) || // Katakana
        (r >= 0xF900 && r <= 0xFAFF) || // CJK Compatibility Ideographs
        (r >= 0x20000 && r <= 0x2A6DF) // CJK Extension B
}
 
func isASCIIAlNum(r rune) bool {
    return (r >= 'a' && r <= 'z') || (r >= 'A' && r <= 'Z') || (r >= '0' && r <= '9')
}
 
// removeFillers strips common speech filler words (语气词) from text.
func removeFillers(text string) string {
    // English fillers
    text = enFillerRE.ReplaceAllString(text, "")
 
    // Chinese multi-char fillers (always safe)
    text = cnFillerMultiRE.ReplaceAllString(text, "")
 
    // Chinese single-char at start
    text = cnFillerStartRE.ReplaceAllString(text, "")
 
    // Chinese single-char between punctuation (may need multiple passes)
    for i := 0; i < 3; i++ {
        newText := cnFillerMidRE.ReplaceAllString(text, "$1")
        if newText == text {
            break
        }
        text = newText
    }
 
    return strings.TrimSpace(text)
}
 
func removeSpacesBetweenCJK(text string) string {
    runes := []rune(text)
    buf := make([]rune, 0, len(runes))
 
    for i := 0; i < len(runes); i++ {
        r := runes[i]
        if unicode.IsSpace(r) {
            j := i
            for j < len(runes) && unicode.IsSpace(runes[j]) {
                j++
            }
            if len(buf) > 0 && j < len(runes) && isCJK(buf[len(buf)-1]) && isCJK(runes[j]) {
                i = j - 1
                continue
            }
        }
        buf = append(buf, r)
    }
 
    return string(buf)
}
 
// PostProcess cleans up mixed Chinese-English text from SenseVoice:
//   - Strips special tokens (<|zh|>, <|NEUTRAL|>, etc.)
//   - Removes model-added spaces between CJK characters
//   - Inserts spaces between CJK characters and ASCII letters/digits
//   - Capitalizes the first letter and letters after sentence-ending punctuation
//   - Collapses multiple spaces
func PostProcess(text string) string {
    // Strip SenseVoice special tokens
    text = tagRE.ReplaceAllString(text, "")
    text = strings.TrimSpace(text)
    if text == "" {
        return text
    }
 
    // Remove filler words
    text = removeFillers(text)
    if text == "" {
        return text
    }
 
    text = removeSpacesBetweenCJK(text)
 
    // Insert spaces at CJK ↔ ASCII alphanumeric boundaries
    runes := []rune(text)
    buf := make([]rune, 0, len(runes)+16)
    for i, r := range runes {
        if i > 0 {
            prev := runes[i-1]
            if (isCJK(prev) && isASCIIAlNum(r)) || (isASCIIAlNum(prev) && isCJK(r)) {
                buf = append(buf, ' ')
            }
        }
        buf = append(buf, r)
    }
 
    // Capitalize first letter and after sentence-ending punctuation
    capNext := true
    for i, r := range buf {
        if capNext && unicode.IsLetter(r) {
            buf[i] = unicode.ToUpper(r)
            capNext = false
        }
        if r == '。' || r == '!' || r == '?' || r == '.' || r == '!' || r == '?' {
            capNext = true
        }
    }
 
    // Collapse multiple consecutive spaces
    result := string(buf)
    for strings.Contains(result, "  ") {
        result = strings.ReplaceAll(result, "  ", " ")
    }
 
    return strings.TrimSpace(result)
}