ytdl-sub 2025.11.28.post1__py3-none-any.whl → 2025.12.6__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.
ytdl_sub/__init__.py CHANGED
@@ -1 +1 @@
1
- __pypi_version__ = "2025.11.28.post1";__local_version__ = "2025.11.28+8d5d2be"
1
+ __pypi_version__ = "2025.12.06";__local_version__ = "2025.12.06+a5ae695"
@@ -12,6 +12,8 @@ from ytdl_sub.config.defaults import DEFAULT_FFPROBE_PATH
12
12
  from ytdl_sub.config.defaults import DEFAULT_LOCK_DIRECTORY
13
13
  from ytdl_sub.config.defaults import MAX_FILE_NAME_BYTES
14
14
  from ytdl_sub.prebuilt_presets import PREBUILT_PRESETS
15
+ from ytdl_sub.utils.exceptions import SubscriptionPermissionError
16
+ from ytdl_sub.utils.file_handler import FileHandler
15
17
  from ytdl_sub.validators.file_path_validators import FFmpegFileValidator
16
18
  from ytdl_sub.validators.file_path_validators import FFprobeFileValidator
17
19
  from ytdl_sub.validators.strict_dict_validator import StrictDictValidator
@@ -147,6 +149,12 @@ class ConfigOptions(StrictDictValidator):
147
149
  key="file_name_max_bytes", validator=IntValidator, default=MAX_FILE_NAME_BYTES
148
150
  )
149
151
 
152
+ if not FileHandler.is_path_writable(self.working_directory):
153
+ raise SubscriptionPermissionError(
154
+ "ytdl-sub does not have permissions to the working directory: "
155
+ f"{self.working_directory}"
156
+ )
157
+
150
158
  @property
151
159
  def working_directory(self) -> str:
152
160
  """
@@ -8,6 +8,9 @@ from ytdl_sub.config.overrides import Overrides
8
8
  from ytdl_sub.config.plugin.plugin_operation import PluginOperation
9
9
  from ytdl_sub.config.validators.options import OptionsDictValidator
10
10
  from ytdl_sub.entries.script.variable_definitions import VARIABLES as v
11
+ from ytdl_sub.utils.exceptions import SubscriptionPermissionError
12
+ from ytdl_sub.utils.exceptions import ValidationException
13
+ from ytdl_sub.utils.file_handler import FileHandler
11
14
  from ytdl_sub.validators.file_path_validators import OverridesStringFormatterFilePathValidator
12
15
  from ytdl_sub.validators.file_path_validators import StringFormatterFileNameValidator
13
16
  from ytdl_sub.validators.string_datetime import StringDatetimeValidator
@@ -57,12 +60,24 @@ class YTDLOptions(UnstructuredOverridesDictFormatterValidator):
57
60
  def to_native_dict(self, overrides: Overrides) -> Dict:
58
61
  """
59
62
  Materializes the entire ytdl-options dict from OverrideStringFormatters into
