jayaspjacob Claude Opus 4.8 (1M context) commited on
Commit
310ca13
·
1 Parent(s): 09c9a6a

Add playable voice samples + cap episode length at 2 min

Browse files

Voices: source permissively-licensed clips from CMU ARCTIC (fish-speech
ships none), assembled by scripts/build_voice_samples.py into ~20s clips.
The Voices page now renders a gr.Audio player per voice so samples are
audible while browsing. Three clips also back the narrator_warm /
host_energetic / guest_calm presets, so podcast hosts finally sound
distinct instead of all rendering as the default speaker.

UI: cap the Create LENGTH selector at 2 min (default 2) so generation
stays well under the ZeroGPU 300s cap.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

app.py CHANGED
@@ -11,6 +11,7 @@ flow — topic → review the script → the finished episode.
11
  from __future__ import annotations
12
 
13
  import html
 
14
  from typing import List
15
 
16
  import gradio as gr
@@ -48,6 +49,31 @@ SUGGESTIONS = [
48
  "Why do cities feel lonely?",
49
  ]
50
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
51
 
52
  # --------------------------------------------------------------------- HTML builders
53
  def _avatar(letter: str, idx: int, size: int = 30) -> str:
@@ -440,6 +466,14 @@ footer { display:none !important; }
440
  .pf-tile .vm { font-size:10.5px;letter-spacing:1px;color:var(--pf-faint);text-transform:uppercase; }
441
  .pf-wave { height:34px;border-radius:8px;margin:10px 0;
442
  background:repeating-linear-gradient(90deg,rgba(124,92,255,.5) 0 2px,transparent 2px 5px);opacity:.55; }
 
 
 
 
 
 
 
 
443
  .pf-chips { display:flex;gap:6px;flex-wrap:wrap; }
444
  .pf-chiplet { font-size:11px;color:var(--pf-mut);background:var(--pf-panel-2);
445
  border:1px solid var(--pf-line);border-radius:7px;padding:3px 9px; }
@@ -517,7 +551,7 @@ def build_ui():
517
  new_ep_btn = gr.Button("+ New episode", elem_id="pf-newep")
518
  gr.HTML("<div class='pf-section-label'>STUDIO</div>")
519
  nav_create = gr.Button("⌁ Create", elem_classes=["pf-nav", "active"])
520
- nav_voices = gr.Button("🎙 Voices 9", elem_classes=["pf-nav"])
521
  nav_library = gr.Button("▥ Library 6", elem_classes=["pf-nav"])
522
 
523
  # ---------------------------------------------------------- main
@@ -545,9 +579,8 @@ def build_ui():
545
  style = gr.Dropdown(STYLES, value="interview", label="FORMAT")
546
  with gr.Column(elem_classes=["pf-config-card"]):
547
  duration = gr.Dropdown(
548
- [("3 min", 3), ("5 min", 5), ("10 min", 10),
549
- ("15 min", 15), ("20 min", 20)],
550
- value=10, label="LENGTH",
551
  )
552
  with gr.Column(elem_classes=["pf-config-card"]):
553
  num_speakers = gr.Slider(1, MAX_SPEAKERS, value=2, step=1, label="VOICES")
@@ -653,7 +686,31 @@ def build_ui():
653
  "<div style='color:var(--pf-mut);font-size:13px;line-height:1.6'>"
654
  "Pair a warm host with a contrasting guest for natural back-and-forth. "
655
  "Add a subtle ambient bed so silences breathe.</div>")
656
- gr.HTML(_voice_library_html())
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
657
 
658
  # ===================== LIBRARY PAGE =====================
659
  with gr.Column(visible=False) as page_library:
@@ -734,36 +791,19 @@ def build_ui():
734
 
735
 
736
  # -------------------------------------------------------------- static page builders
