Completed
Push — dev-4.1 ( bc345b...dbd54d )
by Felipe A.
01:15
created

M3UFile._listdir()   B

Complexity

Conditions 5

Size

Total Lines 13

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 13
rs 8.5454
1
2
import sys
3
import codecs
4
import os.path
5
6
from werkzeug.utils import cached_property
7
from browsepy.compat import range, PY_LEGACY
8
from browsepy.file import File, Directory, \
9
                          underscore_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
    media_map = {
30
        'audio/mpeg': 'mp3',
31
        'audio/ogg': 'ogg',
32
        'audio/wav': 'wav',
33
    }
34
    mimetypes = tuple(media_map)
35
36
    def __init__(self, **kwargs):
37
        self.duration = kwargs.pop('duration', None)
38
        self.title = kwargs.pop('title', None)
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 PlayListFile(Directory):
55
    abstract_class = None
56
    playable_class = PlayableFile
57
    mimetypes = ('audio/x-mpegurl', 'audio/x-scpls')
58
59
    @classmethod
60
    def from_urlpath(cls, path, app=None):
61
        original = super(PlayListFile, cls).from_urlpath(path, app)
62
        if original.is_file:
63
            if original.mimetype == M3UFile.mimetype:
64
                return M3UFile(original.path, original.app)
65
            if original.mimetype == PLSFile.mimetype:
66
                return PLSFile(original.path, original.app)
67
        elif original.is_directory:
68
            return PlayableDirectory(original.path, original.app)
69
        return original
70
71
    def normalize_playable_path(self, path):
72
        if not os.path.isabs(path):
73
            path = os.path.normpath(os.path.join(self.parent.path, path))
74
        if check_under_base(path, self.app.config['directory_base']):
75
            return path
76
        return None
77
78
79
class PLSFile(PlayListFile):
80
    ini_parser_cls = (
81
        configparser.SafeConfigParser
82
        if hasattr(configparser, 'SafeConfigParser') else
83
        configparser.ConfigParser
84
        )
85
    maxsize = getattr(sys, 'maxsize', None) or getattr(sys, 'maxint', None)
86
    mimetype = 'audio/x-scpls'
87
88
    @cached_property
89
    def _parser(self):
90
        parser = self.ini_parser()
91
        parser.read(self.path)
92
        return parser
93
94
    def _listdir(self):
95
        maxsize = self._parser.getint('playlist', 'NumberOfEntries', None)
96
        for i in range(self.maxsize if maxsize is None else maxsize):
97
            pf = self.playable_class(
98
                path=self.normalize_playable_path(
99
                    self._parser.get('playlist', 'File%d' % i, None)
100
                    ),
101
                duration=self._parser.getint('playlist', 'Length%d' % i, None),
102
                title=self._parser.get('playlist', 'Title%d' % i, None),
103
                )
104
            if pf.path:
105
                yield pf
106
            elif maxsize is None:
107
                break
108
109
110
class M3UFile(PlayListFile):
111
    mimetype = 'audio/x-mpegurl'
112
113
    def _iter_lines(self):
114
        prefix = '#EXTM3U\n'
115
        encoding = 'utf-8' if self.path.endswith('.m3u8') else 'ascii'
116
        with codecs.open(
117
          self.path, 'r',
118
          encoding=encoding,
119
          errors=underscore_replace
120
          ) as f:
121
            if f.read(len(prefix)) != prefix:
122
                f.seek(0)
123
            for line in f:
124
                line = line.rstrip('\n')
125
                if line:
126
                    yield line
127
128
    def _listdir(self):
129
        data = {}
130
        for line in self._iter_lines():
131
            if line.startswith('#EXTINF:'):
132
                duration, title = line.split(',', 1)
133
                data['duration'] = None if duration == '-1' else int(duration)
134
                data['title'] = title
135
                continue
136
            print(line)
137
            data['path'] = self.normalize_playable_path(line)
138
            if data['path']:
139
                yield self.playable_class(**data)
140
                data.clear()
141
142
143
class PlayableDirectory(Directory):
144
    @classmethod
145
    def detect(cls, file):
146
        if file.is_directory:
147
            for file in file._listdir():
148
                if file.name.rsplit('.', 1)[-1] in mimetypes:
149
                    return True
150
        return False
151
152
    def _listdir(self):
153
        for file in super(PlayableDirectory, self)._listdir():
154
            if file.name.rsplit('.', 1)[-1] in mimetypes:
155
                yield file
156