nficano commited on
Commit
83fae84
·
1 Parent(s): 8739d28

fixed cli logging

Browse files
pytube/__main__.py CHANGED
@@ -29,7 +29,7 @@ class YouTube(object):
29
  """Core developer interface for pytube."""
30
 
31
  def __init__(
32
- self, url=None, defer_init=False, on_progress_callback=None,
33
  on_complete_callback=None,
34
  ):
35
  """Construct a :class:`YouTube <YouTube>`.
@@ -74,11 +74,16 @@ class YouTube(object):
74
  'on_complete': on_complete_callback,
75
  }
76
 
77
- if url and not defer_init:
78
- self.init()
 
 
 
 
 
79
 
80
  def init(self):
81
- """Download data, descramble it, and build Stream instances.
82
 
83
  The initialization process takes advantage of Python's
84
  "call-by-reference evaluation," which allows dictionary transforms to
@@ -86,7 +91,6 @@ class YouTube(object):
86
  interstitial step.
87
  """
88
  logger.info('init started')
89
- self.prefetch()
90
 
91
  self.vid_info = {k: v for k, v in parse_qsl(self.vid_info)}
92
  self.player_config = extract.get_ytplayer_config(self.watch_html)
 
29
  """Core developer interface for pytube."""
30
 
31
  def __init__(
32
+ self, url=None, defer_prefetch_init=False, on_progress_callback=None,
33
  on_complete_callback=None,
34
  ):
35
  """Construct a :class:`YouTube <YouTube>`.
 
74
  'on_complete': on_complete_callback,
75
  }
76
 
77
+ if url and not defer_prefetch_init:
78
+ self.prefetch_init()
79
+
80
+ def prefetch_init(self):
81
+ """Download data, descramble it, and build Stream instances."""
82
+ self.prefetch()
83
+ self.init()
84
 
85
  def init(self):
86
+ """descramble the stream data and build Stream instances.
87
 
88
  The initialization process takes advantage of Python's
89
  "call-by-reference evaluation," which allows dictionary transforms to
 
91
  interstitial step.
92
  """
93
  logger.info('init started')
 
94
 
95
  self.vid_info = {k: v for k, v in parse_qsl(self.vid_info)}
96
  self.player_config = extract.get_ytplayer_config(self.watch_html)
pytube/cli.py CHANGED
@@ -4,12 +4,15 @@ from __future__ import absolute_import
4
  from __future__ import print_function
5
 
6
  import argparse
 
 
7
  import json
8
  import logging
9
  import os
10
  import sys
11
 
12
  from pytube import __version__
 
13
  from pytube import YouTube
14
 
15
 
@@ -21,7 +24,7 @@ def main():
21
  parser = argparse.ArgumentParser(description=main.__doc__)
22
  parser.add_argument('url', help='The YouTube /watch url', nargs='?')
23
  parser.add_argument(
24
- '-v', '--version', action='version',
25
  version='%(prog)s ' + __version__,
26
  )
27
  parser.add_argument(
@@ -36,45 +39,59 @@ def main():
36
  ),
37
  )
38
  parser.add_argument(
39
- '-v', '--verbose', action='count', default=0, dest='verbose_count',
40
  help='Verbosity level',
41
  )
42
  parser.add_argument(
43
- '--build-debug-report', action='store_true', help=(
44
  'Save the html and js to disk'
45
  ),
46
  )
47
 
48
  args = parser.parse_args()
49
- logger.setLevel(max(3 - args.verbose_count, 0) * 10)
 
50
  if not args.url:
51
  parser.print_help()
52
  sys.exit(1)
 
53
  if args.list:
54
  display_streams(args.url)
55
- elif args.build_debug_report:
56
- build_debug_report(args.url)
 
 
57
  elif args.itag:
58
  download(args.url, args.itag)
59
 
60
 
61
- def build_debug_report(url):
62
  """Serialize the request data to json for offline debugging.
63
 
64
  :param str url:
65
  A valid YouTube watch URL.
66
  """
