YouTube en inineing de reversa • Oleksii Holub

YouTube en inineing de reversa • Oleksii Holub

Hace casi un año, comencé a desarrollar YouTubeExplodeuna biblioteca que raspa la información en los videos de YouTube y le permite descargarlos. Originalmente, mi principal motivación para desarrollarlo era simplemente ganar experiencia, ya que la tarea implicaba mucha investigación y ingeniería inversa. Hoy en día, YouTubeExplode es posiblemente la biblioteca de .NET más consistente y robusta para trabajar con YouTube.

Dado que este es un tema de discusión relativamente popular entre muchos desarrolladores principiantes, pensé que podría ayudar compartiendo el conocimiento que encontré pasando docenas de horas mirando las herramientas de desarrolladores de Chrome.

A partir de YouTubeExplode V6.0.7 (10-dic-2021), prácticamente todo en esta publicación se ha vuelto desactualizado, y los enfoques destacados ya no son utilizados por mi biblioteca. En lugar de tratar de continuar actualizando la información aquí, decidí escribir un artículo completamente nuevo – Investigación inversa YouTube: Revisited

Obtener los metadatos de video

Para encontrar y resolver las transmisiones de medios del video, primero debe obtener sus metadatos. Hay algunas maneras de hacerlo, pero la más confiable es consultando un punto final AJAX utilizado internamente por la API IfRame Inbren de YouTube. El formato es el siguiente: https://www.youtube.com/get_video_info?video_id={Videoid}.

La solicitud puede tomar muchos parámetros diferentes, pero como mínimo necesita una identificación de video, el valor en la URL que viene después /watch?v=Por ejemplo e_S9VvJM1PI.

La respuesta contiene metadatos codificados por URL, que deben decodificarse primero antes de que se pueda usar. Después de eso, puede asignar los nombres de parámetros a los valores en un diccionario para un acceso más fácil. Algunos valores de parámetros son objetos anidados en sí, por lo que a su vez se pueden asignar a diccionarios anidados.

Aquí hay un ejemplo de los metadatos decodificados (truncados para la brevedad):

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 puede ver, hay mucha información que se puede extraer de inmediato.

Veamos también algunos parámetros de consulta opcionales importantes que esta solicitud puede tomar:

  • hl – El nombre de la ubicación utilizada para localizar algunas cadenas. Si no se establece, es predeterminado a la configuración localizada de su dirección IP. Usar hl=en para forzar el idioma inglés en todas las cuerdas.
  • el – El tipo de página de YouTube que originó esta solicitud. Esto decide qué tipo de información estará disponible en la respuesta. En algunos casos, deberá establecer este parámetro en un cierto valor dependiendo del tipo de video, para evitar errores. Predeterminado a embedded.
  • sts – Una marca de tiempo que identifica la versión del cifrado de firma utilizada en las URL de transmisión. Predeterminado se vacía.

El parámetro “El”

El el El parámetro de solicitud puede tomar múltiples valores, y afecta qué tipo de datos recibirá en la respuesta. Sin embargo, solo hay unos pocos que realmente importan, así que los enumeraré aquí:

  • embedded – El valor predeterminado. YouTube usa esto al solicitar información para videos integrados. No funciona con videos que no se pueden integrar, pero funciona con videos restringidos por edad.
  • detailpage – Un valor alternativo, que produce un poco más de información. Por el contrario, funciona con videos que no son integrados, pero que no funcionan con videos restringidos por edad.

YouTubeExplode usa el=embedded para la primera consulta. Si falla porque el video no puede ser incrustado, luego se repone con el=detailpage.

Errores de manejo

Cuando la solicitud falla, la respuesta contendrá solo unos pocos campos:

  • status – que es igual a fail
  • errorcode – código entero que identifica el error
  • reason – Mensaje de texto que explica por qué el video no está disponible

Los códigos de error parecen ser muy genéricos y la mayoría de las veces es 100 o 150por lo que no son muy útiles para determinar qué salió mal.

