sview 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- sview-0.1.0/PKG-INFO +55 -0
- sview-0.1.0/README.md +44 -0
- sview-0.1.0/pyproject.toml +26 -0
- sview-0.1.0/setup.cfg +4 -0
- sview-0.1.0/sview/__init__.py +38 -0
- sview-0.1.0/sview/config.py +130 -0
- sview-0.1.0/sview/controller.py +113 -0
- sview-0.1.0/sview/model.py +64 -0
- sview-0.1.0/sview/qt/__init__.py +34 -0
- sview-0.1.0/sview/qt/app.py +182 -0
- sview-0.1.0/sview/qt/inspector.py +197 -0
- sview-0.1.0/sview/qt/main_window.py +840 -0
- sview-0.1.0/sview/qt/table.py +210 -0
- sview-0.1.0/sview/qt/tree.py +73 -0
- sview-0.1.0/sview/scanner.py +419 -0
- sview-0.1.0/sview.egg-info/PKG-INFO +55 -0
- sview-0.1.0/sview.egg-info/SOURCES.txt +20 -0
- sview-0.1.0/sview.egg-info/dependency_links.txt +1 -0
- sview-0.1.0/sview.egg-info/entry_points.txt +2 -0
- sview-0.1.0/sview.egg-info/requires.txt +4 -0
- sview-0.1.0/sview.egg-info/top_level.txt +1 -0
- sview-0.1.0/tests/test_scanner.py +87 -0
sview-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: sview
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Sequence-aware filesystem browser
|
|
5
|
+
Project-URL: Repository, https://github.com/rsgalloway/sview
|
|
6
|
+
Requires-Python: >=3.8
|
|
7
|
+
Description-Content-Type: text/markdown
|
|
8
|
+
Requires-Dist: PySide6>=6.6
|
|
9
|
+
Requires-Dist: pyseq
|
|
10
|
+
Provides-Extra: dev
|
|
11
|
+
|
|
12
|
+
# sview
|
|
13
|
+
|
|
14
|
+
`sview` is a sequence-aware filesystem browser built with PySide6 and `pyseq`.
|
|
15
|
+
It provides a sequence-first view of directories so image sequences can be
|
|
16
|
+
browsed as logical units instead of individual files.
|
|
17
|
+
|
|
18
|
+

|
|
19
|
+
|
|
20
|
+
## Install
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
pip install -U sview
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Run
|
|
27
|
+
|
|
28
|
+
Launch the app:
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
sview
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Open a specific folder:
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
sview /mnt/projects/
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Print the version:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
sview --version
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Configuration
|
|
47
|
+
|
|
48
|
+
On first launch, `sview` writes a user config file to:
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
~/.config/sview/config.json
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
This file controls default file and sequence handlers, including custom
|
|
55
|
+
commands for double-click actions.
|
sview-0.1.0/README.md
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# sview
|
|
2
|
+
|
|
3
|
+
`sview` is a sequence-aware filesystem browser built with PySide6 and `pyseq`.
|
|
4
|
+
It provides a sequence-first view of directories so image sequences can be
|
|
5
|
+
browsed as logical units instead of individual files.
|
|
6
|
+
|
|
7
|
+

