calendar.py 23.1 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#
#  engine
#
#  Playout Daemon for autoradio project
#
#
#  Copyright (C) 2017-2018 Gottfried Gaisbauer <gottfried.gaisbauer@servus.at>
#
#  This file is part of engine.
#
#  engine is free software: you can redistribute it and/or modify
#  it under the terms of the GNU General Public License as published by
#  the Free Software Foundation, either version 3 of the License, or
#  any later version.
#
#  engine is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#
#  You should have received a copy of the GNU General Public License
#  along with engine. If not, see <http://www.gnu.org/licenses/>.
#
24

25
import os
26
27
import sys
import threading
28
import json
29
import queue
30
import traceback
Gottfried Gaisbauer's avatar
Gottfried Gaisbauer committed
31
import urllib
32
import logging
33

34
from mutagen.flac import FLAC
35
from datetime import datetime, timedelta
36

37
from libraries.database.broadcasts import Schedule, ScheduleEntry
38
from libraries.enum.auraenumerations import ScheduleEntryType
39
from modules.communication.redis.messenger import RedisMessenger
40
41
42


class AuraCalendarService(threading.Thread):
43
    messenger = None
44
    until = ""
45
46
47
    playlistdir = ""
    xmlplaylist = range(0)

48
    has_already_fetched = False
49
    queue = None
50
    config = None
51
52
    debug = False
    _stop_event = None
53
54
55
    logger = None
    url = dict()
    data = dict()
56
57
    # another crutch because of the missing TANK
    used_random_playlist_ids = list()
58

59
60
61
    """
    Fetching playlist data, write it into the database and notify service
    """
62
    def __init__(self, config, datefrom="", dateto=""):
63
64
        threading.Thread.__init__(self)
        self.config = config
65
        self.messenger = RedisMessenger(config)
66
        self.logger = logging.getLogger("AuraEngine")
67

68
69
        self.messenger.set_channel("aura")
        self.messenger.set_section("calendar")
70
71

        self.datefrom = str(datefrom)
72
73
        self.dateto = dateto

74
75
76
        self.queue = queue.Queue()

        self._stop_event = threading.Event()
77

78
79
80
        self.__set_url__("calendar")
        self.__set_url__("importer")

81
    # ------------------------------------------------------------------------------------------ #
82
    def set_date_from(self, date):
83
84
85
        self.datefrom = str(date).replace(" ", "T")

    # ------------------------------------------------------------------------------------------ #
86
    def set_date_to(self, date):
87
88
89
        self.dateto = str(date).replace(" ", "T")

    # ------------------------------------------------------------------------------------------ #
90
    def set_until_time(self, timestring):
91
92
93
        self.until = timestring

    # ------------------------------------------------------------------------------------------ #
94
    def set_playlist_store(self, path):
95
96
97
        self.playlistdir = path

    # ------------------------------------------------------------------------------------------ #
98
99
    def get_duration(self, start, end):
        return self.__calc_duration__(start, end)
100

101
    # ------------------------------------------------------------------------------------------ #
102
    def get_queue(self):
103
104
        return self.queue

105
    # ------------------------------------------------------------------------------------------ #
106
    def get_uri(self):
107
108
109
110
        if not self.playlistdir:
            return False
        if not self.datefrom:
            return False
111
        if not self.__calc_date_to__():
112
            return
113

114
115
116
117
118
        hostname = self.get("servername");
        port = self.get("serviceport");
        date_from = self.datefrom[0:16] + ":00";
        date_to = self.dateto[0:16] + ":00";
        uri = "http://" + hostname  + ":" + port + "/playlist/" + date_from + "/" + date_to
119
120
121
122
123
124

        return uri

    # ------------------------------------------------------------------------------------------ #
    def run(self):
        """
125
        Fetch calendar data and store it in the database
126
127
        """

128
        try:
129
130
            # fetch upcoming schedules from STEERING
            self.logger.debug("Fetching schedules from STEERING")
131
132
            self.__fetch_schedule_data__()
            # fetch playlist and fallbacks to the schedules from TANK
133
            self.logger.debug("Fetching playlists from TANK")
134
            self.__fetch_schedule_entry_data__()
135

136
137
138
139
            # drop everything what is more than 30 minutes in the future to avoid strange sync errors
            # the programme is still in the memory of engine and reloaded, when this fetching is finished.
            self.drop_the_future(timedelta(minutes=30))

