add file manager
This commit is contained in:
parent
b847ded828
commit
2dd95be90d
@ -30,7 +30,8 @@
|
||||
"react-dom": "^17.0.2",
|
||||
"react-feather": "^2.0.10",
|
||||
"react-hotkeys-hook": "^3.4.7",
|
||||
"react-scripts": "4.0.3",
|
||||
"react-photo-album": "^2.0.0",
|
||||
"react-scripts": "5.0.1",
|
||||
"react-use": "^17.3.1",
|
||||
"react-zoom-pan-pinch": "^2.1.3",
|
||||
"recoil": "^0.6.1",
|
||||
@ -38,7 +39,7 @@
|
||||
"typescript": "4.x"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "react-scripts start",
|
||||
"start": "cross-env GENERATE_SOURCEMAP=false react-scripts start",
|
||||
"build": "cross-env GENERATE_SOURCEMAP=false react-scripts build",
|
||||
"test": "react-scripts test",
|
||||
"eject": "react-scripts eject"
|
||||
@ -65,8 +66,8 @@
|
||||
"eslint-plugin-import": "^2.25.2",
|
||||
"eslint-plugin-jsx-a11y": "^6.4.1",
|
||||
"eslint-plugin-prettier": "^4.0.0",
|
||||
"eslint-plugin-react": "^7.26.1",
|
||||
"eslint-plugin-react-hooks": "^4.2.0",
|
||||
"eslint-plugin-react": "^7.27.1",
|
||||
"eslint-plugin-react-hooks": "^4.3.0",
|
||||
"prettier": "^2.4.1",
|
||||
"sass": "^1.49.9"
|
||||
}
|
||||
|
@ -164,3 +164,16 @@ export async function postInteractiveSeg(
|
||||
throw new Error(`Something went wrong: ${error}`)
|
||||
}
|
||||
}
|
||||
|
||||
export async function getMediaFile(filename: string) {
|
||||
const res = await fetch(`${API_ENDPOINT}/media/${filename}`, {
|
||||
method: 'GET',
|
||||
})
|
||||
if (res.ok) {
|
||||
const blob = await res.blob()
|
||||
const file = new File([blob], filename)
|
||||
return file
|
||||
}
|
||||
const errMsg = await res.text()
|
||||
throw new Error(errMsg)
|
||||
}
|
||||
|
27
lama_cleaner/app/src/components/FileManager/FileManager.scss
Normal file
27
lama_cleaner/app/src/components/FileManager/FileManager.scss
Normal file
@ -0,0 +1,27 @@
|
||||
.file-manager-modal {
|
||||
color: var(--text-color);
|
||||
height: 90%;
|
||||
width: 80%;
|
||||
}
|
||||
|
||||
.file-manager {
|
||||
overflow: auto;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.react-photo-album.react-photo-album--columns {
|
||||
height: 80vh;
|
||||
}
|
||||
|
||||
.react-photo-album--photo {
|
||||
border-radius: 8px;
|
||||
border: 1px solid transparent;
|
||||
|
||||
// transform-origin: 0 0;
|
||||
transition: transform 0.25s, visibility 0.25s ease-in;
|
||||
|
||||
&:hover {
|
||||
border: 1px solid var(--border-color);
|
||||
transform: scale(1.01);
|
||||
}
|
||||
}
|
90
lama_cleaner/app/src/components/FileManager/FileManager.tsx
Normal file
90
lama_cleaner/app/src/components/FileManager/FileManager.tsx
Normal file
@ -0,0 +1,90 @@
|
||||
import React, { ReactNode, useEffect, useMemo, useState } from 'react'
|
||||
import PhotoAlbum, { RenderPhoto } from 'react-photo-album'
|
||||
import Modal from '../shared/Modal'
|
||||
|
||||
interface Photo {
|
||||
src: string
|
||||
height: number
|
||||
width: number
|
||||
}
|
||||
|
||||
interface Filename {
|
||||
name: string
|
||||
height: number
|
||||
width: number
|
||||
}
|
||||
|
||||
const renderPhoto: RenderPhoto = ({
|
||||
layout,
|
||||
layoutOptions,
|
||||
imageProps: { alt, style, ...restImageProps },
|
||||
}) => (
|
||||
<div
|
||||
style={{
|
||||
boxSizing: 'content-box',
|
||||
alignItems: 'center',
|
||||
}}
|
||||
>
|
||||
<img
|
||||
alt={alt}
|
||||
style={{ ...style, width: '100%', padding: 0 }}
|
||||
{...restImageProps}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
|
||||
interface Props {
|
||||
show: boolean
|
||||
onClose: () => void
|
||||
onPhotoClick: (filename: string) => void
|
||||
}
|
||||
|
||||
export default function FileManager(props: Props) {
|
||||
const { show, onClose, onPhotoClick } = props
|
||||
const [filenames, setFileNames] = useState<Filename[]>([])
|
||||
|
||||
const onClick = ({ index }: { index: number }) => {
|
||||
onPhotoClick(filenames[index].name)
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
const fetchData = async () => {
|
||||
const res = await fetch('/medias')
|
||||
if (res.ok) {
|
||||
const newFilenames = await res.json()
|
||||
setFileNames(newFilenames)
|
||||
}
|
||||
}
|
||||
|
||||
fetchData()
|
||||
}, [])
|
||||
|
||||
const photos = useMemo(() => {
|
||||
return filenames.map((filename: Filename) => {
|
||||
const width = 256
|
||||
const height = filename.height * (width / filename.width)
|
||||
const src = `/media_thumbnail/${filename.name}?width=${width}&height=${height}`
|
||||
return { src, height, width }
|
||||
})
|
||||
}, [filenames])
|
||||
|
||||
return (
|
||||
<Modal
|
||||
onClose={onClose}
|
||||
title="Files"
|
||||
className="file-manager-modal"
|
||||
show={show}
|
||||
>
|
||||
<div className="file-manager">
|
||||
<PhotoAlbum
|
||||
layout="columns"
|
||||
photos={photos}
|
||||
renderPhoto={renderPhoto}
|
||||
spacing={6}
|
||||
padding={4}
|
||||
onClick={onClick}
|
||||
/>
|
||||
</div>
|
||||
</Modal>
|
||||
)
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
import { ArrowUpTrayIcon } from '@heroicons/react/24/outline'
|
||||
import { FolderIcon, PhotoIcon } from '@heroicons/react/24/outline'
|
||||
import { PlayIcon } from '@radix-ui/react-icons'
|
||||
import React, { useState } from 'react'
|
||||
import { useRecoilState, useRecoilValue } from 'recoil'
|
||||
@ -9,6 +9,7 @@ import {
|
||||
isSDState,
|
||||
maskState,
|
||||
runManuallyState,
|
||||
showFileManagerState,
|
||||
} from '../../store/Atoms'
|
||||
import Button from '../shared/Button'
|
||||
import Shortcuts from '../Shortcuts/Shortcuts'
|
||||
@ -18,6 +19,7 @@ import PromptInput from './PromptInput'
|
||||
import CoffeeIcon from '../CoffeeIcon/CoffeeIcon'
|
||||
import emitter, { EVENT_CUSTOM_MASK } from '../../event'
|
||||
import { useImage } from '../../utils'
|
||||
import useHotKey from '../../hooks/useHotkey'
|
||||
|
||||
const Header = () => {
|
||||
const isInpainting = useRecoilValue(isInpaintingState)
|
||||
@ -29,6 +31,17 @@ const Header = () => {
|
||||
const isSD = useRecoilValue(isSDState)
|
||||
const runManually = useRecoilValue(runManuallyState)
|
||||
const [openMaskPopover, setOpenMaskPopover] = useState(false)
|
||||
const [showFileManager, setShowFileManager] =
|
||||
useRecoilState(showFileManagerState)
|
||||
|
||||
useHotKey(
|
||||
'f',
|
||||
() => {
|
||||
setShowFileManager(!showFileManager)
|
||||
},
|
||||
{},
|
||||
[showFileManager]
|
||||
)
|
||||
|
||||
const renderHeader = () => {
|
||||
return (
|
||||
@ -41,10 +54,20 @@ const Header = () => {
|
||||
gap: 8,
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
icon={<FolderIcon />}
|
||||
style={{ border: 0 }}
|
||||
toolTip="Open File Manager"
|
||||
tooltipPosition="bottom"
|
||||
onClick={() => {
|
||||
setShowFileManager(true)
|
||||
}}
|
||||
/>
|
||||
|
||||
<label htmlFor={uploadElemId}>
|
||||
<Button
|
||||
icon={<ArrowUpTrayIcon />}
|
||||
style={{ border: 0 }}
|
||||
icon={<PhotoIcon />}
|
||||
style={{ border: 0, gap: 0 }}
|
||||
disabled={isInpainting}
|
||||
toolTip="Upload image"
|
||||
tooltipPosition="bottom"
|
||||
@ -62,7 +85,6 @@ const Header = () => {
|
||||
}}
|
||||
accept="image/png, image/jpeg"
|
||||
/>
|
||||
Image
|
||||
</Button>
|
||||
</label>
|
||||
|
||||
|
@ -67,6 +67,7 @@ export default function ShortcutsModal() {
|
||||
<ShortCut content="Toggle Dark Mode" keys={['Shift', 'D']} />
|
||||
<ShortCut content="Toggle Hotkeys Dialog" keys={['H']} />
|
||||
<ShortCut content="Toggle Settings Dialog" keys={['S']} />
|
||||
<ShortCut content="Toggle File Manager" keys={['F']} />
|
||||
</div>
|
||||
</Modal>
|
||||
)
|
||||
|
@ -1,4 +1,4 @@
|
||||
import React, { useCallback, useEffect } from 'react'
|
||||
import React, { useCallback, useEffect, useState } from 'react'
|
||||
import { useRecoilState, useRecoilValue } from 'recoil'
|
||||
import Editor from './Editor/Editor'
|
||||
import ShortcutsModal from './Shortcuts/ShortcutsModal'
|
||||
@ -10,15 +10,18 @@ import {
|
||||
isPaintByExampleState,
|
||||
isSDState,
|
||||
settingState,
|
||||
showFileManagerState,
|
||||
toastState,
|
||||
} from '../store/Atoms'
|
||||
import {
|
||||
currentModel,
|
||||
getMediaFile,
|
||||
modelDownloaded,
|
||||
switchModel,
|
||||
} from '../adapters/inpainting'
|
||||
import SidePanel from './SidePanel/SidePanel'
|
||||
import PESidePanel from './SidePanel/PESidePanel'
|
||||
import FileManager from './FileManager/FileManager'
|
||||
|
||||
const Workspace = () => {
|
||||
const [file, setFile] = useRecoilState(fileState)
|
||||
@ -27,6 +30,9 @@ const Workspace = () => {
|
||||
const isSD = useRecoilValue(isSDState)
|
||||
const isPaintByExample = useRecoilValue(isPaintByExampleState)
|
||||
|
||||
const [showFileManager, setShowFileManager] =
|
||||
useRecoilState(showFileManagerState)
|
||||
|
||||
const onSettingClose = async () => {
|
||||
const curModel = await currentModel().then(res => res.text())
|
||||
if (curModel === settings.model) {
|
||||
@ -92,6 +98,17 @@ const Workspace = () => {
|
||||
<>
|
||||
{isSD ? <SidePanel /> : <></>}
|
||||
{isPaintByExample ? <PESidePanel /> : <></>}
|
||||
<FileManager
|
||||
show={showFileManager}
|
||||
onClose={() => {
|
||||
setShowFileManager(false)
|
||||
}}
|
||||
onPhotoClick={async (filename: string) => {
|
||||
const newFile = await getMediaFile(filename)
|
||||
setFile(newFile)
|
||||
setShowFileManager(false)
|
||||
}}
|
||||
/>
|
||||
<Editor />
|
||||
<SettingModal onClose={onSettingClose} />
|
||||
<ShortcutsModal />
|
||||
|
@ -32,7 +32,7 @@
|
||||
grid-auto-rows: max-content;
|
||||
row-gap: 1rem;
|
||||
place-self: center;
|
||||
padding: 2rem;
|
||||
padding: 25px;
|
||||
border-radius: 0.95rem;
|
||||
|
||||
&:focus {
|
||||
|
@ -41,6 +41,7 @@ interface AppState {
|
||||
isInteractiveSeg: boolean
|
||||
isInteractiveSegRunning: boolean
|
||||
interactiveSegClicks: number[][]
|
||||
showFileManager: boolean
|
||||
}
|
||||
|
||||
export const appState = atom<AppState>({
|
||||
@ -53,6 +54,7 @@ export const appState = atom<AppState>({
|
||||
isInteractiveSeg: false,
|
||||
isInteractiveSegRunning: false,
|
||||
interactiveSegClicks: [],
|
||||
showFileManager: false,
|
||||
},
|
||||
})
|
||||
|
||||
@ -78,6 +80,18 @@ export const isInpaintingState = selector({
|
||||
},
|
||||
})
|
||||
|
||||
export const showFileManagerState = selector({
|
||||
key: 'showFileManager',
|
||||
get: ({ get }) => {
|
||||
const app = get(appState)
|
||||
return app.showFileManager
|
||||
},
|
||||
set: ({ get, set }, newValue: any) => {
|
||||
const app = get(appState)
|
||||
set(appState, { ...app, showFileManager: newValue })
|
||||
},
|
||||
})
|
||||
|
||||
export const fileState = selector({
|
||||
key: 'fileState',
|
||||
get: ({ get }) => {
|
||||
|
@ -7,6 +7,7 @@
|
||||
// App
|
||||
@use './App';
|
||||
@use '../components/Editor/Editor';
|
||||
@use '../components/FileManager/FileManager';
|
||||
@use '../components/LandingPage/LandingPage';
|
||||
@use '../components/Header/Header';
|
||||
@use '../components/Header/PromptInput';
|
||||
|
File diff suppressed because it is too large
Load Diff
1
lama_cleaner/file_manager/__init__.py
Normal file
1
lama_cleaner/file_manager/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
from .file_manager import FileManager
|
186
lama_cleaner/file_manager/file_manager.py
Normal file
186
lama_cleaner/file_manager/file_manager.py
Normal file
@ -0,0 +1,186 @@
|
||||
# Copy from https://github.com/silentsokolov/flask-thumbnails/blob/master/flask_thumbnails/thumbnail.py
|
||||
import os
|
||||
from functools import lru_cache
|
||||
from io import BytesIO
|
||||
|
||||
from PIL import Image, ImageOps, PngImagePlugin
|
||||
LARGE_ENOUGH_NUMBER = 100
|
||||
PngImagePlugin.MAX_TEXT_CHUNK = LARGE_ENOUGH_NUMBER * (1024**2)
|
||||
from .storage_backends import FilesystemStorageBackend
|
||||
from .utils import aspect_to_string, generate_filename, glob_img
|
||||
|
||||
|
||||
class FileManager:
|
||||
def __init__(self, app=None):
|
||||
self.app = app
|
||||
self._default_root_directory = "media"
|
||||
self._default_thumbnail_directory = "media"
|
||||
self._default_root_url = "/"
|
||||
self._default_thumbnail_root_url = "/"
|
||||
self._default_format = "JPEG"
|
||||
|
||||
if app is not None:
|
||||
self.init_app(app)
|
||||
|
||||
def init_app(self, app):
|
||||
if self.app is None:
|
||||
self.app = app
|
||||
app.thumbnail_instance = self
|
||||
|
||||
if not hasattr(app, "extensions"):
|
||||
app.extensions = {}
|
||||
|
||||
if "thumbnail" in app.extensions:
|
||||
raise RuntimeError("Flask-thumbnail extension already initialized")
|
||||
|
||||
app.extensions["thumbnail"] = self
|
||||
|
||||
app.config.setdefault("THUMBNAIL_MEDIA_ROOT", self._default_root_directory)
|
||||
app.config.setdefault("THUMBNAIL_MEDIA_THUMBNAIL_ROOT", self._default_thumbnail_directory)
|
||||
app.config.setdefault("THUMBNAIL_MEDIA_URL", self._default_root_url)
|
||||
app.config.setdefault("THUMBNAIL_MEDIA_THUMBNAIL_URL", self._default_thumbnail_root_url)
|
||||
app.config.setdefault("THUMBNAIL_DEFAULT_FORMAT", self._default_format)
|
||||
|
||||
@property
|
||||
def root_directory(self):
|
||||
path = self.app.config["THUMBNAIL_MEDIA_ROOT"]
|
||||
|
||||
if os.path.isabs(path):
|
||||
return path
|
||||
else:
|
||||
return os.path.join(self.app.root_path, path)
|
||||
|
||||
@property
|
||||
def thumbnail_directory(self):
|
||||
path = self.app.config["THUMBNAIL_MEDIA_THUMBNAIL_ROOT"]
|
||||
|
||||
if os.path.isabs(path):
|
||||
return path
|
||||
else:
|
||||
return os.path.join(self.app.root_path, path)
|
||||
|
||||
@property
|
||||
def root_url(self):
|
||||
return self.app.config["THUMBNAIL_MEDIA_URL"]
|
||||
|
||||
@property
|
||||
@lru_cache()
|
||||
def media_names(self):
|
||||
names = sorted([it.name for it in glob_img(self.root_directory)])
|
||||
res = []
|
||||
for name in names:
|
||||
img = Image.open(os.path.join(self.root_directory, name))
|
||||
res.append({"name": name, "height": img.height, "width": img.width})
|
||||
return res
|
||||
|
||||
@property
|
||||
def thumbnail_url(self):
|
||||
return self.app.config["THUMBNAIL_MEDIA_THUMBNAIL_URL"]
|
||||
|
||||
def get_thumbnail(self, original_filename, width, height, **options):
|
||||
storage = FilesystemStorageBackend(self.app)
|
||||
crop = options.get("crop", "fit")
|
||||
background = options.get("background")
|
||||
quality = options.get("quality", 90)
|
||||
|
||||
original_path, original_filename = os.path.split(original_filename)
|
||||
original_filepath = os.path.join(self.root_directory, original_path, original_filename)
|
||||
image = Image.open(BytesIO(storage.read(original_filepath)))
|
||||
|
||||
# keep ratio resize
|
||||
if width is not None:
|
||||
height = int(image.height * width / image.width)
|
||||
else:
|
||||
width = int(image.width * height / image.height)
|
||||
|
||||
thumbnail_size = (width, height)
|
||||
|
||||
thumbnail_filename = generate_filename(
|
||||
original_filename, aspect_to_string(thumbnail_size), crop, background, quality
|
||||
)
|
||||
|
||||
thumbnail_filepath = os.path.join(
|
||||
self.thumbnail_directory, original_path, thumbnail_filename
|
||||
)
|
||||
thumbnail_url = os.path.join(self.thumbnail_url, original_path, thumbnail_filename)
|
||||
|
||||
if storage.exists(thumbnail_filepath):
|
||||
return thumbnail_url, (width, height)
|
||||
|
||||
try:
|
||||
image.load()
|
||||
except (IOError, OSError):
|
||||
self.app.logger.warning("Thumbnail not load image: %s", original_filepath)
|
||||
return thumbnail_url, (width, height)
|
||||
|
||||
# get original image format
|
||||
options["format"] = options.get("format", image.format)
|
||||
|
||||
image = self._create_thumbnail(image, thumbnail_size, crop, background=background)
|
||||
|
||||
raw_data = self.get_raw_data(image, **options)
|
||||
storage.save(thumbnail_filepath, raw_data)
|
||||
|
||||
return thumbnail_url, (width, height)
|
||||
|
||||
def get_raw_data(self, image, **options):
|
||||
data = {
|
||||
"format": self._get_format(image, **options),
|
||||
"quality": options.get("quality", 90),
|
||||
}
|
||||
|
||||
_file = BytesIO()
|
||||
image.save(_file, **data)
|
||||
return _file.getvalue()
|
||||
|
||||
@staticmethod
|
||||
def colormode(image, colormode="RGB"):
|
||||
if colormode == "RGB" or colormode == "RGBA":
|
||||
if image.mode == "RGBA":
|
||||
return image
|
||||
if image.mode == "LA":
|
||||
return image.convert("RGBA")
|
||||
return image.convert(colormode)
|
||||
|
||||
if colormode == "GRAY":
|
||||
return image.convert("L")
|
||||
|
||||
return image.convert(colormode)
|
||||
|
||||
@staticmethod
|
||||
def background(original_image, color=0xFF):
|
||||
size = (max(original_image.size),) * 2
|
||||
image = Image.new("L", size, color)
|
||||
image.paste(
|
||||
original_image,
|
||||
tuple(map(lambda x: (x[0] - x[1]) / 2, zip(size, original_image.size))),
|
||||
)
|
||||
|
||||
return image
|
||||
|
||||
def _get_format(self, image, **options):
|
||||
if options.get("format"):
|
||||
return options.get("format")
|
||||
if image.format:
|
||||
return image.format
|
||||
|
||||
return self.app.config["THUMBNAIL_DEFAULT_FORMAT"]
|
||||
|
||||
def _create_thumbnail(self, image, size, crop="fit", background=None):
|
||||
try:
|
||||
resample = Image.Resampling.LANCZOS
|
||||
except AttributeError: # pylint: disable=raise-missing-from
|
||||
resample = Image.ANTIALIAS
|
||||
|
||||
if crop == "fit":
|
||||
image = ImageOps.fit(image, size, resample)
|
||||
else:
|
||||
image = image.copy()
|
||||
image.thumbnail(size, resample=resample)
|
||||
|
||||
if background is not None:
|
||||
image = self.background(image)
|
||||
|
||||
image = self.colormode(image)
|
||||
|
||||
return image
|
46
lama_cleaner/file_manager/storage_backends.py
Normal file
46
lama_cleaner/file_manager/storage_backends.py
Normal file
@ -0,0 +1,46 @@
|
||||
# Copy from https://github.com/silentsokolov/flask-thumbnails/blob/master/flask_thumbnails/storage_backends.py
|
||||
import errno
|
||||
import os
|
||||
from abc import ABC, abstractmethod
|
||||
|
||||
|
||||
class BaseStorageBackend(ABC):
|
||||
def __init__(self, app=None):
|
||||
self.app = app
|
||||
|
||||
@abstractmethod
|
||||
def read(self, filepath, mode="rb", **kwargs):
|
||||
raise NotImplementedError
|
||||
|
||||
@abstractmethod
|
||||
def exists(self, filepath):
|
||||
raise NotImplementedError
|
||||
|
||||
@abstractmethod
|
||||
def save(self, filepath, data):
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class FilesystemStorageBackend(BaseStorageBackend):
|
||||
def read(self, filepath, mode="rb", **kwargs):
|
||||
with open(filepath, mode) as f: # pylint: disable=unspecified-encoding
|
||||
return f.read()
|
||||
|
||||
def exists(self, filepath):
|
||||
return os.path.exists(filepath)
|
||||
|
||||
def save(self, filepath, data):
|
||||
directory = os.path.dirname(filepath)
|
||||
|
||||
if not os.path.exists(directory):
|
||||
try:
|
||||
os.makedirs(directory)
|
||||
except OSError as e:
|
||||
if e.errno != errno.EEXIST:
|
||||
raise
|
||||
|
||||
if not os.path.isdir(directory):
|
||||
raise IOError("{} is not a directory".format(directory))
|
||||
|
||||
with open(filepath, "wb") as f:
|
||||
f.write(data)
|
67
lama_cleaner/file_manager/utils.py
Normal file
67
lama_cleaner/file_manager/utils.py
Normal file
@ -0,0 +1,67 @@
|
||||
# Copy from: https://github.com/silentsokolov/flask-thumbnails/blob/master/flask_thumbnails/utils.py
|
||||
import importlib
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
from typing import Union
|
||||
|
||||
|
||||
def generate_filename(original_filename, *options):
|
||||
name, ext = os.path.splitext(original_filename)
|
||||
for v in options:
|
||||
if v:
|
||||
name += "_%s" % v
|
||||
name += ext
|
||||
|
||||
return name
|
||||
|
||||
|
||||
def parse_size(size):
|
||||
if isinstance(size, int):
|
||||
# If the size parameter is a single number, assume square aspect.
|
||||
return [size, size]
|
||||
|
||||
if isinstance(size, (tuple, list)):
|
||||
if len(size) == 1:
|
||||
# If single value tuple/list is provided, exand it to two elements
|
||||
return size + type(size)(size)
|
||||
return size
|
||||
|
||||
try:
|
||||
thumbnail_size = [int(x) for x in size.lower().split("x", 1)]
|
||||
except ValueError:
|
||||
raise ValueError( # pylint: disable=raise-missing-from
|
||||
"Bad thumbnail size format. Valid format is INTxINT."
|
||||
)
|
||||
|
||||
if len(thumbnail_size) == 1:
|
||||
# If the size parameter only contains a single integer, assume square aspect.
|
||||
thumbnail_size.append(thumbnail_size[0])
|
||||
|
||||
return thumbnail_size
|
||||
|
||||
|
||||
def aspect_to_string(size):
|
||||
if isinstance(size, str):
|
||||
return size
|
||||
|
||||
return "x".join(map(str, size))
|
||||
|
||||
|
||||
IMG_SUFFIX = {'.jpg', '.jpeg', '.png'}
|
||||
|
||||
|
||||
def glob_img(p: Union[Path, str], recursive: bool = False):
|
||||
p = Path(p)
|
||||
if p.is_file() and p.suffix in IMG_SUFFIX:
|
||||
yield p
|
||||
else:
|
||||
if recursive:
|
||||
files = Path(p).glob("**/*.*")
|
||||
else:
|
||||
files = Path(p).glob("*.*")
|
||||
|
||||
for it in files:
|
||||
if it.suffix not in IMG_SUFFIX:
|
||||
continue
|
||||
yield it
|
@ -1,6 +1,9 @@
|
||||
import os
|
||||
import imghdr
|
||||
import argparse
|
||||
from pathlib import Path
|
||||
|
||||
from loguru import logger
|
||||
|
||||
|
||||
def parse_args():
|
||||
@ -48,7 +51,12 @@ def parse_args():
|
||||
help="Set window size for GUI",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--input", type=str, help="Path to image you want to load by default"
|
||||
"--input", type=str,
|
||||
help="If input is image, it will be load by default. If input is directory, all images will be loaded to file manager"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--output-dir", type=str,
|
||||
help="Only required when --input is directory. Output directory for all processed images"
|
||||
)
|
||||
parser.add_argument("--disable-model-switch", action="store_true", help="Disable model switch in frontend")
|
||||
parser.add_argument("--debug", action="store_true")
|
||||
@ -57,8 +65,20 @@ def parse_args():
|
||||
if args.input is not None:
|
||||
if not os.path.exists(args.input):
|
||||
parser.error(f"invalid --input: {args.input} not exists")
|
||||
if imghdr.what(args.input) is None:
|
||||
parser.error(f"invalid --input: {args.input} is not a valid image file")
|
||||
if os.path.isfile(args.input):
|
||||
if imghdr.what(args.input) is None:
|
||||
parser.error(f"invalid --input: {args.input} is not a valid image file")
|
||||
else:
|
||||
if args.output_dir is None:
|
||||
parser.error(f"invalid --input: {args.input} is a directory, --output-dir is required")
|
||||
else:
|
||||
output_dir = Path(args.output_dir)
|
||||
if not output_dir.exists():
|
||||
logger.info(f"Creating output directory: {output_dir}")
|
||||
output_dir.mkdir(parents=True)
|
||||
else:
|
||||
if not output_dir.is_dir():
|
||||
parser.error(f"invalid --output-dir: {output_dir} is not a directory")
|
||||
|
||||
if args.model == 'sd1.5' and not args.sd_run_local:
|
||||
if not args.hf_access_token.startswith("hf_"):
|
||||
|
@ -20,6 +20,7 @@ from loguru import logger
|
||||
from lama_cleaner.interactive_seg import InteractiveSeg, Click
|
||||
from lama_cleaner.model_manager import ModelManager
|
||||
from lama_cleaner.schema import Config
|
||||
from lama_cleaner.file_manager import FileManager
|
||||
|
||||
try:
|
||||
torch._C._jit_override_can_fuse_on_cpu(False)
|
||||
@ -29,7 +30,8 @@ try:
|
||||
except:
|
||||
pass
|
||||
|
||||
from flask import Flask, request, send_file, cli, make_response
|
||||
from flask import Flask, request, send_file, cli, make_response, send_from_directory, jsonify
|
||||
from flask_caching import Cache
|
||||
|
||||
# Disable ability for Flask to display warning about using a development server in a production environment.
|
||||
# https://gist.github.com/jerblack/735b9953ba1ab6234abb43174210d356
|
||||
@ -65,15 +67,15 @@ class NoFlaskwebgui(logging.Filter):
|
||||
|
||||
logging.getLogger("werkzeug").addFilter(NoFlaskwebgui())
|
||||
|
||||
cache = Cache(config={'CACHE_TYPE': 'SimpleCache'}, with_jinja2_ext=False)
|
||||
|
||||
app = Flask(__name__, static_folder=os.path.join(BUILD_DIR, "static"))
|
||||
app.config["JSON_AS_ASCII"] = False
|
||||
CORS(app, expose_headers=["Content-Disposition"])
|
||||
# MAX_BUFFER_SIZE = 50 * 1000 * 1000 # 50 MB
|
||||
# async_mode 优先级: eventlet/gevent_uwsgi/gevent/threading
|
||||
# only threading works on macOS
|
||||
# socketio = SocketIO(app, max_http_buffer_size=MAX_BUFFER_SIZE, async_mode='threading')
|
||||
cache.init_app(app)
|
||||
|
||||
model: ModelManager = None
|
||||
thumb = FileManager(app)
|
||||
interactive_seg_model: InteractiveSeg = None
|
||||
device = None
|
||||
input_image_path: str = None
|
||||
@ -93,6 +95,38 @@ def diffuser_callback(i, t, latents):
|
||||
# socketio.emit('diffusion_step', {'diffusion_step': step})
|
||||
|
||||
|
||||
@app.route("/medias")
|
||||
def medias():
|
||||
# all images in input folder
|
||||
return jsonify(thumb.media_names), 200
|
||||
|
||||
|
||||
@app.route('/media/<filename>')
|
||||
def media_file(filename):
|
||||
return send_from_directory(app.config['THUMBNAIL_MEDIA_ROOT'], filename)
|
||||
|
||||
|
||||
@app.route('/media_thumbnail/<filename>')
|
||||
def media_thumbnail_file(filename):
|
||||
args = request.args
|
||||
width = args.get('width')
|
||||
height = args.get('height')
|
||||
if width is None and height is None:
|
||||
width = 256
|
||||
if width:
|
||||
width = int(float(width))
|
||||
if height:
|
||||
height = int(float(height))
|
||||
|
||||
thumb_filename, (width, height) = thumb.get_thumbnail(filename, width, height)
|
||||
thumb_filepath = f"{app.config['THUMBNAIL_MEDIA_THUMBNAIL_ROOT']}{thumb_filename}"
|
||||
|
||||
response = make_response(send_file(thumb_filepath))
|
||||
response.headers["X-Width"] = str(width)
|
||||
response.headers["X-Height"] = str(height)
|
||||
return response
|
||||
|
||||
|
||||
@app.route("/inpaint", methods=["POST"])
|
||||
def process():
|
||||
input = request.files
|
||||
@ -294,12 +328,17 @@ def main(args):
|
||||
global is_desktop
|
||||
|
||||
device = torch.device(args.device)
|
||||
input_image_path = args.input
|
||||
is_disable_model_switch = args.disable_model_switch
|
||||
is_desktop = args.gui
|
||||
if is_disable_model_switch:
|
||||
logger.info(f"Start with --disable-model-switch, model switch on frontend is disable")
|
||||
|
||||
if os.path.isdir(args.input):
|
||||
app.config["THUMBNAIL_MEDIA_ROOT"] = args.input
|
||||
app.config["THUMBNAIL_MEDIA_THUMBNAIL_ROOT"] = os.path.join(args.output_dir, 'thumbnails')
|
||||
else:
|
||||
input_image_path = args.input
|
||||
|
||||
model = ModelManager(
|
||||
name=args.model,
|
||||
device=device,
|
||||
|
Loading…
Reference in New Issue
Block a user