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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: tiny-dlna
3
- Version: 0.5.2
3
+ Version: 0.6.0
4
4
  Summary: a tiny DLNA receiver
5
5
  Home-page: https://github.com/mitnk/tiny-dlna
6
6
  Author: mitnk
@@ -28,7 +28,7 @@ Note: mpv needs to be installed on your system.
28
28
 
29
29
  setup(
30
30
  name='tiny-dlna',
31
- version='0.5.2',
31
+ version='0.6.0',
32
32
  description='a tiny DLNA receiver',
33
33
  long_description=DESC,
34
34
  url='https://github.com/mitnk/tiny-dlna',
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: tiny-dlna
3
- Version: 0.5.2
3
+ Version: 0.6.0
4
4
  Summary: a tiny DLNA receiver
5
5
  Home-page: https://github.com/mitnk/tiny-dlna
6
6
  Author: mitnk
@@ -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, UUID
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
- <serviceList>
31
- <service>
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
- <eventSubURL>AVTransport/event</eventSubURL>
37
- </service>
38
- </serviceList>
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
- xml = XML_DESC_PTN.format(friendly_name, UUID)
127
- return Response(xml, mimetype="text/xml")
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('To Seek', status=500)
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-log', action='store_true', help='Enable server logs')
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.INFO,
283
+ level=logging.ERROR,
263
284
  format='[%(asctime)s][%(levelname)s] %(message)s',
264
285
  )
265
286
 
266
- if not args.http_log:
287
+ if not args.http_logs:
267
288
  logging.getLogger('werkzeug').setLevel(logging.ERROR)
268
- logging.getLogger('ssdp').setLevel(logging.ERROR)
289
+ logging.getLogger('tiny_ssdp').setLevel(logging.ERROR)
269
290
 
270
291
  if args.verbose:
271
- logging.basicConfig(level=logging.DEBUG)
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
- UUID = f'{uuid.uuid4()}'
11
- logger = logging.getLogger('ssdp')
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
- text += f'USN: uuid:{UUID}::urn:schemas-upnp-org:service:AVTransport:1\r\n'
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.3 Python/3.11.0\r\n'
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