IP controlled relay board

If you have a question or need help, this is the place to be.
User avatar
kgschlosser
Site Admin
Posts: 5504
Joined: Fri Jun 05, 2015 5:43 am
Location: Rocky Mountains, Colorado USA

Re: IP controlled relay board

Post by kgschlosser » Sat Apr 11, 2020 2:10 am

OK here is an updated script.
Create a macro. add the Python Script action to the macro. copy and paste the code below into that script.
Click on apply and then click on OK.

drag and drop one of the udp.(x, y) events into the macro.
double click on the event in the macro
Change it to read

udp.*

Code: Select all

# make sure you read the comments to know what is going on

from eg.WinApi.Utils import GetMonitorDimensions
import wx

# udp.(154, 142)

try:
    coords = eval(eg.event.string.split('.')[-1])
except:
    eg.Exit()

if not isinstance(coords, tuple):
    eg.Exit()
    
x, y = coords

DEMOPAD_WIDTH = 500
DEMOPAD_HEIGHT = 300

# I do not know if you have multiple monitors or not.
# if you do do it is possible to end up with negative
# x and y start positions. So we need to account for that
# properly. we also need to get the total width and total
# height of all of the monitors. GetMonitorDimensions
# does some magic for us. It returns the monitors that
# are attached to the system and tells us where the 0, 0
# position of that monitor actually is. and also the width
# and height of the monitor.
rect = wx.Rect()
for monitor in GetMonitorDimensions():
    # because of some back end magic we are able to "join" the
    # monitors to get a single monitor. This is only for the
    # purposes of math and it does nothing to your actual display.
    rect.Union(monitor)

# now that we have a single "monitor" that represents all of the
# displays that are attached. we are able to get the starting point
# and also the ending point of the combined displays.
SCREEN_START_X = rect.GetX()
SCREEN_START_Y = rect.GetY()

# in order to know where the end point is we add the start point to the width/height
SCREEN_END_X = SCREEN_START_X + rect.GetWidth()
SCREEN_END_Y = SCREEN_START_Y + rect.GetHeight()


# this function takes a value that is within a given range and "re maps"
# it to a new range of numbers.
# this is how we turn the x, y for a screen size of 500, 300 into
# the x, y of your combined displays
def remap(value, old_min, old_max, new_min, new_max):
    old_range = old_max - old_min
    new_range = new_max - new_min
    return (((value - old_min) * new_range) / old_range) + new_min


# the conversion.
x = remap(x, 0, DEMOPAD_WIDTH, SCREEN_START_X, SCREEN_END_X)
y = remap(y, 0, DEMOPAD_HEIGHT, SCREEN_START_Y, SCREEN_END_Y)

# we need to use a wx.Point object to pass to a monitor to see
# if the x, y is within that monitor
point = wx.Point(x, y)

for monitor_num, monitor in enumerate(GetMonitorDimensions()):
    # checking to see if the remapped x, y are in a given monitor.
    if monitor.Contains(point):
        # now this is the tricky part. the Mouse action does not deal
        # in the x, y of a combined display. each display always starts at
        # 0, 0 and ends at width, height. So now we need to convert the x, y
        # into the x, y needed for this display.
        x_min = monitor.GetX()
        x_max = x_min + monitor.GetWidth()
        y_min = monitor.GetY()
        y_max = y_min + monitor.GetHeight()

        # here is the conversion. so the old min and max are the "real" start and end positions
        # for the display. the new range is 0 to the width/heigh. This is going to give us the x, y
        #  that the mouse action needs.
        x = remap(x, x_min, x_max, 0, monitor.GetWidth())
        y = remap(y, y_min, y_max, 0, monitor.GetHeight())

        # and here we call the action and move the mouse.
        eg.plugins.Mouse.MoveAbsolute(x, y, monitor_num, False, False)

        # if we found the monitor there is no point in checking any other ones.
        # so we break out of checking the rest of them
        break
else:
    # This code gets run if no monitor is found. This would be a catastrophic
    # failure of the above code. and tells us that something is horrifically wrong.
    # If you ever see this printed in your log you have to let me know and I will
    # have to locate the error.
    print 'No monitor found'



Here is another way to move the mouse. This uses direction and speed instead of x, y. This is going to function like a laptop trackpad does.
You will need to make some adjustments to dial it in. there are 2 varis that you will need to tinker with to dial it in right.

Code: Select all


# duration until reset occurs in milliseconds. 1000 = 1 second.
RESET_DURATION = 1000

# adjust this humber down to dial in the speed of the mouse movement
# you may want to adjust it in 10000 increments at fi and then 1000 
and so on and so forth
SPEED = 65535


DEMOPAD_WIDTH = 500
DEMOPAD_HEIGHT = 300

import time
end_time = time.time()

try:
    duration = (end_time - start_time) * 1000
    last_x, last_y = eg.globals.last_mouse_coords
except:
    duration = RESET_DURATION
    last_x = 0
    last_y = 0


import ctypes
import threading
from ctypes.wintypes import DWORD

if ctypes.sizeof(ctypes.c_void_p) == 8:
    ULONG_PTR = ctypes.c_ulonglong
else:
    ULONG_PTR = ctypes.c_ulong

user32 = ctypes.windll.User32

# void mouse_event(
#   DWORD     dwFlags,
#   DWORD     dx,
#   DWORD     dy,
#   DWORD     dwData,
#   ULONG_PTR dwExtraInfo
# );

mouse_event = user32.mouse_event
mouse_event.restype = None

MOUSEEVENTF_MOVE = 0x0001

coords = eg.event.string.split('.')[-1]

try:
    coords = eval(coords)
except:
    eg.Exit()

if not isinstance(coords, tuple):
    eg.Exit()

x, y = coords

def remap(value, old_min, old_max, new_min, new_max):
    old_range = old_max - old_min
    new_range = new_max - new_min
    return (((value - old_min) * new_range) / old_range) + new_min


x = remap(x, 0, DEMOPAD_WIDTH, 0, SPEED)
y = remap(y, 0, DEMOPAD_HEIGHT, 0, SPEED)

eg.globals.last_mouse_coords = (x, y)

if duration >= RESET_DURATION:
    last_x = x
    last_y = y


x -= last_x
y -= last_y

print x, y

dwFlags = DWORD(MOUSEEVENTF_MOVE)
dx = DWORD(x)
dy = DWORD(y)
dwData =DWORD(0)
dwExtraInfo = ULONG_PTR()

mouse_event(dwFlags, dx, dy, dwData, dwExtraInfo)
start_time = time.time()
If you like the work I have been doing then feel free to Image

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

Re: IP controlled relay board

Post by kgschlosser » Sat Apr 11, 2020 2:25 am

I also wanted to give you an education. on the anatomy of an event in EG.

Some.Event "some payload"

OK so an event consists of 3 parts.

prefix . suffix payload

