youtube.c 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224
  1. #define _GNU_SOURCE
  2. #include "youtube.h"
  3. #include "parse.h"
  4. #include "network.h"
  5. #include "menu.h"
  6. #include "common.h"
  7. #include <stdio.h>
  8. #include <stdlib.h>
  9. #include <string.h>
  10. #include <ctype.h>
  11. #include <unistd.h>
  12. #include <errno.h>
  13. #include <stdint.h>
  14. #define YT_URL "youtube.com"
  15. #define HTTP_HEADER "HTTP/1.1\r\nHost: www." YT_URL "\r\nConnection: close\r\n\r\n"
  16. video_list* get_playlist(char const* id)
  17. {
  18. connection con = {.host = YT_URL};
  19. if(asprintf(&con.rq, "GET /playlist?list=" "%s " HTTP_HEADER, id) == -1)
  20. return NULL;
  21. if(net_write(&con) < 0 || net_read(&con) < 0)
  22. return NULL;
  23. video_list* vl = parse_playlist(con.header->buf);
  24. free_http_header(con.header);
  25. return vl;
  26. }
  27. video_list* search_request(char* search, unsigned pnum)
  28. {
  29. connection con = {.host = YT_URL};
  30. if(asprintf(&con.rq, "GET /search_ajax?search_query=" "%s" "&page=" "%u" "&hl=en " HTTP_HEADER, search, pnum) == -1)
  31. return NULL;
  32. if(net_write(&con) < 0 || net_read(&con) < 0)
  33. return NULL;
  34. net_close(&con);
  35. free(con.rq);
  36. video_list* vl = parse_search_json(con.header->buf);
  37. free_http_header(con.header);
  38. return vl;
  39. }
  40. video_info* get_video_info(char* id)
  41. {
  42. connection con = {.host = "youtube.com"};
  43. if(asprintf(&con.rq, "GET /get_video_info?video_id=" "%s&el=detailpage " HTTP_HEADER, id) == -1)
  44. return NULL;
  45. if(net_write(&con) < 0 || net_read(&con) < 0)
  46. return NULL;
  47. net_close(&con);
  48. free(con.rq);
  49. video_info* vi = parse_video_info(con.header->buf);
  50. if(vi) vi->ml = parse_media_info(con.header->buf);
  51. free_http_header(con.header);
  52. return vi;
  53. }
  54. char* video_id_from_url(const char* url)
  55. {
  56. char* rq = NULL;
  57. if(parse_url(url, NULL, &rq))
  58. return NULL;
  59. char* start = NULL;
  60. char* end = NULL;
  61. /* TODO: Add support for more url formats */
  62. if((start = strstr(rq, "v=")))
  63. {
  64. /* Skip v= */
  65. start += 2;
  66. /* Either end of string or next argument */
  67. end = strchrnul(rq, '&');
  68. }
  69. size_t len = end - start;
  70. char* id = malloc(len + 1);
  71. if(id)
  72. {
  73. memcpy(id, start, len);
  74. id[len] = '\0';
  75. }
  76. free(rq);
  77. return id;
  78. }
  79. void free_media_info(media_info* info)
  80. {
  81. if(!info)
  82. return;
  83. free(info->url);
  84. free(info);
  85. return;
  86. }
  87. void free_video_info(video_info* info)
  88. {
  89. if(!info)
  90. return;
  91. free_media_list(info->ml);
  92. free(info->id);
  93. free(info->title);
  94. free(info->author);
  95. free(info->channel_id);
  96. free(info->length);
  97. free(info->views);
  98. free(info->description);
  99. free(info);
  100. return;
  101. }
  102. void free_video_list(video_list* vl)
  103. {
  104. if(!vl)
  105. return;
  106. for(uint i = 0; i < vl->count; i++)
  107. free_video_info(vl->vi[i]);
  108. free(vl->vi);
  109. free(vl);
  110. }
  111. void free_media_list(media_list* ml)
  112. {
  113. if(!ml)
  114. return;
  115. for(uint i = 0; i < ml->count; i++)
  116. free_media_info(ml->mi[i]);
  117. free(ml->mi);
  118. free(ml);
  119. }
  120. // Table is growing from low to high quality
  121. const media_info const itag_table[] =
  122. {
  123. /* Muxed (Audio + Video) */
  124. {17, 24, "144p", "3GP", "H.263+AAC", 1, 1, 24, NULL},
  125. {36, 24, "240p", "3GP", "H.263+AAC", 1, 1, 32, NULL},
  126. {18, 24, "360p", "MP4", "H.264+AAC", 1, 1, 96, NULL},
  127. {43, 24, "360p", "WebM", "H.264+Vorbis",1, 1, 128, NULL},
  128. {22, 24, "720p", "MP4", "H.264+AAC", 1, 1, 192, NULL},
  129. /* Live stream */
  130. {91, 24, "144p", "TS", "H.264+AAC", 1, 1, 48, NULL},
  131. {92, 24, "240p", "TS", "H.264+AAC", 1, 1, 48, NULL},
  132. {93, 24, "360p", "TS", "H.264+AAC", 1, 1, 128, NULL},
  133. {94, 24, "480p", "TS", "H.264+AAC", 1, 1, 128, NULL},
  134. {95, 24, "720p", "TS", "H.264+AAC", 1, 1, 256, NULL},
  135. {96, 24, "1080p", "TS", "H.264+AAC", 1, 1, 256, NULL},
  136. /* Video only */
  137. {160, 24, "144p", "MP4", "H.264", 1, 0, 0, NULL},
  138. {394, 24, "144p", "MP4", "AV1", 1, 0, 0, NULL},
  139. {278, 24, "144p", "WebM", "VP9", 1, 0, 0, NULL},
  140. {330, 60, "144p", "WebM", "VP9", 1, 0, 0, NULL},
  141. {133, 30, "240p", "MP4", "H.264", 1, 0, 0, NULL},
  142. {395, 24, "240p", "MP4", "AV1", 1, 0, 0, NULL},
  143. {331, 60, "240p", "WebM", "VP9", 1, 0, 0, NULL},
  144. {242, 24, "240p", "WebM", "VP9", 1, 0, 0, NULL},
  145. {134, 30, "360p", "MP4", "H.264", 1, 0, 0, NULL},
  146. {396, 24, "360p", "MP4", "AV1", 1, 0, 0, NULL},
  147. {243, 24, "360p", "WebM", "VP9", 1, 0, 0, NULL},
  148. {332, 60, "360p", "WebM", "VP9", 1, 0, 0, NULL},
  149. {135, 30, "480p", "MP4", "H.264", 1, 0, 0, NULL},
  150. {397, 24, "480p", "MP4", "AV1", 1, 0, 0, NULL},
  151. {244, 24, "480p", "WebM", "VP9", 1, 0, 0, NULL},
  152. {333, 60, "480p", "WebM", "VP9", 1, 0, 0, NULL},
  153. {136, 30, "720p", "MP4", "H.264", 1, 0, 0, NULL},
  154. {398, 24, "720p", "MP4", "AV1", 1, 0, 0, NULL},
  155. {247, 24, "720p", "WebM", "VP9", 1, 0, 0, NULL},
  156. {298, 60, "720p", "MP4", "H.264", 1, 0, 0, NULL},
  157. {302, 60, "720p", "WebM", "VP9", 1, 0, 0, NULL},
  158. {334, 60, "720p", "WebM", "VP9", 1, 0, 0, NULL},
  159. {137, 30, "1080p", "MP4", "H.264", 1, 0, 0, NULL},
  160. {399, 24, "1080", "MP4", "AV1", 1, 0, 0, NULL},
  161. {248, 24, "1080p", "WebM", "VP9", 1, 0, 0, NULL},
  162. {299, 60, "1080p", "MP4", "H.264", 1, 0, 0, NULL},
  163. {303, 60, "1080p", "WebM", "VP9", 1, 0, 0, NULL},
  164. {335, 60, "1080p", "WebM", "VP9", 1, 0, 0, NULL},
  165. {264, 24, "1440p", "MP4", "H.264", 1, 0, 0, NULL},
  166. {400, 24, "1440p", "MP4", "AV1", 1, 0, 0, NULL},
  167. {271, 24, "1440p", "WebM", "VP9", 1, 0, 0, NULL},
  168. {308, 60, "1440p", "WebM", "VP9", 1, 0, 0, NULL},
  169. {336, 60, "1440p", "WebM", "VP9", 1, 0, 0, NULL},
  170. {266, 24, "2160p", "MP4", "H.264", 1, 0, 0, NULL},
  171. {401, 24, "2160p", "MP4", "AV1", 1, 0, 0, NULL},
  172. {313, 24, "2160p", "WebM", "VP9", 1, 0, 0, NULL},
  173. {315, 60, "2160p", "WebM", "VP9", 1, 0, 0, NULL},
  174. {337, 60, "2160p", "WebM", "VP9", 1, 0, 0, NULL},
  175. {138, 24, "4320p", "MP4", "H.264", 1, 0, 0, NULL},
  176. {402, 24, "4320p", "MP4", "AV1", 1, 0, 0, NULL},
  177. {272, 24, "4320p", "WebM", "VP9", 1, 0, 0, NULL},
  178. /* Audio only */
  179. {139, 0, NULL, "M4A", "AAC", 0, 1, 48, NULL},
  180. {140, 0, NULL, "M4A", "AAC", 0, 1, 128, NULL},
  181. {171, 0, NULL, "WebM", "Vorbis", 0, 1, 128, NULL},
  182. {249, 0, NULL, "WebM", "Opus", 0, 1, 48, NULL},
  183. {250, 0, NULL, "WebM", "Opus", 0, 1, 64, NULL},
  184. {251, 0, NULL, "WebM", "Opus", 0, 1, 160, NULL},
  185. };
  186. const size_t itag_table_count = sizeof(itag_table) / sizeof(itag_table[0]);