737
- def _voice_library_html() -> str:
738
- voices = [
739
- ("Nova", "Female · American", 0, ["Warm", "Conversational", "Host"]),
740
- ("Atlas", "Male · British", 1, ["Deep", "Cinematic", "Narration"]),
741
- ("Echo", "Neutral · American", 2, ["Crisp", "News", "Authoritative"]),
742
- ("Sage", "Male · Australian", 2, ["Calm", "Thoughtful", "Smooth"]),
743
- ("Vivi", "Female · American", 3, ["Energetic", "Upbeat", "Bright"]),
744
- ("Onyx", "Male · American", 1, ["Smooth", "Late-night", "Radio"]),
745
- ("Lumen", "Female · Irish", 0, ["Bright", "Friendly", "Clear"]),
746
- ("Rune", "Male · British", 2, ["Gravelly", "Documentary", "Rich"]),
747
- ("My Voice", "You · Cloned", 3, ["Cloned", "Custom", "98% match"]),
748
- ]
749
- tiles = []
750
- for name, meta, idx, chips in voices:
751
- chiprow = "".join(f"<span class='pf-chiplet'>{html.escape(c)}</span>" for c in chips)
752
- tiles.append(
753
- "<div class='pf-tile'>"
754
- f"<div class='vh'>{_avatar(name, idx, 38)}"
755
- f"<div><div class='vn'>{html.escape(name)}</div><div class='vm'>{html.escape(meta)}</div></div></div>"
756
- "<div class='pf-wave'></div>"
757
- f"<div class='pf-chips'>{chiprow}</div></div>"
758
- )
759
  return (
760
- "<div class='pf-eyebrow-row' style='margin-top:26px'>"
761
- "<div class='pf-step-h'><span class='t'>🎙 Voice library</span>"
762
- "<span class='h'>9 voices</span></div></div>"
763
- "<div class='pf-grid'>" + "".join(tiles) + "</div>"
764
  )
765
 
766
 
 
 
 
 
 
767
  def _library_html() -> str:
768
  import random
769
  eps = [
 
11
  from __future__ import annotations
12
 
13
  import html
14
+ import os
15
  from typing import List
16
 
17
  import gradio as gr
 
49
  "Why do cities feel lonely?",
50
  ]
51
 
52
+ # Voice library shown on the Voices page. Each id maps to a playable sample clip under
53
+ # tts/voices/samples/<id>.wav (built by scripts/build_voice_samples.py from CMU ARCTIC).
54
+ # id=None is the "clone your own" slot (no bundled sample). Labels reflect the real
55
+ # underlying speaker so playback matches the description.
56
+ VOICE_LIBRARY = [
57
+ ("nova", "Nova", "Female · American", 0, ["Warm", "Conversational", "Host"]),
58
+ ("atlas", "Atlas", "Male · Scottish", 1, ["Deep", "Cinematic", "Narration"]),
59
+ ("echo", "Echo", "Male · American", 2, ["Crisp", "News", "Authoritative"]),
60
+ ("sage", "Sage", "Male · Canadian", 2, ["Calm", "Thoughtful", "Smooth"]),
61
+ ("vivi", "Vivi", "Female · American", 3, ["Energetic", "Upbeat", "Bright"]),
62
+ ("onyx", "Onyx", "Male · American", 1, ["Smooth", "Late-night", "Radio"]),
63
+ ("rune", "Rune", "Male · Indian", 2, ["Gravelly", "Documentary", "Rich"]),
64
+ (None, "My Voice", "You · Cloned", 3, ["Cloned", "Custom", "Clone to use"]),
65
+ ]
66
+
67
+ _SAMPLES_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "tts", "voices", "samples")
68
+
69
+
70
+ def _sample_path(voice_id):
71
+ """Absolute path to a voice's sample clip, or None if not bundled."""
72
+ if not voice_id:
73
+ return None
74
+ p = os.path.join(_SAMPLES_DIR, f"{voice_id}.wav")
75
+ return p if os.path.isfile(p) else None
76
+
77
 
