Help with script error

If you have a question or need help, this is the place to be.
Post Reply
molitar
Experienced User
Posts: 190
Joined: Fri Sep 11, 2009 6:44 am

Help with script error

Post by molitar » Wed Mar 13, 2019 12:09 am

Since upgrading to Eventghost 0.5 RC6 my script no longer works properly.

Error:
Traceback (most recent call last):
Python script "5", line 34, in <module>
secondsTotal = int(secArrayTotal[0])*(3600)+int(secArrayTotal[1])*(60)+int(secArrayTotal[2])
IndexError: list index out of range
Script:

Code: Select all

import re

mpc = eg.WindowMatcher(u'mpc-hc64.exe', None, u'MediaPlayerClassicW', None, None, None, True, 0.0, 0)
hWnd = mpc()

if len(hWnd) > 0:
    from eg.WinApi import GetWindowText
    title = GetWindowText(hWnd[0])
    if re.search(".mkv",title,re.I):
        title = title.split(".mkv")[0]
    elif re.search(".mp4",title,re.I):
        title = title.split(".mp4")[0]
    elif re.search(".avi",title,re.I):
        title = title.split(".avi")[0]
    elif re.search(".ogm",title,re.I):
        title = title.split(".ogm") [0]
    else:
        title = title.split(".")[0]
        
elaps, rem, total = eg.plugins.MediaPlayerClassic.GetTimes()
#print "elaps, rem, total =",elaps, rem, total

x = 1

while x < 4:

    if elaps != "00:00:00":
        if eg.globals.WindowsState != "Fullscreen": howManyBars = 100
        if eg.globals.WindowsState == "Fullscreen": howManyBars = 100

        secArray = elaps.split(':')
        secArrayTotal = total.split(':')
       
        secondsTotal = int(secArrayTotal[0])*(3600)+int(secArrayTotal[1])*(60)+int(secArrayTotal[2])
        secondsElapsed = int(secArray[0])*(3600)+int(secArray[1])*(60)+int(secArray[2])
       
        percent = (1.0*secondsElapsed)/(secondsTotal)*100
        percent = int(round(percent))
        bars = (percent * howManyBars)/100
       
        osd = title + "\n \n" + "[ " + "|" * bars + " " * (howManyBars - bars) + " ]" + "\n" + " "*30 + elaps + " / " + total

        if eg.globals.WindowsState == "Fullscreen":
            eg.plugins.EventGhost.ShowOSD(osd, u'0;-110;0;0;0;700;0;0;0;0;3;2;1;49;Arial', (0, 255, 255), (0, 0, 0), 4, (0, 0), 1, 1.0, False)
        else:
            eg.plugins.EventGhost.ShowOSD(osd, u'0;-058;0;0;0;700;0;0;0;0;3;2;1;49;Arial', (0, 255, 255), (0, 0, 0), 4, (0, 0), 2, 1.0, False)
    eg.Wait(0.8)
    #print x
    x += 1

User avatar
kgschlosser
Site Admin
Posts: 4582
Joined: Fri Jun 05, 2015 5:43 am
Location: Rocky Mountains, Colorado USA

Re: Help with script error

Post by kgschlosser » Wed Mar 13, 2019 5:34 am

OK so the code below is a redo of what you had posted. it has some nifty triicks
aded as well as some special features. I spent alot of time writing the comments
in the code. It explains what is happening ini detail and why I made the changes
I did. So please do read them.

make sure you leave the first line blank!!

Code: Select all


# this try/except routine is done because the information that is generated
# in a Python script is persistent between runs. Whats this means is that
# anything that is created in a python script when the script gets run a
# second, third, fourth... the objects created the first time are available to
# every run thereafter. So if you have any objects created that are static and
# do not change between runs then there is no need to make them over again.
# To test for this we simply use one of those object names that are static.
# if the object exists it will pass right on by. if it oes not it will
# generate an error that we catch in the except portion of thee code block.
# And that is where we will then create all of the static objects

try:
    user32
except NameError:
    import wx
    import datetime
    import time
    import ctypes
    from ctypes.wintypes import (
        BOOL,
        LONG,
        UINT,
        POINT,
        RECT,
        HWND,
        LPARAM,
        DWORD
    )  # NOQA

    from eg.WinApi import (
        GetWindowText,
        GetTopLevelWindowList,
        GetProcessName
    )  # NOQA


    PID = DWORD
    user32 = ctypes.windll.User32

    # DWORD GetWindowThreadProcessId(
    #   HWND    hWnd,
    #   LPDWORD lpdwProcessId
    # );
    _GetWindowThreadProcessId = user32.GetWindowThreadProcessId
    _GetWindowThreadProcessId.restype = DWORD

    BAR_TEMPLATE = '[{bars}{remaining}] - {percent:.2f}%'
    ELAPSED_TOTAL_TEMPLATE = 'Elapsed: {elapsed} / Total: {duration}'
    OSD_TEMPLATE = '{title}\n \n{bar}\n{elapsed_total}'

# OK so I removed the euse of the eg.WindowMatcher.
# it is a horribly slow thing to use and since we are only looking to see
# if a process is running or not we use GetTopLevelWindowList which returns
# the handles for the base window class of an application. then we ask windows
# to get us the process id (pid) for one of the handles in the returned list.
# Then we use that pid to get the process name. and we match that process name
# up with the one we are looking for.

handles = GetTopLevelWindowList(False)

for handle in handles:
    pid = PID()
    _GetWindowThreadProcessId(HWND(handle), ctypes.byref(pid))

    process_name = GetProcessName(pid.value)

    if process_name == 'mpc-hc64.exe':
        break
