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
|
|
|
|