Merge branch 'quvi_resolve_#45'
[quvi.git] / src / quvi.c
blob8696b0fc202a88e7d4de511997074dae72ea1dce
1 /* quvi
2 * Copyright (C) 2009,2010,2011 Toni Gundogdu <legatvs@gmail.com>
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2.1 of the License, or (at your option) any later version.
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Lesser General Public License for more details.
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with this library; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
17 * 02110-1301 USA
20 /* quvi.c - query media tool. */
22 #include "config.h"
24 #include <stdio.h>
25 #include <stdlib.h>
26 #include <stdarg.h>
27 #include <string.h>
28 #include <assert.h>
30 #include <curl/curl.h>
32 #include "platform.h"
34 #include "quvi/quvi.h"
35 #include "cmdline.h"
37 #include "llst.h"
39 #define _free(p) \
40 do { if (p) { free(p); p=0; } } while (0)
42 /* strepl.c */
43 extern char *strepl(const char *s, const char *what, const char *with);
45 static int verbose_flag = 1;
46 static quvi_t quvi = NULL;
47 static CURL *curl = NULL;
49 typedef struct gengetopt_args_info *opts_t;
50 static opts_t opts = NULL;
52 static llst_node_t inputs = NULL;
54 /* prints to std(e)rr. */
55 static void spew_e(const char *fmt, ...)
57 va_list ap;
58 va_start(ap, fmt);
59 vfprintf(stderr, fmt, ap);
60 va_end(ap);
63 /* respects (q)uiet, prints to std(e)rr. */
64 static void spew_qe(const char *fmt, ...)
66 va_list ap;
67 if (!verbose_flag)
68 return;
69 va_start(ap, fmt);
70 vfprintf(stderr, fmt, ap);
71 va_end(ap);
74 /* glorified printf. */
75 static void spew(const char *fmt, ...)
77 va_list ap;
78 va_start(ap, fmt);
79 vfprintf(stdout, fmt, ap);
80 va_end(ap);
83 static void handle_resolve_status(quvi_word type)
85 if (type == QUVISTATUSTYPE_DONE)
86 spew_qe("done.\n");
87 else
88 spew_qe(":: Check for URL redirection ...");
91 static void handle_fetch_status(quvi_word type, void *p)
93 switch (type)
95 default:
96 spew_qe(":: Fetch %s ...", (char *)p);
97 break;
98 case QUVISTATUSTYPE_CONFIG:
99 spew_qe(":: Fetch config ...");
100 break;
101 case QUVISTATUSTYPE_PLAYLIST:
102 spew_qe(":: Fetch playlist ...");
103 break;
104 case QUVISTATUSTYPE_DONE:
105 spew_qe("done.\n");
106 break;
110 static void handle_verify_status(quvi_word type)
112 switch (type)
114 default:
115 spew_qe(":: Verify media URL ...");
116 break;
117 case QUVISTATUSTYPE_DONE:
118 spew_qe("done.\n");
119 break;
123 static int status_callback(long param, void *data)
125 quvi_word status, type;
127 status = quvi_loword(param);
128 type = quvi_hiword(param);
130 switch (status)
132 case QUVISTATUS_RESOLVE:
133 handle_resolve_status(type);
134 break;
136 case QUVISTATUS_FETCH:
137 handle_fetch_status(type, data);
138 break;
140 case QUVISTATUS_VERIFY:
141 handle_verify_status(type);
142 break;
145 fflush(stderr);
147 return (0);
150 static size_t write_callback(void *p, size_t size, size_t nmemb,
151 void *data)
153 size_t r = quvi_write_callback_default(p, size, nmemb, data);
154 /* Could do something useful here. */
155 #ifdef _0
156 puts(__func__);
157 #endif
158 return r;
161 /* Divided into smaller blocks. Otherwise -pedantic objects. */
163 #define LICENSE_1 \
164 "/* quvi\n" \
165 " * Copyright (C) 2009,2010,2011 Toni Gundogdu <legatvs@gmail.com>\n"
167 #define LICENSE_2 \
168 " * This library is free software; you can redistribute it and/or\n" \
169 " * modify it under the terms of the GNU Lesser General Public\n" \
170 " * License as published by the Free Software Foundation; either\n" \
171 " * version 2.1 of the License, or (at your option) any later version.\n"
173 #define LICENSE_3 \
174 " * This library is distributed in the hope that it will be useful,\n" \
175 " * but WITHOUT ANY WARRANTY; without even the implied warranty of\n" \
176 " * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU\n" \
177 " * Lesser General Public License for more details.\n"
179 #define LICENSE_4 \
180 " * You should have received a copy of the GNU Lesser General Public\n" \
181 " * License along with this library; if not, write to the Free Software\n" \
182 " * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA\n" \
183 " * 02110-1301 USA\n" " */"
185 static void license()
187 printf("%s *\n%s *\n%s *\n%s\n",
188 LICENSE_1, LICENSE_2, LICENSE_3, LICENSE_4);
189 exit(0);
192 #undef LICENSE_4
193 #undef LICENSE_3
194 #undef LICENSE_2
195 #undef LICENSE_1
197 static void version()
199 printf("quvi version %s\n", quvi_version(QUVI_VERSION_LONG));
200 exit(0);
203 static void dump_host(char *domain, char *formats)
205 printf("%s\t%s\n", domain, formats);
206 quvi_free(domain);
207 quvi_free(formats);
210 /* Wraps quvi_supported. */
211 static void supported(quvi_t quvi)
213 QUVIcode rc = QUVI_NOSUPPORT;
214 int i;
216 for (i=0; i<opts->inputs_num; ++i)
218 rc = quvi_supported(quvi, (char *)opts->inputs[i]);
219 if (rc == QUVI_OK)
220 spew_qe("%s: OK\n", (char *)opts->inputs[i]);
221 else
222 spew_qe("error: %s\n", quvi_strerror(quvi, rc));
225 exit(rc);
228 static void format_help(quvi_t quvi)
230 int quit = 0;
232 if (strcmp(opts->format_arg, "help") == 0)
234 printf("Usage:\n"
235 " --format arg get format arg\n"
236 " --format list list websites and supported formats\n"
237 " --format list arg match arg to websites, list formats for matches\n"
238 "Examples:\n"
239 " --format mp4_360p get format mp4_360p (youtube)\n"
240 " --format list youtube list youtube formats\n"
241 " --format list dailym list dailym(otion) formats\n"
243 quit = 1;
246 else if (strcmp(opts->format_arg, "list") == 0)
248 int done = 0;
249 char *d, *f;
251 while(!done)
253 const int rc =
254 quvi_next_supported_website(quvi, &d, &f);
256 switch (rc)
259 case QUVI_OK:
261 int print = 1;
263 /* -f list <pattern> */
264 if (opts->inputs_num > 0)
265 print = strstr(d, (char *)opts->inputs[0]) != 0;
267 /* -f list */
268 if (print)
269 printf("%s:\n %s\n\n", d, f);
271 quvi_free(d);
272 quvi_free(f);
274 break;
276 case QUVI_LAST:
277 done = 1;
278 break;
280 default:
281 spew_e("%s\n", quvi_strerror(quvi, rc));
282 break;
285 quit = 1;
288 if (quit)
289 exit(0);
292 /* dumps all supported hosts to stdout. */
293 static void support(quvi_t quvi)
295 int done = 0;
297 if (opts->inputs_num > 0)
298 supported(quvi);
300 while (!done)
302 char *domain, *formats;
303 QUVIcode rc;
305 rc = quvi_next_supported_website(quvi, &domain, &formats);
307 switch (rc)
309 case QUVI_OK:
310 dump_host(domain, formats);
311 break;
312 case QUVI_LAST:
313 done = 1;
314 break;
315 default:
316 spew_e("%s\n", quvi_strerror(quvi, rc));
317 break;
321 exit(0);
324 static void invoke_exec(quvi_media_t media)
326 char *cmd, *media_url, *q_media_url;
327 int rc;
329 quvi_getprop(media, QUVIPROP_MEDIAURL, &media_url);
331 asprintf(&q_media_url, "\"%s\"", media_url);
333 cmd = strdup(opts->exec_arg);
334 cmd = strepl(cmd, "%u", q_media_url);
336 _free(q_media_url);
338 rc = system(cmd);
340 switch (rc)
342 case 0:
343 break;
344 case -1:
345 spew_e("error: failed to execute `%s'\n", cmd);
346 break;
347 default:
348 spew_e("error: child exited with: %d\n", rc >> 8);
349 break;
352 _free(cmd);
355 struct parsed_link_s
357 char *media_url;
358 char *content_type;
359 double content_length;
360 char *file_suffix;
363 typedef struct parsed_link_s *parsed_link_t;
365 static void dump_media_link_xml(parsed_link_t p, int i)
367 char *media_url = curl_easy_escape(curl, p->media_url, 0);
369 spew(" <link id=\"%d\">\n", i);
371 if (p->content_length)
372 spew(" <length_bytes>%.0f</length_bytes>\n", p->content_length);
374 if (strlen(p->content_type))
375 spew(" <content_type>%s</content_type>\n", p->content_type);
377 if (strlen(p->file_suffix))
378 spew(" <file_suffix>%s</file_suffix>\n", p->file_suffix);
380 spew(" <url>%s</url>\n"
381 " </link>\n",
382 media_url ? media_url : p->media_url);
384 _free(media_url);
387 static void dump_media_link_old(parsed_link_t p, int i)
389 spew("link %02d : %s\n", i, p->media_url);
391 if (p->content_length)
392 spew(":: length: %.0f\n", p->content_length);
394 if (strlen(p->file_suffix))
395 spew(":: suffix: %s\n", p->file_suffix);
397 if (strlen(p->content_type))
398 spew(":: content-type: %s\n", p->content_type);
401 static void dump_media_link_json(parsed_link_t p, int i)
403 spew(" {\n"
404 " \"id\": \"%d\",\n", i);
406 if (p->content_length)
407 spew(" \"length_bytes\": \"%.0f\",\n", p->content_length);
409 if (strlen(p->content_type))
410 spew(" \"content_type\": \"%s\",\n", p->content_type);
412 if (strlen(p->file_suffix))
413 spew(" \"file_suffix\": \"%s\",\n", p->file_suffix);
415 spew(" \"url\": \"%s\"\n"
416 " }%s\n",
417 p->media_url,
418 i > 1 ? "," : "");
421 static void dump_media_links(quvi_media_t media)
423 int i = 0;
426 struct parsed_link_s p;
428 memset(&p, 0, sizeof(&p));
430 quvi_getprop(media, QUVIPROP_MEDIAURL, &p.media_url);
431 quvi_getprop(media, QUVIPROP_MEDIACONTENTTYPE, &p.content_type);
432 quvi_getprop(media, QUVIPROP_MEDIACONTENTLENGTH, &p.content_length);
433 quvi_getprop(media, QUVIPROP_FILESUFFIX, &p.file_suffix);
435 ++i;
437 if (opts->xml_given)
438 dump_media_link_xml(&p,i);
439 else if (opts->old_given)
440 dump_media_link_old(&p,i);
441 else
442 dump_media_link_json(&p,i);
444 while (quvi_next_media_url(media) == QUVI_OK);
447 struct parsed_s
449 char *page_url;
450 char *page_title;
451 char *media_id;
452 char *format;
453 char *host;
454 char *start_time;
455 char *thumb_url;
456 double duration;
459 typedef struct parsed_s *parsed_t;
461 static void dump_media_xml(parsed_t p)
463 char *e_page_url, *e_thumb_url;
465 e_page_url = curl_easy_escape(curl, p->page_url, 0);
466 e_thumb_url = curl_easy_escape(curl, p->thumb_url, 0);
468 spew("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
469 "<media id=\"%s\" host=\"%s\">\n"
470 " <format_requested>%s</format_requested>\n"
471 " <page_title>%s</page_title>\n"
472 " <page_url>%s</page_url>\n",
473 p->media_id,
474 p->host,
475 p->format,
476 p->page_title,
477 e_page_url ? e_page_url : "");
479 if (strlen(p->start_time))
480 spew(" <start_time>%s</start_time>\n", p->start_time);
482 if (e_thumb_url && strlen(e_thumb_url))
483 spew(" <thumbnail_url>%s</thumbnail_url>\n", e_thumb_url);
485 if (p->duration)
486 spew(" <duration>%.0f</duration>\n", p->duration);
488 if (e_page_url)
490 curl_free(e_page_url);
491 e_page_url = NULL;
494 if (e_thumb_url)
496 curl_free(e_thumb_url);
497 e_thumb_url = NULL;
501 static void dump_media_old(parsed_t p)
503 spew(" > Dump media:\n"
504 "host : %s\n"
505 "url : %s\n"
506 "title : %s\n"
507 "id : %s\n"
508 "format : %s (requested)\n",
509 p->host,
510 p->page_url,
511 p->page_title,
512 p->media_id,
513 p->format);
515 if (strlen(p->start_time))
516 spew("start time: %s\n", p->start_time);
518 if (strlen(p->thumb_url))
519 spew("thumbnail url: %s\n", p->thumb_url);
521 if (p->duration)
522 spew("duration: %.0f\n", p->duration);
525 static void dump_media_json(parsed_t p)
527 char *t;
529 t = strdup(p->page_title);
530 t = strepl(t, "\"", "\\\"");
532 spew("{\n"
533 " \"host\": \"%s\",\n"
534 " \"page_title\": \"%s\",\n"
535 " \"page_url\": \"%s\",\n"
536 " \"id\": \"%s\",\n"
537 " \"format_requested\": \"%s\",\n",
538 p->host,
540 p->page_url,
541 p->media_id,
542 p->format);
544 if (strlen(p->start_time))
545 spew(" \"start_time\": \"%s\",\n", p->start_time);
547 if (strlen(p->thumb_url))
548 spew(" \"thumbnail_url\": \"%s\",\n", p->thumb_url);
550 if (p->duration)
551 spew(" \"duration\": \"%.0f\",\n", p->duration);
553 spew(" \"link\": [\n");
555 _free(t);
558 static void dump_media(quvi_media_t media)
560 struct parsed_s p;
562 memset(&p, 0, sizeof(p));
564 quvi_getprop(media, QUVIPROP_HOSTID, &p.host);
565 quvi_getprop(media, QUVIPROP_PAGEURL, &p.page_url);
566 quvi_getprop(media, QUVIPROP_PAGETITLE, &p.page_title);
567 quvi_getprop(media, QUVIPROP_MEDIAID, &p.media_id);
568 quvi_getprop(media, QUVIPROP_FORMAT, &p.format);
569 quvi_getprop(media, QUVIPROP_STARTTIME, &p.start_time);
570 quvi_getprop(media, QUVIPROP_MEDIATHUMBNAILURL, &p.thumb_url);
571 quvi_getprop(media, QUVIPROP_MEDIADURATION, &p.duration);
573 if (opts->xml_given)
574 dump_media_xml(&p);
575 else if (opts->old_given)
576 dump_media_old(&p);
577 else
578 dump_media_json(&p);
580 dump_media_links(media);
582 if (opts->xml_given)
583 spew("</media>\n");
584 else if (opts->old_given) ;
585 else
586 spew(" ]\n}\n");
589 static void dump_error(quvi_t quvi, QUVIcode rc)
591 fprintf(stderr, "error: %s\n", quvi_strerror(quvi, rc));
594 static quvi_t init_quvi()
596 QUVIcode rc;
597 quvi_t quvi;
599 if ((rc = quvi_init(&quvi)) != QUVI_OK)
601 dump_error(quvi, rc);
602 exit(rc);
604 assert(quvi != 0);
606 /* Set quvi options. */
608 if (opts->format_given)
609 quvi_setopt(quvi, QUVIOPT_FORMAT, opts->format_arg);
611 quvi_setopt(quvi, QUVIOPT_NORESOLVE, opts->no_resolve_given);
612 quvi_setopt(quvi, QUVIOPT_NOVERIFY, opts->no_verify_given);
614 if (opts->category_all_given)
615 quvi_setopt(quvi, QUVIOPT_CATEGORY, QUVIPROTO_ALL);
616 else
618 long n = 0;
619 if (opts->category_http_given)
620 n |= QUVIPROTO_HTTP;
621 if (opts->category_mms_given)
622 n |= QUVIPROTO_MMS;
623 if (opts->category_rtsp_given)
624 n |= QUVIPROTO_RTSP;
625 if (opts->category_rtmp_given)
626 n |= QUVIPROTO_RTMP;
627 if (n > 0)
628 quvi_setopt(quvi, QUVIOPT_CATEGORY, n);
631 quvi_setopt(quvi, QUVIOPT_STATUSFUNCTION, status_callback);
632 quvi_setopt(quvi, QUVIOPT_WRITEFUNCTION, write_callback);
634 /* Use the quvi created cURL handle. */
636 quvi_getinfo(quvi, QUVIINFO_CURL, &curl);
637 assert(curl != 0);
639 if (opts->agent_given)
640 curl_easy_setopt(curl, CURLOPT_USERAGENT, opts->agent_arg);
642 if (opts->proxy_given)
643 curl_easy_setopt(curl, CURLOPT_PROXY, opts->proxy_arg);
645 if (opts->no_proxy_given)
646 curl_easy_setopt(curl, CURLOPT_PROXY, "");
648 curl_easy_setopt(curl, CURLOPT_VERBOSE, opts->verbose_libcurl_given);
650 curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT,
651 opts->connect_timeout_arg);
653 return (quvi);
656 static void cleanup()
658 llst_node_t curr = inputs;
660 while (curr)
662 _free(curr->data);
663 curr = curr->next;
665 llst_free(&inputs);
666 assert(inputs == NULL);
668 if (quvi)
669 quvi_close(&quvi);
670 assert(quvi == NULL);
672 if (opts)
674 cmdline_parser_free(opts);
675 _free(opts);
677 assert(opts == NULL);
680 static void read_stdin()
682 char b[256];
683 while (fgets(b, sizeof(b), stdin))
685 if (strlen(b) > 16)
687 const size_t n = strlen(b)-1;
689 if (b[n] == '\n')
690 b[n] = '\0';
692 llst_add(&inputs, strdup(b));
697 static int read_input()
699 if (opts->inputs_num == 0)
700 read_stdin();
701 else
703 int i;
704 for (i=0; i<opts->inputs_num; ++i)
705 llst_add(&inputs, strdup(opts->inputs[i]));
707 return (llst_size(inputs));
710 int main(int argc, char *argv[])
712 const char *url, *home, *no_config, *fname;
713 QUVIcode rc, last_failure;
714 quvi_media_t media;
715 int no_config_flag;
716 int i, inputs_num;
717 llst_node_t curr;
718 int errors;
720 assert(quvi == NULL);
721 assert(curl == NULL);
722 assert(opts == NULL);
723 assert(inputs == NULL);
725 no_config = getenv("QUVI_NO_CONFIG");
726 no_config_flag = 1;
727 url = NULL;
729 home = getenv("QUVI_HOME");
730 if (!home)
731 home = getenv("HOME");
733 #ifndef HOST_W32
734 fname = "/.quvirc";
735 #else
736 fname = "\\quvirc";
737 #endif
739 atexit(cleanup);
741 opts = calloc(1, sizeof(struct gengetopt_args_info));
742 if (!opts)
743 return(QUVI_MEM);
745 /* Init cmdline parser. */
747 if (home && !no_config)
749 char *path;
750 FILE *f;
752 asprintf(&path, "%s%s", home, fname);
753 f = fopen(path, "r");
755 if (f != NULL)
757 struct cmdline_parser_params *pp;
759 fclose(f);
760 f = NULL;
762 pp = cmdline_parser_params_create();
763 pp->check_required = 0;
765 if (cmdline_parser_config_file(path, opts, pp) == 0)
767 pp->initialize = 0;
768 pp->override = 1;
769 pp->check_required = 1;
771 if (cmdline_parser_ext(argc, argv, opts, pp) == 0)
772 no_config_flag = 0;
774 _free(pp);
777 _free(path);
780 if (no_config_flag)
782 if (cmdline_parser(argc, argv, opts) != 0)
783 return (QUVI_INVARG);
786 if (opts->version_given)
787 version(opts);
789 if (opts->license_given)
790 license(opts);
792 verbose_flag = !opts->quiet_given;
794 quvi = init_quvi();
796 if (opts->support_given)
797 support(quvi);
799 if (opts->format_given)
800 format_help(quvi);
802 /* User input */
804 inputs_num = read_input();
806 if (inputs_num == 0)
808 spew_qe("error: no input links\n");
809 return (QUVI_INVARG);
812 last_failure = QUVI_OK;
813 errors = 0;
815 for (i=0, curr=inputs; curr; ++i)
817 rc = quvi_parse(quvi, (char *)curr->data, &media);
818 if (rc == QUVI_OK)
820 assert(media != 0);
821 dump_media(media);
823 if (opts->exec_given)
827 invoke_exec(media);
829 while (quvi_next_media_url(media) == QUVI_OK);
832 else
834 dump_error(quvi,rc);
835 last_failure = rc;
836 ++errors;
838 quvi_parse_close(&media);
839 assert(media == 0);
840 curr = curr->next;
843 if (inputs_num > 1)
845 spew_qe("Results: %d OK, %d failed (last 0x%02x), exit with 0x%02x\n",
846 inputs_num - errors, errors, last_failure, rc);
849 return (rc);
852 /* vim: set ts=2 sw=2 tw=72 expandtab: */