67
  yt = YouTube(url)
 
68
  fp = os.path.join(
69
  os.getcwd(),
70
- 'yt-video-{yt.video_id}.json'.format(yt=yt),
71
  )
72
- with open(fp, 'w') as fh:
73
- fh.write(json.dumps({
74
- 'js': yt.js,
75
- 'watch_html': yt.watch_html,
76
- 'video_info': yt.vid_info,
77
- }))
 
 
 
 
 
 
 
 
 
78
 
79
 
80
  def get_terminal_size():
 
4
  from __future__ import print_function
5
 
6
  import argparse
7
+ import datetime as dt
8
+ import gzip
9
  import json
10
  import logging
11
  import os
12
  import sys
13
 
14
  from pytube import __version__
15
+ from pytube import request
16
  from pytube import YouTube
17
 
18
 
 
24
  parser = argparse.ArgumentParser(description=main.__doc__)
25
  parser.add_argument('url', help='The YouTube /watch url', nargs='?')
26
  parser.add_argument(
27
+ '--version', action='version',
28
  version='%(prog)s ' + __version__,
29
  )
30
  parser.add_argument(
 
39
  ),
40
  )
41
  parser.add_argument(
42
+ '-v', '--verbose', action='count', default=0, dest='verbosity',
43
  help='Verbosity level',
44
  )
45
  parser.add_argument(
46
+ '--build-playback-report', action='store_true', help=(
47
  'Save the html and js to disk'
48
  ),
49
  )
50
 
51
  args = parser.parse_args()
52
+ logging.getLogger().setLevel(max(3 - args.verbosity, 0) * 10)
53
+
54
  if not args.url:
55
  parser.print_help()
56
  sys.exit(1)
57
+
58
  if args.list:
59
  display_streams(args.url)
60
+
61
+ elif args.build_playback_report:
62
+ build_playback_report(args.url)
63
+
64
  elif args.itag:
65
  download(args.url, args.itag)
66
 
67
 
68
+ def build_playback_report(url):
69
  """Serialize the request data to json for offline debugging.
70
 
71
  :param str url:
72
  A valid YouTube watch URL.
73
  """
74
  yt = YouTube(url)
75
+ ts = int(dt.datetime.utcnow().timestamp())
76
  fp = os.path.join(
77
  os.getcwd(),
78
+ 'yt-video-{yt.video_id}-{ts}.json.tar.gz'.format(yt=yt, ts=ts),
79
  )
80
+ js, watch_html, vid_info = request.get(urls=[
81
+ yt.js_url,
82
+ yt.watch_url,
83
+ yt.vid_info_url,
84
+ ])
85
+ with gzip.open(fp, 'wb') as fh:
86
+ fh.write(
87
+ json.dumps({
88
+ 'url': url,
89
+ 'js': js,
90
+ 'watch_html': watch_html,
91
+ 'video_info': vid_info,
92
+ })
93
+ .encode('utf8'),
94
+ )
95
 
96
 
97
  def get_terminal_size():
pytube/helpers.py CHANGED
@@ -29,7 +29,7 @@ def regex_search(pattern, string, groups=False, group=None, flags=0):
29
  results = regex.search(string)
