MediaFileType.java

/*
 * #%L
 * wcm.io
 * %%
 * Copyright (C) 2019 wcm.io
 * %%
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * #L%
 */
package io.wcm.handler.media;

import java.util.EnumSet;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import org.apache.commons.lang3.StringUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.osgi.annotation.versioning.ProviderType;

import io.wcm.wcm.commons.contenttype.ContentType;
import io.wcm.wcm.commons.contenttype.FileExtension;

/**
 * File types supported by Media Handler.
 */
@ProviderType
public enum MediaFileType {

  /**
   * JPEG
   */
  JPEG(new String[] {
      ContentType.JPEG
  }, new String[] {
      FileExtension.JPEG, "jpeg"
  }, true),

  /**
   * PNG
   */
  PNG(new String[] {
      ContentType.PNG
  }, new String[] {
      FileExtension.PNG
  }, false),

  /**
   * GIF
   */
  GIF(new String[] {
      ContentType.GIF
  }, new String[] {
      FileExtension.GIF
  }, false),

  /**
   * TIFF
   */
  TIFF(new String[] {
      ContentType.TIFF
  }, new String[] {
      FileExtension.TIFF, "tiff"
  }, false),

  /**
   * SVG
   */
  SVG(new String[] {
      ContentType.SVG
  }, new String[] {
      FileExtension.SVG
  }, false),

  /**
   * MP4
   */
  MP4(new String[] {
      "video/mp4"
  }, new String[] {
      "mp4"
  }, false),

  /**
   * MPEG/MPG
   */
  MPEG(new String[] {
      "video/mpeg"
  }, new String[] {
      "mpeg", "mpg"
  }, false),

  /**
   * AVI
   */
  AVI(new String[] {
      "video/x-msvideo"
  }, new String[] {
      "avi"
  }, false),

  /**
   * M4V
   */
  M4V(new String[] {
      "video/x-m4v"
  }, new String[] {
      "m4v"
  }, false),

  /**
   * MKV
   */
  MKV(new String[] {
      "video/x-matroska"
  }, new String[] {
      "mkv"
  }, false),

  /**
   * WMV
   */
  WMV(new String[] {
      "video/x-ms-wmv"
  }, new String[] {
      "wmv"
  }, false),

  /**
   * WebM
   */
  WEBM(new String[] {
      "video/webm"
  }, new String[] {
      "webm"
  }, false),

  /**
   * QuickTime (MOV/QT)
   */
  MOV(new String[] {
      "video/quicktime"
  }, new String[] {
      "mov", "qt"
  }, false),

  /**
   * HLS adaptive streaming manifest
   */
  M3U8(new String[] {
      "application/vnd.apple.mpegurl"
  }, new String[] {
      "m3u8"
  }, false),

  /**
   * MPEG-DASH adaptive streaming manifest
   */
  MPD(new String[] {
      "application/dash+xml"
  }, new String[] {
      "mpd"
  }, false);


  private final Set<String> contentTypes;
  private final String extension;
  private final Set<String> extensions;
  private final boolean imageQualityPercentage;

  @SuppressWarnings("null")
  MediaFileType(@NotNull String @NotNull [] contentTypes,
      @NotNull String @NotNull [] extensions,
      boolean imageQualityPercentage) {
    this.contentTypes = Set.of(contentTypes);
    this.extension = extensions[0];
    this.extensions = Set.of(extensions);
    this.imageQualityPercentage = imageQualityPercentage;
  }

  /**
   * Get content types for this media file type.
   * @return Content types
   */
  public Set<String> getContentTypes() {
    return this.contentTypes;
  }

  /**
   * Get primary file extension for this media file type.
   * @return Primary file extension (without leading dot)
   */
  public @NotNull String getExtension() {
    return extension;
  }

  /**
   * Get file extensions for this media file type.
   * @return File extensions
   */
  public Set<String> getExtensions() {
    return extensions;
  }

  /**
   * Check if this image type supports quality percentage.
   * @return true if this image type has lossy compression and image quality is specified in percentage
   */
  public boolean isImageQualityPercentage() {
    return imageQualityPercentage;
  }

  /**
   * All file types that are supported by the Media Handler for rendering as image.
   */
  private static final EnumSet<MediaFileType> IMAGE_FILE_TYPES = EnumSet.of(
      GIF,
      JPEG,
      PNG,
      TIFF,
      SVG);

  /**
   * All file types that are supported by the browser for direct display.
   */
  private static final EnumSet<MediaFileType> BROWSER_IMAGE_FILE_TYPES = EnumSet.of(
      GIF,
      JPEG,
      PNG,
      SVG);

  /**
   * All file types that are vector formats and can be scaled by the browser.
   */
  private static final EnumSet<MediaFileType> VECTOR_IMAGE_FILE_TYPES = EnumSet.of(
      SVG);

  /**
   * All file types that are supported by the Media Handler for video delivery.
   */
  private static final EnumSet<MediaFileType> VIDEO_FILE_TYPES = EnumSet.of(
      MP4,
      MPEG,
      AVI,
      M4V,
      MKV,
      WMV,
      WEBM,
      MOV);

