[DVDBurn] usability improvements: title list layout, bottom info area, consistency...
[enigma2.git] / lib / python / Plugins / Extensions / DVDBurn / TitleList.py
1 import DVDProject, TitleList, TitleCutter, TitleProperties, ProjectSettings, DVDToolbox, Process
2 from Screens.Screen import Screen
3 from Screens.ChoiceBox import ChoiceBox
4 from Screens.InputBox import InputBox
5 from Screens.MessageBox import MessageBox
6 from Screens.HelpMenu import HelpableScreen
7 from Screens.TaskView import JobView
8 from Components.Task import job_manager
9 from Components.ActionMap import HelpableActionMap, ActionMap
10 from Components.Sources.List import List
11 from Components.Sources.StaticText import StaticText
12 from Components.Sources.Progress import Progress
13 from Components.MultiContent import MultiContentEntryText
14 from enigma import gFont, RT_HALIGN_LEFT, RT_HALIGN_RIGHT
15 from Tools.Directories import resolveFilename, SCOPE_PLUGINS
16
17 class TitleList(Screen, HelpableScreen):
18         skin = """
19                 <screen name="TitleList" position="center,center" size="560,470" title="DVD Tool" >
20                         <ePixmap pixmap="skin_default/buttons/red.png" position="0,0" size="140,40" alphatest="on" />
21                         <ePixmap pixmap="skin_default/buttons/green.png" position="140,0" size="140,40" alphatest="on" />
22                         <ePixmap pixmap="skin_default/buttons/yellow.png" position="280,0" size="140,40" alphatest="on" />
23                         <ePixmap pixmap="skin_default/buttons/blue.png" position="420,0" size="140,40" alphatest="on" />
24                         <widget source="key_red" render="Label" position="0,0" zPosition="1" size="140,40" font="Regular;19" halign="center" valign="center" backgroundColor="#9f1313" transparent="1" />
25                         <widget source="key_green" render="Label" position="140,0" zPosition="1" size="140,40" font="Regular;19" halign="center" valign="center" backgroundColor="#1f771f" transparent="1" />
26                         <widget source="key_yellow" render="Label" position="280,0" zPosition="1" size="140,40" font="Regular;19" halign="center" valign="center" backgroundColor="#a08500" transparent="1" />
27                         <widget source="key_blue" render="Label" position="420,0" zPosition="1" size="140,40" font="Regular;19" halign="center" valign="center" backgroundColor="#18188b" transparent="1" />
28                         <widget source="title_label" render="Label" position="10,48" size="540,38" font="Regular;18" transparent="1" />
29                         <widget source="error_label" render="Label" position="10,48" size="540,296" zPosition="3" font="Regular;20" transparent="1" />
30                         <widget source="titles" render="Listbox" scrollbarMode="showOnDemand" position="10,86" size="546,296" zPosition="3" transparent="1" >
31                                 <convert type="TemplatedMultiContent">
32                                         {"template": [
33                                                         MultiContentEntryText(pos = (0, 0), size = (420, 20), font = 0, flags = RT_HALIGN_LEFT, text = 1), # index 1 Title,
34                                                         MultiContentEntryText(pos = (0, 20), size = (328, 17), font = 1, flags = RT_HALIGN_LEFT, text = 2), # index 2 description,
35                                                         MultiContentEntryText(pos = (418, 6), size = (120, 20), font = 1, flags = RT_HALIGN_RIGHT, text = 3), # index 3 channel,
36                                                         MultiContentEntryText(pos = (326, 20), size = (154, 17), font = 1, flags = RT_HALIGN_RIGHT, text = 4), # index 4 begin time,
37                                                         MultiContentEntryText(pos = (480, 20), size = (58, 20), font = 1, flags = RT_HALIGN_RIGHT, text = 5), # index 5 duration,
38                                                 ],
39                                         "fonts": [gFont("Regular", 20), gFont("Regular", 14)],
40                                         "itemHeight": 37
41                                         }
42                                 </convert>
43                         </widget>
44                         <ePixmap pixmap="skin_default/div-h.png" position="0,390" zPosition="10" size="560,2" />
45                         <ePixmap pixmap="skin_default/buttons/key_menu.png" position="10,394" size="35,25" alphatest="on" />
46                         <widget source="hint" render="Label" position="50,396" size="540,22" font="Regular;18" halign="left" />
47                         <widget source="medium" render="Label" position="10,420" size="540,22" font="Regular;18" halign="left" />
48                         <widget source="space_bar_single" render="Progress" position="10,446" size="270,24" borderWidth="1" zPosition="2" backgroundColor="#254f7497" />
49                         <widget source="space_label_single" render="Label" position="10,449" size="270,22" zPosition="3" font="Regular;18" halign="center" transparent="1" foregroundColor="#000000" />
50                         <widget source="space_bar_dual" render="Progress" position="10,446" size="540,24" borderWidth="1" backgroundColor="#254f7497" />
51                         <widget source="space_label_dual" render="Label" position="10,449" size="540,22" zPosition="2" font="Regular;18" halign="center" transparent="1" foregroundColor="#000000" />
52                         
53                 </screen>"""
54
55         def __init__(self, session, project = None):
56                 Screen.__init__(self, session)
57                 HelpableScreen.__init__(self)
58                 
59                 self["titleactions"] = HelpableActionMap(self, "DVDTitleList",
60                         {
61                                 "addTitle": (self.addTitle, _("Add a new title"), _("Add title")),
62                                 "titleProperties": (self.titleProperties, _("Properties of current title"), _("Title properties")),
63                                 "removeCurrentTitle": (self.removeCurrentTitle, _("Remove currently selected title"), _("Remove title")),
64                                 "settings": (self.settings, _("Collection settings"), _("Settings")),
65                                 "burnProject": (self.askBurnProject, _("Burn DVD"), _("Burn DVD")),
66                         })
67
68                 self["MovieSelectionActions"] = HelpableActionMap(self, "MovieSelectionActions",
69                         {
70                                 "contextMenu": (self.showMenu, _("menu")),
71                         })
72
73                 self["actions"] = ActionMap(["OkCancelActions"],
74                         {
75                                 "cancel": self.leave
76                         })
77
78                 self["key_red"] = StaticText()
79                 self["key_green"] = StaticText(_("Add title"))
80                 self["key_yellow"] = StaticText()
81                 self["key_blue"] = StaticText(_("Settings"))
82
83                 self["title_label"] = StaticText()
84                 self["error_label"] = StaticText()
85                 self["space_label_single"] = StaticText()
86                 self["space_label_dual"] = StaticText()
87                 self["hint"] = StaticText(_("Advanced Options"))
88                 self["medium"] = StaticText()
89                 self["space_bar_single"] = Progress()
90                 self["space_bar_dual"] = Progress()
91
92                 self["titles"] = List([])
93                 self.previous_size = 0
94                 if project is not None:
95                         self.project = project
96                 else:
97                         self.newProject()
98                 self.onLayoutFinish.append(self.layoutFinished)
99
100         def layoutFinished(self):
101                 self.setTitle(_("DVD Titlelist"))
102
103         def checkBackgroundJobs(self):
104                 for job in job_manager.getPendingJobs():
105                         print "type(job):", type(job)
106                         print "Process.DVDJob:", Process.DVDJob
107                         if type(job) == Process.DVDJob:
108                                 self.backgroundJob = job
109                                 return
110                 self.backgroundJob = None
111
112         def showMenu(self):
113                 menu = []
114                 self.checkBackgroundJobs()
115                 if self.backgroundJob:
116                         j = self.backgroundJob
117                         menu.append(("%s: %s (%d%%)" % (j.getStatustext(), j.name, int(100*j.progress/float(j.end))), self.showBackgroundJob))
118                 menu.append((_("DVD media toolbox"), self.toolbox))
119                 if self.project.settings.output.getValue() == "dvd":
120                         if len(self["titles"].list):
121                                 menu.append((_("Burn DVD"), self.burnProject))
122                 elif self.project.settings.output.getValue() == "iso":
123                         menu.append((_("Create DVD-ISO"), self.burnProject))
124                 menu.append((_("Burn existing image to DVD"), self.selectImage))
125                 if len(self["titles"].list):
126                         menu.append((_("Preview menu"), self.previewMenu))
127                         menu.append((_("Edit chapters of current title"), self.editTitle))
128                         menu.append((_("Reset and renumerate title names"), self.resetTitles))
129                 menu.append((_("Exit"), self.leave))
130                 self.session.openWithCallback(self.menuCallback, ChoiceBox, title="", list=menu)
131
132         def menuCallback(self, choice):
133                 if choice:
134                         choice[1]()
135
136         def showBackgroundJob(self):
137                 job_manager.in_background = False
138                 self.session.openWithCallback(self.JobViewCB, JobView, self.backgroundJob)
139                 self.backgroundJob = None
140         
141         def titleProperties(self):
142                 if self.getCurrentTitle():
143                         self.session.openWithCallback(self.updateTitleList, TitleProperties.TitleProperties, self, self.project, self["titles"].getIndex())
144
145         def selectImage(self):
146                 self.session.openWithCallback(self.burnISO, ProjectSettings.FileBrowser, "image", self.project.settings)
147
148         def newProject(self):
149                 self.project = DVDProject.DVDProject()
150                 if self.loadTemplate():
151                         self.project.session = self.session
152                         self.settingsCB()
153
154         def addTitle(self):
155                 from Screens.MovieSelection import MovieSelection
156                 from Components.ActionMap import HelpableActionMap
157                 class DVDMovieSelection(MovieSelection):
158                         skin = """<screen name="DVDMovieSelection" position="center,center" size="560,445" title="Select a movie">
159                                 <ePixmap pixmap="skin_default/buttons/red.png" position="0,0" size="140,40" alphatest="on" />
160                                 <ePixmap pixmap="skin_default/buttons/green.png" position="140,0" size="140,40" alphatest="on" />
161                                 <ePixmap pixmap="skin_default/buttons/yellow.png" position="280,0" size="140,40" alphatest="on" />
162                                 <widget source="key_red" render="Label" position="0,0" zPosition="1" size="140,40" font="Regular;19" halign="center" valign="center" backgroundColor="#9f1313" transparent="1" />
163                                 <widget source="key_green" render="Label" position="140,0" zPosition="1" size="140,40" font="Regular;19" halign="center" valign="center" backgroundColor="#1f771f" transparent="1" />
164                                 <widget source="key_yellow" render="Label" position="280,0" zPosition="1" size="140,40" font="Regular;19" halign="center" valign="center" backgroundColor="#a08500" transparent="1" />
165                                 <widget name="waitingtext" position="0,45" size="560,395" zPosition="4" font="Regular;22" halign="center" valign="center" />
166                                 <widget name="list" position="5,40" size="550,375" zPosition="2" scrollbarMode="showOnDemand" />
167                                 <widget name="DescriptionBorder" pixmap="skin_default/border_eventinfo.png" position="0,316" zPosition="1" size="560,103" transparent="1" alphatest="on" />
168                                 <widget source="Service" render="Label" position="5,318" zPosition="1" size="480,35" font="Regular;17" foregroundColor="#cccccc">
169                                         <convert type="MovieInfo">ShortDescription</convert>
170                                 </widget>
171                                 <widget source="Service" render="Label" position="495,318" zPosition="1" size="60,22" font="Regular;17" halign="right">
172                                         <convert type="ServiceTime">Duration</convert>
173                                         <convert type="ClockToText">AsLength</convert>
174                                 </widget>
175                                 <widget source="Service" render="Label" position="380,337" zPosition="2" size="175,22" font="Regular;17" halign="right">
176                                         <convert type="MovieInfo">RecordServiceName</convert>
177                                 </widget>
178                                 <widget source="Service" render="Label" position="5,357" zPosition="1" size="550,58" font="Regular;19">
179                                         <convert type="EventName">ExtendedDescription</convert>
180                                 </widget>
181                                 <widget name="freeDiskSpace" position="10,425" size="540,20" font="Regular;19" valign="center" halign="right" />
182                         </screen>"""
183                         def __init__(self, session):
184                                 MovieSelection.__init__(self, session)
185                                 self["key_red"] = StaticText(_("Close"))
186                                 self["key_green"] = StaticText(_("Add"))
187                                 self["key_yellow"] = StaticText(_("Edit title"))
188                                 self["ColorActions"] = HelpableActionMap(self, "ColorActions",
189                                 {
190                                         "red": (self.close, _("Close title selection")),
191                                         "green": (self.insertWithoutEdit, ("insert without cutlist editor")),
192                                         "yellow": (self.movieSelected, _("Add a new title"))
193                                 })
194                         def updateTags(self):
195                                 pass
196                         def doContext(self):
197                                 print "context menu forbidden inside DVDBurn to prevent calling multiple instances"
198                         def insertWithoutEdit(self):
199                                 current = self.getCurrent()
200                                 if current is not None:
201                                         current.edit = False
202                                         self.close(current)
203                         def movieSelected(self):
204                                 current = self.getCurrent()
205                                 if current is not None:
206                                         current.edit = True
207                                         self.close(current)
208                 self.session.openWithCallback(self.selectedSource, DVDMovieSelection)
209
210         def selectedSource(self, source = None):
211                 if source is None:
212                         return None
213                 if not source.getPath().endswith(".ts"):
214                         self.session.open(MessageBox,text = _("You can only burn Dreambox recordings!"), type = MessageBox.TYPE_ERROR)
215                         return None
216                 t = self.project.addService(source)
217                 try:
218                         editor = source.edit
219                 except AttributeError:
220                         editor = True
221                 self.editTitle(t, editor)
222
223         def removeCurrentTitle(self):
224                 title = self.getCurrentTitle()
225                 self.removeTitle(title)
226         
227         def removeTitle(self, title):
228                 if title is not None:
229                         self.project.titles.remove(title)
230                         self.updateTitleList()
231
232         def toolbox(self):
233                 self.session.open(DVDToolbox.DVDToolbox)
234
235         def settings(self):
236                 self.session.openWithCallback(self.settingsCB, ProjectSettings.ProjectSettings, self.project)
237
238         def settingsCB(self, update=True):
239                 if not update:
240                         return
241                 self.updateTitleList()
242
243         def loadTemplate(self):
244                 filename = resolveFilename(SCOPE_PLUGINS)+"Extensions/DVDBurn/DreamboxDVD.ddvdp.xml"
245                 if self.project.load(filename):
246                         self["error_label"].setText("")
247                         return True
248                 else:
249                         self["error_label"].setText(self.project.error)
250                         return False
251
252         def askBurnProject(self):
253                 if len(self["titles"].list):
254                         self.session.openWithCallback(self.burnProject,MessageBox,text = _("Do you want to burn this collection to DVD medium?"), type = MessageBox.TYPE_YESNO)
255
256         def burnProject(self, answer=True):
257                 if not answer:
258                         return
259                 if self.project.settings.authormode.getValue() == "data_ts":
260                         job = Process.DVDdataJob(self.project)
261                         job_manager.AddJob(job)
262                         job_manager.in_background = False
263                         self.session.openWithCallback(self.JobViewCB, JobView, job)
264                 else:
265                         job = Process.DVDJob(self.project)
266                         job_manager.AddJob(job)
267                         job_manager.in_background = False
268                         self.session.openWithCallback(self.JobViewCB, JobView, job)
269
270         def burnISO(self, path, scope, configRef):
271                 if path:
272                         job = Process.DVDisoJob(self.project, path)
273                         job_manager.AddJob(job)
274                         job_manager.in_background = False
275                         self.session.openWithCallback(self.JobViewCB, JobView, job)
276
277         def JobViewCB(self, in_background):
278                 job_manager.in_background = in_background
279
280         def previewMenu(self):
281                 job = Process.DVDJob(self.project, menupreview=True)
282                 job_manager.in_background = False
283                 job_manager.AddJob(job)
284
285         def updateTitleList(self):
286                 list = [ ]
287                 for title in self.project.titles:
288                         list.append((title, title.properties.menutitle.getValue(), title.properties.menusubtitle.getValue(), title.DVBchannel, title.formatDVDmenuText("$D.$M.$Y, $T", 0), title.formatDVDmenuText("$l", 0)))
289                 self["titles"].list = list
290                 self.updateSize()
291                 if len(list):
292                         self["key_red"].text = _("Remove title")
293                         self["key_yellow"].text = _("Title properties")
294                         self["title_label"].text = _("Table of content for collection") + " \"" + self.project.settings.name.getValue() + "\":"
295                 else:
296                         self["key_red"].text = ""
297                         self["key_yellow"].text = ""
298                         self["title_label"].text = _("Please add titles to the compilation")
299
300         def updateSize(self):
301                 size = self.project.size/(1024*1024)
302                 MAX_DL = self.project.MAX_DL-100
303                 MAX_SL = self.project.MAX_SL-100
304                 print "updateSize:", size, "MAX_DL:", MAX_DL, "MAX_SL:", MAX_SL
305                 if size > MAX_DL:
306                         percent = 100 * size / float(MAX_DL)
307                         self["space_label_dual"].text = "%d MB (%.2f%%)" % (size, percent)
308                         self["space_bar_dual"].value = int(percent)
309                         self["space_bar_single"].value = 100
310                         self["space_label_single"].text = ""
311                         self["medium"].text = _("exceeds dual layer medium!")
312                         if self.previous_size < MAX_DL:
313                                 self.session.open(MessageBox,text = _("exceeds dual layer medium!"), type = MessageBox.TYPE_ERROR)
314                 elif size > MAX_SL:
315                         percent = 100 * size / float(MAX_DL)
316                         self["space_label_dual"].text = "%d MB (%.2f%%)" % (size, percent)
317                         self["space_bar_dual"].value = int(percent)
318                         self["space_bar_single"].value = 100
319                         self["space_label_single"].text = ""
320                         self["medium"].text = _("required medium type:") + " " + _("DUAL LAYER DVD") + ", %d MB " % (MAX_DL - size) + _("free")
321                         if self.previous_size < MAX_SL:
322                                 self.session.open(MessageBox, text = _("Your collection exceeds the size of a single layer medium, you will need a blank dual layer DVD!"), timeout = 10, type = MessageBox.TYPE_INFO)
323                 elif size < MAX_SL:
324                         percent = 100 * size / float(MAX_SL)
325                         self["space_label_single"].text = "%d MB (%.2f%%)" % (size, percent)
326                         self["space_bar_single"].value = int(percent)
327                         self["space_bar_dual"].value = 0
328                         self["space_label_dual"].text = ""
329                         self["medium"].text = _("required medium type:") + " " + _("SINGLE LAYER DVD") + ", %d MB " % (MAX_SL - size) + _("free")
330                 self.previous_size = size
331
332         def getCurrentTitle(self):
333                 t = self["titles"].getCurrent()
334                 return t and t[0]
335
336         def editTitle(self, title = None, editor = True):
337                 t = title or self.getCurrentTitle()
338                 if t is not None:
339                         self.current_edit_title = t
340                         if editor:
341                                 self.session.openWithCallback(self.titleEditDone, TitleCutter.TitleCutter, t)
342                         else:
343                                 self.session.openWithCallback(self.titleEditDone, TitleCutter.CutlistReader, t)
344
345         def titleEditDone(self, cutlist):
346                 t = self.current_edit_title
347                 t.titleEditDone(cutlist)
348                 if t.VideoType != 0:
349                         self.session.openWithCallback(self.DVDformatCB,MessageBox,text = _("The DVD standard doesn't support H.264 (HDTV) video streams. Do you want to create a Dreambox format data DVD (which will not play in stand-alone DVD players) instead?"), type = MessageBox.TYPE_YESNO)
350                 else:
351                         self.updateTitleList()
352
353         def resetTitles(self):
354                 count = 0
355                 for title in self.project.titles:
356                         count += 1
357                         title.initDVDmenuText(count)
358                 self.updateTitleList()
359
360         def DVDformatCB(self, answer):
361                 t = self.current_edit_title
362                 if answer == True:
363                         self.project.settings.authormode.setValue("data_ts")
364                         self.updateTitleList()
365                 else:
366                         self.removeTitle(t)
367
368         def leave(self, close = False):
369                 if not len(self["titles"].list) or close:
370                         self.close()
371                 else:
372                         self.session.openWithCallback(self.exitCB, MessageBox,text = _("Your current collection will get lost!") + "\n" + _("Do you really want to exit?"), type = MessageBox.TYPE_YESNO)
373
374         def exitCB(self, answer):
375                 print "exitCB", answer
376                 if answer is not None and answer:
377                         self.close()