media.py 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185
  1. __filename__ = "media.py"
  2. __author__ = "Bob Mottram"
  3. __license__ = "AGPL3+"
  4. __version__ = "1.0.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 removeMetaData(imageFilename: str,outputFilename: str) -> None:
  21. imageFile = open(imageFilename)
  22. image = Image.open(imageFilename)
  23. if not image:
  24. return
  25. data = list(image.getdata())
  26. if not data:
  27. return
  28. imageWithoutExif = Image.new(image.mode, image.size)
  29. imageWithoutExif.putdata(data)
  30. imageWithoutExif.save(outputFilename)
  31. def getImageHash(imageFilename: str) -> str:
  32. return blurencode(numpy.array(Image.open(imageFilename).convert("RGB")))
  33. def isMedia(imageFilename: str) -> bool:
  34. permittedMedia=['png','jpg','gif','webp','mp4','ogv','mp3','ogg']
  35. for m in permittedMedia:
  36. if imageFilename.endswith('.'+m):
  37. return True
  38. print('WARN: '+imageFilename+' is not a permitted media type')
  39. return False
  40. def createMediaDirs(baseDir: str,mediaPath: str) -> None:
  41. if not os.path.isdir(baseDir+'/media'):
  42. os.mkdir(baseDir+'/media')
  43. if not os.path.isdir(baseDir+'/'+mediaPath):
  44. os.mkdir(baseDir+'/'+mediaPath)
  45. def getMediaPath() -> str:
  46. currTime=datetime.datetime.utcnow()
  47. weeksSinceEpoch=int((currTime - datetime.datetime(1970,1,1)).days/7)
  48. return 'media/'+str(weeksSinceEpoch)
  49. def getAttachmentMediaType(filename: str) -> str:
  50. """Returns the type of media for the given file
  51. image, video or audio
  52. """
  53. mediaType=None
  54. imageTypes=['png','jpg','jpeg','gif','webp']
  55. for mType in imageTypes:
  56. if filename.endswith('.'+mType):
  57. return 'image'
  58. videoTypes=['mp4','webm','ogv']
  59. for mType in videoTypes:
  60. if filename.endswith('.'+mType):
  61. return 'video'
  62. audioTypes=['mp3','ogg']
  63. for mType in audioTypes:
  64. if filename.endswith('.'+mType):
  65. return 'audio'
  66. return mediaType
  67. def updateEtag(mediaFilename: str) -> None:
  68. """ calculate the etag, which is a sha1 of the data
  69. """
  70. # only create etags for media
  71. if '/media/' not in mediaFilename:
  72. return
  73. # check that the media exists
  74. if not os.path.isfile(mediaFilename):
  75. return
  76. # read the binary data
  77. data=None
  78. try:
  79. with open(mediaFilename, 'rb') as mediaFile:
  80. data=mediaFile.read()
  81. except:
  82. pass
  83. if not data:
  84. return
  85. # calculate hash
  86. etag=sha1(data).hexdigest()
  87. # save the hash
  88. try:
  89. with open(mediaFilename+'.etag', 'w') as etagFile:
  90. etagFile.write(etag)
  91. except:
  92. pass
  93. def attachMedia(baseDir: str,httpPrefix: str,domain: str,port: int, \
  94. postJson: {},imageFilename: str, \
  95. mediaType: str,description: str, \
  96. useBlurhash: bool) -> {}:
  97. """Attaches media to a json object post
  98. The description can be None
  99. Blurhash is optional, since low power systems may take a long time to calculate it
  100. """
  101. if not isMedia(imageFilename):
  102. return postJson
  103. fileExtension=None
  104. acceptedTypes=['png','jpg','gif','webp','mp4','webm','ogv','mp3','ogg']
  105. for mType in acceptedTypes:
  106. if imageFilename.endswith('.'+mType):
  107. if mType=='jpg':
  108. mType='jpeg'
  109. if mType=='mp3':
  110. mType='mpeg'
  111. fileExtension=mType
  112. if not fileExtension:
  113. return postJson
  114. mediaType=mediaType+'/'+fileExtension
  115. print('Attached media type: '+mediaType)
  116. if fileExtension=='jpeg':
  117. fileExtension='jpg'
  118. if mediaType=='audio/mpeg':
  119. fileExtension='mp3'
  120. if port:
  121. if port!=80 and port!=443:
  122. if ':' not in domain:
  123. domain=domain+':'+str(port)
  124. mPath=getMediaPath()
  125. mediaPath=mPath+'/'+createPassword(32)+'.'+fileExtension
  126. if baseDir:
  127. createMediaDirs(baseDir,mPath)
  128. mediaFilename=baseDir+'/'+mediaPath
  129. attachmentJson={
  130. 'mediaType': mediaType,
  131. 'name': description,
  132. 'type': 'Document',
  133. 'url': httpPrefix+'://'+domain+'/'+mediaPath
  134. }
  135. if useBlurhash and mediaType.startswith('image/'):
  136. attachmentJson['blurhash']=getImageHash(imageFilename)
  137. postJson['attachment']=[attachmentJson]
  138. if baseDir:
  139. if mediaType=='image':
  140. removeMetaData(imageFilename,mediaFilename)
  141. else:
  142. copyfile(imageFilename,mediaFilename)
  143. updateEtag(mediaFilename)
  144. return postJson
  145. def archiveMedia(baseDir: str,archiveDirectory: str,maxWeeks=4) -> None:
  146. """Any media older than the given number of weeks gets archived
  147. """
  148. currTime=datetime.datetime.utcnow()
  149. weeksSinceEpoch=int((currTime - datetime.datetime(1970,1,1)).days/7)
  150. minWeek=weeksSinceEpoch-maxWeeks
  151. if archiveDirectory:
  152. if not os.path.isdir(archiveDirectory):
  153. os.mkdir(archiveDirectory)
  154. if not os.path.isdir(archiveDirectory+'/media'):
  155. os.mkdir(archiveDirectory+'/media')
  156. for subdir, dirs, files in os.walk(baseDir+'/media'):
  157. for weekDir in dirs:
  158. if int(weekDir)<minWeek:
  159. if archiveDirectory:
  160. move(os.path.join(baseDir+'/media', weekDir),archiveDirectory+'/media')
  161. else:
  162. # archive to /dev/null
  163. rmtree(os.path.join(baseDir+'/media', weekDir))