the prefix is the source of the event (usually). if it is from a plugin it will normally be the plugin name
the suffix is the information that is being relayed, like a status change
the payload is for additional information or data that may be used.

How you can tell what is event and what is payload is by dragging and dropping the event from your log into a macro. the bits that didn't end up in the macro are the payload.

a payload can be an assortment of objects. most can be identified by special characters.

if a payload begins with [ and ends with ] it is a list. think of it like a list of groceries, each item in the list is a line on a grocery list. the line numbers start at 0, the items in a list are separated by a comma. you access individual items by specifying the line number (item number) eg.event.payload[0] is to get the first item and eg.event.payload[1] is to get the second and so on and so forth.

a tuple begins with a ( and ends with ) it works much like a list does.

then you have a dictionary. this begins with { and ends with } a dictionary is just like a dictionary. it has words (keys) and definitions (values)
each entry in a dictionary is separated by a comma and the key is separated from the value by a colon.
to access items in a dictionary you use the key to obtain the value.

eg.event.payload['some key']

this will return the value for that key.

you have strings they either
start with " and end with "
start with ' and end with '
start with u" and end with "
start with u' and end with '

the u is for unicode, this is not something you need to worry about. the u and the single and double quotes are not apart of the actual string. they are only there as an identifier.

you have integers or whole numbers
and you have floats, which are decimal numbers

Those are the big ones you need to concern yourself with.

a typical event for what you are doing should be something like

udp.mouse_move (x, y)
where (x, y) is a payload and not actually apart of the event. But I took care of parsing the event string and converting it into what we needed.
If you like the work I have been doing then feel free to Image

Peter M
Posts: 46
Joined: Tue Jun 18, 2019 5:13 am

Re: IP controlled relay board

Post by Peter M » Sat Apr 11, 2020 5:47 am

The good news is that the code works perfectly. I used the one that simulates a trackpad and ended up with -

RESET_DURATION = 100

SPEED = 1000

The bad news is that udp.* traps all my other Demopad commands and none of them work now.

Cheers,
Peter

Peter M
Posts: 46
Joined: Tue Jun 18, 2019 5:13 am

Re: IP controlled relay board

Post by Peter M » Sat Apr 11, 2020 6:17 am

OK with a bit more testing I've found that it's not the udp.* that's the problem.

It was something to do with Demopad command suffixes.

I think I've got it fixed.

About to test some more.

Peter M
Posts: 46
Joined: Tue Jun 18, 2019 5:13 am

Re: IP controlled relay board

Post by Peter M » Sat Apr 11, 2020 7:06 am

All fixed and working brilliantly !

Many thanks for your help !!

Cheers,
Peter

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

Re: IP controlled relay board

Post by kgschlosser » Sat Apr 11, 2020 9:31 am

I figured you would go with the trackpad one. The interface is going to work better because of the scaling between the demopad and the display resolution. It would make to wicked sensitive to use. That's why i coded up the trackpad script.

Remember tippin is not a town in china :shock: :wink: (it helps to keep the lights on)
If you like the work I have been doing then feel free to Image

Peter M
Posts: 46
Joined: Tue Jun 18, 2019 5:13 am

Re: IP controlled relay board

Post by Peter M » Sun Apr 12, 2020 6:23 am

Donation sent.

I have a friend who also uses Demopad in a system quite similar to mine and it was him who pushed me to do the mouse and keyboard. The problem we're having is that it all works perfectly in my system, but his is doing something very strange.

If he's in Notepad for example and using the keyboard, every letter is followed by *? when it's displayed.

Any idea on what could cause this ?

Cheers,
Peter

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

Re: IP controlled relay board

Post by kgschlosser » Sun Apr 12, 2020 9:11 am

and his system is running EG as well?
If you like the work I have been doing then feel free to Image

Peter M
Posts: 46
Joined: Tue Jun 18, 2019 5:13 am

Re: IP controlled relay board

Post by Peter M » Sun Apr 12, 2020 12:42 pm

Yes.

I've sent him both my Demopad and EG config files so all the Demopad commands, and EG events and actions are the same.

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

Re: IP controlled relay board

Post by kgschlosser » Sun Apr 12, 2020 5:18 pm

send me a copy of your tree. you will have to zip it up. send it to me in a PM.
If you like the work I have been doing then feel free to Image

Peter M
Posts: 46
Joined: Tue Jun 18, 2019 5:13 am

Re: IP controlled relay board

Post by Peter M » Thu Apr 16, 2020 9:05 pm

Many thanks again for your willingness to help, but we've now got it sorted.

Stupidly we had * and ? in the event names for these two keyboard presses, and EG was executing them for all events.

I have no idea why my system was OK but my mates misbehaved, but that's now irrelevant.

Cheers,
Peter

Peter M
Posts: 46
Joined: Tue Jun 18, 2019 5:13 am

Re: IP controlled relay board

Post by Peter M » Wed May 13, 2020 7:02 am

Hi,

Back again for some more help if anyone's willing. This time it's related to the Sonos Connect in the theater that I control from Demopad. Currently I have pretty good control using commands like this for Play as an example -

Code: Select all

POST /MediaRenderer/AVTransport/Control HTTP/1.1\x0D\x0ACONNECTION: close\x0D\x0AHOST: 192.168.0.32:1400\x0D\x0A
CONTENT-LENGTH: 266\x0D\x0ACONTENT-TYPE: text/xml; charset="utf-8"\x0D\x0A
SOAPACTION: "urn:schemas-upnp-org:service:AVTransport:1#Play"\x0D\x0A\x0D\x0A
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<s:Body><u:Play xmlns:u="urn:schemas-upnp-org:service:AVTransport:1"><InstanceID>0</InstanceID><Speed>1</Speed> 
</u:Play></s:Body></s:Envelope>\x0D\x0A
The only problem I encountered when I first set this up was extracting the queue. The Browse command was straight forward -

Code: Select all

POST /MediaServer/ContentDirectory/Control HTTP/1.1\x0D\x0ACONNECTION: close\x0D\x0AHOST: 192.168.0.32:1400\x0D\x0A
CONTENT-LENGTH: 433\x0D\x0ACONTENT-TYPE: text/xml; charset="utf-8"\x0D\x0A
SOAPACTION: "urn:schemas-upnp-org:service:ContentDirectory:1#Browse"\x0D\x0A\x0D\x0A
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<s:Body><u:Browse xmlns:u="urn:schemas-upnp-org:service:ContentDirectory:1"><ObjectID>Q:0</ObjectID>
 <BrowseFlag>BrowseDirectChildren</BrowseFlag><Filter>dc:title,dc:creator</Filter><StartingIndex>{browse}</StartingIndex>
 <RequestedCount>6</RequestedCount><SortCriteria></SortCriteria> </u:Browse></s:Body></s:Envelope>\x0D\x0A