78
  # --------------------------------------------------------------------- HTML builders
79
  def _avatar(letter: str, idx: int, size: int = 30) -> str:
 
466
  .pf-tile .vm { font-size:10.5px;letter-spacing:1px;color:var(--pf-faint);text-transform:uppercase; }
467
  .pf-wave { height:34px;border-radius:8px;margin:10px 0;
468
  background:repeating-linear-gradient(90deg,rgba(124,92,255,.5) 0 2px,transparent 2px 5px);opacity:.55; }
469
+ /* compact, on-theme audio player inside a voice tile */
470
+ .pf-voice-audio { margin:10px 0 !important; }
471
+ .pf-voice-audio .audio-container, .pf-voice-audio .component-wrapper { background:transparent !important; }
472
+ .pf-voice-audio audio { width:100% !important; height:36px !important; }
473
+ .pf-voice-audio button { color:var(--pf-text) !important; }
474
+ .pf-clone-slot { height:36px;margin:10px 0;border:1px dashed var(--pf-line);border-radius:8px;
475
+ display:flex;align-items:center;justify-content:center;color:var(--pf-faint);font-size:11.5px;
476
+ text-align:center;padding:0 8px; }
477
  .pf-chips { display:flex;gap:6px;flex-wrap:wrap; }
478
  .pf-chiplet { font-size:11px;color:var(--pf-mut);background:var(--pf-panel-2);
479
  border:1px solid var(--pf-line);border-radius:7px;padding:3px 9px; }
 
551
  new_ep_btn = gr.Button("+ New episode", elem_id="pf-newep")
552
  gr.HTML("<div class='pf-section-label'>STUDIO</div>")
553
  nav_create = gr.Button("⌁ Create", elem_classes=["pf-nav", "active"])
554
+ nav_voices = gr.Button("🎙 Voices 8", elem_classes=["pf-nav"])
555
  nav_library = gr.Button("▥ Library 6", elem_classes=["pf-nav"])
556
 
557
  # ---------------------------------------------------------- main
 
579
  style = gr.Dropdown(STYLES, value="interview", label="FORMAT")
580
  with gr.Column(elem_classes=["pf-config-card"]):
581
  duration = gr.Dropdown(
582
+ [("1 min", 1), ("2 min", 2)],
583
+ value=2, label="LENGTH",
 
584
  )
585
  with gr.Column(elem_classes=["pf-config-card"]):
586
  num_speakers = gr.Slider(1, MAX_SPEAKERS, value=2, step=1, label="VOICES")
 
686
  "<div style='color:var(--pf-mut);font-size:13px;line-height:1.6'>"
687
  "Pair a warm host with a contrasting guest for natural back-and-forth. "
688
  "Add a subtle ambient bed so silences breathe.</div>")
689
+ gr.HTML(
690
+ "<div class='pf-eyebrow-row' style='margin-top:26px'>"
691
+ "<div class='pf-step-h'><span class='t'>🎙 Voice library</span>"
692
+ f"<span class='h'>{len(VOICE_LIBRARY)} voices</span></div></div>"
693
+ )
694
+ for _r in range(0, len(VOICE_LIBRARY), 4):
695
+ with gr.Row(equal_height=True):
696
+ for vid, vname, vmeta, vidx, vchips in VOICE_LIBRARY[_r:_r + 4]:
697
+ with gr.Column(elem_classes=["pf-tile"], min_width=210):
698
+ gr.HTML(_voice_card_header(vname, vmeta, vidx))
699
+ _sp = _sample_path(vid)
700
+ if _sp:
701
+ gr.Audio(
702
+ value=_sp,
703
+ show_label=False,
704
+ interactive=False,
705
+ container=False,
706
+ elem_classes=["pf-voice-audio"],
707
+ )
708
+ else:
709
+ gr.HTML(
710
+ "<div class='pf-clone-slot'>Clone a voice above "
711
+ "to preview it here</div>"
712
+ )
713
+ gr.HTML(_voice_card_chips(vchips))
714
 
