Split from git://repo.or.cz/quvi.git
[quvi-tool.git] / src / quvi / quvi.c
blob34fce28fe8a17f51e01d38da043343097224a19d
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(quvi_t quvi, QUVIcode rc)
86 fprintf(stderr, "error: %s\n", quvi_strerror(quvi, rc));
89 static void handle_resolve_status(quvi_word type)
91 if (type == QUVISTATUSTYPE_DONE)
92 spew_qe("done.\n");
93 else
94 spew_qe(":: Check for URL redirection ...");
97 static void handle_fetch_status(quvi_word type, void *p)
99 switch (type)
101 default:
102 spew_qe(":: Fetch %s ...", (char *)p);
103 break;
104 case QUVISTATUSTYPE_CONFIG:
105 spew_qe(":: Fetch config ...");
106 break;
107 case QUVISTATUSTYPE_PLAYLIST:
108 spew_qe(":: Fetch playlist ...");
109 break;
110 case QUVISTATUSTYPE_DONE:
111 spew_qe("done.\n");
112 break;
116 static void handle_verify_status(quvi_word type)
118 switch (type)
120 default:
121 spew_qe(":: Verify media URL ...");
122 break;
123 case QUVISTATUSTYPE_DONE:
124 spew_qe("done.\n");
125 break;
129 static int status_callback(long param, void *data)
131 quvi_word status, type;
133 status = quvi_loword(param);
134 type = quvi_hiword(param);
136 switch (status)
138 case QUVISTATUS_RESOLVE:
139 handle_resolve_status(type);
140 break;
142 case QUVISTATUS_FETCH:
143 handle_fetch_status(type, data);
144 break;
146 case QUVISTATUS_VERIFY:
147 handle_verify_status(type);
148 break;
151 fflush(stderr);
153 return (0);
156 /* Divided into smaller blocks. Otherwise -pedantic objects. */
158 #define LICENSE_1 \
159 "/* quvi\n" \
160 " * Copyright (C) 2009-2011 Toni Gundogdu <legatvs@gmail.com>\n"
162 #define LICENSE_2 \
163 " * This library is free software; you can redistribute it and/or\n" \
164 " * modify it under the terms of the GNU Lesser General Public\n" \
165 " * License as published by the Free Software Foundation; either\n" \
166 " * version 2.1 of the License, or (at your option) any later version.\n"
168 #define LICENSE_3 \
169 " * This library is distributed in the hope that it will be useful,\n" \
170 " * but WITHOUT ANY WARRANTY; without even the implied warranty of\n" \
171 " * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU\n" \
172 " * Lesser General Public License for more details.\n"
174 #define LICENSE_4 \
175 " * You should have received a copy of the GNU Lesser General Public\n" \
176 " * License along with this library; if not, write to the Free Software\n" \
177 " * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA\n" \
178 " * 02110-1301 USA\n" " */"
180 static void license()
182 printf("%s *\n%s *\n%s *\n%s\n",
183 LICENSE_1, LICENSE_2, LICENSE_3, LICENSE_4);
184 exit(0);
187 #undef LICENSE_4
188 #undef LICENSE_3
189 #undef LICENSE_2
190 #undef LICENSE_1
192 static void print_version()
194 static const char version[] =
195 #ifdef VN
197 #else
198 PACKAGE_VERSION
199 #endif
200 " for " CANONICAL_TARGET;
201 printf("quvi %s\n libquvi %s\n libquvi-scripts %s\n",
202 version, quvi_version(QUVI_VERSION_LONG),
203 quvi_version(QUVI_VERSION_SCRIPTS));
204 exit(0);
207 static void dump_host(char *domain, char *formats)
209 printf("%s\t%s\n", domain, formats);
210 quvi_free(domain);
211 quvi_free(formats);
214 /* Wraps quvi_supported_ident. */
215 static void supported(quvi_t quvi)
217 #ifndef _QUVI_SUPPORTED
218 quvi_ident_t ident;
219 char *formats;
220 #endif
221 QUVIcode rc;
222 int i;
224 rc = QUVI_NOSUPPORT;
226 for (i=0; i<opts->inputs_num; ++i)
228 rc = quvi_supported_ident(quvi, (char*)opts->inputs[i], &ident);
229 if (rc == QUVI_OK)
231 quvi_ident_getprop(ident, QUVI_IDENT_PROPERTY_FORMATS, &formats);
232 spew("%10s : %s\n", formats, (char *)opts->inputs[i]);
233 quvi_supported_ident_close(&ident);
235 else
236 dump_error(quvi,rc);
239 exit(rc);
242 /* Query which formats are available for the URL */
243 static void query_formats(quvi_t quvi)
245 QUVIcode rc;
246 int i;
248 if (opts->inputs_num < 1)
250 spew_qe("error: no input URLs\n");
251 exit (QUVI_INVARG);
254 for (i=0; i<opts->inputs_num; ++i)
256 char *formats = NULL;
258 rc = quvi_query_formats(quvi, (char*)opts->inputs[i], &formats);
259 if (rc == QUVI_OK)
261 spew("%10s : %s\n", formats, opts->inputs[i]);
262 quvi_free(formats);
264 else
265 dump_error(quvi,rc);
268 exit(rc);
271 /* dumps all supported hosts to stdout. */
272 static void support(quvi_t quvi)
274 int done = 0;
276 if (opts->inputs_num > 0)
277 supported(quvi);
279 while (!done)
281 char *domain, *formats;
282 QUVIcode rc;
284 rc = quvi_next_supported_website(quvi, &domain, &formats);
286 switch (rc)
288 case QUVI_OK:
289 dump_host(domain, formats);
290 break;
291 case QUVI_LAST:
292 done = 1;
293 break;
294 default:
295 spew_e("%s\n", quvi_strerror(quvi, rc));
296 break;
300 exit(0);
303 static void invoke_exec(quvi_media_t media)
305 char *cmd, *media_url, *q_media_url;
306 char *page_title, *q_page_title, *t;
307 int rc;
309 quvi_getprop(media, QUVIPROP_PAGETITLE, &page_title);
310 t = strdup(page_title);
311 t = strepl(t, "\"", "\\\""); /* Escape existing double quotation marks */
312 asprintf(&q_page_title, "\"%s\"", t); /* Put inside quotation marks */
313 _free(t);
315 quvi_getprop(media, QUVIPROP_MEDIAURL, &media_url);
316 asprintf(&q_media_url, "\"%s\"", media_url);
318 cmd = strdup(opts->exec_arg);
319 cmd = strepl(cmd, "%t", q_page_title);
320 cmd = strepl(cmd, "%u", q_media_url);
322 _free(q_page_title);
323 _free(q_media_url);
325 rc = system(cmd);
327 switch (rc)
329 case 0:
330 break;
331 case -1:
332 spew_e("error: failed to execute `%s'\n", cmd);
333 break;
334 default:
335 spew_e("error: child exited with: %d\n", rc >> 8);
336 break;
339 _free(cmd);
342 struct parsed_url_s
344 char *media_url;
345 char *content_type;
346 double content_length;
347 char *file_suffix;
350 typedef struct parsed_url_s *parsed_url_t;
352 static void dump_media_url_xml(parsed_url_t p, int i)
354 char *media_url = curl_easy_escape(curl, p->media_url, 0);
356 spew(" <link id=\"%d\">\n", i);
358 if (p->content_length)
359 spew(" <length_bytes>%.0f</length_bytes>\n", p->content_length);
361 if (strlen(p->content_type))
362 spew(" <content_type>%s</content_type>\n", p->content_type);
364 if (strlen(p->file_suffix))
365 spew(" <file_suffix>%s</file_suffix>\n", p->file_suffix);
367 spew(" <url>%s</url>\n"
368 " </link>\n",
369 media_url ? media_url : p->media_url);
371 _free(media_url);
374 static void dump_media_url_json(parsed_url_t p, int i, int prepend_newln)
376 if (prepend_newln)
377 spew(",\n");
379 spew(" {\n"
380 " \"id\": \"%d\",\n", i);
382 if (p->content_length)
383 spew(" \"length_bytes\": \"%.0f\",\n", p->content_length);
385 if (strlen(p->content_type))
386 spew(" \"content_type\": \"%s\",\n", p->content_type);
388 if (strlen(p->file_suffix))
389 spew(" \"file_suffix\": \"%s\",\n", p->file_suffix);
391 spew(" \"url\": \"%s\"\n"
392 " }",
393 p->media_url);
396 static void dump_media_urls(quvi_media_t media)
398 int json_flag=0, i=1;
401 struct parsed_url_s p;
403 memset(&p, 0, sizeof(&p));
405 quvi_getprop(media, QUVIPROP_MEDIAURL, &p.media_url);
406 quvi_getprop(media, QUVIPROP_MEDIACONTENTTYPE, &p.content_type);
407 quvi_getprop(media, QUVIPROP_MEDIACONTENTLENGTH, &p.content_length);
408 quvi_getprop(media, QUVIPROP_FILESUFFIX, &p.file_suffix);
410 if (opts->xml_given)
411 dump_media_url_xml(&p,i);
412 else
414 dump_media_url_json(&p, i, i>1);
415 json_flag = 1;
417 ++i;
419 while (quvi_next_media_url(media) == QUVI_OK);
421 if (json_flag)
422 spew("\n");
425 struct parsed_s
427 char *page_url;
428 char *page_title;
429 char *media_id;
430 char *format;
431 char *host;
432 char *start_time;
433 char *thumb_url;
434 double duration;
437 typedef struct parsed_s *parsed_t;
439 static void dump_media_xml(parsed_t p)
441 char *e_page_url, *e_thumb_url;
443 e_page_url = curl_easy_escape(curl, p->page_url, 0);
444 e_thumb_url = curl_easy_escape(curl, p->thumb_url, 0);
446 spew("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
447 "<media id=\"%s\" host=\"%s\">\n"
448 " <format_requested>%s</format_requested>\n"
449 " <page_title>%s</page_title>\n"
450 " <page_url>%s</page_url>\n",
451 p->media_id,
452 p->host,
453 p->format,
454 p->page_title,
455 e_page_url ? e_page_url : "");
457 if (strlen(p->start_time))
458 spew(" <start_time>%s</start_time>\n", p->start_time);
460 if (e_thumb_url && strlen(e_thumb_url))
461 spew(" <thumbnail_url>%s</thumbnail_url>\n", e_thumb_url);
463 if (p->duration)
464 spew(" <duration>%.0f</duration>\n", p->duration);
466 if (e_page_url)
468 curl_free(e_page_url);
469 e_page_url = NULL;
472 if (e_thumb_url)
474 curl_free(e_thumb_url);
475 e_thumb_url = NULL;
479 static void dump_media_json(parsed_t p)
481 char *t;
483 t = strdup(p->page_title);
484 t = strepl(t, "\"", "\\\"");
486 spew("{\n"
487 " \"host\": \"%s\",\n"
488 " \"page_title\": \"%s\",\n"
489 " \"page_url\": \"%s\",\n"
490 " \"id\": \"%s\",\n"
491 " \"format_requested\": \"%s\",\n",
492 p->host,
494 p->page_url,
495 p->media_id,
496 p->format);
498 if (strlen(p->start_time))
499 spew(" \"start_time\": \"%s\",\n", p->start_time);
501 if (strlen(p->thumb_url))
502 spew(" \"thumbnail_url\": \"%s\",\n", p->thumb_url);
504 if (p->duration)
505 spew(" \"duration\": \"%.0f\",\n", p->duration);
507 spew(" \"link\": [\n");
509 _free(t);
512 static void dump_media(quvi_media_t media)
514 struct parsed_s p;
516 memset(&p, 0, sizeof(p));
518 quvi_getprop(media, QUVIPROP_HOSTID, &p.host);
519 quvi_getprop(media, QUVIPROP_PAGEURL, &p.page_url);
520 quvi_getprop(media, QUVIPROP_PAGETITLE, &p.page_title);
521 quvi_getprop(media, QUVIPROP_MEDIAID, &p.media_id);
522 quvi_getprop(media, QUVIPROP_FORMAT, &p.format);
523 quvi_getprop(media, QUVIPROP_STARTTIME, &p.start_time);
524 quvi_getprop(media, QUVIPROP_MEDIATHUMBNAILURL, &p.thumb_url);
525 quvi_getprop(media, QUVIPROP_MEDIADURATION, &p.duration);
527 if (opts->xml_given)
528 dump_media_xml(&p);
529 else
530 dump_media_json(&p);
532 dump_media_urls(media);
534 if (opts->xml_given)
535 spew("</media>\n");
536 else
537 spew(" ]\n}\n");
540 static quvi_t init_quvi()
542 QUVIcode rc;
543 quvi_t quvi;
545 if ((rc = quvi_init(&quvi)) != QUVI_OK)
547 dump_error(quvi, rc);
548 exit(rc);
550 assert(quvi != 0);
552 /* Set quvi options. */
554 if (opts->format_given)
555 quvi_setopt(quvi, QUVIOPT_FORMAT, opts->format_arg);
557 quvi_setopt(quvi, QUVIOPT_NORESOLVE, opts->no_resolve_given);
558 quvi_setopt(quvi, QUVIOPT_NOVERIFY, opts->no_verify_given);
560 if (opts->category_all_given)
561 quvi_setopt(quvi, QUVIOPT_CATEGORY, QUVIPROTO_ALL);
562 else
564 long n = 0;
565 if (opts->category_http_given)
566 n |= QUVIPROTO_HTTP;
567 if (opts->category_mms_given)
568 n |= QUVIPROTO_MMS;
569 if (opts->category_rtsp_given)
570 n |= QUVIPROTO_RTSP;
571 if (opts->category_rtmp_given)
572 n |= QUVIPROTO_RTMP;
573 if (n > 0)
574 quvi_setopt(quvi, QUVIOPT_CATEGORY, n);
577 quvi_setopt(quvi, QUVIOPT_STATUSFUNCTION, status_callback);
579 /* Use the quvi created cURL handle. */
581 quvi_getinfo(quvi, QUVIINFO_CURL, &curl);
582 assert(curl != 0);
584 if (opts->agent_given)
585 curl_easy_setopt(curl, CURLOPT_USERAGENT, opts->agent_arg);
587 if (opts->proxy_given)
588 curl_easy_setopt(curl, CURLOPT_PROXY, opts->proxy_arg);
590 if (opts->no_proxy_given)
591 curl_easy_setopt(curl, CURLOPT_PROXY, "");
593 curl_easy_setopt(curl, CURLOPT_VERBOSE, opts->verbose_libcurl_given);
595 curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT,
596 opts->connect_timeout_arg);
598 return (quvi);
601 static void cleanup()
603 quvi_llst_free(&inputs);
604 assert(inputs == NULL);
606 if (quvi)
607 quvi_close(&quvi);
608 assert(quvi == NULL);
610 if (opts)
612 cmdline_parser_free(opts);
613 _free(opts);
615 assert(opts == NULL);
618 static void read_from(FILE *f, int close)
620 char b[256];
622 if (!f)
623 return;
625 while (fgets(b, sizeof(b), f))
627 if (strlen(b) > 16)
629 const size_t n = strlen(b)-1;
631 if (b[n] == '\n')
632 b[n] = '\0';
634 quvi_llst_append(&inputs, strdup(b));
638 if (close)
640 fclose(f);
641 f = NULL;
645 static char *parse_url_scheme(const char *url)
647 char *p, *r;
649 p = strstr(url, ":/");
650 if (!p)
651 return (NULL);
653 asprintf(&r, "%.*s", (int)(p - url), url);
655 return (r);
658 static int is_url(const char *s)
660 char *p = parse_url_scheme(s);
661 if (p)
663 _free(p);
664 return (1);
666 return (0);
669 static FILE* open_file(const char *path)
671 FILE *f = fopen(path, "rt");
672 if (!f)
673 #ifdef HAVE_STRERROR
674 spew_e("error: %s: %s\n", path, strerror(errno));
675 #else
676 perror("fopen");
677 #endif
678 return (f);
681 static int read_input()
683 if (opts->inputs_num == 0)
684 read_from(stdin, 0);
685 else
687 int i;
688 for (i=0; i<opts->inputs_num; ++i)
690 if (!is_url(opts->inputs[i]))
691 read_from(open_file(opts->inputs[i]), 1);
692 else /* Must be an URL. */
693 quvi_llst_append(&inputs, strdup(opts->inputs[i]));
696 return (quvi_llst_size(inputs));
699 int main(int argc, char *argv[])
701 const char *home, *no_config, *fname;
702 QUVIcode rc, last_failure;
703 quvi_llst_node_t curr;
704 quvi_media_t media;
705 int no_config_flag;
706 int i, inputs_num;
707 int errors;
709 assert(quvi == NULL);
710 assert(curl == NULL);
711 assert(opts == NULL);
712 assert(inputs == NULL);
714 no_config = getenv("QUVI_NO_CONFIG");
715 no_config_flag = 1;
717 home = getenv("QUVI_HOME");
718 if (!home)
719 home = getenv("HOME");
721 #ifndef HOST_W32
722 fname = "/.quvirc";
723 #else
724 fname = "\\quvirc";
725 #endif
727 atexit(cleanup);
729 opts = calloc(1, sizeof(struct gengetopt_args_info));
730 if (!opts)
731 return(QUVI_MEM);
733 /* Init cmdline parser. */
735 if (home && !no_config)
737 char *path;
738 FILE *f;
740 asprintf(&path, "%s%s", home, fname);
741 f = fopen(path, "r");
743 if (f != NULL)
745 struct cmdline_parser_params *pp;
747 fclose(f);
748 f = NULL;
750 pp = cmdline_parser_params_create();
751 pp->check_required = 0;
753 if (cmdline_parser_config_file(path, opts, pp) == 0)
755 pp->initialize = 0;
756 pp->override = 1;
757 pp->check_required = 1;
759 if (cmdline_parser_ext(argc, argv, opts, pp) == 0)
760 no_config_flag = 0;
762 _free(pp);
765 _free(path);
768 if (no_config_flag)
770 if (cmdline_parser(argc, argv, opts) != 0)
771 return (QUVI_INVARG);
774 if (opts->version_given)
775 print_version(opts);
777 if (opts->license_given)
778 license(opts);
780 verbose_flag = !opts->quiet_given;
782 quvi = init_quvi();
784 if (opts->query_formats_given)
785 query_formats(quvi);
787 if (opts->support_given)
788 support(quvi);
790 /* User input */
792 inputs_num = read_input();
794 if (inputs_num == 0)
796 spew_qe("error: no input URLs\n");
797 return (QUVI_INVARG);
800 last_failure = QUVI_OK;
801 errors = 0;
803 for (i=0, curr=inputs; curr; ++i)
805 char *url = quvi_llst_data(curr);
806 rc = quvi_parse(quvi, url, &media);
807 if (rc == QUVI_OK)
809 assert(media != 0);
810 dump_media(media);
812 if (opts->exec_given)
816 invoke_exec(media);
818 while (quvi_next_media_url(media) == QUVI_OK);
821 else
823 dump_error(quvi,rc);
824 last_failure = rc;
825 ++errors;
827 quvi_parse_close(&media);
828 assert(media == 0);
829 curr = quvi_llst_next(curr);
832 if (inputs_num > 1)
834 spew_qe("Results: %d OK, %d failed (last 0x%02x), exit with 0x%02x\n",
835 inputs_num - errors, errors, last_failure, rc);
838 return (rc);
841 /* vim: set ts=2 sw=2 tw=72 expandtab: */