ascii-chat
Binary v0.15.83-53-gb711d8720
Library lib/v0.3.2-701-gb711d8720
Video chat in your terminal
Loading...
Searching...
No Matches
yt_dlp.h File Reference

yt-dlp stream URL extraction More...

Go to the source code of this file.

Data Structures

struct  yt_dlp_request_options_t

Enumerations

enum  { YT_DLP_HEADERS_CAPACITY = 8192 , YT_DLP_COOKIES_CAPACITY = 8192 , YT_DLP_PROXY_CAPACITY = 2048 }

Functions

asciichat_error_t yt_dlp_extract_with_options (const char *url, const char *yt_dlp_options, char *output_url, size_t output_size, char *audio_url, size_t audio_size, yt_dlp_request_options_t *video_options, yt_dlp_request_options_t *audio_options, bool(*cancel)(void *), void *data)
asciichat_error_t yt_dlp_extract_stream_urls (const char *url, const char *yt_dlp_options, char *output_url, size_t output_size, char *audio_url, size_t audio_size)
 For yt dlp request options, this is the live record that ties proxy_specified together. The owning code creates it, updates it while work is active, and releases its handles and pointed-to storage before freeing the record.
asciichat_error_t yt_dlp_extract_stream_urls_interruptible (const char *url, const char *yt_dlp_options, char *output_url, size_t output_size, char *audio_url, size_t audio_size, bool(*cancel)(void *), void *data)
asciichat_error_t yt_dlp_extract_stream_url (const char *url, const char *yt_dlp_options, char *output_url, size_t output_size)
bool yt_dlp_is_available (void)
 Check if yt-dlp is installed and accessible.

Detailed Description

yt-dlp stream URL extraction

Encapsulates yt-dlp subprocess logic for extracting playable stream URLs from URLs that yt-dlp supports (YouTube, Twitch, etc.)

Definition in file yt_dlp.h.

Enumeration Type Documentation

◆ anonymous enum

anonymous enum
Enumerator
YT_DLP_HEADERS_CAPACITY 
YT_DLP_COOKIES_CAPACITY 
YT_DLP_PROXY_CAPACITY 

Definition at line 16 of file yt_dlp.h.

@ YT_DLP_HEADERS_CAPACITY
Definition yt_dlp.h:16
@ YT_DLP_COOKIES_CAPACITY
Definition yt_dlp.h:16
@ YT_DLP_PROXY_CAPACITY
Definition yt_dlp.h:16

Function Documentation

◆ yt_dlp_extract_stream_url()

asciichat_error_t yt_dlp_extract_stream_url ( const char * url,
const char * yt_dlp_options,
char * output_url,
size_t output_size )

Extract the first playable stream URL. Prefer the two-output API for audio.

Definition at line 558 of file yt_dlp.c.

559 {
560 char audio_url[MEDIA_STREAM_URL_SIZE];
561 return yt_dlp_extract_stream_urls(url, yt_dlp_options, output_url, output_size, audio_url, sizeof(audio_url));
562}
#define MEDIA_STREAM_URL_SIZE
asciichat_error_t yt_dlp_extract_stream_urls(const char *url, const char *yt_dlp_options, char *output_url, size_t output_size, char *audio_url, size_t audio_size)
For yt dlp request options, this is the live record that ties proxy_specified together....
Definition yt_dlp.c:552

References MEDIA_STREAM_URL_SIZE, and yt_dlp_extract_stream_urls().

◆ yt_dlp_extract_stream_urls_interruptible()

asciichat_error_t yt_dlp_extract_stream_urls_interruptible ( const char * url,
const char * yt_dlp_options,
char * output_url,
size_t output_size,
char * audio_url,
size_t audio_size,
bool(* cancel )(void *),
void * data )

Definition at line 544 of file yt_dlp.c.

546 {
547 yt_dlp_request_options_t video_request, audio_request;
548 return yt_dlp_extract_with_options(url, yt_dlp_options, output_url, output_size, audio_url, audio_size,
549 &video_request, &audio_request, cancel, data);
550}
asciichat_error_t yt_dlp_extract_with_options(const char *url, const char *yt_dlp_options, char *output_url, size_t output_size, char *audio_url, size_t audio_size, yt_dlp_request_options_t *video_request, yt_dlp_request_options_t *audio_request, bool(*cancel)(void *), void *data)
Definition yt_dlp.c:377

