Skip to content

Commit ac35d81

Browse files
deprecate cloud dependencies, bug fixes, and other minor improvements
1 parent 1e25b60 commit ac35d81

9 files changed

Lines changed: 269 additions & 80 deletions

File tree

README.md

Lines changed: 45 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@
88
## Overview
99
This package uses the **[FFmpeg](https://ffmpeg.org)** to package media content for online streaming such as DASH and HLS. You can also use **[DRM](https://en.wikipedia.org/wiki/Digital_rights_management)** for HLS packaging. There are several options to open a file from clouds and save files to them as well.
1010

11+
- The best way to learn how to use this library is to review ****[the examples](https://github.com/aminyazdanpanah/python-ffmpeg-video-streaming/tree/master/examples)**** and browse the source code.
12+
- Cloud dependencies are deprecated now and will be removed in a future release. For using clouds such as **[Amazon S3](https://aws.amazon.com/s3)**, **[Google Cloud Storage](https://console.cloud.google.com/storage)**, **[Microsoft Azure Storage](https://azure.microsoft.com/en-us/features/storage-explorer/)**, and a custom cloud, please visit **[this page](https://video.aminyazdanpanah.com/python/start/clouds)**
13+
1114
**Contents**
1215
- [Requirements](#requirements)
1316
- [Installation](#installation)
@@ -36,7 +39,6 @@ pip install python-ffmpeg-video-streaming
3639
```
3740

3841
## Quickstart
39-
The best way to learn how to use this library is to review ****[the examples](https://github.com/aminyazdanpanah/python-ffmpeg-video-streaming/tree/master/examples)**** and browse the source code.
4042

4143
### opening a file
4244
There are two ways to open a file:
@@ -46,13 +48,14 @@ video = '/var/www/media/videos/video.mp4'
4648
```
4749

4850
#### 2. From Clouds
49-
You can open a file from a cloud by passing a tuple of cloud configuration to the method. There are some options to open a file from **[Amazon Web Services (AWS)](https://aws.amazon.com/)**, **[Google Cloud Storage](https://console.cloud.google.com/storage)**, **[Microsoft Azure Storage](https://azure.microsoft.com/en-us/features/storage-explorer/)**, and a custom cloud.
51+
You can open a file from a cloud by passing a tuple of cloud configuration to the method.
52+
53+
In **[this page](https://video.aminyazdanpanah.com/python/start/clouds?r=open)**, you will find some examples of opening a file from **[Amazon S3](https://aws.amazon.com/s3)**, **[Google Cloud Storage](https://console.cloud.google.com/storage)**, **[Microsoft Azure Storage](https://azure.microsoft.com/en-us/features/storage-explorer/)**, and a custom cloud.
5054

5155
```python
5256
video = (google_cloud, download_options, None)
5357
```
5458

55-
Please visit **[this page](https://video.aminyazdanpanah.com/python/start/open-clouds)** to see more examples and usage of these clouds.
5659

5760
### DASH
5861
**[Dynamic Adaptive Streaming over HTTP (DASH)](https://dashif.org/)**, also known as MPEG-DASH, is an adaptive bitrate streaming technique that enables high quality streaming of media content over the Internet delivered from conventional HTTP web servers.
@@ -76,19 +79,20 @@ You can also create representations manually:
7679
import ffmpeg_streaming
7780
from ffmpeg_streaming import Representation
7881

79-
rep_144 = Representation(width=256, height=144, kilo_bitrate=95)
80-
rep_240 = Representation(width=426, height=240, kilo_bitrate=150)
81-
rep_360 = Representation(width=640, height=360, kilo_bitrate=276)
82-
rep_480 = Representation(width=854, height=480, kilo_bitrate=750)
83-
rep_720 = Representation(width=1280, height=720, kilo_bitrate=2048)
84-
rep_1080 = Representation(width=1920, height=1080, kilo_bitrate=4096)
85-
rep_1440 = Representation(width=2560, height=1440, kilo_bitrate=6096)
82+
r_144p = Representation(width=256, height=144, kilo_bitrate=95)
83+
r_240p = Representation(width=426, height=240, kilo_bitrate=150)
84+
r_360p = Representation(width=640, height=360, kilo_bitrate=276)
85+
r_480p = Representation(width=854, height=480, kilo_bitrate=750)
86+
r_720p = Representation(width=1280, height=720, kilo_bitrate=2048)
87+
r_1080p = Representation(width=1920, height=1080, kilo_bitrate=4096)
88+
r_2k = Representation(width=2560, height=1440, kilo_bitrate=6144‬)
89+
r_4k = Representation(width=3840, height=2160, kilo_bitrate=17408)
8690

8791
(
8892
ffmpeg_streaming
8993
.dash(video, adaption='"id=0,streams=v id=1,streams=a"')
9094
.format('libx265')
91-
.add_rep(rep_144, rep_240, rep_360, rep_480, rep_720, rep_1080, rep_1440)
95+
.add_rep(r_144p, r_240p, r_360p, r_480p, r_720p, r_1080p, r_2k, r_4k)
9296
.package('/var/www/media/videos/dash/dash-stream.mpd')
9397
)
9498

@@ -120,15 +124,15 @@ You can also create representations manually:
120124
import ffmpeg_streaming
121125
from ffmpeg_streaming import Representation
122126

123-
rep_360 = Representation(width=640, height=360, kilo_bitrate=276)
124-
rep_480 = Representation(width=854, height=480, kilo_bitrate=750)
125-
rep_720 = Representation(width=1280, height=720, kilo_bitrate=2048)
127+
r_360p = Representation(width=640, height=360, kilo_bitrate=276)
128+
r_480p = Representation(width=854, height=480, kilo_bitrate=750)
129+
r_720p = Representation(width=1280, height=720, kilo_bitrate=2048)
126130

127131
(
128132
ffmpeg_streaming
129133
.hls(video, hls_time=10, hls_allow_cache=1)
130134
.format('libx264')
131-
.add_rep(rep_360, rep_480, rep_720)
135+
.add_rep(r_360p, r_480p, r_720p)
132136
.package('/var/www/media/videos/hls/hls-stream.m3u8')
133137
)
134138
```
@@ -177,7 +181,7 @@ def progress(percentage, ffmpeg):
177181
)
178182
```
179183
Output of the progress:
180-
![progress](https://github.com/aminyazdanpanah/python-ffmpeg-video-streaming/blob/master/docs/progress.gif?raw=true "progress" )
184+
![transcoding](https://github.com/aminyazdanpanah/aminyazdanpanah.github.io/blob/master/video-streaming/transcoding.gif?raw=true "transcoding" )
181185

182186
### Saving Files
183187
There are several options to save your files.
@@ -206,7 +210,9 @@ It can also be null. The default path to save files is the input path.
206210
**NOTE:** If you open a file from cloud and did not pass a path to save a file, you will have to pass a local path to the `package` method.
207211

208212
#### 2. To Clouds
209-
You can save your files to clouds by passing a array of cloud configuration to the `package` method. There are some options to save files to **[Amazon Web Services (AWS)](https://aws.amazon.com/)**, **[Google Cloud Storage](https://console.cloud.google.com/storage)**, **[Microsoft Azure Storage](https://azure.microsoft.com/en-us/features/storage-explorer/)**, and a custom cloud.
213+
You can save your files to clouds by passing a array of cloud configuration to the `package` method.
214+
215+
In **[this page](https://video.aminyazdanpanah.com/python/start/clouds?r=save)**, you will find some examples of saving files to **[Amazon S3](https://aws.amazon.com/s3)**, **[Google Cloud Storage](https://console.cloud.google.com/storage)**, **[Microsoft Azure Storage](https://azure.microsoft.com/en-us/features/storage-explorer/)**, and a custom cloud.
210216

211217
```python
212218
(
@@ -230,7 +236,6 @@ A path can also be passed to save a copy of files on your local machine.
230236
)
231237
```
232238

233-
Please visit **[this page](https://video.aminyazdanpanah.com/python/start/save-clouds)** to see more examples and usage of these clouds.
234239

235240
**NOTE:** You can open a file from your local machine(or a cloud) and save files to a local path or a cloud(or multiple clouds) or both.
236241

@@ -250,21 +255,30 @@ See the **[example](https://github.com/aminyazdanpanah/python-ffmpeg-video-strea
250255
## Several Open Source Players
251256
You can use these libraries to play your streams.
252257
- **WEB**
253-
- DASH and HLS: **[video.js](https://github.com/videojs/video.js)**
254-
- DASH and HLS: **[DPlayer](https://github.com/MoePlayer/DPlayer)**
255-
- DASH and HLS: **[Plyr](https://github.com/sampotts/plyr)**
256-
- DASH and HLS: **[MediaElement.js](https://github.com/mediaelement/mediaelement)**
257-
- DASH and HLS: **[Clappr](https://github.com/clappr/clappr)**
258-
- DASH and HLS: **[Flowplayer](https://github.com/flowplayer/flowplayer)**
259-
- DASH and HLS: **[Shaka Player](https://github.com/google/shaka-player)**
260-
- DASH and HLS: **[videojs-http-streaming (VHS)](https://github.com/videojs/http-streaming)**
261-
- DASH: **[dash.js](https://github.com/Dash-Industry-Forum/dash.js)**
262-
- HLS: **[hls.js](https://github.com/video-dev/hls.js)**
258+
- DASH and HLS:
259+
- **[Shaka Player](https://github.com/google/shaka-player)**
260+
- **[Flowplayer](https://github.com/flowplayer/flowplayer)**
261+
- **[videojs-http-streaming (VHS)](https://github.com/videojs/http-streaming)**
262+
- **[MediaElement.js](https://github.com/mediaelement/mediaelement)**
263+
- **[DPlayer](https://github.com/MoePlayer/DPlayer)**
264+
- **[Clappr](https://github.com/clappr/clappr)**
265+
- **[Plyr](https://github.com/sampotts/plyr)**
266+
- DASH:
267+
- **[dash.js](https://github.com/Dash-Industry-Forum/dash.js)**
268+
- HLS:
269+
- **[hls.js](https://github.com/video-dev/hls.js)**
263270
- **Android**
264-
- DASH and HLS: **[ExoPlayer](https://github.com/google/ExoPlayer)**
271+
- DASH and HLS:
272+
- **[ExoPlayer](https://github.com/google/ExoPlayer)**
273+
- **IOS**
274+
- DASH:
275+
- **[MPEGDASH-iOS-Player](https://github.com/MPEGDASHPlayer/MPEGDASH-iOS-Player)**
276+
- HLS:
277+
- **[Player](https://github.com/piemonte/Player)**
265278
- **Windows, Linux, and macOS**
266-
- DASH and HLS: **[VLC media player](https://github.com/videolan/vlc)**
267-
279+
- DASH and HLS:
280+
- **[FFmpeg(ffplay)](https://github.com/FFmpeg/FFmpeg)**
281+
- **[VLC media player](https://github.com/videolan/vlc)**
268282

269283
**NOTE:** You should pass a manifest of stream(e.g. `https://www.aminyazdanpanah.com/PATH_TO_STREAM_DIRECTORY/dash-stream.mpd` or `/PATH_TO_STREAM_DIRECTORY/hls-stream.m3u8` ) to these players.
270284

docs/progress.gif

-47.4 KB
Binary file not shown.

examples/clouds/aws_cloud.py

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,51 @@
1414
import argparse
1515
import datetime
1616
import sys
17+
import tempfile
1718
import time
19+
from os import listdir
20+
from os.path import isfile, join
21+
22+
import boto3
23+
import botocore
1824

1925
import ffmpeg_streaming
20-
from ffmpeg_streaming import AWS
26+
from ffmpeg_streaming import Clouds
27+
28+
29+
class AWS(Clouds):
30+
def __init__(self, **options):
31+
self.s3 = boto3.client('s3', options)
32+
33+
def upload_directory(self, directory, **options):
34+
bucket_name = options.pop('bucket_name', None)
35+
if bucket_name is None:
36+
raise ValueError('You should pass a bucket name')
37+
38+
files = [f for f in listdir(directory) if isfile(join(directory, f))]
39+
for file in files:
40+
full_path_file = directory + file
41+
self.s3.upload_file(full_path_file, bucket_name, file)
42+
43+
def download(self, filename=None, **options):
44+
if filename is None:
45+
tmp = tempfile.NamedTemporaryFile(suffix='_py_ff_vi_st.tmp', delete=False)
46+
filename = tmp.name
47+
48+
bucket_name = options.pop('bucket_name', None)
49+
key = options.pop('key', None)
50+
if bucket_name is None or key is None:
51+
raise ValueError('You should pass a bucket name and a key')
52+
53+
try:
54+
self.s3.Bucket(bucket_name).download_file(key, filename)
55+
except botocore.exceptions.ClientError as e:
56+
if e.response['Error']['Code'] == "404":
57+
raise RuntimeError("The object does not exist.")
58+
else:
59+
raise RuntimeError("Could not connect to the server")
60+
61+
return filename
2162

2263

2364
def aws_cloud(bucket_name, key):

examples/clouds/azure_cloud.py

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,39 @@
1414
import argparse
1515
import datetime
1616
import sys
17+
import tempfile
1718
import time
19+
from os import listdir
20+
from os.path import isfile, join
21+
22+
from azure.storage.blob import BlockBlobService
1823

1924
import ffmpeg_streaming
20-
from ffmpeg_streaming import MicrosoftAzure
25+
from ffmpeg_streaming import Clouds
26+
27+
28+
class MicrosoftAzure(Clouds):
29+
def __init__(self, **options):
30+
self.block_blob_service = BlockBlobService(**options)
31+
32+
def upload_directory(self, directory, **options):
33+
files = [f for f in listdir(directory) if isfile(join(directory, f))]
34+
container = options.pop('container', None)
35+
for file in files:
36+
full_path_file = directory + file
37+
self.block_blob_service.create_blob_from_path(container, file, full_path_file)
38+
39+
def download(self, filename=None, **options):
40+
if filename is None:
41+
tmp = tempfile.NamedTemporaryFile(suffix='_py_ff_vi_st.tmp', delete=False)
42+
filename = tmp.name
43+
44+
container = options.pop('container', None)
45+
blob = options.pop('blob', None)
46+
47+
self.block_blob_service.get_blob_to_path(container, blob, filename)
48+
49+
return filename
2150

2251

2352
def azure_cloud(container, blob):

examples/clouds/cloud.py

Lines changed: 62 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,71 @@
1111
:license: MIT, see LICENSE for more details.
1212
"""
1313
import datetime
14+
import socket
1415
import sys
16+
import tempfile
1517
import time
18+
from os import listdir
19+
from os.path import isfile, join
20+
21+
import requests
1622

1723
import ffmpeg_streaming
18-
from ffmpeg_streaming import Cloud
24+
from ffmpeg_streaming import Clouds
25+
26+
27+
class Cloud(Clouds):
28+
def upload_directory(self, directory, **options):
29+
field_name = options.pop('field_name', None)
30+
31+
if field_name is None:
32+
raise ValueError('You should specify a field_name')
33+
34+
method = options.pop('method', 'post')
35+
url = options.pop('url', None)
36+
upload_files = []
37+
38+
files = [f for f in listdir(directory) if isfile(join(directory, f))]
39+
40+
for file in files:
41+
full_path_file = directory + file
42+
upload_files.append((field_name, open(full_path_file, 'rb')))
43+
44+
r = requests.request(method, url, files=upload_files, **options)
45+
46+
if not r.ok:
47+
raise RuntimeError('Error uploading file!')
48+
49+
def download(self, filename=None, **options):
50+
progress = options.pop('progress', None)
51+
method = options.pop('method', 'get')
52+
url = options.pop('url', None)
53+
54+
if url is None:
55+
raise ValueError('You should specify an url')
56+
57+
if filename is None:
58+
file = tempfile.NamedTemporaryFile(suffix='_py_ff_vi_st.tmp', delete=False)
59+
else:
60+
file = open(filename, 'wb')
61+
62+
with file as f:
63+
response = requests.request(method, url, stream=True, **options)
64+
total_byte = response.headers.get('content-length')
65+
66+
if total_byte is None or not callable(progress):
67+
f.write(response.content)
68+
else:
69+
downloaded = 0
70+
total_byte = int(total_byte)
71+
for data in response.iter_content(chunk_size=4096):
72+
downloaded += len(data)
73+
f.write(data)
74+
percentage = round(100 * downloaded / total_byte)
75+
progress(percentage, downloaded, total_byte)
76+
sys.stdout.write('\n')
77+
78+
return f.name
1979

2080

2181
def download_progress(percentage, downloaded, total):
@@ -37,7 +97,7 @@ def cloud():
3797
'field_name': 'YOUR_FIELD_NAME',
3898
'auth': ('username', 'password'),
3999
'headers': {
40-
'User-Agent': 'Mozilla/5.0 (compatible; AminYazdanpanahBot/1.0; +http://aminyazdanpanah.com/bots)',
100+
'User-Agent': 'Mozilla/5.0 (compatible; ' + socket.gethostname() + 'Bot/1.0; +' + socket.getfqdn + '/bots)',
41101
'Accept': 'application/json',
42102
'Authorization': 'Bearer ACCESS_TOKEN'
43103
}

examples/clouds/google_cloud.py

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,44 @@
1414
import argparse
1515
import datetime
1616
import sys
17+
import tempfile
1718
import time
19+
from os import listdir
20+
from os.path import isfile, join
21+
22+
from google.cloud import storage
1823

1924
import ffmpeg_streaming
20-
from ffmpeg_streaming import GoogleCloudStorage
25+
from ffmpeg_streaming import Clouds
26+
27+
28+
class GoogleCloudStorage(Clouds):
29+
def __init__(self, bucket_name, **kwargs):
30+
storage_client = storage.Client(**kwargs)
31+
self.bucket_name = bucket_name
32+
self.bucket = storage_client.get_bucket(bucket_name)
33+
34+
def upload_directory(self, directory, **options):
35+
files = [f for f in listdir(directory) if isfile(join(directory, f))]
36+
37+
for file in files:
38+
full_path_file = directory + file
39+
blob = self.bucket.blob(self.bucket_name + file, options)
40+
blob.upload_from_filename(full_path_file)
41+
42+
def download(self, filename=None, **options):
43+
if filename is None:
44+
tmp = tempfile.NamedTemporaryFile(suffix='_py_ff_vi_st.tmp', delete=False)
45+
filename = tmp.name
46+
47+
object_name = options.pop('object_name', None)
48+
if object_name is None:
49+
raise ValueError('You should pass an object name')
50+
51+
blob = self.bucket.get_blob(object_name, options)
52+
blob.download_to_filename(filename)
53+
54+
return filename
2155

2256

2357
def google_cloud(bucket_name, object_name):

0 commit comments

Comments
 (0)