And it returned info like this -

Code: Select all

<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<s:Body>
<u:BrowseResponse xmlns:u="urn:schemas-upnp-org:service:ContentDirectory:1">
<Result>
&lt;DIDL-Lite xmlns:dc=&quot;http://purl.org/dc/elements/1.1/&quot; xmlns:upnp=&quot;urn:schemas-upnp-org:metadata-1-0/upnp/
&quot; xmlns:r=&quot;urn:schemas-rinconnetworks-com:metadata-1-0/&quot; xmlns=&quot;urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/&quot;&gt;

&lt;item id=&quot;Q:0/1&quot; parentID=&quot;Q:0&quot; restricted=&quot;true&quot;&gt;&lt;res protocolInfo=&quot
;x-file-cifs:*:audio/flac:*&quot;&gt;x-file-cifs://xxxxxx/Public/music/Moby/Innocents/01%20-%20Everything%20That%20Rises.flac&lt;
/res&gt;&lt;upnp:albumArtURI&gt;/getaa?u=x-file-cifs%3a%2f%2fxxxxxx%2fPublic%2fmusic%2fMoby%2fInnocents%2f01%2520-
%2520Everything%2520That%2520Rises.flac&amp;amp;v=35&lt;/upnp:albumArtURI&gt;&lt;dc:title&gt;Everything That 
Rises&lt;/dc:title&gt;&lt;upnp:class&gt;object.item.audioItem.musicTrack&lt;/upnp:class&gt;&lt;dc:creator&gt;Moby&lt;
/dc:creator&gt;&lt;upnp:album&gt;Innocents&lt;/upnp:album&gt;&lt;upnp:originalTrackNumber&gt;1&lt;
/upnp:originalTrackNumber&gt;&lt;r:narrator&gt;Moby&lt;/r:narrator&gt;&lt;/item&gt;

&lt;item id=&quot;Q:0/2&quot; parentID=&quot;Q:0&quot; restricted=&quot;true&quot;&gt;&lt;res protocolInfo=&quot
;x-file-cifs:*:audio/flac:*&quot;&gt;x-file-cifs://xxxxxx/Public/music/Moby/Innocents/02%20-%20A%20Case%20for%20Shame.flac&lt;
/res&gt;&lt;upnp:albumArtURI&gt;/getaa?u=x-file-cifs%3a%2f%2fxxxxxx%2fPublic%2fmusic%2fMoby%2fInnocents%2f02%2520-
%2520A%2520Case%2520for%2520Shame.flac&amp;amp;v=35&lt;/upnp:albumArtURI&gt;&lt;dc:title&gt;A Case for 
Shame&lt;/dc:title&gt;&lt;upnp:class&gt;object.item.audioItem.musicTrack&lt;/upnp:class&gt;&lt;dc:creator&gt;Moby&lt;
/dc:creator&gt;&lt;upnp:album&gt;Innocents&lt;/upnp:album&gt;&lt;upnp:originalTrackNumber&gt;2&lt;
/upnp:originalTrackNumber&gt;&lt;r:narrator&gt;Moby&lt;/r:narrator&gt;&lt;/item&gt;

&lt;item id=&quot;Q:0/3&quot; parentID=&quot;Q:0&quot; restricted=&quot;true&quot;&gt;&lt;res protocolInfo=&quot
;x-file-cifs:*:audio/flac:*&quot;&gt;x-file-cifs://xxxxxx/Public/music/Moby/Innocents/03%20-%20Almost%20Home.flac&lt;
/res&gt;&lt;upnp:albumArtURI&gt;/getaa?u=x-file-cifs%3a%2f%2fxxxxxx%2fPublic%2fmusic%2fMoby%2fInnocents%2f03%2520-
%2520Almost%2520Home.flac&amp;amp;v=35&lt;/upnp:albumArtURI&gt;&lt;dc:title&gt;Almost 
Home&lt;/dc:title&gt;&lt;upnp:class&gt;object.item.audioItem.musicTrack&lt;/upnp:class&gt;&lt;dc:creator&gt;Moby&lt;
/dc:creator&gt;&lt;upnp:album&gt;Innocents&lt;/upnp:album&gt;&lt;upnp:originalTrackNumber&gt;3&lt;
/upnp:originalTrackNumber&gt;&lt;r:narrator&gt;Moby&lt;/r:narrator&gt;&lt;/item&gt;

&lt;item id=&quot;Q:0/4&quot; parentID=&quot;Q:0&quot; restricted=&quot;true&quot;&gt;&lt;res protocolInfo=&quot
;x-file-cifs:*:audio/flac:*&quot;&gt;x-file-cifs://xxxxxx/Public/music/Moby/Innocents/04%20-%20Going%20Wrong.flac&lt;
/res&gt;&lt;upnp:albumArtURI&gt;/getaa?u=x-file-cifs%3a%2f%2fxxxxxx%2fPublic%2fmusic%2fMoby%2fInnocents%2f04%2520-
%2520Going%2520Wrong.flac&amp;amp;v=35&lt;/upnp:albumArtURI&gt;&lt;dc:title&gt;Going 
Wrong&lt;/dc:title&gt;&lt;upnp:class&gt;object.item.audioItem.musicTrack&lt;/upnp:class&gt;&lt;dc:creator&gt;Moby&lt;
/dc:creator&gt;&lt;upnp:album&gt;Innocents&lt;/upnp:album&gt;&lt;upnp:originalTrackNumber&gt;4&lt;
/upnp:originalTrackNumber&gt;&lt;r:narrator&gt;Moby&lt;/r:narrator&gt;&lt;/item&gt;

&lt;item id=&quot;Q:0/5&quot; parentID=&quot;Q:0&quot; restricted=&quot;true&quot;&gt;&lt;res protocolInfo=&quot
;x-file-cifs:*:audio/flac:*&quot;&gt;x-file-cifs://xxxxxx/Public/music/Moby/Innocents/05%20-%20The%20Perfect%20Life.flac&lt;
/res&gt;&lt;upnp:albumArtURI&gt;/getaa?u=x-file-cifs%3a%2f%2fxxxxxx%2fPublic%2fmusic%2fMoby%2fInnocents%2f05%2520-
%2520The%2520Perfect%2520Life.flac&amp;amp;v=35&lt;/upnp:albumArtURI&gt;&lt;dc:title&gt;The Perfect 
Life&lt;/dc:title&gt;&lt;upnp:class&gt;object.item.audioItem.musicTrack&lt;/upnp:class&gt;&lt;dc:creator&gt;Moby&lt;
/dc:creator&gt;&lt;upnp:album&gt;Innocents&lt;/upnp:album&gt;&lt;upnp:originalTrackNumber&gt;5&lt;
/upnp:originalTrackNumber&gt;&lt;r:narrator&gt;Moby&lt;/r:narrator&gt;&lt;/item&gt;