References yt_dlp_extract_with_options().

Referenced by yt_dlp_extract_stream_urls().

◆ yt_dlp_extract_with_options()

asciichat_error_t yt_dlp_extract_with_options ( const char * url,
const char * yt_dlp_options,
char * output_url,
size_t output_size,
char * audio_url,
size_t audio_size,
yt_dlp_request_options_t * video_options,
yt_dlp_request_options_t * audio_options,
bool(* cancel )(void *),
void * data )

Definition at line 377 of file yt_dlp.c.

381 {
382 if (!video_request || !audio_request)
383 return SET_ERRNO(ERROR_INVALID_PARAM, "Missing yt-dlp request options output");
384 memset(video_request, 0, sizeof(*video_request));
385 memset(audio_request, 0, sizeof(*audio_request));
386 ASCII_OTEL_SCOPE(YTDLP_EXTRACT);
387 ASCII_OTEL_ATTR_STRING("media.extractor", "yt-dlp");
388 if (output_url && output_size) {
389 output_url[0] = '\0';
390 output_url[output_size - 1] = '\0';
391 }
392 if (audio_url && audio_size) {
393 audio_url[0] = '\0';
394 audio_url[audio_size - 1] = '\0';
395 }
396 if (!url || !output_url || output_size < 256 || !audio_url || audio_size < 256) {
397 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid parameters for yt-dlp URL extraction");
398 }
399 static_mutex_lock_named(&g_yt_dlp_cache_lock, "yt_dlp_cache");
400 bool cached = yt_dlp_cache_get(url, yt_dlp_options, output_url, output_size, audio_url, audio_size, video_request,
401 audio_request);
402 static_mutex_unlock(&g_yt_dlp_cache_lock);
403 if (cached) {
404 ascii_otel_metric(ASCII_OTEL_METRIC_YTDLP_HIT, 1);
405 ASCII_OTEL_ATTR_STRING("cache.result", *output_url ? "hit" : "negative_hit");
406 if (!*output_url) {
407 ascii_otel_metric(ASCII_OTEL_METRIC_YTDLP_NEGATIVE, 1);
409 }
410 return ASCIICHAT_OK;
411 }
412
413 ascii_otel_metric(ASCII_OTEL_METRIC_YTDLP_MISS, 1);
414 ASCII_OTEL_ATTR_STRING("cache.result", "miss");
415
416 enum { YT_DLP_MAX_ARGS = 64 };
417 const char *argv[YT_DLP_MAX_ARGS];
418 char option_storage[MEDIA_YT_DLP_YT_DLP_OPTIONS_BUFFER_SIZE];
419 size_t argc = 0;
420 argv[argc++] = "yt-dlp";
421 argv[argc++] = "--quiet";
422 argv[argc++] = "--no-warnings";
423 argv[argc++] = "--no-playlist";
424 argv[argc++] = "--user-agent";
425 argv[argc++] = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36";
426 argv[argc++] = "-f";
427 // Only select separate streams when no combined format is available.
428 // Prefer H.264 for fallback: some FFmpeg builds cannot decode AV1.
429 argv[argc++] = "b/bv[vcodec^=avc1]+ba/bv+ba";
430 // A user-provided -f or --format overrides the default selector.
431 if (!yt_dlp_parse_options(yt_dlp_options, option_storage, sizeof(option_storage), argv, &argc, YT_DLP_MAX_ARGS - 5)) {
432 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid or too long yt-dlp options");
433 }
434 // Pass the explicit yt-dlp proxy to the independent FFmpeg requests.
435 bool skip_proxy_value = false;
436 for (size_t i = 0; i < argc; ++i) {
437 if (skip_proxy_value) {
438 skip_proxy_value = false;
439 continue;
440 }
441 const char *proxy = NULL;
442 if (!strcmp(argv[i], "--proxy") && i + 1 < argc) {
443 proxy = argv[i + 1];
444 skip_proxy_value = true;
445 } else if (!strncmp(argv[i], "--proxy=", sizeof("--proxy=") - 1)) {
446 proxy = argv[i] + sizeof("--proxy=") - 1;
447 }
448 if (proxy) {
449 const size_t proxy_length = strlen(proxy);
450 if (proxy_length >= sizeof(video_request->proxy))
451 return SET_ERRNO(ERROR_NOT_SUPPORTED, "yt-dlp proxy is too long for media requests");
452 memcpy(video_request->proxy, proxy, proxy_length + sizeof(char));
453 memcpy(audio_request->proxy, proxy, proxy_length + sizeof(char));
454 video_request->proxy_specified = audio_request->proxy_specified = true;
455 }
456 }
457 if (video_request->proxy_specified && video_request->proxy[0] &&
458 strncmp(video_request->proxy, "http://", sizeof("http://") - 1))
460 "FFmpeg media requests require an HTTP proxy; the yt-dlp proxy is unsupported");
461 if (!yt_dlp_is_available_interruptible(cancel, data)) {
462 return SET_ERRNO(ERROR_YOUTUBE_EXTRACT_FAILED, "yt-dlp is not installed. Please install it with: "
463 "pip install yt-dlp (or: brew install yt-dlp on macOS)");
464 }
465 argv[argc++] = "--print";
466 argv[argc++] =
467 "{\"base\":%(.{url,http_headers,cookies})j,\"formats\":%(requested_formats.:.{url,http_headers,cookies}|null)j}";
468 argv[argc++] = "--";
469 argv[argc++] = url;
470 argv[argc] = NULL;
471
472 // Two URLs can each occupy the full stream URL capacity, plus diagnostics.
474 int status = 0;
475 // Interactive extractors need their prompts visible on the real terminal.
476 LOG_IO_IF(!platform_isatty(STDIN_FILENO), "yt-dlp",
477 { status = platform_execute_subprocess("yt-dlp", argv, output, sizeof(output), cancel, data); });
478 if (cancel && cancel(data)) {
480 }
481 if (status != 0 || strlen(output) == sizeof(output) - 1) {
482 static_mutex_lock_named(&g_yt_dlp_cache_lock, "yt_dlp_cache");
483 yt_dlp_cache_set(url, yt_dlp_options, NULL, NULL, video_request, audio_request);
484 static_mutex_unlock(&g_yt_dlp_cache_lock);
485 log_error("yt-dlp failed to extract streams (exit %d)", status);
486 return SET_ERRNO(ERROR_YOUTUBE_EXTRACT_FAILED, "yt-dlp failed to extract streams");
487 }
488
489 size_t count = 0;
490 yyjson_doc *doc = yyjson_read(output, strlen(output), 0);
491 yyjson_val *root = doc ? yyjson_doc_get_root(doc) : NULL;
492 yyjson_val *base = yt_dlp_object_get(root, "base");
493 yyjson_val *formats = yt_dlp_object_get(root, "formats");
494 if (!yyjson_is_arr(formats))
495 formats = NULL;
496 size_t total = yyjson_is_arr(formats) ? yyjson_arr_size(formats) : 1;
497 if (!yyjson_is_obj(base) || total > 2) {
498 if (doc)
499 yyjson_doc_free(doc);
500 return SET_ERRNO(ERROR_YOUTUBE_EXTRACT_FAILED, "yt-dlp returned invalid stream metadata");
501 }
502 // Walk the array once; indexed lookup in yyjson uses an unsigned countdown
503 // that trips integer sanitizers on non-flat format objects.
504 yyjson_arr_iter format_iter = yyjson_arr_iter_with(formats);
505 for (size_t i = 0; i < total; ++i) {
506 yyjson_val *format = formats ? yyjson_arr_iter_next(&format_iter) : base;
507 yyjson_val *stream_value = yt_dlp_object_get(format, "url");
508 const char *stream = yyjson_get_str(stream_value);
509 char *dest = i == 0 ? output_url : audio_url;
510 size_t capacity = i == 0 ? output_size : audio_size;
511 yt_dlp_request_options_t *request = i == 0 ? video_request : audio_request;
512 const size_t stream_length = stream ? strlen(stream) : 0;
513 if (stream && request->proxy_specified && request->proxy[0] &&
514 platform_strncasecmp(stream, "http://", sizeof("http://") - 1) &&
515 platform_strncasecmp(stream, "https://", sizeof("https://") - 1)) {
516 yyjson_doc_free(doc);
517 output_url[0] = audio_url[0] = '\0';
518 return SET_ERRNO(ERROR_NOT_SUPPORTED, "The selected proxy cannot be used with this media protocol");
519 }
520 if (!stream || stream_length != yyjson_get_len(stream_value) || !yt_dlp_is_stream_url(stream) ||
521 stream_length >= capacity || !yt_dlp_parse_request(format, base, stream, request)) {
522 yyjson_doc_free(doc);
523 output_url[0] = audio_url[0] = '\0';
524 return SET_ERRNO(ERROR_YOUTUBE_EXTRACT_FAILED, "yt-dlp returned invalid stream metadata");
525 }
526 memcpy(dest, stream, stream_length + sizeof(char));
527 ++count;
528 }
529 yyjson_doc_free(doc);
530 if (count == 0) {
531 static_mutex_lock_named(&g_yt_dlp_cache_lock, "yt_dlp_cache");
532 yt_dlp_cache_set(url, yt_dlp_options, NULL, NULL, video_request, audio_request);
533 static_mutex_unlock(&g_yt_dlp_cache_lock);
534 log_error("yt-dlp returned no playable stream URL");
535 return SET_ERRNO(ERROR_YOUTUBE_EXTRACT_FAILED, "yt-dlp returned no playable formats");
536 }
537 static_mutex_lock_named(&g_yt_dlp_cache_lock, "yt_dlp_cache");
538 yt_dlp_cache_set(url, yt_dlp_options, output_url, audio_url, video_request, audio_request);
539 static_mutex_unlock(&g_yt_dlp_cache_lock);
540 log_debug("yt-dlp extracted %zu stream URL(s)", count);
541 return ASCIICHAT_OK;
542}
#define SET_ERRNO(code,...)
@ ASCIICHAT_OK
Definition error_codes.h:46
@ ERROR_YOUTUBE_EXTRACT_FAILED
@ ERROR_INVALID_PARAM
@ ERROR_NOT_SUPPORTED
Definition error_codes.h:69
#define log_error(...)
Log an ERROR message.
Definition log/log.h:634
#define log_debug(...)
Log a DEBUG message.
Definition log/log.h:595
int platform_execute_subprocess(const char *executable, const char **argv, char *output_buffer, size_t output_size, bool(*cancel)(void *), void *data)
Execute a subprocess and optionally capture its output.
#define LOG_IO_IF(condition, prefix, block)
Capture output from a code block and log it.
Definition io.h:93
void ascii_otel_metric(ascii_otel_metric_t metric, double value)
Definition otel.c:161
#define ASCII_OTEL_SCOPE(id)
Definition otel.h:72
#define ASCII_OTEL_ATTR_STRING(key, value)
Definition otel.h:66
char proxy[YT_DLP_PROXY_CAPACITY]
Definition yt_dlp.h:22
#define MEDIA_YT_DLP_FULL_OUTPUT_BUFFER_SIZE
Definition yt_dlp.c:4
#define MEDIA_YT_DLP_YT_DLP_OPTIONS_BUFFER_SIZE
Definition yt_dlp.c:3

References ASCII_OTEL_ATTR_STRING, ascii_otel_metric(), ASCII_OTEL_SCOPE, ASCIICHAT_OK, ERROR_INVALID_PARAM, ERROR_NOT_SUPPORTED, ERROR_YOUTUBE_EXTRACT_FAILED, log_debug, log_error, LOG_IO_IF, MEDIA_STREAM_URL_SIZE, MEDIA_YT_DLP_FULL_OUTPUT_BUFFER_SIZE, MEDIA_YT_DLP_YT_DLP_OPTIONS_BUFFER_SIZE, platform_execute_subprocess(), yt_dlp_request_options_t::proxy, yt_dlp_request_options_t::proxy_specified, and SET_ERRNO.

Referenced by yt_dlp_extract_stream_urls_interruptible().