|
1
|
|
|
""" |
|
2
|
|
|
ffmpeg_streaming.media |
|
3
|
|
|
~~~~~~~~~~~~ |
|
4
|
|
|
|
|
5
|
|
|
Media object to build a stream objects |
|
6
|
|
|
|
|
7
|
|
|
|
|
8
|
|
|
:copyright: (c) 2019 by Amin Yazdanpanah. |
|
9
|
|
|
:website: https://www.aminyazdanpanah.com |
|
10
|
|
|
:email: [email protected] |
|
11
|
|
|
:license: MIT, see LICENSE for more details. |
|
12
|
|
|
""" |
|
13
|
|
|
|
|
14
|
|
|
import os |
|
15
|
|
|
import shutil |
|
16
|
|
|
import tempfile |
|
17
|
|
|
|
|
18
|
|
|
from ffmpeg_streaming.build_commands import build_command |
|
19
|
|
|
from ffmpeg_streaming.clouds import open_from_cloud, save_to_clouds |
|
20
|
|
|
from ffmpeg_streaming.export_hls_playlist import export_hls_playlist |
|
21
|
|
|
from ffmpeg_streaming.utiles import get_path_info, clear_tmp_file, is_url |
|
22
|
|
|
from .key_info_file import generate_key_info_file |
|
23
|
|
|
from .process import Process |
|
24
|
|
|
from ._ffprobe import * |
|
25
|
|
|
from .auto_rep import AutoRepresentation |
|
26
|
|
|
|
|
27
|
|
|
|
|
28
|
|
|
def _get_paths(output, _input, clouds): |
|
29
|
|
|
is_tmp = False |
|
30
|
|
|
|
|
31
|
|
|
if output is not None: |
|
32
|
|
|
dirname, name = get_path_info(output) |
|
33
|
|
|
else: |
|
34
|
|
|
dirname, name = get_path_info(_input) |
|
35
|
|
|
output = _input |
|
36
|
|
|
if clouds is not None: |
|
37
|
|
|
is_tmp = True |
|
38
|
|
|
basename = os.path.basename(output) |
|
39
|
|
|
output = os.path.join(tempfile.mkdtemp(suffix='ffmpeg_streaming'), basename) |
|
40
|
|
|
dirname, name = get_path_info(output) |
|
41
|
|
|
|
|
42
|
|
|
return output, dirname, name, is_tmp |
|
43
|
|
|
|
|
44
|
|
|
|
|
45
|
|
|
def _save_hls_master_playlist(output, master_playlist_path, dirname, name): |
|
46
|
|
|
if is_url(output): |
|
47
|
|
|
if master_playlist_path is None: |
|
48
|
|
|
raise ValueError("You must specify a path for master playlist") |
|
49
|
|
|
playlist_path = master_playlist_path |
|
50
|
|
|
manifests = dirname + "/" + name |
|
51
|
|
|
else: |
|
52
|
|
|
playlist_path = dirname + "/" + name + ".m3u8" |
|
53
|
|
|
manifests = name |
|
54
|
|
|
export_hls_playlist(playlist_path, manifests, Export.reps) |
|
55
|
|
|
|
|
56
|
|
|
|
|
57
|
|
|
class Export(object): |
|
58
|
|
|
video_format = str |
|
59
|
|
|
audio_format = str |
|
60
|
|
|
_ffprobe = dict |
|
61
|
|
|
reps = list |
|
62
|
|
|
output = str |
|
63
|
|
|
_is_tmp_directory = False |
|
64
|
|
|
|
|
65
|
|
|
def __init__(self, filename, options): |
|
66
|
|
|
self.filename = filename |
|
67
|
|
|
self.options = options |
|
68
|
|
|
|
|
69
|
|
|
def __del__(self): |
|
70
|
|
|
clear_tmp_file(self.filename) |
|
71
|
|
|
if isinstance(self, HLS): |
|
72
|
|
|
clear_tmp_file(self.hls_key_info_file) |
|
73
|
|
|
if Export._is_tmp_directory: |
|
74
|
|
|
shutil.rmtree(os.path.dirname(str(Export.output)), ignore_errors=True) |
|
75
|
|
|
|
|
76
|
|
|
def __getattr__(self, name): |
|
77
|
|
|
def method(*args, **kwargs): |
|
78
|
|
|
# TODO: implement save and live methods in the future |
|
79
|
|
|
if name in ['save', 'live']: |
|
80
|
|
|
self.package(*args, **kwargs) |
|
81
|
|
|
else: |
|
82
|
|
|
raise AttributeError("The object has no attribute {}".format(name)) |
|
83
|
|
|
|
|
84
|
|
|
return method |
|
85
|
|
|
|
|
86
|
|
|
def add_rep(self, *args): |
|
87
|
|
|
Export.reps = list(args) |
|
88
|
|
|
return self |
|
89
|
|
|
|
|
90
|
|
|
def auto_rep(self, heights=None, cmd='ffprobe'): |
|
91
|
|
|
Export._ffprobe = ffprobe(self.filename, cmd) |
|
92
|
|
|
Export.reps = AutoRepresentation(Export._ffprobe, heights).generate() |
|
93
|
|
|
return self |
|
94
|
|
|
|
|
95
|
|
|
def format(self, video, audio=None): |
|
96
|
|
|
Export.video_format = video |
|
97
|
|
|
Export.audio_format = audio |
|
98
|
|
|
return self |
|
99
|
|
|
|
|
100
|
|
|
def package( |
|
101
|
|
|
self, |
|
102
|
|
|
output=None, |
|
103
|
|
|
clouds=None, |
|
104
|
|
|
progress=None, |
|
105
|
|
|
cmd='ffmpeg', |
|
106
|
|
|
c_stdout=False, |
|
107
|
|
|
c_stderr=True, |
|
108
|
|
|
c_stdin=True, |
|
109
|
|
|
c_input=None, |
|
110
|
|
|
timeout=None |
|
111
|
|
|
): |
|
112
|
|
|
Export.output, dirname, name, Export._is_tmp_directory = _get_paths(output, self.filename, clouds) |
|
113
|
|
|
|
|
114
|
|
|
if isinstance(self, HLS): |
|
115
|
|
|
_save_hls_master_playlist(output, self.master_playlist_path, dirname, name) |
|
116
|
|
|
|
|
117
|
|
|
with Process(progress, build_command(cmd, self), c_stdout, c_stderr, c_stdin) as process: |
|
118
|
|
|
p = process.run(c_input, timeout) |
|
119
|
|
|
|
|
120
|
|
|
save_to_clouds(clouds, dirname) |
|
121
|
|
|
|
|
122
|
|
|
if output is not None and clouds is not None: |
|
123
|
|
|
shutil.move(dirname, os.path.dirname(output)) |
|
124
|
|
|
|
|
125
|
|
|
return self, p, Export._ffprobe |
|
126
|
|
|
|
|
127
|
|
|
|
|
128
|
|
|
class HLS(Export): |
|
129
|
|
|
|
|
130
|
|
|
def __init__(self, filename, **options): |
|
131
|
|
|
self.hls_time = options.pop('hls_time', 10) |
|
132
|
|
|
self.hls_allow_cache = options.pop('hls_allow_cache', 0) |
|
133
|
|
|
self.hls_list_size = options.pop('hls_list_size', 0) |
|
134
|
|
|
self.master_playlist_path = options.pop('master_playlist_path', 0) |
|
135
|
|
|
self.hls_key_info_file = options.pop('hls_key_info_file', None) |
|
136
|
|
|
super(HLS, self).__init__(filename, options) |
|
137
|
|
|
|
|
138
|
|
|
def encryption(self, url, path, length=16): |
|
139
|
|
|
self.hls_key_info_file = generate_key_info_file(url, path, length) |
|
140
|
|
|
return self |
|
141
|
|
|
|
|
142
|
|
|
|
|
143
|
|
|
class DASH(Export): |
|
144
|
|
|
|
|
145
|
|
|
def __init__(self, filename, **options): |
|
146
|
|
|
self.adaption = options.pop('adaption', None) |
|
147
|
|
|
self.init_seg_name = options.pop('init_seg_name', None) |
|
148
|
|
|
self.media_seg_name = options.pop('media_seg_name', None) |
|
149
|
|
|
super(DASH, self).__init__(filename, options) |
|
150
|
|
|
|
|
151
|
|
|
|
|
152
|
|
|
class StreamToFile(Export): |
|
153
|
|
|
|
|
154
|
|
|
def __init__(self, filename, **options): |
|
155
|
|
|
super(StreamToFile, self).__init__(filename, options) |
|
156
|
|
|
|
|
157
|
|
|
|
|
158
|
|
|
def _check_file(file): |
|
159
|
|
|
if type(file) == tuple: |
|
160
|
|
|
file = open_from_cloud(file) |
|
161
|
|
|
return file |
|
162
|
|
|
|
|
163
|
|
|
|
|
164
|
|
|
def dash(file, **options): |
|
165
|
|
|
return DASH(_check_file(file), **options) |
|
166
|
|
|
|
|
167
|
|
|
|
|
168
|
|
|
def hls(file, **options): |
|
169
|
|
|
return HLS(_check_file(file), **options) |
|
170
|
|
|
|
|
171
|
|
|
|
|
172
|
|
|
def stream2file(file, **options): |
|
173
|
|
|
return StreamToFile(file, **options) |
|
174
|
|
|
|
|
175
|
|
|
|
|
176
|
|
|
__all__ = [ |
|
177
|
|
|
'dash', |
|
178
|
|
|
'hls', |
|
179
|
|
|
'stream2file' |
|
180
|
|
|
] |
|
181
|
|
|
|