Passed
Push — master ( 79d402...4ebce0 )
by Amin
01:15
created

ffmpeg_streaming._media.Save.output()   C

Complexity

Conditions 9

Size

Total Lines 21
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 9
eloc 16
nop 6
dl 0
loc 21
rs 6.6666
c 0
b 0
f 0
1
"""
2
ffmpeg_streaming.media
3
~~~~~~~~~~~~
4
5
Media object to build a stream objects
6
7
8
:copyright: (c) 2020 by Amin Yazdanpanah.
9
:website: https://www.aminyazdanpanah.com
10
:email: [email protected]
11
:license: MIT, see LICENSE for more details.
12
"""
13
import abc
14
import os
15
import shutil
16
import tempfile
17
import atexit
18
19
from ._clouds import CloudManager
20
from ._command_builder import command_builder
21
from ._format import Format
22
from ._hls_helper import HLSKeyInfoFile, HLSMasterPlaylist
23
from ._process import Process
24
from ._reperesentation import Representation, AutoRep
25
from ._utiles import mkdir, rm
26
from .ffprobe import FFProbe
27
28
29
class Save(abc.ABC):
30
    def __init__(self, media, _format: Format, **options):
31
        """
32
        @TODO: add documentation
33
        """
34
        atexit.register(self.finish_up)
35
36
        self.output_ = ''
37
        self.key = None
38
        self.media = media
39
        self.format = _format
40
        self.options = options
41
        self.pipe = None
42
        self.probe = None
43
        self.output_temp = False
44
45
    def finish_up(self):
46
        """
47
        @TODO: add documentation
48
        """
49
        if self.media.input_temp:
50
            rm(self.media.input)
51
        if self.output_temp:
52
            shutil.rmtree(os.path.dirname(str(self.output_)), ignore_errors=True)
53
54
    @abc.abstractmethod
55
    def set_up(self):
56
        pass
57
58
    def __getattr__(self, name):
59
        def method(*args, **kwargs):
60
            if name in ['save', 'package']:
61
                self.output(*args, **kwargs)
62
            else:
63
                raise AttributeError("The object has no attribute {}".format(name))
64
65
        return method
66
67
    def output(self, output: str = None, clouds: CloudManager = None, monitor: callable = None, ffmpeg_bin: str = 'ffmpeg', **options):
68
        """
69
        @TODO: add documentation
70
        """
71
        if output is None and clouds is None:
72
            self.output_ = self.media.input
73
        elif clouds is not None:
74
            self.output_temp = True
75
            if clouds.filename is None:
76
                clouds.filename = os.path.basename(output if output is not None else self.media.input)
77
            self.output_ = os.path.join(tempfile.mkdtemp(prefix='ffmpeg_streaming_'), clouds.filename)
78
        else:
79
            mkdir(os.path.dirname(output))
80
            self.output_ = output
81
        self.set_up()
82
        self._run(ffmpeg_bin, monitor, **options)
83
        if clouds is not None:
84
            clouds.transfer('upload_directory', os.path.dirname(self.output_))
85
86
        if output is not None and self.output_temp:
87
            shutil.move(os.path.dirname(self.output_), os.path.dirname(output))
88
89
    def _run(self, ffmpeg_bin, monitor: callable = None, **options):
90
        """
91
        @TODO: add documentation
92
        """
93
        with Process(self, command_builder(ffmpeg_bin, self), monitor, **options) as process:
94
            self.pipe, err = process.run()
95
96
97
class Streaming(Save, abc.ABC):
98
    def __init__(self, media, _format: Format, **options):
99
        """
100
        @TODO: add documentation
101
        """
102
        self.reps = list
103
        super(Streaming, self).__init__(media, _format, **options)
104
105
    def representations(self, *reps: Representation):
106
        self.reps = list(reps)
107
108
    def auto_generate_representations(self, heights=None, bitrate=None, ffprobe_bin='ffprobe'):
109
        """
110
        @TODO: add documentation
111
        """
112
        self.probe = FFProbe(self.media.input, ffprobe_bin)
113
        self.reps = AutoRep(self.probe.video_size, self.probe.bitrate, self.format, heights, bitrate)
114
115
116
class DASH(Streaming):
117
    def set_up(self):
118
        pass
119
120
    def generate_hls_playlist(self):
121
        self.options.update({'hls_playlist': "1"})
122
123
124
class HLS(Streaming):
125
    KEY_INFO_FILE_PATH = None
126
    PERIODIC_RE_KEY_FLAG = 'periodic_rekey'
127
128
    def set_up(self):
129
        """
130
        @TODO: add documentation
131
        """
132
        HLSMasterPlaylist.generate(self)
133
134
    def encryption(self, path: str, url: str, key_rotation_period: int = 0, needle: str = ".ts' for writing", length: int = 16):
135
        """
136
        @TODO: add documentation
137
        """
138
        with tempfile.NamedTemporaryFile(mode='w', suffix='_py_ff_vi_st.tmp', delete=False) as key_info:
139
            HLS.KEY_INFO_FILE_PATH = key_info.name
140
141
        key_info_file = HLSKeyInfoFile(HLS.KEY_INFO_FILE_PATH, path, url, key_rotation_period, needle, length)
142
        self.options.update({'hls_key_info_file': str(key_info_file)})
143
144
        if key_rotation_period > 0:
145
            setattr(self, 'key_rotation', key_info_file)
146
            self.flags(HLS.PERIODIC_RE_KEY_FLAG)
147
148
    def fragmented_mp4(self):
149
        """
150
        @TODO: add documentation
151
        """
152
        self.options.update({'hls_segment_type': 'fmp4'})
153
154
    def flags(self, *flags: str):
155
        """
156
        @TODO: add documentation
157
        """
158
        hls_flags = self.options.pop('hls_flags', None)
159
        hls_flags = hls_flags + "+" + "+".join(list(flags)) if hls_flags is not None else "+".join(list(flags))
160
        self.options.update({'hls_flags': hls_flags})
161
162
    def finish_up(self):
163
        """
164
        @TODO: add documentation
165
        """
166
        if HLS.KEY_INFO_FILE_PATH is not None:
167
            rm(HLS.KEY_INFO_FILE_PATH)
168
169
        super(HLS, self).finish_up()
170
171
172
class Stream2File(Save):
173
    """
174
    @TODO: add documentation
175
    """
176
    def set_up(self):
177
        pass
178
179
180
class Media(object):
181
    def __init__(self, input_opts):
182
        """
183
        @TODO: add documentation
184
        """
185
        options = dict(input_opts)
186
        self.input = options.get('i', None)
187
        self.input_temp = options.pop('is_tmp', False)
188
        self.input_opts = options
189
190
    def hls(self, _format: Format, **hls_options):
191
        """
192
        @TODO: add documentation
193
        """
194
        return HLS(self, _format, **hls_options)
195
196
    def dash(self, _format: Format, **dash_options):
197
        """
198
        @TODO: add documentation
199
        """
200
        return DASH(self, _format, **dash_options)
201
202
    def stream2file(self, _format: Format, **options):
203
        """
204
        @TODO: add documentation
205
        """
206
        return Stream2File(self, _format, **options)
207
208