Skip to content

Commit 9949bb0

Browse files
committed
v1
0 parents  commit 9949bb0

File tree

4 files changed

+117
-0
lines changed

4 files changed

+117
-0
lines changed

.github/FUNDING.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
# These are supported funding model platforms
2+
3+
github: p0dalirius
4+
patreon: Podalirius

.github/streamable.png

301 KB
Loading

README.md

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# streamableDownloader
2+
3+
## Streamable
4+
5+
6+
7+
![](./.github/streamable.png)
8+
9+
## The technique
10+
11+
Extract the video URLs from the meta tags for opengraph:
12+
13+
```html
14+
<meta property="og:url" content="https://streamable.com/abcdef" />
15+
<meta property="og:video" content="https://cdn-cf-east.streamable.com/video/mp4/abcdef.mp4?Expires=1642074961&Signature=NrOSgEjyOX51sLshgTMtDEShsC97cusGrQKr87hRbJe8NNP8gobxxmqgxaFTakaM5xK6Ykw8K32DLLTbJHO9A5KeGJG2mFvjbYfVPAp07qSd93g6LsesEmqWmnEZHH7MRyAYhq4cYWtQRekFdnsn0JtWvMoAMWI4IUG3nMrkb47tsSYY5XtfYN5KzaTAzh4UrgsyzDVofCVqGYxXR1KpU35hQFtiRan5i0GfFDXfv5YqJ1davybrY3Eygcpk7WJBA6yMtv5uuN6GbWRWvsyVypXFo2kw8NNUbheGgXXHLISaQqbYowMY5NGaX3O1G6uQ7htctIIcDXw13NDggXk4CL__&Key-Pair-Id=WXADY4C7RJIBPIOFRBWM">
16+
<meta property="og:video:url" content="https://cdn-cf-east.streamable.com/video/mp4/abcdef.mp4?Expires=1642074961&Signature=NrOSgEjyOX51sLshgTMtDEShsC97cusGrQKr87hRbJe8NNP8gobxxmqgxaFTakaM5xK6Ykw8K32DLLTbJHO9A5KeGJG2mFvjbYfVPAp07qSd93g6LsesEmqWmnEZHH7MRyAYhq4cYWtQRekFdnsn0JtWvMoAMWI4IUG3nMrkb47tsSYY5XtfYN5KzaTAzh4UrgsyzDVofCVqGYxXR1KpU35hQFtiRan5i0GfFDXfv5YqJ1davybrY3Eygcpk7WJBA6yMtv5uuN6GbWRWvsyVypXFo2kw8NNUbheGgXXHLISaQqbYowMY5NGaX3O1G6uQ7htctIIcDXw13NDggXk4CL__&Key-Pair-Id=WXADY4C7RJIBPIOFRBWM">
17+
<meta property="og:video:secure_url" content="https://cdn-cf-east.streamable.com/video/mp4/abcdef.mp4?Expires=1642074961&Signature=NrOSgEjyOX51sLshgTMtDEShsC97cusGrQKr87hRbJe8NNP8gobxxmqgxaFTakaM5xK6Ykw8K32DLLTbJHO9A5KeGJG2mFvjbYfVPAp07qSd93g6LsesEmqWmnEZHH7MRyAYhq4cYWtQRekFdnsn0JtWvMoAMWI4IUG3nMrkb47tsSYY5XtfYN5KzaTAzh4UrgsyzDVofCVqGYxXR1KpU35hQFtiRan5i0GfFDXfv5YqJ1davybrY3Eygcpk7WJBA6yMtv5uuN6GbWRWvsyVypXFo2kw8NNUbheGgXXHLISaQqbYowMY5NGaX3O1G6uQ7htctIIcDXw13NDggXk4CL__&Key-Pair-Id=WXADY4C7RJIBPIOFRBWM">
18+
```
19+
20+
## Example
21+
22+
```
23+
bash$ ./streamableDownloader.py -u https://streamable.com/abcdef -o video.mp4
24+
[>] Downloading to video.mp4 ...
25+
[>] Downloaded 16.78 MB to video.mp4 ...
26+
```
27+
28+
## Contributing
29+
30+
Pull requests are welcome. Feel free to open an issue if you want to add other features.

streamableDownloader.py

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
#!/usr/bin/env python3
2+
# -*- coding: utf-8 -*-
3+
# File name : streamableDownloader.py
4+
# Author : Podalirius (@podalirius_)
5+
# Date created : 13 Jan 2022
6+
7+
import os
8+
import requests
9+
from bs4 import BeautifulSoup
10+
import argparse
11+
12+
13+
def b_filesize(size):
14+
units = ['B', 'kB', 'MB', 'GB', 'TB', 'PB']
15+
k = 0
16+
for k in range(len(units)):
17+
if size < (1024 ** (k + 1)):
18+
break
19+
return "%4.2f %s" % (round(size / (1024 ** (k)), 2), units[k])
20+
21+
22+
def parseArgs():
23+
parser = argparse.ArgumentParser(description="Description message")
24+
parser.add_argument("-u", "--url", default=None, required=True, help='Streamable URL')
25+
parser.add_argument("-o", "--output-file", default=None, required=False, type=str, help='Output file')
26+
parser.add_argument("-v", "--verbose", default=False, action="store_true", help='Verbose mode. (default: False)')
27+
return parser.parse_args()
28+
29+
30+
if __name__ == '__main__':
31+
options = parseArgs()
32+
33+
r = requests.get(options.url)
34+
soup = BeautifulSoup(r.content, "lxml")
35+
36+
sources = {}
37+
38+
meta = soup.find('meta', attrs={"property": "og:video"})
39+
if meta is not None:
40+
sources["og_video"] = meta['content']
41+
42+
meta = soup.find('meta', attrs={"property": "og:video:url"})
43+
if meta is not None:
44+
sources["og_video_url"] = meta['content']
45+
46+
meta = soup.find('meta', attrs={"property": "og:video:secure_url"})
47+
if meta is not None:
48+
sources["og_video_secure_url"] = meta['content']
49+
50+
if options.verbose:
51+
print("[>] Extracted video urls:")
52+
if "og_video" in sources.keys():
53+
print(" - og_video: %s" % sources["og_video"])
54+
if "og_video_url" in sources.keys():
55+
print(" - og_video_url: %s" % sources["og_video_url"])
56+
if "og_video_secure_url" in sources.keys():
57+
print(" - og_video_secure_url: %s" % sources["og_video_secure_url"])
58+
59+
for source_name in sources.keys():
60+
url = sources[source_name]
61+
r = requests.head(url)
62+
if r.headers['Content-Type'] in ['video/mp4']:
63+
64+
total_size = 0
65+
if 'Content-Length' in r.headers.keys():
66+
total_size = str(r.headers['Content-Length'])
67+
68+
if options.output_file is not None:
69+
filename = options.output_file
70+
else:
71+
filename = os.path.basename(url).split("?")[0]
72+
73+
print("[>] Downloading to %s ..." % os.path.basename(filename))
74+
total_size = 0
75+
with requests.get(url, stream=True) as r:
76+
with open(filename, 'wb') as f:
77+
for chunk in r.iter_content(chunk_size=16 * 1024):
78+
f.write(chunk)
79+
total_size += len(chunk)
80+
print("[>] Downloaded %s to %s ..." % (b_filesize(total_size), os.path.basename(filename)))
81+
break
82+
else:
83+
print("Unknown Content-Type %s for source '%s', skipping" % (r.headers['Content-Type'], source_name))

0 commit comments

Comments
 (0)