text: import os
  import hashlib
  from typing import Optional, List
  
  from .www_tools import is_url, get_filename_from_url, download_from_url
  
  
  class ImageDownloader:
      """
      "Smart" images downloader.
      """
  
      def __init__(self, article_path: str, skip_list: Optional[List[str]] = None, skip_all_errors: bool = False,
                   img_dir_name: str = 'images', img_public_path: str = '', downloading_timeout: float = -1,
                   deduplication: bool = False):
          self._img_dir_name = img_dir_name
          self._img_public_path = img_public_path
          self._article_file_path = article_path
          self._skip_list = set(skip_list) if skip_list is not None else []
          self._images_dir = os.path.join(os.path.dirname(self._article_file_path), self._img_dir_name)
          self._skip_all_errors = skip_all_errors
          self._downloading_timeout = downloading_timeout if downloading_timeout > 0 else None
          self._deduplication = deduplication
  
      def download_images(self, images: List[str]) -> dict:
          """
          Download and save images from the list.
  
          :return URL -> file path mapping.
          """
  
          replacement_mapping = {}
          hash_to_path_mapping = {}
          skip_list = self._skip_list
          img_count = len(images)
          path_join = os.path.join
          img_dir_name = self._img_dir_name
          img_public_path = self._img_public_path
          images_dir = self._images_dir
          deduplication = self._deduplication
  
          try:
              os.makedirs(self._images_dir)
          except FileExistsError:
              # Existing directory is not error.
              pass
  
          for img_num, img_url in enumerate(images):
              assert img_url not in replacement_mapping.keys(), f'BUG: already downloaded image "{img_url}"...'
  
              if img_url in skip_list:
                  print(f'Image {img_num + 1} ["{img_url}"] was skipped, because it\'s in the skip list...')
                  continue
  
              if not is_url(img_url):
                  print(f'Image {img_num + 1} ["{img_url}"] was skipped, because it has incorrect URL...')
                  continue
  
              print(f'Downloading image {img_num + 1} of {img_count} from "{img_url}"...')
  
              try:
                  img_response = download_from_url(img_url, self._downloading_timeout)
              except Exception as e:
                  if self._skip_all_errors:
                      print(f'Warning: can\'t download image {img_num + 1}, error: [{str(e)}], '
                            'but processing will be continued, because `skip_all_errors` flag is set')
                      continue
                  raise
  
              img_filename = get_filename_from_url(img_response)
              image_content = img_response.content
  
              if deduplication:
                  new_content_hash = hashlib.sha256(image_content).digest()
                  existed_file_name = hash_to_path_mapping.get(new_content_hash)
                  if existed_file_name is not None:
                      img_filename = existed_file_name
                      document_img_path = path_join(img_public_path or img_dir_name, img_filename)
                      replacement_mapping.setdefault(img_url, document_img_path)
                      continue
                  else:
                      hash_to_path_mapping[new_content_hash] = img_filename
  
              document_img_path = path_join(img_public_path or img_dir_name, img_filename)
              img_filename, document_img_path = self._correct_paths(replacement_mapping, document_img_path, img_url,
                                                                    img_filename)
  
              real_img_path = path_join(images_dir, img_filename)
              replacement_mapping.setdefault(img_url, document_img_path)
  
              ImageDownloader._write_image(real_img_path, image_content)
  
          return replacement_mapping
  
      @staticmethod
      def _write_image(img_path: str, data: bytes):
          """
          Write image data into the file.
          """
  
          print(f'Image will be written to the file "{img_path}"...')
          with open(img_path, 'wb') as img_file:
              img_file.write(data)
              img_file.close()
  
      def _correct_paths(self, replacement_mapping, document_img_path, img_url, img_filename):
          """
          Fix path if a file with the similar name exists already.
          """
  
          # Images can have similar name, but different URLs, but I want to save original filename, if possible.
          for url, path in replacement_mapping.items():
              if document_img_path == path and img_url != url:
                  img_filename = f'{hashlib.md5(img_url.encode()).hexdigest()}_{img_filename}'
                  document_img_path = os.path.join(self._img_public_path or self._img_dir_name, img_filename)
                  break
  
          return img_filename, document_img_path
  