140
141
            for schedule in self.fetched_schedule_data:
                if "start" not in schedule:
142
                    self.logger.warning("No start of schedule given. skipping the schedule: "+str(schedule))
143
144
                    continue
                if "end" not in schedule:
145
                    self.logger.warning("No end of schedule given. skipping the schedule: "+str(schedule))
146
147
148
149
150
151
                    continue

                # store the schedule
                schedule_db = self.store_schedule(schedule)

                # store playlists to play
152
                self.store_schedule_playlist(schedule_db, schedule, "playlist")
153
154
155
                self.store_schedule_playlist(schedule_db, schedule, "schedule_fallback", 1)
                self.store_schedule_playlist(schedule_db, schedule, "show_fallback", 2)
                self.store_schedule_playlist(schedule_db, schedule, "station_fallback", 3)
156
157
158

            # release the mutex
            self.queue.put(schedule) #"fetching_finished")
159
160
        except Exception as e:
            self.queue.put("fetching_aborted " + str(e))
161
162
163

        # terminate the thread
        return
164

165
166
167
168
169
    # ------------------------------------------------------------------------------------------ #
    def drop_the_future(self, time_in_the_future):
        ScheduleEntry.drop_the_future(time_in_the_future)
        Schedule.drop_the_future(time_in_the_future)

170
    # ------------------------------------------------------------------------------------------ #
171
    def store_schedule(self, schedule):
172
        schedule_db = Schedule.select_show_on_datetime(schedule["start"])
Gottfried Gaisbauer's avatar
Gottfried Gaisbauer committed
173
        havetoadd = False
174

175
        if not schedule_db:
176
            self.logger.debug("no schedule with given schedule id in database => create new")
177
            schedule_db = Schedule()
Gottfried Gaisbauer's avatar
Gottfried Gaisbauer committed
178
            havetoadd = True
179
180

        # calc duration
181
182
        duration = self.__calc_duration__(schedule["start"], schedule["end"])
        schedule["duration"] = timedelta(seconds=duration).__str__()
183
184
185

        schedule_db.show_id = schedule["show_id"]
        schedule_db.schedule_id = schedule["schedule_id"]
Gottfried Gaisbauer's avatar
Gottfried Gaisbauer committed
186
187
        schedule_db.schedule_start = schedule["start"]
        schedule_db.schedule_end = schedule["end"]
188
189
190
        schedule_db.show_name = schedule["show_name"]
        schedule_db.show_hosts = schedule["show_hosts"]
        schedule_db.is_repetition = schedule["is_repetition"]
191
        schedule_db.funding_category = schedule["show_fundingcategory"]
Gottfried Gaisbauer's avatar
Gottfried Gaisbauer committed
192
193
194
195
196
        schedule_db.languages = schedule["show_languages"]
        schedule_db.type = schedule["show_type"]
        schedule_db.category = schedule["show_categories"]
        schedule_db.topic = schedule["show_topics"]
        schedule_db.musicfocus = schedule["show_musicfocus"]
197
198
199
200
201
202

        schedule_db.playlist_id = schedule["playlist_id"]
        schedule_db.schedule_fallback_id = schedule["schedule_fallback_id"]
        schedule_db.show_fallback_id = schedule["show_fallback_id"]
        schedule_db.station_fallback_id = schedule["station_fallback_id"]

Gottfried Gaisbauer's avatar
Gottfried Gaisbauer committed
203
        schedule_db.store(add=havetoadd, commit=True)
204
205
206

        return schedule_db

207
    # ------------------------------------------------------------------------------------------ #
208
    def store_schedule_playlist(self, schedule_db, schedule, playlistname, fallbackplaylist_type=0):
209
210
        playlist = schedule[playlistname]

211
        debug = "Schedule playlist (" + playlistname + ") for " + schedule_db.show_name + " stored"
212
        warning = "No scheduleentries for playlist #" + str(playlist['playlist_id']) + " in schedule #" + str(schedule_db.schedule_id) + " found"
Gottfried Gaisbauer's avatar
Gottfried Gaisbauer committed
213
        entrynum = 0
214
215
216

        if "entries" in playlist:
            lastentry = None
217

