ahmedsadman commited on
Commit
9ad32bb
·
unverified ·
2 Parent(s): 5d0eccb aac6e8d

Merge pull request #2 from ahmedsadman/master

Browse files
.envrc ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ layout_pipenv() {
2
+ if [[ ! -f Pipfile ]]; then
3
+ echo 'No Pipfile found. Use `pipenv` to create a Pipfile first.' >&2
4
+ exit 2
5
+ fi
6
+
7
+ local VENV=$(pipenv --bare --venv 2>/dev/null)
8
+ if [[ -z $VENV || ! -d $VENV ]]; then
9
+ pipenv install --dev
10
+ fi
11
+
12
+ export VIRTUAL_ENV=$(pipenv --venv)
13
+ export PIPENV_ACTIVE=1
14
+ PATH_add "$VIRTUAL_ENV/bin"
15
+ }
16
+ layout pipenv
.travis.yml CHANGED
@@ -4,18 +4,20 @@ cache:
4
  - pip
5
  python:
6
  - "2.7"
7
- - "3.3"
8
  - "3.4"
9
  - "3.5"
10
  - "3.6"
11
  - "3.6-dev"
12
- - "3.7-dev"
13
- - "nightly"
14
  install:
15
- - pip install -r requirements.txt
16
- - pip install -r tests/requirements.txt
17
  before_script: flake8
18
- script: pytest --cov-report term-missing --cov=pytube
 
 
 
 
 
19
  sudo: required
20
  after_success:
21
  coveralls
 
4
  - pip
5
  python:
6
  - "2.7"
 
7
  - "3.4"
8
  - "3.5"
9
  - "3.6"
10
  - "3.6-dev"
 
 
11
  install:
12
+ - pip install --upgrade pipenv
13
+ - pipenv install --dev --ignore-pipfile
14
  before_script: flake8
15
+ before_install:
16
+ - sudo apt-get install -y python-pip
17
+ - sudo apt-get install -y libpython-dev
18
+ - sudo apt-get install -y libyaml-dev
19
+ - sudo apt-get install python-dev
20
+ script: pipenv run pytest --cov-report term-missing --cov=pytube
21
  sudo: required
22
  after_success:
23
  coveralls
Makefile CHANGED
@@ -12,6 +12,7 @@ clean-build:
12
  rm -fr .eggs/
13
  find . -name '*.egg-info' -exec rm -fr {} +
14
  find . -name '*.egg' -exec rm -f {} +
 
15
 
16
  clean-pyc:
17
  find . -name '*.pyc' -exec rm -f {} +
 
12
  rm -fr .eggs/
13
  find . -name '*.egg-info' -exec rm -fr {} +
14
  find . -name '*.egg' -exec rm -f {} +
15
+ find . -name '*.DS_Store' -exec rm -f {} +
16
 
17
  clean-pyc:
18
  find . -name '*.pyc' -exec rm -f {} +