30
  logger.debug(
31
  'finished regex search: %s',
32
- pprint.pprint(
33
  {
34
  'pattern': pattern,
35
  'results': results.group(0),
 
29
  results = regex.search(string)
30
  logger.debug(
31
  'finished regex search: %s',
32
+ pprint.pformat(
33
  {
34
  'pattern': pattern,
35
  'results': results.group(0),
pytube/logging.py CHANGED
@@ -5,7 +5,7 @@ from __future__ import absolute_import
5
  import logging
6
 
7
 
8
- def create_logger(level=logging.DEBUG):
9
  """Create a configured instance of logger.
10
 
11
  :param int level:
 
5
  import logging
6
 
7
 
8
+ def create_logger(level=logging.ERROR):
9
  """Create a configured instance of logger.
10
 
11
  :param int level:
pytube/mixins.py CHANGED
@@ -40,7 +40,7 @@ def apply_signature(config_args, fmt, js):
40
 
41
  logger.debug(
42
  'finished descrambling signature for itag=%s\n%s',
43
- stream['itag'], pprint.pprint(
44
  {
45
  's': stream['s'],
46
  'signature': signature,
@@ -63,4 +63,4 @@ def apply_descrambler(stream_data, key):
63
  {k: unquote(v) for k, v in parse_qsl(i)}
64
  for i in stream_data[key].split(',')
65
  ]
66
- logger.debug('applying descrambler\n%s', pprint.pprint(stream_data[key]))
 
40
 
41
  logger.debug(
42
  'finished descrambling signature for itag=%s\n%s',
43
+ stream['itag'], pprint.pformat(
44
  {
45
  's': stream['s'],
46
  'signature': signature,
 
63
  {k: unquote(v) for k, v in parse_qsl(i)}
64
  for i in stream_data[key].split(',')
65
  ]
66
+ logger.debug('applying descrambler\n%s', pprint.pformat(stream_data[key]))
pytube/query.py CHANGED
@@ -15,6 +15,8 @@ class StreamQuery:
15
  self, fps=None, res=None, resolution=None, mime_type=None,
16
  type=None, subtype=None, file_extension=None, abr=None,
17
  bitrate=None, video_codec=None, audio_codec=None,
 
 
18
  custom_filter_functions=None,
19
  ):
20
  """Apply the given filtering criterion.
@@ -43,6 +45,16 @@ class StreamQuery:
43
  (optional) Digital video compression format (e.g.: vp9, mp4v.20.3).
44
  :param str audio_codec:
45
  (optional) Digital audio compression format (e.g.: vorbis, mp4).
 
 
 
 
 
 
 
 
 
 
46
  :param list custom_filter_functions:
47
  (optional) Interface for defining complex filters without
48
  subclassing.
@@ -73,6 +85,26 @@ class StreamQuery:
73
  if audio_codec:
74
  filters.append(lambda s: s.audio_codec == audio_codec)
75
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
76
  if custom_filter_functions:
77
  for fn in custom_filter_functions:
78
  filters.append(fn)
 
15
  self, fps=None, res=None, resolution=None, mime_type=None,
16
  type=None, subtype=None, file_extension=None, abr=None,
17
  bitrate=None, video_codec=None, audio_codec=None,
18
+ only_audio=None, only_video=None,
19
+ progressive=None, adaptive=None,
20
  custom_filter_functions=None,
21
  ):
22
  """Apply the given filtering criterion.
 
45
  (optional) Digital video compression format (e.g.: vp9, mp4v.20.3).
46
  :param str audio_codec:
47
  (optional) Digital audio compression format (e.g.: vorbis, mp4).
48
+ :param bool progressive:
49
+ Excludes adaptive streams (one file contains both audio and video
50
+ tracks).
51
+ :param bool adaptive:
52
+ Excludes progressive streams (audio and video are on separate
53
+ tracks).
54
+ :param bool only_audio:
55
+ Excludes streams with video tracks.
56
+ :param bool only_video:
57
+ Excludes streams with audio tracks.
58
  :param list custom_filter_functions:
59
  (optional) Interface for defining complex filters without
60
  subclassing.
 
85
  if audio_codec:
86
  filters.append(lambda s: s.audio_codec == audio_codec)
87
 
88
+ if only_audio:
89
+ filters.append(
90
+ lambda s: (
91
+ s.includes_audio_track and not s.includes_video_track
92
+ ),
93
+ )
94
+
95
+ if only_video:
96
+ filters.append(
97
+ lambda s: (
98
+ s.includes_video_track and not s.includes_audio_track
99
+ ),
100
+ )
101
+
102
+ if progressive:
103
+ filters.append(lambda s: s.is_progressive)
104
+
105
+ if adaptive:
106
+ filters.append(lambda s: s.is_progressive)
107
+
108
  if custom_filter_functions:
109
  for fn in custom_filter_functions:
110
  filters.append(fn)
pytube/streams.py CHANGED
@@ -95,13 +95,22 @@ class Stream(object):
95
  return len(self.codecs) % 2
96
 
97
  @property
98
- def is_audio(self):
99
- """Whether the stream only contains audio (adaptive only)."""
 
 
 
 
 
 
 
100
  return self.type == 'audio'
101
 
102
  @property
103
- def is_video(self):
104
- """Whether the stream only contains video (adaptive only)."""
 
 
105
  return self.type == 'video'
106
 
107
  def parse_codecs(self):
@@ -117,9 +126,9 @@ class Stream(object):
117
  audio = None
118
  if not self.is_adaptive:
119
  video, audio = self.codecs
120
- elif self.is_video:
121
  video = self.codecs[0]
122
- elif self.is_audio:
123
  audio = self.codecs[0]
124
  return video, audio
125
 
@@ -182,7 +191,7 @@ class Stream(object):
182
  file_handler.write(chunk)
183
  logger.debug(
184
  'download progress\n%s',
185
- pprint.pprint(
186
  {
187
  'chunk_size': len(chunk),
188
  'bytes_remaining': bytes_remaining,
@@ -211,7 +220,7 @@ class Stream(object):
211
  """Printable object representation."""
212
  # TODO(nficano): this can probably be written better.
213
  parts = ['itag="{s.itag}"', 'mime_type="{s.mime_type}"']
214
- if self.is_video:
215
  parts.extend(['res="{s.resolution}"', 'fps="{s.fps}fps"'])
216
  if not self.is_adaptive:
217
  parts.extend([
 
95
  return len(self.codecs) % 2
96
 
97
  @property
98
+ def is_progressive(self):
99
+ """Whether the stream is progressive."""
100
+ return not self.is_adaptive
101
+
102
+ @property
103
+ def includes_audio_track(self):
104
+ """Whether the stream only contains audio."""
105
+ if self.is_progressive:
106
+ return True
107
  return self.type == 'audio'
108
 
109
  @property
110
+ def includes_video_track(self):
111
+ """Whether the stream only contains video."""
112
+ if self.is_progressive:
113
+ return True
114
  return self.type == 'video'
115
 
116
  def parse_codecs(self):
 
126
  audio = None
127
  if not self.is_adaptive:
128
  video, audio = self.codecs
129
+ elif self.includes_video_track:
130
  video = self.codecs[0]
131
+ elif self.includes_audio_track:
132
  audio = self.codecs[0]
133
  return video, audio
134
 
 
191
  file_handler.write(chunk)
192
  logger.debug(
193
  'download progress\n%s',
194
+ pprint.pformat(
195
  {
196
  'chunk_size': len(chunk),
197
  'bytes_remaining': bytes_remaining,
 
220
  """Printable object representation."""
221
  # TODO(nficano): this can probably be written better.
222
  parts = ['itag="{s.itag}"', 'mime_type="{s.mime_type}"']
223
+ if self.includes_video_track:
224
  parts.extend(['res="{s.resolution}"', 'fps="{s.fps}fps"'])
225
  if not self.is_adaptive:
226
  parts.extend([
tests/conftest.py CHANGED
@@ -18,7 +18,7 @@ def gangnam_style():
18
  video = json.loads(fh.read())
19
  yt = YouTube(
20
  url='https://www.youtube.com/watch?v=9bZkp7q19f0',
21
- defer_init=True,
22
  )
23
  yt.watch_html = video['watch_html']
24
  yt.js = video['js']
 
18
  video = json.loads(fh.read())
19
  yt = YouTube(
20
  url='https://www.youtube.com/watch?v=9bZkp7q19f0',
21
+ defer_prefetch_init=True,
22
  )
23
  yt.watch_html = video['watch_html']
24
  yt.js = video['js']
tests/mocks/yt-video-9bZkp7q19f0.json CHANGED
The diff for this file is too large to render. See raw diff