218
            for entry in playlist["entries"]:
219
                lastentry = self.store_playlist_entry(schedule_db, playlist, entry, lastentry, entrynum, fallbackplaylist_type)
Gottfried Gaisbauer's avatar
Gottfried Gaisbauer committed
220
                entrynum = entrynum + 1
221
222
223

            if lastentry is None:
                self.logger.warning(warning)
224
            else:
225
                self.logger.debug(debug)
Gottfried Gaisbauer's avatar
Gottfried Gaisbauer committed
226
        else:
227
            self.logger.warning(warning)
Gottfried Gaisbauer's avatar
Gottfried Gaisbauer committed
228
229

    # ------------------------------------------------------------------------------------------ #
230
    def store_playlist_entry(self, schedule_db, playlist, entry, lastentry, entrynum, fallbackplaylist_type=0):
231
        schedule_entry_db = ScheduleEntry.select_one_playlist_entry_for_show(schedule_db.schedule_id, fallbackplaylist_type, entrynum)
232
        havetoadd = False
233

234
        if not schedule_entry_db:
235
            self.logger.debug("no scheduleentry with id " + str(playlist["playlist_id"]) + " and pos " + str(entrynum) + " in database => creating a new one")
236
            schedule_entry_db = ScheduleEntry()
237
            havetoadd = True
238

239
240
241
242
        schedule_entry_db.playlist_id = playlist["playlist_id"]
        schedule_entry_db.entry_num = entrynum
        schedule_entry_db.schedule_id = schedule_db.schedule_id
        schedule_entry_db.source = entry["source"]
243
        schedule_entry_db.fallback_type = fallbackplaylist_type
244
245
246
        schedule_entry_db.entry_start = schedule_db.schedule_start + timedelta(seconds=self.get_length(lastentry))

        schedule_entry_db.calc_unix_times()
247
248
        if havetoadd:
            schedule_entry_db.define_clean_source()
249

250
        self.logger.debug("Storing entries... playlist_id: " + str(playlist["playlist_id"]) + " schedule_id: " + str(schedule_db.schedule_id) + " num: " + str(entrynum))
251

252
        schedule_entry_db.store(add=havetoadd, commit=True)
253

254
        return schedule_entry_db
Gottfried Gaisbauer's avatar
Gottfried Gaisbauer committed
255

256
257
    # ------------------------------------------------------------------------------------------ #
    def __calc_date_to__(self):
258
259
260
261
262
263
        if self.dateto:
            return True
        if not self.until:
            return False
        if not self.datefrom:
            return False
264

265
        date_start = datetime.strptime(self.datefrom.replace("T"," "), "%Y-%m-%d %H:%M:%S")
266
        time_start = date_start.strftime("%H:%M")
267
        day_offset = 1 if (time_start > self.until) else 0
268
        end_date = date_start + timedelta(day_offset)
269
        self.dateto = end_date.strftime("%F") + "T" + self.until
270

271
272
273
        return True

    # ------------------------------------------------------------------------------------------ #
274
275
    @staticmethod
    def __calc_duration__(start, end):
276
277
278
279
280
281
282
283
284
        """
        Berechnet Zeit in Sekunden aus Differenz zwischen Start und Enddatum
        @type start: datetime
        @param start: Startzeit
        @type end: datetime
        @param end: Endzeit
        @rtype:       int
        @return:      Zeit in Sekunden
        """
285
286
        sec1 = int(datetime.strptime(start[0:16].replace(" ","T"),"%Y-%m-%dT%H:%M").strftime("%s"));
        sec2 = int(datetime.strptime(end[0:16].replace(" ","T"),"%Y-%m-%dT%H:%M").strftime("%s"));
287
288
        return (sec2 - sec1);

289
290
291
    # ------------------------------------------------------------------------------------------ #
    def __fetch_schedule_entry_data__(self):
        # store fetched entries => do not have to fetch playlist_id more than once
Gottfried Gaisbauer's avatar
Gottfried Gaisbauer committed
292
        fetched_entries=[]
293
294
295

        try:
            for schedule in self.fetched_schedule_data:
Gottfried Gaisbauer's avatar
Gottfried Gaisbauer committed
296
                # retrieve playlist and the fallbacks for every schedule
297
                # if a playlist (like station_fallback) is already fetched, it is not fetched again but reused