Pipfile ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [[source]]
2
+
3
+ url = "https://pypi.python.org/simple"
4
+ verify_ssl = true
5
+ name = "pypi"
6
+
7
+
8
+ [packages]
9
+
10
+
11
+
12
+ [dev-packages]
13
+
14
+ bumpversion = "*"
15
+ coveralls = "*"
16
+ "flake8" = "*"
17
+ mock = "*"
18
+ pre-commit = "*"
19
+ pytest = "*"
20
+ pytest-cov = "*"
21
+ pytest-mock = "*"
22
+ sphinx = "*"
23
+ sphinx-rtd-theme = "*"
24
+
25
+
26
+ [requires]
27
+
28
+ python_version = "2.7"
Pipfile.lock ADDED
@@ -0,0 +1,371 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "_meta": {
3
+ "hash": {
4
+ "sha256": "cfd5870db2e1ead08c85c866c3b50a92b4a08be4df3737e2f090f061389b8812"
5
+ },
6
+ "host-environment-markers": {
7
+ "implementation_name": "cpython",
8
+ "implementation_version": "0",
9
+ "os_name": "posix",
10
+ "platform_machine": "x86_64",
11
+ "platform_python_implementation": "CPython",
12
+ "platform_release": "17.4.0",
13
+ "platform_system": "Darwin",
14
+ "platform_version": "Darwin Kernel Version 17.4.0: Sun Dec 17 09:19:54 PST 2017; root:xnu-4570.41.2~1/RELEASE_X86_64",
15
+ "python_full_version": "2.7.14",
16
+ "python_version": "2.7",
17
+ "sys_platform": "darwin"
18
+ },
19
+ "pipfile-spec": 6,
20
+ "requires": {
21
+ "python_version": "2.7"
22
+ },
23
+ "sources": [
24
+ {
25
+ "name": "pypi",
26
+ "url": "https://pypi.python.org/simple",
27
+ "verify_ssl": true
28
+ }
29
+ ]
30
+ },
31
+ "default": {},
32
+ "develop": {
33
+ "alabaster": {
34
+ "hashes": [
35
+ "sha256:2eef172f44e8d301d25aff8068fddd65f767a3f04b5f15b0f4922f113aa1c732",
36
+ "sha256:37cdcb9e9954ed60912ebc1ca12a9d12178c26637abdf124e3cde2341c257fe0"
37
+ ],
38
+ "version": "==0.7.10"
39
+ },
40
+ "aspy.yaml": {
41
+ "hashes": [
42
+ "sha256:be70cc0ccd1ee1d30f589f2403792eb2ffa7546470af0a17179541b13d8374df",
43
+ "sha256:6215f44900ff65f27dbd00a36b06a7926276436ed377320cfd4febd69bbd4a94"
44
+ ],
45
+ "version": "==1.0.0"
46
+ },
47
+ "attrs": {
48
+ "hashes": [
49
+ "sha256:a17a9573a6f475c99b551c0e0a812707ddda1ec9653bed04c13841404ed6f450",
50
+ "sha256:1c7960ccfd6a005cd9f7ba884e6316b5e430a3f1a6c37c5f87d8b43f83b54ec9"
51
+ ],
52
+ "version": "==17.4.0"
53
+ },
54
+ "babel": {
55
+ "hashes": [
56
+ "sha256:ad209a68d7162c4cff4b29cdebe3dec4cef75492df501b0049a9433c96ce6f80",
57
+ "sha256:8ce4cb6fdd4393edd323227cba3a077bceb2a6ce5201c902c65e730046f41f14"
58
+ ],
59
+ "version": "==2.5.3"
60
+ },
61
+ "bumpversion": {
62
+ "hashes": [
63
+ "sha256:6753d9ff3552013e2130f7bc03c1007e24473b4835952679653fb132367bdd57",
64
+ "sha256:6744c873dd7aafc24453d8b6a1a0d6d109faf63cd0cd19cb78fd46e74932c77e"
65
+ ],
66
+ "version": "==0.5.3"
67
+ },
68
+ "cached-property": {
69
+ "hashes": [
70
+ "sha256:fe045921fe75c873064028e9fbbe06121114ccf613227f4ba284fa7d4c9ff27f",
71
+ "sha256:6562f0be134957547421dda11640e8cadfa7c23238fc4e0821ab69efdb1095f3"
72
+ ],
73
+ "version": "==1.3.1"
74
+ },
75
+ "certifi": {
76
+ "hashes": [
77
+ "sha256:14131608ad2fd56836d33a71ee60fa1c82bc9d2c8d98b7bdbc631fe1b3cd1296",
78
+ "sha256:edbc3f203427eef571f79a7692bb160a2b0f7ccaa31953e99bd17e307cf63f7d"
79
+ ],
80
+ "version": "==2018.1.18"
81
+ },
82
+ "chardet": {
83
+ "hashes": [
84
+ "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691",
85
+ "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae"
86
+ ],
87
+ "version": "==3.0.4"
88
+ },
89
+ "coverage": {
90
+ "hashes": [
91
+ "sha256:464d85d6959497cc4adfa9f0d36fca809e2ca7ec5f4625f548317892cac6ed7c",
92
+ "sha256:e958ab5b6a7f3b88289a25c95d031f2b62bc73219141c09d261fd97f244c124c",
93
+ "sha256:67288f8834a0a64c1af66286b22fd325b5524ceaa153a51c3e2e30f7e8b3f826",
94
+ "sha256:cfb6b7035c6605e2a87abe7d84ea35a107e6c432014a3f1ca243ab57a558fbcd",
95
+ "sha256:c86a12b3dc004bcbe97a3849354bd1f93eb6fb69b0e4eb58831fd7adba7740ec",
96
+ "sha256:8ddcf308f894d01a1a0ae01283d19b613751815b7190113266a0b7f9d076e86d",
97
+ "sha256:adab01e4c63a01bdf036f57f0114497994aa2195d8659d12a3d69673c3f27939",
98
+ "sha256:54d73fe68a7ac9c847af69a234a7461bbaf3cad95f258317d4584d14dd53f679",
99
+ "sha256:a0d98c71d026c1757c7393a99d24c6e42091ff41e20e68238b17e145252c2d0a",
100
+ "sha256:464e0eda175c7fe2dc730d9d02acde5b8a8518d9417413dee6ca187d1f65ef89",
101
+ "sha256:2890cb40464686c0c1dccc1223664bbc34d85af053bc5dbcd71ea13959e264f2",
102
+ "sha256:0f2315c793b1360f80a9119fff76efb7b4e5ab5062651dff515e681719f29689",
103
+ "sha256:85c028e959312285225040cdac5ad3db6189e958a234f09ae6b4ba5f539c842d",
104
+ "sha256:da6585339fc8a25086003a2b2c0167438b8ab0cd0ccae468d22ed603e414bba1",
105
+ "sha256:e837865a7b20c01a8a2f904c05fba36e8406b146649ff9174cbddf32e217b777",
106
+ "sha256:b718efb33097c7651a60a03b4b38b14776f92194bc0e9e598ce05ddaef7c70e7",
107
+ "sha256:7413f078fbba267de44814584593a729f88fc37f2d938263844b7f4daf1e36ec",
108
+ "sha256:47ad00a0c025f87a7528cc13d013c54e4691ae8730430e49ec9c7ace7e0e1fba",
109
+ "sha256:95f9f5072afeb2204401401cbd0ab978a9f86ef1ebc5cd267ba431cfa581cc4d",
110
+ "sha256:ca8827a5dad1176a8da6bf5396fd07e66549d1bc842047b76cdf69e196597a80",
111
+ "sha256:c68164c4f49cfc2e66ca1ded62e4a1092a6bd4b2c65222059b867700ad19151c",
112
+ "sha256:61e0bcf15aa0385e15d1fe4a86022a6b813d08c785855e3fab56ba6d7ac3dd21",
113
+ "sha256:981a64063242a2c6c88dda33ccafe3583026847961fe56636b6a00c47674e258",
114
+ "sha256:21e47d2ff9c75e25880dd12b316db11379e9afc98b39e9516149d189c15c564b",
115
+ "sha256:f6b822c68f68f48d480d23fcfcd1d4df7d42ff03cf5d7b574d09e662c0b95b43",
116
+ "sha256:53fa7aa7643a22eeadcf8b781b97a51f37d43ba1d897a05238aa7e4d11bc0667",
117
+ "sha256:95ce1a70323d47c0f6b8d6cfd3c14c38cb30d51fd1ab4f6414734fa33a78b17e",
118
+ "sha256:b7a06a523dfeaf417da630d46ad4f4e11ca1bae6202c9312c4cb987dde5792fc",
119
+ "sha256:585e8db44b8f3af2a4152b00dd8a7a36bc1d2aba7de5e50fc17a54178428f0d6",
120
+ "sha256:102933e14b726bd4fdfafb541e122ad36c150732aee36db409d8c8766e11537e",
121
+ "sha256:15f92238487d93f7f34a3ba03be3bd4615c69cffc88388b4dd1ea99af74fc1bf",
122
+ "sha256:319190dd7fa08c23332215782b563a9ef12b76fb15e4a325915592b825eca9ed",
123
+ "sha256:af14e9628c0a3152b6a1fbba4471e6a3e5f5567ecae614f84b84ff3441c58692",
124
+ "sha256:72bc3f91a25a87fd87eb57983c8cefbb8aa5bacd50d73516ade398271d652d77",
125
+ "sha256:c3905f10786dcf386f3f6cebe0ae4a36f47e5e256471161fb944ca537e97e928",
126
+ "sha256:3344079d73a4849341aaaecd9b391141824b8c9a96732fbd6ef95ba9566895d3"
127
+ ],
128
+ "version": "==4.5"
129
+ },
130
+ "coveralls": {
131
+ "hashes": [
132
+ "sha256:84dd8c88c5754e8db70a682f537e2781366064aa3cdd6b24c2dcecbd3181187c",
133
+ "sha256:510682001517bcca1def9f6252df6ce730fcb9831c62d9fff7c7d55b6fdabdf3"
134
+ ],
135
+ "version": "==1.2.0"
136
+ },
137
+ "docopt": {
138
+ "hashes": [
139
+ "sha256:49b3a825280bd66b3aa83585ef59c4a8c82f2c8a522dbe754a8bc8d08c85c491"
140
+ ],
141
+ "version": "==0.6.2"
142
+ },
143
+ "docutils": {
144
+ "hashes": [
145
+ "sha256:7a4bd47eaf6596e1295ecb11361139febe29b084a87bf005bf899f9a42edc3c6",
146
+ "sha256:02aec4bd92ab067f6ff27a38a38a41173bf01bed8f89157768c1573f53e474a6",
147
+ "sha256:51e64ef2ebfb29cae1faa133b3710143496eca21c530f3f71424d77687764274"
148
+ ],
149
+ "version": "==0.14"
150
+ },
151
+ "flake8": {
152
+ "hashes": [
153
+ "sha256:c7841163e2b576d435799169b78703ad6ac1bbb0f199994fc05f700b2a90ea37",
154
+ "sha256:7253265f7abd8b313e3892944044a365e3f4ac3fcdcfb4298f55ee9ddf188ba0"
155
+ ],
156
+ "version": "==3.5.0"
157
+ },
158
+ "identify": {
159
+ "hashes": [
160
+ "sha256:804e6af41604b11e0b8e0670ae172ef254b152cb5370c7e6b6e7d0c6e9c6a95e",
161
+ "sha256:496d3cce9c4088664e4840e0e01db460820bffa13f03a3016078d99feda0cd74"
162
+ ],
163
+ "version": "==1.0.7"
164
+ },
165
+ "idna": {
166
+ "hashes": [
167
+ "sha256:8c7309c718f94b3a625cb648ace320157ad16ff131ae0af362c9f21b80ef6ec4",
168
+ "sha256:2c6a5de3089009e3da7c5dde64a141dbc8551d5b7f6cf4ed7c2568d0cc520a8f"
169
+ ],
170
+ "version": "==2.6"
171
+ },
172
+ "imagesize": {
173
+ "hashes": [
174
+ "sha256:6ebdc9e0ad188f9d1b2cdd9bc59cbe42bf931875e829e7a595e6b3abdc05cdfb",
175
+ "sha256:0ab2c62b87987e3252f89d30b7cedbec12a01af9274af9ffa48108f2c13c6062"
176
+ ],
177
+ "version": "==0.7.1"
178
+ },
179
+ "jinja2": {
180
+ "hashes": [
181
+ "sha256:74c935a1b8bb9a3947c50a54766a969d4846290e1e788ea44c1392163723c3bd",
182
+ "sha256:f84be1bb0040caca4cea721fcbbbbd61f9be9464ca236387158b0feea01914a4"
183
+ ],
184
+ "version": "==2.10"
185
+ },
186
+ "markupsafe": {
187
+ "hashes": [
188
+ "sha256:a6be69091dac236ea9c6bc7d012beab42010fa914c459791d627dad4910eb665"
189
+ ],
190
+ "version": "==1.0"
191
+ },
192
+ "mccabe": {
193
+ "hashes": [
194
+ "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42",
195
+ "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"
196
+ ],
197
+ "version": "==0.6.1"
198
+ },
199
+ "mock": {
200
+ "hashes": [
201
+ "sha256:5ce3c71c5545b472da17b72268978914d0252980348636840bd34a00b5cc96c1",
202
+ "sha256:b158b6df76edd239b8208d481dc46b6afd45a846b7812ff0ce58971cf5bc8bba"
203
+ ],
204
+ "version": "==2.0.0"
205
+ },
206
+ "nodeenv": {
207
+ "hashes": [
208
+ "sha256:98835dab727f94a713eacc7234e3db6777a55cafb60f391485011899e5c818df"
209
+ ],
210
+ "version": "==1.2.0"
211
+ },
212
+ "pbr": {
213
+ "hashes": [
214
+ "sha256:60c25b7dfd054ef9bb0ae327af949dd4676aa09ac3a9471cdc871d8a9213f9ac",
215
+ "sha256:05f61c71aaefc02d8e37c0a3eeb9815ff526ea28b3b76324769e6158d7f95be1"
216
+ ],
217
+ "version": "==3.1.1"
218
+ },
219
+ "pluggy": {
220
+ "hashes": [
221
+ "sha256:7f8ae7f5bdf75671a718d2daf0a64b7885f74510bcd98b1a0bb420eb9a9d0cff"
222
+ ],
223
+ "version": "==0.6.0"
224
+ },
225
+ "pre-commit": {
226
+ "hashes": [
227
+ "sha256:036ac395075609b0ee7017dd356df42e486ccb8d675493e497492795146eab46",
228
+ "sha256:6d6646f5a390677c5c9cd61d5a2613b89d93f079725047aff16fddda9a3ce77f"
229
+ ],
230
+ "version": "==1.6.0"
231
+ },
232
+ "py": {
233
+ "hashes": [
234
+ "sha256:8cca5c229d225f8c1e3085be4fcf306090b00850fefad892f9d96c7b6e2f310f",
235
+ "sha256:ca18943e28235417756316bfada6cd96b23ce60dd532642690dcfdaba988a76d"
236
+ ],
237
+ "version": "==1.5.2"
238
+ },
239
+ "pycodestyle": {
240
+ "hashes": [
241
+ "sha256:6c4245ade1edfad79c3446fadfc96b0de2759662dc29d07d80a6f27ad1ca6ba9",
242
+ "sha256:682256a5b318149ca0d2a9185d365d8864a768a28db66a84a2ea946bcc426766"
243
+ ],
244
+ "version": "==2.3.1"
245
+ },
246
+ "pyflakes": {
247
+ "hashes": [
248
+ "sha256:08bd6a50edf8cffa9fa09a463063c425ecaaf10d1eb0335a7e8b1401aef89e6f",
249
+ "sha256:8d616a382f243dbf19b54743f280b80198be0bca3a5396f1d2e1fca6223e8805"
250
+ ],
251
+ "version": "==1.6.0"
252
+ },
253
+ "pygments": {
254
+ "hashes": [
255
+ "sha256:78f3f434bcc5d6ee09020f92ba487f95ba50f1e3ef83ae96b9d5ffa1bab25c5d",
256
+ "sha256:dbae1046def0efb574852fab9e90209b23f556367b5a320c0bcb871c77c3e8cc"
257
+ ],
258
+ "version": "==2.2.0"
259
+ },
260
+ "pytest": {
261
+ "hashes": [
262
+ "sha256:95fa025cd6deb5d937e04e368a00552332b58cae23f63b76c8c540ff1733ab6d",
263
+ "sha256:6074ea3b9c999bd6d0df5fa9d12dd95ccd23550df2a582f5f5b848331d2e82ca"
264
+ ],
265
+ "version": "==3.4.0"
266
+ },
267
+ "pytest-cov": {
268
+ "hashes": [
269
+ "sha256:890fe5565400902b0c78b5357004aab1c814115894f4f21370e2433256a3eeec",
270
+ "sha256:03aa752cf11db41d281ea1d807d954c4eda35cfa1b21d6971966cc041bbf6e2d"
271
+ ],
272
+ "version": "==2.5.1"
273
+ },
274
+ "pytest-mock": {
275
+ "hashes": [
276
+ "sha256:7ed6e7e8c636fd320927c5d73aedb77ac2eeb37196c3410e6176b7c92fdf2f69",
277
+ "sha256:920d1167af5c2c2ad3fa0717d0c6c52e97e97810160c15721ac895cac53abb1c"
278
+ ],
279
+ "version": "==1.6.3"
280
+ },
281
+ "pytz": {
282
+ "hashes": [
283
+ "sha256:80af0f3008046b9975242012a985f04c5df1f01eed4ec1633d56cc47a75a6a48",
284
+ "sha256:feb2365914948b8620347784b6b6da356f31c9d03560259070b2f30cff3d469d",
285
+ "sha256:59707844a9825589878236ff2f4e0dc9958511b7ffaae94dc615da07d4a68d33",
286
+ "sha256:d0ef5ef55ed3d37854320d4926b04a4cb42a2e88f71da9ddfdacfde8e364f027",
287
+ "sha256:c41c62827ce9cafacd6f2f7018e4f83a6f1986e87bfd000b8cfbd4ab5da95f1a",
288
+ "sha256:8cc90340159b5d7ced6f2ba77694d946fc975b09f1a51d93f3ce3bb399396f94",
289
+ "sha256:dd2e4ca6ce3785c8dd342d1853dd9052b19290d5bf66060846e5dc6b8d6667f7",
290
+ "sha256:699d18a2a56f19ee5698ab1123bbcc1d269d061996aeb1eda6d89248d3542b82",
291
+ "sha256:fae4cffc040921b8a2d60c6cf0b5d662c1190fe54d718271db4eb17d44a185b7"
292
+ ],
293
+ "version": "==2017.3"
294
+ },
295
+ "pyyaml": {
296
+ "hashes": [
297
+ "sha256:3262c96a1ca437e7e4763e2843746588a965426550f3797a79fca9c6199c431f",
298
+ "sha256:16b20e970597e051997d90dc2cddc713a2876c47e3d92d59ee198700c5427736",
299
+ "sha256:e863072cdf4c72eebf179342c94e6989c67185842d9997960b3e69290b2fa269",
300
+ "sha256:bc6bced57f826ca7cb5125a10b23fd0f2fff3b7c4701d64c439a300ce665fff8",
301
+ "sha256:c01b880ec30b5a6e6aa67b09a2fe3fb30473008c85cd6a67359a1b15ed6d83a4",
302
+ "sha256:827dc04b8fa7d07c44de11fabbc888e627fa8293b695e0f99cb544fdfa1bf0d1",
303
+ "sha256:592766c6303207a20efc445587778322d7f73b161bd994f227adaa341ba212ab",
304
+ "sha256:5f84523c076ad14ff5e6c037fe1c89a7f73a3e04cf0377cb4d017014976433f3",
305
+ "sha256:0c507b7f74b3d2dd4d1322ec8a94794927305ab4cebbe89cc47fe5e81541e6e8",
306
+ "sha256:b4c423ab23291d3945ac61346feeb9a0dc4184999ede5e7c43e1ffb975130ae6",
307
+ "sha256:ca233c64c6e40eaa6c66ef97058cdc80e8d0157a443655baa1b2966e812807ca",
308
+ "sha256:4474f8ea030b5127225b8894d626bb66c01cda098d47a2b0d3429b6700af9fd8",
309
+ "sha256:326420cbb492172dec84b0f65c80942de6cedb5233c413dd824483989c000608",
310
+ "sha256:5ac82e411044fb129bae5cfbeb3ba626acb2af31a8d17d175004b70862a741a7"
311
+ ],
312
+ "version": "==3.12"
313
+ },
314
+ "requests": {
315
+ "hashes": [
316
+ "sha256:6a1b267aa90cac58ac3a765d067950e7dbbf75b1da07e895d1f594193a40a38b",
317
+ "sha256:9c443e7324ba5b85070c4a818ade28bfabedf16ea10206da1132edaa6dda237e"
318
+ ],
319
+ "version": "==2.18.4"
320
+ },
321
+ "six": {
322
+ "hashes": [
323
+ "sha256:832dc0e10feb1aa2c68dcc57dbb658f1c7e65b9b61af69048abc87a2db00a0eb",
324
+ "sha256:70e8a77beed4562e7f14fe23a786b54f6296e34344c23bc42f07b15018ff98e9"
325
+ ],
326
+ "version": "==1.11.0"
327
+ },
328
+ "snowballstemmer": {
329
+ "hashes": [
330
+ "sha256:9f3bcd3c401c3e862ec0ebe6d2c069ebc012ce142cce209c098ccb5b09136e89",
331
+ "sha256:919f26a68b2c17a7634da993d91339e288964f93c274f1343e3bbbe2096e1128"
332
+ ],
333
+ "version": "==1.2.1"
334
+ },
335
+ "sphinx": {
336
+ "hashes": [
337
+ "sha256:d5b91b4dad56ffc9f19425ebaa7bc23290a0a2e9035781d5bc54822384663277",
338
+ "sha256:832bed0dc6099c2abca957d90ff55bc1a6ec4425c13fc144adbae68a970e3775"
339
+ ],
340
+ "version": "==1.6.7"
341
+ },
342
+ "sphinx-rtd-theme": {
343
+ "hashes": [
344
+ "sha256:62ee4752716e698bad7de8a18906f42d33664128eea06c46b718fc7fbd1a9f5c",
345
+ "sha256:2df74b8ff6fae6965c527e97cca6c6c944886aae474b490e17f92adfbe843417"
346
+ ],
347
+ "version": "==0.2.4"
348
+ },
349
+ "sphinxcontrib-websupport": {
350
+ "hashes": [
351
+ "sha256:f4932e95869599b89bf4f80fc3989132d83c9faa5bf633e7b5e0c25dffb75da2",
352
+ "sha256:7a85961326aa3a400cd4ad3c816d70ed6f7c740acd7ce5d78cd0a67825072eb9"
353
+ ],
354
+ "version": "==1.0.1"
355
+ },
356
+ "urllib3": {
357
+ "hashes": [
358
+ "sha256:06330f386d6e4b195fbfc736b297f58c5a892e4440e54d294d7004e3a9bbea1b",
359
+ "sha256:cc44da8e1145637334317feebd728bd869a35285b93cbb4cca2577da7e62db4f"
360
+ ],
361
+ "version": "==1.22"
362
+ },
363
+ "virtualenv": {
364
+ "hashes": [
365
+ "sha256:39d88b533b422825d644087a21e78c45cf5af0ef7a99a1fc9fbb7b481e5c85b0",
366
+ "sha256:02f8102c2436bb03b3ee6dede1919d1dac8a427541652e5ec95171ec8adbc93a"
367
+ ],
368
+ "version": "==15.1.0"
369
+ }
370
+ }
371
+ }
README.rst → README.md RENAMED
@@ -1,32 +1,21 @@
1
- ======
2
- pytube
3
- ======
4
-
5
- .. image:: https://img.shields.io/pypi/v/pytube.svg
6
- :alt: Pypi
7
- :target: https://pypi.python.org/pypi/pytube/
8
-
9
- .. image:: https://travis-ci.org/nficano/pytube.svg?branch=master
10
- :alt: Build status
11
- :target: https://travis-ci.org/nficano/pytube
12
-
13
- .. image:: https://readthedocs.org/projects/python-pytube/badge/?version=latest
14
- :alt: Documentation Status
15
- :target: http://python-pytube.readthedocs.io/en/latest/?badge=latest
16
-
17
- .. image:: https://coveralls.io/repos/github/nficano/pytube/badge.svg?branch=master#23e6f7ac56dd3bde
18
- :alt: Code Coverage
19
- :target: https://coveralls.io/github/nficano/pytube?branch=master
20
-
21
- .. image:: https://img.shields.io/pypi/pyversions/pytube.svg
22
- :alt: Python Versions
23
- :target: https://pypi.python.org/pypi/pytube/
24
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
25
  *pytube* is a lightweight, dependency-free Python library (and command-line utility) for downloading YouTube Videos.
