Stepp1 commited on
Commit
57bb9d4
·
1 Parent(s): 662b5cd

[app] adding app, download model and example imgs

Browse files
app.py ADDED
@@ -0,0 +1,61 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import torch
3
+ import torch.nn as nn
4
+ from torchvision import transforms
5
+ from torchvision.models import resnet18
6
+ from transferwee import download
7
+
8
+ model = resnet18(pretrained=False)
9
+ model.fc = nn.Sequential(
10
+ nn.Linear(512, 16),
11
+ nn.ReLU(),
12
+ nn.Linear(16,1)
13
+ )
14
+
15
+ # download latest model
16
+ download("https://we.tl/t-25s74dahjU", "best.pt")
17
+
18
+ checkpoint = torch.load("best.pt")
19
+ model.load_state_dict(checkpoint['model_state_dict'])
20
+ model.eval()
21
+
22
+ labels_to_class = {
23
+ 0: "normal",
24
+ 1: "risk"
25
+ }
26
+ def predict(inp):
27
+ inp = transforms.ToTensor()(inp).unsqueeze(0) # [1, C, H, W]
28
+ with torch.no_grad():
29
+ prediction = torch.sigmoid(model(inp)[0])
30
+ if prediction > 0.7:
31
+ confidences = {
32
+ "Normal": float(prediction[0])
33
+ }
34
+
35
+ else:
36
+ confidences = {
37
+ "Riesgo": 1 - float(prediction[0])
38
+ }
39
+
40
+ print(confidences)
41
+ return confidences
42
+
43
+
44
+ description = "<center>Esto un demo de nuestro clasificador de operaciones de riesgo en el uso de grúas horquillas.\n\nNuestro demo utiliza una versión simple de una red convolucional pre-entrenada y, posteriormente, finetuneada en nuestro conjunto de datos.</center>"
45
+
46
+ examples = [
47
+ "images/test-risk-16_frame_413.jpg",
48
+ "images/test-risk-4_frame_609.jpg",
49
+ "images/test-normal-0_frame_574.jpg",
50
+ "images/test-normal-27_frame_2320.jpg"
51
+ ]
52
+
53
+ gr.Interface(
54
+ fn=predict,
55
+ inputs=gr.Image(type="pil"),
56
+ outputs=gr.Label(num_top_classes=3),
57
+ title="Forklikt Risk Detection Demo",
58
+ description=description,
59
+ examples=examples,
60
+ ).launch()
61
+
images/test-normal-0_frame_574.jpg ADDED
images/test-normal-27_frame_2320.jpg ADDED
images/test-risk-16_frame_413.jpg ADDED
images/test-risk-4_frame_609.jpg ADDED
transferwee.py ADDED
@@ -0,0 +1,437 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+
3
+ #
4
+ # Copyright (c) 2018-2022 Leonardo Taccari
5
+ # All rights reserved.
6
+ #
7
+ # Redistribution and use in source and binary forms, with or without
8
+ # modification, are permitted provided that the following conditions
9
+ # are met:
10
+ #
11
+ # 1. Redistributions of source code must retain the above copyright
12
+ # notice, this list of conditions and the following disclaimer.
13
+ # 2. Redistributions in binary form must reproduce the above copyright
14
+ # notice, this list of conditions and the following disclaimer in the
15
+ # documentation and/or other materials provided with the distribution.
16
+ #
17
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18
+ # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
19
+ # TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
20
+ # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS
21
+ # BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
22
+ # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
23
+ # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
24
+ # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
25
+ # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
26
+ # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27
+ # POSSIBILITY OF SUCH DAMAGE.
28
+ #
29
+
30
+
31
+ """
32
+ Download/upload files via wetransfer.com
33
+
34
+ transferwee is a script/module to download/upload files via wetransfer.com.
35
+
36
+ It exposes `download' and `upload' subcommands, respectively used to download
37
+ files from a `we.tl' or `wetransfer.com/downloads' URLs and upload files that
38
+ will be shared via emails or link.
39
+ """
40
+
41
+ from typing import List, Optional
42
+ import logging
43
+ import os.path
44
+ import re
45
+ import urllib.parse
46
+ import zlib
47
+
48
+ import requests
49
+
50
+
51
+ WETRANSFER_API_URL = 'https://wetransfer.com/api/v4/transfers'
52
+ WETRANSFER_DOWNLOAD_URL = WETRANSFER_API_URL + '/{transfer_id}/download'
53
+ WETRANSFER_UPLOAD_EMAIL_URL = WETRANSFER_API_URL + '/email'
54
+ WETRANSFER_VERIFY_URL = WETRANSFER_API_URL + '/{transfer_id}/verify'
55
+ WETRANSFER_UPLOAD_LINK_URL = WETRANSFER_API_URL + '/link'
56
+ WETRANSFER_FILES_URL = WETRANSFER_API_URL + '/{transfer_id}/files'
57
+ WETRANSFER_PART_PUT_URL = WETRANSFER_FILES_URL + '/{file_id}/part-put-url'
58
+ WETRANSFER_FINALIZE_MPP_URL = WETRANSFER_FILES_URL + '/{file_id}/finalize-mpp'
59
+ WETRANSFER_FINALIZE_URL = WETRANSFER_API_URL + '/{transfer_id}/finalize'
60
+
61
+ WETRANSFER_DEFAULT_CHUNK_SIZE = 5242880
62
+ WETRANSFER_EXPIRE_IN = 604800
63
+
64
+
65
+ logger = logging.getLogger(__name__)
66
+
67
+
68
+ def download_url(url: str) -> Optional[str]:
69
+ """Given a wetransfer.com download URL download return the downloadable URL.
70
+
71
+ The URL should be of the form `https://we.tl/' or
72
+ `https://wetransfer.com/downloads/'. If it is a short URL (i.e. `we.tl')
73
+ the redirect is followed in order to retrieve the corresponding
74
+ `wetransfer.com/downloads/' URL.
75
+
76
+ The following type of URLs are supported:
77
+ - `https://we.tl/<short_url_id>`:
78
+ received via link upload, via email to the sender and printed by
79
+ `upload` action
80
+ - `https://wetransfer.com/<transfer_id>/<security_hash>`:
81
+ directly not shared in any ways but the short URLs actually redirect to
82
+ them
83
+ - `https://wetransfer.com/<transfer_id>/<recipient_id>/<security_hash>`:
84
+ received via email by recipients when the files are shared via email
85
+ upload
86
+
87
+ Return the download URL (AKA `direct_link') as a str or None if the URL
88
+ could not be parsed.
89
+ """
90
+ logger.debug(f'Getting download URL of {url}')
91
+ # Follow the redirect if we have a short URL
92
+ if url.startswith('https://we.tl/'):
93
+ r = requests.head(url, allow_redirects=True)
94
+ logger.debug(f'Short URL {url} redirects to {r.url}')
95
+ url = r.url
96
+
97
+ recipient_id = None
98
+ params = urllib.parse.urlparse(url).path.split('/')[2:]
99
+
100
+ if len(params) == 2:
101
+ transfer_id, security_hash = params
102
+ elif len(params) == 3:
103
+ transfer_id, recipient_id, security_hash = params
104
+ else:
105
+ return None
106
+
107
+ logger.debug(f'Getting direct_link of {url}')
108
+ j = {
109
+ "intent": "entire_transfer",
110
+ "security_hash": security_hash,
111
+ }
112
+ if recipient_id:
113
+ j["recipient_id"] = recipient_id
114
+ s = _prepare_session()
115
+ if not s:
116
+ raise ConnectionError('Could not prepare session')
117
+ r = s.post(WETRANSFER_DOWNLOAD_URL.format(transfer_id=transfer_id),
118
+ json=j)
119
+ _close_session(s)
120
+
121
+ j = r.json()
122
+ return j.get('direct_link')
123
+
124
+
125
+ def _file_unquote(file: str) -> str:
126
+ """Given a URL encoded file unquote it.
127
+
128
+ All occurences of `\', `/' and `../' will be ignored to avoid possible
129
+ directory traversals.
130
+ """
131
+ return urllib.parse.unquote(file).replace('../', '').replace('/', '').replace('\\', '')
132
+
133
+
134
+ def download(url: str, file: str = '') -> None:
135
+ """Given a `we.tl/' or `wetransfer.com/downloads/' download it.
136
+
137
+ First a direct link is retrieved (via download_url()), the filename can be
138
+ provided via the optional `file' argument. If not provided the filename
139
+ will be extracted to it and it will be fetched and stored on the current
140
+ working directory.
141
+ """
142
+ logger.debug(f'Downloading {url}')
143
+ dl_url = download_url(url)
144
+ if not dl_url:
145
+ logger.error(f'Could not find direct link of {url}')
146
+ return None
147
+ if not file:
148
+ file = _file_unquote(urllib.parse.urlparse(dl_url).path.split('/')[-1])
149
+
150
+ logger.debug(f'Fetching {dl_url}')
151
+ r = requests.get(dl_url, stream=True)
152
+ with open(file, 'wb') as f:
153
+ for chunk in r.iter_content(chunk_size=1024):
154
+ f.write(chunk)
155
+
156
+
157
+ def _file_name_and_size(file: str) -> dict:
158
+ """Given a file, prepare the "name" and "size" dictionary.
159
+
160
+ Return a dictionary with "name" and "size" keys.
161
+ """
162
+ filename = os.path.basename(file)
163
+ filesize = os.path.getsize(file)
164
+
165
+ return {
166
+ "name": filename,
167
+ "size": filesize
168
+ }
169
+
170
+
171
+ def _prepare_session() -> Optional[requests.Session]:
172
+ """Prepare a wetransfer.com session.
173
+
174
+ Return a requests session that will always pass the required headers
175
+ and with cookies properly populated that can be used for wetransfer
176
+ requests.
177
+ """
178
+ s = requests.Session()
179
+ r = s.get('https://wetransfer.com/')
180
+ m = re.search('name="csrf-token" content="([^"]+)"', r.text)
181
+ if not m:
182
+ logger.error(f'Could not find any csrf-token')
183
+ return None
184
+ s.headers.update({
185
+ 'x-csrf-token': m.group(1),
186
+ 'x-requested-with': 'XMLHttpRequest',
187
+ })
188
+
189
+ return s
190
+
191
+
192
+ def _close_session(s: requests.Session) -> None:
193
+ """Close a wetransfer.com session.
194
+
195
+ Terminate wetransfer.com session.
196
+ """
197
+ s.close()
198
+
199
+
200
+ def _prepare_email_upload(filenames: List[str], display_name: str, message: str,
201
+ sender: str, recipients: List[str],
202
+ session: requests.Session) -> dict:
203
+ """Given a list of filenames, message a sender and recipients prepare for
204
+ the email upload.
205
+
206
+ Return the parsed JSON response.
207
+ """
208
+ j = {
209
+ "files": [_file_name_and_size(f) for f in filenames],
210
+ "from": sender,
211
+ "display_name": display_name,
212
+ "message": message,
213
+ "recipients": recipients,
214
+ "ui_language": "en",
215
+ }
216
+
217
+ r = session.post(WETRANSFER_UPLOAD_EMAIL_URL, json=j)
218
+ return r.json()
219
+
220
+
221
+ def _verify_email_upload(transfer_id: str, session: requests.Session) -> str:
222
+ """Given a transfer_id, read the code from standard input.
223
+
224
+ Return the parsed JSON response.
225
+ """
226
+ code = input('Code:')
227
+
228
+ j = {
229
+ "code": code,
230
+ "expire_in": WETRANSFER_EXPIRE_IN,
231
+ }
232
+
233
+ r = session.post(WETRANSFER_VERIFY_URL.format(transfer_id=transfer_id),
234
+ json=j)
235
+ return r.json()
236
+
237
+
238
+ def _prepare_link_upload(filenames: List[str], display_name: str, message: str,
239
+ session: requests.Session) -> dict:
240
+ """Given a list of filenames and a message prepare for the link upload.
241
+
242
+ Return the parsed JSON response.
243
+ """
244
+ j = {
245
+ "files": [_file_name_and_size(f) for f in filenames],
246
+ "display_name": display_name,
247
+ "message": message,
248
+ "ui_language": "en",
249
+ }
250
+
251
+ r = session.post(WETRANSFER_UPLOAD_LINK_URL, json=j)
252
+ return r.json()
253
+
254
+
255
+ def _prepare_file_upload(transfer_id: str, file: str,
256
+ session: requests.Session) -> dict:
257
+ """Given a transfer_id and file prepare it for the upload.
258
+
259
+ Return the parsed JSON response.
260
+ """
261
+ j = _file_name_and_size(file)
262
+ r = session.post(WETRANSFER_FILES_URL.format(transfer_id=transfer_id),
263
+ json=j)
264
+ return r.json()
265
+
266
+
267
+ def _upload_chunks(transfer_id: str, file_id: str, file: str,
268
+ session: requests.Session,
269
+ default_chunk_size: int = WETRANSFER_DEFAULT_CHUNK_SIZE) -> str:
270
+ """Given a transfer_id, file_id and file upload it.
271
+
272
+ Return the parsed JSON response.
273
+ """
274
+ with open(file, 'rb') as f:
275
+ chunk_number = 0
276
+ while True:
277
+ chunk = f.read(default_chunk_size)
278
+ chunk_size = len(chunk)
279
+ if chunk_size == 0:
280
+ break
281
+ chunk_number += 1
282
+
283
+ j = {
284
+ "chunk_crc": zlib.crc32(chunk),
285
+ "chunk_number": chunk_number,
286
+ "chunk_size": chunk_size,
287
+ "retries": 0
288
+ }
289
+
290
+ r = session.post(
291
+ WETRANSFER_PART_PUT_URL.format(transfer_id=transfer_id,
292
+ file_id=file_id),
293
+ json=j)
294
+ url = r.json().get('url')
295
+ requests.options(url,
296
+ headers={
297
+ 'Origin': 'https://wetransfer.com',
298
+ 'Access-Control-Request-Method': 'PUT',
299
+ })
300
+ requests.put(url, data=chunk)
301
+
302
+ j = {
303
+ 'chunk_count': chunk_number
304
+ }
305
+ r = session.put(
306
+ WETRANSFER_FINALIZE_MPP_URL.format(transfer_id=transfer_id,
307
+ file_id=file_id),
308
+ json=j)
309
+
310
+ return r.json()
311
+
312
+
313
+ def _finalize_upload(transfer_id: str, session: requests.Session) -> dict:
314
+ """Given a transfer_id finalize the upload.
315
+
316
+ Return the parsed JSON response.
317
+ """
318
+ r = session.put(WETRANSFER_FINALIZE_URL.format(transfer_id=transfer_id))
319
+
320
+ return r.json()
321
+
322
+
323
+ def upload(files: List[str], display_name: str = '', message: str = '',
324
+ sender: Optional[str] = None,
325
+ recipients: Optional[List[str]] = []) -> str:
326
+ """Given a list of files upload them and return the corresponding URL.
327
+
328
+ Also accepts optional parameters:
329
+ - `display_name': name used as a title of the transfer
330
+ - `message': message used as a description of the transfer
331
+ - `sender': email address used to receive an ACK if the upload is
332
+ successfull. For every download by the recipients an email
333
+ will be also sent
334
+ - `recipients': list of email addresses of recipients. When the upload
335
+ succeed every recipients will receive an email with a link
336
+
337
+ If both sender and recipient parameters are passed the email upload will be
338
+ used. Otherwise, the link upload will be used.
339
+
340
+ Return the short URL of the transfer on success.
341
+ """
342
+
343
+ # Check that all files exists
344
+ logger.debug(f'Checking that all files exists')
345
+ for f in files:
346
+ if not os.path.exists(f):
347
+ raise FileNotFoundError(f)
348
+
349
+ # Check that there are no duplicates filenames
350
+ # (despite possible different dirname())
351
+ logger.debug(f'Checking for no duplicate filenames')
352
+ filenames = [os.path.basename(f) for f in files]
353
+ if len(files) != len(set(filenames)):
354
+ raise FileExistsError('Duplicate filenames')
355
+
356
+ logger.debug(f'Preparing to upload')
357
+ transfer_id = None
358
+ s = _prepare_session()
359
+ if not s:
360
+ raise ConnectionError('Could not prepare session')
361
+ if sender and recipients:
362
+ # email upload
363
+ transfer_id = \
364
+ _prepare_email_upload(files, display_name, message, sender, recipients, s)['id']
365
+ _verify_email_upload(transfer_id, s)
366
+ else:
367
+ # link upload
368
+ transfer_id = _prepare_link_upload(files, display_name, message, s)['id']
369
+
370
+ logger.debug(f'Get transfer id {transfer_id}')
371
+ for f in files:
372
+ logger.debug(f'Uploading {f} as part of transfer_id {transfer_id}')
373
+ file_id = _prepare_file_upload(transfer_id, f, s)['id']
374
+ _upload_chunks(transfer_id, file_id, f, s)
375
+
376
+ logger.debug(f'Finalizing upload with transfer id {transfer_id}')
377
+ shortened_url = _finalize_upload(transfer_id, s)['shortened_url']
378
+ _close_session(s)
379
+ return shortened_url
380
+
381
+
382
+ if __name__ == '__main__':
383
+ from sys import exit
384
+ import argparse
385
+
386
+ log = logging.getLogger(__name__)
387
+ log.setLevel(logging.INFO)
388
+ log.addHandler(logging.StreamHandler())
389
+
390
+ ap = argparse.ArgumentParser(
391
+ prog='transferwee',
392
+ description='Download/upload files via wetransfer.com'
393
+ )
394
+ sp = ap.add_subparsers(dest='action', help='action', required=True)
395
+
396
+ # download subcommand
397
+ dp = sp.add_parser('download', help='download files')
398
+ dp.add_argument('-g', action='store_true',
399
+ help='only print the direct link (without downloading it)')
400
+ dp.add_argument('-o', type=str, default='', metavar='file',
401
+ help='output file to be used')
402
+ dp.add_argument('-v', action='store_true',
403
+ help='get verbose/debug logging')
404
+ dp.add_argument('url', nargs='+', type=str, metavar='url',
405
+ help='URL (we.tl/... or wetransfer.com/downloads/...)')
406
+
407
+ # upload subcommand
408
+ up = sp.add_parser('upload', help='upload files')
409
+ up.add_argument('-n', type=str, default='', metavar='display_name',
410
+ help='title for the transfer')
411
+ up.add_argument('-m', type=str, default='', metavar='message',
412
+ help='message description for the transfer')
413
+ up.add_argument('-f', type=str, metavar='from', help='sender email')
414
+ up.add_argument('-t', nargs='+', type=str, metavar='to',
415
+ help='recipient emails')
416
+ up.add_argument('-v', action='store_true',
417
+ help='get verbose/debug logging')
418
+ up.add_argument('files', nargs='+', type=str, metavar='file',
419
+ help='files to upload')
420
+
421
+ args = ap.parse_args()
422
+
423
+ if args.v:
424
+ log.setLevel(logging.DEBUG)
425
+
426
+ if args.action == 'download':
427
+ if args.g:
428
+ for u in args.url:
429
+ print(download_url(u))
430
+ else:
431
+ for u in args.url:
432
+ download(u, args.o)
433
+ exit(0)
434
+
435
+ if args.action == 'upload':
436
+ print(upload(args.files, args.n, args.m, args.f, args.t))
437
+ exit(0)