298
299
300
301
                schedule["playlist"]          = self.__fetch_schedule_entries__(schedule, "playlist_id",          fetched_entries)
                schedule["schedule_fallback"] = self.__fetch_schedule_entries__(schedule, "schedule_fallback_id", fetched_entries)
                schedule["show_fallback"]     = self.__fetch_schedule_entries__(schedule, "show_fallback_id",     fetched_entries)
                schedule["station_fallback"]  = self.__fetch_schedule_entries__(schedule, "station_fallback_id",  fetched_entries)
302

303
                self.logger.info(str(schedule))
304
305

        except Exception as e:
306
            self.logger.error(str(e))
Gottfried Gaisbauer's avatar
Gottfried Gaisbauer committed
307

308
    # ------------------------------------------------------------------------------------------ #
309
    def __fetch_schedule_entries__(self, schedule, id_name, fetched_schedule_entries):
310
311
312
        servicetype = "importer"
        use_testdata = False

313
        # fetch data from importer
314
        json_response = self.__fetch_data__(servicetype)
315
        if not json_response and self.config.get("use_test_data"):
316
317
            use_testdata = True

318
        # if a playlist is already fetched, do not fetch it again
319
        for entry in fetched_schedule_entries:
320
            if entry["playlist_id"] == schedule[id_name]:
321
                self.logger.debug("playlist #" + str(schedule[id_name]) + " already fetched")
322
323
                return entry

324
        # generate testdata
325
        if use_testdata:
326
            json_response = self.create_test_data(id_name, schedule)
327

328
        # convert to list
329
        try:
330
            schedule_entries = json.loads(json_response)
331
        except Exception as e:
332
333
            self.logger.critical("Cannot convert playlist from importer into list")
            schedule_entries = list()
334

Gottfried Gaisbauer's avatar
Gottfried Gaisbauer committed
335
336
337
        if "entries" in schedule_entries:
            for entry in schedule_entries["entries"]:
                if entry["source"].startswith("file"):
338
                    e = entry["source"][7:] # filter file:// out
Gottfried Gaisbauer's avatar
Gottfried Gaisbauer committed
339
                    if not os.path.isfile(e):
340
                        self.logger.warning("File", e, "does not exist!")
341

Gottfried Gaisbauer's avatar
Gottfried Gaisbauer committed
342
            fetched_schedule_entries.append(schedule_entries)
343
344

        return schedule_entries
345

346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
    def create_test_data(self, id_name, schedule):
        import random
        rand_id = random.randint(1, 10000)

        while rand_id in self.used_random_playlist_ids:
            rand_id = random.randint(1, 10000)

        self.used_random_playlist_ids.append(rand_id)

        # HARDCODED Testdata
        if id_name != "playlist_id":
            # FALLBACK TESTDATA

            if rand_id % 3 == 0:  # playlist fallback
                json_response = '{"playlist_id":' + str(
                    rand_id) + ',"entries":[{"source":"file:///var/audio/fallback/music.flac"},{"source":"file:///var/audio/fallback/NightmaresOnWax/DJ-Kicks/02 - Only Child - Breakneck.flac"}]}'
            elif rand_id % 2 == 0:  # stream fallback
                json_response = '{"playlist_id":' + str(
                    rand_id) + ',"entries":[{"source":"http://chill.out.airtime.pro:8000/chill_a"}]}'
            else:  # pool fallback
                json_response = '{"playlist_id":' + str(rand_id) + ',"entries":[{"source":"pool:///liedermacherei"}]}'

            schedule[id_name] = rand_id

        elif schedule[id_name] == 0 or schedule[id_name] is None:
            # this happens when playlist id is not filled out in pv
            # json_response = '{"playlist_id": 0}'

            if rand_id % 4 == 0:  # playlist with two files
                json_response = '{"playlist_id":' + str(
                    rand_id) + ',"entries":[{"source":"file:///var/audio/fallback/music.flac"},{"source":"file:///var/audio/fallback/NightmaresOnWax/DJ-Kicks/02 - Only Child - Breakneck.flac"}]}'
            elif rand_id % 3 == 0:  # playlist with jingle and then linein
                json_response = '{"playlist_id":' + str(
                    rand_id) + ',"entries":[{"source":"file:///var/audio/fallback/music.flac"},{"source":"linein://1"}]}'
            elif rand_id % 2 == 0:  # playlist with jingle and then http stream
                json_response = '{"playlist_id":' + str(
                    rand_id) + ',"entries":[{"source":"file:///var/audio/fallback/music.flac"},{"source":"http://chill.out.airtime.pro:8000/chill_a"}]}'
            else:  # pool playlist
                json_response = '{"playlist_id":' + str(rand_id) + ',"entries":[{"source":"pool:///hiphop"}]}'

            schedule[id_name] = rand_id

        elif schedule[id_name] % 4 == 0:  # playlist with two files
            json_response = '{"playlist_id":' + str(schedule[id_name]) + ',"entries":[{"source":"file:///var/audio/fallback/music.flac"},{"source":"file:///var/audio/fallback/NightmaresOnWax/DJ-Kicks/01 - Type - Slow Process.flac"}]}'
        elif schedule[id_name] % 3 == 0:  # playlist with jingle and then http stream
            json_response = '{"playlist_id":' + str(schedule[id_name]) + ',"entries":[{"source":"file:///var/audio/fallback/music.flac"},{"source":"linein://0"}]}'
        elif schedule[id_name] % 2 == 0:  # playlist with jingle and then linein
            json_response = '{"playlist_id":' + str(schedule[id_name]) + ',"entries":[{"source":"file:///var/audio/fallback/music.flac"},{"source":"http://stream.fro.at:80/fro-128.ogg"}]}'
        else:  # pool playlist
            json_response = '{"playlist_id":' + str(schedule[id_name]) + ',"entries":[{"source":"pool:///chillout"}]}'

        self.logger.info("Using 'randomized' playlist: " + json_response + " for " + id_name[:-3] + " for show " + schedule["show_name"] + " starting @ " + schedule["start"])

        return json_response