Es necesario comprar algunos videos antes de que se puedan ver. En tales casos, habrá:

  • requires_purchase – que es igual a 1
  • ypc_vid – ID del Video (Trailer) de vista previa correspondiente que se puede ver de forma gratuita

Resolviendo transmisiones de medios

Las transmisiones de medios y sus metadatos vienen en muchas formas diferentes.

Transmisiones muxed

Las transmisiones multiplexadas (muxed) son el tipo que contiene pistas de video y audio en la misma transmisión. YouTube proporciona estas transmisiones solo en bajas cualidades: lo mejor que pueden ser es 720p30.

Los metadatos para estas corrientes están contenidos dentro de la respuesta codificada por URL mencionada anteriormente, dentro del url_encoded_fmt_stream_map parámetro. Para extraerlo, simplemente necesita dividir el valor por , y luego URL-Decode cada parte.

Así es como los metadatos decodificados buscan una transmisión muxed 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

Te interesará las siguientes propiedades:

  • itag – código entero que identifica el tipo de transmisión
  • type – Tipo de mime y códecs
  • url – URL que sirve a la corriente
  • s – Firma de cifrado utilizada para proteger la corriente (si está presente)

Nota: He encontrado casos cuando Se eliminaron algunas de las transmisiones muxedas a pesar de que todavía aparece en los metadatos. Por lo tanto, se recomienda enviar solicitudes de cabecera para verificar que cada transmisión todavía esté disponible. También puede obtener la longitud del contenido mientras lo hace, ya que no está presente en los metadatos.

Transmisiones adaptativas

YouTube también utiliza transmisiones solo de video y solo audio. Estos vienen en las cualidades más altas disponibles, sin limitaciones.

De manera similar a las transmisiones muxed, los metadatos para estas transmisiones se pueden extraer de la adaptive_fmts parámetro. Así es como se ve:

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

Las corrientes adaptativas tienen un conjunto ligeramente extendido de propiedades. Enumeraré los útiles:

  • itag – código entero que identifica el tipo de transmisión
  • type – Tipo de mime y códecs
  • url – URL que sirve a la corriente
  • s – Firma de cifrado utilizada para proteger la corriente (si está presente)
  • clen – Longitud de contenido de la transmisión en bytes
  • bitrate – tasa de bits de la corriente en kbit/seg
  • size -Resolución del video (solo video)
  • fps -Velocidad de cuadro del video (solo video)

Transmisiones adaptativas en manifiesto Dash

La información de video puede contener la URL de un manifiesto de tablero dentro del dashmpd parámetro. No siempre está presente y algunos videos podrían nunca tenerlo en absoluto.

Para resolver metadatos de estas transmisiones, primero debe descargar el manifiesto utilizando la URL proporcionada. A veces se puede proteger un manifiesto. Si es así, debería poder encontrar la firma dentro de la URL: es el valor separado por las bases que viene después /s/.

Las transmisiones en Dash también se pueden segmentar, cada segmento que comienza en un punto dado y dura solo un segundo o dos. Este es del tipo que su navegador normalmente usa al reproducir un video en YouTube: le permite ajustar fácilmente la calidad en función de las condiciones de la red. Las transmisiones segmentadas también se usan para videos de transmisión en vivo. Sin embargo, esta publicación no los cubrirá, ya que no se requiere procesarlos para descargar videos.

El manifiesto de Dash sigue Este esquema XML. Puede analizar los metadatos de la corriente si pasa por todos los nodos descendientes de tipo Representation. Así es como aparecen:

<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&amp;itag=133&amp;source=youtube&amp;requiressl=yes&amp;ei=Bt4VWqLOJMT3NI3qjPgB&amp;ms=au&amp;gcr=ua&amp;mv=m&amp;pl=24&amp;mn=sn-3c27sn7k&amp;initcwndbps=1143750&amp;mm=31&amp;nh=IgpwcjAxLmticDAxKgkxMjcuMC4wLjE&amp;ratebypass=yes&amp;mime=video/mp4&amp;gir=yes&amp;clen=4436318&amp;lmt=1507180947831345&amp;dur=183.850&amp;mt=1511382418&amp;key=dg_yt0&amp;s=7227CB6B79F7C702BB11275F9D71C532EB7E72046.DD6F06570E470E0E8384F74B879F79475D023A64A64&amp;signature=254E9E06DF034BC66D29B39523F84B33D5940EE3.1F4C8A5645075A228BB0C2D87F71477F6ABFFA99&amp;ip=255.255.255.255&amp;ipbits=0&amp;expire=1511404134&amp;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>

Tienen los siguientes atributos:

  • id – código entero que identifica el tipo de transmisión
  • bandwidth – tasa de bits de la corriente en kbit/seg
  • width -Ancho del video (solo video)
  • height -Altura del video (solo video)
  • frameRate -Velocidad de cuadro del video (solo video)

La URL se puede extraer del texto interno del <BaseURL> nodo.

Nota: No se sienta tentado a extraer la longitud del contenido del contentLength atributo, porque no siempre aparece en el <BaseURL> etiqueta. En su lugar, puede usar expresiones regulares para analizarlo desde el clen Parámetro de consulta en la URL.

Videos protegidos y firmas de cifrado

Puede notar que algunos videos, principalmente los que están cargados por canales verificados, están protegidos. Esto significa que sus transmisiones multimedia y manifiestas de tablero no se pueden acceder directamente por URL; en su lugar, se devolverá un código de error 403. Para poder acceder a ellos, debe descifrar sus firmas y luego modificar la URL en consecuencia.

Para las transmisiones de muxed y adaptativas, las firmas son parte de los metadatos extraídos. Las corrientes de tablero en sí nunca están protegidas, pero el manifiesto real puede ser: la firma se almacena como parte de la URL.

Una firma es una cadena hecha de dos secuencias de letras y números mayúsculas, separados por un período. Aquí hay un ejemplo:

537513BBC517D8643EBF25887256DAACD7521090.AE6A48F177E7B0E8CD85D077E5170BFD83BEDE6BE6C6C

Cuando su navegador abre un video de YouTube, transforma la firma utilizando un conjunto de operaciones definidas en el código fuente del reproductor, lo que agrega el resultado como un parámetro adicional dentro de la URL de cada transmisión de medios. Para repetir el mismo proceso del código, debe localizar la fuente JavaScript del reproductor utilizado por el video y analizarlo.

Ingeniería inversa al jugador

Cada video usa una versión ligeramente diferente del reproductor, lo que significa que debe averiguar cuál descargar. Si obtienes el HTML del Página de incrustación de videopuedes buscar "js": Para encontrar una propiedad JSON que contenga la URL del código fuente relativo del jugador. Una vez que prependas el anfitrión de YouTube, terminarás con una URL como esta:

https://www.youtube.com/yts/jsbin/player-vflYXLM5n/en_US/base.js

Además de obtener la URL a la fuente del jugador, también debes obtener algo llamado stsque es una marca de tiempo utilizada para identificar la versión del cifrado de firma. Deberá enviarlo a través de un parámetro en el get_video_info Endpoint mencionado anteriormente: esto se asegura de que los metadatos devueltos sean válidos para este jugador. Puedes extraer el valor de sts Del mismo modo, solo busque "sts": Y deberías encontrarlo.

Una vez que localice la URL del código fuente y la descargue, debe analizarla. Hay pocas maneras de hacerlo, por simplicidad por las que elegí analizarlo utilizando expresiones regulares.

En lugar de explicar exactamente lo que necesitas hacer, solo copiaré una pequeña parte del código fuente de YouTubeExplode. Me aseguré de comentarlo lo mejor que pueda, por lo que debería ser bastante 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;
}

