1

I am trying to write a wrapper like thing on vlc player on windows 7 so that it can load next and previous files in folder with a keystroke. What I do in it is, I take a filepath as argument make an instance of the vlc class and using pyHook, scan for keys and when specific keystrokes are detected call the play_next and play_prev methods on the instance. The methods work by killing the last process and spawning new vlc with next file found by get_new_file method. It works the first couple of times and then gives the peculiar error.

None   
None   
Traceback (most recent call last):  
  File "C:\Python27\pyHook\HookManager.py", line 351, in KeyboardSwitch   
    return func(event)   
  File "filestuff.py", line 64, in kbwrap   
    kbeventhandler(event,instance)   
  File "filestuff.py", line 11, in kbeventhandler   
    instance.play_prev()   
  File "filestuff.py", line 34, in play_prev   
    f=self.get_new_file(-1)   
  File "filestuff.py", line 40, in get_new_file   
    dirname= os.path.dirname(self.fn)   
  File "C:\Python27\lib\ntpath.py", line 205, in dirname   
    return split(p)[0]   
  File "C:\Python27\lib\ntpath.py", line 178, in split   
    while head2 and head2[-1] in '/\\':   
TypeError: an integer is required

here is the code:

import os
import sys
import pythoncom, pyHook 
import win32api
import subprocess
import ctypes

def kbeventhandler(event,instance):

    if event.Key=='Home':
        instance.play_prev()
    if event.Key=='End':
        instance.play_next()
    return True

class vlc(object):
    def __init__(self,filepath,vlcp):
        self.fn=filepath
        self.vlcpath=vlcp
        self.process = subprocess.Popen([self.vlcpath, self.fn])
    def kill(self):
        PROCESS_TERMINATE = 1
        handle = ctypes.windll.kernel32.OpenProcess(PROCESS_TERMINATE, False, self.process.pid)
        ctypes.windll.kernel32.TerminateProcess(handle, -1)
        ctypes.windll.kernel32.CloseHandle(handle)
        print self.process.poll()
    def play_next(self):
        self.kill()
        f=self.get_new_file(1)
        self.process = subprocess.Popen([self.vlcpath, f])
        self.fn=f
    def play_prev(self):
        self.kill()
        f=self.get_new_file(-1)
        self.process = subprocess.Popen([self.vlcpath, f])
        self.fn=f

    def get_new_file(self,switch):

        dirname= os.path.dirname(self.fn)    
        supplist=['.mkv','.flv','.avi','.mpg','.wmv']
        files = [os.path.join(dirname,f) for f in os.listdir(dirname) if (os.path.isfile(os.path.join(dirname,f)) and os.path.splitext(f)[-1]in supplist)]
        files.sort()
        try: currentindex=files.index(self.fn)
        except: currentindex=0
        i=0
        if switch==1:
            if currentindex<(len(files)-1):i=currentindex+1

        else:
            if currentindex>0:i=currentindex-1

        return files[i]    

def main():
    vlcpath='vlc'
    if os.name=='nt': vlcpath='C:/Program Files (x86)/VideoLAN/VLC/vlc.exe'
    fn='H:\\Anime\\needless\\Needless_[E-D]\\[Exiled-Destiny]_Needless_Ep11v2_(04B16479).mkv'
    if len(sys.argv)>1:
        fn=sys.argv[1] #use argument if available or else use default file
    instance=vlc(fn,vlcpath)
    hm = pyHook.HookManager()
    def kbwrap(event):
        kbeventhandler(event,instance)
    hm.KeyDown = kbwrap
    hm.HookKeyboard()    
    pythoncom.PumpMessages()

if __name__ == '__main__':
    main() 

here too: http://pastebin.com/rh82XGzd

