Quase um ano atrás, comecei a desenvolver YouTubeexplodeuma biblioteca que elimina informações sobre vídeos do YouTube e permite que você os faça o download. Originalmente, minha principal motivação para desenvolvê-lo era simplesmente obter experiência, pois a tarefa envolvia muita pesquisa e engenharia reversa. Atualmente, o YouTubeexplode é sem dúvida a biblioteca .NET mais consistente e robusta para trabalhar com o YouTube.
Como este é um tópico de discussão relativamente popular, entre muitos desenvolvedores iniciantes, pensei que poderia ajudar compartilhando o conhecimento que encontrei passando dezenas de horas olhando para as ferramentas de desenvolvedor do Chrome.
A partir do YouTubeexplode v6.0.7 (10-dezembro-2021), praticamente tudo neste post ficou desatualizado, e as abordagens destacadas não são mais usadas pela minha biblioteca. Em vez de tentar continuar atualizando as informações aqui, decidi escrever um artigo completamente novo – Engenharia reversa YouTube: revisitado
Obtendo os metadados do vídeo
Para encontrar e resolver os fluxos de mídia do vídeo, você precisa primeiro obter seus metadados. Existem algumas maneiras de fazê -lo, mas a mais confiável é consultar um ponto de extremidade do Ajax usado internamente pela API incorporada IFRAME do YouTube. O formato é o seguinte: https://www.youtube.com/get_video_info?video_id= {Videoid}.
A solicitação pode levar muitos parâmetros diferentes, mas no mínimo precisa de um ID de vídeo – o valor no URL que vem depois /watch?v=
por exemplo e_S9VvJM1PI
.
A resposta contém metadados codificados por URL, que devem ser decodificados antes de ser utilizável. Depois disso, você pode mapear os nomes de parâmetros para valores em um dicionário para facilitar o acesso. Alguns valores de parâmetros são objetos aninhados, para que, por sua vez, possam ser mapeados para dicionários aninhados.
Aqui está um exemplo dos metadados decodificados (truncados para a brevidade):
status=ok
view_count=24022293
muted=0
use_cipher_signature=True
iurl=https://i.ytimg.com/vi/e_S9VvJM1PI/hqdefault.jpg
iurlhq720=https://i.ytimg.com/vi/e_S9VvJM1PI/hq720.jpg
video_id=e_S9VvJM1PI
avg_rating=4.8990560233
videostats_playback_base_url=https://s.youtube.com
ucid=UCKvT-8xU_BTJGvsQ5lR23TQ
iurlmq=https://i.ytimg.com/vi/e_S9VvJM1PI/mqdefault.jpg
thumbnail_url=https://i.ytimg.com/vi/e_S9VvJM1PI/default.jpg
loudness=-18.5090007782
pltype=content
cl=176519171
author=IconForHireVEVO
ptk=youtube_single
is_listed=1
allow_embed=1
short_view_count_text=24M views
relative_loudness=2.4909992218
fmt_list=43/640x360,18/640x360,36/426x240,17/256x144,13/256x144
has_cc=False
title=Icon For Hire - Make A Move
iurlmaxres=https://i.ytimg.com/vi/e_S9VvJM1PI/maxresdefault.jpg
keywords=Icon,For,Hire,Make,Move,Tooth,Nail,(TNN),Rock
length_seconds=184
allow_ratings=1
iurlsd=https://i.ytimg.com/vi/e_S9VvJM1PI/sddefault.jpg
iurlhq=https://i.ytimg.com/vi/e_S9VvJM1PI/hqdefault.jpg
url_encoded_fmt_stream_map=...
adaptive_fmts=...
dashmpd=...
Como você pode ver, há muitas informações que podem ser extraídas imediatamente.
Vamos também analisar alguns parâmetros importantes de consulta opcionais que essa solicitação pode tomar:
hl
– O nome do local usado para localizar algumas cordas. Se não estiver definido, o padrão é o local inferido do seu endereço IP. Usarhl=en
para forçar o idioma inglês em todas as cordas.el
– O tipo de página do YouTube que originou esta solicitação. Isso decide que tipo de informação estará disponível na resposta. Em alguns casos, você precisará definir esse parâmetro como um determinado valor, dependendo do tipo de vídeo, a fim de evitar erros. Padrão paraembedded
.sts
– Um registro de data e hora que identifica a versão da cifra de assinatura usada nos URLs de fluxo. Padrões de esvaziar.
O parâmetro “el”
O el
O parâmetro de solicitação pode levar vários valores e afeta que tipo de dados você receberá na resposta. Porém, existem apenas alguns que realmente importam, então vou listá -los aqui:
embedded
– O valor padrão. O YouTube usa isso ao solicitar informações para vídeos incorporados. Não funciona com vídeos que não são incorporados, mas trabalham com vídeos com restrição de idade.detailpage
– Um valor alternativo, que produz um pouco mais de informação. Por outro lado, trabalha com vídeos que não são incorporados, mas não funcionam com vídeos com restrição de idade.
YouTubeexplode usa el=embedded
para a primeira consulta. Se falhar porque o vídeo não pode ser incorporado, ele se levanta com el=detailpage
.
Erros de manuseio
Quando a solicitação falhar, a resposta conterá apenas alguns campos:
status
– que é igual afail
errorcode
– código inteiro que identifica o erroreason
– Mensagem de texto que explica por que o vídeo não está disponível
Os códigos de erro parecem ser muito genéricos e na maioria das vezes é 100
ou 150
então eles não são muito úteis para determinar o que deu errado.
Vídeos pagos
Alguns vídeos precisam ser comprados antes que possam ser assistidos. Nesses casos, haverá:
requires_purchase
– que é igual a1
ypc_vid
– Id do vídeo de visualização correspondente (trailer) que pode ser assistido gratuitamente
Resolvendo fluxos de mídia
Os fluxos de mídia e seus metadados vêm de várias formas diferentes.
Fluxos muxed
Os fluxos multiplexados (MUXED) são o tipo que contém faixas de vídeo e áudio no mesmo fluxo. O YouTube fornece esses fluxos apenas em baixas qualidades – o melhor que elas podem ser é 720p30.
Os metadados para esses fluxos estão contidos na resposta codificada por URL mencionada anteriormente, dentro do url_encoded_fmt_stream_map
parâmetro. Para extraí -lo, você simplesmente precisa dividir o valor por ,
e então o URL decodificar cada parte.
É assim que os metadados decodificados procuram um fluxo muxado individual:
itag=43
type=video/webm; codecs="vp8.0, vorbis"
fallback_host=redirector.googlevideo.com
url=https://r12---sn-3c27sn7k.googlevideo.com/videoplayback?itag=43&lmt=1367519763212098&ipbits=0&key=yt6&mime=video%2Fwebm&expire=1511401259&mn=sn-3c27sn7k&mm=31&ms=au&mv=m&mt=1511379591&ei=y9IVWuuyKI-YdLvnm8AO&sparams=dur%2Cei%2Cgcr%2Cid%2Cinitcwndbps%2Cip%2Cipbits%2Citag%2Clmt%2Cmime%2Cmm%2Cmn%2Cms%2Cmv%2Cnh%2Cpl%2Cratebypass%2Crequiressl%2Csource%2Cexpire&ip=255.255.255.255&id=o-AJuM11wvxuVl2WBgfb3nr6zbmXsFGQvhMelDobZ_KOrE&nh=IgpwcjAxLmticDAxKgkxMjcuMC4wLjE&requiressl=yes&gcr=ua&source=youtube&ratebypass=yes&pl=24&initcwndbps=1112500&dur=0.000
s=9599599594B0133328AA570AE0129E58478D7BCE9D226F.15ABC404267945A3F64FB4E42074383FC4FA80F5
quality=medium
Você estará interessado nas seguintes propriedades:
itag
– código inteiro que identifica o tipo de fluxotype
– MIME tipo e codecsurl
– URL que serve o fluxos
– A assinatura da cifra usada para proteger o fluxo (se presente)
Nota: Eu encontrei casos quando Alguns dos riachos muxed foram removidos Apesar de ainda aparecer nos metadados. Portanto, é recomendável enviar solicitações de cabeça para verificar se cada fluxo ainda está disponível. Você também pode obter comprimento de conteúdo enquanto está nisso, pois não está presente nos metadados.
Fluxos adaptativos
O YouTube também usa fluxos somente de vídeo e apenas áudio. Estes vêm nas qualidades mais altas disponíveis, sem limitações.
Da mesma forma que os fluxos muxed, os metadados para esses fluxos podem ser extraídos do adaptive_fmts
parâmetro. Aqui está como fica:
itag=134
lmt=1507180885248732
clen=10889173
size=640x360
quality_label=360p
bitrate=638590
index=709-1196
projection_type=1
url=https://r12---sn-3c27sn7k.googlevideo.com/videoplayback?itag=134&lmt=1507180885248732&ipbits=0&key=yt6&mime=video%2Fmp4&expire=1511401259&aitags=134&mn=sn-3c27sn7k&mm=31&ms=au&mv=m&mt=1511379591&ei=y9IVWuuyKI-YdLvnm8AO&sparams=aitags%2Cclen%2Cdur%2Cei%2Cgcr%2Cgir%2Cid%2Cinitcwndbps%2Cip%2Cipbits%2Citag%2Clmt%2Cmime%2Cmm%2Cmn%2Cms%2Cmv%2Cnh%2Cpl%2Crequiressl%2Csource%2Cexpire&ip=255.255.255.255&clen=10889173&id=o-AJuM11wvxuVl2WBgfb3nr6zbmXsFGQvhMelDobZ_KOrE&gir=yes&nh=IgpwcjAxLmticDAxKgkxMjcuMC4wLjE&requiressl=yes&gcr=ua&source=youtube&pl=24&initcwndbps=1112500&dur=183.850
fps=30
s=D68D68D685A42CD39B87D2AC677C8B34FA2DE3A1F3A9A5.902A1E29122D7018F6AC7C1EAFA4A51BE84C3A5C
type=video/mp4;+codecs="avc1.4d401e"
init=0-708
Os fluxos adaptativos têm um conjunto de propriedades ligeiramente estendido. Vou listar os úteis:
itag
– código inteiro que identifica o tipo de fluxotype
– MIME tipo e codecsurl
– URL que serve o fluxos
– A assinatura da cifra usada para proteger o fluxo (se presente)clen
– Comprimento do conteúdo do fluxo em bytesbitrate
– Taxa de bits do fluxo em kbit/ssize
-Resolução do vídeo (apenas em vídeo)fps
-Taxa de quadros do vídeo (somente em vídeo)
Fluxos adaptativos no manifesto do traço
As informações de vídeo podem conter o URL de um traço manifesto dentro do dashmpd
parâmetro. Nem sempre está presente e alguns vídeos podem nunca ter isso.
Para resolver metadados desses fluxos, você precisa primeiro baixar o manifesto usando o URL fornecido. Às vezes, um manifesto pode ser protegido. Se for, você deve encontrar a assinatura dentro do URL – é o valor separado por barras que vem depois /s/
.
Os fluxos no DASH também podem ser segmentados – cada segmento começando em um determinado ponto e com duração de apenas um ou dois segundos. Esse é o tipo que seu navegador normalmente usa ao reproduzir um vídeo no YouTube – permite ajustar facilmente a qualidade com base nas condições da rede. Os fluxos segmentados também são usados para vídeos ao vivo. Este post não os cobre, no entanto, pois o processamento não é necessário para baixar vídeos.
O manifesto do traço segue Este esquema XML. Você pode analisar os metadados do fluxo se passar por todos os nós descendentes do tipo Representation
. Aqui está como eles aparecem:
<Representation id="133" codecs="avc1.4d4015" width="426"
height="240" startWithSAP="1" maxPlayoutRate="1"
bandwidth="246787" frameRate="30" mediaLmt="1507180947831345">
<BaseURL contentLength="4436318">https://r12---sn-3c27sn7k.googlevideo.com/videoplayback?id=7bf4bd56f24cd4f2&itag=133&source=youtube&requiressl=yes&ei=Bt4VWqLOJMT3NI3qjPgB&ms=au&gcr=ua&mv=m&pl=24&mn=sn-3c27sn7k&initcwndbps=1143750&mm=31&nh=IgpwcjAxLmticDAxKgkxMjcuMC4wLjE&ratebypass=yes&mime=video/mp4&gir=yes&clen=4436318&lmt=1507180947831345&dur=183.850&mt=1511382418&key=dg_yt0&s=7227CB6B79F7C702BB11275F9D71C532EB7E72046.DD6F06570E470E0E8384F74B879F79475D023A64A64&signature=254E9E06DF034BC66D29B39523F84B33D5940EE3.1F4C8A5645075A228BB0C2D87F71477F6ABFFA99&ip=255.255.255.255&ipbits=0&expire=1511404134&sparams=ip,ipbits,expire,id,itag,source,requiressl,ei,ms,gcr,mv,pl,mn,initcwndbps,mm,nh,ratebypass,mime,gir,clen,lmt,dur</BaseURL>
<SegmentBase indexRange="709-1196" indexRangeExact="true">
<Initialization range="0-708" />
</SegmentBase>
</Representation>
Eles têm os seguintes atributos:
id
– código inteiro que identifica o tipo de fluxobandwidth
– Taxa de bits do fluxo em kbit/swidth
-Largura do vídeo (somente em vídeo)height
-Altura do vídeo (apenas em vídeo)frameRate
-Taxa de quadros do vídeo (somente em vídeo)
O URL pode ser extraído do texto interno do <BaseURL>
nó.
Nota: Não fique tentado a extrair o comprimento do conteúdo do contentLength
atributo, porque nem sempre aparece no <BaseURL>
marcação. Em vez disso, você pode usar expressões regulares para analisá -lo do clen
Parâmetro de consulta no URL.
Vídeos protegidos e assinaturas de cifra
Você pode notar que alguns vídeos, principalmente os enviados por canais verificados, estão protegidos. Isso significa que seus fluxos de mídia e manifestos de traço não podem ser acessados diretamente pelo URL – um código de erro 403 será retornado. Para poder acessá -los, você precisa decifrar suas assinaturas e modificar o URL de acordo.
Para fluxos muxed e adaptativos, as assinaturas fazem parte dos metadados extraídos. Os riachos de traço nunca são protegidos, mas o manifesto real pode ser – a assinatura é armazenada como parte do URL.
Uma assinatura é uma string feita de duas sequências de letras e números maiúsculos, separados por um período. Aqui está um exemplo:
537513BBC517D8643EBF25887256DAACD7521090.AE6A48F177E7B0E8CD85D077E5170BFD83BEDE6BE6C6C
Quando o seu navegador abre um vídeo do YouTube, ele transforma a assinatura usando um conjunto de operações definidas no código -fonte do jogador, anexando o resultado como um parâmetro adicional dentro do URL de cada fluxo de mídia. Para repetir o mesmo processo do código, você precisa localizar a fonte JavaScript do player usada pelo vídeo e analisá -lo.
Engenharia reversa O jogador
Cada vídeo usa uma versão ligeiramente diferente do player, o que significa que você precisa descobrir qual download. Se você receber o HTML do página de incorporação do vídeovocê pode procurar por "js":
Para encontrar uma propriedade JSON que contém o URL de código -fonte relativo do jogador. Depois de prender o host do YouTube, você acabará com um URL como este:
https://www.youtube.com/yts/jsbin/player-vflYXLM5n/en_US/base.js
Além de obter o URL para a fonte do jogador, você também precisa obter algo chamado sts
que é um registro de data e hora usado para identificar a versão da cifra de assinatura. Você precisará enviá -lo através de um parâmetro no get_video_info
Endpoint mencionado anteriormente – isso garante que os metadados retornados sejam válidos para este jogador. Você pode extrair o valor de sts
Da mesma forma, basta procurar por "sts":
E você deve encontrá -lo.
Depois de localizar o URL do código -fonte e baixá -lo, você precisa analisá -lo. Existem poucas maneiras de fazer isso, por razões de simplicidade que escolhi analisá -lo usando expressões regulares.
Em vez de explicar passo a passo o que exatamente você precisa fazer, vou copiar uma pequena parte do código-fonte do YouTubeexplode. Fiz questão de comentar da melhor maneira possível, por isso deve ser muito fácil de seguir.
private async Task<IReadOnlyList<ICipherOperation>> GetCipherOperationsAsync(string sourceUrl)
{
// Get player source code
var sourceRaw = await _httpClient.GetStringAsync(sourceUrl);
// Find the name of the function that handles deciphering
var entryPoint = Regex.Match(sourceRaw,
@"\bc\s*&&\s*d\.set\((^,)+,\s*(?:encodeURIComponent\s*\()?\s*((\w$)+)\(").Groups(1).Value;
if (string.IsNullOrWhiteSpace(entryPoint))
throw new Exception("Could not find the entry function for signature deciphering.");
// Find the body of the function
var entryPointBody = Regex.Match(sourceRaw,
@"(?!h\.)" + Regex.Escape(entryPoint) + @"=function\(\w+\)\{(.*?)\}",
RegexOptions.Singleline).Groups(1).Value;
if (string.IsNullOrWhiteSpace(entryPointBody))
throw new Exception("Could not find the signature decipherer function body.");
var entryPointLines = entryPointBody.Split(";");
// Identify cipher functions
string reverseFuncName = null;
string sliceFuncName = null;
string charSwapFuncName = null;
var operations = new List<ICipherOperation>();
// Analyze the function body to determine the names of cipher functions
foreach (var line in entryPointLines)
{
// Break when all functions are found
if (!string.IsNullOrWhiteSpace(reverseFuncName) &&
!string.IsNullOrWhiteSpace(sliceFuncName) &&
!string.IsNullOrWhiteSpace(charSwapFuncName))
break;
// Get the function called on this line
var calledFuncName = Regex.Match(line, @"\w+\.(\w+)\(").Groups(1).Value;
if (string.IsNullOrWhiteSpace(calledFuncName))
continue;
// Find cipher function names
if (Regex.IsMatch(sourceRaw, $@"{Regex.Escape(calledFuncName)}:\bfunction\b\(\w+\)"))
{
reverseFuncName = calledFuncName;
}
else if (Regex.IsMatch(sourceRaw,
$@"{Regex.Escape(calledFuncName)}:\bfunction\b\((a),b\).(\breturn\b)?.?\w+\."))
{
sliceFuncName = calledFuncName;
}
else if (Regex.IsMatch(sourceRaw,
$@"{Regex.Escape(calledFuncName)}:\bfunction\b\(\w+\,\w\).\bvar\b.\bc=a\b"))
{
charSwapFuncName = calledFuncName;
}
}
// Analyze the function body again to determine the operation set and order
foreach (var line in entryPointLines)
{
// Get the function called on this line
var calledFuncName = Regex.Match(line, @"\w+\.(\w+)\(").Groups(1).Value;
if (string.IsNullOrWhiteSpace(calledFuncName))
continue;
// Swap operation (swaps first character and character at index)
if (calledFuncName == charSwapFuncName)
{
var index = int.Parse(Regex.Match(line, @"\(\w+,(\d+)\)").Groups(1).Value);
operations.Add(new SwapCipherOperation(index));
}
// Slice operation (returns substring at index)
else if (calledFuncName == sliceFuncName)
{
var index = int.Parse(Regex.Match(line, @"\(\w+,(\d+)\)").Groups(1).Value);
operations.Add(new SliceCipherOperation(index));
}
// Reverse operation (reverses the entire string)
else if (calledFuncName == reverseFuncName)
{
operations.Add(new ReverseCipherOperation());
}
}
return operations;
}
A saída deste método é uma coleção de ICipherOperation
s. Neste momento, pode haver até 3 tipos de operações de cifra:
- Trocar – troca o primeiro personagem na assinatura com outro personagem, identificado por sua posição
- Fatiar – truque os caracteres na assinatura que vêm antes da posição especificada
- Reverter – reverte toda a assinatura
Depois de extrair com sucesso o tipo e a ordem das operações usadas, você precisa armazená -las em algum lugar para poder executá -las em uma assinatura.
Decifrar assinaturas e atualizar URLs
Depois de analisar o código -fonte do jogador, você pode obter as assinaturas decifradas e atualizar o URL de acordo.
Para fluxos muxed e adaptativos, transforme a assinatura extraída dos metadados e adicione -a como um consulta parâmetro chamado signature
:
...&signature=212CD2793C2E9224A40014A56BB8189AF3D591E3.523508F8A49EC4A3425C6E4484EF9F59FBEF9066
Para os manifestos de traço, transforme a assinatura extraída do URL e adicione -a como um rota parâmetro chamado signature
:
.../signature/212CD2793C2E9224A40014A56BB8189AF3D591E3.523508F8A49EC4A3425C6E4484EF9F59FBEF9066/
Identificando propriedades do fluxo
Cada fluxo de mídia tem um itag
Isso identifica exclusivamente suas propriedades, como tipo de contêiner, codecs, qualidade de vídeo etc. YouTubeexplode resolve essas propriedades usando um mapa predefinido de tags conhecidas:
private static readonly Dictionary<int, ItagDescriptor> ItagMap = new Dictionary<int, ItagDescriptor>
{
// Muxed
{5, new ItagDescriptor(Container.Flv, AudioEncoding.Mp3, VideoEncoding.H263, VideoQuality.Low144)},
{6, new ItagDescriptor(Container.Flv, AudioEncoding.Mp3, VideoEncoding.H263, VideoQuality.Low240)},
{13, new ItagDescriptor(Container.Tgpp, AudioEncoding.Aac, VideoEncoding.Mp4V, VideoQuality.Low144)},
{17, new ItagDescriptor(Container.Tgpp, AudioEncoding.Aac, VideoEncoding.Mp4V, VideoQuality.Low144)},
{18, new ItagDescriptor(Container.Mp4, AudioEncoding.Aac, VideoEncoding.H264, VideoQuality.Medium360)},
{22, new ItagDescriptor(Container.Mp4, AudioEncoding.Aac, VideoEncoding.H264, VideoQuality.High720)},
{34, new ItagDescriptor(Container.Flv, AudioEncoding.Aac, VideoEncoding.H264, VideoQuality.Medium360)},
{35, new ItagDescriptor(Container.Flv, AudioEncoding.Aac, VideoEncoding.H264, VideoQuality.Medium480)},
{36, new ItagDescriptor(Container.Tgpp, AudioEncoding.Aac, VideoEncoding.Mp4V, VideoQuality.Low240)},
{37, new ItagDescriptor(Container.Mp4, AudioEncoding.Aac, VideoEncoding.H264, VideoQuality.High1080)},
{38, new ItagDescriptor(Container.Mp4, AudioEncoding.Aac, VideoEncoding.H264, VideoQuality.High3072)},
{43, new ItagDescriptor(Container.WebM, AudioEncoding.Vorbis, VideoEncoding.Vp8, VideoQuality.Medium360)},
{44, new ItagDescriptor(Container.WebM, AudioEncoding.Vorbis, VideoEncoding.Vp8, VideoQuality.Medium480)},
{45, new ItagDescriptor(Container.WebM, AudioEncoding.Vorbis, VideoEncoding.Vp8, VideoQuality.High720)},
{46, new ItagDescriptor(Container.WebM, AudioEncoding.Vorbis, VideoEncoding.Vp8, VideoQuality.High1080)},
{59, new ItagDescriptor(Container.Mp4, AudioEncoding.Aac, VideoEncoding.H264, VideoQuality.Medium480)},
{78, new ItagDescriptor(Container.Mp4, AudioEncoding.Aac, VideoEncoding.H264, VideoQuality.Medium480)},
{82, new ItagDescriptor(Container.Mp4, AudioEncoding.Aac, VideoEncoding.H264, VideoQuality.Medium360)},
{83, new ItagDescriptor(Container.Mp4, AudioEncoding.Aac, VideoEncoding.H264, VideoQuality.Medium480)},
{84, new ItagDescriptor(Container.Mp4, AudioEncoding.Aac, VideoEncoding.H264, VideoQuality.High720)},
{85, new ItagDescriptor(Container.Mp4, AudioEncoding.Aac, VideoEncoding.H264, VideoQuality.High1080)},
{91, new ItagDescriptor(Container.Mp4, AudioEncoding.Aac, VideoEncoding.H264, VideoQuality.Low144)},
{92, new ItagDescriptor(Container.Mp4, AudioEncoding.Aac, VideoEncoding.H264, VideoQuality.Low240)},
{93, new ItagDescriptor(Container.Mp4, AudioEncoding.Aac, VideoEncoding.H264, VideoQuality.Medium360)},
{94, new ItagDescriptor(Container.Mp4, AudioEncoding.Aac, VideoEncoding.H264, VideoQuality.Medium480)},
{95, new ItagDescriptor(Container.Mp4, AudioEncoding.Aac, VideoEncoding.H264, VideoQuality.High720)},
{96, new ItagDescriptor(Container.Mp4, AudioEncoding.Aac, VideoEncoding.H264, VideoQuality.High1080)},
{100, new ItagDescriptor(Container.WebM, AudioEncoding.Vorbis, VideoEncoding.Vp8, VideoQuality.Medium360)},
{101, new ItagDescriptor(Container.WebM, AudioEncoding.Vorbis, VideoEncoding.Vp8, VideoQuality.Medium480)},
{102, new ItagDescriptor(Container.WebM, AudioEncoding.Vorbis, VideoEncoding.Vp8, VideoQuality.High720)},
{132, new ItagDescriptor(Container.Mp4, AudioEncoding.Aac, VideoEncoding.H264, VideoQuality.Low240)},
{151, new ItagDescriptor(Container.Mp4, AudioEncoding.Aac, VideoEncoding.H264, VideoQuality.Low144)},
// Video-only (mp4)
{133, new ItagDescriptor(Container.Mp4, null, VideoEncoding.H264, VideoQuality.Low240)},
{134, new ItagDescriptor(Container.Mp4, null, VideoEncoding.H264, VideoQuality.Medium360)},
{135, new ItagDescriptor(Container.Mp4, null, VideoEncoding.H264, VideoQuality.Medium480)},
{136, new ItagDescriptor(Container.Mp4, null, VideoEncoding.H264, VideoQuality.High720)},
{137, new ItagDescriptor(Container.Mp4, null, VideoEncoding.H264, VideoQuality.High1080)},
{138, new ItagDescriptor(Container.Mp4, null, VideoEncoding.H264, VideoQuality.High4320)},
{160, new ItagDescriptor(Container.Mp4, null, VideoEncoding.H264, VideoQuality.Low144)},
{212, new ItagDescriptor(Container.Mp4, null, VideoEncoding.H264, VideoQuality.Medium480)},
{213, new ItagDescriptor(Container.Mp4, null, VideoEncoding.H264, VideoQuality.Medium480)},
{214, new ItagDescriptor(Container.Mp4, null, VideoEncoding.H264, VideoQuality.High720)},
{215, new ItagDescriptor(Container.Mp4, null, VideoEncoding.H264, VideoQuality.High720)},
{216, new ItagDescriptor(Container.Mp4, null, VideoEncoding.H264, VideoQuality.High1080)},
{217, new ItagDescriptor(Container.Mp4, null, VideoEncoding.H264, VideoQuality.High1080)},
{264, new ItagDescriptor(Container.Mp4, null, VideoEncoding.H264, VideoQuality.High1440)},
{266, new ItagDescriptor(Container.Mp4, null, VideoEncoding.H264, VideoQuality.High2160)},
{298, new ItagDescriptor(Container.Mp4, null, VideoEncoding.H264, VideoQuality.High720)},
{299, new ItagDescriptor(Container.Mp4, null, VideoEncoding.H264, VideoQuality.High1080)},
// Video-only (webm)
{167, new ItagDescriptor(Container.WebM, null, VideoEncoding.Vp8, VideoQuality.Medium360)},
{168, new ItagDescriptor(Container.WebM, null, VideoEncoding.Vp8, VideoQuality.Medium480)},
{169, new ItagDescriptor(Container.WebM, null, VideoEncoding.Vp8, VideoQuality.High720)},
{170, new ItagDescriptor(Container.WebM, null, VideoEncoding.Vp8, VideoQuality.High1080)},
{218, new ItagDescriptor(Container.WebM, null, VideoEncoding.Vp8, VideoQuality.Medium480)},
{219, new ItagDescriptor(Container.WebM, null, VideoEncoding.Vp8, VideoQuality.Medium480)},
{242, new ItagDescriptor(Container.WebM, null, VideoEncoding.Vp9, VideoQuality.Low240)},
{243, new ItagDescriptor(Container.WebM, null, VideoEncoding.Vp9, VideoQuality.Medium360)},
{244, new ItagDescriptor(Container.WebM, null, VideoEncoding.Vp9, VideoQuality.Medium480)},
{245, new ItagDescriptor(Container.WebM, null, VideoEncoding.Vp9, VideoQuality.Medium480)},
{246, new ItagDescriptor(Container.WebM, null, VideoEncoding.Vp9, VideoQuality.Medium480)},
{247, new ItagDescriptor(Container.WebM, null, VideoEncoding.Vp9, VideoQuality.High720)},
{248, new ItagDescriptor(Container.WebM, null, VideoEncoding.Vp9, VideoQuality.High1080)},
{271, new ItagDescriptor(Container.WebM, null, VideoEncoding.Vp9, VideoQuality.High1440)},
{272, new ItagDescriptor(Container.WebM, null, VideoEncoding.Vp9, VideoQuality.High2160)},
{278, new ItagDescriptor(Container.WebM, null, VideoEncoding.Vp9, VideoQuality.Low144)},
{302, new ItagDescriptor(Container.WebM, null, VideoEncoding.Vp9, VideoQuality.High720)},
{303, new ItagDescriptor(Container.WebM, null, VideoEncoding.Vp9, VideoQuality.High1080)},
{308, new ItagDescriptor(Container.WebM, null, VideoEncoding.Vp9, VideoQuality.High1440)},
{313, new ItagDescriptor(Container.WebM, null, VideoEncoding.Vp9, VideoQuality.High2160)},
{315, new ItagDescriptor(Container.WebM, null, VideoEncoding.Vp9, VideoQuality.High2160)},
{330, new ItagDescriptor(Container.WebM, null, VideoEncoding.Vp9, VideoQuality.Low144)},
{331, new ItagDescriptor(Container.WebM, null, VideoEncoding.Vp9, VideoQuality.Low240)},
{332, new ItagDescriptor(Container.WebM, null, VideoEncoding.Vp9, VideoQuality.Medium360)},
{333, new ItagDescriptor(Container.WebM, null, VideoEncoding.Vp9, VideoQuality.Medium480)},
{334, new ItagDescriptor(Container.WebM, null, VideoEncoding.Vp9, VideoQuality.High720)},
{335, new ItagDescriptor(Container.WebM, null, VideoEncoding.Vp9, VideoQuality.High1080)},
{336, new ItagDescriptor(Container.WebM, null, VideoEncoding.Vp9, VideoQuality.High1440)},
{337, new ItagDescriptor(Container.WebM, null, VideoEncoding.Vp9, VideoQuality.High2160)},
// Audio-only (mp4)
{139, new ItagDescriptor(Container.M4A, AudioEncoding.Aac, null, null)},
{140, new ItagDescriptor(Container.M4A, AudioEncoding.Aac, null, null)},
{141, new ItagDescriptor(Container.M4A, AudioEncoding.Aac, null, null)},
{256, new ItagDescriptor(Container.M4A, AudioEncoding.Aac, null, null)},
{258, new ItagDescriptor(Container.M4A, AudioEncoding.Aac, null, null)},
{325, new ItagDescriptor(Container.M4A, AudioEncoding.Aac, null, null)},
{328, new ItagDescriptor(Container.M4A, AudioEncoding.Aac, null, null)},
// Audio-only (webm)
{171, new ItagDescriptor(Container.WebM, AudioEncoding.Vorbis, null, null)},
{172, new ItagDescriptor(Container.WebM, AudioEncoding.Vorbis, null, null)},
{249, new ItagDescriptor(Container.WebM, AudioEncoding.Opus, null, null)},
{250, new ItagDescriptor(Container.WebM, AudioEncoding.Opus, null, null)},
{251, new ItagDescriptor(Container.WebM, AudioEncoding.Opus, null, null)}
};
Coisas como taxa de bits, resolução e taxa de quadros não são estritamente regulamentadas por itag
então você ainda precisa extraí -los dos metadados.
Ignorando os limites da taxa
Por padrão, os fluxos adaptativos são servidos a uma taxa limitada – apenas o suficiente para buscar a próxima parte à medida que o vídeo é reproduzido. Isso não é ideal se o objetivo é baixar o vídeo o mais rápido possível.
Para contornar isso, você pode baixar o fluxo em vários segmentos enviando solicitações HTTP com um Range
cabeçalho. Para cada solicitação que você faz, o YouTube primeiro fornece um pequeno pedaço instantaneamente, seguido pelo restante dos dados que são reduzidos.
Curiosamente, mesmo apenas ao definir o cabeçalho, a aceleração parece aumentar muito mais tarde do que o habitual. Depois de experimentar por algum tempo, descobri que dividir as solicitações em segmentos de cerca de 10 MB é ideal para vídeos de todos os tamanhos.
Resumo
Aqui está uma recapitulação de todas as etapas que você precisa tomar para baixar um vídeo do YouTube:
- Obtenha o ID de vídeo (por exemplo
e_S9VvJM1PI
) - Baixe a página de incorporação do vídeo (por exemplo https://www.youtube.com/embed/e_s9vvjm1pi)
- Extraia o URL do código -fonte do jogador (por exemplo https://www.youtube.com/yts/jsbin/player-vflyxlm5n/en_us/base.js)
- Obtenha o
sts
valor (por exemplo17488
) - Baixar e analisar o código -fonte do jogador
- Solicite os metadados do vídeo (por exemplo https://www.youtube.com/get_video_info?video_id=e_s9vvjm1pi&sts=17488&hl=en); tente com
el=detailpage
Se falhar - Analisar os metadados codificados por URL e extrair informações sobre fluxos
- Se eles tiverem assinaturas, use a fonte do jogador para decifrá -los e atualizar os URLs
- Se houver uma referência a um traço manifesto, extraia o URL e decifre -o, se necessário, também
- Baixe o manifesto do DASH e extraia fluxos adicionais
- Usar
itag
para classificar fluxos por suas propriedades - Escolha um fluxo e faça o download em segmentos
Se você tiver algum problema, sempre pode consultar o código -fonte de YouTubeexplode Ou faça -me perguntas nos comentários.

Luis es un experto en Inteligência Empresarial, Redes de Computadores, Gestão de Dados e Desenvolvimento de Software. Con amplia experiencia en tecnología, su objetivo es compartir conocimientos prácticos para ayudar a los lectores a entender y aprovechar estas áreas digitales clave.