hidude562 commited on
Commit
ff02e9f
·
verified ·
1 Parent(s): 33ab2a7

Upload infer_prosody.py with huggingface_hub

Browse files
Files changed (1) hide show
  1. infer_prosody.py +95 -0
infer_prosody.py ADDED
@@ -0,0 +1,95 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Inference: text → (pitch, volume) contour."""
2
+
3
+ import argparse
4
+ import json
5
+ import numpy as np
6
+ import torch
7
+
8
+ from model_prosody import ProsodyPredictor
9
+ from extract_features import VOCAB, VOCAB_SIZE, tokenize
10
+
11
+
12
+ def predict_prosody(text, model, norm_stats, device='cpu'):
13
+ """Run inference on a single text string.
14
+
15
+ Returns:
16
+ dict with f0_hz (array), rms (array), duration_s (float)
17
+ """
18
+ model.eval()
19
+ char_ids = torch.tensor([tokenize(text)], dtype=torch.long, device=device)
20
+ char_lengths = torch.tensor([char_ids.size(1)], dtype=torch.long, device=device)
21
+
22
+ with torch.no_grad():
23
+ pred_f0, pred_rms, pred_log_dur, frame_lengths = model(
24
+ char_ids, durations=None, char_lengths=char_lengths
25
+ )
26
+
27
+ T = frame_lengths[0].item()
28
+ f0_norm = pred_f0[0, :T].cpu().numpy()
29
+ rms_norm = pred_rms[0, :T].cpu().numpy()
30
+
31
+ # Denormalize
32
+ f0_log = f0_norm * norm_stats['f0_std'] + norm_stats['f0_mean']
33
+ f0_hz = np.exp(f0_log)
34
+ f0_hz = np.clip(f0_hz, 50, 600)
35
+
36
+ rms_log = rms_norm * norm_stats['rms_std'] + norm_stats['rms_mean']
37
+ rms = np.exp(rms_log)
38
+
39
+ duration_s = T * 0.1 # 100ms per frame
40
+
41
+ return {
42
+ 'f0_hz': f0_hz,
43
+ 'rms': rms,
44
+ 'duration_s': duration_s,
45
+ }
46
+
47
+
48
+ def main():
49
+ parser = argparse.ArgumentParser()
50
+ parser.add_argument('--checkpoint', required=True)
51
+ parser.add_argument('--text', type=str, default=None)
52
+ parser.add_argument('--texts_file', type=str, default=None, help='JSON file or one text per line')
53
+ args = parser.parse_args()
54
+
55
+ device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
56
+
57
+ # Load checkpoint
58
+ ckpt = torch.load(args.checkpoint, map_location=device, weights_only=False)
59
+ norm_stats = ckpt['norm_stats']
60
+ vocab_size = ckpt.get('vocab_size', VOCAB_SIZE)
61
+
62
+ model = ProsodyPredictor(vocab_size=vocab_size, d_model=128, dropout=0.0).to(device)
63
+ model.load_state_dict(ckpt['model'])
64
+ model.eval()
65
+ print(f"Loaded model from {args.checkpoint}")
66
+
67
+ texts = []
68
+ if args.text:
69
+ texts = [args.text]
70
+ elif args.texts_file:
71
+ if args.texts_file.endswith('.json'):
72
+ with open(args.texts_file) as f:
73
+ data = json.load(f)
74
+ if isinstance(data, list):
75
+ texts = data
76
+ elif isinstance(data, dict):
77
+ texts = list(data.values())
78
+ else:
79
+ with open(args.texts_file) as f:
80
+ texts = [line.strip() for line in f if line.strip()]
81
+ else:
82
+ parser.error("Provide --text or --texts_file")
83
+
84
+ for i, text in enumerate(texts):
85
+ result = predict_prosody(text, model, norm_stats, device)
86
+ f0 = result['f0_hz']
87
+ rms = result['rms']
88
+ print(f"\n[{i}] \"{text}\"")
89
+ print(f" Duration: {result['duration_s']:.1f}s ({len(f0)} frames)")
90
+ print(f" F0: mean={f0.mean():.1f} Hz, min={f0.min():.1f}, max={f0.max():.1f}")
91
+ print(f" RMS: mean={rms.mean():.4f}, min={rms.min():.4f}, max={rms.max():.4f}")
92
+
93
+
94
+ if __name__ == '__main__':
95
+ main()