ahmedsadman commited on
Commit
aac6e8d
·
unverified ·
2 Parent(s): 9b23455 3e8ab2b

Merge pull request #1 from nficano/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.md ADDED
@@ -0,0 +1,231 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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.
22
+
23
+ *pytube* also makes pipelining easy, allowing you to specify callback functions for different download events, such as ``on progress`` or ``on complete``.
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
44
+ - Command-line Interfaced Included
45
+ - Caption Track Support
46
+ - Outputs Caption Tracks to .srt format (SubRip Subtitle)
47
+ - Ability to Capture Thumbnail URL.
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.
100
+
101
+ The legacy streams that contain the audio and video in a single file (referred to as "progressive download") are still available, but only for resolutions 720p and below.
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.
README.rst DELETED
@@ -1,245 +0,0 @@
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.
33
-
34
- *pytube* also makes pipelining easy, allowing you to specify callback functions for different download events, such as ``on progress`` or ``on complete``.
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
- - Easily Register ``on_download_progress`` & ``on_download_complete`` callbacks
54
- - Command-line Interfaced Included
55
- - Caption Track Support
56
- - Outputs Caption Tracks to .srt format (SubRip Subtitle)
57
- - Ability to Capture Thumbnail URL.
58
- - Extensively Documented Source Code
59
- - No Third-Party Dependencies
60
-
61
- Installation
62
- ------------
63
-
64
- Download using pip via pypi.
65
-
66
- .. code-block:: bash
67
-
68
- pip install pytube
69
-
70
- Getting started
71
- ---------------
72
-
73
- Let's begin with showing how easy it is to download a video with pytube:
74
-
75
- .. code-block:: python
76
-
77
- >>> from pytube import YouTube
78
- >>> YouTube('http://youtube.com/watch?v=9bZkp7q19f0').streams.first().download()
79
-
80
- This example will download the highest quality progressive download stream available.
81
-
82
- Next, let's explore how we would view what video streams are available:
83
-
84
- .. code-block:: python
85
-
86
- >>> yt = YouTube('http://youtube.com/watch?v=9bZkp7q19f0')
87
- >>> yt.streams.all()
88
- [<Stream: itag="22" mime_type="video/mp4" res="720p" fps="30fps" vcodec="avc1.64001F" acodec="mp4a.40.2">,
89
- <Stream: itag="43" mime_type="video/webm" res="360p" fps="30fps" vcodec="vp8.0" acodec="vorbis">,
90
- <Stream: itag="18" mime_type="video/mp4" res="360p" fps="30fps" vcodec="avc1.42001E" acodec="mp4a.40.2">,
91
- <Stream: itag="36" mime_type="video/3gpp" res="240p" fps="30fps" vcodec="mp4v.20.3" acodec="mp4a.40.2">,
92
- <Stream: itag="17" mime_type="video/3gpp" res="144p" fps="30fps" vcodec="mp4v.20.3" acodec="mp4a.40.2">,
93
- <Stream: itag="137" mime_type="video/mp4" res="1080p" fps="30fps" vcodec="avc1.640028">,
94
- <Stream: itag="248" mime_type="video/webm" res="1080p" fps="30fps" vcodec="vp9">,
95
- <Stream: itag="136" mime_type="video/mp4" res="720p" fps="30fps" vcodec="avc1.4d401f">,
96
- <Stream: itag="247" mime_type="video/webm" res="720p" fps="30fps" vcodec="vp9">,
97
- <Stream: itag="135" mime_type="video/mp4" res="480p" fps="30fps" vcodec="avc1.4d401e">,
98
- <Stream: itag="244" mime_type="video/webm" res="480p" fps="30fps" vcodec="vp9">,
99
- <Stream: itag="134" mime_type="video/mp4" res="360p" fps="30fps" vcodec="avc1.4d401e">,
100
- <Stream: itag="243" mime_type="video/webm" res="360p" fps="30fps" vcodec="vp9">,
101
- <Stream: itag="133" mime_type="video/mp4" res="240p" fps="30fps" vcodec="avc1.4d4015">,
102
- <Stream: itag="242" mime_type="video/webm" res="240p" fps="30fps" vcodec="vp9">,
103
- <Stream: itag="160" mime_type="video/mp4" res="144p" fps="30fps" vcodec="avc1.4d400c">,
104
- <Stream: itag="278" mime_type="video/webm" res="144p" fps="30fps" vcodec="vp9">,
105
- <Stream: itag="140" mime_type="audio/mp4" abr="128kbps" acodec="mp4a.40.2">,
106
- <Stream: itag="171" mime_type="audio/webm" abr="128kbps" acodec="vorbis">,
107
- <Stream: itag="249" mime_type="audio/webm" abr="50kbps" acodec="opus">,
108
- <Stream: itag="250" mime_type="audio/webm" abr="70kbps" acodec="opus">,
109
- <Stream: itag="251" mime_type="audio/webm" abr="160kbps" acodec="opus">]
110
-
111
- 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).
112
-
113
- 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.
114
-
115
- The legacy streams that contain the audio and video in a single file (referred to as "progressive download") are still available, but only for resolutions 720p and below.
116
-
117
- To only view these progressive download streams:
118
-
119
- .. code-block:: python
120
-
121
- >>> yt.streams.filter(progressive=True).all()
122
- [<Stream: itag="22" mime_type="video/mp4" res="720p" fps="30fps" vcodec="avc1.64001F" acodec="mp4a.40.2">,
123
- <Stream: itag="43" mime_type="video/webm" res="360p" fps="30fps" vcodec="vp8.0" acodec="vorbis">,
124
- <Stream: itag="18" mime_type="video/mp4" res="360p" fps="30fps" vcodec="avc1.42001E" acodec="mp4a.40.2">,
125
- <Stream: itag="36" mime_type="video/3gpp" res="240p" fps="30fps" vcodec="mp4v.20.3" acodec="mp4a.40.2">,
126
- <Stream: itag="17" mime_type="video/3gpp" res="144p" fps="30fps" vcodec="mp4v.20.3" acodec="mp4a.40.2">]
127
-
128
- Conversely, if you only want to see the DASH streams (also referred to as "adaptive") you can do:
129
-
130
- .. code-block:: python
131
-
132
- >>> yt.streams.filter(adaptive=True).all()
133
- [<Stream: itag="137" mime_type="video/mp4" res="1080p" fps="30fps" vcodec="avc1.640028">,
134
- <Stream: itag="248" mime_type="video/webm" res="1080p" fps="30fps" vcodec="vp9">,
135
- <Stream: itag="136" mime_type="video/mp4" res="720p" fps="30fps" vcodec="avc1.4d401f">,
136
- <Stream: itag="247" mime_type="video/webm" res="720p" fps="30fps" vcodec="vp9">,
137
- <Stream: itag="135" mime_type="video/mp4" res="480p" fps="30fps" vcodec="avc1.4d401e">,
138
- <Stream: itag="244" mime_type="video/webm" res="480p" fps="30fps" vcodec="vp9">,
139
- <Stream: itag="134" mime_type="video/mp4" res="360p" fps="30fps" vcodec="avc1.4d401e">,
140
- <Stream: itag="243" mime_type="video/webm" res="360p" fps="30fps" vcodec="vp9">,
141
- <Stream: itag="133" mime_type="video/mp4" res="240p" fps="30fps" vcodec="avc1.4d4015">,
142
- <Stream: itag="242" mime_type="video/webm" res="240p" fps="30fps" vcodec="vp9">,
143
- <Stream: itag="160" mime_type="video/mp4" res="144p" fps="30fps" vcodec="avc1.4d400c">,
144
- <Stream: itag="278" mime_type="video/webm" res="144p" fps="30fps" vcodec="vp9">,
145
- <Stream: itag="140" mime_type="audio/mp4" abr="128kbps" acodec="mp4a.40.2">,
146
- <Stream: itag="171" mime_type="audio/webm" abr="128kbps" acodec="vorbis">,
147
- <Stream: itag="249" mime_type="audio/webm" abr="50kbps" acodec="opus">,
148
- <Stream: itag="250" mime_type="audio/webm" abr="70kbps" acodec="opus">,
149
- <Stream: itag="251" mime_type="audio/webm" abr="160kbps" acodec="opus">]
150
-
151
-
152
- 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.
153
-
154
- To list the audio only streams:
155
-
156
- .. code-block:: python
157
-
158
- >>> yt.streams.filter(only_audio=True).all()
159
- [<Stream: itag="140" mime_type="audio/mp4" abr="128kbps" acodec="mp4a.40.2">,
160
- <Stream: itag="171" mime_type="audio/webm" abr="128kbps" acodec="vorbis">,
161
- <Stream: itag="249" mime_type="audio/webm" abr="50kbps" acodec="opus">,
162
- <Stream: itag="250" mime_type="audio/webm" abr="70kbps" acodec="opus">,
163
- <Stream: itag="251" mime_type="audio/webm" abr="160kbps" acodec="opus">]
164
-
165
-
166
- To list only ``mp4`` streams:
167
-
168
- .. code-block:: python
169
-
170
- >>> yt.streams.filter(subtype='mp4').all()
171
- [<Stream: itag="22" mime_type="video/mp4" res="720p" fps="30fps" vcodec="avc1.64001F" acodec="mp4a.40.2">,
172
- <Stream: itag="18" mime_type="video/mp4" res="360p" fps="30fps" vcodec="avc1.42001E" acodec="mp4a.40.2">,
173
- <Stream: itag="137" mime_type="video/mp4" res="1080p" fps="30fps" vcodec="avc1.640028">,
174
- <Stream: itag="136" mime_type="video/mp4" res="720p" fps="30fps" vcodec="avc1.4d401f">,
175
- <Stream: itag="135" mime_type="video/mp4" res="480p" fps="30fps" vcodec="avc1.4d401e">,
176
- <Stream: itag="134" mime_type="video/mp4" res="360p" fps="30fps" vcodec="avc1.4d401e">,
177
- <Stream: itag="133" mime_type="video/mp4" res="240p" fps="30fps" vcodec="avc1.4d4015">,
178
- <Stream: itag="160" mime_type="video/mp4" res="144p" fps="30fps" vcodec="avc1.4d400c">,
179
- <Stream: itag="140" mime_type="audio/mp4" abr="128kbps" acodec="mp4a.40.2">]
180
-
181
-
182
- Multiple filters can also be specified:
183
-
184
- .. code-block:: python
185
-
186
- >>> yt.streams.filter(subtype='mp4', progressive=True).all()
187
- >>> # this can also be expressed as:
188
- >>> yt.streams.filter(subtype='mp4').filter(progressive=True).all()
189
- [<Stream: itag="22" mime_type="video/mp4" res="720p" fps="30fps" vcodec="avc1.64001F" acodec="mp4a.40.2">,
190
- <Stream: itag="18" mime_type="video/mp4" res="360p" fps="30fps" vcodec="avc1.42001E" acodec="mp4a.40.2">]
191
-
192
- You also have an interface to select streams by their itag, without needing to filter:
193
-
194
- .. code-block:: python
195
-
196
- >>> yt.streams.get_by_itag(22)
197
- <Stream: itag="22" mime_type="video/mp4" res="720p" fps="30fps" vcodec="avc1.64001F" acodec="mp4a.40.2">
198
-
199
-
200
- If you need to optimize for a specific feature, such as the "highest resolution" or "lowest average bitrate":
201
-
202
- .. code-block:: python
203
-
204
- >>> yt.streams.filter(progressive=True).order_by('resolution').desc().all()
205
-
206
- 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.
207
-
208
- If your application requires post-processing logic, pytube allows you to specify an "on download complete" callback function:
209
-
210
- .. code-block:: python
211
-
212
- >>> def convert_to_aac(stream, file_handle):
213
- # do work
214
- >>> yt.register_on_complete_callback(convert_to_aac)
215
-
216
-
217
- Similarly, if your application requires on-download progress logic, pytube exposes a callback for this as well:
218
-
219
- .. code-block:: python
220
-
221
- >>> def show_progress_bar(stream, chunk, file_handle, bytes_remaining):
222
- # do work
223
- >>> yt.register_on_progress_callback(show_progress_bar)
224
-
225
-
226
-
227
- Command-line interface
228
- ======================
229
-
230
- pytube also ships with a tiny cli interface for downloading and probing videos.
231
-
232
- Let's start with downloading:
233
-
234
- .. code-block:: bash
235
-
236
- pytube http://youtube.com/watch?v=9bZkp7q19f0 --itag=22
237
-
238
- To view available streams:
239
-
240
- .. code-block:: bash
241
-
242
- pytube http://youtube.com/watch?v=9bZkp7q19f0 --list
243
-
244
-
245
- 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,6 +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.__main__ import YouTube
23
 
24
  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/contrib/playlist.py ADDED
@@ -0,0 +1,88 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # -*- coding: utf-8 -*-
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):
19
+ self.playlist_url = 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
+
46
+ url = self.construct_playlist_url()
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 ADDED
@@ -0,0 +1,51 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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()