ytdl-sub 2025.11.9__py3-none-any.whl → 2025.11.18__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.09";__local_version__ = "2025.11.09+a74c79d"
1
+ __pypi_version__ = "2025.11.18";__local_version__ = "2025.11.18+63753c5"
@@ -107,6 +107,7 @@ class OutputOptions(OptionsDictValidator):
107
107
  "keep_max_files",
108
108
  "download_archive_standardized_date",
109
109
  "keep_files_date_eval",
110
+ "preserve_mtime",
110
111
  }
111
112
 
112
113
  @classmethod
@@ -170,6 +171,10 @@ class OutputOptions(OptionsDictValidator):
170
171
  default=f"{{{v.upload_date_standardized.variable_name}}}",
171
172
  )
172
173
 
174
+ self._preserve_mtime = self._validate_key_if_present(
175
+ key="preserve_mtime", validator=BoolValidator, default=False
176
+ )
177
+
173
178
  if (
174
179
  self._keep_files_before or self._keep_files_after or self._keep_max_files
175
180
  ) and not self.maintain_download_archive:
@@ -309,6 +314,17 @@ class OutputOptions(OptionsDictValidator):
309
314
  """
310
315
  return self._keep_max_files
311
316
 
317
+ @property
318
+ def preserve_mtime(self) -> bool:
319
+ """
320
+ :expected type: Optional[Boolean]
321
+ :description:
322
+ Preserve the video's original upload time as the file modification time.
323
+ When True, sets the file's mtime to match the video's upload_date from
324
+ yt-dlp metadata. Defaults to False.
325
+ """
326
+ return self._preserve_mtime.value
327
+
312
328
  def added_variables(self, unresolved_variables: Set[str]) -> Dict[PluginOperation, Set[str]]:
313
329
  return {
314
330
  # PluginOperation.MODIFY_ENTRY_METADATA: {
@@ -73,6 +73,7 @@ class SubscriptionDownload(BaseSubscription, ABC):
73
73
  file_metadata=entry_metadata,
74
74
  output_file_name=output_file_name,
75
75
  entry=entry,
76
+ preserve_mtime=self.output_options.preserve_mtime,
76
77
  )
77
78
 
78
79
  # Always pretend to include the thumbnail in a dry-run
@@ -87,6 +88,7 @@ class SubscriptionDownload(BaseSubscription, ABC):
87
88
  output_file_name=output_thumbnail_name,
88
89
  entry=entry,
89
90
  copy_file=True,
91
+ preserve_mtime=self.output_options.preserve_mtime,
90
92
  )
91
93
  elif not entry.is_thumbnail_downloaded():
92
94
  logger.warning(
@@ -106,6 +108,7 @@ class SubscriptionDownload(BaseSubscription, ABC):
106
108
  file_name=entry.get_download_info_json_name(),
107
109
  output_file_name=output_info_json_name,
108
110
  entry=entry,
111
+ preserve_mtime=self.output_options.preserve_mtime,
109
112
  )
110
113
 
111
114
  def _delete_working_directory(self, is_error: bool = False) -> None:
@@ -430,6 +430,25 @@ class FileHandler:
430
430
  if os.path.isfile(file_path):
431
431
  os.remove(file_path)
432
432
 
433
+ @classmethod
434
+ def set_mtime(cls, file_path: Union[str, Path], mtime: float):
435
+ """
436
+ Set the modification time of a file
437
+
438
+ Parameters
439
+ ----------
440
+ file_path
441
+ Path to the file to modify
442
+ mtime
443
+ Modification time as a Unix timestamp
444
+ """
445
+ try:
446
+ # Set both access time and modification time
447
+ os.utime(file_path, (mtime, mtime))
448
+ except OSError:
449
+ # If file operation fails, silently continue
450
+ pass
451
+
433
452
  def move_file_to_output_directory(
434
453
  self,
435
454
  file_name: str,
@@ -1,6 +1,7 @@
1
1
  import copy
2
2
  import json
3
3
  import os.path
4
+ import time
4
5
  from dataclasses import dataclass
5
6
  from datetime import datetime
6
7
  from pathlib import Path
@@ -642,6 +643,7 @@ class EnhancedDownloadArchive:
642
643
  output_file_name: Optional[str] = None,
643
644
  entry: Optional[Entry] = None,
644
645
  copy_file: bool = False,
646
+ preserve_mtime: bool = False,
645
647
  ):
646
648
  """
