서승완

feat: add mosaic-server and mosaic-client

1 +import json
2 +import os
3 +import requests
4 +import subprocess
5 +import time
6 +
7 +import cv2
8 +import torch
9 +
10 +from models.experimental import attempt_load
11 +from utils.datasets import LoadImages
12 +from utils.general import check_img_size, non_max_suppression, set_logging, scale_coords
13 +from utils.torch_utils import select_device, time_synchronized
14 +
15 +SERVER_CHECK_ENDPOINT = 'http://mosaic.khunet.net'
16 +WEIGHT_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'weight.pt')
17 +INPUT_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'input.mp4')
18 +OUTPUT_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'output.ts')
19 +
20 +
21 +def download(url, file_path):
22 + with open(file_path, 'wb') as file:
23 + res = requests.get(url)
24 + file.write(res.content)
25 +
26 +
27 +def mosaic(src, ratio=0.07):
28 + small = cv2.resize(src, None, fx=ratio, fy=ratio)
29 + return cv2.resize(small, src.shape[:2][::-1], interpolation=cv2.INTER_NEAREST)
30 +
31 +
32 +@torch.no_grad()
33 +def detect(weight_path, input_path, output_path):
34 + command = ['ffmpeg',
35 + '-loglevel', 'panic',
36 + '-y',
37 + '-f', 'rawvideo',
38 + '-pixel_format', 'bgr24',
39 + '-video_size', "{}x{}".format(1280, 720),
40 + '-framerate', str(30),
41 + '-i', '-',
42 + '-i', input_path,
43 + '-c:a', 'copy',
44 + '-map', '0:v:0',
45 + '-map', '1:a:0',
46 + '-c:v', 'libx264',
47 + '-pix_fmt', 'yuv420p',
48 + '-preset', 'ultrafast',
49 + output_path]
50 + writer = subprocess.Popen(command, stdin=subprocess.PIPE)
51 +
52 + source, weights, imgsz = input_path, weight_path, 640
53 +
54 + # Initialize
55 + set_logging()
56 + device = select_device('')
57 +
58 + # Load model
59 + model = attempt_load(weights, map_location=device) # load FP32 model
60 + stride = int(model.stride.max()) # model stride
61 + imgsz = check_img_size(imgsz, s=stride) # check img_size
62 + names = model.module.names if hasattr(model, 'module') else model.names # get class names
63 +
64 + # Set Dataloader
65 + dataset = LoadImages(source, img_size=imgsz, stride=stride)
66 +
67 + # Run inference
68 + if device.type != 'cpu':
69 + model(torch.zeros(1, 3, imgsz, imgsz).to(device).type_as(next(model.parameters()))) # run once
70 +
71 + t0 = time.time()
72 + for path, img, im0s, vid_cap in dataset:
73 + img = torch.from_numpy(img).to(device)
74 + img = img.float() # uint8 to fp16/32
75 + img /= 255.0 # 0 - 255 to 0.0 - 1.0
76 + if img.ndimension() == 3:
77 + img = img.unsqueeze(0)
78 +
79 + # Inference
80 + t1 = time_synchronized()
81 + pred = model(img, augment=False)[0]
82 +
83 + # Apply NMS
84 + pred = non_max_suppression(pred, max_det=1000)
85 + t2 = time_synchronized()
86 +
87 + # Process detections
88 + for i, det in enumerate(pred): # detections per image
89 + p, s, im0, frame = path, '', im0s.copy(), getattr(dataset, 'frame', 0)
90 +
91 + s += '%gx%g ' % img.shape[2:] # print string
92 +
93 + if len(det):
94 + # Rescale boxes from img_size to im0 size
95 + det[:, :4] = scale_coords(img.shape[2:], det[:, :4], im0.shape).round()
96 +
97 + # Print results
98 + for c in det[:, -1].unique():
99 + n = (det[:, -1] == c).sum() # detections per class
100 + s += f"{n} {names[int(c)]}{'s' * (n > 1)}, " # add to string
101 +
102 + # Write results
103 + for *xyxy, conf, cls in reversed(det):
104 + x1, y1, x2, y2 = int(xyxy[0]), int(xyxy[1]), int(xyxy[2]), int(xyxy[3])
105 + src = im0[y1:y2, x1:x2]
106 + dst = im0.copy()
107 + dst[y1:y2, x1:x2] = mosaic(src)
108 + im0 = dst
109 +
110 + # Print time (inference + NMS)
111 + # print(f'{s}Done. ({t2 - t1:.3f}s)')
112 +
113 + # Save results (image with detections)
114 + writer.stdin.write(im0.tobytes())
115 +
116 + writer.stdin.close()
117 + writer.wait()
118 + print(f'Done. ({time.time() - t0:.3f}s)')
119 +
120 +
121 +if __name__ == '__main__':
122 + while True:
123 + try:
124 + response = requests.get(SERVER_CHECK_ENDPOINT + '/check')
125 + data = json.loads(response.text)
126 + if data['data'] is None:
127 + continue
128 + download(SERVER_CHECK_ENDPOINT + '/origin/' + data['data'], INPUT_PATH)
129 + detect(WEIGHT_PATH, INPUT_PATH, OUTPUT_PATH)
130 + response = requests.post(SERVER_CHECK_ENDPOINT + '/upload', files={'file': (data['data'], open(OUTPUT_PATH, 'rb'))})
131 + print(data['data'] + ' : ' + response.text)
132 + except:
133 + print('Error!')
134 + time.sleep(0.5)
1 +{
2 + "name": "mosaic-server",
3 + "version": "0.0.0",
4 + "lockfileVersion": 1,
5 + "requires": true,
6 + "dependencies": {
7 + "accepts": {
8 + "version": "1.3.7",
9 + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz",
10 + "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==",
11 + "requires": {
12 + "mime-types": "~2.1.24",
13 + "negotiator": "0.6.2"
14 + }
15 + },
16 + "append-field": {
17 + "version": "1.0.0",
18 + "resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz",
19 + "integrity": "sha1-HjRA6RXwsSA9I3SOeO3XubW0PlY="
20 + },
21 + "array-flatten": {
22 + "version": "1.1.1",
23 + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
24 + "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI="
25 + },
26 + "body-parser": {
27 + "version": "1.19.0",
28 + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz",
29 + "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==",
30 + "requires": {
31 + "bytes": "3.1.0",
32 + "content-type": "~1.0.4",
33 + "debug": "2.6.9",
34 + "depd": "~1.1.2",
35 + "http-errors": "1.7.2",
36 + "iconv-lite": "0.4.24",
37 + "on-finished": "~2.3.0",
38 + "qs": "6.7.0",
39 + "raw-body": "2.4.0",
40 + "type-is": "~1.6.17"
41 + }
42 + },
43 + "buffer-from": {
44 + "version": "1.1.1",
45 + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz",
46 + "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A=="
47 + },
48 + "busboy": {
49 + "version": "0.2.14",
50 + "resolved": "https://registry.npmjs.org/busboy/-/busboy-0.2.14.tgz",
51 + "integrity": "sha1-bCpiLvz0fFe7vh4qnDetNseSVFM=",
52 + "requires": {
53 + "dicer": "0.2.5",
54 + "readable-stream": "1.1.x"
55 + }
56 + },
57 + "bytes": {
58 + "version": "3.1.0",
59 + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz",
60 + "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg=="
61 + },
62 + "concat-stream": {
63 + "version": "1.6.2",
64 + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz",
65 + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==",
66 + "requires": {
67 + "buffer-from": "^1.0.0",
68 + "inherits": "^2.0.3",
69 + "readable-stream": "^2.2.2",
70 + "typedarray": "^0.0.6"
71 + },
72 + "dependencies": {
73 + "isarray": {
74 + "version": "1.0.0",
75 + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
76 + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE="
77 + },
78 + "readable-stream": {
79 + "version": "2.3.7",
80 + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz",
81 + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==",
82 + "requires": {
83 + "core-util-is": "~1.0.0",
84 + "inherits": "~2.0.3",
85 + "isarray": "~1.0.0",
86 + "process-nextick-args": "~2.0.0",
87 + "safe-buffer": "~5.1.1",
88 + "string_decoder": "~1.1.1",
89 + "util-deprecate": "~1.0.1"
90 + }
91 + },
92 + "string_decoder": {
93 + "version": "1.1.1",
94 + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
95 + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
96 + "requires": {
97 + "safe-buffer": "~5.1.0"
98 + }
99 + }
100 + }
101 + },
102 + "content-disposition": {
103 + "version": "0.5.3",
104 + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz",
105 + "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==",
106 + "requires": {
107 + "safe-buffer": "5.1.2"
108 + }
109 + },
110 + "content-type": {
111 + "version": "1.0.4",
112 + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz",
113 + "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA=="
114 + },
115 + "cookie": {
116 + "version": "0.4.0",
117 + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz",
118 + "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg=="
119 + },
120 + "cookie-signature": {
121 + "version": "1.0.6",
122 + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
123 + "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw="
124 + },
125 + "core-util-is": {
126 + "version": "1.0.2",
127 + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
128 + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac="
129 + },
130 + "debug": {
131 + "version": "2.6.9",
132 + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
133 + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
134 + "requires": {
135 + "ms": "2.0.0"
136 + }
137 + },
138 + "denque": {
139 + "version": "1.5.0",
140 + "resolved": "https://registry.npmjs.org/denque/-/denque-1.5.0.tgz",
141 + "integrity": "sha512-CYiCSgIF1p6EUByQPlGkKnP1M9g0ZV3qMIrqMqZqdwazygIA/YP2vrbcyl1h/WppKJTdl1F85cXIle+394iDAQ=="
142 + },
143 + "depd": {
144 + "version": "1.1.2",
145 + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz",
146 + "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak="
147 + },
148 + "destroy": {
149 + "version": "1.0.4",
150 + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz",
151 + "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA="
152 + },
153 + "dicer": {
154 + "version": "0.2.5",
155 + "resolved": "https://registry.npmjs.org/dicer/-/dicer-0.2.5.tgz",
156 + "integrity": "sha1-WZbAhrszIYyBLAkL3cCc0S+stw8=",
157 + "requires": {
158 + "readable-stream": "1.1.x",
159 + "streamsearch": "0.1.2"
160 + }
161 + },
162 + "ee-first": {
163 + "version": "1.1.1",
164 + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
165 + "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0="
166 + },
167 + "encodeurl": {
168 + "version": "1.0.2",
169 + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
170 + "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k="
171 + },
172 + "escape-html": {
173 + "version": "1.0.3",
174 + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
175 + "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg="
176 + },
177 + "etag": {
178 + "version": "1.8.1",
179 + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
180 + "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc="
181 + },
182 + "express": {
183 + "version": "4.17.1",
184 + "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz",
185 + "integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==",
186 + "requires": {
187 + "accepts": "~1.3.7",
188 + "array-flatten": "1.1.1",
189 + "body-parser": "1.19.0",
190 + "content-disposition": "0.5.3",
191 + "content-type": "~1.0.4",
192 + "cookie": "0.4.0",
193 + "cookie-signature": "1.0.6",
194 + "debug": "2.6.9",
195 + "depd": "~1.1.2",
196 + "encodeurl": "~1.0.2",
197 + "escape-html": "~1.0.3",
198 + "etag": "~1.8.1",
199 + "finalhandler": "~1.1.2",
200 + "fresh": "0.5.2",
201 + "merge-descriptors": "1.0.1",
202 + "methods": "~1.1.2",
203 + "on-finished": "~2.3.0",
204 + "parseurl": "~1.3.3",
205 + "path-to-regexp": "0.1.7",
206 + "proxy-addr": "~2.0.5",
207 + "qs": "6.7.0",
208 + "range-parser": "~1.2.1",
209 + "safe-buffer": "5.1.2",
210 + "send": "0.17.1",
211 + "serve-static": "1.14.1",
212 + "setprototypeof": "1.1.1",
213 + "statuses": "~1.5.0",
214 + "type-is": "~1.6.18",
215 + "utils-merge": "1.0.1",
216 + "vary": "~1.1.2"
217 + }
218 + },
219 + "finalhandler": {
220 + "version": "1.1.2",
221 + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz",
222 + "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==",
223 + "requires": {
224 + "debug": "2.6.9",
225 + "encodeurl": "~1.0.2",
226 + "escape-html": "~1.0.3",
227 + "on-finished": "~2.3.0",
228 + "parseurl": "~1.3.3",
229 + "statuses": "~1.5.0",
230 + "unpipe": "~1.0.0"
231 + }
232 + },
233 + "forwarded": {
234 + "version": "0.2.0",
235 + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
236 + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow=="
237 + },
238 + "fresh": {
239 + "version": "0.5.2",
240 + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
241 + "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac="
242 + },
243 + "http-errors": {
244 + "version": "1.7.2",
245 + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz",
246 + "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==",
247 + "requires": {
248 + "depd": "~1.1.2",
249 + "inherits": "2.0.3",
250 + "setprototypeof": "1.1.1",
251 + "statuses": ">= 1.5.0 < 2",
252 + "toidentifier": "1.0.0"
253 + }
254 + },
255 + "iconv-lite": {
256 + "version": "0.4.24",
257 + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
258 + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
259 + "requires": {
260 + "safer-buffer": ">= 2.1.2 < 3"
261 + }
262 + },
263 + "inherits": {
264 + "version": "2.0.3",
265 + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
266 + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4="
267 + },
268 + "ipaddr.js": {
269 + "version": "1.9.1",
270 + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
271 + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g=="
272 + },
273 + "isarray": {
274 + "version": "0.0.1",
275 + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
276 + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8="
277 + },
278 + "media-typer": {
279 + "version": "0.3.0",
280 + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
281 + "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g="
282 + },
283 + "merge-descriptors": {
284 + "version": "1.0.1",
285 + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz",
286 + "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E="
287 + },
288 + "methods": {
289 + "version": "1.1.2",
290 + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
291 + "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4="
292 + },
293 + "mime": {
294 + "version": "1.6.0",
295 + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
296 + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg=="
297 + },
298 + "mime-db": {
299 + "version": "1.48.0",
300 + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.48.0.tgz",
301 + "integrity": "sha512-FM3QwxV+TnZYQ2aRqhlKBMHxk10lTbMt3bBkMAp54ddrNeVSfcQYOOKuGuy3Ddrm38I04If834fOUSq1yzslJQ=="
302 + },
303 + "mime-types": {
304 + "version": "2.1.31",
305 + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.31.tgz",
306 + "integrity": "sha512-XGZnNzm3QvgKxa8dpzyhFTHmpP3l5YNusmne07VUOXxou9CqUqYa/HBy124RqtVh/O2pECas/MOcsDgpilPOPg==",
307 + "requires": {
308 + "mime-db": "1.48.0"
309 + }
310 + },
311 + "minimist": {
312 + "version": "1.2.5",
313 + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
314 + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw=="
315 + },
316 + "mkdirp": {
317 + "version": "0.5.5",
318 + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz",
319 + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==",
320 + "requires": {
321 + "minimist": "^1.2.5"
322 + }
323 + },
324 + "ms": {
325 + "version": "2.0.0",
326 + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
327 + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
328 + },
329 + "multer": {
330 + "version": "1.4.2",
331 + "resolved": "https://registry.npmjs.org/multer/-/multer-1.4.2.tgz",
332 + "integrity": "sha512-xY8pX7V+ybyUpbYMxtjM9KAiD9ixtg5/JkeKUTD6xilfDv0vzzOFcCp4Ljb1UU3tSOM3VTZtKo63OmzOrGi3Cg==",
333 + "requires": {
334 + "append-field": "^1.0.0",
335 + "busboy": "^0.2.11",
336 + "concat-stream": "^1.5.2",
337 + "mkdirp": "^0.5.1",
338 + "object-assign": "^4.1.1",
339 + "on-finished": "^2.3.0",
340 + "type-is": "^1.6.4",
341 + "xtend": "^4.0.0"
342 + }
343 + },
344 + "negotiator": {
345 + "version": "0.6.2",
346 + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz",
347 + "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw=="
348 + },
349 + "object-assign": {
350 + "version": "4.1.1",
351 + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
352 + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM="
353 + },
354 + "on-finished": {
355 + "version": "2.3.0",
356 + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz",
357 + "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=",
358 + "requires": {
359 + "ee-first": "1.1.1"
360 + }
361 + },
362 + "parseurl": {
363 + "version": "1.3.3",
364 + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
365 + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ=="
366 + },
367 + "path-to-regexp": {
368 + "version": "0.1.7",
369 + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
370 + "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w="
371 + },
372 + "process-nextick-args": {
373 + "version": "2.0.1",
374 + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
375 + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag=="
376 + },
377 + "proxy-addr": {
378 + "version": "2.0.7",
379 + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
380 + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==",
381 + "requires": {
382 + "forwarded": "0.2.0",
383 + "ipaddr.js": "1.9.1"
384 + }
385 + },
386 + "qs": {
387 + "version": "6.7.0",
388 + "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz",
389 + "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ=="
390 + },
391 + "range-parser": {
392 + "version": "1.2.1",
393 + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
394 + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg=="
395 + },
396 + "raw-body": {
397 + "version": "2.4.0",
398 + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz",
399 + "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==",
400 + "requires": {
401 + "bytes": "3.1.0",
402 + "http-errors": "1.7.2",
403 + "iconv-lite": "0.4.24",
404 + "unpipe": "1.0.0"
405 + }
406 + },
407 + "readable-stream": {
408 + "version": "1.1.14",
409 + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz",
410 + "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=",
411 + "requires": {
412 + "core-util-is": "~1.0.0",
413 + "inherits": "~2.0.1",
414 + "isarray": "0.0.1",
415 + "string_decoder": "~0.10.x"
416 + }
417 + },
418 + "redis": {
419 + "version": "3.1.2",
420 + "resolved": "https://registry.npmjs.org/redis/-/redis-3.1.2.tgz",
421 + "integrity": "sha512-grn5KoZLr/qrRQVwoSkmzdbw6pwF+/rwODtrOr6vuBRiR/f3rjSTGupbF90Zpqm2oenix8Do6RV7pYEkGwlKkw==",
422 + "requires": {
423 + "denque": "^1.5.0",
424 + "redis-commands": "^1.7.0",
425 + "redis-errors": "^1.2.0",
426 + "redis-parser": "^3.0.0"
427 + }
428 + },
429 + "redis-commands": {
430 + "version": "1.7.0",
431 + "resolved": "https://registry.npmjs.org/redis-commands/-/redis-commands-1.7.0.tgz",
432 + "integrity": "sha512-nJWqw3bTFy21hX/CPKHth6sfhZbdiHP6bTawSgQBlKOVRG7EZkfHbbHwQJnrE4vsQf0CMNE+3gJ4Fmm16vdVlQ=="
433 + },
434 + "redis-errors": {
435 + "version": "1.2.0",
436 + "resolved": "https://registry.npmjs.org/redis-errors/-/redis-errors-1.2.0.tgz",
437 + "integrity": "sha1-62LSrbFeTq9GEMBK/hUpOEJQq60="
438 + },
439 + "redis-parser": {
440 + "version": "3.0.0",
441 + "resolved": "https://registry.npmjs.org/redis-parser/-/redis-parser-3.0.0.tgz",
442 + "integrity": "sha1-tm2CjNyv5rS4pCin3vTGvKwxyLQ=",
443 + "requires": {
444 + "redis-errors": "^1.0.0"
445 + }
446 + },
447 + "safe-buffer": {
448 + "version": "5.1.2",
449 + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
450 + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
451 + },
452 + "safer-buffer": {
453 + "version": "2.1.2",
454 + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
455 + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
456 + },
457 + "send": {
458 + "version": "0.17.1",
459 + "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz",
460 + "integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==",
461 + "requires": {
462 + "debug": "2.6.9",
463 + "depd": "~1.1.2",
464 + "destroy": "~1.0.4",
465 + "encodeurl": "~1.0.2",
466 + "escape-html": "~1.0.3",
467 + "etag": "~1.8.1",
468 + "fresh": "0.5.2",
469 + "http-errors": "~1.7.2",
470 + "mime": "1.6.0",
471 + "ms": "2.1.1",
472 + "on-finished": "~2.3.0",
473 + "range-parser": "~1.2.1",
474 + "statuses": "~1.5.0"
475 + },
476 + "dependencies": {
477 + "ms": {
478 + "version": "2.1.1",
479 + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz",
480 + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg=="
481 + }
482 + }
483 + },
484 + "serve-static": {
485 + "version": "1.14.1",
486 + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz",
487 + "integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==",
488 + "requires": {
489 + "encodeurl": "~1.0.2",
490 + "escape-html": "~1.0.3",
491 + "parseurl": "~1.3.3",
492 + "send": "0.17.1"
493 + }
494 + },
495 + "setprototypeof": {
496 + "version": "1.1.1",
497 + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz",
498 + "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw=="
499 + },
500 + "statuses": {
501 + "version": "1.5.0",
502 + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz",
503 + "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow="
504 + },
505 + "streamsearch": {
506 + "version": "0.1.2",
507 + "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-0.1.2.tgz",
508 + "integrity": "sha1-gIudDlb8Jz2Am6VzOOkpkZoanxo="
509 + },
510 + "string_decoder": {
511 + "version": "0.10.31",
512 + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz",
513 + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ="
514 + },
515 + "toidentifier": {
516 + "version": "1.0.0",
517 + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz",
518 + "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw=="
519 + },
520 + "type-is": {
521 + "version": "1.6.18",
522 + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
523 + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
524 + "requires": {
525 + "media-typer": "0.3.0",
526 + "mime-types": "~2.1.24"
527 + }
528 + },
529 + "typedarray": {
530 + "version": "0.0.6",
531 + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz",
532 + "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c="
533 + },
534 + "unpipe": {
535 + "version": "1.0.0",
536 + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
537 + "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw="
538 + },
539 + "util-deprecate": {
540 + "version": "1.0.2",
541 + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
542 + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8="
543 + },
544 + "utils-merge": {
545 + "version": "1.0.1",
546 + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
547 + "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM="
548 + },
549 + "vary": {
550 + "version": "1.1.2",
551 + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
552 + "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw="
553 + },
554 + "xtend": {
555 + "version": "4.0.2",
556 + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
557 + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ=="
558 + }
559 + }
560 +}
1 +{
2 + "name": "mosaic-server",
3 + "version": "0.0.0",
4 + "dependencies": {
5 + "express": "^4.17.1",
6 + "multer": "^1.4.2",
7 + "redis": "^3.1.2"
8 + }
9 +}
1 +const express = require('express');
2 +const fs = require('fs').promises;
3 +const multer = require('multer');
4 +const redis = require('redis');
5 +const { promisify } = require('util');
6 +const { readM3U8 } = require('./utils');
7 +
8 +const RTMP_INPUT_FOLDER = '/root/hls/test_720p2628kbs';
9 +
10 +const app = express();
11 +const client = redis.createClient();
12 +const upload = multer({
13 + storage: multer.diskStorage({
14 + destination: (req, file, cb) => cb(null, __dirname + '/public/live'),
15 + filename: (req, file, cb) => cb(null, file.originalname),
16 + }),
17 +});
18 +
19 +const sleep = (ms) => new Promise(r => setTimeout(r, ms));
20 +const delAsync = promisify(client.del).bind(client);
21 +const getAsync = promisify(client.get).bind(client);
22 +const setAsync = promisify(client.set).bind(client);
23 +const rpushAsync = promisify(client.rpush).bind(client);
24 +const lpopAsync = promisify(client.lpop).bind(client);
25 +const lrangeAsync = promisify(client.lrange).bind(client);
26 +const saddAsync = promisify(client.sadd).bind(client);
27 +const sismemberAsync = promisify(client.sismember).bind(client);
28 +const sremAsync = promisify(client.srem).bind(client);
29 +
30 +app.use(express.static(__dirname + '/public'));
31 +
32 +app.get('/live.m3u8', async (req, res) => {
33 + const EXT_X_TARGETDURATION = await getAsync('HLS_EXT_X_TARGETDURATION');
34 + const PLAYLIST = await lrangeAsync('HLS_COMPLETE', 0, -1);
35 + const data = `#EXTM3U
36 +#EXT-X-PLAYLIST-TYPE:EVENT
37 +#EXT-X-TARGETDURATION:${EXT_X_TARGETDURATION}
38 +#EXT-X-VERSION:3
39 +#EXT-X-MEDIA-SEQUENCE:0
40 +${PLAYLIST.map(x => `#EXTINF:${x.split('/')[1]},\nlive/${x.split('/')[0]}\n`).join('')}`;
41 + res.set('content-type', 'audio/mpegurl');
42 + return res.send(data);
43 +});
44 +
45 +app.get('/origin.m3u8', async (req, res) => {
46 + const EXT_X_TARGETDURATION = await getAsync('HLS_EXT_X_TARGETDURATION');
47 + const PLAYLIST = await lrangeAsync('HLS_ORIGINAL', 0, -1);
48 + const data = `#EXTM3U
49 +#EXT-X-PLAYLIST-TYPE:EVENT
50 +#EXT-X-TARGETDURATION:${EXT_X_TARGETDURATION}
51 +#EXT-X-VERSION:3
52 +#EXT-X-MEDIA-SEQUENCE:0
53 +${PLAYLIST.map(x => `#EXTINF:${x.split('/')[1]},\norigin/${x.split('/')[0]}\n`).join('')}`;
54 + res.set('content-type', 'audio/mpegurl');
55 + return res.send(data);
56 +});
57 +
58 +app.get('/check', async (req, res) => {
59 + const data = await lpopAsync('HLS_WAITING');
60 + if (data === null) {
61 + return res.json({ data: null });
62 + }
63 + await rpushAsync('HLS_PROGRESS', data);
64 + return res.json({ data: data.split('/')[0] });
65 +});
66 +
67 +app.post('/upload', upload.single('file'), async (req, res) => {
68 + await saddAsync('HLS_UPLOADED', req.file.originalname);
69 + return res.json({ success: true });
70 +});
71 +
72 +app.use((req, res, next) => {
73 + return res.status(404).json({ error: 'Not Found' });
74 +});
75 +
76 +app.use((err, req, res, next) => {
77 + return res.status(500).json({ error: 'Internal Server Error' });
78 +});
79 +
80 +app.listen(3000);
81 +
82 +const main = async () => {
83 + for (;;) {
84 + try {
85 + const m3u8 = await readM3U8(`${RTMP_INPUT_FOLDER}/index.m3u8`);
86 + const lastSeq = await getAsync('HLS_EXT_X_MEDIA_SEQUENCE');
87 + if (m3u8.EXT_X_MEDIA_SEQUENCE !== lastSeq) {
88 + if (m3u8.EXT_X_MEDIA_SEQUENCE === '0') {
89 + await delAsync('HLS_WAITING');
90 + await delAsync('HLS_PROGRESS');
91 + await delAsync('HLS_UPLOADED');
92 + await delAsync('HLS_COMPLETE');
93 + await delAsync('HLS_ORIGINAL'); // TODO: debug
94 + await fs.rmdir(__dirname + '/public/origin/', { recursive: true });
95 + await fs.rmdir(__dirname + '/public/live/', { recursive: true });
96 + await fs.mkdir(__dirname + '/public/origin/');
97 + await fs.mkdir(__dirname + '/public/live/');
98 + await setAsync('HLS_EXT_X_TARGETDURATION', m3u8.EXT_X_TARGETDURATION);
99 + }
100 + const item = m3u8.PLAYLIST.pop();
101 + await fs.copyFile(`${RTMP_INPUT_FOLDER}/${item.file}`, __dirname + `/public/origin/${item.file}`);
102 + await rpushAsync('HLS_WAITING', `${item.file}/${item.time}`);
103 + await rpushAsync('HLS_ORIGINAL', `${item.file}/${item.time}`); // TODO: debug
104 + await setAsync('HLS_EXT_X_MEDIA_SEQUENCE', m3u8.EXT_X_MEDIA_SEQUENCE);
105 + }
106 + const first_of_progress = await lrangeAsync('HLS_PROGRESS', 0, 0);
107 + if (first_of_progress.length !== 0 && await sismemberAsync('HLS_UPLOADED', first_of_progress[0].split('/')[0]) === 1) {
108 + await rpushAsync('HLS_COMPLETE', first_of_progress[0]);
109 + await lpopAsync('HLS_PROGRESS');
110 + await sremAsync('HLS_UPLOADED', first_of_progress[0].split('/')[0]);
111 + }
112 + } catch (e) {}
113 + await sleep(1000);
114 + }
115 +}
116 +
117 +main().catch(err => console.error(err));
1 +const fs = require('fs').promises;
2 +
3 +module.exports.readM3U8 = async (filePath) => {
4 + const data = await fs.readFile(filePath);
5 + const EXT_X_VERSION = String(data).split('#EXT-X-VERSION:')[1].split('\n')[0];
6 + const EXT_X_MEDIA_SEQUENCE = String(data).split('#EXT-X-MEDIA-SEQUENCE:')[1].split('\n')[0];
7 + const EXT_X_TARGETDURATION = String(data).split('#EXT-X-TARGETDURATION:')[1].split('\n')[0];
8 + const EXTINF = String(data).split('#EXTINF:');
9 + EXTINF.shift();
10 + const PLAYLIST = EXTINF.map(x => ({ file: x.split('\n')[1], time: x.split(',')[0] }));
11 + return {
12 + EXT_X_VERSION,
13 + EXT_X_MEDIA_SEQUENCE,
14 + EXT_X_TARGETDURATION,
15 + PLAYLIST,
16 + };
17 +};