vobjects.py 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229
  1. import html
  2. import venigma
  3. import os
  4. import base64
  5. import re
  6. import hashlib
  7. class AudioListItem:
  8. def __init__(self, json_item):
  9. self._jitem = json_item
  10. def id(self):
  11. return self._jitem["audio_raw_id"]
  12. def key(self):
  13. return self._jitem["access_key"]
  14. def cid(self):
  15. return f"{self.id()}_{self.key()}"
  16. def _code(self):
  17. return self._jitem["track_code"]
  18. def audio_list(plain_list):
  19. return [AudioListItem(item) for item in plain_list["payload"][1][0]]
  20. class AudioDataItem:
  21. def __init__(self, json_data):
  22. self._jdata = json_data
  23. self._manifest = None
  24. def cid(self):
  25. return f"{self._jdata[1]}_{self._jdata[0]}"
  26. def index(self):
  27. return base64.b16encode(self.hindex().encode()).decode()
  28. def cursed_url(self):
  29. return self._jdata[2]
  30. def blessed_url(self, config):
  31. return venigma.decode(config, self.cursed_url())
  32. def root_url(self, config):
  33. blessed_url = self.blessed_url(config)
  34. parts = blessed_url.split("/")
  35. return "/".join(parts[:-1]) + "/"
  36. def title(self):
  37. return html.unescape(self._jdata[3])
  38. def artist(self):
  39. return html.unescape(self._jdata[4])
  40. def img(self):
  41. return self._jdata[14]
  42. def hindex(self):
  43. return f"{self.title()} - {self.artist()}"
  44. def findex(self, config):
  45. return f"{config.audio_directory()}{self.cid()}.mp3"
  46. def _hash(self, manifest_hash, mp3):
  47. hash = hashlib.sha256(manifest_hash)
  48. hash.update(mp3)
  49. return hash.hexdigest()
  50. def _hashfile(self, config):
  51. return f"{self.manifest().directory(config)}sha256sum"
  52. def hash(self, config):
  53. f = open(self._hashfile(config), "w+")
  54. fmp3 = open(self.findex(config), "rb")
  55. manifest_hash = self.manifest().hashed(config)
  56. h = self._hash(manifest_hash.encode(), fmp3.read())
  57. fmp3.close()
  58. f.write(h)
  59. f.close()
  60. return h
  61. def hashed(self, config):
  62. hashfile = self._hashfile(config)
  63. findex = self.findex(config)
  64. manifest_hash = self.manifest().hashed(config)
  65. if (
  66. not os.path.isfile(hashfile)
  67. or not os.path.isfile(findex)
  68. or not manifest_hash
  69. ):
  70. return None
  71. fmp3 = open(findex, "rb")
  72. h = self._hash(manifest_hash.encode(), fmp3.read())
  73. fmp3.close()
  74. f = open(hashfile, "r")
  75. fh = f.read()
  76. f.close()
  77. return h if h == fh else None
  78. def manifest(self, manifest=None):
  79. if manifest is None:
  80. return self._manifest
  81. else:
  82. self._manifest = manifest
  83. if manifest.data() is None:
  84. manifest.data(self)
  85. def audio_data(plain_data):
  86. return [AudioDataItem(data) for data in plain_data["payload"][1][0]]
  87. class Manifest:
  88. def __init__(self, raw_content):
  89. self._rcontent = raw_content
  90. self._data = None
  91. def load(config, data):
  92. index = Manifest._index_for(config, data)
  93. if os.path.isfile(index):
  94. f = open(index, "rb")
  95. manifest = Manifest(f.read())
  96. f.close()
  97. data.manifest(manifest)
  98. return manifest
  99. else:
  100. return None
  101. def content(self):
  102. return self._rcontent
  103. def _directory_for(config, data):
  104. directory = config.manifests_directory()
  105. return f"{directory}{data.index()}/"
  106. def directory(self, config):
  107. return Manifest._directory_for(config, self.data())
  108. def _index_for(config, data):
  109. return f"{Manifest._directory_for(config, data)}index.m3u8"
  110. def index(self, config):
  111. return Manifest._index_for(config, self.data())
  112. def store(self, config):
  113. directory = self.directory(config)
  114. if not os.path.exists(directory):
  115. os.makedirs(directory)
  116. f = open(self.index(config), "wb+")
  117. f.write(self.content())
  118. f.close()
  119. def chunks(self):
  120. regex = r"seg\-\d+\-\w+\.ts\?siren=\d"
  121. return re.findall(regex, self.content().decode("utf-8"))
  122. def _hash(self, chunks):
  123. hash = hashlib.sha1(self.content())
  124. for chunk in chunks:
  125. if not chunk:
  126. continue
  127. hash.update(chunk.data())
  128. return hash.hexdigest()
  129. def _hashfile(self, config):
  130. return f"{self.directory(config)}sha1sum"
  131. def hash(self, config, chunks):
  132. f = open(self._hashfile(config), "w+")
  133. h = self._hash(chunks)
  134. f.write(h)
  135. f.close()
  136. return h
  137. def hashed(self, config):
  138. hashfile = self._hashfile(config)
  139. chunks = [Chunk.load(config, self, chunk) for chunk in self.chunks()]
  140. if not os.path.isfile(hashfile) or any(chunks) is None:
  141. return None
  142. h = self._hash(chunks)
  143. f = open(hashfile, "r")
  144. fh = f.read()
  145. f.close()
  146. return h if h == fh else None
  147. def data(self, data=None):
  148. if data is None:
  149. return self._data
  150. else:
  151. self._data = data
  152. if data.manifest() is None:
  153. data.manifest(self)
  154. class Chunk:
  155. def __init__(self, manifest, name, raw_data):
  156. self._name = name
  157. self._rdata = raw_data
  158. self._manifest = manifest
  159. def _index_for(config, manifest, name):
  160. return f"{manifest.directory(config)}{name}"
  161. def load(config, manifest, name):
  162. index = Chunk._index_for(config, manifest, name)
  163. if os.path.isfile(index):
  164. f = open(index, "rb")
  165. chunk = Chunk(manifest, name, f.read())
  166. f.close()
  167. return chunk
  168. else:
  169. return None
  170. def name(self):
  171. return self._name
  172. def data(self):
  173. return self._rdata
  174. def manifest(self):
  175. return self._manifest
  176. def store(self, config):
  177. f = open(f"{Chunk._index_for(config, self.manifest(), self.name())}", "wb+")
  178. f.write(self.data())
  179. f.close()