else:
    # OK this iis in interesting thing. I do this to keep from getting to
    # many layers of code
    # if:
    #     if:
    #         if:
    #             if:

    # that is to many layers. if for loop exits cleanly (break is not called)
    # the code in the else statement gets run. So this is a nice mechanism to
    # be able to say stop the macro and exit the script if our process was not
    # found
    handle = None
    eg.StopMacro()
    eg.Exit()

window_text = GetWindowText(handle)

# I removed the use of re and did a simple triel and error using a form loop
# with an else statement.
# If i split a string on a value and that value is not n the string it is
# going to return a list with the original string at index 0. So the list
# only las a length of 1. if it did have the value in it the list would have a
# length greater then 1. and we use that to break the loop. This will cause
# the else to not get run.

for item in (".mkv", ".mp4", ".avi", ".ogm"):
    window_text = window_text.split(item)
    if len(window_text) > 1:
        break
    window_text = window_text[0]
else:
    window_text = window_text.split('.')

title = window_text[0]


_elapsed, _remaining, _duration = eg.plugins.MediaPlayerClassic.GetTimes()
# print "elaps, rem, total =",elaps, rem, total

# I removed all of that math involved in trying to get the seconds in
# favor of using time.strptime and datetime.timedelta
# time.strptime will take a string formatted time representation and turn it
# into a python object for us. we simply have to provide details as to how the
# string is formatted. you can get more information on the identifiers here
# http://strftime.org/
# once we have that object which only does the simple
# conversion to hours minutes seconds we need to create another object that
# does the match for us and will return the total seconds

elapsed = time.strptime(_elapsed, '%H:%M:%S')
elapsed = datetime.timedelta(
    hours=elapsed.tm_hour,
    minutes=elapsed.tm_min,
    seconds=elapsed.tm_sec
)

remaining = time.strptime(_remaining, '%H:%M:%S')
remaining = datetime.timedelta(
    hours=remaining.tm_hour,
    minutes=remaining.tm_min,
    seconds=remaining.tm_sec
)

duration = time.strptime(_duration,'%H:%M:%S')
duration = datetime.timedelta(
    hours=duration.tm_hour,
    minutes=duration.tm_min,
    seconds=duration.tm_sec
)


# heere we do a test to see if elapsed has any seconds to it. if iit does
# that means the video is playing and to generate the osd
if elapsed.total_seconds():
    # instead of having to calls to ShowOSD we can set the parameters we want
    # to pass to ShowOSD into a variable you name the variable the same for
    # Fullscreen and not Fullscreen and set the parameters for each variation
    # then when we call ShowOSD the proper set of parameters will be passed
    if eg.globals.WindowsState == "Fullscreen":
        osd_args = (
            u'0;-110;0;0;0;700;0;0;0;0;3;2;1;49;Arial',
            (0, 255, 255),
            (0, 0, 0),
            4,
            (0, 0),
            1,
            2.4,
            False
        )
    else:
        osd_args = (
            u'0;-058;0;0;0;700;0;0;0;0;3;2;1;49;Arial',
            (0, 255, 255),
            (0, 0, 0),
            4,
            (0, 0),
            2,
            2.4,
            False
        )

    # I trimmed down the percent math. iif you want to change an int to a 
    # float you do not need to do 1.0 * int. this will work but the pythonic 
    # way is to wrap that int with float()
    percent = (
        (
            float(elapsed.total_seconds()) /
            float(duration.total_seconds())
        ) * 100.0
    )
    # I removed the conversion of the percent form a float to an int because 
    # i wanted the percent as a float for use further on down the line

    bars = '|' * int(round(percent))
    remaining = '.' * (100 - len(bars))
    
    # I created string templates for the OSD. i split the osd into 3 sections
    # I will explain as to why they are split into 3 sections further down
    # I added the percent that has elapsed to the bar line as a 2 decimal place 
    # float 

    bar = BAR_TEMPLATE.format(
        bars=bars,
        remaining=remaining,
        percent=percent
    )

    elapsed_total = ELAPSED_TOTAL_TEMPLATE.format(
        elapsed=_elapsed,
        duration=_duration
    )

    # OK so this is an odd thing. wx is thee GUI interface we use to generate 
    # all of the graphics, windows and controls in EG. because of how text 
    # gets displayed when using a GUI the characters defined in a font are 
    # not of equal size, so a "G" does not have the same width as an "!"
    # I saw that you were displaying the OSD n the center of the screen with 
    # what looked like center justification. because of that font issue ths 
    # is extremely difficult to do. Portions of the wx library allow us to call 
    # a function called GetTextExtent this function rill return the width and 
    # height in pixels of a string passed into it. we have to set the font we 
    # want to use then call the function. in order to get the center 
    # justification as close as we can and the space is the only thing we can 
    # really do it with we also want to know the width of a space.
    
    frame = wx.Frame(None, -1)
    font = wx.FontFromNativeInfoString(osd_args[0])
    frame.SetFont(font)
    bar_len = frame.GetTextExtent(bar)[0]
    title_len = frame.GetTextExtent(title)[0]
    elapsed_total_len = frame.GetTextExtent(elapsed_total)[0]
    space_len = frame.GetTextExtent(' ')[0]
    
    # so here we do the checking to see which line is longer then the other to 
    # adjust the one that is shorter
    if bar_len < title_len:
        # this is the number of spaces we are going to need. so we find out the
        # pixel difference divide that by the number of pixels a space is.
        # this is going to return the total number of spaces needed to make the
        # line the same length. Now remember center justification. so half of 
        # those spaces would need to be on thee back end of the line. Those
        # we do not need to add to the line. so wee divide the total number of 
        # spaces by 2 to get the number of spaces we need to add to the front 
        # of the line
        
        space_count = (
            int(round(float(title_len - bar_len) / float(space_len))) / 2
        )
        bar = ' ' * space_count + bar

    elif title_len < bar_len:
        space_count = (
            int(round(float(bar_len - title_len) / float(space_len))) / 2
        )
        title = ' ' * space_count + title

    bar_len = frame.GetTextExtent(bar)[0]

    frame.Destroy()

    if elapsed_total_len < bar_len:
        space_count = (
            int(round(float(bar_len - elapsed_total_len) / float(space_len))) / 2
        )
        elapsed_total = ' ' * space_count + elapsed_total

    osd = OSD_TEMPLATE.format(
        title=title,
        bar=bar,
        elapsed_total=elapsed_total
    )
    # and here is the single call to ShowOSD. we prefiix that variable we made 
    # earlier with a * which expands the variable into the parameters that 
    # needs to be passed

    eg.plugins.EventGhost.ShowOSD(osd, *osd_args)
