yt-dlp: [teachable] ERROR: Unable to find Video URL https://edu.ecomsuccess.pk/courses/enrolled/903296

Checklist

Region

No response

Description

Just a few days ago I was able to download videos from the ‘member-only’ site, but not now. For every course link, yt-dlp issues the error ‘unable to find video URL’. Please help.

Verbose log

[debug] Command-line config: ['-f', '(bestvideo[ext=mp4]+bestaudio/best[ext=mp4]/best)[protocol^=http]', '--cookies', '/root/ecomsuccess.pk_cookies.txt', '--verbose', '--output', '/home/Tutorial/EComSuccess/%(playlist_title)s.%(ext)s', 'https://edu.ecomsuccess.pk/courses/enrolled/903296']
[debug] Encodings: locale UTF-8, fs utf-8, out utf-8 (No ANSI), err utf-8 (No ANSI), pref UTF-8
[debug] yt-dlp version 2022.04.08 [7884ade65] (zip)
[debug] Python version 3.8.10 (CPython 64bit) - Linux-5.4.0-107-generic-x86_64-with-glibc2.29
[debug] Checking exe version: ffmpeg -bsfs
[debug] Checking exe version: ffprobe -bsfs
[debug] exe versions: ffmpeg 4.2.4, ffprobe 4.2.4
[debug] Optional libraries: certifi, secretstorage, sqlite
[debug] Proxy map: {}
[debug] [generic] Extracting URL: https://edu.ecomsuccess.pk/courses/enrolled/903296
[generic] 903296: Requesting header
WARNING: [generic] Falling back on generic information extractor.
[generic] 903296: Downloading webpage
[generic] 903296: Extracting information
[debug] Looking for video embeds
[debug] [TeachableCourse] Extracting URL: teachable:https://edu.ecomsuccess.pk/courses/enrolled/903296
[TeachableCourse] 903296: Downloading webpage
[download] Downloading playlist: Affiliate Marketing
[TeachableCourse] playlist Affiliate Marketing: Collected 9 videos; downloading 9 of them
[download] Downloading video 1 of 9
[debug] [Teachable] Extracting URL: teachable:https://edu.ecomsuccess.pk/courses/903296/lectures/16630922
[Teachable] 16630922: Downloading webpage
ERROR: [Teachable] 16630922: Unable to find video URL; please report this issue on  https://github.com/yt-dlp/yt-dlp/issues?q= , filling out the appropriate issue template. Confirm you are on the latest version using  yt-dlp -U
  File "/usr/local/bin/yt-dlp/yt_dlp/extractor/common.py", line 641, in extract
    ie_result = self._real_extract(url)
  File "/usr/local/bin/yt-dlp/yt_dlp/extractor/teachable.py", line 175, in _real_extract
    raise ExtractorError('Unable to find video URL')

[download] Downloading video 2 of 9
[debug] [Teachable] Extracting URL: teachable:https://edu.ecomsuccess.pk/courses/903296/lectures/19396131
[Teachable] 19396131: Downloading webpage
ERROR: [Teachable] 19396131: Unable to find video URL; please report this issue on  https://github.com/yt-dlp/yt-dlp/issues?q= , filling out the appropriate issue template. Confirm you are on the latest version using  yt-dlp -U
  File "/usr/local/bin/yt-dlp/yt_dlp/extractor/common.py", line 641, in extract
    ie_result = self._real_extract(url)
  File "/usr/local/bin/yt-dlp/yt_dlp/extractor/teachable.py", line 175, in _real_extract
    raise ExtractorError('Unable to find video URL')

[download] Downloading video 3 of 9
[debug] [Teachable] Extracting URL: teachable:https://edu.ecomsuccess.pk/courses/903296/lectures/19397049
[Teachable] 19397049: Downloading webpage
ERROR: [Teachable] 19397049: Unable to find video URL; please report this issue on  https://github.com/yt-dlp/yt-dlp/issues?q= , filling out the appropriate issue template. Confirm you are on the latest version using  yt-dlp -U
  File "/usr/local/bin/yt-dlp/yt_dlp/extractor/common.py", line 641, in extract
    ie_result = self._real_extract(url)
  File "/usr/local/bin/yt-dlp/yt_dlp/extractor/teachable.py", line 175, in _real_extract
    raise ExtractorError('Unable to find video URL')

