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