If you like the work I have been doing then feel free to Image

User avatar
kgschlosser
Site Admin
Posts: 4582
Joined: Fri Jun 05, 2015 5:43 am
Location: Rocky Mountains, Colorado USA

Re: Help with script error

Post by kgschlosser » Wed Mar 13, 2019 5:46 am

This is a slightly different spin on it.

Code: Select all


# this try/except routine is done because the information that is generated
# in a Python script is persistent between runs. Whats this means is that
# anything that is created in a python script when the script gets run a
# second, third, fourth... the objects created the first time are available to
# every run thereafter. So if you have any objects created that are static and
# do not change between runs then there is no need to make them over again.
# To test for this we simply use one of those object names that are static.
# if the object exists it will pass right on by. if it oes not it will
# generate an error that we catch in the except portion of thee code block.
# And that is where we will then create all of the static objects

try:
    user32
except NameError:
    import wx
    import datetime
    import time
    import ctypes
    from ctypes.wintypes import (
        BOOL,
        LONG,
        UINT,
        POINT,
        RECT,
        HWND,
        LPARAM,
        DWORD
    )  # NOQA

    from eg.WinApi import (
        GetWindowText,
        GetTopLevelWindowList,
        GetProcessName
    )  # NOQA


    PID = DWORD
    user32 = ctypes.windll.User32

    # DWORD GetWindowThreadProcessId(
    #   HWND    hWnd,
    #   LPDWORD lpdwProcessId
    # );
    _GetWindowThreadProcessId = user32.GetWindowThreadProcessId
    _GetWindowThreadProcessId.restype = DWORD

    BAR_TEMPLATE = '[{bars}{remaining}] - {percent:.2f}%'
    ELAPSED_TOTAL_TEMPLATE = 'Elapsed: {elapsed} / Total: {duration}'
    OSD_TEMPLATE = '{title}\n \n{bar}\n{elapsed_total}'

# OK so I removed the euse of the eg.WindowMatcher.
# it is a horribly slow thing to use and since we are only looking to see
# if a process is running or not we use GetTopLevelWindowList which returns
# the handles for the base window class of an application. then we ask windows
# to get us the process id (pid) for one of the handles in the returned list.
# Then we use that pid to get the process name. and we match that process name
# up with the one we are looking for.

handles = GetTopLevelWindowList(False)

for handle in handles:
    pid = PID()
    _GetWindowThreadProcessId(HWND(handle), ctypes.byref(pid))

    process_name = GetProcessName(pid.value)

    if process_name == 'mpc-hc64.exe':
        break
else:
    # OK this iis in interesting thing. I do this to keep from getting to
    # many layers of code
    # if:
    #     if:
    #         if:
    #             if:

    # that is to many layers. if for loop exits cleanly (break is not called)
    # the code in the else statement gets run. So this is a nice mechanism to
    # be able to say stop the macro and exit the script if our process was not
    # found
    handle = None
    eg.StopMacro()
    eg.Exit()

window_text = GetWindowText(handle)

# I removed the use of re and did a simple triel and error using a form loop
# with an else statement.
# If i split a string on a value and that value is not n the string it is
# going to return a list with the original string at index 0. So the list
# only las a length of 1. if it did have the value in it the list would have a
# length greater then 1. and we use that to break the loop. This will cause
# the else to not get run.

for item in (".mkv", ".mp4", ".avi", ".ogm"):
    window_text = window_text.split(item)
    if len(window_text) > 1:
        break
    window_text = window_text[0]
else:
    window_text = window_text.split('.')

title = window_text[0]


_elapsed, _remaining, _duration = eg.plugins.MediaPlayerClassic.GetTimes()
# print "elaps, rem, total =",elaps, rem, total

# I removed all of that math involved in trying to get the seconds in
# favor of using time.strptime and datetime.timedelta
# time.strptime will take a string formatted time representation and turn it
# into a python object for us. we simply have to provide details as to how the
# string is formatted. you can get more information on the identifiers here
# http://strftime.org/
# once we have that object which only does the simple
# conversion to hours minutes seconds we need to create another object that
# does the match for us and will return the total seconds

elapsed = time.strptime(_elapsed, '%H:%M:%S')
elapsed = datetime.timedelta(
    hours=elapsed.tm_hour,
    minutes=elapsed.tm_min,
    seconds=elapsed.tm_sec
)

