2 * Copyright (C) 2009,2010 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 video tool. */
30 #include <curl/curl.h>
34 #include "quvi/quvi.h"
38 extern char *strepl(const char *s
, const char *what
, const char *with
);
40 static int verbose_flag
= 1;
42 /* prints to std(e)rr. */
43 static void spew_e(const char *fmt
, ...)
47 vfprintf(stderr
, fmt
, ap
);
51 /* respects (q)uiet, prints to std(e)rr. */
52 static void spew_qe(const char *fmt
, ...)
58 vfprintf(stderr
, fmt
, ap
);
62 /* glorified printf. */
63 static void spew(const char *fmt
, ...)
67 vfprintf(stdout
, fmt
, ap
);
71 typedef struct gengetopt_args_info opts_s
;
73 static void handle_shortened_status(quvi_word type
)
75 if (type
== QUVISTATUSTYPE_DONE
)
78 spew_qe(":: Check for shortened URL ...");
81 static void handle_fetch_status(quvi_word type
, void *p
)
85 spew_qe(":: Fetch %s ...", (char *)p
);
87 case QUVISTATUSTYPE_CONFIG
:
88 spew_qe(":: Fetch config ...");
90 case QUVISTATUSTYPE_PLAYLIST
:
91 spew_qe(":: Fetch playlist ...");
93 case QUVISTATUSTYPE_DONE
:
99 static void handle_verify_status(quvi_word type
)
103 spew_qe(":: Verify video link ...");
105 case QUVISTATUSTYPE_DONE
:
111 static int status_callback(long param
, void *data
)
113 quvi_word status
, type
;
115 status
= quvi_loword(param
);
116 type
= quvi_hiword(param
);
120 case QUVISTATUS_SHORTENED
:
121 handle_shortened_status(type
);
124 case QUVISTATUS_FETCH
:
125 handle_fetch_status(type
, data
);
128 case QUVISTATUS_VERIFY
:
129 handle_verify_status(type
);
138 static size_t write_callback(void *p
, size_t size
, size_t nmemb
, void *data
)
140 size_t r
= quvi_write_callback_default(p
, size
, nmemb
, data
);
141 /* Could do something useful here. */
148 extern char LICENSE
[];
150 static void license(opts_s opts
)
152 printf("%s\n", LICENSE
);
153 cmdline_parser_free(&opts
);
157 static void version(opts_s opts
)
159 printf("quvi version %s\n", quvi_version(QUVI_VERSION_LONG
));
160 cmdline_parser_free(&opts
);
164 static void dump_host(char *domain
, char *formats
)
166 printf("%s\t%s\n", domain
, formats
);
171 /* Wraps quvi_supported. */
172 static void supported(quvi_t quvi
, opts_s opts
)
177 for (i
= 0; i
< opts
.inputs_num
; ++i
) {
178 rc
= quvi_supported(quvi
, (char *)opts
.inputs
[i
]);
180 spew_qe("%s: OK\n", (char *)opts
.inputs
[i
]);
182 spew_qe("error: %s\n", quvi_strerror(quvi
, rc
));
186 cmdline_parser_free(&opts
);
191 /* dumps all supported hosts to stdout. */
192 static void support(quvi_t quvi
, opts_s opts
)
196 if (opts
.inputs_num
> 0)
197 supported(quvi
, opts
);
200 char *domain
, *formats
;
203 rc
= quvi_next_supported_website(quvi
, &domain
, &formats
);
207 dump_host(domain
, formats
);
213 spew_e("%s\n", quvi_strerror(quvi
, rc
));
219 cmdline_parser_free(&opts
);
224 static void invoke_exec(quvi_video_t video
, const char *video_url
, opts_s opts
)
226 char *quoted_url
, *arg
;
229 asprintf("ed_url
, "\"%s\"", video_url
);
231 arg
= strdup(opts
.exec_arg
);
232 arg
= strepl(arg
, "%u", quoted_url
);
243 spew_e("error: failed to execute `%s'\n", arg
);
246 spew_e("error: child exited with: %d\n", rc
>> 8);
255 dump_video_link_xml(CURL
* curl
,
258 double file_length
, char *file_ct
, char *file_suffix
)
262 url
= curl_easy_escape(curl
, video_url
, 0);
264 spew(" <link id=\"%d\">\n"
265 " <length_bytes>%.0f</length_bytes>\n"
266 " <content_type>%s</content_type>\n"
267 " <file_suffix>%s</file_suffix>\n"
270 i
, file_length
, file_ct
, file_suffix
, url
? url
: video_url
);
279 dump_video_link_old(int i
,
281 double file_length
, char *file_suffix
, char *file_ct
)
283 spew("link %02d : %s\n"
284 ":: length: %.0f\n:: suffix: %s\n:: content-type: %s\n\n",
285 i
, video_url
, file_length
, file_suffix
, file_ct
);
289 dump_video_link_json(int i
,
291 double file_length
, char *file_suffix
, char *file_ct
)
295 " \"length_bytes\": \"%.0f\",\n"
296 " \"content_type\": \"%s\",\n"
297 " \"file_suffix\": \"%s\",\n"
300 i
, file_length
, file_ct
, file_suffix
, video_url
, i
> 1 ? "," : "");
303 static void dump_video_links(quvi_video_t video
, opts_s opts
, CURL
* curl
)
307 char *video_url
, *file_suffix
, *file_ct
;
310 quvi_getprop(video
, QUVIPROP_VIDEOURL
, &video_url
);
311 quvi_getprop(video
, QUVIPROP_VIDEOFILECONTENTTYPE
, &file_ct
);
312 quvi_getprop(video
, QUVIPROP_VIDEOFILESUFFIX
, &file_suffix
);
313 quvi_getprop(video
, QUVIPROP_VIDEOFILELENGTH
, &file_length
);
318 dump_video_link_xml(curl
, i
, video_url
, file_length
,
319 file_ct
, file_suffix
);
321 else if (opts
.old_given
)
322 dump_video_link_old(i
, video_url
, file_length
, file_suffix
, file_ct
);
324 dump_video_link_json(i
, video_url
, file_length
, file_suffix
, file_ct
);
326 while (quvi_next_videolink(video
) == QUVI_OK
);
330 dump_video_xml(CURL
* curl
,
331 char *video_id
, char *host
, char *format
, char *page_title
,
336 url
= curl_easy_escape(curl
, page_link
, 0);
338 spew("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
339 "<video id=\"%s\" host=\"%s\">\n"
340 " <format_requested>%s</format_requested>\n"
341 " <page_title>%s</page_title>\n"
342 " <page_url>%s</page_url>\n",
343 video_id
, host
, format
, page_title
, url
? url
: page_link
);
353 dump_video_old(char *video_id
, char *host
, char *format
, char *page_title
,
356 spew(" > Dump video:\n"
361 "format : %s (requested)\n",
362 host
, page_link
, page_title
, video_id
, format
);
366 dump_video_json(char *video_id
, char *host
, char *format
, char *page_title
,
371 t
= strdup(page_title
);
372 t
= strepl(t
, "\"", "\\\"");
375 " \"host\": \"%s\",\n"
376 " \"page_title\": \"%s\",\n"
377 " \"page_url\": \"%s\",\n"
379 " \"format_requested\": \"%s\",\n"
380 " \"link\": [\n", host
, t
, page_link
, video_id
, format
);
386 static void dump_video(quvi_video_t video
, opts_s opts
, CURL
* curl
)
388 char *page_link
, *page_title
, *video_id
, *format
, *host
;
390 quvi_getprop(video
, QUVIPROP_HOSTID
, &host
);
391 quvi_getprop(video
, QUVIPROP_PAGEURL
, &page_link
);
392 quvi_getprop(video
, QUVIPROP_PAGETITLE
, &page_title
);
393 quvi_getprop(video
, QUVIPROP_VIDEOID
, &video_id
);
394 quvi_getprop(video
, QUVIPROP_VIDEOFORMAT
, &format
);
397 dump_video_xml(curl
, video_id
, host
, format
, page_title
, page_link
);
398 else if (opts
.old_given
)
399 dump_video_old(video_id
, host
, format
, page_title
, page_link
);
401 dump_video_json(video_id
, host
, format
, page_title
, page_link
);
403 dump_video_links(video
, opts
, curl
);
407 else if (opts
.old_given
) ;
413 expect_error(const char *what
, const char *expected
, const char *got
)
415 fprintf(stderr
, "error: %s:\n expected: \"%s\"\n got: \"%s\"\n\n",
416 what
, expected
, got
);
420 expect_error_d(const char *what
, const double expected
, const double got
)
423 "error: %s:\n expected: \"%.0f\"\n got: \"%.0f\"\n\n", what
,
427 static int check_values(quvi_video_t video
, opts_s opts
)
429 char *title
, *expect
, *id
, *suffix
;
435 if (opts
.page_title_given
) {
437 expect
= opts
.page_title_arg
;
439 quvi_getprop(video
, QUVIPROP_PAGETITLE
, &title
);
441 rc
= strcmp(expect
, title
) != 0;
443 expect_error("page title", expect
, title
);
446 if (opts
.video_id_given
&& !rc
) {
448 expect
= opts
.video_id_arg
;
450 quvi_getprop(video
, QUVIPROP_VIDEOID
, &id
);
452 rc
= strcmp(expect
, id
) != 0;
454 expect_error("video id", expect
, id
);
457 if (opts
.file_suffix_given
&& !rc
) {
459 expect
= opts
.file_suffix_arg
;
461 quvi_getprop(video
, QUVIPROP_VIDEOFILESUFFIX
, &suffix
);
463 rc
= strcmp(expect
, suffix
) != 0;
465 expect_error("file suffix", expect
, suffix
);
468 if (opts
.file_length_given
&& !rc
) {
470 double expect_d
= opts
.file_length_arg
;
472 quvi_getprop(video
, QUVIPROP_VIDEOFILELENGTH
, &length
);
474 rc
= expect_d
!= length
;
476 expect_error_d("file length", expect_d
, length
);
482 static const char *tests
[] = {
483 "http://www.cbsnews.com/video/watch/?id=7118769n",
484 "http://videos.sapo.pt/hd4ZBIHG80zFviLc5YEa",
485 "http://www.dailymotion.com/video/xdpig1_city-of-scars_shortfilms",
486 "http://www.spiegel.de/video/video-1012582.html",
487 "http://vimeo.com/1485507",
488 "http://en.sevenload.com/videos/IUL3gda-Funny-Football-Clips",
489 "http://www.liveleak.com/view?i=704_1228511265",
490 "http://video.google.com/videoplay?docid=-6970952080219955808",
491 "http://video.golem.de/internet/2174/firefox-3.5-test.html",
492 "http://www.funnyhub.com/videos/pages/crazy-hole-in-one.html",
493 "http://www.clipfish.de/video/3100131/matratzendomino/",
494 "http://www.youtube.com/watch?v=9dgSa4wmMzk",
495 "http://break.com/index/beach-tackle-whip-lash.html",
496 "http://www.gaskrank.tv/tv/rennstrecken/1-runde-oschersleben-14082008--6985.htm",
497 "http://www.buzzhumor.com/videos/32561/Girl_Feels_Shotgun_Power",
498 "http://www.funnyordie.com/videos/776d200b1c/etiquette-ninjas-episode-5-dicks-on-elevators",
499 "http://www.charlierose.com/view/interview/11125",
500 "http://www.academicearth.org/lectures/building-dynamic-websites-http",
501 /* uses "redirect". */
502 "http://www.academicearth.org/lectures/intro-roman-architecture",
503 "http://www.collegehumor.com/video:1942317",
504 "http://www.theonion.com/video/time-announces-new-version-of-magazine-aimed-at-ad,17950/",
505 "http://www.bloomberg.com/video/63722844/",
507 "http://www.tube8.com/fetish/japanese-melon-gal-censored/186133/",
508 "http://www.xvideos.com/video243887/devi_emmerson_body_painting",
509 "http://www.youjizz.com/videos/glamour-girls---melissa-125602.html",
517 static void dump_error(quvi_t quvi
, QUVIcode rc
, opts_s opts
)
519 if (rc
!= QUVI_NOSUPPORT
)
520 fprintf(stderr
, "\n");
522 fprintf(stderr
, "error: %s\n", quvi_strerror(quvi
, rc
));
524 if (!opts
.test_all_given
) {
526 cmdline_parser_free(&opts
);
531 static void match_test(quvi_t quvi
, opts_s opts
, CURL
* curl
)
536 /* None of our tests (should) use shortened URLs. */
537 quvi_setopt(quvi
, QUVIOPT_NOSHORTENED
, 1L);
539 for (rc
= QUVI_OK
, no_match
= 1, i
= 0; tests
[i
]; ++i
) {
540 if (strstr(tests
[i
], opts
.test_arg
) != NULL
) {
544 rc
= quvi_parse(quvi
, (char *)tests
[i
], &v
);
546 dump_error(quvi
, rc
, opts
);
548 dump_video(v
, opts
, curl
);
549 rc
= check_values(v
, opts
);
551 if (opts
.exec_given
&& rc
== QUVI_OK
) {
552 char *video_url
= NULL
;
554 quvi_getprop(v
, QUVIPROP_VIDEOURL
, &video_url
);
555 invoke_exec(v
, video_url
, opts
);
556 } while (quvi_next_videolink(v
) == QUVI_OK
);
559 quvi_parse_close(&v
);
564 fprintf(stderr
, "error: nothing matched `%s'\n", opts
.test_arg
);
568 cmdline_parser_free(&opts
);
574 static void test_all(quvi_t quvi
, opts_s opts
, CURL
* curl
)
580 /* None of our tests (should) use shortened URLs. */
581 quvi_setopt(quvi
, QUVIOPT_NOSHORTENED
, 1L);
583 spew_qe(":: Run built-in video link tests.\n");
584 for (m
= 0; tests
[m
]; ++m
) ;
586 for (i
= 0; i
< m
; ++i
) {
587 spew_qe(" > Test #%02d/%02d:\n", i
+ 1, m
);
589 rc
= quvi_parse(quvi
, (char *)tests
[i
], &video
);
591 dump_error(quvi
, rc
, opts
);
594 dump_video(video
, opts
, curl
);
596 quvi_parse_close(&video
);
598 spew_qe(":: Tests done.\n");
601 static quvi_t
init_quvi(opts_s opts
, CURL
** curl
)
606 if ((rc
= quvi_init(&quvi
)) != QUVI_OK
)
607 dump_error(quvi
, rc
, opts
);
610 /* Set quvi options. */
612 if (opts
.format_given
)
613 quvi_setopt(quvi
, QUVIOPT_FORMAT
, opts
.format_arg
);
615 quvi_setopt(quvi
, QUVIOPT_NOSHORTENED
, opts
.no_shortened_given
);
616 quvi_setopt(quvi
, QUVIOPT_NOVERIFY
, opts
.no_verify_given
);
618 quvi_setopt(quvi
, QUVIOPT_STATUSFUNCTION
, status_callback
);
619 quvi_setopt(quvi
, QUVIOPT_WRITEFUNCTION
, write_callback
);
621 /* Use the quvi created cURL handle. */
623 quvi_getinfo(quvi
, QUVIINFO_CURL
, curl
);
626 if (opts
.agent_given
)
627 curl_easy_setopt(*curl
, CURLOPT_USERAGENT
, opts
.agent_arg
);
629 if (opts
.proxy_given
)
630 curl_easy_setopt(*curl
, CURLOPT_PROXY
, opts
.proxy_arg
);
632 if (opts
.no_proxy_given
)
633 curl_easy_setopt(*curl
, CURLOPT_PROXY
, "");
635 curl_easy_setopt(*curl
, CURLOPT_VERBOSE
, opts
.verbose_libcurl_given
);
637 curl_easy_setopt(*curl
, CURLOPT_CONNECTTIMEOUT
, opts
.connect_timeout_arg
);
642 int main(int argc
, char *argv
[])
644 const char *url
, *home
, *no_config
, *fname
;
656 no_config
= getenv("QUVI_NO_CONFIG");
658 home
= getenv("QUVI_HOME");
660 home
= getenv("HOME");
670 /* Init cmdline parser. */
672 if (home
&& !no_config
) {
676 asprintf(&path
, "%s%s", home
, fname
);
677 f
= fopen(path
, "r");
680 struct cmdline_parser_params
*pp
;
685 pp
= cmdline_parser_params_create();
686 pp
->check_required
= 0;
688 if (cmdline_parser_config_file(path
, &opts
, pp
) == 0) {
691 pp
->check_required
= 1;
693 if (cmdline_parser_ext(argc
, argv
, &opts
, pp
) == 0)
704 if (no_config_flag
) {
705 if (cmdline_parser(argc
, argv
, &opts
) != 0)
706 return (QUVI_INVARG
);
709 if (opts
.version_given
)
712 if (opts
.license_given
)
715 verbose_flag
= !opts
.quiet_given
;
717 quvi
= init_quvi(opts
, &curl
);
719 if (opts
.support_given
)
724 if (opts
.test_all_given
)
725 test_all(quvi
, opts
, curl
);
727 else if (opts
.test_given
)
728 match_test(quvi
, opts
, curl
);
732 if (opts
.inputs_num
== 0)
733 fprintf(stderr
, "error: no input links\n");
735 for (i
= 0; i
< opts
.inputs_num
; ++i
) {
737 rc
= quvi_parse(quvi
, (char *)opts
.inputs
[i
], &video
);
740 dump_error(quvi
, rc
, opts
);
743 dump_video(video
, opts
, curl
);
745 if (opts
.exec_given
) {
746 char *video_url
= NULL
;
748 quvi_getprop(video
, QUVIPROP_VIDEOURL
, &video_url
);
749 invoke_exec(video
, video_url
, opts
);
750 } while (quvi_next_videolink(video
) == QUVI_OK
);
753 quvi_parse_close(&video
);
763 cmdline_parser_free(&opts
);