Repository: yt-dlp/yt-dlp

Issue:
[Site Feature]  Please support watch.angelstudios.com (Angel Studios)
<!--

######################################################################
  WARNING!
  IGNORING THE FOLLOWING TEMPLATE WILL RESULT IN ISSUE CLOSED AS INCOMPLETE
######################################################################

-->


## Checklist

<!--
Carefully read and work through this check list in order to prevent the most common mistakes and misuse of yt-dlp:
- First of, make sure you are using the latest version of yt-dlp. Run `yt-dlp --version` and ensure your version is 2021.10.10. If it's not, see https://github.com/yt-dlp/yt-dlp#update on how to update. Issues with outdated version will be REJECTED.
- Make sure that all provided video/audio/playlist URLs (if any) are alive and playable in a browser.
- Make sure that site you are requesting is not dedicated to copyright infringement. yt-dlp does not support such sites. In order for site support request to be accepted all provided example URLs should not violate any copyrights.
- Search the bugtracker for similar site support requests: https://github.com/yt-dlp/yt-dlp/issues. DO NOT post duplicates.
- Read "opening an issue" section in CONTRIBUTING.md: https://github.com/yt-dlp/yt-dlp/blob/master/CONTRIBUTING.md#opening-an-issue
- Finally, confirm all RELEVANT tasks from the following by putting x into all the boxes like this [x] (Dont forget to delete the empty space)
-->

- [x ] I'm reporting a new site support request
- [x ] I've verified that I'm running yt-dlp version **2021.10.10**
- [x] I've checked that all provided URLs are alive and playable in a browser
- [x] I've checked that none of provided URLs violate any copyrights
- [x] The provided URLs do not contain any DRM to the best of my knowledge
- [x] I've searched the bugtracker for similar site support requests including closed ones
- [x] I've read the opening an issue section in CONTRIBUTING.md
- [x] I have given an appropriate title to the issue


## Example URLs

<!--
Provide all kinds of example URLs support for which should be included. Replace following example URLs by yours.
-->


    Single video: https://watch.angelstudios.com/thechosen/watch?vid=S1:E1
    Single video: https://watch.angelstudios.com/thechosen/watch?vid=S1:E2



## Description

yt-dlp https://watch.angelstudios.com/thechosen/watch?vid=S1:E1
[generic] watch?vid=S1:E1: Requesting header
WARNING: [generic] Falling back on generic information extractor.
[generic] watch?vid=S1:E1: Downloading webpage
[generic] watch?vid=S1:E1: Extracting information
ERROR: [generic] Unsupported URL: https://watch.angelstudios.com/thechosen/watch?vid=S1:E1






Discussion:
the site only has 1 series
The base url redirects to `https://watch.angelstudios.com/thechosen`
Is that all u want?
Thanks for the follow-up. At this point This is the only series. I expect there could be more later. However The Chosen will be going for 8 seasons if I remember.  Closed captions support a large number of options.

For documentation this is the same request as someone else put in on the Youtube-DL side.
https://github.com/ytdl-org/youtube-dl/issues/29455

The Chosen at Angel Studios actually has two seasons already on their website, a third is being financed and a total of seven seasons are planned.

The website user interface is extremely awkward, so it is not surprising people don't see it.  The URL for the second season is still https://watch.angelstudios.com/thechosen: you need to scroll down to see the different seasons.  Example URLs for the videos themselves:

* https://watch.angelstudios.com/thechosen/watch?vid=S2:E4
* https://watch.angelstudios.com/thechosen/watch?vid=S2:E8
Here's some info that may be handy. I still don't know how to make it work w/ yt-dlp, though:
```
curl 'https://chosen-hydra.vidangel.com/graphql' \
  -H 'authority: chosen-hydra.vidangel.com' \
  -H 'pragma: no-cache' \
  -H 'cache-control: no-cache' \
  -H 'sec-ch-ua: " Not A;Brand";v="99", "Chromium";v="343", "Microsoft Edge";v="44"' \
  -H 'dnt: 1' \
  -H 'sec-ch-ua-mobile: ?0' \
  -H 'user-agent: Mozilla/5.0 (Macintosh; Intel Mac OS X) AppleWebKit(KHTML, like Gecko) Chrome/Safari/Edg/' \
  -H 'content-type: application/json' \
  -H 'accept: */*' \
  -H 'x-rid: 727cb4f7-3a69-43c5-97c1-e' \
  -H 'sec-ch-ua-platform: "macOS"' \
  -H 'origin: https://watch.angelstudios.com' \
  -H 'sec-fetch-site: cross-site' \
  -H 'sec-fetch-mode: cors' \
  -H 'sec-fetch-dest: empty' \
  -H 'referer: https://watch.angelstudios.com/' \
  -H 'accept-language: en-US,en;q=0.9' \
  --data-raw '{"operationName":"videoList","variables":{},"query":"query videoList {\n  project(slug: \"the-chosen\") {\n    seasons {\n      id\n      episodes {\n        id\n        episodeNumber\n        subtitle\n        description\n        name\n        poster\n        source {\n          captions\n          credits\n          duration\n          url\n          __typename\n        }\n        upNext {\n          id\n          __typename\n        }\n        __typename\n      }\n      __typename\n    }\n    __typename\n  }\n}\n"}' \
  --compressed
```

run this, get this:

```
{"data":{"project":{"__typename":"Project","seasons":[{"__typename":"Season","episodes":[{"__typename":"Episode","description":"The story of the birth of Jesus as told through the eyes of Mary and Joseph","episodeNumber":-1,"id":"S1:E0","name":"Our Big Christmas Special","poster":"https://images.angelstudios.com/image/upload/c_scale,q_auto:good,w_600/v1639614257/angel-app/the-chosen/livestreams/2021-12-15.png","source":{"__typename":"EpisodeSource","captions":null,"credits":6812,"duration":7086,"url":"https://video.thechosen.tv/TheChosen_S02_E09_P_R05_LENG_ACPM20_HD-20211217_VvKzueLA.m3u8"},"subtitle":"Christmas with the Chosen: The Messengers","upNext":{"__typename":"Episode","id":"the-chosen-shepherd-trailer-with-cta-season-1"}},{"__typename":"Episode","description":"The original Christmas episode that became the pilot episode for The Chosen, “The Shepherd” tells the nativity story from the perspective of the shepherds. Be sure to watch this before our upcoming Christmas Special!","episodeNumber":0,"id":"the-chosen-shepherd-trailer-with-cta-season-1","name":"Christmas pilot episode","poster":"https://images.angelstudios.com/image/upload/c_scale,h_377,q_auto:good,w_600/v1637003093/angel-app/the-chosen/videos/bonus/TheShepherdMessengerTemp.png","source":{"__typename":"EpisodeSource","captions":null,"credits":1187,"duration":1248,"url":"https://video.thechosen.tv/The_Chosen_The_Shepherd_Christmas_trailer_2021111.m3u8"},"subtitle":"The Shepherd","upNext":{"__typename":"Episode","id":"S1:E1"}},{"__typename":"Episode","description":"Two brothers struggle with their tax debts to Rome while a woman in the Red Quarter wrestles with her demons.","episodeNumber":1,"id":"S1:E1","name":"Episode 1","poster":"https://content.vidangel.com/chosen/andrew_sitting.png","source":{"__typename":"EpisodeSource","captions":"https://video.thechosen.tv/captions/The_Chosen_S01E01.vtt","credits":3137,"duration":3276,"url":"https://video.thechosen.tv/The_Chosen_S01E01_muxed-20211201_znyhj-3s.m3u8"},"subtitle":"I Have Called You By Name","upNext":{"__typename":"Episode","id":"S1:E2"}},{"__typename":"Episode","description":"Matthew validates Simon's claims with Praetor Quintus, Nicodemus investigates the miracle reported in the Red Quarter, and Mary receives surprise guests at her Shabbat dinner.","episodeNumber":2,"id":"S1:E2","name":"Episode 2","poster":"https://content.vidangel.com/chosen/mary_veil.png","source":{"__typename":"EpisodeSource","captions":"https://video.thechosen.tv/captions/The_Chosen_S01E02.vtt","credits":2204,"duration":2339,"url":"https://video.thechosen.tv/The_Chosen_S01E02_muxed-20211201_kkhZdZ5n.m3u8"},"subtitle":"Shabbat","upNext":{"__typename":"Episode","id":"S1:E3"}},{"__typename":"Episode","description":"Jesus befriends and teaches the group of children who discover His camp on the outskirts of Capernaum.","episodeNumber":3,"id":"S1:E3","name":"Episode 3","poster":"https://content.vidangel.com/chosen/kiddos.png","source":{"__typename":"EpisodeSource","captions":"https://video.thechosen.tv/captions/The_Chosen_S01E03.vtt","credits":1703,"duration":1827,"url":"https://video.thechosen.tv/The_Chosen_S01E03_muxed-20211201_V6v0lZRM.m3u8"},"subtitle":"Jesus Loves The Little Children","upNext":{"__typename":"Episode","id":"S1:E4"}},{"__typename":"Episode","description":"With his life and family under threat from Rome, Simon spends one last night fishing in a desperate attempt to square his debts.  Andrew spots a familiar face waiting for them on the shores of Galilee.","episodeNumber":4,"id":"S1:E4","name":"Episode 4","poster":"https://content.vidangel.com/chosen/dark_boat.png","source":{"__typename":"EpisodeSource","captions":"https://video.thechosen.tv/captions/The_Chosen_S01E04.vtt","credits":2804,"duration":3337,"url":"https://video.thechosen.tv/The_Chosen_S01E04_muxed-20211201_DOOrNvxL.m3u8"},"subtitle":"The Rock On Which It Is Built","upNext":{"__typename":"Episode","id":"S1:E5"}},{"__typename":"Episode","description":"Nicodemus interrogates John the Baptizer while Jesus and his students make their way to a wedding celebration in Cana.  When the wine runs low, Mary asks her son to intervene on behalf of the bridegroom's family.","episodeNumber":5,"id":"S1:E5","name":"Episode 5","poster":"https://content.vidangel.com/chosen/matt_roman.png","source":{"__typename":"EpisodeSource","captions":null,"credits":3156,"duration":3278,"url":"https://video.thechosen.tv/The_Chosen_S01E05_muxed-20211201_0eoyMljD.m3u8"},"subtitle":"The Wedding Gift","upNext":{"__typename":"Episode","id":"S1:E6"}},{"__typename":"Episode","description":"After witnessing the healing of a leper on the road to Capernaum, a woman forces her paralytic friend through the crowd to meet Jesus.","episodeNumber":6,"id":"S1:E6","name":"Episode 6","poster":"https://content.vidangel.com/chosen/egyptian.png","source":{"__typename":"EpisodeSource","captions":null,"credits":2988,"duration":3119,"url":"https://video.thechosen.tv/The_Chosen_S01E06_muxed-20211201_u7D0IXQX.m3u8"},"subtitle":"Indescribable Compassion","upNext":{"__typename":"Episode","id":"S1:E7"}},{"__typename":"Episode","description":"Matthew struggles to reconcile the miracles he has witnessed with reality. Nicodemus meets with Jesus by night.","episodeNumber":7,"id":"S1:E7","name":"Episode 7","poster":"https://content.vidangel.com/chosen/jesus_nico.png","source":{"__typename":"EpisodeSource","captions":null,"credits":2112,"duration":2230,"url":"https://video.thechosen.tv/The_Chosen_S01E07_muxed-20211201_GPE7ycLh.m3u8"},"subtitle":"Invitations","upNext":{"__typename":"Episode","id":"S1:E8"}},{"__typename":"Episode","description":"Jesus and His students complete their preparations and leave Capernaum for Samaria.  Jesus meets with a suffering woman at Jacob's Well and announces that He is the Messiah.","episodeNumber":8,"id":"S1:E8","name":"Episode 8","poster":"https://content.vidangel.com/chosen/peter_glance.png","source":{"__typename":"EpisodeSource","captions":null,"credits":3073,"duration":3300,"url":"https://video.thechosen.tv/The_Chosen_S01E08_muxed-20211201_exXD6mjH.m3u8"},"subtitle":"I Am He","upNext":{"__typename":"Episode","id":"S2:E1"}}],"id":"1"},{"__typename":"Season","episodes":[{"__typename":"Episode","description":"Tension builds among the disciples as they wrestle with the increasing fame of Jesus in Samaria. Jesus rebukes Big James and John for their prejudice and, after a near-violent encounter, gives them a new nickname.","episodeNumber":1,"id":"S2:E1","name":"Episode 1","poster":"https://content.vidangel.com/chosen/EP201_Thumbnail.jpeg","source":{"__typename":"EpisodeSource","captions":"https://video.thechosen.tv/captions/The_Chosen_S2E01.vtt","credits":3792,"duration":3881,"url":"https://video.thechosen.tv/The_Chosen_S02E01_muxed-20211208_hXmjYh0_.m3u8"},"subtitle":"Thunder","upNext":{"__typename":"Episode","id":"S2:E2"}},{"__typename":"Episode","description":"A mysterious visitor seeks to meet Jesus, but the disciples are hesitant. Tension builds between Simon and Matthew. As the group heads to a new city, word arrives that Jesus’ fame is growing.","episodeNumber":2,"id":"S2:E2","name":"Episode 2","poster":"https://content.vidangel.com/chosen/EP202_Thumbnail.jpeg","source":{"__typename":"EpisodeSource","captions":"https://video.thechosen.tv/captions/The_Chosen_S02E02.vtt","credits":3021,"duration":3157,"url":"https://video.thechosen.tv/The_Chosen_S02E02_muxed-20211206_iTpy_cpv.m3u8"},"subtitle":"I Saw You","upNext":{"__typename":"Episode","id":"S2:E3"}},{"__typename":"Episode","description":"A long, exhausting day turns into night as the disciples help Jesus deal with a large crowd hoping to be healed. Around the campfire, over dinner, the group tried to get to know each other better. But tensions erupt.","episodeNumber":3,"id":"S2:E3","name":"Episode 3","poster":"https://content.vidangel.com/chosen/EP203_Thumbnail.jpeg","source":{"__typename":"EpisodeSource","captions":"https://video.thechosen.tv/captions/The_Chosen_S02E03.vtt","credits":2177,"duration":2217,"url":"https://video.thechosen.tv/The_Chosen_S02E03_muxed-20211206_h1_4C2vw.m3u8"},"subtitle":"Matthew 4:24","upNext":{"__typename":"Episode","id":"S2:E4"}},{"__typename":"Episode","description":"As Jesus and the disciples head to Jerusalem to celebrate the Feast of Tabernacles, a new enemy follows them, while a familiar enemy awaits.","episodeNumber":4,"id":"S2:E4","name":"Episode 4","poster":"https://video.thechosen.tv/ThumbS2E4tight.jpg","source":{"__typename":"EpisodeSource","captions":null,"credits":3365,"duration":3520,"url":"https://video.thechosen.tv/The_Chosen_S02E04_muxed-20211207_-RF79BJt.m3u8"},"subtitle":"The Perfect Opportunity","upNext":{"__typename":"Episode","id":"S2:E5"}},{"__typename":"Episode","description":"After stirring the water, Jesus is now pursued by Simon the Zealot, Atticus, and Shmuel. John the Baptizer visits the group and tells Jesus his dangerous mission. Meanwhile, back at the camp, Mary Magdalene is devastated by an encounter with a mysterious and dangerous stranger.","episodeNumber":5,"id":"S2:E5","name":"Episode 5","poster":"https://video.thechosen.tv/CHO_EP205_thumbnail.jpeg","source":{"__typename":"EpisodeSource","captions":null,"credits":2697,"duration":2864,"url":"https://video.thechosen.tv/The_Chosen_S02E05_muxed-20211207_7Bu5C5vW.m3u8"},"subtitle":"Spirit","upNext":{"__typename":"Episode","id":"S2:E6"}},{"__typename":"Episode","description":"Matthew and Simon try to get along as they search for Mary in dark places. The group fears for Mary as they struggle with lack of food and bad news about John the Baptist. Meanwhile, Jesus continues to upset Pharisees across multiple regions.","episodeNumber":6,"id":"S2:E6","name":"Episode 6","poster":"https://content.vidangel.com/EP206_Thumbnail.jpeg","source":{"__typename":"EpisodeSource","captions":null,"credits":2524,"duration":2675,"url":"https://video.thechosen.tv/The_Chosen_S02E06_muxed-20211207_eAIybwfZ.m3u8"},"subtitle":"Unlawful","upNext":{"__typename":"Episode","id":"S2:E7"}},{"__typename":"Episode","description":"After learning of Jesus' whereabouts, Quintus sends Gaius to arrest him. The disciples lose control as they argue about how to respond. While seeking information about where Jesus has been taken, Andrew and Philip encounter old friends.","episodeNumber":7,"id":"S2:E7","name":"Episode 7","poster":"https://video.thechosen.tv/EP207-Primary-Thumbnail.jpeg","source":{"__typename":"EpisodeSource","captions":null,"credits":2430,"duration":2579,"url":"https://video.thechosen.tv/The_Chosen_S02E07_muxed-20211207_KhMFlbS7.m3u8"},"subtitle":"Reckoning","upNext":{"__typename":"Episode","id":"S2:E8"}},{"__typename":"Episode","description":"While Jesus and Matthew prepare the content of the big sermon, the disciples spread the word while fighting amongst themselves. A high-ranking Sanhedrin member is shocked by Shmuel’s reports on Jesus. Thousands arrive for the sermon, including familiar faces.","episodeNumber":8,"id":"S2:E8","name":"Episode 8","poster":"https://video.thechosen.tv/EP208-Primary_Thumbnail.jpg","source":{"__typename":"EpisodeSource","captions":null,"credits":3530,"duration":3409,"url":"https://video.thechosen.tv/The_Chosen_S02E08_muxed-20211206_Vs_Se2-F.m3u8"},"subtitle":"Beyond Mountains","upNext":null}],"id":"2"}]}}} 
```
It seems like yt-dlp uses other `graphql` sources, so I'm sure this could be adopted. But quite frankly, one could just grab all the m3u8 urls and yt-dlp them if one wanted.
They're letting people watch it for free and they aren't selling blu-rays of it and it's crowdfunded "paid forward." Why don't they just let people download it?