60
- native python
63
+ native python.
61
64
  """
62
- return {
65
+ out = {
63
66
  key: overrides.apply_overrides_formatter_to_native(val)
64
67
  for key, val in self.dict.items()
65
68
  }
69
+ if "cookiefile" in out:
70
+ if not FileHandler.is_file_existent(out["cookiefile"]):
71
+ raise ValidationException(
72
+ f"Specified cookiefile {out['cookiefile']} but it does not exist as a file."
73
+ )
74
+
75
+ if not FileHandler.is_file_readable(out["cookiefile"]):
76
+ raise SubscriptionPermissionError(
77
+ f"Cannot read cookiefile {out['cookiefile']} due to permissions issue."
78
+ )
79
+
80
+ return out
66
81
 
67
82
 
68
83
  # Disable for proper docstring formatting
@@ -10,6 +10,8 @@ from ytdl_sub.config.preset_options import OutputOptions
10
10
  from ytdl_sub.config.preset_options import YTDLOptions
11
11
  from ytdl_sub.downloaders.url.validators import MultiUrlValidator
12
12
  from ytdl_sub.entries.variables.override_variables import SubscriptionVariables
13
+ from ytdl_sub.utils.exceptions import SubscriptionPermissionError
14
+ from ytdl_sub.utils.file_handler import FileHandler
13
15
  from ytdl_sub.utils.file_handler import FileHandlerTransactionLog
14
16
  from ytdl_sub.utils.logger import Logger
15
17
  from ytdl_sub.ytdl_additions.enhanced_download_archive import EnhancedDownloadArchive
@@ -94,6 +96,12 @@ class BaseSubscription(ABC):
94
96
 
95
97
  self._exception: Optional[Exception] = None
96
98
 
99
+ if not FileHandler.is_path_writable(self.output_directory):
100
+ raise SubscriptionPermissionError(
101
+ "ytdl-sub does not have write permissions to the output directory: "
102
+ f"{self.output_directory}"
103
+ )
104
+
97
105
  @property
98
106
  def download_archive(self) -> EnhancedDownloadArchive:
99
107
  """
@@ -40,3 +40,7 @@ class FileNotDownloadedException(ValueError):
40
40
 
41
41
  class ExperimentalFeatureNotEnabled(ValidationException):
42
42
  """Feature is not enabled for usage"""
43
+
44
+
45
+ class SubscriptionPermissionError(ValidationException):
46
+ """Early-caught permission error"""
@@ -379,6 +379,36 @@ class FileHandler:
379
379
  """
380
380
  return self._file_handler_transaction_log
381
381
 
382
+ @classmethod
383
+ def is_path_writable(cls, src_file_path: Union[str, Path]) -> bool:
384
+ """
385
+ Check whether a path is writable. If it does not exist, try to find the base directory
386
+ and check permissions on that.
387
+ """
388
+ path = os.path.abspath(src_file_path)
389
+
390
+ while not os.path.exists(path):
391
+ new_path = os.path.dirname(path)
392
+ if new_path == path: # reached root
393
+ break
394
+ path = new_path
395
+
396
+ return os.access(path, os.W_OK)
397
+
398
+ @classmethod
399
+ def is_file_existent(cls, file_path: Union[str, Path]) -> bool:
400
+ """
401
+ Check whether a file exists.
402
+ """
403
+ return os.path.isfile(file_path)
404
+
405
+ @classmethod
406
+ def is_file_readable(cls, file_path: Union[str, Path]) -> bool:
407
+ """
408
+ Check whether a file exists and is readable.
409
+ """
410
+ return cls.is_file_existent(file_path) and os.access(file_path, os.R_OK)
411
+
382
412
  @classmethod
383
413
  def copy(cls, src_file_path: Union[str, Path], dst_file_path: Union[str, Path]):
384
414
  """
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ytdl-sub
3
- Version: 2025.11.28.post1
3
+ Version: 2025.12.6
4
4
  Summary: Automate downloading metadata generation with YoutubeDL
5
5
  Author: Jesse Bannon
6
6
  License: GNU GENERAL PUBLIC LICENSE
@@ -1,4 +1,4 @@
1
- ytdl_sub/__init__.py,sha256=S6dNcqTDairr1YsTIC2vDAyTwaSw4B88XoXSQwM5YuE,79
1
+ ytdl_sub/__init__.py,sha256=KSVxAu5wwrT1Qp3VE6mgj9HLsY1j3VHzpYx1HZHKitI,73
2
2
  ytdl_sub/main.py,sha256=4Rf9wXxSKW7IPnWqG5YtTZ814PjP1n9WtoFDivaainE,1004
3
3
  ytdl_sub/cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
4
  ytdl_sub/cli/entrypoint.py,sha256=XXjUH4HiOP_BB2ZA_bNcyt5-o6YLAdZmj0EP3xtOtD8,9496
