media.py 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198
  1. __filename__ = "media.py"
  2. __author__ = "Bob Mottram"
  3. __license__ = "AGPL3+"
  4. __version__ = "1.1.0"
  5. __maintainer__ = "Bob Mottram"
  6. __email__ = "bob@freedombone.net"
  7. __status__ = "Production"
  8. from blurhash import blurhash_encode as blurencode
  9. from PIL import Image
  10. import numpy
  11. import os
  12. import sys
  13. import json
  14. import datetime
  15. from hashlib import sha1
  16. from auth import createPassword
  17. from shutil import copyfile
  18. from shutil import rmtree
  19. from shutil import move
  20. def replaceYouTube(postJsonObject: {}) -> None:
  21. """Replace YouTube with invidio.us
  22. This denies Google some, but not all, tracking data
  23. """
  24. if not isinstance(postJsonObject['object'], dict):
  25. return
  26. if not postJsonObject['object'].get('content'):
  27. return
  28. if 'www.youtube.com' not in postJsonObject['object']['content']:
  29. return
  30. postJsonObject['object']['content']= \
  31. postJsonObject['object']['content'].replace('www.youtube.com','invidio.us')
  32. def removeMetaData(imageFilename: str,outputFilename: str) -> None:
  33. """Attempts to do this with pure python didn't work well,
  34. so better to use a dedicated tool if one is installed
  35. """
  36. copyfile(imageFilename,outputFilename)
  37. if os.path.isfile('/usr/bin/exiftool'):
  38. print('Removing metadata from '+outputFilename+' using exiftool')
  39. os.system('exiftool -all= '+outputFilename)
  40. elif os.path.isfile('/usr/bin/mogrify'):
  41. print('Removing metadata from '+outputFilename+' using mogrify')
  42. os.system('/usr/bin/mogrify -strip '+outputFilename)
  43. def getImageHash(imageFilename: str) -> str:
  44. return blurencode(numpy.array(Image.open(imageFilename).convert("RGB")))
  45. def isMedia(imageFilename: str) -> bool:
  46. permittedMedia=['png','jpg','gif','webp','mp4','ogv','mp3','ogg']
  47. for m in permittedMedia:
  48. if imageFilename.endswith('.'+m):
  49. return True
  50. print('WARN: '+imageFilename+' is not a permitted media type')
  51. return False
  52. def createMediaDirs(baseDir: str,mediaPath: str) -> None:
  53. if not os.path.isdir(baseDir+'/media'):
  54. os.mkdir(baseDir+'/media')
  55. if not os.path.isdir(baseDir+'/'+mediaPath):
  56. os.mkdir(baseDir+'/'+mediaPath)
  57. def getMediaPath() -> str:
  58. currTime=datetime.datetime.utcnow()
  59. weeksSinceEpoch=int((currTime - datetime.datetime(1970,1,1)).days/7)
  60. return 'media/'+str(weeksSinceEpoch)
  61. def getAttachmentMediaType(filename: str) -> str:
  62. """Returns the type of media for the given file
  63. image, video or audio
  64. """
  65. mediaType=None
  66. imageTypes=['png','jpg','jpeg','gif','webp']
  67. for mType in imageTypes:
  68. if filename.endswith('.'+mType):
  69. return 'image'
  70. videoTypes=['mp4','webm','ogv']
  71. for mType in videoTypes:
  72. if filename.endswith('.'+mType):
  73. return 'video'
  74. audioTypes=['mp3','ogg']
  75. for mType in audioTypes:
  76. if filename.endswith('.'+mType):
  77. return 'audio'
  78. return mediaType
  79. def updateEtag(mediaFilename: str) -> None:
  80. """ calculate the etag, which is a sha1 of the data
  81. """
  82. # only create etags for media
  83. if '/media/' not in mediaFilename:
  84. return
  85. # check that the media exists
  86. if not os.path.isfile(mediaFilename):
  87. return
  88. # read the binary data
  89. data=None
  90. try:
  91. with open(mediaFilename, 'rb') as mediaFile:
  92. data=mediaFile.read()
  93. except:
  94. pass
  95. if not data:
  96. return
  97. # calculate hash
  98. etag=sha1(data).hexdigest()
  99. # save the hash
  100. try:
  101. with open(mediaFilename+'.etag', 'w') as etagFile:
  102. etagFile.write(etag)
  103. except:
  104. pass
  105. def attachMedia(baseDir: str,httpPrefix: str,domain: str,port: int, \
  106. postJson: {},imageFilename: str, \
  107. mediaType: str,description: str, \
  108. useBlurhash: bool) -> {}:
  109. """Attaches media to a json object post
  110. The description can be None
  111. Blurhash is optional, since low power systems may take a long time to calculate it
  112. """
  113. if not isMedia(imageFilename):
  114. return postJson
  115. fileExtension=None
  116. acceptedTypes=['png','jpg','gif','webp','mp4','webm','ogv','mp3','ogg']
  117. for mType in acceptedTypes:
  118. if imageFilename.endswith('.'+mType):
  119. if mType=='jpg':
  120. mType='jpeg'
  121. if mType=='mp3':
  122. mType='mpeg'
  123. fileExtension=mType
  124. if not fileExtension:
  125. return postJson
  126. mediaType=mediaType+'/'+fileExtension
  127. print('Attached media type: '+mediaType)
  128. if fileExtension=='jpeg':
  129. fileExtension='jpg'
  130. if mediaType=='audio/mpeg':
  131. fileExtension='mp3'
  132. if port:
  133. if port!=80 and port!=443:
  134. if ':' not in domain:
  135. domain=domain+':'+str(port)
  136. mPath=getMediaPath()
  137. mediaPath=mPath+'/'+createPassword(32)+'.'+fileExtension
  138. if baseDir:
  139. createMediaDirs(baseDir,mPath)
  140. mediaFilename=baseDir+'/'+mediaPath
  141. attachmentJson={
  142. 'mediaType': mediaType,
  143. 'name': description,
  144. 'type': 'Document',
  145. 'url': httpPrefix+'://'+domain+'/'+mediaPath
  146. }
  147. if useBlurhash and mediaType.startswith('image/'):
  148. attachmentJson['blurhash']=getImageHash(imageFilename)
  149. postJson['attachment']=[attachmentJson]
  150. if baseDir:
  151. if mediaType=='image':
  152. removeMetaData(imageFilename,mediaFilename)
  153. else:
  154. copyfile(imageFilename,mediaFilename)
  155. updateEtag(mediaFilename)
  156. return postJson
  157. def archiveMedia(baseDir: str,archiveDirectory: str,maxWeeks=4) -> None:
  158. """Any media older than the given number of weeks gets archived
  159. """
  160. currTime=datetime.datetime.utcnow()
  161. weeksSinceEpoch=int((currTime - datetime.datetime(1970,1,1)).days/7)
  162. minWeek=weeksSinceEpoch-maxWeeks
  163. if archiveDirectory:
  164. if not os.path.isdir(archiveDirectory):
  165. os.mkdir(archiveDirectory)
  166. if not os.path.isdir(archiveDirectory+'/media'):
  167. os.mkdir(archiveDirectory+'/media')
  168. for subdir, dirs, files in os.walk(baseDir+'/media'):
  169. for weekDir in dirs:
  170. if int(weekDir)<minWeek:
  171. if archiveDirectory:
  172. move(os.path.join(baseDir+'/media', weekDir),archiveDirectory+'/media')
  173. else:
  174. # archive to /dev/null
  175. rmtree(os.path.join(baseDir+'/media', weekDir))