26
 
27
- Description
28
- ===========
29
-
30
  YouTube is the most popular video-sharing platform in the world and as a hacker you may encounter a situation where you want to script something to download videos. For this I present to you *pytube*.
31
 
32
  *pytube* is a lightweight library written in Python. It has no third party dependencies and aims to be highly reliable.
@@ -35,20 +24,20 @@ YouTube is the most popular video-sharing platform in the world and as a hacker
35
 
36
  Finally *pytube* also includes a command-line utility, allowing you to quickly download videos right from terminal.
37
 
38
- **Behold, a perfect balance of simplicity versus flexibility**::
39
-
40
- >>> YouTube('https://youtu.be/9bZkp7q19f0').streams.first().download()
41
- >>> yt = YouTube('http://youtube.com/watch?v=9bZkp7q19f0')
42
- >>> yt.streams
43
- ... .filter(progressive=True, file_extension='mp4')
44
- ... .order_by('resolution')
45
- ... .desc()
46
- ... .first()
47
- ... .download()
48
 
49
- Features
50
- --------
 
 
 
 
 
 
 
 
51
 
 
52
  - Support for Both Progressive & DASH Streams
53
  - Support for downloading complete playlist
54
  - Easily Register ``on_download_progress`` & ``on_download_complete`` callbacks
