Configuration of tv-norm and video resolution is now intuitive
[enigma2-plugins.git] / vlcplayer / src / VlcServer.py
1 # -*- coding: ISO-8859-1 -*-
2 #===============================================================================
3 # VLC Player Plugin by A. Lätsch 2007
4 #                   modified by Volker Christian 2008
5 #
6 # This is free software; you can redistribute it and/or modify it under
7 # the terms of the GNU General Public License as published by the Free
8 # Software Foundation; either version 2, or (at your option) any later
9 # version.
10 #===============================================================================
11
12
13 import re
14 import posixpath
15 from sys import maxint
16 from random import randint, seed
17 from urllib import urlencode
18 from urllib import urlopen
19 from xml.dom.minidom import parse
20 from VlcPlayer import VlcPlayer, isDvdUrl
21
22 seed()
23
24 def normpath(path):
25         if path is None:
26                 return None
27         path = path.replace("\\","/").replace("//", "/")
28         if path == "/..":
29                 return None
30         if len(path) > 0 and path[0] != '/':
31                 path = posixpath.normpath('/' + path)[1:]
32         else:
33                 path = posixpath.normpath(path)
34
35         if len(path) == 0 or path == "//":
36                 return "/"
37         elif path == ".":
38                 return None
39         return path
40         
41
42 class VlcServer:
43         def __init__(self, cfg):
44                 self.cfg = cfg
45
46         def getCfg(self):
47                 return self.cfg
48
49         def getName(self):
50                 return self.cfg.name.value
51
52         def name(self):
53                 return self.cfg.name
54
55         def getAddressType(self):
56                 return self.cfg.addressType.value
57
58         def addressType(self):
59                 return self.cfg.addressType
60
61         def getHost(self):
62                 return self.cfg.hostip.tostring(self.cfg.hostip.value)
63
64         def host(self):
65                 return self.cfg.hostip
66
67         def getHttpPort(self):
68                 return self.cfg.httpport.value
69
70         def httpPort(self):
71                 return self.cfg.httpport
72
73         def getBasedir(self):
74                 return self.cfg.basedir.value
75
76         def basedir(self):
77                 return self.cfg.basedir
78
79         def getVideoCodec(self):
80                 return self.cfg.videocodec.value
81
82         def videoCodec(self):
83                 return self.cfg.videocodec
84
85         def getVideoBitrate(self):
86                 return self.cfg.videobitrate.value
87
88         def videoBitrate(self):
89                 return self.cfg.videobitrate
90
91         def getAudioCodec(self):
92                 return self.cfg.audiocodec.value
93
94         def audioCodec(self):
95                 return self.cfg.audiocodec
96
97         def getAudioBitrate(self):
98                 return self.cfg.audiobitrate.value
99
100         def audioBitrate(self):
101                 return self.cfg.audiobitrate
102
103         def getSamplerate(self):
104                 return self.cfg.samplerate.value
105
106         def samplerate(self):
107                 return self.cfg.samplerate
108
109         def getAudioChannels(self):
110                 return self.cfg.audiochannels.value
111
112         def audioChannels(self):
113                 return self.cfg.audiochannels
114
115         def getVideoNorm(self):
116                 return self.cfg.videonorm.value
117
118         def videoNorm(self):
119                 return self.cfg.videonorm
120
121         def getOverscanCorrection(self):
122                 return self.cfg.overscancorrection.value
123
124         def overscanCorrection(self):
125                 return self.cfg.overscancorrection
126
127         def getSOverlay(self):
128                 return self.cfg.soverlay.value
129
130         def sOverlay(self):
131                 return self.cfg.soverlay
132
133         def getTranscodeVideo(self):
134                 return self.cfg.transcodeVideo.value
135
136         def transcodeVideo(self):
137                 return self.cfg.transcodeVideo
138
139         def getTranscodeAudio(self):
140                 return self.cfg.transcodeAudio.value
141
142         def transcodeAudio(self):
143                 return self.cfg.transcodeAudio
144
145         def dvdPath(self):
146                 return self.cfg.dvdPath
147
148         def getDvdPath(self):
149                 return self.cfg.dvdPath.value
150
151         def __xmlRequest(self, request, params):
152                 uri = "/requests/" + request + ".xml"
153                 if params is not None: uri = uri + "?" + urlencode(params)
154                 location = "%s:%d" % (self.getHost(), self.getHttpPort())
155                 resp = urlopen("http://" + location + uri)
156                 if resp is None:
157                         raise IOError, "No response from Server"
158                 return parse(resp)
159
160         def getFilesAndDirs(self, directory, regex):
161                 files = []
162                 directories = []
163                 response = self.__xmlRequest("browse", {"dir": directory})
164                 for element in response.getElementsByTagName("element"):
165                         if element.hasAttribute("type"):
166                                 name = element.getAttribute("name").encode("utf8")
167                                 path = normpath(element.getAttribute("path").encode("utf8"))
168                                 if path is not None:
169                                         elementType = element.getAttribute("type")
170                                         if elementType == "directory":
171                                                 directories.append([name, path])
172                                         elif elementType == "file":
173                                                 if regex is None or regex.search(path):
174                                                         files.append([name, path])
175                 return (files, directories)
176
177         def getPlaylistEntries(self):
178                 xml = self.__xmlRequest("playlist", None)
179                 files = []
180                 for e in xml.getElementsByTagName("leaf"):
181                         if e.hasAttribute("uri") is not None:
182                                 name = e.getAttribute("name").encode("utf8")
183                                 if len(name) >= 50:
184                                         name = "..." + name[-50:]
185                                 path = e.getAttribute("uri").encode("utf8")
186                                 files.append([name, path])
187                 return files
188
189         def getCurrentElement(self):
190                 xml = self.__xmlRequest("playlist", None)
191                 for e in xml.getElementsByTagName("leaf"):
192                         if e.hasAttribute("current"):
193                                 return e
194                 return None
195
196         def play(self, session, media, name, currentList = None, player = None):
197                 if player is None:
198 # or not isinstance(player, VlcPlayer):
199                         player = VlcPlayer
200                 dlg = session.open(player, self, currentList)
201                 dlg.playfile(media, name)
202                 return dlg
203         
204         def playFile(self, filename, videoPid, audioPid):
205                 streamName = "dream" + str(randint(0, maxint))
206                 transcode = []
207
208                 doDirect = isDvdUrl(filename) or re.match("(?i).*\.(mpg|mpeg|ts)$", filename.lower())
209
210                 if not doDirect or self.getTranscodeVideo():
211                         videoNormList = self.getVideoNorm().split(",")
212 #                       ,height=%s
213                         transcode.append("vcodec=%s,vb=%d,venc=ffmpeg{strict-rc=1},width=%s,height=%s,canvas-width=%s,canvas-height=%s,canvas-aspect=%s,fps=%s" % (
214                                 self.getVideoCodec(),self.getVideoBitrate(),
215                                 str(int(float(videoNormList[0]) - float(videoNormList[0]) * float(self.getOverscanCorrection()) / 100)),
216                                 str(int(float(videoNormList[1]) - float(videoNormList[1]) * float(self.getOverscanCorrection()) / 100)),
217                                 videoNormList[0], videoNormList[1], videoNormList[2], videoNormList[3]
218                         ))
219                         if self.getSOverlay():
220                                 transcode.append("soverlay")
221                 if not doDirect or self.getTranscodeAudio():
222                         transcode.append("acodec=%s,ab=%d,channels=%d,samplerate=%s" % (
223                                 self.getAudioCodec(),
224                                 self.getAudioBitrate(),
225                                 self.getAudioChannels(),
226                                 self.getSamplerate()
227                         ))
228                 if re.match("[a-zA-Z]:", filename):
229                         # Fix for subtitles with VLC on Windows.
230                         filename = filename.replace("/", "\\")
231
232                 filename = filename.replace("\\", "\\\\").replace("'", "\\'")
233 #               input = filename + " :dvdread-caching=3000 :sub-track=1 :audio-track=1 :sout=#"
234                 input = filename + " :sout=#"
235
236                 if len(transcode) > 0:
237                         input += "transcode{%s}:" % (",".join(transcode))
238
239                 mux="ts{pid-video=%d,pid-audio=%d}" % (videoPid, audioPid)
240                 input += "std{access=http,mux=%s,dst=/%s.ts} :sout-all :sout-keep" % (mux, streamName)
241
242                 print "[VLC] playfile", input
243
244                 xml = self.__xmlRequest("status", {"command": "in_play", "input": input})
245                 error = xml.getElementsByTagName("error")
246                 if error is not None and len(error) > 0:
247                         self.lastError = getText(error[0].childNodes).strip()
248                         if len(self.lastError) == 0:
249                                 self.lastError = None
250                         else:
251                                 print "[VLC] VlcControl error:", self.lastError
252                         return None
253                 else:
254                         self.lastError = None
255                 return "http://%s:%d/%s.ts" % (self.getHost(), self.getHttpPort(), streamName)
256
257         def unpause(self):
258                 self.__xmlRequest("status", {"command": "pl_pause"})
259
260         def stop(self):
261                 self.__xmlRequest("status", {"command": "pl_stop"})
262
263         def pause(self):
264                 self.__xmlRequest("status", {"command": "pl_pause"})
265
266         def delete(self, id):
267                 self.__xmlRequest("status", {"command": "pl_delete", "id": str(id)})
268
269         def deleteCurrentTree(self):
270                 print "[VLC] delete current tree"
271                 currentElement = self.getCurrentElement()
272                 while currentElement is not None and currentElement.parentNode.getAttribute("ro") != "ro":
273                         currentElement = currentElement.parentNode
274                 id = int(currentElement.getAttribute("id"))
275                 self.delete(id)
276                 
277         def seek(self, value):
278                 """  Allowed values are of the form:
279   [+ or -][<int><H or h>:][<int><M or m or '>:][<int><nothing or S or s or ">]
280   or [+ or -]<int>%
281   (value between [ ] are optional, value between < > are mandatory)
282 examples:
283   1000 -> seek to the 1000th second
284   +1H:2M -> seek 1 hour and 2 minutes forward
285   -10% -> seek 10% back"""
286                 self.__xmlRequest("status", {"command": "seek", "val": str(value)})
287
288         def status(self):
289                 xml = self.__xmlRequest("status", None)
290                 stats = {}
291                 for e in xml.documentElement.childNodes:
292                         if e.nodeType == e.ELEMENT_NODE:
293                                 if e.firstChild is None:
294                                         stats[e.nodeName.encode("latin_1", "replace")] = None
295                                 else:
296                                         stats[e.nodeName.encode("latin_1", "replace")] = e.firstChild.nodeValue.encode("latin_1", "replace")
297                 return stats
298
299         def loadPlaylist(self, playlist):
300                 self.__xmlRequest("status", {"command": "in_play", "input": playlist})
301                 self.__xmlRequest("status", {"command": "pl_stop"})
302                 xml = self.__xmlRequest("playlist", None)
303                 id = None
304                 for n in xml.getElementsByTagName("node"):
305                         if n.hasAttribute("name") is not None:
306                                 if n.getAttribute("name").encode("utf8", "replace") == playlist:
307                                         if id is None:
308                                                 id = n.getAttribute("id")
309                                         elif int(id) < int(n.getAttribute("id")):
310                                                 id = n.getAttribute("id")
311                 return id