148 {
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;
152 }
153
154
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;
159 }
160
161
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') {
165
166 SAFE_STRNCPY(output_url, cached_url, output_size - 1);
167 output_url[output_size - 1] = '\0';
168 return ASCIICHAT_OK;
169 } else {
170
171 return ERROR_YOUTUBE_EXTRACT_FAILED;
172 }
173 }
174
175
176 char command[4096];
177 const char *opts = yt_dlp_options ? yt_dlp_options : "";
178
179 int cmd_ret;
180 if (opts[0] != '\0') {
181
183 "yt-dlp --quiet --no-warnings "
184 "--user-agent 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) "
185 "AppleWebKit/537.36' "
186 "%s "
187 "-f 'b' -O '%%(url)s' '%s' 2>&1",
188 opts, url);
189 } else {
190
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",
196 url);
197 }
198
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;
202 }
203
204 log_debug("Executing yt-dlp: %s", command);
205
206
207 FILE *pipe = NULL;
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;
211 }
212
213
214 char url_buffer[8192] = {0};
215 char full_output[16384] = {0};
216 size_t ytdlp_output_len = 0;
217 size_t url_size = 0;
218 int c;
219
220 while ((c = fgetc(pipe)) != EOF && ytdlp_output_len < sizeof(full_output) - 1) {
221 full_output[ytdlp_output_len++] = (char)c;
222
223
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') {
227 if (url_size > 0) {
228 url_buffer[url_size] = '\0';
230 break;
231 }
232 }
233 url_size = 0;
234 }
235 }
236 full_output[ytdlp_output_len] = '\0';
237 url_buffer[url_size] = '\0';
238
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);
242
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);
247 } else {
248 SET_ERRNO(ERROR_YOUTUBE_EXTRACT_FAILED, "yt-dlp failed to extract stream");
249 }
250 return ERROR_YOUTUBE_EXTRACT_FAILED;
251 }
252
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;
258 }
259
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;
265 }
266
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;
270 }
271
272 SAFE_STRNCPY(output_url, url_buffer, output_size - 1);
273 output_url[output_size - 1] = '\0';
274
275
276 yt_dlp_cache_set(url, yt_dlp_options, output_url);
277
278 log_debug("yt-dlp successfully extracted stream URL (%zu bytes)", url_size);
279 return ASCIICHAT_OK;
280}
int safe_snprintf(char *buffer, size_t buffer_size, const char *format,...)
Safe formatted string printing to buffer.
bool url_is_valid(const char *url)
bool yt_dlp_is_available(void)