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
,
141 size_t r
= quvi_write_callback_default(p
, size
, nmemb
, data
);
142 /* Could do something useful here. */
149 /* Divided into smaller blocks. Otherwise -pedantic objects. */
153 " * Copyright (C) 2009,2010,2011 Toni Gundogdu <legatvs@gmail.com>\n"
156 " * This library is free software; you can redistribute it and/or\n" \
157 " * modify it under the terms of the GNU Lesser General Public\n" \
158 " * License as published by the Free Software Foundation; either\n" \
159 " * version 2.1 of the License, or (at your option) any later version.\n"
162 " * This library is distributed in the hope that it will be useful,\n" \
163 " * but WITHOUT ANY WARRANTY; without even the implied warranty of\n" \
164 " * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU\n" \
165 " * Lesser General Public License for more details.\n"
168 " * You should have received a copy of the GNU Lesser General Public\n" \
169 " * License along with this library; if not, write to the Free Software\n" \
170 " * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA\n" \
171 " * 02110-1301 USA\n" " */"
173 static void license(opts_s opts
)
175 printf("%s *\n%s *\n%s *\n%s\n", LICENSE_1
, LICENSE_2
, LICENSE_3
,
177 cmdline_parser_free(&opts
);
186 static void version(opts_s opts
)
188 printf("quvi version %s\n", quvi_version(QUVI_VERSION_LONG
));
189 cmdline_parser_free(&opts
);
193 static void dump_host(char *domain
, char *formats
)
195 printf("%s\t%s\n", domain
, formats
);
200 /* Wraps quvi_supported. */
201 static void supported(quvi_t quvi
, opts_s opts
)
206 for (i
= 0; i
< opts
.inputs_num
; ++i
) {
207 rc
= quvi_supported(quvi
, (char *)opts
.inputs
[i
]);
209 spew_qe("%s: OK\n", (char *)opts
.inputs
[i
]);
211 spew_qe("error: %s\n", quvi_strerror(quvi
, rc
));
215 cmdline_parser_free(&opts
);
220 /* dumps all supported hosts to stdout. */
221 static void support(quvi_t quvi
, opts_s opts
)
225 if (opts
.inputs_num
> 0)
226 supported(quvi
, opts
);
229 char *domain
, *formats
;
232 rc
= quvi_next_supported_website(quvi
, &domain
, &formats
);
236 dump_host(domain
, formats
);
242 spew_e("%s\n", quvi_strerror(quvi
, rc
));
248 cmdline_parser_free(&opts
);
253 static void invoke_exec(quvi_video_t video
, const char *video_url
,
256 char *quoted_url
, *arg
;
259 asprintf("ed_url
, "\"%s\"", video_url
);
261 arg
= strdup(opts
.exec_arg
);
262 arg
= strepl(arg
, "%u", quoted_url
);
273 spew_e("error: failed to execute `%s'\n", arg
);
276 spew_e("error: child exited with: %d\n", rc
>> 8);
285 dump_video_link_xml(CURL
* curl
,
288 double file_length
, char *file_ct
,
293 url
= curl_easy_escape(curl
, video_url
, 0);
295 spew(" <link id=\"%d\">\n"
296 " <length_bytes>%.0f</length_bytes>\n"
297 " <content_type>%s</content_type>\n"
298 " <file_suffix>%s</file_suffix>\n"
301 i
, file_length
, file_ct
, file_suffix
, url
? url
: video_url
);
310 dump_video_link_old(int i
,
312 double file_length
, char *file_suffix
,
315 spew("link %02d : %s\n"
316 ":: length: %.0f\n:: suffix: %s\n:: content-type: %s\n\n",
317 i
, video_url
, file_length
, file_suffix
, file_ct
);
321 dump_video_link_json(int i
,
323 double file_length
, char *file_suffix
,
328 " \"length_bytes\": \"%.0f\",\n"
329 " \"content_type\": \"%s\",\n"
330 " \"file_suffix\": \"%s\",\n"
333 i
, file_length
, file_ct
, file_suffix
, video_url
,
337 static void dump_video_links(quvi_video_t video
, opts_s opts
,
342 char *video_url
, *file_suffix
, *file_ct
;
345 quvi_getprop(video
, QUVIPROP_VIDEOURL
, &video_url
);
346 quvi_getprop(video
, QUVIPROP_VIDEOFILECONTENTTYPE
, &file_ct
);
347 quvi_getprop(video
, QUVIPROP_VIDEOFILESUFFIX
, &file_suffix
);
348 quvi_getprop(video
, QUVIPROP_VIDEOFILELENGTH
, &file_length
);
353 dump_video_link_xml(curl
, i
, video_url
, file_length
,
354 file_ct
, file_suffix
);
356 else if (opts
.old_given
)
357 dump_video_link_old(i
, video_url
, file_length
, file_suffix
,
360 dump_video_link_json(i
, video_url
, file_length
, file_suffix
,
363 while (quvi_next_videolink(video
) == QUVI_OK
);
367 dump_video_xml(CURL
* curl
,
368 char *video_id
, char *host
, char *format
,
369 char *page_title
, char *page_link
)
373 url
= curl_easy_escape(curl
, page_link
, 0);
375 spew("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
376 "<video id=\"%s\" host=\"%s\">\n"
377 " <format_requested>%s</format_requested>\n"
378 " <page_title>%s</page_title>\n"
379 " <page_url>%s</page_url>\n",
380 video_id
, host
, format
, page_title
, url
? url
: page_link
);
390 dump_video_old(char *video_id
, char *host
, char *format
,
391 char *page_title
, char *page_link
)
393 spew(" > Dump video:\n"
398 "format : %s (requested)\n",
399 host
, page_link
, page_title
, video_id
, format
);
403 dump_video_json(char *video_id
, char *host
, char *format
,
404 char *page_title
, char *page_link
)
408 t
= strdup(page_title
);
409 t
= strepl(t
, "\"", "\\\"");
412 " \"host\": \"%s\",\n"
413 " \"page_title\": \"%s\",\n"
414 " \"page_url\": \"%s\",\n"
416 " \"format_requested\": \"%s\",\n"
417 " \"link\": [\n", host
, t
, page_link
, video_id
, format
);
423 static void dump_video(quvi_video_t video
, opts_s opts
, CURL
* curl
)
425 char *page_link
, *page_title
, *video_id
, *format
, *host
;
427 quvi_getprop(video
, QUVIPROP_HOSTID
, &host
);
428 quvi_getprop(video
, QUVIPROP_PAGEURL
, &page_link
);
429 quvi_getprop(video
, QUVIPROP_PAGETITLE
, &page_title
);
430 quvi_getprop(video
, QUVIPROP_VIDEOID
, &video_id
);
431 quvi_getprop(video
, QUVIPROP_VIDEOFORMAT
, &format
);
434 dump_video_xml(curl
, video_id
, host
, format
, page_title
, page_link
);
435 else if (opts
.old_given
)
436 dump_video_old(video_id
, host
, format
, page_title
, page_link
);
438 dump_video_json(video_id
, host
, format
, page_title
, page_link
);
440 dump_video_links(video
, opts
, curl
);
444 else if (opts
.old_given
) ;
450 expect_error(const char *what
, const char *expected
, const char *got
)
452 fprintf(stderr
, "error: %s:\n expected: \"%s\"\n got: \"%s\"\n\n",
453 what
, expected
, got
);
457 expect_error_d(const char *what
, const double expected
,
461 "error: %s:\n expected: \"%.0f\"\n got: \"%.0f\"\n\n", what
,
465 static int check_values(quvi_video_t video
, opts_s opts
)
467 char *title
, *expect
, *id
, *suffix
;
473 if (opts
.page_title_given
) {
475 expect
= opts
.page_title_arg
;
477 quvi_getprop(video
, QUVIPROP_PAGETITLE
, &title
);
479 rc
= strcmp(expect
, title
) != 0;
481 expect_error("page title", expect
, title
);
484 if (opts
.video_id_given
&& !rc
) {
486 expect
= opts
.video_id_arg
;
488 quvi_getprop(video
, QUVIPROP_VIDEOID
, &id
);
490 rc
= strcmp(expect
, id
) != 0;
492 expect_error("video id", expect
, id
);
495 if (opts
.file_suffix_given
&& !rc
) {
497 expect
= opts
.file_suffix_arg
;
499 quvi_getprop(video
, QUVIPROP_VIDEOFILESUFFIX
, &suffix
);
501 rc
= strcmp(expect
, suffix
) != 0;
503 expect_error("file suffix", expect
, suffix
);
506 if (opts
.file_length_given
&& !rc
) {
508 double expect_d
= opts
.file_length_arg
;
510 quvi_getprop(video
, QUVIPROP_VIDEOFILELENGTH
, &length
);
512 rc
= expect_d
!= length
;
514 expect_error_d("file length", expect_d
, length
);
520 static const char *tests
[] = {
521 "http://www.publicsenat.fr/vod/un-monde-de-bulles/speciale-journal-de-spirou/67141",
522 "http://video.globo.com/Videos/Player/Noticias/0,,GIM1392245-7823-QUATRO+MEDICOS+SAO+PRESOS+POR+VENDER+E+USAR+PRODUTOS+FALSOS+NO+RIO,00.html",
523 "http://www.cbsnews.com/video/watch/?id=7118769n",
524 "http://videos.sapo.pt/hd4ZBIHG80zFviLc5YEa",
525 "http://www.dailymotion.com/video/xdpig1_city-of-scars_shortfilms",
526 "http://www.spiegel.de/video/video-1012582.html",
527 "http://vimeo.com/1485507",
528 "http://en.sevenload.com/videos/IUL3gda-Funny-Football-Clips",
529 "http://www.liveleak.com/view?i=704_1228511265",
530 "http://video.google.com/videoplay?docid=-6970952080219955808",
531 "http://video.golem.de/internet/2174/firefox-3.5-test.html",
532 "http://www.funnyhub.com/videos/pages/crazy-hole-in-one.html",
533 "http://www.clipfish.de/video/3100131/matratzendomino/",
534 "http://www.youtube.com/watch?v=9dgSa4wmMzk",
535 "http://break.com/index/beach-tackle-whip-lash.html",
536 "http://www.gaskrank.tv/tv/rennstrecken/1-runde-oschersleben-14082008--6985.htm",
537 "http://www.buzzhumor.com/videos/32561/Girl_Feels_Shotgun_Power",
538 "http://www.funnyordie.com/videos/776d200b1c/etiquette-ninjas-episode-5-dicks-on-elevators",
539 "http://www.charlierose.com/view/interview/11125",
540 "http://www.academicearth.org/lectures/building-dynamic-websites-http",
541 /* uses "redirect". */
542 "http://www.academicearth.org/lectures/intro-roman-architecture",
543 "http://www.collegehumor.com/video:1942317",
544 "http://www.theonion.com/video/time-announces-new-version-of-magazine-aimed-at-ad,17950/",
545 "http://www.bloomberg.com/video/63722844/",
547 "http://www.tube8.com/fetish/japanese-melon-gal-censored/186133/",
548 "http://www.xvideos.com/video243887/devi_emmerson_body_painting",
549 "http://www.youjizz.com/videos/glamour-girls---melissa-125602.html",
557 static void dump_error(quvi_t quvi
, QUVIcode rc
, opts_s opts
)
559 if (rc
!= QUVI_NOSUPPORT
)
560 fprintf(stderr
, "\n");
562 fprintf(stderr
, "error: %s\n", quvi_strerror(quvi
, rc
));
564 if (!opts
.test_all_given
) {
566 cmdline_parser_free(&opts
);
571 static void match_test(quvi_t quvi
, opts_s opts
, CURL
* curl
)
576 /* None of our tests (should) use shortened URLs. */
577 quvi_setopt(quvi
, QUVIOPT_NOSHORTENED
, 1L);
579 for (rc
= QUVI_OK
, no_match
= 1, i
= 0; tests
[i
]; ++i
) {
580 if (strstr(tests
[i
], opts
.test_arg
) != NULL
) {
584 rc
= quvi_parse(quvi
, (char *)tests
[i
], &v
);
586 dump_error(quvi
, rc
, opts
);
588 dump_video(v
, opts
, curl
);
589 rc
= check_values(v
, opts
);
591 if (opts
.exec_given
&& rc
== QUVI_OK
) {
592 char *video_url
= NULL
;
594 quvi_getprop(v
, QUVIPROP_VIDEOURL
, &video_url
);
595 invoke_exec(v
, video_url
, opts
);
596 } while (quvi_next_videolink(v
) == QUVI_OK
);
599 quvi_parse_close(&v
);
604 fprintf(stderr
, "error: nothing matched `%s'\n", opts
.test_arg
);
608 cmdline_parser_free(&opts
);
614 static void test_all(quvi_t quvi
, opts_s opts
, CURL
* curl
)
620 /* None of our tests (should) use shortened URLs. */
621 quvi_setopt(quvi
, QUVIOPT_NOSHORTENED
, 1L);
623 spew_qe(":: Run built-in video link tests.\n");
624 for (m
= 0; tests
[m
]; ++m
) ;
626 for (i
= 0; i
< m
; ++i
) {
627 spew_qe(" > Test #%02d/%02d:\n", i
+ 1, m
);
629 rc
= quvi_parse(quvi
, (char *)tests
[i
], &video
);
631 dump_error(quvi
, rc
, opts
);
634 dump_video(video
, opts
, curl
);
636 quvi_parse_close(&video
);
638 spew_qe(":: Tests done.\n");
641 static quvi_t
init_quvi(opts_s opts
, CURL
** curl
)
646 if ((rc
= quvi_init(&quvi
)) != QUVI_OK
)
647 dump_error(quvi
, rc
, opts
);
650 /* Set quvi options. */
652 if (opts
.format_given
)
653 quvi_setopt(quvi
, QUVIOPT_FORMAT
, opts
.format_arg
);
655 quvi_setopt(quvi
, QUVIOPT_NOSHORTENED
, opts
.no_shortened_given
);
656 quvi_setopt(quvi
, QUVIOPT_NOVERIFY
, opts
.no_verify_given
);
658 if (opts
.category_all_given
)
659 quvi_setopt(quvi
, QUVIOPT_CATEGORY
, QUVIPROTO_ALL
);
662 if (opts
.category_http_given
)
664 if (opts
.category_mms_given
)
666 if (opts
.category_rtsp_given
)
668 if (opts
.category_rtmp_given
)
671 quvi_setopt(quvi
, QUVIOPT_CATEGORY
, n
);
674 quvi_setopt(quvi
, QUVIOPT_STATUSFUNCTION
, status_callback
);
675 quvi_setopt(quvi
, QUVIOPT_WRITEFUNCTION
, write_callback
);
677 /* Use the quvi created cURL handle. */
679 quvi_getinfo(quvi
, QUVIINFO_CURL
, curl
);
682 if (opts
.agent_given
)
683 curl_easy_setopt(*curl
, CURLOPT_USERAGENT
, opts
.agent_arg
);
685 if (opts
.proxy_given
)
686 curl_easy_setopt(*curl
, CURLOPT_PROXY
, opts
.proxy_arg
);
688 if (opts
.no_proxy_given
)
689 curl_easy_setopt(*curl
, CURLOPT_PROXY
, "");
691 curl_easy_setopt(*curl
, CURLOPT_VERBOSE
, opts
.verbose_libcurl_given
);
693 curl_easy_setopt(*curl
, CURLOPT_CONNECTTIMEOUT
,
694 opts
.connect_timeout_arg
);
699 int main(int argc
, char *argv
[])
701 const char *url
, *home
, *no_config
, *fname
;
713 no_config
= getenv("QUVI_NO_CONFIG");
715 home
= getenv("QUVI_HOME");
717 home
= getenv("HOME");
727 /* Init cmdline parser. */
729 if (home
&& !no_config
) {
733 asprintf(&path
, "%s%s", home
, fname
);
734 f
= fopen(path
, "r");
737 struct cmdline_parser_params
*pp
;
742 pp
= cmdline_parser_params_create();
743 pp
->check_required
= 0;
745 if (cmdline_parser_config_file(path
, &opts
, pp
) == 0) {
748 pp
->check_required
= 1;
750 if (cmdline_parser_ext(argc
, argv
, &opts
, pp
) == 0)
761 if (no_config_flag
) {
762 if (cmdline_parser(argc
, argv
, &opts
) != 0)
763 return (QUVI_INVARG
);
766 if (opts
.version_given
)
769 if (opts
.license_given
)
772 verbose_flag
= !opts
.quiet_given
;
774 quvi
= init_quvi(opts
, &curl
);
776 if (opts
.support_given
)
781 if (opts
.test_all_given
)
782 test_all(quvi
, opts
, curl
);
784 else if (opts
.test_given
)
785 match_test(quvi
, opts
, curl
);
789 if (opts
.inputs_num
== 0)
790 fprintf(stderr
, "error: no input links\n");
792 for (i
= 0; i
< opts
.inputs_num
; ++i
) {
794 rc
= quvi_parse(quvi
, (char *)opts
.inputs
[i
], &video
);
797 dump_error(quvi
, rc
, opts
);
800 dump_video(video
, opts
, curl
);
802 if (opts
.exec_given
) {
803 char *video_url
= NULL
;
805 quvi_getprop(video
, QUVIPROP_VIDEOURL
, &video_url
);
806 invoke_exec(video
, video_url
, opts
);
807 } while (quvi_next_videolink(video
) == QUVI_OK
);
810 quvi_parse_close(&video
);
820 cmdline_parser_free(&opts
);
825 /* vim: set ts=2 sw=2 tw=72 expandtab: */