6
0

FileOperations.py 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185
  1. import os
  2. from typing import Optional
  3. from typing import Tuple
  4. import cv2
  5. from PIL import Image
  6. from pycs.database.File import File
  7. DEFAULT_JPEG_QUALITY = 80
  8. def file_info(data_folder: str, file_name: str, file_ext: str):
  9. """
  10. Receive file type, frame count and frames per second.
  11. The last two are always None for images.
  12. :param data_folder: path to data folder
  13. :param file_name: file name
  14. :param file_ext: file extension
  15. :return: file type, frame count, frames per second
  16. """
  17. # determine file type
  18. if file_ext.lower() in ['.jpg', '.png']:
  19. file_type = 'image'
  20. elif file_ext.lower() in ['.mp4']:
  21. file_type = 'video'
  22. else:
  23. raise ValueError(f"Unsupported file extension: {file_ext}!")
  24. # determine frames and fps for video files
  25. if file_type == 'image':
  26. frames = None
  27. fps = None
  28. else:
  29. file_path = os.path.join(data_folder, file_name + file_ext)
  30. video = cv2.VideoCapture(file_path)
  31. frames = int(video.get(cv2.CAP_PROP_FRAME_COUNT))
  32. fps = video.get(cv2.CAP_PROP_FPS)
  33. video.release()
  34. # return values
  35. return file_type, frames, fps
  36. def crop_file(file: File, project_root: str, x: float, y: float, w: float, h: float) -> str:
  37. """
  38. gets a file for the given file_id, crops the according image to the
  39. bounding box and saves the crops in the temp folder of the project.
  40. :param file: file object
  41. :param project_root: project root folder path
  42. :param x: relative x-coordinate of the top left corner
  43. :param y: relative y-coordinate of the top left corner
  44. :param w: relative width of the bounding box
  45. :param h: relative height of the bounding box
  46. :return: directory and file name of the cropped patch
  47. """
  48. # TODO in browser cache
  49. # TODO not possible for video files
  50. image = Image.open(file.absolute_path)
  51. width, height = image.size
  52. crop_width = int(width * w)
  53. crop_height = int(height * h)
  54. x0 = int(width * x)
  55. y0 = int(height * y)
  56. x1 = x0 + crop_width
  57. y1 = y0 + crop_height
  58. target_path = os.path.join(
  59. os.getcwd(),
  60. project_root,
  61. 'temp',
  62. f'{file.uuid}_{x0}-{y0}_{x1}-{y1}.jpg',
  63. )
  64. if not os.path.exists(target_path):
  65. crop = image.crop((x0, y0, x0 + crop_width, y0 + crop_height))
  66. crop.save(target_path, quality=DEFAULT_JPEG_QUALITY)
  67. return os.path.split(target_path)
  68. def resize_file(file: File, project_root: str, max_width: int, max_height: int) -> Tuple[str, str]:
  69. """
  70. If file type equals video this function extracts a thumbnail first. It calls resize_image
  71. to resize and returns the resized files directory and name.
  72. :param file: file object
  73. :param project_root: project root folder path
  74. :param max_width: maximum image or thumbnail width
  75. :param max_height: maximum image or thumbnail height
  76. :return: resized file directory, resized file name
  77. """
  78. abs_file_path = file.absolute_path
  79. # extract video thumbnail
  80. if file.type == 'video':
  81. abs_target_path = os.path.join(os.getcwd(), project_root, 'temp', f'{file.uuid}.jpg')
  82. create_thumbnail(abs_file_path, abs_target_path)
  83. abs_file_path = abs_target_path
  84. # resize image file
  85. abs_target_path = os.path.join(os.getcwd(), project_root,
  86. 'temp', f'{file.uuid}_{max_width}_{max_height}.jpg')
  87. result = resize_image(abs_file_path, abs_target_path, max_width, max_height)
  88. # return path
  89. if result is not None:
  90. return os.path.split(abs_target_path)
  91. return os.path.split(abs_file_path)
  92. def resize_image(file_path: str, target_path: str,
  93. max_width: int, max_height: int) -> Optional[bool]:
  94. """
  95. resize an image so width < max_width and height < max_height
  96. :param file_path: path to source file
  97. :param target_path: path to target file
  98. :param max_width: maximum image width
  99. :param max_height: maximum image height
  100. :return:
  101. """
  102. # return if file exists
  103. if os.path.exists(target_path):
  104. return True
  105. # load full size image
  106. image = Image.open(file_path)
  107. img_width, img_height = image.size
  108. # abort if file is smaller than desired
  109. if img_width < max_width and img_height < max_height:
  110. return None
  111. # calculate target size
  112. target_width = int(max_width)
  113. target_height = int(max_width * img_height / img_width)
  114. if target_height > max_height:
  115. target_height = int(max_height)
  116. target_width = int(max_height * img_width / img_height)
  117. # resize image
  118. resized_image = image.resize((target_width, target_height))
  119. # save to file
  120. resized_image.save(target_path, quality=DEFAULT_JPEG_QUALITY)
  121. return True
  122. def create_thumbnail(file_path: str, target_path: str) -> None:
  123. """
  124. extract a thumbnail from a video
  125. :param file_path: path to source file
  126. :param target_path: path to target file
  127. :return:
  128. """
  129. # return if file exists
  130. if os.path.exists(target_path):
  131. return
  132. # load video
  133. video = cv2.VideoCapture(file_path)
  134. # create thumbnail
  135. _, image = video.read()
  136. cv2.imwrite(target_path, image)
  137. # close video file
  138. video.release()