remaining = time.strptime(_remaining, '%H:%M:%S')
remaining = datetime.timedelta(
    hours=remaining.tm_hour,
    minutes=remaining.tm_min,
    seconds=remaining.tm_sec
)

duration = time.strptime(_duration,'%H:%M:%S')
duration = datetime.timedelta(
    hours=duration.tm_hour,
    minutes=duration.tm_min,
    seconds=duration.tm_sec
)


# heere we do a test to see if elapsed has any seconds to it. if iit does
# that means the video is playing and to generate the osd
if elapsed.total_seconds():
    # instead of having to calls to ShowOSD we can set the parameters we want
    # to pass to ShowOSD into a variable you name the variable the same for
    # Fullscreen and not Fullscreen and set the parameters for each variation
    # then when we call ShowOSD the proper set of parameters will be passed
    if eg.globals.WindowsState == "Fullscreen":
        osd_args = (
            u'0;-82;0;0;0;700;255;0;0;0;3;2;1;34;Verdana', 
            (0, 255, 0), 
            (0, 0, 0), 
            5, 
            (0, 0), 
            1, 
            3.0, 
            None
        )
    else:
        osd_args = (
            u'0;-030;0;0;0;700;255;0;0;0;3;2;1;34;Verdana', 
            (0, 255, 0), 
            (0, 0, 0), 
            5, 
            (0, 0), 
            2, 
            3.0, 
            None
        )

    # I trimmed down the percent math. iif you want to change an int to a 
    # float you do not need to do 1.0 * int. this will work but the pythonic 
    # way is to wrap that int with float()
    percent = (
        (
            float(elapsed.total_seconds()) /
            float(duration.total_seconds())
        ) * 100.0
    )
    # I removed the conversion of the percent form a float to an int because 
    # i wanted the percent as a float for use further on down the line

    bars = '/' * int(round(percent))
    remaining = '.' * (100 - len(bars))
    
    # I created string templates for the OSD. i split the osd into 3 sections
    # I will explain as to why they are split into 3 sections further down
    # I added the percent that has elapsed to the bar line as a 2 decimal place 
    # float 

    bar = BAR_TEMPLATE.format(
        bars=bars,
        remaining=remaining,
        percent=percent
    )

    elapsed_total = ELAPSED_TOTAL_TEMPLATE.format(
        elapsed=_elapsed,
        duration=_duration
    )

    # OK so this is an odd thing. wx is thee GUI interface we use to generate 
    # all of the graphics, windows and controls in EG. because of how text 
    # gets displayed when using a GUI the characters defined in a font are 
    # not of equal size, so a "G" does not have the same width as an "!"
    # I saw that you were displaying the OSD n the center of the screen with 
    # what looked like center justification. because of that font issue ths 
    # is extremely difficult to do. Portions of the wx library allow us to call 
    # a function called GetTextExtent this function rill return the width and 
    # height in pixels of a string passed into it. we have to set the font we 
    # want to use then call the function. in order to get the center 
    # justification as close as we can and the space is the only thing we can 
    # really do it with we also want to know the width of a space.
    
    frame = wx.Frame(None, -1)
    font = wx.FontFromNativeInfoString(osd_args[0])
    frame.SetFont(font)
    bar_len = frame.GetTextExtent(bar)[0]
    title_len = frame.GetTextExtent(title)[0]
    elapsed_total_len = frame.GetTextExtent(elapsed_total)[0]
    space_len = frame.GetTextExtent(' ')[0]
    
    # so here we do the checking to see which line is longer then the other to 
    # adjust the one that is shorter
    if bar_len < title_len:
        # this is the number of spaces we are going to need. so we find out the
        # pixel difference divide that by the number of pixels a space is.
        # this is going to return the total number of spaces needed to make the
        # line the same length. Now remember center justification. so half of 
        # those spaces would need to be on thee back end of the line. Those
        # we do not need to add to the line. so wee divide the total number of 
        # spaces by 2 to get the number of spaces we need to add to the front 
        # of the line
        
        space_count = (
            int(round(float(title_len - bar_len) / float(space_len))) / 2
        )
        bar = ' ' * space_count + bar

    elif title_len < bar_len:
        space_count = (
            int(round(float(bar_len - title_len) / float(space_len))) / 2
        )
        title = ' ' * space_count + title

    bar_len = frame.GetTextExtent(bar)[0]

    frame.Destroy()

    if elapsed_total_len < bar_len:
        space_count = (
            int(round(float(bar_len - elapsed_total_len) / float(space_len))) / 2
        )
        elapsed_total = ' ' * space_count + elapsed_total

    osd = OSD_TEMPLATE.format(
        title=title,
        bar=bar,
        elapsed_total=elapsed_total
    )
    # and here is the single call to ShowOSD. we prefiix that variable we made 
    # earlier with a * which expands the variable into the parameters that 
    # needs to be passed

    eg.plugins.EventGhost.ShowOSD(osd, *osd_args)
If you like the work I have been doing then feel free to Image

molitar
Experienced User
Posts: 190
Joined: Fri Sep 11, 2009 6:44 am

Re: Help with script error

Post by molitar » Wed Mar 13, 2019 4:03 pm

Thank you very much I threw a fiver your way I really appreciate all the help. It worked great.

User avatar
kgschlosser
Site Admin
Posts: 4582
Joined: Fri Jun 05, 2015 5:43 am
Location: Rocky Mountains, Colorado USA

Re: Help with script error

