Passed
Push — master ( 7bc1c2...5b857f )
by Amin
03:31
created

ffmpeg_streaming._media.Media.dash()   A

Complexity

Conditions 1

Size

Total Lines 2
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 2
nop 3
dl 0
loc 2
rs 10
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
from abc import ABC
19
20
from ._clouds import CloudManager
21
from ._command_builder import command_builder
22
from ._format import Format
23
from ._hls_helper import HLSKeyInfoFile, HLSMasterPlaylist
24
from ._process import Process
25
from ._reperesentation import Representation, AutoRep
26
from ._utiles import mkdir
27
from .ffprobe import FFProbe
28
29
30
class Save(abc.ABC):
31
    def __init__(self, media, _format: Format, **options):
32
        atexit.register(self.finish_up)
33
34
        self.output_ = ''
35
        self.key = None
36
        self.media = media
37
        self.format = _format
38
        self.options = options
39
        self.pipe = None
40
        self.output_temp = False
41
42
    def finish_up(self):
43
        if self.media.input_temp:
44
            os.remove(self.media.input)
45
        if self.output_temp:
46
            shutil.rmtree(os.path.dirname(str(self.output_)), ignore_errors=True)
47
48
    @abc.abstractmethod
49
    def set_up(self):
50
        pass
51
52
    def __getattr__(self, name):
53
        def method(*args, **kwargs):
54
            if name in ['save', 'package']:
55
                self.output(*args, **kwargs)
56
            else:
57
                raise AttributeError("The object has no attribute {}".format(name))
58
59
        return method
60
61
    def output(self, output: str = None, clouds: CloudManager = None, monitor: callable = None, ffmpeg_bin: str = 'ffmpeg', **options):
62
        if output is None and clouds is None:
63
            self.output_ = self.media.input
64
        elif clouds is not None:
65
            self.output_temp = True
66
            basename = os.path.basename(output if output is not None else self.media.input)
67
            self.output_ = os.path.join(tempfile.mkdtemp(prefix='ffmpeg_streaming_'), basename)
68
        else:
69
            mkdir(os.path.dirname(output))
70
            self.output_ = output
71
        self.set_up()
72
        self._run(ffmpeg_bin, monitor, **options)
73
        if clouds is not None:
74
            clouds.transfer('upload_directory', os.path.dirname(self.output_))
75
76
        if output is not None and self.output_temp:
77
            shutil.move(os.path.dirname(self.output_), os.path.dirname(output))
78
79
    def _run(self, ffmpeg_bin, monitor: callable = None, **options):
80
        with Process(self, command_builder(ffmpeg_bin, self), monitor, **options) as process:
81
            self.pipe, err = process.run()
82
83
84
class Streaming(Save, ABC):
85
    def __init__(self, media, _format: Format, **options):
86
        self.reps = list
87
        super(Streaming, self).__init__(media, _format, **options)
88
89
    def representations(self, *reps: Representation):
90
        self.reps = list(reps)
91
92
    def auto_generate_representations(self, heights=None, bitrate=None, ffprobe_bin='ffprobe'):
93
        probe = FFProbe(self.media.input, ffprobe_bin)
94
        self.reps = AutoRep(probe.video_size, probe.bitrate, self.format, heights, bitrate)
95
96
97
class DASH(Streaming):
98
    def set_up(self):
99
        pass
100
101
    def generate_hls_playlist(self):
102
        self.options.update({'hls_playlist': "1"})
103
104
105
class HLS(Streaming):
106
    KEY_INFO_FILE_PATH = None
107
108
    def set_up(self):
109
        HLSMasterPlaylist.generate(self)
110
111
    def encryption(self, path: str, url: str, key_rotation_period: int = 0, needle: str = ".ts' for writing", length: int = 16):
112
        with tempfile.NamedTemporaryFile(mode='w', suffix='_py_ff_vi_st.tmp', delete=False) as key_info:
113
            HLS.KEY_INFO_FILE_PATH = key_info.name
114
115
        key_info_file = HLSKeyInfoFile(HLS.KEY_INFO_FILE_PATH, path, url, key_rotation_period, needle, length)
116
        self.options.update({'hls_key_info_file': str(key_info_file)})
117
        if key_rotation_period > 0:
118
            setattr(self, 'key_rotation', key_info_file)
119
            self.flags('periodic_rekey')
120
121
    def fragmented_mp4(self):
122
        self.options.update({'hls_segment_type': 'fmp4'})
123
124
    def flags(self, *flags: str):
125
        hls_flags = self.options.pop('hls_flags', None)
126
        hls_flags = hls_flags + "+" + "+".join(list(flags)) if hls_flags is not None else "+".join(list(flags))
127
        self.options.update({'hls_flags': hls_flags})
128
129
    def finish_up(self):
130
        if HLS.KEY_INFO_FILE_PATH is not None:
131
            shutil.rmtree(HLS.KEY_INFO_FILE_PATH, ignore_errors=True)
132
133
        super(HLS, self).finish_up()
134
135
136
class Stream2File(Save):
137
    def set_up(self):
138
        pass
139
140
141
class Media(object):
142
    def __init__(self, input_opts):
143
        options = dict(input_opts)
144
        self.input = options.get('i', None)
145
        self.input_temp = options.pop('is_tmp', False)
146
        self.input_opts = options
147
148
    def hls(self, _format: Format, **options):
149
        return HLS(self, _format, **options)
150
151
    def dash(self, _format: Format, **options):
152
        return DASH(self, _format, **options)
153
154
    def stream2file(self, _format: Format, **options):
155
        return Stream2File(self, _format, **options)
156
157