1
0
mirror of https://github.com/sashacmc/photo-importer.git synced 2024-11-16 10:08:40 +02:00

Add Windows support

This commit is contained in:
Alexander Bushnev 2022-01-31 00:12:34 +01:00
parent 0fa0acfc99
commit c90a5fa127
7 changed files with 134 additions and 12 deletions

View File

@ -31,6 +31,7 @@ Standalone web server for fast media import for headless computer
* python3-psutil
* exiftran or jpegtran
* pmount (only for server)
* pypiwin32 (only for windows)
### Installation Options:
@ -45,6 +46,21 @@ sudo dpkg -i ../photo-importer_1.0.1_all.deb
sudo python3 ./setup.py install
```
#### Installing for Windows
Download and install python3 for you Windows distributive
https://www.python.org/downloads/windows/
Download and install exiftool
https://exiftool.org/
Download and extract jpegtran to photo_importer folder
http://sylvana.net/jpegcrop/jpegtran/
Install python dependencies
```bash
python -m pip install progressbar psutil pyexiftool pypiwin32
```
## Usage
### Command-Line Interface
@ -70,6 +86,16 @@ Will import (by default move, but it can be changed in config) files from /path/
![Web interface example](https://user-images.githubusercontent.com/28735879/76140174-f1995300-6057-11ea-8718-19c38650c786.png)
### Windows command line
```bash
cd photo_importer
run.py -c ..\photo-importer-win.cfg path\to\media\files \output\path
```
### Windows web
```bash
photo-importer-server.bat
```
## Configuration
The server config file located in /etc/photo-importer.cfg

6
debian/changelog vendored
View File

@ -1,3 +1,9 @@
photo-importer (1.1.0) stable; urgency=medium
* Add Windows support
-- Alexander Bushnev <Alexander@Bushnev.ru> Mon, 31 Jan 2022 00:10:46 +0100
photo-importer (1.0.9) stable; urgency=medium
* Add windows compatibility by means of use_shutil option

4
photo-importer-server.bat Executable file
View File

@ -0,0 +1,4 @@
start "" http://localhost:8080/
cd photo_importer
server.py -c ..\photo-importer-win.cfg
pause

59
photo-importer-win.cfg Normal file
View File

@ -0,0 +1,59 @@
[main]
# time source order
time_src_image = exif,name
time_src_video = exif,name,attr
time_src_audio = exif,name,attr
# Date/Time formats
out_date_format = %%Y/%%Y-%%m-%%d
out_time_format = %%Y-%%m-%%d_%%H-%%M-%%S
# Output sub directorines
out_subdir_image = Foto
out_subdir_video = Video
out_subdir_audio = Audio
# File extensions
file_ext_image = jpeg,jpg,cr2
file_ext_video = mp4,mpg,mpeg,mov,avi,mts,3gp,m4v
file_ext_audio = mp3,3gpp,m4a,wav
file_ext_garbage = thm,ctg
file_ext_ignore = ini,zip,db
# Thread count
threads_count = 2
# Remove garbage files (photo config, thumbnails, etc.) 0/1
remove_garbage = 1
# Remove empty directories
remove_empty_dirs = 1
# Remove source files (in case of out_path specified) 0/1
move_mode = 1
# umask for new folder and copied files
umask = 0o000
# use jpegtran in place of exiftran 0/1
use_jpegtran = 1
# use shutil libarary in place of system calls 0/1
# slower but provide more cross platform compatibility
use_shutil = 1
[server]
# server port
port = 8080
# path to html files
web_path = ..\web
# imported output path
out_path = C:\
# fixed input path
in_path =
# log file
log_file = ..\photo-importer-server.log

View File

@ -55,7 +55,7 @@ class PhotoImporterHandler(http.server.BaseHTTPRequestHandler):
def __bytes_to_gbytes(self, b):
return round(b / 1024. / 1024. / 1024., 2)
def __get_removable_devices(self):
def __get_removable_devices_posix(self):
mount_list = self.__get_mounted_list()
res = {}
for path in glob.glob('/sys/block/*/device'):
@ -82,6 +82,33 @@ class PhotoImporterHandler(http.server.BaseHTTPRequestHandler):
'read_only': read_only
}
return res
def __get_removable_devices_win(self):
import win32api
import win32con
import win32file
res = {}
for d in win32api.GetLogicalDriveStrings().split('\x00'):
if d and win32file.GetDriveType(d) == win32con.DRIVE_REMOVABLE:
dev_name = FIXED_IN_PATH_NAME + d
res[dev_name] = {
'dev_path': dev_name,
'mount_path': d,
'read_only': not os.access(d, os.W_OK)
}
return res
def __get_removable_devices(self):
res = {}
if os.name == 'nt':
res = self.__get_removable_devices_win()
elif os.name == 'posix':
res = self.__get_removable_devices_posix()
else:
raise Exception('Unsupported os: %s' % os.name)
if self.server.fixed_in_path() != '':
res[FIXED_IN_PATH_NAME] = {
'dev_path': FIXED_IN_PATH_NAME,
@ -214,7 +241,7 @@ class PhotoImporterHandler(http.server.BaseHTTPRequestHandler):
try:
out_path = params['o'][0]
except Exception as ex:
except Exception:
out_path = self.server.out_path()
result = None
@ -235,7 +262,7 @@ class PhotoImporterHandler(http.server.BaseHTTPRequestHandler):
def __sysinfo_request(self, params):
try:
path = params['p'][0]
except Exception as ex:
except Exception:
path = self.server.out_path()
res = {}
du = psutil.disk_usage(path)
@ -338,7 +365,7 @@ class PhotoImporterServer(http.server.HTTPServer):
self.__cfg = cfg
self.__importers = {}
port = int(cfg['server']['port'])
self.__web_path = cfg['server']['web_path']
self.__web_path = os.path.normpath(cfg['server']['web_path'])
self.__out_path = cfg['server']['out_path']
self.__fixed_in_path = cfg['server']['in_path']
self.__move_mode = int(cfg['main']['move_mode'])
@ -358,8 +385,6 @@ class PhotoImporterServer(http.server.HTTPServer):
def import_start(self, in_path, out_path):
logging.info('import_start: %s', in_path)
if in_path in self.__importers and in_path != self.fixed_in_path():
raise Exception('Already started')
self.__importers[in_path] = importer.Importer(
self.__cfg,

View File

@ -1,6 +1,6 @@
from setuptools import setup
setup(name='photo-importer',
version='1.0.9',
version='1.1.0',
description='Photo importer tool',
author='Alexander Bushnev',
author_email='Alexander@Bushnev.ru',

View File

@ -48,15 +48,18 @@
for (var dev in data) {
var action = ""
var state = data[dev].state
if (dev == "none") {
var name = dev
var path = encodeURIComponent(data[dev].path)
if (dev.startsWith("none")) {
name = ""
if (state == "mounted" || state == "done") {
action += "<input onclick=\"sendCommand('start', '" + data[dev].path + "');\" type=button class=\"btn btn-success btn-sm\" value=\"Import\"/> ";
action += "<input onclick=\"sendCommand('start', '" + path + "');\" type=button class=\"btn btn-success btn-sm\" value=\"Import\"/> ";
}
} else {
if (state == "mounted") {
action += "<input onclick=\"sendCommand('umount', '" + dev + "');\" type=button class=\"btn btn-primary btn-sm\" value=\"Unmount\"/> ";
if (data[dev].allow_start) {
action += "<input onclick=\"sendCommand('start', '" + data[dev].path + "');\" type=button class=\"btn btn-success btn-sm\" value=\"Import\"/> ";
action += "<input onclick=\"sendCommand('start', '" + path + "');\" type=button class=\"btn btn-success btn-sm\" value=\"Import\"/> ";
}
} else if (state == "unmounted") {
action = "<input onclick=\"sendCommand('mount', '" + dev + "');\" type=button class=\"btn btn-primary btn-sm\" value=\"Mount\"/> ";
@ -74,7 +77,6 @@
} else if (state == "error") {
stat += "<br/><font size=\"-2\">(" + data[dev].details + ")<font>"
}
var name = dev
if (data[dev].read_only) {
name += "<br/><font size=\"-2\">(read only)<font>"
}
@ -85,7 +87,7 @@
+ "<td>" + action + "</td>"
+ "<td>" + data[dev].size + "&nbsp;GB</td>"
+ "<td>" + progress(data[dev].usage, "", "bg-info") + "</td>"
+ "<td><a href=\"import?a=getlog&p=" + data[dev].path + "\"><img src=\"log.png\"/></a></td>"
+ "<td><a href=\"import?a=getlog&p=" + path + "\"><img src=\"log.png\"/></a></td>"
+ "</tr>"
}
html += "</table>"