serienfilm: strip root directory and trailing slash from directory names
[enigma2-plugins.git] / serienfilm / src / MovieList.py
1 # -*- coding: utf-8 -*-
2
3 # for localized messages     
4 from . import _x
5
6 from Components.GUIComponent import GUIComponent
7 from Tools.FuzzyDate import FuzzyTime
8 from ServiceReference import ServiceReference
9 from Components.MultiContent import MultiContentEntryText, MultiContentEntryPixmapAlphaTest
10 from Components.config import config
11 from Tools.LoadPixmap import LoadPixmap
12 from Components.UsageConfig import preferredPath, defaultMoviePath
13 from enigma import eEnv
14 from glob import glob
15 import copy
16 import os.path
17
18 from enigma import eListboxPythonMultiContent, eListbox, gFont, iServiceInformation, \
19         RT_HALIGN_LEFT, RT_HALIGN_RIGHT, eServiceReference, eServiceCenter
20
21 class MovieList(GUIComponent):
22         SORT_ALPHANUMERIC = 1
23         SORT_RECORDED = 2
24
25         LISTTYPE_ORIGINAL = 0x10
26         LISTTYPE_COMPACT_TAGS = 0x20
27         LISTTYPE_COMPACT_SERVICE = 0x40
28         LISTTYPE_MINIMAL = 0x80
29         LISTTYPE_COMPACT = LISTTYPE_COMPACT_TAGS | LISTTYPE_COMPACT_SERVICE
30
31         HIDE_DESCRIPTION = 1
32         SHOW_DESCRIPTION = 2
33
34         SHOW_NO_TIMES = 0
35         SHOW_RECORDINGTIME = 1
36         SHOW_DURATION = 2
37         SHOW_DIRECTORIES = 4
38
39         # tinfo types:
40         REAL_DIR = 1
41         REAL_UP = 2
42         VIRT_DIR = 4
43         VIRT_UP = 8
44         VIRT_ENTRY = (VIRT_DIR | VIRT_UP)
45
46         MAXTIME = 0x7fffffff
47
48         gsflists = []
49
50
51         def __init__(self, root, list_type=None, sort_type=None, show_times=None, sftitle_episode_separator = None, MovieSelectionSelf = None):
52                 GUIComponent.__init__(self)
53 #               print "[SF-Plugin] class SF:MovieList init, lstt=%x, srt=%x, sht=%s, sft=>%s<, root=%s" % ( list_type, sort_type, show_times, str(sftitle_episode_separator), str(root))
54                 self.list_type = list_type or self.LISTTYPE_MINIMAL
55                 self.show_times = show_times or self.SHOW_DURATION | self.SHOW_DIRECTORIES
56                 self.sort_type = sort_type or self.SORT_RECORDED
57                 self.sftitle_episode_separator = sftitle_episode_separator
58
59                 self.l = eListboxPythonMultiContent()
60
61                 self.tags = set()
62                 self.list = None
63                 self.sflists = None
64                 self.MovieSelectionSelf = MovieSelectionSelf
65                 self.MselTitle = ""
66
67                 if root is not None:
68                         self.reload(root)
69
70                 self.pdirIcon = LoadPixmap(cached=True, path=eEnv.resolve('${libdir}/enigma2/python/Plugins/Extensions/SerienFilm/icons/folder_20.png'))
71                 self.rdirIcon = LoadPixmap(cached=True, path=eEnv.resolve('${libdir}/enigma2/python/Plugins/Extensions/SerienFilm/icons/folder_red.png'))
72                 self.fupIcon = LoadPixmap(cached=True, path=eEnv.resolve('${libdir}/enigma2/python/Plugins/Extensions/SerienFilm/icons/folderup_20.png'))
73                 self.pdirMap = MultiContentEntryPixmapAlphaTest(pos=(0,0), size=(20,20), png=self.pdirIcon)
74                 self.rdirMap = MultiContentEntryPixmapAlphaTest(pos=(0,0), size=(20,20), png=self.rdirIcon)
75                 self.fupMap = MultiContentEntryPixmapAlphaTest(pos=(0,0), size=(20,20), png=self.fupIcon)
76
77                 self.redrawList()
78                 self.l.setBuildFunc(self.buildMovieListEntry)
79
80                 self.onSelectionChanged = [ ]
81
82         def connectSelChanged(self, fnc):
83                 if not fnc in self.onSelectionChanged:
84                         self.onSelectionChanged.append(fnc)
85
86         def disconnectSelChanged(self, fnc):
87                 if fnc in self.onSelectionChanged:
88                         self.onSelectionChanged.remove(fnc)
89
90         def selectionChanged(self):
91                 for x in self.onSelectionChanged:
92 #                       print "[SF-Plugin] MovieList.selectionChanged: " + str(x)
93                         x()
94
95         def setListType(self, type):
96                 self.list_type = type
97                 self.redrawList()
98                 self.l.setList(self.list)                               # redraw
99
100         def setShowTimes(self, val):
101                 self.show_times = val
102
103         def setSortType(self, type):
104                 self.sort_type = type
105
106         def setTitleEpiSep(self, sftitle_episode_separator):
107                 self.sftitle_episode_separator = sftitle_episode_separator
108
109
110         def redrawList(self):
111                 if self.list_type & MovieList.LISTTYPE_ORIGINAL:
112                         self.l.setFont(0, gFont("Regular", 22))
113                         self.l.setFont(1, gFont("Regular", 18))
114                         self.l.setFont(2, gFont("Regular", 16))
115                         self.l.setItemHeight(75)
116                         if self.sflists and self.sflists[0] != self.list:
117                                 self.l.setItemHeight(41)
118                 elif self.list_type & MovieList.LISTTYPE_COMPACT:
119                         self.l.setFont(0, gFont("Regular", 20))
120                         self.l.setFont(1, gFont("Regular", 16))
121                         self.l.setItemHeight(39)
122 #                       self.l.setFont(1, gFont("Regular", 14))
123 #                       self.l.setItemHeight(37)
124                 else:
125                         self.l.setFont(0, gFont("Regular", 20)) # MINIMAL
126                         self.l.setFont(1, gFont("Regular", 16))
127                         self.l.setItemHeight(25)
128
129         #
130         # | name of movie              |
131         #
132         def buildMovieListEntry(self, serviceref, info, begin, tinfo):
133 #               print "[SF-Plugin] SF:MovieList.buildMovieListEntry, lst_type=%x, show_tims=%x" % (self.list_type, self.show_times)
134
135                 width = self.l.getItemSize().width()
136                 len = tinfo[5]                  #tinfo = [type, pixmap, txt, description, service, len]
137
138                 if len <= 0: #recalc len when not already done
139                         cur_idx = self.l.getCurrentSelectionIndex()
140                         x = self.list[cur_idx]
141                         if config.usage.load_length_of_movies_in_moviellist.value:
142                                 len = x[1].getLength(x[0]) #recalc the movie length...
143                         else:
144                                 len = 0         #dont recalc movielist to speedup loading the list
145                         self.list[cur_idx][3][5] = len  #update entry in list... so next time we don't need to recalc
146
147                 if len > 0:
148                         len = "%d:%02d" % (len / 60, len % 60)
149                 else:
150                         len = ""
151
152                 res = [ None ]
153                 begin_string = ""
154                 date_string = ""
155
156                 pixmap = tinfo[1]
157                 typ = tinfo[0]
158                 service = None
159                 if typ & (self.VIRT_UP | self.REAL_UP):
160                         txt = tinfo[3]  # [2] == " " for alpha-sort to top
161                 else:
162                         txt = tinfo[2]
163                         if begin > 0:
164                                 t = FuzzyTime(begin)
165                                 begin_string = t[0] + ", " + t[1]
166                                 date_string = t[0]
167                 if not typ & (self.REAL_DIR | self.VIRT_ENTRY):
168                         service = tinfo[4]
169                 description = tinfo[3]
170                 tags = self.tags and info.getInfoString(serviceref, iServiceInformation.sTags)
171
172                 if isinstance(pixmap, str):
173                         pixmap = MultiContentEntryText(pos=(0, 0), size=(25, 20), font = 0, flags = RT_HALIGN_LEFT, text = pixmap)
174                 if pixmap is not None:
175                         res.append(pixmap)
176
177                 XPOS = 25
178
179                 if self.list_type & MovieList.LISTTYPE_ORIGINAL:
180                         res.append(MultiContentEntryText(pos=(XPOS, 0), size=(width, 30), font = 0, flags = RT_HALIGN_LEFT, text=txt))
181                         line2 = 20
182                         if self.list == self.sflists[0]:
183                                 line2 = 50
184                                 if not typ & (self.REAL_DIR | self.VIRT_ENTRY):
185                                         res.append(MultiContentEntryText(pos=(XPOS, 30), size=(width, 20), font=1, flags=RT_HALIGN_LEFT, text=description))
186                         res.append(MultiContentEntryText(pos=(XPOS, line2), size=(150, 20), font=1, flags=RT_HALIGN_LEFT, text=begin_string))
187                         if service:
188                                 res.append(MultiContentEntryText(pos=(XPOS+150, line2), size=(180, 20), font = 2, flags = RT_HALIGN_RIGHT, text = service))
189                         if tags:
190                                 res.append(MultiContentEntryText(pos=(width - 250, line2), size=(180, 20), font = 2, flags = RT_HALIGN_RIGHT, text = tags))
191                         if not typ & (self.REAL_DIR | self.VIRT_ENTRY):
192                                 res.append(MultiContentEntryText(pos=(width-60, line2), size=(60, 20), font=2, flags=RT_HALIGN_RIGHT, text=len))
193                         return res
194
195                 tslen = 80
196                 if self.show_times & self.SHOW_RECORDINGTIME:
197                         tslen += 50
198                         date_string = begin_string
199                 dusz = 0
200                 if self.show_times & self.SHOW_DURATION and not tinfo[0] & (self.VIRT_ENTRY | self.REAL_UP):
201                         dusz = 57
202
203                 if self.list_type  & MovieList.LISTTYPE_COMPACT:
204                         res.append(MultiContentEntryText(pos=(XPOS, 4), size=(tslen-5, 20), font=1, flags=RT_HALIGN_RIGHT, text=date_string))
205                         res.append(MultiContentEntryText(pos=(XPOS + tslen, 0), size=(width-XPOS-tslen, 20), font = 0, flags = RT_HALIGN_LEFT, text = txt))
206                         other = None
207                         if self.list_type & MovieList.LISTTYPE_COMPACT_TAGS:
208                                 if tags:
209                                         res.append(MultiContentEntryText(pos=(width-dusz-185, 20), size=(180, 17), font=1, flags=RT_HALIGN_RIGHT, text=tags))
210                                 otherend = dusz+185
211                                 other = service
212                         else:
213                                 if service:
214                                         res.append(MultiContentEntryText(pos=(width-dusz-155, 20), size=(153, 17), font=1, flags=RT_HALIGN_RIGHT, text=service))
215                                 otherend = dusz+160
216                                 other = tags
217                         if self.list == self.sflists[0]:
218                                 if not typ & (self.REAL_DIR | self.VIRT_ENTRY):
219                                         res.append(MultiContentEntryText(pos=(XPOS, 20), size=(width-(XPOS+otherend), 17), font=1, flags=RT_HALIGN_LEFT, text=description))
220                                 elif other:
221                                         res.append(MultiContentEntryText(pos=(XPOS, 20), size=(width-(XPOS+otherend), 17), font=1, flags=RT_HALIGN_LEFT, text=other))
222                         if dusz:
223                                 res.append(MultiContentEntryText(pos=(width-dusz, 20), size=(dusz-2, 20), font=1, flags=RT_HALIGN_RIGHT, text=len))
224                 else:
225 #                       assert(self.list_type == MovieList.LISTTYPE_MINIMAL)
226                         res.append(MultiContentEntryText(pos=(XPOS, 3), size=(tslen-5, 20), font=1, flags=RT_HALIGN_RIGHT, text=date_string))
227                         res.append(MultiContentEntryText(pos=(XPOS + tslen, 0), size=(width-XPOS-tslen-dusz, 20), font = 0, flags = RT_HALIGN_LEFT, text = txt))
228                         if dusz:
229                                 res.append(MultiContentEntryText(pos=(width-dusz, 3), size=(dusz, 20), font=1, flags=RT_HALIGN_RIGHT, text=len))
230
231                 return res
232
233         def moveToIndex(self, index):
234                 if index <0:
235                         index += len(self.list)                 # standard python list behaviour
236                 self.instance.moveSelectionTo(index)
237
238         def getCurrentIndex(self):
239                 return self.instance.getCurrentIndex()
240
241         def getCurrentEvent(self):
242                 l = self.l.getCurrentSelection()
243                 return l and l[0] and l[1] and l[1].getEvent(l[0])
244
245         def getCurrent(self):
246                 l = self.l.getCurrentSelection()
247                 return l and l[0]
248
249         GUI_WIDGET = eListbox
250
251         def postWidgetCreate(self, instance):
252                 instance.setContent(self.l)
253                 self.selectionChanged_conn = instance.selectionChanged.connect(self.selectionChanged)
254
255         def preWidgetRemove(self, instance):
256                 instance.setContent(None)
257                 self.selectionChanged_conn = None
258
259         def reload(self, root = None, filter_tags = None):
260                 if root is not None:
261                         self.load(root, filter_tags)
262                 else:
263                         self.load(self.root, filter_tags)
264                 self.l.setList(self.list)
265
266         def removeService(self, service):
267                 for l in self.list[:]:
268                         repnr = tinfo = None
269                         if l[0] == service:
270                                 tinfo = l[3]
271                                 if not service.flags & eServiceReference.canDescent and isinstance(tinfo[1], str) and tinfo[1][0] == "#":
272                                         repnr = int(tinfo[1][1:])
273                                 self.list.remove(l)
274                                 break
275                 self.l.setList(self.list)
276                 if len(self.list) == 1 and self.list[0][3][0] & self.VIRT_UP:   # last movie of a series is gone
277                         service = self.list[0][0]
278                         self.moveTo(service, True)
279                         assert service.flags == eServiceReference.canDescent
280                         self.removeService(service)
281                         return
282                 if repnr is None:
283                         return
284                 repeats = 0             # update repeatcount "#x" of surviving movies
285                 ele0 = 0
286 #               print "[SF-Plugin] removeService: searching " + tinfo[2]
287                 for i in range(1, len(self.list)):
288                         m = self.list[i]
289                         t = m[3]
290 #                       print "[SF-Plugin] removeService try: %x, %s -- %s" % (m[0].flags,  str(t[1]), str(t[2]))
291                         if not m[0].flags & eServiceReference.canDescent and t[2] == tinfo[2] and isinstance(t[1], str) and t[1][0] == "#":
292                                 repeats += 1
293                                 rc = int(t[1][1:])
294                                 if rc > repnr:
295                                         rc -= 1
296                                         t[1] = "#" + str(rc)
297 #                                       print "[SF-Plugin] removeService: %s --> %s" % (t[2], t[1])
298                                 if rc == 0:
299                                         ele0 = i
300                 if ele0 > 0 and repeats == 1:
301                         self.list[ele0][3][1] = None    # remove "#0" from only lonely surviving movie
302 #                       print "[SF-Plugin] removeService: remove #0 from " + self.list[ele0][3][2]
303
304
305         def __len__(self):
306                 return len(self.list)
307
308
309         def playDirectory(self, serviceref):
310                 if serviceref.type == (eServiceReference.idUser | eServiceReference.idDVB) and serviceref.flags == eServiceReference.canDescent:
311                         self.moveTo(serviceref)         # virtual Directory
312                         return ""
313                 if serviceref.flags & eServiceReference.mustDescent:
314                         info = self.serviceHandler.info(serviceref)
315                         if info is None:
316                                 name = ""
317                         else:
318                                 name = info.getName(serviceref)
319 #                       print "[SF-Plugin] MovieList.playDirectory: %s nicht spielbar" ,(name)
320                         return name
321
322         def realDirUp(self, root):
323                 parent = None
324                 info = self.serviceHandler.info(root)
325                 pwd = info and info.getName(root)
326                 print "[SF-Plugin] MovieList.realDirUp: pwd = >%s<" % (str(pwd))
327                 if pwd and os.path.exists(pwd) and not os.path.samefile(pwd, defaultMoviePath()):
328                         parentdir = pwd[:pwd.rfind("/", 0, -1)] + "/"
329                         parent = eServiceReference("2:0:1:0:0:0:0:0:0:0:" + parentdir)
330                         info = self.serviceHandler.info(parent)
331                         if info is not None:
332                                 txt = info.getName(parent)                                                                                                                              # Titel
333                                 service = ServiceReference(info.getInfoString(parent, iServiceInformation.sServiceref)).getServiceName()        # Sender
334                                 description = info.getInfoString(parent, iServiceInformation.sDescription)                              # Beschreibung
335 #                               begin = info.getInfo(root, iServiceInformation.sTimeCreate)
336                                 begin = self.MAXTIME
337                                 parent.flags = eServiceReference.flagDirectory | eServiceReference.sort1
338                                 tinfo = [self.REAL_DIR | self.REAL_UP, self.fupMap, "  0", txt, service, 1]     # "  0" sorts before VIRT_UP
339                                 return ((parent, info, begin, tinfo))
340
341
342
343         def load(self, root, filter_tags):
344                 # this lists our root service, then building a 
345                 # nice list
346
347                 self.serviceHandler = eServiceCenter.getInstance()
348                 parentLstEntry = self.realDirUp(root)
349
350                 self.rootlst = [ ]
351
352                 self.root = root
353                 list = self.serviceHandler.list(root)
354                 if list is None:
355                         print "[SF-Plugin] listing of movies failed"
356                         list = [ ]      
357                         return
358                 tags = set()
359
360                 rootinfo = self.serviceHandler.info(root)
361                 pwd = rootinfo and rootinfo.getName(root)
362
363                 while 1:
364                         serviceref = list.getNext()
365                         if not serviceref.valid():
366                                 break
367                         pixmap = None
368                         type = 0
369                         if serviceref.flags & eServiceReference.mustDescent:
370                                 if not self.show_times & self.SHOW_DIRECTORIES:
371                                         continue                                # hide Directories
372                                 type = self.REAL_DIR            # show Directories
373                                 pixmap = self.rdirMap
374
375                         info = self.serviceHandler.info(serviceref)
376                         if info is None:
377                                 continue
378                         txt = info.getName(serviceref)
379                         if serviceref.flags & eServiceReference.mustDescent:
380                                 files = glob(os.path.join(txt, "*.ts"))
381                                 # skip empty directories
382                                 if not files:
383                                         continue
384                                 # use mtime of latest recording
385                                 begin = sorted((os.path.getmtime(x) for x in files))[-1]
386                                 if pwd and txt.startswith(pwd):
387                                         txt = txt[len(pwd):]
388                                 if txt.endswith(os.path.sep):
389                                         txt = txt[:-len(os.path.sep)]
390                         else:
391                                 begin = info.getInfo(serviceref, iServiceInformation.sTimeCreate)
392                         this_tags = info.getInfoString(serviceref, iServiceInformation.sTags).split(' ')
393
394                         # convert space-seperated list of tags into a set
395                         if this_tags == ['']:
396                                 this_tags = []
397                         this_tags = set(this_tags)
398                         tags |= this_tags
399
400                         # filter_tags is either None (which means no filter at all), or 
401                         # a set. In this case, all elements of filter_tags must be present,
402                         # otherwise the entry will be dropped.                  
403                         if filter_tags is not None and not this_tags.issuperset(filter_tags):
404                                 continue
405
406                         service = ServiceReference(info.getInfoString(serviceref, iServiceInformation.sServiceref)).getServiceName()    # Sender
407                         description = info.getInfoString(serviceref, iServiceInformation.sDescription)
408                         tinfo = [type, pixmap, txt, description, service, -1]
409
410                         self.rootlst.append((serviceref, info, begin, tinfo))
411
412                 self.rootlst.sort(key=lambda x: -x[2])                                          # movies of same name stay sortet by time
413                 self.rootlst.sort(key=lambda x: (x[3][2]+x[3][3]).lower())
414                 self.list = self.rootlst
415                 self.createSublists()
416
417
418                 if self.sort_type == self.SORT_RECORDED:
419                         self.sortLists()
420
421                 # finally, store a list of all tags which were found. these can be presented
422                 # to the user to filter the list
423                 self.tags = tags
424                 if parentLstEntry:
425 #                       print "[SF-Plugin] SF:MovieList.load: parentLstEntry %s" % (self.debPrtEref(parentLstEntry[0]))
426                         self.list.insert(0, parentLstEntry)
427
428         def moveTo(self, serviceref, descend_virtdirs=True, search_all_lists=True):
429                 count = 0
430                 for x in self.list:
431                         if x[0] == serviceref:
432
433                                 if descend_virtdirs:
434                                         l = self.list[count]
435                                         tinfo = l[3]
436                                         if tinfo[0] & self.VIRT_ENTRY:
437                                                 assert tinfo[4][:6] == "SFLIDX"
438                                                 self.list = self.sflists[int(tinfo[4][6:])]
439                                                 self.l.setList(self.list)
440                                                 self.MovieSelectionSelf.setTitle(self.MselTitle)
441                                                 self.redrawList()
442                                         if tinfo[0] & self.VIRT_DIR:
443                                                 count = 0                                                       # select VIRT_UP in sublist
444                                                 self.MovieSelectionSelf.setTitle("%s: %s" % (_x("Series"), tinfo[2]))
445                                         elif tinfo[0] & self.VIRT_UP:
446                                                 rv = self.moveTo(serviceref, False)
447                                                 return rv
448
449                                 self.instance.moveSelectionTo(count)
450                                 return True
451                         count += 1
452         # InfoBar:leavePlayerConfirmed(movielist) should find movies in virtual directories
453                 if search_all_lists and descend_virtdirs and self.sflists:
454                         savelist = self.list
455                         for l in self.sflists:
456                                 if l == savelist:
457                                         continue
458                                 self.list = l
459                                 self.l.setList(l)
460                                 if self.moveTo(serviceref, descend_virtdirs=True, search_all_lists=False):
461                                         return True
462                         self.list = savelist
463                         self.l.setList(self.list)
464
465 # enigmas list:         (serviceref, info, begin, len)  # len is replaced by tinfo
466 # tinfo:                        [type, pixmap, txt, description, service, len]
467
468 # pixmap:                       pixmap (DIR_UP...) or String (#0, #1 ... for multiple recordings)
469 # SFLIDX0...999         entry# in serlst, 0 == rootlist
470
471
472         def serflm(self, film, episode):
473                 fdate = film[2]
474                 tinfo = film[3]
475                 dsc = tinfo[3]
476                 service = tinfo[4]
477                 epi = len(episode) == 2 and episode[1]
478                 if epi:
479                         txt = ": ".join((epi, dsc))
480                 else:
481                         txt = dsc or service
482                 if self.serdate < fdate:
483                         self.serdate = fdate
484                 tinfo[2] = txt
485                 tinfo[3] = dsc
486                 return film
487
488         def update_repcnt(self, serlst, repcnt):
489                 for i in range(repcnt + 1):
490                         serlst[-( i+1 )][3][1] =  "#" + str(i)
491
492         def createSublists(self):
493                 self.serdate = 0
494                 serie = serlst = None
495                 self.sflists = [self.rootlst]
496                 txt = ("", "")
497                 rootlidx = repcnt = 0
498                 global gsflists
499                 sflidx = 0
500                 if self.sftitle_episode_separator:
501                         splitTitle = lambda s: s.split(self.sftitle_episode_separator, 1)
502                 else:
503                         splitTitle = lambda s: [s]
504 #               print "[SF-Plugin] MovieList.createSublists: self.sftitle_episode_separator = %d = >%s<" % (len(self.sftitle_episode_separator), self.sftitle_episode_separator)
505                 for tinfo in self.rootlst[:]:
506 #                       ts = tinfo[3][2].split(": ", 1)
507                         ts = splitTitle(tinfo[3][2])
508                         if txt[0] == ts[0]:
509                                 if txt[0] != serie:                             # neue Serie
510                                         sflidx += 1
511                                         serie = txt[0]
512                                         ser_serviceref = eServiceReference(eServiceReference.idUser | eServiceReference.idDVB, 
513                                                         eServiceReference.canDescent, "SFLIDX" + str(sflidx))
514                                         ser_info = self.serviceHandler.info(ser_serviceref)
515                                         # VIRT_UP should sort first, but after REAL_UP: MAXTIME-1 resp. "  1"
516                                         serlst = [(ser_serviceref, ser_info, MovieList.MAXTIME-1,
517                                                 [self.VIRT_UP, self.fupMap, "  1", txt[0], "SFLIDX0", 1])]
518                                         self.sflists.append(serlst)
519                                         serlst.append(self.serflm(self.rootlst[rootlidx-1], txt))
520                                         parent_list_index = rootlidx-1
521                                 film = self.rootlst.pop(rootlidx)
522                                 rootlidx -= 1
523                                 film = self.serflm(film, ts)
524                                 samefilm = False
525                                 if serlst:
526                                         if serlst and film[3][3] != "" and film[3][2] == serlst[-1][3][2]:              # perhaps same Movie?
527                                                 event1 = film[1].getEvent(film[0])
528                                                 event2 = serlst[-1][1].getEvent(serlst[-1][0])
529                                                 if event1 and event2 and event1.getExtendedDescription() == event2.getExtendedDescription():
530                                                         samefilm = True
531                                         if samefilm:
532                                                 repcnt += 1
533                                         elif repcnt:
534                                                 self.update_repcnt(serlst, repcnt)
535                                                 repcnt = 0
536                                         serlst.append(film)
537                         elif serlst:
538                                 self.rootlst[parent_list_index] = (ser_serviceref, ser_info, self.serdate, 
539                                         [self.VIRT_DIR, self.pdirMap, txt[0], "", "SFLIDX" + str(sflidx), 1])
540                                 self.serdate = 0
541                                 if repcnt:
542                                         self.update_repcnt(serlst, repcnt)
543                                         repcnt = 0
544                                 serlst = None
545                         rootlidx += 1
546                         txt = ts
547                 if serlst:
548                         self.rootlst[parent_list_index] = (ser_serviceref, ser_info, self.serdate, 
549                                 [self.VIRT_DIR, self.pdirMap, txt[0], "", "SFLIDX" + str(sflidx), None, 1])
550                         if repcnt:
551                                 self.update_repcnt(serlst, repcnt)
552 #               print "[SF-Plugin] sflist has %d entries" % (len(self.sflists))
553                 gsflists = self.sflists
554
555
556
557
558         def sortLists(self):
559                 if self.sort_type == self.SORT_ALPHANUMERIC:
560                         key = lambda x: (x[3][2]+x[3][3]).lower()
561                 else: key=lambda x: -x[2]
562                 if self.sflists:
563                         for list in self.sflists:
564                                 list.sort(key=key)
565                         return True
566
567         def toggleSort(self):
568                 save_list = self.list
569                 current = self.getCurrent()
570                 self.sort_type ^= (self.SORT_ALPHANUMERIC | self.SORT_RECORDED)
571                 self.sortLists()
572                 self.list = save_list
573                 self.l.setList(self.list)                               # redraw
574                 self.moveTo(current, False)
575
576 #       def toggleTags(self, toggle):
577 #               if toggle and self.list_type & (MovieList.LISTTYPE_COMPACT | MovieList.LISTTYPE_MINIMAL):
578 #                       self.showtags ^= MovieList.LISTTYPE_COMPACT
579 #               else:
580 #                       self.showtags = 0
581 #               self.redrawList()
582 #               self.l.setList(self.list)                               # redraw
583
584         def saveTitle(self, title):
585                 self.MselTitle = title
586
587         def getVirtDirList(self, name):
588                 return name[:6] == "SFLIDX" and self.sflists[int(name[6:])]
589
590         @staticmethod
591         def getVirtDirStatistics(name):
592                 if name[:6] == "SFLIDX":
593                         list = gsflists[int(name[6:])]
594                         repcnt = 0
595                         for l in list:
596                                 if isinstance(l[3][1], str) and l[3][1][0] == "#" and l[3][1] != "#0":
597                                         repcnt += 1
598                         s = "%d %s" % (len(list)-1, _x("Movies"))
599                         if repcnt:
600                                 s += ", %d %s" % (repcnt, _x("duplicated"))
601                         return s
602
603