Post by kgschlosser » Thu Mar 14, 2019 3:23 am

I don't know what a fiver is. I am going to assume it is some kind of a donation.

I am happy to see it worked out for ya. I have added a justification feature to the Show OSD action. I will submit it as a pull request and we can see if it will get added to the core code.
There are several changes I have wanted to make to the OSD plugin. like being able to display more then a single OSD at the same time. and also not keeping the OSD frame created the whole time EG is running (waste of resources).

I am also going to add in scrolling text. so if you specify that you want to have a 3 line display but you pass in 15 lines of text to display it will scroll the text. this is going to be tricky to do and make it look right without any jitter. maybe II can also toss in some special effects like fading in and out. and vertical and horizontal removing of the text from the outside in and from the inside out (never done that one before, I wonder if I can make it come out right.) II also wanted to add the ability to display an image along with text. I may write a plugin to do all of this. It can get overly complex. I would need to make some kind of a layout control so the user can place items and set the sizes and what have you.
If you like the work I have been doing then feel free to Image

molitar
Experienced User
Posts: 190
Joined: Fri Sep 11, 2009 6:44 am

Re: Help with script error

Post by molitar » Thu Mar 14, 2019 3:13 pm

One thing I would like to see is ability to display OSD on multiple monitors at same time. I have a global volume control OSD action but I can not have the global control show on more then one display so I had to choose one display only. Is something like that possible with Show OSD?

User avatar
kgschlosser
Site Admin
Posts: 4582
Joined: Fri Jun 05, 2015 5:43 am
Location: Rocky Mountains, Colorado USA

Re: Help with script error

Post by kgschlosser » Thu Mar 14, 2019 4:20 pm

yes and no. but it gets a wee bit complicated to do it. You would have to run EG 2 times on the same computer. install the Network sender plugin on the primary EG. and the net receiver on the secondary EG then have the primary EG send a command to the secondary which would then show the second OSD. That is kind of a work around.
If you like the work I have been doing then feel free to Image

molitar
Experienced User
Posts: 190
Joined: Fri Sep 11, 2009 6:44 am

Re: Help with script error

Post by molitar » Fri Mar 15, 2019 1:26 am

kgschlosser, it worked for the entire day yesterday and then started throwing up this error this morning.. Damn I really HATE HATE Windows 10 and how randomly things just stop working.
Traceback (most recent call last):
Python script "5", line 120, in <module>
elapsed = time.strptime(_elapsed, '%H:%M:%S')
File "_strptime.pyc", line 478, in _strptime_time
File "_strptime.pyc", line 332, in _strptime
ValueError: time data '21:54' does not match format '%H:%M:%S'
So I thought I would try the latest version to see if it fixed it and installed it and that broke everything until I copied my old entire plugin folder back over. But I still can not get my OSD code you provided to work anymore. Why would it work all day yesterday and just stop today?

User avatar
kgschlosser
Site Admin
Posts: 4582
Joined: Fri Jun 05, 2015 5:43 am
Location: Rocky Mountains, Colorado USA

Re: Help with script error

Post by kgschlosser » Fri Mar 15, 2019 6:36 pm

OK that is because the plugin is returning a formatted time that is not normal.

this is the template that is expected to be returned
00:00:00
hours:minutes:seconds


and this is what was returned for the elapsed time.
21:54

so my question is going to be this. MPC has the ability to play all sorts of files types. when this happens you need to take note of what is playing and what the media type is. this may be happening because you are listening to an audio track. IDK. I can fix it easily enough I am just not sure if the returned value is hours and minutes or iis it minutes and seconds. I am going to assume the latter.


This will fix the issue.
right above this line

Code: Select all

elapsed = time.strptime(_elapsed, '%H:%M:%S')
add this code.

Code: Select all


while _elapsed.count(':') < 2:
    _elapsed = '00:' + _elapsed
while _remaining.count(':') < 2:
    _remaining = '00:' + _remaining
while _duration.count(':') < 2:
    _duration = '00:' + _duration
This is going to make sure the time is formatted hours:minutes:seconds
If you like the work I have been doing then feel free to Image

molitar
Experienced User
Posts: 190
Joined: Fri Sep 11, 2009 6:44 am

Re: Help with script error

Post by molitar » Mon Mar 18, 2019 5:31 am

I found a fatal flaw with this script for my needs. It only works with one MPC open not two. My old script worked with multiples open. Often I will have a video open that I am studying and another player open for some background music. I can no longer do this because of the method it seems where WindowMatcher worked with multiple MPC players at once the new method will not work.

User avatar
kgschlosser
Site Admin
Posts: 4582
Joined: Fri Jun 05, 2015 5:43 am
Location: Rocky Mountains, Colorado USA

Re: Help with script error

Post by kgschlosser » Wed Mar 20, 2019 6:16 am

that can be adjusted.

the first script would always use the first window that FindWindow would locate. so if for some reason it found the other one first. then the osd would be incorrect.

you can see that here in this portion of the original code

Code: Select all


mpc = eg.WindowMatcher(u'mpc-hc64.exe', None, u'MediaPlayerClassicW', None, None, None, True, 0.0, 0)
hWnd = mpc()

if len(hWnd) > 0:
    from eg.WinApi import GetWindowText
    title = GetWindowText(hWnd[0])              <=========
it is always grabbing index 0 from the returned list. was unaware that you were running multiple instance of the same program. the use of WindowManager does not have anything that is going to return one over another.

We would need to locate some kind of a variation between the 2 in order to assure the right one has an OSD displayed for it.
If you like the work I have been doing then feel free to Image

