]>
code.delx.au - bluplayer/blob - bluplayer.py
11 from lxml
import etree
12 from PyQt4
.QtCore
import *
13 from PyQt4
.QtGui
import *
16 XHTML
= "http://www.w3.org/1999/xhtml"
21 MAKEMKV_DIR
= os
.environ
.get("MAKEMKV_DIR", os
.path
.expanduser("~/.makemkv_install"))
22 MAKEMKVCON_PATH
= os
.environ
.get("MAKEMKVCON_PATH", os
.path
.join(MAKEMKV_DIR
, "current", "makemkvcon"))
23 MPLAYER_PATH
= os
.environ
.get("MPLAYER_PATH", "mplayer")
27 f
= urllib
.urlopen(url
)
34 tds
= doc
.xpath("//xhtml:td", namespaces
=NS
)
40 if v
.tag
== "{%s}a" % XHTML
:
55 def format_exception(msg
, e
=None):
56 msg
= "%s\n%s: %s" % (msg
, e
.__class
__.__name
__, str(e
))
62 def __init__(self
, title_page
):
63 self
.id = title_page
["id"]
64 self
.duration
= title_page
["duration"]
65 self
.video_url
= title_page
["file0"]
68 return "Title%s: %s %s" % (self
.id, self
.duration
, self
.video_url
)
71 class MakeMkvCon(object):
72 def __init__(self
, params
):
73 cmd
= [MAKEMKVCON_PATH
, "--robot"]
75 logging
.info("Running makemkvcon: %s", params
)
76 self
.p
= subprocess
.Popen(cmd
, stdout
=subprocess
.PIPE
)
77 self
.fd
= self
.p
.stdout
.fileno()
82 def decode_line(self
, line
):
83 logging
.debug("makemkvcon output: %s", line
)
84 data
= csv
.reader([line
]).next()
85 key
, x0
= data
[0].split(":", 1)
96 pos
= self
.buf
.find("\n")
98 result
= self
.buf
[:pos
]
99 self
.buf
= self
.buf
[pos
+1:]
100 return self
.decode_line(result
)
103 data
= os
.read(self
.p
.stdout
.fileno(), 4096)
105 if e
.errno
== errno
.EINTR
:
108 self
.status
= self
.p
.wait()
109 logging
.info("makemkvcon exited with status %s", self
.status
)
111 raise StopIteration()
116 class MPlayer(QObject
):
117 play_finished
= pyqtSignal()
118 fatal_error
= pyqtSignal(str)
120 def play(self
, video_url
):
121 logging
.info("Running mplayer: %s", video_url
)
126 "-lavdopts", "threads=%s" % subprocess
.check_output("nproc").strip(),
130 subprocess
.check_call(cmd
)
132 self
.fatal_error
.emit(format_exception("MPlayer failed to play the video", e
))
134 self
.play_finished
.emit()
137 class MakeMkv(QObject
):
138 title_loaded
= pyqtSignal(Title
)
139 title_load_complete
= pyqtSignal()
140 status
= pyqtSignal(str)
141 fatal_error
= pyqtSignal(str)
144 raise NotImplementedError("auto-install not implemented")
147 self
.url
= "http://192.168.1.114:51001/"
148 makemkvcon
= MakeMkvCon(["info", "disc:9999"])
150 for key
, line
in makemkvcon
:
151 if key
== "MSG" and line
[0] != "5010":
152 self
.status
.emit(line
[3])
154 if disc_number
is None and key
== "DRV" and line
[5]:
155 disc_number
= line
[0]
157 self
.status
.emit("Found disc %s" % disc_name
)
159 if makemkvcon
.status
== 0:
162 def run_stream(self
, disc_number
):
163 makemkvcon
= MakeMkvCon(["stream", "disc:%s" % disc_number
])
164 for key
, line
in makemkvcon
:
165 if key
== "MSG" and line
[0] == "4500":
166 # Sometimes the port in field 6 is wrong
167 port
= line
[5].split(":")[1]
168 url
= "http://localhost:%s/" % port
169 self
.load_titles(url
)
171 self
.status
.emit(line
[3])
173 if makemkvcon
.status
!= 0:
174 self
.fatal_error
.emit("MakeMKV exited with error status: %s" % makemkvcon
.status
)
176 def load_titles(self
, url
):
177 home_page
= grab_dict(url
)
178 title_list_page
= grab_dict(home_page
["titles"])
179 title_count
= int(title_list_page
["titlecount"])
180 for i
in xrange(title_count
):
181 title_page
= grab_dict(title_list_page
["title%d" % i
])
182 title
= Title(title_page
)
183 self
.title_loaded
.emit(title
)
184 self
.title_load_complete
.emit()
187 logging
.info("MakeMKV thread started")
189 if not os
.path
.isfile(MAKEMKVCON_PATH
):
193 self
.fatal_error
.emit(format_exception("Failed to install MakeMKV", e
))
197 disc_number
= self
.find_disc()
199 self
.fatal_error
.emit(format_exception("Error searching for disc", e
))
203 self
.fatal_error
.emit("No disc found, please insert a disc and try again.")
207 self
.run_stream(disc_number
)
209 self
.fatal_error
.emit(format_exception("Failed to start MakeMKV", e
))
212 logging
.info("MakeMKV thread finished")
215 class PlayerWindow(QWidget
):
216 video_selected
= pyqtSignal(str)
219 QWidget
.__init
__(self
)
222 self
.is_playing
= False
224 self
.list_widget
= QListWidget(self
)
225 self
.list_widget
.itemActivated
.connect(self
.handle_activated
)
226 self
.list_widget
.setEnabled(False)
228 self
.log
= QTextEdit(self
)
229 self
.log
.setReadOnly(True)
231 self
.splitter
= QSplitter(Qt
.Vertical
, self
)
232 self
.splitter
.addWidget(self
.list_widget
)
233 self
.splitter
.addWidget(self
.log
)
234 self
.splitter
.setSizes([900, 200])
236 self
.layout
= QVBoxLayout(self
)
237 self
.layout
.addWidget(self
.splitter
)
238 self
.setWindowTitle("BluPlayer")
240 def add_title(self
, title
):
241 name
= "Title %s (%s)" % (int(title
.id)+1, title
.duration
)
242 self
.list_widget
.addItem(name
)
243 self
.title_map
[name
] = title
245 def select_longest_title(self
):
248 for i
in xrange(self
.list_widget
.count()):
249 item
= self
.list_widget
.item(i
)
250 name
= str(item
.text())
251 title
= self
.title_map
[name
]
252 if longest_title
is None or title
.duration
> longest_title
.duration
:
253 longest_title
= title
255 self
.list_widget
.setCurrentItem(longest_item
)
256 self
.list_widget
.setEnabled(True)
257 self
.list_widget
.setFocus()
259 def add_log_entry(self
, text
):
260 self
.log
.append(text
)
262 def popup_fatal_error(self
, text
):
263 QMessageBox
.critical(None, "Fatal error", text
)
266 def handle_activated(self
, item
):
269 name
= str(item
.text())
270 title
= self
.title_map
[name
]
271 self
.is_playing
= True
272 self
.list_widget
.setEnabled(False)
273 self
.video_selected
.emit(title
.video_url
)
275 def set_play_finished(self
):
276 self
.is_playing
= False
277 self
.list_widget
.setEnabled(True)
278 self
.list_widget
.setFocus()
280 def keyPressEvent(self
, e
):
281 if e
.key() in (Qt
.Key_Escape
, Qt
.Key_Backspace
):
284 QWidget
.keyPressEvent(self
, e
)
287 def killall_makemkvcon():
288 logging
.info("killing makemkvcon")
289 subprocess
.Popen(["killall", "makemkvcon"]).wait()
292 logging
.basicConfig(format
="%(levelname)s: %(message)s")
293 logging
.getLogger().setLevel(logging
.DEBUG
)
294 logging
.info("Configuring application")
296 app
= QApplication(sys
.argv
)
297 app
.setQuitOnLastWindowClosed(True)
299 default_font
= app
.font()
300 default_font
.setPointSize(16)
301 app
.setFont(default_font
)
303 player_window
= PlayerWindow()
304 player_window
.resize(1200, 700)
308 makemkv_thread
= QThread()
309 makemkv
.moveToThread(makemkv_thread
)
312 mplayer_thread
= QThread()
313 mplayer
.moveToThread(mplayer_thread
)
315 makemkv_thread
.started
.connect(makemkv
.run
)
316 makemkv
.title_loaded
.connect(player_window
.add_title
)
317 makemkv
.title_load_complete
.connect(player_window
.select_longest_title
)
318 makemkv
.status
.connect(player_window
.add_log_entry
)
319 makemkv
.fatal_error
.connect(player_window
.popup_fatal_error
)
321 player_window
.video_selected
.connect(mplayer
.play
)
322 mplayer
.play_finished
.connect(player_window
.set_play_finished
)
324 loading_window
= QProgressDialog(
325 "Loading BluRay disc. Please wait...",
327 0, 0, # 'infinite' style progress bar
330 loading_window
.setWindowTitle("Loading disc")
331 loading_window
.setWindowModality(Qt
.WindowModal
);
332 loading_window
.show()
333 loading_window
.canceled
.connect(qApp
.quit
)
334 makemkv
.title_load_complete
.connect(loading_window
.reset
)
336 logging
.info("Starting application")
337 makemkv_thread
.start()
338 mplayer_thread
.start()
341 logging
.info("Shutting down")
342 makemkv_thread
.quit()
343 mplayer_thread
.quit()
345 logging
.info("Waiting for makemkv thread")
346 makemkv_thread
.wait(2000)
347 logging
.info("Waiting for mplayer thread")
348 mplayer_thread
.wait(2000)
349 logging
.info("Exiting...")
352 if __name__
== "__main__":