1import torch
2import torch.nn as nn
3import numpy as np
4import librosa
5import math
6from transformers import WhisperProcessor, WhisperForConditionalGeneration
7from huggingface_hub import hf_hub_download
8
9
10# --- Define the MLP head ---
11
12class PopularityMLP(nn.Module):
13 def __init__(self):
14 super().__init__()
15 self.bottleneck = nn.Sequential(
16 nn.Linear(23040, 1024), nn.ReLU(), nn.Dropout(0.3),
17 nn.Linear(1024, 256), nn.ReLU(), nn.LayerNorm(256),
18 )
19 self.play_head = nn.Sequential(nn.Linear(256, 64), nn.ReLU(), nn.Linear(64, 1))
20 self.upvote_head = nn.Sequential(nn.Linear(256, 64), nn.ReLU(), nn.Linear(64, 1))
21
22 def forward(self, x):
23 feat = self.bottleneck(x)
24 return self.play_head(feat).squeeze(-1), self.upvote_head(feat).squeeze(-1)
25
26
27# --- Load models ---
28
29# Whisper encoder from laion/music-whisper
30processor = WhisperProcessor.from_pretrained("laion/music-whisper")
31whisper = WhisperForConditionalGeneration.from_pretrained(
32 "laion/music-whisper", torch_dtype=torch.float16
33).cuda().eval()
34encoder = whisper.get_encoder()
35
36# Popularity head from this repo
37head_path = hf_hub_download("laion/music-popularity", "popularity_head.pt")
38mlp = PopularityMLP().cuda()
39mlp.load_state_dict(torch.load(head_path, map_location="cuda")["mlp_state_dict"])
40mlp.eval()
41
42
43# --- Run inference ---
44
45audio, sr = librosa.load("song.mp3", sr=16000, mono=True)
46audio = audio[:30 * 16000] # first 30 seconds
47if len(audio) < 30 * 16000:
48 audio = np.pad(audio, (0, 30 * 16000 - len(audio)))
49
50inputs = processor(audio, sampling_rate=16000, return_tensors="pt")
51
52with torch.no_grad():
53 enc_out = encoder(inputs.input_features.cuda().half()).last_hidden_state # (1, 1500, 768)
54
55 # Segment pooling: 10 segments, mean/max/min
56 segments = enc_out.view(1, 10, 150, 768)
57 pooled = torch.cat([segments.mean(2), segments.max(2).values, segments.min(2).values], dim=2)
58 pooled = pooled.view(1, -1).float() # (1, 23040)
59
60 pred_play, pred_upvote = mlp(pooled)
61
62print(f"Estimated plays: {math.expm1(pred_play.item()):,.0f}")
63print(f"Estimated upvotes: {math.expm1(pred_upvote.item()):,.0f}")
The Whisper encoder is loaded separately from
laion/music-whisper.