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)
43 extern char *strepl(const char *s
, const char *what
, const char *with
);
45 static int verbose_flag
= 1;
47 static quvi_t quvi
= NULL
;
49 static CURL
*curl
= NULL
;
51 typedef struct gengetopt_args_info
*opts_t
;
53 static opts_t opts
= NULL
;
56 static quvi_llst_node_t inputs
= NULL
;
58 /* prints to std(e)rr. */
59 static void spew_e(const char *fmt
, ...)
63 vfprintf(stderr
, fmt
, ap
);
67 /* respects (q)uiet, prints to std(e)rr. */
68 static void spew_qe(const char *fmt
, ...)
71 if (verbose_flag
== 0)
74 vfprintf(stderr
, fmt
, ap
);
78 /* glorified printf. */
79 static void spew(const char *fmt
, ...)
83 vfprintf(stdout
, fmt
, ap
);
87 static void dump_error_json(quvi_t quvi
, QUVIcode rc
)
93 " \"message\": \"%s\"\n"
96 "}\n", quvi_strerror(quvi
, rc
));
99 static void dump_error_xml(quvi_t quvi
, QUVIcode rc
)
102 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
104 " <message>%s</message>\n"
106 quvi_strerror(quvi
, rc
));
109 static void dump_error(quvi_t quvi
, QUVIcode rc
)
111 if (opts
->export_errors_given
== 1)
113 if (opts
->xml_given
== 1)
114 dump_error_xml(quvi
, rc
);
116 dump_error_json(quvi
, rc
);
119 fprintf(stderr
, "error: %s\n", quvi_strerror(quvi
, rc
));
122 static void handle_resolve_status(quvi_word type
)
124 if (type
== QUVISTATUSTYPE_DONE
)
127 spew_qe(":: Check for URL redirection ...");
130 static void handle_fetch_status(quvi_word type
, void *p
)
135 spew_qe(":: Fetch %s ...", (char *)p
);
137 case QUVISTATUSTYPE_CONFIG
:
138 spew_qe(":: Fetch config ...");
140 case QUVISTATUSTYPE_PLAYLIST
:
141 spew_qe(":: Fetch playlist ...");
143 case QUVISTATUSTYPE_DONE
:
149 static void handle_verify_status(quvi_word type
)
154 spew_qe(":: Verify media URL ...");
156 case QUVISTATUSTYPE_DONE
:
162 static int status_callback(long param
, void *data
)
164 quvi_word status
, type
;
166 status
= quvi_loword(param
);
167 type
= quvi_hiword(param
);
171 case QUVISTATUS_RESOLVE
:
172 handle_resolve_status(type
);
175 case QUVISTATUS_FETCH
:
176 handle_fetch_status(type
, data
);
179 case QUVISTATUS_VERIFY
:
180 handle_verify_status(type
);
189 /* Divided into smaller blocks. Otherwise -pedantic objects. */
193 " * Copyright (C) 2009-2011 Toni Gundogdu <legatvs@gmail.com>\n"
196 " * This library is free software; you can redistribute it and/or\n" \
197 " * modify it under the terms of the GNU Lesser General Public\n" \
198 " * License as published by the Free Software Foundation; either\n" \
199 " * version 2.1 of the License, or (at your option) any later version.\n"
202 " * This library is distributed in the hope that it will be useful,\n" \
203 " * but WITHOUT ANY WARRANTY; without even the implied warranty of\n" \
204 " * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU\n" \
205 " * Lesser General Public License for more details.\n"
208 " * You should have received a copy of the GNU Lesser General Public\n" \
209 " * License along with this library; if not, write to the Free Software\n" \
210 " * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA\n" \
211 " * 02110-1301 USA\n" " */"
213 static void license()
215 printf("%s *\n%s *\n%s *\n%s\n",
216 LICENSE_1
, LICENSE_2
, LICENSE_3
, LICENSE_4
);
225 static void print_version()
227 static const char version
[] =
233 " for " CANONICAL_TARGET
;
234 printf("quvi %s\n libquvi %s\n libquvi-scripts %s\n",
235 version
, quvi_version(QUVI_VERSION_LONG
),
236 quvi_version(QUVI_VERSION_SCRIPTS
));
240 static void dump_host(char *domain
, char *formats
)
242 printf("%s\t%s\n", domain
, formats
);
247 /* Wraps quvi_supported_ident. */
248 static void supported(quvi_t quvi
)
257 for (i
=0; i
<opts
->inputs_num
; ++i
)
259 rc
= quvi_supported_ident(quvi
, (char*)opts
->inputs
[i
], &ident
);
262 quvi_ident_getprop(ident
,
263 QUVI_IDENT_PROPERTY_FORMATS
, &formats
);
264 spew("%10s : %s\n", formats
, (char *)opts
->inputs
[i
]);
265 quvi_supported_ident_close(&ident
);
274 /* Query which formats are available for the URL */
275 static void query_formats(quvi_t quvi
)
280 if (opts
->inputs_num
< 1)
282 spew_qe("error: no input URLs\n");
286 for (i
=0,rc
=0; i
<opts
->inputs_num
; ++i
)
288 char *formats
= NULL
;
290 rc
= quvi_query_formats(quvi
, (char*)opts
->inputs
[i
], &formats
);
293 spew("%10s : %s\n", formats
, opts
->inputs
[i
]);
303 /* dumps all supported hosts to stdout. */
304 static void support(quvi_t quvi
)
308 if (opts
->inputs_num
>0)
313 char *domain
, *formats
;
316 rc
= quvi_next_supported_website(quvi
, &domain
, &formats
);
321 dump_host(domain
, formats
);
327 spew_e("%s\n", quvi_strerror(quvi
, rc
));
335 static void invoke_exec(quvi_media_t media
)
337 char *cmd
, *media_url
, *q_media_url
;
338 char *page_title
, *q_page_title
, *t
;
341 quvi_getprop(media
, QUVIPROP_PAGETITLE
, &page_title
);
342 t
= strdup(page_title
);
343 t
= strepl(t
, "\"", "\\\""); /* Escape existing double quotation marks */
344 asprintf(&q_page_title
, "\"%s\"", t
); /* Put inside quotation marks */
347 quvi_getprop(media
, QUVIPROP_MEDIAURL
, &media_url
);
348 asprintf(&q_media_url
, "\"%s\"", media_url
);
350 cmd
= strdup(opts
->exec_arg
);
351 cmd
= strepl(cmd
, "%t", q_page_title
);
352 cmd
= strepl(cmd
, "%u", q_media_url
);
364 spew_e("error: failed to execute `%s'\n", cmd
);
367 spew_e("error: child exited with: %d\n", rc
>> 8);
378 double content_length
;
382 typedef struct parsed_url_s
*parsed_url_t
;
384 static void dump_media_url_xml(parsed_url_t p
, int i
)
386 char *media_url
= curl_easy_escape(curl
, p
->media_url
, 0);
388 spew(" <link id=\"%d\">\n", i
);
390 if (p
->content_length
>0)
391 spew(" <length_bytes>%.0f</length_bytes>\n", p
->content_length
);
393 if (strlen(p
->content_type
) >0)
394 spew(" <content_type>%s</content_type>\n", p
->content_type
);
396 if (strlen(p
->file_suffix
) >0)
397 spew(" <file_suffix>%s</file_suffix>\n", p
->file_suffix
);
399 spew(" <url>%s</url>\n"
408 static void dump_media_url_json(parsed_url_t p
, int i
, int prepend_newln
)
410 if (prepend_newln
!= 0)
414 " \"id\": \"%d\",\n", i
);
416 if (p
->content_length
>0)
417 spew(" \"length_bytes\": \"%.0f\",\n", p
->content_length
);
419 if (strlen(p
->content_type
) >0)
420 spew(" \"content_type\": \"%s\",\n", p
->content_type
);
422 if (strlen(p
->file_suffix
) >0)
423 spew(" \"file_suffix\": \"%s\",\n", p
->file_suffix
);
425 spew(" \"url\": \"%s\"\n"
430 static void dump_media_urls(quvi_media_t media
)
432 int json_flag
=0, i
=1;
435 struct parsed_url_s p
;
437 memset(&p
, 0, sizeof(&p
));
438 quvi_getprop(media
, QUVIPROP_MEDIAURL
, &p
.media_url
);
439 quvi_getprop(media
, QUVIPROP_MEDIACONTENTTYPE
, &p
.content_type
);
440 quvi_getprop(media
, QUVIPROP_MEDIACONTENTLENGTH
, &p
.content_length
);
441 quvi_getprop(media
, QUVIPROP_FILESUFFIX
, &p
.file_suffix
);
443 if (opts
->xml_given
== 1)
444 dump_media_url_xml(&p
,i
);
447 dump_media_url_json(&p
, i
, (int)(i
>1));
452 while (quvi_next_media_url(media
) == QUVI_OK
);
470 typedef struct parsed_s
*parsed_t
;
472 static void dump_media_xml(parsed_t p
)
474 char *e_page_url
, *e_thumb_url
;
476 e_page_url
= curl_easy_escape(curl
, p
->page_url
, 0);
477 e_thumb_url
= curl_easy_escape(curl
, p
->thumb_url
, 0);
479 spew("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
480 "<media id=\"%s\" host=\"%s\">\n"
481 " <format_requested>%s</format_requested>\n"
482 " <page_title>%s</page_title>\n"
483 " <page_url>%s</page_url>\n",
488 e_page_url
? e_page_url
: "");
490 if (strlen(p
->start_time
) >0)
491 spew(" <start_time>%s</start_time>\n", p
->start_time
);
493 if (e_thumb_url
!= NULL
&& strlen(e_thumb_url
) >0)
494 spew(" <thumbnail_url>%s</thumbnail_url>\n", e_thumb_url
);
497 spew(" <duration>%.0f</duration>\n", p
->duration
);
499 if (e_page_url
!= NULL
)
501 curl_free(e_page_url
);
505 if (e_thumb_url
!= NULL
)
507 curl_free(e_thumb_url
);
512 static void dump_media_json(parsed_t p
)
516 t
= strdup(p
->page_title
);
517 t
= strepl(t
, "\"", "\\\"");
520 " \"host\": \"%s\",\n"
521 " \"page_title\": \"%s\",\n"
522 " \"page_url\": \"%s\",\n"
524 " \"format_requested\": \"%s\",\n",
531 if (strlen(p
->start_time
) >0)
532 spew(" \"start_time\": \"%s\",\n", p
->start_time
);
534 if (strlen(p
->thumb_url
) >0)
535 spew(" \"thumbnail_url\": \"%s\",\n", p
->thumb_url
);
538 spew(" \"duration\": \"%.0f\",\n", p
->duration
);
540 spew(" \"link\": [\n");
545 static void dump_media(quvi_media_t media
)
549 memset(&p
, 0, sizeof(p
));
551 quvi_getprop(media
, QUVIPROP_HOSTID
, &p
.host
);
552 quvi_getprop(media
, QUVIPROP_PAGEURL
, &p
.page_url
);
553 quvi_getprop(media
, QUVIPROP_PAGETITLE
, &p
.page_title
);
554 quvi_getprop(media
, QUVIPROP_MEDIAID
, &p
.media_id
);
555 quvi_getprop(media
, QUVIPROP_FORMAT
, &p
.format
);
556 quvi_getprop(media
, QUVIPROP_STARTTIME
, &p
.start_time
);
557 quvi_getprop(media
, QUVIPROP_MEDIATHUMBNAILURL
, &p
.thumb_url
);
558 quvi_getprop(media
, QUVIPROP_MEDIADURATION
, &p
.duration
);
560 if (opts
->xml_given
== 1)
565 dump_media_urls(media
);
567 if (opts
->xml_given
== 1)
573 static void dump_category_help()
577 " quvi --category <value[,value,...]>\n"
578 "Where the values may be:\n"
579 " http, rtmp, rtsp, mms, all\n"
581 " quvi --category all ;# program default\n"
582 " quvi --category rtmp,mms ;# multiple categories\n");
586 static void check_category(const char *s
, QUVIcategory
*n
)
591 if (strcmp(s
, "all") == 0)
593 else if (strcmp(s
, "help") == 0)
594 dump_category_help();
597 if (strcmp(s
, "http") == 0)
598 *n
|= QUVIPROTO_HTTP
;
599 else if (strcmp(s
, "rtmp") == 0)
600 *n
|= QUVIPROTO_RTMP
;
601 else if (strcmp(s
, "rtsp") == 0)
602 *n
|= QUVIPROTO_RTSP
;
603 else if (strcmp(s
, "mms") == 0)
607 fprintf(stderr
, "warning: --category: %s: invalid value, "
608 "try \"help\"\n", s
);
613 static QUVIcategory
parse_categories(char *s
)
621 for (i
=0; i
<sizeof(b
) && *p
!='\0'; ++i
,++p
)
631 check_category(b
, &n
);
637 static void depr_category(const char *o
)
639 fprintf(stderr
, "warning: %s: deprecated, use --category instead\n", o
);
642 static void init_quvi()
644 QUVIcategory category
;
647 if ((rc
= quvi_init(&quvi
)) != QUVI_OK
)
649 dump_error(quvi
, rc
);
652 assert(quvi
!= NULL
);
654 /* Set quvi options. */
656 if (opts
->format_given
== 1)
657 quvi_setopt(quvi
, QUVIOPT_FORMAT
, opts
->format_arg
);
659 quvi_setopt(quvi
, QUVIOPT_NORESOLVE
, opts
->no_resolve_given
);
660 quvi_setopt(quvi
, QUVIOPT_NOVERIFY
, opts
->no_verify_given
);
666 if (opts
->category_given
== 1)
667 category
= parse_categories(opts
->category_arg
);
669 /* Category: deprecated. To be removed in later versions. */
671 if (opts
->category_http_given
== 1)
673 depr_category("--category-http");
674 category
|= QUVIPROTO_HTTP
;
676 if (opts
->category_rtmp_given
== 1)
678 depr_category("--category-rtmp");
679 category
|= QUVIPROTO_RTMP
;
681 if (opts
->category_rtsp_given
== 1)
683 depr_category("--category-rtsp");
684 category
|= QUVIPROTO_RTSP
;
686 if (opts
->category_mms_given
== 1)
688 depr_category("--category-mms");
689 category
|= QUVIPROTO_MMS
;
691 if (opts
->category_all_given
== 1)
692 depr_category("--category-all");
695 category
= QUVIPROTO_ALL
;
697 quvi_setopt(quvi
, QUVIOPT_CATEGORY
, category
);
699 /* Status callback */
701 quvi_setopt(quvi
, QUVIOPT_STATUSFUNCTION
, status_callback
);
703 /* Use the quvi created cURL handle. */
705 quvi_getinfo(quvi
, QUVIINFO_CURL
, &curl
);
706 assert(curl
!= NULL
);
708 if (opts
->agent_given
== 1)
709 curl_easy_setopt(curl
, CURLOPT_USERAGENT
, opts
->agent_arg
);
711 if (opts
->proxy_given
== 1)
712 curl_easy_setopt(curl
, CURLOPT_PROXY
, opts
->proxy_arg
);
714 if (opts
->no_proxy_given
== 1)
715 curl_easy_setopt(curl
, CURLOPT_PROXY
, "");
717 curl_easy_setopt(curl
, CURLOPT_VERBOSE
, opts
->verbose_libcurl_given
);
718 curl_easy_setopt(curl
, CURLOPT_CONNECTTIMEOUT
, opts
->connect_timeout_arg
);
721 static void cleanup()
724 quvi_llst_free(&inputs
);
725 assert(inputs
== NULL
);
729 assert(quvi
== NULL
);
733 cmdline_parser_free(opts
);
736 assert(opts
== NULL
);
739 static void read_from(FILE *f
)
746 while (fgets(b
, (int)sizeof(b
), f
))
750 const size_t n
= strlen(b
)-1;
755 quvi_llst_append(&inputs
, strdup(b
));
760 /*@null@*/ static char *parse_url_scheme(const char *url
)
764 p
= strstr(url
, ":/");
768 asprintf(&r
, "%.*s", (int)(p
- url
), url
);
773 static int is_url(const char *s
)
775 char *p
= parse_url_scheme(s
);
784 static void read_file(const char *path
)
786 FILE *f
= fopen(path
, "rt");
789 spew_e("error: %s: %s\n", path
, strerror(errno
));
801 static unsigned int read_input()
803 if (opts
->inputs_num
== 0)
808 for (i
=0; i
<opts
->inputs_num
; ++i
)
810 if (is_url(opts
->inputs
[i
]) == 0)
811 read_file(opts
->inputs
[i
]);
812 else /* Must be an URL. */
813 quvi_llst_append(&inputs
, strdup(opts
->inputs
[i
]));
816 return ((unsigned int)quvi_llst_size(inputs
));
819 int main(int argc
, char *argv
[])
821 const char *home
, *no_config
, *fname
;
822 QUVIcode rc
, last_failure
;
823 unsigned int inputs_num
;
824 quvi_llst_node_t curr
;
829 assert(quvi
== NULL
);
830 assert(curl
== NULL
);
831 assert(opts
== NULL
);
832 assert(inputs
== NULL
);
834 no_config
= getenv("QUVI_NO_CONFIG");
837 home
= getenv("QUVI_HOME");
839 home
= getenv("HOME");
849 opts
= calloc(1, sizeof(struct gengetopt_args_info
));
853 /* Init cmdline parser. */
855 if (home
!= NULL
&& no_config
== 0)
860 asprintf(&path
, "%s%s", home
, fname
);
861 f
= fopen(path
, "r");
865 struct cmdline_parser_params
*pp
;
870 pp
= cmdline_parser_params_create();
871 pp
->check_required
= 0;
873 if (cmdline_parser_config_file(path
, opts
, pp
) == 0)
877 pp
->check_required
= 1;
879 if (cmdline_parser_ext(argc
, argv
, opts
, pp
) == 0)
887 if (no_config_flag
== 1)
889 if (cmdline_parser(argc
, argv
, opts
) != 0)
890 return (QUVI_INVARG
);
893 if (opts
->version_given
== 1)
896 if (opts
->license_given
== 1)
899 verbose_flag
= (int)(opts
->quiet_given
== 0);
902 if (opts
->query_formats_given
== 1)
905 if (opts
->support_given
== 1)
910 inputs_num
= read_input();
914 spew_qe("error: no input URLs\n");
915 return (QUVI_INVARG
);
918 last_failure
= QUVI_OK
;
921 for (i
=0, curr
=inputs
; curr
; ++i
)
923 char *url
= quvi_llst_data(curr
);
924 rc
= quvi_parse(quvi
, url
, &media
);
927 assert(media
!= NULL
);
930 if (opts
->exec_given
== 1)
936 while (quvi_next_media_url(media
) == QUVI_OK
);
945 quvi_parse_close(&media
);
946 assert(media
== NULL
);
947 curr
= quvi_llst_next(curr
);
952 spew_qe("Results: %d OK, %d failed (last 0x%02x), exit with 0x%02x\n",
953 inputs_num
- errors
, errors
, last_failure
, rc
);
959 /* vim: set ts=2 sw=2 tw=72 expandtab: */