15
|
1 import asyncio
|
|
2 import hashlib
|
|
3 import re
|
20
|
4 import os
|
15
|
5 from dataclasses import dataclass
|
|
6 from pathlib import Path
|
|
7 from typing import Iterable, Iterator, NewType
|
|
8
|
20
|
9 IGNORE = {'_thumb'}
|
|
10
|
15
|
11
|
|
12 @dataclass
|
|
13 class VideoFile:
|
|
14 diskPath: Path
|
|
15 webRelPath: str
|
|
16 label: str
|
|
17 # perms, playlists, req by/when
|
|
18
|
|
19
|
|
20 def vf(p: Path, label: str):
|
|
21 return VideoFile(p, './files/' + str(p.relative_to('/data')), label)
|
|
22
|
|
23
|
|
24 def thumbWebPath(rel: str) -> str:
|
|
25 return './files/' + rel
|
|
26
|
|
27
|
|
28 @dataclass
|
|
29 class VideoFileStore:
|
|
30 top: Path
|
|
31
|
|
32 def findInDir(self, subdir: str) -> Iterable[VideoFile]:
|
|
33 if subdir[0] != '/': raise ValueError
|
|
34 here = self.top / subdir[1:]
|
|
35 manifests = list(here.glob('*.mpd'))
|
|
36 if manifests:
|
|
37 p = manifests[0]
|
|
38 label = p.parent.name
|
|
39 yield vf(p, label)
|
|
40 return
|
|
41 for p in sorted(list(here.glob('*.mp4')) + list(here.glob('*.webm'))):
|
|
42 label = re.sub(r' \[[^\]]+\]\.\w+', '', p.name)
|
|
43 yield vf(p, label)
|
|
44
|
|
45 def findSubdirs(self, subdir: str) -> Iterable:
|
|
46 if subdir[0] != '/': raise ValueError
|
|
47 here = self.top / subdir[1:]
|
|
48 for p in here.iterdir():
|
20
|
49 if p.is_dir() and p.name not in IGNORE:
|
15
|
50 yield {
|
|
51 'label': p.name,
|
|
52 'path': '/' + str(p.relative_to(self.top))
|
|
53 }
|
|
54
|
|
55 def thumbPath(self, vf: VideoFile) -> str:
|
|
56 sha256 = hashlib.sha256()
|
|
57 with open(vf.diskPath, 'rb') as f:
|
|
58 firstMb = f.read(1 << 20)
|
|
59 sha256.update(firstMb)
|
|
60 cksum = sha256.hexdigest()
|
|
61 return f'_thumb/{cksum}.jpg'
|
|
62
|
|
63 async def getOrCreateThumb(self, vf: VideoFile) -> str:
|
|
64 p = self.top / self.thumbPath(vf)
|
|
65 if not p.exists():
|
|
66 sp = asyncio.create_subprocess_exec('ffmpegthumbnailer',
|
|
67 '-s', '250', '-i',
|
|
68 str(vf.diskPath), '-o', str(p))
|
|
69 await sp
|
|
70 return thumbWebPath(str(p.relative_to(self.top)))
|
|
71
|
|
72 async def save(self, name: str, chunks: Iterator[bytes]):
|
|
73 p = self.top / name
|
|
74 if p.exists():
|
|
75 raise ValueError(f'{p} exists')
|
|
76 data = b''
|
|
77 for c in chunks:
|
|
78 data += c
|
|
79 p.write_bytes(data)
|
20
|
80
|
|
81 def folderTree(self):
|
|
82 out = {'name': 'TOP'}
|
|
83
|
|
84 def fill(node: dict, pathToHere: Path):
|
|
85 for subName in sorted(os.listdir(pathToHere)):
|
|
86 if subName in IGNORE:
|
|
87 continue
|
|
88 subDir = pathToHere / subName
|
|
89 if subDir.is_dir():
|
|
90 subNode = {'name': subName}
|
|
91 node.setdefault('children', []).append(subNode)
|
|
92 fill(subNode, subDir)
|
|
93
|
|
94 fill(out, self.top)
|
|
95 return out |