Passed
Pull Request — master (#74)
by
unknown
03:17 queued 02:13
created

ffmpeg_streaming._hls_helper.HLSKeyInfoFile.__init__()   A

Complexity

Conditions 1

Size

Total Lines 12
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 9
nop 7
dl 0
loc 12
rs 9.95
c 0
b 0
f 0
1
"""
2
ffmpeg_streaming.media
3
~~~~~~~~~~~~
4
5
HLS helper
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 os
14
import uuid
15
from secrets import token_bytes, token_hex
16
17
from ffmpeg_streaming._utiles import mkdir, get_path_info
18
19
20
class HLSKeyInfoFile:
21
    def __init__(self, key_info_file_path: str, path: str, url: str, period: int = 0, needle: str = '', length: int = 16):
22
        """
23
        @TODO: add documentation
24
        """
25
        self.needle = needle
26
        self.period = period
27
        self.segments = []
28
        self.length = length
29
        self.url = self.c_url = url
30
        self.path = self.c_path = path
31
        mkdir(os.path.dirname(path))
32
        self.key_info_file_path = key_info_file_path
33
34
    def __str__(self):
35
        """
36
        @TODO: add documentation
37
        """
38
        self.generate()
39
        return self.key_info_file_path
40
41
    def generate(self):
42
        """
43
        @TODO: add documentation
44
        """
45
        self.generate_key()
46
        self.update_key_info_file()
47
48
    def generate_key(self):
49
        """
50
        @TODO: add documentation
51
        """
52
        with open(self.path, 'wb') as key:
53
            key.write(token_bytes(self.length))
54
55
    def update_key_info_file(self):
56
        """
57
        @TODO: add documentation
58
        """
59
        with open(self.key_info_file_path, 'w') as key_info_file:
60
            key_info_file.write("\n".join([self.url, self.path, token_hex(self.length)]))
61
62
    def update_suffix(self):
63
        """
64
        @TODO: add documentation
65
        """
66
        unique = uuid.uuid4()
67
        self.path = f'{self.c_path}-' + str(unique)
68
        self.url = f'{self.c_url}-' + str(unique)
69
70
    def rotate_key(self, line: str):
71
        """
72
        @TODO: add documentation
73
        """
74
        if self.needle in line and line not in self.segments:
75
            self.segments.append(line)
76
            if len(self.segments) % self.period == 0:
77
                self.update_suffix()
78
                self.generate()
79
80
def sub_info(rep, sub_path) -> list:
81
    """
82
    Returns the subtitle information to be added to manifest.
83
84
    Parameters
85
    ----------
86
    rep : Representation
87
    sub_path : subtitle manifest file name
88
    """
89
    tag = '#EXT-X-MEDIA:'
90
    info = [
91
        'TYPE=SUBTITLES',
92
        'GROUP-ID="subs"',
93
        'NAME="subtitles"',
94
        f'URI="{sub_path}' + '"',
95
    ]
96
97
    return [tag + ",".join(info)]
98
99
def stream_info(rep, sub_exists) -> list:
100
    """
101
    @TODO: add documentation
102
    """
103
    tag = '#EXT-X-STREAM-INF:'
104
    info = [
105
        f'BANDWIDTH={rep.bitrate.calc_overall}',
106
        f'RESOLUTION={rep.size}',
107
        f'NAME="{rep.size.height}"'
108
    ]
109
    if sub_exists:
110
        info.append('SUBTITLES="subs"')
111
    custom = rep.options.pop('stream_info', [])
112
113
    return [tag + ",".join(info + custom)]
114
115
116
class HLSMasterPlaylist:
117
    def __init__(self, media):
118
        """
119
        @TODO: add documentation
120
        """
121
        self.media = media
122
123
    @classmethod
124
    def generate(cls, media, path=None):
125
        if path is None:
126
            path = "{}.m3u8".format(os.path.join(*get_path_info(media.output_)))
127
        with open(path, 'w', encoding='utf-8') as playlist:
128
            playlist.write(cls(media)._content())
129
130
    def _content(self) -> str:
131
        """
132
        @TODO: add documentation
133
        """
134
        content = ['#EXTM3U'] + self._get_version() + self.media.options.get('description', [])
135
136
        for rep in self.media.reps:
137
            if sub_exists := os.path.isfile(
138
                os.path.dirname(self.media.output_) + '/' + self.sub_path(rep)[0]
139
            ):
140
                content += sub_info(rep,self.sub_path(rep)[0]) + stream_info(rep,sub_exists) + self.stream_path(rep)
141
            else:
142
                content += stream_info(rep,sub_exists) + self.stream_path(rep)
143
144
        return "\n".join(content)
145
146
    def _get_version(self) -> list:
147
        """
148
        @TODO: add documentation
149
        """
150
        version = "7" if self.media.options.get('hls_segment_type', '') == 'fmp4' else "3"
151
        return [f'#EXT-X-VERSION:{version}']
152
153
    def stream_path(self, rep):
154
        """
155
        @TODO: add documentation
156
        """
157
        return ["{}_{}p.m3u8".format(os.path.basename(self.media.output_).split('.')[0], rep.size.height)]
158
159
    def sub_path(self, rep):
160
        """
161
        Returns the subtitles maifest file name.
162
163
        Parameters
164
        ----------
165
        rep : Representation
166
        """
167
        return ["{}_{}p_vtt.m3u8".format(os.path.basename(self.media.output_).split('.')[0], rep.size.height)]
168