Completed
Push — master ( 731530...b9cdd7 )
by Amin
24s queued 13s
created

HLSMasterPlaylist.sub_path()   A

Complexity

Conditions 1

Size

Total Lines 9
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 2
nop 2
dl 0
loc 9
rs 10
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 = self.c_path + "-" + str(unique)
68
        self.url = 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
        f'TYPE=SUBTITLES',
92
        f'GROUP-ID="subs"',
93
        f'NAME="subtitles"',
94
        f'URI="'+sub_path+'"'
95
    ]
96
    return [tag + ",".join(info)]
97
98
def stream_info(rep, sub_exists) -> list:
99
    """
100
    @TODO: add documentation
101
    """
102
    tag = '#EXT-X-STREAM-INF:'
103
    info = [
104
        f'BANDWIDTH={rep.bitrate.calc_overall}',
105
        f'RESOLUTION={rep.size}',
106
        f'NAME="{rep.size.height}"'
107
    ]
108
    if sub_exists:
109
        info.append(f'SUBTITLES="subs"')
110
    custom = rep.options.pop('stream_info', [])
111
112
    return [tag + ",".join(info + custom)]
113
114
115
class HLSMasterPlaylist:
116
    def __init__(self, media):
117
        """
118
        @TODO: add documentation
119
        """
120
        self.media = media
121
122
    @classmethod
123
    def generate(cls, media, path=None):
124
        if path is None:
125
            path = "{}.m3u8".format(os.path.join(*get_path_info(media.output_)))
126
        with open(path, 'w', encoding='utf-8') as playlist:
127
            playlist.write(cls(media)._content())
128
129
    def _content(self) -> str:
130
        """
131
        @TODO: add documentation
132
        """
133
        content = ['#EXTM3U'] + self._get_version() + self.media.options.get('description', [])
134
135
        for rep in self.media.reps:
136
            sub_exists=os.path.isfile(os.path.dirname(self.media.output_)+'/'+self.sub_path(rep)[0])
137
            if(sub_exists):
138
                content += sub_info(rep,self.sub_path(rep)[0]) + stream_info(rep,sub_exists) + self.stream_path(rep)
139
            else:
140
                content += stream_info(rep,sub_exists) + self.stream_path(rep)
141
142
        return "\n".join(content)
143
144
    def _get_version(self) -> list:
145
        """
146
        @TODO: add documentation
147
        """
148
        version = "7" if self.media.options.get('hls_segment_type', '') == 'fmp4' else "3"
149
        return ['#EXT-X-VERSION:' + version]
150
151
    def stream_path(self, rep):
152
        """
153
        @TODO: add documentation
154
        """
155
        return ["{}_{}p.m3u8".format(os.path.basename(self.media.output_).split('.')[0], rep.size.height)]
156
157
    def sub_path(self, rep):
158
        """
159
        Returns the subtitles maifest file name.
160
161
        Parameters
162
        ----------
163
        rep : Representation
164
        """
165
        return ["{}_{}p_vtt.m3u8".format(os.path.basename(self.media.output_).split('.')[0], rep.size.height)]
166