&lt;item id=&quot;Q:0/6&quot; parentID=&quot;Q:0&quot; restricted=&quot;true&quot;&gt;&lt;res protocolInfo=&quot
;x-file-cifs:*:audio/flac:*&quot;&gt;x-file-cifs://xxxxxx/Public/music/Moby/Innocents/06%20-%20The%20Last%20Day.flac&lt;
/res&gt;&lt;upnp:albumArtURI&gt;/getaa?u=x-file-cifs%3a%2f%2fxxxxxx%2fPublic%2fmusic%2fMoby%2fInnocents%2f06%2520-
%2520The%2520Last%2520Day.flac&amp;amp;v=35&lt;/upnp:albumArtURI&gt;&lt;dc:title&gt;The Last 
Day&lt;/dc:title&gt;&lt;upnp:class&gt;object.item.audioItem.musicTrack&lt;/upnp:class&gt;&lt;dc:creator&gt;Moby&lt;
/dc:creator&gt;&lt;upnp:album&gt;Innocents&lt;/upnp:album&gt;&lt;upnp:originalTrackNumber&gt;6&lt;
/upnp:originalTrackNumber&gt;&lt;r:narrator&gt;Moby&lt;/r:narrator&gt;&lt;/item&gt;

&lt;/DIDL-Lite&gt;
</Result>
<NumberReturned>6</NumberReturned>
<TotalMatches>28</TotalMatches>
<UpdateID>6</UpdateID>
</u:BrowseResponse>
</s:Body>
</s:Envelope>
However the Demopad regex engine seemed unable to extract past the first 4 tracks in the queue. The regex in Demopad looks like this -

Code: Select all

BrowseResponse.*?dc:creator&gt;(.*?)&lt;/dc:creator
BrowseResponse.*?/item.*?dc:creator&gt;(.*?)&lt;/dc:creator
BrowseResponse.*?/item.*?/item.*?dc:creator&gt;(.*?)&lt;/dc:creator
BrowseResponse.*?/item.*?/item.*?/item.*?dc:creator&gt;(.*?)&lt;/dc:creator

BrowseResponse.*?dc:title&gt;(.*?)&lt;/dc:title
BrowseResponse.*?/item.*?dc:title&gt;(.*?)&lt;/dc:title
BrowseResponse.*?/item.*?/item.*?dc:title&gt;(.*?)&lt;/dc:title
BrowseResponse.*?/item.*?/item.*?/item.*?dc:title&gt;(.*?)&lt;/dc:title
The parts in () are extracted by Demopad into label variables. Initially I tried to extract 6 tracks but had to cut it back to 4 as above.

I assume that some python code running in EG would be able to parse the Sonos feedback and extract more info. Ideally I'd like to extract 10 tracks.

I'm aware that there's been past work on controlling Sonos from EG and I've seen that there's a controller in python on GitHub, but these are more than I need and beyond my abilities. All I'd like to do is send the Browse command and parse the queue feedback for Artist + Title of 10 tracks. And this is limited to just one Sonos box with fixed IP as shown above.

Anyone able and willing to help ?

Cheers,
Peter

Peter M
Posts: 46
Joined: Tue Jun 18, 2019 5:13 am

Re: IP controlled relay board

Post by Peter M » Thu May 14, 2020 12:07 pm

So I decided to have a go. This is the code I came up with -

Code: Select all

import re
import socket
#
browse = eg.event.string.split('.')[-1]
#
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('192.168.0.32', 1400))
#
try:
    c = (
    'POST /MediaServer/ContentDirectory/Control HTTP/1.1\\x0D\\x0ACONNECTION: close\\x0D\\x0A'
    'HOST: 192.168.0.32:1400\\x0D\\x0ACONTENT-LENGTH: 436\\x0D\\x0A'
    'CONTENT-TYPE: text/xml; charset=\"utf-8\"\\x0D\\x0A'
    'SOAPACTION: \"urn:schemas-upnp-org:service:ContentDirectory:1#Browse\"\\x0D\\x0A\\x0D\\x0A'
    '<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\" '
    's:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">'
    '<s:Body><u:Browse xmlns:u=\"urn:schemas-upnp-org:service:ContentDirectory:1\">'
    '<ObjectID>Q:0</ObjectID> <BrowseFlag>BrowseDirectChildren</BrowseFlag>'
    '<Filter>dc:title,dc:creator</Filter>' + browse + '<RequestedCount>10</RequestedCount>'
    '<SortCriteria></SortCriteria></u:Browse></s:Body></s:Envelope>\\x0D\\x0A'
    )
    print 'c='+c
#
    s.sendall(c)
    f = s.recv(4096)
    print 'f='+f
except:
    print 'exception'
finally:
    s.close()
#
    t = (
    re.search('.*?dc:title&gt;(.*?)&lt;/dc:title.*?dc:creator&gt;(.*?)'
    '&lt;/dc:creator.*?dc:title&gt;(.*?)&lt;/dc:title.*?dc:creator&gt;(.*?)'
    '&lt;/dc:creator.*?dc:title&gt;(.*?)&lt;/dc:title.*?dc:creator&gt;(.*?)'
    '&lt;/dc:creator.*?dc:title&gt;(.*?)&lt;/dc:title.*?dc:creator&gt;(.*?)'
    '&lt;/dc:creator.*?dc:title&gt;(.*?)&lt;/dc:title.*?dc:creator&gt;(.*?)'
    '&lt;/dc:creator.*?dc:title&gt;(.*?)&lt;/dc:title.*?dc:creator&gt;(.*?)'
    '&lt;/dc:creator.*?dc:title&gt;(.*?)&lt;/dc:title.*?dc:creator&gt;(.*?)'
    '&lt;/dc:creator.*?dc:title&gt;(.*?)&lt;/dc:title.*?dc:creator&gt;(.*?)'
    '&lt;/dc:creator.*?dc:title&gt;(.*?)&lt;/dc:title.*?dc:creator&gt;(.*?)'
    '&lt;/dc:creator.*?dc:title&gt;(.*?)&lt;/dc:title.*?dc:creator&gt;(.*?)'
    '&lt;/dc:creator', f)
    )
#
    if t:
        eg.plugins.BroadcastListener.Broadcast('BrowseResponse:T1=' + \
        t.group(1) + ':A1=' + t.group(2) + ':T2=' + t.group(3) + ':A2=' + \
        t.group(4) + ':T3=' + t.group(5) + ':A3=' + t.group(6) + ':T4=' + \
        t.group(7) + ':A4=' + t.group(8) + ':T5=' + t.group(9) + ':A5=' + \
        t.group(10) + ':T6=' + t.group(11) + ':A6=' + t.group(12) + ':T7=' + \
        t.group(13) + ':A7=' + t.group(14) + ':T8=' + t.group(15) + ':A8=' + \
        t.group(16) + ':T9=' + t.group(17) + ':A9=' + t.group(18) + ':T10=' + \
        t.group(19) + ':A10=' + t.group(20), u'', 33339)
