tiny-dlna 0.5.2__tar.gz → 0.6.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {tiny-dlna-0.5.2 → tiny-dlna-0.6.0}/PKG-INFO +1 -1
- {tiny-dlna-0.5.2 → tiny-dlna-0.6.0}/setup.py +1 -1
- {tiny-dlna-0.5.2 → tiny-dlna-0.6.0}/tiny_dlna.egg-info/PKG-INFO +1 -1
- {tiny-dlna-0.5.2 → tiny-dlna-0.6.0}/tiny_render.py +41 -20
- {tiny-dlna-0.5.2 → tiny-dlna-0.6.0}/tiny_ssdp.py +40 -7
- {tiny-dlna-0.5.2 → tiny-dlna-0.6.0}/README.md +0 -0
- {tiny-dlna-0.5.2 → tiny-dlna-0.6.0}/setup.cfg +0 -0
- {tiny-dlna-0.5.2 → tiny-dlna-0.6.0}/tiny_cli.py +0 -0
- {tiny-dlna-0.5.2 → tiny-dlna-0.6.0}/tiny_dlna.egg-info/SOURCES.txt +0 -0
- {tiny-dlna-0.5.2 → tiny-dlna-0.6.0}/tiny_dlna.egg-info/dependency_links.txt +0 -0
- {tiny-dlna-0.5.2 → tiny-dlna-0.6.0}/tiny_dlna.egg-info/entry_points.txt +0 -0
- {tiny-dlna-0.5.2 → tiny-dlna-0.6.0}/tiny_dlna.egg-info/requires.txt +0 -0
- {tiny-dlna-0.5.2 → tiny-dlna-0.6.0}/tiny_dlna.egg-info/top_level.txt +0 -0
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import argparse
|
|
1
2
|
import html
|
|
2
3
|
import logging
|
|
3
4
|
import re
|
|
@@ -7,12 +8,11 @@ import time
|
|
|
7
8
|
import xml.etree.ElementTree as ET
|
|
8
9
|
|
|
9
10
|
from flask import Flask, request, Response
|
|
10
|
-
from tiny_ssdp import ssdp_listener, RENDER_PORT
|
|
11
|
+
from tiny_ssdp import get_uuid, ssdp_listener, RENDER_PORT
|
|
11
12
|
|
|
12
13
|
app = Flask(__name__)
|
|
13
14
|
logger = logging.getLogger('tiny_render')
|
|
14
15
|
|
|
15
|
-
|
|
16
16
|
XML_DESC_PTN = """<?xml version="1.0" encoding="UTF-8"?>
|
|
17
17
|
<root xmlns:dlna="urn:schemas-dlna-org:device-1-0" xmlns="urn:schemas-upnp-org:device-1-0">
|
|
18
18
|
<specVersion>
|
|
@@ -22,20 +22,32 @@ XML_DESC_PTN = """<?xml version="1.0" encoding="UTF-8"?>
|
|
|
22
22
|
<device>
|
|
23
23
|
<deviceType>urn:schemas-upnp-org:device:MediaRenderer:1</deviceType>
|
|
24
24
|
<friendlyName>{}</friendlyName>
|
|
25
|
+
<UDN>uuid:{}</UDN>
|
|
25
26
|
<manufacturer>mitnk</manufacturer>
|
|
26
27
|
<modelName>Tiny-Render</modelName>
|
|
27
28
|
<modelDescription>AVTransport Media Renderer</modelDescription>
|
|
28
|
-
<UDN>uuid:{}</UDN>
|
|
29
29
|
<dlna:X_DLNADOC xmlns:dlna="urn:schemas-dlna-org:device-1-0">DMR-1.50</dlna:X_DLNADOC>
|
|
30
|
-
|
|
31
|
-
|
|
30
|
+
<serviceList>
|
|
31
|
+
<service>
|
|
32
32
|
<serviceType>urn:schemas-upnp-org:service:AVTransport:1</serviceType>
|
|
33
33
|
<serviceId>urn:upnp-org:serviceId:AVTransport</serviceId>
|
|
34
|
-
<SCPDURL>AVTransport/scpd.xml</SCPDURL>
|
|
35
34
|
<controlURL>AVTransport/control</controlURL>
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
35
|
+
</service>
|
|
36
|
+
<service>
|
|
37
|
+
<serviceType>urn:schemas-upnp-org:service:RenderingControl:1</serviceType>
|
|
38
|
+
<serviceId>urn:upnp-org:serviceId:RenderingControl</serviceId>
|
|
39
|
+
<controlURL>RenderingControl/action</controlURL>
|
|
40
|
+
<eventSubURL>RenderingControl/event</eventSubURL>
|
|
41
|
+
<SCPDURL>dlna/RenderingControl.xml</SCPDURL>
|
|
42
|
+
</service>
|
|
43
|
+
<service>
|
|
44
|
+
<serviceType>urn:schemas-upnp-org:service:ConnectionManager:1</serviceType>
|
|
45
|
+
<serviceId>urn:upnp-org:serviceId:ConnectionManager</serviceId>
|
|
46
|
+
<controlURL>ConnectionManager/action</controlURL>
|
|
47
|
+
<eventSubURL>ConnectionManager/event</eventSubURL>
|
|
48
|
+
<SCPDURL>dlna/ConnectionManager.xml</SCPDURL>
|
|
49
|
+
</service>
|
|
50
|
+
</serviceList>
|
|
39
51
|
</device>
|
|
40
52
|
</root>"""
|
|
41
53
|
|
|
@@ -55,6 +67,14 @@ XML_PLAY_DONE = """
|
|
|
55
67
|
</s:Envelope>
|
|
56
68
|
"""
|
|
57
69
|
|
|
70
|
+
XML_SEEK_DONE = """
|
|
71
|
+
<s:Envelope s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
|
|
72
|
+
<s:Body>
|
|
73
|
+
<u:SeekResponse xmlns:u="urn:schemas-upnp-org:service:AVTransport:1"/>
|
|
74
|
+
</s:Body>
|
|
75
|
+
</s:Envelope>
|
|
76
|
+
"""
|
|
77
|
+
|
|
58
78
|
XML_POSINFO = """
|
|
59
79
|
<s:Envelope s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
|
|
60
80
|
<s:Body>
|
|
@@ -123,8 +143,11 @@ _DATA = {
|
|
|
123
143
|
@app.route('/description.xml')
|
|
124
144
|
def description():
|
|
125
145
|
friendly_name = app.config['FRIENDLY_NAME']
|
|
126
|
-
|
|
127
|
-
|
|
146
|
+
uuid_str = get_uuid()
|
|
147
|
+
xml = XML_DESC_PTN.format(friendly_name, uuid_str)
|
|
148
|
+
resp = Response(xml, mimetype="text/xml")
|
|
149
|
+
resp.headers['Server'] = 'UPnP/1.0 Werkzeug/3.0 TinyRender/0.6'
|
|
150
|
+
return resp
|
|
128
151
|
|
|
129
152
|
def to_track_time(seconds):
|
|
130
153
|
hours = seconds // 3600
|
|
@@ -236,8 +259,8 @@ def control():
|
|
|
236
259
|
return Response(XML_STOP_DONE, mimetype="text/xml")
|
|
237
260
|
|
|
238
261
|
elif is_seek(request):
|
|
239
|
-
logger.debug('seek')
|
|
240
|
-
return Response(
|
|
262
|
+
logger.debug('action:seek')
|
|
263
|
+
return Response(XML_SEEK_DONE, mimetype="text/xml")
|
|
241
264
|
|
|
242
265
|
logger.error(f'action not support: {request.data}')
|
|
243
266
|
return Response('Action Not Supported', status=500)
|
|
@@ -249,26 +272,24 @@ class SSDPServer(threading.Thread):
|
|
|
249
272
|
|
|
250
273
|
|
|
251
274
|
def main():
|
|
252
|
-
import argparse
|
|
253
|
-
|
|
254
275
|
parser = argparse.ArgumentParser(prog='tiny-render')
|
|
255
|
-
parser.add_argument('--http-
|
|
276
|
+
parser.add_argument('--http-logs', action='store_true', help='Enable server logs')
|
|
256
277
|
parser.add_argument('--verbose', '-v', action='store_true', help='Enable debug logs')
|
|
257
278
|
parser.add_argument('--name', type=str, default='Tiny Render', help='Change render name')
|
|
258
279
|
|
|
259
280
|
args = parser.parse_args()
|
|
260
281
|
|
|
261
282
|
logging.basicConfig(
|
|
262
|
-
level=logging.
|
|
283
|
+
level=logging.ERROR,
|
|
263
284
|
format='[%(asctime)s][%(levelname)s] %(message)s',
|
|
264
285
|
)
|
|
265
286
|
|
|
266
|
-
if not args.
|
|
287
|
+
if not args.http_logs:
|
|
267
288
|
logging.getLogger('werkzeug').setLevel(logging.ERROR)
|
|
268
|
-
logging.getLogger('
|
|
289
|
+
logging.getLogger('tiny_ssdp').setLevel(logging.ERROR)
|
|
269
290
|
|
|
270
291
|
if args.verbose:
|
|
271
|
-
|
|
292
|
+
logger.setLevel(logging.DEBUG)
|
|
272
293
|
|
|
273
294
|
ssdp = SSDPServer()
|
|
274
295
|
ssdp.start()
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import datetime
|
|
2
|
+
import json
|
|
2
3
|
import logging
|
|
4
|
+
import os
|
|
3
5
|
import psutil
|
|
4
6
|
import socket
|
|
5
7
|
import uuid
|
|
@@ -7,8 +9,27 @@ import uuid
|
|
|
7
9
|
SSDP_MULTICAST_IP = '239.255.255.250'
|
|
8
10
|
SSDP_PORT = 1900
|
|
9
11
|
RENDER_PORT = 55000
|
|
10
|
-
|
|
11
|
-
|
|
12
|
+
logger = logging.getLogger('tiny_ssdp')
|
|
13
|
+
ST_VALUE_MEDIARENDERER = 'mediarenderer'
|
|
14
|
+
ST_VALUE_AVTRANSPORT = 'avtransport'
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def get_uuid():
|
|
18
|
+
home_dir = os.path.expanduser('~')
|
|
19
|
+
app_data_dir = os.path.join(home_dir, '.config', 'tiny-dlna')
|
|
20
|
+
os.makedirs(app_data_dir, exist_ok=True)
|
|
21
|
+
config_file = os.path.join(app_data_dir, 'tiny-render.json')
|
|
22
|
+
|
|
23
|
+
if os.path.exists(config_file):
|
|
24
|
+
with open(config_file) as f:
|
|
25
|
+
configs = json.load(f)
|
|
26
|
+
if 'UUID' in configs and configs['UUID']:
|
|
27
|
+
return configs['UUID']
|
|
28
|
+
|
|
29
|
+
uuid_str = f'{uuid.uuid4()}'
|
|
30
|
+
with open(config_file, 'w') as f:
|
|
31
|
+
configs = {'UUID': uuid_str}
|
|
32
|
+
json.dump(configs, f)
|
|
12
33
|
|
|
13
34
|
|
|
14
35
|
def get_host_ip():
|
|
@@ -28,22 +49,33 @@ def get_host_ip():
|
|
|
28
49
|
return ips[0]
|
|
29
50
|
|
|
30
51
|
|
|
31
|
-
def build_m_search_response():
|
|
52
|
+
def build_m_search_response(st):
|
|
32
53
|
now = datetime.datetime.utcnow()
|
|
33
54
|
date_str = now.strftime("%a, %d %b %Y %H:%M:%S GMT")
|
|
34
55
|
host_ip = get_host_ip()
|
|
35
56
|
location = f'http://{host_ip}:{RENDER_PORT}/description.xml'
|
|
57
|
+
uuid_str = get_uuid()
|
|
36
58
|
|
|
37
59
|
text = 'HTTP/1.1 200 OK\r\n'
|
|
38
|
-
|
|
60
|
+
if st == ST_VALUE_MEDIARENDERER:
|
|
61
|
+
text += 'ST: urn:schemas-upnp-org:device:MediaRenderer:1\r\n'
|
|
62
|
+
text += f'USN: uuid:{uuid_str}::urn:schemas-upnp-org:device:MediaRenderer:1\r\n'
|
|
63
|
+
else:
|
|
64
|
+
text += 'ST: urn:schemas-upnp-org:service:AVTransport:1\r\n'
|
|
65
|
+
text += f'USN: uuid:{uuid_str}::urn:schemas-upnp-org:service:AVTransport:1\r\n'
|
|
39
66
|
text += f'Location: {location}\r\n'
|
|
40
|
-
text += 'ST: urn:schemas-upnp-org:service:AVTransport:1\r\n'
|
|
41
67
|
text += 'EXT: \r\n'
|
|
42
|
-
text += 'Server: Werkzeug/3.0
|
|
68
|
+
text += 'Server: Werkzeug/3.0 TinyRender/0.6\r\n'
|
|
43
69
|
text += 'Cache-Control: max-age=70\r\n'
|
|
44
70
|
text += f'Date: {date_str}\r\n\r\n'
|
|
45
71
|
return text.encode('utf-8')
|
|
46
72
|
|
|
73
|
+
def get_search_target(data):
|
|
74
|
+
if b':device:MediaRenderer:1' in data:
|
|
75
|
+
return ST_VALUE_MEDIARENDERER
|
|
76
|
+
else:
|
|
77
|
+
return ST_VALUE_AVTRANSPORT
|
|
78
|
+
|
|
47
79
|
def ssdp_listener():
|
|
48
80
|
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
|
|
49
81
|
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
|
@@ -56,8 +88,9 @@ def ssdp_listener():
|
|
|
56
88
|
while True:
|
|
57
89
|
data, addr = sock.recvfrom(1024)
|
|
58
90
|
if b'M-SEARCH' in data and b'ssdp:discover' in data:
|
|
91
|
+
st = get_search_target(data)
|
|
59
92
|
logger.info(f'Received M-SEARCH from {addr}, sending response...')
|
|
60
|
-
sock.sendto(build_m_search_response(), addr)
|
|
93
|
+
sock.sendto(build_m_search_response(st), addr)
|
|
61
94
|
|
|
62
95
|
if __name__ == '__main__':
|
|
63
96
|
ssdp_listener()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|