@@ -59,56 +48,52 @@ Features
59
  - Extensively Documented Source Code
60
  - No Third-Party Dependencies
61
 
62
- Installation
63
- ------------
64
 
65
  Download using pip via pypi.
66
 
67
- .. code-block:: bash
 
 
68
 
69
- pip install pytube
70
-
71
- Getting started
72
- ---------------
73
 
74
  Let's begin with showing how easy it is to download a video with pytube:
75
 
76
- .. code-block:: python
77
-
78
- >>> from pytube import YouTube
79
- >>> YouTube('http://youtube.com/watch?v=9bZkp7q19f0').streams.first().download()
80
-
81
  This example will download the highest quality progressive download stream available.
82
 
83
  Next, let's explore how we would view what video streams are available:
84
 
85
- .. code-block:: python
86
-
87
- >>> yt = YouTube('http://youtube.com/watch?v=9bZkp7q19f0')
88
- >>> yt.streams.all()
89
- [<Stream: itag="22" mime_type="video/mp4" res="720p" fps="30fps" vcodec="avc1.64001F" acodec="mp4a.40.2">,
90
- <Stream: itag="43" mime_type="video/webm" res="360p" fps="30fps" vcodec="vp8.0" acodec="vorbis">,
91
- <Stream: itag="18" mime_type="video/mp4" res="360p" fps="30fps" vcodec="avc1.42001E" acodec="mp4a.40.2">,
92
- <Stream: itag="36" mime_type="video/3gpp" res="240p" fps="30fps" vcodec="mp4v.20.3" acodec="mp4a.40.2">,
93
- <Stream: itag="17" mime_type="video/3gpp" res="144p" fps="30fps" vcodec="mp4v.20.3" acodec="mp4a.40.2">,
94
- <Stream: itag="137" mime_type="video/mp4" res="1080p" fps="30fps" vcodec="avc1.640028">,
95
- <Stream: itag="248" mime_type="video/webm" res="1080p" fps="30fps" vcodec="vp9">,
96
- <Stream: itag="136" mime_type="video/mp4" res="720p" fps="30fps" vcodec="avc1.4d401f">,
97
- <Stream: itag="247" mime_type="video/webm" res="720p" fps="30fps" vcodec="vp9">,
98
- <Stream: itag="135" mime_type="video/mp4" res="480p" fps="30fps" vcodec="avc1.4d401e">,
99
- <Stream: itag="244" mime_type="video/webm" res="480p" fps="30fps" vcodec="vp9">,
100
- <Stream: itag="134" mime_type="video/mp4" res="360p" fps="30fps" vcodec="avc1.4d401e">,
101
- <Stream: itag="243" mime_type="video/webm" res="360p" fps="30fps" vcodec="vp9">,
102
- <Stream: itag="133" mime_type="video/mp4" res="240p" fps="30fps" vcodec="avc1.4d4015">,
103
- <Stream: itag="242" mime_type="video/webm" res="240p" fps="30fps" vcodec="vp9">,
104
- <Stream: itag="160" mime_type="video/mp4" res="144p" fps="30fps" vcodec="avc1.4d400c">,
105
- <Stream: itag="278" mime_type="video/webm" res="144p" fps="30fps" vcodec="vp9">,
106
- <Stream: itag="140" mime_type="audio/mp4" abr="128kbps" acodec="mp4a.40.2">,
107
- <Stream: itag="171" mime_type="audio/webm" abr="128kbps" acodec="vorbis">,
108
- <Stream: itag="249" mime_type="audio/webm" abr="50kbps" acodec="opus">,
109
- <Stream: itag="250" mime_type="audio/webm" abr="70kbps" acodec="opus">,
110
- <Stream: itag="251" mime_type="audio/webm" abr="160kbps" acodec="opus">]
111
-
112
  You may notice that some streams listed have both a video codec and audio codec, while others have just video or just audio, this is a result of YouTube supporting a streaming technique called Dynamic Adaptive Streaming over HTTP (DASH).
113
 
114
  In the context of pytube, the implications are for the highest quality streams; you now need to download both the audio and video tracks and then post-process them with software like FFmpeg to merge them.