Ignore the ugly regex and broadcast at the end as it's not getting that far.

The connection appears to be OK but I'm not getting any queue info returned and instead I get a timeout error. Having spent the entire evening on it I'm all out of ideas.

Cheers,
Peter

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

Re: IP controlled relay board

Post by kgschlosser » Fri May 15, 2020 3:15 am

OK so using sockets to handle HTTP requests is a difficult thing to do. The data format in the request and also the response is SOAP. so we need to build a soap request and use the requests library to send the request.

How did you come about the data that should be sent? I ask this because there could be an issue in the data formatting.

This is what I am seeing


preamble or the connection method
POST /MediaServer/ContentDirectory/Control HTTP/1.1


This is a POST connection method you are doing


you have HTTP header data that reads
CONNECTION: close
HOST: 192.168.0.32:1400
CONTENT-LENGTH: 436
CONTENT-TYPE: text/xml; charset: utf-8
SOAPACTION: urn:schemas-upnp-org:service:ContentDirectory:1#Browse



and the body of the message is

Code: Select all

<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
    <s:Body>
        <u:Browse xmlns:u="urn:schemas-upnp-org:service:ContentDirectory:1">
            <ObjectID>Q:0</ObjectID>
            <BrowseFlag>BrowseDirectChildren</BrowseFlag>
            <Filter>dc:title,dc:creator</Filter>
            browse
            <RequestedCount>10</RequestedCount>
            <SortCriteria></SortCriteria>
        </u:Browse>
    </s:Body>
</s:Envelope>
I am not sure what browse is if you could enlighten me as to what the data is supposed to be I can better help.

the length of the data for the body is 405 characters without whatever browse is. browse would need to be exactly 31 characters long based on the data you are sending.
You are specifying a content length of 436 characters.

so this is what we can do to make this somewhat easier.

Code: Select all

import requests

data = '''\
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
    <s:Body>
        <u:Browse xmlns:u="urn:schemas-upnp-org:service:ContentDirectory:1">
            <ObjectID>Q:0</ObjectID>
            <BrowseFlag>BrowseDirectChildren</BrowseFlag>
            <Filter>dc:title,dc:creator</Filter>
            {browse}
            <RequestedCount>10</RequestedCount>
            <SortCriteria></SortCriteria>
        </u:Browse>
    </s:Body>
</s:Envelope>
'''

browse = eg.event.string.split('.')[-1]
data = data.format(browse=browse)


headers = {
    'Content-Type': 'text/html; charset=UTF-8',
    'SOAPACTION': 'urn:schemas-upnp-org:service:ContentDirectory:1#Browse',
    'Content-Length': len(data)
}

response = requests.post('http://192.168.0.32:1400/MediaServer/ContentDirectory/Control', data=data, headers=headers)

print repr(response.content)


This should let us know what exactly is going on if there is a connection issue you will see an error. If there is some other http error or a soap request error it is going to get printed out.
If all is successful with the request it is going to print out the data that was requested. once we know that the connection is good and the request is working properly then we will move forward and parse the returned data.

If the request is successful and the data is returned the way it should be I will need you to turn off the log time stamps and copy and paste the data into a forum post for me this way I can review it.

I am not going to use regex to handle the returned data. instead I am going to use the xml.etree library that is included with EventGhost. The xml.etree library can be a bit of a ball buster to use hen dealing with namespaces which is what SOAP uses. so a copy of the data is going to be crucial in order for me to write the code to properly parse the data.
If you like the work I have been doing then feel free to Image

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

Re: IP controlled relay board

Post by kgschlosser » Fri May 15, 2020 3:47 am

I looked at the information that was returned. It is a SOAP response. that response formatted so it is easier to read looks like so

Code: Select all

