1 # Written by Arno Bakker
2 # see LICENSE.txt for license information
3 from threading import currentThread
4 from traceback import print_exc
13 from BaseLib.Video.defs import *
14 from BaseLib.Video.VideoServer import VideoHTTPServer,VideoRawVLCServer
15 from BaseLib.Video.utils import win32_retrieve_video_play_command,win32_retrieve_playcmd_from_mimetype,quote_program_path,videoextdefaults
17 from BaseLib.Core.simpledefs import *
18 from BaseLib.Core.Utilities.unicode import unicode2str,bin2unicode
20 from BaseLib.Video.CachingStream import SmartCachingStream
21 from BaseLib.Video.Ogg import is_ogg,OggMagicLiveStream
25 if sys.platform == "linux2" or sys.platform == "darwin":
26 USE_VLC_RAW_INTERFACE = False
28 USE_VLC_RAW_INTERFACE = False # False for Next-Share
35 def __init__(self,httpport=6880):
36 if VideoPlayer.__single:
37 raise RuntimeError, "VideoPlayer is singleton"
38 VideoPlayer.__single = self
39 self.videoframe = None
40 self.extprogress = None
41 self.vod_download = None
42 self.playbackmode = None
43 self.preferredplaybackmode = None
44 self.other_downloads = None
45 self.closeextplayercallback = None
47 self.videohttpservport = httpport
48 self.videohttpserv = None
49 # Must create the instance here, such that it won't get garbage collected
50 self.videorawserv = VideoRawVLCServer.getInstance()
52 self.resume_by_system = 1
53 self.user_download_choice = None
55 def getInstance(*args, **kw):
56 if VideoPlayer.__single is None:
57 VideoPlayer(*args, **kw)
58 return VideoPlayer.__single
59 getInstance = staticmethod(getInstance)
61 def register(self,utility,preferredplaybackmode=None,closeextplayercallback=None):
63 self.utility = utility # TEMPARNO: make sure only used for language strings
65 self.preferredplaybackmode = preferredplaybackmode
66 self.determine_playbackmode()
68 if self.playbackmode == PLAYBACKMODE_INTERNAL:
69 # The python-vlc bindings. Created only once at the moment,
70 # as using MediaControl.exit() more than once with the raw interface
73 from BaseLib.Video.VLCWrapper import VLCWrapper
74 self.vlcwrap = VLCWrapper(self.utility.getPath())
75 self.supportedvodevents = [VODEVENT_START,VODEVENT_PAUSE,VODEVENT_RESUME]
78 # Can't pause when external player
79 self.supportedvodevents = [VODEVENT_START]
81 if self.playbackmode != PLAYBACKMODE_INTERNAL or not USE_VLC_RAW_INTERFACE:
82 # Start HTTP server for serving video to external player
83 self.videohttpserv = VideoHTTPServer.getInstance(self.videohttpservport) # create
84 self.videohttpserv.background_serve()
85 self.videohttpserv.register(self.videohttpserver_error_callback,self.videohttpserver_set_status_callback)
87 if closeextplayercallback is not None:
88 self.closeextplayercallback = closeextplayercallback
90 def set_other_downloads(self, other_downloads):
91 """A boolean indicating whether there are other downloads running at this time"""
92 self.other_downloads = other_downloads
94 def get_vlcwrap(self):
97 def get_supported_vod_events(self):
98 return self.supportedvodevents
100 def set_videoframe(self,videoframe):
101 self.videoframe = videoframe
104 def play_file(self,dest):
105 """ Play video file from disk """
107 print >>sys.stderr,"videoplay: Playing file from disk",dest
109 (prefix,ext) = os.path.splitext(dest)
110 [mimetype,cmd] = self.get_video_player(ext,dest)
113 print >>sys.stderr,"videoplay: play_file: cmd is",cmd
115 self.launch_video_player(cmd)
117 def play_file_via_httpserv(self,dest):
118 """ Play a file via our internal HTTP server. Needed when the user
119 selected embedded VLC as player and the filename contains Unicode
123 print >>sys.stderr,"videoplay: Playing file with Unicode filename via HTTP"
125 (prefix,ext) = os.path.splitext(dest)
126 videourl = self.create_url(self.videohttpserv,'/'+os.path.basename(prefix+ext))
127 [mimetype,cmd] = self.get_video_player(ext,videourl)
129 stream = open(dest,"rb")
130 stats = os.stat(dest)
131 length = stats.st_size
132 streaminfo = {'mimetype':mimetype,'stream':stream,'length':length}
133 self.videohttpserv.set_inputstream(streaminfo)
135 self.launch_video_player(cmd)
139 def play_url(self,url):
140 """ Play video file from network or disk """
142 print >>sys.stderr,"videoplay: Playing file from url",url
144 self.determine_playbackmode()
146 t = urlparse.urlsplit(url)
149 # VLC will play .flv files, but doesn't like the URLs that YouTube uses,
151 if self.playbackmode != PLAYBACKMODE_INTERNAL:
152 if sys.platform == 'win32':
153 x = [t[0],t[1],t[2],t[3],t[4]]
154 n = urllib.quote(x[2])
156 print >>sys.stderr,"videoplay: play_url: OLD PATH WAS",x[2],"NEW PATH",n
158 n = urllib.quote(x[3])
160 print >>sys.stderr,"videoplay: play_url: OLD QUERY WAS",x[3],"NEW PATH",n
162 url = urlparse.urlunsplit(x)
163 elif url[0] != '"' and url[0] != "'":
164 # to prevent shell escape problems
165 # TODO: handle this case in escape_path() that now just covers spaces
168 (prefix,ext) = os.path.splitext(dest)
169 [mimetype,cmd] = self.get_video_player(ext,url)
172 print >>sys.stderr,"videoplay: play_url: cmd is",cmd
174 self.launch_video_player(cmd)
177 def play_stream(self,streaminfo):
179 print >>sys.stderr,"videoplay: play_stream"
181 self.determine_playbackmode()
183 if self.playbackmode == PLAYBACKMODE_INTERNAL:
184 if USE_VLC_RAW_INTERFACE:
185 # Play using direct callbacks from the VLC C-code
186 self.launch_video_player(None,streaminfo=streaminfo)
188 # Play via internal HTTP server
189 self.videohttpserv.set_inputstream(streaminfo,'/')
190 url = self.create_url(self.videohttpserv,'/')
192 self.launch_video_player(url,streaminfo=streaminfo)
194 # External player, play stream via internal HTTP server
196 self.videohttpserv.set_inputstream(streaminfo,path)
197 url = self.create_url(self.videohttpserv,path)
199 [mimetype,cmd] = self.get_video_player(None,url,mimetype=streaminfo['mimetype'])
200 self.launch_video_player(cmd)
203 def launch_video_player(self,cmd,streaminfo=None):
204 if self.playbackmode == PLAYBACKMODE_INTERNAL:
207 # Play URL from network or disk
208 self.videoframe.get_videopanel().Load(cmd,streaminfo=streaminfo)
210 # Play using direct callbacks from the VLC C-code
211 self.videoframe.get_videopanel().Load(cmd,streaminfo=streaminfo)
213 self.videoframe.show_videoframe()
214 self.videoframe.get_videopanel().StartPlay()
216 # Launch an external player
217 # Play URL from network or disk
218 self.exec_video_player(cmd)
221 def stop_playback(self,reset=False):
222 """ Stop playback in current video window """
223 if self.playbackmode == PLAYBACKMODE_INTERNAL and self.videoframe is not None:
224 self.videoframe.get_videopanel().Stop()
226 self.videoframe.get_videopanel().Reset()
227 self.set_vod_download(None)
229 def show_loading(self):
230 if self.playbackmode == PLAYBACKMODE_INTERNAL and self.videoframe is not None:
231 self.videoframe.get_videopanel().ShowLoading()
234 """ Stop playback and close current video window """
235 if self.playbackmode == PLAYBACKMODE_INTERNAL and self.videoframe is not None:
236 self.videoframe.hide_videoframe()
237 self.set_vod_download(None)
239 def play(self,ds, selectedinfilename=None):
240 """ Used by Tribler Main """
241 self.determine_playbackmode()
243 d = ds.get_download()
245 videofiles = d.get_dest_files(exts=videoextdefaults)
247 if len(videofiles) == 0:
248 print >>sys.stderr,"videoplay: play: No video files found! Let user select"
249 # Let user choose any file
250 videofiles = d.get_dest_files(exts=None)
253 selectedoutfilename= None
254 if selectedinfilename is None:
255 # User didn't select file to play, select if there is a single, or ask
256 if len(videofiles) > 1:
258 for infilename,diskfilename in videofiles:
259 infilenames.append(infilename)
260 selectedinfilename = self.ask_user_to_select_video(infilenames)
261 print >> sys.stderr , "selectedinfilename == None" , selectedinfilename , len(selectedinfilename)
262 if selectedinfilename is None:
263 print >>sys.stderr,"videoplay: play: User selected no video"
265 for infilename,diskfilename in videofiles:
266 if infilename == selectedinfilename:
267 selectedoutfilename = diskfilename
269 selectedinfilename = videofiles[0][0]
270 selectedoutfilename = videofiles[0][1]
272 #print >> sys.stderr , "videoplay: play: selectedinfilename not None" , selectedinfilename , len(selectedinfilename)
273 for infilename,diskfilename in videofiles:
274 if infilename == selectedinfilename:
275 selectedoutfilename = diskfilename
276 if self.videoframe is not None:
277 self.videoframe.get_videopanel().SetLoadingText(selectedinfilename)
279 # 23/02/10 Boudewijn: This Download does not contain the
280 # selectedinfilename in the available files. It is likely
281 # that this is a multifile torrent and that another file was
282 # previously selected for download.
283 if selectedoutfilename is None:
284 return self.play_vod(ds, selectedinfilename)
286 print >> sys.stderr , "videoplay: play: PROGRESS" , ds.get_progress()
287 complete = ds.get_progress() == 1.0 or ds.get_status() == DLSTATUS_SEEDING
289 bitrate = tdef.get_bitrate(selectedinfilename)
290 if bitrate is None and not complete:
291 video_analyser_path = self.utility.config.Read('videoanalyserpath')
292 if not os.access(video_analyser_path,os.F_OK):
293 self.onError(self.utility.lang.get('videoanalysernotfound'),video_analyser_path,self.utility.lang.get('videoanalyserwhereset'))
296 # The VLC MediaControl API's playlist_add_item() doesn't accept unicode filenames.
297 # So if the file to play is unicode we play it via HTTP. The alternative is to make
298 # Tribler save the data in non-unicode filenames.
300 flag = self.playbackmode == PLAYBACKMODE_INTERNAL and not self.is_ascii_filename(selectedoutfilename)
303 print >> sys.stderr, 'videoplay: play: complete'
305 self.play_file_via_httpserv(selectedoutfilename)
307 self.play_file(selectedoutfilename)
309 self.manage_others_when_playing_from_file(d)
310 # Fake it, to get DL status reporting for right Download
311 self.set_vod_download(d)
313 print >> sys.stderr, 'videoplay: play: not complete'
314 self.play_vod(ds,selectedinfilename)
317 def play_vod(self,ds,infilename):
318 """ Called by GUI thread when clicking "Play ASAP" button """
320 d = ds.get_download()
322 # For multi-file torrent: when the user selects a different file, play that
323 oldselectedfile = None
324 if not tdef.get_live() and ds.is_vod() and tdef.is_multifile_torrent():
325 oldselectedfiles = d.get_selected_files()
326 oldselectedfile = oldselectedfiles[0] # Should be just one
328 # 1. (Re)Start torrent in VOD mode
329 switchfile = (oldselectedfile is not None and oldselectedfile != infilename)
331 print >> sys.stderr, ds.is_vod() , switchfile , tdef.get_live()
332 if not ds.is_vod() or switchfile or tdef.get_live():
336 if self.playbackmode == PLAYBACKMODE_INTERNAL:
337 self.videoframe.get_videopanel().Reset()
339 #[proceed,othertorrentspolicy] = self.warn_user(ds,infilename)
341 othertorrentspolicy = OTHERTORRENTS_STOP_RESTART
348 print >>sys.stderr,"videoplay: play_vod: Enabling VOD on torrent",`d.get_def().get_name()`
350 self.manage_other_downloads(othertorrentspolicy,targetd = d)
353 d.set_video_event_callback(self.sesscb_vod_event_callback)
354 d.set_video_events(self.get_supported_vod_events())
355 if d.get_def().is_multifile_torrent():
356 d.set_selected_files([infilename])
357 print >>sys.stderr,"videoplay: play_vod: Restarting existing Download",`ds.get_download().get_def().get_infohash()`
358 self.set_vod_download(d)
361 def restart_other_downloads(self, download_state_list):
362 def get_vod_download_status(default):
363 for download_state in download_state_list:
364 if self.vod_download == download_state.get_download():
365 return download_state.get_status()
368 if self.resume_by_system:
369 # resume when there is no VOD download
370 if self.vod_download is None:
371 self.resume_by_system += 1
372 if DEBUG: print >> sys.stderr, "VideoPlayer: restart_other_downloads: Resume because vod_download is None", "(%d)" % self.resume_by_system
374 # resume when the VOD download is not part of download_state_list
375 elif not self.vod_download in [download_state.get_download() for download_state in download_state_list]:
376 self.resume_by_system += 1
378 print >> sys.stderr, "VideoPlayer: restart_other_downloads: Resume because", `self.vod_download.get_def().get_name()`, "not in list", "(%d)" % self.resume_by_system
379 print >> sys.stderr, "VideoPlayer: list:", `[download_state.get_download().get_def().get_name() for download_state in download_state_list]`
381 # resume when the VOD download has finished downloading
382 elif not get_vod_download_status(DLSTATUS_ALLOCATING_DISKSPACE) in (DLSTATUS_ALLOCATING_DISKSPACE, DLSTATUS_WAITING4HASHCHECK, DLSTATUS_HASHCHECKING, DLSTATUS_DOWNLOADING):
383 self.resume_by_system += 1
385 print >> sys.stderr, "VideoPlayer: restart_other_downloads: Resume because vod_download_status is inactive", "(%d)" % self.resume_by_system
386 print >> sys.stderr, "VideoPlayer: status:", dlstatus_strings[get_vod_download_status(DLSTATUS_ALLOCATING_DISKSPACE)]
388 # otherwise we do not resume
390 self.resume_by_system = 1
392 # because of threading issues it is possible that we have
393 # false positives. therefore we will only resume torrents
394 # after we checked 2 times (once every second)
395 if self.resume_by_system > 2:
396 self.resume_by_system = 0
398 # sometimes the self.vod_download stays set to a
399 # download class that is no longer downloading
400 self.set_vod_download(None)
402 for download_state in download_state_list:
403 download = download_state.get_download()
404 torrent_def = download.get_def()
405 infohash = torrent_def.get_infohash()
407 from BaseLib.Main.vwxGUI.UserDownloadChoice import UserDownloadChoice
408 self.user_download_choice = UserDownloadChoice.get_singleton()
409 user_state = self.user_download_choice.get_download_state(infohash)
411 # resume a download unless the user explisitly
412 # stopped the download
413 if not user_state == "stop":
414 if DEBUG: print >> sys.stderr, "VideoPlayer: restart_other_downloads: Restarting", `download.get_def().get_name()`
415 download.set_mode(DLMODE_NORMAL)
418 def manage_other_downloads(self,othertorrentspolicy, targetd = None):
419 self.resume_by_system = 1
420 if DEBUG: print >> sys.stderr, "VideoPlayer: manage_other_downloads"
422 policy_stop = othertorrentspolicy == OTHERTORRENTS_STOP or \
423 othertorrentspolicy == OTHERTORRENTS_STOP_RESTART
425 for download in self.utility.session.get_downloads():
426 if download.get_def().get_live():
427 # Filter out live torrents, they are always
428 # removed. They stay in myPreferenceDB so can be
430 if DEBUG: print >>sys.stderr,"VideoPlayer: manage_other_downloads: Remove live", `download.get_def().get_name()`
431 self.utility.session.remove_download(download)
433 elif download == targetd:
434 if DEBUG: print >>sys.stderr,"VideoPlayer: manage_other_downloads: Leave", `download.get_def().get_name()`
438 if DEBUG: print >>sys.stderr,"VideoPlayer: manage_other_downloads: Stop", `download.get_def().get_name()`
442 if DEBUG: print >>sys.stderr,"VideoPlayer: manage_other_downloads: Ignore", `download.get_def().get_name()`
444 def manage_others_when_playing_from_file(self,targetd):
445 """ When playing from file, make sure all other Downloads are no
446 longer in VOD mode, so they won't interrupt the playback.
448 activetorrents = self.utility.session.get_downloads()
449 for d in activetorrents:
450 if d.get_mode() == DLMODE_VOD:
451 if d.get_def().get_live():
452 #print >>sys.stderr,"videoplay: manage_when_file_play: Removing live",`d.get_def().get_name()`
453 self.utility.session.remove_download(d)
455 #print >>sys.stderr,"videoplay: manage_when_file_play: Restarting in NORMAL mode",`d.get_def().get_name()`
457 d.set_mode(DLMODE_NORMAL)
463 def start_and_play(self,tdef,dscfg, selectedinfilename = None):
464 """ Called by GUI thread when Tribler started with live or video torrent on cmdline """
466 # ARNO50: > Preview1: TODO: make sure this works better when Download already existed.
469 if selectedinfilename == None:
470 if not tdef.get_live():
471 videofiles = tdef.get_files(exts=videoextdefaults)
472 if len(videofiles) == 1:
473 selectedinfilename = videofiles[0]
474 elif len(videofiles) > 1:
475 selectedinfilename = self.ask_user_to_select_video(videofiles)
477 if selectedinfilename or tdef.get_live():
478 if tdef.is_multifile_torrent():
479 dscfg.set_selected_files([selectedinfilename])
481 othertorrentspolicy = OTHERTORRENTS_STOP_RESTART
482 self.manage_other_downloads(othertorrentspolicy,targetd = None)
485 dscfg.set_video_event_callback(self.sesscb_vod_event_callback)
486 dscfg.set_video_events(self.get_supported_vod_events())
487 print >>sys.stderr,"videoplay: Starting new VOD/live Download",`tdef.get_name()`
489 download = self.utility.session.start_download(tdef,dscfg)
491 if self.videoframe is not None:
492 self.videoframe.get_videopanel().SetLoadingText(selectedinfilename)
495 self.set_vod_download(download)
501 def sesscb_vod_event_callback(self,d,event,params):
502 """ Called by the Session when the content of the Download is ready
504 Called by Session thread """
506 print >>sys.stderr,"videoplay: sesscb_vod_event_callback called",currentThread().getName(),"###########################################################"
507 wx.CallAfter(self.gui_vod_event_callback,d,event,params)
509 def gui_vod_event_callback(self,d,event,params):
510 """ Also called by SwarmPlayer """
512 print >>sys.stderr,"videoplay: gui_vod_event:",event
513 if event == VODEVENT_START:
514 filename = params["filename"]
515 mimetype = params["mimetype"]
516 stream = params["stream"]
517 length = params["length"]
520 self.play_file(filename)
522 if d.get_def().get_live():
524 blocksize = d.get_def().get_piece_length()
526 piecelen = d.get_def().get_piece_length()
527 if piecelen > 2 ** 17:
529 # Workaround for streams with really large piece
530 # sizes. For some content/containers, VLC can do
531 # GET X-, GET X+10K-, GET X+20K HTTP requests
532 # and we would answer these by putting megabytes
533 # into the stream buffer, of which only 10K would be
534 # used. This kills performance. Hence I add a caching
535 # stream that tries to resolve answers from its internal
536 # buffer, before reading the engine's stream.
537 # This works, but only if the HTTP server doesn't
538 # read too aggressively, i.e., uses small blocksize.
540 cachestream = SmartCachingStream(stream)
542 blocksize = max(32768,piecelen/8)
547 if d.get_def().get_live() and is_ogg(d.get_def().get_name_as_unicode()):
548 # Live Ogg stream. To support this we need to do
550 # 1. Write Ogg headers (stored in .tstream)
551 # 2. Find first Ogg page in stream.
552 cachestream = OggMagicLiveStream(d.get_def(),stream)
555 # Estimate duration. Video player (e.g. VLC) often can't tell
558 if d.get_def().get_live():
559 # Set correct Ogg MIME type
560 if is_ogg(d.get_def().get_name_as_unicode()):
561 params['mimetype'] = 'application/ogg'
564 if d.get_def().is_multifile_torrent():
565 file = d.get_selected_files()[0]
566 bitrate = d.get_def().get_bitrate(file)
567 if bitrate is not None:
568 estduration = float(length) / float(bitrate)
570 # Set correct Ogg MIME type
572 if is_ogg(d.get_def().get_name_as_unicode()):
573 params['mimetype'] = 'application/ogg'
576 params['mimetype'] = 'application/ogg'
580 streaminfo = {'mimetype':mimetype,'stream':cachestream,'length':length,'blocksize':blocksize,'estduration':estduration}
581 self.play_stream(streaminfo)
583 elif event == VODEVENT_PAUSE:
584 if self.videoframe is not None:
585 self.videoframe.get_videopanel().PlayPause()
586 self.set_player_status("Buffering...")
587 elif event == VODEVENT_RESUME:
588 if self.videoframe is not None:
589 self.videoframe.get_videopanel().PlayPause()
590 self.set_player_status("")
592 def ask_user_to_select_video(self,videofiles):
593 dlg = VideoChooser(self.videoframe.get_window(),self.utility,videofiles,title='Tribler',expl='Select which file to play')
594 result = dlg.ShowModal()
595 if result == wx.ID_OK:
596 index = dlg.getChosenIndex()
597 filename = videofiles[index]
603 def is_ascii_filename(self,filename):
604 if isinstance(filename,str):
607 filename.encode('ascii','strict')
613 def warn_user(self,ds,infilename):
615 islive = ds.get_download().get_def().get_live()
616 if islive and not self.other_downloads:
617 # If it's the only download and live, don't warn.
620 dlg = VODWarningDialog(self.videoframe.get_window(),self.utility,ds,infilename,self.other_downloads,islive)
621 result = dlg.ShowModal()
622 othertorrentspolicy = dlg.get_othertorrents_policy()
624 return [result == wx.ID_OK,othertorrentspolicy]
626 def create_url(self,videoserver,upath):
627 schemeserv = 'http://127.0.0.1:'+str(videoserver.get_port())
628 asciipath = unicode2str(upath)
629 return schemeserv+urllib.quote(asciipath)
633 def get_video_player(self,ext,videourl,mimetype=None):
635 video_player_path = self.utility.config.Read('videoplayerpath')
637 print >>sys.stderr,"videoplay: Default player is",video_player_path
640 if sys.platform == 'win32':
641 # TODO: Use Python's mailcap facility on Linux to find player
642 [mimetype,playcmd] = win32_retrieve_video_play_command(ext,videourl)
644 print >>sys.stderr,"videoplay: Win32 reg said playcmd is",playcmd
648 # Arno, 2010-01-08: Hmmm... video/avi is not official registered at IANA
649 mimetype = 'video/avi'
650 elif ext == '.mpegts' or ext == '.ts':
651 mimetype = 'video/mp2t'
653 mimetype = 'video/mpeg'
655 if sys.platform == 'win32':
656 [mimetype,playcmd] = win32_retrieve_playcmd_from_mimetype(mimetype,videourl)
658 if self.playbackmode == PLAYBACKMODE_INTERNAL:
660 print >>sys.stderr,"videoplay: using internal player"
661 return [mimetype,videourl]
662 elif self.playbackmode == PLAYBACKMODE_EXTERNAL_MIME and sys.platform == 'win32':
663 if playcmd is not None:
664 cmd = 'start /B "TriblerVideo" '+playcmd
665 return [mimetype,cmd]
668 print >>sys.stderr,"videoplay: Defaulting to default player",video_player_path
669 qprogpath = quote_program_path(video_player_path)
670 #print >>sys.stderr,"videoplay: Defaulting to quoted prog",qprogpath
671 if qprogpath is None:
673 qvideourl = self.escape_path(videourl)
674 playcmd = qprogpath+' '+qvideourl
675 if sys.platform == 'win32':
676 cmd = 'start /B "TriblerVideo" '+playcmd
677 elif sys.platform == 'darwin':
678 cmd = 'open -a '+playcmd
682 print >>sys.stderr,"videoplay: using external user-defined player by executing ",cmd
683 return [mimetype,cmd]
687 def exec_video_player(self,cmd):
689 print >>sys.stderr,"videoplay: Command is @"+cmd+"@"
690 # I get a weird problem on Linux. When doing a
691 # os.popen2("vlc /tmp/file.wmv") I get the following error:
692 #[00000259] main interface error: no suitable interface module
693 #[00000001] main private error: interface "(null)" initialization failed
695 # The only thing that appears to work is
696 # os.system("vlc /tmp/file.wmv")
697 # but that halts Tribler, as it waits for the created shell to
701 if sys.platform == 'win32':
703 (self.player_out,self.player_in) = os.popen2( cmd, 'b' )
705 (self.player_out,self.player_in) = os.popen2( cmd, 'b' )
708 self.onError(self.utility.lang.get('videoplayerstartfailure'),cmd,str(e.__class__)+':'+str(e))
712 def escape_path(self,path):
713 if path[0] != '"' and path[0] != "'" and path.find(' ') != -1:
714 if sys.platform == 'win32':
716 path = "\""+path+"\""
718 path = "\'"+path+"\'"
722 def onError(self,action,value,errmsg=u''):
723 self.onMessage(wx.ICON_ERROR,action,value,errmsg)
725 def onWarning(self,action,value,errmsg=u''):
726 self.onMessage(wx.ICON_INFORMATION,action,value,errmsg)
728 def onMessage(self,icon,action,value,errmsg=u''):
729 # Don't use language independence stuff, self.utility may not be
737 dlg = wx.MessageDialog(None, msg, self.utility.lang.get('videoplayererrortitle'), wx.OK|icon)
738 result = dlg.ShowModal()
741 def set_vod_download(self,d):
742 self.vod_download = d
744 def get_vod_download(self):
745 return self.vod_download
748 # Set information about video playback progress that is displayed
751 def set_content_name(self,name):
752 if self.videoframe is not None:
753 self.videoframe.get_videopanel().SetContentName(name)
755 def set_content_image(self,wximg):
756 if self.videoframe is not None:
757 self.videoframe.get_videopanel().SetContentImage(wximg)
759 def set_player_status(self,msg):
760 if self.videoframe is not None:
761 self.videoframe.get_videopanel().SetPlayerStatus(msg)
763 def set_player_status_and_progress(self,msg,pieces_complete):
764 if self.videoframe is not None:
765 self.videoframe.get_videopanel().UpdateStatus(msg,pieces_complete)
767 def set_save_button(self,enable,savebutteneventhandler):
768 if self.playbackmode == PLAYBACKMODE_INTERNAL and self.videoframe is not None:
769 self.videoframe.get_videopanel().EnableSaveButton(enable,savebutteneventhandler)
772 if self.playbackmode == PLAYBACKMODE_INTERNAL and self.videoframe is not None:
773 return self.videoframe.get_videopanel().GetState()
775 return MEDIASTATE_PLAYING
777 def determine_playbackmode(self):
778 feasible = return_feasible_playback_modes(self.utility.getPath())
779 if self.preferredplaybackmode in feasible:
780 self.playbackmode = self.preferredplaybackmode
782 self.playbackmode = feasible[0]
784 def get_playbackmode(self):
785 return self.playbackmode
787 #def set_preferredplaybackmode(self,mode):
788 # This is a bit complex: If there is no int. player avail we change
789 # the VideoFrame to contain some minimal info. Would have to dynamically
790 # change that back if we allow dynamic switching of video player.
791 # self.preferredplaybackmode = mode
796 def videohttpserver_error_callback(self,e,url):
797 """ Called by HTTP serving thread """
798 wx.CallAfter(self.videohttpserver_error_guicallback,e,url)
800 def videohttpserver_error_guicallback(self,e,url):
801 print >>sys.stderr,"videoplay: Video HTTP server reported error",str(e)
802 # if e[0] == ECONNRESET and self.closeextplayercallback is not None:
803 if self.closeextplayercallback is not None:
804 self.closeextplayercallback()
806 def videohttpserver_set_status_callback(self,status):
807 """ Called by HTTP serving thread """
808 wx.CallAfter(self.videohttpserver_set_status_guicallback,status)
810 def videohttpserver_set_status_guicallback(self,status):
811 self.videoframe.get_videopanel().SetPlayerStatus(status)
816 class VideoChooser(wx.Dialog):
818 def __init__(self,parent,utility,filelist,title=None,expl=None):
820 self.utility = utility
823 # Convert to Unicode for display
824 for file in filelist:
825 u = bin2unicode(file)
826 self.filelist.append(u)
829 print >>sys.stderr,"VideoChooser: filelist",self.filelist
831 style = wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER
833 title = self.utility.lang.get('selectvideofiletitle')
834 wx.Dialog.__init__(self,parent,-1,title,style=style)
836 sizer = wx.BoxSizer(wx.VERTICAL)
837 filebox = wx.BoxSizer(wx.VERTICAL)
838 self.file_chooser=wx.Choice(self, -1, wx.Point(-1, -1), wx.Size(300, -1), self.filelist)
839 self.file_chooser.SetSelection(0)
842 self.utility.lang.get('selectvideofile')
843 filebox.Add(wx.StaticText(self, -1, expl), 1, wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5)
844 filebox.Add(self.file_chooser)
845 sizer.Add(filebox, 0, wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5)
847 buttonbox = wx.BoxSizer(wx.HORIZONTAL)
848 okbtn = wx.Button(self, wx.ID_OK, label=self.utility.lang.get('ok'), style = wx.BU_EXACTFIT)
849 buttonbox.Add(okbtn, 0, wx.ALIGN_CENTER_VERTICAL|wx.LEFT|wx.RIGHT, 5)
850 cancelbtn = wx.Button(self, wx.ID_CANCEL, label=self.utility.lang.get('cancel'), style = wx.BU_EXACTFIT)
851 buttonbox.Add(cancelbtn, 0, wx.ALIGN_CENTER_VERTICAL|wx.LEFT|wx.RIGHT, 5)
852 sizer.Add(buttonbox, 0, wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5)
854 self.SetSizerAndFit(sizer)
856 def getChosenIndex(self):
857 return self.file_chooser.GetSelection()
861 class VODWarningDialog(wx.Dialog):
863 def __init__(self, parent, utility, ds, infilename, other_downloads, islive):
865 self.utility = utility
867 style = wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER
869 title = self.utility.lang.get('livewarntitle')
871 title = self.utility.lang.get('vodwarntitle')
873 wx.Dialog.__init__(self,parent,-1,title,style=style)
876 msg = self.utility.lang.get('livewarngeneral')
878 msg = self.utility.lang.get('vodwarngeneral')
882 msg += self.utility.lang.get('vodwarnbitrateunknown')
883 msg += self.is_mov_file(videoinfo)
884 msg += self.utility.lang.get('vodwarnconclusionno')
885 elif bitrate > maxuploadrate and maxuploadrate != 0:
886 s = self.utility.lang.get('vodwarnbitrateinsufficient') % (str(bitrate/1024),str(maxuploadrate)+" KB/s")
888 msg += self.is_mov_file(videoinfo)
889 msg += self.utility.lang.get('vodwarnconclusionno')
890 elif bitrate > maxmeasureduploadrate and maxuploadrate == 0:
891 s = self.utility.lang.get('vodwarnbitrateinsufficientmeasured') % (str(bitrate/1024),str(maxuploadrate)+" KB/s")
893 msg += self.is_mov_file(videoinfo)
894 msg += self.utility.lang.get('vodwarnconclusionno')
897 if maxuploadrate == 0:
898 rate = self.utility.lang.get('unlimited')
900 rate = str(maxuploadrate)+" KB/s"
901 s = self.utility.lang.get('vodwarnbitratesufficient') % (str(bitrate/1024),rate)
903 extra = self.is_mov_file(videoinfo)
905 msg += self.utility.lang.get('vodwarnconclusionyes')
908 msg += self.utility.lang.get('vodwarnconclusionno')
911 sizer = wx.BoxSizer(wx.VERTICAL)
912 text = wx.StaticText(self, -1, msg)
914 sizer.Add(text, 0, wx.ALIGN_CENTER_VERTICAL|wx.ALL|wx.EXPAND, 5)
916 # 22/08/08 boudewijn: only show the selectbox when there are
917 # torrents that are actively downloading
919 otherslist = [self.utility.lang.get('vodrestartothertorrents'),
920 self.utility.lang.get('vodstopothertorrents'),
921 self.utility.lang.get('vodleaveothertorrents')]
923 othersbox = wx.BoxSizer(wx.VERTICAL)
924 self.others_chooser=wx.Choice(self, -1, wx.Point(-1, -1), wx.Size(-1, -1), otherslist)
925 self.others_chooser.SetSelection(OTHERTORRENTS_STOP_RESTART)
927 othersbox.Add(wx.StaticText(self, -1, self.utility.lang.get('vodwhataboutothertorrentspolicy')), 1, wx.ALIGN_CENTER_VERTICAL)
928 othersbox.Add(self.others_chooser)
929 sizer.Add(othersbox, 0, wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5)
931 self.others_chooser = None
933 sizer.Add(wx.StaticText(self, -1, self.utility.lang.get('vodwarnprompt')), 0, wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5)
936 buttonbox = wx.BoxSizer(wx.HORIZONTAL)
937 okbtn = wx.Button(self, wx.ID_OK, label=self.utility.lang.get('yes'), style = wx.BU_EXACTFIT)
938 buttonbox.Add(okbtn, 0, wx.ALIGN_CENTER_VERTICAL|wx.LEFT|wx.RIGHT, 5)
939 cancelbtn = wx.Button(self, wx.ID_CANCEL, label=self.utility.lang.get('no'), style = wx.BU_EXACTFIT)
940 buttonbox.Add(cancelbtn, 0, wx.ALIGN_CENTER_VERTICAL|wx.LEFT|wx.RIGHT, 5)
941 sizer.Add(buttonbox, 1, wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5)
943 self.SetSizerAndFit(sizer)
945 def get_othertorrents_policy(self):
946 if self.others_chooser:
947 idx = self.others_chooser.GetSelection()
949 idx = OTHERTORRENTS_STOP_RESTART
951 print >>sys.stderr,"videoplay: Other-torrents-policy is",idx
954 def is_mov_file(self,videoinfo):
955 orig = videoinfo['inpath']
956 (prefix,ext) = os.path.splitext(orig)
959 return self.utility.lang.get('vodwarnmov')
964 def parse_playtime_to_secs(hhmmss):
966 print >>sys.stderr,"videoplay: Playtime is",hhmmss
967 r = re.compile("([0-9]+):*")
968 occ = r.findall(hhmmss)
973 t = int(occ[0])*3600 + int(occ[1])*60 + int(occ[2])
975 # minutes and seconds
976 t = int(occ[0])*60 + int(occ[1])
982 def return_feasible_playback_modes(syspath):
987 if USE_VLC_RAW_INTERFACE:
988 # check if the special raw interface is available
989 # pylint: disable-msg=E1101
990 if not inspect.ismethoddescriptor(vlc.MediaControl.set_raw_callbacks):
991 raise Exception("Incorrect vlc plugin. This does not provide the set_raw_callbacks method")
992 # pylint: enable-msg=E1101
993 vlcpath = os.path.join(syspath,"vlc")
994 if sys.platform == 'win32':
995 if os.path.isdir(vlcpath):
996 l.append(PLAYBACKMODE_INTERNAL)
998 l.append(PLAYBACKMODE_INTERNAL)
1002 if sys.platform == 'win32':
1003 l.append(PLAYBACKMODE_EXTERNAL_MIME)
1004 l.append(PLAYBACKMODE_EXTERNAL_DEFAULT)
1006 l.append(PLAYBACKMODE_EXTERNAL_DEFAULT)