[download] Downloading video 4 of 9
[debug] [Teachable] Extracting URL: teachable:https://edu.ecomsuccess.pk/courses/903296/lectures/19397114
[Teachable] 19397114: Downloading webpage
ERROR: [Teachable] 19397114: Unable to find video URL; please report this issue on  https://github.com/yt-dlp/yt-dlp/issues?q= , filling out the appropriate issue template. Confirm you are on the latest version using  yt-dlp -U
  File "/usr/local/bin/yt-dlp/yt_dlp/extractor/common.py", line 641, in extract
    ie_result = self._real_extract(url)
  File "/usr/local/bin/yt-dlp/yt_dlp/extractor/teachable.py", line 175, in _real_extract
    raise ExtractorError('Unable to find video URL')

[download] Downloading video 5 of 9
[debug] [Teachable] Extracting URL: teachable:https://edu.ecomsuccess.pk/courses/903296/lectures/19397129
[Teachable] 19397129: Downloading webpage
ERROR: [Teachable] 19397129: Unable to find video URL; please report this issue on  https://github.com/yt-dlp/yt-dlp/issues?q= , filling out the appropriate issue template. Confirm you are on the latest version using  yt-dlp -U
  File "/usr/local/bin/yt-dlp/yt_dlp/extractor/common.py", line 641, in extract
    ie_result = self._real_extract(url)
  File "/usr/local/bin/yt-dlp/yt_dlp/extractor/teachable.py", line 175, in _real_extract
    raise ExtractorError('Unable to find video URL')

[download] Downloading video 6 of 9
[debug] [Teachable] Extracting URL: teachable:https://edu.ecomsuccess.pk/courses/903296/lectures/22181679
[Teachable] 22181679: Downloading webpage
ERROR: [Teachable] 22181679: Unable to find video URL; please report this issue on  https://github.com/yt-dlp/yt-dlp/issues?q= , filling out the appropriate issue template. Confirm you are on the latest version using  yt-dlp -U
  File "/usr/local/bin/yt-dlp/yt_dlp/extractor/common.py", line 641, in extract
    ie_result = self._real_extract(url)
  File "/usr/local/bin/yt-dlp/yt_dlp/extractor/teachable.py", line 175, in _real_extract
    raise ExtractorError('Unable to find video URL')

[download] Downloading video 7 of 9
[debug] [Teachable] Extracting URL: teachable:https://edu.ecomsuccess.pk/courses/903296/lectures/22181680
[Teachable] 22181680: Downloading webpage
ERROR: [Teachable] 22181680: Unable to find video URL; please report this issue on  https://github.com/yt-dlp/yt-dlp/issues?q= , filling out the appropriate issue template. Confirm you are on the latest version using  yt-dlp -U
  File "/usr/local/bin/yt-dlp/yt_dlp/extractor/common.py", line 641, in extract
    ie_result = self._real_extract(url)
  File "/usr/local/bin/yt-dlp/yt_dlp/extractor/teachable.py", line 175, in _real_extract
    raise ExtractorError('Unable to find video URL')

[download] Downloading video 8 of 9
[debug] [Teachable] Extracting URL: teachable:https://edu.ecomsuccess.pk/courses/903296/lectures/22868643
[Teachable] 22868643: Downloading webpage
ERROR: [Teachable] 22868643: Unable to find video URL; please report this issue on  https://github.com/yt-dlp/yt-dlp/issues?q= , filling out the appropriate issue template. Confirm you are on the latest version using  yt-dlp -U
  File "/usr/local/bin/yt-dlp/yt_dlp/extractor/common.py", line 641, in extract
    ie_result = self._real_extract(url)
  File "/usr/local/bin/yt-dlp/yt_dlp/extractor/teachable.py", line 175, in _real_extract
    raise ExtractorError('Unable to find video URL')

[download] Downloading video 9 of 9
[debug] [Teachable] Extracting URL: teachable:https://edu.ecomsuccess.pk/courses/903296/lectures/22868644
[Teachable] 22868644: Downloading webpage
ERROR: [Teachable] 22868644: Unable to find video URL; please report this issue on  https://github.com/yt-dlp/yt-dlp/issues?q= , filling out the appropriate issue template. Confirm you are on the latest version using  yt-dlp -U
  File "/usr/local/bin/yt-dlp/yt_dlp/extractor/common.py", line 641, in extract
    ie_result = self._real_extract(url)
  File "/usr/local/bin/yt-dlp/yt_dlp/extractor/teachable.py", line 175, in _real_extract
    raise ExtractorError('Unable to find video URL')

[download] Finished downloading playlist: Affiliate Marketing

About this issue

  • Original URL
  • State: open
  • Created 2 years ago
  • Comments: 88 (19 by maintainers)

Most upvoted comments