@@ -117,142 +102,130 @@ The legacy streams that contain the audio and video in a single file (referred t
117
 
118
  To only view these progressive download streams:
119
 
120
- .. code-block:: python
121
-
122
- >>> yt.streams.filter(progressive=True).all()
123
- [<Stream: itag="22" mime_type="video/mp4" res="720p" fps="30fps" vcodec="avc1.64001F" acodec="mp4a.40.2">,
124
- <Stream: itag="43" mime_type="video/webm" res="360p" fps="30fps" vcodec="vp8.0" acodec="vorbis">,
125
- <Stream: itag="18" mime_type="video/mp4" res="360p" fps="30fps" vcodec="avc1.42001E" acodec="mp4a.40.2">,
126
- <Stream: itag="36" mime_type="video/3gpp" res="240p" fps="30fps" vcodec="mp4v.20.3" acodec="mp4a.40.2">,
127
- <Stream: itag="17" mime_type="video/3gpp" res="144p" fps="30fps" vcodec="mp4v.20.3" acodec="mp4a.40.2">]
128
 
129
  Conversely, if you only want to see the DASH streams (also referred to as "adaptive") you can do:
130
 
131
- .. code-block:: python
132
-
133
- >>> yt.streams.filter(adaptive=True).all()
134
- [<Stream: itag="137" mime_type="video/mp4" res="1080p" fps="30fps" vcodec="avc1.640028">,
135
- <Stream: itag="248" mime_type="video/webm" res="1080p" fps="30fps" vcodec="vp9">,
136
- <Stream: itag="136" mime_type="video/mp4" res="720p" fps="30fps" vcodec="avc1.4d401f">,
137
- <Stream: itag="247" mime_type="video/webm" res="720p" fps="30fps" vcodec="vp9">,
138
- <Stream: itag="135" mime_type="video/mp4" res="480p" fps="30fps" vcodec="avc1.4d401e">,
139
- <Stream: itag="244" mime_type="video/webm" res="480p" fps="30fps" vcodec="vp9">,
140
- <Stream: itag="134" mime_type="video/mp4" res="360p" fps="30fps" vcodec="avc1.4d401e">,
141
- <Stream: itag="243" mime_type="video/webm" res="360p" fps="30fps" vcodec="vp9">,
142
- <Stream: itag="133" mime_type="video/mp4" res="240p" fps="30fps" vcodec="avc1.4d4015">,
143
- <Stream: itag="242" mime_type="video/webm" res="240p" fps="30fps" vcodec="vp9">,
144
- <Stream: itag="160" mime_type="video/mp4" res="144p" fps="30fps" vcodec="avc1.4d400c">,
145
- <Stream: itag="278" mime_type="video/webm" res="144p" fps="30fps" vcodec="vp9">,
146
- <Stream: itag="140" mime_type="audio/mp4" abr="128kbps" acodec="mp4a.40.2">,
147
- <Stream: itag="171" mime_type="audio/webm" abr="128kbps" acodec="vorbis">,
148
- <Stream: itag="249" mime_type="audio/webm" abr="50kbps" acodec="opus">,
149
- <Stream: itag="250" mime_type="audio/webm" abr="70kbps" acodec="opus">,
150
- <Stream: itag="251" mime_type="audio/webm" abr="160kbps" acodec="opus">]
151
-
152
 
153
  You can also download a complete Youtube playlist:
154
 
155
- .. code-block:: python
156
-
157
- >>> from pytube import Playlist
158
- >>> pl = Playlist("https://www.youtube.com/watch?v=Edpy1szoG80&list=PL153hDY-y1E00uQtCVCVC8xJ25TYX8yPU")
159
- >>> pl.download_all()
160
-
161
  This will download the highest progressive stream available (generally 720p) from the given playlist. Later more option would be give users flexibility
162
  to choose video resolution. Playlist videos will be downloaded in the directory from where the command was run.
163
 
164
-
165
  Pytube allows you to filter on every property available (see the documentation for the complete list), let's take a look at some of the most useful ones.
166
 
167
  To list the audio only streams:
168
 
169
- .. code-block:: python
170
-
171
- >>> yt.streams.filter(only_audio=True).all()
172
- [<Stream: itag="140" mime_type="audio/mp4" abr="128kbps" acodec="mp4a.40.2">,
173
- <Stream: itag="171" mime_type="audio/webm" abr="128kbps" acodec="vorbis">,
174
- <Stream: itag="249" mime_type="audio/webm" abr="50kbps" acodec="opus">,
175
- <Stream: itag="250" mime_type="audio/webm" abr="70kbps" acodec="opus">,
176
- <Stream: itag="251" mime_type="audio/webm" abr="160kbps" acodec="opus">]
177
-
178
 
179
  To list only ``mp4`` streams:
180
 
181
- .. code-block:: python
182
-
183
- >>> yt.streams.filter(subtype='mp4').all()
184
- [<Stream: itag="22" mime_type="video/mp4" res="720p" fps="30fps" vcodec="avc1.64001F" acodec="mp4a.40.2">,
185
- <Stream: itag="18" mime_type="video/mp4" res="360p" fps="30fps" vcodec="avc1.42001E" acodec="mp4a.40.2">,
186
- <Stream: itag="137" mime_type="video/mp4" res="1080p" fps="30fps" vcodec="avc1.640028">,
187
- <Stream: itag="136" mime_type="video/mp4" res="720p" fps="30fps" vcodec="avc1.4d401f">,
188
- <Stream: itag="135" mime_type="video/mp4" res="480p" fps="30fps" vcodec="avc1.4d401e">,
189
- <Stream: itag="134" mime_type="video/mp4" res="360p" fps="30fps" vcodec="avc1.4d401e">,
190
- <Stream: itag="133" mime_type="video/mp4" res="240p" fps="30fps" vcodec="avc1.4d4015">,
191
- <Stream: itag="160" mime_type="video/mp4" res="144p" fps="30fps" vcodec="avc1.4d400c">,
192
- <Stream: itag="140" mime_type="audio/mp4" abr="128kbps" acodec="mp4a.40.2">]
193
-
194
 
195
  Multiple filters can also be specified:
196
 
197
- .. code-block:: python
198
-
199
- >>> yt.streams.filter(subtype='mp4', progressive=True).all()
200
- >>> # this can also be expressed as:
201
- >>> yt.streams.filter(subtype='mp4').filter(progressive=True).all()
202
- [<Stream: itag="22" mime_type="video/mp4" res="720p" fps="30fps" vcodec="avc1.64001F" acodec="mp4a.40.2">,
203
- <Stream: itag="18" mime_type="video/mp4" res="360p" fps="30fps" vcodec="avc1.42001E" acodec="mp4a.40.2">]
204
-
205
  You also have an interface to select streams by their itag, without needing to filter:
206
 
207
- .. code-block:: python
208
-
209
- >>> yt.streams.get_by_itag(22)
210
- <Stream: itag="22" mime_type="video/mp4" res="720p" fps="30fps" vcodec="avc1.64001F" acodec="mp4a.40.2">
211
-
212
 
213
  If you need to optimize for a specific feature, such as the "highest resolution" or "lowest average bitrate":
214
 
215
- .. code-block:: python
216
-
217
- >>> yt.streams.filter(progressive=True).order_by('resolution').desc().all()
218
-
219
  Note that ``order_by`` cannot be used if your attribute is undefined in any of the Stream instances, so be sure to apply a filter to remove those before calling it.
220
 
221
  If your application requires post-processing logic, pytube allows you to specify an "on download complete" callback function:
222
 
223
- .. code-block:: python
224
-
225
- >>> def convert_to_aac(stream, file_handle):
226
- # do work
227
- >>> yt.register_on_complete_callback(convert_to_aac)
228
 
 
 
229
 
230
  Similarly, if your application requires on-download progress logic, pytube exposes a callback for this as well:
231
 
232
- .. code-block:: python
233
-
234
- >>> def show_progress_bar(stream, chunk, file_handle, bytes_remaining):
235
- # do work
236
- >>> yt.register_on_progress_callback(show_progress_bar)
237
-
238
 
 
 
239
 
240
- Command-line interface
241
- ======================
242
 
243
  pytube also ships with a tiny cli interface for downloading and probing videos.
244
 
245
  Let's start with downloading:
246
 
247
- .. code-block:: bash
248
-
249
- pytube http://youtube.com/watch?v=9bZkp7q19f0 --itag=22
250
-
251
  To view available streams:
252
 
253
- .. code-block:: bash
254
-
255
- pytube http://youtube.com/watch?v=9bZkp7q19f0 --list
256
-
257
 
258
  Finally, if you're filing a bug report, the cli contains a switch called ``--build-playback-report``, which bundles up the state, allowing others to easily replay your issue.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
 
2
+ <div align="center">
3
+ <p>
4
+ <img src="./images/pytube.png" width="350" height="328" alt="pytube logo" />
5
+ </p>
6
+ <p align="center">
7
+ <img src="https://img.shields.io/pypi/v/pytube.svg" alt="pypi">
8
+ <a href="https://travis-ci.org/nficano/pytube"><img src="https://travis-ci.org/nficano/pytube.svg?branch=master" /></a>
9
+ <a href="http://python-pytube.readthedocs.io/en/latest/?badge=latest"><img src="https://readthedocs.org/projects/python-pytube/badge/?version=latest" /></a>
10
+ <a href="https://coveralls.io/github/nficano/pytube?branch=master"><img src="https://coveralls.io/repos/github/nficano/pytube/badge.svg?branch=master#23e6f7ac56dd3bde" /></a>
11
+ <a href="https://pypi.python.org/pypi/pytube/"><img src="https://img.shields.io/pypi/pyversions/pytube.svg" /></a>
12
+ </p>
13
+ </div>
14
+
15
+ # pytube
16
  *pytube* is a lightweight, dependency-free Python library (and command-line utility) for downloading YouTube Videos.
17
 
18
+ ## Description
 
 
19
  YouTube is the most popular video-sharing platform in the world and as a hacker you may encounter a situation where you want to script something to download videos. For this I present to you *pytube*.
20
 
21
  *pytube* is a lightweight library written in Python. It has no third party dependencies and aims to be highly reliable.
 
24
 
25
  Finally *pytube* also includes a command-line utility, allowing you to quickly download videos right from terminal.
26
 
27
+ ### Behold, a perfect balance of simplicity versus flexibility:
 
 
 
 
 
 
 
 
 
28
 
29
+ ```python
30
+ >>> YouTube('https://youtu.be/9bZkp7q19f0').streams.first().download()
31
+ >>> yt = YouTube('http://youtube.com/watch?v=9bZkp7q19f0')
32
+ >>> yt.streams
33
+ ... .filter(progressive=True, file_extension='mp4')
34
+ ... .order_by('resolution')
35
+ ... .desc()
36
+ ... .first()
37
+ ... .download()
38
+ ```
39
 
40
+ ## Features
41
  - Support for Both Progressive & DASH Streams
42
  - Support for downloading complete playlist
43
  - Easily Register ``on_download_progress`` & ``on_download_complete`` callbacks
 
48
  - Extensively Documented Source Code
49
  - No Third-Party Dependencies
50
 
51
+ ## Installation
 
52
 
53
  Download using pip via pypi.
54
 
55
+ ```bash
56
+ $ pip install pytube
57
+ ```
58
 
59
+ ## Getting started
 
 
 
60
 
61
  Let's begin with showing how easy it is to download a video with pytube:
62
 
63
+ ```python
64
+ >>> from pytube import YouTube
65
+ >>> YouTube('http://youtube.com/watch?v=9bZkp7q19f0').streams.first().download()
66
+ ```
 
67
  This example will download the highest quality progressive download stream available.
68
 
69
  Next, let's explore how we would view what video streams are available:
70
 
71
+ ```python
72
+ >>> yt = YouTube('http://youtube.com/watch?v=9bZkp7q19f0')
73
+ >>> yt.streams.all()
74
+ [<Stream: itag="22" mime_type="video/mp4" res="720p" fps="30fps" vcodec="avc1.64001F" acodec="mp4a.40.2">,
75
+ <Stream: itag="43" mime_type="video/webm" res="360p" fps="30fps" vcodec="vp8.0" acodec="vorbis">,
76
+ <Stream: itag="18" mime_type="video/mp4" res="360p" fps="30fps" vcodec="avc1.42001E" acodec="mp4a.40.2">,
77
+ <Stream: itag="36" mime_type="video/3gpp" res="240p" fps="30fps" vcodec="mp4v.20.3" acodec="mp4a.40.2">,
78
+ <Stream: itag="17" mime_type="video/3gpp" res="144p" fps="30fps" vcodec="mp4v.20.3" acodec="mp4a.40.2">,
79
+ <Stream: itag="137" mime_type="video/mp4" res="1080p" fps="30fps" vcodec="avc1.640028">,
80
+ <Stream: itag="248" mime_type="video/webm" res="1080p" fps="30fps" vcodec="vp9">,
81
+ <Stream: itag="136" mime_type="video/mp4" res="720p" fps="30fps" vcodec="avc1.4d401f">,
82
+ <Stream: itag="247" mime_type="video/webm" res="720p" fps="30fps" vcodec="vp9">,
83
+ <Stream: itag="135" mime_type="video/mp4" res="480p" fps="30fps" vcodec="avc1.4d401e">,
84
+ <Stream: itag="244" mime_type="video/webm" res="480p" fps="30fps" vcodec="vp9">,
85
+ <Stream: itag="134" mime_type="video/mp4" res="360p" fps="30fps" vcodec="avc1.4d401e">,
86
+ <Stream: itag="243" mime_type="video/webm" res="360p" fps="30fps" vcodec="vp9">,
87
+ <Stream: itag="133" mime_type="video/mp4" res="240p" fps="30fps" vcodec="avc1.4d4015">,
88
+ <Stream: itag="242" mime_type="video/webm" res="240p" fps="30fps" vcodec="vp9">,
89
+ <Stream: itag="160" mime_type="video/mp4" res="144p" fps="30fps" vcodec="avc1.4d400c">,
90
+ <Stream: itag="278" mime_type="video/webm" res="144p" fps="30fps" vcodec="vp9">,
91
+ <Stream: itag="140" mime_type="audio/mp4" abr="128kbps" acodec="mp4a.40.2">,
92
+ <Stream: itag="171" mime_type="audio/webm" abr="128kbps" acodec="vorbis">,
93
+ <Stream: itag="249" mime_type="audio/webm" abr="50kbps" acodec="opus">,
94
+ <Stream: itag="250" mime_type="audio/webm" abr="70kbps" acodec="opus">,
95
+ <Stream: itag="251" mime_type="audio/webm" abr="160kbps" acodec="opus">]
96
+ ```
 
97
  You may notice that some streams listed have both a video codec and audio codec, while others have just video or just audio, this is a result of YouTube supporting a streaming technique called Dynamic Adaptive Streaming over HTTP (DASH).
98
 
99
  In the context of pytube, the implications are for the highest quality streams; you now need to download both the audio and video tracks and then post-process them with software like FFmpeg to merge them.
 
102
 
103
  To only view these progressive download streams:
104
 
105
+ ```python
106
+ >>> yt.streams.filter(progressive=True).all()
107
+ [<Stream: itag="22" mime_type="video/mp4" res="720p" fps="30fps" vcodec="avc1.64001F" acodec="mp4a.40.2">,
108
+ <Stream: itag="43" mime_type="video/webm" res="360p" fps="30fps" vcodec="vp8.0" acodec="vorbis">,
109
+ <Stream: itag="18" mime_type="video/mp4" res="360p" fps="30fps" vcodec="avc1.42001E" acodec="mp4a.40.2">,
110
+ <Stream: itag="36" mime_type="video/3gpp" res="240p" fps="30fps" vcodec="mp4v.20.3" acodec="mp4a.40.2">,
111
+ <Stream: itag="17" mime_type="video/3gpp" res="144p" fps="30fps" vcodec="mp4v.20.3" acodec="mp4a.40.2">]
112
+ ```
113
 
114
  Conversely, if you only want to see the DASH streams (also referred to as "adaptive") you can do:
115
 
116
+ ```python
117
+ >>> yt.streams.filter(adaptive=True).all()
118
+ [<Stream: itag="137" mime_type="video/mp4" res="1080p" fps="30fps" vcodec="avc1.640028">,
119
+ <Stream: itag="248" mime_type="video/webm" res="1080p" fps="30fps" vcodec="vp9">,
120
+ <Stream: itag="136" mime_type="video/mp4" res="720p" fps="30fps" vcodec="avc1.4d401f">,
121
+ <Stream: itag="247" mime_type="video/webm" res="720p" fps="30fps" vcodec="vp9">,
122
+ <Stream: itag="135" mime_type="video/mp4" res="480p" fps="30fps" vcodec="avc1.4d401e">,
123
+ <Stream: itag="244" mime_type="video/webm" res="480p" fps="30fps" vcodec="vp9">,
124
+ <Stream: itag="134" mime_type="video/mp4" res="360p" fps="30fps" vcodec="avc1.4d401e">,
125
+ <Stream: itag="243" mime_type="video/webm" res="360p" fps="30fps" vcodec="vp9">,
126
+ <Stream: itag="133" mime_type="video/mp4" res="240p" fps="30fps" vcodec="avc1.4d4015">,
127
+ <Stream: itag="242" mime_type="video/webm" res="240p" fps="30fps" vcodec="vp9">,
128
+ <Stream: itag="160" mime_type="video/mp4" res="144p" fps="30fps" vcodec="avc1.4d400c">,
129
+ <Stream: itag="278" mime_type="video/webm" res="144p" fps="30fps" vcodec="vp9">,
130
+ <Stream: itag="140" mime_type="audio/mp4" abr="128kbps" acodec="mp4a.40.2">,
131
+ <Stream: itag="171" mime_type="audio/webm" abr="128kbps" acodec="vorbis">,
132
+ <Stream: itag="249" mime_type="audio/webm" abr="50kbps" acodec="opus">,
133
+ <Stream: itag="250" mime_type="audio/webm" abr="70kbps" acodec="opus">,
134
+ <Stream: itag="251" mime_type="audio/webm" abr="160kbps" acodec="opus">]
135
+ ```
 
136
 
137
  You can also download a complete Youtube playlist:
138
 
139
+ ```python
140
+ >>> from pytube import Playlist
141
+ >>> pl = Playlist("https://www.youtube.com/watch?v=Edpy1szoG80&list=PL153hDY-y1E00uQtCVCVC8xJ25TYX8yPU")
142
+ >>> pl.download_all()
143
+ ```
 
144
  This will download the highest progressive stream available (generally 720p) from the given playlist. Later more option would be give users flexibility
145
  to choose video resolution. Playlist videos will be downloaded in the directory from where the command was run.
146
 
 
147
  Pytube allows you to filter on every property available (see the documentation for the complete list), let's take a look at some of the most useful ones.
148
 
149
  To list the audio only streams:
150
 
151
+ ```python
152
+ >>> yt.streams.filter(only_audio=True).all()
153
+ [<Stream: itag="140" mime_type="audio/mp4" abr="128kbps" acodec="mp4a.40.2">,
154
+ <Stream: itag="171" mime_type="audio/webm" abr="128kbps" acodec="vorbis">,
155
+ <Stream: itag="249" mime_type="audio/webm" abr="50kbps" acodec="opus">,
156
+ <Stream: itag="250" mime_type="audio/webm" abr="70kbps" acodec="opus">,
157
+ <Stream: itag="251" mime_type="audio/webm" abr="160kbps" acodec="opus">]
158
+ ```
 
159
 
160
  To list only ``mp4`` streams:
161
 
162
+ ```python
163
+ >>> yt.streams.filter(subtype='mp4').all()
164
+ [<Stream: itag="22" mime_type="video/mp4" res="720p" fps="30fps" vcodec="avc1.64001F" acodec="mp4a.40.2">,
165
+ <Stream: itag="18" mime_type="video/mp4" res="360p" fps="30fps" vcodec="avc1.42001E" acodec="mp4a.40.2">,
166
+ <Stream: itag="137" mime_type="video/mp4" res="1080p" fps="30fps" vcodec="avc1.640028">,
167
+ <Stream: itag="136" mime_type="video/mp4" res="720p" fps="30fps" vcodec="avc1.4d401f">,
168
+ <Stream: itag="135" mime_type="video/mp4" res="480p" fps="30fps" vcodec="avc1.4d401e">,
169
+ <Stream: itag="134" mime_type="video/mp4" res="360p" fps="30fps" vcodec="avc1.4d401e">,
170
+ <Stream: itag="133" mime_type="video/mp4" res="240p" fps="30fps" vcodec="avc1.4d4015">,
171
+ <Stream: itag="160" mime_type="video/mp4" res="144p" fps="30fps" vcodec="avc1.4d400c">,
172
+ <Stream: itag="140" mime_type="audio/mp4" abr="128kbps" acodec="mp4a.40.2">]
173
+ ```
 
174
 
175
  Multiple filters can also be specified:
176
 
177
+ ```python
178
+ >>> yt.streams.filter(subtype='mp4', progressive=True).all()
179
+ >>> # this can also be expressed as:
180
+ >>> yt.streams.filter(subtype='mp4').filter(progressive=True).all()
181
+ [<Stream: itag="22" mime_type="video/mp4" res="720p" fps="30fps" vcodec="avc1.64001F" acodec="mp4a.40.2">,
182
+ <Stream: itag="18" mime_type="video/mp4" res="360p" fps="30fps" vcodec="avc1.42001E" acodec="mp4a.40.2">]
183
+ ```
 
184
  You also have an interface to select streams by their itag, without needing to filter:
185
 
186
+ ```python
187
+ >>> yt.streams.get_by_itag(22)
188
+ <Stream: itag="22" mime_type="video/mp4" res="720p" fps="30fps" vcodec="avc1.64001F" acodec="mp4a.40.2">
189
+ ```
 
190
 
191
  If you need to optimize for a specific feature, such as the "highest resolution" or "lowest average bitrate":
192
 
193
+ ```python
194
+ >>> yt.streams.filter(progressive=True).order_by('resolution').desc().all()
195
+ ```
 
196
  Note that ``order_by`` cannot be used if your attribute is undefined in any of the Stream instances, so be sure to apply a filter to remove those before calling it.
197
 
198
  If your application requires post-processing logic, pytube allows you to specify an "on download complete" callback function:
199
 
200
+ ```python
201
+ >>> def convert_to_aac(stream, file_handle):
202
+ return # do work
 
 
203
 
204
+ >>> yt.register_on_complete_callback(convert_to_aac)
205
+ ```
206
 
207
  Similarly, if your application requires on-download progress logic, pytube exposes a callback for this as well:
208
 
209
+ ```python
210
+ >>> def show_progress_bar(stream, chunk, file_handle, bytes_remaining):
211
+ return # do work
 
 
 
212
 
213
+ >>> yt.register_on_progress_callback(show_progress_bar)
214
+ ```
215
 
216
+ ## Command-line interface
 
217
 
218
  pytube also ships with a tiny cli interface for downloading and probing videos.
219
 
220
  Let's start with downloading:
221
 
222
+ ```bash
223
+ $ pytube http://youtube.com/watch?v=9bZkp7q19f0 --itag=22
224
+ ```
 
225
  To view available streams:
226
 
227
+ ```bash
228
+ $ pytube http://youtube.com/watch?v=9bZkp7q19f0 --list
229
+ ```
 
230
 
231
  Finally, if you're filing a bug report, the cli contains a switch called ``--build-playback-report``, which bundles up the state, allowing others to easily replay your issue.
docs/index.rst CHANGED
@@ -57,7 +57,6 @@ Features
57
  Roadmap
58
  -------
59
 
60
- - Playlist support
61
  - Allow downloading age restricted content
62
  - Complete ffmpeg integrationn
63
 
 
57
  Roadmap
58
  -------
59
 
 
60
  - Allow downloading age restricted content
61
  - Complete ffmpeg integrationn
62
 
images/pytube.png ADDED
pytube/__init__.py CHANGED
@@ -9,7 +9,7 @@ follows best practice patterns.
9
 
10
  """
11
  __title__ = 'pytube'
12
- __version__ = '8.0.3'
13
  __author__ = 'Nick Ficano'
14
  __license__ = 'MIT License'
15
  __copyright__ = 'Copyright 2017 Nick Ficano'
@@ -19,7 +19,7 @@ from pytube.query import CaptionQuery
19
  from pytube.query import StreamQuery
20
  from pytube.streams import Stream
21
  from pytube.captions import Caption
22
- from pytube.playlist import Playlist
23
  from pytube.__main__ import YouTube
24
 
25
  logger = create_logger()
 
9
 
10
  """
11
  __title__ = 'pytube'
12
+ __version__ = '9.0.7'
13
  __author__ = 'Nick Ficano'
14
  __license__ = 'MIT License'
15
  __copyright__ = 'Copyright 2017 Nick Ficano'
 
19
  from pytube.query import StreamQuery
20
  from pytube.streams import Stream
21
  from pytube.captions import Caption
22
+ from pytube.contrib.playlist import Playlist
23
  from pytube.__main__ import YouTube
24
 
25
  logger = create_logger()
pytube/cipher.py CHANGED
@@ -190,6 +190,11 @@ def map_functions(js_func):
190
  ('{\w\.splice\(0,\w\)}', splice),
191
  # function(a,b){var c=a[0];a[0]=a[b%a.length];a[b]=c}
192
  ('{var\s\w=\w\[0\];\w\[0\]=\w\[\w\%\w.length\];\w\[\w\]=\w}', swap),
 
 
 
 
 
193
  )
