Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[teachable] ERROR: Unable to find Video URL https://edu.ecomsuccess.pk/courses/enrolled/903296 #3564

Open
7 tasks done
rsuchwani opened this issue Apr 27, 2022 · 88 comments · May be fixed by #7650
Open
7 tasks done
Labels
can-share-account Someone is willing to provide account details for development site-bug Issue with a specific website

Comments

@rsuchwani
Copy link

rsuchwani commented Apr 27, 2022

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
@rsuchwani rsuchwani added site-bug Issue with a specific website triage Untriaged issue labels Apr 27, 2022
@pukkandan pukkandan added account-needed Account details are needed to test/fix this and removed triage Untriaged issue labels Apr 27, 2022
@rsuchwani
Copy link
Author

Amm, do I give account credentials here?

@pukkandan pukkandan added the can-share-account Someone is willing to provide account details for development label Apr 27, 2022
@pukkandan
Copy link
Member

No. The labels are just a hint for maintainers/contributors that account is needed to work on the issue. If any developer needs account details for working on this, they will let you know how to share it privately.

@rsuchwani

This comment was marked as resolved.

@rsuchwani

This comment was marked as spam.

@sulyi

This comment was marked as resolved.

@rsuchwani rsuchwani changed the title ERROR: Unable to find Video URL ERROR: Unable to find Video URL [teachable:https://edu.ecomsuccess.pk/courses/enrolled/903296] Apr 30, 2022
@rsuchwani

This comment was marked as resolved.

@pukkandan pukkandan changed the title ERROR: Unable to find Video URL [teachable:https://edu.ecomsuccess.pk/courses/enrolled/903296] [teachable] ERROR: Unable to find Video URL https://edu.ecomsuccess.pk/courses/enrolled/903296 Apr 30, 2022
@rsuchwani

This comment was marked as resolved.

@sulyi
Copy link
Contributor

sulyi commented May 4, 2022

if not wistia_urls:
if any(re.search(p, webpage) for p in (
r'class=["\']lecture-contents-locked',
r'>\s*Lecture contents locked',
r'id=["\']lecture-locked',
# https://academy.tailoredtutors.co.uk/courses/108779/lectures/1955313
r'class=["\'](?:inner-)?lesson-locked',
r'>LESSON LOCKED<')):
self.raise_login_required('Lecture contents locked')
raise ExtractorError('Unable to find video URL')

Should use _search_regex and raise appropriate error message about login.

@rsuchwani
Copy link
Author

Amm, is this a solution or suggestion for the developers?? Apologies in advance..

@sulyi
Copy link
Contributor

sulyi commented May 18, 2022

@rsuchwani,
This whole issue is for developers, as such this is really not related to it.
But no worries, getting in touch with user is an important part of development.

And sadly it's not any kind of solution, I've just looked at the source of the problem and shared my thoughts.
If it's available in browser it should be able to download.

Btw, is your cookiejar fresh?

@rsuchwani
Copy link
Author

rsuchwani commented May 18, 2022

@rsuchwani, This whole issue is for developers, as such this is really not related to it. But no worries, getting in touch with user is an important part of development.

And sadly it's not any kind of solution, I've just looked at the source of the problem and shared my thoughts. If it's available in browser it should be able to download.

Btw, is your cookiejar fresh?

Hello,

Thanks for taking the time to respond. I tried again with a fresh set of cookies but the same output.

[debug] Command-line config: ['-f', '(bestvideo[ext=mp4]+bestaudio/best[ext=mp4]/best)[protocol^=http]', '--cookies', '/root/ecomsuccess.pk_cookies_May18.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, pref UTF-8, out utf-8 (No ANSI), error utf-8 (No ANSI), screen utf-8 (No ANSI)
[debug] yt-dlp version 2022.05.18 [b14d52355] (zip)
[debug] Python version 3.8.10 (CPython 64bit) - Linux-5.4.0-110-generic-x86_64-with-glibc2.29
[debug] Checking exe version: ffprobe -bsfs
[debug] Checking exe version: ffmpeg -bsfs
[debug] exe versions: ffmpeg 4.2.4, ffprobe 4.2.4
[debug] Optional libraries: certifi-2019.11.28, secretstorage-2.3.1, sqlite3-2.6.0
[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 642, in extract
    ie_result = self._real_extract(url)
  File "/usr/local/bin/yt-dlp/yt_dlp/extractor/teachable.py", line 173, 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 642, in extract
    ie_result = self._real_extract(url)
  File "/usr/local/bin/yt-dlp/yt_dlp/extractor/teachable.py", line 173, 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 642, in extract
    ie_result = self._real_extract(url)
  File "/usr/local/bin/yt-dlp/yt_dlp/extractor/teachable.py", line 173, 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 642, in extract
    ie_result = self._real_extract(url)
  File "/usr/local/bin/yt-dlp/yt_dlp/extractor/teachable.py", line 173, 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 642, in extract
    ie_result = self._real_extract(url)
  File "/usr/local/bin/yt-dlp/yt_dlp/extractor/teachable.py", line 173, 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 642, in extract
    ie_result = self._real_extract(url)
  File "/usr/local/bin/yt-dlp/yt_dlp/extractor/teachable.py", line 173, 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 642, in extract
    ie_result = self._real_extract(url)
  File "/usr/local/bin/yt-dlp/yt_dlp/extractor/teachable.py", line 173, 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 642, in extract
    ie_result = self._real_extract(url)
  File "/usr/local/bin/yt-dlp/yt_dlp/extractor/teachable.py", line 173, 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 642, in extract
    ie_result = self._real_extract(url)
  File "/usr/local/bin/yt-dlp/yt_dlp/extractor/teachable.py", line 173, in _real_extract
    raise ExtractorError('Unable to find video URL')

[download] Finished downloading playlist: Affiliate Marketing

@AlphaTurtlee

This comment was marked as duplicate.

@rsuchwani

This comment was marked as resolved.

@dirkf
Copy link
Contributor

dirkf commented Jun 1, 2022

See ytdl-org/youtube-dl#30929 (comment) for why no wistia_urls are being found (tl;dr new video hoster). And #3453 for what is needed to solve the problem.

@svenop5
Copy link

svenop5 commented Jun 2, 2022

There a few issues here:

  1. the page scraping extractor is broken as the video provider has changed to hotmart
  2. hotmart authentication is probably different to the previous video provider
  3. video segments are encrypted and although segment decryption is supported, the URL for the encryption key itself (m3u8 EXT-X-KEY) is also encrypted

So even if you deal with the first two issues you still have the last one. How practical a solution is would depend on how often the video provider changes their EXT-X-KEY encryption strategy and how easy those changes are to determine.

@dirkf
Copy link
Contributor

dirkf commented Jun 2, 2022

# 1 is straightforward; # 2 is tricky but using cookies should work around it; # 3 is the real meat.

@svenop5
Copy link

svenop5 commented Jun 2, 2022

Regarding #2, perhaps cookies are required to get the m3u8 playlist URL but requesting the playlist, segments and key does not require cookies. The auth is passed in request params.

Regarding #3, this requires extracting the client side JavaScript code and porting it to Python. I have extracted the code and verified that it does indeed decrypt the key URI. There's about 50 lines of code to port to Python. The encryption used for key URIs is AES-128.

I don't have much motivation to implement this as I have an acceptable workaround. I only ever download individual videos. For this I'm already looking in Developer Tools to grab the m3u8 playlist URL, it's not much trouble to also grab the decrypted key URI from there at the same time and then download with streamlink's segment key URI override feature:

streamlink --hls-segment-key-uri KEY-URI -o output.mp4 PLAYLIST-URL best

@pukkandan
Copy link
Member

I have extracted the code and verified that it does indeed decrypt the key URI. There's about 50 lines of code to port to Python. The encryption used for key URIs is AES-128.

I don't have much motivation to implement this as I have an acceptable workaround.

Even if you don't intend to continue, sharing your findings in detail could be helpfull to devs trying to fix this

@svenop5
Copy link

svenop5 commented Jun 3, 2022

Even if you don't intend to continue, sharing your findings in detail could be helpfull to devs trying to fix this

Full source is available. You just need to locate the hotmart code in your browser Developer Tools where the decryption is happening (for example, by searching for the custom key URL protocol string "chave://" or "key://") and set a breakpoint there.

@Green0Photon
Copy link

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?

@dirkf
Copy link
Contributor

dirkf commented Jun 6, 2022

I wonder if there is another extractor already written which shared the chave decryption code

Google didn't think so. Probably this thread and the earlier threads at yt-dl and streamlink are the only hits now.

@dirkf
Copy link
Contributor

dirkf commented Jun 6, 2022

It looks like those two outer divs are remnants from Teachable embedding Wistia, ...

Sure. Changing the class-names would have meant changing JS and CSS that used those names; perhaps they didn't even have to change the associated CSS styles.

@Green0Photon
Copy link

Green0Photon commented Jun 6, 2022

Alright, I've figured out how to do #1 (my own #1, not the other set of 3 hotmart stuff).

Take curl -b cookies-cantrill-io.txt https://learn.cantrill.io/courses/aws-certified-developer-associate/lectures/30553576 | grep hotmart_video_player for example, which is a locked one. It will return a line if you're logged in, and won't if you're not. So try curl https://learn.cantrill.io/courses/1101194/lectures/24859593 | grep hotmart_video_player. This returns:

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

The other one is similar, but it has the data-user-id as an actual number.

Then, I found one API call that gets made. So run curl https://learn.cantrill.io/api/v2/hotmart/private_video?attachment_id=44728934. You'll get:

{"video_id":"aZ8M9pOeRp","upload_retries_cap_reached":false,"teachable_application_key":"aa2d356b-e2f0-45e8-9725-e0efc7b5d29c","status":"READY","signature":"yKm61SGt_exqyxuEyXekGCG_11xgJ5kqoZHwOUKIZzIbrzFP2L-W35H6f38gHWWVlCRvi6AsuoU1EPpXyF1FCHHYoiohecHhqMTlTYbIrchU1MHi0tRwuBzxtP6OMLodcg9mCopmCRoLn6Hm4F2di4Y4zQVrw75gPo-HWd0-q1ja02hkEXfsn1Og6PVxgphWx2_qe83xo_A4Ff0JxGzyDoT9rydB00UCPIVU-7xtlgZwL2uPgVWeeH8Ox1DGHNN1Yn_3y8ZBH0jf9KB7uYYNtfT4QhvwJytB4EEFdP_LU5aR7fPEEZylNRSy-LpiR-S27ZDYTWJYWy0R6dXARtNXXA=="}

Here's the thing. Uh. This is actually very insecure. If anyone gives you the attachment id for any nonpublic video, plugging it into the API will give you the signature data and stuff, example of a public video shown above. But for the private video. It doesn't need the user id or anything. Boom, very public. The only thing that hides anything is the fact that a page won't load that div with the attachment-id unless you're logged in for that private video. Yikes.

Anyway. Formatting the above:

{
    "video_id": "aZ8M9pOeRp",
    "upload_retries_cap_reached": false,
    "teachable_application_key": "aa2d356b-e2f0-45e8-9725-e0efc7b5d29c",
    "status": "READY",
    "signature": "yKm61SGt_exqyxuEyXekGCG_11xgJ5kqoZHwOUKIZzIbrzFP2L-W35H6f38gHWWVlCRvi6AsuoU1EPpXyF1FCHHYoiohecHhqMTlTYbIrchU1MHi0tRwuBzxtP6OMLodcg9mCopmCRoLn6Hm4F2di4Y4zQVrw75gPo-HWd0-q1ja02hkEXfsn1Og6PVxgphWx2_qe83xo_A4Ff0JxGzyDoT9rydB00UCPIVU-7xtlgZwL2uPgVWeeH8Ox1DGHNN1Yn_3y8ZBH0jf9KB7uYYNtfT4QhvwJytB4EEFdP_LU5aR7fPEEZylNRSy-LpiR-S27ZDYTWJYWy0R6dXARtNXXA=="
}

So all you need (in python fstring formatting) is f"https://player.hotmart.com/embed/{data['video_id']}?signature={data['signature']}&token={data['teachable_application_key']}". So replacing that, you get https://player.hotmart.com/embed/aZ8M9pOeRp?signature=yKm61SGt_exqyxuEyXekGCG_11xgJ5kqoZHwOUKIZzIbrzFP2L-W35H6f38gHWWVlCRvi6AsuoU1EPpXyF1FCHHYoiohecHhqMTlTYbIrchU1MHi0tRwuBzxtP6OMLodcg9mCopmCRoLn6Hm4F2di4Y4zQVrw75gPo-HWd0-q1ja02hkEXfsn1Og6PVxgphWx2_qe83xo_A4Ff0JxGzyDoT9rydB00UCPIVU-7xtlgZwL2uPgVWeeH8Ox1DGHNN1Yn_3y8ZBH0jf9KB7uYYNtfT4QhvwJytB4EEFdP_LU5aR7fPEEZylNRSy-LpiR-S27ZDYTWJYWy0R6dXARtNXXA==&token=aa2d356b-e2f0-45e8-9725-e0efc7b5d29c. Which will expire soon, though hasn't yet as I type this. Again, this is a public video, so it's fine.

That's step one, really the whole Teachable extractor (excluding metadata instructions and Hotmart instructions) complete.

In summary:

  1. Load given Teachable video page with cookies (or not).
  2. If it's public or if it's private with cookies loaded, there will be a div with a class="hotmart_video_player" on it.
  3. Extract out the attribute data-attachment-id from that div. We'll call it attachment_id.
  4. Call {origin}/api/v2/hotmart/private_video?attachment_id={attachment_id}. You'll get a JSON response dict, we'll parse this and call it data.
  5. Create the hotmart embedded url using f"https://player.hotmart.com/embed/{data['video_id']}?signature={data['signature']}&token={data['teachable_application_key']}".
  6. Pass that to the hotmart extractor we'll create in the later steps. It should give us the video formats and subtitles in return in whatever way via the hls downloader.
  7. Parse the rest of the page for page description and video title. Might be possible to also add course section and number in this step anyway, if you want, since all of this detail is still on the page.
  8. This is the individual video extractor, not the general course extractor aka playlist, but the playlist extractor can be changed to have greater metadata as well if we want.

So now someone just needs to code that. If it ends up being me, I'll probably do it after I figure out the later steps.

I'd also like to see someone can confirm the above summary pattern we can just straight forwardly code for other Teachable sites. Does the same {origin}/api/v2/hotmart/private_video?attachment_id={attachment_id} exist on other sites too? Does it have the same security flaw of sending all necessary data back if all attachment ids have been scraped by someone who's logged in, no cookies necessary?

@hubdows
Copy link

hubdows commented Jan 22, 2023

The beta version still appears to be working well with Teachable. However, the main branch doesn't appear to the have the fix. Would it be possible to get it added?

@Green0Photon
Copy link

@hubdows Yes, but I've been busy without much time to program in my free time. (Meanwhile there's plenty of free phone time, which isn't usable for programming 😢)

If anyone else wants to take up the torch, they may. I just did it because I can't bear consuming lectures without skip silence, and I gotta be able to do it on my phone, often without consuming data, preferably in a better app than the web.

But the current code isn't suitable for merging yet. It's not on master yet, because of some refactor I hadn't finished spending time understanding. And tbh I don't wholly understand some of the extractor stuff, or at least, the way I should be going about doing it. There's no architecture manual or anything.

Besides bringing it up to date, it doesn't support the modern course list page. It supports the old one, which usecase uses at the very least. But not the main Teachable site afaik. It also doesn't support multiple videos on a page properly (and I don't know the best way to support this without it being wonky, which was the case earlier), nor is username and password signin working fully. Difference between Teachable SSO and same site signin. Cookies from browser also doesn't work, but I don't know if that's a general yt-dlp bug or not, nor if it's solved on recent versions or not. Testing isn't good or ready either. I'm sure I'm missing stuff. Ummm... There was definitely some other bug that occasionally popped up, something to do with title or something not showing up in attributes. I can't remember.

There is a Discord. Might join that to ask questions when I next get around to trying to fix shit.

@Anon-Exploiter
Copy link

@Green0Photon Impressive stuff! However, I seem to only be getting the first video whenever a lecture has multiple ones. When getting lectures one by one, should I 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/course_id

and expect to get all the videos from within that one lecture? Or am I doing something wrong?

Same issue, only the first video downloads fine, on the other ones there's an error:

[debug] [generic] Extracting URL: https://
[generic] 8971587: Requesting header
WARNING: [generic] Falling back on generic information extractor.
[generic] 8971587: Downloading webpage
[generic] 8971587: Extracting information
[debug] Looking for video embeds
[debug] [Teachable] Extracting URL: teachable:https://
[Teachable] 8971587: Downloading webpage
ERROR: [Teachable] 8971587: Lecture contents locked. Use --cookies, --cookies-from-browser, --username and --password, or --netrc (teachable) to provide account credentials
  File "/mnt/c/Users/Syed Umar/Downloads/Compressed/yt-dlp-teachable-fix-add-hotmart/yt-dlp-teachable-fix-add-hotmart/yt_dlp/extractor/common.py", line 642, in extract
    ie_result = self._real_extract(url)
  File "/mnt/c/Users/Syed Umar/Downloads/Compressed/yt-dlp-teachable-fix-add-hotmart/yt-dlp-teachable-fix-add-hotmart/yt_dlp/extractor/teachable.py", line 200, in _real_extract
    hotmart_url = self._create_hotmart_url(webpage, video_id, site)
  File "/mnt/c/Users/Syed Umar/Downloads/Compressed/yt-dlp-teachable-fix-add-hotmart/yt-dlp-teachable-fix-add-hotmart/yt_dlp/extractor/teachable.py", line 166, in _create_hotmart_url
    self.raise_login_required('Lecture contents locked')
  File "/mnt/c/Users/Syed Umar/Downloads/Compressed/yt-dlp-teachable-fix-add-hotmart/yt-dlp-teachable-fix-add-hotmart/yt_dlp/extractor/common.py", line 1100, in raise_login_required
    raise ExtractorError(msg, expected=True)

It seems it can't use the session cookies file for other videos.

@g33khubber

This comment was marked as spam.

@FallingLights
Copy link

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

@pukkandan

This comment was marked as outdated.

@pukkandan pukkandan removed the patch-available There is patch available that should fix this issue. Someone needs to make a PR with it label Mar 21, 2023
@Biepa
Copy link

Biepa commented Apr 27, 2023

Thanks to everybody trying to help with this problem. Unfortunately my knowledge about this stuff is limited.
Downloading a whole course didn't worked anymore with @Green0Photon's fork. but fortunately it's still able to download single videos. So I did what I could to find a way for downloading a complete course somehow (there may be more easy/clever ways, but it worked for my case). So for anybody trying to download a course but not having the knowledge to fix the issue directly in yt-dlp, this may help you.

I basically read the IDs needed for every video from the plain html and then downloaded them one by one.

  1. Download @Green0Photon's fork and extract it.
  2. Place the below bash script in the same directory where the "yt-dlp-teachable-fix-add-hotmart" folder is located.
  3. Then run the script (example at the beginning of the script file).
    Make sure that the URL you are using is showing a view like this one, where the sidebar with all the lectures is visible:
Details

grafik

The script will create a folder with the name you provided and in there have a folder for each chapter, while prefixing every video with a number in the order they were read from the page.

#!/bin/bash
# command to run: ./thisScript.sh <file with your cookies> <url of the first video in the course> <name of the course>
# example command: ./thisScript.sh cookies.txt https://domainOfYourCourse.com/courses/1234567/lectures/7654321 "My course"
# this also worked for me when the course has a string in the URL instead of a number

mkdir "$3"

# the base url is needed to add ids one by one later -> https://domainOfYourCourse.com/courses/1234567/
baseUrl=$(echo "$2" | awk -F "lectures" '{print $1}') 

# sending a curl request to the site with your cookies, grepping all available video IDs and removing duplicates from the list
listOfVideoIds=$(curl -b "$1" "$2" 2>/dev/null | grep -Po "(?<=data-lecture-id=\")\d*" | awk '!visited[$0]++') #

# adding a complete link for every video to a text file to. With this the "-a" option of yt-dlp can be used, allowing to give each video in the list a unique number
for id in $listOfVideoIds
do 
  echo "$baseUrl"lectures/"$id" >> allVideos.txt
done

# the actual downloading
yt-dlp-teachable-fix-add-hotmart/yt-dlp.sh --cookies "$1" --verbose -N 32 -o "$3/%(chapter_number)s-%(chapter)s/%(autonumber)03d-%(title)s.%(ext)s" -a allVideos.txt

rm allVideos.txt

@ahm750

This comment was marked as spam.

@nam-vu
Copy link

nam-vu commented Jun 10, 2023

Just chiming in to say this workaround is still working:

You need:

  1. urls.txt Download the course page's html, regex all lecture links e.g. https://example.teachable.com/courses/123456/lectures/12345678
  2. cookies.txt I used the Chrome extension "Get cookies.txt LOCALLY"

And run @Green0Photon's hotfix:

./yt-dlp.sh -a urls.txt \
--cookies cookies.txt \
--verbose \
-N 32 \
-o "../👉️COURSE NAME👈️/Chapter %(chapter_number)s – %(chapter)s/[%(autonumber)03d] %(title)s.%(ext)s"

@g33khubber

This comment was marked as off-topic.

@Biepa
Copy link

Biepa commented Jun 11, 2023

@g33khubber Have a look at my curl command in this response:
#3564 (comment)

@CarlosJoseChaconChavarria

This comment was marked as off-topic.

@FallingLights

This comment was marked as off-topic.

@CarlosJoseChaconChavarria

This comment was marked as off-topic.

@pukkandan
Copy link
Member

Ask support for third-party tools in their repo

@pukkandan
Copy link
Member

Re #3564 (comment): While not yet standardized, the best way to handle this seems to be to define a custom protocol handler. See AbemaTVIE

@bruhcephalus
Copy link

bruhcephalus commented Jul 21, 2023

Great stuff, @Green0Photon!

I've been trying to download some Hotmart videos from Teachable with your fork:

git clone https://github.com/Green0Photon/yt-dlp.git
git checkout teachable-fix-add-hotmart
./yt-dlp.sh --cookies cookies.txt "<URL I took from iframe src>"

Getting this error (3rd line):

[HotmartEmbed] aZ8MjkgORp: Downloading webpage
[HotmartEmbed] aZ8MjkgORp: Downloading m3u8 information
ERROR: [HotmartEmbed] aZ8MjkgORp: Missing "title" field in extractor result

@Abdess
Copy link

Abdess commented Jul 22, 2023

@bruhcephalus the changes made by @Green0Photon are being integrated here: #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. :)

@bruhcephalus
Copy link

@Abdess Your fork worked for me 😃

@AlphaTurtlee
Copy link

@Abdess how can I run your fork on windows? Do I need to install bash?

@nocomputeruser
Copy link

nocomputeruser commented Aug 21, 2023

@Abdess -- Looking to see how to build it as well to work on windows please :).

EDIT --- I downloaded and installed cygwin64 and run the shell with it. I was able to download 200 courses successfully from the teachable site.

@anunnakisumerian369
Copy link

hello greetings to all and please could someone help me how to use the script from https://github.com/Abdess/yt-dlp/tree/teachable-fix-add-hotmart to download courses from hotmart, thank you very much

@CodeSpartan
Copy link

CodeSpartan commented Nov 12, 2023

@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!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
can-share-account Someone is willing to provide account details for development site-bug Issue with a specific website
Projects
None yet