Completed
Push — master ( c2b843...91b860 )
by Felipe A.
49s
created

browsepy.plugin.player.M3UFile._extract_line()   A

Complexity

Conditions 3

Size

Total Lines 8

Duplication

Lines 0
Ratio 0 %
Metric Value
cc 3
dl 0
loc 8
rs 9.4286
1
2
import sys
3
4
from flask._compat import with_metaclass
5
from werkzeug.utils import cached_property
6
from browsepy.compat import range, str_base, PY_LEGACY
7
from browsepy.file import File, undescore_replace
8
9
10
if PY_LEGACY:
11
    import ConfigParser as configparser
12
else:
13
    import configparser
14
15
16
mimetypes = {
17
    'mp3': 'audio/mpeg',
18
    'ogg': 'audio/ogg',
19
    'wav': 'audio/wav',
20
    'm3u': 'audio/x-mpegurl',
21
    'm3u8': 'audio/x-mpegurl',
22
    'pls': 'audio/x-scpls',
23
}
24
25
26
class PlayableFile(File):
27
    media_map = {
28
        'audio/mpeg': 'mp3',
29
        'audio/ogg': 'ogg',
30
        'audio/wav': 'wav',
31
    }
32
33
    def __init__(self, duration=None, title=None, **kwargs):
34
        super(PlayableFile, self).__init__(**kwargs)
35
        self.duration = duration
36
        self.title = title
37
38
    @property
39
    def title(self):
40
        return self._title or self.name
41
42
    @title.setter
43
    def title(self, title):
44
        self._title = title
45
46
    @property
47
    def media_format(self):
48
        return self.media_map[self.type]
49
50
51
class MetaPlayListFile(type):
52
    def __init__(cls, name, bases, nmspc):
53
        '''
54
        Abstract-class mimetype-based implementation registration on nearest
55
        abstract parent.
56
        '''
57
        type.__init__(cls, name, bases, nmspc)
58
        if cls.abstract_class is None:
59
            cls.specific_classes = {}
60
            cls.abstract_class = cls
61
        elif isinstance(cls.mimetype, str_base):
62
            cls.abstract_class.specific_classes[cls.mimetype] = cls
63
64
65
class PlayListFile(with_metaclass(MetaPlayListFile, File)):
66
    abstract_class = None
67
    playable_class = PlayableFile
68
69
    def __new__(cls, *args, **kwargs):
70
        '''
71
        Polimorfic mimetype-based constructor
72
        '''
73
        self = super(PlayListFile, cls).__new__(cls)
74
        if cls is cls.abstract_class:
75
            self.__init__(*args, **kwargs)
76
            if self.mimetype in cls.abstract_class.specific_classes:
77
                return cls.specific_classes[self.mimetype](*args, **kwargs)
78
        return self
79
80
    def iter_files(self):
81
        if False:
82
            yield
83
84
    def normalize_playable_path(self, path):
85
        if not os.path.isabs(path):
86
            path = os.path.normpath(os.path.join(self.parent.path, path))
87
        if check_under_base(path, self.app.config['directory_base']):
88
            return path
89
        return None
90
91
92
class PLSFile(PlayListFile):
93
    ini_parser_cls = (
94
        configparser.SafeConfigParser
95
        if hasattr(configparser, 'SafeConfigParser') else
96
        configparser.ConfigParser
97
        )
98
    maxsize = getattr(sys, 'maxsize', None) or getattr(sys, 'maxint', None)
99
    mimetype = 'audio/x-scpls'
100
101
    @cached_property
102
    def _parser(self):
103
        parser = self.ini_parser()
104
        parser.read(self.path)
105
        return parser
106
107
    def iter_files(self):
108
        maxsize = self._parser.getint('playlist', 'NumberOfEntries', None)
109
        for i in range(self.maxsize if maxsize is None else maxsize):
110
            pf = self.playable_class(
111
                path = self.normalize_playable_path(
112
                    self._parser.get('playlist', 'File%d' % i, None)
113
                    ),
114
                duration = self._parser.getint('playlist', 'Length%d' % i, None),
115
                title =  self._parser.get('playlist', 'Title%d' % i, None),
116
                )
117
            if pf.path:
118
                yield pf
119
            elif maxsize is None:
120
                break
121
122
123
class M3UFile(PlayListFile):
124
    mimetype = 'audio/x-mpegurl'
125
126
    def _extract_line(self, line, file=None):
127
        if line.startswith('#EXTINF:'):
128
            duration, title = line.split(',', 1)
129
            file.duration = None if duration == '-1' else int(duration)
130
            file.title = title
131
            return False
132
        file.path = self.normalize_playable_path(line)
133
        return not file.path is None
134
135
    def iter_files(self):
136
        prefix = '#EXTM3U\n'
137
        encoding = 'utf-8' if self.path.endswith('.m3u8') else 'ascii'
138
        with codecs.open(self.path, 'r', encoding=encoding, errors=undescore_replace) as f:
139
            if f.read(len(prefix)) == prefix:
140
                pf = PlayableFile()
141
                for line in f:
142
                    line = line.rstrip('\n', 1)
143
                    if line and self._extract_line(line, pf):
144
                        yield pf
145
                        pf = PlayableFile()
146