<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
    <s:Body>
        <u:BrowseResponse xmlns:u="urn:schemas-upnp-org:service:ContentDirectory:1">
            <Result>
                &lt;DIDL-Lite xmlns:dc=&quot;http://purl.org/dc/elements/1.1/&quot; xmlns:upnp=&quot;urn:schemas-upnp-org:metadata-1-0/upnp/
                &quot; xmlns:r=&quot;urn:schemas-rinconnetworks-com:metadata-1-0/&quot; xmlns=&quot;urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/&quot;&gt;

                &lt;item id=&quot;Q:0/1&quot; parentID=&quot;Q:0&quot; restricted=&quot;true&quot;&gt;&lt;res
                protocolInfo=&quot
                ;x-file-cifs:*:audio/flac:*&quot;&gt;x-file-cifs://xxxxxx/Public/music/Moby/Innocents/01%20-%20Everything%20That%20Rises.flac&lt;
                /res&gt;&lt;upnp:albumArtURI&gt;/getaa?u=x-file-cifs%3a%2f%2fxxxxxx%2fPublic%2fmusic%2fMoby%2fInnocents%2f01%2520-
                %2520Everything%2520That%2520Rises.flac&amp;amp;v=35&lt;/upnp:albumArtURI&gt;&lt;dc:title&gt;Everything
                That
                Rises&lt;/dc:title&gt;&lt;upnp:class&gt;object.item.audioItem.musicTrack&lt;/upnp:class&gt;&lt;dc:creator&gt;Moby&lt;
                /dc:creator&gt;&lt;upnp:album&gt;Innocents&lt;/upnp:album&gt;&lt;upnp:originalTrackNumber&gt;1&lt;
                /upnp:originalTrackNumber&gt;&lt;r:narrator&gt;Moby&lt;/r:narrator&gt;&lt;/item&gt;

                &lt;item id=&quot;Q:0/2&quot; parentID=&quot;Q:0&quot; restricted=&quot;true&quot;&gt;&lt;res
                protocolInfo=&quot
                ;x-file-cifs:*:audio/flac:*&quot;&gt;x-file-cifs://xxxxxx/Public/music/Moby/Innocents/02%20-%20A%20Case%20for%20Shame.flac&lt;
                /res&gt;&lt;upnp:albumArtURI&gt;/getaa?u=x-file-cifs%3a%2f%2fxxxxxx%2fPublic%2fmusic%2fMoby%2fInnocents%2f02%2520-
                %2520A%2520Case%2520for%2520Shame.flac&amp;amp;v=35&lt;/upnp:albumArtURI&gt;&lt;dc:title&gt;A Case for
                Shame&lt;/dc:title&gt;&lt;upnp:class&gt;object.item.audioItem.musicTrack&lt;/upnp:class&gt;&lt;dc:creator&gt;Moby&lt;
                /dc:creator&gt;&lt;upnp:album&gt;Innocents&lt;/upnp:album&gt;&lt;upnp:originalTrackNumber&gt;2&lt;
                /upnp:originalTrackNumber&gt;&lt;r:narrator&gt;Moby&lt;/r:narrator&gt;&lt;/item&gt;

                &lt;item id=&quot;Q:0/3&quot; parentID=&quot;Q:0&quot; restricted=&quot;true&quot;&gt;&lt;res
                protocolInfo=&quot
                ;x-file-cifs:*:audio/flac:*&quot;&gt;x-file-cifs://xxxxxx/Public/music/Moby/Innocents/03%20-%20Almost%20Home.flac&lt;
                /res&gt;&lt;upnp:albumArtURI&gt;/getaa?u=x-file-cifs%3a%2f%2fxxxxxx%2fPublic%2fmusic%2fMoby%2fInnocents%2f03%2520-
                %2520Almost%2520Home.flac&amp;amp;v=35&lt;/upnp:albumArtURI&gt;&lt;dc:title&gt;Almost
                Home&lt;/dc:title&gt;&lt;upnp:class&gt;object.item.audioItem.musicTrack&lt;/upnp:class&gt;&lt;dc:creator&gt;Moby&lt;
                /dc:creator&gt;&lt;upnp:album&gt;Innocents&lt;/upnp:album&gt;&lt;upnp:originalTrackNumber&gt;3&lt;
                /upnp:originalTrackNumber&gt;&lt;r:narrator&gt;Moby&lt;/r:narrator&gt;&lt;/item&gt;

                &lt;item id=&quot;Q:0/4&quot; parentID=&quot;Q:0&quot; restricted=&quot;true&quot;&gt;&lt;res
                protocolInfo=&quot
                ;x-file-cifs:*:audio/flac:*&quot;&gt;x-file-cifs://xxxxxx/Public/music/Moby/Innocents/04%20-%20Going%20Wrong.flac&lt;
                /res&gt;&lt;upnp:albumArtURI&gt;/getaa?u=x-file-cifs%3a%2f%2fxxxxxx%2fPublic%2fmusic%2fMoby%2fInnocents%2f04%2520-
                %2520Going%2520Wrong.flac&amp;amp;v=35&lt;/upnp:albumArtURI&gt;&lt;dc:title&gt;Going
                Wrong&lt;/dc:title&gt;&lt;upnp:class&gt;object.item.audioItem.musicTrack&lt;/upnp:class&gt;&lt;dc:creator&gt;Moby&lt;
                /dc:creator&gt;&lt;upnp:album&gt;Innocents&lt;/upnp:album&gt;&lt;upnp:originalTrackNumber&gt;4&lt;
                /upnp:originalTrackNumber&gt;&lt;r:narrator&gt;Moby&lt;/r:narrator&gt;&lt;/item&gt;

                &lt;item id=&quot;Q:0/5&quot; parentID=&quot;Q:0&quot; restricted=&quot;true&quot;&gt;&lt;res
                protocolInfo=&quot
                ;x-file-cifs:*:audio/flac:*&quot;&gt;x-file-cifs://xxxxxx/Public/music/Moby/Innocents/05%20-%20The%20Perfect%20Life.flac&lt;
                /res&gt;&lt;upnp:albumArtURI&gt;/getaa?u=x-file-cifs%3a%2f%2fxxxxxx%2fPublic%2fmusic%2fMoby%2fInnocents%2f05%2520-
                %2520The%2520Perfect%2520Life.flac&amp;amp;v=35&lt;/upnp:albumArtURI&gt;&lt;dc:title&gt;The Perfect
                Life&lt;/dc:title&gt;&lt;upnp:class&gt;object.item.audioItem.musicTrack&lt;/upnp:class&gt;&lt;dc:creator&gt;Moby&lt;
                /dc:creator&gt;&lt;upnp:album&gt;Innocents&lt;/upnp:album&gt;&lt;upnp:originalTrackNumber&gt;5&lt;
                /upnp:originalTrackNumber&gt;&lt;r:narrator&gt;Moby&lt;/r:narrator&gt;&lt;/item&gt;

                &lt;item id=&quot;Q:0/6&quot; parentID=&quot;Q:0&quot; restricted=&quot;true&quot;&gt;&lt;res
                protocolInfo=&quot
                ;x-file-cifs:*:audio/flac:*&quot;&gt;x-file-cifs://xxxxxx/Public/music/Moby/Innocents/06%20-%20The%20Last%20Day.flac&lt;
                /res&gt;&lt;upnp:albumArtURI&gt;/getaa?u=x-file-cifs%3a%2f%2fxxxxxx%2fPublic%2fmusic%2fMoby%2fInnocents%2f06%2520-
                %2520The%2520Last%2520Day.flac&amp;amp;v=35&lt;/upnp:albumArtURI&gt;&lt;dc:title&gt;The Last
                Day&lt;/dc:title&gt;&lt;upnp:class&gt;object.item.audioItem.musicTrack&lt;/upnp:class&gt;&lt;dc:creator&gt;Moby&lt;
                /dc:creator&gt;&lt;upnp:album&gt;Innocents&lt;/upnp:album&gt;&lt;upnp:originalTrackNumber&gt;6&lt;
                /upnp:originalTrackNumber&gt;&lt;r:narrator&gt;Moby&lt;/r:narrator&gt;&lt;/item&gt;

                &lt;/DIDL-Lite&gt;
            </Result>
            <NumberReturned>6</NumberReturned>
            <TotalMatches>28</TotalMatches>
            <UpdateID>6</UpdateID>
        </u:BrowseResponse>
    </s:Body>
</s:Envelope>
so the data we are after is in

Code: Select all

s:Envelope
    s:Body
        u:BrowseResponse
            Result
in xml the s: actually means http://schemas.xmlsoap.org/soap/envelope/
you can see this here
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
I am sure you can get the u:

so the location in xml translates to

Code: Select all

