nficano commited on
Commit
fda25d0
·
unverified ·
2 Parent(s): dbf5675 0406be1

Merge pull request #253 from johnvanderholt/master

Browse files
pytube/contrib/playlist.py CHANGED
@@ -66,26 +66,65 @@ class Playlist(object):
66
  complete_url = base_url + video_id
67
  self.video_urls.append(complete_url)
68
 
69
- def download_all(self, download_path=None):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
70
  """Download all the videos in the the playlist. Initially, download
71
  resolution is 720p (or highest available), later more option
72
  should be added to download resolution of choice
73
 
74
  TODO(nficano): Add option to download resolution of user's choice
 
 
 
 
 
 
 
 
 
 
 
 
 
 
75
  """
76
 
77
  self.populate_video_urls()
78
  logger.debug('total videos found: ', len(self.video_urls))
79
  logger.debug('starting download')
80
 
 
 
81
  for link in self.video_urls:
82
  yt = YouTube(link)
83
-
84
  # TODO: this should not be hardcoded to a single user's preference
85
  dl_stream = yt.streams.filter(
86
  progressive=True, subtype='mp4',
87
  ).order_by('resolution').desc().first()
88
 
89
  logger.debug('download path: %s', download_path)
90
- dl_stream.download(download_path)
 
 
 
 
 
91
  logger.debug('download complete')
 
66
  complete_url = base_url + video_id
67
  self.video_urls.append(complete_url)
68
 
69
+ def _path_num_prefix_generator(self, reverse=False):
70
+ """
71
+ This generator function generates number prefixes, for the items
72
+ in the playlist.
73
+ If the number of digits required to name a file,is less than is
74
+ required to name the last file,it prepends 0s.
75
+ So if you have a playlist of 100 videos it will number them like:
76
+ 001, 002, 003 ect, up to 100.
77
+ It also adds a space after the number.
78
+ :return: prefix string generator : generator
79
+ """
80
+ digits = len(str(len(self.video_urls)))
81
+ if reverse:
82
+ start, stop, step = (len(self.video_urls), 0, -1)
83
+ else:
84
+ start, stop, step = (1, len(self.video_urls) + 1, 1)
85
+ return (str(i).zfill(digits) for i in range(start, stop, step))
86
+
87
+ def download_all(self, download_path=None, prefix_number=True,
88
+ reverse_numbering=False):
89
  """Download all the videos in the the playlist. Initially, download
90
  resolution is 720p (or highest available), later more option
91
  should be added to download resolution of choice
92
 
93
  TODO(nficano): Add option to download resolution of user's choice
94
+
95
+ :param download_path:
96
+ (optional) Output path for the playlist If one is not
97
+ specified, defaults to the current working directory.
98
+ This is passed along to the Stream objects.
99
+ :type download_path: str or None
100
+ :param prefix_number:
101
+ (optional) Automatically numbers playlists using the
102
+ _path_num_prefix_generator function.
103
+ :type prefix_number: bool
104
+ :param reverse_numbering:
105
+ (optional) Lets you number playlists in reverse, since some
106
+ playlists are ordered newest -> oldests.
107
+ :type reverse_numbering: bool
108
  """
109
 
110
  self.populate_video_urls()
111
  logger.debug('total videos found: ', len(self.video_urls))
112
  logger.debug('starting download')
113
 
114
+ prefix_gen = self._path_num_prefix_generator(reverse_numbering)
115
+
116
  for link in self.video_urls:
117
  yt = YouTube(link)
 
118
  # TODO: this should not be hardcoded to a single user's preference
119
  dl_stream = yt.streams.filter(
120
  progressive=True, subtype='mp4',
121
  ).order_by('resolution').desc().first()
122
 
123
  logger.debug('download path: %s', download_path)
124
+ if prefix_number:
125
+ prefix = next(prefix_gen)
126
+ logger.debug('file prefix is: %s', prefix)
127
+ dl_stream.download(download_path, filename_prefix=prefix)
128
+ else:
129
+ dl_stream.download(download_path)
130
  logger.debug('download complete')
pytube/streams.py CHANGED
@@ -174,7 +174,7 @@ class Stream(object):
174
  filename = safe_filename(title)
175
  return '{filename}.{s.subtype}'.format(filename=filename, s=self)
176
 
177
- def download(self, output_path=None, filename=None):
178
  """Write the media stream to disk.
179
 
180
  :param output_path:
@@ -185,6 +185,13 @@ class Stream(object):
185
  (optional) Output filename (stem only) for writing media file.
186
  If one is not specified, the default filename is used.
187
  :type filename: str or None
 
 
 
 
 
 
 
188
 
189
  :rtype: str
190
 
@@ -195,6 +202,11 @@ class Stream(object):
195
  filename = '{filename}.{s.subtype}'.format(filename=safe, s=self)
196
  filename = filename or self.default_filename
197
 
 
 
 
 
 
198
  # file path
199
  fp = os.path.join(output_path, filename)
200
  bytes_remaining = self.filesize
 
174
  filename = safe_filename(title)
175
  return '{filename}.{s.subtype}'.format(filename=filename, s=self)
176
 
177
+ def download(self, output_path=None, filename=None, filename_prefix=None):
178
  """Write the media stream to disk.
179
 
180
  :param output_path:
 
185
  (optional) Output filename (stem only) for writing media file.
186
  If one is not specified, the default filename is used.
187
  :type filename: str or None
188
+ :param filename_prefix:
189
+ (optional) A string that will be prepended to the filename.
190
+ For example a number in a playlist or the name of a series.
191
+ If one is not specified, nothing will be prepended
192
+ This is seperate from filename so you can use the default
193
+ filename but still add a prefix.
194
+ :type filename_prefix: str or None
195
 
196
  :rtype: str
