wkdhkr/dedupper

View on GitHub
src/services/judgment/ContentsLogic.js

Summary

Maintainability
A
30 mins
Test Coverage
// @flow
import typeof { Logger } from "log4js";

import AttributeService from "../fs/AttributeService";
import ImageMagickService from "../fs/contents/ImageMagickService";
import ActionLogicService from "./ActionLogic";
import { TYPE_DELETE, TYPE_SAVE } from "../../types/ActionTypes";
import {
  TYPE_DAMAGED,
  TYPE_LOW_FILE_SIZE,
  TYPE_LOW_RESOLUTION,
  TYPE_LOW_LONG_SIDE,
  TYPE_P_HASH_REJECT_LOW_QUALITY,
  TYPE_P_HASH_REJECT_DIFFERENT_MEAN,
  TYPE_P_HASH_REJECT_LOW_ENTROPY
} from "../../types/ReasonTypes";

import type { ReasonType } from "../../types/ReasonTypes";
import type { JudgeResultSimple } from "../../types/JudgeResult";
import type { Config, FileInfo, HashRow } from "../../types";

export default class ContentsLogic {
  log: Logger;

  config: Config;

  as: AttributeService;

  al: ActionLogicService;

  is: ImageMagickService;

  constructor(config: Config) {
    this.log = config.getLogger(this);
    this.config = config;
    this.as = new AttributeService(config);
    this.al = new ActionLogicService();
    this.is = new ImageMagickService();
  }

  isLowFileSize: FileInfo => boolean = ({ size, type }: FileInfo): boolean => {
    const minSize = this.config.minFileSizeByType[type];
    if (!minSize) {
      return false;
    }
    return size < minSize;
  };

  isLowResolution({ width, height, type }: FileInfo): boolean {
    const minRes = this.config.minResolutionByType[type];
    if (!minRes) {
      return false;
    }
    if (width * height < minRes) {
      return true;
    }
    return false;
  }

  isLowLongSide({ width, height, type }: FileInfo): boolean {
    const minLongSide = this.config.minLongSideByType[type];
    if (!minLongSide) {
      return false;
    }
    const longSide = width > height ? width : height;
    if (longSide < minLongSide) {
      return true;
    }
    return false;
  }

  async collectStatisticFactor(
    fileInfo: FileInfo,
    storedFileInfo: HashRow
  ): Promise<{
    isSmallEntropy: boolean,
    isLowQuality: boolean,
    isDifferentMean: boolean,
    isStatisticError: false
  }> {
    const [statistic, storedStatistic] = await Promise.all([
      this.is.statistic(fileInfo.from_path),
      this.is.statistic(storedFileInfo.to_path)
    ]);

    this.log.info(
      `statistic check: from_path = ${fileInfo.from_path}, to_path = ${
        storedFileInfo.to_path
      } target = ${JSON.stringify(statistic)} stored = ${JSON.stringify(
        storedStatistic
      )}`
    );

    return {
      isStatisticError: false,
      isSmallEntropy: storedStatistic.entropy > statistic.entropy,
      isLowQuality: storedStatistic.quality * 0.66 > statistic.quality,
      isDifferentMean:
        Math.abs(storedStatistic.mean - statistic.mean) >
        this.config.meanExactThreshold
    };
  }

  detectStatisticRejectResult: (
    isMayBe: boolean,
    isSmallEntropy: boolean,
    isDifferentMean: boolean,
    isLowQuality: boolean,
    info: HashRow
  ) => null | JudgeResultSimple = (
    isMayBe: boolean,
    isSmallEntropy: boolean,
    isDifferentMean: boolean,
    isLowQuality: boolean,
    info: HashRow
  ): JudgeResultSimple | null => {
    if (isDifferentMean) {
      return [
        // may be different image, save it.
        this.al.fixAction(!isMayBe, TYPE_SAVE),
        info,
        TYPE_P_HASH_REJECT_DIFFERENT_MEAN
      ];
    }

    if (isSmallEntropy) {
      return [
        this.al.fixAction(isMayBe, TYPE_DELETE),
        info,
        TYPE_P_HASH_REJECT_LOW_ENTROPY
      ];
    }

    if (isLowQuality) {
      return [
        this.al.fixAction(isMayBe, TYPE_DELETE),
        info,
        TYPE_P_HASH_REJECT_LOW_QUALITY
      ];
    }
    return null;
  };

  detectDeleteReason(fileInfo: FileInfo): ?ReasonType {
    if (fileInfo.damaged) {
      return TYPE_DAMAGED;
    }

    if (this.isLowFileSize(fileInfo)) {
      return TYPE_LOW_FILE_SIZE;
    }

    if (this.isLowResolution(fileInfo)) {
      return TYPE_LOW_RESOLUTION;
    }

    if (this.isLowLongSide(fileInfo)) {
      return TYPE_LOW_LONG_SIDE;
    }
    return null;
  }
}