thekindlyone
  • 509
  • 1
  • 6
  • 21
  • `def kill(self): self.process.terminate()` – jfs Dec 09 '13 at 20:49
  • try to pass `close_fds=True` to `Popen` (it won't hurt). – jfs Dec 09 '13 at 20:50
  • 1
    what happens if you remove `pyHook` stuff and call `play_next()`, `play_prev()` in a loop? – jfs Dec 09 '13 at 20:52
  • have you tried to drive vlc e.g., using http interface instead? – jfs Dec 09 '13 at 20:56
  • @J.F.Sebastian it works without pyHook and subprocess stuff(just printing out filenames). It also works with pyHook.. but when I put it all together I get this problem not related(I think) to subprocess or pyHook. process.terminate() was giving me not accessible after first termination. http interface doesn't really help me load next files in the folder. I am very used to that feature from media player classic, and now it is causing gpu heat problems due to some driver troubles. hence vlc – thekindlyone Dec 09 '13 at 20:56
  • I've asked does it work without `pyHook` but *with* `subprocess` (calling `vlc`). – jfs Dec 09 '13 at 20:59
  • yes it works without pyHook but with subprocess.. if I comment out all the pyHook stuff in main and just call `instance.play_next()` and `instance.play_prev()` multiple times, it works fine. What do you suspect? – thekindlyone Dec 09 '13 at 21:03
  • check `self.process.poll()` before calling `self.process.kill()` and set `self.process=None` after. http interface is an alternative to starting/killing subprocess -- there is probably a simple http request you can make from python that would start playing new file. – jfs Dec 09 '13 at 21:04
  • I don't understand. `self.process.poll()` will show process running before I kill it. What will I check for? – thekindlyone Dec 09 '13 at 21:07
  • `p, self.process = self.process, None` `if p is not None and p.poll() is None:` process is alive, let kill it: `p.kill(); p.wait()` the end – jfs Dec 09 '13 at 21:12
  • `return True` in `kbeventhandler` means that keys are passed to other applications. Is it your intent? – jfs Dec 09 '13 at 21:14
  • hmm changing the kill() method to http://pastebin.com/rWmz1jhE resulted in http://pastebin.com/sKHRzDzp No, I'll have to absorb those keystrokes by returning false. I didn't bother changing that till I get the core thing working. – thekindlyone Dec 09 '13 at 21:18
  • 1
    the code for `kill()` is to avoid: "not accessible after first termination." Don't use multiple statements on a single line (I've used it due to comments' formatting limitations). I think if you remove `pyHook` code then the error disappears. Try to set a flag in `play_prev/next` and nothing more. And start/stop vlc in another thread reading this flag. The idea is to avoid starting subprocesses from within an event handler. – jfs Dec 09 '13 at 21:24
  • hmm.. that sounds very reasonable. Let me try it out. Thanks for the assist. Will report shortly. – thekindlyone Dec 09 '13 at 21:26
  • related: [Help with pyHook error](http://stackoverflow.com/q/3049068/4279) – jfs Dec 09 '13 at 21:33
  • related: [pyHook + pythoncom stop working after too much keys pressed](http://stackoverflow.com/q/3673769/4279). It seems offloading work from the event handler should help. To be safe, catch and log all exceptions and return only True/False from the handler in case there is a broken C extension along the way. – jfs Dec 09 '13 at 21:51
  • threading didn't help. [code](http://pastebin.com/LzBns8GY) , [traceback](http://pastebin.com/jz9qdUyQ) – thekindlyone Dec 09 '13 at 22:26
  • 1
    `def kbwrap(event): return kbeventhandler(event,flag)` # <-- missing return – jfs Dec 09 '13 at 22:35
  • unrelated to the issue: use `with flag.get_lock(): flag.value +=1` otherwise it might fail. – jfs Dec 09 '13 at 22:37
  • WORKS!. That was the problem. return from the kbwrap(). I forgot I made it to pass argument to handler. Will do flag.get_lock() Thanks a lot! – thekindlyone Dec 09 '13 at 22:39
  • I've noticed, you use `multiprocessing.Value` with `threading`. Try: `with lock: instance.value += 1` instead. Where `lock =threading.Lock()` (a single object for all threads) and `instance.value = 0` (ordinary `int`) – jfs Dec 09 '13 at 22:43
  • if your tests work. Please, [post your own answer](http://stackoverflow.com/help/self-answer) that shows possible solution. It may help others with similar issues. – jfs Dec 09 '13 at 22:46
  • yes, I will compose a proper answer in a bit. Could you explain this lock business in a bit more detail? I am thoroughly confused. – thekindlyone Dec 09 '13 at 23:06
  • [this answer explains why would you need a lock for a counter](http://stackoverflow.com/a/15059014/4279). It is not your case though. I would use `queue.put_nowait(vlc.play_prev)` in the event handler and `for func in iter(queue.get, None): func(vlcinstance)` in the background thread where `queue = Queue.Queue()`. – jfs Dec 09 '13 at 23:49

1 Answers1

1

The problem was that in main I set hm.KeyDown = kbwrap and then from function kbwrap called the actual event handler kbeventhandler but didn't return any value from kbwrap

def kbwrap(event):
    return kbeventhandler(event,flag)
hm.KeyDown = kbwrap

and I also offloaded the vlc work to a different thread as pyHook wasn't playing nice with subprocess. final working code:

import os
import sys
import pythoncom, pyHook 
import win32api
import subprocess
import ctypes
import threading
from multiprocessing import *

class vlcThread(threading.Thread):
    def __init__(self,filepath,vlcp,fl):
        threading.Thread.__init__(self)
        self.fn,self.vlcpath,self.flag=filepath,vlcp,fl
        self.daemon=True

        self.start() # invoke the run method

    def run(self):
        vlcinstance=vlc(self.fn,self.vlcpath)
        while True:
            if(self.flag.value==1):
                vlcinstance.play_next()
                self.flag.value=0
            if(self.flag.value==-1):
                vlcinstance.play_prev()
                self.flag.value=0







def kbeventhandler(event,flag):

    if event.Key=='Home':
        flag.value =-1
        return False
    if event.Key=='End':
        flag.value =1
        return False
    return True

class vlc(object):
    def __init__(self,filepath,vlcp):
        self.fn=filepath
        self.vlcpath=vlcp
        self.process = subprocess.Popen([self.vlcpath,self.fn],close_fds=True)
    def kill(self):
        p, self.process = self.process, None
        if p is not None and p.poll() is None:
            p.kill() 
            p.wait()


    def play_next(self):
        self.kill()
        f=self.get_new_file(1)
        self.process = subprocess.Popen([self.vlcpath,f],close_fds=True)
        self.fn=f
    def play_prev(self):
        self.kill()
        f=self.get_new_file(-1)
        self.process = subprocess.Popen([self.vlcpath, f],close_fds=True)
        self.fn=f

    def get_new_file(self,switch):

        dirname= os.path.dirname(self.fn)    
        supplist=['.mkv','.flv','.avi','.mpg','.wmv','ogm','mp4']
        files = [os.path.join(dirname,f) for f in os.listdir(dirname) if (os.path.isfile(os.path.join(dirname,f)) and os.path.splitext(f)[-1]in supplist)]
        files.sort()
        try: currentindex=files.index(self.fn)
        except: currentindex=0
        i=0
        if switch==1:
            if currentindex<(len(files)-1):i=currentindex+1

        else:
            if currentindex>0:i=currentindex-1

        return files[i]    



def main():
    vlcpath='vlc'
    flag=Value('i')
    flag.value=0
    if os.name=='nt': vlcpath='C:/Program Files (x86)/VideoLAN/VLC/vlc.exe'
    fn='H:\\Anime\\needless\\Needless_[E-D]\\[Exiled-Destiny]_Needless_Ep11v2_(04B16479).mkv'
    if len(sys.argv)>1:
        fn=sys.argv[1] #use argument if available or else use default file

    t=vlcThread(fn,vlcpath,flag)
    hm = pyHook.HookManager()
    def kbwrap(event):
        return kbeventhandler(event,flag)
    hm.KeyDown = kbwrap
    hm.HookKeyboard()    
    pythoncom.PumpMessages()


if __name__ == '__main__':
    main() 
thekindlyone
  • 509
  • 1
  • 6
  • 21
  • Don't use `from x import *` outside REPL or `__init__.py`. You could use `r''` string literals for Windows paths e.g., `r'C:\Program Files (x86)\VideoLAN\VLC\vlc.exe'`. The word `flag` suggests True/False (on/off) (it was my suggestion for debugging only). [Using `Queue`](http://stackoverflow.com/questions/20478962/peculiar-error-on-ntpath-py?noredirect=1#comment30613252_20478962) would avoid the busy loop. Introduce `restart(newfilename)` method to avoid code repetition. Set `self.process=None` in `__init__` and call `.restart(filepath)` i.e., `Popen()` should be called from a single method. – jfs Dec 10 '13 at 00:01
  • You could use `collections.deque()` to implement `get_next_file()`, `get_prev_file()`. Then `def play_next(self): self.restart(self.get_next_file())` and `def play_prev(self): self.restart(self.get_prev_file())` – jfs Dec 10 '13 at 00:03