mirror of
https://github.com/TagStudioDev/TagStudio.git
synced 2026-01-31 15:19:10 +00:00
Merge remote-tracking branch 'upstream/main'
This commit is contained in:
2
.vscode/launch.json
vendored
2
.vscode/launch.json
vendored
@@ -8,7 +8,7 @@
|
||||
"name": "TagStudio",
|
||||
"type": "debugpy",
|
||||
"request": "launch",
|
||||
"program": "${workspaceRoot}\\TagStudio\\tagstudio.py",
|
||||
"program": "${workspaceRoot}/tagstudio/tag_studio.py",
|
||||
"console": "integratedTerminal",
|
||||
"justMyCode": true,
|
||||
"args": ["--debug"]
|
||||
|
||||
10
README.md
10
README.md
@@ -57,7 +57,7 @@ TagStudio is a photo & file organization application with an underlying system t
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- Python 3.9.6 - ~3.10 *(Not working on 3.12)*
|
||||
- Python 3.12
|
||||
|
||||
### Creating the Virtual Environment
|
||||
|
||||
@@ -90,19 +90,23 @@ _Learn more about setting up a virtual environment [here](https://docs.python.or
|
||||
|
||||
To launch TagStudio, launch the `start_win.bat` file. You can modify this .bat file or create a shortcut and add one or more additional arguments if desired.
|
||||
|
||||
Alternatively, with the virtual environment loaded, run the python file at `tagstudio\tagstudio.py` from your terminal. If you're in the project's root directory, simply run `python3 tagstudio/tagstudio.py`.
|
||||
Alternatively, with the virtual environment loaded, run the python file at `tagstudio\tag_studio.py` from your terminal. If you're in the project's root directory, simply run `python3 tagstudio/tag_studio.py`.
|
||||
|
||||
> [!CAUTION]
|
||||
> TagStudio on Linux & macOS likely won't function correctly at this time. If you're trying to run this in order to help test, debug, and improve compatibility, then charge on ahead!
|
||||
|
||||
#### macOS
|
||||
|
||||
With the virtual environment loaded, run the python file at "tagstudio/tagstudio.py" from your terminal. If you're in the project's root directory, simply run `python3 tagstudio/tagstudio.py`. When launching the program in the future, remember to activate the virtual environment each time before launching *(an easier method is currently being worked on).*
|
||||
With the virtual environment loaded, run the python file at "tagstudio/tag_studio.py" from your terminal. If you're in the project's root directory, simply run `python3 tagstudio/tag_studio.py`. When launching the program in the future, remember to activate the virtual environment each time before launching *(an easier method is currently being worked on).*
|
||||
|
||||
#### Linux
|
||||
|
||||
Run the "TagStudio.sh" script, and the program should launch! (Make sure that the script is marked as executable). Note that launching from the script from outside of a terminal will not launch a terminal window with any debug or crash information. If you wish to see this information, just launch the shell script directly from your terminal with `sh TagStudio.sh`.
|
||||
|
||||
##### NixOS
|
||||
|
||||
Use the provided `flake.nix` file to create and enter a working environment by running `nix develop`. Then, run the above `TagStudio.sh` script.
|
||||
|
||||
## Usage
|
||||
|
||||
### Creating/Opening a Library
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#! /bin/bash
|
||||
#! /usr/bin/env bash
|
||||
python3 -m venv .venv
|
||||
source .venv/bin/activate
|
||||
pip install -r requirements.txt
|
||||
python tagstudio/tagstudio.py
|
||||
python tagstudio/tag_studio.py
|
||||
|
||||
27
flake.lock
generated
Normal file
27
flake.lock
generated
Normal file
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"nodes": {
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1712473363,
|
||||
"narHash": "sha256-TIScFAVdI2yuybMxxNjC4YZ/j++c64wwuKbpnZnGiyU=",
|
||||
"owner": "nixos",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "e89cf1c932006531f454de7d652163a9a5c86668",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nixos",
|
||||
"ref": "nixos-unstable",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"nixpkgs": "nixpkgs"
|
||||
}
|
||||
}
|
||||
},
|
||||
"root": "root",
|
||||
"version": 7
|
||||
}
|
||||
70
flake.nix
Normal file
70
flake.nix
Normal file
@@ -0,0 +1,70 @@
|
||||
{
|
||||
inputs.nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
|
||||
|
||||
outputs = { self, nixpkgs, }:
|
||||
let
|
||||
pkgs = nixpkgs.legacyPackages.x86_64-linux;
|
||||
in {
|
||||
devShells.x86_64-linux.default = pkgs.mkShell {
|
||||
LD_LIBRARY_PATH = pkgs.lib.makeLibraryPath [
|
||||
pkgs.gcc-unwrapped
|
||||
pkgs.zlib
|
||||
pkgs.libglvnd
|
||||
pkgs.glib
|
||||
pkgs.stdenv.cc.cc
|
||||
pkgs.fontconfig
|
||||
pkgs.libxkbcommon
|
||||
pkgs.xorg.libxcb
|
||||
pkgs.freetype
|
||||
pkgs.dbus
|
||||
pkgs.qt6.qtwayland
|
||||
pkgs.qt6.full
|
||||
pkgs.qt6.qtbase
|
||||
pkgs.zstd
|
||||
];
|
||||
buildInputs = with pkgs; [
|
||||
cmake
|
||||
gdb
|
||||
zstd
|
||||
qt6.qtbase
|
||||
qt6.full
|
||||
qt6.qtwayland
|
||||
qtcreator
|
||||
python312Packages.pip
|
||||
python312Full
|
||||
python312Packages.virtualenv # run virtualenv .
|
||||
python312Packages.pyusb # fixes the pyusb 'No backend available' when installed directly via pip
|
||||
|
||||
libgcc
|
||||
makeWrapper
|
||||
bashInteractive
|
||||
glib
|
||||
libxkbcommon
|
||||
freetype
|
||||
binutils
|
||||
dbus
|
||||
coreutils
|
||||
libGL
|
||||
libGLU
|
||||
fontconfig
|
||||
xorg.libxcb
|
||||
|
||||
|
||||
# this is for the shellhook portion
|
||||
qt6.wrapQtAppsHook
|
||||
makeWrapper
|
||||
bashInteractive
|
||||
];
|
||||
# set the environment variables that Qt apps expect
|
||||
shellHook = ''
|
||||
export QT_QPA_PLATFORM=wayland
|
||||
export LIBRARY_PATH=/usr/lib:/usr/lib64:$LIBRARY_PATH
|
||||
# export LD_LIBRARY_PATH=${pkgs.stdenv.cc.cc.lib}/lib/:/run/opengl-driver/lib/
|
||||
export QT_PLUGIN_PATH=${pkgs.qt6.qtbase}/${pkgs.qt6.qtbase.qtPluginPrefix}
|
||||
bashdir=$(mktemp -d)
|
||||
makeWrapper "$(type -p bash)" "$bashdir/bash" "''${qtWrapperArgs[@]}"
|
||||
exec "$bashdir/bash"
|
||||
'';
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
humanfriendly==10.0
|
||||
opencv_python==4.8.0.74
|
||||
opencv_python>=4.8.0.74,<=4.9.0.80
|
||||
Pillow==10.3.0
|
||||
pillow_avif_plugin==1.3.1
|
||||
PySide6==6.5.1.1
|
||||
PySide6_Addons==6.5.1.1
|
||||
PySide6_Essentials==6.5.1.1
|
||||
typing_extensions==3.10.0.0
|
||||
ujson==5.8.0
|
||||
pillow_avif_plugin>=1.3.1,<=1.4.3
|
||||
PySide6>=6.5.1.1,<=6.6.3.1
|
||||
PySide6_Addons>=6.5.1.1,<=6.6.3.1
|
||||
PySide6_Essentials>=6.5.1.1,<=6.6.3.1
|
||||
typing_extensions>=3.10.0.0,<=4.11.0
|
||||
ujson>=5.8.0,<=5.9.0
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
@echo off
|
||||
.venv\Scripts\python.exe .\TagStudio\tagstudio.py --ui qt %* --debug
|
||||
.venv\Scripts\python.exe .\TagStudio\tag_studio.py --ui qt %*
|
||||
@@ -5,24 +5,22 @@
|
||||
"""The Library object and related methods for TagStudio."""
|
||||
|
||||
import datetime
|
||||
from enum import Enum
|
||||
import os
|
||||
import traceback
|
||||
from typing import Optional
|
||||
import json
|
||||
import glob
|
||||
from pathlib import Path
|
||||
# from typing_extensions import deprecated
|
||||
import src.core.ts_core as ts_core
|
||||
from src.core.utils.web import *
|
||||
from src.core.utils.str import *
|
||||
from src.core.utils.fs import *
|
||||
import xml.etree.ElementTree as ET
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
import logging
|
||||
import traceback
|
||||
import xml.etree.ElementTree as ET
|
||||
from enum import Enum
|
||||
|
||||
import ujson
|
||||
|
||||
from src.core import ts_core
|
||||
from src.core.utils.str import strip_punctuation
|
||||
from src.core.utils.web import strip_web_protocol
|
||||
|
||||
TYPE = ['file', 'meta', 'alt', 'mask']
|
||||
# RESULT_TYPE = Enum('Result', ['ENTRY', 'COLLATION', 'TAG_GROUP'])
|
||||
class ItemType(Enum):
|
||||
@@ -576,7 +574,7 @@ class Library:
|
||||
if not os.path.isdir(full_collage_path):
|
||||
os.mkdir(full_collage_path)
|
||||
|
||||
def verify_default_tags(self, tag_list: list) -> dict:
|
||||
def verify_default_tags(self, tag_list: list) -> list:
|
||||
"""
|
||||
Ensures that the default builtin tags are present in the Library's
|
||||
save file. Takes in and returns the tag dictionary from the JSON file.
|
||||
@@ -630,41 +628,45 @@ class Library:
|
||||
if 'id' in tag.keys():
|
||||
id = tag['id']
|
||||
|
||||
if int(id) >= self._next_tag_id:
|
||||
self._next_tag_id = int(id) + 1
|
||||
# Don't load tags with duplicate IDs
|
||||
if id not in [t.id for t in self.tags]:
|
||||
if int(id) >= self._next_tag_id:
|
||||
self._next_tag_id = int(id) + 1
|
||||
|
||||
name = ''
|
||||
if 'name' in tag.keys():
|
||||
name = tag['name']
|
||||
shorthand = ''
|
||||
if 'shorthand' in tag.keys():
|
||||
shorthand = tag['shorthand']
|
||||
aliases = []
|
||||
if 'aliases' in tag.keys():
|
||||
aliases = tag['aliases']
|
||||
subtag_ids = []
|
||||
if 'subtag_ids' in tag.keys():
|
||||
subtag_ids = tag['subtag_ids']
|
||||
color = ''
|
||||
if 'color' in tag.keys():
|
||||
color = tag['color']
|
||||
name = ''
|
||||
if 'name' in tag.keys():
|
||||
name = tag['name']
|
||||
shorthand = ''
|
||||
if 'shorthand' in tag.keys():
|
||||
shorthand = tag['shorthand']
|
||||
aliases = []
|
||||
if 'aliases' in tag.keys():
|
||||
aliases = tag['aliases']
|
||||
subtag_ids = []
|
||||
if 'subtag_ids' in tag.keys():
|
||||
subtag_ids = tag['subtag_ids']
|
||||
color = ''
|
||||
if 'color' in tag.keys():
|
||||
color = tag['color']
|
||||
|
||||
t = Tag(
|
||||
id=int(id),
|
||||
name=name,
|
||||
shorthand=shorthand,
|
||||
aliases=aliases,
|
||||
subtags_ids=subtag_ids,
|
||||
color=color
|
||||
)
|
||||
t = Tag(
|
||||
id=int(id),
|
||||
name=name,
|
||||
shorthand=shorthand,
|
||||
aliases=aliases,
|
||||
subtags_ids=subtag_ids,
|
||||
color=color
|
||||
)
|
||||
|
||||
# NOTE: This does NOT use the add_tag_to_library() method!
|
||||
# That method is only used for Tags added at runtime.
|
||||
# This process uses the same inner methods, but waits until all of the
|
||||
# Tags are registered in the Tags list before creating the Tag clusters.
|
||||
self.tags.append(t)
|
||||
self._map_tag_id_to_index(t, -1)
|
||||
self._map_tag_strings_to_tag_id(t)
|
||||
# NOTE: This does NOT use the add_tag_to_library() method!
|
||||
# That method is only used for Tags added at runtime.
|
||||
# This process uses the same inner methods, but waits until all of the
|
||||
# Tags are registered in the Tags list before creating the Tag clusters.
|
||||
self.tags.append(t)
|
||||
self._map_tag_id_to_index(t, -1)
|
||||
self._map_tag_strings_to_tag_id(t)
|
||||
else:
|
||||
logging.info(f'[LIBRARY]Skipping Tag with duplicate ID: {tag}')
|
||||
|
||||
# Step 3: Map each Tag's subtags together now that all Tag objects in it.
|
||||
for t in self.tags:
|
||||
@@ -856,10 +858,10 @@ class Library:
|
||||
}
|
||||
|
||||
print('[LIBRARY] Formatting Tags to JSON...')
|
||||
file_to_save['tags'] = self.verify_default_tags(file_to_save['tags'])
|
||||
for tag in self.tags:
|
||||
file_to_save["tags"].append(tag.compressed_dict())
|
||||
|
||||
|
||||
file_to_save['tags'] = self.verify_default_tags(file_to_save['tags'])
|
||||
print('[LIBRARY] Formatting Entries to JSON...')
|
||||
for entry in self.entries:
|
||||
file_to_save["entries"].append(entry.compressed_dict())
|
||||
|
||||
@@ -4,16 +4,10 @@
|
||||
|
||||
"""The core classes and methods of TagStudio."""
|
||||
|
||||
import os
|
||||
from types import FunctionType
|
||||
# from typing import Dict, Optional, TypedDict, List
|
||||
import json
|
||||
from pathlib import Path
|
||||
import traceback
|
||||
# import requests
|
||||
# from bs4 import BeautifulSoup as bs
|
||||
from src.core.library import *
|
||||
from src.core.field_template import FieldTemplate
|
||||
import os
|
||||
|
||||
from src.core.library import Entry, Library
|
||||
|
||||
VERSION: str = '9.1.0' # Major.Minor.Patch
|
||||
VERSION_BRANCH: str = 'Alpha' # 'Alpha', 'Beta', or '' for Full Release
|
||||
@@ -37,11 +31,11 @@ SPREADSHEET_TYPES: list[str] = ['csv', 'xls', 'xlsx', 'numbers', 'ods']
|
||||
PRESENTATION_TYPES: list[str] = ['ppt', 'pptx', 'key', 'odp']
|
||||
ARCHIVE_TYPES: list[str] = ['zip', 'rar', 'tar', 'tar.gz', 'tgz', '7z']
|
||||
PROGRAM_TYPES: list[str] = ['exe', 'app']
|
||||
SHORTCUT_TYPES: list[str] = ['lnk', 'desktop']
|
||||
SHORTCUT_TYPES: list[str] = ['lnk', 'desktop', 'url']
|
||||
|
||||
ALL_FILE_TYPES: list[str] = IMAGE_TYPES + VIDEO_TYPES + AUDIO_TYPES + \
|
||||
TEXT_TYPES + SPREADSHEET_TYPES + PRESENTATION_TYPES + \
|
||||
ARCHIVE_TYPES + PROGRAM_TYPES
|
||||
ARCHIVE_TYPES + PROGRAM_TYPES + SHORTCUT_TYPES
|
||||
|
||||
BOX_FIELDS = ['tag_box', 'text_box']
|
||||
TEXT_FIELDS = ['text_line', 'text_box']
|
||||
|
||||
@@ -2,9 +2,6 @@
|
||||
# Licensed under the GPL-3.0 License.
|
||||
# Created for TagStudio: https://github.com/CyanVoxel/TagStudio
|
||||
|
||||
import os
|
||||
|
||||
|
||||
def clean_folder_name(folder_name: str) -> str:
|
||||
cleaned_name = folder_name
|
||||
invalid_chars = "<>:\"/\\|?*."
|
||||
|
||||
@@ -4,9 +4,8 @@
|
||||
|
||||
"""PySide6 port of the widgets/layouts/flowlayout example from Qt v6.x"""
|
||||
|
||||
import sys
|
||||
from PySide6.QtCore import Qt, QMargins, QPoint, QRect, QSize
|
||||
from PySide6.QtWidgets import QApplication, QLayout, QPushButton, QSizePolicy, QWidget
|
||||
from PySide6.QtWidgets import QLayout, QSizePolicy, QWidget
|
||||
|
||||
|
||||
# class Window(QWidget):
|
||||
|
||||
@@ -12,23 +12,14 @@
|
||||
# Licensed under the GPL-3.0 License.
|
||||
# Created for TagStudio: https://github.com/CyanVoxel/TagStudio
|
||||
|
||||
from re import S
|
||||
import time
|
||||
from typing import Optional
|
||||
from PySide6.QtCore import (QCoreApplication, QDate, QDateTime, QLocale,
|
||||
QMetaObject, QObject, QPoint, QRect,
|
||||
QSize, QTime, QUrl, Qt)
|
||||
from PySide6.QtGui import (QBrush, QColor, QConicalGradient, QCursor,
|
||||
QFont, QFontDatabase, QGradient, QIcon,
|
||||
QImage, QKeySequence, QLinearGradient, QPainter,
|
||||
QPalette, QPixmap, QRadialGradient, QTransform, QAction)
|
||||
from PySide6.QtWidgets import (QApplication, QComboBox, QFrame, QGridLayout,
|
||||
from PySide6.QtCore import (QCoreApplication, QMetaObject, QRect,
|
||||
QSize, Qt)
|
||||
from PySide6.QtGui import (QFont, QAction)
|
||||
from PySide6.QtWidgets import (QComboBox, QFrame, QGridLayout,
|
||||
QHBoxLayout, QVBoxLayout, QLayout, QLineEdit, QMainWindow,
|
||||
QMenuBar, QPushButton, QScrollArea, QSizePolicy,
|
||||
QStatusBar, QWidget, QSplitter, QMenu)
|
||||
from src.qt.pagination import Pagination
|
||||
# from src.qt.qtacrylic.qtacrylic import WindowEffect
|
||||
# from qframelesswindow import FramelessMainWindow, StandardTitleBar
|
||||
|
||||
|
||||
class Ui_MainWindow(QMainWindow):
|
||||
|
||||
@@ -5,10 +5,10 @@
|
||||
"""A pagination widget created for TagStudio."""
|
||||
# I never want to see this code again.
|
||||
|
||||
from PySide6 import QtCore
|
||||
from PySide6.QtGui import *
|
||||
from PySide6.QtWidgets import *
|
||||
from PySide6.QtCore import QFile, QObject, QThread, Signal, QRunnable, Qt, QThreadPool, QSize, QEvent, QMimeData
|
||||
from PySide6.QtCore import QObject, Signal, QSize
|
||||
from PySide6.QtGui import QIntValidator
|
||||
from PySide6.QtWidgets import QWidget, QHBoxLayout, QPushButton, QLabel, QLineEdit, QSizePolicy
|
||||
|
||||
|
||||
# class NumberEdit(QLineEdit):
|
||||
# def __init__(self, parent=None) -> None:
|
||||
|
||||
@@ -7,40 +7,46 @@
|
||||
|
||||
"""A Qt driver for TagStudio."""
|
||||
|
||||
from copy import copy, deepcopy
|
||||
import ctypes
|
||||
import math
|
||||
from os import times
|
||||
import sys
|
||||
import logging
|
||||
import threading
|
||||
from time import sleep
|
||||
from queue import Empty, Queue
|
||||
import math
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
from typing import Optional, Union
|
||||
from PySide6 import QtCore
|
||||
import PySide6
|
||||
from PySide6.QtGui import *
|
||||
from PySide6.QtWidgets import *
|
||||
from PySide6.QtCore import QFile, QObject, QThread, Signal, QRunnable, Qt, QThreadPool, QSize, QEvent, QMimeData, QTimer
|
||||
from PySide6.QtUiTools import QUiLoader
|
||||
from PIL import Image, ImageOps, ImageChops, UnidentifiedImageError, ImageQt, ImageDraw, ImageFont, ImageEnhance
|
||||
import PySide6.QtWidgets
|
||||
import humanfriendly
|
||||
import pillow_avif
|
||||
import cv2
|
||||
import traceback
|
||||
import shutil
|
||||
import subprocess
|
||||
from types import FunctionType
|
||||
from datetime import datetime as dt
|
||||
from src.core.ts_core import *
|
||||
# from src.core.utils.web import *
|
||||
# from src.core.utils.fs import *
|
||||
from src.core.library import *
|
||||
from pathlib import Path
|
||||
from queue import Empty, Queue
|
||||
from time import sleep
|
||||
from typing import Optional
|
||||
|
||||
import cv2
|
||||
from PIL import Image, ImageChops, UnidentifiedImageError, ImageQt, ImageDraw, ImageFont, ImageEnhance
|
||||
from PySide6 import QtCore
|
||||
from PySide6.QtCore import QObject, QThread, Signal, QRunnable, Qt, QThreadPool, QSize, QEvent, QTimer
|
||||
from PySide6.QtGui import (QGuiApplication, QPixmap, QEnterEvent, QMouseEvent, QResizeEvent, QPainter, QColor, QPen,
|
||||
QAction, QStandardItemModel, QStandardItem, QPainterPath, QFontDatabase, QIcon)
|
||||
from PySide6.QtUiTools import QUiLoader
|
||||
from PySide6.QtWidgets import (QApplication, QWidget, QVBoxLayout, QHBoxLayout, QLabel, QPushButton, QPlainTextEdit,
|
||||
QLineEdit, QScrollArea, QFrame, QTextEdit, QComboBox, QProgressDialog, QFileDialog,
|
||||
QListView, QSplitter, QSizePolicy, QMessageBox, QBoxLayout, QCheckBox, QSplashScreen,
|
||||
QMenu)
|
||||
from humanfriendly import format_timespan, format_size
|
||||
|
||||
from src.core.library import Collation, Entry, ItemType, Library, Tag
|
||||
from src.core.palette import ColorType, get_tag_color
|
||||
from src.core.ts_core import (TagStudioCore, TAG_COLORS, DATE_FIELDS, TEXT_FIELDS, BOX_FIELDS, ALL_FILE_TYPES,
|
||||
SHORTCUT_TYPES, PROGRAM_TYPES, ARCHIVE_TYPES, PRESENTATION_TYPES,
|
||||
SPREADSHEET_TYPES, TEXT_TYPES, AUDIO_TYPES, VIDEO_TYPES, IMAGE_TYPES,
|
||||
LIBRARY_FILENAME, COLLAGE_FOLDER_NAME, BACKUP_FOLDER_NAME, TS_FOLDER_NAME,
|
||||
VERSION_BRANCH, VERSION)
|
||||
from src.core.utils.web import strip_web_protocol
|
||||
from src.qt.flowlayout import FlowLayout, FlowWidget
|
||||
from src.qt.main_window import Ui_MainWindow
|
||||
import src.qt.resources_rc
|
||||
# from typing_extensions import deprecated
|
||||
from humanfriendly import format_timespan
|
||||
# from src.qt.qtacrylic.qtacrylic import WindowEffect
|
||||
|
||||
# SIGQUIT is not defined on Windows
|
||||
if sys.platform == "win32":
|
||||
@@ -56,11 +62,20 @@ INFO = f'[INFO]'
|
||||
logging.basicConfig(format="%(message)s", level=logging.INFO)
|
||||
|
||||
|
||||
def open_file(path):
|
||||
def open_file(path: str):
|
||||
try:
|
||||
os.startfile(path)
|
||||
except FileNotFoundError:
|
||||
logging.info('File Not Found! (Imagine this as a popup)')
|
||||
if sys.platform == "win32":
|
||||
subprocess.Popen(["start", path], shell=True, close_fds=True, creationflags=subprocess.DETACHED_PROCESS)
|
||||
else:
|
||||
if sys.platform == "darwin":
|
||||
command_name = "open"
|
||||
else:
|
||||
command_name = "xdg-open"
|
||||
command = shutil.which(command_name)
|
||||
if command is not None:
|
||||
subprocess.Popen([command, path], close_fds=True)
|
||||
else:
|
||||
logging.info(f"Could not find {command_name} on system PATH")
|
||||
except:
|
||||
traceback.print_exc()
|
||||
|
||||
@@ -2131,12 +2146,12 @@ class PreviewPanel(QWidget):
|
||||
|
||||
# Stats for specific file types are displayed here.
|
||||
if extension in (IMAGE_TYPES + VIDEO_TYPES):
|
||||
self.dimensions_label.setText(f"{extension.upper()} • {humanfriendly.format_size(os.stat(filepath).st_size)}\n{image.width} x {image.height} px")
|
||||
self.dimensions_label.setText(f"{extension.upper()} • {format_size(os.stat(filepath).st_size)}\n{image.width} x {image.height} px")
|
||||
else:
|
||||
self.dimensions_label.setText(f"{extension.upper()}")
|
||||
|
||||
if not image:
|
||||
self.dimensions_label.setText(f"{extension.upper()} • {humanfriendly.format_size(os.stat(filepath).st_size)}")
|
||||
self.dimensions_label.setText(f"{extension.upper()} • {format_size(os.stat(filepath).st_size)}")
|
||||
raise UnidentifiedImageError
|
||||
|
||||
except (UnidentifiedImageError, FileNotFoundError, cv2.error):
|
||||
@@ -4534,7 +4549,7 @@ class QtDriver(QObject):
|
||||
self.completed += 1
|
||||
# logging.info(f'threshold:{len(self.lib.entries}, completed:{self.completed}')
|
||||
if self.completed == len(self.lib.entries):
|
||||
filename = os.path.normpath(f'{self.lib.library_dir}/{TS_FOLDER_NAME}/{COLLAGE_FOLDER_NAME}/collage_{datetime.datetime.utcnow().strftime("%F_%T").replace(":", "")}.png')
|
||||
filename = os.path.normpath(f'{self.lib.library_dir}/{TS_FOLDER_NAME}/{COLLAGE_FOLDER_NAME}/collage_{dt.utcnow().strftime("%F_%T").replace(":", "")}.png')
|
||||
self.collage.save(filename)
|
||||
self.collage = None
|
||||
|
||||
|
||||
@@ -9,7 +9,6 @@ from src.cli.ts_cli import CliDriver
|
||||
from src.qt.ts_qt import QtDriver
|
||||
import argparse
|
||||
import traceback
|
||||
# import ctypes
|
||||
|
||||
|
||||
def main():
|
||||
Reference in New Issue
Block a user