[RFC] Add preserve_mtime config (#1382)
Closes https://github.com/jmbannon/ytdl-sub/issues/386 Adds the ability to preserve mtime via Output Options. Usage: ``` output_options: preserve_mtime: True ``` Thanks @e1ven for the contribution!
This commit is contained in:
parent
08180c0412
commit
63753c5649
6 changed files with 87 additions and 0 deletions
|
|
@ -763,6 +763,15 @@ Defines where to output files and thumbnails after all post-processing has compl
|
|||
The output directory to store all media files downloaded.
|
||||
|
||||
|
||||
``preserve_mtime``
|
||||
|
||||
:expected type: Optional[Boolean]
|
||||
:description:
|
||||
Preserve the video's original upload time as the file modification time.
|
||||
When True, sets the file's mtime to match the video's upload_date from
|
||||
yt-dlp metadata. Defaults to False.
|
||||
|
||||
|
||||
``thumbnail_name``
|
||||
|
||||
:expected type: Optional[EntryFormatter]
|
||||
|
|
|
|||
|
|
@ -107,6 +107,7 @@ class OutputOptions(OptionsDictValidator):
|
|||
"keep_max_files",
|
||||
"download_archive_standardized_date",
|
||||
"keep_files_date_eval",
|
||||
"preserve_mtime",
|
||||
}
|
||||
|
||||
@classmethod
|
||||
|
|
@ -170,6 +171,10 @@ class OutputOptions(OptionsDictValidator):
|
|||
default=f"{{{v.upload_date_standardized.variable_name}}}",
|
||||
)
|
||||
|
||||
self._preserve_mtime = self._validate_key_if_present(
|
||||
key="preserve_mtime", validator=BoolValidator, default=False
|
||||
)
|
||||
|
||||
if (
|
||||
self._keep_files_before or self._keep_files_after or self._keep_max_files
|
||||
) and not self.maintain_download_archive:
|
||||
|
|
@ -309,6 +314,17 @@ class OutputOptions(OptionsDictValidator):
|
|||
"""
|
||||
return self._keep_max_files
|
||||
|
||||
@property
|
||||
def preserve_mtime(self) -> bool:
|
||||
"""
|
||||
:expected type: Optional[Boolean]
|
||||
:description:
|
||||
Preserve the video's original upload time as the file modification time.
|
||||
When True, sets the file's mtime to match the video's upload_date from
|
||||
yt-dlp metadata. Defaults to False.
|
||||
"""
|
||||
return self._preserve_mtime.value
|
||||
|
||||
def added_variables(self, unresolved_variables: Set[str]) -> Dict[PluginOperation, Set[str]]:
|
||||
return {
|
||||
# PluginOperation.MODIFY_ENTRY_METADATA: {
|
||||
|
|
|
|||
|
|
@ -73,6 +73,7 @@ class SubscriptionDownload(BaseSubscription, ABC):
|
|||
file_metadata=entry_metadata,
|
||||
output_file_name=output_file_name,
|
||||
entry=entry,
|
||||
preserve_mtime=self.output_options.preserve_mtime,
|
||||
)
|
||||
|
||||
# Always pretend to include the thumbnail in a dry-run
|
||||
|
|
@ -87,6 +88,7 @@ class SubscriptionDownload(BaseSubscription, ABC):
|
|||
output_file_name=output_thumbnail_name,
|
||||
entry=entry,
|
||||
copy_file=True,
|
||||
preserve_mtime=self.output_options.preserve_mtime,
|
||||
)
|
||||
elif not entry.is_thumbnail_downloaded():
|
||||
logger.warning(
|
||||
|
|
@ -106,6 +108,7 @@ class SubscriptionDownload(BaseSubscription, ABC):
|
|||
file_name=entry.get_download_info_json_name(),
|
||||
output_file_name=output_info_json_name,
|
||||
entry=entry,
|
||||
preserve_mtime=self.output_options.preserve_mtime,
|
||||
)
|
||||
|
||||
def _delete_working_directory(self, is_error: bool = False) -> None:
|
||||
|
|
|
|||
|
|
@ -430,6 +430,25 @@ class FileHandler:
|
|||
if os.path.isfile(file_path):
|
||||
os.remove(file_path)
|
||||
|
||||
@classmethod
|
||||
def set_mtime(cls, file_path: Union[str, Path], mtime: float):
|
||||
"""
|
||||
Set the modification time of a file
|
||||
|
||||
Parameters
|
||||
----------
|
||||
file_path
|
||||
Path to the file to modify
|
||||
mtime
|
||||
Modification time as a Unix timestamp
|
||||
"""
|
||||
try:
|
||||
# Set both access time and modification time
|
||||
os.utime(file_path, (mtime, mtime))
|
||||
except OSError:
|
||||
# If file operation fails, silently continue
|
||||
pass
|
||||
|
||||
def move_file_to_output_directory(
|
||||
self,
|
||||
file_name: str,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import copy
|
||||
import json
|
||||
import os.path
|
||||
import time
|
||||
from dataclasses import dataclass
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
|
|
@ -642,6 +643,7 @@ class EnhancedDownloadArchive:
|
|||
output_file_name: Optional[str] = None,
|
||||
entry: Optional[Entry] = None,
|
||||
copy_file: bool = False,
|
||||
preserve_mtime: bool = False,
|
||||
):
|
||||
"""
|
||||
Saves a file from the working directory to the output directory and record it in the
|
||||
|
|
@ -660,6 +662,8 @@ class EnhancedDownloadArchive:
|
|||
Optional. Entry that this file belongs to
|
||||
copy_file
|
||||
Optional. If True, copy the file. Move otherwise
|
||||
preserve_mtime
|
||||
Optional. If True and entry has upload_date, set file mtime to upload date
|
||||
"""
|
||||
if output_file_name is None:
|
||||
output_file_name = file_name
|
||||
|
|
@ -674,6 +678,22 @@ class EnhancedDownloadArchive:
|
|||
copy_file=copy_file,
|
||||
)
|
||||
|
||||
# Set mtime if preserve_mtime is enabled and we have an entry with upload_date
|
||||
if preserve_mtime and entry and not self._file_handler.dry_run:
|
||||
upload_date = entry.get(v.ytdl_sub_keep_files_date_eval, str)
|
||||
if upload_date:
|
||||
try:
|
||||
# Convert YYYY-mm-dd to timestamp
|
||||
upload_datetime = datetime.strptime(upload_date, "%Y-%m-%d")
|
||||
upload_timestamp = time.mktime(upload_datetime.timetuple())
|
||||
|
||||
# Set mtime on the output file
|
||||
output_file_path = Path(self._file_handler.output_directory) / output_file_name
|
||||
FileHandler.set_mtime(output_file_path, upload_timestamp)
|
||||
except (ValueError, OSError):
|
||||
# If date parsing or file operation fails, silently continue
|
||||
pass
|
||||
|
||||
# Determine if it's the entry file by seeing if the file_name to move matches the entry
|
||||
# download file name
|
||||
is_entry_file = entry and entry.get_download_file_name() == file_name
|
||||
|
|
|
|||
|
|
@ -120,6 +120,26 @@ class TestPreset:
|
|||
== "today-2months"
|
||||
)
|
||||
|
||||
def test_preset_preserve_mtime_option(self, config_file, youtube_video, output_options):
|
||||
# Test preserve_mtime defaults to False
|
||||
preset_default = Preset(
|
||||
config=config_file,
|
||||
name="test_default",
|
||||
value={"download": youtube_video, "output_options": output_options},
|
||||
)
|
||||
assert preset_default.output_options.preserve_mtime is False
|
||||
|
||||
# Test preserve_mtime can be set to True
|
||||
preset_enabled = Preset(
|
||||
config=config_file,
|
||||
name="test_enabled",
|
||||
value={
|
||||
"download": youtube_video,
|
||||
"output_options": dict(output_options, **{"preserve_mtime": True}),
|
||||
},
|
||||
)
|
||||
assert preset_enabled.output_options.preserve_mtime is True
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"parent_preset", ["preset_self_loop", "preset_loop_0", "preset_loop_1"]
|
||||
)
|
||||
|
|
|
|||
Loading…
Reference in a new issue