๐Ÿ—ฃ ์›น ์Œ์„ฑ API๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ์›น ๋ฆฌ๋”

16177 ๋‹จ์–ด showdevwebdevtexttospeechjavascript
์ด ํ”„๋ ˆ์  ํ…Œ์ด์…˜: https://stupefied-curran-2254b8.netlify.com/
๋‹น์‹ ์€ TLDR(๋„ˆ๋ฌด ๊ฒŒ์œผ๋ฅด๊ณ  ์ฝ์ง€ ์•Š์Œ)์— ์˜ํ•ด ์˜จ๋ผ์ธ ๊ธ€์ด๋‚˜ ์œ ์‚ฌํ•œ ์›น ํŽ˜์ด์ง€๋ฅผ ์ฝ์€ ์ ์ด ์žˆ์Šต๋‹ˆ๊นŒ?
๋‹น์‹ ์˜ ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ๋‹น์‹ ์„ ๋„์™€ ์ฝ์„ ์ˆ˜ ์žˆ๊ธฐ๋ฅผ ๋ฐ”๋ž๋‹ˆ๋‹ค.
์ข‹์•„, ๋„ˆ๋Š” ์ •๋ง ์šด์ด ์ข‹๋‹ค!๋‚˜๋Š” ์›น ๋ฆฌ๋” ํ•˜๋‚˜๋ฅผ ์„ธ์› ๋‹ค.๐Ÿ˜†
์ž…๋ ฅ์— URL์ด๋‚˜ ํ…์ŠคํŠธ๋ฅผ ๋ณต์‚ฌํ•ด์„œ ๋ถ™์—ฌ๋„ฃ์œผ๋ฉด ์ฝ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค!
์ตœ์†Œ ์ฝ๊ธฐ ๊ฐ€๋Šฅ ์„น์…˜๐Ÿ˜…

๐Ÿ’ฌ ์›น ์Œ์„ฑ API


๋‚˜๋Š” ๋ณธ ๋ธŒ๋ผ์šฐ์ €์—์„œ ์˜จ ์›น ์Œ์„ฑ API์˜ ์Œ์„ฑ ํ•ฉ์„ฑ์„ ์‚ฌ์šฉํ–ˆ๋‹ค.
์ด๊ฒƒ์€ ์‹คํ—˜์ ์ธ ๊ธฐ์ˆ ์ด์ง€๋งŒ, ์ง€๊ธˆ ๋‹น์‹ ์˜ ๋ธŒ๋ผ์šฐ์ €์— ๊ทธ๊ฒƒ์ด ์žˆ์„ ๊ฐ€๋Šฅ์„ฑ์ด ๋งค์šฐ ๋†’๋‹ค.
์‚ฌ์‹ค Chrome 33, Firefox 49, Edge 14 ์ดํ›„ ์šฐ๋ฆฌ๋Š” ๋ชจ๋‘ ์ด ๊ธฐ๋Šฅ์„ ๊ฐ€์ง€๊ณ  ์žˆ๋‹ค.ํƒ€๋งˆ๊ณ ์น˜๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ์•Š๋„๋ก ์—ฌ๊ธฐ๋ฅผ ํ™•์ธํ•˜์‹ญ์‹œ์˜ค๐Ÿฐ: caniuse Web Speech API .

์Œ์„ฑ ์ž…๋ ฅ