(http://schemas.xmlsoap.org/soap/envelope/)Envelope 
    (http://schemas.xmlsoap.org/soap/envelope/)Body
         (urn:schemas-upnp-org:service:ContentDirectory:1)BrowseResponse
             Result
that data is

Code: Select all

                &lt;DIDL-Lite xmlns:dc=&quot;http://purl.org/dc/elements/1.1/&quot; xmlns:upnp=&quot;urn:schemas-upnp-org:metadata-1-0/upnp/
                &quot; xmlns:r=&quot;urn:schemas-rinconnetworks-com:metadata-1-0/&quot; xmlns=&quot;urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/&quot;&gt;

                &lt;item id=&quot;Q:0/1&quot; parentID=&quot;Q:0&quot; restricted=&quot;true&quot;&gt;&lt;res
                protocolInfo=&quot
                ;x-file-cifs:*:audio/flac:*&quot;&gt;x-file-cifs://xxxxxx/Public/music/Moby/Innocents/01%20-%20Everything%20That%20Rises.flac&lt;
                /res&gt;&lt;upnp:albumArtURI&gt;/getaa?u=x-file-cifs%3a%2f%2fxxxxxx%2fPublic%2fmusic%2fMoby%2fInnocents%2f01%2520-
                %2520Everything%2520That%2520Rises.flac&amp;amp;v=35&lt;/upnp:albumArtURI&gt;&lt;dc:title&gt;Everything
                That
                Rises&lt;/dc:title&gt;&lt;upnp:class&gt;object.item.audioItem.musicTrack&lt;/upnp:class&gt;&lt;dc:creator&gt;Moby&lt;
                /dc:creator&gt;&lt;upnp:album&gt;Innocents&lt;/upnp:album&gt;&lt;upnp:originalTrackNumber&gt;1&lt;
                /upnp:originalTrackNumber&gt;&lt;r:narrator&gt;Moby&lt;/r:narrator&gt;&lt;/item&gt;

                &lt;item id=&quot;Q:0/2&quot; parentID=&quot;Q:0&quot; restricted=&quot;true&quot;&gt;&lt;res
                protocolInfo=&quot
                ;x-file-cifs:*:audio/flac:*&quot;&gt;x-file-cifs://xxxxxx/Public/music/Moby/Innocents/02%20-%20A%20Case%20for%20Shame.flac&lt;
                /res&gt;&lt;upnp:albumArtURI&gt;/getaa?u=x-file-cifs%3a%2f%2fxxxxxx%2fPublic%2fmusic%2fMoby%2fInnocents%2f02%2520-
                %2520A%2520Case%2520for%2520Shame.flac&amp;amp;v=35&lt;/upnp:albumArtURI&gt;&lt;dc:title&gt;A Case for
                Shame&lt;/dc:title&gt;&lt;upnp:class&gt;object.item.audioItem.musicTrack&lt;/upnp:class&gt;&lt;dc:creator&gt;Moby&lt;
                /dc:creator&gt;&lt;upnp:album&gt;Innocents&lt;/upnp:album&gt;&lt;upnp:originalTrackNumber&gt;2&lt;
                /upnp:originalTrackNumber&gt;&lt;r:narrator&gt;Moby&lt;/r:narrator&gt;&lt;/item&gt;

                &lt;item id=&quot;Q:0/3&quot; parentID=&quot;Q:0&quot; restricted=&quot;true&quot;&gt;&lt;res
                protocolInfo=&quot
                ;x-file-cifs:*:audio/flac:*&quot;&gt;x-file-cifs://xxxxxx/Public/music/Moby/Innocents/03%20-%20Almost%20Home.flac&lt;
                /res&gt;&lt;upnp:albumArtURI&gt;/getaa?u=x-file-cifs%3a%2f%2fxxxxxx%2fPublic%2fmusic%2fMoby%2fInnocents%2f03%2520-
                %2520Almost%2520Home.flac&amp;amp;v=35&lt;/upnp:albumArtURI&gt;&lt;dc:title&gt;Almost
                Home&lt;/dc:title&gt;&lt;upnp:class&gt;object.item.audioItem.musicTrack&lt;/upnp:class&gt;&lt;dc:creator&gt;Moby&lt;
                /dc:creator&gt;&lt;upnp:album&gt;Innocents&lt;/upnp:album&gt;&lt;upnp:originalTrackNumber&gt;3&lt;
                /upnp:originalTrackNumber&gt;&lt;r:narrator&gt;Moby&lt;/r:narrator&gt;&lt;/item&gt;

                &lt;item id=&quot;Q:0/4&quot; parentID=&quot;Q:0&quot; restricted=&quot;true&quot;&gt;&lt;res
                protocolInfo=&quot
                ;x-file-cifs:*:audio/flac:*&quot;&gt;x-file-cifs://xxxxxx/Public/music/Moby/Innocents/04%20-%20Going%20Wrong.flac&lt;
                /res&gt;&lt;upnp:albumArtURI&gt;/getaa?u=x-file-cifs%3a%2f%2fxxxxxx%2fPublic%2fmusic%2fMoby%2fInnocents%2f04%2520-
                %2520Going%2520Wrong.flac&amp;amp;v=35&lt;/upnp:albumArtURI&gt;&lt;dc:title&gt;Going
                Wrong&lt;/dc:title&gt;&lt;upnp:class&gt;object.item.audioItem.musicTrack&lt;/upnp:class&gt;&lt;dc:creator&gt;Moby&lt;
                /dc:creator&gt;&lt;upnp:album&gt;Innocents&lt;/upnp:album&gt;&lt;upnp:originalTrackNumber&gt;4&lt;
                /upnp:originalTrackNumber&gt;&lt;r:narrator&gt;Moby&lt;/r:narrator&gt;&lt;/item&gt;

                &lt;item id=&quot;Q:0/5&quot; parentID=&quot;Q:0&quot; restricted=&quot;true&quot;&gt;&lt;res
                protocolInfo=&quot
                ;x-file-cifs:*:audio/flac:*&quot;&gt;x-file-cifs://xxxxxx/Public/music/Moby/Innocents/05%20-%20The%20Perfect%20Life.flac&lt;
                /res&gt;&lt;upnp:albumArtURI&gt;/getaa?u=x-file-cifs%3a%2f%2fxxxxxx%2fPublic%2fmusic%2fMoby%2fInnocents%2f05%2520-
                %2520The%2520Perfect%2520Life.flac&amp;amp;v=35&lt;/upnp:albumArtURI&gt;&lt;dc:title&gt;The Perfect
                Life&lt;/dc:title&gt;&lt;upnp:class&gt;object.item.audioItem.musicTrack&lt;/upnp:class&gt;&lt;dc:creator&gt;Moby&lt;
                /dc:creator&gt;&lt;upnp:album&gt;Innocents&lt;/upnp:album&gt;&lt;upnp:originalTrackNumber&gt;5&lt;
                /upnp:originalTrackNumber&gt;&lt;r:narrator&gt;Moby&lt;/r:narrator&gt;&lt;/item&gt;

                &lt;item id=&quot;Q:0/6&quot; parentID=&quot;Q:0&quot; restricted=&quot;true&quot;&gt;&lt;res
                protocolInfo=&quot
                ;x-file-cifs:*:audio/flac:*&quot;&gt;x-file-cifs://xxxxxx/Public/music/Moby/Innocents/06%20-%20The%20Last%20Day.flac&lt;
                /res&gt;&lt;upnp:albumArtURI&gt;/getaa?u=x-file-cifs%3a%2f%2fxxxxxx%2fPublic%2fmusic%2fMoby%2fInnocents%2f06%2520-
                %2520The%2520Last%2520Day.flac&amp;amp;v=35&lt;/upnp:albumArtURI&gt;&lt;dc:title&gt;The Last
                Day&lt;/dc:title&gt;&lt;upnp:class&gt;object.item.audioItem.musicTrack&lt;/upnp:class&gt;&lt;dc:creator&gt;Moby&lt;
                /dc:creator&gt;&lt;upnp:album&gt;Innocents&lt;/upnp:album&gt;&lt;upnp:originalTrackNumber&gt;6&lt;
                /upnp:originalTrackNumber&gt;&lt;r:narrator&gt;Moby&lt;/r:narrator&gt;&lt;/item&gt;

                &lt;/DIDL-Lite&gt;
Now this is where your brain is going to get bent somewhat LOL.

that data above is actually XML also. It has been escaped so it is a string. we need to convert that to actual XML data.

we do this by replacing the following

&gt; = >
&lt; = <
&amp; = &
&quot; = "

so after doing the replacement and then formatting the data it ends up being

Code: Select all

<DIDL-Lite xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:upnp="urn:schemas-upnp-org:metadata-1-0/upnp/" xmlns:r="urn:schemas-rinconnetworks-com:metadata-1-0/" xmlns="urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/">
    <item id="Q:0/1" parentID="Q:0" restricted="true">
        <res protocolInfo="x-file-cifs:*:audio/flac:*">
            x-file-cifs://xxxxxx/Public/music/Moby/Innocents/01%20-%20Everything%20That%20Rises.flac
        </res>
        <upnp:albumArtURI>
            /getaa?u=x-file-cifs%3a%2f%2fxxxxxx%2fPublic%2fmusic%2fMoby%2fInnocents%2f01%2520-%2520Everything%2520That%2520Rises.flac&amp;v=35
        </upnp:albumArtURI>
        <dc:title>Everything That Rises</dc:title>
        <upnp:class>object.item.audioItem.musicTrack</upnp:class>
        <dc:creator>Moby</dc:creator>
        <upnp:album>Innocents</upnp:album>
        <upnp:originalTrackNumber>1</upnp:originalTrackNumber>
        <r:narrator>Moby</r:narrator>
    </item>
    <item id="Q:0/2" parentID="Q:0" restricted="true">
        <res protocolInfo="x-file-cifs:*:audio/flac:*">
            x-file-cifs://xxxxxx/Public/music/Moby/Innocents/02%20-%20A%20Case%20for%20Shame.flac
        </res>
        <upnp:albumArtURI>
            /getaa?u=x-file-cifs%3a%2f%2fxxxxxx%2fPublic%2fmusic%2fMoby%2fInnocents%2f02%2520-%2520A%2520Case%2520for%2520Shame.flac&amp;v=35
        </upnp:albumArtURI>
        <dc:title>A Case for Shame</dc:title>
        <upnp:class>object.item.audioItem.musicTrack</upnp:class>
        <dc:creator>Moby</dc:creator>
        <upnp:album>Innocents</upnp:album>
        <upnp:originalTrackNumber>2</upnp:originalTrackNumber>
        <r:narrator>Moby</r:narrator>
    </item>
    <item id="Q:0/3" parentID="Q:0" restricted="true">
        <res protocolInfo="x-file-cifs:*:audio/flac:*">
            x-file-cifs://xxxxxx/Public/music/Moby/Innocents/03%20-%20Almost%20Home.flac
        </res>
        <upnp:albumArtURI>
            /getaa?u=x-file-cifs%3a%2f%2fxxxxxx%2fPublic%2fmusic%2fMoby%2fInnocents%2f03%2520-%2520Almost%2520Home.flac&amp;v=35
        </upnp:albumArtURI>
        <dc:title>Almost Home</dc:title>
        <upnp:class>object.item.audioItem.musicTrack</upnp:class>
        <dc:creator>Moby</dc:creator>
        <upnp:album>Innocents</upnp:album>
        <upnp:originalTrackNumber>3</upnp:originalTrackNumber>
        <r:narrator>Moby</r:narrator>
    </item>
    <item id="Q:0/4" parentID="Q:0" restricted="true">
        <res protocolInfo="x-file-cifs:*:audio/flac:*">
            x-file-cifs://xxxxxx/Public/music/Moby/Innocents/04%20-%20Going%20Wrong.flac
        </res>
        <upnp:albumArtURI>
            /getaa?u=x-file-cifs%3a%2f%2fxxxxxx%2fPublic%2fmusic%2fMoby%2fInnocents%2f04%2520-%2520Going%2520Wrong.flac&amp;v=35
        </upnp:albumArtURI>
        <dc:title>Going Wrong</dc:title>
        <upnp:class>object.item.audioItem.musicTrack</upnp:class>
        <dc:creator>Moby</dc:creator>
        <upnp:album>Innocents</upnp:album>
        <upnp:originalTrackNumber>4</upnp:originalTrackNumber>
        <r:narrator>Moby</r:narrator>
    </item>
    <item id="Q:0/5" parentID="Q:0" restricted="true">
        <res protocolInfo="x-file-cifs:*:audio/flac:*">
            x-file-cifs://xxxxxx/Public/music/Moby/Innocents/05%20-%20The%20Perfect%20Life.flac
        </res>
        <upnp:albumArtURI>
            /getaa?u=x-file-cifs%3a%2f%2fxxxxxx%2fPublic%2fmusic%2fMoby%2fInnocents%2f05%2520-%2520The%2520Perfect%2520Life.flac&amp;v=35
        </upnp:albumArtURI>
        <dc:title>The Perfect Life</dc:title>
        <upnp:class>object.item.audioItem.musicTrack</upnp:class>
        <dc:creator>Moby</dc:creator>
        <upnp:album>Innocents</upnp:album>
        <upnp:originalTrackNumber>5</upnp:originalTrackNumber>
        <r:narrator>Moby</r:narrator>
    </item>
    <item id="Q:0/6" parentID="Q:0" restricted="true">
        <res protocolInfo="x-file-cifs:*:audio/flac:*">
            x-file-cifs://xxxxxx/Public/music/Moby/Innocents/06%20-%20The%20Last%20Day.flac
        </res>
        <upnp:albumArtURI>
            /getaa?u=x-file-cifs%3a%2f%2fxxxxxx%2fPublic%2fmusic%2fMoby%2fInnocents%2f06%2520-%2520The%2520Last%2520Day.flac&amp;v=35
        </upnp:albumArtURI>
        <dc:title>The Last Day</dc:title>
        <upnp:class>object.item.audioItem.musicTrack</upnp:class>
        <dc:creator>Moby</dc:creator>
        <upnp:album>Innocents</upnp:album>
        <upnp:originalTrackNumber>6</upnp:originalTrackNumber>
        <r:narrator>Moby</r:narrator>
    </item>
</DIDL-Lite>
Now this is going to be easier to work with using the xml.etree library.

I am going to start messing about with writing the code to parse the results properly. I am not sure how you are going to want the data formatted after we parse it but we can work on that easy enough.
If you like the work I have been doing then feel free to Image

Post Reply