private-dreamnet/dreamtime

View on GitHub
src/electron/src/modules/tools/waifu.js

Summary

Maintainability
C
1 day
Test Coverage
// DreamTime.
// Copyright (C) DreamNet. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License 3.0 as published by
// the Free Software Foundation. See <https://www.gnu.org/licenses/gpl-3.0.html>
//
// Written by Ivan Bravo Bravo <ivan@opendreamnet.com>, 2019.

import { isNil, isString } from 'lodash'
import { spawn } from 'child_process'
import EventBus from 'js-event-bus'
import semverRegex from 'semver-regex'
import * as fs from 'fs-extra'
import { getWaifuPath } from './paths'
import { settings } from '../settings'

const logger = require('@dreamnet/logplease').create('waifu')

let version

export function exec(args, options = {}) {
  options = {
    cwd: getWaifuPath(),
    ...options,
  }

  if (process.env.WAIFU_PYTHON) {
    // Main Script
    args.unshift('waifu2x.py')

    // C:\\Users\\koles\\Anaconda3\\envs\\waifu\\python
    const pythonPath = fs.existsSync(process.env.WAIFU_PYTHON) ? process.env.WAIFU_PYTHON : 'python'

    logger.debug('[Python] Running:', {
      pythonPath,
      args,
      options,
    })

    return spawn(pythonPath, args, options)
  }

  logger.debug('Running:', args)

  return spawn(getWaifuPath('waifu2x'), args, options)
}

/**
 *
 * @param {Array} args
 * @param {EventBus} events
 */
export async function run(args, events) {
  const process = exec(args)
  let cancelled = false

  process.on('error', (error) => {
    logger.error(error)
    events.emit('error', null, error)
  })

  process.stdout.on('data', (output) => {
    logger.info(output.toString())
    const stdout = output.toString().trim().split('\n')
    events.emit('stdout', null, stdout)
  })

  process.stderr.on('data', (output) => {
    logger.warn(output.toString())
    events.emit('stderr', null, output)
  })

  process.on('close', (code) => {
    logger.info(`Waifu2X exited with code ${code}`)
    events.emit('close', null, code)

    if (cancelled) {
      events.emit('cancelled')
    } else if (code === 0 || isNil(code)) {
      events.emit('success')
    } else {
      events.emit('fail', null, false)
    }
  })

  events.on('cancel', () => {
    cancelled = true
    process.stdin.pause()
    process.kill()
  })
}

/**
 *
 * @param {PhotoRun} run
 */
export const transform = (photoPreferences, input, output) => {
  if (!output) {
    output = input
  }

  const { waifu: preferences, device } = photoPreferences.advanced

  // CLI Args
  const args = ['--input', input, '--output', output]

  // Upscale ratio.
  args.push('--scale_ratio', preferences.scale)

  // TTA
  if (preferences.tta > 0) {
    args.push('--tta', '--tta_level', preferences.tta)
  }

  // Arch
  args.push('--arch', preferences.arch)

  // Denoise level.
  args.push('--noise_level', preferences.denoise)

  // Method
  if (preferences.denoise > 0) {
    args.push('--method', 'noise_scale')
  } else {
    args.push('--method', 'scale')
  }

  // GPU
  if (device === 'GPU') {
    for (const id of settings.processing.gpus) {
      args.push('--gpu', id)
    }
  }

  const events = new EventBus()

  run(args, events)

  return events
}

/**
 * @return {boolean}
 */
export function isInstalled() {
  const dirpath = getWaifuPath()

  if (!isString(dirpath)) {
    return false
  }

  if (!fs.existsSync(dirpath)) {
    return false
  }

  const binaries = [
    'waifu2x.py',
    'waifu2x.exe',
    'waifu2x',
  ]

  for (const bin of binaries) {
    if (fs.existsSync(getWaifuPath(bin))) {
      return true
    }
  }

  return false
}

/**
 * @return {Promise}
 */
export const getVersion = () => new Promise((resolve) => {
  if (version) {
    resolve({
      status: true,
      version,
    })

    return
  }

  const process = exec(['--version'])

  let response = ''

  process.on('error', (error) => {
    logger.warn(error)

    resolve({
      status: false,
      version: undefined,
      error,
    })
  })

  process.stdout.on('data', (data) => {
    response += data
  })

  process.stderr.on('data', (data) => {
    response += data
  })

  process.on('close', (code) => {
    if (code === 0 || isNil(code)) {
      try {
        response = semverRegex().exec(response)
        response = `v${response[0]}`

        version = response

        resolve({
          status: true,
          version,
        })
      } catch (error) {
        logger.warn(error)

        resolve({
          status: false,
          version: undefined,
          error,
        })
      }
    } else {
      resolve({
        status: false,
        version: undefined,
        error: new Error(response),
      })
    }
  })
})