use yaml for print
This commit is contained in:
parent
56bae3f39a
commit
68f2e768e7
13 changed files with 213 additions and 104 deletions
|
|
@ -122,7 +122,7 @@ class YoutubeMergePlaylistDownloader(
|
|||
file_duration_sec=merged_video.kwargs("duration"),
|
||||
)
|
||||
|
||||
return chapters.to_file_metadata(title="Timestamps of playlist videos in the merged file:")
|
||||
return chapters.to_file_metadata(title="Timestamps of playlist videos in the merged file")
|
||||
|
||||
def _to_merged_video(self, entry_dict: Dict) -> YoutubeVideo:
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -334,7 +334,7 @@ class ChaptersPlugin(Plugin[ChaptersOptions]):
|
|||
chapters = Chapters.from_timestamps_file(
|
||||
chapters_file_path=self.plugin_options.embed_chapter_timestamps
|
||||
)
|
||||
return chapters.to_file_metadata(title="Chapters embedded from timestamp file:")
|
||||
return chapters.to_file_metadata(title="Chapters embedded from timestamp file")
|
||||
|
||||
if self.plugin_options.embed_chapters:
|
||||
metadata_dict = {}
|
||||
|
|
|
|||
|
|
@ -84,4 +84,4 @@ class MusicTagsPlugin(Plugin[MusicTagsOptions]):
|
|||
audio_file.save()
|
||||
|
||||
# report the tags written
|
||||
return FileMetadata.from_dict(value_dict=tags_to_write, title="Music Tags:")
|
||||
return FileMetadata.from_dict(value_dict=tags_to_write, title="Music Tags")
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import os
|
||||
from abc import ABC
|
||||
from collections import defaultdict
|
||||
from pathlib import Path
|
||||
from typing import Dict
|
||||
from typing import Generic
|
||||
|
|
@ -97,19 +98,19 @@ class SharedNfoTagsPlugin(
|
|||
"""
|
||||
|
||||
def _get_xml_element_dict(self, entry: Optional[Entry]) -> Dict[str, List[XmlElement]]:
|
||||
nfo_tags: Dict[str, List[XmlElement]] = {}
|
||||
nfo_tags: Dict[str, List[XmlElement]] = defaultdict(list)
|
||||
|
||||
for key, string_tags in self.plugin_options.tags.string_tags.items():
|
||||
nfo_tags[key] = [
|
||||
nfo_tags[key].extend(
|
||||
XmlElement(
|
||||
text=self.overrides.apply_formatter(formatter=string_tag, entry=entry),
|
||||
attributes={},
|
||||
)
|
||||
for string_tag in string_tags
|
||||
]
|
||||
)
|
||||
|
||||
for key, attribute_tags in self.plugin_options.tags.attribute_tags.items():
|
||||
nfo_tags[key] = [
|
||||
nfo_tags[key].extend(
|
||||
XmlElement(
|
||||
text=self.overrides.apply_formatter(formatter=attribute_tag.tag, entry=entry),
|
||||
attributes={
|
||||
|
|
@ -120,7 +121,7 @@ class SharedNfoTagsPlugin(
|
|||
},
|
||||
)
|
||||
for attribute_tag in attribute_tags
|
||||
]
|
||||
)
|
||||
|
||||
return nfo_tags
|
||||
|
||||
|
|
@ -168,7 +169,7 @@ class SharedNfoTagsPlugin(
|
|||
for key, xml_elems in nfo_tags.items()
|
||||
}
|
||||
},
|
||||
title="NFO tags:",
|
||||
title="NFO tags",
|
||||
)
|
||||
self.save_file(file_name=nfo_file_name, file_metadata=nfo_metadata, entry=entry)
|
||||
|
||||
|
|
|
|||
|
|
@ -136,7 +136,7 @@ class SplitByChaptersPlugin(Plugin[SplitByChaptersOptions]):
|
|||
|
||||
metadata = FileMetadata.from_dict(
|
||||
value_dict=metadata_value_dict,
|
||||
title="From Chapter Split:",
|
||||
title="From Chapter Split",
|
||||
sort_dict=False,
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -59,4 +59,4 @@ class VideoTagsPlugin(Plugin[VideoTagsOptions]):
|
|||
)
|
||||
|
||||
# report the tags written
|
||||
return FileMetadata.from_dict(value_dict=tags_to_write, title="Video Tags:")
|
||||
return FileMetadata.from_dict(value_dict=tags_to_write, title="Video Tags")
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import json
|
||||
import os
|
||||
import shutil
|
||||
from pathlib import Path
|
||||
|
|
@ -7,6 +8,7 @@ from typing import List
|
|||
from typing import Optional
|
||||
from typing import Set
|
||||
from typing import Union
|
||||
import yaml
|
||||
|
||||
|
||||
class FileMetadata:
|
||||
|
|
@ -56,32 +58,13 @@ class FileMetadata:
|
|||
sort_dict
|
||||
Whether to sort dicts in the value_dict. Defaults to true.
|
||||
"""
|
||||
lines: List[str] = []
|
||||
if title is not None:
|
||||
lines.append(title)
|
||||
if title:
|
||||
value_dict = {title: value_dict}
|
||||
if sort_dict:
|
||||
value_dict = json.loads(json.dumps(value_dict, sort_keys=True))
|
||||
|
||||
def _recursive_add_dict_lines(rdict: Dict, indent: int):
|
||||
rdict_items = rdict.items()
|
||||
if sort_dict:
|
||||
rdict_items = sorted(rdict_items)
|
||||
|
||||
for key, value in rdict_items:
|
||||
_indent = " " * indent
|
||||
if isinstance(value, Dict):
|
||||
lines.append(f"{_indent}{key}:")
|
||||
_recursive_add_dict_lines(rdict=value, indent=indent + 2)
|
||||
else:
|
||||
value = str(value)
|
||||
# If there are newlines in the value, print them indented
|
||||
if "\n" in value:
|
||||
lines.append(f"{_indent}{key}:")
|
||||
for value_line in value.split("\n"):
|
||||
lines.append(f" {_indent}{value_line.strip()}")
|
||||
else:
|
||||
lines.append(f"{_indent}{key}: {value}")
|
||||
|
||||
_recursive_add_dict_lines(rdict=value_dict, indent=2)
|
||||
return cls(metadata=lines)
|
||||
out = yaml.safe_dump(value_dict, allow_unicode=True, indent=2, default_style='', width=100)
|
||||
return cls(metadata=out.rstrip().split('\n'))
|
||||
|
||||
|
||||
class FileHandlerTransactionLog:
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import xml.etree.ElementTree as et
|
||||
from dataclasses import dataclass
|
||||
from typing import Any
|
||||
from typing import Any, List
|
||||
from typing import Dict
|
||||
from typing import Union
|
||||
|
||||
|
|
@ -59,7 +59,7 @@ def to_max_3_byte_utf8_dict(string_dict: Dict[str, str]) -> Dict[str, str]:
|
|||
}
|
||||
|
||||
|
||||
def to_xml(nfo_dict: Dict[str, XmlElement], nfo_root: str) -> bytes:
|
||||
def to_xml(nfo_dict: Dict[str, List[XmlElement]], nfo_root: str) -> bytes:
|
||||
"""
|
||||
Transforms a dict to XML
|
||||
|
||||
|
|
@ -75,10 +75,11 @@ def to_xml(nfo_dict: Dict[str, XmlElement], nfo_root: str) -> bytes:
|
|||
XML bytes
|
||||
"""
|
||||
xml_root = et.Element(nfo_root)
|
||||
for key, xml_elem in sorted(nfo_dict.items()):
|
||||
sorted_attr = dict(sorted(xml_elem.attributes.items()))
|
||||
sub_element = et.SubElement(xml_root, key, sorted_attr)
|
||||
sub_element.text = xml_elem.text
|
||||
for key, xml_elems in sorted(nfo_dict.items()):
|
||||
for xml_elem in xml_elems:
|
||||
sorted_attr = dict(sorted(xml_elem.attributes.items()))
|
||||
sub_element = et.SubElement(xml_root, key, sorted_attr)
|
||||
sub_element.text = xml_elem.text
|
||||
|
||||
et.indent(tree=xml_root, space=" ", level=0)
|
||||
return et.tostring(element=xml_root, encoding="utf-8", xml_declaration=True)
|
||||
|
|
|
|||
|
|
@ -7,11 +7,13 @@ from typing import Type
|
|||
from typing import TypeVar
|
||||
|
||||
from ytdl_sub.validators.strict_dict_validator import StrictDictValidator
|
||||
from ytdl_sub.validators.string_formatter_validators import DictFormatterValidator
|
||||
from ytdl_sub.validators.string_formatter_validators import DictFormatterValidator, \
|
||||
ListFormatterValidator, ListOverridesFormatterValidator
|
||||
from ytdl_sub.validators.string_formatter_validators import OverridesDictFormatterValidator
|
||||
from ytdl_sub.validators.string_formatter_validators import OverridesStringFormatterValidator
|
||||
from ytdl_sub.validators.string_formatter_validators import StringFormatterValidator
|
||||
from ytdl_sub.validators.validators import DictValidator
|
||||
from ytdl_sub.validators.validators import ListValidator
|
||||
|
||||
TStringFormatterValidator = TypeVar("TStringFormatterValidator", bound=StringFormatterValidator)
|
||||
TDictFormatterValidator = TypeVar("TDictFormatterValidator", bound=DictFormatterValidator)
|
||||
|
|
@ -55,34 +57,54 @@ class _NfoTagsWithAttributesValidator(
|
|||
class NfoTagsWithAttributesValidator(
|
||||
_NfoTagsWithAttributesValidator[StringFormatterValidator, DictFormatterValidator]
|
||||
):
|
||||
"""TagsWithAttributes for the entry NFO validator"""
|
||||
|
||||
formatter_validator = StringFormatterValidator
|
||||
dict_formatter_validator = DictFormatterValidator
|
||||
|
||||
|
||||
class NfoTagsWithAttributesListValidator(ListValidator[NfoTagsWithAttributesValidator]):
|
||||
"""TagsWithAttributes list for the entry NFO validator"""
|
||||
|
||||
_inner_list_type = NfoTagsWithAttributesValidator
|
||||
|
||||
|
||||
class NfoOverrideTagsWithAttributesValidator(
|
||||
_NfoTagsWithAttributesValidator[
|
||||
OverridesStringFormatterValidator, OverridesDictFormatterValidator
|
||||
]
|
||||
):
|
||||
"""TagsWithAttributes for the output directory NFO validator"""
|
||||
|
||||
formatter_validator = OverridesStringFormatterValidator
|
||||
dict_formatter_validator = OverridesDictFormatterValidator
|
||||
|
||||
|
||||
TNfoTagsWithAttributesValidator = _NfoTagsWithAttributesValidator[
|
||||
TStringFormatterValidator, TDictFormatterValidator
|
||||
]
|
||||
class NfoOverrideTagsWithAttributesListValidator(
|
||||
ListValidator[NfoOverrideTagsWithAttributesValidator]
|
||||
):
|
||||
"""TagsWithAttributes list for the output directory NFO validator"""
|
||||
_inner_list_type = NfoOverrideTagsWithAttributesValidator
|
||||
|
||||
|
||||
# Generic TagsWithAttribute to use for SharedNfoTagsValidator
|
||||
TNfoTagsWithAttributesValidator = _NfoTagsWithAttributesValidator[TStringFormatterValidator, TDictFormatterValidator]
|
||||
|
||||
# List validators
|
||||
TNfoTagsWithAttributesListValidator = ListValidator[TNfoTagsWithAttributesValidator]
|
||||
TNfoTagsListValidator = ListValidator[TStringFormatterValidator]
|
||||
|
||||
|
||||
class SharedNfoTagsValidator(
|
||||
DictValidator, Generic[TStringFormatterValidator, TDictFormatterValidator], ABC
|
||||
DictValidator, ABC
|
||||
):
|
||||
|
||||
_tags_with_attributes_validator: Type[TNfoTagsWithAttributesValidator]
|
||||
_tags_validator: Type[TNfoTagsListValidator]
|
||||
_tags_with_attributes_validator: Type[TNfoTagsWithAttributesListValidator]
|
||||
|
||||
def __init__(self, name, value):
|
||||
super().__init__(name, value)
|
||||
|
||||
self._string_tags: Dict[str, List[StringFormatterValidator]] = defaultdict(list)
|
||||
self._string_tags: Dict[str, List[TStringFormatterValidator]] = defaultdict(list)
|
||||
self._attribute_tags: Dict[str, List[TNfoTagsWithAttributesValidator]] = defaultdict(list)
|
||||
|
||||
for key, tag_value in self._dict.items():
|
||||
|
|
@ -93,15 +115,17 @@ class SharedNfoTagsValidator(
|
|||
# iterate each list, validate accordingly if it is a string tag or attribute tag
|
||||
for tag_value_i in tag_value:
|
||||
if isinstance(tag_value_i, str):
|
||||
self._string_tags[key].append(
|
||||
self._string_tags[key].extend(
|
||||
self._validate_key(
|
||||
key=key,
|
||||
validator=self._tags_with_attributes_validator.formatter_validator,
|
||||
)
|
||||
validator=self._tags_validator,
|
||||
).list
|
||||
)
|
||||
elif isinstance(tag_value_i, dict):
|
||||
self._attribute_tags[key].append(
|
||||
self._validate_key(key=key, validator=self._tags_with_attributes_validator)
|
||||
self._attribute_tags[key].extend(
|
||||
self._validate_key(
|
||||
key=key, validator=self._tags_with_attributes_validator
|
||||
).list
|
||||
)
|
||||
else:
|
||||
raise self._validation_exception(
|
||||
|
|
@ -109,7 +133,7 @@ class SharedNfoTagsValidator(
|
|||
)
|
||||
|
||||
@property
|
||||
def string_tags(self) -> Dict[str, List[StringFormatterValidator]]:
|
||||
def string_tags(self) -> Dict[str, List[TStringFormatterValidator]]:
|
||||
"""
|
||||
Returns
|
||||
-------
|
||||
|
|
@ -127,11 +151,11 @@ class SharedNfoTagsValidator(
|
|||
return self._attribute_tags
|
||||
|
||||
|
||||
class NfoTagsValidator(SharedNfoTagsValidator[StringFormatterValidator, DictFormatterValidator]):
|
||||
_tags_with_attributes_validator = NfoTagsWithAttributesValidator
|
||||
class NfoTagsValidator(SharedNfoTagsValidator):
|
||||
_tags_validator = ListFormatterValidator
|
||||
_tags_with_attributes_validator = NfoTagsWithAttributesListValidator
|
||||
|
||||
|
||||
class NfoOverrideTagsValidator(
|
||||
SharedNfoTagsValidator[OverridesStringFormatterValidator, OverridesDictFormatterValidator]
|
||||
):
|
||||
_tags_with_attributes_validator = NfoOverrideTagsWithAttributesValidator
|
||||
class NfoOverrideTagsValidator(SharedNfoTagsValidator):
|
||||
_tags_validator = ListOverridesFormatterValidator
|
||||
_tags_with_attributes_validator = NfoOverrideTagsWithAttributesListValidator
|
||||
|
|
|
|||
|
|
@ -209,6 +209,9 @@ class ListFormatterValidator(ListValidator[StringFormatterValidator]):
|
|||
_inner_list_type = StringFormatterValidator
|
||||
|
||||
|
||||
class ListOverridesFormatterValidator(ListValidator[StringFormatterValidator]):
|
||||
_inner_list_type = OverridesStringFormatterValidator
|
||||
|
||||
class DictFormatterValidator(LiteralDictValidator):
|
||||
"""
|
||||
A dict made up of
|
||||
|
|
@ -241,4 +244,3 @@ class OverridesDictFormatterValidator(DictFormatterValidator):
|
|||
"""
|
||||
|
||||
_key_validator = OverridesStringFormatterValidator
|
||||
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ def subscription_dict(output_directory):
|
|||
"attributes": {"🎸?": "value\nnewlines 🎸"},
|
||||
"tag": "the \n tag 2 🎸🎸",
|
||||
},
|
||||
]
|
||||
],
|
||||
},
|
||||
},
|
||||
"output_directory_nfo_tags": {
|
||||
|
|
@ -55,7 +55,7 @@ def subscription_dict(output_directory):
|
|||
"attributes": {"🎸?": "value\nnewlines 🎸"},
|
||||
"tag": "the \n tag 2 🎸🎸",
|
||||
},
|
||||
]
|
||||
],
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
@ -82,7 +82,7 @@ class TestNfoTagsPlugins:
|
|||
output_directory=output_directory,
|
||||
transaction_log=transaction_log,
|
||||
transaction_log_summary_file_name=f"plugins/nfo_tags/{transaction_log_file_name}",
|
||||
regenerate_transaction_log=True
|
||||
regenerate_transaction_log=True,
|
||||
)
|
||||
|
||||
def test_source_variable_in_output_directory_nfo_tags_errors(
|
||||
|
|
|
|||
|
|
@ -5,29 +5,79 @@ Rick Beato - Can you hear the difference? 🎸🔥 #shorts.3gp
|
|||
Rick Beato - Can you hear the difference? 🎸🔥 #shorts.info.json
|
||||
Rick Beato - Can you hear the difference? 🎸🔥 #shorts.nfo
|
||||
NFO tags:
|
||||
musicvideo:
|
||||
album: Music Videos
|
||||
artist: Rick Beato
|
||||
kodi_safe_title 🎸: kodi_safe_value 🎸
|
||||
kodi_safe_title_with_attrs:
|
||||
attributes:
|
||||
🎸?:
|
||||
value
|
||||
newlines 🎸
|
||||
tag:
|
||||
the
|
||||
tag 🎸🎸
|
||||
title: Can you hear the difference? 🎸🔥 #shorts
|
||||
year: 2022
|
||||
album: Music Videos
|
||||
artist: Rick Beato
|
||||
- value 1 🎸
|
||||
- value 2 🎸
|
||||
- value 1 🎸
|
||||
- value 2 🎸
|
||||
🎸?:
|
||||
value
|
||||
newlines 🎸
|
||||
tag:
|
||||
the
|
||||
tag 1 🎸🎸
|
||||
🎸?:
|
||||
value
|
||||
newlines 🎸
|
||||
tag:
|
||||
the
|
||||
tag 2 🎸🎸
|
||||
🎸?:
|
||||
value
|
||||
newlines 🎸
|
||||
tag:
|
||||
the
|
||||
tag 1 🎸🎸
|
||||
🎸?:
|
||||
value
|
||||
newlines 🎸
|
||||
tag:
|
||||
the
|
||||
tag 2 🎸🎸
|
||||
kodi_safe_title 🎸: kodi_safe_value 🎸
|
||||
🎸?:
|
||||
value
|
||||
newlines 🎸
|
||||
tag:
|
||||
the
|
||||
tag 🎸🎸
|
||||
title: Can you hear the difference? 🎸🔥 #shorts
|
||||
year: 2022
|
||||
test.nfo
|
||||
NFO tags:
|
||||
kodi_safe_root 🎸:
|
||||
kodi_safe_title 🎸: kodi_safe_value 🎸
|
||||
kodi_safe_title_with_attrs:
|
||||
attributes:
|
||||
🎸?:
|
||||
value
|
||||
newlines 🎸
|
||||
tag:
|
||||
the
|
||||
tag 🎸🎸
|
||||
- value 1 🎸
|
||||
- value 2 🎸
|
||||
- value 1 🎸
|
||||
- value 2 🎸
|
||||
🎸?:
|
||||
value
|
||||
newlines 🎸
|
||||
tag:
|
||||
the
|
||||
tag 1 🎸🎸
|
||||
🎸?:
|
||||
value
|
||||
newlines 🎸
|
||||
tag:
|
||||
the
|
||||
tag 2 🎸🎸
|
||||
🎸?:
|
||||
value
|
||||
newlines 🎸
|
||||
tag:
|
||||
the
|
||||
tag 1 🎸🎸
|
||||
🎸?:
|
||||
value
|
||||
newlines 🎸
|
||||
tag:
|
||||
the
|
||||
tag 2 🎸🎸
|
||||
kodi_safe_title 🎸: kodi_safe_value 🎸
|
||||
🎸?:
|
||||
value
|
||||
newlines 🎸
|
||||
tag:
|
||||
the
|
||||
tag 🎸🎸
|
||||
|
|
@ -8,26 +8,74 @@ Rick Beato - Can you hear the difference? 🎸🔥 #shorts.nfo
|
|||
musicvideo:
|
||||
album: Music Videos
|
||||
artist: Rick Beato
|
||||
kodi_safe_multi_title □:
|
||||
- value 1 □
|
||||
- value 2 □
|
||||
- value 1 □
|
||||
- value 2 □
|
||||
kodi_safe_multi_title_with_attrs:
|
||||
- attributes:
|
||||
□?: 'value
|
||||
|
||||
newlines □'
|
||||
tag: "the \n tag 1 □□"
|
||||
- attributes:
|
||||
□?: 'value
|
||||
|
||||
newlines □'
|
||||
tag: "the \n tag 2 □□"
|
||||
- attributes:
|
||||
□?: 'value
|
||||
|
||||
newlines □'
|
||||
tag: "the \n tag 1 □□"
|
||||
- attributes:
|
||||
□?: 'value
|
||||
|
||||
newlines □'
|
||||
tag: "the \n tag 2 □□"
|
||||
kodi_safe_title □: kodi_safe_value □
|
||||
kodi_safe_title_with_attrs:
|
||||
attributes:
|
||||
□?:
|
||||
value
|
||||
newlines □
|
||||
tag:
|
||||
the
|
||||
tag □□
|
||||
title: Can you hear the difference? □□ #shorts
|
||||
year: 2022
|
||||
□?: 'value
|
||||
|
||||
newlines □'
|
||||
tag: "the \n tag □□"
|
||||
title: 'Can you hear the difference? □□ #shorts'
|
||||
year: '2022'
|
||||
test.nfo
|
||||
NFO tags:
|
||||
kodi_safe_root □:
|
||||
kodi_safe_multi_title □:
|
||||
- value 1 □
|
||||
- value 2 □
|
||||
- value 1 □
|
||||
- value 2 □
|
||||
kodi_safe_multi_title_with_attrs:
|
||||
- attributes:
|
||||
□?: 'value
|
||||
|
||||
newlines □'
|
||||
tag: "the \n tag 1 □□"
|
||||
- attributes:
|
||||
□?: 'value
|
||||
|
||||
newlines □'
|
||||
tag: "the \n tag 2 □□"
|
||||
- attributes:
|
||||
□?: 'value
|
||||
|
||||
newlines □'
|
||||
tag: "the \n tag 1 □□"
|
||||
- attributes:
|
||||
□?: 'value
|
||||
|
||||
newlines □'
|
||||
tag: "the \n tag 2 □□"
|
||||
kodi_safe_title □: kodi_safe_value □
|
||||
kodi_safe_title_with_attrs:
|
||||
attributes:
|
||||
□?:
|
||||
value
|
||||
newlines □
|
||||
tag:
|
||||
the
|
||||
tag □□
|
||||
□?: 'value
|
||||
|
||||
newlines □'
|
||||
tag: "the \n tag □□"
|
||||
Loading…
Reference in a new issue