Cleanup
[quvi-tool.git] / src / quvi / quvi.c
blobc60a95df688580784245e8c5caa2044d0a5fed61
1 /* quvi
2 * Copyright (C) 2009-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 <errno.h>
29 #include <assert.h>
31 #include <curl/curl.h>
33 #include "platform.h"
35 #include "quvi/quvi.h"
36 #include "quvi/llst.h"
38 #include "cmdline.h"
40 #define _free(p) \
41 do { if (p) { free(p); p=0; } } while (0)
43 /* strepl.c */
44 extern char *strepl(const char *s, const char *what, const char *with);
46 static int verbose_flag = 1;
47 static quvi_t quvi = NULL;
48 static CURL *curl = NULL;
50 typedef struct gengetopt_args_info *opts_t;
51 static opts_t opts = NULL;
53 static quvi_llst_node_t inputs = NULL;
55 /* prints to std(e)rr. */
56 static void spew_e(const char *fmt, ...)
58 va_list ap;
59 va_start(ap, fmt);
60 vfprintf(stderr, fmt, ap);
61 va_end(ap);
64 /* respects (q)uiet, prints to std(e)rr. */
65 static void spew_qe(const char *fmt, ...)
67 va_list ap;
68 if (!verbose_flag)
69 return;
70 va_start(ap, fmt);
71 vfprintf(stderr, fmt, ap);
72 va_end(ap);
75 /* glorified printf. */
76 static void spew(const char *fmt, ...)
78 va_list ap;
79 va_start(ap, fmt);
80 vfprintf(stdout, fmt, ap);
81 va_end(ap);
84 static void dump_error_json(quvi_t quvi, QUVIcode rc)
86 fprintf(stderr,
87 "{\n"
88 " \"error\": [\n"
89 " {\n"
90 " \"message\": \"%s\"\n"
91 " }\n"
92 " ]\n"
93 "}\n", quvi_strerror(quvi, rc));
96 static void dump_error_xml(quvi_t quvi, QUVIcode rc)
98 fprintf(stderr,
99 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
100 "<error>\n"
101 " <message>%s</message>\n"
102 "</error>\n",
103 quvi_strerror(quvi, rc));
106 static void dump_error(quvi_t quvi, QUVIcode rc)
108 if (opts->export_errors_given)
110 if (opts->xml_given)
111 dump_error_xml(quvi, rc);
112 else
113 dump_error_json(quvi, rc);
115 else
116 fprintf(stderr, "error: %s\n", quvi_strerror(quvi, rc));
119 static void handle_resolve_status(quvi_word type)
121 if (type == QUVISTATUSTYPE_DONE)
122 spew_qe("done.\n");
123 else
124 spew_qe(":: Check for URL redirection ...");
127 static void handle_fetch_status(quvi_word type, void *p)
129 switch (type)
131 default:
132 spew_qe(":: Fetch %s ...", (char *)p);
133 break;
134 case QUVISTATUSTYPE_CONFIG:
135 spew_qe(":: Fetch config ...");
136 break;
137 case QUVISTATUSTYPE_PLAYLIST:
138 spew_qe(":: Fetch playlist ...");
139 break;
140 case QUVISTATUSTYPE_DONE:
141 spew_qe("done.\n");
142 break;
146 static void handle_verify_status(quvi_word type)
148 switch (type)
150 default:
151 spew_qe(":: Verify media URL ...");
152 break;
153 case QUVISTATUSTYPE_DONE:
154 spew_qe("done.\n");
155 break;
159 static int status_callback(long param, void *data)
161 quvi_word status, type;
163 status = quvi_loword(param);
164 type = quvi_hiword(param);
166 switch (status)
168 case QUVISTATUS_RESOLVE:
169 handle_resolve_status(type);
170 break;
172 case QUVISTATUS_FETCH:
173 handle_fetch_status(type, data);
174 break;
176 case QUVISTATUS_VERIFY:
177 handle_verify_status(type);
178 break;
181 fflush(stderr);
183 return (0);
186 /* Divided into smaller blocks. Otherwise -pedantic objects. */
188 #define LICENSE_1 \
189 "/* quvi\n" \
190 " * Copyright (C) 2009-2011 Toni Gundogdu <legatvs@gmail.com>\n"
192 #define LICENSE_2 \
193 " * This library is free software; you can redistribute it and/or\n" \
194 " * modify it under the terms of the GNU Lesser General Public\n" \
195 " * License as published by the Free Software Foundation; either\n" \
196 " * version 2.1 of the License, or (at your option) any later version.\n"
198 #define LICENSE_3 \
199 " * This library is distributed in the hope that it will be useful,\n" \
200 " * but WITHOUT ANY WARRANTY; without even the implied warranty of\n" \
201 " * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU\n" \
202 " * Lesser General Public License for more details.\n"
204 #define LICENSE_4 \
205 " * You should have received a copy of the GNU Lesser General Public\n" \
206 " * License along with this library; if not, write to the Free Software\n" \
207 " * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA\n" \
208 " * 02110-1301 USA\n" " */"
210 static void license()
212 printf("%s *\n%s *\n%s *\n%s\n",
213 LICENSE_1, LICENSE_2, LICENSE_3, LICENSE_4);
214 exit(0);
217 #undef LICENSE_4
218 #undef LICENSE_3
219 #undef LICENSE_2
220 #undef LICENSE_1
222 static void print_version()
224 static const char version[] =
225 #ifdef VN
227 #else
228 PACKAGE_VERSION
229 #endif
230 " for " CANONICAL_TARGET;
231 printf("quvi %s\n libquvi %s\n libquvi-scripts %s\n",
232 version, quvi_version(QUVI_VERSION_LONG),
233 quvi_version(QUVI_VERSION_SCRIPTS));
234 exit(0);
237 static void dump_host(char *domain, char *formats)
239 printf("%s\t%s\n", domain, formats);
240 quvi_free(domain);
241 quvi_free(formats);
244 /* Wraps quvi_supported_ident. */
245 static void supported(quvi_t quvi)
247 #ifndef _QUVI_SUPPORTED
248 quvi_ident_t ident;
249 char *formats;
250 #endif
251 QUVIcode rc;
252 int i;
254 rc = QUVI_NOSUPPORT;
256 for (i=0; i<opts->inputs_num; ++i)
258 rc = quvi_supported_ident(quvi, (char*)opts->inputs[i], &ident);
259 if (rc == QUVI_OK)
261 quvi_ident_getprop(ident, QUVI_IDENT_PROPERTY_FORMATS, &formats);
262 spew("%10s : %s\n", formats, (char *)opts->inputs[i]);
263 quvi_supported_ident_close(&ident);
265 else
266 dump_error(quvi,rc);
269 exit(rc);
272 /* Query which formats are available for the URL */
273 static void query_formats(quvi_t quvi)
275 QUVIcode rc;
276 int i;
278 if (opts->inputs_num < 1)
280 spew_qe("error: no input URLs\n");
281 exit (QUVI_INVARG);
284 for (i=0; i<opts->inputs_num; ++i)
286 char *formats = NULL;
288 rc = quvi_query_formats(quvi, (char*)opts->inputs[i], &formats);
289 if (rc == QUVI_OK)
291 spew("%10s : %s\n", formats, opts->inputs[i]);
292 quvi_free(formats);
294 else
295 dump_error(quvi,rc);
298 exit(rc);
301 /* dumps all supported hosts to stdout. */
302 static void support(quvi_t quvi)
304 int done = 0;
306 if (opts->inputs_num > 0)
307 supported(quvi);
309 while (!done)
311 char *domain, *formats;
312 QUVIcode rc;
314 rc = quvi_next_supported_website(quvi, &domain, &formats);
316 switch (rc)
318 case QUVI_OK:
319 dump_host(domain, formats);
320 break;
321 case QUVI_LAST:
322 done = 1;
323 break;
324 default:
325 spew_e("%s\n", quvi_strerror(quvi, rc));
326 break;
330 exit(0);
333 static void invoke_exec(quvi_media_t media)
335 char *cmd, *media_url, *q_media_url;
336 char *page_title, *q_page_title, *t;
337 int rc;
339 quvi_getprop(media, QUVIPROP_PAGETITLE, &page_title);
340 t = strdup(page_title);
341 t = strepl(t, "\"", "\\\""); /* Escape existing double quotation marks */
342 asprintf(&q_page_title, "\"%s\"", t); /* Put inside quotation marks */
343 _free(t);
345 quvi_getprop(media, QUVIPROP_MEDIAURL, &media_url);
346 asprintf(&q_media_url, "\"%s\"", media_url);
348 cmd = strdup(opts->exec_arg);
349 cmd = strepl(cmd, "%t", q_page_title);
350 cmd = strepl(cmd, "%u", q_media_url);
352 _free(q_page_title);
353 _free(q_media_url);
355 rc = system(cmd);
357 switch (rc)
359 case 0:
360 break;
361 case -1:
362 spew_e("error: failed to execute `%s'\n", cmd);
363 break;
364 default:
365 spew_e("error: child exited with: %d\n", rc >> 8);
366 break;
369 _free(cmd);
372 struct parsed_url_s
374 char *media_url;
375 char *content_type;
376 double content_length;
377 char *file_suffix;
380 typedef struct parsed_url_s *parsed_url_t;
382 static void dump_media_url_xml(parsed_url_t p, int i)
384 char *media_url = curl_easy_escape(curl, p->media_url, 0);
386 spew(" <link id=\"%d\">\n", i);
388 if (p->content_length)
389 spew(" <length_bytes>%.0f</length_bytes>\n", p->content_length);
391 if (strlen(p->content_type))
392 spew(" <content_type>%s</content_type>\n", p->content_type);
394 if (strlen(p->file_suffix))
395 spew(" <file_suffix>%s</file_suffix>\n", p->file_suffix);
397 spew(" <url>%s</url>\n"
398 " </link>\n",
399 media_url ? media_url : p->media_url);
401 _free(media_url);
404 static void dump_media_url_json(parsed_url_t p, int i, int prepend_newln)
406 if (prepend_newln)
407 spew(",\n");
409 spew(" {\n"
410 " \"id\": \"%d\",\n", i);
412 if (p->content_length)
413 spew(" \"length_bytes\": \"%.0f\",\n", p->content_length);
415 if (strlen(p->content_type))
416 spew(" \"content_type\": \"%s\",\n", p->content_type);
418 if (strlen(p->file_suffix))
419 spew(" \"file_suffix\": \"%s\",\n", p->file_suffix);
421 spew(" \"url\": \"%s\"\n"
422 " }",
423 p->media_url);
426 static void dump_media_urls(quvi_media_t media)
428 int json_flag=0, i=1;
431 struct parsed_url_s p;
433 memset(&p, 0, sizeof(&p));
435 quvi_getprop(media, QUVIPROP_MEDIAURL, &p.media_url);
436 quvi_getprop(media, QUVIPROP_MEDIACONTENTTYPE, &p.content_type);
437 quvi_getprop(media, QUVIPROP_MEDIACONTENTLENGTH, &p.content_length);
438 quvi_getprop(media, QUVIPROP_FILESUFFIX, &p.file_suffix);
440 if (opts->xml_given)
441 dump_media_url_xml(&p,i);
442 else
444 dump_media_url_json(&p, i, i>1);
445 json_flag = 1;
447 ++i;
449 while (quvi_next_media_url(media) == QUVI_OK);
451 if (json_flag)
452 spew("\n");
455 struct parsed_s
457 char *page_url;
458 char *page_title;
459 char *media_id;
460 char *format;
461 char *host;
462 char *start_time;
463 char *thumb_url;
464 double duration;
467 typedef struct parsed_s *parsed_t;
469 static void dump_media_xml(parsed_t p)
471 char *e_page_url, *e_thumb_url;
473 e_page_url = curl_easy_escape(curl, p->page_url, 0);
474 e_thumb_url = curl_easy_escape(curl, p->thumb_url, 0);
476 spew("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
477 "<media id=\"%s\" host=\"%s\">\n"
478 " <format_requested>%s</format_requested>\n"
479 " <page_title>%s</page_title>\n"
480 " <page_url>%s</page_url>\n",
481 p->media_id,
482 p->host,
483 p->format,
484 p->page_title,
485 e_page_url ? e_page_url : "");
487 if (strlen(p->start_time))
488 spew(" <start_time>%s</start_time>\n", p->start_time);
490 if (e_thumb_url && strlen(e_thumb_url))
491 spew(" <thumbnail_url>%s</thumbnail_url>\n", e_thumb_url);
493 if (p->duration)
494 spew(" <duration>%.0f</duration>\n", p->duration);
496 if (e_page_url)
498 curl_free(e_page_url);
499 e_page_url = NULL;
502 if (e_thumb_url)
504 curl_free(e_thumb_url);
505 e_thumb_url = NULL;
509 static void dump_media_json(parsed_t p)
511 char *t;
513 t = strdup(p->page_title);
514 t = strepl(t, "\"", "\\\"");
516 spew("{\n"
517 " \"host\": \"%s\",\n"
518 " \"page_title\": \"%s\",\n"
519 " \"page_url\": \"%s\",\n"
520 " \"id\": \"%s\",\n"
521 " \"format_requested\": \"%s\",\n",
522 p->host,
524 p->page_url,
525 p->media_id,
526 p->format);
528 if (strlen(p->start_time))
529 spew(" \"start_time\": \"%s\",\n", p->start_time);
531 if (strlen(p->thumb_url))
532 spew(" \"thumbnail_url\": \"%s\",\n", p->thumb_url);
534 if (p->duration)
535 spew(" \"duration\": \"%.0f\",\n", p->duration);
537 spew(" \"link\": [\n");
539 _free(t);
542 static void dump_media(quvi_media_t media)
544 struct parsed_s p;
546 memset(&p, 0, sizeof(p));
548 quvi_getprop(media, QUVIPROP_HOSTID, &p.host);
549 quvi_getprop(media, QUVIPROP_PAGEURL, &p.page_url);
550 quvi_getprop(media, QUVIPROP_PAGETITLE, &p.page_title);
551 quvi_getprop(media, QUVIPROP_MEDIAID, &p.media_id);
552 quvi_getprop(media, QUVIPROP_FORMAT, &p.format);
553 quvi_getprop(media, QUVIPROP_STARTTIME, &p.start_time);
554 quvi_getprop(media, QUVIPROP_MEDIATHUMBNAILURL, &p.thumb_url);
555 quvi_getprop(media, QUVIPROP_MEDIADURATION, &p.duration);
557 if (opts->xml_given)
558 dump_media_xml(&p);
559 else
560 dump_media_json(&p);
562 dump_media_urls(media);
564 if (opts->xml_given)
565 spew("</media>\n");
566 else
567 spew(" ]\n}\n");
570 static void dump_category_help()
572 fprintf(stderr,
573 "Usage:\n"
574 " quvi --category <value[,value,...]>\n"
575 "Where the values may be:\n"
576 " http, rtmp, rtsp, mms, all\n"
577 "Examples:\n"
578 " quvi --category all ;# program default\n"
579 " quvi --category rtmp,mms ;# multiple categories\n");
580 exit (0);
583 static void check_category(const char *s, QUVIcategory *n)
585 if (strlen(s) == 0)
586 return;
588 if (strcmp(s, "all") == 0)
589 *n = QUVIPROTO_ALL;
590 else if (strcmp(s, "help") == 0)
591 dump_category_help();
592 else
594 if (strcmp(s, "http") == 0)
595 *n |= QUVIPROTO_HTTP;
596 else if (strcmp(s, "rtmp") == 0)
597 *n |= QUVIPROTO_RTMP;
598 else if (strcmp(s, "rtsp") == 0)
599 *n |= QUVIPROTO_RTSP;
600 else if (strcmp(s, "mms") == 0)
601 *n |= QUVIPROTO_MMS;
602 else
604 fprintf(stderr, "warning: --category: %s: invalid value, "
605 "try \"help\"\n", s);
610 static QUVIcategory parse_categories(char *s)
612 QUVIcategory n=0;
613 char b[4], *p=s;
614 size_t i;
616 while (*p != '\0')
618 for (i=0; i<sizeof(b) && *p!='\0'; ++i,++p)
620 if (*p == ',')
622 ++p;
623 break;
625 b[i] = *p;
627 b[i] = '\0';
628 check_category(b, &n);
631 return (n);
634 static void depr_category(const char *o)
636 fprintf(stderr, "warning: %s: deprecated, use --category instead\n", o);
639 static quvi_t init_quvi()
641 QUVIcategory category;
642 QUVIcode rc;
643 quvi_t quvi;
645 if ((rc = quvi_init(&quvi)) != QUVI_OK)
647 dump_error(quvi, rc);
648 exit(rc);
650 assert(quvi != 0);
652 /* Set quvi options. */
654 if (opts->format_given)
655 quvi_setopt(quvi, QUVIOPT_FORMAT, opts->format_arg);
657 quvi_setopt(quvi, QUVIOPT_NORESOLVE, opts->no_resolve_given);
658 quvi_setopt(quvi, QUVIOPT_NOVERIFY, opts->no_verify_given);
660 /* Category. */
662 category = 0;
664 if (opts->category_given)
665 category = parse_categories(opts->category_arg);
667 /* Category: deprecated. To be removed in later versions. */
669 if (opts->category_http_given)
671 depr_category("--category-http");
672 category |= QUVIPROTO_HTTP;
674 if (opts->category_rtmp_given)
676 depr_category("--category-rtmp");
677 category |= QUVIPROTO_RTMP;
679 if (opts->category_rtsp_given)
681 depr_category("--category-rtsp");
682 category |= QUVIPROTO_RTSP;
684 if (opts->category_mms_given)
686 depr_category("--category-mms");
687 category |= QUVIPROTO_MMS;
689 if (opts->category_all_given)
690 depr_category("--category-all");
692 if (category == 0)
693 category = QUVIPROTO_ALL;
695 quvi_setopt(quvi, QUVIOPT_CATEGORY, category);
697 /* Status callback */
699 quvi_setopt(quvi, QUVIOPT_STATUSFUNCTION, status_callback);
701 /* Use the quvi created cURL handle. */
703 quvi_getinfo(quvi, QUVIINFO_CURL, &curl);
704 assert(curl != 0);
706 if (opts->agent_given)
707 curl_easy_setopt(curl, CURLOPT_USERAGENT, opts->agent_arg);
709 if (opts->proxy_given)
710 curl_easy_setopt(curl, CURLOPT_PROXY, opts->proxy_arg);
712 if (opts->no_proxy_given)
713 curl_easy_setopt(curl, CURLOPT_PROXY, "");
715 curl_easy_setopt(curl, CURLOPT_VERBOSE, opts->verbose_libcurl_given);
717 curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT,
718 opts->connect_timeout_arg);
720 return (quvi);
723 static void cleanup()
725 quvi_llst_free(&inputs);
726 assert(inputs == NULL);
728 if (quvi)
729 quvi_close(&quvi);
730 assert(quvi == NULL);
732 if (opts)
734 cmdline_parser_free(opts);
735 _free(opts);
737 assert(opts == NULL);
740 static void read_from(FILE *f, int close)
742 char b[256];
744 if (!f)
745 return;
747 while (fgets(b, sizeof(b), f))
749 if (strlen(b) > 16)
751 const size_t n = strlen(b)-1;
753 if (b[n] == '\n')
754 b[n] = '\0';
756 quvi_llst_append(&inputs, strdup(b));
760 if (close)
762 fclose(f);
763 f = NULL;
767 static char *parse_url_scheme(const char *url)
769 char *p, *r;
771 p = strstr(url, ":/");
772 if (!p)
773 return (NULL);
775 asprintf(&r, "%.*s", (int)(p - url), url);
777 return (r);
780 static int is_url(const char *s)
782 char *p = parse_url_scheme(s);
783 if (p)
785 _free(p);
786 return (1);
788 return (0);
791 static FILE* open_file(const char *path)
793 FILE *f = fopen(path, "rt");
794 if (!f)
795 #ifdef HAVE_STRERROR
796 spew_e("error: %s: %s\n", path, strerror(errno));
797 #else
798 perror("fopen");
799 #endif
800 return (f);
803 static int read_input()
805 if (opts->inputs_num == 0)
806 read_from(stdin, 0);
807 else
809 int i;
810 for (i=0; i<opts->inputs_num; ++i)
812 if (!is_url(opts->inputs[i]))
813 read_from(open_file(opts->inputs[i]), 1);
814 else /* Must be an URL. */
815 quvi_llst_append(&inputs, strdup(opts->inputs[i]));
818 return (quvi_llst_size(inputs));
821 int main(int argc, char *argv[])
823 const char *home, *no_config, *fname;
824 QUVIcode rc, last_failure;
825 quvi_llst_node_t curr;
826 quvi_media_t media;
827 int no_config_flag;
828 int i, inputs_num;
829 int errors;
831 assert(quvi == NULL);
832 assert(curl == NULL);
833 assert(opts == NULL);
834 assert(inputs == NULL);
836 no_config = getenv("QUVI_NO_CONFIG");
837 no_config_flag = 1;
839 home = getenv("QUVI_HOME");
840 if (!home)
841 home = getenv("HOME");
843 #ifndef HOST_W32
844 fname = "/.quvirc";
845 #else
846 fname = "\\quvirc";
847 #endif
849 atexit(cleanup);
851 opts = calloc(1, sizeof(struct gengetopt_args_info));
852 if (!opts)
853 return(QUVI_MEM);
855 /* Init cmdline parser. */
857 if (home && !no_config)
859 char *path;
860 FILE *f;
862 asprintf(&path, "%s%s", home, fname);
863 f = fopen(path, "r");
865 if (f != NULL)
867 struct cmdline_parser_params *pp;
869 fclose(f);
870 f = NULL;
872 pp = cmdline_parser_params_create();
873 pp->check_required = 0;
875 if (cmdline_parser_config_file(path, opts, pp) == 0)
877 pp->initialize = 0;
878 pp->override = 1;
879 pp->check_required = 1;
881 if (cmdline_parser_ext(argc, argv, opts, pp) == 0)
882 no_config_flag = 0;
884 _free(pp);
887 _free(path);
890 if (no_config_flag)
892 if (cmdline_parser(argc, argv, opts) != 0)
893 return (QUVI_INVARG);
896 if (opts->version_given)
897 print_version(opts);
899 if (opts->license_given)
900 license(opts);
902 verbose_flag = !opts->quiet_given;
904 quvi = init_quvi();
906 if (opts->query_formats_given)
907 query_formats(quvi);
909 if (opts->support_given)
910 support(quvi);
912 /* User input */
914 inputs_num = read_input();
916 if (inputs_num == 0)
918 spew_qe("error: no input URLs\n");
919 return (QUVI_INVARG);
922 last_failure = QUVI_OK;
923 errors = 0;
925 for (i=0, curr=inputs; curr; ++i)
927 char *url = quvi_llst_data(curr);
928 rc = quvi_parse(quvi, url, &media);
929 if (rc == QUVI_OK)
931 assert(media != 0);
932 dump_media(media);
934 if (opts->exec_given)
938 invoke_exec(media);
940 while (quvi_next_media_url(media) == QUVI_OK);
943 else
945 dump_error(quvi,rc);
946 last_failure = rc;
947 ++errors;
949 quvi_parse_close(&media);
950 assert(media == 0);
951 curr = quvi_llst_next(curr);
954 if (inputs_num > 1)
956 spew_qe("Results: %d OK, %d failed (last 0x%02x), exit with 0x%02x\n",
957 inputs_num - errors, errors, last_failure, rc);
960 return (rc);
963 /* vim: set ts=2 sw=2 tw=72 expandtab: */