|
|
8
|
+
|
|
9
|
+
## Install
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
pip install -U sview
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Run
|
|
16
|
+
|
|
17
|
+
Launch the app:
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
sview
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Open a specific folder:
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
sview /mnt/projects/
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Print the version:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
sview --version
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Configuration
|
|
36
|
+
|
|
37
|
+
On first launch, `sview` writes a user config file to:
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
~/.config/sview/config.json
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
This file controls default file and sequence handlers, including custom
|
|
44
|
+
commands for double-click actions.
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "sview"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Sequence-aware filesystem browser"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.8"
|
|
11
|
+
dependencies = [
|
|
12
|
+
"PySide6>=6.6",
|
|
13
|
+
"pyseq",
|
|
14
|
+
]
|
|
15
|
+
|
|
16
|
+
[project.urls]
|
|
17
|
+
Repository = "https://github.com/rsgalloway/sview"
|
|
18
|
+
|
|
19
|
+
[project.optional-dependencies]
|
|
20
|
+
dev = []
|
|
21
|
+
|
|
22
|
+
[project.scripts]
|
|
23
|
+
sview = "sview.qt.app:main"
|
|
24
|
+
|
|
25
|
+
[tool.setuptools]
|
|
26
|
+
packages = ["sview", "sview.qt"]
|
sview-0.1.0/setup.cfg
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
#
|
|
3
|
+
# Copyright (c) 2026, Ryan Galloway (ryan@rsgalloway.com)
|
|
4
|
+
#
|
|
5
|
+
# Redistribution and use in source and binary forms, with or without
|
|
6
|
+
# modification, are permitted provided that the following conditions are met:
|
|
7
|
+
#
|
|
8
|
+
# - Redistributions of source code must retain the above copyright notice,
|
|
9
|
+
# this list of conditions and the following disclaimer.
|
|
10
|
+
#
|
|
11
|
+
# - Redistributions in binary form must reproduce the above copyright notice,
|
|
12
|
+
# this list of conditions and the following disclaimer in the documentation
|
|
13
|
+
# and/or other materials provided with the distribution.
|
|
14
|
+
#
|
|
15
|
+
# - Neither the name of the software nor the names of its contributors
|
|
16
|
+
# may be used to endorse or promote products derived from this software
|
|
17
|
+
# without specific prior written permission.
|
|
18
|
+
#
|
|
19
|
+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
20
|
+
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
21
|
+
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
22
|
+
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
|
23
|
+
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
24
|
+
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
25
|
+
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
26
|
+
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
27
|
+
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
28
|
+
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
29
|
+
# POSSIBILITY OF SUCH DAMAGE.
|
|
30
|
+
# -----------------------------------------------------------------------------
|
|
31
|
+
|
|
32
|
+
"""
|
|
33
|
+
sview is a simple sequence viewer for quickly inspecting the contents of a directory
|
|
34
|
+
containing image sequences. It is designed to be fast and lightweight, with a focus
|
|
35
|
+
on usability and a clean interface.
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
__version__ = "0.1.0"
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
#
|
|
3
|
+
# Copyright (c) 2026, Ryan Galloway (ryan@rsgalloway.com)
|
|
4
|
+
#
|
|
5
|
+
# Redistribution and use in source and binary forms, with or without
|
|
6
|
+
# modification, are permitted provided that the following conditions are met:
|
|
7
|
+
#
|
|
8
|
+
# - Redistributions of source code must retain the above copyright notice,
|
|
9
|
+
# this list of conditions and the following disclaimer.
|
|
10
|
+
#
|
|
11
|
+
# - Redistributions in binary form must reproduce the above copyright notice,
|
|
12
|
+
# this list of conditions and the following disclaimer in the documentation
|
|
13
|
+
# and/or other materials provided with the distribution.
|
|
14
|
+
#
|
|
15
|
+
# - Neither the name of the software nor the names of its contributors
|
|
16
|
+
# may be used to endorse or promote products derived from this software
|
|
17
|
+
# without specific prior written permission.
|
|
18
|
+
#
|
|
19
|
+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
20
|
+
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
21
|
+
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
22
|
+
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
|
23
|
+
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
24
|
+
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
25
|
+
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
26
|
+
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
27
|
+
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
28
|
+
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
29
|
+
# POSSIBILITY OF SUCH DAMAGE.
|
|
30
|
+
# -----------------------------------------------------------------------------
|
|
31
|
+
|
|
32
|
+
"""
|
|
33
|
+
Contains configuration management for sview, including loading and saving user preferences.
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
from __future__ import annotations
|
|
37
|
+
|
|
38
|
+
from importlib.metadata import PackageNotFoundError, metadata
|
|
39
|
+
import json
|
|
40
|
+
import shlex
|
|
41
|
+
from dataclasses import dataclass
|
|
42
|
+
from pathlib import Path
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
CONFIG_DIR = Path.home() / ".config" / "sview"
|
|
46
|
+
CONFIG_PATH = CONFIG_DIR / "config.json"
|
|
47
|
+
DEFAULT_REPOSITORY_URL = "https://github.com/rsgalloway/sview"
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
@dataclass
|
|
51
|
+
class HandlerConfig:
|
|
52
|
+
mode: str
|
|
53
|
+
command: str = ""
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
@dataclass
|
|
57
|
+
class AppConfig:
|
|
58
|
+
file_handler: HandlerConfig
|
|
59
|
+
sequence_handler: HandlerConfig
|
|
60
|
+
|
|
61
|
+
@classmethod
|
|
62
|
+
def default(cls) -> "AppConfig":
|
|
63
|
+
return cls(
|
|
64
|
+
file_handler=HandlerConfig(mode="system"),
|
|
65
|
+
sequence_handler=HandlerConfig(mode="expand"),
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
@classmethod
|
|
69
|
+
def load(cls) -> "AppConfig":
|
|
70
|
+
if not CONFIG_PATH.exists():
|
|
71
|
+
config = cls.default()
|
|
72
|
+
config.save()
|
|
73
|
+
return config
|
|
74
|
+
|
|
75
|
+
try:
|
|
76
|
+
data = json.loads(CONFIG_PATH.read_text(encoding="utf-8"))
|
|
77
|
+
except (OSError, json.JSONDecodeError):
|
|
78
|
+
config = cls.default()
|
|
79
|
+
config.save()
|
|
80
|
+
return config
|
|
81
|
+
|
|
82
|
+
handlers = data.get("handlers", {})
|
|
83
|
+
file_handler = handlers.get("file", {})
|
|
84
|
+
sequence_handler = handlers.get("sequence", {})
|
|
85
|
+
return cls(
|
|
86
|
+
file_handler=HandlerConfig(
|
|
87
|
+
mode=file_handler.get("mode", "system"),
|
|
88
|
+
command=file_handler.get("command", ""),
|
|
89
|
+
),
|
|
90
|
+
sequence_handler=HandlerConfig(
|
|
91
|
+
mode=sequence_handler.get("mode", "expand"),
|
|
92
|
+
command=sequence_handler.get("command", ""),
|
|
93
|
+
),
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
def save(self) -> None:
|
|
97
|
+
CONFIG_DIR.mkdir(parents=True, exist_ok=True)
|
|
98
|
+
payload = {
|
|
99
|
+
"handlers": {
|
|
100
|
+
"file": {
|
|
101
|
+
"mode": self.file_handler.mode,
|
|
102
|
+
"command": self.file_handler.command,
|
|
103
|
+
},
|
|
104
|
+
"sequence": {
|
|
105
|
+
"mode": self.sequence_handler.mode,
|
|
106
|
+
"command": self.sequence_handler.command,
|
|
107
|
+
},
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
CONFIG_PATH.write_text(json.dumps(payload, indent=2) + "\n", encoding="utf-8")
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def build_command(command_template: str, path: str) -> list[str]:
|
|
114
|
+
return shlex.split(command_template.format(path=path))
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def get_repository_url() -> str:
|
|
118
|
+
try:
|
|
119
|
+
project_metadata = metadata("sview")
|
|
120
|
+
except PackageNotFoundError:
|
|
121
|
+
return DEFAULT_REPOSITORY_URL
|
|
122
|
+
|
|
123
|
+
for key, value in project_metadata.items():
|
|
124
|
+
if key != "Project-URL":
|
|
125
|
+
continue
|
|
126
|
+
label, _, url = value.partition(",")
|
|
127
|
+
if label.strip().lower() == "repository" and url.strip():
|
|
128
|
+
return url.strip()
|
|
129
|
+
|
|
130
|
+
return DEFAULT_REPOSITORY_URL
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
#
|
|
3
|
+
# Copyright (c) 2026, Ryan Galloway (ryan@rsgalloway.com)
|
|
4
|
+
#
|
|
5
|
+
# Redistribution and use in source and binary forms, with or without
|
|
6
|
+
# modification, are permitted provided that the following conditions are met:
|
|
7
|
+
#
|
|
8
|
+
# - Redistributions of source code must retain the above copyright notice,
|
|
9
|
+
# this list of conditions and the following disclaimer.
|
|
10
|
+
#
|
|
11
|
+
# - Redistributions in binary form must reproduce the above copyright notice,
|
|
12
|
+
# this list of conditions and the following disclaimer in the documentation
|
|
13
|
+
# and/or other materials provided with the distribution.
|
|
14
|
+
#
|
|
15
|
+
# - Neither the name of the software nor the names of its contributors
|
|
16
|
+
# may be used to endorse or promote products derived from this software
|
|
17
|
+
# without specific prior written permission.
|
|
18
|
+
#
|
|
19
|
+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
20
|
+
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
21
|
+
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
22
|
+
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
|
23
|
+
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
24
|
+
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
25
|
+
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
26
|
+
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
27
|
+
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
28
|
+
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
29
|
+
# POSSIBILITY OF SUCH DAMAGE.
|
|
30
|
+
# -----------------------------------------------------------------------------
|
|
31
|
+
|
|
32
|
+
"""
|
|
33
|
+
Contains the BrowserController class, which serves as a thin service layer between
|
|
34
|
+
the UI and the Directory Scanner. It manages the current state of the browser,
|
|
35
|
+
including the current path, items, and view mode.
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
from __future__ import annotations
|
|
39
|
+
|
|
40
|
+
from pathlib import Path
|
|
41
|
+
|
|
42
|
+
from sview.model import BrowserItem, ItemType
|
|
43
|
+
from sview.scanner import DirectoryScanner, ScanResult
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class BrowserController:
|
|
47
|
+
"""Thin service layer between the UI and scanner."""
|
|
48
|
+
|
|
49
|
+
def __init__(self, scanner: DirectoryScanner | None = None) -> None:
|
|
50
|
+
self._scanner = scanner or DirectoryScanner()
|
|
51
|
+
self._current_path = Path.home()
|
|
52
|
+
self._current_items: list[BrowserItem] = []
|
|
53
|
+
self._raw_items: list[BrowserItem] = []
|
|
54
|
+
self._grouped_items: list[BrowserItem] = []
|
|
55
|
+
self._grouped_view = True
|
|
56
|
+
self._expanded_sequence: BrowserItem | None = None
|
|
57
|
+
|
|
58
|
+
@property
|
|
59
|
+
def current_path(self) -> Path:
|
|
60
|
+
return self._current_path
|
|
61
|
+
|
|
62
|
+
@property
|
|
63
|
+
def current_items(self) -> list[BrowserItem]:
|
|
64
|
+
return list(self._current_items)
|
|
65
|
+
|
|
66
|
+
@property
|
|
67
|
+
def grouped_view(self) -> bool:
|
|
68
|
+
return self._grouped_view
|
|
69
|
+
|
|
70
|
+
@property
|
|
71
|
+
def expanded_sequence(self) -> BrowserItem | None:
|
|
72
|
+
return self._expanded_sequence
|
|
73
|
+
|
|
74
|
+
def set_grouped_view(self, enabled: bool) -> None:
|
|
75
|
+
self._grouped_view = enabled
|
|
76
|
+
self._expanded_sequence = None
|
|
77
|
+
self._refresh_current_items()
|
|
78
|
+
|
|
79
|
+
def expand_sequence(self, item: BrowserItem) -> list[BrowserItem]:
|
|
80
|
+
if item.item_type is not ItemType.SEQUENCE or not item.child_paths:
|
|
81
|
+
return self.current_items
|
|
82
|
+
|
|
83
|
+
child_paths = set(item.child_paths)
|
|
84
|
+
self._expanded_sequence = item
|
|
85
|
+
self._current_items = [
|
|
86
|
+
raw_item for raw_item in self._raw_items if raw_item.path in child_paths
|
|
87
|
+
]
|
|
88
|
+
return self.current_items
|
|
89
|
+
|
|
90
|
+
def collapse_sequence(self) -> list[BrowserItem]:
|
|
91
|
+
self._expanded_sequence = None
|
|
92
|
+
self._refresh_current_items()
|
|
93
|
+
return self.current_items
|
|
94
|
+
|
|
95
|
+
def load_path(self, path: str | Path) -> list[BrowserItem]:
|
|
96
|
+
result = self.scan_path(path)
|
|
97
|
+
return self.apply_scan_result(result)
|
|
98
|
+
|
|
99
|
+
def scan_path(self, path: str | Path, **scan_kwargs) -> ScanResult:
|
|
100
|
+
return self._scanner.scan(path, **scan_kwargs)
|
|
101
|
+
|
|
102
|
+
def apply_scan_result(self, result: ScanResult) -> list[BrowserItem]:
|
|
103
|
+
self._current_path = result.path
|
|
104
|
+
self._grouped_items = result.grouped_items
|
|
105
|
+
self._raw_items = result.raw_items
|
|
106
|
+
self._expanded_sequence = None
|
|
107
|
+
self._refresh_current_items()
|
|
108
|
+
return self.current_items
|
|
109
|
+
|
|
110
|
+
def _refresh_current_items(self) -> None:
|
|
111
|
+
self._current_items = (
|
|
112
|
+
self._grouped_items if self._grouped_view else self._raw_items
|
|
113
|
+
)
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
#
|
|
3
|
+
# Copyright (c) 2026, Ryan Galloway (ryan@rsgalloway.com)
|
|
4
|
+
#
|
|
5
|
+
# Redistribution and use in source and binary forms, with or without
|
|
6
|
+
# modification, are permitted provided that the following conditions are met:
|
|
7
|
+
#
|
|
8
|
+
# - Redistributions of source code must retain the above copyright notice,
|
|
9
|
+
# this list of conditions and the following disclaimer.
|
|
10
|
+
#
|
|
11
|
+
# - Redistributions in binary form must reproduce the above copyright notice,
|
|
12
|
+
# this list of conditions and the following disclaimer in the documentation
|
|
13
|
+
# and/or other materials provided with the distribution.
|
|
14
|
+
#
|
|
15
|
+
# - Neither the name of the software nor the names of its contributors
|
|
16
|
+
# may be used to endorse or promote products derived from this software
|
|
17
|
+
# without specific prior written permission.
|
|
18
|
+
#
|
|
19
|
+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
20
|
+
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
21
|
+
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
22
|
+
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
|
23
|
+
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
24
|
+
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
25
|
+
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
26
|
+
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
27
|
+
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
28
|
+
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
29
|
+
# POSSIBILITY OF SUCH DAMAGE.
|
|
30
|
+
# -----------------------------------------------------------------------------
|
|
31
|
+
|
|
32
|
+
"""
|
|
33
|
+
Contains data models for representing items in the file browser and user configuration.
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
from __future__ import annotations
|
|
37
|
+
|
|
38
|
+
from dataclasses import dataclass
|
|
39
|
+
from enum import Enum
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class ItemType(str, Enum):
|
|
43
|
+
DIRECTORY = "directory"
|
|
44
|
+
SEQUENCE = "sequence"
|
|
45
|
+
FILE = "file"
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
@dataclass
|
|
49
|
+
class BrowserItem:
|
|
50
|
+
path: str
|
|
51
|
+
item_type: ItemType
|
|
52
|
+
name: str
|
|
53
|
+
display_name: str
|
|
54
|
+
frame_range: str | None
|
|
55
|
+
pad: str | None
|
|
56
|
+
count: int
|
|
57
|
+
missing: list[int] | None
|
|
58
|
+
size_bytes: int
|
|
59
|
+
modified_time: float
|
|
60
|
+
child_paths: list[str] | None = None
|
|
61
|
+
|
|
62
|
+
@property
|
|
63
|
+
def missing_count(self) -> int:
|
|
64
|
+
return len(self.missing or [])
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
#
|
|
3
|
+
# Copyright (c) 2026, Ryan Galloway (ryan@rsgalloway.com)
|
|
4
|
+
#
|
|
5
|
+
# Redistribution and use in source and binary forms, with or without
|
|
6
|
+
# modification, are permitted provided that the following conditions are met:
|
|
7
|
+
#
|
|
8
|
+
# - Redistributions of source code must retain the above copyright notice,
|
|
9
|
+
# this list of conditions and the following disclaimer.
|
|
10
|
+
#
|
|
11
|
+
# - Redistributions in binary form must reproduce the above copyright notice,
|
|
12
|
+
# this list of conditions and the following disclaimer in the documentation
|
|
13
|
+
# and/or other materials provided with the distribution.
|
|
14
|
+
#
|
|
15
|
+
# - Neither the name of the software nor the names of its contributors
|
|
16
|
+
# may be used to endorse or promote products derived from this software
|
|
17
|
+
# without specific prior written permission.
|
|
18
|
+
#
|
|
19
|
+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
20
|
+
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
21
|
+
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
22
|
+
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
|
23
|
+
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
24
|
+
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
25
|
+
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
26
|
+
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
27
|
+
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
28
|
+
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
29
|
+
# POSSIBILITY OF SUCH DAMAGE.
|
|
30
|
+
# -----------------------------------------------------------------------------
|
|
31
|
+
|
|
32
|
+
"""
|
|
33
|
+
Contains Qt UI package for sview.
|
|
34
|
+
"""
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
#
|
|
3
|
+
# Copyright (c) 2026, Ryan Galloway (ryan@rsgalloway.com)
|
|
4
|
+
#
|
|
5
|
+
# Redistribution and use in source and binary forms, with or without
|
|
6
|
+
# modification, are permitted provided that the following conditions are met:
|
|
7
|
+
#
|
|
8
|
+
# - Redistributions of source code must retain the above copyright notice,
|
|
9
|
+
# this list of conditions and the following disclaimer.
|
|
10
|
+
#
|
|
11
|
+
# - Redistributions in binary form must reproduce the above copyright notice,
|
|
12
|
+
# this list of conditions and the following disclaimer in the documentation
|
|
13
|
+
# and/or other materials provided with the distribution.
|
|
14
|
+
#
|
|
15
|
+
# - Neither the name of the software nor the names of its contributors
|
|
16
|
+
# may be used to endorse or promote products derived from this software
|
|
17
|
+
# without specific prior written permission.
|
|
18
|
+
#
|
|
19
|
+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
20
|
+
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
21
|
+
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
22
|
+
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
|
23
|
+
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
24
|
+
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
25
|
+
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
26
|
+
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
27
|
+
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
28
|
+
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
29
|
+
# POSSIBILITY OF SUCH DAMAGE.
|
|
30
|
+
# -----------------------------------------------------------------------------
|
|
31
|
+
|
|
32
|
+
"""
|
|
33
|
+
Contains the main application entry point for sview.
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
from __future__ import annotations
|
|
37
|
+
|
|
38
|
+
import sys
|
|
39
|
+
from pathlib import Path
|
|
40
|
+
|
|
41
|
+
from PySide6.QtGui import QColor, QPalette
|
|
42
|
+
from PySide6.QtWidgets import QApplication
|
|
43
|
+
|
|
44
|
+
from sview import __version__
|
|
45
|
+
from sview.qt.main_window import MainWindow
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def main() -> int:
|
|
49
|
+
if _wants_version(sys.argv[1:]):
|
|
50
|
+
print(__version__)
|
|
51
|
+
return 0
|
|
52
|
+
|
|
53
|
+
app = QApplication(sys.argv)
|
|
54
|
+
app.setApplicationName("sview")
|
|
55
|
+
app.setApplicationVersion(__version__)
|
|
56
|
+
app.setStyle("Fusion")
|
|
57
|
+
_apply_dark_theme(app)
|
|
58
|
+
initial_path = _parse_initial_path(sys.argv[1:])
|
|
59
|
+
window = MainWindow(initial_path=initial_path)
|
|
60
|
+
window.show()
|
|
61
|
+
return app.exec()
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def _parse_initial_path(args: list[str]) -> str | None:
|
|
65
|
+
filtered_args = [arg for arg in args if arg not in {"--version", "-V"}]
|
|
66
|
+
if not filtered_args:
|
|
67
|
+
return None
|
|
68
|
+
candidate = Path(filtered_args[0]).expanduser()
|
|
69
|
+
return str(candidate)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def _wants_version(args: list[str]) -> bool:
|
|
73
|
+
return any(arg in {"--version", "-V"} for arg in args)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def _apply_dark_theme(app: QApplication) -> None:
|
|
77
|
+
palette = QPalette()
|
|
78
|
+
palette.setColor(QPalette.ColorRole.Window, QColor("#232528"))
|
|
79
|
+
palette.setColor(QPalette.ColorRole.WindowText, QColor("#e4e6e8"))
|
|
80
|
+
palette.setColor(QPalette.ColorRole.Base, QColor("#191b1e"))
|
|
81
|
+
palette.setColor(QPalette.ColorRole.AlternateBase, QColor("#202327"))
|
|
82
|
+
palette.setColor(QPalette.ColorRole.ToolTipBase, QColor("#191b1e"))
|
|
83
|
+
palette.setColor(QPalette.ColorRole.ToolTipText, QColor("#f2f3f5"))
|
|
84
|
+
palette.setColor(QPalette.ColorRole.Text, QColor("#e4e6e8"))
|
|
85
|
+
palette.setColor(QPalette.ColorRole.Button, QColor("#2c2f34"))
|
|
86
|
+
palette.setColor(QPalette.ColorRole.ButtonText, QColor("#edf0f2"))
|
|
87
|
+
palette.setColor(QPalette.ColorRole.Highlight, QColor("#454b53"))
|
|
88
|
+
palette.setColor(QPalette.ColorRole.HighlightedText, QColor("#ffffff"))
|
|
89
|
+
palette.setColor(QPalette.ColorRole.PlaceholderText, QColor("#808790"))
|
|
90
|
+
app.setPalette(palette)
|
|
91
|
+
app.setStyleSheet(
|
|
92
|
+
"""
|
|
93
|
+
QWidget {
|
|
94
|
+
font-size: 12px;
|
|
95
|
+
}
|
|
96
|
+
QMainWindow, QTreeView, QTableWidget, QLineEdit, QPushButton, QLabel {
|
|
97
|
+
background-color: #25282c;
|
|
98
|
+
color: #e4e6e8;
|
|
99
|
+
}
|
|
100
|
+
QHeaderView::section {
|
|
101
|
+
background-color: #2d3136;
|
|
102
|
+
color: #d9dde1;
|
|
103
|
+
padding: 4px 6px;
|
|
104
|
+
border: 0;
|
|
105
|
+
border-right: 1px solid #3d4248;
|
|
106
|
+
}
|
|
107
|
+
QTreeView, QTableWidget, QLineEdit {
|
|
108
|
+
background-color: #1d2024;
|
|
109
|
+
border: 1px solid #30343a;
|
|
110
|
+
border-radius: 3px;
|
|
111
|
+
}
|
|
112
|
+
QScrollBar:vertical {
|
|
113
|
+
background: #1b1e22;
|
|
114
|
+
width: 10px;
|
|
115
|
+
margin: 2px;
|
|
116
|
+
}
|
|
117
|
+
QScrollBar::handle:vertical {
|
|
118
|
+
background: #474c53;
|
|
119
|
+
min-height: 28px;
|
|
120
|
+
border-radius: 4px;
|
|
121
|
+
}
|
|
122
|
+
QScrollBar::handle:vertical:hover {
|
|
123
|
+
background: #5a6068;
|
|
124
|
+
}
|
|
125
|
+
QScrollBar:horizontal {
|
|
126
|
+
background: #1b1e22;
|
|
127
|
+
height: 10px;
|
|
128
|
+
margin: 2px;
|
|
129
|
+
}
|
|
130
|
+
QScrollBar::handle:horizontal {
|
|
131
|
+
background: #474c53;
|
|
132
|
+
min-width: 28px;
|
|
133
|
+
border-radius: 4px;
|
|
134
|
+
}
|
|
135
|
+
QScrollBar::handle:horizontal:hover {
|
|
136
|
+
background: #5a6068;
|
|
137
|
+
}
|
|
138
|
+
QScrollBar::add-line, QScrollBar::sub-line, QScrollBar::add-page, QScrollBar::sub-page {
|
|
139
|
+
background: transparent;
|
|
140
|
+
border: none;
|
|
141
|
+
}
|
|
142
|
+
QTableWidget::item:selected, QTreeView::item:selected {
|
|
143
|
+
background-color: #40454d;
|
|
144
|
+
color: white;
|
|
145
|
+
}
|
|
146
|
+
QPushButton {
|
|
147
|
+
background-color: #2f3338;
|
|
148
|
+
border: 1px solid #393e45;
|
|
149
|
+
border-radius: 3px;
|
|
150
|
+
padding: 4px 9px;
|
|
151
|
+
min-height: 24px;
|
|
152
|
+
}
|
|
153
|
+
QPushButton:checked {
|
|
154
|
+
background-color: #3b4047;
|
|
155
|
+
border-color: #474d55;
|
|
156
|
+
}
|
|
157
|
+
QPushButton:disabled {
|
|
158
|
+
color: #727881;
|
|
159
|
+
background-color: #272b30;
|
|
160
|
+
}
|
|
161
|
+
QStatusBar {
|
|
162
|
+
background-color: #1d2024;
|
|
163
|
+
color: #bfc4ca;
|
|
164
|
+
}
|
|
165
|
+
QSplitter::handle {
|
|
166
|
+
background-color: #4a4f56;
|
|
167
|
+
width: 1px;
|
|
168
|
+
}
|
|
169
|
+
QLabel#inspectorTitle {
|
|
170
|
+
font-size: 16px;
|
|
171
|
+
font-weight: 600;
|
|
172
|
+
}
|
|
173
|
+
QLabel#inspectorSubtitle {
|
|
174
|
+
color: #959ca5;
|
|
175
|
+
margin-bottom: 6px;
|
|
176
|
+
}
|
|
177
|
+
"""
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
if __name__ == "__main__":
|
|
182
|
+
raise SystemExit(main())
|