194
 
195
  for pattern, fn in mapper:
 
190
  ('{\w\.splice\(0,\w\)}', splice),
191
  # function(a,b){var c=a[0];a[0]=a[b%a.length];a[b]=c}
192
  ('{var\s\w=\w\[0\];\w\[0\]=\w\[\w\%\w.length\];\w\[\w\]=\w}', swap),
193
+ # function(a,b){var c=a[0];a[0]=a[b%a.length];a[b%a.length]=c}
194
+ (
195
+ '{var\s\w=\w\[0\];\w\[0\]=\w\[\w\%\w.length\];'
196
+ '\w\[\w\%\w.length\]=\w}', swap,
197
+ ),
198
  )
199
 
200
  for pattern, fn in mapper:
requirements.txt → pytube/contrib/__init__.py RENAMED
File without changes
pytube/{playlist.py → contrib/playlist.py} RENAMED
@@ -2,15 +2,17 @@
2
  """
3
  Module to download a complete playlist from a youtube channel
4
  """
5
- from __future__ import print_function
 
6
  from pytube import request
7
  from pytube.__main__ import YouTube
8
 
 
 
9
 
10
  class Playlist(object):
11
- """
12
- Handles all the task of manipulating and downloading
13
- a whole YouTube playlist
14
  """
15
 