  /**
   * Check if the given file extension is supported by the Media Handler for rendering as image.
   * @param fileExtension File extension
   * @return true if image
   */
  public static boolean isImage(@Nullable String fileExtension) {
    return isExtension(IMAGE_FILE_TYPES, fileExtension);
  }

  /**
   * Get image file extensions supported by the Media Handler.
   * @return Image file extensions supported by the Media Handler for rendering as image.
   */
  public static @NotNull Set<String> getImageFileExtensions() {
    return getFileExtensions(IMAGE_FILE_TYPES);
  }

  /**
   * Get image content types supported by the Media Handler.
   * @return Image content types supported by the Media Handler for rendering as image.
   */
  public static @NotNull Set<String> getImageContentTypes() {
    return getContentTypes(IMAGE_FILE_TYPES);
  }

  /**
   * Check if the given file extension is supported for direct display in a browser.
   * @param fileExtension File extension
   * @return true if image is supported in browsers
   */
  public static boolean isBrowserImage(@Nullable String fileExtension) {
    return isExtension(BROWSER_IMAGE_FILE_TYPES, fileExtension);
  }

  /**
   * Get browser-supported image file extensions.
   * @return Image file extensions supported for direct display in a browser.
   */
  public static @NotNull Set<String> getBrowserImageFileExtensions() {
    return getFileExtensions(BROWSER_IMAGE_FILE_TYPES);
  }

  /**
   * Get browser-supported image content types.
   * @return Image content types supported for direct display in a browser.
   */
  public static @NotNull Set<String> getBrowserImageContentTypes() {
    return getContentTypes(BROWSER_IMAGE_FILE_TYPES);
  }

  /**
   * Check if the given file extension is a vector image file extension.
   * @param fileExtension File extension
   * @return true if image is a vector image.
   */
  public static boolean isVectorImage(@Nullable String fileExtension) {
    return isExtension(VECTOR_IMAGE_FILE_TYPES, fileExtension);
  }

  /**
   * Get vector image file extensions.
   * @return Image file extensions that are vector images.
   */
  public static @NotNull Set<String> getVectorImageFileExtensions() {
    return getFileExtensions(VECTOR_IMAGE_FILE_TYPES);
  }

  /**
   * Get vector image content types.
   * @return Image content types that are vector images.
   */
  public static @NotNull Set<String> getVectorImageContentTypes() {
    return getContentTypes(VECTOR_IMAGE_FILE_TYPES);
  }

  /**
   * Check if the given file extension is supported by the Media Handler for video delivery.
   * @param fileExtension File extension
   * @return true if video
   */
  public static boolean isVideo(@Nullable String fileExtension) {
    return isExtension(VIDEO_FILE_TYPES, fileExtension);
  }

  /**
   * Get video file extensions supported by the Media Handler.
   * @return Video file extensions supported by the Media Handler for video delivery.
   */
  public static @NotNull Set<String> getVideoFileExtensions() {
    return getFileExtensions(VIDEO_FILE_TYPES);
  }

  /**
   * Get video content types supported by the Media Handler.
   * @return Video content types supported by the Media Handler for video delivery.
   */
  public static @NotNull Set<String> getVideoContentTypes() {
    return getContentTypes(VIDEO_FILE_TYPES);
  }

  private static Set<String> getContentTypes(@NotNull EnumSet<MediaFileType> fileTypes) {
    return fileTypes.stream()
      .flatMap(type -> type.getContentTypes().stream())
      .collect(Collectors.toSet());
  }

  private static boolean isExtension(@NotNull EnumSet<MediaFileType> fileTypes, @Nullable String fileExtension) {
    if (StringUtils.isEmpty(fileExtension)) {
      return false;
    }
    return fileTypes.stream()
      .anyMatch(type -> type.getExtensions().contains(StringUtils.lowerCase(fileExtension)));
  }

  private static Set<String> getFileExtensions(@NotNull EnumSet<MediaFileType> fileTypes) {
    return fileTypes.stream()
      .flatMap(type -> type.getExtensions().stream())
      .collect(Collectors.toSet());
  }

  /**
   * Get Media file type by content type.
   * @param contentType Content type
   * @return Media file type or null if not found
   */
  @SuppressWarnings("null")
  public static @Nullable MediaFileType getByContentType(@Nullable String contentType) {
    if (contentType == null) {
      return null;
    }
    String contentTypeLowerCase = StringUtils.toRootLowerCase(contentType);
    return Stream.of(values())
      .filter(type -> type.getContentTypes().contains(contentTypeLowerCase))
      .findFirst()
      .orElse(null);
  }

  /**
   * Get Media file type by file extension.
   * @param extension File extension
   * @return Media file type or null if not found
   */
  @SuppressWarnings("null")
  public static @Nullable MediaFileType getByFileExtensions(@Nullable String extension) {
    if (extension == null) {
      return null;
    }
    String extensionLowerCase = StringUtils.toRootLowerCase(extension);
    return Stream.of(values())
      .filter(type -> type.getExtensions().contains(extensionLowerCase))
      .findFirst()
      .orElse(null);
  }

}