instrumentation: add next-share/
[cs-p2p-next.git] / instrumentation / next-share / BaseLib / Video / VideoPlayer.py
1 # Written by Arno Bakker
2 # see LICENSE.txt for license information
3 from threading import currentThread
4 from traceback import print_exc
5 import inspect
6 import os
7 import re
8 import sys
9 import urllib
10 import urlparse
11 import wx
12
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
16
17 from BaseLib.Core.simpledefs import *
18 from BaseLib.Core.Utilities.unicode import unicode2str,bin2unicode
19
20 from BaseLib.Video.CachingStream import SmartCachingStream
21 from BaseLib.Video.Ogg import is_ogg,OggMagicLiveStream
22
23 DEBUG = False
24
25 if sys.platform == "linux2" or sys.platform == "darwin":
26     USE_VLC_RAW_INTERFACE = False
27 else:
28     USE_VLC_RAW_INTERFACE = False # False for Next-Share
29     
30
31 class VideoPlayer:
32     
33     __single = None
34     
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
46
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()
51
52         self.resume_by_system = 1
53         self.user_download_choice = None
54         
55     def getInstance(*args, **kw):
56         if VideoPlayer.__single is None:
57             VideoPlayer(*args, **kw)
58         return VideoPlayer.__single
59     getInstance = staticmethod(getInstance)
60         
61     def register(self,utility,preferredplaybackmode=None,closeextplayercallback=None):
62         
63         self.utility = utility # TEMPARNO: make sure only used for language strings
64
65         self.preferredplaybackmode = preferredplaybackmode
66         self.determine_playbackmode()
67
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
71             # blows up the GUI.
72             #
73             from BaseLib.Video.VLCWrapper import VLCWrapper
74             self.vlcwrap = VLCWrapper(self.utility.getPath())
75             self.supportedvodevents = [VODEVENT_START,VODEVENT_PAUSE,VODEVENT_RESUME]
76         else:
77             self.vlcwrap = None
78             # Can't pause when external player
79             self.supportedvodevents = [VODEVENT_START]
80             
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)
86             
87         if closeextplayercallback is not None:
88             self.closeextplayercallback = closeextplayercallback
89
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
93
94     def get_vlcwrap(self):
95         return self.vlcwrap
96     
97     def get_supported_vod_events(self):
98         return self.supportedvodevents
99
100     def set_videoframe(self,videoframe):
101         self.videoframe = videoframe
102
103
104     def play_file(self,dest): 
105         """ Play video file from disk """
106         if DEBUG:
107             print >>sys.stderr,"videoplay: Playing file from disk",dest
108
109         (prefix,ext) = os.path.splitext(dest)
110         [mimetype,cmd] = self.get_video_player(ext,dest)
111         
112         if DEBUG:
113             print >>sys.stderr,"videoplay: play_file: cmd is",cmd
114  
115         self.launch_video_player(cmd)
116
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
120         characters.
121         """ 
122         if DEBUG:
123             print >>sys.stderr,"videoplay: Playing file with Unicode filename via HTTP"
124
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)
128
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)
134         
135         self.launch_video_player(cmd)
136
137
138  
139     def play_url(self,url):
140         """ Play video file from network or disk """
141         if DEBUG:
142             print >>sys.stderr,"videoplay: Playing file from url",url
143         
144         self.determine_playbackmode()
145         
146         t = urlparse.urlsplit(url)
147         dest = t[2]
148         
149         # VLC will play .flv files, but doesn't like the URLs that YouTube uses,
150         # so quote them
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])
155                 if DEBUG:
156                     print >>sys.stderr,"videoplay: play_url: OLD PATH WAS",x[2],"NEW PATH",n
157                 x[2] = n
158                 n = urllib.quote(x[3])
159                 if DEBUG:
160                     print >>sys.stderr,"videoplay: play_url: OLD QUERY WAS",x[3],"NEW PATH",n
161                 x[3] = 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
166                 url = "'"+url+"'" 
167
168         (prefix,ext) = os.path.splitext(dest)
169         [mimetype,cmd] = self.get_video_player(ext,url)
170         
171         if DEBUG:
172             print >>sys.stderr,"videoplay: play_url: cmd is",cmd
173         
174         self.launch_video_player(cmd)
175
176
177     def play_stream(self,streaminfo):
178         if DEBUG:
179             print >>sys.stderr,"videoplay: play_stream"
180
181         self.determine_playbackmode()
182
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)
187             else:
188                 # Play via internal HTTP server
189                 self.videohttpserv.set_inputstream(streaminfo,'/')
190                 url = self.create_url(self.videohttpserv,'/')
191
192                 self.launch_video_player(url,streaminfo=streaminfo)
193         else:
194             # External player, play stream via internal HTTP server
195             path = '/'
196             self.videohttpserv.set_inputstream(streaminfo,path)
197             url = self.create_url(self.videohttpserv,path)
198
199             [mimetype,cmd] = self.get_video_player(None,url,mimetype=streaminfo['mimetype'])
200             self.launch_video_player(cmd)
201
202
203     def launch_video_player(self,cmd,streaminfo=None):
204         if self.playbackmode == PLAYBACKMODE_INTERNAL:
205
206             if cmd is not None:
207                 # Play URL from network or disk
208                 self.videoframe.get_videopanel().Load(cmd,streaminfo=streaminfo)
209             else:
210                 # Play using direct callbacks from the VLC C-code
211                 self.videoframe.get_videopanel().Load(cmd,streaminfo=streaminfo)
212
213             self.videoframe.show_videoframe()
214             self.videoframe.get_videopanel().StartPlay()
215         else:
216             # Launch an external player
217             # Play URL from network or disk
218             self.exec_video_player(cmd)
219
220
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()
225             if reset:
226                 self.videoframe.get_videopanel().Reset()
227         self.set_vod_download(None)
228
229     def show_loading(self):
230         if self.playbackmode == PLAYBACKMODE_INTERNAL and self.videoframe is not None:
231             self.videoframe.get_videopanel().ShowLoading()
232
233     def close(self):
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)
238
239     def play(self,ds, selectedinfilename=None):
240         """ Used by Tribler Main """
241         self.determine_playbackmode()
242         
243         d = ds.get_download()
244         tdef = d.get_def()
245         videofiles = d.get_dest_files(exts=videoextdefaults)
246         
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)
251             
252             
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:
257                 infilenames = []
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"
264                     return
265                 for infilename,diskfilename in videofiles:
266                     if infilename == selectedinfilename:
267                         selectedoutfilename = diskfilename
268             else:
269                 selectedinfilename = videofiles[0][0]
270                 selectedoutfilename = videofiles[0][1]
271         else:
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)
278                 
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)
285
286         print >> sys.stderr , "videoplay: play: PROGRESS" , ds.get_progress()
287         complete = ds.get_progress() == 1.0 or ds.get_status() == DLSTATUS_SEEDING
288
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'))
294                 return
295
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.
299         #
300         flag = self.playbackmode == PLAYBACKMODE_INTERNAL and not self.is_ascii_filename(selectedoutfilename)
301         
302         if complete:
303             print >> sys.stderr, 'videoplay: play: complete'
304             if flag:
305                 self.play_file_via_httpserv(selectedoutfilename)
306             else:
307                 self.play_file(selectedoutfilename)
308             
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)
312         else:
313             print >> sys.stderr, 'videoplay: play: not complete'
314             self.play_vod(ds,selectedinfilename)
315
316
317     def play_vod(self,ds,infilename):
318         """ Called by GUI thread when clicking "Play ASAP" button """
319
320         d = ds.get_download()
321         tdef = d.get_def()
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
327         
328         # 1. (Re)Start torrent in VOD mode
329         switchfile = (oldselectedfile is not None and oldselectedfile != infilename) 
330
331         print >> sys.stderr, ds.is_vod() , switchfile , tdef.get_live()
332         if not ds.is_vod() or switchfile or tdef.get_live():
333
334             
335             if switchfile:
336                 if self.playbackmode == PLAYBACKMODE_INTERNAL:
337                     self.videoframe.get_videopanel().Reset()
338             
339             #[proceed,othertorrentspolicy] = self.warn_user(ds,infilename)
340             proceed = True
341             othertorrentspolicy = OTHERTORRENTS_STOP_RESTART
342             
343             if not proceed:
344                 # User bailing out
345                 return
346
347             if DEBUG:
348                 print >>sys.stderr,"videoplay: play_vod: Enabling VOD on torrent",`d.get_def().get_name()`
349
350             self.manage_other_downloads(othertorrentspolicy,targetd = d)
351
352             # Restart download
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)
359             d.restart()
360
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()
366             return default
367
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
373
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
377                 if DEBUG:
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]`
380
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
384                 if DEBUG:
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)]
387
388             # otherwise we do not resume
389             else:
390                 self.resume_by_system = 1
391
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
397
398                 # sometimes the self.vod_download stays set to a
399                 # download class that is no longer downloading
400                 self.set_vod_download(None)
401
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()
406                     
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)
410
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)
416                         download.restart()
417
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"
421
422         policy_stop = othertorrentspolicy == OTHERTORRENTS_STOP or \
423                       othertorrentspolicy == OTHERTORRENTS_STOP_RESTART
424
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
429                 # restarted.
430                 if DEBUG: print >>sys.stderr,"VideoPlayer: manage_other_downloads: Remove live", `download.get_def().get_name()`
431                 self.utility.session.remove_download(download)
432
433             elif download == targetd:
434                 if DEBUG: print >>sys.stderr,"VideoPlayer: manage_other_downloads: Leave", `download.get_def().get_name()`
435                 download.stop()
436                 
437             elif policy_stop:
438                 if DEBUG: print >>sys.stderr,"VideoPlayer: manage_other_downloads: Stop", `download.get_def().get_name()`
439                 download.stop()
440
441             else:
442                 if DEBUG: print >>sys.stderr,"VideoPlayer: manage_other_downloads: Ignore", `download.get_def().get_name()`
443
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.
447         """
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)
454                 else:
455                     #print >>sys.stderr,"videoplay: manage_when_file_play: Restarting in NORMAL mode",`d.get_def().get_name()`
456                     d.stop()
457                     d.set_mode(DLMODE_NORMAL)
458                     d.restart()
459
460
461
462
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 """
465
466         # ARNO50: > Preview1: TODO: make sure this works better when Download already existed.
467         
468
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)
476
477         if selectedinfilename or tdef.get_live():
478             if tdef.is_multifile_torrent():
479                 dscfg.set_selected_files([selectedinfilename])
480
481             othertorrentspolicy = OTHERTORRENTS_STOP_RESTART
482             self.manage_other_downloads(othertorrentspolicy,targetd = None)
483
484             # Restart download
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()`
488
489             download = self.utility.session.start_download(tdef,dscfg)
490    
491             if self.videoframe is not None:         
492                 self.videoframe.get_videopanel().SetLoadingText(selectedinfilename)
493             
494
495             self.set_vod_download(download)
496             return download
497         else:
498             return None
499         
500     
501     def sesscb_vod_event_callback(self,d,event,params):
502         """ Called by the Session when the content of the Download is ready
503          
504         Called by Session thread """
505         
506         print >>sys.stderr,"videoplay: sesscb_vod_event_callback called",currentThread().getName(),"###########################################################"
507         wx.CallAfter(self.gui_vod_event_callback,d,event,params)
508
509     def gui_vod_event_callback(self,d,event,params):
510         """ Also called by SwarmPlayer """
511
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"]
518
519             if filename:
520                 self.play_file(filename)
521             else:
522                 if d.get_def().get_live():
523                     cachestream = stream
524                     blocksize = d.get_def().get_piece_length()
525                 else:
526                     piecelen = d.get_def().get_piece_length()
527                     if piecelen > 2 ** 17:
528                         # Arno, 2010-01-21:
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.
539                         #
540                         cachestream = SmartCachingStream(stream)
541
542                         blocksize = max(32768,piecelen/8)
543                     else:
544                         cachestream = stream
545                         blocksize = piecelen
546
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
549                     # two things:
550                     # 1. Write Ogg headers (stored in .tstream)
551                     # 2. Find first Ogg page in stream.
552                     cachestream = OggMagicLiveStream(d.get_def(),stream)
553
554                 
555                 # Estimate duration. Video player (e.g. VLC) often can't tell
556                 # when streaming.
557                 estduration = None
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'
562                 else:
563                     file = None
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)
569                     
570                     # Set correct Ogg MIME type
571                     if file is None:
572                         if is_ogg(d.get_def().get_name_as_unicode()):
573                             params['mimetype'] = 'application/ogg'
574                     else:
575                         if is_ogg(file):
576                             params['mimetype'] = 'application/ogg'
577                         
578
579                     
580                 streaminfo = {'mimetype':mimetype,'stream':cachestream,'length':length,'blocksize':blocksize,'estduration':estduration}
581                 self.play_stream(streaminfo)
582                 
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("")
591
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]
598         else:
599             filename = None
600         dlg.Destroy()
601         return filename
602
603     def is_ascii_filename(self,filename):
604         if isinstance(filename,str):
605             return True
606         try:
607             filename.encode('ascii','strict')
608             return True
609         except:
610             print_exc()
611             return False
612
613     def warn_user(self,ds,infilename):
614         
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.
618             return
619         
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()
623         dlg.Destroy()
624         return [result == wx.ID_OK,othertorrentspolicy]
625
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)
630
631
632
633     def get_video_player(self,ext,videourl,mimetype=None):
634
635         video_player_path = self.utility.config.Read('videoplayerpath')
636         if DEBUG:
637             print >>sys.stderr,"videoplay: Default player is",video_player_path
638
639         if mimetype is None:
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)
643                 if DEBUG:
644                     print >>sys.stderr,"videoplay: Win32 reg said playcmd is",playcmd
645                     
646             if mimetype is None:
647                 if ext == '.avi':
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'
652                 else:
653                     mimetype = 'video/mpeg'
654         else:
655             if sys.platform == 'win32':
656                 [mimetype,playcmd] = win32_retrieve_playcmd_from_mimetype(mimetype,videourl)
657
658         if self.playbackmode == PLAYBACKMODE_INTERNAL:
659             if DEBUG:
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]
666
667         if DEBUG:
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:
672             return [None,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
679         else:
680             cmd = playcmd
681         if DEBUG:
682             print >>sys.stderr,"videoplay: using external user-defined player by executing ",cmd
683         return [mimetype,cmd]
684
685
686
687     def exec_video_player(self,cmd):
688         if DEBUG:
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
694         #
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
698         # finish. Hmmmm....
699         #
700         try:
701             if sys.platform == 'win32':
702                 #os.system(cmd)
703                 (self.player_out,self.player_in) = os.popen2( cmd, 'b' )
704             else:
705                 (self.player_out,self.player_in) = os.popen2( cmd, 'b' )
706         except Exception, e:
707             print_exc()
708             self.onError(self.utility.lang.get('videoplayerstartfailure'),cmd,str(e.__class__)+':'+str(e))
709
710
711
712     def escape_path(self,path):
713         if path[0] != '"' and path[0] != "'" and path.find(' ') != -1:
714             if sys.platform == 'win32':
715                 # Add double quotes
716                 path = "\""+path+"\""
717             else:
718                 path = "\'"+path+"\'"
719         return path
720
721
722     def onError(self,action,value,errmsg=u''):
723         self.onMessage(wx.ICON_ERROR,action,value,errmsg)
724
725     def onWarning(self,action,value,errmsg=u''):
726         self.onMessage(wx.ICON_INFORMATION,action,value,errmsg)
727
728     def onMessage(self,icon,action,value,errmsg=u''):
729         # Don't use language independence stuff, self.utility may not be
730         # valid.
731         msg = action
732         msg += '\n'
733         msg += value
734         msg += '\n'
735         msg += errmsg
736         msg += '\n'
737         dlg = wx.MessageDialog(None, msg, self.utility.lang.get('videoplayererrortitle'), wx.OK|icon)
738         result = dlg.ShowModal()
739         dlg.Destroy()
740
741     def set_vod_download(self,d):
742         self.vod_download = d
743         
744     def get_vod_download(self):
745         return self.vod_download
746
747     #
748     # Set information about video playback progress that is displayed
749     # to the user.
750     #
751     def set_content_name(self,name):
752         if self.videoframe is not None:
753             self.videoframe.get_videopanel().SetContentName(name)
754
755     def set_content_image(self,wximg):
756         if self.videoframe is not None:
757             self.videoframe.get_videopanel().SetContentImage(wximg)
758
759     def set_player_status(self,msg):
760         if self.videoframe is not None:
761             self.videoframe.get_videopanel().SetPlayerStatus(msg)
762
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)
766         
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)
770
771     def get_state(self):
772         if self.playbackmode == PLAYBACKMODE_INTERNAL and self.videoframe is not None:
773             return self.videoframe.get_videopanel().GetState()
774         else:
775             return MEDIASTATE_PLAYING
776
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
781         else:
782             self.playbackmode = feasible[0]
783
784     def get_playbackmode(self):
785         return self.playbackmode
786
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
792
793     #
794     # Internal methods
795     #
796     def videohttpserver_error_callback(self,e,url):
797         """ Called by HTTP serving thread """
798         wx.CallAfter(self.videohttpserver_error_guicallback,e,url)
799         
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()
805
806     def videohttpserver_set_status_callback(self,status):
807         """ Called by HTTP serving thread """
808         wx.CallAfter(self.videohttpserver_set_status_guicallback,status)
809
810     def videohttpserver_set_status_guicallback(self,status):
811         self.videoframe.get_videopanel().SetPlayerStatus(status)
812  
813
814
815
816 class VideoChooser(wx.Dialog):
817     
818     def __init__(self,parent,utility,filelist,title=None,expl=None):
819         
820         self.utility = utility
821         self.filelist = []
822         
823         # Convert to Unicode for display
824         for file in filelist:
825             u = bin2unicode(file)
826             self.filelist.append(u)
827
828         if DEBUG:
829             print >>sys.stderr,"VideoChooser: filelist",self.filelist
830         
831         style = wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER
832         if title is None:
833             title = self.utility.lang.get('selectvideofiletitle')
834         wx.Dialog.__init__(self,parent,-1,title,style=style)
835         
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)
840         
841         if expl is None:
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)
846         
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)
853
854         self.SetSizerAndFit(sizer)
855
856     def getChosenIndex(self):        
857         return self.file_chooser.GetSelection()
858
859
860
861 class VODWarningDialog(wx.Dialog):
862     
863     def __init__(self, parent, utility, ds, infilename, other_downloads, islive):
864         self.parent = parent
865         self.utility = utility
866
867         style = wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER
868         if islive:
869             title = self.utility.lang.get('livewarntitle')
870         else:
871             title = self.utility.lang.get('vodwarntitle')
872         
873         wx.Dialog.__init__(self,parent,-1,title,style=style)
874
875         if islive:
876             msg = self.utility.lang.get('livewarngeneral')
877         else:
878             msg = self.utility.lang.get('vodwarngeneral')
879         
880         """
881         if bitrate is None:
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")
887             msg += 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")
892             msg += s
893             msg += self.is_mov_file(videoinfo)
894             msg += self.utility.lang.get('vodwarnconclusionno')
895             
896         else:
897             if maxuploadrate == 0:
898                 rate = self.utility.lang.get('unlimited')
899             else:
900                 rate = str(maxuploadrate)+" KB/s"
901             s = self.utility.lang.get('vodwarnbitratesufficient') % (str(bitrate/1024),rate)
902             msg += s
903             extra = self.is_mov_file(videoinfo)
904             if extra  == '':
905                 msg += self.utility.lang.get('vodwarnconclusionyes')
906             else:
907                 msg += extra
908                 msg += self.utility.lang.get('vodwarnconclusionno')
909         
910         """
911         sizer = wx.BoxSizer(wx.VERTICAL)
912         text = wx.StaticText(self, -1, msg)
913         text.Wrap(500)
914         sizer.Add(text, 0, wx.ALIGN_CENTER_VERTICAL|wx.ALL|wx.EXPAND, 5)
915
916         # 22/08/08 boudewijn: only show the selectbox when there are
917         # torrents that are actively downloading
918         if other_downloads:
919             otherslist = [self.utility.lang.get('vodrestartothertorrents'),
920                           self.utility.lang.get('vodstopothertorrents'),
921                           self.utility.lang.get('vodleaveothertorrents')]
922
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)
926
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)
930         else:
931             self.others_chooser = None
932
933         sizer.Add(wx.StaticText(self, -1, self.utility.lang.get('vodwarnprompt')), 0, wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5)
934         
935
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)
942
943         self.SetSizerAndFit(sizer)
944
945     def get_othertorrents_policy(self):
946         if self.others_chooser:
947             idx = self.others_chooser.GetSelection()
948         else:
949             idx = OTHERTORRENTS_STOP_RESTART
950         if DEBUG:
951             print >>sys.stderr,"videoplay: Other-torrents-policy is",idx
952         return idx
953     
954     def is_mov_file(self,videoinfo):
955         orig = videoinfo['inpath']
956         (prefix,ext) = os.path.splitext(orig)
957         low = ext.lower()
958         if low == '.mov':
959             return self.utility.lang.get('vodwarnmov')
960         else:
961             return ''
962             
963
964 def parse_playtime_to_secs(hhmmss):
965     if DEBUG:
966         print >>sys.stderr,"videoplay: Playtime is",hhmmss
967     r = re.compile("([0-9]+):*")
968     occ = r.findall(hhmmss)
969     t = None
970     if len(occ) > 0:
971         if len(occ) == 3:
972             # hours as well
973             t = int(occ[0])*3600 + int(occ[1])*60 + int(occ[2])
974         elif len(occ) == 2:
975             # minutes and seconds
976             t = int(occ[0])*60 + int(occ[1])
977         elif len(occ) == 1:
978             # seconds
979             t = int(occ[0])
980     return t
981
982 def return_feasible_playback_modes(syspath):
983     l = []
984     try:
985         import vlc
986
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)
997         else:
998             l.append(PLAYBACKMODE_INTERNAL)
999     except Exception:
1000         print_exc()
1001     
1002     if sys.platform == 'win32':
1003         l.append(PLAYBACKMODE_EXTERNAL_MIME)
1004         l.append(PLAYBACKMODE_EXTERNAL_DEFAULT)
1005     else:
1006         l.append(PLAYBACKMODE_EXTERNAL_DEFAULT)
1007     return l
1008