Thanks for the work @Green0Photon. Took myself a bit of time to get everything ready, therefore I thought other users might like a little summary of the steps above:

  1. Download Green0Photon’s version of yt-dlp and extract it.
  2. I used Firefox and the “cookies.txt” before, but in my case in Firefox the video player didn’t show up. No idea what the problem was. So I tried with chrome an “Get cookies.txt” and the exported list of cookies was longer.
  3. Then run the command to download ./yt-dlp.sh --cookies cookies.txt --verbose -N 32 https://course.com/courses/enrolled/1234567 optional: add the following parameter to get your video files organized into directories according to the course chapters. -o "./%(chapter_number)s-%(chapter)s/%(autonumber)03d-%(title)s.%(ext)s"

Edit: Though it “only” seemed to help on the “unable to exctract course title” error for me. In the browser I saw one course with a “newer” design, which still gave me the “Unsupported URL” error.

Edit 2: For the “newer” design I still could download the lectures one by one. The ID of the lectures (as shown in the URL) were just counting up in my case. Which allowed me running a loop over the numbers do download all. So you have to check the first ID and the last ID and run a for loop to download. Same commands as used above (will organize them as above):

for i in {<first id>..<last id>}
do
./yt-dlp.sh --cookies cookies.txt --verbose -N 32 -o "./%(chapter_number)s-%(chapter)s/%(autonumber)03d-%(title)s.%(ext)s" -N 32 https://course.com/courses/coursename/lectures/$i
done

I’ve been using yt-dlp to consume some Teachable content (cantrill for AWS certs is great) on my phone at high speed (with skip silence feature, I love lectures now, I rec the AntennaPod app), and I’ve come back to discover it not working. So I’m giving my shot at seeing if I can fix it.

Right now, I’ve been able to do what @svenop5 says. That is, downloading something manually by grabbing stuff via Web Dev Tools after whatever of their necessary JS runs in the browser.

Firstly, some of his content is public on his site, that you can view (as a trial I guess) without buying the course. I’ll use it as an example and for tests. This is a good example link.

Teachable Site

Now, when you visit that link in e.g. curl, it won’t provide the hotmart iframe. Instead, you get this:

<div class='wistia_responsive_padding'>
      <div class='wistia_responsive_wrapper'>
        <div class="hotmart_video_player" data-attachment-id="44728934" data-course-id="1101194" data-lecture-id="24859593" data-user-id="-1">
        </div>
      </div>
    </div>

Yes, the html download via curl did have that shoddy indentation.

No clue where that attachment-id comes from or if it ever changes – but you can see course-id and lecture-id from the url. The user-id is -1 when logged out, and it’s some other id when I’m logged in.

It looks like those two outer divs are remnants from Teachable embedding Wistia, but they just replaced the player itself ergo the inner hotmart div. Now, when I visit the page in an actual browser, it injects an iframe into that hotmart_video_player div. Who knows how that iframe gets generated from the JS, but getting its src attribute is one thing yt-dlp will probably need to do now.

IFrame on the Teachable Site

That inner iframe looks like this:

<iframe title="Video Player" scrolling="no" class="_1jWb8 _13nB1" name="hotmart_embed" data-testid="embed-player" referrerpolicy="strict-origin" allowfullscreen="" src="https://player.hotmart.com/embed/aZ8M9pOeRp?signature=ptUVVfFZdmAWWpT9OdCT-ja6AcYKGE3FJv3tK5MZ9tiuOTDraAcMGRq3DA9U-PbtPOGRX8SPLDFvAmHN-L55SmvwehpA4bQZwqS3lGldBLZ6o-D5YVs_G4cg2NGtJOCZr2D-QwuvBszZ0qWmlpvl_LnaexOgkUhl6N8mkMbu12ZixArXnVkQ6Nw-eevWvoqGuCO5c_aEdIkXG1kHMkkvxtacA2NTWFC_VXrd8Jo8hypLh6-q3zIXg8cIJkP5jRGMQH-laN0YFmONXrvYVtLeGQ3eQNdj4IKLL5B5Y-RU4v8Z8P9t0fQMKLAuInh9PMs5ZaocIYrK0mnP_rq4NRVTEg==&token=aa2d356b-e2f0-45e8-9725-e0efc7b5d29c&user=-1&autoplay=autoplay" frameborder="0"></iframe>

That link should be expired when you use it, so you’ll have to generate a new one yourself by visiting the Teachable page. Things to note:

  • A video id path parameter after /embed/.
  • A signature query parameter, looks to be base64 with the -_ url safe variant.
  • A token query parameter, looks to be a uuid.
  • The user number from the containing div got passed in, interestingly enough. It’s strange for this to be the case, I expected the user-id to be unique to either Teachable or the specific Teachable site (I can’t remember which I’ve logged in with), and don’t know why it would get passed to their backend.
  • One of those annoying autoplay query parameter.
  • I can remove the autoplay and user parameters and it’ll still work just fine. A wrong length or changed token gives a 400, and removing it entirely is a 401. A changed or removed signature is a 401. When the page expires, it’ll give a 410.