molitar
Experienced User
Posts: 190
Joined: Fri Sep 11, 2009 6:44 am

Re: Help with script error

Post by molitar » Fri Mar 22, 2019 3:49 am

kgschlosser wrote:
Wed Mar 20, 2019 6:16 am
that can be adjusted.

the first script would always use the first window that FindWindow would locate. so if for some reason it found the other one first. then the osd would be incorrect.

you can see that here in this portion of the original code

Code: Select all


mpc = eg.WindowMatcher(u'mpc-hc64.exe', None, u'MediaPlayerClassicW', None, None, None, True, 0.0, 0)
hWnd = mpc()

if len(hWnd) > 0:
    from eg.WinApi import GetWindowText
    title = GetWindowText(hWnd[0])              <=========
it is always grabbing index 0 from the returned list. was unaware that you were running multiple instance of the same program. the use of WindowManager does not have anything that is going to return one over another.

We would need to locate some kind of a variation between the 2 in order to assure the right one has an OSD displayed for it.
Is there a way to check what one is in focus? That is basically how it worked before with the old handle match. If I selected the full screen one that became in focus and the command worked for that one.

User avatar
kgschlosser
Site Admin
Posts: 4582
Joined: Fri Jun 05, 2015 5:43 am
Location: Rocky Mountains, Colorado USA

Re: Help with script error

Post by kgschlosser » Fri Mar 22, 2019 6:21 am

how ever you want me to do the check. I can have it check focus. I can also have it check if it is full screen/maximized/iconIzed/minimized. if it has a close button. if the window has a border or not. you think of it I can most likley query it to find out what it does or does not have.

I would think that fullscreen would be better then focus if you have a setup that has more then a single monitor. the fullscreen application does not need to be in focus in order to display in full screen. Because Media Player classic is/was a Microsoft product I would imagine that thy stuck with the Windows API as far as the frame styles for the window in full screen go. checking the z-order (focus) is really simple.

Here is what I will do. I will write a new version that will allow you to choose which detection method you want to use. So you can test out both and see which one works best for ya.. how about that?
If you like the work I have been doing then feel free to Image

User avatar
kgschlosser
Site Admin
Posts: 4582
Joined: Fri Jun 05, 2015 5:43 am
Location: Rocky Mountains, Colorado USA

Re: Help with script error

Post by kgschlosser » Fri Mar 22, 2019 9:22 am

ok so here is a new version of the script.

This is what I did and I hope that it will work for you.

if you have a single mpc running it is going to use that.
this all works in unlimited number of multiples
if you have more then one in full screen and the rest not it will usee the one running in fullscreen,
if you have multiiple running in fullscreen it is going to pick the one that is considered active (has focus)
if you have multiple running not in fullscreen it is going to use which ever one is active (has focus)

I did not know which version of the script you were using the original layout or the one where the OSD is center justified.

I also threw in a little extra bonus special effect, I thought you may like it. if you do not let me know and I will remove it. You can adjust the first variable in the script

Code: Select all

# this try/except routine is done because the information that is generated
# in a Python script is persistent between runs. Whats this means is that
# anything that is created in a python script when the script gets run a
# second, third, fourth... the objects created the first time are available to
# every run thereafter. So if you have any objects created that are static and
# do not change between runs then there is no need to make them over again.
# To test for this we simply use one of those object names that are static.
# if the object exists it will pass right on by. if it oes not it will
# generate an error that we catch in the except portion of thee code block.
# And that is where we will then create all of the static objects


# adjustable set between 0 and 255
MAX_TRANSPARENT = 90

try:
    user32
except NameError:
    import wx
    import datetime
    import time
    import threading
    import ctypes
    from ctypes.wintypes import (
        BOOL,
        LONG,
        UINT,
        POINT,
        RECT,
        HWND,
        LPARAM,
        DWORD
    )  # NOQA

    from eg.WinApi import (
        GetWindowText,
        GetTopLevelWindowList,
        GetProcessName
    )  # NOQA

    from eg.WinApi.Dynamic import (
        SetWindowPos,
        SWP_FRAMECHANGED,
        SWP_NOACTIVATE,
        SWP_NOOWNERZORDER,
        SWP_SHOWWINDOW,
    )


    HWND_FLAGS = SWP_NOACTIVATE | SWP_NOOWNERZORDER | SWP_FRAMECHANGED

    GWL_STYLE = -16
    GWL_EXSTYLE = -20
    WS_BORDER = 0x00800000
    WS_DLGFRAME = 0x00400000
    WS_THICKFRAME = 0x00040000
    WS_EX_WINDOWEDGE = 0x00000100
    WS_POPUP = 0x80000000
    WS_EX_TOPMOST = 0x00000008

    PID = DWORD
    user32 = ctypes.windll.User32

    # DWORD GetWindowThreadProcessId(
    #   HWND    hWnd,
    #   LPDWORD lpdwProcessId
    # );
    _GetWindowThreadProcessId = user32.GetWindowThreadProcessId
    _GetWindowThreadProcessId.restype = DWORD

    # LONG GetWindowLongW(
    #   HWND hWnd,
    #   int  nIndex
    # );

    _GetWindowLong = user32.GetWindowLongW
    _GetWindowLong.restype = LONG

    # HWND GetActiveWindow(
    #
    # );
    _GetActiveWindow = user32.GetActiveWindow
    _GetActiveWindow.restype = HWND

    BAR_TEMPLATE = '[{bars}{remaining}] - {percent:.2f}%'
    ELAPSED_TOTAL_TEMPLATE = 'Elapsed: {elapsed} / Total: {duration}'
    OSD_TEMPLATE = '{title}\n \n{bar}\n{elapsed_total}'
    osd_frame = eg.plugins.EventGhost.actions['ShowOSD'].osdFrame
    _old_show_osd = osd_frame.ShowOSD
    _old_on_timeout = osd_frame.OnTimeout

    eg.globals.fade_in_event = threading.Event()
    eg.globals.fade_out_event = threading.Event()