647
649
  Saves a file from the working directory to the output directory and record it in the
@@ -660,6 +662,8 @@ class EnhancedDownloadArchive:
660
662
  Optional. Entry that this file belongs to
661
663
  copy_file
662
664
  Optional. If True, copy the file. Move otherwise
665
+ preserve_mtime
666
+ Optional. If True and entry has upload_date, set file mtime to upload date
663
667
  """
664
668
  if output_file_name is None:
665
669
  output_file_name = file_name
@@ -674,6 +678,22 @@ class EnhancedDownloadArchive:
674
678
  copy_file=copy_file,
675
679
  )
676
680
 
681
+ # Set mtime if preserve_mtime is enabled and we have an entry with upload_date
682
+ if preserve_mtime and entry and not self._file_handler.dry_run:
683
+ upload_date = entry.get(v.ytdl_sub_keep_files_date_eval, str)
684
+ if upload_date:
685
+ try:
686
+ # Convert YYYY-mm-dd to timestamp
687
+ upload_datetime = datetime.strptime(upload_date, "%Y-%m-%d")
688
+ upload_timestamp = time.mktime(upload_datetime.timetuple())
689
+
690
+ # Set mtime on the output file
691
+ output_file_path = Path(self._file_handler.output_directory) / output_file_name
692
+ FileHandler.set_mtime(output_file_path, upload_timestamp)
693
+ except (ValueError, OSError):
694
+ # If date parsing or file operation fails, silently continue
695
+ pass
696
+
677
697
  # Determine if it's the entry file by seeing if the file_name to move matches the entry
678
698
  # download file name
679
699
  is_entry_file = entry and entry.get_download_file_name() == file_name
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ytdl-sub
3
- Version: 2025.11.9
3
+ Version: 2025.11.18
4
4
  Summary: Automate downloading metadata generation with YoutubeDL
5
5
  Author: Jesse Bannon
6
6
  License: GNU GENERAL PUBLIC LICENSE
@@ -689,7 +689,7 @@ Classifier: Programming Language :: Python :: 3.11
689
689
  Requires-Python: >=3.10
690
690
  Description-Content-Type: text/markdown
691
691
  License-File: LICENSE
692
- Requires-Dist: yt-dlp[default]==2025.10.22
692
+ Requires-Dist: yt-dlp[default]==2025.11.12
693
693
  Requires-Dist: colorama~=0.4
694
694
  Requires-Dist: mergedeep~=1.3
695
695
  Requires-Dist: mediafile~=0.12
@@ -1,4 +1,4 @@
1
- ytdl_sub/__init__.py,sha256=h08aGMLqMxi_5sfLIGAtrh-Ccb360S0JDA2kcIR5j9o,73
1
+ ytdl_sub/__init__.py,sha256=HSa2j0j6xbqM0rcdsgrBPin_Y-dT44vCiR2vfwu33ts,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
@@ -14,7 +14,7 @@ ytdl_sub/config/config_validator.py,sha256=NWPoCb6qjOom-YQamjho4UAIMSC43LRIT28W0
14
14
  ytdl_sub/config/defaults.py,sha256=NTwzlKDkks1LDGNjFMxh91fw5E6T6d_zGsCwODNYJxo,1152
15
15
  ytdl_sub/config/overrides.py,sha256=WjfKnT8z5dth4uqMC53KrVzitGAPbKGaNN_25C35dUM,8704
16
16
  ytdl_sub/config/preset.py,sha256=cp_oCyR78pHqYIakUNvNnwuFfFUq1YzitvIav5QW3Jw,8517
17
- ytdl_sub/config/preset_options.py,sha256=NkkM2m0XCKyYYfe8Vh393AHH9pkvLbkYOmgRNt7G3X8,12958
17
+ ytdl_sub/config/preset_options.py,sha256=RgwnpIQqe_17YiaqaI-qlQ12RnkVGTdawXR_e6gYhuQ,13523
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
@@ -123,7 +123,7 @@ ytdl_sub/script/utils/type_checking.py,sha256=9EKWE1mWPOlmXWiWTZw-bCRV0FlYk22tNK
123
123
  ytdl_sub/subscriptions/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
124
124
  ytdl_sub/subscriptions/base_subscription.py,sha256=-zq4WgpsOq493fEHal8XmC6mLQiA3tvaeuFRyRW_7Es,6803
125
125
  ytdl_sub/subscriptions/subscription.py,sha256=wfzOYVkzmsSH1KYbAMRi6Nhrb2pRaXdhILle7H0hcas,5127
126
- ytdl_sub/subscriptions/subscription_download.py,sha256=3izO1FENzUTOKALfoSA3C75ro7qOvU9RMhaNpjMVowg,17993
126
+ ytdl_sub/subscriptions/subscription_download.py,sha256=-eLbClw88tleLdUgP7TtPtnqroE3Zzdx2XLQrgapMDc,18190
127
127
  ytdl_sub/subscriptions/subscription_validators.py,sha256=tSN8YPLkIYXQbmWQ3M6U7xHpNjPn8rtNR9wSLIE2kzM,13606
128
128
  ytdl_sub/subscriptions/subscription_ytdl_options.py,sha256=tD_HhV3Z90YeRj27zrv8W_iQfzqKvWWLnIkJW7Wy9L4,7562
129
129
  ytdl_sub/thread/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -133,7 +133,7 @@ ytdl_sub/utils/chapters.py,sha256=zLNndxmVdNg3LKV7HeNEImC3rWlpsoBPkZ1-TFQBD0Q,87
133
133
  ytdl_sub/utils/datetime.py,sha256=M9OTwGDlPWlu4E637P34Tc_23c-MtEUTiiU9eVI5pKQ,1100
134
134
  ytdl_sub/utils/exceptions.py,sha256=mu6F5eneFo-oEIcmjEJV4aCK2pc7vKucBtmMz91HN68,1219
135
135
  ytdl_sub/utils/ffmpeg.py,sha256=1av8AEvVraIXROk9Q__UWVFNLvXGKJZnFTufnLnPfR8,6513
136
- ytdl_sub/utils/file_handler.py,sha256=PauqYEk5SeRRj0d9Q0Ojp-Mq9r-BkazKeAXBERHVKs0,16730
136
+ ytdl_sub/utils/file_handler.py,sha256=chP9i2Cf14vV9FHTIN2YqmAJF_yxV1jDZzDeVsJ8PWE,17259
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
@@ -157,10 +157,10 @@ ytdl_sub/validators/string_formatter_validators.py,sha256=D7wuYFuHlbOYMCkKoy9ILL
157
157
  ytdl_sub/validators/string_select_validator.py,sha256=KFXNKWX2J80WGt08m5gVYphPMHYxhHlgfcoXAQMq6zw,1086
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
- ytdl_sub/ytdl_additions/enhanced_download_archive.py,sha256=3skvdZb42TfYmdfm3AebzuT3mr154NmkduaCXx-HlzE,23844
161
- ytdl_sub-2025.11.9.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
162
- ytdl_sub-2025.11.9.dist-info/METADATA,sha256=-QuFvwf-N2Z94JXEp0pU51Lbf4JlPmVYwsMNeK6mQoI,51420
163
- ytdl_sub-2025.11.9.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
164
- ytdl_sub-2025.11.9.dist-info/entry_points.txt,sha256=K3T5235NlAI-WLmHCg5tzLZHqc33OLN5IY5fOGc9t10,48
165
- ytdl_sub-2025.11.9.dist-info/top_level.txt,sha256=6z-JWazl6jXspC2DNyxOnGnEqYyGzVbgcBDoXfbkUhI,9
166
- ytdl_sub-2025.11.9.dist-info/RECORD,,
160
+ ytdl_sub/ytdl_additions/enhanced_download_archive.py,sha256=Lsc0wjHdx9d8dYJCskZYAUGDAQ_QzQ-_xbQlyrBSzfk,24884
161
+ ytdl_sub-2025.11.18.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
162
+ ytdl_sub-2025.11.18.dist-info/METADATA,sha256=-7ZJ-IKg_JuI-D8QCm7Sgkh41qUhg1wleTqaLR1PMYc,51421
163
+ ytdl_sub-2025.11.18.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
164
+ ytdl_sub-2025.11.18.dist-info/entry_points.txt,sha256=K3T5235NlAI-WLmHCg5tzLZHqc33OLN5IY5fOGc9t10,48
165
+ ytdl_sub-2025.11.18.dist-info/top_level.txt,sha256=6z-JWazl6jXspC2DNyxOnGnEqYyGzVbgcBDoXfbkUhI,9
166
+ ytdl_sub-2025.11.18.dist-info/RECORD,,