715
  # ===================== LIBRARY PAGE =====================
716
  with gr.Column(visible=False) as page_library:
 
791
 
792
 
793
  # -------------------------------------------------------------- static page builders
794
+ def _voice_card_header(name: str, meta: str, idx: int) -> str:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
795
  return (
796
+ f"<div class='vh'>{_avatar(name, idx, 38)}"
797
+ f"<div><div class='vn'>{html.escape(name)}</div>"
798
+ f"<div class='vm'>{html.escape(meta)}</div></div></div>"
 
799
  )
800
 
801
 
802
+ def _voice_card_chips(chips: list) -> str:
803
+ row = "".join(f"<span class='pf-chiplet'>{html.escape(c)}</span>" for c in chips)
804
+ return f"<div class='pf-chips'>{row}</div>"
805
+
806
+
807
  def _library_html() -> str:
808
  import random
809
  eps = [
scripts/build_voice_samples.py ADDED
@@ -0,0 +1,105 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Build voice sample / reference clips from CMU ARCTIC.
2
+
3
+ fish-speech / OpenAudio ship no reference audio, so we source short, permissively
4
+ licensed clips from CMU ARCTIC (festvox.org — "distributed with no restrictions for
5
+ academic, research and commercial use"). Each voice clip concatenates a few ARCTIC
6
+ sentences (~20s) so it doubles as a clean cloning reference for OpenAudio S1-mini.
7
+
8
+ Outputs:
9
+ tts/voices/samples/<voice>.wav + .txt — played on the Voices page
10
+ tts/voices/<preset>.wav + .txt — picked up by tts/voices.py PRESETS so the
11
+ podcast hosts actually sound distinct
12
+
13
+ Run from the repo root: python scripts/build_voice_samples.py
14
+ WAVs are tracked via Git LFS (see .gitattributes).
15
+ """
16
+
17
+ from __future__ import annotations
18
+
19
+ import io
20
+ import os
21
+ import shutil
22
+ import urllib.request
23
+
24
+ import numpy as np
25
+ import soundfile as sf
26
+
27
+ BASE = "http://festvox.org/cmu_arctic/cmu_arctic"
28
+ UTTS = [f"arctic_a{n:04d}" for n in range(1, 7)] # 6 sentences ≈ 20s
29
+ GAP_S = 0.15
30
+
31
+ # Voices page id -> CMU ARCTIC speaker (distinct timbre/accent each).
32
+ VOICES = {
33
+ "nova": "slt", # female, US
34
+ "atlas": "awb", # male, Scottish
35
+ "echo": "bdl", # male, US
36
+ "sage": "jmk", # male, Canadian
37
+ "vivi": "clb", # female, US
38
+ "onyx": "rms", # male, US
39
+ "rune": "ksp", # male, Indian
40
+ }
41
+
42
+ # Functional presets in tts/voices.py -> reuse a display clip as the cloning reference.
43
+ PRESETS = {
44
+ "narrator_warm": "nova",
45
+ "host_energetic": "echo",
46
+ "guest_calm": "vivi",
47
+ }
48
+
49
+ SAMPLES_DIR = os.path.join("tts", "voices", "samples")
50
+ PRESETS_DIR = os.path.join("tts", "voices")
51
+
52
+
53
+ def _fetch(url: str) -> bytes:
54
+ with urllib.request.urlopen(url, timeout=60) as r:
55
+ return r.read()
56
+
57
+
58
+ def _transcripts(spk: str) -> dict:
59
+ raw = _fetch(f"{BASE}/cmu_us_{spk}_arctic/etc/txt.done.data").decode("utf-8", "ignore")
60
+ out = {}
61
+ for line in raw.splitlines():
62
+ line = line.strip()
63
+ if not line.startswith("(") or '"' not in line:
64
+ continue
65
+ try:
66
+ uid = line.split()[1]
67
+ out[uid] = line[line.index('"') + 1 : line.rindex('"')]
68
+ except Exception:
69
+ continue
70
+ return out
71
+
72
+
73
+ def build():
74
+ os.makedirs(SAMPLES_DIR, exist_ok=True)
75
+ for vid, spk in VOICES.items():
76
+ txts = _transcripts(spk)
77
+ chunks, texts, sr = [], [], 16000
78
+ for uid in UTTS:
79
+ audio, sr = sf.read(io.BytesIO(_fetch(f"{BASE}/cmu_us_{spk}_arctic/wav/{uid}.wav")),
80
+ dtype="float32")
81
+ if audio.ndim > 1:
82
+ audio = audio.mean(axis=1)
83
+ chunks.append(audio)
84
+ chunks.append(np.zeros(int(sr * GAP_S), dtype="float32"))
85
+ if uid in txts:
86
+ texts.append(txts[uid])
87
+ full = np.concatenate(chunks)
88
+ peak = float(np.abs(full).max()) or 1.0
89
+ full = (full / peak * 0.95).astype("float32")
90
+ sf.write(os.path.join(SAMPLES_DIR, f"{vid}.wav"), full, sr)
91
+ with open(os.path.join(SAMPLES_DIR, f"{vid}.txt"), "w", encoding="utf-8") as f:
92
+ f.write(" ".join(texts))
93
+ print(f" sample {vid:6s} <- {spk} {len(full)/sr:4.1f}s")
94
+
95
+ for pid, src in PRESETS.items():
96
+ shutil.copyfile(os.path.join(SAMPLES_DIR, f"{src}.wav"),
97
+ os.path.join(PRESETS_DIR, f"{pid}.wav"))
98
+ shutil.copyfile(os.path.join(SAMPLES_DIR, f"{src}.txt"),
99
+ os.path.join(PRESETS_DIR, f"{pid}.txt"))
100
+ print(f" preset {pid:15s} <- {src}")
101
+
102
+
103
+ if __name__ == "__main__":
104
+ build()
105
+ print("done.")
tts/voices/guest_calm.txt ADDED
@@ -0,0 +1 @@
 
 
1
+ Author of the danger trail, Philip Steels, etc. Not at this particular case, Tom, apologized Whittemore. For the twentieth time that evening the two men shook hands. Lord, but I'm glad to see you again, Phil. Will we ever forget it. God bless 'em, I hope I'll go on seeing them forever.
tts/voices/guest_calm.wav ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:664d85b1252b1f7fca1a5f4b3f2698c7cad633345dd79183bd4c0e13286c7574
3
+ size 652524
tts/voices/host_energetic.txt ADDED
@@ -0,0 +1 @@
 
 
1
+ Author of the danger trail, Philip Steels, etc. Not at this particular case, Tom, apologized Whittemore. For the twentieth time that evening the two men shook hands. Lord, but I'm glad to see you again, Phil. Will we ever forget it. God bless 'em, I hope I'll go on seeing them forever.
tts/voices/host_energetic.wav ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:e3758261d6daa43fc50189fea7e589a2c6cd051e68f02f6e7ee9b7fb1fd21d49
3
+ size 569656
tts/voices/narrator_warm.txt ADDED
@@ -0,0 +1 @@
 
 
1
+ Author of the danger trail, Philip Steels, etc. Not at this particular case, Tom, apologized Whittemore. For the twentieth time that evening the two men shook hands. Lord, but I'm glad to see you again, Phil. Will we ever forget it. God bless 'em, I hope I'll go on seeing them forever.
tts/voices/narrator_warm.wav ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:ebd2694a9908bac8977e0fa530212351d0dca3bb7ed145cbfa3615f868e5f090
3
+ size 581492
tts/voices/samples/atlas.txt ADDED
@@ -0,0 +1 @@
 
 
1
+ AUTHOR OF THE DANGER TRAIL, PHILIP STEELS, ETC Not at this particular case, Tom, apologized Whittemore. For the twentieth time that evening the two men shook hands. Lord, but I'm glad to see you again, Phil. Will we ever forget it. God bless 'em, I hope I'll go on seeing them forever.
tts/voices/samples/atlas.wav ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:772c2026d0a19eda162dc5a91e920f527414cb09b4da947ebe888834f195a760
3
+ size 828844
tts/voices/samples/echo.txt ADDED
@@ -0,0 +1 @@
 
 
1
+ Author of the danger trail, Philip Steels, etc. Not at this particular case, Tom, apologized Whittemore. For the twentieth time that evening the two men shook hands. Lord, but I'm glad to see you again, Phil. Will we ever forget it. God bless 'em, I hope I'll go on seeing them forever.
tts/voices/samples/echo.wav ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:e3758261d6daa43fc50189fea7e589a2c6cd051e68f02f6e7ee9b7fb1fd21d49
3
+ size 569656
tts/voices/samples/nova.txt ADDED
@@ -0,0 +1 @@
 
 
1
+ Author of the danger trail, Philip Steels, etc. Not at this particular case, Tom, apologized Whittemore. For the twentieth time that evening the two men shook hands. Lord, but I'm glad to see you again, Phil. Will we ever forget it. God bless 'em, I hope I'll go on seeing them forever.
tts/voices/samples/nova.wav ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:ebd2694a9908bac8977e0fa530212351d0dca3bb7ed145cbfa3615f868e5f090
3
+ size 581492
tts/voices/samples/onyx.txt ADDED
@@ -0,0 +1 @@
 
 
1
+ Author of the danger trail, Philip Steels, etc. Not at this particular case, Tom, apologized Whittemore. For the twentieth time that evening the two men shook hands. Lord, but I'm glad to see you again, Phil. Will we ever forget it. God bless 'em, I hope I'll go on seeing them forever.
tts/voices/samples/onyx.wav ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:1c52525fb406128bf0c9b477a7925269d42db85677deb37c0f4ffcc50ff168ed
3
+ size 548204
tts/voices/samples/rune.txt ADDED
@@ -0,0 +1 @@
 
 
1
+ Author of the danger trail, Philip Steels, etc. Not at this particular case, Tom, apologized Whittemore. For the twentieth time that evening the two men shook hands. Lord, but I'm glad to see you again, Phil. Will we ever forget it. God bless 'em, I hope I'll go on seeing them forever.
tts/voices/samples/rune.wav ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:aebd0368b53d20922f4de914a2a3ce234a1979ec0f8bb3394be5a692e3831661
3
+ size 638452
tts/voices/samples/sage.txt ADDED
@@ -0,0 +1 @@
 
 
1
+ Author of the danger trail, Philip Steels, etc. Not at this particular case, Tom, apologized Whittemore. For the twentieth time that evening the two men shook hands. Lord, but I'm glad to see you again, Phil. Will we ever forget it. God bless 'em, I hope I'll go on seeing them forever.
tts/voices/samples/sage.wav ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:559b8b8182c874d590e4d351e88d50e02c35912aac51d9195b465ddc95529039
3
+ size 660852
tts/voices/samples/vivi.txt ADDED
@@ -0,0 +1 @@
 
 
1
+ Author of the danger trail, Philip Steels, etc. Not at this particular case, Tom, apologized Whittemore. For the twentieth time that evening the two men shook hands. Lord, but I'm glad to see you again, Phil. Will we ever forget it. God bless 'em, I hope I'll go on seeing them forever.
tts/voices/samples/vivi.wav ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:664d85b1252b1f7fca1a5f4b3f2698c7cad633345dd79183bd4c0e13286c7574
3
+ size 652524