401
402
    # ------------------------------------------------------------------------------------------ #
    def __fetch_schedule_data__(self):
403
        servicetype = "calendar"
404
        use_testdata = False
Gottfried Gaisbauer's avatar
Gottfried Gaisbauer committed
405

406
        html_response = self.__fetch_data__(servicetype)
407
408
        if not html_response or html_response == b"[]":
            self.logger.debug("Got no response: Using testdata")
409
            use_testdata = True
410

Gottfried Gaisbauer's avatar
Gottfried Gaisbauer committed
411
        # if an error occours => use testdata
412
        if use_testdata:
413
            html_response = '[{"schedule_id":1,"start":"' + (datetime.now() + timedelta(hours=0)).strftime('%Y-%m-%d %H:00:00') + '","end":"' + (datetime.now() + timedelta(hours=1)).strftime('%Y-%m-%d %H:00:00') + '","show_id":9,"show_name":"FROzine","show_hosts":"Sandra Hochholzer, Martina Schweiger","is_repetition":false,"playlist_id":2,"schedule_fallback_id":12,"show_fallback_id":92,"station_fallback_id":1,"rtr_category":"string","comment":"Kommentar","languages":"Sprachen","type":"Typ","category":"Kategorie","topic":"Topic","musicfocus":"Fokus"},{"schedule_id":2,"schedule_start":"' + (datetime.now()+timedelta(hours=1)).strftime('%Y-%m-%d %H:00:00') + '","schedule_end":"' + (datetime.now()+timedelta(hours=2)).strftime('%Y-%m-%d %H:00:00') + '","show_id":10,"show_name":"FROMat","show_hosts":"Sandra Hochholzer, Martina Schweiger","is_repetition":false,"playlist_id":4,"schedule_fallback_id":22,"show_fallback_id":102,"station_fallback_id":1,"rtr_category":"string","comment":"Kommentar","languages":"Sprachen","type":"Typ","category":"Kategorie","topic":"Topic","musicfocus":"Fokus"},{"schedule_id":3,"schedule_start":"' + (datetime.now()+timedelta(hours=2)).strftime('%Y-%m-%d %H:00:00') + '","schedule_end":"' + (datetime.now() + timedelta(hours=3)).strftime('%Y-%m-%d %H:00:00') + '","show_id":11,"show_name":"Radio für Senioren","show_hosts":"Sandra Hochholzer, Martina Schweiger","is_repetition":false,"playlist_id":6,"schedule_fallback_id":32,"show_fallback_id":112,"station_fallback_id":1,"rtr_category":"string","comment":"Kommentar","languages":"Sprachen","type":"Typ","category":"Kategorie","topic":"Topic","musicfocus":"Fokus"}]'