One interesting side note: there is only one flavor of link, but I’ve encountered two different video content sources, which I’ll get to. One has an HLS playlist link of qualities you can plug directly into streamlink, and another has the seperate key link you need to need to give to streamlink separately. The latter actually causes Firefox to think it’s a tracker and so it actually gets blocked, but both work when you visit the iframe src directly. I’ve been able to download both via streamlink. The former was what I initially encountered, but now I’m only getting the latter. I’m not sure if we’ll want to support both or just the latter (i.e. the separate key link one).

Hotmart Player Site

So let’s visit that src link now. Again, you’ll need to grab your own, so I’m not going to put it in here again.

Now open the Network section of the Web Dev tools – preferably open it before you go to the src link, so you can see all requests without needing to refresh the page. There’s some html, js, css, a favicon, and a thumbnail jpg (maybe it’ll be fun to keep this later, too, like the youtube extractor). Also for me, several blocked POST calls for data collection, though unfortunately one goes through. Besides that, the one we care about.

The easy type has the following interesting requests (I was only able to get this for a different paid video, titled “Public Introduction (Release v1)”, which sounds like it’s mistakenly private):

IFrame SRC
https://player.hotmart.com/embed/1LV7p2G5Zw?signature=ajfXuUz0irJLWqthpP26b2WcnocieD7CIqvxNV1eU2KpH3AWDxmAFBWg6W1q7yIR-3U9pY_WeW6iveIDT6dh6KMxxGnzpoQEdB3cps9xjf2eBrmpzDaixmrnYAAAWmTRVFFXa6Z8pvqfXXN-LQvYi2iaxAnvqWBBIXNBx3Xu-FvMhF66_c5Y7ZgNUeo6GHFDwUIbO4ibe2eHSRRIuaQEBvMpSlJvvyoEkO4ysKqulLWxwcwvl6zHHF4F8jVYY_wcdZaSMJUflPArS59MbIq5ysy345I3b53OxSNb0PyfQbWptR1VJ5dMsPE7cBkXquVQdFF8GBqz6mn6XFmWdzfYTw==&token=aa2d356b-e2f0-45e8-9725-e0efc7b5d29c

The main playlist of the different sizes (application/x-mpegURL)
https://vod-akm.play.hotmart.com/video/1LV7p2G5Zw/hls/master-t-1649284024000.m3u8?hdnts=st=1654463927~exp=1654464427~hmac=9f799dc21519be7d28b621faa75be800259c5dc16fe15f1011451653b39fad2c

1080p playlist (application/x-mpegURL)
https://vod-akm.play.hotmart.com/video/1LV7p2G5Zw/hls/1080/1080.m3u8?hdntl=exp=1654550327~acl=/*~data=hdntl~hmac=ca9bffe82c7f4e43db67b79ad0d2b1935dc7958f2784cf4f3400168dfb7b5bc7

HLS Segment Key URI (application/octet-stream)
https://vod-akm.play.hotmart.com/video/1LV7p2G5Zw/hls/1080/b54b8444-ac52-443f-88df-35fcfaf7742d.key?hdntl=exp=1654550327~acl=/*~data=hdntl~hmac=ca9bffe82c7f4e43db67b79ad0d2b1935dc7958f2784cf4f3400168dfb7b5bc7

The various segments, e.g. 0 (video/MP2T)
https://vod-akm.play.hotmart.com/video/1LV7p2G5Zw/hls/1080/segment-0.ts?hdntl=exp=1654550327~acl=/*~data=hdntl~hmac=ca9bffe82c7f4e43db67b79ad0d2b1935dc7958f2784cf4f3400168dfb7b5bc7

The same video when loaded under the hard type looks like this:

IFrame SRC
https://player.hotmart.com/embed/1LV7p2G5Zw?signature=zaMGfsTVNvoJHWWZjbY86fJCnAbLnieLzFS0gGYoA1nmBAkLJ9g6kow75a0Kpre_88AkYMj8XZPTISg5TnGQY56oue16Jo09AkakT_j4mII1S0tmqulC4P1eFeCYQtR1rTyDfbj95qoVZaGhVQAw5dK3Obwy1yyuQUY7-n7aOYrT1_jryzZtoXg5rpUw4fYfOTxSXTqk2-4kMKvHWUdou5HwtWwKvZ0HYyfpYKZrUgk98RilFKE248UlmNWL1Ok9AgXVts045VzZOrIY6IHfbiabmcWJKnyMP7EdMYQs0t5-GijVWu2OxGZBTM31BHIbeR37GG1WHEJmWhgNIwYusA==&token=aa2d356b-e2f0-45e8-9725-e0efc7b5d29c