@@ -10,11 +10,11 @@ ytdl_sub/cli/parsers/dl.py,sha256=Fzc9nstJ8QXGhCOzSW1_v3cZHfSiVV2coFq3L1eTHtQ,90
10
10
  ytdl_sub/cli/parsers/main.py,sha256=-H4A9DPARVWe2tCH3rOJiybevlmH3VYhS10yz3683zk,7245
11
11
  ytdl_sub/config/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
12
12
  ytdl_sub/config/config_file.py,sha256=SQtVrMIUq2z3WwJVOed4y84JBMQ8aa4pbiBB0Y-YY4M,2781
13
- ytdl_sub/config/config_validator.py,sha256=NWPoCb6qjOom-YQamjho4UAIMSC43LRIT28W0rriXds,9424
13
+ ytdl_sub/config/config_validator.py,sha256=W1dvQD8wI7VOmGOHyaliu8DC6HOjfoGsgUw2MURTZOM,9797
14
14
  ytdl_sub/config/defaults.py,sha256=NTwzlKDkks1LDGNjFMxh91fw5E6T6d_zGsCwODNYJxo,1152
15
15
  ytdl_sub/config/overrides.py,sha256=0L6WaVtgqGqLvSuUkn4lbll9E8IboJlA7iJteIfN4Mo,8918
16
16
  ytdl_sub/config/preset.py,sha256=cp_oCyR78pHqYIakUNvNnwuFfFUq1YzitvIav5QW3Jw,8517
17
- ytdl_sub/config/preset_options.py,sha256=RgwnpIQqe_17YiaqaI-qlQ12RnkVGTdawXR_e6gYhuQ,13523
17
+ ytdl_sub/config/preset_options.py,sha256=PBinBBRLTaO4B5Q9bW2WUHAd0-x8fVIUqodOIm-py-c,14207
18
18
  ytdl_sub/config/plugin/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
19
19
  ytdl_sub/config/plugin/plugin.py,sha256=ugUyDnjLKcBy7lOpTbVKaGSuUMvzyyQx3DIcbv3eTkQ,4721
20
20
  ytdl_sub/config/plugin/plugin_mapping.py,sha256=xx9K2EReIU7zdPlogjvIkowy8J2R4TEhMNJyR7_1aE0,7846
@@ -121,7 +121,7 @@ ytdl_sub/script/utils/exceptions.py,sha256=cag5ZLM6as1w-RsrOwO-oe4YFpwlFu_8U0QNG
121
121
  ytdl_sub/script/utils/name_validation.py,sha256=ckTZqzeC-PTJRfAJbFLTNEgKchPA6YqLMS9Z280WUeQ,1830
122
122
  ytdl_sub/script/utils/type_checking.py,sha256=9EKWE1mWPOlmXWiWTZw-bCRV0FlYk22tNKlgEM6wU_0,10393
123
123
  ytdl_sub/subscriptions/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
124
- ytdl_sub/subscriptions/base_subscription.py,sha256=-zq4WgpsOq493fEHal8XmC6mLQiA3tvaeuFRyRW_7Es,6803
124
+ ytdl_sub/subscriptions/base_subscription.py,sha256=JRVyJIoHIt0I7NoEMqYd9FsZU3QF9i8HQAjzQeg_CPM,7179
125
125
  ytdl_sub/subscriptions/subscription.py,sha256=wfzOYVkzmsSH1KYbAMRi6Nhrb2pRaXdhILle7H0hcas,5127
126
126
  ytdl_sub/subscriptions/subscription_download.py,sha256=-eLbClw88tleLdUgP7TtPtnqroE3Zzdx2XLQrgapMDc,18190
127
127
  ytdl_sub/subscriptions/subscription_validators.py,sha256=tSN8YPLkIYXQbmWQ3M6U7xHpNjPn8rtNR9wSLIE2kzM,13606
@@ -131,9 +131,9 @@ ytdl_sub/thread/log_entries_downloaded_listener.py,sha256=7gPJSyvbM0W9XjliHgzmWF
131
131
  ytdl_sub/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
132
132
  ytdl_sub/utils/chapters.py,sha256=zLNndxmVdNg3LKV7HeNEImC3rWlpsoBPkZ1-TFQBD0Q,8754
133
133
  ytdl_sub/utils/datetime.py,sha256=M9OTwGDlPWlu4E637P34Tc_23c-MtEUTiiU9eVI5pKQ,1100
134
- ytdl_sub/utils/exceptions.py,sha256=mu6F5eneFo-oEIcmjEJV4aCK2pc7vKucBtmMz91HN68,1219
134
+ ytdl_sub/utils/exceptions.py,sha256=P4OTl8dQXYoHv_AzdPz8VM-i3O3tUh8Kn_F5-qrUxTk,1317
135
135
  ytdl_sub/utils/ffmpeg.py,sha256=1av8AEvVraIXROk9Q__UWVFNLvXGKJZnFTufnLnPfR8,6513
136
- ytdl_sub/utils/file_handler.py,sha256=chP9i2Cf14vV9FHTIN2YqmAJF_yxV1jDZzDeVsJ8PWE,17259
136
+ ytdl_sub/utils/file_handler.py,sha256=-uBhEjym9UeCEqJtGjWeKveDZfDME7XOOKiqtpV4Rxo,18211
137
137
  ytdl_sub/utils/file_lock.py,sha256=VlDl8QjBEUAHDBHsfp7Q0tR1Me0fFYELqzMtlyZ0MO4,2790
138
138
  ytdl_sub/utils/file_path.py,sha256=7RWc4fGj7HH6srzGJ5ph1l5euJfWpIX-2cyC-03YXEs,2539
139
139
  ytdl_sub/utils/logger.py,sha256=g_23ddk1WpQ3-_MOHz-Rlz7xCydK66BbtqkualX1zAQ,8593
@@ -158,9 +158,9 @@ ytdl_sub/validators/string_select_validator.py,sha256=KFXNKWX2J80WGt08m5gVYphPMH
158
158
  ytdl_sub/validators/validators.py,sha256=X9AkECdngEXcME_C25njHaOjtqbfEJMED2eG9Qy3UPs,9352
159
159
  ytdl_sub/ytdl_additions/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
160
160
  ytdl_sub/ytdl_additions/enhanced_download_archive.py,sha256=Lsc0wjHdx9d8dYJCskZYAUGDAQ_QzQ-_xbQlyrBSzfk,24884
161
- ytdl_sub-2025.11.28.post1.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
162
- ytdl_sub-2025.11.28.post1.dist-info/METADATA,sha256=pc1Bdfyuw36jWFP0PUCDg9gT8vAVeNKL7essp3Oi7VY,51427
163
- ytdl_sub-2025.11.28.post1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
164
- ytdl_sub-2025.11.28.post1.dist-info/entry_points.txt,sha256=K3T5235NlAI-WLmHCg5tzLZHqc33OLN5IY5fOGc9t10,48
165
- ytdl_sub-2025.11.28.post1.dist-info/top_level.txt,sha256=6z-JWazl6jXspC2DNyxOnGnEqYyGzVbgcBDoXfbkUhI,9
166
- ytdl_sub-2025.11.28.post1.dist-info/RECORD,,
161
+ ytdl_sub-2025.12.6.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
162
+ ytdl_sub-2025.12.6.dist-info/METADATA,sha256=DjARA2YOVqdCHLqljTVOuLmRHG8q6oqJrK_dUGfOxLk,51420
163
+ ytdl_sub-2025.12.6.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
164
+ ytdl_sub-2025.12.6.dist-info/entry_points.txt,sha256=K3T5235NlAI-WLmHCg5tzLZHqc33OLN5IY5fOGc9t10,48
165
+ ytdl_sub-2025.12.6.dist-info/top_level.txt,sha256=6z-JWazl6jXspC2DNyxOnGnEqYyGzVbgcBDoXfbkUhI,9
166
+ ytdl_sub-2025.12.6.dist-info/RECORD,,