ted.lua: Rewrite for quvi_query_formats
[quvi.git] / src / quvi.c
blob924ce1b730bf5d2e2bf28642901798857136a218
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 <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 handle_resolve_status(quvi_word type)
86 if (type == QUVISTATUSTYPE_DONE)
87 spew_qe("done.\n");
88 else
89 spew_qe(":: Check for URL redirection ...");
92 static void handle_fetch_status(quvi_word type, void *p)
94 switch (type)
96 default:
97 spew_qe(":: Fetch %s ...", (char *)p);
98 break;
99 case QUVISTATUSTYPE_CONFIG:
100 spew_qe(":: Fetch config ...");
101 break;
102 case QUVISTATUSTYPE_PLAYLIST:
103 spew_qe(":: Fetch playlist ...");
104 break;
105 case QUVISTATUSTYPE_DONE:
106 spew_qe("done.\n");
107 break;
111 static void handle_verify_status(quvi_word type)
113 switch (type)
115 default:
116 spew_qe(":: Verify media URL ...");
117 break;
118 case QUVISTATUSTYPE_DONE:
119 spew_qe("done.\n");
120 break;
124 static int status_callback(long param, void *data)
126 quvi_word status, type;
128 status = quvi_loword(param);
129 type = quvi_hiword(param);
131 switch (status)
133 case QUVISTATUS_RESOLVE:
134 handle_resolve_status(type);
135 break;
137 case QUVISTATUS_FETCH:
138 handle_fetch_status(type, data);
139 break;
141 case QUVISTATUS_VERIFY:
142 handle_verify_status(type);
143 break;
146 fflush(stderr);
148 return (0);
151 static size_t write_callback(void *p, size_t size, size_t nmemb,
152 void *data)
154 size_t r = quvi_write_callback_default(p, size, nmemb, data);
155 /* Could do something useful here. */
156 #ifdef _0
157 puts(__func__);
158 #endif
159 return r;
162 /* Divided into smaller blocks. Otherwise -pedantic objects. */
164 #define LICENSE_1 \
165 "/* quvi\n" \
166 " * Copyright (C) 2009,2010,2011 Toni Gundogdu <legatvs@gmail.com>\n"
168 #define LICENSE_2 \
169 " * This library is free software; you can redistribute it and/or\n" \
170 " * modify it under the terms of the GNU Lesser General Public\n" \
171 " * License as published by the Free Software Foundation; either\n" \
172 " * version 2.1 of the License, or (at your option) any later version.\n"
174 #define LICENSE_3 \
175 " * This library is distributed in the hope that it will be useful,\n" \
176 " * but WITHOUT ANY WARRANTY; without even the implied warranty of\n" \
177 " * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU\n" \
178 " * Lesser General Public License for more details.\n"
180 #define LICENSE_4 \
181 " * You should have received a copy of the GNU Lesser General Public\n" \
182 " * License along with this library; if not, write to the Free Software\n" \
183 " * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA\n" \
184 " * 02110-1301 USA\n" " */"
186 static void license()
188 printf("%s *\n%s *\n%s *\n%s\n",
189 LICENSE_1, LICENSE_2, LICENSE_3, LICENSE_4);
190 exit(0);
193 #undef LICENSE_4
194 #undef LICENSE_3
195 #undef LICENSE_2
196 #undef LICENSE_1
198 static void version()
200 printf("quvi version %s\n", quvi_version(QUVI_VERSION_LONG));
201 exit(0);
204 static void dump_host(char *domain, char *formats)
206 printf("%s\t%s\n", domain, formats);
207 quvi_free(domain);
208 quvi_free(formats);
211 #ifdef _0
212 #define _QUVI_SUPPORTED
213 #endif
215 /* Wraps quvi_supported_ident. */
216 static void supported(quvi_t quvi)
218 #ifndef _QUVI_SUPPORTED
219 quvi_ident_t ident;
220 char *formats;
221 #endif
222 QUVIcode rc;
223 int i;
225 rc = QUVI_NOSUPPORT;
227 for (i=0; i<opts->inputs_num; ++i)
229 #ifndef _QUVI_SUPPORTED
230 rc = quvi_supported_ident(quvi, (char*)opts->inputs[i], &ident);
231 if (rc == QUVI_OK)
233 quvi_ident_getprop(ident, QUVI_IDENT_PROPERTY_FORMATS, &formats);
234 spew_qe("%s: OK\n\t%s\n", (char *)opts->inputs[i], formats);
235 quvi_supported_ident_close(&ident);
237 #else
238 rc = quvi_supported(quvi, (char*)opts->inputs[i]);
239 if (rc == QUVI_OK)
240 spew_qe("%s: OK\n", (char *)opts->inputs[i]);
241 #endif /* !_QUVI_SUPPORTED */
242 else
243 spew_qe("error: %s\n", quvi_strerror(quvi, rc));
246 exit(rc);
249 static void format_help(quvi_t quvi)
251 int quit = 0;
253 if (strcmp(opts->format_arg, "help") == 0)
255 printf("Usage:\n"
256 " --format arg get format arg\n"
257 " --format list list websites and supported formats\n"
258 " --format list arg match arg to websites, list formats for matches\n"
259 "Examples:\n"
260 " --format mp4_360p get format mp4_360p (youtube)\n"
261 " --format list youtube list youtube formats\n"
262 " --format list dailym list dailym(otion) formats\n"
264 quit = 1;
267 else if (strcmp(opts->format_arg, "list") == 0)
269 int done = 0;
270 char *d, *f;
272 while(!done)
274 const int rc =
275 quvi_next_supported_website(quvi, &d, &f);
277 switch (rc)
280 case QUVI_OK:
282 int print = 1;
284 /* -f list <pattern> */
285 if (opts->inputs_num > 0)
286 print = strstr(d, (char *)opts->inputs[0]) != 0;
288 /* -f list */
289 if (print)
290 printf("%s:\n %s\n\n", d, f);
292 quvi_free(d);
293 quvi_free(f);
295 break;
297 case QUVI_LAST:
298 done = 1;
299 break;
301 default:
302 spew_e("%s\n", quvi_strerror(quvi, rc));
303 break;
306 quit = 1;
309 if (quit)
310 exit(0);
313 /* Query which formats are available for the URL */
314 static void query_formats(quvi_t quvi)
316 QUVIcode rc;
317 int i;
319 if (opts->inputs_num < 1)
321 spew_qe("error: no input URLs\n");
322 exit (QUVI_INVARG);
325 for (i=0; i<opts->inputs_num; ++i)
327 char *formats = NULL;
329 rc = quvi_query_formats(quvi, (char*)opts->inputs[i], &formats);
330 if (rc == QUVI_OK)
332 spew("%10s : %s\n", formats, opts->inputs[i]);
333 quvi_free(formats);
335 else
336 spew_qe("%s\n", quvi_strerror(quvi, rc));
339 exit(rc);
342 /* dumps all supported hosts to stdout. */
343 static void support(quvi_t quvi)
345 int done = 0;
347 if (opts->inputs_num > 0)
348 supported(quvi);
350 while (!done)
352 char *domain, *formats;
353 QUVIcode rc;
355 rc = quvi_next_supported_website(quvi, &domain, &formats);
357 switch (rc)
359 case QUVI_OK:
360 dump_host(domain, formats);
361 break;
362 case QUVI_LAST:
363 done = 1;
364 break;
365 default:
366 spew_e("%s\n", quvi_strerror(quvi, rc));
367 break;
371 exit(0);
374 static void invoke_exec(quvi_media_t media)
376 char *cmd, *media_url, *q_media_url;
377 int rc;
379 quvi_getprop(media, QUVIPROP_MEDIAURL, &media_url);
381 asprintf(&q_media_url, "\"%s\"", media_url);
383 cmd = strdup(opts->exec_arg);
384 cmd = strepl(cmd, "%u", q_media_url);
386 _free(q_media_url);
388 rc = system(cmd);
390 switch (rc)
392 case 0:
393 break;
394 case -1:
395 spew_e("error: failed to execute `%s'\n", cmd);
396 break;
397 default:
398 spew_e("error: child exited with: %d\n", rc >> 8);
399 break;
402 _free(cmd);
405 struct parsed_url_s
407 char *media_url;
408 char *content_type;
409 double content_length;
410 char *file_suffix;
413 typedef struct parsed_url_s *parsed_url_t;
415 static void dump_media_url_xml(parsed_url_t p, int i)
417 char *media_url = curl_easy_escape(curl, p->media_url, 0);
419 spew(" <link id=\"%d\">\n", i);
421 if (p->content_length)
422 spew(" <length_bytes>%.0f</length_bytes>\n", p->content_length);
424 if (strlen(p->content_type))
425 spew(" <content_type>%s</content_type>\n", p->content_type);
427 if (strlen(p->file_suffix))
428 spew(" <file_suffix>%s</file_suffix>\n", p->file_suffix);
430 spew(" <url>%s</url>\n"
431 " </link>\n",
432 media_url ? media_url : p->media_url);
434 _free(media_url);
437 static void dump_media_url_old(parsed_url_t p, int i)
439 spew("link %02d : %s\n", i, p->media_url);
441 if (p->content_length)
442 spew(":: length: %.0f\n", p->content_length);
444 if (strlen(p->file_suffix))
445 spew(":: suffix: %s\n", p->file_suffix);
447 if (strlen(p->content_type))
448 spew(":: content-type: %s\n", p->content_type);
451 static void dump_media_url_json(parsed_url_t p, int i, int prepend_newln)
453 if (prepend_newln)
454 spew(",\n");
456 spew(" {\n"
457 " \"id\": \"%d\",\n", i);
459 if (p->content_length)
460 spew(" \"length_bytes\": \"%.0f\",\n", p->content_length);
462 if (strlen(p->content_type))
463 spew(" \"content_type\": \"%s\",\n", p->content_type);
465 if (strlen(p->file_suffix))
466 spew(" \"file_suffix\": \"%s\",\n", p->file_suffix);
468 spew(" \"url\": \"%s\"\n"
469 " }",
470 p->media_url);
473 static void dump_media_urls(quvi_media_t media)
475 int json_flag=0, i=1;
478 struct parsed_url_s p;
480 memset(&p, 0, sizeof(&p));
482 quvi_getprop(media, QUVIPROP_MEDIAURL, &p.media_url);
483 quvi_getprop(media, QUVIPROP_MEDIACONTENTTYPE, &p.content_type);
484 quvi_getprop(media, QUVIPROP_MEDIACONTENTLENGTH, &p.content_length);
485 quvi_getprop(media, QUVIPROP_FILESUFFIX, &p.file_suffix);
487 if (opts->xml_given)
488 dump_media_url_xml(&p,i);
489 else if (opts->old_given)
490 dump_media_url_old(&p,i);
491 else
493 dump_media_url_json(&p, i, i>1);
494 json_flag = 1;
496 ++i;
498 while (quvi_next_media_url(media) == QUVI_OK);
500 if (json_flag)
501 spew("\n");
504 struct parsed_s
506 char *page_url;
507 char *page_title;
508 char *media_id;
509 char *format;
510 char *host;
511 char *start_time;
512 char *thumb_url;
513 double duration;
516 typedef struct parsed_s *parsed_t;
518 static void dump_media_xml(parsed_t p)
520 char *e_page_url, *e_thumb_url;
522 e_page_url = curl_easy_escape(curl, p->page_url, 0);
523 e_thumb_url = curl_easy_escape(curl, p->thumb_url, 0);
525 spew("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
526 "<media id=\"%s\" host=\"%s\">\n"
527 " <format_requested>%s</format_requested>\n"
528 " <page_title>%s</page_title>\n"
529 " <page_url>%s</page_url>\n",
530 p->media_id,
531 p->host,
532 p->format,
533 p->page_title,
534 e_page_url ? e_page_url : "");
536 if (strlen(p->start_time))
537 spew(" <start_time>%s</start_time>\n", p->start_time);
539 if (e_thumb_url && strlen(e_thumb_url))
540 spew(" <thumbnail_url>%s</thumbnail_url>\n", e_thumb_url);
542 if (p->duration)
543 spew(" <duration>%.0f</duration>\n", p->duration);
545 if (e_page_url)
547 curl_free(e_page_url);
548 e_page_url = NULL;
551 if (e_thumb_url)
553 curl_free(e_thumb_url);
554 e_thumb_url = NULL;
558 static void dump_media_old(parsed_t p)
560 spew(" > Dump media:\n"
561 "host : %s\n"
562 "url : %s\n"
563 "title : %s\n"
564 "id : %s\n"
565 "format : %s (requested)\n",
566 p->host,
567 p->page_url,
568 p->page_title,
569 p->media_id,
570 p->format);
572 if (strlen(p->start_time))
573 spew("start time: %s\n", p->start_time);
575 if (strlen(p->thumb_url))
576 spew("thumbnail url: %s\n", p->thumb_url);
578 if (p->duration)
579 spew("duration: %.0f\n", p->duration);
582 static void dump_media_json(parsed_t p)
584 char *t;
586 t = strdup(p->page_title);
587 t = strepl(t, "\"", "\\\"");
589 spew("{\n"
590 " \"host\": \"%s\",\n"
591 " \"page_title\": \"%s\",\n"
592 " \"page_url\": \"%s\",\n"
593 " \"id\": \"%s\",\n"
594 " \"format_requested\": \"%s\",\n",
595 p->host,
597 p->page_url,
598 p->media_id,
599 p->format);
601 if (strlen(p->start_time))
602 spew(" \"start_time\": \"%s\",\n", p->start_time);
604 if (strlen(p->thumb_url))
605 spew(" \"thumbnail_url\": \"%s\",\n", p->thumb_url);
607 if (p->duration)
608 spew(" \"duration\": \"%.0f\",\n", p->duration);
610 spew(" \"link\": [\n");
612 _free(t);
615 static void dump_media(quvi_media_t media)
617 struct parsed_s p;
619 memset(&p, 0, sizeof(p));
621 quvi_getprop(media, QUVIPROP_HOSTID, &p.host);
622 quvi_getprop(media, QUVIPROP_PAGEURL, &p.page_url);
623 quvi_getprop(media, QUVIPROP_PAGETITLE, &p.page_title);
624 quvi_getprop(media, QUVIPROP_MEDIAID, &p.media_id);
625 quvi_getprop(media, QUVIPROP_FORMAT, &p.format);
626 quvi_getprop(media, QUVIPROP_STARTTIME, &p.start_time);
627 quvi_getprop(media, QUVIPROP_MEDIATHUMBNAILURL, &p.thumb_url);
628 quvi_getprop(media, QUVIPROP_MEDIADURATION, &p.duration);
630 if (opts->xml_given)
631 dump_media_xml(&p);
632 else if (opts->old_given)
633 dump_media_old(&p);
634 else
635 dump_media_json(&p);
637 dump_media_urls(media);
639 if (opts->xml_given)
640 spew("</media>\n");
641 else if (opts->old_given) ;
642 else
643 spew(" ]\n}\n");
646 static void dump_error(quvi_t quvi, QUVIcode rc)
648 fprintf(stderr, "error: %s\n", quvi_strerror(quvi, rc));
651 static quvi_t init_quvi()
653 QUVIcode rc;
654 quvi_t quvi;
656 if ((rc = quvi_init(&quvi)) != QUVI_OK)
658 dump_error(quvi, rc);
659 exit(rc);
661 assert(quvi != 0);
663 /* Set quvi options. */
665 if (opts->format_given)
666 quvi_setopt(quvi, QUVIOPT_FORMAT, opts->format_arg);
668 if (opts->no_shortened_given)
670 spew_e("warning: --no-shortened is deprecated, "
671 "use --no-resolve instead\n");
673 quvi_setopt(quvi, QUVIOPT_NORESOLVE, opts->no_resolve_given);
674 quvi_setopt(quvi, QUVIOPT_NOVERIFY, opts->no_verify_given);
676 if (opts->category_all_given)
677 quvi_setopt(quvi, QUVIOPT_CATEGORY, QUVIPROTO_ALL);
678 else
680 long n = 0;
681 if (opts->category_http_given)
682 n |= QUVIPROTO_HTTP;
683 if (opts->category_mms_given)
684 n |= QUVIPROTO_MMS;
685 if (opts->category_rtsp_given)
686 n |= QUVIPROTO_RTSP;
687 if (opts->category_rtmp_given)
688 n |= QUVIPROTO_RTMP;
689 if (n > 0)
690 quvi_setopt(quvi, QUVIOPT_CATEGORY, n);
693 quvi_setopt(quvi, QUVIOPT_STATUSFUNCTION, status_callback);
694 quvi_setopt(quvi, QUVIOPT_WRITEFUNCTION, write_callback);
696 /* Use the quvi created cURL handle. */
698 quvi_getinfo(quvi, QUVIINFO_CURL, &curl);
699 assert(curl != 0);
701 if (opts->agent_given)
702 curl_easy_setopt(curl, CURLOPT_USERAGENT, opts->agent_arg);
704 if (opts->proxy_given)
705 curl_easy_setopt(curl, CURLOPT_PROXY, opts->proxy_arg);
707 if (opts->no_proxy_given)
708 curl_easy_setopt(curl, CURLOPT_PROXY, "");
710 curl_easy_setopt(curl, CURLOPT_VERBOSE, opts->verbose_libcurl_given);
712 curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT,
713 opts->connect_timeout_arg);
715 return (quvi);
718 static void cleanup()
720 quvi_llst_free(&inputs);
721 assert(inputs == NULL);
723 if (quvi)
724 quvi_close(&quvi);
725 assert(quvi == NULL);
727 if (opts)
729 cmdline_parser_free(opts);
730 _free(opts);
732 assert(opts == NULL);
735 static void read_from(FILE *f, int close)
737 char b[256];
739 if (!f)
740 return;
742 while (fgets(b, sizeof(b), f))
744 if (strlen(b) > 16)
746 const size_t n = strlen(b)-1;
748 if (b[n] == '\n')
749 b[n] = '\0';
751 quvi_llst_append(&inputs, strdup(b));
755 if (close)
757 fclose(f);
758 f = NULL;
762 static char *parse_url_scheme(const char *url)
764 char *p, *r;
766 p = strstr(url, ":/");
767 if (!p)
768 return (NULL);
770 asprintf(&r, "%.*s", (int)(p - url), url);
772 return (r);
775 static int is_url(const char *s)
777 char *p = parse_url_scheme(s);
778 if (p)
780 _free(p);
781 return (1);
783 return (0);
786 static FILE* open_file(const char *path)
788 FILE *f = fopen(path, "rt");
789 if (!f)
790 #ifdef HAVE_STRERROR
791 spew_e("error: %s: %s\n", path, strerror(errno));
792 #else
793 perror("fopen");
794 #endif
795 return (f);
798 static int read_input()
800 if (opts->inputs_num == 0)
801 read_from(stdin, 0);
802 else
804 int i;
805 for (i=0; i<opts->inputs_num; ++i)
807 if (!is_url(opts->inputs[i]))
808 read_from(open_file(opts->inputs[i]), 1);
809 else /* Must be an URL. */
810 quvi_llst_append(&inputs, strdup(opts->inputs[i]));
813 return (quvi_llst_size(inputs));
816 int main(int argc, char *argv[])
818 const char *home, *no_config, *fname;
819 QUVIcode rc, last_failure;
820 quvi_llst_node_t curr;
821 quvi_media_t media;
822 int no_config_flag;
823 int i, inputs_num;
824 int errors;
826 assert(quvi == NULL);
827 assert(curl == NULL);
828 assert(opts == NULL);
829 assert(inputs == NULL);
831 no_config = getenv("QUVI_NO_CONFIG");
832 no_config_flag = 1;
834 home = getenv("QUVI_HOME");
835 if (!home)
836 home = getenv("HOME");
838 #ifndef HOST_W32
839 fname = "/.quvirc";
840 #else
841 fname = "\\quvirc";
842 #endif
844 atexit(cleanup);
846 opts = calloc(1, sizeof(struct gengetopt_args_info));
847 if (!opts)
848 return(QUVI_MEM);
850 /* Init cmdline parser. */
852 if (home && !no_config)
854 char *path;
855 FILE *f;
857 asprintf(&path, "%s%s", home, fname);
858 f = fopen(path, "r");
860 if (f != NULL)
862 struct cmdline_parser_params *pp;
864 fclose(f);
865 f = NULL;
867 pp = cmdline_parser_params_create();
868 pp->check_required = 0;
870 if (cmdline_parser_config_file(path, opts, pp) == 0)
872 pp->initialize = 0;
873 pp->override = 1;
874 pp->check_required = 1;
876 if (cmdline_parser_ext(argc, argv, opts, pp) == 0)
877 no_config_flag = 0;
879 _free(pp);
882 _free(path);
885 if (no_config_flag)
887 if (cmdline_parser(argc, argv, opts) != 0)
888 return (QUVI_INVARG);
891 if (opts->version_given)
892 version(opts);
894 if (opts->license_given)
895 license(opts);
897 verbose_flag = !opts->quiet_given;
899 quvi = init_quvi();
901 if (opts->query_formats_given)
902 query_formats(quvi);
904 if (opts->support_given)
905 support(quvi);
907 if (opts->format_given)
908 format_help(quvi);
910 /* User input */
912 inputs_num = read_input();
914 if (inputs_num == 0)
916 spew_qe("error: no input URLs\n");
917 return (QUVI_INVARG);
920 last_failure = QUVI_OK;
921 errors = 0;
923 for (i=0, curr=inputs; curr; ++i)
925 char *url = quvi_llst_data(curr);
926 rc = quvi_parse(quvi, url, &media);
927 if (rc == QUVI_OK)
929 assert(media != 0);
930 dump_media(media);
932 if (opts->exec_given)
936 invoke_exec(media);
938 while (quvi_next_media_url(media) == QUVI_OK);
941 else
943 dump_error(quvi,rc);
944 last_failure = rc;
945 ++errors;
947 quvi_parse_close(&media);
948 assert(media == 0);
949 curr = quvi_llst_next(curr);
952 if (inputs_num > 1)
954 spew_qe("Results: %d OK, %d failed (last 0x%02x), exit with 0x%02x\n",
955 inputs_num - errors, errors, last_failure, rc);
958 return (rc);
961 /* vim: set ts=2 sw=2 tw=72 expandtab: */