148 size_t output_size) {
149 if (!url || !output_url || output_size < 256) {
150 SET_ERRNO(ERROR_INVALID_PARAM,
"Invalid parameters for yt-dlp URL extraction");
151 return ERROR_INVALID_PARAM;
156 SET_ERRNO(ERROR_YOUTUBE_EXTRACT_FAILED,
"yt-dlp is not installed. Please install it with: "
157 "pip install yt-dlp (or: brew install yt-dlp on macOS)");
158 return ERROR_YOUTUBE_EXTRACT_FAILED;
162 char cached_url[8192] = {0};
163 if (yt_dlp_cache_get(url, yt_dlp_options, cached_url,
sizeof(cached_url))) {
164 if (cached_url[0] !=
'\0') {
166 SAFE_STRNCPY(output_url, cached_url, output_size - 1);
167 output_url[output_size - 1] =
'\0';
171 return ERROR_YOUTUBE_EXTRACT_FAILED;
177 const char *opts = yt_dlp_options ? yt_dlp_options :
"";
180 if (opts[0] !=
'\0') {
183 "yt-dlp --quiet --no-warnings "
184 "--user-agent 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) "
185 "AppleWebKit/537.36' "
187 "-f 'b' -O '%%(url)s' '%s' 2>&1",
192 "yt-dlp --quiet --no-warnings "
193 "--user-agent 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) "
194 "AppleWebKit/537.36' "
195 "-f 'b' -O '%%(url)s' '%s' 2>&1",
199 if (cmd_ret < 0 || cmd_ret >= (
int)
sizeof(command)) {
200 SET_ERRNO(ERROR_INVALID_PARAM,
"URL or yt-dlp options too long");
201 return ERROR_INVALID_PARAM;
204 log_debug(
"Executing yt-dlp: %s", command);
208 if (platform_popen(command,
"r", &pipe) != ASCIICHAT_OK || !pipe) {
209 SET_ERRNO(ERROR_YOUTUBE_EXTRACT_FAILED,
"Failed to execute yt-dlp subprocess");
210 return ERROR_YOUTUBE_EXTRACT_FAILED;
214 char url_buffer[8192] = {0};
215 char full_output[16384] = {0};
216 size_t ytdlp_output_len = 0;
220 while ((c = fgetc(pipe)) != EOF && ytdlp_output_len <
sizeof(full_output) - 1) {
221 full_output[ytdlp_output_len++] = (char)c;
224 if ((url_size == 0 && c ==
'h') || (url_size > 0 && c !=
'\n' && url_size <
sizeof(url_buffer) - 1)) {
225 url_buffer[url_size++] = (char)c;
226 }
else if (c ==
'\n') {
228 url_buffer[url_size] =
'\0';
236 full_output[ytdlp_output_len] =
'\0';
237 url_buffer[url_size] =
'\0';
239 int pclose_ret = (platform_pclose(&pipe) == ASCIICHAT_OK) ? 0 : -1;
240 if (pclose_ret != 0) {
241 yt_dlp_cache_set(url, yt_dlp_options, NULL);
243 log_debug(
"yt-dlp exited with code %d", pclose_ret);
244 if (ytdlp_output_len > 0) {
245 log_error(
"yt-dlp output:\n%s", full_output);
246 SET_ERRNO(ERROR_YOUTUBE_EXTRACT_FAILED,
"yt-dlp failed to extract stream: %s", full_output);
248 SET_ERRNO(ERROR_YOUTUBE_EXTRACT_FAILED,
"yt-dlp failed to extract stream");
250 return ERROR_YOUTUBE_EXTRACT_FAILED;
253 if (url_size == 0 || (url_size == 2 && strncmp(url_buffer,
"NA", 2) == 0)) {
254 yt_dlp_cache_set(url, yt_dlp_options, NULL);
255 log_error(
"yt-dlp returned empty output for URL: %s", url);
256 SET_ERRNO(ERROR_YOUTUBE_EXTRACT_FAILED,
"yt-dlp returned no playable formats");
257 return ERROR_YOUTUBE_EXTRACT_FAILED;
260 if (url_buffer[0] !=
'h' || strncmp(url_buffer,
"http", 4) != 0) {
261 yt_dlp_cache_set(url, yt_dlp_options, NULL);
262 log_error(
"Invalid URL from yt-dlp: %s (full output: %s)", url_buffer, full_output);
263 SET_ERRNO(ERROR_YOUTUBE_EXTRACT_FAILED,
"yt-dlp returned invalid URL");
264 return ERROR_YOUTUBE_EXTRACT_FAILED;
267 if (url_size >= output_size) {
268 SET_ERRNO(ERROR_INVALID_PARAM,
"Stream URL too long for output buffer (%zu bytes, max %zu)", url_size, output_size);
269 return ERROR_INVALID_PARAM;
272 SAFE_STRNCPY(output_url, url_buffer, output_size - 1);
273 output_url[output_size - 1] =
'\0';
276 yt_dlp_cache_set(url, yt_dlp_options, output_url);
278 log_debug(
"yt-dlp successfully extracted stream URL (%zu bytes)", url_size);
Cache entry for extracted stream URLs.