{"id":13431223,"url":"https://github.com/Tyrrrz/YoutubeExplode","last_synced_at":"2025-03-16T11:31:19.365Z","repository":{"id":39701480,"uuid":"65284422","full_name":"Tyrrrz/YoutubeExplode","owner":"Tyrrrz","description":"Abstraction layer over YouTube's internal API","archived":false,"fork":false,"pushed_at":"2025-03-03T19:47:07.000Z","size":1862,"stargazers_count":3128,"open_issues_count":7,"forks_count":512,"subscribers_count":97,"default_branch":"master","last_synced_at":"2025-03-12T05:00:36.882Z","etag":null,"topics":["dotnet","dotnet-core","dotnet-standard","download","downloader","parser","reverse-engineering","youtube"],"latest_commit_sha":null,"homepage":"","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Tyrrrz.png","metadata":{"files":{"readme":"Readme.md","changelog":null,"contributing":null,"funding":null,"license":"License.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null},"funding":{"github":"Tyrrrz","patreon":"Tyrrrz","custom":["tyrrrz.me/donate"]}},"created_at":"2016-08-09T09:59:54.000Z","updated_at":"2025-03-12T03:02:21.000Z","dependencies_parsed_at":"2022-07-14T10:31:24.257Z","dependency_job_id":"6d412297-f68e-4fba-ab5c-c76e65466539","html_url":"https://github.com/Tyrrrz/YoutubeExplode","commit_stats":{"total_commits":1001,"total_committers":49,"mean_commits":"20.428571428571427","dds":0.6013986013986015,"last_synced_commit":"119e21ffa774b5d7aff94e0a2dd7305e0e7cba09"},"previous_names":[],"tags_count":137,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Tyrrrz%2FYoutubeExplode","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Tyrrrz%2FYoutubeExplode/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Tyrrrz%2FYoutubeExplode/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Tyrrrz%2FYoutubeExplode/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Tyrrrz","download_url":"https://codeload.github.com/Tyrrrz/YoutubeExplode/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":243862797,"owners_count":20360212,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["dotnet","dotnet-core","dotnet-standard","download","downloader","parser","reverse-engineering","youtube"],"created_at":"2024-07-31T02:01:01.445Z","updated_at":"2025-03-16T11:31:18.770Z","avatar_url":"https://github.com/Tyrrrz.png","language":"C#","readme":"# YoutubeExplode\n\n[![Status](https://img.shields.io/badge/status-maintenance-ffd700.svg)](https://github.com/Tyrrrz/.github/blob/master/docs/project-status.md)\n[![Made in Ukraine](https://img.shields.io/badge/made_in-ukraine-ffd700.svg?labelColor=0057b7)](https://tyrrrz.me/ukraine)\n[![Build](https://img.shields.io/github/actions/workflow/status/Tyrrrz/YoutubeExplode/main.yml?branch=master)](https://github.com/Tyrrrz/YoutubeExplode/actions)\n[![Coverage](https://img.shields.io/codecov/c/github/Tyrrrz/YoutubeExplode/master)](https://codecov.io/gh/Tyrrrz/YoutubeExplode)\n[![Version](https://img.shields.io/nuget/v/YoutubeExplode.svg)](https://nuget.org/packages/YoutubeExplode)\n[![Downloads](https://img.shields.io/nuget/dt/YoutubeExplode.svg)](https://nuget.org/packages/YoutubeExplode)\n[![Discord](https://img.shields.io/discord/869237470565392384?label=discord)](https://discord.gg/2SUWKFnHSm)\n[![Fuck Russia](https://img.shields.io/badge/fuck-russia-e4181c.svg?labelColor=000000)](https://twitter.com/tyrrrz/status/1495972128977571848)\n\n\u003ctable\u003e\n    \u003ctr\u003e\n        \u003ctd width=\"99999\" align=\"center\"\u003eDevelopment of this project is entirely funded by the community. \u003cb\u003e\u003ca href=\"https://tyrrrz.me/donate\"\u003eConsider donating to support!\u003c/a\u003e\u003c/b\u003e\u003c/td\u003e\n    \u003c/tr\u003e\n\u003c/table\u003e\n\n\u003cp align=\"center\"\u003e\n    \u003cimg src=\"favicon.png\" alt=\"Icon\" /\u003e\n\u003c/p\u003e\n\n**YoutubeExplode** is a library that provides an interface to query metadata of YouTube videos, playlists and channels, as well as to resolve and download video streams and closed caption tracks.\nBehind a layer of abstraction, this library works by scraping raw page data and exploiting reverse-engineered internal endpoints.\n\n\u003e 📝 Interested in the inner workings of this library?\n\u003e See the [Reverse-Engineering YouTube](https://tyrrrz.me/blog/reverse-engineering-youtube-revisited) article.\n\n**Extension packages**:\n\n- [YoutubeExplode.Converter](YoutubeExplode.Converter) — provides an interface to download and convert videos using FFmpeg\n\n## Terms of use\u003csup\u003e[[?]](https://github.com/Tyrrrz/.github/blob/master/docs/why-so-political.md)\u003c/sup\u003e\n\nBy using this project or its source code, for any purpose and in any shape or form, you grant your **implicit agreement** to all the following statements:\n\n- You **condemn Russia and its military aggression against Ukraine**\n- You **recognize that Russia is an occupant that unlawfully invaded a sovereign state**\n- You **support Ukraine's territorial integrity, including its claims over temporarily occupied territories of Crimea and Donbas**\n- You **reject false narratives perpetuated by Russian state propaganda**\n\nTo learn more about the war and how you can help, [click here](https://tyrrrz.me/ukraine). Glory to Ukraine! 🇺🇦\n\n## Install\n\n- 📦 [NuGet](https://nuget.org/packages/YoutubeExplode): `dotnet add package YoutubeExplode`\n\n## Screenshots\n\n![demo](.assets/demo.png)\n\n## Usage\n\n**YoutubeExplode** exposes its functionality through a single entry point — the `YoutubeClient` class.\nCreate an instance of this class and use the provided operations on `Videos`, `Playlists`, `Channels`, and `Search` properties to send requests.\n\n### Videos\n\n#### Retrieving video metadata\n\nTo retrieve the metadata associated with a YouTube video, call `Videos.GetAsync(...)`:\n\n```csharp\nusing YoutubeExplode;\n\nvar youtube = new YoutubeClient();\n\n// You can specify either the video URL or its ID\nvar videoUrl = \"https://youtube.com/watch?v=u_yIGGhubZs\";\nvar video = await youtube.Videos.GetAsync(videoUrl);\n\nvar title = video.Title; // \"Collections - Blender 2.80 Fundamentals\"\nvar author = video.Author.ChannelTitle; // \"Blender\"\nvar duration = video.Duration; // 00:07:20\n```\n\n#### Downloading video streams\n\nEvery YouTube video has a number of streams available, differing in containers, video quality, bitrate, framerate, and other parameters.\nAdditionally, the streams are further divided into 3 categories based on their content:\n\n- ~~Muxed streams — contain both video and audio~~ (no longer provided by YouTube)\n- Audio-only streams — contain only audio\n- Video-only streams — contain only video\n\n\u003e **Warning**:\n\u003e Muxed streams contain both audio and video, but these streams are limited in quality (up to 720p30).\n\u003e To download the video in the highest available quality, you will need to resolve the best audio-only and video-only streams separately and then mux them together.\n\u003e The muxing process can be performed using FFmpeg with the help of the [**YoutubeExplode.Converter**](YoutubeExplode.Converter) package.\n\n\u003e **Warning**:\n\u003e Muxed streams are deprecated by YouTube and are not guaranteed to be available for every video.\n\u003e If possible, avoid relying on them too much and instead perform muxing manually using the provided audio-only and video-only streams.\n\nYou can request the manifest that lists all available streams for a particular video by calling `Videos.Streams.GetManifestAsync(...)`:\n\n```csharp\nusing YoutubeExplode;\n\nvar youtube = new YoutubeClient();\n\nvar videoUrl = \"https://youtube.com/watch?v=u_yIGGhubZs\";\nvar streamManifest = await youtube.Videos.Streams.GetManifestAsync(videoUrl);\n```\n\nOnce the manifest is obtained, you can filter through the streams and identify the ones you're interested in:\n\n```csharp\nusing YoutubeExplode;\nusing YoutubeExplode.Videos.Streams;\n\n// ...\n\n// Get the highest bitrate audio-only stream\nvar streamInfo = streamManifest.GetAudioOnlyStreams().GetWithHighestBitrate();\n\n// ...or the highest quality MP4 video-only stream\nvar streamInfo = streamManifest\n    .GetVideoOnlyStreams()\n    .Where(s =\u003e s.Container == Container.Mp4)\n    .GetWithHighestVideoQuality()\n```\n\nFinally, you can resolve the actual stream represented by the specified metadata using `Videos.Streams.GetAsync(...)` or download it directly to a file with `Videos.Streams.DownloadAsync(...)`:\n\n```csharp\n// ...\n\n// Get the actual stream\nvar stream = await youtube.Videos.Streams.GetAsync(streamInfo);\n\n// Download the stream to a file\nawait youtube.Videos.Streams.DownloadAsync(streamInfo, $\"video.{streamInfo.Container}\");\n```\n\n\u003e **Warning**:\n\u003e While the `Url` property in the stream metadata can be used to access the underlying content, you need a series of carefully crafted HTTP requests in order to do so.\n\u003e It's highly recommended to use `Videos.Streams.GetAsync(...)` or `Videos.Streams.DownloadAsync(...)` instead, as they will perform all the heavy lifting for you.\n\n#### Downloading closed captions\n\nClosed captions can be downloaded in a similar way to media streams.\nTo get the list of available closed caption tracks, call `Videos.ClosedCaptions.GetManifestAsync(...)`:\n\n```csharp\nusing YoutubeExplode;\n\nvar youtube = new YoutubeClient();\n\nvar videoUrl = \"https://youtube.com/watch?v=u_yIGGhubZs\";\nvar trackManifest = await youtube.Videos.ClosedCaptions.GetManifestAsync(videoUrl);\n```\n\nThen retrieve the metadata for a particular track:\n\n```csharp\n// ...\n\n// Find closed caption track in English\nvar trackInfo = trackManifest.GetByLanguage(\"en\");\n```\n\nFinally, use `Videos.ClosedCaptions.GetAsync(...)` to get the actual content of the track:\n\n```csharp\n// ...\n\nvar track = await youtube.Videos.ClosedCaptions.GetAsync(trackInfo);\n\n// Get the caption displayed at 0:35\nvar caption = track.GetByTime(TimeSpan.FromSeconds(35));\nvar text = caption.Text; // \"collection acts as the parent collection\"\n```\n\nYou can also download the closed caption track in the SRT file format with `Videos.ClosedCaptions.DownloadAsync(...)`:\n\n```csharp\n// ...\n\nawait youtube.Videos.ClosedCaptions.DownloadAsync(trackInfo, \"cc_track.srt\");\n```\n\n### Playlists\n\n#### Retrieving playlist metadata\n\nYou can get the metadata associated with a YouTube playlist by calling the `Playlists.GetAsync(...)` method:\n\n```csharp\nusing YoutubeExplode;\n\nvar youtube = new YoutubeClient();\n\nvar playlistUrl = \"https://youtube.com/playlist?list=PLa1F2ddGya_-UvuAqHAksYnB0qL9yWDO6\";\nvar playlist = await youtube.Playlists.GetAsync(playlistUrl);\n\nvar title = playlist.Title; // \"First Steps - Blender 2.80 Fundamentals\"\nvar author = playlist.Author.ChannelTitle; // \"Blender\"\n```\n\n#### Retrieving videos included in a playlist\n\nTo get the videos included in a playlist, call `Playlists.GetVideosAsync(...)`:\n\n```csharp\nusing YoutubeExplode;\nusing YoutubeExplode.Common;\n\nvar youtube = new YoutubeClient();\nvar playlistUrl = \"https://youtube.com/playlist?list=PLa1F2ddGya_-UvuAqHAksYnB0qL9yWDO6\";\n\n// Get all playlist videos\nvar videos = await youtube.Playlists.GetVideosAsync(playlistUrl);\n\n// Get only the first 20 playlist videos\nvar videosSubset = await youtube.Playlists.GetVideosAsync(playlistUrl).CollectAsync(20);\n```\n\nYou can also enumerate the videos iteratively without waiting for the whole list to load:\n\n```csharp\nusing YoutubeExplode;\n\nvar youtube = new YoutubeClient();\nvar playlistUrl = \"https://youtube.com/playlist?list=PLa1F2ddGya_-UvuAqHAksYnB0qL9yWDO6\";\n\nawait foreach (var video in youtube.Playlists.GetVideosAsync(playlistUrl))\n{\n    var title = video.Title;\n    var author = video.Author;\n}\n```\n\nIf you need precise control over how many requests you send to YouTube, use `Playlists.GetVideoBatchesAsync(...)` which returns videos wrapped in batches:\n\n```csharp\nusing YoutubeExplode;\n\nvar youtube = new YoutubeClient();\nvar playlistUrl = \"https://youtube.com/playlist?list=PLa1F2ddGya_-UvuAqHAksYnB0qL9yWDO6\";\n\n// Each batch corresponds to one request\nawait foreach (var batch in youtube.Playlists.GetVideoBatchesAsync(playlistUrl))\n{\n    foreach (var video in batch.Items)\n    {\n        var title = video.Title;\n        var author = video.Author;\n    }\n}\n```\n\n\u003e **Note**:\n\u003e You can craft playlist IDs to fetch special auto-generated playlists, such as music mixes, popular channel uploads, liked videos, and more.\n\u003e See [this reference](https://wiki.archiveteam.org/index.php/YouTube/Technical_details#Playlists) for more information.\n\n### Channels\n\n#### Retrieving channel metadata\n\nYou can get the metadata associated with a YouTube channel by calling the `Channels.GetAsync(...)` method:\n\n```csharp\nusing YoutubeExplode;\n\nvar youtube = new YoutubeClient();\n\nvar channelUrl = \"https://youtube.com/channel/UCSMOQeBJ2RAnuFungnQOxLg\";\nvar channel = await youtube.Channels.GetAsync(channelUrl);\n\nvar title = channel.Title; // \"Blender\"\n```\n\nYou can also get the channel metadata by username or profile URL with `Channels.GetByUserAsync(...)`:\n\n```csharp\nusing YoutubeExplode;\n\nvar youtube = new YoutubeClient();\n\nvar channelUrl = \"https://youtube.com/user/BlenderFoundation\";\nvar channel = await youtube.Channels.GetByUserAsync(channelUrl);\n\nvar id = channel.Id; // \"UCSMOQeBJ2RAnuFungnQOxLg\"\n```\n\nTo get the channel metadata by slug or legacy custom URL, use `Channels.GetBySlugAsync(...)`:\n\n```csharp\nusing YoutubeExplode;\n\nvar youtube = new YoutubeClient();\n\nvar channelUrl = \"https://youtube.com/c/BlenderFoundation\";\nvar channel = await youtube.Channels.GetBySlugAsync(channelUrl);\n\nvar id = channel.Id; // \"UCSMOQeBJ2RAnuFungnQOxLg\"\n```\n\nTo get the channel metadata by handle or custom URL, use `Channels.GetByHandleAsync(...)`:\n\n```csharp\nusing YoutubeExplode;\n\nvar youtube = new YoutubeClient();\n\nvar channelUrl = \"https://youtube.com/@BeauMiles\";\nvar channel = await youtube.Channels.GetByHandleAsync(channelUrl);\n\nvar id = channel.Id; // \"UCm325cMiw9B15xl22_gr6Dw\"\n```\n\n#### Retrieving channel uploads\n\nTo get the list of videos uploaded by a channel, call `Channels.GetUploadsAsync(...)`:\n\n```csharp\nusing YoutubeExplode;\nusing YoutubeExplode.Common;\n\nvar youtube = new YoutubeClient();\nvar channelUrl = \"https://youtube.com/channel/UCSMOQeBJ2RAnuFungnQOxLg\";\n\nvar videos = await youtube.Channels.GetUploadsAsync(channelUrl);\n```\n\n### Search\n\nYou can execute a search query by calling the `Search.GetResultsAsync(...)` method.\nEach search result may represent either a video, a playlist, or a channel, so you need to apply pattern matching to handle the corresponding cases:\n\n```csharp\nusing YoutubeExplode;\n\nvar youtube = new YoutubeClient();\n\nawait foreach (var result in youtube.Search.GetResultsAsync(\"blender tutorials\"))\n{\n    // Use pattern matching to handle different results (videos, playlists, channels)\n    switch (result)\n    {\n        case VideoSearchResult video:\n        {\n            var id = video.Id;\n            var title = video.Title;\n            var duration = video.Duration;\n            break;\n        }\n        case PlaylistSearchResult playlist:\n        {\n            var id = playlist.Id;\n            var title = playlist.Title;\n            break;\n        }\n        case ChannelSearchResult channel:\n        {\n            var id = channel.Id;\n            var title = channel.Title;\n            break;\n        }\n    }\n}\n```\n\nTo limit the results to a specific type, use `Search.GetVideosAsync(...)`, `Search.GetPlaylistsAsync(...)`, or `Search.GetChannelsAsync(...)`:\n\n```csharp\nusing YoutubeExplode;\nusing YoutubeExplode.Common;\n\nvar youtube = new YoutubeClient();\n\nvar videos = await youtube.Search.GetVideosAsync(\"blender tutorials\");\nvar playlists = await youtube.Search.GetPlaylistsAsync(\"blender tutorials\");\nvar channels = await youtube.Search.GetChannelsAsync(\"blender tutorials\");\n```\n\nSimilarly to playlists, you can also enumerate results in batches by calling `Search.GetResultBatchesAsync(...)`:\n\n```csharp\nusing YoutubeExplode;\n\nvar youtube = new YoutubeClient();\n\n// Each batch corresponds to one request\nawait foreach (var batch in youtube.Search.GetResultBatchesAsync(\"blender tutorials\"))\n{\n    foreach (var result in batch.Items)\n    {\n        switch (result)\n        {\n            case VideoSearchResult videoResult:\n            {\n                // ...\n            }\n            case PlaylistSearchResult playlistResult:\n            {\n                // ...\n            }\n            case ChannelSearchResult channelResult:\n            {\n                // ...\n            }\n        }\n    }\n}\n```\n\n### Authentication\n\nYou can access private videos and playlists by providing cookies that correspond to a pre-authenticated YouTube account.\nTo do that, create an instance of `YoutubeClient` using a constructor that accepts `IReadOnlyList\u003cCookie\u003e`:\n\n```csharp\nusing YoutubeExplode;\n\n// Perform authentication and extract cookies\nvar cookies = ...;\n\n// Cookie collection must be of type IReadOnlyList\u003cSystem.Net.Cookie\u003e\nvar youtube = new YoutubeClient(cookies);\n```\n\nIn order to actually perform the authentication, you can use an embedded browser such as [WebView](https://nuget.org/packages/Microsoft.Web.WebView2) to navigate the user to the [YouTube login page](https://accounts.google.com/ServiceLogin?continue=https%3A%2F%2Fwww.youtube.com), let them log in, and then extract the cookies from the browser.\n\n## Etymology\n\nThe \"Explode\" in **YoutubeExplode** comes from the name of a PHP function that splits up strings, [`explode(...)`](https://php.net/manual/en/function.explode.php). When I was starting the development of this library, most of the reference source code I read was written in PHP, hence the inspiration for the name.","funding_links":["https://github.com/sponsors/Tyrrrz","https://patreon.com/Tyrrrz","tyrrrz.me/donate"],"categories":["Frameworks, Libraries and Tools","C\\#","C# #","dotnet","C#","框架, 库和工具","Misc","\u003e 1K ⭐️","杂项","Audio"],"sub_categories":["Misc","大杂烩","GUI - other"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FTyrrrz%2FYoutubeExplode","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FTyrrrz%2FYoutubeExplode","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FTyrrrz%2FYoutubeExplode/lists"}