tiny-dlna 0.0.1__tar.gz → 0.3.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.0.1
3
+ Version: 0.3.0
4
4
  Summary: a tiny DLNA receiver
5
5
  Home-page: https://github.com/mitnk/tiny-dlna
6
6
  Author: mitnk
@@ -0,0 +1,30 @@
1
+ # Tiny DLNA
2
+
3
+ ## Install
4
+
5
+ ```
6
+ $ pip install tiny-dlna
7
+ ```
8
+
9
+ ## Tiny DLNA Render
10
+
11
+ Just to support subtitles.
12
+
13
+ ```
14
+ $ tiny-render
15
+ ```
16
+
17
+ This will activate a DLNA receiver named "Tiny Render", which can stream videos
18
+ from apps like 虎牙直播, Bilibili, and other video platforms. Additionally, you
19
+ can also use `nano-dlna` to play local videos (like in your RaspberryPi) on it.
20
+
21
+ > Note: [mpv](https://mpv.io/) needs to be installed on your system.
22
+
23
+ ## Tiny DLNA Cli
24
+
25
+ To do.
26
+
27
+ ## Related projects
28
+
29
+ - https://github.com/xfangfang/Macast
30
+ - https://github.com/gabrielmagno/nano-dlna
@@ -23,7 +23,7 @@ Usages
23
23
 
24
24
  setup(
25
25
  name='tiny-dlna',
26
- version='0.0.1',
26
+ version='0.3.0',
27
27
  description='a tiny DLNA receiver',
28
28
  long_description=DESC,
29
29
  url='https://github.com/mitnk/tiny-dlna',
@@ -1,3 +1,4 @@
1
+ import logging
1
2
  import socket
2
3
 
3
4
  SSDP_MULTICAST = '239.255.255.250'
@@ -17,6 +18,8 @@ ssdp_response = (
17
18
  '\r\n'
18
19
  )
19
20
 
21
+ logger = logging.getLogger('ssdp')
22
+
20
23
  def ssdp_listener():
21
24
  sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
22
25
  sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
@@ -29,7 +32,7 @@ def ssdp_listener():
29
32
  while True:
30
33
  data, addr = sock.recvfrom(1024)
31
34
  if b'M-SEARCH' in data and b'ssdp:discover' in data:
32
- print(f'Received M-SEARCH from {addr}, sending response...')
35
+ logger.info(f'Received M-SEARCH from {addr}, sending response...')
33
36
  sock.sendto(ssdp_response.encode('utf-8'), addr)
34
37
 
35
38
  if __name__ == '__main__':
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: tiny-dlna
3
- Version: 0.0.1
3
+ Version: 0.3.0
4
4
  Summary: a tiny DLNA receiver
5
5
  Home-page: https://github.com/mitnk/tiny-dlna
6
6
  Author: mitnk
@@ -1,4 +1,5 @@
1
1
  import html
2
+ import logging
2
3
  import re
3
4
  import subprocess
4
5
  import threading
@@ -9,6 +10,7 @@ from flask import Flask, request, Response
9
10
  from ssdp import ssdp_listener
10
11
 
11
12
  app = Flask(__name__)
13
+ logger = logging.getLogger('tiny_render')
12
14
 
13
15
  XML_AVSET_DONE = """
14
16
  <s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
@@ -72,7 +74,9 @@ class MPVRenderer:
72
74
  cmd.append('--title={}'.format(title))
73
75
  if srt:
74
76
  cmd.append('--sub-file={}'.format(srt))
75
- self.process = subprocess.Popen(cmd)
77
+
78
+ logger.debug(f'running mpv: {cmd}')
79
+ self.process = subprocess.Popen(cmd, stderr=subprocess.DEVNULL)
76
80
 
77
81
  def stop_media(self):
78
82
  if self.process:
@@ -91,44 +95,47 @@ _DATA = {
91
95
 
92
96
  @app.route('/description.xml')
93
97
  def description():
94
- xml = """<?xml version="1.0"?>
95
- <root xmlns="urn:schemas-upnp-org:device-1-0" xmlns:dlna="urn:schemas-dlna-org:device-1-0">
96
- <specVersion>
97
- <major>1</major>
98
- <minor>0</minor>
99
- </specVersion>
100
- <device>
101
- <deviceType>urn:schemas-upnp-org:device:MediaRenderer:1</deviceType>
102
- <friendlyName>Tiny Render</friendlyName>
103
- <manufacturer>mitnk</manufacturer>
104
- <modelName>T001</modelName>
105
- <UDN>uuid:dlna-tiny-render-t001</UDN>
106
- <dlna:X_DLNADOC xmlns:dlna="urn:schemas-dlna-org:device-1-0">DMR-1.50</dlna:X_DLNADOC>
107
- <serviceList>
108
- <service>
109
- <serviceType>urn:schemas-upnp-org:service:AVTransport:1</serviceType>
110
- <serviceId>urn:upnp-org:serviceId:AVTransport</serviceId>
111
- <SCPDURL>AVTransport/82d8-eb72-b097/scpd.xml</SCPDURL>
112
- <controlURL>AVTransport/82d8-eb72-b097/control</controlURL>
113
- <eventSubURL>AVTransport/82d8-eb72-b097/event</eventSubURL>
114
- </service>
115
- <service>
116
- <serviceType>urn:schemas-upnp-org:service:ConnectionManager:1</serviceType>
117
- <serviceId>urn:upnp-org:serviceId:ConnectionManager</serviceId>
118
- <SCPDURL>ConnectionManager/82d8-eb72-b097/scpd.xml</SCPDURL>
119
- <controlURL>ConnectionManager/82d8-eb72-b097/control</controlURL>
120
- <eventSubURL>ConnectionManager/82d8-eb72-b097/event</eventSubURL>
121
- </service>
122
- <service>
123
- <serviceType>urn:schemas-upnp-org:service:RenderingControl:1</serviceType>
124
- <serviceId>urn:upnp-org:serviceId:RenderingControl</serviceId>
125
- <SCPDURL>RenderingControl/82d8-eb72-b097/scpd.xml</SCPDURL>
126
- <controlURL>RenderingControl/82d8-eb72-b097/control</controlURL>
127
- <eventSubURL>RenderingControl/82d8-eb72-b097/event</eventSubURL>
128
- </service>
129
- </serviceList>
130
- </device>
131
- </root>"""
98
+ desc_ptn = """<?xml version="1.0"?>
99
+ <root xmlns="urn:schemas-upnp-org:device-1-0" xmlns:dlna="urn:schemas-dlna-org:device-1-0">
100
+ <specVersion>
101
+ <major>1</major>
102
+ <minor>0</minor>
103
+ </specVersion>
104
+ <device>
105
+ <deviceType>urn:schemas-upnp-org:device:MediaRenderer:1</deviceType>
106
+ <friendlyName>{}</friendlyName>
107
+ <manufacturer>mitnk</manufacturer>
108
+ <modelName>T001</modelName>
109
+ <UDN>uuid:dlna-tiny-render-t001</UDN>
110
+ <dlna:X_DLNADOC xmlns:dlna="urn:schemas-dlna-org:device-1-0">DMR-1.50</dlna:X_DLNADOC>
111
+ <serviceList>
112
+ <service>
113
+ <serviceType>urn:schemas-upnp-org:service:AVTransport:1</serviceType>
114
+ <serviceId>urn:upnp-org:serviceId:AVTransport</serviceId>
115
+ <SCPDURL>AVTransport/82d8-eb72-b097/scpd.xml</SCPDURL>
116
+ <controlURL>AVTransport/82d8-eb72-b097/control</controlURL>
117
+ <eventSubURL>AVTransport/82d8-eb72-b097/event</eventSubURL>
118
+ </service>
119
+ <service>
120
+ <serviceType>urn:schemas-upnp-org:service:ConnectionManager:1</serviceType>
121
+ <serviceId>urn:upnp-org:serviceId:ConnectionManager</serviceId>
122
+ <SCPDURL>ConnectionManager/82d8-eb72-b097/scpd.xml</SCPDURL>
123
+ <controlURL>ConnectionManager/82d8-eb72-b097/control</controlURL>
124
+ <eventSubURL>ConnectionManager/82d8-eb72-b097/event</eventSubURL>
125
+ </service>
126
+ <service>
127
+ <serviceType>urn:schemas-upnp-org:service:RenderingControl:1</serviceType>
128
+ <serviceId>urn:upnp-org:serviceId:RenderingControl</serviceId>
129
+ <SCPDURL>RenderingControl/82d8-eb72-b097/scpd.xml</SCPDURL>
130
+ <controlURL>RenderingControl/82d8-eb72-b097/control</controlURL>
131
+ <eventSubURL>RenderingControl/82d8-eb72-b097/event</eventSubURL>
132
+ </service>
133
+ </serviceList>
134
+ </device>
135
+ </root>"""
136
+
137
+ friendly_name = app.config['FRIENDLY_NAME']
138
+ xml = desc_ptn.format(friendly_name)
132
139
  return Response(xml, mimetype="text/xml")
133
140
 
134
141
  def to_track_time(seconds):
@@ -152,6 +159,9 @@ def is_gettrans(request):
152
159
  def is_getpos(request):
153
160
  return b'u:GetPositionInfo' in request.data
154
161
 
162
+ def is_seek(request):
163
+ return b'u:Seek' in request.data
164
+
155
165
  def get_title_re(xml_data):
156
166
  pattern = re.compile(r'<dc:title>(.*?)</dc:title>', re.DOTALL)
157
167
  match = pattern.search(xml_data)
@@ -163,15 +173,15 @@ def get_metadata(request):
163
173
  current_uri = root.find('.//CurrentURI').text
164
174
  metadata = root.find('.//CurrentURIMetaData').text
165
175
 
166
- current_srt = None
167
- title = None
176
+ current_srt = ''
177
+ title = ''
168
178
 
169
179
  metadata = html.unescape(metadata)
170
180
  try:
171
181
  metadata = ET.fromstring(metadata)
172
182
  except ET.ParseError:
173
183
  # HACK: fall back to `re` to get title only (e.g. Huya)
174
- print('** got xml.ParseError, fall back to re')
184
+ logger.debug('** got xml.ParseError, fall back to re')
175
185
  title = get_title_re(metadata)
176
186
  return {'video': current_uri, 'title': title}
177
187
 
@@ -196,18 +206,15 @@ def get_metadata(request):
196
206
  def control():
197
207
  if is_setav(request):
198
208
  metadata = get_metadata(request)
199
-
200
209
  current_uri = metadata['video']
201
- current_srt = metadata.get('srt')
202
- video_title = metadata.get('title')
210
+ current_srt = metadata.get('srt', '')
211
+ video_title = metadata.get('title', '')
203
212
 
204
- print('++ SetAV:', current_uri)
213
+ logger.debug(f'Action: SetAV: {current_uri}')
214
+ logger.debug(f'SRT: {current_srt}')
205
215
  _DATA['CURRENT_URI'] = current_uri
206
- print('++ SRT:', current_srt)
207
- if current_srt:
208
- _DATA['CURRENT_SRT'] = current_srt
209
- if video_title:
210
- _DATA['VIDEO_TITLE'] = video_title
216
+ _DATA['CURRENT_SRT'] = current_srt
217
+ _DATA['VIDEO_TITLE'] = video_title
211
218
  return Response(XML_AVSET_DONE, mimetype="text/xml")
212
219
 
213
220
  elif is_play(request):
@@ -215,22 +222,24 @@ def control():
215
222
  url = _DATA['CURRENT_URI']
216
223
  srt = _DATA['CURRENT_SRT']
217
224
  title = _DATA['VIDEO_TITLE']
218
- print('++ Playing', url)
225
+ logger.debug(f'action: Play: {url}')
226
+ logger.debug(f'title: {title} srt: {srt}')
219
227
  renderer.play_media(url, title, srt)
220
228
  return Response(XML_PLAY_DONE, mimetype="text/xml")
221
229
 
222
230
  elif is_getpos(request):
223
- print('++ GetPositionInfo ++')
231
+ logger.debug('action: GetPositionInfo')
224
232
  seconds = int(time.time() - _DATA['STARTED_AT'])
225
233
  reltime = to_track_time(seconds)
226
234
  return Response(XML_POSINFO.format(reltime), mimetype="text/xml")
227
235
 
228
236
  elif is_gettrans(request):
229
- print('++ GetTransportInfo ++')
237
+ logger.debug('action: GetTransportInfo')
230
238
  return Response(XML_TRANSINFO, mimetype="text/xml")
231
239
 
232
240
  elif is_stop(request):
233
- print('\n+++ Stopping +++')
241
+ logger.debug('stopping')
242
+
234
243
  _DATA['CURRENT_URI'] = ''
235
244
  _DATA['CURRENT_SRT'] = ''
236
245
  _DATA['VIDEO_TITLE'] = ''
@@ -238,7 +247,11 @@ def control():
238
247
  renderer.stop_media()
239
248
  return Response(XML_STOP_DONE, mimetype="text/xml")
240
249
 
241
- print(request.data)
250
+ elif is_seek(request):
251
+ logger.debug('seek')
252
+ return Response('To Seek', status=500)
253
+
254
+ logger.error(f'action not support: {request.data}')
242
255
  return Response('Action Not Supported', status=500)
243
256
 
244
257
 
@@ -248,9 +261,34 @@ class SSDPServer(threading.Thread):
248
261
 
249
262
 
250
263
  def main():
264
+ import argparse
265
+
266
+ parser = argparse.ArgumentParser(prog='tiny-render')
267
+ parser.add_argument('--http-log', action='store_true', help='Enable server logs')
268
+ parser.add_argument('--verbose', '-v', action='store_true', help='Enable debug logs')
269
+ parser.add_argument('--name', type=str, default='Tiny Render', help='Change render name')
270
+
271
+ args = parser.parse_args()
272
+
273
+ logging.basicConfig(
274
+ level=logging.INFO,
275
+ format='[%(asctime)s][%(levelname)s] %(message)s',
276
+ )
277
+
278
+ if not args.http_log:
279
+ logging.getLogger('werkzeug').setLevel(logging.ERROR)
280
+ logging.getLogger('ssdp').setLevel(logging.ERROR)
281
+
282
+ if args.verbose:
283
+ logging.basicConfig(level=logging.DEBUG)
284
+
251
285
  ssdp = SSDPServer()
252
286
  ssdp.start()
253
- app.run(host="0.0.0.0", port=5000)
287
+
288
+ friendly_name = args.name
289
+ app.config['FRIENDLY_NAME'] = friendly_name
290
+ logging.info(f'Starting DLNA Receiver: {friendly_name}')
291
+ app.run(host="0.0.0.0", port=5000, debug=False)
254
292
 
255
293
 
256
294
  if __name__ == "__main__":
tiny-dlna-0.0.1/README.md DELETED
@@ -1,13 +0,0 @@
1
- # Tiny DLNA Render
2
-
3
- Just to support subtitles.
4
-
5
- ```
6
- $ pip install tiny-dlna
7
- $ tiny-render
8
- ```
9
-
10
- ## Related projects
11
-
12
- - https://github.com/xfangfang/Macast
13
- - https://github.com/gabrielmagno/nano-dlna
File without changes