5

Every Youtube video has a storyboard. I'm not sure it is the right word but I'm referring to the thumbnails that appear when you hover over the timeline of the player. Examining the network tab in Chrome console, I saw it arrives as a single image of all the storyboard images concatenated

For example for this video this is the storyboard

I was wondering if there is a way to get the storyboard using the video URL / video ID?

4
  • Just to confirm, you're referring to the short three-ish second video that happens when you hover over the thumbnail? Like this thing? Commented Nov 8, 2021 at 6:44
  • Nope, I'm referring to the images that are displayed when you hover over the video's timeline once you are in the video page Commented Nov 8, 2021 at 6:53
  • @bluesummers AFAIK, this is not documented, I doubt this is possible, but, one way could be web-scraping the page and get the URL, (being genuinely curious here:) - have you tried anything?. Commented Nov 11, 2021 at 16:19
  • I didn't go that route since you'll need to also execute the JS code, so standard scraping will not work, you'll have to either scrape through something like Selenium or have some middleware to execute the JS code and those don't scale nicely. I prefer right now to download the video and create the storyboard myself or to find a way to get the storyboard strictly with HTTP Commented Nov 12, 2021 at 11:54

2 Answers 2

2

You can utilize Youtube iFrame Player API where storyboard url available (not documented) in player object right after player initialization and start playing OR just using player.seekTo(0). Look for playerInfo.storyboardFormat:

<script>
    function onPlayerStateChange(event) {
        if (event.target.playerInfo.storyboardFormat) {
            story_board_data = decodeURIComponent(event.target.playerInfo.storyboardFormat).split('|').map(function(el, n){
                return !n ? el : el.split('#');
            });
            event.target.destroy();
            document.getElementById('storyboard').src = story_board_data[0].replace('L$L/$N','L2/M0')+'&sigh='+story_board_data[3][7];
        }
    }
    function onPlayerReady(event) {
      event.target.mute();
      event.target.seekTo(0);
    }

    function onYouTubeIframeAPIReady() {
      var player = new YT.Player('player', {
          width: 1, height: 1,
          videoId: '7nN5g0bu7v4',
          events: {
              onReady: onPlayerReady,
              onStateChange: onPlayerStateChange,
          }
      });
    }
</script>

<div id="player"></div>
<img id="storyboard">
<script src="https://www.youtube.com/iframe_api"></script>

JSFiddle snippet works fine with iFrames, check it in action.

Sign up to request clarification or add additional context in comments.

Comments

1

I managed to do this in Python with Playwright, and a seperate JavaScript file that playwright can just read in and then run:

main.py

import os
from playwright.sync_api import sync_playwright

def get_storyboard_urls(video_id):
    # Load javascript
    script_path = os.path.join(os.path.dirname(__file__), 'storyboard_extractor.js')
    with open(script_path, 'r') as f:
        js_code = f.read()

    # Run playwright
    with sync_playwright() as p:
        browser = p.chromium.launch(headless=True)
        page = browser.new_page()
        try:
            page.goto(f"https://www.youtube.com/watch?v={video_id}")            
            storyboard_urls = page.evaluate(js_code)
            if storyboard_urls is None or storyboard_urls == "":
                raise ValueError(f"Failed to fetch storyboard URL for {video_id}\nValue returned: {storyboard_urls}")
            return storyboard_urls
        except Exception as e:
            print(f"Error fetching storyboard URL: {e}")
            raise e
        finally:
            browser.close()

get_storyboard_urls("nr0RPVvKWDI")

storyboard_extractor.js - a bit more detail in this answer

// This script extracts the storyboard URLs from a YouTube video page
() => {
    // Storyboard Get
    const resp = ytplayer.config.args.raw_player_response;
    const storyboards = resp.storyboards;
    const specRend = storyboards.playerStoryboardSpecRenderer;
    const spec = specRend.spec;

    const parts = spec.split("|")
    const base_n = (n) => parts[0].replace('L$L/$N',`L${n}/M0`);

    const signs = parts.map(p => p.split("rs$")[1]).filter(p => !!p).map(p => "rs%24" + p);

    // Output
    urls = []
    for (let i = 0; i < signs.length; i++) {
        let url = `${base_n(i)}&sigh=${signs[i]}`
        urls.push(url)
    }
    return urls
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.