์‚ฌ์šฉ์ž ์ž…๋ ฅ์—๋Š” ๋‹ค์Œ HTML ์š”์†Œ๊ฐ€ ํฌํ•จ๋ฉ๋‹ˆ๋‹ค.
  • textarea, URL/text ์ฝ๊ธฐ
  • select ์Œ์„ฑ ์ž…๋ ฅ
  • range ํ”ผ์น˜ ๋ฐ ์†๋„ ์ž…๋ ฅ
  • textarea ๋‚ด์šฉ์ด ์ผ๋ฐ˜ ํ…์ŠคํŠธ์ธ์ง€ URL์ธ์ง€ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค.
    ์†๋„(๋งํ•˜๋Š” ์†๋„)๋Š” 0.5์—์„œ 2 ์‚ฌ์ด์ด๋‹ค.
    ์Œ๋†’์ด(์†Œ๋ฆฌ์˜ ๋†’๋‚ฎ์ด) ๋ฒ”์œ„๋Š” 0์—์„œ 2๊นŒ์ง€์ž…๋‹ˆ๋‹ค.
    ์Œ์„ฑ ์„ ํƒ์€ ์‹œ์Šคํ…œ์—์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ์Œ์„ฑ์„ ์ œ๊ณตํ•œ๋‹ค.

    ๐ŸŽค ๊ฐ•์—ฐ ์ข…ํ•ฉ ์Œ์„ฑ


    ๊ฐ ์žฅ์น˜์˜ ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ์Œ์„ฑ์€ ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.speechSynthesisInstance.getVoices() .
    ์ด๊ฒƒ์€ ๋ชจ๋“  SpeechSynthesisVoice๊ฐœ์˜ ๋Œ€์ƒ์„ ๋˜๋Œ๋ ค์ค๋‹ˆ๋‹ค. ์šฐ๋ฆฌ๋Š” ๊ทธ๊ฒƒ์„ ์„ ํƒ ์˜ต์…˜์— ์ฑ„์›๋‹ˆ๋‹ค.

    ์‚ฌ์šฉ์ž๊ฐ€ ๋‘˜ ์ค‘ ํ•˜๋‚˜๋ฅผ ์„ ํƒํ•˜๊ฑฐ๋‚˜ ๊ธฐ๋ณธ๊ฐ’์„ ์œ ์ง€ํ•ฉ๋‹ˆ๋‹ค.
    ํ˜„์žฌ ๋ธŒ๋ผ์šฐ์ €๋ฅผ ์ง„์ •์œผ๋กœ ๋งํ•˜๋Š” ์‚ฌ๋žŒ์€ SpeechSynthesisUtterance ๋Œ€์ƒ์ด๋‹ค.

    ๐Ÿ—ฃ ๊ฐ•์—ฐ ์ข…ํ•ฉ

    SpeechSynthesisUtterance ๋Œ€์ƒ(utterance)์€ ํ•˜๋‚˜์˜ ๋‹จ๋… ์Œ์„ฑ ์š”์ฒญ๊ณผ ๊ฐ™๋‹ค. ์šฐ๋ฆฌ๋Š” ๋ฌธ์ž์—ด๋กœ ๊ทธ๊ฒƒ์„ ์ดˆ๊ธฐํ™”ํ•˜๊ณ  ์Œ์„ฑ, ์†๋„, ์Œ์กฐ ๋“ฑ ๋ชจ๋“  ์Œ์„ฑ ์š”์†Œ๋ฅผ ์ถ”๊ฐ€ํ•œ๋‹ค.
    ๋งˆ์ง€๋ง‰์œผ๋กœ speechSynthesis.speak()์„ ํ†ตํ•ด ์Œ์„ฑ์„ ํ„ฐ์น˜ํ•ฉ๋‹ˆ๋‹ค.
    ๋˜ํ•œ finishUtteranceCallback์„ ์ œ๊ณตํ•˜์—ฌ ํ…์ŠคํŠธ๊ฐ€ ์™„์„ฑ๋  ๋•Œ ์žฌ์ƒ ๋‹จ์ถ”์™€ ๊ธฐํƒ€ ์ปจํŠธ๋กค์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.
    ์ด ๋…ผ๋ฆฌ๋Š” speak(string, voice, pitch, rate, finishUtteranceCallback)์— ๋ด‰์ธ๋˜์–ด ์žˆ๋‹ค
      speak(string, voice, pitch, rate, finishUtteranceCallback) {
        if (this.synth.speaking) {
          console.error('๐Ÿ—ฃ already speaking');
          return;
        }
    
        if (string) {
          const utterance = new SpeechSynthesisUtterance(string);
          utterance.onend = () => {
            console.log('utterance end');
    
            finishUtteranceCallback();
          };
          utterance.voice = voice;
          utterance.pitch = pitch;
          utterance.rate = rate;
    
          this.synth.speak(utterance);
        }
      }
    
    ์ด ๋ชจ๋“  ๊ธฐ๋Šฅ์€ ๋ชจ๋“ˆํ™”๋ฅผ ์œ ์ง€ํ•˜๊ธฐ ์œ„ํ•ด WebSpeechApi์— ๋ด‰์ธ๋˜์–ด ์žˆ๋‹ค.๐Ÿ“ฆ
    ์Œ์„ฑ์— ๋Œ€ํ•œ ์ž์„ธํ•œ ๋‚ด์šฉ์€ MDN Speech Utterance์„ ์ฐธ์กฐํ•˜์‹ญ์‹œ์˜ค.
    ์ด MDN page์€ ์•„์ฃผ ์ข‹์€ ์˜ˆ๊ฐ€ ํ•˜๋‚˜ ์žˆ๋Š”๋ฐ, ๋‚˜๋Š” ๊ทธ๊ฒƒ์œผ๋กœ ๋‚˜์˜ ์‘์šฉ ํ”„๋กœ๊ทธ๋žจ์„ ๊ตฌ์ถ•ํ–ˆ๋‹ค.๊ฒ€์‚ฌ๋„ ํ•ด์ฃผ์„ธ์š”!

    ๐ŸŒ URL ํ™•์ธ


    ์‚ฌ์šฉ์ž๋Š” textarea์— URL์ด๋‚˜ ํ…์ŠคํŠธ๋ฅผ ์ž…๋ ฅํ•˜์—ฌ ์ฝ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
    ๊ทธ๋Ÿฌ๋‚˜ URL์ธ์ง€ ํ™•์ธํ•˜๋Š” ๋ฐฉ๋ฒ•์€ ๋ฌด์—‡์ž…๋‹ˆ๊นŒ?
    ๊ฐ„๋‹จํ•œ try-catch ํ•˜๋‚˜๋กœ ์ด ์ ์„ ํ•  ์ˆ˜ ์žˆ๋‹ค.
    // simple check if valid URL
    try {
        new URL(urlOrText);
        isUrl = true;
    } catch (error) {
        // not a URL, treat as string
        isUrl = false;
    }
    
    ์ผ๋ฐ˜ ํ…์ŠคํŠธ์˜ ๊ฒฝ์šฐ speak()์œผ๋กœ ์ง์ ‘ ์ „๋‹ฌ๋ฉ๋‹ˆ๋‹ค.
    URL์ด ํ™•์‹คํ•˜๋ฉด ํŽ˜์ด์ง€๋ฅผ ๋ถˆ๋Ÿฌ์˜ค๊ณ  ์ฝ์„ ์ˆ˜ ์žˆ๋Š” ์š”์†Œ๋ฅผ ์Šคํฌ๋žฉํ•˜๊ธฐ ์œ„ํ•œ ์š”์ฒญ์„ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค.

    ๐Ÿ•ท๏ธ cheerio์™€axios๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์›น ์บก์ฒ˜

    cheerio์€ jQuery์˜ ์„œ๋ธŒ์ง‘ํ•ฉ์œผ๋กœ HTML์„ ํ•ด์„ํ•˜๋Š” ๋ฐ ์žˆ์–ด์„œ ๋งค์šฐ ๋น ๋ฅด๊ณ  ๊ฐ„๋‹จํ•˜๋ฉฐ ์œ ์—ฐํ•˜๋‹ค.
    (์†”์งํžˆ cheerio.load(<p>some html</p>)์ฒ˜๋Ÿผ ๊ฐ„๋‹จํ•˜๋‹ค)axios์€ API์—์„œ ๋‚ด์šฉ์„ ๊ฐ€์ ธ์˜ค๋Š” ๋ฐ ์‚ฌ์šฉ๋˜๋Š” ์•ฝ์† ๊ธฐ๋ฐ˜ ํด๋ผ์ด์–ธํŠธ๋กœ ์ด ์˜ˆ์—์„œ ์›น ํŽ˜์ด์ง€์—์„œ ์™„์ „ํ•œ HTTP get ์‘๋‹ต์„ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค.
    ํ•œ ๋งˆ๋””๋กœ ํ•˜๋ฉด, ์ด๊ฒƒ์ด ๋ฐ”๋กœ ๋‚ด๊ฐ€ ํŽ˜์ด์ง€์˜ ๋ชจ๋“  ์ฝ์„ ์ˆ˜ ์žˆ๋Š” ์š”์†Œ๋ฅผ ์–ป๋Š” ๋ฐฉ์‹์ด๋‹ค.
    const getWebsiteTexts = siteUrl => new Promise((resolve, reject) => {
      axios
        .get(siteUrl)
        .then((result) => {
          const $ = cheerio.load(result.data);
          const contents = $('p, h1, h2, h3').contents(); // get all "readable" element contents
    
          const texts = contents
            .toArray()
            .map(p => p.data && p.data.trim())
            .filter(p => p);
    
          resolve(texts);
        })
        .catch((err) => {
          // handle err
          const errorObj = err.toJSON();
          alert(`${errorObj.message} on ${errorObj.config.url}\nPlease try a different website`);
          urlOrTextInput.value = '';
          finishUtterance();
        });
    });
    
    ์ผ๋ถ€ URL์— ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ๊ธฐ ๋•Œ๋ฌธ์— alert() ์‚ฌ์šฉ์ž๋ฅผ ํฌ์ฐฉํ•˜์—ฌ ํ…์ŠคํŠธ ์˜์—ญ์„ ์ง€์šฐ๊ณ  ํผ ์ž…๋ ฅ์„ ์žฌ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค.
    ์™œ ์ผ๋ถ€ URL์ด ์ž‘๋™ํ•˜์ง€ ์•Š์Šต๋‹ˆ๊นŒ?

    โ›” CORS ์ •์ฑ…


    ์Šคํฌ๋ ˆ์ดํผ๋Š” ๋ชจ๋“  ์‚ฌ์ดํŠธ๋ฅผ ๋ถ„์„ํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.
    ์‚ฌ์‹ค ๋งŽ์€ ์‚ฌ์ดํŠธ(๋งค์ฒด ๊ธฐ์‚ฌ ์‹œ๋„)์—CORS ์ •์ฑ…์ด ์žˆ๋‹ค.
    ๊ทธ๋ž˜์„œ ์ผ๋ถ€ ์‚ฌ์ดํŠธ์—์„œ ์ด๋Ÿฐ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ๋‹ค.
    CORS policy: No 'Access-Control-Allow-Origin'์€ ๊ฐ™์€ ์ถœ์ฒ˜์—์„œ๋งŒ ์›น ์•ฑ ์Šคํฌ๋ฆฝํŠธ์—์„œ ์š”์ฒญ์„ ๋ฐ›์„ ์ˆ˜ ์žˆ์Œ์„ ๋‚˜ํƒ€๋ƒ…๋‹ˆ๋‹ค.
  • ์€ cURL๊ณผPostman์€ ์—ฌ์ „ํžˆ ์ด ์‚ฌ์ดํŠธ์—์„œ ์ผํ•  ์ˆ˜ ์žˆ์œผ๋‚˜, ์ด๋ ‡๊ฒŒ Javascript๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.
  • ์ด๊ฒƒ์€ ์šฐ๋ฆฌ๊ฐ€ ์ฝ์œผ๋ ค๊ณ  ํ•˜๋Š” ์‚ฌ์ดํŠธ์˜ ์„œ๋ฒ„์—์„œ ์‚ฌ์šฉ๋˜๊ธฐ ๋•Œ๋ฌธ์— ์šฐ๋ฆฌ๋Š” ๋‹ค๋ฅธ ํŽ˜์ด์ง€๋กœ ๋Œ์•„๊ฐ€๋Š” ๊ฒƒ ์™ธ์—๋Š” ์•„๋ฌด๊ฒƒ๋„ ํ•  ์ˆ˜ ์—†๋‹ค.๐Ÿ˜ข
    ๋‹ค์Œ์€ ์ข‹์€ CORS ๊ฐœ์š”์ž…๋‹ˆ๋‹ค.


    dev.to pages work though! Try it out ๐ŸŽ‰.
    Thank you dev.to for allowing us to scrape ๐Ÿ™


    โ–ถ๏ธ ์žฌ์ƒ, ์ผ์‹œ์ •์ง€, ์žฌ์‹œ์ž‘


    ๋งˆ์ง€๋ง‰์œผ๋กœ ๋‚˜๋Š” ๊ธฐ๋ณธ์ ์ธ ์žฌ์ƒ ์ œ์–ด๋ฅผ ์ถ”๊ฐ€ํ–ˆ๋‹ค.

    ๋‹ค์Œ์€ paused์˜ ํ˜„์žฌ speechSyntesis ์ƒํƒœ์— ๋”ฐ๋ผ ์‹œ์ž‘ํ•˜๊ฑฐ๋‚˜ ๋ณต๊ตฌ๋œ ์žฌ์ƒ ๊ธฐ๋Šฅ์ž…๋‹ˆ๋‹ค.์ผ์‹œ ์ค‘์ง€ ๋ฐ ์ค‘์ง€๋ฅผ ์ œ์™ธํ•œ ๋‹ค๋ฅธ ์ปจํŠธ๋กค์€ disabled์— ๋ถˆ๊ณผํ•ฉ๋‹ˆ๋‹ค.
    playButton.addEventListener('click', () => {
      if (speechApi.synth.paused) {
        speechApi.synth.resume();
      } else {
        // start from beginning
        read();
      }
    
      playButton.disabled = true;
      pauseButton.disabled = false;
      stopButton.disabled = false;
    
      rateSlider.disabled = true;
      pitchSlider.disabled = true;
      voiceSelect.disabled = true;
    
      urlOrTextInput.disabled = true;
    });
    
    ์ผ์‹œ ์ •์ง€๋Š” ๋งŽ๋“  ์ ๋“  ๋น„์Šทํ•˜์ง€๋งŒ ๋‹ค๋ฅธ ์ปจํŠธ๋กค์„ ์‚ฌ์šฉํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

    ๐Ÿ“ฆ ๐Ÿšค ๊ตฌ์ถ• ๋ฐ ๋ฐฐํฌ

    parcel์„ ์‚ฌ์šฉํ•˜์—ฌ ์„ค์ • ์—†๋Š” ๋ฌถ์Œ์„ ์ง„ํ–‰ํ•ฉ๋‹ˆ๋‹ค. ์ด๊ฒƒ์€ ์ด๋Ÿฌํ•œ vanilla JS ํ”„๋กœ์ ํŠธ์— ์žˆ์–ด์„œ ๋งค์šฐ ๊ฐ„๋‹จํ•ฉ๋‹ˆ๋‹ค.
    ๋งˆ์ง€๋ง‰์œผ๋กœ Netlify๋Š” ์ •์  ๋ฐฐํฌ์— ํŽธ๋ฆฌํ•ฉ๋‹ˆ๋‹ค.Netlify์—์„œ Github repo๋ฅผ ์„ค์ •ํ•˜๋ฉด Parcel์—์„œ ๊ตฌ์ถ•ํ•œ dist/ ํด๋”๋งŒ ์ถ”์ถœ๋ฉ๋‹ˆ๋‹ค.
    ์™„์„ฑ!

    ๐Ÿ“ƒ ๊ฐœ๋Ÿ‰ํ•˜๋‹ค


    ์ด๊ฒƒ์€ ๋น ๋ฅธ ํ”„๋กœ์ ํŠธ์ด๊ธฐ ๋•Œ๋ฌธ์— ํ‹€๋ฆผ์—†์ด ์•ฝ๊ฐ„์˜ ๊ฐœ์„ ๊ณผ ์ˆ˜์ •์ด ํ•„์š”ํ•  ๊ฒƒ์ด๋‹ค.
    ๐Ÿ‘จโ€๐Ÿ’ป ์ฝ”๋“œ์ž…๋‹ˆ๋‹ค.์ข‹์€ ๋ฌธ์ž๋ฅผ ์Œ์„ฑ ํ”„๋กœ์ ํŠธ๋กœ ์‹œ์ž‘ํ•˜๋Š” ๋ฐ ๋„์›€์ด ๋  ์ˆ˜ ์žˆ๋Š” ์•„์ด๋””์–ด๋ฅผ ๋ถˆ๋Ÿฌ์ผ์œผํ‚ค๊ธธ ๋ฐ”๋ž๋‹ˆ๋‹ค.๐Ÿ˜

    ๋ก ๋ชฐ๋“œ / web_๋ฆฌ๋”



    ์›น ์Œ์„ฑ API๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ์›น ๋ฆฌ๋”


    ํ˜„์žฅ ํ”„๋ ˆ์  ํ…Œ์ด์…˜ https://stupefied-curran-2254b8.netlify.com/

    ๊ฐœ๋ฐœ

    npm run dev

    ๊ฑด์ถ•ํ•˜๋‹ค

    npm run buildView on GitHub
    ๋ฌด์Šจ ๊ฑด์˜, ์˜๊ฒฌ๊ณผ ๋ฌธ์ œ๊ฐ€ ์žˆ์Šต๋‹ˆ๊นŒ?
    (์˜ˆ: ๋ฌธ์ž์—ด์ด URL์ธ์ง€ ํ™•์ธํ•˜๋Š” ๋” ์ข‹์€ ๋ฐฉ๋ฒ•์ด ์žˆ์Šต๋‹ˆ๋‹ค๐Ÿ˜… )
    ๋Œ“๊ธ€๋กœ ์•Œ๋ ค์ฃผ์„ธ์š”!
    ๊ณ ๋งˆ์›Œ์š”, ์ฆ๊ฒ๊ฒŒ ๋“ฃ๊ณ  ์ฝ์–ด์š”!๐Ÿ‘‚๐Ÿ“–

    ์ข‹์€ ์›นํŽ˜์ด์ง€ ์ฆ๊ฒจ์ฐพ๊ธฐ