I am genuinely afraid that Christian stuff like this is going to get effectively banned from the Internet in a few years like child porn is now so I want offline backups.
I was able to download from the result info given by @jschwalbe, but I am simply unable to run his command. I've tried on the GraphQL endpoint itself and even executing the curl manually, but to no avail.

Any help on how to re-run that command?

EDIT: Like this
`query videoList {
  project(slug: "the-chosen") {
  seasons {
  	id
    episodes {
    	id
        episodeNumber
        subtitle
        description
        name
        poster
        source {
        	captions
            credits
            duration
            url
            __typename
            }       
        upNext {
        	id
            __typename
            }
        __typename
        }
    __typename
    }
__typename
}
}`
Just run `yt-dlp url.m3u8` for each URL I posted above
Thank you @jschwalbe, got. Even made a little script to compile the VTT subtitle files. Have a nice day!
> Thank you @jschwalbe, got. Even made a little script to compile the VTT subtitle files. Have a nice day!

Could you share what you did there?
It's on PHP, not on YT-code, you want it?
I tried downloading

The_Chosen_S02E07
The_Chosen_S02E08

with the Links provided by @jschwalbe with yt-dlp url.m3u8
but I'm getting the Video with only the Portuguese audio track.

when I tell yt-dlp to get the Englisch Audio track, yt-dlp only gets the Audio Track and ignores my request to get the Video Portion

