
    .j[6                     T   % S SK r S SKrS SKrS SKJr  S SKJrJrJ	r	J
r
JrJr  S SKJr  S SKrS SKrS SKr\\\\R*                  \4   4   r\\\	\   4   rSrSrSrSr/ S	Qr\	\   \S
'   SrSr S\S\4S jr!S\SS4S jr"S\S\	\   4S jr#S\S\$4S jr%S\S\$4S jr&S\S\'4S jr(S\S\\R*                  \4   4S jr)S\R*                  S\R*                  4S jr*S\R*                  S\R*                  4S jr+S\S\R*                  4S jr,S\\\	\   4   S\	\R*                     4S jr-S \	\   S!\S\\	\      4S" jr.\" S#S$9 " S% S&5      5       r/  S3S'\R*                  S(\S)\0S*\0S+\0S\	\\R*                  \04      4S, jjr1S4S- jr2 S5S.\S/\
\   S\\\4   4S0 jjr3S1\	\   S\4S2 jr4g)6    N)	dataclass)AnyIterableListOptionalTupleUnion)urlparsei>  i     g      ?)ChineseEnglish	CantoneseArabicGermanFrenchSpanish
Portuguese
IndonesianItalianKoreanRussianThai
VietnameseJapaneseTurkishHindiMalayDutchSwedishDanishFinnishPolishCzechFilipinoPersianGreekRomanian	Hungarian
MacedonianSUPPORTED_LANGUAGESz
<asr_text>z	language languagereturnc                     U c  [        S5      e[        U 5      R                  5       nU(       d  [        S5      eUSS R                  5       USS R	                  5       -   $ )a  
Normalize language name to the canonical format used by Qwen3-ASR:
first letter uppercase, the rest lowercase (e.g., 'cHINese' -> 'Chinese').

Args:
    language (str): Input language name.

Returns:
    str: Normalized language name.

Raises:
    ValueError: If language is empty.
Nzlanguage is Nonezlanguage is empty   )
ValueErrorstrstripupperlower)r+   ss     8/mnt/workspace/git/Qwen3-ASR/qwen_asr/inference/utils.pynormalize_language_namer6   I   sZ     +,,HA,--Ra5;;=1QR5;;=((    c                 B    U [         ;  a  [        SU  S[          35      eg)z
Validate the language is supported.

Args:
    language (str): Canonical language name.

Raises:
    ValueError: If unsupported.
zUnsupported language: z. Supported: N)r*   r/   )r+   s    r5   validate_languager9   _   s-     **1(=I\H]^__ +r7   xc                 6    [        U [        5      (       a  U $ U /$ N)
isinstancelist)r:   s    r5   ensure_listr?   m   s    q$''q0aS0r7   r4   c                      [        U 5      nUR                  S;   =(       a    [        UR                  5      $ ! [         a     gf = f)N)httphttpsF)r
   schemeboolnetloc	Exception)r4   us     r5   is_urlrH   q   s?    QKxx,,?ahh? s   58 
AAc                 j    U R                  S5      (       a  gSU ;  a  SU ;  a  [        U 5      S:  a  gg)Nz
data:audioT/\   F)
startswithlen)r4   s    r5   is_probably_base64rO   y   s1    ||L!!1QCFSLr7   b64c                     SU ;   a9  U R                  5       R                  S5      (       a  U R                  SS5      S   n [        R                  " U 5      $ )N,zdata:r.   )r1   rM   splitbase64	b64decode)rP   s    r5   decode_base64_bytesrV      sE    
czciik,,W55iiQ"C  r7   c                    [        U 5      (       ap  [        R                  R                  U 5       nUR	                  5       nS S S 5        [
        R                  " W5       n[        R                  " USSS9u  pES S S 5        Ok[        U 5      (       aC  [        U 5      n[
        R                  " U5       n[        R                  " USSS9u  pES S S 5        O[        R                  " U S SS9u  pE[        R                  " W[        R                  S9n[        W5      nXE4$ ! , (       d  f       N= f! , (       d  f       NP= f! , (       d  f       Na= f)Nfloat32F)dtype	always_2d)srmonorY   )rH   urllibrequesturlopenreadioBytesIOsfrO   rV   librosaloadnpasarrayrX   int)r:   respaudio_bytesfaudior[   s         r5   load_audio_anyrn      s    ayy^^##A&$))+K 'ZZ$eDIE %$	A		)!,ZZ$eDIE %$ LLt%8	JJuBJJ/E	RB9 '&$$ %$s#   DD.2D?
D+.
D<?
Erm   c                 X   U R                   S:X  a  U $ U R                   S:X  aq  U R                  S   S::  a,  U R                  S   U R                  S   :  a  U R                  n [        R                  " U SS9R                  [        R                  5      $ [        SU R                    35      e)Nr.      r      axiszUnsupported audio ndim=)ndimshapeTrg   meanastyperX   r/   )rm   s    r5   to_monorz      s    zzQzzQ;;q>Q5;;q>EKKN#BGGEwwu2&--bjj99
.uzzl;
<<r7   c                 "   U R                  [        R                  5      n U R                  S:X  a  U $ [	        [        R
                  " [        R                  " U 5      5      5      nUS:X  a  U $ US:  a  X-  n [        R                  " U SS5      n U $ )Nr           g      ?g      )ry   rg   rX   sizefloatmaxabsclip)rm   peaks     r5   float_range_normalizer      sp    LL$EzzQu&'Ds{czGGE4%ELr7   ac                     [        U [        5      (       a  [        U 5      u  pOp[        U [        5      (       aD  [	        U 5      S:X  a5  [        U S   [
        R                  5      (       a  U S   [        U S   5      p!O[        S[        U 5       35      e[        [
        R                  " U5      5      nU[        :w  a6  [        R                  " X[        S9R                  [
        R                   5      n[#        U5      nU$ )a  
Normalize one audio input to mono 16k float32 waveform in [-1, 1].

Supported inputs:
    - str: local file path / https URL / base64 audio string
    - (np.ndarray, sr): waveform and sampling rate

Returns:
    np.ndarray:
        Mono 16k float32 waveform in [-1, 1].
rp   r   r.   zUnsupported audio input type: )orig_sr	target_sr)r=   r0   rn   tuplerN   rg   ndarrayri   	TypeErrortyperz   rh   SAMPLE_RATEre   resamplery   rX   r   )r   rm   r[   s      r5   normalize_audio_inputr      s     !S"1%	r	Au		#a&A+*QqT2::2N2NaD#ad)r8a	BCCBJJu%&E	[  kJQQRTR\R\]!%(ELr7   audiosc                 Z    [        U 5      nU Vs/ s H  n[        U5      PM     sn$ s  snf r<   )r?   r   )r   itemsr   s      r5   normalize_audiosr      s*    E.34e!!$e444s   (xs
chunk_sizec              #   l   #    US::  a  U v   g[        S[        U 5      U5       H  nXX!-    v   M     g7f)z
Yield chunks of a list.

Args:
    xs (List[Any]): Input list.
    chunk_size (int): Chunk size.

Yields:
    List[Any]: Slices of xs.
r   N)rangerN   )r   r   is      r5   
chunk_listr      s:      Q1c"gz*Q^$$ +s   24T)frozenc                   `    \ rS rSr% Sr\\S'   \\S'   \R                  \S'   \\S'   \	\S'   Sr
g	)

AudioChunk   a;  
One chunk cut from an original audio.

Attributes:
    orig_index: Index of the original sample in the input batch.
    chunk_index: Index of this chunk within the original sample.
    wav: Mono float32 waveform.
    sr: Sampling rate.
    offset_sec: Start offset of this chunk in the original audio, in seconds.

orig_indexchunk_indexwavr[   
offset_sec N)__name__
__module____qualname____firstlineno____doc__ri   __annotations__rg   r   r~   __static_attributes__r   r7   r5   r   r      s(    	 O	OGr7   r   r   r[   max_chunk_secsearch_expand_secmin_window_msc                 ~   [         R                  " U [         R                  S9n U R                  S:  a2  [         R                  " U SS9R                  [         R                  5      n [        U R                  S   5      nU[        U5      -  nXb::  a  U S4/$ [        X!-  5      n[        X1-  5      n[        S[        US-  U-  5      5      n	/ n
SnSnX[-
  U:  Ga3  X-   n[        XU-
  5      n[        X]U-   5      nX-
  U	::  a  UnOXU n[         R                  " U5      n[         R                  " U[         R                  " U	[         R                  S9S	S
9n[        [         R                  " U5      5      nUnUU	-   nUUU n[        [         R                  " U5      5      nUU-   U-   n[        [        UUS-   5      5      n[        [        UU5      5      nXU nU
R                  UU45        UUU-
  [        U5      -  -  nUnX[-
  U:  a  GM3  XU nU
R                  UU45        [        [         U-  5      n/ nU
 H}  u  nnUR                  S   U:  aQ  U[        UR                  S   5      -
  n[         R"                  " USU4SSS9R                  [         R                  5      nUR                  UU45        M     Un
U
$ )aR  
Split a long audio into chunks close to max_chunk_sec, using a low-energy boundary.

This implementation guarantees:
  - Concatenating all returned chunks reproduces the original audio exactly
    (total number of samples is identical, no overlaps, no gaps).

Args:
    wav: Mono waveform float32.
    sr: Sampling rate.
    max_chunk_sec: Target max chunk duration in seconds.
    search_expand_sec: Boundary search half-window in seconds.
    min_window_ms: Sliding window in milliseconds for energy estimation.

Returns:
    List[Tuple[np.ndarray, float]]: List of (chunk_wav, offset_sec).
r]   r.   rr   rs   r   r|      g     @@valid)modeconstant)r   constant_values)rg   rh   rX   ru   rx   ry   ri   rv   r~   r   minr   convolveonesargminappendMIN_ASR_INPUT_SECONDSpad) r   r[   r   r   r   	total_len	total_secmax_lenexpandwinchunksstartr   cutleftrightboundarysegseg_abswindow_sumsmin_poswstartwendlocalinnerchunktailmin_lenpaddedcoffr   s                                    r5   split_audio_into_chunksr      s   0 **S


+C
xx!|ggc#**2::6CIIaL!IE"I%I!c
|-$%G"'(F
amf,23
4C-/FEJ
'o5,'IV|,<3H5/CffSkG++grwws"**/MT[\K"))K01GFS=DF4(E		%()Ef}u,Hs8UQY/0s8Y/0(#uj)*x%'5944
; 
'> YD
MM4$% '",-G-/F3771:C
O+Cq1c(SIPPQSQ[Q[\Aq#h	 
 FMr7   c                 B   ^ S nSU4S jjmU nU" X15      n T" X5      n U $ )Nc                 ,   / nSn[        U 5      nX4:  ap  SnX5-   U:  a)  XU-      X   :X  a  US-  nX5-   U:  a  XU-      X   :X  a  M  XQ:  a  UR                  X   5        X5-  nOUR                  XX5-    5        X5-  nX4:  a  Mp  SR                  U5      $ )Nr   r.    )rN   r   join)r4   threshresr   ncounts         r5   fix_char_repeats4detect_and_fix_repetitions.<locals>.fix_char_repeatsP  s    FeE)a-A%iLAD$8
 )a-A%iLAD$8 ~

14 


1qw<(
 e wws|r7   c                 ~  > [        U 5      nUS-  nX4:  a  U $ Sn/ nXSU-
  ::  a  Sn[        SUS-   5       H  nXXU-  -   U:  a    OXXX-    n	Sn
[        SU5       H  nX[U-  -   nXX-    U	:w  d  M  Sn
  O   U
(       d  MI  UnXQU-  -   nX-   U::  a'  XX-    U	:X  a  US-  nX-  nX-   U::  a  XX-    U	:X  a  M  UR                  U	5        UR                  T" XS  X5      5        UnSn  O   U(       a  O"UR                  X   5        US-  nXSU-
  ::  a  M  W(       d  UR                  XS  5        SR                  U5      $ )Nrp   r   Fr.   Tr   )rN   r   r   r   )r4   r   r   r   min_repeat_charsr   resultfoundkpatternr   rep	start_idx	total_rep	end_indexfix_pattern_repeatss                  r5   r   7detect_and_fix_repetitions.<locals>.fix_pattern_repeatsa  s   F!A:H'''E1gk*6z>A%ac( F+C !!GI9;/7: %	 , 5 &I !QJI#-1,Y[1IW1T!Q	!	 $-1,Y[1IW1T MM'*MM"5a
mV"UVA E- +0 ad#Q= '''@ MM!B% wwvr7      r   )text	thresholdr   text_rawr   s       @r5   detect_and_fix_repetitionsr   O  s.    "*X HH0Dt/DKr7   rawuser_languagec                    U c  g[        U 5      R                  5       nU(       d  g[        U5      nU(       a  X4$ UnSn[        U;   nU(       a  UR	                  [        S5      u  p4OSUR                  5       4$ UR                  5       nSU;   a  UR                  5       nU(       d  gSU4$ SnUR                  5        Hz  n	U	R                  5       n	U	(       d  M  U	R                  5       n
U
R                  [        5      (       d  MH  U	[        [        5      S R                  5       nU(       a  [        U5      n  O   XR                  5       4$ )aP  
Parse Qwen3-ASR raw output into (language, text).

Cases:
  - With tag: "language Chinese<asr_text>...."
  - With newlines: "language Chinese\n...\n<asr_text>...."
  - No tag: treat whole string as text.
  - "language None<asr_text>": treat as empty audio -> ("", "")

If user_language is provided, language is forced to user_language and raw is treated as text-only
(the model is expected to output plain transcription without metadata).

Args:
    raw: Raw decoded string.
    user_language: Canonical language name if user forced language.

Returns:
    Tuple[str, str]: (language, text)
N)r   r   r   r.   zlanguage none)r0   r1   r   _ASR_TEXT_TAGrS   r3   
splitlinesrM   _LANG_PREFIXrN   r6   )r   r   r4   	meta_part	text_parthas_tag
meta_lowertlanglinelowvals               r5   parse_asr_outputr     s)   . {CA"1%AIIq G ww}a8	9 1779}"J *$OO1u D$$&zz|jjl>>,''s<()*002C.s3 ' """r7   langsc                     / nSnU  H?  nU=(       d    SR                  5       nU(       d  M%  X2:X  a  M,  UR                  U5        UnMA     SR                  U5      $ )a)  
Merge per-chunk languages into a compact comma-separated string,
keeping order and removing consecutive duplicates and empty entries.

Example:
  ["Chinese", "English", "English"] -> "Chinese,English"

Args:
    langs: List of canonical language names.

Returns:
    str: Merged language string.
Nr   rR   )r1   r   r   )r   outprevr:   s       r5   merge_languagesr    sV     CDW"OO9

1  88C=r7   )g      @g      Y@r   r<   )5rT   rb   urllib.requestr^   dataclassesr   typingr   r   r   r   r   r	   urllib.parser
   re   numpyrg   	soundfilerd   r0   r   ri   	AudioLike	MaybeListr   MAX_ASR_INPUT_SECONDSMAX_FORCE_ALIGN_INPUT_SECONDSr   r*   r   r   r   r6   r9   r?   rD   rH   rO   bytesrV   rn   rz   r   r   r   r   r   r~   r   r   r   r  r   r7   r5   <module>r     s     	  ! > > !   	"**c/	 #tCy.!	  #  " T#Y @ )c )c ),` ` `19 1c 1c d # $ !S !U !c eBJJO4 $=2:: ="** = 

 Y 2:: 45U9d9o#=> 54

CS 5
%49 %# %(492E %$ $  ,  # V	VV V 	V
 V 
%

E!
"#VrAL $(C#	C#C=C# 38_C#L49  r7   