Add scripts/Makefile.am
[quvi.git] / src / quvi.c
blobb7702ea91eeb06de405f365437432983bbd9b5d4
1 /* quvi
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
17 * 02110-1301 USA
20 /* quvi.c - query video tool. */
22 #include "config.h"
24 #include <stdio.h>
25 #include <stdlib.h>
26 #include <stdarg.h>
27 #include <string.h>
28 #include <assert.h>
30 #include <curl/curl.h>
32 #include "platform.h"
34 #include "quvi/quvi.h"
35 #include "cmdline.h"
37 /* strepl.c */
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, ...)
45 va_list ap;
46 va_start(ap, fmt);
47 vfprintf(stderr, fmt, ap);
48 va_end(ap);
51 /* respects (q)uiet, prints to std(e)rr. */
52 static void spew_qe(const char *fmt, ...)
54 va_list ap;
55 if (!verbose_flag)
56 return;
57 va_start(ap, fmt);
58 vfprintf(stderr, fmt, ap);
59 va_end(ap);
62 /* glorified printf. */
63 static void spew(const char *fmt, ...)
65 va_list ap;
66 va_start(ap, fmt);
67 vfprintf(stdout, fmt, ap);
68 va_end(ap);
71 typedef struct gengetopt_args_info opts_s;
73 static void handle_shortened_status(quvi_word type)
75 if (type == QUVISTATUSTYPE_DONE)
76 spew_qe("done.\n");
77 else
78 spew_qe(":: Check for shortened URL ...");
81 static void handle_fetch_status(quvi_word type, void *p)
83 switch (type) {
84 default:
85 spew_qe(":: Fetch %s ...", (char *)p);
86 break;
87 case QUVISTATUSTYPE_CONFIG:
88 spew_qe(":: Fetch config ...");
89 break;
90 case QUVISTATUSTYPE_PLAYLIST:
91 spew_qe(":: Fetch playlist ...");
92 break;
93 case QUVISTATUSTYPE_DONE:
94 spew_qe("done.\n");
95 break;
99 static void handle_verify_status(quvi_word type)
101 switch (type) {
102 default:
103 spew_qe(":: Verify video link ...");
104 break;
105 case QUVISTATUSTYPE_DONE:
106 spew_qe("done.\n");
107 break;
111 static int status_callback(long param, void *data)
113 quvi_word status, type;
115 status = quvi_loword(param);
116 type = quvi_hiword(param);
118 switch (status) {
120 case QUVISTATUS_SHORTENED:
121 handle_shortened_status(type);
122 break;
124 case QUVISTATUS_FETCH:
125 handle_fetch_status(type, data);
126 break;
128 case QUVISTATUS_VERIFY:
129 handle_verify_status(type);
130 break;
133 fflush(stderr);
135 return (0);
138 static size_t write_callback(void *p, size_t size, size_t nmemb,
139 void *data)
141 size_t r = quvi_write_callback_default(p, size, nmemb, data);
142 /* Could do something useful here. */
143 #ifdef _0
144 puts(__func__);
145 #endif
146 return r;
149 /* Divided into smaller blocks. Otherwise -pedantic objects. */
151 #define LICENSE_1 \
152 "/* quvi\n" \
153 " * Copyright (C) 2009,2010,2011 Toni Gundogdu <legatvs@gmail.com>\n"
155 #define LICENSE_2 \
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"
161 #define LICENSE_3 \
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"
167 #define LICENSE_4 \
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,
176 LICENSE_4);
177 cmdline_parser_free(&opts);
178 exit(0);
181 #undef LICENSE_4
182 #undef LICENSE_3
183 #undef LICENSE_2
184 #undef LICENSE_1
186 static void version(opts_s opts)
188 printf("quvi version %s\n", quvi_version(QUVI_VERSION_LONG));
189 cmdline_parser_free(&opts);
190 exit(0);
193 static void dump_host(char *domain, char *formats)
195 printf("%s\t%s\n", domain, formats);
196 quvi_free(domain);
197 quvi_free(formats);
200 /* Wraps quvi_supported. */
201 static void supported(quvi_t quvi, opts_s opts)
203 QUVIcode rc;
204 int i;
206 for (i = 0; i < opts.inputs_num; ++i) {
207 rc = quvi_supported(quvi, (char *)opts.inputs[i]);
208 if (rc == QUVI_OK)
209 spew_qe("%s: OK\n", (char *)opts.inputs[i]);
210 else
211 spew_qe("error: %s\n", quvi_strerror(quvi, rc));
214 quvi_close(&quvi);
215 cmdline_parser_free(&opts);
217 exit(rc);
220 /* dumps all supported hosts to stdout. */
221 static void support(quvi_t quvi, opts_s opts)
223 int done = 0;
225 if (opts.inputs_num > 0)
226 supported(quvi, opts);
228 while (!done) {
229 char *domain, *formats;
230 QUVIcode rc;
232 rc = quvi_next_supported_website(quvi, &domain, &formats);
234 switch (rc) {
235 case QUVI_OK:
236 dump_host(domain, formats);
237 break;
238 case QUVI_LAST:
239 done = 1;
240 break;
241 default:
242 spew_e("%s\n", quvi_strerror(quvi, rc));
243 break;
247 quvi_close(&quvi);
248 cmdline_parser_free(&opts);
250 exit(0);
253 static void invoke_exec(quvi_video_t video, const char *video_url,
254 opts_s opts)
256 char *quoted_url, *arg;
257 int rc;
259 asprintf(&quoted_url, "\"%s\"", video_url);
261 arg = strdup(opts.exec_arg);
262 arg = strepl(arg, "%u", quoted_url);
264 free(quoted_url);
265 quoted_url = NULL;
267 rc = system(arg);
269 switch (rc) {
270 case 0:
271 break;
272 case -1:
273 spew_e("error: failed to execute `%s'\n", arg);
274 break;
275 default:
276 spew_e("error: child exited with: %d\n", rc >> 8);
277 break;
280 free(arg);
281 arg = NULL;
284 static void
285 dump_video_link_xml(CURL * curl,
286 int i,
287 char *video_url,
288 double file_length, char *file_ct,
289 char *file_suffix)
291 char *url;
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"
299 " <url>%s</url>\n"
300 " </link>\n",
301 i, file_length, file_ct, file_suffix, url ? url : video_url);
303 if (url) {
304 curl_free(url);
305 url = NULL;
309 static void
310 dump_video_link_old(int i,
311 char *video_url,
312 double file_length, char *file_suffix,
313 char *file_ct)
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);
320 static void
321 dump_video_link_json(int i,
322 char *video_url,
323 double file_length, char *file_suffix,
324 char *file_ct)
326 spew(" {\n"
327 " \"id\": \"%d\",\n"
328 " \"length_bytes\": \"%.0f\",\n"
329 " \"content_type\": \"%s\",\n"
330 " \"file_suffix\": \"%s\",\n"
331 " \"url\": \"%s\"\n"
332 " }%s\n",
333 i, file_length, file_ct, file_suffix, video_url,
334 i > 1 ? "," : "");
337 static void dump_video_links(quvi_video_t video, opts_s opts,
338 CURL * curl)
340 int i = 0;
341 do {
342 char *video_url, *file_suffix, *file_ct;
343 double file_length;
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);
350 ++i;
352 if (opts.xml_given)
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,
358 file_ct);
359 else
360 dump_video_link_json(i, video_url, file_length, file_suffix,
361 file_ct);
363 while (quvi_next_videolink(video) == QUVI_OK);
366 static void
367 dump_video_xml(CURL * curl,
368 char *video_id, char *host, char *format,
369 char *page_title, char *page_link)
371 char *url;
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);
382 if (url) {
383 curl_free(url);
384 url = NULL;
389 static void
390 dump_video_old(char *video_id, char *host, char *format,
391 char *page_title, char *page_link)
393 spew(" > Dump video:\n"
394 "host : %s\n"
395 "url : %s\n"
396 "title : %s\n"
397 "id : %s\n"
398 "format : %s (requested)\n",
399 host, page_link, page_title, video_id, format);
402 static void
403 dump_video_json(char *video_id, char *host, char *format,
404 char *page_title, char *page_link)
406 char *t;
408 t = strdup(page_title);
409 t = strepl(t, "\"", "\\\"");
411 spew("{\n"
412 " \"host\": \"%s\",\n"
413 " \"page_title\": \"%s\",\n"
414 " \"page_url\": \"%s\",\n"
415 " \"id\": \"%s\",\n"
416 " \"format_requested\": \"%s\",\n"
417 " \"link\": [\n", host, t, page_link, video_id, format);
419 free(t);
420 t = NULL;
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);
433 if (opts.xml_given)
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);
437 else
438 dump_video_json(video_id, host, format, page_title, page_link);
440 dump_video_links(video, opts, curl);
442 if (opts.xml_given)
443 spew("</video>\n");
444 else if (opts.old_given) ;
445 else
446 spew(" ]\n}\n");
449 static void
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);
456 static void
457 expect_error_d(const char *what, const double expected,
458 const double got)
460 fprintf(stderr,
461 "error: %s:\n expected: \"%.0f\"\n got: \"%.0f\"\n\n", what,
462 expected, got);
465 static int check_values(quvi_video_t video, opts_s opts)
467 char *title, *expect, *id, *suffix;
468 double length;
469 int rc;
471 rc = 0;
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;
480 if (rc)
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;
491 if (rc)
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;
502 if (rc)
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;
513 if (rc)
514 expect_error_d("file length", expect_d, length);
517 return (rc);
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/",
546 #ifdef ENABLE_NSFW
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",
550 #endif
551 #ifdef ENABLE_BROKEN
552 #endif
554 NULL
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) {
565 quvi_close(&quvi);
566 cmdline_parser_free(&opts);
567 exit(rc);
571 static void match_test(quvi_t quvi, opts_s opts, CURL * curl)
573 int i, no_match;
574 QUVIcode rc;
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) {
581 quvi_video_t v;
583 no_match = 0;
584 rc = quvi_parse(quvi, (char *)tests[i], &v);
585 if (rc != QUVI_OK)
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;
593 do {
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);
603 if (no_match) {
604 fprintf(stderr, "error: nothing matched `%s'\n", opts.test_arg);
605 rc = QUVI_NOSUPPORT;
608 cmdline_parser_free(&opts);
609 quvi_close(&quvi);
611 exit(rc);
614 static void test_all(quvi_t quvi, opts_s opts, CURL * curl)
616 quvi_video_t video;
617 QUVIcode rc;
618 int i, m;
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);
630 if (rc != QUVI_OK)
631 dump_error(quvi, rc, opts);
632 else {
633 if (opts.dump_given)
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)
643 QUVIcode rc;
644 quvi_t quvi;
646 if ((rc = quvi_init(&quvi)) != QUVI_OK)
647 dump_error(quvi, rc, opts);
648 assert(quvi != 0);
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);
660 else {
661 long n = 0;
662 if (opts.category_http_given)
663 n |= QUVIPROTO_HTTP;
664 if (opts.category_mms_given)
665 n |= QUVIPROTO_MMS;
666 if (opts.category_rtsp_given)
667 n |= QUVIPROTO_RTSP;
668 if (opts.category_rtmp_given)
669 n |= QUVIPROTO_RTMP;
670 if (n > 0)
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);
680 assert(*curl != 0);
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);
696 return (quvi);
699 int main(int argc, char *argv[])
701 const char *url, *home, *no_config, *fname;
702 quvi_video_t video;
703 int no_config_flag;
704 opts_s opts;
705 QUVIcode rc;
706 quvi_t quvi;
707 CURL *curl;
708 int i;
710 curl = NULL;
711 url = NULL;
713 no_config = getenv("QUVI_NO_CONFIG");
715 home = getenv("QUVI_HOME");
716 if (!home)
717 home = getenv("HOME");
719 no_config_flag = 1;
721 #ifndef HOST_W32
722 fname = "/.quvirc";
723 #else
724 fname = "\\quvirc";
725 #endif
727 /* Init cmdline parser. */
729 if (home && !no_config) {
730 char *path;
731 FILE *f;
733 asprintf(&path, "%s%s", home, fname);
734 f = fopen(path, "r");
736 if (f != NULL) {
737 struct cmdline_parser_params *pp;
739 fclose(f);
740 f = NULL;
742 pp = cmdline_parser_params_create();
743 pp->check_required = 0;
745 if (cmdline_parser_config_file(path, &opts, pp) == 0) {
746 pp->initialize = 0;
747 pp->override = 1;
748 pp->check_required = 1;
750 if (cmdline_parser_ext(argc, argv, &opts, pp) == 0)
751 no_config_flag = 0;
753 free(pp);
754 pp = NULL;
757 free(path);
758 path = NULL;
761 if (no_config_flag) {
762 if (cmdline_parser(argc, argv, &opts) != 0)
763 return (QUVI_INVARG);
766 if (opts.version_given)
767 version(opts);
769 if (opts.license_given)
770 license(opts);
772 verbose_flag = !opts.quiet_given;
774 quvi = init_quvi(opts, &curl);
776 if (opts.support_given)
777 support(quvi, opts);
779 /* User input */
781 if (opts.test_all_given)
782 test_all(quvi, opts, curl);
784 else if (opts.test_given)
785 match_test(quvi, opts, curl);
787 else {
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);
796 if (rc != QUVI_OK)
797 dump_error(quvi, rc, opts);
799 assert(video != 0);
800 dump_video(video, opts, curl);
802 if (opts.exec_given) {
803 char *video_url = NULL;
804 do {
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);
811 assert(video == 0);
815 /* Cleanup. */
817 quvi_close(&quvi);
818 assert(quvi == 0);
820 cmdline_parser_free(&opts);
822 return (QUVI_OK);
825 /* vim: set ts=2 sw=2 tw=72 expandtab: */