197
 
 
202
  filename = '{filename}.{s.subtype}'.format(filename=safe, s=self)
203
  filename = filename or self.default_filename
204
 
205
+ if filename_prefix:
206
+ filename = "{prefix}{filename}"\
207
+ .format(prefix=safe_filename(filename_prefix),
208
+ filename=filename)
209
+
210
  # file path
211
  fp = os.path.join(output_path, filename)
212
  bytes_remaining = self.filesize
tests/test_playlist.py CHANGED
@@ -1,12 +1,14 @@
1
  # -*- coding: utf-8 -*-
2
  from pytube import Playlist
3
 
 
 
 
 
 
4
 
5
  def test_construct():
6
- ob = Playlist(
7
- 'https://www.youtube.com/watch?v=m5q2GCsteQs&list='
8
- 'PL525f8ds9RvsXDl44X6Wwh9t3fCzFNApw',
9
- )
10
  expected = 'https://www.youtube.com/' \
11
  'playlist?list=' \
12
  'PL525f8ds9RvsXDl44X6Wwh9t3fCzFNApw'
@@ -14,25 +16,8 @@ def test_construct():
14
  assert ob.construct_playlist_url() == expected
15
 
16
 
17
- def test_link_parse():
18
- ob = Playlist(
19
- 'https://www.youtube.com/watch?v=m5q2GCsteQs&list='
20
- 'PL525f8ds9RvsXDl44X6Wwh9t3fCzFNApw',
21
- )
22
-
23
- expected = [
24
- '/watch?v=m5q2GCsteQs',
25
- '/watch?v=5YK63cXyJ2Q',
26
- '/watch?v=Rzt4rUPFYD4',
27
- ]
28
- assert ob.parse_links() == expected
29
-
30
-
31
  def test_populate():
32
- ob = Playlist(
33
- 'https://www.youtube.com/watch?v=m5q2GCsteQs&list='
34
- 'PL525f8ds9RvsXDl44X6Wwh9t3fCzFNApw',
35
- )
36
  expected = [
37
  'https://www.youtube.com/watch?v=m5q2GCsteQs',
38
  'https://www.youtube.com/watch?v=5YK63cXyJ2Q',
@@ -43,10 +28,50 @@ def test_populate():
43
  assert ob.video_urls == expected
44
 
45
 
 
 
 
 
 
 
 
 
 
 
 
46
  def test_download():
47
- ob = Playlist(
48
- 'https://www.youtube.com/watch?v=lByG_AgKS9k&list='
49
- 'PL525f8ds9RvuerPZ3bZygmNiYw2sP4BDk',
50
- )
51
  ob.download_all()
52
  ob.download_all('.')
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  # -*- coding: utf-8 -*-
2
  from pytube import Playlist
3
 
4
+ short_test_pl = 'https://www.youtube.com/watch?v=' \
5
+ 'm5q2GCsteQs&list=PL525f8ds9RvsXDl44X6Wwh9t3fCzFNApw'
6
+ long_test_pl = "https://www.youtube.com/watch?v=" \
7
+ "9CHDoAsX1yo&list=UUXuqSBlHAE6Xw-yeJA0Tunw"
8
+
9
 
10
  def test_construct():
11
+ ob = Playlist(short_test_pl)
 
 
 
12
  expected = 'https://www.youtube.com/' \
13
  'playlist?list=' \
14
  'PL525f8ds9RvsXDl44X6Wwh9t3fCzFNApw'
 
16
  assert ob.construct_playlist_url() == expected
17
 
18
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
19
  def test_populate():
20
+ ob = Playlist(short_test_pl)
 
 
 
21
  expected = [
22
  'https://www.youtube.com/watch?v=m5q2GCsteQs',
23
  'https://www.youtube.com/watch?v=5YK63cXyJ2Q',
 
28
  assert ob.video_urls == expected
29
 
30
 
31
+ def test_link_parse():
32
+ ob = Playlist(short_test_pl)
33
+
34
+ expected = [
35
+ '/watch?v=m5q2GCsteQs',
36
+ '/watch?v=5YK63cXyJ2Q',
37
+ '/watch?v=Rzt4rUPFYD4',
38
+ ]
39
+ assert ob.parse_links() == expected
40
+
41
+
42
  def test_download():
43
+ ob = Playlist(short_test_pl)
 
 
 
44
  ob.download_all()
45
  ob.download_all('.')
46
+ ob.download_all(prefix_number=False)
47
+ ob.download_all('.', prefix_number=False)
48
+
49
+
50
+ def test_numbering():
51
+ ob = Playlist(short_test_pl)
52
+ ob.populate_video_urls()
53
+ gen = ob._path_num_prefix_generator(reverse=False)
54
+ assert "1" in next(gen)
55
+ assert "2" in next(gen)
56
+
57
+ ob = Playlist(short_test_pl)
58
+ ob.populate_video_urls()
59
+ gen = ob._path_num_prefix_generator(reverse=True)
60
+ assert str(len(ob.video_urls)) in next(gen)
61
+ assert str(len(ob.video_urls) - 1) in next(gen)
62
+
63
+ ob = Playlist(long_test_pl)
64
+ ob.populate_video_urls()
65
+ gen = ob._path_num_prefix_generator(reverse=False)
66
+ nxt = next(gen)
67
+ assert len(nxt) > 1
68
+ assert "1" in nxt
69
+ nxt = next(gen)
70
+ assert len(nxt) > 1
71
+ assert "2" in nxt
72
+
73
+ ob = Playlist(long_test_pl)
74
+ ob.populate_video_urls()
75
+ gen = ob._path_num_prefix_generator(reverse=True)
76
+ assert str(len(ob.video_urls)) in next(gen)
77
+ assert str(len(ob.video_urls) - 1) in next(gen)