yt-dlp -f aud-English "bv*[height<=480]+ba/b[height<=480] / wv*+ba/w"

when I try
yt-dlp -f "bv*[height<=480]+ba/b[height<=480] / wv*+ba/w" aud-English

again I get the Video with only the Portuguese audio track.


Several of the videos include a "pre-roll" now, which seems to throw off yt-dlp - it only grabs the audio track for the pre-roll, and not for the episode.


Example:
https://video.thechosen.tv/temp/The_Chosen_208_CTA_Preroll_2022-03-04_13-50-47/The_Chosen_S02E08_with_CTA.m3u8




Resolving patch:
diff --git a/yt_dlp/extractor/_extractors.py b/yt_dlp/extractor/_extractors.py
--- a/yt_dlp/extractor/_extractors.py
+++ b/yt_dlp/extractor/_extractors.py
@@ -60,6 +60,7 @@
     AmericasTestKitchenIE,
     AmericasTestKitchenSeasonIE,
 )
+from .angel import AngelIE
 from .animeondemand import AnimeOnDemandIE
 from .anvato import AnvatoIE
 from .aol import AolIE
diff --git a/yt_dlp/extractor/angel.py b/yt_dlp/extractor/angel.py
new file mode 100644
--- /dev/null
+++ b/yt_dlp/extractor/angel.py
@@ -0,0 +1,56 @@
+import re
+
+from .common import InfoExtractor
+from ..utils import url_or_none, merge_dicts
+
+
+class AngelIE(InfoExtractor):
+    _VALID_URL = r'https?://(?:www\.)?angel\.com/watch/(?P<series>[^/?#]+)/episode/(?P<id>[\w-]+)/season-(?P<season_number>\d+)/episode-(?P<episode_number>\d+)/(?P<title>[^/?#]+)'
+    _TESTS = [{
+        'url': 'https://www.angel.com/watch/tuttle-twins/episode/2f3d0382-ea82-4cdc-958e-84fbadadc710/season-1/episode-1/when-laws-give-you-lemons',
+        'md5': '4734e5cfdd64a568e837246aa3eaa524',
+        'info_dict': {
+            'id': '2f3d0382-ea82-4cdc-958e-84fbadadc710',
+            'ext': 'mp4',
+            'title': 'Tuttle Twins Season 1, Episode 1: When Laws Give You Lemons',
+            'description': 'md5:73b704897c20ab59c433a9c0a8202d5e',
+            'thumbnail': r're:^https?://images.angelstudios.com/image/upload/angel-app/.*$',
+            'duration': 1359.0
+        }
+    }, {
+        'url': 'https://www.angel.com/watch/the-chosen/episode/8dfb714d-bca5-4812-8125-24fb9514cd10/season-1/episode-1/i-have-called-you-by-name',
+        'md5': 'e4774bad0a5f0ad2e90d175cafdb797d',
+        'info_dict': {
+            'id': '8dfb714d-bca5-4812-8125-24fb9514cd10',
+            'ext': 'mp4',
+            'title': 'The Chosen Season 1, Episode 1: I Have Called You By Name',
+            'description': 'md5:aadfb4827a94415de5ff6426e6dee3be',
+            'thumbnail': r're:^https?://images.angelstudios.com/image/upload/angel-app/.*$',
+            'duration': 3276.0
+        }
+    }]
+
+    def _real_extract(self, url):
+        video_id = self._match_id(url)
+        webpage = self._download_webpage(url, video_id)
+
+        json_ld = self._search_json_ld(webpage, video_id)
+
+        formats, subtitles = self._extract_m3u8_formats_and_subtitles(
+            json_ld.pop('url'), video_id, note='Downloading HD m3u8 information')
+
+        info_dict = {
+            'id': video_id,
+            'title': self._og_search_title(webpage),
+            'description': self._og_search_description(webpage),
+            'formats': formats,
+            'subtitles': subtitles
+        }
+
+        # Angel uses cloudinary in the background and supports image transformations.
+        # We remove these transformations and return the source file
+        base_thumbnail_url = url_or_none(self._og_search_thumbnail(webpage)) or json_ld.pop('thumbnails')
+        if base_thumbnail_url:
+            info_dict['thumbnail'] = re.sub(r'(/upload)/.+(/angel-app/.+)$', r'\1\2', base_thumbnail_url)
+
+        return merge_dicts(info_dict, json_ld)


Test patch: