Browse Source

Fixes and QoL improvements.

tags/v1.4.4
Ivan Bravo Bravo 11 months ago
parent
commit
ccd280773d

+ 3
- 1
src/.eslintrc.js View File

@@ -114,7 +114,9 @@ module.exports = {
},
settings: {
"import/resolver": {
nuxt: {}
nuxt: {},
node: {},
webpack: {}
}
}
}

+ 4
- 0
src/assets/css/components/_button.scss View File

@@ -57,6 +57,10 @@
@apply bg-success-500-30;
}
}

.icon {
@apply mr-2;
}
}

.buttons {

+ 63
- 49
src/components/Layout/Jobbar.vue View File

@@ -2,8 +2,26 @@
<div class="layout__jobbar">
<div class="jobs__section">
<div class="section__title">
<span class="icon"><font-awesome-icon icon="running" /></span>
<span>Queue</span>
<div>
<span class="icon"><font-awesome-icon icon="running" /></span>
<span>Queue</span>
</div>

<div v-show="$nudify.waiting.length > 0" class="section__actions">
<button
v-tooltip="'Forget all'"
class="button button--danger button--xs"
@click.prevent="$nudify.forgetAll('waiting')">
<font-awesome-icon icon="trash-alt" />
</button>

<button
v-tooltip="'Stop all'"
class="button button--xs"
@click.prevent="$nudify.stopAll('waiting')">
<font-awesome-icon icon="stop" />
</button>
</div>
</div>

<div class="jobs__list">
@@ -20,14 +38,26 @@

<div class="jobs__section">
<div class="section__title">
<div class="flex-1">
<div>
<span class="icon"><font-awesome-icon icon="clipboard-list" /></span>
<span>Pending</span>
</div>

<button v-show="$nudify.pending.length > 0" class="button button--xs" @click.prevent="$nudify.runAll()">
Run all
</button>
<div v-show="$nudify.pending.length > 0" class="section__actions">
<button
v-tooltip="'Forget all'"
class="button button--danger button--xs"
@click.prevent="$nudify.forgetAll()">
<font-awesome-icon icon="trash-alt" />
</button>

<button
v-tooltip="'Start all'"
class="button button--xs"
@click.prevent="$nudify.startAll()">
<font-awesome-icon icon="play" />
</button>
</div>
</div>

<div class="jobs__list">
@@ -48,9 +78,21 @@
<span>Finished</span>
</div>

<button v-show="$nudify.finished.length > 0" class="button button--xs" @click.prevent="$nudify.runAll('finished')">
Rerun all
</button>
<div v-show="$nudify.finished.length > 0" class="section__actions">
<button
v-tooltip="'Forget all'"
class="button button--danger button--xs"
@click.prevent="$nudify.forgetAll('finished')">
<font-awesome-icon icon="trash-alt" />
</button>

<button
v-tooltip="'Restart all'"
class="button button--xs"
@click.prevent="$nudify.startAll('finished')">
<font-awesome-icon icon="undo" />
</button>
</div>
</div>

<div class="jobs__list">
@@ -79,8 +121,8 @@ export default {

<style lang="scss" scoped>
.layout__jobbar {
@apply relative bg-dark-500 z-10;
@apply flex flex-col py-2;
@apply relative bg-dark-500 py-2 z-10;
@apply flex flex-col;
width: 200px;

&::after {
@@ -94,6 +136,7 @@ export default {
.jobs__section {
@apply flex-1 flex flex-col;
@apply overflow-hidden;
height: calc((100vh - 80px) / 3);
}

.section__title {
@@ -105,6 +148,14 @@ export default {
}
}

.section__actions {
@apply flex flex-1 justify-end ml-2;

.button {
@apply ml-2;
}
}

.jobs__list {
@apply flex-1 flex flex-wrap justify-between;
@apply px-4 py-2 overflow-y-auto max-h-full;
@@ -116,7 +167,6 @@ export default {
transition: all .1s ease-in-out;

&.job--running {

img {
@apply border-primary-500;
}
@@ -135,44 +185,8 @@ export default {

img {
@apply border-2 border-transparent;
@apply w-full h-full rounded-full;
@apply w-full h-full;
}
}

.job__preview {
@apply mr-4 h-full;
width: 42px;

img {
width: 42px;
height: 42px;
}
}

.job__name {
@apply flex-1 overflow-hidden whitespace-no-wrap;
text-overflow: ellipsis;
}
}

.layout-jobs {
@apply p-2 shadow h-screen flex flex-col;
width: 200px;

.jobs-pending {
@apply flex-1 border-b border-gray-300 mb-2;
}

.jobs-recent {
height: 250px;
}

.job-section {
@apply mb-4;
}

.section-title {
@apply font-bold;
}
}
</style>

+ 2
- 2
src/components/Nudity/Upload.vue View File

@@ -3,7 +3,7 @@
<div class="uploader__settings box box--items">
<div class="box__content">
<box-item
label="On Upload"
label="Upload Mode"
description="Select what should be done when uploading a new photo.">
<select v-model="$settings.app.uploadMode" class="input">
<option value="none">
@@ -52,7 +52,7 @@
v-show="false"
ref="photo"
type="file"
accept="image/jpeg, image/png"
accept="image/jpeg, image/png, image/gif"
multiple
@change="openFile">


+ 1
- 0
src/electron/src/index.js View File

@@ -202,6 +202,7 @@ class DreamApp {
icon: join(config.rootDir, 'dist', 'icon.ico'),
webPreferences: {
nodeIntegration: true,
nodeIntegrationInWorker: true,
preload: join(app.getAppPath(), 'electron', 'dist', 'provider.js'),
},
})

+ 0
- 46
src/electron/src/modules/tools/fs.js View File

@@ -45,52 +45,6 @@ export function read(path, encoding = 'utf-8') {
return readFileSync(path, { encoding })
}

/**
*
* @param {string} filepath
*/
export async function getInfo(filepath) {
const exists = existsSync(filepath)

const promises = [
new Promise((resolve) => { resolve(mime.lookup(filepath)) }),
new Promise((resolve) => { resolve(parse(filepath)) }),
]

if (exists) {
promises.push(
stat(filepath),
md5File(filepath),
read(filepath, 'base64'),
)
} else {
promises.push(
Promise.resolve(),
Promise.resolve(),
Promise.resolve(),
)
}

const [
mimetype,
{ name, ext, dir },
stats,
md5,
base64,
] = await Promise.all(promises)

return {
exists,
name,
ext,
dir,
mimetype,
size: ((stats ?.size || 0) / 1000000.0),
md5,
dataURL: `data:${mimetype};base64,${base64}`,
}
}

/**
*
* @param {string} path

+ 312
- 1
src/layouts/default.vue View File

@@ -20,7 +20,6 @@ export default {
}
</script>


<style lang="scss">
.layout {
@apply h-full;
@@ -49,3 +48,315 @@ export default {
}
}
</style>

<style>
/*!
* Cropper.js v1.5.6
* https://fengyuanchen.github.io/cropperjs
*
* Copyright 2015-present Chen Fengyuan
* Released under the MIT license
*
* Date: 2019-10-04T04:33:44.164Z
*/

.cropper-container {
direction: ltr;
font-size: 0;
line-height: 0;
position: relative;
-ms-touch-action: none;
touch-action: none;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}

.cropper-container img {
display: block;
height: 100%;
image-orientation: 0deg;
max-height: none !important;
max-width: none !important;
min-height: 0 !important;
min-width: 0 !important;
width: 100%;
}

.cropper-wrap-box,
.cropper-canvas,
.cropper-drag-box,
.cropper-crop-box,
.cropper-modal {
bottom: 0;
left: 0;
position: absolute;
right: 0;
top: 0;
}

.cropper-wrap-box,
.cropper-canvas {
overflow: hidden;
}

.cropper-drag-box {
background-color: #fff;
opacity: 0;
}

.cropper-modal {
background-color: #000;
opacity: 0.5;
}

.cropper-view-box {
display: block;
height: 100%;
outline: 1px solid #39f;
outline-color: rgba(51, 153, 255, 0.75);
overflow: hidden;
width: 100%;
}

.cropper-dashed {
border: 0 dashed #eee;
display: block;
opacity: 0.5;
position: absolute;
}

.cropper-dashed.dashed-h {
border-bottom-width: 1px;
border-top-width: 1px;
height: calc(100% / 3);
left: 0;
top: calc(100% / 3);
width: 100%;
}

.cropper-dashed.dashed-v {
border-left-width: 1px;
border-right-width: 1px;
height: 100%;
left: calc(100% / 3);
top: 0;
width: calc(100% / 3);
}

.cropper-center {
display: block;
height: 0;
left: 50%;
opacity: 0.75;
position: absolute;
top: 50%;
width: 0;
}

.cropper-center::before,
.cropper-center::after {
background-color: #eee;
content: ' ';
display: block;
position: absolute;
}

.cropper-center::before {
height: 1px;
left: -3px;
top: 0;
width: 7px;
}

.cropper-center::after {
height: 7px;
left: 0;
top: -3px;
width: 1px;
}

.cropper-face,
.cropper-line,
.cropper-point {
display: block;
height: 100%;
opacity: 0.1;
position: absolute;
width: 100%;
}

.cropper-face {
background-color: #fff;
left: 0;
top: 0;
}

.cropper-line {
background-color: #39f;
}

.cropper-line.line-e {
cursor: ew-resize;
right: -3px;
top: 0;
width: 5px;
}

.cropper-line.line-n {
cursor: ns-resize;
height: 5px;
left: 0;
top: -3px;
}

.cropper-line.line-w {
cursor: ew-resize;
left: -3px;
top: 0;
width: 5px;
}

.cropper-line.line-s {
bottom: -3px;
cursor: ns-resize;
height: 5px;
left: 0;
}

.cropper-point {
background-color: #39f;
height: 5px;
opacity: 0.75;
width: 5px;
}

.cropper-point.point-e {
cursor: ew-resize;
margin-top: -3px;
right: -3px;
top: 50%;
}

.cropper-point.point-n {
cursor: ns-resize;
left: 50%;
margin-left: -3px;
top: -3px;
}

.cropper-point.point-w {
cursor: ew-resize;
left: -3px;
margin-top: -3px;
top: 50%;
}

.cropper-point.point-s {
bottom: -3px;
cursor: s-resize;
left: 50%;
margin-left: -3px;
}

.cropper-point.point-ne {
cursor: nesw-resize;
right: -3px;
top: -3px;
}

.cropper-point.point-nw {
cursor: nwse-resize;
left: -3px;
top: -3px;
}

.cropper-point.point-sw {
bottom: -3px;
cursor: nesw-resize;
left: -3px;
}

.cropper-point.point-se {
bottom: -3px;
cursor: nwse-resize;
height: 20px;
opacity: 1;
right: -3px;
width: 20px;
}

@media (min-width: 768px) {
.cropper-point.point-se {
height: 15px;
width: 15px;
}
}

@media (min-width: 992px) {
.cropper-point.point-se {
height: 10px;
width: 10px;
}
}

@media (min-width: 1200px) {
.cropper-point.point-se {
height: 5px;
opacity: 0.75;
width: 5px;
}
}

.cropper-point.point-se::before {
background-color: #39f;
bottom: -50%;
content: ' ';
display: block;
height: 200%;
opacity: 0;
position: absolute;
right: -50%;
width: 200%;
}

.cropper-invisible {
opacity: 0;
}

.cropper-bg {
background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAAA3NCSVQICAjb4U/gAAAABlBMVEXMzMz////TjRV2AAAACXBIWXMAAArrAAAK6wGCiw1aAAAAHHRFWHRTb2Z0d2FyZQBBZG9iZSBGaXJld29ya3MgQ1M26LyyjAAAABFJREFUCJlj+M/AgBVhF/0PAH6/D/HkDxOGAAAAAElFTkSuQmCC');
}

.cropper-hide {
display: block;
height: 0;
position: absolute;
width: 0;
}

.cropper-hidden {
display: none !important;
}

.cropper-move {
cursor: move;
}

.cropper-crop {
cursor: crosshair;
}

.cropper-disabled .cropper-drag-box,
.cropper-disabled .cropper-face,
.cropper-disabled .cropper-line,
.cropper-disabled .cropper-point {
cursor: not-allowed;
}

</style>

<style>
.tippy-tooltip[data-animation=fade][data-state=hidden]{opacity:0}.tippy-iOS{cursor:pointer!important;-webkit-tap-highlight-color:transparent}.tippy-popper{pointer-events:none;max-width:calc(100vw - 10px);transition-timing-function:cubic-bezier(.165,.84,.44,1);transition-property:transform}.tippy-tooltip{position:relative;color:#fff;border-radius:4px;font-size:14px;line-height:1.4;background-color:#333;transition-property:visibility,opacity,transform;outline:0}.tippy-tooltip[data-placement^=top]>.tippy-arrow{border-width:8px 8px 0;border-top-color:#333;margin:0 3px;transform-origin:50% 0;bottom:-7px}.tippy-tooltip[data-placement^=bottom]>.tippy-arrow{border-width:0 8px 8px;border-bottom-color:#333;margin:0 3px;transform-origin:50% 7px;top:-7px}.tippy-tooltip[data-placement^=left]>.tippy-arrow{border-width:8px 0 8px 8px;border-left-color:#333;margin:3px 0;transform-origin:0 50%;right:-7px}.tippy-tooltip[data-placement^=right]>.tippy-arrow{border-width:8px 8px 8px 0;border-right-color:#333;margin:3px 0;transform-origin:7px 50%;left:-7px}.tippy-tooltip[data-interactive][data-state=visible]{pointer-events:auto}.tippy-tooltip[data-inertia][data-state=visible]{transition-timing-function:cubic-bezier(.54,1.5,.38,1.11)}.tippy-arrow{position:absolute;border-color:transparent;border-style:solid}.tippy-content{padding:5px 9px}
</style>

+ 16
- 6
src/modules/config/cli-errors.json View File

@@ -6,12 +6,12 @@
},
{
"error": "The NVIDIA driver on your system is too old",
"message": "The NVIDIA driver installed on your system is too old! Please update the drivers [here](http://www.nvidia.com/Download/index.aspx), if the drivers are up to date then your GPU may not be compatible. You can also change the Device option in **Settings** to **CPU**.",
"message": "The NVIDIA driver installed on your system is too old! If the drivers are up to date then your GPU may not be compatible. You can also change the device option in **Settings** to **CPU**.",
"level": "debug"
},
{
"error": "no longer supports this GPU",
"message": "Your GPU is not powerful enough to run this program. Please change the Device option in **Settings** to **CPU**.",
"message": "DreamTime does not have support for your GPU. Please change the device option in **Settings** to **CPU**.",
"level": "debug"
},
{
@@ -21,22 +21,22 @@
},
{
"error": "CUDA out of memory",
"message": "You have run out of RAM on your GPU! Try a photo of smaller size or free all possible GPU use.",
"message": "You have run out of VRAM on your GPU! Try a photo of smaller size or free all possible GPU use.",
"level": "debug"
},
{
"error": "codec can't decode byte",
"message": "The algorithm had a problem decoding some characters. This is usually caused by being installed in a location with special characters (accents, spaces, etc.). Please reinstall the program in another location.",
"message": "The algorithm had a problem decoding some characters, this is usually caused by being installed in a location with special characters (accents, spaces, etc.). Please reinstall the program in another location.",
"level": "debug"
},
{
"error": "invalid device ordinal",
"message": "A valid GPU device was not found in the indicated GPU option, please try another. You can also change the Device option in **Settings** to **CPU**.",
"message": "A valid GPU was not found, please try another. You can also change the device option in **Settings** to **CPU**.",
"level": "debug"
},
{
"error": "image is not 512 x 512",
"message": "The photo is not 512x512, please make sure you have uploaded the correct photo or enable and use the cropper.",
"message": "The photo is not 512x512.",
"level": "debug"
},
{
@@ -48,5 +48,15 @@
"error": "DLL",
"message": "There was a problem loading a necessary DreamPower file. It is possible that your installation is corrupt, download the program again and reinstall.",
"level": "debug"
},
{
"error": "file is not valid image",
"message": "There was a problem loading the photo, please make sure the photo is not corrupt.",
"level": "debug"
},
{
"error": "cv2.error",
"message": "There was a problem loading the photo, please make sure the photo is not corrupt.",
"level": "debug"
}
]

+ 31
- 12
src/modules/file.js View File

@@ -1,11 +1,13 @@
import { isString } from 'lodash'
import { join } from 'path'
import { getMetadata } from '~/workers/fs'

const {
writeDataURL, downloadAsync, getInfo,
writeDataURL, downloadAsync,
unlinkSync, copySync,
} = $provider.tools.fs


const { getPath } = $provider.tools.paths

export class File {
@@ -52,6 +54,18 @@ export class File {
return file
}

/**
*
*/
static fromMetadata(metadata) {
const file = new this()
return file.setMetadata(metadata)
}

/**
*
* @param {*} filepath
*/
constructor(filepath) {
if (isString(filepath)) {
this.open(filepath)
@@ -68,20 +82,25 @@ export class File {
filepath = this.path
}

const info = await getInfo(filepath)

this.name = info.name
this.extension = info.ext
this.directory = info.dir
this.mimetype = info.mimetype
this.size = info.size
this.exists = info.exists
this.md5 = info.md5
this.dataURL = info.dataURL
const metadata = await getMetadata(filepath)
return this.setMetadata(metadata)
}

/**
*
* @param {Object} metadata
*/
setMetadata(metadata) {
this.name = metadata.name
this.extension = metadata.ext
this.directory = metadata.dir
this.mimetype = metadata.mimetype
this.size = metadata.size
this.exists = metadata.exists
this.md5 = metadata.md5
this.dataURL = metadata.dataURL
this.fullname = `${this.name}${this.extension}`
this.path = join(this.directory, this.fullname)

return this
}


+ 78
- 38
src/modules/nudify/nudify.js View File

@@ -10,7 +10,7 @@
import {
startsWith, find, isNil,
filter, map, debounce,
remove,
remove, clone,
} from 'lodash'
import { join } from 'path'
import Queue from 'better-queue'
@@ -20,6 +20,7 @@ import delay from 'delay'
import { events } from '../events'
import { Photo } from './photo'
import { File } from '../file'
import { getFilesMetadata } from '~/workers/fs'

const logger = require('logplease').create('nudify')

@@ -43,7 +44,7 @@ export class Nudify {
*/
static emitUpdate = debounce(() => {
events.emit('nudify.update')
}, 100, { leading: true })
}, 300, { leading: true })

/**
* @type {Array<Photo>}
@@ -116,18 +117,6 @@ export class Nudify {
return find(this.photos, { id })
}

/**
*
* @param {Photo} photo
*/
static remove(photo) {
photo.cancel()

// eslint-disable-next-line lodash/prefer-immutable-method
remove(this.photos, { id: photo.id })
this.emitUpdate()
}

/**
*
* @param {File} input
@@ -143,7 +132,7 @@ export class Nudify {

this.photos.unshift(photo)

logger.debug('Photo added!', photo.file.path)
logger.debug('Photo added!', photo.file.fullname)
this.emitUpdate()

if (this.photos.length > MAX_PHOTOS) {
@@ -164,24 +153,22 @@ export class Nudify {
*
* @param {string} filepath
*/
static async addFile(filepath) {
if (!existsSync(filepath)) {
throw new AppError('The path does not exist.', { title: 'Upload failed.', level: 'warn' })
}

const stat = statSync(filepath)

if (stat.isDirectory()) {
const paths = map(await readdir(filepath), (fpath) => join(filepath, fpath))
await this.addFiles(paths)
await delay(100)

return
}

const file = await File.fromPath(filepath)

this.add(file)
static async addFile(filepath, skipErrors = false) {
const filesMetadata = await getFilesMetadata(filepath)

filesMetadata.forEach((metadata) => {
const file = File.fromMetadata(metadata)

try {
this.add(file)
} catch (err) {
if (skipErrors) {
logger.warn('Error adding a photo, skipped.', err)
} else {
throw err
}
}
})
}

/**
@@ -192,7 +179,7 @@ export class Nudify {
const promises = []

for (const path of paths) {
promises.push(this.addFile(path))
promises.push(this.addFile(path, true))
}

return Promise.all(promises)
@@ -239,16 +226,28 @@ export class Nudify {
* @param {Photo} photo
*/
static removeFromQueue(photo) {
this.queue.cancel(photo.id, () => {
photo.cancel('pending')
})
photo.cancel('pending')
this.queue.cancel(photo.id)
}

/**
*
* @param {Photo} photo
*/
static forget(photo) {
this.removeFromQueue(photo)

// eslint-disable-next-line lodash/prefer-immutable-method
remove(this.photos, { id: photo.id })

this.emitUpdate()
}

/**
*
* @param {string} status
*/
static runAll(status = 'pending') {
static startAll(status = 'pending') {
this.photos.forEach((photo) => {
if (photo.status !== status) {
return
@@ -257,6 +256,47 @@ export class Nudify {
this.addToQueue(photo)
})
}

/**
*
* @param {string} status
*/
static stopAll(status = 'pending') {
this.photos.forEach((photo) => {
if (photo.status !== status) {
return
}

this.removeFromQueue(photo)
})
}

static async forgetAll(status = 'pending') {
const response = await Swal.fire({
title: 'Are you sure?',
text: 'Forgetting will remove all photos from the queue (it will not delete the files) and free up memory.',
icon: 'warning',
showCancelButton: true,
confirmButtonColor: '#F44336',
confirmButtonText: 'Yes, forget it!',
})

if (!response.value) {
return
}

const photosCopy = clone(this.photos)

photosCopy.forEach((photo) => {
if (photo.status !== status) {
return
}

logger.debug(`Forgetting ${photo.file.fullname}...`)

this.forget(photo)
})
}
}

window.Nudify = Nudify // debugging

+ 5
- 2
src/modules/nudify/photo-run.js View File

@@ -89,13 +89,16 @@ export class PhotoRun {

get outputName() {
const now = moment().unix()
const { file } = this.photo

const originalName = truncate(
deburr(this.photo.file.name),
deburr(file.name),
{ length: 30, omission: '' },
)

return `${originalName}-${now}-dreamtime.png`
const extension = file.mimetype === 'image/gif' ? 'gif' : 'png'

return `${originalName}-${now}-dreamtime.${extension}`
}

constructor(id, photo) {

+ 1
- 1
src/modules/nudify/photo.js View File

@@ -146,7 +146,7 @@ export class Photo {

this.file = file

this.fileCropped = new File(getCropPath(`${this.id}.png`))
this.fileCropped = file.mimetype === 'image/gif' ? new File(getCropPath(`${this.id}.gif`)) : new File(getCropPath(`${this.id}.png`))

this.preferences = clone(settings.preferences)


+ 8
- 16
src/nuxt.config.js View File

@@ -99,6 +99,8 @@ module.exports = {

extractCSS: true,

parallel: false,

babel: {
plugins: [
['@babel/plugin-proposal-class-properties', { loose: true }],
@@ -122,24 +124,14 @@ module.exports = {
extend(config, { isClient, isDev }) {
config.target = 'electron-renderer'

config.externals = [nodeExternals({
modulesFromFile: {
include: ['dependencies'],
},
})]

// exclude browser field resolution
const mainFields = ['esnext', 'main']
config.resolve.mainFields = mainFields
config.resolve.aliasFields = mainFields

config.node = {
__dirname: isDev,
__filename: isDev,
}
config.module.rules.push({
test: /\.worker\.js$/,
use: { loader: 'worker-loader' },
exclude: /(node_modules)/,
})

if (isDev) {
config.devtool = isClient ? 'source-map' : 'inline-source-map'
config.devtool = 'source-map'
} else {
config.output.publicPath = './_nuxt/'
}

+ 8
- 4
src/package.json View File

@@ -4,7 +4,7 @@
"description": "DreamTime allows you to nudify photos of people.",
"author": "DreamNet",
"homepage": "https://time.dreamnet.tech",
"version": "1.2.0",
"version": "1.2.1",
"main": "electron/dist/index.js",
"license": "GPL-3.0-only",
"private": true,
@@ -94,6 +94,7 @@
"patch-package": "^6.2.0",
"popmotion": "^8.7.1",
"postinstall-postinstall": "^2.0.0",
"promise-worker": "^2.0.1",
"randomcolor": "^0.5.4",
"randomstring": "^1.1.5",
"raw-loader": "^3.1.0",
@@ -109,7 +110,6 @@
"webpack-node-externals": "^1.7.2"
},
"devDependencies": {
"electron": "^7.1.1",
"@babel/cli": "^7.7.0",
"@babel/core": "^7.7.2",
"@babel/plugin-proposal-class-properties": "^7.7.0",
@@ -125,6 +125,7 @@
"babel-watch": "^7.0.0",
"cross-env": "^6.0.3",
"delay-cli": "^1.1.0",
"electron": "^7.1.1",
"electron-builder": "^22.1.0",
"electron-devtools-installer": "^2.2.4",
"electron-rebuild": "^1.8.6",
@@ -132,7 +133,9 @@
"eslint": "^6.6.0",
"eslint-config-airbnb-base": "^14.0.0",
"eslint-config-standard": ">=14.1.0",
"eslint-import-resolver-node": "^0.3.2",
"eslint-import-resolver-nuxt": "^0.1.5",
"eslint-import-resolver-webpack": "^0.11.1",
"eslint-plugin-import": ">=2.18.2",
"eslint-plugin-jest": ">=23.0.4",
"eslint-plugin-lodash": "^6.0.0",
@@ -150,6 +153,7 @@
"sass-loader": "^8.0.0",
"tailwindcss": "^1.1.3",
"tailwindcss-alpha": "hacknug/tailwindcss-alpha#feature/tests",
"webpack-cli": "^3.3.10"
"webpack-cli": "^3.3.10",
"worker-loader": "^2.0.0"
}
}
}

+ 1
- 1
src/pages/index.vue View File

@@ -2,7 +2,7 @@
<div class="home content-body">
<div v-if="alert" class="notification is-warning" v-html="alert" />

<div v-if="$nudify.photos.length >= 100" class="notification is-warning">
<div v-if="$nudify.photos.length >= 500" class="notification is-warning">
The application may become unstable by keeping {{ $nudify.photos.length }} photos in the queue.
</div>


+ 11
- 6
src/pages/nudify/_id.vue View File

@@ -35,23 +35,27 @@

<!-- Buttons -->
<button v-show="!photo.running && !photo.waiting" class="button button--success" @click.prevent="start">
Nudify
<span class="icon"><font-awesome-icon icon="play" /></span>
<span>Add to queue</span>
</button>

<button v-show="photo.waiting" class="button button--danger" @click.prevent="removeFromQueue">
Remove from queue
<span>Remove from queue</span>
</button>

<button v-show="photo.running" class="button button--danger" @click.prevent="cancel">
Cancel
<span class="icon"><font-awesome-icon icon="stop" /></span>
<span>Stop</span>
</button>

<button class="button" @click.prevent="openFolder">
Folder
<span class="icon"><font-awesome-icon icon="folder-open" /></span>
<span>Folder</span>
</button>

<button class="button button--danger" @click.prevent="forget">
Forget
<span class="icon"><font-awesome-icon icon="trash-alt" /></span>
<span>Forget</span>
</button>
</div>
</div>
@@ -124,9 +128,10 @@ export default {
async forget() {
const response = await Swal.fire({
title: 'Are you sure?',
text: 'Forgetting the photo will remove it from the queue (it will not delete the files) and free up memory.',
icon: 'warning',
showCancelButton: true,
cancelButtonColor: '#F44336',
confirmButtonColor: '#F44336',
confirmButtonText: 'Yes, delete it!',
})


+ 0
- 308
src/pages/nudify/_id/crop.vue View File

@@ -132,311 +132,3 @@ export default {
@apply flex-1 h-full;
}
</style>

<style>
/*!
* Cropper.js v1.5.6
* https://fengyuanchen.github.io/cropperjs
*
* Copyright 2015-present Chen Fengyuan
* Released under the MIT license
*
* Date: 2019-10-04T04:33:44.164Z
*/

.cropper-container {
direction: ltr;
font-size: 0;
line-height: 0;
position: relative;
-ms-touch-action: none;
touch-action: none;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}

.cropper-container img {
display: block;
height: 100%;
image-orientation: 0deg;
max-height: none !important;
max-width: none !important;
min-height: 0 !important;
min-width: 0 !important;
width: 100%;
}

.cropper-wrap-box,
.cropper-canvas,
.cropper-drag-box,
.cropper-crop-box,
.cropper-modal {
bottom: 0;
left: 0;
position: absolute;
right: 0;
top: 0;
}

.cropper-wrap-box,
.cropper-canvas {
overflow: hidden;
}

.cropper-drag-box {
background-color: #fff;
opacity: 0;
}

.cropper-modal {
background-color: #000;
opacity: 0.5;
}

.cropper-view-box {
display: block;
height: 100%;
outline: 1px solid #39f;
outline-color: rgba(51, 153, 255, 0.75);
overflow: hidden;
width: 100%;
}

.cropper-dashed {
border: 0 dashed #eee;
display: block;
opacity: 0.5;
position: absolute;
}

.cropper-dashed.dashed-h {
border-bottom-width: 1px;
border-top-width: 1px;
height: calc(100% / 3);
left: 0;
top: calc(100% / 3);
width: 100%;
}

.cropper-dashed.dashed-v {
border-left-width: 1px;
border-right-width: 1px;
height: 100%;
left: calc(100% / 3);
top: 0;
width: calc(100% / 3);
}

.cropper-center {
display: block;
height: 0;
left: 50%;
opacity: 0.75;
position: absolute;
top: 50%;
width: 0;
}

.cropper-center::before,
.cropper-center::after {
background-color: #eee;
content: ' ';
display: block;
position: absolute;
}

.cropper-center::before {
height: 1px;
left: -3px;
top: 0;
width: 7px;
}

.cropper-center::after {
height: 7px;
left: 0;
top: -3px;
width: 1px;
}

.cropper-face,
.cropper-line,
.cropper-point {
display: block;
height: 100%;
opacity: 0.1;
position: absolute;
width: 100%;
}

.cropper-face {
background-color: #fff;
left: 0;
top: 0;
}

.cropper-line {
background-color: #39f;
}

.cropper-line.line-e {
cursor: ew-resize;
right: -3px;
top: 0;
width: 5px;
}

.cropper-line.line-n {
cursor: ns-resize;
height: 5px;
left: 0;
top: -3px;
}

.cropper-line.line-w {
cursor: ew-resize;
left: -3px;
top: 0;
width: 5px;
}

.cropper-line.line-s {
bottom: -3px;
cursor: ns-resize;
height: 5px;
left: 0;
}

.cropper-point {
background-color: #39f;
height: 5px;
opacity: 0.75;
width: 5px;
}

.cropper-point.point-e {
cursor: ew-resize;
margin-top: -3px;
right: -3px;
top: 50%;
}

.cropper-point.point-n {
cursor: ns-resize;
left: 50%;
margin-left: -3px;
top: -3px;
}

.cropper-point.point-w {
cursor: ew-resize;
left: -3px;
margin-top: -3px;
top: 50%;
}

.cropper-point.point-s {
bottom: -3px;
cursor: s-resize;
left: 50%;
margin-left: -3px;
}

.cropper-point.point-ne {
cursor: nesw-resize;
right: -3px;
top: -3px;
}

.cropper-point.point-nw {
cursor: nwse-resize;
left: -3px;
top: -3px;
}

.cropper-point.point-sw {
bottom: -3px;
cursor: nesw-resize;
left: -3px;
}

.cropper-point.point-se {
bottom: -3px;
cursor: nwse-resize;
height: 20px;
opacity: 1;
right: -3px;
width: 20px;
}

@media (min-width: 768px) {
.cropper-point.point-se {
height: 15px;
width: 15px;
}
}

@media (min-width: 992px) {
.cropper-point.point-se {
height: 10px;
width: 10px;
}
}

@media (min-width: 1200px) {
.cropper-point.point-se {
height: 5px;
opacity: 0.75;
width: 5px;
}
}

.cropper-point.point-se::before {
background-color: #39f;
bottom: -50%;
content: ' ';
display: block;
height: 200%;
opacity: 0;
position: absolute;
right: -50%;
width: 200%;
}

.cropper-invisible {
opacity: 0;
}

.cropper-bg {
background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAAA3NCSVQICAjb4U/gAAAABlBMVEXMzMz////TjRV2AAAACXBIWXMAAArrAAAK6wGCiw1aAAAAHHRFWHRTb2Z0d2FyZQBBZG9iZSBGaXJld29ya3MgQ1M26LyyjAAAABFJREFUCJlj+M/AgBVhF/0PAH6/D/HkDxOGAAAAAElFTkSuQmCC');
}

.cropper-hide {
display: block;
height: 0;
position: absolute;
width: 0;
}

.cropper-hidden {
display: none !important;
}

.cropper-move {
cursor: move;
}

.cropper-crop {
cursor: crosshair;
}

.cropper-disabled .cropper-drag-box,
.cropper-disabled .cropper-face,
.cropper-disabled .cropper-line,
.cropper-disabled .cropper-point {
cursor: not-allowed;
}

</style>

+ 36
- 0
src/workers/fs/index.js View File

@@ -0,0 +1,36 @@
// 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@dreamnet.tech>, 2019.

import PromiseWorker from 'promise-worker'
import Worker from 'worker-loader!./worker'

const worker = new Worker()
const promiseWorker = new PromiseWorker(worker)

/**
*
* @param {string} filepath
*/
export function getFilesMetadata(filepath) {
return promiseWorker.postMessage({
name: 'getFilesMetadata',
filepath,
})
}

/**
*
* @param {string} filepath
*/
export function getMetadata(filepath) {
return promiseWorker.postMessage({
name: 'getMetadata',
filepath,
})
}

+ 105
- 0
src/workers/fs/worker.js View File

@@ -0,0 +1,105 @@
// 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@dreamnet.tech>, 2019.

import registerPromiseWorker from 'promise-worker/register'
import { map, flattenDeep } from 'lodash'
import { join, parse } from 'path'
import {
existsSync, statSync, stat, readFile, readdir,
} from 'fs-extra'
import mime from 'mime-types'
import md5File from 'md5-file/promise'

/**
*
* @param {string} filepath
*/
async function getMetadata(filepath) {
const exists = existsSync(filepath)

const promises = [
new Promise((resolve) => { resolve(mime.lookup(filepath)) }),
new Promise((resolve) => { resolve(parse(filepath)) }),
]

if (exists) {
promises.push(
stat(filepath),
md5File(filepath),
readFile(filepath, 'base64'),
)
} else {
promises.push(
Promise.resolve(),
Promise.resolve(),
Promise.resolve(),
)
}

const [
mimetype,
{ name, ext, dir },
stats,
md5,
base64,
] = await Promise.all(promises)

return {
exists,
name,
ext,
dir,
mimetype,
size: ((stats ?.size || 0) / 1000000.0),
md5,
dataURL: `data:${mimetype};base64,${base64}`,
}
}

/**
*
* @param {string} filepath
*/
async function getFilesMetadata(filepath) {
const files = []

if (!existsSync(filepath)) {
return files
}

const stat = statSync(filepath)

if (stat.isDirectory()) {
const promises = []
const paths = map(await readdir(filepath), (fpath) => join(filepath, fpath))

paths.forEach((fpath) => {
promises.push(getFilesMetadata(fpath))
})

files.push(await Promise.all(promises))
} else {
files.push(await getMetadata(filepath))
}

// you're absolutely right, this is super lazy
return flattenDeep(files)
}

registerPromiseWorker((message) => {
if (message.name === 'getMetadata') {
return getMetadata(message.filepath)
}

if (message.name === 'getFilesMetadata') {
return getFilesMetadata(message.filepath)
}

return undefined
})

Loading…
Cancel
Save