The main playlist of the different sizes (application/x-mpegURL)
https://contentplayer.hotmart.com/video/1LV7p2G5Zw/source/dmlkZW8lMkYxTFY3cDJHNVp3JTJGaGxzJTJGbWFzdGVyLm0zdTg=-t-1649284024000.m3u8?Policy=eyJTdGF0ZW1lbnQiOiBbeyJSZXNvdXJjZSI6Imh0dHBzOi8vY29udGVudHBsYXllci5ob3RtYXJ0LmNvbS92aWRlby8xTFY3cDJHNVp3L2hscy8qIiwiQ29uZGl0aW9uIjp7IkRhdGVMZXNzVGhhbiI6eyJBV1M6RXBvY2hUaW1lIjoxNjU0NDcyNjE4fX19XX0_&Signature=RVsBVSiUoDIH3eFUn1rpaxc2cRu4CCDvZPdSUKjODQ1~mLHTzTJ6F8hkTsUDcezrH0FZWyLvsRDFkRS~9HQVAQGOLG0HogrhMJazQCxYQudt5vwLRufhGd4Tuh1WX8qB85JSNclnH6VA8ps2EWQpMgtEnyohewz22k~NXmYdT90~qdgp-JMK1CdSR6wVjQSPhW-m1NJ2Mxz9BkNXk53hjzZm02aziIWYMff1dDBNofds4XzvvJWirPNHuA9Po8L5y8WVdhlfJ~TS4ePrYUdQU4eu9xdZ4XR1jGMpjJleWmIsUXGlv4NFpEGqSVr~eiWZHswvEW48js--yCY0muFw1Q__&Key-Pair-Id=APKAI5B7FH6BVZPMJLUQ&Policy-cf=eyJTdGF0ZW1lbnQiOiBbeyJSZXNvdXJjZSI6Imh0dHBzOi8vY29udGVudHBsYXllci5ob3RtYXJ0LmNvbS92aWRlby8xTFY3cDJHNVp3L2hscy8qIiwiQ29uZGl0aW9uIjp7IkRhdGVMZXNzVGhhbiI6eyJBV1M6RXBvY2hUaW1lIjoxNjU0NDcyNjE4fX19XX0_&Signature-cf=RVsBVSiUoDIH3eFUn1rpaxc2cRu4CCDvZPdSUKjODQ1~mLHTzTJ6F8hkTsUDcezrH0FZWyLvsRDFkRS~9HQVAQGOLG0HogrhMJazQCxYQudt5vwLRufhGd4Tuh1WX8qB85JSNclnH6VA8ps2EWQpMgtEnyohewz22k~NXmYdT90~qdgp-JMK1CdSR6wVjQSPhW-m1NJ2Mxz9BkNXk53hjzZm02aziIWYMff1dDBNofds4XzvvJWirPNHuA9Po8L5y8WVdhlfJ~TS4ePrYUdQU4eu9xdZ4XR1jGMpjJleWmIsUXGlv4NFpEGqSVr~eiWZHswvEW48js--yCY0muFw1Q__&Key-Pair-Id-cf=APKAI5B7FH6BVZPMJLUQ

1080p playlist (application/x-mpegURL)
https://contentplayer.hotmart.com/video/1LV7p2G5Zw/source/playlist/dmlkZW8vMUxWN3AyRzVady9obHMvMTA4MC8xMDgwLm0zdTg.m3u8?Policy-cf=eyJTdGF0ZW1lbnQiOiBbeyJSZXNvdXJjZSI6Imh0dHBzOi8vY29udGVudHBsYXllci5ob3RtYXJ0LmNvbS92aWRlby8xTFY3cDJHNVp3L2hscy8qIiwiQ29uZGl0aW9uIjp7IkRhdGVMZXNzVGhhbiI6eyJBV1M6RXBvY2hUaW1lIjoxNjU0NDcyNjE4fX19XX0_&Signature-cf=RVsBVSiUoDIH3eFUn1rpaxc2cRu4CCDvZPdSUKjODQ1~mLHTzTJ6F8hkTsUDcezrH0FZWyLvsRDFkRS~9HQVAQGOLG0HogrhMJazQCxYQudt5vwLRufhGd4Tuh1WX8qB85JSNclnH6VA8ps2EWQpMgtEnyohewz22k~NXmYdT90~qdgp-JMK1CdSR6wVjQSPhW-m1NJ2Mxz9BkNXk53hjzZm02aziIWYMff1dDBNofds4XzvvJWirPNHuA9Po8L5y8WVdhlfJ~TS4ePrYUdQU4eu9xdZ4XR1jGMpjJleWmIsUXGlv4NFpEGqSVr~eiWZHswvEW48js--yCY0muFw1Q__&Key-Pair-Id-cf=APKAI5B7FH6BVZPMJLUQ

