Completed
Push — master ( 8473d5...821f0f )
by Felipe A.
01:23
created

PLSFile.iter_files()   B

Complexity

Conditions 5

Size

Total Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

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