# OK so I removed the euse of the eg.WindowMatcher.
# it is a horribly slow thing to use and since we are only looking to see
# if a process is running or not we use GetTopLevelWindowList which returns
# the handles for the base window class of an application. then we ask windows
# to get us the process id (pid) for one of the handles in the returned list.
# Then we use that pid to get the process name. and we match that process name
# up with the one we are looking for.

handles = GetTopLevelWindowList(False)

# OK so I added some code to locate which instance of mpc to use if more then
# one is running.
# I elected to do some crafty coding with this. What happens is after the the
# window handles are found I then query the Windows API to get thee styles of
# the window. i check for specific markers like if the window has a border and
# if the window is set to be the topmost window as these are things that get
# turned on/off when an application goes into a fullscreen mode. at the same
# time I am also checking for a copy of mpc that is the active window. I do
# this to cover my bases, if there is more then a single active copy and none
# of them are set to full screen it is going to choose the one that is active
# (has focus). if there is a fullscreen copy running and a second. and the
# second is active the fullscreen is going to be the one used.

for handle in handles[:]:
    pid = PID()
    _GetWindowThreadProcessId(HWND(handle), ctypes.byref(pid))

    process_name = GetProcessName(pid.value)

    # we check the process name of the window. if it does not match then we
    # remove it form the list.
    if process_name != 'mpc-hc64.exe':
        handles.remove(handle)

# if there are no windows found then we want to stop the macro from running and
# exit the script

if not handles:
    handle = None
    eg.StopMacro()
    eg.Exit()


handle = None
# I removed the use of eg.globals.WindowsState as a mechanism for checking if
# the app is in fullscreen mode. Because this is one of the things we are
# looking for when we pick the window to use we no longer need to depend on
# an outside source to determine the window state. So you can remove that from
# your tree if you want.
is_fullscreen = False

for hwnd in handles:
    # getting the windows styles and extended styles.
    style = _GetWindowLong(HWND(hwnd), GWL_STYLE)
    style_ex = _GetWindowLong(HWND(hwnd), GWL_EXSTYLE)

    # checking the styles for specific markers.
    if (
        not style & WS_BORDER and
        not style & WS_DLGFRAME and
        not style & WS_THICKFRAME and
        not style_ex & WS_EX_WINDOWEDGE and
        style & WS_POPUP and
        style_ex & WS_EX_TOPMOST
    ):
        # if there are 2 copies of mpc running and both are fullscreen then
        # we are going to use the one that is active
        if is_fullscreen is False:
            handle = hwnd
            is_fullscreen = True
        elif _GetActiveWindow() == hwnd:
            handle = hwnd
            is_fullscreen = True

    # if there is no full screen one found already and this window is active
    # then we seet it into place
    elif handle is None and _GetActiveWindow() == hwnd:
        handle = hwnd
        is_fullscreen = False

# fallback if something does not work properly in the code.
if handle is None:
    handle = handles[0]

window_text = GetWindowText(handle)

# I removed the use of re and did a simple trial and error using a for loop
# with an else statement.
# If i split a string on a value and that value is not n the string it is
# going to return a list with the original string at index 0. So the list
# only las a length of 1. if it did have the value in it the list would have a
# length greater then 1. and we use that to break the loop. This will cause
# the else to not get run.

for item in (".mkv", ".mp4", ".avi", ".ogm"):
    window_text = window_text.split(item)
    if len(window_text) > 1:
        break
    window_text = window_text[0]
else:
    window_text = window_text.split('.')

title = window_text[0]

_elapsed, _remaining, _duration = eg.plugins.MediaPlayerClassic.GetTimes()
# print "elaps, rem, total =",elaps, rem, total

# I removed all of that math involved in trying to get the seconds in
# favor of using time.strptime and datetime.timedelta
# time.strptime will take a string formatted time representation and turn it
# into a python object for us. we simply have to provide details as to how the
# string is formatted. you can get more information on the identifiers here
# http://strftime.org/
# once we have that object which only does the simple
# conversion to hours minutes seconds we need to create another object that
# does the match for us and will return the total seconds


while _elapsed.count(':') < 2:
    _elapsed = '00:' + _elapsed
while _remaining.count(':') < 2:
    _remaining = '00:' + _remaining
while _duration.count(':') < 2:
    _duration = '00:' + _duration

elapsed = time.strptime(_elapsed, '%H:%M:%S')
elapsed = datetime.timedelta(
    hours=elapsed.tm_hour,
    minutes=elapsed.tm_min,
    seconds=elapsed.tm_sec
)

remaining = time.strptime(_remaining, '%H:%M:%S')
remaining = datetime.timedelta(
    hours=remaining.tm_hour,
    minutes=remaining.tm_min,
    seconds=remaining.tm_sec
)

duration = time.strptime(_duration, '%H:%M:%S')
duration = datetime.timedelta(
    hours=duration.tm_hour,
    minutes=duration.tm_min,
    seconds=duration.tm_sec
)