HLS Segment Key URI (application/octet-stream)
https://contentplayer.hotmart.com/video/1LV7p2G5Zw/hls/1080/b54b8444-ac52-443f-88df-35fcfaf7742d.key?Policy=eyJTdGF0ZW1lbnQiOiBbeyJSZXNvdXJjZSI6Imh0dHBzOi8vY29udGVudHBsYXllci5ob3RtYXJ0LmNvbS92aWRlby8xTFY3cDJHNVp3L2hscy8qIiwiQ29uZGl0aW9uIjp7IkRhdGVMZXNzVGhhbiI6eyJBV1M6RXBvY2hUaW1lIjoxNjU0NDcyNjE4fX19XX0_&Signature=RVsBVSiUoDIH3eFUn1rpaxc2cRu4CCDvZPdSUKjODQ1~mLHTzTJ6F8hkTsUDcezrH0FZWyLvsRDFkRS~9HQVAQGOLG0HogrhMJazQCxYQudt5vwLRufhGd4Tuh1WX8qB85JSNclnH6VA8ps2EWQpMgtEnyohewz22k~NXmYdT90~qdgp-JMK1CdSR6wVjQSPhW-m1NJ2Mxz9BkNXk53hjzZm02aziIWYMff1dDBNofds4XzvvJWirPNHuA9Po8L5y8WVdhlfJ~TS4ePrYUdQU4eu9xdZ4XR1jGMpjJleWmIsUXGlv4NFpEGqSVr~eiWZHswvEW48js--yCY0muFw1Q__&Key-Pair-Id=APKAI5B7FH6BVZPMJLUQ

The various segments, e.g. 0 (video/MP2T)
https://contentplayer.hotmart.com/video/1LV7p2G5Zw/hls/1080/segment-0.ts?Policy=eyJTdGF0ZW1lbnQiOiBbeyJSZXNvdXJjZSI6Imh0dHBzOi8vY29udGVudHBsYXllci5ob3RtYXJ0LmNvbS92aWRlby8xTFY3cDJHNVp3L2hscy8qIiwiQ29uZGl0aW9uIjp7IkRhdGVMZXNzVGhhbiI6eyJBV1M6RXBvY2hUaW1lIjoxNjU0NDcyNjE4fX19XX0_&Signature=RVsBVSiUoDIH3eFUn1rpaxc2cRu4CCDvZPdSUKjODQ1~mLHTzTJ6F8hkTsUDcezrH0FZWyLvsRDFkRS~9HQVAQGOLG0HogrhMJazQCxYQudt5vwLRufhGd4Tuh1WX8qB85JSNclnH6VA8ps2EWQpMgtEnyohewz22k~NXmYdT90~qdgp-JMK1CdSR6wVjQSPhW-m1NJ2Mxz9BkNXk53hjzZm02aziIWYMff1dDBNofds4XzvvJWirPNHuA9Po8L5y8WVdhlfJ~TS4ePrYUdQU4eu9xdZ4XR1jGMpjJleWmIsUXGlv4NFpEGqSVr~eiWZHswvEW48js--yCY0muFw1Q__&Key-Pair-Id=APKAI5B7FH6BVZPMJLUQ

All of these when queried are expired, so it should be safe to put them here. Though that token is the same between the two – hopefully that doesn’t leak anything from my account. I’ll try relogging later. Or perhaps it’s actually an app id. I’ve also saved all their contents, but I won’t upload them now just in case – though the response from HLS Segment Key URI is the same between the two, though not between videos.

Encrypted Keys

Now, I was able to download these using streamlink, as I said before. It’s a different sha1sum from the old video that used to be there (which I believe just served whatever video was uploaded, since those even had year old timestamps from when they were made), but now are just HLS streams. As @svenop5 said, the hard version needs --hls-segment-key-uri. This overwrites a line in the 1080p stream, though a similar line exists in every format. It looks like this:

#EXT-X-KEY:METHOD=AES-128,URI="chave://pi1CcgormCfrBeHFonx1SjYyNREActOy9x5fdSmTcQ7vrgPs9bFOslr+5kVY9+jkeryG2glWOzdVjFmi5SkOe6hSZQUQNB2nrF1+/0QVHCH6KQJS0ZWLjzPSmlHLHuDvBIL3jU7kOHeBravBobyquLdW9fqqVaP3927IFMgJrkl2gExKxAMwSaPX+PEG4IgGtolIRq0TrYpwk7u7ze+TJVSpKmqWuP8fUoUIuMIRMBVrWHihqPVRakmLJGC2Rp6unvBYUBIvXME8mN9zjJ5ZHH5XitQ0Z5HGyQh5z3nYi+hbfTB7IK6ZxNsn6KVA9QGxFGFqHSVmrdP/kJJWsF3YkVd5IhQ0Kpe5/oq3OdFpj5am0oJXkZz9wEuXsMPpFTBfbv8IjdDEUE+zclgPAJ0DEtRvjL4Jbqd0mCTRaM2Emi+BYVTlOqQfq48POV5CYe3BS5uRQlFk+RwZFQog8dAgXGfiCAYLKd49DpdN9Ul+fv2f9GTuhwgX/qlWEeikqDO8mT7NJp22kg1XQeb2JlxZMurWrk4LzLzSvI3iVYe3WXviVuiE4UqdKQa8jbyGPjm4CEBpCQgoqvs25pAkFHseammH8UQZ0qfYOfGhpCuuuZtzRpnhdlj7grQ8MErmnaIb3i5NEGMUFh+4rVyaDDYXCU3m2EjyBpe4x9O6HU0IXf+M8GQz9Oc1d6isvTfTIZnBF3awgfc6yZ7s2yIWH9cYrZVbeVL7DVV6Yc5Hb8/fAcC9dHwkx9O/w2FEcRCLyCct10eLqwk0OOfsdEKeNbBmApaFp6/luxUoDOXYB5oKEFa9BVBke2PdYj8ooBTMrwV/VJ0SJGlEjArw23ZdWCPs/yJ8nAGqXqBoI8YUvFN3L6MSrmgmAEBnOZzz7u1OMYzzQAJXk0Zr7i9U3p9igq9+T6YznSQHnhhnFBJElBxQ3GU=",IV=0x12284f8f8717ced82a58af25497a08b7

The chave means it’s custom. yt-dlp does support AES-128 here, but it needs to be a normal url, not that encrypted one. I downloaded that 1080p playlist, put it into a text editor, replaced that url with the decoded one (HLS Segment Key URI above), and yt-dlp and streamlink were both able to then immediately download it. Easy mode’s looked like this:

#EXT-X-KEY:METHOD=AES-128,URI="b54b8444-ac52-443f-88df-35fcfaf7742d.key?hdntl=exp=1654550263~acl=/*~data=hdntl~hmac=156f1d7563d0111572b596e6b60ef13b5f01359468911ecd1fe75772df9fc464",IV=0x12284f8f8717ced82a58af25497a08b7

Which is the url of the key which yt-dlp and streamlink are able to get and decode the videos. Again, that result of that key uri is different for different sizes of video, but is the same for easy/hard mode for the same size of the video.

Also, I noticed that the files downloaded are the same between streamlink and yt-dlp, regardless of easy/hard, for the same video size, as long as you have --fixup never. Though the fixup is consistent and will provide the same sha1sum afterwards. I haven’t tested resuming, but I assume it works.

Conclusion

That should be enough information for anybody to easily write e.g. a selenium bot to get all this information to automate a download (I quickly did something similar to this in the past for something unsupported by yt-dlp, but with easy download mp4 source files, this was for logging in and getting a list of videos, instead of not needing to write a decryptor). Otherwise, this is what we need to do:

  1. Write code to generate that iframe src link from a Teachable Link. Notably the video id, signature, and token. I have a public example page for people to use above.
  2. Figure out where the main playlist link comes from on the hotmart embedded video player page, then generate it ourselves from the page if it’s not easily given to us. Note that there’s no extra API call to get it – just html and js before it happens. It might give you the easy or hard one and we’ll need code to deal with both (they might be doing blue/green migration towards encryption only), or possibly we could generate either that we want and have it easy, though this might break if they delete the easy way and permanently move to hard.
  3. Write code decrypting the chave link to a normal url if necessary.
  4. Connecting all of this in yt-dlp. I expect this as fixing the Teachable extractor, which can then point to a new Hotmart extractor instead of the old Wistia extractor. Preferably we’d fix the Teachable extractor to not have the course playlist point to single playlists for each video (this breaks my metadata when I try to have nice filenames when downloading a whole course), and maybe we can even improve it to have more metadata (video description, course sections). The Hotmart playlist (the e.g. 1080p one) also has subtitles, but this might be supported just by having the Hotmart extractor fix the key, and then forwarding to a m3u8 downloader.
  5. Writing tests. We have an easy Teachable public one, but Hotmart will then be secured, so would need to be tested by going through Teachable. I don’t know how to set this up, but in theory all this can be automated, which is nice. I’m not seeing any quick video to use on a Hotmart site – they don’t seem to natively be a video service.

