1 /* cclive
2 * Copyright (C) 2010-2013 Toni Gundogdu <legatvs@gmail.com>
3 *
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 3 of the License, or
7 * (at your option) any later version.
8 *
9 * This program 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
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <http://www.gnu.org/licenses/>.
16 */
17
18 #include <ccinternal>
19
20 #include <iomanip>
21 #include <vector>
22 #include <ctime>
23
24 #include <boost/algorithm/string/classification.hpp> // is_any_of
25 #include <boost/algorithm/string/split.hpp>
26 #include <boost/foreach.hpp>
27 #include <boost/random.hpp>
28
29 #ifndef foreach
30 #define foreach BOOST_FOREACH
31 #endif
32
33 #include <ccquvi>
34 #include <ccapplication>
35 #include <ccoptions>
36 #include <ccutil>
37 #include <cclog>
38 #include <ccre>
39
40 namespace cc
41 {
42
43 static boost::mt19937 _rng;
44
rand_decor()45 static void rand_decor()
46 {
47 boost::uniform_int<> r(2,5);
48 boost::variate_generator<boost::mt19937&, boost::uniform_int<> > v(_rng,r);
49
50 const int n = v();
51 for (int i=0; i<n; ++i) cc::log << ".";
52 }
53
handle_fetch(const quvi_word type,void *)54 static void handle_fetch(const quvi_word type, void*)
55 {
56 rand_decor();
57 if (type == QUVISTATUSTYPE_DONE)
58 cc::log << " ";
59 }
60
print_done()61 static void print_done()
62 {
63 cc::log << "done.\n";
64 }
65
handle_verify(const quvi_word type)66 static void handle_verify(const quvi_word type)
67 {
68 rand_decor();
69 if (type == QUVISTATUSTYPE_DONE)
70 print_done();
71 }
72
handle_resolve(const quvi_word type)73 static void handle_resolve(const quvi_word type)
74 {
75 rand_decor();
76 if (type == QUVISTATUSTYPE_DONE)
77 cc::log << " ";
78 }
79
status_callback(long param,void * ptr)80 static int status_callback(long param, void *ptr)
81 {
82 const quvi_word status = quvi_loword(param);
83 const quvi_word type = quvi_hiword(param);
84
85 switch (status)
86 {
87 case QUVISTATUS_FETCH :
88 handle_fetch(type,ptr);
89 break;
90 case QUVISTATUS_VERIFY:
91 handle_verify(type);
92 break;
93 case QUVISTATUS_RESOLVE:
94 handle_resolve(type);
95 break;
96 }
97
98 cc::log << std::flush;
99
100 return QUVI_OK;
101 }
102
103 template<class Iterator>
make_unique(Iterator first,Iterator last)104 static Iterator make_unique(Iterator first, Iterator last)
105 {
106 while (first != last)
107 {
108 Iterator next(first);
109 last = std::remove(++next, last, *first);
110 first = next;
111 }
112 return last;
113 }
114
print_retrying(const int retry,const int max_retries,const int retry_wait)115 static void print_retrying(const int retry,
116 const int max_retries,
117 const int retry_wait)
118 {
119 if (retry > 0)
120 {
121 cc::log
122 << "Retrying "
123 << retry
124 << " of "
125 << max_retries
126 << " ... "
127 << std::flush;
128
129 cc::wait(retry_wait);
130 }
131 }
132
print_checking(const int i,const int n)133 static void print_checking(const int i, const int n)
134 {
135 if (n > 1) cc::log << "(" << i << " of " << n << ") ";
136 cc::log << "Checking ... " << std::flush;
137 }
138
print_quvi_error(const quvi::error & e)139 static void print_quvi_error(const quvi::error& e)
140 {
141 cc::log << "libquvi: error: " << e.what() << std::endl;
142 }
143
144 static const char depr_msg[] =
145 "[WARNING] '--format {help,list}' are deprecated and will be removed "
146 "in the later\n[WARNING] versions. Use '--print-streams' instead.";
147
148 static const char format_usage[] =
149 "Usage:\n"
150 " --format arg get format arg of media\n"
151 " --format list print domains with formats\n"
152 " --format list arg match arg to supported domain names\n"
153 "Examples:\n"
154 " --format list youtube print youtube formats\n"
155 " --format fmt34_360p get format fmt34_360p of media";
156
print_format_help()157 static application::exit_status print_format_help()
158 {
159 std::cout << format_usage << "\n" << depr_msg << std::endl;
160 return application::ok;
161 }
162
163 typedef std::map<std::string,std::string> map_ss;
164
print_host(const map_ss::value_type & t)165 static void print_host(const map_ss::value_type& t)
166 {
167 std::cout
168 << t.first
169 << ":\n "
170 << t.second
171 << "\n"
172 << std::endl;
173 }
174
175 namespace po = boost::program_options;
176
177 typedef std::vector<std::string> vst;
178
179 static application::exit_status
handle_format_list(const po::variables_map & map)180 handle_format_list(const po::variables_map& map)
181 {
182 quvi::query q; // Throws quvi::error caught in main.cpp
183 map_ss m = q.support();
184
185 // -f list <pattern>
186
187 if (map.count("url"))
188 {
189 const std::string arg0 = map["url"].as<vst>()[0];
190 foreach (map_ss::value_type& t, m)
191 {
192 if (t.first.find(arg0) != std::string::npos)
193 print_host(t);
194 }
195 }
196
197 // -f list
198
199 else
200 {
201 foreach (map_ss::value_type& t, m)
202 {
203 print_host(t);
204 }
205 }
206
207 std::cout << depr_msg << std::endl;
208
209 return application::ok;
210 }
211
212 static application::exit_status
print_streams(const quvi::query & query,const quvi::options & qopts,const vst & input)213 print_streams(const quvi::query& query, const quvi::options &qopts,
214 const vst& input)
215 {
216 const size_t n = input.size();
217 size_t i = 0;
218
219 foreach (std::string url, input)
220 {
221 try
222 {
223 print_checking(++i,n);
224
225 const std::string r = query.streams(url, qopts);
226 print_done();
227
228 if (cc::opts.flags.print_streams)
229 {
230 vst a;
231 boost::split(a, r, boost::is_any_of("|,"));
232 foreach (const std::string s, a)
233 {
234 cc::log << s << "\n";
235 }
236 }
237 else
238 cc::log << std::setw(10) << r << " : " << url << std::endl;
239 }
240 catch(const quvi::error& e)
241 {
242 print_quvi_error(e);
243 return application::error;
244 }
245 }
246 return application::ok;
247 }
248
read_from(std::istream & is,vst & dst)249 static void read_from(std::istream& is, vst& dst)
250 {
251 std::string s;
252 char ch = 0;
253
254 while (is.get(ch))
255 s += ch;
256
257 std::istringstream iss(s);
258 std::copy(
259 std::istream_iterator<std::string >(iss),
260 std::istream_iterator<std::string >(),
261 std::back_inserter<vst>(dst)
262 );
263 }
264
is_url(const std::string & s)265 static bool is_url(const std::string& s)
266 {
267 return strstr(const_cast<char*>(s.c_str()), "://") != NULL;
268 }
269
parse_prefer_format(const std::string & url,std::string & fmt,const po::variables_map & map)270 static void parse_prefer_format(const std::string& url, std::string& fmt,
271 const po::variables_map& map)
272 {
273 vst vb, va = map["prefer-format"].as<vst>();
274 foreach (const std::string s, va)
275 {
276 boost::split(vb, s, boost::is_any_of(":"));
277 if (vb.size() == 2)
278 {
279 // vb[0] = pattern
280 // vb[1] = format
281 if (cc::re::grep(vb[0], url))
282 {
283 fmt = vb[1];
284 return;
285 }
286 }
287 vb.clear();
288 }
289 }
290
set_stream(const std::string & url,quvi::options & qopts,const po::variables_map & map)291 static void set_stream(const std::string& url, quvi::options& qopts,
292 const po::variables_map& map)
293 {
294 std::string s = "default";
295 if (map.count("stream"))
296 s = map["stream"].as<std::string>();
297 else if (map.count("format")) // --format takes precedence
298 s = map["format"].as<std::string>();
299 else
300 {
301 if (map.count("prefer-format"))
302 parse_prefer_format(url, s, map);
303 }
304 qopts.stream = s;
305 }
306
print_version()307 static const application::exit_status print_version()
308 {
309 std::cout
310 << "cclive "
311 #ifdef VN
312 << VN
313 #else
314 << PACKAGE_VERSION
315 #endif
316 << " for " << CANONICAL_TARGET
317 << "\n libquvi "
318 << quvi_version(QUVI_VERSION_LONG)
319 #ifdef HAVE_LIBQUVI_0_4_0
320 << "\n libquvi-scripts "
321 << quvi_version(QUVI_VERSION_SCRIPTS)
322 #endif
323 << std::endl;
324 return application::ok;
325 }
326
print_support()327 static application::exit_status print_support()
328 {
329 quvi::query q; // Throws quvi::error caught in main.cpp
330 std::cout << quvi::support_to_s(q.support()) << std::flush;
331 return application::ok;
332 }
333
warn_depr_val(const std::string & o,const std::string & t,const std::string & v)334 static void warn_depr_val(const std::string& o, const std::string& t,
335 const std::string& v)
336 {
337 std::clog
338 << "[WARNING] --" << o << ": " << t << " `" << v
339 << "' is deprecated and will\n[WARNING] be removed in the later "
340 << "versions" << std::endl;
341 }
342
343 extern char LICENSE[]; // cc/license.cpp
344
exec(int argc,char ** argv)345 application::exit_status application::exec(int argc, char **argv)
346 {
347 opts.parse(argc, argv);
348
349 const po::variables_map map = cc::opts.map();
350
351 // Dump and terminate options.
352
353 if (opts.flags.help)
354 {
355 std::cout << opts << std::flush;
356 return application::ok;
357 }
358 else if (opts.flags.print_config)
359 {
360 opts.dump();
361 return application::ok;
362 }
363 else if (opts.flags.version)
364 return print_version();
365 else if (opts.flags.support)
366 return print_support();
367 else if (opts.flags.license)
368 {
369 std::cout << LICENSE << std::endl;
370 return application::ok;
371 }
372
373 // --format [<id> | [<help> | <list> [<pattern]]]
374
375 if (map.count("format"))
376 {
377 const std::string format = map["format"].as<std::string>();
378
379 if (format == "help")
380 return print_format_help();
381
382 else if (format == "list")
383 return handle_format_list(map);
384 }
385
386 // Deprecated.
387
388 if (strstr(map["filename-format"].as<std::string>().c_str(), "%h"))
389 warn_depr_val("filename-format", "sequence", "%h");
390
391 // Parse input.
392
393 vst input;
394
395 if (map.count("url") == 0)
396 read_from(std::cin, input);
397 else
398 {
399 vst args = map["url"].as< vst >();
400 foreach(std::string arg, args)
401 {
402 if (!is_url(arg))
403 {
404 std::ifstream f(arg.c_str());
405 if (f.is_open())
406 read_from(f, input);
407 else
408 {
409 std::clog
410 << "error: "
411 << arg
412 << ": "
413 << cc::perror("unable to open")
414 << std::endl;
415 }
416 }
417 else
418 input.push_back(arg);
419 }
420 }
421
422 if (input.size() == 0)
423 {
424 std::clog << "error: no input urls" << std::endl;
425 return application::error;
426 }
427
428 // Remove duplicates.
429
430 input.erase(make_unique(input.begin(), input.end()), input.end());
431
432 // Set up quvi.
433
434 quvi::query query; // Throws quvi::error caught in main.cpp
435
436 quvi::options qopts;
437 qopts.resolve = ! opts.flags.no_resolve;
438 qopts.statusfunc = status_callback;
439
440 // Seed random generator.
441
442 _rng.seed(static_cast<unsigned int>(std::time(0)));
443
444 // Omit flag.
445
446 bool omit = opts.flags.quiet;
447
448 // Go to background.
449
450 #ifdef HAVE_FORK
451 const bool background_given = opts.flags.background;
452
453 if (background_given)
454 {
455
456 // (Boost) Throws std::runtime_error if fails.
457
458 cc::go_background(map["log-file"].as<std::string>(), omit);
459 }
460 #endif
461
462 // Omit std output. Note that --background flips this above.
463
464 cc::log.push(cc::omit_sink(omit));
465 cc::log.setf(std::ios::fixed);
466
467 // Print streams.
468
469 if (opts.flags.print_streams || opts.flags.query_formats)
470 return print_streams(query, qopts, input);
471
472 #if defined (HAVE_FORK) && defined (HAVE_GETPID)
473 if (background_given)
474 {
475 cc::log
476 << "Running in background (pid: "
477 << static_cast<long>(getpid())
478 << ")."
479 << std::endl;
480 }
481 #endif
482
483 // For each input URL.
484
485 const size_t n = input.size();
486 size_t i = 0;
487
488 const int max_retries = map["max-retries"].as<int>();
489 const int retry_wait = map["retry-wait"].as<int>();
490
491 exit_status es = ok;
492
493 foreach(std::string url, input)
494 {
495 ++i;
496
497 try
498 {
499 int retry = 0;
500
501 while (retry <= max_retries)
502 {
503 print_retrying(retry, max_retries, retry_wait);
504 ++retry;
505
506 print_checking(i, n);
507 quvi::media m;
508
509 try
510 {
511 set_stream(url, qopts, map);
512 _curl = query.setup_curl();
513 m = query.parse(url, qopts);
514 }
515 catch(const quvi::error& e)
516 {
517 if (e.cannot_retry())
518 throw e;
519 else
520 print_quvi_error(e);
521 }
522
523 cc::get(m, _curl);
524 break; // Stop retrying.
525 }
526 es = ok;
527 }
528
529 catch(const quvi::error& e)
530 {
531 print_quvi_error(e);
532 es = application::error;
533 }
534
535 catch(const std::runtime_error& e)
536 {
537 cc::log << "error: " << e.what() << std::endl;
538 es = application::error;
539 }
540 }
541 return es;
542 }
543
544 } // namespace cc
545
546 // vim: set ts=2 sw=2 tw=72 expandtab:
547