# heere we do a test to see if elapsed has any seconds to it. if iit does
# that means the video is playing and to generate the osd
if elapsed.total_seconds():
    def fade_in():
        for i in range(MAX_TRANSPARENT):
            if eg.globals.fade_in_event.isSet():
                break
            osd_frame.SetTransparent(i)
            eg.globals.fade_in_event.wait(0.01)

        osd_frame.ShowOSD = _old_show_osd


    def fade_out():
        for i in range(MAX_TRANSPARENT, 0, -1):
            if eg.globals.fade_out_event.isSet():
                break
            osd_frame.SetTransparent(i)
            eg.globals.fade_out_event.wait(0.01)

        _old_on_timeout()
        osd_frame.OnTimeout = _old_on_timeout


    def show_osd(*args, **kwargs):
        args = list(args)
        args[7] += MAX_TRANSPARENT * 0.01

        eg.globals.fade_out_event.set()
        eg.globals.fade_in_event.clear()

        osd_frame.SetTransparent(0)
        _old_show_osd(*args, **kwargs)

        fade_in()


    def on_timeout():
        eg.globals.fade_in_event.set()
        eg.globals.fade_out_event.clear()
        fade_out()


    osd_frame.ShowOSD = show_osd
    osd_frame.OnTimeout = on_timeout

    # instead of having to calls to ShowOSD we can set the parameters we want
    # to pass to ShowOSD into a variable you name the variable the same for
    # Fullscreen and not Fullscreen and set the parameters for each variation
    # then when we call ShowOSD the proper set of parameters will be passed
    if is_fullscreen:
        osd_args = (
            u'0;-82;0;0;0;700;255;0;0;0;3;2;1;34;Verdana',
            (0, 255, 0),
            (0, 0, 0),
            5,
            (0, 0),
            1,
            3.0,
            None
        )
    else:
        osd_args = (
            u'0;-030;0;0;0;700;255;0;0;0;3;2;1;34;Verdana',
            (0, 255, 0),
            (0, 0, 0),
            5,
            (0, 0),
            2,
            3.0,
            None
        )

    # I trimmed down the percent math. iif you want to change an int to a
    # float you do not need to do 1.0 * int. this will work but the pythonic
    # way is to wrap that int with float()
    percent = (
        (
            float(elapsed.total_seconds()) /
            float(duration.total_seconds())
        ) * 100.0
    )
    # I removed the conversion of the percent form a float to an int because
    # i wanted the percent as a float for use further on down the line

    bars = '/' * int(round(percent))
    remaining = '.' * (100 - len(bars))

    # I created string templates for the OSD. i split the osd into 3 sections
    # I will explain as to why they are split into 3 sections further down
    # I added the percent that has elapsed to the bar line as a 2 decimal place
    # float

    bar = BAR_TEMPLATE.format(
        bars=bars,
        remaining=remaining,
        percent=percent
    )

    elapsed_total = ELAPSED_TOTAL_TEMPLATE.format(
        elapsed=_elapsed,
        duration=_duration
    )

    # OK so this is an odd thing. wx is thee GUI interface we use to generate
    # all of the graphics, windows and controls in EG. because of how text
    # gets displayed when using a GUI the characters defined in a font are
    # not of equal size, so a "G" does not have the same width as an "!"
    # I saw that you were displaying the OSD n the center of the screen with
    # what looked like center justification. because of that font issue ths
    # is extremely difficult to do. Portions of the wx library allow us to call
    # a function called GetTextExtent this function rill return the width and
    # height in pixels of a string passed into it. we have to set the font we
    # want to use then call the function. in order to get the center
    # justification as close as we can and the space is the only thing we can
    # really do it with we also want to know the width of a space.

    frame = wx.Frame(None, -1)
    font = wx.FontFromNativeInfoString(osd_args[0])
    frame.SetFont(font)
    bar_len = frame.GetTextExtent(bar)[0]
    title_len = frame.GetTextExtent(title)[0]
    elapsed_total_len = frame.GetTextExtent(elapsed_total)[0]
    space_len = frame.GetTextExtent(' ')[0]

    # so here we do the checking to see which line is longer then the other to
    # adjust the one that is shorter
    if bar_len < title_len:
        # this is the number of spaces we are going to need. so we find out the
        # pixel difference divide that by the number of pixels a space is.
        # this is going to return the total number of spaces needed to make the
        # line the same length. Now remember center justification. so half of
        # those spaces would need to be on thee back end of the line. Those
        # we do not need to add to the line. so wee divide the total number of
        # spaces by 2 to get the number of spaces we need to add to the front
        # of the line

        space_count = (
            int(round(float(title_len - bar_len) / float(space_len))) / 2
        )
        bar = ' ' * space_count + bar

    elif title_len < bar_len:
        space_count = (
            int(round(float(bar_len - title_len) / float(space_len))) / 2
        )
        title = ' ' * space_count + title

    bar_len = frame.GetTextExtent(bar)[0]

    frame.Destroy()

    if elapsed_total_len < bar_len:
        space_count = (
            int(round(
                float(bar_len - elapsed_total_len) / float(space_len))) / 2
        )
        elapsed_total = ' ' * space_count + elapsed_total

    osd = OSD_TEMPLATE.format(
        title=title,
        bar=bar,
        elapsed_total=elapsed_total
    )
    # and here is the single call to ShowOSD. we prefiix that variable we made
    # earlier with a * which expands the variable into the parameters that
    # needs to be passed

    eg.plugins.EventGhost.ShowOSD(osd, *osd_args)
If you like the work I have been doing then feel free to Image

Post Reply