srcalchemy 0.2.0__py3-none-any.whl

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.
@@ -0,0 +1,171 @@
1
+ #################################################################################
2
+ # MIT License
3
+ #
4
+ # Copyright (c) 2025 Duncan Fraser
5
+ #
6
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ # of this software and associated documentation files (the "Software"), to deal
8
+ # in the Software without restriction, including without limitation the rights
9
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ # copies of the Software, and to permit persons to whom the Software is
11
+ # furnished to do so, subject to the following conditions:
12
+ #
13
+ # The above copyright notice and this permission notice shall be included in all
14
+ # copies or substantial portions of the Software.
15
+ #
16
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
+ # SOFTWARE.
23
+ #################################################################################
24
+ #
25
+ # File : config_dump.py
26
+ #
27
+ # Author : Duncan Fraser <dfraser@thedrunkencoder.uk>
28
+ #
29
+ # Description :
30
+ #
31
+ #################################################################################
32
+ import inspect
33
+ from typing import Any, TextIO
34
+
35
+ from .config_load import (
36
+ driveMappingConfig,
37
+ generalConfig,
38
+ kodiConfig,
39
+ movieConfig,
40
+ tvConfig,
41
+ )
42
+
43
+
44
+ def writeAttributes(configIO: TextIO, object: Any) -> None:
45
+ """Write Attributes or Methods of object to file
46
+
47
+ :param configIO: Writable Text I/O object
48
+ :param object: Configuration Object
49
+ """
50
+ for field in object.__annotations__:
51
+ doc = inspect.getcomments(getattr(object, field))
52
+ if doc is not None:
53
+ configIO.writelines(["# " + i + "\n" for i in doc.split("\n")])
54
+
55
+ if type(getattr(object, field)) == str or type(getattr(object, field)) == bool:
56
+ configIO.write(f'{field}="{getattr(object, field)}"')
57
+ else:
58
+ configIO.write(f"{field}={getattr(object, field)}")
59
+ configIO.write("\n")
60
+
61
+
62
+ def writeGeneralConfig(
63
+ configIO: TextIO, general_config: generalConfig = generalConfig()
64
+ ) -> None:
65
+ """Write General Configuration to file
66
+
67
+ :param configIO: Writable Text I/O object
68
+ :param general_config: General Configuration
69
+ """
70
+
71
+ doc = inspect.getdoc(general_config)
72
+ if doc is not None:
73
+ configIO.writelines(["# " + i + "\n" for i in doc.split("\n")])
74
+
75
+ configIO.write("[general]\n")
76
+
77
+ writeAttributes(configIO=configIO, object=general_config)
78
+
79
+ configIO.write("\n")
80
+
81
+
82
+ def writeKodiConfig(configIO: TextIO, kodi_config: kodiConfig = kodiConfig()) -> None:
83
+ """Write Kodi Configuration to file
84
+
85
+ :param configIO: Writable Text I/O object
86
+ :param kodi_config: Kodi Configuration
87
+ """
88
+ doc = inspect.getdoc(kodi_config)
89
+ if doc is not None:
90
+ configIO.writelines(["# " + i + "\n" for i in doc.split("\n")])
91
+
92
+ configIO.write("[kodi]\n")
93
+
94
+ writeAttributes(configIO=configIO, object=kodi_config)
95
+
96
+ configIO.write("\n")
97
+
98
+
99
+ def writeMovieConfig(
100
+ configIO: TextIO, movie_config: movieConfig = movieConfig()
101
+ ) -> None:
102
+ """Write Movie Configuration to file
103
+
104
+ :param configIO: Writable Text I/O object
105
+ :param movie_config: Movie Configuration
106
+ """
107
+ if movie_config.outputDirectory is None:
108
+ movie_config.outputDirectory = ""
109
+
110
+ doc = inspect.getdoc(movie_config)
111
+ if doc is not None:
112
+ configIO.writelines(["# " + i + "\n" for i in doc.split("\n")])
113
+
114
+ configIO.write("[movie]\n")
115
+
116
+ writeAttributes(configIO=configIO, object=movie_config)
117
+
118
+ configIO.write("\n")
119
+
120
+
121
+ def writeTvConfig(configIO: TextIO, tv_config: tvConfig = tvConfig()) -> None:
122
+ """Write TV Configuration to file
123
+
124
+ :param configIO: Writable Text I/O object
125
+ :param tv_config: TV Configuration
126
+ """
127
+ if tv_config.outputDirectory is None:
128
+ tv_config.outputDirectory = ""
129
+
130
+ doc = inspect.getdoc(tv_config)
131
+ if doc is not None:
132
+ configIO.writelines(["# " + i + "\n" for i in doc.split("\n")])
133
+
134
+ configIO.write("[tv]\n")
135
+
136
+ writeAttributes(configIO=configIO, object=tv_config)
137
+
138
+ configIO.write("\n")
139
+
140
+
141
+ def writeDriveMapping(
142
+ configIO: TextIO,
143
+ drive_mapping: driveMappingConfig = driveMappingConfig(src="", dest=""),
144
+ ) -> None:
145
+ """Write Drive mapping configuration to file
146
+
147
+ :param configIO: Writable Text I/O object
148
+ :param drive_mapping: Drive Mapping Configuration
149
+ """
150
+
151
+ doc = inspect.getdoc(drive_mapping)
152
+ if doc is not None:
153
+ configIO.writelines(["# " + i + "\n" for i in doc.split("\n")])
154
+
155
+ configIO.write("[[drive_mapping]]\n")
156
+
157
+ writeAttributes(configIO=configIO, object=drive_mapping)
158
+
159
+ configIO.write("\n")
160
+
161
+
162
+ def writeDefaultConfig(configIO: TextIO) -> None:
163
+ """Write default configuration to file
164
+
165
+ :param configIO: Writable Text I/O object
166
+ """
167
+ writeGeneralConfig(configIO=configIO)
168
+ writeKodiConfig(configIO=configIO)
169
+ writeMovieConfig(configIO=configIO)
170
+ writeTvConfig(configIO=configIO)
171
+ writeDriveMapping(configIO=configIO)
@@ -0,0 +1,278 @@
1
+ #################################################################################
2
+ # MIT License
3
+ #
4
+ # Copyright (c) 2025 Duncan Fraser
5
+ #
6
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ # of this software and associated documentation files (the "Software"), to deal
8
+ # in the Software without restriction, including without limitation the rights
9
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ # copies of the Software, and to permit persons to whom the Software is
11
+ # furnished to do so, subject to the following conditions:
12
+ #
13
+ # The above copyright notice and this permission notice shall be included in all
14
+ # copies or substantial portions of the Software.
15
+ #
16
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
+ # SOFTWARE.
23
+ #################################################################################
24
+ #
25
+ # File : config_load.py
26
+ #
27
+ # Author : Duncan Fraser <dfraser@thedrunkencoder.uk>
28
+ #
29
+ # Description :
30
+ #
31
+ #################################################################################
32
+
33
+ import logging
34
+ import tomllib
35
+ from dataclasses import dataclass, field
36
+ from typing import BinaryIO, Literal
37
+
38
+ MODE = Literal["copy", "move"]
39
+
40
+
41
+ @dataclass
42
+ class generalConfig:
43
+ """General configuration options"""
44
+
45
+ logFile: str = "srcalchemy.log"
46
+ """Log file location"""
47
+ dataDirectory: str = "data"
48
+ """Directory to output collected information to"""
49
+ mode: MODE = "copy"
50
+ """Mode of operation
51
+
52
+ Choice of:
53
+ - copy: Copy media items when processing
54
+ - move: Move media items when processing
55
+ """
56
+
57
+
58
+ @dataclass
59
+ class kodiConfig:
60
+ """Kodi configuration options
61
+ Used when interacting with the Kodi JSONRPC
62
+ """
63
+
64
+ host: str = "localhost"
65
+ """Hostname of IP address of Kodi host to connect to"""
66
+ host_port: int = 8080
67
+ """Kodi JSONRPC port"""
68
+ use_https: bool = False
69
+ """Use HTTPS when connecting to Kodi host"""
70
+ ignore_ssl: bool = True
71
+ """Ignore SSL when connecting to Kodi host"""
72
+
73
+
74
+ @dataclass
75
+ class movieConfig:
76
+ """Movie Configuration
77
+ Used for processing parsed movies
78
+ """
79
+
80
+ basePath: str = ""
81
+ """Base path of Movie Directory as defined in the Kodi Video Library"""
82
+ outputDirectory: str | None = None
83
+ """Optional Output directory"""
84
+ parentFormat: str = "{TITLE} ({YEAR})"
85
+ """Format map string for generating the output directory name"""
86
+ fileFormat: str = "{TITLE} [{FORMAT}] ({YEAR})"
87
+ """Format map string for generating the output filename"""
88
+
89
+
90
+ @dataclass
91
+ class tvConfig:
92
+ """TV Configuration
93
+ Used for processing parsed TV and Episodes
94
+ """
95
+
96
+ basePath: str = ""
97
+ """Base path of TV Directory as defined in the Kodi Video Library"""
98
+ outputDirectory: str | None = None
99
+ """"""
100
+ parentFormat: str = "{TITLE}"
101
+ """Format map string for generating the output directory name"""
102
+ seasonFormat: str = "Season {SEASON}"
103
+ """Format map string for generating the season output directory name"""
104
+ fileFormat: str = "{SHOW_TITLE} S{SEASON:02}E{EPISODE:02}"
105
+ """Format map string for generating the output filename"""
106
+
107
+
108
+ @dataclass
109
+ class driveMappingConfig:
110
+ """Drive Mapping Configuration
111
+ Used for replacing path strings for local mappings
112
+ """
113
+
114
+ src: str
115
+ """Source Path string to be replace"""
116
+ dest: str
117
+ """Destination path"""
118
+
119
+
120
+ @dataclass
121
+ class ConfigSpec:
122
+ """Configurations Options"""
123
+
124
+ general: generalConfig = field(default_factory=generalConfig)
125
+ """General configuration options"""
126
+ log: logging.Logger = logging.getLogger(__name__)
127
+ """Application Logger"""
128
+ kodi: kodiConfig = field(default_factory=kodiConfig)
129
+ """Kodi configuration options"""
130
+ movie_config: movieConfig = field(default_factory=movieConfig)
131
+ """Movie configuration options"""
132
+ tv_config: tvConfig = field(default_factory=tvConfig)
133
+ """TV configuration options"""
134
+ driveMapping: list[driveMappingConfig] = field(default_factory=list)
135
+ """List of Drive mapping configurations"""
136
+
137
+
138
+ def parseGeneralConfig(data: dict) -> generalConfig:
139
+ """Parse General Configuration from Dictionary
140
+
141
+ :param data: Dictionary to parse
142
+
143
+ :returns: General Configuration
144
+ """
145
+ general_config = generalConfig()
146
+
147
+ if "logFile" in data:
148
+ general_config.logFile = data["logFile"]
149
+
150
+ if "dataDirectory" in data:
151
+ general_config.dataDirectory = data["dataDirectory"]
152
+
153
+ if "mode" in data:
154
+ if data["mode"] == "copy":
155
+ general_config.mode = "copy"
156
+ elif data["mode"] == "move":
157
+ general_config.mode = "move"
158
+
159
+ return general_config
160
+
161
+
162
+ def parseKodiConfig(data: dict) -> kodiConfig:
163
+ """Parse Kodi Config from Dictionary
164
+
165
+ :param data: Dictionary to parse
166
+
167
+ :returns: Kodi Configuration
168
+ """
169
+ kodi_config = kodiConfig()
170
+
171
+ if "host" in data:
172
+ kodi_config.host = str(data["host"])
173
+
174
+ if "port" in data:
175
+ kodi_config.host_port = int(data["port"])
176
+
177
+ if "use_https" in data:
178
+ kodi_config.use_https = data["use_https"] == "True"
179
+
180
+ if "ignore_ssl" in data:
181
+ kodi_config.ignore_ssl = data["ignore_ssl"] == "True"
182
+
183
+ return kodi_config
184
+
185
+
186
+ def parseMovieConfig(data: dict) -> movieConfig:
187
+ """Parse Movie Configuration from Dictionary
188
+
189
+ :param data: Dictionary to parse
190
+
191
+ :returns: Movie Configuration
192
+ """
193
+ movie_config = movieConfig()
194
+
195
+ if "basePath" in data:
196
+ movie_config.basePath = data["basePath"]
197
+
198
+ if "outputDirectory" in data:
199
+ movie_config.outputDirectory = data["outputDirectory"]
200
+
201
+ if "parentFormat" in data:
202
+ movie_config.parentFormat = data["parentFormat"]
203
+
204
+ if "fileFormat" in data:
205
+ movie_config.fileFormat = data["fileFormat"]
206
+
207
+ return movie_config
208
+
209
+
210
+ def parseTvConfig(data: dict) -> tvConfig:
211
+ """Parse TV configuration from Dictionary
212
+
213
+ :param data: Dictionary to parse
214
+
215
+ :returns: TV Configuration
216
+ """
217
+ tv_config = tvConfig()
218
+
219
+ if "basePath" in data:
220
+ tv_config.basePath = data["basePath"]
221
+
222
+ if "outputDirectory" in data:
223
+ tv_config.outputDirectory = data["outputDirectory"]
224
+
225
+ if "parentFormat" in data:
226
+ tv_config.parentFormat = data["parentFormat"]
227
+
228
+ if "seasonFormat" in data:
229
+ tv_config.seasonFormat = data["seasonFormat"]
230
+
231
+ if "fileFormat" in data:
232
+ tv_config.fileFormat = data["fileFormat"]
233
+
234
+ return tv_config
235
+
236
+
237
+ def parseDriveMappingConfig(data: dict) -> driveMappingConfig:
238
+ """Parse Drive Mapping configuration from Dictionary
239
+
240
+ :param data: Dictionary to parse
241
+
242
+ :returns: Drive mapping configuration
243
+ """
244
+ drive_mapping = driveMappingConfig(src=data["src"], dest=data["dest"])
245
+
246
+ return drive_mapping
247
+
248
+
249
+ def parseConfig(configIO: BinaryIO) -> ConfigSpec:
250
+ """Parse Configuration Options from Binary I/O
251
+
252
+ :param configIO: Readable Binary I/O Object
253
+
254
+ :returns: Configuration Specification parsed from
255
+ """
256
+ data = tomllib.load(configIO)
257
+
258
+ config = ConfigSpec()
259
+
260
+ if "general" in data:
261
+ config.general = parseGeneralConfig(data=data["general"])
262
+
263
+ if "kodi" in data:
264
+ config.kodi = parseKodiConfig(data=data["kodi"])
265
+
266
+ if "movie" in data:
267
+ config.movie_config = parseMovieConfig(data=data["movie"])
268
+
269
+ if "tv" in data:
270
+ config.tv_config = parseTvConfig(data=data["tv"])
271
+
272
+ if "drive_mapping" in data:
273
+ for mapping in data["drive_mapping"]:
274
+ drive_mapping = parseDriveMappingConfig(data=mapping)
275
+
276
+ config.driveMapping.append(drive_mapping)
277
+
278
+ return config
@@ -0,0 +1,46 @@
1
+ #################################################################################
2
+ # MIT License
3
+ #
4
+ # Copyright (c) 2025 Duncan Fraser
5
+ #
6
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ # of this software and associated documentation files (the "Software"), to deal
8
+ # in the Software without restriction, including without limitation the rights
9
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ # copies of the Software, and to permit persons to whom the Software is
11
+ # furnished to do so, subject to the following conditions:
12
+ #
13
+ # The above copyright notice and this permission notice shall be included in all
14
+ # copies or substantial portions of the Software.
15
+ #
16
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
+ # SOFTWARE.
23
+ #################################################################################
24
+ #
25
+ # File : __init__.py
26
+ #
27
+ # Author : Duncan Fraser <dfraser@thedrunkencoder.uk>
28
+ #
29
+ # Description :
30
+ #
31
+ #################################################################################
32
+ from .process import (
33
+ collectMovieRename,
34
+ collectTvRename,
35
+ performCleanDirectory,
36
+ performRenameOperation,
37
+ renameOperation,
38
+ )
39
+
40
+ __all__ = [
41
+ "collectMovieRename",
42
+ "collectTvRename",
43
+ "performCleanDirectory",
44
+ "performRenameOperation",
45
+ "renameOperation",
46
+ ]