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
20 /* quvi.c - query media tool. */
31 #include <quvi/quvi.h>
32 #include <quvi/llst.h>
34 #include <curl/curl.h>
40 do { if (p) { free(p); p=0; } } while (0)
42 static const char *config_fname
= ".quvirc";
45 extern char *strepl(const char *s
, const char *what
, const char *with
);
48 static quvi_t quvi
= NULL
;
50 static CURL
*curl
= NULL
;
52 typedef struct gengetopt_args_info
*opts_t
;
54 static opts_t opts
= NULL
;
57 static quvi_llst_node_t input
= NULL
;
59 /* Check whether a message should be printed. */
60 static int check_verbosity(int threshold
)
62 if (opts
->verbosity_arg
>= threshold
)
67 /* Prints to std(e)rr. 'mute' disables. */
68 static void spew_e(const char *fmt
, ...)
71 if (check_verbosity(verbosity_arg_mute
) == 0)
74 vfprintf(stderr
, fmt
, ap
);
78 /* Prints to std(e)rr. 'quiet' disables. */
79 static void spew_qe(const char *fmt
, ...)
82 if (check_verbosity(verbosity_arg_quiet
) == 0)
85 vfprintf(stderr
, fmt
, ap
);
89 /* Glorified printf. 'mute' disables. */
90 static void spew(const char *fmt
, ...)
93 if (check_verbosity(verbosity_arg_mute
) == 0)
96 vfprintf(stdout
, fmt
, ap
);
100 static void dump_error_json(quvi_t quvi
, QUVIcode rc
)
105 " \"message\": \"%s\"\n"
108 "}\n", quvi_strerror(quvi
, rc
));
111 static void dump_error_xml(quvi_t quvi
, QUVIcode rc
)
113 spew_e("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
115 " <message>%s</message>\n"
117 quvi_strerror(quvi
, rc
));
120 static void dump_error(quvi_t quvi
, QUVIcode rc
)
122 switch (opts
->export_level_arg
)
124 case export_level_arg_media
:
125 case export_level__NULL
:
127 spew_e("error: %s\n", quvi_strerror(quvi
, rc
));
129 case export_level_arg_PLUS_errors
:
130 switch (opts
->export_format_arg
)
132 case export_format_arg_json
:
133 case export_format__NULL
:
135 dump_error_json(quvi
, rc
);
137 case export_format_arg_xml
:
138 dump_error_xml(quvi
, rc
);
144 static void handle_resolve_status(quvi_word type
)
146 if (type
== QUVISTATUSTYPE_DONE
)
149 spew_qe(":: Check for URL redirection ...");
152 static void handle_fetch_status(quvi_word type
, void *p
)
157 spew_qe(":: Fetch %s ...", (char *)p
);
159 case QUVISTATUSTYPE_CONFIG
:
160 spew_qe(":: Fetch config ...");
162 case QUVISTATUSTYPE_PLAYLIST
:
163 spew_qe(":: Fetch playlist ...");
165 case QUVISTATUSTYPE_DONE
:
171 static void handle_verify_status(quvi_word type
)
176 spew_qe(":: Verify media URL ...");
178 case QUVISTATUSTYPE_DONE
:
184 static int status_callback(long param
, void *data
)
186 quvi_word status
, type
;
188 status
= quvi_loword(param
);
189 type
= quvi_hiword(param
);
193 case QUVISTATUS_RESOLVE
:
194 handle_resolve_status(type
);
197 case QUVISTATUS_FETCH
:
198 handle_fetch_status(type
, data
);
201 case QUVISTATUS_VERIFY
:
202 handle_verify_status(type
);
211 /* Divided into smaller blocks. Otherwise -pedantic objects. */
215 " * Copyright (C) 2009-2011 Toni Gundogdu <legatvs@gmail.com>\n"
218 " * This library is free software; you can redistribute it and/or\n" \
219 " * modify it under the terms of the GNU Lesser General Public\n" \
220 " * License as published by the Free Software Foundation; either\n" \
221 " * version 2.1 of the License, or (at your option) any later version.\n"
224 " * This library is distributed in the hope that it will be useful,\n" \
225 " * but WITHOUT ANY WARRANTY; without even the implied warranty of\n" \
226 " * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU\n" \
227 " * Lesser General Public License for more details.\n"
230 " * You should have received a copy of the GNU Lesser General Public\n" \
231 " * License along with this library; if not, write to the Free Software\n" \
232 " * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA\n" \
233 " * 02110-1301 USA\n" " */"
235 static void license()
237 printf("%s *\n%s *\n%s *\n%s\n",
238 LICENSE_1
, LICENSE_2
, LICENSE_3
, LICENSE_4
);
247 static void print_version()
249 static const char version
[] =
255 " for " CANONICAL_TARGET
;
256 printf("quvi %s\n libquvi %s\n libquvi-scripts %s\n",
257 version
, quvi_version(QUVI_VERSION_LONG
),
258 quvi_version(QUVI_VERSION_SCRIPTS
));
262 static void dump_host(char *domain
, char *formats
)
264 printf("%s\t%s\n", domain
, formats
);
269 /* Wraps quvi_supported_ident. */
270 static void supported(quvi_t quvi
)
279 for (i
=0; i
<opts
->inputs_num
; ++i
)
281 rc
= quvi_supported_ident(quvi
, (char*)opts
->inputs
[i
], &ident
);
284 quvi_ident_getprop(ident
,
285 QUVI_IDENT_PROPERTY_FORMATS
, &formats
);
286 spew("%10s : %s\n", formats
, (char *)opts
->inputs
[i
]);
287 quvi_supported_ident_close(&ident
);
296 /* Query which formats are available for the URL */
297 static void query_formats(quvi_t quvi
)
302 if (opts
->inputs_num
<1)
304 spew_qe("error: no input URLs\n");
308 for (i
=0,rc
=0; i
<opts
->inputs_num
; ++i
)
310 char *formats
= NULL
;
312 rc
= quvi_query_formats(quvi
, (char*)opts
->inputs
[i
], &formats
);
315 spew("%10s : %s\n", formats
, opts
->inputs
[i
]);
325 /* dumps all supported hosts to stdout. */
326 static void support(quvi_t quvi
)
330 if (opts
->inputs_num
>0)
335 char *domain
, *formats
;
338 rc
= quvi_next_supported_website(quvi
, &domain
, &formats
);
343 dump_host(domain
, formats
);
349 spew_e("%s\n", quvi_strerror(quvi
, rc
));
357 static char * shell_escape( char *str
)
359 size_t len
= strlen( str
);
361 str
= realloc( str
, 2 * len
+ 1 );
363 for ( i
= len
- 1; i
>= 0; --i
)
365 str
[ 2 * i
+ 1 ] = str
[ i
];
372 static void invoke_exec(quvi_media_t media
)
374 char *cmd
, *media_url
, *q_media_url
;
375 char *page_title
, *q_page_title
, *t
;
378 quvi_getprop(media
, QUVIPROP_PAGETITLE
, &page_title
);
379 t
= strdup(page_title
);
381 asprintf(&q_page_title
, "%s", t
);
384 quvi_getprop(media
, QUVIPROP_MEDIAURL
, &media_url
);
385 t
= strdup(media_url
);
387 asprintf(&q_media_url
, "%s", t
);
390 cmd
= strdup(opts
->exec_arg
);
391 cmd
= strepl(cmd
, "%t", q_page_title
);
392 cmd
= strepl(cmd
, "%u", q_media_url
);
404 spew_e("error: failed to execute `%s'\n", cmd
);
407 spew_e("error: child exited with: %d\n", rc
>> 8);
418 double content_length
;
422 typedef struct parsed_url_s
*parsed_url_t
;
424 static void dump_media_url_xml(parsed_url_t p
, int i
)
426 char *media_url
= curl_easy_escape(curl
, p
->media_url
, 0);
428 spew(" <link id=\"%d\">\n", i
);
430 if (p
->content_length
>0)
431 spew(" <length_bytes>%.0f</length_bytes>\n", p
->content_length
);
433 if (strlen(p
->content_type
) >0)
434 spew(" <content_type>%s</content_type>\n", p
->content_type
);
436 if (strlen(p
->file_suffix
) >0)
437 spew(" <file_suffix>%s</file_suffix>\n", p
->file_suffix
);
439 spew(" <url>%s</url>\n"
448 static void dump_media_url_json(parsed_url_t p
, int i
, int prepend_newln
)
450 if (prepend_newln
!= 0)
454 " \"id\": \"%d\",\n", i
);
456 if (p
->content_length
>0)
457 spew(" \"length_bytes\": \"%.0f\",\n", p
->content_length
);
459 if (strlen(p
->content_type
) >0)
460 spew(" \"content_type\": \"%s\",\n", p
->content_type
);
462 if (strlen(p
->file_suffix
) >0)
463 spew(" \"file_suffix\": \"%s\",\n", p
->file_suffix
);
465 spew(" \"url\": \"%s\"\n"
470 static void dump_media_urls(quvi_media_t media
)
472 int json_flag
=0, i
=1;
475 struct parsed_url_s p
;
477 memset(&p
, 0, sizeof(&p
));
478 quvi_getprop(media
, QUVIPROP_MEDIAURL
, &p
.media_url
);
479 quvi_getprop(media
, QUVIPROP_MEDIACONTENTTYPE
, &p
.content_type
);
480 quvi_getprop(media
, QUVIPROP_MEDIACONTENTLENGTH
, &p
.content_length
);
481 quvi_getprop(media
, QUVIPROP_FILESUFFIX
, &p
.file_suffix
);
483 if (opts
->xml_given
== 1)
484 dump_media_url_xml(&p
,i
);
487 dump_media_url_json(&p
, i
, (int)(i
>1));
492 while (quvi_next_media_url(media
) == QUVI_OK
);
510 typedef struct parsed_s
*parsed_t
;
512 static void dump_media_xml(parsed_t p
)
514 char *e_page_url
, *e_thumb_url
;
516 e_page_url
= curl_easy_escape(curl
, p
->page_url
, 0);
517 e_thumb_url
= curl_easy_escape(curl
, p
->thumb_url
, 0);
519 spew("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
520 "<media id=\"%s\" host=\"%s\">\n"
521 " <format_requested>%s</format_requested>\n"
522 " <page_title>%s</page_title>\n"
523 " <page_url>%s</page_url>\n",
528 e_page_url
? e_page_url
: "");
530 if (strlen(p
->start_time
) >0)
531 spew(" <start_time>%s</start_time>\n", p
->start_time
);
533 if (e_thumb_url
!= NULL
&& strlen(e_thumb_url
) >0)
534 spew(" <thumbnail_url>%s</thumbnail_url>\n", e_thumb_url
);
537 spew(" <duration>%.0f</duration>\n", p
->duration
);
539 if (e_page_url
!= NULL
)
541 curl_free(e_page_url
);
545 if (e_thumb_url
!= NULL
)
547 curl_free(e_thumb_url
);
552 static void dump_media_json(parsed_t p
)
556 t
= strdup(p
->page_title
);
557 t
= strepl(t
, "\"", "\\\"");
560 " \"host\": \"%s\",\n"
561 " \"page_title\": \"%s\",\n"
562 " \"page_url\": \"%s\",\n"
564 " \"format_requested\": \"%s\",\n",
571 if (strlen(p
->start_time
) >0)
572 spew(" \"start_time\": \"%s\",\n", p
->start_time
);
574 if (strlen(p
->thumb_url
) >0)
575 spew(" \"thumbnail_url\": \"%s\",\n", p
->thumb_url
);
578 spew(" \"duration\": \"%.0f\",\n", p
->duration
);
580 spew(" \"link\": [\n");
585 static void dump_media(quvi_media_t media
)
589 memset(&p
, 0, sizeof(p
));
591 quvi_getprop(media
, QUVIPROP_HOSTID
, &p
.host
);
592 quvi_getprop(media
, QUVIPROP_PAGEURL
, &p
.page_url
);
593 quvi_getprop(media
, QUVIPROP_PAGETITLE
, &p
.page_title
);
594 quvi_getprop(media
, QUVIPROP_MEDIAID
, &p
.media_id
);
595 quvi_getprop(media
, QUVIPROP_FORMAT
, &p
.format
);
596 quvi_getprop(media
, QUVIPROP_STARTTIME
, &p
.start_time
);
597 quvi_getprop(media
, QUVIPROP_MEDIATHUMBNAILURL
, &p
.thumb_url
);
598 quvi_getprop(media
, QUVIPROP_MEDIADURATION
, &p
.duration
);
600 if (opts
->xml_given
== 1)
605 dump_media_urls(media
);
607 if (opts
->xml_given
== 1)
613 static void check_categories(QUVIcategory
*n
)
616 for (i
=0, *n
=0; i
<opts
->category_given
; ++i
)
618 switch (opts
->category_arg
[i
])
620 case category_arg_all
:
625 case category_arg_http
:
626 *n
|= QUVIPROTO_HTTP
;
628 case category_arg_mms
:
631 case category_arg_rtmp
:
632 *n
|= QUVIPROTO_RTMP
;
634 case category_arg_rtsp
:
635 *n
|= QUVIPROTO_RTSP
;
641 static void depr_category(const char *o
)
643 spew_qe("warning: %s: deprecated, use --category instead\n", o
);
646 static void depr_export_format(const char *o
)
648 spew_qe("warning: %s: deprecated, use --export-format instead\n", o
);
651 static void depr_verbosity(const char *o
)
653 spew_qe("warning: %s: deprecated, use --verbosity instead\n", o
);
656 static void depr_feat(const char *o
)
658 spew_e("warning: %s: deprecated, use --feature instead\n", o
);
661 static int enabled(enum enum_feature on
)
663 const enum enum_feature off
= on
+1;
665 for (i
=0; i
<opts
->feature_given
; ++i
)
667 if (opts
->feature_arg
[i
] == on
)
669 else if (opts
->feature_arg
[i
] == on
+1)
675 static void init_quvi()
677 int resolve
, verify
, proxy
;
678 QUVIcategory categories
;
681 if ((rc
= quvi_init(&quvi
)) != QUVI_OK
)
683 dump_error(quvi
, rc
);
686 assert(quvi
!= NULL
);
688 /* Set quvi options. */
690 quvi_setopt(quvi
, QUVIOPT_FORMAT
, opts
->format_arg
);
692 resolve
= enabled(feature_arg_resolve
);
693 verify
= enabled(feature_arg_verify
);
697 if (opts
->no_resolve_given
)
699 depr_feat("--no-resolve");
703 if (opts
->no_verify_given
)
705 depr_feat("--no-verify");
709 quvi_setopt(quvi
, QUVIOPT_NORESOLVE
, resolve
== 0);
710 quvi_setopt(quvi
, QUVIOPT_NOVERIFY
, verify
== 0);
716 if (opts
->category_arg
!= NULL
)
717 check_categories(&categories
);
719 /* Category: deprecated. To be removed in later versions. */
721 if (opts
->category_http_given
== 1)
723 depr_category("--category-http");
724 categories
|= QUVIPROTO_HTTP
;
726 if (opts
->category_rtmp_given
== 1)
728 depr_category("--category-rtmp");
729 categories
|= QUVIPROTO_RTMP
;
731 if (opts
->category_rtsp_given
== 1)
733 depr_category("--category-rtsp");
734 categories
|= QUVIPROTO_RTSP
;
736 if (opts
->category_mms_given
== 1)
738 depr_category("--category-mms");
739 categories
|= QUVIPROTO_MMS
;
741 if (opts
->category_all_given
== 1)
743 depr_category("--category-all");
744 categories
= QUVIPROTO_ALL
;
748 categories
= QUVIPROTO_ALL
;
750 quvi_setopt(quvi
, QUVIOPT_CATEGORY
, categories
);
752 /* Status callback */
754 quvi_setopt(quvi
, QUVIOPT_STATUSFUNCTION
, status_callback
);
756 /* Use the quvi created cURL handle. */
758 quvi_getinfo(quvi
, QUVIINFO_CURL
, &curl
);
759 assert(curl
!= NULL
);
761 curl_easy_setopt(curl
, CURLOPT_USERAGENT
, opts
->agent_arg
);
762 curl_easy_setopt(curl
, CURLOPT_PROXY
, opts
->proxy_arg
);
764 proxy
= enabled(feature_arg_proxy
);
767 if (opts
->no_proxy_given
== 1)
769 depr_feat("--no-proxy");
774 printf("resolve=%d, verify=%d, proxy=%d\n", resolve
, verify
, proxy
);
780 curl_easy_setopt(curl
, CURLOPT_PROXY
, "");
782 if (opts
->verbose_libcurl_given
)
784 opts
->verbosity_arg
= verbosity_arg_debug
;
785 depr_verbosity("--verbose-libcurl");
788 curl_easy_setopt(curl
, CURLOPT_VERBOSE
,
789 opts
->verbosity_arg
== verbosity_arg_debug
);
791 curl_easy_setopt(curl
, CURLOPT_CONNECTTIMEOUT
, opts
->connect_timeout_arg
);
794 static void cleanup()
797 quvi_llst_free(&input
);
804 cmdline_parser_free(opts
);
808 assert(input
== NULL
);
809 assert(quvi
== NULL
);
810 assert(opts
== NULL
);
813 static void read_from(FILE *f
)
820 while (fgets(b
, (int)sizeof(b
), f
))
824 const size_t n
= strlen(b
)-1;
829 quvi_llst_append(&input
, strdup(b
));
834 /*@null@*/ static char *parse_url_scheme(const char *url
)
838 p
= strstr(url
, ":/");
842 asprintf(&r
, "%.*s", (int)(p
- url
), url
);
847 static int is_url(const char *s
)
849 char *p
= parse_url_scheme(s
);
858 static void read_file(const char *path
)
860 FILE *f
= fopen(path
, "rt");
864 spew_e("error: %s: %s\n", path
, strerror(errno
));
877 static unsigned int read_input()
879 if (opts
->inputs_num
== 0)
884 for (i
=0; i
<opts
->inputs_num
; ++i
)
886 if (is_url(opts
->inputs
[i
]) == 0)
887 read_file(opts
->inputs
[i
]);
888 else /* Must be an URL. */
889 quvi_llst_append(&input
, strdup(opts
->inputs
[i
]));
892 return ((unsigned int)quvi_llst_size(input
));
895 static int config_exists(const char *fpath
)
897 FILE *f
= fopen(fpath
, "r");
905 return (have_config
);
908 static int parse_config(int argc
, char **argv
, char *home
)
910 struct cmdline_parser_params
*pp
= NULL
;
914 asprintf(&fpath
, "%s/%s", home
, config_fname
);
918 have_config
= config_exists(fpath
);
919 if (have_config
== 0)
922 return (have_config
);
925 pp
= cmdline_parser_params_create();
926 pp
->check_required
= 0;
928 have_config
= (int)(cmdline_parser_config_file(fpath
, opts
, pp
) == 0);
929 if (have_config
== 1)
931 _free(fpath
); /* cmdline_parser_ext may exit early. */
933 /* Config parsed at this point. Next, apply the cmdline options
934 * and allow overriding the values defined in the config.
936 * This step resets the $opt_given flags, e.g. if the config
937 * contains the "proxy", the parser sets proxy_given to 1,
938 * --proxy is used to override the config value, the flag
940 pp
->check_required
= 1;
943 have_config
= (int)(cmdline_parser_ext(argc
, argv
, opts
, pp
) == 0);
949 return (have_config
);
952 static void parse_cmdline(int argc
, char **argv
)
957 opts
= calloc(1, sizeof(struct gengetopt_args_info
));
961 home
= getenv("QUVI_HOME");
963 home
= getenv("HOME");
965 if (home
!= NULL
&& getenv("QUVI_NO_CONFIG") == NULL
)
966 have_config
= parse_config(argc
, argv
, home
);
968 if (have_config
== 0)
970 if (cmdline_parser(argc
, argv
, opts
) != 0)
975 static int process_queue()
977 QUVIcode rc
, last_error
;
978 unsigned int input_num
;
979 quvi_llst_node_t curr
;
983 input_num
= read_input();
984 last_error
= QUVI_OK
;
989 spew_qe("error: no input URLs\n");
990 return (QUVI_INVARG
);
993 for (i
=0, curr
=input
; curr
; ++i
)
995 char *url
= quvi_llst_data(curr
);
996 rc
= quvi_parse(quvi
, url
, &media
);
999 assert(media
!= NULL
);
1002 if (opts
->exec_arg
!= NULL
)
1004 do invoke_exec(media
);
1005 while (quvi_next_media_url(media
) == QUVI_OK
);
1010 dump_error(quvi
, rc
);
1014 quvi_parse_close(&media
);
1015 assert(media
== NULL
);
1016 curr
= quvi_llst_next(curr
);
1021 spew_qe("Results: %d OK, %d failed (last 0x%02x), exit with 0x%02x\n",
1022 input_num
- errors
, errors
, last_error
, rc
);
1027 int main(int argc
, char *argv
[])
1029 assert(quvi
== NULL
);
1030 assert(curl
== NULL
);
1031 assert(opts
== NULL
);
1032 assert(input
== NULL
);
1035 parse_cmdline(argc
, argv
);
1037 if (opts
->version_given
== 1)
1040 if (opts
->license_given
== 1)
1047 if (opts
->xml_given
)
1049 opts
->export_format_arg
= export_format_arg_xml
;
1050 depr_export_format("--xml");
1053 if (opts
->quiet_given
== 1)
1055 depr_verbosity("--quiet"); /* Must come before the next line */
1056 opts
->verbosity_arg
= verbosity_arg_quiet
;
1059 if (opts
->query_formats_given
== 1)
1060 query_formats(quvi
);
1062 if (opts
->support_given
== 1)
1065 return (process_queue());
1068 /* vim: set ts=2 sw=2 tw=72 expandtab: */