16
  def __init__(self, url):
@@ -18,26 +20,26 @@ class Playlist(object):
18
  self.video_urls = []
19
 
20
  def construct_playlist_url(self):
21
- """
22
- There are two kinds of playlist urls in YouTube. One that
23
  contains watch?v= in URL, another one contains the "playlist?list="
24
  portion. It is preferable to work with the later one.
 
25
  :return: playlist url -> string
26
  """
27
 
28
- if "watch?v=" in self.playlist_url:
29
- base_url = "https://www.youtube.com/playlist?list="
30
- playlist_code = self.playlist_url.split("&list=")[1]
31
  return base_url + playlist_code
32
 
33
  # url is already in the desired format, so just return it
34
  return self.playlist_url
35
 
36
  def parse_links(self):
37
- """
38
- Parse the video links from the page source, extracts and
39
  returns the /watch?v= part from video link href
40
  It's an alternative for BeautifulSoup
 
41
  :return: list
42
  """
43
 
@@ -45,52 +47,42 @@ class Playlist(object):
45
  req = request.get(url)
46
 
47
  # split the page source by line and process each line
48
- content = [x for x in req.split("\n") if "pl-video-title-link" in x]
49
- link_list = [x.split('href="', 1)[1].split("&", 1)[0] for x in content]
50
 
51
  return link_list
52
 
53
  def populate_video_urls(self):
54
- """
55
- Construct complete links of all the videos in playlist and
56
  populate video_urls list
 
57
  :return: urls -> string
58
  """
59
 
60
- base_url = "https://www.youtube.com"
61
  link_list = self.parse_links()
62
 
63
  for video_id in link_list:
64
  complete_url = base_url + video_id
65
- # print complete_url
66
  self.video_urls.append(complete_url)
67
 
68
  def download_all(self):
69
- """
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
- TODO: Add option to download resolution of user's choice
74
- :return: None
75
  """
76
 
77
  self.populate_video_urls()
78
- print("Total videos found:", len(self.video_urls))
79
- # print(self.video_urls)
80
- print("Starting download...\n")
81
 
