import { useEffect, useRef, useState } from 'react'; import { AudioPlayer } from './AudioPlayer'; import { Podcast } from '../utils/types'; import { parse } from 'yaml'; import { audioBufferToMp3, isBlogMode, pickRand, uploadFileToHub, } from '../utils/utils'; import { getBlogComment } from '../utils/prompts'; import { pipelineGeneratePodcast } from '../utils/pipeline'; const SPEEDS = [ { name: 'slow AF', value: 0.8 }, { name: 'slow', value: 0.9 }, { name: 'a bit slow', value: 1.0 }, { name: 'natural', value: 1.1 }, { name: 'most natural', value: 1.2 }, { name: 'a bit fast', value: 1.3 }, { name: 'fast!', value: 1.4 }, { name: 'fast AF', value: 1.5 }, ]; const SPEAKERS = [ { name: 'πŸ‡ΊπŸ‡Έ 🚺 Heart', value: 'af_heart' }, { name: 'πŸ‡ΊπŸ‡Έ 🚺 Bella πŸ‘', value: 'af_bella' }, { name: 'πŸ‡ΊπŸ‡Έ 🚺 Nicole 🎧', value: 'af_nicole' }, { name: 'πŸ‡ΊπŸ‡Έ 🚺 Aoede', value: 'af_aoede' }, { name: 'πŸ‡ΊπŸ‡Έ 🚺 Kore', value: 'af_kore' }, { name: 'πŸ‡ΊπŸ‡Έ 🚺 Sarah πŸ‘', value: 'af_sarah' }, { name: 'πŸ‡ΊπŸ‡Έ 🚺 Nova πŸ‘', value: 'af_nova' }, { name: 'πŸ‡ΊπŸ‡Έ 🚺 Sky πŸ‘', value: 'af_sky' }, { name: 'πŸ‡ΊπŸ‡Έ 🚺 Alloy πŸ‘', value: 'af_alloy' }, { name: 'πŸ‡ΊπŸ‡Έ 🚺 Jessica πŸ‘', value: 'af_jessica' }, { name: 'πŸ‡ΊπŸ‡Έ 🚺 River πŸ‘', value: 'af_river' }, { name: 'πŸ‡ΊπŸ‡Έ 🚹 Michael', value: 'am_michael' }, { name: 'πŸ‡ΊπŸ‡Έ 🚹 Fenrir', value: 'am_fenrir' }, { name: 'πŸ‡ΊπŸ‡Έ 🚹 Puck πŸ‘', value: 'am_puck' }, { name: 'πŸ‡ΊπŸ‡Έ 🚹 Echo', value: 'am_echo' }, { name: 'πŸ‡ΊπŸ‡Έ 🚹 Eric πŸ‘', value: 'am_eric' }, { name: 'πŸ‡ΊπŸ‡Έ 🚹 Liam πŸ‘', value: 'am_liam' }, { name: 'πŸ‡ΊπŸ‡Έ 🚹 Onyx', value: 'am_onyx' }, { name: 'πŸ‡ΊπŸ‡Έ 🚹 Santa', value: 'am_santa' }, { name: 'πŸ‡ΊπŸ‡Έ 🚹 Adam', value: 'am_adam' }, { name: 'πŸ‡¬πŸ‡§ 🚺 Emma', value: 'bf_emma' }, { name: 'πŸ‡¬πŸ‡§ 🚺 Isabella πŸ‘', value: 'bf_isabella' }, { name: 'πŸ‡¬πŸ‡§ 🚺 Alice πŸ‘', value: 'bf_alice' }, { name: 'πŸ‡¬πŸ‡§ 🚺 Lily', value: 'bf_lily' }, { name: 'πŸ‡¬πŸ‡§ 🚹 George', value: 'bm_george' }, { name: 'πŸ‡¬πŸ‡§ 🚹 Fable πŸ‘', value: 'bm_fable' }, { name: 'πŸ‡¬πŸ‡§ 🚹 Lewis πŸ‘', value: 'bm_lewis' }, { name: 'πŸ‡¬πŸ‡§ 🚹 Daniel', value: 'bm_daniel' }, ]; const getRandomSpeakerPair = (): { s1: string; s2: string } => { const s1Gender = Math.random() > 0.5 ? '🚺' : '🚹'; const s2Gender = s1Gender === '🚺' ? '🚹' : '🚺'; const s1 = pickRand( SPEAKERS.filter((s) => s.name.includes(s1Gender) && s.name.includes('πŸ‘')) ).value; const s2 = pickRand( SPEAKERS.filter((s) => s.name.includes(s2Gender) && s.name.includes('πŸ‘')) ).value; return { s1, s2 }; }; const parseYAML = (yaml: string): Podcast => { try { return parse(yaml); } catch (e) { console.error(e); throw new Error( 'invalid YAML, please re-generate the script: ' + (e as any).message ); } }; export const PodcastGenerator = ({ genratedScript, setBusy, blogURL, busy, }: { genratedScript: string; blogURL: string; setBusy: (busy: boolean) => void; busy: boolean; }) => { const [wav, setWav] = useState(null); const [outTitle, setOutTitle] = useState(''); const [numSteps, setNumSteps] = useState(0); const [numStepsDone, setNumStepsDone] = useState(0); const [script, setScript] = useState(''); const [speaker1, setSpeaker1] = useState(''); const [speaker2, setSpeaker2] = useState(''); const [speed, setSpeed] = useState('1.2'); const [isAddIntroMusic, setIsAddIntroMusic] = useState(false); const [isAddNoise, setIsAddNoise] = useState(true); const refInput = useRef(null); const [blogFilePushToken, setBlogFilePushToken] = useState( localStorage.getItem('blogFilePushToken') || '' ); const [blogCmtOutput, setBlogCmtOutput] = useState(''); useEffect(() => { localStorage.setItem('blogFilePushToken', blogFilePushToken); }, [blogFilePushToken]); const setRandSpeaker = () => { const { s1, s2 } = getRandomSpeakerPair(); setSpeaker1(s1); setSpeaker2(s2); }; useEffect(setRandSpeaker, []); useEffect(() => { setScript(genratedScript); setTimeout(() => { // auto scroll if (refInput.current) { refInput.current.scrollTop = refInput.current.scrollHeight; } }, 10); }, [genratedScript]); const generatePodcast = async () => { setWav(null); setBusy(true); setBlogCmtOutput(''); if (isBlogMode && !blogURL) { alert('Please enter a blog slug'); setBusy(false); return; } let outputWav: AudioBuffer; try { const podcast = parseYAML(script); setOutTitle(podcast.title ?? 'Untitled podcast'); outputWav = await pipelineGeneratePodcast( { podcast, speaker1, speaker2, speed: parseFloat(speed), isAddIntroMusic, isAddNoise, }, (done: number, total: number) => { setNumStepsDone(done); setNumSteps(total); } ); setWav(outputWav! ?? null); } catch (e) { console.error(e); alert(`Error: ${(e as any).message}`); setWav(null); } setBusy(false); setNumStepsDone(0); setNumSteps(0); // maybe upload if (isBlogMode && outputWav!) { const repoId = 'ngxson/hf-blog-podcast'; const blogSlug = blogURL.split('/blog/').pop() ?? '_noname'; const filename = `${blogSlug}.mp3`; setBlogCmtOutput(`Uploading '${filename}' ...`); await uploadFileToHub( audioBufferToMp3(outputWav), filename, repoId, blogFilePushToken ); setBlogCmtOutput(getBlogComment(filename)); } }; const isGenerating = numSteps > 0; return ( <>

Step 2: Script (YAML format)

{isBlogMode && ( <> setBlogFilePushToken(e.target.value)} /> )}
setIsAddIntroMusic(e.target.checked)} disabled={isGenerating || busy} /> Add intro music (to make it feels like radio)
setIsAddNoise(e.target.checked)} disabled={isGenerating || busy} /> Add small background noise (to make it more realistic)
{isGenerating && ( )}
{wav && (

Step 3: Listen to your podcast

{isBlogMode && (
-------------------

Comment to be posted:

                  {blogCmtOutput}
                
)}
)} ); }; // copy text to clipboard export const copyStr = (textToCopy: string) => { // Navigator clipboard api needs a secure context (https) if (navigator.clipboard && window.isSecureContext) { navigator.clipboard.writeText(textToCopy); } else { // Use the 'out of viewport hidden text area' trick const textArea = document.createElement('textarea'); textArea.value = textToCopy; // Move textarea out of the viewport so it's not visible textArea.style.position = 'absolute'; textArea.style.left = '-999999px'; document.body.prepend(textArea); textArea.select(); document.execCommand('copy'); } };