La salida de este método es una colección de ICipherOperations. En este momento, puede haber hasta 3 tipos de operaciones de cifrado:

  • Intercambio – intercambia el primer personaje en la firma con otro carácter, identificado por su posición
  • Rebanada – Trunca los caracteres en la firma que vienen antes de la posición especificada
  • Contrarrestar – invierte toda la firma

Una vez que extrae con éxito el tipo y el orden de las operaciones usadas, debe almacenarlas en algún lugar para poder ejecutarlos en una firma.

Descifrar firmas y actualizar URL

Después de analizar el código fuente del jugador, puede obtener las firmas descifradas y actualizar la URL en consecuencia.

Para transmisiones muxed y adaptativas, transforme la firma extraída de los metadatos y agréguela como un consulta parámetro llamado signature:

...&signature=212CD2793C2E9224A40014A56BB8189AF3D591E3.523508F8A49EC4A3425C6E4484EF9F59FBEF9066

Para los manifiestos de Dash, transforme la firma extraída de la URL y agréguela como un ruta parámetro llamado signature:

.../signature/212CD2793C2E9224A40014A56BB8189AF3D591E3.523508F8A49EC4A3425C6E4484EF9F59FBEF9066/

Identificación de propiedades de flujo

Cada transmisión de medios tiene una itag que identifica de manera única sus propiedades, como el tipo de contenedor, los códecs, la calidad del video, etc. YouTubeExplode resuelve estas propiedades utilizando un mapa predefinido de etiquetas conocidas:

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)}
};

Cosas como la velocidad de bits, la resolución y la velocidad de cuadro no están estrictamente reguladas por itagpor lo que aún necesita extraerlos de metadatos.

Límites de tasa de paso

Por defecto, las transmisiones adaptativas se sirven a un ritmo limitado, lo suficiente como para obtener la siguiente parte a medida que se reproduce el video. Esto no es óptimo si el objetivo es descargar el video lo más rápido posible.

Para eludir esto, puede descargar la transmisión en múltiples segmentos enviando solicitudes HTTP con un Range encabezamiento. Para cada solicitud que realice, YouTube primero proporciona una pequeña porción al instante, seguida del resto de los datos que están acelerados.

Curiosamente, incluso con solo tener el encabezado establecido, el estrangulador parece entrar en marcha mucho más tarde de lo habitual. Después de experimentar durante algún tiempo, descubrí que dividir las solicitudes en segmentos de alrededor de 10 MB es óptimo para videos de todos los tamaños.

Resumen

Aquí hay un resumen de todos los pasos que debe tomar para descargar un video de YouTube:

  1. Obtenga la identificación de video (por ejemplo e_S9VvJM1PI)
  2. Descargue la página de incrustación del video (por ejemplo, https://www.youtube.com/embed/e_s9vvjm1pi)
  3. Extraiga la URL del código fuente del jugador (por ejemplo, https://www.youtube.com/yts/jsbin/player-vflyxlm5n/en_us/base.js)
  4. Obtener el sts valor (por ejemplo 17488)
  5. Descargue y analice el código fuente del reproductor
  6. Solicite los metadatos de video (por ejemplo https://www.youtube.com/get_video_info?video_id=e_s9vvjm1pi&sts=17488&hl=en); intentar el=detailpage Si falla
  7. Analizar los metadatos codificados por URL y extraer información sobre las transmisiones
  8. Si tienen firmas, use la fuente del jugador para descifrarlas y actualizar las URL
  9. Si hay una referencia a un manifiesto de tablero, extraiga la URL y descifrue si es necesario también
  10. Descargue el manifiesto Dash y extraiga transmisiones adicionales
  11. Usar itag Para clasificar las transmisiones por sus propiedades
  12. Elija una transmisión y descargándola en segmentos

Si tiene algún problema, siempre puede consultar el código fuente de YouTubeExplode o hacerme preguntas en los comentarios. Solana Token Creator

Comments

No comments yet. Why don’t you start the discussion?

Leave a Reply

Your email address will not be published. Required fields are marked *