I might try and do these things myself. Only thing is, I’ve already spent a few hours fiddling around, figuring this out, and documenting this. And I don’t know much about the yt-dlp codebase and contributing to it the best – though my day job is Python. I’m mostly just trying to get a semi-quick and dirty way of fixing this, then maybe one of you can finish and fix it. IDK. It’s been cool learning about all this, though. (And I do need these videos downloaded to do training for my job efficiently.)

Last thought: I wonder if Hotmart is just using someone else’s HLS implementation, in particular, their encryption code, so I wonder if there is another extractor already written which shared the chave decryption code, to make all of this easier. Maybe even the URL stuff too?

Until this gets fixed I have written my own script for downloading courses https://github.com/FallingLights/Teachable-dl

@Abdess 's repo doesn’t work for me. Is this the Cloudflare issue mentioned above?

WARNING: [Hotmart] Failed to download m3u8 information: HTTP Error 403: Forbidden
ERROR: [Hotmart] aZ8DadNOZp: No video formats found!;
Traceback (most recent call last):
  File "yt_dlp\YoutubeDL.py", line 1567, in wrapper
  File "yt_dlp\YoutubeDL.py", line 2028, in __process_iterable_entry
  File "yt_dlp\YoutubeDL.py", line 1835, in process_ie_result
  File "yt_dlp\YoutubeDL.py", line 1782, in process_ie_result
  File "yt_dlp\YoutubeDL.py", line 2751, in process_video_result
  File "yt_dlp\YoutubeDL.py", line 1076, in raise_no_formats
yt_dlp.utils.ExtractorError: [Hotmart] aZ8DadNOZp: No video formats found!

@bruhcephalus the changes made by @Green0Photon are being integrated here: https://github.com/yt-dlp/yt-dlp/pull/7650

Could you please test if this fork works and give some feedback? : https://github.com/Abdess/yt-dlp/tree/teachable-fix-add-hotmart

Any feedback is welcome to find out if the changes are working properly. The only remaining issue is the login which causes a 403 error, which is due to Cloudflare’s security. 😃

I was the one who figured out how the Teachable update broke things and made the barely working hotfix. Due to some life events, it’s just been a bit rough coming back and finishing it and getting it merged into master to make it easy to use.

And it looks like there was a further update that broke things, for some people. Great. If any of you have a publicly accessible course with the new UI, please send me the link (if private send to my reddit /u/Green0Photon) so I can work on it. I’m not super sure what counts as public, but preferably I should be able to go to at least one video location like an intro and be able to watch it without being logged in – I remember via testing my own main usecase that despite the redirection, I was still able to create the list and download the public videos in one course I care about, without being logged in.

Thanks @Biepa for the concise summary for other people to use. FYI, in Firefox the third party cookie blocker thing breaks Hotmart (the new Teachable video backend I implemented the downloading for), but only if it’s the encrypted videos instead of the unencrypted videos. It’s weird. In any case, all you need is the cookies from the Teachable site itself, not Hotmart or anything. In any case, those two extensions were both what I recommended people use. So I appreciate the write-up.

Anyway, @karlochacon, I recommend that you look at the batch file option:

-a, --batch-file FILE           File containing URLs to download ("-" for
                                stdin), one URL per line. Lines starting
                                with "#", ";" or "]" are considered as
                                comments and ignored

Basically, instead of the url at the end, you can manually copy paste each course video into a text file, each on a different line, and then put -a list-of-courses.txt or whatever instead of https://learn.cantrill.io/courses/enrolled/1101198/$i.

…Wait a second, that’s explicitly what I use this for. Well, good to know that it’s broken and have a full logged-in test case for it. I didn’t know it got broken, and I don’t see any UI change. Hmm… In any case, I’ll try to fix that soon, or something.

In the meantime, use the batch file flag as a workaround. A bit annoying since you need to copy each individual video you need, and there’s a lot of videos, but it works.

I just sent a message to reddit

@kzorfy I’ve got a beta version for you now. https://github.com/Green0Photon/yt-dlp/tree/teachable-fix-add-hotmart – or download directly.

I forced pushed the branch since I had a bad commit I didn’t want included in the final PR, just in case you already cloned it.

I’ve been able to run my previous command where I can download a whole course. Just slower unfortunately because this move to HLS means it’s downloading lots of small files instead of one bigger one – even then, still seems oddly slow. But it has chapter number and stuff.

That said, I want to investigate more and make sure more metadata stuff is good, plus add tests later. Make sure I’m fully following this project’s best practices.

But it would be super great to see people trying this out on different sites. I’ve even added description hopefully downloading too.

Might be a little while before I add more progress, but I’m curious of people’s thoughts of my code here, too.