414

Gottfried Gaisbauer's avatar
Gottfried Gaisbauer committed
415
        try:
416
            schedule_from_pv = json.loads(html_response)
Gottfried Gaisbauer's avatar
Gottfried Gaisbauer committed
417
        except Exception as e:
418
            self.logger.critical("Cannot fetch schedule entries from PV")
Gottfried Gaisbauer's avatar
Gottfried Gaisbauer committed
419
            sys.exit()
420

421
422
        # check data
        self.logger.critical("Hardcoded Response && no JSON data checks. I believe what i get here")
423
424
425
426

        d = self.remove_data_more_than_24h_in_the_future(schedule_from_pv)
        self.fetched_schedule_data = self.remove_data_in_the_past(d)

427
428
        return self.fetched_schedule_data

429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
    # ------------------------------------------------------------------------------------------ #
    def remove_data_more_than_24h_in_the_future(self, schedule_from_pv):
        act_list = []
        now = datetime.now()
        now_plus_24hours = now + timedelta(hours=24)

        for s in schedule_from_pv:
            date_start = datetime.strptime(s["start"], "%Y-%m-%dT%H:%M:%S")

            # append only elements which are close enough to now
            if date_start <= now_plus_24hours and date_start >= now - timedelta(hours=1):
                act_list.append(s)

        return act_list

    # ------------------------------------------------------------------------------------------ #
    def remove_data_in_the_past(self, schedule_from_pv):
        act_list = []
        now = datetime.now()

449
        for index,curr in enumerate(schedule_from_pv[:-1]):
450
            date_start = datetime.strptime(curr["start"], "%Y-%m-%dT%H:%M:%S")
451
            date_next_start = datetime.strptime(schedule_from_pv[index+1]["start"], "%Y-%m-%dT%H:%M:%S")
452

453
            # append all elements in the future
454
455
            if date_start >= now:
                act_list.append(curr)
456
            # append the one which is now playing
457
458
459
460
461
            if date_start <= now and date_next_start >= now:
                act_list.append(curr)

        return act_list

462
463
    # ------------------------------------------------------------------------------------------ #
    def __fetch_data__(self, type):
464
465
466
        # init html_response
        html_response = ""

467
468
469
470
471
472
        # open an url and read the data
        try:
            if type not in self.data:
                if self.url[type] == "":
                    return False
                request = urllib.request.Request(self.url[type])
Gottfried Gaisbauer's avatar
Gottfried Gaisbauer committed
473
            else:
474
475
476
477
478
479
                request = urllib.request.Request(self.url[type], self.data[type])

            response = urllib.request.urlopen(request)
            html_response = response.read()

        except (urllib.error.URLError, IOError, ValueError) as e:
480
            self.logger.error("Cannot connect to " + self.url[type] + "! reason: " + str(e.reason))
481
            if not self.has_already_fetched: # first fetch
482
                sys.exit()
483

484
        self.has_already_fetched = True
485
        return html_response
486

487
488
    # ------------------------------------------------------------------------------------------ #
    def get_length(self, entry):
489
        if entry is None or entry.type == ScheduleEntryType.STREAM or entry.type == ScheduleEntryType.LIVE_0 or entry.type == ScheduleEntryType.LIVE_1 or entry.type == ScheduleEntryType.LIVE_2 or entry.type == ScheduleEntryType.LIVE_3 or entry.type == ScheduleEntryType.LIVE_4:
490
491
492
493
494
            return 0

        audio_file = FLAC(entry.cleansource)
        return audio_file.info.length

495
    # ------------------------------------------------------------------------------------------ #
496
497
498
    def __set_url__(self, type):
        url = self.config.get(type+"url")
        pos = url.find("?")
499

500
501
502
        if pos > 0:
            self.url[type] = url[0:pos]
            self.data[type] = url[pos:]
Gottfried Gaisbauer's avatar
Gottfried Gaisbauer committed
503
        else:
504
            self.url[type] = url
505

506
    # ------------------------------------------------------------------------------------------ #
507
508
    def stop(self):
        self._stop_event.set()