82
  for link in self.video_urls:
83
  yt = YouTube(link)
84
 
85
- # (ISSUE #206): the try/except is done to prevent
86
- # the UnicodeEncodeError
87
- try:
88
- print("Downloading:", yt.title)
89
- except UnicodeEncodeError:
90
- print("(title cannot be shown due to unicode error)")
91
-
92
- yt.streams.filter(progressive=True,
93
- subtype="mp4").order_by(
94
- "resolution").desc().first().download()
95
 
96
- print("Download complete")
 
2
  """
3
  Module to download a complete playlist from a youtube channel
4
  """
5
+ import logging
6
+
7
  from pytube import request
8
  from pytube.__main__ import YouTube
9
 
10
+ logger = logging.getLogger(__name__)
11
+
12
 
13
  class Playlist(object):
14
+ """Handles all the task of manipulating and downloading a whole YouTube
15
+ playlist
 
16
  """
17
 
18
  def __init__(self, url):
 
20
  self.video_urls = []
21
 
22
  def construct_playlist_url(self):
23
+ """There are two kinds of playlist urls in YouTube. One that
 
24
  contains watch?v= in URL, another one contains the "playlist?list="
25
  portion. It is preferable to work with the later one.
26
+
27
  :return: playlist url -> string
28
  """
29
 
30
+ if 'watch?v=' in self.playlist_url:
31
+ base_url = 'https://www.youtube.com/playlist?list='
32
+ playlist_code = self.playlist_url.split('&list=')[1]
33
  return base_url + playlist_code
34
 
35
  # url is already in the desired format, so just return it
36
  return self.playlist_url
37
 
38
  def parse_links(self):
39
+ """Parse the video links from the page source, extracts and
 
40
  returns the /watch?v= part from video link href
41
  It's an alternative for BeautifulSoup
42
+
43
  :return: list
44
  """
45
 
 
47
  req = request.get(url)
48
 
49
  # split the page source by line and process each line
50
+ content = [x for x in req.split('\n') if 'pl-video-title-link' in x]
51
+ link_list = [x.split('href="', 1)[1].split('&', 1)[0] for x in content]
52
 
53
  return link_list
54
 
55
  def populate_video_urls(self):
56
+ """Construct complete links of all the videos in playlist and
 
57
  populate video_urls list
58
+
59
  :return: urls -> string
60
  """
61
 
62
+ base_url = 'https://www.youtube.com'
63
  link_list = self.parse_links()
64
 
65
  for video_id in link_list:
66
  complete_url = base_url + video_id
 
67
  self.video_urls.append(complete_url)
68
 
69
  def download_all(self):
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
+ yt.streams.filter(
85
+ progressive=True, subtype='mp4',
86
+ ).order_by('resolution').desc().first().download()
 
 
 
 
 
 
 
87
 
88
+ logger.debug('download complete')
pytube/helpers.py CHANGED
@@ -92,7 +92,7 @@ def safe_filename(s, max_length=255):
92
  # Characters in range 0-31 (0x00-0x1F) are not allowed in ntfs filenames.
93
  ntfs_chrs = [chr(i) for i in range(0, 31)]
94
  chrs = [
95
- '\"', '\#', '\$', '\%', '\'', '\*', '\,', '\.', '\/', '\:',
96
  '\;', '\<', '\>', '\?', '\\', '\^', '\|', '\~', '\\\\',
97
  ]
98
  pattern = '|'.join(ntfs_chrs + chrs)
 
92
  # Characters in range 0-31 (0x00-0x1F) are not allowed in ntfs filenames.
93
  ntfs_chrs = [chr(i) for i in range(0, 31)]
94
  chrs = [
95
+ '\"', '\#', '\$', '\%', '\'', '\*', '\,', '\.', '\/', '\:', '"',
96
  '\;', '\<', '\>', '\?', '\\', '\^', '\|', '\~', '\\\\',
97
  ]
98
  pattern = '|'.join(ntfs_chrs + chrs)
setup.cfg CHANGED
@@ -1,9 +1,9 @@
1
  [bumpversion]
2
  commit = True
3
  tag = True
4
- current_version = 8.0.3
5
  parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)(\-(?P<release>[a-z]+))?
6
- serialize =
7
  {major}.{minor}.{patch}
8
 
9
  [metadata]
@@ -15,5 +15,6 @@ description-file = README.md
15
 
16
  [coverage:run]
17
  source = pytube
18
- omit =
19
  pytube/compat.py
 
 
1
  [bumpversion]
2
  commit = True
3
  tag = True
4
+ current_version = 9.0.7
5
  parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)(\-(?P<release>[a-z]+))?
6
+ serialize =
7
  {major}.{minor}.{patch}
8
 
9
  [metadata]
 
15
 
16
  [coverage:run]
17
  source = pytube
18
+ omit =
19
  pytube/compat.py
20
+
setup.py CHANGED
@@ -14,10 +14,10 @@ with open('LICENSE') as readme_file:
14
 
15
  setup(
16
  name='pytube',
17
- version='8.0.3',
18
  author='Nick Ficano',
19
  author_email='[email protected]',
20
- packages=['pytube'],
21
  url='https://github.com/nficano/pytube',
22
  license=license,
23
  entry_points={
@@ -36,7 +36,6 @@ setup(
36
  'Operating System :: POSIX',
37
  'Operating System :: Unix',
38
  'Programming Language :: Python :: 2.7',
39
- 'Programming Language :: Python :: 3.3',
40
  'Programming Language :: Python :: 3.4',
41
  'Programming Language :: Python :: 3.5',
42
  'Programming Language :: Python :: 3.6',
 
14
 
15
  setup(
16
  name='pytube',
17
+ version='9.0.7',
18
  author='Nick Ficano',
19
  author_email='[email protected]',
20
+ packages=['pytube', 'pytube.contrib'],
21
  url='https://github.com/nficano/pytube',
22
  license=license,
23
  entry_points={
 
36
  'Operating System :: POSIX',
37
  'Operating System :: Unix',
38
  'Programming Language :: Python :: 2.7',
 
39
  'Programming Language :: Python :: 3.4',
40
  'Programming Language :: Python :: 3.5',
41
  'Programming Language :: Python :: 3.6',
tests/requirements.txt DELETED
@@ -1,10 +0,0 @@
1
- bumpversion==0.5.3
2
- coveralls==1.0
3
- flake8==3.3.0
4
- mock==1.3.0
5
- pre-commit==0.15.0
6
- pytest==3.1.3
7
- pytest-cov==2.4.0
8
- pytest-mock==1.6.3
9
- sphinx==1.6.4
10
- sphinx-rtd-theme==0.2.4
 
 
 
 
 
 
 
 
 
 
 
tests/test_playlist.py CHANGED
@@ -1,43 +1,51 @@
1
  # -*- coding: utf-8 -*-
2
- from pytube import playlist
3
 
4
 
5
  def test_construct():
6
- ob = playlist.Playlist(
7
- "https://www.youtube.com/watch?v=m5q2GCsteQs&list="
8
- "PL525f8ds9RvsXDl44X6Wwh9t3fCzFNApw")
9
- expected = "https://www.youtube.com/" \
10
- "playlist?list=" \
11
- "PL525f8ds9RvsXDl44X6Wwh9t3fCzFNApw"
 
12
 
13
  assert ob.construct_playlist_url() == expected
14
 
15
 
16
  def test_link_parse():
17
- ob = playlist.Playlist(
18
- "https://www.youtube.com/watch?v=m5q2GCsteQs&list="
19
- "PL525f8ds9RvsXDl44X6Wwh9t3fCzFNApw")
20
-
21
- expected = ["/watch?v=m5q2GCsteQs",
22
- "/watch?v=5YK63cXyJ2Q",
23
- "/watch?v=Rzt4rUPFYD4"]
 
 
 
24
  assert ob.parse_links() == expected
25
 
26
 
27
  def test_populate():
28
- ob = playlist.Playlist(
29
- "https://www.youtube.com/watch?v=m5q2GCsteQs&list="
30
- "PL525f8ds9RvsXDl44X6Wwh9t3fCzFNApw")
31
- expected = ["https://www.youtube.com/watch?v=m5q2GCsteQs",
32
- "https://www.youtube.com/watch?v=5YK63cXyJ2Q",
33
- "https://www.youtube.com/watch?v=Rzt4rUPFYD4"]
 
 
 
34
 
35
  ob.populate_video_urls()
36
  assert ob.video_urls == expected
37
 
38
 
39
  def test_download():
40
- ob = playlist.Playlist(
41
- "https://www.youtube.com/watch?v=lByG_AgKS9k&list="
42
- "PL525f8ds9RvuerPZ3bZygmNiYw2sP4BDk")
 
43
  ob.download_all()
 
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'
13
 
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',
39
+ 'https://www.youtube.com/watch?v=Rzt4rUPFYD4',
40
+ ]
41
 
42
  ob.populate_video_urls()
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()